Compare commits

...

208 Commits

Author SHA1 Message Date
PJB3005
bdeb714070 Version: 267.2.3 2025-12-02 00:56:58 +01:00
PJB3005
500306cbb3 Fix NetBitArraySerializer compatibility.
Apparently NetSerializer treats IDynamicTypeSerializer and IStaticTypeSerializer differently for sealed types??

(cherry-picked from 6bbeaeeba6, without the test changes)

(cherry picked from commit d187018834dfa1cdead9ce5a7b72c38b07f8c81f)
2025-12-02 00:56:58 +01:00
PJB3005
6018f2fb12 Version: 267.2.2 2025-12-01 16:05:20 +01:00
PJB3005
52c69fc293 Backport BitArray .NET 10 serializer fix
83ad6042a7 & b267cd6fb4

Does not include test code to avoid risking merge conflicts.

(cherry picked from commit 415585a30d74fcae61f581808220a7aaeca3eaf5)
2025-12-01 16:05:19 +01:00
PJB3005
4dc17f3aca Version: 267.2.1 2025-09-26 13:40:39 +02:00
PJB3005
d22280f177 Validate that content assemblies have a limited list of names.
Also, only read assemblies once from disk

(cherry picked from commit 443a8dfca65be7d60c4bd46181b4c749b4756114)
2025-09-26 13:40:38 +02:00
ElectroJr
9e8f7092ea Version: 267.2.0 2025-09-26 20:03:34 +12:00
pathetic meowmeow
de188cc773 Defer PredictedQueueDel detachment on the client (#6154)
* Defer PredictedQueueDel detachment on the client

* release notes

* fixes

* I love ambiguous implicit casts

* avoid deferral issues

---------

Co-authored-by: ElectroJr <leonsfriedrich@gmail.com>
2025-09-26 19:13:14 +12:00
DrSmugleaf
784a02c0e7 Fix rga and mapfile validators breaking because the PYYaml author published a broken version (#6230)
* Version: 162.2.1

* Fix rga and mapfile validators breaking because the PYYaml author published a broken version

* Revert "Version: 162.2.1"

This reverts commit 9b7f4d48cf.
2025-09-26 14:04:21 +12:00
PJB3005
c8db7f98db Move IResourceManager.GetContentRoots() to be internal, make it return strings instead
Previous API shouldn't have been content-accessible. It also returned OS paths over ResPath which is incorrect.
2025-09-23 15:07:58 +02:00
DrSmugleaf
318c37e686 Fix CollectionExtensions.TryGetValue erroring for indexes under 0 (#6222)
* Version: 162.2.1

* Fix CollectionExtensions.TryGetValue erroring for indexes under 0

* Revert "Version: 162.2.1"

This reverts commit 9b7f4d48cf.

* release notes

---------

Co-authored-by: ElectroJr <leonsfriedrich@gmail.com>
2025-09-23 16:04:09 +12:00
Leon Friedrich
524be86449 Add new DetachEntity overload (#6217) 2025-09-23 15:47:29 +12:00
Leon Friedrich
ddeb78accd Make toolshed's CommandImplementationAttribute optional (#6218) 2025-09-23 15:47:21 +12:00
Leon Friedrich
585e847818 Fix warnings (#6224)
* Fix warnings

* remove usings
2025-09-23 15:41:08 +12:00
OnsenCapy
ac3cb4dc2a SpriteComponent: allow animations to optionally stop instead of looping (#6210)
* Loop false

* Removed goobstation comments

* Addressed changes

* Add layer field, set auto animated

* release notes

---------

Co-authored-by: ElectroJr <leonsfriedrich@gmail.com>
2025-09-21 14:36:07 +12:00
PJB3005
c06ca39009 Version: 267.1.0 2025-09-19 01:24:02 +02:00
PJB3005
06b11a51f1 Update release notes
That's a lotta stuff.

Decided to try to categorize things this time. See how this works out going forward.
2025-09-19 01:22:36 +02:00
DrSmugleaf
4938a159d4 Change PhysicsSystem.Island.FinalisePositions to TryComp transform component (#6135)
* Change PhysicsSystem.Island.FinalisePositions to TryComp transform component

* Add comment

---------

Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
2025-09-18 17:58:54 +02:00
Kara
27f2e270ce Fix compat mode floats in light attenuation + release notes (#6209)
* Fix compat mode float shit

* release notes
2025-09-18 17:44:02 +02:00
Kara
94e60e0b10 More accurate & controllable pointlight attenuation (#6160)
* modify light attenuation function

* support for changing attenuation curve type + lots of docs

* this is what i defaulted to typing in a prototype, so i guess it should just be this instead

* Allow a continuous range of values between inverse and inversequadratic rather than two set curves

* calc is slang for calculator

* fix

* oops committed it at 1 while testing i think, values are balanced for 0
2025-09-18 16:58:35 +02:00
slarticodefast
c6863033a5 fix PlacementManager.CurrentMousePosition during integration tests (#6208)
* fix CurrentMousePosition

* shorter

* rerun test
2025-09-18 16:38:39 +02:00
metalgearsloth
917878d05f Autocomplete more map commands (#5797)
* Autocomplete more map commands

Also added some extra helper features.

* Finish

* Fix bug, avoid IocResolves

* grid is grid

* file filename clash

* turn hint into option

* a

---------

Co-authored-by: ElectroJr <leonsfriedrich@gmail.com>
2025-09-18 12:38:17 +10:00
Leon Friedrich
10e4766809 Try fix UI state debug assert (#6191)
* Try fix UI state debug assert

* comment
2025-09-18 00:31:57 +02:00
Leon Friedrich
abd5149245 Improve map serialization error logging & exception tolerance (#6188)
* Improve map serialization error logging

* Prevent remove children of erroring entities

* better logging

* Improve error tolerance

* Even more exception tolerance

* missing !

* Improve handling of category errors

Helps prevents weird bugs that arise due to deleting un-initialized entities

* release notes

* Typo fix

---------

Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>
2025-09-17 21:44:56 +02:00
Leon Friedrich
912b6da20a Fix serialization of data-records with get-only properties (#6204)
* Fix serialization of data-records with get-only properties

* even less nesting

* asserts
2025-09-17 19:11:12 +02:00
eoineoineoin
94fe0b7721 Fix bug wrapping wide utf16 characters; add documentation for function (#6194) 2025-09-17 13:31:07 +02:00
PJB3005
9fac1e78fb Make ACZ status host explicitly respond with UTF-8 charset
See 0b2b814e4f
2025-09-17 12:55:43 +02:00
PJB3005
6697b76683 Fix download_manifest_file.py to always decode as UTF-8
See 0b2b814e4f
2025-09-17 12:52:13 +02:00
Andi Lilaj
b2ab247b5b Created Debug Version Panel and Version Info Printer similar to DebugSystemPanel (#5624)
* Created Debug Version Panel and Version Info Printer similar to DebugSystemPanel

* dependency injection

* remove VersionInformationPrinter

* Fix sorting
2025-09-17 17:32:25 +10:00
deltanedas
7411ae8138 replace AnchorEntity debug assert with a log (#5508)
* replace AnchorEntity debug assert with a log

* unfuck cefglue

* no!

---------

Co-authored-by: deltanedas <@deltanedas:kde.org>
Co-authored-by: ElectroJr <leonsfriedrich@gmail.com>
2025-09-17 17:13:18 +10:00
slarticodefast
d398e3a75b Improve error logging in SharedAudioSystem (#6202) 2025-09-17 16:56:21 +10:00
DrSmugleaf
a5047224bb Make net.interp and net.buffer_size CVars CLIENT, REPLICATED instead of CLIENTONLY (#6196)
Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
2025-09-17 12:16:12 +10:00
PJB3005
058821c08b Make sending net messages while disconnected on client log an error. 2025-09-16 18:59:24 +02:00
PJB3005
0c691b061d Don't send client replicated CVars when not connected to a server 2025-09-16 18:59:23 +02:00
Skye
51bbc5dc45 Fix resource loading on non-Windows platforms (#6201) 2025-09-16 11:10:30 +02:00
gus
2d3522e752 Allow multiple clients running WebView on a singular machine. (#5947)
* Multiple CEF instances on a single machine

* remove unused

* meh implementation

* cefextension

* me when partials, race conditions and extractiion of methods exists

* Change remote debugging handling

It now defaults to 9222 again. This doesn't seem to cause any issues when launching 3 clients. The debug port can be reconfigured via CVar if desired.

Also disabled debugging by default outside dev builds.

* Lower MaxAttempts to 15

100 was way too much and gave me anxiety idk.

* Fix non-TOOLS default of remote debug port

* Rewrite locking implementation.

It is much smaller, less complicated and probably more robust too. Should be fully atomic (the previous one wasn't).

* Undo unnecessary style changes

---------

Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
2025-09-16 02:19:59 +02:00
PJB3005
d4f265c314 Fix incorrect path combine in DirLoader and WritableDirProvider
This (and the other couple past commits) reported by Elelzedel.
2025-09-14 14:49:14 +02:00
PJB3005
7654d38612 Move CEF cache out of data directory
Don't want content messing with this...
2025-09-14 14:49:14 +02:00
PJB3005
cdcc255123 Make Robust.Client.WebView.Cef.Program internal. 2025-09-14 14:49:14 +02:00
PJB3005
2f56a6a110 Update SpaceWizards.NFluidSynth to 0.2.2 2025-09-14 14:49:13 +02:00
PJB3005
16fc48cef2 Hide IWritableDirProvider.RootDir on client
This shouldn't be exposed.
2025-09-14 14:49:13 +02:00
āda
5cecbb2cff Fix TransformBounds(Matrix3x2 , Box2Rotated) (#6171)
* jouneys-end

* test

* more tests

* test overkill

* i'm tired

* rip it out

* Keep method but mark it as obsolete

* Release notes

* grammar

---------

Co-authored-by: iaada <iaada@users.noreply.github.com>
Co-authored-by: ElectroJr <leonsfriedrich@gmail.com>
2025-09-13 15:11:59 +10:00
PJB3005
6115d6d5cc Give secondary window rendertextures names. 2025-09-13 01:21:22 +02:00
PJB3005
60d26be139 Add devwindow tab listing render targets
Copy TableContainer from content into engine. It's internal though.

Add various stuff to Clyde to allow the UI to access it. Includes a new IClydeInternal.RenderNow() which seems to not completely explode in my face.
2025-09-13 01:21:22 +02:00
ShadowCommander
186392ea80 Refactor grid or map methods (#5124)
* Rename TryGetMapOrGridCoordinates to make it clearer it gets grid first

* Add terminating or deleted checks to TryGetGridOrMapCoordinates

* Add comment to check if TerminatingOrDeleted check is necessary

* Reorganize AttachToGridOrMap to match TryGetGridOrMapCoordinates

* Move validation to method

* Replace internals with TryGetGridOrMapCoordinates

* Explicitly set coordinates type

* Format

* Change name back for now

* Don't duplicate `TerminatingOrDeleted()` check

* Don't call `GetInvWorldMatrix` for the map

* Don't check `TerminatingOrDeleted(uid)` in `TryGetMapOrGridCoordinates()`

* Fix parenting to terminating grid

* Fix matrix error

---------

Co-authored-by: ElectroJr <leonsfriedrich@gmail.com>
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
2025-09-11 18:05:04 +10:00
PJB3005
ebe4538d4c Add viewport stuff for caching rendering resources properly.
Content nowadays has a bunch of Overlays that all cache IRenderTextures for various funny operations. These are all broken in the face of multiple viewports, as they need to be cached *per viewport*.

This commit adds an ID field & an event to allow content to properly handle these resources.

Also adds some debug commands
2025-09-07 00:38:55 +02:00
PJB3005
745d0e5532 Add WeakReference<T> to sandbox 2025-09-07 00:17:07 +02:00
PJB3005
d4f7e60432 dmetamem command is now behind #if TOOLS 2025-09-06 18:24:25 +02:00
IProduceWidgets
ced127c164 loadgrid no fail (#6169)
* yoink unneeded map check

* yarr

* command line feedback
2025-09-04 23:40:17 +02:00
Kara
f91bcb62b1 Add ability to specify easings for AnimationTrackProperty keyframes (#6180)
* Add ability to specify easing functions in `AnimationTrackProperty`

* should actually be per keyframe
2025-09-04 18:47:30 +02:00
Pieter-Jan Briers
5268a4a3f0 Move SDL3 binding to NuGet package (#6184)
I'm worried about the IDE performance overhead of the 20k lines of LibraryImport it generates into Robust.Client.

Also, this allows me to trim the binding, which saves a tiny amount of space from publishes. Always nice to have.
2025-09-04 18:44:49 +02:00
PJB3005
1f1e50539b Remove EngineFonts/ and Midi/ from server packaging
Unnecessary on server
2025-09-04 17:33:40 +02:00
PJB3005
ea3132bbba Add asset pass to drop all files from the Audio/ directory
Fixes #6183

This drops MidiCustom and attribution YAML files. Other audio files were already handled by the audio metadata pass.
2025-09-04 17:33:14 +02:00
Kyoth25f
67ccaec418 Add UnicodeCategory to sandbox (#6181) 2025-09-04 11:30:53 +02:00
Kara
40b70e9447 Fix predicted audio having incorrect position & occlusion until the next tick (#6178) 2025-09-03 00:20:42 +02:00
Wachtel
7d37db9ce0 CleanContainter Del to PredictedDel (#6174) 2025-09-02 23:50:21 +02:00
Kara
dd8688df3d Fix ProcessStream using the wrong entity (#6177) 2025-09-02 22:29:11 +02:00
DrSmugleaf
b02c53c6ad Fix OutputPanel.SetMessage causing the panel to bounce if setting a message other than the last one (#6163) 2025-09-02 22:10:09 +02:00
PJB3005
38d3b83818 Add display.max_fps CVar.
Applies when vsync is not enabled.

Had to shuffle stuff around to GameController since it involves the game loop.

The implementation isn't great and undershoots the target FPS value (because the OS overshoots the desired sleep value). I tried using SDL_DelayPrecise too but this causes significantly increased CPU usage probably because it spinwaits and all that nonsense, so I decided against it.

I don't know why I bothered to do this. I just got the idea in my head. Kinda feels like a waste of time, but there's no point not committing it at this point.
2025-09-02 22:05:28 +02:00
Pieter-Jan Briers
f02cd0083a Make SpriteSpecifier.Texture fail validation if it contains ".rsi" (#6155)
No pointing to PNGs inside RSIs.
2025-09-02 22:05:13 +02:00
PJB3005
c2c8af16d0 Fix framerate dependent UI animations 2025-08-30 17:37:36 +02:00
PJB3005
b61003e2a0 Fix DebugConsole completions in devwindow
PopupContainer now supports an AltOriginUpProperty so that the completion popup doesn't overlap the input bar.
2025-08-30 17:21:15 +02:00
John
fb0ec52f8c Fix TimeSpan overflow on Read (#6170)
A lot of areas use TimeSpan.MaxValue but when saved and read the current time is added which results in an overflow.
A check is now performed to prevent this.
2025-08-30 00:59:39 +02:00
PJB3005
fee79d8aa5 Make popups/modals in secondary windows work
We were relying on a global PopupRoot & ModalRoot, which only existed in the main window. This means things like OptionButton would pop out on the *main* window when put on secondary windows.

These two roots are now on the UIRoot instead. WindowRoot needs to have a function called to create these if you're using it manually, OSWindow supports it automatically.
2025-08-30 00:58:52 +02:00
PJB3005
4508105412 Make devwindow & uitest2 use OSWindow 2025-08-29 01:07:09 +02:00
PJB3005
a0ebb290e2 Add WrapContainer control
Lays items out sequentially, wrapping them onto different rows/columns if they stop fitting.

Has multiple options and should be very useful, in both content and engine.

Uses the new Axis system to implement layout on 4 axis, re-uses BoxContainer's code so BoxContainer also got a mild refactor.
2025-08-29 00:41:21 +02:00
PJB3005
d45497e53b Add Axis helper types to make UI layout code generic over multiple axis. 2025-08-29 00:16:35 +02:00
PJB3005
ff8dd021c3 Make uitest (not uitest2) also support tab parameter. 2025-08-29 00:14:35 +02:00
PJB3005
34a371ef1f Make OrderedChildCollection implement IReadOnlyList<T>
Implements indexing operation.
2025-08-27 19:39:54 +02:00
Amy
83109b08e9 Add hashing lib to sandbox (#6167)
* box  your sand

* I only need this one anyway
2025-08-25 22:12:07 +02:00
DrSmugleaf
3d7b83db05 Fix crash on startup with more than 255 CVars (#6157)
* Fix crash on startup with more than 255 CVars

* Oop
2025-08-25 00:40:52 +02:00
leonidussaks
41b2ee19a1 add check if state name is empty in rsi validator (#6145)
* add check if state name exists

* Apply suggestions from code review

---------

Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>
2025-08-20 18:49:45 +02:00
PJB3005
856cdb8a3d Version: 267.0.0 2025-08-19 00:36:36 +02:00
PJB3005
b783cd79be Release notes for next release 2025-08-19 00:22:27 +02:00
PJB3005
b5ba964f61 Update client publish script to remove more natives
You know I can probably tell the .NET SDK to not copy these, but figuring that out would be effort.
2025-08-19 00:22:18 +02:00
PJB3005
09676a1d9f Re-enable FreeBSD builds 2025-08-19 00:21:30 +02:00
PJB3005
6959f21927 Disable apphost when publishing client builds
Not used anyways.

Fixes FreeBSD builds.
2025-08-19 00:21:01 +02:00
PJB3005
26a1fb35b5 Fix zstd library load on Linux
Probably important
2025-08-18 23:53:28 +02:00
PJB3005
7fb3ce0e70 Guess we aren't having FreeBSD 2025-08-18 23:51:22 +02:00
PJB3005
5497b52100 Re-enable macOS CI
We should have the missing natives now

Fixes #5076
2025-08-18 22:56:42 +02:00
PJB3005
18b5f33080 Enable ARM64 RIDs for publish
Fixes #5830
2025-08-18 22:55:09 +02:00
PJB3005
30d3367c50 Enable SDL3 by default on ARM64
Enough to unblock releasing ARM64 engines
2025-08-18 22:53:27 +02:00
PJB3005
f6aabd1a22 Update NFluidsynth to 0.2.1
MacOS correct library name loading. Yay
2025-08-18 22:42:21 +02:00
PJB3005
6d229a3eb2 Use OpenAL Soft on macOS
Fixes #6148
2025-08-18 22:38:02 +02:00
PJB3005
da28bdbce5 Fix loading of SDL3 on Unix platforms
Didn't pass the assembly info so it wasn't using the proper resolution system.
2025-08-18 22:30:46 +02:00
PJB3005
0181988225 Update native dependencies
Holy shit
2025-08-18 22:20:17 +02:00
Quantum-cross
fb2ba7460a allow toolshed command spawn:in to work if the prototype doesn't have a PhysicsComponent (#6151) 2025-08-18 11:33:35 +02:00
Hannah Giovanna Dawson
b70d20a217 Update OpenTK and OpenTK.Audio.OpenAL to latest (#6107) 2025-08-18 11:32:19 +02:00
DrSmugleaf
ebc33df457 Make Toolshed ProtoId autocomplete use PrototypeIdsLimited instead of caching completions (#6146)
* Make Toolshed ProtoId autoclomplete use PrototypeIdsLimited instead of caching completions

* Add check for entity prototype
2025-08-17 16:46:20 +02:00
PJB3005
697af6771c Put ClientDllMap.cs behind #if fully 2025-08-17 16:39:35 +02:00
PJB3005
20706870da Add SDL3 to DLL map 2025-08-17 16:39:34 +02:00
PJB3005
372fa39228 Merge branch 'dont-skip-leg-day' 2025-08-17 16:27:20 +02:00
PJB3005
d6bfbe4f6f Disable ARM64 targets by default for now 2025-08-17 16:27:14 +02:00
PJB3005
54645b4adf Use fancy mac symbols for key names 2025-08-17 16:24:04 +02:00
PJB3005
7c16573f3e Force enable compat mode on Qualcomm Windows devices
Broken OpenGL driver.
2025-08-17 16:12:59 +02:00
PJB3005
8935b39987 Remove some unnecessary windows natives from client package
Saves like a megabyte. Oops.
2025-08-17 15:54:14 +02:00
PJB3005
388f8369a8 Trim sharpfont on publish
Saves like 100 KB. Wow.
2025-08-17 15:54:14 +02:00
PJB3005
217d889e36 Update to new SharpFont version 2025-08-17 15:54:13 +02:00
Pieter-Jan Briers
f243baccf2 Get CPU model on Linux ARM64
Uses /proc/cpuinfo
2025-08-16 14:22:46 +02:00
PJB3005
790f42ea70 Try to load zstd as libzstd.1.dylib on macOS
This is the correct name for the dynamic library.

We can make this change without breaking old engine versions, as the launcher overrides the import resolver for zstd.
2025-08-16 14:09:53 +02:00
PJB3005
a5fcf122b8 Unhardcode XAML hot reload marker sln
Was previously hardcoded to just "Space Station14.sln"

Co-authored-by: kaylie <moony@hellomouse.net>
2025-08-16 14:09:53 +02:00
PJB3005
df2d6ab8c2 Detect CPU model name on Windows ARM
Uses WMI query
2025-08-16 14:09:53 +02:00
PGray
23c90c0c45 Serialization: Make null literal check culture-invariant (#6136)
Use StringComparison.OrdinalIgnoreCase instead of ToLower() to avoid culture-sensitive casing issues (e.g., Turkish-i) when detecting YAML null literals.
2025-08-12 13:28:30 +02:00
PJB3005
c69756e7f1 Merge remote-tracking branch 'upstream/master' into dont-skip-leg-day 2025-08-07 21:27:41 +02:00
PJB3005
07fbd5263c Run disconnect callbacks after removing channel from lists
Similar to the previous changes to player sessions, but now one layer lower.

Fixed ServerSendToAll from the relevant callbacks sending to a disconnected channel.
2025-08-07 00:44:06 +02:00
PJB3005
a1cdd60602 Version: 266.0.0 2025-08-06 16:14:11 +02:00
PJB3005
6fcaee91b6 Update release notes 2025-08-06 16:11:03 +02:00
slarticodefast
4d4f353680 Move ScaleVisuals to Content (and improve it) (#6096)
* împrove ScaleVisuals

* toolshedify

* fix

* rerun tests

* remove redundant code

* move to content
2025-08-06 14:33:35 +02:00
Hannah Giovanna Dawson
9f0dad80e4 Fix instrument pausing when outside PVS range (#6113) 2025-08-06 01:12:55 +02:00
Hannah Giovanna Dawson
c3f4b9bd67 Update MidiRenderer to use TryNoteOn and TryNoteOff (#6106)
Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
2025-08-06 01:02:15 +02:00
PJB3005
641411288f Update NFluidsynth to 0.2.0 2025-08-06 00:48:40 +02:00
Hannah Giovanna Dawson
1fb7d3e723 Minimum MIDI note volume (#6127) 2025-08-05 23:35:48 +02:00
PJB3005
8cbc5d4cd8 Raise PlayerStatusChanged after removing disconnected players
This makes it so players aren't in the Sessions list anymore when their status is Disconnected.

Fixes SS14's lobby code sending lobby status updates to the just-disconnected player, which logs an error with the recent net message changes.
2025-08-05 17:31:29 +02:00
PJB3005
b4863dcc38 Properly stop sending messages to disconnected channels.
Log errors, and fix the ChannelClosedException it caused.
2025-08-05 17:12:43 +02:00
Tayrtahn
e771530de2 Mark AutoGenerateComponentStateAttribute fields as readonly (redo) (#6129)
* Mark AutoGenerateComponentStateAttribute fields as readonly

* Remove no-longer-valid test case
2025-08-05 00:48:36 -04:00
Tayrtahn
ce3a5f6bfa Revert "Mark AutoGenerateComponentStateAttribute fields as readonly (#6126)" (#6128)
This reverts commit 1cd802640a.
2025-08-04 18:32:54 -04:00
Tayrtahn
1cd802640a Mark AutoGenerateComponentStateAttribute fields as readonly (#6126) 2025-08-04 18:25:09 -04:00
Perry Fraser
1983734e2d feat: add analyzer for correct AfterAutoHandleStateEvent usage (#6117)
* feat: add analyzer for AfterAutoHandleStateEvent

* fix: correct TestOf attribute

Oopsieeeee.

Also weird newline plus unused import.

* Rerun content tests

* refactor: use ==, not .Contains

* feat: make AttributeHelper.HasAttribute looser

* refactor: use AttributeHelper.HasAttribute

* perf: cache AutoGenStateAttribute's type

* refactor: more pattern matching

ElementAtOrDefault with constant arg is bad; just use positional
matching.
2025-08-04 18:22:06 -04:00
PJB3005
ea380056b4 Make BaseWindow dragging use new cursor shapes
Technology.
2025-08-04 16:34:38 +02:00
PJB3005
9c26fba308 Add uitest tab for mouse cursor shapes 2025-08-04 16:31:51 +02:00
PJB3005
3de48d7595 Add more SDL3-exclusive mouse cursor shapes
They just fall back on GLFW.
2025-08-04 16:31:43 +02:00
pathetic meowmeow
7a510298e1 Add the ability to scale ItemList icons (#6125) 2025-08-04 10:34:46 +02:00
PJB3005
046db645e9 Update ImageSharp to shut up vulnerability warnings
It's just a DoS attack so nothing too major (for us) but still annoying.
2025-08-02 21:52:01 +02:00
Tayrtahn
63e383bb17 Fix NotYamlSerializable analyzer ignoring nullable structs (#5934)
Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
2025-08-02 19:25:48 +02:00
Tayrtahn
e316649fd1 Add a Select button to ProtoId VV editor (#6097)
* Add a Select button to ProtoId VV editor

* Changelog

* Fix ftl string name

---------

Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
2025-08-02 19:08:24 +02:00
Fildrance
121b58ee9a feat: added generic method for getting component from ComponentRegistry (#6082)
* feat: added generic method for getting component from ComponentRegistry

* refactor: corrected xml-doc

* refactor: moved emthod to ComponentRegistry

* Fix release notes entry.

Wording + it was in the template.

* Fix doc comments

* Do not use inappropriate fallible cast.

---------

Co-authored-by: pa.pecherskij <pa.pecherskij@interfax.ru>
Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
2025-08-02 18:55:49 +02:00
PJB3005
d11f4bcc14 Fix release notes AGAIN 2025-08-02 18:53:29 +02:00
Fildrance
735ef09d42 Better unsubscription for multiple ConfigurationManager subscriptions (#6115)
* feat: new method or aggregating multiple config changed subscriptions into one disposable object or more slim unsubscribing code

* refactor: moved nested private class declaration to bottom of class

* refactor: reusing stateful object in tests is not smart

* fix: invalid code for forming new array during InvokeList.Remove call

* refactor: extracted new sub-multiple builder into configuration manager extensions

* refactor: remove unused code

* refactor: removed UnSubscribeActionsDelegates

* refactor: whitespaces and renaming

---------

Co-authored-by: pa.pecherskij <pa.pecherskij@interfax.ru>
2025-08-02 18:35:38 +02:00
Connor Huffine
772173cbaf Fix Non-solution Build: second attempt (#6098)
Downselect Robust.Client.Injectors to 'Debug' or 'Release' when built outside of solution context
2025-08-02 18:23:13 +02:00
PJB3005
4bd7aa16c1 Config no longer logs a warning when saved in integration test
Supersedes #6108

See https://github.com/space-wizards/space-station-14/issues/39196
2025-08-02 17:54:09 +02:00
pathetic meowmeow
bc4b4d3e6f Fix color naming crash (#6102) 2025-08-02 17:15:49 +02:00
Łukasz Mędrek
7d9a039252 add: Dictionary<T, TimeSpan> OnUnpaused generator (#6119)
* add: Dictionary<T, TimeSpan> OnUnpaused generator

* fix

* add: test

* Fix compiler warning from duplicate using

---------

Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
2025-08-02 16:57:43 +02:00
Zeneganto
857f9a540b Add localization support for TileSpawnWindow (#6121) 2025-08-02 16:53:14 +02:00
Perry Fraser
6ae332d543 fix: use the actual top anchor in SetMarginsPreset (#6118) 2025-08-01 13:10:26 +02:00
slarticodefast
f4786f2d90 add QueueDeleteMap to SharedMapSystem (#6116) 2025-07-31 11:13:28 -04:00
Pieter-Jan Briers
dcbe0505dc Revert "Add WeakEntityReference (#5577)" (#6112)
This reverts commit c3489d4ded.
2025-07-29 18:22:17 +02:00
Leon Friedrich
c3489d4ded Add WeakEntityReference (#5577)
* Add WeakEntityReference

* Use NetEntity

* release notes

* A

* Fix merge conflicts

* comments

* A

* Add network serialization test

* Add ToPrettyString support for WeakEntityReference?

* inheritdoc

* Add GetWeakReference methods

* Not-nullable too

* Make EntitySystem proxy method signatures match EntityManager

* Add TryGetEntity

* interface

* fix test

* De-ref GetWeakReference methods

---------

Co-authored-by: Tayrtahn <tayrtahn@gmail.com>
2025-07-29 11:12:49 -04:00
DrSmugleaf
8498634993 Add tests for immutable dicts and sets (#6109) 2025-07-29 01:17:29 +02:00
PJB3005
3d289fbd83 Update NetSerializer
Adds ImmutableDictionary and ImmutableHashSet serializers.
2025-07-29 01:17:19 +02:00
PJB3005
ebce0daa1b Merge remote-tracking branch 'upstream/master' into dont-skip-leg-day 2025-07-28 20:54:36 +02:00
PJB3005
bbbfcca303 Fix RSI preloading with .rsic files 2025-07-28 18:50:20 +02:00
DrSmugleaf
e195ac4ce6 Add ImmutableArrayExtensions All to sandbox.yml (#6110) 2025-07-28 16:05:05 +02:00
PJB3005
dc5cbd085b Enable RSI packing pass in RobustClientAssetGraph 2025-07-26 02:08:34 +02:00
PJB3005
c4dff678a9 Make .rsic packing in asset packaging work
Finishing what I started a couple years ago, the packaging system now packages .rsi files into single .rsic files. This means a single .rsi "file" (1 + N files) becomes a single file when packaged.

This should improve performance on game startup, downloading, etc etc. The total file count for SS14 goes down from 30,000 to 6,000 (with the previous change for merging text files too).

Mostly just involved shuffling a bunch of the RSI loading code around so that it can be re-used for this purpose nicely. The original prototype in the code was copy-pasted, which obviously couldn't be relied upon.

This does mean that if you're loading an RSI's interior PNG directly via a texture path, that PNG will now be unavailable on packaged builds. To avoid this, you can set "rsic": false in the meta.json, so that it gets left alone by the pass.
2025-07-26 01:51:17 +02:00
PJB3005
cd9616c87c Add new text file merge asset pass to RobustClientAssetGraph 2025-07-25 15:57:47 +02:00
PJB3005
d1c6c11755 Add asset pass to merge text files in directories.
This massively reduces the file count of published SS14 builds by a few thousand, by combining YAML prototypes and Fluent files in the same folder into one file.
2025-07-25 15:57:18 +02:00
PJB3005
1ebac7c894 Make prototype load ignore documents with empty values
This happens if you have a YAML file like this:

---
# commented prototype
---
# Real prototype
- type: bla

This case is generated by my (next commit) prototype file merger asset pass, and I don't see any harm in just skipping in this case.

Also improve the logging in general.
2025-07-25 15:54:58 +02:00
PJB3005
6b41be8901 Make AssetPassPackRsis not crap out due to ImageSharp errors.
Still just for testing.
2025-07-25 15:50:21 +02:00
PJB3005
51c929c8ec Version: 265.0.0 2025-07-23 01:47:41 +02:00
PJB3005
4863b09f0a Update release notes 2025-07-23 01:47:16 +02:00
Pieter-Jan Briers
cdd3afaa4c Remove redundant custom math types (#6078)
Vector3, Vector4, Matrix4, and Quaternion are now gone. Use System.Numerics instead.

This commit is just replacing usages, cleaning up using declarations, and moving over the (couple) helpers that are actually important.
2025-07-23 01:15:27 +02:00
slarticodefast
fee67b648c Allow AutoNetworkedField to work with inherited datafields (#6090)
* allow AutoNetworkedField to work for inherited datafields

* fix

* test fix

* typo

* Update Robust.UnitTesting/Shared/GameState/AutoNetworkingTest.cs

---------

Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>
2025-07-23 01:15:12 +02:00
Perry Fraser
0bf4123b8d feat: Add VV editor for tuples (#6065)
* feat: Add VV editor for tuples

* refactor: make tuple editor work in more cases

* feat: support other arity tuples

* fix: correct release notes entry

* refactor: use a new index selector for tuples

Also yank out silly unused code.

* fix: make all non-ValueTuples readonly

* refactor: spell out ValueTuple arities

,,,,,,,,,,,,,,,,,,,,,
2025-07-22 22:31:19 +02:00
Tayrtahn
9ea51432d1 Un-hardcode EntitySpawnWindow's placement mode dropdown (#5994)
* Find PlacementModes by attribute

* Let modes specify priority

* Make some PlacementManager dependencies public so Content can use them

* Space out the priorities a bit more

* xmldoc for attribute

* Revert "xmldoc for attribute"

This reverts commit f1f0299c55.

* Revert "Space out the priorities a bit more"

This reverts commit 549eac1eb2.

* Revert "Make some PlacementManager dependencies public so Content can use them"

This reverts commit c060f6cb2d.

* Revert "Let modes specify priority"

This reverts commit f113b40c7f.

* Revert "Find PlacementModes by attribute"

This reverts commit 27efb6c5cf.

* Completely redo to use PlacementManager's mode dictionary

* Backwards compat

* Cache the value of AllModeNames
2025-07-22 22:14:53 +02:00
Perry Fraser
c8da6f30a3 fix: resolve remaining ResolvedSoundSpecifier warnings (#6057)
Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
2025-07-22 22:08:31 +02:00
Tayrtahn
974c1e827d Change a bunch of static Loc.GetString calls to be properly resolved (#6010)
* Add Loc shortcut to LocalizedCommands

* Fix static methods in commands

---------

Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
2025-07-22 20:33:43 +02:00
Tayrtahn
3c48b24539 Cleanup prototype instantiation warnings in unit tests (#6058) 2025-07-22 20:32:49 +02:00
Fildrance
d8aefe5118 feat: now view-variable controls can be registered from content, or even dynamically added (#6077)
* feat: now view-variable controls can be registered from content, or even dynamically added

* refactor: whitespaces and xml-doc

* refactor: added changelog entry

* refactor: added methods for adding condition at start and at the end

* refactor: merged start/end methods, for IViewVariableControlFactory, improved changelog message

* refactor: replaced bool insertLast with InsertPosition enum

* refactor: reverse order of checks registration in ViewVariableControlFactory c-tor

---------

Co-authored-by: pa.pecherskij <pa.pecherskij@interfax.ru>
2025-07-22 19:48:12 +02:00
Tayrtahn
6d9a4719a9 Validate VV EntProtoId values (#6095) 2025-07-22 19:30:01 +02:00
Pieter-Jan Briers
893173ab17 Add workflow to build all configurations. (#6094)
* Add workflow to build all configurations.

Builds Debug, Tools, Release against Linux, Windows and MacOS TargetOS.

See https://github.com/space-wizards/RobustToolbox/pull/6069#issuecomment-3050607114

* Very epic, GitHub

* Whoops

* Actually add Tools configuration to sln
2025-07-22 19:21:21 +02:00
PJB3005
7c0f1b8031 Fix unused dependency warning outside DEBUG
Supersedes #6069
2025-07-22 18:10:49 +02:00
Tayrtahn
bb57f82811 More informative logging for PVS deleted/uninitialized entity errors (#6084) 2025-07-22 12:33:18 +02:00
Perry Fraser
0fc9b0acd0 Lint for prototype IDs with spaces (#6087)
* feat: lint for prototype IDs with spaces

* feat: also disallow periods

* Update RELEASE-NOTES.md

---------

Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2025-07-22 12:16:57 +02:00
Hannah Giovanna Dawson
f2b7f0d8d2 NoteOn actually being a NoteOff fix (#6092) 2025-07-22 12:10:58 +02:00
PJB3005
0ec189dece IPrototypeManager TryIndex changes
This effectively gracefully reverts 94f98073b0.

IPrototypeManager.TryIndex now no longer logs an error. This is done by adding a new overload without the logError parameter, so most existing code switches to it. The overload with the logError parameter is now obsolete.

As a replacement for defensive programming situations, the new Resolve() should be used instead.

IPrototypeManager.TryIndex() should not be used for handling IDs that should always be valid, only for handling user input and similar.

I also added a lot of docs.
2025-07-22 00:11:59 +02:00
PJB3005
c876eb1f4c Fix TextInputSetRect not accounting for pixel ratio properly.
Fixes it being positioned wrong on macOS.
2025-07-20 19:51:09 +02:00
PJB3005
1037fc735e Make SDL3 file dialogs have parent window.
Somehow needed to avoid causing it to block on macOS.
2025-07-19 18:45:14 +02:00
PJB3005
d5df765467 Package FreeBSD by default.
We won't officially support FreeBSD launcher builds, but this at least allows third-party launcher builds to have an engine to load properly.
2025-07-19 18:21:51 +02:00
PJB3005
93cf9f4227 Disable threaded window blit on macOS
Can probably do this on Linux too, but I didn't test that.

This feature is, fundamentally, a workaround to avoid WGL MakeCurrent() constantly breaking. The extra threading complexity is not a good thing on other platforms, so get rid of it.
2025-07-19 13:22:27 +02:00
PJB3005
d2977e2a63 Fix secondary window closing breaking rendering on macOS 2025-07-19 02:04:46 +02:00
PJB3005
a3f0ea19c4 Avoid WinBlit threads getting stuck forever when their window closes. 2025-07-19 00:42:31 +02:00
PJB3005
d9032b8757 Move some swapping code behind #ifdef
idk I just did this while debugging something and there's no harm committing it.
2025-07-19 00:42:00 +02:00
PJB3005
cba6e37f9f Fix SDL multiwindow freezing in some cases on macOS
Need SDL_HINT_MAC_OPENGL_ASYNC_DISPATCH set, apparently.
2025-07-19 00:41:27 +02:00
PJB3005
90ec9a80c9 Fix publishing script not passing TargetOS properly 2025-07-16 22:04:15 +02:00
PJB3005
7eaf2f590b Merge remote-tracking branch 'upstream/master' into dont-skip-leg-day 2025-07-15 15:23:23 +02:00
PJB3005
0439ea9893 Update packaging script to support ARM64 properly. 2025-07-15 15:14:40 +02:00
PJB3005
74aa8fa9ed Fix ParallelManager cutting off exception info
0% tested
2025-07-12 23:06:35 +02:00
Nemanja
ceeb002692 Mark ValidatePrototypeIdAttribute as obsolete (#6062) 2025-07-12 15:20:08 +02:00
portfiend
78d807b13c Refactor ColorSelectorSliders.cs, fix color slider event stack overflow (#6072)
* add: IColorSelectorStrategy class

defines some common variables and functions that depend on the slider type
my hope is to kill all the switch statements in here

* add: IColorSelectorStrategy FromColorData method

* add: RGB and HSV color slider strategies

* add: initialize ColorSelectorStrategy

* refactor: rename IColorSelectorStrategy to IColorSliderStrategy

this makes more sense i think

* refactor: nuke switch statements, use strategy in colorselectorsliders

* remove: remove GetSliderLabels in favor of strategy

* refactor: better abstraction for slider InputBox.ValueChanged

* refactor: rename OnColorSet to OnSliderValueChanged
more intuitive

* refactor: turn alpha slider max value into a const
no magic numbers

* tweak: make color sliders update channels individually

* fix: add braces around this callback

* tweak: move some variables around
i realize there's an Order to this so

* add: throw error if UpdateSlider is called with invalid value

* add: documentation comments to ColorSelectorSliders

* refactor: simplify UpdateSlider

* refactor: simplify GetColorValueDivisor

* fix: solved the color slider stack overflow

* fix: ensure _strategy is set before other functions use it

* tweak: rename Update to UpdateAllSliders
clearer

* fix: update slider colors on update
accidentally removed it and forgot to put it back

* remove: redundant comment
false alarm

* fix: prevent inputbox infinite event loop
this was also erroneously changing the color whenever the slider type changed

* fix: reviews part 1
- changed ColorSliderStrategy into abstract class
- fixed "strategy" typo
- changed NotImplementedException into ArgumentOutOfRangeException

* fix: make selector strategy static instances
2025-07-12 14:51:02 +02:00
PJB3005
5cd4c187bf Fix VV member group headers 2025-07-12 02:22:04 +02:00
Perry Fraser
fec477bf41 fix: remove unneeded delta in SharedAudioSystem (#6055) 2025-07-12 01:56:22 +02:00
PJB3005
9d00b1f093 Make VV KeyValuePair prop editor have a minimum width
Avoid buttons for references being squashed to zero width.
2025-07-12 01:47:49 +02:00
PJB3005
de0871d17b Fix VV handling of remote KVPair types
There was a bunch of complex code to analyze the full type string the server sent, except I have no idea what use this was. It's both incorrect (the type string isn't guaranteed to work if the remote .NET version is different) and unnecessary as PropertyFor already handles all the cases.
2025-07-12 01:47:49 +02:00
Perry Fraser
053c469cac fix: loosen random timespan debug assert (#6064)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2025-07-11 21:36:13 +02:00
Leon Friedrich
efa8975bc6 Fix pool manager conflicts (#6075) 2025-07-11 20:55:49 +02:00
Errant
4851e913b0 More TimespanSerializer improvements (#5910)
* improved public TryTimeSpan

* don't want any locale shenanigans or misconceptions with the input

* missed a test line

* also support capitalized time unit indicators

* Doesn't need to be nullable.

---------

Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
2025-07-11 20:37:39 +02:00
PJB3005
74a318c521 Allow content to skip certain paths in client/server resource copying
Intended so content can ignore the MapImages folder
2025-07-11 18:15:10 +02:00
pathetic meowmeow
e52a6bbbf2 Add OKLCH-based colour descriptions for colorblindness accessibility (#6067)
* Add OKLCH-based colour descriptions for colorblindness accessibility

* my docs so comment

* my feed so back
2025-07-11 15:40:41 +02:00
PJB3005
e169d6a5a2 Enforce integration instance idleness for more helper members
Also allow them to be accessed regardless if from the integration instance thread.
2025-07-10 16:29:16 +02:00
PJB3005
3634ee636b Pooled integration instances now get marked non-idle
Otherwise, pooled integration instances could behave differently from freshly-spawned ones, creating heisentests.
2025-07-10 16:26:56 +02:00
āda
2349728eab out of my element (#6074)
Co-authored-by: iaada <iaada@users.noreply.github.com>
2025-07-10 12:43:51 +02:00
PJB3005
777f02cadd Fix physics closure allocs and some avoidable struct copies 2025-07-10 12:33:17 +02:00
Myra
0fc6f2bce6 Config load fail is now an error instead of a warning (#6070)
* Config load fail is now an error instead of a warning

* Update RELEASE-NOTES.md

---------

Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>
2025-07-06 20:28:49 +02:00
Tayrtahn
dc3705e520 Add ForbidLiteral to IPrototypeManager methods (#6066)
* Add ForbidLiteral to IPrototypeManager methods

* Cleanup violations
2025-07-06 20:27:32 +02:00
Tayrtahn
01f71ca55a Remove prototype instantiation from AssetPassAudioMetadata (#6059) 2025-06-28 22:18:46 +02:00
PJB3005
c5e812836b Fix loading textures in root folder
Fixes #6052

Also clean up a warning while I'm at it.
2025-06-28 01:37:14 +02:00
PJB3005
56eda3ea92 Version: 264.0.0 2025-06-27 22:03:33 +02:00
Tayrtahn
9dffd36319 Use non-generic TryComp to get MetaDataComponent in DebugAnchoringSystem (#6051) 2025-06-27 20:38:10 +02:00
Tayrtahn
a45b72a1c5 IRobustCloneable and generator support (#5692)
* Add IRobustCloneable and check for it in compnet generator.

* Redo compnetgenerator support; add test

* Disconnect client at end of test

* Actually test for client entities

* Cleanup

* Cleanup 2
2025-06-27 20:37:43 +02:00
Perry Fraser
bd0579ed6d fix: apply scale when calculating sprite bounding box (#6046)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2025-06-26 23:23:57 +02:00
Pieter-Jan Briers
c73b54862e Add analyzers to detect some prototype misuse (#6048)
* Add analyzers to detect some prototype misuse

Detects people marking prototype as NetSerializable.

Detects people creating new prototype instances themselves.

* Update Robust.Analyzers/PrototypeNetSerializableAnalyzer.cs

Co-authored-by: Tayrtahn <tayrtahn@gmail.com>

---------

Co-authored-by: Tayrtahn <tayrtahn@gmail.com>
2025-06-26 22:24:23 +02:00
PJB3005
6436ff8040 Fix prototype manager Index exceptions
Index<T> was documented to throw KeyNotFoundException, but actually threw UnknownPrototypeException. Index(Type type, string id) threw KeyNotFoundException.

This has now been made consistent to be UnknownPrototypeException everywhere.
2025-06-26 16:58:35 +02:00
PJB3005
98313ae369 Update NetSerializer submodule
Makes it report where broken serialization types come from.
2025-06-26 16:58:21 +02:00
PJB3005
0e63391203 Add PrototypeManagerExt.Index that takes nullable ProtoId<T> 2025-06-26 16:52:18 +02:00
wixoa
261bfaeeb8 Add AlwaysActive to WebViewControl (#6047) 2025-06-25 21:50:07 +02:00
Tayrtahn
4017e1f57e Make some PlacementManager dependency fields public (#6044)
* Make some PlacementManager dependency fields public

* Revert "Make some PlacementManager dependency fields public"

This reverts commit 99fe37b502.

* Now part of IPlacementManager
2025-06-23 22:48:25 +02:00
lzk
e170bf1ad2 genetive case (#6045)
* dative

* slipped it

* slipped it twice

* 1

* Update _engine_lib.ftl
2025-06-23 22:47:50 +02:00
PJB3005
da0abd2535 Make functions static to avoid delegate allocations in DataDefinitionAnalyzer. 2025-06-22 13:48:50 +02:00
352 changed files with 8720 additions and 14888 deletions

View File

@@ -0,0 +1,34 @@
name: Build All Configurations
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
build:
strategy:
matrix:
targetOS: [Windows, Linux, MacOS]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4.2.2
with:
submodules: true
- name: Setup .NET
uses: actions/setup-dotnet@v4.1.0
with:
dotnet-version: 9.0.x
- name: Install dependencies
run: dotnet restore
- name: Build Debug
run: dotnet build --no-restore --configuration Debug /p:WarningsAsErrors=nullable /p:TargetOS=${{ matrix.targetOS }}
- name: Build Tools
run: dotnet build --no-restore --configuration Tools /p:WarningsAsErrors=nullable /p:TargetOS=${{ matrix.targetOS }}
- name: Build Release
run: dotnet build --no-restore --configuration Release /p:WarningsAsErrors=nullable /p:TargetOS=${{ matrix.targetOS }}

View File

@@ -10,7 +10,7 @@ jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, windows-latest] # , macos-latest] - temporarily disabled due to libfreetype.dll errors.
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}

View File

@@ -26,7 +26,7 @@ jobs:
dotnet-version: 9.0.x
- name: Package client
run: Tools/package_client_build.py -p windows mac linux
run: Tools/package_client_build.py
- name: Shuffle files around
run: |

View File

@@ -44,10 +44,11 @@
<PackageVersion Include="NUnit3TestAdapter" Version="4.6.0" />
<PackageVersion Include="Nett" Version="0.15.0" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.2" />
<PackageVersion Include="OpenTK.OpenAL" Version="4.7.7" />
<PackageVersion Include="OpenTK.Audio.OpenAL" Version="4.9.4" />
<PackageVersion Include="OpenToolkit.Graphics" Version="4.0.0-pre9.1" />
<PackageVersion Include="Pidgin" Version="3.3.0" />
<PackageVersion Include="Robust.Natives" Version="0.1.1" />
<PackageVersion Include="Robust.Natives" Version="0.2.1" />
<PackageVersion Include="Robust.Natives.Zstd" Version="0.1.0-zstd1.5.7" />
<PackageVersion Include="Robust.Natives.Cef" Version="131.3.5" />
<PackageVersion Include="Robust.Shared.AuthLib" Version="0.1.2" />
<PackageVersion Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.10" />
@@ -55,11 +56,14 @@
<PackageVersion Include="Serilog" Version="4.2.0" />
<PackageVersion Include="Serilog.Sinks.Loki" Version="4.0.0-beta3" />
<PackageVersion Include="SharpZstd.Interop" Version="1.5.2-beta2" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.7" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.11" />
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.1" />
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.1.1" />
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.0.2" />
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.2.2" />
<PackageVersion Include="SpaceWizards.Sdl" Version="1.0.0" />
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.1.0" />
<PackageVersion Include="SpaceWizards.Sodium" Version="0.2.1" />
<PackageVersion Include="libsodium" Version="1.0.20.1" />
<PackageVersion Include="System.Management" Version="9.0.8" />
<PackageVersion Include="TerraFX.Interop.Windows" Version="10.0.26100.1" />
<PackageVersion Include="TerraFX.Interop.Xlib" Version="6.4.0" />
<PackageVersion Include="VorbisPizza" Version="1.3.0" />

View File

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

View File

@@ -16,7 +16,10 @@
<ItemGroup>
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\Robust.Client.NameGenerator\Robust.Client.NameGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\Robust.Client.Injectors\Robust.Client.Injectors.csproj" ReferenceOutputAssembly="false"/>
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\Robust.Client.Injectors\Robust.Client.Injectors.csproj" ReferenceOutputAssembly="false">
<SetConfiguration Condition="'$(Configuration)' == 'DebugOpt'">Configuration=Debug</SetConfiguration>
<SetConfiguration Condition="'$(Configuration)' == 'Tools'">Configuration=Release</SetConfiguration>
</ProjectReference>
</ItemGroup>
<!-- XamlIL does not make use of special Robust configurations like DebugOpt. Convert these down. -->

View File

@@ -54,6 +54,312 @@ END TEMPLATE-->
*None yet*
## 267.2.3
## 267.2.2
## 267.2.1
## 267.2.0
### New features
* Sprites and Sprite layers have a new `Loop` data field that can be set to false to automatically pause animations once they have finished.
### Bugfixes
* Fixed `CollectionExtensions.TryGetValue` throwing an exception when given a negative list index.
* Fixed `EntityManager.PredictedQueueDeleteEntity()` not deferring changes for networked entities until the end of the tick.
* Fixed `EntityManager.IsQueuedForDeletion` not returning true foe entities getting deleted via `PredictedQueueDeleteEntity()`
### Other
* `IResourceManager.GetContentRoots()` has been obsoleted and returns no more results.
### Internal
* `IResourceManager.GetContentRoots()` has been replaced with a similar method on `IResourceManagerInternal`. This new method returns `string`s instead of `ResPath`s, and usage code has been updated to use these paths correctly.
## 267.1.0
### New features
* Animation:
* `AnimationTrackProperty.KeyFrame` can now have easings functions applied.
* Graphics:
* `PointLightComponent` now has two fields, `falloff` and `curveFactor`, for controlling light falloff and the shape of the light attenuation curve.
* `IClydeViewport` now has an `Id` and `ClearCachedResources` event. Together, these allow you to properly cache rendering resources per viewport.
* Miscellaneous:
* Added `display.max_fps` CVar.
* Added `IGameTiming.FrameStartTime`.
* Sandbox:
* Added `System.WeakReference<T>`.
* Added `SpaceWizards.Sodium.CryptoGenericHashBlake2B.Hash()`.
* Added `System.Globalization.UnicodeCategory`.
* Serialization:
* Added a new entity yaml deserialization option (`SerializationOptions.EntityExceptionBehaviour`) that can optionally make deserialization more exception tolerant.
* Tooling:
* `devwindow` now has a tab listing active `IRenderTarget`s, allowing insight into resource consumption.
* `loadgrid` now creates a map if passed an invalid map ID.
* Added game version information to F3 overlay.
* Added completions to more map commands.
* UI system:
* `Control.OrderedChildCollection` (gotten from `.Children`) now implements `IReadOnlyList<Control>`, allowing it to be indexed directly.
* Added `WrapContainer` control. This lays out multiple elements along an axis, wrapping them if there's not enough space. It comes with many options and can handle multiple axes.
* Popups/modals now work in secondary windows. This entails putting roots for these on each UI root.
* If you are not using `OSWindow` and are instead creating secondary windows manually, you need to call `WindowRoot.CreateRootControls()` manually for this to work.
* Added `Axis` enum, `IAxisImplementation` interface and axis implementations. These allow writing general-purpose UI layout code that can work on multiple axis at once.
* WebView:
* Added `web.remote_debug_port` CVar to change Chromium's remote debug port.
### Bugfixes
* Audio:
* Fix audio occlusion & velocity being calculated with the audio entity instead of the source entity.
* Bound UI:
* Try to fix an assert related to `UserInterfaceComponent` delta states.
* Configuration:
* The client no longer tries to send `CLIENT | REPLICATED` CVars when not connected to a server. This could cause test failures.
* Math:
* Fixed `Matrix3Helpers.TransformBounds()` returning an incorrect result. Now it effectively behaves like `Matrix3Helpers.TransformBox()` and has been marked as obsolete.
* Physics:
* Work around an undiagnosed crash processing entities without parents.
* Serialization:
* Fix `[DataRecord]`s with computed get-only properties.
* Resources:
* Fix some edge case broken path joining in `DirLoader` and `WritableDirProvider`.
* Tests:
* Fix `PlacementManager.CurrentMousePosition` in integration tests.
* UI system:
* Animations for the debug console and scrolling are no longer framerate dependent.
* Fix `OutputPanel.SetMessage` triggering a scrolling animation when editing messages other than the last one.
* Fix word wrapping with two-`char` runes in `RichTextLabel` and `OutputPanel`.
* WebView:
* Multiple clients with WebView can now run at the same time, thanks to better CEF cache management.
### Other
* Audio:
* Improved error logging for invalid file names in `SharedAudioSystem`.
* Configuration:
* Fix crash if more than 255 `REPLICATED` CVars exist. Also increased the max size of the CVar replication message.
* Entities:
* Transform:
* `AnchorEntity` logs instead of using an assert for invalid arguments.
* Containers:
* `SharedContainerSystem.CleanContainer` now uses `PredictedDel()` instead.
* Networking:
* The client now logs an error when attempting to send a network message without server connection. Previously, it would be silently dropped.
* `net.interp` and `net.buffer_size` CVars are now `REPLICATED`.
* Graphics:
* The function used for pointlight attenuation has been modified to be c1 continuous as opposed to simply c0 continuous, resulting in smoother boundary behavior.
* RSI validator no longer allows empty (`""`) state names.
* Packaging:
* Server packaging now excludes all files in the `Audio/` directory.
* Server packaging now excludes engine resources `EngineFonts/` and `Midi/`.
* ACZ explicitly specifies manifest charset as UTF-8.
* Serialization:
* `CurTime`-relative `TimeSpan` values that are `MaxValue` now deserialize without overflow.
* `SpriteSpecifier.Texture` will now fail to validate if the path is inside a `.rsi`. Use RSI sprite specifiers instead.
* Resources:
* `IWritableDirProvider.RootDir` is now null on clients.
* WebView:
* CEF cache is no longer in the content-accessible user data directory.
### Internal
* Added some debug commands for debugging viewport resource management: `vp_clear_all_cached` & `vp_test_finalize`
* `uitest` command now supports command argument for tab selection, like `uitest2`.
* Rewrote `BoxContainer` implementation to make use of new axis system.
* Moved `uitest2` and `devwindow` to use the `OSWindow` control.
* SDL3 binding has been moved to `SpaceWizards.Sdl` NuGet package.
* `dmetamem` command has been moved from `DEBUG` to `TOOLS`.
* Consolidate `AttachToGridOrMap` with `TryGetMapOrGridCoordinates`.
* Secondary window render targets have clear names specified.
* Updated `SpaceWizards.NFluidsynth` to `0.2.2`.
* `Robust.Client.WebView.Cef.Program` is now internal.
* `download_manifest_file.py` script in repo now always decodes as UTF-8 correctly.
* Added a new debug assert to game state processing.
## 267.0.0
### Breaking changes
* When a player disconnects, the relevant callbacks are now fired *after* removing the channel from `INetManager`.
### New features
* Engine builds are now published for ARM64 & FreeBSD.
* CPU model names are now detected on Windows & Linux ARM64.
* Toolshed's `spawn:in` command now works on entities without `Physics` component.
### Bugfixes
* SDL3 windowing backend fixes:
* Avoid macOS freezes with multiple windows.
* Fix macOS rendering breaking when closing secondary windows.
* File dialogs properly associate parent windows.
* Fix IME positions not working with UI scaling properly.
* Properly specify library names for loading native library.
* WinBit threads don't permanently stay stuck when their window closes.
* Checking for the "`null`" literal in serialization is now culture invariant.
### Other
* Compat mode on the client now defaults to on for Windows Snapdragon devices, to work around driver bugs.
* Update various libraries & natives. This enables out-of-the-box ARM64 support on all platforms and is a long-overdue modernization.
* Key name displays now use proper Unicode symbols for macOS ⌥ and ⌘.
* Automated CI for RobustToolbox runs on macOS again.
* Autocompletions for `ProtoId<T>` in Toolshed now use `PrototypeIdsLimited` instead of arbitrarily cutting out if more than 256 of a prototype exists.
## 266.0.0
### Breaking changes
* A new analyzer has been added that will error if you attempt to subscribe to `AfterAutoHandleStateEvent` on a
component that doesn't have the `AutoGenerateComponentState` attribute, or doesn't have the first argument of that
attribute set to `true`. In most cases you will want to set said argument to `true`.
* The fields on `AutoGenerateComponentStateAttribute` are now `readonly`. Setting these directly (instead of using the constructor arguments) never worked in the first place, so this change only catches existing programming errors.
* When a player disconnects, `ISharedPlayerManager.PlayerStatusChanged` is now fired *after* removing the session from the `Sessions` list.
* `.rsi` files are now compacted into individual `.rsic` files on packaging. This should significantly reduce file count & improve performance all over release builds, but breaks the ability to access `.png` files into RSIs directly. To avoid this, `"rsic": false` can be specified in the RSI's JSON metadata.
* The `scale` command has been removed, with the intent of it being moved to content instead.
### New features
* ViewVariables editors for `ProtoId` fields now have a Select button which opens a window listing all available prototypes of the appropriate type.
* added **IConfigurationManager**.*SubscribeMultiple* ext. method to provide simpler way to unsubscribe from multiple cvar at once
* Added `SharedMapSystem.QueueDeleteMap`, which deletes a map with the specified MapId in the next tick.
* Added generic version of `ComponentRegistry.TryGetComponent`.
* `AttributeHelper.HasAttribute` has had an overload's type signature loosened from `INamedTypeSymbol` to `ITypeSymbol`.
* Errors are now logged when sending messages to disconnected `INetChannel`s.
* Warnings are now logged if sending a message via Lidgren failed for some reason.
* `.yml` and `.ftl` files in the same directory are now concatenated onto each other, to reduce file count in packaged builds. This is done through the new `AssetPassMergeTextDirectories` pass.
* Added `System.Linq.ImmutableArrayExtensions` to sandbox.
* `ImmutableDictionary<TKey, TValue>` and `ImmutableHashSet<T>` can now be network serialized.
* `[AutoPausedField]` now works on fields of type `Dictionary<TKey, TimeSpan>`.
* `[NotYamlSerializable]` analyzer now detects nullable fields of the not-serializable type.
* `ItemList` items can now have a scale applied for the icon.
* Added new OS mouse cursor shapes for the SDL3 backend. These are not available on the GLFW backend.
* Added `IMidiRenderer.MinVolume` to scale the volume of MIDI notes.
* Added `SharedPhysicsSystem.ScaleFixtures`, to apply the physics-only changes of the prior `scale` command.
### Bugfixes
* `LayoutContainer.SetMarginsPreset` and `SetAnchorAndMarginPreset` now correctly use the provided control's top anchor when calculating the margins for its presets; it previously used the bottom anchor instead. This may result in a few UI differences, by a few pixels at most.
* `IConfigurationManager` no longer logs a warning when saving configuration in an integration test.
* Fixed impossible-to-source `ChannelClosedException`s when sending some net messages to disconnected `INetChannel`s.
* Fixed an edge case causing some color values to throw an error in `ColorNaming`.
* Fresh builds from specific projects should no longer cause errors related to `Robust.Client.Injectors` not being found.
* Stopped errors getting logged about `NoteOff` and `NoteOn` operations failing in MIDI.
* Fixed MIDI players not resuming properly when re-entering PVS range.
### Other
* Updated ImageSharp to 3.1.11 to stop the warning about a DoS vulnerability.
* Prototype YAML documents that are completely empty are now skipped by the prototype loader. Previously they would cause a load error for the whole file.
* `TileSpawnWindow` can now be localized.
* `BaseWindow` uses the new mouse cursor shapes for diagonal resizing.
* `NFluidsynth` has been updated to 0.2.0
### Internal
* Added `uitest` tab for standard mouse cursor shapes.
## 265.0.0
### Breaking changes
* More members in `IntegrationInstance` now enforce that the instance is idle before accessing it.
* `Prototype.ValidateDirectory` now requires that prototype IDs have no spaces or periods in them.
* `IPrototypeManager.TryIndex` no longer logs errors unless using the overload with an optional parameter. Use `Resolve()` instead if error logging is desired.
* `LocalizedCommands` now has a `Loc` property that refers to `LocalizationManager`. This can cause compile failures if you have static methods in child types that referenced static `Loc`.
* `[AutoGenerateComponentState]` now works on parent members for inherited classes. This can cause compile failures in certain formerly silently broken cases with overriden properties.
* `Vector3`, `Vector4`, `Quaternion`, and `Matrix4` have been removed from `Robust.Shared.Maths`. Use the `System.Numerics` types instead.
### New features
* `RobustClientPackaging.WriteClientResources()` and `RobustServerPackaging.WriteServerResources()` now have an overload taking in a set of things to ignore in the content resources directory.
* Added `IPrototypeManager.Resolve()`, which logs an error if the resolved prototype does not exist. This is effectively the previous (but not original) default behavior of `IPrototypeManager.TryIndex`.
* There's now a ViewVariables property editor for tuples.
* Added `ColorNaming` helper functions for getting textual descriptions of color values.
* Added Oklab/Oklch conversion functions for `Color`.
* `ColorSelectorSliders` now displays textual descriptions of color values.
* Added `TimeSpanExt.TryTimeSpan` to parse `TimeSpan`s with the `1.5h` format available in YAML.
* Added `ITestContextLike` and related classes to allow controlling pooled integration instances better.
* `EntProtoId` VV prop editors now don't allow setting invalid prototype IDs, inline with `ProtoId<T>`.
* Custom VV controls can now be registered using `IViewVariableControlFactory`.
* The entity spawn window now shows all placement modes registered with `IPlacementManager`.
* Added `VectorHelpers.InterpolateCubic` for `System.Numerics` `Vector3` and `Vector4`.
* Added deconstruct helpers for `System.Numerics` `Vector3` and `Vector4`.
### Bugfixes
* Pooled integration instances returned by `RobustIntegrationTest` are now treated as non-idle, for consistency with non-pooled startups.
* `SharedAudioSystem.SetState` no longer calls `DirtyField` on `PlaybackPosition`, an unnetworked field.
* Fix loading texture files from the root directory.
* Fix integration test pooling leaking non-reusable instances.
* Fix multiple bugs where VV displayed the wrong property editor for remote values.
* VV displays group headings again in member list.
* Fix a stack overflow that could occur with `ColorSelectorSliders`.
* `MidiRenderer` now properly handles `NoteOn` events with 0 velocity (which should actually be treated as `NoteOff` events).
### Other
* The debug assert for `RobustRandom.Next(TimeSpan, TimeSpan)` now allows for the two arguments to be equal.
* The configuration system will now report an error instead of warning if it fails to load the config file.
* Members in `IntegrationInstance` that enforce the instance is idle now always allow access from the instance's thread (e.g. from a callback).
* `IPrototypeManager` methods now have `[ForbidLiteral]` where appropriate.
* Performance improvements to physics system.
* `[ValidatePrototypeIdAttribute]` has been marked as obsolete.
* `ParallelManager` no longer cuts out exception information for caught job exceptions.
* Improved logging for PVS uninitialized/deleted entity errors.
### Internal
* General code & warning cleanup.
* Fix `VisibilityTest` being unreliable.
* `ColorSelectorSliders` has been internally refactored.
* Added CI workflows that test all RT build configurations.
## 264.0.0
### Breaking changes
* `IPrototypeManager.Index(Type kind, string id)` now throws `UnknownPrototypeException` instead of `KeyNotFoundException`, for consistency with `IPrototypeManager.Index<T>`.
### New features
* Types can now implement the new interface `IRobustCloneable<T>` to be cloned by the component state source generator.
* Added extra Roslyn Analyzers to detect some misuse of prototypes:
* Network serializing prototypes (tagging them with `[Serializable, NetSerializable]`).
* Constructing new instances of prototypes directly.
* Add `PrototypeManagerExt.Index` helper function that takes a nullable `ProtoId<T>`, returning null if the ID is null.
* Added an `AlwaysActive` field to `WebViewControl` to make a browser window active even when not in the UI tree.
* Made some common dependencies accessible through `IPlacementManager`.
* Added a new `GENITIVE()` localization helper function, which is useful for certain languages.
### Bugfixes
* Sprite scale is now correctly applied to sprite boundaries in `SpriteSystem.GetLocalBounds`.
* Fixed documentation for `IPrototypeManager.Index<T>` stating that `KeyNotFoundException` gets thrown, when in actuality `UnknownPrototypeException` gets thrown.
### Other
* More tiny optimizations to `DataDefinitionAnalyzer`.
* NetSerializer has been updated. On debug, it will now report *where* a type that can't be serialized is referenced from.
### Internal
* Minor internal code cleanup.
## 263.0.0
### Breaking changes

View File

@@ -21,7 +21,8 @@ zzzz-object-pronoun = { GENDER($ent) ->
}
# Used internally by the DAT-OBJ() function.
# Not used in en-US. Created for supporting other languages.
# Not used in en-US. Created to support other languages.
# (e.g., "to him," "for her")
zzzz-dat-object = { GENDER($ent) ->
[male] him
[female] her
@@ -29,6 +30,16 @@ zzzz-dat-object = { GENDER($ent) ->
*[neuter] it
}
# Used internally by the GENITIVE() function.
# Not used in en-US. Created to support other languages.
# e.g., "у него" (Russian), "seines Vaters" (German).
zzzz-genitive = { GENDER($ent) ->
[male] his
[female] her
[epicene] their
*[neuter] its
}
# Used internally by the POSS-PRONOUN() function.
zzzz-possessive-pronoun = { GENDER($ent) ->
[male] his

View File

@@ -0,0 +1,3 @@
generic-map = map
generic-grid = grid
generic-mapid = map Id

View File

@@ -0,0 +1,33 @@
color-hue-chroma-lightness = {$lightness} {$chroma} {$hue}
color-hue-chroma = {$chroma} {$hue}
color-hue-lightness = {$lightness} {$hue}
color-very-dark = very dark
color-dark = dark
color-light = light
color-very-light = very light
color-mixed-hue = {$a} {$b}
color-pale = pale
color-gray-adjective = gray
color-strong = strong
color-pink = pink
color-red = red
color-orange = orange
color-yellow = yellow
color-green = green
color-cyan = cyan
color-blue = blue
color-purple = purple
color-brown = brown
color-white = white
color-gray = gray
color-black = black
color-unknown = unknown color, you should not see this
color-pink-color-red = pinkish red
color-red-color-orange = reddish orange
color-orange-color-yellow = orangeish yellow
color-yellow-color-green = yellowish green
color-green-color-cyan = greenish cyan
color-cyan-color-blue = cyanish blue
color-blue-color-purple = blueish purple
color-purple-color-pink = purpleish pink

View File

@@ -411,9 +411,6 @@ cmd-spawn-help = spawn <prototype> OR spawn <prototype> <relative entity ID> OR
cmd-cspawn-desc = Spawns a client-side entity with specific type at your feet.
cmd-cspawn-help = cspawn <entity type>
cmd-scale-desc = Increases or decreases an entity's size naively.
cmd-scale-help = scale <entityUid> <float>
cmd-dumpentities-desc = Dump entity list.
cmd-dumpentities-help = Dumps entity list of UIDs and prototype.
@@ -580,3 +577,5 @@ cmd-localization_set_culture-desc = Set DefaultCulture for the client Localizati
cmd-localization_set_culture-help = Usage: localization_set_culture <cultureName>
cmd-localization_set_culture-culture-name = <cultureName>
cmd-localization_set_culture-changed = Localization changed to { $code } ({ $nativeName } / { $englishName })
cmd-addmap-hint-2 = runMapInit [true / false]

View File

@@ -1,8 +1,6 @@
## EntitySpawnWindow
entity-spawn-window-title = Entity Spawn Panel
entity-spawn-window-search-bar-placeholder = search
entity-spawn-window-clear-button = Clear
entity-spawn-window-replace-button-text = Replace
entity-spawn-window-override-menu-tooltip = Override placement
@@ -22,3 +20,5 @@ output-panel-scroll-down-button-text = Scroll Down
## Common Used
window-erase-button-text = Erase Mode
window-search-bar-placeholder = Search
window-clear-button = Clear

View File

@@ -8,3 +8,18 @@ dev-window-tab-textures-info = Width: { $width } Height: { $height }
PixelType: { $pixelType } sRGB: { $srgb }
Name: { $name }
Est. memory usage: { $bytes }
## "Render Targets" dev window tab
dev-window-tab-render-targets-title = Render Targets
dev-window-tab-render-targets-reload = Reload
dev-window-tab-render-targets-filter = Filter
dev-window-tab-render-targets-column-id = ID
dev-window-tab-render-targets-column-name = Name
dev-window-tab-render-targets-column-size = Size
dev-window-tab-render-targets-column-type = Type
dev-window-tab-render-targets-column-vram = VRAM
dev-window-tab-render-targets-column-thumbnail = Thumbnail
dev-window-tab-render-targets-value-null = null
dev-window-tab-render-targets-value-not-available = Not available
dev-window-tab-render-targets-summary = Total VRAM: { $vram }

View File

@@ -2,6 +2,7 @@ input-key-Escape = Escape
input-key-Control = Control
input-key-Shift = Shift
input-key-Alt = Alt
input-key-Alt-mac = ⌥
input-key-Menu = Menu
input-key-F1 = F1
input-key-F2 = F2
@@ -70,8 +71,8 @@ input-key-MouseButton9 = Mouse 9
input-key-LSystem-win = Left Win
input-key-RSystem-win = Right Win
input-key-LSystem-mac = Left Cmd
input-key-RSystem-mac = Right Cmd
input-key-LSystem-mac = Left
input-key-RSystem-mac = Right
input-key-LSystem-linux = Left Meta
input-key-RSystem-linux = Right Meta

View File

@@ -25,3 +25,9 @@ vv-sound-reference-distance = Reference Distance
vv-sound-loop = Loop
vv-sound-play-offset = Play Offset (s)
vv-sound-variation = Pitch variation
## ProtoId
vv-protoid-id-placeholder = Prototype ID
vv-protoid-select-button-label = Select
vv-protoid-addwindow-title = Set Prototype

View File

@@ -14,6 +14,8 @@ uniform highp vec2 lightCenter;
uniform highp float lightRange;
uniform highp float lightPower;
uniform highp float lightSoftness;
uniform highp float lightFalloff;
uniform highp float lightCurveFactor;
uniform highp float lightIndex;
uniform sampler2D shadowMap;
@@ -47,8 +49,15 @@ void fragment()
discard;
}
highp float dist = dot(diff, diff) + LIGHTING_HEIGHT;
highp float val = clamp((1.0 - clamp(sqrt(dist) / lightRange, 0.0, 1.0)) * (1.0 / (sqrt(dist + 1.0))), 0.0, 1.0);
// this implementation of light attenuation primarily adapted from
// https://lisyarus.github.io/blog/posts/point-light-attenuation.html
highp float sqr_dist = dot(diff, diff) + LIGHTING_HEIGHT;
highp float s = clamp(sqrt(sqr_dist) / lightRange, 0.0, 1.0);
highp float s2 = s * s;
// controls curve by lerping between two variants (inverse-shape and inversequadratic-shape)
highp float curveFactor = mix(s, s2, clamp(lightCurveFactor, 0.0, 1.0));
highp float val = clamp(((1.0 - s2) * (1.0 - s2)) / (1.0 + lightFalloff * curveFactor), 0.0, 1.0);
val *= lightPower;
val *= mask;

View File

@@ -0,0 +1,110 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Testing;
using NUnit.Framework;
using VerifyCS =
Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<Robust.Analyzers.AfterAutoHandleStateAnalyzer,
Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
namespace Robust.Analyzers.Tests;
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
[TestFixture, TestOf(typeof(AfterAutoHandleStateAnalyzer))]
public sealed class AfterAutoHandleStateAnalyzerTest
{
private const string SubscribeEventDef = """
using System;
namespace Robust.Shared.GameObjects;
public readonly struct EntityUid;
public abstract class EntitySystem
{
public void SubscribeLocalEvent<T, TEvent>() where TEvent : notnull { }
}
public interface IComponent;
public interface IComponentState;
""";
// A rare case for block-scoped namespace, I thought. Then I realized this
// only needed the one type definition.
private const string OtherTypeDefs = """
using System;
namespace JetBrains.Annotations
{
public sealed class BaseTypeRequiredAttribute(Type baseType) : Attribute;
}
""";
private static Task Verifier(string code, params DiagnosticResult[] expected)
{
var test = new CSharpAnalyzerTest<AfterAutoHandleStateAnalyzer, DefaultVerifier>
{
TestState = { Sources = { code } }
};
TestHelper.AddEmbeddedSources(test.TestState,
"Robust.Shared.Analyzers.ComponentNetworkGeneratorAuxiliary.cs",
"Robust.Shared.GameObjects.EventBusAttributes.cs");
test.TestState.Sources.Add(("EntitySystem.Subscriptions.cs", SubscribeEventDef));
test.TestState.Sources.Add(("Types.cs", OtherTypeDefs));
test.TestState.ExpectedDiagnostics.AddRange(expected);
return test.RunAsync();
}
[Test]
public async Task Test()
{
const string code = """
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
[AutoGenerateComponentState(true)]
public sealed class AutoGenTrue;
[AutoGenerateComponentState(true, true)]
public sealed class AutoGenTrueTrue;
public sealed class NotAutoGen;
[AutoGenerateComponentState]
public sealed class AutoGenNoArgs;
[AutoGenerateComponentState(false)]
public sealed class AutoGenFalse;
public sealed class Foo : EntitySystem
{
public void Good()
{
// Subscribing to other events works
SubscribeLocalEvent<AutoGenNoArgs, object>();
// First arg true allows subscribing
SubscribeLocalEvent<AutoGenTrue, AfterAutoHandleStateEvent>();
SubscribeLocalEvent<AutoGenTrueTrue, AfterAutoHandleStateEvent>();
}
public void Bad()
{
// Can't subscribe if AutoGenerateComponentState isn't even present
SubscribeLocalEvent<NotAutoGen, AfterAutoHandleStateEvent>();
// Can't subscribe if first arg is not specified/false
SubscribeLocalEvent<AutoGenNoArgs, AfterAutoHandleStateEvent>();
SubscribeLocalEvent<AutoGenFalse, AfterAutoHandleStateEvent>();
}
}
""";
await Verifier(code,
// /0/Test0.cs(29,9): error RA0040: Tried to subscribe to AfterAutoHandleStateEvent for 'NotAutoGen' which doesn't have an AutoGenerateComponentState attribute
VerifyCS.Diagnostic(AfterAutoHandleStateAnalyzer.MissingAttribute).WithSpan(29, 9, 29, 69).WithArguments("NotAutoGen"),
// /0/Test0.cs(32,9): error RA0041: Tried to subscribe to AfterAutoHandleStateEvent for 'AutoGenNoArgs' which doesn't have raiseAfterAutoHandleState set
VerifyCS.Diagnostic(AfterAutoHandleStateAnalyzer.MissingAttributeParam).WithSpan(32, 9, 32, 72).WithArguments("AutoGenNoArgs"),
// /0/Test0.cs(33,9): error RA0041: Tried to subscribe to AfterAutoHandleStateEvent for 'AutoGenFalse' which doesn't have raiseAfterAutoHandleState set
VerifyCS.Diagnostic(AfterAutoHandleStateAnalyzer.MissingAttributeParam).WithSpan(33, 9, 33, 71).WithArguments("AutoGenFalse")
);
}
}

View File

@@ -1,4 +1,4 @@
extern alias SerializationGenerator;
extern alias SerializationGenerator;
using System.Linq;
using System.Reflection;
using Microsoft.CodeAnalysis;
@@ -126,6 +126,48 @@ public sealed class ComponentPauseGeneratorTest
""");
}
[Test]
public void TestDictionary()
{
var result = RunGenerator("""
[AutoGenerateComponentPause]
public sealed partial class FooComponent : IComponent
{
[AutoPausedField]
public Dictionary<string, TimeSpan> Foo;
}
""");
ExpectNoDiagnostics(result);
ExpectSource(
result,
"""
// <auto-generated />
using Robust.Shared.GameObjects;
public partial class FooComponent
{
[RobustAutoGenerated]
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
public sealed class FooComponent_AutoPauseSystem : EntitySystem
{
public override void Initialize()
{
SubscribeLocalEvent<FooComponent, EntityUnpausedEvent>(OnEntityUnpaused);
}
private void OnEntityUnpaused(EntityUid uid, FooComponent component, ref EntityUnpausedEvent args)
{
foreach (var key in component.Foo.Keys)
component.Foo[key] += args.PausedTime;
}
}
}
""");
}
[Test]
public void TestAutoState()
{

View File

@@ -203,6 +203,8 @@ public sealed class DataDefinitionAnalyzerTest
[NotYamlSerializable]
public sealed class NotSerializableClass { }
[NotYamlSerializable]
public readonly struct NotSerializableStruct { }
[DataDefinition]
public sealed partial class Foo
@@ -213,6 +215,21 @@ public sealed class DataDefinitionAnalyzerTest
[DataField]
public NotSerializableClass BadProperty { get; set; }
[DataField]
public NotSerializableClass? BadNullableField;
[DataField]
public NotSerializableStruct BadStructField;
[DataField]
public NotSerializableStruct BadStructProperty { get; set; }
[DataField]
public NotSerializableStruct? BadNullableStructField;
[DataField]
public NotSerializableStruct? BadNullableStructProperty { get; set; }
public NotSerializableClass GoodField; // Not a DataField, not a problem
public NotSerializableClass GoodProperty { get; set; } // Not a DataField, not a problem
@@ -220,10 +237,20 @@ public sealed class DataDefinitionAnalyzerTest
""";
await Verifier(code,
// /0/Test0.cs(10,12): error RA0033: Data field BadField in data definition Foo is type NotSerializableClass, which is not YAML serializable
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldYamlSerializableRule).WithSpan(10, 12, 10, 32).WithArguments("BadField", "Foo", "NotSerializableClass"),
// /0/Test0.cs(13,12): error RA0033: Data field BadProperty in data definition Foo is type NotSerializableClass, which is not YAML serializable
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldYamlSerializableRule).WithSpan(13, 12, 13, 32).WithArguments("BadProperty", "Foo", "NotSerializableClass")
// /0/Test0.cs(12,12): error RA0033: Data field BadField in data definition Foo is type NotSerializableClass, which is not YAML serializable
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldYamlSerializableRule).WithSpan(12, 12, 12, 32).WithArguments("BadField", "Foo", "NotSerializableClass"),
// /0/Test0.cs(15,12): error RA0033: Data field BadProperty in data definition Foo is type NotSerializableClass, which is not YAML serializable
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldYamlSerializableRule).WithSpan(15, 12, 15, 32).WithArguments("BadProperty", "Foo", "NotSerializableClass"),
// /0/Test0.cs(18,12): error RA0036: Data field BadNullableField in data definition Foo is type NotSerializableClass, which is not YAML serializable
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldYamlSerializableRule).WithSpan(18, 12, 18, 33).WithArguments("BadNullableField", "Foo", "NotSerializableClass"),
// /0/Test0.cs(21,12): error RA0036: Data field BadStructField in data definition Foo is type NotSerializableStruct, which is not YAML serializable
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldYamlSerializableRule).WithSpan(21, 12, 21, 33).WithArguments("BadStructField", "Foo", "NotSerializableStruct"),
// /0/Test0.cs(24,12): error RA0036: Data field BadStructProperty in data definition Foo is type NotSerializableStruct, which is not YAML serializable
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldYamlSerializableRule).WithSpan(24, 12, 24, 33).WithArguments("BadStructProperty", "Foo", "NotSerializableStruct"),
// /0/Test0.cs(27,12): error RA0036: Data field BadNullableStructField in data definition Foo is type NotSerializableStruct, which is not YAML serializable
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldYamlSerializableRule).WithSpan(27, 12, 27, 34).WithArguments("BadNullableStructField", "Foo", "NotSerializableStruct"),
// /0/Test0.cs(30,12): error RA0036: Data field BadNullableStructProperty in data definition Foo is type NotSerializableStruct, which is not YAML serializable
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldYamlSerializableRule).WithSpan(30, 12, 30, 34).WithArguments("BadNullableStructProperty", "Foo", "NotSerializableStruct")
);
}
}

View File

@@ -0,0 +1,64 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Testing;
using NUnit.Framework;
using VerifyCS =
Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<Robust.Analyzers.PrototypeInstantiationAnalyzer, Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
namespace Robust.Analyzers.Tests;
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
[TestFixture]
[TestOf(typeof(PrototypeInstantiationAnalyzer))]
public sealed class PrototypeInstantiationAnalyzerTest
{
private static Task Verifier(string code, params DiagnosticResult[] expected)
{
var test = new RTAnalyzerTest<PrototypeInstantiationAnalyzer>()
{
TestState =
{
Sources = { code }
},
};
TestHelper.AddEmbeddedSources(
test.TestState,
"Robust.Shared.Prototypes.Attributes.cs",
"Robust.Shared.Prototypes.IPrototype.cs",
"Robust.Shared.Serialization.Manager.Attributes.DataFieldAttribute.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.Serialization;
using Robust.Shared.Prototypes;
[Prototype]
public sealed class FooPrototype : IPrototype
{
[IdDataField]
public string ID { get; private set; } = default!;
}
public static class Bad
{
public static FooPrototype Real()
{
return new FooPrototype();
}
}
""";
await Verifier(code,
// /0/Test0.cs(15,16): warning RA0039: Do not instantiate prototypes directly. Prototypes should always be instantiated by the prototype manager.
VerifyCS.Diagnostic().WithSpan(15, 16, 15, 34));
}
}

View File

@@ -0,0 +1,61 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Testing;
using NUnit.Framework;
using VerifyCS =
Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<Robust.Analyzers.PrototypeNetSerializableAnalyzer, Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
namespace Robust.Analyzers.Tests;
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
[TestFixture]
[TestOf(typeof(PrototypeNetSerializableAnalyzer))]
public sealed class PrototypeNetSerializableAnalyzerTest
{
private static Task Verifier(string code, params DiagnosticResult[] expected)
{
var test = new RTAnalyzerTest<PrototypeNetSerializableAnalyzer>()
{
TestState =
{
Sources = { code }
},
};
TestHelper.AddEmbeddedSources(
test.TestState,
"Robust.Shared.Serialization.NetSerializableAttribute.cs",
"Robust.Shared.Prototypes.Attributes.cs",
"Robust.Shared.Prototypes.IPrototype.cs",
"Robust.Shared.Serialization.Manager.Attributes.DataFieldAttribute.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 System;
using Robust.Shared.Serialization;
using Robust.Shared.Prototypes;
[Prototype]
[Serializable, NetSerializable]
public sealed class FooPrototype : IPrototype
{
[IdDataField]
public string ID { get; private set; } = default!;
}
""";
await Verifier(code,
// /0/Test0.cs(7,21): warning RA0037: Type FooPrototype is a prototype and marked as [NetSerializable]. Prototypes should not be directly sent over the network, send their IDs instead.
VerifyCS.Diagnostic(PrototypeNetSerializableAnalyzer.RuleNetSerializable).WithSpan(7, 21, 7, 33).WithArguments("FooPrototype"),
// /0/Test0.cs(7,21): warning RA0038: Type FooPrototype is a prototype and marked as [Serializable]. Prototypes should not be directly sent over the network, send their IDs instead.
VerifyCS.Diagnostic(PrototypeNetSerializableAnalyzer.RuleSerializable).WithSpan(7, 21, 7, 33).WithArguments("FooPrototype"));
}
}

View File

@@ -0,0 +1,17 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Testing;
namespace Robust.Analyzers.Tests;
public sealed class RTAnalyzerTest<TAnalyzer> : CSharpAnalyzerTest<TAnalyzer, DefaultVerifier>
where TAnalyzer : DiagnosticAnalyzer, new()
{
protected override ParseOptions CreateParseOptions()
{
var baseOptions = (CSharpParseOptions) base.CreateParseOptions();
return baseOptions.WithPreprocessorSymbols("ROBUST_ANALYZERS_TEST");
}
}

View File

@@ -10,6 +10,7 @@
<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\Analyzers\ComponentNetworkGeneratorAuxiliary.cs" LogicalName="Robust.Shared.Analyzers.ComponentNetworkGeneratorAuxiliary.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\Analyzers\MustCallBaseAttribute.cs" LogicalName="Robust.Shared.IoC.MustCallBaseAttribute.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\Analyzers\PreferNonGenericVariantForAttribute.cs" LogicalName="Robust.Shared.Analyzers.PreferNonGenericVariantForAttribute.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\Analyzers\PreferOtherTypeAttribute.cs" LogicalName="Robust.Shared.Analyzers.PreferOtherTypeAttribute.cs" LinkBase="Implementations" />
@@ -17,6 +18,10 @@
<EmbeddedResource Include="..\Robust.Shared\Analyzers\ObsoleteInheritanceAttribute.cs" LogicalName="Robust.Shared.Analyzers.ObsoleteInheritanceAttribute.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\IoC\DependencyAttribute.cs" LogicalName="Robust.Shared.IoC.DependencyAttribute.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\GameObjects\EventBusAttributes.cs" LogicalName="Robust.Shared.GameObjects.EventBusAttributes.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\Serialization\NetSerializableAttribute.cs" LogicalName="Robust.Shared.Serialization.NetSerializableAttribute.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\Prototypes\Attributes.cs" LogicalName="Robust.Shared.Prototypes.Attributes.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\Prototypes\IPrototype.cs" LogicalName="Robust.Shared.Prototypes.IPrototype.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\Serialization\Manager\Attributes\DataFieldAttribute.cs" LogicalName="Robust.Shared.Serialization.Manager.Attributes.DataFieldAttribute.cs" LinkBase="Implementations" />
</ItemGroup>
<PropertyGroup>

View File

@@ -0,0 +1,85 @@
#nullable enable
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 AfterAutoHandleStateAnalyzer : DiagnosticAnalyzer
{
private const string AfterAutoHandleStateEventName = "AfterAutoHandleStateEvent";
private const string AutoGenStateAttribute = "Robust.Shared.Analyzers.AutoGenerateComponentStateAttribute";
private const string SubscribeLocalEventName = "SubscribeLocalEvent";
public static readonly DiagnosticDescriptor MissingAttribute = new(
Diagnostics.IdAutoGenStateAttributeMissing,
"Unreachable AfterAutoHandleState subscription",
"Tried to subscribe to AfterAutoHandleStateEvent for '{0}' which doesn't have an "
+ "AutoGenerateComponentState attribute",
"Usage",
DiagnosticSeverity.Error,
true,
// Does this even show up anywhere in Rider? >:(
"You must mark your component with '[AutoGenerateComponentState(true)]' to subscribe to this event."
);
public static readonly DiagnosticDescriptor MissingAttributeParam = new(
Diagnostics.IdAutoGenStateParamMissing,
"Unreachable AfterAutoHandleState subscription",
"Tried to subscribe to AfterAutoHandleStateEvent for '{0}' which doesn't have "
+ "raiseAfterAutoHandleState set",
"Usage",
DiagnosticSeverity.Error,
true,
"The AutoGenerateComponentState attribute must be passed 'true' in order to subscribe to this event."
);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
[MissingAttribute, MissingAttributeParam];
public override void Initialize(AnalysisContext context)
{
// This is more to stop user error rather than code generation error
// (Plus this shouldn't affect code gen anyway)
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterCompilationStartAction(compilationContext =>
{
var autoGenStateAttribute = compilationContext.Compilation.GetTypeByMetadataName(AutoGenStateAttribute);
// No attribute, no analyzer.
if (autoGenStateAttribute is null)
return;
compilationContext.RegisterOperationAction(
analysisContext => CheckEventSubscription(analysisContext, autoGenStateAttribute),
OperationKind.Invocation);
});
}
private static void CheckEventSubscription(OperationAnalysisContext context, ITypeSymbol autoGenStateAttribute)
{
if (context.Operation is not IInvocationOperation operation)
return;
// Check the method has the right name and has the right type args
if (operation.TargetMethod is not
{ Name: SubscribeLocalEventName, TypeArguments: [var component, { Name: AfterAutoHandleStateEventName }] })
return;
// Search the component's attributes for something matching autoGenStateAttribute
AttributeHelper.HasAttribute(component, autoGenStateAttribute, out var autoGenAttribute);
// First argument is raiseAfterAutoHandleState—note it shouldn't ever
// be null, since it has a default, but eh.
if (autoGenAttribute?.ConstructorArguments[0].Value is true)
return;
context.ReportDiagnostic(Diagnostic.Create(autoGenAttribute is null ? MissingAttribute : MissingAttributeParam,
operation.Syntax.GetLocation(),
component.Name));
}
}

View File

@@ -121,7 +121,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
}, SymbolKind.NamedType);
}
private void AnalyzeDataDefinition(SyntaxNodeAnalysisContext context)
private static void AnalyzeDataDefinition(SyntaxNodeAnalysisContext context)
{
if (context.Node is not TypeDeclarationSyntax declaration)
return;
@@ -147,7 +147,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
}
}
private void AnalyzeDataField(SyntaxNodeAnalysisContext context)
private static void AnalyzeDataField(SyntaxNodeAnalysisContext context)
{
if (context.Node is not FieldDeclarationSyntax field)
return;
@@ -186,6 +186,8 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
if (context.SemanticModel.GetSymbolInfo(field.Declaration.Type).Symbol is not ITypeSymbol fieldTypeSymbol)
continue;
fieldTypeSymbol = TypeSymbolHelper.GetNullableUnderlyingTypeOrSelf(fieldTypeSymbol);
if (IsNotYamlSerializable(fieldSymbol, fieldTypeSymbol))
{
context.ReportDiagnostic(Diagnostic.Create(DataFieldYamlSerializableRule,
@@ -198,7 +200,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
}
}
private void AnalyzeDataFieldProperty(SyntaxNodeAnalysisContext context)
private static void AnalyzeDataFieldProperty(SyntaxNodeAnalysisContext context)
{
if (context.Node is not PropertyDeclarationSyntax property)
return;
@@ -239,6 +241,8 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
if (context.SemanticModel.GetSymbolInfo(property.Type).Symbol is not ITypeSymbol propertyTypeSymbol)
return;
propertyTypeSymbol = TypeSymbolHelper.GetNullableUnderlyingTypeOrSelf(propertyTypeSymbol);
if (IsNotYamlSerializable(propertySymbol, propertyTypeSymbol))
{
context.ReportDiagnostic(Diagnostic.Create(DataFieldYamlSerializableRule,

View File

@@ -0,0 +1,48 @@
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 PrototypeInstantiationAnalyzer : DiagnosticAnalyzer
{
private const string PrototypeInterfaceType = "Robust.Shared.Prototypes.IPrototype";
public static readonly DiagnosticDescriptor Rule = new(
Diagnostics.IdPrototypeInstantiation,
"Do not instantiate prototypes directly",
"Do not instantiate prototypes directly. Prototypes should always be instantiated by the prototype manager.",
"Usage",
DiagnosticSeverity.Warning,
true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [Rule];
public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.RegisterCompilationStartAction(static ctx =>
{
var prototypeInterface = ctx.Compilation.GetTypeByMetadataName(PrototypeInterfaceType);
if (prototypeInterface == null)
return;
ctx.RegisterOperationAction(symContext => Check(prototypeInterface, symContext), OperationKind.ObjectCreation);
});
}
private static void Check(INamedTypeSymbol prototypeInterface, OperationAnalysisContext ctx)
{
if (ctx.Operation is not IObjectCreationOperation { Type: { } resultType } creationOp)
return;
if (!TypeSymbolHelper.ImplementsInterface(resultType, prototypeInterface))
return;
ctx.ReportDiagnostic(Diagnostic.Create(Rule, creationOp.Syntax.GetLocation()));
}
}

View File

@@ -0,0 +1,76 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Robust.Roslyn.Shared;
namespace Robust.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class PrototypeNetSerializableAnalyzer : DiagnosticAnalyzer
{
private const string PrototypeInterfaceType = "Robust.Shared.Prototypes.IPrototype";
private const string NetSerializableAttributeType = "Robust.Shared.Serialization.NetSerializableAttribute";
public static readonly DiagnosticDescriptor RuleNetSerializable = new(
Diagnostics.IdPrototypeNetSerializable,
"Prototypes should not be [NetSerializable]",
"Type {0} is a prototype and marked as [NetSerializable]. Prototypes should not be directly sent over the network, send their IDs instead.",
"Usage",
DiagnosticSeverity.Warning,
true);
public static readonly DiagnosticDescriptor RuleSerializable = new(
Diagnostics.IdPrototypeSerializable,
"Prototypes should not be [Serializable]",
"Type {0} is a prototype and marked as [Serializable]. Prototypes should not be directly sent over the network, send their IDs instead.",
"Usage",
DiagnosticSeverity.Warning,
true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [
RuleNetSerializable,
RuleSerializable
];
public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.RegisterCompilationStartAction(static ctx =>
{
var prototypeInterface = ctx.Compilation.GetTypeByMetadataName(PrototypeInterfaceType);
var netSerializableAttribute = ctx.Compilation.GetTypeByMetadataName(NetSerializableAttributeType);
if (prototypeInterface == null || netSerializableAttribute == null)
return;
ctx.RegisterSymbolAction(symbolContext => CheckClass(prototypeInterface, netSerializableAttribute, symbolContext), SymbolKind.NamedType);
});
}
private static void CheckClass(
INamedTypeSymbol prototypeInterface,
INamedTypeSymbol netSerializableAttribute,
SymbolAnalysisContext symbolContext)
{
if (symbolContext.Symbol is not INamedTypeSymbol symbol)
return;
if (!TypeSymbolHelper.ImplementsInterface(symbol, prototypeInterface))
return;
if (AttributeHelper.HasAttribute(symbol, netSerializableAttribute, out _))
{
symbolContext.ReportDiagnostic(
Diagnostic.Create(RuleNetSerializable, symbol.Locations[0], symbol.ToDisplayString()));
}
if (symbol.IsSerializable)
{
symbolContext.ReportDiagnostic(
Diagnostic.Create(RuleSerializable, symbol.Locations[0], symbol.ToDisplayString()));
}
}
}

View File

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

View File

@@ -162,9 +162,10 @@ namespace Robust.Client.WebView.Cef
}
}
public bool IsOpen => _data != null;
public bool IsLoading => _data?.Browser.IsLoading ?? false;
public void EnteredTree()
public void StartBrowser()
{
DebugTools.AssertNull(_data);
@@ -195,7 +196,7 @@ namespace Robust.Client.WebView.Cef
_data = new LiveData(texture, client, browser, renderer);
}
public void ExitedTree()
public void CloseBrowser()
{
DebugTools.AssertNotNull(_data);

View File

@@ -0,0 +1,51 @@
using System;
using System.IO;
using Robust.Client.Utility;
namespace Robust.Client.WebView.Cef;
internal sealed partial class WebViewManagerCef
{
private const string BaseCacheName = "cef_cache";
private const string LockFileName = "robust.lock";
private FileStream? _lockFileStream;
private const int MaxAttempts = 15; // This probably shouldn't be a cvar because the only reason you'd need it change for legit just botting the game.
private string FindAndLockCacheDirectory()
{
var rootDir = Path.Combine(UserDataDir.GetRootUserDataDir(_gameController), BaseCacheName);
for (var i = 0; i < MaxAttempts; i++)
{
var cacheDirPath = Path.Combine(rootDir, i.ToString());
if (TryLockCacheDir(i, cacheDirPath))
return cacheDirPath;
}
throw new Exception("Unable to locate available CEF cache directory!");
}
private bool TryLockCacheDir(int attempt, string path)
{
_sawmill.Verbose($"Trying to lock cache directory {attempt}");
// Does not fail if directory already exists.
Directory.CreateDirectory(path);
var lockFilePath = Path.Combine(path, LockFileName);
try
{
var file = File.Open(lockFilePath, FileMode.Create, FileAccess.ReadWrite, FileShare.None);
_lockFileStream = file;
_sawmill.Debug($"Successfully locked CEF cache directory {attempt}");
return true;
}
catch (IOException ex)
{
_sawmill.Error($"Failed to lock cache directory {attempt}: {ex}");
return false;
}
}
}

View File

@@ -5,6 +5,7 @@ using System.Net;
using System.Reflection;
using System.Text;
using Robust.Client.Console;
using Robust.Client.Utility;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.IoC;
@@ -24,6 +25,7 @@ namespace Robust.Client.WebView.Cef
[Dependency] private readonly IDependencyCollection _dependencyCollection = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IGameControllerInternal _gameController = default!;
[Dependency] private readonly IResourceManagerInternal _resourceManager = default!;
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
@@ -59,9 +61,9 @@ namespace Robust.Client.WebView.Cef
if (cefResourcesPath == null)
throw new InvalidOperationException("Unable to locate cef_resources directory!");
var cachePath = "";
if (_resourceManager.UserData is WritableDirProvider userData)
cachePath = userData.GetFullPath(new ResPath("/cef_cache"));
var remoteDebugPort = _cfg.GetCVar(WCVars.WebRemoteDebugPort);
var cachePath = FindAndLockCacheDirectory();
var settings = new CefSettings()
{
@@ -71,7 +73,7 @@ namespace Robust.Client.WebView.Cef
BrowserSubprocessPath = subProcessPath,
LocalesDirPath = Path.Combine(cefResourcesPath, "locales"),
ResourcesDirPath = cefResourcesPath,
RemoteDebuggingPort = 9222,
RemoteDebuggingPort = remoteDebugPort,
CookieableSchemesList = "usr,res",
CachePath = cachePath,
};

View File

@@ -81,11 +81,13 @@ namespace Robust.Client.WebView.Headless
private sealed class WebViewControlImplDummy : DummyBase, IWebViewControlImpl
{
public void EnteredTree()
public bool IsOpen => false;
public void StartBrowser()
{
}
public void ExitedTree()
public void CloseBrowser()
{
}

View File

@@ -9,8 +9,10 @@ namespace Robust.Client.WebView
/// </summary>
internal interface IWebViewControlImpl : IWebViewControl
{
void EnteredTree();
void ExitedTree();
public bool IsOpen { get; }
void StartBrowser();
void CloseBrowser();
void MouseMove(GUIMouseMoveEventArgs args);
void MouseExited();
void MouseWheel(GUIMouseWheelEventArgs args);

View File

@@ -26,4 +26,16 @@ public static class WCVars
/// </summary>
public static readonly CVarDef<bool> WebHeadless =
CVarDef.Create("web.headless", false, CVar.CLIENTONLY);
#if TOOLS
private const int DefaultRemoteDebugPort = 9222;
#else
private const int DefaultRemoteDebugPort = 0;
#endif
/// <summary>
/// If not 0, the port number used for Chromium's remote debugging.
/// </summary>
public static readonly CVarDef<int> WebRemoteDebugPort =
CVarDef.Create("web.remote_debug_port", DefaultRemoteDebugPort, CVar.CLIENTONLY);
}

View File

@@ -14,6 +14,7 @@ namespace Robust.Client.WebView
[Dependency] private readonly IWebViewManagerInternal _webViewManager = default!;
private readonly IWebViewControlImpl _controlImpl;
private bool _alwaysActive;
[ViewVariables(VVAccess.ReadWrite)]
public string Url
@@ -22,6 +23,21 @@ namespace Robust.Client.WebView
set => _controlImpl.Url = value;
}
[ViewVariables(VVAccess.ReadWrite)]
public bool AlwaysActive
{
get => _alwaysActive;
set
{
_alwaysActive = value;
if (_alwaysActive && !_controlImpl.IsOpen)
_controlImpl.StartBrowser();
else if (!_alwaysActive && _controlImpl.IsOpen && !IsInsideTree)
_controlImpl.CloseBrowser();
}
}
[ViewVariables] public bool IsLoading => _controlImpl.IsLoading;
public WebViewControl()
@@ -39,14 +55,16 @@ namespace Robust.Client.WebView
{
base.EnteredTree();
_controlImpl.EnteredTree();
if (!_controlImpl.IsOpen)
_controlImpl.StartBrowser();
}
protected override void ExitedTree()
{
base.ExitedTree();
_controlImpl.ExitedTree();
if (!_alwaysActive)
_controlImpl.CloseBrowser();
}
protected internal override void MouseMove(GUIMouseMoveEventArgs args)

View File

@@ -3,8 +3,6 @@ using System.Collections.Generic;
using System.Numerics;
using Robust.Shared.Animations;
using Robust.Shared.Maths;
using Vector3 = Robust.Shared.Maths.Vector3;
using Vector4 = Robust.Shared.Maths.Vector4;
namespace Robust.Client.Animations
{
@@ -56,8 +54,14 @@ namespace Robust.Client.Animations
}
else
{
var next = KeyFrames[nextKeyFrame];
// Get us a scale 0 -> 1 here.
var t = playingTime / KeyFrames[nextKeyFrame].KeyTime;
var t = playingTime / next.KeyTime;
// Apply easing to time parameter, if one was specified
if (next.Easing != null)
t = next.Easing(t);
switch (InterpolationMode)
{
@@ -122,9 +126,9 @@ namespace Robust.Client.Animations
case Vector2 vector2:
return Vector2Helpers.InterpolateCubic((Vector2) preA, vector2, (Vector2) b, (Vector2) postB, t);
case Vector3 vector3:
return Vector3.InterpolateCubic((Vector3) preA, vector3, (Vector3) b, (Vector3) postB, t);
return VectorHelpers.InterpolateCubic((Vector3) preA, vector3, (Vector3) b, (Vector3) postB, t);
case Vector4 vector4:
return Vector4.InterpolateCubic((Vector4) preA, vector4, (Vector4) b, (Vector4) postB, t);
return VectorHelpers.InterpolateCubic((Vector4) preA, vector4, (Vector4) b, (Vector4) postB, t);
case float f:
return MathHelper.InterpolateCubic((float) preA, f, (float) b, (float) postB, t);
case double d:
@@ -149,10 +153,20 @@ namespace Robust.Client.Animations
/// </summary>
public readonly float KeyTime;
public KeyFrame(object value, float keyTime)
/// <summary>
/// An easing function to apply when interpolating to this keyframe's value.
/// Modifies the time parameter (0..1) of the interpolation between the previous keyframe and this one.
/// </summary>
/// <remarks>
/// See <see cref="Easings"/> for examples of easing functions, or provide your own.
/// </remarks>
public readonly Func<float, float>? Easing;
public KeyFrame(object value, float keyTime, Func<float, float>? easing = null)
{
Value = value;
KeyTime = keyTime;
Easing = easing;
}
}
}

View File

@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using OpenTK.Audio.OpenAL;
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
using Robust.Client.Audio.Sources;
using Robust.Client.ResourceManagement;
using Robust.Shared;
@@ -145,7 +144,7 @@ internal sealed partial class AudioManager : IAudioInternal
private static void RemoveEfx((int sourceHandle, int filterHandle) handles)
{
if (handles.filterHandle != 0)
EFX.DeleteFilter(handles.filterHandle);
ALC.EFX.DeleteFilter(handles.filterHandle);
}
private void _checkAlcError(ALDevice device,

View File

@@ -1,4 +1,3 @@
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
using Robust.Client.Audio.Effects;
using Robust.Shared.Audio.Components;
using Robust.Shared.GameObjects;

View File

@@ -372,13 +372,13 @@ public sealed partial class AudioSystem : SharedAudioSystem
return;
}
var parentUid = xform.ParentUid;
Vector2 worldPos;
component.Volume = component.Params.Volume;
// Handle grid audio differently by using grid position.
if ((component.Flags & AudioFlags.GridAudio) != 0x0)
{
var parentUid = xform.ParentUid;
worldPos = _maps.GetGridPosition(parentUid);
}
else
@@ -412,7 +412,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
}
else
{
var occlusion = GetOcclusion(listener, delta, distance, entity);
var occlusion = GetOcclusion(listener, delta, distance, parentUid);
component.Occlusion = occlusion;
}
@@ -420,11 +420,11 @@ public sealed partial class AudioSystem : SharedAudioSystem
component.Position = worldPos;
// Make race cars go NYYEEOOOOOMMMMM
if (_physicsQuery.TryGetComponent(entity, out var physicsComp))
if (_physicsQuery.TryGetComponent(parentUid, out var physicsComp))
{
// This actually gets the tracked entity's xform & iterates up though the parents for the second time. Bit
// inefficient.
var velocity = _physics.GetMapLinearVelocity(entity, physicsComp, xform);
var velocity = _physics.GetMapLinearVelocity(parentUid, physicsComp);
component.Velocity = velocity;
}
}
@@ -589,6 +589,11 @@ public sealed partial class AudioSystem : SharedAudioSystem
var playing = CreateAndStartPlayingStream(audioParams, specifier, stream);
_xformSys.SetCoordinates(playing.Entity, new EntityCoordinates(entity, Vector2.Zero));
// Since we're playing the sound immediately in the middle of a tick, we need to force ProcessStream -now-
// to set occlusion/position/velocity etc
// otherwise predicted positional sounds will sound very incorrect in several possible ways (e#5802, e#6175) until the next tick
ProcessStream(playing.Entity, playing.Component, Transform(playing.Entity), GetListenerCoordinates());
return playing;
}
@@ -632,6 +637,10 @@ public sealed partial class AudioSystem : SharedAudioSystem
var playing = CreateAndStartPlayingStream(audioParams, specifier, stream);
_xformSys.SetCoordinates(playing.Entity, coordinates);
// see PlayEntity for why this is necessary
ProcessStream(playing.Entity, playing.Component, Transform(playing.Entity), GetListenerCoordinates());
return playing;
}
@@ -714,8 +723,6 @@ public sealed partial class AudioSystem : SharedAudioSystem
offset = Math.Clamp(offset, 0f, maxOffset);
source.PlaybackPosition = offset;
// For server we will rely on the adjusted one but locally we will have to adjust it ourselves.
ApplyAudioParams(comp.Params, comp);
source.StartPlaying();
return (entity, comp);
}

View File

@@ -1,5 +1,6 @@
using System;
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
using System.Numerics;
using OpenTK.Audio.OpenAL;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Effects;
using Robust.Shared.Maths;
@@ -15,16 +16,16 @@ internal sealed class AudioEffect : IAudioEffect
public AudioEffect(IAudioInternal manager)
{
Handle = EFX.GenEffect();
Handle = ALC.EFX.GenEffect();
_master = manager;
EFX.Effect(Handle, EffectInteger.EffectType, (int) EffectType.EaxReverb);
ALC.EFX.Effect(Handle, EffectInteger.EffectType, (int) EffectType.EaxReverb);
}
public void Dispose()
{
if (Handle != 0)
{
EFX.DeleteEffect(Handle);
ALC.EFX.DeleteEffect(Handle);
Handle = 0;
}
}
@@ -43,14 +44,14 @@ internal sealed class AudioEffect : IAudioEffect
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectFloat.EaxReverbDensity, out var value);
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbDensity, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectFloat.EaxReverbDensity, value);
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbDensity, value);
_master._checkAlError();
}
}
@@ -61,14 +62,14 @@ internal sealed class AudioEffect : IAudioEffect
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectFloat.EaxReverbDiffusion, out var value);
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbDiffusion, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectFloat.EaxReverbDiffusion, value);
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbDiffusion, value);
_master._checkAlError();
}
}
@@ -79,14 +80,14 @@ internal sealed class AudioEffect : IAudioEffect
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectFloat.EaxReverbGain, out var value);
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbGain, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectFloat.EaxReverbGain, value);
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbGain, value);
_master._checkAlError();
}
}
@@ -97,14 +98,14 @@ internal sealed class AudioEffect : IAudioEffect
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectFloat.EaxReverbGainHF, out var value);
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbGainHF, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectFloat.EaxReverbGainHF, value);
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbGainHF, value);
_master._checkAlError();
}
}
@@ -115,14 +116,14 @@ internal sealed class AudioEffect : IAudioEffect
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectFloat.EaxReverbGainLF, out var value);
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbGainLF, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectFloat.EaxReverbGainLF, value);
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbGainLF, value);
_master._checkAlError();
}
}
@@ -133,14 +134,14 @@ internal sealed class AudioEffect : IAudioEffect
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectFloat.EaxReverbDecayTime, out var value);
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbDecayTime, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectFloat.EaxReverbDecayTime, value);
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbDecayTime, value);
_master._checkAlError();
}
}
@@ -151,14 +152,14 @@ internal sealed class AudioEffect : IAudioEffect
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectFloat.EaxReverbDecayHFRatio, out var value);
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbDecayHFRatio, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectFloat.EaxReverbDecayHFRatio, value);
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbDecayHFRatio, value);
_master._checkAlError();
}
}
@@ -169,14 +170,14 @@ internal sealed class AudioEffect : IAudioEffect
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectFloat.EaxReverbDecayLFRatio, out var value);
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbDecayLFRatio, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectFloat.EaxReverbDecayLFRatio, value);
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbDecayLFRatio, value);
_master._checkAlError();
}
}
@@ -187,14 +188,14 @@ internal sealed class AudioEffect : IAudioEffect
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectFloat.EaxReverbReflectionsGain, out var value);
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbReflectionsGain, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectFloat.EaxReverbReflectionsGain, value);
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbReflectionsGain, value);
_master._checkAlError();
}
}
@@ -205,14 +206,14 @@ internal sealed class AudioEffect : IAudioEffect
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectFloat.EaxReverbReflectionsDelay, out var value);
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbReflectionsDelay, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectFloat.EaxReverbReflectionsDelay, value);
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbReflectionsDelay, value);
_master._checkAlError();
}
}
@@ -223,7 +224,7 @@ internal sealed class AudioEffect : IAudioEffect
get
{
_checkDisposed();
var value = EFX.GetEffect(Handle, EffectVector3.EaxReverbReflectionsPan);
var value = ALC.EFX.GetEffect(Handle, EffectVector3.EaxReverbReflectionsPan);
_master._checkAlError();
return new Vector3(value.X, value.Z, value.Y);
}
@@ -231,7 +232,7 @@ internal sealed class AudioEffect : IAudioEffect
{
_checkDisposed();
var openVec = new OpenTK.Mathematics.Vector3(value.X, value.Y, value.Z);
EFX.Effect(Handle, EffectVector3.EaxReverbReflectionsPan, ref openVec);
ALC.EFX.Effect(Handle, EffectVector3.EaxReverbReflectionsPan, ref openVec);
_master._checkAlError();
}
}
@@ -242,14 +243,14 @@ internal sealed class AudioEffect : IAudioEffect
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectFloat.EaxReverbLateReverbGain, out var value);
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbLateReverbGain, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectFloat.EaxReverbLateReverbGain, value);
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbLateReverbGain, value);
_master._checkAlError();
}
}
@@ -260,14 +261,14 @@ internal sealed class AudioEffect : IAudioEffect
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectFloat.EaxReverbLateReverbDelay, out var value);
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbLateReverbDelay, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectFloat.EaxReverbLateReverbDelay, value);
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbLateReverbDelay, value);
_master._checkAlError();
}
}
@@ -278,7 +279,7 @@ internal sealed class AudioEffect : IAudioEffect
get
{
_checkDisposed();
var value = EFX.GetEffect(Handle, EffectVector3.EaxReverbLateReverbPan);
var value = ALC.EFX.GetEffect(Handle, EffectVector3.EaxReverbLateReverbPan);
_master._checkAlError();
return new Vector3(value.X, value.Z, value.Y);
}
@@ -286,7 +287,7 @@ internal sealed class AudioEffect : IAudioEffect
{
_checkDisposed();
var openVec = new OpenTK.Mathematics.Vector3(value.X, value.Y, value.Z);
EFX.Effect(Handle, EffectVector3.EaxReverbLateReverbPan, ref openVec);
ALC.EFX.Effect(Handle, EffectVector3.EaxReverbLateReverbPan, ref openVec);
_master._checkAlError();
}
}
@@ -297,14 +298,14 @@ internal sealed class AudioEffect : IAudioEffect
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectFloat.EaxReverbEchoTime, out var value);
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbEchoTime, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectFloat.EaxReverbEchoTime, value);
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbEchoTime, value);
_master._checkAlError();
}
}
@@ -315,14 +316,14 @@ internal sealed class AudioEffect : IAudioEffect
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectFloat.EaxReverbEchoDepth, out var value);
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbEchoDepth, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectFloat.EaxReverbEchoDepth, value);
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbEchoDepth, value);
_master._checkAlError();
}
}
@@ -333,14 +334,14 @@ internal sealed class AudioEffect : IAudioEffect
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectFloat.EaxReverbModulationTime, out var value);
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbModulationTime, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectFloat.EaxReverbModulationTime, value);
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbModulationTime, value);
_master._checkAlError();
}
}
@@ -351,14 +352,14 @@ internal sealed class AudioEffect : IAudioEffect
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectFloat.EaxReverbModulationDepth, out var value);
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbModulationDepth, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectFloat.EaxReverbModulationDepth, value);
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbModulationDepth, value);
_master._checkAlError();
}
}
@@ -369,14 +370,14 @@ internal sealed class AudioEffect : IAudioEffect
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectFloat.EaxReverbAirAbsorptionGainHF, out var value);
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbAirAbsorptionGainHF, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectFloat.EaxReverbAirAbsorptionGainHF, value);
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbAirAbsorptionGainHF, value);
_master._checkAlError();
}
}
@@ -387,14 +388,14 @@ internal sealed class AudioEffect : IAudioEffect
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectFloat.EaxReverbHFReference, out var value);
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbHFReference, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectFloat.EaxReverbHFReference, value);
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbHFReference, value);
_master._checkAlError();
}
}
@@ -405,14 +406,14 @@ internal sealed class AudioEffect : IAudioEffect
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectFloat.EaxReverbLFReference, out var value);
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbLFReference, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectFloat.EaxReverbLFReference, value);
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbLFReference, value);
_master._checkAlError();
}
}
@@ -423,14 +424,14 @@ internal sealed class AudioEffect : IAudioEffect
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectFloat.EaxReverbRoomRolloffFactor, out var value);
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbRoomRolloffFactor, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectFloat.EaxReverbRoomRolloffFactor, value);
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbRoomRolloffFactor, value);
_master._checkAlError();
}
}
@@ -441,14 +442,14 @@ internal sealed class AudioEffect : IAudioEffect
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectInteger.EaxReverbDecayHFLimit, out var value);
ALC.EFX.GetEffect(Handle, EffectInteger.EaxReverbDecayHFLimit, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectInteger.EaxReverbDecayHFLimit, value);
ALC.EFX.Effect(Handle, EffectInteger.EaxReverbDecayHFLimit, value);
_master._checkAlError();
}
}

