Compare commits

...

159 Commits

Author SHA1 Message Date
Vera Aguilera Puerto
b67d24efee ITileDefinition now has a Path string that ClydeTileDefinitionManager uses (#1860) 2021-07-12 10:29:13 +02:00
mirrorcult
d992e47f30 Remove NetMessage deprecated boilerplate entirely (#1830)
* Remove NetMessage boilerplate in favor of virtual properties

* forgot some stuff
2021-07-12 10:28:46 +02:00
Acruid
dadd7b4cc3 Remove Static Component NetIds (#1842)
* ComponentNames are not sent over the network when components are created.

* Removed ComponentStates array from EntityState, now the state is stored directly inside the CompChange struct.

* Remove the unnecessary NetID property from ComponentState.

* Remove Component.NetworkSynchronizeExistence.

* Change GetNetComponents to return both the component and the component NetId.

* Remove public usages of the Component.NetID property.

* Adds the NetIDAttribute that can be applied to components.

* Removed Component.NetID.

* Revert changes to GetComponentState and how prediction works.

* Adds component netID automatic generation.

* Modifies ClientConsoleHost so that commands can be called before Initialize().

* Completely remove static NetIds.

* Renamed NetIDAttribute to NetworkedComponentAttribute.

* Fixing unit tests.
2021-07-12 10:23:13 +02:00
Paul Ritter
baef2bc7f8 some misc serv3 fixes (#1861)
adds a typeserializer
adds null parser to read
adds another overload for deserializationresult.value

Co-authored-by: Paul <ritter.paul1+git@googlemail.com>
2021-07-11 15:44:12 +02:00
metalgearsloth
e0b1a7d64a Add Contains Vector2 method to Box2Rotated (#1851) 2021-07-11 22:49:59 +10:00
metalgearsloth
aea5f83002 Preserve world velocities on parent change (#1859)
* Preserve world velocities on parent change

* Faster func

* Test

* lag

* uhh LAAAGGG
2021-07-11 12:25:02 +10:00
Swept
7df2d1f430 Fixes console loc string (#1858) 2021-07-09 15:13:30 +02:00
Vera Aguilera Puerto
d216c3a1f6 VV command can now get IoC services that don't have an interface. 2021-07-07 18:03:20 +02:00
metalgearsloth
986ec3ef06 Do showbb transforms in the iterator (#1847) 2021-07-04 19:52:21 +02:00
metalgearsloth
60cec9cb84 Make showbb awakeness binary (#1846)
Makes it easier to spot mapping issues because otherwise it might have a massive sleep timer and not be easily visible.
2021-07-04 22:31:34 +10:00
Vera Aguilera Puerto
c06707d519 Adds ServerOptions, improve GameControllerOptions, fix engine integration tests (#1844)
* Adds ServerOptions, improve GameControllerOptions, fix engine integration tests

* Do component auto-registration in engine integration tests by default

* Fix integration tests on content, register components ONLY if not contentstarted or options are null

* Add integration test for engine integration tests working correctly

* Move cvar overrides out of content and into engine.
2021-07-03 15:19:46 +02:00
Pieter-Jan Briers
63128324ab Stop RunTicks overriding tick deltas in integration tests.
Now listens to game timing tick rate.
2021-07-03 13:06:19 +02:00
Pieter-Jan Briers
abea3024b4 Raise event when entity paused state changes. 2021-07-02 17:19:34 +02:00
Pieter-Jan Briers
07dafeb6cd Can now rotate anchored entities 2021-07-02 01:16:25 +02:00
Pieter-Jan Briers
a726d42ae3 Fix ResetPredictedEntities applying data early in some cases.
FullState was getting updated before ResetPredictedEntities ran instead of after. So ResetPredictedEntities was applying data to stuff like containers that depends on entities that hadn't been made yet.
2021-07-01 12:16:18 +02:00
metalgearsloth
d02d186a2f Fix light pop-in 2 (#1798)
* Fix light pop-in

Doh

* Uniqueness

* Refactor to be less bad

* Better perf

* Fix perf problems

* Hide debug commands under preprocessor

Can't imagine anyone using this during live-game.

* CVars
2021-06-29 22:29:47 +10:00
Vera Aguilera Puerto
a6be66949d Fix segfault when MIDI renderer is disposed.
Apparently? You need to dispose of the sequencer BEFORE the synth or a funny segfault will occur... T-Thanks libfluidsynth.
Also unregisters the sequencer clients before disposing, just for cleanliness.
2021-06-28 10:59:20 +02:00
Galactic Chimp
0dfd3b7443 #1462 extracted MapGrid's HasGravity property to separate Content component (#1835) 2021-06-27 15:01:58 +10:00
metalgearsloth
6f90e9a76e MapGridSystem for grid initialization (#1838)
* MapGridSystem for grid initialization

Doesn't seem to be a clean way to hook into grids being initialized (can't duplicate directed bus events and a few systems need to EnsureComponent) hence the event.

* Address reviews
2021-06-27 14:59:38 +10:00
Galactic Chimp
9246b88560 #1607 deprecate IEntityQuery (#1837)
* #1607 partial progress

* #1607 almost done

* #1607 small test tweak

* #1607 PR suggestions

* #1607 PR suggestions

* #1607 indentation tweaks
2021-06-27 13:40:00 +10:00
Vera Aguilera Puerto
61af9db362 Fix bug where anchored entities would be spawned at 0,0 always.
This is because the parent was attached first, and then the Entity Coordinates are set, but position doesn't change because the parent is the same and the entity is anchored.
NOTE: I only fixed the EntityCoordinates overload. Unsure if this is even a problem with MapCoordinates.
2021-06-24 21:41:39 +02:00
Vera Aguilera Puerto
5aa950e7f7 GetTilesIntersecting yield returns instead of creating, populating and returning a list of tiles. 2021-06-23 12:40:31 +02:00
Pieter-Jan Briers
b8903af52f Make public DrawingHandleScreen.DrawString helper method.
This method was copy pasted into 4 separate overlays already.
2021-06-23 01:42:47 +02:00
Pieter-Jan Briers
5b35c4e82b Fix disposed exception on client shutdown (I think?). 2021-06-23 01:26:16 +02:00
metalgearsloth
06db80780c Filter contacts for joints properly (#1836)
If you're colliding with something you obtain a joint with it should be filtered correctly now.
2021-06-21 02:27:07 +02:00
Acruid
e5fd4f5700 DependencyCollection Children (#1833)
Adds a new register function for registering a simple Type without an interface.
2021-06-21 02:15:23 +02:00
Galactic Chimp
e1a199e060 Removed old Loc.GetString() use instances (#1814)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2021-06-21 02:12:53 +02:00
Pieter-Jan Briers
22ac34c7a1 Are you for real, .NET CLI. 2021-06-21 02:01:55 +02:00
Pieter-Jan Briers
04fa6901b1 Fix ordering of command arguments.
Dumb CLI.
2021-06-21 01:57:09 +02:00
Pieter-Jan Briers
8f7d4211e3 Try server GC for content integration tests. 2021-06-21 01:53:40 +02:00
Pieter-Jan Briers
1147d6fd9a Do not cache injector delegates for entity system creation. 2021-06-21 01:44:36 +02:00
Pieter-Jan Briers
e4449c4901 Cache data definitions statically.
Significantly improves integration test times.
2021-06-21 01:31:13 +02:00
Pieter-Jan Briers
dc8963faa5 Use a no-op mapped string serializer in integration tests.
Serialization never happens in integration tests, so...
2021-06-21 01:29:51 +02:00
Pieter-Jan Briers
f9cf9a8fd4 Single-threaded integration tests experiment.
This didn't really seem to improve performance, so it's not enabled, but I'm not deleting the code.
2021-06-21 00:38:37 +02:00
Pieter-Jan Briers
393bdc04bc ItemList doesn't render things outside its clip region. 2021-06-20 19:48:24 +02:00
Pieter-Jan Briers
db4d787b49 use a struct enumerator for EventTables GetReferences. 2021-06-20 18:50:43 +02:00
Pieter-Jan Briers
cd802d6a66 Fix more nullrefs due to dummy sprite IEntity. 2021-06-20 18:50:29 +02:00
Pieter-Jan Briers
be2281a5b3 Remove glue code to allow components to subscribe to EventBus.
No. Just no.
2021-06-20 18:10:21 +02:00
Pieter-Jan Briers
e717396b33 MapManager uses directed remove event instead of hooking global ComponentRemoved event. 2021-06-20 18:07:24 +02:00
Pieter-Jan Briers
db0e49f5dd Defer SpriteComponent.IsInert updates.
Prevents O(n^2) behavior in many cases.
2021-06-20 17:57:45 +02:00
Pieter-Jan Briers
d3b94aa6af Fix name generator xaml file locating logic.
It would do .EndsWidth(), so DropDownDebugConsole and DebugConsole would get confused.

It now properly checks for path separators and also will report an error if there are two candidate XAML files.

Also, fixed the AXN0003 diagnostic having the wrong name.
2021-06-20 15:48:48 +02:00
Acruid
713f702064 Engine Entity Anchoring (#1829)
* Added unit tests for the new anchor system design.

* Amend ME!

* SnapGridComponent now anchors the entity when added or removed.
TransformComponent.Anchored properly replicates it's state to clients.

* Converted all SnapGridPositionChangedEvent subscriptions to AnchorStateChangedEvent.
Removed SnapGridPositionChangedEvent.
Obsoleted SnapGridComponent.

* Allows setting Transform.Anchored in prototypes.

* Changing tile to empty under anchored ents unanchors them.

* More unit testing.

* Migrated transform gamestate tests to RobustServerSimulation.

* Fixed nasty off-by-one error when sending a full server state.

* Adds lifetime stages to Component.

* More test fixing.

* Review changes.
2021-06-19 17:26:18 -07:00
Pieter-Jan Briers
d0b6a9b28c Replace usages of OperatingSystem.IsOSPlatform with OperatingSystem.IsXXX(). 2021-06-19 03:14:39 +02:00
Pieter-Jan Briers
4003781a1b Manually load libEGL.dll to fix ANGLE in dev builds. 2021-06-19 03:05:15 +02:00
Acruid
40586a8f0e Removes all engine component events. (#1832) 2021-06-18 10:44:21 +02:00
metalgearsloth
f4d427f5c5 Pre-reqs for multithreaded physics (#1784)
* Tanks off guard

* Medic

* Diamondback

* Gaming

* Gamingo

* Finalise

* Doh

* Bullet licence

* Marginal cleanup

* Physics licences

* Apply reviews
2021-06-17 16:01:41 +10:00
metalgearsloth
ce2a4282f3 Bandaid GridTileLookup containers (#1789)
* Bandaid GridTileLookup containers

I'm hoping tile entities deprecates this system but this should fix our problems for now.

* Fix stupid
2021-06-16 16:18:23 +02:00
Ygg01
0cc78c1402 Add missing types to type conversion (#1828) 2021-06-15 10:22:24 +02:00
Pieter-Jan Briers
fa0d4da6d1 Implement subscription ordering into EventBus. (#1823)
* Implement subscription ordering into EventBus.

* Topological helpers, rest of code uses shared topological sort, fix bugs.

* Fix tests.

Didn't realize that multi-subscriptions are allowed on input handlers like that??

* Improve and use topological sort helpers more.
2021-06-14 21:34:30 +02:00
Ygg01
33334d6f5c Upgrade Linguini to latest (prevents IDE issue with SG) (#1826) 2021-06-13 22:16:12 +02:00
ShadowCommander
55aba93faf Revert "Update Linguini"
This reverts commit 4f49296a94.
2021-06-13 08:38:01 -07:00
ShadowCommander
4f49296a94 Update Linguini 2021-06-13 08:34:57 -07:00
Vera Aguilera Puerto
65357ee8da ComponentHandleState event (#1778) 2021-06-12 15:50:27 +02:00
Vera Aguilera Puerto
bfcad4001b Adds public methods to ActorSystem. (#1824) 2021-06-12 15:50:01 +02:00
DrSmugleaf
5a3000febb Add directed events for player attach/detach (#1815) 2021-06-12 01:45:43 +02:00
DrSmugleaf
2d236670ab Add SetAndDirtyIfChanged extension method (#1816) 2021-06-12 01:44:28 +02:00
20kdc
796f1480a8 Add more tolerance for if player sessions are connected to dead entities (#1818)
* Add more tolerance for if some calamity causes a player session to be connected to a dead entity

* Fix build issues w/ previous commit (oops)
2021-06-10 09:29:25 +02:00
Ygg01
a8ee7be844 Add linguini loc (#1760)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>
2021-06-09 19:44:18 +02:00
metalgearsloth
60b506cb2a Fix GetWorldAABB again (#1802)
No weird-ass bug this time. There was also an issue where occasionally the AABB on the client would disappear if you had a bunch of objects and used showbb but I think it's fixed now.
2021-06-09 12:25:08 +10:00
metalgearsloth
66117e35ba Don't store lights in rendertree if not enabled (#1803) 2021-06-09 01:37:40 +02:00
metalgearsloth
22cfee4f01 Don't store non-visible / occluded sprites in rendertree (#1805)
Sister commit to the point-light one.
2021-06-09 01:33:24 +02:00
metalgearsloth
d9355e576f Remove PhysicsManager (#1812)
Goodnight sweet prince
2021-06-09 01:26:51 +02:00
Vera Aguilera Puerto
e54e33edb0 Client Networked event subscriptions can now take in EntitySessionEventArgs (#1817) 2021-06-09 01:22:20 +02:00
mirrorcult
25881ce343 MapInit ECS event (#1813)
* MapInit ECS event

* allocs
2021-06-08 22:33:21 +02:00
Acruid
f64197c189 Removes the map and grid entity pivot hack from clientside map networking. (#1811) 2021-06-08 02:45:02 -07:00
metalgearsloth
a7ec907f1d Remove mass from physics comp state (#1810)
Deprecated by fixtures storing mass
2021-06-07 13:56:21 +10:00
metalgearsloth
81272b0bc8 Reduce rendertree allocs (#1806) 2021-06-06 14:50:31 +02:00
metalgearsloth
25c1c6ef91 Fix rendertree updatequeued (#1804)
Woulda pushed directly but I cooked my git so ya know, nothing to see here
2021-06-06 19:25:27 +10:00
ShadowCommander
d1a83134e3 Fix SpriteView when UI is scaled 2021-06-05 19:47:26 -07:00
metalgearsloth
71c2993d20 Add joint support for CollisionWakeComponent (#1794)
* Add joint support for CollisionWakeComponent

* Less bad

* Fix heresy
2021-06-05 18:16:06 +10:00
Galactic Chimp
14fa616723 #1224 removed obsolete FloatMath class (#1800) 2021-06-03 00:11:19 +02:00
metalgearsloth
d46ea9c9ba Add gridtilelookup debugger back in (#1792)
Was reverted from the broadphase PR.
2021-06-01 11:58:44 +10:00
ShadowCommander
ea00ccb34f Fix window not calling OnMouseExited 2021-05-31 18:56:23 -07:00
Pieter-Jan Briers
8e416bb3cb Fix error in RenderingTreeSystem.
Seems related to #1754? Doesn't seem to fix it though.
2021-06-01 00:01:36 +02:00
Galactic Chimp
8e0a26073d #1765 - replaced 3 try-catch clauses with if-else clause + VS autoformat of affected files (#1796) 2021-05-31 22:49:23 +02:00
metalgearsloth
9fa24948ea Fix EntitySystemManagerOrderTest (#1793) 2021-05-31 13:20:09 +02:00
metalgearsloth
b9a9cc4b0f Revert "Immediate broadphase updates (#1687)" (#1790)
This reverts commit ee6aee57bd.
2021-05-31 20:33:29 +10:00
Pieter-Jan Briers
a67345f9bf Add debug log for runtime/OS info on game start. 2021-05-31 10:48:30 +02:00
Pieter-Jan Briers
2a022f39bd Add system for loading extra serializer strings.
"fixture-0" comes up a LOT so we load this manually now.
2021-05-31 10:48:30 +02:00
Vera Aguilera Puerto
a3ba969589 Container modified events are raised directed to the container owner. 2021-05-31 10:14:02 +02:00
metalgearsloth
21e1b75f5d Remove ICollideSpecial entirely (#1782)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2021-05-31 09:41:11 +02:00
metalgearsloth
1f6ddd96a6 Optimise the shit out of showpos (#1788) 2021-05-31 09:11:29 +02:00
metalgearsloth
b4d2cd26aa Named joints (#1787)
1 step closer to ECS physics.
2021-05-31 09:01:02 +02:00
metalgearsloth
ee6aee57bd Immediate broadphase updates (#1687) 2021-05-31 08:58:23 +02:00
DrSmugleaf
ac1705e41f Make NetMessage name and group virtual to remove required boilerplate (#1770) 2021-05-31 08:53:46 +02:00
metalgearsloth
196a6047f1 Use Box2D FindMaxSeparation (#1786) 2021-05-31 08:52:01 +02:00
ShadowCommander
b98b36a461 Add loglevel command line arg (#1769) 2021-05-31 08:41:34 +02:00
mirrorcult
9980a98a94 Add new fluent functions for words that change based on gender (#1775) 2021-05-31 08:41:06 +02:00
Pieter-Jan Briers
5a1a1d0420 Add component names to string serializer. 2021-05-30 22:55:33 +02:00
ShadowCommander
1c99678fe9 Change other physics awake event calls to RaiseLocalEvent 2021-05-30 12:16:17 -07:00
ShadowCommander
4049f10faa Make awake dispatch local events (#1785) 2021-05-30 21:05:16 +02:00
metalgearsloth
331c0bd43f Fix PreventCollideEvent (#1781)
With directed bus it's better to raise it both ways for a collision.
2021-05-30 18:38:28 +02:00
metalgearsloth
604cd2f93d Fix physics nullrefs (#1780)
* Fix physics nullrefs

* woops
2021-05-30 05:15:22 -07:00
Vera Aguilera Puerto
a87db2e57c EntitySystemManager uses IRuntimeLog for exception logging. 2021-05-30 12:27:36 +02:00
ShadowCommander
4c7f0a8d6b Add container helper for entity storage interaction (#1777) 2021-05-29 11:40:36 +02:00
20kdc
9eaf52886a Make RenderingTreeSystem handle parent recursion properly, fixing space-station-14#4040 and possibly other bugs (#1776) 2021-05-29 11:39:35 +02:00
Vera Aguilera Puerto
fce6f6c714 Adds ActorSystem to handle Attaching/Detaching players to/from entities sanely. (#1774) 2021-05-29 11:37:34 +02:00
DrSmugleaf
c04d51d489 Add test for YAML hot reloading (#1773)
* Add test for YAML hot reloading

* Perhaps test the event firing as well
2021-05-27 15:50:44 +02:00
Vera Aguilera Puerto
d310871aa6 CVar for MIDI volume. 2021-05-26 19:27:02 +02:00
Vera Aguilera Puerto
5fa422865f AudioSystem warning now prints audio stream name, if any. 2021-05-26 18:56:30 +02:00
Vera Aguilera Puerto
c137823355 Proper cleanup when detaching player from a deleted entity. 2021-05-26 18:44:37 +02:00
Vera Aguilera Puerto
0a0026b9ae Add formatted message newline helper method. 2021-05-26 10:18:27 +02:00
Pieter-Jan Briers
97a2a5cfae Block loading of R2R'd .NET assemblies in sandboxing. 2021-05-25 16:57:54 +02:00
Pieter-Jan Briers
a266e21d9e Add single-type IoC register call. 2021-05-24 22:05:15 +02:00
Pieter-Jan Briers
ad8bbe6401 Clyde audio improvements:
1. Allow loading audio directly from a sample buffer
2. SetVolumeDirect that takes a 0 -> 1 scale similar to OpenAL's AL_GAIN.
3. Fix OpenAL threading bugs with logging by caching the sawmill.
2021-05-23 22:58:32 +02:00
Pieter-Jan Briers
71ce4749fe Show active input context on DebugInputPanel. 2021-05-23 22:58:32 +02:00
Pieter-Jan Briers
1e33c3c843 MIDI renderer debugging code. 2021-05-23 22:58:32 +02:00
Vera Aguilera Puerto
b3f3ca9725 MIDI renderer uses NumericsHelpers to convert stereo audio to mono. 2021-05-23 13:40:06 +02:00
Vera Aguilera Puerto
33c37393ba Fixes incorrect comment in MidiRenderer.
Pain.
2021-05-23 13:15:31 +02:00
metalgearsloth
ae36529744 Emit line for mapping node exceptions (#1764)
Makes it a billion times more useful when I dun screwed up
2021-05-22 11:36:28 +02:00
20kdc
ed2be48864 Fix Coordinates setter destroying local position during a parent change by migrating parent changes to it (#1763)
This fixes computer boards going missing. (Seriously.)
2021-05-22 11:36:18 +02:00
Vera Aguilera Puerto
7ad5ce302e Directed event improvements (#1761)
* IComponentManager holds a reference to IComponentFactory.

* Directed events for a same <TComp, TEvent> can be duplicated, component references of a component are added to the entity's event tables.

* Fix tests.

* Improvements, duplicated subscriptions are no more

* Cache component factory for faster access

* when the
2021-05-21 17:38:52 -07:00
Vera Aguilera Puerto
647f1a6808 Fixes bug where deleting things causes their sprite to appear and pile on 0,0.
- Cleans up an unneeded event.
2021-05-20 21:17:20 +02:00
metalgearsloth
6c44dd9665 RenderingTreeSystem cleanup (#1759)
* RenderingTreeSystem cleanup

10% less bad but 100% still boilerplate

* Slight changies

* Even better

* New shit just got made

* Apply revews
2021-05-20 18:57:29 +10:00
Acruid
c81413b0b4 Fixes bug where the net_entityreport red PVS range square was drawn at half the actual range. 2021-05-19 15:52:44 -07:00
Vera Aguilera Puerto
88b3a557da Fixes bug with PrototypeIdListSerializer where lists wouldn't be copied at all. 2021-05-19 12:12:13 +02:00
Vera Aguilera Puerto
572eb01290 Opens SCSI window centered
- Kinda fixes it getting NaN'd on resize... This isn't a proper fix, however.
2021-05-19 11:11:35 +02:00
Vera Aguilera Puerto
9dab74c9d5 Fixes SS14Window going off-screen. 2021-05-19 10:55:41 +02:00
Paul
e1cb1e1b9c fixes xamlui nuking itself when one (1) (singular) partial declaration is missing 2021-05-18 20:19:50 +02:00
Vera Aguilera Puerto
a23da702b1 MidiManager now has a Volume property which changes the general midi volume.
- Adds VV attributes to MidiManager and MidiRenderer.
2021-05-18 20:03:20 +02:00
Vera Aguilera Puerto
ae9c2423ff Fixes EntityLookup not being restarted correctly on reconnect. 2021-05-18 17:55:24 +02:00
metalgearsloth
a6dae8e30a GridId caching (#1678)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2021-05-17 11:43:22 +02:00
Pieter-Jan Briers
96c0a4ae1f Automatically unsubscribe event bus registrations in entity system shutdown. (#1758)
* Automatically unsubscribe event bus registrations in entity system shutdown.

* Fix incorrect unsubscription of local events, obsolete unsubscribe methods.

That's what we got tests for.

* = null instead of .Clear()
2021-05-17 11:01:22 +02:00
Vera Aguilera Puerto
c26ebcbc78 QueueDelete method for entities (#1757) 2021-05-17 09:20:55 +02:00
Vera Aguilera Puerto
8334050411 MIDI improvements (#1756) 2021-05-16 19:28:14 +02:00
Vera Aguilera Puerto
cc67edfc2c Unsubscribe from directed event in UI System. 2021-05-15 12:40:35 +02:00
Vera Aguilera Puerto
943ea9e6c8 Shutdown and dispose of BoundUserInterfaces correctly when the UI component gets shutdown. 2021-05-14 14:25:53 +02:00
Pieter-Jan Briers
3aa5cefe03 Stop using component messages in bound UI code. 2021-05-13 03:29:38 +02:00
Pieter-Jan Briers
c5b34bab69 More obsoletions component messages. 2021-05-13 02:24:35 +02:00
Pieter-Jan Briers
e4f24ec125 Remove IPlayerSession on client. 2021-05-13 02:16:55 +02:00
Pieter-Jan Briers
250971ade7 Remove anchored APIs from physics. 2021-05-13 02:13:18 +02:00
Pieter-Jan Briers
718adf9740 Deprecate component messages harder. 2021-05-13 01:24:20 +02:00
20kdc
5d63aa8c95 Deferred Input Context Switches (fixes spawn entities menu while moving) (#1755) 2021-05-12 18:40:26 +02:00
Vera Aguilera Puerto
17af3612a5 Remove IActorComponent, rename BasicActorComponent to ActorComponent. (#1752)
Rename playerSession to PlayerSession.
2021-05-12 13:40:16 +02:00
Pieter-Jan Briers
d2ecf6b9b1 Remove CollidesOnMask
Only usage was a unit test.
2021-05-12 01:58:12 +02:00
Pieter-Jan Briers
2a1eda0d38 Fix tests 2021-05-12 01:57:03 +02:00
Pieter-Jan Briers
f0180abeb0 Missed a spot while fixing warnings. 2021-05-12 00:26:47 +02:00
Pieter-Jan Briers
720f1b1d05 Fix a bunch of compiler warnings. 2021-05-12 00:16:12 +02:00
ShadowCommander
ae45a96753 Fix error when someone else is shooting outside client PVS 2021-05-11 13:21:30 -07:00
ShadowCommander
74257c72ee Add yaml linting for entity prototype parent (#1749)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2021-05-11 21:54:51 +02:00
ShadowCommander
cea088f4b4 Change maxlinvelocity and maxangvelocity to take tickrate (#1746) 2021-05-11 21:53:36 +02:00
Pieter-Jan Briers
88678e7d58 Entity localization refactoring (#1753) 2021-05-11 21:52:54 +02:00
Pieter-Jan Briers
d222e25d22 Remove outdated comment. 2021-05-11 20:58:21 +02:00
Pieter-Jan Briers
f0d7fbb6f2 Allow enumeration of monitor video modes.
Basically I kept dumping debugging code for this into Clyde startup to test multi-monitor stuff with GLFW so I guess I'm making it official now.
2021-05-11 20:57:27 +02:00
Pieter-Jan Briers
adec0e71ec I can't believe Zumorica would clean up this class in the last 24 hours. 2021-05-11 13:30:38 +02:00
Pieter-Jan Briers
86d9067d62 Report error if prototype has invalid parent ID. 2021-05-11 13:24:42 +02:00
Vera Aguilera Puerto
b989c9dbee Remove unused IEntityQuery methods from EntitySystem. 2021-05-10 20:35:52 +02:00
Vera Aguilera Puerto
3101ec1985 Mark IEntityQuery and implementing classes as obsolete. 2021-05-10 20:25:54 +02:00
Vera Aguilera Puerto
9ec2da1777 PlayerSession comment cleanup. 2021-05-10 20:20:44 +02:00
Vera Aguilera Puerto
0acd28e8f4 Slight PrototypeManager cleanup. 2021-05-10 20:18:08 +02:00
Vera Aguilera Puerto
c340f50ee5 Remove unused dependencies. 2021-05-10 20:14:15 +02:00
Vera Aguilera Puerto
2b589215aa Improves the Filter API. (#1747)
- Recipients is now of type `IEnumerable<ICommonSession>`.
    - This way, people need to use the Filter API to modify the recipients, instead of being allowed to modify the underlying collection directly.
- Underlying collection is now a HashSet.
    - We want no duplicates at all. This will ensure that while being quite performant.
- Removes `IFilter`.
    - Let's face it, everyone is gonna end up using `Filter` instead as it has a much more convenient API, and nothing else will EVER implement `IFilter`. For this reason, I've just gone ahead and removed it. Every method used `Filter` already, anyway.
- Adds EntitySystem `RaiseNetworkEvent` overload that takes in a `Filter`.
    - This deduplicates a lot of code that enumerated the recipients and raised a network event per each one.
- Made AddPlayersByPvs aware of `net.pvs` and `net.maxupdaterange`, has a range multiplier parameter.
    - Now it will simply add all players to the filter if PVS is disabled, and add all players in range if it's enabled.
    - The range multiplier parameter allows us to make the PVS filter a bit more permissive, and generally better. The range is doubled by default, as it was already before.
- Adds `AddInRange` method to filters.
    - This is useful for the AudioSystem, and the PVS methods use it, too!
- Adds `RemoveByVisibility` method to filters.
    - This will remove all recipients that don't have a visibility flag in their entity's EyeComponent from the filter.
    - (This will be VERY useful for Ghost Pointing, for example)
- Adds `Clone` method to filters.
    - This is useful in cases where methods take in a filter and want to modify it without modifying the original instance. (See AudioSystem)
2021-05-08 22:27:47 +02:00
Vera Aguilera Puerto
490a567ad4 Makes some PlayerManager methods thread-safe. (#1744) 2021-05-08 22:27:26 +02:00
DrSmugleaf
32f0c49484 Fix missing Attribute suffix from some serialization attributes (#1741)
* Fix missing Attribute suffix from some serialization attributes

* Rename DataDefinition namespace to Definition
2021-05-07 14:23:21 +02:00
DrSmugleaf
61113d2434 Fix comments on serialization PropertyAndFieldDefinitionTest 2021-05-04 14:33:29 +02:00
DrSmugleaf
6ba1baa88c Add summary to seed data definition in Robust.Benchmarks 2021-05-04 14:26:07 +02:00
DrSmugleaf
07867acb9a Add serialization writing benchmark, optimize writing (#1739)
* Add serialization write benchmark

* Add baseline test and rename AddNode to Add in mapping extensions

* Optimize serialization writing

* Make reader delegate private

* Unhardcode baseline test
2021-05-04 14:25:13 +02:00
DrSmugleaf
3e28b083b9 Cleanup serialization markdown, add extensions for easier mapping node manipulation (#1738) 2021-05-04 13:01:07 +02:00
DrSmugleaf
68d9e13edf Fix nullability in ViewVariablesInstance onValueChanged tuple action 2021-05-04 12:08:53 +02:00
438 changed files with 9234 additions and 5310 deletions

View File

@@ -38,4 +38,4 @@ jobs:
- name: Content.Tests
run: dotnet test --no-build Content.Tests/Content.Tests.csproj -v n
- name: Content.IntegrationTests
run: 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

3
.gitmodules vendored
View File

@@ -13,3 +13,6 @@
[submodule "ManagedHttpListener"]
path = ManagedHttpListener
url = https://github.com/space-wizards/ManagedHttpListener.git
[submodule "Linguini"]
path = Linguini
url = https://github.com/space-wizards/Linguini

1
Linguini Submodule

Submodule Linguini added at 62b0e75b91

View File

@@ -23,12 +23,12 @@ namespace OpenToolkit.GraphicsLibraryFramework
return IntPtr.Zero;
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
if (OperatingSystem.IsLinux())
{
return NativeLibrary.Load("libglfw.so.3", assembly, path);
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
if (OperatingSystem.IsMacOS())
{
return NativeLibrary.Load("libglfw.3.dylib", assembly, path);
}

View File

@@ -23,6 +23,48 @@
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- name: Box2D
license:
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.
- name: Bullet Physics SDK
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
http://bulletphysics.org
This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it freely,
subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software.
If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
- name: Castle Core
license: |
Copyright 2004-2016 Castle Project - http://www.castleproject.org/
@@ -317,6 +359,43 @@
See the License for the specific language governing permissions and
limitations under the License.
- name: Farseer Physics Engine
license:
Microsoft Permissive License (Ms-PL)
This license governs use of the accompanying software.
If you use the software, you accept this license.
If you do not accept the license, do not use the software.
1. Definitions
The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law.
A "contribution" is the original software, or any additions or changes to the software.
A "contributor" is any person that distributes its contribution under this license.
"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,
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
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,
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,
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
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
merchantability, fitness for a particular purpose and non-infringement.
- name: Mono.Cecil
license: |
Copyright (c) 2008 - 2015 Jb Evain

View File

@@ -1 +0,0 @@
console-line-edit-placeholder = Command Here

View File

@@ -0,0 +1,11 @@
## EntitySpawnWindow
entity-spawn-window-title = Entity Spawn Panel
entity-spawn-window-search-bar-placeholder = search
entity-spawn-window-clear-button = Clear
entity-spawn-window-erase-button-text = Erase Mode
entity-spawn-window-override-menu-tooltip = Override placement
## Console
console-line-edit-placeholder = Command Here

View File

@@ -0,0 +1 @@
tab-container-not-tab-title-provided = No title

View File

@@ -0,0 +1,11 @@
## ViewVariablesInstanceEntity
view-variable-instance-entity-server-components-add-component-button-placeholder = Add Component
view-variable-instance-entity-client-variables-tab-title = Client Variables
view-variable-instance-entity-client-components-tab-title = Client Components
view-variable-instance-entity-server-variables-tab-title = Server Variables
view-variable-instance-entity-server-components-tab-title = Server Components
view-variable-instance-entity-client-components-search-bar-placeholder = Search
view-variable-instance-entity-server-components-search-bar-placeholder = Search
view-variable-instance-entity-add-window-server-components = Add Component [S]
view-variable-instance-entity-add-window-client-components = Add Component [C]

View File

@@ -1,44 +1,21 @@
using System;
using System.IO;
using System.IO;
using System.Linq;
using BenchmarkDotNet.Attributes;
using Robust.Benchmarks.Serialization.Definitions;
using Robust.Server;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.IoC;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Sequence;
using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel;
namespace Robust.Benchmarks.Serialization.Copy
{
public class SerializationCopyBenchmark
public class SerializationCopyBenchmark : SerializationBenchmark
{
public SerializationCopyBenchmark()
{
IoCManager.InitThread();
ServerIoC.RegisterIoC();
IoCManager.BuildGraph();
var assemblies = new[]
{
AppDomain.CurrentDomain.GetAssemblyByName("Robust.Shared"),
AppDomain.CurrentDomain.GetAssemblyByName("Robust.Server"),
AppDomain.CurrentDomain.GetAssemblyByName("Robust.Benchmarks")
};
foreach (var assembly in assemblies)
{
IoCManager.Resolve<IConfigurationManagerInternal>().LoadCVarsFromAssembly(assembly);
}
IoCManager.Resolve<IReflectionManager>().LoadAssemblies(assemblies);
SerializationManager = IoCManager.Resolve<ISerializationManager>();
SerializationManager.Initialize();
InitializeSerialization();
DataDefinitionWithString = new DataDefinitionWithString {StringField = "ABC"};
@@ -50,8 +27,6 @@ namespace Robust.Benchmarks.Serialization.Copy
Seed = SerializationManager.ReadValueOrThrow<SeedDataDefinition>(seedMapping);
}
private ISerializationManager SerializationManager { get; }
private const string String = "ABC";
private const int Integer = 1;

View File

@@ -6,6 +6,10 @@ using Robust.Shared.Utility;
namespace Robust.Benchmarks.Serialization.Definitions
{
/// <summary>
/// Arbitrarily large data definition for benchmarks.
/// Taken from content.
/// </summary>
[Prototype("seed")]
public class SeedDataDefinition : IPrototype
{
@@ -96,13 +100,15 @@ namespace Robust.Benchmarks.Serialization.Definitions
Repeat
}
public enum Gas {}
public enum Gas
{
}
[DataDefinition]
public struct SeedChemQuantity
{
[DataField("Min")]
public int Min { get; }
public int Min;
[DataField("Max")]
public int Max;

View File

@@ -2,6 +2,9 @@
using BenchmarkDotNet.Attributes;
using Robust.Benchmarks.Serialization.Definitions;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Sequence;
using Robust.Shared.Serialization.Markdown.Value;
using YamlDotNet.RepresentationModel;
namespace Robust.Benchmarks.Serialization.Read
@@ -13,7 +16,7 @@ namespace Robust.Benchmarks.Serialization.Read
InitializeSerialization();
StringDataDefNode = new MappingDataNode();
StringDataDefNode.AddNode(new ValueDataNode("string"), new ValueDataNode("ABC"));
StringDataDefNode.Add(new ValueDataNode("string"), new ValueDataNode("ABC"));
var yamlStream = new YamlStream();
yamlStream.Load(new StringReader(SeedDataDefinition.Prototype));

View File

@@ -0,0 +1,98 @@
using System.Globalization;
using System.IO;
using BenchmarkDotNet.Attributes;
using Robust.Benchmarks.Serialization.Definitions;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Sequence;
using Robust.Shared.Serialization.Markdown.Value;
using YamlDotNet.RepresentationModel;
namespace Robust.Benchmarks.Serialization.Write
{
public class SerializationWriteBenchmark : SerializationBenchmark
{
public SerializationWriteBenchmark()
{
InitializeSerialization();
DataDefinitionWithString = new DataDefinitionWithString {StringField = "ABC"};
var yamlStream = new YamlStream();
yamlStream.Load(new StringReader(SeedDataDefinition.Prototype));
var seedMapping = yamlStream.Documents[0].RootNode.ToDataNodeCast<SequenceDataNode>().Cast<MappingDataNode>(0);
Seed = SerializationManager.ReadValueOrThrow<SeedDataDefinition>(seedMapping);
}
private const string String = "ABC";
private const int Integer = 1;
private DataDefinitionWithString DataDefinitionWithString { get; }
private SeedDataDefinition Seed { get; }
[Benchmark]
public DataNode WriteString()
{
return SerializationManager.WriteValue(String);
}
[Benchmark]
public DataNode WriteInteger()
{
return SerializationManager.WriteValue(Integer);
}
[Benchmark]
public DataNode WriteDataDefinitionWithString()
{
return SerializationManager.WriteValue(DataDefinitionWithString);
}
[Benchmark]
public DataNode WriteSeedDataDefinition()
{
return SerializationManager.WriteValue(Seed);
}
[Benchmark]
public DataNode BaselineWriteSeedDataDefinition()
{
var mapping = new MappingDataNode();
mapping.Add("id", Seed.ID);
mapping.Add("name", Seed.Name);
mapping.Add("seedName", Seed.SeedName);
mapping.Add("displayName", Seed.DisplayName);
mapping.Add("productPrototypes", Seed.ProductPrototypes);
mapping.Add("harvestRepeat", Seed.HarvestRepeat.ToString());
mapping.Add("lifespan", Seed.Lifespan.ToString(CultureInfo.InvariantCulture));
mapping.Add("maturation", Seed.Maturation.ToString(CultureInfo.InvariantCulture));
mapping.Add("production", Seed.Production.ToString(CultureInfo.InvariantCulture));
mapping.Add("yield", Seed.Yield.ToString(CultureInfo.InvariantCulture));
mapping.Add("potency", Seed.Potency.ToString(CultureInfo.InvariantCulture));
mapping.Add("growthStages", Seed.GrowthStages.ToString(CultureInfo.InvariantCulture));
mapping.Add("idealLight", Seed.IdealLight.ToString(CultureInfo.InvariantCulture));
mapping.Add("idealHeat", Seed.IdealHeat.ToString(CultureInfo.InvariantCulture));
var chemicals = new MappingDataNode();
foreach (var (name, quantity) in Seed.Chemicals)
{
chemicals.Add(name, new MappingDataNode
{
["Min"] = new ValueDataNode(quantity.Min.ToString(CultureInfo.InvariantCulture)),
["Max"] = new ValueDataNode(quantity.Max.ToString(CultureInfo.InvariantCulture)),
["PotencyDivisor"] = new ValueDataNode(quantity.PotencyDivisor.ToString(CultureInfo.InvariantCulture))
});
}
mapping.Add("chemicals", chemicals);
return mapping;
}
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
@@ -147,9 +148,10 @@ namespace {nameSpace}
foreach (var typeSymbol in symbols)
{
var xamlFileName = $"{typeSymbol.Name}.xaml";
var relevantXamlFile = context.AdditionalFiles.FirstOrDefault(t => t.Path.EndsWith(xamlFileName));
var xamlFileNameSep = $"{Path.DirectorySeparatorChar}{xamlFileName}";
var relevantXamlFiles = context.AdditionalFiles.Where(t => t.Path.EndsWith(xamlFileNameSep)).ToArray();
if (relevantXamlFile == null)
if (relevantXamlFiles.Length == 0)
{
context.ReportDiagnostic(
Diagnostic.Create(
@@ -165,13 +167,28 @@ namespace {nameSpace}
continue;
}
var txt = relevantXamlFile.GetText()?.ToString();
if (txt == null)
if (relevantXamlFiles.Length > 1)
{
context.ReportDiagnostic(
Diagnostic.Create(
new DiagnosticDescriptor(
"RXN0002",
$"Found multiple candidate XAML files for {typeSymbol}",
$"Multiple files exist with name {xamlFileName}",
"Usage",
DiagnosticSeverity.Error,
true),
typeSymbol.Locations[0]));
continue;
}
var txt = relevantXamlFiles[0].GetText()?.ToString();
if (txt == null)
{
context.ReportDiagnostic(
Diagnostic.Create(
new DiagnosticDescriptor(
"RXN0004",
$"Unexpected empty Xaml-File was found at {xamlFileName}",
"Expected Content due to a Class with the same name being annotated with [GenerateTypedNameReferences].",
"Usage",
@@ -192,7 +209,7 @@ namespace {nameSpace}
context.ReportDiagnostic(
Diagnostic.Create(
new DiagnosticDescriptor(
"AXN0003",
"RXN0003",
"Unhandled exception occured while generating typed Name references.",
$"Unhandled exception occured while generating typed Name references: {e}",
"Usage",
@@ -247,7 +264,6 @@ namespace {nameSpace}
DiagnosticSeverity.Error,
true),
Location.None));
return null;
}
}

View File

@@ -6,14 +6,18 @@ using System.Threading;
using NFluidsynth;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Broadphase;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using Logger = Robust.Shared.Log.Logger;
namespace Robust.Client.Audio.Midi
@@ -32,24 +36,6 @@ namespace Robust.Client.Audio.Midi
/// </returns>
IMidiRenderer? GetNewRenderer();
/*
/// <summary>
/// Checks whether the file at the given path is a valid midi file or not.
/// </summary>
/// <remarks>
/// We add this here so content doesn't need to reference NFluidsynth.
/// </remarks>
bool IsMidiFile(string filename);
/// <summary>
/// Checks whether the file at the given path is a valid midi file or not.
/// </summary>
/// <remarks>
/// We add this here so content doesn't need to reference NFluidsynth.
/// </remarks>
bool IsSoundfontFile(string filename);
*/
/// <summary>
/// Method called every frame.
/// Should be used to update positional audio.
@@ -57,6 +43,11 @@ namespace Robust.Client.Audio.Midi
/// <param name="frameTime"></param>
void FrameUpdate(float frameTime);
/// <summary>
/// Volume, in db.
/// </summary>
float Volume { get; set; }
/// <summary>
/// If true, MIDI support is available.
/// </summary>
@@ -72,9 +63,11 @@ namespace Robust.Client.Audio.Midi
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IResourceManagerInternal _resourceManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IConfigurationManager _cfgMan = default!;
private SharedBroadPhaseSystem _broadPhaseSystem = default!;
[ViewVariables]
public bool IsAvailable
{
get
@@ -85,12 +78,29 @@ namespace Robust.Client.Audio.Midi
}
}
private readonly List<MidiRenderer> _renderers = new();
[ViewVariables]
private readonly List<IMidiRenderer> _renderers = new();
private bool _alive = true;
private Settings? _settings;
private Thread? _midiThread;
private ISawmill _midiSawmill = default!;
private float _volume = 0f;
private bool _volumeDirty = true;
[ViewVariables(VVAccess.ReadWrite)]
public float Volume
{
get => _volume;
set
{
if (MathHelper.CloseTo(_volume, value))
return;
_cfgMan.SetCVar(CVars.MidiVolume, value);
_volumeDirty = true;
}
}
private static readonly string[] LinuxSoundfonts =
{
@@ -117,12 +127,19 @@ namespace Robust.Client.Audio.Midi
private NFluidsynth.Logger.LoggerDelegate _loggerDelegate = default!;
private ISawmill _sawmill = default!;
[ViewVariables(VVAccess.ReadWrite)]
public int OcclusionCollisionMask { get; set; }
private void InitializeFluidsynth()
{
if (FluidsynthInitialized || _failedInitialize) return;
_cfgMan.OnValueChanged(CVars.MidiVolume, value =>
{
_volume = value;
_volumeDirty = true;
}, true);
_midiSawmill = Logger.GetSawmill("midi");
_sawmill = Logger.GetSawmill("midi.fluidsynth");
_loggerDelegate = LoggerDelegate;
@@ -175,18 +192,6 @@ namespace Robust.Client.Audio.Midi
_sawmill.Log(rLevel, message);
}
/*
public bool IsMidiFile(string filename)
{
return SoundFont.IsMidiFile(filename);
}
public bool IsSoundfontFile(string filename)
{
return SoundFont.IsSoundFont(filename);
}
*/
public IMidiRenderer? GetNewRenderer()
{
if (!FluidsynthInitialized)
@@ -220,7 +225,7 @@ namespace Robust.Client.Audio.Midi
// Since the last loaded soundfont takes priority, we load the fallback soundfont before the soundfont.
renderer.LoadSoundfont(FallbackSoundfont);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
if (OperatingSystem.IsLinux())
{
foreach (var filepath in LinuxSoundfonts)
{
@@ -238,19 +243,21 @@ namespace Robust.Client.Audio.Midi
break;
}
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
else if (OperatingSystem.IsMacOS())
{
if (File.Exists(OsxSoundfont) && SoundFont.IsSoundFont(OsxSoundfont))
renderer.LoadSoundfont(OsxSoundfont, true);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
else if (OperatingSystem.IsWindows())
{
if (File.Exists(WindowsSoundfont) && SoundFont.IsSoundFont(WindowsSoundfont))
renderer.LoadSoundfont(WindowsSoundfont, true);
}
lock (_renderers)
{
_renderers.Add(renderer);
}
return renderer;
}
@@ -268,74 +275,79 @@ namespace Robust.Client.Audio.Midi
}
// Update positions of streams every frame.
lock (_renderers)
foreach (var renderer in _renderers)
foreach (var renderer in _renderers)
{
if (renderer.Disposed)
continue;
if(_volumeDirty)
renderer.Source.SetVolume(Volume);
if (!renderer.Mono)
{
if (renderer.Disposed)
continue;
if (!renderer.Mono)
{
renderer.Source.SetGlobal();
continue;
}
MapCoordinates? mapPos = null;
if (renderer.TrackingCoordinates != null)
{
mapPos = renderer.TrackingCoordinates.Value.ToMap(_entityManager);
}
else if (renderer.TrackingEntity != null)
{
mapPos = renderer.TrackingEntity.Transform.MapPosition;
}
if (mapPos != null)
{
var pos = mapPos.Value;
if (pos.MapId != _eyeManager.CurrentMap)
{
renderer.Source.SetVolume(-10000000);
}
else
{
var sourceRelative = _eyeManager.CurrentEye.Position.Position - pos.Position;
var occlusion = 0f;
if (sourceRelative.Length > 0)
{
occlusion = _broadPhaseSystem.IntersectRayPenetration(
pos.MapId,
new CollisionRay(
pos.Position,
sourceRelative.Normalized,
OcclusionCollisionMask),
sourceRelative.Length,
renderer.TrackingEntity);
}
renderer.Source.SetOcclusion(occlusion);
}
if (renderer.Source.SetPosition(pos.Position))
{
continue;
}
if (renderer.TrackingEntity != null)
{
renderer.Source.SetVelocity(renderer.TrackingEntity.GlobalLinearVelocity());
}
if (float.IsNaN(pos.Position.X) || float.IsNaN(pos.Position.Y))
{
// just duck out instead of move to NaN
renderer.Source.SetOcclusion(float.MaxValue);
continue;
}
_midiSawmill?.Warning("Interrupting positional audio, can't set position.");
renderer.Source.StopPlaying();
}
renderer.Source.SetGlobal();
continue;
}
MapCoordinates? mapPos = null;
if (renderer.TrackingCoordinates != null)
{
mapPos = renderer.TrackingCoordinates.Value.ToMap(_entityManager);
}
else if (renderer.TrackingEntity != null)
{
mapPos = renderer.TrackingEntity.Transform.MapPosition;
}
if (mapPos != null)
{
var pos = mapPos.Value;
if (pos.MapId != _eyeManager.CurrentMap)
{
renderer.Source.SetVolume(-10000000);
}
else
{
var sourceRelative = _eyeManager.CurrentEye.Position.Position - pos.Position;
var occlusion = 0f;
if (sourceRelative.Length > 0)
{
occlusion = _broadPhaseSystem.IntersectRayPenetration(
pos.MapId,
new CollisionRay(
pos.Position,
sourceRelative.Normalized,
OcclusionCollisionMask),
sourceRelative.Length,
renderer.TrackingEntity);
}
renderer.Source.SetOcclusion(occlusion);
}
if (renderer.Source.SetPosition(pos.Position))
{
continue;
}
if (renderer.TrackingEntity != null)
{
renderer.Source.SetVelocity(renderer.TrackingEntity.GlobalLinearVelocity());
}
if (float.IsNaN(pos.Position.X) || float.IsNaN(pos.Position.Y))
{
// just duck out instead of move to NaN
renderer.Source.SetOcclusion(float.MaxValue);
continue;
}
_midiSawmill?.Warning("Interrupting positional audio, can't set position.");
renderer.Source.StopPlaying();
}
}
_volumeDirty = false;
}
/// <summary>
@@ -346,6 +358,7 @@ namespace Robust.Client.Audio.Midi
while (_alive)
{
lock (_renderers)
{
for (var i = 0; i < _renderers.Count; i++)
{
var renderer = _renderers[i];
@@ -353,10 +366,11 @@ namespace Robust.Client.Audio.Midi
renderer.Render();
else
{
((IMidiRenderer)renderer).InternalDispose();
renderer.InternalDispose();
_renderers.Remove(renderer);
}
}
}
Thread.Sleep(1);
}
@@ -367,9 +381,13 @@ namespace Robust.Client.Audio.Midi
_alive = false;
_midiThread?.Join();
_settings?.Dispose();
foreach (var renderer in _renderers)
lock (_renderers)
{
renderer?.Dispose();
foreach (var renderer in _renderers)
{
renderer?.Dispose();
}
}
if (FluidsynthInitialized && !_failedInitialize)
@@ -424,6 +442,7 @@ namespace Robust.Client.Audio.Midi
var span = new Span<byte>(buf.ToPointer(), length);
var stream = _openStreams[(int) sfHandle];
// Fluidsynth's docs state that this method should leave the buffer unmodified if it fails. (returns -1)
try
{
// Fluidsynth does a LOT of tiny allocations (frankly, way too much).
@@ -447,6 +466,7 @@ namespace Robust.Client.Audio.Midi
{
return -1;
}
return 0;
}
@@ -468,10 +488,12 @@ namespace Robust.Client.Audio.Midi
public override int Close(IntPtr sfHandle)
{
var stream = _openStreams[(int) sfHandle];
if (!_openStreams.Remove((int) sfHandle, out var stream))
return -1;
stream.Dispose();
_openStreams.Remove((int) sfHandle);
return 0;
}
}
}

View File

@@ -7,7 +7,9 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using MidiEvent = NFluidsynth.MidiEvent;
namespace Robust.Client.Audio.Midi
@@ -21,6 +23,17 @@ namespace Robust.Client.Audio.Midi
public interface IMidiRenderer : IDisposable
{
/// <summary>
/// The buffered audio source of this renderer.
/// </summary>
internal IClydeBufferedAudioSource Source { get; }
/// <summary>
/// Whether this renderer has been disposed or not.
/// </summary>
bool Disposed { get; }
/// <summary>
/// This controls whether the midi file being played will loop or not.
/// </summary>
@@ -110,6 +123,11 @@ namespace Robust.Client.Audio.Midi
/// </summary>
void StopAllNotes();
/// <summary>
/// Render and play MIDI to the audio source.
/// </summary>
internal void Render();
/// <summary>
/// Loads a new soundfont into the renderer.
/// </summary>
@@ -159,7 +177,7 @@ namespace Robust.Client.Audio.Midi
internal void InternalDispose();
}
public class MidiRenderer : IMidiRenderer
internal class MidiRenderer : IMidiRenderer
{
[Dependency] private readonly IClydeAudio _clydeAudio = default!;
[Dependency] private readonly ITaskManager _taskManager = default!;
@@ -186,10 +204,16 @@ namespace Robust.Client.Audio.Midi
private const int SampleRate = 44100;
private const int Buffers = SampleRate / 2205;
private readonly object _playerStateLock = new();
private bool _debugEvents = false;
private SequencerClientId _synthRegister;
private SequencerClientId _debugRegister;
public IClydeBufferedAudioSource Source { get; set; }
IClydeBufferedAudioSource IMidiRenderer.Source => Source;
[ViewVariables]
public bool Disposed { get; private set; } = false;
[ViewVariables(VVAccess.ReadWrite)]
public byte MidiProgram
{
get => _midiProgram;
@@ -203,6 +227,7 @@ namespace Robust.Client.Audio.Midi
}
}
[ViewVariables(VVAccess.ReadWrite)]
public byte MidiBank
{
get => _midiBank;
@@ -216,6 +241,7 @@ namespace Robust.Client.Audio.Midi
}
}
[ViewVariables(VVAccess.ReadWrite)]
public uint MidiSoundfont
{
get => _midiSoundfont;
@@ -229,10 +255,16 @@ namespace Robust.Client.Audio.Midi
}
}
[ViewVariables(VVAccess.ReadWrite)]
public bool DisablePercussionChannel { get; set; } = true;
[ViewVariables(VVAccess.ReadWrite)]
public bool DisableProgramChangeEvent { get; set; } = true;
[ViewVariables(VVAccess.ReadWrite)]
public int PlayerTotalTick => _player?.GetTotalTicks ?? 0;
[ViewVariables(VVAccess.ReadWrite)]
public int PlayerTick
{
get => _player?.CurrentTick ?? 0;
@@ -243,12 +275,19 @@ namespace Robust.Client.Audio.Midi
}
}
[ViewVariables(VVAccess.ReadWrite)]
public uint SequencerTick => _sequencer?.Tick ?? 0;
[ViewVariables(VVAccess.ReadWrite)]
public double SequencerTimeScale => _sequencer?.TimeScale ?? 0;
[ViewVariables(VVAccess.ReadWrite)]
public bool Mono { get; set; }
[ViewVariables]
public MidiRendererStatus Status { get; private set; } = MidiRendererStatus.None;
[ViewVariables(VVAccess.ReadWrite)]
public bool LoopMidi
{
get => _loopMidi;
@@ -260,10 +299,11 @@ namespace Robust.Client.Audio.Midi
}
}
[ViewVariables(VVAccess.ReadWrite)]
public IEntity? TrackingEntity { get; set; } = null;
public EntityCoordinates? TrackingCoordinates { get; set; } = null;
internal bool Free { get; set; } = false;
[ViewVariables(VVAccess.ReadWrite)]
public EntityCoordinates? TrackingCoordinates { get; set; } = null;
internal MidiRenderer(Settings settings, SoundFontLoader soundFontLoader, bool mono = true)
{
@@ -276,6 +316,7 @@ namespace Robust.Client.Audio.Midi
_soundFontLoader = soundFontLoader;
_synth = new Synth(_settings);
_sequencer = new Sequencer(false);
_debugRegister = _sequencer.RegisterClient("honk", DumpSequencerEvent);
_synthRegister = _sequencer.RegisterFluidsynth(_synth);
_synth.AddSoundFontLoader(soundFontLoader);
@@ -285,6 +326,27 @@ namespace Robust.Client.Audio.Midi
Source.StartPlaying();
}
private void DumpSequencerEvent(uint time, SequencerEvent @event)
{
// ReSharper disable once UseStringInterpolation
_midiSawmill.Debug(string.Format(
"{0:D8}: {1} chan:{2:D2} key:{3:D5} bank:{4:D2} ctrl:{5:D5} dur:{6:D5} pitch:{7:D5} prog:{8:D3} val:{9:D5} vel:{10:D5}",
time,
@event.Type.ToString().PadLeft(22),
@event.Channel,
@event.Key,
@event.Bank,
@event.Control,
@event.Duration,
@event.Pitch,
@event.Program,
@event.Value,
@event.Velocity));
@event.Dest = _synthRegister;
_sequencer.SendNow(@event);
}
public bool OpenInput()
{
if (Disposed)
@@ -294,7 +356,11 @@ namespace Robust.Client.Audio.Midi
Status = MidiRendererStatus.Input;
StopAllNotes();
_driver = new MidiDriver(_settings, MidiDriverEventHandler);
lock (_playerStateLock)
{
_driver = new MidiDriver(_settings, MidiDriverEventHandler);
}
return true;
}
@@ -332,8 +398,13 @@ namespace Robust.Client.Audio.Midi
{
if (Status != MidiRendererStatus.Input) return false;
Status = MidiRendererStatus.None;
_driver?.Dispose();
_driver = null;
lock (_playerStateLock)
{
_driver?.Dispose();
_driver = null;
}
StopAllNotes();
return true;
}
@@ -357,7 +428,8 @@ namespace Robust.Client.Audio.Midi
public void StopAllNotes()
{
_synth.AllNotesOff(-1);
lock(_playerStateLock)
_synth.AllNotesOff(-1);
}
public void LoadSoundfont(string filename, bool resetPresets = false)
@@ -372,13 +444,15 @@ namespace Robust.Client.Audio.Midi
public event Action<Shared.Audio.Midi.MidiEvent>? OnMidiEvent;
public event Action? OnMidiPlayerFinished;
internal void Render(int length = SampleRate / 250)
void IMidiRenderer.Render()
{
Render();
}
private void Render(int length = SampleRate / 250)
{
if (Disposed) return;
// SSE needs this.
DebugTools.Assert(length % 4 == 0, "Sample length must be multiple of 4");
var buffersProcessed = Source.GetNumberOfBuffersProcessed();
if(buffersProcessed == Buffers) _midiSawmill.Warning("MIDI buffer overflow!");
if (buffersProcessed == 0) return;
@@ -393,36 +467,16 @@ namespace Robust.Client.Audio.Midi
Source.GetBuffersProcessed(buffers);
lock (_playerStateLock)
{
// _sequencer.Process(10);
_synth?.WriteSampleFloat(length * buffers.Length, audio, 0, Mono ? 1 : 2,
audio, Mono ? length * buffers.Length : 1, Mono ? 1 : 2);
}
if (Mono) // Turn audio to mono
{
var l = length * buffers.Length;
if (Sse.IsSupported)
{
fixed (float* ptr = audio)
{
for (var j = 0; j < l; j += 4)
{
var k = j + l;
var jV = Sse.LoadVector128(ptr + j);
var kV = Sse.LoadVector128(ptr + k);
Sse.Store(j + ptr, Sse.Add(jV, kV));
}
}
}
else
{
for (var j = 0; j < l; j++)
{
var k = j + l;
audio[j] = ((audio[k] + audio[j]));
}
}
NumericsHelpers.Add(audio[..l], audio[l..]);
}
for (var i = 0; i < buffers.Length; i++)
@@ -452,6 +506,7 @@ namespace Robust.Client.Audio.Midi
var timestamp = SequencerTick;
var midiEv = (Shared.Audio.Midi.MidiEvent) midiEvent;
midiEv.Tick = timestamp;
midiEvent.Dispose();
SendMidiEvent(midiEv);
return 0;
}
@@ -462,6 +517,7 @@ namespace Robust.Client.Audio.Midi
var timestamp = SequencerTick;
var midiEv = (Shared.Audio.Midi.MidiEvent) midiEvent;
midiEv.Tick = timestamp;
midiEvent.Dispose();
SendMidiEvent(midiEv);
return 0;
}
@@ -478,16 +534,16 @@ namespace Robust.Client.Audio.Midi
lock(_playerStateLock)
switch (midiEvent.Type)
{
// Note On 0x80
case 144:
_synth.NoteOn(midiEvent.Channel, midiEvent.Key, midiEvent.Velocity);
break;
// Note Off - 0x90
// Note Off - 0x80
case 128:
_synth.NoteOff(midiEvent.Channel, midiEvent.Key);
break;
// Note On 0x90
case 144:
_synth.NoteOn(midiEvent.Channel, midiEvent.Key, midiEvent.Velocity);
break;
// After Touch - 0xA
case 160:
_synth.KeyPressure(midiEvent.Channel, midiEvent.Key, midiEvent.Value);
@@ -522,6 +578,12 @@ namespace Robust.Client.Audio.Midi
case 81:
// System Messages - 0xF0
case 240:
switch (midiEvent.Control)
{
case 11:
_synth.AllNotesOff(midiEvent.Channel);
break;
}
return;
default:
@@ -543,7 +605,7 @@ namespace Robust.Client.Audio.Midi
if (Disposed) return;
var seqEv = (SequencerEvent) midiEvent;
seqEv.Dest = _synthRegister;
seqEv.Dest = _debugEvents ? _debugRegister : _synthRegister;
_sequencer.SendAt(seqEv, time, absolute);
}
@@ -566,10 +628,15 @@ namespace Robust.Client.Audio.Midi
void IMidiRenderer.InternalDispose()
{
Source?.Dispose();
_driver?.Dispose();
// Do NOT dispose of the sequencer after the synth or it'll cause a segfault for some fucking reason.
_sequencer?.UnregisterClient(_debugRegister);
_sequencer?.UnregisterClient(_synthRegister);
_sequencer?.Dispose();
_synth?.Dispose();
_player?.Dispose();
_driver?.Dispose();
_sequencer?.Dispose();
}
}
}

View File

@@ -13,6 +13,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -25,6 +26,7 @@ namespace Robust.Client
[Dependency] private readonly IPlayerManager _playMan = default!;
[Dependency] private readonly INetConfigurationManager _configManager = default!;
[Dependency] private readonly IClientEntityManager _entityManager = default!;
[Dependency] private readonly IEntityLookup _entityLookup = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IDiscordRichPresence _discord = default!;
[Dependency] private readonly IGameTiming _timing = default!;
@@ -165,7 +167,7 @@ namespace Robust.Client
/// receiving states when they join the lobby.
/// </summary>
/// <param name="session">Session of the player.</param>
private void OnPlayerJoinedServer(IPlayerSession session)
private void OnPlayerJoinedServer(ICommonSession session)
{
DebugTools.Assert(RunLevel < ClientRunLevel.Connected);
OnRunLevelChanged(ClientRunLevel.Connected);
@@ -175,20 +177,11 @@ namespace Robust.Client
PlayerJoinedServer?.Invoke(this, new PlayerEventArgs(session));
}
private void GameStartedSetup()
{
_entityManager.Startup();
_mapManager.Startup();
_timing.ResetSimTime();
_timing.Paused = false;
}
/// <summary>
/// Player is joining the game
/// </summary>
/// <param name="session">Session of the player.</param>
private void OnPlayerJoinedGame(IPlayerSession session)
private void OnPlayerJoinedGame(ICommonSession session)
{
DebugTools.Assert(RunLevel >= ClientRunLevel.Connected);
OnRunLevelChanged(ClientRunLevel.InGame);
@@ -218,12 +211,22 @@ namespace Robust.Client
GameStoppedReset();
}
private void GameStartedSetup()
{
_entityManager.Startup();
_mapManager.Startup();
_entityLookup.Startup();
_timing.ResetSimTime();
_timing.Paused = false;
}
private void GameStoppedReset()
{
IoCManager.Resolve<INetConfigurationManager>().FlushMessages();
_gameStates.Reset();
_playMan.Shutdown();
IoCManager.Resolve<IEntityLookup>().Shutdown();
_entityLookup.Shutdown();
_entityManager.Shutdown();
_mapManager.Shutdown();
_discord.ClearPresence();
@@ -295,12 +298,12 @@ namespace Robust.Client
/// <summary>
/// The session that triggered the event.
/// </summary>
private IPlayerSession? Session { get; }
private ICommonSession? Session { get; }
/// <summary>
/// Constructs a new instance of the class.
/// </summary>
public PlayerEventArgs(IPlayerSession? session)
public PlayerEventArgs(ICommonSession? session)
{
Session = session;
}

View File

@@ -45,14 +45,16 @@ namespace Robust.Client
IoCManager.Register<IMapManagerInternal, ClientMapManager>();
IoCManager.Register<IClientMapManager, ClientMapManager>();
IoCManager.Register<IEntityManager, ClientEntityManager>();
IoCManager.Register<IEntityLookup, SharedEntityLookup>();
IoCManager.Register<IEntityLookup, EntityLookup>();
IoCManager.Register<IReflectionManager, ClientReflectionManager>();
IoCManager.Register<IConsoleHost, ClientConsoleHost>();
IoCManager.Register<IClientConsoleHost, ClientConsoleHost>();
IoCManager.Register<IComponentFactory, ClientComponentFactory>();
IoCManager.Register<ITileDefinitionManager, ClydeTileDefinitionManager>();
IoCManager.Register<IClydeTileDefinitionManager, ClydeTileDefinitionManager>();
IoCManager.Register<GameController, GameController>();
IoCManager.Register<IGameController, GameController>();
IoCManager.Register<IGameControllerInternal, GameController>();
IoCManager.Register<IReflectionManager, ClientReflectionManager>();
IoCManager.Register<IResourceManager, ResourceCache>();
IoCManager.Register<IResourceManagerInternal, ResourceCache>();
IoCManager.Register<IResourceCache, ResourceCache>();
@@ -72,8 +74,6 @@ namespace Robust.Client
IoCManager.Register<IDebugDrawingManager, DebugDrawingManager>();
IoCManager.Register<ILightManager, LightManager>();
IoCManager.Register<IDiscordRichPresence, DiscordRichPresence>();
IoCManager.Register<IClientConsoleHost, ClientConsoleHost>();
IoCManager.Register<IConsoleHost, ClientConsoleHost>();
IoCManager.Register<IMidiManager, MidiManager>();
IoCManager.Register<IAuthManager, AuthManager>();
switch (mode)

View File

@@ -17,6 +17,7 @@ namespace Robust.Client
public bool Launcher { get; }
public string? Username { get; }
public IReadOnlyCollection<(string key, string value)> CVars { get; }
public IReadOnlyCollection<(string key, string value)> LogLevels { get; }
// 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?
@@ -31,6 +32,7 @@ namespace Robust.Client
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();
@@ -124,6 +126,26 @@ namespace Robust.Client
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();
@@ -142,6 +164,7 @@ namespace Robust.Client
launcher,
username,
cvars,
logLevels,
connectAddress,
ss14Address,
mountOptions);
@@ -162,6 +185,7 @@ Options:
--launcher Run in launcher mode (no main menu, auto connect).
--username Override username.
--cvar Specifies an additional cvar overriding the config file. Syntax is <key>=<value>
--loglevel Specifies an additional sawmill log level overriding the default values. Syntax is <key>=<value>
--mount-dir Resource directory to mount.
--mount-zip Resource zip to mount.
--help Display this help text and exit.
@@ -175,6 +199,7 @@ Options:
bool launcher,
string? username,
IReadOnlyCollection<(string key, string value)> cVars,
IReadOnlyCollection<(string key, string value)> logLevels,
string connectAddress, string? ss14Address,
MountOptions mountOptions)
{
@@ -184,6 +209,7 @@ Options:
Launcher = launcher;
Username = username;
CVars = cVars;
LogLevels = logLevels;
ConnectAddress = connectAddress;
Ss14Address = ss14Address;
MountOptions = mountOptions;

View File

@@ -36,7 +36,7 @@ namespace Robust.Client.Console
Message = message;
}
}
/// <inheritdoc cref="IClientConsoleHost" />
internal class ClientConsoleHost : ConsoleHost, IClientConsoleHost
{
@@ -45,11 +45,15 @@ namespace Robust.Client.Console
/// <inheritdoc />
public void Initialize()
{
NetManager.RegisterNetMessage<MsgConCmdReg>(MsgConCmdReg.NAME, HandleConCmdReg);
NetManager.RegisterNetMessage<MsgConCmdAck>(MsgConCmdAck.NAME, HandleConCmdAck);
NetManager.RegisterNetMessage<MsgConCmd>(MsgConCmd.NAME, ProcessCommand);
NetManager.RegisterNetMessage<MsgConCmdReg>(HandleConCmdReg);
NetManager.RegisterNetMessage<MsgConCmdAck>(HandleConCmdAck);
NetManager.RegisterNetMessage<MsgConCmd>(ProcessCommand);
Reset();
_requestedCommands = false;
NetManager.Connected += OnNetworkConnected;
LoadConsoleCommands();
SendServerCommandRequest();
LogManager.RootSawmill.AddHandler(new DebugConsoleLogHandler(this));
}
@@ -61,17 +65,6 @@ namespace Robust.Client.Console
ExecuteCommand(null, text);
}
/// <inheritdoc />
public void Reset()
{
AvailableCommands.Clear();
_requestedCommands = false;
NetManager.Connected += OnNetworkConnected;
LoadConsoleCommands();
SendServerCommandRequest();
}
/// <inheritdoc />
public event EventHandler<AddStringArgs>? AddString;
@@ -97,7 +90,7 @@ namespace Robust.Client.Console
return;
// echo the command locally
WriteError(null, "> " + command);
WriteLine(null, "> " + command);
//Commands are processed locally and then sent to the server to be processed there again.
var args = new List<string>();
@@ -142,6 +135,9 @@ namespace Robust.Client.Console
private void OutputText(string text, bool local, bool error)
{
AddString?.Invoke(this, new AddStringArgs(text, local, error));
var level = error ? LogLevel.Warning : LogLevel.Info;
Logger.LogS(level, "CON", text);
}
private void OnNetworkConnected(object? sender, NetChannelArgs netChannelArgs)

View File

@@ -77,7 +77,7 @@ namespace Robust.Client.Console.Commands
message.Append($"net ID: {registration.NetID}");
}
message.Append($", NSE: {registration.NetworkSynchronizeExistence}, references:");
message.Append($", References:");
shell.WriteLine(message.ToString());

View File

@@ -0,0 +1,19 @@
#if DEBUG
using Robust.Client.GameObjects;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
namespace Robust.Client.Console.Commands
{
internal sealed class LightDebugCommand : IConsoleCommand
{
public string Command => "lightbb";
public string Description => "Toggles whether to show light bounding boxes";
public string Help => $"{Command}";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
EntitySystem.Get<DebugLightTreeSystem>().Enabled ^= true;
}
}
}
#endif

View File

@@ -25,6 +25,34 @@ namespace Robust.Client.Console.Commands
}
}
[UsedImplicitly]
public sealed class MonitorInfoCommand : IConsoleCommand
{
public string Command => "monitorinfo";
public string Description => "";
public string Help => "Usage: monitorinfo <id>";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length < 1)
{
shell.WriteError("Expected one argument.");
return;
}
var clyde = IoCManager.Resolve<IClyde>();
var monitor = clyde.EnumerateMonitors().Single(c => c.Id == int.Parse(args[0]));
shell.WriteLine($"{monitor.Id}: {monitor.Name}");
shell.WriteLine($"Video modes:");
foreach (var mode in monitor.VideoModes)
{
shell.WriteLine($" {mode.Width}x{mode.Height} {mode.RefreshRate} Hz {mode.RedBits}/{mode.GreenBits}/{mode.BlueBits}");
}
}
}
[UsedImplicitly]
public sealed class SetMonitorCommand : IConsoleCommand
{

View File

@@ -1,6 +1,5 @@
using Robust.Shared.Console;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
namespace Robust.Client.Console.Commands
{
@@ -41,7 +40,7 @@ namespace Robust.Client.Console.Commands
var mgr = IoCManager.Resolve<IScriptClient>();
if (!mgr.CanScript)
{
shell.WriteError(Loc.GetString("You do not have server side scripting permission."));
shell.WriteError("You do not have server side scripting permission.");
return;
}

View File

@@ -11,11 +11,6 @@ namespace Robust.Client.Console
/// </summary>
void Initialize();
/// <summary>
/// Resets the console to a post-initialized state.
/// </summary>
void Reset();
event EventHandler<AddStringArgs> AddString;
event EventHandler<AddFormattedMessageArgs> AddFormatted;

View File

@@ -17,11 +17,11 @@ namespace Robust.Client.Console
public void Initialize()
{
_netManager.RegisterNetMessage<MsgScriptStop>(MsgScriptStop.NAME);
_netManager.RegisterNetMessage<MsgScriptEval>(MsgScriptEval.NAME);
_netManager.RegisterNetMessage<MsgScriptStart>(MsgScriptStart.NAME);
_netManager.RegisterNetMessage<MsgScriptResponse>(MsgScriptResponse.NAME, ReceiveScriptResponse);
_netManager.RegisterNetMessage<MsgScriptStartAck>(MsgScriptStartAck.NAME, ReceiveScriptStartAckResponse);
_netManager.RegisterNetMessage<MsgScriptStop>();
_netManager.RegisterNetMessage<MsgScriptEval>();
_netManager.RegisterNetMessage<MsgScriptStart>();
_netManager.RegisterNetMessage<MsgScriptResponse>(ReceiveScriptResponse);
_netManager.RegisterNetMessage<MsgScriptStartAck>(ReceiveScriptStartAckResponse);
}
private void ReceiveScriptStartAckResponse(MsgScriptStartAck message)
@@ -30,7 +30,8 @@ namespace Robust.Client.Console
var console = new ScriptConsoleServer(this, session);
_activeConsoles.Add(session, console);
console.Open();
// FIXME: When this is Open(), resizing the window will cause its position to get NaN'd.
console.OpenCentered();
}
private void ReceiveScriptResponse(MsgScriptResponse message)

View File

@@ -1,5 +1,4 @@
#if CLIENT_SCRIPTING
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
@@ -14,7 +13,6 @@ using Microsoft.CodeAnalysis.Text;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.ViewVariables;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Reflection;
using Robust.Shared.Scripting;
@@ -43,14 +41,14 @@ namespace Robust.Client.Console
public ScriptConsoleClient()
{
Title = Loc.GetString("Robust C# Interactive (CLIENT)");
Title = "Robust C# Interactive (CLIENT)";
ScriptInstanceShared.InitDummy();
_globals = new ScriptGlobalsImpl(this);
IoCManager.InjectDependencies(this);
OutputPanel.AddText(Loc.GetString(@"Robust C# interactive console (CLIENT)."));
OutputPanel.AddText("Robust C# interactive console (CLIENT).");
OutputPanel.AddText(">");
}

View File

@@ -8,8 +8,6 @@ using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Reflection;
using Robust.Shared.Scripting;
using Robust.Shared.Timing;
@@ -31,7 +29,7 @@ namespace Robust.Client.Console
ScriptInstanceShared.InitDummy();
Title = Loc.GetString("Watch Window");
Title = "Watch Window";
var mainVBox = new VBoxContainer
{
@@ -49,11 +47,11 @@ namespace Robust.Client.Console
(_addWatchEdit = new HistoryLineEdit
{
HorizontalExpand = true,
PlaceHolder = Loc.GetString("Add watch (C# interactive)")
PlaceHolder = "Add watch (C# interactive)"
}),
(_addWatchButton = new Button
{
Text = Loc.GetString("Add")
Text = "Add"
})
}
}
@@ -118,7 +116,7 @@ namespace Robust.Client.Console
}),
(delButton = new Button
{
Text = Loc.GetString("Remove")
Text = "Remove"
}),
}
});
@@ -178,7 +176,7 @@ namespace Robust.Client.Console
ClipText = true,
HorizontalExpand = true
},
(delButton = new Button {Text = Loc.GetString("Remove")})
(delButton = new Button {Text = "Remove"})
}
});

View File

@@ -7,13 +7,13 @@ namespace Robust.Client
#if FULL_RELEASE
throw new System.InvalidOperationException("ContentStart.Start is not available on a full release.");
#else
GameController.Start(args, true);
GameController.Start(args, new GameControllerOptions(), true);
#endif
}
public static void StartLibrary(string[] args, GameControllerOptions options)
{
GameController.Start(args, true, null, options);
GameController.Start(args, options, true, null);
}
}
}

View File

@@ -23,7 +23,7 @@ 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 IEntityManager _entityManager = default!;
[Dependency] private readonly IEntityLookup _lookup = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
@@ -70,7 +70,7 @@ namespace Robust.Client.Debugging
if (value && !_overlayManager.HasOverlay<EntityPositionOverlay>())
{
_overlayManager.AddOverlay(new EntityPositionOverlay(_entityManager, _eyeManager));
_overlayManager.AddOverlay(new EntityPositionOverlay(_lookup, _eyeManager));
}
else
{
@@ -130,17 +130,17 @@ namespace Robust.Client.Debugging
{
if (body != _hoverBodies[0])
{
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), "------");
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), "------");
row++;
}
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), $"Ent: {body.Owner}");
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Ent: {body.Owner}");
row++;
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), $"Layer: {Convert.ToString(body.CollisionLayer, 2)}");
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Layer: {Convert.ToString(body.CollisionLayer, 2)}");
row++;
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), $"Mask: {Convert.ToString(body.CollisionMask, 2)}");
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Mask: {Convert.ToString(body.CollisionMask, 2)}");
row++;
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), $"Enabled: {body.CanCollide}, Hard: {body.Hard}, Anchored: {(body).BodyType == BodyType.Static}");
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Enabled: {body.CanCollide}, Hard: {body.Hard}, Anchored: {(body).BodyType == BodyType.Static}");
row++;
}
@@ -171,14 +171,15 @@ namespace Robust.Client.Debugging
// all entities have a TransformComponent
var transform = physBody.Owner.Transform;
var worldBox = physBody.GetWorldAABB(_mapManager);
var worldBox = physBody.GetWorldAABB();
if (worldBox.IsEmpty()) continue;
foreach (var fixture in physBody.Fixtures)
{
var shape = fixture.Shape;
var sleepPercent = physBody.Awake ? physBody.SleepTime / sleepThreshold : 1.0f;
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)
@@ -187,6 +188,7 @@ namespace Robust.Client.Debugging
drawnJoints.Add(joint);
joint.DebugDraw(drawing, in viewport);
drawing.SetTransform(in Matrix3.Identity);
}
if (worldBox.Contains(mouseWorldPos))
@@ -199,17 +201,6 @@ namespace Robust.Client.Debugging
}
}
private static void DrawString(DrawingHandleScreen handle, Font font, Vector2 pos, string str)
{
var baseLine = new Vector2(pos.X, font.GetAscent(1) + pos.Y);
foreach (var rune in str.EnumerateRunes())
{
var advance = font.DrawChar(handle, rune, baseLine, 1, Color.White);
baseLine += new Vector2(advance, 0);
}
}
private class PhysDrawingAdapter : DebugDrawingHandle
{
private readonly DrawingHandleWorld _handle;
@@ -270,14 +261,14 @@ namespace Robust.Client.Debugging
private sealed class EntityPositionOverlay : Overlay
{
private readonly IEntityManager _entityManager;
private readonly IEntityLookup _lookup;
private readonly IEyeManager _eyeManager;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public EntityPositionOverlay(IEntityManager entityManager, IEyeManager eyeManager)
public EntityPositionOverlay(IEntityLookup lookup, IEyeManager eyeManager)
{
_entityManager = entityManager;
_lookup = lookup;
_eyeManager = eyeManager;
}
@@ -286,18 +277,17 @@ namespace Robust.Client.Debugging
const float stubLength = 0.25f;
var worldHandle = (DrawingHandleWorld) args.DrawingHandle;
foreach (var entity in _entityManager.GetEntities())
var viewport = _eyeManager.GetWorldViewport();
foreach (var entity in _lookup.GetEntitiesIntersecting(_eyeManager.CurrentMap, viewport))
{
var transform = entity.Transform;
if (transform.MapID != _eyeManager.CurrentMap ||
!_eyeManager.GetWorldViewport().Contains(transform.WorldPosition))
{
continue;
}
var center = transform.WorldPosition;
var xLine = transform.WorldRotation.RotateVec(Vector2.UnitX);
var yLine = transform.WorldRotation.RotateVec(Vector2.UnitY);
var worldRotation = transform.WorldRotation;
var xLine = worldRotation.RotateVec(Vector2.UnitX);
var yLine = worldRotation.RotateVec(Vector2.UnitY);
worldHandle.DrawLine(center, center + xLine * stubLength, Color.Red);
worldHandle.DrawLine(center, center + yLine * stubLength, Color.Green);

View File

@@ -1,12 +1,12 @@
using Robust.Shared.IoC;
using Robust.Shared.Network.Messages;
using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
using Robust.Shared.Enums;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Timing;
namespace Robust.Client.Debugging
{
@@ -54,7 +54,7 @@ namespace Robust.Client.Debugging
public void Initialize()
{
_net.RegisterNetMessage<MsgRay>(MsgRay.NAME, HandleDrawRay);
_net.RegisterNetMessage<MsgRay>(HandleDrawRay);
}
private void HandleDrawRay(MsgRay msg)

View File

@@ -11,7 +11,7 @@ namespace Robust.Client
{
public void Main(IMainArgs args)
{
Start(args.Args, contentStart: false, args);
Start(args.Args, new GameControllerOptions(), contentStart: false, args);
}
}
}

View File

@@ -22,10 +22,10 @@ namespace Robust.Client
public static void Main(string[] args)
{
Start(args);
Start(args, new GameControllerOptions());
}
public static void Start(string[] args, bool contentStart = false, IMainArgs? loaderArgs=null, GameControllerOptions? options = null)
public static void Start(string[] args, GameControllerOptions options, bool contentStart = false, IMainArgs? loaderArgs=null)
{
if (_hasStarted)
{
@@ -40,7 +40,7 @@ namespace Robust.Client
}
}
private static void ParsedMain(CommandLineArgs args, bool contentStart, IMainArgs? loaderArgs, GameControllerOptions? options)
private static void ParsedMain(CommandLineArgs args, bool contentStart, IMainArgs? loaderArgs, GameControllerOptions options)
{
IoCManager.InitThread();
@@ -51,15 +51,13 @@ namespace Robust.Client
var gc = IoCManager.Resolve<GameController>();
gc.SetCommandLineArgs(args);
gc._loaderArgs = loaderArgs;
if(options != null)
gc.Options = options;
// When the game is ran with the startup executable being content,
// we have to disable the separate load context.
// Otherwise the content assemblies will be loaded twice which causes *many* fun bugs.
gc.ContentStart = contentStart;
gc.Run(mode);
gc.Run(mode, options);
}
public void OverrideMainLoop(IGameLoop gameLoop)
@@ -67,9 +65,9 @@ namespace Robust.Client
_mainLoop = gameLoop;
}
public void Run(DisplayMode mode, Func<ILogHandler>? logHandlerFactory = null)
public void Run(DisplayMode mode, GameControllerOptions options, Func<ILogHandler>? logHandlerFactory = null)
{
if (!StartupSystemSplash(logHandlerFactory))
if (!StartupSystemSplash(options, logHandlerFactory))
{
Logger.Fatal("Failed to start game controller!");
return;

View File

@@ -75,14 +75,12 @@ namespace Robust.Client
public GameControllerOptions Options { get; private set; } = new();
public InitialLaunchState LaunchState { get; private set; } = default!;
public bool LoadConfigAndUserData { get; set; } = true;
public void SetCommandLineArgs(CommandLineArgs args)
{
_commandLineArgs = args;
}
private bool StartupContinue(DisplayMode displayMode)
internal bool StartupContinue(DisplayMode displayMode)
{
_clyde.InitializePostWindowing();
_clyde.SetWindowTitle(Options.DefaultWindowTitle);
@@ -95,7 +93,7 @@ namespace Robust.Client
_modLoader.SetUseLoadContext(!ContentStart);
_modLoader.SetEnableSandboxing(Options.Sandboxing);
if (!_modLoader.TryLoadModulesFrom(new ResourcePath("/Assemblies/"), Options.ContentModulePrefix))
if (!_modLoader.TryLoadModulesFrom(Options.AssemblyDirectory, Options.ContentModulePrefix))
{
Logger.Fatal("Errors while loading content assemblies.");
return false;
@@ -125,7 +123,6 @@ namespace Robust.Client
_prototypeManager.Resync();
_mapManager.Initialize();
_entityManager.Initialize();
IoCManager.Resolve<IEntityLookup>().Initialize();
_gameStateManager.Initialize();
_placementManager.Initialize();
_viewVariablesManager.Initialize();
@@ -197,12 +194,35 @@ namespace Robust.Client
return true;
}
private bool StartupSystemSplash(Func<ILogHandler>? logHandlerFactory)
internal bool StartupSystemSplash(GameControllerOptions options, Func<ILogHandler>? logHandlerFactory)
{
Options = options;
ReadInitialLaunchState();
SetupLogging(_logManager, logHandlerFactory ?? (() => new ConsoleLogHandler()));
if (_commandLineArgs != null)
{
foreach (var (sawmill, level) in _commandLineArgs.LogLevels)
{
LogLevel? logLevel;
if (level == "null")
logLevel = null;
else
{
if (!Enum.TryParse<LogLevel>(level, out var result))
{
System.Console.WriteLine($"LogLevel {level} does not exist!");
continue;
}
logLevel = result;
}
_logManager.GetSawmill(sawmill).Level = logLevel;
}
}
ProgramShared.PrintRuntimeInfo(_logManager.RootSawmill);
// Figure out user data directory.
var userDataDir = GetUserDataDir();
@@ -213,7 +233,7 @@ namespace Robust.Client
_configurationManager.LoadCVarsFromAssembly(typeof(GameController).Assembly); // Client
_configurationManager.LoadCVarsFromAssembly(typeof(IConfigurationManager).Assembly); // Shared
if (LoadConfigAndUserData)
if (Options.LoadConfigAndUserData)
{
var configFile = Path.Combine(userDataDir, Options.ConfigFileName);
if (File.Exists(configFile))
@@ -237,13 +257,13 @@ namespace Robust.Client
ProfileOptSetup.Setup(_configurationManager);
_resourceCache.Initialize(LoadConfigAndUserData ? userDataDir : null);
_resourceCache.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
var mountOptions = _commandLineArgs != null
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions) : Options.MountOptions;
ProgramShared.DoMounts(_resourceCache, mountOptions, Options.ContentBuildDirectory,
_loaderArgs != null && !Options.ResourceMountDisabled, ContentStart);
ProgramShared.DoMounts(_resourceCache, mountOptions, Options.ContentBuildDirectory, Options.AssemblyDirectory,
Options.LoadContentResources, _loaderArgs != null && !Options.ResourceMountDisabled, ContentStart);
if (_loaderArgs != null)
{
@@ -450,7 +470,7 @@ namespace Robust.Client
Clyde,
}
private void Cleanup()
internal void Cleanup()
{
_networkManager.Shutdown("Client shutting down");
_midiManager.Shutdown();

View File

@@ -42,6 +42,11 @@ namespace Robust.Client
/// </summary>
public string ContentBuildDirectory { get; init; } = "Content.Client";
/// <summary>
/// Directory to load all assemblies from.
/// </summary>
public ResourcePath AssemblyDirectory { get; init; } = new(@"/Assemblies/");
/// <summary>
/// Directory to load all prototypes from.
/// </summary>
@@ -52,6 +57,16 @@ namespace Robust.Client
/// </summary>
public bool ResourceMountDisabled { get; init; } = false;
/// <summary>
/// Whether to mount content resources when not on FULL_RELEASE.
/// </summary>
public bool LoadContentResources { get; init; } = true;
/// <summary>
/// Whether to load config and user data.
/// </summary>
public bool LoadConfigAndUserData { get; init; } = true;
/// <summary>
/// Whether to disable command line args server auto-connecting.
/// </summary>

View File

@@ -1,66 +1,41 @@
using Robust.Shared.Console;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.Physics;
using Robust.Shared.IoC;
using Robust.Shared.Reflection;
namespace Robust.Client.GameObjects
{
public class ClientComponentFactory : ComponentFactory
internal class ClientComponentFactory : ComponentFactory
{
public ClientComponentFactory()
public ClientComponentFactory(IDynamicTypeFactoryInternal typeFactory, IReflectionManager reflectionManager, IConsoleHost conHost)
: base(typeFactory, reflectionManager, conHost)
{
// Required for the engine to work
Register<MetaDataComponent>();
RegisterReference<MetaDataComponent, IMetaDataComponent>();
// Required for the engine to work
Register<TransformComponent>();
RegisterReference<TransformComponent, ITransformComponent>();
Register<MapComponent>();
RegisterReference<MapComponent, IMapComponent>();
Register<MapGridComponent>();
RegisterReference<MapGridComponent, IMapGridComponent>();
Register<PhysicsComponent>();
RegisterReference<PhysicsComponent, IPhysBody>();
Register<CollisionWakeComponent>();
Register<ContainerManagerComponent>();
RegisterReference<ContainerManagerComponent, IContainerManager>();
RegisterIgnore("KeyBindingInput");
Register<InputComponent>();
Register<SpriteComponent>();
RegisterReference<SpriteComponent, SharedSpriteComponent>();
RegisterReference<SpriteComponent, ISpriteComponent>();
Register<ClientOccluderComponent>();
RegisterReference<ClientOccluderComponent, OccluderComponent>();
Register<EyeComponent>();
RegisterReference<EyeComponent, SharedEyeComponent>();
Register<AppearanceComponent>();
RegisterReference<AppearanceComponent, SharedAppearanceComponent>();
Register<AppearanceTestComponent>();
Register<SnapGridComponent>();
Register<ClientUserInterfaceComponent>();
RegisterReference<ClientUserInterfaceComponent, SharedUserInterfaceComponent>();
Register<AnimationPlayerComponent>();
Register<TimerComponent>();
RegisterClass<MetaDataComponent>();
RegisterClass<TransformComponent>();
RegisterClass<MapComponent>();
RegisterClass<MapGridComponent>();
RegisterClass<PhysicsComponent>();
RegisterClass<CollisionWakeComponent>();
RegisterClass<ClientUserInterfaceComponent>();
RegisterClass<ContainerManagerComponent>();
RegisterClass<InputComponent>();
RegisterClass<SpriteComponent>();
RegisterClass<ClientOccluderComponent>();
RegisterClass<EyeComponent>();
RegisterClass<AppearanceComponent>();
RegisterClass<AppearanceTestComponent>();
RegisterClass<SnapGridComponent>();
RegisterClass<AnimationPlayerComponent>();
RegisterClass<TimerComponent>();
#if DEBUG
Register<DebugExceptionOnAddComponent>();
Register<DebugExceptionInitializeComponent>();
Register<DebugExceptionStartupComponent>();
RegisterClass<DebugExceptionOnAddComponent>();
RegisterClass<DebugExceptionInitializeComponent>();
RegisterClass<DebugExceptionStartupComponent>();
#endif
}

View File

@@ -1,14 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Prometheus;
using Robust.Client.GameStates;
using Robust.Shared.Exceptions;
using Robust.Client.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Timing;
@@ -21,6 +17,7 @@ namespace Robust.Client.GameObjects
/// </summary>
public sealed class ClientEntityManager : EntityManager, IClientEntityManagerInternal
{
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IClientNetManager _networkManager = default!;
[Dependency] private readonly IClientGameStateManager _gameStateManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
@@ -67,7 +64,7 @@ namespace Robust.Client.GameObjects
/// <inheritdoc />
public void SetupNetworking()
{
_networkManager.RegisterNetMessage<MsgEntity>(MsgEntity.NAME, HandleEntityNetworkMessage);
_networkManager.RegisterNetMessage<MsgEntity>(HandleEntityNetworkMessage);
}
public override void TickUpdate(float frameTime, Histogram? histogram)
@@ -109,15 +106,18 @@ namespace Robust.Client.GameObjects
}
/// <inheritdoc />
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
public void SendComponentNetworkMessage(INetChannel? channel, IEntity entity, IComponent component, ComponentMessage message)
{
if (!component.NetID.HasValue)
var netId = ComponentFactory.GetRegistration(component.GetType()).NetID;
if (!netId.HasValue)
throw new ArgumentException($"Component {component.Name} does not have a NetID.", nameof(component));
var msg = _networkManager.CreateNetMessage<MsgEntity>();
msg.Type = EntityMessageType.ComponentMessage;
msg.EntityUid = entity.Uid;
msg.NetId = component.NetID.Value;
msg.NetId = netId.Value;
msg.ComponentMessage = message;
msg.SourceTick = _gameTiming.CurTick;
@@ -147,7 +147,11 @@ namespace Robust.Client.GameObjects
return;
case EntityMessageType.SystemMessage:
ReceivedSystemMessage?.Invoke(this, message.SystemMessage);
var msg = message.SystemMessage;
var sessionType = typeof(EntitySessionMessage<>).MakeGenericType(msg.GetType());
var sessionMsg = Activator.CreateInstance(sessionType, new EntitySessionEventArgs(_playerManager.LocalPlayer!.Session), msg)!;
ReceivedSystemMessage?.Invoke(this, msg);
ReceivedSystemMessage?.Invoke(this, sessionMsg);
return;
}
}

View File

@@ -10,6 +10,7 @@ using YamlDotNet.RepresentationModel;
namespace Robust.Client.GameObjects
{
[ComponentReference(typeof(SharedAppearanceComponent))]
public sealed class AppearanceComponent : SharedAppearanceComponent
{
[ViewVariables]
@@ -103,7 +104,7 @@ namespace Robust.Client.GameObjects
_appearanceDirty = false;
}
public override void Initialize()
protected override void Initialize()
{
base.Initialize();

View File

@@ -10,6 +10,7 @@ using Robust.Shared.ViewVariables;
namespace Robust.Client.GameObjects
{
[ComponentReference(typeof(SharedEyeComponent))]
public class EyeComponent : SharedEyeComponent
{
[Dependency] private readonly IEyeManager _eyeManager = default!;
@@ -115,7 +116,7 @@ namespace Robust.Client.GameObjects
public MapCoordinates? Position => _eye?.Position;
/// <inheritdoc />
public override void Initialize()
protected override void Initialize()
{
base.Initialize();
@@ -155,7 +156,7 @@ namespace Robust.Client.GameObjects
VisibilityMask = state.VisibilityMask;
}
public override void OnRemove()
protected override void OnRemove()
{
base.OnRemove();

View File

@@ -8,10 +8,11 @@ using Robust.Shared.ViewVariables;
namespace Robust.Client.GameObjects
{
[ComponentReference(typeof(OccluderComponent))]
internal sealed class ClientOccluderComponent : OccluderComponent
{
[Dependency] private readonly IMapManager _mapManager = default!;
[ViewVariables] private (GridId, Vector2i) _lastPosition;
[ViewVariables] internal OccluderDir Occluding { get; private set; }
[ViewVariables] internal uint UpdateGeneration { get; set; }
@@ -33,11 +34,11 @@ namespace Robust.Client.GameObjects
if (Owner.Transform.Anchored)
{
SnapGridOnPositionChanged();
AnchorStateChanged();
}
}
public void SnapGridOnPositionChanged()
public void AnchorStateChanged()
{
SendDirty();

View File

@@ -1,9 +1,13 @@
using System;
using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Animations;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
@@ -14,12 +18,12 @@ namespace Robust.Client.GameObjects
{
[RegisterComponent]
[ComponentReference(typeof(IPointLightComponent))]
[NetworkedComponent()]
public class PointLightComponent : Component, IPointLightComponent, ISerializationHooks
{
[Dependency] private readonly IResourceCache _resourceCache = default!;
public override string Name => "PointLight";
public override uint? NetID => NetIDs.POINT_LIGHT;
internal bool TreeUpdateQueued { get; set; }
@@ -43,11 +47,29 @@ namespace Robust.Client.GameObjects
public bool Enabled
{
get => _enabled;
set => _enabled = value;
set
{
if (_enabled == value) return;
_enabled = value;
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new PointLightUpdateEvent());
}
}
[ViewVariables(VVAccess.ReadWrite)]
public bool ContainerOccluded { get; set; }
public bool ContainerOccluded
{
get => _containerOccluded;
set
{
if (_containerOccluded == value) return;
_containerOccluded = value;
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new PointLightUpdateEvent());
}
}
private bool _containerOccluded;
/// <summary>
/// Determines if the light mask should automatically rotate with the entity. (like a flashlight)
@@ -116,30 +138,13 @@ namespace Robust.Client.GameObjects
public bool VisibleNested
{
get => _visibleNested;
set
{
if (_visibleNested == value) return;
_visibleNested = value;
if (value)
{
if (Owner.Transform.Parent == null) return;
_lightOnParent = true;
}
else
{
if (!_lightOnParent) return;
_lightOnParent = false;
}
}
set => _visibleNested = value;
}
[DataField("radius")]
private float _radius = 5f;
[DataField("nestedvisible")]
private bool _visibleNested = true;
private bool _lightOnParent;
[DataField("color")]
private Color _color = Color.White;
[DataField("offset")]
@@ -166,8 +171,10 @@ namespace Robust.Client.GameObjects
get => _radius;
set
{
if (MathHelper.CloseTo(value, _radius)) return;
_radius = MathF.Max(value, 0.01f); // setting radius to 0 causes exceptions, so just use a value close enough to zero that it's unnoticeable.
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new PointLightRadiusChangedMessage(this));
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new PointLightRadiusChangedEvent(this));
}
}
@@ -179,6 +186,9 @@ namespace Robust.Client.GameObjects
Mask = null;
}
[ViewVariables]
internal RenderingTreeComponent? RenderTree { get; set; }
void ISerializationHooks.AfterDeserialization()
{
if (_maskPath != null)
@@ -187,42 +197,13 @@ namespace Robust.Client.GameObjects
}
}
public override void Initialize()
protected override void Initialize()
{
base.Initialize();
UpdateMask();
}
/// <inheritdoc />
public override void HandleMessage(ComponentMessage message, IComponent? component)
{
base.HandleMessage(message, component);
if (message is ParentChangedMessage msg)
{
HandleTransformParentChanged(msg);
}
}
private void HandleTransformParentChanged(ParentChangedMessage obj)
{
// TODO: this does not work for things nested multiply layers deep.
if (!VisibleNested)
{
return;
}
if (obj.NewParent != null && obj.NewParent.IsValid())
{
_lightOnParent = true;
}
else
{
_lightOnParent = false;
}
}
public override void OnRemove()
protected override void OnRemove()
{
base.OnRemove();
@@ -230,7 +211,7 @@ namespace Robust.Client.GameObjects
if (map != MapId.Nullspace)
{
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local,
new RenderTreeRemoveLightMessage(this, map));
new RenderTreeRemoveLightEvent(this, map));
}
}
@@ -248,13 +229,18 @@ namespace Robust.Client.GameObjects
}
}
public struct PointLightRadiusChangedMessage
public class PointLightRadiusChangedEvent : EntityEventArgs
{
public PointLightComponent PointLightComponent { get; }
public PointLightRadiusChangedMessage(PointLightComponent pointLightComponent)
public PointLightRadiusChangedEvent(PointLightComponent pointLightComponent)
{
PointLightComponent = pointLightComponent;
}
}
internal sealed class PointLightUpdateEvent : EntityEventArgs
{
}
}

View File

@@ -27,6 +27,8 @@ using DrawDepthTag = Robust.Shared.GameObjects.DrawDepth;
namespace Robust.Client.GameObjects
{
[ComponentReference(typeof(SharedSpriteComponent))]
[ComponentReference(typeof(ISpriteComponent))]
public sealed class SpriteComponent : SharedSpriteComponent, ISpriteComponent,
IComponentDebug, ISerializationHooks
{
@@ -40,7 +42,13 @@ namespace Robust.Client.GameObjects
public override bool Visible
{
get => _visible;
set => _visible = value;
set
{
if (_visible == value) return;
_visible = value;
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new SpriteUpdateEvent());
}
}
[DataField("drawdepth", customTypeSerializer: typeof(ConstantSerializer<DrawDepthTag>))]
@@ -123,6 +131,9 @@ namespace Robust.Client.GameObjects
[DataField("directional")]
private bool _directional = true;
[ViewVariables]
internal RenderingTreeComponent? RenderTree { get; set; } = null;
[DataField("layerDatums")]
private List<PrototypeLayerData> LayerDatums
{
@@ -138,7 +149,7 @@ namespace Robust.Client.GameObjects
}
set
{
if(value == null) return;
if (value == null) return;
Layers.Clear();
foreach (var layerDatum in value)
@@ -148,11 +159,12 @@ namespace Robust.Client.GameObjects
if (!string.IsNullOrWhiteSpace(layerDatum.RsiPath))
{
var path = TextureRoot / layerDatum.RsiPath;
try
if (IoCManager.Resolve<IResourceCache>().TryGetResource(path, out RSIResource? resource))
{
layer.RSI = IoCManager.Resolve<IResourceCache>().GetResource<RSIResource>(path).RSI;
layer.RSI = resource.RSI;
}
catch
else
{
Logger.ErrorS(LogCategory, "Unable to load layer RSI '{0}'.", path);
}
@@ -251,7 +263,7 @@ namespace Robust.Client.GameObjects
}
_layerMapShared = true;
UpdateIsInert();
QueueUpdateIsInert();
}
}
@@ -294,17 +306,30 @@ namespace Robust.Client.GameObjects
}
[DataField("sprite", readOnly: true)] private string? rsi;
[DataField("layers", readOnly: true)] private List<PrototypeLayerData> layerDatums = new ();
[DataField("layers", readOnly: true)] private List<PrototypeLayerData> layerDatums = new();
[DataField("state", readOnly: true)] private string? state;
[DataField("texture", readOnly: true)] private string? texture;
[ViewVariables(VVAccess.ReadWrite)]
public bool ContainerOccluded { get; set; }
public bool ContainerOccluded
{
get => _containerOccluded;
set
{
if (_containerOccluded == value) return;
_containerOccluded = value;
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new SpriteUpdateEvent());
}
}
private bool _containerOccluded;
[ViewVariables(VVAccess.ReadWrite)]
public bool TreeUpdateQueued { get; set; }
[ViewVariables(VVAccess.ReadWrite)] private bool _inertUpdateQueued;
[ViewVariables(VVAccess.ReadWrite)]
public ShaderInstance? PostShader { get; set; }
@@ -334,13 +359,13 @@ namespace Robust.Client.GameObjects
if (!string.IsNullOrWhiteSpace(rsi))
{
var rsiPath = TextureRoot / rsi;
try
if(IoCManager.Resolve<IResourceCache>().TryGetResource(rsiPath, out RSIResource? resource))
{
BaseRSI = IoCManager.Resolve<IResourceCache>().GetResource<RSIResource>(rsiPath).RSI;
BaseRSI = resource.RSI;
}
catch (Exception e)
else
{
Logger.ErrorS(SpriteComponent.LogCategory, "Unable to load RSI '{0}'. Trace:\n{1}", rsiPath, e);
Logger.ErrorS(LogCategory, "Unable to load RSI '{0}'. Trace:\n{1}", rsiPath);
}
}
}
@@ -468,7 +493,7 @@ namespace Robust.Client.GameObjects
public int AddBlankLayer(int? newIndex = null)
{
var layer = new Layer(this) {Visible = false};
var layer = new Layer(this) { Visible = false };
return AddLayer(layer, newIndex);
}
@@ -497,13 +522,13 @@ namespace Robust.Client.GameObjects
public int AddLayer(Texture? texture, int? newIndex = null)
{
var layer = new Layer(this) {Texture = texture};
var layer = new Layer(this) { Texture = texture };
return AddLayer(layer, newIndex);
}
public int AddLayer(RSI.StateId stateId, int? newIndex = null)
{
var layer = new Layer(this) {State = stateId};
var layer = new Layer(this) { State = stateId };
if (BaseRSI != null && BaseRSI.TryGetState(stateId, out var state))
{
layer.AnimationTimeLeft = state.GetDelay(0);
@@ -549,7 +574,7 @@ namespace Robust.Client.GameObjects
public int AddLayer(RSI.StateId stateId, RSI? rsi, int? newIndex = null)
{
var layer = new Layer(this) {State = stateId, RSI = rsi};
var layer = new Layer(this) { State = stateId, RSI = rsi };
if (rsi != null && rsi.TryGetState(stateId, out var state))
{
layer.AnimationTimeLeft = state.GetDelay(0);
@@ -605,7 +630,7 @@ namespace Robust.Client.GameObjects
index = Layers.Count - 1;
}
UpdateIsInert();
QueueUpdateIsInert();
return index;
}
@@ -632,7 +657,7 @@ namespace Robust.Client.GameObjects
}
}
UpdateIsInert();
QueueUpdateIsInert();
}
public void RemoveLayer(object layerKey)
@@ -1274,8 +1299,8 @@ namespace Robust.Client.GameObjects
var layerColor = color * layer.Color;
var position = -(Vector2) texture.Size / (2f * EyeManager.PixelsPerMeter) + layer.Offset;
var textureSize = texture.Size / (float) EyeManager.PixelsPerMeter;
var position = -(Vector2)texture.Size / (2f * EyeManager.PixelsPerMeter) + layer.Offset;
var textureSize = texture.Size / (float)EyeManager.PixelsPerMeter;
var quad = Box2.FromDimensions(position, textureSize);
// TODO: Implement layer-specific rotation and scale.
@@ -1295,7 +1320,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 = (MathF.PI * 2) / (numDirections * 2);
var segments = (int)(theta / segSize);
var odd = segments % 2;
var result = theta - (segments * segSize) - (odd * segSize);
@@ -1345,18 +1370,6 @@ namespace Robust.Client.GameObjects
return texture;
}
public override void OnRemove()
{
base.OnRemove();
var map = Owner.Transform.MapID;
if (map != MapId.Nullspace)
{
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local,
new RenderTreeRemoveSpriteMessage(this, map));
}
}
public void FrameUpdate(float delta)
{
foreach (var t in Layers)
@@ -1407,7 +1420,7 @@ namespace Robust.Client.GameObjects
if (curState == null)
return;
var thestate = (SpriteComponentState) curState;
var thestate = (SpriteComponentState)curState;
Visible = thestate.Visible;
DrawDepth = thestate.DrawDepth;
@@ -1495,8 +1508,20 @@ namespace Robust.Client.GameObjects
};
}
private void UpdateIsInert()
private void QueueUpdateIsInert()
{
if (_inertUpdateQueued)
return;
_inertUpdateQueued = true;
// Yes that null check is valid because of that stupid fucking dummy IEntity.
// Who thought that was a good idea.
Owner?.EntityManager?.EventBus?.RaiseEvent(EventSource.Local, new SpriteUpdateInertEvent {Sprite = this});
}
internal void DoUpdateIsInert()
{
_inertUpdateQueued = false;
IsInert = true;
foreach (var layer in Layers)
@@ -1576,9 +1601,10 @@ namespace Robust.Client.GameObjects
{
var builder = new StringBuilder();
builder.AppendFormat(
"vis/depth/scl/rot/ofs/col/diral/dir: {0}/{1}/{2}/{3}/{4}/{5}/{6}/{7}\n",
"vis/depth/scl/rot/ofs/col/norot/override/dir: {0}/{1}/{2}/{3}/{4}/{5}/{6}/{8}/{7}\n",
Visible, DrawDepth, Scale, Rotation, Offset,
Color, Directional, GetDir(RSI.State.DirectionType.Dir8, Owner.Transform.WorldRotation)
Color, NoRotation, GetDir(RSI.State.DirectionType.Dir8, Owner.Transform.WorldRotation),
DirectionOverride
);
foreach (var layer in Layers)
@@ -1845,14 +1871,14 @@ namespace Robust.Client.GameObjects
{
AutoAnimated = value;
_parent.UpdateIsInert();
_parent.QueueUpdateIsInert();
}
public void SetVisible(bool value)
{
Visible = value;
_parent.UpdateIsInert();
_parent.QueueUpdateIsInert();
}
public void SetRsi(RSI? rsi)
@@ -1884,7 +1910,7 @@ namespace Robust.Client.GameObjects
}
}
_parent.UpdateIsInert();
_parent.QueueUpdateIsInert();
}
public void SetState(RSI.StateId stateId)
@@ -1916,7 +1942,7 @@ namespace Robust.Client.GameObjects
AnimationTime = 0;
AnimationTimeLeft = state.GetDelay(0);
_parent.UpdateIsInert();
_parent.QueueUpdateIsInert();
}
public void SetTexture(Texture? texture)
@@ -1924,7 +1950,7 @@ namespace Robust.Client.GameObjects
State = default;
Texture = texture;
_parent.UpdateIsInert();
_parent.QueueUpdateIsInert();
}
public void SetOffset(Vector2 offset)
@@ -1976,13 +2002,13 @@ namespace Robust.Client.GameObjects
switch (layerProp)
{
case "texture":
LayerSetTexture(index, (string) value);
LayerSetTexture(index, (string)value);
return;
case "state":
LayerSetState(index, (string) value);
LayerSetState(index, (string)value);
return;
case "color":
LayerSetColor(index, (Color) value);
LayerSetColor(index, (Color)value);
return;
default:
throw new ArgumentException($"Unknown layer property '{layerProp}'");
@@ -2027,7 +2053,7 @@ namespace Robust.Client.GameObjects
yield break;
}
var dummy = new DummyIconEntity {Prototype = prototype};
var dummy = new DummyIconEntity { Prototype = prototype };
var spriteComponent = dummy.AddComponent<SpriteComponent>();
if (prototype.Components.TryGetValue("Appearance", out _))
@@ -2071,7 +2097,7 @@ namespace Robust.Client.GameObjects
return GetFallbackState(resourceCache);
}
var dummy = new DummyIconEntity {Prototype = prototype};
var dummy = new DummyIconEntity { Prototype = prototype };
var spriteComponent = dummy.AddComponent<SpriteComponent>();
dummy.Delete();
@@ -2108,7 +2134,7 @@ namespace Robust.Client.GameObjects
{
var typeFactory = IoCManager.Resolve<IDynamicTypeFactoryInternal>();
var serializationManager = IoCManager.Resolve<ISerializationManager>();
var comp = (T) typeFactory.CreateInstanceUnchecked(typeof(T));
var comp = (T)typeFactory.CreateInstanceUnchecked(typeof(T));
_components[typeof(T)] = comp;
comp.Owner = this;
@@ -2142,7 +2168,7 @@ namespace Robust.Client.GameObjects
public T GetComponent<T>()
{
return (T) _components[typeof(T)];
return (T)_components[typeof(T)];
}
public IComponent GetComponent(Type type)
@@ -2154,7 +2180,7 @@ namespace Robust.Client.GameObjects
{
component = null;
if (!_components.TryGetValue(typeof(T), out var value)) return false;
component = (T) value;
component = (T)value;
return true;
}
@@ -2176,6 +2202,10 @@ namespace Robust.Client.GameObjects
return null;
}
public void QueueDelete()
{
}
public void Delete()
{
}
@@ -2190,10 +2220,12 @@ namespace Robust.Client.GameObjects
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)
{
}
@@ -2204,4 +2236,14 @@ namespace Robust.Client.GameObjects
}
#endregion
}
internal sealed class SpriteUpdateEvent : EntityEventArgs
{
}
internal struct SpriteUpdateInertEvent
{
public SpriteComponent Sprite;
}
}

View File

@@ -0,0 +1,43 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
namespace Robust.Client.GameObjects
{
[RegisterComponent]
public sealed class RenderingTreeComponent : Component
{
public override string Name => "RenderingTree";
internal DynamicTree<SpriteComponent> SpriteTree { get; private set; } = new(SpriteAabbFunc);
internal DynamicTree<PointLightComponent> LightTree { get; private set; } = new(LightAabbFunc);
private static Box2 SpriteAabbFunc(in SpriteComponent value)
{
var worldPos = value.Owner.Transform.WorldPosition;
return new Box2(worldPos, worldPos);
}
private static Box2 LightAabbFunc(in PointLightComponent value)
{
var worldPos = value.Owner.Transform.WorldPosition;
var boxSize = value.Radius * 2;
return Box2.CenteredAround(worldPos, (boxSize, boxSize));
}
internal static Box2 SpriteAabbFunc(SpriteComponent value, Vector2? worldPos = null)
{
worldPos ??= value.Owner.Transform.WorldPosition;
return new Box2(worldPos.Value, worldPos.Value);
}
internal static Box2 LightAabbFunc(PointLightComponent value, Vector2? worldPos = null)
{
worldPos ??= value.Owner.Transform.WorldPosition;
var boxSize = value.Radius * 2;
return Box2.CenteredAround(worldPos.Value, (boxSize, boxSize));
}
}
}

View File

@@ -2,14 +2,14 @@
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Client.GameObjects
{
[ComponentReference(typeof(SharedUserInterfaceComponent))]
public class ClientUserInterfaceComponent : SharedUserInterfaceComponent, ISerializationHooks
{
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
@@ -23,6 +23,9 @@ namespace Robust.Client.GameObjects
[DataField("interfaces", readOnly: true)]
private List<PrototypeData> _interfaceData = new();
[ViewVariables]
public IEnumerable<BoundUserInterface> Interfaces => _openInterfaces.Values;
void ISerializationHooks.AfterDeserialization()
{
_interfaces.Clear();
@@ -33,48 +36,40 @@ namespace Robust.Client.GameObjects
}
}
public override void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel,
ICommonSession? session = null)
internal void MessageReceived(BoundUIWrapMessage msg)
{
base.HandleNetworkMessage(message, netChannel, session);
switch (message)
switch (msg.Message)
{
case BoundInterfaceMessageWrapMessage wrapped:
// Double nested switches who needs readability anyways.
switch (wrapped.Message)
case OpenBoundInterfaceMessage _:
if (_openInterfaces.ContainsKey(msg.UiKey))
{
case OpenBoundInterfaceMessage _:
if (_openInterfaces.ContainsKey(wrapped.UiKey))
{
return;
}
return;
}
OpenInterface(wrapped);
break;
OpenInterface(msg);
break;
case CloseBoundInterfaceMessage _:
Close(wrapped.UiKey, true);
break;
case CloseBoundInterfaceMessage _:
Close(msg.UiKey, true);
break;
default:
if (_openInterfaces.TryGetValue(wrapped.UiKey, out var bi))
{
bi.InternalReceiveMessage(wrapped.Message);
}
break;
default:
if (_openInterfaces.TryGetValue(msg.UiKey, out var bi))
{
bi.InternalReceiveMessage(msg.Message);
}
break;
}
}
private void OpenInterface(BoundInterfaceMessageWrapMessage wrapped)
private void OpenInterface(BoundUIWrapMessage wrapped)
{
var data = _interfaces[wrapped.UiKey];
// TODO: This type should be cached, but I'm too lazy.
var type = _reflectionManager.LooseGetType(data.ClientType);
var boundInterface = (BoundUserInterface) _dynamicTypeFactory.CreateInstance(type, new[]{this, wrapped.UiKey});
var boundInterface =
(BoundUserInterface) _dynamicTypeFactory.CreateInstance(type, new[] {this, wrapped.UiKey});
boundInterface.Open();
_openInterfaces[wrapped.UiKey] = boundInterface;
}
@@ -86,7 +81,7 @@ namespace Robust.Client.GameObjects
return;
}
if(!remoteCall)
if (!remoteCall)
SendMessage(new CloseBoundInterfaceMessage(), uiKey);
_openInterfaces.Remove(uiKey);
boundUserInterface.Dispose();
@@ -94,7 +89,8 @@ namespace Robust.Client.GameObjects
internal void SendMessage(BoundUserInterfaceMessage message, object uiKey)
{
SendNetworkMessage(new BoundInterfaceMessageWrapMessage(message, uiKey));
EntitySystem.Get<UserInterfaceSystem>()
.Send(new BoundUIWrapMessage(Owner.Uid, message, uiKey));
}
}

View File

@@ -41,17 +41,6 @@ namespace Robust.Client.GameObjects
_broadPhaseSystem = Get<SharedBroadPhaseSystem>();
}
public override void Shutdown()
{
base.Shutdown();
UnsubscribeNetworkEvent<PlayAudioEntityMessage>();
UnsubscribeNetworkEvent<PlayAudioGlobalMessage>();
UnsubscribeNetworkEvent<PlayAudioPositionalMessage>();
UnsubscribeNetworkEvent<StopAudioMessageClient>();
UnsubscribeLocalEvent<SoundSystem.QueryAudioSystem>();
}
private void StopAudioMessageHandler(StopAudioMessageClient ev)
{
var stream = _playingClydeStreams.Find(p => p.NetIdentifier == ev.Identifier);
@@ -265,7 +254,7 @@ namespace Robust.Client.GameObjects
if (!source.SetPosition(entity.Transform.WorldPosition))
{
source.Dispose();
Logger.Warning("Can't play positional audio, can't set position.");
Logger.Warning($"Can't play positional audio \"{stream.Name}\", can't set position.");
return null;
}
@@ -312,7 +301,7 @@ namespace Robust.Client.GameObjects
if (!source.SetPosition(coordinates.ToMapPos(EntityManager)))
{
source.Dispose();
Logger.Warning("Can't play positional audio, can't set position.");
Logger.Warning("Can't play positional audio \"{stream.Name}\", can't set position.");
return null;
}

View File

@@ -35,16 +35,7 @@ namespace Robust.Client.GameObjects
SubscribeLocalEvent<OccluderDirtyEvent>(HandleDirtyEvent);
SubscribeLocalEvent<ClientOccluderComponent, SnapGridPositionChangedEvent>(HandleSnapGridMove);
}
/// <inheritdoc />
public override void Shutdown()
{
UnsubscribeLocalEvent<OccluderDirtyEvent>();
UnsubscribeLocalEvent<ClientOccluderComponent, SnapGridPositionChangedEvent>();
base.Shutdown();
SubscribeLocalEvent<ClientOccluderComponent, AnchorStateChangedEvent>(HandleAnchorChanged);
}
public override void FrameUpdate(float frameTime)
@@ -71,9 +62,9 @@ namespace Robust.Client.GameObjects
}
}
private static void HandleSnapGridMove(EntityUid uid, ClientOccluderComponent component, SnapGridPositionChangedEvent args)
private static void HandleAnchorChanged(EntityUid uid, ClientOccluderComponent component, AnchorStateChangedEvent args)
{
component.SnapGridOnPositionChanged();
component.AnchorStateChanged();
}
private void HandleDirtyEvent(OccluderDirtyEvent ev)

View File

@@ -0,0 +1,134 @@
#if DEBUG
using System;
using System.Text;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
namespace Robust.Client.GameObjects
{
internal sealed class DebugGridTileLookupSystem : EntitySystem
{
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
public bool Enabled
{
get => _enabled;
set
{
if (_enabled == value) return;
_enabled = value;
if (_enabled)
{
_label.Visible = true;
LastTile = default;
}
else
{
_label.Text = null;
_label.Visible = false;
}
}
}
private bool _enabled;
private (GridId Grid, Vector2i Indices) LastTile;
// Label and shit that follows cursor
private Label _label = new()
{
Visible = false,
};
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<SendGridTileLookupMessage>(HandleSentEntities);
IoCManager.Resolve<IUserInterfaceManager>().StateRoot.AddChild(_label);
}
public override void Shutdown()
{
base.Shutdown();
UnsubscribeNetworkEvent<SendGridTileLookupMessage>();
IoCManager.Resolve<IUserInterfaceManager>().StateRoot.RemoveChild(_label);
}
private void RequestEntities(GridId gridId, Vector2i indices)
{
if (gridId == GridId.Invalid) return;
RaiseNetworkEvent(new RequestGridTileLookupMessage(gridId, indices));
}
private void HandleSentEntities(SendGridTileLookupMessage message)
{
if (!Enabled) return;
var text = new StringBuilder();
text.AppendLine($"GridId: {LastTile.Grid}, Tile: {LastTile.Indices}");
for (var i = 0; i < message.Entities.Count; i++)
{
var uid = message.Entities[i];
if (!EntityManager.TryGetEntity(uid, out var entity)) continue;
text.AppendLine(entity.ToString());
}
_label.Text = text.ToString();
}
public override void FrameUpdate(float frameTime)
{
base.FrameUpdate(frameTime);
if (!Enabled) return;
var mousePos = _inputManager.MouseScreenPosition;
var worldPos = _eyeManager.ScreenToMap(mousePos);
GridId gridId;
Vector2i tile;
if (_mapManager.TryFindGridAt(worldPos, out var grid))
{
gridId = grid.Index;
tile = grid.WorldToTile(worldPos.Position);
}
else
{
gridId = GridId.Invalid;
tile = new Vector2i((int) MathF.Floor(worldPos.Position.X), (int) MathF.Floor(worldPos.Position.Y));
}
LayoutContainer.SetPosition(_label, mousePos.Position);
if ((gridId, tile).Equals(LastTile)) return;
_label.Text = null;
LastTile = (gridId, tile);
RequestEntities(gridId, tile);
}
}
internal sealed class RequestTileEntities : IConsoleCommand
{
public string Command => "tilelookup";
public string Description => "Used for debugging GridTileLookupSystem";
public string Help => $"{Command}";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
EntitySystem.Get<DebugGridTileLookupSystem>().Enabled ^= true;
}
}
}
#endif

View File

@@ -0,0 +1,84 @@
#if DEBUG
using Robust.Client.Graphics;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
namespace Robust.Client.GameObjects
{
internal sealed class DebugLightTreeSystem : EntitySystem
{
private DebugLightOverlay? _lightOverlay;
public bool Enabled
{
get => _enabled;
set
{
if (_enabled == value) return;
_enabled = value;
var overlayManager = IoCManager.Resolve<IOverlayManager>();
if (_enabled)
{
_lightOverlay = new DebugLightOverlay(
IoCManager.Resolve<IEntityLookup>(),
IoCManager.Resolve<IEyeManager>(),
IoCManager.Resolve<IMapManager>(),
Get<RenderingTreeSystem>());
overlayManager.AddOverlay(_lightOverlay);
}
else
{
overlayManager.RemoveOverlay(_lightOverlay!);
_lightOverlay = null;
}
}
}
private bool _enabled;
private sealed class DebugLightOverlay : Overlay
{
private IEntityLookup _lookup;
private IEyeManager _eyeManager;
private IMapManager _mapManager;
private RenderingTreeSystem _tree;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public DebugLightOverlay(IEntityLookup lookup, IEyeManager eyeManager, IMapManager mapManager, RenderingTreeSystem tree)
{
_lookup = lookup;
_eyeManager = eyeManager;
_mapManager = mapManager;
_tree = tree;
}
protected internal override void Draw(in OverlayDrawArgs args)
{
var map = _eyeManager.CurrentMap;
if (map == MapId.Nullspace) return;
var viewport = _eyeManager.GetWorldViewport();
foreach (var tree in _tree.GetLightTrees(map, viewport))
{
foreach (var light in tree)
{
var aabb = _lookup.GetWorldAabbFromEntity(light.Owner);
if (!aabb.Intersects(viewport)) continue;
args.WorldHandle.DrawRect(aabb, Color.Green.WithAlpha(0.1f));
}
}
}
}
}
}
#endif

View File

@@ -67,9 +67,10 @@ namespace Robust.Client.GameObjects
//Create effect from creation message
var effect = new Effect(message, resourceCache, _mapManager, _entityManager);
effect.Deathtime = gameTiming.CurTime + message.LifeTime;
if (effect.AttachedEntityUid != null)
if (effect.AttachedEntityUid != null
&& _entityManager.TryGetEntity(effect.AttachedEntityUid.Value, out var attachedEntity))
{
effect.AttachedEntity = _entityManager.GetEntity(effect.AttachedEntityUid.Value);
effect.AttachedEntity = attachedEntity;
}
_Effects.Add(effect);

View File

@@ -173,4 +173,24 @@ namespace Robust.Client.GameObjects
AttachedEntity = attachedEntity;
}
}
public class PlayerAttachedEvent : EntityEventArgs
{
public PlayerAttachedEvent(IEntity entity)
{
Entity = entity;
}
public IEntity Entity { get; }
}
public class PlayerDetachedEvent : EntityEventArgs
{
public PlayerDetachedEvent(IEntity entity)
{
Entity = entity;
}
public IEntity Entity { get; }
}
}

View File

@@ -1,11 +1,17 @@
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using JetBrains.Annotations;
using Robust.Client.Physics;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Utility;
namespace Robust.Client.GameObjects
{
@@ -15,23 +21,58 @@ namespace Robust.Client.GameObjects
[UsedImplicitly]
public sealed class RenderingTreeSystem : EntitySystem
{
internal const string LoggerSawmill = "rendertree";
// Nullspace is not indexed. Keep that in mind.
[Dependency] private readonly IMapManagerInternal _mapManager = default!;
private readonly Dictionary<MapId, Dictionary<GridId, MapTrees>> _gridTrees = new();
[Dependency] private readonly IMapManager _mapManager = default!;
private readonly List<SpriteComponent> _spriteQueue = new();
private readonly List<PointLightComponent> _lightQueue = new();
internal DynamicTree<SpriteComponent> GetSpriteTreeForMap(MapId map, GridId grid)
private HashSet<EntityUid> _checkedChildren = new();
/// <summary>
/// <see cref="CVars.MaxLightRadius"/>
/// </summary>
public float MaxLightRadius { get; private set; }
internal IEnumerable<RenderingTreeComponent> GetRenderTrees(MapId mapId, Box2 worldAABB)
{
return _gridTrees[map][grid].SpriteTree;
if (mapId == MapId.Nullspace) yield break;
var enclosed = false;
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
{
yield return EntityManager.GetEntity(grid.GridEntityId).GetComponent<RenderingTreeComponent>();
// if we're enclosed then we know no other grids relevant + don't need the map's rendertree
if (grid.WorldBounds.Encloses(in worldAABB))
{
enclosed = true;
break;
}
}
if (!enclosed)
yield return _mapManager.GetMapEntity(mapId).GetComponent<RenderingTreeComponent>();
}
internal DynamicTree<PointLightComponent> GetLightTreeForMap(MapId map, GridId grid)
internal IEnumerable<DynamicTree<SpriteComponent>> GetSpriteTrees(MapId mapId, Box2 worldAABB)
{
return _gridTrees[map][grid].LightTree;
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()
@@ -43,156 +84,164 @@ namespace Robust.Client.GameObjects
UpdatesAfter.Add(typeof(PhysicsSystem));
_mapManager.MapCreated += MapManagerOnMapCreated;
_mapManager.MapDestroyed += MapManagerOnMapDestroyed;
_mapManager.OnGridCreated += MapManagerOnGridCreated;
_mapManager.OnGridRemoved += MapManagerOnGridRemoved;
SubscribeLocalEvent<EntMapIdChangedMessage>(EntMapIdChanged);
SubscribeLocalEvent<MoveEvent>(EntMoved);
SubscribeLocalEvent<EntParentChangedMessage>(EntParentChanged);
SubscribeLocalEvent<PointLightRadiusChangedMessage>(PointLightRadiusChanged);
SubscribeLocalEvent<RenderTreeRemoveSpriteMessage>(RemoveSprite);
SubscribeLocalEvent<RenderTreeRemoveLightMessage>(RemoveLight);
// Due to how recursion works, this must be done.
SubscribeLocalEvent<MoveEvent>(AnythingMoved);
SubscribeLocalEvent<SpriteComponent, EntMapIdChangedMessage>(SpriteMapChanged);
SubscribeLocalEvent<SpriteComponent, EntParentChangedMessage>(SpriteParentChanged);
SubscribeLocalEvent<SpriteComponent, ComponentRemove>(RemoveSprite);
SubscribeLocalEvent<SpriteComponent, SpriteUpdateEvent>(HandleSpriteUpdate);
SubscribeLocalEvent<PointLightComponent, EntMapIdChangedMessage>(LightMapChanged);
SubscribeLocalEvent<PointLightComponent, EntParentChangedMessage>(LightParentChanged);
SubscribeLocalEvent<PointLightComponent, PointLightRadiusChangedEvent>(PointLightRadiusChanged);
SubscribeLocalEvent<PointLightComponent, RenderTreeRemoveLightEvent>(RemoveLight);
SubscribeLocalEvent<PointLightComponent, PointLightUpdateEvent>(HandleLightUpdate);
SubscribeLocalEvent<RenderingTreeComponent, ComponentRemove>(HandleTreeRemove);
var configManager = IoCManager.Resolve<IConfigurationManager>();
configManager.OnValueChanged(CVars.MaxLightRadius, value => MaxLightRadius = value, true);
}
private void HandleLightUpdate(EntityUid uid, PointLightComponent component, PointLightUpdateEvent args)
{
if (component.TreeUpdateQueued) return;
QueueLightUpdate(component);
}
private void HandleSpriteUpdate(EntityUid uid, SpriteComponent component, SpriteUpdateEvent args)
{
if (component.TreeUpdateQueued) return;
QueueSpriteUpdate(component);
}
private void AnythingMoved(MoveEvent args)
{
AnythingMovedSubHandler(args.Sender.Transform);
}
private void AnythingMovedSubHandler(ITransformComponent sender)
{
// To avoid doing redundant updates (and we don't need to update a grid's children ever)
if (!_checkedChildren.Add(sender.Owner.Uid) ||
sender.Owner.HasComponent<RenderingTreeComponent>()) return;
// This recursive search is needed, as MoveEvent is defined to not care about indirect events like children.
// WHATEVER YOU DO, DON'T REPLACE THIS WITH SPAMMING EVENTS UNLESS YOU HAVE A GUARANTEE IT WON'T LAG THE GC.
// (Struct-based events ok though)
// Ironically this was lagging the GC lolz
if (sender.Owner.TryGetComponent(out SpriteComponent? sprite))
QueueSpriteUpdate(sprite);
if (sender.Owner.TryGetComponent(out PointLightComponent? light))
QueueLightUpdate(light);
foreach (ITransformComponent child in sender.Children)
{
AnythingMovedSubHandler(child);
}
}
// For the RemoveX methods
// If the Transform is removed BEFORE the Sprite/Light,
// then the MapIdChanged code will handle and remove it (because MapId gets set to nullspace).
// Otherwise these will still have their past MapId and that's all we need..
#region SpriteHandlers
private void SpriteMapChanged(EntityUid uid, SpriteComponent component, EntMapIdChangedMessage args)
{
QueueSpriteUpdate(component);
}
private void SpriteParentChanged(EntityUid uid, SpriteComponent component, EntParentChangedMessage args)
{
QueueSpriteUpdate(component);
}
private void RemoveSprite(EntityUid uid, SpriteComponent component, ComponentRemove args)
{
ClearSprite(component);
}
private void ClearSprite(SpriteComponent component)
{
if (component.RenderTree == null) return;
component.RenderTree.SpriteTree.Remove(component);
component.RenderTree = null;
}
private void QueueSpriteUpdate(SpriteComponent component)
{
if (component.TreeUpdateQueued) return;
component.TreeUpdateQueued = true;
_spriteQueue.Add(component);
}
#endregion
#region LightHandlers
private void LightMapChanged(EntityUid uid, PointLightComponent component, EntMapIdChangedMessage args)
{
QueueLightUpdate(component);
}
private void LightParentChanged(EntityUid uid, PointLightComponent component, EntParentChangedMessage args)
{
QueueLightUpdate(component);
}
private void PointLightRadiusChanged(EntityUid uid, PointLightComponent component, PointLightRadiusChangedEvent args)
{
QueueLightUpdate(component);
}
private void RemoveLight(EntityUid uid, PointLightComponent component, RenderTreeRemoveLightEvent args)
{
ClearLight(component);
}
private void ClearLight(PointLightComponent component)
{
if (component.RenderTree == null) return;
component.RenderTree.LightTree.Remove(component);
component.RenderTree = null;
}
private void QueueLightUpdate(PointLightComponent component)
{
if (component.TreeUpdateQueued) return;
component.TreeUpdateQueued = true;
_lightQueue.Add(component);
}
#endregion
public override void Shutdown()
{
base.Shutdown();
_mapManager.MapCreated -= MapManagerOnMapCreated;
_mapManager.MapDestroyed -= MapManagerOnMapDestroyed;
_mapManager.OnGridCreated -= MapManagerOnGridCreated;
_mapManager.OnGridRemoved -= MapManagerOnGridRemoved;
UnsubscribeLocalEvent<EntMapIdChangedMessage>();
UnsubscribeLocalEvent<MoveEvent>();
UnsubscribeLocalEvent<EntParentChangedMessage>();
UnsubscribeLocalEvent<PointLightRadiusChangedMessage>();
UnsubscribeLocalEvent<RenderTreeRemoveSpriteMessage>();
UnsubscribeLocalEvent<RenderTreeRemoveLightMessage>();
}
// For these next 2 methods (the Remove* ones):
// If the Transform is removed BEFORE the Sprite/Light,
// then the MapIdChanged code will handle and remove it (because MapId gets set to nullspace).
// Otherwise these will still have their past MapId and that's all we need..
private void RemoveLight(RenderTreeRemoveLightMessage ev)
private void HandleTreeRemove(EntityUid uid, RenderingTreeComponent component, ComponentRemove args)
{
foreach (var gridId in _mapManager.FindGridIdsIntersecting(ev.Map, MapTrees.LightAabbFunc(ev.Light), true))
foreach (var sprite in component.SpriteTree)
{
_gridTrees[ev.Map][gridId].LightTree.Remove(ev.Light);
}
}
private void RemoveSprite(RenderTreeRemoveSpriteMessage ev)
{
foreach (var gridId in _mapManager.FindGridIdsIntersecting(ev.Map, MapTrees.SpriteAabbFunc(ev.Sprite), true))
{
_gridTrees[ev.Map][gridId].SpriteTree.Remove(ev.Sprite);
}
}
private void PointLightRadiusChanged(PointLightRadiusChangedMessage ev)
{
QueueUpdateLight(ev.PointLightComponent);
}
private void EntParentChanged(EntParentChangedMessage ev)
{
UpdateEntity(ev.Entity);
}
private void EntMoved(MoveEvent ev)
{
UpdateEntity(ev.Sender);
}
private void UpdateEntity(IEntity entity)
{
if (entity.TryGetComponent(out SpriteComponent? spriteComponent))
{
if (!spriteComponent.TreeUpdateQueued)
{
spriteComponent.TreeUpdateQueued = true;
_spriteQueue.Add(spriteComponent);
}
sprite.RenderTree = null;
}
if (entity.TryGetComponent(out PointLightComponent? light))
foreach (var light in component.LightTree)
{
QueueUpdateLight(light);
light.RenderTree = null;
}
foreach (var child in entity.Transform.ChildEntityUids)
{
UpdateEntity(EntityManager.GetEntity(child));
}
}
private void QueueUpdateLight(PointLightComponent light)
{
if (!light.TreeUpdateQueued)
{
light.TreeUpdateQueued = true;
_lightQueue.Add(light);
}
}
private void EntMapIdChanged(EntMapIdChangedMessage ev)
{
// Nullspace is a valid map ID for stuff to have but we also aren't gonna bother indexing it.
// So that's why there's a GetValueOrDefault.
var oldMapTrees = _gridTrees.GetValueOrDefault(ev.OldMapId);
var newMapTrees = _gridTrees.GetValueOrDefault(ev.Entity.Transform.MapID);
// TODO: MMMM probably a better way to do this.
if (ev.Entity.TryGetComponent(out SpriteComponent? sprite))
{
if (oldMapTrees != null)
{
foreach (var (_, gridTree) in oldMapTrees)
{
gridTree.SpriteTree.Remove(sprite);
}
}
var bounds = MapTrees.SpriteAabbFunc(sprite);
foreach (var gridId in _mapManager.FindGridIdsIntersecting(ev.Entity.Transform.MapID, bounds, true))
{
var gridBounds = gridId == GridId.Invalid
? bounds : bounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition);
newMapTrees?[gridId].SpriteTree.AddOrUpdate(sprite, gridBounds);
}
}
if (ev.Entity.TryGetComponent(out PointLightComponent? light))
{
if (oldMapTrees != null)
{
foreach (var (_, gridTree) in oldMapTrees)
{
gridTree.LightTree.Remove(light);
}
}
var bounds = MapTrees.LightAabbFunc(light);
foreach (var gridId in _mapManager.FindGridIdsIntersecting(ev.Entity.Transform.MapID, bounds, true))
{
var gridBounds = gridId == GridId.Invalid
? bounds : bounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition);
newMapTrees?[gridId].LightTree.AddOrUpdate(light, gridBounds);
}
}
}
private void MapManagerOnMapDestroyed(object? sender, MapEventArgs e)
{
_gridTrees.Remove(e.Map);
component.SpriteTree.Clear();
component.LightTree.Clear();
}
private void MapManagerOnMapCreated(object? sender, MapEventArgs e)
@@ -202,109 +251,127 @@ namespace Robust.Client.GameObjects
return;
}
_gridTrees.Add(e.Map, new Dictionary<GridId, MapTrees>
{
{GridId.Invalid, new MapTrees()}
});
_mapManager.GetMapEntity(e.Map).EnsureComponent<RenderingTreeComponent>();
}
private void MapManagerOnGridCreated(MapId mapId, GridId gridId)
{
_gridTrees[mapId].Add(gridId, new MapTrees());
EntityManager.GetEntity(_mapManager.GetGrid(gridId).GridEntityId).EnsureComponent<RenderingTreeComponent>();
}
private void MapManagerOnGridRemoved(MapId mapId, GridId gridId)
private RenderingTreeComponent? GetRenderTree(IEntity entity)
{
_gridTrees[mapId].Remove(gridId);
if (entity.Transform.MapID == MapId.Nullspace ||
entity.HasComponent<RenderingTreeComponent>()) return null;
var parent = entity.Transform.Parent?.Owner;
while (true)
{
if (parent == null) break;
if (parent.TryGetComponent(out RenderingTreeComponent? comp)) return comp;
parent = parent.Transform.Parent?.Owner;
}
return null;
}
public override void FrameUpdate(float frameTime)
{
foreach (var queuedUpdateSprite in _spriteQueue)
_checkedChildren.Clear();
foreach (var sprite in _spriteQueue)
{
var map = queuedUpdateSprite.Owner.Transform.MapID;
if (map == MapId.Nullspace)
sprite.TreeUpdateQueued = false;
if (!sprite.Visible || sprite.ContainerOccluded)
{
ClearSprite(sprite);
continue;
}
var mapTree = _gridTrees[map];
var oldMapTree = sprite.RenderTree;
var newMapTree = GetRenderTree(sprite.Owner);
// TODO: Temp PVS guard
var worldPos = sprite.Owner.Transform.WorldPosition;
foreach (var gridId in _mapManager.FindGridIdsIntersecting(map,
MapTrees.SpriteAabbFunc(queuedUpdateSprite), true))
if (float.IsNaN(worldPos.X) || float.IsNaN(worldPos.Y))
{
mapTree[gridId].SpriteTree.AddOrUpdate(queuedUpdateSprite);
ClearSprite(sprite);
continue;
}
queuedUpdateSprite.TreeUpdateQueued = false;
var treePos = newMapTree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
var aabb = RenderingTreeComponent.SpriteAabbFunc(sprite, worldPos).Translated(-treePos);
// If we're on a new map then clear the old one.
if (oldMapTree != newMapTree)
{
ClearSprite(sprite);
newMapTree?.SpriteTree.Add(sprite, aabb);
}
else
{
newMapTree?.SpriteTree.Update(sprite, aabb);
}
sprite.RenderTree = newMapTree;
}
foreach (var queuedUpdateLight in _lightQueue)
foreach (var light in _lightQueue)
{
var map = queuedUpdateLight.Owner.Transform.MapID;
if (map == MapId.Nullspace)
light.TreeUpdateQueued = false;
if (!light.Enabled || light.ContainerOccluded)
{
ClearLight(light);
continue;
}
var mapTree = _gridTrees[map];
var oldMapTree = light.RenderTree;
var newMapTree = GetRenderTree(light.Owner);
// TODO: Temp PVS guard
var worldPos = light.Owner.Transform.WorldPosition;
foreach (var gridId in _mapManager.FindGridIdsIntersecting(map,
MapTrees.LightAabbFunc(queuedUpdateLight), true))
if (float.IsNaN(worldPos.X) || float.IsNaN(worldPos.Y))
{
mapTree[gridId].LightTree.AddOrUpdate(queuedUpdateLight);
ClearLight(light);
continue;
}
queuedUpdateLight.TreeUpdateQueued = false;
// TODO: Events need a bit of cleanup so we only validate this on initialize and radius changed events
// this is fine for now IMO as it's 1 float check for every light that moves
if (light.Radius > MaxLightRadius)
{
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).Translated(-treePos);
// If we're on a new map then clear the old one.
if (oldMapTree != newMapTree)
{
ClearLight(light);
newMapTree?.LightTree.Add(light, aabb);
}
else
{
newMapTree?.LightTree.Update(light, aabb);
}
light.RenderTree = newMapTree;
}
_spriteQueue.Clear();
_lightQueue.Clear();
}
private sealed class MapTrees
{
public readonly DynamicTree<SpriteComponent> SpriteTree;
public readonly DynamicTree<PointLightComponent> LightTree;
public MapTrees()
{
SpriteTree = new DynamicTree<SpriteComponent>(SpriteAabbFunc);
LightTree = new DynamicTree<PointLightComponent>(LightAabbFunc);
}
internal static Box2 SpriteAabbFunc(in SpriteComponent value)
{
var worldPos = value.Owner.Transform.WorldPosition;
return new Box2(worldPos, worldPos);
}
internal static Box2 LightAabbFunc(in PointLightComponent value)
{
var worldPos = value.Owner.Transform.WorldPosition;
var boxSize = value.Radius * 2;
return Box2.CenteredAround(worldPos, (boxSize, boxSize));
}
}
}
internal struct RenderTreeRemoveSpriteMessage
internal class RenderTreeRemoveLightEvent : EntityEventArgs
{
public RenderTreeRemoveSpriteMessage(SpriteComponent sprite, MapId map)
{
Sprite = sprite;
Map = map;
}
public SpriteComponent Sprite { get; }
public MapId Map { get; }
}
internal struct RenderTreeRemoveLightMessage
{
public RenderTreeRemoveLightMessage(PointLightComponent light, MapId map)
public RenderTreeRemoveLightEvent(PointLightComponent light, MapId map)
{
Light = light;
Map = map;

View File

@@ -1,9 +1,9 @@
using JetBrains.Annotations;
using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
namespace Robust.Client.GameObjects
{
@@ -16,10 +16,29 @@ namespace Robust.Client.GameObjects
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
private RenderingTreeSystem _treeSystem = default!;
private readonly Queue<SpriteComponent> _inertUpdateQueue = new();
public override void Initialize()
{
base.Initialize();
_treeSystem = Get<RenderingTreeSystem>();
SubscribeLocalEvent<SpriteUpdateInertEvent>(QueueUpdateInert);
}
private void QueueUpdateInert(SpriteUpdateInertEvent ev)
{
_inertUpdateQueue.Enqueue(ev.Sprite);
}
/// <inheritdoc />
public override void FrameUpdate(float frameTime)
{
var renderTreeSystem = EntitySystemManager.GetEntitySystem<RenderingTreeSystem>();
while (_inertUpdateQueue.TryDequeue(out var sprite))
{
sprite.DoUpdateIsInert();
}
// So we could calculate the correct size of the entities based on the contents of their sprite...
// Or we can just assume that no entity is larger than 10x10 and get a stupid easy check.
@@ -31,22 +50,11 @@ namespace Robust.Client.GameObjects
return;
}
foreach (var gridId in _mapManager.FindGridIdsIntersecting(currentMap, pvsBounds, true))
foreach (var comp in _treeSystem.GetRenderTrees(currentMap, pvsBounds))
{
Box2 gridBounds;
var bounds = pvsBounds.Translated(-comp.Owner.Transform.WorldPosition);
if (gridId == GridId.Invalid)
{
gridBounds = pvsBounds;
}
else
{
gridBounds = pvsBounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition);
}
var mapTree = renderTreeSystem.GetSpriteTreeForMap(currentMap, gridId);
mapTree.QueryAabb(ref frameTime, (ref float state, in SpriteComponent value) =>
comp.SpriteTree.QueryAabb(ref frameTime, (ref float state, in SpriteComponent value) =>
{
if (value.IsInert)
{
@@ -55,7 +63,7 @@ namespace Robust.Client.GameObjects
value.FrameUpdate(state);
return true;
}, gridBounds, approx: true);
}, bounds, true);
}
}
}

View File

@@ -0,0 +1,37 @@
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Robust.Client.GameObjects
{
[UsedImplicitly]
public sealed class UserInterfaceSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<BoundUIWrapMessage>(MessageReceived);
SubscribeLocalEvent<ClientUserInterfaceComponent, ComponentShutdown>(OnUserInterfaceShutdown);
}
private void OnUserInterfaceShutdown(EntityUid uid, ClientUserInterfaceComponent component, ComponentShutdown args)
{
foreach (var bui in component.Interfaces)
{
bui.Dispose();
}
}
private void MessageReceived(BoundUIWrapMessage ev)
{
var cmp = ComponentManager.GetComponent<ClientUserInterfaceComponent>(ev.Entity);
cmp.MessageReceived(ev);
}
internal void Send(BoundUIWrapMessage msg)
{
RaiseNetworkEvent(msg);
}
}
}

View File

@@ -1,4 +1,3 @@
using System.Collections.Generic;
using Robust.Shared.GameObjects;
namespace Robust.Client.GameObjects

View File

@@ -1,14 +0,0 @@
using Robust.Shared.GameObjects;
namespace Robust.Client.GameObjects
{
public class PlayerAttachedMsg : ComponentMessage
{
}
public class PlayerDetachedMsg : ComponentMessage
{
}
}

View File

@@ -1,29 +1,32 @@
// ReSharper disable once RedundantUsingDirective
// Used in EXCEPTION_TOLERANCE preprocessor
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.Input;
using Robust.Client.Map;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Network.Messages;
using Robust.Client.Player;
using Robust.Client.Timing;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.Exceptions;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Input;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Client.GameStates
{
/// <inheritdoc />
[UsedImplicitly]
public class ClientGameStateManager : IClientGameStateManager
{
private GameStateProcessor _processor = default!;
@@ -35,6 +38,8 @@ namespace Robust.Client.GameStates
_pendingSystemMessages
= new();
private uint _metaCompNetId;
[Dependency] private readonly IComponentFactory _compFactory = default!;
[Dependency] private readonly IClientEntityManagerInternal _entities = default!;
[Dependency] private readonly IEntityLookup _lookup = default!;
@@ -80,8 +85,8 @@ namespace Robust.Client.GameStates
{
_processor = new GameStateProcessor(_timing);
_network.RegisterNetMessage<MsgState>(MsgState.NAME, HandleStateMessage);
_network.RegisterNetMessage<MsgStateAck>(MsgStateAck.NAME);
_network.RegisterNetMessage<MsgState>(HandleStateMessage);
_network.RegisterNetMessage<MsgStateAck>();
_client.RunLevelChanged += RunLevelChanged;
_config.OnValueChanged(CVars.NetInterp, b => _processor.Interpolation = b, true);
@@ -98,6 +103,12 @@ namespace Robust.Client.GameStates
Predicting = _config.GetCVar(CVars.NetPredict);
PredictTickBias = _config.GetCVar(CVars.NetPredictTickBias);
PredictLagBias = _config.GetCVar(CVars.NetPredictLagBias);
var metaId = _compFactory.GetRegistration(typeof(MetaDataComponent)).NetID;
if (!metaId.HasValue)
throw new InvalidOperationException("MetaDataComponent does not have a NetId.");
_metaCompNetId = metaId.Value;
}
/// <inheritdoc />
@@ -209,6 +220,11 @@ namespace Robust.Client.GameStates
ResetPredictedEntities(_timing.CurTick);
}
if (!curState.Extrapolated)
{
_processor.UpdateFullRep(curState);
}
// Store last tick we got from the GameStateProcessor.
_lastProcessedTick = _timing.CurTick;
@@ -318,6 +334,8 @@ namespace Robust.Client.GameStates
private void ResetPredictedEntities(GameTick curTick)
{
var bus = (EntityEventBus) _entities.EventBus;
foreach (var entity in _entities.GetEntities())
{
// TODO: 99% there's an off-by-one here.
@@ -335,17 +353,18 @@ namespace Robust.Client.GameStates
}
// TODO: handle component deletions/creations.
foreach (var comp in _componentManager.GetNetComponents(entity.Uid))
foreach (var (netId, comp) in _componentManager.GetNetComponents(entity.Uid))
{
DebugTools.AssertNotNull(comp.NetID);
DebugTools.AssertNotNull(netId);
if (comp.LastModifiedTick < curTick || !last.TryGetValue(comp.NetID!.Value, out var compState))
if (comp.LastModifiedTick < curTick || !last.TryGetValue(netId, out var compState))
{
continue;
}
Logger.DebugS(CVars.NetPredict.Name, $" And also its component {comp.Name}");
// TODO: Handle interpolation.
bus.RaiseComponentEvent(comp, new ComponentHandleState(compState, null));
comp.HandleComponentState(compState, null);
}
}
@@ -359,24 +378,22 @@ namespace Robust.Client.GameStates
// so that we can later roll back to it (if necessary).
var outputData = new Dictionary<EntityUid, Dictionary<uint, ComponentState>>();
Debug.Assert(_players.LocalPlayer != null, "_players.LocalPlayer != null");
var player = _players.LocalPlayer.Session;
foreach (var createdEntity in createdEntities)
{
var compData = new Dictionary<uint, ComponentState>();
outputData.Add(createdEntity, compData);
foreach (var component in _componentManager.GetNetComponents(createdEntity))
foreach (var (netId, component) in _componentManager.GetNetComponents(createdEntity))
{
Debug.Assert(_players.LocalPlayer != null, "_players.LocalPlayer != null");
var player = _players.LocalPlayer.Session;
var state = component.GetComponentState(player);
if (state.GetType() == typeof(ComponentState))
{
if(state.GetType() == typeof(ComponentState))
continue;
}
compData.Add(state.NetID, state);
compData.Add(netId, state);
}
}
@@ -393,7 +410,7 @@ namespace Robust.Client.GameStates
private List<EntityUid> ApplyGameState(GameState curState, GameState? nextState)
{
_config.TickProcessMessages();
_mapManager.ApplyGameStatePre(curState.MapData);
_mapManager.ApplyGameStatePre(curState.MapData, curState.EntityStates);
var createdEntities = ApplyEntityStates(curState.EntityStates, curState.EntityDeletions,
nextState?.EntityStates);
_players.ApplyPlayerStates(curState.PlayerStates);
@@ -418,16 +435,17 @@ namespace Robust.Client.GameStates
//Known entities
if (_entities.TryGetEntity(es.Uid, out var entity))
{
// Logger.Debug($"[{IGameTiming.TickStampStatic}] MOD {es.Uid}");
toApply.Add(entity, (es, null));
}
else //Unknown entities
{
var metaState = (MetaDataComponentState?) es.ComponentStates
?.FirstOrDefault(c => c.NetID == NetIDs.META_DATA);
var metaState = (MetaDataComponentState?) es.ComponentChanges?.FirstOrDefault(c => c.NetID == _metaCompNetId).State;
if (metaState == null)
{
throw new InvalidOperationException($"Server sent new entity state for {es.Uid} without metadata component!");
}
// Logger.Debug($"[{IGameTiming.TickStampStatic}] CREATE {es.Uid} {metaState.PrototypeId}");
var newEntity = (Entity)_entities.CreateEntity(metaState.PrototypeId, es.Uid);
toApply.Add(newEntity, (es, null));
toInitialize.Add(newEntity);
@@ -454,17 +472,20 @@ namespace Robust.Client.GameStates
}
}
var bus = (EntityEventBus) _entities.EventBus;
// Make sure this is done after all entities have been instantiated.
foreach (var kvStates in toApply)
{
var ent = kvStates.Key;
var entity = (Entity) ent;
HandleEntityState(entity.EntityManager.ComponentManager, entity, kvStates.Value.Item1,
HandleEntityState(entity.EntityManager.ComponentManager, entity, bus, kvStates.Value.Item1,
kvStates.Value.Item2);
}
foreach (var id in deletions)
{
// Logger.Debug($"[{IGameTiming.TickStampStatic}] DELETE {id}");
_entities.DeleteEntity(id);
}
@@ -526,10 +547,10 @@ namespace Robust.Client.GameStates
return created;
}
private void HandleEntityState(IComponentManager compMan, IEntity entity, EntityState? curState,
private void HandleEntityState(IComponentManager compMan, IEntity entity, EntityEventBus bus, EntityState? curState,
EntityState? nextState)
{
var compStateWork = new Dictionary<uint, (ComponentState? curState, ComponentState? nextState)>();
var compStateWork = new Dictionary<ushort, (ComponentState? curState, ComponentState? nextState)>();
var entityUid = entity.Uid;
if (curState?.ComponentChanges != null)
@@ -545,45 +566,50 @@ namespace Robust.Client.GameStates
}
else
{
//Right now we just assume every state from an unseen entity is added
if (compMan.HasComponent(entityUid, compChange.NetID))
continue;
var newComp = (Component) _compFactory.GetComponent(compChange.ComponentName!);
var newComp = (Component) _compFactory.GetComponent(compChange.NetID);
newComp.Owner = entity;
compMan.AddComponent(entity, newComp, true);
compStateWork[compChange.NetID] = (compChange.State, null);
}
}
}
if (curState?.ComponentStates != null)
if (curState?.ComponentChanges != null)
{
foreach (var compState in curState.ComponentStates)
foreach (var compChange in curState.ComponentChanges)
{
compStateWork[compState.NetID] = (compState, null);
compStateWork[compChange.NetID] = (compChange.State, null);
}
}
if (nextState?.ComponentStates != null)
if (nextState?.ComponentChanges != null)
{
foreach (var compState in nextState.ComponentStates)
foreach (var compState in nextState.ComponentChanges)
{
if (compStateWork.TryGetValue(compState.NetID, out var state))
{
compStateWork[compState.NetID] = (state.curState, compState);
compStateWork[compState.NetID] = (state.curState, compState.State);
}
else
{
compStateWork[compState.NetID] = (null, compState);
compStateWork[compState.NetID] = (null, compState.State);
}
}
}
foreach (var (netId, (cur, next)) in compStateWork)
{
if (compMan.TryGetComponent(entityUid, netId, out var component))
if (compMan.TryGetComponent(entityUid, (ushort) netId, out var component))
{
try
{
bus.RaiseComponentEvent(component, new ComponentHandleState(cur, next));
component.HandleComponentState(cur, next);
}
catch (Exception e)

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Robust.Shared.GameObjects;
@@ -149,11 +149,6 @@ namespace Robust.Client.GameStates
{
Logger.DebugS("net.state", $"Applying State: ext={curState!.Extrapolated}, cTick={_timing.CurTick}, fSeq={curState.FromSequence}, tSeq={curState.ToSequence}, buf={_stateBuffer.Count}");
}
if (!curState!.Extrapolated)
{
UpdateFullRep(curState);
}
}
var cState = curState!;
@@ -162,8 +157,10 @@ namespace Robust.Client.GameStates
return applyNextState;
}
private void UpdateFullRep(GameState state)
public void UpdateFullRep(GameState state)
{
// Logger.Debug($"UPDATE FULL REP: {string.Join(", ", state.EntityStates?.Select(e => e.Uid) ?? Enumerable.Empty<EntityUid>())}");
if (state.FromSequence == GameTick.Zero)
{
// Full state.
@@ -198,14 +195,10 @@ namespace Robust.Client.GameStates
{
compData.Remove(change.NetID);
}
}
}
if (entityState.ComponentStates != null)
{
foreach (var compState in entityState.ComponentStates)
{
compData[compState.NetID] = compState;
else if (change.State is not null)
{
compData[change.NetID] = change.State;
}
}
}
}

View File

@@ -146,13 +146,12 @@ namespace Robust.Client.GameStates
private void DrawWorld(in OverlayDrawArgs args)
{
bool pvsEnabled = _configurationManager.GetCVar<bool>("net.pvs");
if(!pvsEnabled)
return;
float pvsSize = _configurationManager.GetCVar<float>("net.maxupdaterange");
float pvsRange = _configurationManager.GetCVar<float>("net.maxupdaterange");
var pvsCenter = _eyeManager.CurrentEye.Position;
Box2 pvsBox = Box2.CenteredAround(pvsCenter.Position, new Vector2(pvsSize, pvsSize));
Box2 pvsBox = Box2.CenteredAround(pvsCenter.Position, new Vector2(pvsRange * 2, pvsRange * 2));
var worldHandle = args.WorldHandle;
@@ -179,7 +178,7 @@ namespace Robust.Client.GameStates
var yPos = 10 + _lineHeight * i;
var name = $"({netEnt.Id}) {ent.Prototype?.ID}";
var color = CalcTextColor(ref netEnt);
DrawString(screenHandle, _font, new Vector2(xPos + (TrafficHistorySize + 4), yPos), name, color);
screenHandle.DrawString(_font, new Vector2(xPos + (TrafficHistorySize + 4), yPos), name, color);
DrawTrafficBox(screenHandle, ref netEnt, xPos, yPos);
}
}
@@ -224,17 +223,6 @@ namespace Robust.Client.GameStates
base.DisposeBehavior();
}
private static void DrawString(DrawingHandleScreen handle, Font font, Vector2 pos, string str, Color textColor)
{
var baseLine = new Vector2(pos.X, font.GetAscent(1) + pos.Y);
foreach (var rune in str.EnumerateRunes())
{
var advance = font.DrawChar(handle, rune, baseLine, 1, textColor);
baseLine += new Vector2(advance, 0);
}
}
private struct NetEntity
{
public GameTick LastUpdate;

View File

@@ -21,6 +21,7 @@ namespace Robust.Client.GameStates
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IClientNetManager _netManager = default!;
[Dependency] private readonly IClientGameStateManager _gameStateManager = default!;
[Dependency] private readonly IComponentFactory _componentFactory = default!;
private const int HistorySize = 60 * 3; // number of ticks to keep in history.
private const int TargetPayloadBps = 56000 / 8; // Target Payload size in Bytes per second. A mind-numbing fifty-six thousand bits per second, who would ever need more?
@@ -90,17 +91,14 @@ namespace Robust.Client.GameStates
sb.Append($"\n Changes:");
foreach (var compChange in entState.ComponentChanges)
{
var del = compChange.Deleted ? 'D' : 'C';
sb.Append($"\n [{del}]{compChange.NetID}:{compChange.ComponentName}");
}
}
var registration = _componentFactory.GetRegistration(compChange.NetID);
var create = compChange.Created ? 'C' : '\0';
var mod = !(compChange.Created || compChange.Created) ? 'M' : '\0';
var del = compChange.Deleted ? 'D' : '\0';
sb.Append($"\n [{create}{mod}{del}]{compChange.NetID}:{registration.Name}");
if (entState.ComponentStates is not null)
{
sb.Append($"\n States:");
foreach (var compState in entState.ComponentStates)
{
sb.Append($"\n {compState.NetID}:{compState.GetType().Name}");
if(compChange.State is not null)
sb.Append($"\n STATE:{compChange.State.GetType().Name}");
}
}
}
@@ -184,7 +182,7 @@ namespace Robust.Client.GameStates
// Draw size if above average
if (drawSizeThreshold * 1.5 < state.Payload)
{
DrawString(handle, _font, new Vector2(xOff, yoff - _font.GetLineHeight(1)), state.Payload.ToString());
handle.DrawString(_font, new Vector2(xOff, yoff - _font.GetLineHeight(1)), state.Payload.ToString());
}
// second tick marks
@@ -224,14 +222,14 @@ namespace Robust.Client.GameStates
handle.DrawLine(new Vector2(leftMargin, midYoff), new Vector2(leftMargin + width, midYoff), Color.DarkGray.WithAlpha(0.8f));
// payload text
DrawString(handle, _font, new Vector2(leftMargin + width, warnYoff), "56K");
DrawString(handle, _font, new Vector2(leftMargin + width, midYoff), "33.6K");
handle.DrawString(_font, new Vector2(leftMargin + width, warnYoff), "56K");
handle.DrawString(_font, new Vector2(leftMargin + width, midYoff), "33.6K");
// interp text info
if(lastLagY != -1)
DrawString(handle, _font, new Vector2(leftMargin + width, lastLagY), $"{lastLagMs.ToString()}ms");
handle.DrawString(_font, new Vector2(leftMargin + width, lastLagY), $"{lastLagMs.ToString()}ms");
DrawString(handle, _font, new Vector2(leftMargin, height + LowerGraphOffset), $"{_gameStateManager.CurrentBufferSize.ToString()} states");
handle.DrawString(_font, new Vector2(leftMargin, height + LowerGraphOffset), $"{_gameStateManager.CurrentBufferSize.ToString()} states");
}
protected override void DisposeBehavior()
@@ -241,17 +239,6 @@ namespace Robust.Client.GameStates
base.DisposeBehavior();
}
private void DrawString(DrawingHandleScreen handle, Font font, Vector2 pos, string str)
{
var baseLine = new Vector2(pos.X, font.GetAscent(1) + pos.Y);
foreach (var rune in str.EnumerateRunes())
{
var advance = font.DrawChar(handle, rune, baseLine, 1, Color.White);
baseLine += new Vector2(advance, 0);
}
}
private class NetShowGraphCommand : IConsoleCommand
{
public string Command => "net_graph";

View File

@@ -44,8 +44,12 @@ namespace Robust.Client.Graphics.Clyde
internal bool IsEfxSupported;
private ISawmill _openALSawmill = default!;
private void _initializeAudio()
{
_openALSawmill = Logger.GetSawmill("clyde.oal");
_audioOpenDevice();
// Create OpenAL context.
@@ -74,9 +78,9 @@ namespace Robust.Client.Graphics.Clyde
_alContextExtensions.Add(extension);
}
Logger.DebugS("clyde.oal", "OpenAL Vendor: {0}", AL.Get(ALGetString.Vendor));
Logger.DebugS("clyde.oal", "OpenAL Renderer: {0}", AL.Get(ALGetString.Renderer));
Logger.DebugS("clyde.oal", "OpenAL Version: {0}", AL.Get(ALGetString.Version));
_openALSawmill.Debug("OpenAL Vendor: {0}", AL.Get(ALGetString.Vendor));
_openALSawmill.Debug("OpenAL Renderer: {0}", AL.Get(ALGetString.Renderer));
_openALSawmill.Debug("OpenAL Version: {0}", AL.Get(ALGetString.Version));
}
private void _audioOpenDevice()
@@ -89,7 +93,7 @@ namespace Robust.Client.Graphics.Clyde
_openALDevice = ALC.OpenDevice(preferredDevice);
if (_openALDevice == IntPtr.Zero)
{
Logger.WarningS("clyde.oal", "Unable to open preferred audio device '{0}': {1}. Falling back default.",
_openALSawmill.Warning("Unable to open preferred audio device '{0}': {1}. Falling back default.",
preferredDevice, ALC.GetError(ALDevice.Null));
_openALDevice = ALC.OpenDevice(null);
@@ -153,7 +157,7 @@ namespace Robust.Client.Graphics.Clyde
// Clear out finalized audio sources.
while (_sourceDisposeQueue.TryDequeue(out var handles))
{
Logger.DebugS("clyde.oal", "Cleaning out source {0} which finalized in another thread.", handles.sourceHandle);
_openALSawmill.Debug("Cleaning out source {0} which finalized in another thread.", handles.sourceHandle);
if (IsEfxSupported) RemoveEfx(handles);
AL.DeleteSource(handles.sourceHandle);
_checkAlError();
@@ -163,7 +167,7 @@ namespace Robust.Client.Graphics.Clyde
// Clear out finalized buffered audio sources.
while (_bufferedSourceDisposeQueue.TryDequeue(out var handles))
{
Logger.DebugS("clyde.oal", "Cleaning out buffered source {0} which finalized in another thread.", handles.sourceHandle);
_openALSawmill.Debug("Cleaning out buffered source {0} which finalized in another thread.", handles.sourceHandle);
if (IsEfxSupported) RemoveEfx(handles);
AL.DeleteSource(handles.sourceHandle);
_checkAlError();
@@ -211,24 +215,24 @@ namespace Robust.Client.Graphics.Clyde
return audioSource;
}
private static void _checkAlcError(ALDevice device,
private void _checkAlcError(ALDevice device,
[CallerMemberName] string callerMember = "",
[CallerLineNumber] int callerLineNumber = -1)
{
var error = ALC.GetError(device);
if (error != AlcError.NoError)
{
Logger.ErrorS("clyde.oal", "[{0}:{1}] ALC error: {2}", callerMember, callerLineNumber, error);
_openALSawmill.Error("[{0}:{1}] ALC error: {2}", callerMember, callerLineNumber, error);
}
}
private static void _checkAlError([CallerMemberName] string callerMember = "",
private void _checkAlError([CallerMemberName] string callerMember = "",
[CallerLineNumber] int callerLineNumber = -1)
{
var error = AL.GetError();
if (error != ALError.NoError)
{
Logger.ErrorS("clyde.oal", "[{0}:{1}] AL error: {2}", callerMember, callerLineNumber, error);
_openALSawmill.Error("[{0}:{1}] AL error: {2}", callerMember, callerLineNumber, error);
}
}
@@ -330,6 +334,35 @@ namespace Robust.Client.Graphics.Clyde
return new AudioStream(handle, length, wav.NumChannels, name);
}
public AudioStream LoadAudioRaw(ReadOnlySpan<short> samples, int channels, int sampleRate, string? name = null)
{
var fmt = channels switch
{
1 => ALFormat.Mono16,
2 => ALFormat.Stereo16,
_ => throw new ArgumentOutOfRangeException(
nameof(channels), "Only stereo and mono is currently supported")
};
var buffer = AL.GenBuffer();
_checkAlError();
unsafe
{
fixed (short* ptr = samples)
{
AL.BufferData(buffer, fmt, (IntPtr) ptr, samples.Length * sizeof(short), sampleRate);
}
}
_checkAlError();
var handle = new ClydeHandle(_audioSampleBuffers.Count);
var length = TimeSpan.FromSeconds((double) samples.Length / channels / sampleRate);
_audioSampleBuffers.Add(new LoadedAudioSample(buffer));
return new AudioStream(handle, length, channels, name);
}
private sealed class LoadedAudioSample
{
public readonly int BufferHandle;
@@ -381,14 +414,14 @@ namespace Robust.Client.Graphics.Clyde
{
_checkDisposed();
AL.SourcePlay(SourceHandle);
_checkAlError();
_master._checkAlError();
}
public void StopPlaying()
{
_checkDisposed();
AL.SourceStop(SourceHandle);
_checkAlError();
_master._checkAlError();
}
public bool IsPlaying
@@ -407,14 +440,14 @@ namespace Robust.Client.Graphics.Clyde
{
_checkDisposed();
AL.GetSource(SourceHandle, ALSourceb.Looping, out var ret);
_checkAlError();
_master._checkAlError();
return ret;
}
set
{
_checkDisposed();
AL.Source(SourceHandle, ALSourceb.Looping, value);
_checkAlError();
_master._checkAlError();
}
}
@@ -422,7 +455,7 @@ namespace Robust.Client.Graphics.Clyde
{
_checkDisposed();
AL.Source(SourceHandle, ALSourceb.SourceRelative, true);
_checkAlError();
_master._checkAlError();
}
public void SetVolume(float decibels)
@@ -436,7 +469,21 @@ namespace Robust.Client.Graphics.Clyde
}
_gain = MathF.Pow(10, decibels / 10);
AL.Source(SourceHandle, ALSourcef.Gain, _gain * priorOcclusion);
_checkAlError();
_master._checkAlError();
}
public void SetVolumeDirect(float scale)
{
_checkDisposed();
var priorOcclusion = 1f;
if (!IsEfxSupported)
{
AL.GetSource(SourceHandle, ALSourcef.Gain, out var priorGain);
priorOcclusion = priorGain / _gain;
}
_gain = scale;
AL.Source(SourceHandle, ALSourcef.Gain, _gain * priorOcclusion);
_master._checkAlError();
}
public void SetOcclusion(float blocks)
@@ -453,7 +500,7 @@ namespace Robust.Client.Graphics.Clyde
gain *= gain * gain;
AL.Source(SourceHandle, ALSourcef.Gain, _gain * gain);
}
_checkAlError();
_master._checkAlError();
}
private void SetOcclusionEfx(float gain, float cutoff)
@@ -473,7 +520,7 @@ namespace Robust.Client.Graphics.Clyde
{
_checkDisposed();
AL.Source(SourceHandle, ALSourcef.SecOffset, seconds);
_checkAlError();
_master._checkAlError();
}
public bool SetPosition(Vector2 position)
@@ -499,7 +546,7 @@ namespace Robust.Client.Graphics.Clyde
#endif
AL.Source(SourceHandle, ALSource3f.Position, x, y, 0);
_checkAlError();
_master._checkAlError();
return true;
}
@@ -526,14 +573,14 @@ namespace Robust.Client.Graphics.Clyde
AL.Source(SourceHandle, ALSource3f.Velocity, x, y, 0);
_checkAlError();
_master._checkAlError();
}
public void SetPitch(float pitch)
{
_checkDisposed();
AL.Source(SourceHandle, ALSourcef.Pitch, pitch);
_checkAlError();
_master._checkAlError();
}
~AudioSource()
@@ -559,7 +606,7 @@ namespace Robust.Client.Graphics.Clyde
if (FilterHandle != 0) EFX.DeleteFilter(FilterHandle);
AL.DeleteSource(SourceHandle);
_master._audioSources.Remove(SourceHandle);
_checkAlError();
_master._checkAlError();
}
SourceHandle = -1;
@@ -609,7 +656,7 @@ namespace Robust.Client.Graphics.Clyde
_checkDisposed();
// ReSharper disable once PossibleInvalidOperationException
AL.SourcePlay(stackalloc int[] {SourceHandle!.Value});
_checkAlError();
_master._checkAlError();
}
public void StopPlaying()
@@ -617,7 +664,7 @@ namespace Robust.Client.Graphics.Clyde
_checkDisposed();
// ReSharper disable once PossibleInvalidOperationException
AL.SourceStop(SourceHandle!.Value);
_checkAlError();
_master._checkAlError();
}
public bool IsPlaying
@@ -643,7 +690,7 @@ namespace Robust.Client.Graphics.Clyde
_mono = false;
// ReSharper disable once PossibleInvalidOperationException
AL.Source(SourceHandle!.Value, ALSourceb.SourceRelative, true);
_checkAlError();
_master._checkAlError();
}
public void SetLooping()
@@ -662,7 +709,21 @@ namespace Robust.Client.Graphics.Clyde
}
_gain = MathF.Pow(10, decibels / 10);
AL.Source(SourceHandle!.Value, ALSourcef.Gain, _gain * priorOcclusion);
_checkAlError();
_master._checkAlError();
}
public void SetVolumeDirect(float scale)
{
_checkDisposed();
var priorOcclusion = 1f;
if (!IsEfxSupported)
{
AL.GetSource(SourceHandle!.Value, ALSourcef.Gain, out var priorGain);
priorOcclusion = priorGain / _gain;
}
_gain = scale;
AL.Source(SourceHandle!.Value, ALSourcef.Gain, _gain * priorOcclusion);
_master._checkAlError();
}
public void SetOcclusion(float blocks)
@@ -680,7 +741,7 @@ namespace Robust.Client.Graphics.Clyde
AL.Source(SourceHandle!.Value, ALSourcef.Gain, gain * _gain);
}
_checkAlError();
_master._checkAlError();
}
private void SetOcclusionEfx(float gain, float cutoff)
@@ -700,7 +761,7 @@ namespace Robust.Client.Graphics.Clyde
_checkDisposed();
// ReSharper disable once PossibleInvalidOperationException
AL.Source(SourceHandle!.Value, ALSourcef.SecOffset, seconds);
_checkAlError();
_master._checkAlError();
}
public bool SetPosition(Vector2 position)
@@ -717,7 +778,7 @@ namespace Robust.Client.Graphics.Clyde
_mono = true;
// ReSharper disable once PossibleInvalidOperationException
AL.Source(SourceHandle!.Value, ALSource3f.Position, x, y, 0);
_checkAlError();
_master._checkAlError();
return true;
}
@@ -744,7 +805,7 @@ namespace Robust.Client.Graphics.Clyde
AL.Source(SourceHandle!.Value, ALSource3f.Velocity, x, y, 0);
_checkAlError();
_master._checkAlError();
}
public void SetPitch(float pitch)
@@ -752,7 +813,7 @@ namespace Robust.Client.Graphics.Clyde
_checkDisposed();
// ReSharper disable once PossibleInvalidOperationException
AL.Source(SourceHandle!.Value, ALSourcef.Pitch, pitch);
_checkAlError();
_master._checkAlError();
}
~BufferedAudioSource()
@@ -783,7 +844,7 @@ namespace Robust.Client.Graphics.Clyde
AL.DeleteSource(SourceHandle.Value);
AL.DeleteBuffers(BufferHandles);
_master._bufferedAudioSources.Remove(SourceHandle.Value);
_checkAlError();
_master._checkAlError();
}
SourceHandle = null;

