mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
68 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f0f020f56 | ||
|
|
5ce8369fb9 | ||
|
|
2446e64033 | ||
|
|
bdd65cda4b | ||
|
|
77e949bfe8 | ||
|
|
d4171351f4 | ||
|
|
e67d0ad3d6 | ||
|
|
aff5711fde | ||
|
|
a886222946 | ||
|
|
5843f1087e | ||
|
|
93c0ce815f | ||
|
|
1c64fa1f28 | ||
|
|
c825c1e413 | ||
|
|
f30fb47834 | ||
|
|
5d255e06c8 | ||
|
|
80357c8ec4 | ||
|
|
ac3a434bdf | ||
|
|
21719b8884 | ||
|
|
dbb6b90654 | ||
|
|
4b92be5324 | ||
|
|
95169b7a71 | ||
|
|
b699e22c85 | ||
|
|
e48cc62d0b | ||
|
|
aade062a49 | ||
|
|
e02166d5c4 | ||
|
|
8037bfae14 | ||
|
|
49781791af | ||
|
|
92719aa29f | ||
|
|
1d47a9677d | ||
|
|
14fe8eba6d | ||
|
|
2ff99d4a62 | ||
|
|
ce8b2d82a3 | ||
|
|
f8f99450db | ||
|
|
a3cf4877e4 | ||
|
|
6ab08f7dc1 | ||
|
|
819f6921cf | ||
|
|
cf91369d27 | ||
|
|
0e1328675c | ||
|
|
a7315b1c95 | ||
|
|
c8f2a55cbe | ||
|
|
7812502b0b | ||
|
|
3149f99954 | ||
|
|
ef8c6379cd | ||
|
|
f932e023ee | ||
|
|
a137c839fc | ||
|
|
3e43b88518 | ||
|
|
3d974e0305 | ||
|
|
b3682017ac | ||
|
|
194743a9b0 | ||
|
|
34d65a7960 | ||
|
|
6cd4a37a8f | ||
|
|
40d879fddc | ||
|
|
df398c5b13 | ||
|
|
804b698172 | ||
|
|
346514c6e0 | ||
|
|
d65e2eb169 | ||
|
|
ff41329ad7 | ||
|
|
6151a26622 | ||
|
|
1c7ae13bfa | ||
|
|
cb6645aebe | ||
|
|
fffe3c56e9 | ||
|
|
204e881690 | ||
|
|
161b1874c2 | ||
|
|
7c3634f1f5 | ||
|
|
9969899f38 | ||
|
|
ed35839942 | ||
|
|
380ccfacd8 | ||
|
|
cc0cc6afb1 |
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
203
RELEASE-NOTES.md
203
RELEASE-NOTES.md
@@ -54,6 +54,209 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
* Fix component trait dictionaries not clearing on reconnect leading to bad GetComponent in areas (e.g. entire game looks black due to no entities).
|
||||
|
||||
|
||||
## 141.2.0
|
||||
|
||||
### Other
|
||||
|
||||
BIN
Resources/EngineFonts/NotoSans/NotoSansMono-Regular.ttf
Normal file
BIN
Resources/EngineFonts/NotoSans/NotoSansMono-Regular.ttf
Normal file
Binary file not shown.
@@ -1,6 +1,6 @@
|
||||
- type: uiTheme
|
||||
id: Default
|
||||
path: /Textures/Interface/Default
|
||||
path: /Textures/Interface/Default/
|
||||
colors:
|
||||
# Root
|
||||
rootBackground: "#000000"
|
||||
|
||||
@@ -17,15 +17,15 @@ cmd-error-dir-not-found = Could not find directory: {$dir}.
|
||||
cmd-failure-no-attached-entity = There is no entity attached to this shell.
|
||||
|
||||
## 'help' command
|
||||
cmd-help-desc = Display general help or help text for a specific command
|
||||
cmd-help-help = Usage: help [command name]
|
||||
cmd-oldhelp-desc = Display general help or help text for a specific command
|
||||
cmd-oldhelp-help = Usage: help [command name]
|
||||
When no command name is provided, displays general-purpose help text. If a command name is provided, displays help text for that command.
|
||||
|
||||
cmd-help-no-args = To display help for a specific command, write 'help <command>'. To list all available commands, write 'list'. To search for commands, use 'list <filter>'.
|
||||
cmd-help-unknown = Unknown command: { $command }
|
||||
cmd-help-top = { $command } - { $description }
|
||||
cmd-help-invalid-args = Invalid amount of arguments.
|
||||
cmd-help-arg-cmdname = [command name]
|
||||
cmd-oldhelp-no-args = To display help for a specific command, write 'help <command>'. To list all available commands, write 'list'. To search for commands, use 'list <filter>'.
|
||||
cmd-oldhelp-unknown = Unknown command: { $command }
|
||||
cmd-oldhelp-top = { $command } - { $description }
|
||||
cmd-oldhelp-invalid-args = Invalid amount of arguments.
|
||||
cmd-oldhelp-arg-cmdname = [command name]
|
||||
|
||||
## 'cvar' command
|
||||
cmd-cvar-desc = Gets or sets a CVar.
|
||||
|
||||
@@ -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}
|
||||
|
||||
169
Resources/Locale/en-US/toolshed-commands.ftl
Normal file
169
Resources/Locale/en-US/toolshed-commands.ftl
Normal file
@@ -0,0 +1,169 @@
|
||||
command-description-tpto =
|
||||
Teleport the given entities to some target entity.
|
||||
command-description-player-list =
|
||||
Returns a list of all player sessions.
|
||||
command-description-player-self =
|
||||
Returns the current player session.
|
||||
command-description-player-imm =
|
||||
Returns the session associated with the player given as argument.
|
||||
command-description-player-entity =
|
||||
Returns the entities of the input sessions.
|
||||
command-description-self =
|
||||
Returns the current attached entity.
|
||||
command-description-physics-velocity =
|
||||
Returns the velocity of the input entities.
|
||||
command-description-physics-angular-velocity =
|
||||
Returns the angular velocity of the input entities.
|
||||
command-description-buildinfo =
|
||||
Provides information about the build of the game.
|
||||
command-description-cmd-list =
|
||||
Returns a list of all commands, for this side.
|
||||
command-description-explain =
|
||||
Explains the given expression, providing command descriptions and signatures.
|
||||
command-description-search =
|
||||
Searches through the input for the provided value.
|
||||
command-description-stopwatch =
|
||||
Measures the execution time of the given expression.
|
||||
command-description-types-consumers =
|
||||
Provides all commands that can consume the given type.
|
||||
command-description-types-tree =
|
||||
Debug tool to return all types the command interpreter can downcast the input to.
|
||||
command-description-types-gettype =
|
||||
Returns the type of the input.
|
||||
command-description-types-fullname =
|
||||
Returns the full name of the input type according to CoreCLR.
|
||||
command-description-as =
|
||||
Casts the input to the given type.
|
||||
Effectively a type hint if you know the type but the interpreter does not.
|
||||
command-description-count =
|
||||
Counts the amount of entries in it's input, returning an integer.
|
||||
command-description-map =
|
||||
Maps the input over the given block, with the provided expected return type.
|
||||
This command may be modified to not need an explicit return type in the future.
|
||||
command-description-select =
|
||||
Selects N objects or N% of objects from the input.
|
||||
One can additionally invert this command with not to make it select everything except N objects instead.
|
||||
command-description-comp =
|
||||
Returns the given component from the input entities, discarding entities without that component.
|
||||
command-description-delete =
|
||||
Deletes the input entities.
|
||||
command-description-ent =
|
||||
Returns the provided entity ID.
|
||||
command-description-entities =
|
||||
Returns all entities on the server.
|
||||
command-description-paused =
|
||||
Filters the input entities by whether or not they are paused.
|
||||
This command can be inverted with not.
|
||||
command-description-with =
|
||||
Filters the input entities by whether or not they have the given component.
|
||||
This command can be inverted with not.
|
||||
command-description-fuck =
|
||||
Throws an exception.
|
||||
command-description-ecscomp-listty =
|
||||
Lists every type of component registered.
|
||||
command-description-cd =
|
||||
Changes the session's current directory to the given relative or absolute path.
|
||||
command-description-ls-here =
|
||||
Lists the contents of the current directory.
|
||||
command-description-ls-in =
|
||||
Lists the contents of the given relative or absolute path.
|
||||
command-description-methods-get =
|
||||
Returns all methods associated with the input type.
|
||||
command-description-methods-overrides =
|
||||
Returns all methods overriden on the input type.
|
||||
command-description-methods-overridesfrom =
|
||||
Returns all methods overriden from the given type on the input type.
|
||||
command-description-cmd-moo =
|
||||
Asks the important questions.
|
||||
command-description-cmd-descloc =
|
||||
Returns the localization string for a command's description.
|
||||
command-description-cmd-getshim =
|
||||
Returns a command's execution shim.
|
||||
command-description-help =
|
||||
Provides a quick rundown of how to use toolshed.
|
||||
command-description-ioc-registered =
|
||||
Returns all the types registered with IoCManager on the current thread (usually the game thread)
|
||||
command-description-ioc-get =
|
||||
Gets an instance of an IoC registration.
|
||||
command-description-loc-tryloc =
|
||||
Tries to get a localization string, returning null if unable.
|
||||
command-description-loc-loc =
|
||||
Gets a localization string, returning the unlocalized string if unable.
|
||||
command-description-physics-angular_velocity =
|
||||
Returns the angular velocity of the given entities.
|
||||
command-description-vars =
|
||||
Provides a list of all variables set in this session.
|
||||
command-description-any =
|
||||
Returns true if there's any values in the input, otherwise false.
|
||||
command-description-ArrowCommand =
|
||||
Assigns the input to a variable.
|
||||
command-description-isempty =
|
||||
Returns true if the input is empty, otherwise false.
|
||||
command-description-isnull =
|
||||
Returns true if the input is null, otherwise false.
|
||||
command-description-unique =
|
||||
Filters the input sequence for uniqueness, removing duplicate values.
|
||||
command-description-where =
|
||||
Given some input sequence IEnumerable<T>, takes a block of signature T -> bool that decides if each input value should be included in the output sequence.
|
||||
command-description-do =
|
||||
Backwards compatibility with BQL, applies the given old commands over the input sequence.
|
||||
command-description-named =
|
||||
Filters the input entities by their name, with the regex ^selector$.
|
||||
command-description-prototyped =
|
||||
Filters the input entities by their prototype.
|
||||
command-description-nearby =
|
||||
Creates a new list of all entities nearby the inputs within the given range.
|
||||
command-description-first =
|
||||
Returns the first entry of the given enumerable.
|
||||
command-description-splat =
|
||||
"Splats" a block, value, or variable, creating N copies of it in a list.
|
||||
command-description-val =
|
||||
Casts the given value, block, or variable to the given type. This is mostly a workaround for current limitations of variables.
|
||||
command-description-actor-controlled =
|
||||
Filters entities by whether or not they're actively controlled.
|
||||
command-description-actor-session =
|
||||
Returns the sessions associated with the input entities.
|
||||
command-description-physics-parent =
|
||||
Returns the parent(s) of the input entities.
|
||||
command-description-emplace =
|
||||
Runs the given block over it's inputs, with the input value placed into the variable $value within the block.
|
||||
Additionally breaks out $wx, $wy, $proto, $desc, $name, and $paused for entities.
|
||||
Can also have breakout values for other types, consult the documentation for that type for further info.
|
||||
command-description-AddCommand =
|
||||
Performs numeric addition.
|
||||
command-description-SubtractCommand =
|
||||
Performs numeric subtraction.
|
||||
command-description-MultiplyCommand =
|
||||
Performs numeric multiplication.
|
||||
command-description-DivideCommand =
|
||||
Performs numeric division.
|
||||
command-description-min =
|
||||
Returns the minimum of two values.
|
||||
command-description-max =
|
||||
Returns the maximum of two values.
|
||||
command-description-BitAndCommand =
|
||||
Performs bitwise AND.
|
||||
command-description-BitOrCommand =
|
||||
Performs bitwise OR.
|
||||
command-description-BitXorCommand =
|
||||
Performs bitwise XOR.
|
||||
command-description-neg =
|
||||
Negates the input.
|
||||
command-description-GreaterThanCommand =
|
||||
Performs a greater-than comparison, x > y.
|
||||
command-description-LessThanCommand =
|
||||
Performs a less-than comparison, x < y.
|
||||
command-description-GreaterThanOrEqualCommand =
|
||||
Performs a greater-than-or-equal comparison, x >= y.
|
||||
command-description-LessThanOrEqualCommand =
|
||||
Performs a less-than-or-equal comparison, x <= y.
|
||||
command-description-EqualCommand =
|
||||
Performs an equality comparison, returning true if the inputs are equal.
|
||||
command-description-NotEqualCommand =
|
||||
Performs an equality comparison, returning true if the inputs are not equal.
|
||||
|
||||
command-description-entitysystemupdateorder-tick =
|
||||
Lists the tick update order of entity systems.
|
||||
|
||||
command-description-entitysystemupdateorder-frame =
|
||||
Lists the frame update order of entity systems.
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using Robust.Client.Graphics;
|
||||
|
||||
namespace Robust.Client.Audio;
|
||||
@@ -8,13 +8,17 @@ public sealed class AudioStream
|
||||
public TimeSpan Length { get; }
|
||||
internal ClydeHandle? ClydeHandle { get; }
|
||||
public string? Name { get; }
|
||||
public string? Title { get; }
|
||||
public string? Artist { get; }
|
||||
public int ChannelCount { get; }
|
||||
|
||||
internal AudioStream(ClydeHandle handle, TimeSpan length, int channelCount, string? name = null)
|
||||
internal AudioStream(ClydeHandle handle, TimeSpan length, int channelCount, string? name = null, string? title = null, string? artist = null)
|
||||
{
|
||||
ClydeHandle = handle;
|
||||
Length = length;
|
||||
ChannelCount = channelCount;
|
||||
Name = name;
|
||||
Title = title;
|
||||
Artist = artist;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ internal sealed partial class ClientConsoleHost
|
||||
private int _completionSeq;
|
||||
|
||||
|
||||
public async Task<CompletionResult> GetCompletions(List<string> args, CancellationToken cancel)
|
||||
public async Task<CompletionResult> GetCompletions(List<string> args, string argStr, CancellationToken cancel)
|
||||
{
|
||||
// Last element is the command currently being typed. May be empty.
|
||||
|
||||
@@ -24,10 +24,10 @@ internal sealed partial class ClientConsoleHost
|
||||
if (delay > 0)
|
||||
await Task.Delay((int)(delay * 1000), cancel);
|
||||
|
||||
return await CalcCompletions(args, cancel);
|
||||
return await CalcCompletions(args, argStr, cancel);
|
||||
}
|
||||
|
||||
private Task<CompletionResult> CalcCompletions(List<string> args, CancellationToken cancel)
|
||||
private Task<CompletionResult> CalcCompletions(List<string> args, string argStr, CancellationToken cancel)
|
||||
{
|
||||
if (args.Count == 1)
|
||||
{
|
||||
@@ -44,10 +44,10 @@ internal sealed partial class ClientConsoleHost
|
||||
if (!AvailableCommands.TryGetValue(args[0], out var cmd))
|
||||
return Task.FromResult(CompletionResult.Empty);
|
||||
|
||||
return cmd.GetCompletionAsync(LocalShell, args.ToArray()[1..], cancel).AsTask();
|
||||
return cmd.GetCompletionAsync(LocalShell, args.ToArray()[1..], argStr, cancel).AsTask();
|
||||
}
|
||||
|
||||
private Task<CompletionResult> DoServerCompletions(List<string> args, CancellationToken cancel)
|
||||
private Task<CompletionResult> DoServerCompletions(List<string> args, string argStr, CancellationToken cancel)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<CompletionResult>();
|
||||
var cts = CancellationTokenSource.CreateLinkedTokenSource(cancel);
|
||||
@@ -62,6 +62,7 @@ internal sealed partial class ClientConsoleHost
|
||||
var msg = new MsgConCompletion
|
||||
{
|
||||
Args = args.ToArray(),
|
||||
ArgString = argStr,
|
||||
Seq = seq
|
||||
};
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ using Robust.Shared.Console;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Players;
|
||||
@@ -21,13 +22,13 @@ namespace Robust.Client.Console
|
||||
{
|
||||
public sealed class AddStringArgs : EventArgs
|
||||
{
|
||||
public string Text { get; }
|
||||
public FormattedMessage Text { get; }
|
||||
|
||||
public bool Local { get; }
|
||||
|
||||
public bool Error { get; }
|
||||
|
||||
public AddStringArgs(string text, bool local, bool error)
|
||||
public AddStringArgs(FormattedMessage text, bool local, bool error)
|
||||
{
|
||||
Text = text;
|
||||
Local = local;
|
||||
@@ -132,10 +133,17 @@ namespace Robust.Client.Console
|
||||
AddFormatted?.Invoke(this, new AddFormattedMessageArgs(message));
|
||||
}
|
||||
|
||||
public override void WriteLine(ICommonSession? session, FormattedMessage msg)
|
||||
{
|
||||
AddFormattedLine(msg);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void WriteError(ICommonSession? session, string text)
|
||||
{
|
||||
OutputText(text, true, true);
|
||||
var msg = new FormattedMessage();
|
||||
msg.AddText(text);
|
||||
OutputText(msg, true, true);
|
||||
}
|
||||
|
||||
public bool IsCmdServer(IConsoleCommand cmd)
|
||||
@@ -151,8 +159,13 @@ namespace Robust.Client.Console
|
||||
if (string.IsNullOrWhiteSpace(command))
|
||||
return;
|
||||
|
||||
WriteLine(null, "");
|
||||
var msg = new FormattedMessage();
|
||||
msg.PushColor(Color.Gold);
|
||||
msg.AddText("> " + command);
|
||||
msg.Pop();
|
||||
// echo the command locally
|
||||
WriteLine(null, "> " + command);
|
||||
OutputText(msg, true, false);
|
||||
|
||||
//Commands are processed locally and then sent to the server to be processed there again.
|
||||
var args = new List<string>();
|
||||
@@ -205,7 +218,9 @@ namespace Robust.Client.Console
|
||||
/// <inheritdoc />
|
||||
public override void WriteLine(ICommonSession? session, string text)
|
||||
{
|
||||
OutputText(text, true, false);
|
||||
var msg = new FormattedMessage();
|
||||
msg.AddText(text);
|
||||
OutputText(msg, true, false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -214,12 +229,12 @@ namespace Robust.Client.Console
|
||||
// We don't have anything to dispose.
|
||||
}
|
||||
|
||||
private void OutputText(string text, bool local, bool error)
|
||||
private void OutputText(FormattedMessage text, bool local, bool error)
|
||||
{
|
||||
AddString?.Invoke(this, new AddStringArgs(text, local, error));
|
||||
|
||||
var level = error ? LogLevel.Warning : LogLevel.Info;
|
||||
_conLogger.Log(level, text);
|
||||
_conLogger.Log(level, text.ToString());
|
||||
}
|
||||
|
||||
private void OnNetworkConnected(object? sender, NetChannelArgs netChannelArgs)
|
||||
@@ -229,7 +244,7 @@ namespace Robust.Client.Console
|
||||
|
||||
private void HandleConCmdAck(MsgConCmdAck msg)
|
||||
{
|
||||
OutputText("< " + msg.Text, false, msg.Error);
|
||||
OutputText(msg.Text, false, msg.Error);
|
||||
}
|
||||
|
||||
private void HandleConCmdReg(MsgConCmdReg msg)
|
||||
@@ -303,13 +318,14 @@ namespace Robust.Client.Console
|
||||
public async ValueTask<CompletionResult> GetCompletionAsync(
|
||||
IConsoleShell shell,
|
||||
string[] args,
|
||||
string argStr,
|
||||
CancellationToken cancel)
|
||||
{
|
||||
var host = (ClientConsoleHost)shell.ConsoleHost;
|
||||
var argsList = args.ToList();
|
||||
argsList.Insert(0, Command);
|
||||
|
||||
return await host.DoServerCompletions(argsList, cancel);
|
||||
return await host.DoServerCompletions(argsList, argStr, cancel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,10 +343,11 @@ namespace Robust.Client.Console
|
||||
public override async ValueTask<CompletionResult> GetCompletionAsync(
|
||||
IConsoleShell shell,
|
||||
string[] args,
|
||||
string argStr,
|
||||
CancellationToken cancel)
|
||||
{
|
||||
var host = (ClientConsoleHost)shell.ConsoleHost;
|
||||
return await host.DoServerCompletions(args.ToList(), cancel);
|
||||
return await host.DoServerCompletions(args.ToList(), argStr[">".Length..], cancel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -33,6 +33,7 @@ using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Profiling;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Replays;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
@@ -85,6 +86,7 @@ namespace Robust.Client
|
||||
[Dependency] private readonly IReplayLoadManager _replayLoader = default!;
|
||||
[Dependency] private readonly IReplayPlaybackManager _replayPlayback = default!;
|
||||
[Dependency] private readonly IReplayRecordingManagerInternal _replayRecording = default!;
|
||||
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
||||
|
||||
private IWebViewManagerHook? _webViewHook;
|
||||
|
||||
@@ -162,6 +164,7 @@ namespace Robust.Client
|
||||
// before prototype load.
|
||||
ProgramShared.FinishCheckBadFileExtensions(checkBadExtensions);
|
||||
|
||||
_reflectionManager.Initialize();
|
||||
_prototypeManager.Initialize();
|
||||
_prototypeManager.LoadDefaultPrototypes();
|
||||
_prototypeManager.ResolveResults();
|
||||
|
||||
@@ -167,40 +167,9 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
public bool TreeUpdateQueued { get; set; }
|
||||
|
||||
[DataField("layerDatums")]
|
||||
private List<PrototypeLayerData> LayerDatums
|
||||
{
|
||||
get
|
||||
{
|
||||
var layerDatums = new List<PrototypeLayerData>();
|
||||
foreach (var layer in Layers)
|
||||
{
|
||||
layerDatums.Add(layer.ToPrototypeData());
|
||||
}
|
||||
|
||||
return layerDatums;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == null) return;
|
||||
|
||||
Layers.Clear();
|
||||
foreach (var layerDatum in value)
|
||||
{
|
||||
AddLayer(layerDatum);
|
||||
}
|
||||
|
||||
_layerMapShared = true;
|
||||
|
||||
QueueUpdateRenderTree();
|
||||
QueueUpdateIsInert();
|
||||
}
|
||||
}
|
||||
|
||||
private RSI? _baseRsi;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("rsi", priority: 2)]
|
||||
public RSI? BaseRSI
|
||||
{
|
||||
get => _baseRsi;
|
||||
@@ -357,7 +326,16 @@ namespace Robust.Client.GameObjects
|
||||
if (layerDatums.Count != 0)
|
||||
{
|
||||
LayerMap.Clear();
|
||||
LayerDatums = layerDatums;
|
||||
Layers.Clear();
|
||||
foreach (var datum in layerDatums)
|
||||
{
|
||||
AddLayer(datum);
|
||||
}
|
||||
|
||||
_layerMapShared = true;
|
||||
|
||||
QueueUpdateRenderTree();
|
||||
QueueUpdateIsInert();
|
||||
}
|
||||
|
||||
UpdateLocalMatrix();
|
||||
|
||||
@@ -314,9 +314,9 @@ public sealed class AudioSystem : SharedAudioSystem
|
||||
return source != null;
|
||||
}
|
||||
|
||||
private PlayingStream CreateAndStartPlayingStream(IClydeAudioSource source, AudioParams? audioParams)
|
||||
private PlayingStream CreateAndStartPlayingStream(IClydeAudioSource source, AudioParams? audioParams, AudioStream stream)
|
||||
{
|
||||
ApplyAudioParams(audioParams, source);
|
||||
ApplyAudioParams(audioParams, source, stream);
|
||||
source.StartPlaying();
|
||||
var playing = new PlayingStream
|
||||
{
|
||||
@@ -365,7 +365,7 @@ public sealed class AudioSystem : SharedAudioSystem
|
||||
|
||||
source.SetGlobal();
|
||||
|
||||
return CreateAndStartPlayingStream(source, audioParams);
|
||||
return CreateAndStartPlayingStream(source, audioParams, stream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -416,7 +416,7 @@ public sealed class AudioSystem : SharedAudioSystem
|
||||
if (!source.SetPosition(worldPos))
|
||||
return Play(stream, fallbackCoordinates.Value, fallbackCoordinates.Value, audioParams);
|
||||
|
||||
var playing = CreateAndStartPlayingStream(source, audioParams);
|
||||
var playing = CreateAndStartPlayingStream(source, audioParams, stream);
|
||||
playing.TrackingEntity = entity;
|
||||
playing.TrackingFallbackCoordinates = fallbackCoordinates != EntityCoordinates.Invalid ? fallbackCoordinates : null;
|
||||
return playing;
|
||||
@@ -469,7 +469,7 @@ public sealed class AudioSystem : SharedAudioSystem
|
||||
return null;
|
||||
}
|
||||
|
||||
var playing = CreateAndStartPlayingStream(source, audioParams);
|
||||
var playing = CreateAndStartPlayingStream(source, audioParams, stream);
|
||||
playing.TrackingCoordinates = coordinates;
|
||||
playing.TrackingFallbackCoordinates = fallbackCoordinates != EntityCoordinates.Invalid ? fallbackCoordinates : null;
|
||||
return playing;
|
||||
@@ -493,7 +493,7 @@ public sealed class AudioSystem : SharedAudioSystem
|
||||
return null;
|
||||
}
|
||||
|
||||
private void ApplyAudioParams(AudioParams? audioParams, IClydeAudioSource source)
|
||||
private void ApplyAudioParams(AudioParams? audioParams, IClydeAudioSource source, AudioStream audio)
|
||||
{
|
||||
if (!audioParams.HasValue)
|
||||
return;
|
||||
@@ -508,8 +508,12 @@ public sealed class AudioSystem : SharedAudioSystem
|
||||
source.SetRolloffFactor(audioParams.Value.RolloffFactor);
|
||||
source.SetMaxDistance(audioParams.Value.MaxDistance);
|
||||
source.SetReferenceDistance(audioParams.Value.ReferenceDistance);
|
||||
source.SetPlaybackPosition(audioParams.Value.PlayOffsetSeconds);
|
||||
source.IsLooping = audioParams.Value.Loop;
|
||||
|
||||
// TODO clamp the offset inside of SetPlaybackPosition() itself.
|
||||
var offset = audioParams.Value.PlayOffsetSeconds;
|
||||
offset = Math.Clamp(offset, 0f, (float) audio.Length.TotalSeconds);
|
||||
source.SetPlaybackPosition(offset);
|
||||
}
|
||||
|
||||
public sealed class PlayingStream : IPlayingAudioStream
|
||||
|
||||
@@ -16,4 +16,9 @@ internal sealed class GridRenderingSystem : EntitySystem
|
||||
{
|
||||
_clyde.RegisterGridEcsEvents();
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
_clyde.ShutdownGridEcsEvents();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -745,10 +745,6 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
}
|
||||
|
||||
var contQuery = _entities.GetEntityQuery<ContainerManagerComponent>();
|
||||
var physicsQuery = _entities.GetEntityQuery<PhysicsComponent>();
|
||||
var fixturesQuery = _entities.GetEntityQuery<FixturesComponent>();
|
||||
var broadQuery = _entities.GetEntityQuery<BroadphaseComponent>();
|
||||
var queuedBroadphaseUpdates = new List<(EntityUid, TransformComponent)>(enteringPvs);
|
||||
|
||||
// Apply entity states.
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
var cache = IoCManager.Resolve<IResourceCache>();
|
||||
_font = new VectorFont(cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
_font = new VectorFont(cache.GetResource<FontResource>("/EngineFonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
_lineHeight = _font.GetLineHeight(1);
|
||||
|
||||
_gameStateManager.GameStateApplied += HandleGameStateApplied;
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
var cache = IoCManager.Resolve<IResourceCache>();
|
||||
_font = new VectorFont(cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
_font = new VectorFont(cache.GetResource<FontResource>("/EngineFonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
|
||||
_gameStateManager.GameStateApplied += HandleGameStateApplied;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Robust.Client.Graphics.Audio
|
||||
readSamples += read;
|
||||
}
|
||||
|
||||
return new OggVorbisData(totalSamples, sampleRate, channels, buffer);
|
||||
return new OggVorbisData(totalSamples, sampleRate, channels, buffer, vorbis.Tags.Title, vorbis.Tags.Artist);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,13 +37,17 @@ namespace Robust.Client.Graphics.Audio
|
||||
public readonly long SampleRate;
|
||||
public readonly long Channels;
|
||||
public readonly ReadOnlyMemory<float> Data;
|
||||
public readonly string Title;
|
||||
public readonly string Artist;
|
||||
|
||||
public OggVorbisData(long totalSamples, long sampleRate, long channels, ReadOnlyMemory<float> data)
|
||||
public OggVorbisData(long totalSamples, long sampleRate, long channels, ReadOnlyMemory<float> data, string title, string artist)
|
||||
{
|
||||
TotalSamples = totalSamples;
|
||||
SampleRate = sampleRate;
|
||||
Channels = channels;
|
||||
Data = data;
|
||||
Title = title;
|
||||
Artist = artist;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,7 +329,7 @@ namespace Robust.Client.Graphics.Audio
|
||||
var handle = new ClydeHandle(_audioSampleBuffers.Count);
|
||||
_audioSampleBuffers.Add(new LoadedAudioSample(buffer));
|
||||
var length = TimeSpan.FromSeconds(vorbis.TotalSamples / (double) vorbis.SampleRate);
|
||||
return new AudioStream(handle, length, (int) vorbis.Channels, name);
|
||||
return new AudioStream(handle, length, (int) vorbis.Channels, name, vorbis.Title, vorbis.Artist);
|
||||
}
|
||||
|
||||
public AudioStream LoadAudioWav(Stream stream, string? name = null)
|
||||
|
||||
@@ -176,6 +176,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_entityManager.EventBus.SubscribeEvent<GridModifiedEvent>(EventSource.Local, this, _updateOnGridModified);
|
||||
}
|
||||
|
||||
public void ShutdownGridEcsEvents()
|
||||
{
|
||||
_entityManager.EventBus.UnsubscribeEvent<TileChangedEvent>(EventSource.Local, this);
|
||||
_entityManager.EventBus.UnsubscribeEvent<GridStartupEvent>(EventSource.Local, this);
|
||||
_entityManager.EventBus.UnsubscribeEvent<GridRemovalEvent>(EventSource.Local, this);
|
||||
_entityManager.EventBus.UnsubscribeEvent<GridModifiedEvent>(EventSource.Local, this);
|
||||
}
|
||||
|
||||
private void GLInitBindings(bool gles)
|
||||
{
|
||||
_glBindingsContext = _glContext!.BindingsContext;
|
||||
|
||||
@@ -88,6 +88,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void ShutdownGridEcsEvents()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void SetWindowTitle(string title)
|
||||
{
|
||||
// Nada.
|
||||
|
||||
@@ -12,9 +12,9 @@ void main()
|
||||
|
||||
lowp vec4 COLOR;
|
||||
|
||||
// [SHADER_CODE]
|
||||
|
||||
lowp vec3 lightSample = texture2D(lightMap, Pos).rgb;
|
||||
|
||||
// [SHADER_CODE]
|
||||
|
||||
gl_FragColor = zAdjustResult(COLOR * VtxModulate * vec4(lightSample, 1.0));
|
||||
}
|
||||
|
||||
@@ -65,6 +65,8 @@ namespace Robust.Client.Graphics
|
||||
|
||||
void RegisterGridEcsEvents();
|
||||
|
||||
void ShutdownGridEcsEvents();
|
||||
|
||||
void RunOnWindowThread(Action action);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -131,12 +131,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 +226,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;
|
||||
@@ -255,7 +265,12 @@ public sealed partial class ReplayLoadManager
|
||||
// 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.");
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.GameStates;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -64,6 +66,18 @@ public sealed partial class ReplayLoadManager
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception("Missing metadata component");
|
||||
if (!entState.ComponentChanges.HasContents)
|
||||
{
|
||||
// This shouldn't be possible, yet it has happened?
|
||||
// TODO this should probably also throw an exception.
|
||||
_sawmill.Error($"Encountered blank entity state? Entity: {entState.Uid}. Last modified: {entState.EntityLastModified}. Attempting to continue.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!_confMan.GetCVar(CVars.ReplayIgnoreErrors))
|
||||
throw new MissingMetadataException(entState.Uid);
|
||||
|
||||
_sawmill.Error($"Missing metadata component. Entity: {entState.Uid}. Last modified: {entState.EntityLastModified}.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ using Robust.Shared.Utility;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Replays;
|
||||
using static Robust.Shared.Replays.ReplayConstants;
|
||||
|
||||
@@ -136,9 +137,21 @@ public sealed partial class ReplayLoadManager
|
||||
if (data == null)
|
||||
throw new Exception("Failed to load yaml metadata");
|
||||
|
||||
TimeSpan duration;
|
||||
var finalData = LoadYamlFinalMetadata(fileReader);
|
||||
if (finalData == null)
|
||||
throw new Exception("Failed to load final yaml metadata");
|
||||
{
|
||||
var msg = "Failed to load final yaml metadata";
|
||||
if (!_confMan.GetCVar(CVars.ReplayIgnoreErrors))
|
||||
throw new Exception(msg);
|
||||
|
||||
_sawmill.Error(msg);
|
||||
duration = TimeSpan.FromDays(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
duration = TimeSpan.Parse(((ValueDataNode) finalData[MetaFinalKeyDuration]).Value);
|
||||
}
|
||||
|
||||
var typeHash = Convert.FromHexString(((ValueDataNode) data[MetaKeyTypeHash]).Value);
|
||||
var stringHash = Convert.FromHexString(((ValueDataNode) data[MetaKeyStringHash]).Value);
|
||||
@@ -146,7 +159,6 @@ public sealed partial class ReplayLoadManager
|
||||
var timeBaseTick = ((ValueDataNode) data[MetaKeyBaseTick]).Value;
|
||||
var timeBaseTimespan = ((ValueDataNode) data[MetaKeyBaseTime]).Value;
|
||||
var clientSide = bool.Parse(((ValueDataNode) data[MetaKeyIsClientRecording]).Value);
|
||||
var duration = TimeSpan.Parse(((ValueDataNode) finalData[MetaFinalKeyDuration]).Value);
|
||||
|
||||
if (!typeHash.SequenceEqual(_serializer.GetSerializableTypesHash()))
|
||||
throw new Exception($"{nameof(IRobustSerializer)} hashes do not match. Loading replays using a bad replay-client version?");
|
||||
|
||||
@@ -97,6 +97,8 @@ internal sealed partial class ReplayPlaybackManager
|
||||
|
||||
if (message is EntityEventArgs args)
|
||||
_entMan.DispatchReceivedNetworkMsg(args);
|
||||
else if (_warned.Add(message.GetType()))
|
||||
_sawmill.Error($"Unhandled replay message: {message.GetType()}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ internal sealed partial class ReplayPlaybackManager : IReplayPlaybackManager
|
||||
|
||||
private bool _initialized;
|
||||
private ISawmill _sawmill = default!;
|
||||
private HashSet<Type> _warned = new();
|
||||
|
||||
public bool Playing
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -40,5 +41,9 @@ namespace Robust.Client.ResourceManagement
|
||||
// Resource load callbacks so content can hook stuff like click maps.
|
||||
event Action<TextureLoadedEventArgs> OnRawTextureLoaded;
|
||||
event Action<RsiLoadedEventArgs> OnRsiLoaded;
|
||||
|
||||
IClyde Clyde { get; }
|
||||
IClydeAudio ClydeAudio { get; }
|
||||
IFontManager FontManager { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,9 @@ namespace Robust.Client.ResourceManagement
|
||||
{
|
||||
internal partial class ResourceCache
|
||||
{
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
[field: Dependency] public IClyde Clyde { get; } = default!;
|
||||
[field: Dependency] public IClydeAudio ClydeAudio { get; } = default!;
|
||||
[field: Dependency] public IFontManager FontManager { get; } = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
|
||||
@@ -70,7 +72,7 @@ namespace Robust.Client.ResourceManagement
|
||||
|
||||
try
|
||||
{
|
||||
TextureResource.LoadTexture(_clyde, data);
|
||||
TextureResource.LoadTexture(Clyde, data);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -198,7 +200,7 @@ namespace Robust.Client.ResourceManagement
|
||||
|
||||
void FinalizeMetaAtlas(int toIndex, Image<Rgba32> sheet)
|
||||
{
|
||||
var atlas = _clyde.LoadTextureFromImage(sheet);
|
||||
var atlas = Clyde.LoadTextureFromImage(sheet);
|
||||
for (int i = finalized + 1; i <= toIndex; i++)
|
||||
{
|
||||
var rsi = rsiList[i];
|
||||
|
||||
@@ -20,14 +20,13 @@ namespace Robust.Client.ResourceManagement
|
||||
|
||||
using (var fileStream = cache.ContentFileRead(path))
|
||||
{
|
||||
var clyde = IoCManager.Resolve<IClydeAudio>();
|
||||
if (path.Extension == "ogg")
|
||||
{
|
||||
AudioStream = clyde.LoadAudioOggVorbis(fileStream, path.ToString());
|
||||
AudioStream = cache.ClydeAudio.LoadAudioOggVorbis(fileStream, path.ToString());
|
||||
}
|
||||
else if (path.Extension == "wav")
|
||||
{
|
||||
AudioStream = clyde.LoadAudioWav(fileStream, path.ToString());
|
||||
AudioStream = cache.ClydeAudio.LoadAudioWav(fileStream, path.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Robust.Client.ResourceManagement
|
||||
|
||||
using (stream)
|
||||
{
|
||||
FontFaceHandle = IoCManager.Resolve<IFontManagerInternal>().Load(stream);
|
||||
FontFaceHandle = ((IFontManagerInternal)cache.FontManager).Load(stream);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,12 +34,10 @@ namespace Robust.Client.ResourceManagement
|
||||
|
||||
public override void Load(IResourceCache cache, ResPath path)
|
||||
{
|
||||
var clyde = IoCManager.Resolve<IClyde>();
|
||||
|
||||
var loadStepData = new LoadStepData {Path = path};
|
||||
LoadPreTexture(cache, loadStepData);
|
||||
|
||||
loadStepData.AtlasTexture = clyde.LoadTextureFromImage(
|
||||
loadStepData.AtlasTexture = cache.Clyde.LoadTextureFromImage(
|
||||
loadStepData.AtlasSheet,
|
||||
loadStepData.Path.ToString());
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Graphics.Clyde;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -27,8 +28,7 @@ namespace Robust.Client.ResourceManagement
|
||||
ParsedShader = ShaderParser.Parse(reader, cache);
|
||||
}
|
||||
|
||||
var clyde = IoCManager.Resolve<IClydeInternal>();
|
||||
ClydeHandle = clyde.LoadShader(ParsedShader, path.ToString());
|
||||
ClydeHandle = ((IClydeInternal)cache.Clyde).LoadShader(ParsedShader, path.ToString());
|
||||
}
|
||||
|
||||
public override void Reload(IResourceCache cache, ResPath path, CancellationToken ct = default)
|
||||
@@ -57,8 +57,7 @@ namespace Robust.Client.ResourceManagement
|
||||
}
|
||||
}
|
||||
|
||||
var clyde = IoCManager.Resolve<IClydeInternal>();
|
||||
clyde.ReloadShader(ClydeHandle, ParsedShader);
|
||||
((IClydeInternal)cache.Clyde).ReloadShader(ClydeHandle, ParsedShader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,6 @@ namespace Robust.Client.ResourceManagement
|
||||
|
||||
public override void Load(IResourceCache cache, ResPath path)
|
||||
{
|
||||
var clyde = IoCManager.Resolve<IClyde>();
|
||||
|
||||
if (path.Directory.Filename.EndsWith(".rsi"))
|
||||
{
|
||||
Logger.WarningS(
|
||||
@@ -33,7 +31,7 @@ namespace Robust.Client.ResourceManagement
|
||||
var data = new LoadStepData {Path = path};
|
||||
|
||||
LoadPreTexture(cache, data);
|
||||
LoadTexture(clyde, data);
|
||||
LoadTexture(cache.Clyde, data);
|
||||
LoadFinish(cache, data);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Control xmlns="https://spacestation14.io"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<OutputPanel Name="Output" VerticalExpand="True">
|
||||
<OutputPanel Name="Output" VerticalExpand="True" StyleClasses="monospace">
|
||||
<OutputPanel.StyleBoxOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#25252add"
|
||||
ContentMarginLeftOverride="3" ContentMarginRightOverride="3"
|
||||
|
||||
@@ -90,7 +90,7 @@ public sealed partial class DebugConsole
|
||||
|
||||
private async void TypeUpdateCompletions(bool fullUpdate)
|
||||
{
|
||||
var (args, _, _) = CalcTypingArgs();
|
||||
var (args, _, _, str) = CalcTypingArgs();
|
||||
|
||||
if (args.Count != _compParamCount)
|
||||
{
|
||||
@@ -101,7 +101,7 @@ public sealed partial class DebugConsole
|
||||
if (fullUpdate)
|
||||
{
|
||||
var seq = ++_compSeqSend;
|
||||
var task = _consoleHost.GetCompletions(args, _compCancel.Token);
|
||||
var task = _consoleHost.GetCompletions(args, str, _compCancel.Token);
|
||||
|
||||
if (!task.IsCompleted)
|
||||
{
|
||||
@@ -140,7 +140,7 @@ public sealed partial class DebugConsole
|
||||
if (_compCurResult == null)
|
||||
return;
|
||||
|
||||
var (_, curTyping, _) = CalcTypingArgs();
|
||||
var (_, curTyping, _, _) = CalcTypingArgs();
|
||||
|
||||
var curSelected = _compFiltered?.Length > 0 ? _compFiltered[_compSelected] : default;
|
||||
_compFiltered = FilterCompletions(_compCurResult.Options, curTyping);
|
||||
@@ -168,7 +168,7 @@ public sealed partial class DebugConsole
|
||||
|
||||
DebugTools.AssertNotNull(_compCurResult);
|
||||
|
||||
var (_, _, endRange) = CalcTypingArgs();
|
||||
var (_, _, endRange, _) = CalcTypingArgs();
|
||||
|
||||
var offset = CommandBar.GetOffsetAtIndex(endRange.start);
|
||||
// Logger.Debug($"Offset: {offset}");
|
||||
@@ -231,7 +231,7 @@ public sealed partial class DebugConsole
|
||||
}
|
||||
}
|
||||
|
||||
private (List<string> args, string curTyping, (int start, int end) lastRange) CalcTypingArgs()
|
||||
private (List<string> args, string curTyping, (int start, int end) lastRange, string argStr) CalcTypingArgs()
|
||||
{
|
||||
var cursor = CommandBar.CursorPosition;
|
||||
// Don't consider text after the cursor.
|
||||
@@ -252,7 +252,7 @@ public sealed partial class DebugConsole
|
||||
else
|
||||
lastRange = (cursor, cursor);
|
||||
|
||||
return (args, args[^1], lastRange);
|
||||
return (args, args[^1], lastRange, text.ToString());
|
||||
}
|
||||
|
||||
private CompletionOption[] FilterCompletions(IEnumerable<CompletionOption> completions, string curTyping)
|
||||
@@ -270,7 +270,7 @@ public sealed partial class DebugConsole
|
||||
{
|
||||
// Figure out typing word so we know how much to replace.
|
||||
var (completion, _, completionFlags) = _compFiltered[_compSelected];
|
||||
var (_, _, lastRange) = CalcTypingArgs();
|
||||
var (_, _, lastRange, _) = CalcTypingArgs();
|
||||
|
||||
// Replace the full word from the start.
|
||||
// This means that letter casing will match the completion suggestion.
|
||||
|
||||
@@ -23,9 +23,9 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
/// <summary>
|
||||
/// Write a line with a specific color to the console window.
|
||||
/// </summary>
|
||||
void AddLine(string text, Color color);
|
||||
void AddLine(FormattedMessage text, Color color);
|
||||
|
||||
void AddLine(string text);
|
||||
void AddLine(FormattedMessage text);
|
||||
|
||||
void AddFormattedLine(FormattedMessage message);
|
||||
|
||||
@@ -108,7 +108,7 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
|
||||
private Color DetermineColor(bool local, bool error)
|
||||
{
|
||||
return Color.White;
|
||||
return error ? Color.Red : Color.White;
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
@@ -136,16 +136,16 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
_flushHistoryToDisk();
|
||||
}
|
||||
|
||||
public void AddLine(string text, Color color)
|
||||
public void AddLine(FormattedMessage text, Color color)
|
||||
{
|
||||
var formatted = new FormattedMessage(3);
|
||||
formatted.PushColor(color);
|
||||
formatted.AddText(text);
|
||||
formatted.AddMessage(text);
|
||||
formatted.Pop();
|
||||
AddFormattedLine(formatted);
|
||||
}
|
||||
|
||||
public void AddLine(string text)
|
||||
public void AddLine(FormattedMessage text)
|
||||
{
|
||||
AddLine(text, Color.White);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using DiscordRPC;
|
||||
using DiscordRPC.Logging;
|
||||
using Robust.Shared;
|
||||
@@ -25,13 +27,17 @@ namespace Robust.Client.Utility
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
var state = _loc.GetString("discord-rpc-in-main-menu");
|
||||
var largeImageKey = _configurationManager.GetCVar(CVars.DiscordRichPresenceSecondIconId);
|
||||
var largeImageText = _loc.GetString("discord-rpc-in-main-menu-logo-text");
|
||||
|
||||
_defaultPresence = new()
|
||||
{
|
||||
State = _loc.GetString("discord-rpc-in-main-menu"),
|
||||
State = Truncate(state, 128),
|
||||
Assets = new Assets
|
||||
{
|
||||
LargeImageKey = "logo",
|
||||
LargeImageText = "I think coolsville SUCKS"
|
||||
LargeImageKey = Truncate(largeImageKey, 32),
|
||||
LargeImageText = Truncate(largeImageText, 128),
|
||||
}
|
||||
};
|
||||
_configurationManager.OnValueChanged(CVars.DiscordEnabled, newValue =>
|
||||
@@ -95,18 +101,60 @@ namespace Robust.Client.Utility
|
||||
|
||||
public void Update(string serverName, string username, string maxUsers, string users)
|
||||
{
|
||||
_activePresence = new RichPresence
|
||||
if (_client == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
Details = _loc.GetString("discord-rpc-on-server", ("servername", serverName)),
|
||||
State = _loc.GetString("discord-rpc-players", ("players", users), ("maxplayers", maxUsers)),
|
||||
Assets = new Assets
|
||||
var details = _loc.GetString("discord-rpc-on-server", ("servername", serverName));
|
||||
var state = _loc.GetString("discord-rpc-players", ("players", users), ("maxplayers", maxUsers));
|
||||
var largeImageText = _loc.GetString("discord-rpc-character", ("username", username));
|
||||
var largeImageKey = _configurationManager.GetCVar(CVars.DiscordRichPresenceMainIconId);
|
||||
var smallImageKey = _configurationManager.GetCVar(CVars.DiscordRichPresenceSecondIconId);
|
||||
|
||||
// Strings are limited by byte count. See the setters in RichPresence. Hence the truncate calls.
|
||||
_activePresence = new RichPresence
|
||||
{
|
||||
LargeImageKey = "devstation",
|
||||
LargeImageText = _loc.GetString("discord-rpc-character", ("username", username)),
|
||||
SmallImageKey = "logo"
|
||||
}
|
||||
};
|
||||
_client?.SetPresence(_activePresence);
|
||||
Details = Truncate(details, 128),
|
||||
State = Truncate(state, 128),
|
||||
Assets = new Assets
|
||||
{
|
||||
LargeImageKey = Truncate(largeImageKey, 32),
|
||||
LargeImageText = Truncate(largeImageText, 128),
|
||||
SmallImageKey = Truncate(smallImageKey, 32)
|
||||
}
|
||||
};
|
||||
_client.SetPresence(_activePresence);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_client.Logger.Error($"Caught exception while updating discord rich presence. Exception:\n{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private string Truncate(string value, int bytes, string postfix = "...")
|
||||
=> Truncate(value, bytes, postfix, Encoding.UTF8);
|
||||
|
||||
/// <summary>
|
||||
/// Truncate strings down to some minimum byte count. If the string gets truncated, it will have the postfix appended.
|
||||
/// </summary>
|
||||
private string Truncate(string value, int bytes, string postfix, Encoding encoding)
|
||||
{
|
||||
value = value.Trim();
|
||||
var output = value;
|
||||
|
||||
// Theres probably a better way of doing this, but I don't know how.
|
||||
// If this wasn't a crude hack this function should
|
||||
while (encoding.GetByteCount(output) > bytes)
|
||||
{
|
||||
if (value.Length == 0)
|
||||
return string.Empty;
|
||||
|
||||
value = value.Substring(0, value.Length - 1);
|
||||
output = value + postfix;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public void ClearPresence()
|
||||
|
||||
@@ -88,7 +88,7 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
var stringified = PrettyPrint.PrintUserFacingWithType(obj, out var typeStringified);
|
||||
if (typeStringified != "")
|
||||
{
|
||||
//var smallFont = new VectorFont(_resourceCache.GetResource<FontResource>("/Fonts/CALIBRI.TTF"), 10);
|
||||
//var smallFont = new VectorFont(_resourceCache.GetResource<FontResource>("/EngineFonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
// Custom ToString() implementation.
|
||||
var headBox = new BoxContainer
|
||||
{
|
||||
|
||||
@@ -121,7 +121,7 @@ namespace Robust.Client.ViewVariables
|
||||
}
|
||||
|
||||
//var smallFont =
|
||||
// new VectorFont(IoCManager.Resolve<IResourceCache>().GetResource<FontResource>("/Fonts/CALIBRI.TTF"),
|
||||
// new VectorFont(IoCManager.Resolve<IResourceCache>().GetResource<FontResource>("/EngineFonts/NotoSans/NotoSans-Regular.ttf"),
|
||||
// 10);
|
||||
|
||||
// Custom ToString() implementation.
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace Robust.Client.ViewVariables
|
||||
};
|
||||
VBox.AddChild(BottomContainer);
|
||||
|
||||
//var smallFont = new VectorFont(_resourceCache.GetResource<FontResource>("/Fonts/CALIBRI.TTF"), 10);
|
||||
//var smallFont = new VectorFont(_resourceCache.GetResource<FontResource>("/EngineFonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
|
||||
_bottomLabel = new Label
|
||||
{
|
||||
|
||||
@@ -30,7 +30,9 @@ using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Profiling;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Replays;
|
||||
using Robust.Shared.Toolshed;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Threading;
|
||||
@@ -101,6 +103,7 @@ namespace Robust.Server
|
||||
[Dependency] private readonly IReplayRecordingManagerInternal _replay = default!;
|
||||
[Dependency] private readonly IGamePrototypeLoadManager _protoLoadMan = default!;
|
||||
[Dependency] private readonly NetworkResourceManager _netResMan = default!;
|
||||
[Dependency] private readonly IReflectionManager _refMan = default!;
|
||||
|
||||
private readonly Stopwatch _uptimeStopwatch = new();
|
||||
|
||||
@@ -367,7 +370,9 @@ namespace Robust.Server
|
||||
_prototype.Initialize();
|
||||
_prototype.LoadDefaultPrototypes();
|
||||
_prototype.ResolveResults();
|
||||
_refMan.Initialize();
|
||||
|
||||
IoCManager.Resolve<ToolshedManager>().Initialize();
|
||||
_consoleHost.Initialize();
|
||||
_entityManager.Startup();
|
||||
_mapManager.Startup();
|
||||
|
||||
@@ -1,162 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Pidgin;
|
||||
using Robust.Shared.GameObjects;
|
||||
using static Pidgin.Parser;
|
||||
using static Pidgin.Parser<char>;
|
||||
|
||||
namespace Robust.Server.Bql
|
||||
{
|
||||
public partial class BqlQueryManager
|
||||
{
|
||||
private readonly Dictionary<Type, Parser<char, BqlQuerySelectorParsed>> _parsers = new();
|
||||
private Parser<char, BqlQuerySelectorParsed> _allQuerySelectors = default!;
|
||||
private Parser<char, (IEnumerable<BqlQuerySelectorParsed>, string)> SimpleQuery => Parser.Map((en, _, rest) => (en, rest), SkipWhitespaces.Then(_allQuerySelectors).Many(), String("do").Then(SkipWhitespaces), Any.ManyString());
|
||||
|
||||
private static Parser<char, string> Word => OneOf(LetterOrDigit, Char('_')).ManyString();
|
||||
|
||||
private static Parser<char, object> Objectify<T>(Parser<char, T> inp)
|
||||
{
|
||||
return Parser.Map(x => (object) x!, inp);
|
||||
}
|
||||
|
||||
private struct SubstitutionData
|
||||
{
|
||||
public string Name;
|
||||
|
||||
public SubstitutionData(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
|
||||
private static Parser<char, SubstitutionData> Substitution =>
|
||||
Try(Char('$').Then(OneOf(Uppercase, Char('_')).ManyString()))
|
||||
.MapWithInput((x, _) => new SubstitutionData(x.ToString()));
|
||||
|
||||
private static Parser<char, int> Integer =>
|
||||
Try(Int(10));
|
||||
|
||||
private static Parser<char, object> SubstitutableInteger =>
|
||||
Objectify(Integer).Or(Objectify(Try(Substitution)));
|
||||
|
||||
private static Parser<char, double> Float =>
|
||||
Try(Real);
|
||||
|
||||
private static Parser<char, object> SubstitutableFloat =>
|
||||
Objectify(Float).Or(Objectify(Try(Substitution)));
|
||||
|
||||
private static Parser<char, double> Percentage =>
|
||||
Try(Real).Before(Char('%'));
|
||||
|
||||
private static Parser<char, object> SubstitutablePercentage =>
|
||||
Objectify(Percentage).Or(Objectify(Try(Substitution)));
|
||||
|
||||
private static Parser<char, EntityUid> EntityId =>
|
||||
Try(Parser.Map(x => new EntityUid(x), Int(10)));
|
||||
|
||||
private static Parser<char, object> SubstitutableEntityId =>
|
||||
Objectify(EntityId).Or(Objectify(Try(Substitution)));
|
||||
|
||||
private Parser<char, Type> Component =>
|
||||
Try(Parser.Map(t => _componentFactory.GetRegistration(t).Type, Word));
|
||||
|
||||
private Parser<char, object> SubstitutableComponent =>
|
||||
Objectify(Component).Or(Objectify(Try(Substitution)));
|
||||
|
||||
private static Parser<char, string> QuotedString =>
|
||||
OneOf(Try(Char('"').Then(OneOf(new []
|
||||
{
|
||||
AnyCharExcept("\"")
|
||||
}).ManyString().Before(Char('"')))), Try(Word));
|
||||
|
||||
private static Parser<char, object> SubstitutableString =>
|
||||
Objectify(QuotedString).Or(Objectify(Try(Substitution)));
|
||||
|
||||
// thing to make sure it all compiles.
|
||||
[UsedImplicitly]
|
||||
private Parser<char, object> TypeSystemCheck =>
|
||||
OneOf(new[]
|
||||
{
|
||||
Objectify(Integer),
|
||||
Objectify(Percentage),
|
||||
Objectify(EntityId),
|
||||
Objectify(Component),
|
||||
Objectify(Float),
|
||||
Objectify(QuotedString)
|
||||
});
|
||||
|
||||
private Parser<char, BqlQuerySelectorParsed> BuildBqlQueryParser(BqlQuerySelector inst)
|
||||
{
|
||||
if (inst.Arguments.Length == 0)
|
||||
{
|
||||
return Parser.Map(_ => new BqlQuerySelectorParsed(new List<object>(), inst.Token, false), SkipWhitespaces);
|
||||
}
|
||||
|
||||
List<Parser<char, object>> argsParsers = new();
|
||||
|
||||
foreach (var (arg, _) in inst.Arguments.Select((x, i) => (x, i)))
|
||||
{
|
||||
List<Parser<char, object>> choices = new();
|
||||
if ((arg & QuerySelectorArgument.String) == QuerySelectorArgument.String)
|
||||
{
|
||||
choices.Add(Try(SubstitutableString.Before(SkipWhitespaces).Labelled("string argument")));
|
||||
}
|
||||
if ((arg & QuerySelectorArgument.Component) == QuerySelectorArgument.Component)
|
||||
{
|
||||
choices.Add(Try(SubstitutableComponent.Before(SkipWhitespaces).Labelled("component argument")));
|
||||
}
|
||||
if ((arg & QuerySelectorArgument.EntityId) == QuerySelectorArgument.EntityId)
|
||||
{
|
||||
choices.Add(Try(SubstitutableEntityId.Before(SkipWhitespaces).Labelled("entity ID argument")));
|
||||
}
|
||||
if ((arg & QuerySelectorArgument.Integer) == QuerySelectorArgument.Integer)
|
||||
{
|
||||
choices.Add(Try(SubstitutableInteger.Before(SkipWhitespaces).Labelled("integer argument")));
|
||||
}
|
||||
if ((arg & QuerySelectorArgument.Percentage) == QuerySelectorArgument.Percentage)
|
||||
{
|
||||
choices.Add(Try(SubstitutablePercentage.Before(SkipWhitespaces).Labelled("percentage argument")));
|
||||
}
|
||||
if ((arg & QuerySelectorArgument.Float) == QuerySelectorArgument.Float)
|
||||
{
|
||||
choices.Add(Try(SubstitutableFloat.Before(SkipWhitespaces).Labelled("float argument")));
|
||||
}
|
||||
|
||||
argsParsers.Add(OneOf(choices));
|
||||
}
|
||||
|
||||
Parser<char, List<object>> finalParser = argsParsers[0].Map(x => new List<object> { x });
|
||||
|
||||
for (var i = 1; i < argsParsers.Count; i++)
|
||||
{
|
||||
finalParser = finalParser.Then(argsParsers[i], (list, o) =>
|
||||
{
|
||||
list.Add(o);
|
||||
return list;
|
||||
}).Labelled("arguments");
|
||||
}
|
||||
|
||||
return Parser.Map(args => new BqlQuerySelectorParsed(args, inst.Token, false), finalParser);
|
||||
}
|
||||
|
||||
private void DoParserSetup()
|
||||
{
|
||||
foreach (var inst in _instances)
|
||||
{
|
||||
_parsers.Add(inst.GetType(), BuildBqlQueryParser(inst));
|
||||
}
|
||||
|
||||
_allQuerySelectors = Parser.Map((a,b) => (a,b), Try(String("not").Before(Char(' '))).Optional(), OneOf(_instances.Select(x =>
|
||||
Try(String(x.Token).Before(Char(' '))))).Then(tok =>
|
||||
_parsers[_queriesByToken[tok].GetType()])
|
||||
).Map(pair =>
|
||||
{
|
||||
pair.b.Inverted = pair.a.HasValue;
|
||||
return pair.b;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Pidgin;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Server.Bql
|
||||
{
|
||||
public partial class BqlQueryManager
|
||||
{
|
||||
public (IEnumerable<EntityUid>, string) SimpleParseAndExecute(string query)
|
||||
{
|
||||
var parsed = SimpleQuery.Parse(query);
|
||||
if (parsed.Success)
|
||||
{
|
||||
var selectors = parsed.Value.Item1.ToArray();
|
||||
if (selectors.Length == 0)
|
||||
{
|
||||
return (_entityManager.GetEntities(), parsed.Value.Item2);
|
||||
}
|
||||
|
||||
var entities = _queriesByToken[selectors[0].Token]
|
||||
.DoInitialSelection(selectors[0].Arguments, selectors[0].Inverted, _entityManager);
|
||||
|
||||
foreach (var sel in selectors[1..])
|
||||
{
|
||||
entities = _queriesByToken[sel.Token].DoSelection(entities, sel.Arguments, sel.Inverted, _entityManager);
|
||||
}
|
||||
|
||||
return (entities, parsed.Value.Item2);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception(parsed.Error!.RenderErrorMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Server.Bql
|
||||
{
|
||||
public sealed partial class BqlQueryManager : IBqlQueryManager
|
||||
{
|
||||
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
||||
[Dependency] private readonly IComponentFactory _componentFactory = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
private readonly List<BqlQuerySelector> _instances = new();
|
||||
private readonly Dictionary<string, BqlQuerySelector> _queriesByToken = new();
|
||||
|
||||
/// <summary>
|
||||
/// Automatically registers all query selectors with the parser/executor.
|
||||
/// </summary>
|
||||
public void DoAutoRegistrations()
|
||||
{
|
||||
foreach (var type in _reflectionManager.FindTypesWithAttribute<RegisterBqlQuerySelectorAttribute>())
|
||||
{
|
||||
RegisterClass(type);
|
||||
}
|
||||
|
||||
DoParserSetup();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internally registers the given <see cref="BqlQuerySelector"/>.
|
||||
/// </summary>
|
||||
/// <param name="bqlQuerySelector">The selector to register</param>
|
||||
private void RegisterClass(Type bqlQuerySelector)
|
||||
{
|
||||
DebugTools.Assert(bqlQuerySelector.BaseType == typeof(BqlQuerySelector));
|
||||
var inst = (BqlQuerySelector)Activator.CreateInstance(bqlQuerySelector)!;
|
||||
_instances.Add(inst);
|
||||
_queriesByToken.Add(inst.Token, inst);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,368 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Robust.Server.Bql
|
||||
{
|
||||
[RegisterBqlQuerySelector]
|
||||
public sealed class WithQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "with";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.Component };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
var comp = (Type) arguments[0];
|
||||
return input.Where(x => entityManager.HasComponent(x, comp) ^ isInverted);
|
||||
}
|
||||
|
||||
public override IEnumerable<EntityUid> DoInitialSelection(IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
if (isInverted)
|
||||
{
|
||||
return base.DoInitialSelection(arguments, isInverted, entityManager);
|
||||
}
|
||||
|
||||
return entityManager.GetAllComponents((Type) arguments[0], includePaused: true)
|
||||
.Select(x => x.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public sealed class NamedQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "named";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.String };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
var r = new Regex("^" + (string) arguments[0] + "$");
|
||||
return input.Where(e =>
|
||||
{
|
||||
if (entityManager.TryGetComponent<MetaDataComponent>(e, out var metaDataComponent))
|
||||
return r.IsMatch(metaDataComponent.EntityName) ^ isInverted;
|
||||
return isInverted;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public sealed class ParentedToQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "parentedto";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.EntityId };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
var uid = (EntityUid) arguments[0];
|
||||
return input.Where(e => (entityManager.TryGetComponent<TransformComponent>(e, out var transform) &&
|
||||
transform.ParentUid == uid) ^ isInverted);
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public sealed class RecursiveParentedToQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "rparentedto";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.EntityId };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
var uid = (EntityUid) arguments[0];
|
||||
return input.Where(e =>
|
||||
{
|
||||
if (!entityManager.TryGetComponent<TransformComponent>(e, out var transform))
|
||||
return isInverted;
|
||||
while (transform.ParentUid != EntityUid.Invalid)
|
||||
{
|
||||
if ((transform.ParentUid == uid) ^ isInverted)
|
||||
return true;
|
||||
if (!entityManager.TryGetComponent(transform.ParentUid, out transform))
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public sealed class ChildrenQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "children";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => Array.Empty<QuerySelectorArgument>();
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
foreach (var uid in input)
|
||||
{
|
||||
if (!entityManager.TryGetComponent(uid, out TransformComponent? xform)) continue;
|
||||
|
||||
foreach (var child in xform.ChildEntities)
|
||||
{
|
||||
yield return child;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public sealed class RecursiveChildrenQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "rchildren";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => Array.Empty<QuerySelectorArgument>();
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments,
|
||||
bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
IEnumerable<EntityUid> toSearch = input;
|
||||
|
||||
while (true)
|
||||
{
|
||||
// TODO: Reduce LINQ chaining
|
||||
var doing = toSearch.Where(entityManager.HasComponent<TransformComponent>).Select(entityManager.GetComponent<TransformComponent>).ToArray();
|
||||
var search = doing.SelectMany(x => x.ChildEntities);
|
||||
if (!search.Any())
|
||||
break;
|
||||
toSearch = doing.SelectMany(x => x.ChildEntities).Where(x => x != EntityUid.Invalid);
|
||||
|
||||
foreach (var xform in doing)
|
||||
{
|
||||
foreach (var uid in xform.ChildEntities)
|
||||
{
|
||||
// This should never happen anyway
|
||||
if (uid == EntityUid.Invalid) continue;
|
||||
yield return uid;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public sealed class ParentQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "parent";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => Array.Empty<QuerySelectorArgument>();
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
return input.Where(entityManager.HasComponent<TransformComponent>)
|
||||
.Distinct();
|
||||
}
|
||||
|
||||
public override IEnumerable<EntityUid> DoInitialSelection(IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
return DoSelection(
|
||||
Query(entityManager.AllEntityQueryEnumerator<TransformComponent>()),
|
||||
arguments,
|
||||
isInverted, entityManager);
|
||||
|
||||
IEnumerable<EntityUid> Query(AllEntityQueryEnumerator<TransformComponent> enumerator)
|
||||
{
|
||||
while (enumerator.MoveNext(out var entityUid, out _))
|
||||
{
|
||||
yield return entityUid;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public sealed class AboveQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "above";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new [] { QuerySelectorArgument.String };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
var tileDefinitionManager = IoCManager.Resolve<ITileDefinitionManager>();
|
||||
var tileTy = tileDefinitionManager[(string) arguments[0]];
|
||||
|
||||
var map = IoCManager.Resolve<IMapManager>();
|
||||
if (tileTy.TileId == 0)
|
||||
{
|
||||
return input.Where(e => entityManager.TryGetComponent<TransformComponent>(e, out var transform) && (transform.GridUid is null) ^ isInverted);
|
||||
}
|
||||
else
|
||||
{
|
||||
return input.Where(e =>
|
||||
{
|
||||
if (!entityManager.TryGetComponent<TransformComponent>(e, out var transform)) return isInverted;
|
||||
|
||||
if (!map.TryGetGrid(transform.GridUid, out var grid))
|
||||
return isInverted;
|
||||
|
||||
return (grid.GetTileRef(transform.Coordinates).Tile.TypeId == tileTy.TileId) ^ isInverted;
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
// ReSharper disable once InconsistentNaming the name is correct shut up
|
||||
public sealed class OnGridQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "ongrid";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new [] { QuerySelectorArgument.Integer };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
// TODO: Probably easier and significantly faster to just iterate the grid's children.
|
||||
var grid = new EntityUid((int) arguments[0]);
|
||||
return input.Where(e => (entityManager.TryGetComponent<TransformComponent>(e, out var transform) && transform.GridUid == grid) ^ isInverted);
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
// ReSharper disable once InconsistentNaming the name is correct shut up
|
||||
public sealed class OnMapQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "onmap";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new [] { QuerySelectorArgument.Integer };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
// TODO: Just use EntityLookup GetEntitiesInMap
|
||||
var map = new MapId((int) arguments[0]);
|
||||
return input.Where(e => (entityManager.TryGetComponent<TransformComponent>(e, out var transform) && transform.MapID == map) ^ isInverted);
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public sealed class PrototypedQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "prototyped";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.String };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
var name = (string) arguments[0];
|
||||
return input.Where(e => (entityManager.TryGetComponent<MetaDataComponent>(e, out var metaData) && metaData.EntityPrototype?.ID == name) ^ isInverted);
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public sealed class RecursivePrototypedQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "rprototyped";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.String };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
var name = (string) arguments[0];
|
||||
return input.Where(e =>
|
||||
{
|
||||
if (!entityManager.TryGetComponent<MetaDataComponent>(e, out var metaData))
|
||||
return isInverted;
|
||||
if ((metaData.EntityPrototype?.ID == name) ^ isInverted)
|
||||
return true;
|
||||
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
|
||||
return metaData.EntityPrototype != null && prototypeManager.EnumerateParents<EntityPrototype>(metaData.EntityPrototype.ID).Any(x => x.Name == name) ^ isInverted;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public sealed class SelectQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "select";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.Integer | QuerySelectorArgument.Percentage };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
if (arguments[0] is int)
|
||||
{
|
||||
var inp = input.OrderBy(_ => Guid.NewGuid()).ToArray();
|
||||
var taken = (int) arguments[0];
|
||||
|
||||
if (isInverted)
|
||||
taken = Math.Max(0, inp.Length - taken);
|
||||
|
||||
return inp.Take(taken);
|
||||
}
|
||||
|
||||
var enumerable = input.OrderBy(_ => Guid.NewGuid()).ToArray();
|
||||
var amount = isInverted
|
||||
? (int) Math.Floor(enumerable.Length * Math.Clamp(1 - (double) arguments[0], 0, 1))
|
||||
: (int) Math.Floor(enumerable.Length * Math.Clamp((double) arguments[0], 0, 1));
|
||||
return enumerable.Take(amount);
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public sealed class NearQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "near";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.Float };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
var radius = (float)(double)arguments[0];
|
||||
var entityLookup = entityManager.System<EntityLookupSystem>();
|
||||
var xformQuery = entityManager.GetEntityQuery<TransformComponent>();
|
||||
var distinct = new HashSet<EntityUid>();
|
||||
|
||||
foreach (var uid in input)
|
||||
{
|
||||
foreach (var near in entityLookup.GetEntitiesInRange(xformQuery.GetComponent(uid).Coordinates,
|
||||
radius))
|
||||
{
|
||||
if (!distinct.Add(near)) continue;
|
||||
yield return near;
|
||||
}
|
||||
}
|
||||
|
||||
//BUG: GetEntitiesInRange effectively uses manhattan distance. This is not intended, near is supposed to be circular.
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
// ReSharper disable once InconsistentNaming the name is correct shut up
|
||||
public sealed class AnchoredQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "anchored";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => Array.Empty<QuerySelectorArgument>();
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
return input.Where(e => (entityManager.TryGetComponent<TransformComponent>(e, out var transform) && transform.Anchored) ^ isInverted);
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public sealed class PausedQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "paused";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => Array.Empty<QuerySelectorArgument>();
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
return input.Where(e => entityManager.GetComponent<MetaDataComponent>(e).EntityPaused ^ isInverted);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Server.Bql
|
||||
{
|
||||
[Flags]
|
||||
[PublicAPI]
|
||||
public enum QuerySelectorArgument
|
||||
{
|
||||
Integer = 0b00000001,
|
||||
Float = 0b00000010,
|
||||
String = 0b00000100,
|
||||
Percentage = 0b00001000,
|
||||
Component = 0b00010000,
|
||||
//SubQuery = 0b00100000,
|
||||
EntityId = 0b01000000,
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public abstract class BqlQuerySelector
|
||||
{
|
||||
/// <summary>
|
||||
/// The token name for the given QuerySelector, for example `when`.
|
||||
/// </summary>
|
||||
public virtual string Token => throw new NotImplementedException();
|
||||
|
||||
/// <summary>
|
||||
/// Arguments for the given QuerySelector, presented as "what arguments are permitted in what spot".
|
||||
/// </summary>
|
||||
public virtual QuerySelectorArgument[] Arguments => throw new NotImplementedException();
|
||||
|
||||
/// <summary>
|
||||
/// Performs a transform over it's input entity list, whether that be filtering (selecting) or expanding the
|
||||
/// input on some criteria like what entities are nearby.
|
||||
/// </summary>
|
||||
/// <param name="input">Input entity list.</param>
|
||||
/// <param name="arguments">Parsed selector arguments.</param>
|
||||
/// <param name="isInverted">Whether the query is inverted.</param>
|
||||
/// <param name="entityManager">The entity manager.</param>
|
||||
/// <returns>New list of entities</returns>
|
||||
/// <exception cref="NotImplementedException">someone is a moron if this happens.</exception>
|
||||
public abstract IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input,
|
||||
IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager);
|
||||
|
||||
/// <summary>
|
||||
/// Performs selection as the first selector in the query. Allows for optimizing when you can be more efficient
|
||||
/// than just querying every entity.
|
||||
/// </summary>
|
||||
/// <param name="arguments"></param>
|
||||
/// <param name="isInverted"></param>
|
||||
/// <param name="entityManager"></param>
|
||||
/// <returns></returns>
|
||||
public virtual IEnumerable<EntityUid> DoInitialSelection(IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
return DoSelection(entityManager.GetEntities(), arguments, isInverted, entityManager);
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
protected BqlQuerySelector() {}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Robust.Server.Bql
|
||||
{
|
||||
public struct BqlQuerySelectorParsed
|
||||
{
|
||||
public List<object> Arguments;
|
||||
public string Token;
|
||||
public bool Inverted;
|
||||
|
||||
public BqlQuerySelectorParsed(List<object> arguments, string token, bool inverted)
|
||||
{
|
||||
Arguments = arguments;
|
||||
Token = token;
|
||||
Inverted = inverted;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Server.Bql
|
||||
{
|
||||
public sealed class ForAllCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IBqlQueryManager _bql = default!;
|
||||
[Dependency] private readonly IEntityManager _entities = default!;
|
||||
|
||||
public override string Command => "forall";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length < 2)
|
||||
{
|
||||
shell.WriteLine(Help);
|
||||
return;
|
||||
}
|
||||
|
||||
var transformSystem = _entities.System<SharedTransformSystem>();
|
||||
|
||||
var (entities, rest) = _bql.SimpleParseAndExecute(argStr[6..]);
|
||||
|
||||
foreach (var ent in entities.ToList())
|
||||
{
|
||||
var cmds = SubstituteEntityDetails(_entities, transformSystem, shell, ent, rest).Split(";");
|
||||
foreach (var cmd in cmds)
|
||||
{
|
||||
shell.ExecuteCommand(cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This will be refactored out soon.
|
||||
private static string SubstituteEntityDetails(
|
||||
IEntityManager entMan,
|
||||
SharedTransformSystem transformSystem,
|
||||
IConsoleShell shell,
|
||||
EntityUid ent,
|
||||
string ruleString)
|
||||
{
|
||||
var transform = entMan.GetComponent<TransformComponent>(ent);
|
||||
var metadata = entMan.GetComponent<MetaDataComponent>(ent);
|
||||
|
||||
var worldPos = transformSystem.GetWorldPosition(transform);
|
||||
var localPos = transform.LocalPosition;
|
||||
|
||||
// gross, is there a better way to do this?
|
||||
ruleString = ruleString.Replace("$ID", ent.ToString());
|
||||
ruleString = ruleString.Replace("$WX", worldPos.X.ToString(CultureInfo.InvariantCulture));
|
||||
ruleString = ruleString.Replace("$WY", worldPos.Y.ToString(CultureInfo.InvariantCulture));
|
||||
ruleString = ruleString.Replace("$LX", localPos.X.ToString(CultureInfo.InvariantCulture));
|
||||
ruleString = ruleString.Replace("$LY", localPos.Y.ToString(CultureInfo.InvariantCulture));
|
||||
ruleString = ruleString.Replace("$NAME", metadata.EntityName);
|
||||
|
||||
if (shell.Player is { AttachedEntity: { } pEntity})
|
||||
{
|
||||
var pTransform = entMan.GetComponent<TransformComponent>(pEntity);
|
||||
|
||||
var pWorldPos = transformSystem.GetWorldPosition(pTransform);
|
||||
var pLocalPos = pTransform.LocalPosition;
|
||||
|
||||
ruleString = ruleString.Replace("$PID", pEntity.ToString());
|
||||
ruleString = ruleString.Replace("$PWX", pWorldPos.X.ToString(CultureInfo.InvariantCulture));
|
||||
ruleString = ruleString.Replace("$PWY", pWorldPos.Y.ToString(CultureInfo.InvariantCulture));
|
||||
ruleString = ruleString.Replace("$PLX", pLocalPos.X.ToString(CultureInfo.InvariantCulture));
|
||||
ruleString = ruleString.Replace("$PLY", pLocalPos.Y.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
return ruleString;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Server.Bql
|
||||
{
|
||||
public interface IBqlQueryManager
|
||||
{
|
||||
public (IEnumerable<EntityUid>, string) SimpleParseAndExecute(string query);
|
||||
void DoAutoRegistrations();
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Robust.Server.Bql
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
[BaseTypeRequired(typeof(BqlQuerySelector))]
|
||||
[MeansImplicitUse]
|
||||
[PublicAPI]
|
||||
public sealed class RegisterBqlQuerySelectorAttribute : Attribute
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Toolshed;
|
||||
using Robust.Shared.Toolshed.Errors;
|
||||
|
||||
namespace Robust.Server.Console
|
||||
{
|
||||
internal sealed class ConGroupController : IConGroupController
|
||||
internal sealed class ConGroupController : IConGroupController, IPermissionController
|
||||
{
|
||||
public IConGroupControllerImplementation? Implementation { get; set; }
|
||||
|
||||
@@ -30,5 +34,11 @@ namespace Robust.Server.Console
|
||||
{
|
||||
return Implementation?.CanAdminReloadPrototypes(session) ?? false;
|
||||
}
|
||||
|
||||
public bool CheckInvokable(CommandSpec command, ICommonSession? user, out IConError? error)
|
||||
{
|
||||
error = null;
|
||||
return Implementation?.CheckInvokable(command, user, out error) ?? false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Toolshed;
|
||||
|
||||
namespace Robust.Server.Console
|
||||
{
|
||||
public interface IConGroupControllerImplementation
|
||||
public interface IConGroupControllerImplementation : IPermissionController
|
||||
{
|
||||
bool CanCommand(IPlayerSession session, string cmdName);
|
||||
bool CanAdminPlace(IPlayerSession session);
|
||||
|
||||
@@ -9,6 +9,8 @@ using Robust.Shared.Log;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Toolshed;
|
||||
using Robust.Shared.Toolshed.Syntax;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Server.Console
|
||||
@@ -19,6 +21,7 @@ namespace Robust.Server.Console
|
||||
[Dependency] private readonly IConGroupController _groupController = default!;
|
||||
[Dependency] private readonly IPlayerManager _players = default!;
|
||||
[Dependency] private readonly ISystemConsoleManager _systemConsole = default!;
|
||||
[Dependency] private readonly ToolshedManager _toolshed = default!;
|
||||
|
||||
public ServerConsoleHost() : base(isServer: true) {}
|
||||
|
||||
@@ -45,19 +48,31 @@ namespace Robust.Server.Console
|
||||
/// <inheritdoc />
|
||||
public override void WriteLine(ICommonSession? session, string text)
|
||||
{
|
||||
var msg = new FormattedMessage();
|
||||
msg.AddText(text);
|
||||
if (session is IPlayerSession playerSession)
|
||||
OutputText(playerSession, text, false);
|
||||
OutputText(playerSession, msg, false);
|
||||
else
|
||||
OutputText(null, text, false);
|
||||
OutputText(null, msg, false);
|
||||
}
|
||||
|
||||
public override void WriteLine(ICommonSession? session, FormattedMessage msg)
|
||||
{
|
||||
if (session is IPlayerSession playerSession)
|
||||
OutputText(playerSession, msg, false);
|
||||
else
|
||||
OutputText(null, msg, false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void WriteError(ICommonSession? session, string text)
|
||||
{
|
||||
var msg = new FormattedMessage();
|
||||
msg.AddText(text);
|
||||
if (session is IPlayerSession playerSession)
|
||||
OutputText(playerSession, text, true);
|
||||
OutputText(playerSession, msg, true);
|
||||
else
|
||||
OutputText(null, text, true);
|
||||
OutputText(null, msg, true);
|
||||
}
|
||||
|
||||
public bool IsCmdServer(IConsoleCommand cmd) => true;
|
||||
@@ -70,13 +85,13 @@ namespace Robust.Server.Console
|
||||
var localShell = shell.ConsoleHost.LocalShell;
|
||||
var sudoShell = new SudoShell(this, localShell, shell);
|
||||
ExecuteInShell(sudoShell, argStr["sudo ".Length..]);
|
||||
}, (shell, args) =>
|
||||
}, (shell, args, argStr) =>
|
||||
{
|
||||
var localShell = shell.ConsoleHost.LocalShell;
|
||||
var sudoShell = new SudoShell(this, localShell, shell);
|
||||
|
||||
#pragma warning disable CA2012
|
||||
return CalcCompletions(sudoShell, args);
|
||||
return CalcCompletions(sudoShell, args, argStr);
|
||||
#pragma warning restore CA2012
|
||||
});
|
||||
|
||||
@@ -117,6 +132,18 @@ namespace Robust.Server.Console
|
||||
AnyCommandExecuted?.Invoke(shell, cmdName, command, cmdArgs);
|
||||
conCmd.Execute(shell, command, cmdArgs);
|
||||
}
|
||||
else
|
||||
{
|
||||
// toolshed time
|
||||
_toolshed.InvokeCommand(shell, command, null, out var res, out var ctx);
|
||||
|
||||
foreach (var err in ctx.GetErrors())
|
||||
{
|
||||
ctx.WriteLine(err.Describe());
|
||||
}
|
||||
|
||||
shell.WriteLine(_toolshed.PrettyPrintType(res));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -162,7 +189,7 @@ namespace Robust.Server.Console
|
||||
ExecuteCommand(session, text);
|
||||
}
|
||||
|
||||
private void OutputText(IPlayerSession? session, string text, bool error)
|
||||
private void OutputText(IPlayerSession? session, FormattedMessage text, bool error)
|
||||
{
|
||||
if (session != null)
|
||||
{
|
||||
@@ -183,10 +210,25 @@ namespace Robust.Server.Console
|
||||
private async void HandleConCompletions(MsgConCompletion message)
|
||||
{
|
||||
var session = _players.GetSessionByChannel(message.MsgChannel);
|
||||
|
||||
var shell = new ConsoleShell(this, session, false);
|
||||
|
||||
var result = await CalcCompletions(shell, message.Args);
|
||||
var result = await CalcCompletions(shell, message.Args, message.ArgString);
|
||||
|
||||
if ((result.Options.Length == 0 && result.Hint is null) || message.Args.Length <= 1)
|
||||
{
|
||||
var parser = new ForwardParser(message.ArgString, _toolshed);
|
||||
CommandRun.TryParse(false, true, parser, null, null, false, out _, out var completions, out _);
|
||||
if (completions == null)
|
||||
{
|
||||
goto done;
|
||||
}
|
||||
var (shedRes, _) = await completions.Value;
|
||||
shedRes ??= CompletionResult.Empty;
|
||||
result = new CompletionResult(shedRes.Options.Concat(result.Options).ToArray(), shedRes.Hint ?? result.Hint);
|
||||
}
|
||||
|
||||
done:
|
||||
var msg = new MsgConCompletionResp
|
||||
{
|
||||
Result = result,
|
||||
@@ -199,7 +241,7 @@ namespace Robust.Server.Console
|
||||
NetManager.ServerSendMessage(msg, message.MsgChannel);
|
||||
}
|
||||
|
||||
private ValueTask<CompletionResult> CalcCompletions(IConsoleShell shell, string[] args)
|
||||
private ValueTask<CompletionResult> CalcCompletions(IConsoleShell shell, string[] args, string argStr)
|
||||
{
|
||||
// Logger.Debug(string.Join(", ", args));
|
||||
|
||||
@@ -217,7 +259,7 @@ namespace Robust.Server.Console
|
||||
if (!ShellCanExecute(shell, cmdName))
|
||||
return ValueTask.FromResult(CompletionResult.Empty);
|
||||
|
||||
return cmd.GetCompletionAsync(shell, args[1..], default);
|
||||
return cmd.GetCompletionAsync(shell, args[1..], argStr, default);
|
||||
}
|
||||
|
||||
private sealed class SudoShell : IConsoleShell
|
||||
@@ -254,6 +296,12 @@ namespace Robust.Server.Console
|
||||
_sudoer.WriteLine(text);
|
||||
}
|
||||
|
||||
public void WriteLine(FormattedMessage message)
|
||||
{
|
||||
_owner.WriteLine(message);
|
||||
_sudoer.WriteLine(message);
|
||||
}
|
||||
|
||||
public void WriteError(string text)
|
||||
{
|
||||
_owner.WriteError(text);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,29 @@ 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));
|
||||
if (md.EntityLastModifiedTick <= fromTick)
|
||||
continue;
|
||||
|
||||
var state = GetEntityState(player, uid, fromTick, md);
|
||||
|
||||
// Temporary debugging code.
|
||||
// TODO REMOVE TEMPORARY CODE
|
||||
if (state.Empty)
|
||||
{
|
||||
var msg = $"{nameof(GetEntityState)} returned an empty state while enumerating all. Entity {ToPrettyString(uid)}. Net component Data:";
|
||||
foreach (var (_, cmp) in EntityManager.GetNetComponents(uid))
|
||||
{
|
||||
msg += $"\nName: {_factory.GetComponentName(cmp.GetType())}" +
|
||||
$"Enabled: {cmp.NetSyncEnabled}, " +
|
||||
$"Lifestage: {cmp.LifeStage}, " +
|
||||
$"OwnerOnly: {cmp.SendOnlyToOwner}, " +
|
||||
$"SessionSpecific: {cmp.SessionSpecific}, " +
|
||||
$"LastModified: {cmp.LastModifiedTick}";
|
||||
}
|
||||
Log.Error(msg);
|
||||
}
|
||||
|
||||
stateEntities.Add(state);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -1058,7 +1079,27 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
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));
|
||||
|
||||
var state = GetEntityState(player, uid, fromTick, md);
|
||||
|
||||
// Temporary debugging code.
|
||||
// TODO REMOVE TEMPORARY CODE
|
||||
if (state.Empty)
|
||||
{
|
||||
var msg = $"{nameof(GetEntityState)} returned an empty state for new entity {ToPrettyString(uid)}. Net component Data:";
|
||||
foreach (var (_, cmp) in EntityManager.GetNetComponents(uid))
|
||||
{
|
||||
msg += $"\nName: {_factory.GetComponentName(cmp.GetType())}" +
|
||||
$"Enabled: {cmp.NetSyncEnabled}, " +
|
||||
$"Lifestage: {cmp.LifeStage}, " +
|
||||
$"OwnerOnly: {cmp.SendOnlyToOwner}, " +
|
||||
$"SessionSpecific: {cmp.SessionSpecific}, " +
|
||||
$"LastModified: {cmp.LastModifiedTick}";
|
||||
}
|
||||
Log.Error(msg);
|
||||
}
|
||||
|
||||
stateEntities.Add(state);
|
||||
}
|
||||
|
||||
foreach (var uid in dirty)
|
||||
@@ -1070,7 +1111,10 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
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));
|
||||
|
||||
var state = GetEntityState(player, uid, fromTick, md);
|
||||
if (!state.Empty)
|
||||
stateEntities.Add(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,7 +320,7 @@ namespace Robust.Server.Physics
|
||||
var tilePos = offset + tile;
|
||||
var bounds = _lookup.GetLocalBounds(tilePos, oldGrid.TileSize);
|
||||
|
||||
foreach (var ent in _lookup.GetEntitiesIntersecting(oldGridUid, tilePos, LookupFlags.Dynamic | LookupFlags.Sundries))
|
||||
foreach (var ent in _lookup.GetEntitiesIntersecting(oldGridUid, tilePos, 0f, LookupFlags.Dynamic | LookupFlags.Sundries))
|
||||
{
|
||||
// Consider centre of entity position maybe?
|
||||
var entXform = xformQuery.GetComponent(ent);
|
||||
|
||||
@@ -14,6 +14,7 @@ using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Placement;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Robust.Server.Placement
|
||||
@@ -68,7 +69,7 @@ namespace Robust.Server.Placement
|
||||
HandlePlacementRequest(msg);
|
||||
break;
|
||||
case PlacementManagerMessage.RequestEntRemove:
|
||||
HandleEntRemoveReq(msg.EntityUid);
|
||||
HandleEntRemoveReq(msg);
|
||||
break;
|
||||
case PlacementManagerMessage.RequestRectRemove:
|
||||
HandleRectRemoveReq(msg);
|
||||
@@ -155,6 +156,9 @@ namespace Robust.Server.Placement
|
||||
|
||||
foreach (var ent in toDelete)
|
||||
{
|
||||
var placementEraseEvent = new PlacementEntityEvent(ent, coordinates, PlacementEventAction.Erase, msg.MsgChannel.UserId);
|
||||
_entityManager.EventBus.RaiseEvent(EventSource.Local, placementEraseEvent);
|
||||
|
||||
_entityManager.DeleteEntity(ent);
|
||||
}
|
||||
}
|
||||
@@ -162,15 +166,18 @@ namespace Robust.Server.Placement
|
||||
|
||||
var created = _entityManager.SpawnEntity(entityTemplateName, coordinates);
|
||||
|
||||
var placementCreateEvent = new PlacementEntityEvent(created, coordinates, PlacementEventAction.Create, msg.MsgChannel.UserId);
|
||||
_entityManager.EventBus.RaiseEvent(EventSource.Local, placementCreateEvent);
|
||||
|
||||
_entityManager.GetComponent<TransformComponent>(created).LocalRotation = dirRcv.ToAngle();
|
||||
}
|
||||
else
|
||||
{
|
||||
PlaceNewTile(tileType, coordinates);
|
||||
PlaceNewTile(tileType, coordinates, msg.MsgChannel.UserId);
|
||||
}
|
||||
}
|
||||
|
||||
private void PlaceNewTile(ushort tileType, EntityCoordinates coordinates)
|
||||
private void PlaceNewTile(ushort tileType, EntityCoordinates coordinates, NetUserId placingUserId)
|
||||
{
|
||||
if (!coordinates.IsValid(_entityManager)) return;
|
||||
|
||||
@@ -184,6 +191,9 @@ namespace Robust.Server.Placement
|
||||
if (grid != null) // stick to existing grid
|
||||
{
|
||||
grid.SetTile(coordinates, new Tile(tileType));
|
||||
|
||||
var placementEraseEvent = new PlacementTileEvent(tileType, coordinates, placingUserId);
|
||||
_entityManager.EventBus.RaiseEvent(EventSource.Local, placementEraseEvent);
|
||||
}
|
||||
else if (tileType != 0) // create a new grid
|
||||
{
|
||||
@@ -192,14 +202,21 @@ namespace Robust.Server.Placement
|
||||
newGridXform.WorldPosition = coordinates.Position - newGrid.TileSizeHalfVector; // assume bottom left tile origin
|
||||
var tilePos = newGrid.WorldToTile(coordinates.Position);
|
||||
newGrid.SetTile(tilePos, new Tile(tileType));
|
||||
|
||||
var placementEraseEvent = new PlacementTileEvent(tileType, coordinates, placingUserId);
|
||||
_entityManager.EventBus.RaiseEvent(EventSource.Local, placementEraseEvent);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleEntRemoveReq(EntityUid entityUid)
|
||||
private void HandleEntRemoveReq(MsgPlacement msg)
|
||||
{
|
||||
//TODO: Some form of admin check
|
||||
if (_entityManager.EntityExists(entityUid))
|
||||
_entityManager.DeleteEntity(entityUid);
|
||||
if (!_entityManager.EntityExists(msg.EntityUid))
|
||||
return;
|
||||
|
||||
var placementEraseEvent = new PlacementEntityEvent(msg.EntityUid, _entityManager.GetComponent<TransformComponent>(msg.EntityUid).Coordinates, PlacementEventAction.Erase, msg.MsgChannel.UserId);
|
||||
_entityManager.EventBus.RaiseEvent(EventSource.Local, placementEraseEvent);
|
||||
_entityManager.DeleteEntity(msg.EntityUid);
|
||||
}
|
||||
|
||||
private void HandleRectRemoveReq(MsgPlacement msg)
|
||||
@@ -211,6 +228,8 @@ namespace Robust.Server.Placement
|
||||
{
|
||||
if (_entityManager.Deleted(entity) || _entityManager.HasComponent<MapGridComponent>(entity) || _entityManager.HasComponent<ActorComponent>(entity))
|
||||
continue;
|
||||
var placementEraseEvent = new PlacementEntityEvent(entity, _entityManager.GetComponent<TransformComponent>(entity).Coordinates, PlacementEventAction.Erase, msg.MsgChannel.UserId);
|
||||
_entityManager.EventBus.RaiseEvent(EventSource.Local, placementEraseEvent);
|
||||
_entityManager.DeleteEntity(entity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
@@ -34,7 +33,7 @@ public sealed class ServerSpriteSpecifierSerializer : SpriteSpecifierSerializer
|
||||
}
|
||||
|
||||
var path = serializationManager.ValidateNode<ResPath>(
|
||||
new ValueDataNode($"{SpriteSpecifierSerializer.TextureRoot / valuePathNode.Value}"), context);
|
||||
new ValueDataNode($"{TextureRoot / valuePathNode.Value}"), context);
|
||||
|
||||
if (path is ErrorNode) return path;
|
||||
|
||||
@@ -44,11 +43,15 @@ public sealed class ServerSpriteSpecifierSerializer : SpriteSpecifierSerializer
|
||||
// meta.json
|
||||
|
||||
var statePath = serializationManager.ValidateNode<ResPath>(
|
||||
new ValueDataNode($"{SpriteSpecifierSerializer.TextureRoot / valuePathNode.Value / valueStateNode.Value}.png"),
|
||||
new ValueDataNode($"{TextureRoot / valuePathNode.Value / valueStateNode.Value}.png"),
|
||||
context);
|
||||
|
||||
if (statePath is ErrorNode) return statePath;
|
||||
|
||||
return new ValidatedMappingNode(new());
|
||||
return new ValidatedMappingNode(new()
|
||||
{
|
||||
{ new ValidatedValueNode(new ValueDataNode("sprite")), new ValidatedValueNode(pathNode)},
|
||||
{ new ValidatedValueNode(new ValueDataNode("state")), new ValidatedValueNode(valueStateNode)},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -71,12 +72,12 @@ internal sealed class HubManager
|
||||
_interval = TimeSpan.FromSeconds(interval);
|
||||
_httpClient?.Dispose();
|
||||
|
||||
_httpClient = new HttpClient(new SocketsHttpHandler
|
||||
{
|
||||
// Keep-alive connections stay open for longer than the advertise interval.
|
||||
// This way the same HTTPS connection can be re-used.
|
||||
PooledConnectionIdleTimeout = _interval + TimeSpan.FromSeconds(10),
|
||||
});
|
||||
var socketsHandler = HappyEyeballsHttp.CreateHttpHandler();
|
||||
// Keep-alive connections stay open for longer than the advertise interval.
|
||||
// This way the same HTTPS connection can be re-used.
|
||||
socketsHandler.PooledConnectionIdleTimeout = _interval + TimeSpan.FromSeconds(10);
|
||||
|
||||
_httpClient = new HttpClient(socketsHandler);
|
||||
|
||||
HttpClientUserAgent.AddUserAgent(_httpClient);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Robust.Server.Bql;
|
||||
using Robust.Server.Configuration;
|
||||
using Robust.Server.Console;
|
||||
using Robust.Server.DataMetrics;
|
||||
@@ -82,7 +81,6 @@ namespace Robust.Server
|
||||
deps.Register<IScriptHost, ScriptHost>();
|
||||
deps.Register<IMetricsManager, MetricsManager>();
|
||||
deps.Register<IAuthManager, AuthManager>();
|
||||
deps.Register<IBqlQueryManager, BqlQueryManager>();
|
||||
deps.Register<HubManager, HubManager>();
|
||||
deps.Register<IRobustSerializer, ServerRobustSerializer>();
|
||||
deps.Register<IRobustSerializerInternal, ServerRobustSerializer>();
|
||||
@@ -93,6 +91,7 @@ namespace Robust.Server
|
||||
deps.Register<INetConfigurationManagerInternal, ServerNetConfigurationManager>();
|
||||
deps.Register<IGamePrototypeLoadManager, GamePrototypeLoadManager>();
|
||||
deps.Register<NetworkResourceManager>();
|
||||
deps.Register<IHttpClientHolder, HttpClientHolder>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ using Robust.Shared.Asynchronous;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -28,7 +29,7 @@ namespace Robust.Server.ServerStatus
|
||||
|
||||
// Ping watchdog every 15 seconds.
|
||||
private static readonly TimeSpan PingGap = TimeSpan.FromSeconds(15);
|
||||
private readonly HttpClient _httpClient = new();
|
||||
private readonly HttpClient _httpClient = new(HappyEyeballsHttp.CreateHttpHandler());
|
||||
|
||||
private TimeSpan? _lastPing;
|
||||
private string? _watchdogToken;
|
||||
|
||||
24
Robust.Server/Toolshed/Commands/Players/ActorCommand.cs
Normal file
24
Robust.Server/Toolshed/Commands/Players/ActorCommand.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Toolshed;
|
||||
|
||||
namespace Robust.Server.Toolshed.Commands.Players;
|
||||
|
||||
[ToolshedCommand]
|
||||
public sealed class ActorCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation("controlled")]
|
||||
public IEnumerable<EntityUid> Controlled([PipedArgument] IEnumerable<EntityUid> input)
|
||||
{
|
||||
return input.Where(HasComp<ActorComponent>);
|
||||
}
|
||||
|
||||
[CommandImplementation("session")]
|
||||
public IEnumerable<IPlayerSession> Session([PipedArgument] IEnumerable<EntityUid> input)
|
||||
{
|
||||
return input.Where(HasComp<ActorComponent>).Select(x => Comp<ActorComponent>(x).PlayerSession);
|
||||
}
|
||||
}
|
||||
84
Robust.Server/Toolshed/Commands/Players/PlayersCommand.cs
Normal file
84
Robust.Server/Toolshed/Commands/Players/PlayersCommand.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Toolshed;
|
||||
using Robust.Shared.Toolshed.Errors;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Server.Toolshed.Commands.Players;
|
||||
|
||||
[ToolshedCommand]
|
||||
public sealed class PlayerCommand : ToolshedCommand
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
||||
[CommandImplementation("list")]
|
||||
public IEnumerable<IPlayerSession> Players()
|
||||
=> _playerManager.ServerSessions;
|
||||
|
||||
[CommandImplementation("self")]
|
||||
public IPlayerSession Self([CommandInvocationContext] IInvocationContext ctx)
|
||||
{
|
||||
if (ctx.Session is null)
|
||||
{
|
||||
ctx.ReportError(new NotForServerConsoleError());
|
||||
}
|
||||
|
||||
return (IPlayerSession)ctx.Session!;
|
||||
}
|
||||
|
||||
[CommandImplementation("imm")]
|
||||
public ICommonSession Immediate(
|
||||
[CommandInvocationContext] IInvocationContext ctx,
|
||||
[CommandArgument] string username
|
||||
)
|
||||
{
|
||||
_playerManager.TryGetSessionByUsername(username, out var session);
|
||||
|
||||
if (session is null)
|
||||
{
|
||||
if (Guid.TryParse(username, out var guid))
|
||||
{
|
||||
_playerManager.TryGetSessionById(new NetUserId(guid), out session);
|
||||
}
|
||||
}
|
||||
|
||||
if (session is null)
|
||||
{
|
||||
ctx.ReportError(new NoSuchPlayerError(username));
|
||||
}
|
||||
|
||||
return session!;
|
||||
}
|
||||
|
||||
[CommandImplementation("entity")]
|
||||
public IEnumerable<EntityUid> GetPlayerEntity([PipedArgument] IEnumerable<IPlayerSession> sessions)
|
||||
{
|
||||
return sessions.Select(x => x.AttachedEntity).Where(x => x is not null).Cast<EntityUid>();
|
||||
}
|
||||
|
||||
[CommandImplementation("entity")]
|
||||
public EntityUid GetPlayerEntity([PipedArgument] IPlayerSession sessions)
|
||||
{
|
||||
return sessions.AttachedEntity ?? default;
|
||||
}
|
||||
}
|
||||
|
||||
public record struct NoSuchPlayerError(string Username) : IConError
|
||||
{
|
||||
public FormattedMessage DescribeInner()
|
||||
{
|
||||
return FormattedMessage.FromMarkup($"No player with the username/GUID {Username} could be found.");
|
||||
}
|
||||
|
||||
public string? Expression { get; set; }
|
||||
public Vector2i? IssueSpan { get; set; }
|
||||
public StackTrace? Trace { get; set; }
|
||||
}
|
||||
35
Robust.Shared.Scripting/ILCommand.cs
Normal file
35
Robust.Shared.Scripting/ILCommand.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using ILReader;
|
||||
using ILReader.Readers;
|
||||
using Robust.Shared.Toolshed;
|
||||
|
||||
namespace Robust.Shared.Scripting;
|
||||
|
||||
[ToolshedCommand]
|
||||
public sealed class ILCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation("dumpil")]
|
||||
public void DumpIL(
|
||||
[CommandInvocationContext] IInvocationContext ctx,
|
||||
[PipedArgument] MethodInfo info
|
||||
)
|
||||
{
|
||||
var reader = GetReader(info);
|
||||
|
||||
foreach (var instruction in reader)
|
||||
{
|
||||
if (instruction is null)
|
||||
break;
|
||||
|
||||
ctx.WriteLine(instruction.ToString()!);
|
||||
}
|
||||
}
|
||||
|
||||
private IILReader GetReader(MethodBase method)
|
||||
{
|
||||
IILReaderConfiguration cfg = ILReader.Configuration.Resolve(method);
|
||||
return cfg.GetReader(method);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ILReader.Core" Version="1.0.0.4" />
|
||||
<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" />
|
||||
|
||||
@@ -10,7 +10,10 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Toolshed;
|
||||
using Robust.Shared.Toolshed.Errors;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Scripting
|
||||
@@ -19,7 +22,7 @@ namespace Robust.Shared.Scripting
|
||||
[SuppressMessage("ReSharper", "IdentifierTypo")]
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
[SuppressMessage("ReSharper", "CA1822")]
|
||||
public abstract class ScriptGlobalsShared
|
||||
public abstract class ScriptGlobalsShared : IInvocationContext
|
||||
{
|
||||
private const BindingFlags DefaultHelpFlags =
|
||||
BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public;
|
||||
@@ -30,6 +33,8 @@ namespace Robust.Shared.Scripting
|
||||
[field: Dependency] public IMapManager map { get; } = default!;
|
||||
[field: Dependency] public IDependencyCollection dependencies { get; } = default!;
|
||||
|
||||
[field: Dependency] public ToolshedManager shed { get; } = default!;
|
||||
|
||||
protected ScriptGlobalsShared(IDependencyCollection dependencies)
|
||||
{
|
||||
dependencies.InjectDependencies(this);
|
||||
@@ -161,6 +166,24 @@ namespace Robust.Shared.Scripting
|
||||
public abstract void write(object toString);
|
||||
public abstract void show(object obj);
|
||||
|
||||
public object? tsh(string toolshedCommand)
|
||||
{
|
||||
shed.InvokeCommand(this, toolshedCommand, null, out var res);
|
||||
return res;
|
||||
}
|
||||
|
||||
public T tsh<T>(string toolshedCommand)
|
||||
{
|
||||
shed.InvokeCommand(this, toolshedCommand, null, out var res);
|
||||
return (T)res!;
|
||||
}
|
||||
|
||||
public TOut tsh<TIn, TOut>(TIn value, string toolshedCommand)
|
||||
{
|
||||
shed.InvokeCommand(this, toolshedCommand, value, out var res);
|
||||
return (TOut)res!;
|
||||
}
|
||||
|
||||
#region EntityManager proxy methods
|
||||
public T Comp<T>(EntityUid uid) where T : Component
|
||||
=> ent.GetComponent<T>(uid);
|
||||
@@ -227,5 +250,34 @@ namespace Robust.Shared.Scripting
|
||||
return ent.EntityQuery<TComp1, TComp2, TComp3>(includePaused);
|
||||
}
|
||||
#endregion
|
||||
|
||||
public bool CheckInvokable(CommandSpec command, out IConError? error)
|
||||
{
|
||||
error = null;
|
||||
return true; // Do as I say!
|
||||
}
|
||||
|
||||
public ICommonSession? Session => null;
|
||||
|
||||
public void WriteLine(string line)
|
||||
{
|
||||
write(line);
|
||||
}
|
||||
|
||||
public void ReportError(IConError err)
|
||||
{
|
||||
write(err);
|
||||
}
|
||||
|
||||
public IEnumerable<IConError> GetErrors()
|
||||
{
|
||||
return Array.Empty<IConError>();
|
||||
}
|
||||
|
||||
public void ClearErrors()
|
||||
{
|
||||
}
|
||||
|
||||
public Dictionary<string, object?> Variables { get; } = new();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1188,6 +1188,12 @@ namespace Robust.Shared
|
||||
public static readonly CVarDef<bool> DiscordEnabled =
|
||||
CVarDef.Create("discord.enabled", true, CVar.CLIENTONLY);
|
||||
|
||||
public static readonly CVarDef<string> DiscordRichPresenceMainIconId =
|
||||
CVarDef.Create("discord.rich_main_icon_id", "devstation", CVar.CLIENTONLY);
|
||||
|
||||
public static readonly CVarDef<string> DiscordRichPresenceSecondIconId =
|
||||
CVarDef.Create("discord.rich_second_icon_id", "logo", CVar.CLIENTONLY);
|
||||
|
||||
/*
|
||||
* RES
|
||||
*/
|
||||
@@ -1503,8 +1509,8 @@ namespace Robust.Shared
|
||||
public static readonly CVarDef<bool> ReplayDynamicalScrubbing = CVarDef.Create("replay.dynamical_scrubbing", true);
|
||||
|
||||
/// <summary>
|
||||
/// When recording replays,
|
||||
/// should we attempt to make a valid content bundle that can be directly executed by the launcher?
|
||||
/// When recording replays, should we attempt to make a valid content bundle that can be directly executed by
|
||||
/// the launcher?
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This requires the server's build information to be sufficiently filled out.
|
||||
@@ -1512,6 +1518,16 @@ namespace Robust.Shared
|
||||
public static readonly CVarDef<bool> ReplayMakeContentBundle =
|
||||
CVarDef.Create("replay.make_content_bundle", true);
|
||||
|
||||
/// <summary>
|
||||
/// If true, this will cause the replay client to ignore some errors while loading a replay file.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This might make otherwise broken replays playable, but ignoring these errors is also very likely to
|
||||
/// cause unexpected and confusing errors elsewhere. By default this is disabled so that users report the
|
||||
/// original exception rather than sending people on a wild-goose chase to find a non-existent bug.
|
||||
/// </remarks>
|
||||
public static readonly CVarDef<bool> ReplayIgnoreErrors =
|
||||
CVarDef.Create("replay.ignore_errors", false, CVar.CLIENTONLY | CVar.ARCHIVE);
|
||||
/*
|
||||
* CFG
|
||||
*/
|
||||
|
||||
@@ -5,31 +5,31 @@ namespace Robust.Shared.Console.Commands;
|
||||
|
||||
internal sealed class HelpCommand : LocalizedCommands
|
||||
{
|
||||
public override string Command => "help";
|
||||
public override string Command => "oldhelp";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
switch (args.Length)
|
||||
{
|
||||
case 0:
|
||||
shell.WriteLine(Loc.GetString("cmd-help-no-args"));
|
||||
shell.WriteLine(Loc.GetString("cmd-oldhelp-no-args"));
|
||||
break;
|
||||
|
||||
case 1:
|
||||
var commandName = args[0];
|
||||
if (!shell.ConsoleHost.AvailableCommands.TryGetValue(commandName, out var cmd))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-help-unknown", ("command", commandName)));
|
||||
shell.WriteError(Loc.GetString("cmd-oldhelp-unknown", ("command", commandName)));
|
||||
return;
|
||||
}
|
||||
|
||||
shell.WriteLine(Loc.GetString("cmd-help-top", ("command", cmd.Command),
|
||||
shell.WriteLine(Loc.GetString("cmd-oldhelp-top", ("command", cmd.Command),
|
||||
("description", cmd.Description)));
|
||||
shell.WriteLine(cmd.Help);
|
||||
break;
|
||||
|
||||
default:
|
||||
shell.WriteError(Loc.GetString("cmd-help-invalid-args"));
|
||||
shell.WriteError(Loc.GetString("cmd-oldhelp-invalid-args"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ internal sealed class HelpCommand : LocalizedCommands
|
||||
var host = shell.ConsoleHost;
|
||||
return CompletionResult.FromHintOptions(
|
||||
host.AvailableCommands.Values.OrderBy(c => c.Command).Select(c => new CompletionOption(c.Command, c.Description)).ToArray(),
|
||||
Loc.GetString("cmd-help-arg-cmdname"));
|
||||
Loc.GetString("cmd-oldhelp-arg-cmdname"));
|
||||
}
|
||||
|
||||
return CompletionResult.Empty;
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Globalization;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Placement;
|
||||
|
||||
namespace Robust.Shared.Console.Commands;
|
||||
|
||||
@@ -21,14 +22,20 @@ public sealed class SpawnCommand : LocalizedCommands
|
||||
|
||||
var pAE = shell.Player?.AttachedEntity ?? EntityUid.Invalid;
|
||||
|
||||
PlacementEntityEvent? placementEv = null;
|
||||
|
||||
if (args.Length == 1 && pAE != EntityUid.Invalid)
|
||||
{
|
||||
_entityManager.SpawnEntity(args[0], _entityManager.GetComponent<TransformComponent>(pAE).Coordinates);
|
||||
var entityCoordinates = _entityManager.GetComponent<TransformComponent>(pAE).Coordinates;
|
||||
var createdEntity = _entityManager.SpawnEntity(args[0], entityCoordinates);
|
||||
placementEv = new PlacementEntityEvent(createdEntity, entityCoordinates, PlacementEventAction.Create, shell.Player?.UserId);
|
||||
}
|
||||
else if (args.Length == 2)
|
||||
{
|
||||
var uid = EntityUid.Parse(args[1]);
|
||||
_entityManager.SpawnEntity(args[0], _entityManager.GetComponent<TransformComponent>(uid).Coordinates);
|
||||
var entityCoordinates = _entityManager.GetComponent<TransformComponent>(uid).Coordinates;
|
||||
var createdEntity = _entityManager.SpawnEntity(args[0], entityCoordinates);
|
||||
placementEv = new PlacementEntityEvent(createdEntity, entityCoordinates, PlacementEventAction.Create, shell.Player?.UserId);
|
||||
}
|
||||
else if (pAE != EntityUid.Invalid)
|
||||
{
|
||||
@@ -37,7 +44,11 @@ public sealed class SpawnCommand : LocalizedCommands
|
||||
float.Parse(args[2], CultureInfo.InvariantCulture),
|
||||
_entityManager.GetComponent<TransformComponent>(pAE).MapID);
|
||||
|
||||
_entityManager.SpawnEntity(args[0], coords);
|
||||
var createdEntity = _entityManager.SpawnEntity(args[0], coords);
|
||||
placementEv = new PlacementEntityEvent(createdEntity, _entityManager.GetComponent<TransformComponent>(createdEntity).Coordinates, PlacementEventAction.Create, shell.Player?.UserId);
|
||||
}
|
||||
|
||||
if (placementEv != null)
|
||||
_entityManager.EventBus.RaiseEvent(EventSource.Local, placementEv.Value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ using Robust.Shared.Network;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.Console
|
||||
@@ -173,6 +174,8 @@ namespace Robust.Shared.Console
|
||||
|
||||
//TODO: IConsoleOutput for [e#1225]
|
||||
public abstract void WriteLine(ICommonSession? session, string text);
|
||||
public abstract void WriteLine(ICommonSession? session, FormattedMessage msg);
|
||||
|
||||
public abstract void WriteError(ICommonSession? session, string text);
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -324,10 +327,11 @@ namespace Robust.Shared.Console
|
||||
public ValueTask<CompletionResult> GetCompletionAsync(
|
||||
IConsoleShell shell,
|
||||
string[] args,
|
||||
string argStr,
|
||||
CancellationToken cancel)
|
||||
{
|
||||
if (CompletionCallbackAsync != null)
|
||||
return CompletionCallbackAsync(shell, args);
|
||||
return CompletionCallbackAsync(shell, args, argStr);
|
||||
|
||||
if (CompletionCallback != null)
|
||||
return ValueTask.FromResult(CompletionCallback(shell, args));
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Console
|
||||
{
|
||||
@@ -48,6 +49,11 @@ namespace Robust.Shared.Console
|
||||
ConsoleHost.WriteLine(Player, text);
|
||||
}
|
||||
|
||||
public void WriteLine(FormattedMessage message)
|
||||
{
|
||||
ConsoleHost.WriteLine(Player, message);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void WriteError(string text)
|
||||
{
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
@@ -8,7 +9,7 @@ namespace Robust.Shared.Console
|
||||
/// Basic interface to handle console commands. Any class implementing this will be
|
||||
/// registered with the console system through reflection.
|
||||
/// </summary>
|
||||
[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)]
|
||||
[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors), Obsolete("New commands should utilize RtShellCommand.")]
|
||||
public interface IConsoleCommand
|
||||
{
|
||||
/// <summary>
|
||||
@@ -78,7 +79,7 @@ namespace Robust.Shared.Console
|
||||
/// <remarks>
|
||||
/// If this method is implemented, <see cref="GetCompletion"/> will not be automatically called.
|
||||
/// </remarks>
|
||||
ValueTask<CompletionResult> GetCompletionAsync(IConsoleShell shell, string[] args, CancellationToken cancel)
|
||||
ValueTask<CompletionResult> GetCompletionAsync(IConsoleShell shell, string[] args, string argStr, CancellationToken cancel)
|
||||
{
|
||||
return ValueTask.FromResult(GetCompletion(shell, args));
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Console
|
||||
{
|
||||
@@ -21,7 +22,7 @@ namespace Robust.Shared.Console
|
||||
/// <summary>
|
||||
/// Called to fetch completions for a console command (async). See <see cref="IConsoleCommand.GetCompletionAsync"/> for details.
|
||||
/// </summary>
|
||||
public delegate ValueTask<CompletionResult> ConCommandCompletionAsyncCallback(IConsoleShell shell, string[] args);
|
||||
public delegate ValueTask<CompletionResult> ConCommandCompletionAsyncCallback(IConsoleShell shell, string[] args, string argStr);
|
||||
|
||||
public delegate void ConAnyCommandCallback(IConsoleShell shell, string commandName, string argStr, string[] args);
|
||||
|
||||
@@ -249,6 +250,8 @@ namespace Robust.Shared.Console
|
||||
/// <param name="text">Text message to send.</param>
|
||||
void WriteLine(ICommonSession? session, string text);
|
||||
|
||||
void WriteLine(ICommonSession? session, FormattedMessage msg);
|
||||
|
||||
/// <summary>
|
||||
/// Sends a foreground colored text string to the remote session.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Console
|
||||
{
|
||||
@@ -54,6 +55,8 @@ namespace Robust.Shared.Console
|
||||
/// <param name="text">Line of text to write.</param>
|
||||
void WriteLine(string text);
|
||||
|
||||
void WriteLine(FormattedMessage message);
|
||||
|
||||
/// <summary>
|
||||
/// Write an error line to the console window.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -5,6 +6,7 @@ using Robust.Shared.Localization;
|
||||
|
||||
namespace Robust.Shared.Console;
|
||||
|
||||
[Obsolete("You should use ToolshedCommand instead.")]
|
||||
public abstract class LocalizedCommands : IConsoleCommand
|
||||
{
|
||||
[Dependency] protected readonly ILocalizationManager LocalizationManager = default!;
|
||||
@@ -23,12 +25,12 @@ public abstract class LocalizedCommands : IConsoleCommand
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract void Execute(IConsoleShell shell, string argStr, string[] args);
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual CompletionResult GetCompletion(IConsoleShell shell, string[] args) => CompletionResult.Empty;
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual ValueTask<CompletionResult> GetCompletionAsync(IConsoleShell shell, string[] args,
|
||||
public virtual ValueTask<CompletionResult> GetCompletionAsync(IConsoleShell shell, string[] args, string argStr,
|
||||
CancellationToken cancel)
|
||||
{
|
||||
return ValueTask.FromResult(GetCompletion(shell, args));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Robust.Shared.Collections;
|
||||
|
||||
namespace Robust.Shared.ContentPack
|
||||
{
|
||||
@@ -14,7 +15,23 @@ namespace Robust.Shared.ContentPack
|
||||
/// <returns></returns>
|
||||
public static Assembly GetAssemblyByName(this AppDomain domain, string name)
|
||||
{
|
||||
return domain.GetAssemblies().Single(assembly => assembly.GetName().Name == name);
|
||||
var assemblies = new ValueList<Assembly>(1);
|
||||
|
||||
foreach (var assembly in domain.GetAssemblies())
|
||||
{
|
||||
if (assembly.GetName().Name != name)
|
||||
continue;
|
||||
|
||||
assemblies.Add(assembly);
|
||||
}
|
||||
|
||||
if (assemblies.Count != 1)
|
||||
{
|
||||
var assemblyDesc = string.Join(" ", assemblies.Select(o => o.GetName().Name));
|
||||
throw new InvalidOperationException($"Expected 1 assembly for {name}, found {assemblies.Count}. Found {assemblyDesc}");
|
||||
}
|
||||
|
||||
return assemblies[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -190,6 +191,14 @@ namespace Robust.Shared.ContentPack
|
||||
throw new FileNotFoundException($"Path '{path}' contains invalid characters/filenames.");
|
||||
}
|
||||
#endif
|
||||
|
||||
if (path.Value.CanonPath.EndsWith(ResPath.Separator))
|
||||
{
|
||||
// This is a folder, not a file.
|
||||
fileStream = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var (prefix, root) in _contentRoots)
|
||||
{
|
||||
if (!path.Value.TryRelativeTo(prefix, out var relative))
|
||||
|
||||
@@ -16,11 +16,11 @@ namespace Robust.Shared.GameObjects
|
||||
[DataDefinition]
|
||||
public sealed class PrototypeData
|
||||
{
|
||||
[DataField("key", readOnly: true, required: true)]
|
||||
public Enum UiKey { get; set; } = default!;
|
||||
[DataField("key", required: true)]
|
||||
public Enum UiKey { get; } = default!;
|
||||
|
||||
[DataField("type", readOnly: true, required: true)]
|
||||
public string ClientType { get; set; } = default!;
|
||||
[DataField("type", required: true)]
|
||||
public string ClientType { get; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum range before a BUI auto-closes. A non-positive number means there is no limit.
|
||||
|
||||
@@ -223,8 +223,16 @@ namespace Robust.Shared.GameObjects
|
||||
subscriptions.BroadcastRegistrations.Add(subscriptionTuple);
|
||||
|
||||
var inverseSubscription = _inverseEventSubscriptions.GetOrNew(subscriber);
|
||||
if (!inverseSubscription.ContainsKey(eventType))
|
||||
inverseSubscription.Add(eventType, subscriptionTuple);
|
||||
if (!inverseSubscription.TryAdd(eventType, subscriptionTuple))
|
||||
{
|
||||
// If this needs to be supported in the future, then event subscribing needs to be updated.
|
||||
// Also, we need to ensure that separate local + network subs dont break anything. If the event handlers
|
||||
// are identical, local+network subs should be combined into a single sub anyways. Separate subs are
|
||||
// probably just erroneous.
|
||||
var name = subscriber.GetType().Name;
|
||||
throw new InvalidOperationException(
|
||||
$"{name} attempted to subscribe twice to the same event: {eventType.Name}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -77,7 +77,10 @@ namespace Robust.Shared.GameObjects
|
||||
_netComponents.Clear();
|
||||
_entCompIndex.Clear();
|
||||
_deleteSet.Clear();
|
||||
FillComponentDict();
|
||||
foreach (var dict in _entTraitDict.Values)
|
||||
{
|
||||
dict.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void AddComponentRefType(CompIdx type)
|
||||
@@ -1267,28 +1270,26 @@ namespace Robust.Shared.GameObjects
|
||||
#endregion
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IComponent> GetAllComponents(Type type, bool includePaused = false)
|
||||
public IEnumerable<(EntityUid Uid, Component Component)> GetAllComponents(Type type, bool includePaused = false)
|
||||
{
|
||||
var comps = _entTraitDict[type];
|
||||
|
||||
if (includePaused)
|
||||
{
|
||||
foreach (var comp in comps.Values)
|
||||
foreach (var (uid, comp) in comps)
|
||||
{
|
||||
if (comp.Deleted) continue;
|
||||
|
||||
yield return comp;
|
||||
yield return (uid, comp);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var metaQuery = GetEntityQuery<MetaDataComponent>();
|
||||
|
||||
foreach (var comp in comps.Values)
|
||||
foreach (var (uid, comp) in comps)
|
||||
{
|
||||
if (comp.Deleted || !metaQuery.TryGetComponent(comp.Owner, out var meta) || meta.EntityPaused) continue;
|
||||
if (comp.Deleted || !_metaQuery.TryGetComponent(uid, out var meta) || meta.EntityPaused) continue;
|
||||
|
||||
yield return comp;
|
||||
yield return (uid, comp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +77,11 @@ namespace Robust.Shared.GameObjects
|
||||
public event Action<EntityUid>? EntityInitialized;
|
||||
public event Action<EntityUid>? EntityStarted;
|
||||
public event Action<EntityUid>? EntityDeleted;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when an entity is queued for deletion. Not raised if an entity is deleted.
|
||||
/// </summary>
|
||||
public event Action<EntityUid>? EntityQueueDeleted;
|
||||
public event Action<EntityUid>? EntityDirtied; // only raised after initialization
|
||||
|
||||
private string _xformName = string.Empty;
|
||||
@@ -637,8 +642,11 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
public virtual void QueueDeleteEntity(EntityUid uid)
|
||||
{
|
||||
if(QueuedDeletionsSet.Add(uid))
|
||||
QueuedDeletions.Enqueue(uid);
|
||||
if (!QueuedDeletionsSet.Add(uid))
|
||||
return;
|
||||
|
||||
QueuedDeletions.Enqueue(uid);
|
||||
EntityQueueDeleted?.Invoke(uid);
|
||||
}
|
||||
|
||||
public bool IsQueuedForDeletion(EntityUid uid) => QueuedDeletionsSet.Contains(uid);
|
||||
|
||||
@@ -186,7 +186,7 @@ public partial class EntitySystem
|
||||
/// Marks an entity as dirty.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected void Dirty(EntityUid uid, MetaDataComponent? meta = null)
|
||||
protected void DirtyEntity(EntityUid uid, MetaDataComponent? meta = null)
|
||||
{
|
||||
EntityManager.DirtyEntity(uid, meta);
|
||||
}
|
||||
@@ -296,7 +296,7 @@ public partial class EntitySystem
|
||||
if (!Exists(uid))
|
||||
return false;
|
||||
|
||||
Dirty(uid);
|
||||
DirtyEntity(uid);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -206,6 +206,10 @@ namespace Robust.Shared.GameObjects
|
||||
void IPostInjectInit.PostInject()
|
||||
{
|
||||
Log = LogManager.GetSawmill(SawmillName);
|
||||
|
||||
#if !DEBUG
|
||||
Log.Level = LogLevel.Info;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -393,6 +393,9 @@ namespace Robust.Shared.GameObjects
|
||||
return mFrameUpdate!.DeclaringType != typeof(EntitySystem);
|
||||
}
|
||||
|
||||
internal IEnumerable<Type> FrameUpdateOrder => _frameUpdateOrder.Select(c => c.GetType());
|
||||
internal IEnumerable<Type> TickUpdateOrder => _updateOrder.Select(c => c.System.GetType());
|
||||
|
||||
private struct UpdateReg
|
||||
{
|
||||
[ViewVariables] public IEntitySystem System;
|
||||
|
||||
28
Robust.Shared/GameObjects/EntitySystemUpdateOrderCommand.cs
Normal file
28
Robust.Shared/GameObjects/EntitySystemUpdateOrderCommand.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Toolshed;
|
||||
|
||||
namespace Robust.Shared.GameObjects;
|
||||
|
||||
[ToolshedCommand]
|
||||
internal sealed class EntitySystemUpdateOrderCommand : ToolshedCommand
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
|
||||
[CommandImplementation("tick")]
|
||||
public IEnumerable<Type> Tick()
|
||||
{
|
||||
var mgr = (EntitySystemManager)_entitySystemManager;
|
||||
|
||||
return mgr.TickUpdateOrder;
|
||||
}
|
||||
|
||||
[CommandImplementation("frame")]
|
||||
public IEnumerable<Type> Frame()
|
||||
{
|
||||
var mgr = (EntitySystemManager)_entitySystemManager;
|
||||
|
||||
return mgr.FrameUpdateOrder;
|
||||
}
|
||||
}
|
||||
@@ -488,7 +488,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <param name="type">A trait or component type to check for.</param>
|
||||
/// <param name="includePaused"></param>
|
||||
/// <returns>All components that are the specified type.</returns>
|
||||
IEnumerable<IComponent> GetAllComponents(Type type, bool includePaused = false);
|
||||
IEnumerable<(EntityUid Uid, Component Component)> GetAllComponents(Type type, bool includePaused = false);
|
||||
|
||||
/// <summary>
|
||||
/// Culls all components from the collection that are marked as deleted. This needs to be called often.
|
||||
|
||||
@@ -25,29 +25,34 @@ public sealed partial class EntityLookupSystem
|
||||
private void AddEntitiesIntersecting(
|
||||
EntityUid lookupUid,
|
||||
HashSet<EntityUid> intersecting,
|
||||
Box2 worldAABB,
|
||||
Box2 localAABB,
|
||||
LookupFlags flags)
|
||||
{
|
||||
var lookup = _broadQuery.GetComponent(lookupUid);
|
||||
var invMatrix = _transform.GetInvWorldMatrix(lookupUid);
|
||||
var localAABB = invMatrix.TransformBox(worldAABB);
|
||||
var state = (intersecting, flags);
|
||||
|
||||
if ((flags & LookupFlags.Dynamic) != 0x0)
|
||||
{
|
||||
lookup.DynamicTree.QueryAabb(ref intersecting,
|
||||
static (ref HashSet<EntityUid> state, in FixtureProxy value) =>
|
||||
lookup.DynamicTree.QueryAabb(ref state,
|
||||
static (ref (HashSet<EntityUid> intersecting, LookupFlags flags) tuple, in FixtureProxy value) =>
|
||||
{
|
||||
state.Add(value.Entity);
|
||||
if ((tuple.flags & LookupFlags.Sensors) == 0x0 && !value.Fixture.Hard)
|
||||
return true;
|
||||
|
||||
tuple.intersecting.Add(value.Entity);
|
||||
return true;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.Static) != 0x0)
|
||||
{
|
||||
lookup.StaticTree.QueryAabb(ref intersecting,
|
||||
static (ref HashSet<EntityUid> state, in FixtureProxy value) =>
|
||||
lookup.StaticTree.QueryAabb(ref state,
|
||||
static (ref (HashSet<EntityUid> intersecting, LookupFlags flags) tuple, in FixtureProxy value) =>
|
||||
{
|
||||
state.Add(value.Entity);
|
||||
if ((tuple.flags & LookupFlags.Sensors) == 0x0 && !value.Fixture.Hard)
|
||||
return true;
|
||||
|
||||
tuple.intersecting.Add(value.Entity);
|
||||
return true;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
@@ -79,50 +84,12 @@ public sealed partial class EntityLookupSystem
|
||||
Box2Rotated worldBounds,
|
||||
LookupFlags flags)
|
||||
{
|
||||
var lookup = _broadQuery.GetComponent(lookupUid);
|
||||
var invMatrix = _transform.GetInvWorldMatrix(lookupUid);
|
||||
// We don't just use CalcBoundingBox because the transformed bounds might be tighter.
|
||||
var localAABB = invMatrix.TransformBox(worldBounds);
|
||||
|
||||
if ((flags & LookupFlags.Dynamic) != 0x0)
|
||||
{
|
||||
lookup.DynamicTree.QueryAabb(ref intersecting,
|
||||
static (ref HashSet<EntityUid> state, in FixtureProxy value) =>
|
||||
{
|
||||
state.Add(value.Entity);
|
||||
return true;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.Static) != 0x0)
|
||||
{
|
||||
lookup.StaticTree.QueryAabb(ref intersecting,
|
||||
static (ref HashSet<EntityUid> state, in FixtureProxy value) =>
|
||||
{
|
||||
state.Add(value.Entity);
|
||||
return true;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.StaticSundries) == LookupFlags.StaticSundries)
|
||||
{
|
||||
lookup.StaticSundriesTree.QueryAabb(ref intersecting,
|
||||
static (ref HashSet<EntityUid> state, in EntityUid value) =>
|
||||
{
|
||||
state.Add(value);
|
||||
return true;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.Sundries) != 0x0)
|
||||
{
|
||||
lookup.SundriesTree.QueryAabb(ref intersecting,
|
||||
static (ref HashSet<EntityUid> state, in EntityUid value) =>
|
||||
{
|
||||
state.Add(value);
|
||||
return true;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
// Someday we'll split these but maybe it's wishful thinking.
|
||||
AddEntitiesIntersecting(lookupUid, intersecting, localAABB, flags);
|
||||
}
|
||||
|
||||
private bool AnyEntitiesIntersecting(EntityUid lookupUid,
|
||||
@@ -132,35 +99,41 @@ public sealed partial class EntityLookupSystem
|
||||
{
|
||||
var lookup = _broadQuery.GetComponent(lookupUid);
|
||||
var localAABB = _transform.GetInvWorldMatrix(lookupUid).TransformBox(worldAABB);
|
||||
var state = (ignored, found: false);
|
||||
var state = (ignored, flags, found: false);
|
||||
|
||||
if ((flags & LookupFlags.Dynamic) != 0x0)
|
||||
{
|
||||
lookup.DynamicTree.QueryAabb(ref state, (ref (EntityUid? ignored, bool found) tuple, in FixtureProxy value) =>
|
||||
lookup.DynamicTree.QueryAabb(ref state, static (ref (EntityUid? ignored, LookupFlags flags, bool found) tuple, in FixtureProxy value) =>
|
||||
{
|
||||
if (tuple.ignored == value.Entity)
|
||||
if (tuple.ignored == value.Entity || ((tuple.flags & LookupFlags.Sensors) == 0x0 && !value.Fixture.Hard))
|
||||
return true;
|
||||
|
||||
tuple.found = true;
|
||||
return false;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
|
||||
if (state.found)
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.Static) != 0x0)
|
||||
{
|
||||
lookup.StaticTree.QueryAabb(ref state, (ref (EntityUid? ignored, bool found) tuple, in FixtureProxy value) =>
|
||||
lookup.StaticTree.QueryAabb(ref state, static (ref (EntityUid? ignored, LookupFlags flags, bool found) tuple, in FixtureProxy value) =>
|
||||
{
|
||||
if (tuple.ignored == value.Entity)
|
||||
if (tuple.ignored == value.Entity || ((tuple.flags & LookupFlags.Sensors) == 0x0 && !value.Fixture.Hard))
|
||||
return true;
|
||||
|
||||
tuple.found = true;
|
||||
return false;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
|
||||
if (state.found)
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.StaticSundries) == LookupFlags.StaticSundries)
|
||||
{
|
||||
lookup.StaticSundriesTree.QueryAabb(ref state, static (ref (EntityUid? ignored, bool found) tuple, in EntityUid value) =>
|
||||
lookup.StaticSundriesTree.QueryAabb(ref state, static (ref (EntityUid? ignored, LookupFlags flags, bool found) tuple, in EntityUid value) =>
|
||||
{
|
||||
if (tuple.ignored == value)
|
||||
return true;
|
||||
@@ -168,11 +141,14 @@ public sealed partial class EntityLookupSystem
|
||||
tuple.found = true;
|
||||
return false;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
|
||||
if (state.found)
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.Sundries) != 0x0)
|
||||
{
|
||||
lookup.SundriesTree.QueryAabb(ref state, static (ref (EntityUid? ignored, bool found) tuple, in EntityUid value) =>
|
||||
lookup.SundriesTree.QueryAabb(ref state, static (ref (EntityUid? ignored, LookupFlags flags, bool found) tuple, in EntityUid value) =>
|
||||
{
|
||||
if (tuple.ignored == value)
|
||||
return true;
|
||||
@@ -182,10 +158,7 @@ public sealed partial class EntityLookupSystem
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
|
||||
if (state.found)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
return state.found;
|
||||
}
|
||||
|
||||
private bool AnyEntitiesIntersecting(EntityUid lookupUid,
|
||||
@@ -193,62 +166,8 @@ public sealed partial class EntityLookupSystem
|
||||
LookupFlags flags,
|
||||
EntityUid? ignored = null)
|
||||
{
|
||||
var lookup = _broadQuery.GetComponent(lookupUid);
|
||||
var localAABB = _transform.GetInvWorldMatrix(lookupUid).TransformBox(worldBounds);
|
||||
var state = (ignored, found: false);
|
||||
|
||||
if ((flags & LookupFlags.Dynamic) != 0x0)
|
||||
{
|
||||
lookup.DynamicTree.QueryAabb(ref state, (ref (EntityUid? ignored, bool found) tuple, in FixtureProxy value) =>
|
||||
{
|
||||
if (tuple.ignored == value.Entity)
|
||||
return true;
|
||||
|
||||
tuple.found = true;
|
||||
return false;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.Static) != 0x0)
|
||||
{
|
||||
lookup.StaticTree.QueryAabb(ref state, (ref (EntityUid? ignored, bool found) tuple, in FixtureProxy value) =>
|
||||
{
|
||||
if (tuple.ignored == value.Entity)
|
||||
return true;
|
||||
|
||||
tuple.found = true;
|
||||
return false;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.StaticSundries) == LookupFlags.StaticSundries)
|
||||
{
|
||||
lookup.StaticSundriesTree.QueryAabb(ref state, static (ref (EntityUid? ignored, bool found) tuple, in EntityUid value) =>
|
||||
{
|
||||
if (tuple.ignored == value)
|
||||
return true;
|
||||
|
||||
tuple.found = true;
|
||||
return false;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.Sundries) != 0x0)
|
||||
{
|
||||
lookup.SundriesTree.QueryAabb(ref state, static (ref (EntityUid? ignored, bool found) tuple, in EntityUid value) =>
|
||||
{
|
||||
if (tuple.ignored == value)
|
||||
return true;
|
||||
|
||||
tuple.found = true;
|
||||
return false;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
|
||||
if (state.found)
|
||||
return true;
|
||||
|
||||
return state.found;
|
||||
return AnyEntitiesIntersecting(lookupUid, localAABB, flags, ignored);
|
||||
}
|
||||
|
||||
private void RecursiveAdd(EntityUid uid, ref ValueList<EntityUid> toAdd)
|
||||
@@ -359,14 +278,15 @@ public sealed partial class EntityLookupSystem
|
||||
var intersecting = new HashSet<EntityUid>();
|
||||
|
||||
// Get grid entities
|
||||
var state = (this, _map, intersecting, worldAABB, flags);
|
||||
var state = (this, _map, intersecting, worldAABB, _transform, flags);
|
||||
|
||||
_mapManager.FindGridsIntersecting(mapId, worldAABB, ref state,
|
||||
static (EntityUid gridUid, MapGridComponent grid, ref (
|
||||
EntityLookupSystem lookup, SharedMapSystem _map, HashSet<EntityUid> intersecting,
|
||||
Box2 worldAABB, LookupFlags flags) tuple) =>
|
||||
Box2 worldAABB, SharedTransformSystem xformSystem, LookupFlags flags) tuple) =>
|
||||
{
|
||||
tuple.lookup.AddEntitiesIntersecting(gridUid, tuple.intersecting, tuple.worldAABB, tuple.flags);
|
||||
var localAABB = tuple.xformSystem.GetInvWorldMatrix(gridUid).TransformBox(tuple.worldAABB);
|
||||
tuple.lookup.AddEntitiesIntersecting(gridUid, tuple.intersecting, localAABB, tuple.flags);
|
||||
|
||||
if ((tuple.flags & LookupFlags.Static) != 0x0)
|
||||
{
|
||||
@@ -385,7 +305,9 @@ public sealed partial class EntityLookupSystem
|
||||
|
||||
// Get map entities
|
||||
var mapUid = _mapManager.GetMapEntityId(mapId);
|
||||
AddEntitiesIntersecting(mapUid, intersecting, worldAABB, flags);
|
||||
// Transform just in case future proofing?
|
||||
var localAABB = _transform.GetInvWorldMatrix(mapUid).TransformBox(worldAABB);
|
||||
AddEntitiesIntersecting(mapUid, intersecting, localAABB, flags);
|
||||
AddContained(intersecting, flags);
|
||||
|
||||
return intersecting;
|
||||
@@ -656,42 +578,7 @@ public sealed partial class EntityLookupSystem
|
||||
foreach (var index in gridIndices)
|
||||
{
|
||||
var aabb = GetLocalBounds(index, tileSize);
|
||||
|
||||
if ((flags & LookupFlags.Dynamic) != 0x0)
|
||||
{
|
||||
lookup.DynamicTree.QueryAabb(ref intersecting, static (ref HashSet<EntityUid> state, in FixtureProxy value) =>
|
||||
{
|
||||
state.Add(value.Entity);
|
||||
return true;
|
||||
}, aabb, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.Static) != 0x0)
|
||||
{
|
||||
lookup.StaticTree.QueryAabb(ref intersecting, static (ref HashSet<EntityUid> state, in FixtureProxy value) =>
|
||||
{
|
||||
state.Add(value.Entity);
|
||||
return true;
|
||||
}, aabb, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.StaticSundries) == LookupFlags.StaticSundries)
|
||||
{
|
||||
lookup.StaticSundriesTree.QueryAabb(ref intersecting, static (ref HashSet<EntityUid> intersecting, in EntityUid value) =>
|
||||
{
|
||||
intersecting.Add(value);
|
||||
return true;
|
||||
}, aabb, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.Sundries) != 0x0)
|
||||
{
|
||||
lookup.SundriesTree.QueryAabb(ref intersecting, static (ref HashSet<EntityUid> intersecting, in EntityUid value) =>
|
||||
{
|
||||
intersecting.Add(value);
|
||||
return true;
|
||||
}, aabb, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
intersecting.UnionWith(GetEntitiesIntersecting(lookup, aabb, flags));
|
||||
}
|
||||
|
||||
AddContained(intersecting, flags);
|
||||
@@ -699,7 +586,7 @@ public sealed partial class EntityLookupSystem
|
||||
return intersecting;
|
||||
}
|
||||
|
||||
public HashSet<EntityUid> GetEntitiesIntersecting(EntityUid gridId, Vector2i gridIndices, LookupFlags flags = DefaultFlags)
|
||||
public HashSet<EntityUid> GetEntitiesIntersecting(EntityUid gridId, Vector2i gridIndices, float enlargement = TileEnlargementRadius, LookupFlags flags = DefaultFlags)
|
||||
{
|
||||
// Technically this doesn't consider anything overlapping from outside the grid but is this an issue?
|
||||
if (!_mapManager.TryGetGrid(gridId, out var grid))
|
||||
@@ -708,51 +595,70 @@ public sealed partial class EntityLookupSystem
|
||||
var lookup = _broadQuery.GetComponent(gridId);
|
||||
var tileSize = grid.TileSize;
|
||||
var aabb = GetLocalBounds(gridIndices, tileSize);
|
||||
aabb = aabb.Enlarged(enlargement);
|
||||
return GetEntitiesIntersecting(lookup, aabb, flags);
|
||||
}
|
||||
|
||||
public HashSet<EntityUid> GetEntitiesIntersecting(BroadphaseComponent lookup, Box2 aabb, LookupFlags flags = DefaultFlags)
|
||||
public HashSet<EntityUid> GetEntitiesIntersecting(BroadphaseComponent lookup, Box2 localAABB, LookupFlags flags = DefaultFlags)
|
||||
{
|
||||
var intersecting = new HashSet<EntityUid>();
|
||||
// Dummy tree
|
||||
var state = (lookup.StaticSundriesTree._b2Tree, intersecting, flags);
|
||||
|
||||
if ((flags & LookupFlags.Dynamic) != 0x0)
|
||||
{
|
||||
lookup.DynamicTree.QueryAabb(ref intersecting,
|
||||
static (ref HashSet<EntityUid> intersecting, in FixtureProxy value) =>
|
||||
lookup.DynamicTree.QueryAabb(ref state,
|
||||
static (ref (B2DynamicTree<EntityUid> _, HashSet<EntityUid> intersecting, LookupFlags flags) tuple,
|
||||
in FixtureProxy value) =>
|
||||
{
|
||||
intersecting.Add(value.Entity);
|
||||
if ((tuple.flags & LookupFlags.Sensors) != 0x0 || value.Fixture.Hard)
|
||||
{
|
||||
tuple.intersecting.Add(value.Entity);
|
||||
}
|
||||
|
||||
return true;
|
||||
}, aabb, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.Static) != 0x0)
|
||||
{
|
||||
lookup.StaticTree.QueryAabb(ref intersecting,
|
||||
static (ref HashSet<EntityUid> intersecting, in FixtureProxy value) =>
|
||||
lookup.StaticTree.QueryAabb(ref state,
|
||||
static (ref (B2DynamicTree<EntityUid> _, HashSet<EntityUid> intersecting, LookupFlags flags) tuple,
|
||||
in FixtureProxy value) =>
|
||||
{
|
||||
intersecting.Add(value.Entity);
|
||||
if ((tuple.flags & LookupFlags.Sensors) != 0x0 || value.Fixture.Hard)
|
||||
{
|
||||
tuple.intersecting.Add(value.Entity);
|
||||
}
|
||||
|
||||
return true;
|
||||
}, aabb, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
|
||||
var state = (lookup.StaticSundriesTree._b2Tree, intersecting);
|
||||
if ((flags & LookupFlags.StaticSundries) == LookupFlags.StaticSundries)
|
||||
{
|
||||
lookup.StaticSundriesTree._b2Tree.Query(ref state, static (ref (B2DynamicTree<EntityUid> _b2Tree, HashSet<EntityUid> intersecting) tuple, DynamicTree.Proxy proxy) =>
|
||||
{
|
||||
tuple.intersecting.Add(tuple._b2Tree.GetUserData(proxy));
|
||||
return true;
|
||||
}, aabb);
|
||||
state = (lookup.StaticSundriesTree._b2Tree, intersecting, flags);
|
||||
|
||||
lookup.StaticSundriesTree._b2Tree.Query(ref state,
|
||||
static (ref (B2DynamicTree<EntityUid> _b2Tree, HashSet<EntityUid> intersecting, LookupFlags flags) tuple,
|
||||
DynamicTree.Proxy proxy) =>
|
||||
{
|
||||
tuple.intersecting.Add(tuple._b2Tree.GetUserData(proxy));
|
||||
return true;
|
||||
}, localAABB);
|
||||
}
|
||||
|
||||
state = (lookup.SundriesTree._b2Tree, intersecting);
|
||||
if ((flags & LookupFlags.Sundries) != 0x0)
|
||||
{
|
||||
lookup.SundriesTree._b2Tree.Query(ref state, static (ref (B2DynamicTree<EntityUid> _b2Tree, HashSet<EntityUid> intersecting) tuple, DynamicTree.Proxy proxy) =>
|
||||
{
|
||||
tuple.intersecting.Add(tuple._b2Tree.GetUserData(proxy));
|
||||
return true;
|
||||
}, aabb);
|
||||
state = (lookup.SundriesTree._b2Tree, intersecting, flags);
|
||||
|
||||
lookup.SundriesTree._b2Tree.Query(ref state,
|
||||
static (ref (B2DynamicTree<EntityUid> _b2Tree, HashSet<EntityUid> intersecting, LookupFlags flags) tuple,
|
||||
DynamicTree.Proxy proxy) =>
|
||||
{
|
||||
tuple.intersecting.Add(tuple._b2Tree.GetUserData(proxy));
|
||||
return true;
|
||||
}, localAABB);
|
||||
}
|
||||
|
||||
AddContained(intersecting, flags);
|
||||
@@ -762,12 +668,13 @@ public sealed partial class EntityLookupSystem
|
||||
|
||||
public HashSet<EntityUid> GetEntitiesIntersecting(EntityUid gridId, Box2 worldAABB, LookupFlags flags = DefaultFlags)
|
||||
{
|
||||
if (!_mapManager.GridExists(gridId))
|
||||
return new HashSet<EntityUid>();
|
||||
|
||||
var intersecting = new HashSet<EntityUid>();
|
||||
|
||||
AddEntitiesIntersecting(gridId, intersecting, worldAABB, flags);
|
||||
if (!_mapManager.GridExists(gridId))
|
||||
return intersecting;
|
||||
|
||||
var localAABB = _transform.GetInvWorldMatrix(gridId).TransformBox(worldAABB);
|
||||
AddEntitiesIntersecting(gridId, intersecting, localAABB, flags);
|
||||
AddContained(intersecting, flags);
|
||||
|
||||
return intersecting;
|
||||
@@ -775,10 +682,11 @@ public sealed partial class EntityLookupSystem
|
||||
|
||||
public HashSet<EntityUid> GetEntitiesIntersecting(EntityUid gridId, Box2Rotated worldBounds, LookupFlags flags = DefaultFlags)
|
||||
{
|
||||
if (!_mapManager.GridExists(gridId)) return new HashSet<EntityUid>();
|
||||
|
||||
var intersecting = new HashSet<EntityUid>();
|
||||
|
||||
if (!_mapManager.GridExists(gridId))
|
||||
return intersecting;
|
||||
|
||||
AddEntitiesIntersecting(gridId, intersecting, worldBounds, flags);
|
||||
AddContained(intersecting, flags);
|
||||
|
||||
@@ -786,104 +694,9 @@ public sealed partial class EntityLookupSystem
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public IEnumerable<EntityUid> GetEntitiesIntersecting(TileRef tileRef, LookupFlags flags = DefaultFlags)
|
||||
public IEnumerable<EntityUid> GetEntitiesIntersecting(TileRef tileRef, float enlargement = TileEnlargementRadius, LookupFlags flags = DefaultFlags)
|
||||
{
|
||||
return GetEntitiesIntersecting(tileRef.GridUid, tileRef.GridIndices, flags);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Lookup Query
|
||||
|
||||
public HashSet<EntityUid> GetEntitiesIntersecting(BroadphaseComponent component, ref Box2 worldAABB, LookupFlags flags = DefaultFlags)
|
||||
{
|
||||
var intersecting = new HashSet<EntityUid>();
|
||||
var localAABB = _transform.GetInvWorldMatrix(component.Owner).TransformBox(worldAABB);
|
||||
|
||||
if ((flags & LookupFlags.Dynamic) != 0x0)
|
||||
{
|
||||
component.DynamicTree.QueryAabb(ref intersecting, static (ref HashSet<EntityUid> intersecting, in FixtureProxy value) =>
|
||||
{
|
||||
intersecting.Add(value.Entity);
|
||||
return true;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.Static) != 0x0)
|
||||
{
|
||||
component.StaticTree.QueryAabb(ref intersecting, static (ref HashSet<EntityUid> intersecting, in FixtureProxy value) =>
|
||||
{
|
||||
intersecting.Add(value.Entity);
|
||||
return true;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.StaticSundries) == LookupFlags.StaticSundries)
|
||||
{
|
||||
component.StaticSundriesTree.QueryAabb(ref intersecting, static (ref HashSet<EntityUid> intersecting, in EntityUid value) =>
|
||||
{
|
||||
intersecting.Add(value);
|
||||
return true;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.Sundries) != 0x0)
|
||||
{
|
||||
component.SundriesTree.QueryAabb(ref intersecting, static (ref HashSet<EntityUid> intersecting, in EntityUid value) =>
|
||||
{
|
||||
intersecting.Add(value);
|
||||
return true;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
|
||||
AddContained(intersecting, flags);
|
||||
|
||||
return intersecting;
|
||||
}
|
||||
|
||||
public HashSet<EntityUid> GetLocalEntitiesIntersecting(BroadphaseComponent component, Box2 localAABB, LookupFlags flags = DefaultFlags)
|
||||
{
|
||||
var intersecting = new HashSet<EntityUid>();
|
||||
|
||||
if ((flags & LookupFlags.Dynamic) != 0x0)
|
||||
{
|
||||
component.DynamicTree.QueryAabb(ref intersecting, static (ref HashSet<EntityUid> intersecting, in FixtureProxy value) =>
|
||||
{
|
||||
intersecting.Add(value.Entity);
|
||||
return true;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.Static) != 0x0)
|
||||
{
|
||||
component.StaticTree.QueryAabb(ref intersecting, static (ref HashSet<EntityUid> intersecting, in FixtureProxy value) =>
|
||||
{
|
||||
intersecting.Add(value.Entity);
|
||||
return true;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.StaticSundries) == LookupFlags.StaticSundries)
|
||||
{
|
||||
component.StaticSundriesTree.QueryAabb(ref intersecting, static (ref HashSet<EntityUid> intersecting, in EntityUid value) =>
|
||||
{
|
||||
intersecting.Add(value);
|
||||
return true;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.Sundries) != 0x0)
|
||||
{
|
||||
component.SundriesTree.QueryAabb(ref intersecting, static (ref HashSet<EntityUid> intersecting, in EntityUid value) =>
|
||||
{
|
||||
intersecting.Add(value);
|
||||
return true;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
|
||||
AddContained(intersecting, flags);
|
||||
|
||||
return intersecting;
|
||||
return GetEntitiesIntersecting(tileRef.GridUid, tileRef.GridIndices, enlargement, flags);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Numerics;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
@@ -20,12 +21,10 @@ public sealed partial class EntityLookupSystem
|
||||
HashSet<T> intersecting,
|
||||
Box2 worldAABB,
|
||||
LookupFlags flags,
|
||||
EntityQuery<BroadphaseComponent> lookupQuery,
|
||||
EntityQuery<TransformComponent> xformQuery,
|
||||
EntityQuery<T> query) where T : Component
|
||||
{
|
||||
var lookup = lookupQuery.GetComponent(lookupUid);
|
||||
var invMatrix = _transform.GetInvWorldMatrix(lookupUid, xformQuery);
|
||||
var lookup = _broadQuery.GetComponent(lookupUid);
|
||||
var invMatrix = _transform.GetInvWorldMatrix(lookupUid);
|
||||
var localAABB = invMatrix.TransformBox(worldAABB);
|
||||
var state = (intersecting, query);
|
||||
|
||||
@@ -78,9 +77,85 @@ public sealed partial class EntityLookupSystem
|
||||
}
|
||||
}
|
||||
|
||||
private void RecursiveAdd<T>(EntityUid uid, ref ValueList<T> toAdd, EntityQuery<TransformComponent> xformQuery, EntityQuery<T> query) where T : Component
|
||||
private bool AnyComponentsIntersecting<T>(
|
||||
EntityUid lookupUid,
|
||||
Box2 worldAABB,
|
||||
LookupFlags flags,
|
||||
EntityQuery<T> query,
|
||||
EntityUid? ignored = null) where T : Component
|
||||
{
|
||||
var childEnumerator = xformQuery.GetComponent(uid).ChildEnumerator;
|
||||
var lookup = _broadQuery.GetComponent(lookupUid);
|
||||
var invMatrix = _transform.GetInvWorldMatrix(lookupUid);
|
||||
var localAABB = invMatrix.TransformBox(worldAABB);
|
||||
var state = (query, ignored, found: false);
|
||||
|
||||
if ((flags & LookupFlags.Dynamic) != 0x0)
|
||||
{
|
||||
lookup.DynamicTree.QueryAabb(ref state,
|
||||
static (ref (EntityQuery<T> query, EntityUid? ignored, bool found) tuple, in FixtureProxy value) =>
|
||||
{
|
||||
if (value.Entity == tuple.ignored)
|
||||
return true;
|
||||
|
||||
tuple.found = true;
|
||||
return false;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
|
||||
if (state.found)
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.Static) != 0x0)
|
||||
{
|
||||
lookup.StaticTree.QueryAabb(ref state,
|
||||
static (ref (EntityQuery<T> query, EntityUid? ignored, bool found) tuple, in FixtureProxy value) =>
|
||||
{
|
||||
if (value.Entity == tuple.ignored)
|
||||
return true;
|
||||
|
||||
tuple.found = true;
|
||||
return false;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
|
||||
if (state.found)
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.StaticSundries) == LookupFlags.StaticSundries)
|
||||
{
|
||||
lookup.StaticSundriesTree.QueryAabb(ref state,
|
||||
static (ref (EntityQuery<T> query, EntityUid? ignored, bool found) tuple, in EntityUid value) =>
|
||||
{
|
||||
if (value == tuple.ignored)
|
||||
return true;
|
||||
|
||||
tuple.found = true;
|
||||
return false;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
|
||||
if (state.found)
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.Sundries) != 0x0)
|
||||
{
|
||||
lookup.SundriesTree.QueryAabb(ref state,
|
||||
static (ref (EntityQuery<T> query, EntityUid? ignored, bool found) tuple, in EntityUid value) =>
|
||||
{
|
||||
if (value == tuple.ignored)
|
||||
return true;
|
||||
|
||||
tuple.found = true;
|
||||
return false;
|
||||
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
|
||||
return state.found;
|
||||
}
|
||||
|
||||
private void RecursiveAdd<T>(EntityUid uid, ref ValueList<T> toAdd, EntityQuery<T> query) where T : Component
|
||||
{
|
||||
var childEnumerator = _xformQuery.GetComponent(uid).ChildEnumerator;
|
||||
|
||||
while (childEnumerator.MoveNext(out var child))
|
||||
{
|
||||
@@ -89,20 +164,19 @@ public sealed partial class EntityLookupSystem
|
||||
toAdd.Add(compies);
|
||||
}
|
||||
|
||||
RecursiveAdd(child.Value, ref toAdd, xformQuery, query);
|
||||
RecursiveAdd(child.Value, ref toAdd, query);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddContained<T>(HashSet<T> intersecting, LookupFlags flags, EntityQuery<TransformComponent> xformQuery, EntityQuery<T> query) where T : Component
|
||||
private void AddContained<T>(HashSet<T> intersecting, LookupFlags flags, EntityQuery<T> query) where T : Component
|
||||
{
|
||||
if ((flags & LookupFlags.Contained) == 0x0) return;
|
||||
|
||||
var conQuery = GetEntityQuery<ContainerManagerComponent>();
|
||||
var toAdd = new ValueList<T>();
|
||||
|
||||
foreach (var comp in intersecting)
|
||||
{
|
||||
if (!conQuery.TryGetComponent(comp.Owner, out var conManager)) continue;
|
||||
if (!_containerQuery.TryGetComponent(comp.Owner, out var conManager)) continue;
|
||||
|
||||
foreach (var con in conManager.GetAllContainers())
|
||||
{
|
||||
@@ -113,7 +187,7 @@ public sealed partial class EntityLookupSystem
|
||||
toAdd.Add(compies);
|
||||
}
|
||||
|
||||
RecursiveAdd(contained, ref toAdd, xformQuery, query);
|
||||
RecursiveAdd(contained, ref toAdd, query);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -148,26 +222,21 @@ public sealed partial class EntityLookupSystem
|
||||
// Like .Queries but works with components
|
||||
#region Box2
|
||||
|
||||
public bool AnyComponentsIntersecting(Type type, MapId mapId, Box2 worldAABB, LookupFlags flags = DefaultFlags)
|
||||
public bool AnyComponentsIntersecting(Type type, MapId mapId, Box2 worldAABB, EntityUid? ignored = null, LookupFlags flags = DefaultFlags)
|
||||
{
|
||||
DebugTools.Assert(typeof(Component).IsAssignableFrom(type));
|
||||
if (mapId == MapId.Nullspace) return false;
|
||||
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
var intersecting = new HashSet<Component>();
|
||||
|
||||
if (!UseBoundsQuery(type, worldAABB.Height * worldAABB.Width))
|
||||
{
|
||||
var metaQuery = GetEntityQuery<MetaDataComponent>();
|
||||
|
||||
foreach (var comp in EntityManager.GetAllComponents(type, true))
|
||||
foreach (var (uid, comp) in EntityManager.GetAllComponents(type, true))
|
||||
{
|
||||
var xform = xformQuery.GetComponent(comp.Owner);
|
||||
var xform = _xformQuery.GetComponent(uid);
|
||||
|
||||
if (xform.MapID != mapId ||
|
||||
!worldAABB.Contains(_transform.GetWorldPosition(comp.Owner, xformQuery)) ||
|
||||
!worldAABB.Contains(_transform.GetWorldPosition(xform)) ||
|
||||
((flags & LookupFlags.Contained) == 0x0 &&
|
||||
_container.IsEntityOrParentInContainer(comp.Owner, metaQuery.GetComponent(comp.Owner), xform, metaQuery, xformQuery)))
|
||||
_container.IsEntityOrParentInContainer(uid, _metaQuery.GetComponent(uid), xform, _metaQuery, _xformQuery)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -178,63 +247,81 @@ public sealed partial class EntityLookupSystem
|
||||
else
|
||||
{
|
||||
var query = EntityManager.GetEntityQuery(type);
|
||||
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
|
||||
|
||||
// Get grid entities
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
|
||||
{
|
||||
AddComponentsIntersecting(grid.Owner, intersecting, worldAABB, flags, lookupQuery, xformQuery, query);
|
||||
}
|
||||
var state = (this, worldAABB, flags, query, ignored, found: false);
|
||||
|
||||
_mapManager.FindGridsIntersecting(mapId, worldAABB, ref state,
|
||||
static (EntityUid uid, MapGridComponent grid, ref
|
||||
(EntityLookupSystem system,
|
||||
Box2 worldAABB,
|
||||
LookupFlags flags,
|
||||
EntityQuery<Component> query,
|
||||
EntityUid? ignored,
|
||||
bool found) tuple) =>
|
||||
{
|
||||
if (!tuple.system.AnyComponentsIntersecting(uid, tuple.worldAABB, tuple.flags, tuple.query, tuple.ignored))
|
||||
return true;
|
||||
tuple.found = true;
|
||||
return false;
|
||||
}, (flags & LookupFlags.Approximate) != 0x0);
|
||||
|
||||
// Get map entities
|
||||
var mapUid = _mapManager.GetMapEntityId(mapId);
|
||||
AddComponentsIntersecting(mapUid, intersecting, worldAABB, flags, lookupQuery, xformQuery, query);
|
||||
AddContained(intersecting, flags, xformQuery, query);
|
||||
AnyComponentsIntersecting(mapUid, worldAABB, flags, query, ignored);
|
||||
}
|
||||
|
||||
return intersecting.Count > 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
public HashSet<Component> GetComponentsIntersecting(Type type, MapId mapId, Box2 worldAABB, LookupFlags flags = DefaultFlags)
|
||||
{
|
||||
DebugTools.Assert(typeof(Component).IsAssignableFrom(type));
|
||||
if (mapId == MapId.Nullspace) return new HashSet<Component>();
|
||||
if (mapId == MapId.Nullspace)
|
||||
return new HashSet<Component>();
|
||||
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
var intersecting = new HashSet<Component>();
|
||||
|
||||
if (!UseBoundsQuery(type, worldAABB.Height * worldAABB.Width))
|
||||
{
|
||||
var metaQuery = GetEntityQuery<MetaDataComponent>();
|
||||
|
||||
foreach (var comp in EntityManager.GetAllComponents(type, true))
|
||||
foreach (var (uid, comp) in EntityManager.GetAllComponents(type, true))
|
||||
{
|
||||
var xform = xformQuery.GetComponent(comp.Owner);
|
||||
var xform = _xformQuery.GetComponent(uid);
|
||||
|
||||
if (xform.MapID != mapId ||
|
||||
!worldAABB.Contains(_transform.GetWorldPosition(comp.Owner, xformQuery)) ||
|
||||
!worldAABB.Contains(_transform.GetWorldPosition(xform)) ||
|
||||
((flags & LookupFlags.Contained) == 0x0 &&
|
||||
_container.IsEntityOrParentInContainer(comp.Owner, metaQuery.GetComponent(comp.Owner), xform, metaQuery, xformQuery)))
|
||||
_container.IsEntityOrParentInContainer(uid, _metaQuery.GetComponent(uid), xform, _metaQuery, _xformQuery)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
intersecting.Add((Component) comp);
|
||||
intersecting.Add(comp);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var query = EntityManager.GetEntityQuery(type);
|
||||
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
|
||||
|
||||
// Get grid entities
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
|
||||
{
|
||||
AddComponentsIntersecting(grid.Owner, intersecting, worldAABB, flags, lookupQuery, xformQuery, query);
|
||||
}
|
||||
var state = (this, worldAABB, flags, query, intersecting);
|
||||
|
||||
_mapManager.FindGridsIntersecting(mapId, worldAABB, ref state,
|
||||
static (EntityUid uid, MapGridComponent grid,
|
||||
ref (EntityLookupSystem system,
|
||||
Box2 worldAABB,
|
||||
LookupFlags flags,
|
||||
EntityQuery<Component> query,
|
||||
HashSet<Component> intersecting) tuple) =>
|
||||
{
|
||||
tuple.system.AddComponentsIntersecting(uid, tuple.intersecting, tuple.worldAABB, tuple.flags, tuple.query);
|
||||
return true;
|
||||
}, (flags & LookupFlags.Approximate) != 0x0);
|
||||
|
||||
// Get map entities
|
||||
var mapUid = _mapManager.GetMapEntityId(mapId);
|
||||
AddComponentsIntersecting(mapUid, intersecting, worldAABB, flags, lookupQuery, xformQuery, query);
|
||||
AddContained(intersecting, flags, xformQuery, query);
|
||||
AddComponentsIntersecting(mapUid, intersecting, worldAABB, flags, query);
|
||||
AddContained(intersecting, flags, query);
|
||||
}
|
||||
|
||||
return intersecting;
|
||||
@@ -244,7 +331,6 @@ public sealed partial class EntityLookupSystem
|
||||
{
|
||||
if (mapId == MapId.Nullspace) return new HashSet<T>();
|
||||
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
var intersecting = new HashSet<T>();
|
||||
|
||||
if (!UseBoundsQuery<T>(worldAABB.Height * worldAABB.Width))
|
||||
@@ -253,24 +339,33 @@ public sealed partial class EntityLookupSystem
|
||||
|
||||
while (query.MoveNext(out var comp, out var xform))
|
||||
{
|
||||
if (xform.MapID != mapId || !worldAABB.Contains(_transform.GetWorldPosition(xform, xformQuery))) continue;
|
||||
if (xform.MapID != mapId || !worldAABB.Contains(_transform.GetWorldPosition(xform))) continue;
|
||||
intersecting.Add(comp);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var query = GetEntityQuery<T>();
|
||||
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
|
||||
|
||||
// Get grid entities
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
|
||||
var state = (this, worldAABB, flags, query, intersecting);
|
||||
|
||||
_mapManager.FindGridsIntersecting(mapId, worldAABB, ref state,
|
||||
static (EntityUid uid, MapGridComponent grid,
|
||||
ref (EntityLookupSystem system,
|
||||
Box2 worldAABB,
|
||||
LookupFlags flags,
|
||||
EntityQuery<T> query,
|
||||
HashSet<T> intersecting) tuple) =>
|
||||
{
|
||||
AddComponentsIntersecting(grid.Owner, intersecting, worldAABB, flags, lookupQuery, xformQuery, query);
|
||||
}
|
||||
tuple.system.AddComponentsIntersecting(uid, tuple.intersecting, tuple.worldAABB, tuple.flags, tuple.query);
|
||||
return true;
|
||||
}, (flags & LookupFlags.Approximate) != 0x0);
|
||||
|
||||
// Get map entities
|
||||
var mapUid = _mapManager.GetMapEntityId(mapId);
|
||||
AddComponentsIntersecting(mapUid, intersecting, worldAABB, flags, lookupQuery, xformQuery, query);
|
||||
AddContained(intersecting, flags, xformQuery, query);
|
||||
AddComponentsIntersecting(mapUid, intersecting, worldAABB, flags, query);
|
||||
AddContained(intersecting, flags, query);
|
||||
}
|
||||
|
||||
return intersecting;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,10 +12,13 @@ public abstract class MetaDataSystem : EntitySystem
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
|
||||
private EntityPausedEvent _pausedEvent = new();
|
||||
private EntityPausedEvent _pausedEvent;
|
||||
|
||||
private EntityQuery<MetaDataComponent> _metaQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
_metaQuery = GetEntityQuery<MetaDataComponent>();
|
||||
SubscribeLocalEvent<MetaDataComponent, ComponentHandleState>(OnMetaDataHandle);
|
||||
SubscribeLocalEvent<MetaDataComponent, ComponentGetState>(OnMetaDataGetState);
|
||||
}
|
||||
@@ -41,25 +44,25 @@ public abstract class MetaDataSystem : EntitySystem
|
||||
|
||||
public void SetEntityName(EntityUid uid, string value, MetaDataComponent? metadata = null)
|
||||
{
|
||||
if (!Resolve(uid, ref metadata) || value.Equals(metadata.EntityName))
|
||||
if (!_metaQuery.Resolve(uid, ref metadata) || value.Equals(metadata.EntityName))
|
||||
return;
|
||||
|
||||
metadata._entityName = value;
|
||||
Dirty(metadata);
|
||||
Dirty(uid, metadata, metadata);
|
||||
}
|
||||
|
||||
public void SetEntityDescription(EntityUid uid, string value, MetaDataComponent? metadata = null)
|
||||
{
|
||||
if (!Resolve(uid, ref metadata) || value.Equals(metadata.EntityDescription))
|
||||
if (!_metaQuery.Resolve(uid, ref metadata) || value.Equals(metadata.EntityDescription))
|
||||
return;
|
||||
|
||||
metadata._entityDescription = value;
|
||||
Dirty(metadata);
|
||||
Dirty(uid, metadata, metadata);
|
||||
}
|
||||
|
||||
internal void SetEntityPrototype(EntityUid uid, EntityPrototype? value, MetaDataComponent? metadata = null)
|
||||
{
|
||||
if (!Resolve(uid, ref metadata) || value?.Equals(metadata._entityPrototype) == true)
|
||||
if (!_metaQuery.Resolve(uid, ref metadata) || value?.Equals(metadata._entityPrototype) == true)
|
||||
return;
|
||||
|
||||
// The ID string should never change after an entity has been created.
|
||||
@@ -71,7 +74,7 @@ public abstract class MetaDataSystem : EntitySystem
|
||||
|
||||
public bool EntityPaused(EntityUid uid, MetaDataComponent? metadata = null)
|
||||
{
|
||||
if (!Resolve(uid, ref metadata))
|
||||
if (!_metaQuery.Resolve(uid, ref metadata))
|
||||
return true;
|
||||
|
||||
return metadata.EntityPaused;
|
||||
@@ -79,7 +82,7 @@ public abstract class MetaDataSystem : EntitySystem
|
||||
|
||||
public void SetEntityPaused(EntityUid uid, bool value, MetaDataComponent? metadata = null)
|
||||
{
|
||||
if (!Resolve(uid, ref metadata)) return;
|
||||
if (!_metaQuery.Resolve(uid, ref metadata)) return;
|
||||
|
||||
if (metadata.EntityPaused == value) return;
|
||||
|
||||
@@ -97,7 +100,7 @@ public abstract class MetaDataSystem : EntitySystem
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
}
|
||||
|
||||
Dirty(metadata);
|
||||
Dirty(uid, metadata, metadata);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -105,7 +108,7 @@ public abstract class MetaDataSystem : EntitySystem
|
||||
/// </summary>
|
||||
public TimeSpan GetPauseTime(EntityUid uid, MetaDataComponent? metadata = null)
|
||||
{
|
||||
if (!Resolve(uid, ref metadata))
|
||||
if (!_metaQuery.Resolve(uid, ref metadata))
|
||||
return TimeSpan.Zero;
|
||||
|
||||
return (_timing.CurTime - metadata.PauseTime) ?? TimeSpan.Zero;
|
||||
@@ -122,7 +125,7 @@ public abstract class MetaDataSystem : EntitySystem
|
||||
|
||||
public void AddFlag(EntityUid uid, MetaDataFlags flags, MetaDataComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component)) return;
|
||||
if (!_metaQuery.Resolve(uid, ref component)) return;
|
||||
|
||||
component.Flags |= flags;
|
||||
}
|
||||
@@ -133,7 +136,7 @@ public abstract class MetaDataSystem : EntitySystem
|
||||
/// </summary>
|
||||
public void RemoveFlag(EntityUid uid, MetaDataFlags flags, MetaDataComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
if (!_metaQuery.Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
var toRemove = component.Flags & flags;
|
||||
@@ -141,7 +144,7 @@ public abstract class MetaDataSystem : EntitySystem
|
||||
return;
|
||||
|
||||
var ev = new MetaFlagRemoveAttemptEvent(toRemove);
|
||||
EntityManager.EventBus.RaiseLocalEvent(uid, ref ev, true);
|
||||
RaiseLocalEvent(uid, ref ev, true);
|
||||
|
||||
component.Flags &= ~ev.ToRemove;
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ public abstract partial class SharedTransformSystem
|
||||
DebugTools.Assert(xformQuery.GetComponent(oldGridUid).MapID == xformQuery.GetComponent(newGridUid).MapID);
|
||||
DebugTools.Assert(xform._anchored);
|
||||
|
||||
Dirty(xform);
|
||||
Dirty(uid, xform);
|
||||
var ev = new ReAnchorEvent(uid, oldGridUid, newGridUid, tilePos, xform);
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
}
|
||||
@@ -81,11 +81,11 @@ public abstract partial class SharedTransformSystem
|
||||
return false;
|
||||
|
||||
var wasAnchored = xform._anchored;
|
||||
Dirty(xform);
|
||||
Dirty(uid, xform);
|
||||
xform._anchored = true;
|
||||
|
||||
// Mark as static before doing position changes, to avoid the velocity change on parent change.
|
||||
_physics.TrySetBodyType(uid, BodyType.Static);
|
||||
_physics.TrySetBodyType(uid, BodyType.Static, xform: xform);
|
||||
|
||||
if (!wasAnchored && xform.Running)
|
||||
{
|
||||
@@ -117,28 +117,20 @@ public abstract partial class SharedTransformSystem
|
||||
if (!xform._anchored)
|
||||
return;
|
||||
|
||||
Dirty(xform);
|
||||
Dirty(uid, xform);
|
||||
xform._anchored = false;
|
||||
|
||||
if (setPhysics)
|
||||
_physics.TrySetBodyType(uid, BodyType.Dynamic);
|
||||
_physics.TrySetBodyType(uid, BodyType.Dynamic, xform: xform);
|
||||
|
||||
if (xform.LifeStage < ComponentLifeStage.Initialized)
|
||||
return;
|
||||
|
||||
if (TryComp(xform.GridUid, out MapGridComponent? grid))
|
||||
if (_gridQuery.TryGetComponent(xform.GridUid, out var grid))
|
||||
{
|
||||
var tileIndices = _map.TileIndicesFor(xform.GridUid.Value, grid, xform.Coordinates);
|
||||
_map.RemoveFromSnapGridCell(xform.GridUid.Value, grid, tileIndices, uid);
|
||||
}
|
||||
else if (xform.Initialized)
|
||||
{
|
||||
//HACK: Client grid pivot causes this.
|
||||
//TODO: make grid components the actual grid
|
||||
|
||||
// I have NFI what the comment above is on about, but this doesn't seem good, so lets log an error if it happens.
|
||||
Log.Error($"Missing grid while unanchoring {ToPrettyString(uid)}");
|
||||
}
|
||||
|
||||
if (!xform.Running)
|
||||
return;
|
||||
@@ -486,7 +478,9 @@ public abstract partial class SharedTransformSystem
|
||||
{
|
||||
if (value.EntityId == uid)
|
||||
{
|
||||
QueueDel(uid);
|
||||
DetachParentToNull(uid, xform);
|
||||
if (_netMan.IsServer || uid.IsClientSide())
|
||||
QueueDel(uid);
|
||||
throw new InvalidOperationException($"Attempted to parent an entity to itself: {ToPrettyString(uid)}");
|
||||
}
|
||||
|
||||
@@ -494,13 +488,17 @@ public abstract partial class SharedTransformSystem
|
||||
{
|
||||
if (!_xformQuery.Resolve(value.EntityId, ref newParent, false))
|
||||
{
|
||||
QueueDel(uid);
|
||||
DetachParentToNull(uid, xform);
|
||||
if (_netMan.IsServer || uid.IsClientSide())
|
||||
QueueDel(uid);
|
||||
throw new InvalidOperationException($"Attempted to parent entity {ToPrettyString(uid)} to non-existent entity {value.EntityId}");
|
||||
}
|
||||
|
||||
if (newParent.LifeStage > ComponentLifeStage.Running || LifeStage(value.EntityId) > EntityLifeStage.MapInitialized)
|
||||
{
|
||||
QueueDel(uid);
|
||||
DetachParentToNull(uid, xform);
|
||||
if (_netMan.IsServer || uid.IsClientSide())
|
||||
QueueDel(uid);
|
||||
throw new InvalidOperationException($"Attempted to re-parent to a terminating object. Entity: {ToPrettyString(uid)}, new parent: {ToPrettyString(value.EntityId)}");
|
||||
}
|
||||
|
||||
@@ -514,7 +512,7 @@ public abstract partial class SharedTransformSystem
|
||||
if (recursiveXform.ParentUid == uid)
|
||||
{
|
||||
if (!_gameTiming.ApplyingState)
|
||||
throw new InvalidOperationException($"Attempted to parent an entity to one of its descendants! {ToPrettyString(uid)}");
|
||||
throw new InvalidOperationException($"Attempted to parent an entity to one of its descendants! {ToPrettyString(uid)}. new parent: {ToPrettyString(value.EntityId)}");
|
||||
|
||||
// Client is halfway through applying server state, which can sometimes lead to a temporarily circular transform hierarchy.
|
||||
// E.g., client is holding a foldable bed and predicts dropping & sitting in it -> reset to holding it -> bed is parent of player and vice versa.
|
||||
@@ -1097,7 +1095,7 @@ public abstract partial class SharedTransformSystem
|
||||
|
||||
DebugTools.Assert(!xform.NoLocalRotation || xform.LocalRotation == 0);
|
||||
|
||||
Dirty(xform);
|
||||
Dirty(xform.Owner, xform);
|
||||
xform.MatricesDirty = true;
|
||||
|
||||
if (!xform.Initialized)
|
||||
@@ -1314,40 +1312,53 @@ public abstract partial class SharedTransformSystem
|
||||
|
||||
public void DetachParentToNull(EntityUid uid, TransformComponent xform, TransformComponent? oldXform)
|
||||
{
|
||||
if (xform._parent.IsValid())
|
||||
DebugTools.Assert(uid == xform.Owner);
|
||||
|
||||
var parent = xform._parent;
|
||||
if (!parent.IsValid())
|
||||
{
|
||||
DebugTools.Assert(uid == xform.Owner);
|
||||
var oldParent = xform._parent;
|
||||
if (!oldParent.IsValid())
|
||||
DebugTools.Assert(!xform.Anchored,
|
||||
$"Entity is anchored but has no parent? Entity: {ToPrettyString(uid)}");
|
||||
|
||||
DebugTools.Assert((MetaData(uid).Flags & MetaDataFlags.InContainer) == 0x0,
|
||||
$"Entity is in a container but has no parent? Entity: {ToPrettyString(uid)}");
|
||||
|
||||
if (xform.Broadphase != null)
|
||||
{
|
||||
DebugTools.Assert(!xform.Anchored);
|
||||
DebugTools.Assert((MetaData(uid).Flags & MetaDataFlags.InContainer) == 0x0);
|
||||
return;
|
||||
DebugTools.Assert(
|
||||
xform.Broadphase == BroadphaseData.Invalid
|
||||
|| xform.Broadphase.Value.Uid == uid
|
||||
|| Deleted(xform.Broadphase.Value.Uid)
|
||||
|| Terminating(xform.Broadphase.Value.Uid),
|
||||
$"Entity has no parent but is on some broadphase? Entity: {ToPrettyString(uid)}. Broadphase: {ToPrettyString(xform.Broadphase.Value.Uid)}");
|
||||
}
|
||||
|
||||
// Before making any changes to physics or transforms, remove from the current broadphase
|
||||
_lookup.RemoveFromEntityTree(uid, xform);
|
||||
|
||||
// Stop any active lerps
|
||||
xform.NextPosition = null;
|
||||
xform.NextRotation = null;
|
||||
xform.LerpParent = EntityUid.Invalid;
|
||||
|
||||
if (xform.Anchored && _metaQuery.TryGetComponent(xform.GridUid, out var meta) && meta.EntityLifeStage <= EntityLifeStage.MapInitialized)
|
||||
{
|
||||
var grid = Comp<MapGridComponent>(xform.GridUid.Value);
|
||||
var tileIndices = _map.TileIndicesFor(xform.GridUid.Value, grid, xform.Coordinates);
|
||||
_map.RemoveFromSnapGridCell(xform.GridUid.Value, grid, tileIndices, uid);
|
||||
xform._anchored = false;
|
||||
var anchorStateChangedEvent = new AnchorStateChangedEvent(xform, true);
|
||||
RaiseLocalEvent(uid, ref anchorStateChangedEvent, true);
|
||||
}
|
||||
|
||||
SetCoordinates(uid, xform, default, Angle.Zero, oldParent: oldXform);
|
||||
DebugTools.Assert((MetaData(uid).Flags & MetaDataFlags.InContainer) == 0x0);
|
||||
return;
|
||||
}
|
||||
else
|
||||
DebugTools.Assert(!xform.Anchored);
|
||||
|
||||
// Before making any changes to physics or transforms, remove from the current broadphase
|
||||
_lookup.RemoveFromEntityTree(uid, xform);
|
||||
|
||||
// Stop any active lerps
|
||||
xform.NextPosition = null;
|
||||
xform.NextRotation = null;
|
||||
xform.LerpParent = EntityUid.Invalid;
|
||||
|
||||
if (xform.Anchored
|
||||
&& _metaQuery.TryGetComponent(xform.GridUid, out var meta)
|
||||
&& meta.EntityLifeStage <= EntityLifeStage.MapInitialized)
|
||||
{
|
||||
var grid = Comp<MapGridComponent>(xform.GridUid.Value);
|
||||
var tileIndices = _map.TileIndicesFor(xform.GridUid.Value, grid, xform.Coordinates);
|
||||
_map.RemoveFromSnapGridCell(xform.GridUid.Value, grid, tileIndices, uid);
|
||||
xform._anchored = false;
|
||||
var anchorStateChangedEvent = new AnchorStateChangedEvent(xform, true);
|
||||
RaiseLocalEvent(uid, ref anchorStateChangedEvent, true);
|
||||
}
|
||||
|
||||
SetCoordinates(uid, xform, default, Angle.Zero, oldParent: oldXform);
|
||||
|
||||
DebugTools.Assert((MetaData(uid).Flags & MetaDataFlags.InContainer) == 0x0,
|
||||
$"Entity is in a container after having been detached to null-space? Entity: {ToPrettyString(uid)}");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -9,6 +9,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
@@ -21,6 +22,7 @@ namespace Robust.Shared.GameObjects
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly INetManager _netMan = default!;
|
||||
|
||||
private EntityQuery<MapGridComponent> _gridQuery;
|
||||
private EntityQuery<MetaDataComponent> _metaQuery;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user