View File

@@ -1,4 +1,4 @@
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
using OpenTK.Audio.OpenAL;
using Robust.Shared.Audio.Effects;
namespace Robust.Client.Audio.Effects;
@@ -6,13 +6,13 @@ namespace Robust.Client.Audio.Effects;
/// <inheritdoc />
internal sealed class AuxiliaryAudio : IAuxiliaryAudio
{
internal int Handle = EFX.GenAuxiliaryEffectSlot();
internal int Handle = ALC.EFX.GenAuxiliaryEffectSlot();
public void Dispose()
{
if (Handle != -1)
{
EFX.DeleteAuxiliaryEffectSlot(Handle);
ALC.EFX.DeleteAuxiliaryEffectSlot(Handle);
Handle = -1;
}
}
@@ -22,11 +22,11 @@ internal sealed class AuxiliaryAudio : IAuxiliaryAudio
{
if (effect is AudioEffect audEffect)
{
EFX.AuxiliaryEffectSlot(Handle, EffectSlotInteger.Effect, audEffect.Handle);
ALC.EFX.AuxiliaryEffectSlot(Handle, EffectSlotInteger.Effect, audEffect.Handle);
}
else
{
EFX.AuxiliaryEffectSlot(Handle, EffectSlotInteger.Effect, 0);
ALC.EFX.AuxiliaryEffectSlot(Handle, EffectSlotInteger.Effect, 0);
}
}
}

