mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 11:40:52 +01:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e25ead588 | ||
|
|
cfae6e1f95 | ||
|
|
da56851846 | ||
|
|
69ed2c3c33 | ||
|
|
c558a0327b | ||
|
|
3bb7df3254 | ||
|
|
ab6bd19817 | ||
|
|
73ef69aa94 | ||
|
|
656835e7fa | ||
|
|
26c87b5858 | ||
|
|
be36001ab8 | ||
|
|
2002402af8 |
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
@@ -54,6 +54,39 @@ 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
|
||||
|
||||
@@ -82,6 +82,7 @@ namespace Robust.Client.GameObjects
|
||||
var viewport = args.WorldBounds;
|
||||
var worldHandle = args.WorldHandle;
|
||||
|
||||
var fixturesQuery = _entityManager.GetEntityQuery<FixturesComponent>();
|
||||
_grids.Clear();
|
||||
_mapManager.FindGridsIntersecting(currentMap, viewport, ref _grids);
|
||||
foreach (var grid in _grids)
|
||||
@@ -89,13 +90,15 @@ namespace Robust.Client.GameObjects
|
||||
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];
|
||||
|
||||
@@ -960,7 +960,7 @@ namespace Robust.Client.GameStates
|
||||
|
||||
// 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 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeAndStart(Dictionary<NetEntity, EntityState> toCreate)
|
||||
private void InitializeAndStart(
|
||||
Dictionary<NetEntity, EntityState> toCreate,
|
||||
EntityQuery<MetaDataComponent> metas,
|
||||
EntityQuery<TransformComponent> xforms)
|
||||
{
|
||||
_toStart.Clear();
|
||||
|
||||
@@ -1197,22 +1200,8 @@ namespace Robust.Client.GameStates
|
||||
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 @@ namespace Robust.Client.GameStates
|
||||
_brokenEnts.Clear();
|
||||
}
|
||||
|
||||
private void InitializeRecursive(
|
||||
EntityUid entity,
|
||||
MetaDataComponent meta,
|
||||
EntityQuery<MetaDataComponent> metas,
|
||||
EntityQuery<TransformComponent> 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)
|
||||
{
|
||||
|
||||
@@ -148,7 +148,7 @@ internal partial class UserInterfaceManager
|
||||
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 @@ internal partial class UserInterfaceManager
|
||||
|
||||
public void UpdateHovered()
|
||||
{
|
||||
var ctrl = MouseGetControl(_inputManager.MouseScreenPosition);
|
||||
var ctrl = MouseGetControl(_inputManager.MouseScreenPosition);
|
||||
SetHovered(ctrl);
|
||||
}
|
||||
|
||||
|
||||
@@ -215,6 +215,9 @@ namespace Robust.Client.UserInterface
|
||||
{
|
||||
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);
|
||||
|
||||
@@ -81,6 +81,7 @@ namespace Robust.Server.GameObjects
|
||||
InitializeEntity(entity, meta);
|
||||
}
|
||||
|
||||
[Obsolete("Use StartEntity")]
|
||||
void IServerEntityManagerInternal.FinishEntityStartup(EntityUid entity)
|
||||
{
|
||||
StartEntity(entity);
|
||||
|
||||
@@ -110,6 +110,7 @@ namespace Robust.Shared.GameObjects
|
||||
return dict.Count;
|
||||
}
|
||||
|
||||
[Obsolete("Use InitializeEntity")]
|
||||
public void InitializeComponents(EntityUid uid, MetaDataComponent? metadata = null)
|
||||
{
|
||||
DebugTools.AssertOwner(uid, metadata);
|
||||
@@ -145,6 +146,7 @@ namespace Robust.Shared.GameObjects
|
||||
SetLifeStage(metadata, EntityLifeStage.Initialized);
|
||||
}
|
||||
|
||||
[Obsolete("Use StartEntity")]
|
||||
public void StartComponents(EntityUid uid)
|
||||
{
|
||||
// Startup() can modify _components
|
||||
@@ -342,6 +344,8 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
private void AddComponentInternal<T>(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 @@ namespace Robust.Shared.GameObjects
|
||||
bool terminating,
|
||||
MetaDataComponent? meta)
|
||||
{
|
||||
ThreadCheck();
|
||||
|
||||
if (component.Deleted)
|
||||
{
|
||||
_sawmill.Warning($"Deleting an already deleted component. Entity: {ToPrettyString(uid)}, Component: {_componentFactory.GetComponentName(component.GetType())}.");
|
||||
|
||||
@@ -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 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
public bool Initialized { get; protected set; }
|
||||
|
||||
#if DEBUG
|
||||
private int _mainThreadId;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of <see cref="EntityManager"/>.
|
||||
/// </summary>
|
||||
@@ -138,6 +143,10 @@ namespace Robust.Shared.GameObjects
|
||||
_sawmill = LogManager.GetSawmill("entity");
|
||||
_resolveSawmill = LogManager.GetSawmill("resolve");
|
||||
|
||||
#if DEBUG
|
||||
_mainThreadId = Environment.CurrentManagedThreadId;
|
||||
#endif
|
||||
|
||||
Initialized = true;
|
||||
}
|
||||
|
||||
@@ -511,6 +520,8 @@ namespace Robust.Shared.GameObjects
|
||||
if (!Started)
|
||||
return;
|
||||
|
||||
ThreadCheck();
|
||||
|
||||
if (meta.EntityLifeStage >= EntityLifeStage.Deleted)
|
||||
return;
|
||||
|
||||
@@ -752,6 +763,8 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
private EntityUid AllocEntity(out MetaDataComponent metadata)
|
||||
{
|
||||
ThreadCheck();
|
||||
|
||||
var uid = GenerateEntityUid();
|
||||
|
||||
#if DEBUG
|
||||
@@ -864,15 +877,35 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
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<MetaDataComponent>(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 @@ namespace Robust.Shared.GameObjects
|
||||
/// Generates a unique network id and increments <see cref="NextNetworkId"/>
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
@@ -2,6 +2,7 @@ namespace Robust.Shared.GameObjects;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly record struct EntityRenamedEvent(string NewName);
|
||||
public readonly record struct EntityRenamedEvent(EntityUid Uid, string OldName, string NewName);
|
||||
|
||||
@@ -22,11 +22,13 @@ namespace Robust.Shared.GameObjects
|
||||
/// <summary>
|
||||
/// Calls Initialize() on all registered components of the entity.
|
||||
/// </summary>
|
||||
[Obsolete("Use InitializeEntity")]
|
||||
void InitializeComponents(EntityUid uid, MetaDataComponent? meta = null);
|
||||
|
||||
/// <summary>
|
||||
/// Calls Startup() on all registered components of the entity.
|
||||
/// </summary>
|
||||
[Obsolete("Use StartEntity")]
|
||||
void StartComponents(EntityUid uid);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -252,7 +252,7 @@ public sealed partial class EntityLookupSystem
|
||||
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 @@ public sealed partial class EntityLookupSystem
|
||||
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 sealed partial class EntityLookupSystem
|
||||
{
|
||||
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<EntityUid> GetEntitiesIntersecting(MapId mapId, Box2 worldAABB, LookupFlags flags = DefaultFlags)
|
||||
@@ -487,23 +469,8 @@ public sealed partial class EntityLookupSystem
|
||||
{
|
||||
if (mapId == MapId.Nullspace) return;
|
||||
|
||||
// Get grid entities
|
||||
var state = (this, intersecting, worldAABB, _transform, flags);
|
||||
|
||||
_mapManager.FindGridsIntersecting(mapId, worldAABB, ref state,
|
||||
static (EntityUid gridUid, MapGridComponent _, ref (
|
||||
EntityLookupSystem lookup, HashSet<EntityUid> 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 sealed partial class EntityLookupSystem
|
||||
public bool AnyEntitiesIntersecting(MapId mapId, Box2Rotated worldBounds, LookupFlags flags = DefaultFlags)
|
||||
{
|
||||
// Don't need to check contained entities as they have the same bounds as the parent.
|
||||
var worldAABB = worldBounds.CalcBoundingBox();
|
||||
|
||||
const bool found = false;
|
||||
var state = (this, worldBounds, flags, found);
|
||||
|
||||
_mapManager.FindGridsIntersecting(mapId, worldAABB, ref state,
|
||||
static (EntityUid uid, MapGridComponent grid, ref (EntityLookupSystem lookup, Box2Rotated worldBounds, LookupFlags flags, bool found) tuple) =>
|
||||
{
|
||||
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<EntityUid> GetEntitiesIntersecting(MapId mapId, Box2Rotated worldBounds, LookupFlags flags = DefaultFlags)
|
||||
{
|
||||
var intersecting = new HashSet<EntityUid>();
|
||||
|
||||
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<EntityUid> 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 sealed partial class EntityLookupSystem
|
||||
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<EntityUid> GetEntitiesInRange(EntityUid uid, float range, LookupFlags flags = DefaultFlags)
|
||||
|
||||
@@ -542,7 +542,7 @@ public sealed partial class EntityLookupSystem
|
||||
{
|
||||
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 sealed partial class EntityLookupSystem
|
||||
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 sealed partial class EntityLookupSystem
|
||||
{
|
||||
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 sealed partial class EntityLookupSystem
|
||||
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 sealed partial class EntityLookupSystem
|
||||
|
||||
public void GetEntitiesInRange<T>(MapId mapId, Vector2 worldPos, float range, HashSet<Entity<T>> 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);
|
||||
}
|
||||
|
||||
@@ -47,12 +47,14 @@ public abstract class MetaDataSystem : EntitySystem
|
||||
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);
|
||||
|
||||
@@ -94,9 +94,9 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
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 @@ namespace Robust.Shared.GameObjects
|
||||
// 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 @@ namespace Robust.Shared.GameObjects
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ using Robust.Shared.Map.Events;
|
||||
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 @@ public abstract partial class SharedMapSystem
|
||||
private void OnGridHandleState(EntityUid uid, MapGridComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
HashSet<MapChunk> modifiedChunks;
|
||||
|
||||
switch (args.Current)
|
||||
{
|
||||
case MapGridComponentDeltaState delta:
|
||||
@@ -257,9 +259,9 @@ public abstract partial class SharedMapSystem
|
||||
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 @@ public abstract partial class SharedMapSystem
|
||||
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 @@ public abstract partial class SharedMapSystem
|
||||
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 @@ public abstract partial class SharedMapSystem
|
||||
#endif
|
||||
}
|
||||
|
||||
private void ApplyChunkData(EntityUid uid, MapGridComponent component, ChunkDatum data,
|
||||
private void ApplyChunkData(
|
||||
EntityUid uid,
|
||||
MapGridComponent component,
|
||||
Vector2i index,
|
||||
ChunkDatum data,
|
||||
HashSet<MapChunk> modifiedChunks)
|
||||
{
|
||||
bool shapeChanged = false;
|
||||
@@ -315,7 +317,7 @@ public abstract partial class SharedMapSystem
|
||||
|
||||
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 @@ public abstract partial class SharedMapSystem
|
||||
|
||||
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 @@ public abstract partial class SharedMapSystem
|
||||
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 @@ public abstract partial class SharedMapSystem
|
||||
return;
|
||||
}
|
||||
|
||||
List<ChunkDatum>? chunkData;
|
||||
Dictionary<Vector2i, ChunkDatum>? chunkData;
|
||||
var fromTick = args.FromTick;
|
||||
|
||||
if (component.LastTileModifiedTick < fromTick)
|
||||
@@ -381,7 +394,7 @@ public abstract partial class SharedMapSystem
|
||||
}
|
||||
else
|
||||
{
|
||||
chunkData = new List<ChunkDatum>();
|
||||
chunkData = new Dictionary<Vector2i, ChunkDatum>();
|
||||
|
||||
foreach (var (tick, indices) in component.ChunkDeletionHistory)
|
||||
{
|
||||
@@ -391,7 +404,7 @@ public abstract partial class SharedMapSystem
|
||||
// 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 @@ public abstract partial class SharedMapSystem
|
||||
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 @@ public abstract partial class SharedMapSystem
|
||||
return;
|
||||
|
||||
HashSet<Vector2> 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 @@ public abstract partial class SharedMapSystem
|
||||
|
||||
private void GetFullState(EntityUid uid, MapGridComponent component, ref ComponentGetState args)
|
||||
{
|
||||
var chunkData = new Dictionary<Vector2i, Tile[]>();
|
||||
var chunkData = new Dictionary<Vector2i, ChunkDatum>();
|
||||
|
||||
foreach (var (index, chunk) in GetMapChunks(uid, component))
|
||||
{
|
||||
@@ -453,7 +466,7 @@ public abstract partial class SharedMapSystem
|
||||
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 @@ public abstract partial class SharedMapSystem
|
||||
#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 @@ public abstract partial class SharedMapSystem
|
||||
|
||||
if (TryComp<GridTreeComponent>(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 @@ public abstract partial class SharedMapSystem
|
||||
|
||||
if (TryComp<GridTreeComponent>(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 @@ public abstract partial class SharedMapSystem
|
||||
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 @@ public abstract partial class SharedMapSystem
|
||||
}
|
||||
}
|
||||
|
||||
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 @@ public abstract partial class SharedMapSystem
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -5,6 +5,7 @@ using Robust.Shared.Map;
|
||||
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 @@ namespace Robust.Shared.GameObjects
|
||||
[Dependency] private readonly IComponentFactory _factory = default!;
|
||||
[Dependency] private readonly MetaDataSystem _meta = default!;
|
||||
|
||||
private EntityQuery<FixturesComponent> _fixturesQuery;
|
||||
private EntityQuery<MapComponent> _mapQuery;
|
||||
private EntityQuery<MapGridComponent> _gridQuery;
|
||||
private EntityQuery<MetaDataComponent> _metaQuery;
|
||||
@@ -34,6 +36,7 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_fixturesQuery = GetEntityQuery<FixturesComponent>();
|
||||
_mapQuery = GetEntityQuery<MapComponent>();
|
||||
_gridQuery = GetEntityQuery<MapGridComponent>();
|
||||
_metaQuery = GetEntityQuery<MetaDataComponent>();
|
||||
|
||||
@@ -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<string>? 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)
|
||||
{
|
||||
if (data.TileData != null)
|
||||
{
|
||||
TileData = new Tile[data.TileData.Length];
|
||||
data.TileData.CopyTo(TileData, 0);
|
||||
}
|
||||
|
||||
if (data.Fixtures != null)
|
||||
{
|
||||
Fixtures = new HashSet<string>(data.Fixtures);
|
||||
}
|
||||
|
||||
CachedBounds = data.CachedBounds;
|
||||
}
|
||||
|
||||
private ChunkDatum(Tile[] tileData, HashSet<string> fixtures, Box2i cachedBounds)
|
||||
{
|
||||
Index = index;
|
||||
TileData = tileData;
|
||||
Fixtures = fixtures;
|
||||
CachedBounds = cachedBounds;
|
||||
}
|
||||
|
||||
public static ChunkDatum CreateModified(Vector2i index, Tile[] tileData)
|
||||
public static ChunkDatum CreateModified(Tile[] tileData, HashSet<string> fixtures, Box2i cachedBounds)
|
||||
{
|
||||
return new ChunkDatum(index, tileData);
|
||||
}
|
||||
|
||||
public static ChunkDatum CreateDeleted(Vector2i index)
|
||||
{
|
||||
return new ChunkDatum(index, null!);
|
||||
return new ChunkDatum(tileData, fixtures, cachedBounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -335,7 +335,7 @@ namespace Robust.Shared.Map.Components
|
||||
/// Serialized state of a <see cref="MapGridComponentState"/>.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
internal sealed class MapGridComponentState(ushort chunkSize, Dictionary<Vector2i, Tile[]> fullGridData, GameTick lastTileModifiedTick) : ComponentState
|
||||
internal sealed class MapGridComponentState(ushort chunkSize, Dictionary<Vector2i, ChunkDatum> fullGridData, GameTick lastTileModifiedTick) : ComponentState
|
||||
{
|
||||
/// <summary>
|
||||
/// The size of the chunks in the map grid.
|
||||
@@ -345,7 +345,7 @@ namespace Robust.Shared.Map.Components
|
||||
/// <summary>
|
||||
/// Networked chunk data containing the full grid state.
|
||||
/// </summary>
|
||||
public Dictionary<Vector2i, Tile[]> FullGridData = fullGridData;
|
||||
public Dictionary<Vector2i, ChunkDatum> FullGridData = fullGridData;
|
||||
|
||||
/// <summary>
|
||||
/// Last game tick that the tile on the grid was modified.
|
||||
@@ -357,7 +357,7 @@ namespace Robust.Shared.Map.Components
|
||||
/// Serialized state of a <see cref="MapGridComponentState"/>.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
internal sealed class MapGridComponentDeltaState(ushort chunkSize, List<ChunkDatum>? chunkData, GameTick lastTileModifiedTick)
|
||||
internal sealed class MapGridComponentDeltaState(ushort chunkSize, Dictionary<Vector2i, ChunkDatum>? chunkData, GameTick lastTileModifiedTick)
|
||||
: ComponentState, IComponentDeltaState<MapGridComponentState>
|
||||
{
|
||||
/// <summary>
|
||||
@@ -368,7 +368,7 @@ namespace Robust.Shared.Map.Components
|
||||
/// <summary>
|
||||
/// Networked chunk data.
|
||||
/// </summary>
|
||||
public readonly List<ChunkDatum>? ChunkData = chunkData;
|
||||
public readonly Dictionary<Vector2i, ChunkDatum>? ChunkData = chunkData;
|
||||
|
||||
/// <summary>
|
||||
/// Last game tick that the tile on the grid was modified.
|
||||
@@ -382,12 +382,12 @@ namespace Robust.Shared.Map.Components
|
||||
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 @@ namespace Robust.Shared.Map.Components
|
||||
|
||||
public MapGridComponentState CreateNewFullState(MapGridComponentState state)
|
||||
{
|
||||
var fullGridData = new Dictionary<Vector2i, Tile[]>(state.FullGridData.Count);
|
||||
var fullGridData = new Dictionary<Vector2i, ChunkDatum>(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);
|
||||
|
||||
@@ -39,12 +39,11 @@ namespace Robust.Shared.Map
|
||||
/// <summary>
|
||||
/// Chunk-local AABB of this chunk.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Box2i CachedBounds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Physics fixtures that make up this grid chunk.
|
||||
/// </summary>
|
||||
public Dictionary<string, Fixture> Fixtures { get; } = new();
|
||||
[ViewVariables]
|
||||
internal HashSet<string> Fixtures = new();
|
||||
|
||||
/// <summary>
|
||||
/// The last game simulation tick that a tile on this chunk was modified.
|
||||
|
||||
@@ -18,14 +18,16 @@ internal partial class MapManager
|
||||
ChunkEnumerator enumerator,
|
||||
IPhysShape shape,
|
||||
Transform shapeTransform,
|
||||
EntityUid gridUid)
|
||||
Entity<FixturesComponent> 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 @@ internal partial class MapManager
|
||||
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 @@ internal partial class MapManager
|
||||
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 @@ internal partial class MapManager
|
||||
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,
|
||||
|
||||
@@ -249,24 +249,36 @@ namespace Robust.Shared.Physics.Systems
|
||||
|
||||
// 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<FixturesComponent, MapGridComponent, PhysicsComponent>(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<FixturesComponent, MapGridComponent, PhysicsComponent> grid,
|
||||
Transform transform,
|
||||
Matrix3x2 worldMatrix,
|
||||
Matrix3x2 invWorldMatrix,
|
||||
SharedMapSystem _map,
|
||||
SharedPhysicsSystem _physicsSystem,
|
||||
SharedTransformSystem xformSystem,
|
||||
EntityQuery<FixturesComponent> fixturesQuery,
|
||||
EntityQuery<PhysicsComponent> physicsQuery,
|
||||
EntityQuery<TransformComponent> xformQuery) tuple) =>
|
||||
{
|
||||
if (tuple.gridUid == uid ||
|
||||
if (tuple.grid.Owner == uid ||
|
||||
!tuple.xformQuery.TryGetComponent(uid, out var collidingXform))
|
||||
{
|
||||
return true;
|
||||
@@ -277,38 +289,43 @@ namespace Robust.Shared.Physics.Systems
|
||||
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,
|
||||
|
||||
@@ -81,7 +81,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
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;
|
||||
}
|
||||
@@ -103,7 +103,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using NUnit.Framework;
|
||||
|
||||
// So it can use RobustServerSimulation.
|
||||
[assembly: InternalsVisibleTo("Robust.Benchmarks")]
|
||||
|
||||
[assembly: Parallelizable(ParallelScope.Fixtures)]
|
||||
|
||||
@@ -18,6 +18,12 @@ namespace Robust.UnitTesting.Shared
|
||||
{
|
||||
private static readonly MapId MapId = new MapId(1);
|
||||
|
||||
private static readonly TestCaseData[] IntersectingCases = new[]
|
||||
{
|
||||
// Big offset
|
||||
new TestCaseData(true, new MapCoordinates(new Vector2(10.5f, 10.5f), MapId), new MapCoordinates(new Vector2(10.5f, 10.5f), MapId), 0.25f, true),
|
||||
};
|
||||
|
||||
private static readonly TestCaseData[] InRangeCases = new[]
|
||||
{
|
||||
new TestCaseData(true, new MapCoordinates(Vector2.One, MapId), new MapCoordinates(Vector2.Zero, MapId), 0.5f, false),
|
||||
@@ -207,6 +213,32 @@ namespace Robust.UnitTesting.Shared
|
||||
mapManager.DeleteMap(spawnPos.MapId);
|
||||
}
|
||||
|
||||
[Test, TestCaseSource(nameof(IntersectingCases))]
|
||||
public void TestGridIntersecting(bool physics, MapCoordinates spawnPos, MapCoordinates queryPos, float range, bool result)
|
||||
{
|
||||
var sim = RobustServerSimulation.NewSimulation();
|
||||
var server = sim.InitializeInstance();
|
||||
|
||||
var lookup = server.Resolve<IEntitySystemManager>().GetEntitySystem<EntityLookupSystem>();
|
||||
var entManager = server.Resolve<IEntityManager>();
|
||||
var mapManager = server.Resolve<IMapManager>();
|
||||
var mapSystem = entManager.System<SharedMapSystem>();
|
||||
|
||||
mapSystem.CreateMap(spawnPos.MapId);
|
||||
var grid = SetupGrid(spawnPos.MapId, mapSystem, entManager, mapManager);
|
||||
|
||||
if (physics)
|
||||
GetPhysicsEntity(entManager, spawnPos);
|
||||
else
|
||||
entManager.Spawn(null, spawnPos);
|
||||
|
||||
_ = entManager.SpawnEntity(null, spawnPos);
|
||||
var bounds = new Box2Rotated(Box2.CenteredAround(queryPos.Position, new Vector2(range, range)));
|
||||
|
||||
Assert.That(lookup.GetEntitiesIntersecting(queryPos.MapId, bounds).Count > 0, Is.EqualTo(result));
|
||||
mapManager.DeleteMap(spawnPos.MapId);
|
||||
}
|
||||
|
||||
[Test, TestCaseSource(nameof(InRangeCases))]
|
||||
public void TestGridInRange(bool physics, MapCoordinates spawnPos, MapCoordinates queryPos, float range, bool result)
|
||||
{
|
||||
|
||||
@@ -38,7 +38,11 @@ public sealed partial class ComponentStateTests : RobustIntegrationTest
|
||||
server.Post(() => server.CfgMan.SetCVar(CVars.NetPVS, true));
|
||||
|
||||
// Set up map.
|
||||
var map = server.System<SharedMapSystem>().CreateMap();
|
||||
EntityUid map = default;
|
||||
server.Post(() =>
|
||||
{
|
||||
map = server.System<SharedMapSystem>().CreateMap();
|
||||
});
|
||||
|
||||
await RunTicks();
|
||||
|
||||
@@ -157,7 +161,11 @@ public sealed partial class ComponentStateTests : RobustIntegrationTest
|
||||
server.Post(() => server.CfgMan.SetCVar(CVars.NetPVS, true));
|
||||
|
||||
// Set up map.
|
||||
var map = server.System<SharedMapSystem>().CreateMap();
|
||||
EntityUid map = default;
|
||||
server.Post(() =>
|
||||
{
|
||||
map = server.System<SharedMapSystem>().CreateMap();
|
||||
});
|
||||
|
||||
await RunTicks();
|
||||
|
||||
|
||||
@@ -37,7 +37,10 @@ public sealed partial class NoSharedReferencesTest : RobustIntegrationTest
|
||||
client.Post(() => netMan.ClientConnect(null!, 0, null!));
|
||||
|
||||
// Set up map.
|
||||
var map = server.System<SharedMapSystem>().CreateMap();
|
||||
server.Post(() =>
|
||||
{
|
||||
server.System<SharedMapSystem>().CreateMap();
|
||||
});
|
||||
|
||||
await RunTicks();
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
@@ -8,62 +9,92 @@ using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.UnitTesting.Server;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.Map
|
||||
namespace Robust.UnitTesting.Shared.Map;
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether grid fixtures are being generated correctly.
|
||||
/// </summary>
|
||||
[Parallelizable(ParallelScope.All)]
|
||||
[TestFixture]
|
||||
public sealed class GridFixtures_Tests : RobustIntegrationTest
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests whether grid fixtures are being generated correctly.
|
||||
/// Tests that grid fixtures match what's expected.
|
||||
/// </summary>
|
||||
[Parallelizable(ParallelScope.All)]
|
||||
[TestFixture]
|
||||
public sealed class GridFixtures_Tests : RobustIntegrationTest
|
||||
[Test]
|
||||
public void TestGridFixtureDeletion()
|
||||
{
|
||||
[Test]
|
||||
public async Task TestGridFixtures()
|
||||
var server = RobustServerSimulation.NewSimulation().InitializeInstance();
|
||||
var map = server.CreateMap();
|
||||
var entManager = server.Resolve<IEntityManager>();
|
||||
var grid = server.Resolve<IMapManager>().CreateGridEntity(map.MapId);
|
||||
var mapSystem = entManager.System<SharedMapSystem>();
|
||||
var fixtures = entManager.GetComponent<FixturesComponent>(grid);
|
||||
|
||||
mapSystem.SetTiles(grid, new List<(Vector2i GridIndices, Tile Tile)>()
|
||||
{
|
||||
var server = StartServer();
|
||||
await server.WaitIdleAsync();
|
||||
(Vector2i.Zero, new Tile(1)),
|
||||
(Vector2i.Right, new Tile(1)),
|
||||
(Vector2i.Right * 2, new Tile(1)),
|
||||
(Vector2i.Up, new Tile(1)),
|
||||
});
|
||||
|
||||
var entManager = server.ResolveDependency<IEntityManager>();
|
||||
var mapManager = server.ResolveDependency<IMapManager>();
|
||||
var physSystem = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<SharedPhysicsSystem>();
|
||||
Assert.That(fixtures.FixtureCount, Is.EqualTo(2));
|
||||
Assert.That(grid.Comp.LocalAABB.Equals(new Box2(0f, 0f, 3f, 2f)));
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
entManager.System<SharedMapSystem>().CreateMap(out var mapId);
|
||||
var grid = mapManager.CreateGridEntity(mapId);
|
||||
mapSystem.SetTile(grid, Vector2i.Up, Tile.Empty);
|
||||
|
||||
// Should be nothing if grid empty
|
||||
Assert.That(entManager.TryGetComponent(grid, out PhysicsComponent? gridBody));
|
||||
Assert.That(entManager.TryGetComponent(grid, out FixturesComponent? manager));
|
||||
Assert.That(manager!.FixtureCount, Is.EqualTo(0));
|
||||
Assert.That(gridBody!.BodyType, Is.EqualTo(BodyType.Static));
|
||||
Assert.That(fixtures.FixtureCount, Is.EqualTo(1));
|
||||
Assert.That(grid.Comp.LocalAABB.Equals(new Box2(0f, 0f, 3f, 1f)));
|
||||
}
|
||||
|
||||
// 1 fixture if we only ever update the 1 chunk
|
||||
grid.Comp.SetTile(Vector2i.Zero, new Tile(1));
|
||||
[Test]
|
||||
public async Task TestGridFixtures()
|
||||
{
|
||||
var server = StartServer();
|
||||
await server.WaitIdleAsync();
|
||||
|
||||
Assert.That(manager.FixtureCount, Is.EqualTo(1));
|
||||
// Also should only be a single tile.
|
||||
var bounds = manager.Fixtures.First().Value.Shape.ComputeAABB(new Transform(Vector2.Zero, (float) Angle.Zero.Theta), 0);
|
||||
// Poly probably has Box2D's radius added to it so won't be a unit square
|
||||
Assert.That(MathHelper.CloseToPercent(Box2.Area(bounds), 1.0f, 0.1f));
|
||||
var entManager = server.ResolveDependency<IEntityManager>();
|
||||
var mapManager = server.ResolveDependency<IMapManager>();
|
||||
var physSystem = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<SharedPhysicsSystem>();
|
||||
|
||||
// Now do 2 tiles (same chunk)
|
||||
grid.Comp.SetTile(new Vector2i(0, 1), new Tile(1));
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
entManager.System<SharedMapSystem>().CreateMap(out var mapId);
|
||||
var grid = mapManager.CreateGridEntity(mapId);
|
||||
|
||||
Assert.That(manager.FixtureCount, Is.EqualTo(1));
|
||||
bounds = manager.Fixtures.First().Value.Shape.ComputeAABB(new Transform(Vector2.Zero, (float) Angle.Zero.Theta), 0);
|
||||
// Should be nothing if grid empty
|
||||
Assert.That(entManager.TryGetComponent(grid, out PhysicsComponent? gridBody));
|
||||
Assert.That(entManager.TryGetComponent(grid, out FixturesComponent? manager));
|
||||
Assert.That(manager!.FixtureCount, Is.EqualTo(0));
|
||||
Assert.That(gridBody!.BodyType, Is.EqualTo(BodyType.Static));
|
||||
|
||||
// Even if we add a new tile old fixture should stay the same if they don't connect.
|
||||
Assert.That(MathHelper.CloseToPercent(Box2.Area(bounds), 2.0f, 0.1f));
|
||||
// 1 fixture if we only ever update the 1 chunk
|
||||
grid.Comp.SetTile(Vector2i.Zero, new Tile(1));
|
||||
|
||||
// If we add a new chunk should be 2 now
|
||||
grid.Comp.SetTile(new Vector2i(0, -1), new Tile(1));
|
||||
Assert.That(manager.FixtureCount, Is.EqualTo(2));
|
||||
Assert.That(manager.FixtureCount, Is.EqualTo(1));
|
||||
// Also should only be a single tile.
|
||||
var bounds = manager.Fixtures.First().Value.Shape.ComputeAABB(new Transform(Vector2.Zero, (float) Angle.Zero.Theta), 0);
|
||||
// Poly probably has Box2D's radius added to it so won't be a unit square
|
||||
Assert.That(MathHelper.CloseToPercent(Box2.Area(bounds), 1.0f, 0.1f));
|
||||
|
||||
physSystem.SetLinearVelocity(grid, Vector2.One, manager: manager, body: gridBody);
|
||||
Assert.That(gridBody.LinearVelocity.Length, Is.EqualTo(0f));
|
||||
});
|
||||
}
|
||||
// Now do 2 tiles (same chunk)
|
||||
grid.Comp.SetTile(new Vector2i(0, 1), new Tile(1));
|
||||
|
||||
Assert.That(manager.FixtureCount, Is.EqualTo(1));
|
||||
bounds = manager.Fixtures.First().Value.Shape.ComputeAABB(new Transform(Vector2.Zero, (float) Angle.Zero.Theta), 0);
|
||||
|
||||
// Even if we add a new tile old fixture should stay the same if they don't connect.
|
||||
Assert.That(MathHelper.CloseToPercent(Box2.Area(bounds), 2.0f, 0.1f));
|
||||
|
||||
// If we add a new chunk should be 2 now
|
||||
grid.Comp.SetTile(new Vector2i(0, -1), new Tile(1));
|
||||
Assert.That(manager.FixtureCount, Is.EqualTo(2));
|
||||
|
||||
physSystem.SetLinearVelocity(grid, Vector2.One, manager: manager, body: gridBody);
|
||||
Assert.That(gridBody.LinearVelocity.Length, Is.EqualTo(0f));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,30 @@ namespace Robust.UnitTesting.Shared.Physics;
|
||||
[TestFixture]
|
||||
public sealed class Collision_Test
|
||||
{
|
||||
[Test]
|
||||
public void TestHardCollidable()
|
||||
{
|
||||
var sim = RobustServerSimulation.NewSimulation().InitializeInstance();
|
||||
var entManager = sim.Resolve<IEntityManager>();
|
||||
|
||||
var fixtures = entManager.System<FixtureSystem>();
|
||||
var physics = entManager.System<SharedPhysicsSystem>();
|
||||
|
||||
var map = sim.CreateMap();
|
||||
|
||||
var bodyAUid = entManager.SpawnAttachedTo(null, new EntityCoordinates(map.Uid, Vector2.Zero));
|
||||
var bodyBUid = entManager.SpawnAttachedTo(null, new EntityCoordinates(map.Uid, Vector2.Zero));
|
||||
var bodyA = entManager.AddComponent<PhysicsComponent>(bodyAUid);
|
||||
var bodyB = entManager.AddComponent<PhysicsComponent>(bodyBUid);
|
||||
|
||||
Assert.That(!physics.IsHardCollidable(bodyAUid, bodyBUid));
|
||||
|
||||
fixtures.CreateFixture(bodyAUid, "fix1", new Fixture(new PhysShapeCircle(0.5f), 1, 1, true));
|
||||
fixtures.CreateFixture(bodyBUid, "fix1", new Fixture(new PhysShapeCircle(0.5f), 1, 1, true));
|
||||
|
||||
Assert.That(physics.IsHardCollidable(bodyAUid, bodyBUid));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCollision()
|
||||
{
|
||||
|
||||
@@ -28,7 +28,7 @@ def main():
|
||||
|
||||
|
||||
def verify_version(version: str):
|
||||
parts = version.split(".")
|
||||
parts = version.split("-")[0].split(".")
|
||||
if len(parts) != 3:
|
||||
print("Version must be split into three parts with '.'")
|
||||
sys.exit(1)
|
||||
|
||||
Reference in New Issue
Block a user