mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
168 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a83787d58 | ||
|
|
6dd9f9e0f5 | ||
|
|
7f3445b1c6 | ||
|
|
5c15f26f09 | ||
|
|
b8fbe5e465 | ||
|
|
32049e34f2 | ||
|
|
26b09283f3 | ||
|
|
6c6360e50a | ||
|
|
9877323195 | ||
|
|
b6f52f4c27 | ||
|
|
c1789cbbaf | ||
|
|
f44f5b5a98 | ||
|
|
f4faa1ad3d | ||
|
|
97d03c6954 | ||
|
|
8accbc700a | ||
|
|
9f013534b3 | ||
|
|
0b8febf6a6 | ||
|
|
188985ecc2 | ||
|
|
42434d1f49 | ||
|
|
87492cb0c3 | ||
|
|
517ae7f1bd | ||
|
|
73357f022b | ||
|
|
012faa0a16 | ||
|
|
7a8abb3db7 | ||
|
|
d6ce7e950b | ||
|
|
e39b249070 | ||
|
|
5b889936be | ||
|
|
712809195d | ||
|
|
64b5d6e323 | ||
|
|
baa607532d | ||
|
|
ff064dd859 | ||
|
|
8305bffcac | ||
|
|
17a8972052 | ||
|
|
56e03eae3e | ||
|
|
1f948e17c4 | ||
|
|
efaed42b24 | ||
|
|
be2e31ff9d | ||
|
|
a891cacae5 | ||
|
|
e9e0117402 | ||
|
|
24114d87e6 | ||
|
|
7cd78f3f4e | ||
|
|
ca64aae7f0 | ||
|
|
be0fb4250c | ||
|
|
eba58cb893 | ||
|
|
ced6d5a8b0 | ||
|
|
495671576e | ||
|
|
0611674915 | ||
|
|
e6ab61fe42 | ||
|
|
5232e61d38 | ||
|
|
a3e90aa04e | ||
|
|
7a719d9f61 | ||
|
|
61385b7c21 | ||
|
|
2503f9c4e0 | ||
|
|
eb092e90ef | ||
|
|
eb556c8728 | ||
|
|
2e398ded08 | ||
|
|
75cf15caa2 | ||
|
|
8d7d6a26ba | ||
|
|
d593ffbb47 | ||
|
|
15b377dbd6 | ||
|
|
d212479689 | ||
|
|
2d3379d7f4 | ||
|
|
7b171b2212 | ||
|
|
7e8a5e199f | ||
|
|
ae74a5d7d4 | ||
|
|
9446ab76f9 | ||
|
|
bb5cb10d57 | ||
|
|
d6ccaee6d4 | ||
|
|
ce8982e371 | ||
|
|
371cd7ddf1 | ||
|
|
6573531d5d | ||
|
|
b1e1f27aa3 | ||
|
|
80ce583454 | ||
|
|
2b4a428f9f | ||
|
|
41ee330828 | ||
|
|
f82452c855 | ||
|
|
785e2f84f6 | ||
|
|
2bdc1d77ca | ||
|
|
51205beb56 | ||
|
|
ef325b4780 | ||
|
|
ede337a869 | ||
|
|
d416344aef | ||
|
|
fb98eb1a0c | ||
|
|
ed9a0b4812 | ||
|
|
e7c6151310 | ||
|
|
005c2e784a | ||
|
|
1009dd3ea0 | ||
|
|
87b82160b0 | ||
|
|
fc318c9ecd | ||
|
|
ac957ca7fc | ||
|
|
1bdd82b0bf | ||
|
|
0150c5e6ff | ||
|
|
b2cc90d00f | ||
|
|
1f8b89e92f | ||
|
|
33d394295e | ||
|
|
4934a9c5a5 | ||
|
|
91ebc3eb02 | ||
|
|
bc84590a33 | ||
|
|
e3944dc6fb | ||
|
|
6a77f4c27b | ||
|
|
6246ae412e | ||
|
|
ac86accc20 | ||
|
|
19ff7f25ca | ||
|
|
3f83733a03 | ||
|
|
438fed2f0e | ||
|
|
816a535a92 | ||
|
|
01df42aa8f | ||
|
|
a200d73ef9 | ||
|
|
8d30735ffb | ||
|
|
2686150f9d | ||
|
|
d720e9393b | ||
|
|
f5b1c26bec | ||
|
|
3204002c72 | ||
|
|
4c79d0c6d0 | ||
|
|
e0d38fb8bd | ||
|
|
250f6ca7db | ||
|
|
773365c185 | ||
|
|
9a1e6af586 | ||
|
|
dc96318379 | ||
|
|
31a3f145de | ||
|
|
331e1fcc81 | ||
|
|
dc7a51e582 | ||
|
|
bfe8e687da | ||
|
|
04b6d60d76 | ||
|
|
5bc5bfd58a | ||
|
|
56899b4e64 | ||
|
|
a23915e0dd | ||
|
|
726d91c5e8 | ||
|
|
d8a8783680 | ||
|
|
8839dd9a3b | ||
|
|
0296d9635c | ||
|
|
f24d9751d4 | ||
|
|
58ac82ae55 | ||
|
|
b9130bf236 | ||
|
|
a2cd33afe5 | ||
|
|
1772651049 | ||
|
|
826fa4d131 | ||
|
|
0b712ae86c | ||
|
|
22528fc484 | ||
|
|
20a411e6ce | ||
|
|
6f9ed8a242 | ||
|
|
790f4c1309 | ||
|
|
0b62cb6445 | ||
|
|
9d0f4d8a08 | ||
|
|
5069b0ccf9 | ||
|
|
12cfdb2175 | ||
|
|
b5e079815d | ||
|
|
ca3a3279c5 | ||
|
|
003752a161 | ||
|
|
55e51cba9c | ||
|
|
2cd829f4f6 | ||
|
|
43138669ec | ||
|
|
3fe30bc00f | ||
|
|
3ccbdeac6a | ||
|
|
9eb9c91da6 | ||
|
|
28d2b47a2c | ||
|
|
049ffa05e4 | ||
|
|
2f36a0a5fc | ||
|
|
41d03db59d | ||
|
|
68df887a65 | ||
|
|
e0bbcd7b08 | ||
|
|
525815427e | ||
|
|
70224ac100 | ||
|
|
dabb090dc2 | ||
|
|
ace8334a3e | ||
|
|
d8e70b4d52 | ||
|
|
2fca0e03ee | ||
|
|
b6980964b6 |
2
.github/workflows/build-docfx.yml
vendored
2
.github/workflows/build-docfx.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
with:
|
||||
dotnet-version: 7.0.x
|
||||
dotnet-version: 8.0.x
|
||||
|
||||
- name: Install dependencies
|
||||
run: dotnet restore
|
||||
|
||||
2
.github/workflows/build-test.yml
vendored
2
.github/workflows/build-test.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
with:
|
||||
dotnet-version: 7.0.x
|
||||
dotnet-version: 8.0.x
|
||||
- name: Install dependencies
|
||||
run: dotnet restore
|
||||
- name: Build
|
||||
|
||||
2
.github/workflows/publish-client.yml
vendored
2
.github/workflows/publish-client.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
with:
|
||||
dotnet-version: 7.0.x
|
||||
dotnet-version: 8.0.x
|
||||
|
||||
- name: Package client
|
||||
run: Tools/package_client_build.py -p windows mac linux
|
||||
|
||||
2
.github/workflows/test-content.yml
vendored
2
.github/workflows/test-content.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
with:
|
||||
dotnet-version: 7.0.x
|
||||
dotnet-version: 8.0.x
|
||||
- name: Disable submodule autoupdate
|
||||
run: touch BuildChecker/DISABLE_SUBMODULE_AUTOUPDATE
|
||||
|
||||
|
||||
Submodule Lidgren.Network/Lidgren.Network updated: f19cea8010...45f89ca263
@@ -10,6 +10,9 @@
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
|
||||
<SkipRobustAnalyzer>true</SkipRobustAnalyzer>
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>12.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<Project>
|
||||
<!-- Engine-specific properties. Content should not use this file. -->
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<LangVersion>11</LangVersion>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<LangVersion>12</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<WarningsAsErrors>nullable</WarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<!-- Import this at the end of any project files in Robust and Content. -->
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="Robust.Custom.targets" Condition="Exists('Robust.Custom.targets')"/>
|
||||
|
||||
@@ -61,18 +61,5 @@ namespace OpenToolkit.GraphicsLibraryFramework
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GLFWException"/> class with the specified context
|
||||
/// and the serialization information.
|
||||
/// </summary>
|
||||
/// <param name="info">The <see cref="SerializationInfo"/> associated with this exception.</param>
|
||||
/// <param name="context">
|
||||
/// A <see cref="StreamingContext"/> that represents the context of this exception.
|
||||
/// </param>
|
||||
protected GLFWException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
419
RELEASE-NOTES.md
419
RELEASE-NOTES.md
@@ -54,6 +54,423 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 201.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* The `zCircleGradient` shader function arguments have changed. It now requires a pixel-size to ensure that the gradient is properly entered.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed some PVS null reference errors.
|
||||
|
||||
|
||||
## 200.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* MappingDataNode is now ordered.
|
||||
* Make invalid AutoNetworkedFields compiler errors.
|
||||
|
||||
### New features
|
||||
|
||||
* `OSWindowStyles.NoTitleBar` (supported only on Linux X11 for now).
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Avoid calling DirtyEntity when a component's last modified tick is not current.
|
||||
* Fix `tpgrid` allowing moving grids to nullspace.
|
||||
|
||||
### Other
|
||||
|
||||
* `OSWindowStyles.NoTitleOptions` is now supported on Linux X11.
|
||||
|
||||
|
||||
## 199.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Various `IEntityManager` C# events now use `Entity<MetadataComponent>` instead of `EntityUid`
|
||||
* Entity visibility masks now use a ushort instead of an integer.
|
||||
* Run grid traversal on entity spawn.
|
||||
|
||||
### New features
|
||||
|
||||
* Added two new `IEntityManager` C# events that get raiseed before and after deleting ("flushing") all entities.
|
||||
* Added a new `DeleteEntity()` override that takes in the entity's metadata and transform components.
|
||||
* Add better audio logs.
|
||||
* Expand z-library shader.
|
||||
* Add a Box2i union for Vector2i and add a Contains variant that assumes the Vector2i is a tile and not a point.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Try to prevent some NREs in PVS.
|
||||
* More PVS fixes and cleanup.
|
||||
|
||||
|
||||
## 198.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* `IClydeViewport` now provides access to the light render target.
|
||||
* Added a style-class to the `MenuBar` popup control.
|
||||
* Added `NextGaussian()` extension method for `System.Random`.
|
||||
* Added per-session variant of `PvsOverrideSystem.AddForceSend()`.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Stopped the client from logging errors when attempting to delete invalid entities.
|
||||
|
||||
### Other
|
||||
|
||||
* The `DevWindow` UI inspector has been improved a bit and it now groups properties by their defining type.
|
||||
|
||||
|
||||
## 198.0.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix preprocessor flag for FULL_RELEASE preventing building.
|
||||
|
||||
|
||||
## 198.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Disable DefaultMagicAczProvider for FULL_RELEASE as it's only meant for debugging.
|
||||
|
||||
### New features
|
||||
|
||||
* Automatic UI scale is disabled by default for non-main windows. If desired, it can be re-enabled per window by changing `WindowRoot.DisableAutoScaling`.
|
||||
* Add UI click and hover sound support via IUserInterfaceManager.SetClickSound / .SetHoverSound
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix GetEntitiesIntersecting for map entities without grids.
|
||||
|
||||
### Other
|
||||
|
||||
* Print more diagnostics on server startup.
|
||||
|
||||
|
||||
## 197.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* ACZ improvements: `IStatusHost.InvalidateAcz()` and `IStatusHost.SetFullHybridAczProvider()`.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixes a PVS bug that happens when grids moved across maps.
|
||||
* Fixes sprite animations not working properly
|
||||
|
||||
|
||||
## 197.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* PvsOverrideSystem has been reworked:
|
||||
* Session and global overrides now default to always being recursive (i.e., sending all children).
|
||||
* Session & global overrides will always respect a client's PVS budgets.
|
||||
* Entities with an override will now still be sent in the same way as other entities if they are within a player's view. If you want to prevent them from being sent, you need to use visibility masks.
|
||||
* Entities can have more than one kind of override (i.e., multiple sessions).
|
||||
|
||||
### New features
|
||||
|
||||
* Added a `PvsSize ` field to `EyeComponent`, which can be used to modify the PVS range of an eye.
|
||||
* Added a new `NetLowLodRange` cvar for reducing the number of distant entities that get sent to a player. If a PVS chunk is beyond this range (but still within PVS range), then only high-priority entities on that chunk will get sent.
|
||||
* Added a new metadata flag for tagging an entity as a "high prority" entity that should get sent even on distant chunks. This only works for entities that are directly attached to a grid or map. This is currently used by lights & occluders.
|
||||
|
||||
### Other
|
||||
|
||||
* PVS has been reworked again, and should hopefully be noticeable faster.
|
||||
* PVS now prioritizes sending chunks that are closer to a player's eyes.
|
||||
|
||||
|
||||
## 196.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Dirtying a non-networked component will now fail a debug assert.
|
||||
* The `IInvocationContext` interface for toolshed commands now requires a UserId field. The session field should be cleared if a player disconnects.
|
||||
|
||||
### New features
|
||||
|
||||
* `LocalizationManager` now supports multiple fallback cultures
|
||||
* SpriteView now supports using a `NetEntity` to select an entity to draw.
|
||||
* Added methods for simultaneously dirtying several components on the same entity.
|
||||
* Animated sprite layers now have a "Cycle" option that will reverse an animation when it finishes.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed a recursion/stack-overflow in `GridTraversalSystem`
|
||||
* Ensure `Robust.Client.WebView` processes get shut down if game process exits uncleanly.
|
||||
* Fixed Toolshed commands not properly functioning after disconnecting and reconnecting.
|
||||
|
||||
### Other
|
||||
|
||||
* Console command completions no longer suggest toolshed commands for non-toolshed commands.
|
||||
|
||||
|
||||
|
||||
## 195.0.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixes playing audio using audio streams
|
||||
* Fixes placement manager exceptions when placing self deleting / spawner entities
|
||||
* Fixed `IPrototypeManager.EnumeratePrototypes<T>` throwing an exception when there are no instances.
|
||||
|
||||
|
||||
## 195.0.0
|
||||
|
||||
### New features
|
||||
|
||||
* Generic versions of `DebugTools.AssertEquals()` functions.
|
||||
* `[Prototype]` now does not need to have a name specified, the name is inferred from the class name.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixes a physics bug that could cause deleted entities to remain on the physics map.
|
||||
* Fixes a bug in entity lookup code that could cause clients to get stuck in an infinite loop.
|
||||
|
||||
### Other
|
||||
|
||||
* `Robust.Client.WebView` has been brought alive again.
|
||||
* The addition of physics joints is no longer deferred to the next tick.
|
||||
* Grid traversal is no longer deferred to the next tick.
|
||||
* Integration tests now fail when console commands log errors.
|
||||
|
||||
|
||||
## 194.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* `IAudioManager` has APIs to directly load `AudioStream`s from data streams.
|
||||
* `AudioSystem` has new `Play*` methods.
|
||||
* `EntityCoordinates.TryDelta()`
|
||||
* `EntityLookupSystem.GetEntitiesInRange()` untyped hashset overload has `flags` parameter.
|
||||
|
||||
|
||||
## 194.0.2
|
||||
|
||||
### Internal
|
||||
|
||||
* Added some null-checks to PVS to try reduce the error spam.
|
||||
|
||||
|
||||
## 194.0.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed `Control.SetPositionInParent` failing to move an entity to the last position.
|
||||
* Fixed audio occlusion not working.
|
||||
|
||||
### Internal
|
||||
|
||||
* Added some logs for grid/map deletion and movement to debug some map loading issues.
|
||||
* Refactored some parts of PVS. It should be slightly faster, though the game may be unstable for a bit.
|
||||
|
||||
## 194.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* MoveEvent is no longer raised broadcast, subscribe to the SharedTransformSystem.OnGlobalMoveEvent C# event instead
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed the game sometimes freezing while trying to load specific audio files.
|
||||
|
||||
|
||||
## 193.2.0
|
||||
|
||||
### Other
|
||||
|
||||
* Added more PVS error logs
|
||||
|
||||
|
||||
## 193.1.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed an exception when building in FULL_RELEASE
|
||||
|
||||
|
||||
## 193.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Added FrozenDictionary and FrozenHashSet to sandbox whitelist
|
||||
* Added yaml type serializers for FrozenDictionary and FrozenHashSet
|
||||
* Added `IPrototypeManager.GetInstances<T>()`
|
||||
* `IPrototypeManager` now also raises `PrototypesReloadedEventArgs` as a system event.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Might fix some PVS bugs added in the last version.
|
||||
|
||||
### Internal
|
||||
|
||||
* Various static dictionaries have been converted into FrozenDictionary.
|
||||
|
||||
|
||||
## 193.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* The `TransformChildrenEnumerator`'s out values are now non-nullable
|
||||
|
||||
### New features
|
||||
|
||||
* Added `IPrototypeManager.TryGetInstances()`, which returns a dictionary of prototype instances for a given prototype kind/type.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed `BaseAudioSource.SetAuxiliary()` throwing errors on non-EFX systems
|
||||
|
||||
### Internal
|
||||
|
||||
|
||||
* The internals of PVS system have been reworked to reduce the number of dictionary lookups.
|
||||
* `RobustMappedStringSerializer` now uses frozen dictionaries
|
||||
* `IPrototypeManager` now uses frozen dictionaries
|
||||
|
||||
|
||||
## 192.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* `EntitySystem.TryGetEntity` is now `protected`.
|
||||
|
||||
### Internal
|
||||
|
||||
* PVS message ack processing now happens asynchronously
|
||||
* Dependency collections now use a `FrozenDictionary`
|
||||
|
||||
|
||||
## 191.0.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
.* Fix sandbox being broken thanks to .NET 8.
|
||||
|
||||
|
||||
## 191.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Robust now uses **.NET 8**. Nyoom.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* `IResourceCache.TryGetResource<T>` won't silently eat all exceptions anymore.
|
||||
|
||||
|
||||
## 190.1.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Revert broadphase job to prevent OOM from logs.
|
||||
|
||||
|
||||
## 190.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add OnGrabbed / OnReleased to slider controls.
|
||||
* Add Rotation method for matrices and also make the precision slightly better when angles are passed in by taking double-precision not single-precision floats.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix some grid setting asserts when adding gridcomponent to existing maps.
|
||||
|
||||
|
||||
## 190.0.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add color gradients to sliders.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix HSV / HSL producing black colors on 360 hue.
|
||||
* Stop terminating entities from prematurely detaching to nullspace.
|
||||
* Ensure shader parameters update when swapping instances.
|
||||
|
||||
### Other
|
||||
|
||||
* Add more verbose logging to OpenAL errors.
|
||||
|
||||
### Internal
|
||||
|
||||
* Change NetSyncEnabled to an assert and fix instances where it slips through to PVS.
|
||||
|
||||
|
||||
## 189.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Use the base AudioParams for networking not the z-offset adjusted ones.
|
||||
* Modulate SpriteView sprites by the control's color modulation.
|
||||
|
||||
### New features
|
||||
|
||||
* Improve YAML linter error messages for parent nodes.
|
||||
* ExpandPvsEvent will also be raised directed to the session's attached entity.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Client clientside entity error spam.
|
||||
|
||||
### Internal
|
||||
|
||||
* Set priorGain to 0 where no EFX is supported for audio rather than 0.5.
|
||||
* Try to hotfix MIDI lock contention more via a semaphore.
|
||||
|
||||
|
||||
## 188.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Return null buffered audio if there's an exception and use the dummy instance internally.
|
||||
* Use entity name then suffix for entity spawn window ordering.
|
||||
* Change MidiManager volume to gain.
|
||||
* Remove EntityQuery from the MapVelocity API.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Potentially fix some audio issues by setting gain to half where EFX not found and the prior gain was 0.
|
||||
* Log errors upon trying to spawn audio attached to deleted entities instead of trying to spawn them and erroring later.
|
||||
* Fixed predicted audio spawns not applying the adjusted audio params.
|
||||
* Fix GetDimensions for the screenhandle where the text is only a single line.
|
||||
|
||||
|
||||
## 187.2.0
|
||||
|
||||
### New features
|
||||
|
||||
* Added a cancellable bool to physics sleeping events where we may wish to cancel it.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix corrupted physics awake state leading to client mispredicts.
|
||||
|
||||
|
||||
## 187.1.2
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Hotfix contact nullrefs if they're modified during manifold generation.
|
||||
|
||||
|
||||
## 187.1.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Revert physics solver job to fix crashes until box2d v3 rolls around.
|
||||
* Don't RegenerateContacts if the body isn't collidable to avoid putting non-collidable proxies on the movebuffer.
|
||||
|
||||
|
||||
## 187.1.0
|
||||
|
||||
### Bugfixes
|
||||
@@ -296,7 +713,7 @@ END TEMPLATE-->
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Most methods in ActorSystem have been moved to ISharedPlayerManager.
|
||||
* Most methods in ActorSystem have been moved to ISharedPlayerManager.
|
||||
* Several actor/player related components and events have been moved to shared.
|
||||
|
||||
### New features
|
||||
|
||||
@@ -12,3 +12,8 @@
|
||||
id: bgra
|
||||
kind: source
|
||||
path: "/Shaders/Internal/bgra.swsl"
|
||||
|
||||
- type: shader
|
||||
id: ColorPicker
|
||||
kind: source
|
||||
path: "/Shaders/color_picker.swsl"
|
||||
|
||||
@@ -9,6 +9,7 @@ cmd-parse-failure-float = {$arg} is not a valid float.
|
||||
cmd-parse-failure-bool = {$arg} is not a valid bool.
|
||||
cmd-parse-failure-uid = {$arg} is not a valid entity UID.
|
||||
cmd-parse-failure-mapid = {$arg} is not a valid MapId.
|
||||
cmd-parse-failure-grid = {$arg} is not a valid grid.
|
||||
cmd-parse-failure-entity-exist = UID {$arg} does not correspond to an existing entity.
|
||||
|
||||
cmd-error-file-not-found = Could not find file: {$file}.
|
||||
|
||||
46
Resources/Shaders/color_picker.swsl
Normal file
46
Resources/Shaders/color_picker.swsl
Normal file
@@ -0,0 +1,46 @@
|
||||
// Simple shader for creating a box with colours varying along the x and y axes.
|
||||
|
||||
uniform highp vec2 size;
|
||||
uniform highp vec2 offset;
|
||||
|
||||
uniform highp vec4 xAxis;
|
||||
uniform highp vec4 yAxis;
|
||||
uniform highp vec4 baseColor;
|
||||
|
||||
uniform bool hsv;
|
||||
|
||||
void fragment()
|
||||
{
|
||||
// Calculate local uv coordinates.
|
||||
// I.e., if using this shader to draw a box to the screen, (0,0) is the bottom left of the box.
|
||||
|
||||
highp float yCoords = 1.0/SCREEN_PIXEL_SIZE.y - FRAGCOORD.y;
|
||||
highp vec2 uv = vec2(FRAGCOORD.x - offset.x, yCoords - offset.y);
|
||||
uv /= size;
|
||||
uv.y = 1.0 - uv.y;
|
||||
|
||||
highp vec4 modulate = baseColor + uv.x * xAxis + uv.y * yAxis;
|
||||
|
||||
if (hsv)
|
||||
{
|
||||
modulate.xyz = hsv2rgb(modulate.xyz);
|
||||
}
|
||||
|
||||
// The UV used for the texture lookup is the TEXTURE UV coordinate, which is different from the coordinates computed above.
|
||||
COLOR = zTexture(UV) * modulate;
|
||||
}
|
||||
|
||||
|
||||
// hsv to RGB conversion taken from www.shadertoy.com/view/MsS3Wc
|
||||
|
||||
// The MIT License
|
||||
// Copyright © 2014 Inigo Quilez
|
||||
// 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.
|
||||
// https://www.youtube.com/c/InigoQuilez
|
||||
// https://iquilezles.org
|
||||
|
||||
highp vec3 hsv2rgb( in highp vec3 c )
|
||||
{
|
||||
highp vec3 rgb = clamp( abs(mod(c.x*6.0+vec3(0.0,4.0,2.0),6.0)-3.0)-1.0, 0.0, 1.0 );
|
||||
return c.z * mix( vec3(1.0), rgb, c.y);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Robust.Server.Containers;
|
||||
using Robust.Server.GameStates;
|
||||
@@ -11,7 +12,9 @@ using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.UnitTesting.Server;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.UnitTesting;
|
||||
|
||||
namespace Robust.Benchmarks.Transform;
|
||||
|
||||
@@ -19,114 +22,177 @@ namespace Robust.Benchmarks.Transform;
|
||||
/// This benchmark tests various transform/move related functions with an entity that has many children.
|
||||
/// </summary>
|
||||
[Virtual, MemoryDiagnoser]
|
||||
public class RecursiveMoveBenchmark
|
||||
public class RecursiveMoveBenchmark : RobustIntegrationTest
|
||||
{
|
||||
private ISimulation _simulation = default!;
|
||||
private IEntityManager _entMan = default!;
|
||||
private SharedTransformSystem _transform = default!;
|
||||
private ContainerSystem _container = default!;
|
||||
private PvsSystem _pvs = default!;
|
||||
private EntityCoordinates _mapCoords;
|
||||
private EntityCoordinates _gridCoords;
|
||||
private EntityCoordinates _gridCoords2;
|
||||
private EntityUid _ent;
|
||||
private EntityUid _child;
|
||||
private TransformComponent _childXform = default!;
|
||||
private EntityQuery<TransformComponent> _query;
|
||||
private ICommonSession[] _players = default!;
|
||||
private PvsSession _session = default!;
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
_simulation = RobustServerSimulation
|
||||
.NewSimulation()
|
||||
.InitializeInstance();
|
||||
ProgramShared.PathOffset = "../../../../";
|
||||
var server = StartServer();
|
||||
var client = StartClient();
|
||||
|
||||
if (!_simulation.Resolve<IConfigurationManager>().GetCVar(CVars.NetPVS))
|
||||
throw new InvalidOperationException("PVS must be enabled");
|
||||
Task.WhenAll(client.WaitIdleAsync(), server.WaitIdleAsync()).Wait();
|
||||
|
||||
var mapMan = server.ResolveDependency<IMapManager>();
|
||||
_entMan = server.ResolveDependency<IEntityManager>();
|
||||
var confMan = server.ResolveDependency<IConfigurationManager>();
|
||||
var sPlayerMan = server.ResolveDependency<ISharedPlayerManager>();
|
||||
|
||||
_entMan = _simulation.Resolve<IEntityManager>();
|
||||
_transform = _entMan.System<SharedTransformSystem>();
|
||||
_container = _entMan.System<ContainerSystem>();
|
||||
_pvs = _entMan.System<PvsSystem>();
|
||||
_query = _entMan.GetEntityQuery<TransformComponent>();
|
||||
|
||||
// Create map & grid
|
||||
var mapMan = _simulation.Resolve<IMapManager>();
|
||||
var mapSys = _entMan.System<SharedMapSystem>();
|
||||
var mapId = mapMan.CreateMap();
|
||||
var map = mapMan.GetMapEntityId(mapId);
|
||||
var gridComp = mapMan.CreateGridEntity(mapId);
|
||||
var grid = gridComp.Owner;
|
||||
_gridCoords = new EntityCoordinates(grid, .5f, .5f);
|
||||
_mapCoords = new EntityCoordinates(map, 100, 100);
|
||||
mapSys.SetTile(grid, gridComp, Vector2i.Zero, new Tile(1));
|
||||
|
||||
// Next, we will spawn our test entity. This entity will have a complex transform/container hierarchy.
|
||||
// This is intended to be representative of a typical SS14 player entity, with organs. clothing, and a full backpack.
|
||||
_ent = _entMan.Spawn();
|
||||
var netMan = client.ResolveDependency<IClientNetManager>();
|
||||
client.SetConnectTarget(server);
|
||||
client.Post(() => netMan.ClientConnect(null!, 0, null!));
|
||||
server.Post(() => confMan.SetCVar(CVars.NetPVS, true));
|
||||
|
||||
// Quick check that SetCoordinates actually changes the parent as expected
|
||||
// I.e., ensure that grid-traversal code doesn't just dump the entity on the map.
|
||||
_transform.SetCoordinates(_ent, _gridCoords);
|
||||
if (_query.GetComponent(_ent).ParentUid != _gridCoords.EntityId)
|
||||
throw new Exception("Grid traversal error.");
|
||||
|
||||
_transform.SetCoordinates(_ent, _mapCoords);
|
||||
if (_query.GetComponent(_ent).ParentUid != _mapCoords.EntityId)
|
||||
throw new Exception("Grid traversal error.");
|
||||
|
||||
// Add 5 direct children in slots to represent clothing.
|
||||
for (var i = 0; i < 5; i++)
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
var id = $"inventory{i}";
|
||||
_container.EnsureContainer<ContainerSlot>(_ent, id);
|
||||
if (!_entMan.TrySpawnInContainer(null, _ent, id, out _))
|
||||
throw new Exception($"Failed to setup entity");
|
||||
server.WaitRunTicks(1).Wait();
|
||||
client.WaitRunTicks(1).Wait();
|
||||
}
|
||||
|
||||
// body parts
|
||||
_container.EnsureContainer<Container>(_ent, "body");
|
||||
for (var i = 0; i < 5; i++)
|
||||
// Ensure client & server ticks are synced.
|
||||
// Client runs 1 tick ahead
|
||||
{
|
||||
// Simple organ
|
||||
if (!_entMan.TrySpawnInContainer(null, _ent, "body", out _))
|
||||
throw new Exception($"Failed to setup entity");
|
||||
var sTick = (int)server.Timing.CurTick.Value;
|
||||
var cTick = (int)client.Timing.CurTick.Value;
|
||||
var delta = cTick - sTick;
|
||||
|
||||
// body part that has another body part / limb
|
||||
if (!_entMan.TrySpawnInContainer(null, _ent, "body", out var limb))
|
||||
throw new Exception($"Failed to setup entity");
|
||||
if (delta > 1)
|
||||
server.WaitRunTicks(delta - 1).Wait();
|
||||
else if (delta < 1)
|
||||
client.WaitRunTicks(1 - delta).Wait();
|
||||
|
||||
_container.EnsureContainer<ContainerSlot>(limb.Value, "limb");
|
||||
if (!_entMan.TrySpawnInContainer(null, limb.Value, "limb", out _))
|
||||
throw new Exception($"Failed to setup entity");
|
||||
sTick = (int)server.Timing.CurTick.Value;
|
||||
cTick = (int)client.Timing.CurTick.Value;
|
||||
delta = cTick - sTick;
|
||||
if (delta != 1)
|
||||
throw new Exception("Failed setup");
|
||||
}
|
||||
|
||||
// Backpack
|
||||
_container.EnsureContainer<ContainerSlot>(_ent, "inventory-backpack");
|
||||
if (!_entMan.TrySpawnInContainer(null, _ent, "inventory-backpack", out var backpack))
|
||||
throw new Exception($"Failed to setup entity");
|
||||
|
||||
// Misc backpack contents.
|
||||
var backpackStorage = _container.EnsureContainer<Container>(backpack.Value, "storage");
|
||||
for (var i = 0; i < 10; i++)
|
||||
// Set up map and spawn player
|
||||
server.WaitPost(() =>
|
||||
{
|
||||
if (!_entMan.TrySpawnInContainer(null, backpack.Value, "storage", out _))
|
||||
var mapId = mapMan.CreateMap();
|
||||
var map = mapMan.GetMapEntityId(mapId);
|
||||
var gridComp = mapMan.CreateGridEntity(mapId);
|
||||
var grid = gridComp.Owner;
|
||||
mapSys.SetTile(grid, gridComp, Vector2i.Zero, new Tile(1));
|
||||
_gridCoords = new EntityCoordinates(grid, .5f, .5f);
|
||||
_gridCoords2 = new EntityCoordinates(grid, .5f, .6f);
|
||||
_mapCoords = new EntityCoordinates(map, 100, 100);
|
||||
|
||||
var playerUid = _entMan.SpawnEntity(null, _mapCoords);
|
||||
|
||||
// Attach player.
|
||||
var session = sPlayerMan.Sessions.First();
|
||||
server.PlayerMan.SetAttachedEntity(session, playerUid);
|
||||
sPlayerMan.JoinGame(session);
|
||||
|
||||
// Next, we will spawn our test entity. This entity will have a complex transform/container hierarchy.
|
||||
// This is intended to be representative of a typical SS14 player entity, with organs. clothing, and a full backpack.
|
||||
_ent = _entMan.Spawn();
|
||||
|
||||
// Quick check that SetCoordinates actually changes the parent as expected
|
||||
// I.e., ensure that grid-traversal code doesn't just dump the entity on the map.
|
||||
_transform.SetCoordinates(_ent, _gridCoords);
|
||||
if (_query.GetComponent(_ent).ParentUid != _gridCoords.EntityId)
|
||||
throw new Exception("Grid traversal error.");
|
||||
|
||||
_transform.SetCoordinates(_ent, _mapCoords);
|
||||
if (_query.GetComponent(_ent).ParentUid != _mapCoords.EntityId)
|
||||
throw new Exception("Grid traversal error.");
|
||||
|
||||
// Add 5 direct children in slots to represent clothing.
|
||||
for (var i = 0; i < 5; i++)
|
||||
{
|
||||
var id = $"inventory{i}";
|
||||
_container.EnsureContainer<ContainerSlot>(_ent, id);
|
||||
if (!_entMan.TrySpawnInContainer(null, _ent, id, out _))
|
||||
throw new Exception($"Failed to setup entity");
|
||||
}
|
||||
|
||||
// body parts
|
||||
_container.EnsureContainer<Container>(_ent, "body");
|
||||
for (var i = 0; i < 5; i++)
|
||||
{
|
||||
// Simple organ
|
||||
if (!_entMan.TrySpawnInContainer(null, _ent, "body", out _))
|
||||
throw new Exception($"Failed to setup entity");
|
||||
|
||||
// body part that has another body part / limb
|
||||
if (!_entMan.TrySpawnInContainer(null, _ent, "body", out var limb))
|
||||
throw new Exception($"Failed to setup entity");
|
||||
|
||||
_container.EnsureContainer<ContainerSlot>(limb.Value, "limb");
|
||||
if (!_entMan.TrySpawnInContainer(null, limb.Value, "limb", out _))
|
||||
throw new Exception($"Failed to setup entity");
|
||||
}
|
||||
|
||||
// Backpack
|
||||
_container.EnsureContainer<ContainerSlot>(_ent, "inventory-backpack");
|
||||
if (!_entMan.TrySpawnInContainer(null, _ent, "inventory-backpack", out var backpack))
|
||||
throw new Exception($"Failed to setup entity");
|
||||
|
||||
// Misc backpack contents.
|
||||
var backpackStorage = _container.EnsureContainer<Container>(backpack.Value, "storage");
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
if (!_entMan.TrySpawnInContainer(null, backpack.Value, "storage", out _))
|
||||
throw new Exception($"Failed to setup entity");
|
||||
}
|
||||
|
||||
// Emergency box inside of the backpack
|
||||
var box = backpackStorage.ContainedEntities.First();
|
||||
var boxContainer = _container.EnsureContainer<Container>(box, "storage");
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
if (!_entMan.TrySpawnInContainer(null, box, "storage", out _))
|
||||
throw new Exception($"Failed to setup entity");
|
||||
}
|
||||
|
||||
// Deepest child.
|
||||
_child = boxContainer.ContainedEntities.First();
|
||||
_childXform = _query.GetComponent(_child);
|
||||
|
||||
_players = new[] {session};
|
||||
_session = _pvs.PlayerData[session];
|
||||
}).Wait();
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
server.WaitRunTicks(1).Wait();
|
||||
client.WaitRunTicks(1).Wait();
|
||||
}
|
||||
|
||||
// Emergency box inside of the backpack
|
||||
var box = backpackStorage.ContainedEntities.First();
|
||||
var boxContainer = _container.EnsureContainer<Container>(box, "storage");
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
if (!_entMan.TrySpawnInContainer(null, box, "storage", out _))
|
||||
throw new Exception($"Failed to setup entity");
|
||||
}
|
||||
PvsTick();
|
||||
PvsTick();
|
||||
}
|
||||
|
||||
// Deepest child.
|
||||
_child = boxContainer.ContainedEntities.First();
|
||||
_childXform = _query.GetComponent(_child);
|
||||
|
||||
_pvs.ProcessCollections();
|
||||
private void PvsTick()
|
||||
{
|
||||
_session.ClearState();
|
||||
_pvs.CacheSessionData(_players);
|
||||
_pvs.GetVisibleChunks();
|
||||
_pvs.ProcessVisibleChunksSequential();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -140,6 +206,13 @@ public class RecursiveMoveBenchmark
|
||||
_transform.SetCoordinates(_ent, _mapCoords);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void MoveEntityASmidge()
|
||||
{
|
||||
_transform.SetCoordinates(_ent, _gridCoords);
|
||||
_transform.SetCoordinates(_ent, _gridCoords2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Like <see cref="MoveEntity"/>, but also processes queued PVS chunk updates.
|
||||
/// </summary>
|
||||
@@ -147,9 +220,18 @@ public class RecursiveMoveBenchmark
|
||||
public void MoveAndUpdateChunks()
|
||||
{
|
||||
_transform.SetCoordinates(_ent, _gridCoords);
|
||||
_pvs.ProcessCollections();
|
||||
PvsTick();
|
||||
_transform.SetCoordinates(_ent, _mapCoords);
|
||||
_pvs.ProcessCollections();
|
||||
PvsTick();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void MoveASmidgeAndUpdateChunk()
|
||||
{
|
||||
_transform.SetCoordinates(_ent, _gridCoords);
|
||||
PvsTick();
|
||||
_transform.SetCoordinates(_ent, _gridCoords2);
|
||||
PvsTick();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
|
||||
85
Robust.Client.WebView/Cef/BaseRobustCefClient.cs
Normal file
85
Robust.Client.WebView/Cef/BaseRobustCefClient.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.WebView.Cef;
|
||||
|
||||
/// <summary>
|
||||
/// Shared functionality for all <see cref="CefClient"/> implementations.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Locks down a bunch of CEF functionality we absolutely do not need content to do right now.
|
||||
/// </remarks>
|
||||
internal abstract class BaseRobustCefClient : CefClient
|
||||
{
|
||||
private readonly RobustCefPrintHandler _printHandler = new();
|
||||
private readonly RobustCefPermissionHandler _permissionHandler = new();
|
||||
private readonly RobustCefDialogHandler _dialogHandler = new();
|
||||
private readonly RobustCefDragHandler _dragHandler = new();
|
||||
|
||||
protected override CefPrintHandler GetPrintHandler() => _printHandler;
|
||||
protected override CefPermissionHandler GetPermissionHandler() => _permissionHandler;
|
||||
protected override CefDialogHandler GetDialogHandler() => _dialogHandler;
|
||||
protected override CefDragHandler GetDragHandler() => _dragHandler;
|
||||
|
||||
private sealed class RobustCefPrintHandler : CefPrintHandler
|
||||
{
|
||||
protected override void OnPrintSettings(CefBrowser browser, CefPrintSettings settings, bool getDefaults)
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool OnPrintDialog(CefBrowser browser, bool hasSelection, CefPrintDialogCallback callback)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override bool OnPrintJob(CefBrowser browser, string documentName, string pdfFilePath, CefPrintJobCallback callback)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void OnPrintReset(CefBrowser browser)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class RobustCefPermissionHandler : CefPermissionHandler
|
||||
{
|
||||
protected override bool OnRequestMediaAccessPermission(
|
||||
CefBrowser browser,
|
||||
CefFrame frame,
|
||||
string requestingOrigin,
|
||||
CefMediaAccessPermissionTypes requestedPermissions,
|
||||
CefMediaAccessCallback callback)
|
||||
{
|
||||
callback.Cancel();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class RobustCefDialogHandler : CefDialogHandler
|
||||
{
|
||||
protected override bool OnFileDialog(
|
||||
CefBrowser browser,
|
||||
CefFileDialogMode mode,
|
||||
string title,
|
||||
string defaultFilePath,
|
||||
string[] acceptFilters,
|
||||
CefFileDialogCallback callback)
|
||||
{
|
||||
callback.Cancel();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class RobustCefDragHandler : CefDragHandler
|
||||
{
|
||||
protected override bool OnDragEnter(CefBrowser browser, CefDragData dragData, CefDragOperationsMask mask)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnDraggableRegionsChanged(CefBrowser browser, CefFrame frame, CefDraggableRegion[] regions)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.WebView.Cef
|
||||
@@ -19,6 +22,8 @@ namespace Robust.Client.WebView.Cef
|
||||
|
||||
var mainArgs = new CefMainArgs(argv);
|
||||
|
||||
StartWatchThread();
|
||||
|
||||
// This will block executing until the subprocess is shut down.
|
||||
var code = CefRuntime.ExecuteProcess(mainArgs, new RobustCefApp(null), IntPtr.Zero);
|
||||
|
||||
@@ -29,5 +34,44 @@ namespace Robust.Client.WebView.Cef
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
private static void StartWatchThread()
|
||||
{
|
||||
//
|
||||
// CEF has this nasty habit of not shutting down all its processes if the parent crashes.
|
||||
// Great!
|
||||
//
|
||||
// We use a separate thread in each CEF child process to watch the main PID.
|
||||
// If it exits, we kill ourselves after a couple seconds.
|
||||
//
|
||||
|
||||
if (Environment.GetEnvironmentVariable("ROBUST_CEF_BROWSER_PROCESS_ID") is not { } parentIdString)
|
||||
return;
|
||||
|
||||
if (Environment.GetEnvironmentVariable("ROBUST_CEF_BROWSER_PROCESS_MODULE") is not { } parentModuleString)
|
||||
return;
|
||||
|
||||
if (!int.TryParse(parentIdString, CultureInfo.InvariantCulture, out var parentId))
|
||||
return;
|
||||
|
||||
var process = Process.GetProcessById(parentId);
|
||||
if ((process.MainModule?.FileName ?? "") != parentModuleString)
|
||||
{
|
||||
process.Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
new Thread(() => WatchThread(process)) { Name = "CEF Watch Thread", IsBackground = true }
|
||||
.Start();
|
||||
}
|
||||
|
||||
private static void WatchThread(Process p)
|
||||
{
|
||||
p.WaitForExit();
|
||||
|
||||
Thread.Sleep(3000);
|
||||
|
||||
Environment.Exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,12 +76,9 @@ namespace Robust.Client.WebView.Cef
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override unsafe bool Read(IntPtr dataOut, int bytesToRead, out int bytesRead, CefResourceReadCallback callback)
|
||||
protected override bool Read(Span<byte> response, out int bytesRead, CefResourceReadCallback callback)
|
||||
{
|
||||
var byteSpan = new Span<byte>((void*) dataOut, bytesToRead);
|
||||
|
||||
bytesRead = _stream.Read(byteSpan);
|
||||
|
||||
bytesRead = _stream.Read(response);
|
||||
return bytesRead != 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,10 +31,12 @@ namespace Robust.Client.WebView.Cef
|
||||
// Disable zygote on Linux.
|
||||
commandLine.AppendSwitch("--no-zygote");
|
||||
|
||||
// Work around https://bitbucket.org/chromiumembedded/cef/issues/3213/ozone-egl-initialization-does-not-have
|
||||
// Work around https://github.com/chromiumembedded/cef/issues/3213
|
||||
// Desktop GL force makes Chromium not try to load its own ANGLE/Swiftshader so load paths aren't problematic.
|
||||
if (OperatingSystem.IsLinux())
|
||||
commandLine.AppendSwitch("--use-gl", "desktop");
|
||||
// UPDATE: That bug got fixed and now this workaround breaks CEF.
|
||||
// Keeping all this comment history in case I ever wanan remember what the `--use-gl` flag is.
|
||||
//if (OperatingSystem.IsLinux())
|
||||
// commandLine.AppendSwitch("--use-gl", "desktop");
|
||||
|
||||
// commandLine.AppendSwitch("--single-process");
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ using Xilium.CefGlue;
|
||||
namespace Robust.Client.WebView.Cef
|
||||
{
|
||||
// Simple CEF client.
|
||||
internal sealed class RobustCefClient : CefClient
|
||||
internal sealed class RobustCefClient : BaseRobustCefClient
|
||||
{
|
||||
private readonly CefRenderHandler _renderHandler;
|
||||
private readonly CefRequestHandler _requestHandler;
|
||||
|
||||
@@ -150,7 +150,7 @@ namespace Robust.Client.WebView.Cef
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class WindowCefClient : CefClient
|
||||
private sealed class WindowCefClient : BaseRobustCefClient
|
||||
{
|
||||
private readonly CefLifeSpanHandler _lifeSpanHandler;
|
||||
private readonly CefRequestHandler _requestHandler;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
@@ -85,6 +86,10 @@ namespace Robust.Client.WebView.Cef
|
||||
|
||||
_app = new RobustCefApp(_sawmill);
|
||||
|
||||
var process = Process.GetCurrentProcess();
|
||||
Environment.SetEnvironmentVariable("ROBUST_CEF_BROWSER_PROCESS_ID", process.Id.ToString());
|
||||
Environment.SetEnvironmentVariable("ROBUST_CEF_BROWSER_PROCESS_MODULE", process.MainModule?.FileName ?? "");
|
||||
|
||||
// So these arguments look like nonsense, but it turns out CEF is just *like that*.
|
||||
// The first argument is literally nonsense, but it needs to be there as otherwise the second argument doesn't apply
|
||||
// The second argument turns off CEF's bullshit error handling, which breaks dotnet's error handling.
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2020.3.0" />
|
||||
<PackageReference Include="Robust.Natives.Cef" Version="102.0.9" />
|
||||
<PackageReference Include="Robust.Natives.Cef" Version="120.1.9" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
28
Robust.Client.WebView/TestBrowseWindow.cs
Normal file
28
Robust.Client.WebView/TestBrowseWindow.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System.Numerics;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Robust.Client.WebView;
|
||||
|
||||
internal sealed class TestBrowseWindow : DefaultWindow
|
||||
{
|
||||
protected override Vector2 ContentsMinimumSize => new Vector2(640, 480);
|
||||
|
||||
public TestBrowseWindow()
|
||||
{
|
||||
var wv = new WebViewControl();
|
||||
wv.Url = "https://spacestation14.io";
|
||||
|
||||
Contents.AddChild(wv);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class TestBrowseWindowCommand : LocalizedCommands
|
||||
{
|
||||
public override string Command => "test_browse_window";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
new TestBrowseWindow().Open();
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using Robust.Client.WebView.Headless;
|
||||
using Robust.Client.WebViewHook;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
[assembly: WebViewManagerImpl(typeof(WebViewManager))]
|
||||
@@ -22,6 +23,9 @@ namespace Robust.Client.WebView
|
||||
var cfg = dependencies.Resolve<IConfigurationManagerInternal>();
|
||||
cfg.LoadCVarsFromAssembly(typeof(WebViewManager).Assembly);
|
||||
|
||||
var refl = dependencies.Resolve<IReflectionManager>();
|
||||
refl.LoadAssemblies(typeof(WebViewManager).Assembly);
|
||||
|
||||
dependencies.RegisterInstance<IWebViewManager>(this);
|
||||
dependencies.RegisterInstance<IWebViewManagerInternal>(this);
|
||||
|
||||
|
||||
@@ -97,11 +97,11 @@ internal partial class AudioManager
|
||||
// check the git history, I originally used libvorbisfile which worked and loaded 16 bit LPCM.
|
||||
if (vorbis.Channels == 1)
|
||||
{
|
||||
format = ALFormat.MonoFloat32Ext;
|
||||
format = ALFormat.Mono16;
|
||||
}
|
||||
else if (vorbis.Channels == 2)
|
||||
{
|
||||
format = ALFormat.StereoFloat32Ext;
|
||||
format = ALFormat.Stereo16;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -110,9 +110,9 @@ internal partial class AudioManager
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (float* ptr = vorbis.Data.Span)
|
||||
fixed (short* ptr = vorbis.Data.Span)
|
||||
{
|
||||
AL.BufferData(buffer, format, (IntPtr) ptr, vorbis.Data.Length * sizeof(float),
|
||||
AL.BufferData(buffer, format, (IntPtr) ptr, vorbis.Data.Length * sizeof(short),
|
||||
(int) vorbis.SampleRate);
|
||||
}
|
||||
}
|
||||
@@ -214,9 +214,28 @@ internal partial class AudioManager
|
||||
return new AudioStream(handle, length, channels, name);
|
||||
}
|
||||
|
||||
public void SetMasterVolume(float newVolume)
|
||||
public void SetMasterGain(float newGain)
|
||||
{
|
||||
AL.Listener(ALListenerf.Gain, newVolume);
|
||||
if (newGain < 0f)
|
||||
{
|
||||
OpenALSawmill.Error("Tried to set master gain below 0, clamping to 0");
|
||||
AL.Listener(ALListenerf.Gain, 0f);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
#region Platform hack for MacOS
|
||||
// HACK/BUG: Apple's OpenAL implementation has a bug where values of 0f for listener gain don't actually
|
||||
// HACK/BUG: prevent sound playback. Workaround is to cap the minimum gain at a value just above 0.
|
||||
if (OperatingSystem.IsMacOS() && newGain == 0f)
|
||||
{
|
||||
OpenALSawmill.Verbose("Not setting gain to 0 because Apple can't write an OpenAL implementation");
|
||||
AL.Listener(ALListenerf.Gain, float.Epsilon);
|
||||
return;
|
||||
}
|
||||
#endregion Platform hack for MacOS
|
||||
|
||||
AL.Listener(ALListenerf.Gain, newGain);
|
||||
}
|
||||
|
||||
public void SetAttenuation(Attenuation attenuation)
|
||||
@@ -282,13 +301,15 @@ internal partial class AudioManager
|
||||
return audioSource;
|
||||
}
|
||||
|
||||
public IBufferedAudioSource CreateBufferedAudioSource(int buffers, bool floatAudio=false)
|
||||
/// <inheritdoc/>
|
||||
IBufferedAudioSource? IAudioInternal.CreateBufferedAudioSource(int buffers, bool floatAudio=false)
|
||||
{
|
||||
var source = AL.GenSource();
|
||||
|
||||
if (!AL.IsSource(source))
|
||||
{
|
||||
OpenALSawmill.Error("Failed to generate source. Too many simultaneous audio streams? {0}", Environment.StackTrace);
|
||||
return null;
|
||||
}
|
||||
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
|
||||
@@ -115,7 +115,7 @@ internal sealed partial class AudioManager : IAudioInternal
|
||||
|
||||
IsEfxSupported = HasAlDeviceExtension("ALC_EXT_EFX");
|
||||
|
||||
_cfg.OnValueChanged(CVars.AudioMasterVolume, SetMasterVolume, true);
|
||||
_cfg.OnValueChanged(CVars.AudioMasterVolume, SetMasterGain, true);
|
||||
}
|
||||
|
||||
internal bool IsMainThread()
|
||||
@@ -140,6 +140,18 @@ internal sealed partial class AudioManager : IAudioInternal
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Like _checkAlError but allows custom data to be passed in as relevant.
|
||||
/// </summary>
|
||||
internal void LogALError(string message, [CallerMemberName] string callerMember = "", [CallerLineNumber] int callerLineNumber = -1)
|
||||
{
|
||||
var error = AL.GetError();
|
||||
if (error != ALError.NoError)
|
||||
{
|
||||
OpenALSawmill.Error("[{0}:{1}] AL error: {2}, {3}. Stacktrace is {4}", callerMember, callerLineNumber, error, message, Environment.StackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
public void _checkAlError([CallerMemberName] string callerMember = "", [CallerLineNumber] int callerLineNumber = -1)
|
||||
{
|
||||
var error = AL.GetError();
|
||||
|
||||
@@ -62,6 +62,18 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
{
|
||||
_zOffset = value;
|
||||
_audio.SetZOffset(value);
|
||||
|
||||
var query = AllEntityQuery<AudioComponent>();
|
||||
|
||||
while (query.MoveNext(out var audio))
|
||||
{
|
||||
// Pythagoras back to normal then adjust.
|
||||
var maxDistance = GetAudioDistance(audio.Params.MaxDistance);
|
||||
var refDistance = GetAudioDistance(audio.Params.ReferenceDistance);
|
||||
|
||||
audio.MaxDistance = maxDistance;
|
||||
audio.ReferenceDistance = refDistance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +130,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
/// </summary>
|
||||
public void SetMasterVolume(float value)
|
||||
{
|
||||
_audio.SetMasterVolume(value);
|
||||
_audio.SetMasterGain(value);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
@@ -146,12 +158,24 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
return;
|
||||
}
|
||||
|
||||
// Source has already been set
|
||||
if (component.Loaded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryGetAudio(component.FileName, out var audioResource))
|
||||
{
|
||||
Log.Error($"Error creating audio source for {audioResource}, can't find file {component.FileName}");
|
||||
return;
|
||||
}
|
||||
|
||||
SetupSource(component, audioResource);
|
||||
component.Loaded = true;
|
||||
}
|
||||
|
||||
private void SetupSource(AudioComponent component, AudioResource audioResource, TimeSpan? length = null)
|
||||
{
|
||||
var source = _audio.CreateAudioSource(audioResource);
|
||||
|
||||
if (source == null)
|
||||
@@ -170,8 +194,10 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
// Don't play until first frame so occlusion etc. are correct.
|
||||
component.Gain = 0f;
|
||||
|
||||
length ??= GetAudioLength(component.FileName);
|
||||
|
||||
// If audio came into range then start playback at the correct position.
|
||||
var offset = (Timing.CurTime - component.AudioStart).TotalSeconds % GetAudioLength(component.FileName).TotalSeconds;
|
||||
var offset = (Timing.CurTime - component.AudioStart).TotalSeconds % length.Value.TotalSeconds;
|
||||
|
||||
if (offset > 0)
|
||||
{
|
||||
@@ -300,7 +326,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
return;
|
||||
}
|
||||
|
||||
var paramsGain = MathF.Pow(10, component.Params.Volume / 10);
|
||||
var paramsGain = VolumeToGain(component.Params.Volume);
|
||||
|
||||
// Thought I'd never have to manually calculate gain again but this is the least
|
||||
// unpleasant audio I could get at the moment.
|
||||
@@ -325,13 +351,6 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
var delta = worldPos - listener.Position;
|
||||
var distance = delta.Length();
|
||||
|
||||
if (distance > 0f && distance < 0.01f)
|
||||
{
|
||||
worldPos = listener.Position;
|
||||
delta = Vector2.Zero;
|
||||
distance = 0f;
|
||||
}
|
||||
|
||||
// Out of range so just clip it for us.
|
||||
if (distance > component.MaxDistance)
|
||||
{
|
||||
@@ -340,6 +359,13 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
return;
|
||||
}
|
||||
|
||||
if (distance > 0f && distance < 0.01f)
|
||||
{
|
||||
worldPos = listener.Position;
|
||||
delta = Vector2.Zero;
|
||||
distance = 0f;
|
||||
}
|
||||
|
||||
// Update audio occlusion
|
||||
var occlusion = GetOcclusion(listener, delta, distance, entity);
|
||||
component.Occlusion = occlusion;
|
||||
@@ -383,18 +409,13 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryCreateAudioSource(AudioStream stream, [NotNullWhen(true)] out IAudioSource? source)
|
||||
private bool TryGetAudio(AudioStream stream, [NotNullWhen(true)] out AudioResource? audio)
|
||||
{
|
||||
if (!Timing.IsFirstTimePredicted)
|
||||
{
|
||||
source = null;
|
||||
Log.Error($"Tried to create audio source outside of prediction!");
|
||||
DebugTools.Assert(false);
|
||||
return false;
|
||||
}
|
||||
if (_resourceCache.TryGetResource(stream, out audio))
|
||||
return true;
|
||||
|
||||
source = _audio.CreateAudioSource(stream);
|
||||
return source != null;
|
||||
Log.Error($"Server failed to play audio stream {stream.Title}.");
|
||||
return false;
|
||||
}
|
||||
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string filename, EntityCoordinates coordinates,
|
||||
@@ -411,7 +432,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPredicted(SoundSpecifier? sound, EntityUid source, EntityUid? user, AudioParams? audioParams = null)
|
||||
{
|
||||
if (Timing.IsFirstTimePredicted || sound == null)
|
||||
if (Timing.IsFirstTimePredicted && sound != null)
|
||||
return PlayEntity(sound, Filter.Local(), source, false, audioParams);
|
||||
|
||||
return null; // uhh Lets hope predicted audio never needs to somehow store the playing audio....
|
||||
@@ -419,7 +440,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPredicted(SoundSpecifier? sound, EntityCoordinates coordinates, EntityUid? user, AudioParams? audioParams = null)
|
||||
{
|
||||
if (Timing.IsFirstTimePredicted || sound == null)
|
||||
if (Timing.IsFirstTimePredicted && sound != null)
|
||||
return PlayStatic(sound, Filter.Local(), coordinates, false, audioParams);
|
||||
|
||||
return null;
|
||||
@@ -449,7 +470,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
/// </summary>
|
||||
/// <param name="stream">The audio stream to play.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
private (EntityUid Entity, AudioComponent Component)? PlayGlobal(AudioStream stream, AudioParams? audioParams = null)
|
||||
public (EntityUid Entity, AudioComponent Component)? PlayGlobal(AudioStream stream, AudioParams? audioParams = null)
|
||||
{
|
||||
var (entity, component) = CreateAndStartPlayingStream(audioParams, stream);
|
||||
component.Global = true;
|
||||
@@ -484,8 +505,14 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
/// <param name="stream">The audio stream to play.</param>
|
||||
/// <param name="entity">The entity "emitting" the audio.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
private (EntityUid Entity, AudioComponent Component)? PlayEntity(AudioStream stream, EntityUid entity, AudioParams? audioParams = null)
|
||||
public (EntityUid Entity, AudioComponent Component)? PlayEntity(AudioStream stream, EntityUid entity, AudioParams? audioParams = null)
|
||||
{
|
||||
if (TerminatingOrDeleted(entity))
|
||||
{
|
||||
Log.Error($"Tried to play coordinates audio on a terminating / deleted entity {ToPrettyString(entity)}");
|
||||
return null;
|
||||
}
|
||||
|
||||
var playing = CreateAndStartPlayingStream(audioParams, stream);
|
||||
_xformSys.SetCoordinates(playing.Entity, new EntityCoordinates(entity, Vector2.Zero));
|
||||
|
||||
@@ -519,8 +546,14 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
/// <param name="stream">The audio stream to play.</param>
|
||||
/// <param name="coordinates">The coordinates at which to play the audio.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
private (EntityUid Entity, AudioComponent Component)? PlayStatic(AudioStream stream, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
public (EntityUid Entity, AudioComponent Component)? PlayStatic(AudioStream stream, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
{
|
||||
if (TerminatingOrDeleted(coordinates.EntityId))
|
||||
{
|
||||
Log.Error($"Tried to play coordinates audio on a terminating / deleted entity {ToPrettyString(coordinates.EntityId)}");
|
||||
return null;
|
||||
}
|
||||
|
||||
var playing = CreateAndStartPlayingStream(audioParams, stream);
|
||||
_xformSys.SetCoordinates(playing.Entity, coordinates);
|
||||
return playing;
|
||||
@@ -550,6 +583,16 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
return PlayGlobal(filename, audioParams);
|
||||
}
|
||||
|
||||
public override void LoadStream<T>(AudioComponent component, T stream)
|
||||
{
|
||||
if (stream is AudioStream audioStream)
|
||||
{
|
||||
TryGetAudio(audioStream, out var audio);
|
||||
SetupSource(component, audio!, audioStream.Length);
|
||||
component.Loaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, EntityUid recipient, AudioParams? audioParams = null)
|
||||
{
|
||||
@@ -584,7 +627,8 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
{
|
||||
var audioP = audioParams ?? AudioParams.Default;
|
||||
var entity = EntityManager.CreateEntityUninitialized("Audio", MapCoordinates.Nullspace);
|
||||
var comp = SetupAudio(entity, stream.Name!, audioP);
|
||||
var comp = SetupAudio(entity, null, audioP, stream.Length);
|
||||
LoadStream(comp, stream);
|
||||
EntityManager.InitializeAndStartEntity(entity);
|
||||
var source = comp.Source;
|
||||
|
||||
@@ -593,8 +637,8 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
offset = Math.Clamp(offset, 0f, (float) stream.Length.TotalSeconds - 0.01f);
|
||||
source.PlaybackPosition = offset;
|
||||
|
||||
ApplyAudioParams(audioP, comp);
|
||||
comp.Params = audioP;
|
||||
// For server we will rely on the adjusted one but locally we will have to adjust it ourselves.
|
||||
ApplyAudioParams(comp.Params, comp);
|
||||
source.StartPlaying();
|
||||
return (entity, comp);
|
||||
}
|
||||
@@ -607,8 +651,8 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
source.Pitch = audioParams.Pitch;
|
||||
source.Volume = audioParams.Volume;
|
||||
source.RolloffFactor = audioParams.RolloffFactor;
|
||||
source.MaxDistance = audioParams.MaxDistance;
|
||||
source.ReferenceDistance = audioParams.ReferenceDistance;
|
||||
source.MaxDistance = GetAudioDistance(audioParams.MaxDistance);
|
||||
source.ReferenceDistance = GetAudioDistance(audioParams.ReferenceDistance);
|
||||
source.Looping = audioParams.Loop;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ internal sealed class HeadlessAudioManager : IAudioInternal
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IBufferedAudioSource CreateBufferedAudioSource(int buffers, bool floatAudio = false)
|
||||
public IBufferedAudioSource? CreateBufferedAudioSource(int buffers, bool floatAudio = false)
|
||||
{
|
||||
return DummyBufferedAudioSource.Instance;
|
||||
}
|
||||
@@ -56,7 +56,7 @@ internal sealed class HeadlessAudioManager : IAudioInternal
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetMasterVolume(float value)
|
||||
public void SetMasterGain(float newGain)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Robust.Client.Audio;
|
||||
/// <summary>
|
||||
/// Handles clientside audio.
|
||||
/// </summary>
|
||||
internal interface IAudioInternal
|
||||
internal interface IAudioInternal : IAudioManager
|
||||
{
|
||||
void InitializePostWindowing();
|
||||
void Shutdown();
|
||||
@@ -21,9 +21,11 @@ internal interface IAudioInternal
|
||||
/// </summary>
|
||||
void FlushALDisposeQueues();
|
||||
|
||||
IAudioSource? CreateAudioSource(AudioStream stream);
|
||||
|
||||
IBufferedAudioSource CreateBufferedAudioSource(int buffers, bool floatAudio=false);
|
||||
/// <summary>
|
||||
/// Returns a buffered audio source.
|
||||
/// </summary>
|
||||
/// <returns>null if unable to create the source.</returns>
|
||||
IBufferedAudioSource? CreateBufferedAudioSource(int buffers, bool floatAudio=false);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the velocity for the audio listener.
|
||||
@@ -40,7 +42,6 @@ internal interface IAudioInternal
|
||||
/// </summary>
|
||||
void SetRotation(Angle angle);
|
||||
|
||||
void SetMasterVolume(float value);
|
||||
void SetAttenuation(Attenuation attenuation);
|
||||
|
||||
/// <summary>
|
||||
@@ -59,10 +60,4 @@ internal interface IAudioInternal
|
||||
/// Manually calculates the specified gain for an attenuation source with the specified distance.
|
||||
/// </summary>
|
||||
float GetAttenuationGain(float distance, float rolloffFactor, float referenceDistance, float maxDistance);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
22
Robust.Client/Audio/IAudioManager.cs
Normal file
22
Robust.Client/Audio/IAudioManager.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Robust.Client.Audio.Sources;
|
||||
using Robust.Shared.Audio.Sources;
|
||||
|
||||
namespace Robust.Client.Audio;
|
||||
|
||||
/// <summary>
|
||||
/// Public audio API for stuff that can't go through <see cref="AudioSystem"/>
|
||||
/// </summary>
|
||||
public interface IAudioManager
|
||||
{
|
||||
IAudioSource? CreateAudioSource(AudioStream stream);
|
||||
|
||||
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 SetMasterGain(float gain);
|
||||
}
|
||||
@@ -17,9 +17,9 @@ public interface IMidiManager
|
||||
bool IsAvailable { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Volume, in db.
|
||||
/// Gain of audio.
|
||||
/// </summary>
|
||||
float Volume { get; set; }
|
||||
float Gain { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This method tries to return a midi renderer ready to be used.
|
||||
|
||||
@@ -9,6 +9,7 @@ using NFluidsynth;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Asynchronous;
|
||||
using Robust.Shared.Audio.Midi;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Exceptions;
|
||||
@@ -69,28 +70,32 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
|
||||
[ViewVariables] private readonly List<IMidiRenderer> _renderers = new();
|
||||
|
||||
// To avoid lock contention for now just don't update that much fam.
|
||||
// To avoid lock contention until some kind of MIDI refactor.
|
||||
private TimeSpan _nextUpdate;
|
||||
private TimeSpan _updateFrequency = TimeSpan.FromSeconds(0.1f);
|
||||
|
||||
private SemaphoreSlim _updateSemaphore = new(1);
|
||||
|
||||
private bool _alive = true;
|
||||
[ViewVariables] private Settings? _settings;
|
||||
private Thread? _midiThread;
|
||||
private ISawmill _midiSawmill = default!;
|
||||
private float _volume = 0f;
|
||||
private float _gain = 0f;
|
||||
private bool _volumeDirty = true;
|
||||
|
||||
// Not reliable until Fluidsynth is initialized!
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float Volume
|
||||
public float Gain
|
||||
{
|
||||
get => _volume;
|
||||
get => _gain;
|
||||
set
|
||||
{
|
||||
if (MathHelper.CloseToPercent(_volume, value))
|
||||
var clamped = Math.Clamp(value, 0f, 1f);
|
||||
|
||||
if (MathHelper.CloseToPercent(_gain, clamped))
|
||||
return;
|
||||
|
||||
_cfgMan.SetCVar(CVars.MidiVolume, value);
|
||||
_cfgMan.SetCVar(CVars.MidiVolume, clamped);
|
||||
_volumeDirty = true;
|
||||
}
|
||||
}
|
||||
@@ -142,7 +147,7 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
|
||||
_cfgMan.OnValueChanged(CVars.MidiVolume, value =>
|
||||
{
|
||||
_volume = value;
|
||||
_gain = value;
|
||||
_volumeDirty = true;
|
||||
}, true);
|
||||
|
||||
@@ -340,7 +345,7 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
renderer.LoadSoundfont(file.ToString());
|
||||
}
|
||||
|
||||
renderer.Source.Volume = _volume;
|
||||
renderer.Source.Gain = _gain;
|
||||
|
||||
lock (_renderers)
|
||||
{
|
||||
@@ -364,21 +369,23 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
if (_nextUpdate > _timing.RealTime)
|
||||
return;
|
||||
|
||||
// I don't care for accuracy we only have this for performance for now.
|
||||
_nextUpdate = _timing.RealTime + _updateFrequency;
|
||||
|
||||
// Update positions of streams occasionally.
|
||||
// This has a lot of code duplication with AudioSystem.FrameUpdate(), and they should probably be combined somehow.
|
||||
// so TRUE
|
||||
|
||||
lock (_renderers)
|
||||
{
|
||||
if (_renderers.Count == 0)
|
||||
return;
|
||||
_updateJob.OurPosition = _audioSys.GetListenerCoordinates();
|
||||
|
||||
_updateJob.OurPosition = _audioSys.GetListenerCoordinates();
|
||||
_parallel.ProcessNow(_updateJob, _renderers.Count);
|
||||
}
|
||||
// This semaphore is here to avoid lock contention as much as possible.
|
||||
_updateSemaphore.Wait();
|
||||
|
||||
// The ONLY time this should be contested is with ThreadUpdate.
|
||||
// If that becomes NOT the case then just lock this, remove the semaphore, and drop the update frequency even harder.
|
||||
// ReSharper disable once InconsistentlySynchronizedField
|
||||
_parallel.ProcessNow(_updateJob, _renderers.Count);
|
||||
|
||||
_updateSemaphore.Release();
|
||||
|
||||
_volumeDirty = false;
|
||||
}
|
||||
@@ -393,7 +400,7 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
|
||||
if (_volumeDirty)
|
||||
{
|
||||
renderer.Source.Volume = Volume;
|
||||
renderer.Source.Gain = Gain;
|
||||
}
|
||||
|
||||
if (!renderer.Mono)
|
||||
@@ -433,7 +440,7 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
// Was previously muted maybe so try unmuting it?
|
||||
if (renderer.Source.Gain == 0f)
|
||||
{
|
||||
renderer.Source.Volume = Volume;
|
||||
renderer.Source.Gain = Gain;
|
||||
}
|
||||
|
||||
var worldPos = mapPos.Position;
|
||||
@@ -489,21 +496,39 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
{
|
||||
lock (_renderers)
|
||||
{
|
||||
var toRemove = new ValueList<IMidiRenderer>();
|
||||
|
||||
for (var i = 0; i < _renderers.Count; i++)
|
||||
{
|
||||
var renderer = _renderers[i];
|
||||
if (!renderer.Disposed)
|
||||
{
|
||||
if (renderer.Master is { Disposed: true })
|
||||
renderer.Master = null;
|
||||
|
||||
renderer.Render();
|
||||
lock (renderer)
|
||||
{
|
||||
if (!renderer.Disposed)
|
||||
{
|
||||
if (renderer.Master is { Disposed: true })
|
||||
renderer.Master = null;
|
||||
|
||||
renderer.Render();
|
||||
}
|
||||
else
|
||||
{
|
||||
toRemove.Add(renderer);
|
||||
}
|
||||
}
|
||||
else
|
||||
}
|
||||
|
||||
if (toRemove.Count > 0)
|
||||
{
|
||||
_updateSemaphore.Wait();
|
||||
|
||||
foreach (var renderer in toRemove)
|
||||
{
|
||||
renderer.InternalDispose();
|
||||
_renderers.Remove(renderer);
|
||||
}
|
||||
|
||||
_updateSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -681,7 +706,7 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
{
|
||||
public int MinimumBatchParallel => 2;
|
||||
|
||||
public int BatchSize => 2;
|
||||
public int BatchSize => 1;
|
||||
|
||||
public MidiManager Manager;
|
||||
|
||||
@@ -690,7 +715,13 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
|
||||
public void Execute(int index)
|
||||
{
|
||||
Manager.UpdateRenderer(Renderers[index], OurPosition);
|
||||
// The indices shouldn't be able to be touched while this job is running, just the renderer itself getting locked.
|
||||
var renderer = Renderers[index];
|
||||
|
||||
lock (renderer)
|
||||
{
|
||||
Manager.UpdateRenderer(renderer, OurPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -255,7 +255,7 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
_taskManager = taskManager;
|
||||
_midiSawmill = midiSawmill;
|
||||
|
||||
Source = clydeAudio.CreateBufferedAudioSource(Buffers, true);
|
||||
Source = clydeAudio.CreateBufferedAudioSource(Buffers, true) ?? DummyBufferedAudioSource.Instance;
|
||||
Source.SampleRate = SampleRate;
|
||||
_settings = settings;
|
||||
_soundFontLoader = soundFontLoader;
|
||||
|
||||
@@ -10,7 +10,7 @@ using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Audio.Sources;
|
||||
|
||||
internal abstract class BaseAudioSource : IAudioSource
|
||||
public abstract class BaseAudioSource : IAudioSource
|
||||
{
|
||||
/*
|
||||
* This may look weird having all these methods here however
|
||||
@@ -27,22 +27,25 @@ internal abstract class BaseAudioSource : IAudioSource
|
||||
/// </summary>
|
||||
protected int FilterHandle;
|
||||
|
||||
protected readonly AudioManager Master;
|
||||
internal readonly AudioManager Master;
|
||||
|
||||
/// <summary>
|
||||
/// Prior gain that was set.
|
||||
/// </summary>
|
||||
private float _gain;
|
||||
|
||||
private float _occlusion;
|
||||
|
||||
private bool IsEfxSupported => Master.IsEfxSupported;
|
||||
|
||||
protected BaseAudioSource(AudioManager master, int sourceHandle)
|
||||
internal BaseAudioSource(AudioManager master, int sourceHandle)
|
||||
{
|
||||
Master = master;
|
||||
SourceHandle = sourceHandle;
|
||||
AL.GetSource(SourceHandle, ALSourcef.Gain, out _gain);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Pause()
|
||||
{
|
||||
AL.SourcePause(SourceHandle);
|
||||
@@ -66,6 +69,13 @@ internal abstract class BaseAudioSource : IAudioSource
|
||||
Playing = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Restart()
|
||||
{
|
||||
AL.SourceRewind(SourceHandle);
|
||||
StartPlaying();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual bool Playing
|
||||
{
|
||||
@@ -157,7 +167,22 @@ internal abstract class BaseAudioSource : IAudioSource
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public float Pitch { get; set; }
|
||||
public float Pitch
|
||||
{
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.GetSource(SourceHandle, ALSourcef.Pitch, out var value);
|
||||
Master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle, ALSourcef.Pitch, value);
|
||||
Master._checkAlError();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public float Volume
|
||||
@@ -188,12 +213,13 @@ internal abstract class BaseAudioSource : IAudioSource
|
||||
if (!IsEfxSupported)
|
||||
{
|
||||
AL.GetSource(SourceHandle, ALSourcef.Gain, out var priorGain);
|
||||
priorOcclusion = priorGain / _gain;
|
||||
// Default to 0 to avoid spiking audio, just means it will be muted for a frame in this case.
|
||||
priorOcclusion = _gain == 0 ? 1f : priorGain / _gain;
|
||||
}
|
||||
|
||||
_gain = value;
|
||||
AL.Source(SourceHandle, ALSourcef.Gain, _gain * priorOcclusion);
|
||||
Master._checkAlError();
|
||||
Master.LogALError($"Gain is {_gain:0.00} and priorOcclusion is {priorOcclusion:0.00}. EFX supported: {IsEfxSupported}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,7 +237,7 @@ internal abstract class BaseAudioSource : IAudioSource
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle, ALSourcef.MaxDistance, value);
|
||||
Master._checkAlError();
|
||||
Master.LogALError($"MaxDistance is {value:0.00}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,7 +255,7 @@ internal abstract class BaseAudioSource : IAudioSource
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle, ALSourcef.RolloffFactor, value);
|
||||
Master._checkAlError();
|
||||
Master.LogALError($"RolloffFactor is {value:0.00}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,20 +273,14 @@ internal abstract class BaseAudioSource : IAudioSource
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle, ALSourcef.ReferenceDistance, value);
|
||||
Master._checkAlError();
|
||||
Master.LogALError($"ReferenceDistance is {value:0.00}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public float Occlusion
|
||||
{
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.GetSource(SourceHandle, ALSourcef.MaxDistance, out var value);
|
||||
Master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
get => _occlusion;
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
@@ -275,6 +295,8 @@ internal abstract class BaseAudioSource : IAudioSource
|
||||
gain *= gain * gain;
|
||||
AL.Source(SourceHandle, ALSourcef.Gain, _gain * gain);
|
||||
}
|
||||
|
||||
_occlusion = value;
|
||||
Master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -293,7 +315,7 @@ internal abstract class BaseAudioSource : IAudioSource
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle, ALSourcef.SecOffset, value);
|
||||
Master._checkAlError();
|
||||
Master._checkAlError($"Tried to set invalid playback position of {value:0.00}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,9 +346,11 @@ internal abstract class BaseAudioSource : IAudioSource
|
||||
}
|
||||
}
|
||||
|
||||
public void SetAuxiliary(IAuxiliaryAudio? audio)
|
||||
void IAudioSource.SetAuxiliary(IAuxiliaryAudio? audio)
|
||||
{
|
||||
_checkDisposed();
|
||||
if (!IsEfxSupported)
|
||||
return;
|
||||
|
||||
if (audio is AuxiliaryAudio impAudio)
|
||||
{
|
||||
|
||||
@@ -20,8 +20,6 @@ internal sealed class BufferedAudioSource : BaseAudioSource, IBufferedAudioSourc
|
||||
private bool _float = false;
|
||||
private int FilterHandle;
|
||||
|
||||
private float _gain;
|
||||
|
||||
public int SampleRate { get; set; } = 44100;
|
||||
|
||||
private bool IsEfxSupported => _master.IsEfxSupported;
|
||||
@@ -37,7 +35,6 @@ internal sealed class BufferedAudioSource : BaseAudioSource, IBufferedAudioSourc
|
||||
BufferMap[bufferHandle] = i;
|
||||
}
|
||||
_float = floatAudio;
|
||||
AL.GetSource(sourceHandle, ALSourcef.Gain, out _gain);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -107,6 +107,7 @@ namespace Robust.Client
|
||||
deps.Register<IClyde, ClydeHeadless>();
|
||||
deps.Register<IClipboardManager, ClydeHeadless>();
|
||||
deps.Register<IClydeInternal, ClydeHeadless>();
|
||||
deps.Register<IAudioManager, HeadlessAudioManager>();
|
||||
deps.Register<IAudioInternal, HeadlessAudioManager>();
|
||||
deps.Register<IInputManager, InputManager>();
|
||||
deps.Register<IFileDialogManager, DummyFileDialogManager>();
|
||||
@@ -116,6 +117,7 @@ namespace Robust.Client
|
||||
deps.Register<IClyde, Clyde>();
|
||||
deps.Register<IClipboardManager, Clyde>();
|
||||
deps.Register<IClydeInternal, Clyde>();
|
||||
deps.Register<IAudioManager, AudioManager>();
|
||||
deps.Register<IAudioInternal, AudioManager>();
|
||||
deps.Register<IInputManager, ClydeInputManager>();
|
||||
deps.Register<IFileDialogManager, FileDialogManager>();
|
||||
|
||||
@@ -8,7 +8,7 @@ using Robust.Shared.Network.Messages;
|
||||
|
||||
namespace Robust.Client.Console;
|
||||
|
||||
internal sealed partial class ClientConsoleHost
|
||||
internal partial class ClientConsoleHost
|
||||
{
|
||||
private readonly Dictionary<int, PendingCompletion> _completionsPending = new();
|
||||
private int _completionSeq;
|
||||
|
||||
@@ -47,7 +47,8 @@ namespace Robust.Client.Console
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IClientConsoleHost" />
|
||||
internal sealed partial class ClientConsoleHost : ConsoleHost, IClientConsoleHost, IConsoleHostInternal, IPostInjectInit
|
||||
[Virtual]
|
||||
internal partial class ClientConsoleHost : ConsoleHost, IClientConsoleHost, IConsoleHostInternal, IPostInjectInit
|
||||
{
|
||||
[Dependency] private readonly IClientConGroupController _conGroup = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
@@ -198,6 +199,7 @@ namespace Robust.Client.Console.Commands
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
internal sealed class ShowRayCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystems = default!;
|
||||
@@ -224,6 +226,7 @@ namespace Robust.Client.Console.Commands
|
||||
mgr.DebugRayLifetime = TimeSpan.FromSeconds(duration);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
internal sealed class DisconnectCommand : LocalizedCommands
|
||||
{
|
||||
@@ -492,9 +495,9 @@ namespace Robust.Client.Console.Commands
|
||||
}
|
||||
}
|
||||
|
||||
internal static List<(string, string)> PropertyValuesFor(Control control)
|
||||
internal static List<MemberInfo> GetAllMembers(Control control)
|
||||
{
|
||||
var members = new List<(string, string)>();
|
||||
var members = new List<MemberInfo>();
|
||||
var type = control.GetType();
|
||||
|
||||
foreach (var fieldInfo in type.GetAllFields())
|
||||
@@ -504,7 +507,7 @@ namespace Robust.Client.Console.Commands
|
||||
continue;
|
||||
}
|
||||
|
||||
members.Add((fieldInfo.Name, fieldInfo.GetValue(control)?.ToString() ?? "null"));
|
||||
members.Add(fieldInfo);
|
||||
}
|
||||
|
||||
foreach (var propertyInfo in type.GetAllProperties())
|
||||
@@ -514,7 +517,19 @@ namespace Robust.Client.Console.Commands
|
||||
continue;
|
||||
}
|
||||
|
||||
members.Add((propertyInfo.Name, propertyInfo.GetValue(control)?.ToString() ?? "null"));
|
||||
members.Add(propertyInfo);
|
||||
}
|
||||
|
||||
return members;
|
||||
}
|
||||
|
||||
internal static List<(string, string)> PropertyValuesFor(Control control)
|
||||
{
|
||||
var members = new List<(string, string)>();
|
||||
|
||||
foreach (var fieldInfo in GetAllMembers(control))
|
||||
{
|
||||
members.Add((fieldInfo.Name, fieldInfo.GetValue(control)?.ToString() ?? "null"));
|
||||
}
|
||||
|
||||
foreach (var (attachedProperty, value) in control.AllAttachedProperties)
|
||||
@@ -526,6 +541,35 @@ namespace Robust.Client.Console.Commands
|
||||
members.Sort((a, b) => string.Compare(a.Item1, b.Item1, StringComparison.Ordinal));
|
||||
return members;
|
||||
}
|
||||
|
||||
internal static Dictionary<string, List<(string, string)>> PropertyValuesForInheritance(Control control)
|
||||
{
|
||||
var returnVal = new Dictionary<string, List<(string, string)>>();
|
||||
var engine = typeof(Control).Assembly;
|
||||
|
||||
foreach (var member in GetAllMembers(control))
|
||||
{
|
||||
var type = member.DeclaringType!;
|
||||
var cname = type.Assembly == engine ? type.Name : type.ToString();
|
||||
|
||||
if (type != typeof(Control))
|
||||
cname = $"Control > {cname}";
|
||||
|
||||
returnVal.GetOrNew(cname).Add((member.Name, member.GetValue(control)?.ToString() ?? "null"));
|
||||
}
|
||||
|
||||
foreach (var (attachedProperty, value) in control.AllAttachedProperties)
|
||||
{
|
||||
var cname = $"Attached > {attachedProperty.OwningType.Name}";
|
||||
returnVal.GetOrNew(cname).Add((attachedProperty.Name, value?.ToString() ?? "null"));
|
||||
}
|
||||
|
||||
foreach (var v in returnVal.Values)
|
||||
{
|
||||
v.Sort((a, b) => string.Compare(a.Item1, b.Item1, StringComparison.Ordinal));
|
||||
}
|
||||
return returnVal;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class SetClipboardCommand : LocalizedCommands
|
||||
|
||||
@@ -4,18 +4,17 @@ using System.Numerics;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Debugging;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Client.Debugging
|
||||
{
|
||||
internal sealed class DebugRayDrawingSystem : SharedDebugRayDrawingSystem
|
||||
{
|
||||
#if DEBUG
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTimer = default!;
|
||||
|
||||
@@ -28,6 +27,8 @@ namespace Robust.Client.Debugging
|
||||
public Vector2 RayHit;
|
||||
public TimeSpan LifeTime;
|
||||
public bool DidActuallyHit;
|
||||
public bool Server;
|
||||
public MapId Map;
|
||||
}
|
||||
|
||||
public bool DebugDrawRays
|
||||
@@ -73,7 +74,8 @@ namespace Robust.Client.Debugging
|
||||
DidActuallyHit = ev.Results != null,
|
||||
RayOrigin = ev.Ray.Position,
|
||||
RayHit = ev.Results?.HitPos ?? ev.Ray.Direction * ev.MaxLength + ev.Ray.Position,
|
||||
LifeTime = _gameTimer.RealTime + DebugRayLifetime
|
||||
LifeTime = _gameTimer.RealTime + DebugRayLifetime,
|
||||
Map = ev.Map
|
||||
};
|
||||
|
||||
_raysWithLifeTime.Add(newRayWithLifetime);
|
||||
@@ -93,7 +95,9 @@ namespace Robust.Client.Debugging
|
||||
DidActuallyHit = msg.DidHit,
|
||||
RayOrigin = msg.RayOrigin,
|
||||
RayHit = msg.RayHit,
|
||||
LifeTime = _gameTimer.RealTime + DebugRayLifetime
|
||||
LifeTime = _gameTimer.RealTime + DebugRayLifetime,
|
||||
Server = true,
|
||||
Map = msg.Map
|
||||
};
|
||||
|
||||
_raysWithLifeTime.Add(newRayWithLifetime);
|
||||
@@ -114,10 +118,20 @@ namespace Robust.Client.Debugging
|
||||
var handle = args.WorldHandle;
|
||||
foreach (var ray in _owner._raysWithLifeTime)
|
||||
{
|
||||
if (args.MapId != ray.Map)
|
||||
continue;
|
||||
|
||||
Color color;
|
||||
if (ray.Server)
|
||||
color = ray.DidActuallyHit ? Color.Cyan : Color.Orange;
|
||||
else
|
||||
color = ray.DidActuallyHit ? Color.Blue : Color.Red;
|
||||
|
||||
handle.DrawLine(
|
||||
ray.RayOrigin,
|
||||
ray.RayHit,
|
||||
ray.DidActuallyHit ? Color.Yellow : Color.Magenta);
|
||||
color
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,5 +142,6 @@ namespace Robust.Client.Debugging
|
||||
_owner._raysWithLifeTime.RemoveAll(r => r.LifeTime < _owner._gameTimer.RealTime);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,9 +109,15 @@ namespace Robust.Client
|
||||
{
|
||||
IsBackground = false,
|
||||
Priority = priority,
|
||||
Name = "Game thread",
|
||||
Name = "Game thread"
|
||||
};
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
// Necessary for CEF to not complain when using CEF debug binaries.
|
||||
_gameThread.SetApartmentState(ApartmentState.STA);
|
||||
}
|
||||
|
||||
_gameThread.Start();
|
||||
|
||||
// Will block until game exit
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
public override void QueueDeleteEntity(EntityUid? uid)
|
||||
{
|
||||
if (uid == null)
|
||||
if (uid == null || uid == EntityUid.Invalid)
|
||||
return;
|
||||
|
||||
if (IsClientSide(uid.Value))
|
||||
@@ -98,25 +98,38 @@ namespace Robust.Client.GameObjects
|
||||
base.Dirty(ent, meta);
|
||||
}
|
||||
|
||||
public override EntityStringRepresentation ToPrettyString(EntityUid uid, MetaDataComponent? metaDataComponent = null)
|
||||
/// <inheritdoc />
|
||||
public override void Dirty<T1, T2>(Entity<T1, T2> ent, MetaDataComponent? meta = null)
|
||||
{
|
||||
if (_playerManager.LocalPlayer?.ControlledEntity == uid)
|
||||
return base.ToPrettyString(uid) with { Session = _playerManager.LocalPlayer.Session };
|
||||
if (_gameTiming.InPrediction)
|
||||
base.Dirty(ent, meta);
|
||||
}
|
||||
|
||||
return base.ToPrettyString(uid);
|
||||
/// <inheritdoc />
|
||||
public override void Dirty<T1, T2, T3>(Entity<T1, T2, T3> ent, MetaDataComponent? meta = null)
|
||||
{
|
||||
if (_gameTiming.InPrediction)
|
||||
base.Dirty(ent, meta);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Dirty<T1, T2, T3, T4>(Entity<T1, T2, T3, T4> ent, MetaDataComponent? meta = null)
|
||||
{
|
||||
if (_gameTiming.InPrediction)
|
||||
base.Dirty(ent, meta);
|
||||
}
|
||||
|
||||
public override void RaisePredictiveEvent<T>(T msg)
|
||||
{
|
||||
var localPlayer = _playerManager.LocalPlayer;
|
||||
DebugTools.AssertNotNull(localPlayer);
|
||||
var session = _playerManager.LocalSession;
|
||||
DebugTools.AssertNotNull(session);
|
||||
|
||||
var sequence = _stateMan.SystemMessageDispatched(msg);
|
||||
EntityNetManager?.SendSystemNetworkMessage(msg, sequence);
|
||||
|
||||
DebugTools.Assert(!_stateMan.IsPredictionEnabled || _gameTiming.InPrediction && _gameTiming.IsFirstTimePredicted || _client.RunLevel != ClientRunLevel.Connected);
|
||||
|
||||
var eventArgs = new EntitySessionEventArgs(localPlayer!.Session);
|
||||
var eventArgs = new EntitySessionEventArgs(session!);
|
||||
EventBus.RaiseEvent(EventSource.Local, msg);
|
||||
EventBus.RaiseEvent(EventSource.Local, new EntitySessionMessage<T>(eventArgs, msg));
|
||||
}
|
||||
|
||||
@@ -18,11 +18,5 @@ namespace Robust.Client.GameObjects
|
||||
public ComponentStateApplyException(string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
}
|
||||
|
||||
protected ComponentStateApplyException(
|
||||
SerializationInfo info,
|
||||
StreamingContext context) : base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,6 +319,7 @@ namespace Robust.Client.GameObjects
|
||||
Scale = Vector2.One,
|
||||
Visible = true,
|
||||
RenderingStrategy = LayerRenderingStrategy.UseSpriteStrategy,
|
||||
Cycle = false,
|
||||
});
|
||||
state = null;
|
||||
texture = null;
|
||||
@@ -794,6 +795,7 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
|
||||
layer.RenderingStrategy = layerDatum.RenderingStrategy ?? layer.RenderingStrategy;
|
||||
layer.Cycle = layerDatum.Cycle;
|
||||
|
||||
layer.Color = layerDatum.Color ?? layer.Color;
|
||||
layer._rotation = layerDatum.Rotation ?? layer._rotation;
|
||||
@@ -1515,6 +1517,19 @@ namespace Robust.Client.GameObjects
|
||||
[ViewVariables] public float AnimationTime;
|
||||
[ViewVariables] public int AnimationFrame;
|
||||
|
||||
/// <summary>
|
||||
/// Is the animation currently playing in reverse.
|
||||
/// </summary>
|
||||
[ViewVariables] public bool Reversed { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// If every animation delay finishes do we reverse it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only applies if the state is auto-animated.
|
||||
/// </remarks>
|
||||
[ViewVariables] public bool Cycle;
|
||||
|
||||
private RSI.State? _actualState;
|
||||
[ViewVariables] public RSI.State? ActualState => _actualState;
|
||||
|
||||
@@ -2033,15 +2048,49 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
internal void AdvanceFrameAnimation(RSI.State state)
|
||||
{
|
||||
// Can't advance frames without more than 1 delay which is already checked above.
|
||||
var delayCount = state.DelayCount;
|
||||
while (AnimationTimeLeft < 0)
|
||||
{
|
||||
AnimationFrame += 1;
|
||||
|
||||
if (AnimationFrame >= delayCount)
|
||||
if (Reversed)
|
||||
{
|
||||
AnimationFrame = 0;
|
||||
AnimationTime = -AnimationTimeLeft;
|
||||
AnimationFrame -= 1;
|
||||
|
||||
// Animation finished, do we cycle back to positive or reset.
|
||||
if (AnimationFrame < 0)
|
||||
{
|
||||
if (Cycle)
|
||||
{
|
||||
AnimationFrame = 1;
|
||||
Reversed = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
AnimationFrame = delayCount - 1;
|
||||
}
|
||||
|
||||
AnimationTime = -AnimationTimeLeft;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AnimationFrame += 1;
|
||||
|
||||
// Animation finished, do we reverse or reset.
|
||||
if (AnimationFrame >= delayCount)
|
||||
{
|
||||
if (Cycle)
|
||||
{
|
||||
AnimationFrame = delayCount - 2;
|
||||
Reversed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
AnimationFrame = 0;
|
||||
}
|
||||
|
||||
AnimationTime = -AnimationTimeLeft;
|
||||
}
|
||||
}
|
||||
|
||||
AnimationTimeLeft += state.GetDelay(AnimationFrame);
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Map.Enumerators;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using static Robust.Shared.GameObjects.OccluderComponent;
|
||||
|
||||
namespace Robust.Client.GameObjects;
|
||||
@@ -34,13 +31,12 @@ internal sealed class ClientOccluderSystem : OccluderSystem
|
||||
SubscribeLocalEvent<OccluderComponent, ComponentShutdown>(OnShutdown);
|
||||
}
|
||||
|
||||
public override void SetEnabled(EntityUid uid, bool enabled, OccluderComponent? comp = null)
|
||||
public override void SetEnabled(EntityUid uid, bool enabled, OccluderComponent? comp = null, MetaDataComponent? meta = null)
|
||||
{
|
||||
if (!Resolve(uid, ref comp, false) || enabled == comp.Enabled)
|
||||
return;
|
||||
|
||||
comp.Enabled = enabled;
|
||||
Dirty(uid, comp);
|
||||
base.SetEnabled(uid, enabled, comp, meta);
|
||||
|
||||
var xform = Transform(uid);
|
||||
QueueTreeUpdate(uid, comp, xform);
|
||||
|
||||
@@ -54,12 +54,13 @@ namespace Robust.Client.GameObjects
|
||||
DebugTools.Assert(ExpectedEntities.TryGetValue(netEntity, out var expectedContainer) && expectedContainer == cont && cont.ExpectedEntities.Contains(netEntity));
|
||||
}
|
||||
|
||||
private void HandleEntityInitialized(EntityUid uid)
|
||||
private void HandleEntityInitialized(Entity<MetaDataComponent> ent)
|
||||
{
|
||||
if (!RemoveExpectedEntity(GetNetEntity(uid), out var container))
|
||||
var (uid, meta) = ent;
|
||||
if (!RemoveExpectedEntity(meta.NetEntity, out var container))
|
||||
return;
|
||||
|
||||
container.Insert(uid, EntityManager, transform: TransformQuery.GetComponent(uid), meta: MetaQuery.GetComponent(uid));
|
||||
Insert((uid, TransformQuery.GetComponent(uid), MetaQuery.GetComponent(uid), null), container);
|
||||
}
|
||||
|
||||
private void HandleComponentState(EntityUid uid, ContainerManagerComponent component, ref ComponentHandleState args)
|
||||
@@ -81,17 +82,17 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
foreach (var entity in container.ContainedEntities.ToArray())
|
||||
{
|
||||
container.Remove(entity,
|
||||
EntityManager,
|
||||
TransformQuery.GetComponent(entity),
|
||||
MetaQuery.GetComponent(entity),
|
||||
Remove(
|
||||
(entity, TransformQuery.GetComponent(entity), MetaQuery.GetComponent(entity)),
|
||||
container,
|
||||
force: true,
|
||||
reparent: false);
|
||||
reparent: false
|
||||
);
|
||||
|
||||
DebugTools.Assert(!container.Contains(entity));
|
||||
}
|
||||
|
||||
container.Shutdown(EntityManager, _netMan);
|
||||
ShutdownContainer(container);
|
||||
toDelete.Add(id);
|
||||
}
|
||||
|
||||
@@ -108,7 +109,7 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
var type = _serializer.FindSerializedType(typeof(BaseContainer), data.ContainerType);
|
||||
container = _dynFactory.CreateInstanceUnchecked<BaseContainer>(type!, inject:false);
|
||||
container.Init(id, uid, component);
|
||||
InitContainer(container, (uid, component), id);
|
||||
component.Containers.Add(id, container);
|
||||
}
|
||||
|
||||
@@ -132,13 +133,12 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
foreach (var entity in toRemove)
|
||||
{
|
||||
container.Remove(
|
||||
entity,
|
||||
EntityManager,
|
||||
TransformQuery.GetComponent(entity),
|
||||
MetaQuery.GetComponent(entity),
|
||||
Remove(
|
||||
(entity, TransformQuery.GetComponent(entity), MetaQuery.GetComponent(entity)),
|
||||
container,
|
||||
force: true,
|
||||
reparent: false);
|
||||
reparent: false
|
||||
);
|
||||
|
||||
DebugTools.Assert(!container.Contains(entity));
|
||||
}
|
||||
@@ -188,11 +188,12 @@ namespace Robust.Client.GameObjects
|
||||
continue;
|
||||
|
||||
RemoveExpectedEntity(netEnt, out _);
|
||||
container.Insert(entity, EntityManager,
|
||||
TransformQuery.GetComponent(entity),
|
||||
Insert(
|
||||
(entity, TransformQuery.GetComponent(entity), MetaQuery.GetComponent(entity), null),
|
||||
container,
|
||||
xform,
|
||||
MetaQuery.GetComponent(entity),
|
||||
force: true);
|
||||
force: true
|
||||
);
|
||||
|
||||
DebugTools.Assert(container.Contains(entity));
|
||||
}
|
||||
@@ -222,7 +223,7 @@ namespace Robust.Client.GameObjects
|
||||
return;
|
||||
}
|
||||
|
||||
container.Insert(message.Entity, EntityManager);
|
||||
Insert(message.Entity, container);
|
||||
}
|
||||
|
||||
public void AddExpectedEntity(NetEntity netEntity, BaseContainer container)
|
||||
@@ -324,32 +325,30 @@ namespace Robust.Client.GameObjects
|
||||
if (_pointLightQuery.TryGetComponent(entity, out var light))
|
||||
_lightSys.SetContainerOccluded(entity, lightOccluded, light);
|
||||
|
||||
var childEnumerator = xform.ChildEnumerator;
|
||||
|
||||
// Try to avoid TryComp if we already know stuff is occluded.
|
||||
if ((!spriteOccluded || !lightOccluded) && TryComp<ContainerManagerComponent>(entity, out var manager))
|
||||
{
|
||||
while (childEnumerator.MoveNext(out var child))
|
||||
foreach (var child in xform._children)
|
||||
{
|
||||
// Thank god it's by value and not by ref.
|
||||
var childSpriteOccluded = spriteOccluded;
|
||||
var childLightOccluded = lightOccluded;
|
||||
|
||||
// We already know either sprite or light is not occluding so need to check container.
|
||||
if (manager.TryGetContainer(child.Value, out var container))
|
||||
if (manager.TryGetContainer(child, out var container))
|
||||
{
|
||||
childSpriteOccluded = childSpriteOccluded || !container.ShowContents;
|
||||
childLightOccluded = childLightOccluded || container.OccludesLight;
|
||||
}
|
||||
|
||||
UpdateEntity(child.Value, TransformQuery.GetComponent(child.Value), childSpriteOccluded, childLightOccluded);
|
||||
UpdateEntity(child, TransformQuery.GetComponent(child), childSpriteOccluded, childLightOccluded);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while (childEnumerator.MoveNext(out var child))
|
||||
foreach (var child in xform._children)
|
||||
{
|
||||
UpdateEntity(child.Value, TransformQuery.GetComponent(child.Value), spriteOccluded, lightOccluded);
|
||||
UpdateEntity(child, TransformQuery.GetComponent(child), spriteOccluded, lightOccluded);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,30 +8,28 @@ using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
namespace Robust.Client.GameObjects;
|
||||
|
||||
public sealed class MapSystem : SharedMapSystem
|
||||
{
|
||||
public sealed class MapSystem : SharedMapSystem
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
[Dependency] private readonly IResourceCache _resource = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
[Dependency] private readonly IResourceCache _resource = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
|
||||
base.Initialize();
|
||||
_overlayManager.AddOverlay(new TileEdgeOverlay(EntityManager, _resource, _tileDefinitionManager));
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_overlayManager.AddOverlay(new TileEdgeOverlay(EntityManager, _mapManager, _resource, _tileDefinitionManager));
|
||||
}
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_overlayManager.RemoveOverlay<TileEdgeOverlay>();
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_overlayManager.RemoveOverlay<TileEdgeOverlay>();
|
||||
}
|
||||
|
||||
protected override void OnMapAdd(EntityUid uid, MapComponent component, ComponentAdd args)
|
||||
{
|
||||
EnsureComp<PhysicsMapComponent>(uid);
|
||||
}
|
||||
protected override void OnMapAdd(EntityUid uid, MapComponent component, ComponentAdd args)
|
||||
{
|
||||
EnsureComp<PhysicsMapComponent>(uid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +68,10 @@ namespace Robust.Client.GameObjects
|
||||
return RemCompDeferred<PointLightComponent>(uid);
|
||||
}
|
||||
|
||||
protected override void UpdatePriority(EntityUid uid, SharedPointLightComponent comp, MetaDataComponent meta)
|
||||
{
|
||||
}
|
||||
|
||||
private void HandleInit(EntityUid uid, PointLightComponent component, ComponentInit args)
|
||||
{
|
||||
SetMask(component.MaskPath, component);
|
||||
@@ -95,28 +99,23 @@ namespace Robust.Client.GameObjects
|
||||
_lightTree.QueueTreeUpdate(uid, clientComp);
|
||||
}
|
||||
|
||||
public override void SetEnabled(EntityUid uid, bool enabled, SharedPointLightComponent? comp = null)
|
||||
public override void SetEnabled(EntityUid uid, bool enabled, SharedPointLightComponent? comp = null, MetaDataComponent? meta = null)
|
||||
{
|
||||
if (!ResolveLight(uid, ref comp) || enabled == comp.Enabled || comp is not PointLightComponent clientComp)
|
||||
return;
|
||||
|
||||
comp.Enabled = enabled;
|
||||
RaiseLocalEvent(uid, new PointLightToggleEvent(comp.Enabled));
|
||||
Dirty(uid, comp);
|
||||
|
||||
base.SetEnabled(uid, enabled, comp, meta);
|
||||
if (!comp.ContainerOccluded)
|
||||
_lightTree.QueueTreeUpdate(uid, clientComp);
|
||||
}
|
||||
|
||||
public override void SetRadius(EntityUid uid, float radius, SharedPointLightComponent? comp = null)
|
||||
public override void SetRadius(EntityUid uid, float radius, SharedPointLightComponent? comp = null, MetaDataComponent? meta = null)
|
||||
{
|
||||
if (!ResolveLight(uid, ref comp) || MathHelper.CloseToPercent(radius, comp.Radius) ||
|
||||
comp is not PointLightComponent clientComp)
|
||||
return;
|
||||
|
||||
comp.Radius = radius;
|
||||
Dirty(uid, comp);
|
||||
|
||||
base.SetRadius(uid, radius, comp, meta);
|
||||
if (clientComp.TreeUid != null)
|
||||
_lightTree.QueueTreeUpdate(uid, clientComp);
|
||||
}
|
||||
|
||||
@@ -122,14 +122,13 @@ public sealed partial class SpriteSystem
|
||||
return GetFallbackState();
|
||||
}
|
||||
|
||||
private void OnPrototypesReloaded(PrototypesReloadedEventArgs protoReloaded)
|
||||
private void OnPrototypesReloaded(PrototypesReloadedEventArgs args)
|
||||
{
|
||||
// Check if any EntityPrototype has been changed.
|
||||
if (!protoReloaded.ByType.TryGetValue(typeof(EntityPrototype), out var changedSet))
|
||||
if (!args.TryGetModified<EntityPrototype>(out var modified))
|
||||
return;
|
||||
|
||||
// Remove all changed prototypes from the cache, if they're there.
|
||||
foreach (var (prototype, _) in changedSet.Modified)
|
||||
foreach (var prototype in modified)
|
||||
{
|
||||
// Let's be lazy and not regenerate them until something needs them again.
|
||||
_cachedPrototypeIcons.Remove(prototype);
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
UpdatesAfter.Add(typeof(SpriteTreeSystem));
|
||||
|
||||
_proto.PrototypesReloaded += OnPrototypesReloaded;
|
||||
SubscribeLocalEvent<PrototypesReloadedEventArgs>(OnPrototypesReloaded);
|
||||
SubscribeLocalEvent<SpriteComponent, SpriteUpdateInertEvent>(QueueUpdateInert);
|
||||
SubscribeLocalEvent<SpriteComponent, ComponentInit>(OnInit);
|
||||
|
||||
@@ -75,7 +75,6 @@ namespace Robust.Client.GameObjects
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_proto.PrototypesReloaded -= OnPrototypesReloaded;
|
||||
_cfg.UnsubValueChanged(CVars.RenderSpriteDirectionBias, OnBiasChanged);
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ public sealed class ClientDirtySystem : EntitySystem
|
||||
return;
|
||||
|
||||
// Client-side entity deletion is not supported and will cause errors.
|
||||
Log.Error($"Predicting the deletion of a networked entity: {ToPrettyString(ev.Entity)}. Trace: {Environment.StackTrace}");
|
||||
Log.Error($"Predicting the deletion of a networked entity: {ToPrettyString(ev.Entity.Owner, ev.Entity.Comp)}. Trace: {Environment.StackTrace}");
|
||||
}
|
||||
|
||||
private void OnCompRemoved(RemovedComponentEventArgs args)
|
||||
@@ -71,9 +71,9 @@ public sealed class ClientDirtySystem : EntitySystem
|
||||
RemovedComponents.Clear();
|
||||
}
|
||||
|
||||
private void OnEntityDirty(EntityUid e)
|
||||
private void OnEntityDirty(Entity<MetaDataComponent> e)
|
||||
{
|
||||
if (_timing.InPrediction && !IsClientSide(e))
|
||||
if (_timing.InPrediction && !IsClientSide(e, e))
|
||||
DirtyEntities.Add(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,19 +174,21 @@ namespace Robust.Client.GameStates
|
||||
|
||||
private void OnComponentAdded(AddedComponentEventArgs args)
|
||||
{
|
||||
if (_resettingPredictedEntities)
|
||||
{
|
||||
var comp = args.ComponentType;
|
||||
if (!_resettingPredictedEntities)
|
||||
return;
|
||||
|
||||
if (comp.NetID == null)
|
||||
return;
|
||||
var comp = args.ComponentType;
|
||||
if (comp.NetID == null)
|
||||
return;
|
||||
|
||||
_sawmill.Error($"""
|
||||
Added component {comp.Name} with net id {comp.NetID} while resetting predicted entities.
|
||||
Stack trace:
|
||||
{Environment.StackTrace}
|
||||
""");
|
||||
}
|
||||
if (_entityManager.IsClientSide(args.BaseArgs.Owner))
|
||||
return;
|
||||
|
||||
_sawmill.Error($"""
|
||||
Added component {comp.Name} to entity {_entityManager.ToPrettyString(args.BaseArgs.Owner)} while resetting predicted entities.
|
||||
Stack trace:
|
||||
{Environment.StackTrace}
|
||||
""");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -341,7 +343,7 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
|
||||
// If we were waiting for a new state, we are now applying it.
|
||||
if (_processor.WaitingForFull)
|
||||
if (curState.FromSequence == GameTick.Zero)
|
||||
{
|
||||
_processor.OnFullStateReceived();
|
||||
_timing.LastProcessedTick = curState.ToSequence;
|
||||
@@ -349,7 +351,10 @@ namespace Robust.Client.GameStates
|
||||
PartialStateReset(curState, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugTools.Assert(!_processor.WaitingForFull);
|
||||
_timing.LastProcessedTick += 1;
|
||||
}
|
||||
|
||||
_timing.CurTick = _timing.LastRealTick = _timing.LastProcessedTick;
|
||||
|
||||
@@ -530,7 +535,6 @@ namespace Robust.Client.GameStates
|
||||
using var __ = _timing.StartStateApplicationArea();
|
||||
|
||||
// This is terrible, and I hate it. This also needs to run even when prediction is disabled.
|
||||
_entitySystemManager.GetEntitySystem<SharedGridTraversalSystem>().QueuedEvents.Clear();
|
||||
_entitySystemManager.GetEntitySystem<TransformSystem>().Reset();
|
||||
|
||||
if (!PredictionNeedsResetting)
|
||||
@@ -663,14 +667,13 @@ namespace Robust.Client.GameStates
|
||||
|
||||
foreach (var netEntity in createdEntities)
|
||||
{
|
||||
var (createdEntity, meta) = _entityManager.GetEntityData(netEntity);
|
||||
var (_, meta) = _entityManager.GetEntityData(netEntity);
|
||||
var compData = _compDataPool.Get();
|
||||
_outputData.Add(netEntity, compData);
|
||||
|
||||
foreach (var (netId, component) in meta.NetComponents)
|
||||
{
|
||||
if (!component.NetSyncEnabled)
|
||||
continue;
|
||||
DebugTools.Assert(component.NetSyncEnabled);
|
||||
|
||||
var state = _entityManager.GetComponentState(bus, component, null, GameTick.Zero);
|
||||
DebugTools.Assert(state is not IComponentDeltaState delta || delta.FullState);
|
||||
@@ -981,16 +984,15 @@ namespace Robust.Client.GameStates
|
||||
xformSys.DetachParentToNull(ent, xform);
|
||||
|
||||
// Then detach all children.
|
||||
var childEnumerator = xform.ChildEnumerator;
|
||||
while (childEnumerator.MoveNext(out var child))
|
||||
foreach (var child in xform._children)
|
||||
{
|
||||
xformSys.DetachParentToNull(child.Value, xforms.GetComponent(child.Value), xform);
|
||||
xformSys.DetachParentToNull(child, xforms.GetComponent(child), xform);
|
||||
|
||||
if (deleteClientChildren
|
||||
&& !deleteClientEntities // don't add duplicates
|
||||
&& _entities.IsClientSide(child.Value))
|
||||
&& _entities.IsClientSide(child))
|
||||
{
|
||||
_toDelete.Add(child.Value);
|
||||
_toDelete.Add(child);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1038,7 +1040,7 @@ namespace Robust.Client.GameStates
|
||||
var childEnumerator = xform.ChildEnumerator;
|
||||
while (childEnumerator.MoveNext(out var child))
|
||||
{
|
||||
xformSys.DetachParentToNull(child.Value, xforms.GetComponent(child.Value), xform);
|
||||
xformSys.DetachParentToNull(child, xforms.GetComponent(child), xform);
|
||||
}
|
||||
|
||||
// Finally, delete the entity.
|
||||
@@ -1129,7 +1131,7 @@ namespace Robust.Client.GameStates
|
||||
(containerMeta.Flags & MetaDataFlags.Detached) == 0 &&
|
||||
containerSys.TryGetContainingContainer(xform.ParentUid, ent.Value, out container, null, true))
|
||||
{
|
||||
container.Remove(ent.Value, _entities, xform, meta, false, true);
|
||||
containerSys.Remove((ent.Value, xform, meta), container, false, true);
|
||||
}
|
||||
|
||||
meta._flags |= MetaDataFlags.Detached;
|
||||
@@ -1217,7 +1219,9 @@ namespace Robust.Client.GameStates
|
||||
|
||||
foreach (var (id, comp) in meta.NetComponents)
|
||||
{
|
||||
if (comp.NetSyncEnabled && !curState.NetComponents.Contains(id))
|
||||
DebugTools.Assert(comp.NetSyncEnabled);
|
||||
|
||||
if (!curState.NetComponents.Contains(id))
|
||||
_toRemove.Add(comp);
|
||||
}
|
||||
|
||||
@@ -1427,7 +1431,7 @@ namespace Robust.Client.GameStates
|
||||
void _recursiveRemoveState(NetEntity netEntity, TransformComponent xform, EntityQuery<MetaDataComponent> metaQuery, EntityQuery<TransformComponent> xformQuery)
|
||||
{
|
||||
_processor._lastStateFullRep.Remove(netEntity);
|
||||
foreach (var child in xform.ChildEntities)
|
||||
foreach (var child in xform._children)
|
||||
{
|
||||
if (xformQuery.TryGetComponent(child, out var childXform) &&
|
||||
metaQuery.TryGetComponent(child, out var childMeta))
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -18,10 +20,15 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private readonly Dictionary<EntityUid, Dictionary<Vector2i, MapChunkData>> _mapChunkData =
|
||||
new();
|
||||
|
||||
/// <summary>
|
||||
/// To avoid spamming errors we'll just log it once and move on.
|
||||
/// </summary>
|
||||
private HashSet<Type> _erroredGridOverlays = new();
|
||||
|
||||
private int _verticesPerChunk(MapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * 4;
|
||||
private int _indicesPerChunk(MapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * GetQuadBatchIndexCount();
|
||||
|
||||
private void _drawGrids(Viewport viewport, Box2Rotated worldBounds, IEye eye)
|
||||
private void _drawGrids(Viewport viewport, Box2 worldAABB, Box2Rotated worldBounds, IEye eye)
|
||||
{
|
||||
var mapId = eye.Position.MapId;
|
||||
if (!_mapManager.MapExists(mapId))
|
||||
@@ -30,27 +37,35 @@ namespace Robust.Client.Graphics.Clyde
|
||||
mapId = MapId.Nullspace;
|
||||
}
|
||||
|
||||
SetTexture(TextureUnit.Texture0, _tileDefinitionManager.TileTextureAtlas);
|
||||
SetTexture(TextureUnit.Texture1, _lightingReady ? viewport.LightRenderTarget.Texture : _stockTextureWhite);
|
||||
|
||||
var gridProgram = ActivateShaderInstance(_defaultShader.Handle).Item1;
|
||||
SetupGlobalUniformsImmediate(gridProgram, (ClydeTexture) _tileDefinitionManager.TileTextureAtlas);
|
||||
|
||||
gridProgram.SetUniformTextureMaybe(UniIMainTexture, TextureUnit.Texture0);
|
||||
gridProgram.SetUniformTextureMaybe(UniILightTexture, TextureUnit.Texture1);
|
||||
gridProgram.SetUniform(UniIModUV, new Vector4(0, 0, 1, 1));
|
||||
|
||||
var grids = new List<Entity<MapGridComponent>>();
|
||||
_mapManager.FindGridsIntersecting(mapId, worldBounds, ref grids);
|
||||
|
||||
var requiresFlush = true;
|
||||
GLShaderProgram gridProgram = default!;
|
||||
var gridOverlays = GetOverlaysForSpace(OverlaySpace.WorldSpaceGrids);
|
||||
|
||||
foreach (var mapGrid in grids)
|
||||
{
|
||||
if (!_mapChunkData.ContainsKey(mapGrid))
|
||||
if (!_mapChunkData.TryGetValue(mapGrid, out var data))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (requiresFlush)
|
||||
{
|
||||
SetTexture(TextureUnit.Texture0, _tileDefinitionManager.TileTextureAtlas);
|
||||
SetTexture(TextureUnit.Texture1, _lightingReady ? viewport.LightRenderTarget.Texture : _stockTextureWhite);
|
||||
gridProgram = ActivateShaderInstance(_defaultShader.Handle).Item1;
|
||||
SetupGlobalUniformsImmediate(gridProgram, (ClydeTexture) _tileDefinitionManager.TileTextureAtlas);
|
||||
|
||||
gridProgram.SetUniformTextureMaybe(UniIMainTexture, TextureUnit.Texture0);
|
||||
gridProgram.SetUniformTextureMaybe(UniILightTexture, TextureUnit.Texture1);
|
||||
gridProgram.SetUniform(UniIModUV, new Vector4(0, 0, 1, 1));
|
||||
}
|
||||
|
||||
var transform = _entityManager.GetComponent<TransformComponent>(mapGrid);
|
||||
gridProgram.SetUniform(UniIModelMatrix, transform.WorldMatrix);
|
||||
var enumerator = mapGrid.Comp.GetMapChunks(worldBounds);
|
||||
var data = _mapChunkData[mapGrid];
|
||||
|
||||
while (enumerator.MoveNext(out var chunk))
|
||||
{
|
||||
@@ -72,6 +87,31 @@ namespace Robust.Client.Graphics.Clyde
|
||||
GL.DrawElements(GetQuadGLPrimitiveType(), datum.TileCount * GetQuadBatchIndexCount(), DrawElementsType.UnsignedShort, 0);
|
||||
CheckGlError();
|
||||
}
|
||||
|
||||
requiresFlush = false;
|
||||
|
||||
foreach (var overlay in gridOverlays)
|
||||
{
|
||||
if (overlay is not IGridOverlay iGrid)
|
||||
{
|
||||
if (!_erroredGridOverlays.Add(overlay.GetType()))
|
||||
{
|
||||
_clydeSawmill.Error($"Tried to render grid overlay {overlay.GetType()} that doesn't implement {nameof(IGridOverlay)}");
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
iGrid.Grid = mapGrid;
|
||||
iGrid.RequiresFlush = false;
|
||||
RenderSingleWorldOverlay(overlay, viewport, OverlaySpace.WorldSpaceGrids, worldAABB, worldBounds);
|
||||
requiresFlush |= iGrid.RequiresFlush;
|
||||
}
|
||||
|
||||
if (requiresFlush)
|
||||
{
|
||||
FlushRenderQueue();
|
||||
}
|
||||
}
|
||||
|
||||
CullEmptyChunks();
|
||||
|
||||
@@ -497,7 +497,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
using (DebugGroup("Grids"))
|
||||
using (_prof.Group("Grids"))
|
||||
{
|
||||
_drawGrids(viewport, worldBounds, eye);
|
||||
_drawGrids(viewport, worldAABB, worldBounds, eye);
|
||||
}
|
||||
|
||||
// We will also render worldspace overlays here so we can do them under / above entities as necessary
|
||||
|
||||
@@ -38,6 +38,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_clyde.DrawSetModelTransform(matrix);
|
||||
}
|
||||
|
||||
public Matrix3 GetModelTransform()
|
||||
{
|
||||
return _clyde.DrawGetModelTransform();
|
||||
}
|
||||
|
||||
public void SetProjView(in Matrix3 proj, in Matrix3 view)
|
||||
{
|
||||
_clyde.DrawSetProjViewTransform(proj, view);
|
||||
@@ -222,7 +227,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
var clydeShader = (ClydeShaderInstance?) shader;
|
||||
|
||||
_clyde.DrawUseShader(clydeShader?.Handle ?? _clyde._defaultShader.Handle);
|
||||
_clyde.DrawUseShader(clydeShader ?? _clyde._defaultShader);
|
||||
}
|
||||
|
||||
public ShaderInstance? GetShader()
|
||||
{
|
||||
return _clyde._queuedShaderInstance == _clyde._defaultShader
|
||||
? null
|
||||
: _clyde._queuedShaderInstance;
|
||||
}
|
||||
|
||||
public void Viewport(Box2i viewport)
|
||||
@@ -285,11 +297,21 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_renderHandle.SetModelTransform(matrix);
|
||||
}
|
||||
|
||||
public override Matrix3 GetTransform()
|
||||
{
|
||||
return _renderHandle.GetModelTransform();
|
||||
}
|
||||
|
||||
public override void UseShader(ShaderInstance? shader)
|
||||
{
|
||||
_renderHandle.UseShader(shader);
|
||||
}
|
||||
|
||||
public override ShaderInstance? GetShader()
|
||||
{
|
||||
return _renderHandle.GetShader();
|
||||
}
|
||||
|
||||
public override void DrawPrimitives(DrawPrimitiveTopology primitiveTopology, Texture texture,
|
||||
ReadOnlySpan<DrawVertexUV2DColor> vertices)
|
||||
{
|
||||
@@ -380,11 +402,21 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_renderHandle.SetModelTransform(matrix);
|
||||
}
|
||||
|
||||
public override Matrix3 GetTransform()
|
||||
{
|
||||
return _renderHandle.GetModelTransform();
|
||||
}
|
||||
|
||||
public override void UseShader(ShaderInstance? shader)
|
||||
{
|
||||
_renderHandle.UseShader(shader);
|
||||
}
|
||||
|
||||
public override ShaderInstance? GetShader()
|
||||
{
|
||||
return _renderHandle.GetShader();
|
||||
}
|
||||
|
||||
public override void DrawCircle(Vector2 position, float radius, Color color, bool filled = true)
|
||||
{
|
||||
int divisions = Math.Max(16,(int)(radius * 16));
|
||||
|
||||
@@ -78,7 +78,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
// private LoadedTexture? _batchLoadedTexture;
|
||||
// Contains the shader instance that's currently being used by the (queue) stage for new commands.
|
||||
private ClydeHandle _queuedShader;
|
||||
private ClydeHandle _queuedShader => _queuedShaderInstance.Handle;
|
||||
|
||||
private ClydeShaderInstance _queuedShaderInstance = default!;
|
||||
|
||||
// Current projection & view matrices that are being used ot render.
|
||||
// This gets updated to keep track during (queue) and (misc), but not during (submit).
|
||||
@@ -314,7 +316,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
// Reset renderer state.
|
||||
_currentMatrixModel = Matrix3.Identity;
|
||||
_queuedShader = _defaultShader.Handle;
|
||||
_queuedShaderInstance = _defaultShader;
|
||||
SetScissorFull(null);
|
||||
}
|
||||
|
||||
@@ -443,9 +445,13 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_isStencilling = false;
|
||||
}
|
||||
|
||||
if (!instance.ParametersDirty)
|
||||
if (instance.Parameters.Count == 0)
|
||||
return (program, instance);
|
||||
|
||||
if (shader.LastInstance == instance && !instance.ParametersDirty)
|
||||
return (program, instance);
|
||||
|
||||
shader.LastInstance = instance;
|
||||
instance.ParametersDirty = false;
|
||||
|
||||
int textureUnitVal = 0;
|
||||
@@ -531,6 +537,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_currentMatrixModel = matrix;
|
||||
}
|
||||
|
||||
private Matrix3 DrawGetModelTransform()
|
||||
{
|
||||
return _currentMatrixModel;
|
||||
}
|
||||
|
||||
private void DrawSetProjViewTransform(in Matrix3 proj, in Matrix3 view)
|
||||
{
|
||||
BreakBatch();
|
||||
@@ -700,9 +711,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_currentScissorState = scissorBox;
|
||||
}
|
||||
|
||||
private void DrawUseShader(ClydeHandle handle)
|
||||
private void DrawUseShader(ClydeShaderInstance instance)
|
||||
{
|
||||
_queuedShader = handle;
|
||||
_queuedShaderInstance = instance;
|
||||
}
|
||||
|
||||
private void DrawClear(Color color, int stencil, ClearBufferMask mask)
|
||||
@@ -875,7 +886,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
SetScissorFull(null);
|
||||
BindRenderTargetFull(_mainWindow!.RenderTarget);
|
||||
_batchMetaData = null;
|
||||
_queuedShader = _defaultShader.Handle;
|
||||
_queuedShaderInstance = _defaultShader;
|
||||
|
||||
GL.Viewport(0, 0, _mainWindow!.FramebufferSize.X, _mainWindow!.FramebufferSize.Y);
|
||||
}
|
||||
|
||||
@@ -48,6 +48,10 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
[ViewVariables]
|
||||
public string? Name;
|
||||
|
||||
// Last instance that used this shader.
|
||||
// Used to ensure that shader uniforms get updated.
|
||||
public LoadedShaderInstance? LastInstance;
|
||||
}
|
||||
|
||||
private sealed class LoadedShaderInstance
|
||||
@@ -158,7 +162,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
_defaultShader = (ClydeShaderInstance) InstanceShader(defaultLoadedShader);
|
||||
|
||||
_queuedShader = _defaultShader.Handle;
|
||||
_queuedShaderInstance = _defaultShader;
|
||||
}
|
||||
|
||||
private string ReadEmbeddedShader(string fileName)
|
||||
|
||||
@@ -198,6 +198,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
IRenderTexture IClydeViewport.RenderTarget => RenderTarget;
|
||||
IRenderTexture IClydeViewport.LightRenderTarget => LightRenderTarget;
|
||||
public IEye? Eye { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +76,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private Thread? _gameThread;
|
||||
|
||||
private ISawmill _clydeSawmill = default!;
|
||||
private ISawmill _sawmillOgl = default!;
|
||||
|
||||
private IBindingsContext _glBindingsContext = default!;
|
||||
@@ -91,6 +92,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
public bool InitializePreWindowing()
|
||||
{
|
||||
_clydeSawmill = _logManager.GetSawmill("clyde");
|
||||
_sawmillOgl = _logManager.GetSawmill("clyde.ogl");
|
||||
|
||||
_cfg.OnValueChanged(CVars.DisplayOGLCheckErrors, b => _checkGLErrors = b, true);
|
||||
|
||||
@@ -463,6 +463,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public IRenderTexture RenderTarget { get; } =
|
||||
new DummyRenderTexture(Vector2i.One, new DummyTexture(Vector2i.One));
|
||||
|
||||
public IRenderTexture LightRenderTarget { get; } =
|
||||
new DummyRenderTexture(Vector2i.One, new DummyTexture(Vector2i.One));
|
||||
|
||||
public IEye? Eye { get; set; }
|
||||
public Vector2i Size { get; }
|
||||
public Color? ClearColor { get; set; } = Color.Black;
|
||||
|
||||
@@ -20,11 +20,5 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public ShaderCompilationException(string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
}
|
||||
|
||||
protected ShaderCompilationException(
|
||||
SerializationInfo info,
|
||||
StreamingContext context) : base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,5 +137,70 @@ highp vec4 zTexture(highp vec2 uv)
|
||||
return zTextureSpec(TEXTURE, uv);
|
||||
}
|
||||
|
||||
// -- color --
|
||||
|
||||
// Grayscale function for the ITU's Rec BT-709. Primarily intended for HDTVs, but standard sRGB monitors are coincidentally extremely close.
|
||||
highp float zGrayscale_BT709(highp vec3 col) {
|
||||
return dot(col, vec3(0.2126, 0.7152, 0.0722));
|
||||
}
|
||||
|
||||
// Grayscale function for the ITU's Rec BT-601, primarily intended for SDTV, but amazing for a handful of niche use-cases.
|
||||
highp float zGrayscale_BT601(highp vec3 col) {
|
||||
return dot(col, vec3(0.299, 0.587, 0.114));
|
||||
}
|
||||
|
||||
// If you don't have any reason to be specifically using the above grayscale functions, then you should default to this.
|
||||
highp float zGrayscale(highp vec3 col) {
|
||||
return zGrayscale_BT709(col);
|
||||
}
|
||||
|
||||
// -- noise --
|
||||
|
||||
//zRandom, zNoise, and zFBM are derived from https://godotshaders.com/snippet/2d-noise/ and https://godotshaders.com/snippet/fractal-brownian-motion-fbm/
|
||||
highp vec2 zRandom(highp vec2 uv){
|
||||
uv = vec2( dot(uv, vec2(127.1,311.7) ),
|
||||
dot(uv, vec2(269.5,183.3) ) );
|
||||
return -1.0 + 2.0 * fract(sin(uv) * 43758.5453123);
|
||||
}
|
||||
|
||||
highp float zNoise(highp vec2 uv) {
|
||||
highp vec2 uv_index = floor(uv);
|
||||
highp vec2 uv_fract = fract(uv);
|
||||
|
||||
highp vec2 blur = smoothstep(0.0, 1.0, uv_fract);
|
||||
|
||||
return mix( mix( dot( zRandom(uv_index + vec2(0.0,0.0) ), uv_fract - vec2(0.0,0.0) ),
|
||||
dot( zRandom(uv_index + vec2(1.0,0.0) ), uv_fract - vec2(1.0,0.0) ), blur.x),
|
||||
mix( dot( zRandom(uv_index + vec2(0.0,1.0) ), uv_fract - vec2(0.0,1.0) ),
|
||||
dot( zRandom(uv_index + vec2(1.0,1.0) ), uv_fract - vec2(1.0,1.0) ), blur.x), blur.y) * 0.5 + 0.5;
|
||||
}
|
||||
|
||||
highp float zFBM(highp vec2 uv) {
|
||||
const int octaves = 6;
|
||||
highp float amplitude = 0.5;
|
||||
highp float frequency = 3.0;
|
||||
highp float value = 0.0;
|
||||
|
||||
for(int i = 0; i < octaves; i++) {
|
||||
value += amplitude * zNoise(frequency * uv);
|
||||
amplitude *= 0.5;
|
||||
frequency *= 2.0;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
// -- generative --
|
||||
|
||||
// Function that creates a circular gradient. Screenspace shader bread n butter.
|
||||
highp float zCircleGradient(highp vec2 ps, highp vec2 coord, highp float maxi, highp float radius, highp float dist, highp float power) {
|
||||
highp float rad = (radius * ps.y) * 0.001;
|
||||
highp float aspectratio = ps.x / ps.y;
|
||||
highp vec2 totaldistance = ((ps * 0.5) - coord) / (rad * ps);
|
||||
totaldistance.x *= aspectratio;
|
||||
highp float length = (length(totaldistance) * ps.y) - dist;
|
||||
return pow(clamp(length, 0.0, maxi), power);
|
||||
}
|
||||
|
||||
// -- Utilities End --
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Shared;
|
||||
@@ -65,7 +66,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return MouseButtonMap[button];
|
||||
}
|
||||
|
||||
private static readonly Dictionary<GlfwButton, Button> MouseButtonMap = new()
|
||||
private static readonly FrozenDictionary<GlfwButton, Button> MouseButtonMap = new Dictionary<GlfwButton, Button>()
|
||||
{
|
||||
{GlfwButton.Left, Button.Left},
|
||||
{GlfwButton.Middle, Button.Middle},
|
||||
@@ -75,10 +76,10 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{GlfwButton.Button6, Button.Button6},
|
||||
{GlfwButton.Button7, Button.Button7},
|
||||
{GlfwButton.Button8, Button.Button8},
|
||||
};
|
||||
}.ToFrozenDictionary();
|
||||
|
||||
private static readonly Dictionary<GlfwKey, Key> KeyMap;
|
||||
private static readonly Dictionary<Key, GlfwKey> KeyMapReverse;
|
||||
private static readonly FrozenDictionary<GlfwKey, Key> KeyMap;
|
||||
private static readonly FrozenDictionary<Key, GlfwKey> KeyMapReverse;
|
||||
|
||||
|
||||
internal static Key ConvertGlfwKey(GlfwKey key)
|
||||
@@ -218,14 +219,16 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{GlfwKey.F24, Key.F24},
|
||||
{GlfwKey.Pause, Key.Pause},
|
||||
{GlfwKey.World1, Key.World1},
|
||||
};
|
||||
}.ToFrozenDictionary();
|
||||
|
||||
KeyMapReverse = new Dictionary<Key, GlfwKey>();
|
||||
var keyMapReverse = new Dictionary<Key, GlfwKey>();
|
||||
|
||||
foreach (var (key, value) in KeyMap)
|
||||
{
|
||||
KeyMapReverse[value] = key;
|
||||
keyMapReverse[value] = key;
|
||||
}
|
||||
|
||||
KeyMapReverse = keyMapReverse.ToFrozenDictionary();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,11 @@ using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using TerraFX.Interop.Windows;
|
||||
using TerraFX.Interop.Xlib;
|
||||
using GlfwImage = OpenToolkit.GraphicsLibraryFramework.Image;
|
||||
using Monitor = OpenToolkit.GraphicsLibraryFramework.Monitor;
|
||||
using Window = OpenToolkit.GraphicsLibraryFramework.Window;
|
||||
using X11Window = TerraFX.Interop.Xlib.Window;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
@@ -468,6 +471,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
GLFW.MaximizeWindow(window);
|
||||
}
|
||||
|
||||
if ((parameters.Styles & OSWindowStyles.NoTitleBar) != 0)
|
||||
{
|
||||
GLFW.WindowHint(WindowHintBool.Decorated, false);
|
||||
}
|
||||
|
||||
if ((parameters.Styles & OSWindowStyles.NoTitleOptions) != 0)
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
@@ -481,6 +489,35 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Cast to long here to work around a bug in rider with nint bitwise operators.
|
||||
(nint)((long)Windows.GetWindowLongPtrW(hWnd, GWL.GWL_STYLE) & ~WS.WS_SYSMENU));
|
||||
}
|
||||
else if (OperatingSystem.IsLinux())
|
||||
{
|
||||
try
|
||||
{
|
||||
var x11Window = (X11Window)GLFW.GetX11Window(window);
|
||||
var x11Display = (Display*) GLFW.GetX11Display(window);
|
||||
DebugTools.Assert(x11Window != X11Window.NULL);
|
||||
// https://specifications.freedesktop.org/wm-spec/wm-spec-latest.html#idm46181547486832
|
||||
var newPropValString = Marshal.StringToCoTaskMemUTF8("_NET_WM_WINDOW_TYPE_DIALOG");
|
||||
var newPropVal = Xlib.XInternAtom(x11Display, (sbyte*)newPropValString, Xlib.False);
|
||||
DebugTools.Assert(newPropVal != Atom.NULL);
|
||||
|
||||
var propNameString = Marshal.StringToCoTaskMemUTF8("_NET_WM_WINDOW_TYPE");
|
||||
#pragma warning disable CA1806
|
||||
// [display] [window] [property] [type] [format (8, 16,32)] [mode] [data] [element count]
|
||||
Xlib.XChangeProperty(x11Display, x11Window,
|
||||
Xlib.XInternAtom(x11Display, (sbyte*)propNameString, Xlib.False), // should never be null; part of spec
|
||||
Xlib.XA_ATOM, 32, Xlib.PropModeReplace,
|
||||
(byte*)&newPropVal, 1);
|
||||
#pragma warning restore CA1806
|
||||
|
||||
Marshal.FreeCoTaskMem(newPropValString);
|
||||
Marshal.FreeCoTaskMem(propNameString);
|
||||
}
|
||||
catch (EntryPointNotFoundException)
|
||||
{
|
||||
_sawmill.Warning("OSWindowStyles.NoTitleOptions not implemented on this windowing manager");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_sawmill.Warning("OSWindowStyles.NoTitleOptions not implemented on this platform");
|
||||
@@ -500,6 +537,25 @@ namespace Robust.Client.Graphics.Clyde
|
||||
GWLP.GWLP_HWNDPARENT,
|
||||
ownerHWnd);
|
||||
}
|
||||
else if (OperatingSystem.IsLinux())
|
||||
{
|
||||
try
|
||||
{
|
||||
var x11Display = (Display*) GLFW.GetX11Display(window);
|
||||
var thisWindow = (X11Window)GLFW.GetX11Window(window);
|
||||
var parentWindow = (X11Window)GLFW.GetX11Window(ownerWindow);
|
||||
DebugTools.Assert(thisWindow != X11Window.NULL);
|
||||
DebugTools.Assert(parentWindow != X11Window.NULL);
|
||||
|
||||
#pragma warning disable CA1806
|
||||
Xlib.XSetTransientForHint(x11Display, thisWindow, parentWindow);
|
||||
#pragma warning restore CA1806
|
||||
}
|
||||
catch (EntryPointNotFoundException)
|
||||
{
|
||||
_sawmill.Warning("owner windows not implemented on this windowing manager");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_sawmill.Warning("owner windows not implemented on this platform");
|
||||
|
||||
@@ -125,12 +125,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public GlfwException(string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
}
|
||||
|
||||
protected GlfwException(
|
||||
SerializationInfo info,
|
||||
StreamingContext context) : base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using Robust.Client.Input;
|
||||
@@ -16,7 +17,7 @@ internal partial class Clyde
|
||||
{
|
||||
// Indices are values of SDL_Scancode
|
||||
private static readonly Key[] KeyMap;
|
||||
private static readonly Dictionary<Key, SDL_Scancode> KeyMapReverse;
|
||||
private static readonly FrozenDictionary<Key, SDL_Scancode> KeyMapReverse;
|
||||
private static readonly Button[] MouseButtonMap;
|
||||
|
||||
// TODO: to avoid having to ask the windowing thread, key names are cached.
|
||||
@@ -202,15 +203,17 @@ internal partial class Clyde
|
||||
MapKey(SDL_SCANCODE_F24, Key.F24);
|
||||
MapKey(SDL_SCANCODE_PAUSE, Key.Pause);
|
||||
|
||||
KeyMapReverse = new Dictionary<Key, SDL_Scancode>();
|
||||
var keyMapReverse = new Dictionary<Key, SDL_Scancode>();
|
||||
|
||||
for (var code = 0; code < KeyMap.Length; code++)
|
||||
{
|
||||
var key = KeyMap[code];
|
||||
if (key != Key.Unknown)
|
||||
KeyMapReverse[key] = (SDL_Scancode) code;
|
||||
keyMapReverse[key] = (SDL_Scancode) code;
|
||||
}
|
||||
|
||||
KeyMapReverse = keyMapReverse.ToFrozenDictionary();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
static void MapKey(SDL_Scancode code, Key key)
|
||||
{
|
||||
|
||||
@@ -55,8 +55,12 @@ namespace Robust.Client.Graphics
|
||||
|
||||
public abstract void SetTransform(in Matrix3 matrix);
|
||||
|
||||
public abstract Matrix3 GetTransform();
|
||||
|
||||
public abstract void UseShader(ShaderInstance? shader);
|
||||
|
||||
public abstract ShaderInstance? GetShader();
|
||||
|
||||
// ---- DrawPrimitives: Vector2 API ----
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -116,7 +116,6 @@ namespace Robust.Client.Graphics
|
||||
{
|
||||
baseLine.Y += lineHeight;
|
||||
advanceTotal.Y += lineHeight;
|
||||
advanceTotal.X = Math.Max(advanceTotal.X, baseLine.X);
|
||||
baseLine.X = 0f;
|
||||
continue;
|
||||
}
|
||||
@@ -128,6 +127,7 @@ namespace Robust.Client.Graphics
|
||||
|
||||
var advance = metrics.Value.Advance;
|
||||
baseLine += new Vector2(advance, 0);
|
||||
advanceTotal.X = MathF.Max(baseLine.X, advanceTotal.X);
|
||||
}
|
||||
|
||||
return advanceTotal;
|
||||
|
||||
@@ -15,7 +15,8 @@ namespace Robust.Client.Graphics
|
||||
/// which can be either stretched or tiled to fill up
|
||||
/// the space the box is being drawn in.
|
||||
/// </summary>
|
||||
public sealed class StyleBoxTexture : StyleBox
|
||||
[Virtual]
|
||||
public class StyleBoxTexture : StyleBox
|
||||
{
|
||||
public StyleBoxTexture()
|
||||
{
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace Robust.Client.Graphics
|
||||
/// The render target that is rendered to when rendering this viewport.
|
||||
/// </summary>
|
||||
IRenderTexture RenderTarget { get; }
|
||||
IRenderTexture LightRenderTarget { get; }
|
||||
IEye? Eye { get; set; }
|
||||
Vector2i Size { get; }
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace Robust.Client.Graphics
|
||||
Vector2 scale,
|
||||
Angle? worldRot,
|
||||
Angle eyeRotation = default,
|
||||
Shared.Maths.Direction? overrideDirection = null,
|
||||
Direction? overrideDirection = null,
|
||||
SpriteComponent? sprite = null,
|
||||
TransformComponent? xform = null,
|
||||
SharedTransformSystem? xformSystem = null);
|
||||
|
||||
14
Robust.Client/Graphics/Overlays/GridOverlay.cs
Normal file
14
Robust.Client/Graphics/Overlays/GridOverlay.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Robust.Client.Graphics;
|
||||
|
||||
public abstract class GridOverlay : Overlay, IGridOverlay
|
||||
{
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceGrids;
|
||||
|
||||
public Entity<MapGridComponent> Grid { get; set; }
|
||||
|
||||
public bool RequiresFlush { get; set; }
|
||||
}
|
||||
18
Robust.Client/Graphics/Overlays/IGridOverlay.cs
Normal file
18
Robust.Client/Graphics/Overlays/IGridOverlay.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Robust.Client.Graphics;
|
||||
|
||||
/// <summary>
|
||||
/// Marks this overlay as implementing per-grid rendering.
|
||||
/// </summary>
|
||||
public interface IGridOverlay
|
||||
{
|
||||
Entity<MapGridComponent> Grid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Should we flush the render or can we keep going.
|
||||
/// </summary>
|
||||
public bool RequiresFlush { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -227,7 +228,7 @@ namespace Robust.Client.Graphics
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "StringLiteralTypo")]
|
||||
private static readonly Dictionary<ShaderDataType, string> _nativeTypes = new()
|
||||
private static readonly FrozenDictionary<ShaderDataType, string> _nativeTypes = new Dictionary<ShaderDataType, string>()
|
||||
{
|
||||
{ShaderDataType.Void, "void"},
|
||||
{ShaderDataType.Bool, "bool"},
|
||||
@@ -252,7 +253,7 @@ namespace Robust.Client.Graphics
|
||||
{ShaderDataType.Sampler2D, "sampler2D"},
|
||||
{ShaderDataType.ISampler2D, "isampler2D"},
|
||||
{ShaderDataType.USampler2D, "usampler2D"},
|
||||
};
|
||||
}.ToFrozenDictionary();
|
||||
}
|
||||
|
||||
internal enum ShaderLightMode : byte
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
@@ -637,7 +638,7 @@ namespace Robust.Client.Graphics
|
||||
Colon,
|
||||
}
|
||||
|
||||
private static readonly Dictionary<Symbols, string> _symbolStringMap = new()
|
||||
private static readonly FrozenDictionary<Symbols, string> _symbolStringMap = new Dictionary<Symbols, string>()
|
||||
{
|
||||
{Symbols.Semicolon, ";\n"},
|
||||
{Symbols.Comma, ","},
|
||||
@@ -679,6 +680,6 @@ namespace Robust.Client.Graphics
|
||||
{Symbols.GreaterOrEq, ">="},
|
||||
{Symbols.QuestionMark, "?"},
|
||||
{Symbols.Colon, ":"},
|
||||
};
|
||||
}.ToFrozenDictionary();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
@@ -536,17 +537,17 @@ namespace Robust.Client.Graphics
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "StringLiteralTypo")]
|
||||
private static readonly Dictionary<string, ShaderPrecisionQualifier> _shaderTypePrecisionMap =
|
||||
new()
|
||||
private static readonly FrozenDictionary<string, ShaderPrecisionQualifier> _shaderTypePrecisionMap =
|
||||
new Dictionary<string, ShaderPrecisionQualifier>()
|
||||
{
|
||||
{"lowp", ShaderPrecisionQualifier.Low},
|
||||
{"mediump", ShaderPrecisionQualifier.Medium},
|
||||
{"highp", ShaderPrecisionQualifier.High}
|
||||
};
|
||||
}.ToFrozenDictionary();
|
||||
|
||||
[SuppressMessage("ReSharper", "StringLiteralTypo")]
|
||||
private static readonly Dictionary<string, ShaderDataType> _shaderTypeMap =
|
||||
new()
|
||||
private static readonly FrozenDictionary<string, ShaderDataType> _shaderTypeMap =
|
||||
new Dictionary<string, ShaderDataType>()
|
||||
{
|
||||
{"void", ShaderDataType.Void},
|
||||
{"bool", ShaderDataType.Bool},
|
||||
@@ -571,7 +572,7 @@ namespace Robust.Client.Graphics
|
||||
{"sampler2D", ShaderDataType.Sampler2D},
|
||||
{"isampler2D", ShaderDataType.ISampler2D},
|
||||
{"usampler2D", ShaderDataType.USampler2D},
|
||||
};
|
||||
}.ToFrozenDictionary();
|
||||
|
||||
[PublicAPI]
|
||||
internal readonly struct TextFileRange
|
||||
|
||||
@@ -59,6 +59,11 @@ namespace Robust.Client.Graphics
|
||||
/// <summary>
|
||||
/// Hide title buttons such as close and minimize.
|
||||
/// </summary>
|
||||
NoTitleOptions = 1 << 0
|
||||
NoTitleOptions = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// Completely hide the title bar
|
||||
/// </summary>
|
||||
NoTitleBar = 1 << 1,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
@@ -28,7 +29,7 @@ namespace Robust.Client.Input
|
||||
return _mouseKeyMap[button];
|
||||
}
|
||||
|
||||
private static readonly Dictionary<Button, Keyboard.Key> _mouseKeyMap = new()
|
||||
private static readonly FrozenDictionary<Button, Keyboard.Key> _mouseKeyMap = new Dictionary<Button, Keyboard.Key>()
|
||||
{
|
||||
{Button.Left, Keyboard.Key.MouseLeft},
|
||||
{Button.Middle, Keyboard.Key.MouseMiddle},
|
||||
@@ -40,7 +41,7 @@ namespace Robust.Client.Input
|
||||
{Button.Button8, Keyboard.Key.MouseButton8},
|
||||
{Button.Button9, Keyboard.Key.MouseButton9},
|
||||
{Button.LastButton, Keyboard.Key.Unknown},
|
||||
};
|
||||
}.ToFrozenDictionary();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Map;
|
||||
@@ -14,21 +11,15 @@ namespace Robust.Client.Map;
|
||||
/// <summary>
|
||||
/// Draws border sprites for tiles that support them.
|
||||
/// </summary>
|
||||
public sealed class TileEdgeOverlay : Overlay
|
||||
public sealed class TileEdgeOverlay : GridOverlay
|
||||
{
|
||||
private readonly IEntityManager _entManager;
|
||||
private readonly IMapManager _mapManager;
|
||||
private readonly IResourceCache _resource;
|
||||
private readonly ITileDefinitionManager _tileDefManager;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowEntities;
|
||||
|
||||
private List<Entity<MapGridComponent>> _grids = new();
|
||||
|
||||
public TileEdgeOverlay(IEntityManager entManager, IMapManager mapManager, IResourceCache resource, ITileDefinitionManager tileDefManager)
|
||||
public TileEdgeOverlay(IEntityManager entManager, IResourceCache resource, ITileDefinitionManager tileDefManager)
|
||||
{
|
||||
_entManager = entManager;
|
||||
_mapManager = mapManager;
|
||||
_resource = resource;
|
||||
_tileDefManager = tileDefManager;
|
||||
ZIndex = -1;
|
||||
@@ -39,94 +30,90 @@ public sealed class TileEdgeOverlay : Overlay
|
||||
if (args.MapId == MapId.Nullspace)
|
||||
return;
|
||||
|
||||
_grids.Clear();
|
||||
_mapManager.FindGridsIntersecting(args.MapId, args.WorldBounds, ref _grids);
|
||||
|
||||
var mapSystem = _entManager.System<SharedMapSystem>();
|
||||
var xformSystem = _entManager.System<SharedTransformSystem>();
|
||||
|
||||
foreach (var grid in _grids)
|
||||
var tileSize = Grid.Comp.TileSize;
|
||||
var tileDimensions = new Vector2(tileSize, tileSize);
|
||||
var (_, _, worldMatrix, invMatrix) = xformSystem.GetWorldPositionRotationMatrixWithInv(Grid.Owner);
|
||||
args.WorldHandle.SetTransform(worldMatrix);
|
||||
var localAABB = invMatrix.TransformBox(args.WorldBounds);
|
||||
|
||||
var enumerator = mapSystem.GetLocalTilesEnumerator(Grid.Owner, Grid, localAABB, false);
|
||||
|
||||
while (enumerator.MoveNext(out var tileRef))
|
||||
{
|
||||
var tileSize = grid.Comp.TileSize;
|
||||
var tileDimensions = new Vector2(tileSize, tileSize);
|
||||
var (_, _, worldMatrix, invMatrix) = xformSystem.GetWorldPositionRotationMatrixWithInv(grid.Owner);
|
||||
args.WorldHandle.SetTransform(worldMatrix);
|
||||
var localAABB = invMatrix.TransformBox(args.WorldBounds);
|
||||
var tileDef = _tileDefManager[tileRef.Tile.TypeId];
|
||||
|
||||
var enumerator = mapSystem.GetLocalTilesEnumerator(grid.Owner, grid.Comp, localAABB, false);
|
||||
if (tileDef.EdgeSprites.Count == 0)
|
||||
continue;
|
||||
|
||||
while (enumerator.MoveNext(out var tileRef))
|
||||
// Get what tiles border us to determine what sprites we need to draw.
|
||||
for (var x = -1; x <= 1; x++)
|
||||
{
|
||||
var tileDef = _tileDefManager[tileRef.Tile.TypeId];
|
||||
|
||||
if (tileDef.EdgeSprites.Count == 0)
|
||||
continue;
|
||||
|
||||
// Get what tiles border us to determine what sprites we need to draw.
|
||||
for (var x = -1; x <= 1; x++)
|
||||
for (var y = -1; y <= 1; y++)
|
||||
{
|
||||
for (var y = -1; y <= 1; y++)
|
||||
if (x == 0 && y == 0)
|
||||
continue;
|
||||
|
||||
var neighborIndices = new Vector2i(tileRef.GridIndices.X + x, tileRef.GridIndices.Y + y);
|
||||
var neighborTile = mapSystem.GetTileRef(Grid.Owner, Grid, neighborIndices);
|
||||
var neighborDef = _tileDefManager[neighborTile.Tile.TypeId];
|
||||
|
||||
// If it's the same tile then no edge to be drawn.
|
||||
if (tileRef.Tile.TypeId == neighborTile.Tile.TypeId)
|
||||
continue;
|
||||
|
||||
// Don't draw if the the neighbor tile edges should draw over us (or if we have the same priority)
|
||||
if (neighborDef.EdgeSprites.Count != 0 && neighborDef.EdgeSpritePriority >= tileDef.EdgeSpritePriority)
|
||||
continue;
|
||||
|
||||
var direction = new Vector2i(x, y).AsDirection();
|
||||
|
||||
// No edge tile
|
||||
if (!tileDef.EdgeSprites.TryGetValue(direction, out var edgePath))
|
||||
continue;
|
||||
|
||||
var texture = _resource.GetResource<TextureResource>(edgePath);
|
||||
var box = Box2.FromDimensions(neighborIndices, tileDimensions);
|
||||
|
||||
var angle = Angle.Zero;
|
||||
|
||||
// If we ever need one for both cardinals and corners then update this.
|
||||
switch (direction)
|
||||
{
|
||||
if (x == 0 && y == 0)
|
||||
continue;
|
||||
|
||||
var neighborIndices = new Vector2i(tileRef.GridIndices.X + x, tileRef.GridIndices.Y + y);
|
||||
var neighborTile = mapSystem.GetTileRef(grid.Owner, grid.Comp, neighborIndices);
|
||||
var neighborDef = _tileDefManager[neighborTile.Tile.TypeId];
|
||||
|
||||
// If it's the same tile then no edge to be drawn.
|
||||
if (tileRef.Tile.TypeId == neighborTile.Tile.TypeId)
|
||||
continue;
|
||||
|
||||
// Don't draw if the the neighbor tile edges should draw over us (or if we have the same priority)
|
||||
if (neighborDef.EdgeSprites.Count != 0 && neighborDef.EdgeSpritePriority >= tileDef.EdgeSpritePriority)
|
||||
continue;
|
||||
|
||||
var direction = new Vector2i(x, y).AsDirection();
|
||||
|
||||
// No edge tile
|
||||
if (!tileDef.EdgeSprites.TryGetValue(direction, out var edgePath))
|
||||
continue;
|
||||
|
||||
var texture = _resource.GetResource<TextureResource>(edgePath);
|
||||
var box = Box2.FromDimensions(neighborIndices, tileDimensions);
|
||||
|
||||
var angle = Angle.Zero;
|
||||
|
||||
// If we ever need one for both cardinals and corners then update this.
|
||||
switch (direction)
|
||||
{
|
||||
// Corner sprites
|
||||
case Direction.SouthEast:
|
||||
break;
|
||||
case Direction.NorthEast:
|
||||
angle = new Angle(MathF.PI / 2f);
|
||||
break;
|
||||
case Direction.NorthWest:
|
||||
angle = new Angle(MathF.PI);
|
||||
break;
|
||||
case Direction.SouthWest:
|
||||
angle = new Angle(MathF.PI * 1.5f);
|
||||
break;
|
||||
// Edge sprites
|
||||
case Direction.South:
|
||||
break;
|
||||
case Direction.East:
|
||||
angle = new Angle(MathF.PI / 2f);
|
||||
break;
|
||||
case Direction.North:
|
||||
angle = new Angle(MathF.PI);
|
||||
break;
|
||||
case Direction.West:
|
||||
angle = new Angle(MathF.PI * 1.5f);
|
||||
break;
|
||||
}
|
||||
|
||||
if (angle == Angle.Zero)
|
||||
args.WorldHandle.DrawTextureRect(texture.Texture, box);
|
||||
else
|
||||
args.WorldHandle.DrawTextureRect(texture.Texture, new Box2Rotated(box, angle, box.Center));
|
||||
// Corner sprites
|
||||
case Direction.SouthEast:
|
||||
break;
|
||||
case Direction.NorthEast:
|
||||
angle = new Angle(MathF.PI / 2f);
|
||||
break;
|
||||
case Direction.NorthWest:
|
||||
angle = new Angle(MathF.PI);
|
||||
break;
|
||||
case Direction.SouthWest:
|
||||
angle = new Angle(MathF.PI * 1.5f);
|
||||
break;
|
||||
// Edge sprites
|
||||
case Direction.South:
|
||||
break;
|
||||
case Direction.East:
|
||||
angle = new Angle(MathF.PI / 2f);
|
||||
break;
|
||||
case Direction.North:
|
||||
angle = new Angle(MathF.PI);
|
||||
break;
|
||||
case Direction.West:
|
||||
angle = new Angle(MathF.PI * 1.5f);
|
||||
break;
|
||||
}
|
||||
|
||||
if (angle == Angle.Zero)
|
||||
args.WorldHandle.DrawTextureRect(texture.Texture, box);
|
||||
else
|
||||
args.WorldHandle.DrawTextureRect(texture.Texture, new Box2Rotated(box, angle, box.Center));
|
||||
|
||||
RequiresFlush = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,8 +99,8 @@ public sealed partial class PhysicsSystem
|
||||
if (xform.MapUid is not { } map)
|
||||
continue;
|
||||
|
||||
if (maps.Add(map) && TryComp(map, out PhysicsMapComponent? physMap) &&
|
||||
TryComp(map, out MapComponent? mapComp))
|
||||
if (maps.Add(map) && PhysMapQuery.TryGetComponent(map, out var physMap) &&
|
||||
MapQuery.TryGetComponent(map, out var mapComp))
|
||||
_broadphase.FindNewContacts(physMap, mapComp.MapId);
|
||||
|
||||
contacts.AddRange(physics.Contacts);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -24,6 +25,8 @@ public interface IResourceCache : IResourceManager
|
||||
bool TryGetResource<T>(ResPath path, [NotNullWhen(true)] out T? resource)
|
||||
where T : BaseResource, new();
|
||||
|
||||
bool TryGetResource(AudioStream stream, [NotNullWhen(true)] out AudioResource? resource);
|
||||
|
||||
void ReloadResource<T>(string path)
|
||||
where T : BaseResource, new();
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
@@ -45,13 +47,13 @@ internal sealed partial class ResourceCache : ResourceManager, IResourceCacheInt
|
||||
{
|
||||
if (useFallback && resource.Fallback != null)
|
||||
{
|
||||
Logger.Error(
|
||||
Sawmill.Error(
|
||||
$"Exception while loading resource {typeof(T)} at '{path}', resorting to fallback.\n{Environment.StackTrace}\n{e}");
|
||||
return GetResource<T>(resource.Fallback.Value, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Error(
|
||||
Sawmill.Error(
|
||||
$"Exception while loading resource {typeof(T)} at '{path}', no fallback available\n{Environment.StackTrace}\n{e}");
|
||||
throw;
|
||||
}
|
||||
@@ -81,11 +83,23 @@ internal sealed partial class ResourceCache : ResourceManager, IResourceCacheInt
|
||||
cache[path] = resource;
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
resource = null;
|
||||
return false;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Sawmill.Error($"Exception while loading resource {typeof(T)} at '{path}'\n{e}");
|
||||
resource = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetResource(AudioStream stream, [NotNullWhen(true)] out AudioResource? resource)
|
||||
{
|
||||
resource = new AudioResource(stream);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ReloadResource<T>(string path) where T : BaseResource, new()
|
||||
@@ -109,7 +123,7 @@ internal sealed partial class ResourceCache : ResourceManager, IResourceCacheInt
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error($"Exception while reloading resource {typeof(T)} at '{path}'\n{e}");
|
||||
Sawmill.Error($"Exception while reloading resource {typeof(T)} at '{path}'\n{e}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,11 @@ public sealed class AudioResource : BaseResource
|
||||
{
|
||||
public AudioStream AudioStream { get; private set; } = default!;
|
||||
|
||||
public void Load(AudioStream stream)
|
||||
{
|
||||
AudioStream = stream;
|
||||
}
|
||||
|
||||
public override void Load(IDependencyCollection dependencies, ResPath path)
|
||||
{
|
||||
var cache = dependencies.Resolve<IResourceManager>();
|
||||
@@ -39,6 +44,13 @@ public sealed class AudioResource : BaseResource
|
||||
}
|
||||
}
|
||||
|
||||
public AudioResource(AudioStream stream) : base()
|
||||
{
|
||||
AudioStream = stream;
|
||||
}
|
||||
|
||||
public AudioResource() : base(){}
|
||||
|
||||
public static implicit operator AudioStream(AudioResource res)
|
||||
{
|
||||
return res.AudioStream;
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.20348-rc2" PrivateAssets="compile" />
|
||||
<PackageReference Condition="'$(FullRelease)' != 'True'" Include="JetBrains.Profiler.Api" Version="1.2.0" PrivateAssets="compile" />
|
||||
<PackageReference Include="SpaceWizards.Sodium" Version="0.2.1" PrivateAssets="compile" />
|
||||
<PackageReference Include="Microsoft.NET.ILLink.Tasks" Version="8.0.0" />
|
||||
<PackageReference Include="TerraFX.Interop.Xlib" Version="6.4.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(EnableClientScripting)' == 'True'">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Features" Version="4.0.1" PrivateAssets="compile" />
|
||||
@@ -53,6 +55,7 @@
|
||||
<RobustLinkRoots Include="Robust.Client" />
|
||||
<RobustLinkRoots Include="Robust.Shared" />
|
||||
<RobustLinkAssemblies Include="TerraFX.Interop.Windows" />
|
||||
<RobustLinkAssemblies Include="TerraFX.Interop.Xlib" />
|
||||
<RobustLinkAssemblies Include="OpenToolkit.Graphics" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -24,7 +24,10 @@ namespace Robust.Client.UserInterface
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables]
|
||||
public ICollection<string> StyleClasses { get; }
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public IReadOnlyCollection<string> StylePseudoClass => _stylePseudoClass;
|
||||
|
||||
[ViewVariables]
|
||||
|
||||
@@ -820,8 +820,18 @@ namespace Robust.Client.UserInterface
|
||||
return;
|
||||
}
|
||||
|
||||
// If it was at the top index and we re-add it there then don't throw.
|
||||
Parent._orderedChildren.RemoveAt(posInParent);
|
||||
Parent._orderedChildren.Insert(position, this);
|
||||
|
||||
if (position == Parent._orderedChildren.Count)
|
||||
{
|
||||
Parent._orderedChildren.Add(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
Parent._orderedChildren.Insert(position, this);
|
||||
}
|
||||
|
||||
Parent.ChildMoved(this, posInParent, position);
|
||||
}
|
||||
|
||||
|
||||
@@ -213,7 +213,12 @@ public sealed class EntitySpawningUIController : UIController
|
||||
_shownEntities.Add(prototype);
|
||||
}
|
||||
|
||||
_shownEntities.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal));
|
||||
_shownEntities.Sort((a, b) => {
|
||||
var namesComparation = string.Compare(a.Name, b.Name, StringComparison.Ordinal);
|
||||
if (namesComparation == 0)
|
||||
return string.Compare(a.EditorSuffix, b.EditorSuffix, StringComparison.Ordinal);
|
||||
return namesComparation;
|
||||
});
|
||||
|
||||
_window.PrototypeList.TotalItemCount = _shownEntities.Count;
|
||||
_window.PrototypeScrollContainer.SetScrollValue(new Vector2(0, 0));
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Client.UserInterface.Controls
|
||||
@@ -106,9 +111,24 @@ namespace Robust.Client.UserInterface.Controls
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the button's press state and also handles click sounds.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public void SetClickPressed(bool value)
|
||||
{
|
||||
Pressed = value;
|
||||
|
||||
if (Pressed != value)
|
||||
return;
|
||||
|
||||
UserInterfaceManager.ClickSound();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether key functions other than <see cref="EngineKeyFunctions.UIClick"/> trigger the button.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public bool EnableAllKeybinds
|
||||
{
|
||||
get => _enableAllKeybinds;
|
||||
@@ -220,7 +240,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
// Can't un press a radio button directly.
|
||||
if (Group == null || !Pressed)
|
||||
{
|
||||
Pressed = !Pressed;
|
||||
SetClickPressed(!Pressed);
|
||||
OnPressed?.Invoke(buttonEventArgs);
|
||||
OnToggled?.Invoke(new ButtonToggledEventArgs(Pressed, this, args));
|
||||
UnsetOtherGroupButtons();
|
||||
@@ -261,7 +281,11 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
if (args.Function == EngineKeyFunctions.UIClick && ToggleMode && _attemptingPress == 1)
|
||||
{
|
||||
Pressed = !Pressed;
|
||||
SetClickPressed(!Pressed);
|
||||
}
|
||||
else
|
||||
{
|
||||
UserInterfaceManager.ClickSound();
|
||||
}
|
||||
|
||||
OnPressed?.Invoke(buttonEventArgs);
|
||||
@@ -316,6 +340,11 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
base.MouseEntered();
|
||||
|
||||
if (!Disabled)
|
||||
{
|
||||
UserInterfaceManager.HoverSound();
|
||||
}
|
||||
|
||||
var drawMode = DrawMode;
|
||||
_beingHovered = true;
|
||||
if (drawMode != DrawMode)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Log;
|
||||
|
||||
namespace Robust.Client.UserInterface.Controls;
|
||||
|
||||
@@ -15,6 +17,15 @@ public sealed class ColorSelectorSliders : Control
|
||||
set
|
||||
{
|
||||
_currentColor = value;
|
||||
switch (SelectorType)
|
||||
{
|
||||
case ColorSelectorType.Rgb:
|
||||
_colorData = new Vector4(_currentColor.R, _currentColor.G, _currentColor.B, _currentColor.A);
|
||||
break;
|
||||
case ColorSelectorType.Hsv:
|
||||
_colorData = Color.ToHsv(value);
|
||||
break;
|
||||
}
|
||||
Update();
|
||||
}
|
||||
}
|
||||
@@ -24,6 +35,15 @@ public sealed class ColorSelectorSliders : Control
|
||||
get => _currentType;
|
||||
set
|
||||
{
|
||||
switch ((_currentType, value))
|
||||
{
|
||||
case (ColorSelectorType.Rgb, ColorSelectorType.Hsv):
|
||||
_colorData = Color.ToHsv(Color);
|
||||
break;
|
||||
case (ColorSelectorType.Hsv, ColorSelectorType.Rgb):
|
||||
_colorData = new Vector4(_currentColor.R, _currentColor.G, _currentColor.B, _currentColor.A);
|
||||
break;
|
||||
}
|
||||
_currentType = value;
|
||||
UpdateType();
|
||||
Update();
|
||||
@@ -45,6 +65,7 @@ public sealed class ColorSelectorSliders : Control
|
||||
|
||||
private bool _updating = false;
|
||||
private Color _currentColor = Color.White;
|
||||
private Vector4 _colorData;
|
||||
private ColorSelectorType _currentType = ColorSelectorType.Rgb;
|
||||
private bool _isAlphaVisible = false;
|
||||
|
||||
@@ -68,12 +89,19 @@ public sealed class ColorSelectorSliders : Control
|
||||
private OptionButton _typeSelector;
|
||||
private List<ColorSelectorType> _types = new();
|
||||
|
||||
private static ShaderInstance _shader = default!;
|
||||
|
||||
private ColorSelectorStyleBox _topStyle;
|
||||
private ColorSelectorStyleBox _middleStyle;
|
||||
private ColorSelectorStyleBox _bottomStyle;
|
||||
|
||||
public ColorSelectorSliders()
|
||||
{
|
||||
_topColorSlider = new ColorableSlider
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
BackgroundStyleBoxOverride = _topStyle = new(),
|
||||
MaxValue = 1.0f
|
||||
};
|
||||
|
||||
@@ -81,6 +109,7 @@ public sealed class ColorSelectorSliders : Control
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
BackgroundStyleBoxOverride = _middleStyle = new(),
|
||||
MaxValue = 1.0f
|
||||
};
|
||||
|
||||
@@ -88,6 +117,7 @@ public sealed class ColorSelectorSliders : Control
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
BackgroundStyleBoxOverride = _bottomStyle = new(),
|
||||
MaxValue = 1.0f
|
||||
};
|
||||
|
||||
@@ -212,7 +242,7 @@ public sealed class ColorSelectorSliders : Control
|
||||
rootBox.AddChild(bodyBox);
|
||||
|
||||
UpdateType();
|
||||
Update();
|
||||
Color = _currentColor;
|
||||
}
|
||||
|
||||
private void UpdateType()
|
||||
@@ -222,6 +252,11 @@ public sealed class ColorSelectorSliders : Control
|
||||
_topSliderLabel.Text = labels.topLabel;
|
||||
_middleSliderLabel.Text = labels.middleLabel;
|
||||
_bottomSliderLabel.Text = labels.bottomLabel;
|
||||
|
||||
bool hsv = SelectorType == ColorSelectorType.Hsv;
|
||||
_topStyle.ConfigureSlider( hsv ? ColorSelectorStyleBox.ColorSliderPreset.Hue : ColorSelectorStyleBox.ColorSliderPreset.Red);
|
||||
_middleStyle.ConfigureSlider( hsv ? ColorSelectorStyleBox.ColorSliderPreset.Saturation : ColorSelectorStyleBox.ColorSliderPreset.Green);
|
||||
_bottomStyle.ConfigureSlider( hsv ? ColorSelectorStyleBox.ColorSliderPreset.Value : ColorSelectorStyleBox.ColorSliderPreset.Blue);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
@@ -232,43 +267,41 @@ public sealed class ColorSelectorSliders : Control
|
||||
return;
|
||||
|
||||
_updating = true;
|
||||
_topColorSlider.SetColor(_currentColor);
|
||||
_middleColorSlider.SetColor(_currentColor);
|
||||
_bottomColorSlider.SetColor(_currentColor);
|
||||
_topStyle.SetBaseColor(_colorData);
|
||||
_middleStyle.SetBaseColor(_colorData);
|
||||
_bottomStyle.SetBaseColor(_colorData);
|
||||
|
||||
switch (SelectorType)
|
||||
{
|
||||
case ColorSelectorType.Rgb:
|
||||
_topColorSlider.Value = Color.R;
|
||||
_middleColorSlider.Value = Color.G;
|
||||
_bottomColorSlider.Value = Color.B;
|
||||
_topColorSlider.Value = _colorData.X;
|
||||
_middleColorSlider.Value = _colorData.Y;
|
||||
_bottomColorSlider.Value = _colorData.Z;
|
||||
|
||||
_topInputBox.Value = (int)(Color.R * 255.0f);
|
||||
_middleInputBox.Value = (int)(Color.G * 255.0f);
|
||||
_bottomInputBox.Value = (int)(Color.B * 255.0f);
|
||||
_topInputBox.Value = (int)(_colorData.X * 255.0f);
|
||||
_middleInputBox.Value = (int)(_colorData.Y * 255.0f);
|
||||
_bottomInputBox.Value = (int)(_colorData.Z * 255.0f);
|
||||
|
||||
break;
|
||||
case ColorSelectorType.Hsv:
|
||||
Vector4 color = Color.ToHsv(Color);
|
||||
|
||||
// dumb workaround because the formula for
|
||||
// HSV calculation results in a negative
|
||||
// number in any value past 300 degrees
|
||||
if (color.X > 0)
|
||||
if (_colorData.X > 0)
|
||||
{
|
||||
_topColorSlider.Value = color.X;
|
||||
_topInputBox.Value = (int)(color.X * 360.0f);
|
||||
_topColorSlider.Value = _colorData.X;
|
||||
_topInputBox.Value = (int)(_colorData.X * 360.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
_topInputBox.Value = (int)(_topColorSlider.Value * 360.0f);
|
||||
}
|
||||
|
||||
_middleColorSlider.Value = color.Y;
|
||||
_bottomColorSlider.Value = color.Z;
|
||||
_middleColorSlider.Value = _colorData.Y;
|
||||
_bottomColorSlider.Value = _colorData.Z;
|
||||
|
||||
_middleInputBox.Value = (int)(color.Y * 100.0f);
|
||||
_bottomInputBox.Value = (int)(color.Z * 100.0f);
|
||||
_middleInputBox.Value = (int)(_colorData.Y * 100.0f);
|
||||
_bottomInputBox.Value = (int)(_colorData.Z * 100.0f);
|
||||
|
||||
|
||||
break;
|
||||
@@ -361,25 +394,16 @@ public sealed class ColorSelectorSliders : Control
|
||||
return;
|
||||
}
|
||||
|
||||
switch (SelectorType)
|
||||
_colorData = new Vector4(_topColorSlider.Value, _middleColorSlider.Value, _bottomColorSlider.Value, _alphaSlider.Value);
|
||||
|
||||
_currentColor = SelectorType switch
|
||||
{
|
||||
case ColorSelectorType.Rgb:
|
||||
Color rgbColor = new Color(_topColorSlider.Value, _middleColorSlider.Value, _bottomColorSlider.Value, _alphaSlider.Value);
|
||||
ColorSelectorType.Hsv => Color.FromHsv(_colorData),
|
||||
_ => new Color(_colorData.X, _colorData.Y, _colorData.Z, _colorData.W)
|
||||
};
|
||||
|
||||
_currentColor = rgbColor;
|
||||
Update();
|
||||
|
||||
OnColorChanged!(rgbColor);
|
||||
break;
|
||||
case ColorSelectorType.Hsv:
|
||||
Color hsvColor = Color.FromHsv(new Vector4(_topColorSlider.Value, _middleColorSlider.Value, _bottomColorSlider.Value, _alphaSlider.Value));
|
||||
|
||||
_currentColor = hsvColor;
|
||||
Update();
|
||||
|
||||
OnColorChanged!(hsvColor);
|
||||
break;
|
||||
}
|
||||
Update();
|
||||
OnColorChanged?.Invoke(_currentColor);
|
||||
}
|
||||
|
||||
private enum ColorSliderOrder
|
||||
|
||||
130
Robust.Client/UserInterface/Controls/ColorSelectorStyleBox.cs
Normal file
130
Robust.Client/UserInterface/Controls/ColorSelectorStyleBox.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Robust.Client.UserInterface.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// Style box for colouring sliders and 2-d colour selectors. E.g., this could be used to draw the typical HSV colour
|
||||
/// selection rainbow.
|
||||
/// </summary>
|
||||
public sealed class ColorSelectorStyleBox : StyleBoxTexture
|
||||
{
|
||||
public const string TexturePath = "/Textures/Interface/Nano/slider_fill.svg.96dpi.png";
|
||||
public static ProtoId<ShaderPrototype> Prototype = "ColorPicker";
|
||||
|
||||
private ShaderInstance _shader;
|
||||
|
||||
/// <summary>
|
||||
/// Base background colour.
|
||||
/// </summary>
|
||||
public Vector4 BaseColor;
|
||||
|
||||
/// <summary>
|
||||
/// Colour to add to the background colour along the X-axis.
|
||||
/// I.e., from left to right the background colour will vary from (BaseColour) to (BaseColour + XAxis)
|
||||
/// </summary>
|
||||
public Vector4 XAxis;
|
||||
|
||||
/// <summary>
|
||||
/// Colour to add to the background colour along the y-axis.
|
||||
/// I.e., from left to right the background colour will vary from (BaseColour) to (BaseColour + XAxis)
|
||||
/// </summary>
|
||||
public Vector4 YAxis;
|
||||
|
||||
/// <summary>
|
||||
/// If true, then <see cref="BaseColor"/>, <see cref="XAxis"/>, and <see cref="YAxis"/> will be interpreted as HSVa
|
||||
/// colours.
|
||||
/// </summary>
|
||||
public bool Hsv;
|
||||
|
||||
public ColorSelectorStyleBox(ColorSliderPreset preset = ColorSliderPreset.Red)
|
||||
{
|
||||
Texture = IoCManager.Resolve<IResourceCache>().GetResource<TextureResource>(TexturePath);
|
||||
_shader = IoCManager.Resolve<IPrototypeManager>().Index(Prototype).InstanceUnique();
|
||||
SetPatchMargin(Margin.All, 12);
|
||||
ConfigureSlider(preset);
|
||||
}
|
||||
|
||||
protected override void DoDraw(DrawingHandleScreen handle, UIBox2 box, float uiScale)
|
||||
{
|
||||
var old = handle.GetShader();
|
||||
handle.UseShader(_shader);
|
||||
|
||||
var globalPixelPos = handle.GetTransform().Transform(default);
|
||||
_shader.SetParameter("size", box.Size);
|
||||
_shader.SetParameter("offset", globalPixelPos);
|
||||
_shader.SetParameter("xAxis", XAxis);
|
||||
_shader.SetParameter("yAxis", YAxis);
|
||||
_shader.SetParameter("baseColor", BaseColor);
|
||||
_shader.SetParameter("hsv", Hsv);
|
||||
|
||||
base.DoDraw(handle, box, uiScale);
|
||||
handle.UseShader(old);
|
||||
}
|
||||
|
||||
public void ConfigureSlider(ColorSliderPreset preset)
|
||||
{
|
||||
Hsv = preset > ColorSliderPreset.Blue;
|
||||
|
||||
if (preset == ColorSliderPreset.HueValue)
|
||||
{
|
||||
XAxis = new(1, 0, 0, 0); // Hue;
|
||||
YAxis = new(0, 0, 1, 0); // value;
|
||||
return;
|
||||
}
|
||||
|
||||
YAxis = default;
|
||||
XAxis = preset switch
|
||||
{
|
||||
ColorSliderPreset.Red or ColorSliderPreset.Hue => new(1, 0, 0, 0),
|
||||
ColorSliderPreset.Green or ColorSliderPreset.Saturation => new(0, 1, 0, 0),
|
||||
_ => new(0, 0, 1, 0),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method that sets the base color by taking in some color and removing the components that are controlled by the x and y axes.
|
||||
/// </summary>
|
||||
public void SetBaseColor(Color color)
|
||||
{
|
||||
var colorData = Hsv
|
||||
? Color.ToHsv(color)
|
||||
: new Vector4(color.R, color.G, color.B, color.A);
|
||||
SetBaseColor(colorData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method that sets the base color by taking in some color and removing the components that are controlled by the x and y axes.
|
||||
/// </summary>
|
||||
public void SetBaseColor(Vector4 colorData)
|
||||
{
|
||||
BaseColor = colorData - colorData * XAxis - colorData * YAxis;
|
||||
}
|
||||
|
||||
public enum ColorSliderPreset : byte
|
||||
{
|
||||
// Horizontal red slider
|
||||
Red = 1,
|
||||
|
||||
// Horizontal green slider
|
||||
Green = 2,
|
||||
|
||||
// Horizontal blue slider
|
||||
Blue = 3,
|
||||
|
||||
// Horizontal hue slider
|
||||
Hue = 4,
|
||||
|
||||
// Horizontal situation slider
|
||||
Saturation = 5,
|
||||
|
||||
// Horizontal saturation slider
|
||||
Value = 6,
|
||||
|
||||
// 2-D hue-value box
|
||||
HueValue = 7
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
[Virtual]
|
||||
public class MenuBar : PanelContainer
|
||||
{
|
||||
public const string StyleClassMenuBarPopup = "menuBarPopup";
|
||||
private readonly List<Menu> _menus = new();
|
||||
private readonly List<MenuTopButton> _buttons = new();
|
||||
private readonly BoxContainer _hBox;
|
||||
@@ -40,7 +41,8 @@ namespace Robust.Client.UserInterface.Controls
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
MinSize = new Vector2(300, 0)
|
||||
})
|
||||
}
|
||||
},
|
||||
StyleClasses = { StyleClassMenuBarPopup }
|
||||
};
|
||||
_popup.OnPopupHide += PopupHidden;
|
||||
UserInterfaceManager.ModalRoot.AddChild(_popup);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Numerics;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -14,6 +15,9 @@ namespace Robust.Client.UserInterface.Controls
|
||||
public const string StylePropertyFill = "fill";
|
||||
public const string StylePropertyGrabber = "grabber";
|
||||
|
||||
public event Action<Slider>? OnGrabbed;
|
||||
public event Action<Slider>? OnReleased;
|
||||
|
||||
protected readonly PanelContainer _foregroundPanel;
|
||||
protected readonly PanelContainer _backgroundPanel;
|
||||
protected readonly PanelContainer _fillPanel;
|
||||
@@ -135,6 +139,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
HandlePositionChange(args.RelativePosition);
|
||||
_grabbed = true;
|
||||
OnGrabbed?.Invoke(this);
|
||||
}
|
||||
|
||||
protected internal override void KeyBindUp(GUIBoundKeyEventArgs args)
|
||||
@@ -144,6 +149,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
if (args.Function != EngineKeyFunctions.UIClick) return;
|
||||
|
||||
_grabbed = false;
|
||||
OnReleased?.Invoke(this);
|
||||
}
|
||||
|
||||
protected internal override void MouseMove(GUIMouseMoveEventArgs args)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
@@ -13,17 +14,18 @@ namespace Robust.Client.UserInterface.Controls
|
||||
[Virtual]
|
||||
public class SpriteView : Control
|
||||
{
|
||||
private SpriteSystem? _spriteSystem;
|
||||
private SpriteSystem? _sprite;
|
||||
private SharedTransformSystem? _transform;
|
||||
IEntityManager _entMan;
|
||||
|
||||
[ViewVariables]
|
||||
public SpriteComponent? Sprite { get; private set; }
|
||||
|
||||
public SpriteComponent? Sprite => Entity?.Comp1;
|
||||
|
||||
[ViewVariables]
|
||||
public EntityUid? Entity { get; private set; }
|
||||
public Entity<SpriteComponent, TransformComponent>? Entity { get; private set; }
|
||||
|
||||
public Entity<SpriteComponent>? Ent => Entity == null || Sprite == null ? null : (Entity.Value, Sprite);
|
||||
[ViewVariables]
|
||||
public NetEntity? NetEnt { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// This field configures automatic scaling of the sprite. This automatic scaling is done before
|
||||
@@ -118,23 +120,48 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
public SpriteView()
|
||||
{
|
||||
_entMan = IoCManager.Resolve<IEntityManager>();
|
||||
if (_entMan.TryGetComponent(Entity, out SpriteComponent? sprite))
|
||||
{
|
||||
Sprite = sprite;
|
||||
}
|
||||
|
||||
IoCManager.Resolve(ref _entMan);
|
||||
RectClipContent = true;
|
||||
}
|
||||
|
||||
public SpriteView(EntityUid? uid, IEntityManager entMan)
|
||||
{
|
||||
_entMan = entMan;
|
||||
RectClipContent = true;
|
||||
SetEntity(uid);
|
||||
}
|
||||
|
||||
public SpriteView(NetEntity uid, IEntityManager entMan)
|
||||
{
|
||||
_entMan = entMan;
|
||||
RectClipContent = true;
|
||||
SetEntity(uid);
|
||||
}
|
||||
|
||||
public void SetEntity(NetEntity netEnt)
|
||||
{
|
||||
if (netEnt == NetEnt)
|
||||
return;
|
||||
|
||||
Entity = null;
|
||||
NetEnt = netEnt;
|
||||
}
|
||||
|
||||
public void SetEntity(EntityUid? uid)
|
||||
{
|
||||
Entity = uid;
|
||||
if (Entity?.Owner == uid)
|
||||
return;
|
||||
|
||||
if (_entMan.TryGetComponent(Entity, out SpriteComponent? sprite))
|
||||
if (!_entMan.TryGetComponent(uid, out SpriteComponent? sprite)
|
||||
|| !_entMan.TryGetComponent(uid, out TransformComponent? xform))
|
||||
{
|
||||
Sprite = sprite;
|
||||
Entity = null;
|
||||
NetEnt = null;
|
||||
return;
|
||||
}
|
||||
|
||||
Entity = new(uid.Value, sprite, xform);
|
||||
NetEnt = _entMan.GetNetEntity(uid);
|
||||
}
|
||||
|
||||
protected override Vector2 MeasureOverride(Vector2 availableSize)
|
||||
@@ -146,13 +173,10 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
private void UpdateSize()
|
||||
{
|
||||
if (Entity == null || Sprite == null)
|
||||
{
|
||||
_spriteSize = default;
|
||||
if (!ResolveEntity(out _, out var sprite, out _))
|
||||
return;
|
||||
}
|
||||
|
||||
var spriteBox = Sprite.CalculateRotatedBoundingBox(default, _worldRotation ?? Angle.Zero, _eyeRotation)
|
||||
var spriteBox = sprite.CalculateRotatedBoundingBox(default, _worldRotation ?? Angle.Zero, _eyeRotation)
|
||||
.CalcBoundingBox();
|
||||
|
||||
if (!SpriteOffset)
|
||||
@@ -194,18 +218,14 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
internal override void DrawInternal(IRenderHandle renderHandle)
|
||||
{
|
||||
if (Entity is not {} uid || Sprite == null)
|
||||
if (!ResolveEntity(out var uid, out var sprite, out var xform))
|
||||
return;
|
||||
|
||||
if (Sprite.Deleted)
|
||||
{
|
||||
SetEntity(null);
|
||||
return;
|
||||
}
|
||||
_sprite ??= _entMan.System<SpriteSystem>();
|
||||
_transform ??= _entMan.System<TransformSystem>();
|
||||
|
||||
// Ensure the sprite is animated despite possible not being visible in any viewport.
|
||||
_spriteSystem ??= _entMan.System<SpriteSystem>();
|
||||
_spriteSystem.ForceUpdate(uid);
|
||||
_sprite.ForceUpdate(uid);
|
||||
|
||||
var stretchVec = Stretch switch
|
||||
{
|
||||
@@ -217,11 +237,47 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
var offset = SpriteOffset
|
||||
? Vector2.Zero
|
||||
: - (-_eyeRotation).RotateVec(Sprite.Offset) * new Vector2(1, -1) * EyeManager.PixelsPerMeter;
|
||||
: - (-_eyeRotation).RotateVec(sprite.Offset) * new Vector2(1, -1) * EyeManager.PixelsPerMeter;
|
||||
|
||||
var position = PixelSize / 2 + offset * stretch * UIScale;
|
||||
var scale = Scale * UIScale * stretch;
|
||||
renderHandle.DrawEntity(uid, position, scale, _worldRotation, _eyeRotation, OverrideDirection, Sprite);
|
||||
|
||||
// control modulation is applied automatically to the screen handle, but here we need to use the world handle
|
||||
var world = renderHandle.DrawingHandleWorld;
|
||||
var oldModulate = world.Modulate;
|
||||
world.Modulate *= Modulate * ActualModulateSelf;
|
||||
|
||||
renderHandle.DrawEntity(uid, position, scale, _worldRotation, _eyeRotation, OverrideDirection, sprite, xform, _transform);
|
||||
world.Modulate = oldModulate;
|
||||
}
|
||||
|
||||
private bool ResolveEntity(
|
||||
out EntityUid uid,
|
||||
[NotNullWhen(true)] out SpriteComponent? sprite,
|
||||
[NotNullWhen(true)] out TransformComponent? xform)
|
||||
{
|
||||
if (Entity != null)
|
||||
{
|
||||
(uid, sprite, xform) = Entity.Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
sprite = null;
|
||||
xform = null;
|
||||
uid = default;
|
||||
|
||||
if (NetEnt == null)
|
||||
return false;
|
||||
|
||||
if (!_entMan.TryGetEntity(NetEnt, out var ent))
|
||||
return false;
|
||||
|
||||
SetEntity(ent);
|
||||
if (Entity == null)
|
||||
return false;
|
||||
|
||||
(uid, sprite, xform) = Entity.Value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Numerics;
|
||||
@@ -228,7 +229,7 @@ public sealed class TextEdit : Control
|
||||
private bool IsPlaceholderVisible => Rope.IsNullOrEmpty(_textRope) && _placeholder != null;
|
||||
|
||||
// Table used by cursor movement system, see below.
|
||||
private static readonly Dictionary<BoundKeyFunction, MoveType> MoveTypeMap = new()
|
||||
private static readonly FrozenDictionary<BoundKeyFunction, MoveType> MoveTypeMap = new Dictionary<BoundKeyFunction, MoveType>()
|
||||
{
|
||||
// @formatter:off
|
||||
{ EngineKeyFunctions.TextCursorLeft, MoveType.Left },
|
||||
@@ -249,7 +250,7 @@ public sealed class TextEdit : Control
|
||||
{ EngineKeyFunctions.TextCursorSelectBegin, MoveType.BeginOfLine | MoveType.SelectFlag },
|
||||
{ EngineKeyFunctions.TextCursorSelectEnd, MoveType.EndOfLine | MoveType.SelectFlag },
|
||||
// @formatter:on
|
||||
};
|
||||
}.ToFrozenDictionary();
|
||||
|
||||
protected internal override void KeyBindDown(GUIBoundKeyEventArgs args)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
@@ -13,5 +14,17 @@ namespace Robust.Client.UserInterface.Controls
|
||||
public override float UIScale => UIScaleSet;
|
||||
internal float UIScaleSet { get; set; }
|
||||
public override IClydeWindow Window { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Disable automatic scaling of window <see cref="UIScale"/> based on resolution.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Disabled by default for non-main windows as those most likely are smaller popup windows,
|
||||
/// that won't make sense with the default parameters.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="CVars.ResAutoScaleEnabled"/>
|
||||
public bool DisableAutoScaling { get; set; } = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Intrinsics.Arm;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using X86Aes = System.Runtime.Intrinsics.X86.Aes;
|
||||
using ArmAes = System.Runtime.Intrinsics.Arm.Aes;
|
||||
|
||||
namespace Robust.Client.UserInterface.CustomControls.DebugMonitorControls;
|
||||
|
||||
@@ -36,139 +28,6 @@ internal sealed class DebugSystemPanel : PanelContainer
|
||||
|
||||
HorizontalAlignment = HAlignment.Left;
|
||||
|
||||
var version = typeof(DebugSystemPanel).Assembly.GetName().Version;
|
||||
|
||||
contents.Text = @$"OS: {RuntimeInformation.OSDescription} {RuntimeInformation.OSArchitecture}
|
||||
.NET Runtime: {RuntimeInformation.FrameworkDescription} {RuntimeInformation.RuntimeIdentifier}
|
||||
Server GC: {GCSettings.IsServerGC}
|
||||
Processor: {Environment.ProcessorCount}x {SystemInformation.GetProcessorModel()}
|
||||
Architecture: {RuntimeInformation.ProcessArchitecture}
|
||||
Robust Version: {version}
|
||||
Compile Options: {GetCompileOptions()}
|
||||
Intrinsics: {GetIntrinsics()}";
|
||||
}
|
||||
|
||||
private static string GetCompileOptions()
|
||||
{
|
||||
var options = new List<string>();
|
||||
|
||||
#if DEVELOPMENT
|
||||
options.Add("DEVELOPMENT");
|
||||
#endif
|
||||
|
||||
#if FULL_RELEASE
|
||||
options.Add("FULL_RELEASE");
|
||||
#endif
|
||||
|
||||
#if TOOLS
|
||||
options.Add("TOOLS");
|
||||
#endif
|
||||
|
||||
#if DEBUG
|
||||
options.Add("DEBUG");
|
||||
#endif
|
||||
|
||||
#if RELEASE
|
||||
options.Add("RELEASE");
|
||||
#endif
|
||||
|
||||
#if EXCEPTION_TOLERANCE
|
||||
options.Add("EXCEPTION_TOLERANCE");
|
||||
#endif
|
||||
|
||||
#if CLIENT_SCRIPTING
|
||||
options.Add("CLIENT_SCRIPTING");
|
||||
#endif
|
||||
|
||||
#if USE_SYSTEM_SQLITE
|
||||
options.Add("USE_SYSTEM_SQLITE");
|
||||
#endif
|
||||
|
||||
return string.Join(';', options);
|
||||
}
|
||||
|
||||
private static string GetIntrinsics()
|
||||
{
|
||||
var options = new List<string>();
|
||||
|
||||
// - No put the oof back, hello?
|
||||
|
||||
// x86
|
||||
|
||||
if (X86Aes.IsSupported)
|
||||
options.Add(nameof(X86Aes));
|
||||
|
||||
if (Avx.IsSupported)
|
||||
options.Add(nameof(Avx));
|
||||
|
||||
if (Avx2.IsSupported)
|
||||
options.Add(nameof(Avx2));
|
||||
|
||||
if (Bmi1.IsSupported)
|
||||
options.Add(nameof(Bmi1));
|
||||
|
||||
if (Bmi2.IsSupported)
|
||||
options.Add(nameof(Bmi2));
|
||||
|
||||
if (Fma.IsSupported)
|
||||
options.Add(nameof(Fma));
|
||||
|
||||
if (Lzcnt.IsSupported)
|
||||
options.Add(nameof(Lzcnt));
|
||||
|
||||
if (Pclmulqdq.IsSupported)
|
||||
options.Add(nameof(Pclmulqdq));
|
||||
|
||||
if (Popcnt.IsSupported)
|
||||
options.Add(nameof(Popcnt));
|
||||
|
||||
if (Sse.IsSupported)
|
||||
options.Add(nameof(Sse));
|
||||
|
||||
if (Sse2.IsSupported)
|
||||
options.Add(nameof(Sse2));
|
||||
|
||||
if (Sse3.IsSupported)
|
||||
options.Add(nameof(Sse3));
|
||||
|
||||
if (Ssse3.IsSupported)
|
||||
options.Add(nameof(Ssse3));
|
||||
|
||||
if (Sse41.IsSupported)
|
||||
options.Add(nameof(Sse41));
|
||||
|
||||
if (Sse42.IsSupported)
|
||||
options.Add(nameof(Sse42));
|
||||
|
||||
if (X86Base.IsSupported)
|
||||
options.Add(nameof(X86Base));
|
||||
|
||||
// ARM
|
||||
|
||||
if (AdvSimd.IsSupported)
|
||||
options.Add(nameof(AdvSimd));
|
||||
|
||||
if (ArmAes.IsSupported)
|
||||
options.Add(nameof(ArmAes));
|
||||
|
||||
if (ArmBase.IsSupported)
|
||||
options.Add(nameof(ArmBase));
|
||||
|
||||
if (Crc32.IsSupported)
|
||||
options.Add(nameof(Crc32));
|
||||
|
||||
if (Dp.IsSupported)
|
||||
options.Add(nameof(Dp));
|
||||
|
||||
if (Rdm.IsSupported)
|
||||
options.Add(nameof(Rdm));
|
||||
|
||||
if (Sha1.IsSupported)
|
||||
options.Add(nameof(Sha1));
|
||||
|
||||
if (Sha256.IsSupported)
|
||||
options.Add(nameof(Sha256));
|
||||
|
||||
return string.Join(';', options);
|
||||
contents.Text = string.Join('\n', RuntimeInformationPrinter.GetInformationDump());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,29 @@
|
||||
<Control xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="Robust.Client.UserInterface.DevWindowTabUI">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<SplitContainer VerticalExpand="True" Orientation="Horizontal">
|
||||
<BoxContainer Orientation="Vertical" MinWidth="250">
|
||||
<!--
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Button Name="RefreshButton" Text="{Loc 'dev-window-ui-refresh'}" />
|
||||
x:Class="Robust.Client.UserInterface.DevWindowTabUI"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics">
|
||||
<PanelContainer HorizontalExpand="True" VerticalExpand="True">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#25252add" ContentMarginTopOverride="3" />
|
||||
</PanelContainer.PanelOverride>
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<PanelContainer Name="TopbarButtons" HorizontalExpand="True">
|
||||
<BoxContainer HorizontalExpand="True" Orientation="Horizontal">
|
||||
<Button Name="ControlPicker" ToggleMode="True" Text="Inspect Element" />
|
||||
<Button Name="RefreshPropertiesButton" Text="Refresh Props" />
|
||||
</BoxContainer>
|
||||
-->
|
||||
<Button Name="ControlPicker" ToggleMode="True" Text="Inspect" />
|
||||
<ScrollContainer VerticalExpand="True">
|
||||
<BoxContainer Name="ControlTreeRoot" Orientation="Vertical" MouseFilter="Stop" />
|
||||
</PanelContainer>
|
||||
<SplitContainer HorizontalExpand="True" VerticalExpand="True"
|
||||
Orientation="Horizontal" ResizeMode="RespectChildrenMinSize">
|
||||
<ScrollContainer>
|
||||
<PanelContainer Name="TreeRootBG">
|
||||
<BoxContainer Name="ControlTreeRoot" Orientation="Vertical" MouseFilter="Stop" />
|
||||
</PanelContainer>
|
||||
</ScrollContainer>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Button Name="RefreshPropertiesButton" Text="Refresh" />
|
||||
</BoxContainer>
|
||||
|
||||
<ScrollContainer VerticalExpand="True" HScrollEnabled="False">
|
||||
<GridContainer Name="ControlProperties" Columns="2" />
|
||||
<ScrollContainer HScrollEnabled="False">
|
||||
<BoxContainer Name="ControlProperties" Orientation="Vertical" MouseFilter="Stop" />
|
||||
</ScrollContainer>
|
||||
</BoxContainer>
|
||||
</SplitContainer>
|
||||
</BoxContainer>
|
||||
</SplitContainer>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</Control>
|
||||
|
||||
@@ -10,174 +10,223 @@ using Robust.Shared.Input;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.UserInterface
|
||||
namespace Robust.Client.UserInterface;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class DevWindowTabUI : Control
|
||||
{
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class DevWindowTabUI : Control
|
||||
[Dependency] private readonly IClydeInternal _clyde = default!;
|
||||
[Dependency] private readonly IInputManager _input = default!;
|
||||
|
||||
public Control? SelectedControl { get; private set; }
|
||||
private Dictionary<Control, DevWindowUITreeEntry> ControlMap { get; } = new();
|
||||
private Control? LastHoveredControl { get; set; }
|
||||
|
||||
public event Action? SelectedControlChanged;
|
||||
|
||||
public DevWindowTabUI()
|
||||
{
|
||||
[Dependency] private readonly IClydeInternal _clyde = default!;
|
||||
[Dependency] private readonly IInputManager _input = default!;
|
||||
InitializeComponent();
|
||||
|
||||
public Control? SelectedControl { get; private set; }
|
||||
private Dictionary<Control, DevWindowUITreeEntry> ControlMap { get; } = new();
|
||||
private Control? LastHoveredControl { get; set; }
|
||||
|
||||
public event Action? SelectedControlChanged;
|
||||
|
||||
public DevWindowTabUI()
|
||||
TopbarButtons.PanelOverride = new StyleBoxFlat
|
||||
{
|
||||
InitializeComponent();
|
||||
BorderThickness = new Thickness(0, 0, 0, 1),
|
||||
BorderColor = Color.Gray,
|
||||
ContentMarginLeftOverride = 3,
|
||||
ContentMarginRightOverride = 3,
|
||||
ContentMarginBottomOverride = 1,
|
||||
ContentMarginTopOverride = 3,
|
||||
};
|
||||
TreeRootBG.PanelOverride = new StyleBoxFlat
|
||||
{
|
||||
BorderThickness = new Thickness(0, 0, 1, 0),
|
||||
BorderColor = Color.Gray,
|
||||
ContentMarginLeftOverride = 3,
|
||||
ContentMarginBottomOverride = 3,
|
||||
};
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
ControlTreeRoot.OnKeyBindDown += ControlTreeRootOnKeyBindDown;
|
||||
RefreshPropertiesButton.OnPressed += _ => Refresh();
|
||||
}
|
||||
|
||||
private Control? GetControlUnderMouse()
|
||||
{
|
||||
return UserInterfaceManager.MouseGetControl(_input.MouseScreenPosition);
|
||||
}
|
||||
|
||||
private void OnMouseMove(MouseMoveEventArgs obj)
|
||||
{
|
||||
if (!ControlPicker.Pressed)
|
||||
return;
|
||||
|
||||
var controlUnderMouse = GetControlUnderMouse();
|
||||
if (LastHoveredControl == controlUnderMouse)
|
||||
return;
|
||||
|
||||
LastHoveredControl = controlUnderMouse;
|
||||
|
||||
Stack<Control> entryStack = new();
|
||||
DevWindowUITreeEntry? entry = null;
|
||||
var control = controlUnderMouse;
|
||||
while (control != null)
|
||||
{
|
||||
if (ControlMap.TryGetValue(control, out entry))
|
||||
break;
|
||||
|
||||
entryStack.Push(control);
|
||||
control = control.Parent;
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
if (entry == null)
|
||||
return;
|
||||
|
||||
ControlTreeRoot.OnKeyBindDown += ControlTreeRootOnKeyBindDown;
|
||||
RefreshPropertiesButton.OnPressed += _ => Refresh();
|
||||
if (entryStack.Count > 0)
|
||||
entry.Open();
|
||||
|
||||
foreach (var subEntry in entryStack)
|
||||
{
|
||||
ControlMap[subEntry].Open();
|
||||
}
|
||||
|
||||
private Control? GetControlUnderMouse()
|
||||
SelectControl(controlUnderMouse);
|
||||
}
|
||||
|
||||
private bool OnUIKeyBindStateChanged(BoundKeyEventArgs arg)
|
||||
{
|
||||
if (arg.Function != EngineKeyFunctions.UIClick)
|
||||
return false;
|
||||
|
||||
if (!ControlPicker.Pressed)
|
||||
return false;
|
||||
|
||||
var control = GetControlUnderMouse();
|
||||
if (control == ControlPicker)
|
||||
return false;
|
||||
|
||||
ControlPicker.Pressed = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ControlTreeRootOnKeyBindDown(GUIBoundKeyEventArgs obj)
|
||||
{
|
||||
if (obj.Function != EngineKeyFunctions.UIClick)
|
||||
return;
|
||||
|
||||
obj.Handle();
|
||||
SelectControl(null);
|
||||
}
|
||||
|
||||
protected override void EnteredTree()
|
||||
{
|
||||
base.EnteredTree();
|
||||
|
||||
// Load tree roots.
|
||||
foreach (var root in UserInterfaceManager.AllRoots)
|
||||
{
|
||||
return UserInterfaceManager.MouseGetControl(_input.MouseScreenPosition);
|
||||
var entry = new DevWindowUITreeEntry(this, root);
|
||||
|
||||
ControlTreeRoot.AddChild(entry);
|
||||
}
|
||||
|
||||
private void OnMouseMove(MouseMoveEventArgs obj)
|
||||
{
|
||||
if (!ControlPicker.Pressed)
|
||||
return;
|
||||
UserInterfaceManager.OnPostDrawUIRoot += OnPostDrawUIRoot;
|
||||
_clyde.MouseMove += OnMouseMove;
|
||||
_input.UIKeyBindStateChanged += OnUIKeyBindStateChanged;
|
||||
}
|
||||
|
||||
var controlUnderMouse = GetControlUnderMouse();
|
||||
if (LastHoveredControl == controlUnderMouse)
|
||||
return;
|
||||
protected override void ExitedTree()
|
||||
{
|
||||
base.ExitedTree();
|
||||
|
||||
LastHoveredControl = controlUnderMouse;
|
||||
// Clear tree children.
|
||||
ControlTreeRoot.RemoveAllChildren();
|
||||
|
||||
Stack<Control> entryStack = new();
|
||||
DevWindowUITreeEntry? entry = null;
|
||||
var control = controlUnderMouse;
|
||||
while (control != null)
|
||||
{
|
||||
if (ControlMap.TryGetValue(control, out entry))
|
||||
break;
|
||||
UserInterfaceManager.OnPostDrawUIRoot -= OnPostDrawUIRoot;
|
||||
_clyde.MouseMove -= OnMouseMove;
|
||||
_input.UIKeyBindStateChanged -= OnUIKeyBindStateChanged;
|
||||
}
|
||||
|
||||
entryStack.Push(control);
|
||||
control = control.Parent;
|
||||
}
|
||||
private void OnPostDrawUIRoot(PostDrawUIRootEventArgs eventArgs)
|
||||
{
|
||||
if (SelectedControl == null || eventArgs.Root != SelectedControl.Root)
|
||||
return;
|
||||
|
||||
if (entry == null)
|
||||
return;
|
||||
var rect = UIBox2i.FromDimensions(SelectedControl.GlobalPixelPosition, SelectedControl.PixelSize);
|
||||
eventArgs.DrawingHandle.DrawRect(rect, Color.Cyan.WithAlpha(0.35f));
|
||||
}
|
||||
|
||||
if (entryStack.Count > 0)
|
||||
entry.Open();
|
||||
internal void EntryAdded(DevWindowUITreeEntry entry)
|
||||
{
|
||||
ControlMap[entry.VisControl] = entry;
|
||||
}
|
||||
|
||||
foreach (var subEntry in entryStack)
|
||||
{
|
||||
ControlMap[subEntry].Open();
|
||||
}
|
||||
|
||||
SelectControl(controlUnderMouse);
|
||||
}
|
||||
|
||||
private bool OnUIKeyBindStateChanged(BoundKeyEventArgs arg)
|
||||
{
|
||||
if (arg.Function != EngineKeyFunctions.UIClick)
|
||||
return false;
|
||||
|
||||
if (!ControlPicker.Pressed)
|
||||
return false;
|
||||
|
||||
var control = GetControlUnderMouse();
|
||||
if (control == ControlPicker)
|
||||
return false;
|
||||
|
||||
ControlPicker.Pressed = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ControlTreeRootOnKeyBindDown(GUIBoundKeyEventArgs obj)
|
||||
{
|
||||
if (obj.Function != EngineKeyFunctions.UIClick)
|
||||
return;
|
||||
|
||||
obj.Handle();
|
||||
public void EntryRemoved(DevWindowUITreeEntry entry)
|
||||
{
|
||||
if (SelectedControl == entry.VisControl)
|
||||
SelectControl(null);
|
||||
}
|
||||
|
||||
protected override void EnteredTree()
|
||||
ControlMap.Remove(entry.VisControl);
|
||||
}
|
||||
|
||||
public void SelectControl(Control? control)
|
||||
{
|
||||
SelectedControl = control;
|
||||
|
||||
SelectedControlChanged?.Invoke();
|
||||
|
||||
Refresh();
|
||||
}
|
||||
|
||||
private void Refresh()
|
||||
{
|
||||
ControlProperties.RemoveAllChildren();
|
||||
|
||||
if (SelectedControl == null)
|
||||
return;
|
||||
|
||||
var props = GuiDumpCommand.PropertyValuesForInheritance(SelectedControl);
|
||||
foreach (var (classname, values) in props)
|
||||
{
|
||||
base.EnteredTree();
|
||||
|
||||
// Load tree roots.
|
||||
foreach (var root in UserInterfaceManager.AllRoots)
|
||||
ControlProperties.AddChild(new PanelContainer
|
||||
{
|
||||
var entry = new DevWindowUITreeEntry(this, root);
|
||||
|
||||
ControlTreeRoot.AddChild(entry);
|
||||
}
|
||||
|
||||
UserInterfaceManager.OnPostDrawUIRoot += OnPostDrawUIRoot;
|
||||
_clyde.MouseMove += OnMouseMove;
|
||||
_input.UIKeyBindStateChanged += OnUIKeyBindStateChanged;
|
||||
}
|
||||
|
||||
protected override void ExitedTree()
|
||||
{
|
||||
base.ExitedTree();
|
||||
|
||||
// Clear tree children.
|
||||
ControlTreeRoot.RemoveAllChildren();
|
||||
|
||||
UserInterfaceManager.OnPostDrawUIRoot -= OnPostDrawUIRoot;
|
||||
_clyde.MouseMove -= OnMouseMove;
|
||||
_input.UIKeyBindStateChanged -= OnUIKeyBindStateChanged;
|
||||
}
|
||||
|
||||
private void OnPostDrawUIRoot(PostDrawUIRootEventArgs eventArgs)
|
||||
{
|
||||
if (SelectedControl == null || eventArgs.Root != SelectedControl.Root)
|
||||
return;
|
||||
|
||||
var rect = UIBox2i.FromDimensions(SelectedControl.GlobalPixelPosition, SelectedControl.PixelSize);
|
||||
eventArgs.DrawingHandle.DrawRect(rect, Color.Cyan.WithAlpha(0.35f));
|
||||
}
|
||||
|
||||
internal void EntryAdded(DevWindowUITreeEntry entry)
|
||||
{
|
||||
ControlMap[entry.VisControl] = entry;
|
||||
}
|
||||
|
||||
public void EntryRemoved(DevWindowUITreeEntry entry)
|
||||
{
|
||||
if (SelectedControl == entry.VisControl)
|
||||
SelectControl(null);
|
||||
|
||||
ControlMap.Remove(entry.VisControl);
|
||||
}
|
||||
|
||||
public void SelectControl(Control? control)
|
||||
{
|
||||
SelectedControl = control;
|
||||
|
||||
SelectedControlChanged?.Invoke();
|
||||
|
||||
Refresh();
|
||||
}
|
||||
|
||||
|
||||
private void Refresh()
|
||||
{
|
||||
ControlProperties.RemoveAllChildren();
|
||||
|
||||
if (SelectedControl == null)
|
||||
return;
|
||||
|
||||
var props = GuiDumpCommand.PropertyValuesFor(SelectedControl);
|
||||
foreach (var (prop, value) in props)
|
||||
Children = { new Label
|
||||
{
|
||||
Text = classname,
|
||||
Margin = new Thickness(3, 2)
|
||||
}},
|
||||
PanelOverride = new StyleBoxFlat
|
||||
{
|
||||
BorderThickness = new Thickness(0, 1),
|
||||
BorderColor = Color.Gray,
|
||||
BackgroundColor = Color.FromHex("#555555"),
|
||||
}
|
||||
});
|
||||
foreach (var (prop, value) in values)
|
||||
{
|
||||
ControlProperties.AddChild(new Label { Text = prop });
|
||||
ControlProperties.AddChild(new Label { Text = value });
|
||||
ControlProperties.AddChild(new BoxContainer
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||
SeparationOverride = 3,
|
||||
Margin = new Thickness(3, 1),
|
||||
Children =
|
||||
{
|
||||
new BoxContainer
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||
Children =
|
||||
{
|
||||
new Label { Text = $"{prop}", FontColorOverride = Color.GreenYellow },
|
||||
new Label { Text = ":" }, // this is for the non colored ":", intentional
|
||||
}
|
||||
},
|
||||
new Label { Text = $"{value}" },
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Audio.Sources;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Audio.Sources;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Robust.Client.UserInterface
|
||||
@@ -119,6 +121,20 @@ namespace Robust.Client.UserInterface
|
||||
void DeferAction(Action action);
|
||||
|
||||
public event Action<Control>? OnKeyBindDown;
|
||||
|
||||
void SetClickSound(IAudioSource? source);
|
||||
|
||||
/// <summary>
|
||||
/// Plays the UI click sound if relevant
|
||||
/// </summary>
|
||||
void ClickSound();
|
||||
|
||||
void SetHoverSound(IAudioSource? source);
|
||||
|
||||
/// <summary>
|
||||
/// Plays the UI hover sound if relevant.
|
||||
/// </summary>
|
||||
void HoverSound();
|
||||
}
|
||||
|
||||
public readonly struct PostDrawUIRootEventArgs
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user