Compare commits

...

278 Commits

Author SHA1 Message Date
metalgearsloth
75fc9089c3 Add weld joints and other misc changes for docking (#2197) 2021-11-11 19:54:08 +11:00
Kara D
0972601a43 Revert "entity creation crash fixes"
This reverts commit 603c252c48.
2021-11-10 12:09:31 -07:00
Paul
603c252c48 entity creation crash fixes 2021-11-10 18:57:47 +01:00
Paul
d5b1c044b7 fixes the crash for realsies 2021-11-10 18:34:21 +01:00
metalgearsloth
4600f0531d Fix centre of mass (#2212) 2021-11-10 17:25:38 +01:00
Leon Friedrich
c88498eca9 Add function to directly perform lookups using an EntityLookupComponent (#2200) 2021-11-11 01:12:49 +11:00
Paul Ritter
f15f1eb345 adds gridremovalevent (#2201)
Co-authored-by: Paul <ritter.paul1+git@googlemail.com>
2021-11-10 23:27:11 +11:00
Pieter-Jan Briers
5be3ced05a UI scaling for WebViewControl. 2021-11-10 11:57:58 +01:00
Pieter-Jan Briers
7f03e88e97 OSWindow not adjusts SetSize when window is resized. 2021-11-10 11:57:12 +01:00
Pieter-Jan Briers
8e3fa3e52d Pass + as command line arg to client/server to execute commands after init. 2021-11-10 02:01:31 +01:00
metalgearsloth
f9ae3e1fc2 Significantly optimise server when PVS disabled (#2196) 2021-11-10 01:38:37 +01:00
mirrorcult
bf9e95fa8a Update README.md 2021-11-09 17:21:37 -07:00
ZorenZal
030a7d265b Added set English language checkbox (#2136) 2021-11-10 01:12:36 +01:00
Pieter-Jan Briers
df70e94743 ununupdated comment 2021-11-10 01:09:06 +01:00
metalgearsloth
d68cd4d7eb Add overlay for physics COM (#2210) 2021-11-10 01:05:08 +01:00
Pieter-Jan Briers
d098881bff Make BVH tree expansion exponential.
Fixes #2215
2021-11-10 00:59:53 +01:00
ike709
b8fbe32c27 Bump Robust.Client.WebView TargetFramework to .NET 6 (#2214) 2021-11-09 20:17:37 +01:00
Pieter-Jan Briers
02d2bd31e7 Update Lidgren submodule; re-enable encryption. 2021-11-09 19:34:55 +01:00
Pieter-Jan Briers
bd0dba0df0 Disable network encryption to bandaid it for .NET 6. 2021-11-09 18:07:59 +01:00
Pieter-Jan Briers
17164ead34 Fix release compile. 2021-11-09 15:35:37 +01:00
Vera Aguilera Puerto
079e099a3b Makes many things use OwnerUid instead of Owner.Uid 2021-11-09 15:11:02 +01:00
Javier Guardia Fernández
1c8ed1c5b2 Update to .NET 6 and C# 10 (#2211)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2021-11-09 14:59:19 +01:00
Vera Aguilera Puerto
d4cdb7ff3b Tumbler command uses EntityUid 2021-11-09 13:02:01 +01:00
Vera Aguilera Puerto
31d301b91f ActorSystem and PlayerSession partially use EntityUid
Non-breaking changes
2021-11-09 12:54:18 +01:00
Vera Aguilera Puerto
07c65474cb LocalPlayer EntityUid 2021-11-09 12:38:25 +01:00
Vera Aguilera Puerto
8c3c8fceea ViewSubscriberSystem uses EntityUid for everything 2021-11-09 11:37:31 +01:00
Vera Aguilera Puerto
ee894d8a8e Move the last responsabilities from Entity to EntityManager. 2021-11-08 16:09:12 +01:00
Vera Aguilera Puerto
ce169ed15e Move LastModifiedTick out of Entity and into MetaDataComponent. (#2209) 2021-11-08 15:56:49 +01:00
Vera Aguilera Puerto
9b215098e4 Remove ITransformComponent. 2021-11-08 12:49:55 +01:00
Vera Aguilera Puerto
78d01fd25d Remove every ITransformComponent usage. 2021-11-08 12:44:03 +01:00
Vera Aguilera Puerto
8e8bfbe0cc Obsolete ITransformComponent 2021-11-08 12:28:49 +01:00
Vera Aguilera Puerto
45b0a49ffb Fix build 2021-11-08 12:23:22 +01:00
Vera Aguilera Puerto
58638c9109 Make TransformComponent public, IEntity keeps a reference to TransformComponent. 2021-11-08 12:21:34 +01:00
Vera Aguilera Puerto
2f2a397ecf Fix TransformComponent access modifiers (#2207)
Part 1 of ITransformComponent removal.
2021-11-08 12:18:53 +01:00
Vera Aguilera Puerto
78c551d854 Remove DummyIconEntity, use actual entities instead. (#2206) 2021-11-08 12:17:13 +01:00
Azzy
679e07d9ea Fixes ViewVariables sprite rotation bug (#2202) 2021-11-07 18:47:27 +11:00
DrSmugleaf
fa5d0235ec Prepend Client and Server to tests ran message 2021-11-06 11:42:01 +01:00
Javier Guardia Fernández
04d029b9a2 Add test pooling (#2146)
* Add test pooling and global test setup

* WIP test pooling changes

* Make asynchronous tests the default again

* Finish fixing tests, make test threads background threads

* Un-pool tests with custom cvars

* Fix not changing FailureLogLevel cvar on instance return

* Fix error when overriding already overriden cvar

* Don't pool some physics integration tests

* Unpool engine tests, the technology just isn't there yet

* Remove explicit Pool = false from physics tests

* Change default pooling setting to false

* Didn't need this anyway

* Remove ConfigurationManager.ResetOverrides

* Bring back enum cvar override parsing

* Make integrationInstances name clearer > notPooledInstaces
Make clients ready and servers ready internal

* Add logging test instances

* Give more info on ran tests

* Show total clients and servers in test output

* Wipe LayerMap on SpriteComponent.AfterDeserialize

* Fix server not properly kicking clients

* Rider shut

* Make all test metrics report totals

* Format tests ran better

* Replace Console.WriteLine with TestContext.Out.WriteLine

* Fix two server test pooling info prints using total clients instead of total servers

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2021-11-06 11:18:18 +01:00
Pieter-Jan Briers
6e84821233 Package CEF as NuGet package, does not need to be manually downloaded anymore. 2021-11-05 14:50:47 +01:00
Pieter-Jan Briers
881cfeb9a9 Small IRawInputControl doc. 2021-11-05 14:50:47 +01:00
Pieter-Jan Briers
0187e85700 Eye offset no longer offsets FOV.
This means camera recoil in SS14 won't clip into walls.
2021-11-04 17:37:00 +01:00
Pieter-Jan Briers
dee8a87acd Use headless WebViewManager on headless clients. 2021-11-03 15:26:52 +01:00
Pieter-Jan Briers
d4a7e8f3e0 Move CEF RequestResult to correct folder. 2021-11-03 15:14:46 +01:00
Pieter-Jan Briers
dae6424667 Allow swapping out internal WebViewManager implementation.
Everything moved to interfaces.
2021-11-03 15:12:49 +01:00
Pieter-Jan Briers
3770149cfc Update CefGlue 2021-11-03 15:03:25 +01:00
Alex Evgrashin
341c279265 Bandaid sprite animation in SpriteView (#2181) 2021-11-02 19:50:29 +01:00
metalgearsloth
37c20723ab Add WorldBounds as an arg for overlays (#2190) 2021-11-02 18:00:06 +01:00
ike709
c3e4a64ad7 Makes client connection timeout a cvar (#2186) 2021-11-02 17:56:17 +01:00
metalgearsloth
225446920a Make transform delete entity if parent invalid (#2193) 2021-11-02 17:54:48 +01:00
Saphire Lattice
9b2a50b1a8 Make camera lerp towards the grid rotation, and keep rotation when stepping off onto the world grid (#2187)
* Make camera lerp towards the grid rotation, and keep rotation when stepping off onto the world grid

* Fix lerp targeting, add a bunch of comments
2021-11-02 13:49:28 +01:00
metalgearsloth
b4ed513e8c Optimise anchor snap 2021-11-02 16:55:12 +11:00
20kdc
c28f2d77c3 Placement Manager: Spin Cycle 2 (#2184) 2021-11-02 16:21:03 +11:00
metalgearsloth
3e344d00a8 Move PVS to a system (#2189) 2021-11-02 16:19:11 +11:00
moonheart08
c321400347 Fix savemap/loadmap arguments. (#2055) 2021-11-02 16:07:31 +11:00
metalgearsloth
a4ff5d65ec Dirty changes (#2174) 2021-11-02 16:05:45 +11:00
Pieter-Jan Briers
97c70124a5 [CEF] properly close streams when loading from res:// and such. 2021-11-02 01:53:38 +01:00
Pieter-Jan Briers
cdcc5239ab Rename WebView control to WebViewControl to avoid namespace name clash. 2021-11-01 21:06:30 +01:00
Pieter-Jan Briers
ba2f464249 Restructure CEF stuff in preparation for launcher packaging.
Robust.Client.CEF Renamed to Robust.Client.WebView since CEF should really be an implementation detail.
Content is no longer responsible for initializing and managing the module, this is done automatically by the engine.
WebView is initialized by declaring it in a manifest.yml file in the game resources. In the future the launcher will read this same file to manage WebView module versions.
CefManager has been made private and the content-visible API is now IWebViewManager.
2021-11-01 21:03:51 +01:00
Paul
4210f30460 fixes positional audio not accounting for rotation 2021-10-31 22:49:05 +01:00
moonheart08
9fc95591d9 Introduce BQLv2, part 1 (#2170) 2021-10-31 20:23:13 +01:00
E F R
0a59079a4a Graphics: add a Font variant that supports font stacking (#2182) 2021-10-31 14:48:58 +01:00
20kdc
89f168c04d Improves PlacementManager (construction/etc.) handling of parent world rotation (#2165) 2021-10-31 14:47:18 +01:00
Visne
f11ac39cd5 Fix all warnings (#2171) 2021-10-31 12:22:58 +01:00
metalgearsloth
380de8c4c3 Expose OwnerUid on Component (#2183) 2021-10-31 11:35:21 +01:00
Vera Aguilera Puerto
539c161ea3 Fix client build when scripting is disabled. 2021-10-31 10:55:31 +01:00
metalgearsloth
b07187459b Fix physics for invalid parents 2021-10-31 15:24:12 +11:00
Pieter-Jan Briers
97fb54b6d7 Registry based HWIDs for Windows. 2021-10-30 14:21:23 +02:00
wixoa
fe30d974ca Add ImageSharp's Image.Load<>() to the sandbox (#2179) 2021-10-30 13:26:42 +02:00
E F R
884fade25a Add tab "completion" for the server-side C# console (#2176) 2021-10-30 13:26:23 +02:00
Pieter-Jan Briers
a3ab745121 Fix debug console not animating when dropping down for the first time. 2021-10-29 17:41:51 +02:00
ShadowCommander
4e0ad23272 Fix sound orientation when eye is rotated (#2177) 2021-10-29 16:49:16 +02:00
Vera Aguilera Puerto
b9b9cd0711 Revert "Fix PVS crash (#2173)"
This reverts commit b05b1a7c86.
2021-10-28 17:58:23 +02:00
metalgearsloth
b05b1a7c86 Fix PVS crash (#2173) 2021-10-29 01:26:39 +11:00
Visne
4ced901358 Remove SpriteComponent.Directional (#2172) 2021-10-28 13:20:06 +02:00
metalgearsloth
898d5a3ba5 Spaghettifix GetEntitiesIntersecting (#2167) 2021-10-27 08:55:24 +02:00
Visne
068c05c355 Use enum for access level in XamlUiPartialClassGenerator (#2161) 2021-10-27 08:52:09 +02:00
Saphire Lattice
cd693875e6 Fix resizing (#2160)
* Fix resizing moving the window beyond its minimum size and causing it to move visually

* Do a NaN-check the SetSize when resizing

* Use float instead to avoid conversion

As per PJB3005's suggestion

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

Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>
2021-10-27 12:45:36 +11:00
20kdc
e2723a83b3 Revert "Technically fixes lathes, but not properly, but doesn't make things worse." :D (#2166)
This reverts commit cb19430d5c.
2021-10-27 12:44:34 +11:00
20kdc
f6ac8fbe1f Expose the Box2Rotated intersecting function in IEntityLookup for Content use (#2164) 2021-10-26 00:27:55 +11:00
metalgearsloth
a1e0f18bd6 Add Box2Rotated support to EntityLookup (#2163) 2021-10-25 15:00:18 +02:00
moonheart08
1e7a481911 better spawn command (#2156)
* better spawn command

* Update Robust.Server/Console/Commands/SpawnCommand.cs

Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com>

* Update Robust.Server/Console/Commands/SpawnCommand.cs

Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com>

* ok help

this is a web edit, sue me!

Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com>
2021-10-24 22:15:47 -07:00
ShadowCommander
331f14b31a Fix audio warnings (#2158) 2021-10-24 23:43:04 +02:00
metalgearsloth
88660e1958 Use layer rotations for sprites (#2159) 2021-10-24 23:42:43 +02:00
Visne
d9af00c931 Make autogenerated XAML UI fields access level settable (#2148)
* Make autogenerated XAML fields public instead of protected

* Make field always public, remove stray commented out code

* Remove encapsulation for previously protected XAML UI fields

* Make access level settable

* Set access to public for DebugConsole LineEdit

* Address reviews and add documentation

* Remove documentation

* Address reviews
2021-10-24 23:20:27 +02:00
ShadowCommander
b8cb037151 Fix _enableAllKeybinds buttons breaking due to Use (#2157) 2021-10-24 01:43:46 -07:00
20kdc
32d8bd8600 Apply MIDI volume properly on initializtion of FluidSynth (#2153) 2021-10-24 00:12:30 +02:00
mirrorcult
f5a1e06f21 Merge pull request #2051 from Visne/sizeflags
Remove SizeFlags
2021-10-23 15:00:49 -07:00
Visne
48a5cb75d6 Restart tests 2021-10-23 23:54:55 +02:00
E F R
3e6b5a7739 UserInterfaceSystem: fail gracefully if the ClientUserInterfaceComponent goes away (#2155) 2021-10-23 19:38:44 +02:00
ShadowCommander
9ac0aa4cd6 Improve tpto (#2150)
Add teleporting other players to a target player
2021-10-23 10:22:40 -07:00
metalgearsloth
50fb8ca9e5 Fix sprite rounding issues (#2152)
* Fix sprite rounding issues

Should fix all of the issues at exactly 45 degrees

* Do Paul's review
2021-10-23 23:48:26 +11:00
metalgearsloth
7da89765ac Fix half of 45 degree sprite issues 2021-10-23 14:43:27 +11:00
Vera Aguilera Puerto
ce58c6688f Fix rare exception with QueueDelete modified collection
Now we use an actual queue for the deletion order, and the hashset for occupancy.
2021-10-23 00:08:04 +02:00
Vera Aguilera Puerto
2f03640200 Removing a component from an entity removes & deletes it immediately. (#2151) 2021-10-22 23:38:37 +02:00
20kdc
55fecc95a9 MIDI VolumeBoost property (#2141) 2021-10-22 09:09:24 +02:00
Alex Evgrashin
8b54ad79cf Int slider with input field (#2142) 2021-10-22 09:07:45 +02:00
Vera Aguilera Puerto
88dec23a03 Fix wrong logic with protected component removal, use fancy pattern. 2021-10-21 15:10:38 +02:00
20kdc
079702347d Show current rotation in entity spawn window (#2129) 2021-10-21 14:24:56 +02:00
metalgearsloth
d18bf3d5ac Force joint bodies awake
Woops
2021-10-21 20:47:17 +11:00
Pieter-Jan Briers
ed832748b1 list command now shows client commands again. 2021-10-21 01:39:16 +02:00
metalgearsloth
6491e51f3d Reduce FindGridsIntersecting allocations (#2119)
* Reduce FindGridsIntersecting allocations

I love struct enumerators

* Do slightly smarter pooling in EntityViewCulling

Even more alloc reductions

* Forgot to re-add this pooling

* Fix missing chunks
2021-10-19 13:30:52 +11:00
Pieter-Jan Briers
58e7fb3a17 Work around PEReader crashes on Linux.
Fixes #2130
2021-10-18 14:11:07 +02:00
mirrorcult
a9c7926226 Merge pull request #1942 from ShadowCommander/fix-sound
Change Audio to also use Grid Coordinates as a fallback
2021-10-18 00:28:11 -07:00
metalgearsloth
241dc0b752 Remove empty joint components (#2139) 2021-10-17 22:45:15 +02:00
Javier Guardia Fernández
566948f1c0 Add a virtual array of assemblies for SerializationTest to load so content can use it (#2137) 2021-10-16 15:38:18 +02:00
Javier Guardia Fernández
dbba440f7e Fix deserializing null structs (#2138)
* Fix deserializing null structs

* Fix deserializing for not nullable structs

* Add tests
2021-10-16 15:38:06 +02:00
Vera Aguilera Puerto
5b1e9eec27 Fixes component deletion/removal not being synced with clients. (#2128) 2021-10-15 23:52:09 +02:00
Pieter-Jan Briers
8ef95f0199 Update Lidgren 2021-10-15 15:59:44 +02:00
Leon Friedrich
b71e8f140a fix GridTileToWorldPos (#2134) 2021-10-15 22:59:44 +11:00
metalgearsloth
f8397099de Add a good GetCollidingEntities method that takes in body 2021-10-15 13:40:08 +11:00
metalgearsloth
5bbf1703ac Hack to avoid eye flicker on parent change 2021-10-15 11:55:47 +11:00
metalgearsloth
1357e38759 Fix controlmob crash (#2133) 2021-10-15 11:01:35 +11:00
Pieter-Jan Briers
655ecbab45 Update Lidgren submodule 2021-10-14 19:18:35 +02:00
Vera Aguilera Puerto
a0ef63bd4a Makes RemoveComponentImmediate more like RemoveComponentDeferred. (#2126)
- Exception tolerance added
- UID is now passed in the arguments
- Is able to remove protected components if needed.
- Actually calls `ComponentDependencyManager` to let it know the component has been removed.
2021-10-14 00:41:57 +02:00
Pieter-Jan Briers
f34763f11e Don't pack natives with engine client builds.
These are never used by the launcher due to the deployment model, so removing these saves quite a few megabytes.
2021-10-13 20:13:59 +02:00
Pieter-Jan Briers
b1f6b4cbe0 Remove .appveyor.yml
I doubt we'll ever use appveyor again, so...
2021-10-13 15:56:32 +02:00
Pieter-Jan Briers
94708881b3 Update Lidgren.
Optimizes Socket usage to remove allocations, minor bugfixes.
2021-10-13 15:55:04 +02:00
metalgearsloth
0f6dbac51c Fix gridbuffer broadphase (#2116)
It's for shuttle movement; it's getting bulldozed someday anyway but this fixes it.
2021-10-13 11:16:06 +11:00
metalgearsloth
e67de121ca Use spans for chunk mesh (#2124) 2021-10-11 17:06:21 +02:00
20kdc
17979db216 Technically fixes lathes, but not properly, but doesn't make things worse. (#2123)
I am aware this PR is terrible, but you saw what happened to the proper fix.
2021-10-11 16:10:38 +02:00
Pieter-Jan Briers
8d0070b5c3 Add IsUiOpen helper to UISystem. 2021-10-11 01:50:34 +02:00
Pieter-Jan Briers
034c392cbe Add GetComponentOrNull extension method. 2021-10-11 01:37:19 +02:00
Pieter-Jan Briers
160bbc3a72 Fix layout of entity spawn window. 2021-10-11 01:36:59 +02:00
metalgearsloth
3a4d228e94 Fix container manager deserialization 2021-10-11 02:56:37 +11:00
metalgearsloth
57f57f9d9f Fix PVS dumbdumb (#2122)
Was wondering why this was showing up on the profiler, woops
2021-10-11 02:27:04 +11:00
ShadowCommander
0592444252 Merge branch 'fix-sound' of github.com:ShadowCommander/RobustToolbox into fix-sound 2021-10-10 02:26:00 -07:00
ShadowCommander
d8499f2e60 Merge branch 'master' into fix-sound 2021-10-10 02:24:55 -07:00
metalgearsloth
476b5182f8 Fix y-sorting on rotated grids 2021-10-10 17:36:51 +11:00
metalgearsloth
6c7ab1bd82 Add AABBs to the physics debug drawing 2021-10-10 16:54:22 +11:00
metalgearsloth
526ed31b0d Re-parent unanchored entities (#2046) 2021-10-10 14:27:19 +11:00
metalgearsloth
3ac5552276 Cache more broadphase stuff internally (#2071)
* Don't generate physics contacts for non-predicted bodies

These are all just handled on the server anyway so it's a significant waste of performance on the client for busy scenes.

* Significantly optimise broadphase updates

Cache all of the broadphase data properly now and also save the cache for physics step to boot.

* Fix cross-map broadphases

* Remove unnecessary clear

* fix

* Fixes

* Numerous ray fixes
2021-10-10 14:18:10 +11:00
Flipp Syder
7a51c22514 Fixes SpriteComponent.AddLayer for specific RSI definition (#2115) 2021-10-09 20:21:12 +02:00
metalgearsloth
aa339eb504 Add component that toggles collision on anchoring (#2113) 2021-10-09 18:27:31 +02:00
mirrorcult
ffb3800664 Opt out of shadow casting for some lights (#2085)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2021-10-09 18:21:22 +02:00
Pieter-Jan Briers
39eb1a7d75 Clyde RenderOverlays now calls FlushRenderQueue.
This fixes association of API calls with the correct debug groups, and reduces (error prone) boilerplate.
2021-10-09 18:20:37 +02:00
20kdc
c25ab2fcb1 Fix FOV wall shadowing not working properly due to broken fov-lighting (#2114) 2021-10-09 17:27:36 +02:00
Vera Aguilera Puerto
22e0fbc6c1 The descendants of my friends are also my friends! Improves FriendAnalyzer.
In short, if you specify a shared type as a friend, any inheriting types will also count as friends, automatically. This could be abused but... That's why "sealed" exists, r-r-right?
2021-10-09 16:04:09 +02:00
Paul Ritter
72071826a4 fix placement grid (#2072) 2021-10-09 15:19:06 +02:00
Pieter-Jan Briers
8b9c1ab9a1 Fix warnings in Robust.Client 2021-10-09 15:08:08 +02:00
Vera Aguilera Puerto
16d5da5414 Check that parent uid is valid before dirtying oneself. 2021-10-09 12:41:57 +02:00
mirrorcult
39f82694bf Merge pull request #2111 from 20kdc/krakensbane-for-workgroups-8p6-focus
Stop spinning from messing as much with shadows
2021-10-08 16:28:50 -07:00
20kdc
79c3c09851 Stop spinning from messing as much with shadows
Shadows now seem to remain pretty stable up to the point where nothing is stable.
Sounds good to me.

Now, I know what you're going to say. "But, but, Kraken *fren*!"
*sighs* No. Kraken BAD. Kraken harm spesspeople.
2021-10-08 21:08:42 +01:00
Leon Friedrich
ad7370f0b3 Add option to make ScrollContainer return size. (#2107) 2021-10-08 16:01:01 +02:00
Pieter-Jan Briers
75cd8a0a12 Fix primitive restart on GLES3.
Was detected as incorrect version and didn't distinguish the difference between FixedIndex and not.
2021-10-08 14:54:53 +02:00
Ygg01
915a812832 Add extra helpers to Direction (#2093) 2021-10-08 13:34:45 +02:00
metalgearsloth
c8e7fe9f1f Reduce angular sleep tolerance even more 2021-10-08 18:34:48 +11:00
metalgearsloth
9803ed9cad Bandaid entity deletion entitylookup (#2102) 2021-10-08 09:14:19 +02:00
Leon Friedrich
f5f8a59c86 Add can-see / visibility function to ContainerHelpers (#2105) 2021-10-08 09:12:34 +02:00
20kdc
f4e3dfa601 Fix FOV shader to allow for s p i n (#2103) 2021-10-08 09:04:21 +02:00
metalgearsloth
9305b261bb Decrease angular sleep tolerance 2021-10-08 14:15:54 +11:00
ike709
3a161af4a5 Changes cefglue to use our fork (#2097)
Co-authored-by: ike709 <ike709@github.com>
2021-10-07 22:08:22 +02:00
Vera Aguilera Puerto
0447d8d3b9 Fix typo in BaseServer comment 2021-10-07 11:17:40 +02:00
ike709
c2eed5c007 Update the comments related to the CEF binary (#2098) 2021-10-07 09:53:14 +02:00
metalgearsloth
d9464e2621 Fix rotated grid collision (#2100) 2021-10-07 13:29:13 +11:00
Swept
1a70813f3c Merge pull request #2099 from 20kdc/oh-my-goodness-were-doomed 2021-10-06 18:45:20 -07:00
20kdc
a3a69f821f Revert "Fix stuck lathes properly. (#2091)" b/c of "Tried to overwrite a protected component." errors
This reverts commit a60993c60d.
2021-10-07 02:23:57 +01:00
Pieter-Jan Briers
c72988b05b Fix TickUpdate not firing on client when prediction is off. 2021-10-06 23:09:49 +02:00
ike709
11bc9c0fe4 Adds everything else OpenDream needs to the sandbox (#2060) 2021-10-06 22:50:37 +02:00
Pieter-Jan Briers
98593b7b33 NetListAsArray uses span instead of array now. 2021-10-05 15:22:46 +02:00
Vera Aguilera Puerto
4b30a94126 EntityManager SpawnEntity overloads consistently run map init on entities.
Fixes #2092
2021-10-05 14:44:43 +02:00
Pieter-Jan Briers
1c8958d312 Reduce .ToArray() in PVS with NetListAsArray<T>. 2021-10-05 14:37:39 +02:00
Pieter-Jan Briers
ef2f81a77a Update NetSerializer and Lidgren submodules.
Lidgren has slightly better buffer management, NetSerializer has NetListAsArray<T>.
2021-10-05 14:37:39 +02:00
Vera Aguilera Puerto
3935c63a80 EntityManager Initialized set by Initialize() method and not InitializeComponents() 2021-10-05 11:40:19 +02:00
Vera Aguilera Puerto
76ab68dc3d SharedContainerSystem proxy methods use UID to add components.
- Also disables logging missing comps on TryX methods.
2021-10-05 11:34:59 +02:00
Vera Aguilera Puerto
a986292aa2 Fix EntityManager.InitializeEntity being static for no reason at all.
- Also fixes `EntityInitialized` not being invoked on many cases. (On the client, on entities created by `ClientGameStateManager`, for example...)
2021-10-05 10:59:15 +02:00
Vera Aguilera Puerto
082fac52cd Remove redundant deleted entity check. 2021-10-05 10:14:25 +02:00
20kdc
a60993c60d Fix stuck lathes properly. (#2091) 2021-10-04 12:33:35 +02:00
Vera Aguilera Puerto
97ffc9ecc2 Resolves now log errors for missing components. (#2088) 2021-10-04 12:29:39 +02:00
Vera Aguilera Puerto
ab4f2c91b4 Move LifeStage and Paused data to MetaDataComponent. (#2068) 2021-10-04 12:29:13 +02:00
Vera Aguilera Puerto
5f2cc942cb Update UserInterfaceSystem proxies to use resolves. 2021-10-04 12:20:03 +02:00
metalgearsloth
5a343477a9 Joints refactor (#1954)
* feex

* Cleanup

* Remove unneeded matrix

* Fixes

* Fix preprocessor

* localanchor cleanup

* Break this build so I remember to fix it later.

* Fixes

* Woops

* Fixes

* Fixup merge

* Stuff

* Update debug drawing

* Also dis thanks fork

* fren

* More colors

* Default drawing

* Fixes

* reviews
2021-10-04 15:34:15 +11:00
Pieter-Jan Briers
7bc847c9a4 Multithread game state serialization, fix some possible thread bugs.
This won't help too much because serialization is extremely alloc-heavy and it causes tons of GC pressure, so GC will drastically reduce the effectiveness of parallelization. Still helps a bit though.
2021-10-04 02:20:13 +02:00
Pieter-Jan Briers
cfc7f26100 Kick command now handles kick reason better. 2021-10-04 01:45:33 +02:00
Vera Aguilera Puerto
6eb3989362 EyeComponent VisibilityMask is set to 1 by default. 2021-10-03 21:02:27 +02:00
20kdc
04fd324e4a Expose LocalBounds in IMapGrid (for space-wizards/space-station-14#4745 ) (#2090) 2021-10-03 23:49:02 +11:00
Vera Aguilera Puerto
c9e999d024 Adds Write/Copy TypeSerializer methods for base SpriteSpecifier. (#2089) 2021-10-03 13:41:41 +02:00
Vera Aguilera Puerto
243f405bab Add Filter for getting players from entities.
- Also adds method to merge two filters.
2021-10-03 11:57:44 +02:00
metalgearsloth
2353f0ed6f Debugdraw enhancements (#2080)
* Debugdraw enhancements

* Fix up the overlays

* Changes

* Box2D licence

* Update Robust.Client/Console/Commands/PhysicsOverlayCommands.cs

Co-authored-by: Paul Ritter <ritter.paul1@googlemail.com>

* Fix dependency

Co-authored-by: Paul Ritter <ritter.paul1@googlemail.com>
2021-10-03 17:58:08 +11:00
Vera Aguilera Puerto
8be4b39449 Adds utility "PrototypeFlags" collection. (#2074) 2021-10-02 12:14:24 +02:00
Vera Aguilera Puerto
e2c7d95519 Don't make SubscriptionsEnumerator nullable for no good reason. 2021-10-02 11:28:38 +02:00
Vera Aguilera Puerto
0e6c00573f EventTables' "TryGetSubscriptions" doesn't throw anymore when it can't find the uid on the event tables. 2021-10-02 11:26:38 +02:00
Vera Aguilera Puerto
fdcaa83b63 AudioSystem uses TryGet for ITransformComponent. 2021-10-01 12:48:24 +02:00
metalgearsloth
cb1b93e89c Revert this because it makes contacts look like shit 2021-09-30 22:49:37 +10:00
ike709
7d271a83b1 Updates cefglue (#2083)
Co-authored-by: ike709 <ike709@github.com>
2021-09-29 21:35:08 +02:00
Visne
58ea614c1f Specify defaults for alignment explicitly 2021-09-29 19:12:17 +02:00
metalgearsloth
ebaba35e8b Aggressively sleep physics bodies (#2078)
* Aggressively sleep physics bodies

* Fix tests

* Also buff angular damping
2021-09-29 20:06:56 +10:00
Vera Aguilera Puerto
35f4a4b011 Adds OwnerUid property to components. 2021-09-29 11:23:48 +02:00
metalgearsloth
0eebfe6931 Optimise anchored entity states for PVS (#2067) 2021-09-29 10:47:30 +02:00
metalgearsloth
9dafe46552 Don't generate physics contacts for non-predicted bodies (#2070)
These are all just handled on the server anyway so it's a significant waste of performance on the client for busy scenes.
2021-09-29 10:45:43 +02:00
metalgearsloth
b5d3772ef4 Fix items getting stuck awake 2021-09-29 13:59:12 +10:00
metalgearsloth
9baa2e6469 Slight grid fixtures optimisation (#2075) 2021-09-29 00:11:20 +10:00
Vera Aguilera Puerto
87f5c059c4 Remove IComponentManager entirely. 2021-09-28 13:40:48 +02:00
Vera Aguilera Puerto
fbcdd63988 Gets rid of most ComponentManager usages. (#2076)
- Obsoletes more `ComponentManager` fields/properties.
2021-09-28 13:36:18 +02:00
Vera Aguilera Puerto
07542ff6eb Further IComponentManager warning cleanup. 2021-09-28 12:56:12 +02:00
Vera Aguilera Puerto
56dd3ff03b Clean up BaseServer slightly.
- It doesn't CullRemovedComponents twice anymore.
- Remove IComponentManager from it.
2021-09-28 12:42:25 +02:00
Vera Aguilera Puerto
15778d3af4 Fix some warnings regarding IComponentManager. 2021-09-28 12:41:49 +02:00
Vera Aguilera Puerto
0fce2e438a Add EntityUid overloads for SoundSystem, AudioSystem. 2021-09-28 11:41:15 +02:00
Vera Aguilera Puerto
7d39e130f4 Fix bug involving the MapCoordinates SpawnEntity overload.
When you tried to spawn an anchored entity, it wouldn't spawn it in the position you specified, as the entity is anchored...
Now, we forcibly disable anchoring just for moving the entity, and restore the old anchor state when we're done.
Yes, this is a bad solution. No, I don't regret it: We need to get our shit together and have a way to forcibly teleport/move entities that are anchored for things like this.
2021-09-27 20:16:23 +02:00
Vera Aguilera Puerto
85249e7be7 Add Filter overloads for EntityUid. 2021-09-27 14:59:22 +02:00
metalgearsloth
e91ce4c5ad Fix animation crash 2021-09-27 15:29:43 +10:00
metalgearsloth
09fb921f38 Fix occluder edges for grid rotation (#2054) 2021-09-27 14:53:09 +10:00
metalgearsloth
1acad96eff Optimise animations a bit (#2006)
* Optimise animations

L'il bit of ECS at the time.

* Add pointlight event too

* System methods

* Bool flag

* Fix perf problem

* Use RQ
2021-09-27 14:51:10 +10:00
metalgearsloth
3dac144bef Remie is in engine now (#2066) 2021-09-27 14:38:41 +10:00
metalgearsloth
bccda8d87c Fix deleting grid crashing 2021-09-27 09:57:07 +10:00
metalgearsloth
5af8732054 Optimise ServerMapManager GetStateData
Free 4% performance on PVS
2021-09-26 17:10:48 +10:00
metalgearsloth
6f057e0c85 Set capacity for containermanager getcompstate 2021-09-26 16:52:02 +10:00
Paul
d18743a3cc improves vectorserializer copypasta 2021-09-26 02:13:40 +02:00
ike709
c785311a74 Adds support for "1x2" Vector2i serializing (#2065) 2021-09-26 02:04:47 +02:00
Vera Aguilera Puerto
949a584325 Fix missing using directive when EXCEPTION_TOLERANCE is enabled. 2021-09-25 18:31:18 +02:00
Vera Aguilera Puerto
c7461221b9 Merge ComponentManager into EntityManager. (#2064)
* Merge ComponentManager into EntityManager.

* Obsolete IComponentManager.
2021-09-25 18:23:11 +02:00
Pieter-Jan Briers
cd2942a0da Fix splitcontainer layout calculations. 2021-09-25 18:18:33 +02:00
Vera Aguilera Puerto
0b8cb5e3b6 Add proxy methods to SharedContainerSystem. (#2061) 2021-09-25 18:18:11 +02:00
metalgearsloth
aa3992255e Use approximate bounds for sprite rendering (#2052) 2021-09-23 23:53:37 +10:00
metalgearsloth
6d0cf375d0 Hotfix centroids
Not the permanent solution given it doesn't handle offset vertices but good enough for now.
2021-09-23 13:19:38 +10:00
Pieter-Jan Briers
52ad027854 Increase display.blur_light_factor to 0.001f 2021-09-21 08:09:02 +02:00
metalgearsloth
60ac47fad7 Fix rendertexture for rotated eyes (#2053) 2021-09-20 18:19:05 +02:00
metalgearsloth
8b462cc099 Remove 2 unused field dependencies 2021-09-21 00:18:33 +10:00
metalgearsloth
e918e40cd2 Fix ShapeTest warnings 2021-09-21 00:01:49 +10:00
metalgearsloth
39656f6810 Fix fixture mass setter crash 2021-09-20 22:30:31 +10:00
metalgearsloth
68576ace72 Accurate grid bounds (#2027)
* Fright night

* Shitty bounds working

* No more fixture leak

* Optimise TryFindGridAt

Should be O(1) now instead of the previous O(n) for number of fixtures

* ambush

* Merge stuffies

* Merge to master

* Fixes I guess

* Fix client sync

* Fix grid deserialization

* Jank test

* Fix deferral shitfuckery

* Optimise and remove

* Fixes

* Just werk dam u

* Optimisations

* Bunch of fixes

* FINALLY IT'S DONE

* Fixes

* Fix

* Comment
2021-09-20 21:07:31 +10:00
Vera Aguilera Puerto
ac21e24f33 Proper xmldocs for Resolves. 2021-09-20 11:06:42 +02:00
Vera Aguilera Puerto
843b8e69ef EntitySystem Resolve helper methods. (#2047)
* EntitySystem Resolve helper methods.

* Debug assert for component ownership

* Fix assert
2021-09-20 11:03:24 +02:00
metalgearsloth
a827bbec2b Add a debug system for showing anchored ents (#2030) 2021-09-20 11:00:30 +02:00
Visne
a5be8e723e Remove some unused obsolete stuff (#2049) 2021-09-20 10:59:50 +02:00
metalgearsloth
bac3ff969b Don't sleep items parented to map (#2031) 2021-09-20 10:58:44 +02:00
Javier Guardia Fernández
bfabc8e51a Fix deleting empty containers on the client deleting their previous contents (#2036) 2021-09-20 10:58:14 +02:00
Paul Ritter
c116cd6d8b new xaml error for invalid root node type (#2038)
Co-authored-by: Paul <ritter.paul1+git@googlemail.com>
2021-09-20 10:57:43 +02:00
Visne
30f39dfb66 Remove MarginContainer (#2050) 2021-09-20 10:43:12 +02:00
Pieter-Jan Briers
6b81c283f5 Light blur experiment. 2021-09-20 10:40:46 +02:00
Pieter-Jan Briers
c91170b50e Explicit GL debugger detection to fix ANGLE console spam. 2021-09-20 10:40:30 +02:00
Pieter-Jan Briers
831dd0e5bb Flush render queue after WorldSpaceBelowFOV()
Fixes FOV appearing below atmos in SS14.
2021-09-20 10:14:49 +02:00
Swept
028dea2819 Updates documentation link in comment 2021-09-20 01:25:57 +00:00
Visne
5cbad8b3d9 Remove SizeFlags 2021-09-20 02:15:14 +02:00
Pieter-Jan Briers
9117f8c776 These IRenderHandle APIs are going public, deal with it. 2021-09-19 23:37:47 +02:00
mirrorcult
a46c539462 Merge pull request #2048 from Visne/vbox-hbox-remove
Remove VBoxContainer/HBoxContainer completely
2021-09-19 13:03:17 -07:00
Visne
c9cbeadc9e Trigger tests 2021-09-19 21:37:42 +02:00
Visne
8f1386cb19 Remove VBoxContainer/HBoxContainer completely 2021-09-19 18:41:10 +02:00
Vera Aguilera Puerto
3b902e0557 IMetaDataComponent implements IComponent 2021-09-19 10:46:09 +02:00
ike709
3142dcbfda Adds some OpenDream things to the sandbox whitelist (#2042) 2021-09-18 22:40:22 +02:00
Vera Aguilera Puerto
29ffd07712 MapCoordinates Offset overload that takes two floats. 2021-09-18 13:01:30 +02:00
Vera Aguilera Puerto
afd687e392 Add Offset method to MapCoordinates. 2021-09-18 12:49:54 +02:00
Vera Aguilera Puerto
72341a25af VV attribute for ViewSubscriptions in PlayerSession. 2021-09-18 11:14:44 +02:00
Vera Aguilera Puerto
6d03840fbd Makes BoundUserInterfaces partially ECS. (#2033) 2021-09-18 10:41:06 +02:00
metalgearsloth
ea2d63b404 Add test for lookup anchoring (#2035)
* Add test for lookup anchoring

* Updates

* Fix test
2021-09-17 01:43:41 +10:00
metalgearsloth
42a137d106 Raise anchoring broadcasted 2021-09-17 00:06:02 +10:00
metalgearsloth
8f8c1eccc3 hotfix eyes 2021-09-16 17:42:30 +10:00
metalgearsloth
b91fa6ba75 Reduce position approx tolerance 2021-09-16 14:03:33 +10:00
metalgearsloth
e270f4f06c Reduce velocity approx tolerance 2021-09-16 13:57:18 +10:00
metalgearsloth
56b7957878 Receive testbed from content (#2034) 2021-09-16 13:02:43 +10:00
metalgearsloth
209eb5fea0 Fix 90% of grid rotation bugs (#2003)
* View box2rotated

* Changes

* Stash someday

* Sync

* Grid rotation in a commit

* SIMD comment

* Minor TryFindGridAt optimisation

* More fixes

* Optimise rays a tad

* Reduce code duplication

* Fix anchoring

* More fixes woopsie

* Eye matching parent

* Centre of mass stuffsies

* Remove TODO

* Add TODO

* Revert anchor crash

* Fix master merge-in whoopsie

* Fixes

* Woops

* Fixed viewport transform

* Re-fix rendering
2021-09-16 12:59:16 +10:00
Javier Guardia Fernández
417954e66c Simplify how some serialization expression trees return values (#2040) 2021-09-15 21:00:42 +02:00
Visne
e4fb860985 Fix licenses (#2041) 2021-09-16 00:18:51 +10:00
Pieter-Jan Briers
d70481aedd Update Linguini
This fixes several compiler warnings.
2021-09-15 16:17:52 +02:00
Pieter-Jan Briers
52eb581b56 Fix some compiler warnings. 2021-09-15 15:55:27 +02:00
Pieter-Jan Briers
918fa8e3b9 Fix handling of key names GLFW returns as null. 2021-09-15 13:37:24 +02:00
Visne
1c7f19bf67 Add OptionButton margin (#2020) 2021-09-15 01:49:49 +02:00
Javier Guardia Fernández
0cbff8dee1 Change serialization reading to only do type checks once per type (#2018)
* Change serialization reading to only do type checks once per type

* Optimize for sealed types in arrays

* Oops the context

* Fix ISelfSerialize node type and null values

* Add int read test

* Remove nullability from constructor and property of DeserializedValue

* Add clearing readers to serialization shutdown

* Fix struct populate default values
Remove some notnull constraints

* Replace robust gen with normal il generator
2021-09-13 18:14:52 +02:00
metalgearsloth
e7e08e5dd6 Merge branch 'master' into fix-sound 2021-09-13 20:39:42 +10:00
metalgearsloth
670ff54ef0 Fix SetAsBox 2021-09-13 19:46:31 +10:00
metalgearsloth
8f519c52b6 Fix multi-viewer PVS crash 2021-09-13 15:59:18 +10:00
Javier Guardia Fernández
fbd29afbd8 Fix data definitions sometimes not being found in tests (#2026) 2021-09-12 11:47:50 +02:00
Javier Guardia Fernández
2bbdb23716 Fix tests getting stuck until terminated (#2025) 2021-09-12 11:42:46 +02:00
Javier Guardia Fernández
e2dda67eef Add test for IPopulateDefaultValues (#2024) 2021-09-12 10:07:59 +02:00
metalgearsloth
699615df97 Remove Snapus Gridus (#2014) 2021-09-12 16:34:51 +10:00
Javier Guardia Fernández
e804994a5b Fix serialization manager shutdown (#2011) 2021-09-11 00:00:31 +02:00
metalgearsloth
24f7ecccc0 Remove AppearanceTestComponent (#2015) 2021-09-10 23:59:42 +02:00
Javier Guardia Fernández
21d43350f0 Add benchmark for serializing arrays of strings, ints and definitions (#2017)
* Add benchmark for serializing arrays of strings, ints and definitions

* Add sealed elements array benchmark
2021-09-10 17:57:16 +02:00
Javier Guardia Fernández
63eb7847a1 Add test for serializing nullable ints (#2016) 2021-09-10 17:50:02 +02:00
metalgearsloth
4a4f07a10c StopPlaying won't throw if already disposed (#2013) 2021-09-09 12:53:33 +02:00
Javier Guardia Fernández
1c816941ec Change serialization writing with serializers to use expression trees (#2010)
* Change serialization writing with serializers to use expression trees

* Add test
2021-09-08 13:05:34 +02:00
Javier Guardia Fernández
2143c9abc3 Change serialization reading with serializers to use expression trees (#2009)
* Change serialization reading with serializers to use expression trees

* Add test

* Eliminate lambda capture
2021-09-08 12:07:17 +02:00
Vera Aguilera Puerto
4b6ceed586 Adds FileNotFoundException to the sandbox whitelist. 2021-09-08 09:52:20 +02:00
metalgearsloth
49c6c0c9d8 Fix anchoring crashes 2021-09-08 10:55:44 +10:00
metalgearsloth
54f6143be3 Quick audio fix nothing to see here 2021-09-07 18:17:58 +10:00
ShadowCommander
9ac8db37cc Add invalid check 2021-08-28 02:37:20 -07:00
ShadowCommander
2d6eebfae2 Rename GridCoordinates to FallbackCoordinates and move GetFallbackCoordinates to shared 2021-08-28 02:29:47 -07:00
ShadowCommander
5540d643ef Change from MapCoordinates to GridCoordinates 2021-08-12 01:45:07 -07:00
ShadowCommander
76c1d9f97b Change Audio to also use MapCoordinates as a fallback 2021-08-10 20:42:46 -07:00
462 changed files with 14728 additions and 7157 deletions

View File

@@ -1,52 +0,0 @@
environment:
sonarqubekey:
secure: h3llq6OeVa94hJ71UOEQSQDq75vFt+doso7iFry0gvt/fFcyeonY9wY+ETOIVITK
global:
PYTHONUNBUFFERED: True
HEADLESS: 1 # For the unit tests.
version: 0.1.0.{build}
pull_requests:
do_not_increment_build_number: true
image: Visual Studio 2019
install:
- ps: >
if (-Not $env:APPVEYOR_PULL_REQUEST_NUMBER -And $env:APPVEYOR_REPO_BRANCH -Eq "master")
{
cinst msbuild-sonarqube-runner;
}
before_build:
- cmd: py -3.5 -m pip install --user requests
- cmd: git submodule update --init --recursive
- ps: >
if (-Not $env:APPVEYOR_PULL_REQUEST_NUMBER -And $env:APPVEYOR_REPO_BRANCH -Eq "master")
{
SonarScanner.MSBuild.exe begin /k:"ss14" /d:"sonar.host.url=https://sonarcloud.io" /d:"sonar.login=$env:sonarqubekey" /o:"space-wizards" /d:sonar.cs.nunit.reportsPaths="$(Get-Location)\nunitTestResult.xml";
}
platform: x64
configuration: Debug
cache:
- packages -> **\*.csproj
- Dependencies
build:
project: RobustToolbox.sln
parallel: false
verbosity: minimal
build_script:
- ps: dotnet build RobustToolbox.sln /p:AppVeyor=yes
test_script:
- ps: dotnet test Robust.UnitTesting/Robust.UnitTesting.csproj
after_test:
- ps: >
if (-Not $env:APPVEYOR_PULL_REQUEST_NUMBER -And $env:APPVEYOR_REPO_BRANCH -Eq "master")
{
SonarScanner.MSBuild.exe end /d:"sonar.login=$env:sonarqubekey";
}

View File

@@ -22,7 +22,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: 5.0.100
dotnet-version: 6.0.100
- name: Install dependencies
run: dotnet restore
- name: Build

View File

@@ -42,7 +42,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: 5.0.100
dotnet-version: 6.0.100
- name: Build
run: dotnet build

View File

@@ -23,7 +23,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: 5.0.100
dotnet-version: 6.0.100
- name: Package client
run: Tools/package_client_build.py -p windows mac linux

View File

@@ -21,7 +21,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: 5.0.100
dotnet-version: 6.0.100
- name: Disable submodule autoupdate
run: touch BuildChecker/DISABLE_SUBMODULE_AUTOUPDATE
@@ -38,4 +38,4 @@ jobs:
- name: Content.Tests
run: dotnet test --no-build Content.Tests/Content.Tests.csproj -v n
- name: Content.IntegrationTests
run: COMPlus_gcServer=1 dotnet test --no-build Content.IntegrationTests/Content.IntegrationTests.csproj -v n
run: COMPlus_gcServer=1 dotnet test --no-build Content.IntegrationTests/Content.IntegrationTests.csproj -v n

2
.gitmodules vendored
View File

@@ -18,4 +18,4 @@
url = https://github.com/space-wizards/Linguini
[submodule "cefglue"]
path = cefglue
url = https://gitlab.com/xiliumhq/chromiumembedded/cefglue/
url = https://github.com/space-wizards/cefglue.git

View File

@@ -3,10 +3,10 @@
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<DefineConstants Condition="'$(TargetFramework)' == 'netstandard2.1' or '$(TargetFramework)' == 'netcoreapp3.1'">$(DefineConstants);HAS_FULL_SPAN</DefineConstants>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<DefaultItemExcludes>Lidgren.Network/**/*</DefaultItemExcludes>
<DefineConstants>$(DefineConstants);USE_RELEASE_STATISTICS</DefineConstants>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>

View File

@@ -1,8 +1,8 @@
<Project>
<!-- Engine-specific properties. Content should not use this file. -->
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<LangVersion>9</LangVersion>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>10</LangVersion>
<Nullable>enable</Nullable>
<WarningsAsErrors>nullable</WarningsAsErrors>
</PropertyGroup>

View File

@@ -26,7 +26,7 @@
<TargetOS Condition="'$(TargetOS)' == ''">$(ActualOS)</TargetOS>
<Python>python3</Python>
<Python Condition="'$(ActualOS)' == 'Windows'">py -3</Python>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<EnableClientScripting>True</EnableClientScripting>
<!-- Client scripting is disabled on full release builds for security and size reasons. -->
<EnableClientScripting Condition="'$(FullRelease)' == 'True'">False</EnableClientScripting>

View File

@@ -10,7 +10,7 @@ Use the [content repo](https://github.com/space-wizards/space-station-14) for ac
## Documentation/Wiki
The [HackMD Wiki](https://hackmd.io/@ss14/docs/wiki) has documentation on SS14s content, engine, game design and more. We also have lots of resources for new contributors to the project.
The [wiki](https://docs.spacestation14.io/) has documentation on SS14s content, engine, game design and more. We also have lots of resources for new contributors to the project.
## Contributing

View File

@@ -24,7 +24,7 @@
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- name: Box2D
license:
license: |
MIT License
Copyright (c) 2019 Erin Catto
@@ -33,7 +33,7 @@
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
@@ -48,7 +48,7 @@
SOFTWARE.
- name: Bullet Physics SDK
license:
license: |
The files in this repository are licensed under the zlib license, except for the files under 'Extras' and examples/ThirdPartyLibs.
Bullet Continuous Collision Detection and Physics Library
@@ -60,10 +60,10 @@
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.
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.
3. This notice may not be removed or altered from any source distribution.
- name: Castle Core
license: |
@@ -360,7 +360,7 @@
limitations under the License.
- name: Farseer Physics Engine
license:
license: |
Microsoft Permissive License (Ms-PL)
This license governs use of the accompanying software.
@@ -374,26 +374,26 @@
"Licensed patents" are a contributor's patent claims that read directly on its contribution.
2. Grant of Rights
(A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3,
each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution,
(A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3,
each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution,
prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.
(B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3,
each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to
make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or
(B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3,
each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to
make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or
derivative works of the contribution in the software.
3. Conditions and Limitations
(A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
(B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software,
(B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software,
your patent license from such contributor to the software ends automatically.
(C) If you distribute any portion of the software, you must retain all copyright, patent, trademark,
(C) If you distribute any portion of the software, you must retain all copyright, patent, trademark,
and attribution notices that are present in the software.
(D) If you distribute any portion of the software in source code form, you may do so only under this license by
including a complete copy of this license with your distribution. If you distribute any portion of the software in
(D) If you distribute any portion of the software in source code form, you may do so only under this license by
including a complete copy of this license with your distribution. If you distribute any portion of the software in
compiled or object code form, you may only do so under a license that complies with this license.
(E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties,
guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change.
To the extent permitted under your local laws, the contributors exclude the implied warranties of
(E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties,
guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change.
To the extent permitted under your local laws, the contributors exclude the implied warranties of
merchantability, fitness for a particular purpose and non-infringement.
- name: Mono.Cecil

View File

@@ -2,29 +2,15 @@ preset raw;
#include "/Shaders/Internal/shadow_cast_shared.swsl"
#include "/Shaders/Internal/fov_shared.swsl"
const highp float g_MinVariance = 0.0;
varying highp vec2 worldPosition;
// Center of the FOV, in world coordinates.
uniform highp vec2 center;
void vertex()
{
highp vec3 transformed = modelMatrix * vec3(VERTEX, 1.0);
worldPosition = transformed.xy;
transformed = projectionMatrix * viewMatrix * transformed;
VERTEX = transformed.xy;
}
void fragment()
{
highp vec2 diff = worldPosition - center;
highp float ourDist = length(worldSpaceDiff);
highp float ourDist = length(diff);
highp vec2 occlDist = occludeDepth(diff, TEXTURE, 0.25);
highp vec2 occlDist = occludeDepth(worldSpaceDiff, TEXTURE, 0.25);
highp float occlusion = ChebyshevUpperBound(occlDist, ourDist);

View File

@@ -2,32 +2,18 @@ preset raw;
#include "/Shaders/Internal/shadow_cast_shared.swsl"
#include "/Shaders/Internal/fov_shared.swsl"
const highp float g_MinVariance = 0.0;
varying highp vec2 worldPosition;
// Center of the FOV, in world coordinates.
uniform highp vec2 center;
void vertex()
{
highp vec3 transformed = modelMatrix * vec3(VERTEX, 1.0);
worldPosition = transformed.xy;
transformed = projectionMatrix * viewMatrix * transformed;
VERTEX = transformed.xy;
}
void fragment()
{
highp vec2 diff = worldPosition - center;
highp float ourDist = length(worldSpaceDiff);
highp float ourDist = length(diff);
highp float occlDist = occludeDepth(diff, TEXTURE, 0.75).r;
highp float occlDist = occludeDepth(worldSpaceDiff, TEXTURE, 0.75).r;
// *Very* simple biased shadow check for FOV.
if (!doesOcclude(diff, TEXTURE, 0.75, -0.75/32.0))
if (!doesOcclude(worldSpaceDiff, TEXTURE, 0.75, -0.75/32.0))
{
discard;
}

View File

@@ -0,0 +1,16 @@
// Shared between fov-lighting.swsl and fov.swsl, which both use the Clyde quad,
// manually transformed into clip-space to cover the entire viewport
// World-space position offset from centre to pixel.
varying highp vec2 worldSpaceDiff;
// Inverted transformation matrix from clip coordinates to difference coordinates.
uniform highp mat3 clipToDiff;
void vertex()
{
// Convert quad-space (0.0 to 1.0) to clip-space (-1.0 to 1.0)
VERTEX = (VERTEX.xy - 0.5) * 2.0;
worldSpaceDiff = (clipToDiff * vec3(VERTEX, 1.0)).xy;
}

View File

@@ -0,0 +1,41 @@
preset raw;
uniform highp vec2 direction;
uniform highp vec2 size;
uniform highp float radius;
varying highp vec2 pos;
varying highp vec4 blurPos1;
varying highp vec4 blurPos2;
void vertex()
{
highp float aspect = size.y / size.x;
highp float horRadius = aspect * radius;
highp vec2 offset = vec2(horRadius, radius) * direction;
VERTEX = apply_mvp(VERTEX);
pos = (VERTEX + vec2(1.0)) / 2.0;
blurPos1.xy = pos + offset;
blurPos1.zw = pos - offset;
blurPos2.xy = pos + offset * 2.0;
blurPos2.zw = pos - offset * 2.0;
}
void fragment()
{
// Very simple gaussian blur.
highp vec4 sum = zTexture(pos) * 0.375;
sum += zTexture(blurPos1.xy) * 0.25;
sum += zTexture(blurPos1.zw) * 0.25;
sum += zTexture(blurPos2.xy) * 0.0625;
sum += zTexture(blurPos2.zw) * 0.0625;
COLOR = sum;
}

View File

@@ -40,7 +40,7 @@ void fragment()
highp vec2 diff = worldPosition - lightCenter;
// Totally not hacky PCF on top of VSM.
highp float occlusion = createOcclusion(diff);
highp float occlusion = lightIndex < 0.0 ? 1.0 : createOcclusion(diff);
if (occlusion == 0.0)
{

View File

@@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
@@ -92,7 +93,7 @@ namespace Robust.Analyzers
continue;
// If we find that the containing class is specified in the attribute, return! All is good.
if (SymbolEqualityComparer.Default.Equals(containingType, t))
if (InheritsFromOrEquals(containingType, t))
return;
}
@@ -103,5 +104,26 @@ namespace Robust.Analyzers
}
}
}
private bool InheritsFromOrEquals(INamedTypeSymbol type, INamedTypeSymbol baseType)
{
foreach (var otherType in GetBaseTypesAndThis(type))
{
if (SymbolEqualityComparer.Default.Equals(otherType, baseType))
return true;
}
return false;
}
private IEnumerable<INamedTypeSymbol> GetBaseTypesAndThis(INamedTypeSymbol namedType)
{
var current = namedType;
while (current != null)
{
yield return current;
current = current.BaseType;
}
}
}
}

View File

@@ -0,0 +1,11 @@
using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Benchmarks.Serialization.Definitions
{
[DataDefinition]
public sealed class SealedDataDefinitionWithString
{
[DataField("string")]
public string StringField { get; init; } = default!;
}
}

View File

@@ -0,0 +1,127 @@
using BenchmarkDotNet.Attributes;
using Robust.Benchmarks.Serialization.Definitions;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Sequence;
using Robust.Shared.Serialization.Markdown.Value;
namespace Robust.Benchmarks.Serialization
{
[MemoryDiagnoser]
public class SerializationArrayBenchmark : SerializationBenchmark
{
public SerializationArrayBenchmark()
{
InitializeSerialization();
OneStringDefNode = new SequenceDataNode();
OneStringDefNode.Add(new MappingDataNode
{
["string"] = new ValueDataNode("ABC")
});
TenStringDefsNode = new SequenceDataNode();
for (var i = 0; i < 10; i++)
{
TenStringDefsNode.Add(new MappingDataNode
{
["string"] = new ValueDataNode("ABC")
});
}
}
private SequenceDataNode EmptyNode { get; } = new();
private SequenceDataNode OneIntNode { get; } = new("1");
private SequenceDataNode TenIntsNode { get; } = new("1", "2", "3", "4", "5", "6", "7", "8", "9", "10");
private SequenceDataNode OneStringDefNode { get; }
private SequenceDataNode TenStringDefsNode { get; }
[Benchmark]
[BenchmarkCategory("read")]
public string[]? ReadEmptyString()
{
return SerializationManager.ReadValue<string[]>(EmptyNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public string[]? ReadOneString()
{
return SerializationManager.ReadValue<string[]>(OneIntNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public string[]? ReadTenStrings()
{
return SerializationManager.ReadValue<string[]>(TenIntsNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public int[]? ReadEmptyInt()
{
return SerializationManager.ReadValue<int[]>(EmptyNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public int[]? ReadOneInt()
{
return SerializationManager.ReadValue<int[]>(OneIntNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public int[]? ReadTenInts()
{
return SerializationManager.ReadValue<int[]>(TenIntsNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public DataDefinitionWithString[]? ReadEmptyStringDataDef()
{
return SerializationManager.ReadValue<DataDefinitionWithString[]>(EmptyNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public DataDefinitionWithString[]? ReadOneStringDataDef()
{
return SerializationManager.ReadValue<DataDefinitionWithString[]>(OneStringDefNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public DataDefinitionWithString[]? ReadTenStringDataDefs()
{
return SerializationManager.ReadValue<DataDefinitionWithString[]>(TenStringDefsNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public SealedDataDefinitionWithString[]? ReadEmptySealedStringDataDef()
{
return SerializationManager.ReadValue<SealedDataDefinitionWithString[]>(EmptyNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public SealedDataDefinitionWithString[]? ReadOneSealedStringDataDef()
{
return SerializationManager.ReadValue<SealedDataDefinitionWithString[]>(OneStringDefNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public SealedDataDefinitionWithString[]? ReadTenSealedStringDataDefs()
{
return SerializationManager.ReadValue<SealedDataDefinitionWithString[]>(TenStringDefsNode);
}
}
}

View File

@@ -1,593 +0,0 @@
using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.UserInterface;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using SixLabors.ImageSharp.PixelFormats;
using Xilium.CefGlue;
using static Robust.Client.CEF.CefKeyCodes;
using static Robust.Client.CEF.CefKeyCodes.ChromiumKeyboardCode;
using static Robust.Client.Input.Keyboard;
namespace Robust.Client.CEF
{
// Funny browser control to integrate in UI.
public class BrowserControl : Control, IBrowserControl, IRawInputControl
{
private const int ScrollSpeed = 50;
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly IInputManager _inputMgr = default!;
[Dependency] private readonly CefManager _cef = default!;
private RobustRequestHandler _requestHandler = new RobustRequestHandler(Logger.GetSawmill("root"));
private LiveData? _data;
private string _startUrl = "about:blank";
[ViewVariables(VVAccess.ReadWrite)]
public string Url
{
get => _data == null ? _startUrl : _data.Browser.GetMainFrame().Url;
set
{
if (_data == null)
_startUrl = value;
else
_data.Browser.GetMainFrame().LoadUrl(value);
}
}
[ViewVariables] public bool IsLoading => _data?.Browser.IsLoading ?? false;
private readonly Dictionary<Key, ChromiumKeyboardCode> _keyMap = new()
{
[Key.A] = VKEY_A,
[Key.B] = VKEY_B,
[Key.C] = VKEY_C,
[Key.D] = VKEY_D,
[Key.E] = VKEY_E,
[Key.F] = VKEY_F,
[Key.G] = VKEY_G,
[Key.H] = VKEY_H,
[Key.I] = VKEY_I,
[Key.J] = VKEY_J,
[Key.K] = VKEY_K,
[Key.L] = VKEY_L,
[Key.M] = VKEY_M,
[Key.N] = VKEY_N,
[Key.O] = VKEY_O,
[Key.P] = VKEY_P,
[Key.Q] = VKEY_Q,
[Key.R] = VKEY_R,
[Key.S] = VKEY_S,
[Key.T] = VKEY_T,
[Key.U] = VKEY_U,
[Key.V] = VKEY_V,
[Key.W] = VKEY_W,
[Key.X] = VKEY_X,
[Key.Y] = VKEY_Y,
[Key.Z] = VKEY_Z,
[Key.Num0] = VKEY_0,
[Key.Num1] = VKEY_1,
[Key.Num2] = VKEY_2,
[Key.Num3] = VKEY_3,
[Key.Num4] = VKEY_4,
[Key.Num5] = VKEY_5,
[Key.Num6] = VKEY_6,
[Key.Num7] = VKEY_7,
[Key.Num8] = VKEY_8,
[Key.Num9] = VKEY_9,
[Key.NumpadNum0] = VKEY_NUMPAD0,
[Key.NumpadNum1] = VKEY_NUMPAD1,
[Key.NumpadNum2] = VKEY_NUMPAD2,
[Key.NumpadNum3] = VKEY_NUMPAD3,
[Key.NumpadNum4] = VKEY_NUMPAD4,
[Key.NumpadNum5] = VKEY_NUMPAD5,
[Key.NumpadNum6] = VKEY_NUMPAD6,
[Key.NumpadNum7] = VKEY_NUMPAD7,
[Key.NumpadNum8] = VKEY_NUMPAD8,
[Key.NumpadNum9] = VKEY_NUMPAD9,
[Key.Escape] = VKEY_ESCAPE,
[Key.Control] = VKEY_CONTROL,
[Key.Shift] = VKEY_SHIFT,
[Key.Alt] = VKEY_MENU,
[Key.LSystem] = VKEY_LWIN,
[Key.RSystem] = VKEY_RWIN,
[Key.LBracket] = VKEY_OEM_4,
[Key.RBracket] = VKEY_OEM_6,
[Key.SemiColon] = VKEY_OEM_1,
[Key.Comma] = VKEY_OEM_COMMA,
[Key.Period] = VKEY_OEM_PERIOD,
[Key.Apostrophe] = VKEY_OEM_7,
[Key.Slash] = VKEY_OEM_2,
[Key.BackSlash] = VKEY_OEM_5,
[Key.Tilde] = VKEY_OEM_3,
[Key.Equal] = VKEY_OEM_PLUS,
[Key.Space] = VKEY_SPACE,
[Key.Return] = VKEY_RETURN,
[Key.BackSpace] = VKEY_BACK,
[Key.Tab] = VKEY_TAB,
[Key.PageUp] = VKEY_PRIOR,
[Key.PageDown] = VKEY_NEXT,
[Key.End] = VKEY_END,
[Key.Home] = VKEY_HOME,
[Key.Insert] = VKEY_INSERT,
[Key.Delete] = VKEY_DELETE,
[Key.Minus] = VKEY_OEM_MINUS,
[Key.NumpadAdd] = VKEY_ADD,
[Key.NumpadSubtract] = VKEY_SUBTRACT,
[Key.NumpadDivide] = VKEY_DIVIDE,
[Key.NumpadMultiply] = VKEY_MULTIPLY,
[Key.NumpadDecimal] = VKEY_DECIMAL,
[Key.Left] = VKEY_LEFT,
[Key.Right] = VKEY_RIGHT,
[Key.Up] = VKEY_UP,
[Key.Down] = VKEY_DOWN,
[Key.F1] = VKEY_F1,
[Key.F2] = VKEY_F2,
[Key.F3] = VKEY_F3,
[Key.F4] = VKEY_F4,
[Key.F5] = VKEY_F5,
[Key.F6] = VKEY_F6,
[Key.F7] = VKEY_F7,
[Key.F8] = VKEY_F8,
[Key.F9] = VKEY_F9,
[Key.F10] = VKEY_F10,
[Key.F11] = VKEY_F11,
[Key.F12] = VKEY_F12,
[Key.F13] = VKEY_F13,
[Key.F14] = VKEY_F14,
[Key.F15] = VKEY_F15,
[Key.Pause] = VKEY_PAUSE,
};
public BrowserControl()
{
CanKeyboardFocus = true;
KeyboardFocusOnClick = true;
MouseFilter = MouseFilterMode.Stop;
IoCManager.InjectDependencies(this);
}
protected override void EnteredTree()
{
base.EnteredTree();
_cef.CheckInitialized();
DebugTools.AssertNull(_data);
// A funny render handler that will allow us to render to the control.
var renderer = new ControlRenderHandler(this);
// A funny web cef client. This can actually be shared by multiple browsers, but I'm not sure how the
// rendering would work in that case? TODO CEF: Investigate a way to share the web client?
var client = new RobustCefClient(renderer, _requestHandler, new RobustLoadHandler());
var info = CefWindowInfo.Create();
// FUNFACT: If you DO NOT set these below and set info.Width/info.Height instead, you get an external window
// Good to know, huh? Setup is the same, except you can pass a dummy render handler to the CEF client.
info.SetAsWindowless(IntPtr.Zero, false); // TODO CEF: Pass parent handle?
info.WindowlessRenderingEnabled = true;
var settings = new CefBrowserSettings()
{
WindowlessFrameRate = 60
};
// Create the web browser! And by default, we go to about:blank.
var browser = CefBrowserHost.CreateBrowserSync(info, client, settings, _startUrl);
var texture = _clyde.CreateBlankTexture<Bgra32>(Vector2i.One);
_data = new LiveData(texture, client, browser, renderer);
}
protected override void ExitedTree()
{
base.ExitedTree();
DebugTools.AssertNotNull(_data);
_data!.Texture.Dispose();
_data.Browser.GetHost().CloseBrowser(true);
_data = null;
}
protected internal override void MouseMove(GUIMouseMoveEventArgs args)
{
base.MouseMove(args);
if (_data == null)
return;
// Logger.Debug();
var modifiers = CalcMouseModifiers();
var mouseEvent = new CefMouseEvent(
(int) args.RelativePosition.X, (int) args.RelativePosition.Y,
modifiers);
_data.Browser.GetHost().SendMouseMoveEvent(mouseEvent, false);
}
protected internal override void MouseExited()
{
base.MouseExited();
if (_data == null)
return;
var modifiers = CalcMouseModifiers();
_data.Browser.GetHost().SendMouseMoveEvent(new CefMouseEvent(0, 0, modifiers), true);
}
protected internal override void MouseWheel(GUIMouseWheelEventArgs args)
{
base.MouseWheel(args);
if (_data == null)
return;
var modifiers = CalcMouseModifiers();
var mouseEvent = new CefMouseEvent(
(int) args.RelativePosition.X, (int) args.RelativePosition.Y,
modifiers);
_data.Browser.GetHost().SendMouseWheelEvent(
mouseEvent,
(int) args.Delta.X * ScrollSpeed,
(int) args.Delta.Y * ScrollSpeed);
}
bool IRawInputControl.RawKeyEvent(in GuiRawKeyEvent guiRawEvent)
{
if (_data == null)
return false;
var host = _data.Browser.GetHost();
if (guiRawEvent.Key is Key.MouseLeft or Key.MouseMiddle or Key.MouseRight)
{
var key = guiRawEvent.Key switch
{
Key.MouseLeft => CefMouseButtonType.Left,
Key.MouseMiddle => CefMouseButtonType.Middle,
Key.MouseRight => CefMouseButtonType.Right,
_ => default // not possible
};
var mouseEvent = new CefMouseEvent(
guiRawEvent.MouseRelative.X, guiRawEvent.MouseRelative.Y,
CefEventFlags.None);
// Logger.Debug($"MOUSE: {guiRawEvent.Action} {guiRawEvent.Key} {guiRawEvent.ScanCode} {key}");
// TODO: double click support?
host.SendMouseClickEvent(mouseEvent, key, guiRawEvent.Action == RawKeyAction.Up, 1);
}
else
{
// TODO: Handle left/right modifier keys??
if (!_keyMap.TryGetValue(guiRawEvent.Key, out var vkKey))
vkKey = default;
// Logger.Debug($"{guiRawEvent.Action} {guiRawEvent.Key} {guiRawEvent.ScanCode} {vkKey}");
var lParam = 0;
lParam |= (guiRawEvent.ScanCode & 0xFF) << 16;
if (guiRawEvent.Action != RawKeyAction.Down)
lParam |= 1 << 30;
if (guiRawEvent.Action == RawKeyAction.Up)
lParam |= 1 << 31;
var modifiers = CalcModifiers(guiRawEvent.Key);
host.SendKeyEvent(new CefKeyEvent
{
// Repeats are sent as key downs, I guess?
EventType = guiRawEvent.Action == RawKeyAction.Up
? CefKeyEventType.KeyUp
: CefKeyEventType.RawKeyDown,
NativeKeyCode = lParam,
// NativeKeyCode = guiRawEvent.ScanCode,
WindowsKeyCode = (int) vkKey,
IsSystemKey = false, // TODO
Modifiers = modifiers
});
if (guiRawEvent.Action != RawKeyAction.Up && guiRawEvent.Key == Key.Return)
{
host.SendKeyEvent(new CefKeyEvent
{
EventType = CefKeyEventType.Char,
WindowsKeyCode = '\r',
NativeKeyCode = lParam,
Modifiers = modifiers
});
}
}
return true;
}
private CefEventFlags CalcModifiers(Key key)
{
CefEventFlags modifiers = default;
if (_inputMgr.IsKeyDown(Key.Control))
modifiers |= CefEventFlags.ControlDown;
if (_inputMgr.IsKeyDown(Key.Alt))
modifiers |= CefEventFlags.AltDown;
if (_inputMgr.IsKeyDown(Key.Shift))
modifiers |= CefEventFlags.ShiftDown;
if (_inputMgr.IsKeyDown(Key.Shift))
modifiers |= CefEventFlags.ShiftDown;
return modifiers;
}
private CefEventFlags CalcMouseModifiers()
{
CefEventFlags modifiers = default;
if (_inputMgr.IsKeyDown(Key.Control))
modifiers |= CefEventFlags.ControlDown;
if (_inputMgr.IsKeyDown(Key.Alt))
modifiers |= CefEventFlags.AltDown;
if (_inputMgr.IsKeyDown(Key.Shift))
modifiers |= CefEventFlags.ShiftDown;
if (_inputMgr.IsKeyDown(Key.Shift))
modifiers |= CefEventFlags.ShiftDown;
if (_inputMgr.IsKeyDown(Key.MouseLeft))
modifiers |= CefEventFlags.LeftMouseButton;
if (_inputMgr.IsKeyDown(Key.MouseMiddle))
modifiers |= CefEventFlags.MiddleMouseButton;
if (_inputMgr.IsKeyDown(Key.MouseRight))
modifiers |= CefEventFlags.RightMouseButton;
return modifiers;
}
protected internal override void TextEntered(GUITextEventArgs args)
{
base.TextEntered(args);
if (_data == null)
return;
var host = _data.Browser.GetHost();
Span<char> buf = stackalloc char[2];
var written = args.AsRune.EncodeToUtf16(buf);
for (var i = 0; i < written; i++)
{
host.SendKeyEvent(new CefKeyEvent
{
EventType = CefKeyEventType.Char,
WindowsKeyCode = buf[i],
Character = buf[i],
UnmodifiedCharacter = buf[i]
});
}
}
protected override void Resized()
{
base.Resized();
if (_data == null)
return;
_data.Browser.GetHost().NotifyMoveOrResizeStarted();
_data.Browser.GetHost().WasResized();
_data.Texture.Dispose();
_data.Texture = _clyde.CreateBlankTexture<Bgra32>((PixelWidth, PixelHeight));
}
protected internal override void Draw(DrawingHandleScreen handle)
{
base.Draw(handle);
if (_data == null)
return;
var bufImg = _data.Renderer.Buffer.Buffer;
_data.Texture.SetSubImage(
Vector2i.Zero,
bufImg,
new UIBox2i(
0, 0,
Math.Min(PixelWidth, bufImg.Width),
Math.Min(PixelHeight, bufImg.Height)));
handle.DrawTexture(_data.Texture, Vector2.Zero);
}
public void StopLoad()
{
if (_data == null)
throw new InvalidOperationException();
_data.Browser.StopLoad();
}
public void Reload()
{
if (_data == null)
throw new InvalidOperationException();
_data.Browser.Reload();
}
public bool GoBack()
{
if (_data == null)
throw new InvalidOperationException();
if (!_data.Browser.CanGoBack)
return false;
_data.Browser.GoBack();
return true;
}
public bool GoForward()
{
if (_data == null)
throw new InvalidOperationException();
if (!_data.Browser.CanGoForward)
return false;
_data.Browser.GoForward();
return true;
}
public void ExecuteJavaScript(string code)
{
if (_data == null)
throw new InvalidOperationException();
// TODO: this should not run until the browser is done loading seriously does this even work?
_data.Browser.GetMainFrame().ExecuteJavaScript(code, string.Empty, 1);
}
public void AddResourceRequestHandler(Action<RequestHandlerContext> handler)
{
_requestHandler.AddResourceRequestHandler(handler);
}
public void RemoveResourceRequestHandler(Action<RequestHandlerContext> handler)
{
_requestHandler.RemoveResourceRequestHandler(handler);
}
public void AddBeforeBrowseHandler(Action<BeforeBrowseContext> handler)
{
_requestHandler.AddBeforeBrowseHandler(handler);
}
public void RemoveBeforeBrowseHandler(Action<BeforeBrowseContext> handler)
{
_requestHandler.RemoveBeforeBrowseHandler(handler);
}
private sealed class LiveData
{
public OwnedTexture Texture;
public readonly RobustCefClient Client;
public readonly CefBrowser Browser;
public readonly ControlRenderHandler Renderer;
public LiveData(
OwnedTexture texture,
RobustCefClient client,
CefBrowser browser,
ControlRenderHandler renderer)
{
Texture = texture;
Client = client;
Browser = browser;
Renderer = renderer;
}
}
}
internal class ControlRenderHandler : CefRenderHandler
{
public ImageBuffer Buffer { get; }
private Control _control;
internal ControlRenderHandler(Control control)
{
Buffer = new ImageBuffer(control);
_control = control;
}
protected override CefAccessibilityHandler? GetAccessibilityHandler() => null;
protected override void GetViewRect(CefBrowser browser, out CefRectangle rect)
{
if (_control.Disposed)
{
rect = new CefRectangle();
return;
}
// TODO CEF: Do we need to pass real screen coords? Cause what we do already works...
//var screenCoords = _control.ScreenCoordinates;
//rect = new CefRectangle((int) screenCoords.X, (int) screenCoords.Y, (int)Math.Max(_control.Size.X, 1), (int)Math.Max(_control.Size.Y, 1));
// We do the max between size and 1 because it will LITERALLY CRASH WITHOUT AN ERROR otherwise.
rect = new CefRectangle(0, 0, (int) Math.Max(_control.Size.X, 1), (int) Math.Max(_control.Size.Y, 1));
}
protected override bool GetScreenInfo(CefBrowser browser, CefScreenInfo screenInfo)
{
if (_control.Disposed)
return false;
// TODO CEF: Get actual scale factor?
screenInfo.DeviceScaleFactor = 1.0f;
return true;
}
protected override void OnPopupSize(CefBrowser browser, CefRectangle rect)
{
if (_control.Disposed)
return;
}
protected override void OnPaint(CefBrowser browser, CefPaintElementType type, CefRectangle[] dirtyRects,
IntPtr buffer, int width, int height)
{
if (_control.Disposed)
return;
foreach (var dirtyRect in dirtyRects)
{
Buffer.UpdateBuffer(width, height, buffer, dirtyRect);
}
}
protected override void OnAcceleratedPaint(CefBrowser browser, CefPaintElementType type,
CefRectangle[] dirtyRects, IntPtr sharedHandle)
{
// Unused, but we're forced to implement it so.. NOOP.
}
protected override void OnScrollOffsetChanged(CefBrowser browser, double x, double y)
{
if (_control.Disposed)
return;
}
protected override void OnImeCompositionRangeChanged(CefBrowser browser, CefRange selectedRange,
CefRectangle[] characterBounds)
{
if (_control.Disposed)
return;
}
}
}

View File

@@ -1,104 +0,0 @@
using System;
using System.IO;
using JetBrains.Annotations;
using Robust.Shared.ContentPack;
using Robust.Shared.Log;
using Robust.Shared.Utility;
// The library we're using right now. TODO CEF: Do we want to use something else? We will need to ship it ourselves if so.
using Xilium.CefGlue;
namespace Robust.Client.CEF
{
// Register this with IoC.
// TODO CEF: think if making this inherit CefApp is a good idea...
// TODO CEF: A way to handle external window browsers...
[UsedImplicitly]
public partial class CefManager
{
private CefApp _app = default!;
private bool _initialized = false;
/// <summary>
/// Call this to initialize CEF.
/// </summary>
public void Initialize()
{
DebugTools.Assert(!_initialized);
string subProcessName;
if (OperatingSystem.IsWindows())
subProcessName = "Robust.Client.CEF.exe";
else if (OperatingSystem.IsLinux())
subProcessName = "Robust.Client.CEF";
else
throw new NotSupportedException("Unsupported platform for CEF!");
var subProcessPath = PathHelpers.ExecutableRelativeFile(subProcessName);
var settings = new CefSettings()
{
WindowlessRenderingEnabled = true, // So we can render to our UI controls.
ExternalMessagePump = false, // Unsure, honestly. TODO CEF: Research this?
NoSandbox = true, // Not disabling the sandbox crashes CEF.
BrowserSubprocessPath = subProcessPath,
LocalesDirPath = Path.Combine(PathHelpers.GetExecutableDirectory(), "locales"),
ResourcesDirPath = PathHelpers.GetExecutableDirectory(),
RemoteDebuggingPort = 9222
};
Logger.Info($"CEF Version: {CefRuntime.ChromeVersion}");
// --------------------------- README --------------------------------------------------
// By the way! You're gonna need the CEF binaries in your client's bin folder.
// More specifically, version cef_binary_91.1.21+g9dd45fe+chromium-91.0.4472.114
// https://cef-builds.spotifycdn.com/cef_binary_91.1.21%2Bg9dd45fe%2Bchromium-91.0.4472.114_windows64_minimal.tar.bz2
// https://cef-builds.spotifycdn.com/cef_binary_91.1.21%2Bg9dd45fe%2Bchromium-91.0.4472.114_linux64_minimal.tar.bz2
// Here's how to get it to work:
// 1. Copy all the contents of "Release" to the bin folder.
// 2. Copy all the contents of "Resources" to the bin folder.
// Supposedly, you should just need libcef.so in Release and icudtl.dat in Resources...
// The rest might be optional.
// Maybe. Good luck! If you get odd crashes with no info and a weird exit code, use GDB!
// -------------------------------------------------------------------------------------
_app = new RobustCefApp();
// We pass no main arguments...
CefRuntime.Initialize(new CefMainArgs(null), settings, _app, IntPtr.Zero);
// TODO CEF: After this point, debugging breaks. No, literally. My client crashes but ONLY with the debugger.
// I have tried using the DEBUG and RELEASE versions of libcef.so, stripped or non-stripped...
// And nothing seemed to work. Odd.
_initialized = true;
}
public void CheckInitialized()
{
if (!_initialized)
throw new InvalidOperationException("CefManager has not been initialized!");
}
/// <summary>
/// Needs to be called regularly for CEF to keep working.
/// </summary>
public void Update()
{
DebugTools.Assert(_initialized);
// Calling this makes CEF do its work, without using its own update loop.
CefRuntime.DoMessageLoopWork();
}
/// <summary>
/// Call before program shutdown.
/// </summary>
public void Shutdown()
{
DebugTools.Assert(_initialized);
CefRuntime.Shutdown();
}
}
}

View File

@@ -1,9 +0,0 @@
using System;
namespace Robust.Client.CEF
{
public interface IBrowserWindow : IBrowserControl, IDisposable
{
bool Closed { get; }
}
}

View File

@@ -1,33 +0,0 @@
using System;
using Xilium.CefGlue;
namespace Robust.Client.CEF
{
public 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)
{
// This is a workaround for this to work on UNIX.
var argv = args;
if (CefRuntime.Platform != CefRuntimePlatform.Windows)
{
argv = new string[args.Length + 1];
Array.Copy(args, 0, argv, 1, args.Length);
argv[0] = "-";
}
var mainArgs = new CefMainArgs(argv);
// This will block executing until the subprocess is shut down.
var code = CefRuntime.ExecuteProcess(mainArgs, null, IntPtr.Zero);
if (code != 0)
{
System.Console.WriteLine($"CEF Subprocess exited unsuccessfully with exit code {code}! Arguments: {string.Join(' ', argv)}");
}
return code;
}
}
}

View File

@@ -1,25 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\MSBuild\Robust.Properties.targets" />
<Import Project="..\MSBuild\Robust.Engine.props" />
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<PlatformTarget>x64</PlatformTarget>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<OutputType>WinExe</OutputType>
</PropertyGroup>
<Import Project="..\MSBuild\Robust.DefineConstants.targets" />
<Target Name="RobustAfterBuild" AfterTargets="Build" />
<Import Project="..\MSBuild\Robust.Engine.targets" />
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2020.3.0" />
<PackageReference Include="System.Drawing.Common" Version="5.0.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\cefglue\CefGlue\CefGlue.csproj" />
<ProjectReference Include="..\Robust.Client\Robust.Client.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,19 @@
using System;
using Microsoft.CodeAnalysis;
namespace Robust.Client.NameGenerator
{
public class InvalidXamlRootTypeException : Exception
{
public readonly INamedTypeSymbol ExpectedType;
public readonly INamedTypeSymbol ExpectedBaseType;
public readonly INamedTypeSymbol Actual;
public InvalidXamlRootTypeException(INamedTypeSymbol actual, INamedTypeSymbol expectedType, INamedTypeSymbol expectedBaseType)
{
Actual = actual;
ExpectedType = expectedType;
ExpectedBaseType = expectedBaseType;
}
}
}

View File

@@ -13,5 +13,6 @@
<Compile Link="XamlX\filename" Include="../XamlX/src/XamlX/**/*.cs" />
<Compile Remove="../XamlX/src/XamlX/**/SreTypeSystem.cs" />
<Compile Remove="../XamlX/src/XamlX/obj/**" />
<Compile Include="..\Robust.Client\UserInterface\ControlPropertyAccess.cs" />
</ItemGroup>
</Project>

View File

@@ -6,6 +6,7 @@ using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
using Robust.Client.UserInterface;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
@@ -37,9 +38,10 @@ namespace Robust.Client.AutoGenerated
class NameVisitor : IXamlAstVisitor
{
private List<(string name, string type)> _names = new List<(string name, string type)>();
private readonly List<(string name, string type, AccessLevel access)> _names =
new List<(string name, string type, AccessLevel access)>();
public static List<(string name, string type)> GetNames(IXamlAstNode node)
public static List<(string name, string type, AccessLevel access)> GetNames(IXamlAstNode node)
{
var visitor = new NameVisitor();
node.Visit(visitor);
@@ -56,27 +58,42 @@ namespace Robust.Client.AutoGenerated
{
var clrtype = objectNode.Type.GetClrType();
var isControl = IsControl(clrtype);
//clrtype.Interfaces.Any(i =>
//i.IsInterface && i.FullName == "Robust.Client.UserInterface.IControl");
if (!isControl)
return node;
// Find Name and Access properties
XamlAstTextNode nameText = null;
XamlAstTextNode accessText = null;
foreach (var child in objectNode.Children)
{
if (child is XamlAstXamlPropertyValueNode propertyValueNode &&
propertyValueNode.Property is XamlAstNamePropertyReference namedProperty &&
namedProperty.Name == "Name" &&
propertyValueNode.Values.Count > 0 &&
propertyValueNode.Values[0] is XamlAstTextNode text)
{
var reg = (text.Text, $@"{clrtype.Namespace}.{clrtype.Name}");
if (!_names.Contains(reg))
switch (namedProperty.Name)
{
_names.Add(reg);
case "Name":
nameText = text;
break;
case "Access":
accessText = text;
break;
}
}
}
if (nameText == null)
return node;
var reg = (nameText.Text,
$@"{clrtype.Namespace}.{clrtype.Name}",
accessText != null ? (AccessLevel) Enum.Parse(typeof(AccessLevel), accessText.Text) : AccessLevel.Protected);
if (!_names.Contains(reg))
{
_names.Add(reg);
}
}
return node;
@@ -94,7 +111,8 @@ namespace Robust.Client.AutoGenerated
private static string GenerateSourceCode(
INamedTypeSymbol classSymbol,
string xamlFile,
CSharpCompilation comp)
CSharpCompilation comp,
string fileName)
{
var className = classSymbol.Name;
var nameSpace = classSymbol.ContainingNamespace.ToDisplayString();
@@ -112,11 +130,48 @@ namespace Robust.Client.AutoGenerated
compiler.Transform(parsed);
var initialRoot = (XamlAstObjectNode) parsed.Root;
var names = NameVisitor.GetNames(initialRoot);
var fieldAccess = classSymbol.IsSealed ? "private" : "protected";
//var names = NameVisitor.GetNames((XamlAstObjectNode)XDocumentXamlParser.Parse(xamlFile).Root);
var namedControls = names.Select(info => " " +
$"{fieldAccess} global::{info.type} {info.name} => " +
$"this.FindControl<global::{info.type}>(\"{info.name}\");");
var rootType = (INamedTypeSymbol)initialRoot.Type.GetClrType().Id;
var rootTypeString = rootType.ToString();
if (classSymbol.ToString() != rootTypeString && classSymbol.BaseType?.ToString() != rootTypeString)
throw new InvalidXamlRootTypeException(rootType, classSymbol, classSymbol.BaseType);
var namedControls = names.Select(info =>
{
(string name, string type, AccessLevel access) = info;
string accessStr;
switch (access)
{
case AccessLevel.Public:
accessStr = "public";
break;
case AccessLevel.Protected when classSymbol.IsSealed:
case AccessLevel.PrivateProtected when classSymbol.IsSealed:
case AccessLevel.Private:
accessStr = "private";
break;
case AccessLevel.Protected:
accessStr = "protected";
break;
case AccessLevel.PrivateProtected:
accessStr = "private protected";
break;
case AccessLevel.Internal:
case AccessLevel.ProtectedInternal when classSymbol.IsSealed:
accessStr = "internal";
break;
case AccessLevel.ProtectedInternal:
accessStr = "protected internal";
break;
default:
throw new ArgumentException($"Invalid access level \"{Enum.GetName(typeof(AccessLevel), access)}\" " +
$"for control {name} in file {fileName}.");
}
return $" {accessStr} global::{type} {name} => this.FindControl<global::{type}>(\"{name}\");";
});
return $@"// <auto-generated />
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
@@ -130,7 +185,6 @@ namespace {nameSpace}
";
}
public void Execute(GeneratorExecutionContext context)
{
var comp = (CSharpCompilation) context.Compilation;
@@ -201,9 +255,24 @@ namespace {nameSpace}
try
{
var sourceCode = GenerateSourceCode(typeSymbol, txt, comp);
var sourceCode = GenerateSourceCode(typeSymbol, txt, comp, xamlFileName);
context.AddSource($"{typeSymbol.Name}.g.cs", SourceText.From(sourceCode, Encoding.UTF8));
}
catch (InvalidXamlRootTypeException invRootType)
{
context.ReportDiagnostic(
Diagnostic.Create(
new DiagnosticDescriptor(
"RXN0005",
$"XAML-File {xamlFileName} has the wrong root type!",
$"{xamlFileName}: Expected root type '{invRootType.ExpectedType}' or '{invRootType.ExpectedBaseType}', but got '{invRootType.Actual}'.",
"Usage",
DiagnosticSeverity.Error,
true),
Location.Create(xamlFileName, new TextSpan(0, 0),
new LinePositionSpan(new LinePosition(0, 0), new LinePosition(0, 0)))));
continue;
}
catch (Exception e)
{
context.ReportDiagnostic(
@@ -257,7 +326,7 @@ namespace {nameSpace}
context.ReportDiagnostic(
Diagnostic.Create(
new DiagnosticDescriptor(
"RXN0004",
"RXN0006",
missingPartialKeywordMessage,
missingPartialKeywordMessage,
"Usage",

View File

@@ -1,4 +1,4 @@
namespace Robust.Client.CEF
namespace Robust.Client.WebView
{
public sealed class BrowserWindowCreateParameters
{

View File

@@ -1,8 +1,8 @@
using Xilium.CefGlue;
namespace Robust.Client.CEF
namespace Robust.Client.WebView.Cef
{
public sealed class BeforeBrowseContext
internal sealed class CefBeforeBrowseContext : IBeforeBrowseContext
{
internal readonly CefRequest CefRequest;
@@ -14,7 +14,7 @@ namespace Robust.Client.CEF
public bool IsCancelled { get; private set; }
internal BeforeBrowseContext(
internal CefBeforeBrowseContext(
bool isRedirect,
bool userGesture,
CefRequest cefRequest)

View File

@@ -1,7 +1,7 @@

using System.Diagnostics.CodeAnalysis;
namespace Robust.Client.CEF
namespace Robust.Client.WebView.Cef
{
[SuppressMessage("ReSharper", "InconsistentNaming")]
[SuppressMessage("ReSharper", "UnusedMember.Global")]

View File

@@ -3,9 +3,9 @@ using System.IO;
using System.Net;
using Xilium.CefGlue;
namespace Robust.Client.CEF
namespace Robust.Client.WebView.Cef
{
public sealed class RequestHandlerContext
internal sealed class CefRequestHandlerContext : IRequestHandlerContext
{
internal readonly CefRequest CefRequest;
@@ -22,7 +22,7 @@ namespace Robust.Client.CEF
internal IRequestResult? Result { get; private set; }
internal RequestHandlerContext(
internal CefRequestHandlerContext(
bool isNavigation,
bool isDownload,
string requestInitiator,

View File

@@ -1,22 +1,14 @@
using System;
using Robust.Client.UserInterface;
using Robust.Client.Utility;
using Robust.Shared.Maths;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using Xilium.CefGlue;
namespace Robust.Client.CEF
namespace Robust.Client.WebView.Cef
{
internal sealed class ImageBuffer
{
private readonly Control _control;
public ImageBuffer(Control control)
{
_control = control;
}
public Image<Bgra32> Buffer { get; private set; } = new(1, 1);
public unsafe void UpdateBuffer(int width, int height, IntPtr buffer, CefRectangle dirtyRect)

View File

@@ -0,0 +1,88 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using Robust.Shared.ContentPack;
using Xilium.CefGlue;
namespace Robust.Client.WebView.Cef
{
public 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)
{
// This is a workaround for this to work on UNIX.
var argv = args;
if (CefRuntime.Platform != CefRuntimePlatform.Windows)
{
argv = new string[args.Length + 1];
Array.Copy(args, 0, argv, 1, args.Length);
argv[0] = "-";
}
/*
if (OperatingSystem.IsLinux())
{
// Chromium tries to load libEGL.so and libGLESv2.so relative to the process executable on Linux.
// (Compared to Windows where it is relative to Chromium's *module*)
// There is a TODO "is this correct?" in the Chromium code for this.
// Great.
//CopyDllToExecutableDir("libEGL.so");
//CopyDllToExecutableDir("libGLESv2.so");
// System.Threading.Thread.Sleep(200000);
}
*/
var mainArgs = new CefMainArgs(argv);
// This will block executing until the subprocess is shut down.
var code = CefRuntime.ExecuteProcess(mainArgs, null, IntPtr.Zero);
if (code != 0)
{
System.Console.WriteLine($"CEF Subprocess exited unsuccessfully with exit code {code}! Arguments: {string.Join(' ', argv)}");
}
return code;
}
/* private static void CopyDllToExecutableDir(string dllName)
{
var executableDir = PathHelpers.GetExecutableDirectory();
var targetPath = Path.Combine(executableDir, dllName);
if (File.Exists(targetPath))
return;
// Find source file.
string? srcFile = null;
foreach (var searchDir in WebViewManagerCef.NativeDllSearchDirectories())
{
var searchPath = Path.Combine(searchDir, dllName);
if (File.Exists(searchPath))
{
srcFile = searchPath;
break;
}
}
if (srcFile == null)
return;
for (var i = 0; i < 5; i++)
{
try
{
if (File.Exists(targetPath))
return;
File.Copy(srcFile, targetPath);
return;
}
catch
{
// Catching race condition lock errors and stuff I guess.
}
}
} */
}
}

View File

@@ -3,7 +3,7 @@ using System.IO;
using System.Net;
using Xilium.CefGlue;
namespace Robust.Client.CEF
namespace Robust.Client.WebView.Cef
{
internal interface IRequestResult
{
@@ -88,6 +88,13 @@ namespace Robust.Client.CEF
protected override void Cancel()
{
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
_stream.Dispose();
}
}
}
}

View File

@@ -3,7 +3,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Log;
using Xilium.CefGlue;
namespace Robust.Client.CEF
namespace Robust.Client.WebView.Cef
{
internal class RobustCefApp : CefApp
{
@@ -24,6 +24,13 @@ namespace Robust.Client.CEF
{
// Disable zygote on Linux.
commandLine.AppendSwitch("--no-zygote");
// Work around https://bitbucket.org/chromiumembedded/cef/issues/3213/ozone-egl-initialization-does-not-have
// Desktop GL force makes Chromium not try to load its own ANGLE/Swiftshader so load paths aren't problematic.
if (OperatingSystem.IsLinux())
commandLine.AppendSwitch("--use-gl", "desktop");
// commandLine.AppendSwitch("--single-process");
//commandLine.AppendSwitch("--disable-gpu");
//commandLine.AppendSwitch("--disable-gpu-compositing");

View File

@@ -1,6 +1,6 @@
using Xilium.CefGlue;
namespace Robust.Client.CEF
namespace Robust.Client.WebView.Cef
{
// Simple CEF client.
internal class RobustCefClient : CefClient

View File

@@ -1,6 +1,6 @@
using Xilium.CefGlue;
namespace Robust.Client.CEF
namespace Robust.Client.WebView.Cef
{
public sealed class RobustLoadHandler : CefLoadHandler
{

View File

@@ -3,20 +3,20 @@ using System.Collections.Generic;
using Robust.Shared.Log;
using Xilium.CefGlue;
namespace Robust.Client.CEF
namespace Robust.Client.WebView.Cef
{
internal sealed class RobustRequestHandler : CefRequestHandler
{
private readonly ISawmill _sawmill;
private readonly List<Action<RequestHandlerContext>> _resourceRequestHandlers = new();
private readonly List<Action<BeforeBrowseContext>> _beforeBrowseHandlers = new();
private readonly List<Action<IRequestHandlerContext>> _resourceRequestHandlers = new();
private readonly List<Action<IBeforeBrowseContext>> _beforeBrowseHandlers = new();
public RobustRequestHandler(ISawmill sawmill)
{
_sawmill = sawmill;
}
public void AddResourceRequestHandler(Action<RequestHandlerContext> handler)
public void AddResourceRequestHandler(Action<IRequestHandlerContext> handler)
{
lock (_resourceRequestHandlers)
{
@@ -24,7 +24,7 @@ namespace Robust.Client.CEF
}
}
public void RemoveResourceRequestHandler(Action<RequestHandlerContext> handler)
public void RemoveResourceRequestHandler(Action<IRequestHandlerContext> handler)
{
lock (_resourceRequestHandlers)
{
@@ -32,7 +32,7 @@ namespace Robust.Client.CEF
}
}
public void AddBeforeBrowseHandler(Action<BeforeBrowseContext> handler)
public void AddBeforeBrowseHandler(Action<IBeforeBrowseContext> handler)
{
lock (_beforeBrowseHandlers)
{
@@ -40,7 +40,7 @@ namespace Robust.Client.CEF
}
}
public void RemoveBeforeBrowseHandler(Action<BeforeBrowseContext> handler)
public void RemoveBeforeBrowseHandler(Action<IBeforeBrowseContext> handler)
{
lock (_beforeBrowseHandlers)
{
@@ -61,7 +61,7 @@ namespace Robust.Client.CEF
{
_sawmill.Debug($"HANDLING REQUEST: {request.Url}");
var context = new RequestHandlerContext(isNavigation, isDownload, requestInitiator, request);
var context = new CefRequestHandlerContext(isNavigation, isDownload, requestInitiator, request);
foreach (var handler in _resourceRequestHandlers)
{
@@ -85,7 +85,7 @@ namespace Robust.Client.CEF
{
lock (_beforeBrowseHandlers)
{
var context = new BeforeBrowseContext(isRedirect, userGesture, request);
var context = new CefBeforeBrowseContext(isRedirect, userGesture, request);
foreach (var handler in _beforeBrowseHandlers)
{

View File

@@ -6,26 +6,25 @@ using Robust.Shared.Log;
using Robust.Shared.ViewVariables;
using Xilium.CefGlue;
namespace Robust.Client.CEF
namespace Robust.Client.WebView.Cef
{
public partial class CefManager
internal partial class WebViewManagerCef
{
[Dependency] private readonly IClydeInternal _clyde = default!;
private readonly List<BrowserWindowImpl> _browserWindows = new();
private readonly List<WebViewWindowImpl> _browserWindows = new();
public IEnumerable<IBrowserWindow> AllBrowserWindows => _browserWindows;
public IEnumerable<IWebViewWindow> AllBrowserWindows => _browserWindows;
public IBrowserWindow CreateBrowserWindow(BrowserWindowCreateParameters createParams)
public IWebViewWindow CreateBrowserWindow(BrowserWindowCreateParameters createParams)
{
var mainHWnd = (_clyde.MainWindow as IClydeWindowInternal)?.WindowsHWnd ?? 0;
var info = CefWindowInfo.Create();
info.Width = createParams.Width;
info.Height = createParams.Height;
info.Bounds = new CefRectangle(0, 0, createParams.Width, createParams.Height);
info.SetAsPopup(mainHWnd, "ss14cef");
var impl = new BrowserWindowImpl(this);
var impl = new WebViewWindowImpl(this);
var lifeSpanHandler = new WindowLifeSpanHandler(impl);
var reqHandler = new RobustRequestHandler(Logger.GetSawmill("root"));
@@ -40,13 +39,13 @@ namespace Robust.Client.CEF
return impl;
}
private sealed class BrowserWindowImpl : IBrowserWindow
private sealed class WebViewWindowImpl : IWebViewWindow
{
private readonly CefManager _manager;
private readonly WebViewManagerCef _manager;
internal CefBrowser Browser = default!;
internal RobustRequestHandler RequestHandler = default!;
public Action<RequestHandlerContext>? OnResourceRequest { get; set; }
public Action<CefRequestHandlerContext>? OnResourceRequest { get; set; }
[ViewVariables(VVAccess.ReadWrite)]
public string Url
@@ -73,7 +72,7 @@ namespace Robust.Client.CEF
}
}
public BrowserWindowImpl(CefManager manager)
public WebViewWindowImpl(WebViewManagerCef manager)
{
_manager = manager;
}
@@ -116,12 +115,12 @@ namespace Robust.Client.CEF
Browser.GetMainFrame().ExecuteJavaScript(code, string.Empty, 1);
}
public void AddResourceRequestHandler(Action<RequestHandlerContext> handler)
public void AddResourceRequestHandler(Action<IRequestHandlerContext> handler)
{
RequestHandler.AddResourceRequestHandler(handler);
}
public void RemoveResourceRequestHandler(Action<RequestHandlerContext> handler)
public void RemoveResourceRequestHandler(Action<IRequestHandlerContext> handler)
{
RequestHandler.RemoveResourceRequestHandler(handler);
}
@@ -168,9 +167,9 @@ namespace Robust.Client.CEF
private sealed class WindowLifeSpanHandler : CefLifeSpanHandler
{
private readonly BrowserWindowImpl _windowImpl;
private readonly WebViewWindowImpl _windowImpl;
public WindowLifeSpanHandler(BrowserWindowImpl windowImpl)
public WindowLifeSpanHandler(WebViewWindowImpl windowImpl)
{
_windowImpl = windowImpl;
}

View File

@@ -0,0 +1,579 @@
using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.UserInterface;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using SixLabors.ImageSharp.PixelFormats;
using Xilium.CefGlue;
using static Robust.Client.WebView.Cef.CefKeyCodes.ChromiumKeyboardCode;
using static Robust.Client.Input.Keyboard;
namespace Robust.Client.WebView.Cef
{
internal partial class WebViewManagerCef
{
public IWebViewControlImpl MakeControlImpl(WebViewControl owner)
{
var impl = new ControlImpl(owner);
_dependencyCollection.InjectDependencies(impl);
return impl;
}
private sealed class ControlImpl : IWebViewControlImpl
{
private static readonly Dictionary<Key, CefKeyCodes.ChromiumKeyboardCode> KeyMap = new()
{
[Key.A] = VKEY_A,
[Key.B] = VKEY_B,
[Key.C] = VKEY_C,
[Key.D] = VKEY_D,
[Key.E] = VKEY_E,
[Key.F] = VKEY_F,
[Key.G] = VKEY_G,
[Key.H] = VKEY_H,
[Key.I] = VKEY_I,
[Key.J] = VKEY_J,
[Key.K] = VKEY_K,
[Key.L] = VKEY_L,
[Key.M] = VKEY_M,
[Key.N] = VKEY_N,
[Key.O] = VKEY_O,
[Key.P] = VKEY_P,
[Key.Q] = VKEY_Q,
[Key.R] = VKEY_R,
[Key.S] = VKEY_S,
[Key.T] = VKEY_T,
[Key.U] = VKEY_U,
[Key.V] = VKEY_V,
[Key.W] = VKEY_W,
[Key.X] = VKEY_X,
[Key.Y] = VKEY_Y,
[Key.Z] = VKEY_Z,
[Key.Num0] = VKEY_0,
[Key.Num1] = VKEY_1,
[Key.Num2] = VKEY_2,
[Key.Num3] = VKEY_3,
[Key.Num4] = VKEY_4,
[Key.Num5] = VKEY_5,
[Key.Num6] = VKEY_6,
[Key.Num7] = VKEY_7,
[Key.Num8] = VKEY_8,
[Key.Num9] = VKEY_9,
[Key.NumpadNum0] = VKEY_NUMPAD0,
[Key.NumpadNum1] = VKEY_NUMPAD1,
[Key.NumpadNum2] = VKEY_NUMPAD2,
[Key.NumpadNum3] = VKEY_NUMPAD3,
[Key.NumpadNum4] = VKEY_NUMPAD4,
[Key.NumpadNum5] = VKEY_NUMPAD5,
[Key.NumpadNum6] = VKEY_NUMPAD6,
[Key.NumpadNum7] = VKEY_NUMPAD7,
[Key.NumpadNum8] = VKEY_NUMPAD8,
[Key.NumpadNum9] = VKEY_NUMPAD9,
[Key.Escape] = VKEY_ESCAPE,
[Key.Control] = VKEY_CONTROL,
[Key.Shift] = VKEY_SHIFT,
[Key.Alt] = VKEY_MENU,
[Key.LSystem] = VKEY_LWIN,
[Key.RSystem] = VKEY_RWIN,
[Key.LBracket] = VKEY_OEM_4,
[Key.RBracket] = VKEY_OEM_6,
[Key.SemiColon] = VKEY_OEM_1,
[Key.Comma] = VKEY_OEM_COMMA,
[Key.Period] = VKEY_OEM_PERIOD,
[Key.Apostrophe] = VKEY_OEM_7,
[Key.Slash] = VKEY_OEM_2,
[Key.BackSlash] = VKEY_OEM_5,
[Key.Tilde] = VKEY_OEM_3,
[Key.Equal] = VKEY_OEM_PLUS,
[Key.Space] = VKEY_SPACE,
[Key.Return] = VKEY_RETURN,
[Key.BackSpace] = VKEY_BACK,
[Key.Tab] = VKEY_TAB,
[Key.PageUp] = VKEY_PRIOR,
[Key.PageDown] = VKEY_NEXT,
[Key.End] = VKEY_END,
[Key.Home] = VKEY_HOME,
[Key.Insert] = VKEY_INSERT,
[Key.Delete] = VKEY_DELETE,
[Key.Minus] = VKEY_OEM_MINUS,
[Key.NumpadAdd] = VKEY_ADD,
[Key.NumpadSubtract] = VKEY_SUBTRACT,
[Key.NumpadDivide] = VKEY_DIVIDE,
[Key.NumpadMultiply] = VKEY_MULTIPLY,
[Key.NumpadDecimal] = VKEY_DECIMAL,
[Key.Left] = VKEY_LEFT,
[Key.Right] = VKEY_RIGHT,
[Key.Up] = VKEY_UP,
[Key.Down] = VKEY_DOWN,
[Key.F1] = VKEY_F1,
[Key.F2] = VKEY_F2,
[Key.F3] = VKEY_F3,
[Key.F4] = VKEY_F4,
[Key.F5] = VKEY_F5,
[Key.F6] = VKEY_F6,
[Key.F7] = VKEY_F7,
[Key.F8] = VKEY_F8,
[Key.F9] = VKEY_F9,
[Key.F10] = VKEY_F10,
[Key.F11] = VKEY_F11,
[Key.F12] = VKEY_F12,
[Key.F13] = VKEY_F13,
[Key.F14] = VKEY_F14,
[Key.F15] = VKEY_F15,
[Key.Pause] = VKEY_PAUSE,
};
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly IInputManager _inputMgr = default!;
public readonly WebViewControl Owner;
public ControlImpl(WebViewControl owner)
{
Owner = owner;
}
private const int ScrollSpeed = 50;
private readonly RobustRequestHandler _requestHandler = new(Logger.GetSawmill("root"));
private LiveData? _data;
private string _startUrl = "about:blank";
public string Url
{
get => _data == null ? _startUrl : _data.Browser.GetMainFrame().Url;
set
{
if (_data == null)
_startUrl = value;
else
_data.Browser.GetMainFrame().LoadUrl(value);
}
}
public bool IsLoading => _data?.Browser.IsLoading ?? false;
public void EnteredTree()
{
DebugTools.AssertNull(_data);
// A funny render handler that will allow us to render to the control.
var renderer = new ControlRenderHandler(this);
// A funny web cef client. This can actually be shared by multiple browsers, but I'm not sure how the
// rendering would work in that case? TODO CEF: Investigate a way to share the web client?
var client = new RobustCefClient(renderer, _requestHandler, new RobustLoadHandler());
var info = CefWindowInfo.Create();
// FUNFACT: If you DO NOT set these below and set info.Width/info.Height instead, you get an external window
// Good to know, huh? Setup is the same, except you can pass a dummy render handler to the CEF client.
info.SetAsWindowless(IntPtr.Zero, false); // TODO CEF: Pass parent handle?
info.WindowlessRenderingEnabled = true;
var settings = new CefBrowserSettings()
{
WindowlessFrameRate = 60
};
// Create the web browser! And by default, we go to about:blank.
var browser = CefBrowserHost.CreateBrowserSync(info, client, settings, _startUrl);
var texture = _clyde.CreateBlankTexture<Bgra32>(Vector2i.One);
_data = new LiveData(texture, client, browser, renderer);
}
public void ExitedTree()
{
DebugTools.AssertNotNull(_data);
_data!.Texture.Dispose();
_data.Browser.GetHost().CloseBrowser(true);
_data = null;
}
public void MouseMove(GUIMouseMoveEventArgs args)
{
if (_data == null)
return;
// Logger.Debug();
var modifiers = CalcMouseModifiers();
var mouseEvent = new CefMouseEvent(
(int)args.RelativePosition.X, (int)args.RelativePosition.Y,
modifiers);
_data.Browser.GetHost().SendMouseMoveEvent(mouseEvent, false);
}
public void MouseExited()
{
if (_data == null)
return;
var modifiers = CalcMouseModifiers();
_data.Browser.GetHost().SendMouseMoveEvent(new CefMouseEvent(0, 0, modifiers), true);
}
public void MouseWheel(GUIMouseWheelEventArgs args)
{
if (_data == null)
return;
var modifiers = CalcMouseModifiers();
var mouseEvent = new CefMouseEvent(
(int)args.RelativePosition.X, (int)args.RelativePosition.Y,
modifiers);
_data.Browser.GetHost().SendMouseWheelEvent(
mouseEvent,
(int)args.Delta.X * ScrollSpeed,
(int)args.Delta.Y * ScrollSpeed);
}
public bool RawKeyEvent(in GuiRawKeyEvent guiRawEvent)
{
if (_data == null)
return false;
var host = _data.Browser.GetHost();
if (guiRawEvent.Key is Key.MouseLeft or Key.MouseMiddle or Key.MouseRight)
{
var key = guiRawEvent.Key switch
{
Key.MouseLeft => CefMouseButtonType.Left,
Key.MouseMiddle => CefMouseButtonType.Middle,
Key.MouseRight => CefMouseButtonType.Right,
_ => default // not possible
};
var mouseEvent = new CefMouseEvent(
guiRawEvent.MouseRelative.X, guiRawEvent.MouseRelative.Y,
CefEventFlags.None);
// Logger.Debug($"MOUSE: {guiRawEvent.Action} {guiRawEvent.Key} {guiRawEvent.ScanCode} {key}");
// TODO: double click support?
host.SendMouseClickEvent(mouseEvent, key, guiRawEvent.Action == RawKeyAction.Up, 1);
}
else
{
// TODO: Handle left/right modifier keys??
if (!KeyMap.TryGetValue(guiRawEvent.Key, out var vkKey))
vkKey = default;
// Logger.Debug($"{guiRawEvent.Action} {guiRawEvent.Key} {guiRawEvent.ScanCode} {vkKey}");
var lParam = 0;
lParam |= (guiRawEvent.ScanCode & 0xFF) << 16;
if (guiRawEvent.Action != RawKeyAction.Down)
lParam |= 1 << 30;
if (guiRawEvent.Action == RawKeyAction.Up)
lParam |= 1 << 31;
var modifiers = CalcModifiers(guiRawEvent.Key);
host.SendKeyEvent(new CefKeyEvent
{
// Repeats are sent as key downs, I guess?
EventType = guiRawEvent.Action == RawKeyAction.Up
? CefKeyEventType.KeyUp
: CefKeyEventType.RawKeyDown,
NativeKeyCode = lParam,
// NativeKeyCode = guiRawEvent.ScanCode,
WindowsKeyCode = (int)vkKey,
IsSystemKey = false, // TODO
Modifiers = modifiers
});
if (guiRawEvent.Action != RawKeyAction.Up && guiRawEvent.Key == Key.Return)
{
host.SendKeyEvent(new CefKeyEvent
{
EventType = CefKeyEventType.Char,
WindowsKeyCode = '\r',
NativeKeyCode = lParam,
Modifiers = modifiers
});
}
}
return true;
}
private CefEventFlags CalcModifiers(Key key)
{
CefEventFlags modifiers = default;
if (_inputMgr.IsKeyDown(Key.Control))
modifiers |= CefEventFlags.ControlDown;
if (_inputMgr.IsKeyDown(Key.Alt))
modifiers |= CefEventFlags.AltDown;
if (_inputMgr.IsKeyDown(Key.Shift))
modifiers |= CefEventFlags.ShiftDown;
if (_inputMgr.IsKeyDown(Key.Shift))
modifiers |= CefEventFlags.ShiftDown;
return modifiers;
}
private CefEventFlags CalcMouseModifiers()
{
CefEventFlags modifiers = default;
if (_inputMgr.IsKeyDown(Key.Control))
modifiers |= CefEventFlags.ControlDown;
if (_inputMgr.IsKeyDown(Key.Alt))
modifiers |= CefEventFlags.AltDown;
if (_inputMgr.IsKeyDown(Key.Shift))
modifiers |= CefEventFlags.ShiftDown;
if (_inputMgr.IsKeyDown(Key.Shift))
modifiers |= CefEventFlags.ShiftDown;
if (_inputMgr.IsKeyDown(Key.MouseLeft))
modifiers |= CefEventFlags.LeftMouseButton;
if (_inputMgr.IsKeyDown(Key.MouseMiddle))
modifiers |= CefEventFlags.MiddleMouseButton;
if (_inputMgr.IsKeyDown(Key.MouseRight))
modifiers |= CefEventFlags.RightMouseButton;
return modifiers;
}
public void TextEntered(GUITextEventArgs args)
{
if (_data == null)
return;
var host = _data.Browser.GetHost();
Span<char> buf = stackalloc char[2];
var written = args.AsRune.EncodeToUtf16(buf);
for (var i = 0; i < written; i++)
{
host.SendKeyEvent(new CefKeyEvent
{
EventType = CefKeyEventType.Char,
WindowsKeyCode = buf[i],
Character = buf[i],
UnmodifiedCharacter = buf[i]
});
}
}
public void Resized()
{
if (_data == null)
return;
_data.Browser.GetHost().NotifyMoveOrResizeStarted();
_data.Browser.GetHost().WasResized();
_data.Texture.Dispose();
_data.Texture = _clyde.CreateBlankTexture<Bgra32>((Owner.PixelWidth, Owner.PixelHeight));
}
public void Draw(DrawingHandleScreen handle)
{
if (_data == null)
return;
var bufImg = _data.Renderer.Buffer.Buffer;
_data.Texture.SetSubImage(
Vector2i.Zero,
bufImg,
new UIBox2i(
0, 0,
Math.Min(Owner.PixelWidth, bufImg.Width),
Math.Min(Owner.PixelHeight, bufImg.Height)));
handle.DrawTexture(_data.Texture, Vector2.Zero);
}
public void StopLoad()
{
if (_data == null)
throw new InvalidOperationException();
_data.Browser.StopLoad();
}
public void Reload()
{
if (_data == null)
throw new InvalidOperationException();
_data.Browser.Reload();
}
public bool GoBack()
{
if (_data == null)
throw new InvalidOperationException();
if (!_data.Browser.CanGoBack)
return false;
_data.Browser.GoBack();
return true;
}
public bool GoForward()
{
if (_data == null)
throw new InvalidOperationException();
if (!_data.Browser.CanGoForward)
return false;
_data.Browser.GoForward();
return true;
}
public void ExecuteJavaScript(string code)
{
if (_data == null)
throw new InvalidOperationException();
// TODO: this should not run until the browser is done loading seriously does this even work?
_data.Browser.GetMainFrame().ExecuteJavaScript(code, string.Empty, 1);
}
public void AddResourceRequestHandler(Action<IRequestHandlerContext> handler)
{
_requestHandler.AddResourceRequestHandler(handler);
}
public void RemoveResourceRequestHandler(Action<IRequestHandlerContext> handler)
{
_requestHandler.RemoveResourceRequestHandler(handler);
}
public void AddBeforeBrowseHandler(Action<IBeforeBrowseContext> handler)
{
_requestHandler.AddBeforeBrowseHandler(handler);
}
public void RemoveBeforeBrowseHandler(Action<IBeforeBrowseContext> handler)
{
_requestHandler.RemoveBeforeBrowseHandler(handler);
}
private sealed class LiveData
{
public OwnedTexture Texture;
public readonly RobustCefClient Client;
public readonly CefBrowser Browser;
public readonly ControlRenderHandler Renderer;
public LiveData(
OwnedTexture texture,
RobustCefClient client,
CefBrowser browser,
ControlRenderHandler renderer)
{
Texture = texture;
Client = client;
Browser = browser;
Renderer = renderer;
}
}
}
private sealed class ControlRenderHandler : CefRenderHandler
{
public ImageBuffer Buffer { get; }
private ControlImpl _control;
internal ControlRenderHandler(ControlImpl control)
{
Buffer = new ImageBuffer();
_control = control;
}
protected override CefAccessibilityHandler? GetAccessibilityHandler() => null;
protected override void GetViewRect(CefBrowser browser, out CefRectangle rect)
{
if (_control.Owner.Disposed)
{
rect = new CefRectangle();
return;
}
// TODO CEF: Do we need to pass real screen coords? Cause what we do already works...
//var screenCoords = _control.ScreenCoordinates;
//rect = new CefRectangle((int) screenCoords.X, (int) screenCoords.Y, (int)Math.Max(_control.Size.X, 1), (int)Math.Max(_control.Size.Y, 1));
// We do the max between size and 1 because it will LITERALLY CRASH WITHOUT AN ERROR otherwise.
rect = new CefRectangle(
0, 0,
(int)Math.Max(_control.Owner.Size.X, 1), (int)Math.Max(_control.Owner.Size.Y, 1));
}
protected override bool GetScreenInfo(CefBrowser browser, CefScreenInfo screenInfo)
{
if (_control.Owner.Disposed)
return false;
screenInfo.DeviceScaleFactor = _control.Owner.UIScale;
return true;
}
protected override void OnPopupSize(CefBrowser browser, CefRectangle rect)
{
if (_control.Owner.Disposed)
return;
}
protected override void OnPaint(CefBrowser browser, CefPaintElementType type, CefRectangle[] dirtyRects,
IntPtr buffer, int width, int height)
{
if (_control.Owner.Disposed)
return;
foreach (var dirtyRect in dirtyRects)
{
Buffer.UpdateBuffer(width, height, buffer, dirtyRect);
}
}
protected override void OnAcceleratedPaint(CefBrowser browser, CefPaintElementType type,
CefRectangle[] dirtyRects, IntPtr sharedHandle)
{
// Unused, but we're forced to implement it so.. NOOP.
}
protected override void OnScrollOffsetChanged(CefBrowser browser, double x, double y)
{
if (_control.Owner.Disposed)
return;
}
protected override void OnImeCompositionRangeChanged(CefBrowser browser, CefRange selectedRange,
CefRectangle[] characterBounds)
{
if (_control.Owner.Disposed)
return;
}
}
}
}

View File

@@ -0,0 +1,101 @@
using System;
using System.IO;
using Robust.Shared.ContentPack;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Xilium.CefGlue;
namespace Robust.Client.WebView.Cef
{
internal partial class WebViewManagerCef : IWebViewManagerImpl
{
private CefApp _app = default!;
[Dependency] private readonly IDependencyCollection _dependencyCollection = default!;
public void Initialize()
{
IoCManager.Instance!.InjectDependencies(this, oneOff: true);
string subProcessName;
if (OperatingSystem.IsWindows())
subProcessName = "Robust.Client.WebView.exe";
else if (OperatingSystem.IsLinux())
subProcessName = "Robust.Client.WebView";
else
throw new NotSupportedException("Unsupported platform for CEF!");
var subProcessPath = PathHelpers.ExecutableRelativeFile(subProcessName);
var cefResourcesPath = LocateCefResources();
// System.Console.WriteLine(AppContext.GetData("NATIVE_DLL_SEARCH_DIRECTORIES"));
if (cefResourcesPath == null)
throw new InvalidOperationException("Unable to locate cef_resources directory!");
var settings = new CefSettings()
{
WindowlessRenderingEnabled = true, // So we can render to our UI controls.
ExternalMessagePump = false, // Unsure, honestly. TODO CEF: Research this?
NoSandbox = true, // Not disabling the sandbox crashes CEF.
BrowserSubprocessPath = subProcessPath,
LocalesDirPath = Path.Combine(cefResourcesPath, "locales"),
ResourcesDirPath = cefResourcesPath,
RemoteDebuggingPort = 9222,
};
Logger.Info($"CEF Version: {CefRuntime.ChromeVersion}");
_app = new RobustCefApp();
// We pass no main arguments...
CefRuntime.Initialize(new CefMainArgs(null), settings, _app, IntPtr.Zero);
// TODO CEF: After this point, debugging breaks. No, literally. My client crashes but ONLY with the debugger.
// I have tried using the DEBUG and RELEASE versions of libcef.so, stripped or non-stripped...
// And nothing seemed to work. Odd.
}
private static string? LocateCefResources()
{
if (ProbeDir(PathHelpers.GetExecutableDirectory(), out var path))
return path;
foreach (var searchDir in NativeDllSearchDirectories())
{
if (ProbeDir(searchDir, out path))
return path;
}
return null;
static bool ProbeDir(string dir, out string path)
{
path = Path.Combine(dir, "cef_resources");
return Directory.Exists(path);
}
}
internal static string[] NativeDllSearchDirectories()
{
var sepChar = OperatingSystem.IsWindows() ? ';' : ':';
var searchDirectories = ((string)AppContext.GetData("NATIVE_DLL_SEARCH_DIRECTORIES")!)
.Split(sepChar, StringSplitOptions.RemoveEmptyEntries);
return searchDirectories;
}
public void Update()
{
// Calling this makes CEF do its work, without using its own update loop.
CefRuntime.DoMessageLoopWork();
}
public void Shutdown()
{
CefRuntime.Shutdown();
}
}
}

View File

@@ -0,0 +1,127 @@
using System;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
namespace Robust.Client.WebView.Headless
{
internal sealed class WebViewManagerHeadless : IWebViewManagerImpl
{
public IWebViewWindow CreateBrowserWindow(BrowserWindowCreateParameters createParams)
{
return new WebViewWindowDummy();
}
public IWebViewControlImpl MakeControlImpl(WebViewControl owner)
{
return new WebViewControlImplDummy();
}
public void Initialize()
{
// Nop
}
public void Update()
{
// Nop
}
public void Shutdown()
{
// Nop
}
private abstract class DummyBase : IWebViewControl
{
public string Url { get; set; } = "about:blank";
public bool IsLoading => true;
public void StopLoad()
{
}
public void Reload()
{
}
public bool GoBack()
{
return false;
}
public bool GoForward()
{
return false;
}
public void ExecuteJavaScript(string code)
{
}
public void AddResourceRequestHandler(Action<IRequestHandlerContext> handler)
{
}
public void RemoveResourceRequestHandler(Action<IRequestHandlerContext> handler)
{
}
}
private sealed class WebViewControlImplDummy : DummyBase, IWebViewControlImpl
{
public void EnteredTree()
{
}
public void ExitedTree()
{
}
public void MouseMove(GUIMouseMoveEventArgs args)
{
}
public void MouseExited()
{
}
public void MouseWheel(GUIMouseWheelEventArgs args)
{
}
public bool RawKeyEvent(in GuiRawKeyEvent guiRawEvent)
{
return false;
}
public void TextEntered(GUITextEventArgs args)
{
}
public void Resized()
{
}
public void Draw(DrawingHandleScreen handle)
{
}
public void AddBeforeBrowseHandler(Action<IBeforeBrowseContext> handler)
{
}
public void RemoveBeforeBrowseHandler(Action<IBeforeBrowseContext> handler)
{
}
}
private sealed class WebViewWindowDummy : DummyBase, IWebViewWindow
{
public void Dispose()
{
}
public bool Closed => false;
}
}
}

View File

@@ -0,0 +1,12 @@
namespace Robust.Client.WebView
{
public interface IBeforeBrowseContext
{
string Url { get; }
string Method { get; }
bool IsRedirect { get; }
bool UserGesture { get; }
bool IsCancelled { get; }
void DoCancel();
}
}

View File

@@ -0,0 +1,18 @@
using System.IO;
using System.Net;
namespace Robust.Client.WebView
{
public interface IRequestHandlerContext
{
bool IsNavigation { get; }
bool IsDownload { get; }
string RequestInitiator { get; }
string Url { get; }
string Method { get; }
bool IsHandled { get; }
bool IsCancelled { get; }
void DoCancel();
void DoRespondStream(Stream stream, string contentType, HttpStatusCode code = HttpStatusCode.OK);
}
}

View File

@@ -1,8 +1,9 @@
using System;
using Robust.Client.WebView.Cef;
namespace Robust.Client.CEF
namespace Robust.Client.WebView
{
public interface IBrowserControl
public interface IWebViewControl
{
/// <summary>
/// Current URL of the browser. Set to load a new page.
@@ -42,7 +43,7 @@ namespace Robust.Client.CEF
/// <param name="code">JavaScript code.</param>
void ExecuteJavaScript(string code);
void AddResourceRequestHandler(Action<RequestHandlerContext> handler);
void RemoveResourceRequestHandler(Action<RequestHandlerContext> handler);
void AddResourceRequestHandler(Action<IRequestHandlerContext> handler);
void RemoveResourceRequestHandler(Action<IRequestHandlerContext> handler);
}
}

View File

@@ -0,0 +1,24 @@
using System;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
namespace Robust.Client.WebView
{
/// <summary>
/// Internal swappable implementation of <see cref="WebViewControl"/>.
/// </summary>
internal interface IWebViewControlImpl : IWebViewControl
{
void EnteredTree();
void ExitedTree();
void MouseMove(GUIMouseMoveEventArgs args);
void MouseExited();
void MouseWheel(GUIMouseWheelEventArgs args);
bool RawKeyEvent(in GuiRawKeyEvent guiRawEvent);
void TextEntered(GUITextEventArgs args);
void Resized();
void Draw(DrawingHandleScreen handle);
void AddBeforeBrowseHandler(Action<IBeforeBrowseContext> handler);
void RemoveBeforeBrowseHandler(Action<IBeforeBrowseContext> handler);
}
}

View File

@@ -0,0 +1,7 @@
namespace Robust.Client.WebView
{
public interface IWebViewManager
{
IWebViewWindow CreateBrowserWindow(BrowserWindowCreateParameters createParams);
}
}

View File

@@ -0,0 +1,14 @@
using Robust.Client.WebViewHook;
namespace Robust.Client.WebView
{
/// <summary>
/// Internal implementation of WebViewManager that is switched out by <see cref="IWebViewManagerHook"/>.
/// </summary>
internal interface IWebViewManagerImpl : IWebViewManagerInternal
{
void Initialize();
void Update();
void Shutdown();
}
}

View File

@@ -0,0 +1,7 @@
namespace Robust.Client.WebView
{
internal interface IWebViewManagerInternal : IWebViewManager
{
IWebViewControlImpl MakeControlImpl(WebViewControl owner);
}
}

View File

@@ -0,0 +1,9 @@
using System;
namespace Robust.Client.WebView
{
public interface IWebViewWindow : IWebViewControl, IDisposable
{
bool Closed { get; }
}
}

View File

@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\MSBuild\Robust.Properties.targets" />
<Import Project="..\MSBuild\Robust.Engine.props" />
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<PlatformTarget>x64</PlatformTarget>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<OutputType>WinExe</OutputType>
</PropertyGroup>
<Import Project="..\MSBuild\Robust.DefineConstants.targets" />
<Target Name="RobustAfterBuild" AfterTargets="Build" />
<Import Project="..\MSBuild\Robust.Engine.targets" />
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2020.3.0" />
<PackageReference Include="System.Drawing.Common" Version="5.0.2" />
<PackageReference Include="Robust.Natives.Cef" Version="95.7.14" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\cefglue\CefGlue\CefGlue.csproj" />
<ProjectReference Include="..\Robust.Client\Robust.Client.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,145 @@
using System;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.WebView.Cef;
using Robust.Shared.IoC;
using Robust.Shared.ViewVariables;
namespace Robust.Client.WebView
{
/// <summary>
/// An UI control that presents web content.
/// </summary>
public sealed class WebViewControl : Control, IWebViewControl, IRawInputControl
{
[Dependency] private readonly IWebViewManagerInternal _webViewManager = default!;
private readonly IWebViewControlImpl _controlImpl;
[ViewVariables(VVAccess.ReadWrite)]
public string Url
{
get => _controlImpl.Url;
set => _controlImpl.Url = value;
}
[ViewVariables] public bool IsLoading => _controlImpl.IsLoading;
public WebViewControl()
{
CanKeyboardFocus = true;
KeyboardFocusOnClick = true;
MouseFilter = MouseFilterMode.Stop;
IoCManager.InjectDependencies(this);
_controlImpl = _webViewManager.MakeControlImpl(this);
}
protected override void EnteredTree()
{
base.EnteredTree();
_controlImpl.EnteredTree();
}
protected override void ExitedTree()
{
base.ExitedTree();
_controlImpl.ExitedTree();
}
protected internal override void MouseMove(GUIMouseMoveEventArgs args)
{
base.MouseMove(args);
_controlImpl.MouseMove(args);
}
protected internal override void MouseExited()
{
base.MouseExited();
_controlImpl.MouseExited();
}
protected internal override void MouseWheel(GUIMouseWheelEventArgs args)
{
base.MouseWheel(args);
_controlImpl.MouseWheel(args);
}
bool IRawInputControl.RawKeyEvent(in GuiRawKeyEvent guiRawEvent)
{
return _controlImpl.RawKeyEvent(guiRawEvent);
}
protected internal override void TextEntered(GUITextEventArgs args)
{
base.TextEntered(args);
_controlImpl.TextEntered(args);
}
protected override void Resized()
{
base.Resized();
_controlImpl.Resized();
}
protected internal override void Draw(DrawingHandleScreen handle)
{
base.Draw(handle);
_controlImpl.Draw(handle);
}
public void StopLoad()
{
_controlImpl.StopLoad();
}
public void Reload()
{
_controlImpl.Reload();
}
public bool GoBack()
{
return _controlImpl.GoBack();
}
public bool GoForward()
{
return _controlImpl.GoForward();
}
public void ExecuteJavaScript(string code)
{
_controlImpl.ExecuteJavaScript(code);
}
public void AddResourceRequestHandler(Action<IRequestHandlerContext> handler)
{
_controlImpl.AddResourceRequestHandler(handler);
}
public void RemoveResourceRequestHandler(Action<IRequestHandlerContext> handler)
{
_controlImpl.RemoveResourceRequestHandler(handler);
}
public void AddBeforeBrowseHandler(Action<IBeforeBrowseContext> handler)
{
_controlImpl.AddBeforeBrowseHandler(handler);
}
public void RemoveBeforeBrowseHandler(Action<IBeforeBrowseContext> handler)
{
_controlImpl.RemoveBeforeBrowseHandler(handler);
}
}
}

View File

@@ -0,0 +1,59 @@
using Robust.Client.WebView;
using Robust.Client.WebView.Cef;
using Robust.Client.WebView.Headless;
using Robust.Client.WebViewHook;
using Robust.Shared.IoC;
using Robust.Shared.Utility;
[assembly: WebViewManagerImpl(typeof(WebViewManager))]
namespace Robust.Client.WebView
{
internal sealed class WebViewManager : IWebViewManagerInternal, IWebViewManagerHook
{
private IWebViewManagerImpl? _impl;
public void Initialize(GameController.DisplayMode mode)
{
DebugTools.Assert(_impl == null, "WebViewManager has already been initialized!");
IoCManager.RegisterInstance<IWebViewManager>(this);
IoCManager.RegisterInstance<IWebViewManagerInternal>(this);
if (mode == GameController.DisplayMode.Headless)
_impl = new WebViewManagerHeadless();
else
_impl = new WebViewManagerCef();
_impl.Initialize();
}
public void Update()
{
DebugTools.Assert(_impl != null, "WebViewManager has not yet been initialized!");
_impl!.Update();
}
public void Shutdown()
{
DebugTools.Assert(_impl != null, "WebViewManager has not yet been initialized!");
_impl!.Shutdown();
}
public IWebViewWindow CreateBrowserWindow(BrowserWindowCreateParameters createParams)
{
DebugTools.Assert(_impl != null, "WebViewManager has not yet been initialized!");
return _impl!.CreateBrowserWindow(createParams);
}
public IWebViewControlImpl MakeControlImpl(WebViewControl owner)
{
DebugTools.Assert(_impl != null, "WebViewManager has not yet been initialized!");
return _impl!.MakeControlImpl(owner);
}
}
}

View File

@@ -65,7 +65,7 @@ namespace Robust.Client.Audio.Midi
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IConfigurationManager _cfgMan = default!;
private SharedBroadphaseSystem _broadPhaseSystem = default!;
private SharedPhysicsSystem _broadPhaseSystem = default!;
[ViewVariables]
public bool IsAvailable
@@ -88,13 +88,14 @@ namespace Robust.Client.Audio.Midi
private float _volume = 0f;
private bool _volumeDirty = true;
// Not reliable until Fluidsynth is initialized!
[ViewVariables(VVAccess.ReadWrite)]
public float Volume
{
get => _volume;
set
{
if (MathHelper.CloseTo(_volume, value))
if (MathHelper.CloseToPercent(_volume, value))
return;
_cfgMan.SetCVar(CVars.MidiVolume, value);
@@ -134,6 +135,7 @@ namespace Robust.Client.Audio.Midi
{
if (FluidsynthInitialized || _failedInitialize) return;
_volume = _cfgMan.GetCVar(CVars.MidiVolume);
_cfgMan.OnValueChanged(CVars.MidiVolume, value =>
{
_volume = value;
@@ -175,7 +177,7 @@ namespace Robust.Client.Audio.Midi
_midiThread = new Thread(ThreadUpdate);
_midiThread.Start();
_broadPhaseSystem = EntitySystem.Get<SharedBroadphaseSystem>();
_broadPhaseSystem = EntitySystem.Get<SharedPhysicsSystem>();
FluidsynthInitialized = true;
}
@@ -254,11 +256,12 @@ namespace Robust.Client.Audio.Midi
renderer.LoadSoundfont(WindowsSoundfont, true);
}
renderer.Source.SetVolume(Volume);
lock (_renderers)
{
_renderers.Add(renderer);
}
return renderer;
}
finally

View File

@@ -37,6 +37,11 @@ namespace Robust.Client.Audio.Midi
/// </summary>
bool LoopMidi { get; set; }
/// <summary>
/// This increases all note on velocities to 127.
/// </summary>
bool VolumeBoost { get; set; }
/// <summary>
/// The midi program (instrument) the renderer is using.
/// </summary>
@@ -297,6 +302,9 @@ namespace Robust.Client.Audio.Midi
}
}
[ViewVariables(VVAccess.ReadWrite)]
public bool VolumeBoost { get; set; }
[ViewVariables(VVAccess.ReadWrite)]
public IEntity? TrackingEntity { get; set; } = null;
@@ -539,6 +547,8 @@ namespace Robust.Client.Audio.Midi
// Note On 0x90
case 144:
if (VolumeBoost)
midiEvent.Velocity = 127;
_synth.NoteOn(midiEvent.Channel, midiEvent.Key, midiEvent.Velocity);
break;

View File

@@ -31,7 +31,6 @@ namespace Robust.Client
[Dependency] private readonly IDiscordRichPresence _discord = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IClientGameStateManager _gameStates = default!;
[Dependency] private readonly IDebugDrawingManager _debugDrawMan = default!;
/// <inheritdoc />
public ushort DefaultPort { get; } = 1212;
@@ -57,7 +56,6 @@ namespace Robust.Client
_configManager.OnValueChanged(CVars.NetTickrate, TickRateChanged, invokeImmediately: true);
_playMan.Initialize();
_debugDrawMan.Initialize();
Reset();
}

View File

@@ -72,7 +72,6 @@ namespace Robust.Client
IoCManager.Register<IUserInterfaceManager, UserInterfaceManager>();
IoCManager.Register<IUserInterfaceManagerInternal, UserInterfaceManager>();
IoCManager.Register<IDebugDrawing, DebugDrawing>();
IoCManager.Register<IDebugDrawingManager, DebugDrawingManager>();
IoCManager.Register<ILightManager, LightManager>();
IoCManager.Register<IDiscordRichPresence, DiscordRichPresence>();
IoCManager.Register<IMidiManager, MidiManager>();

View File

@@ -4,177 +4,186 @@ using Robust.Shared;
using Robust.Shared.Utility;
using C = System.Console;
namespace Robust.Client
namespace Robust.Client;
internal sealed class CommandLineArgs
{
internal sealed class CommandLineArgs
public MountOptions MountOptions { get; }
public bool Headless { get; }
public bool SelfContained { get; }
public bool Connect { get; }
public string ConnectAddress { get; }
public string? Ss14Address { get; }
public bool Launcher { get; }
public string? Username { get; }
public IReadOnlyCollection<(string key, string value)> CVars { get; }
public IReadOnlyCollection<(string key, string value)> LogLevels { get; }
public IReadOnlyList<string> ExecCommands { get; set; }
// Manual parser because C# has no good command line parsing libraries. Also dependencies bad.
// Also I don't like spending 100ms parsing command line args. Do you?
public static bool TryParse(IReadOnlyList<string> args, [NotNullWhen(true)] out CommandLineArgs? parsed)
{
public MountOptions MountOptions { get; }
public bool Headless { get; }
public bool SelfContained { get; }
public bool Connect { get; }
public string ConnectAddress { get; }
public string? Ss14Address { get; }
public bool Launcher { get; }
public string? Username { get; }
public IReadOnlyCollection<(string key, string value)> CVars { get; }
public IReadOnlyCollection<(string key, string value)> LogLevels { get; }
parsed = null;
var headless = false;
var selfContained = false;
var connect = false;
var connectAddress = "localhost";
string? ss14Address = null;
var launcher = false;
string? username = null;
var cvars = new List<(string, string)>();
var logLevels = new List<(string, string)>();
var mountOptions = new MountOptions();
var execCommands = new List<string>();
// Manual parser because C# has no good command line parsing libraries. Also dependencies bad.
// Also I don't like spending 100ms parsing command line args. Do you?
public static bool TryParse(IReadOnlyList<string> args, [NotNullWhen(true)] out CommandLineArgs? parsed)
using var enumerator = args.GetEnumerator();
while (enumerator.MoveNext())
{
parsed = null;
var headless = false;
var selfContained = false;
var connect = false;
var connectAddress = "localhost";
string? ss14Address = null;
var launcher = false;
string? username = null;
var cvars = new List<(string, string)>();
var logLevels = new List<(string, string)>();
var mountOptions = new MountOptions();
using var enumerator = args.GetEnumerator();
while (enumerator.MoveNext())
var arg = enumerator.Current;
if (arg == "--connect")
{
var arg = enumerator.Current;
if (arg == "--connect")
connect = true;
}
else if (arg == "--connect-address")
{
if (!enumerator.MoveNext())
{
connect = true;
}
else if (arg == "--connect-address")
{
if (!enumerator.MoveNext())
{
C.WriteLine("Missing connection address.");
return false;
}
connectAddress = enumerator.Current;
}
else if (arg == "--ss14-address")
{
if (!enumerator.MoveNext())
{
C.WriteLine("Missing SS14 address.");
return false;
}
ss14Address = enumerator.Current;
}
else if (arg == "--self-contained")
{
selfContained = true;
}
else if (arg == "--launcher")
{
launcher = true;
}
else if (arg == "--headless")
{
headless = true;
}
else if (arg == "--username")
{
if (!enumerator.MoveNext())
{
C.WriteLine("Missing username.");
return false;
}
username = enumerator.Current;
}
else if (arg == "--cvar")
{
if (!enumerator.MoveNext())
{
C.WriteLine("Missing cvar value.");
return false;
}
var cvar = enumerator.Current;
DebugTools.AssertNotNull(cvar);
var pos = cvar.IndexOf('=');
if (pos == -1)
{
C.WriteLine("Expected = in cvar.");
return false;
}
cvars.Add((cvar[..pos], cvar[(pos + 1)..]));
}
else if (arg == "--mount-zip")
{
if (!enumerator.MoveNext())
{
C.WriteLine("Missing mount path");
return false;
}
mountOptions.ZipMounts.Add(enumerator.Current);
}
else if (arg == "--mount-dir")
{
if (!enumerator.MoveNext())
{
C.WriteLine("Missing mount path");
return false;
}
mountOptions.DirMounts.Add(enumerator.Current);
}
else if (arg == "--loglevel")
{
if (!enumerator.MoveNext())
{
C.WriteLine("Missing loglevel sawmill.");
return false;
}
var loglevel = enumerator.Current;
DebugTools.AssertNotNull(loglevel);
var pos = loglevel.IndexOf('=');
if (pos == -1)
{
C.WriteLine("Expected = in loglevel.");
return false;
}
logLevels.Add((loglevel[..pos], loglevel[(pos + 1)..]));
}
else if (arg == "--help")
{
PrintHelp();
C.WriteLine("Missing connection address.");
return false;
}
else
{
C.WriteLine("Unknown argument: {0}", arg);
}
connectAddress = enumerator.Current;
}
else if (arg == "--ss14-address")
{
if (!enumerator.MoveNext())
{
C.WriteLine("Missing SS14 address.");
return false;
}
parsed = new CommandLineArgs(
headless,
selfContained,
connect,
launcher,
username,
cvars,
logLevels,
connectAddress,
ss14Address,
mountOptions);
ss14Address = enumerator.Current;
}
else if (arg == "--self-contained")
{
selfContained = true;
}
else if (arg == "--launcher")
{
launcher = true;
}
else if (arg == "--headless")
{
headless = true;
}
else if (arg == "--username")
{
if (!enumerator.MoveNext())
{
C.WriteLine("Missing username.");
return false;
}
return true;
username = enumerator.Current;
}
else if (arg == "--cvar")
{
if (!enumerator.MoveNext())
{
C.WriteLine("Missing cvar value.");
return false;
}
var cvar = enumerator.Current;
DebugTools.AssertNotNull(cvar);
var pos = cvar.IndexOf('=');
if (pos == -1)
{
C.WriteLine("Expected = in cvar.");
return false;
}
cvars.Add((cvar[..pos], cvar[(pos + 1)..]));
}
else if (arg == "--mount-zip")
{
if (!enumerator.MoveNext())
{
C.WriteLine("Missing mount path");
return false;
}
mountOptions.ZipMounts.Add(enumerator.Current);
}
else if (arg == "--mount-dir")
{
if (!enumerator.MoveNext())
{
C.WriteLine("Missing mount path");
return false;
}
mountOptions.DirMounts.Add(enumerator.Current);
}
else if (arg == "--loglevel")
{
if (!enumerator.MoveNext())
{
C.WriteLine("Missing loglevel sawmill.");
return false;
}
var loglevel = enumerator.Current;
DebugTools.AssertNotNull(loglevel);
var pos = loglevel.IndexOf('=');
if (pos == -1)
{
C.WriteLine("Expected = in loglevel.");
return false;
}
logLevels.Add((loglevel[..pos], loglevel[(pos + 1)..]));
}
else if (arg == "--help")
{
PrintHelp();
return false;
}
else if (arg.StartsWith("+"))
{
execCommands.Add(arg[1..]);
}
else
{
C.WriteLine("Unknown argument: {0}", arg);
}
}
private static void PrintHelp()
{
C.WriteLine(@"
parsed = new CommandLineArgs(
headless,
selfContained,
connect,
launcher,
username,
cvars,
logLevels,
connectAddress,
ss14Address,
mountOptions,
execCommands);
return true;
}
private static void PrintHelp()
{
C.WriteLine(@"
Usage: Robust.Client [options] [+command [+command]]
Options:
--headless Run without graphics/audio/input.
--self-contained Store data relative to executable instead of user-global locations.
@@ -189,30 +198,34 @@ Options:
--mount-dir Resource directory to mount.
--mount-zip Resource zip to mount.
--help Display this help text and exit.
");
}
private CommandLineArgs(
bool headless,
bool selfContained,
bool connect,
bool launcher,
string? username,
IReadOnlyCollection<(string key, string value)> cVars,
IReadOnlyCollection<(string key, string value)> logLevels,
string connectAddress, string? ss14Address,
MountOptions mountOptions)
{
Headless = headless;
SelfContained = selfContained;
Connect = connect;
Launcher = launcher;
Username = username;
CVars = cVars;
LogLevels = logLevels;
ConnectAddress = connectAddress;
Ss14Address = ss14Address;
MountOptions = mountOptions;
}
+command: You can pass a set of commands, prefixed by +,
to be executed in the console in order after the game has finished initializing.
");
}
private CommandLineArgs(
bool headless,
bool selfContained,
bool connect,
bool launcher,
string? username,
IReadOnlyCollection<(string key, string value)> cVars,
IReadOnlyCollection<(string key, string value)> logLevels,
string connectAddress, string? ss14Address,
MountOptions mountOptions,
IReadOnlyList<string> execCommands)
{
Headless = headless;
SelfContained = selfContained;
Connect = connect;
Launcher = launcher;
Username = username;
CVars = cVars;
LogLevels = logLevels;
ConnectAddress = connectAddress;
Ss14Address = ss14Address;
MountOptions = mountOptions;
ExecCommands = execCommands;
}
}

View File

@@ -24,7 +24,6 @@ namespace Robust.Client.Console.Commands
var entityUid = EntityUid.Parse(args[0]);
var componentName = args[1];
var compManager = IoCManager.Resolve<IComponentManager>();
var compFactory = IoCManager.Resolve<IComponentFactory>();
var entityManager = IoCManager.Resolve<IEntityManager>();
@@ -33,7 +32,7 @@ namespace Robust.Client.Console.Commands
component.Owner = entity;
compManager.AddComponent(entity, component);
entityManager.AddComponent(entity, component);
}
}
@@ -55,12 +54,12 @@ namespace Robust.Client.Console.Commands
var entityUid = EntityUid.Parse(args[0]);
var componentName = args[1];
var compManager = IoCManager.Resolve<IComponentManager>();
var entManager = IoCManager.Resolve<IEntityManager>();
var compFactory = IoCManager.Resolve<IComponentFactory>();
var registration = compFactory.GetRegistration(componentName);
compManager.RemoveComponent(entityUid, registration.Type);
entManager.RemoveComponent(entityUid, registration.Type);
}
}
}

View File

@@ -161,19 +161,6 @@ namespace Robust.Client.Console.Commands
}
}
internal class ShowBoundingBoxesCommand : IConsoleCommand
{
public string Command => "showbb";
public string Help => "";
public string Description => "Enables debug drawing over all bounding boxes in the game, showing their size.";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var mgr = IoCManager.Resolve<IDebugDrawing>();
mgr.DebugColliders = !mgr.DebugColliders;
}
}
internal class ShowPositionsCommand : IConsoleCommand
{
public string Command => "showpos";
@@ -201,16 +188,16 @@ namespace Robust.Client.Console.Commands
return;
}
if (!int.TryParse(args[0], out var id))
if (!float.TryParse(args[0], out var duration))
{
shell.WriteError($"{args[0]} is not a valid integer.");
shell.WriteError($"{args[0]} is not a valid float.");
return;
}
var mgr = IoCManager.Resolve<IDebugDrawingManager>();
var mgr = EntitySystem.Get<DebugRayDrawingSystem>();
mgr.DebugDrawRays = !mgr.DebugDrawRays;
shell.WriteError("Toggled showing rays to:" + mgr.DebugDrawRays.ToString());
mgr.DebugRayLifetime = TimeSpan.FromSeconds((double)int.Parse(args[0], CultureInfo.InvariantCulture));
shell.WriteError("Toggled showing rays to:" + mgr.DebugDrawRays);
mgr.DebugRayLifetime = TimeSpan.FromSeconds(duration);
}
}
@@ -632,8 +619,9 @@ namespace Robust.Client.Console.Commands
}
});
tabContainer.AddChild(new HSplitContainer
tabContainer.AddChild(new SplitContainer
{
Orientation = SplitContainer.SplitOrientation.Horizontal,
Children =
{
new PanelContainer

View File

@@ -0,0 +1,19 @@
#if DEBUG
using Robust.Client.Debugging;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
namespace Robust.Client.Console.Commands
{
public sealed class DebugAnchoredCommand : IConsoleCommand
{
public string Command => "showanchored";
public string Description => $"Shows anchored entities on a particular tile";
public string Help => $"{Command}";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
EntitySystem.Get<DebugAnchoringSystem>().Enabled ^= true;
}
}
}
#endif

View File

@@ -63,7 +63,7 @@ namespace Robust.Client.Console.Commands
var conGroup = IoCManager.Resolve<IClientConGroupController>();
foreach (var command in shell.ConsoleHost.RegisteredCommands.Values
.Where(p => p.Command.Contains(filter) && conGroup.CanCommand(p.Command))
.Where(p => p.Command.Contains(filter) && (p is not ServerDummyCommand || conGroup.CanCommand(p.Command)))
.OrderBy(c => c.Command))
{
shell.WriteLine(command.Command + ": " + command.Description);

View File

@@ -7,8 +7,8 @@ namespace Robust.Client.Console.Commands
public sealed class PhysicsOverlayCommands : IConsoleCommand
{
public string Command => "physics";
public string Description => $"{Command} <contactnormals / contactpoints / shapes>";
public string Help => $"{Command} <overlay>";
public string Description => $"Shows a debug physics overlay. The arg supplied specifies the overlay.";
public string Help => $"{Command} <aabbs / contactnormals / contactpoints / joints / shapeinfo / shapes>";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 1)
@@ -21,12 +21,24 @@ namespace Robust.Client.Console.Commands
switch (args[0])
{
case "aabbs":
system.Flags ^= PhysicsDebugFlags.AABBs;
break;
case "com":
system.Flags ^= PhysicsDebugFlags.COM;
break;
case "contactnormals":
system.Flags ^= PhysicsDebugFlags.ContactNormals;
break;
case "contactpoints":
system.Flags ^= PhysicsDebugFlags.ContactPoints;
break;
case "joints":
system.Flags ^= PhysicsDebugFlags.Joints;
break;
case "shapeinfo":
system.Flags ^= PhysicsDebugFlags.ShapeInfo;
break;
case "shapes":
system.Flags ^= PhysicsDebugFlags.Shapes;
break;

View File

@@ -0,0 +1,127 @@
using System;
using System.Collections.Immutable;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Input;
using Robust.Shared.Maths;
using Robust.Shared.Network.Messages;
using Robust.Shared.Utility;
using static Robust.Client.UserInterface.Controls.BoxContainer;
using static Robust.Shared.Network.Messages.MsgScriptCompletionResponse;
namespace Robust.Client.Console
{
public class Completions : SS14Window
{
private HistoryLineEdit _textBar;
private ScrollContainer _suggestPanel = new()
{
HScrollEnabled = false,
};
private BoxContainer _suggestBox = new()
{
Orientation = LayoutOrientation.Vertical,
HorizontalAlignment = HAlignment.Left,
};
public Completions(HistoryLineEdit textBar) : base()
{
Title = "Suggestions";
MouseFilter = MouseFilterMode.Pass;
_textBar = textBar;
_suggestPanel.AddChild(_suggestBox);
ContentsContainer.AddChild(_suggestPanel);
}
private bool _firstopen = true;
public void OpenAt(Vector2 position, Vector2 size)
{
if (_firstopen)
{
SetSize = size;
LayoutContainer.SetPosition(this, position);
_firstopen = false;
}
Open();
}
private ImmutableArray<LiteResult> _results;
public void Update()
{
_suggestBox.RemoveAllChildren();
foreach (var res in _results)
{
var label = new Entry(res);
label.OnKeyBindDown += ev =>
{
if (ev.Function == EngineKeyFunctions.UIClick)
_textBar.InsertAtCursor(label.Result.Properties["InsertionText"]);
};
_suggestBox.AddChild(label);
}
}
public void TextChanged() => Close();
public void SetSuggestions(MsgScriptCompletionResponse response)
{
_results = response.Results;
Update();
}
// Label and ghetto button.
public class Entry : RichTextLabel
{
public readonly LiteResult Result;
public Entry(LiteResult result)
{
MouseFilter = MouseFilterMode.Stop;
Result = result;
var compl = new FormattedMessage();
var dim = Color.FromHsl((0f, 0f, 0.8f, 1f));
// warning: ew ahead
string basen = "default";
if (Result.Tags.Contains("Interface"))
basen = "interface name";
else if (Result.Tags.Contains("Class"))
basen = "class name";
else if (Result.Tags.Contains("Struct"))
basen = "struct name";
else if (Result.Tags.Contains("Keyword"))
basen = "keyword";
else if (Result.Tags.Contains("Namespace"))
basen = "namespace name";
else if (Result.Tags.Contains("Method"))
basen = "method name";
else if (Result.Tags.Contains("Property"))
basen = "property name";
else if (Result.Tags.Contains("Field"))
basen = "field name";
Color basec = ScriptingColorScheme.ColorScheme[basen];
compl.PushColor(basec * dim);
compl.AddText(Result.DisplayTextPrefix);
compl.PushColor(basec);
compl.AddText(Result.DisplayText);
compl.PushColor(basec * dim);
compl.AddText(Result.DisplayTextSuffix);
compl.AddText(" [" + String.Join(", ", Result.Tags) + "]");
if (Result.InlineDescription.Length != 0)
{
compl.PushNewline();
compl.AddText(": ");
compl.PushColor(Color.LightSlateGray);
compl.AddText(Result.InlineDescription);
}
SetMessage(compl);
}
}
}
}

View File

@@ -46,6 +46,16 @@ namespace Robust.Client.Console
}
protected override void Complete()
{
var msg = _client._netManager.CreateNetMessage<MsgScriptCompletion>();
msg.ScriptSession = _session;
msg.Code = InputBar.Text;
msg.Cursor = InputBar.CursorPosition;
_client._netManager.ClientSendMessage(msg);
}
public override void Close()
{
base.Close();
@@ -95,6 +105,12 @@ namespace Robust.Client.Console
OutputPanel.AddText(">");
}
public void ReceiveCompletionResponse(MsgScriptCompletionResponse response)
{
Suggestions.SetSuggestions(response);
Suggestions.OpenAt((Position.X + Size.X, Position.Y), (Size.X / 2, Size.Y));
}
}
}
}

View File

@@ -20,6 +20,8 @@ namespace Robust.Client.Console
_netManager.RegisterNetMessage<MsgScriptStop>();
_netManager.RegisterNetMessage<MsgScriptEval>();
_netManager.RegisterNetMessage<MsgScriptStart>();
_netManager.RegisterNetMessage<MsgScriptCompletion>();
_netManager.RegisterNetMessage<MsgScriptCompletionResponse>(ReceiveScriptCompletionResponse);
_netManager.RegisterNetMessage<MsgScriptResponse>(ReceiveScriptResponse);
_netManager.RegisterNetMessage<MsgScriptStartAck>(ReceiveScriptStartAckResponse);
}
@@ -44,6 +46,16 @@ namespace Robust.Client.Console
console.ReceiveResponse(message);
}
private void ReceiveScriptCompletionResponse(MsgScriptCompletionResponse message)
{
if (!_activeConsoles.TryGetValue(message.ScriptSession, out var console))
{
return;
}
console.ReceiveCompletionResponse(message);
}
public bool CanScript => _conGroupController.CanScript();
public void StartSession()

View File

@@ -53,6 +53,9 @@ namespace Robust.Client.Console
OutputPanel.AddText(">");
}
// No-op for now.
protected override void Complete() { }
protected override async void Run()
{
var code = InputBar.Text;

View File

@@ -0,0 +1,96 @@
#if DEBUG
using System.Text;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
namespace Robust.Client.Debugging
{
public sealed class DebugAnchoringSystem : EntitySystem
{
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
private Label? _label;
private (GridId GridId, TileRef Tile)? _hovered;
public bool Enabled
{
get => _enabled;
set
{
if (_enabled == value) return;
_enabled = value;
if (_enabled)
{
_label = new Label();
IoCManager.Resolve<IUserInterfaceManager>().StateRoot.AddChild(_label);
}
else
{
IoCManager.Resolve<IUserInterfaceManager>().StateRoot.RemoveChild(_label!);
_label = null;
_hovered = null;
}
}
}
private bool _enabled;
public override void FrameUpdate(float frameTime)
{
base.FrameUpdate(frameTime);
if (!Enabled) return;
if (_label == null)
{
DebugTools.Assert($"Debug Label for {nameof(DebugAnchoringSystem)} is null!");
return;
}
var mouseSpot = _inputManager.MouseScreenPosition;
var spot = _eyeManager.ScreenToMap(mouseSpot);
if (!_mapManager.TryFindGridAt(spot, out var grid))
{
_label.Text = string.Empty;
_hovered = null;
return;
}
var tile = grid.GetTileRef(spot);
_label.Position = mouseSpot.Position + new Vector2(32, 0);
if (_hovered?.GridId == grid.Index && _hovered?.Tile == tile) return;
_hovered = (grid.Index, tile);
var text = new StringBuilder();
foreach (var ent in grid.GetAnchoredEntities(spot))
{
if (!EntityManager.TryGetEntity(ent, out var entity))
{
text.AppendLine($"uid: {ent}, invalid");
}
else
{
text.AppendLine($"uid: {ent}, {entity.Name}");
}
}
_label.Text = text.ToString();
}
}
}
#endif

View File

@@ -1,19 +1,8 @@
using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.ResourceManagement;
using Robust.Shared.Enums;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Broadphase;
using Robust.Shared.Physics.Dynamics.Joints;
using Robust.Shared.Prototypes;
namespace Robust.Client.Debugging
{
@@ -22,39 +11,10 @@ namespace Robust.Client.Debugging
{
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IEntityLookup _lookup = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
private bool _debugColliders;
private bool _debugPositions;
/// <inheritdoc />
public bool DebugColliders
{
get => _debugColliders;
set
{
if (value == DebugColliders)
{
return;
}
_debugColliders = value;
if (value && !_overlayManager.HasOverlay<PhysicsOverlay>())
{
_overlayManager.AddOverlay(new PhysicsOverlay(_eyeManager,
_prototypeManager, _inputManager, _mapManager));
}
else
{
_overlayManager.RemoveOverlay<PhysicsOverlay>();
}
}
}
/// <inheritdoc />
public bool DebugPositions
{
@@ -79,187 +39,6 @@ namespace Robust.Client.Debugging
}
}
private class PhysicsOverlay : Overlay
{
private readonly IEyeManager _eyeManager;
private readonly IMapManager _mapManager;
private readonly IInputManager _inputManager;
public override OverlaySpace Space => OverlaySpace.WorldSpace | OverlaySpace.ScreenSpace;
private readonly ShaderInstance _shader;
private readonly Font _font;
private Vector2 _hoverStartScreen = Vector2.Zero;
private List<IPhysBody> _hoverBodies = new();
public PhysicsOverlay(IEyeManager eyeMan, IPrototypeManager protoMan, IInputManager inputManager, IMapManager mapManager)
{
_eyeManager = eyeMan;
_inputManager = inputManager;
_mapManager = mapManager;
_shader = protoMan.Index<ShaderPrototype>("unshaded").Instance();
var cache = IoCManager.Resolve<IResourceCache>();
_font = new VectorFont(cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
}
/// <inheritdoc />
protected internal override void Draw(in OverlayDrawArgs args)
{
switch (args.Space)
{
case OverlaySpace.ScreenSpace:
DrawScreen(args);
break;
case OverlaySpace.WorldSpace:
DrawWorld(args);
break;
}
}
private void DrawScreen(in OverlayDrawArgs args)
{
var screenHandle = args.ScreenHandle;
var lineHeight = _font.GetLineHeight(1f);
Vector2 drawPos = _hoverStartScreen + new Vector2(20, 0) + new Vector2(0, -(_hoverBodies.Count * 4 * lineHeight / 2f));
int row = 0;
foreach (var body in _hoverBodies)
{
if (body != _hoverBodies[0])
{
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), "------");
row++;
}
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Ent: {body.Owner}");
row++;
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Layer: {Convert.ToString(body.CollisionLayer, 2)}");
row++;
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Mask: {Convert.ToString(body.CollisionMask, 2)}");
row++;
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Enabled: {body.CanCollide}, Hard: {body.Hard}, Anchored: {(body).BodyType == BodyType.Static}");
row++;
}
}
private void DrawWorld(in OverlayDrawArgs args)
{
var worldHandle = args.WorldHandle;
worldHandle.UseShader(_shader);
var drawing = new PhysDrawingAdapter(worldHandle);
_hoverBodies.Clear();
var mouseScreenPos = _inputManager.MouseScreenPosition;
var mouseWorldPos = _eyeManager.ScreenToMap(mouseScreenPos).Position;
_hoverStartScreen = mouseScreenPos.Position;
var viewport = _eyeManager.GetWorldViewport();
if (viewport.IsEmpty()) return;
var mapId = _eyeManager.CurrentMap;
var colorEdge = Color.Red.WithAlpha(0.33f);
var drawnJoints = new HashSet<Joint>();
foreach (var physBody in EntitySystem.Get<SharedBroadphaseSystem>().GetCollidingEntities(mapId, viewport))
{
if (physBody.Owner.HasComponent<MapGridComponent>()) continue;
// all entities have a TransformComponent
var transform = physBody.Owner.Transform;
var worldBox = physBody.GetWorldAABB();
if (worldBox.IsEmpty()) continue;
foreach (var fixture in physBody.Fixtures)
{
var shape = fixture.Shape;
var sleepPercent = physBody.Awake ? 0.0f : 1.0f;
shape.DebugDraw(drawing, transform.WorldMatrix, in viewport, sleepPercent);
drawing.SetTransform(in Matrix3.Identity);
}
foreach (var joint in physBody.Joints)
{
if (drawnJoints.Contains(joint)) continue;
drawnJoints.Add(joint);
joint.DebugDraw(drawing, in viewport);
drawing.SetTransform(in Matrix3.Identity);
}
if (worldBox.Contains(mouseWorldPos))
{
_hoverBodies.Add(physBody);
}
// draw AABB
worldHandle.DrawRect(worldBox, colorEdge, false);
}
}
private class PhysDrawingAdapter : DebugDrawingHandle
{
private readonly DrawingHandleWorld _handle;
public PhysDrawingAdapter(DrawingHandleWorld worldHandle)
{
_handle = worldHandle;
}
public override Color WakeMixColor => Color.White;
public override Color GridFillColor => Color.Blue.WithAlpha(0.05f);
public override Color RectFillColor => Color.Green.WithAlpha(0.25f);
public override Color CalcWakeColor(Color color, float wakePercent)
{
var percent = MathHelper.Clamp(wakePercent, 0, 1);
var r = 1 - (percent * (1 - color.R));
var g = 1 - (percent * (1 - color.G));
var b = 1 - (percent * (1 - color.B));
return new Color(r, g, b, color.A);
}
public override void DrawRect(in Box2 box, in Color color)
{
_handle.DrawRect(box, color);
}
public override void DrawRect(in Box2Rotated box, in Color color)
{
_handle.DrawRect(box, color);
}
public override void DrawCircle(Vector2 origin, float radius, in Color color)
{
_handle.DrawCircle(origin, radius, color);
}
public override void DrawPolygonShape(Vector2[] vertices, in Color color)
{
_handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, vertices, color);
}
public override void DrawLine(Vector2 start, Vector2 end, in Color color)
{
_handle.DrawLine(start, end, color);
}
public override void SetTransform(in Matrix3 transform)
{
_handle.SetTransform(transform);
}
}
}
private sealed class EntityPositionOverlay : Overlay
{
private readonly IEntityLookup _lookup;

View File

@@ -20,22 +20,49 @@
* 3. This notice may not be removed or altered from any source distribution.
*/
// MIT License
// Copyright (c) 2019 Erin Catto
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
/* Heavily inspired by Farseer */
using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.ResourceManagement;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Physics.Dynamics.Contacts;
using Robust.Shared.Physics.Dynamics.Joints;
namespace Robust.Client.Debugging
{
internal sealed class DebugPhysicsSystem : SharedDebugPhysicsSystem
public sealed class DebugPhysicsSystem : SharedDebugPhysicsSystem
{
/*
* Used for debugging shapes, controllers, joints, contacts
@@ -46,7 +73,7 @@ namespace Robust.Client.Debugging
private const int MaxContactPoints = 2048;
internal int PointCount;
internal ContactPoint[] _points = new ContactPoint[MaxContactPoints];
internal ContactPoint[] Points = new ContactPoint[MaxContactPoints];
public PhysicsDebugFlags Flags
{
@@ -56,7 +83,14 @@ namespace Robust.Client.Debugging
if (value == _flags) return;
if (_flags == PhysicsDebugFlags.None)
IoCManager.Resolve<IOverlayManager>().AddOverlay(new PhysicsDebugOverlay(this));
IoCManager.Resolve<IOverlayManager>().AddOverlay(
new PhysicsDebugOverlay(
EntityManager,
IoCManager.Resolve<IEyeManager>(),
IoCManager.Resolve<IInputManager>(),
IoCManager.Resolve<IResourceCache>(),
this,
Get<SharedPhysicsSystem>()));
if (value == PhysicsDebugFlags.None)
IoCManager.Resolve<IOverlayManager>().RemoveOverlay(typeof(PhysicsDebugOverlay));
@@ -69,7 +103,7 @@ namespace Robust.Client.Debugging
public override void HandlePreSolve(Contact contact, in Manifold oldManifold)
{
if ((Flags & PhysicsDebugFlags.ContactPoints) != 0)
if ((Flags & (PhysicsDebugFlags.ContactPoints | PhysicsDebugFlags.ContactNormals)) != 0)
{
Manifold manifold = contact.Manifold;
@@ -78,22 +112,24 @@ namespace Robust.Client.Debugging
Fixture fixtureA = contact.FixtureA!;
PointState[] state1, state2;
CollisionManager.GetPointStates(out state1, out state2, oldManifold, manifold);
var state1 = new PointState[2];
var state2 = new PointState[2];
CollisionManager.GetPointStates(ref state1, ref state2, oldManifold, manifold);
Span<Vector2> points = stackalloc Vector2[2];
contact.GetWorldManifold(_physicsManager, out var normal, points);
for (int i = 0; i < manifold.PointCount && PointCount < MaxContactPoints; ++i)
ContactPoint cp = Points[PointCount];
for (var i = 0; i < manifold.PointCount && PointCount < MaxContactPoints; ++i)
{
if (fixtureA == null)
_points[i] = new ContactPoint();
Points[i] = new ContactPoint();
ContactPoint cp = _points[PointCount];
cp.Position = points[i];
cp.Normal = normal;
cp.State = state2[i];
_points[PointCount] = cp;
Points[PointCount] = cp;
++PointCount;
}
}
@@ -108,58 +144,348 @@ namespace Robust.Client.Debugging
}
[Flags]
internal enum PhysicsDebugFlags : byte
public enum PhysicsDebugFlags : byte
{
None = 0,
/// <summary>
/// Shows the world point for each contact in the viewport.
/// </summary>
ContactPoints = 1 << 0,
/// <summary>
/// Shows the world normal for each contact in the viewport.
/// </summary>
ContactNormals = 1 << 1,
/// <summary>
/// Shows all physics shapes in the viewport.
/// </summary>
Shapes = 1 << 2,
ShapeInfo = 1 << 3,
Joints = 1 << 4,
AABBs = 1 << 5,
COM = 1 << 6,
}
internal sealed class PhysicsDebugOverlay : Overlay
{
private DebugPhysicsSystem _physics = default!;
private IEntityManager _entityManager = default!;
private IEyeManager _eyeManager = default!;
private IInputManager _inputManager = default!;
private DebugPhysicsSystem _debugPhysicsSystem = default!;
private SharedPhysicsSystem _physicsSystem = default!;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public override OverlaySpace Space => OverlaySpace.WorldSpace | OverlaySpace.ScreenSpace;
public PhysicsDebugOverlay(DebugPhysicsSystem system)
private static readonly Color JointColor = new(0.5f, 0.8f, 0.8f);
private readonly Font _font;
private HashSet<Joint> _drawnJoints = new();
public PhysicsDebugOverlay(IEntityManager entityManager, IEyeManager eyeManager, IInputManager inputManager, IResourceCache cache, DebugPhysicsSystem system, SharedPhysicsSystem physicsSystem)
{
_physics = system;
_entityManager = entityManager;
_eyeManager = eyeManager;
_inputManager = inputManager;
_debugPhysicsSystem = system;
_physicsSystem = physicsSystem;
_font = new VectorFont(cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
}
private void DrawWorld(DrawingHandleWorld worldHandle, OverlayDrawArgs args)
{
var viewBounds = args.WorldBounds;
var viewAABB = args.WorldAABB;
var mapId = _eyeManager.CurrentMap;
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.Shapes) != 0)
{
foreach (var physBody in _physicsSystem.GetCollidingEntities(mapId, viewBounds))
{
if (physBody.Owner.HasComponent<MapGridComponent>()) continue;
var xform = physBody.GetTransform();
const float AlphaModifier = 0.2f;
foreach (var fixture in physBody.Fixtures)
{
// Invalid shape - Box2D doesn't check for IsSensor
if (physBody.BodyType == BodyType.Dynamic && fixture.Mass == 0f)
{
DrawShape(worldHandle, fixture, xform, Color.Red.WithAlpha(AlphaModifier));
}
else if (!physBody.CanCollide)
{
DrawShape(worldHandle, fixture, xform, new Color(0.5f, 0.5f, 0.3f).WithAlpha(AlphaModifier));
}
else if (physBody.BodyType == BodyType.Static)
{
DrawShape(worldHandle, fixture, xform, new Color(0.5f, 0.9f, 0.5f).WithAlpha(AlphaModifier));
}
else if ((physBody.BodyType & (BodyType.Kinematic | BodyType.KinematicController)) != 0x0)
{
DrawShape(worldHandle, fixture, xform, new Color(0.5f, 0.5f, 0.9f).WithAlpha(AlphaModifier));
}
else if (!physBody.Awake)
{
DrawShape(worldHandle, fixture, xform, new Color(0.6f, 0.6f, 0.6f).WithAlpha(AlphaModifier));
}
else
{
DrawShape(worldHandle, fixture, xform, new Color(0.9f, 0.7f, 0.7f).WithAlpha(AlphaModifier));
}
}
}
}
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.COM) != 0)
{
foreach (var physBody in _physicsSystem.GetCollidingEntities(mapId, viewBounds))
{
Color color;
const float Alpha = 0.25f;
float size;
if (physBody.Owner.HasComponent<MapGridComponent>())
{
color = Color.Orange.WithAlpha(Alpha);
size = 1f;
}
else
{
color = Color.Purple.WithAlpha(Alpha);
size = 0.2f;
}
var transform = physBody.GetTransform();
worldHandle.DrawCircle(Transform.Mul(transform, physBody.LocalCenter), size, color);
}
}
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.AABBs) != 0)
{
foreach (var physBody in _physicsSystem.GetCollidingEntities(mapId, viewBounds))
{
if (physBody.Owner.HasComponent<MapGridComponent>()) continue;
var xform = physBody.GetTransform();
const float AlphaModifier = 0.2f;
Box2? aabb = null;
foreach (var fixture in physBody.Fixtures)
{
for (var i = 0; i < fixture.Shape.ChildCount; i++)
{
var shapeBB = fixture.Shape.ComputeAABB(xform, i);
aabb = aabb?.Union(shapeBB) ?? shapeBB;
}
}
if (aabb == null) continue;
worldHandle.DrawRect(aabb.Value, Color.Red.WithAlpha(AlphaModifier), false);
}
}
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.Joints) != 0x0)
{
_drawnJoints.Clear();
foreach (var jointComponent in _entityManager.EntityQuery<JointComponent>(true))
{
if (jointComponent.JointCount == 0 ||
!_entityManager.TryGetComponent(jointComponent.Owner.Uid, out TransformComponent? xf1) ||
!viewAABB.Contains(xf1.WorldPosition)) continue;
foreach (var (_, joint) in jointComponent.Joints)
{
if (_drawnJoints.Contains(joint)) continue;
DrawJoint(worldHandle, joint);
_drawnJoints.Add(joint);
}
}
}
if ((_debugPhysicsSystem.Flags & (PhysicsDebugFlags.ContactPoints | PhysicsDebugFlags.ContactNormals)) != 0)
{
const float axisScale = 0.3f;
for (var i = 0; i < _debugPhysicsSystem.PointCount; ++i)
{
var point = _debugPhysicsSystem.Points[i];
const float radius = 0.1f;
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.ContactPoints) != 0)
{
if (point.State == PointState.Add)
worldHandle.DrawCircle(point.Position, radius, new Color(255, 77, 243, 255));
else if (point.State == PointState.Persist)
worldHandle.DrawCircle(point.Position, radius, new Color(255, 77, 77, 255));
}
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.ContactNormals) != 0)
{
Vector2 p1 = point.Position;
Vector2 p2 = p1 + point.Normal * axisScale;
worldHandle.DrawLine(p1, p2, new Color(255, 102, 230, 255));
}
}
_debugPhysicsSystem.PointCount = 0;
}
}
private void DrawScreen(DrawingHandleScreen screenHandle, OverlayDrawArgs args)
{
var mapId = _eyeManager.CurrentMap;
var mousePos = _inputManager.MouseScreenPosition;
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.ShapeInfo) != 0x0)
{
var hoverBodies = new List<PhysicsComponent>();
var bounds = Box2.UnitCentered.Translated(_eyeManager.ScreenToMap(mousePos.Position).Position);
foreach (var physBody in _physicsSystem.GetCollidingEntities(mapId, bounds))
{
if (physBody.Owner.HasComponent<MapGridComponent>()) continue;
hoverBodies.Add(physBody);
}
var lineHeight = _font.GetLineHeight(1f);
var drawPos = mousePos.Position + new Vector2(20, 0) + new Vector2(0, -(hoverBodies.Count * 4 * lineHeight / 2f));
int row = 0;
foreach (var body in hoverBodies)
{
if (body != hoverBodies[0])
{
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), "------");
row++;
}
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Ent: {body.Owner}");
row++;
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Layer: {Convert.ToString(body.CollisionLayer, 2)}");
row++;
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Mask: {Convert.ToString(body.CollisionMask, 2)}");
row++;
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Enabled: {body.CanCollide}, Hard: {body.Hard}, Anchored: {(body).BodyType == BodyType.Static}");
row++;
}
}
}
protected internal override void Draw(in OverlayDrawArgs args)
{
if (_physics.Flags == PhysicsDebugFlags.None) return;
if (_debugPhysicsSystem.Flags == PhysicsDebugFlags.None) return;
var worldHandle = args.WorldHandle;
if ((_physics.Flags & PhysicsDebugFlags.Shapes) != 0)
switch (args.Space)
{
// Port DebugDrawing over.
case OverlaySpace.ScreenSpace:
DrawScreen((DrawingHandleScreen) args.DrawingHandle, args);
break;
case OverlaySpace.WorldSpace:
DrawWorld((DrawingHandleWorld) args.DrawingHandle, args);
break;
}
}
if ((_physics.Flags & PhysicsDebugFlags.ContactPoints) != 0)
private void DrawShape(DrawingHandleWorld worldHandle, Fixture fixture, Transform xform, Color color)
{
switch (fixture.Shape)
{
const float axisScale = 0.3f;
case PhysShapeCircle circle:
var center = Transform.Mul(xform, circle.Position);
worldHandle.DrawCircle(center, circle.Radius, color);
break;
case EdgeShape edge:
var v1 = Transform.Mul(xform, edge.Vertex1);
var v2 = Transform.Mul(xform, edge.Vertex2);
worldHandle.DrawLine(v1, v2, color);
for (int i = 0; i < _physics.PointCount; ++i)
{
DebugPhysicsSystem.ContactPoint point = _physics._points[i];
if (point.State == PointState.Add)
worldHandle.DrawCircle(point.Position, 0.5f, new Color(255, 77, 243, 77));
else if (point.State == PointState.Persist)
worldHandle.DrawCircle(point.Position, 0.5f, new Color(255, 77, 77, 77));
if ((_physics.Flags & PhysicsDebugFlags.ContactNormals) != 0)
if (edge.OneSided)
{
Vector2 p1 = point.Position;
Vector2 p2 = p1 + point.Normal * axisScale;
worldHandle.DrawLine(p1, p2, new Color(255, 102, 230, 102));
worldHandle.DrawCircle(v1, 0.1f, color);
worldHandle.DrawCircle(v2, 0.1f, color);
}
}
_physics.PointCount = 0;
break;
case PolygonShape poly:
Span<Vector2> verts = stackalloc Vector2[poly.Vertices.Length];
for (var i = 0; i < verts.Length; i++)
{
verts[i] = Transform.Mul(xform, poly.Vertices[i]);
}
worldHandle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, verts, color);
break;
default:
return;
}
}
private void DrawJoint(DrawingHandleWorld worldHandle, Joint joint)
{
if (!_entityManager.TryGetComponent(joint.BodyAUid, out TransformComponent? xform1) ||
!_entityManager.TryGetComponent(joint.BodyBUid, out TransformComponent? xform2)) return;
var matrix1 = xform1.WorldMatrix;
var matrix2 = xform2.WorldMatrix;
var xf1 = new Vector2(matrix1.R0C2, matrix1.R1C2);
var xf2 = new Vector2(matrix2.R0C2, matrix2.R1C2);
var p1 = matrix1.Transform(joint.LocalAnchorA);
var p2 = matrix2.Transform(joint.LocalAnchorB);
var xfa = new Transform(xf1, xform1.WorldRotation);
var xfb = new Transform(xf2, xform2.WorldRotation);
switch (joint)
{
case DistanceJoint:
worldHandle.DrawLine(xf1, xf2, JointColor);
break;
case PrismaticJoint prisma:
var pA = Transform.Mul(xfa, joint.LocalAnchorA);
var pB = Transform.Mul(xfb, joint.LocalAnchorB);
var axis = Transform.Mul(xfa.Quaternion2D, prisma._localXAxisA);
Color c1 = new(0.7f, 0.7f, 0.7f);
Color c2 = new(0.3f, 0.9f, 0.3f);
Color c3 = new(0.9f, 0.3f, 0.3f);
Color c4 = new(0.3f, 0.3f, 0.9f);
Color c5 = new(0.4f, 0.4f, 0.4f);
worldHandle.DrawLine(pA, pB, c5);
if (prisma.EnableLimit)
{
var lower = pA + axis * prisma.LowerTranslation;
var upper = pA + axis * prisma.UpperTranslation;
var perp = Transform.Mul(xfa.Quaternion2D, prisma._localYAxisA);
worldHandle.DrawLine(lower, upper, c1);
worldHandle.DrawLine(lower - perp * 0.5f, lower + perp * 0.5f, c2);
worldHandle.DrawLine(upper - perp * 0.5f, upper + perp * 0.5f, c3);
}
else
{
worldHandle.DrawLine(pA - axis * 1.0f, pA + axis * 1.0f, c1);
}
worldHandle.DrawCircle(pA, 0.5f, c1);
worldHandle.DrawCircle(pB, 0.5f, c4);
break;
default:
worldHandle.DrawLine(xf1, p1, JointColor);
worldHandle.DrawLine(p1, p2, JointColor);
worldHandle.DrawLine(xf2, p2, JointColor);
break;
}
}
}

View File

@@ -2,21 +2,22 @@ using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Physics;
using Robust.Shared.Timing;
namespace Robust.Client.Debugging
{
internal class DebugDrawingManager : IDebugDrawingManager
internal sealed class DebugRayDrawingSystem : EntitySystem
{
[Dependency] private readonly IClientNetManager _net = default!;
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly IGameTiming _gameTimer = default!;
private readonly List<RayWithLifetime> raysWithLifeTime = new();
private readonly List<RayWithLifetime> _raysWithLifeTime = new();
private bool _debugDrawRays;
private struct RayWithLifetime
@@ -52,9 +53,30 @@ namespace Robust.Client.Debugging
public TimeSpan DebugRayLifetime { get; set; } = TimeSpan.FromSeconds(5);
public void Initialize()
public override void Initialize()
{
_net.RegisterNetMessage<MsgRay>(HandleDrawRay);
base.Initialize();
SubscribeNetworkEvent<MsgRay>(HandleDrawRay);
// To catch anything that's client-only and not sent by the server.
SubscribeLocalEvent<DebugDrawRayMessage>(OnDebugDrawRay);
}
private void OnDebugDrawRay(DebugDrawRayMessage ev)
{
if (!_debugDrawRays)
{
return;
}
var newRayWithLifetime = new RayWithLifetime
{
DidActuallyHit = ev.Data.Results != null,
RayOrigin = ev.Data.Ray.Position,
RayHit = ev.Data.Results?.HitPos ?? ev.Data.Ray.Direction * ev.Data.MaxLength + ev.Data.Ray.Position,
LifeTime = _gameTimer.RealTime + DebugRayLifetime
};
_raysWithLifeTime.Add(newRayWithLifetime);
}
private void HandleDrawRay(MsgRay msg)
@@ -74,15 +96,15 @@ namespace Robust.Client.Debugging
LifeTime = _gameTimer.RealTime + DebugRayLifetime
};
raysWithLifeTime.Add(newRayWithLifetime);
_raysWithLifeTime.Add(newRayWithLifetime);
}
private sealed class DebugDrawRayOverlay : Overlay
{
private readonly DebugDrawingManager _owner;
private readonly DebugRayDrawingSystem _owner;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public DebugDrawRayOverlay(DebugDrawingManager owner)
public DebugDrawRayOverlay(DebugRayDrawingSystem owner)
{
_owner = owner;
}
@@ -90,7 +112,7 @@ namespace Robust.Client.Debugging
protected internal override void Draw(in OverlayDrawArgs args)
{
var handle = args.WorldHandle;
foreach (var ray in _owner.raysWithLifeTime)
foreach (var ray in _owner._raysWithLifeTime)
{
handle.DrawLine(
ray.RayOrigin,
@@ -103,7 +125,7 @@ namespace Robust.Client.Debugging
{
base.FrameUpdate(args);
_owner.raysWithLifeTime.RemoveAll(r => r.LifeTime < _owner._gameTimer.RealTime);
_owner._raysWithLifeTime.RemoveAll(r => r.LifeTime < _owner._gameTimer.RealTime);
}
}
}

View File

@@ -5,11 +5,6 @@
/// </summary>
public interface IDebugDrawing
{
/// <summary>
/// Toggles the visual overlay of physics bodies for each entity on screen.
/// </summary>
bool DebugColliders { get; set; }
/// <summary>
/// Toggles the visual overlay of the local origin for each entity on screen.
/// </summary>

View File

@@ -1,11 +0,0 @@
using System;
namespace Robust.Client.Debugging
{
public interface IDebugDrawingManager
{
bool DebugDrawRays { get; set; }
TimeSpan DebugRayLifetime { get; set; }
void Initialize();
}
}

View File

@@ -0,0 +1,52 @@
using System;
using System.Reflection;
using Robust.Client.WebViewHook;
using Robust.Shared.Log;
using Robust.Shared.Utility;
namespace Robust.Client
{
internal sealed partial class GameController
{
private void LoadOptionalRobustModules(GameController.DisplayMode mode)
{
// In the future, this manifest should be loaded somewhere else and used for more parts of init.
// For now, this is fine.
var manifest = LoadResourceManifest();
foreach (var module in manifest.Modules)
{
switch (module)
{
case "Robust.Client.WebView":
LoadRobustWebView(mode);
break;
default:
Logger.Error($"Unknown Robust module: {module}");
return;
}
}
}
private void LoadRobustWebView(GameController.DisplayMode mode)
{
Logger.Debug("Loading Robust.Client.WebView");
var assembly = LoadRobustModuleAssembly("Robust.Client.WebView");
var attribute = assembly.GetCustomAttribute<WebViewManagerImplAttribute>()!;
DebugTools.AssertNotNull(attribute);
var managerType = attribute.ImplementationType;
_webViewHook = (IWebViewManagerHook)Activator.CreateInstance(managerType)!;
_webViewHook.Initialize(mode);
Logger.Debug("Done initializing Robust.Client.WebView");
}
private Assembly LoadRobustModuleAssembly(string assemblyName)
{
// TODO: Launcher distribution and all that stuff.
return Assembly.Load(assemblyName);
}
}
}

View File

@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Runtime;
@@ -11,12 +10,12 @@ using Robust.Client.GameStates;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.Placement;
using Robust.Client.Player;
using Robust.Client.ResourceManagement;
using Robust.Client.State;
using Robust.Client.UserInterface;
using Robust.Client.Utility;
using Robust.Client.ViewVariables;
using Robust.Client.WebViewHook;
using Robust.LoaderApi;
using Robust.Shared;
using Robust.Shared.Asynchronous;
@@ -32,6 +31,7 @@ using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel;
namespace Robust.Client
{
@@ -67,6 +67,8 @@ namespace Robust.Client
[Dependency] private readonly IMidiManager _midiManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
private IWebViewManagerHook? _webViewHook;
private CommandLineArgs? _commandLineArgs;
// Arguments for loader-load. Not used otherwise.
@@ -87,7 +89,7 @@ namespace Robust.Client
_clyde.SetWindowTitle(Options.DefaultWindowTitle);
_taskManager.Initialize();
_fontManager.SetFontDpi((uint) _configurationManager.GetCVar(CVars.DisplayFontDpi));
_fontManager.SetFontDpi((uint)_configurationManager.GetCVar(CVars.DisplayFontDpi));
// Disable load context usage on content start.
// This prevents Content.Client being loaded twice and things like csi blowing up because of it.
@@ -107,6 +109,9 @@ namespace Robust.Client
IoCManager.Resolve<ISerializationManager>().Initialize();
// Load optional Robust modules.
LoadOptionalRobustModules(displayMode);
// Call Init in game assemblies.
_modLoader.BroadcastRunLevel(ModRunLevel.PreInit);
_modLoader.BroadcastRunLevel(ModRunLevel.Init);
@@ -193,9 +198,44 @@ namespace Robust.Client
_client.ConnectToServer(LaunchState.ConnectEndpoint);
}
ProgramShared.RunExecCommands(_console, _commandLineArgs?.ExecCommands);
return true;
}
private ResourceManifestData LoadResourceManifest()
{
// Parses /manifest.yml for game-specific settings that cannot be exclusively set up by content code.
if (!_resourceCache.TryContentFileRead("/manifest.yml", out var stream))
return new ResourceManifestData(Array.Empty<string>());
var yamlStream = new YamlStream();
using (stream)
{
using var streamReader = new StreamReader(stream, EncodingHelpers.UTF8);
yamlStream.Load(streamReader);
}
if (yamlStream.Documents.Count != 1 || yamlStream.Documents[0].RootNode is not YamlMappingNode mapping)
{
throw new InvalidOperationException(
"Expected a single YAML document with root mapping for /manifest.yml");
}
var modules = Array.Empty<string>();
if (mapping.TryGetNode("modules", out var modulesMap))
{
var sequence = (YamlSequenceNode)modulesMap;
modules = new string[sequence.Children.Count];
for (var i = 0; i < modules.Length; i++)
{
modules[i] = sequence[i].AsString();
}
}
return new ResourceManifestData(modules);
}
internal bool StartupSystemSplash(GameControllerOptions options, Func<ILogHandler>? logHandlerFactory)
{
Options = options;
@@ -217,8 +257,10 @@ namespace Robust.Client
System.Console.WriteLine($"LogLevel {level} does not exist!");
continue;
}
logLevel = result;
}
_logManager.GetSawmill(sawmill).Level = logLevel;
}
}
@@ -259,7 +301,7 @@ namespace Robust.Client
{
// Handle GameControllerOptions implicit CVar overrides.
_configurationManager.OverrideConVars(new []
_configurationManager.OverrideConVars(new[]
{
(CVars.DisplayWindowIconSet.Name, options.WindowIconSet.ToString()),
(CVars.DisplaySplashLogo.Name, options.SplashLogo.ToString())
@@ -271,9 +313,11 @@ namespace Robust.Client
_resourceCache.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
var mountOptions = _commandLineArgs != null
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions) : Options.MountOptions;
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions)
: Options.MountOptions;
ProgramShared.DoMounts(_resourceCache, mountOptions, Options.ContentBuildDirectory, Options.AssemblyDirectory,
ProgramShared.DoMounts(_resourceCache, mountOptions, Options.ContentBuildDirectory,
Options.AssemblyDirectory,
Options.LoadContentResources, _loaderArgs != null && !Options.ResourceMountDisabled, ContentStart);
if (_loaderArgs != null)
@@ -394,6 +438,7 @@ namespace Robust.Client
private void Update(FrameEventArgs frameEventArgs)
{
_webViewHook?.Update();
_clyde.FrameProcess(frameEventArgs);
_modLoader.BroadcastUpdate(ModUpdateLevel.FramePreEngine, frameEventArgs);
_stateManager.FrameUpdate(frameEventArgs);
@@ -442,7 +487,7 @@ namespace Robust.Client
var uh = logManager.GetSawmill("unhandled");
AppDomain.CurrentDomain.UnhandledException += (sender, args) =>
{
var message = ((Exception) args.ExceptionObject).ToString();
var message = ((Exception)args.ExceptionObject).ToString();
uh.Log(args.IsTerminating ? LogLevel.Fatal : LogLevel.Error, message);
};
@@ -485,11 +530,15 @@ namespace Robust.Client
{
_modLoader.Shutdown();
_webViewHook?.Shutdown();
_networkManager.Shutdown("Client shutting down");
_midiManager.Shutdown();
IoCManager.Resolve<IEntityLookup>().Shutdown();
_entityManager.Shutdown();
_clyde.Shutdown();
}
private sealed record ResourceManifestData(string[] Modules);
}
}

View File

@@ -28,8 +28,6 @@ namespace Robust.Client.GameObjects
RegisterClass<OccluderTreeComponent>();
RegisterClass<EyeComponent>();
RegisterClass<AppearanceComponent>();
RegisterClass<AppearanceTestComponent>();
RegisterClass<SnapGridComponent>();
RegisterClass<AnimationPlayerComponent>();
RegisterClass<TimerComponent>();

View File

@@ -40,7 +40,7 @@ namespace Robust.Client.GameObjects
void IClientEntityManagerInternal.InitializeEntity(IEntity entity)
{
EntityManager.InitializeEntity((Entity)entity);
base.InitializeEntity((Entity)entity);
}
void IClientEntityManagerInternal.StartEntity(IEntity entity)

View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Robust.Client.Animations;
using Robust.Shared.GameObjects;
using static Robust.Client.Animations.AnimationPlaybackShared;
@@ -12,11 +11,17 @@ namespace Robust.Client.GameObjects
/// </summary>
public sealed class AnimationPlayerComponent : Component
{
// TODO: Give this component a friend someday. Way too much content shit to change atm ._.
public override string Name => "AnimationPlayer";
private readonly Dictionary<string, AnimationPlayback> _playingAnimations
public int PlayingAnimationCount => PlayingAnimations.Count;
internal readonly Dictionary<string, AnimationPlayback> PlayingAnimations
= new();
internal bool HasPlayingAnimation = false;
/// <summary>
/// Start playing an animation.
/// </summary>
@@ -26,50 +31,33 @@ namespace Robust.Client.GameObjects
/// </param>
public void Play(Animation animation, string key)
{
EntitySystem.Get<AnimationPlayerSystem>().AddComponent(this);
var playback = new AnimationPlayback(animation);
_playingAnimations.Add(key, playback);
PlayingAnimations.Add(key, playback);
}
public bool HasRunningAnimation(string key)
{
return _playingAnimations.ContainsKey(key);
return PlayingAnimations.ContainsKey(key);
}
public void Stop(string key)
{
_playingAnimations.Remove(key);
PlayingAnimations.Remove(key);
}
internal void Update(float frameTime)
/// <summary>
/// Temporary method until the event is replaced with eventbus.
/// </summary>
internal void AnimationComplete(string key)
{
if (_playingAnimations.Count == 0)
{
return;
}
List<string>? toRemove = null;
// TODO: Get rid of this ToArray() allocation.
foreach (var (key, playback) in _playingAnimations.ToArray())
{
var keep = UpdatePlayback(Owner, playback, frameTime);
if (!keep)
{
toRemove ??= new List<string>();
toRemove.Add(key);
}
}
if (toRemove != null)
{
foreach (var key in toRemove)
{
_playingAnimations.Remove(key);
AnimationCompleted?.Invoke(key);
}
}
#pragma warning disable 618
AnimationCompleted?.Invoke(key);
#pragma warning restore 618
}
[Obsolete("Use AnimationCompletedEvent instead")]
public event Action<string>? AnimationCompleted;
}
}

View File

@@ -158,22 +158,4 @@ namespace Robust.Client.GameObjects
{
}
}
sealed class AppearanceTestComponent : Component
{
public override string Name => "AppearanceTest";
float time;
bool state;
public void OnUpdate(float frameTime)
{
time += frameTime;
if (time > 1)
{
time -= 1;
Owner.GetComponent<AppearanceComponent>().SetData("test", state = !state);
}
}
}
}

View File

@@ -27,7 +27,6 @@ namespace Robust.Client.GameObjects
private bool _setDrawFovOnInitialize = true;
[DataField("zoom")]
private Vector2 _setZoomOnInitialize = Vector2.One;
private Vector2 _offset = Vector2.Zero;
public IEye? Eye => _eye;
@@ -85,14 +84,11 @@ namespace Robust.Client.GameObjects
public override Vector2 Offset
{
get => _offset;
get => _eye?.Offset ?? default;
set
{
if(_offset.EqualsApprox(value))
return;
_offset = value;
UpdateEyePosition();
if (_eye != null)
_eye.Offset = value;
}
}
@@ -171,7 +167,7 @@ namespace Robust.Client.GameObjects
{
if (_eye == null) return;
var mapPos = Owner.Transform.MapPosition;
_eye.Position = new MapCoordinates(mapPos.Position + _offset, mapPos.MapId);
_eye.Position = new MapCoordinates(mapPos.Position, mapPos.MapId);
}
}
}

View File

@@ -80,7 +80,7 @@ namespace Robust.Client.GameObjects
var position = Owner.Transform.Coordinates;
foreach (var neighbor in grid.GetInDir(position, dir))
{
if (Owner.EntityManager.ComponentManager.TryGetComponent(neighbor, out ClientOccluderComponent? comp) && comp.Enabled)
if (Owner.EntityManager.TryGetComponent(neighbor, out ClientOccluderComponent? comp) && comp.Enabled)
{
Occluding |= oclDir;
break;

View File

@@ -120,6 +120,12 @@ namespace Robust.Client.GameObjects
set => _visibleNested = value;
}
/// <summary>
/// Whether this pointlight should cast shadows
/// </summary>
[DataField("castShadows")]
public bool CastShadows = true;
[DataField("nestedvisible")]
private bool _visibleNested = true;
[DataField("autoRot")]
@@ -142,7 +148,7 @@ namespace Robust.Client.GameObjects
get => _radius;
set
{
if (MathHelper.CloseTo(value, _radius)) return;
if (MathHelper.CloseToPercent(value, _radius)) return;
base.Radius = value;
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new PointLightRadiusChangedEvent(this));
@@ -163,7 +169,7 @@ namespace Robust.Client.GameObjects
}
}
internal sealed class PointLightUpdateEvent : EntityEventArgs
public sealed class PointLightUpdateEvent : EntityEventArgs
{
}

View File

@@ -43,15 +43,6 @@ namespace Robust.Client.GameObjects
[Animatable]
Color Color { get; set; }
/// <summary>
/// Controls whether we use RSI directions to rotate, or just get angular rotation applied.
/// If true, all rotation to this sprite component is negated (that is rotation from say the owner being rotated).
/// Rotation transformations on individual layers still apply.
/// If false, all layers get locked to south and rotation is a transformation.
/// </summary>
[Obsolete("Use NoRotation and/or DirectionOverride")]
bool Directional { get; set; }
/// <summary>
/// All sprite rotation is locked, and will always be drawn upright on
/// the screen, regardless of world or view orientation.

View File

@@ -71,11 +71,11 @@ namespace Robust.Client.GameObjects
{
var handle = args.WorldHandle;
var currentMap = _eyeManager.CurrentMap;
var viewport = _eyeManager.GetWorldViewport();
var viewport = _eyeManager.GetWorldViewbounds();
foreach (var comp in _renderTree.GetRenderTrees(currentMap, viewport))
{
var localAABB = viewport.Translated(-comp.Owner.Transform.WorldPosition);
var localAABB = comp.Owner.Transform.InvWorldMatrix.TransformBox(viewport);
foreach (var sprite in comp.SpriteTree.QueryAabb(localAABB))
{

View File

@@ -47,7 +47,7 @@ namespace Robust.Client.GameObjects
if (_visible == value) return;
_visible = value;
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new SpriteUpdateEvent());
Owner.EntityManager.EventBus.RaiseLocalEvent(OwnerUid, new SpriteUpdateEvent());
}
}
@@ -114,23 +114,6 @@ namespace Robust.Client.GameObjects
set => color = value;
}
/// <summary>
/// Controls whether we use RSI directions to rotate, or just get angular rotation applied.
/// If true, all rotation to this sprite component is negated (that is rotation from say the owner being rotated).
/// Rotation transformations on individual layers still apply.
/// If false, all layers get locked to south and rotation is a transformation.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[Obsolete("Use NoRotation and/or DirectionOverride")]
public bool Directional
{
get => _directional;
set => _directional = value;
}
[DataField("directional")]
private bool _directional = true;
[ViewVariables]
internal RenderingTreeComponent? RenderTree { get; set; } = null;
@@ -319,7 +302,7 @@ namespace Robust.Client.GameObjects
{
if (_containerOccluded == value) return;
_containerOccluded = value;
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new SpriteUpdateEvent());
Owner.EntityManager.EventBus.RaiseLocalEvent(OwnerUid, new SpriteUpdateEvent());
}
}
@@ -389,6 +372,7 @@ namespace Robust.Client.GameObjects
if (layerDatums.Count != 0)
{
LayerMap.Clear();
LayerDatums = layerDatums;
}
}
@@ -402,7 +386,6 @@ namespace Robust.Client.GameObjects
{
//deep copying things to avoid entanglement
_baseRsi = other._baseRsi;
_directional = other._directional;
_visible = other._visible;
_layerMapShared = other._layerMapShared;
color = other.color;
@@ -564,7 +547,7 @@ namespace Robust.Client.GameObjects
Logger.ErrorS(LogCategory, "Unable to load RSI '{0}'. Trace:\n{1}", rsiPath, Environment.StackTrace);
}
return AddLayer(stateId, res?.RSI);
return AddLayer(stateId, res?.RSI, newIndex);
}
public int AddLayerState(string stateId, ResourcePath rsiPath, int? newIndex = null)
@@ -1217,8 +1200,7 @@ namespace Robust.Client.GameObjects
RenderInternal(drawingHandle, eyeRotation, worldRotation, Vector2.Zero, overrideDirection);
}
[DataField("noRot")]
private bool _screenLock = true;
[DataField("noRot")] private bool _screenLock = false;
[DataField("overrideDir")]
private Direction _overrideDirection = Direction.East;
@@ -1262,7 +1244,7 @@ namespace Robust.Client.GameObjects
}
else
{
angle = CalcRectWorldAngle(worldRotation, numDirs);
angle = CalcRectWorldAngle(worldRotation + eyeRotation, numDirs) - eyeRotation;
}
var sWorldRotation = angle;
@@ -1271,6 +1253,12 @@ namespace Robust.Client.GameObjects
private void RenderInternal(DrawingHandleWorld drawingHandle, Angle eyeRotation, Angle worldRotation, Vector2 worldPosition, Direction? overrideDirection)
{
// Reduce the angles to fix math shenanigans
worldRotation = worldRotation.Reduced();
if (worldRotation.Theta < 0)
worldRotation = new Angle(worldRotation.Theta + Math.Tau);
var localMatrix = GetLocalMatrix();
foreach (var layer in Layers)
@@ -1281,18 +1269,19 @@ namespace Robust.Client.GameObjects
}
var numDirs = GetLayerDirectionCount(layer);
var layerRotation = worldRotation + layer.Rotation;
CalcModelMatrix(numDirs, eyeRotation, worldRotation, worldPosition, out var modelMatrix);
CalcModelMatrix(numDirs, eyeRotation, layerRotation, worldPosition, out var modelMatrix);
Matrix3.Multiply(ref localMatrix, ref modelMatrix, out var transformMatrix);
drawingHandle.SetTransform(in transformMatrix);
RenderLayer(drawingHandle, layer, worldRotation, overrideDirection);
RenderLayer(drawingHandle, layer, eyeRotation, layerRotation, overrideDirection);
}
}
private void RenderLayer(DrawingHandleWorld drawingHandle, Layer layer, Angle worldRotation, Direction? overrideDirection)
private void RenderLayer(DrawingHandleWorld drawingHandle, Layer layer, Angle eyeRotation, Angle worldRotation, Direction? overrideDirection)
{
var texture = GetRenderTexture(layer, worldRotation, overrideDirection);
var texture = GetRenderTexture(layer, worldRotation + eyeRotation, overrideDirection);
if (layer.Shader != null)
{
@@ -1322,7 +1311,7 @@ namespace Robust.Client.GameObjects
public static Angle CalcRectWorldAngle(Angle worldAngle, int numDirections)
{
var theta = worldAngle.Theta;
var segSize = (MathF.PI * 2) / (numDirections * 2);
var segSize = (Math.PI * 2) / (numDirections * 2);
var segments = (int)(theta / segSize);
var odd = segments % 2;
var result = theta - (segments * segSize) - (odd * segSize);
@@ -1664,7 +1653,7 @@ namespace Robust.Client.GameObjects
internal void UpdateBounds()
{
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new SpriteUpdateEvent());
Owner.EntityManager.EventBus.RaiseLocalEvent(OwnerUid, new SpriteUpdateEvent());
}
/// <summary>
@@ -2062,36 +2051,35 @@ namespace Robust.Client.GameObjects
public static IEnumerable<IDirectionalTextureProvider> GetPrototypeTextures(EntityPrototype prototype, IResourceCache resourceCache)
{
return GetPrototypeTextures(prototype, resourceCache, out var _);
}
public static IEnumerable<IDirectionalTextureProvider> GetPrototypeTextures(EntityPrototype prototype, IResourceCache resourceCache, out bool noRot)
{
var results = new List<IDirectionalTextureProvider>();
noRot = false;
var icon = IconComponent.GetPrototypeIcon(prototype, resourceCache);
if (icon != null)
{
yield return icon;
yield break;
results.Add(icon);
return results;
}
if (!prototype.Components.TryGetValue("Sprite", out _))
{
yield return resourceCache.GetFallback<TextureResource>().Texture;
yield break;
results.Add(resourceCache.GetFallback<TextureResource>().Texture);
return results;
}
var dummy = new DummyIconEntity { Prototype = prototype };
var spriteComponent = dummy.AddComponent<SpriteComponent>();
if (prototype.Components.TryGetValue("Appearance", out _))
{
var appearanceComponent = dummy.AddComponent<AppearanceComponent>();
foreach (var layer in appearanceComponent.Visualizers)
{
layer.InitializeEntity(dummy);
layer.OnChangeData(appearanceComponent);
}
}
var entityManager = IoCManager.Resolve<IEntityManager>();
var dummy = entityManager.SpawnEntity(prototype.ID, MapCoordinates.Nullspace).Uid;
var spriteComponent = entityManager.EnsureComponent<SpriteComponent>(dummy);
var anyTexture = false;
foreach (var layer in spriteComponent.AllLayers)
{
if (layer.Texture != null) yield return layer.Texture;
if (layer.Texture != null)
results.Add(layer.Texture);
if (!layer.RsiState.IsValid || !layer.Visible) continue;
var rsi = layer.Rsi ?? spriteComponent.BaseRSI;
@@ -2099,14 +2087,17 @@ namespace Robust.Client.GameObjects
!rsi.TryGetState(layer.RsiState, out var state))
continue;
yield return state;
results.Add(state);
anyTexture = true;
}
dummy.Delete();
noRot = spriteComponent.NoRotation;
entityManager.DeleteEntity(dummy);
if (!anyTexture)
yield return resourceCache.GetFallback<TextureResource>().Texture;
results.Add(resourceCache.GetFallback<TextureResource>().Texture);
return results;
}
public static IRsiStateLike GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache)
@@ -2119,144 +2110,14 @@ namespace Robust.Client.GameObjects
return GetFallbackState(resourceCache);
}
var dummy = new DummyIconEntity { Prototype = prototype };
var spriteComponent = dummy.AddComponent<SpriteComponent>();
dummy.Delete();
var entityManager = IoCManager.Resolve<IEntityManager>();
var dummy = entityManager.SpawnEntity(prototype.ID, MapCoordinates.Nullspace).Uid;
var spriteComponent = entityManager.EnsureComponent<SpriteComponent>(dummy);
var result = spriteComponent.Icon ?? GetFallbackState(resourceCache);
entityManager.DeleteEntity(dummy);
return spriteComponent.Icon ?? GetFallbackState(resourceCache);
return result;
}
#region DummyIconEntity
private class DummyIconEntity : IEntity
{
public GameTick LastModifiedTick { get; } = GameTick.Zero;
public IEntityManager EntityManager { get; } = null!;
public string Name { get; set; } = string.Empty;
public EntityUid Uid { get; } = EntityUid.Invalid;
EntityLifeStage IEntity.LifeStage { get => _lifeStage; set => _lifeStage = value; }
public bool Initialized { get; } = false;
public bool Initializing { get; } = false;
public bool Deleted { get; } = true;
public bool Paused { get; set; }
public EntityPrototype? Prototype { get; set; }
public string Description { get; set; } = string.Empty;
public bool IsValid()
{
return false;
}
public ITransformComponent Transform { get; } = null!;
public IMetaDataComponent MetaData { get; } = null!;
private Dictionary<Type, IComponent> _components = new();
private EntityLifeStage _lifeStage;
public T AddComponent<T>() where T : Component, new()
{
var typeFactory = IoCManager.Resolve<IDynamicTypeFactoryInternal>();
var serializationManager = IoCManager.Resolve<ISerializationManager>();
var comp = (T)typeFactory.CreateInstanceUnchecked(typeof(T));
_components[typeof(T)] = comp;
comp.Owner = this;
if (typeof(ISpriteComponent).IsAssignableFrom(typeof(T)))
{
_components[typeof(ISpriteComponent)] = comp;
}
if (Prototype != null && Prototype.TryGetComponent<T>(comp.Name, out var node))
{
comp = serializationManager.Copy(node, comp)!;
}
return comp;
}
public void RemoveComponent<T>()
{
_components.Remove(typeof(T));
}
public bool HasComponent<T>()
{
return _components.ContainsKey(typeof(T));
}
public bool HasComponent(Type type)
{
return _components.ContainsKey(type);
}
public T GetComponent<T>()
{
return (T)_components[typeof(T)];
}
public IComponent GetComponent(Type type)
{
return null!;
}
public bool TryGetComponent<T>([NotNullWhen(true)] out T? component) where T : class
{
component = null;
if (!_components.TryGetValue(typeof(T), out var value)) return false;
component = (T)value;
return true;
}
public T? GetComponentOrNull<T>() where T : class
{
return null;
}
public bool TryGetComponent(Type type, [NotNullWhen(true)] out IComponent? component)
{
component = null;
if (!_components.TryGetValue(type, out var value)) return false;
component = value;
return true;
}
public IComponent? GetComponentOrNull(Type type)
{
return null;
}
public void QueueDelete()
{
}
public void Delete()
{
}
public IEnumerable<IComponent> GetAllComponents()
{
return Enumerable.Empty<IComponent>();
}
public IEnumerable<T> GetAllComponents<T>()
{
return Enumerable.Empty<T>();
}
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
public void SendMessage(IComponent? owner, ComponentMessage message)
{
}
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
public void SendNetworkMessage(IComponent owner, ComponentMessage message, INetChannel? channel = null)
{
}
public void Dirty()
{
}
}
#endregion
}
internal sealed class SpriteUpdateEvent : EntityEventArgs

View File

@@ -15,12 +15,13 @@ namespace Robust.Client.GameObjects
private static Box2 SpriteAabbFunc(in SpriteComponent value)
{
var worldPos = value.Owner.Transform.WorldPosition;
var bounds = value.CalculateBoundingBox(worldPos);
var worldRot = value.Owner.Transform.WorldRotation;
var bounds = new Box2Rotated(value.CalculateBoundingBox(worldPos), worldRot, worldPos);
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
var offset = -tree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
var localAABB = tree?.Owner.Transform.InvWorldMatrix.TransformBox(bounds) ?? bounds.CalcBoundingBox();
return bounds.Translated(offset);
return localAABB;
}
private static Box2 LightAabbFunc(in PointLightComponent value)
@@ -29,31 +30,34 @@ namespace Robust.Client.GameObjects
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
var boxSize = value.Radius * 2;
var pos = worldPos - tree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
var localPos = tree?.Owner.Transform.InvWorldMatrix.Transform(worldPos) ?? worldPos;
return Box2.CenteredAround(pos, (boxSize, boxSize));
return Box2.CenteredAround(localPos, (boxSize, boxSize));
}
internal static Box2 SpriteAabbFunc(SpriteComponent value, Vector2? worldPos = null)
internal static Box2 SpriteAabbFunc(SpriteComponent value, Vector2? worldPos = null, Angle? worldRot = null)
{
worldPos ??= value.Owner.Transform.WorldPosition;
var bounds = value.CalculateBoundingBox(worldPos.Value);
worldRot ??= value.Owner.Transform.WorldRotation;
var bounds = new Box2Rotated(value.CalculateBoundingBox(worldPos.Value), worldRot.Value, worldPos.Value);
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
var offset = -tree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
var localAABB = tree?.Owner.Transform.InvWorldMatrix.TransformBox(bounds) ?? bounds.CalcBoundingBox();
return bounds.Translated(offset);
return localAABB;
}
internal static Box2 LightAabbFunc(PointLightComponent value, Vector2? worldPos = null)
{
// Lights are circles so don't need entity's rotation
worldPos ??= value.Owner.Transform.WorldPosition;
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
var boxSize = value.Radius * 2;
var pos = worldPos - tree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
var localPos = tree?.Owner.Transform.InvWorldMatrix.Transform(worldPos.Value) ?? worldPos.Value;
return Box2.CenteredAround(pos, (boxSize, boxSize));
return Box2.CenteredAround(localPos, (boxSize, boxSize));
}
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using Robust.Client.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Reflection;
@@ -85,6 +86,10 @@ namespace Robust.Client.GameObjects
SendMessage(new CloseBoundInterfaceMessage(), uiKey);
_openInterfaces.Remove(uiKey);
boundUserInterface.Dispose();
var playerSession = IoCManager.Resolve<IPlayerManager>().LocalPlayer?.Session;
if(playerSession != null)
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new BoundUIClosedEvent(uiKey, Owner.Uid, playerSession));
}
internal void SendMessage(BoundUserInterfaceMessage message, object uiKey)

View File

@@ -1,15 +1,98 @@
using System.Collections.Generic;
using Robust.Client.Animations;
using Robust.Shared.GameObjects;
using Robust.Shared.Utility;
namespace Robust.Client.GameObjects
{
internal sealed class AnimationPlayerSystem : EntitySystem
public sealed class AnimationPlayerSystem : EntitySystem
{
private readonly List<AnimationPlayerComponent> _activeAnimations = new();
public override void FrameUpdate(float frameTime)
{
foreach (var animationPlayerComponent in EntityManager.ComponentManager.EntityQuery<AnimationPlayerComponent>(true))
for (var i = _activeAnimations.Count - 1; i >= 0; i--)
{
animationPlayerComponent.Update(frameTime);
var anim = _activeAnimations[i];
if (!Update(anim, frameTime)) continue;
_activeAnimations.RemoveSwap(i);
anim.HasPlayingAnimation = false;
}
}
internal void AddComponent(AnimationPlayerComponent component)
{
if (component.HasPlayingAnimation) return;
_activeAnimations.Add(component);
component.HasPlayingAnimation = true;
}
private bool Update(AnimationPlayerComponent component, float frameTime)
{
if (component.PlayingAnimationCount == 0 ||
component.Deleted)
return true;
var remie = new RemQueue<string>();
foreach (var (key, playback) in component.PlayingAnimations)
{
var keep = AnimationPlaybackShared.UpdatePlayback(component.Owner, playback, frameTime);
if (!keep)
{
remie.Add(key);
}
}
foreach (var key in remie)
{
component.PlayingAnimations.Remove(key);
EntityManager.EventBus.RaiseLocalEvent(component.Owner.Uid, new AnimationCompletedEvent {Uid = component.Owner.Uid, Key = key});
component.AnimationComplete(key);
}
return false;
}
/// <summary>
/// Start playing an animation.
/// </summary>
public void Play(EntityUid uid, Animation animation, string key)
{
var component = EntityManager.EnsureComponent<AnimationPlayerComponent>(EntityManager.GetEntity(uid));
Play(component, animation, key);
}
/// <summary>
/// Start playing an animation.
/// </summary>
public void Play(AnimationPlayerComponent component, Animation animation, string key)
{
AddComponent(component);
var playback = new AnimationPlaybackShared.AnimationPlayback(animation);
component.PlayingAnimations.Add(key, playback);
}
public bool HasRunningAnimation(EntityUid uid, string key)
{
return EntityManager.TryGetComponent(uid, out AnimationPlayerComponent? component) &&
component.PlayingAnimations.ContainsKey(key);
}
public bool HasRunningAnimation(AnimationPlayerComponent component, string key)
{
return component.PlayingAnimations.ContainsKey(key);
}
public void Stop(AnimationPlayerComponent component, string key)
{
component.PlayingAnimations.Remove(key);
}
}
public sealed class AnimationCompletedEvent : EntityEventArgs
{
public EntityUid Uid { get; init; }
public string Key { get; init; } = string.Empty;
}
}

View File

@@ -1,15 +0,0 @@
using Robust.Shared.GameObjects;
namespace Robust.Client.GameObjects
{
class AppearanceTestSystem : EntitySystem
{
public override void Update(float frameTime)
{
foreach (var appearanceTestComponent in EntityManager.ComponentManager.EntityQuery<AppearanceTestComponent>(true))
{
appearanceTestComponent.OnUpdate(frameTime);
}
}
}
}

View File

@@ -12,21 +12,20 @@ using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Broadphase;
using Robust.Shared.Player;
using Robust.Shared.Utility;
namespace Robust.Client.GameObjects
{
[UsedImplicitly]
public class AudioSystem : EntitySystem, IAudioSystem
public class AudioSystem : SharedAudioSystem, IAudioSystem
{
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IClydeAudio _clyde = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly SharedBroadphaseSystem _broadPhaseSystem = default!;
[Dependency] private readonly SharedPhysicsSystem _broadPhaseSystem = default!;
private readonly List<PlayingStream> _playingClydeStreams = new();
@@ -65,7 +64,7 @@ namespace Robust.Client.GameObjects
return;
}
var stream = (PlayingStream?) Play(ev.FileName, ev.Coordinates, ev.AudioParams);
var stream = (PlayingStream?) Play(ev.FileName, ev.Coordinates, ev.FallbackCoordinates, ev.AudioParams);
if (stream != null)
{
stream.NetIdentifier = ev.Identifier;
@@ -84,8 +83,8 @@ namespace Robust.Client.GameObjects
private void PlayAudioEntityHandler(PlayAudioEntityMessage ev)
{
var stream = EntityManager.TryGetEntity(ev.EntityUid, out var entity) ?
(PlayingStream?) Play(ev.FileName, entity, ev.AudioParams)
: (PlayingStream?) Play(ev.FileName, ev.Coordinates, ev.AudioParams);
(PlayingStream?) Play(ev.FileName, entity, ev.FallbackCoordinates, ev.AudioParams)
: (PlayingStream?) Play(ev.FileName, ev.Coordinates, ev.FallbackCoordinates, ev.AudioParams);
if (stream != null)
{
@@ -135,6 +134,10 @@ namespace Robust.Client.GameObjects
mapPos = stream.TrackingEntity.Transform.MapPosition;
}
// TODO Remove when coordinates can't be NaN
if (mapPos == null || !float.IsFinite(mapPos.Value.X) || !float.IsFinite(mapPos.Value.Y))
mapPos = stream.TrackingFallbackCoordinates?.ToMap(_entityManager);
if (mapPos != null)
{
var pos = mapPos.Value;
@@ -165,7 +168,7 @@ namespace Robust.Client.GameObjects
switch (stream.Attenuation)
{
case Attenuation.Default:
gain = 0f;
gain = 1f;
break;
// You thought I'd implement clamping per source? Hell no that's just for the overall OpenAL setting
// I didn't even wanna implement this much for linear but figured it'd be cleaner.
@@ -190,7 +193,8 @@ namespace Robust.Client.GameObjects
throw new ArgumentOutOfRangeException($"No implemented attenuation for {stream.Attenuation.ToString()}");
}
var actualGain = MathF.Max(0f, MathF.Pow(10, stream.Volume / 10) + gain);
var volume = MathF.Pow(10, stream.Volume / 10);
var actualGain = MathF.Max(0f, volume * gain);
stream.Source.SetVolumeDirect(actualGain);
stream.Source.SetOcclusion(occlusion);
@@ -211,7 +215,6 @@ namespace Robust.Client.GameObjects
{
stream.Source.SetVelocity(stream.TrackingEntity.GlobalLinearVelocity());
}
}
}
}
@@ -276,12 +279,14 @@ namespace Robust.Client.GameObjects
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="entity">The entity "emitting" the audio.</param>
/// <param name="fallbackCoordinates">The map or grid coordinates at which to play the audio when entity is invalid.</param>
/// <param name="audioParams"></param>
private IPlayingAudioStream? Play(string filename, IEntity entity, AudioParams? audioParams = null)
private IPlayingAudioStream? Play(string filename, IEntity entity, EntityCoordinates fallbackCoordinates,
AudioParams? audioParams = null)
{
if (_resourceCache.TryGetResource<AudioResource>(new ResourcePath(filename), out var audio))
{
return Play(audio, entity, audioParams);
return Play(audio, entity, fallbackCoordinates, audioParams);
}
Logger.Error($"Server tried to play audio file {filename} which does not exist.");
@@ -293,15 +298,15 @@ namespace Robust.Client.GameObjects
/// </summary>
/// <param name="stream">The audio stream to play.</param>
/// <param name="entity">The entity "emitting" the audio.</param>
/// <param name="fallbackCoordinates">The map or grid coordinates at which to play the audio when entity is invalid.</param>
/// <param name="audioParams"></param>
private IPlayingAudioStream? Play(AudioStream stream, IEntity entity, AudioParams? audioParams = null)
private IPlayingAudioStream? Play(AudioStream stream, IEntity entity, EntityCoordinates fallbackCoordinates,
AudioParams? audioParams = null)
{
var source = _clyde.CreateAudioSource(stream);
if (!source.SetPosition(entity.Transform.WorldPosition))
{
source.Dispose();
Logger.Warning($"Can't play positional audio \"{stream.Name}\", can't set position.");
return null;
return Play(stream, fallbackCoordinates, fallbackCoordinates, audioParams);
}
ApplyAudioParams(audioParams, source);
@@ -311,6 +316,7 @@ namespace Robust.Client.GameObjects
{
Source = source,
TrackingEntity = entity,
TrackingFallbackCoordinates = fallbackCoordinates != EntityCoordinates.Invalid ? fallbackCoordinates : null,
Attenuation = audioParams?.Attenuation ?? Attenuation.Default,
MaxDistance = audioParams?.MaxDistance ?? float.MaxValue,
ReferenceDistance = audioParams?.ReferenceDistance ?? 1f,
@@ -326,12 +332,14 @@ namespace Robust.Client.GameObjects
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="coordinates">The coordinates at which to play the audio.</param>
/// <param name="fallbackCoordinates">The map or grid coordinates at which to play the audio when coordinates are invalid.</param>
/// <param name="audioParams"></param>
private IPlayingAudioStream? Play(string filename, EntityCoordinates coordinates, AudioParams? audioParams = null)
private IPlayingAudioStream? Play(string filename, EntityCoordinates coordinates, EntityCoordinates fallbackCoordinates,
AudioParams? audioParams = null)
{
if (_resourceCache.TryGetResource<AudioResource>(new ResourcePath(filename), out var audio))
{
return Play(audio, coordinates, audioParams);
return Play(audio, coordinates, fallbackCoordinates, audioParams);
}
Logger.Error($"Server tried to play audio file {filename} which does not exist.");
@@ -343,18 +351,24 @@ namespace Robust.Client.GameObjects
/// </summary>
/// <param name="stream">The audio stream to play.</param>
/// <param name="coordinates">The coordinates at which to play the audio.</param>
/// <param name="fallbackCoordinates">The map or grid coordinates at which to play the audio when coordinates are invalid.</param>
/// <param name="audioParams"></param>
private IPlayingAudioStream? Play(AudioStream stream, EntityCoordinates coordinates,
AudioParams? audioParams = null)
EntityCoordinates fallbackCoordinates, AudioParams? audioParams = null)
{
var source = _clyde.CreateAudioSource(stream);
if (!source.SetPosition(coordinates.ToMapPos(EntityManager)))
if (!source.SetPosition(fallbackCoordinates.Position))
{
source.Dispose();
Logger.Warning($"Can't play positional audio \"{stream.Name}\", can't set position.");
return null;
}
if (!coordinates.IsValid(_entityManager))
{
coordinates = fallbackCoordinates;
}
ApplyAudioParams(audioParams, source);
source.StartPlaying();
@@ -362,6 +376,7 @@ namespace Robust.Client.GameObjects
{
Source = source,
TrackingCoordinates = coordinates,
TrackingFallbackCoordinates = fallbackCoordinates != EntityCoordinates.Invalid ? fallbackCoordinates : null,
Attenuation = audioParams?.Attenuation ?? Attenuation.Default,
MaxDistance = audioParams?.MaxDistance ?? float.MaxValue,
ReferenceDistance = audioParams?.ReferenceDistance ?? 1f,
@@ -394,6 +409,7 @@ namespace Robust.Client.GameObjects
public IClydeAudioSource Source = default!;
public IEntity TrackingEntity = default!;
public EntityCoordinates? TrackingCoordinates;
public EntityCoordinates? TrackingFallbackCoordinates;
public bool Done;
public float Volume;
@@ -439,13 +455,19 @@ namespace Robust.Client.GameObjects
/// <inheritdoc />
public IPlayingAudioStream? Play(Filter playerFilter, string filename, IEntity entity, AudioParams? audioParams = null)
{
return Play(filename, entity, audioParams);
return Play(filename, entity, GetFallbackCoordinates(entity.Transform.MapPosition), audioParams);
}
public IPlayingAudioStream? Play(Filter playerFilter, string filename, EntityUid uid, AudioParams? audioParams = null)
{
return EntityManager.TryGetEntity(uid, out var entity)
? Play(filename, entity, GetFallbackCoordinates(entity.Transform.MapPosition), audioParams) : null;
}
/// <inheritdoc />
public IPlayingAudioStream? Play(Filter playerFilter, string filename, EntityCoordinates coordinates, AudioParams? audioParams = null)
{
return Play(filename, coordinates, audioParams);
return Play(filename, coordinates, GetFallbackCoordinates(coordinates.ToMap(_entityManager)), audioParams);
}
}
}

View File

@@ -52,7 +52,7 @@ namespace Robust.Client.GameObjects
while (_dirtyEntities.TryDequeue(out var entity))
{
if (EntityManager.EntityExists(entity)
&& ComponentManager.TryGetComponent(entity, out ClientOccluderComponent? occluder)
&& EntityManager.TryGetComponent(entity, out ClientOccluderComponent? occluder)
&& occluder.UpdateGeneration != _updateGeneration)
{
occluder.Update();
@@ -100,7 +100,7 @@ namespace Robust.Client.GameObjects
{
foreach (var entity in candidates)
{
if (ComponentManager.HasComponent<ClientOccluderComponent>(entity))
if (EntityManager.HasComponent<ClientOccluderComponent>(entity))
{
_dirtyEntities.Enqueue(entity);
}

View File

@@ -59,6 +59,7 @@ namespace Robust.Client.GameObjects
{
if (!cast.ContainerSet.Any(data => data.Id == id))
{
container.EmptyContainer(true);
container.Shutdown();
toDelete ??= new List<string>();
toDelete.Add(id);

View File

@@ -60,7 +60,6 @@ namespace Robust.Client.GameObjects
public override void Shutdown()
{
base.Shutdown();
UnsubscribeNetworkEvent<SendGridTileLookupMessage>();
IoCManager.Resolve<IUserInterfaceManager>().StateRoot.RemoveChild(_label);
}

View File

@@ -65,14 +65,15 @@ namespace Robust.Client.GameObjects
var map = _eyeManager.CurrentMap;
if (map == MapId.Nullspace) return;
var viewport = _eyeManager.GetWorldViewport();
var viewport = _eyeManager.GetWorldViewbounds();
var worldBounds = viewport.CalcBoundingBox();
foreach (var tree in _tree.GetLightTrees(map, viewport))
foreach (var tree in _tree.GetRenderTrees(map, viewport))
{
foreach (var light in tree)
foreach (var light in tree.LightTree)
{
var aabb = _lookup.GetWorldAabbFromEntity(light.Owner);
if (!aabb.Intersects(viewport)) continue;
if (!aabb.Intersects(worldBounds)) continue;
args.WorldHandle.DrawRect(aabb, Color.Green.WithAlpha(0.1f));
}

View File

@@ -2,11 +2,14 @@
using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Client.Physics;
using Robust.Client.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.Input;
using Robust.Shared.Input.Binding;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
#nullable enable
@@ -18,14 +21,23 @@ namespace Robust.Client.GameObjects
[UsedImplicitly]
internal class EyeUpdateSystem : EntitySystem
{
// How fast the camera rotates in radians
private const float CameraRotateSpeed = MathF.PI;
private const float CameraSnapTolerance = 0.01f;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
#pragma warning disable 649, CS8618
// ReSharper disable once NotNullMemberIsNotInitialized
[Dependency] private readonly IEyeManager _eyeManager;
#pragma warning restore 649, CS8618
private TransformComponent? _lastParent;
private TransformComponent? _lerpTo;
private Angle LerpStartRotation;
private float _accumulator;
public bool IsLerping { get => _lerpTo != null; }
// How fast the camera rotates in radians / s
private const float CameraRotateSpeed = MathF.PI;
// PER THIS AMOUNT OF TIME MILLISECONDS
private const float CameraRotateTimeUnit = 1.2f;
// Safety override
private const float _lerpTimeMax = CameraRotateTimeUnit + 0.4f;
/// <inheritdoc />
public override void Initialize()
@@ -55,39 +67,69 @@ namespace Robust.Client.GameObjects
public override void FrameUpdate(float frameTime)
{
var currentEye = _eyeManager.CurrentEye;
var inputSystem = EntitySystemManager.GetEntitySystem<InputSystem>();
var direction = 0;
if (inputSystem.CmdStates[EngineKeyFunctions.CameraRotateRight] == BoundKeyState.Down)
{
direction += 1;
}
// TODO: Content should have its own way of handling this. We should have a default behavior that they can overwrite.
if (inputSystem.CmdStates[EngineKeyFunctions.CameraRotateLeft] == BoundKeyState.Down)
{
direction -= 1;
}
var playerTransform = _playerManager.LocalPlayer?.ControlledEntity?.Transform;
// apply camera rotation
if(direction != 0)
{
currentEye.Rotation += CameraRotateSpeed * frameTime * direction;
currentEye.Rotation = currentEye.Rotation.Reduced();
}
else
{
// snap to cardinal directions
var closestDir = currentEye.Rotation.GetCardinalDir().ToVec();
var currentDir = currentEye.Rotation.ToVec();
if (playerTransform == null) return;
var dot = Vector2.Dot(closestDir, currentDir);
if (MathHelper.CloseTo(dot, 1, CameraSnapTolerance))
var gridId = playerTransform.GridID;
var parent = gridId != GridId.Invalid && EntityManager.TryGetEntity(_mapManager.GetGrid(gridId).GridEntityId, out var gridEnt) ?
gridEnt.Transform
: _mapManager.GetMapEntity(playerTransform.MapID).Transform;
// Make sure that we don't fire the vomit carousel when we spawn in
if (_lastParent is null)
_lastParent = parent;
// Set a default for target rotation
var parentRotation = -parent.WorldRotation;
// Reuse current rotation when stepping into space
if (parent.GridID == GridId.Invalid)
parentRotation = currentEye.Rotation;
// Handle grid change in general
if (_lastParent != parent)
_lerpTo = parent;
// And we are up and running!
if (_lerpTo is not null)
{
// Handle a case where we have beeing spinning around, but suddenly got off onto a different grid
if (parent != _lerpTo) {
LerpStartRotation = currentEye.Rotation;
_lerpTo = parent;
_accumulator = 0f;
}
_accumulator += frameTime;
var changeNeeded = (float) (LerpStartRotation - parentRotation).Theta;
var changeLerp = _accumulator / (Math.Abs(changeNeeded % MathF.PI) / CameraRotateSpeed * CameraRotateTimeUnit);
currentEye.Rotation = Angle.Lerp(LerpStartRotation, parentRotation, changeLerp);
// Either we have overshot, or we have taken way too long on this, emergency reset time
if (changeLerp > 1.0f || _accumulator >= _lerpTimeMax)
{
currentEye.Rotation = closestDir.ToAngle();
_lastParent = parent;
_lerpTo = null;
_accumulator = 0f;
}
}
foreach (var eyeComponent in EntityManager.ComponentManager.EntityQuery<EyeComponent>(true))
// We are just fine, or we finished a lerp (and probably overshot)
if (_lerpTo is null)
{
currentEye.Rotation = parentRotation;
LerpStartRotation = parentRotation;
}
foreach (var eyeComponent in EntityManager.EntityQuery<EyeComponent>(true))
{
eyeComponent.UpdateEyePosition();
}

View File

@@ -64,25 +64,21 @@ namespace Robust.Client.GameObjects
foreach (var grid in _mapManager.FindGridsIntersecting(currentMap, viewport))
{
var mapGrid = (IMapGridInternal) grid;
var gridEnt = _entityManager.GetEntity(grid.GridEntityId);
var worldPos = gridEnt.Transform.WorldPosition;
var worldRot = gridEnt.Transform.WorldRotation;
if (!_entityManager.TryGetComponent<PhysicsComponent>(gridEnt.Uid, out var body)) continue;
foreach (var (_, chunk) in mapGrid.GetMapChunks())
var transform = body.GetTransform();
foreach (var fixture in body.Fixtures)
{
var chunkBounds = chunk.CalcWorldBounds(worldPos, worldRot);
var aabb = chunkBounds.CalcBoundingBox();
// Calc world bounds for chunk.
if (!aabb.Intersects(in viewport))
for (var i = 0; i < fixture.Shape.ChildCount; i++)
{
continue;
}
var aabb = fixture.Shape.ComputeAABB(transform, i);
args.WorldHandle.DrawRect(chunkBounds, Color.Green.WithAlpha(0.2f), true);
args.WorldHandle.DrawRect(aabb, Color.Red, false);
args.WorldHandle.DrawRect(aabb, Color.Green.WithAlpha(0.2f));
args.WorldHandle.DrawRect(aabb, Color.Red.WithAlpha(0.5f), false);
}
}
}
}

View File

@@ -37,6 +37,18 @@ namespace Robust.Client.GameObjects
/// </summary>
public float MaxLightRadius { get; private set; }
internal IEnumerable<RenderingTreeComponent> GetRenderTrees(MapId mapId, Box2Rotated worldBounds)
{
if (mapId == MapId.Nullspace) yield break;
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldBounds))
{
yield return EntityManager.GetEntity(grid.GridEntityId).GetComponent<RenderingTreeComponent>();
}
yield return _mapManager.GetMapEntity(mapId).GetComponent<RenderingTreeComponent>();
}
internal IEnumerable<RenderingTreeComponent> GetRenderTrees(MapId mapId, Box2 worldAABB)
{
if (mapId == MapId.Nullspace) yield break;
@@ -49,22 +61,6 @@ namespace Robust.Client.GameObjects
yield return _mapManager.GetMapEntity(mapId).GetComponent<RenderingTreeComponent>();
}
internal IEnumerable<DynamicTree<SpriteComponent>> GetSpriteTrees(MapId mapId, Box2 worldAABB)
{
foreach (var comp in GetRenderTrees(mapId, worldAABB))
{
yield return comp.SpriteTree;
}
}
internal IEnumerable<DynamicTree<PointLightComponent>> GetLightTrees(MapId mapId, Box2 worldAABB)
{
foreach (var comp in GetRenderTrees(mapId, worldAABB))
{
yield return comp.LightTree;
}
}
public override void Initialize()
{
base.Initialize();
@@ -113,7 +109,7 @@ namespace Robust.Client.GameObjects
AnythingMovedSubHandler(args.Sender.Transform);
}
private void AnythingMovedSubHandler(ITransformComponent sender)
private void AnythingMovedSubHandler(TransformComponent sender)
{
// To avoid doing redundant updates (and we don't need to update a grid's children ever)
if (!_checkedChildren.Add(sender.Owner.Uid) ||
@@ -129,7 +125,7 @@ namespace Robust.Client.GameObjects
if (sender.Owner.TryGetComponent(out PointLightComponent? light))
QueueLightUpdate(light);
foreach (ITransformComponent child in sender.Children)
foreach (TransformComponent child in sender.Children)
{
AnythingMovedSubHandler(child);
}
@@ -340,7 +336,6 @@ namespace Robust.Client.GameObjects
Logger.WarningS(LoggerSawmill, $"Light radius for {light.Owner} set above max radius of {MaxLightRadius}. This may lead to pop-in.");
}
var treePos = newMapTree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
var aabb = RenderingTreeComponent.LightAabbFunc(light, worldPos);
// If we're on a new map then clear the old one.

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