Compare commits

...

20 Commits

Author SHA1 Message Date
metalgearsloth
380ccfacd8 Version: 141.2.1 2023-07-29 16:19:20 +10:00
Leon Friedrich
cc0cc6afb1 Don't recreate _entTraitDict on disconnect (#4200) 2023-07-29 16:16:25 +10:00
Pieter-Jan Briers
8cc2a17444 Version: 141.2.0 2023-07-28 22:16:59 +02:00
Pieter-Jan Briers
9c64fbfce2 Fix protocol abuse exception. 2023-07-27 19:30:39 +02:00
metalgearsloth
0218c4b969 Version: 141.1.0 2023-07-27 18:22:48 +10:00
Vordenburg
cd72523701 Add CollisionLayerChangeEvent (#4147) 2023-07-27 18:20:29 +10:00
metalgearsloth
76bb9b4b19 Run MapInit on pmanager entities (#4196) 2023-07-27 06:49:09 +12:00
metalgearsloth
afdfbba312 Version: 141.0.0 2023-07-26 22:41:12 +10:00
metalgearsloth
8b4925863e Cull Component.Initialize (#4191) 2023-07-26 22:37:45 +10:00
Pieter-Jan Briers
e1597da4c7 Enable EXCEPTION_TOLERANCE on Tools.
This wasn't the case yet??? Whoops.
2023-07-24 20:52:39 +02:00
Pieter-Jan Briers
8375a4038b Throw error if creation of buffered audio source fails.
This can happen if we're out of audio streams. Before, we just kinda pretended like everything was OK, which easily caused crash bugs in e.g. MIDI.

Ideally the audio engine would be less terrible and this could be handled better than "throw new Exception()", but I'm fixing a stack overflow here alright?
2023-07-24 20:52:11 +02:00
Pieter-Jan Briers
8270442d66 Hard exit server on double ^C. 2023-07-23 17:44:20 +02:00
Pieter-Jan Briers
85e1920b95 Version: 140.0.0 2023-07-23 15:41:44 +02:00
Pieter-Jan Briers
5347eb3350 Replay recording API improvements. (#4193) 2023-07-23 15:36:35 +02:00
metalgearsloth
e4a14d1ec8 Start MapGrid ECS (#4185) 2023-07-23 20:50:23 +10:00
metalgearsloth
c52db4d3f2 Version: 139.0.0 2023-07-23 16:13:06 +10:00
metalgearsloth
89f78d76ab Cull Component.Startup (#4190) 2023-07-23 16:04:37 +10:00
metalgearsloth
bbc4668f9c Version: 138.1.0 2023-07-18 21:41:40 +10:00
metalgearsloth
ce4016965e Add NoLerp methods for rotation (#4186) 2023-07-18 21:40:09 +10:00
Pieter-Jan Briers
21e74c9881 Fix ordering of control AnimationCompleted event.
This was changed recently, and it caused exceptions in SS14 (wire hacking animations).
2023-07-18 01:04:49 +02:00
66 changed files with 1761 additions and 1320 deletions

View File

@@ -23,7 +23,7 @@
<PropertyGroup Condition="'$(FullRelease)' != 'True'">
<DefineConstants>$(DefineConstants);DEVELOPMENT</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<PropertyGroup Condition="'$(Configuration)' == 'Release' Or '$(Configuration)' == 'Tools'">
<DefineConstants>$(DefineConstants);EXCEPTION_TOLERANCE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(EnableClientScripting)' == 'True'">

View File

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

View File

@@ -54,6 +54,80 @@ END TEMPLATE-->
*None yet*
## 141.2.1
### Bugfixes
* Fix component trait dictionaries not clearing on reconnect leading to bad GetComponent in areas (e.g. entire game looks black due to no entities).
## 141.2.0
### Other
* Fix bug in `NetManager` that allowed exception spam through protocol abuse.
## 141.1.0
### New features
* MapInitEvent is run clientside for placementmanager entities to predict entity appearances.
* Add CollisionLayerChangeEvent for physics fixtures.
## 141.0.0
### Breaking changes
* Component.Initialize has been fully replaced with the Eventbus.
### Bugfixes
* Fixed potential crashes if buffered audio sources (e.g. MIDI) fail to create due to running out of audio streams.
### Other
* Pressing `^C` twice on the server will now cause it to hard-exit immediately.
* `Tools` now has `EXCEPTION_TOLERANCE` enabled.
## 140.0.0
### Breaking changes
* `IReplayRecordingManager.RecordingFinished` now takes a `ReplayRecordingFinished` object as argument.
* `IReplayRecordingManager.GetReplayStats` now returns a `ReplayRecordingStats` struct instead of a tuple. The units have also been normalized
### New features
* `IReplayRecordingManager` can now track a "state" object for an active recording.
* If the path given to `IReplayRecordingManager.TryStartRecording` is rooted, the base replay directory is ignored.
### Other
* `IReplayRecordingManager` no longer considers itself recording inside `RecordingFinished`.
* `IReplayRecordingManager.Initialize()` was moved to an engine-internal interface.
## 139.0.0
### Breaking changes
* Remove Component.Startup(), fully replacing it with the Eventbus.
## 138.1.0
### New features
* Add rotation methods to TransformSystem for no lerp.
### Bugfixes
* Fix AnimationCompleted ordering.
## 138.0.0
### Breaking changes

View File

@@ -22,7 +22,7 @@ cmd-replay-skip-hint = Ticks or timespan (HH:MM:SS).
cmd-replay-set-time-desc = Jump forwards or backwards to some specific time.
cmd-replay-set-time-help = replay_set <tick or time>
cmd-replay-set-time-hint = Tick or timespan (HH:MM:SS), starting from
cmd-replay-set-time-hint = Tick or timespan (HH:MM:SS), starting from
cmd-replay-error-time = "{$time}" is not an integer or timespan.
cmd-replay-error-args = Wrong number of arguments.
@@ -33,7 +33,7 @@ cmd-replay-error-run-level = You cannot load a replay while connected to a serve
# Recording commands
cmd-replay-recording-start-desc = Starts a replay recording, optionally with some time limit.
cmd-replay-recording-start-help = Usage: replay_recording_start [name] [overwrite] [time limit]
cmd-replay-recording-start-help = Usage: replay_recording_start [name] [overwrite] [time limit]
cmd-replay-recording-start-success = Started recording a replay.
cmd-replay-recording-start-already-recording = Already recording a replay.
cmd-replay-recording-start-error = An error occurred while trying to start the recording.
@@ -48,7 +48,7 @@ cmd-replay-recording-stop-not-recording = Not currently recording a replay.
cmd-replay-recording-stats-desc = Displays information about the current replay recording.
cmd-replay-recording-stats-help = Usage: replay_recording_stats
cmd-replay-recording-stats-result = Duration: {$time} min, Ticks: {$ticks}, Size: {$size} mb, rate: {$rate} mb/min.
cmd-replay-recording-stats-result = Duration: {$time} min, Ticks: {$ticks}, Size: {$size} MB, rate: {$rate} MB/min.
# Time Control UI
@@ -56,4 +56,4 @@ replay-time-box-scrubbing-label = Dynamic Scrubbing
replay-time-box-replay-time-label = Recording Time: {$current} / {$end} ({$percentage}%)
replay-time-box-server-time-label = Server Time: {$current} / {$end}
replay-time-box-index-label = Index: {$current} / {$total}
replay-time-box-tick-label = Tick: {$current} / {$total}
replay-time-box-tick-label = Tick: {$current} / {$total}

View File

@@ -84,6 +84,7 @@ namespace Robust.Client
deps.Register<IReplayLoadManager, ReplayLoadManager>();
deps.Register<IReplayPlaybackManager, ReplayPlaybackManager>();
deps.Register<IReplayRecordingManager, ReplayRecordingManager>();
deps.Register<IReplayRecordingManagerInternal, ReplayRecordingManager>();
deps.Register<IClientGameStateManager, ClientGameStateManager>();
deps.Register<IBaseClient, BaseClient>();
deps.Register<IPlayerManager, PlayerManager>();

View File

@@ -621,6 +621,7 @@ namespace Robust.Client.Console.Commands
internal sealed class ChunkInfoCommand : LocalizedCommands
{
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IMapManager _map = default!;
[Dependency] private readonly IEyeManager _eye = default!;
[Dependency] private readonly IInputManager _input = default!;
@@ -631,16 +632,17 @@ namespace Robust.Client.Console.Commands
{
var mousePos = _eye.ScreenToMap(_input.MouseScreenPosition);
if (!_map.TryFindGridAt(mousePos, out _, out var grid))
if (!_map.TryFindGridAt(mousePos, out var gridUid, out var grid))
{
shell.WriteLine("No grid under your mouse cursor.");
return;
}
var chunkIndex = grid.LocalToChunkIndices(grid.MapToGrid(mousePos));
var chunk = grid.GetOrAddChunk(chunkIndex);
var mapSystem = _entManager.System<SharedMapSystem>();
var chunkIndex = mapSystem.LocalToChunkIndices(gridUid, grid, grid.MapToGrid(mousePos));
var chunk = mapSystem.GetOrAddChunk(gridUid, grid, chunkIndex);
shell.WriteLine($"worldBounds: {grid.CalcWorldAABB(chunk)} localBounds: {chunk.CachedBounds}");
shell.WriteLine($"worldBounds: {mapSystem.CalcWorldAABB(gridUid, grid, chunk)} localBounds: {chunk.CachedBounds}");
}
}

View File

@@ -84,7 +84,7 @@ namespace Robust.Client
[Dependency] private readonly NetworkResourceManager _netResMan = default!;
[Dependency] private readonly IReplayLoadManager _replayLoader = default!;
[Dependency] private readonly IReplayPlaybackManager _replayPlayback = default!;
[Dependency] private readonly IReplayRecordingManager _replayRecording = default!;
[Dependency] private readonly IReplayRecordingManagerInternal _replayRecording = default!;
private IWebViewManagerHook? _webViewHook;
@@ -766,6 +766,8 @@ namespace Robust.Client
internal void CleanupGameThread()
{
_replayRecording.Shutdown();
_modLoader.Shutdown();
// CEF specifically makes a massive silent stink of it if we don't shut it down from the correct thread.

View File

@@ -15,15 +15,12 @@ namespace Robust.Client.GameObjects
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[ViewVariables]
private Eye? _eye = default!;
[ViewVariables] internal Eye? _eye = default!;
// Horrible hack to get around ordering issues.
private bool _setCurrentOnInitialize;
[DataField("drawFov")]
private bool _setDrawFovOnInitialize = true;
[DataField("zoom")]
private Vector2 _setZoomOnInitialize = Vector2.One;
internal bool _setCurrentOnInitialize;
[DataField("drawFov")] internal bool _setDrawFovOnInitialize = true;
[DataField("zoom")] internal Vector2 _setZoomOnInitialize = Vector2.One;
/// <summary>
/// If not null, this entity is used to update the eye's position instead of just using the component's owner.
@@ -119,31 +116,6 @@ namespace Robust.Client.GameObjects
[ViewVariables]
public MapCoordinates? Position => _eye?.Position;
/// <inheritdoc />
protected override void Initialize()
{
base.Initialize();
_eye = new Eye
{
Position = _entityManager.GetComponent<TransformComponent>(Owner).MapPosition,
Zoom = _setZoomOnInitialize,
DrawFov = _setDrawFovOnInitialize
};
if ((_eyeManager.CurrentEye == _eye) != _setCurrentOnInitialize)
{
if (_setCurrentOnInitialize)
{
_eyeManager.ClearCurrentEye();
}
else
{
_eyeManager.CurrentEye = _eye;
}
}
}
/// <summary>
/// Updates the Eye of this entity with the transform position. This has to be called every frame to
/// keep the view following the entity.

View File

@@ -40,7 +40,7 @@ internal sealed class ClientOccluderSystem : OccluderSystem
return;
comp.Enabled = enabled;
Dirty(comp);
Dirty(uid, comp);
var xform = Transform(uid);
QueueTreeUpdate(uid, comp, xform);

View File

@@ -38,7 +38,8 @@ public sealed class DebugEntityLookupSystem : EntitySystem
IoCManager.Resolve<IOverlayManager>().AddOverlay(
new EntityLookupOverlay(
EntityManager,
Get<EntityLookupSystem>()));
EntityManager.System<EntityLookupSystem>(),
EntityManager.System<SharedTransformSystem>()));
}
else
{
@@ -52,31 +53,35 @@ public sealed class DebugEntityLookupSystem : EntitySystem
public sealed class EntityLookupOverlay : Overlay
{
private IEntityManager _entityManager = default!;
private EntityLookupSystem _lookup = default!;
private readonly IEntityManager _entityManager;
private readonly EntityLookupSystem _lookup;
private readonly SharedTransformSystem _transform;
private EntityQuery<TransformComponent> _xformQuery;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public EntityLookupOverlay(IEntityManager entManager, EntityLookupSystem lookup)
public EntityLookupOverlay(IEntityManager entManager, EntityLookupSystem lookup, SharedTransformSystem transform)
{
_entityManager = entManager;
_lookup = lookup;
_xformQuery = entManager.GetEntityQuery<TransformComponent>();
_transform = transform;
}
protected internal override void Draw(in OverlayDrawArgs args)
{
var worldHandle = args.WorldHandle;
var xformQuery = _entityManager.GetEntityQuery<TransformComponent>();
var worldBounds = args.WorldBounds;
foreach (var lookup in _lookup.FindLookupsIntersecting(args.MapId, args.WorldBounds))
// TODO: Static version
_lookup.FindLookupsIntersecting(args.MapId, worldBounds, (uid, lookup) =>
{
var lookupXform = xformQuery.GetComponent(lookup.Owner);
var (_, rotation, matrix, invMatrix) = lookupXform.GetWorldPositionRotationMatrixWithInv();
var (_, rotation, matrix, invMatrix) = _transform.GetWorldPositionRotationMatrixWithInv(uid);
worldHandle.SetTransform(matrix);
var lookupAABB = invMatrix.TransformBox(args.WorldBounds);
var lookupAABB = invMatrix.TransformBox(worldBounds);
var ents = new List<EntityUid>();
lookup.DynamicTree.QueryAabb(ref ents, static (ref List<EntityUid> state, in FixtureProxy value) =>
@@ -105,20 +110,22 @@ public sealed class EntityLookupOverlay : Overlay
foreach (var ent in ents)
{
if (_entityManager.Deleted(ent)) continue;
var xform = xformQuery.GetComponent(ent);
if (_entityManager.Deleted(ent))
continue;
var xform = _xformQuery.GetComponent(ent);
//DebugTools.Assert(!ent.IsInContainer(_entityManager));
var (entPos, entRot) = xform.GetWorldPositionRotation();
var (entPos, entRot) = _transform.GetWorldPositionRotation(ent);
var lookupPos = invMatrix.Transform(entPos);
var lookupRot = entRot - rotation;
var aabb = _lookup.GetAABB(ent, lookupPos, lookupRot, xform, xformQuery);
var aabb = _lookup.GetAABB(ent, lookupPos, lookupRot, xform, _xformQuery);
worldHandle.DrawRect(aabb, Color.Blue.WithAlpha(0.2f));
}
}
});
worldHandle.SetTransform(Matrix3.Identity);
}

View File

@@ -1,17 +1,44 @@
using Robust.Client.Graphics;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
namespace Robust.Client.GameObjects;
public sealed class EyeSystem : SharedEyeSystem
{
[Dependency] private readonly IEyeManager _eyeManager = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<EyeComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<EyeComponent, ComponentRemove>(OnRemove);
SubscribeLocalEvent<EyeComponent, ComponentHandleState>(OnHandleState);
}
private void OnInit(EntityUid uid, EyeComponent component, ComponentInit args)
{
component._eye = new Eye
{
Position = Transform(uid).MapPosition,
Zoom = component._setZoomOnInitialize,
DrawFov = component._setDrawFovOnInitialize
};
if ((_eyeManager.CurrentEye == component._eye) != component._setCurrentOnInitialize)
{
if (component._setCurrentOnInitialize)
{
_eyeManager.ClearCurrentEye();
}
else
{
_eyeManager.CurrentEye = component._eye;
}
}
}
private void OnRemove(EntityUid uid, EyeComponent component, ComponentRemove args)
{
component.Current = false;

View File

@@ -37,7 +37,7 @@ namespace Robust.Client.GameObjects
return;
comp.ContainerOccluded = occluded;
Dirty(comp);
Dirty(uid, comp);
if (comp.Enabled)
_lightTree.QueueTreeUpdate(uid, comp);
@@ -50,7 +50,7 @@ namespace Robust.Client.GameObjects
comp._enabled = enabled;
RaiseLocalEvent(uid, new PointLightToggleEvent(comp.Enabled));
Dirty(comp);
Dirty(uid, comp);
var cast = (PointLightComponent)comp;
if (!cast.ContainerOccluded)
@@ -63,7 +63,7 @@ namespace Robust.Client.GameObjects
return;
comp._radius = radius;
Dirty(comp);
Dirty(uid, comp);
var cast = (PointLightComponent)comp;
if (cast.TreeUid != null)

View File

@@ -22,6 +22,13 @@ public sealed partial class TransformSystem
base.SetLocalPositionNoLerp(xform, value);
}
public override void SetLocalRotationNoLerp(TransformComponent xform, Angle angle)
{
xform.NextRotation = null;
xform.LerpParent = EntityUid.Invalid;
base.SetLocalRotationNoLerp(xform, angle);
}
public override void SetLocalRotation(TransformComponent xform, Angle angle)
{
xform.PrevRotation = xform._localRotation;

View File

@@ -261,6 +261,9 @@ namespace Robust.Client.Graphics.Audio
{
var source = AL.GenSource();
if (!AL.IsSource(source))
throw new Exception("Failed to generate source. Too many simultaneous audio streams?");
// ReSharper disable once PossibleInvalidOperationException
var audioSource = new BufferedAudioSource(this, source, AL.GenBuffers(buffers), floatAudio);

View File

@@ -23,7 +23,7 @@ namespace Robust.Client.Physics
{
if (_enableDebug == value) return;
Sawmill.Info($"Set grid fixture debug to {value}");
Log.Info($"Set grid fixture debug to {value}");
_enableDebug = value;
if (_enableDebug)
@@ -59,7 +59,7 @@ namespace Robust.Client.Physics
private void OnDebugMessage(ChunkSplitDebugMessage ev)
{
Sawmill.Info($"Received grid fixture debug data");
Log.Info($"Received grid fixture debug data");
if (!_enableDebug) return;
_nodes[ev.Grid] = ev.Nodes;

View File

@@ -694,6 +694,9 @@ namespace Robust.Client.Placement
IsActive = true;
CurrentPlacementOverlayEntity = EntityManager.SpawnEntity(templateName, MapCoordinates.Nullspace);
EntityManager.RunMapInit(
CurrentPlacementOverlayEntity.Value,
EntityManager.GetComponent<MetaDataComponent>(CurrentPlacementOverlayEntity.Value));
}
public void PreparePlacementSprite(SpriteComponent sprite)

View File

@@ -78,15 +78,16 @@ internal sealed class ReplayRecordingManager : SharedReplayRecordingManager
IWritableDirProvider directory,
string? name = null,
bool overwrite = false,
TimeSpan? duration = null)
TimeSpan? duration = null,
object? state = null)
{
if (!base.TryStartRecording(directory, name, overwrite, duration))
if (!base.TryStartRecording(directory, name, overwrite, duration, state))
return false;
var (state, detachMsg) = CreateFullState();
var (gameState, detachMsg) = CreateFullState();
if (detachMsg != null)
RecordReplayMessage(detachMsg);
Update(state);
Update(gameState);
return true;
}

View File

@@ -55,12 +55,12 @@ namespace Robust.Client.UserInterface
continue;
toRemove.Add(key);
AnimationCompleted?.Invoke(key);
}
foreach (var key in toRemove)
{
_playingAnimations.Remove(key);
AnimationCompleted?.Invoke(key);
}
}
}

View File

@@ -62,18 +62,19 @@ namespace Robust.Client.UserInterface.CustomControls.DebugMonitorControls
var screenSize = _displayManager.ScreenSize;
var screenScale = _displayManager.MainWindow.ContentScale;
MapCoordinates mouseWorldMap;
EntityCoordinates mouseGridPos;
TileRef tile;
mouseWorldMap = _eyeManager.ScreenToMap(mouseScreenPos);
var mouseWorldMap = _eyeManager.ScreenToMap(mouseScreenPos);
if (mouseWorldMap == MapCoordinates.Nullspace)
return;
if (_mapManager.TryFindGridAt(mouseWorldMap, out _, out var mouseGrid))
var mapSystem = _entityManager.System<SharedMapSystem>();
if (_mapManager.TryFindGridAt(mouseWorldMap, out var mouseGridUid, out var mouseGrid))
{
mouseGridPos = mouseGrid.MapToGrid(mouseWorldMap);
tile = mouseGrid.GetTileRef(mouseGridPos);
mouseGridPos = mapSystem.MapToGrid(mouseGridUid, mouseWorldMap);
tile = mapSystem.GetTileRef(mouseGridUid, mouseGrid, mouseGridPos);
}
else
{

View File

@@ -98,7 +98,7 @@ namespace Robust.Server
[Dependency] private readonly ISerializationManager _serialization = default!;
[Dependency] private readonly IStatusHost _statusHost = default!;
[Dependency] private readonly IComponentFactory _componentFactory = default!;
[Dependency] private readonly IReplayRecordingManager _replay = default!;
[Dependency] private readonly IReplayRecordingManagerInternal _replay = default!;
[Dependency] private readonly IGamePrototypeLoadManager _protoLoadMan = default!;
[Dependency] private readonly NetworkResourceManager _netResMan = default!;
@@ -633,6 +633,8 @@ namespace Robust.Server
// called right before main loop returns, do all saving/cleanup in here
public void Cleanup()
{
_replay.Shutdown();
_modLoader.Shutdown();
_playerManager.Shutdown();

View File

@@ -47,6 +47,7 @@ namespace Robust.Server.Console
private long _lastReceivedBytes;
private long _lastSentBytes;
private bool _hasCancelled;
public void Dispose()
{
@@ -319,8 +320,15 @@ namespace Robust.Server.Console
private void CancelKeyHandler(object? sender, ConsoleCancelEventArgs args)
{
if (_hasCancelled)
{
Con.WriteLine("Double CancelKey, terminating process.");
return;
}
// Handle process exiting ourselves.
args.Cancel = true;
_hasCancelled = true;
_taskManager.RunOnMainThread(() => { _baseServer.Shutdown("CancelKey"); });
}

View File

@@ -44,6 +44,7 @@ public sealed class MapLoaderSystem : EntitySystem
private IServerEntityManagerInternal _serverEntityManager = default!;
[Dependency] private readonly ITileDefinitionManager _tileDefManager = default!;
[Dependency] private readonly MetaDataSystem _meta = default!;
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
private ISawmill _logLoader = default!;
@@ -764,11 +765,12 @@ public sealed class MapLoaderSystem : EntitySystem
SequenceDataNode yamlGridChunks = (SequenceDataNode)yamlGrid["chunks"];
var grid = AllocateMapGrid(gridComp, yamlGridInfo);
var gridUid = grid.Owner;
foreach (var chunkNode in yamlGridChunks.Cast<MappingDataNode>())
{
var (chunkOffsetX, chunkOffsetY) = _serManager.Read<Vector2i>(chunkNode["ind"]);
_serManager.Read(chunkNode, _context, instanceProvider: () => grid.GetOrAddChunk(chunkOffsetX, chunkOffsetY), notNullableOverride: true);
_serManager.Read(chunkNode, _context, instanceProvider: () => _mapSystem.GetOrAddChunk(gridUid, grid, chunkOffsetX, chunkOffsetY), notNullableOverride: true);
}
}
}

View File

@@ -70,7 +70,7 @@ public sealed class ServerMetaDataSystem : MetaDataSystem
foreach (var (_, comp) in EntityManager.GetNetComponents(uid))
{
if (comp.SessionSpecific || comp.SendOnlyToOwner)
Dirty(comp);
Dirty(uid, comp);
}
}

View File

@@ -107,15 +107,12 @@ internal sealed partial class PvsSystem : EntitySystem
private EntityQuery<EyeComponent> _eyeQuery;
private EntityQuery<TransformComponent> _xformQuery;
private ISawmill _sawmill = default!;
public override void Initialize()
{
base.Initialize();
_eyeQuery = GetEntityQuery<EyeComponent>();
_xformQuery = GetEntityQuery<TransformComponent>();
_sawmill = Logger.GetSawmill("PVS");
_entityPvsCollection = RegisterPVSCollection<EntityUid>();
@@ -182,7 +179,7 @@ internal sealed partial class PvsSystem : EntitySystem
sb.Append($" Entity last sent: {lastSeenTick.Value}");
}
_sawmill.Warning(sb.ToString());
Log.Warning(sb.ToString());
sessionData.LastSeenAt.Clear();
@@ -231,7 +228,7 @@ internal sealed partial class PvsSystem : EntitySystem
private PVSCollection<TIndex> RegisterPVSCollection<TIndex>() where TIndex : IComparable<TIndex>, IEquatable<TIndex>
{
var collection = new PVSCollection<TIndex>(_sawmill, EntityManager, _transform);
var collection = new PVSCollection<TIndex>(Log, EntityManager, _transform);
_pvsCollections.Add(collection);
return collection;
}
@@ -334,12 +331,12 @@ internal sealed partial class PvsSystem : EntitySystem
if (e.NewStatus == SessionStatus.InGame)
{
if (!PlayerData.TryAdd(e.Session, new()))
_sawmill.Error($"Attempted to add player to _playerVisibleSets, but they were already present? Session:{e.Session}");
Log.Error($"Attempted to add player to _playerVisibleSets, but they were already present? Session:{e.Session}");
foreach (var pvsCollection in _pvsCollections)
{
if (!pvsCollection.AddPlayer(e.Session))
_sawmill.Error($"Attempted to add player to pvsCollection, but they were already present? Session:{e.Session}");
Log.Error($"Attempted to add player to pvsCollection, but they were already present? Session:{e.Session}");
}
return;
}
@@ -353,7 +350,7 @@ internal sealed partial class PvsSystem : EntitySystem
foreach (var pvsCollection in _pvsCollections)
{
if (!pvsCollection.RemovePlayer(e.Session))
_sawmill.Error($"Attempted to remove player from pvsCollection, but they were already removed? Session:{e.Session}");
Log.Error($"Attempted to remove player from pvsCollection, but they were already removed? Session:{e.Session}");
}
if (data.Overflow != null)
@@ -822,7 +819,7 @@ internal sealed partial class PvsSystem : EntitySystem
#if DEBUG
// This happens relatively frequently for the current TickBuffer value, and doesn't really provide any
// useful info when not debugging/testing locally. Hence only enable on DEBUG.
_sawmill.Debug($"Client {session} exceeded tick buffer.");
Log.Debug($"Client {session} exceeded tick buffer.");
#endif
}
else if (oldEntry.Value.Value != lastAcked)
@@ -980,7 +977,7 @@ internal sealed partial class PvsSystem : EntitySystem
if (metaDataComponent.EntityLifeStage >= EntityLifeStage.Terminating)
{
var rep = new EntityStringRepresentation(uid, metaDataComponent.EntityDeleted, metaDataComponent.EntityName, metaDataComponent.EntityPrototype?.ID);
_sawmill.Error($"Attempted to add a deleted entity to PVS send set: '{rep}'. Trace:\n{Environment.StackTrace}");
Log.Error($"Attempted to add a deleted entity to PVS send set: '{rep}'. Trace:\n{Environment.StackTrace}");
return;
}
@@ -1110,7 +1107,7 @@ internal sealed partial class PvsSystem : EntitySystem
if (component.Deleted || !component.Initialized)
{
_sawmill.Error("Entity manager returned deleted or uninitialized components while sending entity data");
Log.Error("Entity manager returned deleted or uninitialized components while sending entity data");
continue;
}

View File

@@ -77,7 +77,7 @@ internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingData
id = tileDefinitionManager[defName].TileId;
var tile = new Tile(id, flags, variant);
chunk.SetTile(x, y, tile);
chunk.TrySetTile(x, y, tile, out _, out _);
}
}
@@ -143,7 +143,7 @@ internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingData
{
for (ushort x = 0; x < chunk.ChunkSize; x++)
{
chunk.SetTile(x, y, source.GetTile(x, y));
chunk.TrySetTile(x, y, source.GetTile(x, y), out _, out _);
}
}

View File

@@ -70,6 +70,7 @@ namespace Robust.Server
deps.Register<IServerEntityManagerInternal, ServerEntityManager>();
deps.Register<IServerGameStateManager, ServerGameStateManager>();
deps.Register<IReplayRecordingManager, ReplayRecordingManager>();
deps.Register<IReplayRecordingManagerInternal, ReplayRecordingManager>();
deps.Register<IServerReplayRecordingManager, ReplayRecordingManager>();
deps.Register<IServerNetManager, NetManager>();
deps.Register<IStatusHost, StatusHost>();

View File

@@ -15,8 +15,16 @@ internal sealed class RecursiveMoveSystem : EntitySystem
{
[Dependency] private readonly IMapManager _mapManager = default!;
private EntityQuery<TransformComponent> _xformQuery;
bool Subscribed = false;
public override void Initialize()
{
base.Initialize();
_xformQuery = GetEntityQuery<TransformComponent>();
}
internal void AddSubscription()
{
if (Subscribed)
@@ -34,14 +42,12 @@ internal sealed class RecursiveMoveSystem : EntitySystem
DebugTools.Assert(!_mapManager.IsMap(args.Sender));
DebugTools.Assert(!_mapManager.IsGrid(args.Sender));
var xformQuery = GetEntityQuery<TransformComponent>();
AnythingMovedSubHandler(args.Sender, args.Component, xformQuery);
AnythingMovedSubHandler(args.Sender, args.Component);
}
private void AnythingMovedSubHandler(
EntityUid uid,
TransformComponent xform,
EntityQuery<TransformComponent> xformQuery)
TransformComponent xform)
{
// TODO maybe use a c# event? This event gets raised a lot.
// Would probably help with server performance and is also the main bottleneck for replay scrubbing.
@@ -55,8 +61,8 @@ internal sealed class RecursiveMoveSystem : EntitySystem
var childEnumerator = xform.ChildEnumerator;
while (childEnumerator.MoveNext(out var child))
{
if (xformQuery.TryGetComponent(child.Value, out var childXform))
AnythingMovedSubHandler(child.Value, childXform, xformQuery);
if (_xformQuery.TryGetComponent(child.Value, out var childXform))
AnythingMovedSubHandler(child.Value, childXform);
}
}
}

View File

@@ -54,7 +54,7 @@ internal sealed class TeleportCommand : LocalizedCommands
if (_map.TryFindGridAt(mapId, position, out var gridUid, out var grid))
{
var gridPos = grid.WorldToLocal(position);
var gridPos = xformSystem.GetInvWorldMatrix(gridUid).Transform(position);
xformSystem.SetCoordinates(entity, transform, new EntityCoordinates(gridUid, gridPos));
}

View File

@@ -75,14 +75,7 @@ namespace Robust.Shared.GameObjects
LifeStage = ComponentLifeStage.Initializing;
entManager.EventBus.RaiseComponentEvent(this, type, CompInitInstance);
Initialize();
#if DEBUG
if (LifeStage != ComponentLifeStage.Initialized)
{
DebugTools.Assert($"Component {this.GetType().Name} did not call base {nameof(Initialize)} in derived method.");
}
#endif
LifeStage = ComponentLifeStage.Initialized;
}
/// <summary>
@@ -95,14 +88,7 @@ namespace Robust.Shared.GameObjects
LifeStage = ComponentLifeStage.Starting;
entManager.EventBus.RaiseComponentEvent(this, CompStartupInstance);
Startup();
#if DEBUG
if (LifeStage != ComponentLifeStage.Running)
{
DebugTools.Assert($"Component {this.GetType().Name} did not call base {nameof(Startup)} in derived method.");
}
#endif
LifeStage = ComponentLifeStage.Running;
}
/// <summary>
@@ -170,26 +156,6 @@ namespace Robust.Shared.GameObjects
private static readonly ComponentShutdown CompShutdownInstance = new();
private static readonly ComponentRemove CompRemoveInstance = new();
/// <summary>
/// Called when all of the entity's other components have been added and are available,
/// But are not necessarily initialized yet. DO NOT depend on the values of other components to be correct.
/// </summary>
protected virtual void Initialize()
{
LifeStage = ComponentLifeStage.Initialized;
}
/// <summary>
/// Starts up a component. This is called automatically after all components are Initialized and the entity is Initialized.
/// </summary>
/// <remarks>
/// Components are allowed to remove themselves in their own Startup function.
/// </remarks>
protected virtual void Startup()
{
LifeStage = ComponentLifeStage.Running;
}
/// <summary>
/// Called when the component is removed from an entity.
/// Shuts down the component.

