Compare commits

...

68 Commits

Author SHA1 Message Date
metalgearsloth
c76444a33f Version: 256.0.0 2025-05-10 13:40:38 +10:00
B_Kirill
4754661467 Cleanup warnings: CS0649 (#5891)
* Clean up

* Remove "struct UpdateTreesJob"

* Use #pragma

* Use #if DEBUG

* More #if DEBUG
2025-05-10 12:40:15 +10:00
B_Kirill
2a8b776ee9 Cleanup warnings: CS0414 (#5892)
* Clean up

* Use #pragma
2025-05-10 12:39:46 +10:00
SpaceManiac
7d8e5a5841 Fix linear lookup on a dictionary in PlacementManager (#5911) 2025-05-06 16:05:58 +02:00
Centronias
8e416e4519 Makes ItemList not run deselection callback on all list items (#5861)
* Makes ItemList not run deselection callback on all list items

even when they weren't selected

* I cannot be expected to do things intelligently at 2a

* Optimize local usage.

* I'm pretty sure the test failure isn't from ItemList

* switch to avoiding doing anything in the `Selected` setter if the value-to-set-to is the same as the current value.
2025-05-06 14:05:05 +02:00
SlamBamActionman
65f74943d3 Add support for rotated/mirrored tiles (#5652)
* Initial commit

* Add tile rotation/mirror perms

* Nicer UI for the rotation

* Review fixes (also seemed to have missed applying the serialization reading oops)

* One less byte, one less struct size!

* Pretty sure it goes here too

* Fix error
2025-05-05 23:13:31 +10:00
Errant
eb5ed12270 Serialize TimeSpan from text (#5865)
* timespanserializer cleanup

* string reading

* high speed, low drag

* unit tests

* Update Robust.Shared/Serialization/TypeSerializers/Implementations/TimespanSerializer.cs

---------

Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>
2025-05-05 00:57:01 +02:00
PJB3005
c43b7b16c0 Add CancellationTokenRegistration to sandbox 2025-05-05 00:45:05 +02:00
slarticodefast
aee03f0805 fix yaml hotreloading (#5907) 2025-05-04 04:33:37 +02:00
dffdff2423
cfd2b03248 Check audio file signatures instead of extensions (#5894)
* Check audio file signatures instead of extensions

Fixes #5789

Test audio files based on their magic bytes.

* Test for a seekable stream

* Remove Take and Skip linq
2025-05-04 04:33:01 +02:00
dffdff2423
8905a3fe14 Add documentation to the serializer interfaces and remove ITypeReaderWriter (#5897)
* Add documentation to the serializer interfaces

* Remove ITypeReaderWriter and fix the docs

* Fix spelling errors and incorrect docstrings
2025-05-04 04:03:17 +02:00
PJB3005
a878da5b80 Allow texture preload to be skipped for some textures
This is a far cry from a proper resource tracking system, but it's something to avoid a ton of otherwise-unused parallax textures being loaded at game start and consuming VRAM.
2025-05-04 02:24:57 +02:00
Leon Friedrich
806c23e034 Move EntityExt.AsNullable extension methods into the Entity struct (#5899)
* Move `EntityExt.AsNullable` extension methods into the Entity struct

* use constructor

* a
2025-05-04 01:24:23 +02:00
metalgearsloth
e80f5d13a1 Add Vector2i / bitmask conversions (#5901)
Content uses for a couple tile-based flags.
2025-05-04 01:23:04 +02:00
PJB3005
a6905151b6 Move PointLight component states to shared
Necessary so the client can calculate an initial state, which is necessary for prediction and replay seeking to work properly.

Fixes the nuke in SS14 not having its light turn off when going back in a replay.
2025-05-02 01:21:17 +02:00
PJB3005
e742f021fa Dev window tab to show all loaded textures 2025-04-30 15:50:39 +02:00
metalgearsloth
62b4714f1f Version: 255.1.0 2025-04-30 23:38:21 +10:00
Leon Friedrich
1d0404953f Add GridUidChangedEvent and MapUidChangedEvent (#5893)
* Add GridUidChangedEvent and MapUidChangedEvent

* cleanup

* Fix assert

* more fixes

* docs

* record struct

* Use implicit tuple constructor

* stinky review

---------

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2025-04-30 18:36:53 +10:00
Leon Friedrich
d0da13f895 Fix CompileRobustXamlTask for benchmarks (#5902)
* Fix benchmarks

* I love it when path separators are also escape chars
2025-04-30 13:16:28 +10:00
metalgearsloth
ff23f98b26 Document container events (#5904)
It's hard to discern what it's raised directed on and this makes it much easier.
2025-04-30 13:10:03 +10:00
Leon Friedrich
ccfef2a786 Fix PVS NRE (#5900) 2025-04-28 20:11:18 +10:00
B_Kirill
62ce9724fc Clean up (#5890) 2025-04-26 22:59:02 +10:00
Milon
3bbe0e7f44 ftl hot reloading (#5874)
* sloth is so going to kill me

* the voices in my head told me to do this

* Register ILocalizationManagerInternal on client

* Avoid breaking change

* Cleanup

* Release notes
2025-04-26 22:38:56 +10:00
chromiumboy
addd8b5bdd Initial commit (#5880) 2025-04-25 22:19:11 +10:00
ElectroJr
03f8d4d3e0 Version: 255.0.0 2025-04-25 17:32:45 +12:00
Leon Friedrich
728d541ca5 Remove assert in UserInterfaceManager.KeyBindDown (#5889) 2025-04-25 15:30:26 +10:00
Leon Friedrich
4cbce064b8 Fix grid fixtures using locale dependent ids (#5887) 2025-04-24 12:49:04 +02:00
IProduceWidgets
93bb7b1532 Sandbox whitelist for calendar stuff. (#5888) 2025-04-24 12:46:41 +02:00
Pieter-Jan Briers
3ba91d2ed0 Add cycle detection when setting MIDI renderer masters (#5879)
If these things ever get in a cycle (as they did in some SS14 replays),
it'll likely get the client stuck in an infinite loop. Avoid that.
2025-04-24 18:12:59 +10:00
Tayrtahn
8f9e0f6bab Fix warnings in PhysicsMap_Test (#5884) 2025-04-24 18:10:13 +10:00
Tayrtahn
3ed408de5d Fix warnings in Joints_Test (#5885) 2025-04-24 18:09:27 +10:00
DrSmugleaf
2f85408f8f Fix SharedPhysicsSystem.CollideContacts looping contacts that are in nullspace (#5881)
* Fix SharedJointSystem.CreateDistanceJoint taking in an int for minimumDistance instead of a float

* Fix SharedPhysicsSystem.CollideContacts looping contacts that are in nullspace
2025-04-22 22:50:56 +10:00
Leon Friedrich
f49b01b1b7 PredictedDeleteEntity fixes (#5873)
* PredictedDeleteEntity fixes

* release notes
2025-04-20 23:38:43 +10:00
Leon Friedrich
3ce764311d Make RobustIntegrationTest pool by default (#5872) 2025-04-20 17:53:55 +10:00
B_Kirill
adf0b6ae78 Fix CA2264 in Resource Manager (#5870) 2025-04-20 05:02:16 +02:00
metalgearsloth
04406311dc Use EntityQuery for map / grid calls (#5869)
Avoids the type lookup etc etc.
2025-04-20 12:59:06 +10:00
Kyle Tyo
ee45a608b9 Replace IsMap and IsGrid calls with HasComp (#5866) 2025-04-20 12:21:19 +10:00
metalgearsloth
077c91a54b Add autocomplete to scale command (#5864)
* Add autocomplete to scale command

* Also the scale

* this
2025-04-19 22:50:09 +10:00
metalgearsloth
2ac17009ee Deep-copy fixtures on client (#5863)
Fixes a LOTTA bugs
2025-04-19 19:31:36 +10:00
metalgearsloth
a73d1b6666 Update container log warning for pred spawns (#5860)
This was on the full branch I just forgot to pull it out. This is intended that they can go into containers.
2025-04-19 18:08:19 +10:00
metalgearsloth
e5d6f194be Add nullable PredictedDel overloads (#5859)
Forgor when I updated it to Entity<T>
2025-04-19 17:47:25 +10:00
Pieter-Jan Briers
72d893dec5 Add "obsolete inheritance" analyzer (#5858)
This allows us to make it obsolete to *inherit* from a class, and only that.

Intended so people stop inheriting UI controls for no good reason.

Fixes #5856
2025-04-19 17:29:17 +10:00
metalgearsloth
191d7ab81c Version: 254.1.0 2025-04-19 16:53:53 +10:00
metalgearsloth
65d2f2dd2f Entity spawn prediction v1 (#5841)
* Entity spawn prediction v1

Client can't properly interact with this but that requires additional work on top so we can cleanly split this.

* This

* delete fix

* cats
2025-04-19 16:50:41 +10:00
Dae
02b451db2a Add no derivatives licenses to rga validation (#5749) 2025-04-19 12:23:21 +10:00
Ed
d5d4584e11 Update Clyde.GridRendering.cs (#5852) 2025-04-19 00:01:16 +02:00
metalgearsloth
b146b1b82c Version: 254.0.0 2025-04-18 19:06:45 +10:00
Leon Friedrich
2f8f4f2f7a Make MappingDataNode use string keys (#5783)
* string keys

* obsoletions

* Fix ValueTupleSerializer

* Release notes

* fix release note conflict

* cleanup

* Fix yaml validator & tests

* cleanup release notes

* a

* enumerator allocations

* Also sequence enumerator alloc
2025-04-18 19:01:34 +10:00
Tayrtahn
7365a59bd9 Cleanup warnings in CollisionWake_Test (#5847) 2025-04-18 12:05:48 +10:00
Tayrtahn
37560f663b Add GetContainingContainers method to SharedContainerSystem (#5803)
* Add SharedContainerSystem.EnumerateContainingContainers

* More obvious name
2025-04-17 21:51:23 +10:00
TemporalOroboros
bd489e9218 A compilation of simple one-line fixes (#5661)
* Fix warnings in SharedJointSystem

* Fix reference to EC TransformComponent method
Replaces call to TransformComponent.GetMapUid with SharedTransformSystem.GetMap

* Fix obsolete calls in SharedPhysicsSystem.Contacts.cs
Fixes a couple calls to obsolete varients of SetAwake and an obsolete call to RegenerateContacts by converting them to their Entity<T> varients

* Fix obsolete call in SharedPhysicsSystem.Components.cs
Adds one set of parenthesis to convert a 'uid, comp, comp, comp' call to an 'Entity<T, T, T> call.

* Removes unused local var
Removes an unused list of broadphases that was being allocated in TryCollideRect

* One-line fixes in SharedPhysicsSystem.Islands.cs
Fixes all of the easy warnings regarding physics island processing, the rest require more complicated changes than a simple argument rearrangement

* Fix obsolete method call in SharedMapSystem

* Fix a few obsolete ToMap calls in EntityLookup.Queries

* Fix calls to obsolete EntityCoordinate methods in SharedMapSystem.Grids

* Fix calls to obsolete EntityCoordinate methods in SharedLookupSystem.ComponentQueries

* Fix a few obsolete method calls in entity spawning

* Fix obsolete method calls in MapLoaderSystem

* Fix obsolete method call in GridFixtureSystem

* Fix obsolete IsMapInitialized call in SaveMap command

* Fix obsolete MapPosition reference in Client.EyeSystem

* Fix obsolete EntitySystem.Get<TSystem> references in DebugLightTreeSystem

* Fix obsolete EntitySystem.Get<TSystem> reference in DebugEntityLookup command

* Fix obsolete method calls in SpriteBoundsOverlay
Slightly more complicated than the rest, but it's really just changing an unused dependency over to use SharedTransformSystem

* Remove unused IClyde references from controls
LineEdit and TextEdit never use their IClyde dependencies and it generates a warning so yeet

* Remove use of EntitySystem.Get from lightbb command

* Fix DebugDrawingSystem
Removes a bunch of unused private IEntityManager vars
Also removes an obsolete use of TransformComponent.GetWorldPositionRotation

* Removes duplicate position set when splitting grids
There's nothing saying why this is this way and the blame looks like it was an oversight when replacing a bit where they set position and then rotation
Please, oh Chesterton's Fence, spare me your wrath

* Fix obsolete method use in PlacementMode

* Fix obsolete method use in Placement Modes

* Removes unused local var in gamestate management

* Fix unreachable code warnings in gamestate management
Use #else sections to make sure they don't complain about being on the wrong side of a throw

* Fix obsolete ToMap use in EyeManager

* Make InputManager use a sawmill to log

* Fix obsolete ContainerManagerComponent method calls in ContainerSystem

* Make ClientPrototypeManager use a sawmill for logging

* poke tests

* Use LocalizedEntityCommands for SpriteBoundsOverlay toggle

* Use LocalizedEntityCommands for system toggles

---------

Co-authored-by: ElectroJr <leonsfriedrich@gmail.com>
2025-04-17 11:57:03 +10:00
metalgearsloth
33166e8866 Component lifecycle generics (#5844)
Non-breaking minor.
2025-04-16 21:46:14 +10:00
Leon Friedrich
ed16032280 Disable tile edges (#5842) 2025-04-16 21:25:31 +10:00
metalgearsloth
cf785c886b Mark GetCollidingEntities as obsolete (#5831)
Bad methods, entitylookup is better (it does everything for area queries) and anyone who wants contacts should be querying those directly.
2025-04-14 18:50:01 +10:00
metalgearsloth
6f28c396cf Version: 253.0.0 2025-04-14 14:12:32 +10:00
metalgearsloth
b631f408f2 ItemList optimisation (#5796)
- VV AddComponent window no longer takes 300ms every time you press a key.
- Significantly optimise ItemList internally.
2025-04-14 13:55:48 +10:00
Leon Friedrich
34f4cf9452 More map validator fixes (#5820) 2025-04-14 13:55:02 +10:00
B_Kirill
6a4e4cf3b4 Cleanup warnings: Commands (#5825) 2025-04-14 13:54:42 +10:00
metalgearsloth
8aefa5c53e Make TestPoint use generics (#5828) 2025-04-14 01:49:07 +10:00
DrSmugleaf
2cfc981aa3 Fix popup text overflowing the sides of the screen (#5788)
* Fix popup text overflowing the sides of the screen

* Fix not escaping text
2025-04-13 12:59:23 +02:00
Leon Friedrich
4987c324d9 Fix bad debug assert (#5826) 2025-04-13 20:50:46 +10:00
DrSmugleaf
5450ddd0ba Fix SharedJointSystem.CreateDistanceJoint taking in an int for minimumDistance instead of a float (#5824) 2025-04-13 16:22:22 +10:00
Tayrtahn
378a10678c Improve location reporting for non-writeable DataFields (#5715)
* Better location reporting for readonly DataField errors

* Better location reporting for readonly DataField property errors

* Use SyntaxKind instead of string for TryGetModifierLocation
2025-04-13 16:22:05 +10:00
Leon Friedrich
2e0735b92f Fix NRE in screen-space overlays (#5823)
* Fix pre-init screen-space overlays

* Debug asserts
2025-04-13 16:21:02 +10:00
Leon Friedrich
5756d15333 Make BoundUserInterfaceMessageAttempt broadcast again (#5821) 2025-04-12 18:22:13 +10:00
Leon Friedrich
b6f74b8dea Fix logMissing in EntitySystem.Resolve() (#5822) 2025-04-12 17:40:08 +10:00
Leon Friedrich
3800c5707e Add new SerializationManager.PushComposition overload (#5785) 2025-04-12 12:58:40 +10:00
Leon Friedrich
8f49785b4e Fix RemCompDeferred not always setting lifestage (#5786)
* Fix RemCompDeferred not setting lifestage

* spaelling
2025-04-12 12:57:11 +10:00
210 changed files with 3054 additions and 991 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

@@ -71,6 +71,6 @@
</PropertyGroup>
<Exec
Condition="'$(_RobustUseExternalMSBuild)' == 'true'"
Command="&quot;$(DOTNET_HOST_PATH)&quot; msbuild /nodereuse:false $(MSBuildProjectFile) /t:CompileRobustXaml /p:_RobustForceInternalMSBuild=true /p:Configuration=$(Configuration) /p:RuntimeIdentifier=$(RuntimeIdentifier) /p:TargetFramework=$(TargetFramework) /p:BuildProjectReferences=false"/>
Command="&quot;$(DOTNET_HOST_PATH)&quot; msbuild /nodereuse:false $(MSBuildProjectFile) /t:CompileRobustXaml /p:_RobustForceInternalMSBuild=true /p:Configuration=$(Configuration) /p:RuntimeIdentifier=$(RuntimeIdentifier) /p:TargetFramework=$(TargetFramework) /p:BuildProjectReferences=false /p:IntermediateOutputPath=&quot;$(IntermediateOutputPath.TrimEnd('\'))/&quot;"/>
</Target>
</Project>

View File

@@ -1,4 +1,4 @@
# Release notes for RobustToolbox.
# Release notes for RobustToolbox.
<!--
NOTE: automatically updated sometimes by version.py.
@@ -54,6 +54,139 @@ END TEMPLATE-->
*None yet*
## 256.0.0
### Breaking changes
* `ITypeReaderWriter<TType, TNode>` has been removed due to being unused. Implement `ITypeSerializer<TType, TNode>` instead
* Moved AsNullable extension methods to the Entity struct.
### New features
* Add DevWindow tab to show all loaded textures.
* Add Vector2i / bitmask converfsion helpers.
* Allow texture preload to be skipped for some textures.
* Check audio file signatures instead of extensions.
* Add CancellationTokenRegistration to sandbox.
* Add the ability to serialize TimeSpan from text.
* Add support for rotated / mirrored tiles.
### Bugfixes
* Fix yaml hot reloading.
* Fix a linear dictionary lookup in PlacementManager.
### Other
* Make ItemList not run deselection callback on all items if they aren't selected.
* Cleanup warnings for CS0649 & CS0414.
### Internal
* Move PointLight component states to shared.
## 255.1.0
### New features
* The client localisation manager now supports hot-reloading ftl files.
* TransformSystem can now raise `GridUidChangedEvent` and `MapUidChangedEvent` when a entity's grid or map changes. This event is only raised if the `ExtraTransformEvents` metadata flag is enabled.
### Bugfixes
* Fixed a server crash due to a `NullReferenceException` in PVS system when a player's local entity is also one of their view subscriptions.
* Fix CompileRobustXamlTask for benchmarks.
* .ftl files will now hot reload.
* Fix placementmanager sometimes not clearing.
### Other
* Container events are now documented.
## 255.0.0
### Breaking changes
* `RobustIntegrationTest` now pools server/client instances by default. If a custom settings class is provided, it will still disable pooling unless explicitly enabled.
* Server/Client instances that are returned to the pool should be disconnected. This might require you to update some tests.
* Pooled instances also require you to use `RobustIntegrationTest` methods like `WaitPost()` to ensure the correct thread is used.
### Bugfixes
* Fix `EntityDeserializer` improperly setting entity lifestages when loading a post-mapinit map.
* Fix `EntityManager.PredictedDeleteEntity()` not deleting pure client-side entities.
* Fix grid fixtures using a locale dependent id. This could cause some clients to crash/freeze when connected to a server with a different locale.
### Other
* Add logic to block cycles in master MIDI renderers, which could otherwise cause client freezes.
## 254.1.0
### New features
* Add CC ND licences to the RGA validator.
* Add entity spawn prediction and entity deletion prediction. This is currently limited as you are unable to predict interactions with these entities. These are done via the new methods prefixed with "Predicted". You can also manually flag an entity as a predicted spawn with the `FlagPredicted` method which will clean it up when prediction is reset.
### Bugfixes
* Fix tile edge rendering for neighbor tiles being the same priority.
### Other
* Fix SpawnAttachedTo's system proxy method not the rotation arg like EntityManager.
## 254.0.0
### Breaking changes
* Yaml mappings/dictionaries now only support string keys instead of generic nodes
* Several MappingDataNode method arguments or return values now use strings instead of a DataNode object
* The MappingDataNode class has various helper methods that still accept a ValueDataNode, but these methods are marked as obsolete and may be removed in the future.
* yaml validators should use `MappingDataNode.GetKeyNode()` when validating mapping keys, so that errors can print node start & end information
* ValueTuple yaml serialization has changed
* Previously they would get serialized into a single mapping with one entry (i.e., `{foo : bar }`)
* Now they serialize into a sequence (i.e., `[foo, bar]`)
* The ValueTuple serializer will still try to read mappings, but due to the MappingDataNode this may fail if the previously serialized "key" can't be read as a simple string
### New features
* Add cvar to disable tile edges.
* Add GetContainingContainers method to ContainerSystem to recursively get containers upwards on an entity.
### Internal
* Make component lifecycle methods use generics.
## 253.0.0
### New features
* Add a new `SerializationManager.PushComposition()` overload that takes in a single parent instead of an array of parents.
* `BoundUserInterfaceMessageAttempt` once again gets raised as a broadcast event, in addition to being directed.
* This effectively reverts the breaking part of the changes made in v252.0.0
* Fix CreateDistanceJoint using an int instead of a float for minimum distance.
### Bugfixes
* Fix deferred component removal not setting the component's life stage to `ComponentLifeStage.Stopped` if the component has not yet been initialised.
* Fix some `EntitySystem.Resolve()` overloads not respecting the optional `logMissing` argument.
* Fix screen-space overlays not being useable without first initializing/starting entity manager & systems
* ItemList is now significantly optimized. VV's `AddComponent` window in particular should be much faster.
* Fix some more MapValidator fields.
* Fix popup text overflowing the sides of the screen.
* Improve location reporting for non-writeable datafields via analyzer.
### Other
* TestPoint now uses generics rather than IPhysShape directly.
## 252.0.0
### Breaking changes

View File

@@ -1,5 +1,7 @@
### Localization for engine console commands
cmd-hint-float = [float]
## generic command errors
cmd-invalid-arg-number-error = Invalid number of arguments.

View File

@@ -9,6 +9,7 @@ entity-spawn-window-override-menu-tooltip = Override placement
## TileSpawnWindow
tile-spawn-window-title = Place Tiles
tile-spawn-window-mirror-button-text = Mirror Tiles
## Console

View File

@@ -0,0 +1,10 @@
## "Textures" dev window tab
dev-window-tab-textures-title = Textures
dev-window-tab-textures-reload = Reload
dev-window-tab-textures-filter = Filter
dev-window-tab-textures-summary = Total (est): { $bytes }
dev-window-tab-textures-info = Width: { $width } Height: { $height }
PixelType: { $pixelType } sRGB: { $srgb }
Name: { $name }
Est. memory usage: { $bytes }

View File

@@ -87,4 +87,66 @@ public sealed class DataDefinitionAnalyzerTest
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldNoVVReadWriteRule).WithSpan(35, 17, 35, 50).WithArguments("Bad", "Foo")
);
}
[Test]
public async Task ReadOnlyFieldTest()
{
const string code = """
using System;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Shared.Serialization.Manager.Attributes
{
public class DataFieldBaseAttribute : Attribute;
public class DataFieldAttribute : DataFieldBaseAttribute;
public sealed class DataDefinitionAttribute : Attribute;
}
[DataDefinition]
public sealed partial class Foo
{
[DataField]
public readonly int Bad;
[DataField]
public int Good;
}
""";
await Verifier(code,
// /0/Test0.cs(15,12): error RA0019: Data field Bad in data definition Foo is readonly
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldWritableRule).WithSpan(15, 12, 15, 20).WithArguments("Bad", "Foo")
);
}
[Test]
public async Task ReadOnlyPropertyTest()
{
const string code = """
using System;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Shared.Serialization.Manager.Attributes
{
public class DataFieldBaseAttribute : Attribute;
public class DataFieldAttribute : DataFieldBaseAttribute;
public sealed class DataDefinitionAttribute : Attribute;
}
[DataDefinition]
public sealed partial class Foo
{
[DataField]
public int Bad { get; }
[DataField]
public int Good { get; private set; }
}
""";
await Verifier(code,
// /0/Test0.cs(15,20): error RA0020: Data field property Bad in data definition Foo does not have a setter
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldPropertyWritableRule).WithSpan(15, 20, 15, 28).WithArguments("Bad", "Foo")
);
}
}

View File

@@ -0,0 +1,86 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Testing;
using NUnit.Framework;
using VerifyCS =
Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<Robust.Analyzers.ObsoleteInheritanceAnalyzer, Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
namespace Robust.Analyzers.Tests;
/// <summary>
/// Analyzer that implements <c>[ObsoleteInheritance]</c> checking, to give obsoletion warnings for inheriting types
/// that should never have been virtual.
/// </summary>
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
[TestFixture]
public sealed class ObsoleteInheritanceAnalyzerTest
{
private static Task Verifier(string code, params DiagnosticResult[] expected)
{
var test = new CSharpAnalyzerTest<ObsoleteInheritanceAnalyzer, DefaultVerifier>()
{
TestState =
{
Sources = { code },
},
};
TestHelper.AddEmbeddedSources(
test.TestState,
"Robust.Shared.Analyzers.ObsoleteInheritanceAttribute.cs"
);
// ExpectedDiagnostics cannot be set, so we need to AddRange here...
test.TestState.ExpectedDiagnostics.AddRange(expected);
return test.RunAsync();
}
[Test]
public async Task TestBasic()
{
const string code = """
using Robust.Shared.Analyzers;
[ObsoleteInheritance]
public class Base;
public class NotAllowed : Base;
""";
await Verifier(code,
// /0/Test0.cs(6,14): warning RA0034: Type 'NotAllowed' inherits from 'Base', which has obsoleted inheriting from itself
VerifyCS.Diagnostic(ObsoleteInheritanceAnalyzer.Rule).WithSpan(6, 14, 6, 24).WithArguments("NotAllowed", "Base")
);
}
[Test]
public async Task TestMessage()
{
const string code = """
using Robust.Shared.Analyzers;
[ObsoleteInheritance("Sus")]
public class Base;
public class NotAllowed : Base;
""";
await Verifier(code,
// /0/Test0.cs(6,14): warning RA0034: Type 'NotAllowed' inherits from 'Base', which has obsoleted inheriting from itself: "Sus"
VerifyCS.Diagnostic(ObsoleteInheritanceAnalyzer.RuleWithMessage).WithSpan(6, 14, 6, 24).WithArguments("NotAllowed", "Base", "Sus")
);
}
[Test]
public async Task TestNormal()
{
const string code = """
public class Base;
public class AllowedAllowed : Base;
""";
await Verifier(code);
}
}

View File

@@ -14,6 +14,7 @@
<EmbeddedResource Include="..\Robust.Shared\Analyzers\PreferNonGenericVariantForAttribute.cs" LogicalName="Robust.Shared.Analyzers.PreferNonGenericVariantForAttribute.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\Analyzers\PreferOtherTypeAttribute.cs" LogicalName="Robust.Shared.Analyzers.PreferOtherTypeAttribute.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\Analyzers\ForbidLiteralAttribute.cs" LogicalName="Robust.Shared.Analyzers.ForbidLiteralAttribute.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\Analyzers\ObsoleteInheritanceAttribute.cs" LogicalName="Robust.Shared.Analyzers.ObsoleteInheritanceAttribute.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\IoC\DependencyAttribute.cs" LogicalName="Robust.Shared.IoC.DependencyAttribute.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\GameObjects\EventBusAttributes.cs" LogicalName="Robust.Shared.GameObjects.EventBusAttributes.cs" LinkBase="Implementations" />
</ItemGroup>

View File

@@ -41,7 +41,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
"Make sure to mark any type containing a nested data definition as partial."
);
private static readonly DiagnosticDescriptor DataFieldWritableRule = new(
public static readonly DiagnosticDescriptor DataFieldWritableRule = new(
Diagnostics.IdDataFieldWritable,
"Data field must not be readonly",
"Data field {0} in data definition {1} is readonly",
@@ -51,7 +51,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
"Make sure to remove the readonly modifier."
);
private static readonly DiagnosticDescriptor DataFieldPropertyWritableRule = new(
public static readonly DiagnosticDescriptor DataFieldPropertyWritableRule = new(
Diagnostics.IdDataFieldPropertyWritable,
"Data field property must have a setter",
"Data field property {0} in data definition {1} does not have a setter",
@@ -149,7 +149,8 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
if (IsReadOnlyDataField(type, fieldSymbol))
{
context.ReportDiagnostic(Diagnostic.Create(DataFieldWritableRule, context.Node.GetLocation(), fieldSymbol.Name, type.Name));
TryGetModifierLocation(field, SyntaxKind.ReadOnlyKeyword, out var location);
context.ReportDiagnostic(Diagnostic.Create(DataFieldWritableRule, location, fieldSymbol.Name, type.Name));
}
if (HasRedundantTag(fieldSymbol))
@@ -185,7 +186,8 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
if (IsReadOnlyDataField(type, propertySymbol))
{
context.ReportDiagnostic(Diagnostic.Create(DataFieldPropertyWritableRule, context.Node.GetLocation(), propertySymbol.Name, type.Name));
var location = property.AccessorList != null ? property.AccessorList.GetLocation() : property.GetLocation();
context.ReportDiagnostic(Diagnostic.Create(DataFieldPropertyWritableRule, location, propertySymbol.Name, type.Name));
}
if (HasRedundantTag(propertySymbol))
@@ -285,6 +287,20 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
return false;
}
private static bool TryGetModifierLocation(MemberDeclarationSyntax syntax, SyntaxKind modifierKind, out Location location)
{
foreach (var modifier in syntax.Modifiers)
{
if (modifier.IsKind(modifierKind))
{
location = modifier.GetLocation();
return true;
}
}
location = syntax.GetLocation();
return false;
}
private static bool IsReadOnlyMember(ITypeSymbol type, ISymbol member)
{
if (member is IFieldSymbol field)

View File

@@ -0,0 +1,75 @@
#nullable enable
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Robust.Roslyn.Shared;
namespace Robust.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class ObsoleteInheritanceAnalyzer : DiagnosticAnalyzer
{
private const string Attribute = "Robust.Shared.Analyzers.ObsoleteInheritanceAttribute";
public static readonly DiagnosticDescriptor Rule = new(
Diagnostics.IdObsoleteInheritance,
"Parent type has obsoleted inheritance",
"Type '{0}' inherits from '{1}', which has obsoleted inheriting from itself",
"Usage",
DiagnosticSeverity.Warning,
true);
public static readonly DiagnosticDescriptor RuleWithMessage = new(
Diagnostics.IdObsoleteInheritanceWithMessage,
"Parent type has obsoleted inheritance",
"Type '{0}' inherits from '{1}', which has obsoleted inheriting from itself: \"{2}\"",
"Usage",
DiagnosticSeverity.Warning,
true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [Rule, RuleWithMessage];
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterSymbolAction(CheckClass, SymbolKind.NamedType);
}
private static void CheckClass(SymbolAnalysisContext context)
{
if (context.Symbol is not INamedTypeSymbol typeSymbol)
return;
if (typeSymbol.IsValueType || typeSymbol.BaseType is not { } baseType)
return;
if (!AttributeHelper.HasAttribute(baseType, Attribute, out var data))
return;
var location = context.Symbol.Locations[0];
if (GetMessageFromAttributeData(data) is { } message)
{
context.ReportDiagnostic(Diagnostic.Create(
RuleWithMessage,
location,
[typeSymbol.Name, baseType.Name, message]));
}
else
{
context.ReportDiagnostic(Diagnostic.Create(
Rule,
location,
[typeSymbol.Name, baseType.Name]));
}
}
private static string? GetMessageFromAttributeData(AttributeData data)
{
if (data.ConstructorArguments is not [var message, ..])
return null;
return message.Value as string;
}
}

View File

@@ -42,8 +42,8 @@ public class RecursiveMoveBenchmark : RobustIntegrationTest
public void GlobalSetup()
{
ProgramShared.PathOffset = "../../../../";
var server = StartServer();
var client = StartClient();
var server = StartServer(new() {Pool = false});
var client = StartClient(new() {Pool = false});
Task.WhenAll(client.WaitIdleAsync(), server.WaitIdleAsync()).Wait();

View File

@@ -226,6 +226,9 @@ internal sealed class MidiRenderer : IMidiRenderer
if (value == _master)
return;
if (CheckMasterCycle(value))
throw new InvalidOperationException("Tried to set master to a child of this renderer!");
if (_master is { Disposed: false })
{
try
@@ -729,4 +732,22 @@ internal sealed class MidiRenderer : IMidiRenderer
_synth?.Dispose();
_player?.Dispose();
}
/// <summary>
/// Check that a given renderer is not already a child of this renderer, i.e. it would introduce a cycle if set as master of this renderer.
/// </summary>
private bool CheckMasterCycle(IMidiRenderer? otherRenderer)
{
// Doesn't inside drift, cringe.
while (otherRenderer != null)
{
if (otherRenderer == this)
return true;
otherRenderer = otherRenderer.Master;
}
return false;
}
}

View File

@@ -10,6 +10,7 @@ using Robust.Client.Graphics;
using Robust.Client.Graphics.Clyde;
using Robust.Client.HWId;
using Robust.Client.Input;
using Robust.Client.Localization;
using Robust.Client.Map;
using Robust.Client.Placement;
using Robust.Client.Player;
@@ -36,6 +37,7 @@ using Robust.Shared.Console;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Physics;
@@ -104,6 +106,8 @@ namespace Robust.Client
deps.Register<IGamePrototypeLoadManager, GamePrototypeLoadManager>();
deps.Register<NetworkResourceManager>();
deps.Register<IReloadManager, ReloadManager>();
deps.Register<ILocalizationManager, ClientLocalizationManager>();
deps.Register<ILocalizationManagerInternal, ClientLocalizationManager>();
switch (mode)
{

View File

@@ -1,17 +1,19 @@
#if DEBUG
using Robust.Client.Debugging;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Robust.Client.Console.Commands
{
public sealed class DebugAnchoredCommand : LocalizedCommands
public sealed class DebugAnchoredCommand : LocalizedEntityCommands
{
[Dependency] private readonly DebugAnchoringSystem _system = default!;
public override string Command => "showanchored";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
EntitySystem.Get<DebugAnchoringSystem>().Enabled ^= true;
_system.Enabled ^= true;
}
}
}

View File

@@ -1,17 +1,19 @@
#if DEBUG
using Robust.Client.GameObjects;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Robust.Client.Console.Commands
{
internal sealed class LightDebugCommand : LocalizedCommands
internal sealed class LightDebugCommand : LocalizedEntityCommands
{
[Dependency] private readonly DebugLightTreeSystem _system = default!;
public override string Command => "lightbb";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
EntitySystem.Get<DebugLightTreeSystem>().Enabled ^= true;
_system.Enabled ^= true;
}
}
}

View File

@@ -1,19 +1,18 @@
using Robust.Client.GameObjects;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Robust.Client.Console.Commands
{
public sealed class ShowPlayerVelocityCommand : LocalizedCommands
public sealed class ShowPlayerVelocityCommand : LocalizedEntityCommands
{
[Dependency] private readonly IEntitySystemManager _entitySystems = default!;
[Dependency] private readonly ShowPlayerVelocityDebugSystem _system = default!;
public override string Command => "showplayervelocity";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
_entitySystems.GetEntitySystem<ShowPlayerVelocityDebugSystem>().Enabled ^= true;
_system.Enabled ^= true;
}
}
}

View File

@@ -31,6 +31,7 @@ using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.Exceptions;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Network;
@@ -94,6 +95,7 @@ namespace Robust.Client
[Dependency] private readonly IReplayRecordingManagerInternal _replayRecording = default!;
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
[Dependency] private readonly IReloadManager _reload = default!;
[Dependency] private readonly ILocalizationManager _loc = default!;
private IWebViewManagerHook? _webViewHook;
@@ -180,6 +182,7 @@ namespace Robust.Client
_serializer.Initialize();
_inputManager.Initialize();
_console.Initialize();
_loc.Initialize();
// Make sure this is done before we try to load prototypes,
// avoid any possibility of race conditions causing the check to not finish

View File

@@ -0,0 +1,123 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Robust.Client.GameObjects;
public sealed partial class ClientEntityManager
{
public override EntityUid PredictedSpawnAttachedTo(string? protoName, EntityCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default)
{
var ent = SpawnAttachedTo(protoName, coordinates, overrides, rotation);
FlagPredicted(ent);
return ent;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override EntityUid PredictedSpawn(string? protoName = null, ComponentRegistry? overrides = null, bool doMapInit = true)
{
var ent = Spawn(protoName, overrides, doMapInit);
FlagPredicted(ent);
return ent;
}
public override EntityUid PredictedSpawn(string? protoName, MapCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default!)
{
var ent = Spawn(protoName, coordinates, overrides, rotation);
FlagPredicted(ent);
return ent;
}
public override EntityUid PredictedSpawnAtPosition(string? protoName, EntityCoordinates coordinates, ComponentRegistry? overrides = null)
{
var ent = SpawnAtPosition(protoName, coordinates, overrides);
FlagPredicted(ent);
return ent;
}
public override bool PredictedTrySpawnNextTo(
string? protoName,
EntityUid target,
[NotNullWhen(true)] out EntityUid? uid,
TransformComponent? xform = null,
ComponentRegistry? overrides = null)
{
if (!TrySpawnNextTo(protoName, target, out uid, xform, overrides))
return false;
FlagPredicted(uid.Value);
return true;
}
public override bool PredictedTrySpawnInContainer(
string? protoName,
EntityUid containerUid,
string containerId,
[NotNullWhen(true)] out EntityUid? uid,
ContainerManagerComponent? containerComp = null,
ComponentRegistry? overrides = null)
{
if (!TrySpawnInContainer(protoName, containerUid, containerId, out uid, containerComp, overrides))
return false;
FlagPredicted(uid.Value);
return true;
}
public override EntityUid PredictedSpawnNextToOrDrop(string? protoName, EntityUid target, TransformComponent? xform = null, ComponentRegistry? overrides = null)
{
var ent = SpawnNextToOrDrop(protoName, target, xform, overrides);
FlagPredicted(ent);
return ent;
}
public override EntityUid PredictedSpawnInContainerOrDrop(
string? protoName,
EntityUid containerUid,
string containerId,
TransformComponent? xform = null,
ContainerManagerComponent? containerComp = null,
ComponentRegistry? overrides = null)
{
var ent = SpawnInContainerOrDrop(protoName, containerUid, containerId, xform, containerComp, overrides);
FlagPredicted(ent);
return ent;
}
public override EntityUid PredictedSpawnInContainerOrDrop(
string? protoName,
EntityUid containerUid,
string containerId,
out bool inserted,
TransformComponent? xform = null,
ContainerManagerComponent? containerComp = null,
ComponentRegistry? overrides = null)
{
var ent = SpawnInContainerOrDrop(protoName,
containerUid,
containerId,
out inserted,
xform,
containerComp,
overrides);
FlagPredicted(ent);
return ent;
}
public override void FlagPredicted(Entity<MetaDataComponent?> ent)
{
if (!MetaQuery.Resolve(ent.Owner, ref ent.Comp))
return;
DebugTools.Assert(IsClientSide(ent.Owner, ent.Comp));
EnsureComponent<PredictedSpawnComponent>(ent.Owner);
// TODO: Need to map call site or something, needs to be consistent between client and server.
}
}

View File

@@ -291,5 +291,54 @@ namespace Robust.Client.GameObjects
}
}
#endregion
/// <inheritdoc />
public override void PredictedDeleteEntity(Entity<MetaDataComponent?, TransformComponent?> ent)
{
if (!MetaQuery.Resolve(ent.Owner, ref ent.Comp1)
|| ent.Comp1.EntityDeleted
|| !TransformQuery.Resolve(ent.Owner, ref ent.Comp2))
{
return;
}
// So there's 3 scenarios:
// 1. Networked entity we just move to nullspace and rely on state handling.
// 2. Clientside predicted entity we delete and rely on state handling.
// 3. Clientside only entity that actually needs deleting here.
if (ent.Comp1.NetEntity.IsClientSide())
{
DeleteEntity(ent, ent.Comp1, ent.Comp2);
}
else
{
_xforms.DetachEntity(ent, ent.Comp2);
}
}
/// <inheritdoc />
public override void PredictedQueueDeleteEntity(Entity<MetaDataComponent?, TransformComponent?> ent)
{
if (IsQueuedForDeletion(ent.Owner)
|| !MetaQuery.Resolve(ent.Owner, ref ent.Comp1)
|| ent.Comp1.EntityDeleted
|| !TransformQuery.Resolve(ent.Owner, ref ent.Comp2))
{
return;
}
if (ent.Comp1.NetEntity.IsClientSide())
{
// client-side QueueDeleteEntity re-fetches MetadataComp and checks IsClientSide().
// base call to skip that.
// TODO create override that takes in metadata comp
base.QueueDeleteEntity(ent);
}
else
{
_xforms.DetachEntity(ent.Owner, ent.Comp2);
}
}
}
}

View File

@@ -10,19 +10,21 @@ using Robust.Shared.Utility;
namespace Robust.Client.GameObjects
{
public sealed class ShowSpriteBBCommand : LocalizedCommands
public sealed class ShowSpriteBBCommand : LocalizedEntityCommands
{
[Dependency] private readonly SpriteBoundsSystem _system = default!;
public override string Command => "showspritebb";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
EntitySystem.Get<SpriteBoundsSystem>().Enabled ^= true;
_system.Enabled ^= true;
}
}
public sealed class SpriteBoundsSystem : EntitySystem
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly SharedTransformSystem _xformSystem = default!;
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly SpriteTreeSystem _spriteTree = default!;
@@ -40,7 +42,7 @@ namespace Robust.Client.GameObjects
if (_enabled)
{
DebugTools.AssertNull(_overlay);
_overlay = new SpriteBoundsOverlay(_spriteTree, _entityManager);
_overlay = new SpriteBoundsOverlay(_spriteTree, _xformSystem);
_overlayManager.AddOverlay(_overlay);
}
else
@@ -59,13 +61,13 @@ namespace Robust.Client.GameObjects
{
public override OverlaySpace Space => OverlaySpace.WorldSpace;
private readonly IEntityManager _entityManager;
private readonly SharedTransformSystem _xformSystem;
private SpriteTreeSystem _renderTree;
public SpriteBoundsOverlay(SpriteTreeSystem renderTree, IEntityManager entityManager)
public SpriteBoundsOverlay(SpriteTreeSystem renderTree, SharedTransformSystem xformSystem)
{
_renderTree = renderTree;
_entityManager = entityManager;
_xformSystem = xformSystem;
}
protected internal override void Draw(in OverlayDrawArgs args)
@@ -76,7 +78,7 @@ namespace Robust.Client.GameObjects
foreach (var (sprite, xform) in _renderTree.QueryAabb(currentMap, viewport))
{
var (worldPos, worldRot) = xform.GetWorldPositionRotation();
var (worldPos, worldRot) = _xformSystem.GetWorldPositionRotation(xform);
var bounds = sprite.CalculateRotatedBoundingBox(worldPos, worldRot, args.Viewport.Eye?.Rotation ?? default);
// Get scaled down bounds used to indicate the "south" of a sprite.

View File

@@ -14,7 +14,9 @@ namespace Robust.Client.GameObjects
private EntityQuery<AnimationPlayerComponent> _playerQuery;
private EntityQuery<MetaDataComponent> _metaQuery;
#pragma warning disable CS0414
[Dependency] private readonly IComponentFactory _compFact = default!;
#pragma warning restore CS0414
public override void Initialize()
{

View File

@@ -303,7 +303,7 @@ namespace Robust.Client.GameObjects
while (parent.IsValid() && (!spriteOccluded || !lightOccluded))
{
var parentXform = TransformQuery.GetComponent(parent);
if (TryComp<ContainerManagerComponent>(parent, out var manager) && manager.TryGetContainer(child, out var container))
if (TryComp<ContainerManagerComponent>(parent, out var manager) && TryGetContainingContainer(parent, child, out var container, manager))
{
spriteOccluded = spriteOccluded || !container.ShowContents;
lightOccluded = lightOccluded || container.OccludesLight;
@@ -344,7 +344,7 @@ namespace Robust.Client.GameObjects
var childLightOccluded = lightOccluded;
// We already know either sprite or light is not occluding so need to check container.
if (manager.TryGetContainer(child, out var container))
if (TryGetContainingContainer(entity, child, out var container, manager))
{
childSpriteOccluded = childSpriteOccluded || !container.ShowContents;
childLightOccluded = childLightOccluded || container.OccludesLight;

View File

@@ -2,24 +2,24 @@ using System.Collections.Generic;
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Shared.Console;
using Robust.Shared.Containers;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Utility;
using Color = Robust.Shared.Maths.Color;
namespace Robust.Client.GameObjects;
public sealed class DebugEntityLookupCommand : LocalizedCommands
public sealed class DebugEntityLookupCommand : LocalizedEntityCommands
{
[Dependency] private readonly DebugEntityLookupSystem _system = default!;
public override string Command => "togglelookup";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
EntitySystem.Get<DebugEntityLookupSystem>().Enabled ^= true;
_system.Enabled ^= true;
}
}

View File

@@ -26,10 +26,10 @@ namespace Robust.Client.GameObjects
if (_enabled)
{
_lightOverlay = new DebugLightOverlay(
EntitySystem.Get<EntityLookupSystem>(),
EntityManager.System<EntityLookupSystem>(),
IoCManager.Resolve<IEyeManager>(),
IoCManager.Resolve<IMapManager>(),
Get<LightTreeSystem>());
EntityManager.System<LightTreeSystem>());
overlayManager.AddOverlay(_lightOverlay);
}

View File

@@ -62,7 +62,7 @@ public sealed class EyeSystem : SharedEyeSystem
eyeComponent.Target = null;
}
eyeComponent.Eye.Position = xform.MapPosition;
eyeComponent.Eye.Position = TransformSystem.GetMapCoordinates(xform);
}
}
}

View File

@@ -16,6 +16,7 @@ namespace Robust.Client.GameObjects
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<PointLightComponent, ComponentGetState>(OnLightGetState);
SubscribeLocalEvent<PointLightComponent, ComponentInit>(HandleInit);
SubscribeLocalEvent<PointLightComponent, ComponentHandleState>(OnLightHandleState);
}

View File

@@ -13,6 +13,7 @@ using Robust.Client.Physics;
using Robust.Client.Player;
using Robust.Client.Timing;
using Robust.Shared;
using Robust.Shared.Collections;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
using Robust.Shared.Containers;
@@ -563,6 +564,21 @@ namespace Robust.Client.GameStates
var metaQuery = _entities.GetEntityQuery<MetaDataComponent>();
RemQueue<IComponent> toRemove = new();
// Handle predicted entity spawns.
var predicted = new ValueList<EntityUid>();
var predictedQuery = _entities.AllEntityQueryEnumerator<PredictedSpawnComponent>();
while (predictedQuery.MoveNext(out var uid, out var _))
{
predicted.Add(uid);
}
// Entity will get re-created as part of the tick.
foreach (var ent in predicted)
{
_entities.DeleteEntity(ent);
}
foreach (var entity in system.DirtyEntities)
{
DebugTools.Assert(toRemove.Count == 0);

View File

@@ -123,7 +123,8 @@ namespace Robust.Client.Graphics
/// <inheritdoc />
public ScreenCoordinates CoordinatesToScreen(EntityCoordinates point)
{
return MapToScreen(point.ToMap(_entityManager, _entityManager.System<SharedTransformSystem>()));
var transformSystem = _entityManager.System<SharedTransformSystem>();
return MapToScreen(transformSystem.ToMapCoordinates(point));
}
public ScreenCoordinates MapToScreen(MapCoordinates point)

View File

@@ -30,6 +30,23 @@ namespace Robust.Client.Graphics.Clyde
private int _indicesPerChunk(MapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * GetQuadBatchIndexCount();
private List<Entity<MapGridComponent>> _grids = new();
private bool _drawTileEdges;
private void RenderTileEdgesChanges(bool value)
{
_drawTileEdges = value;
if (!value)
return;
// Dirty all Edges
foreach (var gridData in _mapChunkData.Values)
{
foreach (var chunk in gridData.Values)
{
chunk.EdgeDirty = true;
}
}
}
private void _drawGrids(Viewport viewport, Box2 worldAABB, Box2Rotated worldBounds, IEye eye)
{
@@ -81,6 +98,9 @@ namespace Robust.Client.Graphics.Clyde
_updateChunkMesh(mapGrid, chunk, datum);
if (!_drawTileEdges)
continue;
// Dirty edge tiles for next step.
datum.EdgeDirty = true;
@@ -99,17 +119,16 @@ namespace Robust.Client.Graphics.Clyde
}
}
enumerator = mapSystem.GetMapChunks(mapGrid.Owner, mapGrid.Comp, worldBounds);
// Handle edge sprites.
while (enumerator.MoveNext(out var chunk))
if (_drawTileEdges)
{
var datum = data[chunk.Indices];
if (!datum.EdgeDirty)
continue;
_updateChunkEdges(mapGrid, chunk, datum);
enumerator = mapSystem.GetMapChunks(mapGrid.Owner, mapGrid.Comp, worldBounds);
while (enumerator.MoveNext(out var chunk))
{
var datum = data[chunk.Indices];
if (datum.EdgeDirty)
_updateChunkEdges(mapGrid, chunk, datum);
}
}
enumerator = mapSystem.GetMapChunks(mapGrid.Owner, mapGrid.Comp, worldBounds);
@@ -129,7 +148,7 @@ namespace Robust.Client.Graphics.Clyde
CheckGlError();
}
if (datum.EdgeCount > 0)
if (_drawTileEdges && datum.EdgeCount > 0)
{
BindVertexArray(datum.EdgeVAO);
CheckGlError();
@@ -138,7 +157,6 @@ namespace Robust.Client.Graphics.Clyde
GL.DrawElements(GetQuadGLPrimitiveType(), datum.EdgeCount * GetQuadBatchIndexCount(), DrawElementsType.UnsignedShort, 0);
CheckGlError();
}
}
requiresFlush = false;
@@ -236,7 +254,11 @@ namespace Robust.Client.Graphics.Clyde
region = regionMaybe[tile.Variant];
}
WriteTileToBuffers(i, gridX, gridY, vertexBuffer, indexBuffer, region);
var rotationMirroring = _tileDefinitionManager[tile.TypeId].AllowRotationMirror
? tile.RotationMirroring
: 0;
WriteTileToBuffers(i, gridX, gridY, vertexBuffer, indexBuffer, region, rotationMirroring);
i += 1;
}
}
@@ -274,7 +296,8 @@ namespace Robust.Client.Graphics.Clyde
var gridX = x + chunkOriginScaled.X;
var gridY = y + chunkOriginScaled.Y;
var tile = chunk.GetTile(x, y);
var tileDef = _tileDefinitionManager[tile.TypeId];
if (!_tileDefinitionManager.TryGetDefinition(tile.TypeId, out var tileDef))
continue;
// Edge render
for (var nx = -1; nx <= 1; nx++)
@@ -288,14 +311,15 @@ namespace Robust.Client.Graphics.Clyde
if (!maps.TryGetTile(grid.Comp, neighborIndices, out var neighborTile))
continue;
var neighborDef = _tileDefinitionManager[neighborTile.TypeId];
if (!_tileDefinitionManager.TryGetDefinition(neighborTile.TypeId, out var neighborDef))
continue;
// If it's the same tile then no edge to be drawn.
if (tile.TypeId == neighborTile.TypeId || neighborDef.EdgeSprites.Count == 0)
continue;
// If neighbor is a lower priority then us then don't draw on our tile.
if (neighborDef.EdgeSpritePriority < tileDef.EdgeSpritePriority)
// If neighbor is a lower or same priority then us then don't draw on our tile.
if (neighborDef.EdgeSpritePriority <= tileDef.EdgeSpritePriority)
continue;
var direction = new Vector2i(nx, ny).AsDirection().GetOpposite();
@@ -305,7 +329,7 @@ namespace Robust.Client.Graphics.Clyde
continue;
var region = regionMaybe[0];
WriteTileToBuffers(i, gridX, gridY, vertexBuffer, indexBuffer, region);
WriteTileToBuffers(i, gridX, gridY, vertexBuffer, indexBuffer, region, 0);
i += 1;
}
}
@@ -425,13 +449,57 @@ namespace Robust.Client.Graphics.Clyde
int gridY,
Span<Vertex2D> vertexBuffer,
Span<ushort> indexBuffer,
Box2 region)
Box2 region,
int rotationMirroring)
{
var rLeftBottom = (region.Left, region.Bottom);
var rRightBottom = (region.Right, region.Bottom);
var rRightTop = (region.Right, region.Top);
var rLeftTop = (region.Left, region.Top);
// The vertices must be changed if there's any rotation or mirroring to the tile
if (rotationMirroring != 0)
{
// Rotate the tile
for (int r = 0; r < rotationMirroring % 4; r++)
{
(rLeftBottom, rRightBottom, rRightTop, rLeftTop) =
(rLeftTop, rLeftBottom, rRightBottom, rRightTop);
}
// Mirror on the x-axis
if (rotationMirroring >= 4)
{
if (rotationMirroring % 2 == 0)
{
rLeftBottom = (rLeftBottom.Item1.Equals(region.Left) ? region.Right : region.Left,
rLeftBottom.Item2);
rRightBottom = (rRightBottom.Item1.Equals(region.Left) ? region.Right : region.Left,
rRightBottom.Item2);
rRightTop = (rRightTop.Item1.Equals(region.Left) ? region.Right : region.Left,
rRightTop.Item2);
rLeftTop = (rLeftTop.Item1.Equals(region.Left) ? region.Right : region.Left,
rLeftTop.Item2);
}
else
{
rLeftBottom = (rLeftBottom.Item1,
rLeftBottom.Item2.Equals(region.Bottom) ? region.Top : region.Bottom);
rRightBottom = (rRightBottom.Item1,
rRightBottom.Item2.Equals(region.Bottom) ? region.Top : region.Bottom);
rRightTop = (rRightTop.Item1,
rRightTop.Item2.Equals(region.Bottom) ? region.Top : region.Bottom);
rLeftTop = (rLeftTop.Item1,
rLeftTop.Item2.Equals(region.Bottom) ? region.Top : region.Bottom);
}
}
}
var vIdx = i * 4;
vertexBuffer[vIdx + 0] = new Vertex2D(gridX, gridY, region.Left, region.Bottom, Color.White);
vertexBuffer[vIdx + 1] = new Vertex2D(gridX + 1, gridY, region.Right, region.Bottom, Color.White);
vertexBuffer[vIdx + 2] = new Vertex2D(gridX + 1, gridY + 1, region.Right, region.Top, Color.White);
vertexBuffer[vIdx + 3] = new Vertex2D(gridX, gridY + 1, region.Left, region.Top, Color.White);
vertexBuffer[vIdx + 0] = new Vertex2D(gridX, gridY, rLeftBottom.Left, rLeftBottom.Bottom, Color.White);
vertexBuffer[vIdx + 1] = new Vertex2D(gridX + 1, gridY, rRightBottom.Right, rRightBottom.Bottom, Color.White);
vertexBuffer[vIdx + 2] = new Vertex2D(gridX + 1, gridY + 1, rRightTop.Right, rRightTop.Top, Color.White);
vertexBuffer[vIdx + 3] = new Vertex2D(gridX, gridY + 1, rLeftTop.Left, rLeftTop.Top, Color.White);
var nIdx = i * GetQuadBatchIndexCount();
var tIdx = (ushort)(i * 4);
QuadBatchIndexWrite(indexBuffer, ref nIdx, tIdx);

View File

@@ -123,9 +123,13 @@ namespace Robust.Client.Graphics.Clyde
private void RenderSingleWorldOverlay(Overlay overlay, Viewport vp, OverlaySpace space, in Box2 worldBox, in Box2Rotated worldBounds)
{
// Check that entity manager has started.
// This is required for us to be able to use MapSystem.
DebugTools.Assert(_entityManager.Started, "Entity manager should be started/initialized before rendering world-space overlays");
DebugTools.Assert(space != OverlaySpace.ScreenSpaceBelowWorld && space != OverlaySpace.ScreenSpace);
var mapId = vp.Eye!.Position.MapId;
var mapId = vp.Eye?.Position.MapId ?? MapId.Nullspace;
var args = new OverlayDrawArgs(space, null, vp, _renderHandle, new UIBox2i((0, 0), vp.Size), _mapSystem.GetMapOrInvalid(mapId), mapId, worldBox, worldBounds);
if (!overlay.BeforeDraw(args))
@@ -152,6 +156,7 @@ namespace Robust.Client.Graphics.Clyde
private void RenderOverlays(Viewport vp, OverlaySpace space, in Box2 worldBox, in Box2Rotated worldBounds)
{
DebugTools.Assert(space != OverlaySpace.ScreenSpaceBelowWorld && space != OverlaySpace.ScreenSpace);
using (DebugGroup($"Overlays: {space}"))
{
foreach (var overlay in GetOverlaysForSpace(space))
@@ -176,9 +181,18 @@ namespace Robust.Client.Graphics.Clyde
var worldBounds = CalcWorldBounds(vp);
var worldAABB = worldBounds.CalcBoundingBox();
var mapId = vp.Eye!.Position.MapId;
var mapId = vp.Eye?.Position.MapId ?? MapId.Nullspace;
var mapUid = EntityUid.Invalid;
var args = new OverlayDrawArgs(space, vpControl, vp, handle, bounds, _mapSystem.GetMapOrInvalid(mapId), mapId, worldAABB, worldBounds);
// Screen space overlays may be getting used before entity manager & entity systems have been initialized.
// This might mean that _mapSystem is currently null.
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
if (_entityManager.Started && _mapSystem != null)
mapUid = _mapSystem.GetMapOrInvalid(mapId);
DebugTools.Assert(_mapSystem != null || !_entityManager.Started);
var args = new OverlayDrawArgs(space, vpControl, vp, handle, bounds, mapUid, mapId, worldAABB, worldBounds);
foreach (var overlay in list)
{

View File

@@ -305,8 +305,8 @@ namespace Robust.Client.Graphics.Clyde
IsSrgb = srgb,
Name = name,
MemoryPressure = memoryPressure,
TexturePixelType = pixType
// TextureInstance = new WeakReference<ClydeTexture>(instance)
TexturePixelType = pixType,
TextureInstance = new WeakReference<ClydeTexture>(instance)
};
_loadedTextures.Add(id, loaded);
@@ -466,15 +466,15 @@ namespace Robust.Client.Graphics.Clyde
{
var white = new Image<Rgba32>(1, 1);
white[0, 0] = new Rgba32(255, 255, 255, 255);
_stockTextureWhite = (ClydeTexture) Texture.LoadFromImage(white);
_stockTextureWhite = (ClydeTexture) Texture.LoadFromImage(white, name: "StockTextureWhite");
var black = new Image<Rgba32>(1, 1);
black[0, 0] = new Rgba32(0, 0, 0, 255);
_stockTextureBlack = (ClydeTexture) Texture.LoadFromImage(black);
_stockTextureBlack = (ClydeTexture) Texture.LoadFromImage(black, name: "StockTextureBlack");
var blank = new Image<Rgba32>(1, 1);
blank[0, 0] = new Rgba32(0, 0, 0, 0);
_stockTextureTransparent = (ClydeTexture) Texture.LoadFromImage(blank);
_stockTextureTransparent = (ClydeTexture) Texture.LoadFromImage(blank, name: "StockTextureTransparent");
}
/// <summary>
@@ -571,7 +571,7 @@ namespace Robust.Client.Graphics.Clyde
}
}
private sealed class LoadedTexture
internal sealed class LoadedTexture
{
public GLHandle OpenGLObject;
public int Width;
@@ -582,10 +582,10 @@ namespace Robust.Client.Graphics.Clyde
public TexturePixelType TexturePixelType;
public Vector2i Size => (Width, Height);
// public WeakReference<ClydeTexture> TextureInstance;
public required WeakReference<ClydeTexture> TextureInstance;
}
private enum TexturePixelType : byte
internal enum TexturePixelType : byte
{
RenderTarget = 0,
Rgba32,
@@ -686,5 +686,16 @@ namespace Robust.Client.Graphics.Clyde
_ => throw new ArgumentException(nameof(stockTexture))
};
}
public IEnumerable<(ClydeTexture, LoadedTexture)> GetLoadedTextures()
{
foreach (var loaded in _loadedTextures.Values)
{
if (!loaded.TextureInstance.TryGetTarget(out var textureInstance))
continue;
yield return (textureInstance, loaded);
}
}
}
}

View File

@@ -121,6 +121,7 @@ namespace Robust.Client.Graphics.Clyde
_cfg.OnValueChanged(CVars.LightSoftShadows, SoftShadowsChanged, true);
_cfg.OnValueChanged(CVars.MaxLightCount, MaxLightsChanged, true);
_cfg.OnValueChanged(CVars.MaxOccluderCount, MaxOccludersChanged, true);
_cfg.OnValueChanged(CVars.RenderTileEdges, RenderTileEdgesChanges, true);
// I can't be bothered to tear down and set these threads up in a cvar change handler.
// Windows and Linux can be trusted to not explode with threaded windowing,

View File

@@ -72,6 +72,11 @@ namespace Robust.Client.Graphics.Clyde
return new DummyTexture((1, 1));
}
public IEnumerable<(Clyde.ClydeTexture, Clyde.LoadedTexture)> GetLoadedTextures()
{
return [];
}
public ClydeDebugLayers DebugLayers { get; set; }
public string GetKeyName(Keyboard.Key key) => string.Empty;

View File

@@ -11,7 +11,7 @@ namespace Robust.Client.Graphics.Clyde
/// Basically just a handle around the integer object handles returned by OpenGL.
/// </summary>
[PublicAPI]
private struct GLHandle : IEquatable<GLHandle>
internal struct GLHandle : IEquatable<GLHandle>
{
public readonly uint Handle;

View File

@@ -87,11 +87,13 @@ namespace Robust.Client.Graphics.Clyde
if (cmd.Cursor != default)
ptr = _winThreadCursors[cmd.Cursor].Ptr;
#if DEBUG
if (_win32Experience)
{
// Based on a true story.
Thread.Sleep(15);
}
#endif
GLFW.SetCursor(window, ptr);
}

View File

@@ -23,7 +23,9 @@ namespace Robust.Client.Graphics.Clyde
private readonly ISawmill _sawmillGlfw;
private bool _glfwInitialized;
#if DEBUG
private bool _win32Experience;
#endif
public GlfwWindowingImpl(Clyde clyde, IDependencyCollection deps)
{

View File

@@ -54,6 +54,7 @@ namespace Robust.Client.Graphics
IClydeDebugStats DebugStats { get; }
Texture GetStockTexture(ClydeStockTexture stockTexture);
IEnumerable<(Clyde.Clyde.ClydeTexture, Clyde.Clyde.LoadedTexture)> GetLoadedTextures();
ClydeDebugLayers DebugLayers { get; set; }

View File

@@ -48,6 +48,7 @@ namespace Robust.Client.Input
[Dependency] private readonly IUserInterfaceManagerInternal _uiMgr = default!;
[Dependency] private readonly IConsoleHost _console = default!;
[Dependency] private readonly ISerializationManager _serialization = default!;
private ISawmill _logger = default!;
private bool _currentlyFindingViewport;
@@ -114,6 +115,8 @@ namespace Robust.Client.Input
/// <inheritdoc />
public void Initialize()
{
_logger = Logger.GetSawmill("input");
NetworkBindMap = new BoundKeyMap(_reflectionManager);
NetworkBindMap.PopulateKeyFunctionsMap();
@@ -130,7 +133,7 @@ namespace Robust.Client.Input
}
catch (Exception e)
{
Logger.ErrorS("input", "Failed to load user keybindings: " + e);
_logger.Error("Failed to load user keybindings: " + e);
}
}
@@ -531,8 +534,7 @@ namespace Robust.Client.Input
if (reg.Type != KeyBindingType.Command && !NetworkBindMap.FunctionExists(reg.Function.FunctionName))
{
Logger.DebugS("input", "Key function in {0} does not exist: '{1}'.", file,
reg.Function);
_logger.Debug("Key function in {0} does not exist: '{1}'.", file, reg.Function);
invalid = true;
}

View File

@@ -0,0 +1,33 @@
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Utility;
namespace Robust.Client.Localization;
internal sealed class ClientLocalizationManager : LocalizationManager, ILocalizationManagerInternal
{
[Dependency] private readonly IReloadManager _reload = default!;
void ILocalizationManager.Initialize() => Initialize();
/// <inheritdoc/>
public override void Initialize()
{
base.Initialize();
_reload.Register(LocaleDirPath, "*.ftl");
_reload.OnChanged += OnReload;
}
/// <summary>
/// Handles Fluent hot reloading via LocalizationManager.ReloadLocalizations()
/// </summary>
private void OnReload(ResPath args)
{
if (args.Extension != "ftl")
return;
ReloadLocalizations();
}
}

View File

@@ -24,10 +24,20 @@ namespace Robust.Client.Placement
Direction Direction { get; set; }
/// <summary>
/// Gets called when Direction changed (presently for EntitySpawnWindow UI)
/// Whether a tile placement should be mirrored or not.
/// </summary>
bool Mirrored { get; set; }
/// <summary>
/// Gets called when Direction changed (presently for EntitySpawnWindow/TileSpawnWindow UI)
/// </summary>
event EventHandler DirectionChanged;
/// <summary>
/// Gets called when Mirrored changed (presently for TileSpawnWindow UI)
/// </summary>
event EventHandler MirroredChanged;
/// <summary>
/// Gets called when the PlacementManager changed its build/erase mode or when the hijacks changed
/// </summary>

View File

@@ -30,11 +30,13 @@ namespace Robust.Client.Placement.Modes
return;
}
var mapId = MouseCoords.GetMapId(pManager.EntityManager);
var transformSys = pManager.EntityManager.System<SharedTransformSystem>();
var mapId = transformSys.GetMapId(MouseCoords);
var snapToEntities = EntitySystem.Get<EntityLookupSystem>().GetEntitiesInRange(MouseCoords, SnapToRange)
var snapToEntities = pManager.EntityManager.System<EntityLookupSystem>()
.GetEntitiesInRange(MouseCoords, SnapToRange)
.Where(entity => pManager.EntityManager.GetComponent<MetaDataComponent>(entity).EntityPrototype == pManager.CurrentPrototype && pManager.EntityManager.GetComponent<TransformComponent>(entity).MapID == mapId)
.OrderBy(entity => (pManager.EntityManager.GetComponent<TransformComponent>(entity).WorldPosition - MouseCoords.ToMapPos(pManager.EntityManager, pManager.EntityManager.System<SharedTransformSystem>())).LengthSquared())
.OrderBy(entity => (transformSys.GetWorldPosition(entity) - transformSys.ToMapCoordinates(MouseCoords).Position).LengthSquared())
.ToList();
if (snapToEntities.Count == 0)
@@ -51,10 +53,11 @@ namespace Robust.Client.Placement.Modes
var closestBounds = component.BaseRSI.Size;
var closestPos = transformSys.GetWorldPosition(closestTransform);
var closestRect =
Box2.FromDimensions(
closestTransform.WorldPosition.X - closestBounds.X / 2f,
closestTransform.WorldPosition.Y - closestBounds.Y / 2f,
closestPos.X - closestBounds.X / 2f,
closestPos.Y - closestBounds.Y / 2f,
closestBounds.X, closestBounds.Y);
var sides = new[]

View File

@@ -21,7 +21,8 @@ namespace Robust.Client.Placement.Modes
{
MouseCoords = ScreenToCursorGrid(mouseScreen);
var gridIdOpt = MouseCoords.GetGridUid(pManager.EntityManager);
var transformSys = pManager.EntityManager.System<SharedTransformSystem>();
var gridIdOpt = transformSys.GetGrid(MouseCoords);
SnapSize = 1f;
if (gridIdOpt is EntityUid gridId && gridId.IsValid())
{

View File

@@ -16,8 +16,10 @@ namespace Robust.Client.Placement.Modes
{
MouseCoords = ScreenToCursorGrid(mouseScreen);
var transformSys = pManager.EntityManager.System<SharedTransformSystem>();
var tileSize = 1f;
var gridIdOpt = MouseCoords.GetGridUid(pManager.EntityManager);
var gridIdOpt = transformSys.GetGrid(MouseCoords);
if (gridIdOpt is EntityUid gridId && gridId.IsValid())
{

View File

@@ -16,9 +16,11 @@ namespace Robust.Client.Placement.Modes
{
MouseCoords = ScreenToCursorGrid(mouseScreen);
var transformSys = pManager.EntityManager.System<SharedTransformSystem>();
var tileSize = 1f;
var gridIdOpt = MouseCoords.GetGridUid(pManager.EntityManager);
var gridIdOpt = transformSys.GetGrid(MouseCoords);
if (gridIdOpt is EntityUid gridId && gridId.IsValid())
{
var mapGrid = pManager.EntityManager.GetComponent<MapGridComponent>(gridId);

View File

@@ -174,6 +174,18 @@ namespace Robust.Client.Placement
private Direction _direction = Direction.South;
private bool _mirrored;
public bool Mirrored
{
get => _mirrored;
set
{
_mirrored = value;
MirroredChanged?.Invoke(this, EventArgs.Empty);
}
}
/// <inheritdoc />
public Direction Direction
{
@@ -188,6 +200,9 @@ namespace Robust.Client.Placement
/// <inheritdoc />
public event EventHandler? DirectionChanged;
/// <inheritdoc />
public event EventHandler? MirroredChanged;
private PlacementOverlay _drawOverlay = default!;
private bool _isActive;
@@ -356,6 +371,12 @@ namespace Robust.Client.Placement
public event EventHandler? PlacementChanged;
public void Clear()
{
ClearWithoutDeactivation();
IsActive = false;
}
private void ClearWithoutDeactivation()
{
PlacementChanged?.Invoke(this, EventArgs.Empty);
Hijack = null;
@@ -365,7 +386,6 @@ namespace Robust.Client.Placement
CurrentMode = null;
DeactivateSpecialPlacement();
_placenextframe = false;
IsActive = false;
Eraser = false;
EraserRect = null;
PlacementOffset = Vector2i.Zero;
@@ -480,18 +500,17 @@ namespace Robust.Client.Placement
public void BeginHijackedPlacing(PlacementInformation info, PlacementHijack? hijack = null)
{
Clear();
ClearWithoutDeactivation();
CurrentPermission = info;
if (!_modeDictionary.TryFirstOrNull(pair => pair.Key.Equals(CurrentPermission.PlacementOption), out KeyValuePair<string, Type>? placeMode))
if (info.PlacementOption is not { } option || !_modeDictionary.TryGetValue(option, out var placeMode))
{
_sawmill.Log(LogLevel.Warning, $"Invalid placement mode `{CurrentPermission.PlacementOption}`");
_sawmill.Log(LogLevel.Warning, $"Invalid placement mode `{info.PlacementOption}`");
Clear();
return;
}
CurrentMode = (PlacementMode) Activator.CreateInstance(placeMode.Value.Value, this)!;
CurrentPermission = info;
CurrentMode = (PlacementMode) Activator.CreateInstance(placeMode, this)!;
if (hijack != null)
{
@@ -772,7 +791,9 @@ namespace Robust.Client.Placement
var grid = EntityManager.GetComponent<MapGridComponent>(gridId);
// no point changing the tile to the same thing.
if (Maps.GetTileRef(gridId, grid, coordinates).Tile.TypeId == CurrentPermission.TileType)
var tileRef = Maps.GetTileRef(gridId, grid, coordinates).Tile;
if (tileRef.TypeId == CurrentPermission.TileType &&
tileRef.RotationMirroring == Tile.DirectionToByte(Direction) + (Mirrored ? 4 : 0))
return;
}
@@ -796,9 +817,14 @@ namespace Robust.Client.Placement
};
if (CurrentPermission.IsTile)
{
message.TileType = CurrentPermission.TileType;
message.Mirrored = Mirrored;
}
else
{
message.EntityTemplateName = CurrentPermission.EntityType;
}
// world x and y
message.NetCoordinates = EntityManager.GetNetCoordinates(coordinates);

View File

@@ -121,8 +121,8 @@ namespace Robust.Client.Placement
{
if (!coordinate.IsValid(pManager.EntityManager))
return; // Just some paranoia just in case
var worldPos = coordinate.ToMapPos(pManager.EntityManager, transformSys);
var worldRot = pManager.EntityManager.GetComponent<TransformComponent>(coordinate.EntityId).WorldRotation + dirAng;
var worldPos = transformSys.ToMapCoordinates(coordinate).Position;
var worldRot = transformSys.GetWorldRotation(coordinate.EntityId) + dirAng;
sprite.Color = IsValidPosition(coordinate) ? ValidPlaceColor : InvalidPlaceColor;
var rot = args.Viewport.Eye?.Rotation ?? default;
@@ -230,7 +230,7 @@ namespace Robust.Client.Placement
var range = pManager.CurrentPermission!.Range;
var transformSys = pManager.EntityManager.System<SharedTransformSystem>();
if (range > 0 && !pManager.EntityManager.GetComponent<TransformComponent>(controlled).Coordinates.InRange(pManager.EntityManager, transformSys, coordinates, range))
if (range > 0 && !transformSys.InRange(pManager.EntityManager.GetComponent<TransformComponent>(controlled).Coordinates, coordinates, range))
return false;
return true;
}
@@ -239,7 +239,7 @@ namespace Robust.Client.Placement
{
var bounds = pManager.ColliderAABB;
var transformSys = pManager.EntityManager.System<SharedTransformSystem>();
var mapCoords = coordinates.ToMap(pManager.EntityManager, transformSys);
var mapCoords = transformSys.ToMapCoordinates(coordinates);
var (x, y) = mapCoords.Position;
var collisionBox = Box2.FromDimensions(
@@ -248,7 +248,9 @@ namespace Robust.Client.Placement
bounds.Width,
bounds.Height);
return EntitySystem.Get<SharedPhysicsSystem>().TryCollideRect(collisionBox, mapCoords.MapId);
return pManager.EntityManager
.System<SharedPhysicsSystem>()
.TryCollideRect(collisionBox, mapCoords.MapId);
}
protected Vector2 ScreenToWorld(Vector2 point)
@@ -265,10 +267,8 @@ namespace Robust.Client.Placement
{
var mapCoords = pManager.EyeManager.PixelToMap(coords.Position);
var transformSys = pManager.EntityManager.System<SharedTransformSystem>();
if (!pManager.MapManager.TryFindGridAt(mapCoords, out var gridUid, out var grid))
if (!pManager.MapManager.TryFindGridAt(mapCoords, out var gridUid, out _))
{
return transformSys.ToCoordinates(mapCoords);
}

View File

@@ -2,9 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using Robust.Client.Timing;
using Robust.Client.Utility;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Prototypes;
@@ -15,7 +13,9 @@ namespace Robust.Client.Prototypes
public sealed class ClientPrototypeManager : PrototypeManager
{
[Dependency] private readonly INetManager _netManager = default!;
#pragma warning disable CS0414
[Dependency] private readonly IClientGameTiming _timing = default!;
#pragma warning restore CS0414
[Dependency] private readonly IGameControllerInternal _controller = default!;
[Dependency] private readonly IReloadManager _reload = default!;
@@ -56,7 +56,7 @@ namespace Robust.Client.Prototypes
using var _ = _timing.StartStateApplicationArea();
ReloadPrototypes([file]);
Logger.Info($"Reloaded prototypes in {sw.ElapsedMilliseconds} ms");
Sawmill.Info($"Reloaded prototypes in {sw.ElapsedMilliseconds} ms");
#endif
}
}

View File

@@ -59,7 +59,14 @@ namespace Robust.Client.ResourceManagement
{
try
{
TextureResource.LoadPreTexture(_manager, data);
TextureResource.LoadTextureParameters(_manager, data);
if (!data.LoadParameters.Preload)
{
data.Skip = true;
return;
}
TextureResource.LoadPreTextureData(_manager, data);
}
catch (Exception e)
{
@@ -72,7 +79,7 @@ namespace Robust.Client.ResourceManagement
foreach (var data in texList)
{
if (data.Bad)
if (data.Bad || data.Skip)
continue;
try
@@ -87,6 +94,7 @@ namespace Robust.Client.ResourceManagement
}
var errors = 0;
var skipped = 0;
foreach (var data in texList)
{
if (data.Bad)
@@ -95,6 +103,12 @@ namespace Robust.Client.ResourceManagement
continue;
}
if (data.Skip)
{
skipped += 1;
continue;
}
try
{
var texResource = new TextureResource();
@@ -110,9 +124,10 @@ namespace Robust.Client.ResourceManagement
}
sawmill.Debug(
"Preloaded {CountLoaded} textures ({CountErrored} errored) in {LoadTime}",
texList.Length,
"Preloaded {CountLoaded} textures ({CountErrored} errored, {CountSkipped} skipped) in {LoadTime}",
texList.Length - skipped - errors,
errors,
skipped,
sw.Elapsed);
}

View File

@@ -1,8 +1,8 @@
using System;
using System.IO;
using System.Linq;
using System.Threading;
using Robust.Client.Audio;
using Robust.Shared.Audio;
using Robust.Shared.ContentPack;
using Robust.Shared.IoC;
using Robust.Shared.Utility;
@@ -11,6 +11,13 @@ namespace Robust.Client.ResourceManagement;
public sealed class AudioResource : BaseResource
{
// from: https://en.wikipedia.org/wiki/List_of_file_signatures
private static readonly byte[] OggSignature = "OggS"u8.ToArray();
private static readonly byte[] RiffSignature = "RIFF"u8.ToArray();
private const int WavSignatureStart = 8; // RIFF????
private static readonly byte[] WavSignature = "WAVE"u8.ToArray();
private const int MaxSignatureLength = 12; // RIFF????WAVE
public AudioStream AudioStream { get; private set; } = default!;
public void Load(AudioStream stream)
@@ -28,14 +35,19 @@ public sealed class AudioResource : BaseResource
}
using var fileStream = cache.ContentFileRead(path);
var seekableStream = fileStream.CanSeek ? fileStream : fileStream.CopyToMemoryStream();
byte[] signature = seekableStream.ReadExact(MaxSignatureLength);
seekableStream.Seek(0, SeekOrigin.Begin);
var audioManager = dependencies.Resolve<IAudioInternal>();
if (path.Extension == "ogg")
if (signature[..OggSignature.Length].SequenceEqual(OggSignature))
{
AudioStream = audioManager.LoadAudioOggVorbis(fileStream, path.ToString());
AudioStream = audioManager.LoadAudioOggVorbis(seekableStream, path.ToString());
}
else if (path.Extension == "wav")
else if (signature[..RiffSignature.Length].SequenceEqual(RiffSignature)
&& signature[WavSignatureStart..MaxSignatureLength].SequenceEqual(WavSignature))
{
AudioStream = audioManager.LoadAudioWav(fileStream, path.ToString());
AudioStream = audioManager.LoadAudioWav(seekableStream, path.ToString());
}
else
{

View File

@@ -32,18 +32,22 @@ namespace Robust.Client.ResourceManagement
var data = new LoadStepData {Path = path};
LoadPreTexture(dependencies.Resolve<IResourceManager>(), data);
LoadTextureParameters(dependencies.Resolve<IResourceManager>(), data);
LoadPreTextureData(dependencies.Resolve<IResourceManager>(), data);
LoadTexture(dependencies.Resolve<IClyde>(), data);
LoadFinish(dependencies.Resolve<IResourceCache>(), data);
}
internal static void LoadPreTexture(IResourceManager cache, LoadStepData data)
internal static void LoadPreTextureData(IResourceManager cache, LoadStepData data)
{
using (var stream = cache.ContentFileRead(data.Path))
{
data.Image = Image.Load<Rgba32>(stream);
}
}
internal static void LoadTextureParameters(IResourceManager cache, LoadStepData data)
{
data.LoadParameters = TryLoadTextureParameters(cache, data.Path) ?? TextureLoadParameters.Default;
}
@@ -95,7 +99,8 @@ namespace Robust.Client.ResourceManagement
{
var data = new LoadStepData {Path = path};
LoadPreTexture(dependencies.Resolve<IResourceManager>(), data);
LoadTextureParameters(dependencies.Resolve<IResourceManager>(), data);
LoadPreTextureData(dependencies.Resolve<IResourceManager>(), data);
if (data.Image.Width == Texture.Width && data.Image.Height == Texture.Height)
{
@@ -119,6 +124,7 @@ namespace Robust.Client.ResourceManagement
public Image<Rgba32> Image = default!;
public TextureLoadParameters LoadParameters;
public OwnedTexture Texture = default!;
public bool Skip;
public bool Bad;
}

View File

@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using Robust.Client.Graphics;
using Robust.Client.Placement;
using Robust.Client.Placement.Modes;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
@@ -28,19 +30,23 @@ public sealed class TileSpawningUIController : UIController
private readonly List<ITileDefinition> _shownTiles = new();
private bool _clearingTileSelections;
private bool _eraseTile;
private bool _mirrorableTile; // Tracks if the chosen tile even can be mirrored.
private bool _mirroredTile;
public override void Initialize()
{
DebugTools.Assert(_init == false);
_init = true;
_placement.PlacementChanged += ClearTileSelection;
_placement.DirectionChanged += OnDirectionChanged;
_placement.MirroredChanged += OnMirroredChanged;
}
private void StartTilePlacement(int tileType)
{
var newObjInfo = new PlacementInformation
{
PlacementOption = "AlignTileAny",
PlacementOption = nameof(AlignTileAny),
TileType = tileType,
Range = 400,
IsTile = true
@@ -67,6 +73,17 @@ public sealed class TileSpawningUIController : UIController
args.Button.Pressed = args.Pressed;
}
private void OnTileMirroredToggled(ButtonToggledEventArgs args)
{
if (_window == null || _window.Disposed)
return;
_placement.Mirrored = args.Pressed;
_mirroredTile = _placement.Mirrored;
args.Button.Pressed = args.Pressed;
}
public void ToggleWindow()
{
EnsureWindow();
@@ -78,6 +95,9 @@ public sealed class TileSpawningUIController : UIController
else
{
_window.Open();
UpdateEntityDirectionLabel();
UpdateMirroredButton();
_window.SearchBar.GrabKeyboardFocus();
}
}
@@ -93,6 +113,10 @@ public sealed class TileSpawningUIController : UIController
_window.TileList.OnItemDeselected += OnTileItemDeselected;
_window.EraseButton.Pressed = _eraseTile;
_window.EraseButton.OnToggled += OnTileEraseToggled;
_window.MirroredButton.Disabled = !_mirrorableTile;
_window.RotationLabel.FontColorOverride = _mirrorableTile ? Color.White : Color.Gray;
_window.MirroredButton.Pressed = _mirroredTile;
_window.MirroredButton.OnToggled += OnTileMirroredToggled;
BuildTileList();
}
@@ -110,6 +134,7 @@ public sealed class TileSpawningUIController : UIController
_window.TileList.ClearSelected();
_clearingTileSelections = false;
_window.EraseButton.Pressed = false;
_window.MirroredButton.Pressed = _placement.Mirrored;
}
private void OnTileClearPressed(ButtonEventArgs args)
@@ -137,6 +162,7 @@ public sealed class TileSpawningUIController : UIController
{
var definition = _shownTiles[args.ItemIndex];
StartTilePlacement(definition.TileId);
UpdateMirroredButton();
}
private void OnTileItemDeselected(ItemList.ItemListDeselectedEventArgs args)
@@ -149,6 +175,41 @@ public sealed class TileSpawningUIController : UIController
_placement.Clear();
}
private void OnDirectionChanged(object? sender, EventArgs e)
{
UpdateEntityDirectionLabel();
}
private void UpdateEntityDirectionLabel()
{
if (_window == null || _window.Disposed)
return;
_window.RotationLabel.Text = _placement.Direction.ToString();
}
private void OnMirroredChanged(object? sender, EventArgs e)
{
UpdateMirroredButton();
}
private void UpdateMirroredButton()
{
if (_window == null || _window.Disposed)
return;
if (_placement.CurrentPermission != null && _placement.CurrentPermission.IsTile)
{
var allowed = _tiles[_placement.CurrentPermission.TileType].AllowRotationMirror;
_mirrorableTile = allowed;
_window.MirroredButton.Disabled = !_mirrorableTile;
_window.RotationLabel.FontColorOverride = _mirrorableTile ? Color.White : Color.Gray;
}
_mirroredTile = _placement.Mirrored;
_window.MirroredButton.Pressed = _mirroredTile;
}
private void BuildTileList(string? searchStr = null)
{
if (_window == null || _window.Disposed) return;

View File

@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Shared.Collections;
using Robust.Shared.Graphics;
using Robust.Shared.Input;
using Robust.Shared.Maths;
@@ -83,9 +84,23 @@ namespace Robust.Client.UserInterface.Controls
_updateScrollbarVisibility();
}
public void Add(IEnumerable<Item> items)
{
foreach (var item in items)
{
if(item.Owner != this) throw new ArgumentException("Item is owned by another ItemList!");
_itemList.Add(item);
item.OnSelected += Select;
item.OnDeselected += Deselect;
}
Recalculate();
}
public void Add(Item item)
{
if (item == null) return;
if(item.Owner != this) throw new ArgumentException("Item is owned by another ItemList!");
_itemList.Add(item);
@@ -93,9 +108,19 @@ namespace Robust.Client.UserInterface.Controls
item.OnSelected += Select;
item.OnDeselected += Deselect;
RecalculateContentHeight();
if (_isAtBottom && ScrollFollowing)
_scrollBar.MoveToEnd();
Recalculate();
}
public void AddItems(IEnumerable<string> texts, Texture? icon = null, bool selectable = true, object? metadata = null)
{
var items = new ValueList<Item>();
foreach (var text in texts)
{
items.Add(new Item(this) {Text = text, Icon = icon, Selectable = selectable, Metadata = metadata});
}
Add(items);
}
public Item AddItem(string text, Texture? icon = null, bool selectable = true, object? metadata = null)
@@ -107,11 +132,15 @@ namespace Robust.Client.UserInterface.Controls
public void Clear()
{
foreach (var item in _itemList.ToArray())
// Handle this manually so we can just clear all at once.
foreach (var item in _itemList)
{
Remove(item);
item.OnSelected -= Select;
item.OnDeselected -= Deselect;
}
_itemList.Clear();
Recalculate();
_totalContentHeight = 0;
}
@@ -125,25 +154,35 @@ namespace Robust.Client.UserInterface.Controls
_itemList.CopyTo(array, arrayIndex);
}
private void InternalRemoveAt(int index)
{
if (_itemList.Count <= index)
return;
// If you modify this then also make sure to update Clear!
var item = _itemList[index];
_itemList.RemoveAt(index);
item.OnSelected -= Select;
item.OnDeselected -= Deselect;
}
public bool Remove(Item item)
{
if (item == null) return false;
var value = _itemList.Remove(item);
item.OnSelected -= Select;
item.OnDeselected -= Deselect;
RecalculateContentHeight();
if (_isAtBottom && ScrollFollowing)
_scrollBar.MoveToEnd();
Recalculate();
return value;
}
public void RemoveAt(int index)
{
Remove(this[index]);
InternalRemoveAt(index);
Recalculate();
}
public IEnumerator<Item> GetEnumerator()
@@ -161,16 +200,24 @@ namespace Robust.Client.UserInterface.Controls
return _itemList.IndexOf(item);
}
public void Insert(int index, Item item)
private void InternalInsert(int index, Item item)
{
if (item == null) return;
if(item.Owner != this) throw new ArgumentException("Item is owned by another ItemList!");
_itemList.Insert(index, item);
item.OnSelected += Select;
item.OnDeselected += Deselect;
}
public void Insert(int index, Item item)
{
InternalInsert(index, item);
Recalculate();
}
private void Recalculate()
{
RecalculateContentHeight();
if (_isAtBottom && ScrollFollowing)
_scrollBar.MoveToEnd();
@@ -191,7 +238,6 @@ namespace Robust.Client.UserInterface.Controls
SetItems(newItems, (a,b) => string.Compare(a.Text, b.Text));
}
/// <inheritdoc />
/// <summary>
/// This variant allows for a custom equality operator to compare items, when
/// comparing the Item text is not desired.
@@ -215,13 +261,13 @@ namespace Robust.Client.UserInterface.Controls
else if (cmpResult > 0)
{
// Item exists in our list, but not in `newItems`. Remove it.
RemoveAt(i);
InternalRemoveAt(i);
i--;
}
else if (cmpResult < 0)
{
// A new entry which doesn't exist in our list. Insert it.
Insert(i + 1, newItems[j]);
InternalInsert(i + 1, newItems[j]);
j--;
}
}
@@ -229,16 +275,18 @@ namespace Robust.Client.UserInterface.Controls
// Any remaining items in our list don't exist in `newItems` so remove them
while (i >= 0)
{
RemoveAt(i);
InternalRemoveAt(i);
i--;
}
// And finally, any remaining items in `newItems` don't exist in our list. Create them.
while (j >= 0)
{
Insert(0, newItems[j]);
InternalInsert(0, newItems[j]);
j--;
}
Recalculate();
}
// Without this attribute, this would compile into a property called "Item", causing problems with the Item class.
@@ -290,9 +338,12 @@ namespace Robust.Client.UserInterface.Controls
public void ClearSelected(int? except = null)
{
foreach (var item in GetSelected())
for (var i = 0; i < _itemList.Count; i++)
{
if(IndexOf(item) == except) continue;
if (i == except)
continue;
var item = _itemList[i];
item.Selected = false;
}
}
@@ -690,7 +741,7 @@ namespace Robust.Client.UserInterface.Controls
get => _selected;
set
{
if (!Selectable) return;
if (!Selectable || _selected == value) return;
_selected = value;
if(_selected) OnSelected?.Invoke(this);
else OnDeselected?.Invoke(this);

View File

@@ -10,7 +10,9 @@
</BoxContainer>
<ItemList Name="TileList" Access="Public" VerticalExpand="True"/>
<BoxContainer Orientation="Horizontal">
<Button Name="MirroredButton" Access="Public" ToggleMode="True" Text="{Loc tile-spawn-window-mirror-button-text}"/>
<Button Name="EraseButton" Access="Public" ToggleMode="True" Text="{Loc window-erase-button-text}"/>
</BoxContainer>
<Label Name="RotationLabel" Access="Public"/>
</BoxContainer>
</TileSpawnWindow>

View File

@@ -5,5 +5,6 @@
<DebugConsole Name="DebugConsole" />
<DevWindowTabUI Name="UI" />
<DevWindowTabPerf Name="Perf" />
<DevWindowTabTextures Name="Textures" />
</TabContainer>
</Control>

View File

@@ -8,6 +8,7 @@ using Robust.Client.UserInterface.Stylesheets;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Console;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
namespace Robust.Client.UserInterface
{
@@ -26,6 +27,7 @@ namespace Robust.Client.UserInterface
TabContainer.SetTabTitle(DebugConsole, "Debug Console");
TabContainer.SetTabTitle(UI, "User Interface");
TabContainer.SetTabTitle(Perf, "Profiling");
TabContainer.SetTabTitle(Textures, Loc.GetString("dev-window-tab-textures-title"));
Stylesheet =
new DefaultStylesheet(IoCManager.Resolve<IResourceCache>(), IoCManager.Resolve<IUserInterfaceManager>()).Stylesheet;

View File

@@ -0,0 +1,26 @@
<Control xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Robust.Client.UserInterface.DevWindowTabTextures">
<SplitContainer Orientation="Horizontal">
<!-- Left pane: list of textures -->
<BoxContainer Orientation="Vertical" MinWidth="200">
<Button Name="ReloadButton" Text="{Loc 'dev-window-tab-textures-reload'}" />
<LineEdit Name="SearchBar" PlaceHolder="{Loc 'dev-window-tab-textures-filter'}" />
<ScrollContainer HScrollEnabled="False" VerticalExpand="True">
<BoxContainer Name="TextureList" Orientation="Vertical" />
</ScrollContainer>
<Label Name="SummaryLabel" Margin="4" />
</BoxContainer>
<!-- Right pane: show the selected texture info -->
<Control MinWidth="400">
<BoxContainer Orientation="Vertical">
<TextureRect Name="SelectedTextureDisplay" VerticalExpand="True" CanShrink="True"
Stretch="KeepAspectCentered" />
<Label Name="SelectedTextureInfo" />
</BoxContainer>
</Control>
</SplitContainer>
</Control>

View File

@@ -0,0 +1,136 @@
using System;
using System.Linq;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.Graphics.Clyde;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Utility;
namespace Robust.Client.UserInterface;
/// <summary>
/// Shows all loaded textures in the game.
/// </summary>
[GenerateTypedNameReferences]
internal sealed partial class DevWindowTabTextures : Control
{
[Dependency] private readonly IClydeInternal _clyde = null!;
[Dependency] private readonly ILocalizationManager _loc = null!;
public DevWindowTabTextures()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
ReloadButton.OnPressed += _ => Reload();
SearchBar.OnTextChanged += _ => Reload();
}
protected override void VisibilityChanged(bool newVisible)
{
if (newVisible)
{
Reload();
}
else
{
// Clear to release memory when tab not visible.
Clear();
}
}
private void Clear()
{
TextureList.RemoveAllChildren();
}
private void Reload()
{
Clear();
var total = 0L;
foreach (var (clydeTexture, loadedTexture) in _clyde.GetLoadedTextures()
.OrderByDescending(tup => tup.Item2.MemoryPressure))
{
if (!string.IsNullOrWhiteSpace(SearchBar.Text))
{
if (loadedTexture.Name is not { } name)
continue;
if (!name.Contains(SearchBar.Text, StringComparison.CurrentCultureIgnoreCase))
continue;
}
if (loadedTexture.MemoryPressure == 0)
{
// Bad hack to avoid showing render targets lol.
continue;
}
var button = new ContainerButton
{
Children = { new TextureEntry(loadedTexture, clydeTexture) }
};
button.OnPressed += _ => SelectTexture(loadedTexture, clydeTexture);
TextureList.AddChild(button);
total += loadedTexture.MemoryPressure;
}
SummaryLabel.Text =
_loc.GetString("dev-window-tab-textures-summary", ("bytes", ByteHelpers.FormatBytes(total)));
}
private void SelectTexture(Clyde.LoadedTexture loaded, Clyde.ClydeTexture texture)
{
SelectedTextureDisplay.Texture = texture;
SelectedTextureInfo.Text = _loc.GetString("dev-window-tab-textures-info",
("width", loaded.Width),
("height", loaded.Height),
("pixelType", loaded.TexturePixelType),
("srgb", loaded.IsSrgb),
("name", loaded.Name ?? ""),
("bytes", ByteHelpers.FormatBytes(loaded.MemoryPressure)));
}
private sealed class TextureEntry : Control
{
public TextureEntry(Clyde.LoadedTexture loaded, Clyde.ClydeTexture texture)
{
SetHeight = 64;
var bytes = ByteHelpers.FormatBytes(loaded.MemoryPressure);
var label = loaded.Name == null
? $"{texture.TextureId} ({bytes})"
: $"{loaded.Name}\n{texture.TextureId} ({bytes})";
AddChild(new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
Children =
{
new TextureRect
{
SetWidth = 64,
Texture = texture,
CanShrink = true,
RectClipContent = true,
Stretch = TextureRect.StretchMode.Scale
},
new Label
{
Text = label,
ClipText = true,
HorizontalExpand = true
}
}
});
}
}
}

View File

@@ -0,0 +1,7 @@
namespace Robust.Client.UserInterface;
internal static class UIConstants
{
public const string ObsoleteInheritanceMessage =
"Do not inherit from standard UI controls, compose via nesting instead";
}

View File

@@ -103,7 +103,6 @@ internal partial class UserInterfaceManager
return;
}
var guiArgs = new GUIBoundKeyEventArgs(args.Function, args.State, args.PointerLocation, args.CanFocus,
args.PointerLocation.Position / control.UIScale - control.GlobalPosition,
args.PointerLocation.Position - control.GlobalPixelPosition);
@@ -115,10 +114,6 @@ internal partial class UserInterfaceManager
args.Handle();
}
// Attempt to ensure that keybind-up events get raised after a keybind-down.
DebugTools.Assert(!_focusedControls.TryGetValue(args.Function, out var existing)
|| !existing.VisibleInTree
|| args.IsRepeat && existing == control);
_focusedControls[args.Function] = control;
OnKeyBindDown?.Invoke(control);

View File

@@ -5,9 +5,9 @@ using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Profiling;
using Robust.Shared.Utility;
namespace Robust.Client.UserInterface;
internal sealed partial class UserInterfaceManager
@@ -61,7 +61,7 @@ internal sealed partial class UserInterfaceManager
Title = string.IsNullOrEmpty(title) ? Loc.GetString("popup-title") : title,
};
var label = new Label { Text = contents };
var label = new RichTextLabel { Text = $"[color=white]{FormattedMessage.EscapeText(contents)}[/color]" };
var vBox = new BoxContainer
{

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using Robust.Client.Graphics;
using Robust.Shared;
@@ -21,12 +20,14 @@ internal sealed class ReloadManager : IReloadManager
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly ILogManager _logMan = default!;
[Dependency] private readonly IResourceManager _res = default!;
#pragma warning disable CS0414
[Dependency] private readonly ITaskManager _tasks = default!;
#pragma warning restore CS0414
private readonly TimeSpan _reloadDelay = TimeSpan.FromMilliseconds(10);
private CancellationTokenSource _reloadToken = new();
private readonly HashSet<ResPath> _reloadQueue = new();
private List<FileSystemWatcher> _watchers = new();
private List<FileSystemWatcher> _watchers = new(); // this list is never used but needed to prevent them from being garbage collected
public event Action<ResPath>? OnChanged;
@@ -69,6 +70,11 @@ internal sealed class ReloadManager : IReloadManager
_reloadQueue.Clear();
}
public void Register(ResPath directory, string filter)
{
Register(directory.ToString(), filter);
}
public void Register(string directory, string filter)
{
if (!_cfg.GetCVar(CVars.ResPrototypeReloadWatch))
@@ -90,7 +96,7 @@ internal sealed class ReloadManager : IReloadManager
NotifyFilter = NotifyFilters.LastWrite
};
_watchers.Add(watcher);
_watchers.Add(watcher); // prevent garbage collection
watcher.Changed += OnWatch;
@@ -100,7 +106,7 @@ internal sealed class ReloadManager : IReloadManager
}
catch (IOException ex)
{
Logger.Error($"Watching resources in path {path} threw an exception:\n{ex}");
_sawmill.Error($"Watching resources in path {path} threw an exception:\n{ex}");
}
}
@@ -136,6 +142,6 @@ internal sealed class ReloadManager : IReloadManager
}
});
}
#endif
#endif
}
}

View File

@@ -2,11 +2,15 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Threading;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Collections;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Timing;
namespace Robust.Client.ViewVariables
{
@@ -51,15 +55,19 @@ namespace Robust.Client.ViewVariables
_lastSearch = search;
EntryItemList.ClearSelected();
EntryItemList.Clear();
AddButton.Disabled = true;
var items = new ValueList<string>();
foreach (var component in _entries)
{
if(!string.IsNullOrEmpty(search) && !component.Contains(search, StringComparison.InvariantCultureIgnoreCase))
continue;
EntryItemList.AddItem(component);
items.Add(component);
}
EntryItemList.AddItems(items);
}
private void OnSearchTextChanged(LineEdit.LineEditEventArgs obj)

View File

@@ -37,6 +37,8 @@ public static class Diagnostics
public const string IdPreferOtherType = "RA0031";
public const string IdDuplicateDependency = "RA0032";
public const string IdForbidLiteral = "RA0033";
public const string IdObsoleteInheritance = "RA0034";
public const string IdObsoleteInheritanceWithMessage = "RA0035";
public static SuppressionDescriptor MeansImplicitAssignment =>
new SuppressionDescriptor("RADC1000", "CS0649", "Marked as implicitly assigned.");

View File

@@ -331,6 +331,7 @@ namespace Robust.Server
// TODO: solve this properly.
_serializer.Initialize();
_loc.Initialize();
_loc.AddLoadedToStringSerializer(_stringSerializer);
//IoCManager.Resolve<IMapLoader>().LoadedMapData +=

View File

@@ -4,6 +4,7 @@ using Robust.Server.GameObjects;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision.Shapes;
@@ -16,6 +17,20 @@ public sealed class ScaleCommand : LocalizedCommands
[Dependency] private readonly IEntityManager _entityManager = default!;
public override string Command => "scale";
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
{
switch (args.Length)
{
case 1:
return CompletionResult.FromOptions(CompletionHelper.NetEntities(args[0], entManager: _entityManager));
case 2:
return CompletionResult.FromHint(Loc.GetString("cmd-hint-float"));
default:
return CompletionResult.Empty;
}
}
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 2)

View File

@@ -14,6 +14,19 @@ public sealed class PointLightSystem : SharedPointLightSystem
base.Initialize();
SubscribeLocalEvent<PointLightComponent, ComponentGetState>(OnLightGetState);
SubscribeLocalEvent<PointLightComponent, ComponentStartup>(OnLightStartup);
SubscribeLocalEvent<PointLightComponent, ComponentShutdown>(OnLightShutdown);
SubscribeLocalEvent<PointLightComponent, MetaFlagRemoveAttemptEvent>(OnFlagRemoveAttempt);
}
private void OnLightShutdown(Entity<PointLightComponent> ent, ref ComponentShutdown args)
{
UpdatePriority(ent.Owner, ent.Comp, MetaData(ent.Owner));
}
private void OnFlagRemoveAttempt(Entity<PointLightComponent> ent, ref MetaFlagRemoveAttemptEvent args)
{
if (IsHighPriority(ent.Comp))
args.ToRemove &= ~MetaDataFlags.PvsPriority;
}
private void OnLightStartup(EntityUid uid, PointLightComponent component, ComponentStartup args)
@@ -21,24 +34,14 @@ public sealed class PointLightSystem : SharedPointLightSystem
UpdatePriority(uid, component, MetaData(uid));
}
protected override void UpdatePriority(EntityUid uid, SharedPointLightComponent comp, MetaDataComponent meta)
private bool IsHighPriority(SharedPointLightComponent comp)
{
var isHighPriority = comp.Enabled && comp.CastShadows && (comp.Radius > 7);
_metadata.SetFlag((uid, meta), MetaDataFlags.PvsPriority, isHighPriority);
return comp is {Enabled: true, CastShadows: true, Radius: > 7, LifeStage: <= ComponentLifeStage.Running};
}
private void OnLightGetState(EntityUid uid, PointLightComponent component, ref ComponentGetState args)
protected override void UpdatePriority(EntityUid uid, SharedPointLightComponent comp, MetaDataComponent meta)
{
args.State = new PointLightComponentState()
{
Color = component.Color,
Enabled = component.Enabled,
Energy = component.Energy,
Offset = component.Offset,
Radius = component.Radius,
Softness = component.Softness,
CastShadows = component.CastShadows,
};
_metadata.SetFlag((uid, meta), MetaDataFlags.PvsPriority, IsHighPriority(comp));
}
public override SharedPointLightComponent EnsureLight(EntityUid uid)

View File

@@ -9,12 +9,30 @@ public sealed class ServerOccluderSystem : OccluderSystem
{
[Dependency] private readonly MetaDataSystem _metadata = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<OccluderComponent, MetaFlagRemoveAttemptEvent>(OnFlagRemoveAttempt);
}
private void OnFlagRemoveAttempt(Entity<OccluderComponent> ent, ref MetaFlagRemoveAttemptEvent args)
{
if (ent.Comp is {Enabled: true, LifeStage: <= ComponentLifeStage.Running})
args.ToRemove &= ~MetaDataFlags.PvsPriority;
}
protected override void OnCompStartup(EntityUid uid, OccluderComponent component, ComponentStartup args)
{
base.OnCompStartup(uid, component, args);
_metadata.SetFlag(uid, MetaDataFlags.PvsPriority, component.Enabled);
}
protected override void OnCompRemoved(EntityUid uid, OccluderComponent component, ComponentRemove args)
{
base.OnCompRemoved(uid, component, args);
_metadata.SetFlag(uid, MetaDataFlags.PvsPriority, false);
}
public override void SetEnabled(EntityUid uid, bool enabled, OccluderComponent? comp = null, MetaDataComponent? meta = null)
{
if (!Resolve(uid, ref comp, false))

View File

@@ -204,7 +204,7 @@ internal struct PvsMetadata
{
DebugTools.AssertEqual(NetEntity, comp.NetEntity);
DebugTools.AssertEqual(VisMask, comp.VisibilityMask);
DebugTools.Assert(LifeStage == comp.EntityLifeStage);
DebugTools.AssertEqual(LifeStage, comp.EntityLifeStage);
DebugTools.Assert(LastModifiedTick == comp.EntityLastModifiedTick || LastModifiedTick.Value == 0);
}
}

View File

@@ -166,11 +166,11 @@ internal sealed partial class PvsSystem
var session = pvsSession.Session;
if (session.Status != SessionStatus.InGame)
{
pvsSession.Viewers = Array.Empty<Entity<TransformComponent, EyeComponent?>>();
pvsSession.Viewers = Array.Empty<Entity<TransformComponent, EyeComponent?>>();
return;
}
// Fast path
// The majority of players will have no view subscriptions
if (session.ViewSubscriptions.Count == 0)
{
if (session.AttachedEntity is not {} attached)
@@ -184,15 +184,21 @@ internal sealed partial class PvsSystem
return;
}
var count = session.ViewSubscriptions.Count;
var i = 0;
if (session.AttachedEntity is { } local)
{
Array.Resize(ref pvsSession.Viewers, session.ViewSubscriptions.Count + 1);
if (!session.ViewSubscriptions.Contains(local))
count += 1;
Array.Resize(ref pvsSession.Viewers, count);
// Attached entity is always the first viewer, to prioritize it and help reduce pop-in for the "main" eye.
pvsSession.Viewers[i++] = (local, Transform(local), _eyeQuery.CompOrNull(local));
}
else
{
Array.Resize(ref pvsSession.Viewers, session.ViewSubscriptions.Count);
Array.Resize(ref pvsSession.Viewers, count);
}
foreach (var ent in session.ViewSubscriptions)
@@ -200,6 +206,8 @@ internal sealed partial class PvsSystem
if (ent != session.AttachedEntity)
pvsSession.Viewers[i++] = (ent, Transform(ent), _eyeQuery.CompOrNull(ent));
}
DebugTools.AssertEqual(i, pvsSession.Viewers.Length);
}
private void ProcessVisibleChunks()

View File

@@ -0,0 +1,8 @@
using Robust.Shared.Localization;
namespace Robust.Server.Localization;
internal sealed class ServerLocalizationManager : LocalizationManager, ILocalizationManager
{
void ILocalizationManager.Initialize() => Initialize();
}

View File

@@ -268,7 +268,6 @@ namespace Robust.Server.Physics
newGrids[i] = newGridUid;
// Keep same origin / velocity etc; this makes updating a lot faster and easier.
_xformSystem.SetWorldPosition(newGridXform, gridPos);
_xformSystem.SetWorldPositionRotation(newGridUid, gridPos, gridRot, newGridXform);
var splitBody = _bodyQuery.GetComponent(newGridUid);
_physics.SetLinearVelocity(newGridUid, mapBody.LinearVelocity, body: splitBody);
@@ -290,7 +289,7 @@ namespace Robust.Server.Physics
}
_maps.SetTiles(newGrid.Owner, newGrid.Comp, tileData);
DebugTools.Assert(_mapManager.IsGrid(newGridUid), "A split grid had no tiles?");
DebugTools.Assert(_gridQuery.HasComp(newGridUid), "A split grid had no tiles?");
// Set tiles on new grid + update anchored entities
foreach (var node in group)

View File

@@ -179,11 +179,14 @@ namespace Robust.Server.Placement
}
else
{
PlaceNewTile(tileType, coordinates, msg.MsgChannel.UserId);
if (_tileDefinitionManager[tileType].AllowRotationMirror)
PlaceNewTile(tileType, coordinates, msg.MsgChannel.UserId, Tile.DirectionToByte(dirRcv), msg.Mirrored);
else
PlaceNewTile(tileType, coordinates, msg.MsgChannel.UserId, Tile.DirectionToByte(Direction.South), false);
}
}
private void PlaceNewTile(int tileType, EntityCoordinates coordinates, NetUserId placingUserId)
private void PlaceNewTile(int tileType, EntityCoordinates coordinates, NetUserId placingUserId, byte direction, bool mirrored)
{
if (!coordinates.IsValid(_entityManager)) return;
@@ -193,7 +196,7 @@ namespace Robust.Server.Placement
if (_entityManager.TryGetComponent(coordinates.EntityId, out grid)
|| _mapManager.TryFindGridAt(_xformSystem.ToMapCoordinates(coordinates), out gridId, out grid))
{
_maps.SetTile(gridId, grid, coordinates, new Tile(tileType));
_maps.SetTile(gridId, grid, coordinates, new Tile(tileType, rotationMirroring: (byte)(direction + (mirrored ? 4 : 0))));
var placementEraseEvent = new PlacementTileEvent(tileType, coordinates, placingUserId);
_entityManager.EventBus.RaiseEvent(EventSource.Local, placementEraseEvent);
@@ -207,7 +210,7 @@ namespace Robust.Server.Placement
_xformSystem.SetWorldPosition(newGridXform, coordinates.Position - newGrid.Comp.TileSizeHalfVector); // assume bottom left tile origin
var tilePos = _maps.WorldToTile(newGrid.Owner, newGrid.Comp, coordinates.Position);
_maps.SetTile(newGrid.Owner, newGrid.Comp, tilePos, new Tile(tileType));
_maps.SetTile(newGrid.Owner, newGrid.Comp, tilePos, new Tile(tileType, rotationMirroring: (byte)(direction + (mirrored ? 4 : 0))));
var placementEraseEvent = new PlacementTileEvent(tileType, coordinates, placingUserId);
_entityManager.EventBus.RaiseEvent(EventSource.Local, placementEraseEvent);

View File

@@ -13,8 +13,10 @@ namespace Robust.Server.Prototypes
{
public sealed class ServerPrototypeManager : PrototypeManager
{
#pragma warning disable CS0414
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IConGroupController _conGroups = default!;
#pragma warning restore CS0414
[Dependency] private readonly INetManager _netManager = default!;
[Dependency] private readonly IBaseServerInternal _server = default!;

View File

@@ -4,6 +4,7 @@ using Robust.Server.Console;
using Robust.Server.DataMetrics;
using Robust.Server.GameObjects;
using Robust.Server.GameStates;
using Robust.Server.Localization;
using Robust.Server.Placement;
using Robust.Server.Player;
using Robust.Server.Prototypes;
@@ -21,6 +22,7 @@ using Robust.Shared.Console;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Player;
@@ -97,7 +99,9 @@ namespace Robust.Server
deps.Register<NetworkResourceManager>();
deps.Register<IHttpClientHolder, HttpClientHolder>();
deps.Register<UploadedContentManager>();
deps.Register<IHWId, DummyHWId>();
deps.Register<IHWId, DummyHWId>();
deps.Register<ILocalizationManager, ServerLocalizationManager>();
deps.Register<ILocalizationManagerInternal, ServerLocalizationManager>();
}
}
}

View File

@@ -0,0 +1,28 @@
using System;
namespace Robust.Shared.Analyzers;
/// <summary>
/// Indicates that the ability to <i>inherit</i> this type is obsolete, and attempting to do so should give a warning.
/// </summary>
/// <remarks>
/// This is useful to gracefully deal with types that should never have had <see cref="VirtualAttribute"/>.
/// </remarks>
/// <seealso cref="VirtualAttribute"/>
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class ObsoleteInheritanceAttribute : Attribute
{
/// <summary>
/// An optional message provided alongside this obsoletion.
/// </summary>
public string? Message { get; }
public ObsoleteInheritanceAttribute()
{
}
public ObsoleteInheritanceAttribute(string message)
{
Message = message;
}
}

View File

@@ -9,6 +9,7 @@ namespace Robust.Shared.Analyzers;
/// Robust uses analyzers to prevent accidental usage of non-sealed classes:
/// a class must be either marked [Virtual], abstract, or sealed.
/// </remarks>
/// <seealso cref="ObsoleteInheritanceAttribute"/>
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class VirtualAttribute : Attribute
{

View File

@@ -968,6 +968,13 @@ namespace Robust.Shared
public static readonly CVarDef<string> RenderFOVColor =
CVarDef.Create("render.fov_color", Color.Black.ToHex(), CVar.REPLICATED | CVar.SERVER);
/// <summary>
/// Whether to render tile edges, which is where some tiles can partially overlap other adjacent tiles on a grid.
/// E.g., snow tiles partly extending beyond their own tile to blend together with different adjacent tiles types.
/// </summary>
public static readonly CVarDef<bool> RenderTileEdges =
CVarDef.Create("render.tile_edges", true, CVar.CLIENTONLY);
/*
* CONTROLS
*/

View File

@@ -1,6 +1,7 @@
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Utility;
namespace Robust.Shared.ComponentTrees;
@@ -13,20 +14,23 @@ namespace Robust.Shared.ComponentTrees;
/// </remarks>
internal sealed class RecursiveMoveSystem : EntitySystem
{
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
public delegate void TreeRecursiveMoveEventHandler(EntityUid uid, TransformComponent xform);
public event TreeRecursiveMoveEventHandler? OnTreeRecursiveMove;
private EntityQuery<MapComponent> _mapQuery;
private EntityQuery<MapGridComponent> _gridQuery;
private EntityQuery<TransformComponent> _xformQuery;
bool _subscribed = false;
private bool _subscribed = false;
public override void Initialize()
{
base.Initialize();
_gridQuery = GetEntityQuery<MapGridComponent>();
_mapQuery = GetEntityQuery<MapComponent>();
_xformQuery = GetEntityQuery<TransformComponent>();
}
@@ -52,8 +56,8 @@ internal sealed class RecursiveMoveSystem : EntitySystem
if (args.Component.MapUid == args.Sender || args.Component.GridUid == args.Sender)
return;
DebugTools.Assert(!_mapManager.IsMap(args.Sender));
DebugTools.Assert(!_mapManager.IsGrid(args.Sender));
DebugTools.Assert(!_mapQuery.HasComp(args.Sender));
DebugTools.Assert(!_gridQuery.HasComp(args.Sender));
AnythingMovedSubHandler(args.Sender, args.Component);
}

View File

@@ -37,7 +37,7 @@ sealed class AddMapCommand : LocalizedEntityCommands
sealed class RemoveMapCommand : LocalizedCommands
{
[Dependency] private readonly IMapManager _map = default!;
[Dependency] private readonly IEntitySystemManager _systems = default!;
public override string Command => "rmmap";
public override bool RequireServerOrSingleplayer => true;
@@ -51,14 +51,15 @@ sealed class RemoveMapCommand : LocalizedCommands
}
var mapId = new MapId(int.Parse(args[0]));
var mapSystem = _systems.GetEntitySystem<SharedMapSystem>();
if (!_map.MapExists(mapId))
if (!mapSystem.MapExists(mapId))
{
shell.WriteError($"Map {mapId.Value} does not exist.");
return;
}
_map.DeleteMap(mapId);
mapSystem.DeleteMap(mapId);
shell.WriteLine($"Map {mapId.Value} was removed.");
}
}

View File

@@ -20,6 +20,7 @@ internal sealed class TeleportCommand : LocalizedEntityCommands
[Dependency] private readonly IMapManager _map = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
public override string Command => "tp";
public override bool RequireServerOrSingleplayer => true;
@@ -46,7 +47,7 @@ internal sealed class TeleportCommand : LocalizedEntityCommands
else
mapId = transform.MapID;
if (!_map.MapExists(mapId))
if (!_mapSystem.MapExists(mapId))
{
shell.WriteError($"Map {mapId} doesn't exist!");
return;
@@ -60,9 +61,11 @@ internal sealed class TeleportCommand : LocalizedEntityCommands
}
else
{
var mapEnt = _map.GetMapEntityIdOrThrow(mapId);
_transform.SetWorldPosition((entity, transform), position);
_transform.SetParent(entity, transform, mapEnt);
if (_mapSystem.TryGetMap(mapId, out var mapEnt))
{
_transform.SetWorldPosition((entity, transform), position);
_transform.SetParent(entity, transform, mapEnt.Value);
}
}
shell.WriteLine($"Teleported {shell.Player} to {mapId}:{posX},{posY}.");

View File

@@ -71,7 +71,7 @@ namespace Robust.Shared.Containers
#if DEBUG
// TODO make this a proper debug assert when gun code no longer fudges client-side spawn prediction.
if (entMan.IsClientSide(toInsert) && !entMan.IsClientSide(Owner) && Manager.NetSyncEnabled)
if (entMan.IsClientSide(toInsert) && !entMan.IsClientSide(Owner) && Manager.NetSyncEnabled && !entMan.HasComponent<PredictedSpawnComponent>(toInsert))
Logger.Warning("Inserting a client-side entity into a networked container slot. This will block the container slot and may cause issues.");
#endif
ContainedEntity = toInsert;

View File

@@ -16,6 +16,9 @@ public abstract class ContainerAttemptEventBase : CancellableEntityEventArgs
}
}
/// <summary>
/// Raised directed on the container when attempting to insert an entity.
/// </summary>
public sealed class ContainerIsInsertingAttemptEvent : ContainerAttemptEventBase
{
/// <summary>
@@ -23,7 +26,7 @@ public sealed class ContainerIsInsertingAttemptEvent : ContainerAttemptEventBase
/// I.e., could the entity be inserted if the container doesn't contain anything else?
/// </summary>
public bool AssumeEmpty { get; set; }
public ContainerIsInsertingAttemptEvent(BaseContainer container, EntityUid entityUid, bool assumeEmpty)
: base(container, entityUid)
{
@@ -31,6 +34,9 @@ public sealed class ContainerIsInsertingAttemptEvent : ContainerAttemptEventBase
}
}
/// <summary>
/// Raised directed on the entity being inserted into the container.
/// </summary>
public sealed class ContainerGettingInsertedAttemptEvent : ContainerAttemptEventBase
{
/// <summary>
@@ -46,6 +52,9 @@ public sealed class ContainerGettingInsertedAttemptEvent : ContainerAttemptEvent
}
}
/// <summary>
/// Raised directed on the container when attempting to remove an entity.
/// </summary>
public sealed class ContainerIsRemovingAttemptEvent : ContainerAttemptEventBase
{
public ContainerIsRemovingAttemptEvent(BaseContainer container, EntityUid entityUid) : base(container, entityUid)
@@ -53,6 +62,9 @@ public sealed class ContainerIsRemovingAttemptEvent : ContainerAttemptEventBase
}
}
/// <summary>
/// Raised directed on the entity being removed from the container.
/// </summary>
public sealed class ContainerGettingRemovedAttemptEvent : ContainerAttemptEventBase
{
public ContainerGettingRemovedAttemptEvent(BaseContainer container, EntityUid entityUid) : base(container, entityUid)

View File

@@ -530,6 +530,39 @@ namespace Robust.Shared.Containers
return false;
}
/// <summary>
/// Returns the full chain of containers containing the entity passed in, from innermost to outermost.
/// </summary>
/// <remarks>
/// The resulting collection includes the container directly containing the entity (if any),
/// the container containing that container, and so on until reaching the outermost container.
/// </remarks>
public IEnumerable<BaseContainer> GetContainingContainers(Entity<TransformComponent?> ent)
{
if (!ent.Owner.IsValid())
yield break;
if (!Resolve(ent, ref ent.Comp))
yield break;
var child = ent.Owner;
var parent = ent.Comp.ParentUid;
while (parent.IsValid())
{
if (((MetaQuery.GetComponent(child).Flags & MetaDataFlags.InContainer) == MetaDataFlags.InContainer) &&
_managerQuery.TryGetComponent(parent, out var conManager) &&
TryGetContainingContainer(parent, child, out var parentContainer, conManager))
{
yield return parentContainer;
}
var parentXform = TransformQuery.GetComponent(parent);
child = parent;
parent = parentXform.ParentUid;
}
}
/// <summary>
/// Gets the top-most container in the hierarchy for this entity, if it exists.
/// </summary>

View File

@@ -273,8 +273,6 @@ namespace Robust.Shared.ContentPack
public IEnumerable<string> ContentGetDirectoryEntries(ResPath path)
{
ArgumentNullException.ThrowIfNull(path, nameof(path));
if (!path.IsRooted)
throw new ArgumentException("Path is not rooted", nameof(path));

View File

@@ -497,6 +497,23 @@ Types:
DebuggerVisualizerAttribute: { All: True }
Stopwatch: { All: True }
System.Globalization:
# all the calendars are here, screw making our own.
Calendar: { All: True }
ChineseLunisolarCalendar: { All: True }
GregorianCalendar: { All: True }
HebrewCalendar: { All: True }
HijriCalendar: { All: True }
JapaneseCalendar: { All: True }
JapaneseLunisolarCalendar: { All: True }
JulianCalendar: { All: True }
KoreanCalendar: { All: True }
KoreanLunisolarCalendar: { All: True }
PersianCalendar: { All: True }
TaiwanCalendar: { All: True }
TaiwanLunisolarCalendar: { All: True }
ThaiBuddhistCalendar: { All: True }
UmAlQuraCalendar: { All: True }
#end calendars
CompareOptions: { }
CultureInfo: { All: True }
DateTimeStyles: { All: True } # Enum
@@ -896,6 +913,7 @@ Types:
System.Threading:
CancellationToken: { All: True }
CancellationTokenSource: { All: True }
CancellationTokenRegistration: { All: True }
Interlocked: { All: True }
Monitor: { All: True }
System.Web:
@@ -1071,6 +1089,7 @@ Types:
DateTime: { All: True }
DateTimeKind: { } # Enum
DateTimeOffset: { All: True }
DayOfWeek: { } # Enum
Decimal: { All: True }
Delegate:
Methods:

View File

@@ -475,7 +475,7 @@ public sealed class EntityDeserializer :
foreach (var (key, value) in tileMap.Children)
{
var yamlTileId = ((ValueDataNode) key).AsInt();
var yamlTileId = int.Parse(key, CultureInfo.InvariantCulture);
var tileName = ((ValueDataNode) value).Value;
if (migrations.TryGetValue(tileName, out var @new))
tileName = @new;
@@ -1000,7 +1000,7 @@ public sealed class EntityDeserializer :
continue;
DebugTools.Assert(meta.EntityLifeStage == EntityLifeStage.Initialized);
meta.EntityLifeStage = EntityLifeStage.MapInitialized;
EntMan.SetLifeStage(meta, EntityLifeStage.MapInitialized);
}
_log.Debug($"Finished flagging mapinit in {_stopwatch.Elapsed}");

View File

@@ -155,9 +155,9 @@ public sealed class EntitySerializer : ISerializationContext,
public event IsSerializableDelegate? OnIsSerializeable;
public delegate void IsSerializableDelegate(Entity<MetaDataComponent> ent, ref bool serializable);
public EntitySerializer(IDependencyCollection _dependency, SerializationOptions options)
public EntitySerializer(IDependencyCollection dependency, SerializationOptions options)
{
_dependency.InjectDependencies(this);
dependency.InjectDependencies(this);
_log = _logMan.GetSawmill("entity_serializer");
SerializerProvider.RegisterSerializer(this);

View File

@@ -69,22 +69,45 @@ internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingData
var tileDefinitionManager = dependencies.Resolve<ITileDefinitionManager>();
node.TryGetValue(new ValueDataNode("version"), out var versionNode);
node.TryGetValue("version", out var versionNode);
var version = ((ValueDataNode?) versionNode)?.AsInt() ?? 1;
for (ushort y = 0; y < chunk.ChunkSize; y++)
// Old map files will throw an error if they do not have a rotation/mirror byte stored.
if (version >= 7)
{
for (ushort x = 0; x < chunk.ChunkSize; x++)
for (ushort y = 0; y < chunk.ChunkSize; y++)
{
var id = version < 6 ? reader.ReadUInt16() : reader.ReadInt32();
var flags = reader.ReadByte();
var variant = reader.ReadByte();
for (ushort x = 0; x < chunk.ChunkSize; x++)
{
var id = reader.ReadInt32();
var flags = reader.ReadByte();
var variant = reader.ReadByte();
var rotationMirroring = reader.ReadByte();
var defName = tileMap[id];
id = tileDefinitionManager[defName].TileId;
var defName = tileMap[id];
id = tileDefinitionManager[defName].TileId;
var tile = new Tile(id, flags, variant);
chunk.TrySetTile(x, y, tile, out _, out _);
var tile = new Tile(id, flags, variant, rotationMirroring);
chunk.TrySetTile(x, y, tile, out _, out _);
}
}
}
else
{
for (ushort y = 0; y < chunk.ChunkSize; y++)
{
for (ushort x = 0; x < chunk.ChunkSize; x++)
{
var id = version < 6 ? reader.ReadUInt16() : reader.ReadInt32();
var flags = reader.ReadByte();
var variant = reader.ReadByte();
var defName = tileMap[id];
id = tileDefinitionManager[defName].TileId;
var tile = new Tile(id, flags, variant);
chunk.TrySetTile(x, y, tile, out _, out _);
}
}
}
@@ -106,7 +129,7 @@ internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingData
var gridNode = new ValueDataNode();
root.Add("tiles", gridNode);
root.Add("version", new ValueDataNode("6"));
root.Add("version", new ValueDataNode("7"));
gridNode.Value = SerializeTiles(value, context as EntitySerializer);
@@ -116,7 +139,7 @@ internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingData
private static string SerializeTiles(MapChunk chunk, EntitySerializer? serializer)
{
// number of bytes written per tile, because sizeof(Tile) is useless.
const int structSize = 6;
const int structSize = 7;
var nTiles = chunk.ChunkSize * chunk.ChunkSize * structSize;
var barr = new byte[nTiles];
@@ -134,6 +157,7 @@ internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingData
writer.Write(tile.TypeId);
writer.Write((byte) tile.Flags);
writer.Write(tile.Variant);
writer.Write(tile.RotationMirroring);
}
}
return Convert.ToBase64String(barr);
@@ -153,6 +177,7 @@ internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingData
writer.Write(yamlId);
writer.Write((byte) tile.Flags);
writer.Write(tile.Variant);
writer.Write(tile.RotationMirroring);
}
}
}

View File

@@ -147,7 +147,7 @@ namespace Robust.Shared.GameObjects
/// The current lifetime stage of this entity. You can use this to check
/// if the entity is initialized or being deleted.
/// </summary>
[ViewVariables]
[ViewVariables, Access(typeof(EntityManager), Other = AccessPermissions.ReadExecute)]
public EntityLifeStage EntityLifeStage { get; internal set; }
public MetaDataFlags Flags
@@ -238,6 +238,11 @@ namespace Robust.Shared.GameObjects
/// a grid or map.
/// </summary>
PvsPriority = 1 << 4,
/// <summary>
/// If set, transform system will raise events directed at this entity whenever the GridUid or MapUid are modified.
/// </summary>
ExtraTransformEvents = 1 << 5,
}
/// <summary>

View File

@@ -0,0 +1,7 @@
namespace Robust.Shared.GameObjects;
/// <summary>
/// Indicates the attached entity was spawn predicted and should be reconciled when the server states comes in.
/// </summary>
[RegisterComponent]
public sealed partial class PredictedSpawnComponent : Component;

View File

@@ -402,32 +402,6 @@ namespace Robust.Shared.GameObjects
_entMan.EntitySysManager.GetEntitySystem<SharedTransformSystem>().AttachToGridOrMap(Owner, this);
}
internal void UpdateChildMapIdsRecursive(
MapId newMapId,
EntityUid? newUid,
bool mapPaused,
EntityQuery<TransformComponent> xformQuery,
EntityQuery<MetaDataComponent> metaQuery,
MetaDataSystem system)
{
foreach (var child in _children)
{
//Set Paused state
var metaData = metaQuery.GetComponent(child);
system.SetEntityPaused(child, mapPaused, metaData);
var concrete = xformQuery.GetComponent(child);
concrete.MapUid = newUid;
concrete.MapID = newMapId;
if (concrete.ChildCount != 0)
{
concrete.UpdateChildMapIdsRecursive(newMapId, newUid, mapPaused, xformQuery, metaQuery, system);
}
}
}
[Obsolete("Use TransformSystem.SetParent() instead")]
public void AttachParent(EntityUid parent)
{

View File

@@ -47,9 +47,10 @@ public record struct Entity<T> : IFluentEntityUid, IAsType<EntityUid>
comp = Comp;
}
public EntityUid AsType() => Owner;
public override int GetHashCode() => Owner.GetHashCode();
public Entity<T?> AsNullable() => new(Owner, Comp);
public EntityUid AsType() => Owner;
}
[NotYamlSerializable]
@@ -118,6 +119,8 @@ public record struct Entity<T1, T2> : IFluentEntityUid, IAsType<EntityUid>
return new Entity<T1>(ent.Owner, ent.Comp1);
}
public override int GetHashCode() => Owner.GetHashCode();
public Entity<T1?, T2?> AsNullable() => new(Owner, Comp1, Comp2);
public EntityUid AsType() => Owner;
}
@@ -223,6 +226,8 @@ public record struct Entity<T1, T2, T3> : IFluentEntityUid, IAsType<EntityUid>
#endregion
public override int GetHashCode() => Owner.GetHashCode();
public Entity<T1?, T2?, T3?> AsNullable() => new(Owner, Comp1, Comp2, Comp3);
public EntityUid AsType() => Owner;
}
@@ -352,6 +357,8 @@ public record struct Entity<T1, T2, T3, T4> : IFluentEntityUid, IAsType<EntityUi
#endregion
public override int GetHashCode() => Owner.GetHashCode();
public Entity<T1?, T2?, T3?, T4?> AsNullable() => new(Owner, Comp1, Comp2, Comp3, Comp4);
public EntityUid AsType() => Owner;
}
@@ -505,6 +512,8 @@ public record struct Entity<T1, T2, T3, T4, T5> : IFluentEntityUid, IAsType<Enti
#endregion
public override int GetHashCode() => Owner.GetHashCode();
public Entity<T1?, T2?, T3?, T4?, T5?> AsNullable() => new(Owner, Comp1, Comp2, Comp3, Comp4, Comp5);
public EntityUid AsType() => Owner;
}
@@ -682,6 +691,8 @@ public record struct Entity<T1, T2, T3, T4, T5, T6> : IFluentEntityUid, IAsType<
#endregion
public override int GetHashCode() => Owner.GetHashCode();
public Entity<T1?, T2?, T3?, T4?, T5?, T6?> AsNullable() => new(Owner, Comp1, Comp2, Comp3, Comp4, Comp5, Comp6);
public EntityUid AsType() => Owner;
}
@@ -883,8 +894,9 @@ public record struct Entity<T1, T2, T3, T4, T5, T6, T7> : IFluentEntityUid, IAsT
#endregion
public override int GetHashCode() => Owner.GetHashCode();
public Entity<T1?, T2?, T3?, T4?, T5?, T6?, T7?> AsNullable() => new(Owner, Comp1, Comp2, Comp3, Comp4, Comp5, Comp6, Comp7);
public EntityUid AsType() => Owner;
}
[NotYamlSerializable]
@@ -1109,5 +1121,7 @@ public record struct Entity<T1, T2, T3, T4, T5, T6, T7, T8> : IFluentEntityUid,
#endregion
public override int GetHashCode() => Owner.GetHashCode();
public Entity<T1?, T2?, T3?, T4?, T5?, T6?, T7?, T8?> AsNullable() => new(Owner, Comp1, Comp2, Comp3, Comp4, Comp5, Comp6, Comp7, Comp8);
public EntityUid AsType() => Owner;
}

View File

@@ -1,104 +0,0 @@
namespace Robust.Shared.GameObjects;
public static class EntityExt
{
/// <summary>
/// Converts an Entity{T} to Entity{T?}, making it compatible with methods that take nullable components.
/// </summary>
/// <typeparam name="T">The component type. Must implement IComponent.</typeparam>
/// <param name="ent">The source entity to convert.</param>
/// <returns>An Entity{T?} with the same owner and component as the source entity.</returns>
/// <example>
/// <code>
/// // Instead of:
/// Entity{MyComponent?} nullable = (ent, ent.Comp);
///
/// // You can write:
/// Entity{MyComponent?} nullable = ent.AsNullable();
/// </code>
/// </example>
public static Entity<T?> AsNullable<T>(this Entity<T> ent) where T : IComponent
{
return new(ent.Owner, ent.Comp);
}
/// <inheritdoc cref="AsNullable{T}" />
public static Entity<T1?, T2?> AsNullable<T1, T2>(this Entity<T1, T2> ent)
where T1 : IComponent
where T2 : IComponent
{
return new(ent.Owner, ent.Comp1, ent.Comp2);
}
/// <inheritdoc cref="AsNullable{T}" />
public static Entity<T1?, T2?, T3?> AsNullable<T1, T2, T3>(this Entity<T1, T2, T3> ent)
where T1 : IComponent
where T2 : IComponent
where T3 : IComponent
{
return new(ent.Owner, ent.Comp1, ent.Comp2, ent.Comp3);
}
/// <inheritdoc cref="AsNullable{T}" />
public static Entity<T1?, T2?, T3?, T4?> AsNullable<T1, T2, T3, T4>(this Entity<T1, T2, T3, T4> ent)
where T1 : IComponent
where T2 : IComponent
where T3 : IComponent
where T4 : IComponent
{
return new(ent.Owner, ent.Comp1, ent.Comp2, ent.Comp3, ent.Comp4);
}
/// <inheritdoc cref="AsNullable{T}" />
public static Entity<T1?, T2?, T3?, T4?, T5?> AsNullable<T1, T2, T3, T4, T5>(this Entity<T1, T2, T3, T4, T5> ent)
where T1 : IComponent
where T2 : IComponent
where T3 : IComponent
where T4 : IComponent
where T5 : IComponent
{
return new(ent.Owner, ent.Comp1, ent.Comp2, ent.Comp3, ent.Comp4, ent.Comp5);
}
/// <inheritdoc cref="AsNullable{T}" />
public static Entity<T1?, T2?, T3?, T4?, T5?, T6?> AsNullable<T1, T2, T3, T4, T5, T6>(
this Entity<T1, T2, T3, T4, T5, T6> ent)
where T1 : IComponent
where T2 : IComponent
where T3 : IComponent
where T4 : IComponent
where T5 : IComponent
where T6 : IComponent
{
return new(ent.Owner, ent.Comp1, ent.Comp2, ent.Comp3, ent.Comp4, ent.Comp5, ent.Comp6);
}
/// <inheritdoc cref="AsNullable{T}" />
public static Entity<T1?, T2?, T3?, T4?, T5?, T6?, T7?> AsNullable<T1, T2, T3, T4, T5, T6, T7>(
this Entity<T1, T2, T3, T4, T5, T6, T7> ent)
where T1 : IComponent
where T2 : IComponent
where T3 : IComponent
where T4 : IComponent
where T5 : IComponent
where T6 : IComponent
where T7 : IComponent
{
return new(ent.Owner, ent.Comp1, ent.Comp2, ent.Comp3, ent.Comp4, ent.Comp5, ent.Comp6, ent.Comp7);
}
/// <inheritdoc cref="AsNullable{T}" />
public static Entity<T1?, T2?, T3?, T4?, T5?, T6?, T7?, T8?> AsNullable<T1, T2, T3, T4, T5, T6, T7, T8>(
this Entity<T1, T2, T3, T4, T5, T6, T7, T8> ent)
where T1 : IComponent
where T2 : IComponent
where T3 : IComponent
where T4 : IComponent
where T5 : IComponent
where T6 : IComponent
where T7 : IComponent
where T8 : IComponent
{
return new(ent.Owner, ent.Comp1, ent.Comp2, ent.Comp3, ent.Comp4, ent.Comp5, ent.Comp6, ent.Comp7, ent.Comp8);
}
}

View File

@@ -595,12 +595,23 @@ namespace Robust.Shared.GameObjects
if (!_deleteSet.Add(component))
{
// already deferred deletion
// Already deferring deletion
DebugTools.Assert(component.LifeStage >= ComponentLifeStage.Stopped);
return;
}
if (component.LifeStage >= ComponentLifeStage.Initialized && component.LifeStage <= ComponentLifeStage.Running)
DebugTools.Assert(component.LifeStage >= ComponentLifeStage.Added);
if (component.LifeStage is >= ComponentLifeStage.Initialized and < ComponentLifeStage.Stopping)
LifeShutdown(uid, component, _componentFactory.GetIndex(component.GetType()));
else if (component.LifeStage == ComponentLifeStage.Added)
{
// The component was added, but never initialized or started. It's kinda weird to add and then
// immediately defer-remove a component, but oh well. Let's just set the life stage directly and not
// raise shutdown events? The removal events will still get called later.
// This is also what LifeShutdown() would also do, albeit behind a DebugAssert.
component.LifeStage = ComponentLifeStage.Stopped;
}
#if EXCEPTION_TOLERANCE
}
catch (Exception e)

View File

@@ -14,8 +14,9 @@ public partial class EntityManager
/// Increases the life stage from <see cref="ComponentLifeStage.PreAdd" /> to <see cref="ComponentLifeStage.Added" />,
/// after raising a <see cref="ComponentAdd"/> event.
/// </summary>
internal void LifeAddToEntity(EntityUid uid, IComponent component, CompIdx idx)
internal void LifeAddToEntity<T>(EntityUid uid, T component, CompIdx idx) where T : IComponent
{
DebugTools.Assert(!_deleteSet.Contains(component));
DebugTools.Assert(component.LifeStage == ComponentLifeStage.PreAdd);
#pragma warning disable CS0618 // Type or member is obsolete
@@ -32,8 +33,9 @@ public partial class EntityManager
/// Increases the life stage from <see cref="ComponentLifeStage.Added" /> to <see cref="ComponentLifeStage.Initialized" />,
/// calling <see cref="Initialize" />.
/// </summary>
internal void LifeInitialize(EntityUid uid, IComponent component, CompIdx idx)
internal void LifeInitialize<T>(EntityUid uid, T component, CompIdx idx) where T : IComponent
{
DebugTools.Assert(!_deleteSet.Contains(component));
DebugTools.Assert(component.LifeStage == ComponentLifeStage.Added);
component.LifeStage = ComponentLifeStage.Initializing;
@@ -45,8 +47,9 @@ public partial class EntityManager
/// Increases the life stage from <see cref="ComponentLifeStage.Initialized" /> to
/// <see cref="ComponentLifeStage.Running" />, calling <see cref="Startup" />.
/// </summary>
internal void LifeStartup(EntityUid uid, IComponent component, CompIdx idx)
internal void LifeStartup<T>(EntityUid uid, T component, CompIdx idx) where T : IComponent
{
DebugTools.Assert(!_deleteSet.Contains(component));
DebugTools.Assert(component.LifeStage == ComponentLifeStage.Initialized);
component.LifeStage = ComponentLifeStage.Starting;
@@ -61,7 +64,7 @@ public partial class EntityManager
/// <remarks>
/// Components are allowed to remove themselves in their own Startup function.
/// </remarks>
internal void LifeShutdown(EntityUid uid, IComponent component, CompIdx idx)
internal void LifeShutdown<T>(EntityUid uid, T component, CompIdx idx) where T : IComponent
{
DebugTools.Assert(component.LifeStage is >= ComponentLifeStage.Initializing and < ComponentLifeStage.Stopping);
@@ -81,7 +84,7 @@ public partial class EntityManager
/// Increases the life stage from <see cref="ComponentLifeStage.Stopped" /> to <see cref="ComponentLifeStage.Deleted" />,
/// calling <see cref="Component.OnRemove" />.
/// </summary>
internal void LifeRemoveFromEntity(EntityUid uid, IComponent component, CompIdx idx)
internal void LifeRemoveFromEntity<T>(EntityUid uid, T component, CompIdx idx) where T : IComponent
{
// can be called at any time after PreAdd, including inside other life stage events.
DebugTools.Assert(component.LifeStage != ComponentLifeStage.PreAdd);

View File

@@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using Robust.Shared.Containers;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
namespace Robust.Shared.GameObjects;
@@ -79,7 +80,7 @@ public partial class EntityManager
throw new InvalidOperationException($"Tried to spawn entity {protoName} on invalid coordinates {coordinates}.");
var entity = CreateEntityUninitialized(protoName, coordinates, overrides, rotation);
InitializeAndStartEntity(entity, coordinates.GetMapId(this));
InitializeAndStartEntity(entity, _xforms.GetMapId(coordinates));
return entity;
}
@@ -100,7 +101,7 @@ public partial class EntityManager
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public EntityUid SpawnAtPosition(string? protoName, EntityCoordinates coordinates, ComponentRegistry? overrides = null)
=> Spawn(protoName, coordinates.ToMap(this, _xforms), overrides);
=> Spawn(protoName, _xforms.ToMapCoordinates(coordinates), overrides);
public bool TrySpawnNextTo(
string? protoName,
@@ -206,4 +207,91 @@ public partial class EntityManager
return uid;
}
#region Prediction
public virtual EntityUid PredictedSpawnAttachedTo(string? protoName, EntityCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default)
{
return SpawnAttachedTo(protoName, coordinates, overrides, rotation);
}
public virtual EntityUid PredictedSpawn(string? protoName = null, ComponentRegistry? overrides = null, bool doMapInit = true)
{
return Spawn(protoName, overrides, doMapInit);
}
public virtual EntityUid PredictedSpawn(string? protoName, MapCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default!)
{
return Spawn(protoName, coordinates, overrides, rotation);
}
public virtual EntityUid PredictedSpawnAtPosition(string? protoName, EntityCoordinates coordinates, ComponentRegistry? overrides = null)
{
return SpawnAtPosition(protoName, coordinates, overrides);
}
public virtual bool PredictedTrySpawnNextTo(
string? protoName,
EntityUid target,
[NotNullWhen(true)] out EntityUid? uid,
TransformComponent? xform = null,
ComponentRegistry? overrides = null)
{
return TrySpawnNextTo(protoName, target, out uid, xform, overrides);
}
public virtual bool PredictedTrySpawnInContainer(
string? protoName,
EntityUid containerUid,
string containerId,
[NotNullWhen(true)] out EntityUid? uid,
ContainerManagerComponent? containerComp = null,
ComponentRegistry? overrides = null)
{
return TrySpawnInContainer(protoName, containerUid, containerId, out uid, containerComp, overrides);
}
public virtual EntityUid PredictedSpawnNextToOrDrop(string? protoName, EntityUid target, TransformComponent? xform = null, ComponentRegistry? overrides = null)
{
return SpawnNextToOrDrop(protoName, target, xform, overrides);
}
public virtual EntityUid PredictedSpawnInContainerOrDrop(
string? protoName,
EntityUid containerUid,
string containerId,
TransformComponent? xform = null,
ContainerManagerComponent? containerComp = null,
ComponentRegistry? overrides = null)
{
return SpawnInContainerOrDrop(protoName, containerUid, containerId, xform, containerComp, overrides);
}
public virtual EntityUid PredictedSpawnInContainerOrDrop(
string? protoName,
EntityUid containerUid,
string containerId,
out bool inserted,
TransformComponent? xform = null,
ContainerManagerComponent? containerComp = null,
ComponentRegistry? overrides = null)
{
return SpawnInContainerOrDrop(protoName,
containerUid,
containerId,
out inserted,
xform,
containerComp,
overrides);
}
/// <summary>
/// Flags an entity as being a predicted spawn and should be deleted when its corresponding tick comes in.
/// </summary>
public virtual void FlagPredicted(Entity<MetaDataComponent?> ent)
{
}
#endregion
}

View File

@@ -48,7 +48,7 @@ namespace Robust.Shared.GameObjects
// I feel like PJB might shed me for putting a system dependency here, but its required for setting entity
// positions on spawn....
private SharedTransformSystem _xforms = default!;
protected SharedTransformSystem _xforms = default!;
private SharedContainerSystem _containers = default!;
public EntityQuery<MetaDataComponent> MetaQuery;
@@ -693,6 +693,36 @@ namespace Robust.Shared.GameObjects
public bool IsQueuedForDeletion(EntityUid uid) => QueuedDeletionsSet.Contains(uid);
/// <inheritdoc />
public virtual void PredictedDeleteEntity(Entity<MetaDataComponent?, TransformComponent?> ent)
{
DeleteEntity(ent.Owner);
}
/// <inheritdoc />
public void PredictedDeleteEntity(Entity<MetaDataComponent?, TransformComponent?>? ent)
{
if (ent == null)
return;
PredictedDeleteEntity(ent.Value);
}
/// <inheritdoc />
public virtual void PredictedQueueDeleteEntity(Entity<MetaDataComponent?, TransformComponent?> ent)
{
QueueDeleteEntity(ent.Owner);
}
/// <inheritdoc />
public virtual void PredictedQueueDeleteEntity(Entity<MetaDataComponent?, TransformComponent?>? ent)
{
if (ent == null)
return;
PredictedQueueDeleteEntity(ent.Value);
}
public bool EntityExists(EntityUid uid)
{
return MetaQuery.HasComponentInternal(uid);

View File

@@ -782,6 +782,34 @@ public partial class EntitySystem
EntityManager.QueueDeleteEntity(uid);
}
/// <inheritdoc cref="IEntityManager.DeleteEntity(EntityUid?)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void PredictedDel(Entity<MetaDataComponent?, TransformComponent?> ent)
{
EntityManager.PredictedDeleteEntity(ent);
}
/// <inheritdoc cref="IEntityManager.DeleteEntity(EntityUid?)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void PredictedDel(Entity<MetaDataComponent?, TransformComponent?>? ent)
{
EntityManager.PredictedDeleteEntity(ent);
}
/// <inheritdoc cref="IEntityManager.QueueDeleteEntity(EntityUid)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void PredictedQueueDel(Entity<MetaDataComponent?, TransformComponent?> ent)
{
EntityManager.PredictedQueueDeleteEntity(ent);
}
/// <inheritdoc cref="IEntityManager.QueueDeleteEntity(EntityUid?)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void PredictedQueueDel(Entity<MetaDataComponent?, TransformComponent?>? ent)
{
EntityManager.PredictedQueueDeleteEntity(ent);
}
/// <inheritdoc cref="IEntityManager.TryQueueDeleteEntity(EntityUid?)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected bool TryQueueDel(EntityUid? uid)
@@ -812,8 +840,8 @@ public partial class EntitySystem
/// <inheritdoc cref="IEntityManager.SpawnAttachedTo" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected EntityUid SpawnAttachedTo(string? prototype, EntityCoordinates coordinates, ComponentRegistry? overrides = null)
=> EntityManager.SpawnAttachedTo(prototype, coordinates, overrides);
protected EntityUid SpawnAttachedTo(string? prototype, EntityCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default)
=> EntityManager.SpawnAttachedTo(prototype, coordinates, overrides, rotation);
/// <inheritdoc cref="IEntityManager.SpawnAtPosition" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -871,6 +899,74 @@ public partial class EntitySystem
#endregion
#region PredictedSpawning
protected void FlagPredicted(Entity<MetaDataComponent?> ent)
{
EntityManager.FlagPredicted(ent);
}
/// <inheritdoc cref="IEntityManager.SpawnAttachedTo" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected EntityUid PredictedSpawnAttachedTo(string? prototype, EntityCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default)
=> EntityManager.PredictedSpawnAttachedTo(prototype, coordinates, overrides, rotation);
/// <inheritdoc cref="IEntityManager.SpawnAtPosition" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected EntityUid PredictedSpawnAtPosition(string? prototype, EntityCoordinates coordinates, ComponentRegistry? overrides = null)
=> EntityManager.PredictedSpawnAtPosition(prototype, coordinates, overrides);
/// <inheritdoc cref="IEntityManager.TrySpawnInContainer" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected bool PredictedTrySpawnInContainer(
string? protoName,
EntityUid containerUid,
string containerId,
[NotNullWhen(true)] out EntityUid? uid,
ContainerManagerComponent? containerComp = null,
ComponentRegistry? overrides = null)
{
return EntityManager.PredictedTrySpawnInContainer(protoName, containerUid, containerId, out uid, containerComp, overrides);
}
/// <inheritdoc cref="IEntityManager.TrySpawnNextTo" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected bool PredictedTrySpawnNextTo(
string? protoName,
EntityUid target,
[NotNullWhen(true)] out EntityUid? uid,
TransformComponent? xform = null,
ComponentRegistry? overrides = null)
{
return EntityManager.PredictedTrySpawnNextTo(protoName, target, out uid, xform, overrides);
}
/// <inheritdoc cref="IEntityManager.SpawnNextToOrDrop" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected EntityUid PredictedSpawnNextToOrDrop(
string? protoName,
EntityUid target,
TransformComponent? xform = null,
ComponentRegistry? overrides = null)
{
return EntityManager.PredictedSpawnNextToOrDrop(protoName, target, xform, overrides);
}
/// <inheritdoc cref="IEntityManager.SpawnInContainerOrDrop" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected EntityUid PredictedSpawnInContainerOrDrop(
string? protoName,
EntityUid containerUid,
string containerId,
TransformComponent? xform = null,
ContainerManagerComponent? container = null,
ComponentRegistry? overrides = null)
{
return EntityManager.PredictedSpawnInContainerOrDrop(protoName, containerUid, containerId, xform, container, overrides);
}
#endregion
#region Utils
/// <summary>

View File

@@ -37,18 +37,22 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc cref="Resolve{TComp}(Robust.Shared.GameObjects.EntityUid,ref TComp?,bool)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected bool Resolve(EntityUid uid, [NotNullWhen(true)] ref MetaDataComponent? component,
protected bool Resolve(
EntityUid uid,
[NotNullWhen(true)] ref MetaDataComponent? component,
bool logMissing = true)
{
return EntityManager.MetaQuery.Resolve(uid, ref component);
return EntityManager.MetaQuery.Resolve(uid, ref component, logMissing);
}
/// <inheritdoc cref="Resolve{TComp}(Robust.Shared.GameObjects.EntityUid,ref TComp?,bool)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected bool Resolve(EntityUid uid, [NotNullWhen(true)] ref TransformComponent? component,
protected bool Resolve(
EntityUid uid,
[NotNullWhen(true)] ref TransformComponent? component,
bool logMissing = true)
{
return EntityManager.TransformQuery.Resolve(uid, ref component);
return EntityManager.TransformQuery.Resolve(uid, ref component, logMissing);
}
/// <summary>

Some files were not shown because too many files have changed in this diff Show More