mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 11:40:52 +01:00
Compare commits
93 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6c55085fe | ||
|
|
30eafd26e7 | ||
|
|
63423d96b4 | ||
|
|
474334aff2 | ||
|
|
be102f86bf | ||
|
|
d7962c7190 | ||
|
|
7fe9385c3b | ||
|
|
a9d9d1348a | ||
|
|
4eaf624555 | ||
|
|
e37c131fb4 | ||
|
|
9df4606492 | ||
|
|
3be8070274 | ||
|
|
8a440d705f | ||
|
|
5849474022 | ||
|
|
b7d67c0ece | ||
|
|
b04cf71bc0 | ||
|
|
ea87df649a | ||
|
|
dab7a9112f | ||
|
|
d3dc89832a | ||
|
|
3ade9ca447 | ||
|
|
149e9a2613 | ||
|
|
d164148ce2 | ||
|
|
d898b52449 | ||
|
|
dcf7a1e580 | ||
|
|
65c6bb74eb | ||
|
|
d6d88bea91 | ||
|
|
d6467f768a | ||
|
|
4f0f020f56 | ||
|
|
5ce8369fb9 | ||
|
|
2446e64033 | ||
|
|
bdd65cda4b | ||
|
|
77e949bfe8 | ||
|
|
d4171351f4 | ||
|
|
e67d0ad3d6 | ||
|
|
aff5711fde | ||
|
|
a886222946 | ||
|
|
5843f1087e | ||
|
|
93c0ce815f | ||
|
|
1c64fa1f28 | ||
|
|
c825c1e413 | ||
|
|
f30fb47834 | ||
|
|
5d255e06c8 | ||
|
|
80357c8ec4 | ||
|
|
ac3a434bdf | ||
|
|
21719b8884 | ||
|
|
dbb6b90654 | ||
|
|
4b92be5324 | ||
|
|
95169b7a71 | ||
|
|
b699e22c85 | ||
|
|
e48cc62d0b | ||
|
|
aade062a49 | ||
|
|
e02166d5c4 | ||
|
|
8037bfae14 | ||
|
|
49781791af | ||
|
|
92719aa29f | ||
|
|
1d47a9677d | ||
|
|
14fe8eba6d | ||
|
|
2ff99d4a62 | ||
|
|
ce8b2d82a3 | ||
|
|
f8f99450db | ||
|
|
a3cf4877e4 | ||
|
|
6ab08f7dc1 | ||
|
|
819f6921cf | ||
|
|
cf91369d27 | ||
|
|
0e1328675c | ||
|
|
a7315b1c95 | ||
|
|
c8f2a55cbe | ||
|
|
7812502b0b | ||
|
|
3149f99954 | ||
|
|
ef8c6379cd | ||
|
|
f932e023ee | ||
|
|
a137c839fc | ||
|
|
3e43b88518 | ||
|
|
3d974e0305 | ||
|
|
b3682017ac | ||
|
|
194743a9b0 | ||
|
|
34d65a7960 | ||
|
|
6cd4a37a8f | ||
|
|
40d879fddc | ||
|
|
df398c5b13 | ||
|
|
804b698172 | ||
|
|
346514c6e0 | ||
|
|
d65e2eb169 | ||
|
|
ff41329ad7 | ||
|
|
6151a26622 | ||
|
|
1c7ae13bfa | ||
|
|
cb6645aebe | ||
|
|
fffe3c56e9 | ||
|
|
204e881690 | ||
|
|
161b1874c2 | ||
|
|
7c3634f1f5 | ||
|
|
9969899f38 | ||
|
|
ed35839942 |
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
260
RELEASE-NOTES.md
260
RELEASE-NOTES.md
@@ -54,6 +54,266 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 148.3.0
|
||||
|
||||
### New features
|
||||
|
||||
* Happy eyeballs delay can be configured.
|
||||
* Added more colors.
|
||||
* Allow pre-startup components to be shut down.
|
||||
* Added tile texture reload command.
|
||||
* Add implementation of Random.Pick(ValueList<T> ..).
|
||||
* Add IntegrationInstance fields for common dependencies.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Prevent invalid prototypes from being spawned.
|
||||
* Change default value of EntityLastModifiedTick from zero to one.
|
||||
* Make DiscordRichPresence icon CVars server-side with replication.
|
||||
|
||||
|
||||
## 148.2.0
|
||||
|
||||
### New features
|
||||
|
||||
* `SpinBox.LineEditControl` exposes the underlying `LineEdit`.
|
||||
* Add VV attributes to various fields across overlay and sessions.
|
||||
* Add IsPaused to EntityManager to check if an entity is paused.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix SetActiveTheme not updating the theme.
|
||||
|
||||
|
||||
## 148.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Added IgnoreUIChecksComponent that lets entities ignore bound user interface range checks which would normally close the UI.
|
||||
* Add support for F16-F24 keybinds.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix gamestate bug where PVS is disabled.
|
||||
|
||||
### Other
|
||||
|
||||
* EntityQuery.HasComponent override for nullable entity uids.
|
||||
|
||||
|
||||
## 148.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Several NuGet dependencies are now private assets.
|
||||
* Added `IViewportControl.PixelToMap()` and `PixelToMapEvent`. These are variants of the existing screen-to-map functions that should account for distortion effects.
|
||||
|
||||
### New features
|
||||
|
||||
* Added several new rich-text tags, including italic and bold-italic.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed log messages for unknown components not working due to threaded IoC issues.
|
||||
* Replay recordings no longer record invalid prototype uploads.
|
||||
|
||||
|
||||
## 147.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Renamed one of the EntitySystem.Dirty() methods to `DirtyEntity()` to avoid confusion with the component-dirtying methods.
|
||||
|
||||
### New features
|
||||
|
||||
* Added debug commands that return the entity system update order.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed a bug in MetaDataSystem that was causing the metadata component to not be marked as dirty.
|
||||
|
||||
|
||||
## 146.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Remove readOnly for DataFields and rename some ShaderPrototype C# fields internally to align with the normal schema.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Add InvariantCulture to angle validation.
|
||||
|
||||
### Internal
|
||||
|
||||
* Add some additional EntityQuery<T> usages and remove a redundant CanCollide call on fixture shutdown.
|
||||
|
||||
|
||||
## 145.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Removed some old SpriteComponent data-fields ("rsi", and "layerDatums").
|
||||
|
||||
### New features
|
||||
|
||||
* Added `ActorSystem.TryGetActorFromUserId()`.
|
||||
* Added IPrototypeManager.EnumerateKinds().
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed SpriteSpecifierSerializer yaml validation not working properly.
|
||||
* Fixed IoC/Threading exceptions in `Resource.Load()`.
|
||||
* Fixed `TransformSystem.SetCoordinates()` throwing uninformative client-side errors.
|
||||
* Fixed `IResourceManager.ContentFileExists()` and `TryContentFileRead()` throwing exceptions on windows when trying to open a directory.
|
||||
|
||||
|
||||
## 144.0.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix some EntityLookup queries incorrectly being double transformed internally.
|
||||
* Shrink TileEnlargement even further for EntityLookup default queries.
|
||||
|
||||
|
||||
## 144.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Add new args to entitylookup methods to allow for shrinkage of tile-bounds checks. Default changed to shrink the grid-local AABB by the polygon skin to avoid clipping neighboring tile entities.
|
||||
* Non-hard fixtures will no longer count by default for EntityLookup.
|
||||
|
||||
### New features
|
||||
|
||||
* Added new EntityLookup flag to return non-hard fixtures or not.
|
||||
|
||||
|
||||
## 143.3.0
|
||||
|
||||
### New features
|
||||
|
||||
* Entity placement and spawn commands now raise informative events that content can handle.
|
||||
* Replay clients can now optionally ignore some errors instead of refusing to load the replay.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* `AudioParams.PlayOffsetSecond` will no longer apply an offset that is larger then the length of the audio stream.
|
||||
* Fixed yaml serialization of arrays of virtual/abstract objects.
|
||||
|
||||
|
||||
### Other
|
||||
|
||||
* Removed an incorrect gamestate debug assert.
|
||||
|
||||
|
||||
## 143.2.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add support for tests to load extra prototypes from multiple sources.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix named toolshed command.
|
||||
* Unsubscribe from grid rendering events on shutdown.
|
||||
|
||||
### Other
|
||||
|
||||
* Remove unnecessary test prototypes.
|
||||
|
||||
|
||||
## 143.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add locale support for grammatical measure words.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Don't raise contact events for entities that were QueueDeleted during the tick.
|
||||
* Exception on duplicate broadcast subscriptions as this was unsupported behaviour.
|
||||
|
||||
### Other
|
||||
|
||||
* Add VV ReadWrite to PhysicsComponent BodyStatus.
|
||||
|
||||
|
||||
## 143.0.0
|
||||
|
||||
### New features
|
||||
|
||||
|
||||
- Toolshed, a tacit shell language, has been introduced.
|
||||
- Use Robust.Shared.ToolshedManager to invoke commands, with optional input and output.
|
||||
- Implement IInvocationContext for custom invocation contexts i.e. scripting systems.
|
||||
|
||||
|
||||
## 142.1.2
|
||||
|
||||
### Other
|
||||
|
||||
* Don't log an error on failing to resolve for joint relay refreshing.
|
||||
|
||||
|
||||
## 142.1.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed a bad debug assert in `DetachParentToNull()`
|
||||
|
||||
|
||||
## 142.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* `IHttpClientHolder` holds a shared `HttpClient` for use by content. It has Happy Eyeballs fixed and an appropriate `User-Agent`.
|
||||
* Added `DataNode.ToString()`. Makes it easier to save yaml files and debug code.
|
||||
* Added some cvars to modify discord rich presence icons.
|
||||
* .ogg files now read the `Artist` and `Title` tags and make them available via new fields in `AudioStream`.
|
||||
* The default fragment shaders now have access to the local light level (`lowp vec3 lightSample`).
|
||||
* Added `IPrototypeManager.ValidateAllPrototypesSerializable()`, which can be used to check that all currently loaded prototypes can be serialised & deserialised.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix certain debug commands and tools crashing on non-SS14 RobustToolbox games due to a missing font.
|
||||
* Discord rich presence strings are now truncated if they are too long.
|
||||
* Fixed a couple of broadphase/entity-lookup update bugs that were affecting containers and entities attached to other (non-grid/map) entities.
|
||||
* Fixed `INetChannel.Disconnect()` not properly disconnecting clients in integration tests.
|
||||
|
||||
### Other
|
||||
|
||||
* Outgoing HTTP requests now all use Happy Eyeballs to try to prioritize IPv6. This is necessary because .NET still does not support this critical feature itself.
|
||||
* Made various physics related component properties VV-editable.
|
||||
* The default EntitySystem sawmill log level now defaults to `Info` instead of `Verbose`. The level remains verbose when in debug mode.
|
||||
|
||||
### Internal
|
||||
|
||||
* The debug asserts in `DetachParentToNull()` are now more informative.
|
||||
|
||||
|
||||
## 142.0.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix Enum serialization.
|
||||
|
||||
|
||||
## 142.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* `EntityManager.GetAllComponents()` now returns a (EntityUid, Component) tuple
|
||||
|
||||
### New features
|
||||
|
||||
* Added `IPrototypeManager.ValidateFields()`, which uses reflection to validate that the default values of c# string fields correspond to valid entity prototypes. Validates any fields with a `ValidatePrototypeIdAttribute` and any data-field that uses the PrototypeIdSerializer custom type serializer.
|
||||
|
||||
### Other
|
||||
|
||||
* Replay playback will now log errors when encountering unhandled messages.
|
||||
* Made `GetAssemblyByName()` throw descriptive error messages.
|
||||
* Improved performance of various EntityLookupSystem functions
|
||||
|
||||
|
||||
## 141.2.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
BIN
Resources/EngineFonts/NotoSans/NotoSansMono-Regular.ttf
Normal file
BIN
Resources/EngineFonts/NotoSans/NotoSansMono-Regular.ttf
Normal file
Binary file not shown.
@@ -1,6 +1,6 @@
|
||||
- type: uiTheme
|
||||
id: Default
|
||||
path: /Textures/Interface/Default
|
||||
path: /Textures/Interface/Default/
|
||||
colors:
|
||||
# Root
|
||||
rootBackground: "#000000"
|
||||
|
||||
@@ -17,15 +17,15 @@ 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-oldhelp-desc = Display general help or help text for a specific command
|
||||
cmd-oldhelp-help = Usage: help [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>'.
|
||||
cmd-help-unknown = Unknown command: { $command }
|
||||
cmd-help-top = { $command } - { $description }
|
||||
cmd-help-invalid-args = Invalid amount of arguments.
|
||||
cmd-help-arg-cmdname = [command name]
|
||||
cmd-oldhelp-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>'.
|
||||
cmd-oldhelp-unknown = Unknown command: { $command }
|
||||
cmd-oldhelp-top = { $command } - { $description }
|
||||
cmd-oldhelp-invalid-args = Invalid amount of arguments.
|
||||
cmd-oldhelp-arg-cmdname = [command name]
|
||||
|
||||
## 'cvar' command
|
||||
cmd-cvar-desc = Gets or sets a CVar.
|
||||
@@ -558,3 +558,6 @@ cmd-vfs_ls-help = Usage: vfs_list <path>
|
||||
|
||||
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
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
discord-rpc-in-main-menu = In Main Menu
|
||||
discord-rpc-in-main-menu-logo-text = I think coolsville SUCKS
|
||||
discord-rpc-character = Username: {$username}
|
||||
discord-rpc-on-server = On Server: {$servername}
|
||||
discord-rpc-players = Players: {$players}/{$maxplayers}
|
||||
discord-rpc-players = Players: {$players}/{$maxplayers}
|
||||
|
||||
@@ -18,6 +18,15 @@ input-key-F12 = F12
|
||||
input-key-F13 = F13
|
||||
input-key-F14 = F14
|
||||
input-key-F15 = F15
|
||||
input-key-F16 = F16
|
||||
input-key-F17 = F17
|
||||
input-key-F18 = F18
|
||||
input-key-F19 = F19
|
||||
input-key-F20 = F20
|
||||
input-key-F21 = F21
|
||||
input-key-F22 = F22
|
||||
input-key-F23 = F23
|
||||
input-key-F24 = F24
|
||||
input-key-Pause = Pause
|
||||
input-key-Left = Left
|
||||
input-key-Up = Up
|
||||
|
||||
169
Resources/Locale/en-US/toolshed-commands.ftl
Normal file
169
Resources/Locale/en-US/toolshed-commands.ftl
Normal file
@@ -0,0 +1,169 @@
|
||||
command-description-tpto =
|
||||
Teleport the given entities to some target entity.
|
||||
command-description-player-list =
|
||||
Returns a list of all player sessions.
|
||||
command-description-player-self =
|
||||
Returns the current player session.
|
||||
command-description-player-imm =
|
||||
Returns the session associated with the player given as argument.
|
||||
command-description-player-entity =
|
||||
Returns the entities of the input sessions.
|
||||
command-description-self =
|
||||
Returns the current attached entity.
|
||||
command-description-physics-velocity =
|
||||
Returns the velocity of the input entities.
|
||||
command-description-physics-angular-velocity =
|
||||
Returns the angular velocity of the input entities.
|
||||
command-description-buildinfo =
|
||||
Provides information about the build of the game.
|
||||
command-description-cmd-list =
|
||||
Returns a list of all commands, for this side.
|
||||
command-description-explain =
|
||||
Explains the given expression, providing command descriptions and signatures.
|
||||
command-description-search =
|
||||
Searches through the input for the provided value.
|
||||
command-description-stopwatch =
|
||||
Measures the execution time of the given expression.
|
||||
command-description-types-consumers =
|
||||
Provides all commands that can consume the given type.
|
||||
command-description-types-tree =
|
||||
Debug tool to return all types the command interpreter can downcast the input to.
|
||||
command-description-types-gettype =
|
||||
Returns the type of the input.
|
||||
command-description-types-fullname =
|
||||
Returns the full name of the input type according to CoreCLR.
|
||||
command-description-as =
|
||||
Casts the input to the given type.
|
||||
Effectively a type hint if you know the type but the interpreter does not.
|
||||
command-description-count =
|
||||
Counts the amount of entries in it's input, returning an integer.
|
||||
command-description-map =
|
||||
Maps the input over the given block, with the provided expected return type.
|
||||
This command may be modified to not need an explicit return type in the future.
|
||||
command-description-select =
|
||||
Selects N objects or N% of objects from the input.
|
||||
One can additionally invert this command with not to make it select everything except N objects instead.
|
||||
command-description-comp =
|
||||
Returns the given component from the input entities, discarding entities without that component.
|
||||
command-description-delete =
|
||||
Deletes the input entities.
|
||||
command-description-ent =
|
||||
Returns the provided entity ID.
|
||||
command-description-entities =
|
||||
Returns all entities on the server.
|
||||
command-description-paused =
|
||||
Filters the input entities by whether or not they are paused.
|
||||
This command can be inverted with not.
|
||||
command-description-with =
|
||||
Filters the input entities by whether or not they have the given component.
|
||||
This command can be inverted with not.
|
||||
command-description-fuck =
|
||||
Throws an exception.
|
||||
command-description-ecscomp-listty =
|
||||
Lists every type of component registered.
|
||||
command-description-cd =
|
||||
Changes the session's current directory to the given relative or absolute path.
|
||||
command-description-ls-here =
|
||||
Lists the contents of the current directory.
|
||||
command-description-ls-in =
|
||||
Lists the contents of the given relative or absolute path.
|
||||
command-description-methods-get =
|
||||
Returns all methods associated with the input type.
|
||||
command-description-methods-overrides =
|
||||
Returns all methods overriden on the input type.
|
||||
command-description-methods-overridesfrom =
|
||||
Returns all methods overriden from the given type on the input type.
|
||||
command-description-cmd-moo =
|
||||
Asks the important questions.
|
||||
command-description-cmd-descloc =
|
||||
Returns the localization string for a command's description.
|
||||
command-description-cmd-getshim =
|
||||
Returns a command's execution shim.
|
||||
command-description-help =
|
||||
Provides a quick rundown of how to use toolshed.
|
||||
command-description-ioc-registered =
|
||||
Returns all the types registered with IoCManager on the current thread (usually the game thread)
|
||||
command-description-ioc-get =
|
||||
Gets an instance of an IoC registration.
|
||||
command-description-loc-tryloc =
|
||||
Tries to get a localization string, returning null if unable.
|
||||
command-description-loc-loc =
|
||||
Gets a localization string, returning the unlocalized string if unable.
|
||||
command-description-physics-angular_velocity =
|
||||
Returns the angular velocity of the given entities.
|
||||
command-description-vars =
|
||||
Provides a list of all variables set in this session.
|
||||
command-description-any =
|
||||
Returns true if there's any values in the input, otherwise false.
|
||||
command-description-ArrowCommand =
|
||||
Assigns the input to a variable.
|
||||
command-description-isempty =
|
||||
Returns true if the input is empty, otherwise false.
|
||||
command-description-isnull =
|
||||
Returns true if the input is null, otherwise false.
|
||||
command-description-unique =
|
||||
Filters the input sequence for uniqueness, removing duplicate values.
|
||||
command-description-where =
|
||||
Given some input sequence IEnumerable<T>, takes a block of signature T -> bool that decides if each input value should be included in the output sequence.
|
||||
command-description-do =
|
||||
Backwards compatibility with BQL, applies the given old commands over the input sequence.
|
||||
command-description-named =
|
||||
Filters the input entities by their name, with the regex ^selector$.
|
||||
command-description-prototyped =
|
||||
Filters the input entities by their prototype.
|
||||
command-description-nearby =
|
||||
Creates a new list of all entities nearby the inputs within the given range.
|
||||
command-description-first =
|
||||
Returns the first entry of the given enumerable.
|
||||
command-description-splat =
|
||||
"Splats" a block, value, or variable, creating N copies of it in a list.
|
||||
command-description-val =
|
||||
Casts the given value, block, or variable to the given type. This is mostly a workaround for current limitations of variables.
|
||||
command-description-actor-controlled =
|
||||
Filters entities by whether or not they're actively controlled.
|
||||
command-description-actor-session =
|
||||
Returns the sessions associated with the input entities.
|
||||
command-description-physics-parent =
|
||||
Returns the parent(s) of the input entities.
|
||||
command-description-emplace =
|
||||
Runs the given block over it's inputs, with the input value placed into the variable $value within the block.
|
||||
Additionally breaks out $wx, $wy, $proto, $desc, $name, and $paused for entities.
|
||||
Can also have breakout values for other types, consult the documentation for that type for further info.
|
||||
command-description-AddCommand =
|
||||
Performs numeric addition.
|
||||
command-description-SubtractCommand =
|
||||
Performs numeric subtraction.
|
||||
command-description-MultiplyCommand =
|
||||
Performs numeric multiplication.
|
||||
command-description-DivideCommand =
|
||||
Performs numeric division.
|
||||
command-description-min =
|
||||
Returns the minimum of two values.
|
||||
command-description-max =
|
||||
Returns the maximum of two values.
|
||||
command-description-BitAndCommand =
|
||||
Performs bitwise AND.
|
||||
command-description-BitOrCommand =
|
||||
Performs bitwise OR.
|
||||
command-description-BitXorCommand =
|
||||
Performs bitwise XOR.
|
||||
command-description-neg =
|
||||
Negates the input.
|
||||
command-description-GreaterThanCommand =
|
||||
Performs a greater-than comparison, x > y.
|
||||
command-description-LessThanCommand =
|
||||
Performs a less-than comparison, x < y.
|
||||
command-description-GreaterThanOrEqualCommand =
|
||||
Performs a greater-than-or-equal comparison, x >= y.
|
||||
command-description-LessThanOrEqualCommand =
|
||||
Performs a less-than-or-equal comparison, x <= y.
|
||||
command-description-EqualCommand =
|
||||
Performs an equality comparison, returning true if the inputs are equal.
|
||||
command-description-NotEqualCommand =
|
||||
Performs an equality comparison, returning true if the inputs are not equal.
|
||||
|
||||
command-description-entitysystemupdateorder-tick =
|
||||
Lists the tick update order of entity systems.
|
||||
|
||||
command-description-entitysystemupdateorder-frame =
|
||||
Lists the frame update order of entity systems.
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using Robust.Client.Graphics;
|
||||
|
||||
namespace Robust.Client.Audio;
|
||||
@@ -8,13 +8,17 @@ public sealed class AudioStream
|
||||
public TimeSpan Length { get; }
|
||||
internal ClydeHandle? ClydeHandle { get; }
|
||||
public string? Name { get; }
|
||||
public string? Title { get; }
|
||||
public string? Artist { get; }
|
||||
public int ChannelCount { get; }
|
||||
|
||||
internal AudioStream(ClydeHandle handle, TimeSpan length, int channelCount, string? name = null)
|
||||
internal AudioStream(ClydeHandle handle, TimeSpan length, int channelCount, string? name = null, string? title = null, string? artist = null)
|
||||
{
|
||||
ClydeHandle = handle;
|
||||
Length = length;
|
||||
ChannelCount = channelCount;
|
||||
Name = name;
|
||||
Title = title;
|
||||
Artist = artist;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ namespace Robust.Client
|
||||
deps.Register<IComponentFactory, ComponentFactory>();
|
||||
deps.Register<ITileDefinitionManager, ClydeTileDefinitionManager>();
|
||||
deps.Register<IClydeTileDefinitionManager, ClydeTileDefinitionManager>();
|
||||
deps.Register<ClydeTileDefinitionManager, ClydeTileDefinitionManager>();
|
||||
deps.Register<GameController, GameController>();
|
||||
deps.Register<IGameController, GameController>();
|
||||
deps.Register<IGameControllerInternal, GameController>();
|
||||
|
||||
@@ -14,7 +14,7 @@ internal sealed partial class ClientConsoleHost
|
||||
private int _completionSeq;
|
||||
|
||||
|
||||
public async Task<CompletionResult> GetCompletions(List<string> args, CancellationToken cancel)
|
||||
public async Task<CompletionResult> GetCompletions(List<string> args, string argStr, CancellationToken cancel)
|
||||
{
|
||||
// Last element is the command currently being typed. May be empty.
|
||||
|
||||
@@ -24,10 +24,10 @@ internal sealed partial class ClientConsoleHost
|
||||
if (delay > 0)
|
||||
await Task.Delay((int)(delay * 1000), cancel);
|
||||
|
||||
return await CalcCompletions(args, cancel);
|
||||
return await CalcCompletions(args, argStr, cancel);
|
||||
}
|
||||
|
||||
private Task<CompletionResult> CalcCompletions(List<string> args, CancellationToken cancel)
|
||||
private Task<CompletionResult> CalcCompletions(List<string> args, string argStr, CancellationToken cancel)
|
||||
{
|
||||
if (args.Count == 1)
|
||||
{
|
||||
@@ -44,10 +44,10 @@ internal sealed partial class ClientConsoleHost
|
||||
if (!AvailableCommands.TryGetValue(args[0], out var cmd))
|
||||
return Task.FromResult(CompletionResult.Empty);
|
||||
|
||||
return cmd.GetCompletionAsync(LocalShell, args.ToArray()[1..], cancel).AsTask();
|
||||
return cmd.GetCompletionAsync(LocalShell, args.ToArray()[1..], argStr, cancel).AsTask();
|
||||
}
|
||||
|
||||
private Task<CompletionResult> DoServerCompletions(List<string> args, CancellationToken cancel)
|
||||
private Task<CompletionResult> DoServerCompletions(List<string> args, string argStr, CancellationToken cancel)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<CompletionResult>();
|
||||
var cts = CancellationTokenSource.CreateLinkedTokenSource(cancel);
|
||||
@@ -62,6 +62,7 @@ internal sealed partial class ClientConsoleHost
|
||||
var msg = new MsgConCompletion
|
||||
{
|
||||
Args = args.ToArray(),
|
||||
ArgString = argStr,
|
||||
Seq = seq
|
||||
};
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ using Robust.Shared.Console;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Players;
|
||||
@@ -21,13 +22,13 @@ namespace Robust.Client.Console
|
||||
{
|
||||
public sealed class AddStringArgs : EventArgs
|
||||
{
|
||||
public string Text { get; }
|
||||
public FormattedMessage Text { get; }
|
||||
|
||||
public bool Local { get; }
|
||||
|
||||
public bool Error { get; }
|
||||
|
||||
public AddStringArgs(string text, bool local, bool error)
|
||||
public AddStringArgs(FormattedMessage text, bool local, bool error)
|
||||
{
|
||||
Text = text;
|
||||
Local = local;
|
||||
@@ -132,10 +133,17 @@ namespace Robust.Client.Console
|
||||
AddFormatted?.Invoke(this, new AddFormattedMessageArgs(message));
|
||||
}
|
||||
|
||||
public override void WriteLine(ICommonSession? session, FormattedMessage msg)
|
||||
{
|
||||
AddFormattedLine(msg);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void WriteError(ICommonSession? session, string text)
|
||||
{
|
||||
OutputText(text, true, true);
|
||||
var msg = new FormattedMessage();
|
||||
msg.AddText(text);
|
||||
OutputText(msg, true, true);
|
||||
}
|
||||
|
||||
public bool IsCmdServer(IConsoleCommand cmd)
|
||||
@@ -151,8 +159,13 @@ namespace Robust.Client.Console
|
||||
if (string.IsNullOrWhiteSpace(command))
|
||||
return;
|
||||
|
||||
WriteLine(null, "");
|
||||
var msg = new FormattedMessage();
|
||||
msg.PushColor(Color.Gold);
|
||||
msg.AddText("> " + command);
|
||||
msg.Pop();
|
||||
// echo the command locally
|
||||
WriteLine(null, "> " + command);
|
||||
OutputText(msg, true, false);
|
||||
|
||||
//Commands are processed locally and then sent to the server to be processed there again.
|
||||
var args = new List<string>();
|
||||
@@ -205,7 +218,9 @@ namespace Robust.Client.Console
|
||||
/// <inheritdoc />
|
||||
public override void WriteLine(ICommonSession? session, string text)
|
||||
{
|
||||
OutputText(text, true, false);
|
||||
var msg = new FormattedMessage();
|
||||
msg.AddText(text);
|
||||
OutputText(msg, true, false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -214,12 +229,12 @@ namespace Robust.Client.Console
|
||||
// We don't have anything to dispose.
|
||||
}
|
||||
|
||||
private void OutputText(string text, bool local, bool error)
|
||||
private void OutputText(FormattedMessage text, bool local, bool error)
|
||||
{
|
||||
AddString?.Invoke(this, new AddStringArgs(text, local, error));
|
||||
|
||||
var level = error ? LogLevel.Warning : LogLevel.Info;
|
||||
_conLogger.Log(level, text);
|
||||
_conLogger.Log(level, text.ToString());
|
||||
}
|
||||
|
||||
private void OnNetworkConnected(object? sender, NetChannelArgs netChannelArgs)
|
||||
@@ -229,7 +244,7 @@ namespace Robust.Client.Console
|
||||
|
||||
private void HandleConCmdAck(MsgConCmdAck msg)
|
||||
{
|
||||
OutputText("< " + msg.Text, false, msg.Error);
|
||||
OutputText(msg.Text, false, msg.Error);
|
||||
}
|
||||
|
||||
private void HandleConCmdReg(MsgConCmdReg msg)
|
||||
@@ -303,13 +318,14 @@ namespace Robust.Client.Console
|
||||
public async ValueTask<CompletionResult> GetCompletionAsync(
|
||||
IConsoleShell shell,
|
||||
string[] args,
|
||||
string argStr,
|
||||
CancellationToken cancel)
|
||||
{
|
||||
var host = (ClientConsoleHost)shell.ConsoleHost;
|
||||
var argsList = args.ToList();
|
||||
argsList.Insert(0, Command);
|
||||
|
||||
return await host.DoServerCompletions(argsList, cancel);
|
||||
return await host.DoServerCompletions(argsList, argStr, cancel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,10 +343,11 @@ namespace Robust.Client.Console
|
||||
public override async ValueTask<CompletionResult> GetCompletionAsync(
|
||||
IConsoleShell shell,
|
||||
string[] args,
|
||||
string argStr,
|
||||
CancellationToken cancel)
|
||||
{
|
||||
var host = (ClientConsoleHost)shell.ConsoleHost;
|
||||
return await host.DoServerCompletions(args.ToList(), cancel);
|
||||
return await host.DoServerCompletions(args.ToList(), argStr[">".Length..], cancel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -630,7 +630,7 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var mousePos = _eye.ScreenToMap(_input.MouseScreenPosition);
|
||||
var mousePos = _eye.PixelToMap(_input.MouseScreenPosition);
|
||||
|
||||
if (!_map.TryFindGridAt(mousePos, out var gridUid, out var grid))
|
||||
{
|
||||
|
||||
@@ -19,6 +19,6 @@ namespace Robust.Client.Console
|
||||
|
||||
void AddFormattedLine(FormattedMessage message);
|
||||
|
||||
Task<CompletionResult> GetCompletions(List<string> args, CancellationToken cancel);
|
||||
Task<CompletionResult> GetCompletions(List<string> args, string argStr, CancellationToken cancel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace Robust.Client.Debugging
|
||||
}
|
||||
|
||||
var mouseSpot = _inputManager.MouseScreenPosition;
|
||||
var spot = _eyeManager.ScreenToMap(mouseSpot);
|
||||
var spot = _eyeManager.PixelToMap(mouseSpot);
|
||||
|
||||
if (!_mapManager.TryFindGridAt(spot, out var gridUid, out var grid))
|
||||
{
|
||||
|
||||
@@ -218,7 +218,7 @@ namespace Robust.Client.Debugging
|
||||
_debugPhysicsSystem = system;
|
||||
_lookup = lookup;
|
||||
_physicsSystem = physicsSystem;
|
||||
_font = new VectorFont(cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
_font = new VectorFont(cache.GetResource<FontResource>("/EngineFonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
}
|
||||
|
||||
private void DrawWorld(DrawingHandleWorld worldHandle, OverlayDrawArgs args)
|
||||
@@ -371,7 +371,7 @@ namespace Robust.Client.Debugging
|
||||
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.ShapeInfo) != 0x0)
|
||||
{
|
||||
var hoverBodies = new List<PhysicsComponent>();
|
||||
var bounds = Box2.UnitCentered.Translated(_eyeManager.ScreenToMap(mousePos.Position).Position);
|
||||
var bounds = Box2.UnitCentered.Translated(_eyeManager.PixelToMap(mousePos.Position).Position);
|
||||
|
||||
foreach (var physBody in _physicsSystem.GetCollidingEntities(mapId, bounds))
|
||||
{
|
||||
@@ -404,7 +404,7 @@ namespace Robust.Client.Debugging
|
||||
|
||||
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.Distance) != 0x0)
|
||||
{
|
||||
var mapPos = _eyeManager.ScreenToMap(mousePos);
|
||||
var mapPos = _eyeManager.PixelToMap(mousePos);
|
||||
|
||||
if (mapPos.MapId != args.MapId)
|
||||
return;
|
||||
|
||||
@@ -33,6 +33,7 @@ using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Profiling;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Replays;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
@@ -85,6 +86,7 @@ namespace Robust.Client
|
||||
[Dependency] private readonly IReplayLoadManager _replayLoader = default!;
|
||||
[Dependency] private readonly IReplayPlaybackManager _replayPlayback = default!;
|
||||
[Dependency] private readonly IReplayRecordingManagerInternal _replayRecording = default!;
|
||||
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
||||
|
||||
private IWebViewManagerHook? _webViewHook;
|
||||
|
||||
@@ -162,6 +164,7 @@ namespace Robust.Client
|
||||
// before prototype load.
|
||||
ProgramShared.FinishCheckBadFileExtensions(checkBadExtensions);
|
||||
|
||||
_reflectionManager.Initialize();
|
||||
_prototypeManager.Initialize();
|
||||
_prototypeManager.LoadDefaultPrototypes();
|
||||
_prototypeManager.ResolveResults();
|
||||
|
||||
@@ -167,40 +167,9 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
public bool TreeUpdateQueued { get; set; }
|
||||
|
||||
[DataField("layerDatums")]
|
||||
private List<PrototypeLayerData> LayerDatums
|
||||
{
|
||||
get
|
||||
{
|
||||
var layerDatums = new List<PrototypeLayerData>();
|
||||
foreach (var layer in Layers)
|
||||
{
|
||||
layerDatums.Add(layer.ToPrototypeData());
|
||||
}
|
||||
|
||||
return layerDatums;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == null) return;
|
||||
|
||||
Layers.Clear();
|
||||
foreach (var layerDatum in value)
|
||||
{
|
||||
AddLayer(layerDatum);
|
||||
}
|
||||
|
||||
_layerMapShared = true;
|
||||
|
||||
QueueUpdateRenderTree();
|
||||
QueueUpdateIsInert();
|
||||
}
|
||||
}
|
||||
|
||||
private RSI? _baseRsi;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("rsi", priority: 2)]
|
||||
public RSI? BaseRSI
|
||||
{
|
||||
get => _baseRsi;
|
||||
@@ -357,7 +326,16 @@ namespace Robust.Client.GameObjects
|
||||
if (layerDatums.Count != 0)
|
||||
{
|
||||
LayerMap.Clear();
|
||||
LayerDatums = layerDatums;
|
||||
Layers.Clear();
|
||||
foreach (var datum in layerDatums)
|
||||
{
|
||||
AddLayer(datum);
|
||||
}
|
||||
|
||||
_layerMapShared = true;
|
||||
|
||||
QueueUpdateRenderTree();
|
||||
QueueUpdateIsInert();
|
||||
}
|
||||
|
||||
UpdateLocalMatrix();
|
||||
|
||||
@@ -314,9 +314,9 @@ public sealed class AudioSystem : SharedAudioSystem
|
||||
return source != null;
|
||||
}
|
||||
|
||||
private PlayingStream CreateAndStartPlayingStream(IClydeAudioSource source, AudioParams? audioParams)
|
||||
private PlayingStream CreateAndStartPlayingStream(IClydeAudioSource source, AudioParams? audioParams, AudioStream stream)
|
||||
{
|
||||
ApplyAudioParams(audioParams, source);
|
||||
ApplyAudioParams(audioParams, source, stream);
|
||||
source.StartPlaying();
|
||||
var playing = new PlayingStream
|
||||
{
|
||||
@@ -365,7 +365,7 @@ public sealed class AudioSystem : SharedAudioSystem
|
||||
|
||||
source.SetGlobal();
|
||||
|
||||
return CreateAndStartPlayingStream(source, audioParams);
|
||||
return CreateAndStartPlayingStream(source, audioParams, stream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -416,7 +416,7 @@ public sealed class AudioSystem : SharedAudioSystem
|
||||
if (!source.SetPosition(worldPos))
|
||||
return Play(stream, fallbackCoordinates.Value, fallbackCoordinates.Value, audioParams);
|
||||
|
||||
var playing = CreateAndStartPlayingStream(source, audioParams);
|
||||
var playing = CreateAndStartPlayingStream(source, audioParams, stream);
|
||||
playing.TrackingEntity = entity;
|
||||
playing.TrackingFallbackCoordinates = fallbackCoordinates != EntityCoordinates.Invalid ? fallbackCoordinates : null;
|
||||
return playing;
|
||||
@@ -469,7 +469,7 @@ public sealed class AudioSystem : SharedAudioSystem
|
||||
return null;
|
||||
}
|
||||
|
||||
var playing = CreateAndStartPlayingStream(source, audioParams);
|
||||
var playing = CreateAndStartPlayingStream(source, audioParams, stream);
|
||||
playing.TrackingCoordinates = coordinates;
|
||||
playing.TrackingFallbackCoordinates = fallbackCoordinates != EntityCoordinates.Invalid ? fallbackCoordinates : null;
|
||||
return playing;
|
||||
@@ -493,7 +493,7 @@ public sealed class AudioSystem : SharedAudioSystem
|
||||
return null;
|
||||
}
|
||||
|
||||
private void ApplyAudioParams(AudioParams? audioParams, IClydeAudioSource source)
|
||||
private void ApplyAudioParams(AudioParams? audioParams, IClydeAudioSource source, AudioStream audio)
|
||||
{
|
||||
if (!audioParams.HasValue)
|
||||
return;
|
||||
@@ -508,8 +508,12 @@ public sealed class AudioSystem : SharedAudioSystem
|
||||
source.SetRolloffFactor(audioParams.Value.RolloffFactor);
|
||||
source.SetMaxDistance(audioParams.Value.MaxDistance);
|
||||
source.SetReferenceDistance(audioParams.Value.ReferenceDistance);
|
||||
source.SetPlaybackPosition(audioParams.Value.PlayOffsetSeconds);
|
||||
source.IsLooping = audioParams.Value.Loop;
|
||||
|
||||
// TODO clamp the offset inside of SetPlaybackPosition() itself.
|
||||
var offset = audioParams.Value.PlayOffsetSeconds;
|
||||
offset = Math.Clamp(offset, 0f, (float) audio.Length.TotalSeconds);
|
||||
source.SetPlaybackPosition(offset);
|
||||
}
|
||||
|
||||
public sealed class PlayingStream : IPlayingAudioStream
|
||||
|
||||
@@ -16,4 +16,9 @@ internal sealed class GridRenderingSystem : EntitySystem
|
||||
{
|
||||
_clyde.RegisterGridEcsEvents();
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
_clyde.ShutdownGridEcsEvents();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -745,10 +745,6 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
}
|
||||
|
||||
var contQuery = _entities.GetEntityQuery<ContainerManagerComponent>();
|
||||
var physicsQuery = _entities.GetEntityQuery<PhysicsComponent>();
|
||||
var fixturesQuery = _entities.GetEntityQuery<FixturesComponent>();
|
||||
var broadQuery = _entities.GetEntityQuery<BroadphaseComponent>();
|
||||
var queuedBroadphaseUpdates = new List<(EntityUid, TransformComponent)>(enteringPvs);
|
||||
|
||||
// Apply entity states.
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
var cache = IoCManager.Resolve<IResourceCache>();
|
||||
_font = new VectorFont(cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
_font = new VectorFont(cache.GetResource<FontResource>("/EngineFonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
_lineHeight = _font.GetLineHeight(1);
|
||||
|
||||
_gameStateManager.GameStateApplied += HandleGameStateApplied;
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
var cache = IoCManager.Resolve<IResourceCache>();
|
||||
_font = new VectorFont(cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
_font = new VectorFont(cache.GetResource<FontResource>("/EngineFonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
|
||||
_gameStateManager.GameStateApplied += HandleGameStateApplied;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Robust.Client.Graphics.Audio
|
||||
readSamples += read;
|
||||
}
|
||||
|
||||
return new OggVorbisData(totalSamples, sampleRate, channels, buffer);
|
||||
return new OggVorbisData(totalSamples, sampleRate, channels, buffer, vorbis.Tags.Title, vorbis.Tags.Artist);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,13 +37,17 @@ namespace Robust.Client.Graphics.Audio
|
||||
public readonly long SampleRate;
|
||||
public readonly long Channels;
|
||||
public readonly ReadOnlyMemory<float> Data;
|
||||
public readonly string Title;
|
||||
public readonly string Artist;
|
||||
|
||||
public OggVorbisData(long totalSamples, long sampleRate, long channels, ReadOnlyMemory<float> data)
|
||||
public OggVorbisData(long totalSamples, long sampleRate, long channels, ReadOnlyMemory<float> data, string title, string artist)
|
||||
{
|
||||
TotalSamples = totalSamples;
|
||||
SampleRate = sampleRate;
|
||||
Channels = channels;
|
||||
Data = data;
|
||||
Title = title;
|
||||
Artist = artist;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,7 +329,7 @@ namespace Robust.Client.Graphics.Audio
|
||||
var handle = new ClydeHandle(_audioSampleBuffers.Count);
|
||||
_audioSampleBuffers.Add(new LoadedAudioSample(buffer));
|
||||
var length = TimeSpan.FromSeconds(vorbis.TotalSamples / (double) vorbis.SampleRate);
|
||||
return new AudioStream(handle, length, (int) vorbis.Channels, name);
|
||||
return new AudioStream(handle, length, (int) vorbis.Channels, name, vorbis.Title, vorbis.Artist);
|
||||
}
|
||||
|
||||
public AudioStream LoadAudioWav(Stream stream, string? name = null)
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace Robust.Client.Graphics
|
||||
[Dependency] private readonly IClyde _displayManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
|
||||
private ISawmill _logMill = default!;
|
||||
|
||||
// We default to this when we get set to a null eye.
|
||||
private readonly FixedEye _defaultEye = new();
|
||||
@@ -53,6 +54,7 @@ namespace Robust.Client.Graphics
|
||||
void IEyeManager.Initialize()
|
||||
{
|
||||
MainViewport = _uiManager.MainViewport;
|
||||
_logMill = IoCManager.Resolve<ILogManager>().RootSawmill;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -129,14 +131,14 @@ namespace Robust.Client.Graphics
|
||||
/// <inheritdoc />
|
||||
public ScreenCoordinates CoordinatesToScreen(EntityCoordinates point)
|
||||
{
|
||||
return MapToScreen(point.ToMap(_entityManager));
|
||||
return MapToScreen(point.ToMap(_entityManager, _entityManager.System<SharedTransformSystem>()));
|
||||
}
|
||||
|
||||
public ScreenCoordinates MapToScreen(MapCoordinates point)
|
||||
{
|
||||
if (CurrentEye.Position.MapId != point.MapId)
|
||||
{
|
||||
Logger.Error($"Attempted to convert map coordinates ({point}) to screen coordinates with an eye on another map ({CurrentEye.Position.MapId})");
|
||||
_logMill.Error($"Attempted to convert map coordinates ({point}) to screen coordinates with an eye on another map ({CurrentEye.Position.MapId})");
|
||||
return new(default, WindowId.Invalid);
|
||||
}
|
||||
|
||||
@@ -146,12 +148,10 @@ namespace Robust.Client.Graphics
|
||||
/// <inheritdoc />
|
||||
public MapCoordinates ScreenToMap(ScreenCoordinates point)
|
||||
{
|
||||
var (pos, window) = point;
|
||||
|
||||
if (_uiManager.MouseGetControl(point) is not IViewportControl viewport)
|
||||
return default;
|
||||
|
||||
return viewport.ScreenToMap(pos);
|
||||
return viewport.ScreenToMap(point.Position);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -159,6 +159,21 @@ namespace Robust.Client.Graphics
|
||||
{
|
||||
return MainViewport.ScreenToMap(point);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MapCoordinates PixelToMap(ScreenCoordinates point)
|
||||
{
|
||||
if (_uiManager.MouseGetControl(point) is not IViewportControl viewport)
|
||||
return default;
|
||||
|
||||
return viewport.PixelToMap(point.Position);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MapCoordinates PixelToMap(Vector2 point)
|
||||
{
|
||||
return MainViewport.PixelToMap(point);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class CurrentEyeChangedEvent : EntityEventArgs
|
||||
|
||||
@@ -79,6 +79,16 @@ namespace Robust.Client.Graphics
|
||||
/// <returns>Corresponding point in the world.</returns>
|
||||
MapCoordinates ScreenToMap(Vector2 point);
|
||||
|
||||
/// <summary>
|
||||
/// Similar to <see cref="ScreenToMap(ScreenCoordinates)"/>, except it should compensate for the effects of shaders on viewports.
|
||||
/// </summary>
|
||||
MapCoordinates PixelToMap(ScreenCoordinates point);
|
||||
|
||||
/// <summary>
|
||||
/// Similar to <see cref="ScreenToMap(Vector2)"/>, except it should compensate for the effects of shaders on viewports.
|
||||
/// </summary>
|
||||
MapCoordinates PixelToMap(Vector2 point);
|
||||
|
||||
void ClearCurrentEye();
|
||||
void Initialize();
|
||||
}
|
||||
|
||||
@@ -176,6 +176,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_entityManager.EventBus.SubscribeEvent<GridModifiedEvent>(EventSource.Local, this, _updateOnGridModified);
|
||||
}
|
||||
|
||||
public void ShutdownGridEcsEvents()
|
||||
{
|
||||
_entityManager.EventBus.UnsubscribeEvent<TileChangedEvent>(EventSource.Local, this);
|
||||
_entityManager.EventBus.UnsubscribeEvent<GridStartupEvent>(EventSource.Local, this);
|
||||
_entityManager.EventBus.UnsubscribeEvent<GridRemovalEvent>(EventSource.Local, this);
|
||||
_entityManager.EventBus.UnsubscribeEvent<GridModifiedEvent>(EventSource.Local, this);
|
||||
}
|
||||
|
||||
private void GLInitBindings(bool gles)
|
||||
{
|
||||
_glBindingsContext = _glContext!.BindingsContext;
|
||||
|
||||
@@ -88,6 +88,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void ShutdownGridEcsEvents()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void SetWindowTitle(string title)
|
||||
{
|
||||
// Nada.
|
||||
|
||||
@@ -12,9 +12,9 @@ void main()
|
||||
|
||||
lowp vec4 COLOR;
|
||||
|
||||
// [SHADER_CODE]
|
||||
|
||||
lowp vec3 lightSample = texture2D(lightMap, Pos).rgb;
|
||||
|
||||
// [SHADER_CODE]
|
||||
|
||||
gl_FragColor = zAdjustResult(COLOR * VtxModulate * vec4(lightSample, 1.0));
|
||||
}
|
||||
|
||||
@@ -207,6 +207,15 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{GlfwKey.F13, Key.F13},
|
||||
{GlfwKey.F14, Key.F14},
|
||||
{GlfwKey.F15, Key.F15},
|
||||
{GlfwKey.F16, Key.F16},
|
||||
{GlfwKey.F17, Key.F17},
|
||||
{GlfwKey.F18, Key.F18},
|
||||
{GlfwKey.F19, Key.F19},
|
||||
{GlfwKey.F20, Key.F20},
|
||||
{GlfwKey.F21, Key.F21},
|
||||
{GlfwKey.F22, Key.F22},
|
||||
{GlfwKey.F23, Key.F23},
|
||||
{GlfwKey.F24, Key.F24},
|
||||
{GlfwKey.Pause, Key.Pause},
|
||||
{GlfwKey.World1, Key.World1},
|
||||
};
|
||||
|
||||
@@ -191,6 +191,15 @@ internal partial class Clyde
|
||||
MapKey(SDL_SCANCODE_F13, Key.F13);
|
||||
MapKey(SDL_SCANCODE_F14, Key.F14);
|
||||
MapKey(SDL_SCANCODE_F15, Key.F15);
|
||||
MapKey(SDL_SCANCODE_F16, Key.F16);
|
||||
MapKey(SDL_SCANCODE_F17, Key.F17);
|
||||
MapKey(SDL_SCANCODE_F18, Key.F18);
|
||||
MapKey(SDL_SCANCODE_F19, Key.F19);
|
||||
MapKey(SDL_SCANCODE_F20, Key.F20);
|
||||
MapKey(SDL_SCANCODE_F21, Key.F21);
|
||||
MapKey(SDL_SCANCODE_F22, Key.F22);
|
||||
MapKey(SDL_SCANCODE_F23, Key.F23);
|
||||
MapKey(SDL_SCANCODE_F24, Key.F24);
|
||||
MapKey(SDL_SCANCODE_PAUSE, Key.Pause);
|
||||
|
||||
KeyMapReverse = new Dictionary<Key, SDL_Scancode>();
|
||||
|
||||
@@ -65,6 +65,8 @@ namespace Robust.Client.Graphics
|
||||
|
||||
void RegisterGridEcsEvents();
|
||||
|
||||
void ShutdownGridEcsEvents();
|
||||
|
||||
void RunOnWindowThread(Action action);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
@@ -11,6 +12,7 @@ namespace Robust.Client.Graphics
|
||||
{
|
||||
[Dependency] private readonly ILogManager _logMan = default!;
|
||||
|
||||
[ViewVariables]
|
||||
private readonly Dictionary<Type, Overlay> _overlays = new Dictionary<Type, Overlay>();
|
||||
private ISawmill _logger = default!;
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Robust.Client.Graphics
|
||||
public sealed class ShaderPrototype : IPrototype, ISerializationHooks
|
||||
{
|
||||
[ViewVariables]
|
||||
[IdDataFieldAttribute]
|
||||
[IdDataField]
|
||||
public string ID { get; } = default!;
|
||||
|
||||
[ViewVariables] private ShaderKind Kind;
|
||||
@@ -65,12 +65,12 @@ namespace Robust.Client.Graphics
|
||||
|
||||
case ShaderKind.Canvas:
|
||||
|
||||
var hasLight = rawMode != "unshaded";
|
||||
var hasLight = _rawMode != "unshaded";
|
||||
ShaderBlendMode? blend = null;
|
||||
if (rawBlendMode != null)
|
||||
if (_rawBlendMode != null)
|
||||
{
|
||||
if (!Enum.TryParse<ShaderBlendMode>(rawBlendMode.ToUpper(), out var parsed))
|
||||
Logger.Error($"invalid mode: {rawBlendMode}");
|
||||
if (!Enum.TryParse<ShaderBlendMode>(_rawBlendMode.ToUpper(), out var parsed))
|
||||
Logger.Error($"invalid mode: {_rawBlendMode}");
|
||||
else
|
||||
blend = parsed;
|
||||
}
|
||||
@@ -94,11 +94,20 @@ namespace Robust.Client.Graphics
|
||||
return Instance().Duplicate();
|
||||
}
|
||||
|
||||
[DataField("kind", readOnly: true, required: true)] private string _rawKind = default!;
|
||||
[DataField("path", readOnly: true)] private ResPath path;
|
||||
[DataField("params", readOnly: true)] private Dictionary<string, string>? paramMapping;
|
||||
[DataField("light_mode", readOnly: true)] private string? rawMode;
|
||||
[DataField("blend_mode", readOnly: true)] private string? rawBlendMode;
|
||||
[DataField("kind", required: true)]
|
||||
private readonly string _rawKind = default!;
|
||||
|
||||
[DataField("path")]
|
||||
private readonly ResPath? _path;
|
||||
|
||||
[DataField("params")]
|
||||
private readonly Dictionary<string, string>? _paramMapping;
|
||||
|
||||
[DataField("light_mode")]
|
||||
private readonly string? _rawMode;
|
||||
|
||||
[DataField("blend_mode")]
|
||||
private readonly string? _rawBlendMode;
|
||||
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
{
|
||||
@@ -106,18 +115,22 @@ namespace Robust.Client.Graphics
|
||||
{
|
||||
case "source":
|
||||
Kind = ShaderKind.Source;
|
||||
if (path == null) throw new InvalidOperationException();
|
||||
_source = IoCManager.Resolve<IResourceCache>().GetResource<ShaderSourceResource>(path);
|
||||
|
||||
if (paramMapping != null)
|
||||
// TODO use a custom type serializer.
|
||||
if (_path == null)
|
||||
throw new InvalidOperationException("Source shaders must specify a source file.");
|
||||
|
||||
_source = IoCManager.Resolve<IResourceCache>().GetResource<ShaderSourceResource>(_path.Value);
|
||||
|
||||
if (_paramMapping != null)
|
||||
{
|
||||
_params = new Dictionary<string, object>();
|
||||
foreach (var item in paramMapping!)
|
||||
foreach (var item in _paramMapping!)
|
||||
{
|
||||
var name = item.Key;
|
||||
if (!_source.ParsedShader.Uniforms.TryGetValue(name, out var uniformDefinition))
|
||||
{
|
||||
Logger.ErrorS("shader", "Shader param '{0}' does not exist on shader '{1}'", name, path);
|
||||
Logger.ErrorS("shader", "Shader param '{0}' does not exist on shader '{1}'", name, _path);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -161,6 +161,15 @@ namespace Robust.Client.Input
|
||||
F13,
|
||||
F14,
|
||||
F15,
|
||||
F16,
|
||||
F17,
|
||||
F18,
|
||||
F19,
|
||||
F20,
|
||||
F21,
|
||||
F22,
|
||||
F23,
|
||||
F24,
|
||||
Pause,
|
||||
World1,
|
||||
}
|
||||
|
||||
@@ -3,13 +3,16 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Map;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Toolshed;
|
||||
using Robust.Shared.Utility;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
@@ -52,8 +55,11 @@ namespace Robust.Client.Map
|
||||
_genTextureAtlas();
|
||||
}
|
||||
|
||||
private void _genTextureAtlas()
|
||||
internal void _genTextureAtlas()
|
||||
{
|
||||
_tileRegions.Clear();
|
||||
_tileTextureAtlas = null;
|
||||
|
||||
var defList = TileDefs.Where(t => t.Sprite != null).ToList();
|
||||
|
||||
// If there are no tile definitions, we do nothing.
|
||||
@@ -144,4 +150,17 @@ namespace Robust.Client.Map
|
||||
_tileTextureAtlas = Texture.LoadFromImage(sheet, "Tile Atlas");
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ReloadTileTexturesCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly ClydeTileDefinitionManager _tile = default!;
|
||||
|
||||
public override string Command => "reloadtiletextures";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
_tile._genTextureAtlas();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -532,7 +532,7 @@ namespace Robust.Client.Placement
|
||||
return false;
|
||||
}
|
||||
coordinates = EntityCoordinates.FromMap(MapManager,
|
||||
EyeManager.ScreenToMap(InputManager.MouseScreenPosition));
|
||||
EyeManager.PixelToMap(InputManager.MouseScreenPosition));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ namespace Robust.Client.Placement
|
||||
public IEnumerable<EntityCoordinates> LineCoordinates()
|
||||
{
|
||||
var mouseScreen = pManager.InputManager.MouseScreenPosition;
|
||||
var mousePos = pManager.EyeManager.ScreenToMap(mouseScreen);
|
||||
var mousePos = pManager.EyeManager.PixelToMap(mouseScreen);
|
||||
|
||||
if (mousePos.MapId == MapId.Nullspace)
|
||||
yield break;
|
||||
@@ -165,7 +165,7 @@ namespace Robust.Client.Placement
|
||||
public IEnumerable<EntityCoordinates> GridCoordinates()
|
||||
{
|
||||
var mouseScreen = pManager.InputManager.MouseScreenPosition;
|
||||
var mousePos = pManager.EyeManager.ScreenToMap(mouseScreen);
|
||||
var mousePos = pManager.EyeManager.PixelToMap(mouseScreen);
|
||||
|
||||
if (mousePos.MapId == MapId.Nullspace)
|
||||
yield break;
|
||||
@@ -254,7 +254,7 @@ namespace Robust.Client.Placement
|
||||
|
||||
protected EntityCoordinates ScreenToCursorGrid(ScreenCoordinates coords)
|
||||
{
|
||||
var mapCoords = pManager.EyeManager.ScreenToMap(coords.Position);
|
||||
var mapCoords = pManager.EyeManager.PixelToMap(coords.Position);
|
||||
if (!pManager.MapManager.TryFindGridAt(mapCoords, out var gridUid, out var grid))
|
||||
{
|
||||
return EntityCoordinates.FromMap(pManager.MapManager, mapCoords);
|
||||
|
||||
@@ -3,14 +3,18 @@ using System.Collections.Generic;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Client.Player
|
||||
{
|
||||
public interface IPlayerManager : Shared.Players.ISharedPlayerManager
|
||||
{
|
||||
new IEnumerable<ICommonSession> Sessions { get; }
|
||||
|
||||
[ViewVariables]
|
||||
IReadOnlyDictionary<NetUserId, ICommonSession> SessionsDict { get; }
|
||||
|
||||
[ViewVariables]
|
||||
LocalPlayer? LocalPlayer { get; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -2,6 +2,7 @@ using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Client.Player
|
||||
{
|
||||
@@ -17,11 +18,14 @@ namespace Robust.Client.Player
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[ViewVariables]
|
||||
public EntityUid? AttachedEntity { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[ViewVariables]
|
||||
public NetUserId UserId { get; }
|
||||
|
||||
[ViewVariables]
|
||||
internal string Name { get; set; } = "<Unknown>";
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -31,9 +35,11 @@ namespace Robust.Client.Player
|
||||
set => this.Name = value;
|
||||
}
|
||||
|
||||
[ViewVariables]
|
||||
internal short Ping { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[ViewVariables]
|
||||
public INetChannel ConnectedClient { get; internal set; } = null!;
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -24,7 +24,7 @@ public sealed class LiveProfileViewControl : Control
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
if (!_resourceCache.TryGetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf", out var font))
|
||||
if (!_resourceCache.TryGetResource<FontResource>("/EngineFonts/NotoSans/NotoSans-Regular.ttf", out var font))
|
||||
return;
|
||||
|
||||
_font = font.MakeDefault();
|
||||
|
||||
@@ -7,6 +7,7 @@ using Robust.Shared.Network;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Client.Upload.Commands;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Replays;
|
||||
@@ -131,12 +132,16 @@ public sealed partial class ReplayLoadManager
|
||||
var ticksSinceLastCheckpoint = 0;
|
||||
var spawnedTracker = 0;
|
||||
var stateTracker = 0;
|
||||
var curState = state0;
|
||||
for (var i = 1; i < states.Count; i++)
|
||||
{
|
||||
if (i % 10 == 0)
|
||||
await callback(i, states.Count, LoadingState.ProcessingFiles, false);
|
||||
|
||||
var curState = states[i];
|
||||
var lastState = curState;
|
||||
curState = states[i];
|
||||
DebugTools.Assert(curState.FromSequence <= lastState.ToSequence);
|
||||
|
||||
UpdatePlayerStates(curState.PlayerStates.Span, playerStates);
|
||||
UpdateEntityStates(curState.EntityStates.Span, entStates, ref spawnedTracker, ref stateTracker, detached);
|
||||
UpdateMessages(messages[i], uploadedFiles, prototypes, cvars, detachQueue, ref timeBase);
|
||||
@@ -222,7 +227,13 @@ public sealed partial class ReplayLoadManager
|
||||
// forwards. Also, note that files HAVE to be uploaded while generating checkpoints, in case
|
||||
// someone spawns an entity that relies on uploaded data.
|
||||
if (!ignoreDuplicates)
|
||||
throw new NotSupportedException("Overwriting an existing file is not yet supported by replays.");
|
||||
{
|
||||
var msg = $"Overwriting an existing file upload! Path: {path}";
|
||||
if (_confMan.GetCVar(CVars.ReplayIgnoreErrors))
|
||||
_sawmill.Error(msg);
|
||||
else
|
||||
throw new NotSupportedException(msg);
|
||||
}
|
||||
|
||||
message.Messages.RemoveSwap(i);
|
||||
break;
|
||||
@@ -240,31 +251,56 @@ public sealed partial class ReplayLoadManager
|
||||
continue;
|
||||
|
||||
message.Messages.RemoveSwap(i);
|
||||
var changed = new Dictionary<Type, HashSet<string>>();
|
||||
_protoMan.LoadString(protoUpload.PrototypeData, true, changed);
|
||||
|
||||
foreach (var (kind, ids) in changed)
|
||||
try
|
||||
{
|
||||
var protos = prototypes[kind];
|
||||
var count = protos.Count;
|
||||
protos.UnionWith(ids);
|
||||
if (!ignoreDuplicates && ids.Count + count != protos.Count)
|
||||
{
|
||||
// An existing prototype was overwritten. Much like for resource uploading, supporting this
|
||||
// requires tracking the last-modified time of prototypes and either resetting or applying
|
||||
// prototype changes when jumping around in time. This also requires reworking how the initial
|
||||
// implicit state data is generated, because we can't simply cache it anymore.
|
||||
// Also, does reloading prototypes in release mode modify existing entities?
|
||||
throw new NotSupportedException($"Overwriting an existing prototype is not yet supported by replays.");
|
||||
}
|
||||
LoadPrototype(protoUpload.PrototypeData, prototypes, ignoreDuplicates);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (e is NotSupportedException || !_confMan.GetCVar(CVars.ReplayIgnoreErrors))
|
||||
throw;
|
||||
|
||||
_protoMan.ResolveResults();
|
||||
_protoMan.ReloadPrototypes(changed);
|
||||
_locMan.ReloadLocalizations();
|
||||
var msg = $"Caught exception while parsing uploaded prototypes in a replay. Exception: {e}";
|
||||
_sawmill.Error(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadPrototype(
|
||||
string data,
|
||||
Dictionary<Type, HashSet<string>> prototypes,
|
||||
bool ignoreDuplicates)
|
||||
{
|
||||
var changed = new Dictionary<Type, HashSet<string>>();
|
||||
_protoMan.LoadString(data, true, changed);
|
||||
|
||||
foreach (var (kind, ids) in changed)
|
||||
{
|
||||
var protos = prototypes[kind];
|
||||
var count = protos.Count;
|
||||
protos.UnionWith(ids);
|
||||
if (!ignoreDuplicates && ids.Count + count != protos.Count)
|
||||
{
|
||||
// An existing prototype was overwritten. Much like for resource uploading, supporting this
|
||||
// requires tracking the last-modified time of prototypes and either resetting or applying
|
||||
// prototype changes when jumping around in time. This also requires reworking how the initial
|
||||
// implicit state data is generated, because we can't simply cache it anymore.
|
||||
// Also, does reloading prototypes in release mode modify existing entities?
|
||||
|
||||
var msg = $"Overwriting an existing prototype! Kind: {kind.Name}. Ids: {string.Join(", ", ids)}";
|
||||
if (_confMan.GetCVar(CVars.ReplayIgnoreErrors))
|
||||
_sawmill.Error(msg);
|
||||
else
|
||||
throw new NotSupportedException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
_protoMan.ResolveResults();
|
||||
_protoMan.ReloadPrototypes(changed);
|
||||
_locMan.ReloadLocalizations();
|
||||
}
|
||||
|
||||
private void UpdateDeletions(NetListAsArray<EntityUid> entityDeletions,
|
||||
Dictionary<EntityUid, EntityState> entStates, HashSet<EntityUid> detached)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.GameStates;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -64,6 +66,18 @@ public sealed partial class ReplayLoadManager
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception("Missing metadata component");
|
||||
if (!entState.ComponentChanges.HasContents)
|
||||
{
|
||||
// This shouldn't be possible, yet it has happened?
|
||||
// TODO this should probably also throw an exception.
|
||||
_sawmill.Error($"Encountered blank entity state? Entity: {entState.Uid}. Last modified: {entState.EntityLastModified}. Attempting to continue.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!_confMan.GetCVar(CVars.ReplayIgnoreErrors))
|
||||
throw new MissingMetadataException(entState.Uid);
|
||||
|
||||
_sawmill.Error($"Missing metadata component. Entity: {entState.Uid}. Last modified: {entState.EntityLastModified}.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ using Robust.Shared.Utility;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Replays;
|
||||
using static Robust.Shared.Replays.ReplayConstants;
|
||||
|
||||
@@ -136,9 +137,21 @@ public sealed partial class ReplayLoadManager
|
||||
if (data == null)
|
||||
throw new Exception("Failed to load yaml metadata");
|
||||
|
||||
TimeSpan duration;
|
||||
var finalData = LoadYamlFinalMetadata(fileReader);
|
||||
if (finalData == null)
|
||||
throw new Exception("Failed to load final yaml metadata");
|
||||
{
|
||||
var msg = "Failed to load final yaml metadata";
|
||||
if (!_confMan.GetCVar(CVars.ReplayIgnoreErrors))
|
||||
throw new Exception(msg);
|
||||
|
||||
_sawmill.Error(msg);
|
||||
duration = TimeSpan.FromDays(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
duration = TimeSpan.Parse(((ValueDataNode) finalData[MetaFinalKeyDuration]).Value);
|
||||
}
|
||||
|
||||
var typeHash = Convert.FromHexString(((ValueDataNode) data[MetaKeyTypeHash]).Value);
|
||||
var stringHash = Convert.FromHexString(((ValueDataNode) data[MetaKeyStringHash]).Value);
|
||||
@@ -146,7 +159,6 @@ public sealed partial class ReplayLoadManager
|
||||
var timeBaseTick = ((ValueDataNode) data[MetaKeyBaseTick]).Value;
|
||||
var timeBaseTimespan = ((ValueDataNode) data[MetaKeyBaseTime]).Value;
|
||||
var clientSide = bool.Parse(((ValueDataNode) data[MetaKeyIsClientRecording]).Value);
|
||||
var duration = TimeSpan.Parse(((ValueDataNode) finalData[MetaFinalKeyDuration]).Value);
|
||||
|
||||
if (!typeHash.SequenceEqual(_serializer.GetSerializableTypesHash()))
|
||||
throw new Exception($"{nameof(IRobustSerializer)} hashes do not match. Loading replays using a bad replay-client version?");
|
||||
|
||||
@@ -97,6 +97,8 @@ internal sealed partial class ReplayPlaybackManager
|
||||
|
||||
if (message is EntityEventArgs args)
|
||||
_entMan.DispatchReceivedNetworkMsg(args);
|
||||
else if (_warned.Add(message.GetType()))
|
||||
_sawmill.Error($"Unhandled replay message: {message.GetType()}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ internal sealed partial class ReplayPlaybackManager : IReplayPlaybackManager
|
||||
|
||||
private bool _initialized;
|
||||
private ISawmill _sawmill = default!;
|
||||
private HashSet<Type> _warned = new();
|
||||
|
||||
public bool Playing
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -40,5 +41,9 @@ namespace Robust.Client.ResourceManagement
|
||||
// Resource load callbacks so content can hook stuff like click maps.
|
||||
event Action<TextureLoadedEventArgs> OnRawTextureLoaded;
|
||||
event Action<RsiLoadedEventArgs> OnRsiLoaded;
|
||||
|
||||
IClyde Clyde { get; }
|
||||
IClydeAudio ClydeAudio { get; }
|
||||
IFontManager FontManager { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,9 @@ namespace Robust.Client.ResourceManagement
|
||||
{
|
||||
internal partial class ResourceCache
|
||||
{
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
[field: Dependency] public IClyde Clyde { get; } = default!;
|
||||
[field: Dependency] public IClydeAudio ClydeAudio { get; } = default!;
|
||||
[field: Dependency] public IFontManager FontManager { get; } = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
|
||||
@@ -70,7 +72,7 @@ namespace Robust.Client.ResourceManagement
|
||||
|
||||
try
|
||||
{
|
||||
TextureResource.LoadTexture(_clyde, data);
|
||||
TextureResource.LoadTexture(Clyde, data);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -198,7 +200,7 @@ namespace Robust.Client.ResourceManagement
|
||||
|
||||
void FinalizeMetaAtlas(int toIndex, Image<Rgba32> sheet)
|
||||
{
|
||||
var atlas = _clyde.LoadTextureFromImage(sheet);
|
||||
var atlas = Clyde.LoadTextureFromImage(sheet);
|
||||
for (int i = finalized + 1; i <= toIndex; i++)
|
||||
{
|
||||
var rsi = rsiList[i];
|
||||
|
||||
@@ -20,14 +20,13 @@ namespace Robust.Client.ResourceManagement
|
||||
|
||||
using (var fileStream = cache.ContentFileRead(path))
|
||||
{
|
||||
var clyde = IoCManager.Resolve<IClydeAudio>();
|
||||
if (path.Extension == "ogg")
|
||||
{
|
||||
AudioStream = clyde.LoadAudioOggVorbis(fileStream, path.ToString());
|
||||
AudioStream = cache.ClydeAudio.LoadAudioOggVorbis(fileStream, path.ToString());
|
||||
}
|
||||
else if (path.Extension == "wav")
|
||||
{
|
||||
AudioStream = clyde.LoadAudioWav(fileStream, path.ToString());
|
||||
AudioStream = cache.ClydeAudio.LoadAudioWav(fileStream, path.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Robust.Client.ResourceManagement
|
||||
|
||||
using (stream)
|
||||
{
|
||||
FontFaceHandle = IoCManager.Resolve<IFontManagerInternal>().Load(stream);
|
||||
FontFaceHandle = ((IFontManagerInternal)cache.FontManager).Load(stream);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,12 +34,10 @@ namespace Robust.Client.ResourceManagement
|
||||
|
||||
public override void Load(IResourceCache cache, ResPath path)
|
||||
{
|
||||
var clyde = IoCManager.Resolve<IClyde>();
|
||||
|
||||
var loadStepData = new LoadStepData {Path = path};
|
||||
LoadPreTexture(cache, loadStepData);
|
||||
|
||||
loadStepData.AtlasTexture = clyde.LoadTextureFromImage(
|
||||
loadStepData.AtlasTexture = cache.Clyde.LoadTextureFromImage(
|
||||
loadStepData.AtlasSheet,
|
||||
loadStepData.Path.ToString());
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Graphics.Clyde;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -27,8 +28,7 @@ namespace Robust.Client.ResourceManagement
|
||||
ParsedShader = ShaderParser.Parse(reader, cache);
|
||||
}
|
||||
|
||||
var clyde = IoCManager.Resolve<IClydeInternal>();
|
||||
ClydeHandle = clyde.LoadShader(ParsedShader, path.ToString());
|
||||
ClydeHandle = ((IClydeInternal)cache.Clyde).LoadShader(ParsedShader, path.ToString());
|
||||
}
|
||||
|
||||
public override void Reload(IResourceCache cache, ResPath path, CancellationToken ct = default)
|
||||
@@ -57,8 +57,7 @@ namespace Robust.Client.ResourceManagement
|
||||
}
|
||||
}
|
||||
|
||||
var clyde = IoCManager.Resolve<IClydeInternal>();
|
||||
clyde.ReloadShader(ClydeHandle, ParsedShader);
|
||||
((IClydeInternal)cache.Clyde).ReloadShader(ClydeHandle, ParsedShader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,6 @@ namespace Robust.Client.ResourceManagement
|
||||
|
||||
public override void Load(IResourceCache cache, ResPath path)
|
||||
{
|
||||
var clyde = IoCManager.Resolve<IClyde>();
|
||||
|
||||
if (path.Directory.Filename.EndsWith(".rsi"))
|
||||
{
|
||||
Logger.WarningS(
|
||||
@@ -33,7 +31,7 @@ namespace Robust.Client.ResourceManagement
|
||||
var data = new LoadStepData {Path = path};
|
||||
|
||||
LoadPreTexture(cache, data);
|
||||
LoadTexture(clyde, data);
|
||||
LoadTexture(cache.Clyde, data);
|
||||
LoadFinish(cache, data);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,25 +10,26 @@
|
||||
<RobustILLink>true</RobustILLink>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.0.175" />
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.0.175" PrivateAssets="compile" />
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2021.3.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="6.0.9" />
|
||||
<PackageReference Include="SQLitePCLRaw.provider.sqlite3" Version="2.1.2" Condition="'$(UseSystemSqlite)' == 'True'" />
|
||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.2" Condition="'$(UseSystemSqlite)' != 'True'" />
|
||||
<PackageReference Include="SpaceWizards.NFluidsynth" Version="0.1.1" />
|
||||
<PackageReference Include="NVorbis" Version="0.10.1" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="6.0.9" PrivateAssets="compile" />
|
||||
<PackageReference Include="SQLitePCLRaw.provider.sqlite3" Version="2.1.2" Condition="'$(UseSystemSqlite)' == 'True'" PrivateAssets="compile" />
|
||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.2" Condition="'$(UseSystemSqlite)' != 'True'" PrivateAssets="compile" />
|
||||
<PackageReference Include="SpaceWizards.NFluidsynth" Version="0.1.1" PrivateAssets="compile" />
|
||||
<PackageReference Include="NVorbis" Version="0.10.1" PrivateAssets="compile" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
|
||||
<PackageReference Include="OpenToolkit.Graphics" Version="4.0.0-pre9.1" />
|
||||
<PackageReference Include="OpenTK.OpenAL" Version="4.7.5" />
|
||||
<PackageReference Include="SpaceWizards.SharpFont" Version="1.0.1" />
|
||||
<PackageReference Include="OpenToolkit.Graphics" Version="4.0.0-pre9.1" PrivateAssets="compile" />
|
||||
<PackageReference Include="OpenTK.OpenAL" Version="4.7.5" PrivateAssets="compile" />
|
||||
<PackageReference Include="SpaceWizards.SharpFont" Version="1.0.1" PrivateAssets="compile" />
|
||||
<PackageReference Include="Robust.Natives" Version="0.1.1" />
|
||||
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.20348-rc2" />
|
||||
<PackageReference Condition="'$(FullRelease)' != 'True'" Include="JetBrains.Profiler.Api" Version="1.2.0" />
|
||||
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.20348-rc2" PrivateAssets="compile" />
|
||||
<PackageReference Condition="'$(FullRelease)' != 'True'" Include="JetBrains.Profiler.Api" Version="1.2.0" PrivateAssets="compile" />
|
||||
<PackageReference Include="SpaceWizards.Sodium" Version="0.2.1" PrivateAssets="compile" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(EnableClientScripting)' == 'True'">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Features" Version="4.0.1" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.0.1" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.0.1" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Features" Version="4.0.1" PrivateAssets="compile" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.0.1" PrivateAssets="compile" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.0.1" PrivateAssets="compile" />
|
||||
|
||||
<ProjectReference Include="..\Robust.Shared.Scripting\Robust.Shared.Scripting.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
@@ -29,7 +28,7 @@ public sealed class ClientSpriteSpecifierSerializer : SpriteSpecifierSerializer
|
||||
}
|
||||
|
||||
var res = dependencies.Resolve<IResourceCache>();
|
||||
var rsiPath = SpriteSpecifierSerializer.TextureRoot / valuePathNode.Value;
|
||||
var rsiPath = TextureRoot / valuePathNode.Value;
|
||||
if (!res.TryGetResource(rsiPath, out RSIResource? resource))
|
||||
{
|
||||
return new ErrorNode(node, "Failed to load RSI");
|
||||
@@ -40,6 +39,10 @@ public sealed class ClientSpriteSpecifierSerializer : SpriteSpecifierSerializer
|
||||
return new ErrorNode(node, "Invalid RSI state");
|
||||
}
|
||||
|
||||
return new ValidatedMappingNode(new());
|
||||
return new ValidatedMappingNode(new()
|
||||
{
|
||||
{ new ValidatedValueNode(new ValueDataNode("sprite")), new ValidatedValueNode(pathNode)},
|
||||
{ new ValidatedValueNode(new ValueDataNode("state")), new ValidatedValueNode(valueStateNode)},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
public void AddMessage(FormattedMessage message)
|
||||
{
|
||||
var entry = new RichTextEntry(message, this, _tagManager);
|
||||
var entry = new RichTextEntry(message, this, _tagManager, null);
|
||||
|
||||
entry.Update(_getFont(), _getContentBox().Width, UIScale);
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Numerics;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.RichText;
|
||||
@@ -21,18 +22,18 @@ namespace Robust.Client.UserInterface.Controls
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
public void SetMessage(FormattedMessage message)
|
||||
public void SetMessage(FormattedMessage message, Type[]? tagsAllowed = null, Color? defaultColor = null)
|
||||
{
|
||||
_message = message;
|
||||
_entry = new RichTextEntry(_message, this, _tagManager);
|
||||
_entry = new RichTextEntry(_message, this, _tagManager, tagsAllowed, defaultColor);
|
||||
InvalidateMeasure();
|
||||
}
|
||||
|
||||
public void SetMessage(string message)
|
||||
public void SetMessage(string message, Type[]? tagsAllowed = null, Color? defaultColor = null)
|
||||
{
|
||||
var msg = new FormattedMessage();
|
||||
msg.AddText(message);
|
||||
SetMessage(msg);
|
||||
SetMessage(msg, tagsAllowed, defaultColor);
|
||||
}
|
||||
|
||||
public string? GetMessage() => _message?.ToMarkup();
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
public const string LeftButtonStyle = "spinbox-left";
|
||||
public const string RightButtonStyle = "spinbox-right";
|
||||
public const string MiddleButtonStyle = "spinbox-middle";
|
||||
private LineEdit _lineEdit;
|
||||
public LineEdit LineEditControl { get; }
|
||||
private List<Button> _leftButtons = new();
|
||||
private List<Button> _rightButtons = new();
|
||||
private int _stepSize = 1;
|
||||
@@ -35,7 +35,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
return;
|
||||
}
|
||||
_value = value;
|
||||
_lineEdit.Text = value.ToString();
|
||||
LineEditControl.Text = value.ToString();
|
||||
ValueChanged?.Invoke(new ValueChangedEventArgs(value));
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
return;
|
||||
}
|
||||
_value = value;
|
||||
_lineEdit.Text = value.ToString();
|
||||
LineEditControl.Text = value.ToString();
|
||||
}
|
||||
|
||||
public event Action<ValueChangedEventArgs>? ValueChanged;
|
||||
@@ -62,17 +62,17 @@ namespace Robust.Client.UserInterface.Controls
|
||||
Orientation = LayoutOrientation.Horizontal;
|
||||
MouseFilter = MouseFilterMode.Pass;
|
||||
|
||||
_lineEdit = new LineEdit
|
||||
LineEditControl = new LineEdit
|
||||
{
|
||||
MinSize = new Vector2(40, 0),
|
||||
HorizontalExpand = true
|
||||
};
|
||||
AddChild(_lineEdit);
|
||||
AddChild(LineEditControl);
|
||||
|
||||
Value = 0;
|
||||
|
||||
_lineEdit.IsValid = (str) => int.TryParse(str, out var i) && (IsValid == null || IsValid(i));
|
||||
_lineEdit.OnTextChanged += (args) =>
|
||||
LineEditControl.IsValid = (str) => int.TryParse(str, out var i) && (IsValid == null || IsValid(i));
|
||||
LineEditControl.OnTextChanged += (args) =>
|
||||
{
|
||||
if (int.TryParse(args.Text, out int i))
|
||||
Value = i;
|
||||
@@ -143,8 +143,8 @@ namespace Robust.Client.UserInterface.Controls
|
||||
/// </summary>
|
||||
public bool LineEditDisabled
|
||||
{
|
||||
get => !_lineEdit.Editable;
|
||||
set => _lineEdit.Editable = !value;
|
||||
get => !LineEditControl.Editable;
|
||||
set => LineEditControl.Editable = !value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -185,7 +185,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
base.MouseWheel(args);
|
||||
|
||||
if (!_lineEdit.HasKeyboardFocus())
|
||||
if (!LineEditControl.HasKeyboardFocus())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Control xmlns="https://spacestation14.io"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<OutputPanel Name="Output" VerticalExpand="True">
|
||||
<OutputPanel Name="Output" VerticalExpand="True" StyleClasses="monospace">
|
||||
<OutputPanel.StyleBoxOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#25252add"
|
||||
ContentMarginLeftOverride="3" ContentMarginRightOverride="3"
|
||||
|
||||
@@ -90,7 +90,7 @@ public sealed partial class DebugConsole
|
||||
|
||||
private async void TypeUpdateCompletions(bool fullUpdate)
|
||||
{
|
||||
var (args, _, _) = CalcTypingArgs();
|
||||
var (args, _, _, str) = CalcTypingArgs();
|
||||
|
||||
if (args.Count != _compParamCount)
|
||||
{
|
||||
@@ -101,7 +101,7 @@ public sealed partial class DebugConsole
|
||||
if (fullUpdate)
|
||||
{
|
||||
var seq = ++_compSeqSend;
|
||||
var task = _consoleHost.GetCompletions(args, _compCancel.Token);
|
||||
var task = _consoleHost.GetCompletions(args, str, _compCancel.Token);
|
||||
|
||||
if (!task.IsCompleted)
|
||||
{
|
||||
@@ -140,7 +140,7 @@ public sealed partial class DebugConsole
|
||||
if (_compCurResult == null)
|
||||
return;
|
||||
|
||||
var (_, curTyping, _) = CalcTypingArgs();
|
||||
var (_, curTyping, _, _) = CalcTypingArgs();
|
||||
|
||||
var curSelected = _compFiltered?.Length > 0 ? _compFiltered[_compSelected] : default;
|
||||
_compFiltered = FilterCompletions(_compCurResult.Options, curTyping);
|
||||
@@ -168,7 +168,7 @@ public sealed partial class DebugConsole
|
||||
|
||||
DebugTools.AssertNotNull(_compCurResult);
|
||||
|
||||
var (_, _, endRange) = CalcTypingArgs();
|
||||
var (_, _, endRange, _) = CalcTypingArgs();
|
||||
|
||||
var offset = CommandBar.GetOffsetAtIndex(endRange.start);
|
||||
// Logger.Debug($"Offset: {offset}");
|
||||
@@ -231,7 +231,7 @@ public sealed partial class DebugConsole
|
||||
}
|
||||
}
|
||||
|
||||
private (List<string> args, string curTyping, (int start, int end) lastRange) CalcTypingArgs()
|
||||
private (List<string> args, string curTyping, (int start, int end) lastRange, string argStr) CalcTypingArgs()
|
||||
{
|
||||
var cursor = CommandBar.CursorPosition;
|
||||
// Don't consider text after the cursor.
|
||||
@@ -252,7 +252,7 @@ public sealed partial class DebugConsole
|
||||
else
|
||||
lastRange = (cursor, cursor);
|
||||
|
||||
return (args, args[^1], lastRange);
|
||||
return (args, args[^1], lastRange, text.ToString());
|
||||
}
|
||||
|
||||
private CompletionOption[] FilterCompletions(IEnumerable<CompletionOption> completions, string curTyping)
|
||||
@@ -270,7 +270,7 @@ public sealed partial class DebugConsole
|
||||
{
|
||||
// Figure out typing word so we know how much to replace.
|
||||
var (completion, _, completionFlags) = _compFiltered[_compSelected];
|
||||
var (_, _, lastRange) = CalcTypingArgs();
|
||||
var (_, _, lastRange, _) = CalcTypingArgs();
|
||||
|
||||
// Replace the full word from the start.
|
||||
// This means that letter casing will match the completion suggestion.
|
||||
|
||||
@@ -23,9 +23,9 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
/// <summary>
|
||||
/// Write a line with a specific color to the console window.
|
||||
/// </summary>
|
||||
void AddLine(string text, Color color);
|
||||
void AddLine(FormattedMessage text, Color color);
|
||||
|
||||
void AddLine(string text);
|
||||
void AddLine(FormattedMessage text);
|
||||
|
||||
void AddFormattedLine(FormattedMessage message);
|
||||
|
||||
@@ -108,7 +108,7 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
|
||||
private Color DetermineColor(bool local, bool error)
|
||||
{
|
||||
return Color.White;
|
||||
return error ? Color.Red : Color.White;
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
@@ -136,16 +136,16 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
_flushHistoryToDisk();
|
||||
}
|
||||
|
||||
public void AddLine(string text, Color color)
|
||||
public void AddLine(FormattedMessage text, Color color)
|
||||
{
|
||||
var formatted = new FormattedMessage(3);
|
||||
formatted.PushColor(color);
|
||||
formatted.AddText(text);
|
||||
formatted.AddMessage(text);
|
||||
formatted.Pop();
|
||||
AddFormattedLine(formatted);
|
||||
}
|
||||
|
||||
public void AddLine(string text)
|
||||
public void AddLine(FormattedMessage text)
|
||||
{
|
||||
AddLine(text, Color.White);
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace Robust.Client.UserInterface.CustomControls.DebugMonitorControls
|
||||
EntityCoordinates mouseGridPos;
|
||||
TileRef tile;
|
||||
|
||||
var mouseWorldMap = _eyeManager.ScreenToMap(mouseScreenPos);
|
||||
var mouseWorldMap = _eyeManager.PixelToMap(mouseScreenPos);
|
||||
if (mouseWorldMap == MapCoordinates.Nullspace)
|
||||
return;
|
||||
|
||||
|
||||
@@ -24,6 +24,11 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
/// </param>
|
||||
MapCoordinates ScreenToMap(Vector2 coords);
|
||||
|
||||
/// <summary>
|
||||
/// Similar to <see cref="ScreenToMap(Vector2)"/>, except it should compensate for the effects of shaders on viewports.
|
||||
/// </summary>
|
||||
MapCoordinates PixelToMap(Vector2 point);
|
||||
|
||||
/// <summary>
|
||||
/// Converts a point on the map to screen coordinates.
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Robust.Client.UserInterface.CustomControls;
|
||||
|
||||
/// <summary>
|
||||
/// An event used to reverse distortion effects applied by shaders.
|
||||
/// Used to find the map position that visible pixels originate from so that severe distortion shaders do not make interaction nigh-impossible.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct PixelToMapEvent(Vector2 LocalPosition, IViewportControl Control, IClydeViewport Viewport)
|
||||
{
|
||||
/// <summary>
|
||||
/// The local position of the pixel within the <see cref="Control"/> that we are trying to convert to a map position.
|
||||
/// </summary>
|
||||
public readonly Vector2 LocalPosition = LocalPosition;
|
||||
|
||||
/// <summary>
|
||||
/// The original (or WIP) location of the pixel within the <see cref="Control"/> that we are trying to convert to a map position.
|
||||
/// Used as the output of the event.
|
||||
/// </summary>
|
||||
public Vector2 VisiblePosition = LocalPosition;
|
||||
|
||||
/// <summary>
|
||||
/// The control the pixel we are considering is located within.
|
||||
/// </summary>
|
||||
public readonly IViewportControl Control = Control;
|
||||
|
||||
/// <summary>
|
||||
/// The viewport being displayed by the control we are considering.
|
||||
/// </summary>
|
||||
public readonly IClydeViewport Viewport = Viewport;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Numerics;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
@@ -13,8 +14,9 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
[Virtual]
|
||||
public class ViewportContainer : Control, IViewportControl
|
||||
{
|
||||
private readonly IClyde _displayManager;
|
||||
private readonly IInputManager _inputManager;
|
||||
[Dependency] private readonly IClyde _displayManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
|
||||
public IClydeViewport? Viewport { get; set; }
|
||||
|
||||
@@ -36,8 +38,7 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
|
||||
public ViewportContainer()
|
||||
{
|
||||
_displayManager = IoCManager.Resolve<IClyde>();
|
||||
_inputManager = IoCManager.Resolve<IInputManager>();
|
||||
IoCManager.InjectDependencies(this);
|
||||
MouseFilter = MouseFilterMode.Stop;
|
||||
Resized();
|
||||
}
|
||||
@@ -99,8 +100,7 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
// -- Handlers: In --
|
||||
|
||||
// -- Utils / S2M-M2S Base --
|
||||
|
||||
public MapCoordinates LocalPixelToMap(Vector2 point)
|
||||
public MapCoordinates LocalCoordsToMap(Vector2 point)
|
||||
{
|
||||
if (Viewport == null)
|
||||
return default;
|
||||
@@ -111,6 +111,19 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
return Viewport.LocalToWorld(point);
|
||||
}
|
||||
|
||||
public MapCoordinates LocalPixelToMap(Vector2 point)
|
||||
{
|
||||
if (Viewport == null)
|
||||
return default;
|
||||
|
||||
// pre-scaler
|
||||
point *= _viewportResolution;
|
||||
var ev = new PixelToMapEvent(point, this, Viewport);
|
||||
_entityManager.EventBus.RaiseEvent(EventSource.Local, ref ev);
|
||||
|
||||
return Viewport.LocalToWorld(ev.VisiblePosition);
|
||||
}
|
||||
|
||||
public Vector2 WorldToLocalPixel(Vector2 point)
|
||||
{
|
||||
if (Viewport?.Eye == null)
|
||||
@@ -127,6 +140,12 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
// -- Utils / S2M-M2S Extended --
|
||||
|
||||
public MapCoordinates ScreenToMap(Vector2 point)
|
||||
{
|
||||
return LocalCoordsToMap(point - GlobalPixelPosition);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public MapCoordinates PixelToMap(Vector2 point)
|
||||
{
|
||||
return LocalPixelToMap(point - GlobalPixelPosition);
|
||||
}
|
||||
|
||||
29
Robust.Client/UserInterface/RichText/BoldItalicTag.cs
Normal file
29
Robust.Client/UserInterface/RichText/BoldItalicTag.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.UserInterface.RichText;
|
||||
|
||||
public sealed class BoldItalicTag : IMarkupTag
|
||||
{
|
||||
public const string BoldItalicFont = "DefaultBoldItalic";
|
||||
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
public string Name => "bolditalic";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void PushDrawContext(MarkupNode node, MarkupDrawingContext context)
|
||||
{
|
||||
var font = FontTag.CreateFont(context.Font, node, _resourceCache, _prototypeManager, BoldItalicFont);
|
||||
context.Font.Push(font);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void PopDrawContext(MarkupNode node, MarkupDrawingContext context)
|
||||
{
|
||||
context.Font.Pop();
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Linq;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -14,12 +15,18 @@ public sealed class BoldTag : IMarkupTag
|
||||
|
||||
public string Name => "bold";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void PushDrawContext(MarkupNode node, MarkupDrawingContext context)
|
||||
{
|
||||
var font = FontTag.CreateFont(context.Font, node, _resourceCache, _prototypeManager, BoldFont);
|
||||
var font = FontTag.CreateFont(context.Font, node, _resourceCache, _prototypeManager,
|
||||
context.Tags.Any(static x => x is ItalicTag)
|
||||
? BoldItalicTag.BoldItalicFont
|
||||
: BoldFont
|
||||
);
|
||||
context.Font.Push(font);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void PopDrawContext(MarkupNode node, MarkupDrawingContext context)
|
||||
{
|
||||
context.Font.Pop();
|
||||
|
||||
11
Robust.Client/UserInterface/RichText/BulletTag.cs
Normal file
11
Robust.Client/UserInterface/RichText/BulletTag.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.UserInterface.RichText;
|
||||
|
||||
public sealed class BulletTag : IMarkupTag
|
||||
{
|
||||
public string Name => "bullet";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string TextBefore(MarkupNode _) => " · ";
|
||||
}
|
||||
@@ -8,7 +8,7 @@ namespace Robust.Client.UserInterface.RichText;
|
||||
/// </summary>
|
||||
public sealed class ColorTag : IMarkupTag
|
||||
{
|
||||
private static readonly Color DefaultColor = new(200, 200, 200);
|
||||
public static readonly Color DefaultColor = new(200, 200, 200);
|
||||
|
||||
public string Name => "color";
|
||||
|
||||
|
||||
@@ -14,12 +14,14 @@ namespace Robust.Client.UserInterface.RichText;
|
||||
public sealed class FontTag : IMarkupTag
|
||||
{
|
||||
public const string DefaultFont = "Default";
|
||||
public const int DefaultSize = 12;
|
||||
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
public string Name => "font";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void PushDrawContext(MarkupNode node, MarkupDrawingContext context)
|
||||
{
|
||||
string fontId = node.Value.StringValue ?? DefaultFont;
|
||||
@@ -28,6 +30,7 @@ public sealed class FontTag : IMarkupTag
|
||||
context.Font.Push(font);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void PopDrawContext(MarkupNode node, MarkupDrawingContext context)
|
||||
{
|
||||
context.Font.Pop();
|
||||
@@ -44,7 +47,7 @@ public sealed class FontTag : IMarkupTag
|
||||
IPrototypeManager prototypeManager,
|
||||
string fontId)
|
||||
{
|
||||
var size = 12;
|
||||
var size = DefaultSize;
|
||||
|
||||
if (contextFontStack.TryPeek(out var previousFont))
|
||||
{
|
||||
|
||||
36
Robust.Client/UserInterface/RichText/HeadingTag.cs
Normal file
36
Robust.Client/UserInterface/RichText/HeadingTag.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.UserInterface.RichText;
|
||||
|
||||
public sealed class HeadingTag : IMarkupTag
|
||||
{
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
public string Name => "head";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void PushDrawContext(MarkupNode node, MarkupDrawingContext context)
|
||||
{
|
||||
if (!node.Value.TryGetLong(out var levelParam))
|
||||
return;
|
||||
|
||||
var level = Math.Min(Math.Max((int)levelParam, 1), 3);
|
||||
node.Attributes["size"] = new MarkupParameter(
|
||||
(int)Math.Ceiling(FontTag.DefaultSize * 2 / Math.Sqrt(level))
|
||||
);
|
||||
|
||||
var font = FontTag.CreateFont(context.Font, node, _resourceCache, _prototypeManager, BoldTag.BoldFont);
|
||||
context.Font.Push(font);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void PopDrawContext(MarkupNode node, MarkupDrawingContext context)
|
||||
{
|
||||
context.Font.Pop();
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Linq;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -11,14 +12,21 @@ public sealed class ItalicTag : IMarkupTag
|
||||
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
public string Name => "italic";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void PushDrawContext(MarkupNode node, MarkupDrawingContext context)
|
||||
{
|
||||
var font = FontTag.CreateFont(context.Font, node, _resourceCache, _prototypeManager, ItalicFont);
|
||||
var font = FontTag.CreateFont(context.Font, node, _resourceCache, _prototypeManager,
|
||||
context.Tags.Any(static x => x is BoldTag)
|
||||
? BoldItalicTag.BoldItalicFont
|
||||
: ItalicFont
|
||||
);
|
||||
context.Font.Push(font);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void PopDrawContext(MarkupNode node, MarkupDrawingContext context)
|
||||
{
|
||||
context.Font.Pop();
|
||||
|
||||
@@ -8,22 +8,26 @@ public sealed class MarkupDrawingContext
|
||||
{
|
||||
public readonly Stack<Color> Color;
|
||||
public readonly Stack<Font> Font;
|
||||
public readonly List<IMarkupTag> Tags;
|
||||
|
||||
public MarkupDrawingContext()
|
||||
{
|
||||
Color = new Stack<Color>();
|
||||
Font = new Stack<Font>();
|
||||
Tags = new List<IMarkupTag>();
|
||||
}
|
||||
|
||||
public MarkupDrawingContext(int capacity)
|
||||
{
|
||||
Color = new Stack<Color>(capacity);
|
||||
Font = new Stack<Font>(capacity);
|
||||
Tags = new List<IMarkupTag>();
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
Color.Clear();
|
||||
Font.Clear();
|
||||
Tags.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Sandboxing;
|
||||
@@ -15,24 +16,29 @@ public sealed class MarkupTagManager
|
||||
/// <summary>
|
||||
/// Tags defined in engine need to be instantiated here because of sandboxing
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, IMarkupTag> _markupTagTypes = new()
|
||||
{
|
||||
{"color", new ColorTag()},
|
||||
{"cmdlink", new CommandLinkTag()},
|
||||
{"font", new FontTag()},
|
||||
{"bold", new BoldTag()},
|
||||
{"italic", new ItalicTag()}
|
||||
};
|
||||
private readonly Dictionary<string, IMarkupTag> _markupTagTypes = new IMarkupTag[] {
|
||||
new BoldItalicTag(),
|
||||
new BoldTag(),
|
||||
new BulletTag(),
|
||||
new ColorTag(),
|
||||
new CommandLinkTag(),
|
||||
new FontTag(),
|
||||
new HeadingTag(),
|
||||
new ItalicTag()
|
||||
}.ToDictionary(x => x.Name.ToLower(), x => x);
|
||||
|
||||
/// <summary>
|
||||
/// A list of <see cref="IMarkupTag"/> types that shouldn't be instantiated through reflection
|
||||
/// </summary>
|
||||
private readonly List<Type> _engineTypes = new()
|
||||
{
|
||||
typeof(BoldItalicTag),
|
||||
typeof(BoldTag),
|
||||
typeof(BulletTag),
|
||||
typeof(ColorTag),
|
||||
typeof(CommandLinkTag),
|
||||
typeof(FontTag),
|
||||
typeof(BoldTag),
|
||||
typeof(HeadingTag),
|
||||
typeof(ItalicTag)
|
||||
};
|
||||
|
||||
@@ -59,9 +65,11 @@ public sealed class MarkupTagManager
|
||||
return _markupTagTypes.GetValueOrDefault(name);
|
||||
}
|
||||
|
||||
public bool TryGetMarkupTag(string name, [NotNullWhen(true)] out IMarkupTag? tag)
|
||||
public bool TryGetMarkupTag(string name, Type[]? tagsAllowed, [NotNullWhen(true)] out IMarkupTag? tag)
|
||||
{
|
||||
if (_markupTagTypes.TryGetValue(name, out var markupTag))
|
||||
if (_markupTagTypes.TryGetValue(name, out var markupTag)
|
||||
// Using a whitelist prevents new tags from sneaking in.
|
||||
&& (tagsAllowed == null || Array.IndexOf(tagsAllowed, markupTag.GetType()) != -1))
|
||||
{
|
||||
tag = markupTag;
|
||||
return true;
|
||||
|
||||
@@ -16,9 +16,9 @@ namespace Robust.Client.UserInterface
|
||||
/// </summary>
|
||||
internal struct RichTextEntry
|
||||
{
|
||||
private static readonly Color DefaultColor = new(200, 200, 200);
|
||||
|
||||
private readonly Color _defaultColor;
|
||||
private readonly MarkupTagManager _tagManager;
|
||||
private readonly Type[]? _tagsAllowed;
|
||||
|
||||
public readonly FormattedMessage Message;
|
||||
|
||||
@@ -39,13 +39,15 @@ namespace Robust.Client.UserInterface
|
||||
|
||||
private readonly Dictionary<int, Control> _tagControls = new();
|
||||
|
||||
public RichTextEntry(FormattedMessage message, Control parent, MarkupTagManager tagManager)
|
||||
public RichTextEntry(FormattedMessage message, Control parent, MarkupTagManager tagManager, Type[]? tagsAllowed = null, Color? defaultColor = null)
|
||||
{
|
||||
Message = message;
|
||||
Height = 0;
|
||||
Width = 0;
|
||||
LineBreaks = default;
|
||||
_defaultColor = defaultColor ?? new(200, 200, 200);
|
||||
_tagManager = tagManager;
|
||||
_tagsAllowed = tagsAllowed;
|
||||
|
||||
var nodeIndex = -1;
|
||||
foreach (var node in Message.Nodes)
|
||||
@@ -55,7 +57,7 @@ namespace Robust.Client.UserInterface
|
||||
if (node.Name == null)
|
||||
continue;
|
||||
|
||||
if (!_tagManager.TryGetMarkupTag(node.Name, out var tag) || !tag.TryGetControl(node, out var control))
|
||||
if (!_tagManager.TryGetMarkupTag(node.Name, _tagsAllowed, out var tag) || !tag.TryGetControl(node, out var control))
|
||||
continue;
|
||||
|
||||
parent.Children.Add(control);
|
||||
@@ -82,7 +84,7 @@ namespace Robust.Client.UserInterface
|
||||
var wordWrap = new WordWrap(maxSizeX);
|
||||
var context = new MarkupDrawingContext();
|
||||
context.Font.Push(defaultFont);
|
||||
context.Color.Push(DefaultColor);
|
||||
context.Color.Push(_defaultColor);
|
||||
|
||||
// Go over every node.
|
||||
// Nodes can change the markup drawing context and return additional text.
|
||||
@@ -171,7 +173,7 @@ namespace Robust.Client.UserInterface
|
||||
float uiScale)
|
||||
{
|
||||
context.Clear();
|
||||
context.Color.Push(DefaultColor);
|
||||
context.Color.Push(_defaultColor);
|
||||
context.Font.Push(defaultFont);
|
||||
|
||||
var globalBreakCounter = 0;
|
||||
@@ -186,7 +188,7 @@ namespace Robust.Client.UserInterface
|
||||
var text = ProcessNode(node, context);
|
||||
if (!context.Color.TryPeek(out var color) || !context.Font.TryPeek(out var font))
|
||||
{
|
||||
color = DefaultColor;
|
||||
color = _defaultColor;
|
||||
font = defaultFont;
|
||||
}
|
||||
|
||||
@@ -226,15 +228,17 @@ namespace Robust.Client.UserInterface
|
||||
return node.Value.StringValue ?? "";
|
||||
|
||||
//Skip the node if there is no markup tag for it.
|
||||
if (!_tagManager.TryGetMarkupTag(node.Name, out var tag))
|
||||
if (!_tagManager.TryGetMarkupTag(node.Name, _tagsAllowed, out var tag))
|
||||
return "";
|
||||
|
||||
if (!node.Closing)
|
||||
{
|
||||
context.Tags.Add(tag);
|
||||
tag.PushDrawContext(node, context);
|
||||
return tag.TextBefore(node);
|
||||
}
|
||||
|
||||
context.Tags.Remove(tag);
|
||||
tag.PopDrawContext(node, context);
|
||||
return tag.TextAfter(node);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,9 @@ public sealed class DefaultStylesheet
|
||||
{
|
||||
var notoSansFont = res.GetResource<FontResource>("/EngineFonts/NotoSans/NotoSans-Regular.ttf");
|
||||
var notoSansFont12 = new VectorFont(notoSansFont, 12);
|
||||
var notoSansMonoFont = res.GetResource<FontResource>("/EngineFonts/NotoSans/NotoSansMono-Regular.ttf");
|
||||
var notoSansMono12 = new VectorFont(notoSansMonoFont, 12);
|
||||
|
||||
|
||||
var theme = userInterfaceManager.CurrentTheme;
|
||||
|
||||
@@ -38,6 +41,13 @@ public sealed class DefaultStylesheet
|
||||
|
||||
Stylesheet = new Stylesheet(new StyleRule[]
|
||||
{
|
||||
/*
|
||||
* Debug console and other monospace things.
|
||||
*/
|
||||
|
||||
Element().Class("monospace")
|
||||
.Prop("font", notoSansMono12),
|
||||
|
||||
/*
|
||||
* OS Window defaults
|
||||
*/
|
||||
|
||||
@@ -29,7 +29,7 @@ internal partial class UserInterfaceManager
|
||||
public void SetActiveTheme(string themeName)
|
||||
{
|
||||
if (!_themes.TryGetValue(themeName, out var theme) || (theme == CurrentTheme)) return;
|
||||
CurrentTheme = theme;
|
||||
UpdateTheme(theme);
|
||||
}
|
||||
|
||||
public void SetDefaultTheme(string themeId)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using DiscordRPC;
|
||||
using DiscordRPC.Logging;
|
||||
using Robust.Shared;
|
||||
@@ -25,13 +27,17 @@ namespace Robust.Client.Utility
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
var state = _loc.GetString("discord-rpc-in-main-menu");
|
||||
var largeImageKey = _configurationManager.GetCVar(CVars.DiscordRichPresenceSecondIconId);
|
||||
var largeImageText = _loc.GetString("discord-rpc-in-main-menu-logo-text");
|
||||
|
||||
_defaultPresence = new()
|
||||
{
|
||||
State = _loc.GetString("discord-rpc-in-main-menu"),
|
||||
State = Truncate(state, 128),
|
||||
Assets = new Assets
|
||||
{
|
||||
LargeImageKey = "logo",
|
||||
LargeImageText = "I think coolsville SUCKS"
|
||||
LargeImageKey = Truncate(largeImageKey, 32),
|
||||
LargeImageText = Truncate(largeImageText, 128),
|
||||
}
|
||||
};
|
||||
_configurationManager.OnValueChanged(CVars.DiscordEnabled, newValue =>
|
||||
@@ -95,18 +101,60 @@ namespace Robust.Client.Utility
|
||||
|
||||
public void Update(string serverName, string username, string maxUsers, string users)
|
||||
{
|
||||
_activePresence = new RichPresence
|
||||
if (_client == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
Details = _loc.GetString("discord-rpc-on-server", ("servername", serverName)),
|
||||
State = _loc.GetString("discord-rpc-players", ("players", users), ("maxplayers", maxUsers)),
|
||||
Assets = new Assets
|
||||
var details = _loc.GetString("discord-rpc-on-server", ("servername", serverName));
|
||||
var state = _loc.GetString("discord-rpc-players", ("players", users), ("maxplayers", maxUsers));
|
||||
var largeImageText = _loc.GetString("discord-rpc-character", ("username", username));
|
||||
var largeImageKey = _configurationManager.GetCVar(CVars.DiscordRichPresenceMainIconId);
|
||||
var smallImageKey = _configurationManager.GetCVar(CVars.DiscordRichPresenceSecondIconId);
|
||||
|
||||
// Strings are limited by byte count. See the setters in RichPresence. Hence the truncate calls.
|
||||
_activePresence = new RichPresence
|
||||
{
|
||||
LargeImageKey = "devstation",
|
||||
LargeImageText = _loc.GetString("discord-rpc-character", ("username", username)),
|
||||
SmallImageKey = "logo"
|
||||
}
|
||||
};
|
||||
_client?.SetPresence(_activePresence);
|
||||
Details = Truncate(details, 128),
|
||||
State = Truncate(state, 128),
|
||||
Assets = new Assets
|
||||
{
|
||||
LargeImageKey = Truncate(largeImageKey, 32),
|
||||
LargeImageText = Truncate(largeImageText, 128),
|
||||
SmallImageKey = Truncate(smallImageKey, 32)
|
||||
}
|
||||
};
|
||||
_client.SetPresence(_activePresence);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_client.Logger.Error($"Caught exception while updating discord rich presence. Exception:\n{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private string Truncate(string value, int bytes, string postfix = "...")
|
||||
=> Truncate(value, bytes, postfix, Encoding.UTF8);
|
||||
|
||||
/// <summary>
|
||||
/// Truncate strings down to some minimum byte count. If the string gets truncated, it will have the postfix appended.
|
||||
/// </summary>
|
||||
private string Truncate(string value, int bytes, string postfix, Encoding encoding)
|
||||
{
|
||||
value = value.Trim();
|
||||
var output = value;
|
||||
|
||||
// Theres probably a better way of doing this, but I don't know how.
|
||||
// If this wasn't a crude hack this function should
|
||||
while (encoding.GetByteCount(output) > bytes)
|
||||
{
|
||||
if (value.Length == 0)
|
||||
return string.Empty;
|
||||
|
||||
value = value.Substring(0, value.Length - 1);
|
||||
output = value + postfix;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public void ClearPresence()
|
||||
|
||||
@@ -88,7 +88,7 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
var stringified = PrettyPrint.PrintUserFacingWithType(obj, out var typeStringified);
|
||||
if (typeStringified != "")
|
||||
{
|
||||
//var smallFont = new VectorFont(_resourceCache.GetResource<FontResource>("/Fonts/CALIBRI.TTF"), 10);
|
||||
//var smallFont = new VectorFont(_resourceCache.GetResource<FontResource>("/EngineFonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
// Custom ToString() implementation.
|
||||
var headBox = new BoxContainer
|
||||
{
|
||||
|
||||
@@ -121,7 +121,7 @@ namespace Robust.Client.ViewVariables
|
||||
}
|
||||
|
||||
//var smallFont =
|
||||
// new VectorFont(IoCManager.Resolve<IResourceCache>().GetResource<FontResource>("/Fonts/CALIBRI.TTF"),
|
||||
// new VectorFont(IoCManager.Resolve<IResourceCache>().GetResource<FontResource>("/EngineFonts/NotoSans/NotoSans-Regular.ttf"),
|
||||
// 10);
|
||||
|
||||
// Custom ToString() implementation.
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace Robust.Client.ViewVariables
|
||||
};
|
||||
VBox.AddChild(BottomContainer);
|
||||
|
||||
//var smallFont = new VectorFont(_resourceCache.GetResource<FontResource>("/Fonts/CALIBRI.TTF"), 10);
|
||||
//var smallFont = new VectorFont(_resourceCache.GetResource<FontResource>("/EngineFonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
|
||||
_bottomLabel = new Label
|
||||
{
|
||||
|
||||
@@ -30,7 +30,9 @@ using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Profiling;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Replays;
|
||||
using Robust.Shared.Toolshed;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Threading;
|
||||
@@ -101,6 +103,7 @@ namespace Robust.Server
|
||||
[Dependency] private readonly IReplayRecordingManagerInternal _replay = default!;
|
||||
[Dependency] private readonly IGamePrototypeLoadManager _protoLoadMan = default!;
|
||||
[Dependency] private readonly NetworkResourceManager _netResMan = default!;
|
||||
[Dependency] private readonly IReflectionManager _refMan = default!;
|
||||
|
||||
private readonly Stopwatch _uptimeStopwatch = new();
|
||||
|
||||
@@ -367,7 +370,9 @@ namespace Robust.Server
|
||||
_prototype.Initialize();
|
||||
_prototype.LoadDefaultPrototypes();
|
||||
_prototype.ResolveResults();
|
||||
_refMan.Initialize();
|
||||
|
||||
IoCManager.Resolve<ToolshedManager>().Initialize();
|
||||
_consoleHost.Initialize();
|
||||
_entityManager.Startup();
|
||||
_mapManager.Startup();
|
||||
|
||||
@@ -1,162 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Pidgin;
|
||||
using Robust.Shared.GameObjects;
|
||||
using static Pidgin.Parser;
|
||||
using static Pidgin.Parser<char>;
|
||||
|
||||
namespace Robust.Server.Bql
|
||||
{
|
||||
public partial class BqlQueryManager
|
||||
{
|
||||
private readonly Dictionary<Type, Parser<char, BqlQuerySelectorParsed>> _parsers = new();
|
||||
private Parser<char, BqlQuerySelectorParsed> _allQuerySelectors = default!;
|
||||
private Parser<char, (IEnumerable<BqlQuerySelectorParsed>, string)> SimpleQuery => Parser.Map((en, _, rest) => (en, rest), SkipWhitespaces.Then(_allQuerySelectors).Many(), String("do").Then(SkipWhitespaces), Any.ManyString());
|
||||
|
||||
private static Parser<char, string> Word => OneOf(LetterOrDigit, Char('_')).ManyString();
|
||||
|
||||
private static Parser<char, object> Objectify<T>(Parser<char, T> inp)
|
||||
{
|
||||
return Parser.Map(x => (object) x!, inp);
|
||||
}
|
||||
|
||||
private struct SubstitutionData
|
||||
{
|
||||
public string Name;
|
||||
|
||||
public SubstitutionData(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
|
||||
private static Parser<char, SubstitutionData> Substitution =>
|
||||
Try(Char('$').Then(OneOf(Uppercase, Char('_')).ManyString()))
|
||||
.MapWithInput((x, _) => new SubstitutionData(x.ToString()));
|
||||
|
||||
private static Parser<char, int> Integer =>
|
||||
Try(Int(10));
|
||||
|
||||
private static Parser<char, object> SubstitutableInteger =>
|
||||
Objectify(Integer).Or(Objectify(Try(Substitution)));
|
||||
|
||||
private static Parser<char, double> Float =>
|
||||
Try(Real);
|
||||
|
||||
private static Parser<char, object> SubstitutableFloat =>
|
||||
Objectify(Float).Or(Objectify(Try(Substitution)));
|
||||
|
||||
private static Parser<char, double> Percentage =>
|
||||
Try(Real).Before(Char('%'));
|
||||
|
||||
private static Parser<char, object> SubstitutablePercentage =>
|
||||
Objectify(Percentage).Or(Objectify(Try(Substitution)));
|
||||
|
||||
private static Parser<char, EntityUid> EntityId =>
|
||||
Try(Parser.Map(x => new EntityUid(x), Int(10)));
|
||||
|
||||
private static Parser<char, object> SubstitutableEntityId =>
|
||||
Objectify(EntityId).Or(Objectify(Try(Substitution)));
|
||||
|
||||
private Parser<char, Type> Component =>
|
||||
Try(Parser.Map(t => _componentFactory.GetRegistration(t).Type, Word));
|
||||
|
||||
private Parser<char, object> SubstitutableComponent =>
|
||||
Objectify(Component).Or(Objectify(Try(Substitution)));
|
||||
|
||||
private static Parser<char, string> QuotedString =>
|
||||
OneOf(Try(Char('"').Then(OneOf(new []
|
||||
{
|
||||
AnyCharExcept("\"")
|
||||
}).ManyString().Before(Char('"')))), Try(Word));
|
||||
|
||||
private static Parser<char, object> SubstitutableString =>
|
||||
Objectify(QuotedString).Or(Objectify(Try(Substitution)));
|
||||
|
||||
// thing to make sure it all compiles.
|
||||
[UsedImplicitly]
|
||||
private Parser<char, object> TypeSystemCheck =>
|
||||
OneOf(new[]
|
||||
{
|
||||
Objectify(Integer),
|
||||
Objectify(Percentage),
|
||||
Objectify(EntityId),
|
||||
Objectify(Component),
|
||||
Objectify(Float),
|
||||
Objectify(QuotedString)
|
||||
});
|
||||
|
||||
private Parser<char, BqlQuerySelectorParsed> BuildBqlQueryParser(BqlQuerySelector inst)
|
||||
{
|
||||
if (inst.Arguments.Length == 0)
|
||||
{
|
||||
return Parser.Map(_ => new BqlQuerySelectorParsed(new List<object>(), inst.Token, false), SkipWhitespaces);
|
||||
}
|
||||
|
||||
List<Parser<char, object>> argsParsers = new();
|
||||
|
||||
foreach (var (arg, _) in inst.Arguments.Select((x, i) => (x, i)))
|
||||
{
|
||||
List<Parser<char, object>> choices = new();
|
||||
if ((arg & QuerySelectorArgument.String) == QuerySelectorArgument.String)
|
||||
{
|
||||
choices.Add(Try(SubstitutableString.Before(SkipWhitespaces).Labelled("string argument")));
|
||||
}
|
||||
if ((arg & QuerySelectorArgument.Component) == QuerySelectorArgument.Component)
|
||||
{
|
||||
choices.Add(Try(SubstitutableComponent.Before(SkipWhitespaces).Labelled("component argument")));
|
||||
}
|
||||
if ((arg & QuerySelectorArgument.EntityId) == QuerySelectorArgument.EntityId)
|
||||
{
|
||||
choices.Add(Try(SubstitutableEntityId.Before(SkipWhitespaces).Labelled("entity ID argument")));
|
||||
}
|
||||
if ((arg & QuerySelectorArgument.Integer) == QuerySelectorArgument.Integer)
|
||||
{
|
||||
choices.Add(Try(SubstitutableInteger.Before(SkipWhitespaces).Labelled("integer argument")));
|
||||
}
|
||||
if ((arg & QuerySelectorArgument.Percentage) == QuerySelectorArgument.Percentage)
|
||||
{
|
||||
choices.Add(Try(SubstitutablePercentage.Before(SkipWhitespaces).Labelled("percentage argument")));
|
||||
}
|
||||
if ((arg & QuerySelectorArgument.Float) == QuerySelectorArgument.Float)
|
||||
{
|
||||
choices.Add(Try(SubstitutableFloat.Before(SkipWhitespaces).Labelled("float argument")));
|
||||
}
|
||||
|
||||
argsParsers.Add(OneOf(choices));
|
||||
}
|
||||
|
||||
Parser<char, List<object>> finalParser = argsParsers[0].Map(x => new List<object> { x });
|
||||
|
||||
for (var i = 1; i < argsParsers.Count; i++)
|
||||
{
|
||||
finalParser = finalParser.Then(argsParsers[i], (list, o) =>
|
||||
{
|
||||
list.Add(o);
|
||||
return list;
|
||||
}).Labelled("arguments");
|
||||
}
|
||||
|
||||
return Parser.Map(args => new BqlQuerySelectorParsed(args, inst.Token, false), finalParser);
|
||||
}
|
||||
|
||||
private void DoParserSetup()
|
||||
{
|
||||
foreach (var inst in _instances)
|
||||
{
|
||||
_parsers.Add(inst.GetType(), BuildBqlQueryParser(inst));
|
||||
}
|
||||
|
||||
_allQuerySelectors = Parser.Map((a,b) => (a,b), Try(String("not").Before(Char(' '))).Optional(), OneOf(_instances.Select(x =>
|
||||
Try(String(x.Token).Before(Char(' '))))).Then(tok =>
|
||||
_parsers[_queriesByToken[tok].GetType()])
|
||||
).Map(pair =>
|
||||
{
|
||||
pair.b.Inverted = pair.a.HasValue;
|
||||
return pair.b;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Pidgin;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Server.Bql
|
||||
{
|
||||
public partial class BqlQueryManager
|
||||
{
|
||||
public (IEnumerable<EntityUid>, string) SimpleParseAndExecute(string query)
|
||||
{
|
||||
var parsed = SimpleQuery.Parse(query);
|
||||
if (parsed.Success)
|
||||
{
|
||||
var selectors = parsed.Value.Item1.ToArray();
|
||||
if (selectors.Length == 0)
|
||||
{
|
||||
return (_entityManager.GetEntities(), parsed.Value.Item2);
|
||||
}
|
||||
|
||||
var entities = _queriesByToken[selectors[0].Token]
|
||||
.DoInitialSelection(selectors[0].Arguments, selectors[0].Inverted, _entityManager);
|
||||
|
||||
foreach (var sel in selectors[1..])
|
||||
{
|
||||
entities = _queriesByToken[sel.Token].DoSelection(entities, sel.Arguments, sel.Inverted, _entityManager);
|
||||
}
|
||||
|
||||
return (entities, parsed.Value.Item2);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception(parsed.Error!.RenderErrorMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Server.Bql
|
||||
{
|
||||
public sealed partial class BqlQueryManager : IBqlQueryManager
|
||||
{
|
||||
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
||||
[Dependency] private readonly IComponentFactory _componentFactory = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
private readonly List<BqlQuerySelector> _instances = new();
|
||||
private readonly Dictionary<string, BqlQuerySelector> _queriesByToken = new();
|
||||
|
||||
/// <summary>
|
||||
/// Automatically registers all query selectors with the parser/executor.
|
||||
/// </summary>
|
||||
public void DoAutoRegistrations()
|
||||
{
|
||||
foreach (var type in _reflectionManager.FindTypesWithAttribute<RegisterBqlQuerySelectorAttribute>())
|
||||
{
|
||||
RegisterClass(type);
|
||||
}
|
||||
|
||||
DoParserSetup();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internally registers the given <see cref="BqlQuerySelector"/>.
|
||||
/// </summary>
|
||||
/// <param name="bqlQuerySelector">The selector to register</param>
|
||||
private void RegisterClass(Type bqlQuerySelector)
|
||||
{
|
||||
DebugTools.Assert(bqlQuerySelector.BaseType == typeof(BqlQuerySelector));
|
||||
var inst = (BqlQuerySelector)Activator.CreateInstance(bqlQuerySelector)!;
|
||||
_instances.Add(inst);
|
||||
_queriesByToken.Add(inst.Token, inst);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,368 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Robust.Server.Bql
|
||||
{
|
||||
[RegisterBqlQuerySelector]
|
||||
public sealed class WithQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "with";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.Component };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
var comp = (Type) arguments[0];
|
||||
return input.Where(x => entityManager.HasComponent(x, comp) ^ isInverted);
|
||||
}
|
||||
|
||||
public override IEnumerable<EntityUid> DoInitialSelection(IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
if (isInverted)
|
||||
{
|
||||
return base.DoInitialSelection(arguments, isInverted, entityManager);
|
||||
}
|
||||
|
||||
return entityManager.GetAllComponents((Type) arguments[0], includePaused: true)
|
||||
.Select(x => x.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public sealed class NamedQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "named";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.String };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
var r = new Regex("^" + (string) arguments[0] + "$");
|
||||
return input.Where(e =>
|
||||
{
|
||||
if (entityManager.TryGetComponent<MetaDataComponent>(e, out var metaDataComponent))
|
||||
return r.IsMatch(metaDataComponent.EntityName) ^ isInverted;
|
||||
return isInverted;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public sealed class ParentedToQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "parentedto";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.EntityId };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
var uid = (EntityUid) arguments[0];
|
||||
return input.Where(e => (entityManager.TryGetComponent<TransformComponent>(e, out var transform) &&
|
||||
transform.ParentUid == uid) ^ isInverted);
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public sealed class RecursiveParentedToQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "rparentedto";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.EntityId };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
var uid = (EntityUid) arguments[0];
|
||||
return input.Where(e =>
|
||||
{
|
||||
if (!entityManager.TryGetComponent<TransformComponent>(e, out var transform))
|
||||
return isInverted;
|
||||
while (transform.ParentUid != EntityUid.Invalid)
|
||||
{
|
||||
if ((transform.ParentUid == uid) ^ isInverted)
|
||||
return true;
|
||||
if (!entityManager.TryGetComponent(transform.ParentUid, out transform))
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public sealed class ChildrenQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "children";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => Array.Empty<QuerySelectorArgument>();
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
foreach (var uid in input)
|
||||
{
|
||||
if (!entityManager.TryGetComponent(uid, out TransformComponent? xform)) continue;
|
||||
|
||||
foreach (var child in xform.ChildEntities)
|
||||
{
|
||||
yield return child;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public sealed class RecursiveChildrenQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "rchildren";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => Array.Empty<QuerySelectorArgument>();
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments,
|
||||
bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
IEnumerable<EntityUid> toSearch = input;
|
||||
|
||||
while (true)
|
||||
{
|
||||
// TODO: Reduce LINQ chaining
|
||||
var doing = toSearch.Where(entityManager.HasComponent<TransformComponent>).Select(entityManager.GetComponent<TransformComponent>).ToArray();
|
||||
var search = doing.SelectMany(x => x.ChildEntities);
|
||||
if (!search.Any())
|
||||
break;
|
||||
toSearch = doing.SelectMany(x => x.ChildEntities).Where(x => x != EntityUid.Invalid);
|
||||
|
||||
foreach (var xform in doing)
|
||||
{
|
||||
foreach (var uid in xform.ChildEntities)
|
||||
{
|
||||
// This should never happen anyway
|
||||
if (uid == EntityUid.Invalid) continue;
|
||||
yield return uid;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public sealed class ParentQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "parent";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => Array.Empty<QuerySelectorArgument>();
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
return input.Where(entityManager.HasComponent<TransformComponent>)
|
||||
.Distinct();
|
||||
}
|
||||
|
||||
public override IEnumerable<EntityUid> DoInitialSelection(IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
return DoSelection(
|
||||
Query(entityManager.AllEntityQueryEnumerator<TransformComponent>()),
|
||||
arguments,
|
||||
isInverted, entityManager);
|
||||
|
||||
IEnumerable<EntityUid> Query(AllEntityQueryEnumerator<TransformComponent> enumerator)
|
||||
{
|
||||
while (enumerator.MoveNext(out var entityUid, out _))
|
||||
{
|
||||
yield return entityUid;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public sealed class AboveQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "above";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new [] { QuerySelectorArgument.String };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
var tileDefinitionManager = IoCManager.Resolve<ITileDefinitionManager>();
|
||||
var tileTy = tileDefinitionManager[(string) arguments[0]];
|
||||
|
||||
var map = IoCManager.Resolve<IMapManager>();
|
||||
if (tileTy.TileId == 0)
|
||||
{
|
||||
return input.Where(e => entityManager.TryGetComponent<TransformComponent>(e, out var transform) && (transform.GridUid is null) ^ isInverted);
|
||||
}
|
||||
else
|
||||
{
|
||||
return input.Where(e =>
|
||||
{
|
||||
if (!entityManager.TryGetComponent<TransformComponent>(e, out var transform)) return isInverted;
|
||||
|
||||
if (!map.TryGetGrid(transform.GridUid, out var grid))
|
||||
return isInverted;
|
||||
|
||||
return (grid.GetTileRef(transform.Coordinates).Tile.TypeId == tileTy.TileId) ^ isInverted;
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
// ReSharper disable once InconsistentNaming the name is correct shut up
|
||||
public sealed class OnGridQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "ongrid";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new [] { QuerySelectorArgument.Integer };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
// TODO: Probably easier and significantly faster to just iterate the grid's children.
|
||||
var grid = new EntityUid((int) arguments[0]);
|
||||
return input.Where(e => (entityManager.TryGetComponent<TransformComponent>(e, out var transform) && transform.GridUid == grid) ^ isInverted);
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
// ReSharper disable once InconsistentNaming the name is correct shut up
|
||||
public sealed class OnMapQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "onmap";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new [] { QuerySelectorArgument.Integer };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
// TODO: Just use EntityLookup GetEntitiesInMap
|
||||
var map = new MapId((int) arguments[0]);
|
||||
return input.Where(e => (entityManager.TryGetComponent<TransformComponent>(e, out var transform) && transform.MapID == map) ^ isInverted);
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public sealed class PrototypedQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "prototyped";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.String };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
var name = (string) arguments[0];
|
||||
return input.Where(e => (entityManager.TryGetComponent<MetaDataComponent>(e, out var metaData) && metaData.EntityPrototype?.ID == name) ^ isInverted);
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public sealed class RecursivePrototypedQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "rprototyped";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.String };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
var name = (string) arguments[0];
|
||||
return input.Where(e =>
|
||||
{
|
||||
if (!entityManager.TryGetComponent<MetaDataComponent>(e, out var metaData))
|
||||
return isInverted;
|
||||
if ((metaData.EntityPrototype?.ID == name) ^ isInverted)
|
||||
return true;
|
||||
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
|
||||
return metaData.EntityPrototype != null && prototypeManager.EnumerateParents<EntityPrototype>(metaData.EntityPrototype.ID).Any(x => x.Name == name) ^ isInverted;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public sealed class SelectQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "select";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.Integer | QuerySelectorArgument.Percentage };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
if (arguments[0] is int)
|
||||
{
|
||||
var inp = input.OrderBy(_ => Guid.NewGuid()).ToArray();
|
||||
var taken = (int) arguments[0];
|
||||
|
||||
if (isInverted)
|
||||
taken = Math.Max(0, inp.Length - taken);
|
||||
|
||||
return inp.Take(taken);
|
||||
}
|
||||
|
||||
var enumerable = input.OrderBy(_ => Guid.NewGuid()).ToArray();
|
||||
var amount = isInverted
|
||||
? (int) Math.Floor(enumerable.Length * Math.Clamp(1 - (double) arguments[0], 0, 1))
|
||||
: (int) Math.Floor(enumerable.Length * Math.Clamp((double) arguments[0], 0, 1));
|
||||
return enumerable.Take(amount);
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public sealed class NearQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "near";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.Float };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
var radius = (float)(double)arguments[0];
|
||||
var entityLookup = entityManager.System<EntityLookupSystem>();
|
||||
var xformQuery = entityManager.GetEntityQuery<TransformComponent>();
|
||||
var distinct = new HashSet<EntityUid>();
|
||||
|
||||
foreach (var uid in input)
|
||||
{
|
||||
foreach (var near in entityLookup.GetEntitiesInRange(xformQuery.GetComponent(uid).Coordinates,
|
||||
radius))
|
||||
{
|
||||
if (!distinct.Add(near)) continue;
|
||||
yield return near;
|
||||
}
|
||||
}
|
||||
|
||||
//BUG: GetEntitiesInRange effectively uses manhattan distance. This is not intended, near is supposed to be circular.
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
// ReSharper disable once InconsistentNaming the name is correct shut up
|
||||
public sealed class AnchoredQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "anchored";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => Array.Empty<QuerySelectorArgument>();
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
return input.Where(e => (entityManager.TryGetComponent<TransformComponent>(e, out var transform) && transform.Anchored) ^ isInverted);
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public sealed class PausedQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "paused";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => Array.Empty<QuerySelectorArgument>();
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
return input.Where(e => entityManager.GetComponent<MetaDataComponent>(e).EntityPaused ^ isInverted);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Server.Bql
|
||||
{
|
||||
[Flags]
|
||||
[PublicAPI]
|
||||
public enum QuerySelectorArgument
|
||||
{
|
||||
Integer = 0b00000001,
|
||||
Float = 0b00000010,
|
||||
String = 0b00000100,
|
||||
Percentage = 0b00001000,
|
||||
Component = 0b00010000,
|
||||
//SubQuery = 0b00100000,
|
||||
EntityId = 0b01000000,
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public abstract class BqlQuerySelector
|
||||
{
|
||||
/// <summary>
|
||||
/// The token name for the given QuerySelector, for example `when`.
|
||||
/// </summary>
|
||||
public virtual string Token => throw new NotImplementedException();
|
||||
|
||||
/// <summary>
|
||||
/// Arguments for the given QuerySelector, presented as "what arguments are permitted in what spot".
|
||||
/// </summary>
|
||||
public virtual QuerySelectorArgument[] Arguments => throw new NotImplementedException();
|
||||
|
||||
/// <summary>
|
||||
/// Performs a transform over it's input entity list, whether that be filtering (selecting) or expanding the
|
||||
/// input on some criteria like what entities are nearby.
|
||||
/// </summary>
|
||||
/// <param name="input">Input entity list.</param>
|
||||
/// <param name="arguments">Parsed selector arguments.</param>
|
||||
/// <param name="isInverted">Whether the query is inverted.</param>
|
||||
/// <param name="entityManager">The entity manager.</param>
|
||||
/// <returns>New list of entities</returns>
|
||||
/// <exception cref="NotImplementedException">someone is a moron if this happens.</exception>
|
||||
public abstract IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input,
|
||||
IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager);
|
||||
|
||||
/// <summary>
|
||||
/// Performs selection as the first selector in the query. Allows for optimizing when you can be more efficient
|
||||
/// than just querying every entity.
|
||||
/// </summary>
|
||||
/// <param name="arguments"></param>
|
||||
/// <param name="isInverted"></param>
|
||||
/// <param name="entityManager"></param>
|
||||
/// <returns></returns>
|
||||
public virtual IEnumerable<EntityUid> DoInitialSelection(IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
return DoSelection(entityManager.GetEntities(), arguments, isInverted, entityManager);
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
protected BqlQuerySelector() {}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Robust.Server.Bql
|
||||
{
|
||||
public struct BqlQuerySelectorParsed
|
||||
{
|
||||
public List<object> Arguments;
|
||||
public string Token;
|
||||
public bool Inverted;
|
||||
|
||||
public BqlQuerySelectorParsed(List<object> arguments, string token, bool inverted)
|
||||
{
|
||||
Arguments = arguments;
|
||||
Token = token;
|
||||
Inverted = inverted;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Server.Bql
|
||||
{
|
||||
public sealed class ForAllCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IBqlQueryManager _bql = default!;
|
||||
[Dependency] private readonly IEntityManager _entities = default!;
|
||||
|
||||
public override string Command => "forall";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length < 2)
|
||||
{
|
||||
shell.WriteLine(Help);
|
||||
return;
|
||||
}
|
||||
|
||||
var transformSystem = _entities.System<SharedTransformSystem>();
|
||||
|
||||
var (entities, rest) = _bql.SimpleParseAndExecute(argStr[6..]);
|
||||
|
||||
foreach (var ent in entities.ToList())
|
||||
{
|
||||
var cmds = SubstituteEntityDetails(_entities, transformSystem, shell, ent, rest).Split(";");
|
||||
foreach (var cmd in cmds)
|
||||
{
|
||||
shell.ExecuteCommand(cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This will be refactored out soon.
|
||||
private static string SubstituteEntityDetails(
|
||||
IEntityManager entMan,
|
||||
SharedTransformSystem transformSystem,
|
||||
IConsoleShell shell,
|
||||
EntityUid ent,
|
||||
string ruleString)
|
||||
{
|
||||
var transform = entMan.GetComponent<TransformComponent>(ent);
|
||||
var metadata = entMan.GetComponent<MetaDataComponent>(ent);
|
||||
|
||||
var worldPos = transformSystem.GetWorldPosition(transform);
|
||||
var localPos = transform.LocalPosition;
|
||||
|
||||
// gross, is there a better way to do this?
|
||||
ruleString = ruleString.Replace("$ID", ent.ToString());
|
||||
ruleString = ruleString.Replace("$WX", worldPos.X.ToString(CultureInfo.InvariantCulture));
|
||||
ruleString = ruleString.Replace("$WY", worldPos.Y.ToString(CultureInfo.InvariantCulture));
|
||||
ruleString = ruleString.Replace("$LX", localPos.X.ToString(CultureInfo.InvariantCulture));
|
||||
ruleString = ruleString.Replace("$LY", localPos.Y.ToString(CultureInfo.InvariantCulture));
|
||||
ruleString = ruleString.Replace("$NAME", metadata.EntityName);
|
||||
|
||||
if (shell.Player is { AttachedEntity: { } pEntity})
|
||||
{
|
||||
var pTransform = entMan.GetComponent<TransformComponent>(pEntity);
|
||||
|
||||
var pWorldPos = transformSystem.GetWorldPosition(pTransform);
|
||||
var pLocalPos = pTransform.LocalPosition;
|
||||
|
||||
ruleString = ruleString.Replace("$PID", pEntity.ToString());
|
||||
ruleString = ruleString.Replace("$PWX", pWorldPos.X.ToString(CultureInfo.InvariantCulture));
|
||||
ruleString = ruleString.Replace("$PWY", pWorldPos.Y.ToString(CultureInfo.InvariantCulture));
|
||||
ruleString = ruleString.Replace("$PLX", pLocalPos.X.ToString(CultureInfo.InvariantCulture));
|
||||
ruleString = ruleString.Replace("$PLY", pLocalPos.Y.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
return ruleString;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Server.Bql
|
||||
{
|
||||
public interface IBqlQueryManager
|
||||
{
|
||||
public (IEnumerable<EntityUid>, string) SimpleParseAndExecute(string query);
|
||||
void DoAutoRegistrations();
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Robust.Server.Bql
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
[BaseTypeRequired(typeof(BqlQuerySelector))]
|
||||
[MeansImplicitUse]
|
||||
[PublicAPI]
|
||||
public sealed class RegisterBqlQuerySelectorAttribute : Attribute
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Toolshed;
|
||||
using Robust.Shared.Toolshed.Errors;
|
||||
|
||||
namespace Robust.Server.Console
|
||||
{
|
||||
internal sealed class ConGroupController : IConGroupController
|
||||
internal sealed class ConGroupController : IConGroupController, IPermissionController
|
||||
{
|
||||
public IConGroupControllerImplementation? Implementation { get; set; }
|
||||
|
||||
@@ -30,5 +34,11 @@ namespace Robust.Server.Console
|
||||
{
|
||||
return Implementation?.CanAdminReloadPrototypes(session) ?? false;
|
||||
}
|
||||
|
||||
public bool CheckInvokable(CommandSpec command, ICommonSession? user, out IConError? error)
|
||||
{
|
||||
error = null;
|
||||
return Implementation?.CheckInvokable(command, user, out error) ?? false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Toolshed;
|
||||
|
||||
namespace Robust.Server.Console
|
||||
{
|
||||
public interface IConGroupControllerImplementation
|
||||
public interface IConGroupControllerImplementation : IPermissionController
|
||||
{
|
||||
bool CanCommand(IPlayerSession session, string cmdName);
|
||||
bool CanAdminPlace(IPlayerSession session);
|
||||
|
||||
@@ -9,6 +9,8 @@ using Robust.Shared.Log;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Toolshed;
|
||||
using Robust.Shared.Toolshed.Syntax;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Server.Console
|
||||
@@ -19,6 +21,7 @@ namespace Robust.Server.Console
|
||||
[Dependency] private readonly IConGroupController _groupController = default!;
|
||||
[Dependency] private readonly IPlayerManager _players = default!;
|
||||
[Dependency] private readonly ISystemConsoleManager _systemConsole = default!;
|
||||
[Dependency] private readonly ToolshedManager _toolshed = default!;
|
||||
|
||||
public ServerConsoleHost() : base(isServer: true) {}
|
||||
|
||||
@@ -45,19 +48,31 @@ namespace Robust.Server.Console
|
||||
/// <inheritdoc />
|
||||
public override void WriteLine(ICommonSession? session, string text)
|
||||
{
|
||||
var msg = new FormattedMessage();
|
||||
msg.AddText(text);
|
||||
if (session is IPlayerSession playerSession)
|
||||
OutputText(playerSession, text, false);
|
||||
OutputText(playerSession, msg, false);
|
||||
else
|
||||
OutputText(null, text, false);
|
||||
OutputText(null, msg, false);
|
||||
}
|
||||
|
||||
public override void WriteLine(ICommonSession? session, FormattedMessage msg)
|
||||
{
|
||||
if (session is IPlayerSession playerSession)
|
||||
OutputText(playerSession, msg, false);
|
||||
else
|
||||
OutputText(null, msg, false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void WriteError(ICommonSession? session, string text)
|
||||
{
|
||||
var msg = new FormattedMessage();
|
||||
msg.AddText(text);
|
||||
if (session is IPlayerSession playerSession)
|
||||
OutputText(playerSession, text, true);
|
||||
OutputText(playerSession, msg, true);
|
||||
else
|
||||
OutputText(null, text, true);
|
||||
OutputText(null, msg, true);
|
||||
}
|
||||
|
||||
public bool IsCmdServer(IConsoleCommand cmd) => true;
|
||||
@@ -70,13 +85,13 @@ namespace Robust.Server.Console
|
||||
var localShell = shell.ConsoleHost.LocalShell;
|
||||
var sudoShell = new SudoShell(this, localShell, shell);
|
||||
ExecuteInShell(sudoShell, argStr["sudo ".Length..]);
|
||||
}, (shell, args) =>
|
||||
}, (shell, args, argStr) =>
|
||||
{
|
||||
var localShell = shell.ConsoleHost.LocalShell;
|
||||
var sudoShell = new SudoShell(this, localShell, shell);
|
||||
|
||||
#pragma warning disable CA2012
|
||||
return CalcCompletions(sudoShell, args);
|
||||
return CalcCompletions(sudoShell, args, argStr);
|
||||
#pragma warning restore CA2012
|
||||
});
|
||||
|
||||
@@ -117,6 +132,18 @@ namespace Robust.Server.Console
|
||||
AnyCommandExecuted?.Invoke(shell, cmdName, command, cmdArgs);
|
||||
conCmd.Execute(shell, command, cmdArgs);
|
||||
}
|
||||
else
|
||||
{
|
||||
// toolshed time
|
||||
_toolshed.InvokeCommand(shell, command, null, out var res, out var ctx);
|
||||
|
||||
foreach (var err in ctx.GetErrors())
|
||||
{
|
||||
ctx.WriteLine(err.Describe());
|
||||
}
|
||||
|
||||
shell.WriteLine(_toolshed.PrettyPrintType(res));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -162,7 +189,7 @@ namespace Robust.Server.Console
|
||||
ExecuteCommand(session, text);
|
||||
}
|
||||
|
||||
private void OutputText(IPlayerSession? session, string text, bool error)
|
||||
private void OutputText(IPlayerSession? session, FormattedMessage text, bool error)
|
||||
{
|
||||
if (session != null)
|
||||
{
|
||||
@@ -183,10 +210,25 @@ namespace Robust.Server.Console
|
||||
private async void HandleConCompletions(MsgConCompletion message)
|
||||
{
|
||||
var session = _players.GetSessionByChannel(message.MsgChannel);
|
||||
|
||||
var shell = new ConsoleShell(this, session, false);
|
||||
|
||||
var result = await CalcCompletions(shell, message.Args);
|
||||
var result = await CalcCompletions(shell, message.Args, message.ArgString);
|
||||
|
||||
if ((result.Options.Length == 0 && result.Hint is null) || message.Args.Length <= 1)
|
||||
{
|
||||
var parser = new ForwardParser(message.ArgString, _toolshed);
|
||||
CommandRun.TryParse(false, true, parser, null, null, false, out _, out var completions, out _);
|
||||
if (completions == null)
|
||||
{
|
||||
goto done;
|
||||
}
|
||||
var (shedRes, _) = await completions.Value;
|
||||
shedRes ??= CompletionResult.Empty;
|
||||
result = new CompletionResult(shedRes.Options.Concat(result.Options).ToArray(), shedRes.Hint ?? result.Hint);
|
||||
}
|
||||
|
||||
done:
|
||||
var msg = new MsgConCompletionResp
|
||||
{
|
||||
Result = result,
|
||||
@@ -199,7 +241,7 @@ namespace Robust.Server.Console
|
||||
NetManager.ServerSendMessage(msg, message.MsgChannel);
|
||||
}
|
||||
|
||||
private ValueTask<CompletionResult> CalcCompletions(IConsoleShell shell, string[] args)
|
||||
private ValueTask<CompletionResult> CalcCompletions(IConsoleShell shell, string[] args, string argStr)
|
||||
{
|
||||
// Logger.Debug(string.Join(", ", args));
|
||||
|
||||
@@ -217,7 +259,7 @@ namespace Robust.Server.Console
|
||||
if (!ShellCanExecute(shell, cmdName))
|
||||
return ValueTask.FromResult(CompletionResult.Empty);
|
||||
|
||||
return cmd.GetCompletionAsync(shell, args[1..], default);
|
||||
return cmd.GetCompletionAsync(shell, args[1..], argStr, default);
|
||||
}
|
||||
|
||||
private sealed class SudoShell : IConsoleShell
|
||||
@@ -254,6 +296,12 @@ namespace Robust.Server.Console
|
||||
_sudoer.WriteLine(text);
|
||||
}
|
||||
|
||||
public void WriteLine(FormattedMessage message)
|
||||
{
|
||||
_owner.WriteLine(message);
|
||||
_sudoer.WriteLine(message);
|
||||
}
|
||||
|
||||
public void WriteError(string text)
|
||||
{
|
||||
_owner.WriteError(text);
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Server.GameObjects;
|
||||
|
||||
/// <summary>
|
||||
/// Lets any entities with this component ignore user interface range checks that would normally
|
||||
/// close the UI automatically.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class IgnoreUIRangeComponent : Component
|
||||
{
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Server.GameObjects
|
||||
@@ -11,6 +14,8 @@ namespace Robust.Server.GameObjects
|
||||
[UsedImplicitly]
|
||||
public sealed class ActorSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -116,6 +121,20 @@ namespace Robust.Server.GameObjects
|
||||
// The player is fully detached now that the component has shut down.
|
||||
RaiseLocalEvent(entity, new PlayerDetachedEvent(entity, component.PlayerSession), true);
|
||||
}
|
||||
|
||||
public bool TryGetActorFromUserId(NetUserId? userId, [NotNullWhen(true)] out IPlayerSession? actor, [MaybeNullWhen(true)] out EntityUid? actorEntity)
|
||||
{
|
||||
actor = null;
|
||||
actorEntity = null;
|
||||
if (userId != null)
|
||||
{
|
||||
if (!_playerManager.TryGetSessionById(userId.Value, out actor))
|
||||
return false;
|
||||
actorEntity = actor.AttachedEntity;
|
||||
}
|
||||
|
||||
return actor != null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -17,6 +17,8 @@ namespace Robust.Server.GameObjects
|
||||
[Dependency] private readonly IPlayerManager _playerMan = default!;
|
||||
[Dependency] private readonly TransformSystem _xformSys = default!;
|
||||
|
||||
private EntityQuery<IgnoreUIRangeComponent> _ignoreUIRangeQuery;
|
||||
|
||||
private readonly List<IPlayerSession> _sessionCache = new();
|
||||
|
||||
private readonly Dictionary<IPlayerSession, List<BoundUserInterface>> _openInterfaces = new();
|
||||
@@ -30,6 +32,8 @@ namespace Robust.Server.GameObjects
|
||||
SubscribeLocalEvent<ServerUserInterfaceComponent, ComponentInit>(OnUserInterfaceInit);
|
||||
SubscribeLocalEvent<ServerUserInterfaceComponent, ComponentShutdown>(OnUserInterfaceShutdown);
|
||||
_playerMan.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||
|
||||
_ignoreUIRangeQuery = GetEntityQuery<IgnoreUIRangeComponent>();
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
@@ -174,6 +178,9 @@ namespace Robust.Server.GameObjects
|
||||
if (!query.TryGetComponent(session.AttachedEntity, out var xform))
|
||||
continue;
|
||||
|
||||
if (_ignoreUIRangeQuery.HasComponent(session.AttachedEntity))
|
||||
continue;
|
||||
|
||||
if (uiMap != xform.MapID)
|
||||
{
|
||||
CloseUi(ui, session, activeUis);
|
||||
|
||||
@@ -81,30 +81,35 @@ namespace Robust.Server.GameObjects
|
||||
|
||||
private protected override EntityUid CreateEntity(string? prototypeName, EntityUid uid = default, IEntityLoadContext? context = null)
|
||||
{
|
||||
var entity = base.CreateEntity(prototypeName, uid, context);
|
||||
if (prototypeName == null)
|
||||
return base.CreateEntity(prototypeName, uid, context);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(prototypeName))
|
||||
{
|
||||
var prototype = PrototypeManager.Index<EntityPrototype>(prototypeName);
|
||||
if (!PrototypeManager.TryIndex<EntityPrototype>(prototypeName, out var prototype))
|
||||
throw new EntityCreationException($"Attempted to spawn an entity with an invalid prototype: {prototypeName}");
|
||||
|
||||
// At this point in time, all data configure on the entity *should* be purely from the prototype.
|
||||
// As such, we can reset the modified ticks to Zero,
|
||||
// which indicates "not different from client's own deserialization".
|
||||
// So the initial data for the component or even the creation doesn't have to be sent over the wire.
|
||||
foreach (var (netId, component) in GetNetComponents(entity))
|
||||
{
|
||||
// Make sure to ONLY get components that are defined in the prototype.
|
||||
// Others could be instantiated directly by AddComponent (e.g. ContainerManager).
|
||||
// And those aren't guaranteed to exist on the client, so don't clear them.
|
||||
var compName = ComponentFactory.GetComponentName(component.GetType());
|
||||
if (prototype.Components.ContainsKey(compName))
|
||||
component.ClearTicks();
|
||||
}
|
||||
}
|
||||
var entity = base.CreateEntity(prototype, uid, context);
|
||||
|
||||
// At this point in time, all data configure on the entity *should* be purely from the prototype.
|
||||
// As such, we can reset the modified ticks to Zero,
|
||||
// which indicates "not different from client's own deserialization".
|
||||
// So the initial data for the component or even the creation doesn't have to be sent over the wire.
|
||||
ClearTicks(entity, prototype);
|
||||
return entity;
|
||||
}
|
||||
|
||||
private void ClearTicks(EntityUid entity, EntityPrototype prototype)
|
||||
{
|
||||
foreach (var (netId, component) in GetNetComponents(entity))
|
||||
{
|
||||
// Make sure to ONLY get components that are defined in the prototype.
|
||||
// Others could be instantiated directly by AddComponent (e.g. ContainerManager).
|
||||
// And those aren't guaranteed to exist on the client, so don't clear them.
|
||||
var compName = ComponentFactory.GetComponentName(netId);
|
||||
if (prototype.Components.ContainsKey(compName))
|
||||
component.ClearTicks();
|
||||
}
|
||||
}
|
||||
|
||||
public override EntityStringRepresentation ToPrettyString(EntityUid uid)
|
||||
{
|
||||
TryGetComponent(uid, out ActorComponent? actor);
|
||||
|
||||
@@ -34,6 +34,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
[Shared.IoC.Dependency] private readonly IServerNetConfigurationManager _netConfigManager = default!;
|
||||
[Shared.IoC.Dependency] private readonly IServerGameStateManager _serverGameStateManager = default!;
|
||||
[Shared.IoC.Dependency] private readonly IParallelManager _parallelManager = default!;
|
||||
[Shared.IoC.Dependency] private readonly IComponentFactory _factory = default!;
|
||||
|
||||
public const float ChunkSize = 8;
|
||||
|
||||
@@ -1033,9 +1034,23 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
while (query.MoveNext(out var uid, out var md))
|
||||
{
|
||||
DebugTools.Assert(md.EntityLifeStage >= EntityLifeStage.Initialized);
|
||||
DebugTools.Assert(md.EntityLastModifiedTick >= md.CreationTick);
|
||||
if (md.EntityLastModifiedTick > fromTick)
|
||||
stateEntities.Add(GetEntityState(player, uid, fromTick, md));
|
||||
DebugTools.Assert(md.EntityLifeStage < EntityLifeStage.Terminating);
|
||||
if (md.EntityLastModifiedTick <= fromTick)
|
||||
continue;
|
||||
|
||||
var state = GetEntityState(player, uid, fromTick, md);
|
||||
|
||||
if (state.Empty)
|
||||
{
|
||||
Log.Error($@"{nameof(GetEntityState)} returned an empty state while enumerating entities.
|
||||
Tick: {fromTick}--{_gameTiming.CurTick}
|
||||
Entity: {ToPrettyString(uid)}
|
||||
Last modified: {md.EntityLastModifiedTick}
|
||||
Metadata last modified: {md.LastModifiedTick}
|
||||
Transform last modified: {Transform(uid).LastModifiedTick}");
|
||||
}
|
||||
|
||||
stateEntities.Add(state);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -1056,9 +1071,24 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
continue;
|
||||
|
||||
DebugTools.Assert(md.EntityLifeStage >= EntityLifeStage.Initialized);
|
||||
DebugTools.Assert(md.EntityLastModifiedTick >= md.CreationTick || md.EntityLastModifiedTick == GameTick.Zero);
|
||||
DebugTools.Assert(md.EntityLastModifiedTick > fromTick || md.EntityLastModifiedTick == GameTick.Zero);
|
||||
stateEntities.Add(GetEntityState(player, uid, fromTick, md));
|
||||
DebugTools.Assert(md.EntityLifeStage < EntityLifeStage.Terminating);
|
||||
DebugTools.Assert(md.EntityLastModifiedTick >= md.CreationTick);
|
||||
DebugTools.Assert(md.EntityLastModifiedTick > fromTick);
|
||||
|
||||
var state = GetEntityState(player, uid, fromTick, md);
|
||||
|
||||
if (state.Empty)
|
||||
{
|
||||
Log.Error($@"{nameof(GetEntityState)} returned an empty state for a new entity.
|
||||
Tick: {fromTick}--{_gameTiming.CurTick}
|
||||
Entity: {ToPrettyString(uid)}
|
||||
Last modified: {md.EntityLastModifiedTick}
|
||||
Metadata last modified: {md.LastModifiedTick}
|
||||
Transform last modified: {Transform(uid).LastModifiedTick}");
|
||||
continue;
|
||||
}
|
||||
|
||||
stateEntities.Add(state);
|
||||
}
|
||||
|
||||
foreach (var uid in dirty)
|
||||
@@ -1068,9 +1098,13 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
continue;
|
||||
|
||||
DebugTools.Assert(md.EntityLifeStage >= EntityLifeStage.Initialized);
|
||||
DebugTools.Assert(md.EntityLastModifiedTick >= md.CreationTick || md.EntityLastModifiedTick == GameTick.Zero);
|
||||
DebugTools.Assert(md.EntityLastModifiedTick > fromTick || md.EntityLastModifiedTick == GameTick.Zero);
|
||||
stateEntities.Add(GetEntityState(player, uid, fromTick, md));
|
||||
DebugTools.Assert(md.EntityLifeStage < EntityLifeStage.Terminating);
|
||||
DebugTools.Assert(md.EntityLastModifiedTick >= md.CreationTick);
|
||||
DebugTools.Assert(md.EntityLastModifiedTick > fromTick);
|
||||
|
||||
var state = GetEntityState(player, uid, fromTick, md);
|
||||
if (!state.Empty)
|
||||
stateEntities.Add(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user