View File

@@ -94,7 +94,6 @@ namespace Robust.Shared.GameObjects
[Obsolete("Use a system update loop instead")]
public static void SpawnRepeatingTimer(this EntityUid entity, int milliseconds, Action onFired, CancellationToken cancellationToken)
{
var entMan = IoCManager.Resolve<IEntityManager>();
entity
.EnsureTimerComponent()
.SpawnRepeating(milliseconds, onFired, cancellationToken);

View File

@@ -49,8 +49,6 @@ namespace Robust.Shared.GameObjects
private UniqueIndexHkm<EntityUid, Component> _entCompIndex =
new(ComponentCollectionCapacity);
private EntityQuery<MetaDataComponent> _metaQuery;
/// <inheritdoc />
public event Action<AddedComponentEventArgs>? ComponentAdded;
@@ -68,7 +66,6 @@ namespace Robust.Shared.GameObjects
FillComponentDict();
_componentFactory.ComponentAdded += OnComponentAdded;
_componentFactory.ComponentReferenceAdded += OnComponentReferenceAdded;
_metaQuery = GetEntityQuery<MetaDataComponent>();
}
/// <summary>
@@ -80,7 +77,10 @@ namespace Robust.Shared.GameObjects
_netComponents.Clear();
_entCompIndex.Clear();
_deleteSet.Clear();
FillComponentDict();
foreach (var dict in _entTraitDict.Values)
{
dict.Clear();
}
}
private void AddComponentRefType(CompIdx type)

View File

@@ -18,6 +18,8 @@ namespace Robust.Shared.GameObjects
{
public delegate void EntityUidQueryCallback(EntityUid uid);
public delegate void ComponentQueryCallback<T>(EntityUid uid, T component) where T : Component;
/// <inheritdoc />
[Virtual]
public partial class EntityManager : IEntityManager
@@ -36,6 +38,9 @@ namespace Robust.Shared.GameObjects
// positions on spawn....
private SharedTransformSystem _xforms = default!;
private EntityQuery<MetaDataComponent> _metaQuery;
private EntityQuery<TransformComponent> _xformQuery;
#endregion Dependencies
/// <inheritdoc />
@@ -76,6 +81,8 @@ namespace Robust.Shared.GameObjects
private string _xformName = string.Empty;
private SharedMapSystem _mapSystem = default!;
private ISawmill _sawmill = default!;
private ISawmill _resolveSawmill = default!;
@@ -219,7 +226,10 @@ namespace Robust.Shared.GameObjects
_entitySystemManager.Initialize();
Started = true;
_eventBus.CalcOrdering();
_xforms = _entitySystemManager.GetEntitySystem<SharedTransformSystem>();
_mapSystem = System<SharedMapSystem>();
_xforms = System<SharedTransformSystem>();
_metaQuery = GetEntityQuery<MetaDataComponent>();
_xformQuery = GetEntityQuery<TransformComponent>();
}
public virtual void Shutdown()
@@ -306,7 +316,7 @@ namespace Robust.Shared.GameObjects
if (coordinates.IsValid(this))
{
_xforms.SetCoordinates(newEntity, GetComponent<TransformComponent>(newEntity), coordinates, unanchor: false);
_xforms.SetCoordinates(newEntity, _xformQuery.GetComponent(newEntity), coordinates, unanchor: false);
}
return newEntity;
@@ -316,7 +326,7 @@ namespace Robust.Shared.GameObjects
public virtual EntityUid CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates, ComponentRegistry? overrides = null)
{
var newEntity = CreateEntity(prototypeName, default, overrides);
var transform = GetComponent<TransformComponent>(newEntity);
var transform = _xformQuery.GetComponent(newEntity);
if (coordinates.MapId == MapId.Nullspace)
{
@@ -333,7 +343,7 @@ namespace Robust.Shared.GameObjects
EntityCoordinates coords;
if (transform.Anchored && _mapManager.TryFindGridAt(coordinates, out var gridUid, out var grid))
{
coords = new EntityCoordinates(gridUid, grid.WorldToLocal(coordinates.Position));
coords = new EntityCoordinates(gridUid, _mapSystem.WorldToLocal(gridUid, grid, coordinates.Position));
_xforms.SetCoordinates(newEntity, transform, coords, unanchor: false);
}
else
@@ -743,7 +753,7 @@ namespace Robust.Shared.GameObjects
private protected void LoadEntity(EntityUid entity, IEntityLoadContext? context)
{
EntityPrototype.LoadEntity(GetComponent<MetaDataComponent>(entity).EntityPrototype, entity, ComponentFactory, this, _serManager, context);
EntityPrototype.LoadEntity(_metaQuery.GetComponent(entity).EntityPrototype, entity, ComponentFactory, this, _serManager, context);
}
private protected void LoadEntity(EntityUid entity, IEntityLoadContext? context, EntityPrototype? prototype)
@@ -755,12 +765,12 @@ namespace Robust.Shared.GameObjects
{
try
{
var meta = GetComponent<MetaDataComponent>(entity);
var meta = _metaQuery.GetComponent(entity);
InitializeEntity(entity, meta);
StartEntity(entity);
// If the map we're initializing the entity on is initialized, run map init on it.
if (_mapManager.IsMapInitialized(mapId ?? GetComponent<TransformComponent>(entity).MapID))
if (_mapManager.IsMapInitialized(mapId ?? _xformQuery.GetComponent(entity).MapID))
RunMapInit(entity, meta);
}
catch (Exception e)

View File

@@ -40,7 +40,7 @@ namespace Robust.Shared.GameObjects
else if (TryComp(uid, out PhysicsComponent? physics))
_physics.SetCanCollide(uid, true, body: physics);
Dirty(component);
Dirty(uid, component);
}
private void OnHandleState(EntityUid uid, CollisionWakeComponent component, ref ComponentHandleState args)

View File

@@ -1,10 +1,10 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using Robust.Shared.Collections;
using Robust.Shared.Containers;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Dynamics;
@@ -26,12 +26,10 @@ public sealed partial class EntityLookupSystem
EntityUid lookupUid,
HashSet<EntityUid> intersecting,
Box2 worldAABB,
LookupFlags flags,
EntityQuery<BroadphaseComponent> lookupQuery,
EntityQuery<TransformComponent> xformQuery)
LookupFlags flags)
{
var lookup = lookupQuery.GetComponent(lookupUid);
var invMatrix = _transform.GetInvWorldMatrix(lookupUid, xformQuery);
var lookup = _broadQuery.GetComponent(lookupUid);
var invMatrix = _transform.GetInvWorldMatrix(lookupUid);
var localAABB = invMatrix.TransformBox(worldAABB);
if ((flags & LookupFlags.Dynamic) != 0x0)
@@ -79,12 +77,10 @@ public sealed partial class EntityLookupSystem
EntityUid lookupUid,
HashSet<EntityUid> intersecting,
Box2Rotated worldBounds,
LookupFlags flags,
EntityQuery<BroadphaseComponent> lookupQuery,
EntityQuery<TransformComponent> xformQuery)
LookupFlags flags)
{
var lookup = lookupQuery.GetComponent(lookupUid);
var invMatrix = _transform.GetInvWorldMatrix(lookupUid, xformQuery);
var lookup = _broadQuery.GetComponent(lookupUid);
var invMatrix = _transform.GetInvWorldMatrix(lookupUid);
// We don't just use CalcBoundingBox because the transformed bounds might be tighter.
var localAABB = invMatrix.TransformBox(worldBounds);
@@ -132,12 +128,10 @@ public sealed partial class EntityLookupSystem
private bool AnyEntitiesIntersecting(EntityUid lookupUid,
Box2 worldAABB,
LookupFlags flags,
EntityQuery<BroadphaseComponent> lookupQuery,
EntityQuery<TransformComponent> xformQuery,
EntityUid? ignored = null)
{
var lookup = lookupQuery.GetComponent(lookupUid);
var localAABB = xformQuery.GetComponent(lookupUid).InvWorldMatrix.TransformBox(worldAABB);
var lookup = _broadQuery.GetComponent(lookupUid);
var localAABB = _transform.GetInvWorldMatrix(lookupUid).TransformBox(worldAABB);
var state = (ignored, found: false);
if ((flags & LookupFlags.Dynamic) != 0x0)
@@ -197,12 +191,10 @@ public sealed partial class EntityLookupSystem
private bool AnyEntitiesIntersecting(EntityUid lookupUid,
Box2Rotated worldBounds,
LookupFlags flags,
EntityQuery<BroadphaseComponent> lookupQuery,
EntityQuery<TransformComponent> xformQuery,
EntityUid? ignored = null)
{
var lookup = lookupQuery.GetComponent(lookupUid);
var localAABB = xformQuery.GetComponent(lookupUid).InvWorldMatrix.TransformBox(worldBounds);
var lookup = _broadQuery.GetComponent(lookupUid);
var localAABB = _transform.GetInvWorldMatrix(lookupUid).TransformBox(worldBounds);
var state = (ignored, found: false);
if ((flags & LookupFlags.Dynamic) != 0x0)
@@ -259,35 +251,35 @@ public sealed partial class EntityLookupSystem
return state.found;
}
private void RecursiveAdd(EntityUid uid, ref ValueList<EntityUid> toAdd, EntityQuery<TransformComponent> xformQuery)
private void RecursiveAdd(EntityUid uid, ref ValueList<EntityUid> toAdd)
{
var childEnumerator = xformQuery.GetComponent(uid).ChildEnumerator;
var childEnumerator = _xformQuery.GetComponent(uid).ChildEnumerator;
while (childEnumerator.MoveNext(out var child))
{
toAdd.Add(child.Value);
RecursiveAdd(child.Value, ref toAdd, xformQuery);
RecursiveAdd(child.Value, ref toAdd);
}
}
private void AddContained(HashSet<EntityUid> intersecting, LookupFlags flags, EntityQuery<TransformComponent> xformQuery)
private void AddContained(HashSet<EntityUid> intersecting, LookupFlags flags)
{
if ((flags & LookupFlags.Contained) == 0x0 || intersecting.Count == 0)
return;
var conQuery = GetEntityQuery<ContainerManagerComponent>();
var toAdd = new ValueList<EntityUid>();
foreach (var uid in intersecting)
{
if (!conQuery.TryGetComponent(uid, out var conManager)) continue;
if (!_containerQuery.TryGetComponent(uid, out var conManager))
continue;
foreach (var con in conManager.GetAllContainers())
{
foreach (var contained in con.ContainedEntities)
{
toAdd.Add(contained);
RecursiveAdd(contained, ref toAdd, xformQuery);
RecursiveAdd(contained, ref toAdd);
}
}
}
@@ -321,11 +313,9 @@ public sealed partial class EntityLookupSystem
float arcWidth,
LookupFlags flags = DefaultFlags)
{
var xformQuery = GetEntityQuery<TransformComponent>();
foreach (var entity in GetEntitiesInRange(coordinates, range * 2, flags))
{
var angle = new Angle(_transform.GetWorldPosition(entity, xformQuery) - coordinates.Position);
var angle = new Angle(_transform.GetWorldPosition(entity) - coordinates.Position);
if (angle.Degrees < direction.Degrees + arcWidth / 2 &&
angle.Degrees > direction.Degrees - arcWidth / 2)
yield return entity;
@@ -340,48 +330,63 @@ public sealed partial class EntityLookupSystem
{
if (mapId == MapId.Nullspace) return false;
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
// Don't need to check contained entities as they have the same bounds as the parent.
var found = false;
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
{
if (AnyEntitiesIntersecting(grid.Owner, worldAABB, flags, lookupQuery, xformQuery)) return true;
}
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.AnyEntitiesIntersecting(uid, tuple.worldAABB, tuple.flags))
return true;
tuple.found = true;
return false;
});
if (state.found)
return true;
var mapUid = _mapManager.GetMapEntityId(mapId);
return AnyEntitiesIntersecting(mapUid, worldAABB, flags, lookupQuery, xformQuery);
return AnyEntitiesIntersecting(mapUid, worldAABB, flags);
}
public HashSet<EntityUid> GetEntitiesIntersecting(MapId mapId, Box2 worldAABB, LookupFlags flags = DefaultFlags)
{
if (mapId == MapId.Nullspace) return new HashSet<EntityUid>();
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
var intersecting = new HashSet<EntityUid>();
// Get grid entities
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
{
AddEntitiesIntersecting(grid.Owner, intersecting, worldAABB, flags, lookupQuery, xformQuery);
var state = (this, _map, intersecting, worldAABB, flags);
if ((flags & LookupFlags.Static) != 0x0)
_mapManager.FindGridsIntersecting(mapId, worldAABB, ref state,
static (EntityUid gridUid, MapGridComponent grid, ref (
EntityLookupSystem lookup, SharedMapSystem _map, HashSet<EntityUid> intersecting,
Box2 worldAABB, LookupFlags flags) tuple) =>
{
foreach (var uid in grid.GetAnchoredEntities(worldAABB))
tuple.lookup.AddEntitiesIntersecting(gridUid, tuple.intersecting, tuple.worldAABB, tuple.flags);
if ((tuple.flags & LookupFlags.Static) != 0x0)
{
if (Deleted(uid)) continue;
intersecting.Add(uid);
// TODO: Need a struct enumerator version.
foreach (var uid in tuple._map.GetAnchoredEntities(gridUid, grid, tuple.worldAABB))
{
if (tuple.lookup.Deleted(uid))
continue;
tuple.intersecting.Add(uid);
}
}
}
}
return true;
});
// Get map entities
var mapUid = _mapManager.GetMapEntityId(mapId);
AddEntitiesIntersecting(mapUid, intersecting, worldAABB, flags, lookupQuery, xformQuery);
AddContained(intersecting, flags, xformQuery);
AddEntitiesIntersecting(mapUid, intersecting, worldAABB, flags);
AddContained(intersecting, flags);
return intersecting;
}
@@ -392,39 +397,55 @@ public sealed partial class EntityLookupSystem
public bool AnyEntitiesIntersecting(MapId mapId, Box2Rotated worldBounds, LookupFlags flags = DefaultFlags)
{
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
// Don't need to check contained entities as they have the same bounds as the parent.
var worldAABB = worldBounds.CalcBoundingBox();
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldBounds.CalcBoundingBox()))
{
if (AnyEntitiesIntersecting(grid.Owner, worldBounds, flags, lookupQuery, xformQuery)) return true;
}
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;
});
if (state.found)
return true;
var mapUid = _mapManager.GetMapEntityId(mapId);
return AnyEntitiesIntersecting(mapUid, worldBounds, flags, lookupQuery, xformQuery);
return AnyEntitiesIntersecting(mapUid, worldBounds, flags);
}
public HashSet<EntityUid> GetEntitiesIntersecting(MapId mapId, Box2Rotated worldBounds, LookupFlags flags = DefaultFlags)
{
if (mapId == MapId.Nullspace) return new HashSet<EntityUid>();
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
var intersecting = new HashSet<EntityUid>();
if (mapId == MapId.Nullspace)
return intersecting;
// Get grid entities
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldBounds.CalcBoundingBox()))
var state = (this, intersecting, worldBounds, flags);
_mapManager.FindGridsIntersecting(mapId, worldBounds.CalcBoundingBox(), ref state, static
(EntityUid uid, MapGridComponent _,
ref (EntityLookupSystem lookup,
HashSet<EntityUid> intersecting,
Box2Rotated worldBounds,
LookupFlags flags) tuple) =>
{
AddEntitiesIntersecting(grid.Owner, intersecting, worldBounds, flags, lookupQuery, xformQuery);
}
tuple.lookup.AddEntitiesIntersecting(uid, tuple.intersecting, tuple.worldBounds, tuple.flags);
return true;
});
// Get map entities
var mapUid = _mapManager.GetMapEntityId(mapId);
AddEntitiesIntersecting(mapUid, intersecting, worldBounds, flags, lookupQuery, xformQuery);
AddContained(intersecting, flags, xformQuery);
AddEntitiesIntersecting(mapUid, intersecting, worldBounds, flags);
AddContained(intersecting, flags);
return intersecting;
}
@@ -438,50 +459,72 @@ public sealed partial class EntityLookupSystem
public bool AnyEntitiesIntersecting(EntityUid uid, LookupFlags flags = DefaultFlags)
{
var worldAABB = GetWorldAABB(uid);
var mapID = Transform(uid).MapID;
var mapID = _xformQuery.GetComponent(uid).MapID;
if (mapID == MapId.Nullspace) return false;
if (mapID == MapId.Nullspace)
return false;
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
const bool found = false;
var state = (this, worldAABB, flags, found, uid);
_mapManager.FindGridsIntersecting(mapID, worldAABB, ref state,
static (EntityUid gridUid, MapGridComponent grid,
ref (EntityLookupSystem lookup, Box2 worldAABB, LookupFlags flags, bool found, EntityUid ignored) tuple) =>
{
if (tuple.lookup.AnyEntitiesIntersecting(gridUid, tuple.worldAABB, tuple.flags, tuple.ignored))
{
tuple.found = true;
return false;
}
foreach (var grid in _mapManager.FindGridsIntersecting(mapID, worldAABB))
{
if (AnyEntitiesIntersecting(grid.Owner, worldAABB, flags, lookupQuery, xformQuery, uid))
return true;
}
});
var mapUid = _mapManager.GetMapEntityId(mapID);
return AnyEntitiesIntersecting(mapUid, worldAABB, flags, lookupQuery, xformQuery, uid);
return AnyEntitiesIntersecting(mapUid, worldAABB, flags, uid);
}
public bool AnyEntitiesInRange(EntityUid uid, float range, LookupFlags flags = DefaultFlags)
{
var mapPos = Transform(uid).MapPosition;
var mapPos = _xformQuery.GetComponent(uid).MapPosition;
if (mapPos.MapId == MapId.Nullspace) return false;
if (mapPos.MapId == MapId.Nullspace)
return false;
var rangeVec = new Vector2(range, range);
var worldAABB = new Box2(mapPos.Position - rangeVec, mapPos.Position + rangeVec);
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
foreach (var grid in _mapManager.FindGridsIntersecting(mapPos.MapId, worldAABB))
const bool found = false;
var state = (this, worldAABB, flags, found, uid);
_mapManager.FindGridsIntersecting(mapPos.MapId, worldAABB, ref state, static (
EntityUid gridUid,
MapGridComponent _, ref (
EntityLookupSystem lookup,
Box2 worldAABB,
LookupFlags flags,
bool found,
EntityUid ignored) tuple) =>
{
if (AnyEntitiesIntersecting(grid.Owner, worldAABB, flags, lookupQuery, xformQuery, uid))
return true;
}
if (tuple.lookup.AnyEntitiesIntersecting(gridUid, tuple.worldAABB, tuple.flags, tuple.ignored))
{
tuple.found = true;
return false;
}
return true;
});
var mapUid = _mapManager.GetMapEntityId(mapPos.MapId);
return AnyEntitiesIntersecting(mapUid, worldAABB, flags, lookupQuery, xformQuery, uid);
return AnyEntitiesIntersecting(mapUid, worldAABB, flags, uid);
}
public HashSet<EntityUid> GetEntitiesInRange(EntityUid uid, float range, LookupFlags flags = DefaultFlags)
{
var mapPos = Transform(uid).MapPosition;
var mapPos = _xformQuery.GetComponent(uid).MapPosition;
if (mapPos.MapId == MapId.Nullspace) return new HashSet<EntityUid>();
if (mapPos.MapId == MapId.Nullspace)
return new HashSet<EntityUid>();
var intersecting = GetEntitiesInRange(mapPos, range, flags);
intersecting.Remove(uid);
@@ -490,12 +533,13 @@ public sealed partial class EntityLookupSystem
public HashSet<EntityUid> GetEntitiesIntersecting(EntityUid uid, LookupFlags flags = DefaultFlags)
{
var xform = Transform(uid);
var xform = _xformQuery.GetComponent(uid);
var mapId = xform.MapID;
if (mapId == MapId.Nullspace) return new HashSet<EntityUid>();
if (mapId == MapId.Nullspace)
return new HashSet<EntityUid>();
var (worldPos, worldRot) = xform.GetWorldPositionRotation();
var (worldPos, worldRot) = _transform.GetWorldPositionRotation(xform);
var bounds = GetAABBNoContainer(uid, worldPos, worldRot);
var intersecting = GetEntitiesIntersecting(mapId, bounds, flags);
@@ -509,7 +553,8 @@ public sealed partial class EntityLookupSystem
public bool AnyEntitiesIntersecting(EntityCoordinates coordinates, LookupFlags flags = DefaultFlags)
{
if (!coordinates.IsValid(EntityManager)) return false;
if (!coordinates.IsValid(EntityManager))
return false;
var mapPos = coordinates.ToMap(EntityManager, _transform);
return AnyEntitiesIntersecting(mapPos, flags);
@@ -517,7 +562,8 @@ public sealed partial class EntityLookupSystem
public bool AnyEntitiesInRange(EntityCoordinates coordinates, float range, LookupFlags flags = DefaultFlags)
{
if (!coordinates.IsValid(EntityManager)) return false;
if (!coordinates.IsValid(EntityManager))
return false;
var mapPos = coordinates.ToMap(EntityManager, _transform);
return AnyEntitiesInRange(mapPos, range, flags);
@@ -581,7 +627,8 @@ public sealed partial class EntityLookupSystem
{
DebugTools.Assert(range > 0, "Range must be a positive float");
if (mapId == MapId.Nullspace) return new HashSet<EntityUid>();
if (mapId == MapId.Nullspace)
return new HashSet<EntityUid>();
// TODO: Actual circles
var rangeVec = new Vector2(range, range);
@@ -601,7 +648,7 @@ public sealed partial class EntityLookupSystem
// Technically this doesn't consider anything overlapping from outside the grid but is this an issue?
if (!_mapManager.TryGetGrid(gridId, out var grid)) return new HashSet<EntityUid>();
var lookup = Comp<BroadphaseComponent>(gridId);
var lookup = _broadQuery.GetComponent(gridId);
var intersecting = new HashSet<EntityUid>();
var tileSize = grid.TileSize;
@@ -647,8 +694,7 @@ public sealed partial class EntityLookupSystem
}
}
var xformQuery = GetEntityQuery<TransformComponent>();
AddContained(intersecting, flags, xformQuery);
AddContained(intersecting, flags);
return intersecting;
}
@@ -656,8 +702,10 @@ public sealed partial class EntityLookupSystem
public HashSet<EntityUid> GetEntitiesIntersecting(EntityUid gridId, Vector2i gridIndices, LookupFlags flags = DefaultFlags)
{
// Technically this doesn't consider anything overlapping from outside the grid but is this an issue?
if (!_mapManager.TryGetGrid(gridId, out var grid)) return new HashSet<EntityUid>();
var lookup = Comp<BroadphaseComponent>(gridId);
if (!_mapManager.TryGetGrid(gridId, out var grid))
return new HashSet<EntityUid>();
var lookup = _broadQuery.GetComponent(gridId);
var tileSize = grid.TileSize;
var aabb = GetLocalBounds(gridIndices, tileSize);
return GetEntitiesIntersecting(lookup, aabb, flags);
@@ -707,22 +755,20 @@ public sealed partial class EntityLookupSystem
}, aabb);
}
var xformQuery = GetEntityQuery<TransformComponent>();
AddContained(intersecting, flags, xformQuery);
AddContained(intersecting, flags);
return intersecting;
}
public HashSet<EntityUid> GetEntitiesIntersecting(EntityUid gridId, Box2 worldAABB, LookupFlags flags = DefaultFlags)
{
if (!_mapManager.GridExists(gridId)) return new HashSet<EntityUid>();
if (!_mapManager.GridExists(gridId))
return new HashSet<EntityUid>();
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
var intersecting = new HashSet<EntityUid>();
AddEntitiesIntersecting(gridId, intersecting, worldAABB, flags, lookupQuery, xformQuery);
AddContained(intersecting, flags, xformQuery);
AddEntitiesIntersecting(gridId, intersecting, worldAABB, flags);
AddContained(intersecting, flags);
return intersecting;
}
@@ -731,12 +777,10 @@ public sealed partial class EntityLookupSystem
{
if (!_mapManager.GridExists(gridId)) return new HashSet<EntityUid>();
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
var intersecting = new HashSet<EntityUid>();
AddEntitiesIntersecting(gridId, intersecting, worldBounds, flags, lookupQuery, xformQuery);
AddContained(intersecting, flags, xformQuery);
AddEntitiesIntersecting(gridId, intersecting, worldBounds, flags);
AddContained(intersecting, flags);
return intersecting;
}
@@ -754,8 +798,7 @@ public sealed partial class EntityLookupSystem
public HashSet<EntityUid> GetEntitiesIntersecting(BroadphaseComponent component, ref Box2 worldAABB, LookupFlags flags = DefaultFlags)
{
var intersecting = new HashSet<EntityUid>();
var xformQuery = GetEntityQuery<TransformComponent>();
var localAABB = xformQuery.GetComponent(component.Owner).InvWorldMatrix.TransformBox(worldAABB);
var localAABB = _transform.GetInvWorldMatrix(component.Owner).TransformBox(worldAABB);
if ((flags & LookupFlags.Dynamic) != 0x0)
{
@@ -793,7 +836,7 @@ public sealed partial class EntityLookupSystem
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
}
AddContained(intersecting, flags, xformQuery);
AddContained(intersecting, flags);
return intersecting;
}
@@ -838,7 +881,7 @@ public sealed partial class EntityLookupSystem
}, localAABB, (flags & LookupFlags.Approximate) != 0x0);
}
AddContained(intersecting, flags, GetEntityQuery<TransformComponent>());
AddContained(intersecting, flags);
return intersecting;
}
@@ -850,36 +893,24 @@ public sealed partial class EntityLookupSystem
/// <summary>
/// Gets the relevant <see cref="BroadphaseComponent"/> that intersects the specified area.
/// </summary>
public IEnumerable<BroadphaseComponent> FindLookupsIntersecting(MapId mapId, Box2 worldAABB)
public void FindLookupsIntersecting(MapId mapId, Box2Rotated worldBounds, ComponentQueryCallback<BroadphaseComponent> callback)
{
if (mapId == MapId.Nullspace) yield break;
if (mapId == MapId.Nullspace)
return;
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
var mapUid = _mapManager.GetMapEntityId(mapId);
callback(mapUid, _broadQuery.GetComponent(mapUid));
yield return lookupQuery.GetComponent(_mapManager.GetMapEntityId(mapId));
var state = (callback, _broadQuery);
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
{
yield return lookupQuery.GetComponent(grid.Owner);
}
}
/// <summary>
/// Gets the relevant <see cref="BroadphaseComponent"/> that intersects the specified area.
/// </summary>
public IEnumerable<BroadphaseComponent> FindLookupsIntersecting(MapId mapId, Box2Rotated worldBounds)
{
if (mapId == MapId.Nullspace) yield break;
var lookupQuery = GetEntityQuery<BroadphaseComponent>();
yield return lookupQuery.GetComponent(_mapManager.GetMapEntityId(mapId));
// Copy-paste with above but the query may differ slightly internally.
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldBounds))
{
yield return lookupQuery.GetComponent(grid.Owner);
}
_mapManager.FindGridsIntersecting(mapId, worldBounds, ref state,
static (EntityUid uid, MapGridComponent grid,
ref (ComponentQueryCallback<BroadphaseComponent> callback, EntityQuery<BroadphaseComponent> _broadQuery)
tuple) =>
{
tuple.callback(uid, tuple._broadQuery.GetComponent(uid));
return true;
});
}
#endregion
@@ -904,8 +935,7 @@ public sealed partial class EntityLookupSystem
if (worldMatrix == null || angle == null)
{
var gridXform = Transform(tileRef.GridUid);
var (_, wAng, wMat) = gridXform.GetWorldPositionRotationMatrix();
var (_, wAng, wMat) = _transform.GetWorldPositionRotationMatrix(tileRef.GridUid);
worldMatrix = wMat;
angle = wAng;
}