View File

@@ -213,4 +213,6 @@ public interface IMidiRenderer : IDisposable
/// Actually disposes of this renderer. Do NOT use outside the MIDI thread.
/// </summary>
internal void InternalDispose();
byte MinVolume { get; set; }
}

View File

@@ -42,7 +42,7 @@ internal sealed partial class MidiManager : IMidiManager
[Dependency] private readonly IRuntimeLog _runtime = default!;
private AudioSystem _audioSys = default!;
private SharedPhysicsSystem _broadPhaseSystem = default!;
private SharedPhysicsSystem _physics = default!;
private SharedTransformSystem _xformSystem = default!;
public IReadOnlyList<IMidiRenderer> Renderers
@@ -81,7 +81,7 @@ internal sealed partial class MidiManager : IMidiManager
private Thread? _midiThread;
private ISawmill _midiSawmill = default!;
private float _gain = 0f;
private bool _volumeDirty = true;
private bool _gainDirty = true;
// Not reliable until Fluidsynth is initialized!
[ViewVariables(VVAccess.ReadWrite)]
@@ -96,7 +96,7 @@ internal sealed partial class MidiManager : IMidiManager
return;
_cfgMan.SetCVar(CVars.MidiVolume, clamped);
_volumeDirty = true;
_gainDirty = true;
}
}
@@ -114,7 +114,8 @@ internal sealed partial class MidiManager : IMidiManager
"/usr/share/sounds/sf2/TimGM6mb.sf2",
};
private static readonly string WindowsSoundfont = $@"{Environment.GetEnvironmentVariable("SystemRoot")}\system32\drivers\gm.dls";
private static readonly string WindowsSoundfont =
$@"{Environment.GetEnvironmentVariable("SystemRoot")}\system32\drivers\gm.dls";
private const string OsxSoundfont =
"/System/Library/Components/CoreAudio.component/Contents/Resources/gs_instruments.dls";
@@ -145,11 +146,13 @@ internal sealed partial class MidiManager : IMidiManager
{
if (FluidsynthInitialized || _failedInitialize) return;
_cfgMan.OnValueChanged(CVars.MidiVolume, value =>
{
_gain = value;
_volumeDirty = true;
}, true);
_cfgMan.OnValueChanged(CVars.MidiVolume,
value =>
{
_gain = value;
_gainDirty = true;
},
true);
_midiSawmill = _logger.GetSawmill("midi");
#if DEBUG
@@ -167,13 +170,15 @@ internal sealed partial class MidiManager : IMidiManager
// not a directory, preserve the old file and create an actual directory
else if (!_resourceManager.UserData.IsDir(CustomSoundfontDirectory))
{
_resourceManager.UserData.Rename(CustomSoundfontDirectory, CustomSoundfontDirectory.WithName(CustomSoundfontDirectory.Filename + ".old"));
_resourceManager.UserData.Rename(CustomSoundfontDirectory,
CustomSoundfontDirectory.WithName(CustomSoundfontDirectory.Filename + ".old"));
_resourceManager.UserData.CreateDir(CustomSoundfontDirectory);
}
try
{
NFluidsynth.Logger.SetLoggerMethod(_loggerDelegate); // Will cause a safe DllNotFoundException if not available.
NFluidsynth.Logger
.SetLoggerMethod(_loggerDelegate); // Will cause a safe DllNotFoundException if not available.
_settings = new Settings();
_settings["synth.sample-rate"].DoubleValue = 44100;
@@ -193,7 +198,7 @@ internal sealed partial class MidiManager : IMidiManager
//_settings["synth.verbose"].IntValue = 1; // Useful for debugging.
var midiParallel = _cfgMan.GetCVar(CVars.MidiParallelism);
_settings["synth.polyphony"].IntValue = Math.Clamp(1024 + (int)(Math.Log2(midiParallel) * 2048), 1, 65535);
_settings["synth.polyphony"].IntValue = Math.Clamp(1024 + (int) (Math.Log2(midiParallel) * 2048), 1, 65535);
_settings["synth.cpu-cores"].IntValue = Math.Clamp(midiParallel, 1, 256);
_midiSawmill.Debug($"Synth Cores: {_settings["synth.cpu-cores"].IntValue}");
@@ -219,7 +224,7 @@ internal sealed partial class MidiManager : IMidiManager
};
_audioSys = _entityManager.EntitySysManager.GetEntitySystem<AudioSystem>();
_broadPhaseSystem = _entityManager.EntitySysManager.GetEntitySystem<SharedPhysicsSystem>();
_physics = _entityManager.EntitySysManager.GetEntitySystem<SharedPhysicsSystem>();
_xformSystem = _entityManager.System<SharedTransformSystem>();
_entityManager.GetEntityQuery<PhysicsComponent>();
_entityManager.GetEntityQuery<TransformComponent>();
@@ -263,7 +268,8 @@ internal sealed partial class MidiManager : IMidiManager
{
soundfontLoader.SetCallbacks(_soundfontLoaderCallbacks);
var renderer = new MidiRenderer(_settings!, soundfontLoader, mono, this, _audio, _taskManager, _midiSawmill);
var renderer =
new MidiRenderer(_settings!, soundfontLoader, mono, this, _audio, _taskManager, _midiSawmill);
LoadSoundFontSetup(renderer);
@@ -273,6 +279,7 @@ internal sealed partial class MidiManager : IMidiManager
{
_renderers.Add(renderer);
}
return renderer;
}
finally
@@ -309,99 +316,23 @@ internal sealed partial class MidiManager : IMidiManager
_updateSemaphore.Release();
_volumeDirty = false;
_gainDirty = false;
}
private void UpdateRenderer(IMidiRenderer renderer, MapCoordinates listener)
{
// TODO: This should be sharing more code with AudioSystem.
try
{
if (renderer.Disposed)
return;
if (_volumeDirty)
{
renderer.Source.Gain = Gain;
}
if (!renderer.Mono)
{
renderer.Source.Global = true;
return;
}
MapCoordinates mapPos;
if (renderer.TrackingEntity is {} trackedEntity && !_entityManager.Deleted(trackedEntity))
{
renderer.TrackingCoordinates = _xformSystem.GetMapCoordinates(renderer.TrackingEntity.Value);
// Pause it if the attached entity is paused.
if (_entityManager.IsPaused(renderer.TrackingEntity))
{
renderer.Source.Pause();
return;
}
}
else if (renderer.TrackingCoordinates == null)
{
renderer.Source.Pause();
return;
}
mapPos = renderer.TrackingCoordinates.Value;
// If it's on a different map then just mute it, not pause.
if (mapPos.MapId == MapId.Nullspace || mapPos.MapId != listener.MapId)
{
renderer.Source.Gain = 0f;
return;
}
// Was previously muted maybe so try unmuting it?
if (renderer.Source.Gain == 0f)
{
renderer.Source.Gain = Gain;
}
var worldPos = mapPos.Position;
var delta = worldPos - listener.Position;
var distance = delta.Length();
// Update position
// Out of range so just clip it for us.
if (distance > renderer.Source.MaxDistance)
{
// Still keeps the source playing, just with no volume.
renderer.Source.Gain = 0f;
return;
}
// Same imprecision suppression as audiosystem.
if (distance > 0f && distance < 0.01f)
{
worldPos = listener.Position;
delta = Vector2.Zero;
distance = 0f;
}
renderer.Source.Position = worldPos;
// Update velocity (doppler).
if (!_entityManager.Deleted(renderer.TrackingEntity))
{
var velocity = _broadPhaseSystem.GetMapLinearVelocity(renderer.TrackingEntity.Value);
renderer.Source.Velocity = velocity;
}
if (!renderer.Source.Global)
UpdateLocalRenderer(renderer, listener);
else
{
renderer.Source.Velocity = Vector2.Zero;
}
// Update occlusion
var occlusion = _audioSys.GetOcclusion(listener, delta, distance, renderer.TrackingEntity);
renderer.Source.Occlusion = occlusion;
UpdateGlobalRenderer(renderer);
}
catch (Exception ex)
{
@@ -409,6 +340,58 @@ internal sealed partial class MidiManager : IMidiManager
}
}
private void UpdateLocalRenderer(IMidiRenderer renderer, MapCoordinates listener)
{
if (_entityManager.Deleted(renderer.TrackingEntity) || _entityManager.IsPaused(renderer.TrackingEntity))
{
renderer.Source.Gain = 0f;
return;
}
MapCoordinates mapCoords = _xformSystem.GetMapCoordinates(renderer.TrackingEntity.Value);
renderer.TrackingCoordinates = mapCoords;
if (mapCoords.MapId == MapId.Nullspace || mapCoords.MapId != listener.MapId)
{
renderer.Source.Gain = 0f;
return;
}
Vector2 mapPosition = mapCoords.Position;
Vector2 listenerDelta = mapPosition - listener.Position;
var listenerDeltaLength = listenerDelta.Length();
if (listenerDeltaLength > renderer.Source.MaxDistance)
{
renderer.Source.Gain = 0f;
return;
}
if (listenerDeltaLength is > 0f and < 0.01f)
{
mapPosition = listener.Position;
listenerDelta = Vector2.Zero;
listenerDeltaLength = 0f;
}
if (_gainDirty || renderer.Source.Gain == 0f)
renderer.Source.Gain = Gain;
renderer.Source.Position = mapPosition;
renderer.Source.Velocity = _physics.GetMapLinearVelocity(renderer.TrackingEntity.Value);
renderer.Source.Occlusion =
_audioSys.GetOcclusion(listener, listenerDelta, listenerDeltaLength, renderer.TrackingEntity);
}
private void UpdateGlobalRenderer(IMidiRenderer renderer)
{
if (_gainDirty)
renderer.Source.Gain = Gain;
}
/// <summary>
/// Main method for the thread rendering the midi audio.
/// </summary>
@@ -428,7 +411,7 @@ internal sealed partial class MidiManager : IMidiManager
{
if (!renderer.Disposed)
{
if (renderer.Master is { Disposed: true })
if (renderer.Master is {Disposed: true})
renderer.Master = null;
renderer.Render();

View File

@@ -214,6 +214,11 @@ internal sealed partial class MidiRenderer : IMidiRenderer
[ViewVariables]
public BitArray FilteredChannels { get; } = new(RobustMidiEvent.MaxChannels);
[ViewVariables]
public byte MinVolume { get => _minVolume; set => _minVolume = value; }
private byte _minVolume;
[ViewVariables(VVAccess.ReadWrite)]
public byte? VelocityOverride { get; set; } = null;
@@ -539,14 +544,7 @@ internal sealed partial class MidiRenderer : IMidiRenderer
if (velocity <= 0)
continue;
try
{
_synth.NoteOn(channel, key, velocity);
}
catch (FluidSynthInteropException e)
{
_midiSawmill.Error($"CH:{channel} KEY:{key} VEL:{velocity} {e.ToStringBetter()}");
}
_synth.TryNoteOn(channel, key, velocity);
}
}
@@ -574,19 +572,32 @@ internal sealed partial class MidiRenderer : IMidiRenderer
{
case RobustMidiCommand.NoteOff:
_rendererState.NoteVelocities.AsSpan[midiEvent.Channel].AsSpan[midiEvent.Key] = 0;
_synth.NoteOff(midiEvent.Channel, midiEvent.Key);
break;
_synth.TryNoteOff(midiEvent.Channel, midiEvent.Key);
break;
case RobustMidiCommand.NoteOn:
// Velocity 0 *can* represent a NoteOff event.
var velocity = midiEvent.Velocity;
if (velocity == 0)
{
_rendererState.NoteVelocities.AsSpan[midiEvent.Channel].AsSpan[midiEvent.Key] = 0;
_synth.TryNoteOn(midiEvent.Channel, midiEvent.Key, velocity);
break;
}
if (FilteredChannels[midiEvent.Channel])
break;
var velocity = VelocityOverride ?? midiEvent.Velocity;
if (MinVolume > 0)
velocity = (byte)Math.Floor(MathHelper.Lerp(MinVolume, 127, (float)velocity / 127));
velocity = VelocityOverride ?? velocity;
_rendererState.NoteVelocities.AsSpan[midiEvent.Channel].AsSpan[midiEvent.Key] = velocity;
_synth.NoteOn(midiEvent.Channel, midiEvent.Key, velocity);
break;
_synth.TryNoteOn(midiEvent.Channel, midiEvent.Key, velocity);
break;
case RobustMidiCommand.AfterTouch:
_rendererState.NoteVelocities.AsSpan[midiEvent.Channel].AsSpan[midiEvent.Key] = midiEvent.Value;
_synth.KeyPressure(midiEvent.Channel, midiEvent.Key, midiEvent.Value);

View File

@@ -1,7 +1,6 @@
using System;
using System.Numerics;
using OpenTK.Audio.OpenAL;
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
using Robust.Shared.Audio;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
@@ -77,7 +76,7 @@ internal sealed class AudioSource : BaseAudioSource
else
{
if (FilterHandle != 0)
EFX.DeleteFilter(FilterHandle);
ALC.EFX.DeleteFilter(FilterHandle);
AL.DeleteSource(SourceHandle);
Master.RemoveAudioSource(SourceHandle);

View File

@@ -1,7 +1,6 @@
using System;
using System.Numerics;
using OpenTK.Audio.OpenAL;
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
using Robust.Client.Audio.Effects;
using Robust.Shared.Audio.Effects;
using Robust.Shared.Audio.Sources;
@@ -82,9 +81,9 @@ public abstract class BaseAudioSource : IAudioSource
get
{
_checkDisposed();
var state = AL.GetSourceState(SourceHandle);
var state = AL.GetSource(SourceHandle, ALGetSourcei.SourceState);
Master._checkAlError();
return state == ALSourceState.Playing;
return state == (int)ALSourceState.Playing;
}
set
{
@@ -362,11 +361,11 @@ public abstract class BaseAudioSource : IAudioSource
if (audio is AuxiliaryAudio impAudio)
{
EFX.Source(SourceHandle, EFXSourceInteger3.AuxiliarySendFilter, impAudio.Handle, 0, 0);
ALC.EFX.Source(SourceHandle, EFXSourceInteger3.AuxiliarySendFilter, impAudio.Handle, 0, 0);
}
else
{
EFX.Source(SourceHandle, EFXSourceInteger3.AuxiliarySendFilter, 0, 0, 0);
ALC.EFX.Source(SourceHandle, EFXSourceInteger3.AuxiliarySendFilter, 0, 0, 0);
}
Master._checkAlError();
@@ -376,12 +375,12 @@ public abstract class BaseAudioSource : IAudioSource
{
if (FilterHandle == 0)
{
FilterHandle = EFX.GenFilter();
EFX.Filter(FilterHandle, FilterInteger.FilterType, (int) FilterType.Lowpass);
FilterHandle = ALC.EFX.GenFilter();
ALC.EFX.Filter(FilterHandle, FilterInteger.FilterType, (int) FilterType.Lowpass);
}
EFX.Filter(FilterHandle, FilterFloat.LowpassGain, gain);
EFX.Filter(FilterHandle, FilterFloat.LowpassGainHF, cutoff);
ALC.EFX.Filter(FilterHandle, FilterFloat.LowpassGain, gain);
ALC.EFX.Filter(FilterHandle, FilterFloat.LowpassGainHF, cutoff);
AL.Source(SourceHandle, ALSourcei.EfxDirectFilter, FilterHandle);
}

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using OpenTK.Audio.OpenAL;
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
using Robust.Shared.Audio.Sources;
namespace Robust.Client.Audio.Sources;
@@ -37,9 +36,9 @@ internal sealed class BufferedAudioSource : BaseAudioSource, IBufferedAudioSourc
get
{
_checkDisposed();
var state = AL.GetSourceState(SourceHandle);
var state = AL.GetSource(SourceHandle, ALGetSourcei.SourceState);
_master._checkAlError();
return state == ALSourceState.Playing;
return state == (int)ALSourceState.Playing;
}
set
{
@@ -84,7 +83,7 @@ internal sealed class BufferedAudioSource : BaseAudioSource, IBufferedAudioSourc
else
{
if (FilterHandle != 0)
EFX.DeleteFilter(FilterHandle);
ALC.EFX.DeleteFilter(FilterHandle);
AL.DeleteSource(SourceHandle);
AL.DeleteBuffers(BufferHandles);

View File

@@ -144,6 +144,7 @@ namespace Robust.Client
deps.Register<IViewVariablesManager, ClientViewVariablesManager>();
deps.Register<IClientViewVariablesManager, ClientViewVariablesManager>();
deps.Register<IClientViewVariablesManagerInternal, ClientViewVariablesManager>();
deps.Register<IViewVariableControlFactory, ViewVariableControlFactory>();
deps.Register<IClientConGroupController, ClientConGroupController>();
deps.Register<IScriptClient, ScriptClient>();
deps.Register<IRobustSerializer, ClientRobustSerializer>();

View File

@@ -71,7 +71,7 @@ internal sealed class ClientNetConfigurationManager : NetConfigurationManager, I
// Actually set the CVar
base.SetCVar(name, value, force);
if ((flags & CVar.REPLICATED) == 0)
if ((flags & CVar.REPLICATED) == 0 || !NetManager.IsConnected)
return;
var msg = new MsgConVars();

View File

@@ -4,7 +4,7 @@ using Robust.Shared.ContentPack;
namespace Robust.Client.Console.Commands
{
#if DEBUG
#if TOOLS
internal sealed class DumpMetadataMembersCommand : LocalizedCommands
{
public override string Command => "dmetamem";

View File

@@ -0,0 +1,124 @@
using System;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Maths;
using ItemJustification = Robust.Client.UserInterface.Controls.WrapContainer.ItemJustification;
namespace Robust.Client.Console.Commands;
internal sealed partial class UITestControl
{
private sealed class TabWrapContainer : Control
{
private readonly CheckBox _equalSizeBox;
private readonly CheckBox _reverseBox;
private readonly OptionButton _axisButton;
private readonly OptionButton _justifyButton;
private readonly LineEdit _separationEdit;
private readonly LineEdit _crossSeparationEdit;
public TabWrapContainer()
{
var container = new WrapContainer
{
MouseFilter = MouseFilterMode.Stop,
VerticalExpand = true,
};
var random = new Random(3005);
for (var i = 0; i < 35; i++)
{
var val = random.Next(1, 16);
var text = string.Create(val, 0, (span, _) => span.Fill('O'));
container.AddChild(new Button { Text = text });
}
AddChild(new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Vertical,
Children =
{
new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
SeparationOverride = 4,
Children =
{
(_equalSizeBox = new CheckBox
{
Text = nameof(WrapContainer.EqualSize)
}),
(_reverseBox = new CheckBox
{
Text = nameof(WrapContainer.Reverse)
}),
(_axisButton = new OptionButton()),
(_justifyButton = new OptionButton()),
(_separationEdit = new LineEdit
{
PlaceHolder = "Separation",
SetWidth = 100,
}),
(_crossSeparationEdit = new LineEdit
{
PlaceHolder = "Cross Separation",
SetWidth = 100,
})
}
},
new PanelContainer
{
PanelOverride = new StyleBoxFlat { BackgroundColor = Color.Black },
Children =
{
container
}
}
},
});
_axisButton.AddItem(nameof(Axis.Horizontal), (int)Axis.Horizontal);
_axisButton.AddItem(nameof(Axis.HorizontalReverse), (int)Axis.HorizontalReverse);
_axisButton.AddItem(nameof(Axis.Vertical), (int)Axis.Vertical);
_axisButton.AddItem(nameof(Axis.VerticalReverse), (int)Axis.VerticalReverse);
_axisButton.OnItemSelected += args =>
{
_axisButton.SelectId(args.Id);
container.LayoutAxis = (Axis)args.Id;
};
_justifyButton.AddItem(nameof(ItemJustification.Begin), (int)ItemJustification.Begin);
_justifyButton.AddItem(nameof(ItemJustification.Center), (int)ItemJustification.Center);
_justifyButton.AddItem(nameof(ItemJustification.End), (int)ItemJustification.End);
_justifyButton.OnItemSelected += args =>
{
_justifyButton.SelectId(args.Id);
container.Justification = (ItemJustification)args.Id;
};
_equalSizeBox.OnPressed += _ => container.EqualSize = _equalSizeBox.Pressed;
_reverseBox.OnPressed += _ => container.Reverse = _reverseBox.Pressed;
_separationEdit.OnTextChanged += args =>
{
if (!int.TryParse(args.Text, out var sep))
sep = 0;
container.SeparationOverride = sep;
};
_crossSeparationEdit.OnTextChanged += args =>
{
if (!int.TryParse(args.Text, out var sep))
sep = 0;
container.CrossSeparationOverride = sep;
};
}
}
}

View File

@@ -4,8 +4,6 @@ using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Console;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
@@ -44,7 +42,10 @@ Suspendisse hendrerit blandit urna ut laoreet. Suspendisse ac elit at erat males
var progressBar = new ProgressBar { MaxValue = 10, Value = 5 };
vBox.AddChild(progressBar);
var optionButton = new OptionButton();
var optionButton = new OptionButton
{
ToolTip = "This button has a tooltip. Spooky!"
};
optionButton.AddItem("Honk");
optionButton.AddItem("Foo");
optionButton.AddItem("Bar");
@@ -154,6 +155,8 @@ Suspendisse hendrerit blandit urna ut laoreet. Suspendisse ac elit at erat males
_sprite = new TabSpriteView();
_tabContainer.AddChild(_sprite);
_tabContainer.AddChild(TabCursorShapes());
_tabContainer.AddChild(new TabWrapContainer { Name = nameof(Tab.WrapContainer) });
}
public void OnClosed()
@@ -210,6 +213,53 @@ Suspendisse hendrerit blandit urna ut laoreet. Suspendisse ac elit at erat males
return label;
}
private Control TabCursorShapes()
{
var box = new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Vertical,
};
var styleBox = new StyleBoxFlat
{
BackgroundColor = Color.Black
};
foreach (var cursorName in Enum.GetNames<CursorShape>())
{
// Go over names due to duplicate definitions in the enum.
var cursor = Enum.Parse<CursorShape>(cursorName);
// Wow was I bad at API design.
if (cursor == CursorShape.Custom)
continue;
var panel = new PanelContainer
{
PanelOverride = styleBox,
DefaultCursorShape = cursor,
MouseFilter = MouseFilterMode.Stop,
MinHeight = 30,
Children =
{
new Label
{
Text = cursorName,
VerticalAlignment = VAlignment.Center,
Margin = new Thickness(4)
}
}
};
box.AddChild(panel);
}
return new ScrollContainer
{
Children = { box },
VScrollEnabled = true,
HScrollEnabled = false,
Name = nameof(Tab.TabCursorShapes),
};
}
public void SelectTab(Tab tab)
{
_tabContainer.CurrentTab = (int)tab;
@@ -226,32 +276,14 @@ Suspendisse hendrerit blandit urna ut laoreet. Suspendisse ac elit at erat males
TextEdit = 6,
RichText = 7,
SpriteView = 8,
TabCursorShapes = 9,
WrapContainer = 10,
}
}
internal sealed class UITestCommand : LocalizedCommands
internal abstract class BaseUITestCommand : LocalizedCommands
{
public override string Command => "uitest";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
var window = new DefaultWindow { MinSize = new(800, 600) };
var control = new UITestControl();
window.OnClose += control.OnClosed;
window.Contents.AddChild(control);
window.OpenCentered();
}
}
internal sealed class UITest2Command : LocalizedCommands
{
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly IUserInterfaceManager _uiMgr = default!;
public override string Command => "uitest2";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
public sealed override void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length > 1)
{
@@ -272,18 +304,10 @@ internal sealed class UITest2Command : LocalizedCommands
control.SelectTab(tab);
}
var window = _clyde.CreateWindow(new WindowCreateParameters
{
Title = Loc.GetString("cmd-uitest2-title"),
});
var root = _uiMgr.CreateWindowRoot(window);
window.DisposeOnClose = true;
window.RequestClosed += _ => control.OnClosed();
root.AddChild(control);
CreateWindow(control);
}
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
public sealed override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
{
if (args.Length == 1)
{
@@ -294,4 +318,35 @@ internal sealed class UITest2Command : LocalizedCommands
return CompletionResult.Empty;
}
protected abstract void CreateWindow(UITestControl control);
}
internal sealed class UITestCommand : BaseUITestCommand
{
public override string Command => "uitest";
protected override void CreateWindow(UITestControl control)
{
var window = new DefaultWindow { MinSize = new(800, 600) };
window.OnClose += control.OnClosed;
window.Contents.AddChild(control);
window.OpenCentered();
}
}
internal sealed class UITest2Command : BaseUITestCommand
{
public override string Command => "uitest2";
protected override void CreateWindow(UITestControl control)
{
var window = new OSWindow
{
Title = Loc.GetString("cmd-uitest2-title"),
};
window.AddChild(control);
window.Closed += control.OnClosed;
window.Show();
}
}

