Compare commits

...

66 Commits

Author SHA1 Message Date
ElectroJr
4f0f020f56 Version: 147.0.0 2023-08-10 00:40:01 -04:00
Leon Friedrich
5ce8369fb9 Rename a Dirty() proxy method to DirtyEntity() (#4253) 2023-08-10 14:17:57 +10:00
Pieter-Jan Briers
2446e64033 entitysystemupdateorder debug command 2023-08-08 21:36:14 +02:00
metalgearsloth
bdd65cda4b Version: 146.0.0 2023-08-08 17:26:42 +10:00
Leon Friedrich
77e949bfe8 More serialization related changes, (#4250) 2023-08-08 17:22:10 +10:00
metalgearsloth
d4171351f4 Metadata + cancollide stuff (#4247) 2023-08-08 12:03:27 +10:00
metalgearsloth
e67d0ad3d6 Xform stuff (#4246) 2023-08-08 11:55:56 +10:00
Artur
aff5711fde Add missing CultureInfo.InvariantCulture in angle validaton (#4248) 2023-08-08 11:52:24 +10:00
ElectroJr
a886222946 Version: 145.0.0 2023-08-06 21:45:09 -04:00
Leon Friedrich
5843f1087e Add ContentFileRead test and fix file reading on windows (#4242) 2023-08-07 11:39:09 +10:00
Leon Friedrich
93c0ce815f Add IPrototypeManager.EnumerateKinds() (#4244) 2023-08-07 11:24:34 +10:00
Leon Friedrich
1c64fa1f28 Fix TransformSystem.SetCoordinates() error logs. (#4245) 2023-08-07 11:24:26 +10:00
Leon Friedrich
c825c1e413 Remove IoCManager.Resolve calls in Resource.Load (#4243) 2023-08-07 11:24:15 +10:00
Chief-Engineer
f30fb47834 Add GetActorFromUserId to actor system (#4239) 2023-08-07 11:24:00 +10:00
Leon Friedrich
5d255e06c8 Fix SpriteSpecifier yaml validator (#4241) 2023-08-06 22:14:02 +10:00
metalgearsloth
80357c8ec4 Version: 144.0.1 2023-08-06 15:07:48 +10:00
metalgearsloth
ac3a434bdf Shrink entitylookup tile enlargement even further (#4240) 2023-08-06 15:06:08 +10:00
metalgearsloth
21719b8884 Version: 144.0.0 2023-08-06 12:45:49 +10:00
metalgearsloth
dbb6b90654 Tile enlargement + new flag for lookups (#4205) 2023-08-06 12:41:27 +10:00
ElectroJr
4b92be5324 Version: 143.3.0 2023-08-05 21:16:11 -04:00
Leon Friedrich
95169b7a71 Add temporary debug logs (#4237) 2023-08-06 11:11:23 +10:00
Leon Friedrich
b699e22c85 More serialization fixes (#4224) 2023-08-06 11:08:48 +10:00
Leon Friedrich
e48cc62d0b Clamp audio offset in ApplyAudioParams() (#4221) 2023-08-06 10:38:53 +10:00
Leon Friedrich
aade062a49 Allow replay loading to ignore some errors (#4236) 2023-08-06 10:33:31 +10:00
Chief-Engineer
e02166d5c4 add placement events (#4235) 2023-08-06 10:32:04 +10:00
Leon Friedrich
8037bfae14 Remove an incorrect assert (#4238) 2023-08-06 10:29:30 +10:00
metalgearsloth
49781791af Version: 143.2.0 2023-08-05 12:43:34 +10:00
metalgearsloth
92719aa29f Shutdown grid rendering events (#4234) 2023-08-05 11:38:16 +10:00
Chief-Engineer
1d47a9677d fix named toolshed command (#4230) 2023-08-04 14:51:06 +10:00
Leon Friedrich
14fe8eba6d Remove unnecessary dummy test prototypes (#4233) 2023-08-04 14:50:53 +10:00
Leon Friedrich
2ff99d4a62 Add support for tests to load extra prototypes from multiple "files" (#4232) 2023-08-04 14:50:08 +10:00
metalgearsloth
ce8b2d82a3 Version: 143.1.0 2023-08-04 12:40:21 +10:00
Leon Friedrich
f8f99450db Error on duplicate broadcast subscriptions (#4231) 2023-08-04 12:34:10 +10:00
metalgearsloth
a3cf4877e4 Don't raise contact events for qdel ents (#4126) 2023-08-04 12:22:59 +10:00
metalgearsloth
6ab08f7dc1 Add BodyStatus readwrite (#4227) 2023-08-04 12:22:50 +10:00
Vordenburg
819f6921cf Add locale support for grammatical measure words (#4064) 2023-08-03 20:00:47 +10:00
moonheart08
cf91369d27 Version: 143.0.0 2023-08-02 16:07:25 -05:00
Moony
0e1328675c Toolshed (#4197)
* Saving work

* Move shit to engine

* lord

* merg

* awa

* bql is kill

* forgot the fucking bike rack

* bql is kill for real

* pjb will kill me

* aughfhbdj

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

* a

* adgddf

* gdsgvfvxshngfgh

* b

* hfsjhghj

* hbfdjjh

* tf you mean i have to document it

* follow C# standards

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

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

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

* e

* Toolshed type system adventure.

* a log

* a

* a e i o u

* awa

* fix tests

* Arithmetic commands.

* a

* parse improvements

---------

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

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

View File

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

View File

@@ -54,6 +54,202 @@ 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

View File

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

View File

@@ -17,15 +17,15 @@ cmd-error-dir-not-found = Could not find directory: {$dir}.
cmd-failure-no-attached-entity = There is no entity attached to this shell.
## 'help' command
cmd-help-desc = Display general help or help text for a specific command
cmd-help-help = Usage: help [command name]
cmd-oldhelp-desc = Display general help or help text for a specific command
cmd-oldhelp-help = Usage: help [command name]
When no command name is provided, displays general-purpose help text. If a command name is provided, displays help text for that command.
cmd-help-no-args = To display help for a specific command, write 'help <command>'. To list all available commands, write 'list'. To search for commands, use 'list <filter>'.
cmd-help-unknown = Unknown command: { $command }
cmd-help-top = { $command } - { $description }
cmd-help-invalid-args = Invalid amount of arguments.
cmd-help-arg-cmdname = [command name]
cmd-oldhelp-no-args = To display help for a specific command, write 'help <command>'. To list all available commands, write 'list'. To search for commands, use 'list <filter>'.
cmd-oldhelp-unknown = Unknown command: { $command }
cmd-oldhelp-top = { $command } - { $description }
cmd-oldhelp-invalid-args = Invalid amount of arguments.
cmd-oldhelp-arg-cmdname = [command name]
## 'cvar' command
cmd-cvar-desc = Gets or sets a CVar.

View File

@@ -1,4 +1,5 @@
discord-rpc-in-main-menu = In Main Menu
discord-rpc-in-main-menu-logo-text = I think coolsville SUCKS
discord-rpc-character = Username: {$username}
discord-rpc-on-server = On Server: {$servername}
discord-rpc-players = Players: {$players}/{$maxplayers}
discord-rpc-players = Players: {$players}/{$maxplayers}

View File

@@ -0,0 +1,169 @@
command-description-tpto =
Teleport the given entities to some target entity.
command-description-player-list =
Returns a list of all player sessions.
command-description-player-self =
Returns the current player session.
command-description-player-imm =
Returns the session associated with the player given as argument.
command-description-player-entity =
Returns the entities of the input sessions.
command-description-self =
Returns the current attached entity.
command-description-physics-velocity =
Returns the velocity of the input entities.
command-description-physics-angular-velocity =
Returns the angular velocity of the input entities.
command-description-buildinfo =
Provides information about the build of the game.
command-description-cmd-list =
Returns a list of all commands, for this side.
command-description-explain =
Explains the given expression, providing command descriptions and signatures.
command-description-search =
Searches through the input for the provided value.
command-description-stopwatch =
Measures the execution time of the given expression.
command-description-types-consumers =
Provides all commands that can consume the given type.
command-description-types-tree =
Debug tool to return all types the command interpreter can downcast the input to.
command-description-types-gettype =
Returns the type of the input.
command-description-types-fullname =
Returns the full name of the input type according to CoreCLR.
command-description-as =
Casts the input to the given type.
Effectively a type hint if you know the type but the interpreter does not.
command-description-count =
Counts the amount of entries in it's input, returning an integer.
command-description-map =
Maps the input over the given block, with the provided expected return type.
This command may be modified to not need an explicit return type in the future.
command-description-select =
Selects N objects or N% of objects from the input.
One can additionally invert this command with not to make it select everything except N objects instead.
command-description-comp =
Returns the given component from the input entities, discarding entities without that component.
command-description-delete =
Deletes the input entities.
command-description-ent =
Returns the provided entity ID.
command-description-entities =
Returns all entities on the server.
command-description-paused =
Filters the input entities by whether or not they are paused.
This command can be inverted with not.
command-description-with =
Filters the input entities by whether or not they have the given component.
This command can be inverted with not.
command-description-fuck =
Throws an exception.
command-description-ecscomp-listty =
Lists every type of component registered.
command-description-cd =
Changes the session's current directory to the given relative or absolute path.
command-description-ls-here =
Lists the contents of the current directory.
command-description-ls-in =
Lists the contents of the given relative or absolute path.
command-description-methods-get =
Returns all methods associated with the input type.
command-description-methods-overrides =
Returns all methods overriden on the input type.
command-description-methods-overridesfrom =
Returns all methods overriden from the given type on the input type.
command-description-cmd-moo =
Asks the important questions.
command-description-cmd-descloc =
Returns the localization string for a command's description.
command-description-cmd-getshim =
Returns a command's execution shim.
command-description-help =
Provides a quick rundown of how to use toolshed.
command-description-ioc-registered =
Returns all the types registered with IoCManager on the current thread (usually the game thread)
command-description-ioc-get =
Gets an instance of an IoC registration.
command-description-loc-tryloc =
Tries to get a localization string, returning null if unable.
command-description-loc-loc =
Gets a localization string, returning the unlocalized string if unable.
command-description-physics-angular_velocity =
Returns the angular velocity of the given entities.
command-description-vars =
Provides a list of all variables set in this session.
command-description-any =
Returns true if there's any values in the input, otherwise false.
command-description-ArrowCommand =
Assigns the input to a variable.
command-description-isempty =
Returns true if the input is empty, otherwise false.
command-description-isnull =
Returns true if the input is null, otherwise false.
command-description-unique =
Filters the input sequence for uniqueness, removing duplicate values.
command-description-where =
Given some input sequence IEnumerable<T>, takes a block of signature T -> bool that decides if each input value should be included in the output sequence.
command-description-do =
Backwards compatibility with BQL, applies the given old commands over the input sequence.
command-description-named =
Filters the input entities by their name, with the regex ^selector$.
command-description-prototyped =
Filters the input entities by their prototype.
command-description-nearby =
Creates a new list of all entities nearby the inputs within the given range.
command-description-first =
Returns the first entry of the given enumerable.
command-description-splat =
"Splats" a block, value, or variable, creating N copies of it in a list.
command-description-val =
Casts the given value, block, or variable to the given type. This is mostly a workaround for current limitations of variables.
command-description-actor-controlled =
Filters entities by whether or not they're actively controlled.
command-description-actor-session =
Returns the sessions associated with the input entities.
command-description-physics-parent =
Returns the parent(s) of the input entities.
command-description-emplace =
Runs the given block over it's inputs, with the input value placed into the variable $value within the block.
Additionally breaks out $wx, $wy, $proto, $desc, $name, and $paused for entities.
Can also have breakout values for other types, consult the documentation for that type for further info.
command-description-AddCommand =
Performs numeric addition.
command-description-SubtractCommand =
Performs numeric subtraction.
command-description-MultiplyCommand =
Performs numeric multiplication.
command-description-DivideCommand =
Performs numeric division.
command-description-min =
Returns the minimum of two values.
command-description-max =
Returns the maximum of two values.
command-description-BitAndCommand =
Performs bitwise AND.
command-description-BitOrCommand =
Performs bitwise OR.
command-description-BitXorCommand =
Performs bitwise XOR.
command-description-neg =
Negates the input.
command-description-GreaterThanCommand =
Performs a greater-than comparison, x > y.
command-description-LessThanCommand =
Performs a less-than comparison, x < y.
command-description-GreaterThanOrEqualCommand =
Performs a greater-than-or-equal comparison, x >= y.
command-description-LessThanOrEqualCommand =
Performs a less-than-or-equal comparison, x <= y.
command-description-EqualCommand =
Performs an equality comparison, returning true if the inputs are equal.
command-description-NotEqualCommand =
Performs an equality comparison, returning true if the inputs are not equal.
command-description-entitysystemupdateorder-tick =
Lists the tick update order of entity systems.
command-description-entitysystemupdateorder-frame =
Lists the frame update order of entity systems.

View File

@@ -1,4 +1,4 @@
using System;
using System;
using Robust.Client.Graphics;
namespace Robust.Client.Audio;
@@ -8,13 +8,17 @@ public sealed class AudioStream
public TimeSpan Length { get; }
internal ClydeHandle? ClydeHandle { get; }
public string? Name { get; }
public string? Title { get; }
public string? Artist { get; }
public int ChannelCount { get; }
internal AudioStream(ClydeHandle handle, TimeSpan length, int channelCount, string? name = null)
internal AudioStream(ClydeHandle handle, TimeSpan length, int channelCount, string? name = null, string? title = null, string? artist = null)
{
ClydeHandle = handle;
Length = length;
ChannelCount = channelCount;
Name = name;
Title = title;
Artist = artist;
}
}

View File

@@ -14,7 +14,7 @@ internal sealed partial class ClientConsoleHost
private int _completionSeq;
public async Task<CompletionResult> GetCompletions(List<string> args, CancellationToken cancel)
public async Task<CompletionResult> GetCompletions(List<string> args, string argStr, CancellationToken cancel)
{
// Last element is the command currently being typed. May be empty.
@@ -24,10 +24,10 @@ internal sealed partial class ClientConsoleHost
if (delay > 0)
await Task.Delay((int)(delay * 1000), cancel);
return await CalcCompletions(args, cancel);
return await CalcCompletions(args, argStr, cancel);
}
private Task<CompletionResult> CalcCompletions(List<string> args, CancellationToken cancel)
private Task<CompletionResult> CalcCompletions(List<string> args, string argStr, CancellationToken cancel)
{
if (args.Count == 1)
{
@@ -44,10 +44,10 @@ internal sealed partial class ClientConsoleHost
if (!AvailableCommands.TryGetValue(args[0], out var cmd))
return Task.FromResult(CompletionResult.Empty);
return cmd.GetCompletionAsync(LocalShell, args.ToArray()[1..], cancel).AsTask();
return cmd.GetCompletionAsync(LocalShell, args.ToArray()[1..], argStr, cancel).AsTask();
}
private Task<CompletionResult> DoServerCompletions(List<string> args, CancellationToken cancel)
private Task<CompletionResult> DoServerCompletions(List<string> args, string argStr, CancellationToken cancel)
{
var tcs = new TaskCompletionSource<CompletionResult>();
var cts = CancellationTokenSource.CreateLinkedTokenSource(cancel);
@@ -62,6 +62,7 @@ internal sealed partial class ClientConsoleHost
var msg = new MsgConCompletion
{
Args = args.ToArray(),
ArgString = argStr,
Seq = seq
};

View File

@@ -10,6 +10,7 @@ using Robust.Shared.Console;
using Robust.Shared.Enums;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Players;
@@ -21,13 +22,13 @@ namespace Robust.Client.Console
{
public sealed class AddStringArgs : EventArgs
{
public string Text { get; }
public FormattedMessage Text { get; }
public bool Local { get; }
public bool Error { get; }
public AddStringArgs(string text, bool local, bool error)
public AddStringArgs(FormattedMessage text, bool local, bool error)
{
Text = text;
Local = local;
@@ -132,10 +133,17 @@ namespace Robust.Client.Console
AddFormatted?.Invoke(this, new AddFormattedMessageArgs(message));
}
public override void WriteLine(ICommonSession? session, FormattedMessage msg)
{
AddFormattedLine(msg);
}
/// <inheritdoc />
public override void WriteError(ICommonSession? session, string text)
{
OutputText(text, true, true);
var msg = new FormattedMessage();
msg.AddText(text);
OutputText(msg, true, true);
}
public bool IsCmdServer(IConsoleCommand cmd)
@@ -151,8 +159,13 @@ namespace Robust.Client.Console
if (string.IsNullOrWhiteSpace(command))
return;
WriteLine(null, "");
var msg = new FormattedMessage();
msg.PushColor(Color.Gold);
msg.AddText("> " + command);
msg.Pop();
// echo the command locally
WriteLine(null, "> " + command);
OutputText(msg, true, false);
//Commands are processed locally and then sent to the server to be processed there again.
var args = new List<string>();
@@ -205,7 +218,9 @@ namespace Robust.Client.Console
/// <inheritdoc />
public override void WriteLine(ICommonSession? session, string text)
{
OutputText(text, true, false);
var msg = new FormattedMessage();
msg.AddText(text);
OutputText(msg, true, false);
}
/// <inheritdoc />
@@ -214,12 +229,12 @@ namespace Robust.Client.Console
// We don't have anything to dispose.
}
private void OutputText(string text, bool local, bool error)
private void OutputText(FormattedMessage text, bool local, bool error)
{
AddString?.Invoke(this, new AddStringArgs(text, local, error));
var level = error ? LogLevel.Warning : LogLevel.Info;
_conLogger.Log(level, text);
_conLogger.Log(level, text.ToString());
}
private void OnNetworkConnected(object? sender, NetChannelArgs netChannelArgs)
@@ -229,7 +244,7 @@ namespace Robust.Client.Console
private void HandleConCmdAck(MsgConCmdAck msg)
{
OutputText("< " + msg.Text, false, msg.Error);
OutputText(msg.Text, false, msg.Error);
}
private void HandleConCmdReg(MsgConCmdReg msg)
@@ -303,13 +318,14 @@ namespace Robust.Client.Console
public async ValueTask<CompletionResult> GetCompletionAsync(
IConsoleShell shell,
string[] args,
string argStr,
CancellationToken cancel)
{
var host = (ClientConsoleHost)shell.ConsoleHost;
var argsList = args.ToList();
argsList.Insert(0, Command);
return await host.DoServerCompletions(argsList, cancel);
return await host.DoServerCompletions(argsList, argStr, cancel);
}
}
@@ -327,10 +343,11 @@ namespace Robust.Client.Console
public override async ValueTask<CompletionResult> GetCompletionAsync(
IConsoleShell shell,
string[] args,
string argStr,
CancellationToken cancel)
{
var host = (ClientConsoleHost)shell.ConsoleHost;
return await host.DoServerCompletions(args.ToList(), cancel);
return await host.DoServerCompletions(args.ToList(), argStr[">".Length..], cancel);
}
}
}

View File

@@ -19,6 +19,6 @@ namespace Robust.Client.Console
void AddFormattedLine(FormattedMessage message);
Task<CompletionResult> GetCompletions(List<string> args, CancellationToken cancel);
Task<CompletionResult> GetCompletions(List<string> args, string argStr, CancellationToken cancel);
}
}

View File

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

View File

@@ -33,6 +33,7 @@ using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Profiling;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Replays;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
@@ -85,6 +86,7 @@ namespace Robust.Client
[Dependency] private readonly IReplayLoadManager _replayLoader = default!;
[Dependency] private readonly IReplayPlaybackManager _replayPlayback = default!;
[Dependency] private readonly IReplayRecordingManagerInternal _replayRecording = default!;
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
private IWebViewManagerHook? _webViewHook;
@@ -162,6 +164,7 @@ namespace Robust.Client
// before prototype load.
ProgramShared.FinishCheckBadFileExtensions(checkBadExtensions);
_reflectionManager.Initialize();
_prototypeManager.Initialize();
_prototypeManager.LoadDefaultPrototypes();
_prototypeManager.ResolveResults();

View File

@@ -167,40 +167,9 @@ namespace Robust.Client.GameObjects
public bool TreeUpdateQueued { get; set; }
[DataField("layerDatums")]
private List<PrototypeLayerData> LayerDatums
{
get
{
var layerDatums = new List<PrototypeLayerData>();
foreach (var layer in Layers)
{
layerDatums.Add(layer.ToPrototypeData());
}
return layerDatums;
}
set
{
if (value == null) return;
Layers.Clear();
foreach (var layerDatum in value)
{
AddLayer(layerDatum);
}
_layerMapShared = true;
QueueUpdateRenderTree();
QueueUpdateIsInert();
}
}
private RSI? _baseRsi;
[ViewVariables(VVAccess.ReadWrite)]
[DataField("rsi", priority: 2)]
public RSI? BaseRSI
{
get => _baseRsi;
@@ -357,7 +326,16 @@ namespace Robust.Client.GameObjects
if (layerDatums.Count != 0)
{
LayerMap.Clear();
LayerDatums = layerDatums;
Layers.Clear();
foreach (var datum in layerDatums)
{
AddLayer(datum);
}
_layerMapShared = true;
QueueUpdateRenderTree();
QueueUpdateIsInert();
}
UpdateLocalMatrix();

View File

@@ -314,9 +314,9 @@ public sealed class AudioSystem : SharedAudioSystem
return source != null;
}
private PlayingStream CreateAndStartPlayingStream(IClydeAudioSource source, AudioParams? audioParams)
private PlayingStream CreateAndStartPlayingStream(IClydeAudioSource source, AudioParams? audioParams, AudioStream stream)
{
ApplyAudioParams(audioParams, source);
ApplyAudioParams(audioParams, source, stream);
source.StartPlaying();
var playing = new PlayingStream
{
@@ -365,7 +365,7 @@ public sealed class AudioSystem : SharedAudioSystem
source.SetGlobal();
return CreateAndStartPlayingStream(source, audioParams);
return CreateAndStartPlayingStream(source, audioParams, stream);
}
/// <summary>
@@ -416,7 +416,7 @@ public sealed class AudioSystem : SharedAudioSystem
if (!source.SetPosition(worldPos))
return Play(stream, fallbackCoordinates.Value, fallbackCoordinates.Value, audioParams);
var playing = CreateAndStartPlayingStream(source, audioParams);
var playing = CreateAndStartPlayingStream(source, audioParams, stream);
playing.TrackingEntity = entity;
playing.TrackingFallbackCoordinates = fallbackCoordinates != EntityCoordinates.Invalid ? fallbackCoordinates : null;
return playing;
@@ -469,7 +469,7 @@ public sealed class AudioSystem : SharedAudioSystem
return null;
}
var playing = CreateAndStartPlayingStream(source, audioParams);
var playing = CreateAndStartPlayingStream(source, audioParams, stream);
playing.TrackingCoordinates = coordinates;
playing.TrackingFallbackCoordinates = fallbackCoordinates != EntityCoordinates.Invalid ? fallbackCoordinates : null;
return playing;
@@ -493,7 +493,7 @@ public sealed class AudioSystem : SharedAudioSystem
return null;
}
private void ApplyAudioParams(AudioParams? audioParams, IClydeAudioSource source)
private void ApplyAudioParams(AudioParams? audioParams, IClydeAudioSource source, AudioStream audio)
{
if (!audioParams.HasValue)
return;
@@ -508,8 +508,12 @@ public sealed class AudioSystem : SharedAudioSystem
source.SetRolloffFactor(audioParams.Value.RolloffFactor);
source.SetMaxDistance(audioParams.Value.MaxDistance);
source.SetReferenceDistance(audioParams.Value.ReferenceDistance);
source.SetPlaybackPosition(audioParams.Value.PlayOffsetSeconds);
source.IsLooping = audioParams.Value.Loop;
// TODO clamp the offset inside of SetPlaybackPosition() itself.
var offset = audioParams.Value.PlayOffsetSeconds;
offset = Math.Clamp(offset, 0f, (float) audio.Length.TotalSeconds);
source.SetPlaybackPosition(offset);
}
public sealed class PlayingStream : IPlayingAudioStream

View File

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

View File

@@ -745,10 +745,6 @@ namespace Robust.Client.GameStates
}
}
var contQuery = _entities.GetEntityQuery<ContainerManagerComponent>();
var physicsQuery = _entities.GetEntityQuery<PhysicsComponent>();
var fixturesQuery = _entities.GetEntityQuery<FixturesComponent>();
var broadQuery = _entities.GetEntityQuery<BroadphaseComponent>();
var queuedBroadphaseUpdates = new List<(EntityUid, TransformComponent)>(enteringPvs);
// Apply entity states.

View File

@@ -41,7 +41,7 @@ namespace Robust.Client.GameStates
{
IoCManager.InjectDependencies(this);
var cache = IoCManager.Resolve<IResourceCache>();
_font = new VectorFont(cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
_font = new VectorFont(cache.GetResource<FontResource>("/EngineFonts/NotoSans/NotoSans-Regular.ttf"), 10);
_lineHeight = _font.GetLineHeight(1);
_gameStateManager.GameStateApplied += HandleGameStateApplied;

View File

@@ -53,7 +53,7 @@ namespace Robust.Client.GameStates
{
IoCManager.InjectDependencies(this);
var cache = IoCManager.Resolve<IResourceCache>();
_font = new VectorFont(cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
_font = new VectorFont(cache.GetResource<FontResource>("/EngineFonts/NotoSans/NotoSans-Regular.ttf"), 10);
_gameStateManager.GameStateApplied += HandleGameStateApplied;
}

View File

@@ -27,7 +27,7 @@ namespace Robust.Client.Graphics.Audio
readSamples += read;
}
return new OggVorbisData(totalSamples, sampleRate, channels, buffer);
return new OggVorbisData(totalSamples, sampleRate, channels, buffer, vorbis.Tags.Title, vorbis.Tags.Artist);
}
}
@@ -37,13 +37,17 @@ namespace Robust.Client.Graphics.Audio
public readonly long SampleRate;
public readonly long Channels;
public readonly ReadOnlyMemory<float> Data;
public readonly string Title;
public readonly string Artist;
public OggVorbisData(long totalSamples, long sampleRate, long channels, ReadOnlyMemory<float> data)
public OggVorbisData(long totalSamples, long sampleRate, long channels, ReadOnlyMemory<float> data, string title, string artist)
{
TotalSamples = totalSamples;
SampleRate = sampleRate;
Channels = channels;
Data = data;
Title = title;
Artist = artist;
}
}
}

View File

@@ -329,7 +329,7 @@ namespace Robust.Client.Graphics.Audio
var handle = new ClydeHandle(_audioSampleBuffers.Count);
_audioSampleBuffers.Add(new LoadedAudioSample(buffer));
var length = TimeSpan.FromSeconds(vorbis.TotalSamples / (double) vorbis.SampleRate);
return new AudioStream(handle, length, (int) vorbis.Channels, name);
return new AudioStream(handle, length, (int) vorbis.Channels, name, vorbis.Title, vorbis.Artist);
}
public AudioStream LoadAudioWav(Stream stream, string? name = null)

View File

@@ -176,6 +176,14 @@ namespace Robust.Client.Graphics.Clyde
_entityManager.EventBus.SubscribeEvent<GridModifiedEvent>(EventSource.Local, this, _updateOnGridModified);
}
public void ShutdownGridEcsEvents()
{
_entityManager.EventBus.UnsubscribeEvent<TileChangedEvent>(EventSource.Local, this);
_entityManager.EventBus.UnsubscribeEvent<GridStartupEvent>(EventSource.Local, this);
_entityManager.EventBus.UnsubscribeEvent<GridRemovalEvent>(EventSource.Local, this);
_entityManager.EventBus.UnsubscribeEvent<GridModifiedEvent>(EventSource.Local, this);
}
private void GLInitBindings(bool gles)
{
_glBindingsContext = _glContext!.BindingsContext;

View File

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

View File

@@ -12,9 +12,9 @@ void main()
lowp vec4 COLOR;
// [SHADER_CODE]
lowp vec3 lightSample = texture2D(lightMap, Pos).rgb;
// [SHADER_CODE]
gl_FragColor = zAdjustResult(COLOR * VtxModulate * vec4(lightSample, 1.0));
}

View File

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

View File

@@ -20,7 +20,7 @@ namespace Robust.Client.Graphics
public sealed class ShaderPrototype : IPrototype, ISerializationHooks
{
[ViewVariables]
[IdDataFieldAttribute]
[IdDataField]
public string ID { get; } = default!;
[ViewVariables] private ShaderKind Kind;
@@ -65,12 +65,12 @@ namespace Robust.Client.Graphics
case ShaderKind.Canvas:
var hasLight = rawMode != "unshaded";
var hasLight = _rawMode != "unshaded";
ShaderBlendMode? blend = null;
if (rawBlendMode != null)
if (_rawBlendMode != null)
{
if (!Enum.TryParse<ShaderBlendMode>(rawBlendMode.ToUpper(), out var parsed))
Logger.Error($"invalid mode: {rawBlendMode}");
if (!Enum.TryParse<ShaderBlendMode>(_rawBlendMode.ToUpper(), out var parsed))
Logger.Error($"invalid mode: {_rawBlendMode}");
else
blend = parsed;
}
@@ -94,11 +94,20 @@ namespace Robust.Client.Graphics
return Instance().Duplicate();
}
[DataField("kind", readOnly: true, required: true)] private string _rawKind = default!;
[DataField("path", readOnly: true)] private ResPath path;
[DataField("params", readOnly: true)] private Dictionary<string, string>? paramMapping;
[DataField("light_mode", readOnly: true)] private string? rawMode;
[DataField("blend_mode", readOnly: true)] private string? rawBlendMode;
[DataField("kind", required: true)]
private readonly string _rawKind = default!;
[DataField("path")]
private readonly ResPath? _path;
[DataField("params")]
private readonly Dictionary<string, string>? _paramMapping;
[DataField("light_mode")]
private readonly string? _rawMode;
[DataField("blend_mode")]
private readonly string? _rawBlendMode;
void ISerializationHooks.AfterDeserialization()
{
@@ -106,18 +115,22 @@ namespace Robust.Client.Graphics
{
case "source":
Kind = ShaderKind.Source;
if (path == null) throw new InvalidOperationException();
_source = IoCManager.Resolve<IResourceCache>().GetResource<ShaderSourceResource>(path);
if (paramMapping != null)
// TODO use a custom type serializer.
if (_path == null)
throw new InvalidOperationException("Source shaders must specify a source file.");
_source = IoCManager.Resolve<IResourceCache>().GetResource<ShaderSourceResource>(_path.Value);
if (_paramMapping != null)
{
_params = new Dictionary<string, object>();
foreach (var item in paramMapping!)
foreach (var item in _paramMapping!)
{
var name = item.Key;
if (!_source.ParsedShader.Uniforms.TryGetValue(name, out var uniformDefinition))
{
Logger.ErrorS("shader", "Shader param '{0}' does not exist on shader '{1}'", name, path);
Logger.ErrorS("shader", "Shader param '{0}' does not exist on shader '{1}'", name, _path);
continue;
}

View File

@@ -24,7 +24,7 @@ public sealed class LiveProfileViewControl : Control
{
IoCManager.InjectDependencies(this);
if (!_resourceCache.TryGetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf", out var font))
if (!_resourceCache.TryGetResource<FontResource>("/EngineFonts/NotoSans/NotoSans-Regular.ttf", out var font))
return;
_font = font.MakeDefault();

View File

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

View File

@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using Robust.Client.GameStates;
using Robust.Shared;
using Robust.Shared.GameObjects;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -64,6 +66,18 @@ public sealed partial class ReplayLoadManager
}
}
throw new Exception("Missing metadata component");
if (!entState.ComponentChanges.HasContents)
{
// This shouldn't be possible, yet it has happened?
// TODO this should probably also throw an exception.
_sawmill.Error($"Encountered blank entity state? Entity: {entState.Uid}. Last modified: {entState.EntityLastModified}. Attempting to continue.");
return null;
}
if (!_confMan.GetCVar(CVars.ReplayIgnoreErrors))
throw new MissingMetadataException(entState.Uid);
_sawmill.Error($"Missing metadata component. Entity: {entState.Uid}. Last modified: {entState.EntityLastModified}.");
return null;
}
}

View File

@@ -11,6 +11,7 @@ using Robust.Shared.Utility;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Robust.Shared;
using Robust.Shared.Replays;
using static Robust.Shared.Replays.ReplayConstants;
@@ -136,9 +137,21 @@ public sealed partial class ReplayLoadManager
if (data == null)
throw new Exception("Failed to load yaml metadata");
TimeSpan duration;
var finalData = LoadYamlFinalMetadata(fileReader);
if (finalData == null)
throw new Exception("Failed to load final yaml metadata");
{
var msg = "Failed to load final yaml metadata";
if (!_confMan.GetCVar(CVars.ReplayIgnoreErrors))
throw new Exception(msg);
_sawmill.Error(msg);
duration = TimeSpan.FromDays(1);
}
else
{
duration = TimeSpan.Parse(((ValueDataNode) finalData[MetaFinalKeyDuration]).Value);
}
var typeHash = Convert.FromHexString(((ValueDataNode) data[MetaKeyTypeHash]).Value);
var stringHash = Convert.FromHexString(((ValueDataNode) data[MetaKeyStringHash]).Value);
@@ -146,7 +159,6 @@ public sealed partial class ReplayLoadManager
var timeBaseTick = ((ValueDataNode) data[MetaKeyBaseTick]).Value;
var timeBaseTimespan = ((ValueDataNode) data[MetaKeyBaseTime]).Value;
var clientSide = bool.Parse(((ValueDataNode) data[MetaKeyIsClientRecording]).Value);
var duration = TimeSpan.Parse(((ValueDataNode) finalData[MetaFinalKeyDuration]).Value);
if (!typeHash.SequenceEqual(_serializer.GetSerializableTypesHash()))
throw new Exception($"{nameof(IRobustSerializer)} hashes do not match. Loading replays using a bad replay-client version?");

View File

@@ -97,6 +97,8 @@ internal sealed partial class ReplayPlaybackManager
if (message is EntityEventArgs args)
_entMan.DispatchReceivedNetworkMsg(args);
else if (_warned.Add(message.GetType()))
_sawmill.Error($"Unhandled replay message: {message.GetType()}.");
}
}
}

View File

@@ -55,6 +55,7 @@ internal sealed partial class ReplayPlaybackManager : IReplayPlaybackManager
private bool _initialized;
private ISawmill _sawmill = default!;
private HashSet<Type> _warned = new();
public bool Playing
{

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Client.Graphics;
using Robust.Shared.ContentPack;
using Robust.Shared.Utility;
@@ -40,5 +41,9 @@ namespace Robust.Client.ResourceManagement
// Resource load callbacks so content can hook stuff like click maps.
event Action<TextureLoadedEventArgs> OnRawTextureLoaded;
event Action<RsiLoadedEventArgs> OnRsiLoaded;
IClyde Clyde { get; }
IClydeAudio ClydeAudio { get; }
IFontManager FontManager { get; }
}
}

View File

@@ -18,7 +18,9 @@ namespace Robust.Client.ResourceManagement
{
internal partial class ResourceCache
{
[Dependency] private readonly IClyde _clyde = default!;
[field: Dependency] public IClyde Clyde { get; } = default!;
[field: Dependency] public IClydeAudio ClydeAudio { get; } = default!;
[field: Dependency] public IFontManager FontManager { get; } = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
@@ -70,7 +72,7 @@ namespace Robust.Client.ResourceManagement
try
{
TextureResource.LoadTexture(_clyde, data);
TextureResource.LoadTexture(Clyde, data);
}
catch (Exception e)
{
@@ -198,7 +200,7 @@ namespace Robust.Client.ResourceManagement
void FinalizeMetaAtlas(int toIndex, Image<Rgba32> sheet)
{
var atlas = _clyde.LoadTextureFromImage(sheet);
var atlas = Clyde.LoadTextureFromImage(sheet);
for (int i = finalized + 1; i <= toIndex; i++)
{
var rsi = rsiList[i];

View File

@@ -20,14 +20,13 @@ namespace Robust.Client.ResourceManagement
using (var fileStream = cache.ContentFileRead(path))
{
var clyde = IoCManager.Resolve<IClydeAudio>();
if (path.Extension == "ogg")
{
AudioStream = clyde.LoadAudioOggVorbis(fileStream, path.ToString());
AudioStream = cache.ClydeAudio.LoadAudioOggVorbis(fileStream, path.ToString());
}
else if (path.Extension == "wav")
{
AudioStream = clyde.LoadAudioWav(fileStream, path.ToString());
AudioStream = cache.ClydeAudio.LoadAudioWav(fileStream, path.ToString());
}
else
{

View File

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

View File

@@ -34,12 +34,10 @@ namespace Robust.Client.ResourceManagement
public override void Load(IResourceCache cache, ResPath path)
{
var clyde = IoCManager.Resolve<IClyde>();
var loadStepData = new LoadStepData {Path = path};
LoadPreTexture(cache, loadStepData);
loadStepData.AtlasTexture = clyde.LoadTextureFromImage(
loadStepData.AtlasTexture = cache.Clyde.LoadTextureFromImage(
loadStepData.AtlasSheet,
loadStepData.Path.ToString());

View File

@@ -1,6 +1,7 @@
using System.IO;
using System.Threading;
using Robust.Client.Graphics;
using Robust.Client.Graphics.Clyde;
using Robust.Shared.ContentPack;
using Robust.Shared.IoC;
using Robust.Shared.Utility;
@@ -27,8 +28,7 @@ namespace Robust.Client.ResourceManagement
ParsedShader = ShaderParser.Parse(reader, cache);
}
var clyde = IoCManager.Resolve<IClydeInternal>();
ClydeHandle = clyde.LoadShader(ParsedShader, path.ToString());
ClydeHandle = ((IClydeInternal)cache.Clyde).LoadShader(ParsedShader, path.ToString());
}
public override void Reload(IResourceCache cache, ResPath path, CancellationToken ct = default)
@@ -57,8 +57,7 @@ namespace Robust.Client.ResourceManagement
}
}
var clyde = IoCManager.Resolve<IClydeInternal>();
clyde.ReloadShader(ClydeHandle, ParsedShader);
((IClydeInternal)cache.Clyde).ReloadShader(ClydeHandle, ParsedShader);
}
}
}

View File

@@ -20,8 +20,6 @@ namespace Robust.Client.ResourceManagement
public override void Load(IResourceCache cache, ResPath path)
{
var clyde = IoCManager.Resolve<IClyde>();
if (path.Directory.Filename.EndsWith(".rsi"))
{
Logger.WarningS(
@@ -33,7 +31,7 @@ namespace Robust.Client.ResourceManagement
var data = new LoadStepData {Path = path};
LoadPreTexture(cache, data);
LoadTexture(clyde, data);
LoadTexture(cache.Clyde, data);
LoadFinish(cache, data);
}

View File

@@ -1,5 +1,4 @@
using Robust.Client.ResourceManagement;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Attributes;
@@ -29,7 +28,7 @@ public sealed class ClientSpriteSpecifierSerializer : SpriteSpecifierSerializer
}
var res = dependencies.Resolve<IResourceCache>();
var rsiPath = SpriteSpecifierSerializer.TextureRoot / valuePathNode.Value;
var rsiPath = TextureRoot / valuePathNode.Value;
if (!res.TryGetResource(rsiPath, out RSIResource? resource))
{
return new ErrorNode(node, "Failed to load RSI");
@@ -40,6 +39,10 @@ public sealed class ClientSpriteSpecifierSerializer : SpriteSpecifierSerializer
return new ErrorNode(node, "Invalid RSI state");
}
return new ValidatedMappingNode(new());
return new ValidatedMappingNode(new()
{
{ new ValidatedValueNode(new ValueDataNode("sprite")), new ValidatedValueNode(pathNode)},
{ new ValidatedValueNode(new ValueDataNode("state")), new ValidatedValueNode(valueStateNode)},
});
}
}

View File

@@ -1,7 +1,7 @@
<Control xmlns="https://spacestation14.io"
xmlns:gfx="clr-namespace:Robust.Client.Graphics">
<BoxContainer Orientation="Vertical">
<OutputPanel Name="Output" VerticalExpand="True">
<OutputPanel Name="Output" VerticalExpand="True" StyleClasses="monospace">
<OutputPanel.StyleBoxOverride>
<gfx:StyleBoxFlat BackgroundColor="#25252add"
ContentMarginLeftOverride="3" ContentMarginRightOverride="3"

View File

@@ -90,7 +90,7 @@ public sealed partial class DebugConsole
private async void TypeUpdateCompletions(bool fullUpdate)
{
var (args, _, _) = CalcTypingArgs();
var (args, _, _, str) = CalcTypingArgs();
if (args.Count != _compParamCount)
{
@@ -101,7 +101,7 @@ public sealed partial class DebugConsole
if (fullUpdate)
{
var seq = ++_compSeqSend;
var task = _consoleHost.GetCompletions(args, _compCancel.Token);
var task = _consoleHost.GetCompletions(args, str, _compCancel.Token);
if (!task.IsCompleted)
{
@@ -140,7 +140,7 @@ public sealed partial class DebugConsole
if (_compCurResult == null)
return;
var (_, curTyping, _) = CalcTypingArgs();
var (_, curTyping, _, _) = CalcTypingArgs();
var curSelected = _compFiltered?.Length > 0 ? _compFiltered[_compSelected] : default;
_compFiltered = FilterCompletions(_compCurResult.Options, curTyping);
@@ -168,7 +168,7 @@ public sealed partial class DebugConsole
DebugTools.AssertNotNull(_compCurResult);
var (_, _, endRange) = CalcTypingArgs();
var (_, _, endRange, _) = CalcTypingArgs();
var offset = CommandBar.GetOffsetAtIndex(endRange.start);
// Logger.Debug($"Offset: {offset}");
@@ -231,7 +231,7 @@ public sealed partial class DebugConsole
}
}
private (List<string> args, string curTyping, (int start, int end) lastRange) CalcTypingArgs()
private (List<string> args, string curTyping, (int start, int end) lastRange, string argStr) CalcTypingArgs()
{
var cursor = CommandBar.CursorPosition;
// Don't consider text after the cursor.
@@ -252,7 +252,7 @@ public sealed partial class DebugConsole
else
lastRange = (cursor, cursor);
return (args, args[^1], lastRange);
return (args, args[^1], lastRange, text.ToString());
}
private CompletionOption[] FilterCompletions(IEnumerable<CompletionOption> completions, string curTyping)
@@ -270,7 +270,7 @@ public sealed partial class DebugConsole
{
// Figure out typing word so we know how much to replace.
var (completion, _, completionFlags) = _compFiltered[_compSelected];
var (_, _, lastRange) = CalcTypingArgs();
var (_, _, lastRange, _) = CalcTypingArgs();
// Replace the full word from the start.
// This means that letter casing will match the completion suggestion.

View File

@@ -23,9 +23,9 @@ namespace Robust.Client.UserInterface.CustomControls
/// <summary>
/// Write a line with a specific color to the console window.
/// </summary>
void AddLine(string text, Color color);
void AddLine(FormattedMessage text, Color color);
void AddLine(string text);
void AddLine(FormattedMessage text);
void AddFormattedLine(FormattedMessage message);
@@ -108,7 +108,7 @@ namespace Robust.Client.UserInterface.CustomControls
private Color DetermineColor(bool local, bool error)
{
return Color.White;
return error ? Color.Red : Color.White;
}
protected override void FrameUpdate(FrameEventArgs args)
@@ -136,16 +136,16 @@ namespace Robust.Client.UserInterface.CustomControls
_flushHistoryToDisk();
}
public void AddLine(string text, Color color)
public void AddLine(FormattedMessage text, Color color)
{
var formatted = new FormattedMessage(3);
formatted.PushColor(color);
formatted.AddText(text);
formatted.AddMessage(text);
formatted.Pop();
AddFormattedLine(formatted);
}
public void AddLine(string text)
public void AddLine(FormattedMessage text)
{
AddLine(text, Color.White);
}

View File

@@ -15,6 +15,9 @@ public sealed class DefaultStylesheet
{
var notoSansFont = res.GetResource<FontResource>("/EngineFonts/NotoSans/NotoSans-Regular.ttf");
var notoSansFont12 = new VectorFont(notoSansFont, 12);
var notoSansMonoFont = res.GetResource<FontResource>("/EngineFonts/NotoSans/NotoSansMono-Regular.ttf");
var notoSansMono12 = new VectorFont(notoSansMonoFont, 12);
var theme = userInterfaceManager.CurrentTheme;
@@ -38,6 +41,13 @@ public sealed class DefaultStylesheet
Stylesheet = new Stylesheet(new StyleRule[]
{
/*
* Debug console and other monospace things.
*/
Element().Class("monospace")
.Prop("font", notoSansMono12),
/*
* OS Window defaults
*/

View File

@@ -1,3 +1,5 @@
using System;
using System.Text;
using DiscordRPC;
using DiscordRPC.Logging;
using Robust.Shared;
@@ -25,13 +27,17 @@ namespace Robust.Client.Utility
public void Initialize()
{
var state = _loc.GetString("discord-rpc-in-main-menu");
var largeImageKey = _configurationManager.GetCVar(CVars.DiscordRichPresenceSecondIconId);
var largeImageText = _loc.GetString("discord-rpc-in-main-menu-logo-text");
_defaultPresence = new()
{
State = _loc.GetString("discord-rpc-in-main-menu"),
State = Truncate(state, 128),
Assets = new Assets
{
LargeImageKey = "logo",
LargeImageText = "I think coolsville SUCKS"
LargeImageKey = Truncate(largeImageKey, 32),
LargeImageText = Truncate(largeImageText, 128),
}
};
_configurationManager.OnValueChanged(CVars.DiscordEnabled, newValue =>
@@ -95,18 +101,60 @@ namespace Robust.Client.Utility
public void Update(string serverName, string username, string maxUsers, string users)
{
_activePresence = new RichPresence
if (_client == null)
return;
try
{
Details = _loc.GetString("discord-rpc-on-server", ("servername", serverName)),
State = _loc.GetString("discord-rpc-players", ("players", users), ("maxplayers", maxUsers)),
Assets = new Assets
var details = _loc.GetString("discord-rpc-on-server", ("servername", serverName));
var state = _loc.GetString("discord-rpc-players", ("players", users), ("maxplayers", maxUsers));
var largeImageText = _loc.GetString("discord-rpc-character", ("username", username));
var largeImageKey = _configurationManager.GetCVar(CVars.DiscordRichPresenceMainIconId);
var smallImageKey = _configurationManager.GetCVar(CVars.DiscordRichPresenceSecondIconId);
// Strings are limited by byte count. See the setters in RichPresence. Hence the truncate calls.
_activePresence = new RichPresence
{
LargeImageKey = "devstation",
LargeImageText = _loc.GetString("discord-rpc-character", ("username", username)),
SmallImageKey = "logo"
}
};
_client?.SetPresence(_activePresence);
Details = Truncate(details, 128),
State = Truncate(state, 128),
Assets = new Assets
{
LargeImageKey = Truncate(largeImageKey, 32),
LargeImageText = Truncate(largeImageText, 128),
SmallImageKey = Truncate(smallImageKey, 32)
}
};
_client.SetPresence(_activePresence);
}
catch (Exception ex)
{
_client.Logger.Error($"Caught exception while updating discord rich presence. Exception:\n{ex}");
}
}
private string Truncate(string value, int bytes, string postfix = "...")
=> Truncate(value, bytes, postfix, Encoding.UTF8);
/// <summary>
/// Truncate strings down to some minimum byte count. If the string gets truncated, it will have the postfix appended.
/// </summary>
private string Truncate(string value, int bytes, string postfix, Encoding encoding)
{
value = value.Trim();
var output = value;
// Theres probably a better way of doing this, but I don't know how.
// If this wasn't a crude hack this function should
while (encoding.GetByteCount(output) > bytes)
{
if (value.Length == 0)
return string.Empty;
value = value.Substring(0, value.Length - 1);
output = value + postfix;
}
return output;
}
public void ClearPresence()

View File

@@ -88,7 +88,7 @@ namespace Robust.Client.ViewVariables.Instances
var stringified = PrettyPrint.PrintUserFacingWithType(obj, out var typeStringified);
if (typeStringified != "")
{
//var smallFont = new VectorFont(_resourceCache.GetResource<FontResource>("/Fonts/CALIBRI.TTF"), 10);
//var smallFont = new VectorFont(_resourceCache.GetResource<FontResource>("/EngineFonts/NotoSans/NotoSans-Regular.ttf"), 10);
// Custom ToString() implementation.
var headBox = new BoxContainer
{

View File

@@ -121,7 +121,7 @@ namespace Robust.Client.ViewVariables
}
//var smallFont =
// new VectorFont(IoCManager.Resolve<IResourceCache>().GetResource<FontResource>("/Fonts/CALIBRI.TTF"),
// new VectorFont(IoCManager.Resolve<IResourceCache>().GetResource<FontResource>("/EngineFonts/NotoSans/NotoSans-Regular.ttf"),
// 10);
// Custom ToString() implementation.

View File

@@ -57,7 +57,7 @@ namespace Robust.Client.ViewVariables
};
VBox.AddChild(BottomContainer);
//var smallFont = new VectorFont(_resourceCache.GetResource<FontResource>("/Fonts/CALIBRI.TTF"), 10);
//var smallFont = new VectorFont(_resourceCache.GetResource<FontResource>("/EngineFonts/NotoSans/NotoSans-Regular.ttf"), 10);
_bottomLabel = new Label
{

View File

@@ -30,7 +30,9 @@ using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Profiling;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Replays;
using Robust.Shared.Toolshed;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Threading;
@@ -101,6 +103,7 @@ namespace Robust.Server
[Dependency] private readonly IReplayRecordingManagerInternal _replay = default!;
[Dependency] private readonly IGamePrototypeLoadManager _protoLoadMan = default!;
[Dependency] private readonly NetworkResourceManager _netResMan = default!;
[Dependency] private readonly IReflectionManager _refMan = default!;
private readonly Stopwatch _uptimeStopwatch = new();
@@ -367,7 +370,9 @@ namespace Robust.Server
_prototype.Initialize();
_prototype.LoadDefaultPrototypes();
_prototype.ResolveResults();
_refMan.Initialize();
IoCManager.Resolve<ToolshedManager>().Initialize();
_consoleHost.Initialize();
_entityManager.Startup();
_mapManager.Startup();

View File

@@ -1,162 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Pidgin;
using Robust.Shared.GameObjects;
using static Pidgin.Parser;
using static Pidgin.Parser<char>;
namespace Robust.Server.Bql
{
public partial class BqlQueryManager
{
private readonly Dictionary<Type, Parser<char, BqlQuerySelectorParsed>> _parsers = new();
private Parser<char, BqlQuerySelectorParsed> _allQuerySelectors = default!;
private Parser<char, (IEnumerable<BqlQuerySelectorParsed>, string)> SimpleQuery => Parser.Map((en, _, rest) => (en, rest), SkipWhitespaces.Then(_allQuerySelectors).Many(), String("do").Then(SkipWhitespaces), Any.ManyString());
private static Parser<char, string> Word => OneOf(LetterOrDigit, Char('_')).ManyString();
private static Parser<char, object> Objectify<T>(Parser<char, T> inp)
{
return Parser.Map(x => (object) x!, inp);
}
private struct SubstitutionData
{
public string Name;
public SubstitutionData(string name)
{
Name = name;
}
}
private static Parser<char, SubstitutionData> Substitution =>
Try(Char('$').Then(OneOf(Uppercase, Char('_')).ManyString()))
.MapWithInput((x, _) => new SubstitutionData(x.ToString()));
private static Parser<char, int> Integer =>
Try(Int(10));
private static Parser<char, object> SubstitutableInteger =>
Objectify(Integer).Or(Objectify(Try(Substitution)));
private static Parser<char, double> Float =>
Try(Real);
private static Parser<char, object> SubstitutableFloat =>
Objectify(Float).Or(Objectify(Try(Substitution)));
private static Parser<char, double> Percentage =>
Try(Real).Before(Char('%'));
private static Parser<char, object> SubstitutablePercentage =>
Objectify(Percentage).Or(Objectify(Try(Substitution)));
private static Parser<char, EntityUid> EntityId =>
Try(Parser.Map(x => new EntityUid(x), Int(10)));
private static Parser<char, object> SubstitutableEntityId =>
Objectify(EntityId).Or(Objectify(Try(Substitution)));
private Parser<char, Type> Component =>
Try(Parser.Map(t => _componentFactory.GetRegistration(t).Type, Word));
private Parser<char, object> SubstitutableComponent =>
Objectify(Component).Or(Objectify(Try(Substitution)));
private static Parser<char, string> QuotedString =>
OneOf(Try(Char('"').Then(OneOf(new []
{
AnyCharExcept("\"")
}).ManyString().Before(Char('"')))), Try(Word));
private static Parser<char, object> SubstitutableString =>
Objectify(QuotedString).Or(Objectify(Try(Substitution)));
// thing to make sure it all compiles.
[UsedImplicitly]
private Parser<char, object> TypeSystemCheck =>
OneOf(new[]
{
Objectify(Integer),
Objectify(Percentage),
Objectify(EntityId),
Objectify(Component),
Objectify(Float),
Objectify(QuotedString)
});
private Parser<char, BqlQuerySelectorParsed> BuildBqlQueryParser(BqlQuerySelector inst)
{
if (inst.Arguments.Length == 0)
{
return Parser.Map(_ => new BqlQuerySelectorParsed(new List<object>(), inst.Token, false), SkipWhitespaces);
}
List<Parser<char, object>> argsParsers = new();
foreach (var (arg, _) in inst.Arguments.Select((x, i) => (x, i)))
{
List<Parser<char, object>> choices = new();
if ((arg & QuerySelectorArgument.String) == QuerySelectorArgument.String)
{
choices.Add(Try(SubstitutableString.Before(SkipWhitespaces).Labelled("string argument")));
}
if ((arg & QuerySelectorArgument.Component) == QuerySelectorArgument.Component)
{
choices.Add(Try(SubstitutableComponent.Before(SkipWhitespaces).Labelled("component argument")));
}
if ((arg & QuerySelectorArgument.EntityId) == QuerySelectorArgument.EntityId)
{
choices.Add(Try(SubstitutableEntityId.Before(SkipWhitespaces).Labelled("entity ID argument")));
}
if ((arg & QuerySelectorArgument.Integer) == QuerySelectorArgument.Integer)
{
choices.Add(Try(SubstitutableInteger.Before(SkipWhitespaces).Labelled("integer argument")));
}
if ((arg & QuerySelectorArgument.Percentage) == QuerySelectorArgument.Percentage)
{
choices.Add(Try(SubstitutablePercentage.Before(SkipWhitespaces).Labelled("percentage argument")));
}
if ((arg & QuerySelectorArgument.Float) == QuerySelectorArgument.Float)
{
choices.Add(Try(SubstitutableFloat.Before(SkipWhitespaces).Labelled("float argument")));
}
argsParsers.Add(OneOf(choices));
}
Parser<char, List<object>> finalParser = argsParsers[0].Map(x => new List<object> { x });
for (var i = 1; i < argsParsers.Count; i++)
{
finalParser = finalParser.Then(argsParsers[i], (list, o) =>
{
list.Add(o);
return list;
}).Labelled("arguments");
}
return Parser.Map(args => new BqlQuerySelectorParsed(args, inst.Token, false), finalParser);
}
private void DoParserSetup()
{
foreach (var inst in _instances)
{
_parsers.Add(inst.GetType(), BuildBqlQueryParser(inst));
}
_allQuerySelectors = Parser.Map((a,b) => (a,b), Try(String("not").Before(Char(' '))).Optional(), OneOf(_instances.Select(x =>
Try(String(x.Token).Before(Char(' '))))).Then(tok =>
_parsers[_queriesByToken[tok].GetType()])
).Map(pair =>
{
pair.b.Inverted = pair.a.HasValue;
return pair.b;
});
}
}
}

View File

@@ -1,39 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Pidgin;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Robust.Server.Bql
{
public partial class BqlQueryManager
{
public (IEnumerable<EntityUid>, string) SimpleParseAndExecute(string query)
{
var parsed = SimpleQuery.Parse(query);
if (parsed.Success)
{
var selectors = parsed.Value.Item1.ToArray();
if (selectors.Length == 0)
{
return (_entityManager.GetEntities(), parsed.Value.Item2);
}
var entities = _queriesByToken[selectors[0].Token]
.DoInitialSelection(selectors[0].Arguments, selectors[0].Inverted, _entityManager);
foreach (var sel in selectors[1..])
{
entities = _queriesByToken[sel.Token].DoSelection(entities, sel.Arguments, sel.Inverted, _entityManager);
}
return (entities, parsed.Value.Item2);
}
else
{
throw new Exception(parsed.Error!.RenderErrorMessage());
}
}
}
}

View File

@@ -1,44 +0,0 @@
using System;
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Reflection;
using Robust.Shared.Utility;
namespace Robust.Server.Bql
{
public sealed partial class BqlQueryManager : IBqlQueryManager
{
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
[Dependency] private readonly IComponentFactory _componentFactory = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
private readonly List<BqlQuerySelector> _instances = new();
private readonly Dictionary<string, BqlQuerySelector> _queriesByToken = new();
/// <summary>
/// Automatically registers all query selectors with the parser/executor.
/// </summary>
public void DoAutoRegistrations()
{
foreach (var type in _reflectionManager.FindTypesWithAttribute<RegisterBqlQuerySelectorAttribute>())
{
RegisterClass(type);
}
DoParserSetup();
}
/// <summary>
/// Internally registers the given <see cref="BqlQuerySelector"/>.
/// </summary>
/// <param name="bqlQuerySelector">The selector to register</param>
private void RegisterClass(Type bqlQuerySelector)
{
DebugTools.Assert(bqlQuerySelector.BaseType == typeof(BqlQuerySelector));
var inst = (BqlQuerySelector)Activator.CreateInstance(bqlQuerySelector)!;
_instances.Add(inst);
_queriesByToken.Add(inst.Token, inst);
}
}
}

View File

@@ -1,368 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
namespace Robust.Server.Bql
{
[RegisterBqlQuerySelector]
public sealed class WithQuerySelector : BqlQuerySelector
{
public override string Token => "with";
public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.Component };
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
{
var comp = (Type) arguments[0];
return input.Where(x => entityManager.HasComponent(x, comp) ^ isInverted);
}
public override IEnumerable<EntityUid> DoInitialSelection(IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
{
if (isInverted)
{
return base.DoInitialSelection(arguments, isInverted, entityManager);
}
return entityManager.GetAllComponents((Type) arguments[0], includePaused: true)
.Select(x => x.Owner);
}
}
[RegisterBqlQuerySelector]
public sealed class NamedQuerySelector : BqlQuerySelector
{
public override string Token => "named";
public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.String };
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
{
var r = new Regex("^" + (string) arguments[0] + "$");
return input.Where(e =>
{
if (entityManager.TryGetComponent<MetaDataComponent>(e, out var metaDataComponent))
return r.IsMatch(metaDataComponent.EntityName) ^ isInverted;
return isInverted;
});
}
}
[RegisterBqlQuerySelector]
public sealed class ParentedToQuerySelector : BqlQuerySelector
{
public override string Token => "parentedto";
public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.EntityId };
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
{
var uid = (EntityUid) arguments[0];
return input.Where(e => (entityManager.TryGetComponent<TransformComponent>(e, out var transform) &&
transform.ParentUid == uid) ^ isInverted);
}
}
[RegisterBqlQuerySelector]
public sealed class RecursiveParentedToQuerySelector : BqlQuerySelector
{
public override string Token => "rparentedto";
public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.EntityId };
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
{
var uid = (EntityUid) arguments[0];
return input.Where(e =>
{
if (!entityManager.TryGetComponent<TransformComponent>(e, out var transform))
return isInverted;
while (transform.ParentUid != EntityUid.Invalid)
{
if ((transform.ParentUid == uid) ^ isInverted)
return true;
if (!entityManager.TryGetComponent(transform.ParentUid, out transform))
return false;
}
return false;
});
}
}
[RegisterBqlQuerySelector]
public sealed class ChildrenQuerySelector : BqlQuerySelector
{
public override string Token => "children";
public override QuerySelectorArgument[] Arguments => Array.Empty<QuerySelectorArgument>();
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
{
foreach (var uid in input)
{
if (!entityManager.TryGetComponent(uid, out TransformComponent? xform)) continue;
foreach (var child in xform.ChildEntities)
{
yield return child;
}
}
}
}
[RegisterBqlQuerySelector]
public sealed class RecursiveChildrenQuerySelector : BqlQuerySelector
{
public override string Token => "rchildren";
public override QuerySelectorArgument[] Arguments => Array.Empty<QuerySelectorArgument>();
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments,
bool isInverted, IEntityManager entityManager)
{
IEnumerable<EntityUid> toSearch = input;
while (true)
{
// TODO: Reduce LINQ chaining
var doing = toSearch.Where(entityManager.HasComponent<TransformComponent>).Select(entityManager.GetComponent<TransformComponent>).ToArray();
var search = doing.SelectMany(x => x.ChildEntities);
if (!search.Any())
break;
toSearch = doing.SelectMany(x => x.ChildEntities).Where(x => x != EntityUid.Invalid);
foreach (var xform in doing)
{
foreach (var uid in xform.ChildEntities)
{
// This should never happen anyway
if (uid == EntityUid.Invalid) continue;
yield return uid;
}
}
}
}
}
[RegisterBqlQuerySelector]
public sealed class ParentQuerySelector : BqlQuerySelector
{
public override string Token => "parent";
public override QuerySelectorArgument[] Arguments => Array.Empty<QuerySelectorArgument>();
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
{
return input.Where(entityManager.HasComponent<TransformComponent>)
.Distinct();
}
public override IEnumerable<EntityUid> DoInitialSelection(IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
{
return DoSelection(
Query(entityManager.AllEntityQueryEnumerator<TransformComponent>()),
arguments,
isInverted, entityManager);
IEnumerable<EntityUid> Query(AllEntityQueryEnumerator<TransformComponent> enumerator)
{
while (enumerator.MoveNext(out var entityUid, out _))
{
yield return entityUid;
}
}
}
}
[RegisterBqlQuerySelector]
public sealed class AboveQuerySelector : BqlQuerySelector
{
public override string Token => "above";
public override QuerySelectorArgument[] Arguments => new [] { QuerySelectorArgument.String };
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
{
var tileDefinitionManager = IoCManager.Resolve<ITileDefinitionManager>();
var tileTy = tileDefinitionManager[(string) arguments[0]];
var map = IoCManager.Resolve<IMapManager>();
if (tileTy.TileId == 0)
{
return input.Where(e => entityManager.TryGetComponent<TransformComponent>(e, out var transform) && (transform.GridUid is null) ^ isInverted);
}
else
{
return input.Where(e =>
{
if (!entityManager.TryGetComponent<TransformComponent>(e, out var transform)) return isInverted;
if (!map.TryGetGrid(transform.GridUid, out var grid))
return isInverted;
return (grid.GetTileRef(transform.Coordinates).Tile.TypeId == tileTy.TileId) ^ isInverted;
});
}
}
}
[RegisterBqlQuerySelector]
// ReSharper disable once InconsistentNaming the name is correct shut up
public sealed class OnGridQuerySelector : BqlQuerySelector
{
public override string Token => "ongrid";
public override QuerySelectorArgument[] Arguments => new [] { QuerySelectorArgument.Integer };
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
{
// TODO: Probably easier and significantly faster to just iterate the grid's children.
var grid = new EntityUid((int) arguments[0]);
return input.Where(e => (entityManager.TryGetComponent<TransformComponent>(e, out var transform) && transform.GridUid == grid) ^ isInverted);
}
}
[RegisterBqlQuerySelector]
// ReSharper disable once InconsistentNaming the name is correct shut up
public sealed class OnMapQuerySelector : BqlQuerySelector
{
public override string Token => "onmap";
public override QuerySelectorArgument[] Arguments => new [] { QuerySelectorArgument.Integer };
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
{
// TODO: Just use EntityLookup GetEntitiesInMap
var map = new MapId((int) arguments[0]);
return input.Where(e => (entityManager.TryGetComponent<TransformComponent>(e, out var transform) && transform.MapID == map) ^ isInverted);
}
}
[RegisterBqlQuerySelector]
public sealed class PrototypedQuerySelector : BqlQuerySelector
{
public override string Token => "prototyped";
public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.String };
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
{
var name = (string) arguments[0];
return input.Where(e => (entityManager.TryGetComponent<MetaDataComponent>(e, out var metaData) && metaData.EntityPrototype?.ID == name) ^ isInverted);
}
}
[RegisterBqlQuerySelector]
public sealed class RecursivePrototypedQuerySelector : BqlQuerySelector
{
public override string Token => "rprototyped";
public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.String };
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
{
var name = (string) arguments[0];
return input.Where(e =>
{
if (!entityManager.TryGetComponent<MetaDataComponent>(e, out var metaData))
return isInverted;
if ((metaData.EntityPrototype?.ID == name) ^ isInverted)
return true;
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
return metaData.EntityPrototype != null && prototypeManager.EnumerateParents<EntityPrototype>(metaData.EntityPrototype.ID).Any(x => x.Name == name) ^ isInverted;
});
}
}
[RegisterBqlQuerySelector]
public sealed class SelectQuerySelector : BqlQuerySelector
{
public override string Token => "select";
public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.Integer | QuerySelectorArgument.Percentage };
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
{
if (arguments[0] is int)
{
var inp = input.OrderBy(_ => Guid.NewGuid()).ToArray();
var taken = (int) arguments[0];
if (isInverted)
taken = Math.Max(0, inp.Length - taken);
return inp.Take(taken);
}
var enumerable = input.OrderBy(_ => Guid.NewGuid()).ToArray();
var amount = isInverted
? (int) Math.Floor(enumerable.Length * Math.Clamp(1 - (double) arguments[0], 0, 1))
: (int) Math.Floor(enumerable.Length * Math.Clamp((double) arguments[0], 0, 1));
return enumerable.Take(amount);
}
}
[RegisterBqlQuerySelector]
public sealed class NearQuerySelector : BqlQuerySelector
{
public override string Token => "near";
public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.Float };
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
{
var radius = (float)(double)arguments[0];
var entityLookup = entityManager.System<EntityLookupSystem>();
var xformQuery = entityManager.GetEntityQuery<TransformComponent>();
var distinct = new HashSet<EntityUid>();
foreach (var uid in input)
{
foreach (var near in entityLookup.GetEntitiesInRange(xformQuery.GetComponent(uid).Coordinates,
radius))
{
if (!distinct.Add(near)) continue;
yield return near;
}
}
//BUG: GetEntitiesInRange effectively uses manhattan distance. This is not intended, near is supposed to be circular.
}
}
[RegisterBqlQuerySelector]
// ReSharper disable once InconsistentNaming the name is correct shut up
public sealed class AnchoredQuerySelector : BqlQuerySelector
{
public override string Token => "anchored";
public override QuerySelectorArgument[] Arguments => Array.Empty<QuerySelectorArgument>();
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
{
return input.Where(e => (entityManager.TryGetComponent<TransformComponent>(e, out var transform) && transform.Anchored) ^ isInverted);
}
}
[RegisterBqlQuerySelector]
public sealed class PausedQuerySelector : BqlQuerySelector
{
public override string Token => "paused";
public override QuerySelectorArgument[] Arguments => Array.Empty<QuerySelectorArgument>();
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
{
return input.Where(e => entityManager.GetComponent<MetaDataComponent>(e).EntityPaused ^ isInverted);
}
}
}

View File

@@ -1,63 +0,0 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Robust.Server.Bql
{
[Flags]
[PublicAPI]
public enum QuerySelectorArgument
{
Integer = 0b00000001,
Float = 0b00000010,
String = 0b00000100,
Percentage = 0b00001000,
Component = 0b00010000,
//SubQuery = 0b00100000,
EntityId = 0b01000000,
}
[PublicAPI]
public abstract class BqlQuerySelector
{
/// <summary>
/// The token name for the given QuerySelector, for example `when`.
/// </summary>
public virtual string Token => throw new NotImplementedException();
/// <summary>
/// Arguments for the given QuerySelector, presented as "what arguments are permitted in what spot".
/// </summary>
public virtual QuerySelectorArgument[] Arguments => throw new NotImplementedException();
/// <summary>
/// Performs a transform over it's input entity list, whether that be filtering (selecting) or expanding the
/// input on some criteria like what entities are nearby.
/// </summary>
/// <param name="input">Input entity list.</param>
/// <param name="arguments">Parsed selector arguments.</param>
/// <param name="isInverted">Whether the query is inverted.</param>
/// <param name="entityManager">The entity manager.</param>
/// <returns>New list of entities</returns>
/// <exception cref="NotImplementedException">someone is a moron if this happens.</exception>
public abstract IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input,
IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager);
/// <summary>
/// Performs selection as the first selector in the query. Allows for optimizing when you can be more efficient
/// than just querying every entity.
/// </summary>
/// <param name="arguments"></param>
/// <param name="isInverted"></param>
/// <param name="entityManager"></param>
/// <returns></returns>
public virtual IEnumerable<EntityUid> DoInitialSelection(IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
{
return DoSelection(entityManager.GetEntities(), arguments, isInverted, entityManager);
}
[UsedImplicitly]
protected BqlQuerySelector() {}
}
}

View File

@@ -1,19 +0,0 @@
using System;
using System.Collections.Generic;
namespace Robust.Server.Bql
{
public struct BqlQuerySelectorParsed
{
public List<object> Arguments;
public string Token;
public bool Inverted;
public BqlQuerySelectorParsed(List<object> arguments, string token, bool inverted)
{
Arguments = arguments;
Token = token;
Inverted = inverted;
}
}
}

View File

@@ -1,78 +0,0 @@
using System.Globalization;
using System.Linq;
using Robust.Server.Player;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Robust.Server.Bql
{
public sealed class ForAllCommand : LocalizedCommands
{
[Dependency] private readonly IBqlQueryManager _bql = default!;
[Dependency] private readonly IEntityManager _entities = default!;
public override string Command => "forall";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length < 2)
{
shell.WriteLine(Help);
return;
}
var transformSystem = _entities.System<SharedTransformSystem>();
var (entities, rest) = _bql.SimpleParseAndExecute(argStr[6..]);
foreach (var ent in entities.ToList())
{
var cmds = SubstituteEntityDetails(_entities, transformSystem, shell, ent, rest).Split(";");
foreach (var cmd in cmds)
{
shell.ExecuteCommand(cmd);
}
}
}
// This will be refactored out soon.
private static string SubstituteEntityDetails(
IEntityManager entMan,
SharedTransformSystem transformSystem,
IConsoleShell shell,
EntityUid ent,
string ruleString)
{
var transform = entMan.GetComponent<TransformComponent>(ent);
var metadata = entMan.GetComponent<MetaDataComponent>(ent);
var worldPos = transformSystem.GetWorldPosition(transform);
var localPos = transform.LocalPosition;
// gross, is there a better way to do this?
ruleString = ruleString.Replace("$ID", ent.ToString());
ruleString = ruleString.Replace("$WX", worldPos.X.ToString(CultureInfo.InvariantCulture));
ruleString = ruleString.Replace("$WY", worldPos.Y.ToString(CultureInfo.InvariantCulture));
ruleString = ruleString.Replace("$LX", localPos.X.ToString(CultureInfo.InvariantCulture));
ruleString = ruleString.Replace("$LY", localPos.Y.ToString(CultureInfo.InvariantCulture));
ruleString = ruleString.Replace("$NAME", metadata.EntityName);
if (shell.Player is { AttachedEntity: { } pEntity})
{
var pTransform = entMan.GetComponent<TransformComponent>(pEntity);
var pWorldPos = transformSystem.GetWorldPosition(pTransform);
var pLocalPos = pTransform.LocalPosition;
ruleString = ruleString.Replace("$PID", pEntity.ToString());
ruleString = ruleString.Replace("$PWX", pWorldPos.X.ToString(CultureInfo.InvariantCulture));
ruleString = ruleString.Replace("$PWY", pWorldPos.Y.ToString(CultureInfo.InvariantCulture));
ruleString = ruleString.Replace("$PLX", pLocalPos.X.ToString(CultureInfo.InvariantCulture));
ruleString = ruleString.Replace("$PLY", pLocalPos.Y.ToString(CultureInfo.InvariantCulture));
}
return ruleString;
}
}
}

View File

@@ -1,11 +0,0 @@
using System.Collections.Generic;
using Robust.Shared.GameObjects;
namespace Robust.Server.Bql
{
public interface IBqlQueryManager
{
public (IEnumerable<EntityUid>, string) SimpleParseAndExecute(string query);
void DoAutoRegistrations();
}
}

View File

@@ -1,14 +0,0 @@
using System;
using JetBrains.Annotations;
namespace Robust.Server.Bql
{
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
[BaseTypeRequired(typeof(BqlQuerySelector))]
[MeansImplicitUse]
[PublicAPI]
public sealed class RegisterBqlQuerySelectorAttribute : Attribute
{
}
}

View File

@@ -1,8 +1,12 @@
using Robust.Server.Player;
using Robust.Shared.Map;
using Robust.Shared.Players;
using Robust.Shared.Toolshed;
using Robust.Shared.Toolshed.Errors;
namespace Robust.Server.Console
{
internal sealed class ConGroupController : IConGroupController
internal sealed class ConGroupController : IConGroupController, IPermissionController
{
public IConGroupControllerImplementation? Implementation { get; set; }
@@ -30,5 +34,11 @@ namespace Robust.Server.Console
{
return Implementation?.CanAdminReloadPrototypes(session) ?? false;
}
public bool CheckInvokable(CommandSpec command, ICommonSession? user, out IConError? error)
{
error = null;
return Implementation?.CheckInvokable(command, user, out error) ?? false;
}
}
}

View File

@@ -1,8 +1,9 @@
using Robust.Server.Player;
using Robust.Shared.Toolshed;
namespace Robust.Server.Console
{
public interface IConGroupControllerImplementation
public interface IConGroupControllerImplementation : IPermissionController
{
bool CanCommand(IPlayerSession session, string cmdName);
bool CanAdminPlace(IPlayerSession session);

View File

@@ -9,6 +9,8 @@ using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Players;
using Robust.Shared.Toolshed;
using Robust.Shared.Toolshed.Syntax;
using Robust.Shared.Utility;
namespace Robust.Server.Console
@@ -19,6 +21,7 @@ namespace Robust.Server.Console
[Dependency] private readonly IConGroupController _groupController = default!;
[Dependency] private readonly IPlayerManager _players = default!;
[Dependency] private readonly ISystemConsoleManager _systemConsole = default!;
[Dependency] private readonly ToolshedManager _toolshed = default!;
public ServerConsoleHost() : base(isServer: true) {}
@@ -45,19 +48,31 @@ namespace Robust.Server.Console
/// <inheritdoc />
public override void WriteLine(ICommonSession? session, string text)
{
var msg = new FormattedMessage();
msg.AddText(text);
if (session is IPlayerSession playerSession)
OutputText(playerSession, text, false);
OutputText(playerSession, msg, false);
else
OutputText(null, text, false);
OutputText(null, msg, false);
}
public override void WriteLine(ICommonSession? session, FormattedMessage msg)
{
if (session is IPlayerSession playerSession)
OutputText(playerSession, msg, false);
else
OutputText(null, msg, false);
}
/// <inheritdoc />
public override void WriteError(ICommonSession? session, string text)
{
var msg = new FormattedMessage();
msg.AddText(text);
if (session is IPlayerSession playerSession)
OutputText(playerSession, text, true);
OutputText(playerSession, msg, true);
else
OutputText(null, text, true);
OutputText(null, msg, true);
}
public bool IsCmdServer(IConsoleCommand cmd) => true;
@@ -70,13 +85,13 @@ namespace Robust.Server.Console
var localShell = shell.ConsoleHost.LocalShell;
var sudoShell = new SudoShell(this, localShell, shell);
ExecuteInShell(sudoShell, argStr["sudo ".Length..]);
}, (shell, args) =>
}, (shell, args, argStr) =>
{
var localShell = shell.ConsoleHost.LocalShell;
var sudoShell = new SudoShell(this, localShell, shell);
#pragma warning disable CA2012
return CalcCompletions(sudoShell, args);
return CalcCompletions(sudoShell, args, argStr);
#pragma warning restore CA2012
});
@@ -117,6 +132,18 @@ namespace Robust.Server.Console
AnyCommandExecuted?.Invoke(shell, cmdName, command, cmdArgs);
conCmd.Execute(shell, command, cmdArgs);
}
else
{
// toolshed time
_toolshed.InvokeCommand(shell, command, null, out var res, out var ctx);
foreach (var err in ctx.GetErrors())
{
ctx.WriteLine(err.Describe());
}
shell.WriteLine(_toolshed.PrettyPrintType(res));
}
}
catch (Exception e)
{
@@ -162,7 +189,7 @@ namespace Robust.Server.Console
ExecuteCommand(session, text);
}
private void OutputText(IPlayerSession? session, string text, bool error)
private void OutputText(IPlayerSession? session, FormattedMessage text, bool error)
{
if (session != null)
{
@@ -183,10 +210,25 @@ namespace Robust.Server.Console
private async void HandleConCompletions(MsgConCompletion message)
{
var session = _players.GetSessionByChannel(message.MsgChannel);
var shell = new ConsoleShell(this, session, false);
var result = await CalcCompletions(shell, message.Args);
var result = await CalcCompletions(shell, message.Args, message.ArgString);
if ((result.Options.Length == 0 && result.Hint is null) || message.Args.Length <= 1)
{
var parser = new ForwardParser(message.ArgString, _toolshed);
CommandRun.TryParse(false, true, parser, null, null, false, out _, out var completions, out _);
if (completions == null)
{
goto done;
}
var (shedRes, _) = await completions.Value;
shedRes ??= CompletionResult.Empty;
result = new CompletionResult(shedRes.Options.Concat(result.Options).ToArray(), shedRes.Hint ?? result.Hint);
}
done:
var msg = new MsgConCompletionResp
{
Result = result,
@@ -199,7 +241,7 @@ namespace Robust.Server.Console
NetManager.ServerSendMessage(msg, message.MsgChannel);
}
private ValueTask<CompletionResult> CalcCompletions(IConsoleShell shell, string[] args)
private ValueTask<CompletionResult> CalcCompletions(IConsoleShell shell, string[] args, string argStr)
{
// Logger.Debug(string.Join(", ", args));
@@ -217,7 +259,7 @@ namespace Robust.Server.Console
if (!ShellCanExecute(shell, cmdName))
return ValueTask.FromResult(CompletionResult.Empty);
return cmd.GetCompletionAsync(shell, args[1..], default);
return cmd.GetCompletionAsync(shell, args[1..], argStr, default);
}
private sealed class SudoShell : IConsoleShell
@@ -254,6 +296,12 @@ namespace Robust.Server.Console
_sudoer.WriteLine(text);
}
public void WriteLine(FormattedMessage message)
{
_owner.WriteLine(message);
_sudoer.WriteLine(message);
}
public void WriteError(string text)
{
_owner.WriteError(text);

View File

@@ -1,6 +1,9 @@
using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations;
using Robust.Server.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Network;
using Robust.Shared.Utility;
namespace Robust.Server.GameObjects
@@ -11,6 +14,8 @@ namespace Robust.Server.GameObjects
[UsedImplicitly]
public sealed class ActorSystem : EntitySystem
{
[Dependency] private readonly IPlayerManager _playerManager = default!;
public override void Initialize()
{
base.Initialize();
@@ -116,6 +121,20 @@ namespace Robust.Server.GameObjects
// The player is fully detached now that the component has shut down.
RaiseLocalEvent(entity, new PlayerDetachedEvent(entity, component.PlayerSession), true);
}
public bool TryGetActorFromUserId(NetUserId? userId, [NotNullWhen(true)] out IPlayerSession? actor, [MaybeNullWhen(true)] out EntityUid? actorEntity)
{
actor = null;
actorEntity = null;
if (userId != null)
{
if (!_playerManager.TryGetSessionById(userId.Value, out actor))
return false;
actorEntity = actor.AttachedEntity;
}
return actor != null;
}
}
/// <summary>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -206,6 +206,10 @@ namespace Robust.Shared.GameObjects
void IPostInjectInit.PostInject()
{
Log = LogManager.GetSawmill(SawmillName);
#if !DEBUG
Log.Level = LogLevel.Info;
#endif
}
}
}

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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