View File

@@ -69,6 +69,7 @@ namespace Robust.Shared.GameObjects
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly INetManager _netMan = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
private EntityQuery<BroadphaseComponent> _broadQuery;

View File

@@ -53,7 +53,7 @@ public abstract class OccluderSystem : ComponentTreeSystem<OccluderTreeComponent
return;
comp.BoundingBox = box;
Dirty(comp);
Dirty(uid, comp);
if (comp.TreeUid != null)
QueueTreeUpdate(uid, comp);
@@ -65,7 +65,7 @@ public abstract class OccluderSystem : ComponentTreeSystem<OccluderTreeComponent
return;
comp.Enabled = enabled;
Dirty(comp);
Dirty(uid, comp);
QueueTreeUpdate(uid, comp);
}
#endregion

View File

@@ -46,7 +46,7 @@ public abstract class SharedAppearanceSystem : EntitySystem
DebugTools.Assert(value.GetType().IsValueType || value is ICloneable, "Appearance data values must be cloneable.");
component.AppearanceData[key] = value;
Dirty(component);
Dirty(uid, component);
QueueUpdate(uid, component);
}

View File

@@ -10,11 +10,12 @@ using Robust.Shared.Random;
namespace Robust.Shared.GameObjects;
public abstract class SharedAudioSystem : EntitySystem
{
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IPrototypeManager _protoMan = default!;
[Dependency] protected readonly IConfigurationManager CfgManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IPrototypeManager _protoMan = default!;
[Dependency] protected readonly IRobustRandom RandMan = default!;
[Dependency] protected readonly ISharedPlayerManager PlayerManager = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
/// <summary>
/// Default max range at which the sound can be heard.
@@ -292,7 +293,7 @@ public abstract class SharedAudioSystem : EntitySystem
protected EntityCoordinates GetFallbackCoordinates(MapCoordinates mapCoordinates)
{
if (_mapManager.TryFindGridAt(mapCoordinates, out var gridUid, out var mapGrid))
return new EntityCoordinates(gridUid, mapGrid.WorldToLocal(mapCoordinates.Position));
return new EntityCoordinates(gridUid, _map.WorldToLocal(gridUid, mapGrid, mapCoordinates.Position));
if (_mapManager.HasMapEntity(mapCoordinates.MapId))
return new EntityCoordinates(_mapManager.GetMapEntityId(mapCoordinates.MapId), mapCoordinates.Position);

View File

@@ -1,6 +1,8 @@
using Robust.Shared.IoC;
namespace Robust.Shared.GameObjects;
public abstract class SharedEyeSystem : EntitySystem
{
[Dependency] protected readonly SharedTransformSystem TransformSystem = default!;
}

View File

@@ -7,6 +7,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Map.Events;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision.Shapes;
@@ -21,9 +22,9 @@ namespace Robust.Shared.GameObjects
public abstract class SharedGridFixtureSystem : EntitySystem
{
[Dependency] private readonly FixtureSystem _fixtures = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
protected ISawmill Sawmill = default!;
private bool _enabled;
private float _fixtureEnlargement;
@@ -33,12 +34,17 @@ namespace Robust.Shared.GameObjects
{
base.Initialize();
UpdatesBefore.Add(typeof(SharedBroadphaseSystem));
Sawmill = Logger.GetSawmill("physics");
_cfg.OnValueChanged(CVars.GenerateGridFixtures, SetEnabled, true);
_cfg.OnValueChanged(CVars.GridFixtureEnlargement, SetEnlargement, true);
SubscribeLocalEvent<GridInitializeEvent>(OnGridInit);
SubscribeLocalEvent<RegenerateGridBoundsEvent>(OnGridBoundsRegenerate);
}
private void OnGridBoundsRegenerate(ref RegenerateGridBoundsEvent ev)
{
RegenerateCollision(ev.Entity, ev.ChunkRectangles, ev.RemovedChunks);
}
protected virtual void OnGridInit(GridInitializeEvent ev)
@@ -48,7 +54,7 @@ namespace Robust.Shared.GameObjects
// This will also check for grid splits if applicable.
var grid = Comp<MapGridComponent>(ev.EntityUid);
grid.RegenerateCollision(grid.GetMapChunks().Values.ToHashSet());
_map.RegenerateCollision(ev.EntityUid, grid, _map.GetMapChunks(ev.EntityUid, grid).Values.ToHashSet());
}
public override void Shutdown()
@@ -68,23 +74,24 @@ namespace Robust.Shared.GameObjects
Dictionary<MapChunk, List<Box2i>> mapChunks,
List<MapChunk> removedChunks)
{
if (!_enabled) return;
if (!_enabled)
return;
if (!EntityManager.TryGetComponent(uid, out PhysicsComponent? body))
{
Sawmill.Error($"Trying to regenerate collision for {uid} that doesn't have {nameof(body)}");
Log.Error($"Trying to regenerate collision for {uid} that doesn't have {nameof(body)}");
return;
}
if (!EntityManager.TryGetComponent(uid, out FixturesComponent? manager))
{
Sawmill.Error($"Trying to regenerate collision for {uid} that doesn't have {nameof(manager)}");
Log.Error($"Trying to regenerate collision for {uid} that doesn't have {nameof(manager)}");
return;
}
if (!EntityManager.TryGetComponent(uid, out TransformComponent? xform))
{
Sawmill.Error($"Trying to regenerate collision for {uid} that doesn't have {nameof(TransformComponent)}");
Log.Error($"Trying to regenerate collision for {uid} that doesn't have {nameof(TransformComponent)}");
return;
}

View File

@@ -1,12 +1,15 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using Robust.Shared.GameStates;
using Robust.Shared.Map;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Map.Components;
using Robust.Shared.Map.Enumerators;
using Robust.Shared.Map.Events;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Timing;
@@ -224,7 +227,7 @@ public abstract partial class SharedMapSystem
if (chunkData.IsDeleted())
continue;
var chunk = component.GetOrAddChunk(chunkData.Index);
var chunk = GetOrAddChunk(uid, component, chunkData.Index);
chunk.SuppressCollisionRegeneration = true;
DebugTools.Assert(chunkData.TileData.Length == component.ChunkSize * component.ChunkSize);
@@ -237,7 +240,7 @@ public abstract partial class SharedMapSystem
if (chunk.GetTile(x, y) == tile)
continue;
chunk.SetTile(x, y, tile);
SetChunkTile(uid, component, chunk, x, y, tile);
modified.Add((new Vector2i(chunk.X * component.ChunkSize + x, chunk.Y * component.ChunkSize + y), tile));
}
}
@@ -247,13 +250,13 @@ public abstract partial class SharedMapSystem
{
if (chunkData.IsDeleted())
{
component.RemoveChunk(chunkData.Index);
RemoveChunk(uid, component, chunkData.Index);
continue;
}
var chunk = component.GetOrAddChunk(chunkData.Index);
var chunk = GetOrAddChunk(uid, component, chunkData.Index);
chunk.SuppressCollisionRegeneration = false;
component.RegenerateCollision(chunk);
RegenerateCollision(uid, component, chunk);
}
}
@@ -263,12 +266,12 @@ public abstract partial class SharedMapSystem
foreach (var index in component.Chunks.Keys)
{
if (!state.FullGridData.ContainsKey(index))
component.RemoveChunk(index);
RemoveChunk(uid, component, index);
}
foreach (var (index, tiles) in state.FullGridData)
{
var chunk = component.GetOrAddChunk(index);
var chunk = GetOrAddChunk(uid, component, index);
chunk.SuppressCollisionRegeneration = true;
DebugTools.Assert(tiles.Length == component.ChunkSize * component.ChunkSize);
@@ -281,13 +284,13 @@ public abstract partial class SharedMapSystem
if (chunk.GetTile(x, y) == tile)
continue;
chunk.SetTile(x, y, tile);
SetChunkTile(uid, component, chunk, x, y, tile);
modified.Add((new Vector2i(chunk.X * component.ChunkSize + x, chunk.Y * component.ChunkSize + y), tile));
}
}
chunk.SuppressCollisionRegeneration = false;
component.RegenerateCollision(chunk);
RegenerateCollision(uid, component, chunk);
}
}
@@ -300,7 +303,7 @@ public abstract partial class SharedMapSystem
{
if (args.FromTick <= component.CreationTick)
{
GetFullState(component, ref args);
GetFullState(uid, component, ref args);
return;
}
@@ -324,7 +327,7 @@ public abstract partial class SharedMapSystem
chunkData.Add(ChunkDatum.CreateDeleted(indices));
}
foreach (var (index, chunk) in component.GetMapChunks())
foreach (var (index, chunk) in GetMapChunks(uid, component))
{
if (chunk.LastTileModifiedTick < fromTick)
continue;
@@ -348,11 +351,11 @@ public abstract partial class SharedMapSystem
args.State = new MapGridComponentState(component.ChunkSize, chunkData);
}
private void GetFullState(MapGridComponent component, ref ComponentGetState args)
private void GetFullState(EntityUid uid, MapGridComponent component, ref ComponentGetState args)
{
var chunkData = new Dictionary<Vector2i, Tile[]>();
foreach (var (index, chunk) in component.GetMapChunks())
foreach (var (index, chunk) in GetMapChunks(uid, component))
{
var tileBuffer = new Tile[component.ChunkSize * (uint)component.ChunkSize];
@@ -385,7 +388,6 @@ public abstract partial class SharedMapSystem
foreach (var chunk in component.Chunks.Values)
{
chunk.TileModified += component.OnTileModified;
chunk.LastTileModifiedTick = curTick;
}
@@ -445,7 +447,7 @@ public abstract partial class SharedMapSystem
if (!Resolve(uid, ref xform))
return new Box2();
var (worldPos, worldRot) = _transform.GetWorldPositionRotation(xform, GetEntityQuery<TransformComponent>());
var (worldPos, worldRot) = _transform.GetWorldPositionRotation(xform);
var aabb = grid.LocalAABB.Translated(worldPos);
return new Box2Rotated(aabb, worldRot, worldPos).CalcBoundingBox();
@@ -456,7 +458,7 @@ public abstract partial class SharedMapSystem
DebugTools.Assert(!EntityManager.HasComponent<MapComponent>(uid));
var aabb = GetWorldAABB(uid, grid);
if (!TryComp<TransformComponent>(uid, out var xform))
if (!_xformQuery.TryGetComponent(uid, out var xform))
return;
if (TryComp<GridTreeComponent>(xform.MapUid, out var gridTree))
@@ -486,4 +488,796 @@ public abstract partial class SharedMapSystem
movedGrids.MovedGrids.Remove(uid);
}
}
private void RemoveChunk(EntityUid uid, MapGridComponent grid, Vector2i origin)
{
if (!grid.Chunks.TryGetValue(origin, out var chunk))
return;
if (_netManager.IsServer)
grid.ChunkDeletionHistory.Add((_timing.CurTick, chunk.Indices));
chunk.Fixtures.Clear();
grid.Chunks.Remove(origin);
if (grid.Chunks.Count == 0)
RaiseLocalEvent(uid, new EmptyGridEvent { GridId = uid }, true);
}
/// <summary>
/// Regenerates the chunk local bounds of this chunk.
/// </summary>
private void RegenerateCollision(EntityUid uid, MapGridComponent grid, MapChunk mapChunk)
{
RegenerateCollision(uid, grid, new HashSet<MapChunk> { mapChunk });
}
/// <summary>
/// Regenerate collision for multiple chunks at once; faster than doing it individually.
/// </summary>
internal void RegenerateCollision(EntityUid uid, MapGridComponent grid, IReadOnlySet<MapChunk> chunks)
{
if (HasComp<MapComponent>(uid))
return;
var chunkRectangles = new Dictionary<MapChunk, List<Box2i>>(chunks.Count);
var removedChunks = new List<MapChunk>();
foreach (var mapChunk in chunks)
{
// Even if the chunk is still removed still need to make sure bounds are updated (for now...)
// generate collision rectangles for this chunk based on filled tiles.
GridChunkPartition.PartitionChunk(mapChunk, out var localBounds, out var rectangles);
mapChunk.CachedBounds = localBounds;
if (mapChunk.FilledTiles > 0)
chunkRectangles.Add(mapChunk, rectangles);
else
{
// Gone. Reduced to atoms
// Need to do this before RemoveChunk because it clears fixtures.
FixturesComponent? manager = null;
PhysicsComponent? body = null;
TransformComponent? xform = null;
foreach (var fixture in mapChunk.Fixtures)
{
_fixtures.DestroyFixture(uid, fixture, false, manager: manager, body: body, xform: xform);
}
RemoveChunk(uid, grid, mapChunk.Indices);
removedChunks.Add(mapChunk);
}
}
grid.LocalAABB = new Box2();
foreach (var chunk in grid.Chunks.Values)
{
var chunkBounds = chunk.CachedBounds;
if (chunkBounds.Size.Equals(Vector2i.Zero))
continue;
if (grid.LocalAABB.Size == Vector2.Zero)
{
var gridBounds = chunkBounds.Translated(chunk.Indices * chunk.ChunkSize);
grid.LocalAABB = gridBounds;
}
else
{
var gridBounds = chunkBounds.Translated(chunk.Indices * chunk.ChunkSize);
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);
}
#region TileAccess
public TileRef GetTileRef(EntityUid uid, MapGridComponent grid, MapCoordinates coords)
{
return GetTileRef(uid, grid, CoordinatesToTile(uid, grid, coords));
}
public TileRef GetTileRef(EntityUid uid, MapGridComponent grid, EntityCoordinates coords)
{
return GetTileRef(uid, grid, CoordinatesToTile(uid, grid, coords));
}
public TileRef GetTileRef(EntityUid uid, MapGridComponent grid, Vector2i tileCoordinates)
{
var chunkIndices = GridTileToChunkIndices(uid, grid, tileCoordinates);
if (!grid.Chunks.TryGetValue(chunkIndices, out var output))
{
// Chunk doesn't exist, return a tileRef to an empty (space) tile.
return new TileRef(uid, tileCoordinates.X, tileCoordinates.Y, default);
}
var chunkTileIndices = output.GridTileToChunkTile(tileCoordinates);
return GetTileRef(uid, grid, output, (ushort)chunkTileIndices.X, (ushort)chunkTileIndices.Y);
}
/// <summary>
/// Returns the tile at the given chunk indices.
/// </summary>
/// <param name="mapChunk"></param>
/// <param name="xIndex">The X tile index relative to the chunk origin.</param>
/// <param name="yIndex">The Y tile index relative to the chunk origin.</param>
/// <returns>A reference to a tile.</returns>
internal TileRef GetTileRef(EntityUid uid, MapGridComponent grid, MapChunk mapChunk, ushort xIndex, ushort yIndex)
{
if (xIndex >= mapChunk.ChunkSize)
throw new ArgumentOutOfRangeException(nameof(xIndex), "Tile indices out of bounds.");
if (yIndex >= mapChunk.ChunkSize)
throw new ArgumentOutOfRangeException(nameof(yIndex), "Tile indices out of bounds.");
var indices = mapChunk.ChunkTileToGridTile(new Vector2i(xIndex, yIndex));
return new TileRef(uid, indices, mapChunk.GetTile(xIndex, yIndex));
}
public IEnumerable<TileRef> GetAllTiles(EntityUid uid, MapGridComponent grid, bool ignoreEmpty = true)
{
foreach (var kvChunk in grid.Chunks)
{
var chunk = kvChunk.Value;
for (ushort x = 0; x < grid.ChunkSize; x++)
{
for (ushort y = 0; y < grid.ChunkSize; y++)
{
var tile = chunk.GetTile(x, y);
if (ignoreEmpty && tile.IsEmpty)
continue;
var (gridX, gridY) = new Vector2i(x, y) + chunk.Indices * grid.ChunkSize;
yield return new TileRef(uid, gridX, gridY, tile);
}
}
}
}
public GridTileEnumerator GetAllTilesEnumerator(EntityUid uid, MapGridComponent grid, bool ignoreEmpty = true)
{
return new GridTileEnumerator(uid, grid.Chunks.GetEnumerator(), grid.ChunkSize, ignoreEmpty);
}
public void SetTile(EntityUid uid, MapGridComponent grid, EntityCoordinates coords, Tile tile)
{
var localTile = CoordinatesToTile(uid, grid, coords);
SetTile(uid, grid, new Vector2i(localTile.X, localTile.Y), tile);
}
public void SetTile(EntityUid uid, MapGridComponent grid, Vector2i gridIndices, Tile tile)
{
var (chunk, chunkTile) = ChunkAndOffsetForTile(uid, grid, gridIndices);
SetChunkTile(uid, grid, chunk, (ushort)chunkTile.X, (ushort)chunkTile.Y, tile);
// Ideally we'd to this here for consistency but apparently tile modified does it or something.
// Yeah it's noodly.
// RegenerateCollision(chunk);
}
public void SetTiles(EntityUid uid, MapGridComponent grid, List<(Vector2i GridIndices, Tile Tile)> tiles)
{
if (tiles.Count == 0)
return;
var chunks = new HashSet<MapChunk>(Math.Max(1, tiles.Count / grid.ChunkSize));
foreach (var (gridIndices, tile) in tiles)
{
var (chunk, chunkTile) = ChunkAndOffsetForTile(uid, grid, gridIndices);
chunks.Add(chunk);
chunk.SuppressCollisionRegeneration = true;
SetChunkTile(uid, grid, chunk, (ushort)chunkTile.X, (ushort)chunkTile.Y, tile);
}
foreach (var chunk in chunks)
{
chunk.SuppressCollisionRegeneration = false;
}
RegenerateCollision(uid, grid, chunks);
}
public IEnumerable<TileRef> GetLocalTilesIntersecting(EntityUid uid, MapGridComponent grid, Box2Rotated localArea, bool ignoreEmpty = true,
Predicate<TileRef>? predicate = null)
{
var localAABB = localArea.CalcBoundingBox();
return GetLocalTilesIntersecting(uid, grid, localAABB, ignoreEmpty, predicate);
}
public IEnumerable<TileRef> GetTilesIntersecting(EntityUid uid, MapGridComponent grid, Box2Rotated worldArea, bool ignoreEmpty = true,
Predicate<TileRef>? predicate = null)
{
var matrix = _transform.GetInvWorldMatrix(uid);
var localArea = matrix.TransformBox(worldArea);
foreach (var tile in GetLocalTilesIntersecting(uid, grid, localArea, ignoreEmpty, predicate))
{
yield return tile;
}
}
public IEnumerable<TileRef> GetTilesIntersecting(EntityUid uid, MapGridComponent grid, Box2 worldArea, bool ignoreEmpty = true,
Predicate<TileRef>? predicate = null)
{
var matrix = _transform.GetInvWorldMatrix(uid);
var localArea = matrix.TransformBox(worldArea);
foreach (var tile in GetLocalTilesIntersecting(uid, grid, localArea, ignoreEmpty, predicate))
{
yield return tile;
}
}
public IEnumerable<TileRef> GetLocalTilesIntersecting(EntityUid uid, MapGridComponent grid, Box2 localArea, bool ignoreEmpty = true,
Predicate<TileRef>? predicate = null)
{
// TODO: Should move the intersecting calls onto mapmanager system and then allow people to pass in xform / xformquery
// that way we can avoid the GetComp here.
var gridTileLb = new Vector2i((int)Math.Floor(localArea.Left), (int)Math.Floor(localArea.Bottom));
// If we have 20.1 we want to include that tile but if we have 20 then we don't.
var gridTileRt = new Vector2i((int)Math.Ceiling(localArea.Right), (int)Math.Ceiling(localArea.Top));
for (var x = gridTileLb.X; x < gridTileRt.X; x++)
{
for (var y = gridTileLb.Y; y < gridTileRt.Y; y++)
{
var gridChunk = GridTileToChunkIndices(uid, grid, new Vector2i(x, y));
if (grid.Chunks.TryGetValue(gridChunk, out var chunk))
{
var chunkTile = chunk.GridTileToChunkTile(new Vector2i(x, y));
var tile = GetTileRef(uid, grid, chunk, (ushort)chunkTile.X, (ushort)chunkTile.Y);
if (ignoreEmpty && tile.Tile.IsEmpty)
continue;
if (predicate == null || predicate(tile))
yield return tile;
}
else if (!ignoreEmpty)
{
var tile = new TileRef(uid, x, y, Tile.Empty);
if (predicate == null || predicate(tile))
yield return tile;
}
}
}
}
public IEnumerable<TileRef> GetTilesIntersecting(EntityUid uid, MapGridComponent grid, Circle worldArea, bool ignoreEmpty = true,
Predicate<TileRef>? predicate = null)
{
var aabb = new Box2(worldArea.Position.X - worldArea.Radius, worldArea.Position.Y - worldArea.Radius,
worldArea.Position.X + worldArea.Radius, worldArea.Position.Y + worldArea.Radius);
var circleGridPos = new EntityCoordinates(uid, WorldToLocal(uid, grid, worldArea.Position));
foreach (var tile in GetTilesIntersecting(uid, grid, aabb, ignoreEmpty, predicate))
{
var local = GridTileToLocal(uid, grid, tile.GridIndices);
if (!local.TryDistance(EntityManager, _transform, circleGridPos, out var distance))
{
continue;
}
if (distance <= worldArea.Radius)
{
yield return tile;
}
}
}
private bool TryGetTile(EntityUid uid, MapGridComponent grid, Vector2i indices, bool ignoreEmpty, [NotNullWhen(true)] out TileRef? tileRef, Predicate<TileRef>? predicate = null)
{
// Similar to TryGetTileRef but for the tiles intersecting iterators.
var gridChunk = GridTileToChunkIndices(uid, grid, indices);
if (grid.Chunks.TryGetValue(gridChunk, out var chunk))
{
var chunkTile = chunk.GridTileToChunkTile(indices);
var tile = GetTileRef(uid, grid, chunk, (ushort)chunkTile.X, (ushort)chunkTile.Y);
if (ignoreEmpty && tile.Tile.IsEmpty)
{
tileRef = null;
return false;
}
if (predicate == null || predicate(tile))
{
tileRef = tile;
return true;
}
}
else if (!ignoreEmpty)
{
var tile = new TileRef(uid, indices.X, indices.Y, Tile.Empty);
if (predicate == null || predicate(tile))
{
tileRef = tile;
return true;
}
}
tileRef = null;
return false;
}
#endregion TileAccess
#region ChunkAccess
internal MapChunk GetOrAddChunk(EntityUid uid, MapGridComponent grid, int xIndex, int yIndex)
{
return GetOrAddChunk(uid, grid, new Vector2i(xIndex, yIndex));
}
internal bool TryGetChunk(EntityUid uid, MapGridComponent grid, Vector2i chunkIndices, [NotNullWhen(true)] out MapChunk? chunk)
{
return grid.Chunks.TryGetValue(chunkIndices, out chunk);
}
internal MapChunk GetOrAddChunk(EntityUid uid, MapGridComponent grid, Vector2i chunkIndices)
{
if (grid.Chunks.TryGetValue(chunkIndices, out var output))
return output;
var newChunk = new MapChunk(chunkIndices.X, chunkIndices.Y, grid.ChunkSize)
{
LastTileModifiedTick = _timing.CurTick
};
return grid.Chunks[chunkIndices] = newChunk;
}
public bool HasChunk(EntityUid uid, MapGridComponent grid, Vector2i chunkIndices)
{
return grid.Chunks.ContainsKey(chunkIndices);
}
internal IReadOnlyDictionary<Vector2i, MapChunk> GetMapChunks(EntityUid uid, MapGridComponent grid)
{
return grid.Chunks;
}
internal ChunkEnumerator GetMapChunks(EntityUid uid, MapGridComponent grid, Box2 worldAABB)
{
var localAABB = _transform.GetInvWorldMatrix(uid).TransformBox(worldAABB);
return new ChunkEnumerator(grid.Chunks, localAABB, grid.ChunkSize);
}
internal ChunkEnumerator GetMapChunks(EntityUid uid, MapGridComponent grid, Box2Rotated worldArea)
{
var matrix = _transform.GetInvWorldMatrix(uid);
var localArea = matrix.TransformBox(worldArea);
return new ChunkEnumerator(grid.Chunks, localArea, grid.ChunkSize);
}
internal ChunkEnumerator GetLocalMapChunks(EntityUid uid, MapGridComponent grid, Box2 localAABB)
{
return new ChunkEnumerator(grid.Chunks, localAABB, grid.ChunkSize);
}
#endregion ChunkAccess
#region SnapGridAccess
public int AnchoredEntityCount(EntityUid uid, MapGridComponent grid, Vector2i pos)
{
var gridChunkPos = GridTileToChunkIndices(uid, grid, pos);
if (!grid.Chunks.TryGetValue(gridChunkPos, out var chunk))
return 0;
var (x, y) = chunk.GridTileToChunkTile(pos);
return chunk.GetSnapGrid((ushort)x, (ushort)y)?.Count ?? 0; // ?
}
public IEnumerable<EntityUid> GetAnchoredEntities(EntityUid uid, MapGridComponent grid, MapCoordinates coords)
{
return GetAnchoredEntities(uid, grid, TileIndicesFor(uid, grid, coords));
}
public IEnumerable<EntityUid> GetAnchoredEntities(EntityUid uid, MapGridComponent grid, EntityCoordinates coords)
{
return GetAnchoredEntities(uid, grid, TileIndicesFor(uid, grid, coords));
}
public IEnumerable<EntityUid> GetAnchoredEntities(EntityUid uid, MapGridComponent grid, Vector2i pos)
{
// Because some content stuff checks neighboring tiles (which may not actually exist) we won't just
// create an entire chunk for it.
var gridChunkPos = GridTileToChunkIndices(uid, grid, pos);
if (!grid.Chunks.TryGetValue(gridChunkPos, out var chunk)) return Enumerable.Empty<EntityUid>();
var chunkTile = chunk.GridTileToChunkTile(pos);
return chunk.GetSnapGridCell((ushort)chunkTile.X, (ushort)chunkTile.Y);
}
public AnchoredEntitiesEnumerator GetAnchoredEntitiesEnumerator(EntityUid uid, MapGridComponent grid, Vector2i pos)
{
var gridChunkPos = GridTileToChunkIndices(uid, grid, pos);
if (!grid.Chunks.TryGetValue(gridChunkPos, out var chunk)) return AnchoredEntitiesEnumerator.Empty;
var chunkTile = chunk.GridTileToChunkTile(pos);
var snapgrid = chunk.GetSnapGrid((ushort)chunkTile.X, (ushort)chunkTile.Y);
return snapgrid == null
? AnchoredEntitiesEnumerator.Empty
: new AnchoredEntitiesEnumerator(snapgrid.GetEnumerator());
}
public IEnumerable<EntityUid> GetLocalAnchoredEntities(EntityUid uid, MapGridComponent grid, Box2 localAABB)
{
foreach (var tile in GetLocalTilesIntersecting(uid, grid, localAABB, true, null))
{
foreach (var ent in GetAnchoredEntities(uid, grid, tile.GridIndices))
{
yield return ent;
}
}
}
public IEnumerable<EntityUid> GetAnchoredEntities(EntityUid uid, MapGridComponent grid, Box2 worldAABB)
{
foreach (var tile in GetTilesIntersecting(uid, grid, worldAABB))
{
foreach (var ent in GetAnchoredEntities(uid, grid, tile.GridIndices))
{
yield return ent;
}
}
}
public IEnumerable<EntityUid> GetAnchoredEntities(EntityUid uid, MapGridComponent grid, Box2Rotated worldBounds)
{
foreach (var tile in GetTilesIntersecting(uid, grid, worldBounds))
{
foreach (var ent in GetAnchoredEntities(uid, grid, tile.GridIndices))
{
yield return ent;
}
}
}
public Vector2i TileIndicesFor(EntityUid uid, MapGridComponent grid, EntityCoordinates coords)
{
#if DEBUG
var mapId = _xformQuery.GetComponent(uid).MapID;
DebugTools.Assert(mapId == coords.GetMapId(EntityManager));
#endif
return SnapGridLocalCellFor(uid, grid, LocalToGrid(uid, grid, coords));
}
public Vector2i TileIndicesFor(EntityUid uid, MapGridComponent grid, MapCoordinates worldPos)
{
#if DEBUG
var mapId = _xformQuery.GetComponent(uid).MapID;
DebugTools.Assert(mapId == worldPos.MapId);
#endif
var localPos = WorldToLocal(uid, grid, worldPos.Position);
return SnapGridLocalCellFor(uid, grid, localPos);
}
private Vector2i SnapGridLocalCellFor(EntityUid uid, MapGridComponent grid, Vector2 localPos)
{
var x = (int)Math.Floor(localPos.X / grid.TileSize);
var y = (int)Math.Floor(localPos.Y / grid.TileSize);
return new Vector2i(x, y);
}
public bool IsAnchored(EntityUid uid, MapGridComponent grid, EntityCoordinates coords, EntityUid euid)
{
var tilePos = TileIndicesFor(uid, grid, coords);
var (chunk, chunkTile) = ChunkAndOffsetForTile(uid, grid, tilePos);
var snapgrid = chunk.GetSnapGrid((ushort)chunkTile.X, (ushort)chunkTile.Y);
return snapgrid?.Contains(euid) == true;
}
public bool AddToSnapGridCell(EntityUid gridUid, MapGridComponent grid, Vector2i pos, EntityUid euid)
{
var (chunk, chunkTile) = ChunkAndOffsetForTile(gridUid, grid, pos);
if (chunk.GetTile((ushort)chunkTile.X, (ushort)chunkTile.Y).IsEmpty)
return false;
chunk.AddToSnapGridCell((ushort)chunkTile.X, (ushort)chunkTile.Y, euid);
return true;
}
public bool AddToSnapGridCell(EntityUid gridUid, MapGridComponent grid, EntityCoordinates coords, EntityUid euid)
{
return AddToSnapGridCell(gridUid, grid, TileIndicesFor(gridUid, grid, coords), euid);
}
public void RemoveFromSnapGridCell(EntityUid gridUid, MapGridComponent grid, Vector2i pos, EntityUid euid)
{
var (chunk, chunkTile) = ChunkAndOffsetForTile(gridUid, grid, pos);
chunk.RemoveFromSnapGridCell((ushort)chunkTile.X, (ushort)chunkTile.Y, euid);
}
public void RemoveFromSnapGridCell(EntityUid gridUid, MapGridComponent grid, EntityCoordinates coords, EntityUid euid)
{
RemoveFromSnapGridCell(gridUid, grid, TileIndicesFor(gridUid, grid, coords), euid);
}
private (MapChunk, Vector2i) ChunkAndOffsetForTile(EntityUid uid, MapGridComponent grid, Vector2i pos)
{
var gridChunkIndices = GridTileToChunkIndices(uid, grid, pos);
var chunk = GetOrAddChunk(uid, grid, gridChunkIndices);
var chunkTile = chunk.GridTileToChunkTile(pos);
return (chunk, chunkTile);
}
public IEnumerable<EntityUid> GetInDir(EntityUid uid, MapGridComponent grid, EntityCoordinates position, Direction dir)
{
var pos = GetDirection(TileIndicesFor(uid, grid, position), dir);
return GetAnchoredEntities(uid, grid, pos);
}
public IEnumerable<EntityUid> GetOffset(EntityUid uid, MapGridComponent grid, EntityCoordinates coords, Vector2i offset)
{
var pos = TileIndicesFor(uid, grid, coords) + offset;
return GetAnchoredEntities(uid, grid, pos);
}
public IEnumerable<EntityUid> GetLocal(EntityUid uid, MapGridComponent grid, EntityCoordinates coords)
{
return GetAnchoredEntities(uid, grid, TileIndicesFor(uid, grid, coords));
}
public EntityCoordinates DirectionToGrid(EntityUid uid, MapGridComponent grid, EntityCoordinates coords, Direction direction)
{
return GridTileToLocal(uid, grid, GetDirection(TileIndicesFor(uid, grid, coords), direction));
}
public IEnumerable<EntityUid> GetCardinalNeighborCells(EntityUid uid, MapGridComponent grid, EntityCoordinates coords)
{
var position = TileIndicesFor(uid, grid, coords);
foreach (var cell in GetAnchoredEntities(uid, grid, position))
yield return cell;
foreach (var cell in GetAnchoredEntities(uid, grid, position + new Vector2i(0, 1)))
yield return cell;
foreach (var cell in GetAnchoredEntities(uid, grid, position + new Vector2i(0, -1)))
yield return cell;
foreach (var cell in GetAnchoredEntities(uid, grid, position + new Vector2i(1, 0)))
yield return cell;
foreach (var cell in GetAnchoredEntities(uid, grid, position + new Vector2i(-1, 0)))
yield return cell;
}
public IEnumerable<EntityUid> GetCellsInSquareArea(EntityUid uid, MapGridComponent grid, EntityCoordinates coords, int n)
{
var position = TileIndicesFor(uid, grid, coords);
for (var y = -n; y <= n; ++y)
for (var x = -n; x <= n; ++x)
{
var enumerator = GetAnchoredEntitiesEnumerator(uid, grid, position + new Vector2i(x, y));
while (enumerator.MoveNext(out var cell))
{
yield return cell.Value;
}
}
}
#endregion
#region Transforms
public Vector2 WorldToLocal(EntityUid uid, MapGridComponent grid, Vector2 posWorld)
{
var matrix = _transform.GetInvWorldMatrix(uid);
return matrix.Transform(posWorld);
}
public EntityCoordinates MapToGrid(EntityUid uid, MapCoordinates posWorld)
{
var mapId = _xformQuery.GetComponent(uid).MapID;
if (posWorld.MapId != mapId)
throw new ArgumentException(
$"Grid {uid} is on map {mapId}, but coords are on map {posWorld.MapId}.",
nameof(posWorld));
if (!TryComp<MapGridComponent>(uid, out var grid))
{
return new EntityCoordinates(MapManager.GetMapEntityId(posWorld.MapId), new Vector2(posWorld.X, posWorld.Y));
}
return new EntityCoordinates(uid, WorldToLocal(uid, grid, posWorld.Position));
}
public Vector2 LocalToWorld(EntityUid uid, MapGridComponent grid, Vector2 posLocal)
{
var matrix = _transform.GetWorldMatrix(uid);
return matrix.Transform(posLocal);
}
public Vector2i WorldToTile(EntityUid uid, MapGridComponent grid, Vector2 posWorld)
{
var local = WorldToLocal(uid, grid, posWorld);
var x = (int)Math.Floor(local.X / grid.TileSize);
var y = (int)Math.Floor(local.Y / grid.TileSize);
return new Vector2i(x, y);
}
public Vector2i LocalToTile(EntityUid uid, MapGridComponent grid, EntityCoordinates coordinates)
{
var position = LocalToGrid(uid, grid, coordinates);
return new Vector2i((int) Math.Floor(position.X / grid.TileSize), (int) Math.Floor(position.Y / grid.TileSize));
}
public Vector2i CoordinatesToTile(EntityUid uid, MapGridComponent grid, MapCoordinates coords)
{
#if DEBUG
var mapId = _xformQuery.GetComponent(uid).MapID;
DebugTools.Assert(mapId == coords.MapId);
#endif
var local = WorldToLocal(uid, grid, coords.Position);
var x = (int)Math.Floor(local.X / grid.TileSize);
var y = (int)Math.Floor(local.Y / grid.TileSize);
return new Vector2i(x, y);
}
public Vector2i CoordinatesToTile(EntityUid uid, MapGridComponent grid, EntityCoordinates coords)
{
#if DEBUG
var mapId = _xformQuery.GetComponent(uid).MapID;
DebugTools.Assert(mapId == coords.GetMapId(EntityManager));
#endif
var local = LocalToGrid(uid, grid, coords);
var x = (int)Math.Floor(local.X / grid.TileSize);
var y = (int)Math.Floor(local.Y / grid.TileSize);
return new Vector2i(x, y);
}
public Vector2i LocalToChunkIndices(EntityUid uid, MapGridComponent grid, EntityCoordinates gridPos)
{
var local = LocalToGrid(uid, grid, gridPos);
var x = (int)Math.Floor(local.X / (grid.TileSize * grid.ChunkSize));
var y = (int)Math.Floor(local.Y / (grid.TileSize * grid.ChunkSize));
return new Vector2i(x, y);
}
public Vector2 LocalToGrid(EntityUid uid, MapGridComponent grid, EntityCoordinates position)
{
return position.EntityId == uid
? position.Position
: WorldToLocal(uid, grid, position.ToMapPos(EntityManager, _transform));
}
public bool CollidesWithGrid(EntityUid uid, MapGridComponent grid, Vector2i indices)
{
var chunkIndices = GridTileToChunkIndices(uid, grid, indices);
if (!grid.Chunks.TryGetValue(chunkIndices, out var chunk))
return false;
var cTileIndices = chunk.GridTileToChunkTile(indices);
return chunk.GetTile((ushort)cTileIndices.X, (ushort)cTileIndices.Y).TypeId != Tile.Empty.TypeId;
}
public Vector2i GridTileToChunkIndices(EntityUid uid, MapGridComponent grid, Vector2i gridTile)
{
var x = (int)Math.Floor(gridTile.X / (float) grid.ChunkSize);
var y = (int)Math.Floor(gridTile.Y / (float) grid.ChunkSize);
return new Vector2i(x, y);
}
public EntityCoordinates GridTileToLocal(EntityUid uid, MapGridComponent grid, Vector2i gridTile)
{
return new(uid,
new Vector2(gridTile.X * grid.TileSize + (grid.TileSize / 2f), gridTile.Y * grid.TileSize + (grid.TileSize / 2f)));
}
public Vector2 GridTileToWorldPos(EntityUid uid, MapGridComponent grid, Vector2i gridTile)
{
var locX = gridTile.X * grid.TileSize + (grid.TileSize / 2f);
var locY = gridTile.Y * grid.TileSize + (grid.TileSize / 2f);
return _transform.GetWorldMatrix(uid).Transform(new Vector2(locX, locY));
}
public MapCoordinates GridTileToWorld(EntityUid uid, MapGridComponent grid, Vector2i gridTile)
{
var parentMapId = _xformQuery.GetComponent(uid).MapID;
return new(GridTileToWorldPos(uid, grid, gridTile), parentMapId);
}
public bool TryGetTileRef(EntityUid uid, MapGridComponent grid, Vector2i indices, out TileRef tile)
{
var chunkIndices = GridTileToChunkIndices(uid, grid, indices);
if (!grid.Chunks.TryGetValue(chunkIndices, out var chunk))
{
tile = default;
return false;
}
var cTileIndices = chunk.GridTileToChunkTile(indices);
tile = GetTileRef(uid, grid, chunk, (ushort)cTileIndices.X, (ushort)cTileIndices.Y);
return true;
}
public bool TryGetTileRef(EntityUid uid, MapGridComponent grid, EntityCoordinates coords, out TileRef tile)
{
return TryGetTileRef(uid, grid, CoordinatesToTile(uid, grid, coords), out tile);
}
public bool TryGetTileRef(EntityUid uid, MapGridComponent grid, Vector2 worldPos, out TileRef tile)
{
return TryGetTileRef(uid, grid, WorldToTile(uid, grid, worldPos), out tile);
}
#endregion Transforms
/// <summary>
/// Calculate the world space AABB for this chunk.
/// </summary>
internal Box2 CalcWorldAABB(EntityUid uid, MapGridComponent grid, MapChunk mapChunk)
{
var (position, rotation) =
_transform.GetWorldPositionRotation(uid);
var chunkPosition = mapChunk.Indices;
var tileScale = grid.TileSize;
var chunkScale = mapChunk.ChunkSize;
var worldPos = position + rotation.RotateVec(chunkPosition * tileScale * chunkScale);
return new Box2Rotated(
((Box2)mapChunk.CachedBounds
.Scale(tileScale))
.Translated(worldPos),
rotation, worldPos).CalcBoundingBox();
}
private void OnTileModified(EntityUid uid, MapGridComponent grid, MapChunk mapChunk, Vector2i tileIndices, Tile newTile, Tile oldTile,
bool shapeChanged)
{
// As the collision regeneration can potentially delete the chunk we'll notify of the tile changed first.
var gridTile = mapChunk.ChunkTileToGridTile(tileIndices);
mapChunk.LastTileModifiedTick = _timing.CurTick;
grid.LastTileModifiedTick = _timing.CurTick;
Dirty(grid);
// The map serializer currently sets tiles of unbound grids as part of the deserialization process
// It properly sets SuppressOnTileChanged so that the event isn't spammed for every tile on the grid.
// ParentMapId is not able to be accessed on unbound grids, so we can't even call this function for unbound grids.
if (!MapManager.SuppressOnTileChanged)
{
var newTileRef = new TileRef(uid, gridTile, newTile);
_mapInternal.RaiseOnTileChanged(newTileRef, oldTile);
}
if (shapeChanged && !mapChunk.SuppressCollisionRegeneration)
{
RegenerateCollision(uid, grid, mapChunk);
}
}
}