View File

@@ -11,7 +11,7 @@ namespace Robust.Client.Graphics.Clyde
{
static Clyde()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
if (OperatingSystem.IsWindows() &&
RuntimeInformation.ProcessArchitecture == Architecture.X64 &&
Environment.GetEnvironmentVariable("ROBUST_INTEGRATED_GPU") != "1")
{
@@ -28,20 +28,20 @@ namespace Robust.Client.Graphics.Clyde
}
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
if (OperatingSystem.IsWindows())
{
return;
}
NativeLibrary.SetDllImportResolver(typeof(GL).Assembly, (name, assembly, path) =>
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
if (OperatingSystem.IsLinux()
&& _dllMapLinux.TryGetValue(name, out var mappedName))
{
return NativeLibrary.Load(mappedName);
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)
if (OperatingSystem.IsMacOS()
&& _dllMapMacOS.TryGetValue(name, out mappedName))
{
return NativeLibrary.Load(mappedName);

View File

@@ -366,33 +366,14 @@ namespace Robust.Client.Graphics.Clyde
private void ProcessSpriteEntities(MapId map, Box2 worldBounds,
RefList<(SpriteComponent sprite, Matrix3 matrix, Angle worldRot, float yWorldPos)> list)
{
var spriteSystem = _entitySystemManager.GetEntitySystem<RenderingTreeSystem>();
foreach (var gridId in _mapManager.FindGridIdsIntersecting(map, worldBounds, true))
foreach (var comp in _entitySystemManager.GetEntitySystem<RenderingTreeSystem>().GetRenderTrees(map, worldBounds))
{
Box2 gridBounds;
var bounds = worldBounds.Translated(-comp.Owner.Transform.WorldPosition);
if (gridId == GridId.Invalid)
{
gridBounds = worldBounds;
}
else
{
gridBounds = worldBounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition);
}
var tree = spriteSystem.GetSpriteTreeForMap(map, gridId);
tree.QueryAabb(ref list, ((
comp.SpriteTree.QueryAabb(ref list, ((
ref RefList<(SpriteComponent sprite, Matrix3 matrix, Angle worldRot, float yWorldPos)> state,
in SpriteComponent value) =>
{
// TODO: Probably value in storing this as its own DynamicTree
if (value.ContainerOccluded || !value.Visible)
{
return true;
}
var entity = value.Owner;
var transform = entity.Transform;
@@ -404,7 +385,7 @@ namespace Robust.Client.Graphics.Clyde
entry.yWorldPos = worldPos.Y;
return true;
}), gridBounds, approx: true);
}), bounds, true);
}
}

View File

@@ -149,16 +149,16 @@ namespace Robust.Client.Graphics.Clyde
// FOV FBO.
_fovRenderTarget = CreateRenderTarget((FovMapSize, 2),
new RenderTargetFormatParameters(_hasGLFloatFramebuffers ? RenderTargetColorFormat.RG32F : RenderTargetColorFormat.Rgba8, true),
new TextureSampleParameters {WrapMode = TextureWrapMode.Repeat},
new TextureSampleParameters { WrapMode = TextureWrapMode.Repeat },
nameof(_fovRenderTarget));
if (_hasGLSamplerObjects)
{
_fovFilterSampler = new GLHandle(GL.GenSampler());
GL.SamplerParameter(_fovFilterSampler.Handle, SamplerParameterName.TextureMagFilter, (int) All.Linear);
GL.SamplerParameter(_fovFilterSampler.Handle, SamplerParameterName.TextureMinFilter, (int) All.Linear);
GL.SamplerParameter(_fovFilterSampler.Handle, SamplerParameterName.TextureWrapS, (int) All.Repeat);
GL.SamplerParameter(_fovFilterSampler.Handle, SamplerParameterName.TextureWrapT, (int) All.Repeat);
GL.SamplerParameter(_fovFilterSampler.Handle, SamplerParameterName.TextureMagFilter, (int)All.Linear);
GL.SamplerParameter(_fovFilterSampler.Handle, SamplerParameterName.TextureMinFilter, (int)All.Linear);
GL.SamplerParameter(_fovFilterSampler.Handle, SamplerParameterName.TextureWrapS, (int)All.Repeat);
GL.SamplerParameter(_fovFilterSampler.Handle, SamplerParameterName.TextureWrapT, (int)All.Repeat);
CheckGlError();
}
@@ -180,20 +180,17 @@ namespace Robust.Client.Graphics.Clyde
_fovCalculationProgram = _compileProgram(depthVert, depthFrag, attribLocations, "Shadow Depth Program");
var debugShader = _resourceCache.GetResource<ShaderSourceResource>("/Shaders/Internal/depth-debug.swsl");
_fovDebugShaderInstance = (ClydeShaderInstance) InstanceShader(debugShader.ClydeHandle);
_fovDebugShaderInstance = (ClydeShaderInstance)InstanceShader(debugShader.ClydeHandle);
ClydeHandle LoadShaderHandle(string path)
{
try
if (_resourceCache.TryGetResource(path, out ShaderSourceResource? resource))
{
var shaderSource = _resourceCache.GetResource<ShaderSourceResource>(path);
return shaderSource.ClydeHandle;
}
catch (Exception ex)
{
Logger.Warning($"Can't load shader {path}\n{ex.GetType().Name}: {ex.Message}");
return default;
return resource.ClydeHandle;
}
Logger.Warning($"Can't load shader {path}\n");
return default;
}
_lightSoftShaderHandle = LoadShaderHandle("/Shaders/Internal/light-soft.swsl");
@@ -214,7 +211,7 @@ namespace Robust.Client.Graphics.Clyde
{
// Calculate maximum distance for the projection based on screen size.
var screenSizeCut = viewport.Size / EyeManager.PixelsPerMeter;
var maxDist = (float) Math.Max(screenSizeCut.X, screenSizeCut.Y);
var maxDist = (float)Math.Max(screenSizeCut.X, screenSizeCut.Y);
// FOV is rendered twice.
// Once with back face culling like regular lighting.
@@ -497,24 +494,16 @@ namespace Robust.Client.Graphics.Clyde
GetLightsToRender(MapId map, in Box2 worldBounds)
{
var renderingTreeSystem = _entitySystemManager.GetEntitySystem<RenderingTreeSystem>();
var enlargedBounds = worldBounds.Enlarged(renderingTreeSystem.MaxLightRadius);
// Use worldbounds for this one as we only care if the light intersects our actual bounds
var state = (this, worldBounds, count: 0);
foreach (var gridId in _mapManager.FindGridIdsIntersecting(map, worldBounds, true))
foreach (var comp in renderingTreeSystem.GetRenderTrees(map, enlargedBounds))
{
Box2 gridBounds;
var bounds = worldBounds.Translated(-comp.Owner.Transform.WorldPosition);
if (gridId == GridId.Invalid)
{
gridBounds = worldBounds;
}
else
{
gridBounds = worldBounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition);
}
var lightTree = renderingTreeSystem.GetLightTreeForMap(map, gridId);
lightTree.QueryAabb(ref state, (ref (Clyde clyde, Box2 worldBounds, int count) state, in PointLightComponent light) =>
comp.LightTree.QueryAabb(ref state, (ref (Clyde clyde, Box2 worldBounds, int count) state, in PointLightComponent light) =>
{
var transform = light.Owner.Transform;
@@ -524,12 +513,6 @@ namespace Robust.Client.Graphics.Clyde
return false;
}
// TODO: Don't insert into trees for these, same as sprites.
if (!light.Enabled || light.ContainerOccluded)
{
return true;
}
var lightPos = transform.WorldMatrix.Transform(light.Offset);
var circle = new Circle(lightPos, light.Radius);
@@ -544,7 +527,7 @@ namespace Robust.Client.Graphics.Clyde
state.clyde._lightsToRenderList[state.count++] = (light, lightPos, distanceSquared);
return true;
}, gridBounds);
}, bounds);
}
if (state.count > _maxLightsPerScene)
@@ -590,7 +573,7 @@ namespace Robust.Client.Graphics.Clyde
SetupGlobalUniformsImmediate(shader, viewport.LightRenderTarget.Texture);
shader.SetUniformMaybe("size", (Vector2) viewport.WallBleedIntermediateRenderTarget1.Size);
shader.SetUniformMaybe("size", (Vector2)viewport.WallBleedIntermediateRenderTarget1.Size);
shader.SetUniformTextureMaybe(UniIMainTexture, TextureUnit.Texture0);
var size = viewport.WallBleedIntermediateRenderTarget1.Size;
@@ -809,7 +792,7 @@ namespace Robust.Client.Graphics.Clyde
occluderTree.QueryAabb((in OccluderComponent sOccluder) =>
{
var occluder = (ClientOccluderComponent) sOccluder;
var occluder = (ClientOccluderComponent)sOccluder;
var transform = occluder.Owner.Transform;
if (!occluder.Enabled)
{
@@ -893,11 +876,11 @@ namespace Robust.Client.Graphics.Clyde
// DddD
// HHhh
// deflection
arrayVIBuffer[avi++] = (byte) ((((vi + 1) & 2) != 0) ? 0 : 255);
arrayVIBuffer[avi++] = (byte)((((vi + 1) & 2) != 0) ? 0 : 255);
// height
arrayVIBuffer[avi++] = (byte) (((vi & 2) != 0) ? 0 : 255);
arrayVIBuffer[avi++] = (byte)(((vi & 2) != 0) ? 0 : 255);
}
QuadBatchIndexWrite(indexBuffer, ref ii, (ushort) aiBase);
QuadBatchIndexWrite(indexBuffer, ref ii, (ushort)aiBase);
}
// North face (TL/TR)
@@ -931,7 +914,7 @@ namespace Robust.Client.Graphics.Clyde
arrayMaskBuffer[ami + 3] = new Vector2(blX, blY);
// Generate mask indices.
QuadBatchIndexWrite(indexMaskBuffer, ref imi, (ushort) ami);
QuadBatchIndexWrite(indexMaskBuffer, ref imi, (ushort)ami);
ami += 4;
@@ -973,7 +956,7 @@ namespace Robust.Client.Graphics.Clyde
var lightMapSize = GetLightMapSize(viewport.Size);
var lightMapSizeQuart = GetLightMapSize(viewport.Size, true);
var lightMapColorFormat = _hasGLFloatFramebuffers ? RenderTargetColorFormat.R11FG11FB10F : RenderTargetColorFormat.Rgba8;
var lightMapSampleParameters = new TextureSampleParameters {Filter = true};
var lightMapSampleParameters = new TextureSampleParameters { Filter = true };
viewport.LightRenderTarget?.Dispose();
viewport.WallMaskRenderTarget?.Dispose();
@@ -1010,14 +993,14 @@ namespace Robust.Client.Graphics.Clyde
private Vector2i GetLightMapSize(Vector2i screenSize, bool furtherDivide = false)
{
var divider = (float) _lightmapDivider;
var divider = (float)_lightmapDivider;
if (furtherDivide)
{
divider *= 2;
}
var w = (int) Math.Ceiling(screenSize.X / divider);
var h = (int) Math.Ceiling(screenSize.Y / divider);
var w = (int)Math.Ceiling(screenSize.X / divider);
var h = (int)Math.Ceiling(screenSize.Y / divider);
return (w, h);
}
@@ -1043,7 +1026,7 @@ namespace Robust.Client.Graphics.Clyde
// Shadow FBO.
_shadowRenderTarget = CreateRenderTarget((ShadowMapSize, _maxLightsPerScene),
new RenderTargetFormatParameters(_hasGLFloatFramebuffers ? RenderTargetColorFormat.RG32F : RenderTargetColorFormat.Rgba8, true),
new TextureSampleParameters {WrapMode = TextureWrapMode.Repeat, Filter = true},
new TextureSampleParameters { WrapMode = TextureWrapMode.Repeat, Filter = true },
nameof(_shadowRenderTarget));
}

View File

@@ -131,7 +131,7 @@ namespace Robust.Client.Graphics.Clyde
if (!succeeded)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
if (OperatingSystem.IsWindows())
{
var msgBoxContent = "Failed to create the game window. " +
"This probably means your GPU is too old to play the game. " +
@@ -164,7 +164,7 @@ namespace Robust.Client.Graphics.Clyde
private IEnumerable<Image<Rgba32>> LoadWindowIcons()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
if (OperatingSystem.IsMacOS())
{
// Does nothing on macOS so don't bother.
yield break;
@@ -470,18 +470,20 @@ namespace Robust.Client.Graphics.Clyde
private sealed class MonitorHandle : IClydeMonitor
{
public MonitorHandle(int id, string name, Vector2i size, int refreshRate)
public MonitorHandle(int id, string name, Vector2i size, int refreshRate, VideoMode[] videoModes)
{
Id = id;
Name = name;
Size = size;
RefreshRate = refreshRate;
VideoModes = videoModes;
}
public int Id { get; }
public string Name { get; }
public Vector2i Size { get; }
public int RefreshRate { get; }
public IEnumerable<VideoMode> VideoModes { get; }
}
private abstract class MonitorReg

View File

@@ -59,7 +59,6 @@ namespace Robust.Client.Graphics.Clyde
private bool _enableSoftShadows = true;
private bool _checkGLErrors;
private bool _initialized;
private Thread? _gameThread;
@@ -105,7 +104,6 @@ namespace Robust.Client.Graphics.Clyde
return false;
_initializeAudio();
_initialized = true;
return true;
}

View File

@@ -48,14 +48,14 @@ namespace Robust.Client.Graphics.Clyde
public IClydeDebugInfo DebugInfo { get; } = new DummyDebugInfo();
public IClydeDebugStats DebugStats { get; } = new DummyDebugStats();
public event Action<TextEventArgs>? TextEntered;
public event Action<MouseMoveEventArgs>? MouseMove;
public event Action<MouseEnterLeaveEventArgs>? MouseEnterLeave;
public event Action<KeyEventArgs>? KeyUp;
public event Action<KeyEventArgs>? KeyDown;
public event Action<MouseWheelEventArgs>? MouseWheel;
public event Action<WindowClosedEventArgs>? CloseWindow;
public event Action<WindowDestroyedEventArgs>? DestroyWindow;
public event Action<TextEventArgs>? TextEntered { add { } remove { } }
public event Action<MouseMoveEventArgs>? MouseMove { add { } remove { } }
public event Action<MouseEnterLeaveEventArgs>? MouseEnterLeave { add { } remove { } }
public event Action<KeyEventArgs>? KeyUp { add { } remove { } }
public event Action<KeyEventArgs>? KeyDown { add { } remove { } }
public event Action<MouseWheelEventArgs>? MouseWheel { add { } remove { } }
public event Action<WindowClosedEventArgs>? CloseWindow { add { } remove { } }
public event Action<WindowDestroyedEventArgs>? DestroyWindow { add { } remove { } }
public Texture GetStockTexture(ClydeStockTexture stockTexture)
{
@@ -248,6 +248,12 @@ namespace Robust.Client.Graphics.Clyde
return new(default, default, 1, name);
}
public AudioStream LoadAudioRaw(ReadOnlySpan<short> samples, int channels, int sampleRate, string? name = null)
{
// TODO: Might wanna actually load this so the length gets reported correctly.
return new(default, default, channels, name);
}
public IClydeAudioSource CreateAudioSource(AudioStream stream)
{
return DummyAudioSource.Instance;
@@ -323,6 +329,11 @@ namespace Robust.Client.Graphics.Clyde
// Nada.
}
public void SetVolumeDirect(float scale)
{
// Nada.
}
public void SetOcclusion(float blocks)
{
// Nada.
@@ -594,7 +605,7 @@ namespace Robust.Client.Graphics.Clyde
public bool IsVisible { get; set; } = true;
public Vector2 ContentScale => Vector2.One;
public bool DisposeOnClose { get; set; }
public event Action<WindowClosedEventArgs>? Closed;
public event Action<WindowClosedEventArgs>? Closed { add { } remove { } }
public void MaximizeOnMonitor(IClydeMonitor monitor)
{

View File

@@ -71,7 +71,7 @@ namespace Robust.Client.Graphics.Clyde
var impl = (CursorImpl) cursor;
DebugTools.Assert(impl.Owner == this);
if (impl.Id == null)
if (impl.Id == default)
{
throw new ObjectDisposedException(nameof(cursor));
}

View File

@@ -1,6 +1,8 @@
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using OpenToolkit.GraphicsLibraryFramework;
using Robust.Shared.Utility;
using GlfwVideoMode = OpenToolkit.GraphicsLibraryFramework.VideoMode;
namespace Robust.Client.Graphics.Clyde
{
@@ -18,7 +20,6 @@ namespace Robust.Client.Graphics.Clyde
private readonly Dictionary<int, WinThreadMonitorReg> _winThreadMonitors = new();
// Can't use ClydeHandle because it's 64 bit.
// TODO: this should be MONITOR ID.
private int _nextMonitorId = 1;
private int _primaryMonitorId;
private readonly Dictionary<int, GlfwMonitorReg> _monitors = new();
@@ -39,6 +40,7 @@ namespace Robust.Client.Graphics.Clyde
ProcessEvents();
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void WinThreadSetupMonitor(Monitor* monitor)
{
var id = _nextMonitorId++;
@@ -48,12 +50,31 @@ namespace Robust.Client.Graphics.Clyde
var name = GLFW.GetMonitorName(monitor);
var videoMode = GLFW.GetVideoMode(monitor);
var modesPtr = GLFW.GetVideoModesRaw(monitor, out var modeCount);
var modes = new VideoMode[modeCount];
for (var i = 0; i < modes.Length; i++)
{
modes[i] = ConvertVideoMode(modesPtr[i]);
}
GLFW.SetMonitorUserPointer(monitor, (void*) id);
_winThreadMonitors.Add(id, new WinThreadMonitorReg {Ptr = monitor});
SendEvent(new EventMonitorSetup(id, name, *videoMode));
SendEvent(new EventMonitorSetup(id, name, ConvertVideoMode(*videoMode), modes));
}
private static VideoMode ConvertVideoMode(in GlfwVideoMode mode)
{
return new()
{
Width = (ushort) mode.Width,
Height = (ushort) mode.Height,
RedBits = (byte) mode.RedBits,
RefreshRate = (ushort) mode.RefreshRate,
GreenBits = (byte) mode.GreenBits,
BlueBits = (byte) mode.BlueBits,
};
}
private void ProcessSetupMonitor(EventMonitorSetup ev)
@@ -61,8 +82,9 @@ namespace Robust.Client.Graphics.Clyde
var impl = new MonitorHandle(
ev.Id,
ev.Name,
(ev.Mode.Width, ev.Mode.Height),
ev.Mode.RefreshRate);
(ev.CurrentMode.Width, ev.CurrentMode.Height),
ev.CurrentMode.RefreshRate,
ev.AllModes);
_clyde._monitorHandles.Add(impl);
_monitors[ev.Id] = new GlfwMonitorReg

View File

@@ -210,7 +210,8 @@ namespace Robust.Client.Graphics.Clyde
(
int Id,
string Name,
VideoMode Mode
VideoMode CurrentMode,
VideoMode[] AllModes
) : EventBase;
private record EventMonitorDestroy

View File

@@ -1,4 +1,4 @@
using System.Runtime.InteropServices;
using System;
using System.Threading.Channels;
using System.Threading.Tasks;
using OpenToolkit.GraphicsLibraryFramework;
@@ -13,11 +13,6 @@ namespace Robust.Client.Graphics.Clyde
{
private sealed partial class GlfwWindowingImpl
{
// glfwPostEmptyEvent is broken on macOS and crashes when not called from the main thread
// (despite what the docs claim, and yes this makes it useless).
// Because of this, we just forego it and use glfwWaitEventsTimeout on macOS instead.
private static readonly bool IsMacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
private bool _windowingRunning;
private ChannelWriter<CmdBase> _cmdWriter = default!;
private ChannelReader<CmdBase> _cmdReader = default!;
@@ -53,7 +48,10 @@ namespace Robust.Client.Graphics.Clyde
while (_windowingRunning)
{
if (IsMacOS)
// glfwPostEmptyEvent is broken on macOS and crashes when not called from the main thread
// (despite what the docs claim, and yes this makes it useless).
// Because of this, we just forego it and use glfwWaitEventsTimeout on macOS instead.
if (OperatingSystem.IsMacOS())
GLFW.WaitEventsTimeout(0.008);
else
GLFW.WaitEvents();
@@ -157,7 +155,7 @@ namespace Robust.Client.Graphics.Clyde
_cmdWriter.TryWrite(cmd);
// Post empty event to unstuck WaitEvents if necessary.
if (!IsMacOS)
if (!OperatingSystem.IsMacOS())
GLFW.PostEmptyEvent();
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
using System.Threading.Tasks;
using OpenToolkit;
using OpenToolkit.Graphics.OpenGL4;
@@ -33,6 +34,7 @@ namespace Robust.Client.Graphics.Clyde
private GlfwWindowReg? _mainWindow;
private GlfwBindingsContext _mainGraphicsContext = default!;
private int _nextWindowId = 1;
private static bool _eglLoaded;
public async Task<WindowHandle> WindowCreate(WindowCreateParameters parameters)
{
@@ -474,6 +476,20 @@ namespace Robust.Client.Graphics.Clyde
GLFW.WindowHint(WindowHintContextApi.ContextCreationApi, ContextApi.EglContextApi);
GLFW.WindowHint(WindowHintOpenGlProfile.OpenGlProfile, OpenGlProfile.Any);
GLFW.WindowHint(WindowHintBool.SrgbCapable, false);
if (!_eglLoaded && OperatingSystem.IsWindows())
{
// On non-published builds (so, development), GLFW can't find libEGL.dll
// because it'll be in runtimes/<rid>/native/ instead of next to the actual executable.
// We manually preload the library here so that GLFW will find it when it does its thing.
NativeLibrary.TryLoad(
"libEGL.dll",
typeof(Clyde).Assembly,
DllImportSearchPath.SafeDirectories,
out _);
_eglLoaded = true;
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Text;
using Robust.Shared.Maths;
namespace Robust.Client.Graphics
@@ -39,5 +40,57 @@ namespace Robust.Client.Graphics
DrawPrimitiveTopology type = filled ? DrawPrimitiveTopology.TriangleFan : DrawPrimitiveTopology.LineStrip;
DrawPrimitives(type, buffer, color);
}
/// <summary>
/// Draw a simple string to the screen at the specified position.
/// </summary>
/// <remarks>
/// This method is primarily intended for debug purposes and does not handle things like UI scaling.
/// </remarks>
/// <returns>
/// The space taken up (horizontal and vertical) by the text.
/// </returns>
/// <param name="font">The font to render with.</param>
/// <param name="pos">The top-left corner to start drawing text at.</param>
/// <param name="str">The text to draw.</param>
/// <param name="color">The color of text to draw.</param>
public Vector2 DrawString(Font font, Vector2 pos, string str, Color color)
{
var advanceTotal = Vector2.Zero;
var baseLine = new Vector2(pos.X, font.GetAscent(1) + pos.Y);
var lineHeight = font.GetLineHeight(1);
foreach (var rune in str.EnumerateRunes())
{
if (rune == new Rune('\n'))
{
baseLine.X = pos.X;
baseLine.Y += lineHeight;
advanceTotal.Y += lineHeight;
continue;
}
var advance = font.DrawChar(this, rune, baseLine, 1, color);
advanceTotal.X += advance;
baseLine += new Vector2(advance, 0);
}
return advanceTotal;
}
/// <summary>
/// Draw a simple string to the screen at the specified position.
/// </summary>
/// <remarks>
/// This method is primarily intended for debug purposes and does not handle things like UI scaling.
/// </remarks>
/// <returns>
/// The space taken up (horizontal and vertical) by the text.
/// </returns>
/// <param name="font">The font to render with.</param>
/// <param name="pos">The top-left corner to start drawing text at.</param>
/// <param name="str">The text to draw.</param>
public Vector2 DrawString(Font font, Vector2 pos, string str)
=> DrawString(font, pos, str, Color.White);
}
}

View File

@@ -1,4 +1,5 @@
using System.IO;
using System;
using System.IO;
using Robust.Client.Audio;
namespace Robust.Client.Graphics
@@ -8,6 +9,7 @@ namespace Robust.Client.Graphics
// AUDIO SYSTEM DOWN BELOW.
AudioStream LoadAudioOggVorbis(Stream stream, string? name = null);
AudioStream LoadAudioWav(Stream stream, string? name = null);
AudioStream LoadAudioRaw(ReadOnlySpan<short> samples, int channels, int sampleRate, string? name = null);
void SetMasterVolume(float newVolume);

View File

@@ -18,6 +18,7 @@ namespace Robust.Client.Graphics
void SetPitch(float pitch);
void SetGlobal();
void SetVolume(float decibels);
void SetVolumeDirect(float decibels);
void SetOcclusion(float blocks);
void SetPlaybackPosition(float seconds);
void SetVelocity(Vector2 velocity);

View File

@@ -1,4 +1,5 @@
using Robust.Shared.Maths;
using System.Collections.Generic;
using Robust.Shared.Maths;
namespace Robust.Client.Graphics
{
@@ -14,5 +15,7 @@ namespace Robust.Client.Graphics
string Name { get; }
Vector2i Size { get; }
int RefreshRate { get; }
IEnumerable<VideoMode> VideoModes { get; }
}
}

View File

@@ -0,0 +1,12 @@
namespace Robust.Client.Graphics
{
public struct VideoMode
{
public ushort Width;
public ushort Height;
public ushort RefreshRate;
public byte RedBits;
public byte BlueBits;
public byte GreenBits;
}
}

View File

@@ -10,8 +10,7 @@ namespace Robust.Client
GameControllerOptions Options { get; }
bool ContentStart { get; set; }
void SetCommandLineArgs(CommandLineArgs args);
bool LoadConfigAndUserData { get; set; }
void Run(GameController.DisplayMode mode, Func<ILogHandler>? logHandlerFactory = null);
void Run(GameController.DisplayMode mode, GameControllerOptions options, Func<ILogHandler>? logHandlerFactory = null);
void KeyDown(KeyEventArgs keyEvent);
void KeyUp(KeyEventArgs keyEvent);
void TextEntered(TextEventArgs textEvent);

View File

@@ -1,7 +1,6 @@
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System;
using System.Collections.Generic;
using Robust.Shared.Localization;
using Robust.Shared.Map;
namespace Robust.Client.Input
{
@@ -179,9 +178,9 @@ namespace Robust.Client.Input
var locId = $"input-key-{key}";
if (key == Key.LSystem || key == Key.RSystem)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
if (OperatingSystem.IsWindows())
locId += "-win";
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
else if (OperatingSystem.IsMacOS())
locId += "-mac";
else
locId += "-linux";

View File

@@ -16,11 +16,12 @@ using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Value;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using YamlDotNet.Core;
@@ -145,13 +146,13 @@ namespace Robust.Client.Input
.Where(p => _bindingsByFunction[p].Count == 0)
.ToArray();
mapping.AddNode("version", new ValueDataNode("1"));
mapping.AddNode("binds", serializationManager.WriteValue(modifiedBindings));
mapping.AddNode("leaveEmpty", serializationManager.WriteValue(leaveEmpty));
mapping.Add("version", new ValueDataNode("1"));
mapping.Add("binds", serializationManager.WriteValue(modifiedBindings));
mapping.Add("leaveEmpty", serializationManager.WriteValue(leaveEmpty));
var path = new ResourcePath(KeybindsPath);
using var writer = new StreamWriter(_resourceMan.UserData.Create(path));
var stream = new YamlStream {new(mapping.ToMappingNode())};
var stream = new YamlStream {new(mapping.ToYaml())};
stream.Save(new YamlMappingFix(new Emitter(writer)), false);
}
@@ -333,6 +334,7 @@ namespace Robust.Client.Input
{
// christ this crap *is* re-entrant thanks to PlacementManager and
// I honestly have no idea what the best solution here is.
// note from the future: context switches won't cause re-entrancy anymore because InputContextContainer defers context switches
DebugTools.Assert(!_currentlyFindingViewport, "Re-entrant key events??");
try
@@ -340,6 +342,8 @@ namespace Robust.Client.Input
// This is terrible but anyways.
// This flag keeps track of "did a viewport fire the key up for us" so we know we don't do it again.
_currentlyFindingViewport = true;
// And this stops context switches from causing crashes
Contexts.DeferringEnabled = true;
binding.State = state;
@@ -360,12 +364,14 @@ namespace Robust.Client.Input
finally
{
_currentlyFindingViewport = false;
Contexts.DeferringEnabled = false;
}
}
public void ViewportKeyEvent(Control? viewport, BoundKeyEventArgs eventArgs)
{
_currentlyFindingViewport = false;
Contexts.DeferringEnabled = false;
var cmd = GetInputCommand(eventArgs.Function);
// TODO: Allow input commands to still get forwarded to server if necessary.
@@ -453,7 +459,7 @@ namespace Robust.Client.Input
var robustMapping = mapping.ToDataNode() as MappingDataNode;
if (robustMapping == null) throw new InvalidOperationException();
if (robustMapping.TryGetNode("binds", out var BaseKeyRegsNode))
if (robustMapping.TryGet("binds", out var BaseKeyRegsNode))
{
var baseKeyRegs = serializationManager.ReadValueOrThrow<KeyBindingRegistration[]>(BaseKeyRegsNode);
@@ -482,7 +488,7 @@ namespace Robust.Client.Input
}
}
if (userData && robustMapping.TryGetNode("leaveEmpty", out var node))
if (userData && robustMapping.TryGet("leaveEmpty", out var node))
{
var leaveEmpty = serializationManager.ReadValueOrThrow<BoundKeyFunction[]>(node);

View File

@@ -1,72 +1,111 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Utility;
namespace Robust.Client.Map
{
internal class ClientMapManager : MapManager, IClientMapManager
{
[Dependency] private readonly INetManager _netManager = default!;
public void ApplyGameStatePre(GameStateMapData? data)
public void ApplyGameStatePre(GameStateMapData? data, EntityState[]? entityStates)
{
// There was no map data this tick, so nothing to do.
if(data == null)
return;
var createdGrids = data.CreatedGrids != null
? new Dictionary<GridId, GameStateMapData.GridCreationDatum>(data.CreatedGrids)
: null;
// First we need to figure out all the NEW MAPS.
if(data.CreatedMaps != null)
{
DebugTools.Assert(entityStates is not null, "Received new maps, but no entity state.");
foreach (var mapId in data.CreatedMaps)
{
// map already exists from a previous state.
if (_maps.Contains(mapId))
{
continue;
}
CreateMap(mapId);
EntityUid mapEuid = default;
//get shared euid of map comp entity
foreach (var entityState in entityStates!)
{
if(entityState.ComponentChanges is null)
continue;
foreach (var compChange in entityState.ComponentChanges)
{
if (compChange.State is not MapComponentState mapCompState || mapCompState.MapId != mapId)
continue;
mapEuid = entityState.Uid;
goto BreakMapEntSearch;
}
}
BreakMapEntSearch:
DebugTools.Assert(mapEuid != default, $"Could not find corresponding entity state for new map {mapId}.");
CreateMap(mapId, mapEuid);
}
}
// Then make all the grids.
if(data.CreatedGrids != null)
{
var gridData = data.GridData != null
? new Dictionary<GridId, GameStateMapData.GridDatum>(data.GridData)
: null;
DebugTools.AssertNotNull(createdGrids);
DebugTools.Assert(data.GridData is not null, "Received new grids, but GridData was null.");
foreach (var (gridId, creationDatum) in data.CreatedGrids)
{
if (_grids.ContainsKey(gridId))
{
continue;
EntityUid gridEuid = default;
//get shared euid of map comp entity
foreach (var entityState in entityStates!)
{
if (entityState.ComponentChanges is null)
continue;
foreach (var compState in entityState.ComponentChanges)
{
if (compState.State is not MapGridComponentState gridCompState || gridCompState.GridIndex != gridId)
continue;
gridEuid = entityState.Uid;
goto BreakGridEntSearch;
}
}
BreakGridEntSearch:
DebugTools.Assert(gridEuid != default, $"Could not find corresponding entity state for new grid {gridId}.");
MapId gridMapId = default;
foreach (var kvData in data.GridData!)
{
if (kvData.Key != gridId)
continue;
gridMapId = kvData.Value.Coordinates.MapId;
break;
}
CreateGrid(gridData![gridId].Coordinates.MapId, gridId, creationDatum.ChunkSize);
DebugTools.Assert(gridMapId != default, $"Could not find corresponding gridData for new grid {gridId}.");
CreateGrid(gridMapId, gridId, creationDatum.ChunkSize, gridEuid);
}
}
// Process all grid updates.
if(data.GridData != null)
{
SuppressOnTileChanged = true;
// Ok good all the grids and maps exist now.
foreach (var (gridId, gridDatum) in data.GridData)
{
var grid = _grids[gridId];
if (grid.ParentMapId != gridDatum.Coordinates.MapId)
{
@@ -112,99 +151,9 @@ namespace Robust.Client.Map
public void ApplyGameStatePost(GameStateMapData? data)
{
DebugTools.Assert(_netManager.IsClient, "Only the client should call this.");
if(data == null) // if there is no data, there is nothing to do!
return;
// maps created on the client in pre-state are linked to client entities
// resolve new maps with their shared component that the server just gave us
// and delete the client entities
if (data.CreatedMaps != null)
{
foreach (var mapId in data.CreatedMaps)
{
// CreateMap should have set this
DebugTools.Assert(_mapEntities.ContainsKey(mapId));
// this was already linked in a previous state.
if(!_mapEntities[mapId].IsClientSide())
continue;
// get the existing client entity for the map.
var cEntity = EntityManager.GetEntity(_mapEntities[mapId]);
// locate the entity that represents this map that was just sent to us
IEntity? sharedMapEntity = null;
var mapComps = EntityManager.ComponentManager.EntityQuery<IMapComponent>(true);
foreach (var mapComp in mapComps)
{
if (!mapComp.Owner.Uid.IsClientSide() && mapComp.WorldMap == mapId)
{
sharedMapEntity = mapComp.Owner;
_mapEntities[mapId] = mapComp.Owner.Uid;
Logger.DebugS("map", $"Map {mapId} pivoted bound entity from {cEntity.Uid} to {mapComp.Owner.Uid}.");
break;
}
}
// verify shared entity was found (the server sent us one)
DebugTools.AssertNotNull(sharedMapEntity);
DebugTools.Assert(!_mapEntities[mapId].IsClientSide());
// Transfer client child grids made in GameStatePre to the shared component
// so they are not deleted
foreach (var childGridTrans in cEntity.Transform.Children.ToList())
{
childGridTrans.AttachParent(sharedMapEntity!);
}
// remove client entity
var cGridComp = cEntity.GetComponent<IMapComponent>();
cGridComp.ClearMapId();
cEntity.Delete();
}
}
// grids created on the client in pre-state are linked to client entities
// resolve new grids with their shared component that the server just gave us
// and delete the client entities
if (data.CreatedGrids != null)
{
foreach (var kvNewGrid in data.CreatedGrids)
{
var grid = _grids[kvNewGrid.Key];
// this was already linked in a previous state.
if(!grid.GridEntityId.IsClientSide())
continue;
// remove the existing client entity.
var cEntity = EntityManager.GetEntity(grid.GridEntityId);
var cGridComp = cEntity.GetComponent<IMapGridComponent>();
// prevents us from deleting the grid when deleting the grid entity
if(cEntity.Uid.IsClientSide())
cGridComp.ClearGridId();
cEntity.Delete(); // normal entities are already parented to the shared comp, client comp has no children
var gridComps = EntityManager.ComponentManager.EntityQuery<IMapGridComponent>(true);
foreach (var gridComp in gridComps)
{
if (gridComp.GridIndex == kvNewGrid.Key)
{
grid.GridEntityId = gridComp.Owner.Uid;
Logger.DebugS("map", $"Grid {grid.Index} pivoted bound entity from {cEntity.Uid} to {grid.GridEntityId}.");
break;
}
}
DebugTools.Assert(!grid.GridEntityId.IsClientSide());
}
}
if(data.DeletedGrids != null)
{
foreach (var grid in data.DeletedGrids)

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
@@ -17,7 +18,7 @@ namespace Robust.Client.Map
[Dependency] private readonly IResourceCache _resourceCache = default!;
private Texture? _tileTextureAtlas;
public Texture TileTextureAtlas => _tileTextureAtlas ?? Texture.Transparent;
private readonly Dictionary<ushort, Box2> _tileRegions = new();
@@ -61,7 +62,7 @@ namespace Robust.Client.Map
var row = i / dimensionX;
Image<Rgba32> image;
using (var stream = _resourceCache.ContentFileRead($"/Textures/Constructible/Tiles/{def.SpriteName}.png"))
using (var stream = _resourceCache.ContentFileRead(Path.Join(def.Path, $"{def.SpriteName}.png")))
{
image = Image.Load<Rgba32>(stream);
}

View File

@@ -1,3 +1,4 @@
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Map;
@@ -7,7 +8,7 @@ namespace Robust.Client.Map
{
// Two methods here, so that new grids etc can be made BEFORE entities get states applied,
// but old ones can be deleted after.
void ApplyGameStatePre(GameStateMapData? data);
void ApplyGameStatePre(GameStateMapData? data, EntityState[]? entityStates);
void ApplyGameStatePost(GameStateMapData? data);
}
}

View File

@@ -52,7 +52,7 @@ namespace Robust.Client.Physics
public override void Shutdown()
{
base.Shutdown();
UnsubscribeLocalEvent<IslandSolveMessage>();
IoCManager.Resolve<IOverlayManager>().RemoveOverlay(typeof(PhysicsIslandOverlay));
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using Robust.Client.GameObjects;
@@ -25,7 +25,6 @@ namespace Robust.Client.Placement
{
public partial class PlacementManager : IPlacementManager, IDisposable
{
[Dependency] public readonly IPhysicsManager PhysicsManager = default!;
[Dependency] private readonly IClientNetManager NetworkManager = default!;
[Dependency] public readonly IPlayerManager PlayerManager = default!;
[Dependency] public readonly IResourceCache ResourceCache = default!;
@@ -156,7 +155,7 @@ namespace Robust.Client.Placement
{
_drawingShader = _prototypeManager.Index<ShaderPrototype>("unshaded").Instance();
NetworkManager.RegisterNetMessage<MsgPlacement>(MsgPlacement.NAME, HandlePlacementMessage);
NetworkManager.RegisterNetMessage<MsgPlacement>(HandlePlacementMessage);
_modeDictionary.Clear();
foreach (var type in ReflectionManager.GetAllChildren<PlacementMode>())

View File

@@ -2,13 +2,14 @@ using System;
using System.Collections.Generic;
using Robust.Shared.GameStates;
using Robust.Shared.Network;
using Robust.Shared.Players;
namespace Robust.Client.Player
{
public interface IPlayerManager : Shared.Players.ISharedPlayerManager
{
new IEnumerable<IPlayerSession> Sessions { get; }
IReadOnlyDictionary<NetUserId, IPlayerSession> SessionsDict { get; }
new IEnumerable<ICommonSession> Sessions { get; }
IReadOnlyDictionary<NetUserId, ICommonSession> SessionsDict { get; }
LocalPlayer? LocalPlayer { get; }

View File

@@ -1,11 +0,0 @@
using System;
using Robust.Shared.Players;
namespace Robust.Client.Player
{
/// <summary>
/// Client side session of a player.
/// </summary>
[Obsolete("Use the base " + nameof(ICommonSession))]
public interface IPlayerSession : ICommonSession { }
}

View File

@@ -1,9 +1,9 @@
using System;
using System;
using Robust.Client.GameObjects;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.ViewVariables;
namespace Robust.Client.Player
@@ -36,7 +36,7 @@ namespace Robust.Client.Player
/// Session of the local client.
/// </summary>
[ViewVariables]
public IPlayerSession Session => InternalSession;
public ICommonSession Session => InternalSession;
internal PlayerSession InternalSession { get; set; } = default!;
@@ -70,10 +70,10 @@ namespace Robust.Client.Player
eye.Current = true;
EntityAttached?.Invoke(new EntityAttachedEventArgs(entity));
entity.SendMessage(null, new PlayerAttachedMsg());
// notify ECS Systems
ControlledEntity.EntityManager.EventBus.RaiseEvent(EventSource.Local, new PlayerAttachSysMessage(ControlledEntity));
ControlledEntity.EntityManager.EventBus.RaiseLocalEvent(ControlledEntity.Uid, new PlayerAttachedEvent(ControlledEntity));
}
/// <summary>
@@ -82,13 +82,13 @@ namespace Robust.Client.Player
public void DetachEntity()
{
var previous = ControlledEntity;
if (previous != null && previous.Initialized && !previous.Deleted)
if (previous is {Initialized: true, Deleted: false})
{
previous.GetComponent<EyeComponent>().Current = false;
previous.SendMessage(null, new PlayerDetachedMsg());
// notify ECS Systems
previous.EntityManager.EventBus.RaiseEvent(EventSource.Local, new PlayerAttachSysMessage(null));
previous.EntityManager.EventBus.RaiseLocalEvent(previous.Uid, new PlayerDetachedEvent(previous));
}
ControlledEntity = null;

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
@@ -28,7 +27,7 @@ namespace Robust.Client.Player
/// <summary>
/// Active sessions of connected clients to the server.
/// </summary>
private readonly Dictionary<NetUserId, IPlayerSession> _sessions = new();
private readonly Dictionary<NetUserId, ICommonSession> _sessions = new();
/// <inheritdoc />
public IEnumerable<ICommonSession> NetworkedSessions
@@ -69,10 +68,10 @@ namespace Robust.Client.Player
/// <inheritdoc />
[ViewVariables]
IEnumerable<IPlayerSession> IPlayerManager.Sessions => _sessions.Values;
IEnumerable<ICommonSession> IPlayerManager.Sessions => _sessions.Values;
/// <inheritdoc />
public IReadOnlyDictionary<NetUserId, IPlayerSession> SessionsDict => _sessions;
public IReadOnlyDictionary<NetUserId, ICommonSession> SessionsDict => _sessions;
/// <inheritdoc />
public event EventHandler? PlayerListUpdated;
@@ -82,8 +81,8 @@ namespace Robust.Client.Player
{
_client.RunLevelChanged += OnRunLevelChanged;
_network.RegisterNetMessage<MsgPlayerListReq>(MsgPlayerListReq.NAME);
_network.RegisterNetMessage<MsgPlayerList>(MsgPlayerList.NAME, HandlePlayerList);
_network.RegisterNetMessage<MsgPlayerListReq>();
_network.RegisterNetMessage<MsgPlayerList>(HandlePlayerList);
}
/// <inheritdoc />

View File

@@ -5,9 +5,8 @@ using Robust.Shared.Players;
namespace Robust.Client.Player
{
internal sealed class PlayerSession : IPlayerSession
internal sealed class PlayerSession : ICommonSession
{
/// <inheritdoc />
internal SessionStatus Status { get; set; } = SessionStatus.Connecting;
/// <inheritdoc />
@@ -26,17 +25,15 @@ namespace Robust.Client.Player
/// <inheritdoc />
public NetUserId UserId { get; }
/// <inheritdoc cref="IPlayerSession" />
internal string Name { get; set; } = "<Unknown>";
/// <inheritdoc cref="IPlayerSession" />
/// <inheritdoc />
string ICommonSession.Name
{
get => this.Name;
set => this.Name = value;
}
/// <inheritdoc />
internal short Ping { get; set; }
/// <inheritdoc />
@@ -51,7 +48,7 @@ namespace Robust.Client.Player
/// <summary>
/// Creates an instance of a PlayerSession.
/// </summary
/// </summary>
public PlayerSession(NetUserId user)
{
UserId = user;

View File

@@ -30,7 +30,7 @@ namespace Robust.Client.Prototypes
{
base.Initialize();
_netManager.RegisterNetMessage<MsgReloadPrototypes>(MsgReloadPrototypes.NAME, accept: NetMessageAccept.Server);
_netManager.RegisterNetMessage<MsgReloadPrototypes>(accept: NetMessageAccept.Server);
_clyde.OnWindowFocused += WindowFocusedChanged;

View File

@@ -6,7 +6,9 @@ using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.Manager.Result;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Validation;
using Robust.Shared.Serialization.Markdown.Value;
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
namespace Robust.Client.Serialization
@@ -19,7 +21,7 @@ namespace Robust.Client.Serialization
bool skipHook,
ISerializationContext? context = null)
{
if (!node.TryGetNode("type", out var typeNode))
if (!node.TryGet("type", out var typeNode))
throw new InvalidMappingException("No type specified for AppearanceVisualizer!");
if (typeNode is not ValueDataNode typeValueDataNode)
@@ -32,7 +34,7 @@ namespace Robust.Client.Serialization
$"Invalid type {typeValueDataNode.Value} specified for AppearanceVisualizer!");
var newNode = (MappingDataNode)node.Copy();
newNode.RemoveNode("type");
newNode.Remove("type");
return serializationManager.Read(type, newNode, context, skipHook);
}
@@ -40,7 +42,7 @@ namespace Robust.Client.Serialization
IDependencyCollection dependencies,
ISerializationContext? context)
{
if (!node.TryGetNode("type", out var typeNode) || typeNode is not ValueDataNode valueNode)
if (!node.TryGet("type", out var typeNode) || typeNode is not ValueDataNode valueNode)
{
return new ErrorNode(node, "Missing/Invalid type", true);
}
@@ -53,14 +55,14 @@ namespace Robust.Client.Serialization
return new ErrorNode(node, $"Failed to resolve type: {valueNode.Value}", true);
}
return serializationManager.ValidateNode(type, node.CopyCast<MappingDataNode>().RemoveNode("type"));
return serializationManager.ValidateNode(type, node.CopyCast<MappingDataNode>().Remove("type"));
}
public DataNode Write(ISerializationManager serializationManager, AppearanceVisualizer value, bool alwaysWrite = false,
ISerializationContext? context = null)
{
var mapping = serializationManager.WriteValueAs<MappingDataNode>(value.GetType(), value, alwaysWrite, context);
mapping.AddNode("type", new ValueDataNode(value.GetType().Name));
mapping.Add("type", new ValueDataNode(value.GetType().Name));
return mapping;
}

View File

@@ -10,7 +10,7 @@ namespace Robust.Client.UserInterface.Controls
{
public Label Label { get; }
public Button() : base()
public Button()
{
AddStyleClass(StyleClassButton);
Label = new Label

View File

@@ -14,7 +14,7 @@ namespace Robust.Client.UserInterface.Controls
public Label Label { get; }
public TextureRect TextureRect { get; }
public CheckBox() : base()
public CheckBox()
{
ToggleMode = true;

View File

@@ -13,7 +13,7 @@ namespace Robust.Client.UserInterface.Controls
public const string StylePseudoClassHover = "hover";
public const string StylePseudoClassDisabled = "disabled";
public ContainerButton() : base()
public ContainerButton()
{
DrawModeChanged();
}

View File

@@ -318,6 +318,8 @@ namespace Robust.Client.UserInterface.Controls
{
base.Draw(handle);
var sizeBox = PixelSizeBox;
var font = ActualFont;
var listBg = ActualBackground;
var iconBg = ActualItemBackground;
@@ -346,31 +348,38 @@ namespace Robust.Client.UserInterface.Controls
itemHeight = item.IconSize.Y;
}
itemHeight = Math.Max(itemHeight, ActualFont.GetHeight(UIScale));
itemHeight = Math.Max(itemHeight, font.GetHeight(UIScale));
itemHeight += ActualItemBackground.MinimumSize.Y;
item.Region = UIBox2.FromDimensions(0, offset, PixelWidth, itemHeight);
var region = UIBox2.FromDimensions(0, offset, PixelWidth, itemHeight);
item.Region = region;
bg.Draw(handle, item.Region.Value);
var contentBox = bg.GetContentBox(item.Region.Value);
var drawOffset = contentBox.TopLeft;
if (item.Icon != null)
if (region.Intersects(sizeBox))
{
if (item.IconRegion.Size == Vector2.Zero)
{
handle.DrawTextureRect(item.Icon, UIBox2.FromDimensions(drawOffset, item.Icon.Size), item.IconModulate);
}
else
{
handle.DrawTextureRectRegion(item.Icon, UIBox2.FromDimensions(drawOffset, item.Icon.Size), item.IconRegion, item.IconModulate);
}
}
bg.Draw(handle, item.Region.Value);
if (item.Text != null)
{
var textBox = new UIBox2(contentBox.Left + item.IconSize.X, contentBox.Top, contentBox.Right, contentBox.Bottom);
DrawTextInternal(handle, item.Text, textBox);
var contentBox = bg.GetContentBox(item.Region.Value);
var drawOffset = contentBox.TopLeft;
if (item.Icon != null)
{
if (item.IconRegion.Size == Vector2.Zero)
{
handle.DrawTextureRect(item.Icon, UIBox2.FromDimensions(drawOffset, item.Icon.Size),
item.IconModulate);
}
else
{
handle.DrawTextureRectRegion(item.Icon, UIBox2.FromDimensions(drawOffset, item.Icon.Size),
item.IconRegion, item.IconModulate);
}
}
if (item.Text != null)
{
var textBox = new UIBox2(contentBox.Left + item.IconSize.X, contentBox.Top, contentBox.Right,
contentBox.Bottom);
DrawTextInternal(handle, item.Text, textBox);
}
}
offset += itemHeight;

View File

@@ -52,7 +52,7 @@ namespace Robust.Client.UserInterface.Controls
public event EventHandler<ValueChangedEventArgs>? ValueChanged;
public SpinBox() : base()
public SpinBox()
{
MouseFilter = MouseFilterMode.Pass;

View File

@@ -1,4 +1,4 @@
using Robust.Client.GameObjects;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.Maths;
@@ -48,7 +48,7 @@ namespace Robust.Client.UserInterface.Controls
return;
}
renderHandle.DrawEntity(Sprite.Owner, GlobalPixelPosition + PixelSize / 2, Scale, OverrideDirection);
renderHandle.DrawEntity(Sprite.Owner, GlobalPixelPosition + PixelSize / 2, Scale * UIScale, OverrideDirection);
}
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using Robust.Client.Graphics;
using Robust.Shared.Input;
using Robust.Shared.Localization;
@@ -75,7 +75,7 @@ namespace Robust.Client.UserInterface.Controls
var control = GetChild(tab);
var title = control.GetValue(TabTitleProperty);
return title ?? control.Name ?? Loc.GetString("No title");
return title ?? control.Name ?? Loc.GetString("tab-container-not-tab-title-provided");
}
public static string? GetTabTitle(Control control)

View File

@@ -20,7 +20,7 @@ namespace Robust.Client.UserInterface.Controls
public bool HideRoot { get; set; }
public Item? Root => _root;
public Item? TreeRoot => _root;
public Item? Selected => _selectedIndex == null ? null : _itemList[_selectedIndex.Value];

View File

@@ -1,4 +1,4 @@
using System;
using System;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Input;
using Robust.Shared.IoC;
@@ -165,6 +165,8 @@ namespace Robust.Client.UserInterface.CustomControls
protected internal override void MouseExited()
{
base.MouseExited();
if (Resizable && CurrentDrag == DragMode.None)
{
DefaultCursorShape = CursorShape.Arrow;

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