mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
135 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef26a680fc | ||
|
|
62fd21e489 | ||
|
|
67e3f52a9d | ||
|
|
408feb6dd5 | ||
|
|
feb9e1db69 | ||
|
|
613705613b | ||
|
|
80a053c0a9 | ||
|
|
657455dae0 | ||
|
|
8ae35e12ee | ||
|
|
4e2c0e431b | ||
|
|
9c41f19eaf | ||
|
|
f75ce13f00 | ||
|
|
ac45a0a64b | ||
|
|
a8a73e28f4 | ||
|
|
e5983a9ec1 | ||
|
|
b7fa39d8cc | ||
|
|
3c30ed749c | ||
|
|
eb1a2ae9b4 | ||
|
|
ee0c31a8c3 | ||
|
|
4ab61b840a | ||
|
|
df29fa438a | ||
|
|
a3756c29bd | ||
|
|
4dc17f3aca | ||
|
|
d22280f177 | ||
|
|
9e8f7092ea | ||
|
|
de188cc773 | ||
|
|
784a02c0e7 | ||
|
|
c8db7f98db | ||
|
|
318c37e686 | ||
|
|
524be86449 | ||
|
|
ddeb78accd | ||
|
|
585e847818 | ||
|
|
ac3cb4dc2a | ||
|
|
c06ca39009 | ||
|
|
06b11a51f1 | ||
|
|
4938a159d4 | ||
|
|
27f2e270ce | ||
|
|
94e60e0b10 | ||
|
|
c6863033a5 | ||
|
|
917878d05f | ||
|
|
10e4766809 | ||
|
|
abd5149245 | ||
|
|
912b6da20a | ||
|
|
94fe0b7721 | ||
|
|
9fac1e78fb | ||
|
|
6697b76683 | ||
|
|
b2ab247b5b | ||
|
|
7411ae8138 | ||
|
|
d398e3a75b | ||
|
|
a5047224bb | ||
|
|
058821c08b | ||
|
|
0c691b061d | ||
|
|
51bbc5dc45 | ||
|
|
2d3522e752 | ||
|
|
d4f265c314 | ||
|
|
7654d38612 | ||
|
|
cdcc255123 | ||
|
|
2f56a6a110 | ||
|
|
16fc48cef2 | ||
|
|
5cecbb2cff | ||
|
|
6115d6d5cc | ||
|
|
60d26be139 | ||
|
|
186392ea80 | ||
|
|
ebe4538d4c | ||
|
|
745d0e5532 | ||
|
|
d4f7e60432 | ||
|
|
ced127c164 | ||
|
|
f91bcb62b1 | ||
|
|
5268a4a3f0 | ||
|
|
1f1e50539b | ||
|
|
ea3132bbba | ||
|
|
67ccaec418 | ||
|
|
40b70e9447 | ||
|
|
7d37db9ce0 | ||
|
|
dd8688df3d | ||
|
|
b02c53c6ad | ||
|
|
38d3b83818 | ||
|
|
f02cd0083a | ||
|
|
c2c8af16d0 | ||
|
|
b61003e2a0 | ||
|
|
fb0ec52f8c | ||
|
|
fee79d8aa5 | ||
|
|
4508105412 | ||
|
|
a0ebb290e2 | ||
|
|
d45497e53b | ||
|
|
ff8dd021c3 | ||
|
|
34a371ef1f | ||
|
|
83109b08e9 | ||
|
|
3d7b83db05 | ||
|
|
41b2ee19a1 | ||
|
|
856cdb8a3d | ||
|
|
b783cd79be | ||
|
|
b5ba964f61 | ||
|
|
09676a1d9f | ||
|
|
6959f21927 | ||
|
|
26a1fb35b5 | ||
|
|
7fb3ce0e70 | ||
|
|
5497b52100 | ||
|
|
18b5f33080 | ||
|
|
30d3367c50 | ||
|
|
f6aabd1a22 | ||
|
|
6d229a3eb2 | ||
|
|
da28bdbce5 | ||
|
|
0181988225 | ||
|
|
fb2ba7460a | ||
|
|
b70d20a217 | ||
|
|
ebc33df457 | ||
|
|
697af6771c | ||
|
|
20706870da | ||
|
|
372fa39228 | ||
|
|
d6bfbe4f6f | ||
|
|
54645b4adf | ||
|
|
7c16573f3e | ||
|
|
8935b39987 | ||
|
|
388f8369a8 | ||
|
|
217d889e36 | ||
|
|
f243baccf2 | ||
|
|
790f42ea70 | ||
|
|
a5fcf122b8 | ||
|
|
df2d6ab8c2 | ||
|
|
23c90c0c45 | ||
|
|
c69756e7f1 | ||
|
|
07fbd5263c | ||
|
|
ebce0daa1b | ||
|
|
c876eb1f4c | ||
|
|
1037fc735e | ||
|
|
d5df765467 | ||
|
|
93cf9f4227 | ||
|
|
d2977e2a63 | ||
|
|
a3f0ea19c4 | ||
|
|
d9032b8757 | ||
|
|
cba6e37f9f | ||
|
|
90ec9a80c9 | ||
|
|
7eaf2f590b | ||
|
|
0439ea9893 |
2
.github/workflows/build-test.yml
vendored
2
.github/workflows/build-test.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest] # , macos-latest] - temporarily disabled due to libfreetype.dll errors.
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
|
||||
2
.github/workflows/publish-client.yml
vendored
2
.github/workflows/publish-client.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
dotnet-version: 9.0.x
|
||||
|
||||
- name: Package client
|
||||
run: Tools/package_client_build.py -p windows mac linux
|
||||
run: Tools/package_client_build.py
|
||||
|
||||
- name: Shuffle files around
|
||||
run: |
|
||||
|
||||
@@ -44,10 +44,11 @@
|
||||
<PackageVersion Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||
<PackageVersion Include="Nett" Version="0.15.0" />
|
||||
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.2" />
|
||||
<PackageVersion Include="OpenTK.OpenAL" Version="4.7.7" />
|
||||
<PackageVersion Include="OpenTK.Audio.OpenAL" Version="4.9.4" />
|
||||
<PackageVersion Include="OpenToolkit.Graphics" Version="4.0.0-pre9.1" />
|
||||
<PackageVersion Include="Pidgin" Version="3.3.0" />
|
||||
<PackageVersion Include="Robust.Natives" Version="0.1.1" />
|
||||
<PackageVersion Include="Robust.Natives" Version="0.2.3" />
|
||||
<PackageVersion Include="Robust.Natives.Zstd" Version="0.1.1-zstd1.5.7" />
|
||||
<PackageVersion Include="Robust.Natives.Cef" Version="131.3.5" />
|
||||
<PackageVersion Include="Robust.Shared.AuthLib" Version="0.1.2" />
|
||||
<PackageVersion Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.10" />
|
||||
@@ -57,9 +58,12 @@
|
||||
<PackageVersion Include="SharpZstd.Interop" Version="1.5.2-beta2" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.11" />
|
||||
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.1" />
|
||||
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.2.0" />
|
||||
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.0.2" />
|
||||
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.2.2" />
|
||||
<PackageVersion Include="SpaceWizards.Sdl" Version="1.0.0" />
|
||||
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.1.0" />
|
||||
<PackageVersion Include="SpaceWizards.Sodium" Version="0.2.1" />
|
||||
<PackageVersion Include="libsodium" Version="1.0.20.1" />
|
||||
<PackageVersion Include="System.Management" Version="9.0.8" />
|
||||
<PackageVersion Include="TerraFX.Interop.Windows" Version="10.0.26100.1" />
|
||||
<PackageVersion Include="TerraFX.Interop.Xlib" Version="6.4.0" />
|
||||
<PackageVersion Include="VorbisPizza" Version="1.3.0" />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
187
RELEASE-NOTES.md
187
RELEASE-NOTES.md
@@ -54,6 +54,193 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 267.3.2
|
||||
|
||||
|
||||
## 267.3.1
|
||||
|
||||
|
||||
## 267.3.0
|
||||
|
||||
### New features
|
||||
|
||||
* Sandbox:
|
||||
* Added `System.DateOnly` and `System.TimeOnly`.
|
||||
* `MapId`, `MapCoordinates`, and `EntityCoordinates` are now yaml serialisable
|
||||
* The base component tree lookup system has new methods including several new `QueryAabb()` overloads that take in a collection and various new `IntersectRay()` overloads that should replace `IntersectRayWithPredicate`.
|
||||
* Added `OccluderSystem.InRangeUnoccluded()` for checking for occluders that lie between two points.
|
||||
* `LocalizedCommands` now pass the command name as an argument to the localized help text.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed `MapLoaderSystem.SerializeEntitiesRecursive()` not properly serialising when given multiple root entities (e.g., multiple maps)
|
||||
* Fixed yaml hot reloading throwing invalid path exceptions.
|
||||
* The `EntityManager.CreateEntityUninitialized` overload that uses MapCoordinates now actually attaches entities to a grid if one is present at those coordinates, as was stated in it's documentation.
|
||||
* Fixed physics joint relays not being properly updated when an entity is removed from a container.
|
||||
|
||||
### Other
|
||||
|
||||
* Updated natives again to attempt to fix issues caused by the previous update.
|
||||
|
||||
|
||||
## 267.2.1
|
||||
|
||||
|
||||
## 267.2.0
|
||||
|
||||
### New features
|
||||
|
||||
* Sprites and Sprite layers have a new `Loop` data field that can be set to false to automatically pause animations once they have finished.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed `CollectionExtensions.TryGetValue` throwing an exception when given a negative list index.
|
||||
* Fixed `EntityManager.PredictedQueueDeleteEntity()` not deferring changes for networked entities until the end of the tick.
|
||||
* Fixed `EntityManager.IsQueuedForDeletion` not returning true foe entities getting deleted via `PredictedQueueDeleteEntity()`
|
||||
|
||||
### Other
|
||||
|
||||
* `IResourceManager.GetContentRoots()` has been obsoleted and returns no more results.
|
||||
|
||||
### Internal
|
||||
|
||||
* `IResourceManager.GetContentRoots()` has been replaced with a similar method on `IResourceManagerInternal`. This new method returns `string`s instead of `ResPath`s, and usage code has been updated to use these paths correctly.
|
||||
|
||||
|
||||
## 267.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Animation:
|
||||
* `AnimationTrackProperty.KeyFrame` can now have easings functions applied.
|
||||
* Graphics:
|
||||
* `PointLightComponent` now has two fields, `falloff` and `curveFactor`, for controlling light falloff and the shape of the light attenuation curve.
|
||||
* `IClydeViewport` now has an `Id` and `ClearCachedResources` event. Together, these allow you to properly cache rendering resources per viewport.
|
||||
* Miscellaneous:
|
||||
* Added `display.max_fps` CVar.
|
||||
* Added `IGameTiming.FrameStartTime`.
|
||||
* Sandbox:
|
||||
* Added `System.WeakReference<T>`.
|
||||
* Added `SpaceWizards.Sodium.CryptoGenericHashBlake2B.Hash()`.
|
||||
* Added `System.Globalization.UnicodeCategory`.
|
||||
* Serialization:
|
||||
* Added a new entity yaml deserialization option (`SerializationOptions.EntityExceptionBehaviour`) that can optionally make deserialization more exception tolerant.
|
||||
* Tooling:
|
||||
* `devwindow` now has a tab listing active `IRenderTarget`s, allowing insight into resource consumption.
|
||||
* `loadgrid` now creates a map if passed an invalid map ID.
|
||||
* Added game version information to F3 overlay.
|
||||
* Added completions to more map commands.
|
||||
* UI system:
|
||||
* `Control.OrderedChildCollection` (gotten from `.Children`) now implements `IReadOnlyList<Control>`, allowing it to be indexed directly.
|
||||
* Added `WrapContainer` control. This lays out multiple elements along an axis, wrapping them if there's not enough space. It comes with many options and can handle multiple axes.
|
||||
* Popups/modals now work in secondary windows. This entails putting roots for these on each UI root.
|
||||
* If you are not using `OSWindow` and are instead creating secondary windows manually, you need to call `WindowRoot.CreateRootControls()` manually for this to work.
|
||||
* Added `Axis` enum, `IAxisImplementation` interface and axis implementations. These allow writing general-purpose UI layout code that can work on multiple axis at once.
|
||||
* WebView:
|
||||
* Added `web.remote_debug_port` CVar to change Chromium's remote debug port.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Audio:
|
||||
* Fix audio occlusion & velocity being calculated with the audio entity instead of the source entity.
|
||||
* Bound UI:
|
||||
* Try to fix an assert related to `UserInterfaceComponent` delta states.
|
||||
* Configuration:
|
||||
* The client no longer tries to send `CLIENT | REPLICATED` CVars when not connected to a server. This could cause test failures.
|
||||
* Math:
|
||||
* Fixed `Matrix3Helpers.TransformBounds()` returning an incorrect result. Now it effectively behaves like `Matrix3Helpers.TransformBox()` and has been marked as obsolete.
|
||||
* Physics:
|
||||
* Work around an undiagnosed crash processing entities without parents.
|
||||
* Serialization:
|
||||
* Fix `[DataRecord]`s with computed get-only properties.
|
||||
* Resources:
|
||||
* Fix some edge case broken path joining in `DirLoader` and `WritableDirProvider`.
|
||||
* Tests:
|
||||
* Fix `PlacementManager.CurrentMousePosition` in integration tests.
|
||||
* UI system:
|
||||
* Animations for the debug console and scrolling are no longer framerate dependent.
|
||||
* Fix `OutputPanel.SetMessage` triggering a scrolling animation when editing messages other than the last one.
|
||||
* Fix word wrapping with two-`char` runes in `RichTextLabel` and `OutputPanel`.
|
||||
* WebView:
|
||||
* Multiple clients with WebView can now run at the same time, thanks to better CEF cache management.
|
||||
|
||||
### Other
|
||||
|
||||
* Audio:
|
||||
* Improved error logging for invalid file names in `SharedAudioSystem`.
|
||||
* Configuration:
|
||||
* Fix crash if more than 255 `REPLICATED` CVars exist. Also increased the max size of the CVar replication message.
|
||||
* Entities:
|
||||
* Transform:
|
||||
* `AnchorEntity` logs instead of using an assert for invalid arguments.
|
||||
* Containers:
|
||||
* `SharedContainerSystem.CleanContainer` now uses `PredictedDel()` instead.
|
||||
* Networking:
|
||||
* The client now logs an error when attempting to send a network message without server connection. Previously, it would be silently dropped.
|
||||
* `net.interp` and `net.buffer_size` CVars are now `REPLICATED`.
|
||||
* Graphics:
|
||||
* The function used for pointlight attenuation has been modified to be c1 continuous as opposed to simply c0 continuous, resulting in smoother boundary behavior.
|
||||
* RSI validator no longer allows empty (`""`) state names.
|
||||
* Packaging:
|
||||
* Server packaging now excludes all files in the `Audio/` directory.
|
||||
* Server packaging now excludes engine resources `EngineFonts/` and `Midi/`.
|
||||
* ACZ explicitly specifies manifest charset as UTF-8.
|
||||
* Serialization:
|
||||
* `CurTime`-relative `TimeSpan` values that are `MaxValue` now deserialize without overflow.
|
||||
* `SpriteSpecifier.Texture` will now fail to validate if the path is inside a `.rsi`. Use RSI sprite specifiers instead.
|
||||
* Resources:
|
||||
* `IWritableDirProvider.RootDir` is now null on clients.
|
||||
* WebView:
|
||||
* CEF cache is no longer in the content-accessible user data directory.
|
||||
|
||||
### Internal
|
||||
|
||||
* Added some debug commands for debugging viewport resource management: `vp_clear_all_cached` & `vp_test_finalize`
|
||||
* `uitest` command now supports command argument for tab selection, like `uitest2`.
|
||||
* Rewrote `BoxContainer` implementation to make use of new axis system.
|
||||
* Moved `uitest2` and `devwindow` to use the `OSWindow` control.
|
||||
* SDL3 binding has been moved to `SpaceWizards.Sdl` NuGet package.
|
||||
* `dmetamem` command has been moved from `DEBUG` to `TOOLS`.
|
||||
* Consolidate `AttachToGridOrMap` with `TryGetMapOrGridCoordinates`.
|
||||
* Secondary window render targets have clear names specified.
|
||||
* Updated `SpaceWizards.NFluidsynth` to `0.2.2`.
|
||||
* `Robust.Client.WebView.Cef.Program` is now internal.
|
||||
* `download_manifest_file.py` script in repo now always decodes as UTF-8 correctly.
|
||||
* Added a new debug assert to game state processing.
|
||||
|
||||
## 267.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* When a player disconnects, the relevant callbacks are now fired *after* removing the channel from `INetManager`.
|
||||
|
||||
### New features
|
||||
|
||||
* Engine builds are now published for ARM64 & FreeBSD.
|
||||
* CPU model names are now detected on Windows & Linux ARM64.
|
||||
* Toolshed's `spawn:in` command now works on entities without `Physics` component.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* SDL3 windowing backend fixes:
|
||||
* Avoid macOS freezes with multiple windows.
|
||||
* Fix macOS rendering breaking when closing secondary windows.
|
||||
* File dialogs properly associate parent windows.
|
||||
* Fix IME positions not working with UI scaling properly.
|
||||
* Properly specify library names for loading native library.
|
||||
|
||||
* WinBit threads don't permanently stay stuck when their window closes.
|
||||
* Checking for the "`null`" literal in serialization is now culture invariant.
|
||||
|
||||
### Other
|
||||
|
||||
* Compat mode on the client now defaults to on for Windows Snapdragon devices, to work around driver bugs.
|
||||
* Update various libraries & natives. This enables out-of-the-box ARM64 support on all platforms and is a long-overdue modernization.
|
||||
* Key name displays now use proper Unicode symbols for macOS ⌥ and ⌘.
|
||||
* Automated CI for RobustToolbox runs on macOS again.
|
||||
* Autocompletions for `ProtoId<T>` in Toolshed now use `PrototypeIdsLimited` instead of arbitrarily cutting out if more than 256 of a prototype exists.
|
||||
|
||||
|
||||
## 266.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
3
Resources/Locale/en-US/_generic.ftl
Normal file
3
Resources/Locale/en-US/_generic.ftl
Normal file
@@ -0,0 +1,3 @@
|
||||
generic-map = map
|
||||
generic-grid = grid
|
||||
generic-mapid = map Id
|
||||
@@ -1,16 +1,16 @@
|
||||
# Loc strings for various entity state & client-side PVS related commands
|
||||
|
||||
cmd-reset-ent-help = Usage: resetent <Entity UID>
|
||||
cmd-reset-ent-desc = Reset an entity to the most recently received server state. This will also reset entities that have been detached to null-space.
|
||||
cmd-reset-ent-help = Usage: {$command} <Entity UID>
|
||||
cmd-reset-ent-desc = Reset an entity to the most recently received server state. This will also reset entities that have been detached to null-space.
|
||||
|
||||
cmd-reset-all-ents-help = Usage: resetallents
|
||||
cmd-reset-all-ents-desc = Resets all entities to the most recently received server state. This only impacts entities that have not been detached to null-space.
|
||||
cmd-reset-all-ents-help = Usage: {$command}
|
||||
cmd-reset-all-ents-desc = Resets all entities to the most recently received server state. This only impacts entities that have not been detached to null-space.
|
||||
|
||||
cmd-detach-ent-help = Usage: detachent <Entity UID>
|
||||
cmd-detach-ent-help = Usage: {$command} <Entity UID>
|
||||
cmd-detach-ent-desc = Detach an entity to null-space, as if it had left PVS range.
|
||||
|
||||
cmd-local-delete-help = Usage: localdelete <Entity UID>
|
||||
cmd-local-delete-help = Usage: {$command} <Entity UID>
|
||||
cmd-local-delete-desc = Deletes an entity. Unlike the normal delete command, this is CLIENT-SIDE. Unless the entity is a client-side entity, this will likely cause errors.
|
||||
|
||||
cmd-full-state-reset-help = Usage: fullstatereset
|
||||
cmd-full-state-reset-help = Usage: {$command}
|
||||
cmd-full-state-reset-desc = Discards any entity state information and requests a full-state from the server.
|
||||
|
||||
@@ -23,8 +23,8 @@ cmd-error-dir-not-found = Could not find directory: {$dir}.
|
||||
cmd-failure-no-attached-entity = There is no entity attached to this shell.
|
||||
|
||||
## 'help' command
|
||||
cmd-help-desc = Display general help or help text for a specific command
|
||||
cmd-help-help = Usage: help [command name]
|
||||
cmd-help-desc = Display general help or help text for a specific command.
|
||||
cmd-help-help = Usage: {$command} [command name]
|
||||
When no command name is provided, displays general-purpose help text. If a command name is provided, displays help text for that command.
|
||||
|
||||
cmd-help-no-args = To display help for a specific command, write 'help <command>'. To list all available commands, write 'list'. To search for commands, use 'list <filter>'.
|
||||
@@ -35,7 +35,7 @@ cmd-help-arg-cmdname = [command name]
|
||||
|
||||
## 'cvar' command
|
||||
cmd-cvar-desc = Gets or sets a CVar.
|
||||
cmd-cvar-help = Usage: cvar <name | ?> [value]
|
||||
cmd-cvar-help = Usage: {$command} <name | ?> [value]
|
||||
If a value is passed, the value is parsed and stored as the new value of the CVar.
|
||||
If not, the current value of the CVar is displayed.
|
||||
Use 'cvar ?' to get a list of all registered CVars.
|
||||
@@ -49,14 +49,14 @@ cmd-cvar-value-hidden = <value hidden>
|
||||
|
||||
## 'cvar_subs' command
|
||||
cmd-cvar_subs-desc = Lists the OnValueChanged subscriptions for a CVar.
|
||||
cmd-cvar_subs-help = Usage: cvar_subs <name>
|
||||
cmd-cvar_subs-help = Usage: {$command} <name>
|
||||
|
||||
cmd-cvar_subs-invalid-args = Must provide exactly one argument.
|
||||
cmd-cvar_subs-arg-name = <name>
|
||||
|
||||
## 'list' command
|
||||
cmd-list-desc = Lists available commands, with optional search filter
|
||||
cmd-list-help = Usage: list [filter]
|
||||
cmd-list-desc = Lists available commands, with optional search filter.
|
||||
cmd-list-help = Usage: {$command} [filter]
|
||||
Lists all available commands. If an argument is provided, it will be used to filter commands by name.
|
||||
|
||||
cmd-list-heading = SIDE NAME DESC{"\u000A"}-------------------------{"\u000A"}
|
||||
@@ -64,13 +64,13 @@ cmd-list-heading = SIDE NAME DESC{"\u000A"}-------------------------{
|
||||
cmd-list-arg-filter = [filter]
|
||||
|
||||
## '>' command, aka remote exec
|
||||
cmd-remoteexec-desc = Executes server-side commands
|
||||
cmd-remoteexec-desc = Executes server-side commands.
|
||||
cmd-remoteexec-help = Usage: > <command> [arg] [arg] [arg...]
|
||||
Executes a command on the server. This is necessary if a command with the same name exists on the client, as simply running the command would run the client command first.
|
||||
|
||||
## 'gc' command
|
||||
cmd-gc-desc = Run the GC (Garbage Collector)
|
||||
cmd-gc-help = Usage: gc [generation]
|
||||
cmd-gc-desc = Run the GC (Garbage Collector).
|
||||
cmd-gc-help = Usage: {$command} [generation]
|
||||
Uses GC.Collect() to execute the Garbage Collector.
|
||||
If an argument is provided, it is parsed as a GC generation number and GC.Collect(int) is used.
|
||||
Use the 'gfc' command to do an LOH-compacting full GC.
|
||||
@@ -79,13 +79,13 @@ cmd-gc-arg-generation = [generation]
|
||||
|
||||
## 'gcf' command
|
||||
cmd-gcf-desc = Run the GC, fully, compacting LOH and everything.
|
||||
cmd-gcf-help = Usage: gcf
|
||||
cmd-gcf-help = Usage: {$command}
|
||||
Does a full GC.Collect(2, GCCollectionMode.Forced, true, true) while also compacting LOH.
|
||||
This will probably lock up for hundreds of milliseconds, be warned.
|
||||
|
||||
## 'gc_mode' command
|
||||
cmd-gc_mode-desc = Change/Read the GC Latency mode
|
||||
cmd-gc_mode-help = Usage: gc_mode [type]
|
||||
cmd-gc_mode-desc = Change/Read the GC Latency mode.
|
||||
cmd-gc_mode-help = Usage: {$command} [type]
|
||||
If no argument is provided, returns the current GC latency mode.
|
||||
If an argument is passed, it is parsed as GCLatencyMode and set as the GC latency mode.
|
||||
|
||||
@@ -98,8 +98,8 @@ cmd-gc_mode-result = resulting gc latency mode: { $mode }
|
||||
cmd-gc_mode-arg-type = [type]
|
||||
|
||||
## 'mem' command
|
||||
cmd-mem-desc = Prints managed memory info
|
||||
cmd-mem-help = Usage: mem
|
||||
cmd-mem-desc = Prints managed memory info.
|
||||
cmd-mem-help = Usage: {$command}
|
||||
|
||||
cmd-mem-report = Heap Size: { TOSTRING($heapSize, "N0") }
|
||||
Total Allocated: { TOSTRING($totalAllocated, "N0") }
|
||||
@@ -108,26 +108,26 @@ cmd-mem-report = Heap Size: { TOSTRING($heapSize, "N0") }
|
||||
cmd-physics-overlay = {$overlay} is not a recognised overlay
|
||||
|
||||
## 'lsasm' command
|
||||
cmd-lsasm-desc = Lists loaded assemblies by load context
|
||||
cmd-lsasm-desc = Lists loaded assemblies by load context.
|
||||
cmd-lsasm-help = Usage: lsasm
|
||||
|
||||
## 'exec' command
|
||||
cmd-exec-desc = Executes a script file from the game's writeable user data
|
||||
cmd-exec-help = Usage: exec <fileName>
|
||||
cmd-exec-desc = Executes a script file from the game's writeable user data.
|
||||
cmd-exec-help = Usage: {$command} <fileName>
|
||||
Each line in the file is executed as a single command, unless it starts with a #
|
||||
|
||||
cmd-exec-arg-filename = <fileName>
|
||||
|
||||
## 'dump_net_comps' command
|
||||
cmd-dump_net_comps-desc = Prints the table of networked components.
|
||||
cmd-dump_net_comps-help = Usage: dump_net-comps
|
||||
cmd-dump_net_comps-help = Usage: {$command}
|
||||
|
||||
cmd-dump_net_comps-error-writeable = Registration still writeable, network ids have not been generated.
|
||||
cmd-dump_net_comps-header = Networked Component Registrations:
|
||||
|
||||
## 'dump_event_tables' command
|
||||
cmd-dump_event_tables-desc = Prints directed event tables for an entity.
|
||||
cmd-dump_event_tables-help = Usage: dump_event_tables <entityUid>
|
||||
cmd-dump_event_tables-help = Usage: {$command} <entityUid>
|
||||
|
||||
cmd-dump_event_tables-missing-arg-entity = Missing entity argument
|
||||
cmd-dump_event_tables-error-entity = Invalid entity
|
||||
@@ -135,7 +135,7 @@ cmd-dump_event_tables-arg-entity = <entityUid>
|
||||
|
||||
## 'monitor' command
|
||||
cmd-monitor-desc = Toggles a debug monitor in the F3 menu.
|
||||
cmd-monitor-help = Usage: monitor <name>
|
||||
cmd-monitor-help = Usage: {$command} <name>
|
||||
Possible monitors are: { $monitors }
|
||||
You can also use the special values "-all" and "+all" to hide or show all monitors, respectively.
|
||||
|
||||
@@ -148,13 +148,13 @@ cmd-monitor-plus-all-hint = Shows all monitors
|
||||
|
||||
## 'setambientlight' command
|
||||
cmd-set-ambient-light-desc = Allows you to set the ambient light for the specified map, in SRGB.
|
||||
cmd-set-ambient-light-help = setambientlight [mapid] [r g b a]
|
||||
cmd-set-ambient-light-help = Usage: {$command} [mapid] [r g b a]
|
||||
cmd-set-ambient-light-parse = Unable to parse args as a byte values for a color.
|
||||
|
||||
## Mapping commands
|
||||
|
||||
cmd-savemap-desc = Serializes a map to disk. Will not save a post-init map unless forced.
|
||||
cmd-savemap-help = savemap <MapID> <Path> [force]
|
||||
cmd-savemap-help = Usage: {$command} <MapID> <Path> [force]
|
||||
cmd-savemap-not-exist = Target map does not exist.
|
||||
cmd-savemap-init-warning = Attempted to save a post-init map without forcing the save.
|
||||
cmd-savemap-attempt = Attempting to save map {$mapId} to {$path}.
|
||||
@@ -165,7 +165,7 @@ cmd-hint-savemap-path = <Path>
|
||||
cmd-hint-savemap-force = [bool]
|
||||
|
||||
cmd-loadmap-desc = Loads a map from disk into the game.
|
||||
cmd-loadmap-help = loadmap <MapID> <Path> [x] [y] [rotation] [consistentUids]
|
||||
cmd-loadmap-help = Usage: {$command} <MapID> <Path> [x] [y] [rotation] [consistentUids]
|
||||
cmd-loadmap-nullspace = You cannot load into map 0.
|
||||
cmd-loadmap-exists = Map {$mapId} already exists.
|
||||
cmd-loadmap-success = Map {$mapId} has been loaded from {$path}.
|
||||
@@ -180,73 +180,74 @@ cmd-hint-savebp-id = <Grid EntityID>
|
||||
## 'flushcookies' command
|
||||
# Note: the flushcookies command is from Robust.Client.WebView, it's not in the main engine code.
|
||||
|
||||
cmd-flushcookies-desc = Flush CEF cookie storage to disk
|
||||
cmd-flushcookies-help = This ensure cookies are properly saved to disk in the event of unclean shutdowns.
|
||||
cmd-flushcookies-desc = Flush CEF cookie storage to disk.
|
||||
cmd-flushcookies-help = Usage: {$command}
|
||||
This ensure cookies are properly saved to disk in the event of unclean shutdowns.
|
||||
Note that the actual operation is asynchronous.
|
||||
|
||||
cmd-ldrsc-desc = Pre-caches a resource.
|
||||
cmd-ldrsc-help = Usage: ldrsc <path> <type>
|
||||
cmd-ldrsc-help = Usage: {$command} <path> <type>
|
||||
|
||||
cmd-rldrsc-desc = Reloads a resource.
|
||||
cmd-rldrsc-help = Usage: rldrsc <path> <type>
|
||||
cmd-rldrsc-help = Usage: {$command} <path> <type>
|
||||
|
||||
cmd-gridtc-desc = Gets the tile count of a grid.
|
||||
cmd-gridtc-help = Usage: gridtc <gridId>
|
||||
cmd-gridtc-help = Usage: {$command} <gridId>
|
||||
|
||||
|
||||
# Client-side commands
|
||||
cmd-guidump-desc = Dump GUI tree to /guidump.txt in user data.
|
||||
cmd-guidump-help = Usage: guidump
|
||||
cmd-guidump-help = Usage: {$command}
|
||||
|
||||
cmd-uitest-desc = Open a dummy UI testing window
|
||||
cmd-uitest-help = Usage: uitest
|
||||
cmd-uitest-desc = Open a dummy UI testing window.
|
||||
cmd-uitest-help = Usage: {$command}
|
||||
|
||||
## 'uitest2' command
|
||||
cmd-uitest2-desc = Opens a UI control testing OS window
|
||||
cmd-uitest2-help = Usage: uitest2 <tab>
|
||||
cmd-uitest2-desc = Opens a UI control testing OS window.
|
||||
cmd-uitest2-help = Usage: {$command} <tab>
|
||||
cmd-uitest2-arg-tab = <tab>
|
||||
cmd-uitest2-error-args = Expected at most one argument
|
||||
cmd-uitest2-error-tab = Invalid tab: '{$value}'
|
||||
cmd-uitest2-title = UITest2
|
||||
|
||||
|
||||
cmd-setclipboard-desc = Sets the system clipboard
|
||||
cmd-setclipboard-help = Usage: setclipboard <text>
|
||||
cmd-setclipboard-desc = Sets the system clipboard.
|
||||
cmd-setclipboard-help = Usage: {$command} <text>
|
||||
|
||||
cmd-getclipboard-desc = Gets the system clipboard
|
||||
cmd-getclipboard-help = Usage: Getclipboard
|
||||
cmd-getclipboard-desc = Gets the system clipboard.
|
||||
cmd-getclipboard-help = Usage: {$command}
|
||||
|
||||
cmd-togglelight-desc = Toggles light rendering.
|
||||
cmd-togglelight-help = Usage: togglelight
|
||||
cmd-togglelight-help = Usage: {$command}
|
||||
|
||||
cmd-togglefov-desc = Toggles fov for client.
|
||||
cmd-togglefov-help = Usage: togglefov
|
||||
cmd-togglefov-help = Usage: {$command}
|
||||
|
||||
cmd-togglehardfov-desc = Toggles hard fov for client. (for debugging space-station-14#2353)
|
||||
cmd-togglehardfov-help = Usage: togglehardfov
|
||||
cmd-togglehardfov-help = Usage: {$command}
|
||||
|
||||
cmd-toggleshadows-desc = Toggles shadow rendering.
|
||||
cmd-toggleshadows-help = Usage: toggleshadows
|
||||
cmd-toggleshadows-help = Usage: {$command}
|
||||
|
||||
cmd-togglelightbuf-desc = Toggles lighting rendering. This includes shadows but not FOV.
|
||||
cmd-togglelightbuf-help = Usage: togglelightbuf
|
||||
cmd-togglelightbuf-help = Usage: {$command}
|
||||
|
||||
cmd-chunkinfo-desc = Gets info about a chunk under your mouse cursor.
|
||||
cmd-chunkinfo-help = Usage: chunkinfo
|
||||
cmd-chunkinfo-help = Usage: {$command}
|
||||
|
||||
cmd-rldshader-desc = Reloads all shaders.
|
||||
cmd-rldshader-help = Usage: rldshader
|
||||
cmd-rldshader-help = Usage: {$command}
|
||||
|
||||
cmd-cldbglyr-desc = Toggle fov and light debug layers.
|
||||
cmd-cldbglyr-help= Usage: cldbglyr <layer>: Toggle <layer>
|
||||
cmd-cldbglyr-help= Usage: {$command} <layer>: Toggle <layer>
|
||||
cldbglyr: Turn all Layers off
|
||||
|
||||
cmd-key-info-desc = Keys key info for a key.
|
||||
cmd-key-info-help = Usage: keyinfo <Key>
|
||||
cmd-key-info-help = Usage: {$command} <Key>
|
||||
|
||||
## 'bind' command
|
||||
cmd-bind-desc = Binds an input key combination to an input command.
|
||||
cmd-bind-help = Usage: bind { cmd-bind-arg-key } { cmd-bind-arg-mode } { cmd-bind-arg-command }
|
||||
cmd-bind-help = Usage: {$command} { cmd-bind-arg-key } { cmd-bind-arg-mode } { cmd-bind-arg-command }
|
||||
Note that this DOES NOT automatically save bindings.
|
||||
Use the 'svbind' command to save binding configuration.
|
||||
|
||||
@@ -255,316 +256,322 @@ cmd-bind-arg-mode = <BindMode>
|
||||
cmd-bind-arg-command = <InputCommand>
|
||||
|
||||
cmd-net-draw-interp-desc = Toggles the debug drawing of the network interpolation.
|
||||
cmd-net-draw-interp-help = Usage: net_draw_interp
|
||||
cmd-net-draw-interp-help = Usage: {$command}
|
||||
|
||||
cmd-net-watch-ent-desc = Dumps all network updates for an EntityId to the console.
|
||||
cmd-net-watch-ent-help = Usage: net_watchent <0|EntityUid>
|
||||
cmd-net-watch-ent-help = Usage: {$command} <0|EntityUid>
|
||||
|
||||
cmd-net-refresh-desc = Requests a full server state.
|
||||
cmd-net-refresh-help = Usage: net_refresh
|
||||
cmd-net-refresh-help = Usage: {$command}
|
||||
|
||||
cmd-net-entity-report-desc = Toggles the net entity report panel.
|
||||
cmd-net-entity-report-help = Usage: net_entityreport
|
||||
cmd-net-entity-report-help = Usage: {$command}
|
||||
|
||||
cmd-fill-desc = Fill up the console for debugging.
|
||||
cmd-fill-help = Fills the console with some nonsense for debugging.
|
||||
cmd-fill-help = Usage: {$command}
|
||||
Fills the console with some nonsense for debugging.
|
||||
|
||||
cmd-cls-desc = Clears the console.
|
||||
cmd-cls-help = Clears the debug console of all messages.
|
||||
cmd-cls-help = Usage: {$command}
|
||||
Clears the debug console of all messages.
|
||||
|
||||
cmd-sendgarbage-desc = Sends garbage to the server.
|
||||
cmd-sendgarbage-help = The server will reply with 'no u'
|
||||
cmd-sendgarbage-help = Usage: {$command}
|
||||
The server will reply with 'no u'
|
||||
|
||||
cmd-loadgrid-desc = Loads a grid from a file into an existing map.
|
||||
cmd-loadgrid-help = loadgrid <MapID> <Path> [x y] [rotation] [storeUids]
|
||||
cmd-loadgrid-help = Usage: {$command} <MapID> <Path> [x y] [rotation] [storeUids]
|
||||
|
||||
cmd-loc-desc = Prints the absolute location of the player's entity to console.
|
||||
cmd-loc-help = loc
|
||||
cmd-loc-help = Usage: {$command}
|
||||
|
||||
cmd-tpgrid-desc = Teleports a grid to a new location.
|
||||
cmd-tpgrid-help = tpgrid <gridId> <X> <Y> [<MapId>]
|
||||
cmd-tpgrid-help = Usage: {$command} <gridId> <X> <Y> [<MapId>]
|
||||
|
||||
cmd-rmgrid-desc = Removes a grid from a map. You cannot remove the default grid.
|
||||
cmd-rmgrid-help = rmgrid <gridId>
|
||||
cmd-rmgrid-help = Usage: {$command} <gridId>
|
||||
|
||||
cmd-mapinit-desc = Runs map init on a map.
|
||||
cmd-mapinit-help = mapinit <mapID>
|
||||
cmd-mapinit-help = Usage: {$command} <mapID>
|
||||
|
||||
cmd-lsmap-desc = Lists maps.
|
||||
cmd-lsmap-help = lsmap
|
||||
cmd-lsmap-help = Usage: {$command}
|
||||
|
||||
cmd-lsgrid-desc = Lists grids.
|
||||
cmd-lsgrid-help = lsgrid
|
||||
cmd-lsgrid-help = Usage: {$command}
|
||||
|
||||
cmd-addmap-desc = Adds a new empty map to the round. If the mapID already exists, this command does nothing.
|
||||
cmd-addmap-help = addmap <mapID> [pre-init]
|
||||
cmd-addmap-help = Usage: {$command} <mapID> [pre-init]
|
||||
|
||||
cmd-rmmap-desc = Removes a map from the world. You cannot remove nullspace.
|
||||
cmd-rmmap-help = rmmap <mapId>
|
||||
cmd-rmmap-help = Usage: {$command} <mapId>
|
||||
|
||||
cmd-savegrid-desc = Serializes a grid to disk.
|
||||
cmd-savegrid-help = savegrid <gridID> <Path>
|
||||
cmd-savegrid-help = Usage: {$command} <gridID> <Path>
|
||||
|
||||
cmd-testbed-desc = Loads a physics testbed on the specified map.
|
||||
cmd-testbed-help = testbed <mapid> <test>
|
||||
cmd-testbed-help = Usage: {$command} <mapid> <test>
|
||||
|
||||
## 'flushcookies' command
|
||||
# Note: the flushcookies command is from Robust.Client.WebView, it's not in the main engine code.
|
||||
|
||||
## 'addcomp' command
|
||||
cmd-addcomp-desc = Adds a component to an entity.
|
||||
cmd-addcomp-help = addcomp <uid> <componentName>
|
||||
cmd-addcomp-help = Usage: {$command} <uid> <componentName>
|
||||
cmd-addcompc-desc = Adds a component to an entity on the client.
|
||||
cmd-addcompc-help = addcompc <uid> <componentName>
|
||||
cmd-addcompc-help = Usage: {$command} <uid> <componentName>
|
||||
|
||||
## 'rmcomp' command
|
||||
cmd-rmcomp-desc = Removes a component from an entity.
|
||||
cmd-rmcomp-help = rmcomp <uid> <componentName>
|
||||
cmd-rmcomp-help = Usage: {$command} <uid> <componentName>
|
||||
cmd-rmcompc-desc = Removes a component from an entity on the client.
|
||||
cmd-rmcompc-help = rmcomp <uid> <componentName>
|
||||
cmd-rmcompc-help = Usage: {$command} <uid> <componentName>
|
||||
|
||||
## 'addview' command
|
||||
cmd-addview-desc = Allows you to subscribe to an entity's view for debugging purposes.
|
||||
cmd-addview-help = addview <entityUid>
|
||||
cmd-addview-help = Usage: {$command} <entityUid>
|
||||
cmd-addviewc-desc = Allows you to subscribe to an entity's view for debugging purposes.
|
||||
cmd-addviewc-help = addview <entityUid>
|
||||
cmd-addviewc-help = Usage: {$command} <entityUid>
|
||||
|
||||
## 'removeview' command
|
||||
cmd-removeview-desc = Allows you to unsubscribe to an entity's view for debugging purposes.
|
||||
cmd-removeview-help = removeview <entityUid>
|
||||
cmd-removeview-help = Usage: {$command} <entityUid>
|
||||
|
||||
## 'loglevel' command
|
||||
cmd-loglevel-desc = Changes the log level for a provided sawmill.
|
||||
cmd-loglevel-help = Usage: loglevel <sawmill> <level>
|
||||
cmd-loglevel-help = Usage: {$command} <sawmill> <level>
|
||||
sawmill: A label prefixing log messages. This is the one you're setting the level for.
|
||||
level: The log level. Must match one of the values of the LogLevel enum.
|
||||
|
||||
cmd-testlog-desc = Writes a test log to a sawmill.
|
||||
cmd-testlog-help = Usage: testlog <sawmill> <level> <message>
|
||||
cmd-testlog-help = Usage: {$command} <sawmill> <level> <message>
|
||||
sawmill: A label prefixing the logged message.
|
||||
level: The log level. Must match one of the values of the LogLevel enum.
|
||||
message: The message to be logged. Wrap this in double quotes if you want to use spaces.
|
||||
|
||||
## 'vv' command
|
||||
cmd-vv-desc = Opens View Variables.
|
||||
cmd-vv-help = Usage: vv <entity ID|IoC interface name|SIoC interface name>
|
||||
cmd-vv-help = Usage: {$command} <entity ID|IoC interface name|SIoC interface name>
|
||||
|
||||
## 'showvelocities' command
|
||||
cmd-showvelocities-desc = Displays your angular and linear velocities.
|
||||
cmd-showvelocities-help = Usage: showvelocities
|
||||
cmd-showvelocities-help = Usage: {$command}
|
||||
|
||||
## 'setinputcontext' command
|
||||
cmd-setinputcontext-desc = Sets the active input context.
|
||||
cmd-setinputcontext-help = Usage: setinputcontext <context>
|
||||
cmd-setinputcontext-help = Usage: {$command} <context>
|
||||
|
||||
## 'forall' command
|
||||
cmd-forall-desc = Runs a command over all entities with a given component.
|
||||
cmd-forall-help = Usage: forall <bql query> do <command...>
|
||||
cmd-forall-help = Usage: {$command} <bql query> do <command...>
|
||||
|
||||
## 'delete' command
|
||||
cmd-delete-desc = Deletes the entity with the specified ID.
|
||||
cmd-delete-help = delete <entity UID>
|
||||
cmd-delete-help = Usage: {$command} <entity UID>
|
||||
|
||||
# System commands
|
||||
cmd-showtime-desc = Shows the server time.
|
||||
cmd-showtime-help = showtime
|
||||
cmd-showtime-help = Usage: {$command}
|
||||
|
||||
cmd-restart-desc = Gracefully restarts the server (not just the round).
|
||||
cmd-restart-help = restart
|
||||
cmd-restart-help = Usage: {$command}
|
||||
|
||||
cmd-shutdown-desc = Gracefully shuts down the server.
|
||||
cmd-shutdown-help = shutdown
|
||||
cmd-shutdown-help = Usage: {$command}
|
||||
|
||||
cmd-saveconfig-desc = Saves the server configuration to the config file.
|
||||
cmd-saveconfig-help = saveconfig
|
||||
cmd-saveconfig-help = Usage: {$command}
|
||||
|
||||
cmd-netaudit-desc = Prints into about NetMsg security.
|
||||
cmd-netaudit-help = netaudit
|
||||
cmd-netaudit-help = Usage: {$command}
|
||||
|
||||
# Player commands
|
||||
cmd-tp-desc = Teleports a player to any location in the round.
|
||||
cmd-tp-help = tp <x> <y> [<mapID>]
|
||||
cmd-tp-help = Usage: {$command} <x> <y> [<mapID>]
|
||||
|
||||
cmd-tpto-desc = Teleports the current player or the specified players/entities to the location of the first player/entity.
|
||||
cmd-tpto-help = tpto <username|uid> [username|NetEntity]...
|
||||
cmd-tpto-help = Usage: {$command} <username|uid> [username|NetEntity]...
|
||||
cmd-tpto-destination-hint = destination (NetEntity or username)
|
||||
cmd-tpto-victim-hint = entity to teleport (NetEntity or username)
|
||||
cmd-tpto-parse-error = Cant resolve entity or player: {$str}
|
||||
|
||||
cmd-listplayers-desc = Lists all players currently connected.
|
||||
cmd-listplayers-help = listplayers
|
||||
cmd-listplayers-help = Usage: {$command}
|
||||
|
||||
cmd-kick-desc = Kicks a connected player out of the server, disconnecting them.
|
||||
cmd-kick-help = kick <PlayerIndex> [<Reason>]
|
||||
cmd-kick-help = Usage: {$command} <PlayerIndex> [<Reason>]
|
||||
|
||||
# Spin command
|
||||
cmd-spin-desc = Causes an entity to spin. Default entity is the attached player's parent.
|
||||
cmd-spin-help = spin velocity [drag] [entityUid]
|
||||
cmd-spin-help = Usage: {$command} velocity [drag] [entityUid]
|
||||
|
||||
# Localization command
|
||||
cmd-rldloc-desc = Reloads localization (client & server).
|
||||
cmd-rldloc-help = Usage: rldloc
|
||||
cmd-rldloc-help = Usage: {$command}
|
||||
|
||||
# Debug entity controls
|
||||
cmd-spawn-desc = Spawns an entity with specific type.
|
||||
cmd-spawn-help = spawn <prototype> OR spawn <prototype> <relative entity ID> OR spawn <prototype> <x> <y>
|
||||
cmd-spawn-help = Usage: {$command} <prototype> | {$command} <prototype> <relative entity ID> | {$command} <prototype> <x> <y>
|
||||
cmd-cspawn-desc = Spawns a client-side entity with specific type at your feet.
|
||||
cmd-cspawn-help = cspawn <entity type>
|
||||
cmd-cspawn-help = Usage: {$command} <entity type>
|
||||
|
||||
cmd-dumpentities-desc = Dump entity list.
|
||||
cmd-dumpentities-help = Dumps entity list of UIDs and prototype.
|
||||
cmd-dumpentities-help = Usage: {$command}
|
||||
Dumps entity list of UIDs and prototype.
|
||||
|
||||
cmd-getcomponentregistration-desc = Gets component registration information.
|
||||
cmd-getcomponentregistration-help = Usage: getcomponentregistration <componentName>
|
||||
cmd-getcomponentregistration-help = Usage: {$command} <componentName>
|
||||
|
||||
cmd-showrays-desc = Toggles debug drawing of physics rays. An integer for <raylifetime> must be provided.
|
||||
cmd-showrays-help = Usage: showrays <raylifetime>
|
||||
cmd-showrays-help = Usage: {$command} <raylifetime>
|
||||
|
||||
cmd-disconnect-desc = Immediately disconnect from the server and go back to the main menu.
|
||||
cmd-disconnect-help = Usage: disconnect
|
||||
cmd-disconnect-help = Usage: {$command}
|
||||
|
||||
cmd-entfo-desc = Displays verbose diagnostics for an entity.
|
||||
cmd-entfo-help = Usage: entfo <entityuid>
|
||||
cmd-entfo-help = Usage: {$command} <entityuid>
|
||||
The entity UID can be prefixed with 'c' to convert it to a client entity UID.
|
||||
|
||||
cmd-fuck-desc = Throws an exception
|
||||
cmd-fuck-help = Usage: fuck
|
||||
cmd-fuck-desc = Throws an exception.
|
||||
cmd-fuck-help = Usage: {$command}
|
||||
|
||||
cmd-showpos-desc = Show the position of all entities on the screen.
|
||||
cmd-showpos-help = Usage: showpos
|
||||
cmd-showpos-help = Usage: {$command}
|
||||
|
||||
cmd-showrot-desc = Show the rotation of all entities on the screen.
|
||||
cmd-showrot-help = Usage: showrot
|
||||
cmd-showrot-help = Usage: {$command}
|
||||
|
||||
cmd-showvel-desc = Show the local velocity of all entites on the screen.
|
||||
cmd-showvel-help = Usage: showvel
|
||||
cmd-showvel-help = Usage: {$command}
|
||||
|
||||
cmd-showangvel-desc = Show the angular velocity of all entities on the screen.
|
||||
cmd-showangvel-help = Usage: showangvel
|
||||
cmd-showangvel-help = Usage: {$command}
|
||||
|
||||
cmd-sggcell-desc = Lists entities on a snap grid cell.
|
||||
cmd-sggcell-help = Usage: sggcell <gridID> <vector2i>\nThat vector2i param is in the form x<int>,y<int>.
|
||||
cmd-sggcell-help = Usage: {$command} <gridID> <vector2i>\nThat vector2i param is in the form x<int>,y<int>.
|
||||
|
||||
cmd-overrideplayername-desc = Changes the name used when attempting to connect to the server.
|
||||
cmd-overrideplayername-help = Usage: overrideplayername <name>
|
||||
cmd-overrideplayername-help = Usage: {$command} <name>
|
||||
|
||||
cmd-showanchored-desc = Shows anchored entities on a particular tile
|
||||
cmd-showanchored-help = Usage: showanchored
|
||||
cmd-showanchored-desc = Shows anchored entities on a particular tile.
|
||||
cmd-showanchored-help = Usage: {$command}
|
||||
|
||||
cmd-dmetamem-desc = Dumps a type's members in a format suitable for the sandbox configuration file.
|
||||
cmd-dmetamem-help = Usage: dmetamem <type>
|
||||
cmd-dmetamem-help = Usage: {$command} <type>
|
||||
|
||||
cmd-launchauth-desc = Load authentication tokens from launcher data to aid in testing of live servers.
|
||||
cmd-launchauth-help = Usage: launchauth <account name>
|
||||
cmd-launchauth-help = Usage: {$command} <account name>
|
||||
|
||||
cmd-lightbb-desc = Toggles whether to show light bounding boxes.
|
||||
cmd-lightbb-help = Usage: lightbb
|
||||
cmd-lightbb-help = Usage: {$command}
|
||||
|
||||
cmd-monitorinfo-desc = Monitors info
|
||||
cmd-monitorinfo-help = Usage: monitorinfo <id>
|
||||
cmd-monitorinfo-desc = Monitors info.
|
||||
cmd-monitorinfo-help = Usage: {$command} <id>
|
||||
|
||||
cmd-setmonitor-desc = Set monitor
|
||||
cmd-setmonitor-help = Usage: setmonitor <id>
|
||||
cmd-setmonitor-desc = Set monitor.
|
||||
cmd-setmonitor-help = Usage: {$command} <id>
|
||||
|
||||
cmd-physics-desc = Shows a debug physics overlay. The arg supplied specifies the overlay.
|
||||
cmd-physics-help = Usage: physics <aabbs / com / contactnormals / contactpoints / distance / joints / shapeinfo / shapes>
|
||||
cmd-physics-help = Usage: {$command} <aabbs / com / contactnormals / contactpoints / distance / joints / shapeinfo / shapes>
|
||||
|
||||
cmd-hardquit-desc = Kills the game client instantly.
|
||||
cmd-hardquit-help = Kills the game client instantly, leaving no traces. No telling the server goodbye.
|
||||
cmd-hardquit-help = Usage: {$command}
|
||||
Kills the game client instantly, leaving no traces. No telling the server goodbye.
|
||||
|
||||
cmd-quit-desc = Shuts down the game client gracefully.
|
||||
cmd-quit-help = Properly shuts down the game client, notifying the connected server and such.
|
||||
cmd-quit-help = Usage: {$command}
|
||||
Properly shuts down the game client, notifying the connected server and such.
|
||||
|
||||
cmd-csi-desc = Opens a C# interactive console.
|
||||
cmd-csi-help = Usage: csi
|
||||
cmd-csi-help = Usage: {$command}
|
||||
|
||||
cmd-scsi-desc = Opens a C# interactive console on the server.
|
||||
cmd-scsi-help = Usage: scsi
|
||||
cmd-scsi-help = Usage: {$command}
|
||||
|
||||
cmd-watch-desc = Opens a variable watch window.
|
||||
cmd-watch-help = Usage: watch
|
||||
cmd-watch-help = Usage: {$command}
|
||||
|
||||
cmd-showspritebb-desc = Toggle whether sprite bounds are shown
|
||||
cmd-showspritebb-help = Usage: showspritebb
|
||||
cmd-showspritebb-desc = Toggle whether sprite bounds are shown.
|
||||
cmd-showspritebb-help = Usage: {$command}
|
||||
|
||||
cmd-togglelookup-desc = Shows / hides entitylookup bounds via an overlay.
|
||||
cmd-togglelookup-help = Usage: togglelookup
|
||||
cmd-togglelookup-help = Usage: {$command}
|
||||
|
||||
cmd-net_entityreport-desc = Toggles the net entity report panel.
|
||||
cmd-net_entityreport-help = Usage: net_entityreport
|
||||
cmd-net_entityreport-help = Usage: {$command}
|
||||
|
||||
cmd-net_refresh-desc = Requests a full server state.
|
||||
cmd-net_refresh-help = Usage: net_refresh
|
||||
cmd-net_refresh-help = Usage: {$command}
|
||||
|
||||
cmd-net_graph-desc = Toggles the net statistics panel.
|
||||
cmd-net_graph-help = Usage: net_graph
|
||||
cmd-net_graph-help = Usage: {$command}
|
||||
|
||||
cmd-net_watchent-desc = Dumps all network updates for an EntityId to the console.
|
||||
cmd-net_watchent-help = Usage: net_watchent <0|EntityUid>
|
||||
cmd-net_watchent-help = Usage: {$command} <0|EntityUid>
|
||||
|
||||
cmd-net_draw_interp-desc = Toggles the debug drawing of the network interpolation.
|
||||
cmd-net_draw_interp-help = Usage: net_draw_interp <0|EntityUid>
|
||||
cmd-net_draw_interp-help = Usage: {$command} <0|EntityUid>
|
||||
|
||||
cmd-vram-desc = Displays video memory usage statics by the game.
|
||||
cmd-vram-help = Usage: vram
|
||||
cmd-vram-help = Usage: {$command}
|
||||
|
||||
cmd-showislands-desc = Shows the current physics bodies involved in each physics island.
|
||||
cmd-showislands-help = Usage: showislands
|
||||
cmd-showislands-help = Usage: {$command}
|
||||
|
||||
cmd-showgridnodes-desc = Shows the nodes for grid split purposes.
|
||||
cmd-showgridnodes-help = Usage: showgridnodes
|
||||
cmd-showgridnodes-help = Usage: {$command}
|
||||
|
||||
cmd-profsnap-desc = Make a profiling snapshot.
|
||||
cmd-profsnap-help = Usage: profsnap
|
||||
cmd-profsnap-help = Usage: {$command}
|
||||
|
||||
cmd-devwindow-desc = Dev Window
|
||||
cmd-devwindow-help = Usage: devwindow
|
||||
cmd-devwindow-desc = Dev Window.
|
||||
cmd-devwindow-help = Usage: {$command}
|
||||
|
||||
cmd-scene-desc = Immediately changes the UI scene/state.
|
||||
cmd-scene-help = Usage: scene <className>
|
||||
cmd-scene-help = Usage: {$command} <className>
|
||||
|
||||
cmd-szr_stats-desc = Report serializer statistics.
|
||||
cmd-szr_stats-help = Usage: szr_stats
|
||||
cmd-szr_stats-help = Usage: {$command}
|
||||
|
||||
cmd-hwid-desc = Returns the current HWID (HardWare ID).
|
||||
cmd-hwid-help = Usage: hwid
|
||||
cmd-hwid-help = Usage: {$command}
|
||||
|
||||
cmd-vvread-desc = Retrieve a path's value using VV (View Variables).
|
||||
cmd-vvread-help = Usage: vvread <path>
|
||||
cmd-vvread-help = Usage: {$command} <path>
|
||||
|
||||
cmd-vvwrite-desc = Modify a path's value using VV (View Variables).
|
||||
cmd-vvwrite-help = Usage: vvwrite <path>
|
||||
cmd-vvwrite-help = Usage: {$command} <path>
|
||||
|
||||
cmd-vvinvoke-desc = Invoke/Call a path with arguments using VV.
|
||||
cmd-vvinvoke-help = Usage: vvinvoke <path> [arguments...]
|
||||
cmd-vvinvoke-help = Usage: {$command} <path> [arguments...]
|
||||
|
||||
cmd-dump_dependency_injectors-desc = Dump IoCManager's dependency injector cache.
|
||||
cmd-dump_dependency_injectors-help = Usage: dump_dependency_injectors
|
||||
cmd-dump_dependency_injectors-help = Usage: {$command}
|
||||
cmd-dump_dependency_injectors-total-count = Total count: { $total }
|
||||
|
||||
cmd-dump_netserializer_type_map-desc = Dump NetSerializer's type map and serializer hash.
|
||||
cmd-dump_netserializer_type_map-help = Usage: dump_netserializer_type_map
|
||||
cmd-dump_netserializer_type_map-help = Usage: {$command}
|
||||
|
||||
cmd-hub_advertise_now-desc = Immediately advertise to the master hub server
|
||||
cmd-hub_advertise_now-help = Usage: hub_advertise_now
|
||||
cmd-hub_advertise_now-desc = Immediately advertise to the master hub server.
|
||||
cmd-hub_advertise_now-help = Usage: {$command}
|
||||
|
||||
cmd-echo-desc = Echo arguments back to the console
|
||||
cmd-echo-help = Usage: echo "<message>"
|
||||
cmd-echo-desc = Echo arguments back to the console.
|
||||
cmd-echo-help = Usage: {$command} "<message>"
|
||||
|
||||
## 'vfs_ls' command
|
||||
cmd-vfs_ls-desc = List directory contents in the VFS.
|
||||
cmd-vfs_ls-help = Usage: vfs_list <path>
|
||||
cmd-vfs_ls-help = Usage: {$command} <path>
|
||||
Example:
|
||||
vfs_list /Assemblies
|
||||
|
||||
cmd-vfs_ls-err-args = Need exactly 1 argument.
|
||||
cmd-vfs_ls-hint-path = <path>
|
||||
|
||||
cmd-reloadtiletextures-desc = Reloads the tile texture atlas to allow hot reloading tile sprites
|
||||
cmd-reloadtiletextures-help = Usage: reloadtiletextures
|
||||
cmd-reloadtiletextures-desc = Reloads the tile texture atlas to allow hot reloading tile sprites.
|
||||
cmd-reloadtiletextures-help = Usage: {$command}
|
||||
|
||||
cmd-audio_length-desc = Shows the length of an audio file
|
||||
cmd-audio_length-help = Usage: audio_length { cmd-audio_length-arg-file-name }
|
||||
cmd-audio_length-help = Usage: {$command} { cmd-audio_length-arg-file-name }
|
||||
cmd-audio_length-arg-file-name = <file name>
|
||||
|
||||
## PVS
|
||||
@@ -573,7 +580,9 @@ cmd-pvs-override-info-empty = Entity {$nuid} has no PVS overrides.
|
||||
cmd-pvs-override-info-global = Entity {$nuid} has a global override.
|
||||
cmd-pvs-override-info-clients = Entity {$nuid} has a session override for {$clients}.
|
||||
|
||||
cmd-localization_set_culture-desc = Set DefaultCulture for the client LocalizationManager
|
||||
cmd-localization_set_culture-help = Usage: localization_set_culture <cultureName>
|
||||
cmd-localization_set_culture-desc = Set DefaultCulture for the client LocalizationManager.
|
||||
cmd-localization_set_culture-help = Usage: {$command} <cultureName>
|
||||
cmd-localization_set_culture-culture-name = <cultureName>
|
||||
cmd-localization_set_culture-changed = Localization changed to { $code } ({ $nativeName } / { $englishName })
|
||||
|
||||
cmd-addmap-hint-2 = runMapInit [true / false]
|
||||
|
||||
@@ -8,3 +8,18 @@ dev-window-tab-textures-info = Width: { $width } Height: { $height }
|
||||
PixelType: { $pixelType } sRGB: { $srgb }
|
||||
Name: { $name }
|
||||
Est. memory usage: { $bytes }
|
||||
|
||||
## "Render Targets" dev window tab
|
||||
dev-window-tab-render-targets-title = Render Targets
|
||||
dev-window-tab-render-targets-reload = Reload
|
||||
dev-window-tab-render-targets-filter = Filter
|
||||
dev-window-tab-render-targets-column-id = ID
|
||||
dev-window-tab-render-targets-column-name = Name
|
||||
dev-window-tab-render-targets-column-size = Size
|
||||
dev-window-tab-render-targets-column-type = Type
|
||||
dev-window-tab-render-targets-column-vram = VRAM
|
||||
dev-window-tab-render-targets-column-thumbnail = Thumbnail
|
||||
|
||||
dev-window-tab-render-targets-value-null = null
|
||||
dev-window-tab-render-targets-value-not-available = Not available
|
||||
dev-window-tab-render-targets-summary = Total VRAM: { $vram }
|
||||
|
||||
@@ -2,6 +2,7 @@ input-key-Escape = Escape
|
||||
input-key-Control = Control
|
||||
input-key-Shift = Shift
|
||||
input-key-Alt = Alt
|
||||
input-key-Alt-mac = ⌥
|
||||
input-key-Menu = Menu
|
||||
input-key-F1 = F1
|
||||
input-key-F2 = F2
|
||||
@@ -70,8 +71,8 @@ input-key-MouseButton9 = Mouse 9
|
||||
|
||||
input-key-LSystem-win = Left Win
|
||||
input-key-RSystem-win = Right Win
|
||||
input-key-LSystem-mac = Left Cmd
|
||||
input-key-RSystem-mac = Right Cmd
|
||||
input-key-LSystem-mac = Left ⌘
|
||||
input-key-RSystem-mac = Right ⌘
|
||||
input-key-LSystem-linux = Left Meta
|
||||
input-key-RSystem-linux = Right Meta
|
||||
|
||||
|
||||
@@ -428,3 +428,7 @@ command-description-cmd-info =
|
||||
On its own, this means it'll print the command's help message.
|
||||
command-description-comp-rm =
|
||||
Removes the given component from the entity.
|
||||
|
||||
command-description-overlay-toggle = Toggle an overlay on or off
|
||||
command-description-overlay-add = Add an overlay (if it does not already exist)
|
||||
command-description-overlay-remove = Remove an overlay
|
||||
|
||||
@@ -14,6 +14,8 @@ uniform highp vec2 lightCenter;
|
||||
uniform highp float lightRange;
|
||||
uniform highp float lightPower;
|
||||
uniform highp float lightSoftness;
|
||||
uniform highp float lightFalloff;
|
||||
uniform highp float lightCurveFactor;
|
||||
uniform highp float lightIndex;
|
||||
uniform sampler2D shadowMap;
|
||||
|
||||
@@ -47,8 +49,15 @@ void fragment()
|
||||
discard;
|
||||
}
|
||||
|
||||
highp float dist = dot(diff, diff) + LIGHTING_HEIGHT;
|
||||
highp float val = clamp((1.0 - clamp(sqrt(dist) / lightRange, 0.0, 1.0)) * (1.0 / (sqrt(dist + 1.0))), 0.0, 1.0);
|
||||
// this implementation of light attenuation primarily adapted from
|
||||
// https://lisyarus.github.io/blog/posts/point-light-attenuation.html
|
||||
highp float sqr_dist = dot(diff, diff) + LIGHTING_HEIGHT;
|
||||
|
||||
highp float s = clamp(sqrt(sqr_dist) / lightRange, 0.0, 1.0);
|
||||
highp float s2 = s * s;
|
||||
// controls curve by lerping between two variants (inverse-shape and inversequadratic-shape)
|
||||
highp float curveFactor = mix(s, s2, clamp(lightCurveFactor, 0.0, 1.0));
|
||||
highp float val = clamp(((1.0 - s2) * (1.0 - s2)) / (1.0 + lightFalloff * curveFactor), 0.0, 1.0);
|
||||
|
||||
val *= lightPower;
|
||||
val *= mask;
|
||||
|
||||
@@ -6,7 +6,7 @@ using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.WebView.Cef
|
||||
{
|
||||
public static class Program
|
||||
internal static class Program
|
||||
{
|
||||
// This was supposed to be the main entry for the subprocess program... It doesn't work.
|
||||
public static int Main(string[] args)
|
||||
|
||||
51
Robust.Client.WebView/Cef/WebViewManagerCef.Lock.cs
Normal file
51
Robust.Client.WebView/Cef/WebViewManagerCef.Lock.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Robust.Client.Utility;
|
||||
|
||||
namespace Robust.Client.WebView.Cef;
|
||||
|
||||
internal sealed partial class WebViewManagerCef
|
||||
{
|
||||
private const string BaseCacheName = "cef_cache";
|
||||
private const string LockFileName = "robust.lock";
|
||||
private FileStream? _lockFileStream;
|
||||
private const int MaxAttempts = 15; // This probably shouldn't be a cvar because the only reason you'd need it change for legit just botting the game.
|
||||
|
||||
private string FindAndLockCacheDirectory()
|
||||
{
|
||||
var rootDir = Path.Combine(UserDataDir.GetRootUserDataDir(_gameController), BaseCacheName);
|
||||
|
||||
for (var i = 0; i < MaxAttempts; i++)
|
||||
{
|
||||
var cacheDirPath = Path.Combine(rootDir, i.ToString());
|
||||
|
||||
if (TryLockCacheDir(i, cacheDirPath))
|
||||
return cacheDirPath;
|
||||
}
|
||||
|
||||
throw new Exception("Unable to locate available CEF cache directory!");
|
||||
}
|
||||
|
||||
private bool TryLockCacheDir(int attempt, string path)
|
||||
{
|
||||
_sawmill.Verbose($"Trying to lock cache directory {attempt}");
|
||||
|
||||
// Does not fail if directory already exists.
|
||||
Directory.CreateDirectory(path);
|
||||
|
||||
var lockFilePath = Path.Combine(path, LockFileName);
|
||||
|
||||
try
|
||||
{
|
||||
var file = File.Open(lockFilePath, FileMode.Create, FileAccess.ReadWrite, FileShare.None);
|
||||
_lockFileStream = file;
|
||||
_sawmill.Debug($"Successfully locked CEF cache directory {attempt}");
|
||||
return true;
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
_sawmill.Error($"Failed to lock cache directory {attempt}: {ex}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -24,6 +25,7 @@ namespace Robust.Client.WebView.Cef
|
||||
|
||||
[Dependency] private readonly IDependencyCollection _dependencyCollection = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IGameControllerInternal _gameController = default!;
|
||||
[Dependency] private readonly IResourceManagerInternal _resourceManager = default!;
|
||||
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
@@ -59,9 +61,9 @@ namespace Robust.Client.WebView.Cef
|
||||
if (cefResourcesPath == null)
|
||||
throw new InvalidOperationException("Unable to locate cef_resources directory!");
|
||||
|
||||
var cachePath = "";
|
||||
if (_resourceManager.UserData is WritableDirProvider userData)
|
||||
cachePath = userData.GetFullPath(new ResPath("/cef_cache"));
|
||||
var remoteDebugPort = _cfg.GetCVar(WCVars.WebRemoteDebugPort);
|
||||
|
||||
var cachePath = FindAndLockCacheDirectory();
|
||||
|
||||
var settings = new CefSettings()
|
||||
{
|
||||
@@ -71,7 +73,7 @@ namespace Robust.Client.WebView.Cef
|
||||
BrowserSubprocessPath = subProcessPath,
|
||||
LocalesDirPath = Path.Combine(cefResourcesPath, "locales"),
|
||||
ResourcesDirPath = cefResourcesPath,
|
||||
RemoteDebuggingPort = 9222,
|
||||
RemoteDebuggingPort = remoteDebugPort,
|
||||
CookieableSchemesList = "usr,res",
|
||||
CachePath = cachePath,
|
||||
};
|
||||
|
||||
@@ -26,4 +26,16 @@ public static class WCVars
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> WebHeadless =
|
||||
CVarDef.Create("web.headless", false, CVar.CLIENTONLY);
|
||||
|
||||
#if TOOLS
|
||||
private const int DefaultRemoteDebugPort = 9222;
|
||||
#else
|
||||
private const int DefaultRemoteDebugPort = 0;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// If not 0, the port number used for Chromium's remote debugging.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> WebRemoteDebugPort =
|
||||
CVarDef.Create("web.remote_debug_port", DefaultRemoteDebugPort, CVar.CLIENTONLY);
|
||||
}
|
||||
|
||||
@@ -54,8 +54,14 @@ namespace Robust.Client.Animations
|
||||
}
|
||||
else
|
||||
{
|
||||
var next = KeyFrames[nextKeyFrame];
|
||||
|
||||
// Get us a scale 0 -> 1 here.
|
||||
var t = playingTime / KeyFrames[nextKeyFrame].KeyTime;
|
||||
var t = playingTime / next.KeyTime;
|
||||
|
||||
// Apply easing to time parameter, if one was specified
|
||||
if (next.Easing != null)
|
||||
t = next.Easing(t);
|
||||
|
||||
switch (InterpolationMode)
|
||||
{
|
||||
@@ -147,10 +153,20 @@ namespace Robust.Client.Animations
|
||||
/// </summary>
|
||||
public readonly float KeyTime;
|
||||
|
||||
public KeyFrame(object value, float keyTime)
|
||||
/// <summary>
|
||||
/// An easing function to apply when interpolating to this keyframe's value.
|
||||
/// Modifies the time parameter (0..1) of the interpolation between the previous keyframe and this one.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See <see cref="Easings"/> for examples of easing functions, or provide your own.
|
||||
/// </remarks>
|
||||
public readonly Func<float, float>? Easing;
|
||||
|
||||
public KeyFrame(object value, float keyTime, Func<float, float>? easing = null)
|
||||
{
|
||||
Value = value;
|
||||
KeyTime = keyTime;
|
||||
Easing = easing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using OpenTK.Audio.OpenAL;
|
||||
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
|
||||
using Robust.Client.Audio.Sources;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared;
|
||||
@@ -145,7 +144,7 @@ internal sealed partial class AudioManager : IAudioInternal
|
||||
private static void RemoveEfx((int sourceHandle, int filterHandle) handles)
|
||||
{
|
||||
if (handles.filterHandle != 0)
|
||||
EFX.DeleteFilter(handles.filterHandle);
|
||||
ALC.EFX.DeleteFilter(handles.filterHandle);
|
||||
}
|
||||
|
||||
private void _checkAlcError(ALDevice device,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
|
||||
using Robust.Client.Audio.Effects;
|
||||
using Robust.Shared.Audio.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
@@ -372,13 +372,13 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
return;
|
||||
}
|
||||
|
||||
var parentUid = xform.ParentUid;
|
||||
Vector2 worldPos;
|
||||
component.Volume = component.Params.Volume;
|
||||
|
||||
// Handle grid audio differently by using grid position.
|
||||
if ((component.Flags & AudioFlags.GridAudio) != 0x0)
|
||||
{
|
||||
var parentUid = xform.ParentUid;
|
||||
worldPos = _maps.GetGridPosition(parentUid);
|
||||
}
|
||||
else
|
||||
@@ -412,7 +412,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
}
|
||||
else
|
||||
{
|
||||
var occlusion = GetOcclusion(listener, delta, distance, entity);
|
||||
var occlusion = GetOcclusion(listener, delta, distance, parentUid);
|
||||
component.Occlusion = occlusion;
|
||||
}
|
||||
|
||||
@@ -420,11 +420,11 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
component.Position = worldPos;
|
||||
|
||||
// Make race cars go NYYEEOOOOOMMMMM
|
||||
if (_physicsQuery.TryGetComponent(entity, out var physicsComp))
|
||||
if (_physicsQuery.TryGetComponent(parentUid, out var physicsComp))
|
||||
{
|
||||
// This actually gets the tracked entity's xform & iterates up though the parents for the second time. Bit
|
||||
// inefficient.
|
||||
var velocity = _physics.GetMapLinearVelocity(entity, physicsComp, xform);
|
||||
var velocity = _physics.GetMapLinearVelocity(parentUid, physicsComp);
|
||||
component.Velocity = velocity;
|
||||
}
|
||||
}
|
||||
@@ -589,6 +589,11 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
var playing = CreateAndStartPlayingStream(audioParams, specifier, stream);
|
||||
_xformSys.SetCoordinates(playing.Entity, new EntityCoordinates(entity, Vector2.Zero));
|
||||
|
||||
// Since we're playing the sound immediately in the middle of a tick, we need to force ProcessStream -now-
|
||||
// to set occlusion/position/velocity etc
|
||||
// otherwise predicted positional sounds will sound very incorrect in several possible ways (e#5802, e#6175) until the next tick
|
||||
ProcessStream(playing.Entity, playing.Component, Transform(playing.Entity), GetListenerCoordinates());
|
||||
|
||||
return playing;
|
||||
}
|
||||
|
||||
@@ -632,6 +637,10 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
|
||||
var playing = CreateAndStartPlayingStream(audioParams, specifier, stream);
|
||||
_xformSys.SetCoordinates(playing.Entity, coordinates);
|
||||
|
||||
// see PlayEntity for why this is necessary
|
||||
ProcessStream(playing.Entity, playing.Component, Transform(playing.Entity), GetListenerCoordinates());
|
||||
|
||||
return playing;
|
||||
}
|
||||
|
||||
@@ -714,8 +723,6 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
offset = Math.Clamp(offset, 0f, maxOffset);
|
||||
source.PlaybackPosition = offset;
|
||||
|
||||
// For server we will rely on the adjusted one but locally we will have to adjust it ourselves.
|
||||
ApplyAudioParams(comp.Params, comp);
|
||||
source.StartPlaying();
|
||||
return (entity, comp);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
|
||||
using OpenTK.Audio.OpenAL;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Effects;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -16,16 +16,16 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
|
||||
public AudioEffect(IAudioInternal manager)
|
||||
{
|
||||
Handle = EFX.GenEffect();
|
||||
Handle = ALC.EFX.GenEffect();
|
||||
_master = manager;
|
||||
EFX.Effect(Handle, EffectInteger.EffectType, (int) EffectType.EaxReverb);
|
||||
ALC.EFX.Effect(Handle, EffectInteger.EffectType, (int) EffectType.EaxReverb);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Handle != 0)
|
||||
{
|
||||
EFX.DeleteEffect(Handle);
|
||||
ALC.EFX.DeleteEffect(Handle);
|
||||
Handle = 0;
|
||||
}
|
||||
}
|
||||
@@ -44,14 +44,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbDensity, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbDensity, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbDensity, value);
|
||||
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbDensity, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -62,14 +62,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbDiffusion, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbDiffusion, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbDiffusion, value);
|
||||
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbDiffusion, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -80,14 +80,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbGain, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbGain, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbGain, value);
|
||||
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbGain, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -98,14 +98,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbGainHF, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbGainHF, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbGainHF, value);
|
||||
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbGainHF, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -116,14 +116,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbGainLF, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbGainLF, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbGainLF, value);
|
||||
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbGainLF, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -134,14 +134,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbDecayTime, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbDecayTime, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbDecayTime, value);
|
||||
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbDecayTime, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -152,14 +152,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbDecayHFRatio, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbDecayHFRatio, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbDecayHFRatio, value);
|
||||
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbDecayHFRatio, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -170,14 +170,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbDecayLFRatio, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbDecayLFRatio, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbDecayLFRatio, value);
|
||||
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbDecayLFRatio, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -188,14 +188,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbReflectionsGain, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbReflectionsGain, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbReflectionsGain, value);
|
||||
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbReflectionsGain, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -206,14 +206,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbReflectionsDelay, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbReflectionsDelay, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbReflectionsDelay, value);
|
||||
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbReflectionsDelay, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -224,7 +224,7 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
var value = EFX.GetEffect(Handle, EffectVector3.EaxReverbReflectionsPan);
|
||||
var value = ALC.EFX.GetEffect(Handle, EffectVector3.EaxReverbReflectionsPan);
|
||||
_master._checkAlError();
|
||||
return new Vector3(value.X, value.Z, value.Y);
|
||||
}
|
||||
@@ -232,7 +232,7 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
{
|
||||
_checkDisposed();
|
||||
var openVec = new OpenTK.Mathematics.Vector3(value.X, value.Y, value.Z);
|
||||
EFX.Effect(Handle, EffectVector3.EaxReverbReflectionsPan, ref openVec);
|
||||
ALC.EFX.Effect(Handle, EffectVector3.EaxReverbReflectionsPan, ref openVec);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -243,14 +243,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbLateReverbGain, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbLateReverbGain, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbLateReverbGain, value);
|
||||
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbLateReverbGain, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -261,14 +261,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbLateReverbDelay, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbLateReverbDelay, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbLateReverbDelay, value);
|
||||
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbLateReverbDelay, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -279,7 +279,7 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
var value = EFX.GetEffect(Handle, EffectVector3.EaxReverbLateReverbPan);
|
||||
var value = ALC.EFX.GetEffect(Handle, EffectVector3.EaxReverbLateReverbPan);
|
||||
_master._checkAlError();
|
||||
return new Vector3(value.X, value.Z, value.Y);
|
||||
}
|
||||
@@ -287,7 +287,7 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
{
|
||||
_checkDisposed();
|
||||
var openVec = new OpenTK.Mathematics.Vector3(value.X, value.Y, value.Z);
|
||||
EFX.Effect(Handle, EffectVector3.EaxReverbLateReverbPan, ref openVec);
|
||||
ALC.EFX.Effect(Handle, EffectVector3.EaxReverbLateReverbPan, ref openVec);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -298,14 +298,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbEchoTime, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbEchoTime, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbEchoTime, value);
|
||||
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbEchoTime, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -316,14 +316,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbEchoDepth, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbEchoDepth, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbEchoDepth, value);
|
||||
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbEchoDepth, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -334,14 +334,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbModulationTime, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbModulationTime, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbModulationTime, value);
|
||||
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbModulationTime, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -352,14 +352,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbModulationDepth, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbModulationDepth, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbModulationDepth, value);
|
||||
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbModulationDepth, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -370,14 +370,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbAirAbsorptionGainHF, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbAirAbsorptionGainHF, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbAirAbsorptionGainHF, value);
|
||||
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbAirAbsorptionGainHF, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -388,14 +388,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbHFReference, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbHFReference, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbHFReference, value);
|
||||
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbHFReference, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -406,14 +406,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbLFReference, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbLFReference, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbLFReference, value);
|
||||
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbLFReference, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -424,14 +424,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbRoomRolloffFactor, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbRoomRolloffFactor, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbRoomRolloffFactor, value);
|
||||
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbRoomRolloffFactor, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -442,14 +442,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectInteger.EaxReverbDecayHFLimit, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectInteger.EaxReverbDecayHFLimit, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectInteger.EaxReverbDecayHFLimit, value);
|
||||
ALC.EFX.Effect(Handle, EffectInteger.EaxReverbDecayHFLimit, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
|
||||
using OpenTK.Audio.OpenAL;
|
||||
using Robust.Shared.Audio.Effects;
|
||||
|
||||
namespace Robust.Client.Audio.Effects;
|
||||
@@ -6,13 +6,13 @@ namespace Robust.Client.Audio.Effects;
|
||||
/// <inheritdoc />
|
||||
internal sealed class AuxiliaryAudio : IAuxiliaryAudio
|
||||
{
|
||||
internal int Handle = EFX.GenAuxiliaryEffectSlot();
|
||||
internal int Handle = ALC.EFX.GenAuxiliaryEffectSlot();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Handle != -1)
|
||||
{
|
||||
EFX.DeleteAuxiliaryEffectSlot(Handle);
|
||||
ALC.EFX.DeleteAuxiliaryEffectSlot(Handle);
|
||||
Handle = -1;
|
||||
}
|
||||
}
|
||||
@@ -22,11 +22,11 @@ internal sealed class AuxiliaryAudio : IAuxiliaryAudio
|
||||
{
|
||||
if (effect is AudioEffect audEffect)
|
||||
{
|
||||
EFX.AuxiliaryEffectSlot(Handle, EffectSlotInteger.Effect, audEffect.Handle);
|
||||
ALC.EFX.AuxiliaryEffectSlot(Handle, EffectSlotInteger.Effect, audEffect.Handle);
|
||||
}
|
||||
else
|
||||
{
|
||||
EFX.AuxiliaryEffectSlot(Handle, EffectSlotInteger.Effect, 0);
|
||||
ALC.EFX.AuxiliaryEffectSlot(Handle, EffectSlotInteger.Effect, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using OpenTK.Audio.OpenAL;
|
||||
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -77,7 +76,7 @@ internal sealed class AudioSource : BaseAudioSource
|
||||
else
|
||||
{
|
||||
if (FilterHandle != 0)
|
||||
EFX.DeleteFilter(FilterHandle);
|
||||
ALC.EFX.DeleteFilter(FilterHandle);
|
||||
|
||||
AL.DeleteSource(SourceHandle);
|
||||
Master.RemoveAudioSource(SourceHandle);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using OpenTK.Audio.OpenAL;
|
||||
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
|
||||
using Robust.Client.Audio.Effects;
|
||||
using Robust.Shared.Audio.Effects;
|
||||
using Robust.Shared.Audio.Sources;
|
||||
@@ -82,9 +81,9 @@ public abstract class BaseAudioSource : IAudioSource
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
var state = AL.GetSourceState(SourceHandle);
|
||||
var state = AL.GetSource(SourceHandle, ALGetSourcei.SourceState);
|
||||
Master._checkAlError();
|
||||
return state == ALSourceState.Playing;
|
||||
return state == (int)ALSourceState.Playing;
|
||||
}
|
||||
set
|
||||
{
|
||||
@@ -362,11 +361,11 @@ public abstract class BaseAudioSource : IAudioSource
|
||||
|
||||
if (audio is AuxiliaryAudio impAudio)
|
||||
{
|
||||
EFX.Source(SourceHandle, EFXSourceInteger3.AuxiliarySendFilter, impAudio.Handle, 0, 0);
|
||||
ALC.EFX.Source(SourceHandle, EFXSourceInteger3.AuxiliarySendFilter, impAudio.Handle, 0, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
EFX.Source(SourceHandle, EFXSourceInteger3.AuxiliarySendFilter, 0, 0, 0);
|
||||
ALC.EFX.Source(SourceHandle, EFXSourceInteger3.AuxiliarySendFilter, 0, 0, 0);
|
||||
}
|
||||
|
||||
Master._checkAlError();
|
||||
@@ -376,12 +375,12 @@ public abstract class BaseAudioSource : IAudioSource
|
||||
{
|
||||
if (FilterHandle == 0)
|
||||
{
|
||||
FilterHandle = EFX.GenFilter();
|
||||
EFX.Filter(FilterHandle, FilterInteger.FilterType, (int) FilterType.Lowpass);
|
||||
FilterHandle = ALC.EFX.GenFilter();
|
||||
ALC.EFX.Filter(FilterHandle, FilterInteger.FilterType, (int) FilterType.Lowpass);
|
||||
}
|
||||
|
||||
EFX.Filter(FilterHandle, FilterFloat.LowpassGain, gain);
|
||||
EFX.Filter(FilterHandle, FilterFloat.LowpassGainHF, cutoff);
|
||||
ALC.EFX.Filter(FilterHandle, FilterFloat.LowpassGain, gain);
|
||||
ALC.EFX.Filter(FilterHandle, FilterFloat.LowpassGainHF, cutoff);
|
||||
AL.Source(SourceHandle, ALSourcei.EfxDirectFilter, FilterHandle);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenTK.Audio.OpenAL;
|
||||
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
|
||||
using Robust.Shared.Audio.Sources;
|
||||
|
||||
namespace Robust.Client.Audio.Sources;
|
||||
@@ -37,9 +36,9 @@ internal sealed class BufferedAudioSource : BaseAudioSource, IBufferedAudioSourc
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
var state = AL.GetSourceState(SourceHandle);
|
||||
var state = AL.GetSource(SourceHandle, ALGetSourcei.SourceState);
|
||||
_master._checkAlError();
|
||||
return state == ALSourceState.Playing;
|
||||
return state == (int)ALSourceState.Playing;
|
||||
}
|
||||
set
|
||||
{
|
||||
@@ -84,7 +83,7 @@ internal sealed class BufferedAudioSource : BaseAudioSource, IBufferedAudioSourc
|
||||
else
|
||||
{
|
||||
if (FilterHandle != 0)
|
||||
EFX.DeleteFilter(FilterHandle);
|
||||
ALC.EFX.DeleteFilter(FilterHandle);
|
||||
|
||||
AL.DeleteSource(SourceHandle);
|
||||
AL.DeleteBuffers(BufferHandles);
|
||||
|
||||
@@ -31,8 +31,7 @@ public sealed class LightTreeSystem : ComponentTreeSystem<LightTreeComponent, Po
|
||||
|
||||
var pos = XformSystem.GetRelativePosition(
|
||||
entry.Transform,
|
||||
entry.Component.TreeUid.Value,
|
||||
GetEntityQuery<TransformComponent>());
|
||||
entry.Component.TreeUid.Value);
|
||||
|
||||
return ExtractAabb(in entry, pos, default);
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ internal sealed class ClientNetConfigurationManager : NetConfigurationManager, I
|
||||
// Actually set the CVar
|
||||
base.SetCVar(name, value, force);
|
||||
|
||||
if ((flags & CVar.REPLICATED) == 0)
|
||||
if ((flags & CVar.REPLICATED) == 0 || !NetManager.IsConnected)
|
||||
return;
|
||||
|
||||
var msg = new MsgConVars();
|
||||
|
||||
@@ -4,7 +4,7 @@ using Robust.Shared.ContentPack;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
#if DEBUG
|
||||
#if TOOLS
|
||||
internal sealed class DumpMetadataMembersCommand : LocalizedCommands
|
||||
{
|
||||
public override string Command => "dmetamem";
|
||||
|
||||
124
Robust.Client/Console/Commands/UITestCommand.TabWrapContainer.cs
Normal file
124
Robust.Client/Console/Commands/UITestCommand.TabWrapContainer.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
using System;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Maths;
|
||||
using ItemJustification = Robust.Client.UserInterface.Controls.WrapContainer.ItemJustification;
|
||||
|
||||
namespace Robust.Client.Console.Commands;
|
||||
|
||||
internal sealed partial class UITestControl
|
||||
{
|
||||
private sealed class TabWrapContainer : Control
|
||||
{
|
||||
private readonly CheckBox _equalSizeBox;
|
||||
private readonly CheckBox _reverseBox;
|
||||
private readonly OptionButton _axisButton;
|
||||
private readonly OptionButton _justifyButton;
|
||||
private readonly LineEdit _separationEdit;
|
||||
private readonly LineEdit _crossSeparationEdit;
|
||||
|
||||
public TabWrapContainer()
|
||||
{
|
||||
var container = new WrapContainer
|
||||
{
|
||||
MouseFilter = MouseFilterMode.Stop,
|
||||
VerticalExpand = true,
|
||||
};
|
||||
|
||||
var random = new Random(3005);
|
||||
|
||||
for (var i = 0; i < 35; i++)
|
||||
{
|
||||
var val = random.Next(1, 16);
|
||||
|
||||
var text = string.Create(val, 0, (span, _) => span.Fill('O'));
|
||||
container.AddChild(new Button { Text = text });
|
||||
}
|
||||
|
||||
AddChild(new BoxContainer
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Vertical,
|
||||
Children =
|
||||
{
|
||||
new BoxContainer
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||
SeparationOverride = 4,
|
||||
Children =
|
||||
{
|
||||
(_equalSizeBox = new CheckBox
|
||||
{
|
||||
Text = nameof(WrapContainer.EqualSize)
|
||||
}),
|
||||
(_reverseBox = new CheckBox
|
||||
{
|
||||
Text = nameof(WrapContainer.Reverse)
|
||||
}),
|
||||
(_axisButton = new OptionButton()),
|
||||
(_justifyButton = new OptionButton()),
|
||||
(_separationEdit = new LineEdit
|
||||
{
|
||||
PlaceHolder = "Separation",
|
||||
SetWidth = 100,
|
||||
}),
|
||||
(_crossSeparationEdit = new LineEdit
|
||||
{
|
||||
PlaceHolder = "Cross Separation",
|
||||
SetWidth = 100,
|
||||
})
|
||||
}
|
||||
},
|
||||
new PanelContainer
|
||||
{
|
||||
PanelOverride = new StyleBoxFlat { BackgroundColor = Color.Black },
|
||||
Children =
|
||||
{
|
||||
container
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
_axisButton.AddItem(nameof(Axis.Horizontal), (int)Axis.Horizontal);
|
||||
_axisButton.AddItem(nameof(Axis.HorizontalReverse), (int)Axis.HorizontalReverse);
|
||||
_axisButton.AddItem(nameof(Axis.Vertical), (int)Axis.Vertical);
|
||||
_axisButton.AddItem(nameof(Axis.VerticalReverse), (int)Axis.VerticalReverse);
|
||||
|
||||
_axisButton.OnItemSelected += args =>
|
||||
{
|
||||
_axisButton.SelectId(args.Id);
|
||||
container.LayoutAxis = (Axis)args.Id;
|
||||
};
|
||||
|
||||
_justifyButton.AddItem(nameof(ItemJustification.Begin), (int)ItemJustification.Begin);
|
||||
_justifyButton.AddItem(nameof(ItemJustification.Center), (int)ItemJustification.Center);
|
||||
_justifyButton.AddItem(nameof(ItemJustification.End), (int)ItemJustification.End);
|
||||
|
||||
_justifyButton.OnItemSelected += args =>
|
||||
{
|
||||
_justifyButton.SelectId(args.Id);
|
||||
container.Justification = (ItemJustification)args.Id;
|
||||
};
|
||||
|
||||
_equalSizeBox.OnPressed += _ => container.EqualSize = _equalSizeBox.Pressed;
|
||||
_reverseBox.OnPressed += _ => container.Reverse = _reverseBox.Pressed;
|
||||
|
||||
_separationEdit.OnTextChanged += args =>
|
||||
{
|
||||
if (!int.TryParse(args.Text, out var sep))
|
||||
sep = 0;
|
||||
|
||||
container.SeparationOverride = sep;
|
||||
};
|
||||
|
||||
_crossSeparationEdit.OnTextChanged += args =>
|
||||
{
|
||||
if (!int.TryParse(args.Text, out var sep))
|
||||
sep = 0;
|
||||
|
||||
container.CrossSeparationOverride = sep;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,6 @@ using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -44,7 +42,10 @@ Suspendisse hendrerit blandit urna ut laoreet. Suspendisse ac elit at erat males
|
||||
var progressBar = new ProgressBar { MaxValue = 10, Value = 5 };
|
||||
vBox.AddChild(progressBar);
|
||||
|
||||
var optionButton = new OptionButton();
|
||||
var optionButton = new OptionButton
|
||||
{
|
||||
ToolTip = "This button has a tooltip. Spooky!"
|
||||
};
|
||||
optionButton.AddItem("Honk");
|
||||
optionButton.AddItem("Foo");
|
||||
optionButton.AddItem("Bar");
|
||||
@@ -155,6 +156,7 @@ Suspendisse hendrerit blandit urna ut laoreet. Suspendisse ac elit at erat males
|
||||
_sprite = new TabSpriteView();
|
||||
_tabContainer.AddChild(_sprite);
|
||||
_tabContainer.AddChild(TabCursorShapes());
|
||||
_tabContainer.AddChild(new TabWrapContainer { Name = nameof(Tab.WrapContainer) });
|
||||
}
|
||||
|
||||
public void OnClosed()
|
||||
@@ -275,32 +277,13 @@ Suspendisse hendrerit blandit urna ut laoreet. Suspendisse ac elit at erat males
|
||||
RichText = 7,
|
||||
SpriteView = 8,
|
||||
TabCursorShapes = 9,
|
||||
WrapContainer = 10,
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class UITestCommand : LocalizedCommands
|
||||
internal abstract class BaseUITestCommand : LocalizedCommands
|
||||
{
|
||||
public override string Command => "uitest";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var window = new DefaultWindow { MinSize = new(800, 600) };
|
||||
var control = new UITestControl();
|
||||
window.OnClose += control.OnClosed;
|
||||
window.Contents.AddChild(control);
|
||||
|
||||
window.OpenCentered();
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class UITest2Command : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _uiMgr = default!;
|
||||
|
||||
public override string Command => "uitest2";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public sealed override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length > 1)
|
||||
{
|
||||
@@ -321,18 +304,10 @@ internal sealed class UITest2Command : LocalizedCommands
|
||||
control.SelectTab(tab);
|
||||
}
|
||||
|
||||
var window = _clyde.CreateWindow(new WindowCreateParameters
|
||||
{
|
||||
Title = Loc.GetString("cmd-uitest2-title"),
|
||||
});
|
||||
|
||||
var root = _uiMgr.CreateWindowRoot(window);
|
||||
window.DisposeOnClose = true;
|
||||
window.RequestClosed += _ => control.OnClosed();
|
||||
root.AddChild(control);
|
||||
CreateWindow(control);
|
||||
}
|
||||
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
public sealed override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (args.Length == 1)
|
||||
{
|
||||
@@ -343,4 +318,35 @@ internal sealed class UITest2Command : LocalizedCommands
|
||||
|
||||
return CompletionResult.Empty;
|
||||
}
|
||||
|
||||
protected abstract void CreateWindow(UITestControl control);
|
||||
}
|
||||
|
||||
internal sealed class UITestCommand : BaseUITestCommand
|
||||
{
|
||||
public override string Command => "uitest";
|
||||
|
||||
protected override void CreateWindow(UITestControl control)
|
||||
{
|
||||
var window = new DefaultWindow { MinSize = new(800, 600) };
|
||||
window.OnClose += control.OnClosed;
|
||||
window.Contents.AddChild(control);
|
||||
window.OpenCentered();
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class UITest2Command : BaseUITestCommand
|
||||
{
|
||||
public override string Command => "uitest2";
|
||||
|
||||
protected override void CreateWindow(UITestControl control)
|
||||
{
|
||||
var window = new OSWindow
|
||||
{
|
||||
Title = Loc.GetString("cmd-uitest2-title"),
|
||||
};
|
||||
window.AddChild(control);
|
||||
window.Closed += control.OnClosed;
|
||||
window.Show();
|
||||
}
|
||||
}
|
||||
|
||||
44
Robust.Client/Console/Commands/ViewportDebugCommands.cs
Normal file
44
Robust.Client/Console/Commands/ViewportDebugCommands.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
#if TOOLS
|
||||
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Console.Commands;
|
||||
|
||||
internal sealed class ViewportClearAllCachedCommand : IConsoleCommand
|
||||
{
|
||||
[Dependency] private readonly IClydeInternal _clyde = default!;
|
||||
|
||||
public string Command => "vp_clear_all_cached";
|
||||
public string Description => "Fires IClydeViewport.ClearCachedResources on all viewports";
|
||||
public string Help => "";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
_clyde.ViewportsClearAllCached();
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ViewportTestFinalizeCommand : IConsoleCommand
|
||||
{
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
|
||||
public string Command => "vp_test_finalize";
|
||||
public string Description => "Creates a viewport, renders it once, then leaks it (finalizes it).";
|
||||
public string Help => "";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var vp = _clyde.CreateViewport(new Vector2i(1920, 1080), nameof(ViewportTestFinalizeCommand));
|
||||
vp.Eye = _eyeManager.CurrentEye;
|
||||
|
||||
vp.Render();
|
||||
|
||||
// Leak it.
|
||||
}
|
||||
}
|
||||
|
||||
#endif // TOOLS
|
||||
53
Robust.Client/Debugging/OverlayCommand.cs
Normal file
53
Robust.Client/Debugging/OverlayCommand.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Toolshed;
|
||||
using Robust.Shared.Toolshed.TypeParsers;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.Debugging;
|
||||
|
||||
[ToolshedCommand]
|
||||
internal sealed class OverlayCommand : ToolshedCommand
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlay = default!;
|
||||
[Dependency] private readonly IDynamicTypeFactoryInternal _factory = default!;
|
||||
|
||||
[CommandImplementation("toggle")]
|
||||
internal void Toggle([CommandArgument(customParser:typeof(ReflectionTypeParser<Overlay>))] Type overlay)
|
||||
{
|
||||
if (!overlay.IsSubclassOf(typeof(Overlay)))
|
||||
throw new ArgumentException("Type must be a subclass of overlay");
|
||||
|
||||
if (_overlay.HasOverlay(overlay))
|
||||
Remove(overlay);
|
||||
else
|
||||
Add(overlay);
|
||||
}
|
||||
|
||||
[CommandImplementation("add")]
|
||||
internal void Add([CommandArgument(customParser: typeof(ReflectionTypeParser<Overlay>))] Type overlay)
|
||||
{
|
||||
if (!overlay.IsSubclassOf(typeof(Overlay)))
|
||||
throw new ArgumentException("Type must be a subclass of overlay");
|
||||
|
||||
if (!overlay.HasParameterlessConstructor())
|
||||
throw new ArgumentException("Type must have parameterless constructor");
|
||||
|
||||
if (_overlay.HasOverlay(overlay))
|
||||
return;
|
||||
|
||||
// TODO OVERLAYS Give overlays the ContentAccessAllowedAttribute?
|
||||
var instance = (Overlay) _factory.CreateInstanceUnchecked(overlay, oneOff: true);
|
||||
if (instance is IPostInjectInit init)
|
||||
init.PostInject();
|
||||
|
||||
_overlay.AddOverlay(instance);
|
||||
}
|
||||
|
||||
[CommandImplementation("remove")]
|
||||
public void Remove([CommandArgument(customParser: typeof(ReflectionTypeParser<Overlay>))] Type overlay)
|
||||
{
|
||||
_overlay.RemoveOverlay(overlay);
|
||||
}
|
||||
}
|
||||
229
Robust.Client/Debugging/Overlays/TileDebugOverlay.cs
Normal file
229
Robust.Client/Debugging/Overlays/TileDebugOverlay.cs
Normal file
@@ -0,0 +1,229 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Debugging.Overlays;
|
||||
|
||||
/// <summary>
|
||||
/// This is an abstract helper class that can be used to create simple debug overlays that need to render tile based data.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public abstract class TileDebugOverlay : Overlay, IPostInjectInit
|
||||
{
|
||||
[Dependency] protected readonly IEntityManager Entity = default!;
|
||||
[Dependency] protected readonly IEyeManager Eye = default!;
|
||||
[Dependency] protected readonly IMapManager MapMan = default!;
|
||||
[Dependency] protected readonly IInputManager Input = default!;
|
||||
[Dependency] protected readonly IUserInterfaceManager Ui = default!;
|
||||
[Dependency] protected readonly IResourceCache Cache = default!;
|
||||
|
||||
protected SharedTransformSystem Transform = default!;
|
||||
protected MapSystem Map = default!;
|
||||
protected EntityLookupSystem Lookup = default!;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace | OverlaySpace.ScreenSpace;
|
||||
|
||||
protected Font Font = default!;
|
||||
protected List<Entity<MapGridComponent>> Grids = new();
|
||||
|
||||
public void PostInject()
|
||||
{
|
||||
Transform = Entity.System<SharedTransformSystem>();
|
||||
Map = Entity.System<MapSystem>();
|
||||
Lookup = Entity.System<EntityLookupSystem>();
|
||||
var font = Cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf");
|
||||
Font = new VectorFont(font, 8);
|
||||
Init();
|
||||
}
|
||||
|
||||
protected virtual void Init()
|
||||
{
|
||||
}
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
Grids.Clear();
|
||||
if (args.Viewport.Eye?.Position.MapId is not {} map || map == MapId.Nullspace)
|
||||
return;
|
||||
|
||||
MapMan.FindGridsIntersecting(map, args.WorldBounds, ref Grids);
|
||||
|
||||
foreach (var grid in Grids)
|
||||
{
|
||||
switch (args.Space)
|
||||
{
|
||||
case OverlaySpace.ScreenSpace:
|
||||
DrawScreen(args, grid);
|
||||
break;
|
||||
case OverlaySpace.WorldSpace:
|
||||
DrawWorld(args, grid);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Grids.Clear();
|
||||
}
|
||||
|
||||
protected virtual void DrawScreen(in OverlayDrawArgs args, Entity<MapGridComponent> grid)
|
||||
{
|
||||
var handle = args.ScreenHandle;
|
||||
var (_, _, matrix, invMatrix) = Transform.GetWorldPositionRotationMatrixWithInv(grid.Owner);
|
||||
var gridBounds = invMatrix.TransformBox(args.WorldBounds).Enlarged(grid.Comp.TileSize * 2);
|
||||
var tilesEnumerator = Map.GetLocalTilesEnumerator(grid, grid, gridBounds);
|
||||
while (tilesEnumerator.MoveNext(out var tile))
|
||||
{
|
||||
var tileBounds = Lookup.GetLocalBounds(tile, grid.Comp.TileSize);
|
||||
if (!gridBounds.Intersects(tileBounds))
|
||||
continue;
|
||||
var screenTileCentre = Eye.WorldToScreen(Vector2.Transform(tileBounds.Center, matrix));
|
||||
DrawTileText(handle, screenTileCentre, tile.GridIndices, grid);
|
||||
}
|
||||
|
||||
// Draw mouse tooltip
|
||||
DrawTooltip(handle);
|
||||
|
||||
}
|
||||
|
||||
protected virtual void DrawTooltip(DrawingHandleScreen handle)
|
||||
{
|
||||
var mousePos = Input.MouseScreenPosition;
|
||||
if (!mousePos.IsValid)
|
||||
return;
|
||||
|
||||
if (Ui.MouseGetControl(mousePos) is not IViewportControl viewport)
|
||||
return;
|
||||
|
||||
var coords = viewport.PixelToMap(mousePos.Position);
|
||||
|
||||
if (!MapMan.TryFindGridAt(coords, out var grid, out var comp))
|
||||
return;
|
||||
|
||||
var local = Map.WorldToLocal(grid, comp, coords.Position);
|
||||
var x = (int) Math.Floor(local.X / comp.TileSize);
|
||||
var y = (int) Math.Floor(local.Y / comp.TileSize);
|
||||
var indices = new Vector2i(x, y);
|
||||
|
||||
DrawTooltip(handle, mousePos.Position, local, indices, (grid, comp));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw a tooltip around the mouse
|
||||
/// </summary>
|
||||
/// <param name="mouseScreen">The mouse's screen coordinates</param>
|
||||
/// <param name="mouseLocal">The mouse's local grid coordinates</param>
|
||||
/// <param name="indices">The mouse's tile indices</param>
|
||||
/// <param name="grid">The grid that the mouse is hovering over</param>
|
||||
protected virtual void DrawTooltip(DrawingHandleScreen handle, Vector2 mouseScreen, Vector2 mouseLocal, Vector2i indices, Entity<MapGridComponent> grid)
|
||||
{
|
||||
if (GetTooltip(mouseLocal, indices, grid) is not { } text)
|
||||
return;
|
||||
|
||||
var lineHeight = Font.GetLineHeight(1f);
|
||||
var offset = new Vector2(0, lineHeight);
|
||||
handle.DrawString(Font, mouseScreen - offset, text);
|
||||
}
|
||||
|
||||
protected virtual void DrawTileText(DrawingHandleScreen handle, Vector2 tileCentre, Vector2i indices, Entity<MapGridComponent> grid)
|
||||
{
|
||||
if (GetText(indices, grid) is {} text)
|
||||
handle.DrawString(Font, tileCentre, text);
|
||||
}
|
||||
|
||||
protected virtual void DrawWorld(in OverlayDrawArgs args, Entity<MapGridComponent> grid)
|
||||
{
|
||||
var handle = args.WorldHandle;
|
||||
var (_, _, matrix, invMatrix) = Transform.GetWorldPositionRotationMatrixWithInv(grid.Owner);
|
||||
var gridBounds = invMatrix.TransformBox(args.WorldBounds).Enlarged(grid.Comp.TileSize * 2);
|
||||
var tilesEnumerator = Map.GetLocalTilesEnumerator(grid, grid, gridBounds);
|
||||
while (tilesEnumerator.MoveNext(out var tile))
|
||||
{
|
||||
handle.SetTransform(matrix);
|
||||
var tileBounds = Lookup.GetLocalBounds(tile, grid.Comp.TileSize);
|
||||
if (gridBounds.Intersects(tileBounds))
|
||||
DrawTile(handle, tileBounds, tile.GridIndices, grid);
|
||||
}
|
||||
|
||||
handle.SetTransform(Matrix3x2.Identity);
|
||||
}
|
||||
|
||||
protected virtual void DrawTile(DrawingHandleWorld handle, Box2 tile, Vector2i indices, Entity<MapGridComponent> grid)
|
||||
{
|
||||
if (GetColor(indices, grid) is not { } color)
|
||||
return;
|
||||
|
||||
handle.DrawRect(tile, color.Border, filled: false);
|
||||
handle.DrawRect(tile, color.Fill, filled: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get text that will be rendered in a grid tile.
|
||||
/// </summary>
|
||||
protected abstract string? GetText(Vector2i indices, Entity<MapGridComponent> grid);
|
||||
|
||||
/// <summary>
|
||||
/// Get tooltip text that will be shown next to the mouse.
|
||||
/// </summary>
|
||||
/// <param name="mousePos">The mouse's position relative to the grid.</param>
|
||||
/// <param name="gridIndices">The grid indices corresponding to the mouse's position</param>
|
||||
/// <param name="grid">The grid that the mouse is over.</param>
|
||||
protected abstract string? GetTooltip(Vector2 mousePos, Vector2i indices, Entity<MapGridComponent> grid);
|
||||
|
||||
/// <summary>
|
||||
/// Get a border & fill color that will be used to draw a grid tile.
|
||||
/// </summary>
|
||||
protected abstract (Color Fill, Color Border)? GetColor(Vector2i indices, Entity<MapGridComponent> grid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Variant of <see cref="TileDebugOverlay"/> that exists to draw simple float information for each tile.
|
||||
/// </summary>
|
||||
public abstract class TileFloatDebugOverlay : TileDebugOverlay
|
||||
{
|
||||
protected virtual float MinValue => 0;
|
||||
protected virtual float MaxValue => 1;
|
||||
protected abstract float? GetData(Vector2i indices, Entity<MapGridComponent> grid);
|
||||
|
||||
protected override string? GetText(Vector2i indices, Entity<MapGridComponent> grid)
|
||||
{
|
||||
return GetData(indices, grid)?.ToString("F2");
|
||||
}
|
||||
|
||||
protected override string? GetTooltip(Vector2 mousePos, Vector2i indices, Entity<MapGridComponent> grid)
|
||||
{
|
||||
return GetData(indices, grid)?.ToString("F2");
|
||||
}
|
||||
|
||||
protected override (Color Fill, Color Border)? GetColor(Vector2i indices, Entity<MapGridComponent> grid)
|
||||
{
|
||||
if (GetData(indices, grid) is not { } value)
|
||||
return null;
|
||||
|
||||
var color = Gradient(value, MinValue, MaxValue);
|
||||
return (color.WithAlpha(0.2f), color);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simple yellow -> orange -> red gradient.
|
||||
/// </summary>
|
||||
public Color Gradient(float value, float min, float max)
|
||||
{
|
||||
// map min to 1, max to 0
|
||||
value = (value - min) / (max - min);
|
||||
return value < 0.5f
|
||||
? Color.InterpolateBetween(Color.Yellow, Color.Orange, value * 2)
|
||||
: Color.InterpolateBetween(Color.Orange, Color.Red, (value - 0.5f) * 2);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using SDL3;
|
||||
|
||||
namespace Robust.Client
|
||||
{
|
||||
@@ -93,6 +94,8 @@ namespace Robust.Client
|
||||
|
||||
public void Run(DisplayMode mode, GameControllerOptions options, Func<ILogHandler>? logHandlerFactory = null)
|
||||
{
|
||||
_displayMode = mode;
|
||||
|
||||
if (!StartupSystemSplash(options, logHandlerFactory))
|
||||
{
|
||||
_logger.Fatal("Failed to start game controller!");
|
||||
|
||||
@@ -110,6 +110,8 @@ namespace Robust.Client
|
||||
|
||||
private ResourceManifestData? _resourceManifest;
|
||||
|
||||
private DisplayMode _displayMode;
|
||||
|
||||
public void SetCommandLineArgs(CommandLineArgs args)
|
||||
{
|
||||
_commandLineArgs = args;
|
||||
@@ -273,6 +275,9 @@ namespace Robust.Client
|
||||
}
|
||||
};
|
||||
|
||||
_configurationManager.OnValueChanged(CVars.DisplayMaxFPS, _ => UpdateVsyncConfig());
|
||||
_configurationManager.OnValueChanged(CVars.DisplayVSync, _ => UpdateVsyncConfig(), invokeImmediately: true);
|
||||
|
||||
_clyde.Ready();
|
||||
|
||||
if (_resourceManifest!.AutoConnect &&
|
||||
@@ -387,7 +392,7 @@ namespace Robust.Client
|
||||
|
||||
_prof.Initialize();
|
||||
|
||||
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
|
||||
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null, hideUserDataDir: true);
|
||||
|
||||
var mountOptions = _commandLineArgs != null
|
||||
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions)
|
||||
@@ -709,6 +714,30 @@ namespace Robust.Client
|
||||
}
|
||||
|
||||
|
||||
private void UpdateVsyncConfig()
|
||||
{
|
||||
if (_displayMode == DisplayMode.Headless)
|
||||
return;
|
||||
|
||||
var vsync = _configurationManager.GetCVar(CVars.DisplayVSync);
|
||||
var maxFps = Math.Clamp(_configurationManager.GetCVar(CVars.DisplayMaxFPS), 0, 10_000);
|
||||
|
||||
_clyde.VsyncEnabled = vsync;
|
||||
|
||||
if (_mainLoop == null)
|
||||
return;
|
||||
|
||||
if (vsync || maxFps == 0)
|
||||
{
|
||||
_mainLoop.SleepMode = SleepMode.None;
|
||||
}
|
||||
else
|
||||
{
|
||||
_mainLoop.SleepMode = SleepMode.Limit;
|
||||
_mainLoop.LimitMinFrameTime = TimeSpan.FromSeconds(1.0 / maxFps);
|
||||
}
|
||||
}
|
||||
|
||||
internal enum DisplayMode : byte
|
||||
{
|
||||
Headless,
|
||||
|
||||
@@ -29,6 +29,9 @@ namespace Robust.Client.GameObjects
|
||||
internal event Action? AfterStartup;
|
||||
internal event Action? AfterShutdown;
|
||||
|
||||
private readonly Queue<EntityUid> _queuedPredictedDeletions = new();
|
||||
private readonly HashSet<EntityUid> _queuedPredictedDeletionsSet = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SetupNetworking();
|
||||
@@ -213,6 +216,34 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
using (histogram?.WithLabels("PredictedQueueDel").NewTimer())
|
||||
{
|
||||
while (_queuedPredictedDeletions.TryDequeue(out var uid))
|
||||
{
|
||||
if (!MetaQuery.TryGetComponentInternal(uid, out var meta))
|
||||
continue;
|
||||
|
||||
if (meta.EntityLifeStage >= EntityLifeStage.Terminating)
|
||||
continue;
|
||||
|
||||
var xform = TransformQuery.GetComponentInternal(uid);
|
||||
if (meta.NetEntity.IsClientSide())
|
||||
{
|
||||
DeleteEntity(uid, meta, xform);
|
||||
}
|
||||
else
|
||||
{
|
||||
_xforms.DetachEntity(uid, xform, meta, null);
|
||||
// base call bypasses IGameTiming.InPrediction check
|
||||
// This is pretty janky and there should be a way for the client to dirty an entity outside of prediction
|
||||
// TODO PREDICTION
|
||||
base.Dirty(uid, xform, meta);
|
||||
}
|
||||
}
|
||||
|
||||
_queuedPredictedDeletionsSet.Clear();
|
||||
}
|
||||
|
||||
base.TickUpdate(frameTime, noPredictions, histogram);
|
||||
}
|
||||
|
||||
@@ -317,18 +348,23 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void PredictedQueueDeleteEntity(Entity<MetaDataComponent?, TransformComponent?> ent)
|
||||
{
|
||||
if (IsQueuedForDeletion(ent.Owner)
|
||||
|| !MetaQuery.Resolve(ent.Owner, ref ent.Comp1)
|
||||
|| ent.Comp1.EntityLifeStage >= EntityLifeStage.Terminating
|
||||
|| !TransformQuery.Resolve(ent.Owner, ref ent.Comp2))
|
||||
{
|
||||
return;
|
||||
}
|
||||
public override bool IsQueuedForDeletion(EntityUid uid)
|
||||
=> QueuedDeletionsSet.Contains(uid) || _queuedPredictedDeletions.Contains(uid);
|
||||
|
||||
if (ent.Comp1.NetEntity.IsClientSide())
|
||||
/// <inheritdoc />
|
||||
public override void PredictedQueueDeleteEntity(Entity<MetaDataComponent?> ent)
|
||||
{
|
||||
// Some UIs get disposed after entity-manager has shut down and already deleted all entities.
|
||||
if (!Started)
|
||||
return;
|
||||
|
||||
if (IsQueuedForDeletion(ent.Owner))
|
||||
return;
|
||||
|
||||
if (!MetaQuery.Resolve(ent.Owner, ref ent.Comp, false))
|
||||
return;
|
||||
|
||||
if (ent.Comp.NetEntity.IsClientSide())
|
||||
{
|
||||
// client-side QueueDeleteEntity re-fetches MetadataComp and checks IsClientSide().
|
||||
// base call to skip that.
|
||||
@@ -337,7 +373,10 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
else
|
||||
{
|
||||
_xforms.DetachEntity(ent.Owner, ent.Comp2);
|
||||
if (!_queuedPredictedDeletionsSet.Add(ent.Owner))
|
||||
return;
|
||||
|
||||
_queuedPredictedDeletions.Enqueue(ent.Owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,6 +294,16 @@ namespace Robust.Client.GameObjects
|
||||
LocalMatrix = Matrix3Helpers.CreateTransform(in offset, in rotation, in scale);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If false, this will prevent any of this sprite's animated layers from looping their animation.
|
||||
/// This will set <see cref="Layer.AutoAnimated"/> whenever any layer's animation finishes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If this is false, this effectively overrides each layer's own <see cref="Layer.Loop"/>.
|
||||
/// </remarks>
|
||||
[DataField]
|
||||
public bool Loop = true;
|
||||
|
||||
/// <summary>
|
||||
/// Update this sprite component to visibly match the current state of other at the time
|
||||
/// this is called. Does not keep them perpetually in sync.
|
||||
@@ -601,6 +611,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
layer.RenderingStrategy = layerDatum.RenderingStrategy ?? layer.RenderingStrategy;
|
||||
layer.Cycle = layerDatum.Cycle;
|
||||
layer.Loop = layerDatum.Loop;
|
||||
|
||||
layer.Color = layerDatum.Color ?? layer.Color;
|
||||
layer._rotation = layerDatum.Rotation ?? layer._rotation;
|
||||
@@ -1157,6 +1168,15 @@ namespace Robust.Client.GameObjects
|
||||
/// </remarks>
|
||||
[ViewVariables] public bool Cycle;
|
||||
|
||||
/// <summary>
|
||||
/// If false, this will prevent the layer's animation from looping.
|
||||
/// This will set <see cref="AutoAnimated"/> to false once the animation finishes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This may be overriden by the parent's loop property.
|
||||
/// </remarks>
|
||||
[ViewVariables] public bool Loop = true;
|
||||
|
||||
// TODO SPRITE ACCESS
|
||||
internal RSI.State? _actualState;
|
||||
[ViewVariables] public RSI.State? ActualState => _actualState;
|
||||
@@ -1336,6 +1356,8 @@ namespace Robust.Client.GameObjects
|
||||
DirOffset = toClone.DirOffset;
|
||||
_autoAnimated = toClone._autoAnimated;
|
||||
RenderingStrategy = toClone.RenderingStrategy;
|
||||
Cycle = toClone.Cycle;
|
||||
Loop = toClone.Loop;
|
||||
if (toClone.CopyToShaderParameters is { } copyToShaderParameters)
|
||||
CopyToShaderParameters = new CopyToShaderParameters(copyToShaderParameters);
|
||||
}
|
||||
@@ -1663,17 +1685,25 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
internal void AdvanceFrameAnimation(RSI.State state)
|
||||
{
|
||||
// Can't advance frames without more than 1 delay which is already checked above.
|
||||
var delayCount = state.DelayCount;
|
||||
|
||||
while (AnimationTimeLeft < 0)
|
||||
{
|
||||
if (Reversed)
|
||||
{
|
||||
AnimationFrame -= 1;
|
||||
|
||||
// Animation finished, do we cycle back to positive or reset.
|
||||
if (AnimationFrame < 0)
|
||||
{
|
||||
if (!Loop || !_parent.Loop)
|
||||
{
|
||||
// stop at first frame
|
||||
AnimationFrame = 0;
|
||||
AnimationTimeLeft = 0;
|
||||
AutoAnimated = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (Cycle)
|
||||
{
|
||||
AnimationFrame = 1;
|
||||
@@ -1691,9 +1721,17 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
AnimationFrame += 1;
|
||||
|
||||
// Animation finished, do we reverse or reset.
|
||||
if (AnimationFrame >= delayCount)
|
||||
{
|
||||
if (!Loop || !_parent.Loop)
|
||||
{
|
||||
// stop at last frame
|
||||
AnimationFrame = delayCount - 1;
|
||||
AnimationTimeLeft = 0;
|
||||
AutoAnimated = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (Cycle)
|
||||
{
|
||||
AnimationFrame = delayCount - 2;
|
||||
@@ -1711,6 +1749,7 @@ namespace Robust.Client.GameObjects
|
||||
AnimationTimeLeft += state.GetDelay(AnimationFrame);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Diagnostics.Contracts;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Map;
|
||||
using Robust.Client.ResourceManagement;
|
||||
@@ -9,13 +10,14 @@ namespace Robust.Client.GameObjects;
|
||||
|
||||
public sealed class MapSystem : SharedMapSystem
|
||||
{
|
||||
protected override MapId GetNextMapId()
|
||||
[Pure]
|
||||
internal override MapId GetNextMapId()
|
||||
{
|
||||
// Client-side map entities use negative map Ids to avoid conflict with server-side maps.
|
||||
var id = new MapId(--LastMapId);
|
||||
var id = new MapId(LastMapId - 1);
|
||||
while (MapExists(id) || UsedIds.Contains(id))
|
||||
{
|
||||
id = new MapId(--LastMapId);
|
||||
id = new MapId(id.Value - 1);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ namespace Robust.Client.GameObjects
|
||||
component.Enabled = state.Enabled;
|
||||
component.Offset = state.Offset;
|
||||
component.Softness = state.Softness;
|
||||
component.Falloff = state.Falloff;
|
||||
component.CurveFactor = state.CurveFactor;
|
||||
component.CastShadows = state.CastShadows;
|
||||
component.Energy = state.Energy;
|
||||
component.Radius = state.Radius;
|
||||
|
||||
@@ -88,6 +88,7 @@ public sealed partial class SpriteSystem
|
||||
|
||||
target.Comp.RenderOrder = source.Comp.RenderOrder;
|
||||
target.Comp.GranularLayersRendering = source.Comp.GranularLayersRendering;
|
||||
target.Comp.Loop = source.Comp.Loop;
|
||||
|
||||
DirtyBounds(target!);
|
||||
_tree.QueueTreeUpdate(target!);
|
||||
|
||||
@@ -220,24 +220,34 @@ Had full state: {LastFullState != null}"
|
||||
{
|
||||
var compState = change.State;
|
||||
|
||||
if (compState is IComponentDeltaState delta
|
||||
&& compData.TryGetValue(change.NetID, out var old)) // May fail if relying on implicit data
|
||||
if (compState is not IComponentDeltaState delta)
|
||||
{
|
||||
DebugTools.Assert(old is not IComponentDeltaState, "last state is not a full state");
|
||||
|
||||
if (cloneDelta)
|
||||
{
|
||||
compState = delta.CreateNewFullState(old!);
|
||||
}
|
||||
else
|
||||
{
|
||||
delta.ApplyToFullState(old!);
|
||||
compState = old;
|
||||
}
|
||||
DebugTools.Assert(compState is not IComponentDeltaState, "newly constructed state is not a full state");
|
||||
compData[change.NetID] = compState;
|
||||
continue;
|
||||
}
|
||||
|
||||
compData[change.NetID] = compState;
|
||||
if (!compData.TryGetValue(change.NetID, out var old))
|
||||
{
|
||||
// Either the server needs to ensure that the initial state it sends to a client is a full
|
||||
// state, or the client needs to be able to construct an implicit full state (i.e., get-state
|
||||
// code needs to be in shared code).
|
||||
//
|
||||
// Without this, the client won't be able to reset predicted changes made to this component.
|
||||
DebugTools.Assert("Received delta state without having received or constructed an implicit full state");
|
||||
continue;
|
||||
}
|
||||
|
||||
DebugTools.Assert(old is not IComponentDeltaState, "last state is not a full state");
|
||||
|
||||
if (!cloneDelta)
|
||||
{
|
||||
delta.ApplyToFullState(old!);
|
||||
continue;
|
||||
}
|
||||
|
||||
var newFull = delta.CreateNewFullState(old!);
|
||||
compData[change.NetID] = newFull;
|
||||
DebugTools.Assert(newFull is not IComponentDeltaState, "constructed state is not a full state");
|
||||
}
|
||||
|
||||
if (entityState.NetComponents == null)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Log;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
|
||||
@@ -121,6 +121,19 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
public void RenderNow(IRenderTarget renderTarget, Action<IRenderHandle> callback)
|
||||
{
|
||||
ClearRenderState();
|
||||
|
||||
_renderHandle.RenderInRenderTarget(
|
||||
renderTarget,
|
||||
() =>
|
||||
{
|
||||
callback(_renderHandle);
|
||||
},
|
||||
null);
|
||||
}
|
||||
|
||||
private void RenderSingleWorldOverlay(Overlay overlay, Viewport vp, OverlaySpace space, in Box2 worldBox, in Box2Rotated worldBounds)
|
||||
{
|
||||
// Check that entity manager has started.
|
||||
|
||||
@@ -451,6 +451,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var lastPower = float.NaN;
|
||||
var lastColor = new Color(float.NaN, float.NaN, float.NaN, float.NaN);
|
||||
var lastSoftness = float.NaN;
|
||||
var lastFalloff = float.NaN;
|
||||
var lastCurveFactor = float.NaN;
|
||||
Texture? lastMask = null;
|
||||
|
||||
using (_prof.Group("Draw Lights"))
|
||||
@@ -504,6 +506,18 @@ namespace Robust.Client.Graphics.Clyde
|
||||
lightShader.SetUniformMaybe("lightSoftness", lastSoftness);
|
||||
}
|
||||
|
||||
if (!MathHelper.CloseToPercent(lastFalloff, component.Falloff))
|
||||
{
|
||||
lastFalloff = component.Falloff;
|
||||
lightShader.SetUniformMaybe("lightFalloff", lastFalloff);
|
||||
}
|
||||
|
||||
if (!MathHelper.CloseToPercent(lastCurveFactor, component.CurveFactor))
|
||||
{
|
||||
lastCurveFactor = component.CurveFactor;
|
||||
lightShader.SetUniformMaybe("lightCurveFactor", lastCurveFactor);
|
||||
}
|
||||
|
||||
lightShader.SetUniformMaybe("lightCenter", lightPos);
|
||||
lightShader.SetUniformMaybe("lightIndex",
|
||||
component.CastShadows ? (i + 0.5f) / ShadowTexture.Height : -1);
|
||||
|
||||
@@ -209,6 +209,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var pressure = estPixSize * size.X * size.Y;
|
||||
|
||||
var handle = AllocRid();
|
||||
var renderTarget = new RenderTexture(size, textureObject, this, handle);
|
||||
var data = new LoadedRenderTarget
|
||||
{
|
||||
IsWindow = false,
|
||||
@@ -220,10 +221,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
MemoryPressure = pressure,
|
||||
ColorFormat = format.ColorFormat,
|
||||
SampleParameters = sampleParameters,
|
||||
Instance = new WeakReference<RenderTargetBase>(renderTarget),
|
||||
Name = name,
|
||||
};
|
||||
|
||||
//GC.AddMemoryPressure(pressure);
|
||||
var renderTarget = new RenderTexture(size, textureObject, this, handle);
|
||||
_renderTargets.Add(handle, data);
|
||||
return renderTarget;
|
||||
}
|
||||
@@ -301,10 +303,22 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class LoadedRenderTarget
|
||||
public IEnumerable<(RenderTargetBase, LoadedRenderTarget)> GetLoadedRenderTextures()
|
||||
{
|
||||
foreach (var loaded in _renderTargets.Values)
|
||||
{
|
||||
if (!loaded.Instance.TryGetTarget(out var instance))
|
||||
continue;
|
||||
|
||||
yield return (instance, loaded);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class LoadedRenderTarget
|
||||
{
|
||||
public bool IsWindow;
|
||||
public WindowId WindowId;
|
||||
public string? Name;
|
||||
|
||||
public Vector2i Size;
|
||||
public bool IsSrgb;
|
||||
@@ -325,9 +339,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public long MemoryPressure;
|
||||
|
||||
public TextureSampleParameters? SampleParameters;
|
||||
|
||||
public required WeakReference<RenderTargetBase> Instance;
|
||||
}
|
||||
|
||||
private abstract class RenderTargetBase : IRenderTarget
|
||||
internal abstract class RenderTargetBase : IRenderTarget
|
||||
{
|
||||
protected readonly Clyde Clyde;
|
||||
private bool _disposed;
|
||||
@@ -389,7 +405,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class RenderTexture : RenderTargetBase, IRenderTexture
|
||||
internal sealed class RenderTexture : RenderTargetBase, IRenderTexture
|
||||
{
|
||||
public RenderTexture(Vector2i size, ClydeTexture texture, Clyde clyde, ClydeHandle handle)
|
||||
: base(clyde, handle)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
@@ -15,10 +16,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private readonly Dictionary<ClydeHandle, WeakReference<Viewport>> _viewports =
|
||||
new();
|
||||
|
||||
private long _nextViewportId = 1;
|
||||
|
||||
private readonly ConcurrentQueue<ViewportDisposeData> _viewportDisposeQueue = new();
|
||||
|
||||
private Viewport CreateViewport(Vector2i size, TextureSampleParameters? sampleParameters = default, string? name = null)
|
||||
{
|
||||
var handle = AllocRid();
|
||||
var viewport = new Viewport(handle, name, this)
|
||||
var viewport = new Viewport(_nextViewportId++, handle, name, this)
|
||||
{
|
||||
Size = size,
|
||||
RenderTarget = CreateRenderTarget(size,
|
||||
@@ -59,28 +64,43 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private void FlushViewportDispose()
|
||||
{
|
||||
// Free of allocations unless a dead viewport is found.
|
||||
List<ClydeHandle>? toRemove = null;
|
||||
foreach (var (handle, viewportRef) in _viewports)
|
||||
while (_viewportDisposeQueue.TryDequeue(out var data))
|
||||
{
|
||||
if (!viewportRef.TryGetTarget(out _))
|
||||
{
|
||||
toRemove ??= new List<ClydeHandle>();
|
||||
toRemove.Add(handle);
|
||||
}
|
||||
}
|
||||
|
||||
if (toRemove == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var remove in toRemove)
|
||||
{
|
||||
_viewports.Remove(remove);
|
||||
DisposeViewport(data);
|
||||
}
|
||||
}
|
||||
|
||||
private void DisposeViewport(ViewportDisposeData disposeData)
|
||||
{
|
||||
_clydeSawmill.Warning($"Viewport {disposeData.Id} got leaked");
|
||||
|
||||
_viewports.Remove(disposeData.Handle);
|
||||
if (disposeData.ClearEvent is not { } clearEvent)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
clearEvent(disposeData.ClearEventData);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_clydeSawmill.Error($"Caught exception while disposing viewport: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
#if TOOLS
|
||||
public void ViewportsClearAllCached()
|
||||
{
|
||||
foreach (var vpRef in _viewports.Values)
|
||||
{
|
||||
if (!vpRef.TryGetTarget(out var vp))
|
||||
continue;
|
||||
|
||||
vp.FireClear();
|
||||
}
|
||||
}
|
||||
#endif // TOOLS
|
||||
|
||||
private sealed class Viewport : IClydeViewport
|
||||
{
|
||||
private readonly ClydeHandle _handle;
|
||||
@@ -106,17 +126,20 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
public string? Name { get; }
|
||||
|
||||
public Viewport(ClydeHandle handle, string? name, Clyde clyde)
|
||||
public Viewport(long id, ClydeHandle handle, string? name, Clyde clyde)
|
||||
{
|
||||
Name = name;
|
||||
_handle = handle;
|
||||
_clyde = clyde;
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public Vector2i Size { get; set; }
|
||||
public event Action<ClearCachedViewportResourcesEvent>? ClearCachedResources;
|
||||
public Color? ClearColor { get; set; } = Color.Black;
|
||||
public Vector2 RenderScale { get; set; } = Vector2.One;
|
||||
public bool AutomaticRender { get; set; }
|
||||
public long Id { get; }
|
||||
|
||||
void IClydeViewport.Render()
|
||||
{
|
||||
@@ -186,20 +209,56 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_clyde.RenderOverlaysDirect(this, control, handle, OverlaySpace.ScreenSpace, viewportBounds);
|
||||
}
|
||||
|
||||
~Viewport()
|
||||
{
|
||||
_clyde._viewportDisposeQueue.Enqueue(DisposeData(referenceSelf: false));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
|
||||
RenderTarget.Dispose();
|
||||
LightRenderTarget.Dispose();
|
||||
WallMaskRenderTarget.Dispose();
|
||||
WallBleedIntermediateRenderTarget1.Dispose();
|
||||
WallBleedIntermediateRenderTarget2.Dispose();
|
||||
|
||||
_clyde._viewports.Remove(_handle);
|
||||
_clyde.DisposeViewport(DisposeData(referenceSelf: false));
|
||||
}
|
||||
|
||||
private ViewportDisposeData DisposeData(bool referenceSelf)
|
||||
{
|
||||
return new ViewportDisposeData
|
||||
{
|
||||
Handle = _handle,
|
||||
Id = Id,
|
||||
ClearEvent = ClearCachedResources,
|
||||
ClearEventData = MakeClearEvent(referenceSelf)
|
||||
};
|
||||
}
|
||||
|
||||
private ClearCachedViewportResourcesEvent MakeClearEvent(bool referenceSelf)
|
||||
{
|
||||
return new ClearCachedViewportResourcesEvent(Id, referenceSelf ? this : null);
|
||||
}
|
||||
|
||||
public void FireClear()
|
||||
{
|
||||
ClearCachedResources?.Invoke(MakeClearEvent(referenceSelf: true));
|
||||
}
|
||||
|
||||
IRenderTexture IClydeViewport.RenderTarget => RenderTarget;
|
||||
IRenderTexture IClydeViewport.LightRenderTarget => LightRenderTarget;
|
||||
public IEye? Eye { get; set; }
|
||||
}
|
||||
|
||||
private sealed class ViewportDisposeData
|
||||
{
|
||||
public ClydeHandle Handle;
|
||||
public long Id;
|
||||
public Action<ClearCachedViewportResourcesEvent>? ClearEvent;
|
||||
public ClearCachedViewportResourcesEvent ClearEventData;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Client.Input;
|
||||
@@ -101,6 +102,10 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
_windowingThread = Thread.CurrentThread;
|
||||
|
||||
// Default to SDL3 on ARM64. GLFW is not feature complete there (lacking file dialog implementation)
|
||||
if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
||||
_cfg.SetCVar(CVars.DisplayWindowingApi, "sdl3");
|
||||
|
||||
var windowingApi = _cfg.GetCVar(CVars.DisplayWindowingApi);
|
||||
IWindowingImpl winImpl;
|
||||
|
||||
@@ -349,15 +354,17 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_windowHandles.Add(reg.Handle);
|
||||
|
||||
var rtId = AllocRid();
|
||||
var renderTarget = new RenderWindow(this, rtId);
|
||||
_renderTargets.Add(rtId, new LoadedRenderTarget
|
||||
{
|
||||
Size = reg.FramebufferSize,
|
||||
IsWindow = true,
|
||||
WindowId = reg.Id,
|
||||
IsSrgb = true
|
||||
IsSrgb = true,
|
||||
Instance = new WeakReference<RenderTargetBase>(renderTarget),
|
||||
});
|
||||
|
||||
reg.RenderTarget = new RenderWindow(this, rtId);
|
||||
reg.RenderTarget = renderTarget;
|
||||
|
||||
_glContext!.WindowCreated(glSpec, reg);
|
||||
}
|
||||
@@ -374,6 +381,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
if (reg.IsDisposed)
|
||||
return;
|
||||
|
||||
_sawmillWin.Debug($"Destroying window {reg.Id}");
|
||||
|
||||
reg.IsDisposed = true;
|
||||
|
||||
_glContext!.WindowDestroyed(reg);
|
||||
@@ -398,10 +407,17 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_glContext?.SwapAllBuffers();
|
||||
}
|
||||
|
||||
private void VSyncChanged(bool newValue)
|
||||
public bool VsyncEnabled
|
||||
{
|
||||
_vSync = newValue;
|
||||
_glContext?.UpdateVSync();
|
||||
get => _vSync;
|
||||
set
|
||||
{
|
||||
if (_vSync == value)
|
||||
return;
|
||||
|
||||
_vSync = value;
|
||||
_glContext?.UpdateVSync();
|
||||
}
|
||||
}
|
||||
|
||||
private void WindowModeChanged(int mode)
|
||||
|
||||
@@ -114,7 +114,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_proto.PrototypesReloaded += OnProtoReload;
|
||||
|
||||
_cfg.OnValueChanged(CVars.DisplayOGLCheckErrors, b => _checkGLErrors = b, true);
|
||||
_cfg.OnValueChanged(CVars.DisplayVSync, VSyncChanged, true);
|
||||
_cfg.OnValueChanged(CVars.DisplayWindowMode, WindowModeChanged, true);
|
||||
_cfg.OnValueChanged(CVars.LightResolutionScale, LightResolutionScaleChanged, true);
|
||||
_cfg.OnValueChanged(CVars.MaxShadowcastingLights, MaxShadowcastingLightsChanged, true);
|
||||
@@ -128,7 +127,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// macOS cannot.
|
||||
if (OperatingSystem.IsWindows() || OperatingSystem.IsLinux())
|
||||
_cfg.OverrideDefault(CVars.DisplayThreadWindowApi, true);
|
||||
|
||||
#if MACOS
|
||||
// Trust macOS to not need threaded window blitting.
|
||||
// (threaded window blitting is a workaround to avoid having to frequently MakeCurrent() on Windows, as it is broken).
|
||||
_cfg.OverrideDefault(CVars.DisplayThreadWindowBlit, false);
|
||||
#endif
|
||||
_threadWindowBlit = _cfg.GetCVar(CVars.DisplayThreadWindowBlit);
|
||||
_threadWindowApi = _cfg.GetCVar(CVars.DisplayThreadWindowApi);
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public bool IsFocused => true;
|
||||
private readonly List<IClydeWindow> _windows = new();
|
||||
private int _nextWindowId = 2;
|
||||
private long _nextViewportId = 1;
|
||||
|
||||
public ShaderInstance InstanceShader(ShaderSourceResource handle, bool? light = null, ShaderBlendMode? blend = null)
|
||||
{
|
||||
@@ -75,6 +76,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return [];
|
||||
}
|
||||
|
||||
public IEnumerable<(Clyde.RenderTargetBase, Clyde.LoadedRenderTarget)> GetLoadedRenderTextures()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public ClydeDebugLayers DebugLayers { get; set; }
|
||||
|
||||
public string GetKeyName(Keyboard.Key key) => string.Empty;
|
||||
@@ -240,7 +246,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public IClydeViewport CreateViewport(Vector2i size, TextureSampleParameters? sampleParameters,
|
||||
string? name = null)
|
||||
{
|
||||
return new Viewport(size);
|
||||
return new Viewport(_nextViewportId++, size);
|
||||
}
|
||||
|
||||
public IEnumerable<IClydeMonitor> EnumerateMonitors()
|
||||
@@ -307,6 +313,19 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
public IFileDialogManagerImplementation? FileDialogImpl => null;
|
||||
|
||||
public bool VsyncEnabled { get; set; }
|
||||
|
||||
#if TOOLS
|
||||
public void ViewportsClearAllCached()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
#endif // TOOLS
|
||||
|
||||
public void RenderNow(IRenderTarget renderTarget, Action<IRenderHandle> callback)
|
||||
{
|
||||
}
|
||||
|
||||
private sealed class DummyCursor : ICursor
|
||||
{
|
||||
public void Dispose()
|
||||
@@ -482,15 +501,19 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private sealed class Viewport : IClydeViewport
|
||||
{
|
||||
public Viewport(Vector2i size)
|
||||
public Viewport(long id, Vector2i size)
|
||||
{
|
||||
Size = size;
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ClearCachedResources?.Invoke(new ClearCachedViewportResourcesEvent(Id, null));
|
||||
}
|
||||
|
||||
public long Id { get; }
|
||||
|
||||
public IRenderTexture RenderTarget { get; } =
|
||||
new DummyRenderTexture(Vector2i.One, new DummyTexture(Vector2i.One));
|
||||
|
||||
@@ -499,6 +522,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
public IEye? Eye { get; set; }
|
||||
public Vector2i Size { get; }
|
||||
public event Action<ClearCachedViewportResourcesEvent>? ClearCachedResources;
|
||||
public Color? ClearColor { get; set; } = Color.Black;
|
||||
public Vector2 RenderScale { get; set; }
|
||||
public bool AutomaticRender { get; set; }
|
||||
|
||||
@@ -102,6 +102,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
var data = _windowData[reg.Id];
|
||||
data.BlitDoneEvent?.Set();
|
||||
// Set events so blit thread properly wakes up and notices it needs to shut down.
|
||||
data.BlitStartEvent?.Set();
|
||||
|
||||
_windowData.Remove(reg.Id);
|
||||
}
|
||||
@@ -326,11 +328,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
reg.RenderTexture?.Dispose();
|
||||
|
||||
reg.RenderTexture = Clyde.CreateRenderTarget(reg.Reg.FramebufferSize, new RenderTargetFormatParameters
|
||||
{
|
||||
ColorFormat = RenderTargetColorFormat.Rgba8Srgb,
|
||||
HasDepthStencil = true
|
||||
});
|
||||
reg.RenderTexture = Clyde.CreateRenderTarget(
|
||||
reg.Reg.FramebufferSize,
|
||||
new RenderTargetFormatParameters
|
||||
{
|
||||
ColorFormat = RenderTargetColorFormat.Rgba8Srgb,
|
||||
HasDepthStencil = true
|
||||
},
|
||||
name: $"{reg.Reg.Id}-RenderTexture");
|
||||
// Necessary to correctly sync multi-context blitting.
|
||||
reg.RenderTexture.MakeGLFence = true;
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
/* SDL3-CS - C# Bindings for SDL3
|
||||
*
|
||||
* Copyright (c) 2024 Colin Jackson
|
||||
*
|
||||
* This software is provided 'as-is', without any express or implied warranty.
|
||||
* In no event will the authors be held liable for any damages arising from
|
||||
* the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1. The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software in a
|
||||
* product, an acknowledgment in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
*
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
*
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
*
|
||||
* Colin "cryy22" Jackson <c@cryy22.art>
|
||||
*
|
||||
*/
|
||||
@@ -1,56 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace SDL3;
|
||||
|
||||
public static partial class SDL
|
||||
{
|
||||
// Extensions to SDL3-CS that aren't part of the main library.
|
||||
|
||||
[LibraryImport(nativeLibName)]
|
||||
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
|
||||
public static unsafe partial void SDL_SetLogOutputFunction(delegate* unmanaged[Cdecl] <void*, int, SDL_LogPriority, byte*, void> callback, void* userdata);
|
||||
|
||||
[LibraryImport(nativeLibName)]
|
||||
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
|
||||
public static unsafe partial SDLBool SDL_AddEventWatch(delegate* unmanaged[Cdecl] <void*, SDL_Event*, byte> filter, void* userdata);
|
||||
|
||||
[LibraryImport(nativeLibName)]
|
||||
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
|
||||
public static unsafe partial void SDL_RemoveEventWatch(delegate* unmanaged[Cdecl] <void*, SDL_Event*, byte> filter, void* userdata);
|
||||
|
||||
[LibraryImport(nativeLibName, StringMarshalling = StringMarshalling.Utf8)]
|
||||
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
|
||||
public static unsafe partial void SDL_ShowFileDialogWithProperties(int type, delegate* unmanaged[Cdecl]<void*, byte**, int, void> callback, void* userdata, uint properties);
|
||||
|
||||
[LibraryImport(nativeLibName, EntryPoint = "SDL_WaitEvent")]
|
||||
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
|
||||
public static partial SDLBool SDL_WaitEventRef(ref SDL_Event @event);
|
||||
|
||||
public const byte SDL_BUTTON_LEFT = 1;
|
||||
public const byte SDL_BUTTON_MIDDLE = 2;
|
||||
public const byte SDL_BUTTON_RIGHT = 3;
|
||||
public const byte SDL_BUTTON_X1 = 4;
|
||||
public const byte SDL_BUTTON_X2 = 5;
|
||||
|
||||
public const int SDL_GL_CONTEXT_PROFILE_CORE = 0x0001;
|
||||
public const int SDL_GL_CONTEXT_PROFILE_COMPATIBILITY = 0x0002;
|
||||
public const int SDL_GL_CONTEXT_PROFILE_ES = 0x0004;
|
||||
|
||||
public const int SDL_GL_CONTEXT_DEBUG_FLAG = 0x0001;
|
||||
public const int SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG = 0x0002;
|
||||
public const int SDL_GL_CONTEXT_ROBUST_ACCESS_FLAG = 0x0004;
|
||||
public const int SDL_GL_CONTEXT_RESET_ISOLATION_FLAG = 0x0008;
|
||||
|
||||
public const int SDL_FILEDIALOG_OPENFILE = 0;
|
||||
public const int SDL_FILEDIALOG_SAVEFILE = 1;
|
||||
public const int SDL_FILEDIALOG_OPENFOLDER = 2;
|
||||
|
||||
public const string SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER = "SDL.filedialog.nfilters";
|
||||
public const string SDL_PROP_FILE_DIALOG_FILTERS_POINTER = "SDL.filedialog.filters";
|
||||
|
||||
public static int SDL_VERSIONNUM_MAJOR(int version) => version / 1000000;
|
||||
public static int SDL_VERSIONNUM_MINOR(int version) => version / 1000 % 1000;
|
||||
public static int SDL_VERSIONNUM_MICRO(int version) => version % 1000;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -81,6 +81,11 @@ internal partial class Clyde
|
||||
case EventQuit:
|
||||
ProcessEventQuit();
|
||||
break;
|
||||
#if MACOS
|
||||
case EventWindowDestroyed:
|
||||
ProcessEventWindowDestroyed();
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
_sawmill.Error($"Unknown SDL3 event type: {evb.GetType().Name}");
|
||||
break;
|
||||
@@ -255,5 +260,15 @@ internal partial class Clyde
|
||||
{
|
||||
_clyde.SendInputModeChanged();
|
||||
}
|
||||
|
||||
#if MACOS
|
||||
private void ProcessEventWindowDestroyed()
|
||||
{
|
||||
// For some reason, on macOS, closing a secondary window
|
||||
// causes the GL context on the primary thread to crap itself.
|
||||
// Rebinding it seems to fix it.
|
||||
GLMakeContextCurrent(_clyde._mainWindow);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,10 @@ internal partial class Clyde
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Giving a parent window is required to avoid the file dialog being blocking on macOS.
|
||||
var mainWindow = (Sdl3WindowReg)_clyde._mainWindow!;
|
||||
SDL.SDL_SetPointerProperty(props, SDL.SDL_PROP_FILE_DIALOG_WINDOW_POINTER, mainWindow.Sdl3Window);
|
||||
|
||||
var task = ShowFileDialogWithProperties(type, props);
|
||||
|
||||
SDL.SDL_DestroyProperties(props);
|
||||
|
||||
@@ -278,5 +278,9 @@ internal partial class Clyde
|
||||
private sealed class EventKeyMapChanged : EventBase;
|
||||
|
||||
private sealed class EventQuit : EventBase;
|
||||
|
||||
#if MACOS
|
||||
private sealed class EventWindowDestroyed : EventBase;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,10 @@ using Robust.Shared.Maths;
|
||||
using SDL3;
|
||||
using TerraFX.Interop.Windows;
|
||||
using TerraFX.Interop.Xlib;
|
||||
#if WINDOWS
|
||||
using BOOL = TerraFX.Interop.Windows.BOOL;
|
||||
using Windows = TerraFX.Interop.Windows.Windows;
|
||||
#endif
|
||||
using GLAttr = SDL3.SDL.SDL_GLAttr;
|
||||
using X11Window = TerraFX.Interop.Xlib.Window;
|
||||
|
||||
@@ -142,9 +144,12 @@ internal partial class Clyde
|
||||
});
|
||||
}
|
||||
|
||||
private static void WinThreadWinDestroy(CmdWinDestroy cmd)
|
||||
private void WinThreadWinDestroy(CmdWinDestroy cmd)
|
||||
{
|
||||
SDL.SDL_DestroyWindow(cmd.Window);
|
||||
#if MACOS
|
||||
SendEvent(new EventWindowDestroyed());
|
||||
#endif
|
||||
}
|
||||
|
||||
private (nint window, nint context) CreateSdl3WindowForRenderer(
|
||||
@@ -461,6 +466,7 @@ internal partial class Clyde
|
||||
var reg = (Sdl3WindowReg)window;
|
||||
var windowPtr = WinPtr(reg);
|
||||
|
||||
#if WINDOWS
|
||||
// On Windows, SwapBuffers does not correctly sync to the DWM compositor.
|
||||
// This means OpenGL vsync is effectively broken by default on Windows.
|
||||
// We manually sync via DwmFlush(). GLFW does this automatically, SDL3 does not.
|
||||
@@ -473,7 +479,7 @@ internal partial class Clyde
|
||||
var dwmFlush = false;
|
||||
var swapInterval = 0;
|
||||
|
||||
if (OperatingSystem.IsWindows() && !reg.Fullscreen && reg.SwapInterval > 0)
|
||||
if (!reg.Fullscreen && reg.SwapInterval > 0)
|
||||
{
|
||||
BOOL compositing;
|
||||
// 6.2 is Windows 8
|
||||
@@ -492,9 +498,12 @@ internal partial class Clyde
|
||||
swapInterval = reg.SwapInterval;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
//_sawmill.Debug($"Swapping: {window.Id} @ {_clyde._gameTiming.CurFrame}");
|
||||
SDL.SDL_GL_SwapWindow(windowPtr);
|
||||
|
||||
#if WINDOWS
|
||||
if (dwmFlush)
|
||||
{
|
||||
var i = swapInterval;
|
||||
@@ -505,6 +514,7 @@ internal partial class Clyde
|
||||
|
||||
SDL.SDL_GL_SetSwapInterval(swapInterval);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public uint? WindowGetX11Id(WindowReg window)
|
||||
@@ -547,17 +557,18 @@ internal partial class Clyde
|
||||
|
||||
public void TextInputSetRect(WindowReg reg, UIBox2i rect, int cursor)
|
||||
{
|
||||
var ratio = ((Sdl3WindowReg)reg).PixelRatio;
|
||||
SendCmd(new CmdTextInputSetRect
|
||||
{
|
||||
Window = WinPtr(reg),
|
||||
Rect = new SDL.SDL_Rect
|
||||
{
|
||||
x = rect.Left,
|
||||
y = rect.Top,
|
||||
w = rect.Width,
|
||||
h = rect.Height
|
||||
x = (int)(rect.Left / ratio.X),
|
||||
y = (int)(rect.Top / ratio.Y),
|
||||
w = (int)(rect.Width / ratio.X),
|
||||
h = (int)(rect.Height / ratio.Y)
|
||||
},
|
||||
Cursor = cursor
|
||||
Cursor = (int)(cursor / ratio.X)
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,10 @@ internal partial class Clyde
|
||||
// https://github.com/libsdl-org/SDL/issues/11813
|
||||
SDL.SDL_SetHint(SDL.SDL_HINT_WINDOWS_GAMEINPUT, "0");
|
||||
|
||||
#if MACOS
|
||||
SDL.SDL_SetHint(SDL.SDL_HINT_MAC_OPENGL_ASYNC_DISPATCH, "1");
|
||||
#endif
|
||||
|
||||
var res = SDL.SDL_Init(SDL.SDL_InitFlags.SDL_INIT_VIDEO | SDL.SDL_InitFlags.SDL_INIT_EVENTS);
|
||||
if (!res)
|
||||
{
|
||||
|
||||
@@ -55,6 +55,7 @@ namespace Robust.Client.Graphics
|
||||
|
||||
Texture GetStockTexture(ClydeStockTexture stockTexture);
|
||||
IEnumerable<(Clyde.Clyde.ClydeTexture, Clyde.Clyde.LoadedTexture)> GetLoadedTextures();
|
||||
IEnumerable<(Clyde.Clyde.RenderTargetBase, Clyde.Clyde.LoadedRenderTarget)> GetLoadedRenderTextures();
|
||||
|
||||
ClydeDebugLayers DebugLayers { get; set; }
|
||||
|
||||
@@ -72,5 +73,20 @@ namespace Robust.Client.Graphics
|
||||
void RunOnWindowThread(Action action);
|
||||
|
||||
IFileDialogManagerImplementation? FileDialogImpl { get; }
|
||||
|
||||
bool VsyncEnabled { get; set; }
|
||||
|
||||
// Viewports
|
||||
|
||||
#if TOOLS
|
||||
|
||||
/// <summary>
|
||||
/// Fires <see cref="IClydeViewport.ClearCachedResources"/> on all viewports. For debugging.
|
||||
/// </summary>
|
||||
void ViewportsClearAllCached();
|
||||
|
||||
#endif // TOOLS
|
||||
|
||||
void RenderNow(IRenderTarget renderTarget, Action<IRenderHandle> callback);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,11 @@ namespace Robust.Client.Graphics
|
||||
/// </summary>
|
||||
public interface IClydeViewport : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// A unique ID for this viewport. No other viewport with this ID can ever exist in the app lifetime.
|
||||
/// </summary>
|
||||
long Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The render target that is rendered to when rendering this viewport.
|
||||
/// </summary>
|
||||
@@ -22,6 +27,16 @@ namespace Robust.Client.Graphics
|
||||
IEye? Eye { get; set; }
|
||||
Vector2i Size { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Raised when the viewport indicates that any cached rendering resources (e.g. render targets)
|
||||
/// should be purged.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This event is raised if the viewport is disposed (manually or via finalization).
|
||||
/// However, code should expect this event to be raised at any time, even if the viewport is not disposed fully.
|
||||
/// </remarks>
|
||||
event Action<ClearCachedViewportResourcesEvent> ClearCachedResources;
|
||||
|
||||
/// <summary>
|
||||
/// Color to clear the render target to before rendering. If null, no clearing will happen.
|
||||
/// </summary>
|
||||
@@ -85,4 +100,23 @@ namespace Robust.Client.Graphics
|
||||
IViewportControl control,
|
||||
in UIBox2i viewportBounds);
|
||||
}
|
||||
|
||||
public struct ClearCachedViewportResourcesEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IClydeViewport.Id"/> of the viewport.
|
||||
/// </summary>
|
||||
public readonly long ViewportId;
|
||||
|
||||
/// <summary>
|
||||
/// The viewport itself. This is not available if the viewport was disposed.
|
||||
/// </summary>
|
||||
public readonly IClydeViewport? Viewport;
|
||||
|
||||
internal ClearCachedViewportResourcesEvent(long viewportId, IClydeViewport? viewport)
|
||||
{
|
||||
ViewportId = viewportId;
|
||||
Viewport = viewport;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,6 +197,13 @@ namespace Robust.Client.Input
|
||||
locId += "-linux";
|
||||
}
|
||||
|
||||
#if MACOS
|
||||
if (key == Key.Alt)
|
||||
{
|
||||
locId += "-mac";
|
||||
}
|
||||
#endif
|
||||
|
||||
if (loc.TryGetString(locId, out var name))
|
||||
return name;
|
||||
|
||||
|
||||
@@ -562,7 +562,7 @@ namespace Robust.Client.Placement
|
||||
}
|
||||
|
||||
coordinates = InputManager.MouseScreenPosition;
|
||||
return true;
|
||||
return coordinates.IsValid;
|
||||
}
|
||||
|
||||
private bool CurrentEraserMouseCoordinates(out EntityCoordinates coordinates)
|
||||
|
||||
@@ -11,7 +11,6 @@ namespace Robust.Client.Replays.Commands;
|
||||
public abstract class BaseReplayCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] protected readonly IReplayPlaybackManager PlaybackManager = default!;
|
||||
protected ILocalizationManager Loc => LocalizationManager;
|
||||
|
||||
public override string Description => Loc.GetString($"cmd-{Command.Replace('_','-')}-desc");
|
||||
|
||||
|
||||
@@ -18,8 +18,9 @@
|
||||
<PackageReference Include="SpaceWizards.NFluidsynth" PrivateAssets="compile" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" />
|
||||
<PackageReference Include="OpenToolkit.Graphics" PrivateAssets="compile" />
|
||||
<PackageReference Include="OpenTK.OpenAL" PrivateAssets="compile" />
|
||||
<PackageReference Include="OpenTK.Audio.OpenAL" PrivateAssets="compile" />
|
||||
<PackageReference Include="SpaceWizards.SharpFont" PrivateAssets="compile" />
|
||||
<PackageReference Include="SpaceWizards.Sdl" PrivateAssets="compile" />
|
||||
<PackageReference Include="Robust.Natives" />
|
||||
<PackageReference Include="TerraFX.Interop.Windows" PrivateAssets="compile" />
|
||||
<PackageReference Condition="'$(RobustToolsBuild)' == 'True'" Include="JetBrains.Profiler.Api" PrivateAssets="compile" />
|
||||
@@ -63,6 +64,8 @@
|
||||
<RobustLinkAssemblies Include="TerraFX.Interop.Windows" />
|
||||
<RobustLinkAssemblies Include="TerraFX.Interop.Xlib" />
|
||||
<RobustLinkAssemblies Include="OpenToolkit.Graphics" />
|
||||
<RobustLinkAssemblies Include="SpaceWizards.Sdl" />
|
||||
<RobustLinkAssemblies Include="SpaceWizards.SharpFont" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="..\MSBuild\Robust.Properties.targets" />
|
||||
|
||||
192
Robust.Client/UserInterface/Axis.cs
Normal file
192
Robust.Client/UserInterface/Axis.cs
Normal file
@@ -0,0 +1,192 @@
|
||||
using System.Numerics;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.UserInterface;
|
||||
|
||||
/// <summary>
|
||||
/// Defines an axis that certain controls can be laid out along.
|
||||
/// </summary>
|
||||
/// <seealso cref="IAxisImplementation"/>
|
||||
public enum Axis : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Items are laid out left to right.
|
||||
/// </summary>
|
||||
Horizontal,
|
||||
|
||||
/// <summary>
|
||||
/// Items are laid out right to left.
|
||||
/// </summary>
|
||||
HorizontalReverse,
|
||||
|
||||
/// <summary>
|
||||
/// Items are laid out top to bottom.
|
||||
/// </summary>
|
||||
Vertical,
|
||||
|
||||
/// <summary>
|
||||
/// Items are laid out bottom to top.
|
||||
/// </summary>
|
||||
VerticalReverse,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface that implements the rules of an <see cref="Axis"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// To make it easier to write code that supports all 4 layout axis, layout code is advised to use generics over this
|
||||
/// type and its implementors.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// An axis has a "main" and a "cross" axis. For example,
|
||||
/// <see cref="HorizontalAxis"/> has the main axis go left to right, and the cross axis go top to bottom.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The functions in this interface primarily allow converting between "UI space" (the normal UI coordinate system) and
|
||||
/// "axis space" (same as UI space for <see cref="HorizontalAxis"/>). This allows you to write all code as if you're
|
||||
/// doing only horizontal layout, but automatically have it work on all axis.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="HorizontalAxis"/>
|
||||
/// <seealso cref="HorizontalReverseAxis"/>
|
||||
/// <seealso cref="VerticalAxis"/>
|
||||
/// <seealso cref="VerticalReverseAxis"/>
|
||||
public interface IAxisImplementation
|
||||
{
|
||||
//
|
||||
// To/from axis space conversions
|
||||
//
|
||||
|
||||
/// <summary>
|
||||
/// Convert a size value (e.g. from <see cref="Control.DesiredSize"/>) from UI space to axis space.
|
||||
/// </summary>
|
||||
static abstract Vector2 SizeToAxis(Vector2 size);
|
||||
|
||||
/// <summary>
|
||||
/// Convert a size value (e.g. for <see cref="Control.Measure"/>) from axis space to UI space.
|
||||
/// </summary>
|
||||
static abstract Vector2 SizeFromAxis(Vector2 size);
|
||||
|
||||
/// <summary>
|
||||
/// Convert a box (e.g. for <see cref="Control.Arrange"/>) from axis space to UI space.
|
||||
/// </summary>
|
||||
/// <param name="box">The box to convert, in axis space.</param>
|
||||
/// <param name="spaceSize">The amount of space, in UI space, that the layout is happening relative to.</param>
|
||||
static abstract UIBox2 BoxFromAxis(UIBox2 box, Vector2 spaceSize);
|
||||
|
||||
//
|
||||
// Control
|
||||
//
|
||||
|
||||
/// <summary>
|
||||
/// Gets the "expand flag" (<see cref="Control.HorizontalExpand"/> or <see cref="Control.VerticalExpand"/>) for a
|
||||
/// control that is appropriate for the main axis.
|
||||
/// </summary>
|
||||
static abstract bool GetMainExpandFlag(Control control);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Axis implementation for <see cref="Axis.Horizontal"/>.
|
||||
/// </summary>
|
||||
public struct HorizontalAxis : IAxisImplementation
|
||||
{
|
||||
public static Vector2 SizeToAxis(Vector2 size)
|
||||
{
|
||||
return size;
|
||||
}
|
||||
|
||||
public static Vector2 SizeFromAxis(Vector2 size)
|
||||
{
|
||||
return size;
|
||||
}
|
||||
|
||||
public static UIBox2 BoxFromAxis(UIBox2 box, Vector2 spaceSize)
|
||||
{
|
||||
return box;
|
||||
}
|
||||
|
||||
public static bool GetMainExpandFlag(Control control)
|
||||
{
|
||||
return control.HorizontalExpand;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Axis implementation for <see cref="Axis.HorizontalReverse"/>.
|
||||
/// </summary>
|
||||
public struct HorizontalReverseAxis : IAxisImplementation
|
||||
{
|
||||
public static Vector2 SizeToAxis(Vector2 size)
|
||||
{
|
||||
return size;
|
||||
}
|
||||
|
||||
public static Vector2 SizeFromAxis(Vector2 size)
|
||||
{
|
||||
return size;
|
||||
}
|
||||
|
||||
public static UIBox2 BoxFromAxis(UIBox2 box, Vector2 spaceSize)
|
||||
{
|
||||
return new UIBox2(spaceSize.X - box.Right, box.Top, spaceSize.X - box.Left, box.Bottom);
|
||||
}
|
||||
|
||||
public static bool GetMainExpandFlag(Control control)
|
||||
{
|
||||
return control.HorizontalExpand;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Axis implementation for <see cref="Axis.Vertical"/>.
|
||||
/// </summary>
|
||||
public struct VerticalAxis : IAxisImplementation
|
||||
{
|
||||
public static Vector2 SizeToAxis(Vector2 size)
|
||||
{
|
||||
return new Vector2(size.Y, size.X);
|
||||
}
|
||||
|
||||
public static Vector2 SizeFromAxis(Vector2 size)
|
||||
{
|
||||
return new Vector2(size.Y, size.X);
|
||||
}
|
||||
|
||||
public static UIBox2 BoxFromAxis(UIBox2 box, Vector2 spaceSize)
|
||||
{
|
||||
return new UIBox2(box.Top, box.Left, box.Bottom, box.Right);
|
||||
}
|
||||
|
||||
public static bool GetMainExpandFlag(Control control)
|
||||
{
|
||||
return control.VerticalExpand;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Axis implementation for <see cref="Axis.VerticalReverse"/>.
|
||||
/// </summary>
|
||||
public struct VerticalReverseAxis : IAxisImplementation
|
||||
{
|
||||
public static Vector2 SizeToAxis(Vector2 size)
|
||||
{
|
||||
return new Vector2(size.Y, size.X);
|
||||
}
|
||||
|
||||
public static Vector2 SizeFromAxis(Vector2 size)
|
||||
{
|
||||
return new Vector2(size.Y, size.X);
|
||||
}
|
||||
|
||||
public static UIBox2 BoxFromAxis(UIBox2 box, Vector2 spaceSize)
|
||||
{
|
||||
return new UIBox2(box.Top, spaceSize.Y - box.Right, box.Bottom, spaceSize.Y - box.Left);
|
||||
}
|
||||
|
||||
public static bool GetMainExpandFlag(Control control)
|
||||
{
|
||||
return control.VerticalExpand;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1049,7 +1049,7 @@ namespace Robust.Client.UserInterface
|
||||
Ignore = 2,
|
||||
}
|
||||
|
||||
public sealed class OrderedChildCollection : ICollection<Control>, IReadOnlyCollection<Control>
|
||||
public sealed class OrderedChildCollection : ICollection<Control>, IReadOnlyList<Control>
|
||||
{
|
||||
private readonly Control Owner;
|
||||
|
||||
@@ -1101,6 +1101,7 @@ namespace Robust.Client.UserInterface
|
||||
|
||||
int ICollection<Control>.Count => Owner.ChildCount;
|
||||
int IReadOnlyCollection<Control>.Count => Owner.ChildCount;
|
||||
public Control this[int index] => Owner._orderedChildren[index];
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
|
||||
@@ -27,8 +27,6 @@ namespace Robust.Client.UserInterface.Controls
|
||||
/// </remarks>
|
||||
public AlignMode Align { get; set; }
|
||||
|
||||
private bool Vertical => Orientation == LayoutOrientation.Vertical;
|
||||
|
||||
public LayoutOrientation Orientation
|
||||
{
|
||||
get => _orientation;
|
||||
@@ -56,19 +54,24 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
protected override Vector2 MeasureOverride(Vector2 availableSize)
|
||||
{
|
||||
// Account for separation.
|
||||
var separation = ActualSeparation * (Children.Where(c => c.Visible).Count() - 1);
|
||||
var desiredSize = Vector2.Zero;
|
||||
if (Vertical)
|
||||
if (Orientation == LayoutOrientation.Vertical)
|
||||
{
|
||||
desiredSize.Y += separation;
|
||||
availableSize.Y = Math.Max(0, availableSize.Y - separation);
|
||||
return MeasureItems<VerticalAxis>(availableSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
desiredSize.X += separation;
|
||||
availableSize.X = Math.Max(0, availableSize.X - separation);
|
||||
return MeasureItems<HorizontalAxis>(availableSize);
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 MeasureItems<TAxis>(Vector2 availableSize) where TAxis : IAxisImplementation
|
||||
{
|
||||
availableSize = TAxis.SizeToAxis(availableSize);
|
||||
|
||||
// Account for separation.
|
||||
var separation = ActualSeparation * (Children.Where(c => c.Visible).Count() - 1);
|
||||
var desiredSize = new Vector2(separation, 0);
|
||||
availableSize.X = Math.Max(0, availableSize.X) - separation;
|
||||
|
||||
// First, we measure non-stretching children.
|
||||
foreach (var child in Children)
|
||||
@@ -76,46 +79,74 @@ namespace Robust.Client.UserInterface.Controls
|
||||
if (!child.Visible)
|
||||
continue;
|
||||
|
||||
child.Measure(availableSize);
|
||||
child.Measure(TAxis.SizeFromAxis(availableSize));
|
||||
var childDesired = TAxis.SizeToAxis(child.DesiredSize);
|
||||
|
||||
if (Vertical)
|
||||
{
|
||||
desiredSize.Y += child.DesiredSize.Y;
|
||||
desiredSize.X = Math.Max(desiredSize.X, child.DesiredSize.X);
|
||||
availableSize.Y = Math.Max(0, availableSize.Y - child.DesiredSize.Y);
|
||||
}
|
||||
else
|
||||
{
|
||||
desiredSize.X += child.DesiredSize.X;
|
||||
desiredSize.Y = Math.Max(desiredSize.Y, child.DesiredSize.Y);
|
||||
availableSize.X = Math.Max(0, availableSize.X - child.DesiredSize.X);
|
||||
}
|
||||
desiredSize.X += childDesired.X;
|
||||
desiredSize.Y = Math.Max(desiredSize.Y, childDesired.Y);
|
||||
|
||||
availableSize.X = Math.Max(0, availableSize.X - childDesired.X);
|
||||
}
|
||||
|
||||
return desiredSize;
|
||||
return TAxis.SizeFromAxis(desiredSize);
|
||||
}
|
||||
|
||||
protected override Vector2 ArrangeOverride(Vector2 finalSize)
|
||||
{
|
||||
var separation = ActualSeparation;
|
||||
var visibleChildCount = Children.Where(c => c.Visible).Count();
|
||||
|
||||
var stretchAvail = Vertical ? finalSize.Y : finalSize.X;
|
||||
if (Orientation == LayoutOrientation.Vertical)
|
||||
{
|
||||
LayOutItems<VerticalAxis>(default, finalSize, Align, Children, 0, ChildCount, separation);
|
||||
}
|
||||
else
|
||||
{
|
||||
LayOutItems<HorizontalAxis>(default, finalSize, Align, Children, 0, ChildCount, separation);
|
||||
}
|
||||
|
||||
return finalSize;
|
||||
}
|
||||
|
||||
internal static void LayOutItems<TAxis>(
|
||||
Vector2 baseOffset,
|
||||
Vector2 finalSize,
|
||||
AlignMode align,
|
||||
OrderedChildCollection children,
|
||||
int start,
|
||||
int end,
|
||||
float separation,
|
||||
Vector2? fixedSize = null)
|
||||
where TAxis : IAxisImplementation
|
||||
{
|
||||
var realFinalSize = finalSize;
|
||||
finalSize = TAxis.SizeToAxis(finalSize);
|
||||
fixedSize = fixedSize == null ? null : TAxis.SizeToAxis(fixedSize.Value);
|
||||
|
||||
var visibleChildCount = 0;
|
||||
for (var i = start; i < end; i++)
|
||||
{
|
||||
if (children[i].Visible)
|
||||
visibleChildCount += 1;
|
||||
}
|
||||
|
||||
var stretchAvail = finalSize.X;
|
||||
stretchAvail -= separation * (visibleChildCount - 1);
|
||||
stretchAvail = Math.Max(0, stretchAvail);
|
||||
|
||||
// Step one: figure out the sizes of all our children and whether they want to stretch.
|
||||
var sizeList = new List<(Control control, float size, bool stretch)>(visibleChildCount);
|
||||
var totalStretchRatio = 0f;
|
||||
foreach (var child in Children)
|
||||
for (var i = start; i < end; i++)
|
||||
{
|
||||
var child = children[i];
|
||||
if (!child.Visible)
|
||||
continue;
|
||||
|
||||
bool stretch = Vertical ? child.VerticalExpand : child.HorizontalExpand;
|
||||
bool stretch = TAxis.GetMainExpandFlag(child);
|
||||
if (!stretch)
|
||||
{
|
||||
var size = Vertical ? child.DesiredSize.Y : child.DesiredSize.X;
|
||||
var measuredSize = fixedSize ?? TAxis.SizeToAxis(child.DesiredSize);
|
||||
var size = measuredSize.X;
|
||||
size = Math.Clamp(size, 0, stretchAvail);
|
||||
stretchAvail -= size;
|
||||
sizeList.Add((child, size, false));
|
||||
@@ -146,7 +177,8 @@ namespace Robust.Client.UserInterface.Controls
|
||||
continue;
|
||||
|
||||
var share = stretchAvail * control.SizeFlagsStretchRatio / totalStretchRatio;
|
||||
var desired = Vertical ? control.DesiredSize.Y : control.DesiredSize.X;
|
||||
var measuredSize = fixedSize ?? TAxis.SizeToAxis(control.DesiredSize);
|
||||
var desired = measuredSize.X;
|
||||
if (share >= desired)
|
||||
{
|
||||
sizeList[i] = (control, share, true);
|
||||
@@ -164,7 +196,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
else
|
||||
{
|
||||
// No stretching children -> offset the children based on the alignment.
|
||||
switch (Align)
|
||||
switch (align)
|
||||
{
|
||||
case AlignMode.Begin:
|
||||
break;
|
||||
@@ -190,22 +222,14 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
first = false;
|
||||
|
||||
UIBox2 targetBox;
|
||||
if (Vertical)
|
||||
{
|
||||
targetBox = new UIBox2(0, offset, finalSize.X, offset + size);
|
||||
}
|
||||
else
|
||||
{
|
||||
targetBox = new UIBox2(offset, 0, offset + size, finalSize.Y);
|
||||
}
|
||||
var targetBox = TAxis.BoxFromAxis(new UIBox2(offset, 0, offset + size, finalSize.Y), realFinalSize);
|
||||
|
||||
targetBox = targetBox.Translated(baseOffset);
|
||||
|
||||
control.Arrange(targetBox);
|
||||
|
||||
offset += size;
|
||||
}
|
||||
|
||||
return finalSize;
|
||||
}
|
||||
|
||||
public enum AlignMode : byte
|
||||
|
||||
@@ -121,11 +121,14 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
if (show)
|
||||
{
|
||||
if (Root == null)
|
||||
throw new InvalidOperationException("No UI root! We can't pop up!");
|
||||
|
||||
var globalPos = GlobalPosition;
|
||||
_popupVBox.Measure(Vector2Helpers.Infinity);
|
||||
var (minX, minY) = _popupVBox.DesiredSize;
|
||||
var box = UIBox2.FromDimensions(globalPos, new Vector2(Math.Max(minX, Width), minY));
|
||||
UserInterfaceManager.ModalRoot.AddChild(_popup);
|
||||
Root.ModalRoot.AddChild(_popup);
|
||||
_popup.Open(box);
|
||||
}
|
||||
else
|
||||
@@ -136,7 +139,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
private void OnPopupHide()
|
||||
{
|
||||
UserInterfaceManager.ModalRoot.RemoveChild(_popup);
|
||||
_popup.Orphan();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -135,7 +135,13 @@ namespace Robust.Client.UserInterface.Controls
|
||||
ClydeWindow.Resized += OnWindowResized;
|
||||
|
||||
_root = UserInterfaceManager.CreateWindowRoot(ClydeWindow);
|
||||
_root.CreateRootControls();
|
||||
|
||||
// Add ourselves *after* creating the root.
|
||||
// This way root controls are valid in EnteredTree().
|
||||
// We have to re-organize the controls after, of course.
|
||||
_root.AddChild(this);
|
||||
SetPositionFirst();
|
||||
|
||||
// Resize the window by our UIScale
|
||||
ClydeWindow.Size = new((int)(ClydeWindow.Size.X * UIScale), (int)(ClydeWindow.Size.Y * UIScale));
|
||||
|
||||
@@ -153,13 +153,16 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
if (show)
|
||||
{
|
||||
if (Root == null)
|
||||
throw new InvalidOperationException("No UI root! We can't pop up!");
|
||||
|
||||
var globalPos = GlobalPosition;
|
||||
globalPos.Y += Size.Y + 1; // Place it below us, with a safety margin.
|
||||
globalPos.Y -= Margin.SumVertical;
|
||||
OptionsScroll.Measure(Window?.Size ?? Vector2Helpers.Infinity);
|
||||
var (minX, minY) = OptionsScroll.DesiredSize;
|
||||
var box = UIBox2.FromDimensions(globalPos, new Vector2(Math.Max(minX, Width), minY));
|
||||
UserInterfaceManager.ModalRoot.AddChild(_popup);
|
||||
Root.ModalRoot.AddChild(_popup);
|
||||
_popup.Open(box);
|
||||
}
|
||||
else
|
||||
@@ -170,7 +173,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
private void OnPopupHide()
|
||||
{
|
||||
UserInterfaceManager.ModalRoot.RemoveChild(_popup);
|
||||
_popup.Orphan();
|
||||
}
|
||||
|
||||
private void ButtonOnPressed(ButtonEventArgs obj)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Numerics;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.RichText;
|
||||
@@ -154,6 +155,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
public void SetMessage(Index index, FormattedMessage message, Type[]? tagsAllowed = null, Color? defaultColor = null)
|
||||
{
|
||||
var atBottom = !_scrollDownButton.Visible;
|
||||
var oldEntry = _entries[index];
|
||||
var font = _getFont();
|
||||
_totalContentHeight -= oldEntry.Height + font.GetLineSeparation(UIScale);
|
||||
@@ -164,6 +166,10 @@ namespace Robust.Client.UserInterface.Controls
|
||||
_entries[index] = entry;
|
||||
|
||||
AddNewItemHeight(font, in entry);
|
||||
|
||||
_scrollBar.MaxValue = Math.Max(_scrollBar.Page, _totalContentHeight);
|
||||
if (atBottom)
|
||||
_scrollBar.Value = _scrollBar.MaxValue;
|
||||
}
|
||||
|
||||
private void AddNewItemHeight(Font font, in RichTextEntry entry)
|
||||
@@ -278,7 +284,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.Contracts.Pure]
|
||||
[Pure]
|
||||
private Font _getFont()
|
||||
{
|
||||
if (TryGetStyleProperty<Font>("font", out var font))
|
||||
@@ -289,7 +295,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
return UserInterfaceManager.ThemeDefaults.DefaultFont;
|
||||
}
|
||||
|
||||
[System.Diagnostics.Contracts.Pure]
|
||||
[Pure]
|
||||
private StyleBox? _getStyleBox()
|
||||
{
|
||||
if (StyleBoxOverride != null)
|
||||
@@ -301,14 +307,14 @@ namespace Robust.Client.UserInterface.Controls
|
||||
return box;
|
||||
}
|
||||
|
||||
[System.Diagnostics.Contracts.Pure]
|
||||
[Pure]
|
||||
private float _getScrollSpeed()
|
||||
{
|
||||
// The scroll speed depends on the UI scale because the scroll bar is working with physical pixels.
|
||||
return GetScrollSpeed(_getFont(), UIScale);
|
||||
}
|
||||
|
||||
[System.Diagnostics.Contracts.Pure]
|
||||
[Pure]
|
||||
private UIBox2 _getContentBox()
|
||||
{
|
||||
var style = _getStyleBox();
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
public bool CloseOnEscape { get; set; } = true;
|
||||
|
||||
public virtual void Open(UIBox2? box = null, Vector2? altPos = null)
|
||||
public virtual void Open(UIBox2? box = null, Vector2? altPos = null, Vector2? altPosUp = null)
|
||||
{
|
||||
if (Visible)
|
||||
{
|
||||
@@ -44,10 +44,12 @@ namespace Robust.Client.UserInterface.Controls
|
||||
if (box != null &&
|
||||
(_desiredSize != box.Value.Size ||
|
||||
PopupContainer.GetPopupOrigin(this) != box.Value.TopLeft ||
|
||||
PopupContainer.GetAltOrigin(this) != altPos))
|
||||
PopupContainer.GetAltOrigin(this) != altPos ||
|
||||
PopupContainer.GetAltOriginUp(this) != altPosUp))
|
||||
{
|
||||
PopupContainer.SetPopupOrigin(this, box.Value.TopLeft);
|
||||
PopupContainer.SetAltOrigin(this, altPos);
|
||||
PopupContainer.SetAltOriginUp(this, altPosUp);
|
||||
|
||||
_desiredSize = box.Value.Size;
|
||||
InvalidateMeasure();
|
||||
|
||||
@@ -27,6 +27,13 @@ namespace Robust.Client.UserInterface.Controls
|
||||
public static readonly AttachedProperty AltOriginProperty = AttachedProperty.Create("AltOrigin",
|
||||
typeof(PopupContainer), typeof(Vector2?), changed: PopupOriginChangedCallback);
|
||||
|
||||
/// <summary>
|
||||
/// Alternative position to bottom-left-align the popup if <see cref="PopupOriginProperty"/>
|
||||
/// would put it off-screen vertically.
|
||||
/// </summary>
|
||||
public static readonly AttachedProperty AltOriginUpProperty = AttachedProperty.Create("AltOriginUp",
|
||||
typeof(PopupContainer), typeof(Vector2?), changed: PopupOriginChangedCallback);
|
||||
|
||||
public PopupContainer()
|
||||
{
|
||||
RectClipContent = true;
|
||||
@@ -47,11 +54,21 @@ namespace Robust.Client.UserInterface.Controls
|
||||
return control.GetValue<Vector2?>(AltOriginProperty);
|
||||
}
|
||||
|
||||
public static Vector2? GetAltOriginUp(Control control)
|
||||
{
|
||||
return control.GetValue<Vector2?>(AltOriginUpProperty);
|
||||
}
|
||||
|
||||
public static void SetAltOrigin(Control control, Vector2? origin)
|
||||
{
|
||||
control.SetValue(AltOriginProperty, origin);
|
||||
}
|
||||
|
||||
public static void SetAltOriginUp(Control control, Vector2? origin)
|
||||
{
|
||||
control.SetValue(AltOriginUpProperty, origin);
|
||||
}
|
||||
|
||||
private static void PopupOriginChangedCallback(Control owner, AttachedPropertyChangedEventArgs eventArgs)
|
||||
{
|
||||
if (owner.Parent is PopupContainer container)
|
||||
@@ -67,47 +84,58 @@ namespace Robust.Client.UserInterface.Controls
|
||||
var size = child.DesiredSize;
|
||||
var offset = child.GetValue<Vector2>(PopupOriginProperty);
|
||||
var altPos = child.GetValue<Vector2?>(AltOriginProperty);
|
||||
var altPosUp = child.GetValue<Vector2?>(AltOriginUpProperty);
|
||||
|
||||
var (r, b) = size + offset; // bottom right corner.
|
||||
var box = UIBox2.FromDimensions(offset, size);
|
||||
|
||||
var isAltPos = false;
|
||||
var isAltPosUp = false;
|
||||
|
||||
// Clamp the right edge.
|
||||
if (r > Width)
|
||||
if (box.Right > Width)
|
||||
{
|
||||
// Try to position at alt pos.
|
||||
if (altPos != null && altPos.Value.X - size.X > 0)
|
||||
{
|
||||
// There is horizontal room at the alt pos so there we go.
|
||||
isAltPos = true;
|
||||
offset = new Vector2(altPos.Value.X - size.X, altPos.Value.Y);
|
||||
(_, b) = size + offset;
|
||||
box = UIBox2.FromDimensions(new Vector2(altPos.Value.X - size.X, altPos.Value.Y), size);
|
||||
}
|
||||
else
|
||||
{
|
||||
offset -= new Vector2(r - Width, 0);
|
||||
box = box.Translated(new Vector2(-(box.Right - Width), 0));
|
||||
}
|
||||
}
|
||||
|
||||
// Clamp the bottom edge.
|
||||
if (b > Height)
|
||||
if (box.Bottom > Height)
|
||||
{
|
||||
offset -= new Vector2(0, b - Height);
|
||||
// Try to position at alt pos.
|
||||
if (altPosUp != null && altPosUp.Value.Y - size.Y > 0)
|
||||
{
|
||||
// There is vertical room at the alt pos so there we go.
|
||||
isAltPosUp = true;
|
||||
box = UIBox2.FromDimensions(new Vector2(altPosUp.Value.X, altPosUp.Value.Y - size.Y), size);
|
||||
}
|
||||
else
|
||||
{
|
||||
box = box.Translated(new Vector2(0, -(box.Bottom - Height)));
|
||||
}
|
||||
}
|
||||
|
||||
// Try to clamp the left edge.
|
||||
if (offset.X < 0 && !isAltPos)
|
||||
if (box.Left < 0 && !isAltPos)
|
||||
{
|
||||
offset -= new Vector2(offset.X, 0);
|
||||
box = box.Translated(new Vector2(-offset.X, 0));
|
||||
}
|
||||
|
||||
// Try to clamp the top edge.
|
||||
if (offset.Y < 0)
|
||||
if (box.Top < 0 && !isAltPosUp)
|
||||
{
|
||||
offset -= new Vector2(0, offset.Y);
|
||||
box = box.Translated(new Vector2(0, -offset.Y));
|
||||
}
|
||||
|
||||
child.Arrange(UIBox2.FromDimensions(offset, size));
|
||||
child.Arrange(box);
|
||||
}
|
||||
|
||||
return finalSize;
|
||||
|
||||
@@ -74,7 +74,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
else
|
||||
{
|
||||
_updating = true;
|
||||
Value = MathHelper.Lerp(Value, ValueTarget, Math.Min(args.DeltaSeconds * 15, 1));
|
||||
Value = UIAnimations.LerpAnimate(Value, ValueTarget, args.DeltaSeconds, 15);
|
||||
_updating = false;
|
||||
}
|
||||
}
|
||||
|
||||
270
Robust.Client/UserInterface/Controls/TableContainer.cs
Normal file
270
Robust.Client/UserInterface/Controls/TableContainer.cs
Normal file
@@ -0,0 +1,270 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.UserInterface.Controls;
|
||||
|
||||
internal sealed class TableContainer : Container
|
||||
{
|
||||
private int _columns = 1;
|
||||
|
||||
/// <summary>
|
||||
/// The absolute minimum width a column can be forced to.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// If a column *asks* for less width than this (small contents), it can still be smaller.
|
||||
/// But if it asks for more it cannot go below this width.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public float MinForcedColumnWidth { get; set; } = 50;
|
||||
|
||||
// Scratch space used while calculating layout, cached to avoid regular allocations during layout pass.
|
||||
private ColumnData[] _columnDataCache = [];
|
||||
private RowData[] _rowDataCache = [];
|
||||
|
||||
/// <summary>
|
||||
/// How many columns should be displayed.
|
||||
/// </summary>
|
||||
public int Columns
|
||||
{
|
||||
get => _columns;
|
||||
set
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(value, 1, nameof(value));
|
||||
|
||||
_columns = value;
|
||||
}
|
||||
}
|
||||
|
||||
protected override Vector2 MeasureOverride(Vector2 availableSize)
|
||||
{
|
||||
ResetCachedArrays();
|
||||
|
||||
// Do a first pass measuring all child controls as if they're given infinite space.
|
||||
// This gives us a maximum width the columns want, which we use to proportion them later.
|
||||
var columnIdx = 0;
|
||||
foreach (var child in Children)
|
||||
{
|
||||
ref var column = ref _columnDataCache[columnIdx];
|
||||
|
||||
child.Measure(new Vector2(float.PositiveInfinity, float.PositiveInfinity));
|
||||
column.MaxWidth = Math.Max(column.MaxWidth, child.DesiredSize.X);
|
||||
|
||||
columnIdx += 1;
|
||||
if (columnIdx == _columns)
|
||||
columnIdx = 0;
|
||||
}
|
||||
|
||||
// Calculate Slack and MinWidth for all columns. Also calculate sums for all columns.
|
||||
var totalMinWidth = 0f;
|
||||
var totalMaxWidth = 0f;
|
||||
var totalSlack = 0f;
|
||||
|
||||
for (var c = 0; c < _columns; c++)
|
||||
{
|
||||
ref var column = ref _columnDataCache[c];
|
||||
column.MinWidth = Math.Min(column.MaxWidth, MinForcedColumnWidth);
|
||||
column.Slack = column.MaxWidth - column.MinWidth;
|
||||
|
||||
totalMinWidth += column.MinWidth;
|
||||
totalMaxWidth += column.MaxWidth;
|
||||
totalSlack += column.Slack;
|
||||
}
|
||||
|
||||
if (totalMaxWidth <= availableSize.X)
|
||||
{
|
||||
// We want less horizontal space than we're given. Huh, that's convenient.
|
||||
// Just set assigned width to be however much they asked for.
|
||||
// We could probably skip the second measure pass in this scenario,
|
||||
// but that's just an optimization, so I don't care right now.
|
||||
//
|
||||
// There's probably a very clever way to make this behavior work with the else block of logic,
|
||||
// just by fiddling with the math.
|
||||
// I'm dumb, it's 4:30 AM. Yeah, I *started* at 2 AM.
|
||||
for (var c = 0; c < _columns; c++)
|
||||
{
|
||||
ref var column = ref _columnDataCache[c];
|
||||
|
||||
column.AssignedWidth = column.MaxWidth;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// We don't have enough horizontal space,
|
||||
// at least without causing *some* sort of word wrapping (assuming text contents).
|
||||
//
|
||||
// Assign horizontal space proportional to the wanted maximum size of the columns.
|
||||
var assignableWidth = Math.Max(0, availableSize.X - totalMinWidth);
|
||||
for (var c = 0; c < _columns; c++)
|
||||
{
|
||||
ref var column = ref _columnDataCache[c];
|
||||
|
||||
var slackRatio = column.Slack / totalSlack;
|
||||
column.AssignedWidth = column.MinWidth + slackRatio * assignableWidth;
|
||||
}
|
||||
}
|
||||
|
||||
// Go over controls for a second measuring pass, this time giving them their assigned measure width.
|
||||
// This will give us a height to slot into per-row data.
|
||||
// We still measure assuming infinite vertical space.
|
||||
// This control can't properly handle being constrained on the Y axis.
|
||||
columnIdx = 0;
|
||||
var rowIdx = 0;
|
||||
foreach (var child in Children)
|
||||
{
|
||||
ref var column = ref _columnDataCache[columnIdx];
|
||||
ref var row = ref _rowDataCache[rowIdx];
|
||||
|
||||
child.Measure(new Vector2(column.AssignedWidth, float.PositiveInfinity));
|
||||
row.MeasuredHeight = Math.Max(row.MeasuredHeight, child.DesiredSize.Y);
|
||||
|
||||
columnIdx += 1;
|
||||
if (columnIdx == _columns)
|
||||
{
|
||||
columnIdx = 0;
|
||||
rowIdx += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Sum up height of all rows to get final measured table height.
|
||||
var totalHeight = 0f;
|
||||
for (var r = 0; r < _rowDataCache.Length; r++)
|
||||
{
|
||||
ref var row = ref _rowDataCache[r];
|
||||
totalHeight += row.MeasuredHeight;
|
||||
}
|
||||
|
||||
return new Vector2(Math.Min(availableSize.X, totalMaxWidth), totalHeight);
|
||||
}
|
||||
|
||||
protected override Vector2 ArrangeOverride(Vector2 finalSize)
|
||||
{
|
||||
// TODO: Expand to fit given vertical space.
|
||||
|
||||
// Calculate MinWidth and Slack sums again from column data.
|
||||
// We could've cached these from measure but whatever.
|
||||
var totalMinWidth = 0f;
|
||||
var totalSlack = 0f;
|
||||
|
||||
for (var c = 0; c < _columns; c++)
|
||||
{
|
||||
ref var column = ref _columnDataCache[c];
|
||||
totalMinWidth += column.MinWidth;
|
||||
totalSlack += column.Slack;
|
||||
}
|
||||
|
||||
// Calculate new width based on final given size, also assign horizontal positions of all columns.
|
||||
var assignableWidth = Math.Max(0, finalSize.X - totalMinWidth);
|
||||
var xPos = 0f;
|
||||
for (var c = 0; c < _columns; c++)
|
||||
{
|
||||
ref var column = ref _columnDataCache[c];
|
||||
|
||||
var slackRatio = column.Slack / totalSlack;
|
||||
column.ArrangedWidth = column.MinWidth + slackRatio * assignableWidth;
|
||||
column.ArrangedX = xPos;
|
||||
|
||||
xPos += column.ArrangedWidth;
|
||||
}
|
||||
|
||||
// Do actual arrangement row-by-row.
|
||||
var arrangeY = 0f;
|
||||
for (var r = 0; r < _rowDataCache.Length; r++)
|
||||
{
|
||||
ref var row = ref _rowDataCache[r];
|
||||
|
||||
for (var c = 0; c < _columns; c++)
|
||||
{
|
||||
ref var column = ref _columnDataCache[c];
|
||||
var index = c + r * _columns;
|
||||
|
||||
if (index >= ChildCount) // Quit early if we don't actually fill out the row.
|
||||
break;
|
||||
var child = GetChild(c + r * _columns);
|
||||
|
||||
child.Arrange(UIBox2.FromDimensions(column.ArrangedX, arrangeY, column.ArrangedWidth, row.MeasuredHeight));
|
||||
}
|
||||
|
||||
arrangeY += row.MeasuredHeight;
|
||||
}
|
||||
|
||||
return finalSize with { Y = arrangeY };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure cached array space is allocated to correct size and is reset to a clean slate.
|
||||
/// </summary>
|
||||
private void ResetCachedArrays()
|
||||
{
|
||||
// 1-argument Array.Clear() is not currently available in sandbox (added in .NET 6).
|
||||
|
||||
if (_columnDataCache.Length != _columns)
|
||||
_columnDataCache = new ColumnData[_columns];
|
||||
|
||||
Array.Clear(_columnDataCache, 0, _columnDataCache.Length);
|
||||
|
||||
var rowCount = ChildCount / _columns;
|
||||
if (ChildCount % _columns != 0)
|
||||
rowCount += 1;
|
||||
|
||||
if (rowCount != _rowDataCache.Length)
|
||||
_rowDataCache = new RowData[rowCount];
|
||||
|
||||
Array.Clear(_rowDataCache, 0, _rowDataCache.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Per-column data used during layout.
|
||||
/// </summary>
|
||||
private struct ColumnData
|
||||
{
|
||||
// Measure data.
|
||||
|
||||
/// <summary>
|
||||
/// The maximum width any control in this column wants, if given infinite space.
|
||||
/// Maximum of all controls on the column.
|
||||
/// </summary>
|
||||
public float MaxWidth;
|
||||
|
||||
/// <summary>
|
||||
/// The minimum width this column may be given.
|
||||
/// This is either <see cref="MaxWidth"/> or <see cref="TableContainer.MinForcedColumnWidth"/>.
|
||||
/// </summary>
|
||||
public float MinWidth;
|
||||
|
||||
/// <summary>
|
||||
/// Difference between max and min width; how much this column can expand from its minimum.
|
||||
/// </summary>
|
||||
public float Slack;
|
||||
|
||||
/// <summary>
|
||||
/// How much horizontal space this column was assigned at measure time.
|
||||
/// </summary>
|
||||
public float AssignedWidth;
|
||||
|
||||
// Arrange data.
|
||||
|
||||
/// <summary>
|
||||
/// How much horizontal space this column was assigned at arrange time.
|
||||
/// </summary>
|
||||
public float ArrangedWidth;
|
||||
|
||||
/// <summary>
|
||||
/// The horizontal position this column was assigned at arrange time.
|
||||
/// </summary>
|
||||
public float ArrangedX;
|
||||
}
|
||||
|
||||
private struct RowData
|
||||
{
|
||||
// Measure data.
|
||||
|
||||
/// <summary>
|
||||
/// How much height the tallest control on this row was measured at,
|
||||
/// measuring for infinite vertical space but assigned column width.
|
||||
/// </summary>
|
||||
public float MeasuredHeight;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,9 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
public Color? BackgroundColor { get; set; }
|
||||
|
||||
public virtual LayoutContainer PopupRoot => throw new NotSupportedException();
|
||||
public virtual PopupContainer ModalRoot => throw new NotSupportedException();
|
||||
|
||||
private Color _styleBgColor;
|
||||
|
||||
internal Color ActualBgColor => BackgroundColor ?? _styleBgColor;
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
using Robust.Client.Graphics;
|
||||
using System;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
public sealed class WindowRoot : UIRoot
|
||||
{
|
||||
private PopupContainer? _modalRoot;
|
||||
private LayoutContainer? _popupRoot;
|
||||
|
||||
internal WindowRoot(IClydeWindow window)
|
||||
{
|
||||
Window = window;
|
||||
@@ -32,5 +34,39 @@ namespace Robust.Client.UserInterface.Controls
|
||||
/// </remarks>
|
||||
/// <seealso cref="CVars.ResAutoScaleEnabled"/>
|
||||
public bool DisableAutoScaling { get; set; } = true;
|
||||
|
||||
public override PopupContainer ModalRoot => _modalRoot ?? throw new InvalidOperationException(
|
||||
$"Tried to access root controls without calling {nameof(CreateRootControls)}!");
|
||||
|
||||
public override LayoutContainer PopupRoot => _popupRoot ?? throw new InvalidOperationException(
|
||||
$"Tried to access root controls without calling {nameof(CreateRootControls)}!");
|
||||
|
||||
/// <summary>
|
||||
/// Creates root controls (e.g. <see cref="UIRoot.ModalRoot"/>) that are necessary for the UI system to
|
||||
/// fully function.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This should be called *after* inserting the main content into this instance,
|
||||
/// so that the created root controls (e.g. popups) correctly stay on top.
|
||||
/// </remarks>
|
||||
public void CreateRootControls()
|
||||
{
|
||||
if (_modalRoot != null)
|
||||
throw new InvalidOperationException("We've already created root controls!");
|
||||
|
||||
_modalRoot = new PopupContainer
|
||||
{
|
||||
Name = nameof(ModalRoot),
|
||||
MouseFilter = MouseFilterMode.Ignore,
|
||||
};
|
||||
AddChild(_modalRoot);
|
||||
|
||||
_popupRoot = new LayoutContainer
|
||||
{
|
||||
Name = nameof(PopupRoot),
|
||||
MouseFilter = MouseFilterMode.Ignore
|
||||
};
|
||||
AddChild(_popupRoot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
330
Robust.Client/UserInterface/Controls/WrapContainer.cs
Normal file
330
Robust.Client/UserInterface/Controls/WrapContainer.cs
Normal file
@@ -0,0 +1,330 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.UserInterface.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// Lays of children sequentially, "wrapping" them onto another row/column if necessary.
|
||||
/// </summary>
|
||||
public sealed class WrapContainer : Container
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the amount of space between two children, on the main axis.
|
||||
/// </summary>
|
||||
public const string StylePropertySeparation = "separation";
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the amount of space between two children, on the cross axis.
|
||||
/// </summary>
|
||||
public const string StylePropertyCrossSeparation = "cross-separation";
|
||||
|
||||
// Parameters.
|
||||
private Axis _layoutAxis;
|
||||
private ItemJustification _justification;
|
||||
private bool _equalSize;
|
||||
private bool _reverse;
|
||||
private int? _separationOverride;
|
||||
private int? _crossSeparationOverride;
|
||||
|
||||
// Cached layout data.
|
||||
private ValueList<(int endIndex, float cross)> _rowIndices;
|
||||
private float _lastMeasureCross;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the amount of space between two children, on the main axis.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This property overrides <see cref="StylePropertySeparation"/>, if set.
|
||||
/// </remarks>
|
||||
public int? SeparationOverride
|
||||
{
|
||||
get => _separationOverride;
|
||||
set
|
||||
{
|
||||
_separationOverride = value;
|
||||
InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the amount of space between two children, on the cross axis.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This property overrides <see cref="StylePropertyCrossSeparation"/>, if set.
|
||||
/// </remarks>
|
||||
public int? CrossSeparationOverride
|
||||
{
|
||||
get => _crossSeparationOverride;
|
||||
set
|
||||
{
|
||||
_crossSeparationOverride = value;
|
||||
InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Axis"/> along which to lay out children.
|
||||
/// </summary>
|
||||
public Axis LayoutAxis
|
||||
{
|
||||
get => _layoutAxis;
|
||||
set
|
||||
{
|
||||
_layoutAxis = value;
|
||||
InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If true, all children will be laid out with the size of the largest child.
|
||||
/// </summary>
|
||||
public bool EqualSize
|
||||
{
|
||||
get => _equalSize;
|
||||
set
|
||||
{
|
||||
_equalSize = value;
|
||||
InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines where items will be laid out on an individual row/column.
|
||||
/// </summary>
|
||||
public ItemJustification Justification
|
||||
{
|
||||
get => _justification;
|
||||
set
|
||||
{
|
||||
_justification = value;
|
||||
InvalidateArrange();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If true, reverses the order on which wrapping rows/columns are laid out.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// On horizontal axis, the first children are on the top row, and new rows are added <i>downwards</i>.
|
||||
/// With <see cref="Reverse"/> set to true,
|
||||
/// the first children are instead on the bottom row, with new rows growing upwards.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public bool Reverse
|
||||
{
|
||||
get => _reverse;
|
||||
set
|
||||
{
|
||||
_reverse = value;
|
||||
InvalidateArrange();
|
||||
}
|
||||
}
|
||||
|
||||
private int ActualSeparation
|
||||
{
|
||||
get
|
||||
{
|
||||
if (TryGetStyleProperty(StylePropertySeparation, out int separation))
|
||||
{
|
||||
return separation;
|
||||
}
|
||||
|
||||
return SeparationOverride ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
private int ActualCrossSeparation
|
||||
{
|
||||
get
|
||||
{
|
||||
if (TryGetStyleProperty(StylePropertyCrossSeparation, out int separation))
|
||||
{
|
||||
return separation;
|
||||
}
|
||||
|
||||
return CrossSeparationOverride ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
protected override Vector2 MeasureOverride(Vector2 availableSize)
|
||||
{
|
||||
var axis = LayoutAxis;
|
||||
|
||||
return axis switch
|
||||
{
|
||||
Axis.Horizontal => MeasureImplementation<HorizontalAxis>(availableSize),
|
||||
Axis.HorizontalReverse => MeasureImplementation<HorizontalReverseAxis>(availableSize),
|
||||
Axis.Vertical => MeasureImplementation<VerticalAxis>(availableSize),
|
||||
Axis.VerticalReverse => MeasureImplementation<VerticalReverseAxis>(availableSize),
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
|
||||
private Vector2 MeasureImplementation<TAxis>(Vector2 availableSize) where TAxis : IAxisImplementation
|
||||
{
|
||||
_rowIndices.Clear();
|
||||
|
||||
var realAvailableSize = availableSize;
|
||||
availableSize = TAxis.SizeToAxis(availableSize);
|
||||
|
||||
// TODO: Round to pixels properly.
|
||||
var separation = ActualSeparation;
|
||||
var crossSeparation = ActualCrossSeparation;
|
||||
var curMainSize = 0f;
|
||||
var curCrossSize = 0f;
|
||||
var totalCrossSize = 0f;
|
||||
var maxMainSize = 0f;
|
||||
var firstOnRow = true;
|
||||
|
||||
var equalDesiredSize = _equalSize ? TAxis.SizeToAxis(GetMaxMeasure(realAvailableSize)) : default;
|
||||
var countLaidOut = 0;
|
||||
|
||||
foreach (var control in Children)
|
||||
{
|
||||
Vector2 controlDesiredSize;
|
||||
if (_equalSize)
|
||||
{
|
||||
controlDesiredSize = equalDesiredSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
control.Measure(realAvailableSize);
|
||||
controlDesiredSize = TAxis.SizeToAxis(control.DesiredSize);
|
||||
}
|
||||
|
||||
var controlMainSize = controlDesiredSize.X;
|
||||
var spaceTaken = controlMainSize + (firstOnRow ? 0 : separation);
|
||||
if (curMainSize + spaceTaken > availableSize.X)
|
||||
{
|
||||
// We've wrapped.
|
||||
RowEnd(lastRow: false);
|
||||
curMainSize = controlMainSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
curMainSize += spaceTaken;
|
||||
}
|
||||
curCrossSize = Math.Max(curCrossSize, controlDesiredSize.Y);
|
||||
firstOnRow = false;
|
||||
countLaidOut += 1;
|
||||
}
|
||||
|
||||
RowEnd(lastRow: true);
|
||||
|
||||
_lastMeasureCross = totalCrossSize;
|
||||
|
||||
return TAxis.SizeFromAxis(new Vector2(maxMainSize, totalCrossSize));
|
||||
|
||||
void RowEnd(bool lastRow)
|
||||
{
|
||||
maxMainSize = Math.Max(maxMainSize, curMainSize);
|
||||
totalCrossSize += curCrossSize;
|
||||
if (!lastRow)
|
||||
totalCrossSize += crossSeparation;
|
||||
_rowIndices.Add((countLaidOut, curCrossSize));
|
||||
curCrossSize = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 GetMaxDesired()
|
||||
{
|
||||
var vec = Vector2.Zero;
|
||||
|
||||
foreach (var child in Children)
|
||||
{
|
||||
vec = Vector2.Max(vec, child.DesiredSize);
|
||||
}
|
||||
|
||||
return vec;
|
||||
}
|
||||
|
||||
private Vector2 GetMaxMeasure(Vector2 availableSize)
|
||||
{
|
||||
var vec = Vector2.Zero;
|
||||
|
||||
foreach (var child in Children)
|
||||
{
|
||||
child.Measure(availableSize);
|
||||
vec = Vector2.Max(vec, child.DesiredSize);
|
||||
}
|
||||
|
||||
return vec;
|
||||
}
|
||||
|
||||
protected override Vector2 ArrangeOverride(Vector2 finalSize)
|
||||
{
|
||||
var axis = LayoutAxis;
|
||||
|
||||
return axis switch
|
||||
{
|
||||
Axis.Horizontal => ArrangeImplementation<HorizontalAxis>(finalSize),
|
||||
Axis.HorizontalReverse => ArrangeImplementation<HorizontalReverseAxis>(finalSize),
|
||||
Axis.Vertical => ArrangeImplementation<VerticalAxis>(finalSize),
|
||||
Axis.VerticalReverse => ArrangeImplementation<VerticalReverseAxis>(finalSize),
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
|
||||
private Vector2 ArrangeImplementation<TAxis>(Vector2 finalSize) where TAxis : IAxisImplementation
|
||||
{
|
||||
var realFinalSize = finalSize;
|
||||
finalSize = TAxis.SizeToAxis(finalSize);
|
||||
|
||||
var separation = ActualSeparation;
|
||||
var crossSeparation = ActualCrossSeparation;
|
||||
|
||||
var baseOffset = Reverse ? new Vector2(0, _lastMeasureCross) : Vector2.Zero;
|
||||
|
||||
var fixedSize = _equalSize ? (Vector2?)GetMaxDesired() : null;
|
||||
|
||||
var start = 0;
|
||||
for (var i = 0; i < _rowIndices.Count; i++)
|
||||
{
|
||||
var (endIndex, cross) = _rowIndices[i];
|
||||
|
||||
if (Reverse)
|
||||
{
|
||||
baseOffset.Y -= cross;
|
||||
}
|
||||
|
||||
var box = TAxis.BoxFromAxis(UIBox2.FromDimensions(baseOffset, finalSize with { Y = cross }), realFinalSize);
|
||||
|
||||
BoxContainer.LayOutItems<TAxis>(
|
||||
box.TopLeft,
|
||||
box.Size,
|
||||
(BoxContainer.AlignMode)_justification,
|
||||
Children,
|
||||
start,
|
||||
endIndex,
|
||||
separation,
|
||||
fixedSize);
|
||||
|
||||
start = endIndex;
|
||||
|
||||
if (Reverse)
|
||||
{
|
||||
baseOffset.Y -= crossSeparation;
|
||||
}
|
||||
else
|
||||
{
|
||||
baseOffset.Y = baseOffset.Y + cross + crossSeparation;
|
||||
}
|
||||
}
|
||||
|
||||
return realFinalSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Justification values for <see cref="WrapContainer.Justification"/>.
|
||||
/// </summary>
|
||||
public enum ItemJustification : byte
|
||||
{
|
||||
// These MUST match the values in BoxContainer.
|
||||
Begin = BoxContainer.AlignMode.Begin,
|
||||
Center = BoxContainer.AlignMode.Center,
|
||||
End = BoxContainer.AlignMode.End
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared;
|
||||
@@ -224,10 +225,13 @@ public sealed partial class DebugConsole
|
||||
|
||||
if (_compPopup.Contents.ChildCount != 0)
|
||||
{
|
||||
_compPopup.Open(
|
||||
UIBox2.FromDimensions(
|
||||
offset - _compPopup.Contents.Margin.Left, CommandBar.GlobalPosition.Y + CommandBar.Height + 2,
|
||||
5, 5));
|
||||
var box = UIBox2.FromDimensions(
|
||||
offset - _compPopup.Contents.Margin.Left,
|
||||
CommandBar.GlobalPosition.Y + CommandBar.Height + 2,
|
||||
5,
|
||||
5);
|
||||
var altPosUp = new Vector2(offset - _compPopup.Contents.Margin.Left, CommandBar.GlobalPosition.Y);
|
||||
_compPopup.Open(box, altPosUp: altPosUp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
_consoleHost.ClearText += OnClearText;
|
||||
_cfg.OnValueChanged(CVars.ConMaxEntries, MaxEntriesChanged, true);
|
||||
|
||||
UserInterfaceManager.ModalRoot.AddChild(_compPopup);
|
||||
Root!.ModalRoot.AddChild(_compPopup);
|
||||
}
|
||||
|
||||
protected override void ExitedTree()
|
||||
@@ -95,7 +95,7 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
_consoleHost.ClearText -= OnClearText;
|
||||
_cfg.UnsubValueChanged(CVars.ConMaxEntries, MaxEntriesChanged);
|
||||
|
||||
UserInterfaceManager.ModalRoot.RemoveChild(_compPopup);
|
||||
_compPopup.Orphan();
|
||||
}
|
||||
|
||||
private void MaxEntriesChanged(int value)
|
||||
|
||||
@@ -1,42 +1,39 @@
|
||||
using System;
|
||||
using Robust.Client.GameStates;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.Profiling;
|
||||
using Robust.Client.State;
|
||||
using Robust.Client.Timing;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Robust.Client.UserInterface.CustomControls.DebugMonitorControls
|
||||
{
|
||||
internal sealed class DebugMonitors : BoxContainer, IDebugMonitors
|
||||
{
|
||||
[Dependency] private readonly IClientGameTiming _timing = default!;
|
||||
[Dependency] private readonly IClientGameStateManager _state = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IClientNetManager _net = default!;
|
||||
|
||||
private readonly Control[] _monitors = new Control[Enum.GetNames<DebugMonitor>().Length];
|
||||
|
||||
//TODO: Think about a factory for this
|
||||
public DebugMonitors(IClientGameTiming gameTiming, IPlayerManager playerManager, IEyeManager eyeManager,
|
||||
IInputManager inputManager, IStateManager stateManager, IClyde displayManager, IClientNetManager netManager,
|
||||
IMapManager mapManager)
|
||||
public void Init()
|
||||
{
|
||||
Visible = false;
|
||||
|
||||
SeparationOverride = 2;
|
||||
Orientation = LayoutOrientation.Vertical;
|
||||
|
||||
Add(DebugMonitor.Fps, new FpsCounter(gameTiming));
|
||||
Add(DebugMonitor.Fps, new FpsCounter(_timing));
|
||||
Add(DebugMonitor.Coords, new DebugCoordsPanel());
|
||||
Add(DebugMonitor.Net, new DebugNetPanel(netManager, gameTiming));
|
||||
Add(DebugMonitor.Bandwidth, new DebugNetBandwidthPanel(netManager, gameTiming));
|
||||
Add(DebugMonitor.Time, new DebugTimePanel(gameTiming, IoCManager.Resolve<IClientGameStateManager>()));
|
||||
Add(DebugMonitor.Frames, new FrameGraph(gameTiming, IoCManager.Resolve<IConfigurationManager>()));
|
||||
Add(DebugMonitor.Net, new DebugNetPanel(_net, _timing));
|
||||
Add(DebugMonitor.Bandwidth, new DebugNetBandwidthPanel(_net, _timing));
|
||||
Add(DebugMonitor.Time, new DebugTimePanel(_timing, _state));
|
||||
Add(DebugMonitor.Frames, new FrameGraph(_timing, _cfg));
|
||||
Add(DebugMonitor.Memory, new DebugMemoryPanel());
|
||||
Add(DebugMonitor.Clyde, new DebugClydePanel { HorizontalAlignment = HAlignment.Left });
|
||||
Add(DebugMonitor.System, new DebugSystemPanel { HorizontalAlignment = HAlignment.Left });
|
||||
Add(DebugMonitor.Version, new DebugVersionPanel(_cfg) {HorizontalAlignment = HAlignment.Left});
|
||||
Add(DebugMonitor.Input, new DebugInputPanel { HorizontalAlignment = HAlignment.Left });
|
||||
Add(DebugMonitor.Prof, new LiveProfileViewControl());
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.UserInterface.CustomControls.DebugMonitorControls
|
||||
{
|
||||
internal sealed class DebugVersionPanel : PanelContainer
|
||||
{
|
||||
public DebugVersionPanel(IConfigurationManager cfg)
|
||||
{
|
||||
var contents = new Label
|
||||
{
|
||||
FontColorShadowOverride = Color.Black,
|
||||
};
|
||||
AddChild(contents);
|
||||
|
||||
PanelOverride = new StyleBoxFlat
|
||||
{
|
||||
BackgroundColor = new Color(35, 134, 37, 138),
|
||||
ContentMarginLeftOverride = 5,
|
||||
ContentMarginRightOverride = 5,
|
||||
ContentMarginTopOverride = 5,
|
||||
ContentMarginBottomOverride = 5,
|
||||
};
|
||||
|
||||
MouseFilter = contents.MouseFilter = MouseFilterMode.Ignore;
|
||||
|
||||
// Set visible explicitly
|
||||
Visible = true;
|
||||
HorizontalAlignment = HAlignment.Left;
|
||||
VerticalAlignment = VAlignment.Top;
|
||||
|
||||
var buildInfo = GameBuildInformation.GetBuildInfoFromConfig(cfg);
|
||||
contents.Text = $"Fork ID: {buildInfo.ForkId}\nFork Version: {buildInfo.Version}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,7 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
}
|
||||
else
|
||||
{
|
||||
_curAnchorOffset = MathHelper.Lerp(_curAnchorOffset, targetOffset, args.DeltaSeconds * 20);
|
||||
_curAnchorOffset = UIAnimations.LerpAnimate(_curAnchorOffset, targetOffset, args.DeltaSeconds, 20);
|
||||
}
|
||||
|
||||
UpdateAnchorOffset();
|
||||
|
||||
@@ -6,5 +6,6 @@
|
||||
<DevWindowTabUI Name="UI" />
|
||||
<DevWindowTabPerf Name="Perf" />
|
||||
<DevWindowTabTextures Name="Textures" />
|
||||
<DevWindowTabRenderTargets Name="RenderTargets" />
|
||||
</TabContainer>
|
||||
</Control>
|
||||
|
||||
@@ -28,6 +28,7 @@ namespace Robust.Client.UserInterface
|
||||
TabContainer.SetTabTitle(UI, "User Interface");
|
||||
TabContainer.SetTabTitle(Perf, "Profiling");
|
||||
TabContainer.SetTabTitle(Textures, Loc.GetString("dev-window-tab-textures-title"));
|
||||
TabContainer.SetTabTitle(RenderTargets, Loc.GetString("dev-window-tab-render-targets-title"));
|
||||
|
||||
Stylesheet =
|
||||
new DefaultStylesheet(IoCManager.Resolve<IResourceCache>(), IoCManager.Resolve<IUserInterfaceManager>()).Stylesheet;
|
||||
@@ -41,26 +42,14 @@ namespace Robust.Client.UserInterface
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var clyde = IoCManager.Resolve<IClyde>();
|
||||
var monitor = clyde.EnumerateMonitors().First();
|
||||
if (args.Length > 0)
|
||||
var window = new OSWindow
|
||||
{
|
||||
var id = int.Parse(args[0]);
|
||||
monitor = clyde.EnumerateMonitors().Single(m => m.Id == id);
|
||||
}
|
||||
|
||||
var window = clyde.CreateWindow(new WindowCreateParameters
|
||||
{
|
||||
//Maximized = true,
|
||||
Title = "Robust Debug Window",
|
||||
//Monitor = monitor,
|
||||
});
|
||||
var root = IoCManager.Resolve<IUserInterfaceManager>().CreateWindowRoot(window);
|
||||
window.DisposeOnClose = true;
|
||||
|
||||
};
|
||||
var control = new DevWindow();
|
||||
window.AddChild(control);
|
||||
|
||||
root.AddChild(control);
|
||||
window.Show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<Control xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="Robust.Client.UserInterface.DevWindowTabRenderTargets">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Button Text="{Loc 'dev-window-tab-render-targets-reload'}" Name="ReloadButton" />
|
||||
</BoxContainer>
|
||||
<LineEdit Name="FilterEdit" PlaceHolder="{Loc 'dev-window-tab-render-targets-filter'}" />
|
||||
<ScrollContainer VerticalExpand="True" VScrollEnabled="True" HScrollEnabled="False">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<TableContainer Name="Table" Margin="2" Columns="6">
|
||||
<Label Margin="4" Text="{Loc 'dev-window-tab-render-targets-column-id'}" />
|
||||
<Label Margin="4" Text="{Loc 'dev-window-tab-render-targets-column-name'}" />
|
||||
<Label Margin="4" Text="{Loc 'dev-window-tab-render-targets-column-size'}" />
|
||||
<Label Margin="4" Text="{Loc 'dev-window-tab-render-targets-column-type'}" />
|
||||
<Label Margin="4" Text="{Loc 'dev-window-tab-render-targets-column-vram'}" />
|
||||
<Label Margin="4" Text="{Loc 'dev-window-tab-render-targets-column-thumbnail'}" />
|
||||
</TableContainer>
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Name="TotalLabel" Margin="4" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
|
||||
</Control>
|
||||
@@ -0,0 +1,242 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using RTCF = Robust.Client.Graphics.RenderTargetColorFormat;
|
||||
|
||||
namespace Robust.Client.UserInterface;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
internal sealed partial class DevWindowTabRenderTargets : Control
|
||||
{
|
||||
[Dependency] private readonly IClydeInternal _clyde = default!;
|
||||
[Dependency] private readonly ILocalizationManager _loc = default!;
|
||||
|
||||
private readonly Control[] _gridHeader;
|
||||
|
||||
private readonly StyleBoxFlat _styleAltRow = new() { BackgroundColor = Color.FromHex("#222") };
|
||||
|
||||
#if TOOLS
|
||||
private readonly HashSet<IRenderTarget> _copyTextures = new();
|
||||
#endif // TOOLS
|
||||
|
||||
public DevWindowTabRenderTargets()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_gridHeader = Table.Children.ToArray();
|
||||
|
||||
ReloadButton.OnPressed += _ => Reload();
|
||||
FilterEdit.OnTextChanged += _ => Reload();
|
||||
}
|
||||
|
||||
protected override void VisibilityChanged(bool newVisible)
|
||||
{
|
||||
base.VisibilityChanged(newVisible);
|
||||
|
||||
if (newVisible)
|
||||
{
|
||||
Reload();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Clear to release memory when tab not visible.
|
||||
Clear();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ExitedTree()
|
||||
{
|
||||
base.ExitedTree();
|
||||
|
||||
Clear();
|
||||
}
|
||||
|
||||
private void Clear()
|
||||
{
|
||||
Table.RemoveAllChildren();
|
||||
|
||||
#if TOOLS
|
||||
foreach (var copiedTexture in _copyTextures)
|
||||
{
|
||||
copiedTexture.Dispose();
|
||||
}
|
||||
|
||||
_copyTextures.Clear();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void Reload()
|
||||
{
|
||||
Table.RemoveAllChildren();
|
||||
|
||||
foreach (var header in _gridHeader)
|
||||
{
|
||||
Table.AddChild(header);
|
||||
}
|
||||
|
||||
var totalVram = 0L;
|
||||
|
||||
var even = true;
|
||||
|
||||
var rts = _clyde.GetLoadedRenderTextures().OrderBy(x => x.Item1.Handle.Value).ToArray();
|
||||
foreach (var (instance, loaded) in rts)
|
||||
{
|
||||
#if TOOLS
|
||||
if (_copyTextures.Contains(instance))
|
||||
continue;
|
||||
#endif // TOOLS
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(FilterEdit.Text))
|
||||
{
|
||||
if (loaded.Name is not { } name)
|
||||
continue;
|
||||
|
||||
if (!name.Contains(FilterEdit.Text, StringComparison.CurrentCultureIgnoreCase))
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
AddColumnText(instance.Handle.Value.ToString());
|
||||
|
||||
if (loaded.Name != null)
|
||||
AddColumnText(loaded.Name);
|
||||
else if (loaded.WindowId != WindowId.Invalid)
|
||||
AddColumnText(loaded.WindowId.ToString());
|
||||
else
|
||||
AddColumnText(_loc.GetString("dev-window-tab-render-targets-value-null"));
|
||||
|
||||
AddColumnText(loaded.Size.ToString());
|
||||
var type = loaded.ColorFormat.ToString();
|
||||
if (loaded.DepthStencilHandle != default)
|
||||
type += "+DS";
|
||||
AddColumnText(type);
|
||||
AddColumnText(ByteHelpers.FormatBytes(loaded.MemoryPressure));
|
||||
|
||||
// Disable texture thumbnails outside TOOLS.
|
||||
// Avoid people cheating by using devwindow to see through walls. Barely.
|
||||
#if TOOLS
|
||||
if (instance is IRenderTexture renderTexture)
|
||||
{
|
||||
var clone = CloneTexture(renderTexture.Texture, loaded.ColorFormat);
|
||||
|
||||
_copyTextures.Add(clone);
|
||||
|
||||
AddColumn(new TextureRect
|
||||
{
|
||||
Texture = clone.Texture,
|
||||
Stretch = TextureRect.StretchMode.KeepAspect
|
||||
});
|
||||
}
|
||||
else
|
||||
#endif // TOOLS
|
||||
{
|
||||
AddColumnText(_loc.GetString("dev-window-tab-render-targets-value-not-available"));
|
||||
}
|
||||
|
||||
totalVram += loaded.MemoryPressure;
|
||||
|
||||
even = !even;
|
||||
}
|
||||
|
||||
TotalLabel.Text = Loc.GetString(
|
||||
"dev-window-tab-render-targets-summary",
|
||||
("vram", ByteHelpers.FormatBytes(totalVram)));
|
||||
|
||||
return;
|
||||
|
||||
void AddColumnText(string text)
|
||||
{
|
||||
var richTextLabel = new RichTextLabel { Margin = new Thickness(4) };
|
||||
richTextLabel.SetMessage(text, defaultColor: Color.White);
|
||||
AddColumn(richTextLabel);
|
||||
}
|
||||
|
||||
void AddColumn(Control control)
|
||||
{
|
||||
control.VerticalAlignment = VAlignment.Center;
|
||||
|
||||
if (even)
|
||||
{
|
||||
control = new PanelContainer
|
||||
{
|
||||
PanelOverride = _styleAltRow,
|
||||
Children = { control },
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// Wrapping control so we can use SetHeight.
|
||||
control = new Control
|
||||
{
|
||||
Children = { control },
|
||||
};
|
||||
}
|
||||
|
||||
control.SetHeight = 50;
|
||||
|
||||
Table.AddChild(control);
|
||||
}
|
||||
}
|
||||
|
||||
#if TOOLS
|
||||
private IRenderTexture CloneTexture(Texture texture, RTCF colorFormat)
|
||||
{
|
||||
var thumbnailSize = GetThumbnailSize(texture.Size);
|
||||
|
||||
var rt = _clyde.CreateRenderTarget(
|
||||
thumbnailSize,
|
||||
new RenderTargetFormatParameters
|
||||
{
|
||||
ColorFormat = colorFormat,
|
||||
HasDepthStencil = false,
|
||||
},
|
||||
new TextureSampleParameters
|
||||
{
|
||||
Filter = true,
|
||||
},
|
||||
name: $"{nameof(DevWindowTabRenderTargets)}-clone");
|
||||
|
||||
_clyde.RenderNow(rt,
|
||||
handle =>
|
||||
{
|
||||
handle.DrawingHandleScreen.DrawTextureRect(texture, UIBox2.FromDimensions(Vector2.Zero, thumbnailSize));
|
||||
});
|
||||
|
||||
return rt;
|
||||
}
|
||||
|
||||
private static Vector2i GetThumbnailSize(Vector2i textureSize)
|
||||
{
|
||||
const int maxHeight = 50;
|
||||
const int maxWidth = 100;
|
||||
|
||||
var (w, h) = (Vector2)textureSize;
|
||||
|
||||
if (h > maxHeight)
|
||||
{
|
||||
w /= h / maxHeight;
|
||||
h = maxHeight;
|
||||
}
|
||||
|
||||
if (w > maxWidth)
|
||||
{
|
||||
h /= w / maxWidth;
|
||||
w = maxWidth;
|
||||
}
|
||||
|
||||
return new Vector2i((int)w, (int)h);
|
||||
}
|
||||
#endif // TOOLS
|
||||
}
|
||||
@@ -36,5 +36,6 @@ public enum DebugMonitor
|
||||
Input,
|
||||
Bandwidth,
|
||||
Prof,
|
||||
System
|
||||
System,
|
||||
Version
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Robust.Client.UserInterface
|
||||
/// <param name="tooltip">control to position (current size will be used to determine bounds)</param>
|
||||
public static void PositionTooltip(Control tooltip)
|
||||
{
|
||||
PositionTooltip(tooltip.UserInterfaceManager.RootControl.Size,
|
||||
PositionTooltip(tooltip.Root!.Size,
|
||||
tooltip.UserInterfaceManager.MousePositionScaled.Position,
|
||||
tooltip);
|
||||
}
|
||||
|
||||
13
Robust.Client/UserInterface/UIAnimations.cs
Normal file
13
Robust.Client/UserInterface/UIAnimations.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.UserInterface;
|
||||
|
||||
internal static class UIAnimations
|
||||
{
|
||||
// From https://blog.pkh.me/p/41-fixing-the-iterative-damping-interpolation-in-video-games.html
|
||||
public static float LerpAnimate(float a, float b, float dt, float rate)
|
||||
{
|
||||
return MathHelper.Lerp(a, b, 1f - MathF.Exp(-dt * rate));
|
||||
}
|
||||
}
|
||||
@@ -333,7 +333,7 @@ internal partial class UserInterfaceManager
|
||||
|
||||
if (_suppliedTooltip != null)
|
||||
{
|
||||
PopupRoot.RemoveChild(_suppliedTooltip);
|
||||
_suppliedTooltip.Orphan();
|
||||
_suppliedTooltip = null;
|
||||
}
|
||||
|
||||
@@ -538,7 +538,7 @@ internal partial class UserInterfaceManager
|
||||
if (_showingTooltip) return;
|
||||
_showingTooltip = true;
|
||||
var hovered = CurrentlyHovered;
|
||||
if (hovered == null)
|
||||
if (hovered == null || hovered.Root == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -563,7 +563,7 @@ internal partial class UserInterfaceManager
|
||||
if (_suppliedTooltip == null)
|
||||
return;
|
||||
|
||||
PopupRoot.AddChild(_suppliedTooltip);
|
||||
hovered.Root.PopupRoot.AddChild(_suppliedTooltip);
|
||||
Tooltips.PositionTooltip(_suppliedTooltip);
|
||||
hovered.PerformShowTooltip();
|
||||
}
|
||||
|
||||
@@ -3,10 +3,8 @@ using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.State;
|
||||
using Robust.Client.Timing;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.CustomControls.DebugMonitorControls;
|
||||
@@ -20,9 +18,7 @@ using Robust.Shared.Input;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Profiling;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Reflection;
|
||||
@@ -37,13 +33,9 @@ namespace Robust.Client.UserInterface
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
[Dependency] private readonly IFontManager _fontManager = default!;
|
||||
[Dependency] private readonly IClydeInternal _clyde = default!;
|
||||
[Dependency] private readonly IClientGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IStateManager _stateManager = default!;
|
||||
[Dependency] private readonly IClientNetManager _netManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
[Dependency] private readonly IUserInterfaceManagerInternal _userInterfaceManager = default!;
|
||||
[Dependency] private readonly IDynamicTypeFactoryInternal _typeFactory = default!;
|
||||
@@ -86,10 +78,10 @@ namespace Robust.Client.UserInterface
|
||||
|
||||
[ViewVariables] public ViewportContainer MainViewport { get; private set; } = default!;
|
||||
[ViewVariables] public LayoutContainer StateRoot { get; private set; } = default!;
|
||||
[ViewVariables] public PopupContainer ModalRoot { get; private set; } = default!;
|
||||
[ViewVariables] public PopupContainer ModalRoot => RootControl.ModalRoot;
|
||||
[ViewVariables] public WindowRoot RootControl { get; private set; } = default!;
|
||||
[ViewVariables] public LayoutContainer WindowRoot { get; private set; } = default!;
|
||||
[ViewVariables] public LayoutContainer PopupRoot { get; private set; } = default!;
|
||||
[ViewVariables] public LayoutContainer PopupRoot => RootControl.PopupRoot;
|
||||
[ViewVariables] public DropDownDebugConsole DebugConsole { get; private set; } = default!;
|
||||
[ViewVariables] public IDebugMonitors DebugMonitors => _debugMonitors;
|
||||
private DebugMonitors _debugMonitors = default!;
|
||||
@@ -117,10 +109,11 @@ namespace Robust.Client.UserInterface
|
||||
|
||||
DebugConsole = new DropDownDebugConsole();
|
||||
RootControl.AddChild(DebugConsole);
|
||||
DebugConsole.SetPositionInParent(ModalRoot.GetPositionInParent());
|
||||
DebugConsole.SetPositionInParent(RootControl.ModalRoot.GetPositionInParent());
|
||||
|
||||
_debugMonitors = new DebugMonitors(_gameTiming, _playerManager, _eyeManager, _inputManager, _stateManager,
|
||||
_clyde, _netManager, _mapManager);
|
||||
_debugMonitors = new DebugMonitors();
|
||||
_rootDependencies.InjectDependencies(_debugMonitors);
|
||||
_debugMonitors.Init();
|
||||
DebugConsole.BelowConsole.AddChild(_debugMonitors);
|
||||
|
||||
_inputManager.SetInputCommand(EngineKeyFunctions.ShowDebugConsole,
|
||||
@@ -177,19 +170,7 @@ namespace Robust.Client.UserInterface
|
||||
};
|
||||
RootControl.AddChild(WindowRoot);
|
||||
|
||||
ModalRoot = new PopupContainer
|
||||
{
|
||||
Name = "ModalRoot",
|
||||
MouseFilter = Control.MouseFilterMode.Ignore,
|
||||
};
|
||||
RootControl.AddChild(ModalRoot);
|
||||
|
||||
PopupRoot = new LayoutContainer
|
||||
{
|
||||
Name = "PopupRoot",
|
||||
MouseFilter = Control.MouseFilterMode.Ignore
|
||||
};
|
||||
RootControl.AddChild(PopupRoot);
|
||||
RootControl.CreateRootControls();
|
||||
}
|
||||
|
||||
public void InitializeTesting()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Text;
|
||||
using Robust.Client.Graphics;
|
||||
@@ -36,10 +36,18 @@ internal struct WordWrap
|
||||
LastRune = new Rune('A');
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add <paramref name="rune" /> as the next glyph in the sequence and update internal line break state.
|
||||
/// </summary>
|
||||
/// <param name="breakLine">If non-null, indicates that a line break needs to be added at this rune
|
||||
/// index within the string. This index will refer to the offset of a previously-entered rune</param>
|
||||
/// <param name="breakNewLine">If non-null, indicates the rune index of an entered newline</param>
|
||||
/// <param name="skip">If true, indicates that the rune should occupy zero space. Currently only used
|
||||
/// for newlines.</param>
|
||||
public void NextRune(Rune rune, out int? breakLine, out int? breakNewLine, out bool skip)
|
||||
{
|
||||
BreakIndexCounter = NextBreakIndexCounter;
|
||||
NextBreakIndexCounter += rune.Utf16SequenceLength;
|
||||
NextBreakIndexCounter += 1;
|
||||
|
||||
breakLine = null;
|
||||
breakNewLine = null;
|
||||
|
||||
@@ -3,7 +3,9 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Asynchronous;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
@@ -18,24 +20,25 @@ namespace Robust.Client.UserInterface.XAML.Proxy;
|
||||
/// </remarks>
|
||||
internal sealed class XamlHotReloadManager : IXamlHotReloadManager
|
||||
{
|
||||
private const string MarkerFileName = "SpaceStation14.sln";
|
||||
|
||||
[Dependency] ILogManager _logManager = null!;
|
||||
[Dependency] private readonly IResourceManager _resources = null!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = null!;
|
||||
[Dependency] private readonly ILogManager _logManager = null!;
|
||||
[Dependency] private readonly IResourceManagerInternal _resources = null!;
|
||||
[Dependency] private readonly ITaskManager _taskManager = null!;
|
||||
[Dependency] private readonly IXamlProxyManager _xamlProxyManager = null!;
|
||||
|
||||
private ISawmill _sawmill = null!;
|
||||
private FileSystemWatcher? _watcher;
|
||||
private string _markerFileName = null!;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_markerFileName = _cfg.GetCVar(CVars.XamlHotReloadMarkerName);
|
||||
_sawmill = _logManager.GetSawmill("xamlhotreload");
|
||||
var codeLocation = InferCodeLocation();
|
||||
|
||||
if (codeLocation == null)
|
||||
{
|
||||
_sawmill.Warning($"could not find code -- where is {MarkerFileName}?");
|
||||
_sawmill.Warning($"could not find code -- where is {_markerFileName}?");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -117,9 +120,9 @@ internal sealed class XamlHotReloadManager : IXamlHotReloadManager
|
||||
private string? InferCodeLocation()
|
||||
{
|
||||
// ascend upwards from each content root until the solution file is found
|
||||
foreach (var contentRoot in _resources.GetContentRoots())
|
||||
foreach (var baseSystemPath in _resources.GetContentRoots())
|
||||
{
|
||||
var systemPath = contentRoot.ToRelativeSystemPath();
|
||||
var systemPath = baseSystemPath;
|
||||
while (true)
|
||||
{
|
||||
var files = Array.Empty<string>();
|
||||
@@ -129,7 +132,7 @@ internal sealed class XamlHotReloadManager : IXamlHotReloadManager
|
||||
}
|
||||
catch (IOException) { } // this is allowed to fail, and if so we just keep going up
|
||||
|
||||
if (files.Any(f => Path.GetFileName(f).Equals(MarkerFileName, StringComparison.InvariantCultureIgnoreCase)))
|
||||
if (files.Any(f => Path.GetFileName(f).Equals(_markerFileName, StringComparison.InvariantCultureIgnoreCase)))
|
||||
{
|
||||
return systemPath;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
using System;
|
||||
#if !WINDOWS
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using OpenTK.Audio.OpenAL;
|
||||
using SDL3;
|
||||
|
||||
namespace Robust.Client.Utility
|
||||
{
|
||||
@@ -9,32 +12,31 @@ namespace Robust.Client.Utility
|
||||
[ModuleInitializer]
|
||||
internal static void Initialize()
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
return;
|
||||
|
||||
NativeLibrary.SetDllImportResolver(typeof(ClientDllMap).Assembly, (name, assembly, path) =>
|
||||
{
|
||||
if (name == "swnfd.dll")
|
||||
{
|
||||
if (OperatingSystem.IsLinux())
|
||||
return NativeLibrary.Load("libswnfd.so", assembly, path);
|
||||
|
||||
if (OperatingSystem.IsMacOS())
|
||||
return NativeLibrary.Load("libswnfd.dylib", assembly, path);
|
||||
|
||||
return IntPtr.Zero;
|
||||
#if LINUX || FREEBSD
|
||||
return NativeLibrary.Load("libswnfd.so", assembly, path);
|
||||
#elif MACOS
|
||||
return NativeLibrary.Load("libswnfd.dylib", assembly, path);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (name == "libEGL.dll")
|
||||
{
|
||||
if (OperatingSystem.IsLinux())
|
||||
return NativeLibrary.Load("libEGL.so", assembly, path);
|
||||
|
||||
return IntPtr.Zero;
|
||||
#if LINUX || FREEBSD
|
||||
return NativeLibrary.Load("libEGL.so", assembly, path);
|
||||
#endif
|
||||
}
|
||||
|
||||
return IntPtr.Zero;
|
||||
});
|
||||
|
||||
#if MACOS
|
||||
OpenALLibraryNameContainer.OverridePath = "libopenal.1.dylib";
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -19,7 +19,7 @@ internal sealed class ReloadManager : IReloadManager
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly ILogManager _logMan = default!;
|
||||
[Dependency] private readonly IResourceManager _res = default!;
|
||||
[Dependency] private readonly IResourceManagerInternal _res = default!;
|
||||
#pragma warning disable CS0414
|
||||
[Dependency] private readonly ITaskManager _tasks = default!;
|
||||
#pragma warning restore CS0414
|
||||
@@ -58,13 +58,11 @@ internal sealed class ReloadManager : IReloadManager
|
||||
{
|
||||
foreach (var file in _reloadQueue)
|
||||
{
|
||||
var rootedFile = file.ToRootedPath();
|
||||
|
||||
if (!_res.ContentFileExists(rootedFile))
|
||||
if (!_res.ContentFileExists(file))
|
||||
continue;
|
||||
|
||||
_sawmill.Info($"Reloading {rootedFile}");
|
||||
OnChanged?.Invoke(rootedFile);
|
||||
_sawmill.Info($"Reloading {file}");
|
||||
OnChanged?.Invoke(file);
|
||||
}
|
||||
|
||||
_reloadQueue.Clear();
|
||||
@@ -72,7 +70,7 @@ internal sealed class ReloadManager : IReloadManager
|
||||
|
||||
public void Register(ResPath directory, string filter)
|
||||
{
|
||||
Register(directory.ToString(), filter);
|
||||
Register(directory.ToRelativeSystemPath(), filter);
|
||||
}
|
||||
|
||||
public void Register(string directory, string filter)
|
||||
@@ -83,7 +81,7 @@ internal sealed class ReloadManager : IReloadManager
|
||||
#if TOOLS
|
||||
foreach (var root in _res.GetContentRoots())
|
||||
{
|
||||
var path = root + directory;
|
||||
var path = Path.Join(root, directory);
|
||||
|
||||
if (!Directory.Exists(path))
|
||||
{
|
||||
@@ -128,17 +126,18 @@ internal sealed class ReloadManager : IReloadManager
|
||||
|
||||
_tasks.RunOnMainThread(() =>
|
||||
{
|
||||
var fullPath = args.FullPath.Replace(Path.DirectorySeparatorChar, '/');
|
||||
var file = new ResPath(fullPath);
|
||||
|
||||
foreach (var rootIter in _res.GetContentRoots())
|
||||
{
|
||||
if (!file.TryRelativeTo(rootIter, out var relative))
|
||||
var relPath = Path.GetRelativePath(rootIter, args.FullPath);
|
||||
if (relPath == args.FullPath)
|
||||
{
|
||||
// Different root (i.e., "C:/" and "D:/")
|
||||
continue;
|
||||
}
|
||||
|
||||
_reloadQueue.Add(relative.Value);
|
||||
var file = ResPath.FromRelativeSystemPath(relPath).ToRootedPath();
|
||||
if (!file.CanonPath.Contains("/../"))
|
||||
_reloadQueue.Add(file);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -24,8 +24,9 @@ namespace Robust.Packaging;
|
||||
/// PresetPassesCore --2--> Output
|
||||
/// InputResources --> PresetPassesResources
|
||||
/// PresetPassesResources --1--> AudioMetadata
|
||||
/// PresetPassesResources --2--> NormalizeTextResources
|
||||
/// PresetPassesResources --3--> PrefixResources
|
||||
/// PresetPassesResources --2--> DropAudioFiles
|
||||
/// PresetPassesResources --3--> NormalizeTextResources
|
||||
/// PresetPassesResources --4--> PrefixResources
|
||||
/// AudioMetadata --> PrefixResources
|
||||
/// NormalizeTextResources --> PrefixResources
|
||||
/// PrefixResources --> Output
|
||||
@@ -59,6 +60,17 @@ public sealed class RobustServerAssetGraph
|
||||
public AssetPassPipe PresetPassesResources { get; }
|
||||
public AssetPassAudioMetadata AudioMetadata { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Used to drop all files in the <c>Audio/</c> directory.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Most audio files are actually removed by <see cref="AudioMetadata"/>.
|
||||
/// This pass cleans up stuff like attribution files and soundfonts in <c>Audio/MidiCustom</c>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public AssetPassFilterDrop DropAudioFiles { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Normalizes text files in resources.
|
||||
/// </summary>
|
||||
@@ -103,10 +115,12 @@ public sealed class RobustServerAssetGraph
|
||||
PresetPassesResources = new AssetPassPipe { Name = "RobustServerAssetGraphPresetPassesResources" };
|
||||
NormalizeTextResources = new AssetPassNormalizeText { Name = "RobustServerAssetGraphNormalizeTextResources" };
|
||||
AudioMetadata = new AssetPassAudioMetadata { Name = "RobustServerAssetGraphAudioMetadata" };
|
||||
DropAudioFiles = new AssetPassFilterDrop(p => p.Path.StartsWith("Audio/")) { Name = "RobustServerAssetGraphDropAudioFiles" };
|
||||
PrefixResources = new AssetPassPrefix("Resources/") { Name = "RobustServerAssetGraphPrefixResources" };
|
||||
|
||||
PresetPassesResources.AddDependency(InputResources);
|
||||
AudioMetadata.AddDependency(PresetPassesResources).AddBefore(NormalizeTextResources);
|
||||
AudioMetadata.AddDependency(PresetPassesResources).AddBefore(DropAudioFiles);
|
||||
DropAudioFiles.AddDependency(PresetPassesResources).AddBefore(NormalizeTextResources);
|
||||
NormalizeTextResources.AddDependency(PresetPassesResources).AddBefore(PrefixResources);
|
||||
PrefixResources.AddDependency(PresetPassesResources);
|
||||
PrefixResources.AddDependency(AudioMetadata);
|
||||
@@ -124,6 +138,7 @@ public sealed class RobustServerAssetGraph
|
||||
NormalizeTextResources,
|
||||
AudioMetadata,
|
||||
PrefixResources,
|
||||
DropAudioFiles
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@ public sealed class RobustServerPackaging
|
||||
{
|
||||
"Textures",
|
||||
"Fonts",
|
||||
"EngineFonts",
|
||||
"Shaders",
|
||||
"Midi"
|
||||
};
|
||||
|
||||
public static async Task WriteServerResources(
|
||||
|
||||
@@ -297,7 +297,7 @@ namespace Robust.Server
|
||||
: null;
|
||||
|
||||
// Set up the VFS
|
||||
_resources.Initialize(dataDir);
|
||||
_resources.Initialize(dataDir, hideUserDataDir: false);
|
||||
|
||||
var mountOptions = _commandLineArgs != null
|
||||
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions) : Options.MountOptions;
|
||||
|
||||
@@ -102,8 +102,8 @@ namespace Robust.Server.Console.Commands
|
||||
var sys = _system.GetEntitySystem<SharedMapSystem>();
|
||||
if (!sys.MapExists(mapId))
|
||||
{
|
||||
shell.WriteError("Target map does not exist.");
|
||||
return;
|
||||
shell.WriteError($"MapID {intMapId} did not exist, creating without map init");
|
||||
sys.CreateMap(mapId, false); // doesnt runmapinit to be conservative.
|
||||
}
|
||||
|
||||
Vector2 offset = default;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user