View File

@@ -0,0 +1,44 @@
#if TOOLS
using Robust.Client.Graphics;
using Robust.Shared.Console;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
namespace Robust.Client.Console.Commands;
internal sealed class ViewportClearAllCachedCommand : IConsoleCommand
{
[Dependency] private readonly IClydeInternal _clyde = default!;
public string Command => "vp_clear_all_cached";
public string Description => "Fires IClydeViewport.ClearCachedResources on all viewports";
public string Help => "";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
_clyde.ViewportsClearAllCached();
}
}
internal sealed class ViewportTestFinalizeCommand : IConsoleCommand
{
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
public string Command => "vp_test_finalize";
public string Description => "Creates a viewport, renders it once, then leaks it (finalizes it).";
public string Help => "";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var vp = _clyde.CreateViewport(new Vector2i(1920, 1080), nameof(ViewportTestFinalizeCommand));
vp.Eye = _eyeManager.CurrentEye;
vp.Render();
// Leak it.
}
}
#endif // TOOLS

View File

@@ -85,7 +85,7 @@ namespace Robust.Client.Console
MouseFilter = MouseFilterMode.Stop;
Result = result;
var compl = new FormattedMessage();
var dim = Color.FromHsl((0f, 0f, 0.8f, 1f));
var dim = Color.FromHsl(new Vector4(0f, 0f, 0.8f, 1f));
// warning: ew ahead
string basen = "default";