View File

@@ -0,0 +1,25 @@
using System;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
namespace Robust.Shared.GameObjects;
public abstract partial class SharedMapSystem
{
/// <summary>
/// Replaces a single tile inside of the chunk.
/// </summary>
/// <param name="xIndex">The X tile index relative to the chunk.</param>
/// <param name="yIndex">The Y tile index relative to the chunk.</param>
/// <param name="tile">The new tile to insert.</param>
internal void SetChunkTile(EntityUid uid, MapGridComponent grid, MapChunk chunk, ushort xIndex, ushort yIndex, Tile tile)
{
if (!chunk.TrySetTile(xIndex, yIndex, tile, out var oldTile, out var shapeChanged))
return;
var tileIndices = new Vector2i(xIndex, yIndex);
OnTileModified(uid, grid, chunk, tileIndices, tile, oldTile, shapeChanged);
}
}

View File

@@ -6,21 +6,30 @@ using System.Collections.Generic;
using Robust.Shared.GameStates;
using Robust.Shared.Map.Components;
using System.Linq;
using Robust.Shared.Network;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Timing;
namespace Robust.Shared.GameObjects
{
[UsedImplicitly]
public abstract partial class SharedMapSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] protected readonly IMapManager MapManager = default!;
[Dependency] private readonly IMapManagerInternal _mapInternal = default!;
[Dependency] private readonly INetManager _netManager = default!;
[Dependency] private readonly FixtureSystem _fixtures = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
private EntityQuery<TransformComponent> _xformQuery;
public override void Initialize()
{
base.Initialize();
_xformQuery = GetEntityQuery<TransformComponent>();
InitializeMap();
InitializeGrid();

View File

@@ -37,7 +37,7 @@ namespace Robust.Shared.GameObjects
comp._enabled = enabled;
RaiseLocalEvent(uid, new PointLightToggleEvent(comp.Enabled));
Dirty(comp);
Dirty(uid, comp);
}
public virtual void SetRadius(EntityUid uid, float radius, SharedPointLightComponent? comp = null)
@@ -46,7 +46,7 @@ namespace Robust.Shared.GameObjects
return;
comp._radius = radius;
Dirty(comp);
Dirty(uid, comp);
}
}
}

