Compare commits

...

93 Commits

Author SHA1 Message Date
DrSmugleaf
f6c55085fe Version: 148.3.0 2023-08-21 19:01:15 -07:00
DrSmugleaf
30eafd26e7 Fix test checking that Robust's and .NET's colors are equal (#4287) 2023-08-21 16:26:06 -07:00
Pieter-Jan Briers
63423d96b4 Fixes for new color PR (#4278)
Undo change to violet color

add to named color list
2023-08-21 23:06:20 +02:00
Morb
474334aff2 Make DiscordRichPresence icon CVars server-side with replication (#4272) 2023-08-21 10:44:40 +02:00
Leon Friedrich
be102f86bf Add IntegrationInstance fields for common dependencies (#4283) 2023-08-21 14:35:27 +10:00
Tom Leys
d7962c7190 Add implementation of Random.Pick(ValueList<T> ..) (#4257) 2023-08-21 13:56:18 +10:00
Leon Friedrich
7fe9385c3b Change default value of EntityLastModifiedTick from zero to one. (#4282)
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2023-08-21 13:55:41 +10:00
Kara
a9d9d1348a Tile texture reload command (#4268) 2023-08-21 13:46:58 +10:00
Leon Friedrich
4eaf624555 Allow pre-startup components to be shut down. (#4281) 2023-08-21 13:45:57 +10:00
Leon Friedrich
e37c131fb4 Prevent invalid prototypes from being spawned (#4279) 2023-08-21 12:29:02 +10:00
PrPleGoo
9df4606492 Added colors (#4278) 2023-08-20 18:48:00 +02:00
Pieter-Jan Briers
3be8070274 Happy eyeballs delay can be configured. 2023-08-20 17:45:43 +02:00
metalgearsloth
8a440d705f Version: 148.2.0 2023-08-20 15:53:35 +10:00
metalgearsloth
5849474022 Add IsPaused to EntityManager (#4277) 2023-08-20 15:50:11 +10:00
c4llv07e
b7d67c0ece Fix UserInterface.SetActiveTheme didn't update theme (#4263) 2023-08-20 03:36:38 +10:00
Pieter-Jan Briers
b04cf71bc0 Expose SpinBox.LineEditControl 2023-08-19 16:32:05 +02:00
Leon Friedrich
ea87df649a Add readonly VV attributes to various fields. (#4265) 2023-08-20 00:13:18 +10:00
metalgearsloth
dab7a9112f Version: 148.1.0 2023-08-16 19:19:46 +10:00
DrSmugleaf
d3dc89832a Add component that lets entities ignore BUI range checks (#4264) 2023-08-16 15:41:01 +10:00
Leon Friedrich
3ade9ca447 Add support for f16-f24 keys (#4261) 2023-08-13 22:15:18 +10:00
Leon Friedrich
149e9a2613 Fix a gamestate bug. (#4260) 2023-08-13 11:22:15 +10:00
ElectroJr
d164148ce2 Version: 148.0.0 2023-08-12 19:40:55 -04:00
/ʊniɹɑː/
d898b52449 more richtext (#4187) 2023-08-13 09:34:44 +10:00
TemporalOroboros
dcf7a1e580 PixelToMap (#4188) 2023-08-13 09:34:33 +10:00
Pieter-Jan Briers
65c6bb74eb Mark a bunch of NuGet dependencies as private compile assets (#4258) 2023-08-13 07:22:14 +10:00
Leon Friedrich
d6d88bea91 Fix replay handling of bad prototype uploads (#4259) 2023-08-13 07:07:47 +10:00
Pieter-Jan Briers
d6467f768a Fix Logger calls in ComponentRegistrySerializer 2023-08-11 23:50:54 +02:00
ElectroJr
4f0f020f56 Version: 147.0.0 2023-08-10 00:40:01 -04:00
Leon Friedrich
5ce8369fb9 Rename a Dirty() proxy method to DirtyEntity() (#4253) 2023-08-10 14:17:57 +10:00
Pieter-Jan Briers
2446e64033 entitysystemupdateorder debug command 2023-08-08 21:36:14 +02:00
metalgearsloth
bdd65cda4b Version: 146.0.0 2023-08-08 17:26:42 +10:00
Leon Friedrich
77e949bfe8 More serialization related changes, (#4250) 2023-08-08 17:22:10 +10:00
metalgearsloth
d4171351f4 Metadata + cancollide stuff (#4247) 2023-08-08 12:03:27 +10:00
metalgearsloth
e67d0ad3d6 Xform stuff (#4246) 2023-08-08 11:55:56 +10:00
Artur
aff5711fde Add missing CultureInfo.InvariantCulture in angle validaton (#4248) 2023-08-08 11:52:24 +10:00
ElectroJr
a886222946 Version: 145.0.0 2023-08-06 21:45:09 -04:00
Leon Friedrich
5843f1087e Add ContentFileRead test and fix file reading on windows (#4242) 2023-08-07 11:39:09 +10:00
Leon Friedrich
93c0ce815f Add IPrototypeManager.EnumerateKinds() (#4244) 2023-08-07 11:24:34 +10:00
Leon Friedrich
1c64fa1f28 Fix TransformSystem.SetCoordinates() error logs. (#4245) 2023-08-07 11:24:26 +10:00
Leon Friedrich
c825c1e413 Remove IoCManager.Resolve calls in Resource.Load (#4243) 2023-08-07 11:24:15 +10:00
Chief-Engineer
f30fb47834 Add GetActorFromUserId to actor system (#4239) 2023-08-07 11:24:00 +10:00
Leon Friedrich
5d255e06c8 Fix SpriteSpecifier yaml validator (#4241) 2023-08-06 22:14:02 +10:00
metalgearsloth
80357c8ec4 Version: 144.0.1 2023-08-06 15:07:48 +10:00
metalgearsloth
ac3a434bdf Shrink entitylookup tile enlargement even further (#4240) 2023-08-06 15:06:08 +10:00
metalgearsloth
21719b8884 Version: 144.0.0 2023-08-06 12:45:49 +10:00
metalgearsloth
dbb6b90654 Tile enlargement + new flag for lookups (#4205) 2023-08-06 12:41:27 +10:00
ElectroJr
4b92be5324 Version: 143.3.0 2023-08-05 21:16:11 -04:00
Leon Friedrich
95169b7a71 Add temporary debug logs (#4237) 2023-08-06 11:11:23 +10:00
Leon Friedrich
b699e22c85 More serialization fixes (#4224) 2023-08-06 11:08:48 +10:00
Leon Friedrich
e48cc62d0b Clamp audio offset in ApplyAudioParams() (#4221) 2023-08-06 10:38:53 +10:00
Leon Friedrich
aade062a49 Allow replay loading to ignore some errors (#4236) 2023-08-06 10:33:31 +10:00
Chief-Engineer
e02166d5c4 add placement events (#4235) 2023-08-06 10:32:04 +10:00
Leon Friedrich
8037bfae14 Remove an incorrect assert (#4238) 2023-08-06 10:29:30 +10:00
metalgearsloth
49781791af Version: 143.2.0 2023-08-05 12:43:34 +10:00
metalgearsloth
92719aa29f Shutdown grid rendering events (#4234) 2023-08-05 11:38:16 +10:00
Chief-Engineer
1d47a9677d fix named toolshed command (#4230) 2023-08-04 14:51:06 +10:00
Leon Friedrich
14fe8eba6d Remove unnecessary dummy test prototypes (#4233) 2023-08-04 14:50:53 +10:00
Leon Friedrich
2ff99d4a62 Add support for tests to load extra prototypes from multiple "files" (#4232) 2023-08-04 14:50:08 +10:00
metalgearsloth
ce8b2d82a3 Version: 143.1.0 2023-08-04 12:40:21 +10:00
Leon Friedrich
f8f99450db Error on duplicate broadcast subscriptions (#4231) 2023-08-04 12:34:10 +10:00
metalgearsloth
a3cf4877e4 Don't raise contact events for qdel ents (#4126) 2023-08-04 12:22:59 +10:00
metalgearsloth
6ab08f7dc1 Add BodyStatus readwrite (#4227) 2023-08-04 12:22:50 +10:00
Vordenburg
819f6921cf Add locale support for grammatical measure words (#4064) 2023-08-03 20:00:47 +10:00
moonheart08
cf91369d27 Version: 143.0.0 2023-08-02 16:07:25 -05:00
Moony
0e1328675c Toolshed (#4197)
* Saving work

* Move shit to engine

* lord

* merg

* awa

* bql is kill

* forgot the fucking bike rack

* bql is kill for real

* pjb will kill me

* aughfhbdj

* yo ho here we go on my way to the MINES

* a

* adgddf

* gdsgvfvxshngfgh

* b

* hfsjhghj

* hbfdjjh

* tf you mean i have to document it

* follow C# standards

* Assorted cleanup and documentation pass, minor bugfix in ValueRefParser.

* Start porting old commands, remove that pesky prefix in favor of integrating with the shell.

* Fix valueref up a bit, improve autocomplete for it.

* e

* Toolshed type system adventure.

* a log

* a

* a e i o u

* awa

* fix tests

* Arithmetic commands.

* a

* parse improvements

---------

Co-authored-by: moonheart08 <moonheart08@users.noreply.github.com>
2023-08-02 16:06:16 -05:00
metalgearsloth
a7315b1c95 Version: 142.1.2 2023-08-02 13:43:19 +10:00
metalgearsloth
c8f2a55cbe Don't log error on relay resolve (#4223) 2023-08-02 13:42:07 +10:00
ElectroJr
7812502b0b Version: 142.1.1 2023-08-01 22:42:01 -04:00
Leon Friedrich
3149f99954 Fix bad DetachParentToNull() assert (#4222) 2023-08-02 12:40:59 +10:00
ElectroJr
ef8c6379cd Version: 142.1.0 2023-08-01 20:54:36 -04:00
KIBORG04
f932e023ee Configurable Discord Rich Presence icons and some localization (#4174) 2023-08-02 10:36:45 +10:00
Leon Friedrich
a137c839fc Add prototype serialization validation methods. (#4210) 2023-08-02 10:29:55 +10:00
Leon Friedrich
3e43b88518 Fix bad disconnects in tests (#4217) 2023-08-02 10:29:17 +10:00
metalgearsloth
3d974e0305 Fix entitylookup recursion (#4218) 2023-08-02 10:28:10 +10:00
deathride58
b3682017ac Enables lightsample manipulation shenanigans (#4201) 2023-08-02 04:52:38 +10:00
metalgearsloth
194743a9b0 Set sawmill default to info on release (#4198) 2023-08-02 04:33:31 +10:00
Vera Aguilera Puerto
34d65a7960 Use engine font instead of SS14-specific font in a few places. (#4206) 2023-08-02 04:32:31 +10:00
metalgearsloth
6cd4a37a8f Add VV readwrite to physics stuff (#4220) 2023-08-02 04:31:53 +10:00
Vordenburg
40d879fddc Read Artist and Title tags from Ogg Vorbis files (#4207) 2023-08-02 04:31:20 +10:00
Leon Friedrich
df398c5b13 Truncate discord rich presence strings (#4213) 2023-08-02 04:30:22 +10:00
Leon Friedrich
804b698172 Improve DetachParentToNull() debug asserts (#4194) 2023-08-02 04:27:18 +10:00
metalgearsloth
346514c6e0 Allow overriding joint relays (#4219) 2023-08-02 04:27:01 +10:00
Leon Friedrich
d65e2eb169 Fix test IoC error (#4216) 2023-08-01 18:57:14 +10:00
Leon Friedrich
ff41329ad7 Add DataNode.ToString() (#4209) 2023-08-01 12:13:46 +10:00
Leon Friedrich
6151a26622 Remove unnecessary component fetch in AnyComponentsIntersecting (#4215) 2023-08-01 12:13:32 +10:00
Pieter-Jan Briers
1c7ae13bfa Happy Eyeballs for HttpClient use.
All HttpClient usages in the engine now use Happy Eyeballs, same implementation as the launcher.

Makes a IHttpClientHolder type so content can profit from this technology too. Didn't make use of this in all HttpClient usages in the engine itself, due to varying circumstances making it annoying to refactor.
2023-07-31 22:51:25 +02:00
metalgearsloth
cb6645aebe Version: 142.0.1 2023-07-31 14:33:25 +10:00
Leon Friedrich
fffe3c56e9 Fix enum serialization (#4208) 2023-07-30 20:17:22 +10:00
ElectroJr
204e881690 Version: 142.0.0 2023-07-29 13:42:33 -04:00
metalgearsloth
161b1874c2 Some comp query optimisations (#4203) 2023-07-30 03:34:35 +10:00
metalgearsloth
7c3634f1f5 Add better GetAssemblyByName debug (#4192) 2023-07-30 03:32:02 +10:00
Leon Friedrich
9969899f38 Add method to validate prototype ids in c# fields (#4189) 2023-07-30 03:31:17 +10:00
Leon Friedrich
ed35839942 Add warning for unhandled replay messages (#4199) 2023-07-29 18:04:56 +10:00
295 changed files with 10245 additions and 2687 deletions

View File

@@ -1,4 +1,4 @@
<Project>
<!-- This file automatically reset by Tools/version.py -->
<!-- This file automatically reset by Tools/version.py -->

View File

@@ -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

View File

@@ -1,6 +1,6 @@
- type: uiTheme
id: Default
path: /Textures/Interface/Default
path: /Textures/Interface/Default/
colors:
# Root
rootBackground: "#000000"

View File

@@ -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

View File

@@ -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}

View File

@@ -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

View 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.

View File

@@ -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;
}
}

View File

@@ -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>();

View File

@@ -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
};

View File

@@ -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);
}
}
}

View File

@@ -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))
{

View File

@@ -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);
}
}

View File

@@ -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))
{

View File

@@ -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;

View File

@@ -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();

View File

@@ -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();

View File

@@ -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

View File

@@ -16,4 +16,9 @@ internal sealed class GridRenderingSystem : EntitySystem
{
_clyde.RegisterGridEcsEvents();
}
public override void Shutdown()
{
_clyde.ShutdownGridEcsEvents();
}
}

View File

@@ -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.

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;
}
}
}

View File

@@ -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)

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -88,6 +88,11 @@ namespace Robust.Client.Graphics.Clyde
// Nada.
}
public void ShutdownGridEcsEvents()
{
}
public void SetWindowTitle(string title)
{
// Nada.

View File

@@ -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));
}

View File

@@ -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},
};

View File

@@ -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>();

View File

@@ -65,6 +65,8 @@ namespace Robust.Client.Graphics
void RegisterGridEcsEvents();
void ShutdownGridEcsEvents();
void RunOnWindowThread(Action action);
}
}

View File

@@ -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!;

View File

@@ -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;
}

View File

@@ -161,6 +161,15 @@ namespace Robust.Client.Input
F13,
F14,
F15,
F16,
F17,
F18,
F19,
F20,
F21,
F22,
F23,
F24,
Pause,
World1,
}

View File

@@ -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();
}
}
}

View File

@@ -532,7 +532,7 @@ namespace Robust.Client.Placement
return false;
}
coordinates = EntityCoordinates.FromMap(MapManager,
EyeManager.ScreenToMap(InputManager.MouseScreenPosition));
EyeManager.PixelToMap(InputManager.MouseScreenPosition));
return true;
}
}

View File

@@ -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);

View File

@@ -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>

View File

@@ -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 />

View File

@@ -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();

View File

@@ -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)
{

View File

@@ -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;
}
}

View File

@@ -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?");

View File

@@ -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()}.");
}
}
}

View File

@@ -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
{

View File

@@ -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; }
}
}

View File

@@ -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];

View File

@@ -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
{

View File

@@ -18,7 +18,7 @@ namespace Robust.Client.ResourceManagement
using (stream)
{
FontFaceHandle = IoCManager.Resolve<IFontManagerInternal>().Load(stream);
FontFaceHandle = ((IFontManagerInternal)cache.FontManager).Load(stream);
}
}

View File

@@ -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());

View File

@@ -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);
}
}
}

View File

@@ -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);
}

View File

@@ -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>

View File

@@ -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)},
});
}
}

View File

@@ -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);

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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"

View File

@@ -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.

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -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);
}

View 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();
}
}

View File

@@ -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();

View 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 _) => " · ";
}

View File

@@ -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";

View File

@@ -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))
{

View 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();
}
}

View File

@@ -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();

View File

@@ -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();
}
}

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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
*/

View File

@@ -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)

View File

@@ -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()

View File

@@ -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
{

View File

@@ -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.

View File

@@ -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
{

View File

@@ -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();

View File

@@ -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;
});
}
}
}

View File

@@ -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());
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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() {}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}

View File

@@ -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
{
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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
{
}

View File

@@ -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>

View File

@@ -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);

View File

@@ -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);

View File

@@ -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