View File

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

View File

@@ -8,6 +8,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using SDL3;
namespace Robust.Client
{
@@ -93,6 +94,8 @@ namespace Robust.Client
public void Run(DisplayMode mode, GameControllerOptions options, Func<ILogHandler>? logHandlerFactory = null)
{
_displayMode = mode;
if (!StartupSystemSplash(options, logHandlerFactory))
{
_logger.Fatal("Failed to start game controller!");

View File

@@ -110,6 +110,8 @@ namespace Robust.Client
private ResourceManifestData? _resourceManifest;
private DisplayMode _displayMode;
public void SetCommandLineArgs(CommandLineArgs args)
{
_commandLineArgs = args;
@@ -273,6 +275,9 @@ namespace Robust.Client
}
};
_configurationManager.OnValueChanged(CVars.DisplayMaxFPS, _ => UpdateVsyncConfig());
_configurationManager.OnValueChanged(CVars.DisplayVSync, _ => UpdateVsyncConfig(), invokeImmediately: true);
_clyde.Ready();
if (_resourceManifest!.AutoConnect &&
@@ -387,7 +392,7 @@ namespace Robust.Client
_prof.Initialize();
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null, hideUserDataDir: true);
var mountOptions = _commandLineArgs != null
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions)
@@ -709,6 +714,30 @@ namespace Robust.Client
}
private void UpdateVsyncConfig()
{
if (_displayMode == DisplayMode.Headless)
return;
var vsync = _configurationManager.GetCVar(CVars.DisplayVSync);
var maxFps = Math.Clamp(_configurationManager.GetCVar(CVars.DisplayMaxFPS), 0, 10_000);
_clyde.VsyncEnabled = vsync;
if (_mainLoop == null)
return;
if (vsync || maxFps == 0)
{
_mainLoop.SleepMode = SleepMode.None;
}
else
{
_mainLoop.SleepMode = SleepMode.Limit;
_mainLoop.LimitMinFrameTime = TimeSpan.FromSeconds(1.0 / maxFps);
}
}
internal enum DisplayMode : byte
{
Headless,

View File

@@ -29,6 +29,9 @@ namespace Robust.Client.GameObjects
internal event Action? AfterStartup;
internal event Action? AfterShutdown;
private readonly Queue<EntityUid> _queuedPredictedDeletions = new();
private readonly HashSet<EntityUid> _queuedPredictedDeletionsSet = new();
public override void Initialize()
{
SetupNetworking();
@@ -213,6 +216,34 @@ namespace Robust.Client.GameObjects
}
}
using (histogram?.WithLabels("PredictedQueueDel").NewTimer())
{
while (_queuedPredictedDeletions.TryDequeue(out var uid))
{
if (!MetaQuery.TryGetComponentInternal(uid, out var meta))
continue;
if (meta.EntityLifeStage >= EntityLifeStage.Terminating)
continue;
var xform = TransformQuery.GetComponentInternal(uid);
if (meta.NetEntity.IsClientSide())
{
DeleteEntity(uid, meta, xform);
}
else
{
_xforms.DetachEntity(uid, xform, meta, null);
// base call bypasses IGameTiming.InPrediction check
// This is pretty janky and there should be a way for the client to dirty an entity outside of prediction
// TODO PREDICTION
base.Dirty(uid, xform, meta);
}
}
_queuedPredictedDeletionsSet.Clear();
}
base.TickUpdate(frameTime, noPredictions, histogram);
}
@@ -317,18 +348,23 @@ namespace Robust.Client.GameObjects
}
}
/// <inheritdoc />
public override void PredictedQueueDeleteEntity(Entity<MetaDataComponent?, TransformComponent?> ent)
{
if (IsQueuedForDeletion(ent.Owner)
|| !MetaQuery.Resolve(ent.Owner, ref ent.Comp1)
|| ent.Comp1.EntityLifeStage >= EntityLifeStage.Terminating
|| !TransformQuery.Resolve(ent.Owner, ref ent.Comp2))
{
return;
}
public override bool IsQueuedForDeletion(EntityUid uid)
=> QueuedDeletionsSet.Contains(uid) || _queuedPredictedDeletions.Contains(uid);
if (ent.Comp1.NetEntity.IsClientSide())
/// <inheritdoc />
public override void PredictedQueueDeleteEntity(Entity<MetaDataComponent?> ent)
{
// Some UIs get disposed after entity-manager has shut down and already deleted all entities.
if (!Started)
return;
if (IsQueuedForDeletion(ent.Owner))
return;
if (!MetaQuery.Resolve(ent.Owner, ref ent.Comp, false))
return;
if (ent.Comp.NetEntity.IsClientSide())
{
// client-side QueueDeleteEntity re-fetches MetadataComp and checks IsClientSide().
// base call to skip that.
@@ -337,7 +373,10 @@ namespace Robust.Client.GameObjects
}
else
{
_xforms.DetachEntity(ent.Owner, ent.Comp2);
if (!_queuedPredictedDeletionsSet.Add(ent.Owner))
return;
_queuedPredictedDeletions.Enqueue(ent.Owner);
}
}
}