View File

@@ -33,8 +33,8 @@ public abstract partial class SharedTransformSystem
EntityQuery<TransformComponent> xformQuery)
{
// Bypass some of the expensive stuff in unanchoring / anchoring.
oldGrid.RemoveFromSnapGridCell(tilePos, uid);
newGrid.AddToSnapGridCell(tilePos, uid);
_map.RemoveFromSnapGridCell(oldGridUid, oldGrid, tilePos, uid);
_map.AddToSnapGridCell(newGridUid, newGrid, tilePos, uid);
// TODO: Could do this re-parent way better.
// Unfortunately we don't want any anchoring events to go out hence... this.
xform._anchored = false;
@@ -77,7 +77,7 @@ public abstract partial class SharedTransformSystem
MapGridComponent grid,
Vector2i tileIndices)
{
if (!grid.AddToSnapGridCell(tileIndices, uid))
if (!_map.AddToSnapGridCell(gridUid, grid, tileIndices, uid))
return false;
var wasAnchored = xform._anchored;
@@ -94,7 +94,7 @@ public abstract partial class SharedTransformSystem
}
// Anchor snapping. If there is a coordinate change, it will dirty the component for us.
var pos = new EntityCoordinates(gridUid, grid.GridTileToLocal(tileIndices).Position);
var pos = new EntityCoordinates(gridUid, _map.GridTileToLocal(gridUid, grid, tileIndices).Position);
SetCoordinates(uid, xform, pos, unanchor: false);
return true;
@@ -102,14 +102,14 @@ public abstract partial class SharedTransformSystem
public bool AnchorEntity(EntityUid uid, TransformComponent xform, MapGridComponent grid)
{
var tileIndices = grid.TileIndicesFor(xform.Coordinates);
var tileIndices = _map.TileIndicesFor(grid.Owner, grid, xform.Coordinates);
return AnchorEntity(uid, xform, grid, tileIndices);
}
public bool AnchorEntity(EntityUid uid, TransformComponent xform)
{
return _mapManager.TryGetGrid(xform.GridUid, out var grid)
&& AnchorEntity(uid, xform, grid, grid.TileIndicesFor(xform.Coordinates));
&& AnchorEntity(uid, xform, grid, _map.TileIndicesFor(xform.GridUid.Value, grid, xform.Coordinates));
}
public void Unanchor(EntityUid uid, TransformComponent xform, bool setPhysics = true)
@@ -128,8 +128,8 @@ public abstract partial class SharedTransformSystem
if (TryComp(xform.GridUid, out MapGridComponent? grid))
{
var tileIndices = grid.TileIndicesFor(xform.Coordinates);
grid.RemoveFromSnapGridCell(tileIndices, uid);
var tileIndices = _map.TileIndicesFor(xform.GridUid.Value, grid, xform.Coordinates);
_map.RemoveFromSnapGridCell(xform.GridUid.Value, grid, tileIndices, uid);
}
else if (xform.Initialized)
{
@@ -156,7 +156,7 @@ public abstract partial class SharedTransformSystem
/// </summary>
public bool ContainsEntity(TransformComponent xform, EntityUid entity)
{
return ContainsEntity(xform, entity, GetEntityQuery<TransformComponent>());
return ContainsEntity(xform, entity, _xformQuery);
}
/// <inheritdoc cref="ContainsEntity(Robust.Shared.GameObjects.TransformComponent,Robust.Shared.GameObjects.EntityUid)"/>
@@ -168,7 +168,7 @@ public abstract partial class SharedTransformSystem
/// <inheritdoc cref="ContainsEntity(Robust.Shared.GameObjects.TransformComponent,Robust.Shared.GameObjects.EntityUid)"/>
public bool ContainsEntity(TransformComponent xform, TransformComponent entityTransform)
{
return ContainsEntity(xform, entityTransform, GetEntityQuery<TransformComponent>());
return ContainsEntity(xform, entityTransform, _xformQuery);
}
/// <inheritdoc cref="ContainsEntity(Robust.Shared.GameObjects.TransformComponent,Robust.Shared.GameObjects.EntityUid)"/>
@@ -227,11 +227,9 @@ public abstract partial class SharedTransformSystem
return value;
}
var xformQuery = GetEntityQuery<TransformComponent>();
if (!component._mapIdInitialized)
{
FindMapIdAndSet(uid, component, EntityManager, xformQuery, _mapManager);
FindMapIdAndSet(uid, component, EntityManager, _xformQuery, _mapManager);
component._mapIdInitialized = true;
}
@@ -241,7 +239,7 @@ public abstract partial class SharedTransformSystem
// Note that _children is a HashSet<EntityUid>,
// so duplicate additions (which will happen) don't matter.
var parentXform = xformQuery.GetComponent(component.ParentUid);
var parentXform = _xformQuery.GetComponent(component.ParentUid);
if (parentXform.LifeStage > ComponentLifeStage.Running || LifeStage(component.ParentUid) > EntityLifeStage.MapInitialized)
{
var msg = $"Attempted to re-parent to a terminating object. Entity: {ToPrettyString(component.ParentUid)}, new parent: {ToPrettyString(uid)}";
@@ -365,7 +363,7 @@ public abstract partial class SharedTransformSystem
DebugTools.Assert(!HasComp<MapGridComponent>(uid));
DebugTools.Assert(gridId == null || HasComp<MapGridComponent>(gridId));
xformQuery ??= GetEntityQuery<TransformComponent>();
xformQuery ??= _xformQuery;
SetGridIdRecursive(uid, xform, gridId, xformQuery.Value);
}
@@ -418,6 +416,16 @@ public abstract partial class SharedTransformSystem
#region Local Rotation
public void SetLocalRotationNoLerp(EntityUid uid, Angle angle)
{
SetLocalRotationNoLerp(_xformQuery.GetComponent(uid), angle);
}
public virtual void SetLocalRotationNoLerp(TransformComponent xform, Angle angle)
{
xform.LocalRotation = angle;
}
public void SetLocalRotation(EntityUid uid, Angle value, TransformComponent? xform = null)
{
if (!Resolve(uid, ref xform)) return;
@@ -464,7 +472,7 @@ public abstract partial class SharedTransformSystem
Unanchor(uid, xform);
// Set new values
Dirty(xform);
Dirty(uid, xform);
xform.MatricesDirty = true;
xform._localPosition = value.Position;
@@ -476,8 +484,6 @@ public abstract partial class SharedTransformSystem
// Perform parent change logic
if (value.EntityId != xform._parent)
{
var xformQuery = GetEntityQuery<TransformComponent>();
if (value.EntityId == uid)
{
QueueDel(uid);
@@ -486,7 +492,7 @@ public abstract partial class SharedTransformSystem
if (value.EntityId.IsValid())
{
if (!xformQuery.Resolve(value.EntityId, ref newParent, false))
if (!_xformQuery.Resolve(value.EntityId, ref newParent, false))
{
QueueDel(uid);
throw new InvalidOperationException($"Attempted to parent entity {ToPrettyString(uid)} to non-existent entity {value.EntityId}");
@@ -520,13 +526,13 @@ public abstract partial class SharedTransformSystem
}
recursiveUid = recursiveXform.ParentUid;
recursiveXform = xformQuery.GetComponent(recursiveUid);
recursiveXform = _xformQuery.GetComponent(recursiveUid);
}
}
}
if (xform._parent.IsValid())
xformQuery.Resolve(xform._parent, ref oldParent);
_xformQuery.Resolve(xform._parent, ref oldParent);
oldParent?._children.Remove(uid);
newParent?._children.Add(uid);
@@ -536,7 +542,7 @@ public abstract partial class SharedTransformSystem
if (newParent != null)
{
xform.ChangeMapId(newParent.MapID, xformQuery);
xform.ChangeMapId(newParent.MapID, _xformQuery);
if (!xform._gridInitialized)
InitializeGridUid(uid, xform);
@@ -549,18 +555,18 @@ public abstract partial class SharedTransformSystem
}
else
{
xform.ChangeMapId(MapId.Nullspace, xformQuery);
xform.ChangeMapId(MapId.Nullspace, _xformQuery);
if (!xform._gridInitialized)
InitializeGridUid(uid, xform);
else
SetGridId(uid, xform, null, xformQuery);
SetGridId(uid, xform, null, _xformQuery);
}
if (xform.Initialized)
{
// preserve world rotation
if (rotation == null && oldParent != null && newParent != null && !xform.NoLocalRotation)
xform._localRotation += GetWorldRotation(oldParent, xformQuery) - GetWorldRotation(newParent, xformQuery);
xform._localRotation += GetWorldRotation(oldParent) - GetWorldRotation(newParent);
DebugTools.Assert(!xform.NoLocalRotation || xform.LocalRotation == 0);
@@ -588,7 +594,7 @@ public abstract partial class SharedTransformSystem
public void ReparentChildren(EntityUid oldUid, EntityUid uid)
{
ReparentChildren(oldUid, uid, GetEntityQuery<TransformComponent>());
ReparentChildren(oldUid, uid, _xformQuery);
}
/// <summary>
@@ -615,7 +621,7 @@ public abstract partial class SharedTransformSystem
public TransformComponent? GetParent(EntityUid uid)
{
return GetParent(uid, GetEntityQuery<TransformComponent>());
return GetParent(uid, _xformQuery);
}
public TransformComponent? GetParent(EntityUid uid, EntityQuery<TransformComponent> xformQuery)
@@ -625,7 +631,7 @@ public abstract partial class SharedTransformSystem
public TransformComponent? GetParent(TransformComponent xform)
{
return GetParent(xform, GetEntityQuery<TransformComponent>());
return GetParent(xform, _xformQuery);
}
public TransformComponent? GetParent(TransformComponent xform, EntityQuery<TransformComponent> xformQuery)
@@ -636,13 +642,12 @@ public abstract partial class SharedTransformSystem
public void SetParent(EntityUid uid, EntityUid parent)
{
var query = GetEntityQuery<TransformComponent>();
SetParent(uid, query.GetComponent(uid), parent, query);
SetParent(uid, _xformQuery.GetComponent(uid), parent, _xformQuery);
}
public void SetParent(EntityUid uid, TransformComponent xform, EntityUid parent, TransformComponent? parentXform = null)
{
SetParent(uid, xform, parent, GetEntityQuery<TransformComponent>(), parentXform);
SetParent(uid, xform, parent, _xformQuery, parentXform);
}
public void SetParent(EntityUid uid, TransformComponent xform, EntityUid parent, EntityQuery<TransformComponent> xformQuery, TransformComponent? parentXform = null)
@@ -701,8 +706,8 @@ public abstract partial class SharedTransformSystem
// remove from any old grid lookups
if (xform.Anchored && TryComp(xform.ParentUid, out MapGridComponent? grid))
{
var tileIndices = grid.TileIndicesFor(xform.Coordinates);
grid.RemoveFromSnapGridCell(tileIndices, uid);
var tileIndices = _map.TileIndicesFor(xform.ParentUid, grid, xform.Coordinates);
_map.RemoveFromSnapGridCell(xform.ParentUid, grid, tileIndices, uid);
}
// Set anchor state true during the move event unless the entity wasn't and isn't being anchored. This avoids unnecessary entity lookup changes.
@@ -719,8 +724,8 @@ public abstract partial class SharedTransformSystem
{
if (xform.ParentUid == xform.GridUid && TryComp(xform.GridUid, out MapGridComponent? newGrid))
{
var tileIndices = newGrid.TileIndicesFor(xform.Coordinates);
newGrid.AddToSnapGridCell(tileIndices, uid);
var tileIndices = _map.TileIndicesFor(xform.GridUid.Value, newGrid, xform.Coordinates);
_map.AddToSnapGridCell(xform.GridUid.Value, newGrid, tileIndices, uid);
}
else
{
@@ -768,8 +773,7 @@ public abstract partial class SharedTransformSystem
[Pure]
public Matrix3 GetWorldMatrix(EntityUid uid)
{
var query = GetEntityQuery<TransformComponent>();
return GetWorldMatrix(query.GetComponent(uid), query);
return GetWorldMatrix(_xformQuery.GetComponent(uid), _xformQuery);
}
// Temporary until it's moved here
@@ -777,7 +781,7 @@ public abstract partial class SharedTransformSystem
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Matrix3 GetWorldMatrix(TransformComponent component)
{
return GetWorldMatrix(component, GetEntityQuery<TransformComponent>());
return GetWorldMatrix(component, _xformQuery);
}
[Pure]
@@ -835,6 +839,12 @@ public abstract partial class SharedTransformSystem
return GetWorldPosition(component);
}
[Pure]
public (Vector2 WorldPosition, Angle WorldRotation) GetWorldPositionRotation(EntityUid uid)
{
return GetWorldPositionRotation(_xformQuery.GetComponent(uid));
}
[Pure]
public (Vector2 WorldPosition, Angle WorldRotation) GetWorldPositionRotation(TransformComponent component)
{
@@ -937,7 +947,7 @@ public abstract partial class SharedTransformSystem
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetWorldPosition(TransformComponent component, Vector2 worldPos)
{
SetWorldPosition(component, worldPos, GetEntityQuery<TransformComponent>());
SetWorldPosition(component, worldPos, _xformQuery);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -963,8 +973,7 @@ public abstract partial class SharedTransformSystem
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Angle GetWorldRotation(EntityUid uid)
{
var query = GetEntityQuery<TransformComponent>();
return GetWorldRotation(query.GetComponent(uid), query);
return GetWorldRotation(_xformQuery.GetComponent(uid), _xformQuery);
}
// Temporary until it's moved here
@@ -972,7 +981,7 @@ public abstract partial class SharedTransformSystem
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Angle GetWorldRotation(TransformComponent component)
{
return GetWorldRotation(component, GetEntityQuery<TransformComponent>());
return GetWorldRotation(component, _xformQuery);
}
[Pure]
@@ -1040,7 +1049,7 @@ public abstract partial class SharedTransformSystem
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetWorldPositionRotation(TransformComponent component, Vector2 worldPos, Angle worldRot)
{
SetWorldPositionRotation(component, worldPos, worldRot, GetEntityQuery<TransformComponent>());
SetWorldPositionRotation(component, worldPos, worldRot, _xformQuery);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -1105,15 +1114,14 @@ public abstract partial class SharedTransformSystem
[Pure]
public Matrix3 GetInvWorldMatrix(EntityUid uid)
{
var query = GetEntityQuery<TransformComponent>();
return GetInvWorldMatrix(query.GetComponent(uid), query);
return GetInvWorldMatrix(_xformQuery.GetComponent(uid), _xformQuery);
}
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Matrix3 GetInvWorldMatrix(TransformComponent component)
{
return GetInvWorldMatrix(component, GetEntityQuery<TransformComponent>());
return GetInvWorldMatrix(component, _xformQuery);
}
[Pure]
@@ -1138,15 +1146,14 @@ public abstract partial class SharedTransformSystem
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3 WorldMatrix)
GetWorldPositionRotationMatrix(EntityUid uid)
{
var query = GetEntityQuery<TransformComponent>();
return GetWorldPositionRotationMatrix(query.GetComponent(uid), query);
return GetWorldPositionRotationMatrix(_xformQuery.GetComponent(uid), _xformQuery);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3 WorldMatrix)
GetWorldPositionRotationMatrix(TransformComponent xform)
{
return GetWorldPositionRotationMatrix(xform, GetEntityQuery<TransformComponent>());
return GetWorldPositionRotationMatrix(xform, _xformQuery);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -1176,7 +1183,7 @@ public abstract partial class SharedTransformSystem
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3 InvWorldMatrix) GetWorldPositionRotationInvMatrix(TransformComponent xform)
{
return GetWorldPositionRotationInvMatrix(xform, GetEntityQuery<TransformComponent>());
return GetWorldPositionRotationInvMatrix(xform, _xformQuery);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -1200,15 +1207,14 @@ public abstract partial class SharedTransformSystem
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3 WorldMatrix, Matrix3 InvWorldMatrix)
GetWorldPositionRotationMatrixWithInv(EntityUid uid)
{
var query = GetEntityQuery<TransformComponent>();
return GetWorldPositionRotationMatrixWithInv(query.GetComponent(uid), query);
return GetWorldPositionRotationMatrixWithInv(_xformQuery.GetComponent(uid), _xformQuery);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3 WorldMatrix, Matrix3 InvWorldMatrix)
GetWorldPositionRotationMatrixWithInv(TransformComponent xform)
{
return GetWorldPositionRotationMatrixWithInv(xform, GetEntityQuery<TransformComponent>());
return GetWorldPositionRotationMatrixWithInv(xform, _xformQuery);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -1231,9 +1237,8 @@ public abstract partial class SharedTransformSystem
#region AttachToGridOrMap
public void AttachToGridOrMap(EntityUid uid, TransformComponent? xform = null)
{
var query = GetEntityQuery<TransformComponent>();
if (query.Resolve(uid, ref xform))
AttachToGridOrMap(uid, xform, query);
if (_xformQuery.Resolve(uid, ref xform))
AttachToGridOrMap(uid, xform, _xformQuery);
}
public void AttachToGridOrMap(EntityUid uid, TransformComponent xform, EntityQuery<TransformComponent> query)
@@ -1271,18 +1276,17 @@ public abstract partial class SharedTransformSystem
public bool TryGetMapOrGridCoordinates(EntityUid uid, [NotNullWhen(true)] out EntityCoordinates? coordinates, TransformComponent? xform = null)
{
var query = GetEntityQuery<TransformComponent>();
coordinates = null;
if (!query.Resolve(uid, ref xform))
if (!_xformQuery.Resolve(uid, ref xform))
return false;
if (!xform.ParentUid.IsValid())
return false;
EntityUid newParent;
var oldPos = GetWorldPosition(xform, query);
if (_mapManager.TryFindGridAt(xform.MapID, oldPos, query, out var gridUid, out _))
var oldPos = GetWorldPosition(xform, _xformQuery);
if (_mapManager.TryFindGridAt(xform.MapID, oldPos, _xformQuery, out var gridUid, out _))
{
newParent = gridUid;
}
@@ -1295,7 +1299,7 @@ public abstract partial class SharedTransformSystem
return false;
}
coordinates = new(newParent, GetInvWorldMatrix(newParent, query).Transform(oldPos));
coordinates = new(newParent, GetInvWorldMatrix(newParent, _xformQuery).Transform(oldPos));
return true;
}
#endregion
@@ -1332,8 +1336,8 @@ public abstract partial class SharedTransformSystem
if (xform.Anchored && _metaQuery.TryGetComponent(xform.GridUid, out var meta) && meta.EntityLifeStage <= EntityLifeStage.MapInitialized)
{
var grid = Comp<MapGridComponent>(xform.GridUid.Value);
var tileIndices = grid.TileIndicesFor(xform.Coordinates);
grid.RemoveFromSnapGridCell(tileIndices, uid);
var tileIndices = _map.TileIndicesFor(xform.GridUid.Value, grid, xform.Coordinates);
_map.RemoveFromSnapGridCell(xform.GridUid.Value, grid, tileIndices, uid);
xform._anchored = false;
var anchorStateChangedEvent = new AnchorStateChangedEvent(xform, true);
RaiseLocalEvent(uid, ref anchorStateChangedEvent, true);
@@ -1352,7 +1356,7 @@ public abstract partial class SharedTransformSystem
{
if (LifeStage(uid) > EntityLifeStage.Initialized)
{
SetGridId(uid, component, uid, GetEntityQuery<TransformComponent>());
SetGridId(uid, component, uid, _xformQuery);
return;
}
component._gridInitialized = true;

View File

@@ -19,6 +19,7 @@ namespace Robust.Shared.GameObjects
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
private EntityQuery<MapGridComponent> _gridQuery;
@@ -254,7 +255,7 @@ namespace Robust.Shared.GameObjects
return GetWorldPosition(xform).Floored();
// We're on a grid, need to convert the coordinates to grid tiles.
return _mapManager.GetGrid(xform.GridUid.Value).CoordinatesToTile(xform.Coordinates);
return _map.CoordinatesToTile(xform.GridUid.Value, Comp<MapGridComponent>(xform.GridUid.Value), xform.Coordinates);
}
}

View File

@@ -1,20 +1,13 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Map;
using Robust.Shared.Map.Enumerators;
using Robust.Shared.Map.Events;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Timing;
@@ -27,10 +20,8 @@ namespace Robust.Shared.Map.Components
[NetworkedComponent]
public sealed class MapGridComponent : Component
{
[Dependency] private readonly IEntityManager _entMan = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IMapManagerInternal _mapManager = default!;
[Dependency] private readonly INetManager _netManager = default!;
[Dependency] private readonly IEntityManager _entManager = default!;
private SharedMapSystem MapSystem => _entManager.System<SharedMapSystem>();
// This field is used for deserialization internally in the map loader.
// If you want to remove this, you would have to restructure the map save file.
@@ -40,6 +31,9 @@ namespace Robust.Shared.Map.Components
[DataField("chunkSize")] internal ushort ChunkSize = 16;
[ViewVariables]
public int ChunkCount => Chunks.Count;
/// <summary>
/// The length of the side of a square tile in world units.
/// </summary>
@@ -70,7 +64,7 @@ namespace Robust.Shared.Map.Components
internal readonly Dictionary<Vector2i, MapChunk> Chunks = new();
[ViewVariables]
public Box2 LocalAABB { get; private set; }
public Box2 LocalAABB { get; internal set; }
/// <summary>
/// Set to enable or disable grid splitting.
@@ -79,849 +73,221 @@ namespace Robust.Shared.Map.Components
[ViewVariables(VVAccess.ReadWrite), DataField("canSplit")]
public bool CanSplit = true;
internal void RemoveChunk(Vector2i origin)
{
if (!Chunks.TryGetValue(origin, out var chunk))
return;
if (_netManager.IsServer)
ChunkDeletionHistory.Add((_timing.CurTick, chunk.Indices));
chunk.Fixtures.Clear();
Chunks.Remove(origin);
if (Chunks.Count == 0)
_entMan.EventBus.RaiseLocalEvent(Owner, new EmptyGridEvent { GridId = Owner }, true);
}
/// <summary>
/// Regenerate collision for multiple chunks at once; faster than doing it individually.
/// </summary>
internal void RegenerateCollision(IReadOnlySet<MapChunk> chunks)
{
if (_entMan.HasComponent<MapComponent>(Owner))
return;
var chunkRectangles = new Dictionary<MapChunk, List<Box2i>>(chunks.Count);
var removedChunks = new List<MapChunk>();
var fixtureSystem = _entMan.EntitySysManager.GetEntitySystem<FixtureSystem>();
_entMan.EntitySysManager.TryGetEntitySystem(out SharedGridFixtureSystem? system);
foreach (var mapChunk in chunks)
{
// Even if the chunk is still removed still need to make sure bounds are updated (for now...)
// generate collision rectangles for this chunk based on filled tiles.
GridChunkPartition.PartitionChunk(mapChunk, out var localBounds, out var rectangles);
mapChunk.CachedBounds = localBounds;
if (mapChunk.FilledTiles > 0)
chunkRectangles.Add(mapChunk, rectangles);
else
{
// Gone. Reduced to atoms
// Need to do this before RemoveChunk because it clears fixtures.
FixturesComponent? manager = null;
PhysicsComponent? body = null;
TransformComponent? xform = null;
foreach (var fixture in mapChunk.Fixtures)
{
fixtureSystem.DestroyFixture(Owner, fixture, false, manager: manager, body: body, xform: xform);
}
RemoveChunk(mapChunk.Indices);
removedChunks.Add(mapChunk);
}
}
LocalAABB = new Box2();
foreach (var chunk in Chunks.Values)
{
var chunkBounds = chunk.CachedBounds;
if (chunkBounds.Size.Equals(Vector2i.Zero))
continue;
if (LocalAABB.Size == Vector2.Zero)
{
var gridBounds = chunkBounds.Translated(chunk.Indices * chunk.ChunkSize);
LocalAABB = gridBounds;
}
else
{
var gridBounds = chunkBounds.Translated(chunk.Indices * chunk.ChunkSize);
LocalAABB = LocalAABB.Union(gridBounds);
}
}
// May have been deleted from the bulk update above!
if (_entMan.Deleted(Owner))
return;
// TODO: Move this to the component when we combine.
_entMan.EntitySysManager.GetEntitySystem<SharedPhysicsSystem>().WakeBody(Owner);
_entMan.EntitySysManager.GetEntitySystem<SharedMapSystem>().OnGridBoundsChange(Owner, this);
system?.RegenerateCollision(Owner, chunkRectangles, removedChunks);
}
/// <summary>
/// Regenerates the chunk local bounds of this chunk.
/// </summary>
internal void RegenerateCollision(MapChunk mapChunk)
{
RegenerateCollision(new HashSet<MapChunk> { mapChunk });
}
#region TileAccess
/// <inheritdoc />
public TileRef GetTileRef(MapCoordinates coords)
{
return GetTileRef(CoordinatesToTile(coords));
return MapSystem.GetTileRef(Owner, this, coords);
}
/// <inheritdoc />
public TileRef GetTileRef(EntityCoordinates coords)
{
return GetTileRef(CoordinatesToTile(coords));
return MapSystem.GetTileRef(Owner, this, coords);
}
/// <inheritdoc />
public TileRef GetTileRef(Vector2i tileCoordinates)
{
var chunkIndices = GridTileToChunkIndices(tileCoordinates);
if (!Chunks.TryGetValue(chunkIndices, out var output))
{
// Chunk doesn't exist, return a tileRef to an empty (space) tile.
return new TileRef(Owner, tileCoordinates.X, tileCoordinates.Y, default);
}
var chunkTileIndices = output.GridTileToChunkTile(tileCoordinates);
return GetTileRef(output, (ushort)chunkTileIndices.X, (ushort)chunkTileIndices.Y);
return MapSystem.GetTileRef(Owner, this, tileCoordinates);
}
/// <summary>
/// Returns the tile at the given chunk indices.
/// </summary>
/// <param name="mapChunk"></param>
/// <param name="xIndex">The X tile index relative to the chunk origin.</param>
/// <param name="yIndex">The Y tile index relative to the chunk origin.</param>
/// <returns>A reference to a tile.</returns>
internal TileRef GetTileRef(MapChunk mapChunk, ushort xIndex, ushort yIndex)
{
if (xIndex >= mapChunk.ChunkSize)
throw new ArgumentOutOfRangeException(nameof(xIndex), "Tile indices out of bounds.");
if (yIndex >= mapChunk.ChunkSize)
throw new ArgumentOutOfRangeException(nameof(yIndex), "Tile indices out of bounds.");
var indices = mapChunk.ChunkTileToGridTile(new Vector2i(xIndex, yIndex));
return new TileRef(Owner, indices, mapChunk.GetTile(xIndex, yIndex));
}
/// <inheritdoc />
public IEnumerable<TileRef> GetAllTiles(bool ignoreEmpty = true)
{
foreach (var kvChunk in Chunks)
{
var chunk = kvChunk.Value;
for (ushort x = 0; x < ChunkSize; x++)
{
for (ushort y = 0; y < ChunkSize; y++)
{
var tile = chunk.GetTile(x, y);
if (ignoreEmpty && tile.IsEmpty)
continue;
var (gridX, gridY) = new Vector2i(x, y) + chunk.Indices * ChunkSize;
yield return new TileRef(Owner, gridX, gridY, tile);
}
}
}
return MapSystem.GetAllTiles(Owner, this, ignoreEmpty);
}
/// <inheritdoc />
public GridTileEnumerator GetAllTilesEnumerator(bool ignoreEmpty = true)
{
return new GridTileEnumerator(Owner, Chunks.GetEnumerator(), ChunkSize, ignoreEmpty);
return MapSystem.GetAllTilesEnumerator(Owner, this, ignoreEmpty);
}
/// <inheritdoc />
public void SetTile(EntityCoordinates coords, Tile tile)
{
var localTile = CoordinatesToTile(coords);
SetTile(new Vector2i(localTile.X, localTile.Y), tile);
MapSystem.SetTile(Owner, this, coords, tile);
}
/// <inheritdoc />
public void SetTile(Vector2i gridIndices, Tile tile)
{
var (chunk, chunkTile) = ChunkAndOffsetForTile(gridIndices);
chunk.SetTile((ushort)chunkTile.X, (ushort)chunkTile.Y, tile);
// Ideally we'd to this here for consistency but apparently tile modified does it or something.
// Yeah it's noodly.
// RegenerateCollision(chunk);
MapSystem.SetTile(Owner, this, gridIndices, tile);
}
/// <inheritdoc />
public void SetTiles(List<(Vector2i GridIndices, Tile Tile)> tiles)
{
if (tiles.Count == 0) return;
var chunks = new HashSet<MapChunk>(Math.Max(1, tiles.Count / ChunkSize));
foreach (var (gridIndices, tile) in tiles)
{
var (chunk, chunkTile) = ChunkAndOffsetForTile(gridIndices);
chunks.Add(chunk);
chunk.SuppressCollisionRegeneration = true;
chunk.SetTile((ushort)chunkTile.X, (ushort)chunkTile.Y, tile);
}
foreach (var chunk in chunks)
{
chunk.SuppressCollisionRegeneration = false;
}
RegenerateCollision(chunks);
MapSystem.SetTiles(Owner, this, tiles);
}
public IEnumerable<TileRef> GetLocalTilesIntersecting(Box2Rotated localArea, bool ignoreEmpty = true,
Predicate<TileRef>? predicate = null)
{
var localAABB = localArea.CalcBoundingBox();
return GetLocalTilesIntersecting(localAABB, ignoreEmpty, predicate);
return MapSystem.GetLocalTilesIntersecting(Owner, this, localArea, ignoreEmpty, predicate);
}
/// <inheritdoc />
public IEnumerable<TileRef> GetTilesIntersecting(Box2Rotated worldArea, bool ignoreEmpty = true,
Predicate<TileRef>? predicate = null)
{
var matrix = _entMan.GetComponent<TransformComponent>(Owner).InvWorldMatrix;
var localArea = matrix.TransformBox(worldArea);
foreach (var tile in GetLocalTilesIntersecting(localArea, ignoreEmpty, predicate))
{
yield return tile;
}
return MapSystem.GetTilesIntersecting(Owner, this, worldArea, ignoreEmpty, predicate);
}
/// <inheritdoc />
public IEnumerable<TileRef> GetTilesIntersecting(Box2 worldArea, bool ignoreEmpty = true,
Predicate<TileRef>? predicate = null)
{
var matrix = _entMan.GetComponent<TransformComponent>(Owner).InvWorldMatrix;
var localArea = matrix.TransformBox(worldArea);
foreach (var tile in GetLocalTilesIntersecting(localArea, ignoreEmpty, predicate))
{
yield return tile;
}
return MapSystem.GetTilesIntersecting(Owner, this, worldArea, ignoreEmpty, predicate);
}
public IEnumerable<TileRef> GetLocalTilesIntersecting(Box2 localArea, bool ignoreEmpty = true,
Predicate<TileRef>? predicate = null)
{
// TODO: Should move the intersecting calls onto mapmanager system and then allow people to pass in xform / xformquery
// that way we can avoid the GetComp here.
var gridTileLb = new Vector2i((int)Math.Floor(localArea.Left), (int)Math.Floor(localArea.Bottom));
// If we have 20.1 we want to include that tile but if we have 20 then we don't.
var gridTileRt = new Vector2i((int)Math.Ceiling(localArea.Right), (int)Math.Ceiling(localArea.Top));
for (var x = gridTileLb.X; x < gridTileRt.X; x++)
{
for (var y = gridTileLb.Y; y < gridTileRt.Y; y++)
{
var gridChunk = GridTileToChunkIndices(new Vector2i(x, y));
if (Chunks.TryGetValue(gridChunk, out var chunk))
{
var chunkTile = chunk.GridTileToChunkTile(new Vector2i(x, y));
var tile = GetTileRef(chunk, (ushort)chunkTile.X, (ushort)chunkTile.Y);
if (ignoreEmpty && tile.Tile.IsEmpty)
continue;
if (predicate == null || predicate(tile))
yield return tile;
}
else if (!ignoreEmpty)
{
var tile = new TileRef(Owner, x, y, new Tile());
if (predicate == null || predicate(tile))
yield return tile;
}
}
}
return MapSystem.GetLocalTilesIntersecting(Owner, this, localArea, ignoreEmpty, predicate);
}
/// <inheritdoc />
public IEnumerable<TileRef> GetTilesIntersecting(Circle worldArea, bool ignoreEmpty = true,
Predicate<TileRef>? predicate = null)
{
var aabb = new Box2(worldArea.Position.X - worldArea.Radius, worldArea.Position.Y - worldArea.Radius,
worldArea.Position.X + worldArea.Radius, worldArea.Position.Y + worldArea.Radius);
var circleGridPos = new EntityCoordinates(Owner, WorldToLocal(worldArea.Position));
foreach (var tile in GetTilesIntersecting(aabb, ignoreEmpty, predicate))
{
var local = GridTileToLocal(tile.GridIndices);
if (!local.TryDistance(_entMan, circleGridPos, out var distance))
{
continue;
}
if (distance <= worldArea.Radius)
{
yield return tile;
}
}
}
private bool TryGetTile(Vector2i indices, bool ignoreEmpty, [NotNullWhen(true)] out TileRef? tileRef, Predicate<TileRef>? predicate = null)
{
// Similar to TryGetTileRef but for the tiles intersecting iterators.
var gridChunk = GridTileToChunkIndices(indices);
if (Chunks.TryGetValue(gridChunk, out var chunk))
{
var chunkTile = chunk.GridTileToChunkTile(indices);
var tile = GetTileRef(chunk, (ushort)chunkTile.X, (ushort)chunkTile.Y);
if (ignoreEmpty && tile.Tile.IsEmpty)
{
tileRef = null;
return false;
}
if (predicate == null || predicate(tile))
{
tileRef = tile;
return true;
}
}
else if (!ignoreEmpty)
{
var tile = new TileRef(Owner, indices.X, indices.Y, Tile.Empty);
if (predicate == null || predicate(tile))
{
tileRef = tile;
return true;
}
}
tileRef = null;
return false;
return MapSystem.GetTilesIntersecting(Owner, this, worldArea, ignoreEmpty, predicate);
}
#endregion TileAccess
#region ChunkAccess
/// <summary>
/// The total number of allocated chunks in the grid.
/// </summary>
public int ChunkCount => Chunks.Count;
/// <inheritdoc />
internal MapChunk GetOrAddChunk(int xIndex, int yIndex)
{
return GetOrAddChunk(new Vector2i(xIndex, yIndex));
}
internal bool TryGetChunk(Vector2i chunkIndices, [NotNullWhen(true)] out MapChunk? chunk)
{
return Chunks.TryGetValue(chunkIndices, out chunk);
return MapSystem.TryGetChunk(Owner, this, chunkIndices, out chunk);
}
/// <inheritdoc />
internal MapChunk GetOrAddChunk(Vector2i chunkIndices)
{
if (Chunks.TryGetValue(chunkIndices, out var output))
return output;
var newChunk = new MapChunk(chunkIndices.X, chunkIndices.Y, ChunkSize);
newChunk.LastTileModifiedTick = _mapManager.GameTiming.CurTick;
if (Initialized)
newChunk.TileModified += OnTileModified;
return Chunks[chunkIndices] = newChunk;
}
/// <inheritdoc />
public bool HasChunk(Vector2i chunkIndices)
{
return Chunks.ContainsKey(chunkIndices);
}
/// <inheritdoc />
internal IReadOnlyDictionary<Vector2i, MapChunk> GetMapChunks()
{
return Chunks;
return MapSystem.GetMapChunks(Owner, this);
}
/// <inheritdoc />
internal ChunkEnumerator GetMapChunks(Box2 worldAABB)
{
var localAABB = _entMan.GetComponent<TransformComponent>(Owner).InvWorldMatrix
.TransformBox(worldAABB);
return new ChunkEnumerator(Chunks, localAABB, ChunkSize);
}
/// <inheritdoc />
internal ChunkEnumerator GetMapChunks(Box2Rotated worldArea)
{
var matrix = _entMan.GetComponent<TransformComponent>(Owner).InvWorldMatrix;
var localArea = matrix.TransformBox(worldArea);
return new ChunkEnumerator(Chunks, localArea, ChunkSize);
}
internal ChunkEnumerator GetLocalMapChunks(Box2 localAABB)
{
return new ChunkEnumerator(Chunks, localAABB, ChunkSize);
return MapSystem.GetMapChunks(Owner, this, worldArea);
}
#endregion ChunkAccess
#region SnapGridAccess
/// <inheritdoc />
public int AnchoredEntityCount(Vector2i pos)
{
var gridChunkPos = GridTileToChunkIndices(pos);
if (!Chunks.TryGetValue(gridChunkPos, out var chunk)) return 0;
var (x, y) = chunk.GridTileToChunkTile(pos);
return chunk.GetSnapGrid((ushort)x, (ushort)y)?.Count ?? 0; // ?
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetAnchoredEntities(MapCoordinates coords)
{
return GetAnchoredEntities(TileIndicesFor(coords));
return MapSystem.GetAnchoredEntities(Owner, this, coords);
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetAnchoredEntities(EntityCoordinates coords)
{
return GetAnchoredEntities(TileIndicesFor(coords));
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetAnchoredEntities(Vector2i pos)
{
// Because some content stuff checks neighboring tiles (which may not actually exist) we won't just
// create an entire chunk for it.
var gridChunkPos = GridTileToChunkIndices(pos);
if (!Chunks.TryGetValue(gridChunkPos, out var chunk)) return Enumerable.Empty<EntityUid>();
var chunkTile = chunk.GridTileToChunkTile(pos);
return chunk.GetSnapGridCell((ushort)chunkTile.X, (ushort)chunkTile.Y);
return MapSystem.GetAnchoredEntities(Owner, this, pos);
}
public AnchoredEntitiesEnumerator GetAnchoredEntitiesEnumerator(Vector2i pos)
{
var gridChunkPos = GridTileToChunkIndices(pos);
if (!Chunks.TryGetValue(gridChunkPos, out var chunk)) return AnchoredEntitiesEnumerator.Empty;
var chunkTile = chunk.GridTileToChunkTile(pos);
var snapgrid = chunk.GetSnapGrid((ushort)chunkTile.X, (ushort)chunkTile.Y);
return snapgrid == null
? AnchoredEntitiesEnumerator.Empty
: new AnchoredEntitiesEnumerator(snapgrid.GetEnumerator());
return MapSystem.GetAnchoredEntitiesEnumerator(Owner, this, pos);
}
public IEnumerable<EntityUid> GetLocalAnchoredEntities(Box2 localAABB)
{
foreach (var tile in GetLocalTilesIntersecting(localAABB, true, null))
{
foreach (var ent in GetAnchoredEntities(tile.GridIndices))
{
yield return ent;
}
}
return MapSystem.GetLocalAnchoredEntities(Owner, this, localAABB);
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetAnchoredEntities(Box2 worldAABB)
{
foreach (var tile in GetTilesIntersecting(worldAABB))
{
foreach (var ent in GetAnchoredEntities(tile.GridIndices))
{
yield return ent;
}
}
return MapSystem.GetAnchoredEntities(Owner, this, worldAABB);
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetAnchoredEntities(Box2Rotated worldBounds)
{
foreach (var tile in GetTilesIntersecting(worldBounds))
{
foreach (var ent in GetAnchoredEntities(tile.GridIndices))
{
yield return ent;
}
}
}
/// <inheritdoc />
public Vector2i TileIndicesFor(EntityCoordinates coords)
{
#if DEBUG
var mapId = _entMan.GetComponent<TransformComponent>(Owner).MapID;
DebugTools.Assert(mapId == coords.GetMapId(_entMan));
#endif
return SnapGridLocalCellFor(LocalToGrid(coords));
return MapSystem.TileIndicesFor(Owner, this, coords);
}
/// <inheritdoc />
public Vector2i TileIndicesFor(MapCoordinates worldPos)
{
#if DEBUG
var mapId = _entMan.GetComponent<TransformComponent>(Owner).MapID;
DebugTools.Assert(mapId == worldPos.MapId);
#endif
var localPos = WorldToLocal(worldPos.Position);
return SnapGridLocalCellFor(localPos);
return MapSystem.TileIndicesFor(Owner, this, worldPos);
}
private Vector2i SnapGridLocalCellFor(Vector2 localPos)
{
var x = (int)Math.Floor(localPos.X / TileSize);
var y = (int)Math.Floor(localPos.Y / TileSize);
return new Vector2i(x, y);
}
public bool IsAnchored(EntityCoordinates coords, EntityUid euid)
{
var tilePos = TileIndicesFor(coords);
var (chunk, chunkTile) = ChunkAndOffsetForTile(tilePos);
var snapgrid = chunk.GetSnapGrid((ushort)chunkTile.X, (ushort)chunkTile.Y);
return snapgrid?.Contains(euid) == true;
}
/// <inheritdoc />
public bool AddToSnapGridCell(Vector2i pos, EntityUid euid)
{
var (chunk, chunkTile) = ChunkAndOffsetForTile(pos);
if (chunk.GetTile((ushort)chunkTile.X, (ushort)chunkTile.Y).IsEmpty)
return false;
chunk.AddToSnapGridCell((ushort)chunkTile.X, (ushort)chunkTile.Y, euid);
return true;
}
/// <inheritdoc />
public bool AddToSnapGridCell(EntityCoordinates coords, EntityUid euid)
{
return AddToSnapGridCell(TileIndicesFor(coords), euid);
}
/// <inheritdoc />
public void RemoveFromSnapGridCell(Vector2i pos, EntityUid euid)
{
var (chunk, chunkTile) = ChunkAndOffsetForTile(pos);
chunk.RemoveFromSnapGridCell((ushort)chunkTile.X, (ushort)chunkTile.Y, euid);
}
/// <inheritdoc />
public void RemoveFromSnapGridCell(EntityCoordinates coords, EntityUid euid)
{
RemoveFromSnapGridCell(TileIndicesFor(coords), euid);
}
private (MapChunk, Vector2i) ChunkAndOffsetForTile(Vector2i pos)
{
var gridChunkIndices = GridTileToChunkIndices(pos);
var chunk = GetOrAddChunk(gridChunkIndices);
var chunkTile = chunk.GridTileToChunkTile(pos);
return (chunk, chunkTile);
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetInDir(EntityCoordinates position, Direction dir)
{
var pos = SharedMapSystem.GetDirection(TileIndicesFor(position), dir);
return GetAnchoredEntities(pos);
return MapSystem.GetInDir(Owner, this, position, dir);
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetOffset(EntityCoordinates coords, Vector2i offset)
{
var pos = TileIndicesFor(coords) + offset;
return GetAnchoredEntities(pos);
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetLocal(EntityCoordinates coords)
{
return GetAnchoredEntities(TileIndicesFor(coords));
return MapSystem.GetLocal(Owner, this, coords);
}
/// <inheritdoc />
public EntityCoordinates DirectionToGrid(EntityCoordinates coords, Direction direction)
{
return GridTileToLocal(SharedMapSystem.GetDirection(TileIndicesFor(coords), direction));
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetCardinalNeighborCells(EntityCoordinates coords)
{
var position = TileIndicesFor(coords);
foreach (var cell in GetAnchoredEntities(position))
yield return cell;
foreach (var cell in GetAnchoredEntities(position + new Vector2i(0, 1)))
yield return cell;
foreach (var cell in GetAnchoredEntities(position + new Vector2i(0, -1)))
yield return cell;
foreach (var cell in GetAnchoredEntities(position + new Vector2i(1, 0)))
yield return cell;
foreach (var cell in GetAnchoredEntities(position + new Vector2i(-1, 0)))
yield return cell;
return MapSystem.GetCardinalNeighborCells(Owner, this, coords);
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetCellsInSquareArea(EntityCoordinates coords, int n)
{
var position = TileIndicesFor(coords);
for (var y = -n; y <= n; ++y)
for (var x = -n; x <= n; ++x)
{
var enumerator = GetAnchoredEntitiesEnumerator(position + new Vector2i(x, y));
while (enumerator.MoveNext(out var cell))
{
yield return cell.Value;
}
}
return MapSystem.GetCellsInSquareArea(Owner, this, coords, n);
}
#endregion
#region Transforms
/// <inheritdoc />
public Vector2 WorldToLocal(Vector2 posWorld)
{
var matrix = _entMan.GetComponent<TransformComponent>(Owner).InvWorldMatrix;
return matrix.Transform(posWorld);
return MapSystem.WorldToLocal(Owner, this, posWorld);
}
/// <inheritdoc />
public EntityCoordinates MapToGrid(MapCoordinates posWorld)
{
var mapId = _entMan.GetComponent<TransformComponent>(Owner).MapID;
if (posWorld.MapId != mapId)
throw new ArgumentException(
$"Grid {Owner} is on map {mapId}, but coords are on map {posWorld.MapId}.",
nameof(posWorld));
if (!_mapManager.TryGetGrid(Owner, out var grid))
{
return new EntityCoordinates(_mapManager.GetMapEntityId(posWorld.MapId), new Vector2(posWorld.X, posWorld.Y));
}
return new EntityCoordinates(((Component) grid).Owner, WorldToLocal(posWorld.Position));
return MapSystem.MapToGrid(Owner, posWorld);
}
/// <inheritdoc />
public Vector2 LocalToWorld(Vector2 posLocal)
{
var matrix = _entMan.GetComponent<TransformComponent>(Owner).WorldMatrix;
return matrix.Transform(posLocal);
return MapSystem.LocalToWorld(Owner, this, posLocal);
}
public Vector2i WorldToTile(Vector2 posWorld)
{
var local = WorldToLocal(posWorld);
var x = (int)Math.Floor(local.X / TileSize);
var y = (int)Math.Floor(local.Y / TileSize);
return new Vector2i(x, y);
return MapSystem.WorldToTile(Owner, this, posWorld);
}
public Vector2i LocalToTile(EntityCoordinates coordinates)
{
var position = LocalToGrid(coordinates);
return new Vector2i((int) Math.Floor(position.X / TileSize), (int) Math.Floor(position.Y / TileSize));
return MapSystem.LocalToTile(Owner, this, coordinates);
}
/// <inheritdoc />
public Vector2i CoordinatesToTile(MapCoordinates coords)
{
#if DEBUG
var mapId = _entMan.GetComponent<TransformComponent>(Owner).MapID;
DebugTools.Assert(mapId == coords.MapId);
#endif
var local = WorldToLocal(coords.Position);
var x = (int)Math.Floor(local.X / TileSize);
var y = (int)Math.Floor(local.Y / TileSize);
return new Vector2i(x, y);
}
/// <inheritdoc />
public Vector2i CoordinatesToTile(EntityCoordinates coords)
{
#if DEBUG
var mapId = _entMan.GetComponent<TransformComponent>(Owner).MapID;
DebugTools.Assert(mapId == coords.GetMapId(_entMan));
#endif
var local = LocalToGrid(coords);
var x = (int)Math.Floor(local.X / TileSize);
var y = (int)Math.Floor(local.Y / TileSize);
return new Vector2i(x, y);
}
/// <inheritdoc />
public Vector2i LocalToChunkIndices(EntityCoordinates gridPos)
{
var local = LocalToGrid(gridPos);
var x = (int)Math.Floor(local.X / (TileSize * ChunkSize));
var y = (int)Math.Floor(local.Y / (TileSize * ChunkSize));
return new Vector2i(x, y);
}
public Vector2 LocalToGrid(EntityCoordinates position)
{
return position.EntityId == Owner
? position.Position
: WorldToLocal(position.ToMapPos(_entMan));
return MapSystem.CoordinatesToTile(Owner, this, coords);
}
public bool CollidesWithGrid(Vector2i indices)
{
var chunkIndices = GridTileToChunkIndices(indices);
if (!Chunks.TryGetValue(chunkIndices, out var chunk))
return false;
var cTileIndices = chunk.GridTileToChunkTile(indices);
return chunk.GetTile((ushort)cTileIndices.X, (ushort)cTileIndices.Y).TypeId != Tile.Empty.TypeId;
return MapSystem.CollidesWithGrid(Owner, this, indices);
}
/// <inheritdoc />
public Vector2i GridTileToChunkIndices(Vector2i gridTile)
{
var x = (int)Math.Floor(gridTile.X / (float)ChunkSize);
var y = (int)Math.Floor(gridTile.Y / (float)ChunkSize);
return new Vector2i(x, y);
return MapSystem.GridTileToChunkIndices(Owner, this, gridTile);
}
/// <inheritdoc />
public EntityCoordinates GridTileToLocal(Vector2i gridTile)
{
return new(Owner,
new Vector2(gridTile.X * TileSize + (TileSize / 2f), gridTile.Y * TileSize + (TileSize / 2f)));
return MapSystem.GridTileToLocal(Owner, this, gridTile);
}
public Vector2 GridTileToWorldPos(Vector2i gridTile)
{
var locX = gridTile.X * TileSize + (TileSize / 2f);
var locY = gridTile.Y * TileSize + (TileSize / 2f);
var xform = _entMan.GetComponent<TransformComponent>(Owner);
return xform.WorldMatrix.Transform(new Vector2(locX, locY));
return MapSystem.GridTileToWorldPos(Owner, this, gridTile);
}
public MapCoordinates GridTileToWorld(Vector2i gridTile)
{
var parentMapId = _entMan.GetComponent<TransformComponent>(Owner).MapID;
return new(GridTileToWorldPos(gridTile), parentMapId);
return MapSystem.GridTileToWorld(Owner, this, gridTile);
}
/// <inheritdoc />
public bool TryGetTileRef(Vector2i indices, out TileRef tile)
{
var chunkIndices = GridTileToChunkIndices(indices);
if (!Chunks.TryGetValue(chunkIndices, out var chunk))
{
tile = default;
return false;
}
var cTileIndices = chunk.GridTileToChunkTile(indices);
tile = GetTileRef(chunk, (ushort)cTileIndices.X, (ushort)cTileIndices.Y);
return true;
return MapSystem.TryGetTileRef(Owner, this, indices, out tile);
}
/// <inheritdoc />
public bool TryGetTileRef(EntityCoordinates coords, out TileRef tile)
{
return TryGetTileRef(CoordinatesToTile(coords), out tile);
}
/// <inheritdoc />
public bool TryGetTileRef(Vector2 worldPos, out TileRef tile)
{
return TryGetTileRef(WorldToTile(worldPos), out tile);
}
#endregion Transforms
/// <summary>
/// Calculate the world space AABB for this chunk.
/// </summary>
internal Box2 CalcWorldAABB(MapChunk mapChunk)
{
var (position, rotation) =
_entMan.GetComponent<TransformComponent>(Owner).GetWorldPositionRotation();
var chunkPosition = mapChunk.Indices;
var tileScale = TileSize;
var chunkScale = mapChunk.ChunkSize;
var worldPos = position + rotation.RotateVec(chunkPosition * tileScale * chunkScale);
return new Box2Rotated(
((Box2)mapChunk.CachedBounds
.Scale(tileScale))
.Translated(worldPos),
rotation, worldPos).CalcBoundingBox();
}
internal void OnTileModified(MapChunk mapChunk, Vector2i tileIndices, Tile newTile, Tile oldTile,
bool shapeChanged)
{
// As the collision regeneration can potentially delete the chunk we'll notify of the tile changed first.
var gridTile = mapChunk.ChunkTileToGridTile(tileIndices);
mapChunk.LastTileModifiedTick = _mapManager.GameTiming.CurTick;
LastTileModifiedTick = _mapManager.GameTiming.CurTick;
_entMan.Dirty(this);
// The map serializer currently sets tiles of unbound grids as part of the deserialization process
// It properly sets SuppressOnTileChanged so that the event isn't spammed for every tile on the grid.
// ParentMapId is not able to be accessed on unbound grids, so we can't even call this function for unbound grids.
if (!_mapManager.SuppressOnTileChanged)
{
var newTileRef = new TileRef(Owner, gridTile, newTile);
_mapManager.RaiseOnTileChanged(newTileRef, oldTile);
}
if (shapeChanged && !mapChunk.SuppressCollisionRegeneration)
{
RegenerateCollision(mapChunk);
}
return MapSystem.TryGetTileRef(Owner, this, coords, out tile);
}
}

View File

@@ -24,66 +24,67 @@ namespace Robust.Shared.Map
IoCManager.Resolve(ref entityManager, ref mapManager);
var gridId = coords.GetGridUid(entityManager);
var mapSystem = entityManager.System<SharedMapSystem>();
if (mapManager.TryGetGrid(gridId, out var mapGrid))
{
return mapGrid.GridTileToLocal(mapGrid.CoordinatesToTile(coords));
return mapSystem.GridTileToLocal(gridId.Value, mapGrid, mapSystem.CoordinatesToTile(gridId.Value, mapGrid, coords));
}
else
var mapCoords = coords.ToMap(entityManager);
if (mapManager.TryFindGridAt(mapCoords, out var gridUid, out mapGrid))
{
var mapCoords = coords.ToMap(entityManager);
if (mapManager.TryFindGridAt(mapCoords, out _, out mapGrid))
{
return mapGrid.GridTileToLocal(mapGrid.CoordinatesToTile(coords));
}
// create a box around the cursor
var gridSearchBox = Box2.UnitCentered.Scale(searchBoxSize).Translated(mapCoords.Position);
// find grids in search box
var gridsInArea = mapManager.FindGridsIntersecting(mapCoords.MapId, gridSearchBox);
// find closest grid intersecting our search box.
MapGridComponent? closest = null;
var distance = float.PositiveInfinity;
var intersect = new Box2();
var xformQuery = entityManager.GetEntityQuery<TransformComponent>();
foreach (var grid in gridsInArea)
{
var gridXform = xformQuery.GetComponent(grid.Owner);
// TODO: Use CollisionManager to get nearest edge.
// figure out closest intersect
var gridIntersect = gridSearchBox.Intersect(gridXform.WorldMatrix.TransformBox(grid.LocalAABB));
var gridDist = (gridIntersect.Center - mapCoords.Position).LengthSquared();
if (gridDist >= distance)
continue;
distance = gridDist;
closest = grid;
intersect = gridIntersect;
}
if (closest != null) // stick to existing grid
{
// round to nearest cardinal dir
var normal = mapCoords.Position - intersect.Center;
// round coords to center of tile
var tileIndices = closest.WorldToTile(intersect.Center);
var tileCenterWorld = closest.GridTileToWorldPos(tileIndices);
// move mouse one tile out along normal
var newTilePos = tileCenterWorld + normal * closest.TileSize;
coords = new EntityCoordinates(closest.Owner, closest.WorldToLocal(newTilePos));
}
//else free place
return mapSystem.GridTileToLocal(gridUid, mapGrid, mapSystem.CoordinatesToTile(gridUid, mapGrid, coords));
}
// create a box around the cursor
var gridSearchBox = Box2.UnitCentered.Scale(searchBoxSize).Translated(mapCoords.Position);
// find grids in search box
var gridsInArea = mapManager.FindGridsIntersecting(mapCoords.MapId, gridSearchBox);
// find closest grid intersecting our search box.
gridUid = EntityUid.Invalid;
MapGridComponent? closest = null;
var distance = float.PositiveInfinity;
var intersect = new Box2();
var xformQuery = entityManager.GetEntityQuery<TransformComponent>();
foreach (var grid in gridsInArea)
{
var gridXform = xformQuery.GetComponent(grid.Owner);
// TODO: Use CollisionManager to get nearest edge.
// figure out closest intersect
var gridIntersect = gridSearchBox.Intersect(gridXform.WorldMatrix.TransformBox(grid.LocalAABB));
var gridDist = (gridIntersect.Center - mapCoords.Position).LengthSquared();
if (gridDist >= distance)
continue;
gridUid = grid.Owner;
distance = gridDist;
closest = grid;
intersect = gridIntersect;
}
if (closest != null) // stick to existing grid
{
// round to nearest cardinal dir
var normal = mapCoords.Position - intersect.Center;
// round coords to center of tile
var tileIndices = mapSystem.WorldToTile(gridUid, closest, intersect.Center);
var tileCenterWorld = mapSystem.GridTileToWorldPos(gridUid, closest, tileIndices);
// move mouse one tile out along normal
var newTilePos = tileCenterWorld + normal * closest.TileSize;
coords = new EntityCoordinates(gridUid, mapSystem.WorldToLocal(gridUid, closest, newTilePos));
}
//else free place
return coords;
}
}

View File

@@ -191,10 +191,12 @@ namespace Robust.Shared.Map
if(!IsValid(entityManager))
return new Vector2i();
var mapSystem = entityManager.System<SharedMapSystem>();
var gridIdOpt = GetGridUid(entityManager);
if (gridIdOpt is { } gridId && gridId.IsValid())
{
return mapManager.GetGrid(gridId).GetTileRef(this).GridIndices;
var grid = mapManager.GetGrid(gridId);
return mapSystem.GetTileRef(gridId, grid, this).GridIndices;
}
var vec = ToMapPos(entityManager, transformSystem);

View File

@@ -0,0 +1,21 @@
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
namespace Robust.Shared.Map.Events;
/// <summary>
/// Raised directed on a grid to get its bounds.
/// </summary>
/// <remarks>
/// Really this exists to get around test dependency creeping.
/// </remarks>
[ByRefEvent]
internal readonly record struct RegenerateGridBoundsEvent(EntityUid Entity, Dictionary<MapChunk, List<Box2i>> ChunkRectangles, List<MapChunk> RemovedChunks)
{
public readonly EntityUid Entity = Entity;
public readonly Dictionary<MapChunk, List<Box2i>> ChunkRectangles = ChunkRectangles;
public readonly List<MapChunk> RemovedChunks = RemovedChunks;
}

View File

@@ -22,15 +22,9 @@ namespace Robust.Shared.Map
private readonly Vector2i _gridIndices;
[ViewVariables]
private readonly Tile[,] _tiles;
[ViewVariables] internal readonly Tile[,] Tiles;
private readonly SnapGridCell[,] _snapGrid;
/// <summary>
/// Invoked when a tile is modified on this chunk.
/// </summary>
public event TileModifiedDelegate? TileModified;
/// <summary>
/// Keeps a running count of the number of filled tiles in this chunk.
/// </summary>
@@ -38,7 +32,7 @@ namespace Robust.Shared.Map
/// This will always be between 1 and <see cref="ChunkSize"/>^2.
/// </remarks>
[ViewVariables]
internal int FilledTiles { get; private set; }
internal int FilledTiles { get; set; }
/// <summary>
/// Chunk-local AABB of this chunk.
@@ -73,7 +67,7 @@ namespace Robust.Shared.Map
_gridIndices = new Vector2i(x, y);
ChunkSize = chunkSize;
_tiles = new Tile[ChunkSize, ChunkSize];
Tiles = new Tile[ChunkSize, ChunkSize];
_snapGrid = new SnapGridCell[ChunkSize, ChunkSize];
}
@@ -113,58 +107,12 @@ namespace Robust.Shared.Map
if (yIndex >= ChunkSize)
throw new ArgumentOutOfRangeException(nameof(yIndex), "Tile indices out of bounds.");
return _tiles[xIndex, yIndex];
return Tiles[xIndex, yIndex];
}
public Tile GetTile(Vector2i indices)
{
return _tiles[indices.X, indices.Y];
}
/// <summary>
/// Replaces a single tile inside of the chunk.
/// </summary>
/// <param name="xIndex">The X tile index relative to the chunk.</param>
/// <param name="yIndex">The Y tile index relative to the chunk.</param>
/// <param name="tile">The new tile to insert.</param>
public void SetTile(ushort xIndex, ushort yIndex, Tile tile)
{
if (xIndex >= ChunkSize)
throw new ArgumentOutOfRangeException(nameof(xIndex), "Tile indices out of bounds.");
if (yIndex >= ChunkSize)
throw new ArgumentOutOfRangeException(nameof(yIndex), "Tile indices out of bounds.");
// same tile, no point to continue
if (_tiles[xIndex, yIndex] == tile)
return;
var oldTile = _tiles[xIndex, yIndex];
var oldFilledTiles = FilledTiles;
if (oldTile.IsEmpty != tile.IsEmpty)
{
if (oldTile.IsEmpty)
{
FilledTiles += 1;
}
else
{
FilledTiles -= 1;
}
}
var shapeChanged = oldFilledTiles != FilledTiles;
DebugTools.Assert(FilledTiles >= 0);
_tiles[xIndex, yIndex] = tile;
var tileIndices = new Vector2i(xIndex, yIndex);
// God I hate C# events sometimes.
DebugTools.Assert(TileModified == null || TileModified.GetInvocationList().Length <= 1);
TileModified?.Invoke(this, tileIndices, tile, oldTile, shapeChanged);
return Tiles[indices.X, indices.Y];
}
/// <summary>
@@ -266,6 +214,48 @@ namespace Robust.Shared.Map
{
public List<EntityUid>? Center;
}
/// <summary>
/// Sets the tile without any callbacks.
/// Do not call this unless you know what you are doing.
/// </summary>
internal bool TrySetTile(ushort xIndex, ushort yIndex, Tile tile, out Tile oldTile, out bool shapeChanged)
{
if (xIndex >= Tiles.Length)
throw new ArgumentOutOfRangeException(nameof(xIndex), "Tile indices out of bounds.");
if (yIndex >= Tiles.Length)
throw new ArgumentOutOfRangeException(nameof(yIndex), "Tile indices out of bounds.");
// same tile, no point to continue
if (Tiles[xIndex, yIndex] == tile)
{
oldTile = Tile.Empty;
shapeChanged = false;
return false;
}
oldTile = Tiles[xIndex, yIndex];
var oldFilledTiles = FilledTiles;
if (oldTile.IsEmpty != tile.IsEmpty)
{
if (oldTile.IsEmpty)
{
FilledTiles += 1;
}
else
{
FilledTiles -= 1;
}
}
shapeChanged = oldFilledTiles != FilledTiles;
DebugTools.Assert(FilledTiles >= 0);
Tiles[xIndex, yIndex] = tile;
return true;
}
}
/// <summary>
@@ -276,5 +266,5 @@ namespace Robust.Shared.Map
/// <param name="newTile">New version of the tile.</param>
/// <param name="oldTile">Old version of the tile.</param>
/// <param name="chunkShapeChanged">If changing this tile changed the shape of the chunk.</param>
internal delegate void TileModifiedDelegate(MapChunk mapChunk, Vector2i tileIndices, Tile newTile, Tile oldTile, bool chunkShapeChanged);
internal delegate void TileModifiedDelegate(EntityUid uid, MapGridComponent grid, MapChunk mapChunk, Vector2i tileIndices, Tile newTile, Tile oldTile, bool chunkShapeChanged);
}

View File

@@ -1,13 +1,11 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
using Robust.Shared.GameObjects;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
namespace Robust.Shared.Map;
@@ -29,24 +27,20 @@ internal partial class MapManager
return;
}
var physicsQuery = EntityManager.GetEntityQuery<PhysicsComponent>();
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
var xformSystem = EntityManager.System<SharedTransformSystem>();
var state = (worldAABB, gridTree.Tree, callback, approx, physicsQuery, xformQuery, xformSystem);
var state = (worldAABB, gridTree.Tree, callback, approx, this, _transformSystem);
gridTree.Tree.Query(ref state,
static (ref (Box2 worldAABB,
B2DynamicTree<(EntityUid Uid, MapGridComponent Grid)> gridTree,
GridCallback callback,
bool approx,
EntityQuery<PhysicsComponent> physicsQuery, EntityQuery<TransformComponent> xformQuery,
MapManager mapManager,
SharedTransformSystem xformSystem) tuple,
DynamicTree.Proxy proxy) =>
{
var data = tuple.gridTree.GetUserData(proxy);
if (!tuple.approx && !IsIntersecting(tuple.worldAABB, data.Uid, data.Grid,
tuple.physicsQuery, tuple.xformQuery, tuple.xformSystem))
if (!tuple.approx && !tuple.mapManager.IsIntersecting(tuple.worldAABB, data.Uid, data.Grid))
{
return true;
}
@@ -77,10 +71,7 @@ internal partial class MapManager
callback(mapUid, grid, ref state);
}
var physicsQuery = EntityManager.GetEntityQuery<PhysicsComponent>();
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
var xformSystem = EntityManager.System<SharedTransformSystem>();
var state2 = (state, worldAABB, gridTree.Tree, callback, approx, physicsQuery, xformQuery, xformSystem);
var state2 = (state, worldAABB, gridTree.Tree, callback, approx, this, _transformSystem);
gridTree.Tree.Query(ref state2, static (ref (
TState state,
@@ -88,15 +79,13 @@ internal partial class MapManager
B2DynamicTree<(EntityUid Uid, MapGridComponent Grid)> gridTree,
GridCallback<TState> callback,
bool approx,
EntityQuery<PhysicsComponent> physicsQuery,
EntityQuery<TransformComponent> xformQuery,
MapManager mapManager,
SharedTransformSystem xformSystem) tuple,
DynamicTree.Proxy proxy) =>
{
var data = tuple.gridTree.GetUserData(proxy);
if (!tuple.approx && !IsIntersecting(tuple.worldAABB, data.Uid, data.Grid,
tuple.physicsQuery, tuple.xformQuery, tuple.xformSystem))
if (!tuple.approx && !tuple.mapManager.IsIntersecting(tuple.worldAABB, data.Uid, data.Grid))
{
return true;
}
@@ -119,22 +108,18 @@ internal partial class MapManager
FindGridsIntersecting(mapId, worldBounds.CalcBoundingBox(), ref state, callback, approx, includeMap);
}
private static bool IsIntersecting(
private bool IsIntersecting(
Box2 aabb,
EntityUid gridUid,
MapGridComponent grid,
EntityQuery<PhysicsComponent> physicsQuery,
EntityQuery<TransformComponent> xformQuery,
SharedTransformSystem xformSystem)
MapGridComponent grid)
{
var xformComp = xformQuery.GetComponent(gridUid);
var (worldPos, worldRot, matrix, invMatrix) = xformSystem.GetWorldPositionRotationMatrixWithInv(xformComp, xformQuery);
var (worldPos, worldRot, matrix, invMatrix) = _transformSystem.GetWorldPositionRotationMatrixWithInv(gridUid);
var overlap = matrix.TransformBox(grid.LocalAABB).Intersect(aabb);
var localAABB = invMatrix.TransformBox(overlap);
if (physicsQuery.HasComponent(gridUid))
if (_physicsQuery.HasComponent(gridUid))
{
var enumerator = grid.GetLocalMapChunks(localAABB);
var enumerator = _mapSystem.GetLocalMapChunks(gridUid, grid, localAABB);
var transform = new Transform(worldPos, worldRot);
@@ -186,14 +171,13 @@ internal partial class MapManager
uid = EntityUid.Invalid;
grid = null;
var xformSystem = EntityManager.System<SharedTransformSystem>();
var state = (uid, grid, worldPos, xformQuery, xformSystem);
var state = (uid, grid, worldPos, _mapSystem, _transformSystem);
FindGridsIntersecting(mapId, aabb, ref state, static (EntityUid iUid, MapGridComponent iGrid, ref (
EntityUid uid,
MapGridComponent? grid,
Vector2 worldPos,
EntityQuery<TransformComponent> xformQuery,
SharedMapSystem mapSystem,
SharedTransformSystem xformSystem) tuple) =>
{
// Turn the worldPos into a localPos and work out the relevant chunk we need to check
@@ -201,7 +185,7 @@ internal partial class MapManager
// (though now we need some extra calcs up front).
// Doesn't use WorldBounds because it's just an AABB.
var matrix = tuple.xformSystem.GetInvWorldMatrix(iUid, tuple.xformQuery);
var matrix = tuple.xformSystem.GetInvWorldMatrix(iUid);
var localPos = matrix.Transform(tuple.worldPos);
// NOTE:
@@ -209,9 +193,10 @@ internal partial class MapManager
// you account for the fact that fixtures are shrunk slightly!
var chunkIndices = SharedMapSystem.GetChunkIndices(localPos, iGrid.ChunkSize);
if (!iGrid.HasChunk(chunkIndices)) return true;
if (!tuple.mapSystem.HasChunk(iUid, iGrid, chunkIndices))
return true;
var chunk = iGrid.GetOrAddChunk(chunkIndices);
var chunk = tuple.mapSystem.GetOrAddChunk(iUid, iGrid, chunkIndices);
var chunkRelative = SharedMapSystem.GetChunkRelative(localPos, iGrid.ChunkSize);
var chunkTile = chunk.GetTile(chunkRelative);
@@ -241,8 +226,7 @@ internal partial class MapManager
/// </summary>
public bool TryFindGridAt(MapId mapId, Vector2 worldPos, out EntityUid uid, [NotNullWhen(true)] out MapGridComponent? grid)
{
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
return TryFindGridAt(mapId, worldPos, xformQuery, out uid, out grid);
return TryFindGridAt(mapId, worldPos, _xformQuery, out uid, out grid);
}
/// <summary>

View File

@@ -4,6 +4,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Components;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -20,9 +21,17 @@ internal partial class MapManager : IMapManagerInternal, IEntityEventSubscriber
private ISawmill _sawmill = default!;
private SharedMapSystem _mapSystem = default!;
private SharedTransformSystem _transformSystem = default!;
private EntityQuery<PhysicsComponent> _physicsQuery;
private EntityQuery<TransformComponent> _xformQuery;
/// <inheritdoc />
public void Initialize()
{
_physicsQuery = EntityManager.GetEntityQuery<PhysicsComponent>();
_xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
_sawmill = Logger.GetSawmill("map");
#if DEBUG
@@ -36,6 +45,9 @@ internal partial class MapManager : IMapManagerInternal, IEntityEventSubscriber
/// <inheritdoc />
public void Startup()
{
_transformSystem = EntityManager.System<SharedTransformSystem>();
_mapSystem = EntityManager.System<SharedMapSystem>();
#if DEBUG
DebugTools.Assert(_dbgGuardInit);
_dbgGuardRunning = true;
@@ -52,9 +64,12 @@ internal partial class MapManager : IMapManagerInternal, IEntityEventSubscriber
#endif
_sawmill.Debug("Stopping...");
foreach (var mapComp in EntityManager.EntityQuery<MapComponent>())
// TODO: AllEntityQuery instead???
var query = EntityManager.EntityQueryEnumerator<MapComponent>();
while (query.MoveNext(out var uid, out _))
{
EntityManager.DeleteEntity(mapComp.Owner);
EntityManager.DeleteEntity(uid);
}
}
@@ -65,9 +80,11 @@ internal partial class MapManager : IMapManagerInternal, IEntityEventSubscriber
// Don't just call Shutdown / Startup because we don't want to touch the subscriptions on gridtrees
// Restart can be called any time during a game, whereas shutdown / startup are typically called upon connection.
foreach (var mapComp in EntityManager.EntityQuery<MapComponent>())
var query = EntityManager.EntityQueryEnumerator<MapComponent>();
while (query.MoveNext(out var uid, out _))
{
EntityManager.DeleteEntity(mapComp.Owner);
EntityManager.DeleteEntity(uid);
}
}

View File

@@ -845,7 +845,13 @@ namespace Robust.Shared.Network
return true;
}
var channel = _channels[msg.SenderConnection];
if (!_channels.TryGetValue(msg.SenderConnection, out var channel))
{
_logger.Warning($"{msg.SenderConnection.RemoteEndPoint}: Got unexpected data packet before handshake completion.");
msg.SenderConnection.Disconnect("Unexpected packet before handshake completion");
return true;
}
var encryption = IsServer ? channel.Encryption : _clientEncryption;

View File

@@ -179,7 +179,7 @@ public abstract class Joint : IEquatable<Joint>
[DataField("breakpoint")]
private float _breakpoint = float.MaxValue;
private double _breakpointSquared = Double.MaxValue;
private double _breakpointSquared = double.MaxValue;
// TODO: Later nerd
// serializer.DataField(this, x => x.BodyA, "bodyA", EntityUid.Invalid);
@@ -191,10 +191,10 @@ public abstract class Joint : IEquatable<Joint>
IoCManager.Resolve(ref entMan);
if (entMan.TryGetComponent(BodyAUid, out PhysicsComponent? physics))
entMan.Dirty(physics);
entMan.Dirty(BodyAUid, physics);
if (entMan.TryGetComponent(BodyBUid, out physics))
entMan.Dirty(physics);
entMan.Dirty(BodyBUid, physics);
}
protected Joint() {}

View File

@@ -0,0 +1,19 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Physics.Components;
namespace Robust.Shared.Physics.Events
{
/// <summary>
/// These events are broadcast (not directed) whenever an entity's ability to collide changes.
/// </summary>
[ByRefEvent]
public readonly struct CollisionLayerChangeEvent
{
public readonly PhysicsComponent Body;
public CollisionLayerChangeEvent(PhysicsComponent body)
{
Body = body;
}
}
}

View File

@@ -9,6 +9,7 @@ using Robust.Shared.Log;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Physics.Events;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
@@ -355,6 +356,9 @@ namespace Robust.Shared.Physics.Systems
if (resetMass)
_physics.ResetMassData(uid, manager, body);
// Save the old layer to see if an event should be raised later.
var oldLayer = body.CollisionLayer;
// Normally this method is called when fixtures need to be dirtied anyway so no point in returning early I think
body.CollisionMask = mask;
body.CollisionLayer = layer;
@@ -363,6 +367,12 @@ namespace Robust.Shared.Physics.Systems
if (manager.FixtureCount == 0)
_physics.SetCanCollide(uid, false, manager: manager, body: body);
if (oldLayer != layer)
{
var ev = new CollisionLayerChangeEvent(body);
RaiseLocalEvent(ref ev);
}
if (dirty)
Dirty(manager);
}

View File

@@ -20,15 +20,19 @@ namespace Robust.Shared.Physics.Systems
{
public abstract class SharedBroadphaseSystem : EntitySystem
{
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IMapManagerInternal _mapManager = default!;
[Dependency] private readonly IParallelManager _parallel = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly SharedPhysicsSystem _physicsSystem = default!;
[Dependency] private readonly SharedGridTraversalSystem _traversal = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
[Dependency] private readonly SharedPhysicsSystem _physicsSystem = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
private ISawmill _logger = default!;
private EntityQuery<BroadphaseComponent> _broadphaseQuery;
private EntityQuery<MapGridComponent> _gridQuery;
private EntityQuery<PhysicsComponent> _physicsQuery;
private EntityQuery<TransformComponent> _xformQuery;
/*
* Okay so Box2D has its own "MoveProxy" stuff so you can easily find new contacts when required.
@@ -53,9 +57,12 @@ namespace Robust.Shared.Physics.Systems
{
base.Initialize();
_logger = Logger.GetSawmill("physics");
UpdatesOutsidePrediction = true;
_broadphaseQuery = GetEntityQuery<BroadphaseComponent>();
_gridQuery = GetEntityQuery<MapGridComponent>();
_physicsQuery = GetEntityQuery<PhysicsComponent>();
_xformQuery = GetEntityQuery<TransformComponent>();
UpdatesOutsidePrediction = true;
UpdatesAfter.Add(typeof(SharedTransformSystem));
_cfg.OnValueChanged(CVars.BroadphaseExpand, SetBroadphaseExpand, true);
@@ -79,27 +86,24 @@ namespace Robust.Shared.Physics.Systems
PhysicsMapComponent component,
MapId mapId,
HashSet<EntityUid> movedGrids,
Dictionary<FixtureProxy, Box2> gridMoveBuffer,
EntityQuery<BroadphaseComponent> broadQuery,
EntityQuery<TransformComponent> xformQuery)
Dictionary<FixtureProxy, Box2> gridMoveBuffer)
{
// None moved this tick
if (movedGrids.Count == 0) return;
var mapBroadphase = broadQuery.GetComponent(_mapManager.GetMapEntityId(mapId));
var mapBroadphase = _broadphaseQuery.GetComponent(_mapManager.GetMapEntityId(mapId));
// This is so that if we're on a broadphase that's moving (e.g. a grid) we need to make sure anything
// we move over is getting checked for collisions, and putting it on the movebuffer is the easiest way to do so.
var moveBuffer = component.MoveBuffer;
var gridQuery = GetEntityQuery<MapGridComponent>();
foreach (var gridUid in movedGrids)
{
var grid = gridQuery.GetComponent(gridUid);
var xform = xformQuery.GetComponent(gridUid);
var grid = _gridQuery.GetComponent(gridUid);
var xform = _xformQuery.GetComponent(gridUid);
DebugTools.Assert(xform.MapID == mapId);
var worldAABB = _transform.GetWorldMatrix(xform, xformQuery).TransformBox(grid.LocalAABB);
var worldAABB = _transform.GetWorldMatrix(xform).TransformBox(grid.LocalAABB);
var enlargedAABB = worldAABB.Enlarged(_broadphaseExpand);
var state = (moveBuffer, gridMoveBuffer);
@@ -111,7 +115,7 @@ namespace Robust.Shared.Physics.Systems
{
moveBuffer[proxy] = worldAABB;
// If something is in our AABB then try grid traversal for it
_traversal.CheckTraverse(proxy.Entity, xformQuery.GetComponent(proxy.Entity));
_traversal.CheckTraverse(proxy.Entity, _xformQuery.GetComponent(proxy.Entity));
}
}
@@ -157,12 +161,8 @@ namespace Robust.Shared.Physics.Systems
var movedGrids = Comp<MovedGridsComponent>(mapUid).MovedGrids;
var gridMoveBuffer = new Dictionary<FixtureProxy, Box2>();
var broadphaseQuery = GetEntityQuery<BroadphaseComponent>();
var physicsQuery = GetEntityQuery<PhysicsComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
// Find any entities being driven over that might need to be considered
FindGridContacts(component, mapId, movedGrids, gridMoveBuffer, broadphaseQuery, xformQuery);
FindGridContacts(component, mapId, movedGrids, gridMoveBuffer);
// There is some mariana trench levels of bullshit going on.
// We essentially need to re-create Box2D's FindNewContacts but in a way that allows us to check every
@@ -174,7 +174,7 @@ namespace Robust.Shared.Physics.Systems
// to cache a bunch of stuff to make up for it.
// Handle grids first as they're not stored on map broadphase at all.
HandleGridCollisions(mapId, movedGrids, physicsQuery, xformQuery);
HandleGridCollisions(mapId, movedGrids);
// EZ
if (moveBuffer.Count == 0)
@@ -212,7 +212,7 @@ namespace Robust.Shared.Physics.Systems
var proxyBody = proxy.Body;
DebugTools.Assert(!proxyBody.Deleted);
var state = (this, proxy, worldAABB, buffer, xformQuery, broadphaseQuery);
var state = (this, proxy, worldAABB, buffer);
// Get every broadphase we may be intersecting.
_mapManager.FindGridsIntersecting(mapId, worldAABB.Enlarged(_broadphaseExpand), ref state,
@@ -220,18 +220,16 @@ namespace Robust.Shared.Physics.Systems
SharedBroadphaseSystem system,
FixtureProxy proxy,
Box2 worldAABB,
List<FixtureProxy> pairBuffer,
EntityQuery<TransformComponent> xformQuery,
EntityQuery<BroadphaseComponent> broadphaseQuery) tuple) =>
List<FixtureProxy> pairBuffer) tuple) =>
{
ref var buffer = ref tuple.pairBuffer;
tuple.system.FindPairs(tuple.proxy, tuple.worldAABB, uid, buffer, tuple.xformQuery, tuple.broadphaseQuery);
tuple.system.FindPairs(tuple.proxy, tuple.worldAABB, uid, buffer);
return true;
});
// Struct ref moment, I have no idea what's fastest.
buffer = state.buffer;
FindPairs(proxy, worldAABB, _mapManager.GetMapEntityId(mapId), buffer, xformQuery, broadphaseQuery);
FindPairs(proxy, worldAABB, _mapManager.GetMapEntityId(mapId), buffer);
}
});
@@ -272,27 +270,23 @@ namespace Robust.Shared.Physics.Systems
private void HandleGridCollisions(
MapId mapId,
HashSet<EntityUid> movedGrids,
EntityQuery<PhysicsComponent> physicsQuery,
EntityQuery<TransformComponent> xformQuery)
HashSet<EntityUid> movedGrids)
{
var gridQuery = GetEntityQuery<MapGridComponent>();
foreach (var gridUid in movedGrids)
{
var grid = gridQuery.GetComponent(gridUid);
var xform = xformQuery.GetComponent(gridUid);
var grid = _gridQuery.GetComponent(gridUid);
var xform = _xformQuery.GetComponent(gridUid);
DebugTools.Assert(xform.MapID == mapId);
var (worldPos, worldRot, worldMatrix, invWorldMatrix) = _transform.GetWorldPositionRotationMatrixWithInv(xform, xformQuery);
var (worldPos, worldRot, worldMatrix, invWorldMatrix) = _transform.GetWorldPositionRotationMatrixWithInv(xform);
var aabb = new Box2Rotated(grid.LocalAABB, worldRot).CalcBoundingBox().Translated(worldPos);
// TODO: Need to handle grids colliding with non-grid entities with the same layer
// (nothing in SS14 does this yet).
var transform = _physicsSystem.GetPhysicsTransform(gridUid, xformQuery: xformQuery);
var state = (gridUid, grid, transform, worldMatrix, invWorldMatrix, _physicsSystem, _transform, physicsQuery, xformQuery);
var transform = _physicsSystem.GetPhysicsTransform(gridUid, xformQuery: _xformQuery);
var state = (gridUid, grid, transform, worldMatrix, invWorldMatrix, _map, _physicsSystem, _transform, _physicsQuery, _xformQuery);
_mapManager.FindGridsIntersecting(mapId, aabb, ref state,
static (EntityUid uid, MapGridComponent component,
@@ -301,6 +295,7 @@ namespace Robust.Shared.Physics.Systems
Transform transform,
Matrix3 worldMatrix,
Matrix3 invWorldMatrix,
SharedMapSystem _map,
SharedPhysicsSystem _physicsSystem,
SharedTransformSystem xformSystem,
EntityQuery<PhysicsComponent> physicsQuery,
@@ -320,7 +315,7 @@ namespace Robust.Shared.Physics.Systems
var aabb1 = tuple.grid.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.grid.GetLocalMapChunks(aabb1);
var ourChunks = tuple._map.GetLocalMapChunks(tuple.gridUid, tuple.grid, aabb1);
var physicsA = tuple.physicsQuery.GetComponent(tuple.gridUid);
var physicsB = tuple.physicsQuery.GetComponent(uid);
@@ -331,7 +326,7 @@ namespace Robust.Shared.Physics.Systems
tuple.worldMatrix.TransformBox(
ourChunk.CachedBounds.Translated(ourChunk.Indices * tuple.grid.ChunkSize));
var ourChunkOtherRef = otherGridInvMatrix.TransformBox(ourChunkWorld);
var collidingChunks = component.GetLocalMapChunks(ourChunkOtherRef);
var collidingChunks = tuple._map.GetLocalMapChunks(uid, component, ourChunkOtherRef);
while (collidingChunks.MoveNext(out var collidingChunk))
{
@@ -372,14 +367,12 @@ namespace Robust.Shared.Physics.Systems
FixtureProxy proxy,
Box2 worldAABB,
EntityUid broadphase,
List<FixtureProxy> pairBuffer,
EntityQuery<TransformComponent> xformQuery,
EntityQuery<BroadphaseComponent> broadphaseQuery)
List<FixtureProxy> pairBuffer)
{
DebugTools.Assert(proxy.Body.CanCollide);
// Broadphase can't intersect with entities on itself so skip.
if (proxy.Entity == broadphase || !xformQuery.TryGetComponent(proxy.Entity, out var xform))
if (proxy.Entity == broadphase || !_xformQuery.TryGetComponent(proxy.Entity, out var xform))
{
return;
}
@@ -389,7 +382,7 @@ namespace Robust.Shared.Physics.Systems
DebugTools.AssertNotNull(xform.Broadphase);
if (!_lookup.TryGetCurrentBroadphase(xform, out var proxyBroad))
{
_logger.Error($"Found null broadphase for {ToPrettyString(proxy.Entity)}");
Log.Error($"Found null broadphase for {ToPrettyString(proxy.Entity)}");
DebugTools.Assert(false);
return;
}
@@ -401,10 +394,10 @@ namespace Robust.Shared.Physics.Systems
}
else
{
aabb = _transform.GetInvWorldMatrix(broadphase, xformQuery).TransformBox(worldAABB);
aabb = _transform.GetInvWorldMatrix(broadphase).TransformBox(worldAABB);
}
var broadphaseComp = broadphaseQuery.GetComponent(broadphase);
var broadphaseComp = _broadphaseQuery.GetComponent(broadphase);
var state = (pairBuffer, proxy);
QueryBroadphase(broadphaseComp.DynamicTree, state, aabb);
@@ -512,7 +505,7 @@ namespace Robust.Shared.Physics.Systems
}
// Won't worry about accurate bounds checks as it's probably slower in most use cases.
var chunkEnumerator = mapGrid.GetMapChunks(aabb);
var chunkEnumerator = _map.GetMapChunks(bUid, mapGrid, aabb);
if (chunkEnumerator.MoveNext(out _))
{

View File

@@ -79,7 +79,7 @@ public abstract partial class SharedJointSystem
if (relayTarget.Relayed.Remove(uid))
{
// TODO: Comp cleanup.
Dirty(relayTarget);
Dirty(component.Relay.Value, relayTarget);
}
}
@@ -91,10 +91,10 @@ public abstract partial class SharedJointSystem
if (relayTarget.Relayed.Add(uid))
{
_physics.WakeBody(relay.Value);
Dirty(relayTarget);
Dirty(relay.Value, relayTarget);
}
}
Dirty(component);
Dirty(uid, component);
}
}

View File

@@ -11,11 +11,6 @@ namespace Robust.Shared.Replays;
public interface IReplayRecordingManager
{
/// <summary>
/// Initializes the replay manager.
/// </summary>
void Initialize();
/// <summary>
/// Whether or not a replay recording can currently be started.
/// </summary>
@@ -51,6 +46,14 @@ public interface IReplayRecordingManager
/// </summary>
bool IsRecording { get; }
/// <summary>
/// Gets the <c>state</c> object passed into <see cref="TryStartRecording"/> for the current recording.
/// </summary>
/// <remarks>
/// Returns <see langword="null"/> if there is no active replay recording.
/// </remarks>
public object? ActiveRecordingState { get; }
/// <summary>
/// Processes pending write tasks and saves the replay data for the current tick. This should be called even if a
/// replay is not currently being recorded.
@@ -62,20 +65,20 @@ public interface IReplayRecordingManager
/// to the recording's metadata file, as well as to provide serializable messages that get replayed when the replay
/// is initially loaded. E.g., this should contain networked events that would get sent to a newly connected client.
/// </summary>
event Action<MappingDataNode, List<object>>? RecordingStarted;
event Action<MappingDataNode, List<object>> RecordingStarted;
/// <summary>
/// This gets invoked whenever a replay recording is stopping. Subscribers can use this to add extra yaml data to the
/// recording's metadata file.
/// </summary>
event Action<MappingDataNode>? RecordingStopped;
event Action<MappingDataNode> RecordingStopped;
/// <summary>
/// This gets invoked after a replay recording has finished and provides information about where the replay data
/// was saved. Note that this only means that all write tasks have started, however some of the file tasks may not
/// have finished yet. See <see cref="WaitWriteTasks"/>.
/// </summary>
event Action<IWritableDirProvider, ResPath>? RecordingFinished;
event Action<ReplayRecordingFinished> RecordingFinished;
/// <summary>
/// Tries to starts a replay recording.
@@ -93,12 +96,16 @@ public interface IReplayRecordingManager
/// <param name="duration">
/// Optional time limit for the recording.
/// </param>
/// <param name="state">
/// An arbitrary object that is available in <see cref="ActiveRecordingState"/> and <see cref="RecordingFinished"/>.
/// </param>
/// <returns>Returns true if the recording was successfully started.</returns>
bool TryStartRecording(
IWritableDirProvider directory,
string? name = null,
bool overwrite = false,
TimeSpan? duration = null);
TimeSpan? duration = null,
object? state = null);
/// <summary>
/// Stops an ongoing replay recording.
@@ -106,10 +113,9 @@ public interface IReplayRecordingManager
void StopRecording();
/// <summary>
/// Returns information about the currently ongoing replay recording, including the currently elapsed time and the
/// compressed replay size.
/// Returns information about the currently ongoing replay recording.
/// </summary>
(float Minutes, int Ticks, float Size, float UncompressedSize) GetReplayStats();
ReplayRecordingStats GetReplayStats();
/// <summary>
/// Returns a task that will wait for all the current writing tasks to finish.
@@ -119,3 +125,36 @@ public interface IReplayRecordingManager
/// </exception>
Task WaitWriteTasks();
}
/// <summary>
/// Event data for <see cref="IReplayRecordingManager.RecordingFinished"/>.
/// </summary>
/// <param name="Directory">The writable dir provider in which the replay is being recorded.</param>
/// <param name="Path">The path to the replay in <paramref name="Directory"/>.</param>
/// <param name="State">The state object passed to <see cref="IReplayRecordingManager.TryStartRecording"/>.</param>
public record ReplayRecordingFinished(IWritableDirProvider Directory, ResPath Path, object? State);
/// <summary>
/// Statistics for an active replay recording.
/// </summary>
/// <param name="Time">The simulation time the replay has been recording for.</param>
/// <param name="Ticks">The amount of simulation ticks the replay has recorded.</param>
/// <param name="Size">The total compressed size of the replay data blobs.</param>
/// <param name="UncompressedSize">The total uncompressed size of the replay data blobs.</param>
public record struct ReplayRecordingStats(TimeSpan Time, uint Ticks, long Size, long UncompressedSize);
/// <summary>
/// Engine-internal functions for <see cref="IReplayRecordingManager"/>.
/// </summary>
internal interface IReplayRecordingManagerInternal : IReplayRecordingManager
{
/// <summary>
/// Initializes the replay manager.
/// </summary>
void Initialize();
/// <summary>
/// Shut down any active replay recording, at engine shutdown.
/// </summary>
void Shutdown();
}

View File

@@ -99,11 +99,15 @@ internal sealed class ReplayStatsCommand : LocalizedCommands
{
if (_replay.IsRecording)
{
var (time, tick, size, _) = _replay.GetReplayStats();
var stats = _replay.GetReplayStats();
var sizeMb = stats.Size / (1024f * 1024f);
var minutes = stats.Time.TotalMinutes;
shell.WriteLine(Loc.GetString("cmd-replay-recording-stats-result",
("time", time.ToString("F1")),
("ticks", tick), ("size", size.ToString("F1")),
("rate", (size/time).ToString("F2"))));
("time", minutes.ToString("F1")),
("ticks", stats.Ticks),
("size", sizeMb.ToString("F1")),
("rate", (sizeMb / minutes).ToString("F2"))));
}
else
shell.WriteLine(Loc.GetString("cmd-replay-recording-stop-not-recording"));

View File

@@ -21,13 +21,14 @@ using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading.Channels;
using System.Threading.Tasks;
using Robust.Shared.Asynchronous;
using Robust.Shared.Network;
using YamlDotNet.RepresentationModel;
using static Robust.Shared.Replays.ReplayConstants;
namespace Robust.Shared.Replays;
internal abstract partial class SharedReplayRecordingManager : IReplayRecordingManager
internal abstract partial class SharedReplayRecordingManager : IReplayRecordingManagerInternal
{
// date format for default replay names. Like the sortable template, but without colons.
public const string DefaultReplayNameFormat = "yyyy-MM-dd_HH-mm-ss";
@@ -38,10 +39,11 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
[Dependency] private readonly IRobustSerializer _serializer = default!;
[Dependency] private readonly INetManager _netMan = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly ITaskManager _taskManager = default!;
public event Action<MappingDataNode, List<object>>? RecordingStarted;
public event Action<MappingDataNode>? RecordingStopped;
public event Action<IWritableDirProvider, ResPath>? RecordingFinished;
public event Action<ReplayRecordingFinished>? RecordingFinished;
private ISawmill _sawmill = default!;
private List<object> _queuedMessages = new();
@@ -53,9 +55,9 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
private bool _enabled;
public bool IsRecording => _recState != null;
public object? ActiveRecordingState => _recState?.State;
private RecordingState? _recState;
/// <inheritdoc/>
public virtual void Initialize()
{
_sawmill = _logManager.GetSawmill("replay");
@@ -66,6 +68,18 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
NetConf.OnValueChanged(CVars.NetPVSCompressLevel, OnCompressionChanged);
}
public void Shutdown()
{
if (IsRecording)
{
StopRecording();
DebugTools.Assert(!IsRecording);
}
_taskManager.BlockWaitOnTask(WaitWriteTasks());
}
public virtual bool CanStartRecording()
{
return !IsRecording && _enabled;
@@ -137,7 +151,8 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
IWritableDirProvider directory,
string? name = null,
bool overwrite = false,
TimeSpan? duration = null)
TimeSpan? duration = null,
object? state = null)
{
if (!CanStartRecording())
return false;
@@ -152,7 +167,7 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
filePath = filePath.WithName(filePath.Filename + ".zip");
var basePath = new ResPath(NetConf.GetCVar(CVars.ReplayDirectory)).ToRootedPath();
filePath = basePath / filePath.ToRelativePath();
filePath = basePath / filePath;
// Make sure to create parent directory.
directory.CreateDir(filePath.Directory);
@@ -203,7 +218,8 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
commandQueue.Writer,
writeTask,
directory,
filePath
filePath,
state
);
try
@@ -348,8 +364,10 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
var document = new YamlDocument(yamlMetadata.ToYaml());
WriteYaml(recState, ReplayZipFolder / FileMetaFinal, document);
UpdateWriteTasks();
RecordingFinished?.Invoke(recState.DestDir, recState.DestPath);
Reset();
var finishedData = new ReplayRecordingFinished(recState.DestDir, recState.DestPath, recState.State);
RecordingFinished?.Invoke(finishedData);
}
private void WriteContentBundleInfo(RecordingState recState)
@@ -403,17 +421,17 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
return info;
}
public (float Minutes, int Ticks, float Size, float UncompressedSize) GetReplayStats()
public ReplayRecordingStats GetReplayStats()
{
if (_recState == null)
throw new InvalidOperationException("Not recording replay!");
var time = (Timing.CurTime - _recState.StartTime).TotalMinutes;
var time = Timing.CurTime - _recState.StartTime;
var tick = Timing.CurTick.Value - _recState.StartTick.Value;
var size = _recState.CompressedSize / (1024f * 1024f);
var altSize = _recState.UncompressedSize / (1024f * 1024f);
var size = _recState.CompressedSize;
var altSize = _recState.UncompressedSize;
return ((float)time, (int)tick, size, altSize);
return new ReplayRecordingStats(time, tick, size, altSize);
}
/// <summary>
@@ -428,6 +446,7 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
public readonly Task WriteTask;
public readonly IWritableDirProvider DestDir;
public readonly ResPath DestPath;
public readonly object? State;
// Tick and time when the recording was started.
public readonly GameTick StartTick;
@@ -437,8 +456,8 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
public readonly TimeSpan? EndTime;
public int Index;
public int CompressedSize;
public int UncompressedSize;
public long CompressedSize;
public long UncompressedSize;
public RecordingState(
ZipArchive zip,
@@ -450,11 +469,13 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
ChannelWriter<Action> writeCommandChannel,
Task writeTask,
IWritableDirProvider destDir,
ResPath destPath)
ResPath destPath,
object? state)
{
WriteTask = writeTask;
DestDir = destDir;
DestPath = destPath;
State = state;
Zip = zip;
Buffer = buffer;
CompressionContext = compressionContext;

View File

@@ -27,6 +27,7 @@ using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Utility;
using EyeComponent = Robust.Server.GameObjects.EyeComponent;
using MapSystem = Robust.Server.GameObjects.MapSystem;
namespace Robust.UnitTesting
{
@@ -86,6 +87,7 @@ namespace Robust.UnitTesting
var systems = deps.Resolve<IEntitySystemManager>();
// Required systems
systems.LoadExtraSystemType<MapSystem>();
systems.LoadExtraSystemType<EntityLookupSystem>();
// uhhh so maybe these are the wrong system for the client, but I CBF adding sprite system and all the rest,

View File

@@ -37,7 +37,6 @@ namespace Robust.UnitTesting.Shared
public void TestAnchoring()
{
var sim = RobustServerSimulation.NewSimulation();
// sim.RegisterEntitySystems(m => m.LoadExtraSystemType<EntityLookupSystem>());
var server = sim.InitializeInstance();
var lookup = server.Resolve<IEntitySystemManager>().GetEntitySystem<EntityLookupSystem>();
@@ -61,7 +60,7 @@ namespace Robust.UnitTesting.Shared
// When anchoring it should still get returned.
xform.Anchored = true;
Assert.That(xform.Anchored);
Assert.That(lookup.GetEntitiesIntersecting(mapId, theMapSpotBeingUsed).ToList().Count, Is.EqualTo(1));
Assert.That(lookup.GetEntitiesIntersecting(mapId, theMapSpotBeingUsed).ToList(), Has.Count.EqualTo(1));
xform.Anchored = false;
Assert.That(lookup.GetEntitiesIntersecting(mapId, theMapSpotBeingUsed).ToList().Count, Is.EqualTo(1));

View File

@@ -65,7 +65,7 @@ namespace Robust.UnitTesting.Shared.Map
var x = i % size;
var y = i / size;
chunk.SetTile((ushort)x, (ushort)y, new Tile((ushort)tiles[i]));
chunk.TrySetTile((ushort)x, (ushort)y, new Tile((ushort)tiles[i]), out _, out _);
}
return chunk;

View File

@@ -25,6 +25,7 @@ namespace Robust.UnitTesting.Shared.Map
await server.WaitIdleAsync();
var entMan = server.ResolveDependency<IEntityManager>();
var mapMan = server.ResolveDependency<IMapManager>();
await server.WaitAssertion(() =>
@@ -35,18 +36,18 @@ namespace Robust.UnitTesting.Shared.Map
var coordinates = new EntityCoordinates(gridEnt, new Vector2(10, 0));
// if no rotation and 0,0 position should just be the same coordinate.
Assert.That(IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(gridEnt).WorldRotation, Is.EqualTo(Angle.Zero));
Assert.That(entMan.GetComponent<TransformComponent>(gridEnt).WorldRotation, Is.EqualTo(Angle.Zero));
Assert.That(grid.WorldToLocal(coordinates.Position), Is.EqualTo(coordinates.Position));
// Rotate 180 degrees should show -10, 0 for the position in map-terms and 10, 0 for the position in entity terms (i.e. no change).
IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(gridEnt).WorldRotation += new Angle(MathF.PI);
Assert.That(IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(gridEnt).WorldRotation, Is.EqualTo(new Angle(MathF.PI)));
entMan.GetComponent<TransformComponent>(gridEnt).WorldRotation += new Angle(MathF.PI);
Assert.That(entMan.GetComponent<TransformComponent>(gridEnt).WorldRotation, Is.EqualTo(new Angle(MathF.PI)));
// Check the map coordinate rotates correctly
Assert.That(grid.WorldToLocal(new Vector2(10, 0)).EqualsApprox(new Vector2(-10, 0), 0.01f));
Assert.That(grid.LocalToWorld(coordinates.Position).EqualsApprox(new Vector2(-10, 0), 0.01f));
// Now we'll do the same for 180 degrees.
IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(gridEnt).WorldRotation += MathF.PI / 2f;
entMan.GetComponent<TransformComponent>(gridEnt).WorldRotation += MathF.PI / 2f;
// If grid facing down then worldpos of 10, 0 gets rotated 90 degrees CCW and hence should be 0, 10
Assert.That(grid.WorldToLocal(new Vector2(10, 0)).EqualsApprox(new Vector2(0, 10), 0.01f));
// If grid facing down then local 10,0 pos should just return 0, -10 given it's aligned with the rotation.
@@ -64,6 +65,7 @@ namespace Robust.UnitTesting.Shared.Map
var entMan = server.ResolveDependency<IEntityManager>();
var mapMan = server.ResolveDependency<IMapManager>();
var mapSystem = entMan.System<SharedMapSystem>();
await server.WaitAssertion(() =>
{
@@ -86,26 +88,26 @@ namespace Robust.UnitTesting.Shared.Map
Assert.That(chunks.Count, Is.EqualTo(1));
var chunk = chunks[0];
var aabb = grid.CalcWorldAABB(chunk);
var aabb = mapSystem.CalcWorldAABB(gridEnt, grid, chunk);
var bounds = new Box2(new Vector2(0, 0), new Vector2(2, 10));
// With all cardinal directions these should align.
Assert.That(aabb, Is.EqualTo(bounds));
IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(gridEnt).LocalRotation = new Angle(Math.PI);
aabb = grid.CalcWorldAABB(chunk);
entMan.GetComponent<TransformComponent>(gridEnt).LocalRotation = new Angle(Math.PI);
aabb = mapSystem.CalcWorldAABB(gridEnt, grid, chunk);
bounds = new Box2(new Vector2(-2, -10), new Vector2(0, 0));
Assert.That(aabb.EqualsApprox(bounds), $"Expected bounds of {aabb} and got {bounds}");
IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(gridEnt).LocalRotation = new Angle(-Math.PI / 2);
aabb = grid.CalcWorldAABB(chunk);
entMan.GetComponent<TransformComponent>(gridEnt).LocalRotation = new Angle(-Math.PI / 2);
aabb = mapSystem.CalcWorldAABB(gridEnt, grid, chunk);
bounds = new Box2(new Vector2(0, -2), new Vector2(10, 0));
Assert.That(aabb.EqualsApprox(bounds), $"Expected bounds of {aabb} and got {bounds}");
IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(gridEnt).LocalRotation = new Angle(-Math.PI / 4);
aabb = grid.CalcWorldAABB(chunk);
entMan.GetComponent<TransformComponent>(gridEnt).LocalRotation = new Angle(-Math.PI / 4);
aabb = mapSystem.CalcWorldAABB(gridEnt, grid, chunk);
bounds = new Box2(new Vector2(0, -1.4142135f), new Vector2(8.485281f, 7.071068f));
Assert.That(aabb.EqualsApprox(bounds), $"Expected bounds of {aabb} and got {bounds}");