diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props
index 6fba408d005..61514327453 100644
--- a/MSBuild/Robust.Engine.Version.props
+++ b/MSBuild/Robust.Engine.Version.props
@@ -1,4 +1,4 @@
- 231.1.1
+ 233.0.2
diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md
index c341863d211..2444f05af62 100644
--- a/RELEASE-NOTES.md
+++ b/RELEASE-NOTES.md
@@ -40,10 +40,14 @@ END TEMPLATE-->
### New features
* AddComponent now has an overload for ComponentRegistryEntry.
+* `MarkupNode` is now `IEquatable`. It already supported equality checks, now it implements the interface.
+* Added `Entity` overloads to the following `SharedMapSystem` methods: `GetTileRef`, `GetAnchoredEntities`, `TileIndicesFor`.
+* Added `EntityUid`-only overloads to the following `SharedTransformSystem` methods: `AnchorEntity`, `Unanchor`.
### Bugfixes
-*None yet*
+* Fixed equality checks for `MarkupNode` not properly handling attributes.
+* Fixed `MarkupNode` not having a `GetHashCode()` implementation.
### Other
@@ -54,6 +58,64 @@ END TEMPLATE-->
*None yet*
+## 233.0.2
+
+### Bugfixes
+
+* Fix exceptions in client game state handling for grids. Now they will rely upon the networked fixture data and not try to rebuild in the grid state handler.
+
+
+## 233.0.1
+
+### Bugfixes
+
+* Fix IsHardCollidable component to EntityUid references.
+
+
+## 233.0.0
+
+### Breaking changes
+
+* Made EntityRenamed a broadcast event & added additional args.
+* Made test runs parallelizable.
+* Added a debug assert that other threads aren't touching entities.
+
+### Bugfixes
+
+* Fix some entitylookup method transformations and add more tests.
+* Fix mousehover not updating if new controls showed up under the mouse.
+
+### Internal
+
+* `ClientGameStateManager` now only initialises or starts entities after their parents have already been initialized. There are also some new debug asserts to try ensure that this rule isn't broken elsewhere.
+* Engine version script now supports dashes.
+
+
+## 232.0.0
+
+### Breaking changes
+
+* Obsolete method `AppearanceComponent.TryGetData` is now access-restricted to `SharedAppearanceSystem`; use `SharedAppearanceSystem.TryGetData` instead.
+
+### New features
+
+* Added `SharedAppearanceSystem.AppendData`, which appends non-existing `AppearanceData` from one `AppearanceComponent` to another.
+* Added `AppearanceComponent.AppearanceDataInit`, which can be used to set initial `AppearanceData` entries in .yaml.
+
+### Bugfixes
+
+* Fix BUI interfaces not deep-copying in state handling.
+* Add Robust.Xaml.csproj to the solution to fix some XAML issues.
+
+### Other
+
+* Serialization will now add type tags (`!type:`) for necessary `NodeData` when writing (currently only for `object` nodes).
+
+### Internal
+
+* Added `ObjectSerializer`, which handles serialization of the generic `object` type.
+
+
## 231.1.1
### Bugfixes
diff --git a/Robust.Benchmarks/Physics/BoxStackBenchmark.cs b/Robust.Benchmarks/Physics/BoxStackBenchmark.cs
new file mode 100644
index 00000000000..406264f54b3
--- /dev/null
+++ b/Robust.Benchmarks/Physics/BoxStackBenchmark.cs
@@ -0,0 +1,96 @@
+using System;
+using System.Numerics;
+using BenchmarkDotNet.Attributes;
+using Microsoft.Extensions.Configuration;
+using Robust.Shared.Analyzers;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Map;
+using Robust.Shared.Physics;
+using Robust.Shared.Physics.Collision.Shapes;
+using Robust.Shared.Physics.Components;
+using Robust.Shared.Physics.Dynamics;
+using Robust.Shared.Physics.Systems;
+using Robust.UnitTesting.Server;
+
+namespace Robust.Benchmarks.Physics;
+
+[Virtual]
+[MediumRunJob]
+public class PhysicsBoxStackBenchmark
+{
+ private ISimulation _sim = default!;
+
+ [GlobalSetup]
+ public void Setup()
+ {
+ _sim = RobustServerSimulation.NewSimulation().InitializeInstance();
+
+ var entManager = _sim.Resolve();
+ entManager.System().CreateMap(out var mapId);
+ SetupTumbler(entManager, mapId);
+
+ for (var i = 0; i < 30; i++)
+ {
+ entManager.TickUpdate(0.016f, false);
+ }
+ }
+
+ [Benchmark]
+ public void BoxStack()
+ {
+ var entManager = _sim.Resolve();
+
+ for (var i = 0; i < 10000; i++)
+ {
+ entManager.TickUpdate(0.016f, false);
+ }
+ }
+
+ private void SetupTumbler(IEntityManager entManager, MapId mapId)
+ {
+ var physics = entManager.System();
+ var fixtures = entManager.System();
+
+ var groundUid = entManager.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
+ var ground = entManager.AddComponent(groundUid);
+
+ var horizontal = new EdgeShape(new Vector2(-40, 0), new Vector2(40, 0));
+ fixtures.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 2, 2, true), body: ground);
+
+ var vertical = new EdgeShape(new Vector2(10, 0), new Vector2(10, 10));
+ fixtures.CreateFixture(groundUid, "fix2", new Fixture(vertical, 2, 2, true), body: ground);
+
+ var xs = new[]
+ {
+ 0.0f, -10.0f, -5.0f, 5.0f, 10.0f
+ };
+
+ var columnCount = 1;
+ var rowCount = 15;
+ PolygonShape shape;
+
+ for (var j = 0; j < columnCount; j++)
+ {
+ for (var i = 0; i < rowCount; i++)
+ {
+ var x = 0.0f;
+
+ var boxUid = entManager.SpawnEntity(null,
+ new MapCoordinates(new Vector2(xs[j] + x, 0.55f + 1.1f * i), mapId));
+ var box = entManager.AddComponent(boxUid);
+
+ physics.SetBodyType(boxUid, BodyType.Dynamic, body: box);
+
+ shape = new PolygonShape();
+ shape.SetAsBox(0.5f, 0.5f);
+ physics.SetFixedRotation(boxUid, false, body: box);
+ fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true), body: box);
+
+ physics.WakeBody(boxUid, body: box);
+ physics.SetSleepingAllowed(boxUid, box, false);
+ }
+ }
+
+ physics.WakeBody(groundUid, body: ground);
+ }
+}
diff --git a/Robust.Benchmarks/Physics/CircleStackBenchmark.cs b/Robust.Benchmarks/Physics/CircleStackBenchmark.cs
new file mode 100644
index 00000000000..eb79a6b9a53
--- /dev/null
+++ b/Robust.Benchmarks/Physics/CircleStackBenchmark.cs
@@ -0,0 +1,92 @@
+using System.Numerics;
+using BenchmarkDotNet.Attributes;
+using Robust.Shared.Analyzers;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Map;
+using Robust.Shared.Physics;
+using Robust.Shared.Physics.Collision.Shapes;
+using Robust.Shared.Physics.Components;
+using Robust.Shared.Physics.Dynamics;
+using Robust.Shared.Physics.Systems;
+using Robust.UnitTesting.Server;
+
+namespace Robust.Benchmarks.Physics;
+
+[Virtual]
+public class PhysicsCircleStackBenchmark
+{
+ private ISimulation _sim = default!;
+
+ [GlobalSetup]
+ public void Setup()
+ {
+ _sim = RobustServerSimulation.NewSimulation().InitializeInstance();
+
+ var entManager = _sim.Resolve();
+ entManager.System().CreateMap(out var mapId);
+ SetupTumbler(entManager, mapId);
+
+ for (var i = 0; i < 30; i++)
+ {
+ entManager.TickUpdate(0.016f, false);
+ }
+ }
+
+ [Benchmark]
+ public void CircleStack()
+ {
+ var entManager = _sim.Resolve();
+
+ for (var i = 0; i < 10000; i++)
+ {
+ entManager.TickUpdate(0.016f, false);
+ }
+ }
+
+ private void SetupTumbler(IEntityManager entManager, MapId mapId)
+ {
+ var physics = entManager.System();
+ var fixtures = entManager.System();
+
+ var groundUid = entManager.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
+ var ground = entManager.AddComponent(groundUid);
+
+ var horizontal = new EdgeShape(new Vector2(-40, 0), new Vector2(40, 0));
+ fixtures.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 2, 2, true), body: ground);
+
+ var vertical = new EdgeShape(new Vector2(20, 0), new Vector2(20, 20));
+ fixtures.CreateFixture(groundUid, "fix2", new Fixture(vertical, 2, 2, true), body: ground);
+
+ var xs = new[]
+ {
+ 0.0f, -10.0f, -5.0f, 5.0f, 10.0f
+ };
+
+ var columnCount = 1;
+ var rowCount = 15;
+ PhysShapeCircle shape;
+
+ for (var j = 0; j < columnCount; j++)
+ {
+ for (var i = 0; i < rowCount; i++)
+ {
+ var x = 0.0f;
+
+ var boxUid = entManager.SpawnEntity(null,
+ new MapCoordinates(new Vector2(xs[j] + x, 0.55f + 2.1f * i), mapId));
+ var box = entManager.AddComponent(boxUid);
+
+ physics.SetBodyType(boxUid, BodyType.Dynamic, body: box);
+ shape = new PhysShapeCircle(0.5f);
+ physics.SetFixedRotation(boxUid, false, body: box);
+ // TODO: Need to detect shape and work out if we need to use fixedrotation
+
+ fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true, 5f));
+ physics.WakeBody(boxUid, body: box);
+ physics.SetSleepingAllowed(boxUid, box, false);
+ }
+ }
+
+ physics.WakeBody(groundUid, body: ground);
+ }
+}
diff --git a/Robust.Benchmarks/Physics/PyramidBenchmark.cs b/Robust.Benchmarks/Physics/PyramidBenchmark.cs
new file mode 100644
index 00000000000..505176dfa65
--- /dev/null
+++ b/Robust.Benchmarks/Physics/PyramidBenchmark.cs
@@ -0,0 +1,91 @@
+using System;
+using System.Numerics;
+using BenchmarkDotNet.Attributes;
+using Robust.Shared.Analyzers;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Map;
+using Robust.Shared.Physics;
+using Robust.Shared.Physics.Collision.Shapes;
+using Robust.Shared.Physics.Components;
+using Robust.Shared.Physics.Dynamics;
+using Robust.Shared.Physics.Systems;
+using Robust.UnitTesting.Server;
+
+namespace Robust.Benchmarks.Physics;
+
+[Virtual]
+public class PhysicsPyramidBenchmark
+{
+ private ISimulation _sim = default!;
+
+ [GlobalSetup]
+ public void Setup()
+ {
+ _sim = RobustServerSimulation.NewSimulation().InitializeInstance();
+
+ var entManager = _sim.Resolve();
+ entManager.System().CreateMap(out var mapId);
+ SetupTumbler(entManager, mapId);
+
+ for (var i = 0; i < 300; i++)
+ {
+ entManager.TickUpdate(0.016f, false);
+ }
+ }
+
+ [Benchmark]
+ public void Pyramid()
+ {
+ var entManager = _sim.Resolve();
+
+ for (var i = 0; i < 5000; i++)
+ {
+ entManager.TickUpdate(0.016f, false);
+ }
+ }
+
+ private void SetupTumbler(IEntityManager entManager, MapId mapId)
+ {
+ const byte count = 20;
+
+ // Setup ground
+ var physics = entManager.System();
+ var fixtures = entManager.System();
+ var groundUid = entManager.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
+ var ground = entManager.AddComponent(groundUid);
+
+ var horizontal = new EdgeShape(new Vector2(40, 0), new Vector2(-40, 0));
+ fixtures.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 2, 2, true), body: ground);
+ physics.WakeBody(groundUid, body: ground);
+
+ // Setup boxes
+ float a = 0.5f;
+ PolygonShape shape = new();
+ shape.SetAsBox(a, a);
+
+ var x = new Vector2(-7.0f, 0.75f);
+ Vector2 y;
+ Vector2 deltaX = new Vector2(0.5625f, 1.25f);
+ Vector2 deltaY = new Vector2(1.125f, 0.0f);
+
+ for (var i = 0; i < count; ++i)
+ {
+ y = x;
+
+ for (var j = i; j < count; ++j)
+ {
+ var boxUid = entManager.SpawnEntity(null, new MapCoordinates(y, mapId));
+ var box = entManager.AddComponent(boxUid);
+ physics.SetBodyType(boxUid, BodyType.Dynamic, body: box);
+
+ fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true, 5f), body: box);
+ y += deltaY;
+
+ physics.WakeBody(boxUid, body: box);
+ physics.SetSleepingAllowed(boxUid, box, false);
+ }
+
+ x += deltaX;
+ }
+ }
+}
diff --git a/Robust.Benchmarks/Physics/TumblerBenchmark.cs b/Robust.Benchmarks/Physics/TumblerBenchmark.cs
new file mode 100644
index 00000000000..f2e44185be6
--- /dev/null
+++ b/Robust.Benchmarks/Physics/TumblerBenchmark.cs
@@ -0,0 +1,105 @@
+using System;
+using System.Numerics;
+using BenchmarkDotNet.Attributes;
+using Robust.Shared.Analyzers;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Map;
+using Robust.Shared.Physics;
+using Robust.Shared.Physics.Collision.Shapes;
+using Robust.Shared.Physics.Components;
+using Robust.Shared.Physics.Dynamics;
+using Robust.Shared.Physics.Systems;
+using Robust.UnitTesting.Server;
+
+namespace Robust.Benchmarks.Physics;
+
+[Virtual]
+public class PhysicsTumblerBenchmark
+{
+ private ISimulation _sim = default!;
+
+ [GlobalSetup]
+ public void Setup()
+ {
+ _sim = RobustServerSimulation.NewSimulation().InitializeInstance();
+
+ var entManager = _sim.Resolve();
+ var physics = entManager.System();
+ var fixtures = entManager.System();
+ entManager.System().CreateMap(out var mapId);
+ SetupTumbler(entManager, mapId);
+
+ for (var i = 0; i < 800; i++)
+ {
+ entManager.TickUpdate(0.016f, false);
+ var boxUid = entManager.SpawnEntity(null, new MapCoordinates(0f, 10f, mapId));
+ var box = entManager.AddComponent(boxUid);
+ physics.SetBodyType(boxUid, BodyType.Dynamic, body: box);
+ physics.SetFixedRotation(boxUid, false, body: box);
+ var shape = new PolygonShape();
+ shape.SetAsBox(0.125f, 0.125f);
+ fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true, 0.0625f), body: box);
+ physics.WakeBody(boxUid, body: box);
+ physics.SetSleepingAllowed(boxUid, box, false);
+ }
+ }
+
+ [Benchmark]
+ public void Tumbler()
+ {
+ var entManager = _sim.Resolve();
+
+ for (var i = 0; i < 5000; i++)
+ {
+ entManager.TickUpdate(0.016f, false);
+ }
+ }
+
+ private void SetupTumbler(IEntityManager entManager, MapId mapId)
+ {
+ var physics = entManager.System();
+ var fixtures = entManager.System();
+ var joints = entManager.System();
+
+ var groundUid = entManager.SpawnEntity(null, new MapCoordinates(0f, 0f, mapId));
+ var ground = entManager.AddComponent(groundUid);
+ // Due to lookup changes fixtureless bodies are invalid, so
+ var cShape = new PhysShapeCircle(1f);
+ fixtures.CreateFixture(groundUid, "fix1", new Fixture(cShape, 0, 0, false));
+
+ var bodyUid = entManager.SpawnEntity(null, new MapCoordinates(0f, 10f, mapId));
+ var body = entManager.AddComponent(bodyUid);
+
+ physics.SetBodyType(bodyUid, BodyType.Dynamic, body: body);
+ physics.SetSleepingAllowed(bodyUid, body, false);
+ physics.SetFixedRotation(bodyUid, false, body: body);
+
+
+ // TODO: Box2D just deref, bleh shape structs someday
+ var shape1 = new PolygonShape();
+ shape1.SetAsBox(0.5f, 10.0f, new Vector2(10.0f, 0.0f), 0.0f);
+ fixtures.CreateFixture(bodyUid, "fix1", new Fixture(shape1, 2, 0, true, 20f));
+
+ var shape2 = new PolygonShape();
+ shape2.SetAsBox(0.5f, 10.0f, new Vector2(-10.0f, 0.0f), 0f);
+ fixtures.CreateFixture(bodyUid, "fix2", new Fixture(shape2, 2, 0, true, 20f));
+
+ var shape3 = new PolygonShape();
+ shape3.SetAsBox(10.0f, 0.5f, new Vector2(0.0f, 10.0f), 0f);
+ fixtures.CreateFixture(bodyUid, "fix3", new Fixture(shape3, 2, 0, true, 20f));
+
+ var shape4 = new PolygonShape();
+ shape4.SetAsBox(10.0f, 0.5f, new Vector2(0.0f, -10.0f), 0f);
+ fixtures.CreateFixture(bodyUid, "fix4", new Fixture(shape4, 2, 0, true, 20f));
+
+ physics.WakeBody(groundUid, body: ground);
+ physics.WakeBody(bodyUid, body: body);
+ var revolute = joints.CreateRevoluteJoint(groundUid, bodyUid);
+ revolute.LocalAnchorA = new Vector2(0f, 10f);
+ revolute.LocalAnchorB = new Vector2(0f, 0f);
+ revolute.ReferenceAngle = 0f;
+ revolute.MotorSpeed = 0.05f * MathF.PI;
+ revolute.MaxMotorTorque = 100000000f;
+ revolute.EnableMotor = true;
+ }
+}
diff --git a/Robust.Client/GameObjects/EntitySystems/ContainerSystem.cs b/Robust.Client/GameObjects/EntitySystems/ContainerSystem.cs
index 46ec41efa53..d646d34541a 100644
--- a/Robust.Client/GameObjects/EntitySystems/ContainerSystem.cs
+++ b/Robust.Client/GameObjects/EntitySystems/ContainerSystem.cs
@@ -17,7 +17,6 @@ namespace Robust.Client.GameObjects
{
public sealed class ContainerSystem : SharedContainerSystem
{
- [Dependency] private readonly INetManager _netMan = default!;
[Dependency] private readonly IRobustSerializer _serializer = default!;
[Dependency] private readonly IDynamicTypeFactoryInternal _dynFactory = default!;
[Dependency] private readonly PointLightSystem _lightSys = default!;
diff --git a/Robust.Client/GameObjects/EntitySystems/GridChunkBoundsDebugSystem.cs b/Robust.Client/GameObjects/EntitySystems/GridChunkBoundsDebugSystem.cs
index 0472ec6f283..15d469ef76f 100644
--- a/Robust.Client/GameObjects/EntitySystems/GridChunkBoundsDebugSystem.cs
+++ b/Robust.Client/GameObjects/EntitySystems/GridChunkBoundsDebugSystem.cs
@@ -82,6 +82,7 @@ protected internal override void Draw(in OverlayDrawArgs args)
var viewport = args.WorldBounds;
var worldHandle = args.WorldHandle;
+ var fixturesQuery = _entityManager.GetEntityQuery();
_grids.Clear();
_mapManager.FindGridsIntersecting(currentMap, viewport, ref _grids);
foreach (var grid in _grids)
@@ -89,13 +90,15 @@ protected internal override void Draw(in OverlayDrawArgs args)
var worldMatrix = _transformSystem.GetWorldMatrix(grid);
worldHandle.SetTransform(worldMatrix);
var transform = new Transform(Vector2.Zero, Angle.Zero);
+ var fixtures = fixturesQuery.Comp(grid.Owner);
var chunkEnumerator = _mapSystem.GetMapChunks(grid.Owner, grid.Comp, viewport);
while (chunkEnumerator.MoveNext(out var chunk))
{
- foreach (var fixture in chunk.Fixtures.Values)
+ foreach (var id in chunk.Fixtures)
{
+ var fixture = fixtures.Fixtures[id];
var poly = (PolygonShape) fixture.Shape;
var verts = new Vector2[poly.VertexCount];
diff --git a/Robust.Client/GameObjects/EntitySystems/InputSystem.cs b/Robust.Client/GameObjects/EntitySystems/InputSystem.cs
index 8bc5cca0d3e..2ec62ceda42 100644
--- a/Robust.Client/GameObjects/EntitySystems/InputSystem.cs
+++ b/Robust.Client/GameObjects/EntitySystems/InputSystem.cs
@@ -20,7 +20,6 @@ namespace Robust.Client.GameObjects
///
public sealed class InputSystem : SharedInputSystem, IPostInjectInit
{
- [Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IClientGameStateManager _stateManager = default!;
diff --git a/Robust.Client/GameStates/ClientGameStateManager.cs b/Robust.Client/GameStates/ClientGameStateManager.cs
index f1d01566878..30240d7abb5 100644
--- a/Robust.Client/GameStates/ClientGameStateManager.cs
+++ b/Robust.Client/GameStates/ClientGameStateManager.cs
@@ -960,7 +960,7 @@ public IEnumerable ApplyGameState(GameState curState, GameState? next
// Initialize and start the newly created entities.
if (_toCreate.Count > 0)
- InitializeAndStart(_toCreate);
+ InitializeAndStart(_toCreate, metas, xforms);
_prof.WriteValue("State Size", ProfData.Int32(curSpan.Length));
_prof.WriteValue("Entered PVS", ProfData.Int32(enteringPvs));
@@ -1188,7 +1188,10 @@ private void Detach(GameTick maxTick,
}
}
- private void InitializeAndStart(Dictionary toCreate)
+ private void InitializeAndStart(
+ Dictionary toCreate,
+ EntityQuery metas,
+ EntityQuery xforms)
{
_toStart.Clear();
@@ -1197,22 +1200,8 @@ private void InitializeAndStart(Dictionary toCreate)
EntityUid entity = default;
foreach (var netEntity in toCreate.Keys)
{
- try
- {
- (entity, var meta) = _entityManager.GetEntityData(netEntity);
- _entities.InitializeEntity(entity, meta);
- _toStart.Add((entity, netEntity));
- }
- catch (Exception e)
- {
- _sawmill.Error($"Server entity threw in Init: nent={netEntity}, ent={_entities.ToPrettyString(entity)}");
- _runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(InitializeAndStart)}");
- _toCreate.Remove(netEntity);
- _brokenEnts.Add(entity);
-#if !EXCEPTION_TOLERANCE
- throw;
-#endif
- }
+ (entity, var meta) = _entityManager.GetEntityData(netEntity);
+ InitializeRecursive(entity, meta, metas, xforms);
}
}
@@ -1244,6 +1233,44 @@ private void InitializeAndStart(Dictionary toCreate)
_brokenEnts.Clear();
}
+ private void InitializeRecursive(
+ EntityUid entity,
+ MetaDataComponent meta,
+ EntityQuery metas,
+ EntityQuery xforms)
+ {
+ var xform = xforms.GetComponent(entity);
+ if (xform.ParentUid is {Valid: true} parent)
+ {
+ var parentMeta = metas.GetComponent(parent);
+ if (parentMeta.EntityLifeStage < EntityLifeStage.Initialized)
+ InitializeRecursive(parent, parentMeta, metas, xforms);
+ }
+
+ if (meta.EntityLifeStage >= EntityLifeStage.Initialized)
+ {
+ // Was probably already initialized because one of its children appeared earlier in the list.
+ DebugTools.AssertEqual(_toStart.Count(x => x.Item1 == entity), 1);
+ return;
+ }
+
+ try
+ {
+ _entities.InitializeEntity(entity, meta);
+ _toStart.Add((entity, meta.NetEntity));
+ }
+ catch (Exception e)
+ {
+ _sawmill.Error($"Server entity threw in Init: nent={meta.NetEntity}, ent={_entities.ToPrettyString(entity)}");
+ _runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(InitializeAndStart)}");
+ _toCreate.Remove(meta.NetEntity);
+ _brokenEnts.Add(entity);
+#if !EXCEPTION_TOLERANCE
+ throw;
+#endif
+ }
+ }
+
private void HandleEntityState(EntityUid uid, NetEntity netEntity, MetaDataComponent meta, IEventBus bus, EntityState? curState,
EntityState? nextState, GameTick lastApplied, GameTick toTick, bool enteringPvs)
{
diff --git a/Robust.Client/GameStates/NetInterpOverlay.cs b/Robust.Client/GameStates/NetInterpOverlay.cs
index 1dd3d7cef38..b1cbc02b8ca 100644
--- a/Robust.Client/GameStates/NetInterpOverlay.cs
+++ b/Robust.Client/GameStates/NetInterpOverlay.cs
@@ -15,7 +15,6 @@ internal sealed class NetInterpOverlay : Overlay
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
- [Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private readonly EntityLookupSystem _lookup;
diff --git a/Robust.Client/Graphics/Clyde/Clyde.cs b/Robust.Client/Graphics/Clyde/Clyde.cs
index a1e2484c441..736116e5abc 100644
--- a/Robust.Client/Graphics/Clyde/Clyde.cs
+++ b/Robust.Client/Graphics/Clyde/Clyde.cs
@@ -32,7 +32,6 @@ namespace Robust.Client.Graphics.Clyde
internal sealed partial class Clyde : IClydeInternal, IPostInjectInit, IEntityEventSubscriber
{
[Dependency] private readonly IClydeTileDefinitionManager _tileDefinitionManager = default!;
- [Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly ILightManager _lightManager = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
diff --git a/Robust.Client/Replays/Loading/ReplayLoadManager.Checkpoints.cs b/Robust.Client/Replays/Loading/ReplayLoadManager.Checkpoints.cs
index ec3b9f8c02e..656f5b80a25 100644
--- a/Robust.Client/Replays/Loading/ReplayLoadManager.Checkpoints.cs
+++ b/Robust.Client/Replays/Loading/ReplayLoadManager.Checkpoints.cs
@@ -23,7 +23,7 @@ public sealed partial class ReplayLoadManager
// Scratch data used by UpdateEntityStates.
// Avoids copying changes for every change to an entity between checkpoints, instead copies once per checkpoint on
// first change. We can also use this to avoid building a dictionary of ComponentChange inside the inner loop.
- private class UpdateScratchData
+ private sealed class UpdateScratchData
{
public Dictionary Changes;
public EntityState lastChange;
diff --git a/Robust.Client/UserInterface/Controllers/Implementations/EntitySpawningUIController.cs b/Robust.Client/UserInterface/Controllers/Implementations/EntitySpawningUIController.cs
index 66b11f6ffa1..70eab2f376e 100644
--- a/Robust.Client/UserInterface/Controllers/Implementations/EntitySpawningUIController.cs
+++ b/Robust.Client/UserInterface/Controllers/Implementations/EntitySpawningUIController.cs
@@ -21,7 +21,6 @@ public sealed class EntitySpawningUIController : UIController
{
[Dependency] private readonly IPlacementManager _placement = default!;
[Dependency] private readonly IPrototypeManager _prototypes = default!;
- [Dependency] private readonly IResourceCache _resources = default!;
private EntitySpawnWindow? _window;
private readonly List _shownEntities = new();
diff --git a/Robust.Client/UserInterface/UserInterfaceManager.Input.cs b/Robust.Client/UserInterface/UserInterfaceManager.Input.cs
index a82b4c2a133..2b647131fc0 100644
--- a/Robust.Client/UserInterface/UserInterfaceManager.Input.cs
+++ b/Robust.Client/UserInterface/UserInterfaceManager.Input.cs
@@ -148,7 +148,7 @@ public void MouseMove(MouseMoveEventArgs mouseMoveEventArgs)
var newHovered = MouseGetControl(mouseMoveEventArgs.Position);
SetHovered(newHovered);
- var target = ControlFocused ?? newHovered;
+ var target = ControlFocused ?? CurrentlyHovered;
if (target != null)
{
var pos = mouseMoveEventArgs.Position.Position;
@@ -164,7 +164,7 @@ public void MouseMove(MouseMoveEventArgs mouseMoveEventArgs)
public void UpdateHovered()
{
- var ctrl = MouseGetControl(_inputManager.MouseScreenPosition);
+ var ctrl = MouseGetControl(_inputManager.MouseScreenPosition);
SetHovered(ctrl);
}
diff --git a/Robust.Client/UserInterface/UserInterfaceManager.cs b/Robust.Client/UserInterface/UserInterfaceManager.cs
index fd4bc38e640..af7c1eb6afe 100644
--- a/Robust.Client/UserInterface/UserInterfaceManager.cs
+++ b/Robust.Client/UserInterface/UserInterfaceManager.cs
@@ -215,6 +215,9 @@ public void FrameUpdate(FrameEventArgs args)
{
using (_prof.Group("Update"))
{
+ // Update hovered. Can't rely upon mouse movement due to New controls potentially coming up.
+ UpdateHovered();
+
foreach (var root in _roots)
{
CheckRootUIScaleUpdate(root);
diff --git a/Robust.Server/Console/Commands/TestbedCommand.cs b/Robust.Server/Console/Commands/TestbedCommand.cs
deleted file mode 100644
index 37a3f8c44c3..00000000000
--- a/Robust.Server/Console/Commands/TestbedCommand.cs
+++ /dev/null
@@ -1,335 +0,0 @@
-// MIT License
-
-// Copyright (c) 2019 Erin Catto
-
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
-
-
-using System;
-using System.Numerics;
-using Robust.Server.Player;
-using Robust.Shared.Console;
-using Robust.Shared.GameObjects;
-using Robust.Shared.IoC;
-using Robust.Shared.Map;
-using Robust.Shared.Maths;
-using Robust.Shared.Physics;
-using Robust.Shared.Physics.Collision.Shapes;
-using Robust.Shared.Physics.Components;
-using Robust.Shared.Physics.Controllers;
-using Robust.Shared.Physics.Dynamics;
-using Robust.Shared.Physics.Dynamics.Joints;
-using Robust.Shared.Physics.Systems;
-using Robust.Shared.Timing;
-
-namespace Robust.Server.Console.Commands
-{
- /*
- * I didn't use blueprints because this is way easier to iterate upon as I can shit out testbed upon testbed on new maps
- * and never have to leave my debugger.
- */
-
- ///
- /// Copies of Box2D's physics testbed for debugging.
- ///
- public sealed class TestbedCommand : LocalizedCommands
- {
- [Dependency] private readonly IEntityManager _ent = default!;
- [Dependency] private readonly IMapManager _map = default!;
-
- public override string Command => "testbed";
-
- public override void Execute(IConsoleShell shell, string argStr, string[] args)
- {
- if (args.Length != 2)
- {
- shell.WriteError("Require 2 args for testbed!");
- return;
- }
-
- if (!int.TryParse(args[0], out var mapInt))
- {
- shell.WriteError($"Unable to parse map {args[0]}");
- return;
- }
-
- var mapId = new MapId(mapInt);
- if (!_map.MapExists(mapId))
- {
- shell.WriteError($"map {args[0]} does not exist");
- return;
- }
-
- if (shell.Player == null)
- {
- shell.WriteError("No player found");
- return;
- }
-
- Action testbed;
- SetupPlayer(mapId, shell);
-
- switch (args[1])
- {
- case "boxstack":
- testbed = () => CreateBoxStack(mapId);
- break;
- case "circlestack":
- testbed = () => CreateCircleStack(mapId);
- break;
- case "pyramid":
- testbed = () => CreatePyramid(mapId);
- break;
- case "tumbler":
- testbed = () => CreateTumbler(mapId);
- break;
- default:
- shell.WriteError($"testbed {args[0]} not found!");
- return;
- }
-
- Timer.Spawn(1000, () =>
- {
- if (!_map.MapExists(mapId)) return;
- testbed();
- });
-
- shell.WriteLine($"Testbed on map {mapId}");
- }
-
- private void SetupPlayer(MapId mapId, IConsoleShell shell)
- {
- _map.SetMapPaused(mapId, false);
- var mapUid = _map.GetMapEntityIdOrThrow(mapId);
- _ent.System().SetGravity(mapUid, new Vector2(0, -9.8f));
-
- shell.ExecuteCommand("aghost");
- shell.ExecuteCommand($"tp 0 0 {mapId}");
- shell.RemoteExecuteCommand($"physics shapes");
-
- return;
- }
-
- private void CreateBoxStack(MapId mapId)
- {
- var physics = _ent.System();
- var fixtures = _ent.System();
-
- var groundUid = _ent.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
- var ground = _ent.AddComponent(groundUid);
-
- var horizontal = new EdgeShape(new Vector2(-40, 0), new Vector2(40, 0));
- fixtures.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 2, 2, true), body: ground);
-
- var vertical = new EdgeShape(new Vector2(10, 0), new Vector2(10, 10));
- fixtures.CreateFixture(groundUid, "fix2", new Fixture(vertical, 2, 2, true), body: ground);
-
- var xs = new[]
- {
- 0.0f, -10.0f, -5.0f, 5.0f, 10.0f
- };
-
- var columnCount = 1;
- var rowCount = 15;
- PolygonShape shape;
-
- for (var j = 0; j < columnCount; j++)
- {
- for (var i = 0; i < rowCount; i++)
- {
- var x = 0.0f;
-
- var boxUid = _ent.SpawnEntity(null,
- new MapCoordinates(new Vector2(xs[j] + x, 0.55f + 1.1f * i), mapId));
- var box = _ent.AddComponent(boxUid);
-
- physics.SetBodyType(boxUid, BodyType.Dynamic, body: box);
-
- shape = new PolygonShape();
- shape.SetAsBox(0.5f, 0.5f);
- physics.SetFixedRotation(boxUid, false, body: box);
- fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true), body: box);
-
- physics.WakeBody(boxUid, body: box);
- }
- }
-
- physics.WakeBody(groundUid, body: ground);
- }
-
- private void CreateCircleStack(MapId mapId)
- {
- var physics = _ent.System();
- var fixtures = _ent.System();
-
- var groundUid = _ent.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
- var ground = _ent.AddComponent(groundUid);
-
- var horizontal = new EdgeShape(new Vector2(-40, 0), new Vector2(40, 0));
- fixtures.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 2, 2, true), body: ground);
-
- var vertical = new EdgeShape(new Vector2(20, 0), new Vector2(20, 20));
- fixtures.CreateFixture(groundUid, "fix2", new Fixture(vertical, 2, 2, true), body: ground);
-
- var xs = new[]
- {
- 0.0f, -10.0f, -5.0f, 5.0f, 10.0f
- };
-
- var columnCount = 1;
- var rowCount = 15;
- PhysShapeCircle shape;
-
- for (var j = 0; j < columnCount; j++)
- {
- for (var i = 0; i < rowCount; i++)
- {
- var x = 0.0f;
-
- var boxUid = _ent.SpawnEntity(null,
- new MapCoordinates(new Vector2(xs[j] + x, 0.55f + 2.1f * i), mapId));
- var box = _ent.AddComponent(boxUid);
-
- physics.SetBodyType(boxUid, BodyType.Dynamic, body: box);
- shape = new PhysShapeCircle(0.5f);
- physics.SetFixedRotation(boxUid, false, body: box);
- // TODO: Need to detect shape and work out if we need to use fixedrotation
-
- fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true, 5f));
- physics.WakeBody(boxUid, body: box);
- }
- }
-
- physics.WakeBody(groundUid, body: ground);
- }
-
- private void CreatePyramid(MapId mapId)
- {
- const byte count = 20;
-
- // Setup ground
- var physics = _ent.System();
- var fixtures = _ent.System();
- var groundUid = _ent.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
- var ground = _ent.AddComponent(groundUid);
-
- var horizontal = new EdgeShape(new Vector2(40, 0), new Vector2(-40, 0));
- fixtures.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 2, 2, true), body: ground);
- physics.WakeBody(groundUid, body: ground);
-
- // Setup boxes
- float a = 0.5f;
- PolygonShape shape = new();
- shape.SetAsBox(a, a);
-
- var x = new Vector2(-7.0f, 0.75f);
- Vector2 y;
- Vector2 deltaX = new Vector2(0.5625f, 1.25f);
- Vector2 deltaY = new Vector2(1.125f, 0.0f);
-
- for (var i = 0; i < count; ++i)
- {
- y = x;
-
- for (var j = i; j < count; ++j)
- {
- var boxUid = _ent.SpawnEntity(null, new MapCoordinates(y, mapId));
- var box = _ent.AddComponent(boxUid);
- physics.SetBodyType(boxUid, BodyType.Dynamic, body: box);
-
- fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true, 5f), body: box);
- y += deltaY;
-
- physics.WakeBody(boxUid, body: box);
- }
-
- x += deltaX;
- }
- }
-
- private void CreateTumbler(MapId mapId)
- {
- var physics = _ent.System();
- var fixtures = _ent.System();
- var joints = _ent.System();
-
- var groundUid = _ent.SpawnEntity(null, new MapCoordinates(0f, 0f, mapId));
- var ground = _ent.AddComponent(groundUid);
- // Due to lookup changes fixtureless bodies are invalid, so
- var cShape = new PhysShapeCircle(1f);
- fixtures.CreateFixture(groundUid, "fix1", new Fixture(cShape, 0, 0, false));
-
- var bodyUid = _ent.SpawnEntity(null, new MapCoordinates(0f, 10f, mapId));
- var body = _ent.AddComponent(bodyUid);
-
- physics.SetBodyType(bodyUid, BodyType.Dynamic, body: body);
- physics.SetSleepingAllowed(bodyUid, body, false);
- physics.SetFixedRotation(bodyUid, false, body: body);
-
-
- // TODO: Box2D just deref, bleh shape structs someday
- var shape1 = new PolygonShape();
- shape1.SetAsBox(0.5f, 10.0f, new Vector2(10.0f, 0.0f), 0.0f);
- fixtures.CreateFixture(bodyUid, "fix1", new Fixture(shape1, 2, 0, true, 20f));
-
- var shape2 = new PolygonShape();
- shape2.SetAsBox(0.5f, 10.0f, new Vector2(-10.0f, 0.0f), 0f);
- fixtures.CreateFixture(bodyUid, "fix2", new Fixture(shape2, 2, 0, true, 20f));
-
- var shape3 = new PolygonShape();
- shape3.SetAsBox(10.0f, 0.5f, new Vector2(0.0f, 10.0f), 0f);
- fixtures.CreateFixture(bodyUid, "fix3", new Fixture(shape3, 2, 0, true, 20f));
-
- var shape4 = new PolygonShape();
- shape4.SetAsBox(10.0f, 0.5f, new Vector2(0.0f, -10.0f), 0f);
- fixtures.CreateFixture(bodyUid, "fix4", new Fixture(shape4, 2, 0, true, 20f));
-
- physics.WakeBody(groundUid, body: ground);
- physics.WakeBody(bodyUid, body: body);
- var revolute = joints.CreateRevoluteJoint(groundUid, bodyUid);
- revolute.LocalAnchorA = new Vector2(0f, 10f);
- revolute.LocalAnchorB = new Vector2(0f, 0f);
- revolute.ReferenceAngle = 0f;
- revolute.MotorSpeed = 0.05f * MathF.PI;
- revolute.MaxMotorTorque = 100000000f;
- revolute.EnableMotor = true;
-
- // Box2D has this as 800 which is jesus christo.
- // Wouldn't recommend higher than 100 in debug and higher than 300 on release unless
- // you really want a profile.
- var count = 300;
-
- for (var i = 0; i < count; i++)
- {
- Timer.Spawn(i * 20, () =>
- {
- if (!_map.MapExists(mapId)) return;
- var boxUid = _ent.SpawnEntity(null, new MapCoordinates(0f, 10f, mapId));
- var box = _ent.AddComponent(boxUid);
- physics.SetBodyType(boxUid, BodyType.Dynamic, body: box);
- physics.SetFixedRotation(boxUid, false, body: box);
- var shape = new PolygonShape();
- shape.SetAsBox(0.125f, 0.125f);
- fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true, 0.0625f), body: box);
- physics.WakeBody(boxUid, body: box);
- });
- }
- }
- }
-}
diff --git a/Robust.Server/GameObjects/ServerEntityManager.cs b/Robust.Server/GameObjects/ServerEntityManager.cs
index 000cbc6a2e9..ead2ee7e148 100644
--- a/Robust.Server/GameObjects/ServerEntityManager.cs
+++ b/Robust.Server/GameObjects/ServerEntityManager.cs
@@ -81,6 +81,7 @@ void IServerEntityManagerInternal.FinishEntityInitialization(EntityUid entity, M
InitializeEntity(entity, meta);
}
+ [Obsolete("Use StartEntity")]
void IServerEntityManagerInternal.FinishEntityStartup(EntityUid entity)
{
StartEntity(entity);
diff --git a/Robust.Server/Toolshed/Commands/Players/PlayersCommand.cs b/Robust.Server/Toolshed/Commands/Players/PlayersCommand.cs
index 6ff6c1040a4..6b82b005644 100644
--- a/Robust.Server/Toolshed/Commands/Players/PlayersCommand.cs
+++ b/Robust.Server/Toolshed/Commands/Players/PlayersCommand.cs
@@ -81,7 +81,7 @@ public record struct NoSuchPlayerError(string Username) : IConError
{
public FormattedMessage DescribeInner()
{
- return FormattedMessage.FromMarkup($"No player with the username/GUID {Username} could be found.");
+ return FormattedMessage.FromUnformatted($"No player with the username/GUID {Username} could be found.");
}
public string? Expression { get; set; }
diff --git a/Robust.Shared/Containers/ContainerManagerComponent.cs b/Robust.Shared/Containers/ContainerManagerComponent.cs
index b96ac472b3d..e86e266a42b 100644
--- a/Robust.Shared/Containers/ContainerManagerComponent.cs
+++ b/Robust.Shared/Containers/ContainerManagerComponent.cs
@@ -19,7 +19,6 @@ namespace Robust.Shared.Containers
[RegisterComponent, ComponentProtoName("ContainerContainer")]
public sealed partial class ContainerManagerComponent : Component, ISerializationHooks
{
- [Dependency] private readonly IDynamicTypeFactoryInternal _dynFactory = default!;
[Dependency] private readonly IEntityManager _entMan = default!;
[DataField("containers")]
diff --git a/Robust.Shared/GameObjects/Components/Appearance/AppearanceComponent.cs b/Robust.Shared/GameObjects/Components/Appearance/AppearanceComponent.cs
index 8816d055227..daef024d494 100644
--- a/Robust.Shared/GameObjects/Components/Appearance/AppearanceComponent.cs
+++ b/Robust.Shared/GameObjects/Components/Appearance/AppearanceComponent.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.GameStates;
+using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects;
@@ -15,6 +16,7 @@ namespace Robust.Shared.GameObjects;
/// Visualization works client side with derivatives of the VisualizerSystem class and corresponding components.
///
[RegisterComponent, NetworkedComponent]
+[Access(typeof(SharedAppearanceSystem))]
public sealed partial class AppearanceComponent : Component
{
///
@@ -32,6 +34,19 @@ public sealed partial class AppearanceComponent : Component
[ViewVariables] internal Dictionary AppearanceData = new();
+ private Dictionary? _appearanceDataInit;
+
+ ///
+ /// Sets starting values for AppearanceData.
+ ///
+ ///
+ /// Should only be filled in via prototype .yaml; subsequent data must be set via SharedAppearanceSystem.SetData().
+ ///
+ [DataField(readOnly: true)] public Dictionary? AppearanceDataInit {
+ get { return _appearanceDataInit; }
+ set { AppearanceData = value ?? AppearanceData; _appearanceDataInit = value; }
+ }
+
[Obsolete("Use SharedAppearanceSystem instead")]
public bool TryGetData(Enum key, [NotNullWhen(true)] out T data)
{
diff --git a/Robust.Shared/GameObjects/Components/UserInterface/UserInterfaceComponent.cs b/Robust.Shared/GameObjects/Components/UserInterface/UserInterfaceComponent.cs
index de5a3f3bb1a..5e5efed8f76 100644
--- a/Robust.Shared/GameObjects/Components/UserInterface/UserInterfaceComponent.cs
+++ b/Robust.Shared/GameObjects/Components/UserInterface/UserInterfaceComponent.cs
@@ -69,9 +69,11 @@ public sealed partial class InterfaceData
[DataField]
public bool RequireInputValidation = true;
- public InterfaceData(string type)
+ public InterfaceData(InterfaceData data)
{
- ClientType = type;
+ ClientType = data.ClientType;
+ InteractionRange = data.InteractionRange;
+ RequireInputValidation = data.RequireInputValidation;
}
}
diff --git a/Robust.Shared/GameObjects/EntityManager.Components.cs b/Robust.Shared/GameObjects/EntityManager.Components.cs
index 29e370523f0..2ab8298cd4e 100644
--- a/Robust.Shared/GameObjects/EntityManager.Components.cs
+++ b/Robust.Shared/GameObjects/EntityManager.Components.cs
@@ -110,6 +110,7 @@ public int Count(Type component)
return dict.Count;
}
+ [Obsolete("Use InitializeEntity")]
public void InitializeComponents(EntityUid uid, MetaDataComponent? metadata = null)
{
DebugTools.AssertOwner(uid, metadata);
@@ -145,6 +146,7 @@ public void InitializeComponents(EntityUid uid, MetaDataComponent? metadata = nu
SetLifeStage(metadata, EntityLifeStage.Initialized);
}
+ [Obsolete("Use StartEntity")]
public void StartComponents(EntityUid uid)
{
// Startup() can modify _components
@@ -342,6 +344,8 @@ private void AddComponentInternal(EntityUid uid, T component, bool overwrite,
private void AddComponentInternal(EntityUid uid, T component, ComponentRegistration reg, bool overwrite, bool skipInit, MetaDataComponent metadata) where T : IComponent
{
+ ThreadCheck();
+
// We can't use typeof(T) here in case T is just Component
DebugTools.Assert(component is MetaDataComponent ||
(metadata ?? MetaQuery.GetComponent(uid)).EntityLifeStage < EntityLifeStage.Terminating,
@@ -602,6 +606,8 @@ private void RemoveComponentImmediate(
bool terminating,
MetaDataComponent? meta)
{
+ ThreadCheck();
+
if (component.Deleted)
{
_sawmill.Warning($"Deleting an already deleted component. Entity: {ToPrettyString(uid)}, Component: {_componentFactory.GetComponentName(component.GetType())}.");
diff --git a/Robust.Shared/GameObjects/EntityManager.cs b/Robust.Shared/GameObjects/EntityManager.cs
index c854bf9e869..7ba519b7eae 100644
--- a/Robust.Shared/GameObjects/EntityManager.cs
+++ b/Robust.Shared/GameObjects/EntityManager.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
@@ -117,6 +118,10 @@ public abstract partial class EntityManager : IEntityManager
public bool Initialized { get; protected set; }
+#if DEBUG
+ private int _mainThreadId;
+#endif
+
///
/// Constructs a new instance of .
///
@@ -138,6 +143,10 @@ public virtual void Initialize()
_sawmill = LogManager.GetSawmill("entity");
_resolveSawmill = LogManager.GetSawmill("resolve");
+#if DEBUG
+ _mainThreadId = Environment.CurrentManagedThreadId;
+#endif
+
Initialized = true;
}
@@ -511,6 +520,8 @@ public void DeleteEntity(EntityUid e, MetaDataComponent meta, TransformComponent
if (!Started)
return;
+ ThreadCheck();
+
if (meta.EntityLifeStage >= EntityLifeStage.Deleted)
return;
@@ -752,6 +763,8 @@ private protected EntityUid AllocEntity(
///
private EntityUid AllocEntity(out MetaDataComponent metadata)
{
+ ThreadCheck();
+
var uid = GenerateEntityUid();
#if DEBUG
@@ -864,15 +877,35 @@ public void InitializeAndStartEntity(Entity entity, bool doM
public void InitializeEntity(EntityUid entity, MetaDataComponent? meta = null)
{
+ // Ideally, entities only ever get initialized once their parent has already been initialized.
+ // Note that this doesn't guarantee that an uninitialized entity will never have initialized children.
+ // In particular, for the client this might happen when applying a new game state that re-parents an
+ // existing entity to a newly created entity. The new entity only gets initialiuzed & started at the end,
+ // after the old/existing entity was already moved to the new parent.
+ DebugTools.Assert(TransformQuery.GetComponent(entity).ParentUid is not { Valid: true } parent
+ || MetaQuery.GetComponent(parent).EntityLifeStage >= EntityLifeStage.Initialized);
+
DebugTools.AssertOwner(entity, meta);
meta ??= GetComponent(entity);
+#pragma warning disable CS0618 // Type or member is obsolete
InitializeComponents(entity, meta);
+#pragma warning restore CS0618 // Type or member is obsolete
EntityInitialized?.Invoke((entity, meta));
}
public void StartEntity(EntityUid entity)
{
+ // Ideally, entities only ever get initialized once their parent has already been initialized.
+ // Note that this doesn't guarantee that an uninitialized entity will never have initialized children.
+ // In particular, for the client this might happen when applying a new game state that re-parents an
+ // existing entity to a newly created entity. The new entity only gets initialiuzed & started at the end,
+ // after the old/existing entity was already moved to the new parent.
+ DebugTools.Assert(TransformQuery.GetComponent(entity).ParentUid is not { Valid: true } parent
+ || MetaQuery.GetComponent(parent).EntityLifeStage >= EntityLifeStage.Initialized);
+
+#pragma warning disable CS0618 // Type or member is obsolete
StartComponents(entity);
+#pragma warning restore CS0618 // Type or member is obsolete
}
public void RunMapInit(EntityUid entity, MetaDataComponent meta)
@@ -953,6 +986,16 @@ internal EntityUid GenerateEntityUid()
/// Generates a unique network id and increments
///
protected virtual NetEntity GenerateNetEntity() => new(NextNetworkId++);
+
+ [Conditional("DEBUG")]
+ protected void ThreadCheck()
+ {
+#if DEBUG
+ DebugTools.Assert(
+ Environment.CurrentManagedThreadId == _mainThreadId,
+ "Environment.CurrentManagedThreadId == _mainThreadId");
+#endif
+ }
}
public enum EntityMessageType : byte
diff --git a/Robust.Shared/GameObjects/EntitySystemMessages/EntityRenamedEvent.cs b/Robust.Shared/GameObjects/EntitySystemMessages/EntityRenamedEvent.cs
index 798359b6b35..cbb29dafc80 100644
--- a/Robust.Shared/GameObjects/EntitySystemMessages/EntityRenamedEvent.cs
+++ b/Robust.Shared/GameObjects/EntitySystemMessages/EntityRenamedEvent.cs
@@ -2,6 +2,7 @@ namespace Robust.Shared.GameObjects;
///
/// Raised directed on an entity when its name is changed.
+/// Contains the EntityUid as systems may need to subscribe to it without targeting a specific component.
///
[ByRefEvent]
-public readonly record struct EntityRenamedEvent(string NewName);
+public readonly record struct EntityRenamedEvent(EntityUid Uid, string OldName, string NewName);
diff --git a/Robust.Shared/GameObjects/IEntityManager.Components.cs b/Robust.Shared/GameObjects/IEntityManager.Components.cs
index c371a3a055d..0a749eb4010 100644
--- a/Robust.Shared/GameObjects/IEntityManager.Components.cs
+++ b/Robust.Shared/GameObjects/IEntityManager.Components.cs
@@ -22,11 +22,13 @@ public partial interface IEntityManager
///
/// Calls Initialize() on all registered components of the entity.
///
+ [Obsolete("Use InitializeEntity")]
void InitializeComponents(EntityUid uid, MetaDataComponent? meta = null);
///
/// Calls Startup() on all registered components of the entity.
///
+ [Obsolete("Use StartEntity")]
void StartComponents(EntityUid uid);
///
diff --git a/Robust.Shared/GameObjects/Systems/EntityLookup.Queries.cs b/Robust.Shared/GameObjects/Systems/EntityLookup.Queries.cs
index e9051c87c82..f332a94c54c 100644
--- a/Robust.Shared/GameObjects/Systems/EntityLookup.Queries.cs
+++ b/Robust.Shared/GameObjects/Systems/EntityLookup.Queries.cs
@@ -252,7 +252,7 @@ private bool AnyEntitiesIntersecting(MapId mapId,
var localTransform = state.Physics.GetRelativePhysicsTransform(state.Transform, uid);
var localAabb = state.Shape.ComputeAABB(localTransform, 0);
- if (state.Lookup.AnyEntitiesIntersecting(uid, state.Shape, localAabb, state.Transform, state.Flags, ignored: state.Ignored))
+ if (state.Lookup.AnyEntitiesIntersecting(uid, state.Shape, localAabb, localTransform, state.Flags, ignored: state.Ignored))
{
state.Found = true;
return false;
@@ -266,7 +266,7 @@ private bool AnyEntitiesIntersecting(MapId mapId,
var mapUid = _map.GetMapOrInvalid(mapId);
var localTransform = state.Physics.GetRelativePhysicsTransform(state.Transform, mapUid);
var localAabb = state.Shape.ComputeAABB(localTransform, 0);
- state.Found = AnyEntitiesIntersecting(mapUid, shape, localAabb, shapeTransform, flags, ignored);
+ state.Found = AnyEntitiesIntersecting(mapUid, shape, localAabb, localTransform, flags, ignored);
}
return state.Found;
@@ -454,26 +454,8 @@ public bool AnyEntitiesIntersecting(MapId mapId, Box2 worldAABB, LookupFlags fla
{
if (mapId == MapId.Nullspace) return false;
- // Don't need to check contained entities as they have the same bounds as the parent.
- var found = false;
-
- var state = (this, worldAABB, flags, found);
-
- _mapManager.FindGridsIntersecting(mapId, worldAABB, ref state,
- static (EntityUid uid, MapGridComponent _, ref (EntityLookupSystem lookup, Box2 worldAABB, LookupFlags flags, bool found) tuple) =>
- {
- if (!tuple.lookup.AnyLocalEntitiesIntersecting(uid, tuple.worldAABB, tuple.flags))
- return true;
-
- tuple.found = true;
- return false;
- }, approx: true, includeMap: false);
-
- if (state.found)
- return true;
-
- var mapUid = _map.GetMapOrInvalid(mapId);
- return AnyLocalEntitiesIntersecting(mapUid, worldAABB, flags);
+ var shape = new Polygon(worldAABB);
+ return AnyEntitiesIntersecting(mapId, shape, Physics.Transform.Empty, flags);
}
public HashSet GetEntitiesIntersecting(MapId mapId, Box2 worldAABB, LookupFlags flags = DefaultFlags)
@@ -487,23 +469,8 @@ public void GetEntitiesIntersecting(MapId mapId, Box2 worldAABB, HashSet intersecting,
- Box2 worldAABB, SharedTransformSystem xformSystem, LookupFlags flags) tuple) =>
- {
- var localAABB = tuple.xformSystem.GetInvWorldMatrix(gridUid).TransformBox(tuple.worldAABB);
- tuple.lookup.AddLocalEntitiesIntersecting(gridUid, tuple.intersecting, localAABB, tuple.flags);
- return true;
- }, approx: true, includeMap: false);
-
- // Get map entities
- var mapUid = _map.GetMapOrInvalid(mapId);
- AddLocalEntitiesIntersecting(mapUid, intersecting, worldAABB, flags);
- AddContained(intersecting, flags);
+ var shape = new Polygon(worldAABB);
+ AddEntitiesIntersecting(mapId, intersecting, shape, Physics.Transform.Empty, flags);
}
#endregion
@@ -513,73 +480,15 @@ public void GetEntitiesIntersecting(MapId mapId, Box2 worldAABB, HashSet
- {
- if (tuple.lookup.AnyEntitiesIntersecting(uid, tuple.worldBounds, tuple.flags))
- {
- tuple.found = true;
- return false;
- }
- return true;
- }, approx: true, includeMap: false);
-
- if (state.found)
- return true;
-
- var mapUid = _map.GetMapOrInvalid(mapId);
- return AnyEntitiesIntersecting(mapUid, worldBounds, flags);
+ var shape = new Polygon(worldBounds);
+ return AnyEntitiesIntersecting(mapId, shape, Physics.Transform.Empty, flags);
}
public HashSet GetEntitiesIntersecting(MapId mapId, Box2Rotated worldBounds, LookupFlags flags = DefaultFlags)
{
var intersecting = new HashSet();
-
- if (mapId == MapId.Nullspace)
- return intersecting;
-
- var mapUid = _map.GetMapOrInvalid(mapId);
-
- // Get grid entities
var shape = new Polygon(worldBounds);
- var transform = Physics.Transform.Empty;
-
- var state = (this, _physics, intersecting, transform, shape, flags);
-
- _mapManager.FindGridsIntersecting(mapUid, shape, transform, ref state,
- static (
- EntityUid uid,
- MapGridComponent grid,
- ref (EntityLookupSystem lookup,
- SharedPhysicsSystem _physics,
- HashSet intersecting,
- Transform transform,
- Polygon shape, LookupFlags flags) state) =>
- {
- var localTransform = state._physics.GetRelativePhysicsTransform(state.transform, uid);
- var localAabb = state.shape.ComputeAABB(localTransform, 0);
-
- state.lookup.AddEntitiesIntersecting(uid,
- state.intersecting,
- state.shape,
- localAabb,
- state.transform,
- state.flags);
- return true;
- });
-
- // Get map entities
- var localTransform = _physics.GetRelativePhysicsTransform(transform, mapUid);
- var localAabb = shape.ComputeAABB(localTransform, 0);
-
- AddEntitiesIntersecting(mapUid, intersecting, shape, localAabb, transform, flags);
- AddContained(intersecting, flags);
-
+ AddEntitiesIntersecting(mapId, intersecting, shape, Physics.Transform.Empty, flags);
return intersecting;
}
@@ -599,47 +508,8 @@ public bool AnyEntitiesInRange(EntityUid uid, float range, LookupFlags flags = D
if (mapPos.MapId == MapId.Nullspace)
return false;
- var rangeVec = new Vector2(range, range);
- var worldAABB = new Box2(mapPos.Position - rangeVec, mapPos.Position + rangeVec);
- var circle = new PhysShapeCircle(range, mapPos.Position);
-
- const bool found = false;
- var transform = Physics.Transform.Empty;
- var state = (this, _physics, transform, circle, flags, found, uid);
-
- _mapManager.FindGridsIntersecting(mapPos.MapId, worldAABB, ref state, static (
- EntityUid gridUid,
- MapGridComponent _, ref (
- EntityLookupSystem lookup,
- SharedPhysicsSystem physics,
- Transform worldTransform,
- PhysShapeCircle circle,
- LookupFlags flags,
- bool found,
- EntityUid ignored) tuple) =>
- {
- var localTransform = tuple.physics.GetRelativePhysicsTransform(tuple.worldTransform, gridUid);
- var localAabb = tuple.circle.ComputeAABB(localTransform, 0);
-
- if (tuple.lookup.AnyEntitiesIntersecting(gridUid, tuple.circle, localAabb, localTransform, tuple.flags, tuple.ignored))
- {
- tuple.found = true;
- return false;
- }
-
- return true;
- }, approx: true, includeMap: false);
-
- if (state.found)
- {
- return true;
- }
-
- var mapUid = _map.GetMapOrInvalid(mapPos.MapId);
- var localTransform = _physics.GetRelativePhysicsTransform(transform, uid);
- var localAabb = circle.ComputeAABB(localTransform, 0);
-
- return AnyEntitiesIntersecting(mapUid, circle, localAabb, localTransform, flags, uid);
+ var shape = new PhysShapeCircle(range, mapPos.Position);
+ return AnyEntitiesIntersecting(mapPos.MapId, shape, Physics.Transform.Empty, flags, uid);
}
public HashSet GetEntitiesInRange(EntityUid uid, float range, LookupFlags flags = DefaultFlags)
diff --git a/Robust.Shared/GameObjects/Systems/EntityLookupSystem.ComponentQueries.cs b/Robust.Shared/GameObjects/Systems/EntityLookupSystem.ComponentQueries.cs
index 52e6134ff14..6b001b024e3 100644
--- a/Robust.Shared/GameObjects/Systems/EntityLookupSystem.ComponentQueries.cs
+++ b/Robust.Shared/GameObjects/Systems/EntityLookupSystem.ComponentQueries.cs
@@ -542,7 +542,7 @@ public void GetEntitiesIntersecting(Type type, MapId mapId, IPhysShape shape, Tr
{
var localTransform = state.Physics.GetRelativePhysicsTransform(state.Transform, uid);
var localAabb = state.Shape.ComputeAABB(localTransform, 0);
- state.Lookup.AddEntitiesIntersecting(uid, state.Intersecting, state.Shape, localAabb, state.Transform, state.Flags, state.Query);
+ state.Lookup.AddEntitiesIntersecting(uid, state.Intersecting, state.Shape, localAabb, localTransform, state.Flags, state.Query);
return true;
}, approx: true, includeMap: false);
@@ -550,7 +550,7 @@ public void GetEntitiesIntersecting(Type type, MapId mapId, IPhysShape shape, Tr
var localTransform = state.Physics.GetRelativePhysicsTransform(state.Transform, mapUid);
var localAabb = state.Shape.ComputeAABB(localTransform, 0);
- AddEntitiesIntersecting(mapUid, intersecting, shape, localAabb, shapeTransform, flags, query);
+ AddEntitiesIntersecting(mapUid, intersecting, shape, localAabb, localTransform, flags, query);
AddContained(intersecting, flags, query);
}
@@ -586,7 +586,7 @@ public void GetEntitiesIntersecting(MapId mapId, IPhysShape shape, Transform
{
var localTransform = state.Physics.GetRelativePhysicsTransform(state.Transform, uid);
var localAabb = state.Shape.ComputeAABB(localTransform, 0);
- state.Lookup.AddEntitiesIntersecting(uid, state.Intersecting, state.Shape, localAabb, state.Transform, state.Flags, state.Query);
+ state.Lookup.AddEntitiesIntersecting(uid, state.Intersecting, state.Shape, localAabb, localTransform, state.Flags, state.Query);
return true;
}, approx: true, includeMap: false);
@@ -595,7 +595,7 @@ public void GetEntitiesIntersecting(MapId mapId, IPhysShape shape, Transform
var localTransform = state.Physics.GetRelativePhysicsTransform(state.Transform, mapUid);
var localAabb = state.Shape.ComputeAABB(localTransform, 0);
- AddEntitiesIntersecting(mapUid, entities, shape, localAabb, shapeTransform, flags, query);
+ AddEntitiesIntersecting(mapUid, entities, shape, localAabb, localTransform, flags, query);
AddContained(entities, flags, query);
}
}
@@ -678,8 +678,8 @@ public HashSet GetComponentsInRange(MapId mapId, Vector2 worldPos, float r
public void GetEntitiesInRange(MapId mapId, Vector2 worldPos, float range, HashSet> entities, LookupFlags flags = DefaultFlags) where T : IComponent
{
- var shape = new PhysShapeCircle(range);
- var transform = new Transform(worldPos, 0f);
+ var shape = new PhysShapeCircle(range, worldPos);
+ var transform = Physics.Transform.Empty;
GetEntitiesInRange(mapId, shape, transform, entities, flags);
}
diff --git a/Robust.Shared/GameObjects/Systems/MetaDataSystem.cs b/Robust.Shared/GameObjects/Systems/MetaDataSystem.cs
index a67ba3d2589..2ee1e89e91f 100644
--- a/Robust.Shared/GameObjects/Systems/MetaDataSystem.cs
+++ b/Robust.Shared/GameObjects/Systems/MetaDataSystem.cs
@@ -47,12 +47,14 @@ public void SetEntityName(EntityUid uid, string value, MetaDataComponent? metada
if (!_metaQuery.Resolve(uid, ref metadata) || value.Equals(metadata.EntityName))
return;
+ var oldName = metadata.EntityName;
+
metadata._entityName = value;
if (raiseEvents)
{
- var ev = new EntityRenamedEvent(value);
- RaiseLocalEvent(uid, ref ev);
+ var ev = new EntityRenamedEvent(uid, oldName, value);
+ RaiseLocalEvent(uid, ref ev, true);
}
Dirty(uid, metadata, metadata);
diff --git a/Robust.Shared/GameObjects/Systems/SharedAppearanceSystem.cs b/Robust.Shared/GameObjects/Systems/SharedAppearanceSystem.cs
index 38ea710beac..55905a26df6 100644
--- a/Robust.Shared/GameObjects/Systems/SharedAppearanceSystem.cs
+++ b/Robust.Shared/GameObjects/Systems/SharedAppearanceSystem.cs
@@ -115,6 +115,32 @@ public void CopyData(Entity src, Entity
+ /// Appends appearance data from src to dest. If a key/value pair already exists in dest, it gets replaced.
+ /// If src has no nothing is done.
+ /// If dest has no AppearanceComponent then it is created.
+ ///
+ public void AppendData(Entity src, Entity dest)
+ {
+ if (!Resolve(src, ref src.Comp, false))
+ return;
+
+ AppendData(src.Comp, dest);
+ }
+
+ public void AppendData(AppearanceComponent srcComp, Entity dest)
+ {
+ dest.Comp ??= EnsureComp(dest);
+
+ foreach (var (key, value) in srcComp.AppearanceData)
+ {
+ dest.Comp.AppearanceData[key] = value;
+ }
+
+ Dirty(dest, dest.Comp);
+ QueueUpdate(dest, dest.Comp);
+ }
}
[Serializable, NetSerializable]
diff --git a/Robust.Shared/GameObjects/Systems/SharedGridFixtureSystem.cs b/Robust.Shared/GameObjects/Systems/SharedGridFixtureSystem.cs
index a60f78c84c1..0de5870a86c 100644
--- a/Robust.Shared/GameObjects/Systems/SharedGridFixtureSystem.cs
+++ b/Robust.Shared/GameObjects/Systems/SharedGridFixtureSystem.cs
@@ -94,9 +94,9 @@ internal void RegenerateCollision(
{
UpdateFixture(uid, chunk, rectangles, body, manager, xform);
- foreach (var (id, fixture) in chunk.Fixtures)
+ foreach (var id in chunk.Fixtures)
{
- fixtures[id] = fixture;
+ fixtures[id] = manager.Fixtures[id];
}
}
@@ -157,8 +157,9 @@ private bool UpdateFixture(EntityUid uid, MapChunk chunk, List rectangles
// Check if we even need to issue an eventbus event
var updated = false;
- foreach (var (oldId, oldFixture) in chunk.Fixtures)
+ foreach (var oldId in chunk.Fixtures)
{
+ var oldFixture = manager.Fixtures[oldId];
var existing = false;
// Handle deleted / updated fixtures
@@ -196,16 +197,16 @@ private bool UpdateFixture(EntityUid uid, MapChunk chunk, List rectangles
// Anything remaining is a new fixture (or at least, may have not serialized onto the chunk yet).
foreach (var (id, fixture) in newFixtures.Span)
{
+ chunk.Fixtures.Add(id);
var existingFixture = _fixtures.GetFixtureOrNull(uid, id, manager: manager);
// Check if it's the same (otherwise remove anyway).
if (existingFixture?.Shape is PolygonShape poly &&
poly.EqualsApprox((PolygonShape) fixture.Shape))
{
- chunk.Fixtures.Add(id, existingFixture);
+
continue;
}
- chunk.Fixtures.Add(id, fixture);
_fixtures.CreateFixture(uid, id, fixture, false, manager, body, xform);
}
diff --git a/Robust.Shared/GameObjects/Systems/SharedMapSystem.Grid.cs b/Robust.Shared/GameObjects/Systems/SharedMapSystem.Grid.cs
index 929b904ace3..47a750f05f7 100644
--- a/Robust.Shared/GameObjects/Systems/SharedMapSystem.Grid.cs
+++ b/Robust.Shared/GameObjects/Systems/SharedMapSystem.Grid.cs
@@ -12,6 +12,7 @@
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
+using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -245,6 +246,7 @@ protected virtual void UpdatePvsChunks(Entity modifiedChunks;
+
switch (args.Current)
{
case MapGridComponentDeltaState delta:
@@ -257,9 +259,9 @@ private void OnGridHandleState(EntityUid uid, MapGridComponent component, ref Co
if (delta.ChunkData == null)
return;
- foreach (var chunkData in delta.ChunkData)
+ foreach (var (index, chunkData) in delta.ChunkData)
{
- ApplyChunkData(uid, component, chunkData, modifiedChunks);
+ ApplyChunkData(uid, component, index, chunkData, modifiedChunks);
}
component.LastTileModifiedTick = delta.LastTileModifiedTick;
@@ -277,12 +279,12 @@ private void OnGridHandleState(EntityUid uid, MapGridComponent component, ref Co
foreach (var index in component.Chunks.Keys)
{
if (!state.FullGridData.ContainsKey(index))
- ApplyChunkData(uid, component, ChunkDatum.CreateDeleted(index), modifiedChunks);
+ ApplyChunkData(uid, component, index, ChunkDatum.Empty, modifiedChunks);
}
- foreach (var (index, tiles) in state.FullGridData)
+ foreach (var (index, data) in state.FullGridData)
{
- ApplyChunkData(uid, component, ChunkDatum.CreateModified(index, tiles), modifiedChunks);
+ ApplyChunkData(uid, component, index, new(data), modifiedChunks);
}
break;
@@ -291,12 +293,8 @@ private void OnGridHandleState(EntityUid uid, MapGridComponent component, ref Co
return;
}
- var count = component.Chunks.Count;
- RegenerateCollision(uid, component, modifiedChunks);
-
- // Regeneration can remove chunks in general, but it shouldn't do that here as the state handling
- // should already have removed all the chunks.
- DebugTools.AssertEqual(component.Chunks.Count, count);
+ RegenerateAabb(component);
+ OnGridBoundsChange(uid, component);
#if DEBUG
foreach (var chunk in component.Chunks.Values)
@@ -307,7 +305,11 @@ private void OnGridHandleState(EntityUid uid, MapGridComponent component, ref Co
#endif
}
- private void ApplyChunkData(EntityUid uid, MapGridComponent component, ChunkDatum data,
+ private void ApplyChunkData(
+ EntityUid uid,
+ MapGridComponent component,
+ Vector2i index,
+ ChunkDatum data,
HashSet modifiedChunks)
{
bool shapeChanged = false;
@@ -315,7 +317,7 @@ private void ApplyChunkData(EntityUid uid, MapGridComponent component, ChunkDatu
if (data.IsDeleted())
{
- if (!component.Chunks.TryGetValue(data.Index, out var deletedChunk))
+ if (!component.Chunks.TryGetValue(index, out var deletedChunk))
return;
// Deleted chunks still need to raise tile-changed events.
@@ -329,18 +331,18 @@ private void ApplyChunkData(EntityUid uid, MapGridComponent component, ChunkDatu
var gridIndices = deletedChunk.ChunkTileToGridTile((x, y));
var newTileRef = new TileRef(uid, gridIndices, Tile.Empty);
- _mapInternal.RaiseOnTileChanged(newTileRef, oldTile, data.Index);
+ _mapInternal.RaiseOnTileChanged(newTileRef, oldTile, index);
}
}
- component.Chunks.Remove(data.Index);
+ component.Chunks.Remove(index);
// TODO is this required?
modifiedChunks.Add(deletedChunk);
return;
}
- var chunk = GetOrAddChunk(uid, component, data.Index);
+ var chunk = GetOrAddChunk(uid, component, index);
chunk.SuppressCollisionRegeneration = true;
DebugTools.Assert(data.TileData.Any(x => !x.IsEmpty));
DebugTools.Assert(data.TileData.Length == component.ChunkSize * component.ChunkSize);
@@ -355,13 +357,24 @@ private void ApplyChunkData(EntityUid uid, MapGridComponent component, ChunkDatu
shapeChanged |= tileShapeChanged;
var gridIndices = chunk.ChunkTileToGridTile((x, y));
var newTileRef = new TileRef(uid, gridIndices, tile);
- _mapInternal.RaiseOnTileChanged(newTileRef, oldTile, data.Index);
+ _mapInternal.RaiseOnTileChanged(newTileRef, oldTile, index);
}
}
+ if (data.Fixtures != null && !chunk.Fixtures.SetEquals(data.Fixtures))
+ {
+ chunk.Fixtures.Clear();
+
+ if (data.Fixtures != null)
+ chunk.Fixtures.UnionWith(data.Fixtures);
+ }
+
+ chunk.CachedBounds = data.CachedBounds!.Value;
chunk.SuppressCollisionRegeneration = false;
if (shapeChanged)
+ {
modifiedChunks.Add(chunk);
+ }
}
private void OnGridGetState(EntityUid uid, MapGridComponent component, ref ComponentGetState args)
@@ -372,7 +385,7 @@ private void OnGridGetState(EntityUid uid, MapGridComponent component, ref Compo
return;
}
- List? chunkData;
+ Dictionary? chunkData;
var fromTick = args.FromTick;
if (component.LastTileModifiedTick < fromTick)
@@ -381,7 +394,7 @@ private void OnGridGetState(EntityUid uid, MapGridComponent component, ref Compo
}
else
{
- chunkData = new List();
+ chunkData = new Dictionary();
foreach (var (tick, indices) in component.ChunkDeletionHistory)
{
@@ -391,7 +404,7 @@ private void OnGridGetState(EntityUid uid, MapGridComponent component, ref Compo
// Chunk may have been re-added sometime after it was deleted, but before deletion history was culled.
if (!component.Chunks.TryGetValue(indices, out var chunk))
{
- chunkData.Add(ChunkDatum.CreateDeleted(indices));
+ chunkData.Add(indices, ChunkDatum.Empty);
continue;
}
@@ -416,7 +429,7 @@ private void OnGridGetState(EntityUid uid, MapGridComponent component, ref Compo
tileBuffer[x * component.ChunkSize + y] = chunk.GetTile((ushort)x, (ushort)y);
}
}
- chunkData.Add(ChunkDatum.CreateModified(index, tileBuffer));
+ chunkData.Add(index, ChunkDatum.CreateModified(tileBuffer, chunk.Fixtures, chunk.CachedBounds));
}
}
@@ -427,12 +440,12 @@ private void OnGridGetState(EntityUid uid, MapGridComponent component, ref Compo
return;
HashSet keys = new();
- foreach (var chunk in chunkData)
+ foreach (var (index, chunk) in chunkData)
{
- if (chunk.TileData == null)
+ if (chunk.IsDeleted())
continue;
- DebugTools.Assert(keys.Add(chunk.Index), "Duplicate chunk");
+ DebugTools.Assert(keys.Add(index), "Duplicate chunk");
DebugTools.Assert(chunk.TileData.Any(x => !x.IsEmpty), "Empty non-deleted chunk");
}
#endif
@@ -440,7 +453,7 @@ private void OnGridGetState(EntityUid uid, MapGridComponent component, ref Compo
private void GetFullState(EntityUid uid, MapGridComponent component, ref ComponentGetState args)
{
- var chunkData = new Dictionary();
+ var chunkData = new Dictionary();
foreach (var (index, chunk) in GetMapChunks(uid, component))
{
@@ -453,7 +466,7 @@ private void GetFullState(EntityUid uid, MapGridComponent component, ref Compone
tileBuffer[x * component.ChunkSize + y] = chunk.GetTile((ushort)x, (ushort)y);
}
}
- chunkData.Add(index, tileBuffer);
+ chunkData.Add(index, ChunkDatum.CreateModified(tileBuffer, chunk.Fixtures, chunk.CachedBounds));
}
args.State = new MapGridComponentState(component.ChunkSize, chunkData, component.LastTileModifiedTick);
@@ -461,7 +474,7 @@ private void GetFullState(EntityUid uid, MapGridComponent component, ref Compone
#if DEBUG
foreach (var chunk in chunkData.Values)
{
- DebugTools.Assert(chunk.Any(x => !x.IsEmpty));
+ DebugTools.Assert(chunk.TileData!.Any(x => !x.IsEmpty));
}
#endif
}
@@ -495,7 +508,7 @@ private void OnGridInit(EntityUid uid, MapGridComponent component, ComponentInit
if (TryComp(xform.MapUid, out var gridTree))
{
- var proxy = gridTree.Tree.CreateProxy(in aabb, (uid, component));
+ var proxy = gridTree.Tree.CreateProxy(in aabb, (uid, _fixturesQuery.Comp(uid), component));
DebugTools.Assert(component.MapProxy == DynamicTree.Proxy.Free);
component.MapProxy = proxy;
}
@@ -549,7 +562,7 @@ private void AddGrid(EntityUid uid, MapGridComponent grid)
if (TryComp(xform.MapUid, out var gridTree))
{
- var proxy = gridTree.Tree.CreateProxy(in aabb, (uid, grid));
+ var proxy = gridTree.Tree.CreateProxy(in aabb, (uid, _fixturesQuery.Comp(uid), grid));
DebugTools.Assert(grid.MapProxy == DynamicTree.Proxy.Free);
grid.MapProxy = proxy;
}
@@ -629,9 +642,9 @@ internal void RegenerateCollision(EntityUid uid, MapGridComponent grid, IReadOnl
PhysicsComponent? body = null;
TransformComponent? xform = null;
- foreach (var (id, fixture) in mapChunk.Fixtures)
+ foreach (var id in mapChunk.Fixtures)
{
- _fixtures.DestroyFixture(uid, id, fixture, false, manager: manager, body: body, xform: xform);
+ _fixtures.DestroyFixture(uid, id, false, manager: manager, body: body, xform: xform);
}
RemoveChunk(uid, grid, mapChunk.Indices);
@@ -639,6 +652,20 @@ internal void RegenerateCollision(EntityUid uid, MapGridComponent grid, IReadOnl
}
}
+ RegenerateAabb(grid);
+
+ // May have been deleted from the bulk update above!
+ if (Deleted(uid))
+ return;
+
+ _physics.WakeBody(uid);
+ OnGridBoundsChange(uid, grid);
+ var ev = new RegenerateGridBoundsEvent(uid, chunkRectangles, removedChunks);
+ RaiseLocalEvent(ref ev);
+ }
+
+ private void RegenerateAabb(MapGridComponent grid)
+ {
grid.LocalAABB = new Box2();
foreach (var chunk in grid.Chunks.Values)
@@ -659,15 +686,6 @@ internal void RegenerateCollision(EntityUid uid, MapGridComponent grid, IReadOnl
grid.LocalAABB = grid.LocalAABB.Union(gridBounds);
}
}
-
- // May have been deleted from the bulk update above!
- if (Deleted(uid))
- return;
-
- _physics.WakeBody(uid);
- OnGridBoundsChange(uid, grid);
- var ev = new RegenerateGridBoundsEvent(uid, chunkRectangles, removedChunks);
- RaiseLocalEvent(ref ev);
}
///
@@ -689,16 +707,31 @@ private void ClearEmptyMapChunks(EntityUid uid, MapGridComponent grid, IReadOnly
#region TileAccess
+ public TileRef GetTileRef(Entity grid, MapCoordinates coords)
+ {
+ return GetTileRef(grid.Owner, grid.Comp, coords);
+ }
+
public TileRef GetTileRef(EntityUid uid, MapGridComponent grid, MapCoordinates coords)
{
return GetTileRef(uid, grid, CoordinatesToTile(uid, grid, coords));
}
+ public TileRef GetTileRef(Entity grid, EntityCoordinates coords)
+ {
+ return GetTileRef(grid.Owner, grid.Comp, coords);
+ }
+
public TileRef GetTileRef(EntityUid uid, MapGridComponent grid, EntityCoordinates coords)
{
return GetTileRef(uid, grid, CoordinatesToTile(uid, grid, coords));
}
+ public TileRef GetTileRef(Entity grid, Vector2i tileCoordinates)
+ {
+ return GetTileRef(grid.Owner, grid.Comp, tileCoordinates);
+ }
+
public TileRef GetTileRef(EntityUid uid, MapGridComponent grid, Vector2i tileCoordinates)
{
var chunkIndices = GridTileToChunkIndices(uid, grid, tileCoordinates);
@@ -1071,16 +1104,31 @@ public int AnchoredEntityCount(EntityUid uid, MapGridComponent grid, Vector2i po
return chunk.GetSnapGrid((ushort)x, (ushort)y)?.Count ?? 0; // ?
}
+ public IEnumerable GetAnchoredEntities(Entity grid, MapCoordinates coords)
+ {
+ return GetAnchoredEntities(grid.Owner, grid.Comp, coords);
+ }
+
public IEnumerable GetAnchoredEntities(EntityUid uid, MapGridComponent grid, MapCoordinates coords)
{
return GetAnchoredEntities(uid, grid, TileIndicesFor(uid, grid, coords));
}
+ public IEnumerable GetAnchoredEntities(Entity grid, EntityCoordinates coords)
+ {
+ return GetAnchoredEntities(grid.Owner, grid.Comp, coords);
+ }
+
public IEnumerable GetAnchoredEntities(EntityUid uid, MapGridComponent grid, EntityCoordinates coords)
{
return GetAnchoredEntities(uid, grid, TileIndicesFor(uid, grid, coords));
}
+ public IEnumerable GetAnchoredEntities(Entity grid, Vector2i pos)
+ {
+ return GetAnchoredEntities(grid.Owner, grid.Comp, pos);
+ }
+
public IEnumerable GetAnchoredEntities(EntityUid uid, MapGridComponent grid, Vector2i pos)
{
// Because some content stuff checks neighboring tiles (which may not actually exist) we won't just
@@ -1173,6 +1221,11 @@ public Vector2i TileIndicesFor(EntityUid uid, MapGridComponent grid, EntityCoord
return SnapGridLocalCellFor(uid, grid, LocalToGrid(uid, grid, coords));
}
+ public Vector2i TileIndicesFor(Entity grid, EntityCoordinates coords)
+ {
+ return TileIndicesFor(grid.Owner, grid.Comp, coords);
+ }
+
public Vector2i TileIndicesFor(EntityUid uid, MapGridComponent grid, MapCoordinates worldPos)
{
#if DEBUG
@@ -1184,6 +1237,11 @@ public Vector2i TileIndicesFor(EntityUid uid, MapGridComponent grid, MapCoordina
return SnapGridLocalCellFor(uid, grid, localPos);
}
+ public Vector2i TileIndicesFor(Entity grid, MapCoordinates coords)
+ {
+ return TileIndicesFor(grid.Owner, grid.Comp, coords);
+ }
+
private Vector2i SnapGridLocalCellFor(EntityUid uid, MapGridComponent grid, Vector2 localPos)
{
var x = (int)Math.Floor(localPos.X / grid.TileSize);
diff --git a/Robust.Shared/GameObjects/Systems/SharedMapSystem.cs b/Robust.Shared/GameObjects/Systems/SharedMapSystem.cs
index b99177cc18f..f87a98e33cb 100644
--- a/Robust.Shared/GameObjects/Systems/SharedMapSystem.cs
+++ b/Robust.Shared/GameObjects/Systems/SharedMapSystem.cs
@@ -5,6 +5,7 @@
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Network;
+using Robust.Shared.Physics;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Timing;
@@ -23,6 +24,7 @@ public abstract partial class SharedMapSystem : EntitySystem
[Dependency] private readonly IComponentFactory _factory = default!;
[Dependency] private readonly MetaDataSystem _meta = default!;
+ private EntityQuery _fixturesQuery;
private EntityQuery _mapQuery;
private EntityQuery _gridQuery;
private EntityQuery _metaQuery;
@@ -34,6 +36,7 @@ public override void Initialize()
{
base.Initialize();
+ _fixturesQuery = GetEntityQuery();
_mapQuery = GetEntityQuery();
_gridQuery = GetEntityQuery();
_metaQuery = GetEntityQuery();
diff --git a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs
index 130d160e4c9..aed80ff5cba 100644
--- a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs
+++ b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs
@@ -107,6 +107,11 @@ public bool AnchorEntity(EntityUid uid, TransformComponent xform, MapGridCompone
return AnchorEntity(uid, xform, grid.Owner, grid, tileIndices);
}
+ public bool AnchorEntity(EntityUid uid)
+ {
+ return AnchorEntity(uid, XformQuery.GetComponent(uid));
+ }
+
public bool AnchorEntity(EntityUid uid, TransformComponent xform)
{
return AnchorEntity((uid, xform));
@@ -127,6 +132,11 @@ public bool AnchorEntity(Entity entity, Entity ent, ref Com
// I.e., don't resend the whole BUI state just because a new user opened it.
var actors = new Dictionary>();
- args.State = new UserInterfaceComponent.UserInterfaceComponentState(actors, ent.Comp.States, ent.Comp.Interfaces);
+
+ var dataCopy = new Dictionary();
+
+ foreach (var (weh, a) in ent.Comp.Interfaces)
+ {
+ dataCopy[weh] = new InterfaceData(a);
+ }
+
+ args.State = new UserInterfaceComponent.UserInterfaceComponentState(actors, ent.Comp.States, dataCopy);
// Ensure that only the player that currently has the UI open gets to know what they have it open.
if (args.ReplayState)
@@ -335,11 +343,7 @@ private void OnUserInterfaceHandleState(Entity ent, ref
foreach (var data in state.Data)
{
- ent.Comp.Interfaces[data.Key] = new(data.Value.ClientType)
- {
- InteractionRange = data.Value.InteractionRange,
- RequireInputValidation = data.Value.RequireInputValidation,
- };
+ ent.Comp.Interfaces[data.Key] = new(data.Value);
}
foreach (var key in ent.Comp.Actors.Keys)
diff --git a/Robust.Shared/GameStates/GameStateMapData.cs b/Robust.Shared/GameStates/GameStateMapData.cs
index 3fb09a608d7..2daf838c77a 100644
--- a/Robust.Shared/GameStates/GameStateMapData.cs
+++ b/Robust.Shared/GameStates/GameStateMapData.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.Map;
using Robust.Shared.Maths;
@@ -9,33 +10,49 @@ namespace Robust.Shared.GameStates
[Serializable, NetSerializable]
public readonly struct ChunkDatum
{
- public readonly Vector2i Index;
+ public static readonly ChunkDatum Empty = new ChunkDatum();
+
+ public readonly HashSet? Fixtures;
// Definitely wasteful to send EVERY tile.
// Optimize away future coder.
// Also it's stored row-major.
public readonly Tile[]? TileData;
+ public readonly Box2i? CachedBounds;
+
[MemberNotNullWhen(false, nameof(TileData))]
public bool IsDeleted()
{
return TileData == null;
}
- private ChunkDatum(Vector2i index, Tile[] tileData)
+ internal ChunkDatum(ChunkDatum data)
{
- Index = index;
- TileData = tileData;
+ if (data.TileData != null)
+ {
+ TileData = new Tile[data.TileData.Length];
+ data.TileData.CopyTo(TileData, 0);
+ }
+
+ if (data.Fixtures != null)
+ {
+ Fixtures = new HashSet(data.Fixtures);
+ }
+
+ CachedBounds = data.CachedBounds;
}
- public static ChunkDatum CreateModified(Vector2i index, Tile[] tileData)
+ private ChunkDatum(Tile[] tileData, HashSet fixtures, Box2i cachedBounds)
{
- return new ChunkDatum(index, tileData);
+ TileData = tileData;
+ Fixtures = fixtures;
+ CachedBounds = cachedBounds;
}
- public static ChunkDatum CreateDeleted(Vector2i index)
+ public static ChunkDatum CreateModified(Tile[] tileData, HashSet fixtures, Box2i cachedBounds)
{
- return new ChunkDatum(index, null!);
+ return new ChunkDatum(tileData, fixtures, cachedBounds);
}
}
}
diff --git a/Robust.Shared/Map/Components/GridTreeComponent.cs b/Robust.Shared/Map/Components/GridTreeComponent.cs
index 8e888938812..8a38b1b2b50 100644
--- a/Robust.Shared/Map/Components/GridTreeComponent.cs
+++ b/Robust.Shared/Map/Components/GridTreeComponent.cs
@@ -9,5 +9,5 @@ namespace Robust.Shared.Map.Components;
public sealed partial class GridTreeComponent : Component
{
[ViewVariables]
- public readonly B2DynamicTree<(EntityUid Uid, MapGridComponent Grid)> Tree = new();
+ public readonly B2DynamicTree<(EntityUid Uid, FixturesComponent Fixtures, MapGridComponent Grid)> Tree = new();
}
diff --git a/Robust.Shared/Map/Components/MapGridComponent.cs b/Robust.Shared/Map/Components/MapGridComponent.cs
index 2ba82aaeeb2..882d6a821d1 100644
--- a/Robust.Shared/Map/Components/MapGridComponent.cs
+++ b/Robust.Shared/Map/Components/MapGridComponent.cs
@@ -335,7 +335,7 @@ public bool TryGetTileRef(EntityCoordinates coords, out TileRef tile)
/// Serialized state of a .
///
[Serializable, NetSerializable]
- internal sealed class MapGridComponentState(ushort chunkSize, Dictionary fullGridData, GameTick lastTileModifiedTick) : ComponentState
+ internal sealed class MapGridComponentState(ushort chunkSize, Dictionary fullGridData, GameTick lastTileModifiedTick) : ComponentState
{
///
/// The size of the chunks in the map grid.
@@ -345,7 +345,7 @@ internal sealed class MapGridComponentState(ushort chunkSize, Dictionary
/// Networked chunk data containing the full grid state.
///
- public Dictionary FullGridData = fullGridData;
+ public Dictionary FullGridData = fullGridData;
///
/// Last game tick that the tile on the grid was modified.
@@ -357,7 +357,7 @@ internal sealed class MapGridComponentState(ushort chunkSize, Dictionary.
///
[Serializable, NetSerializable]
- internal sealed class MapGridComponentDeltaState(ushort chunkSize, List? chunkData, GameTick lastTileModifiedTick)
+ internal sealed class MapGridComponentDeltaState(ushort chunkSize, Dictionary? chunkData, GameTick lastTileModifiedTick)
: ComponentState, IComponentDeltaState
{
///
@@ -368,7 +368,7 @@ internal sealed class MapGridComponentDeltaState(ushort chunkSize, List
/// Networked chunk data.
///
- public readonly List? ChunkData = chunkData;
+ public readonly Dictionary? ChunkData = chunkData;
///
/// Last game tick that the tile on the grid was modified.
@@ -382,12 +382,12 @@ public void ApplyToFullState(MapGridComponentState state)
if (ChunkData == null)
return;
- foreach (var data in ChunkData)
+ foreach (var (index, data) in ChunkData)
{
if (data.IsDeleted())
- state.FullGridData!.Remove(data.Index);
+ state.FullGridData.Remove(index);
else
- state.FullGridData![data.Index] = data.TileData;
+ state.FullGridData[index] = new(data);
}
state.LastTileModifiedTick = LastTileModifiedTick;
@@ -395,12 +395,11 @@ public void ApplyToFullState(MapGridComponentState state)
public MapGridComponentState CreateNewFullState(MapGridComponentState state)
{
- var fullGridData = new Dictionary(state.FullGridData.Count);
+ var fullGridData = new Dictionary(state.FullGridData.Count);
foreach (var (key, value) in state.FullGridData)
{
- var arr = fullGridData[key] = new Tile[value.Length];
- Array.Copy(value, arr, value.Length);
+ fullGridData[key] = new(value);
}
var newState = new MapGridComponentState(ChunkSize, fullGridData, LastTileModifiedTick);
diff --git a/Robust.Shared/Map/MapChunk.cs b/Robust.Shared/Map/MapChunk.cs
index 98cc4d4d3be..8c685b62098 100644
--- a/Robust.Shared/Map/MapChunk.cs
+++ b/Robust.Shared/Map/MapChunk.cs
@@ -39,12 +39,11 @@ internal sealed class MapChunk
///
/// Chunk-local AABB of this chunk.
///
+ [ViewVariables]
public Box2i CachedBounds { get; set; }
- ///
- /// Physics fixtures that make up this grid chunk.
- ///
- public Dictionary Fixtures { get; } = new();
+ [ViewVariables]
+ internal HashSet Fixtures = new();
///
/// The last game simulation tick that a tile on this chunk was modified.
diff --git a/Robust.Shared/Map/MapManager.Queries.cs b/Robust.Shared/Map/MapManager.Queries.cs
index 5d90e96d182..e61e572747c 100644
--- a/Robust.Shared/Map/MapManager.Queries.cs
+++ b/Robust.Shared/Map/MapManager.Queries.cs
@@ -18,14 +18,16 @@ private bool IsIntersecting(
ChunkEnumerator enumerator,
IPhysShape shape,
Transform shapeTransform,
- EntityUid gridUid)
+ Entity grid)
{
- var gridTransform = _physics.GetPhysicsTransform(gridUid);
+ var gridTransform = _physics.GetPhysicsTransform(grid);
while (enumerator.MoveNext(out var chunk))
{
- foreach (var fixture in chunk.Fixtures.Values)
+ foreach (var id in chunk.Fixtures)
{
+ var fixture = grid.Comp.Fixtures[id];
+
for (var j = 0; j < fixture.Shape.ChildCount; j++)
{
if (_manifolds.TestOverlap(shape, 0, fixture.Shape, j, shapeTransform, gridTransform))
@@ -169,7 +171,7 @@ private void FindGridsIntersecting(EntityUid mapEnt, IPhysShape shape, B
if (!overlappingChunks.MoveNext(out _))
return true;
}
- else if (!state.MapManager.IsIntersecting(overlappingChunks, state.Shape, state.Transform, data.Uid))
+ else if (!state.MapManager.IsIntersecting(overlappingChunks, state.Shape, state.Transform, (data.Uid, data.Fixtures)))
{
return true;
}
@@ -345,7 +347,7 @@ private readonly record struct GridQueryState(
Box2 WorldAABB,
IPhysShape Shape,
Transform Transform,
- B2DynamicTree<(EntityUid Uid, MapGridComponent Grid)> Tree,
+ B2DynamicTree<(EntityUid Uid, FixturesComponent Fixtures, MapGridComponent Grid)> Tree,
SharedMapSystem MapSystem,
MapManager MapManager,
SharedTransformSystem TransformSystem,
@@ -357,7 +359,7 @@ private record struct GridQueryState(
Box2 WorldAABB,
IPhysShape Shape,
Transform Transform,
- B2DynamicTree<(EntityUid Uid, MapGridComponent Grid)> Tree,
+ B2DynamicTree<(EntityUid Uid, FixturesComponent Fixtures, MapGridComponent Grid)> Tree,
SharedMapSystem MapSystem,
MapManager MapManager,
SharedTransformSystem TransformSystem,
diff --git a/Robust.Shared/Physics/Shapes/Polygon.cs b/Robust.Shared/Physics/Shapes/Polygon.cs
index 78fd9a55a4b..64f0f3bd77c 100644
--- a/Robust.Shared/Physics/Shapes/Polygon.cs
+++ b/Robust.Shared/Physics/Shapes/Polygon.cs
@@ -1,7 +1,6 @@
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
-using System.Runtime.Intrinsics;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Serialization.Manager.Attributes;
@@ -197,28 +196,6 @@ public bool Equals(IPhysShape? other)
return true;
}
- public bool Equals(PolygonShape? other)
- {
- if (ReferenceEquals(null, other))
- return false;
- if (ReferenceEquals(this, other))
- return true;
-
- if (!Radius.Equals(other.Radius) || VertexCount != other.VertexCount)
- return false;
-
- for (var i = 0; i < VertexCount; i++)
- {
- var vert = Vertices[i];
- var otherVert = other.Vertices[i];
-
- if (!vert.Equals(otherVert))
- return false;
- }
-
- return true;
- }
-
public override int GetHashCode()
{
return HashCode.Combine(VertexCount, Vertices.AsSpan(0, VertexCount).ToArray(), Radius);
diff --git a/Robust.Shared/Physics/Systems/SharedBroadphaseSystem.cs b/Robust.Shared/Physics/Systems/SharedBroadphaseSystem.cs
index c57b0fe1a30..18aee0396f3 100644
--- a/Robust.Shared/Physics/Systems/SharedBroadphaseSystem.cs
+++ b/Robust.Shared/Physics/Systems/SharedBroadphaseSystem.cs
@@ -249,24 +249,36 @@ private void HandleGridCollisions(
// TODO: Need to handle grids colliding with non-grid entities with the same layer
// (nothing in SS14 does this yet).
+ var fixture = _fixturesQuery.Comp(gridUid);
+ var physics = _physicsQuery.Comp(gridUid);
var transform = _physicsSystem.GetPhysicsTransform(gridUid);
- var state = (gridUid, grid, transform, worldMatrix, invWorldMatrix, _map, _physicsSystem, _transform, _physicsQuery, _xformQuery);
+ var state = (
+ new Entity(gridUid, fixture, grid, physics),
+ transform,
+ worldMatrix,
+ invWorldMatrix,
+ _map,
+ _physicsSystem,
+ _transform,
+ _fixturesQuery,
+ _physicsQuery,
+ _xformQuery);
_mapManager.FindGridsIntersecting(mapId, aabb, ref state,
static (EntityUid uid, MapGridComponent component,
- ref (EntityUid gridUid,
- MapGridComponent grid,
+ ref (Entity grid,
Transform transform,
Matrix3x2 worldMatrix,
Matrix3x2 invWorldMatrix,
SharedMapSystem _map,
SharedPhysicsSystem _physicsSystem,
SharedTransformSystem xformSystem,
+ EntityQuery fixturesQuery,
EntityQuery physicsQuery,
EntityQuery xformQuery) tuple) =>
{
- if (tuple.gridUid == uid ||
+ if (tuple.grid.Owner == uid ||
!tuple.xformQuery.TryGetComponent(uid, out var collidingXform))
{
return true;
@@ -277,38 +289,43 @@ private void HandleGridCollisions(
var otherTransform = tuple._physicsSystem.GetPhysicsTransform(uid);
// Get Grid2 AABB in grid1 ref
- var aabb1 = tuple.grid.LocalAABB.Intersect(tuple.invWorldMatrix.TransformBox(otherGridBounds));
+ var aabb1 = tuple.grid.Comp2.LocalAABB.Intersect(tuple.invWorldMatrix.TransformBox(otherGridBounds));
// TODO: AddPair has a nasty check in there that's O(n) but that's also a general physics problem.
- var ourChunks = tuple._map.GetLocalMapChunks(tuple.gridUid, tuple.grid, aabb1);
- var physicsA = tuple.physicsQuery.GetComponent(tuple.gridUid);
+ var ourChunks = tuple._map.GetLocalMapChunks(tuple.grid.Owner, tuple.grid, aabb1);
+ var physicsA = tuple.grid.Comp3;
var physicsB = tuple.physicsQuery.GetComponent(uid);
+ var fixturesB = tuple.fixturesQuery.Comp(uid);
// Only care about chunks on other grid overlapping us.
while (ourChunks.MoveNext(out var ourChunk))
{
var ourChunkWorld =
tuple.worldMatrix.TransformBox(
- ourChunk.CachedBounds.Translated(ourChunk.Indices * tuple.grid.ChunkSize));
+ ourChunk.CachedBounds.Translated(ourChunk.Indices * tuple.grid.Comp2.ChunkSize));
var ourChunkOtherRef = otherGridInvMatrix.TransformBox(ourChunkWorld);
var collidingChunks = tuple._map.GetLocalMapChunks(uid, component, ourChunkOtherRef);
while (collidingChunks.MoveNext(out var collidingChunk))
{
- foreach (var (ourId, fixture) in ourChunk.Fixtures)
+ foreach (var ourId in ourChunk.Fixtures)
{
+ var fixture = tuple.grid.Comp1.Fixtures[ourId];
+
for (var i = 0; i < fixture.Shape.ChildCount; i++)
{
var fixAABB = fixture.Shape.ComputeAABB(tuple.transform, i);
- foreach (var (otherId, otherFixture) in collidingChunk.Fixtures)
+ foreach (var otherId in collidingChunk.Fixtures)
{
+ var otherFixture = fixturesB.Fixtures[otherId];
+
for (var j = 0; j < otherFixture.Shape.ChildCount; j++)
{
var otherAABB = otherFixture.Shape.ComputeAABB(otherTransform, j);
if (!fixAABB.Intersects(otherAABB)) continue;
- tuple._physicsSystem.AddPair(tuple.gridUid, uid,
+ tuple._physicsSystem.AddPair(tuple.grid.Owner, uid,
ourId, otherId,
fixture, i,
otherFixture, j,
diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Fixtures.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Fixtures.cs
index f5199867d13..907a03a4775 100644
--- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Fixtures.cs
+++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Fixtures.cs
@@ -81,7 +81,7 @@ public bool IsCurrentlyHardCollidable(Entity bodyA
if (!_fixturesQuery.Resolve(bodyA, ref bodyA.Comp1, false) ||
!_fixturesQuery.Resolve(bodyB, ref bodyB.Comp1, false) ||
!PhysicsQuery.Resolve(bodyA, ref bodyA.Comp2, false) ||
- !PhysicsQuery.Resolve(bodyA, ref bodyB.Comp2, false))
+ !PhysicsQuery.Resolve(bodyB, ref bodyB.Comp2, false))
{
return false;
}
diff --git a/Robust.Shared/Serialization/Manager/Definition/DataDefinitionUtility.cs b/Robust.Shared/Serialization/Manager/Definition/DataDefinitionUtility.cs
index 1a6adb89c58..72aec126e46 100644
--- a/Robust.Shared/Serialization/Manager/Definition/DataDefinitionUtility.cs
+++ b/Robust.Shared/Serialization/Manager/Definition/DataDefinitionUtility.cs
@@ -2,7 +2,7 @@
namespace Robust.Shared.Serialization.Manager.Definition;
-public class DataDefinitionUtility
+public static class DataDefinitionUtility
{
public static string AutoGenerateTag(string name)
{
diff --git a/Robust.Shared/Serialization/Manager/SerializationManager.Writing.cs b/Robust.Shared/Serialization/Manager/SerializationManager.Writing.cs
index 40f2e577d5f..3bc5109dc28 100644
--- a/Robust.Shared/Serialization/Manager/SerializationManager.Writing.cs
+++ b/Robust.Shared/Serialization/Manager/SerializationManager.Writing.cs
@@ -260,7 +260,12 @@ public DataNode WriteValue(T value, bool alwaysWrite = false, ISerializationC
return ValueDataNode.Null();
}
- return GetOrCreateWriteGenericDelegate(value, notNullableOverride)(value, alwaysWrite, context);
+ var node = GetOrCreateWriteGenericDelegate(value, notNullableOverride)(value, alwaysWrite, context);
+
+ if (typeof(T) == typeof(object))
+ node.Tag = "!type:" + value.GetType().Name;
+
+ return node;
}
public DataNode WriteValue(ITypeWriter writer, T value, bool alwaysWrite = false,
diff --git a/Robust.Shared/Serialization/TypeSerializers/Implementations/Generic/ObjectSerializer.cs b/Robust.Shared/Serialization/TypeSerializers/Implementations/Generic/ObjectSerializer.cs
new file mode 100644
index 00000000000..d9d93aca47d
--- /dev/null
+++ b/Robust.Shared/Serialization/TypeSerializers/Implementations/Generic/ObjectSerializer.cs
@@ -0,0 +1,92 @@
+using System;
+using Robust.Shared.Reflection;
+using Robust.Shared.IoC;
+using Robust.Shared.Serialization.Manager;
+using Robust.Shared.Serialization.Markdown;
+using Robust.Shared.Serialization.Markdown.Validation;
+using Robust.Shared.Serialization.Markdown.Value;
+using Robust.Shared.Serialization.TypeSerializers.Interfaces;
+using Robust.Shared.Serialization.Manager.Attributes;
+
+namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Generic;
+
+[TypeSerializer]
+public sealed class ObjectSerializer : ITypeSerializer