View File

@@ -30,8 +30,6 @@ using Robust.Shared.ViewVariables;
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;
using SysVec4 = System.Numerics.Vector4;
#pragma warning disable CS0618 // Type or member is obsolete
namespace Robust.Client.GameObjects
@@ -296,6 +294,16 @@ namespace Robust.Client.GameObjects
LocalMatrix = Matrix3Helpers.CreateTransform(in offset, in rotation, in scale);
}
/// <summary>
/// If false, this will prevent any of this sprite's animated layers from looping their animation.
/// This will set <see cref="Layer.AutoAnimated"/> whenever any layer's animation finishes.
/// </summary>
/// <remarks>
/// If this is false, this effectively overrides each layer's own <see cref="Layer.Loop"/>.
/// </remarks>
[DataField]
public bool Loop = true;
/// <summary>
/// Update this sprite component to visibly match the current state of other at the time
/// this is called. Does not keep them perpetually in sync.
@@ -603,6 +611,7 @@ namespace Robust.Client.GameObjects
layer.RenderingStrategy = layerDatum.RenderingStrategy ?? layer.RenderingStrategy;
layer.Cycle = layerDatum.Cycle;
layer.Loop = layerDatum.Loop;
layer.Color = layerDatum.Color ?? layer.Color;
layer._rotation = layerDatum.Rotation ?? layer._rotation;
@@ -1159,6 +1168,15 @@ namespace Robust.Client.GameObjects
/// </remarks>
[ViewVariables] public bool Cycle;
/// <summary>
/// If false, this will prevent the layer's animation from looping.
/// This will set <see cref="AutoAnimated"/> to false once the animation finishes.
/// </summary>
/// <remarks>
/// This may be overriden by the parent's loop property.
/// </remarks>
[ViewVariables] public bool Loop = true;
// TODO SPRITE ACCESS
internal RSI.State? _actualState;
[ViewVariables] public RSI.State? ActualState => _actualState;
@@ -1338,6 +1356,8 @@ namespace Robust.Client.GameObjects
DirOffset = toClone.DirOffset;
_autoAnimated = toClone._autoAnimated;
RenderingStrategy = toClone.RenderingStrategy;
Cycle = toClone.Cycle;
Loop = toClone.Loop;
if (toClone.CopyToShaderParameters is { } copyToShaderParameters)
CopyToShaderParameters = new CopyToShaderParameters(copyToShaderParameters);
}
@@ -1665,17 +1685,25 @@ namespace Robust.Client.GameObjects
internal void AdvanceFrameAnimation(RSI.State state)
{
// Can't advance frames without more than 1 delay which is already checked above.
var delayCount = state.DelayCount;
while (AnimationTimeLeft < 0)
{
if (Reversed)
{
AnimationFrame -= 1;
// Animation finished, do we cycle back to positive or reset.
if (AnimationFrame < 0)
{
if (!Loop || !_parent.Loop)
{
// stop at first frame
AnimationFrame = 0;
AnimationTimeLeft = 0;
AutoAnimated = false;
return;
}
if (Cycle)
{
AnimationFrame = 1;
@@ -1693,9 +1721,17 @@ namespace Robust.Client.GameObjects
{
AnimationFrame += 1;
// Animation finished, do we reverse or reset.
if (AnimationFrame >= delayCount)
{
if (!Loop || !_parent.Loop)
{
// stop at last frame
AnimationFrame = delayCount - 1;
AnimationTimeLeft = 0;
AutoAnimated = false;
return;
}
if (Cycle)
{
AnimationFrame = delayCount - 2;
@@ -1713,6 +1749,7 @@ namespace Robust.Client.GameObjects
AnimationTimeLeft += state.GetDelay(AnimationFrame);
}
}
}
/// <summary>

View File

@@ -1,3 +1,4 @@
using System.Diagnostics.Contracts;
using Robust.Client.Graphics;
using Robust.Client.Map;
using Robust.Client.ResourceManagement;
@@ -9,13 +10,14 @@ namespace Robust.Client.GameObjects;
public sealed class MapSystem : SharedMapSystem
{
protected override MapId GetNextMapId()
[Pure]
internal override MapId GetNextMapId()
{
// Client-side map entities use negative map Ids to avoid conflict with server-side maps.
var id = new MapId(--LastMapId);
var id = new MapId(LastMapId - 1);
while (MapExists(id) || UsedIds.Contains(id))
{
id = new MapId(--LastMapId);
id = new MapId(id.Value - 1);
}
return id;
}

View File

@@ -29,6 +29,8 @@ namespace Robust.Client.GameObjects
component.Enabled = state.Enabled;
component.Offset = state.Offset;
component.Softness = state.Softness;
component.Falloff = state.Falloff;
component.CurveFactor = state.CurveFactor;
component.CastShadows = state.CastShadows;
component.Energy = state.Energy;
component.Radius = state.Radius;

View File

@@ -1,25 +0,0 @@
using System.Numerics;
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
namespace Robust.Client.GameObjects;
public sealed class ScaleVisualsSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ScaleVisualsComponent, AppearanceChangeEvent>(OnChangeData);
}
private void OnChangeData(EntityUid uid, ScaleVisualsComponent component, ref AppearanceChangeEvent ev)
{
if (!ev.AppearanceData.TryGetValue(ScaleVisuals.Scale, out var scale) ||
ev.Sprite == null) return;
var vecScale = (Vector2)scale;
// Set it directly because prediction may call this multiple times.
ev.Sprite.Scale = vecScale;
}
}

View File

@@ -32,7 +32,7 @@ public sealed partial class SpriteSystem
bounds = bounds.Union(GetLocalBounds(layer));
}
sprite.Comp._bounds = bounds;
sprite.Comp._bounds = bounds.Scale(sprite.Comp.Scale);
sprite.Comp.BoundsDirty = false;
return sprite.Comp._bounds;
}

View File

@@ -88,6 +88,7 @@ public sealed partial class SpriteSystem
target.Comp.RenderOrder = source.Comp.RenderOrder;
target.Comp.GranularLayersRendering = source.Comp.GranularLayersRendering;
target.Comp.Loop = source.Comp.Loop;
DirtyBounds(target!);
_tree.QueueTreeUpdate(target!);

View File

@@ -7,8 +7,6 @@ using Robust.Shared.Graphics.RSI;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using static Robust.Client.GameObjects.SpriteComponent;
using Vector4 = Robust.Shared.Maths.Vector4;
using SysVec4 = System.Numerics.Vector4;
namespace Robust.Client.GameObjects;
@@ -157,7 +155,7 @@ public sealed partial class SpriteSystem
// Negative color modulation values are by the default shader to disable light shading.
// Specifically we set colour = - 1 - colour
// This is good enough to ensure that non-negative values become negative & is trivially invertible.
layerColor = new(new SysVec4(-1) - layerColor.RGBA);
layerColor = new(new Vector4(-1) - layerColor.RGBA);
}
drawingHandle.DrawTextureRectRegion(texture, quad, layerColor);

View File

@@ -220,24 +220,34 @@ Had full state: {LastFullState != null}"
{
var compState = change.State;
if (compState is IComponentDeltaState delta
&& compData.TryGetValue(change.NetID, out var old)) // May fail if relying on implicit data
if (compState is not IComponentDeltaState delta)
{
DebugTools.Assert(old is not IComponentDeltaState, "last state is not a full state");
if (cloneDelta)
{
compState = delta.CreateNewFullState(old!);
}
else
{
delta.ApplyToFullState(old!);
compState = old;
}
DebugTools.Assert(compState is not IComponentDeltaState, "newly constructed state is not a full state");
compData[change.NetID] = compState;
continue;
}
compData[change.NetID] = compState;
if (!compData.TryGetValue(change.NetID, out var old))
{
// Either the server needs to ensure that the initial state it sends to a client is a full
// state, or the client needs to be able to construct an implicit full state (i.e., get-state
// code needs to be in shared code).
//
// Without this, the client won't be able to reset predicted changes made to this component.
DebugTools.Assert("Received delta state without having received or constructed an implicit full state");
continue;
}
DebugTools.Assert(old is not IComponentDeltaState, "last state is not a full state");
if (!cloneDelta)
{
delta.ApplyToFullState(old!);
continue;
}
var newFull = delta.CreateNewFullState(old!);
compData[change.NetID] = newFull;
DebugTools.Assert(newFull is not IComponentDeltaState, "constructed state is not a full state");
}
if (entityState.NetComponents == null)

View File

@@ -13,6 +13,8 @@ namespace Robust.Client.GameStates
{
internal sealed class NetInterpOverlay : Overlay
{
private static readonly ProtoId<ShaderPrototype> UnshadedShader = "unshaded";
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
@@ -32,7 +34,7 @@ namespace Robust.Client.GameStates
{
IoCManager.InjectDependencies(this);
_lookup = lookup;
_shader = _prototypeManager.Index<ShaderPrototype>("unshaded").Instance();
_shader = _prototypeManager.Index(UnshadedShader).Instance();
_container = _entityManager.System<SharedContainerSystem>();
_xform = _entityManager.System<SharedTransformSystem>();
}

View File

@@ -1,6 +1,10 @@
using System;
using System.Runtime.InteropServices;
using Robust.Shared;
using Robust.Shared.Log;
#if WINDOWS
using TerraFX.Interop.Windows;
using TerraFX.Interop.DirectX;
#endif
namespace Robust.Client.Graphics.Clyde
{
@@ -13,6 +17,8 @@ namespace Robust.Client.Graphics.Clyde
private void InitGLContextManager()
{
CheckForceCompatMode();
// Advanced GL contexts currently disabled due to lack of testing etc.
if (OperatingSystem.IsWindows() && _cfg.GetCVar(CVars.DisplayAngle))
{
@@ -55,6 +61,74 @@ namespace Robust.Client.Graphics.Clyde
_glContext = new GLContextWindow(this);
}
private void CheckForceCompatMode()
{
#if WINDOWS
// Qualcomm (Snapdragon/Adreno) devices have broken OpenGL drivers on Windows.
if (CheckIsQualcommDevice())
{
_sawmillOgl.Info("We appear to be on a Qualcomm device. Enabling compat mode due to broken OpenGL driver");
_cfg.OverrideDefault(CVars.DisplayCompat, true);
}
#endif
}
#if WINDOWS
private static unsafe bool CheckIsQualcommDevice()
{
// Ideally we would check the OpenGL driver instead... but OpenGL is terrible so that's impossible.
// Let's just check with DXGI instead.
IDXGIFactory1* dxgiFactory;
ThrowIfFailed(
nameof(DirectX.CreateDXGIFactory1),
DirectX.CreateDXGIFactory1(Windows.__uuidof<IDXGIFactory1>(), (void**) &dxgiFactory));
try
{
uint idx = 0;
IDXGIAdapter* adapter;
while (dxgiFactory->EnumAdapters(idx, &adapter) != DXGI.DXGI_ERROR_NOT_FOUND)
{
try
{
DXGI_ADAPTER_DESC desc;
ThrowIfFailed("GetDesc", adapter->GetDesc(&desc));
var descString = ((ReadOnlySpan<char>)desc.Description).TrimEnd('\0');
if (descString.Contains("qualcomm", StringComparison.OrdinalIgnoreCase) ||
descString.Contains("snapdragon", StringComparison.OrdinalIgnoreCase) ||
descString.Contains("adreno", StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
finally
{
adapter->Release();
}
idx += 1;
}
}
finally
{
dxgiFactory->Release();
}
return false;
}
private static void ThrowIfFailed(string methodName, HRESULT hr)
{
if (Windows.FAILED(hr))
{
Marshal.ThrowExceptionForHR(hr);
}
}
#endif
private struct GLContextSpec
{
public int Major;

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Client.ResourceManagement;
using Robust.Shared.Enums;

View File

@@ -121,6 +121,19 @@ namespace Robust.Client.Graphics.Clyde
}
}
public void RenderNow(IRenderTarget renderTarget, Action<IRenderHandle> callback)
{
ClearRenderState();
_renderHandle.RenderInRenderTarget(
renderTarget,
() =>
{
callback(_renderHandle);
},
null);
}
private void RenderSingleWorldOverlay(Overlay overlay, Viewport vp, OverlaySpace space, in Box2 worldBox, in Box2Rotated worldBounds)
{
// Check that entity manager has started.

View File

@@ -4,7 +4,6 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using JetBrains.Annotations;
using Robust.Shared.Maths;
using Vector3 = Robust.Shared.Maths.Vector3;
namespace Robust.Client.Graphics.Clyde
{

View File

@@ -18,7 +18,6 @@ using Robust.Shared.Graphics;
using static Robust.Shared.GameObjects.OccluderComponent;
using Robust.Shared.Utility;
using TextureWrapMode = Robust.Shared.Graphics.TextureWrapMode;
using Vector4 = Robust.Shared.Maths.Vector4;
namespace Robust.Client.Graphics.Clyde
{
@@ -452,6 +451,8 @@ namespace Robust.Client.Graphics.Clyde
var lastPower = float.NaN;
var lastColor = new Color(float.NaN, float.NaN, float.NaN, float.NaN);
var lastSoftness = float.NaN;
var lastFalloff = float.NaN;
var lastCurveFactor = float.NaN;
Texture? lastMask = null;
using (_prof.Group("Draw Lights"))
@@ -505,6 +506,18 @@ namespace Robust.Client.Graphics.Clyde
lightShader.SetUniformMaybe("lightSoftness", lastSoftness);
}
if (!MathHelper.CloseToPercent(lastFalloff, component.Falloff))
{
lastFalloff = component.Falloff;
lightShader.SetUniformMaybe("lightFalloff", lastFalloff);
}
if (!MathHelper.CloseToPercent(lastCurveFactor, component.CurveFactor))
{
lastCurveFactor = component.CurveFactor;
lightShader.SetUniformMaybe("lightCurveFactor", lastCurveFactor);
}
lightShader.SetUniformMaybe("lightCenter", lightPos);
lightShader.SetUniformMaybe("lightIndex",
component.CastShadows ? (i + 0.5f) / ShadowTexture.Height : -1);

View File

@@ -209,6 +209,7 @@ namespace Robust.Client.Graphics.Clyde
var pressure = estPixSize * size.X * size.Y;
var handle = AllocRid();
var renderTarget = new RenderTexture(size, textureObject, this, handle);
var data = new LoadedRenderTarget
{
IsWindow = false,
@@ -220,10 +221,11 @@ namespace Robust.Client.Graphics.Clyde
MemoryPressure = pressure,
ColorFormat = format.ColorFormat,
SampleParameters = sampleParameters,
Instance = new WeakReference<RenderTargetBase>(renderTarget),
Name = name,
};
//GC.AddMemoryPressure(pressure);
var renderTarget = new RenderTexture(size, textureObject, this, handle);
_renderTargets.Add(handle, data);
return renderTarget;
}
@@ -301,10 +303,22 @@ namespace Robust.Client.Graphics.Clyde
}
}
private sealed class LoadedRenderTarget
public IEnumerable<(RenderTargetBase, LoadedRenderTarget)> GetLoadedRenderTextures()
{
foreach (var loaded in _renderTargets.Values)
{
if (!loaded.Instance.TryGetTarget(out var instance))
continue;
yield return (instance, loaded);
}
}
internal sealed class LoadedRenderTarget
{
public bool IsWindow;
public WindowId WindowId;
public string? Name;
public Vector2i Size;
public bool IsSrgb;
@@ -325,9 +339,11 @@ namespace Robust.Client.Graphics.Clyde
public long MemoryPressure;
public TextureSampleParameters? SampleParameters;
public required WeakReference<RenderTargetBase> Instance;
}
private abstract class RenderTargetBase : IRenderTarget
internal abstract class RenderTargetBase : IRenderTarget
{
protected readonly Clyde Clyde;
private bool _disposed;
@@ -389,7 +405,7 @@ namespace Robust.Client.Graphics.Clyde
}
}
private sealed class RenderTexture : RenderTargetBase, IRenderTexture
internal sealed class RenderTexture : RenderTargetBase, IRenderTexture
{
public RenderTexture(Vector2i size, ClydeTexture texture, Clyde clyde, ClydeHandle handle)
: base(clyde, handle)

View File

@@ -11,8 +11,6 @@ using Robust.Shared.Graphics;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using TKStencilOp = OpenToolkit.Graphics.OpenGL4.StencilOp;
using Vector3 = Robust.Shared.Maths.Vector3;
using Vector4 = Robust.Shared.Maths.Vector4;
namespace Robust.Client.Graphics.Clyde
{
@@ -541,7 +539,7 @@ namespace Robust.Client.Graphics.Clyde
case Matrix3x2 matrix3:
program.SetUniform(name, matrix3);
break;
case Matrix4 matrix4:
case Matrix4x4 matrix4:
program.SetUniform(name, matrix4);
break;
case ClydeTexture clydeTexture:

View File

@@ -10,8 +10,6 @@ using Robust.Shared.Graphics;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using Vector3 = Robust.Shared.Maths.Vector3;
using Vector4 = Robust.Shared.Maths.Vector4;
namespace Robust.Client.Graphics.Clyde
{
@@ -528,7 +526,7 @@ namespace Robust.Client.Graphics.Clyde
data.Parameters[name] = value;
}
private protected override void SetParameterImpl(string name, in Matrix4 value)
private protected override void SetParameterImpl(string name, in Matrix4x4 value)
{
var data = Parent._shaderInstances[Handle];
data.ParametersDirty = true;

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Numerics;
using Robust.Client.UserInterface.CustomControls;
@@ -15,10 +16,14 @@ namespace Robust.Client.Graphics.Clyde
private readonly Dictionary<ClydeHandle, WeakReference<Viewport>> _viewports =
new();
private long _nextViewportId = 1;
private readonly ConcurrentQueue<ViewportDisposeData> _viewportDisposeQueue = new();
private Viewport CreateViewport(Vector2i size, TextureSampleParameters? sampleParameters = default, string? name = null)
{
var handle = AllocRid();
var viewport = new Viewport(handle, name, this)
var viewport = new Viewport(_nextViewportId++, handle, name, this)
{
Size = size,
RenderTarget = CreateRenderTarget(size,
@@ -59,28 +64,43 @@ namespace Robust.Client.Graphics.Clyde
private void FlushViewportDispose()
{
// Free of allocations unless a dead viewport is found.
List<ClydeHandle>? toRemove = null;
foreach (var (handle, viewportRef) in _viewports)
while (_viewportDisposeQueue.TryDequeue(out var data))
{
if (!viewportRef.TryGetTarget(out _))
{
toRemove ??= new List<ClydeHandle>();
toRemove.Add(handle);
}
}
if (toRemove == null)
{
return;
}
foreach (var remove in toRemove)
{
_viewports.Remove(remove);
DisposeViewport(data);
}
}
private void DisposeViewport(ViewportDisposeData disposeData)
{
_clydeSawmill.Warning($"Viewport {disposeData.Id} got leaked");
_viewports.Remove(disposeData.Handle);
if (disposeData.ClearEvent is not { } clearEvent)
return;
try
{
clearEvent(disposeData.ClearEventData);
}
catch (Exception ex)
{
_clydeSawmill.Error($"Caught exception while disposing viewport: {ex}");
}
}
#if TOOLS
public void ViewportsClearAllCached()
{
foreach (var vpRef in _viewports.Values)
{
if (!vpRef.TryGetTarget(out var vp))
continue;
vp.FireClear();
}
}
#endif // TOOLS
private sealed class Viewport : IClydeViewport
{
private readonly ClydeHandle _handle;
@@ -106,17 +126,20 @@ namespace Robust.Client.Graphics.Clyde
public string? Name { get; }
public Viewport(ClydeHandle handle, string? name, Clyde clyde)
public Viewport(long id, ClydeHandle handle, string? name, Clyde clyde)
{
Name = name;
_handle = handle;
_clyde = clyde;
Id = id;
}
public Vector2i Size { get; set; }
public event Action<ClearCachedViewportResourcesEvent>? ClearCachedResources;
public Color? ClearColor { get; set; } = Color.Black;
public Vector2 RenderScale { get; set; } = Vector2.One;
public bool AutomaticRender { get; set; }
public long Id { get; }
void IClydeViewport.Render()
{
@@ -186,20 +209,56 @@ namespace Robust.Client.Graphics.Clyde
_clyde.RenderOverlaysDirect(this, control, handle, OverlaySpace.ScreenSpace, viewportBounds);
}
~Viewport()
{
_clyde._viewportDisposeQueue.Enqueue(DisposeData(referenceSelf: false));
}
public void Dispose()
{
GC.SuppressFinalize(this);
RenderTarget.Dispose();
LightRenderTarget.Dispose();
WallMaskRenderTarget.Dispose();
WallBleedIntermediateRenderTarget1.Dispose();
WallBleedIntermediateRenderTarget2.Dispose();
_clyde._viewports.Remove(_handle);
_clyde.DisposeViewport(DisposeData(referenceSelf: false));
}
private ViewportDisposeData DisposeData(bool referenceSelf)
{
return new ViewportDisposeData
{
Handle = _handle,
Id = Id,
ClearEvent = ClearCachedResources,
ClearEventData = MakeClearEvent(referenceSelf)
};
}
private ClearCachedViewportResourcesEvent MakeClearEvent(bool referenceSelf)
{
return new ClearCachedViewportResourcesEvent(Id, referenceSelf ? this : null);
}
public void FireClear()
{
ClearCachedResources?.Invoke(MakeClearEvent(referenceSelf: true));
}
IRenderTexture IClydeViewport.RenderTarget => RenderTarget;
IRenderTexture IClydeViewport.LightRenderTarget => LightRenderTarget;
public IEye? Eye { get; set; }
}
private sealed class ViewportDisposeData
{
public ClydeHandle Handle;
public long Id;
public Action<ClearCachedViewportResourcesEvent>? ClearEvent;
public ClearCachedViewportResourcesEvent ClearEventData;
}
}
}

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Robust.Client.Input;
@@ -101,6 +102,10 @@ namespace Robust.Client.Graphics.Clyde
_windowingThread = Thread.CurrentThread;
// Default to SDL3 on ARM64. GLFW is not feature complete there (lacking file dialog implementation)
if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
_cfg.SetCVar(CVars.DisplayWindowingApi, "sdl3");
var windowingApi = _cfg.GetCVar(CVars.DisplayWindowingApi);
IWindowingImpl winImpl;
@@ -349,15 +354,17 @@ namespace Robust.Client.Graphics.Clyde
_windowHandles.Add(reg.Handle);
var rtId = AllocRid();
var renderTarget = new RenderWindow(this, rtId);
_renderTargets.Add(rtId, new LoadedRenderTarget
{
Size = reg.FramebufferSize,
IsWindow = true,
WindowId = reg.Id,
IsSrgb = true
IsSrgb = true,
Instance = new WeakReference<RenderTargetBase>(renderTarget),
});
reg.RenderTarget = new RenderWindow(this, rtId);
reg.RenderTarget = renderTarget;
_glContext!.WindowCreated(glSpec, reg);
}
@@ -374,6 +381,8 @@ namespace Robust.Client.Graphics.Clyde
if (reg.IsDisposed)
return;
_sawmillWin.Debug($"Destroying window {reg.Id}");
reg.IsDisposed = true;
_glContext!.WindowDestroyed(reg);
@@ -398,10 +407,17 @@ namespace Robust.Client.Graphics.Clyde
_glContext?.SwapAllBuffers();
}
private void VSyncChanged(bool newValue)
public bool VsyncEnabled
{
_vSync = newValue;
_glContext?.UpdateVSync();
get => _vSync;
set
{
if (_vSync == value)
return;
_vSync = value;
_glContext?.UpdateVSync();
}
}
private void WindowModeChanged(int mode)

View File

@@ -114,7 +114,6 @@ namespace Robust.Client.Graphics.Clyde
_proto.PrototypesReloaded += OnProtoReload;
_cfg.OnValueChanged(CVars.DisplayOGLCheckErrors, b => _checkGLErrors = b, true);
_cfg.OnValueChanged(CVars.DisplayVSync, VSyncChanged, true);
_cfg.OnValueChanged(CVars.DisplayWindowMode, WindowModeChanged, true);
_cfg.OnValueChanged(CVars.LightResolutionScale, LightResolutionScaleChanged, true);
_cfg.OnValueChanged(CVars.MaxShadowcastingLights, MaxShadowcastingLightsChanged, true);
@@ -128,7 +127,11 @@ namespace Robust.Client.Graphics.Clyde
// macOS cannot.
if (OperatingSystem.IsWindows() || OperatingSystem.IsLinux())
_cfg.OverrideDefault(CVars.DisplayThreadWindowApi, true);
#if MACOS
// Trust macOS to not need threaded window blitting.
// (threaded window blitting is a workaround to avoid having to frequently MakeCurrent() on Windows, as it is broken).
_cfg.OverrideDefault(CVars.DisplayThreadWindowBlit, false);
#endif
_threadWindowBlit = _cfg.GetCVar(CVars.DisplayThreadWindowBlit);
_threadWindowApi = _cfg.GetCVar(CVars.DisplayThreadWindowApi);

View File

@@ -17,8 +17,6 @@ using Robust.Shared.Timing;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using Color = Robust.Shared.Maths.Color;
using Vector3 = Robust.Shared.Maths.Vector3;
using Vector4 = Robust.Shared.Maths.Vector4;
namespace Robust.Client.Graphics.Clyde
{
@@ -36,6 +34,7 @@ namespace Robust.Client.Graphics.Clyde
public bool IsFocused => true;
private readonly List<IClydeWindow> _windows = new();
private int _nextWindowId = 2;
private long _nextViewportId = 1;
public ShaderInstance InstanceShader(ShaderSourceResource handle, bool? light = null, ShaderBlendMode? blend = null)
{
@@ -77,6 +76,11 @@ namespace Robust.Client.Graphics.Clyde
return [];
}
public IEnumerable<(Clyde.RenderTargetBase, Clyde.LoadedRenderTarget)> GetLoadedRenderTextures()
{
return [];
}
public ClydeDebugLayers DebugLayers { get; set; }
public string GetKeyName(Keyboard.Key key) => string.Empty;
@@ -242,7 +246,7 @@ namespace Robust.Client.Graphics.Clyde
public IClydeViewport CreateViewport(Vector2i size, TextureSampleParameters? sampleParameters,
string? name = null)
{
return new Viewport(size);
return new Viewport(_nextViewportId++, size);
}
public IEnumerable<IClydeMonitor> EnumerateMonitors()
@@ -309,6 +313,19 @@ namespace Robust.Client.Graphics.Clyde
public IFileDialogManagerImplementation? FileDialogImpl => null;
public bool VsyncEnabled { get; set; }
#if TOOLS
public void ViewportsClearAllCached()
{
throw new NotImplementedException();
}
#endif // TOOLS
public void RenderNow(IRenderTarget renderTarget, Action<IRenderHandle> callback)
{
}
private sealed class DummyCursor : ICursor
{
public void Dispose()
@@ -398,7 +415,7 @@ namespace Robust.Client.Graphics.Clyde
{
}
private protected override void SetParameterImpl(string name, in Matrix4 value)
private protected override void SetParameterImpl(string name, in Matrix4x4 value)
{
}
@@ -484,15 +501,19 @@ namespace Robust.Client.Graphics.Clyde
private sealed class Viewport : IClydeViewport
{
public Viewport(Vector2i size)
public Viewport(long id, Vector2i size)
{
Size = size;
Id = id;
}
public void Dispose()
{
ClearCachedResources?.Invoke(new ClearCachedViewportResourcesEvent(Id, null));
}
public long Id { get; }
public IRenderTexture RenderTarget { get; } =
new DummyRenderTexture(Vector2i.One, new DummyTexture(Vector2i.One));
@@ -501,6 +522,7 @@ namespace Robust.Client.Graphics.Clyde
public IEye? Eye { get; set; }
public Vector2i Size { get; }
public event Action<ClearCachedViewportResourcesEvent>? ClearCachedResources;
public Color? ClearColor { get; set; } = Color.Black;
public Vector2 RenderScale { get; set; }
public bool AutomaticRender { get; set; }

View File

@@ -102,6 +102,8 @@ namespace Robust.Client.Graphics.Clyde
{
var data = _windowData[reg.Id];
data.BlitDoneEvent?.Set();
// Set events so blit thread properly wakes up and notices it needs to shut down.
data.BlitStartEvent?.Set();
_windowData.Remove(reg.Id);
}
@@ -326,11 +328,14 @@ namespace Robust.Client.Graphics.Clyde
{
reg.RenderTexture?.Dispose();
reg.RenderTexture = Clyde.CreateRenderTarget(reg.Reg.FramebufferSize, new RenderTargetFormatParameters
{
ColorFormat = RenderTargetColorFormat.Rgba8Srgb,
HasDepthStencil = true
});
reg.RenderTexture = Clyde.CreateRenderTarget(
reg.Reg.FramebufferSize,
new RenderTargetFormatParameters
{
ColorFormat = RenderTargetColorFormat.Rgba8Srgb,
HasDepthStencil = true
},
name: $"{reg.Reg.Id}-RenderTexture");
// Necessary to correctly sync multi-context blitting.
reg.RenderTexture.MakeGLFence = true;
}

View File

@@ -5,8 +5,6 @@ using System.Runtime.CompilerServices;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using Vector3 = Robust.Shared.Maths.Vector3;
using Vector4 = Robust.Shared.Maths.Vector4;
namespace Robust.Client.Graphics.Clyde
{
@@ -277,20 +275,20 @@ namespace Robust.Client.Graphics.Clyde
_clyde.CheckGlError();
}
public void SetUniform(string uniformName, in Matrix4 matrix, bool transpose=true)
public void SetUniform(string uniformName, in Matrix4x4 matrix, bool transpose=true)
{
var uniformId = GetUniform(uniformName);
SetUniformDirect(uniformId, matrix, transpose);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private unsafe void SetUniformDirect(int uniformId, in Matrix4 value, bool transpose=true)
private unsafe void SetUniformDirect(int uniformId, in Matrix4x4 value, bool transpose=true)
{
Matrix4 tmpTranspose = value;
Matrix4x4 tmpTranspose = value;
if (transpose)
{
// transposition not supported on GLES2, & no access to _hasGLES
tmpTranspose.Transpose();
tmpTranspose = Matrix4x4.Transpose(value);
}
GL.UniformMatrix4(uniformId, 1, false, (float*) &tmpTranspose);
_clyde.CheckGlError();
@@ -551,7 +549,7 @@ namespace Robust.Client.Graphics.Clyde
}
}
public void SetUniformMaybe(string uniformName, in Matrix4 value, bool transpose=true)
public void SetUniformMaybe(string uniformName, in Matrix4x4 value, bool transpose=true)
{
if (TryGetUniform(uniformName, out var slot))
{

View File

@@ -128,6 +128,19 @@ namespace Robust.Client.Graphics.Clyde
AddStandardCursor(StandardCursorShape.Hand, CursorShape.Hand);
AddStandardCursor(StandardCursorShape.HResize, CursorShape.HResize);
AddStandardCursor(StandardCursorShape.VResize, CursorShape.VResize);
AddStandardCursor(StandardCursorShape.Progress, CursorShape.Arrow);
AddStandardCursor(StandardCursorShape.NWSEResize, CursorShape.Crosshair);
AddStandardCursor(StandardCursorShape.NESWResize, CursorShape.Crosshair);
AddStandardCursor(StandardCursorShape.Move, CursorShape.Crosshair);
AddStandardCursor(StandardCursorShape.NotAllowed, CursorShape.Arrow);
AddStandardCursor(StandardCursorShape.NWResize, CursorShape.Crosshair);
AddStandardCursor(StandardCursorShape.NResize, CursorShape.VResize);
AddStandardCursor(StandardCursorShape.NEResize, CursorShape.Crosshair);
AddStandardCursor(StandardCursorShape.EResize, CursorShape.HResize);
AddStandardCursor(StandardCursorShape.SEResize, CursorShape.Crosshair);
AddStandardCursor(StandardCursorShape.SResize, CursorShape.VResize);
AddStandardCursor(StandardCursorShape.SWResize, CursorShape.Crosshair);
AddStandardCursor(StandardCursorShape.WResize, CursorShape.HResize);
}
private sealed class CursorImpl : ICursor

View File

@@ -1,25 +0,0 @@
/* SDL3-CS - C# Bindings for SDL3
*
* Copyright (c) 2024 Colin Jackson
*
* This software is provided 'as-is', without any express or implied warranty.
* In no event will the authors be held liable for any damages arising from
* the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software in a
* product, an acknowledgment in the product documentation would be
* appreciated but is not required.
*
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
*
* 3. This notice may not be removed or altered from any source distribution.
*
* Colin "cryy22" Jackson <c@cryy22.art>
*
*/

View File

@@ -1,56 +0,0 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SDL3;
public static partial class SDL
{
// Extensions to SDL3-CS that aren't part of the main library.
[LibraryImport(nativeLibName)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
public static unsafe partial void SDL_SetLogOutputFunction(delegate* unmanaged[Cdecl] <void*, int, SDL_LogPriority, byte*, void> callback, void* userdata);
[LibraryImport(nativeLibName)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
public static unsafe partial SDLBool SDL_AddEventWatch(delegate* unmanaged[Cdecl] <void*, SDL_Event*, byte> filter, void* userdata);
[LibraryImport(nativeLibName)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
public static unsafe partial void SDL_RemoveEventWatch(delegate* unmanaged[Cdecl] <void*, SDL_Event*, byte> filter, void* userdata);
[LibraryImport(nativeLibName, StringMarshalling = StringMarshalling.Utf8)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
public static unsafe partial void SDL_ShowFileDialogWithProperties(int type, delegate* unmanaged[Cdecl]<void*, byte**, int, void> callback, void* userdata, uint properties);
[LibraryImport(nativeLibName, EntryPoint = "SDL_WaitEvent")]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
public static partial SDLBool SDL_WaitEventRef(ref SDL_Event @event);
public const byte SDL_BUTTON_LEFT = 1;
public const byte SDL_BUTTON_MIDDLE = 2;
public const byte SDL_BUTTON_RIGHT = 3;
public const byte SDL_BUTTON_X1 = 4;
public const byte SDL_BUTTON_X2 = 5;
public const int SDL_GL_CONTEXT_PROFILE_CORE = 0x0001;
public const int SDL_GL_CONTEXT_PROFILE_COMPATIBILITY = 0x0002;
public const int SDL_GL_CONTEXT_PROFILE_ES = 0x0004;
public const int SDL_GL_CONTEXT_DEBUG_FLAG = 0x0001;
public const int SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG = 0x0002;
public const int SDL_GL_CONTEXT_ROBUST_ACCESS_FLAG = 0x0004;
public const int SDL_GL_CONTEXT_RESET_ISOLATION_FLAG = 0x0008;
public const int SDL_FILEDIALOG_OPENFILE = 0;
public const int SDL_FILEDIALOG_SAVEFILE = 1;
public const int SDL_FILEDIALOG_OPENFOLDER = 2;
public const string SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER = "SDL.filedialog.nfilters";
public const string SDL_PROP_FILE_DIALOG_FILTERS_POINTER = "SDL.filedialog.filters";
public static int SDL_VERSIONNUM_MAJOR(int version) => version / 1000000;
public static int SDL_VERSIONNUM_MINOR(int version) => version / 1000 % 1000;
public static int SDL_VERSIONNUM_MICRO(int version) => version % 1000;
}

File diff suppressed because it is too large Load Diff

View File

@@ -94,6 +94,19 @@ internal partial class Clyde
Add(StandardCursorShape.Hand, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_POINTER);
Add(StandardCursorShape.HResize, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_EW_RESIZE);
Add(StandardCursorShape.VResize, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_NS_RESIZE);
Add(StandardCursorShape.Progress, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_PROGRESS);
Add(StandardCursorShape.NWSEResize, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_NWSE_RESIZE);
Add(StandardCursorShape.NESWResize, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_NESW_RESIZE);
Add(StandardCursorShape.Move, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_MOVE);
Add(StandardCursorShape.NotAllowed, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_NOT_ALLOWED);
Add(StandardCursorShape.NWResize, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_NW_RESIZE);
Add(StandardCursorShape.NResize, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_N_RESIZE);
Add(StandardCursorShape.NEResize, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_NE_RESIZE);
Add(StandardCursorShape.EResize, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_E_RESIZE);
Add(StandardCursorShape.SEResize, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_SE_RESIZE);
Add(StandardCursorShape.SResize, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_S_RESIZE);
Add(StandardCursorShape.SWResize, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_SW_RESIZE);
Add(StandardCursorShape.WResize, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_W_RESIZE);
void Add(StandardCursorShape shape, SDL.SDL_SystemCursor sysCursor)
{

View File

@@ -81,6 +81,11 @@ internal partial class Clyde
case EventQuit:
ProcessEventQuit();
break;
#if MACOS
case EventWindowDestroyed:
ProcessEventWindowDestroyed();
break;
#endif
default:
_sawmill.Error($"Unknown SDL3 event type: {evb.GetType().Name}");
break;
@@ -255,5 +260,15 @@ internal partial class Clyde
{
_clyde.SendInputModeChanged();
}
#if MACOS
private void ProcessEventWindowDestroyed()
{
// For some reason, on macOS, closing a secondary window
// causes the GL context on the primary thread to crap itself.
// Rebinding it seems to fix it.
GLMakeContextCurrent(_clyde._mainWindow);
}
#endif
}
}

View File

@@ -46,6 +46,10 @@ internal partial class Clyde
}
}
// NOTE: Giving a parent window is required to avoid the file dialog being blocking on macOS.
var mainWindow = (Sdl3WindowReg)_clyde._mainWindow!;
SDL.SDL_SetPointerProperty(props, SDL.SDL_PROP_FILE_DIALOG_WINDOW_POINTER, mainWindow.Sdl3Window);
var task = ShowFileDialogWithProperties(type, props);
SDL.SDL_DestroyProperties(props);

View File

@@ -278,5 +278,9 @@ internal partial class Clyde
private sealed class EventKeyMapChanged : EventBase;
private sealed class EventQuit : EventBase;
#if MACOS
private sealed class EventWindowDestroyed : EventBase;
#endif
}
}

View File

@@ -7,8 +7,10 @@ using Robust.Shared.Maths;
using SDL3;
using TerraFX.Interop.Windows;
using TerraFX.Interop.Xlib;
#if WINDOWS
using BOOL = TerraFX.Interop.Windows.BOOL;
using Windows = TerraFX.Interop.Windows.Windows;
#endif
using GLAttr = SDL3.SDL.SDL_GLAttr;
using X11Window = TerraFX.Interop.Xlib.Window;
@@ -142,9 +144,12 @@ internal partial class Clyde
});
}
private static void WinThreadWinDestroy(CmdWinDestroy cmd)
private void WinThreadWinDestroy(CmdWinDestroy cmd)
{
SDL.SDL_DestroyWindow(cmd.Window);
#if MACOS
SendEvent(new EventWindowDestroyed());
#endif
}
private (nint window, nint context) CreateSdl3WindowForRenderer(
@@ -461,6 +466,7 @@ internal partial class Clyde
var reg = (Sdl3WindowReg)window;
var windowPtr = WinPtr(reg);
#if WINDOWS
// On Windows, SwapBuffers does not correctly sync to the DWM compositor.
// This means OpenGL vsync is effectively broken by default on Windows.
// We manually sync via DwmFlush(). GLFW does this automatically, SDL3 does not.
@@ -473,7 +479,7 @@ internal partial class Clyde
var dwmFlush = false;
var swapInterval = 0;
if (OperatingSystem.IsWindows() && !reg.Fullscreen && reg.SwapInterval > 0)
if (!reg.Fullscreen && reg.SwapInterval > 0)
{
BOOL compositing;
// 6.2 is Windows 8
@@ -492,9 +498,12 @@ internal partial class Clyde
swapInterval = reg.SwapInterval;
}
}
#endif
//_sawmill.Debug($"Swapping: {window.Id} @ {_clyde._gameTiming.CurFrame}");
SDL.SDL_GL_SwapWindow(windowPtr);
#if WINDOWS
if (dwmFlush)
{
var i = swapInterval;
@@ -505,6 +514,7 @@ internal partial class Clyde
SDL.SDL_GL_SetSwapInterval(swapInterval);
}
#endif
}
public uint? WindowGetX11Id(WindowReg window)
@@ -547,17 +557,18 @@ internal partial class Clyde
public void TextInputSetRect(WindowReg reg, UIBox2i rect, int cursor)
{
var ratio = ((Sdl3WindowReg)reg).PixelRatio;
SendCmd(new CmdTextInputSetRect
{
Window = WinPtr(reg),
Rect = new SDL.SDL_Rect
{
x = rect.Left,
y = rect.Top,
w = rect.Width,
h = rect.Height
x = (int)(rect.Left / ratio.X),
y = (int)(rect.Top / ratio.Y),
w = (int)(rect.Width / ratio.X),
h = (int)(rect.Height / ratio.Y)
},
Cursor = cursor
Cursor = (int)(cursor / ratio.X)
});
}

View File

@@ -61,6 +61,10 @@ internal partial class Clyde
// https://github.com/libsdl-org/SDL/issues/11813
SDL.SDL_SetHint(SDL.SDL_HINT_WINDOWS_GAMEINPUT, "0");
#if MACOS
SDL.SDL_SetHint(SDL.SDL_HINT_MAC_OPENGL_ASYNC_DISPATCH, "1");
#endif
var res = SDL.SDL_Init(SDL.SDL_InitFlags.SDL_INIT_VIDEO | SDL.SDL_InitFlags.SDL_INIT_EVENTS);
if (!res)
{

View File

@@ -55,6 +55,7 @@ namespace Robust.Client.Graphics
Texture GetStockTexture(ClydeStockTexture stockTexture);
IEnumerable<(Clyde.Clyde.ClydeTexture, Clyde.Clyde.LoadedTexture)> GetLoadedTextures();
IEnumerable<(Clyde.Clyde.RenderTargetBase, Clyde.Clyde.LoadedRenderTarget)> GetLoadedRenderTextures();
ClydeDebugLayers DebugLayers { get; set; }
@@ -72,5 +73,20 @@ namespace Robust.Client.Graphics
void RunOnWindowThread(Action action);
IFileDialogManagerImplementation? FileDialogImpl { get; }
bool VsyncEnabled { get; set; }
// Viewports
#if TOOLS
/// <summary>
/// Fires <see cref="IClydeViewport.ClearCachedResources"/> on all viewports. For debugging.
/// </summary>
void ViewportsClearAllCached();
#endif // TOOLS
void RenderNow(IRenderTarget renderTarget, Action<IRenderHandle> callback);
}
}

View File

@@ -13,6 +13,11 @@ namespace Robust.Client.Graphics
/// </summary>
public interface IClydeViewport : IDisposable
{
/// <summary>
/// A unique ID for this viewport. No other viewport with this ID can ever exist in the app lifetime.
/// </summary>
long Id { get; }
/// <summary>
/// The render target that is rendered to when rendering this viewport.
/// </summary>
@@ -22,6 +27,16 @@ namespace Robust.Client.Graphics
IEye? Eye { get; set; }
Vector2i Size { get; }
/// <summary>
/// Raised when the viewport indicates that any cached rendering resources (e.g. render targets)
/// should be purged.
/// </summary>
/// <remarks>
/// This event is raised if the viewport is disposed (manually or via finalization).
/// However, code should expect this event to be raised at any time, even if the viewport is not disposed fully.
/// </remarks>
event Action<ClearCachedViewportResourcesEvent> ClearCachedResources;
/// <summary>
/// Color to clear the render target to before rendering. If null, no clearing will happen.
/// </summary>
@@ -85,4 +100,23 @@ namespace Robust.Client.Graphics
IViewportControl control,
in UIBox2i viewportBounds);
}
public struct ClearCachedViewportResourcesEvent
{
/// <summary>
/// The <see cref="IClydeViewport.Id"/> of the viewport.
/// </summary>
public readonly long ViewportId;
/// <summary>
/// The viewport itself. This is not available if the viewport was disposed.
/// </summary>
public readonly IClydeViewport? Viewport;
internal ClearCachedViewportResourcesEvent(long viewportId, IClydeViewport? viewport)
{
ViewportId = viewportId;
Viewport = viewport;
}
}
}

View File

@@ -4,8 +4,6 @@ using Robust.Shared.Graphics;
using Robust.Shared.Maths;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
using Vector3 = Robust.Shared.Maths.Vector3;
using Vector4 = Robust.Shared.Maths.Vector4;
namespace Robust.Client.Graphics
{
@@ -169,7 +167,7 @@ namespace Robust.Client.Graphics
SetParameterImpl(name, value);
}
public void SetParameter(string name, in Matrix4 value)
public void SetParameter(string name, in Matrix4x4 value)
{
EnsureAlive();
EnsureMutable();
@@ -236,7 +234,7 @@ namespace Robust.Client.Graphics
private protected abstract void SetParameterImpl(string name, bool value);
private protected abstract void SetParameterImpl(string name, bool[] value);
private protected abstract void SetParameterImpl(string name, in Matrix3x2 value);
private protected abstract void SetParameterImpl(string name, in Matrix4 value);
private protected abstract void SetParameterImpl(string name, in Matrix4x4 value);
private protected abstract void SetParameterImpl(string name, Texture value);
private protected abstract void SetStencilImpl(StencilParameters value);
}

View File

@@ -11,8 +11,6 @@ using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using YamlDotNet.RepresentationModel;
using Vector3 = Robust.Shared.Maths.Vector3;
using Vector4 = Robust.Shared.Maths.Vector4;
namespace Robust.Client.Graphics
{
@@ -222,7 +220,7 @@ namespace Robust.Client.Graphics
case Matrix3x2 i:
instance.SetParameter(key, i);
break;
case Matrix4 i:
case Matrix4x4 i:
instance.SetParameter(key, i);
break;
}

View File

@@ -15,6 +15,11 @@ namespace Robust.Client.Graphics
/// </summary>
IBeam,
/// <summary>
/// Alias for <see cref="IBeam"/>.
/// </summary>
Text = IBeam,
/// <summary>
/// The crosshair shape. Used when dragging and dropping.
/// </summary>
@@ -25,16 +30,135 @@ namespace Robust.Client.Graphics
/// </summary>
Hand,
/// <summary>
/// Alias for <see cref="Hand"/>
/// </summary>
Pointer = Hand,
/// <summary>
/// The horizontal resize shape. Used when mousing over something that can be horizontally resized.
/// </summary>
HResize,
/// <summary>
/// Alias for <see cref="EWResize"/>
/// </summary>
EWResize = HResize,
/// <summary>
/// The vertical resize shape. Used when mousing over something that can be vertically resized.
/// </summary>
VResize,
/// <summary>
/// Alias for <see cref="VResize"/>.
/// </summary>
NSResize = VResize,
/// <summary>
/// Program is busy doing something.
/// </summary>
/// <remarks>
/// This cursor is not always available and may be substituted.
/// </remarks>
Progress,
/// <summary>
/// Diagonal resize shape for northwest-southeast resizing.
/// </summary>
/// <remarks>
/// This cursor is not always available and may be substituted.
/// </remarks>
NWSEResize,
/// <summary>
/// Diagonal resize shape for northeast-southwest resizing.
/// </summary>
/// <remarks>
/// This cursor is not always available and may be substituted.
/// </remarks>
NESWResize,
/// <summary>
/// 4-way arrow move icon.
/// </summary>
/// <remarks>
/// This cursor is not always available and may be substituted.
/// </remarks>
Move,
/// <summary>
/// An action is not allowed.
/// </summary>
/// <remarks>
/// This cursor is not always available and may be substituted.
/// </remarks>
NotAllowed,
/// <summary>
/// One-directional resize to the northwest.
/// </summary>
/// <remarks>
/// This cursor is not always available and may be substituted.
/// </remarks>
NWResize,
/// <summary>
/// One-directional resize to the north.
/// </summary>
/// <remarks>
/// This cursor is not always available and may be substituted.
/// </remarks>
NResize,
/// <summary>
/// One-directional resize to the northeast.
/// </summary>
/// <remarks>
/// This cursor is not always available and may be substituted.
/// </remarks>
NEResize,
/// <summary>
/// One-directional resize to the east.
/// </summary>
/// <remarks>
/// This cursor is not always available and may be substituted.
/// </remarks>
EResize,
/// <summary>
/// One-directional resize to the southeast.
/// </summary>
/// <remarks>
/// This cursor is not always available and may be substituted.
/// </remarks>
SEResize,
/// <summary>
/// One-directional resize to the south.
/// </summary>
/// <remarks>
/// This cursor is not always available and may be substituted.
/// </remarks>
SResize,
/// <summary>
/// One-directional resize to the southwest.
/// </summary>
/// <remarks>
/// This cursor is not always available and may be substituted.
/// </remarks>
SWResize,
/// <summary>
/// One-directional resize to the west.
/// </summary>
/// <remarks>
/// This cursor is not always available and may be substituted.
/// </remarks>
WResize,
/// <summary>
/// Not a real value
/// </summary>

View File

@@ -197,6 +197,13 @@ namespace Robust.Client.Input
locId += "-linux";
}
#if MACOS
if (key == Key.Alt)
{
locId += "-mac";
}
#endif
if (loc.TryGetString(locId, out var name))
return name;

View File

@@ -1,5 +1,8 @@
using System;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
@@ -18,6 +21,10 @@ namespace Robust.Client.Placement
PlacementMode? CurrentMode { get; set; }
PlacementInformation? CurrentPermission { get; set; }
IEntityManager EntityManager { get; }
IEyeManager EyeManager { get; }
IMapManager MapManager { get; }
/// <summary>
/// The direction to spawn the entity in (presently exposed for EntitySpawnWindow UI)
/// </summary>
@@ -49,5 +56,15 @@ namespace Robust.Client.Placement
void ToggleEraserHijacked(PlacementHijack hijack);
void FrameUpdate(FrameEventArgs e);
/// <summary>
/// The name of the placement mode option to just use the default for the selected entity.
/// </summary>
const string DefaultModeName = "Default";
/// <summary>
/// An array of the names of all available placement modes.
/// </summary>
string[] AllModeNames { get; }
}
}

View File

@@ -1,7 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Robust.Shared.Map;
using Vector2 = System.Numerics.Vector2;
namespace Robust.Client.Placement.Modes
{

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