mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 11:40:52 +01:00
Compare commits
2 Commits
ISerializa
...
prototype-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d49075a970 | ||
|
|
a67acd453c |
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<PropertyGroup><Version>0.8.67</Version></PropertyGroup>
|
||||
<PropertyGroup><Version>0.8.52</Version></PropertyGroup>
|
||||
</Project>
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace Robust.Client
|
||||
[Dependency] private readonly IPlayerManager _playMan = default!;
|
||||
[Dependency] private readonly INetConfigurationManager _configManager = default!;
|
||||
[Dependency] private readonly IClientEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IEntityLookup _entityLookup = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IDiscordRichPresence _discord = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
@@ -212,6 +213,7 @@ namespace Robust.Client
|
||||
{
|
||||
_entityManager.Startup();
|
||||
_mapManager.Startup();
|
||||
_entityLookup.Startup();
|
||||
|
||||
_timing.ResetSimTime();
|
||||
_timing.Paused = false;
|
||||
@@ -222,6 +224,7 @@ namespace Robust.Client
|
||||
IoCManager.Resolve<INetConfigurationManager>().FlushMessages();
|
||||
_gameStates.Reset();
|
||||
_playMan.Shutdown();
|
||||
_entityLookup.Shutdown();
|
||||
_entityManager.Shutdown();
|
||||
_mapManager.Shutdown();
|
||||
_discord.ClearPresence();
|
||||
|
||||
@@ -47,6 +47,7 @@ namespace Robust.Client
|
||||
IoCManager.Register<IMapManagerInternal, NetworkedMapManager>();
|
||||
IoCManager.Register<INetworkedMapManager, NetworkedMapManager>();
|
||||
IoCManager.Register<IEntityManager, ClientEntityManager>();
|
||||
IoCManager.Register<IEntityLookup, EntityLookup>();
|
||||
IoCManager.Register<IReflectionManager, ClientReflectionManager>();
|
||||
IoCManager.Register<IConsoleHost, ClientConsoleHost>();
|
||||
IoCManager.Register<IClientConsoleHost, ClientConsoleHost>();
|
||||
@@ -71,6 +72,7 @@ namespace Robust.Client
|
||||
IoCManager.Register<IStateManager, StateManager>();
|
||||
IoCManager.Register<IUserInterfaceManager, UserInterfaceManager>();
|
||||
IoCManager.Register<IUserInterfaceManagerInternal, UserInterfaceManager>();
|
||||
IoCManager.Register<IDebugDrawing, DebugDrawing>();
|
||||
IoCManager.Register<ILightManager, LightManager>();
|
||||
IoCManager.Register<IDiscordRichPresence, DiscordRichPresence>();
|
||||
IoCManager.Register<IMidiManager, MidiManager>();
|
||||
|
||||
@@ -109,13 +109,13 @@ namespace Robust.Client.Console
|
||||
if (AvailableCommands.ContainsKey(commandName))
|
||||
{
|
||||
var playerManager = IoCManager.Resolve<IPlayerManager>();
|
||||
#if !DEBUG
|
||||
|
||||
if (!_conGroup.CanCommand(commandName) && playerManager.LocalPlayer?.Session.Status > SessionStatus.Connecting)
|
||||
{
|
||||
WriteError(null, $"Insufficient perms for command: {commandName}");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
var command1 = AvailableCommands[commandName];
|
||||
args.RemoveAt(0);
|
||||
var shell = new ConsoleShell(this, null);
|
||||
|
||||
@@ -168,7 +168,7 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var mgr = EntitySystem.Get<DebugDrawingSystem>();
|
||||
var mgr = IoCManager.Resolve<IDebugDrawing>();
|
||||
mgr.DebugPositions = !mgr.DebugPositions;
|
||||
}
|
||||
}
|
||||
@@ -861,7 +861,7 @@ namespace Robust.Client.Console.Commands
|
||||
var chunkIndex = grid.LocalToChunkIndices(grid.MapToGrid(mousePos));
|
||||
var chunk = internalGrid.GetChunk(chunkIndex);
|
||||
|
||||
shell.WriteLine($"worldBounds: {internalGrid.CalcWorldAABB(chunk)} localBounds: {chunk.CachedBounds}");
|
||||
shell.WriteLine($"worldBounds: {chunk.CalcWorldAABB()} localBounds: {chunk.CalcLocalBounds()}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,21 +6,17 @@ using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Debugging
|
||||
{
|
||||
/// <summary>
|
||||
/// A collection of visual debug overlays for the client game.
|
||||
/// </summary>
|
||||
public sealed class DebugDrawingSystem : EntitySystem
|
||||
/// <inheritdoc />
|
||||
public sealed class DebugDrawing : IDebugDrawing
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IEntityLookup _lookup = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
|
||||
private bool _debugPositions;
|
||||
|
||||
/// <summary>
|
||||
/// Toggles the visual overlay of the local origin for each entity on screen.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public bool DebugPositions
|
||||
{
|
||||
get => _debugPositions;
|
||||
@@ -46,13 +42,13 @@ namespace Robust.Client.Debugging
|
||||
|
||||
private sealed class EntityPositionOverlay : Overlay
|
||||
{
|
||||
private readonly EntityLookupSystem _lookup;
|
||||
private readonly IEntityLookup _lookup;
|
||||
private readonly IEyeManager _eyeManager;
|
||||
private readonly IEntityManager _entityManager;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
public EntityPositionOverlay(EntityLookupSystem lookup, IEyeManager eyeManager, IEntityManager entityManager)
|
||||
public EntityPositionOverlay(IEntityLookup lookup, IEyeManager eyeManager, IEntityManager entityManager)
|
||||
{
|
||||
_lookup = lookup;
|
||||
_eyeManager = eyeManager;
|
||||
@@ -65,11 +61,13 @@ namespace Robust.Client.Debugging
|
||||
|
||||
var worldHandle = (DrawingHandleWorld) args.DrawingHandle;
|
||||
var viewport = _eyeManager.GetWorldViewport();
|
||||
var xformQuery = _entityManager.GetEntityQuery<TransformComponent>();
|
||||
|
||||
foreach (var entity in _lookup.GetEntitiesIntersecting(_eyeManager.CurrentMap, viewport))
|
||||
{
|
||||
var (center, worldRotation) = xformQuery.GetComponent(entity).GetWorldPositionRotation();
|
||||
var transform = _entityManager.GetComponent<TransformComponent>(entity);
|
||||
|
||||
var center = transform.WorldPosition;
|
||||
var worldRotation = transform.WorldRotation;
|
||||
|
||||
var xLine = worldRotation.RotateVec(Vector2.UnitX);
|
||||
var yLine = worldRotation.RotateVec(Vector2.UnitY);
|
||||
13
Robust.Client/Debugging/IDebugDrawing.cs
Normal file
13
Robust.Client/Debugging/IDebugDrawing.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace Robust.Client.Debugging
|
||||
{
|
||||
/// <summary>
|
||||
/// A collection of visual debug overlays for the client game.
|
||||
/// </summary>
|
||||
public interface IDebugDrawing
|
||||
{
|
||||
/// <summary>
|
||||
/// Toggles the visual overlay of the local origin for each entity on screen.
|
||||
/// </summary>
|
||||
bool DebugPositions { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,7 @@ namespace Robust.Client
|
||||
[Dependency] private readonly IClientConsoleHost _console = default!;
|
||||
[Dependency] private readonly ITimerManager _timerManager = default!;
|
||||
[Dependency] private readonly IClientEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IEntityLookup _lookup = default!;
|
||||
[Dependency] private readonly IPlacementManager _placementManager = default!;
|
||||
[Dependency] private readonly IClientGameStateManager _gameStateManager = default!;
|
||||
[Dependency] private readonly IOverlayManagerInternal _overlayManager = default!;
|
||||
@@ -454,7 +455,6 @@ namespace Robust.Client
|
||||
private void Tick(FrameEventArgs frameEventArgs)
|
||||
{
|
||||
_modLoader.BroadcastUpdate(ModUpdateLevel.PreEngine, frameEventArgs);
|
||||
_console.CommandBufferExecute();
|
||||
_timerManager.UpdateTimers(frameEventArgs);
|
||||
_taskManager.ProcessPendingTasks();
|
||||
|
||||
@@ -470,6 +470,7 @@ namespace Robust.Client
|
||||
// The last real tick is the current tick! This way we won't be in "prediction" mode.
|
||||
_gameTiming.LastRealTick = _gameTiming.CurTick;
|
||||
_entityManager.TickUpdate(frameEventArgs.DeltaSeconds, noPredictions: false);
|
||||
_lookup.Update();
|
||||
}
|
||||
|
||||
_modLoader.BroadcastUpdate(ModUpdateLevel.PostEngine, frameEventArgs);
|
||||
@@ -509,7 +510,7 @@ namespace Robust.Client
|
||||
logManager.GetSawmill("discord").Level = LogLevel.Warning;
|
||||
logManager.GetSawmill("net.predict").Level = LogLevel.Info;
|
||||
logManager.GetSawmill("szr").Level = LogLevel.Info;
|
||||
logManager.GetSawmill("loc").Level = LogLevel.Warning;
|
||||
logManager.GetSawmill("loc").Level = LogLevel.Error;
|
||||
|
||||
#if DEBUG_ONLY_FCE_INFO
|
||||
#if DEBUG_ONLY_FCE_LOG
|
||||
@@ -574,6 +575,7 @@ namespace Robust.Client
|
||||
|
||||
_networkManager.Shutdown("Client shutting down");
|
||||
_midiManager.Shutdown();
|
||||
IoCManager.Resolve<IEntityLookup>().Shutdown();
|
||||
_entityManager.Shutdown();
|
||||
_clyde.Shutdown();
|
||||
_clydeAudio.Shutdown();
|
||||
|
||||
@@ -8,7 +8,7 @@ using Robust.Shared.ViewVariables;
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
[ComponentReference(typeof(OccluderComponent))]
|
||||
public sealed class ClientOccluderComponent : OccluderComponent
|
||||
internal sealed class ClientOccluderComponent : OccluderComponent
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
@@ -71,29 +71,13 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
Occluding = OccluderDir.None;
|
||||
|
||||
if (Deleted)
|
||||
return;
|
||||
|
||||
// Content may want to override the default behavior for occlusion.
|
||||
var xform = _entityManager.GetComponent<TransformComponent>(Owner);
|
||||
var ev = new OccluderDirectionsEvent
|
||||
if (Deleted || !_entityManager.GetComponent<TransformComponent>(Owner).Anchored)
|
||||
{
|
||||
Component = xform,
|
||||
};
|
||||
|
||||
_entityManager.EventBus.RaiseLocalEvent(Owner, ref ev);
|
||||
|
||||
if (ev.Handled)
|
||||
{
|
||||
Occluding = ev.Directions;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!xform.Anchored)
|
||||
return;
|
||||
|
||||
var grid = _mapManager.GetGrid(xform.GridID);
|
||||
var position = xform.Coordinates;
|
||||
var grid = _mapManager.GetGrid(_entityManager.GetComponent<TransformComponent>(Owner).GridID);
|
||||
var position = _entityManager.GetComponent<TransformComponent>(Owner).Coordinates;
|
||||
void CheckDir(Direction dir, OccluderDir oclDir)
|
||||
{
|
||||
foreach (var neighbor in grid.GetInDir(position, dir))
|
||||
@@ -106,7 +90,7 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
var angle = xform.LocalRotation;
|
||||
var angle = _entityManager.GetComponent<TransformComponent>(Owner).LocalRotation;
|
||||
var dirRolling = angle.GetCardinalDir();
|
||||
// dirRolling starts at effective south
|
||||
|
||||
@@ -121,28 +105,15 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
CheckDir(dirRolling, OccluderDir.East);
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum OccluderDir : byte
|
||||
{
|
||||
None = 0,
|
||||
North = 1,
|
||||
East = 1 << 1,
|
||||
South = 1 << 2,
|
||||
West = 1 << 3,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised by occluders when trying to get occlusion directions.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public struct OccluderDirectionsEvent
|
||||
{
|
||||
public bool Handled = false;
|
||||
public OccluderDir Directions = OccluderDir.None;
|
||||
public TransformComponent Component = default!;
|
||||
|
||||
public OccluderDirectionsEvent() {}
|
||||
[Flags]
|
||||
internal enum OccluderDir : byte
|
||||
{
|
||||
None = 0,
|
||||
North = 1,
|
||||
East = 1 << 1,
|
||||
South = 1 << 2,
|
||||
West = 1 << 3,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,8 +119,7 @@ namespace Robust.Client.GameObjects
|
||||
/// This is useful to allow layer map configs to be defined in prototypes,
|
||||
/// while still allowing code to create configs if they're absent.
|
||||
/// </remarks>
|
||||
/// <returns>Index of the new layer.</returns>
|
||||
int LayerMapReserveBlank(object key);
|
||||
void LayerMapReserveBlank(object key);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a layer without texture (thus falling back to the error texture).
|
||||
@@ -146,8 +145,8 @@ namespace Robust.Client.GameObjects
|
||||
void RemoveLayer(int layer);
|
||||
void RemoveLayer(object layerKey);
|
||||
|
||||
void LayerSetShader(int layer, ShaderInstance shader, string? prototype = null);
|
||||
void LayerSetShader(object layerKey, ShaderInstance shader, string? prototype = null);
|
||||
void LayerSetShader(int layer, ShaderInstance shader);
|
||||
void LayerSetShader(object layerKey, ShaderInstance shader);
|
||||
void LayerSetShader(int layer, string shaderName);
|
||||
void LayerSetShader(object layerKey, string shaderName);
|
||||
|
||||
@@ -218,8 +217,8 @@ namespace Robust.Client.GameObjects
|
||||
int GetLayerDirectionCount(ISpriteLayer layer);
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the rotated sprite bounding box in world-space coordinates.
|
||||
/// Calculate sprite bounding box in world-space coordinates.
|
||||
/// </summary>
|
||||
Box2Rotated CalculateRotatedBoundingBox(Vector2 worldPosition, Angle worldRotation, IEye? eye = null);
|
||||
Box2 CalculateBoundingBox(Vector2 worldPos);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
RSI.State.Direction EffectiveDirection(Angle worldRotation);
|
||||
|
||||
Vector2 LocalToLayer(Vector2 localPos);
|
||||
|
||||
/// <summary>
|
||||
/// Layer size in pixels.
|
||||
/// Don't account layer scale or sprite world transform.
|
||||
@@ -35,6 +37,6 @@ namespace Robust.Client.GameObjects
|
||||
/// Calculate layer bounding box in sprite local-space coordinates.
|
||||
/// </summary>
|
||||
/// <returns>Bounding box in sprite local-space coordinates.</returns>
|
||||
Box2 CalculateBoundingBox(Angle worldAngle);
|
||||
Box2 CalculateBoundingBox();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,16 +84,10 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
foreach (var sprite in comp.SpriteTree.QueryAabb(localAABB))
|
||||
{
|
||||
var (worldPos, worldRot) = _entityManager.GetComponent<TransformComponent>(sprite.Owner).GetWorldPositionRotation();
|
||||
var bounds = sprite.CalculateRotatedBoundingBox(worldPos, worldRot);
|
||||
|
||||
// Get scaled down bounds used to indicate the "south" of a sprite.
|
||||
var localBound = bounds.Box;
|
||||
var smallLocal = localBound.Scale(0.2f).Translated(-new Vector2(0f, localBound.Extents.Y));
|
||||
var southIndicator = new Box2Rotated(smallLocal, bounds.Rotation, bounds.Origin);
|
||||
|
||||
var worldPos = _entityManager.GetComponent<TransformComponent>(sprite.Owner).WorldPosition;
|
||||
var bounds = sprite.CalculateBoundingBox(worldPos);
|
||||
handle.DrawRect(bounds, Color.Red.WithAlpha(0.2f));
|
||||
handle.DrawRect(southIndicator, Color.Blue.WithAlpha(0.5f));
|
||||
handle.DrawRect(bounds.Scale(0.2f).Translated(-new Vector2(0f, bounds.Extents.Y)), Color.Blue.WithAlpha(0.5f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using DrawDepthTag = Robust.Shared.GameObjects.DrawDepth;
|
||||
using RSIDirection = Robust.Client.Graphics.RSI.State.Direction;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
@@ -32,8 +31,6 @@ namespace Robust.Client.GameObjects
|
||||
[Dependency] private readonly IResourceCache resourceCache = default!;
|
||||
[Dependency] private readonly IPrototypeManager prototypes = default!;
|
||||
[Dependency] private readonly IEntityManager entities = default!;
|
||||
[Dependency] private readonly IReflectionManager reflection = default!;
|
||||
[Dependency] private readonly IEyeManager eyeManager = default!;
|
||||
|
||||
[DataField("visible")]
|
||||
private bool _visible = true;
|
||||
@@ -75,11 +72,7 @@ namespace Robust.Client.GameObjects
|
||||
public Vector2 Scale
|
||||
{
|
||||
get => scale;
|
||||
set
|
||||
{
|
||||
scale = value;
|
||||
UpdateLocalMatrix();
|
||||
}
|
||||
set => scale = value;
|
||||
}
|
||||
|
||||
[DataField("rotation")]
|
||||
@@ -90,11 +83,7 @@ namespace Robust.Client.GameObjects
|
||||
public Angle Rotation
|
||||
{
|
||||
get => rotation;
|
||||
set
|
||||
{
|
||||
rotation = value;
|
||||
UpdateLocalMatrix();
|
||||
}
|
||||
set => rotation = value;
|
||||
}
|
||||
|
||||
[DataField("offset")]
|
||||
@@ -108,18 +97,12 @@ namespace Robust.Client.GameObjects
|
||||
public Vector2 Offset
|
||||
{
|
||||
get => offset;
|
||||
set
|
||||
{
|
||||
offset = value;
|
||||
UpdateLocalMatrix();
|
||||
}
|
||||
set => offset = value;
|
||||
}
|
||||
|
||||
[DataField("color")]
|
||||
private Color color = Color.White;
|
||||
|
||||
public Matrix3 LocalMatrix = Matrix3.Identity;
|
||||
|
||||
[Animatable]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Color Color
|
||||
@@ -151,7 +134,113 @@ namespace Robust.Client.GameObjects
|
||||
Layers.Clear();
|
||||
foreach (var layerDatum in value)
|
||||
{
|
||||
AddLayer(layerDatum);
|
||||
var anyTextureAttempted = false;
|
||||
var layer = new Layer(this);
|
||||
if (!string.IsNullOrWhiteSpace(layerDatum.RsiPath))
|
||||
{
|
||||
var path = TextureRoot / layerDatum.RsiPath;
|
||||
|
||||
if (IoCManager.Resolve<IResourceCache>().TryGetResource(path, out RSIResource? resource))
|
||||
{
|
||||
layer.RSI = resource.RSI;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.ErrorS(LogCategory, "Unable to load layer RSI '{0}'.", path);
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(layerDatum.State))
|
||||
{
|
||||
anyTextureAttempted = true;
|
||||
var theRsi = layer.RSI ?? BaseRSI;
|
||||
if (theRsi == null)
|
||||
{
|
||||
Logger.ErrorS(LogCategory,
|
||||
"Layer has no RSI to load states from. Cannot use 'state' property. ({0})",
|
||||
layerDatum.State);
|
||||
}
|
||||
else
|
||||
{
|
||||
var stateid = new RSI.StateId(layerDatum.State);
|
||||
layer.State = stateid;
|
||||
if (theRsi.TryGetState(stateid, out var state))
|
||||
{
|
||||
// Always use south because this layer will be cached in the serializer.
|
||||
layer.AnimationTimeLeft = state.GetDelay(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.ErrorS(LogCategory,
|
||||
$"State '{stateid}' not found in RSI: '{theRsi.Path}'.",
|
||||
stateid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(layerDatum.TexturePath))
|
||||
{
|
||||
anyTextureAttempted = true;
|
||||
if (layer.State.IsValid)
|
||||
{
|
||||
Logger.ErrorS(LogCategory,
|
||||
"Cannot specify 'texture' on a layer if it has an RSI state specified."
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
layer.Texture =
|
||||
IoCManager.Resolve<IResourceCache>().GetResource<TextureResource>(TextureRoot / layerDatum.TexturePath);
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(layerDatum.Shader))
|
||||
{
|
||||
if (IoCManager.Resolve<IPrototypeManager>().TryIndex<ShaderPrototype>(layerDatum.Shader, out var prototype))
|
||||
{
|
||||
layer.Shader = prototype.Instance();
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.ErrorS(LogCategory,
|
||||
"Shader prototype '{0}' does not exist.",
|
||||
layerDatum.Shader);
|
||||
}
|
||||
}
|
||||
|
||||
layer.Color = layerDatum.Color;
|
||||
layer.Rotation = layerDatum.Rotation;
|
||||
layer._offset = layerDatum.Offset;
|
||||
// If neither state: nor texture: were provided we assume that they want a blank invisible layer.
|
||||
layer.Visible = anyTextureAttempted && layerDatum.Visible;
|
||||
layer.Scale = layerDatum.Scale;
|
||||
|
||||
Layers.Add(layer);
|
||||
|
||||
if (layerDatum.MapKeys != null)
|
||||
{
|
||||
var index = Layers.Count - 1;
|
||||
foreach (var keyString in layerDatum.MapKeys)
|
||||
{
|
||||
object key;
|
||||
if (IoCManager.Resolve<IReflectionManager>().TryParseEnumReference(keyString, out var @enum))
|
||||
{
|
||||
key = @enum;
|
||||
}
|
||||
else
|
||||
{
|
||||
key = keyString;
|
||||
}
|
||||
|
||||
if (LayerMap.ContainsKey(key))
|
||||
{
|
||||
Logger.ErrorS(LogCategory, "Duplicate layer map key definition: {0}", key);
|
||||
continue;
|
||||
}
|
||||
|
||||
LayerMap.Add(key, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_layerMapShared = true;
|
||||
@@ -247,13 +336,11 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(rsi))
|
||||
{
|
||||
var rsiPath = TextureRoot / rsi;
|
||||
if(resourceCache.TryGetResource(rsiPath, out RSIResource? resource))
|
||||
if(IoCManager.Resolve<IResourceCache>().TryGetResource(rsiPath, out RSIResource? resource))
|
||||
{
|
||||
BaseRSI = resource.RSI;
|
||||
}
|
||||
@@ -286,8 +373,6 @@ namespace Robust.Client.GameObjects
|
||||
LayerMap.Clear();
|
||||
LayerDatums = layerDatums;
|
||||
}
|
||||
|
||||
UpdateLocalMatrix();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -305,7 +390,6 @@ namespace Robust.Client.GameObjects
|
||||
offset = other.offset;
|
||||
rotation = other.rotation;
|
||||
scale = other.scale;
|
||||
UpdateLocalMatrix();
|
||||
drawDepth = other.drawDepth;
|
||||
_screenLock = other._screenLock;
|
||||
_overrideDirection = other._overrideDirection;
|
||||
@@ -331,14 +415,9 @@ namespace Robust.Client.GameObjects
|
||||
RenderOrder = other.RenderOrder;
|
||||
}
|
||||
|
||||
internal void UpdateLocalMatrix()
|
||||
{
|
||||
LocalMatrix = Matrix3.CreateTransform(in offset, in rotation, in scale);
|
||||
}
|
||||
|
||||
public Matrix3 GetLocalMatrix()
|
||||
{
|
||||
return LocalMatrix;
|
||||
return Matrix3.CreateTransform(in offset, in rotation, in scale);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -383,17 +462,14 @@ namespace Robust.Client.GameObjects
|
||||
_layerMapShared = false;
|
||||
}
|
||||
|
||||
public int LayerMapReserveBlank(object key)
|
||||
public void LayerMapReserveBlank(object key)
|
||||
{
|
||||
if (LayerMapTryGet(key, out var index))
|
||||
if (LayerMapTryGet(key, out var _))
|
||||
{
|
||||
return index;
|
||||
return;
|
||||
}
|
||||
|
||||
index = AddBlankLayer();
|
||||
LayerMapSet(key, index);
|
||||
|
||||
return index;
|
||||
LayerMapSet(key, AddBlankLayer());
|
||||
}
|
||||
|
||||
public int AddBlankLayer(int? newIndex = null)
|
||||
@@ -402,19 +478,6 @@ namespace Robust.Client.GameObjects
|
||||
return AddLayer(layer, newIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a new layer based on some <see cref="PrototypeLayerData"/>.
|
||||
/// </summary>
|
||||
public int AddLayer(PrototypeLayerData layerDatum, int? newIndex = null)
|
||||
{
|
||||
var layer = new Layer(this);
|
||||
|
||||
var index = AddLayer(layer, newIndex);
|
||||
|
||||
LayerSetData(index, layerDatum);
|
||||
return index;
|
||||
}
|
||||
|
||||
public int AddLayer(string texturePath, int? newIndex = null)
|
||||
{
|
||||
return AddLayer(new ResourcePath(texturePath), newIndex);
|
||||
@@ -590,144 +653,7 @@ namespace Robust.Client.GameObjects
|
||||
RemoveLayer(layer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fills in a layer's values using some <see cref="PrototypeLayerData"/>.
|
||||
/// </summary>
|
||||
public void LayerSetData(int index, PrototypeLayerData layerDatum)
|
||||
{
|
||||
if (Layers.Count <= index)
|
||||
{
|
||||
Logger.ErrorS(LogCategory, "Layer with index '{0}' does not exist, cannot set layer data! Trace:\n{1}",
|
||||
index, Environment.StackTrace);
|
||||
return;
|
||||
}
|
||||
|
||||
var layer = Layers[index];
|
||||
|
||||
var anyTextureAttempted = false;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(layerDatum.RsiPath))
|
||||
{
|
||||
var path = TextureRoot / layerDatum.RsiPath;
|
||||
|
||||
if (resourceCache.TryGetResource(path, out RSIResource? resource))
|
||||
{
|
||||
layer.RSI = resource.RSI;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.ErrorS(LogCategory, "Unable to load layer RSI '{0}'.", path);
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(layerDatum.State))
|
||||
{
|
||||
anyTextureAttempted = true;
|
||||
var theRsi = layer.RSI ?? BaseRSI;
|
||||
if (theRsi == null)
|
||||
{
|
||||
Logger.ErrorS(LogCategory,
|
||||
"Layer has no RSI to load states from. Cannot use 'state' property. ({0})",
|
||||
layerDatum.State);
|
||||
}
|
||||
else
|
||||
{
|
||||
var stateid = new RSI.StateId(layerDatum.State);
|
||||
layer.State = stateid;
|
||||
if (theRsi.TryGetState(stateid, out var state))
|
||||
{
|
||||
// Always use south because this layer will be cached in the serializer.
|
||||
layer.AnimationTimeLeft = state.GetDelay(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.ErrorS(LogCategory,
|
||||
$"State '{stateid}' not found in RSI: '{theRsi.Path}'.",
|
||||
stateid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(layerDatum.TexturePath))
|
||||
{
|
||||
anyTextureAttempted = true;
|
||||
if (layer.State.IsValid)
|
||||
{
|
||||
Logger.ErrorS(LogCategory,
|
||||
"Cannot specify 'texture' on a layer if it has an RSI state specified."
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
layer.Texture =
|
||||
resourceCache.GetResource<TextureResource>(TextureRoot / layerDatum.TexturePath);
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(layerDatum.Shader))
|
||||
{
|
||||
if (prototypes.TryIndex<ShaderPrototype>(layerDatum.Shader, out var prototype))
|
||||
{
|
||||
layer.ShaderPrototype = layerDatum.Shader;
|
||||
layer.Shader = prototype.Instance();
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.ErrorS(LogCategory,
|
||||
"Shader prototype '{0}' does not exist.",
|
||||
layerDatum.Shader);
|
||||
}
|
||||
}
|
||||
|
||||
if (layerDatum.MapKeys != null)
|
||||
{
|
||||
foreach (var keyString in layerDatum.MapKeys)
|
||||
{
|
||||
object key;
|
||||
if (reflection.TryParseEnumReference(keyString, out var @enum))
|
||||
{
|
||||
key = @enum;
|
||||
}
|
||||
else
|
||||
{
|
||||
key = keyString;
|
||||
}
|
||||
|
||||
if (LayerMap.TryGetValue(key, out var mappedIndex))
|
||||
{
|
||||
if (mappedIndex != index)
|
||||
Logger.ErrorS(LogCategory, "Duplicate layer map key definition: {0}", key);
|
||||
continue;
|
||||
}
|
||||
|
||||
_layerMapEnsurePrivate();
|
||||
LayerMap[key] = index;
|
||||
}
|
||||
}
|
||||
|
||||
layer.Color = layerDatum.Color;
|
||||
layer._rotation = layerDatum.Rotation;
|
||||
layer._offset = layerDatum.Offset;
|
||||
layer._scale = layerDatum.Scale;
|
||||
layer.UpdateLocalMatrix();
|
||||
|
||||
// If neither state: nor texture: were provided we assume that they want a blank invisible layer.
|
||||
layer.Visible = anyTextureAttempted && layerDatum.Visible;
|
||||
}
|
||||
|
||||
public void LayerSetData(object layerKey, PrototypeLayerData data)
|
||||
{
|
||||
if (!LayerMapTryGet(layerKey, out var layer))
|
||||
{
|
||||
Logger.ErrorS(LogCategory, "Layer with key '{0}' does not exist, cannot set shader! Trace:\n{1}",
|
||||
layerKey, Environment.StackTrace);
|
||||
return;
|
||||
}
|
||||
|
||||
LayerSetData(layer, data);
|
||||
}
|
||||
|
||||
public void LayerSetShader(int layer, ShaderInstance? shader, string? prototype = null)
|
||||
public void LayerSetShader(int layer, ShaderInstance? shader)
|
||||
{
|
||||
if (Layers.Count <= layer)
|
||||
{
|
||||
@@ -738,10 +664,9 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
var theLayer = Layers[layer];
|
||||
theLayer.Shader = shader;
|
||||
theLayer.ShaderPrototype = prototype;
|
||||
}
|
||||
|
||||
public void LayerSetShader(object layerKey, ShaderInstance shader, string? prototype = null)
|
||||
public void LayerSetShader(object layerKey, ShaderInstance shader)
|
||||
{
|
||||
if (!LayerMapTryGet(layerKey, out var layer))
|
||||
{
|
||||
@@ -750,7 +675,7 @@ namespace Robust.Client.GameObjects
|
||||
return;
|
||||
}
|
||||
|
||||
LayerSetShader(layer, shader, prototype);
|
||||
LayerSetShader(layer, shader);
|
||||
}
|
||||
|
||||
public void LayerSetShader(int layer, string shaderName)
|
||||
@@ -759,12 +684,10 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
Logger.ErrorS(LogCategory, "Shader prototype '{0}' does not exist. Trace:\n{1}", shaderName,
|
||||
Environment.StackTrace);
|
||||
|
||||
LayerSetShader(layer, null, null);
|
||||
return;
|
||||
}
|
||||
|
||||
LayerSetShader(layer, prototype.Instance(), shaderName);
|
||||
// This will set the shader to null if it does not exist.
|
||||
LayerSetShader(layer, prototype?.Instance());
|
||||
}
|
||||
|
||||
public void LayerSetShader(object layerKey, string shaderName)
|
||||
@@ -1225,7 +1148,7 @@ namespace Robust.Client.GameObjects
|
||||
return;
|
||||
}
|
||||
|
||||
Layers[layer].Offset = layerOffset;
|
||||
Layers[layer].SetOffset(layerOffset);
|
||||
}
|
||||
|
||||
public void LayerSetOffset(object layerKey, Vector2 layerOffset)
|
||||
@@ -1307,6 +1230,25 @@ namespace Robust.Client.GameObjects
|
||||
RenderInternal(drawingHandle, eyeRotation, worldRotation, worldPosition, overrideDir);
|
||||
}
|
||||
|
||||
private void CalcModelMatrix(int numDirs, Angle eyeRotation, Angle worldRotation, Vector2 worldPosition, out Matrix3 modelMatrix)
|
||||
{
|
||||
Angle angle;
|
||||
|
||||
if (_screenLock)
|
||||
{
|
||||
// Negate the eye rotation in the model matrix, so that later when the view matrix is applied the
|
||||
// sprite will be locked upright to the screen
|
||||
angle = new Angle(-eyeRotation.Theta);
|
||||
}
|
||||
else
|
||||
{
|
||||
angle = CalcRectWorldAngle(worldRotation + eyeRotation, numDirs) - eyeRotation;
|
||||
}
|
||||
|
||||
var sWorldRotation = angle;
|
||||
modelMatrix = Matrix3.CreateTransform(in worldPosition, in sWorldRotation);
|
||||
}
|
||||
|
||||
private void RenderInternal(DrawingHandleWorld drawingHandle, Angle eyeRotation, Angle worldRotation, Vector2 worldPosition, Direction? overrideDirection)
|
||||
{
|
||||
// Reduce the angles to fix math shenanigans
|
||||
@@ -1315,16 +1257,56 @@ namespace Robust.Client.GameObjects
|
||||
if (worldRotation.Theta < 0)
|
||||
worldRotation = new Angle(worldRotation.Theta + Math.Tau);
|
||||
|
||||
// worldRotation + eyeRotation should be the angle of the entity on-screen. If no-rot is enabled this is just set to zero.
|
||||
// However, at some point later the eye-matrix is applied separately, so we subtract -eye rotation for now:
|
||||
var entityMatrix = Matrix3.CreateTransform(worldPosition, NoRotation ? -eyeRotation : worldRotation);
|
||||
// sprite matrix, WITHOUT offset.
|
||||
// offset is applied after sprite numDirs snapping/rotation correction
|
||||
// --> apply at same time as layer offset
|
||||
var spriteMatrix = Matrix3.CreateTransform(Vector2.Zero, rotation, scale);
|
||||
|
||||
Matrix3.Multiply(ref LocalMatrix, ref entityMatrix, out var transform);
|
||||
|
||||
var angle = worldRotation + eyeRotation; // angle on-screen. Used to decide the direction of 4/8 directional RSIs
|
||||
foreach (var layer in Layers)
|
||||
{
|
||||
layer.Render(drawingHandle, ref transform, angle, overrideDirection);
|
||||
if (!layer.Visible)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var numDirs = GetLayerDirectionCount(layer);
|
||||
var layerRotation = worldRotation + layer.Rotation;
|
||||
var layerPosition = worldPosition + layerRotation.RotateVec(layer._offset + offset);
|
||||
|
||||
CalcModelMatrix(numDirs, eyeRotation, layerRotation, layerPosition, out var modelMatrix);
|
||||
Matrix3.Multiply(ref spriteMatrix, ref modelMatrix, out var transformMatrix);
|
||||
drawingHandle.SetTransform(in transformMatrix);
|
||||
|
||||
RenderLayer(drawingHandle, layer, eyeRotation, layerRotation, overrideDirection);
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderLayer(DrawingHandleWorld drawingHandle, Layer layer, Angle eyeRotation, Angle worldRotation, Direction? overrideDirection)
|
||||
{
|
||||
var texture = GetRenderTexture(layer, worldRotation + eyeRotation, overrideDirection);
|
||||
|
||||
if (layer.Shader != null)
|
||||
{
|
||||
drawingHandle.UseShader(layer.Shader);
|
||||
}
|
||||
|
||||
var layerColor = color * layer.Color;
|
||||
|
||||
var position = -(Vector2)texture.Size / (2f * EyeManager.PixelsPerMeter);
|
||||
var textureSize = texture.Size / (float)EyeManager.PixelsPerMeter;
|
||||
var quad = Box2.FromDimensions(position, textureSize);
|
||||
|
||||
// TODO: Implement layer-specific rotation and scale.
|
||||
// Apply these directly to the box.
|
||||
// Oh and when you do update Layer.LocalToLayer so content doesn't break.
|
||||
|
||||
// handle.Modulate changes the color
|
||||
// drawingHandle.SetTransform() is set above, turning the quad into local space vertices
|
||||
drawingHandle.DrawTextureRectRegion(texture, quad, layerColor);
|
||||
|
||||
if (layer.Shader != null)
|
||||
{
|
||||
drawingHandle.UseShader(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1360,6 +1342,27 @@ namespace Robust.Client.GameObjects
|
||||
};
|
||||
}
|
||||
|
||||
private Texture GetRenderTexture(Layer layer, Angle worldRotation, Direction? overrideDirection)
|
||||
{
|
||||
var texture = layer.Texture;
|
||||
|
||||
if (layer.State.IsValid)
|
||||
{
|
||||
// Pull texture from RSI state instead.
|
||||
var rsi = layer.RSI ?? BaseRSI;
|
||||
if (rsi == null || !rsi.TryGetState(layer.State, out var state))
|
||||
{
|
||||
state = GetFallbackState(resourceCache);
|
||||
}
|
||||
|
||||
var layerSpecificDir = layer.EffectiveDirection(state, worldRotation, overrideDirection);
|
||||
texture = state.GetFrame(layerSpecificDir, layer.AnimationFrame);
|
||||
}
|
||||
|
||||
texture ??= resourceCache.GetFallback<TextureResource>().Texture;
|
||||
return texture;
|
||||
}
|
||||
|
||||
public void FrameUpdate(float delta)
|
||||
{
|
||||
foreach (var t in Layers)
|
||||
@@ -1414,14 +1417,12 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
Visible = thestate.Visible;
|
||||
DrawDepth = thestate.DrawDepth;
|
||||
scale = thestate.Scale;
|
||||
rotation = thestate.Rotation;
|
||||
offset = thestate.Offset;
|
||||
UpdateLocalMatrix();
|
||||
Scale = thestate.Scale;
|
||||
Rotation = thestate.Rotation;
|
||||
Offset = thestate.Offset;
|
||||
Color = thestate.Color;
|
||||
RenderOrder = thestate.RenderOrder;
|
||||
|
||||
|
||||
if (thestate.BaseRsiPath != null && BaseRSI != null)
|
||||
{
|
||||
if (resourceCache.TryGetResource<RSIResource>(TextureRoot / thestate.BaseRsiPath, out var res))
|
||||
@@ -1437,8 +1438,43 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
// Maybe optimize this to NOT fully clear the layers. (see LayerDatums setter function)
|
||||
LayerDatums = thestate.Layers;
|
||||
// Maybe optimize this to NOT full clear.
|
||||
Layers.Clear();
|
||||
for (var i = 0; i < thestate.Layers.Count; i++)
|
||||
{
|
||||
var netlayer = thestate.Layers[i];
|
||||
var layer = new Layer(this)
|
||||
{
|
||||
// These are easy so do them here.
|
||||
Scale = netlayer.Scale,
|
||||
Rotation = netlayer.Rotation,
|
||||
Visible = netlayer.Visible,
|
||||
Color = netlayer.Color
|
||||
};
|
||||
Layers.Add(layer);
|
||||
|
||||
// Using the public API to handle errors.
|
||||
// Probably slow as crap.
|
||||
// Who am I kidding, DEFINITELY.
|
||||
if (netlayer.Shader != null)
|
||||
{
|
||||
LayerSetShader(i, netlayer.Shader);
|
||||
}
|
||||
|
||||
if (netlayer.RsiPath != null)
|
||||
{
|
||||
LayerSetRSI(i, netlayer.RsiPath);
|
||||
}
|
||||
|
||||
if (netlayer.TexturePath != null)
|
||||
{
|
||||
LayerSetTexture(i, netlayer.TexturePath);
|
||||
}
|
||||
else if (netlayer.State != null)
|
||||
{
|
||||
LayerSetState(i, netlayer.State);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void QueueUpdateIsInert()
|
||||
@@ -1517,61 +1553,35 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Box2Rotated CalculateRotatedBoundingBox(Vector2 worldPosition, Angle worldRotation, IEye? eye = null)
|
||||
public Box2 CalculateBoundingBox(Vector2 worldPos)
|
||||
{
|
||||
// fast check for empty sprites
|
||||
if (!Visible || Layers.Count == 0)
|
||||
{
|
||||
return new Box2Rotated(new Box2(worldPosition, worldPosition), Angle.Zero, worldPosition);
|
||||
}
|
||||
|
||||
// We need to modify world rotation so that it lies between 0 and 2pi.
|
||||
// This matters for 4 or 8 directional sprites deciding which quadrant (octant?) they lie in.
|
||||
// the 0->2pi convention is set by the sprite-rendering code that selects the layers.
|
||||
// See RenderInternal().
|
||||
|
||||
worldRotation = worldRotation.Reduced();
|
||||
if (worldRotation.Theta < 0)
|
||||
worldRotation = new Angle(worldRotation.Theta + Math.Tau);
|
||||
|
||||
eye ??= eyeManager.CurrentEye;
|
||||
|
||||
// Need relative angle on screen for determining the sprite rsi direction.
|
||||
Angle relativeRotation = NoRotation
|
||||
? Angle.Zero
|
||||
: worldRotation + eye.Rotation;
|
||||
if (Layers.Count == 0)
|
||||
return new Box2(worldPos, worldPos);
|
||||
|
||||
// we need to calculate bounding box taking into account all nested layers
|
||||
// because layers can have offsets, scale or rotation, we need to calculate a new BB
|
||||
// based on lowest bottomLeft and highest topRight points from all layers
|
||||
var box = Layers[0].CalculateBoundingBox(relativeRotation);
|
||||
// because layers can have offsets, scale or rotation we need to calculate a new BB
|
||||
// based on lowest bottomLeft and hightest topRight points from all layers
|
||||
var box = Layers[0].CalculateBoundingBox();
|
||||
|
||||
for (int i = 1; i < Layers.Count; i++)
|
||||
{
|
||||
var layer = Layers[i];
|
||||
if (!layer.Visible) continue;
|
||||
var layerBB = layer.CalculateBoundingBox(relativeRotation);
|
||||
var layerBB = layer.CalculateBoundingBox();
|
||||
|
||||
box = box.Union(layerBB);
|
||||
}
|
||||
|
||||
// Next, what we do is take the box2 and apply the sprite's transform, and then the entity's transform. We
|
||||
// could do this via Matrix3.TransformBox, but that only yields bounding boxes. So instead we manually
|
||||
// transform our box by the combination of these matrices:
|
||||
// apply sprite transformations and calculate sprite bounding box
|
||||
// we can optimize it a bit, if sprite doesn't have rotation
|
||||
var spriteBox = box.Scale(Scale);
|
||||
var spriteHasRotation = !Rotation.EqualsApprox(Angle.Zero);
|
||||
var spriteBB = spriteHasRotation ?
|
||||
new Box2Rotated(spriteBox, Rotation).CalcBoundingBox() : spriteBox;
|
||||
|
||||
if (Scale != Vector2.One)
|
||||
box = box.Scale(Scale);
|
||||
|
||||
var adjustedOffset = NoRotation
|
||||
? (-eye.Rotation).RotateVec(Offset)
|
||||
: worldRotation.RotateVec(Offset);
|
||||
|
||||
Vector2 position = adjustedOffset + worldPosition;
|
||||
Angle finalRotation = NoRotation
|
||||
? Rotation - eye.Rotation
|
||||
: Rotation + worldRotation;
|
||||
|
||||
return new Box2Rotated(box.Translated(position), finalRotation, position);
|
||||
// move it all to world transform system (with sprite offset)
|
||||
var worldBB = spriteBB.Translated(Offset + worldPos);
|
||||
return worldBB;
|
||||
}
|
||||
|
||||
internal void UpdateBounds()
|
||||
@@ -1605,11 +1615,10 @@ namespace Robust.Client.GameObjects
|
||||
Flip = 3,
|
||||
}
|
||||
|
||||
public sealed class Layer : ISpriteLayer, ISerializationHooks
|
||||
public sealed class Layer : ISpriteLayer
|
||||
{
|
||||
[ViewVariables] private readonly SpriteComponent _parent;
|
||||
|
||||
[ViewVariables] public string? ShaderPrototype;
|
||||
[ViewVariables] public ShaderInstance? Shader;
|
||||
[ViewVariables] public Texture? Texture;
|
||||
|
||||
@@ -1619,37 +1628,11 @@ namespace Robust.Client.GameObjects
|
||||
[ViewVariables] public float AnimationTime;
|
||||
[ViewVariables] public int AnimationFrame;
|
||||
|
||||
public Matrix3 LocalMatrix = Matrix3.Identity;
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Vector2 Scale { get; set; } = Vector2.One;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Vector2 Scale
|
||||
{
|
||||
get => _scale;
|
||||
set
|
||||
{
|
||||
if (_scale.EqualsApprox(value)) return;
|
||||
|
||||
_scale = value;
|
||||
UpdateLocalMatrix();
|
||||
_parent.UpdateBounds();
|
||||
}
|
||||
}
|
||||
internal Vector2 _scale = Vector2.One;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Angle Rotation
|
||||
{
|
||||
get => _rotation;
|
||||
set
|
||||
{
|
||||
if (_rotation.EqualsApprox(value)) return;
|
||||
|
||||
_rotation = value;
|
||||
UpdateLocalMatrix();
|
||||
_parent.UpdateBounds();
|
||||
}
|
||||
}
|
||||
internal Angle _rotation = Angle.Zero;
|
||||
public Angle Rotation { get; set; }
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Visible = true;
|
||||
@@ -1669,7 +1652,6 @@ namespace Robust.Client.GameObjects
|
||||
if (_offset.EqualsApprox(value)) return;
|
||||
|
||||
_offset = value;
|
||||
UpdateLocalMatrix();
|
||||
_parent.UpdateBounds();
|
||||
}
|
||||
}
|
||||
@@ -1693,7 +1675,6 @@ namespace Robust.Client.GameObjects
|
||||
if (toClone.Shader != null)
|
||||
{
|
||||
Shader = toClone.Shader.Mutable ? toClone.Shader.Duplicate() : toClone.Shader;
|
||||
ShaderPrototype = toClone.ShaderPrototype;
|
||||
}
|
||||
Texture = toClone.Texture;
|
||||
RSI = toClone.RSI;
|
||||
@@ -1701,26 +1682,14 @@ namespace Robust.Client.GameObjects
|
||||
AnimationTimeLeft = toClone.AnimationTimeLeft;
|
||||
AnimationTime = toClone.AnimationTime;
|
||||
AnimationFrame = toClone.AnimationFrame;
|
||||
_scale = toClone.Scale;
|
||||
_rotation = toClone.Rotation;
|
||||
_offset = toClone.Offset;
|
||||
UpdateLocalMatrix();
|
||||
Scale = toClone.Scale;
|
||||
Rotation = toClone.Rotation;
|
||||
Visible = toClone.Visible;
|
||||
Color = toClone.Color;
|
||||
DirOffset = toClone.DirOffset;
|
||||
AutoAnimated = toClone.AutoAnimated;
|
||||
}
|
||||
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
{
|
||||
UpdateLocalMatrix();
|
||||
}
|
||||
|
||||
internal void UpdateLocalMatrix()
|
||||
{
|
||||
LocalMatrix = Matrix3.CreateTransform(in _offset, in _rotation, in _scale);
|
||||
}
|
||||
|
||||
RSI? ISpriteLayer.Rsi { get => RSI; set => SetRsi(value); }
|
||||
RSI.StateId ISpriteLayer.RsiState { get => State; set => SetState(value); }
|
||||
Texture? ISpriteLayer.Texture { get => Texture; set => SetTexture(value); }
|
||||
@@ -1732,7 +1701,7 @@ namespace Robust.Client.GameObjects
|
||||
Color = Color,
|
||||
Rotation = Rotation,
|
||||
Scale = Scale,
|
||||
Shader = ShaderPrototype,
|
||||
//todo Shader = Shader,
|
||||
State = State.Name,
|
||||
Visible = Visible,
|
||||
RsiPath = RSI?.Path?.ToString(),
|
||||
@@ -1761,7 +1730,7 @@ namespace Robust.Client.GameObjects
|
||||
set => SetAutoAnimated(value);
|
||||
}
|
||||
|
||||
public RSIDirection EffectiveDirection(Angle worldRotation)
|
||||
public RSI.State.Direction EffectiveDirection(Angle worldRotation)
|
||||
{
|
||||
if (State == default)
|
||||
{
|
||||
@@ -1782,16 +1751,22 @@ namespace Robust.Client.GameObjects
|
||||
return default;
|
||||
}
|
||||
|
||||
public RSIDirection EffectiveDirection(RSI.State state, Angle worldRotation,
|
||||
public Vector2 LocalToLayer(Vector2 localPos)
|
||||
{
|
||||
// TODO: scale & rotation for layers is currently unimplemented.
|
||||
return localPos;
|
||||
}
|
||||
|
||||
public RSI.State.Direction EffectiveDirection(RSI.State state, Angle worldRotation,
|
||||
Direction? overrideDirection)
|
||||
{
|
||||
if (state.Directions == RSI.State.DirectionType.Dir1)
|
||||
{
|
||||
return RSIDirection.South;
|
||||
return RSI.State.Direction.South;
|
||||
}
|
||||
else
|
||||
{
|
||||
RSIDirection dir;
|
||||
RSI.State.Direction dir;
|
||||
if (overrideDirection != null)
|
||||
{
|
||||
dir = overrideDirection.Value.Convert(state.Directions);
|
||||
@@ -1923,6 +1898,11 @@ namespace Robust.Client.GameObjects
|
||||
_parent.QueueUpdateIsInert();
|
||||
}
|
||||
|
||||
public void SetOffset(Vector2 offset)
|
||||
{
|
||||
Offset = offset;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Vector2i PixelSize
|
||||
{
|
||||
@@ -1943,121 +1923,10 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Box2 CalculateBoundingBox(Angle angle)
|
||||
public Box2 CalculateBoundingBox()
|
||||
{
|
||||
// Other than some special cases for simple layers, this will basically just apply the matrix obtained
|
||||
// via GetLayerDrawMatrix() to this layer's bounding box.
|
||||
|
||||
var rsiState = GetActualState();
|
||||
|
||||
var dir = (rsiState == null || rsiState.Directions == RSI.State.DirectionType.Dir1)
|
||||
? RSIDirection.South
|
||||
: angle.ToRsiDirection(rsiState.Directions);
|
||||
|
||||
// special case for simple layers. The vast majority of layers are like this.
|
||||
if (_rotation == Angle.Zero)
|
||||
{
|
||||
var textureSize = PixelSize / EyeManager.PixelsPerMeter;
|
||||
|
||||
// this switch block is basically an explicit version of the `rsiDirectionMatrix` in `GetLayerDrawMatrix()`.
|
||||
var box = dir switch
|
||||
{
|
||||
// No rotation:
|
||||
RSIDirection.South or RSIDirection.North => Box2.CenteredAround(Offset, textureSize),
|
||||
// rotate 90 degrees:
|
||||
RSIDirection.East or RSIDirection.West => Box2.CenteredAround(Offset, (textureSize.Y, textureSize.X)),
|
||||
// rotated 45 degrees (any 45 degree rotated rectangle has a square bounding box with sides of length (x+y)/sqrt(2) )
|
||||
_ => Box2.CenteredAround(Offset, Vector2.One * (textureSize.X + textureSize.Y) / MathF.Sqrt(2))
|
||||
};
|
||||
|
||||
return _scale == Vector2.One ? box : box.Scale(_scale);
|
||||
}
|
||||
|
||||
// Welp we have some non-zero _rotation, so lets just apply the generalized layer transform and get the bounding box from where;
|
||||
GetLayerDrawMatrix(dir, out var layerDrawMatrix);
|
||||
return layerDrawMatrix.TransformBox(Box2.CentredAroundZero(PixelSize / EyeManager.PixelsPerMeter));
|
||||
}
|
||||
|
||||
internal RSI.State? GetActualState()
|
||||
{
|
||||
if (!State.IsValid)
|
||||
return null;
|
||||
|
||||
// Pull texture from RSI state
|
||||
var rsi = RSI ?? _parent.BaseRSI;
|
||||
if (rsi == null || !rsi.TryGetState(State, out var rsiState))
|
||||
{
|
||||
rsiState = GetFallbackState(_parent.resourceCache);
|
||||
}
|
||||
|
||||
return rsiState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given the apparent rotation of an entity on screen (world + eye rotation), get layer's matrix for drawing &
|
||||
/// relevant RSI direction.
|
||||
/// </summary>
|
||||
public void GetLayerDrawMatrix(RSIDirection dir, out Matrix3 layerDrawMatrix)
|
||||
{
|
||||
if (_parent.NoRotation)
|
||||
layerDrawMatrix = LocalMatrix;
|
||||
else
|
||||
{
|
||||
var rsiDirectionMatrix = Matrix3.CreateTransform(Vector2.Zero, -dir.Convert().ToAngle());
|
||||
Matrix3.Multiply(ref rsiDirectionMatrix, ref LocalMatrix, out layerDrawMatrix);
|
||||
}
|
||||
}
|
||||
|
||||
internal void Render(DrawingHandleWorld drawingHandle, ref Matrix3 spriteMatrix, Angle angle, Direction? overrideDirection)
|
||||
{
|
||||
if (!Visible)
|
||||
return;
|
||||
|
||||
var rsiState = GetActualState();
|
||||
|
||||
var dir = (rsiState == null || rsiState.Directions == RSI.State.DirectionType.Dir1)
|
||||
? RSIDirection.South
|
||||
: angle.ToRsiDirection(rsiState.Directions);
|
||||
|
||||
// Set the drawing transform for this layer
|
||||
GetLayerDrawMatrix(dir, out var layerMatrix);
|
||||
Matrix3.Multiply(ref layerMatrix, ref spriteMatrix, out var transformMatrix);
|
||||
drawingHandle.SetTransform(in transformMatrix);
|
||||
|
||||
// The direction used to draw the sprite can differ from the one that the angle would naively suggest,
|
||||
// due to direction overrides or offsets.
|
||||
if (overrideDirection != null && rsiState != null)
|
||||
dir = overrideDirection.Value.Convert(rsiState.Directions);
|
||||
dir = dir.OffsetRsiDir(DirOffset);
|
||||
|
||||
// Get the correct directional texture from the state, and draw it!
|
||||
var texture = GetRenderTexture(rsiState, dir);
|
||||
RenderTexture(drawingHandle, texture);
|
||||
}
|
||||
|
||||
private void RenderTexture(DrawingHandleWorld drawingHandle, Texture texture)
|
||||
{
|
||||
if (Shader != null)
|
||||
drawingHandle.UseShader(Shader);
|
||||
|
||||
var layerColor = _parent.color * Color;
|
||||
|
||||
var position = -(Vector2)texture.Size / (2f * EyeManager.PixelsPerMeter);
|
||||
var textureSize = texture.Size / (float)EyeManager.PixelsPerMeter;
|
||||
var quad = Box2.FromDimensions(position, textureSize);
|
||||
|
||||
drawingHandle.DrawTextureRectRegion(texture, quad, layerColor);
|
||||
|
||||
if (Shader != null)
|
||||
drawingHandle.UseShader(null);
|
||||
}
|
||||
|
||||
private Texture GetRenderTexture(RSI.State? state, RSIDirection dir)
|
||||
{
|
||||
if (state == null)
|
||||
return Texture ?? _parent.resourceCache.GetFallback<TextureResource>().Texture;
|
||||
|
||||
return state.GetFrame(dir, AnimationFrame);
|
||||
// TODO: scale & rotation for layers is currently unimplemented.
|
||||
return Box2.CenteredAround(Offset, PixelSize / EyeManager.PixelsPerMeter);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2165,7 +2034,6 @@ namespace Robust.Client.GameObjects
|
||||
return results;
|
||||
}
|
||||
|
||||
[Obsolete("Use SpriteSystem")]
|
||||
public static IRsiStateLike GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache)
|
||||
{
|
||||
var icon = IconComponent.GetPrototypeIcon(prototype, resourceCache);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -7,7 +6,7 @@ using Robust.Shared.GameStates;
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class AppearanceSystem : SharedAppearanceSystem
|
||||
internal sealed class AppearanceSystem : SharedAppearanceSystem
|
||||
{
|
||||
private readonly Queue<ClientAppearanceComponent> _queuedUpdates = new();
|
||||
|
||||
@@ -51,33 +50,10 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
if (!stateDiff) return;
|
||||
|
||||
component.AppearanceData = CloneAppearanceData(actualState.Data);
|
||||
component.AppearanceData = actualState.Data;
|
||||
MarkDirty(component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Take in an appearance data dictionary and attempt to clone it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// As some appearance data values are not simple value-type objects, this is not just a shallow clone.
|
||||
/// </remarks>
|
||||
private Dictionary<object, object> CloneAppearanceData(Dictionary<object, object> data)
|
||||
{
|
||||
Dictionary<object, object> newDict = new(data.Count);
|
||||
|
||||
foreach (var (key, value) in data)
|
||||
{
|
||||
if (value.GetType().IsValueType)
|
||||
newDict[key] = value;
|
||||
else if (value is ICloneable cloneable)
|
||||
newDict[key] = cloneable.Clone();
|
||||
else
|
||||
throw new NotSupportedException("Invalid object in appearance data dictionary. Appearance data must be cloneable");
|
||||
}
|
||||
|
||||
return newDict;
|
||||
}
|
||||
|
||||
public override void MarkDirty(AppearanceComponent component)
|
||||
{
|
||||
if (component.AppearanceDirty)
|
||||
@@ -131,7 +107,7 @@ namespace Robust.Client.GameObjects
|
||||
[ByRefEvent]
|
||||
public struct AppearanceChangeEvent
|
||||
{
|
||||
public AppearanceComponent Component;
|
||||
public IReadOnlyDictionary<object, object> AppearanceData;
|
||||
public AppearanceComponent Component = default!;
|
||||
public IReadOnlyDictionary<object, object> AppearanceData = default!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ namespace Robust.Client.GameObjects
|
||||
// This container is expecting an entity... but it got parented to some other entity???
|
||||
// Ah well, the sever should send a new container state that updates expected entities so just ignore it for now.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
RemoveExpectedEntity(message.Entity);
|
||||
|
||||
@@ -210,101 +210,68 @@ namespace Robust.Client.GameObjects
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
base.FrameUpdate(frameTime);
|
||||
var pointQuery = EntityManager.GetEntityQuery<PointLightComponent>();
|
||||
var spriteQuery = EntityManager.GetEntityQuery<SpriteComponent>();
|
||||
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
|
||||
foreach (var toUpdate in _updateQueue)
|
||||
{
|
||||
if (Deleted(toUpdate))
|
||||
if (EntityManager.Deleted(toUpdate))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
UpdateEntityRecursively(toUpdate, xformQuery, pointQuery, spriteQuery);
|
||||
UpdateEntityRecursively(toUpdate);
|
||||
}
|
||||
|
||||
_updateQueue.Clear();
|
||||
}
|
||||
|
||||
private void UpdateEntityRecursively(
|
||||
EntityUid entity,
|
||||
EntityQuery<TransformComponent> xformQuery,
|
||||
EntityQuery<PointLightComponent> pointQuery,
|
||||
EntityQuery<SpriteComponent> spriteQuery)
|
||||
private void UpdateEntityRecursively(EntityUid entity)
|
||||
{
|
||||
// Recursively go up parents and containers to see whether both sprites and lights need to be occluded
|
||||
// Could maybe optimise this more by checking nearest parent that has sprite / light and whether it's container
|
||||
// occluded but this probably isn't a big perf issue.
|
||||
var xform = xformQuery.GetComponent(entity);
|
||||
var parent = xform.ParentUid;
|
||||
var child = entity;
|
||||
var spriteOccluded = false;
|
||||
var lightOccluded = false;
|
||||
// TODO: Since we are recursing down,
|
||||
// we could cache ShowContents data here to speed it up for children.
|
||||
// Am lazy though.
|
||||
UpdateEntity(entity);
|
||||
|
||||
while (parent.IsValid() && !spriteOccluded && !lightOccluded)
|
||||
foreach (var child in EntityManager.GetComponent<TransformComponent>(entity).Children)
|
||||
{
|
||||
var parentXform = xformQuery.GetComponent(parent);
|
||||
if (TryComp<ContainerManagerComponent>(parent, out var manager) && manager.TryGetContainer(child, out var container))
|
||||
{
|
||||
spriteOccluded = spriteOccluded || !container.ShowContents;
|
||||
lightOccluded = lightOccluded || container.OccludesLight;
|
||||
}
|
||||
|
||||
child = parent;
|
||||
parent = parentXform.ParentUid;
|
||||
UpdateEntityRecursively(child.Owner);
|
||||
}
|
||||
|
||||
// Alright so
|
||||
// This is the CBT bit.
|
||||
// The issue is we need to go through the children and re-check whether they are or are not contained.
|
||||
// if they are contained then the occlusion values may need updating for all those children
|
||||
UpdateEntity(entity, xform, xformQuery, pointQuery, spriteQuery, spriteOccluded, lightOccluded);
|
||||
}
|
||||
|
||||
private void UpdateEntity(
|
||||
EntityUid entity,
|
||||
TransformComponent xform,
|
||||
EntityQuery<TransformComponent> xformQuery,
|
||||
EntityQuery<PointLightComponent> pointQuery,
|
||||
EntityQuery<SpriteComponent> spriteQuery,
|
||||
bool spriteOccluded,
|
||||
bool lightOccluded)
|
||||
private void UpdateEntity(EntityUid entity)
|
||||
{
|
||||
if (spriteQuery.TryGetComponent(entity, out var sprite))
|
||||
if (EntityManager.TryGetComponent(entity, out SpriteComponent? sprite))
|
||||
{
|
||||
sprite.ContainerOccluded = spriteOccluded;
|
||||
}
|
||||
sprite.ContainerOccluded = false;
|
||||
|
||||
if (pointQuery.TryGetComponent(entity, out var light))
|
||||
{
|
||||
light.ContainerOccluded = lightOccluded;
|
||||
}
|
||||
|
||||
var childEnumerator = xform.ChildEnumerator;
|
||||
|
||||
// Try to avoid TryComp if we already know stuff is occluded.
|
||||
if ((!spriteOccluded || !lightOccluded) && TryComp<ContainerManagerComponent>(entity, out var manager))
|
||||
{
|
||||
while (childEnumerator.MoveNext(out var child))
|
||||
// We have to recursively scan for containers upwards in case of nested containers.
|
||||
var tempParent = entity;
|
||||
while (tempParent.TryGetContainer(out var container))
|
||||
{
|
||||
// Thank god it's by value and not by ref.
|
||||
var childSpriteOccluded = spriteOccluded;
|
||||
var childLightOccluded = lightOccluded;
|
||||
|
||||
// We already know either sprite or light is not occluding so need to check container.
|
||||
if (manager.TryGetContainer(child.Value, out var container))
|
||||
if (!container.ShowContents)
|
||||
{
|
||||
childSpriteOccluded = childSpriteOccluded || !container.ShowContents;
|
||||
childLightOccluded = childLightOccluded || container.OccludesLight;
|
||||
sprite.ContainerOccluded = true;
|
||||
break;
|
||||
}
|
||||
|
||||
UpdateEntity(child.Value, xformQuery.GetComponent(child.Value), xformQuery, pointQuery, spriteQuery, childSpriteOccluded, childLightOccluded);
|
||||
tempParent = container.Owner;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
if (EntityManager.TryGetComponent(entity, out PointLightComponent? light))
|
||||
{
|
||||
while (childEnumerator.MoveNext(out var child))
|
||||
light.ContainerOccluded = false;
|
||||
|
||||
// We have to recursively scan for containers upwards in case of nested containers.
|
||||
var tempParent = entity;
|
||||
while (tempParent.TryGetContainer(out var container))
|
||||
{
|
||||
UpdateEntity(child.Value, xformQuery.GetComponent(child.Value), xformQuery, pointQuery, spriteQuery, spriteOccluded, lightOccluded);
|
||||
if (container.OccludesLight)
|
||||
{
|
||||
light.ContainerOccluded = true;
|
||||
break;
|
||||
}
|
||||
|
||||
tempParent = container.Owner;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace Robust.Client.GameObjects
|
||||
if (_enabled)
|
||||
{
|
||||
_lightOverlay = new DebugLightOverlay(
|
||||
EntitySystem.Get<EntityLookupSystem>(),
|
||||
IoCManager.Resolve<IEntityLookup>(),
|
||||
IoCManager.Resolve<IEyeManager>(),
|
||||
IoCManager.Resolve<IMapManager>(),
|
||||
Get<RenderingTreeSystem>());
|
||||
@@ -44,7 +44,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
private sealed class DebugLightOverlay : Overlay
|
||||
{
|
||||
private EntityLookupSystem _lookup;
|
||||
private IEntityLookup _lookup;
|
||||
private IEyeManager _eyeManager;
|
||||
private IMapManager _mapManager;
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
public DebugLightOverlay(EntityLookupSystem lookup, IEyeManager eyeManager, IMapManager mapManager, RenderingTreeSystem tree)
|
||||
public DebugLightOverlay(IEntityLookup lookup, IEyeManager eyeManager, IMapManager mapManager, RenderingTreeSystem tree)
|
||||
{
|
||||
_lookup = lookup;
|
||||
_eyeManager = eyeManager;
|
||||
@@ -72,7 +72,7 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
foreach (var light in tree.LightTree)
|
||||
{
|
||||
var aabb = _lookup.GetWorldAABB(light.Owner);
|
||||
var aabb = _lookup.GetWorldAabbFromEntity(light.Owner);
|
||||
if (!aabb.Intersects(worldBounds)) continue;
|
||||
|
||||
args.WorldHandle.DrawRect(aabb, Color.Green.WithAlpha(0.1f));
|
||||
|
||||
@@ -323,7 +323,7 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
private readonly IPlayerManager _playerManager;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
private readonly ShaderInstance _unshadedShader;
|
||||
private readonly EffectSystem _owner;
|
||||
|
||||
@@ -4,15 +4,11 @@ using Robust.Client.Input;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
@@ -25,8 +21,6 @@ namespace Robust.Client.GameObjects
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IClientGameStateManager _stateManager = default!;
|
||||
[Dependency] private readonly IConsoleHost _conHost = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
private readonly IPlayerCommandStates _cmdStates = new PlayerCommandStates();
|
||||
|
||||
@@ -114,43 +108,6 @@ namespace Robust.Client.GameObjects
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<PlayerAttachSysMessage>(OnAttachedEntityChanged);
|
||||
|
||||
_conHost.RegisterCommand("incmd",
|
||||
"Inserts an input command into the simulation",
|
||||
"incmd <KeyFunction> <d|u KeyState> <wxPos> <wyPos>",
|
||||
GenerateInputCommand);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
_conHost.UnregisterCommand("incmd");
|
||||
}
|
||||
|
||||
private void GenerateInputCommand(IConsoleShell shell, string argstr, string[] args)
|
||||
{
|
||||
var localPlayer = _playerManager.LocalPlayer;
|
||||
if(localPlayer is null)
|
||||
return;
|
||||
|
||||
var pent = localPlayer.ControlledEntity;
|
||||
if(pent is null)
|
||||
return;
|
||||
|
||||
BoundKeyFunction keyFunction = new BoundKeyFunction(args[0]);
|
||||
BoundKeyState state = args[1] == "u" ? BoundKeyState.Up: BoundKeyState.Down;
|
||||
|
||||
var pxform = Transform(pent.Value);
|
||||
var wPos = pxform.WorldPosition + new Vector2(float.Parse(args[2]), float.Parse(args[3]));
|
||||
var coords = EntityCoordinates.FromMap(EntityManager, pent.Value, new MapCoordinates(wPos, pxform.MapID));
|
||||
|
||||
var funcId = _inputManager.NetworkBindMap.KeyFunctionID(keyFunction);
|
||||
|
||||
var message = new FullInputCmdMessage(_timing.CurTick, _timing.TickFraction, funcId, state,
|
||||
coords, new ScreenCoordinates(0, 0, default), EntityUid.Invalid);
|
||||
|
||||
HandleInputCommand(localPlayer.Session, keyFunction, message);
|
||||
}
|
||||
|
||||
private void OnAttachedEntityChanged(PlayerAttachSysMessage message)
|
||||
|
||||
@@ -113,18 +113,12 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
private void AnythingMoved(ref MoveEvent args)
|
||||
{
|
||||
var pointQuery = EntityManager.GetEntityQuery<PointLightComponent>();
|
||||
var spriteQuery = EntityManager.GetEntityQuery<SpriteComponent>();
|
||||
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
var xforms = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
|
||||
AnythingMovedSubHandler(args.Sender, xformQuery, pointQuery, spriteQuery);
|
||||
AnythingMovedSubHandler(args.Sender, xforms);
|
||||
}
|
||||
|
||||
private void AnythingMovedSubHandler(
|
||||
EntityUid uid,
|
||||
EntityQuery<TransformComponent> xformQuery,
|
||||
EntityQuery<PointLightComponent> pointQuery,
|
||||
EntityQuery<SpriteComponent> spriteQuery)
|
||||
private void AnythingMovedSubHandler(EntityUid uid, EntityQuery<TransformComponent> xforms)
|
||||
{
|
||||
// To avoid doing redundant updates (and we don't need to update a grid's children ever)
|
||||
if (!_checkedChildren.Add(uid) || EntityManager.HasComponent<RenderingTreeComponent>(uid)) return;
|
||||
@@ -133,19 +127,17 @@ namespace Robust.Client.GameObjects
|
||||
// WHATEVER YOU DO, DON'T REPLACE THIS WITH SPAMMING EVENTS UNLESS YOU HAVE A GUARANTEE IT WON'T LAG THE GC.
|
||||
// (Struct-based events ok though)
|
||||
// Ironically this was lagging the GC lolz
|
||||
if (spriteQuery.TryGetComponent(uid, out var sprite))
|
||||
if (EntityManager.TryGetComponent(uid, out SpriteComponent? sprite))
|
||||
QueueSpriteUpdate(sprite);
|
||||
|
||||
if (pointQuery.TryGetComponent(uid, out var light))
|
||||
if (EntityManager.TryGetComponent(uid, out PointLightComponent? light))
|
||||
QueueLightUpdate(light);
|
||||
|
||||
if (!xformQuery.TryGetComponent(uid, out var xform)) return;
|
||||
if (!xforms.TryGetComponent(uid, out var xform)) return;
|
||||
|
||||
var childEnumerator = xform.ChildEnumerator;
|
||||
|
||||
while (childEnumerator.MoveNext(out var child))
|
||||
foreach (var child in xform.ChildEntities)
|
||||
{
|
||||
AnythingMovedSubHandler(child.Value, xformQuery, pointQuery, spriteQuery);
|
||||
AnythingMovedSubHandler(child, xforms);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,10 +369,13 @@ namespace Robust.Client.GameObjects
|
||||
private Box2 SpriteAabbFunc(in SpriteComponent value)
|
||||
{
|
||||
var xforms = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
|
||||
var xform = xforms.GetComponent(value.Owner);
|
||||
var (worldPos, worldRot) = xform.GetWorldPositionRotation();
|
||||
var bounds = new Box2Rotated(value.CalculateBoundingBox(worldPos), worldRot, worldPos);
|
||||
var tree = GetRenderTree(value.Owner, xforms);
|
||||
|
||||
return SpriteAabbFunc(value, worldPos, worldRot, xforms);
|
||||
return tree == null ? bounds.CalcBoundingBox() : xforms.GetComponent(tree.Owner).InvWorldMatrix.TransformBox(bounds);
|
||||
}
|
||||
|
||||
private Box2 LightAabbFunc(in PointLightComponent value)
|
||||
@@ -397,7 +392,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
private Box2 SpriteAabbFunc(SpriteComponent value, Vector2 worldPos, Angle worldRot, EntityQuery<TransformComponent> xforms)
|
||||
{
|
||||
var bounds = value.CalculateRotatedBoundingBox(worldPos, worldRot);
|
||||
var bounds = new Box2Rotated(value.CalculateBoundingBox(worldPos), worldRot, worldPos);
|
||||
var tree = GetRenderTree(value.Owner, xforms);
|
||||
|
||||
return tree == null ? bounds.CalcBoundingBox() : xforms.GetComponent(tree.Owner).InvWorldMatrix.TransformBox(bounds);
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.GameObjects;
|
||||
|
||||
public sealed partial class SpriteSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
|
||||
[Pure]
|
||||
public Texture Frame0(SpriteSpecifier specifier)
|
||||
{
|
||||
return RsiStateLike(specifier).Default;
|
||||
}
|
||||
|
||||
[Pure]
|
||||
public IRsiStateLike RsiStateLike(SpriteSpecifier specifier)
|
||||
{
|
||||
switch (specifier)
|
||||
{
|
||||
case SpriteSpecifier.Texture tex:
|
||||
return tex.GetTexture(_resourceCache);
|
||||
|
||||
case SpriteSpecifier.Rsi rsi:
|
||||
return GetState(rsi);
|
||||
|
||||
case SpriteSpecifier.EntityPrototype prototypeIcon:
|
||||
if (!_proto.TryIndex<EntityPrototype>(prototypeIcon.EntityPrototypeId, out var prototype))
|
||||
{
|
||||
Logger.Error("Failed to load PrototypeIcon {0}", prototypeIcon.EntityPrototypeId);
|
||||
return SpriteComponent.GetFallbackState(_resourceCache);
|
||||
}
|
||||
|
||||
return SpriteComponent.GetPrototypeIcon(prototype, _resourceCache);
|
||||
|
||||
default:
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
[Pure]
|
||||
public IRsiStateLike GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache)
|
||||
{
|
||||
var icon = IconComponent.GetPrototypeIcon(prototype, _resourceCache);
|
||||
if (icon != null) return icon;
|
||||
|
||||
if (!prototype.Components.ContainsKey("Sprite"))
|
||||
{
|
||||
return SpriteComponent.GetFallbackState(resourceCache);
|
||||
}
|
||||
|
||||
var dummy = Spawn(prototype.ID, MapCoordinates.Nullspace);
|
||||
var spriteComponent = EnsureComp<SpriteComponent>(dummy);
|
||||
var result = spriteComponent.Icon ?? SpriteComponent.GetFallbackState(resourceCache);
|
||||
Del(dummy);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[Pure]
|
||||
public RSI.State GetState(SpriteSpecifier.Rsi rsiSpecifier)
|
||||
{
|
||||
if (_resourceCache.TryGetResource<RSIResource>(
|
||||
SharedSpriteComponent.TextureRoot / rsiSpecifier.RsiPath,
|
||||
out var theRsi) &&
|
||||
theRsi.RSI.TryGetState(rsiSpecifier.RsiState, out var state))
|
||||
{
|
||||
return state;
|
||||
}
|
||||
|
||||
Logger.Error("Failed to load RSI {0}", rsiSpecifier.RsiPath);
|
||||
return SpriteComponent.GetFallbackState(_resourceCache);
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ namespace Robust.Client.GameObjects
|
||||
/// Updates the layer animation for every visible sprite.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed partial class SpriteSystem : EntitySystem
|
||||
public sealed class SpriteSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly RenderingTreeSystem _treeSystem = default!;
|
||||
|
||||
@@ -45,6 +45,7 @@ namespace Robust.Client.GameStates
|
||||
|
||||
[Dependency] private readonly IComponentFactory _compFactory = default!;
|
||||
[Dependency] private readonly IClientEntityManagerInternal _entities = default!;
|
||||
[Dependency] private readonly IEntityLookup _lookup = default!;
|
||||
[Dependency] private readonly IPlayerManager _players = default!;
|
||||
[Dependency] private readonly IClientNetManager _network = default!;
|
||||
[Dependency] private readonly IBaseClient _client = default!;
|
||||
@@ -331,6 +332,8 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
|
||||
_entities.TickUpdate((float) _timing.TickPeriod.TotalSeconds, noPredictions: !IsPredictionEnabled);
|
||||
|
||||
_lookup.Update();
|
||||
}
|
||||
|
||||
private void ResetPredictedEntities(GameTick curTick)
|
||||
|
||||
@@ -18,8 +18,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private readonly Dictionary<GridId, Dictionary<Vector2i, MapChunkData>> _mapChunkData =
|
||||
new();
|
||||
|
||||
private int _verticesPerChunk(MapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * 4;
|
||||
private int _indicesPerChunk(MapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * GetQuadBatchIndexCount();
|
||||
private int _verticesPerChunk(IMapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * 4;
|
||||
private int _indicesPerChunk(IMapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * GetQuadBatchIndexCount();
|
||||
|
||||
private void _drawGrids(Viewport viewport, Box2Rotated worldBounds, IEye eye)
|
||||
{
|
||||
@@ -78,7 +78,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
private void _updateChunkMesh(IMapGrid grid, MapChunk chunk)
|
||||
private void _updateChunkMesh(IMapGrid grid, IMapChunk chunk)
|
||||
{
|
||||
var data = _mapChunkData[grid.Index];
|
||||
|
||||
@@ -91,36 +91,25 @@ namespace Robust.Client.Graphics.Clyde
|
||||
Span<Vertex2D> vertexBuffer = stackalloc Vertex2D[_verticesPerChunk(chunk)];
|
||||
|
||||
var i = 0;
|
||||
var cSz = grid.ChunkSize;
|
||||
var cScaled = chunk.Indices * cSz;
|
||||
for (ushort x = 0; x < cSz; x++)
|
||||
foreach (var tile in chunk)
|
||||
{
|
||||
for (ushort y = 0; y < cSz; y++)
|
||||
var regionMaybe = _tileDefinitionManager.TileAtlasRegion(tile.Tile);
|
||||
if (regionMaybe == null)
|
||||
{
|
||||
var tile = chunk.GetTile(x, y);
|
||||
if (tile.IsEmpty)
|
||||
continue;
|
||||
|
||||
var regionMaybe = _tileDefinitionManager.TileAtlasRegion(tile.TypeId);
|
||||
if (regionMaybe == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var region = regionMaybe.Value;
|
||||
var gx = x + cScaled.X;
|
||||
var gy = y + cScaled.Y;
|
||||
|
||||
var vIdx = i * 4;
|
||||
vertexBuffer[vIdx + 0] = new Vertex2D(gx, gy, region.Left, region.Bottom);
|
||||
vertexBuffer[vIdx + 1] = new Vertex2D(gx + 1, gy, region.Right, region.Bottom);
|
||||
vertexBuffer[vIdx + 2] = new Vertex2D(gx + 1, gy + 1, region.Right, region.Top);
|
||||
vertexBuffer[vIdx + 3] = new Vertex2D(gx, gy + 1, region.Left, region.Top);
|
||||
var nIdx = i * GetQuadBatchIndexCount();
|
||||
var tIdx = (ushort)(i * 4);
|
||||
QuadBatchIndexWrite(indexBuffer, ref nIdx, tIdx);
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
var region = regionMaybe.Value;
|
||||
|
||||
var vIdx = i * 4;
|
||||
vertexBuffer[vIdx + 0] = new Vertex2D(tile.X, tile.Y, region.Left, region.Bottom);
|
||||
vertexBuffer[vIdx + 1] = new Vertex2D(tile.X + 1, tile.Y, region.Right, region.Bottom);
|
||||
vertexBuffer[vIdx + 2] = new Vertex2D(tile.X + 1, tile.Y + 1, region.Right, region.Top);
|
||||
vertexBuffer[vIdx + 3] = new Vertex2D(tile.X, tile.Y + 1, region.Left, region.Top);
|
||||
var nIdx = i * GetQuadBatchIndexCount();
|
||||
var tIdx = (ushort) (i * 4);
|
||||
QuadBatchIndexWrite(indexBuffer, ref nIdx, tIdx);
|
||||
i += 1;
|
||||
}
|
||||
|
||||
GL.BindVertexArray(datum.VAO);
|
||||
@@ -133,7 +122,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
datum.TileCount = i;
|
||||
}
|
||||
|
||||
private MapChunkData _initChunkBuffers(IMapGrid grid, MapChunk chunk)
|
||||
private MapChunkData _initChunkBuffers(IMapGrid grid, IMapChunk chunk)
|
||||
{
|
||||
var vao = GenVertexArray();
|
||||
BindVertexArray(vao);
|
||||
@@ -170,7 +159,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return datum;
|
||||
}
|
||||
|
||||
private bool _isChunkDirty(IMapGrid grid, MapChunk chunk)
|
||||
private bool _isChunkDirty(IMapGrid grid, IMapChunk chunk)
|
||||
{
|
||||
var data = _mapChunkData[grid.Index];
|
||||
return !data.TryGetValue(chunk.Indices, out var datum) || datum.Dirty;
|
||||
|
||||
@@ -24,17 +24,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
public ClydeDebugLayers DebugLayers { get; set; }
|
||||
|
||||
private readonly RefList<(SpriteComponent sprite, Vector2 worldPos, Angle worldRotation, Box2 spriteScreenBB)>
|
||||
private readonly RefList<(SpriteComponent sprite, Matrix3 worldMatrix, Angle worldRotation, float yWorldPos)>
|
||||
_drawingSpriteList
|
||||
=
|
||||
new();
|
||||
|
||||
// TODO allow this scale to be passed with PostShader as variable
|
||||
/// <summary>
|
||||
/// Some shaders that enlarge the final sprite, like emission or highlight effects, need to use a slightly larger render target.
|
||||
/// </summary>
|
||||
public static float PostShadeScale = 1.25f;
|
||||
|
||||
public void Render()
|
||||
{
|
||||
CheckTransferringScreenshots();
|
||||
@@ -223,8 +217,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
RenderOverlays(viewport, OverlaySpace.WorldSpaceBelowEntities, worldAABB, worldBounds);
|
||||
|
||||
var screenSize = viewport.Size;
|
||||
eye.GetViewMatrix(out var eyeMatrix, eye.Scale);
|
||||
|
||||
ProcessSpriteEntities(mapId, viewport, eye, worldBounds, _drawingSpriteList);
|
||||
ProcessSpriteEntities(mapId, eyeMatrix, worldBounds, _drawingSpriteList);
|
||||
|
||||
var worldOverlays = new List<Overlay>();
|
||||
|
||||
@@ -281,12 +276,30 @@ namespace Robust.Client.Graphics.Clyde
|
||||
break;
|
||||
}
|
||||
|
||||
var matrix = entry.worldMatrix;
|
||||
var worldPosition = new Vector2(matrix.R0C2, matrix.R1C2);
|
||||
|
||||
RenderTexture? entityPostRenderTarget = null;
|
||||
Vector2i roundedPos = default;
|
||||
if (entry.sprite.PostShader != null)
|
||||
{
|
||||
// get the size of the sprite on screen, scaled slightly to allow for shaders that increase the final sprite size.
|
||||
var screenSpriteSize = (Vector2i) (entry.spriteScreenBB.Size * PostShadeScale).Rounded();
|
||||
// calculate world bounding box
|
||||
var spriteBB = entry.sprite.CalculateBoundingBox(worldPosition);
|
||||
var spriteLB = spriteBB.BottomLeft;
|
||||
var spriteRT = spriteBB.TopRight;
|
||||
|
||||
// finally we can calculate screen bounding in pixels
|
||||
var screenLB = viewport.WorldToLocal(spriteLB);
|
||||
var screenRT = viewport.WorldToLocal(spriteRT);
|
||||
|
||||
// we need to scale RT a for effects like emission or highlight
|
||||
// scale can be passed with PostShader as variable in future
|
||||
var postShadeScale = 1.25f;
|
||||
var screenSpriteSize = (Vector2i) ((screenRT - screenLB) * postShadeScale).Rounded();
|
||||
|
||||
// Rotate the vector by the eye angle, otherwise the bounding box will be incorrect
|
||||
screenSpriteSize = (Vector2i) eye.Rotation.RotateVec(screenSpriteSize).Rounded();
|
||||
screenSpriteSize.Y = -screenSpriteSize.Y;
|
||||
|
||||
// I'm not 100% sure why it works, but without it post-shader
|
||||
// can be lower or upper by 1px than original sprite depending on sprite rotation or scale
|
||||
@@ -309,14 +322,16 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Calculate viewport so that the entity thinks it's drawing to the same position,
|
||||
// which is necessary for light application,
|
||||
// but it's ACTUALLY drawing into the center of the render target.
|
||||
roundedPos = (Vector2i) entry.spriteScreenBB.Center;
|
||||
var flippedPos = new Vector2i(roundedPos.X, screenSize.Y - roundedPos.Y);
|
||||
var spritePos = spriteBB.Center;
|
||||
var screenPos = viewport.WorldToLocal(spritePos);
|
||||
var (roundedX, roundedY) = roundedPos = (Vector2i) screenPos;
|
||||
var flippedPos = new Vector2i(roundedX, screenSize.Y - roundedY);
|
||||
flippedPos -= entityPostRenderTarget.Size / 2;
|
||||
_renderHandle.Viewport(Box2i.FromDimensions(-flippedPos, screenSize));
|
||||
}
|
||||
}
|
||||
|
||||
entry.sprite.Render(_renderHandle.DrawingHandleWorld, eye.Rotation, in entry.worldRotation, in entry.worldPos);
|
||||
entry.sprite.Render(_renderHandle.DrawingHandleWorld, eye.Rotation, in entry.worldRotation, in worldPosition);
|
||||
|
||||
if (entry.sprite.PostShader != null && entityPostRenderTarget != null)
|
||||
{
|
||||
@@ -354,26 +369,17 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private void ProcessSpriteEntities(MapId map, Viewport view, IEye eye, Box2Rotated worldBounds,
|
||||
RefList<(SpriteComponent sprite, Vector2 worldPos, Angle worldRot, Box2 spriteScreenBB)> list)
|
||||
private void ProcessSpriteEntities(MapId map, Matrix3 eyeMatrix, Box2Rotated worldBounds,
|
||||
RefList<(SpriteComponent sprite, Matrix3 matrix, Angle worldRot, float yWorldPos)> list)
|
||||
{
|
||||
var xforms = _entityManager.GetEntityQuery<TransformComponent>();
|
||||
|
||||
// Construct a matrix equivalent for Viewport.WorldToLocal()
|
||||
eye.GetViewMatrix(out var viewMatrix, view.RenderScale);
|
||||
var uiProjmatrix = Matrix3.Identity;
|
||||
uiProjmatrix.R0C0 = EyeManager.PixelsPerMeter;
|
||||
uiProjmatrix.R1C1 = -EyeManager.PixelsPerMeter;
|
||||
uiProjmatrix.R0C2 = view.Size.X / 2f;
|
||||
uiProjmatrix.R1C2 = view.Size.Y / 2f;
|
||||
var worldToLocal = viewMatrix * uiProjmatrix;
|
||||
|
||||
foreach (var comp in _entitySystemManager.GetEntitySystem<RenderingTreeSystem>().GetRenderTrees(map, worldBounds))
|
||||
{
|
||||
var bounds = xforms.GetComponent(comp.Owner).InvWorldMatrix.TransformBox(worldBounds);
|
||||
|
||||
comp.SpriteTree.QueryAabb(ref list, (
|
||||
ref RefList<(SpriteComponent sprite, Vector2 worldPos, Angle worldRot, Box2 spriteScreenBB)> state,
|
||||
ref RefList<(SpriteComponent sprite, Matrix3 matrix, Angle worldRot, float yWorldPos)> state,
|
||||
in SpriteComponent value) =>
|
||||
{
|
||||
var entity = value.Owner;
|
||||
@@ -381,10 +387,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
ref var entry = ref state.AllocAdd();
|
||||
entry.sprite = value;
|
||||
(entry.worldPos, entry.worldRot) = transform.GetWorldPositionRotation();
|
||||
|
||||
var spriteWorldBB = value.CalculateRotatedBoundingBox(entry.worldPos, entry.worldRot, eye);
|
||||
entry.spriteScreenBB = worldToLocal.TransformBox(spriteWorldBB);
|
||||
Vector2 worldPos;
|
||||
(worldPos, entry.worldRot, entry.matrix) = transform.GetWorldPositionRotationMatrix();
|
||||
var eyePos = eyeMatrix.Transform(worldPos);
|
||||
// Didn't use the bounds from the query as that has to be re-calculated (and is probably more expensive than this).
|
||||
var bounds = value.CalculateBoundingBox(eyePos);
|
||||
entry.yWorldPos = eyePos.Y - bounds.Extents.Y;
|
||||
return true;
|
||||
|
||||
}, bounds, true);
|
||||
|
||||
@@ -514,14 +514,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var enlargedBounds = worldAABB.Enlarged(renderingTreeSystem.MaxLightRadius);
|
||||
|
||||
// Use worldbounds for this one as we only care if the light intersects our actual bounds
|
||||
var state = (this, worldAABB, count: 0, shadowCastingCount: 0);
|
||||
var state = (this, worldAABB, count: 0);
|
||||
var xforms = _entityManager.GetEntityQuery<TransformComponent>();
|
||||
|
||||
foreach (var comp in renderingTreeSystem.GetRenderTrees(map, enlargedBounds))
|
||||
{
|
||||
var bounds = xforms.GetComponent(comp.Owner).InvWorldMatrix.TransformBox(worldBounds);
|
||||
|
||||
comp.LightTree.QueryAabb(ref state, (ref (Clyde clyde, Box2 worldAABB, int count, int shadowCastingCount) state, in PointLightComponent light) =>
|
||||
comp.LightTree.QueryAabb(ref state, (ref (Clyde clyde, Box2 worldAABB, int count) state, in PointLightComponent light) =>
|
||||
{
|
||||
if (state.count >= LightsToRenderListSize)
|
||||
{
|
||||
@@ -543,9 +543,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the light is a shadow casting light, keep a separate track of that
|
||||
if (light.CastShadows) state.shadowCastingCount++;
|
||||
|
||||
float distanceSquared = (state.worldAABB.Center - lightPos).LengthSquared;
|
||||
state.clyde._lightsToRenderList[state.count++] = (light, lightPos, distanceSquared);
|
||||
|
||||
@@ -553,29 +550,17 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}, bounds);
|
||||
}
|
||||
|
||||
if (state.shadowCastingCount > _maxLightsPerScene)
|
||||
if (state.count > _maxLightsPerScene)
|
||||
{
|
||||
// There are too many lights casting shadows to fit in the scene.
|
||||
// There are too many lights to fit in the scene.
|
||||
// This check must occur before occluder expansion, or else bad things happen.
|
||||
|
||||
// First, partition the array based on whether the lights are shadow casting or not
|
||||
// (non shadow casting lights should be the first partition, shadow casting lights the second)
|
||||
// Sort lights by distance.
|
||||
Array.Sort(_lightsToRenderList, 0, state.count, Comparer<(PointLightComponent light, Vector2 pos, float distanceSquared)>.Create((x, y) =>
|
||||
{
|
||||
if (x.light.CastShadows && !y.light.CastShadows) return 1;
|
||||
else if (!x.light.CastShadows && y.light.CastShadows) return -1;
|
||||
else return 0;
|
||||
}));
|
||||
|
||||
// Next, sort just the shadow casting lights by distance.
|
||||
Array.Sort(_lightsToRenderList, state.count - state.shadowCastingCount, state.shadowCastingCount, Comparer<(PointLightComponent light, Vector2 pos, float distanceSquared)>.Create((x, y) =>
|
||||
{
|
||||
return x.distanceSquared.CompareTo(y.distanceSquared);
|
||||
}));
|
||||
|
||||
// Then effectively delete the furthest lights, by setting the end of the array to exclude N
|
||||
// number of shadow casting lights (where N is the number above the max number per scene.)
|
||||
state.count -= state.shadowCastingCount - _maxLightsPerScene;
|
||||
// Then effectively delete the furthest lights.
|
||||
state.count = _maxLightsPerScene;
|
||||
}
|
||||
|
||||
// When culling occluders later, we can't just remove any occluders outside the worldBounds.
|
||||
|
||||
@@ -994,9 +994,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private sealed class SpriteDrawingOrderComparer : IComparer<int>
|
||||
{
|
||||
private readonly RefList<(SpriteComponent, Vector2, Angle, Box2)> _drawList;
|
||||
private readonly RefList<(SpriteComponent, Matrix3, Angle, float)> _drawList;
|
||||
|
||||
public SpriteDrawingOrderComparer(RefList<(SpriteComponent, Vector2, Angle, Box2)> drawList)
|
||||
public SpriteDrawingOrderComparer(RefList<(SpriteComponent, Matrix3, Angle, float)> drawList)
|
||||
{
|
||||
_drawList = drawList;
|
||||
}
|
||||
@@ -1019,8 +1019,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return cmp;
|
||||
}
|
||||
|
||||
// compare the top of the sprite's BB for y-sorting. Because screen coordinates are flipped, the "top" of the BB is actually the "bottom".
|
||||
cmp = _drawList[x].Item4.Top.CompareTo(_drawList[y].Item4.Top);
|
||||
cmp = _drawList[y].Item4.CompareTo(_drawList[x].Item4);
|
||||
|
||||
if (cmp != 0)
|
||||
{
|
||||
|
||||
@@ -24,16 +24,9 @@ namespace Robust.Client.Map
|
||||
|
||||
private readonly Dictionary<ushort, Box2> _tileRegions = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public Box2? TileAtlasRegion(Tile tile)
|
||||
{
|
||||
return TileAtlasRegion(tile.TypeId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Box2? TileAtlasRegion(ushort tileType)
|
||||
{
|
||||
if (_tileRegions.TryGetValue(tileType, out var region))
|
||||
if (_tileRegions.TryGetValue(tile.TypeId, out var region))
|
||||
{
|
||||
return region;
|
||||
}
|
||||
|
||||
@@ -19,11 +19,5 @@ namespace Robust.Client.Map
|
||||
/// </summary>
|
||||
/// <returns>If null, do not draw the tile at all.</returns>
|
||||
Box2? TileAtlasRegion(Tile tile);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the region inside the texture atlas to use to draw a tile type.
|
||||
/// </summary>
|
||||
/// <returns>If null, do not draw the tile at all.</returns>
|
||||
Box2? TileAtlasRegion(ushort tileType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace Robust.Client.Placement.Modes
|
||||
|
||||
var mapId = MouseCoords.GetMapId(pManager.EntityManager);
|
||||
|
||||
var snapToEntities = EntitySystem.Get<EntityLookupSystem>().GetEntitiesInRange(MouseCoords, SnapToRange)
|
||||
var snapToEntities = IoCManager.Resolve<IEntityLookup>().GetEntitiesInRange(MouseCoords, SnapToRange)
|
||||
.Where(entity => pManager.EntityManager.GetComponent<MetaDataComponent>(entity).EntityPrototype == pManager.CurrentPrototype && pManager.EntityManager.GetComponent<TransformComponent>(entity).MapID == mapId)
|
||||
.OrderBy(entity => (pManager.EntityManager.GetComponent<TransformComponent>(entity).WorldPosition - MouseCoords.ToMapPos(pManager.EntityManager)).LengthSquared)
|
||||
.ToList();
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace Robust.Client.Placement.Modes
|
||||
var topRight = new Vector2(CurrentTile.X + 0.99f, CurrentTile.Y + 0.99f);
|
||||
var box = new Box2(bottomLeft, topRight);
|
||||
|
||||
return !EntitySystem.Get<EntityLookupSystem>().AnyEntitiesIntersecting(map, box);
|
||||
return !IoCManager.Resolve<IEntityLookup>().AnyEntitiesIntersecting(map, box);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,7 +230,7 @@ namespace Robust.Client.ResourceManagement
|
||||
|
||||
using (var manifestFile = cache.ContentFileRead(manifestPath))
|
||||
{
|
||||
if (manifestFile.CanSeek && manifestFile.Length <= 4096)
|
||||
if (manifestFile.Length <= 4096)
|
||||
{
|
||||
// Most RSIs are actually tiny so if that's the case just load them into a stackalloc buffer.
|
||||
// Avoids a ton of allocations with stream reader etc
|
||||
|
||||
@@ -53,7 +53,6 @@
|
||||
<Compile Update="UserInterface\CustomControls\DefaultWindow.xaml.cs">
|
||||
<DependentUpon>DefaultWindow.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Remove="Debugging\IDebugDrawing.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="..\MSBuild\Robust.Engine.targets" />
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -22,7 +22,6 @@ namespace Robust.Client.Utility
|
||||
.Texture;
|
||||
}
|
||||
|
||||
[Obsolete("Use SpriteSystem")]
|
||||
public static RSI.State GetState(this SpriteSpecifier.Rsi rsiSpecifier, IResourceCache cache)
|
||||
{
|
||||
if (cache.TryGetResource<RSIResource>(
|
||||
@@ -37,7 +36,6 @@ namespace Robust.Client.Utility
|
||||
return SpriteComponent.GetFallbackState(cache);
|
||||
}
|
||||
|
||||
[Obsolete("Use SpriteSystem")]
|
||||
public static Texture Frame0(this SpriteSpecifier specifier)
|
||||
{
|
||||
return specifier.RsiStateLike().Default;
|
||||
@@ -82,7 +80,6 @@ namespace Robust.Client.Utility
|
||||
return specifier.RsiStateLike();
|
||||
}
|
||||
|
||||
[Obsolete("Use SpriteSystem")]
|
||||
public static IRsiStateLike RsiStateLike(this SpriteSpecifier specifier)
|
||||
{
|
||||
var resC = IoCManager.Resolve<IResourceCache>();
|
||||
|
||||
@@ -66,17 +66,18 @@ namespace Robust.Server
|
||||
|
||||
[Dependency] private readonly IConfigurationManagerInternal _config = default!;
|
||||
[Dependency] private readonly IServerEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IEntityLookup _lookup = default!;
|
||||
[Dependency] private readonly ILogManager _log = default!;
|
||||
[Dependency] private readonly IRobustSerializer _serializer = default!;
|
||||
[Dependency] private readonly IGameTiming _time = default!;
|
||||
[Dependency] private readonly IResourceManagerInternal _resources = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly ITimerManager _timerManager = default!;
|
||||
[Dependency] private readonly ITimerManager timerManager = default!;
|
||||
[Dependency] private readonly IServerGameStateManager _stateManager = default!;
|
||||
[Dependency] private readonly IServerNetManager _network = default!;
|
||||
[Dependency] private readonly ISystemConsoleManager _systemConsole = default!;
|
||||
[Dependency] private readonly ITaskManager _taskManager = default!;
|
||||
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
|
||||
[Dependency] private readonly IRuntimeLog runtimeLog = default!;
|
||||
[Dependency] private readonly IModLoaderInternal _modLoader = default!;
|
||||
[Dependency] private readonly IWatchdogApi _watchdogApi = default!;
|
||||
[Dependency] private readonly HubManager _hubManager = default!;
|
||||
@@ -343,6 +344,7 @@ namespace Robust.Server
|
||||
_consoleHost.Initialize();
|
||||
_entityManager.Startup();
|
||||
_mapManager.Startup();
|
||||
IoCManager.Resolve<IEntityLookup>().Startup();
|
||||
_stateManager.Initialize();
|
||||
|
||||
var reg = _entityManager.ComponentFactory.GetRegistration<TransformComponent>();
|
||||
@@ -601,6 +603,7 @@ namespace Robust.Server
|
||||
_network.Shutdown($"Server shutting down: {_shutdownReason}");
|
||||
|
||||
// shutdown entities
|
||||
IoCManager.Resolve<IEntityLookup>().Shutdown();
|
||||
_entityManager.Cleanup();
|
||||
|
||||
if (_config.GetCVar(CVars.LogRuntimeLog))
|
||||
@@ -611,7 +614,7 @@ namespace Robust.Server
|
||||
Directory.CreateDirectory(relPath);
|
||||
var pathToWrite = Path.Combine(relPath,
|
||||
"Runtime-" + DateTime.Now.ToString("yyyy-MM-dd-THH-mm-ss") + ".txt");
|
||||
File.WriteAllText(pathToWrite, _runtimeLog.Display(), EncodingHelpers.UTF8);
|
||||
File.WriteAllText(pathToWrite, runtimeLog.Display(), EncodingHelpers.UTF8);
|
||||
}
|
||||
|
||||
AppDomain.CurrentDomain.ProcessExit -= ProcessExiting;
|
||||
@@ -656,8 +659,7 @@ namespace Robust.Server
|
||||
|
||||
using (TickUsage.WithLabels("Timers").NewTimer())
|
||||
{
|
||||
_consoleHost.CommandBufferExecute();
|
||||
_timerManager.UpdateTimers(frameEventArgs);
|
||||
timerManager.UpdateTimers(frameEventArgs);
|
||||
}
|
||||
|
||||
using (TickUsage.WithLabels("AsyncTasks").NewTimer())
|
||||
@@ -668,6 +670,8 @@ namespace Robust.Server
|
||||
// Pass Histogram into the IEntityManager.Update so it can do more granular measuring.
|
||||
_entityManager.TickUpdate(frameEventArgs.DeltaSeconds, noPredictions: false, TickUsage);
|
||||
|
||||
_lookup.Update();
|
||||
|
||||
using (TickUsage.WithLabels("PostEngine").NewTimer())
|
||||
{
|
||||
_modLoader.BroadcastUpdate(ModUpdateLevel.PostEngine, frameEventArgs);
|
||||
|
||||
@@ -311,7 +311,7 @@ namespace Robust.Server.Bql
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
var radius = (float)(double)arguments[0];
|
||||
var entityLookup = EntitySystem.Get<EntityLookupSystem>();
|
||||
var entityLookup = IoCManager.Resolve<IEntityLookup>();
|
||||
|
||||
// TODO: Make this a foreach and reduce LINQ chain because it'll allocate a LOT
|
||||
//BUG: GetEntitiesInRange effectively uses manhattan distance. This is not intended, near is supposed to be circular.
|
||||
|
||||
@@ -29,13 +29,14 @@ namespace Robust.Server.Console.Commands
|
||||
var mapId = new MapId(int.Parse(args[0]));
|
||||
|
||||
var mapMgr = IoCManager.Resolve<IMapManager>();
|
||||
var pauseMgr = IoCManager.Resolve<IPauseManager>();
|
||||
|
||||
if (!mapMgr.MapExists(mapId))
|
||||
{
|
||||
mapMgr.CreateMap(mapId);
|
||||
if (args.Length >= 2 && args[1] == "false")
|
||||
{
|
||||
mapMgr.AddUninitializedMap(mapId);
|
||||
pauseMgr.AddUninitializedMap(mapId);
|
||||
}
|
||||
|
||||
shell.WriteLine($"Map with ID {mapId} created.");
|
||||
@@ -218,11 +219,7 @@ namespace Robust.Server.Console.Commands
|
||||
}
|
||||
|
||||
IoCManager.Resolve<IMapLoader>().LoadMap(mapId, args[1]);
|
||||
|
||||
if (mapManager.MapExists(mapId))
|
||||
shell.WriteLine($"Map {mapId} has been loaded from {args[1]}.");
|
||||
else
|
||||
shell.WriteError($"Error while loading map from {args[1]}.");
|
||||
shell.WriteLine($"Map {mapId} has been loaded from {args[1]}.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,6 +318,7 @@ namespace Robust.Server.Console.Commands
|
||||
}
|
||||
|
||||
var mapManager = IoCManager.Resolve<IMapManager>();
|
||||
var pauseManager = IoCManager.Resolve<IPauseManager>();
|
||||
|
||||
var arg = args[0];
|
||||
var mapId = new MapId(int.Parse(arg, CultureInfo.InvariantCulture));
|
||||
@@ -331,13 +329,13 @@ namespace Robust.Server.Console.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
if (mapManager.IsMapInitialized(mapId))
|
||||
if (pauseManager.IsMapInitialized(mapId))
|
||||
{
|
||||
shell.WriteError("Map is already initialized!");
|
||||
return;
|
||||
}
|
||||
|
||||
mapManager.DoMapInitialize(mapId);
|
||||
pauseManager.DoMapInitialize(mapId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,16 +348,17 @@ namespace Robust.Server.Console.Commands
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var mapManager = IoCManager.Resolve<IMapManager>();
|
||||
var pauseManager = IoCManager.Resolve<IPauseManager>();
|
||||
|
||||
var msg = new StringBuilder();
|
||||
|
||||
foreach (var mapId in mapManager.GetAllMapIds().OrderBy(id => id.Value))
|
||||
{
|
||||
msg.AppendFormat("{0}: init: {1}, paused: {2}, ent: {3}, grids: {4}\n",
|
||||
mapId, mapManager.IsMapInitialized(mapId),
|
||||
mapManager.IsMapPaused(mapId),
|
||||
mapManager.GetMapEntityId(mapId),
|
||||
string.Join(",", mapManager.GetAllMapGrids(mapId).Select(grid => grid.Index)));
|
||||
mapId, pauseManager.IsMapInitialized(mapId),
|
||||
pauseManager.IsMapPaused(mapId),
|
||||
string.Join(",", mapManager.GetAllMapGrids(mapId).Select(grid => grid.Index)),
|
||||
mapManager.GetMapEntityId(mapId));
|
||||
}
|
||||
|
||||
shell.WriteLine(msg.ToString());
|
||||
@@ -381,7 +380,7 @@ namespace Robust.Server.Console.Commands
|
||||
foreach (var grid in mapManager.GetAllGrids().OrderBy(grid => grid.Index.Value))
|
||||
{
|
||||
msg.AppendFormat("{0}: map: {1}, ent: {2}, pos: {3} \n",
|
||||
grid.Index, grid.ParentMapId, grid.GridEntityId, grid.WorldPosition);
|
||||
grid.Index, grid.ParentMapId, grid.WorldPosition, grid.GridEntityId);
|
||||
}
|
||||
|
||||
shell.WriteLine(msg.ToString());
|
||||
|
||||
@@ -109,8 +109,9 @@ namespace Robust.Server.Console.Commands
|
||||
private void SetupPlayer(MapId mapId, IConsoleShell shell, IPlayerSession? player, IMapManager mapManager)
|
||||
{
|
||||
if (mapId == MapId.Nullspace) return;
|
||||
mapManager.SetMapPaused(mapId, false);
|
||||
var mapUid = mapManager.GetMapEntityIdOrThrow(mapId);
|
||||
var pauseManager = IoCManager.Resolve<IPauseManager>();
|
||||
pauseManager.SetMapPaused(mapId, false);
|
||||
var mapUid = IoCManager.Resolve<IMapManager>().GetMapEntityIdOrThrow(mapId);
|
||||
IoCManager.Resolve<IEntityManager>().GetComponent<SharedPhysicsMapComponent>(mapUid).Gravity = new Vector2(0, -9.8f);
|
||||
|
||||
return;
|
||||
|
||||
@@ -3,7 +3,7 @@ using Robust.Shared.GameStates;
|
||||
|
||||
namespace Robust.Server.GameObjects;
|
||||
|
||||
public sealed class AppearanceSystem : SharedAppearanceSystem
|
||||
internal sealed class AppearanceSystem : SharedAppearanceSystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
|
||||
@@ -5,31 +5,30 @@ namespace Robust.Server.GameStates;
|
||||
|
||||
public struct ChunkIndicesEnumerator
|
||||
{
|
||||
private Vector2i _bottomLeft;
|
||||
private Vector2i _topRight;
|
||||
private Vector2i _topLeft;
|
||||
private Vector2i _bottomRight;
|
||||
|
||||
private int _x;
|
||||
private int _y;
|
||||
|
||||
public ChunkIndicesEnumerator(Vector2 viewPos, float range, float chunkSize)
|
||||
public ChunkIndicesEnumerator(Box2 viewBox, float chunkSize)
|
||||
{
|
||||
_bottomLeft = ((viewPos - range) / chunkSize).Floored();
|
||||
// Also floor this as we get the whole chunk anyway.
|
||||
_topRight = ((viewPos + range) / chunkSize).Floored();
|
||||
_topLeft = (viewBox.TopLeft / chunkSize).Floored();
|
||||
_bottomRight = (viewBox.BottomRight / chunkSize).Floored();
|
||||
|
||||
_x = _bottomLeft.X;
|
||||
_y = _bottomLeft.Y;
|
||||
_x = _topLeft.X;
|
||||
_y = _bottomRight.Y;
|
||||
}
|
||||
|
||||
public bool MoveNext([NotNullWhen(true)] out Vector2i? chunkIndices)
|
||||
{
|
||||
if (_y > _topRight.Y)
|
||||
if (_y > _topLeft.Y)
|
||||
{
|
||||
_x++;
|
||||
_y = _bottomLeft.Y;
|
||||
_y = _bottomRight.Y;
|
||||
}
|
||||
|
||||
if (_x > _topRight.X)
|
||||
if (_x > _bottomRight.X)
|
||||
{
|
||||
chunkIndices = null;
|
||||
return false;
|
||||
|
||||
@@ -37,6 +37,7 @@ public interface IPVSCollection
|
||||
public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : IComparable<TIndex>, IEquatable<TIndex>
|
||||
{
|
||||
[Shared.IoC.Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Shared.IoC.Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static Vector2i GetChunkIndices(Vector2 coordinates)
|
||||
@@ -80,14 +81,14 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
private readonly List<(GameTick tick, TIndex index)> _deletionHistory = new();
|
||||
|
||||
/// <summary>
|
||||
/// An index containing the <see cref="IIndexLocation"/>s of all <see cref="TIndex"/>.
|
||||
/// An index containing the <see cref="IndexLocation"/>s of all <see cref="TIndex"/>.
|
||||
/// </summary>
|
||||
private readonly Dictionary<TIndex, IIndexLocation> _indexLocations = new();
|
||||
private readonly Dictionary<TIndex, IndexLocation> _indexLocations = new();
|
||||
|
||||
/// <summary>
|
||||
/// Buffer of all locationchanges since the last process call
|
||||
/// </summary>
|
||||
private readonly Dictionary<TIndex, IIndexLocation> _locationChangeBuffer = new();
|
||||
private readonly Dictionary<TIndex, IndexLocation> _locationChangeBuffer = new();
|
||||
/// <summary>
|
||||
/// Buffer of all indexremovals since the last process call
|
||||
/// </summary>
|
||||
@@ -102,7 +103,7 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
{
|
||||
var changedIndices = new HashSet<TIndex>(_locationChangeBuffer.Keys);
|
||||
|
||||
var changedChunkLocations = new HashSet<IIndexLocation>();
|
||||
var changedChunkLocations = new HashSet<IndexLocation>();
|
||||
foreach (var (index, tick) in _removalBuffer)
|
||||
{
|
||||
//changes dont need to be computed if we are removing the index anyways
|
||||
@@ -153,7 +154,7 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
|
||||
public HashSet<TIndex>.Enumerator GetElementsForSession(ICommonSession session) => _localOverrides[session].GetEnumerator();
|
||||
|
||||
private void AddIndexInternal(TIndex index, IIndexLocation location)
|
||||
private void AddIndexInternal(TIndex index, IndexLocation location)
|
||||
{
|
||||
switch (location)
|
||||
{
|
||||
@@ -185,7 +186,7 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
_indexLocations.Add(index, location);
|
||||
}
|
||||
|
||||
private IIndexLocation? RemoveIndexInternal(TIndex index)
|
||||
private IndexLocation? RemoveIndexInternal(TIndex index)
|
||||
{
|
||||
// the index might be gone due to disconnects/grid-/map-deletions
|
||||
if (!_indexLocations.TryGetValue(index, out var location))
|
||||
@@ -351,14 +352,14 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
var gridId = coordinates.GetGridId(_entityManager);
|
||||
if (gridId != GridId.Invalid)
|
||||
{
|
||||
var gridIndices = GetChunkIndices(coordinates.Position);
|
||||
var gridIndices = GetChunkIndices(_mapManager.GetGrid(gridId).LocalToGrid(coordinates));
|
||||
UpdateIndex(index, gridId, gridIndices, true); //skip overridecheck bc we already did it (saves some dict lookups)
|
||||
return;
|
||||
}
|
||||
|
||||
var mapCoordinates = coordinates.ToMap(_entityManager);
|
||||
var mapIndices = GetChunkIndices(coordinates.Position);
|
||||
UpdateIndex(index, mapCoordinates.MapId, mapIndices, true); //skip overridecheck bc we already did it (saves some dict lookups)
|
||||
var mapId = coordinates.GetMapId(_entityManager);
|
||||
var mapIndices = GetChunkIndices(coordinates.ToMapPos(_entityManager));
|
||||
UpdateIndex(index, mapId, mapIndices, true); //skip overridecheck bc we already did it (saves some dict lookups)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -391,7 +392,7 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
RegisterUpdate(index, new MapChunkLocation(mapId, chunkIndices));
|
||||
}
|
||||
|
||||
private void RegisterUpdate(TIndex index, IIndexLocation location)
|
||||
private void RegisterUpdate(TIndex index, IndexLocation location)
|
||||
{
|
||||
if(_indexLocations.TryGetValue(index, out var oldLocation) && oldLocation == location) return;
|
||||
|
||||
@@ -399,78 +400,14 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IndexLocations
|
||||
|
||||
private abstract record IndexLocation;
|
||||
private record MapChunkLocation(MapId MapId, Vector2i ChunkIndices) : IndexLocation;
|
||||
private record GridChunkLocation(GridId GridId, Vector2i ChunkIndices) : IndexLocation;
|
||||
private record GlobalOverride : IndexLocation;
|
||||
private record LocalOverride(ICommonSession Session) : IndexLocation;
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#region IndexLocations
|
||||
|
||||
public interface IIndexLocation {};
|
||||
|
||||
public interface IChunkIndexLocation{ };
|
||||
|
||||
public struct MapChunkLocation : IIndexLocation, IChunkIndexLocation, IEquatable<MapChunkLocation>
|
||||
{
|
||||
public MapChunkLocation(MapId mapId, Vector2i chunkIndices)
|
||||
{
|
||||
MapId = mapId;
|
||||
ChunkIndices = chunkIndices;
|
||||
}
|
||||
|
||||
public MapId MapId { get; init; }
|
||||
public Vector2i ChunkIndices { get; init; }
|
||||
|
||||
public bool Equals(MapChunkLocation other)
|
||||
{
|
||||
return MapId.Equals(other.MapId) && ChunkIndices.Equals(other.ChunkIndices);
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is MapChunkLocation other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(MapId, ChunkIndices);
|
||||
}
|
||||
}
|
||||
|
||||
public struct GridChunkLocation : IIndexLocation, IChunkIndexLocation, IEquatable<GridChunkLocation>
|
||||
{
|
||||
public GridChunkLocation(GridId gridId, Vector2i chunkIndices)
|
||||
{
|
||||
GridId = gridId;
|
||||
ChunkIndices = chunkIndices;
|
||||
}
|
||||
|
||||
public GridId GridId { get; init; }
|
||||
public Vector2i ChunkIndices { get; init; }
|
||||
|
||||
public bool Equals(GridChunkLocation other)
|
||||
{
|
||||
return GridId.Equals(other.GridId) && ChunkIndices.Equals(other.ChunkIndices);
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is GridChunkLocation other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(GridId, ChunkIndices);
|
||||
}
|
||||
}
|
||||
|
||||
public struct GlobalOverride : IIndexLocation { }
|
||||
|
||||
public struct LocalOverride : IIndexLocation
|
||||
{
|
||||
public LocalOverride(ICommonSession session)
|
||||
{
|
||||
Session = session;
|
||||
}
|
||||
|
||||
public ICommonSession Session { get; init; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Composition;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using NetSerializer;
|
||||
@@ -25,9 +23,7 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
[Shared.IoC.Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Shared.IoC.Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Shared.IoC.Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
[Shared.IoC.Dependency] private readonly IServerEntityManager _serverEntManager = default!;
|
||||
[Shared.IoC.Dependency] private readonly IServerGameStateManager _stateManager = default!;
|
||||
[Shared.IoC.Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
public const float ChunkSize = 8;
|
||||
|
||||
@@ -39,7 +35,7 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
/// <summary>
|
||||
/// Is view culling enabled, or will we send the whole map?
|
||||
/// </summary>
|
||||
public bool CullingEnabled { get; private set; }
|
||||
private bool _cullingEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// How many new entities we can send per tick (dont wanna nuke the clients mailbox).
|
||||
@@ -56,41 +52,21 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
/// </summary>
|
||||
private float _viewSize;
|
||||
|
||||
/// <summary>
|
||||
/// If PVS disabled then we'll track if we've dumped all entities on the player.
|
||||
/// This way any future ticks can be orders of magnitude faster as we only send what changes.
|
||||
/// </summary>
|
||||
public HashSet<ICommonSession> SeenAllEnts = new();
|
||||
|
||||
/// <summary>
|
||||
/// All <see cref="Robust.Shared.GameObjects.EntityUid"/>s a <see cref="ICommonSession"/> saw last iteration.
|
||||
/// </summary>
|
||||
private readonly Dictionary<ICommonSession, Dictionary<EntityUid, PVSEntityVisiblity>> _playerVisibleSets = new();
|
||||
/// <summary>
|
||||
/// All <see cref="Robust.Shared.GameObjects.EntityUid"/>s a <see cref="ICommonSession"/> saw along its entire connection.
|
||||
/// </summary>
|
||||
private readonly Dictionary<ICommonSession, HashSet<EntityUid>> _playerSeenSets = new();
|
||||
|
||||
private PVSCollection<EntityUid> _entityPvsCollection = default!;
|
||||
public PVSCollection<EntityUid> EntityPVSCollection => _entityPvsCollection;
|
||||
private readonly List<IPVSCollection> _pvsCollections = new();
|
||||
|
||||
private readonly ObjectPool<Dictionary<EntityUid, PVSEntityVisiblity>> _visSetPool
|
||||
= new DefaultObjectPool<Dictionary<EntityUid, PVSEntityVisiblity>>(
|
||||
new DictPolicy<EntityUid, PVSEntityVisiblity>(), MaxVisPoolSize);
|
||||
|
||||
private readonly ObjectPool<HashSet<EntityUid>> _uidSetPool
|
||||
= new DefaultObjectPool<HashSet<EntityUid>>(new SetPolicy<EntityUid>(), MaxVisPoolSize);
|
||||
|
||||
private readonly ObjectPool<Dictionary<EntityUid, MetaDataComponent>> _chunkCachePool =
|
||||
new DefaultObjectPool<Dictionary<EntityUid, MetaDataComponent>>(
|
||||
new DictPolicy<EntityUid, MetaDataComponent>(), MaxVisPoolSize);
|
||||
|
||||
private readonly ObjectPool<HashSet<int>> _playerChunkPool =
|
||||
new DefaultObjectPool<HashSet<int>>(new SetPolicy<int>(), MaxVisPoolSize);
|
||||
|
||||
private readonly ObjectPool<RobustTree<EntityUid>> _treePool =
|
||||
new DefaultObjectPool<RobustTree<EntityUid>>(new TreePolicy<EntityUid>());
|
||||
private readonly ObjectPool<Dictionary<EntityUid, PVSEntityVisiblity>> _visSetPool =
|
||||
new DefaultObjectPool<Dictionary<EntityUid, PVSEntityVisiblity>>(
|
||||
new DefaultPooledObjectPolicy<Dictionary<EntityUid, PVSEntityVisiblity>>(), MaxVisPoolSize);
|
||||
private readonly ObjectPool<HashSet<EntityUid>> _viewerEntsPool
|
||||
= new DefaultObjectPool<HashSet<EntityUid>>(new DefaultPooledObjectPolicy<HashSet<EntityUid>>(), MaxVisPoolSize);
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -103,7 +79,7 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
_mapManager.OnGridRemoved += OnGridRemoved;
|
||||
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||
SubscribeLocalEvent<MoveEvent>(OnEntityMove);
|
||||
SubscribeLocalEvent<TransformComponent, ComponentStartup>(OnTransformStartup);
|
||||
SubscribeLocalEvent<TransformComponent, ComponentInit>(OnTransformInit);
|
||||
EntityManager.EntityDeleted += OnEntityDeleted;
|
||||
|
||||
_configManager.OnValueChanged(CVars.NetPVS, SetPvs, true);
|
||||
@@ -145,7 +121,7 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
|
||||
private void SetPvs(bool value)
|
||||
{
|
||||
CullingEnabled = value;
|
||||
_cullingEnabled = value;
|
||||
}
|
||||
|
||||
private void OnNewEntityBudgetChanged(int obj)
|
||||
@@ -163,21 +139,7 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
|
||||
public void Cleanup(IEnumerable<IPlayerSession> sessions)
|
||||
{
|
||||
var playerSessions = sessions.ToArray();
|
||||
|
||||
if (!CullingEnabled)
|
||||
{
|
||||
foreach (var player in playerSessions)
|
||||
{
|
||||
SeenAllEnts.Add(player);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SeenAllEnts.Clear();
|
||||
}
|
||||
|
||||
CleanupDirty(playerSessions);
|
||||
CleanupDirty(sessions);
|
||||
}
|
||||
|
||||
public void CullDeletionHistory(GameTick oldestAck)
|
||||
@@ -208,36 +170,29 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
|
||||
private void OnEntityMove(ref MoveEvent ev)
|
||||
{
|
||||
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
var coordinates = _transform.GetMoverCoordinates(ev.Component);
|
||||
UpdateEntityRecursive(ev.Sender, ev.Component, coordinates, xformQuery, false);
|
||||
UpdateEntityRecursive(ev.Sender, ev.Component);
|
||||
}
|
||||
|
||||
private void OnTransformStartup(EntityUid uid, TransformComponent component, ComponentStartup args)
|
||||
private void OnTransformInit(EntityUid uid, TransformComponent component, ComponentInit args)
|
||||
{
|
||||
// use Startup because GridId is not set during the eventbus init yet!
|
||||
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
var coordinates = _transform.GetMoverCoordinates(component);
|
||||
UpdateEntityRecursive(uid, component, coordinates, xformQuery, false);
|
||||
UpdateEntityRecursive(uid, component);
|
||||
}
|
||||
|
||||
private void UpdateEntityRecursive(EntityUid uid, TransformComponent xform, EntityCoordinates coordinates, EntityQuery<TransformComponent> xformQuery, bool mover)
|
||||
private void UpdateEntityRecursive(EntityUid uid, TransformComponent? transformComponent = null)
|
||||
{
|
||||
if (mover && !xform.LocalPosition.Equals(Vector2.Zero))
|
||||
{
|
||||
coordinates = _transform.GetMoverCoordinates(xform);
|
||||
}
|
||||
if(!Resolve(uid, ref transformComponent))
|
||||
return;
|
||||
|
||||
_entityPvsCollection.UpdateIndex(uid, coordinates);
|
||||
_entityPvsCollection.UpdateIndex(uid, transformComponent.Coordinates);
|
||||
|
||||
// since elements are cached grid-/map-relative, we dont need to update a given grids/maps children
|
||||
if(_mapManager.IsGrid(uid) || _mapManager.IsMap(uid)) return;
|
||||
|
||||
var children = xform.ChildEnumerator;
|
||||
var children = transformComponent.ChildEnumerator;
|
||||
|
||||
while (children.MoveNext(out var child))
|
||||
{
|
||||
UpdateEntityRecursive(child.Value, xformQuery.GetComponent(child.Value), coordinates, xformQuery, true);
|
||||
UpdateEntityRecursive(child.Value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,9 +209,8 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
}
|
||||
else if (e.NewStatus == SessionStatus.Disconnected)
|
||||
{
|
||||
var playerVisSet = _playerVisibleSets[e.Session];
|
||||
_visSetPool.Return(_playerVisibleSets[e.Session]);
|
||||
_playerVisibleSets.Remove(e.Session);
|
||||
_visSetPool.Return(playerVisSet);
|
||||
_playerSeenSets.Remove(e.Session);
|
||||
foreach (var pvsCollection in _pvsCollections)
|
||||
{
|
||||
@@ -306,219 +260,114 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
|
||||
#endregion
|
||||
|
||||
public (List<(uint, IChunkIndexLocation)> , HashSet<int>[], EntityUid[][] viewers) GetChunks(IPlayerSession[] sessions)
|
||||
{
|
||||
var chunkList = new List<(uint, IChunkIndexLocation)>();
|
||||
var playerChunks = new HashSet<int>[sessions.Length];
|
||||
var eyeQuery = EntityManager.GetEntityQuery<EyeComponent>();
|
||||
var transformQuery = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
var viewerEntities = new EntityUid[sessions.Length][];
|
||||
|
||||
// Keep track of the index of each chunk we use for a faster index lookup.
|
||||
var mapIndices = new Dictionary<uint, Dictionary<MapChunkLocation, int>>(4);
|
||||
var gridIndices = new Dictionary<uint, Dictionary<GridChunkLocation, int>>(4);
|
||||
|
||||
for (int i = 0; i < sessions.Length; i++)
|
||||
{
|
||||
var session = sessions[i];
|
||||
playerChunks[i] = _playerChunkPool.Get();
|
||||
|
||||
var viewers = GetSessionViewers(session);
|
||||
viewerEntities[i] = new EntityUid[viewers.Count];
|
||||
viewers.CopyTo(viewerEntities[i]);
|
||||
|
||||
foreach (var eyeEuid in viewers)
|
||||
{
|
||||
var (viewPos, range, mapId) = CalcViewBounds(in eyeEuid, transformQuery);
|
||||
|
||||
uint visMask = EyeComponent.DefaultVisibilityMask;
|
||||
if (eyeQuery.TryGetComponent(eyeEuid, out var eyeComp))
|
||||
visMask = eyeComp.VisibilityMask;
|
||||
|
||||
// Get the nyoom dictionary for index lookups.
|
||||
if (!mapIndices.TryGetValue(visMask, out var mapDict))
|
||||
{
|
||||
mapDict = new Dictionary<MapChunkLocation, int>(32);
|
||||
mapIndices[visMask] = mapDict;
|
||||
}
|
||||
|
||||
var mapChunkEnumerator = new ChunkIndicesEnumerator(viewPos, range, ChunkSize);
|
||||
|
||||
while (mapChunkEnumerator.MoveNext(out var chunkIndices))
|
||||
{
|
||||
var chunkLocation = new MapChunkLocation(mapId, chunkIndices.Value);
|
||||
var entry = (visMask, chunkLocation);
|
||||
|
||||
if (mapDict.TryGetValue(chunkLocation, out var indexOf))
|
||||
{
|
||||
playerChunks[i].Add(indexOf);
|
||||
}
|
||||
else
|
||||
{
|
||||
playerChunks[i].Add(chunkList.Count);
|
||||
mapDict.Add(chunkLocation, chunkList.Count);
|
||||
chunkList.Add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the nyoom dictionary for index lookups.
|
||||
if (!gridIndices.TryGetValue(visMask, out var gridDict))
|
||||
{
|
||||
gridDict = new Dictionary<GridChunkLocation, int>(32);
|
||||
gridIndices[visMask] = gridDict;
|
||||
}
|
||||
|
||||
_mapManager.FindGridsIntersectingEnumerator(mapId, new Box2(viewPos - range, viewPos + range), out var gridEnumerator, true);
|
||||
while (gridEnumerator.MoveNext(out var mapGrid))
|
||||
{
|
||||
var localPos = transformQuery.GetComponent(mapGrid.GridEntityId).InvWorldMatrix.Transform(viewPos);
|
||||
|
||||
var gridChunkEnumerator =
|
||||
new ChunkIndicesEnumerator(localPos, range, ChunkSize);
|
||||
|
||||
while (gridChunkEnumerator.MoveNext(out var gridChunkIndices))
|
||||
{
|
||||
var chunkLocation = new GridChunkLocation(mapGrid.Index, gridChunkIndices.Value);
|
||||
var entry = (visMask, chunkLocation);
|
||||
|
||||
if (gridDict.TryGetValue(chunkLocation, out var indexOf))
|
||||
{
|
||||
playerChunks[i].Add(indexOf);
|
||||
}
|
||||
else
|
||||
{
|
||||
playerChunks[i].Add(chunkList.Count);
|
||||
gridDict.Add(chunkLocation, chunkList.Count);
|
||||
chunkList.Add(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_uidSetPool.Return(viewers);
|
||||
}
|
||||
|
||||
return (chunkList, playerChunks, viewerEntities);
|
||||
}
|
||||
|
||||
public (Dictionary<EntityUid, MetaDataComponent> mData, RobustTree<EntityUid> tree)? CalculateChunk(IChunkIndexLocation chunkLocation, uint visMask, EntityQuery<TransformComponent> transform, EntityQuery<MetaDataComponent> metadata)
|
||||
{
|
||||
var chunk = chunkLocation switch
|
||||
{
|
||||
GridChunkLocation gridChunkLocation => _entityPvsCollection.TryGetChunk(gridChunkLocation.GridId,
|
||||
gridChunkLocation.ChunkIndices, out var gridChunk)
|
||||
? gridChunk
|
||||
: null,
|
||||
MapChunkLocation mapChunkLocation => _entityPvsCollection.TryGetChunk(mapChunkLocation.MapId,
|
||||
mapChunkLocation.ChunkIndices, out var mapChunk)
|
||||
? mapChunk
|
||||
: null
|
||||
};
|
||||
if (chunk == null) return null;
|
||||
var chunkSet = _chunkCachePool.Get();
|
||||
var tree = _treePool.Get();
|
||||
foreach (var uid in chunk)
|
||||
{
|
||||
AddToChunkSetRecursively(in uid, visMask, tree, chunkSet, transform, metadata);
|
||||
}
|
||||
|
||||
return (chunkSet, tree);
|
||||
}
|
||||
|
||||
public void ReturnToPool((Dictionary<EntityUid, MetaDataComponent> metadata, RobustTree<EntityUid> tree)?[] chunkCache, HashSet<int>[] playerChunks)
|
||||
{
|
||||
foreach (var chunk in chunkCache)
|
||||
{
|
||||
if(!chunk.HasValue) continue;
|
||||
_chunkCachePool.Return(chunk.Value.metadata);
|
||||
_treePool.Return(chunk.Value.tree);
|
||||
}
|
||||
|
||||
foreach (var playerChunk in playerChunks)
|
||||
{
|
||||
_playerChunkPool.Return(playerChunk);
|
||||
}
|
||||
}
|
||||
|
||||
private bool AddToChunkSetRecursively(in EntityUid uid, uint visMask, RobustTree<EntityUid> tree, Dictionary<EntityUid, MetaDataComponent> set, EntityQuery<TransformComponent> transform,
|
||||
EntityQuery<MetaDataComponent> metadata)
|
||||
{
|
||||
//are we valid?
|
||||
//sometimes uids gets added without being valid YET (looking at you mapmanager) (mapcreate & gridcreated fire before the uids becomes valid)
|
||||
if (!uid.IsValid()) return false;
|
||||
|
||||
if (set.ContainsKey(uid)) return true;
|
||||
|
||||
var mComp = metadata.GetComponent(uid);
|
||||
|
||||
// TODO: Don't need to know about parents so no longer need to use bool for this method.
|
||||
// If the eye is missing ANY layer this entity or any of its parents belongs to, it is considered invisible.
|
||||
if ((visMask & mComp.VisibilityMask) != mComp.VisibilityMask)
|
||||
return false;
|
||||
|
||||
var parent = transform.GetComponent(uid).ParentUid;
|
||||
|
||||
if (parent.IsValid() && //is it not a worldentity?
|
||||
!set.ContainsKey(parent) && //was the parent not yet added to toSend?
|
||||
!AddToChunkSetRecursively(in parent, visMask, tree, set, transform, metadata)) //did we just fail to add the parent?
|
||||
return false; //we failed? suppose we dont get added either
|
||||
|
||||
//todo paul i want it to crash here if it gets added double bc that shouldnt happen and will add alot of unneeded cycles, make this a simpl assignment at some point maybe idk
|
||||
tree.Set(uid, parent);
|
||||
set.Add(uid, mComp);
|
||||
return true;
|
||||
}
|
||||
|
||||
public (List<EntityState>? updates, List<EntityUid>? deletions) CalculateEntityStates(IPlayerSession session,
|
||||
GameTick fromTick, GameTick toTick,
|
||||
(Dictionary<EntityUid, MetaDataComponent> metadata, RobustTree<EntityUid> tree)?[] chunkCache,
|
||||
HashSet<int> chunkIndices, EntityQuery<MetaDataComponent> mQuery, EntityQuery<TransformComponent> tQuery,
|
||||
EntityUid[] viewerEntities)
|
||||
public (List<EntityState>? updates, List<EntityUid>? deletions) CalculateEntityStates(ICommonSession session,
|
||||
GameTick fromTick, GameTick toTick)
|
||||
{
|
||||
DebugTools.Assert(session.Status == SessionStatus.InGame);
|
||||
var newEntitiesSent = 0;
|
||||
var entitiesSent = 0;
|
||||
|
||||
var deletions = _entityPvsCollection.GetDeletedIndices(fromTick);
|
||||
if (!_cullingEnabled)
|
||||
{
|
||||
var allStates = GetAllEntityStates(session, fromTick, toTick);
|
||||
return (allStates, deletions);
|
||||
}
|
||||
|
||||
var playerVisibleSet = _playerVisibleSets[session];
|
||||
var visibleEnts = _visSetPool.Get();
|
||||
var seenSet = _playerSeenSets[session];
|
||||
var deletions = _entityPvsCollection.GetDeletedIndices(fromTick);
|
||||
|
||||
foreach (var i in chunkIndices)
|
||||
visibleEnts.Clear();
|
||||
|
||||
var eyeQuery = EntityManager.GetEntityQuery<EyeComponent>();
|
||||
var transformQuery = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
var metadataQuery = EntityManager.GetEntityQuery<MetaDataComponent>();
|
||||
|
||||
var globalOverridesEnumerator = _entityPvsCollection.GlobalOverridesEnumerator;
|
||||
while(globalOverridesEnumerator.MoveNext())
|
||||
{
|
||||
var cache = chunkCache[i];
|
||||
if(!cache.HasValue) continue;
|
||||
var rootNodes = cache.Value.tree.GetRootNodes();
|
||||
foreach (var rootNode in rootNodes)
|
||||
var uid = globalOverridesEnumerator.Current;
|
||||
//todo paul reenable budgetcheck here once you fix mapmanager
|
||||
TryAddToVisibleEnts(
|
||||
in uid,
|
||||
seenSet,
|
||||
playerVisibleSet,
|
||||
visibleEnts,
|
||||
fromTick,
|
||||
ref newEntitiesSent,
|
||||
ref entitiesSent,
|
||||
metadataQuery,
|
||||
transformQuery,
|
||||
dontSkip: true);
|
||||
}
|
||||
globalOverridesEnumerator.Dispose();
|
||||
|
||||
var localOverridesEnumerator = _entityPvsCollection.GetElementsForSession(session);
|
||||
while (localOverridesEnumerator.MoveNext())
|
||||
{
|
||||
var uid = localOverridesEnumerator.Current;
|
||||
//todo paul reenable budgetcheck here once you fix mapmanager
|
||||
TryAddToVisibleEnts(in uid, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent, ref entitiesSent, metadataQuery, transformQuery, dontSkip: true);
|
||||
}
|
||||
localOverridesEnumerator.Dispose();
|
||||
|
||||
var expandEvent = new ExpandPvsEvent((IPlayerSession) session, new List<EntityUid>());
|
||||
RaiseLocalEvent(ref expandEvent);
|
||||
foreach (var entityUid in expandEvent.Entities)
|
||||
{
|
||||
TryAddToVisibleEnts(in entityUid, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent, ref entitiesSent, metadataQuery, transformQuery);
|
||||
}
|
||||
|
||||
var viewers = GetSessionViewers(session);
|
||||
|
||||
foreach (var eyeEuid in viewers)
|
||||
{
|
||||
var (viewBox, mapId) = CalcViewBounds(in eyeEuid, transformQuery);
|
||||
|
||||
uint visMask = EyeComponent.DefaultVisibilityMask;
|
||||
if (eyeQuery.TryGetComponent(eyeEuid, out var eyeComp))
|
||||
visMask = eyeComp.VisibilityMask;
|
||||
|
||||
//todo at some point just register the viewerentities as localoverrides
|
||||
TryAddToVisibleEnts(in eyeEuid, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent, ref entitiesSent, metadataQuery, transformQuery, visMask, dontSkip: true);
|
||||
|
||||
var mapChunkEnumerator = new ChunkIndicesEnumerator(viewBox, ChunkSize);
|
||||
|
||||
while (mapChunkEnumerator.MoveNext(out var chunkIndices))
|
||||
{
|
||||
RecursivelyAddTreeNode(in rootNode, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent,
|
||||
ref entitiesSent, cache.Value.metadata);
|
||||
if(_entityPvsCollection.TryGetChunk(mapId, chunkIndices.Value, out var chunk))
|
||||
{
|
||||
foreach (var index in chunk)
|
||||
{
|
||||
TryAddToVisibleEnts(in index, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent, ref entitiesSent, metadataQuery, transformQuery, visMask);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_mapManager.FindGridsIntersectingEnumerator(mapId, viewBox, out var gridEnumerator, true);
|
||||
while (gridEnumerator.MoveNext(out var mapGrid))
|
||||
{
|
||||
var gridXform = transformQuery.GetComponent(mapGrid.GridEntityId);
|
||||
|
||||
var gridChunkEnumerator =
|
||||
new ChunkIndicesEnumerator(gridXform.InvWorldMatrix.TransformBox(viewBox), ChunkSize);
|
||||
|
||||
while (gridChunkEnumerator.MoveNext(out var gridChunkIndices))
|
||||
{
|
||||
if (_entityPvsCollection.TryGetChunk(mapGrid.Index, gridChunkIndices.Value, out var chunk))
|
||||
{
|
||||
foreach (var index in chunk)
|
||||
{
|
||||
TryAddToVisibleEnts(in index, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent, ref entitiesSent, metadataQuery, transformQuery, visMask);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cache.Value.tree.ReturnRootNodes(rootNodes);
|
||||
}
|
||||
|
||||
var globalEnumerator = _entityPvsCollection.GlobalOverridesEnumerator;
|
||||
while (globalEnumerator.MoveNext())
|
||||
{
|
||||
var uid = globalEnumerator.Current;
|
||||
RecursivelyAddOverride(in uid, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent,
|
||||
ref entitiesSent, mQuery, tQuery);
|
||||
}
|
||||
globalEnumerator.Dispose();
|
||||
|
||||
var localEnumerator = _entityPvsCollection.GetElementsForSession(session);
|
||||
while (localEnumerator.MoveNext())
|
||||
{
|
||||
var uid = localEnumerator.Current;
|
||||
RecursivelyAddOverride(in uid, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent,
|
||||
ref entitiesSent, mQuery, tQuery);
|
||||
}
|
||||
localEnumerator.Dispose();
|
||||
|
||||
foreach (var viewerEntity in viewerEntities)
|
||||
{
|
||||
RecursivelyAddOverride(in viewerEntity, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent,
|
||||
ref entitiesSent, mQuery, tQuery);
|
||||
}
|
||||
viewers.Clear();
|
||||
_viewerEntsPool.Return(viewers);
|
||||
|
||||
var entityStates = new List<EntityState>();
|
||||
|
||||
@@ -528,7 +377,8 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
continue;
|
||||
|
||||
var @new = visiblity == PVSEntityVisiblity.Entered;
|
||||
var state = GetEntityState(session, entityUid, @new ? GameTick.Zero : fromTick, mQuery.GetComponent(entityUid).Flags);
|
||||
|
||||
var state = GetEntityState(session, entityUid, @new ? GameTick.Zero : fromTick);
|
||||
|
||||
//this entity is not new & nothing changed
|
||||
if(!@new && state.Empty) continue;
|
||||
@@ -561,42 +411,7 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
private void RecursivelyAddTreeNode(
|
||||
in RobustTree<EntityUid>.TreeNode node,
|
||||
HashSet<EntityUid> seenSet,
|
||||
Dictionary<EntityUid, PVSEntityVisiblity> previousVisibleEnts,
|
||||
Dictionary<EntityUid, PVSEntityVisiblity> toSend,
|
||||
GameTick fromTick,
|
||||
ref int newEntitiesSent,
|
||||
ref int totalEnteredEntities,
|
||||
Dictionary<EntityUid, MetaDataComponent> metaDataCache)
|
||||
{
|
||||
//are we valid?
|
||||
//sometimes uids gets added without being valid YET (looking at you mapmanager) (mapcreate & gridcreated fire before the uids becomes valid)
|
||||
|
||||
// As every map is parented to uid 0 in the tree we still need to get their children, plus because we go top-down
|
||||
// we may find duplicate parents with children we haven't encountered before
|
||||
// on different chunks (this is especially common with direct grid children)
|
||||
if (node.Value.IsValid() && !toSend.ContainsKey(node.Value))
|
||||
{
|
||||
//are we new?
|
||||
var (entered, budgetFail) = ProcessEntry(in node.Value, seenSet, previousVisibleEnts, ref newEntitiesSent,
|
||||
ref totalEnteredEntities);
|
||||
|
||||
if (budgetFail) return;
|
||||
|
||||
AddToSendSet(in node.Value, metaDataCache[node.Value], toSend, fromTick, entered);
|
||||
}
|
||||
|
||||
//our children are important regardless! iterate them!
|
||||
foreach (var child in node.Children)
|
||||
{
|
||||
RecursivelyAddTreeNode(in child, seenSet, previousVisibleEnts, toSend, fromTick, ref newEntitiesSent,
|
||||
ref totalEnteredEntities, metaDataCache);
|
||||
}
|
||||
}
|
||||
|
||||
public bool RecursivelyAddOverride(
|
||||
private bool TryAddToVisibleEnts(
|
||||
in EntityUid uid,
|
||||
HashSet<EntityUid> seenSet,
|
||||
Dictionary<EntityUid, PVSEntityVisiblity> previousVisibleEnts,
|
||||
@@ -604,39 +419,50 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
GameTick fromTick,
|
||||
ref int newEntitiesSent,
|
||||
ref int totalEnteredEntities,
|
||||
EntityQuery<MetaDataComponent> metaQuery,
|
||||
EntityQuery<TransformComponent> transQuery)
|
||||
EntityQuery<MetaDataComponent> metadataQuery,
|
||||
EntityQuery<TransformComponent> transformQuery,
|
||||
uint? visMask = null,
|
||||
bool dontSkip = false,
|
||||
bool trustParent = false)
|
||||
{
|
||||
//are we valid?
|
||||
//are we valid yet?
|
||||
//sometimes uids gets added without being valid YET (looking at you mapmanager) (mapcreate & gridcreated fire before the uids becomes valid)
|
||||
if (!uid.IsValid()) return false;
|
||||
|
||||
//did we already get added?
|
||||
if (toSend.ContainsKey(uid)) return true;
|
||||
|
||||
var parent = transQuery.GetComponent(uid).ParentUid;
|
||||
if (parent.IsValid() && !RecursivelyAddOverride(in parent, seenSet, previousVisibleEnts, toSend, fromTick,
|
||||
ref newEntitiesSent, ref totalEnteredEntities, metaQuery, transQuery))
|
||||
return false;
|
||||
var metadata = metadataQuery.GetComponent(uid);
|
||||
|
||||
var (entered, _) = ProcessEntry(in uid, seenSet, previousVisibleEnts, ref newEntitiesSent, ref totalEnteredEntities);
|
||||
// if we are invisible, we are not going into the visSet, so don't worry about parents, and children are not going in
|
||||
if (visMask != null)
|
||||
{
|
||||
// TODO: Don't need to know about parents so no longer need to use bool for this method.
|
||||
|
||||
AddToSendSet(in uid, metaQuery.GetComponent(uid), toSend, fromTick, entered);
|
||||
return true;
|
||||
}
|
||||
// If the eye is missing ANY layer this entity or any of its parents belongs to, it is considered invisible.
|
||||
if ((visMask & metadata.VisibilityMask) != metadata.VisibilityMask)
|
||||
return false;
|
||||
}
|
||||
|
||||
private (bool entered, bool budgetFail) ProcessEntry(in EntityUid uid, HashSet<EntityUid> seenSet,
|
||||
Dictionary<EntityUid, PVSEntityVisiblity> previousVisibleEnts,
|
||||
ref int newEntitiesSent,
|
||||
ref int totalEnteredEntities)
|
||||
{
|
||||
var parent = transformQuery.GetComponent(uid).ParentUid;
|
||||
|
||||
if (!trustParent && //do we have it on good authority the parent exists?
|
||||
parent.IsValid() && //is it not a worldentity?
|
||||
!toSend.ContainsKey(parent) && //was the parent not yet added to toSend?
|
||||
!TryAddToVisibleEnts(in parent, seenSet, previousVisibleEnts, toSend, fromTick, ref newEntitiesSent, ref totalEnteredEntities, metadataQuery, transformQuery, visMask)) //did we just fail to add the parent?
|
||||
return false; //we failed? suppose we dont get added either
|
||||
|
||||
//did we already get added through the parent call?
|
||||
if (toSend.ContainsKey(uid)) return true;
|
||||
|
||||
//are we new?
|
||||
var @new = !seenSet.Contains(uid);
|
||||
var entered = @new | !previousVisibleEnts.Remove(uid);
|
||||
|
||||
if (entered)
|
||||
{
|
||||
if (totalEnteredEntities >= _entityBudget)
|
||||
return (entered, true);
|
||||
if (!dontSkip && totalEnteredEntities >= _entityBudget)
|
||||
return false;
|
||||
|
||||
totalEnteredEntities++;
|
||||
}
|
||||
@@ -644,110 +470,81 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
if (@new)
|
||||
{
|
||||
//we just entered pvs, do we still have enough budget to send us?
|
||||
if(newEntitiesSent >= _newEntityBudget)
|
||||
return (entered, true);
|
||||
if(!dontSkip && newEntitiesSent >= _newEntityBudget)
|
||||
return false;
|
||||
|
||||
newEntitiesSent++;
|
||||
seenSet.Add(uid);
|
||||
}
|
||||
|
||||
return (entered, false);
|
||||
}
|
||||
|
||||
private void AddToSendSet(in EntityUid uid, MetaDataComponent metaDataComponent, Dictionary<EntityUid, PVSEntityVisiblity> toSend, GameTick fromTick, bool entered)
|
||||
{
|
||||
if (entered)
|
||||
{
|
||||
toSend.Add(uid, PVSEntityVisiblity.Entered);
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (metaDataComponent.EntityLastModifiedTick < fromTick)
|
||||
if (metadata.EntityLastModifiedTick < fromTick)
|
||||
{
|
||||
//entity has been sent before and hasnt been updated since
|
||||
toSend.Add(uid, PVSEntityVisiblity.StayedUnchanged);
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
//add us
|
||||
toSend.Add(uid, PVSEntityVisiblity.StayedChanged);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all entity states that have been modified after and including the provided tick.
|
||||
/// </summary>
|
||||
public (List<EntityState>? updates, List<EntityUid>? deletions) GetAllEntityStates(ICommonSession player, GameTick fromTick, GameTick toTick)
|
||||
private List<EntityState>? GetAllEntityStates(ICommonSession player, GameTick fromTick, GameTick toTick)
|
||||
{
|
||||
var deletions = _entityPvsCollection.GetDeletedIndices(fromTick);
|
||||
// no point sending an empty collection
|
||||
if (deletions.Count == 0) deletions = default;
|
||||
List<EntityState> stateEntities;
|
||||
|
||||
var stateEntities = new List<EntityState>();
|
||||
stateEntities = new List<EntityState>();
|
||||
var seenEnts = new HashSet<EntityUid>();
|
||||
var slowPath = false;
|
||||
var metadataQuery = EntityManager.GetEntityQuery<MetaDataComponent>();
|
||||
|
||||
if (!SeenAllEnts.Contains(player))
|
||||
for (var i = fromTick.Value; i <= toTick.Value; i++)
|
||||
{
|
||||
// Give them E V E R Y T H I N G
|
||||
stateEntities = new List<EntityState>(EntityManager.EntityCount);
|
||||
|
||||
// This is the same as iterating every existing entity.
|
||||
foreach (var md in EntityManager.EntityQuery<MetaDataComponent>(true))
|
||||
var tick = new GameTick(i);
|
||||
if (!TryGetTick(tick, out var add, out var dirty))
|
||||
{
|
||||
slowPath = true;
|
||||
break;
|
||||
}
|
||||
|
||||
foreach (var uid in add)
|
||||
{
|
||||
if (!seenEnts.Add(uid)) continue;
|
||||
// This is essentially the same as IEntityManager.EntityExists, but returning MetaDataComponent.
|
||||
if (!metadataQuery.TryGetComponent(uid, out MetaDataComponent? md)) continue;
|
||||
|
||||
DebugTools.Assert(md.EntityLifeStage >= EntityLifeStage.Initialized);
|
||||
stateEntities.Add(GetEntityState(player, md.Owner, GameTick.Zero, md.Flags));
|
||||
|
||||
if (md.EntityLastModifiedTick >= fromTick)
|
||||
stateEntities.Add(GetEntityState(player, uid, GameTick.Zero));
|
||||
}
|
||||
|
||||
return (stateEntities.Count == 0 ? default : stateEntities, deletions);
|
||||
}
|
||||
|
||||
// Just get the relevant entities that have been dirtied
|
||||
// This should be extremely fast.
|
||||
if (!slowPath)
|
||||
{
|
||||
for (var i = fromTick.Value; i <= toTick.Value; i++)
|
||||
foreach (var uid in dirty)
|
||||
{
|
||||
// Fallback to dumping every entity on them.
|
||||
var tick = new GameTick(i);
|
||||
if (!TryGetTick(tick, out var add, out var dirty))
|
||||
{
|
||||
slowPath = true;
|
||||
break;
|
||||
}
|
||||
DebugTools.Assert(!add.Contains(uid));
|
||||
|
||||
foreach (var uid in add)
|
||||
{
|
||||
if (!seenEnts.Add(uid)) continue;
|
||||
// This is essentially the same as IEntityManager.EntityExists, but returning MetaDataComponent.
|
||||
if (!metadataQuery.TryGetComponent(uid, out var md)) continue;
|
||||
if (!seenEnts.Add(uid)) continue;
|
||||
if (!metadataQuery.TryGetComponent(uid, out MetaDataComponent? md)) continue;
|
||||
|
||||
DebugTools.Assert(md.EntityLifeStage >= EntityLifeStage.Initialized);
|
||||
DebugTools.Assert(md.EntityLifeStage >= EntityLifeStage.Initialized);
|
||||
|
||||
if (md.EntityLastModifiedTick >= fromTick)
|
||||
stateEntities.Add(GetEntityState(player, uid, GameTick.Zero, md.Flags));
|
||||
}
|
||||
|
||||
foreach (var uid in dirty)
|
||||
{
|
||||
DebugTools.Assert(!add.Contains(uid));
|
||||
|
||||
if (!seenEnts.Add(uid)) continue;
|
||||
if (!metadataQuery.TryGetComponent(uid, out var md)) continue;
|
||||
|
||||
DebugTools.Assert(md.EntityLifeStage >= EntityLifeStage.Initialized);
|
||||
|
||||
if (md.EntityLastModifiedTick >= fromTick)
|
||||
stateEntities.Add(GetEntityState(player, uid, fromTick, md.Flags));
|
||||
}
|
||||
if (md.EntityLastModifiedTick >= fromTick)
|
||||
stateEntities.Add(GetEntityState(player, uid, fromTick));
|
||||
}
|
||||
}
|
||||
|
||||
if (!slowPath)
|
||||
{
|
||||
if (stateEntities.Count == 0) stateEntities = default;
|
||||
|
||||
return (stateEntities, deletions);
|
||||
return stateEntities.Count == 0 ? default : stateEntities;
|
||||
}
|
||||
|
||||
stateEntities = new List<EntityState>(EntityManager.EntityCount);
|
||||
@@ -758,13 +555,11 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
DebugTools.Assert(md.EntityLifeStage >= EntityLifeStage.Initialized);
|
||||
|
||||
if (md.EntityLastModifiedTick >= fromTick)
|
||||
stateEntities.Add(GetEntityState(player, md.Owner, fromTick, md.Flags));
|
||||
stateEntities.Add(GetEntityState(player, md.Owner, fromTick));
|
||||
}
|
||||
|
||||
// no point sending an empty collection
|
||||
if (stateEntities.Count == 0) stateEntities = default;
|
||||
|
||||
return (stateEntities, deletions);
|
||||
return stateEntities.Count == 0 ? default : stateEntities;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -773,16 +568,11 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
/// <param name="player">The player to generate this state for.</param>
|
||||
/// <param name="entityUid">Uid of the entity to generate the state from.</param>
|
||||
/// <param name="fromTick">Only provide delta changes from this tick.</param>
|
||||
/// <param name="flags">Any applicable metadata flags</param>
|
||||
/// <returns>New entity State for the given entity.</returns>
|
||||
private EntityState GetEntityState(ICommonSession player, EntityUid entityUid, GameTick fromTick, MetaDataFlags flags)
|
||||
private EntityState GetEntityState(ICommonSession player, EntityUid entityUid, GameTick fromTick)
|
||||
{
|
||||
var bus = EntityManager.EventBus;
|
||||
var changed = new List<ComponentChange>();
|
||||
// Whether this entity has any component states that are only for a specific session.
|
||||
// TODO: This GetComp is probably expensive, less expensive than before, but ideally we'd cache it somewhere or something from a previous getcomp
|
||||
// Probably still needs tweaking but checking for add / changed states up front should do most of the work.
|
||||
var specificStates = (flags & MetaDataFlags.EntitySpecific) == MetaDataFlags.EntitySpecific;
|
||||
|
||||
foreach (var (netId, component) in EntityManager.GetNetComponents(entityUid))
|
||||
{
|
||||
@@ -797,22 +587,10 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
|
||||
DebugTools.Assert(component.LastModifiedTick >= component.CreationTick);
|
||||
|
||||
var addState = false;
|
||||
var changeState = false;
|
||||
if (!EntityManager.CanGetComponentState(bus, component, player))
|
||||
continue;
|
||||
|
||||
// We'll check the properties first; if we ever have specific states then doing the struct event is expensive.
|
||||
if (component.CreationTick != GameTick.Zero && component.CreationTick >= fromTick && !component.Deleted)
|
||||
addState = true;
|
||||
else if (component.NetSyncEnabled && component.LastModifiedTick != GameTick.Zero && component.LastModifiedTick >= fromTick)
|
||||
changeState = true;
|
||||
|
||||
if (!addState && !changeState)
|
||||
continue;
|
||||
|
||||
if (specificStates && !EntityManager.CanGetComponentState(bus, component, player))
|
||||
continue;
|
||||
|
||||
if (addState)
|
||||
{
|
||||
ComponentState? state = null;
|
||||
if (component.NetSyncEnabled && component.LastModifiedTick != GameTick.Zero &&
|
||||
@@ -823,14 +601,14 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
changed.Add(ComponentChange.Added(netId, state));
|
||||
}
|
||||
else
|
||||
else if (component.NetSyncEnabled && component.LastModifiedTick != GameTick.Zero &&
|
||||
component.LastModifiedTick >= fromTick)
|
||||
{
|
||||
DebugTools.Assert(changeState);
|
||||
changed.Add(ComponentChange.Changed(netId, EntityManager.GetComponentState(bus, component)));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var netId in _serverEntManager.GetDeletedComponents(entityUid, fromTick))
|
||||
foreach (var netId in ((IServerEntityManager)EntityManager).GetDeletedComponents(entityUid, fromTick))
|
||||
{
|
||||
changed.Add(ComponentChange.Removed(netId));
|
||||
}
|
||||
@@ -840,7 +618,7 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
|
||||
private HashSet<EntityUid> GetSessionViewers(ICommonSession session)
|
||||
{
|
||||
var viewers = _uidSetPool.Get();
|
||||
var viewers = _viewerEntsPool.Get();
|
||||
if (session.Status != SessionStatus.InGame)
|
||||
return viewers;
|
||||
|
||||
@@ -860,69 +638,14 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
}
|
||||
|
||||
// Read Safe
|
||||
private (Vector2 worldPos, float range, MapId mapId) CalcViewBounds(in EntityUid euid, EntityQuery<TransformComponent> transformQuery)
|
||||
private (Box2 view, MapId mapId) CalcViewBounds(in EntityUid euid, EntityQuery<TransformComponent> transformQuery)
|
||||
{
|
||||
var xform = transformQuery.GetComponent(euid);
|
||||
return (xform.WorldPosition, _viewSize / 2f, xform.MapID);
|
||||
}
|
||||
|
||||
public sealed class SetPolicy<T> : PooledObjectPolicy<HashSet<T>>
|
||||
{
|
||||
public override HashSet<T> Create()
|
||||
{
|
||||
return new HashSet<T>();
|
||||
}
|
||||
var view = Box2.UnitCentered.Scale(_viewSize).Translated(xform.WorldPosition);
|
||||
var map = xform.MapID;
|
||||
|
||||
public override bool Return(HashSet<T> obj)
|
||||
{
|
||||
obj.Clear();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ListPolicy<T> : PooledObjectPolicy<List<T>>
|
||||
{
|
||||
public override List<T> Create()
|
||||
{
|
||||
return new();
|
||||
}
|
||||
|
||||
public override bool Return(List<T> obj)
|
||||
{
|
||||
obj.Clear();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class DictPolicy<T1, T2> : PooledObjectPolicy<Dictionary<T1, T2>> where T1 : notnull
|
||||
{
|
||||
public override Dictionary<T1, T2> Create()
|
||||
{
|
||||
return new Dictionary<T1, T2>();
|
||||
}
|
||||
|
||||
public override bool Return(Dictionary<T1, T2> obj)
|
||||
{
|
||||
obj.Clear();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TreePolicy<T> : PooledObjectPolicy<RobustTree<T>> where T : notnull
|
||||
{
|
||||
private readonly ObjectPool<HashSet<RobustTree<T>.TreeNode>> _treeNodeSetPool =
|
||||
new DefaultObjectPool<HashSet<RobustTree<T>.TreeNode>>(new SetPolicy<RobustTree<T>.TreeNode>());
|
||||
|
||||
public override RobustTree<T> Create()
|
||||
{
|
||||
return new RobustTree<T>(_treeNodeSetPool.Get, _treeNodeSetPool.Return);
|
||||
}
|
||||
|
||||
public override bool Return(RobustTree<T> obj)
|
||||
{
|
||||
obj.Clear();
|
||||
return true;
|
||||
}
|
||||
return (view, map);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Robust.Server.GameStates;
|
||||
|
||||
public sealed class RobustTree<T> where T : notnull
|
||||
{
|
||||
private Dictionary<T, TreeNode> _nodeIndex = new();
|
||||
|
||||
private Dictionary<T, T> _parents = new();
|
||||
private HashSet<T> _rootNodes = new();
|
||||
|
||||
private Func<HashSet<TreeNode>> _setProvider;
|
||||
private Action<HashSet<TreeNode>> _setConsumer;
|
||||
|
||||
public RobustTree(Func<HashSet<TreeNode>>? setProvider = null, Action<HashSet<TreeNode>>? setConsumer = null)
|
||||
{
|
||||
_setProvider = setProvider ?? (static () => new());
|
||||
_setConsumer = setConsumer ?? (static (_) => {});
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
// TODO: This is hella expensive
|
||||
foreach (var value in _nodeIndex.Values)
|
||||
{
|
||||
_setConsumer(value.Children);
|
||||
}
|
||||
_nodeIndex.Clear();
|
||||
_parents.Clear();
|
||||
_rootNodes.Clear();
|
||||
}
|
||||
|
||||
public void Remove(T value, bool mend = false)
|
||||
{
|
||||
if (!_nodeIndex.TryGetValue(value, out var node))
|
||||
throw new InvalidOperationException("Node doesnt exist.");
|
||||
|
||||
if (_rootNodes.Contains(value))
|
||||
{
|
||||
foreach (var child in node.Children)
|
||||
{
|
||||
_parents.Remove(child.Value);
|
||||
_rootNodes.Add(child.Value);
|
||||
}
|
||||
_setConsumer(node.Children);
|
||||
_rootNodes.Remove(value);
|
||||
_nodeIndex.Remove(value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_parents.TryGetValue(value, out var parent))
|
||||
{
|
||||
foreach (var child in node.Children)
|
||||
{
|
||||
if (mend)
|
||||
{
|
||||
_parents[child.Value] = parent;
|
||||
_nodeIndex[parent].Children.Add(child);
|
||||
}
|
||||
else
|
||||
{
|
||||
_parents.Remove(child.Value);
|
||||
_rootNodes.Add(child.Value);
|
||||
}
|
||||
}
|
||||
|
||||
_setConsumer(node.Children);
|
||||
_parents.Remove(value);
|
||||
_nodeIndex.Remove(value);
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Node neither had a parent nor was a RootNode.");
|
||||
}
|
||||
|
||||
public TreeNode Set(T rootNode)
|
||||
{
|
||||
//root node, for now
|
||||
if (_nodeIndex.TryGetValue(rootNode, out var node))
|
||||
{
|
||||
if(!_rootNodes.Contains(rootNode))
|
||||
throw new InvalidOperationException("Node already exists as non-root node.");
|
||||
return node;
|
||||
}
|
||||
|
||||
node = new TreeNode(rootNode, _setProvider());
|
||||
_nodeIndex.Add(rootNode, node);
|
||||
_rootNodes.Add(rootNode);
|
||||
return node;
|
||||
}
|
||||
|
||||
public TreeNode Set(T child, T parent)
|
||||
{
|
||||
if (!_nodeIndex.TryGetValue(parent, out var parentNode))
|
||||
parentNode = Set(parent);
|
||||
|
||||
if (_nodeIndex.TryGetValue(child, out var existingNode))
|
||||
{
|
||||
if (_rootNodes.Contains(child))
|
||||
{
|
||||
parentNode.Children.Add(existingNode);
|
||||
_rootNodes.Remove(child);
|
||||
_parents.Add(child, parent);
|
||||
return existingNode;
|
||||
}
|
||||
|
||||
if (!_parents.TryGetValue(child, out var previousParent) || _nodeIndex.TryGetValue(previousParent, out var previousParentNode))
|
||||
throw new InvalidOperationException("Could not find old parent for non-root node.");
|
||||
|
||||
previousParentNode.Children.Remove(existingNode);
|
||||
parentNode.Children.Add(existingNode);
|
||||
_parents[child] = parent;
|
||||
return existingNode;
|
||||
}
|
||||
|
||||
existingNode = new TreeNode(child, _setProvider());
|
||||
_nodeIndex.Add(child, existingNode);
|
||||
parentNode.Children.Add(existingNode);
|
||||
_parents.Add(child, parent);
|
||||
return existingNode;
|
||||
}
|
||||
|
||||
// todo paul optimize this maybe as its basically all this is used for.
|
||||
public HashSet<TreeNode> GetRootNodes()
|
||||
{
|
||||
var nodes = _setProvider();
|
||||
foreach (var node in _rootNodes)
|
||||
{
|
||||
nodes.Add(_nodeIndex[node]);
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
public void ReturnRootNodes(HashSet<TreeNode> rootNodes)
|
||||
{
|
||||
_setConsumer(rootNodes);
|
||||
}
|
||||
|
||||
public readonly struct TreeNode : IEquatable<TreeNode>
|
||||
{
|
||||
public readonly T Value;
|
||||
public readonly HashSet<TreeNode> Children;
|
||||
|
||||
public TreeNode(T value, HashSet<TreeNode> children)
|
||||
{
|
||||
Value = value;
|
||||
Children = children;
|
||||
}
|
||||
|
||||
public bool Equals(TreeNode other)
|
||||
{
|
||||
return Value.Equals(other.Value) && Children.Equals(other.Children);
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is TreeNode other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Value, Children);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Enums;
|
||||
@@ -128,37 +125,6 @@ namespace Robust.Server.GameStates
|
||||
// people not in the game don't get states
|
||||
var players = _playerManager.ServerSessions.Where(o => o.Status == SessionStatus.InGame).ToArray();
|
||||
|
||||
//todo paul oh my god make this less shit
|
||||
EntityQuery<MetaDataComponent> metadataQuery = default!;
|
||||
EntityQuery<TransformComponent> transformQuery = default!;
|
||||
HashSet<int>[] playerChunks = default!;
|
||||
EntityUid[][] viewerEntities = default!;
|
||||
(Dictionary<EntityUid, MetaDataComponent> metadata, RobustTree<EntityUid> tree)?[] chunkCache = default!;
|
||||
|
||||
if (_pvs.CullingEnabled)
|
||||
{
|
||||
List<(uint, IChunkIndexLocation)> chunks;
|
||||
(chunks, playerChunks, viewerEntities) = _pvs.GetChunks(players);
|
||||
const int ChunkBatchSize = 2;
|
||||
var chunksCount = chunks.Count;
|
||||
var chunkBatches = (int) MathF.Ceiling((float) chunksCount / ChunkBatchSize);
|
||||
chunkCache =
|
||||
new (Dictionary<EntityUid, MetaDataComponent> metadata, RobustTree<EntityUid> tree)?[chunksCount];
|
||||
transformQuery = _entityManager.GetEntityQuery<TransformComponent>();
|
||||
metadataQuery = _entityManager.GetEntityQuery<MetaDataComponent>();
|
||||
Parallel.For(0, chunkBatches, i =>
|
||||
{
|
||||
var start = i * ChunkBatchSize;
|
||||
var end = Math.Min(start + ChunkBatchSize, chunksCount);
|
||||
|
||||
for (var j = start; j < end; ++j)
|
||||
{
|
||||
var (visMask, chunkIndexLocation) = chunks[j];
|
||||
chunkCache[j] = _pvs.CalculateChunk(chunkIndexLocation, visMask, transformQuery, metadataQuery);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const int BatchSize = 2;
|
||||
var batches = (int) MathF.Ceiling((float) players.Length / BatchSize);
|
||||
|
||||
@@ -169,9 +135,11 @@ namespace Robust.Server.GameStates
|
||||
|
||||
for (var j = start; j < end; ++j)
|
||||
{
|
||||
var session = players[j];
|
||||
|
||||
try
|
||||
{
|
||||
SendStateUpdate(j);
|
||||
SendStateUpdate(session);
|
||||
}
|
||||
catch (Exception e) // Catch EVERY exception
|
||||
{
|
||||
@@ -180,10 +148,8 @@ namespace Robust.Server.GameStates
|
||||
}
|
||||
});
|
||||
|
||||
void SendStateUpdate(int sessionIndex)
|
||||
void SendStateUpdate(IPlayerSession session)
|
||||
{
|
||||
var session = players[sessionIndex];
|
||||
|
||||
// KILL IT WITH FIRE
|
||||
if(mainThread != Thread.CurrentThread)
|
||||
IoCManager.InitThread(new DependencyCollection(parentDeps), true);
|
||||
@@ -195,10 +161,7 @@ namespace Robust.Server.GameStates
|
||||
DebugTools.Assert("Why does this channel not have an entry?");
|
||||
}
|
||||
|
||||
var (entStates, deletions) = _pvs.CullingEnabled
|
||||
? _pvs.CalculateEntityStates(session, lastAck, _gameTiming.CurTick, chunkCache,
|
||||
playerChunks[sessionIndex], metadataQuery, transformQuery, viewerEntities[sessionIndex])
|
||||
: _pvs.GetAllEntityStates(session, lastAck, _gameTiming.CurTick);
|
||||
var (entStates, deletions) = _pvs.CalculateEntityStates(session, lastAck, _gameTiming.CurTick);
|
||||
var playerStates = _playerManager.GetPlayerStates(lastAck);
|
||||
var mapData = _mapManager.GetStateData(lastAck);
|
||||
|
||||
@@ -231,8 +194,6 @@ namespace Robust.Server.GameStates
|
||||
_networkManager.ServerSendMessage(stateUpdateMessage, channel);
|
||||
}
|
||||
|
||||
if(_pvs.CullingEnabled)
|
||||
_pvs.ReturnToPool(chunkCache, playerChunks);
|
||||
_pvs.Cleanup(_playerManager.ServerSessions);
|
||||
var oldestAck = new GameTick(oldestAckValue);
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Validation;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using YamlDotNet.Core;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
@@ -43,6 +44,7 @@ namespace Robust.Server.Maps
|
||||
[Dependency] private readonly IMapManagerInternal _mapManager = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
|
||||
[Dependency] private readonly IServerEntityManagerInternal _serverEntityManager = default!;
|
||||
[Dependency] private readonly IPauseManager _pauseManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
public event Action<YamlStream, string>? LoadedMapData;
|
||||
@@ -52,7 +54,7 @@ namespace Robust.Server.Maps
|
||||
{
|
||||
var grid = _mapManager.GetGrid(gridId);
|
||||
|
||||
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _prototypeManager);
|
||||
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _pauseManager, _prototypeManager);
|
||||
context.RegisterGrid(grid);
|
||||
var root = context.Serialize();
|
||||
var document = new YamlDocument(root);
|
||||
@@ -98,7 +100,7 @@ namespace Robust.Server.Maps
|
||||
throw new InvalidDataException("Cannot instance map with multiple grids as blueprint.");
|
||||
}
|
||||
|
||||
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager,
|
||||
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _pauseManager,
|
||||
_prototypeManager, (YamlMappingNode) data.RootNode, mapId, options);
|
||||
context.Deserialize();
|
||||
grid = context.Grids[0];
|
||||
@@ -118,7 +120,7 @@ namespace Robust.Server.Maps
|
||||
_serverEntityManager.GetComponent<MetaDataComponent>(entity).EntityLifeStage = EntityLifeStage.MapInitialized;
|
||||
}
|
||||
}
|
||||
else if (_mapManager.IsMapInitialized(mapId))
|
||||
else if (_pauseManager.IsMapInitialized(mapId))
|
||||
{
|
||||
foreach (var entity in context.Entities)
|
||||
{
|
||||
@@ -126,7 +128,7 @@ namespace Robust.Server.Maps
|
||||
}
|
||||
}
|
||||
|
||||
if (_mapManager.IsMapPaused(mapId))
|
||||
if (_pauseManager.IsMapPaused(mapId))
|
||||
{
|
||||
foreach (var entity in context.Entities)
|
||||
{
|
||||
@@ -139,7 +141,7 @@ namespace Robust.Server.Maps
|
||||
public void SaveMap(MapId mapId, string yamlPath)
|
||||
{
|
||||
Logger.InfoS("map", $"Saving map {mapId} to {yamlPath}");
|
||||
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _prototypeManager);
|
||||
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _pauseManager, _prototypeManager);
|
||||
foreach (var grid in _mapManager.GetAllMapGrids(mapId))
|
||||
{
|
||||
context.RegisterGrid(grid);
|
||||
@@ -205,7 +207,7 @@ namespace Robust.Server.Maps
|
||||
|
||||
LoadedMapData?.Invoke(data.Stream, resPath.ToString());
|
||||
|
||||
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager,
|
||||
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _pauseManager,
|
||||
_prototypeManager, (YamlMappingNode) data.RootNode, mapId, options);
|
||||
context.Deserialize();
|
||||
|
||||
@@ -224,6 +226,7 @@ namespace Robust.Server.Maps
|
||||
private readonly IMapManagerInternal _mapManager;
|
||||
private readonly ITileDefinitionManager _tileDefinitionManager;
|
||||
private readonly IServerEntityManagerInternal _serverEntityManager;
|
||||
private readonly IPauseManager _pauseManager;
|
||||
private readonly IPrototypeManager _prototypeManager;
|
||||
|
||||
private readonly MapLoadOptions? _loadOptions;
|
||||
@@ -257,11 +260,12 @@ namespace Robust.Server.Maps
|
||||
public bool MapIsPostInit { get; private set; }
|
||||
|
||||
public MapContext(IMapManagerInternal maps, ITileDefinitionManager tileDefs,
|
||||
IServerEntityManagerInternal entities, IPrototypeManager prototypeManager)
|
||||
IServerEntityManagerInternal entities, IPauseManager pauseManager, IPrototypeManager prototypeManager)
|
||||
{
|
||||
_mapManager = maps;
|
||||
_tileDefinitionManager = tileDefs;
|
||||
_serverEntityManager = entities;
|
||||
_pauseManager = pauseManager;
|
||||
_prototypeManager = prototypeManager;
|
||||
|
||||
RootNode = new YamlMappingNode();
|
||||
@@ -279,12 +283,13 @@ namespace Robust.Server.Maps
|
||||
|
||||
public MapContext(IMapManagerInternal maps, ITileDefinitionManager tileDefs,
|
||||
IServerEntityManagerInternal entities,
|
||||
IPrototypeManager prototypeManager,
|
||||
IPauseManager pauseManager, IPrototypeManager prototypeManager,
|
||||
YamlMappingNode node, MapId targetMapId, MapLoadOptions options)
|
||||
{
|
||||
_mapManager = maps;
|
||||
_tileDefinitionManager = tileDefs;
|
||||
_serverEntityManager = entities;
|
||||
_pauseManager = pauseManager;
|
||||
_loadOptions = options;
|
||||
|
||||
RootNode = node;
|
||||
@@ -607,7 +612,7 @@ namespace Robust.Server.Maps
|
||||
|
||||
if (!MapIsPostInit)
|
||||
{
|
||||
_mapManager.AddUninitializedMap(TargetMap);
|
||||
_pauseManager.AddUninitializedMap(TargetMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -714,7 +719,7 @@ namespace Robust.Server.Maps
|
||||
var isPostInit = false;
|
||||
foreach (var grid in Grids)
|
||||
{
|
||||
if (_mapManager.IsMapInitialized(grid.ParentMapId))
|
||||
if (_pauseManager.IsMapInitialized(grid.ParentMapId))
|
||||
{
|
||||
isPostInit = true;
|
||||
break;
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace Robust.Server.Maps
|
||||
return gridn;
|
||||
}
|
||||
|
||||
private static YamlNode SerializeChunk(MapChunk chunk)
|
||||
private static YamlNode SerializeChunk(IMapChunk chunk)
|
||||
{
|
||||
var root = new YamlMappingNode();
|
||||
var value = new YamlScalarNode($"{chunk.X},{chunk.Y}");
|
||||
@@ -51,7 +51,7 @@ namespace Robust.Server.Maps
|
||||
return root;
|
||||
}
|
||||
|
||||
private static string SerializeTiles(MapChunk chunk)
|
||||
private static string SerializeTiles(IMapChunk chunk)
|
||||
{
|
||||
// number of bytes written per tile, because sizeof(Tile) is useless.
|
||||
const int structSize = 4;
|
||||
|
||||
@@ -209,7 +209,7 @@ namespace Robust.Server.Placement
|
||||
{
|
||||
EntityCoordinates start = msg.EntityCoordinates;
|
||||
Vector2 rectSize = msg.RectSize;
|
||||
foreach (EntityUid entity in EntitySystem.Get<EntityLookupSystem>().GetEntitiesIntersecting(start.GetMapId(_entityManager),
|
||||
foreach (EntityUid entity in IoCManager.Resolve<IEntityLookup>().GetEntitiesIntersecting(start.GetMapId(_entityManager),
|
||||
new Box2(start.Position, start.Position + rectSize)))
|
||||
{
|
||||
if (_entityManager.Deleted(entity) || _entityManager.HasComponent<IMapGridComponent>(entity) || _entityManager.HasComponent<ActorComponent>(entity))
|
||||
|
||||
@@ -50,6 +50,7 @@ namespace Robust.Server
|
||||
IoCManager.Register<IMapManagerInternal, NetworkedMapManager>();
|
||||
IoCManager.Register<INetworkedMapManager, NetworkedMapManager>();
|
||||
IoCManager.Register<IEntityManager, ServerEntityManager>();
|
||||
IoCManager.Register<IEntityLookup, EntityLookup>();
|
||||
IoCManager.Register<IEntityNetworkManager, ServerEntityManager>();
|
||||
IoCManager.Register<IServerEntityNetworkManager, ServerEntityManager>();
|
||||
IoCManager.Register<IMapLoader, MapLoader>();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
@@ -135,17 +135,5 @@ namespace Robust.Shared.Maths
|
||||
{
|
||||
return $"({Left}, {Bottom}, {Right}, {Top})";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Multiplies each side of the box by the scalar.
|
||||
/// </summary>
|
||||
public Box2i Scale(int scalar)
|
||||
{
|
||||
return new Box2i(
|
||||
Left * scalar,
|
||||
Bottom * scalar,
|
||||
Right * scalar,
|
||||
Top * scalar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,12 +130,6 @@ namespace Robust.Shared.Maths
|
||||
return new((int) MathF.Floor(X), (int) MathF.Floor(Y));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly Vector2i Ceiled()
|
||||
{
|
||||
return new((int) MathF.Ceiling(X), (int) MathF.Ceiling(Y));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subtracts a vector from another, returning a new vector.
|
||||
/// </summary>
|
||||
|
||||
@@ -1006,17 +1006,6 @@ namespace Robust.Shared
|
||||
public static readonly CVarDef<bool> ResTexturePreloadCache =
|
||||
CVarDef.Create("res.texture_preload_cache", true, CVar.CLIENTONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Override seekability of resource streams returned by ResourceManager.
|
||||
/// See <see cref="ContentPack.StreamSeekMode"/> for int values.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is intended to be a debugging tool primarily.
|
||||
/// Non-default seek modes WILL result in worse performance.
|
||||
/// </remarks>
|
||||
public static readonly CVarDef<int> ResStreamSeekMode =
|
||||
CVarDef.Create("res.stream_seek_mode", (int)ContentPack.StreamSeekMode.None);
|
||||
|
||||
/*
|
||||
* DEBUG
|
||||
*/
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Robust.Shared.Console;
|
||||
|
||||
public sealed class CommandBuffer
|
||||
{
|
||||
private const string DelayMarker = "-DELAY-";
|
||||
|
||||
private int _tickrate = 0;
|
||||
private int _delay = 0;
|
||||
|
||||
private readonly LinkedList<string> _commandBuffer = new();
|
||||
|
||||
public void Append(string command)
|
||||
{
|
||||
_commandBuffer.AddLast(command);
|
||||
}
|
||||
|
||||
public void Insert(string command)
|
||||
{
|
||||
_commandBuffer.AddFirst(command);
|
||||
}
|
||||
|
||||
public void Tick(byte tickRate)
|
||||
{
|
||||
_tickrate = tickRate;
|
||||
|
||||
if (_delay > 0)
|
||||
{
|
||||
_delay -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetCommand([MaybeNullWhen(false)]out string command)
|
||||
{
|
||||
var next = _commandBuffer.First;
|
||||
|
||||
if (next is null) // nothing to do here
|
||||
{
|
||||
command = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (next.Value.Equals(DelayMarker))
|
||||
{
|
||||
if (_delay == 0) // just finished
|
||||
{
|
||||
_commandBuffer.RemoveFirst();
|
||||
return TryGetCommand(out command);
|
||||
}
|
||||
else // currently counting down delay
|
||||
{
|
||||
command = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (next.Value.StartsWith("wait "))
|
||||
{
|
||||
var sTicks = next.Value.Substring(5);
|
||||
_commandBuffer.RemoveFirst();
|
||||
if (string.IsNullOrWhiteSpace(sTicks) || !int.TryParse(sTicks, out var ticks)) // messed up command
|
||||
{
|
||||
return TryGetCommand(out command);
|
||||
}
|
||||
|
||||
// Setup Timing
|
||||
_commandBuffer.AddFirst(DelayMarker);
|
||||
_delay = ticks;
|
||||
|
||||
command = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// normal command
|
||||
_commandBuffer.RemoveFirst();
|
||||
command = next.Value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,7 @@ namespace Robust.Shared.Console.Commands
|
||||
continue;
|
||||
}
|
||||
|
||||
shell.ConsoleHost.AppendCommand(line);
|
||||
shell.ExecuteCommand(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ using Robust.Shared.Log;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.Console
|
||||
@@ -18,16 +17,13 @@ namespace Robust.Shared.Console
|
||||
protected const string SawmillName = "con";
|
||||
|
||||
[Dependency] protected readonly ILogManager LogManager = default!;
|
||||
[Dependency] private readonly IReflectionManager ReflectionManager = default!;
|
||||
[Dependency] protected readonly IReflectionManager ReflectionManager = default!;
|
||||
[Dependency] protected readonly INetManager NetManager = default!;
|
||||
[Dependency] private readonly IDynamicTypeFactoryInternal _typeFactory = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
[ViewVariables]
|
||||
protected readonly Dictionary<string, IConsoleCommand> AvailableCommands = new();
|
||||
|
||||
private readonly CommandBuffer _commandBuffer = new CommandBuffer();
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsServer => NetManager.IsServer;
|
||||
|
||||
@@ -120,36 +116,6 @@ namespace Robust.Shared.Console
|
||||
ExecuteCommand(null, command);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AppendCommand(string command)
|
||||
{
|
||||
_commandBuffer.Append(command);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void InsertCommand(string command)
|
||||
{
|
||||
_commandBuffer.Insert(command);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void CommandBufferExecute()
|
||||
{
|
||||
_commandBuffer.Tick(_timing.TickRate);
|
||||
|
||||
while (_commandBuffer.TryGetCommand(out var cmd))
|
||||
{
|
||||
try
|
||||
{
|
||||
ExecuteCommand(cmd);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LocalShell.WriteError(e.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A console command that was registered inline through <see cref="IConsoleHost"/>.
|
||||
/// </summary>
|
||||
|
||||
@@ -81,35 +81,11 @@ namespace Robust.Shared.Console
|
||||
IConsoleShell GetSessionShell(ICommonSession session);
|
||||
|
||||
/// <summary>
|
||||
/// Execute a command string immediately on the local shell, bypassing the command buffer completely.
|
||||
/// Execute a command string on the local shell.
|
||||
/// </summary>
|
||||
/// <param name="command">Command string to execute.</param>
|
||||
void ExecuteCommand(string command);
|
||||
|
||||
/// <summary>
|
||||
/// Appends a command into the end of the command buffer on the local shell.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This command will be ran *sometime* in the future, depending on how many waits are in the buffer.
|
||||
/// </remarks>
|
||||
/// <param name="command">Command string to execute.</param>
|
||||
void AppendCommand(string command);
|
||||
|
||||
/// <summary>
|
||||
/// Inserts a command into the front of the command buffer on the local shell.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This command will preempt the next command executed in the command buffer.
|
||||
/// </remarks>
|
||||
/// <param name="command">Command string to execute.</param>
|
||||
void InsertCommand(string command);
|
||||
|
||||
/// <summary>
|
||||
/// Processes any contents of the command buffer on the local shell. This needs to be called regularly (once a tick),
|
||||
/// inside the simulation. Pausing the server should prevent the buffer from being processed.
|
||||
/// </summary>
|
||||
void CommandBufferExecute();
|
||||
|
||||
/// <summary>
|
||||
/// Executes a command string on this specific session shell. If the command does not exist, the command will be forwarded
|
||||
/// to the
|
||||
|
||||
@@ -81,7 +81,6 @@ namespace Robust.Shared.Containers
|
||||
// spatially move the object to the location of the container. If you don't want this functionality, the
|
||||
// calling code can save the local position before calling this function, and apply it afterwords.
|
||||
transform.LocalPosition = Vector2.Zero;
|
||||
transform.LocalRotation = Angle.Zero;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -209,43 +209,6 @@ namespace Robust.Shared.Containers
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the top-most container in the hierarchy for this entity, if it exists.
|
||||
/// </summary>
|
||||
public bool TryGetOuterContainer(EntityUid uid, TransformComponent xform, [NotNullWhen(true)] out IContainer? container)
|
||||
{
|
||||
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
return TryGetOuterContainer(uid, xform, out container, xformQuery);
|
||||
}
|
||||
|
||||
public bool TryGetOuterContainer(EntityUid uid, TransformComponent xform,
|
||||
[NotNullWhen(true)] out IContainer? container, EntityQuery<TransformComponent> xformQuery)
|
||||
{
|
||||
container = null;
|
||||
|
||||
if (!uid.IsValid())
|
||||
return false;
|
||||
|
||||
var conQuery = EntityManager.GetEntityQuery<ContainerManagerComponent>();
|
||||
var child = uid;
|
||||
var parent = xform.ParentUid;
|
||||
|
||||
while (parent.IsValid())
|
||||
{
|
||||
if (conQuery.TryGetComponent(parent, out var conManager) &&
|
||||
conManager.TryGetContainer(child, out var parentContainer))
|
||||
{
|
||||
container = parentContainer;
|
||||
}
|
||||
|
||||
var parentXform = xformQuery.GetComponent(parent);
|
||||
child = parent;
|
||||
parent = parentXform.ParentUid;
|
||||
}
|
||||
|
||||
return container != null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// Eject entities from their parent container if the parent change is done by the transform only.
|
||||
|
||||
@@ -111,7 +111,7 @@ namespace Robust.Shared.ContentPack
|
||||
var fullStopwatch = Stopwatch.StartNew();
|
||||
|
||||
var resolver = CreateResolver();
|
||||
using var peReader = ModLoader.MakePEReader(assembly, leaveOpen: true);
|
||||
using var peReader = new PEReader(assembly, PEStreamOptions.LeaveOpen);
|
||||
var reader = peReader.GetMetadataReader();
|
||||
|
||||
var asmName = reader.GetString(reader.GetAssemblyDefinition().Name);
|
||||
@@ -853,13 +853,13 @@ namespace Robust.Shared.ContentPack
|
||||
continue;
|
||||
}
|
||||
|
||||
return ModLoader.MakePEReader(File.OpenRead(path));
|
||||
return ReaderFromStream(File.OpenRead(path));
|
||||
}
|
||||
|
||||
var extraStream = _parent.ExtraRobustLoader?.Invoke(dllName);
|
||||
if (extraStream != null)
|
||||
{
|
||||
return ModLoader.MakePEReader(extraStream);
|
||||
return ReaderFromStream(extraStream);
|
||||
}
|
||||
|
||||
foreach (var resLoadPath in _resLoadPaths)
|
||||
@@ -867,7 +867,7 @@ namespace Robust.Shared.ContentPack
|
||||
try
|
||||
{
|
||||
var path = resLoadPath / dllName;
|
||||
return ModLoader.MakePEReader(_parent._res.ContentFileRead(path));
|
||||
return ReaderFromStream(_parent._res.ContentFileRead(path));
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
@@ -877,6 +877,23 @@ namespace Robust.Shared.ContentPack
|
||||
return null;
|
||||
}
|
||||
|
||||
private static PEReader ReaderFromStream(Stream stream)
|
||||
{
|
||||
if (OperatingSystem.IsLinux() && stream is FileStream)
|
||||
{
|
||||
// PEReader is bugged on Linux and not properly thread safe when doing memory mapping.
|
||||
// As such, we never pass it a file stream so it uses a different, non-bugged code path.
|
||||
// See https://github.com/dotnet/runtime/issues/60545
|
||||
var ms = new MemoryStream();
|
||||
stream.CopyTo(ms);
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
stream.Dispose();
|
||||
stream = ms;
|
||||
}
|
||||
|
||||
return new PEReader(stream);
|
||||
}
|
||||
|
||||
public PEReader? Resolve(string simpleName)
|
||||
{
|
||||
return _dictionary.GetOrAdd(simpleName, ResolveCore);
|
||||
|
||||
@@ -160,7 +160,7 @@ namespace Robust.Shared.ContentPack
|
||||
|
||||
private static (string[] refs, string name) GetAssemblyReferenceData(Stream stream)
|
||||
{
|
||||
using var reader = ModLoader.MakePEReader(stream);
|
||||
using var reader = new PEReader(stream);
|
||||
var metaReader = reader.GetMetadataReader();
|
||||
|
||||
var name = metaReader.GetString(metaReader.GetAssemblyDefinition().Name);
|
||||
@@ -383,13 +383,5 @@ namespace Robust.Shared.ContentPack
|
||||
EngineModuleDirectories = _engineModuleDirectories.ToArray()
|
||||
};
|
||||
}
|
||||
|
||||
internal static PEReader MakePEReader(Stream stream, bool leaveOpen=false)
|
||||
{
|
||||
if (!stream.CanSeek)
|
||||
stream = leaveOpen ? stream.CopyToMemoryStream() : stream.ConsumeToMemoryStream();
|
||||
|
||||
return new PEReader(stream, leaveOpen ? PEStreamOptions.LeaveOpen : default);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Robust.Shared.ContentPack;
|
||||
|
||||
internal sealed class NonSeekableStream : Stream
|
||||
{
|
||||
private readonly Stream _baseStream;
|
||||
|
||||
public NonSeekableStream(Stream baseStream)
|
||||
{
|
||||
_baseStream = baseStream;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
_baseStream.Dispose();
|
||||
}
|
||||
|
||||
public override ValueTask DisposeAsync()
|
||||
{
|
||||
return _baseStream.DisposeAsync();
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
_baseStream.Flush();
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
return _baseStream.Read(buffer, offset, count);
|
||||
}
|
||||
|
||||
public override int Read(Span<byte> buffer)
|
||||
{
|
||||
return _baseStream.Read(buffer);
|
||||
}
|
||||
|
||||
public override int ReadByte()
|
||||
{
|
||||
return _baseStream.ReadByte();
|
||||
}
|
||||
|
||||
public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = new CancellationToken())
|
||||
{
|
||||
return _baseStream.ReadAsync(buffer, cancellationToken);
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
_baseStream.Write(buffer, offset, count);
|
||||
}
|
||||
|
||||
public override void Write(ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
_baseStream.Write(buffer);
|
||||
}
|
||||
|
||||
public override void WriteByte(byte value)
|
||||
{
|
||||
_baseStream.WriteByte(value);
|
||||
}
|
||||
|
||||
public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = new CancellationToken())
|
||||
{
|
||||
return _baseStream.WriteAsync(buffer, cancellationToken);
|
||||
}
|
||||
|
||||
public override bool CanRead => _baseStream.CanRead;
|
||||
public override bool CanSeek => false;
|
||||
public override bool CanWrite => _baseStream.CanWrite;
|
||||
// .NET mingles seekability and exposing length.
|
||||
// This makes absolutely no sense but ok.
|
||||
public override long Length => throw new NotSupportedException();
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => throw new NotSupportedException();
|
||||
set => throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -23,8 +23,6 @@ namespace Robust.Shared.ContentPack
|
||||
private readonly List<(ResourcePath prefix, IContentRoot root)> _contentRoots =
|
||||
new();
|
||||
|
||||
private StreamSeekMode _streamSeekMode;
|
||||
|
||||
// Special file names on Windows like serial ports.
|
||||
private static readonly Regex BadPathSegmentRegex =
|
||||
new("^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$", RegexOptions.IgnoreCase);
|
||||
@@ -47,8 +45,6 @@ namespace Robust.Shared.ContentPack
|
||||
{
|
||||
UserData = new VirtualWritableDirProvider();
|
||||
}
|
||||
|
||||
_config.OnValueChanged(CVars.ResStreamSeekMode, i => _streamSeekMode = (StreamSeekMode)i, true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -195,9 +191,8 @@ namespace Robust.Shared.ContentPack
|
||||
continue;
|
||||
}
|
||||
|
||||
if (root.TryGetFile(relative, out var stream))
|
||||
if (root.TryGetFile(relative, out fileStream))
|
||||
{
|
||||
fileStream = WrapStream(stream);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -211,35 +206,6 @@ namespace Robust.Shared.ContentPack
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply <see cref="_streamSeekMode"/> to the provided stream.
|
||||
/// </summary>
|
||||
private Stream WrapStream(Stream stream)
|
||||
{
|
||||
switch (_streamSeekMode)
|
||||
{
|
||||
case StreamSeekMode.None:
|
||||
return stream;
|
||||
|
||||
case StreamSeekMode.ForceSeekable:
|
||||
if (stream.CanSeek)
|
||||
return stream;
|
||||
|
||||
var ms = new MemoryStream(stream.CopyToArray(), writable: false);
|
||||
stream.Dispose();
|
||||
return ms;
|
||||
|
||||
case StreamSeekMode.ForceNonSeekable:
|
||||
if (!stream.CanSeek)
|
||||
return stream;
|
||||
|
||||
return new NonSeekableStream(stream);
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ContentFileExists(string path)
|
||||
{
|
||||
|
||||
@@ -689,7 +689,6 @@ Types:
|
||||
CancellationToken: { All: True }
|
||||
CancellationTokenSource: { All: True }
|
||||
Interlocked: { All: True }
|
||||
Monitor: { All: True }
|
||||
System.Web:
|
||||
HttpUtility:
|
||||
Methods:
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
namespace Robust.Shared.ContentPack;
|
||||
|
||||
/// <summary>
|
||||
/// Seekability force mode for ResourceManager.
|
||||
/// </summary>
|
||||
public enum StreamSeekMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Do not do anything special. Streams will be seekable if the VFS can provide it (e.g. not compressed).
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// All streams will be forced as seekable by buffering them in memory if necessary.
|
||||
/// </summary>
|
||||
ForceSeekable = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Force streams to be non-seekable by wrapping them in another stream instances.
|
||||
/// </summary>
|
||||
ForceNonSeekable = 2
|
||||
}
|
||||
@@ -2,7 +2,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.GameObjects;
|
||||
@@ -28,8 +27,6 @@ public abstract class AppearanceComponent : Component
|
||||
if (AppearanceData.TryGetValue(key, out var existing) && existing.Equals(value))
|
||||
return;
|
||||
|
||||
DebugTools.Assert(value.GetType().IsValueType || value is ICloneable, "Appearance data values must be cloneable.");
|
||||
|
||||
AppearanceData[key] = value;
|
||||
Dirty();
|
||||
EntitySystem.Get<SharedAppearanceSystem>().MarkDirty(this);
|
||||
@@ -40,8 +37,6 @@ public abstract class AppearanceComponent : Component
|
||||
if (AppearanceData.TryGetValue(key, out var existing) && existing.Equals(value))
|
||||
return;
|
||||
|
||||
DebugTools.Assert(value.GetType().IsValueType || value is ICloneable, "Appearance data values must be cloneable.");
|
||||
|
||||
AppearanceData[key] = value;
|
||||
Dirty();
|
||||
EntitySystem.Get<SharedAppearanceSystem>().MarkDirty(this);
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
public interface ILookupWorldBox2Component
|
||||
{
|
||||
Box2 GetAABB(Transform transform);
|
||||
Box2 GetWorldAABB(Vector2? worldPos = null, Angle? worldRot = null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,8 +266,12 @@ namespace Robust.Shared.GameObjects
|
||||
Dirty(_entMan);
|
||||
}
|
||||
|
||||
public Box2 GetAABB(Transform transform)
|
||||
public Box2 GetWorldAABB(Vector2? worldPos = null, Angle? worldRot = null)
|
||||
{
|
||||
worldPos ??= _entMan.GetComponent<TransformComponent>(Owner).WorldPosition;
|
||||
worldRot ??= _entMan.GetComponent<TransformComponent>(Owner).WorldRotation;
|
||||
var transform = new Transform(worldPos.Value, (float) worldRot.Value.Theta);
|
||||
|
||||
var bounds = new Box2(transform.Position, transform.Position);
|
||||
|
||||
foreach (var fixture in _entMan.GetComponent<FixturesComponent>(Owner).Fixtures.Values)
|
||||
@@ -282,24 +286,13 @@ namespace Robust.Shared.GameObjects
|
||||
return bounds;
|
||||
}
|
||||
|
||||
[Obsolete("Use the GetWorldAABB on EntityLookupSystem")]
|
||||
public Box2 GetWorldAABB(Vector2? worldPos = null, Angle? worldRot = null)
|
||||
public Box2 GetWorldAABB(Vector2 worldPos, Angle worldRot, EntityQuery<FixturesComponent> fixtures)
|
||||
{
|
||||
if (worldPos == null && worldRot == null)
|
||||
{
|
||||
(worldPos, worldRot) = _entMan.GetComponent<TransformComponent>(Owner).GetWorldPositionRotation();
|
||||
}
|
||||
else
|
||||
{
|
||||
worldPos ??= _entMan.GetComponent<TransformComponent>(Owner).WorldPosition;
|
||||
worldRot ??= _entMan.GetComponent<TransformComponent>(Owner).WorldRotation;
|
||||
}
|
||||
|
||||
var transform = new Transform(worldPos.Value, (float) worldRot.Value.Theta);
|
||||
var transform = new Transform(worldPos, (float) worldRot.Theta);
|
||||
|
||||
var bounds = new Box2(transform.Position, transform.Position);
|
||||
|
||||
foreach (var fixture in _entMan.GetComponent<FixturesComponent>(Owner).Fixtures.Values)
|
||||
foreach (var fixture in fixtures.GetComponent(Owner).Fixtures.Values)
|
||||
{
|
||||
for (var i = 0; i < fixture.Shape.ChildCount; i++)
|
||||
{
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed class IgnorePauseComponent : Component
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
|
||||
protected override void OnAdd()
|
||||
{
|
||||
base.OnAdd();
|
||||
IoCManager.Resolve<IEntityManager>().GetComponent<MetaDataComponent>(Owner).EntityPaused = false;
|
||||
_entMan.GetComponent<MetaDataComponent>(Owner).EntityPaused = false;
|
||||
}
|
||||
|
||||
protected override void OnRemove()
|
||||
{
|
||||
base.OnRemove();
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
if (IoCManager.Resolve<IMapManager>().IsMapPaused(entMan.GetComponent<TransformComponent>(Owner).MapID))
|
||||
if (IoCManager.Resolve<IPauseManager>().IsMapPaused(_entMan.GetComponent<TransformComponent>(Owner).MapID))
|
||||
{
|
||||
entMan.GetComponent<MetaDataComponent>(Owner).EntityPaused = true;
|
||||
_entMan.GetComponent<MetaDataComponent>(Owner).EntityPaused = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,13 +15,12 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
bool LightingEnabled { get; set; }
|
||||
MapId WorldMap { get; }
|
||||
bool MapPaused { get; internal set; }
|
||||
bool MapPreInit { get; internal set; }
|
||||
void ClearMapId();
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IMapComponent"/>
|
||||
[ComponentReference(typeof(IMapComponent))]
|
||||
[NetworkedComponent]
|
||||
[NetworkedComponent()]
|
||||
public sealed class MapComponent : Component, IMapComponent
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
@@ -41,22 +40,10 @@ namespace Robust.Shared.GameObjects
|
||||
internal set => _mapIndex = value;
|
||||
}
|
||||
|
||||
internal bool MapPaused { get; set; } = false;
|
||||
|
||||
/// <inheritdoc />
|
||||
bool IMapComponent.MapPaused
|
||||
public void ClearMapId()
|
||||
{
|
||||
get => this.MapPaused;
|
||||
set => this.MapPaused = value;
|
||||
}
|
||||
|
||||
internal bool MapPreInit { get; set; } = false;
|
||||
|
||||
/// <inheritdoc />
|
||||
bool IMapComponent.MapPreInit
|
||||
{
|
||||
get => this.MapPreInit;
|
||||
set => this.MapPreInit = value;
|
||||
_mapIndex = MapId.Nullspace;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -75,9 +62,8 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
_mapIndex = state.MapId;
|
||||
LightingEnabled = state.LightingEnabled;
|
||||
var xformQuery = _entMan.GetEntityQuery<TransformComponent>();
|
||||
|
||||
xformQuery.GetComponent(Owner).ChangeMapId(_mapIndex, xformQuery);
|
||||
_entMan.GetComponent<TransformComponent>(Owner).ChangeMapId(_mapIndex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
if (result)
|
||||
{
|
||||
xform.ParentUid = Owner;
|
||||
xform.Parent = _entMan.GetComponent<TransformComponent>(Owner);
|
||||
|
||||
// anchor snapping
|
||||
xform.LocalPosition = Grid.GridTileToLocal(tileIndices).Position;
|
||||
|
||||
@@ -50,9 +50,11 @@ namespace Robust.Shared.GameObjects
|
||||
[NetworkedComponent]
|
||||
public sealed class MetaDataComponent : Component
|
||||
{
|
||||
[DataField("name")] internal string? _entityName;
|
||||
[DataField("desc")] internal string? _entityDescription;
|
||||
internal EntityPrototype? _entityPrototype;
|
||||
[DataField("name")]
|
||||
private string? _entityName;
|
||||
[DataField("desc")]
|
||||
private string? _entityDescription;
|
||||
private EntityPrototype? _entityPrototype;
|
||||
private bool _entityPaused;
|
||||
|
||||
// Every entity starts at tick 1, because they are conceptually created in the time between 0->1
|
||||
@@ -132,9 +134,6 @@ namespace Robust.Shared.GameObjects
|
||||
[ViewVariables]
|
||||
public EntityLifeStage EntityLifeStage { get; internal set; }
|
||||
|
||||
[ViewVariables]
|
||||
public MetaDataFlags Flags { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// The sum of our visibility layer and our parent's visibility layers.
|
||||
/// Server-only.
|
||||
@@ -148,11 +147,9 @@ namespace Robust.Shared.GameObjects
|
||||
get => _entityPaused;
|
||||
set
|
||||
{
|
||||
if (_entityPaused == value)
|
||||
return;
|
||||
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
if (value && entMan.HasComponent<IgnorePauseComponent>(Owner))
|
||||
|
||||
if (_entityPaused == value || value && entMan.HasComponent<IgnorePauseComponent>(Owner))
|
||||
return;
|
||||
|
||||
_entityPaused = value;
|
||||
@@ -164,6 +161,26 @@ namespace Robust.Shared.GameObjects
|
||||
public bool EntityInitializing => EntityLifeStage == EntityLifeStage.Initializing;
|
||||
public bool EntityDeleted => EntityLifeStage >= EntityLifeStage.Deleted;
|
||||
|
||||
|
||||
public override ComponentState GetComponentState()
|
||||
{
|
||||
return new MetaDataComponentState(_entityName, _entityDescription, EntityPrototype?.ID);
|
||||
}
|
||||
|
||||
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
|
||||
{
|
||||
base.HandleComponentState(curState, nextState);
|
||||
|
||||
if (!(curState is MetaDataComponentState state))
|
||||
return;
|
||||
|
||||
_entityName = state.Name;
|
||||
_entityDescription = state.Description;
|
||||
|
||||
if(state.PrototypeId != null)
|
||||
_entityPrototype = IoCManager.Resolve<IPrototypeManager>().Index<EntityPrototype>(state.PrototypeId);
|
||||
}
|
||||
|
||||
internal override void ClearTicks()
|
||||
{
|
||||
// Do not clear modified ticks.
|
||||
@@ -173,14 +190,4 @@ namespace Robust.Shared.GameObjects
|
||||
ClearCreationTick();
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum MetaDataFlags : byte
|
||||
{
|
||||
None = 0,
|
||||
/// <summary>
|
||||
/// Whether the entity has states specific to a particular player.
|
||||
/// </summary>
|
||||
EntitySpecific = 1 << 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ namespace Robust.Shared.GameObjects
|
||||
[DataField("color")]
|
||||
public Color Color = Color.White;
|
||||
[DataField("map")]
|
||||
public HashSet<string>? MapKeys;
|
||||
public List<string>? MapKeys;
|
||||
|
||||
public static PrototypeLayerData New()
|
||||
{
|
||||
|
||||
@@ -8,7 +8,6 @@ using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log; //Needed for release build, do not remove
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -85,12 +84,10 @@ namespace Robust.Shared.GameObjects
|
||||
if (_gridId.Equals(value)) return;
|
||||
|
||||
_gridId = value;
|
||||
var childEnumerator = ChildEnumerator;
|
||||
var xformQuery = _entMan.GetEntityQuery<TransformComponent>();
|
||||
|
||||
while (childEnumerator.MoveNext(out var child))
|
||||
foreach (var transformComponent in Children)
|
||||
{
|
||||
xformQuery.GetComponent(child.Value).GridID = value;
|
||||
var child = transformComponent;
|
||||
child.GridID = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,7 +137,7 @@ namespace Robust.Shared.GameObjects
|
||||
if (!DeferUpdates)
|
||||
{
|
||||
RebuildMatrices();
|
||||
var rotateEvent = new RotateEvent(Owner, oldRotation, _localRotation, this);
|
||||
var rotateEvent = new RotateEvent(Owner, oldRotation, _localRotation);
|
||||
_entMan.EventBus.RaiseLocalEvent(Owner, ref rotateEvent);
|
||||
}
|
||||
else
|
||||
@@ -158,18 +155,12 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
get
|
||||
{
|
||||
var parent = _parent;
|
||||
var xformQuery = _entMan.GetEntityQuery<TransformComponent>();
|
||||
var rotation = _localRotation;
|
||||
|
||||
while (parent.IsValid())
|
||||
if (_parent.IsValid())
|
||||
{
|
||||
var parentXform = xformQuery.GetComponent(parent);
|
||||
rotation += parentXform._localRotation;
|
||||
parent = parentXform.ParentUid;
|
||||
return Parent!.WorldRotation + _localRotation;
|
||||
}
|
||||
|
||||
return rotation;
|
||||
return _localRotation;
|
||||
}
|
||||
set
|
||||
{
|
||||
@@ -210,11 +201,7 @@ namespace Robust.Shared.GameObjects
|
||||
public EntityUid ParentUid
|
||||
{
|
||||
get => _parent;
|
||||
set
|
||||
{
|
||||
if (value == _parent) return;
|
||||
Parent = _entMan.GetComponent<TransformComponent>(value);
|
||||
}
|
||||
set => Parent = _entMan.GetComponent<TransformComponent>(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -224,21 +211,15 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
get
|
||||
{
|
||||
var xformQuery = _entMan.GetEntityQuery<TransformComponent>();
|
||||
var parent = _parent;
|
||||
var myMatrix = _localMatrix;
|
||||
|
||||
while (parent.IsValid())
|
||||
if (_parent.IsValid())
|
||||
{
|
||||
var parentXform = xformQuery.GetComponent(parent);
|
||||
var parentMatrix = parentXform._localMatrix;
|
||||
parent = parentXform.ParentUid;
|
||||
|
||||
var parentMatrix = Parent!.WorldMatrix;
|
||||
var myMatrix = GetLocalMatrix();
|
||||
Matrix3.Multiply(ref myMatrix, ref parentMatrix, out var result);
|
||||
myMatrix = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
return myMatrix;
|
||||
return GetLocalMatrix();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,21 +230,15 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
get
|
||||
{
|
||||
var xformQuery = _entMan.GetEntityQuery<TransformComponent>();
|
||||
var parent = _parent;
|
||||
var myMatrix = _invLocalMatrix;
|
||||
|
||||
while (parent.IsValid())
|
||||
if (_parent.IsValid())
|
||||
{
|
||||
var parentXform = xformQuery.GetComponent(parent);
|
||||
var parentMatrix = parentXform._invLocalMatrix;
|
||||
parent = parentXform.ParentUid;
|
||||
|
||||
Matrix3.Multiply(ref parentMatrix, ref myMatrix, out var result);
|
||||
myMatrix = result;
|
||||
var matP = Parent!.InvWorldMatrix;
|
||||
var myMatrix = GetLocalMatrixInv();
|
||||
Matrix3.Multiply(ref matP, ref myMatrix, out var result);
|
||||
return result;
|
||||
}
|
||||
|
||||
return myMatrix;
|
||||
return GetLocalMatrixInv();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,26 +314,25 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
if (!sameParent)
|
||||
{
|
||||
var xformQuery = _entMan.GetEntityQuery<TransformComponent>();
|
||||
changedParent = true;
|
||||
var newParent = xformQuery.GetComponent(value.EntityId);
|
||||
var newParent = _entMan.GetComponent<TransformComponent>(value.EntityId);
|
||||
|
||||
DebugTools.Assert(newParent != this,
|
||||
$"Can't parent a {nameof(TransformComponent)} to itself.");
|
||||
|
||||
// That's already our parent, don't bother attaching again.
|
||||
|
||||
var oldParent = _parent.IsValid() ? xformQuery.GetComponent(_parent) : null;
|
||||
var oldParent = Parent;
|
||||
var uid = Owner;
|
||||
oldParent?._children.Remove(uid);
|
||||
newParent._children.Add(uid);
|
||||
|
||||
// offset position from world to parent
|
||||
_parent = value.EntityId;
|
||||
ChangeMapId(newParent.MapID, xformQuery);
|
||||
ChangeMapId(newParent.MapID);
|
||||
|
||||
// Cache new GridID before raising the event.
|
||||
GridID = GetGridIndex(xformQuery);
|
||||
GridID = GetGridIndex();
|
||||
|
||||
var entParentChangedMessage = new EntParentChangedMessage(Owner, oldParent?.Owner);
|
||||
_entMan.EventBus.RaiseLocalEvent(Owner, ref entParentChangedMessage);
|
||||
@@ -369,8 +343,7 @@ namespace Robust.Shared.GameObjects
|
||||
// This may not in fact be the right thing.
|
||||
if (changedParent || !DeferUpdates)
|
||||
RebuildMatrices();
|
||||
|
||||
Dirty(_entMan);
|
||||
Dirty();
|
||||
|
||||
if (!DeferUpdates)
|
||||
{
|
||||
@@ -486,11 +459,10 @@ namespace Robust.Shared.GameObjects
|
||||
if (_children.Count == 0) yield break;
|
||||
|
||||
var xforms = _entMan.GetEntityQuery<TransformComponent>();
|
||||
var children = ChildEnumerator;
|
||||
|
||||
while (children.MoveNext(out var child))
|
||||
foreach (var child in _children)
|
||||
{
|
||||
yield return xforms.GetComponent(child.Value);
|
||||
yield return xforms.GetComponent(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -535,23 +507,22 @@ namespace Robust.Shared.GameObjects
|
||||
// Children MAY be initialized here before their parents are.
|
||||
// We do this whole dance to handle this recursively,
|
||||
// setting _mapIdInitialized along the way to avoid going to the IMapComponent every iteration.
|
||||
static MapId FindMapIdAndSet(TransformComponent xform, IEntityManager entMan, EntityQuery<TransformComponent> xformQuery)
|
||||
static MapId FindMapIdAndSet(TransformComponent p, IEntityManager entMan)
|
||||
{
|
||||
if (xform._mapIdInitialized)
|
||||
if (p._mapIdInitialized)
|
||||
{
|
||||
return xform.MapID;
|
||||
return p.MapID;
|
||||
}
|
||||
|
||||
MapId value;
|
||||
|
||||
if (xform._parent.IsValid())
|
||||
if (p._parent.IsValid())
|
||||
{
|
||||
value = FindMapIdAndSet(xformQuery.GetComponent(xform._parent), entMan, xformQuery);
|
||||
value = FindMapIdAndSet((TransformComponent) p.Parent!, entMan);
|
||||
}
|
||||
else
|
||||
{
|
||||
// second level node, terminates recursion up the branch of the tree
|
||||
if (entMan.TryGetComponent(xform.Owner, out IMapComponent? mapComp))
|
||||
if (entMan.TryGetComponent(p.Owner, out IMapComponent? mapComp))
|
||||
{
|
||||
value = mapComp.WorldMap;
|
||||
}
|
||||
@@ -561,16 +532,14 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
xform.MapID = value;
|
||||
xform._mapIdInitialized = true;
|
||||
p.MapID = value;
|
||||
p._mapIdInitialized = true;
|
||||
return value;
|
||||
}
|
||||
|
||||
var xformQuery = _entMan.GetEntityQuery<TransformComponent>();
|
||||
|
||||
if (!_mapIdInitialized)
|
||||
{
|
||||
FindMapIdAndSet(this, _entMan, xformQuery);
|
||||
FindMapIdAndSet(this, _entMan);
|
||||
|
||||
_mapIdInitialized = true;
|
||||
}
|
||||
@@ -580,14 +549,14 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
// Note that _children is a SortedSet<EntityUid>,
|
||||
// so duplicate additions (which will happen) don't matter.
|
||||
xformQuery.GetComponent(_parent)._children.Add(Owner);
|
||||
((TransformComponent) Parent!)._children.Add(Owner);
|
||||
}
|
||||
|
||||
GridID = GetGridIndex(xformQuery);
|
||||
GridID = GetGridIndex();
|
||||
RebuildMatrices();
|
||||
}
|
||||
|
||||
private GridId GetGridIndex(EntityQuery<TransformComponent> xformQuery)
|
||||
private GridId GetGridIndex()
|
||||
{
|
||||
if (_entMan.HasComponent<IMapComponent>(Owner))
|
||||
{
|
||||
@@ -601,7 +570,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
if (_parent.IsValid())
|
||||
{
|
||||
return xformQuery.GetComponent(_parent).GridID;
|
||||
return Parent!.GridID;
|
||||
}
|
||||
|
||||
return _mapManager.TryFindGridAt(MapID, WorldPosition, out var mapgrid) ? mapgrid.Index : GridId.Invalid;
|
||||
@@ -616,13 +585,13 @@ namespace Robust.Shared.GameObjects
|
||||
base.Startup();
|
||||
|
||||
// Keep the cached matrices in sync with the fields.
|
||||
Dirty(_entMan);
|
||||
Dirty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run MoveEvent, RotateEvent, and UpdateEntityTree updates.
|
||||
/// </summary>
|
||||
public void RunDeferred()
|
||||
public void RunDeferred(Box2 worldAABB)
|
||||
{
|
||||
// if we resolved to (close enough) to the OG position then no update.
|
||||
if ((_oldCoords == null || _oldCoords.Equals(Coordinates)) &&
|
||||
@@ -635,14 +604,14 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
if (_oldCoords != null)
|
||||
{
|
||||
var moveEvent = new MoveEvent(Owner, _oldCoords.Value, Coordinates, this);
|
||||
var moveEvent = new MoveEvent(Owner, _oldCoords.Value, Coordinates, this, worldAABB);
|
||||
_entMan.EventBus.RaiseLocalEvent(Owner, ref moveEvent);
|
||||
_oldCoords = null;
|
||||
}
|
||||
|
||||
if (_oldLocalRotation != null)
|
||||
{
|
||||
var rotateEvent = new RotateEvent(Owner, _oldLocalRotation.Value, _localRotation, this);
|
||||
var rotateEvent = new RotateEvent(Owner, _oldLocalRotation.Value, _localRotation, worldAABB);
|
||||
_entMan.EventBus.RaiseLocalEvent(Owner, ref rotateEvent);
|
||||
_oldLocalRotation = null;
|
||||
}
|
||||
@@ -752,36 +721,25 @@ namespace Robust.Shared.GameObjects
|
||||
Coordinates = new EntityCoordinates(newParent.Owner, newParent.InvWorldMatrix.Transform(WorldPosition));
|
||||
}
|
||||
|
||||
internal void ChangeMapId(MapId newMapId, EntityQuery<TransformComponent> xformQuery)
|
||||
internal void ChangeMapId(MapId newMapId)
|
||||
{
|
||||
if (newMapId == MapID)
|
||||
return;
|
||||
|
||||
var oldMapId = MapID;
|
||||
|
||||
//Set Paused state
|
||||
var mapPaused = _mapManager.IsMapPaused(newMapId);
|
||||
var metaData = _entMan.GetComponent<MetaDataComponent>(Owner);
|
||||
metaData.EntityPaused = mapPaused;
|
||||
|
||||
MapID = newMapId;
|
||||
MapIdChanged(oldMapId);
|
||||
var xforms = _entMan.GetEntityQuery<TransformComponent>();
|
||||
var metaEnts = _entMan.GetEntityQuery<MetaDataComponent>();
|
||||
UpdateChildMapIdsRecursive(MapID, mapPaused, xforms, metaEnts);
|
||||
UpdateChildMapIdsRecursive(MapID, _entMan);
|
||||
}
|
||||
|
||||
private void UpdateChildMapIdsRecursive(MapId newMapId, bool mapPaused, EntityQuery<TransformComponent> xformQuery, EntityQuery<MetaDataComponent> metaQuery)
|
||||
private void UpdateChildMapIdsRecursive(MapId newMapId, IEntityManager entMan)
|
||||
{
|
||||
var childEnumerator = ChildEnumerator;
|
||||
var xforms = _entMan.GetEntityQuery<TransformComponent>();
|
||||
|
||||
while (childEnumerator.MoveNext(out var child))
|
||||
foreach (var child in _children)
|
||||
{
|
||||
//Set Paused state
|
||||
var metaData = metaQuery.GetComponent(child.Value);
|
||||
metaData.EntityPaused = mapPaused;
|
||||
|
||||
var concrete = xformQuery.GetComponent(child.Value);
|
||||
var concrete = xforms.GetComponent(child);
|
||||
var old = concrete.MapID;
|
||||
|
||||
concrete.MapID = newMapId;
|
||||
@@ -789,7 +747,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
if (concrete.ChildCount != 0)
|
||||
{
|
||||
concrete.UpdateChildMapIdsRecursive(newMapId, mapPaused, xformQuery, metaQuery);
|
||||
concrete.UpdateChildMapIdsRecursive(newMapId, entMan);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -842,14 +800,14 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
var parent = _parent;
|
||||
var worldRot = _localRotation;
|
||||
var worldMatrix = _localMatrix;
|
||||
var worldMatrix = GetLocalMatrix();
|
||||
|
||||
// By doing these all at once we can elide multiple IsValid + GetComponent calls
|
||||
while (parent.IsValid())
|
||||
{
|
||||
var xform = xforms.GetComponent(parent);
|
||||
worldRot += xform.LocalRotation;
|
||||
var parentMatrix = xform._localMatrix;
|
||||
var parentMatrix = xform.GetLocalMatrix();
|
||||
Matrix3.Multiply(ref worldMatrix, ref parentMatrix, out var result);
|
||||
worldMatrix = result;
|
||||
parent = xform.ParentUid;
|
||||
@@ -903,8 +861,8 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
var parent = _parent;
|
||||
var worldRot = _localRotation;
|
||||
var invMatrix = _invLocalMatrix;
|
||||
var worldMatrix = _localMatrix;
|
||||
var invMatrix = GetLocalMatrixInv();
|
||||
var worldMatrix = GetLocalMatrix();
|
||||
|
||||
// By doing these all at once we can elide multiple IsValid + GetComponent calls
|
||||
while (parent.IsValid())
|
||||
@@ -912,11 +870,11 @@ namespace Robust.Shared.GameObjects
|
||||
var xform = xformQuery.GetComponent(parent);
|
||||
worldRot += xform.LocalRotation;
|
||||
|
||||
var parentMatrix = xform._localMatrix;
|
||||
var parentMatrix = xform.GetLocalMatrix();
|
||||
Matrix3.Multiply(ref worldMatrix, ref parentMatrix, out var result);
|
||||
worldMatrix = result;
|
||||
|
||||
var parentInvMatrix = xform._invLocalMatrix;
|
||||
var parentInvMatrix = xform.GetLocalMatrixInv();
|
||||
Matrix3.Multiply(ref parentInvMatrix, ref invMatrix, out var invResult);
|
||||
invMatrix = invResult;
|
||||
|
||||
@@ -1047,6 +1005,16 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
public Matrix3 GetLocalMatrix()
|
||||
{
|
||||
return _localMatrix;
|
||||
}
|
||||
|
||||
public Matrix3 GetLocalMatrixInv()
|
||||
{
|
||||
return _invLocalMatrix;
|
||||
}
|
||||
|
||||
private void RebuildMatrices()
|
||||
{
|
||||
var pos = _localPosition;
|
||||
@@ -1141,18 +1109,24 @@ namespace Robust.Shared.GameObjects
|
||||
[ByRefEvent]
|
||||
public readonly struct MoveEvent
|
||||
{
|
||||
public MoveEvent(EntityUid sender, EntityCoordinates oldPos, EntityCoordinates newPos, TransformComponent component)
|
||||
public MoveEvent(EntityUid sender, EntityCoordinates oldPos, EntityCoordinates newPos, TransformComponent component, Box2? worldAABB = null)
|
||||
{
|
||||
Sender = sender;
|
||||
OldPosition = oldPos;
|
||||
NewPosition = newPos;
|
||||
Component = component;
|
||||
WorldAABB = worldAABB;
|
||||
}
|
||||
|
||||
public readonly EntityUid Sender;
|
||||
public readonly EntityCoordinates OldPosition;
|
||||
public readonly EntityCoordinates NewPosition;
|
||||
public readonly TransformComponent Component;
|
||||
|
||||
/// <summary>
|
||||
/// New AABB of the entity.
|
||||
/// </summary>
|
||||
public readonly Box2? WorldAABB;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1161,18 +1135,22 @@ namespace Robust.Shared.GameObjects
|
||||
[ByRefEvent]
|
||||
public readonly struct RotateEvent
|
||||
{
|
||||
public RotateEvent(EntityUid sender, Angle oldRotation, Angle newRotation, TransformComponent xform)
|
||||
public RotateEvent(EntityUid sender, Angle oldRotation, Angle newRotation, Box2? worldAABB = null)
|
||||
{
|
||||
Sender = sender;
|
||||
OldRotation = oldRotation;
|
||||
NewRotation = newRotation;
|
||||
Component = xform;
|
||||
WorldAABB = worldAABB;
|
||||
}
|
||||
|
||||
public readonly EntityUid Sender;
|
||||
public readonly Angle OldRotation;
|
||||
public readonly Angle NewRotation;
|
||||
public readonly TransformComponent Component;
|
||||
|
||||
/// <summary>
|
||||
/// New AABB of the entity.
|
||||
/// </summary>
|
||||
public readonly Box2? WorldAABB;
|
||||
}
|
||||
|
||||
public struct TransformChildrenEnumerator : IDisposable
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace Robust.Shared.GameObjects
|
||||
[IoC.Dependency] protected readonly IEntitySystemManager EntitySystemManager = default!;
|
||||
[IoC.Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[IoC.Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[IoC.Dependency] private readonly IPauseManager _pauseManager = default!;
|
||||
|
||||
#endregion Dependencies
|
||||
|
||||
@@ -82,7 +83,6 @@ namespace Robust.Shared.GameObjects
|
||||
if (Started)
|
||||
throw new InvalidOperationException("Startup() called multiple times");
|
||||
|
||||
// TODO: Probably better to call this on its own given it's so infrequent.
|
||||
EntitySystemManager.Initialize();
|
||||
Started = true;
|
||||
}
|
||||
@@ -277,13 +277,12 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
private void RecursiveDeleteEntity(EntityUid uid)
|
||||
{
|
||||
if (!TryGetComponent(uid, out MetaDataComponent metadata) || metadata.EntityDeleted)
|
||||
if (!TryGetComponent(uid, out MetaDataComponent metadata) || metadata.EntityDeleted)
|
||||
return; //TODO: Why was this still a child if it was already deleted?
|
||||
|
||||
var transform = GetComponent<TransformComponent>(uid);
|
||||
metadata.EntityLifeStage = EntityLifeStage.Terminating;
|
||||
var ev = new EntityTerminatingEvent(uid);
|
||||
EventBus.RaiseLocalEvent(uid, ref ev, false);
|
||||
EventBus.RaiseLocalEvent(uid, new EntityTerminatingEvent(), false);
|
||||
|
||||
// DeleteEntity modifies our _children collection, we must cache the collection to iterate properly
|
||||
foreach (var child in transform._children.ToArray())
|
||||
@@ -439,7 +438,7 @@ namespace Robust.Shared.GameObjects
|
||||
StartEntity(entity);
|
||||
|
||||
// If the map we're initializing the entity on is initialized, run map init on it.
|
||||
if (_mapManager.IsMapInitialized(mapId))
|
||||
if (_pauseManager.IsMapInitialized(mapId))
|
||||
entity.RunMapInit();
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
@@ -571,47 +571,4 @@ public partial class EntitySystem
|
||||
=> new($"Entity {uid} does not have a component of type {typeof(T)}");
|
||||
|
||||
#endregion
|
||||
|
||||
#region Entity Query
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected EntityQuery<T> GetEntityQuery<T>() where T : Component
|
||||
{
|
||||
return EntityManager.GetEntityQuery<T>();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected IEnumerable<TComp1> EntityQuery<TComp1>(bool includePaused = false) where TComp1 : Component
|
||||
{
|
||||
return EntityManager.EntityQuery<TComp1>(includePaused);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected IEnumerable<(TComp1, TComp2)> EntityQuery<TComp1, TComp2>(bool includePaused = false)
|
||||
where TComp1 : Component
|
||||
where TComp2 : Component
|
||||
{
|
||||
return EntityManager.EntityQuery<TComp1, TComp2>(includePaused);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected IEnumerable<(TComp1, TComp2, TComp3)> EntityQuery<TComp1, TComp2, TComp3>(bool includePaused = false)
|
||||
where TComp1 : Component
|
||||
where TComp2 : Component
|
||||
where TComp3 : Component
|
||||
{
|
||||
return EntityManager.EntityQuery<TComp1, TComp2, TComp3>(includePaused);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected IEnumerable<(TComp1, TComp2, TComp3, TComp4)> EntityQuery<TComp1, TComp2, TComp3, TComp4>(bool includePaused = false)
|
||||
where TComp1 : Component
|
||||
where TComp2 : Component
|
||||
where TComp3 : Component
|
||||
where TComp4 : Component
|
||||
{
|
||||
return EntityManager.EntityQuery<TComp1, TComp2, TComp3, TComp4>(includePaused);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -105,29 +105,14 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Initialize(bool discover = true)
|
||||
public void Initialize()
|
||||
{
|
||||
// Tempted to make this an assert
|
||||
// However, EntityManager calls this directly so we'd need to remove that and manually call it.
|
||||
if (_initialized) return;
|
||||
|
||||
var excludedTypes = new HashSet<Type>();
|
||||
|
||||
_systemDependencyCollection = new(IoCManager.Instance!);
|
||||
var subTypes = new Dictionary<Type, Type>();
|
||||
_systemTypes.Clear();
|
||||
IEnumerable<Type> systems;
|
||||
|
||||
if (discover)
|
||||
{
|
||||
systems = _reflectionManager.GetAllChildren<IEntitySystem>().Concat(_extraLoadedTypes);
|
||||
}
|
||||
else
|
||||
{
|
||||
systems = _extraLoadedTypes;
|
||||
}
|
||||
|
||||
foreach (var type in systems)
|
||||
foreach (var type in _reflectionManager.GetAllChildren<IEntitySystem>().Concat(_extraLoadedTypes))
|
||||
{
|
||||
Logger.DebugS("go.sys", "Initializing entity system {0}", type);
|
||||
|
||||
@@ -266,7 +251,6 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_extraLoadedTypes.Clear();
|
||||
_systemTypes.Clear();
|
||||
_updateOrder = Array.Empty<UpdateReg>();
|
||||
_frameUpdateOrder = Array.Empty<IEntitySystem>();
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
/// Raised when an entity parent is changed.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly struct EntParentChangedMessage
|
||||
public sealed class EntParentChangedMessage : EntityEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Entity that was adopted. The transform component has a property with the new parent.
|
||||
|
||||
@@ -3,14 +3,5 @@ namespace Robust.Shared.GameObjects
|
||||
/// <summary>
|
||||
/// The children of this entity are about to be deleted.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public struct EntityTerminatingEvent
|
||||
{
|
||||
public EntityUid Owner;
|
||||
|
||||
public EntityTerminatingEvent(EntityUid uid)
|
||||
{
|
||||
Owner = uid;
|
||||
}
|
||||
}
|
||||
public sealed class EntityTerminatingEvent : EntityEventArgs { }
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using JetBrains.Annotations;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
@@ -171,7 +172,7 @@ namespace Robust.Shared.GameObjects
|
||||
set
|
||||
{
|
||||
if (MetaData is {} metaData)
|
||||
metaData.EntityDescription = value;
|
||||
metaData.EntityName = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -89,9 +89,8 @@ namespace Robust.Shared.GameObjects
|
||||
/// <summary>
|
||||
/// Initialize, discover systems and initialize them through <see cref="IEntitySystem.Initialize"/>.
|
||||
/// </summary>
|
||||
/// <param name="discover">Whether we should automatically find systems or have they been supplied already.</param>
|
||||
/// <seealso cref="IEntitySystem.Initialize"/>
|
||||
void Initialize(bool discover = true);
|
||||
void Initialize();
|
||||
|
||||
/// <summary>
|
||||
/// Clean up, shut down all systems through <see cref="IEntitySystem.Shutdown"/> and remove them.
|
||||
|
||||
@@ -1,476 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.GameObjects;
|
||||
|
||||
public sealed partial class EntityLookupSystem
|
||||
{
|
||||
// TODO: Need to nuke most of the below and cleanup when entitylookup gets optimised some more (physics + containers).
|
||||
|
||||
private LookupsEnumerator GetLookupsIntersecting(MapId mapId, Box2 worldAABB)
|
||||
{
|
||||
_mapManager.FindGridsIntersectingEnumerator(mapId, worldAABB, out var gridEnumerator, true);
|
||||
|
||||
return new LookupsEnumerator(EntityManager, _mapManager, mapId, gridEnumerator);
|
||||
}
|
||||
|
||||
private struct LookupsEnumerator
|
||||
{
|
||||
private IEntityManager EntityManager;
|
||||
private IMapManager _mapManager;
|
||||
|
||||
private MapId _mapId;
|
||||
private FindGridsEnumerator _enumerator;
|
||||
|
||||
private bool _final;
|
||||
|
||||
public LookupsEnumerator(IEntityManager entityManager, IMapManager mapManager, MapId mapId, FindGridsEnumerator enumerator)
|
||||
{
|
||||
EntityManager = entityManager;
|
||||
_mapManager = mapManager;
|
||||
|
||||
_mapId = mapId;
|
||||
_enumerator = enumerator;
|
||||
_final = false;
|
||||
}
|
||||
|
||||
public bool MoveNext([NotNullWhen(true)] out EntityLookupComponent? component)
|
||||
{
|
||||
if (!_enumerator.MoveNext(out var grid))
|
||||
{
|
||||
if (_final || _mapId == MapId.Nullspace)
|
||||
{
|
||||
component = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
_final = true;
|
||||
EntityUid mapUid = _mapManager.GetMapEntityIdOrThrow(_mapId);
|
||||
component = EntityManager.GetComponent<EntityLookupComponent>(mapUid);
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: Recursive and all that.
|
||||
component = EntityManager.GetComponent<EntityLookupComponent>(grid.GridEntityId);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<EntityUid> GetAnchored(MapId mapId, Box2 worldAABB, LookupFlags flags)
|
||||
{
|
||||
if ((flags & LookupFlags.IncludeAnchored) == 0x0) yield break;
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
|
||||
{
|
||||
foreach (var uid in grid.GetAnchoredEntities(worldAABB))
|
||||
{
|
||||
if (!EntityManager.EntityExists(uid)) continue;
|
||||
yield return uid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<EntityUid> GetAnchored(MapId mapId, Box2Rotated worldBounds, LookupFlags flags)
|
||||
{
|
||||
if ((flags & LookupFlags.IncludeAnchored) == 0x0) yield break;
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldBounds))
|
||||
{
|
||||
foreach (var uid in grid.GetAnchoredEntities(worldBounds))
|
||||
{
|
||||
if (!EntityManager.EntityExists(uid)) continue;
|
||||
yield return uid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool AnyEntitiesIntersecting(MapId mapId, Box2 box, LookupFlags flags = LookupFlags.IncludeAnchored)
|
||||
{
|
||||
var found = false;
|
||||
var enumerator = GetLookupsIntersecting(mapId, box);
|
||||
|
||||
while (enumerator.MoveNext(out var lookup))
|
||||
{
|
||||
var offsetBox = EntityManager.GetComponent<TransformComponent>(lookup.Owner).InvWorldMatrix.TransformBox(box);
|
||||
|
||||
lookup.Tree.QueryAabb(ref found, (ref bool found, in EntityUid ent) =>
|
||||
{
|
||||
if (EntityManager.Deleted(ent))
|
||||
return true;
|
||||
|
||||
found = true;
|
||||
return false;
|
||||
|
||||
}, offsetBox, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
foreach (var _ in GetAnchored(mapId, box, flags))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
public void FastEntitiesIntersecting(in MapId mapId, ref Box2 worldAABB, EntityUidQueryCallback callback, LookupFlags flags = LookupFlags.IncludeAnchored)
|
||||
{
|
||||
var enumerator = GetLookupsIntersecting(mapId, worldAABB);
|
||||
while (enumerator.MoveNext(out var lookup))
|
||||
{
|
||||
var offsetBox = EntityManager.GetComponent<TransformComponent>(lookup.Owner).InvWorldMatrix.TransformBox(worldAABB);
|
||||
|
||||
lookup.Tree._b2Tree.FastQuery(ref offsetBox, (ref EntityUid data) => callback(data));
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.IncludeAnchored) != 0x0)
|
||||
{
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
|
||||
{
|
||||
foreach (var uid in grid.GetAnchoredEntities(worldAABB))
|
||||
{
|
||||
if (!EntityManager.EntityExists(uid)) continue;
|
||||
callback(uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void FastEntitiesIntersecting(EntityLookupComponent lookup, ref Box2 localAABB, EntityUidQueryCallback callback)
|
||||
{
|
||||
lookup.Tree._b2Tree.FastQuery(ref localAABB, (ref EntityUid data) => callback(data));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<EntityUid> GetEntitiesIntersecting(MapId mapId, Box2 worldAABB, LookupFlags flags = LookupFlags.IncludeAnchored)
|
||||
{
|
||||
if (mapId == MapId.Nullspace) return Enumerable.Empty<EntityUid>();
|
||||
|
||||
var list = new List<EntityUid>();
|
||||
var enumerator = GetLookupsIntersecting(mapId, worldAABB);
|
||||
|
||||
while (enumerator.MoveNext(out var lookup))
|
||||
{
|
||||
var offsetBox = EntityManager.GetComponent<TransformComponent>(lookup.Owner).InvWorldMatrix.TransformBox(worldAABB);
|
||||
|
||||
lookup.Tree.QueryAabb(ref list, (ref List<EntityUid> list, in EntityUid ent) =>
|
||||
{
|
||||
if (!EntityManager.Deleted(ent))
|
||||
{
|
||||
list.Add(ent);
|
||||
}
|
||||
return true;
|
||||
}, offsetBox, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
|
||||
foreach (var ent in GetAnchored(mapId, worldAABB, flags))
|
||||
{
|
||||
list.Add(ent);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<EntityUid> GetEntitiesIntersecting(MapId mapId, Box2Rotated worldBounds, LookupFlags flags = LookupFlags.IncludeAnchored)
|
||||
{
|
||||
if (mapId == MapId.Nullspace) return Enumerable.Empty<EntityUid>();
|
||||
|
||||
var list = new List<EntityUid>();
|
||||
var worldAABB = worldBounds.CalcBoundingBox();
|
||||
var enumerator = GetLookupsIntersecting(mapId, worldAABB);
|
||||
|
||||
while (enumerator.MoveNext(out var lookup))
|
||||
{
|
||||
var offsetBox = EntityManager.GetComponent<TransformComponent>(lookup.Owner).InvWorldMatrix.TransformBox(worldBounds);
|
||||
|
||||
lookup.Tree.QueryAabb(ref list, (ref List<EntityUid> list, in EntityUid ent) =>
|
||||
{
|
||||
if (!EntityManager.Deleted(ent))
|
||||
{
|
||||
list.Add(ent);
|
||||
}
|
||||
return true;
|
||||
}, offsetBox, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
|
||||
foreach (var ent in GetAnchored(mapId, worldBounds, flags))
|
||||
{
|
||||
list.Add(ent);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<EntityUid> GetEntitiesIntersecting(MapId mapId, Vector2 position, LookupFlags flags = LookupFlags.IncludeAnchored)
|
||||
{
|
||||
if (mapId == MapId.Nullspace) return Enumerable.Empty<EntityUid>();
|
||||
|
||||
var aabb = new Box2(position, position).Enlarged(PointEnlargeRange);
|
||||
var list = new List<EntityUid>();
|
||||
var state = (list, position);
|
||||
|
||||
var enumerator = GetLookupsIntersecting(mapId, aabb);
|
||||
|
||||
while (enumerator.MoveNext(out var lookup))
|
||||
{
|
||||
var localPoint = EntityManager.GetComponent<TransformComponent>(lookup.Owner).InvWorldMatrix.Transform(position);
|
||||
|
||||
lookup.Tree.QueryPoint(ref state, (ref (List<EntityUid> list, Vector2 position) state, in EntityUid ent) =>
|
||||
{
|
||||
if (Intersecting(ent, state.position))
|
||||
{
|
||||
state.list.Add(ent);
|
||||
}
|
||||
return true;
|
||||
}, localPoint, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.IncludeAnchored) != 0x0 &&
|
||||
_mapManager.TryFindGridAt(mapId, position, out var grid) &&
|
||||
grid.TryGetTileRef(position, out var tile))
|
||||
{
|
||||
foreach (var uid in grid.GetAnchoredEntities(tile.GridIndices))
|
||||
{
|
||||
if (!EntityManager.EntityExists(uid)) continue;
|
||||
state.list.Add(uid);
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<EntityUid> GetEntitiesIntersecting(MapCoordinates position, LookupFlags flags = LookupFlags.IncludeAnchored)
|
||||
{
|
||||
return GetEntitiesIntersecting(position.MapId, position.Position, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<EntityUid> GetEntitiesIntersecting(EntityCoordinates position, LookupFlags flags = LookupFlags.IncludeAnchored)
|
||||
{
|
||||
var mapPos = position.ToMap(EntityManager);
|
||||
return GetEntitiesIntersecting(mapPos.MapId, mapPos.Position, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<EntityUid> GetEntitiesIntersecting(EntityUid entity, float enlarged = 0f, LookupFlags flags = LookupFlags.IncludeAnchored)
|
||||
{
|
||||
var worldAABB = GetWorldAABB(entity);
|
||||
var xform = EntityManager.GetComponent<TransformComponent>(entity);
|
||||
|
||||
var (worldPos, worldRot) = xform.GetWorldPositionRotation();
|
||||
|
||||
var enumerator = GetLookupsIntersecting(xform.MapID, worldAABB);
|
||||
var list = new List<EntityUid>();
|
||||
|
||||
while (enumerator.MoveNext(out var lookup))
|
||||
{
|
||||
// To get the tightest bounds possible we'll re-calculate it for each lookup.
|
||||
var localBounds = GetLookupBounds(entity, lookup, worldPos, worldRot, enlarged);
|
||||
|
||||
lookup.Tree.QueryAabb(ref list, (ref List<EntityUid> list, in EntityUid ent) =>
|
||||
{
|
||||
if (!EntityManager.Deleted(ent))
|
||||
{
|
||||
list.Add(ent);
|
||||
}
|
||||
return true;
|
||||
}, localBounds, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
|
||||
foreach (var ent in GetAnchored(xform.MapID, worldAABB, flags))
|
||||
{
|
||||
list.Add(ent);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private Box2 GetLookupBounds(EntityUid uid, EntityLookupComponent lookup, Vector2 worldPos, Angle worldRot, float enlarged)
|
||||
{
|
||||
var (_, lookupRot, lookupInvWorldMatrix) = EntityManager.GetComponent<TransformComponent>(lookup.Owner).GetWorldPositionRotationInvMatrix();
|
||||
|
||||
var localPos = lookupInvWorldMatrix.Transform(worldPos);
|
||||
var localRot = worldRot - lookupRot;
|
||||
|
||||
if (EntityManager.TryGetComponent(uid, out FixturesComponent? manager))
|
||||
{
|
||||
var transform = new Transform(localPos, localRot);
|
||||
Box2? aabb = null;
|
||||
|
||||
foreach (var (_, fixture) in manager.Fixtures)
|
||||
{
|
||||
if (!fixture.Hard) continue;
|
||||
for (var i = 0; i < fixture.Shape.ChildCount; i++)
|
||||
{
|
||||
aabb = aabb?.Union(fixture.Shape.ComputeAABB(transform, i)) ?? fixture.Shape.ComputeAABB(transform, i);
|
||||
}
|
||||
}
|
||||
|
||||
if (aabb != null)
|
||||
{
|
||||
return aabb.Value.Enlarged(enlarged);
|
||||
}
|
||||
}
|
||||
|
||||
// So IsEmpty checks don't get triggered
|
||||
return new Box2(localPos - float.Epsilon, localPos + float.Epsilon);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsIntersecting(EntityUid entityOne, EntityUid entityTwo)
|
||||
{
|
||||
var position = EntityManager.GetComponent<TransformComponent>(entityOne).MapPosition.Position;
|
||||
return Intersecting(entityTwo, position);
|
||||
}
|
||||
|
||||
private bool Intersecting(EntityUid entity, Vector2 mapPosition)
|
||||
{
|
||||
if (EntityManager.TryGetComponent(entity, out IPhysBody? component))
|
||||
{
|
||||
if (component.GetWorldAABB().Contains(mapPosition))
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var transform = EntityManager.GetComponent<TransformComponent>(entity);
|
||||
var entPos = transform.WorldPosition;
|
||||
if (MathHelper.CloseToPercent(entPos.X, mapPosition.X)
|
||||
&& MathHelper.CloseToPercent(entPos.Y, mapPosition.Y))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<EntityUid> GetEntitiesInRange(EntityCoordinates position, float range, LookupFlags flags = LookupFlags.IncludeAnchored)
|
||||
{
|
||||
var mapCoordinates = position.ToMap(EntityManager);
|
||||
var mapPosition = mapCoordinates.Position;
|
||||
var aabb = new Box2(mapPosition - new Vector2(range, range),
|
||||
mapPosition + new Vector2(range, range));
|
||||
return GetEntitiesIntersecting(mapCoordinates.MapId, aabb, flags);
|
||||
// TODO: Use a circle shape here mate
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<EntityUid> GetEntitiesInRange(MapId mapId, Box2 box, float range, LookupFlags flags = LookupFlags.IncludeAnchored)
|
||||
{
|
||||
var aabb = box.Enlarged(range);
|
||||
return GetEntitiesIntersecting(mapId, aabb, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<EntityUid> GetEntitiesInRange(MapId mapId, Vector2 point, float range, LookupFlags flags = LookupFlags.IncludeAnchored)
|
||||
{
|
||||
var aabb = new Box2(point, point).Enlarged(range);
|
||||
return GetEntitiesIntersecting(mapId, aabb, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<EntityUid> GetEntitiesInRange(EntityUid entity, float range, LookupFlags flags = LookupFlags.IncludeAnchored)
|
||||
{
|
||||
var worldAABB = GetWorldAABB(entity);
|
||||
return GetEntitiesInRange(EntityManager.GetComponent<TransformComponent>(entity).MapID, worldAABB, range, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<EntityUid> GetEntitiesInArc(EntityCoordinates coordinates, float range, Angle direction,
|
||||
float arcWidth, LookupFlags flags = LookupFlags.IncludeAnchored)
|
||||
{
|
||||
var position = coordinates.ToMap(EntityManager).Position;
|
||||
|
||||
foreach (var entity in GetEntitiesInRange(coordinates, range * 2, flags))
|
||||
{
|
||||
var angle = new Angle(EntityManager.GetComponent<TransformComponent>(entity).WorldPosition - position);
|
||||
if (angle.Degrees < direction.Degrees + arcWidth / 2 &&
|
||||
angle.Degrees > direction.Degrees - arcWidth / 2)
|
||||
yield return entity;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<EntityUid> GetEntitiesInMap(MapId mapId, LookupFlags flags = LookupFlags.IncludeAnchored)
|
||||
{
|
||||
DebugTools.Assert((flags & LookupFlags.Approximate) == 0x0);
|
||||
|
||||
foreach (EntityLookupComponent comp in EntityManager.EntityQuery<EntityLookupComponent>(true))
|
||||
{
|
||||
if (EntityManager.GetComponent<TransformComponent>(comp.Owner).MapID != mapId) continue;
|
||||
|
||||
foreach (var entity in comp.Tree)
|
||||
{
|
||||
if (EntityManager.Deleted(entity)) continue;
|
||||
|
||||
yield return entity;
|
||||
}
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.IncludeAnchored) == 0x0) yield break;
|
||||
|
||||
foreach (var grid in _mapManager.GetAllMapGrids(mapId))
|
||||
{
|
||||
foreach (var tile in grid.GetAllTiles())
|
||||
{
|
||||
foreach (var uid in grid.GetAnchoredEntities(tile.GridIndices))
|
||||
{
|
||||
if (!EntityManager.EntityExists(uid)) continue;
|
||||
yield return uid;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<EntityUid> GetEntitiesAt(MapId mapId, Vector2 position, LookupFlags flags = LookupFlags.IncludeAnchored)
|
||||
{
|
||||
if (mapId == MapId.Nullspace) return Enumerable.Empty<EntityUid>();
|
||||
|
||||
var list = new List<EntityUid>();
|
||||
|
||||
var state = (list, position);
|
||||
|
||||
var aabb = new Box2(position, position).Enlarged(PointEnlargeRange);
|
||||
var enumerator = GetLookupsIntersecting(mapId, aabb);
|
||||
|
||||
while (enumerator.MoveNext(out var lookup))
|
||||
{
|
||||
var offsetPos = EntityManager.GetComponent<TransformComponent>(lookup.Owner).InvWorldMatrix.Transform(position);
|
||||
|
||||
lookup.Tree.QueryPoint(ref state, (ref (List<EntityUid> list, Vector2 position) state, in EntityUid ent) =>
|
||||
{
|
||||
state.list.Add(ent);
|
||||
return true;
|
||||
}, offsetPos, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.IncludeAnchored) != 0x0)
|
||||
{
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, aabb))
|
||||
{
|
||||
foreach (var uid in grid.GetAnchoredEntities(aabb))
|
||||
{
|
||||
if (!EntityManager.EntityExists(uid)) continue;
|
||||
list.Add(uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Shared.GameObjects;
|
||||
|
||||
public sealed partial class EntityLookupSystem
|
||||
public sealed partial class EntityLookup
|
||||
{
|
||||
#region Grid Methods
|
||||
|
||||
@@ -21,7 +21,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 Enumerable.Empty<EntityUid>();
|
||||
|
||||
var lookup = EntityManager.GetComponent<EntityLookupComponent>(grid.GridEntityId);
|
||||
var lookup = _entityManager.GetComponent<EntityLookupComponent>(grid.GridEntityId);
|
||||
var results = new HashSet<EntityUid>();
|
||||
var tileSize = grid.TileSize;
|
||||
|
||||
@@ -32,13 +32,13 @@ public sealed partial class EntityLookupSystem
|
||||
|
||||
lookup.Tree._b2Tree.FastQuery(ref aabb, (ref EntityUid data) =>
|
||||
{
|
||||
if (EntityManager.Deleted(data)) return;
|
||||
if (_entityManager.Deleted(data)) return;
|
||||
results.Add(data);
|
||||
});
|
||||
|
||||
foreach (var ent in grid.GetAnchoredEntities(index))
|
||||
{
|
||||
if (EntityManager.Deleted(ent)) continue;
|
||||
if (_entityManager.Deleted(ent)) continue;
|
||||
results.Add(ent);
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,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 Enumerable.Empty<EntityUid>();
|
||||
|
||||
var lookup = EntityManager.GetComponent<EntityLookupComponent>(grid.GridEntityId);
|
||||
var lookup = _entityManager.GetComponent<EntityLookupComponent>(grid.GridEntityId);
|
||||
var tileSize = grid.TileSize;
|
||||
|
||||
var aabb = GetLocalBounds(gridIndices, tileSize);
|
||||
@@ -59,13 +59,13 @@ public sealed partial class EntityLookupSystem
|
||||
|
||||
lookup.Tree._b2Tree.FastQuery(ref aabb, (ref EntityUid data) =>
|
||||
{
|
||||
if (EntityManager.Deleted(data)) return;
|
||||
if (_entityManager.Deleted(data)) return;
|
||||
results.Add(data);
|
||||
});
|
||||
|
||||
foreach (var ent in grid.GetAnchoredEntities(gridIndices))
|
||||
{
|
||||
if (EntityManager.Deleted(ent)) continue;
|
||||
if (_entityManager.Deleted(ent)) continue;
|
||||
results.Add(ent);
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ public sealed partial class EntityLookupSystem
|
||||
|
||||
if (worldMatrix == null || angle == null)
|
||||
{
|
||||
var gridXform = EntityManager.GetComponent<TransformComponent>(grid.GridEntityId);
|
||||
var gridXform = _entityManager.GetComponent<TransformComponent>(grid.GridEntityId);
|
||||
var (_, wAng, wMat) = gridXform.GetWorldPositionRotationMatrix();
|
||||
worldMatrix = wMat;
|
||||
angle = wAng;
|
||||
|
||||
890
Robust.Shared/GameObjects/Systems/EntityLookup.cs
Normal file
890
Robust.Shared/GameObjects/Systems/EntityLookup.cs
Normal file
@@ -0,0 +1,890 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
[Flags]
|
||||
public enum LookupFlags : byte
|
||||
{
|
||||
None = 0,
|
||||
Approximate = 1 << 0,
|
||||
IncludeAnchored = 1 << 1,
|
||||
// IncludeGrids = 1 << 2,
|
||||
}
|
||||
|
||||
// TODO: Nuke IEntityLookup and just make a system
|
||||
public interface IEntityLookup
|
||||
{
|
||||
// Not an EntitySystem given _entityManager has a dependency on it which means it's just easier to IoC it for tests.
|
||||
|
||||
void Startup();
|
||||
|
||||
void Shutdown();
|
||||
|
||||
void Update();
|
||||
bool AnyEntitiesIntersecting(MapId mapId, Box2 box, LookupFlags flags = LookupFlags.IncludeAnchored);
|
||||
|
||||
IEnumerable<EntityUid> GetEntitiesInMap(MapId mapId, LookupFlags flags = LookupFlags.IncludeAnchored);
|
||||
|
||||
IEnumerable<EntityUid> GetEntitiesAt(MapId mapId, Vector2 position, LookupFlags flags = LookupFlags.IncludeAnchored);
|
||||
|
||||
IEnumerable<EntityUid> GetEntitiesInArc(EntityCoordinates coordinates, float range, Angle direction,
|
||||
float arcWidth, LookupFlags flags = LookupFlags.IncludeAnchored);
|
||||
|
||||
IEnumerable<EntityUid> GetEntitiesIntersecting(GridId gridId, IEnumerable<Vector2i> gridIndices);
|
||||
|
||||
IEnumerable<EntityUid> GetEntitiesIntersecting(GridId gridId, Vector2i gridIndices);
|
||||
|
||||
IEnumerable<EntityUid> GetEntitiesIntersecting(TileRef tileRef);
|
||||
|
||||
IEnumerable<EntityUid> GetEntitiesIntersecting(MapId mapId, Box2 worldAABB, LookupFlags flags = LookupFlags.IncludeAnchored);
|
||||
|
||||
IEnumerable<EntityUid> GetEntitiesIntersecting(MapId mapId, Box2Rotated worldAABB, LookupFlags flags = LookupFlags.IncludeAnchored);
|
||||
|
||||
IEnumerable<EntityUid> GetEntitiesIntersecting(EntityUid entity, float enlarged = 0f, LookupFlags flags = LookupFlags.IncludeAnchored);
|
||||
|
||||
IEnumerable<EntityUid> GetEntitiesIntersecting(MapCoordinates position, LookupFlags flags = LookupFlags.IncludeAnchored);
|
||||
|
||||
IEnumerable<EntityUid> GetEntitiesIntersecting(EntityCoordinates position, LookupFlags flags = LookupFlags.IncludeAnchored);
|
||||
|
||||
IEnumerable<EntityUid> GetEntitiesIntersecting(MapId mapId, Vector2 position, LookupFlags flags = LookupFlags.IncludeAnchored);
|
||||
|
||||
void FastEntitiesIntersecting(in MapId mapId, ref Box2 worldAABB, EntityUidQueryCallback callback, LookupFlags flags = LookupFlags.IncludeAnchored);
|
||||
|
||||
void FastEntitiesIntersecting(EntityLookupComponent lookup, ref Box2 localAABB, EntityUidQueryCallback callback);
|
||||
|
||||
IEnumerable<EntityUid> GetEntitiesInRange(EntityCoordinates position, float range, LookupFlags flags = LookupFlags.IncludeAnchored);
|
||||
|
||||
IEnumerable<EntityUid> GetEntitiesInRange(EntityUid entity, float range, LookupFlags flags = LookupFlags.IncludeAnchored);
|
||||
|
||||
IEnumerable<EntityUid> GetEntitiesInRange(MapId mapId, Vector2 point, float range, LookupFlags flags = LookupFlags.IncludeAnchored);
|
||||
|
||||
IEnumerable<EntityUid> GetEntitiesInRange(MapId mapId, Box2 box, float range, LookupFlags flags = LookupFlags.IncludeAnchored);
|
||||
|
||||
bool IsIntersecting(EntityUid entityOne, EntityUid entityTwo);
|
||||
|
||||
bool UpdateEntityTree(EntityUid entity, TransformComponent xform, Box2? worldAABB = null);
|
||||
|
||||
void RemoveFromEntityTrees(EntityUid entity);
|
||||
|
||||
Box2 GetWorldAabbFromEntity(in EntityUid ent, TransformComponent? xform = null);
|
||||
|
||||
Box2 GetLocalBounds(TileRef tileRef, ushort tileSize);
|
||||
|
||||
Box2 GetLocalBounds(Vector2i gridIndices, ushort tileSize);
|
||||
|
||||
Box2Rotated GetWorldBounds(TileRef tileRef, Matrix3? worldMatrix = null, Angle? angle = null);
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed partial class EntityLookup : IEntityLookup, IEntityEventSubscriber
|
||||
{
|
||||
private readonly IEntityManager _entityManager;
|
||||
private readonly IMapManager _mapManager;
|
||||
|
||||
private const int GrowthRate = 256;
|
||||
|
||||
private const float PointEnlargeRange = .00001f / 2;
|
||||
|
||||
// Using stacks so we always use latest data (given we only run it once per entity).
|
||||
private readonly Stack<MoveEvent> _moveQueue = new();
|
||||
private readonly Stack<RotateEvent> _rotateQueue = new();
|
||||
private readonly Queue<EntParentChangedMessage> _parentChangeQueue = new();
|
||||
|
||||
/// <summary>
|
||||
/// Like RenderTree we need to enlarge our lookup range for EntityLookupComponent as an entity is only ever on
|
||||
/// 1 EntityLookupComponent at a time (hence it may overlap without another lookup).
|
||||
/// </summary>
|
||||
private float _lookupEnlargementRange;
|
||||
|
||||
/// <summary>
|
||||
/// Move and rotate events generate the same update so no point duplicating work in the same tick.
|
||||
/// </summary>
|
||||
private readonly HashSet<EntityUid> _handledThisTick = new();
|
||||
|
||||
// TODO: Should combine all of the methods that check for IPhysBody and just use the one GetWorldAabbFromEntity method
|
||||
|
||||
// TODO: Combine GridTileLookupSystem and entity anchoring together someday.
|
||||
// Queries are a bit of spaghet rn but ideally you'd just have:
|
||||
// A) The fast tile-based one
|
||||
// B) The physics-only one (given physics needs it to be fast af)
|
||||
// C) A generic use one that covers anything not caught in the above.
|
||||
|
||||
public bool Started { get; private set; } = false;
|
||||
|
||||
public EntityLookup(IEntityManager entityManager, IMapManager mapManager)
|
||||
{
|
||||
_entityManager = entityManager;
|
||||
_mapManager = mapManager;
|
||||
}
|
||||
|
||||
public void Startup()
|
||||
{
|
||||
if (Started)
|
||||
{
|
||||
throw new InvalidOperationException("Startup() called multiple times.");
|
||||
}
|
||||
|
||||
var configManager = IoCManager.Resolve<IConfigurationManager>();
|
||||
configManager.OnValueChanged(CVars.LookupEnlargementRange, value => _lookupEnlargementRange = value, true);
|
||||
|
||||
var eventBus = _entityManager.EventBus;
|
||||
eventBus.SubscribeEvent(EventSource.Local, this, (ref MoveEvent ev) => _moveQueue.Push(ev));
|
||||
eventBus.SubscribeEvent(EventSource.Local, this, (ref RotateEvent ev) => _rotateQueue.Push(ev));
|
||||
eventBus.SubscribeEvent(EventSource.Local, this, (ref EntParentChangedMessage ev) => _parentChangeQueue.Enqueue(ev));
|
||||
eventBus.SubscribeEvent<AnchorStateChangedEvent>(EventSource.Local, this, HandleAnchored);
|
||||
|
||||
eventBus.SubscribeLocalEvent<EntityLookupComponent, ComponentAdd>(OnLookupAdd);
|
||||
eventBus.SubscribeLocalEvent<EntityLookupComponent, ComponentShutdown>(OnLookupShutdown);
|
||||
eventBus.SubscribeEvent<GridInitializeEvent>(EventSource.Local, this, OnGridInit);
|
||||
|
||||
_entityManager.EntityDeleted += OnEntityDeleted;
|
||||
_entityManager.EntityInitialized += OnEntityInit;
|
||||
_mapManager.MapCreated += OnMapCreated;
|
||||
Started = true;
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
// If we haven't even started up, there's nothing to clean up then.
|
||||
if (!Started)
|
||||
return;
|
||||
|
||||
_moveQueue.Clear();
|
||||
_rotateQueue.Clear();
|
||||
_handledThisTick.Clear();
|
||||
_parentChangeQueue.Clear();
|
||||
|
||||
_entityManager.EntityDeleted -= OnEntityDeleted;
|
||||
_entityManager.EntityInitialized -= OnEntityInit;
|
||||
_mapManager.MapCreated -= OnMapCreated;
|
||||
Started = false;
|
||||
}
|
||||
|
||||
private void HandleAnchored(ref AnchorStateChangedEvent @event)
|
||||
{
|
||||
// This event needs to be handled immediately as anchoring is handled immediately
|
||||
// and any callers may potentially get duplicate entities that just changed state.
|
||||
if (@event.Anchored)
|
||||
{
|
||||
RemoveFromEntityTrees(@event.Entity);
|
||||
}
|
||||
else if (_entityManager.TryGetComponent(@event.Entity, out MetaDataComponent? meta) && meta.EntityLifeStage < EntityLifeStage.Terminating)
|
||||
{
|
||||
var xform = _entityManager.GetComponent<TransformComponent>(@event.Entity);
|
||||
UpdateEntityTree(@event.Entity, xform);
|
||||
}
|
||||
// else -> the entity is terminating. We can ignore this un-anchor event, as this entity will be removed by the tree via OnEntityDeleted.
|
||||
}
|
||||
|
||||
private void OnLookupShutdown(EntityUid uid, EntityLookupComponent component, ComponentShutdown args)
|
||||
{
|
||||
component.Tree.Clear();
|
||||
}
|
||||
|
||||
private void OnGridInit(GridInitializeEvent ev)
|
||||
{
|
||||
_entityManager.EnsureComponent<EntityLookupComponent>(ev.EntityUid);
|
||||
}
|
||||
|
||||
private void OnLookupAdd(EntityUid uid, EntityLookupComponent component, ComponentAdd args)
|
||||
{
|
||||
int capacity;
|
||||
|
||||
if (_entityManager.TryGetComponent(uid, out TransformComponent? xform))
|
||||
{
|
||||
capacity = (int) Math.Min(256, Math.Ceiling(xform.ChildCount / (float) GrowthRate) * GrowthRate);
|
||||
}
|
||||
else
|
||||
{
|
||||
capacity = 256;
|
||||
}
|
||||
|
||||
component.Tree = new DynamicTree<EntityUid>(
|
||||
GetRelativeAABBFromEntity,
|
||||
capacity: capacity,
|
||||
growthFunc: x => x == GrowthRate ? GrowthRate * 8 : x * 2
|
||||
);
|
||||
}
|
||||
|
||||
private Box2 GetRelativeAABBFromEntity(in EntityUid entity)
|
||||
{
|
||||
// TODO: Should feed in AABB to lookup so it's not enlarged unnecessarily
|
||||
|
||||
var aabb = GetWorldAABB(entity);
|
||||
var tree = GetLookup(entity);
|
||||
|
||||
if (tree == null)
|
||||
return aabb;
|
||||
|
||||
return _entityManager.GetComponent<TransformComponent>(tree.Owner).InvWorldMatrix.TransformBox(aabb);
|
||||
}
|
||||
|
||||
private void OnEntityDeleted(object? sender, EntityUid uid)
|
||||
{
|
||||
RemoveFromEntityTrees(uid);
|
||||
}
|
||||
|
||||
private void OnEntityInit(object? sender, EntityUid uid)
|
||||
{
|
||||
var xform = _entityManager.GetComponent<TransformComponent>(uid);
|
||||
if (xform.Anchored) return;
|
||||
UpdateEntityTree(uid, xform);
|
||||
}
|
||||
|
||||
private void OnMapCreated(object? sender, MapEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.Map == MapId.Nullspace) return;
|
||||
|
||||
_mapManager.GetMapEntityId(eventArgs.Map).EnsureComponent<EntityLookupComponent>();
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Acruid said he'd deal with Update being called around I_entityManager later.
|
||||
|
||||
// Could be more efficient but essentially nuke their old lookup and add to new lookup if applicable.
|
||||
while (_parentChangeQueue.TryDequeue(out var mapChangeEvent))
|
||||
{
|
||||
_handledThisTick.Add(mapChangeEvent.Entity);
|
||||
RemoveFromEntityTrees(mapChangeEvent.Entity);
|
||||
|
||||
if (_entityManager.Deleted(mapChangeEvent.Entity)) continue;
|
||||
|
||||
var xform = _entityManager.GetComponent<TransformComponent>(mapChangeEvent.Entity);
|
||||
|
||||
if (xform.Anchored) continue;
|
||||
|
||||
UpdateEntityTree(mapChangeEvent.Entity, xform, GetWorldAabbFromEntity(mapChangeEvent.Entity));
|
||||
}
|
||||
|
||||
while (_moveQueue.TryPop(out var moveEvent))
|
||||
{
|
||||
if (!_handledThisTick.Add(moveEvent.Sender) ||
|
||||
_entityManager.Deleted(moveEvent.Sender)) continue;
|
||||
|
||||
var xform = _entityManager.GetComponent<TransformComponent>(moveEvent.Sender);
|
||||
|
||||
if (xform.Anchored) continue;
|
||||
|
||||
DebugTools.Assert(!xform.Anchored);
|
||||
UpdateEntityTree(moveEvent.Sender, xform, moveEvent.WorldAABB);
|
||||
}
|
||||
|
||||
while (_rotateQueue.TryPop(out var rotateEvent))
|
||||
{
|
||||
if (!_handledThisTick.Add(rotateEvent.Sender) ||
|
||||
_entityManager.Deleted(rotateEvent.Sender)) continue;
|
||||
|
||||
var xform = _entityManager.GetComponent<TransformComponent>(rotateEvent.Sender);
|
||||
|
||||
if (xform.Anchored) continue;
|
||||
|
||||
DebugTools.Assert(!xform.Anchored);
|
||||
UpdateEntityTree(rotateEvent.Sender, xform, rotateEvent.WorldAABB);
|
||||
}
|
||||
|
||||
_handledThisTick.Clear();
|
||||
}
|
||||
|
||||
#region Spatial Queries
|
||||
|
||||
private LookupsEnumerator GetLookupsIntersecting(MapId mapId, Box2 worldAABB)
|
||||
{
|
||||
_mapManager.FindGridsIntersectingEnumerator(mapId, worldAABB, out var gridEnumerator, true);
|
||||
|
||||
return new LookupsEnumerator(_entityManager, _mapManager, mapId, gridEnumerator);
|
||||
}
|
||||
|
||||
private struct LookupsEnumerator
|
||||
{
|
||||
private IEntityManager _entityManager;
|
||||
private IMapManager _mapManager;
|
||||
|
||||
private MapId _mapId;
|
||||
private FindGridsEnumerator _enumerator;
|
||||
|
||||
private bool _final;
|
||||
|
||||
public LookupsEnumerator(IEntityManager entityManager, IMapManager mapManager, MapId mapId, FindGridsEnumerator enumerator)
|
||||
{
|
||||
_entityManager = entityManager;
|
||||
_mapManager = mapManager;
|
||||
|
||||
_mapId = mapId;
|
||||
_enumerator = enumerator;
|
||||
_final = false;
|
||||
}
|
||||
|
||||
public bool MoveNext([NotNullWhen(true)] out EntityLookupComponent? component)
|
||||
{
|
||||
if (!_enumerator.MoveNext(out var grid))
|
||||
{
|
||||
if (_final || _mapId == MapId.Nullspace)
|
||||
{
|
||||
component = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
_final = true;
|
||||
EntityUid mapUid = _mapManager.GetMapEntityIdOrThrow(_mapId);
|
||||
component = _entityManager.GetComponent<EntityLookupComponent>(mapUid);
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: Recursive and all that.
|
||||
component = _entityManager.GetComponent<EntityLookupComponent>(grid.GridEntityId);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<EntityUid> GetAnchored(MapId mapId, Box2 worldAABB, LookupFlags flags)
|
||||
{
|
||||
if ((flags & LookupFlags.IncludeAnchored) == 0x0) yield break;
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
|
||||
{
|
||||
foreach (var uid in grid.GetAnchoredEntities(worldAABB))
|
||||
{
|
||||
if (!_entityManager.EntityExists(uid)) continue;
|
||||
yield return uid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<EntityUid> GetAnchored(MapId mapId, Box2Rotated worldBounds, LookupFlags flags)
|
||||
{
|
||||
if ((flags & LookupFlags.IncludeAnchored) == 0x0) yield break;
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldBounds))
|
||||
{
|
||||
foreach (var uid in grid.GetAnchoredEntities(worldBounds))
|
||||
{
|
||||
if (!_entityManager.EntityExists(uid)) continue;
|
||||
yield return uid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool AnyEntitiesIntersecting(MapId mapId, Box2 box, LookupFlags flags = LookupFlags.IncludeAnchored)
|
||||
{
|
||||
var found = false;
|
||||
var enumerator = GetLookupsIntersecting(mapId, box);
|
||||
|
||||
while (enumerator.MoveNext(out var lookup))
|
||||
{
|
||||
var offsetBox = _entityManager.GetComponent<TransformComponent>(lookup.Owner).InvWorldMatrix.TransformBox(box);
|
||||
|
||||
lookup.Tree.QueryAabb(ref found, (ref bool found, in EntityUid ent) =>
|
||||
{
|
||||
if (_entityManager.Deleted(ent))
|
||||
return true;
|
||||
|
||||
found = true;
|
||||
return false;
|
||||
|
||||
}, offsetBox, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
foreach (var _ in GetAnchored(mapId, box, flags))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
public void FastEntitiesIntersecting(in MapId mapId, ref Box2 worldAABB, EntityUidQueryCallback callback, LookupFlags flags = LookupFlags.IncludeAnchored)
|
||||
{
|
||||
var enumerator = GetLookupsIntersecting(mapId, worldAABB);
|
||||
while (enumerator.MoveNext(out var lookup))
|
||||
{
|
||||
var offsetBox = _entityManager.GetComponent<TransformComponent>(lookup.Owner).InvWorldMatrix.TransformBox(worldAABB);
|
||||
|
||||
lookup.Tree._b2Tree.FastQuery(ref offsetBox, (ref EntityUid data) => callback(data));
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.IncludeAnchored) != 0x0)
|
||||
{
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
|
||||
{
|
||||
foreach (var uid in grid.GetAnchoredEntities(worldAABB))
|
||||
{
|
||||
if (!_entityManager.EntityExists(uid)) continue;
|
||||
callback(uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void FastEntitiesIntersecting(EntityLookupComponent lookup, ref Box2 localAABB, EntityUidQueryCallback callback)
|
||||
{
|
||||
lookup.Tree._b2Tree.FastQuery(ref localAABB, (ref EntityUid data) => callback(data));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<EntityUid> GetEntitiesIntersecting(MapId mapId, Box2 worldAABB, LookupFlags flags = LookupFlags.IncludeAnchored)
|
||||
{
|
||||
if (mapId == MapId.Nullspace) return Enumerable.Empty<EntityUid>();
|
||||
|
||||
var list = new List<EntityUid>();
|
||||
var enumerator = GetLookupsIntersecting(mapId, worldAABB);
|
||||
|
||||
while (enumerator.MoveNext(out var lookup))
|
||||
{
|
||||
var offsetBox = _entityManager.GetComponent<TransformComponent>(lookup.Owner).InvWorldMatrix.TransformBox(worldAABB);
|
||||
|
||||
lookup.Tree.QueryAabb(ref list, (ref List<EntityUid> list, in EntityUid ent) =>
|
||||
{
|
||||
if (!_entityManager.Deleted(ent))
|
||||
{
|
||||
list.Add(ent);
|
||||
}
|
||||
return true;
|
||||
}, offsetBox, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
|
||||
foreach (var ent in GetAnchored(mapId, worldAABB, flags))
|
||||
{
|
||||
list.Add(ent);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<EntityUid> GetEntitiesIntersecting(MapId mapId, Box2Rotated worldBounds, LookupFlags flags = LookupFlags.IncludeAnchored)
|
||||
{
|
||||
if (mapId == MapId.Nullspace) return Enumerable.Empty<EntityUid>();
|
||||
|
||||
var list = new List<EntityUid>();
|
||||
var worldAABB = worldBounds.CalcBoundingBox();
|
||||
var enumerator = GetLookupsIntersecting(mapId, worldAABB);
|
||||
|
||||
while (enumerator.MoveNext(out var lookup))
|
||||
{
|
||||
var offsetBox = _entityManager.GetComponent<TransformComponent>(lookup.Owner).InvWorldMatrix.TransformBox(worldBounds);
|
||||
|
||||
lookup.Tree.QueryAabb(ref list, (ref List<EntityUid> list, in EntityUid ent) =>
|
||||
{
|
||||
if (!_entityManager.Deleted(ent))
|
||||
{
|
||||
list.Add(ent);
|
||||
}
|
||||
return true;
|
||||
}, offsetBox, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
|
||||
foreach (var ent in GetAnchored(mapId, worldBounds, flags))
|
||||
{
|
||||
list.Add(ent);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<EntityUid> GetEntitiesIntersecting(MapId mapId, Vector2 position, LookupFlags flags = LookupFlags.IncludeAnchored)
|
||||
{
|
||||
if (mapId == MapId.Nullspace) return Enumerable.Empty<EntityUid>();
|
||||
|
||||
var aabb = new Box2(position, position).Enlarged(PointEnlargeRange);
|
||||
var list = new List<EntityUid>();
|
||||
var state = (list, position);
|
||||
|
||||
var enumerator = GetLookupsIntersecting(mapId, aabb);
|
||||
|
||||
while (enumerator.MoveNext(out var lookup))
|
||||
{
|
||||
var localPoint = _entityManager.GetComponent<TransformComponent>(lookup.Owner).InvWorldMatrix.Transform(position);
|
||||
|
||||
lookup.Tree.QueryPoint(ref state, (ref (List<EntityUid> list, Vector2 position) state, in EntityUid ent) =>
|
||||
{
|
||||
if (Intersecting(ent, state.position))
|
||||
{
|
||||
state.list.Add(ent);
|
||||
}
|
||||
return true;
|
||||
}, localPoint, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.IncludeAnchored) != 0x0 &&
|
||||
_mapManager.TryFindGridAt(mapId, position, out var grid) &&
|
||||
grid.TryGetTileRef(position, out var tile))
|
||||
{
|
||||
foreach (var uid in grid.GetAnchoredEntities(tile.GridIndices))
|
||||
{
|
||||
if (!_entityManager.EntityExists(uid)) continue;
|
||||
state.list.Add(uid);
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<EntityUid> GetEntitiesIntersecting(MapCoordinates position, LookupFlags flags = LookupFlags.IncludeAnchored)
|
||||
{
|
||||
return GetEntitiesIntersecting(position.MapId, position.Position, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<EntityUid> GetEntitiesIntersecting(EntityCoordinates position, LookupFlags flags = LookupFlags.IncludeAnchored)
|
||||
{
|
||||
var mapPos = position.ToMap(_entityManager);
|
||||
return GetEntitiesIntersecting(mapPos.MapId, mapPos.Position, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<EntityUid> GetEntitiesIntersecting(EntityUid entity, float enlarged = 0f, LookupFlags flags = LookupFlags.IncludeAnchored)
|
||||
{
|
||||
var worldAABB = GetWorldAabbFromEntity(entity);
|
||||
var xform = _entityManager.GetComponent<TransformComponent>(entity);
|
||||
|
||||
var (worldPos, worldRot) = xform.GetWorldPositionRotation();
|
||||
|
||||
var enumerator = GetLookupsIntersecting(xform.MapID, worldAABB);
|
||||
var list = new List<EntityUid>();
|
||||
|
||||
while (enumerator.MoveNext(out var lookup))
|
||||
{
|
||||
// To get the tightest bounds possible we'll re-calculate it for each lookup.
|
||||
var localBounds = GetLookupBounds(entity, lookup, worldPos, worldRot, enlarged);
|
||||
|
||||
lookup.Tree.QueryAabb(ref list, (ref List<EntityUid> list, in EntityUid ent) =>
|
||||
{
|
||||
if (!_entityManager.Deleted(ent))
|
||||
{
|
||||
list.Add(ent);
|
||||
}
|
||||
return true;
|
||||
}, localBounds, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
|
||||
foreach (var ent in GetAnchored(xform.MapID, worldAABB, flags))
|
||||
{
|
||||
list.Add(ent);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private Box2 GetLookupBounds(EntityUid uid, EntityLookupComponent lookup, Vector2 worldPos, Angle worldRot, float enlarged)
|
||||
{
|
||||
var (_, lookupRot, lookupInvWorldMatrix) = _entityManager.GetComponent<TransformComponent>(lookup.Owner).GetWorldPositionRotationInvMatrix();
|
||||
|
||||
var localPos = lookupInvWorldMatrix.Transform(worldPos);
|
||||
var localRot = worldRot - lookupRot;
|
||||
|
||||
if (_entityManager.TryGetComponent(uid, out FixturesComponent? manager))
|
||||
{
|
||||
var transform = new Transform(localPos, localRot);
|
||||
Box2? aabb = null;
|
||||
|
||||
foreach (var (_, fixture) in manager.Fixtures)
|
||||
{
|
||||
if (!fixture.Hard) continue;
|
||||
for (var i = 0; i < fixture.Shape.ChildCount; i++)
|
||||
{
|
||||
aabb = aabb?.Union(fixture.Shape.ComputeAABB(transform, i)) ?? fixture.Shape.ComputeAABB(transform, i);
|
||||
}
|
||||
}
|
||||
|
||||
if (aabb != null)
|
||||
{
|
||||
return aabb.Value.Enlarged(enlarged);
|
||||
}
|
||||
}
|
||||
|
||||
// So IsEmpty checks don't get triggered
|
||||
return new Box2(localPos - float.Epsilon, localPos + float.Epsilon);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsIntersecting(EntityUid entityOne, EntityUid entityTwo)
|
||||
{
|
||||
var position = _entityManager.GetComponent<TransformComponent>(entityOne).MapPosition.Position;
|
||||
return Intersecting(entityTwo, position);
|
||||
}
|
||||
|
||||
private bool Intersecting(EntityUid entity, Vector2 mapPosition)
|
||||
{
|
||||
if (_entityManager.TryGetComponent(entity, out IPhysBody? component))
|
||||
{
|
||||
if (component.GetWorldAABB().Contains(mapPosition))
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var transform = _entityManager.GetComponent<TransformComponent>(entity);
|
||||
var entPos = transform.WorldPosition;
|
||||
if (MathHelper.CloseToPercent(entPos.X, mapPosition.X)
|
||||
&& MathHelper.CloseToPercent(entPos.Y, mapPosition.Y))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<EntityUid> GetEntitiesInRange(EntityCoordinates position, float range, LookupFlags flags = LookupFlags.IncludeAnchored)
|
||||
{
|
||||
var mapCoordinates = position.ToMap(_entityManager);
|
||||
var mapPosition = mapCoordinates.Position;
|
||||
var aabb = new Box2(mapPosition - new Vector2(range, range),
|
||||
mapPosition + new Vector2(range, range));
|
||||
return GetEntitiesIntersecting(mapCoordinates.MapId, aabb, flags);
|
||||
// TODO: Use a circle shape here mate
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<EntityUid> GetEntitiesInRange(MapId mapId, Box2 box, float range, LookupFlags flags = LookupFlags.IncludeAnchored)
|
||||
{
|
||||
var aabb = box.Enlarged(range);
|
||||
return GetEntitiesIntersecting(mapId, aabb, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<EntityUid> GetEntitiesInRange(MapId mapId, Vector2 point, float range, LookupFlags flags = LookupFlags.IncludeAnchored)
|
||||
{
|
||||
var aabb = new Box2(point, point).Enlarged(range);
|
||||
return GetEntitiesIntersecting(mapId, aabb, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<EntityUid> GetEntitiesInRange(EntityUid entity, float range, LookupFlags flags = LookupFlags.IncludeAnchored)
|
||||
{
|
||||
var worldAABB = GetWorldAabbFromEntity(entity);
|
||||
return GetEntitiesInRange(_entityManager.GetComponent<TransformComponent>(entity).MapID, worldAABB, range, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<EntityUid> GetEntitiesInArc(EntityCoordinates coordinates, float range, Angle direction,
|
||||
float arcWidth, LookupFlags flags = LookupFlags.IncludeAnchored)
|
||||
{
|
||||
var position = coordinates.ToMap(_entityManager).Position;
|
||||
|
||||
foreach (var entity in GetEntitiesInRange(coordinates, range * 2, flags))
|
||||
{
|
||||
var angle = new Angle(_entityManager.GetComponent<TransformComponent>(entity).WorldPosition - position);
|
||||
if (angle.Degrees < direction.Degrees + arcWidth / 2 &&
|
||||
angle.Degrees > direction.Degrees - arcWidth / 2)
|
||||
yield return entity;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<EntityUid> GetEntitiesInMap(MapId mapId, LookupFlags flags = LookupFlags.IncludeAnchored)
|
||||
{
|
||||
DebugTools.Assert((flags & LookupFlags.Approximate) == 0x0);
|
||||
|
||||
foreach (EntityLookupComponent comp in _entityManager.EntityQuery<EntityLookupComponent>(true))
|
||||
{
|
||||
if (_entityManager.GetComponent<TransformComponent>(comp.Owner).MapID != mapId) continue;
|
||||
|
||||
foreach (var entity in comp.Tree)
|
||||
{
|
||||
if (_entityManager.Deleted(entity)) continue;
|
||||
|
||||
yield return entity;
|
||||
}
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.IncludeAnchored) == 0x0) yield break;
|
||||
|
||||
foreach (var grid in _mapManager.GetAllMapGrids(mapId))
|
||||
{
|
||||
foreach (var tile in grid.GetAllTiles())
|
||||
{
|
||||
foreach (var uid in grid.GetAnchoredEntities(tile.GridIndices))
|
||||
{
|
||||
if (!_entityManager.EntityExists(uid)) continue;
|
||||
yield return uid;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<EntityUid> GetEntitiesAt(MapId mapId, Vector2 position, LookupFlags flags = LookupFlags.IncludeAnchored)
|
||||
{
|
||||
if (mapId == MapId.Nullspace) return Enumerable.Empty<EntityUid>();
|
||||
|
||||
var list = new List<EntityUid>();
|
||||
|
||||
var state = (list, position);
|
||||
|
||||
var aabb = new Box2(position, position).Enlarged(PointEnlargeRange);
|
||||
var enumerator = GetLookupsIntersecting(mapId, aabb);
|
||||
|
||||
while (enumerator.MoveNext(out var lookup))
|
||||
{
|
||||
var offsetPos = _entityManager.GetComponent<TransformComponent>(lookup.Owner).InvWorldMatrix.Transform(position);
|
||||
|
||||
lookup.Tree.QueryPoint(ref state, (ref (List<EntityUid> list, Vector2 position) state, in EntityUid ent) =>
|
||||
{
|
||||
state.list.Add(ent);
|
||||
return true;
|
||||
}, offsetPos, (flags & LookupFlags.Approximate) != 0x0);
|
||||
}
|
||||
|
||||
if ((flags & LookupFlags.IncludeAnchored) != 0x0)
|
||||
{
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, aabb))
|
||||
{
|
||||
foreach (var uid in grid.GetAnchoredEntities(aabb))
|
||||
{
|
||||
if (!_entityManager.EntityExists(uid)) continue;
|
||||
list.Add(uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Entity DynamicTree
|
||||
|
||||
private EntityLookupComponent? GetLookup(EntityUid entity)
|
||||
{
|
||||
// TODO: This should just be passed in when we cleanup EntityLookup a bit.
|
||||
var xforms = _entityManager.GetEntityQuery<TransformComponent>();
|
||||
var xform = xforms.GetComponent(entity);
|
||||
|
||||
if (xform.MapID == MapId.Nullspace)
|
||||
return null;
|
||||
|
||||
var lookups = _entityManager.GetEntityQuery<EntityLookupComponent>();
|
||||
var parent = xform.ParentUid;
|
||||
|
||||
// if it's map return null. Grids should return the map's broadphase.
|
||||
if (lookups.HasComponent(entity) &&
|
||||
!parent.IsValid())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
while (parent.IsValid())
|
||||
{
|
||||
if (lookups.TryGetComponent(parent, out var comp)) return comp;
|
||||
parent = xforms.GetComponent(parent).ParentUid;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool UpdateEntityTree(EntityUid entity, TransformComponent xform, Box2? worldAABB = null)
|
||||
{
|
||||
DebugTools.Assert(!_entityManager.Deleted(entity));
|
||||
|
||||
var lookup = GetLookup(entity);
|
||||
|
||||
if (lookup == null)
|
||||
{
|
||||
RemoveFromEntityTrees(entity);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Temp PVS guard for when we clear dynamictree for now.
|
||||
worldAABB ??= GetWorldAabbFromEntity(entity, xform);
|
||||
var center = worldAABB.Value.Center;
|
||||
|
||||
DebugTools.Assert(!float.IsNaN(center.X) && !float.IsNaN(center.Y));
|
||||
|
||||
var aabb = _entityManager.GetComponent<TransformComponent>(lookup.Owner).InvWorldMatrix.TransformBox(worldAABB.Value);
|
||||
|
||||
// for debugging
|
||||
var necessary = 0;
|
||||
|
||||
if (lookup.Tree.AddOrUpdate(entity, aabb))
|
||||
{
|
||||
++necessary;
|
||||
}
|
||||
|
||||
if (!_entityManager.HasComponent<EntityLookupComponent>(entity))
|
||||
{
|
||||
DebugTools.Assert(!_entityManager.HasComponent<IMapGridComponent>(entity));
|
||||
|
||||
var children = xform.ChildEnumerator;
|
||||
|
||||
while (children.MoveNext(out var child))
|
||||
{
|
||||
if (!_handledThisTick.Add(child.Value)) continue;
|
||||
|
||||
var childXform = _entityManager.GetComponent<TransformComponent>(child.Value);
|
||||
|
||||
if (UpdateEntityTree(child.Value, childXform))
|
||||
{
|
||||
++necessary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return necessary > 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RemoveFromEntityTrees(EntityUid entity)
|
||||
{
|
||||
// TODO: Need to fix ordering issues and then we can just directly remove it from the tree
|
||||
// rather than this O(n) legacy garbage.
|
||||
// Also we can't early returns because somehow it gets added to multiple trees!!!
|
||||
foreach (var lookup in _entityManager.EntityQuery<EntityLookupComponent>(true))
|
||||
{
|
||||
lookup.Tree.Remove(entity);
|
||||
}
|
||||
}
|
||||
|
||||
public Box2 GetWorldAabbFromEntity(in EntityUid ent, TransformComponent? xform = null)
|
||||
{
|
||||
return GetWorldAABB(ent, xform);
|
||||
}
|
||||
|
||||
private Box2 GetWorldAABB(in EntityUid ent, TransformComponent? xform = null)
|
||||
{
|
||||
Vector2 pos;
|
||||
xform ??= _entityManager.GetComponent<TransformComponent>(ent);
|
||||
|
||||
if ((!_entityManager.EntityExists(ent) ? EntityLifeStage.Deleted : _entityManager.GetComponent<MetaDataComponent>(ent).EntityLifeStage) >= EntityLifeStage.Deleted)
|
||||
{
|
||||
pos = xform.WorldPosition;
|
||||
return new Box2(pos, pos);
|
||||
}
|
||||
|
||||
// MOCKS WHY
|
||||
if (ent.TryGetContainer(out var container, _entityManager))
|
||||
{
|
||||
return GetWorldAABB(container.Owner);
|
||||
}
|
||||
|
||||
pos = xform.WorldPosition;
|
||||
|
||||
return _entityManager.TryGetComponent(ent, out ILookupWorldBox2Component? lookup) ?
|
||||
lookup.GetWorldAABB(pos) :
|
||||
new Box2(pos, pos);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,463 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
[Flags]
|
||||
public enum LookupFlags : byte
|
||||
{
|
||||
None = 0,
|
||||
Approximate = 1 << 0,
|
||||
IncludeAnchored = 1 << 1,
|
||||
// IncludeGrids = 1 << 2,
|
||||
}
|
||||
|
||||
public sealed partial class EntityLookupSystem : EntitySystem
|
||||
{
|
||||
[IoC.Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[IoC.Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
[IoC.Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
private const int GrowthRate = 256;
|
||||
|
||||
private const float PointEnlargeRange = .00001f / 2;
|
||||
|
||||
/// <summary>
|
||||
/// Like RenderTree we need to enlarge our lookup range for EntityLookupComponent as an entity is only ever on
|
||||
/// 1 EntityLookupComponent at a time (hence it may overlap without another lookup).
|
||||
/// </summary>
|
||||
private float _lookupEnlargementRange;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
var configManager = IoCManager.Resolve<IConfigurationManager>();
|
||||
configManager.OnValueChanged(CVars.LookupEnlargementRange, value => _lookupEnlargementRange = value, true);
|
||||
|
||||
SubscribeLocalEvent<MoveEvent>(OnMove);
|
||||
SubscribeLocalEvent<RotateEvent>(OnRotate);
|
||||
SubscribeLocalEvent<EntParentChangedMessage>(OnParentChange);
|
||||
SubscribeLocalEvent<AnchorStateChangedEvent>(OnAnchored);
|
||||
SubscribeLocalEvent<UpdateLookupBoundsEvent>(OnBoundsUpdate);
|
||||
|
||||
SubscribeLocalEvent<EntityLookupComponent, ComponentAdd>(OnLookupAdd);
|
||||
SubscribeLocalEvent<EntityLookupComponent, ComponentShutdown>(OnLookupShutdown);
|
||||
SubscribeLocalEvent<GridInitializeEvent>(OnGridInit);
|
||||
|
||||
SubscribeLocalEvent<EntityTerminatingEvent>(OnTerminate);
|
||||
|
||||
EntityManager.EntityInitialized += OnEntityInit;
|
||||
_mapManager.MapCreated += OnMapCreated;
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
EntityManager.EntityInitialized -= OnEntityInit;
|
||||
_mapManager.MapCreated -= OnMapCreated;
|
||||
}
|
||||
|
||||
private void OnAnchored(ref AnchorStateChangedEvent args)
|
||||
{
|
||||
// This event needs to be handled immediately as anchoring is handled immediately
|
||||
// and any callers may potentially get duplicate entities that just changed state.
|
||||
if (args.Anchored)
|
||||
{
|
||||
RemoveFromEntityTree(args.Entity);
|
||||
}
|
||||
else if (EntityManager.TryGetComponent(args.Entity, out MetaDataComponent? meta) && meta.EntityLifeStage < EntityLifeStage.Terminating)
|
||||
{
|
||||
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
var xform = xformQuery.GetComponent(args.Entity);
|
||||
var lookup = GetLookup(args.Entity, xform, xformQuery);
|
||||
|
||||
if (lookup == null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
var lookupXform = xformQuery.GetComponent(lookup.Owner);
|
||||
var coordinates = _transform.GetMoverCoordinates(xform.Coordinates, xformQuery);
|
||||
DebugTools.Assert(coordinates.EntityId == lookup.Owner);
|
||||
|
||||
// If we're contained then LocalRotation should be 0 anyway.
|
||||
var aabb = GetAABB(args.Entity, coordinates.Position, _transform.GetWorldRotation(xform) - _transform.GetWorldRotation(lookupXform), xform, xformQuery);
|
||||
AddToEntityTree(lookup, xform, aabb, xformQuery);
|
||||
}
|
||||
// else -> the entity is terminating. We can ignore this un-anchor event, as this entity will be removed by the tree via OnEntityDeleted.
|
||||
}
|
||||
|
||||
#region DynamicTree
|
||||
|
||||
private void OnLookupShutdown(EntityUid uid, EntityLookupComponent component, ComponentShutdown args)
|
||||
{
|
||||
component.Tree.Clear();
|
||||
}
|
||||
|
||||
private void OnGridInit(GridInitializeEvent ev)
|
||||
{
|
||||
EntityManager.EnsureComponent<EntityLookupComponent>(ev.EntityUid);
|
||||
}
|
||||
|
||||
private void OnLookupAdd(EntityUid uid, EntityLookupComponent component, ComponentAdd args)
|
||||
{
|
||||
int capacity;
|
||||
|
||||
if (EntityManager.TryGetComponent(uid, out TransformComponent? xform))
|
||||
{
|
||||
capacity = (int) Math.Min(256, Math.Ceiling(xform.ChildCount / (float) GrowthRate) * GrowthRate);
|
||||
}
|
||||
else
|
||||
{
|
||||
capacity = 256;
|
||||
}
|
||||
|
||||
component.Tree = new DynamicTree<EntityUid>(
|
||||
GetTreeAABB,
|
||||
capacity: capacity,
|
||||
growthFunc: x => x == GrowthRate ? GrowthRate * 8 : x * 2
|
||||
);
|
||||
}
|
||||
|
||||
private void OnMapCreated(object? sender, MapEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.Map == MapId.Nullspace) return;
|
||||
|
||||
EntityManager.EnsureComponent<EntityLookupComponent>(_mapManager.GetMapEntityId(eventArgs.Map));
|
||||
}
|
||||
|
||||
private Box2 GetTreeAABB(in EntityUid entity)
|
||||
{
|
||||
// TODO: Should feed in AABB to lookup so it's not enlarged unnecessarily
|
||||
var aabb = GetWorldAABB(entity);
|
||||
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
var tree = GetLookup(entity, xformQuery);
|
||||
|
||||
if (tree == null)
|
||||
return aabb;
|
||||
|
||||
return xformQuery.GetComponent(tree.Owner).InvWorldMatrix.TransformBox(aabb);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Entity events
|
||||
|
||||
private void OnTerminate(ref EntityTerminatingEvent args)
|
||||
{
|
||||
RemoveFromEntityTree(args.Owner, false);
|
||||
}
|
||||
|
||||
private void OnEntityInit(object? sender, EntityUid uid)
|
||||
{
|
||||
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
|
||||
if (!xformQuery.TryGetComponent(uid, out var xform) ||
|
||||
xform.Anchored ||
|
||||
_mapManager.IsMap(uid) ||
|
||||
_mapManager.IsGrid(uid)) return;
|
||||
|
||||
var lookup = GetLookup(uid, xform, xformQuery);
|
||||
|
||||
// If nullspace or the likes.
|
||||
if (lookup == null) return;
|
||||
|
||||
var lookupXform = xformQuery.GetComponent(lookup.Owner);
|
||||
var coordinates = _transform.GetMoverCoordinates(xform.Coordinates, xformQuery);
|
||||
DebugTools.Assert(coordinates.EntityId == lookup.Owner);
|
||||
|
||||
// If we're contained then LocalRotation should be 0 anyway.
|
||||
var aabb = GetAABB(uid, coordinates.Position, _transform.GetWorldRotation(xform) - _transform.GetWorldRotation(lookupXform), xform, xformQuery);
|
||||
|
||||
// Any child entities should be handled by their own OnEntityInit
|
||||
AddToEntityTree(lookup, xform, aabb, xformQuery, false);
|
||||
}
|
||||
|
||||
private void OnMove(ref MoveEvent args)
|
||||
{
|
||||
UpdatePosition(args.Sender, args.Component);
|
||||
}
|
||||
|
||||
private void OnRotate(ref RotateEvent args)
|
||||
{
|
||||
UpdatePosition(args.Sender, args.Component);
|
||||
}
|
||||
|
||||
private void UpdatePosition(EntityUid uid, TransformComponent xform)
|
||||
{
|
||||
// Even if the entity is contained it may have children that aren't so we still need to update.
|
||||
if (!CanMoveUpdate(uid, xform)) return;
|
||||
|
||||
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
var lookup = GetLookup(uid, xform, xformQuery);
|
||||
|
||||
if (lookup == null) return;
|
||||
|
||||
var lookupXform = xformQuery.GetComponent(lookup.Owner);
|
||||
var coordinates = _transform.GetMoverCoordinates(xform.Coordinates, xformQuery);
|
||||
var aabb = GetAABB(uid, coordinates.Position, _transform.GetWorldRotation(xform) - _transform.GetWorldRotation(lookupXform), xformQuery.GetComponent(uid), xformQuery);
|
||||
AddToEntityTree(lookup, xform, aabb, xformQuery);
|
||||
}
|
||||
|
||||
private bool CanMoveUpdate(EntityUid uid, TransformComponent xform)
|
||||
{
|
||||
return !_mapManager.IsMap(uid) &&
|
||||
!_mapManager.IsGrid(uid) &&
|
||||
!_container.IsEntityInContainer(uid, xform);
|
||||
}
|
||||
|
||||
private void OnParentChange(ref EntParentChangedMessage args)
|
||||
{
|
||||
if (_mapManager.IsMap(args.Entity) ||
|
||||
_mapManager.IsGrid(args.Entity) ||
|
||||
EntityManager.GetComponent<MetaDataComponent>(args.Entity).EntityLifeStage < EntityLifeStage.Initialized) return;
|
||||
|
||||
EntityLookupComponent? oldLookup = null;
|
||||
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
var xform = xformQuery.GetComponent(args.Entity);
|
||||
|
||||
if (args.OldParent != null)
|
||||
{
|
||||
oldLookup = GetLookup(args.OldParent.Value, xformQuery);
|
||||
}
|
||||
|
||||
var newLookup = GetLookup(args.Entity, xform, xformQuery);
|
||||
|
||||
// If parent is the same then no need to do anything as position should stay the same.
|
||||
if (oldLookup == newLookup) return;
|
||||
|
||||
RemoveFromEntityTree(oldLookup, xform, xformQuery);
|
||||
|
||||
if (newLookup != null)
|
||||
AddToEntityTree(newLookup, xform, xformQuery);
|
||||
}
|
||||
|
||||
private void OnBoundsUpdate(ref UpdateLookupBoundsEvent ev)
|
||||
{
|
||||
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
var xform = xformQuery.GetComponent(ev.Uid);
|
||||
|
||||
if (xform.Anchored || _container.IsEntityInContainer(ev.Uid, xform)) return;
|
||||
|
||||
var lookup = GetLookup(ev.Uid, xform, xformQuery);
|
||||
|
||||
if (lookup == null) return;
|
||||
|
||||
var lookupXform = xformQuery.GetComponent(lookup.Owner);
|
||||
var coordinates = _transform.GetMoverCoordinates(xform.Coordinates, xformQuery);
|
||||
// If we're contained then LocalRotation should be 0 anyway.
|
||||
var aabb = GetAABB(xform.Owner, coordinates.Position, _transform.GetWorldRotation(xform) - _transform.GetWorldRotation(lookupXform), xform, xformQuery);
|
||||
|
||||
// TODO: Only container children need updating so could manually do this slightly better.
|
||||
AddToEntityTree(lookup, xform, aabb, xformQuery);
|
||||
}
|
||||
|
||||
private void AddToEntityTree(
|
||||
EntityLookupComponent lookup,
|
||||
TransformComponent xform,
|
||||
EntityQuery<TransformComponent> xformQuery,
|
||||
bool recursive = true,
|
||||
bool contained = false)
|
||||
{
|
||||
var lookupXform = xformQuery.GetComponent(lookup.Owner);
|
||||
var coordinates = _transform.GetMoverCoordinates(xform.Coordinates, xformQuery);
|
||||
// If we're contained then LocalRotation should be 0 anyway.
|
||||
var aabb = GetAABB(xform.Owner, coordinates.Position, _transform.GetWorldRotation(xform) - _transform.GetWorldRotation(lookupXform), xform, xformQuery);
|
||||
AddToEntityTree(lookup, xform, aabb, xformQuery, recursive, contained);
|
||||
}
|
||||
|
||||
private void AddToEntityTree(
|
||||
EntityLookupComponent? lookup,
|
||||
TransformComponent xform,
|
||||
Box2 aabb,
|
||||
EntityQuery<TransformComponent> xformQuery,
|
||||
bool recursive = true,
|
||||
bool contained = false)
|
||||
{
|
||||
// If entity is in nullspace then no point keeping track of data structure.
|
||||
if (lookup == null) return;
|
||||
|
||||
if (!xform.Anchored)
|
||||
lookup.Tree.AddOrUpdate(xform.Owner, aabb);
|
||||
|
||||
var childEnumerator = xform.ChildEnumerator;
|
||||
|
||||
if (xform.ChildCount == 0 || !recursive) return;
|
||||
|
||||
// TODO: Pass this down instead son.
|
||||
var lookupXform = xformQuery.GetComponent(lookup.Owner);
|
||||
// TODO: Just don't store contained stuff, it's way too expensive for updates and makes the tree much bigger.
|
||||
|
||||
// Recursively update children.
|
||||
if (contained)
|
||||
{
|
||||
// Just re-use the topmost AABB.
|
||||
while (childEnumerator.MoveNext(out var child))
|
||||
{
|
||||
AddToEntityTree(lookup, xformQuery.GetComponent(child.Value), aabb, xformQuery, contained: true);
|
||||
}
|
||||
}
|
||||
// If they're in a container then it just uses the parent's AABB.
|
||||
else if (EntityManager.TryGetComponent<ContainerManagerComponent>(xform.Owner, out var conManager))
|
||||
{
|
||||
while (childEnumerator.MoveNext(out var child))
|
||||
{
|
||||
if (conManager.ContainsEntity(child.Value))
|
||||
{
|
||||
AddToEntityTree(lookup, xformQuery.GetComponent(child.Value), aabb, xformQuery, contained: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
var coordinates = _transform.GetMoverCoordinates(xform.Coordinates, xformQuery);
|
||||
var childXform = xformQuery.GetComponent(child.Value);
|
||||
// TODO: If we have 0 position and not contained can optimise these further, but future problem.
|
||||
var childAABB = GetAABBNoContainer(child.Value, coordinates.Position, childXform.WorldRotation - lookupXform.WorldRotation);
|
||||
AddToEntityTree(lookup, childXform, childAABB, xformQuery);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while (childEnumerator.MoveNext(out var child))
|
||||
{
|
||||
var coordinates = _transform.GetMoverCoordinates(xform.Coordinates, xformQuery);
|
||||
var childXform = xformQuery.GetComponent(child.Value);
|
||||
// TODO: If we have 0 position and not contained can optimise these further, but future problem.
|
||||
var childAABB = GetAABBNoContainer(child.Value, coordinates.Position, childXform.WorldRotation - lookupXform.WorldRotation);
|
||||
AddToEntityTree(lookup, childXform, childAABB, xformQuery);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveFromEntityTree(EntityUid uid, bool recursive = true)
|
||||
{
|
||||
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
var xform = xformQuery.GetComponent(uid);
|
||||
var lookup = GetLookup(uid, xform, xformQuery);
|
||||
RemoveFromEntityTree(lookup, xform, xformQuery, recursive);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recursively iterates through this entity's children and removes them from the entitylookupcomponent.
|
||||
/// </summary>
|
||||
private void RemoveFromEntityTree(EntityLookupComponent? lookup, TransformComponent xform, EntityQuery<TransformComponent> xformQuery, bool recursive = true)
|
||||
{
|
||||
// TODO: Move this out of the loop
|
||||
if (lookup == null) return;
|
||||
|
||||
lookup.Tree.Remove(xform.Owner);
|
||||
|
||||
if (!recursive) return;
|
||||
|
||||
var childEnumerator = xform.ChildEnumerator;
|
||||
|
||||
while (childEnumerator.MoveNext(out var child))
|
||||
{
|
||||
RemoveFromEntityTree(lookup, xformQuery.GetComponent(child.Value), xformQuery);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private EntityLookupComponent? GetLookup(EntityUid entity, EntityQuery<TransformComponent> xformQuery)
|
||||
{
|
||||
var xform = xformQuery.GetComponent(entity);
|
||||
return GetLookup(entity, xform, xformQuery);
|
||||
}
|
||||
|
||||
private EntityLookupComponent? GetLookup(EntityUid uid, TransformComponent xform, EntityQuery<TransformComponent> xformQuery)
|
||||
{
|
||||
if (xform.MapID == MapId.Nullspace)
|
||||
return null;
|
||||
|
||||
var parent = xform.ParentUid;
|
||||
var lookupQuery = EntityManager.GetEntityQuery<EntityLookupComponent>();
|
||||
|
||||
// If we're querying a map / grid just return it directly.
|
||||
if (lookupQuery.TryGetComponent(uid, out var lookup))
|
||||
{
|
||||
return lookup;
|
||||
}
|
||||
|
||||
while (parent.IsValid())
|
||||
{
|
||||
if (lookupQuery.TryGetComponent(parent, out var comp)) return comp;
|
||||
parent = xformQuery.GetComponent(parent).ParentUid;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
#region Bounds
|
||||
|
||||
/// <summary>
|
||||
/// Get the AABB of an entity with the supplied position and angle. Tries to consider if the entity is in a container.
|
||||
/// </summary>
|
||||
private Box2 GetAABB(EntityUid uid, Vector2 position, Angle angle, TransformComponent xform, EntityQuery<TransformComponent> xformQuery)
|
||||
{
|
||||
// If we're in a container then we just use the container's bounds.
|
||||
if (_container.TryGetOuterContainer(uid, xform, out var container, xformQuery))
|
||||
{
|
||||
return GetAABBNoContainer(container.Owner, position, angle);
|
||||
}
|
||||
|
||||
return GetAABBNoContainer(uid, position, angle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the AABB of an entity with the supplied position and angle without considering containers.
|
||||
/// </summary>
|
||||
private Box2 GetAABBNoContainer(EntityUid uid, Vector2 position, Angle angle)
|
||||
{
|
||||
// DebugTools.Assert(!_container.IsEntityInContainer(uid, xform));
|
||||
Box2 localAABB;
|
||||
var transform = new Transform(position, angle);
|
||||
|
||||
if (EntityManager.TryGetComponent<ILookupWorldBox2Component>(uid, out var worldLookup))
|
||||
{
|
||||
localAABB = worldLookup.GetAABB(transform);
|
||||
}
|
||||
else
|
||||
{
|
||||
localAABB = new Box2Rotated(new Box2(transform.Position, transform.Position), transform.Quaternion2D.Angle, transform.Position).CalcBoundingBox();
|
||||
}
|
||||
|
||||
return localAABB;
|
||||
}
|
||||
|
||||
public Box2 GetWorldAABB(EntityUid uid, TransformComponent? xform = null)
|
||||
{
|
||||
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
xform ??= xformQuery.GetComponent(uid);
|
||||
var (worldPos, worldRot) = xform.GetWorldPositionRotation();
|
||||
|
||||
return GetAABB(uid, worldPos, worldRot, xform, xformQuery);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flags this entity for an update to their lookup bounds.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly struct UpdateLookupBoundsEvent
|
||||
{
|
||||
public readonly EntityUid Uid;
|
||||
|
||||
public UpdateLookupBoundsEvent(EntityUid uid)
|
||||
{
|
||||
Uid = uid;
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Robust.Shared.GameObjects;
|
||||
|
||||
public sealed class MetaDataSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<MetaDataComponent, ComponentHandleState>(OnMetaDataHandle);
|
||||
SubscribeLocalEvent<MetaDataComponent, ComponentGetState>(OnMetaDataGetState);
|
||||
}
|
||||
|
||||
private void OnMetaDataGetState(EntityUid uid, MetaDataComponent component, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new MetaDataComponentState(component._entityName, component._entityDescription, component._entityPrototype?.ID);
|
||||
}
|
||||
|
||||
private void OnMetaDataHandle(EntityUid uid, MetaDataComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not MetaDataComponentState state)
|
||||
return;
|
||||
|
||||
component._entityName = state.Name;
|
||||
component._entityDescription = state.Description;
|
||||
|
||||
if(state.PrototypeId != null)
|
||||
component._entityPrototype = _proto.Index<EntityPrototype>(state.PrototypeId);
|
||||
}
|
||||
|
||||
public void AddFlag(EntityUid uid, MetaDataFlags flags, MetaDataComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component)) return;
|
||||
|
||||
component.Flags |= flags;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to remove the specific flag from metadata.
|
||||
/// Other systems can choose not to allow the removal if it's still relevant.
|
||||
/// </summary>
|
||||
public void RemoveFlag(EntityUid uid, MetaDataFlags flags, MetaDataComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component) ||
|
||||
(component.Flags & flags) == 0x0) return;
|
||||
|
||||
var ev = new MetaFlagRemoveAttemptEvent();
|
||||
EntityManager.EventBus.RaiseLocalEvent(component.Owner, ref ev);
|
||||
|
||||
if (ev.Cancelled) return;
|
||||
|
||||
component.Flags &= ~flags;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised if <see cref="MetaDataSystem"/> is trying to remove a particular flag.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public struct MetaFlagRemoveAttemptEvent
|
||||
{
|
||||
public bool Cancelled = false;
|
||||
|
||||
public MetaFlagRemoveAttemptEvent()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ using Robust.Shared.Serialization;
|
||||
|
||||
namespace Robust.Shared.GameObjects;
|
||||
|
||||
public abstract class SharedAppearanceSystem : EntitySystem
|
||||
internal abstract class SharedAppearanceSystem : EntitySystem
|
||||
{
|
||||
public virtual void MarkDirty(AppearanceComponent component) {}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
public abstract class SharedGridFixtureSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly FixtureSystem _fixtures = default!;
|
||||
|
||||
private bool _enabled;
|
||||
@@ -51,25 +52,30 @@ namespace Robust.Shared.GameObjects
|
||||
// Just in case there's any deleted we'll ToArray
|
||||
foreach (var (_, chunk) in gridInternal.GetMapChunks().ToArray())
|
||||
{
|
||||
gridInternal.RegenerateCollision(chunk);
|
||||
chunk.RegenerateCollision();
|
||||
}
|
||||
}
|
||||
|
||||
internal void RegenerateCollision(EntityUid gridEuid, MapChunk chunk, List<Box2i> rectangles)
|
||||
internal void RegenerateCollision(MapChunk chunk, List<Box2i> rectangles)
|
||||
{
|
||||
if (!_enabled) return;
|
||||
|
||||
DebugTools.Assert(chunk.FilledTiles > 0);
|
||||
if (!_mapManager.TryGetGrid(chunk.GridId, out var grid) ||
|
||||
!EntityManager.EntityExists(grid.GridEntityId)) return;
|
||||
|
||||
if (!EntityManager.TryGetComponent(gridEuid, out PhysicsComponent? physicsComponent))
|
||||
var gridEnt = grid.GridEntityId;
|
||||
|
||||
DebugTools.Assert(chunk.ValidTiles > 0);
|
||||
|
||||
if (!EntityManager.TryGetComponent(gridEnt, out PhysicsComponent? physicsComponent))
|
||||
{
|
||||
Logger.ErrorS("physics", $"Trying to regenerate collision for {gridEuid} that doesn't have {nameof(physicsComponent)}");
|
||||
Logger.ErrorS("physics", $"Trying to regenerate collision for {gridEnt} that doesn't have {nameof(physicsComponent)}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EntityManager.TryGetComponent(gridEuid, out FixturesComponent? fixturesComponent))
|
||||
if (!EntityManager.TryGetComponent(gridEnt, out FixturesComponent? fixturesComponent))
|
||||
{
|
||||
Logger.ErrorS("physics", $"Trying to regenerate collision for {gridEuid} that doesn't have {nameof(fixturesComponent)}");
|
||||
Logger.ErrorS("physics", $"Trying to regenerate collision for {gridEnt} that doesn't have {nameof(fixturesComponent)}");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -164,7 +170,7 @@ namespace Robust.Shared.GameObjects
|
||||
if (updated)
|
||||
{
|
||||
_fixtures.FixtureUpdate(fixturesComponent, physicsComponent);
|
||||
EntityManager.EventBus.RaiseLocalEvent(gridEuid,new GridFixtureChangeEvent {NewFixtures = chunk.Fixtures});
|
||||
EntityManager.EventBus.RaiseLocalEvent(gridEnt,new GridFixtureChangeEvent {NewFixtures = chunk.Fixtures});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.GameObjects;
|
||||
|
||||
public abstract partial class SharedPhysicsSystem
|
||||
{
|
||||
[Dependency] private readonly FixtureSystem _fixtures = default!;
|
||||
|
||||
#region Collision Masks & Layers
|
||||
|
||||
public void AddCollisionMask(FixturesComponent component, Fixture fixture, int mask)
|
||||
{
|
||||
if ((fixture.CollisionMask & mask) == mask) return;
|
||||
|
||||
DebugTools.Assert(component.Fixtures.ContainsKey(fixture.ID));
|
||||
fixture._collisionMask |= mask;
|
||||
|
||||
if (TryComp<PhysicsComponent>(component.Owner, out var body))
|
||||
{
|
||||
_fixtures.FixtureUpdate(component, body);
|
||||
}
|
||||
|
||||
_broadphaseSystem.Refilter(fixture);
|
||||
}
|
||||
|
||||
public void SetCollisionMask(FixturesComponent component, Fixture fixture, int mask)
|
||||
{
|
||||
if (fixture.CollisionMask == mask) return;
|
||||
|
||||
DebugTools.Assert(component.Fixtures.ContainsKey(fixture.ID));
|
||||
fixture._collisionMask = mask;
|
||||
|
||||
if (TryComp<PhysicsComponent>(component.Owner, out var body))
|
||||
{
|
||||
_fixtures.FixtureUpdate(component, body);
|
||||
}
|
||||
|
||||
_broadphaseSystem.Refilter(fixture);
|
||||
}
|
||||
|
||||
public void RemoveCollisionMask(FixturesComponent component, Fixture fixture, int mask)
|
||||
{
|
||||
if ((fixture.CollisionMask & mask) == 0x0) return;
|
||||
|
||||
DebugTools.Assert(component.Fixtures.ContainsKey(fixture.ID));
|
||||
fixture._collisionMask &= ~mask;
|
||||
|
||||
if (TryComp<PhysicsComponent>(component.Owner, out var body))
|
||||
{
|
||||
_fixtures.FixtureUpdate(component, body);
|
||||
}
|
||||
|
||||
_broadphaseSystem.Refilter(fixture);
|
||||
}
|
||||
|
||||
public void AddCollisionLayer(FixturesComponent component, Fixture fixture, int layer)
|
||||
{
|
||||
if ((fixture.CollisionLayer & layer) == layer) return;
|
||||
|
||||
DebugTools.Assert(component.Fixtures.ContainsKey(fixture.ID));
|
||||
fixture._collisionLayer |= layer;
|
||||
|
||||
if (TryComp<PhysicsComponent>(component.Owner, out var body))
|
||||
{
|
||||
_fixtures.FixtureUpdate(component, body);
|
||||
}
|
||||
|
||||
_broadphaseSystem.Refilter(fixture);
|
||||
}
|
||||
|
||||
public void SetCollisionLayer(FixturesComponent component, Fixture fixture, int layer)
|
||||
{
|
||||
if (fixture.CollisionLayer == layer) return;
|
||||
|
||||
DebugTools.Assert(component.Fixtures.ContainsKey(fixture.ID));
|
||||
fixture._collisionLayer = layer;
|
||||
|
||||
if (TryComp<PhysicsComponent>(component.Owner, out var body))
|
||||
{
|
||||
_fixtures.FixtureUpdate(component, body);
|
||||
}
|
||||
|
||||
_broadphaseSystem.Refilter(fixture);
|
||||
}
|
||||
|
||||
public void RemoveCollisionLayer(FixturesComponent component, Fixture fixture, int layer)
|
||||
{
|
||||
if ((fixture.CollisionLayer & layer) == 0x0) return;
|
||||
|
||||
DebugTools.Assert(component.Fixtures.ContainsKey(fixture.ID));
|
||||
fixture._collisionLayer &= ~layer;
|
||||
|
||||
if (TryComp<PhysicsComponent>(component.Owner, out var body))
|
||||
{
|
||||
_fixtures.FixtureUpdate(component, body);
|
||||
}
|
||||
|
||||
_broadphaseSystem.Refilter(fixture);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -70,7 +70,7 @@ namespace Robust.Shared.GameObjects
|
||||
IoCManager.Resolve<IIslandManager>().Initialize();
|
||||
|
||||
var configManager = IoCManager.Resolve<IConfigurationManager>();
|
||||
configManager.OnValueChanged(CVars.AutoClearForces, OnAutoClearChange);
|
||||
configManager.OnValueChanged(CVars.AutoClearForces, OnAutoClearChange, true);
|
||||
}
|
||||
|
||||
private void HandlePhysicsMapInit(EntityUid uid, SharedPhysicsMapComponent component, ComponentInit args)
|
||||
@@ -81,7 +81,6 @@ namespace Robust.Shared.GameObjects
|
||||
component.ContactManager = new();
|
||||
component.ContactManager.Initialize();
|
||||
component.ContactManager.MapId = component.MapId;
|
||||
component.AutoClearForces = IoCManager.Resolve<IConfigurationManager>().GetCVar(CVars.AutoClearForces);
|
||||
|
||||
component.ContactManager.KinematicControllerCollision += KinematicControllerCollision;
|
||||
}
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Shared.GameObjects;
|
||||
|
||||
public abstract partial class SharedTransformSystem
|
||||
{
|
||||
#region World Matrix
|
||||
|
||||
[Pure]
|
||||
public Matrix3 GetWorldMatrix(EntityUid uid)
|
||||
{
|
||||
return Transform(uid).WorldMatrix;
|
||||
}
|
||||
|
||||
// Temporary until it's moved here
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Matrix3 GetWorldMatrix(TransformComponent component)
|
||||
{
|
||||
return component.WorldMatrix;
|
||||
}
|
||||
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Matrix3 GetWorldMatrix(EntityUid uid, EntityQuery<TransformComponent> xformQuery)
|
||||
{
|
||||
return GetWorldMatrix(xformQuery.GetComponent(uid));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region World Rotation
|
||||
|
||||
[Pure]
|
||||
public Angle GetWorldRotation(EntityUid uid)
|
||||
{
|
||||
return Transform(uid).WorldRotation;
|
||||
}
|
||||
|
||||
// Temporary until it's moved here
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Angle GetWorldRotation(TransformComponent component)
|
||||
{
|
||||
return component.WorldRotation;
|
||||
}
|
||||
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Angle GetWorldRotation(EntityUid uid, EntityQuery<TransformComponent> xformQuery)
|
||||
{
|
||||
return GetWorldRotation(xformQuery.GetComponent(uid));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Inverse World Matrix
|
||||
|
||||
[Pure]
|
||||
public Matrix3 GetInvWorldMatrix(EntityUid uid)
|
||||
{
|
||||
return Comp<TransformComponent>(uid).InvWorldMatrix;
|
||||
}
|
||||
|
||||
// Temporary until it's moved here
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Matrix3 GetInvWorldMatrix(TransformComponent component)
|
||||
{
|
||||
return component.InvWorldMatrix;
|
||||
}
|
||||
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Matrix3 GetInvWorldMatrix(EntityUid uid, EntityQuery<TransformComponent> xformQuery)
|
||||
{
|
||||
return GetInvWorldMatrix(xformQuery.GetComponent(uid));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -7,10 +7,10 @@ using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
public abstract partial class SharedTransformSystem : EntitySystem
|
||||
public abstract class SharedTransformSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _entityLookup = default!;
|
||||
[Dependency] private readonly IEntityLookup _entityLookup = default!;
|
||||
|
||||
private readonly Queue<MoveEvent> _gridMoves = new();
|
||||
private readonly Queue<MoveEvent> _otherMoves = new();
|
||||
@@ -100,65 +100,5 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public EntityCoordinates GetMoverCoordinates(TransformComponent xform)
|
||||
{
|
||||
// If they're parented directly to the map or grid then just return the coordinates.
|
||||
if (!_mapManager.TryGetGrid(xform.GridID, out var grid))
|
||||
{
|
||||
var mapUid = _mapManager.GetMapEntityId(xform.MapID);
|
||||
var coordinates = xform.Coordinates;
|
||||
|
||||
// Parented directly to the map.
|
||||
if (xform.ParentUid == mapUid)
|
||||
return coordinates;
|
||||
|
||||
return new EntityCoordinates(mapUid, coordinates.ToMapPos(EntityManager));
|
||||
}
|
||||
|
||||
// Parented directly to the grid
|
||||
if (grid.GridEntityId == xform.ParentUid)
|
||||
return xform.Coordinates;
|
||||
|
||||
// Parented to grid so convert their pos back to the grid.
|
||||
var gridPos = Transform(grid.GridEntityId).InvWorldMatrix.Transform(xform.WorldPosition);
|
||||
return new EntityCoordinates(grid.GridEntityId, gridPos);
|
||||
}
|
||||
|
||||
public EntityCoordinates GetMoverCoordinates(EntityCoordinates coordinates, EntityQuery<TransformComponent> xformQuery)
|
||||
{
|
||||
// GridID isn't ready during EntityInit so YAY
|
||||
IMapGrid? grid = null;
|
||||
var ent = coordinates.EntityId;
|
||||
|
||||
while (ent.IsValid())
|
||||
{
|
||||
if (_mapManager.TryGetGrid(ent, out grid))
|
||||
break;
|
||||
|
||||
ent = xformQuery.GetComponent(ent).ParentUid;
|
||||
}
|
||||
|
||||
// If they're parented directly to the map or grid then just return the coordinates.
|
||||
if (grid == null)
|
||||
{
|
||||
var mapPos = coordinates.ToMap(EntityManager);
|
||||
var mapUid = _mapManager.GetMapEntityId(mapPos.MapId);
|
||||
|
||||
// Parented directly to the map.
|
||||
if (coordinates.EntityId == mapUid)
|
||||
return coordinates;
|
||||
|
||||
return new EntityCoordinates(mapUid, mapPos.Position);
|
||||
}
|
||||
|
||||
// Parented directly to the grid
|
||||
if (grid.GridEntityId == coordinates.EntityId)
|
||||
return coordinates;
|
||||
|
||||
// Parented to grid so convert their pos back to the grid.
|
||||
var gridPos = Transform(grid.GridEntityId).InvWorldMatrix.Transform(coordinates.ToMapPos(EntityManager));
|
||||
return new EntityCoordinates(grid.GridEntityId, gridPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,69 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Linguini.Bundle.Errors;
|
||||
using Linguini.Syntax.Parser.Error;
|
||||
|
||||
namespace Robust.Shared.Localization;
|
||||
|
||||
internal static class LocHelper
|
||||
namespace Robust.Shared.Localization
|
||||
{
|
||||
public static string FormatCompileErrors(this ParseError self, ReadOnlyMemory<char> resource,
|
||||
string? newLine = null)
|
||||
internal static class LocHelper
|
||||
{
|
||||
ErrorSpan span = new(self.Row, self.Slice!.Value.Start.Value, self.Slice.Value.End.Value,
|
||||
self.Position.Start.Value, self.Position.End.Value);
|
||||
return FormatErrors(self.Message, span, resource, newLine);
|
||||
}
|
||||
|
||||
private static string FormatErrors(string message, ErrorSpan span, ReadOnlyMemory<char> resource, string? newLine)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
var errContext = resource.Slice(span.StartSpan, span.EndSpan - span.StartSpan).ToString();
|
||||
var lines = new List<ReadOnlyMemory<char>>(5);
|
||||
var currLineOffset = 0;
|
||||
var lastStart = 0;
|
||||
for (var i = 0; i < span.StartMark - span.StartSpan; i++)
|
||||
public static string FormatCompileErrors(this ParseError self, ReadOnlyMemory<char> resource)
|
||||
{
|
||||
switch (errContext[i])
|
||||
{
|
||||
// Reset current line so that mark aligns with the reported error
|
||||
// We cheat here a bit, since we both `\r\n` and `\n` end with '\n'
|
||||
case '\n':
|
||||
if (i > 0 && errContext[i - 1] == '\r')
|
||||
{
|
||||
lines.Add(resource.Slice(lastStart, currLineOffset - 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
lines.Add(resource.Slice(lastStart, currLineOffset));
|
||||
}
|
||||
|
||||
lastStart = currLineOffset + 1;
|
||||
currLineOffset = 0;
|
||||
break;
|
||||
default:
|
||||
currLineOffset++;
|
||||
break;
|
||||
}
|
||||
ErrorSpan span = new(self.Row, self.Slice!.Value.Start.Value, self.Slice.Value.End.Value,
|
||||
self.Position.Start.Value, self.Position.End.Value);
|
||||
return FormatErrors(self.Message, span, resource);
|
||||
}
|
||||
|
||||
lines.Add(resource.Slice(lastStart, resource.Length - lastStart));
|
||||
|
||||
|
||||
var lastLine = $"{span.Row + lines.Count - 1}".Length;
|
||||
for (var index = 0; index < lines.Count; index++)
|
||||
private static string FormatErrors(string message, ErrorSpan span, ReadOnlyMemory<char> resource)
|
||||
{
|
||||
var line = lines[index];
|
||||
|
||||
sb.Append(newLine ?? Environment.NewLine).Append(' ').Append($"{span.Row + index}".PadLeft(lastLine))
|
||||
.Append(" |").Append(line);
|
||||
var sb = new StringBuilder();
|
||||
var row = $" {span.Row} ";
|
||||
var errContext = resource.Slice(span.StartSpan, span.EndSpan - span.StartSpan).ToString();
|
||||
sb.Append(row).Append('|')
|
||||
.AppendLine(errContext);
|
||||
sb.Append(' ', row.Length).Append('|')
|
||||
.Append(' ', span.StartMark - span.StartSpan - 1).Append('^', span.EndMark - span.StartMark)
|
||||
.AppendLine($" {message}");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
sb.Append(newLine ?? Environment.NewLine)
|
||||
.Append(' ', currLineOffset + lastLine + 3)
|
||||
.Append('^', span.EndMark - span.StartMark)
|
||||
.Append($" {message}");
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace Robust.Shared.Localization
|
||||
|
||||
if (!TryGetString(messageId, out var msg))
|
||||
{
|
||||
_logSawmill.Debug("Unknown messageId ({culture}): {messageId}", _defaultCulture.Name, messageId);
|
||||
_logSawmill.Warning("Unknown messageId ({culture}): {messageId}", _defaultCulture.Name, messageId);
|
||||
msg = messageId;
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace Robust.Shared.Localization
|
||||
|
||||
if (!TryGetString(messageId, out var msg, args0))
|
||||
{
|
||||
_logSawmill.Debug("Unknown messageId ({culture}): {messageId}", _defaultCulture.Name, messageId);
|
||||
_logSawmill.Warning("Unknown messageId ({culture}): {messageId}", _defaultCulture.Name, messageId);
|
||||
msg = messageId;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Robust.Shared.Map
|
||||
/// <param name="chunk"></param>
|
||||
/// <param name="bounds">The overall bounds that covers every rectangle.</param>
|
||||
/// <param name="rectangles">Each individual rectangle comprising the chunk's bounds</param>
|
||||
public static void PartitionChunk(MapChunk chunk, out Box2i bounds, out List<Box2i> rectangles)
|
||||
public static void PartitionChunk(IMapChunk chunk, out Box2i bounds, out List<Box2i> rectangles)
|
||||
{
|
||||
rectangles = new List<Box2i>();
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using System;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Robust.Shared.Map
|
||||
@@ -15,14 +13,6 @@ namespace Robust.Shared.Map
|
||||
|
||||
internal readonly int Value;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of <see cref="GridId"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This should NOT be used in regular code, and is only public for special/legacy
|
||||
/// cases. Generally you should only use this for parsing a GridId in console commands
|
||||
/// and immediately check if the grid actually exists in the <see cref="IMapManager"/>.
|
||||
/// </remarks>
|
||||
public GridId(int value)
|
||||
{
|
||||
Value = value;
|
||||
@@ -67,30 +57,6 @@ namespace Robust.Shared.Map
|
||||
return self.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="GridId"/> is an alias of the <see cref="EntityUid"/> that
|
||||
/// holds the <see cref="IMapGridComponent"/>, so it can be implicitly converted.
|
||||
/// </summary>
|
||||
public static implicit operator EntityUid(GridId self)
|
||||
{
|
||||
// If this throws, you are either using an unallocated gridId,
|
||||
// or using it after the grid was freed. Both of these are bugs.
|
||||
return IoCManager.Resolve<IMapManager>().GetGridEuid(self);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="GridId"/> is an alias of the <see cref="EntityUid"/> that
|
||||
/// holds the <see cref="IMapGridComponent"/>.
|
||||
/// </summary>
|
||||
public static implicit operator GridId(EntityUid euid)
|
||||
{
|
||||
// If this throws, you are using an EntityUid that isn't a grid.
|
||||
// This would raise the question, "Why does your code think this entity is a grid?".
|
||||
// Grid-ness is defined by the entity having an IMapGridComponent,
|
||||
// was the component removed without you knowing?
|
||||
return IoCManager.Resolve<IMapManager>().GetGridComp(euid).GridIndex;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value.ToString();
|
||||
|
||||
110
Robust.Shared/Map/IMapChunk.cs
Normal file
110
Robust.Shared/Map/IMapChunk.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Shared.Map
|
||||
{
|
||||
/// <summary>
|
||||
/// A square section of a <see cref="IMapGrid"/>.
|
||||
/// </summary>
|
||||
internal interface IMapChunk : IEnumerable<TileRef>
|
||||
{
|
||||
/// <summary>
|
||||
/// The number of tiles per side of the square chunk.
|
||||
/// </summary>
|
||||
ushort ChunkSize { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The X index of this chunk inside the <see cref="IMapGrid"/>.
|
||||
/// </summary>
|
||||
int X { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The Y index of this chunk inside the <see cref="IMapGrid"/>.
|
||||
/// </summary>
|
||||
int Y { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The positional indices of this chunk in the <see cref="IMapGrid"/>.
|
||||
/// </summary>
|
||||
Vector2i Indices { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the tile at the given indices.
|
||||
/// </summary>
|
||||
/// <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>
|
||||
TileRef GetTileRef(ushort xIndex, ushort yIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the tile reference at the given indices.
|
||||
/// </summary>
|
||||
/// <param name="indices">The tile indices relative to the chunk origin.</param>
|
||||
/// <returns>A reference to a tile.</returns>
|
||||
TileRef GetTileRef(Vector2i indices);
|
||||
|
||||
Tile GetTile(ushort xIndex, ushort yIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Returns all of the tiles in the chunk, while optionally filtering empty files.
|
||||
/// Returned order is guaranteed to be row-major.
|
||||
/// </summary>
|
||||
/// <param name="ignoreEmpty">Will empty (space) tiles be added to the collection?</param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<TileRef> GetAllTiles(bool ignoreEmpty = true);
|
||||
|
||||
/// <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>
|
||||
void SetTile(ushort xIndex, ushort yIndex, Tile tile);
|
||||
|
||||
/// <summary>
|
||||
/// Transforms Tile indices relative to the grid into tile indices relative to this chunk.
|
||||
/// </summary>
|
||||
/// <param name="gridTile">Tile indices relative to the grid.</param>
|
||||
/// <returns>Tile indices relative to this chunk.</returns>
|
||||
Vector2i GridTileToChunkTile(Vector2i gridTile);
|
||||
|
||||
/// <summary>
|
||||
/// Translates chunk tile indices to grid tile indices.
|
||||
/// </summary>
|
||||
/// <param name="chunkTile">The indices relative to the chunk origin.</param>
|
||||
/// <returns>The indices relative to the grid origin.</returns>
|
||||
Vector2i ChunkTileToGridTile(Vector2i chunkTile);
|
||||
|
||||
IEnumerable<EntityUid> GetSnapGridCell(ushort xCell, ushort yCell);
|
||||
|
||||
void AddToSnapGridCell(ushort xCell, ushort yCell, EntityUid euid);
|
||||
void RemoveFromSnapGridCell(ushort xCell, ushort yCell, EntityUid euid);
|
||||
IEnumerable<EntityUid> GetAllAnchoredEnts();
|
||||
|
||||
/// <summary>
|
||||
/// Like <see cref="GetAllAnchoredEnts"/>... but fast.
|
||||
/// </summary>
|
||||
void FastGetAllAnchoredEnts(EntityUidQueryCallback callback);
|
||||
|
||||
Box2i CalcLocalBounds();
|
||||
|
||||
// TODO: We can rely on the fixture instead for the bounds in the future but we also need to
|
||||
// update the rendering to account for it. Better working on accurate grid bounds after rotation IMO.
|
||||
/// <summary>
|
||||
/// Calculate the bounds of this chunk.
|
||||
/// </summary>
|
||||
Box2Rotated CalcWorldBounds(Vector2? worldPos = null, Angle? worldRot = null);
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the AABB for this chunk.
|
||||
/// </summary>
|
||||
Box2 CalcWorldAABB(Vector2? worldPos = null, Angle? worldRot = null);
|
||||
|
||||
/// <summary>
|
||||
/// Tests if a point is on top of a non-empty tile.
|
||||
/// </summary>
|
||||
/// <param name="localIndices">Local tile indices</param>
|
||||
bool CollidesWithChunk(Vector2i localIndices);
|
||||
}
|
||||
}
|
||||
22
Robust.Shared/Map/IMapChunkInternal.cs
Normal file
22
Robust.Shared/Map/IMapChunkInternal.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Shared.Map
|
||||
{
|
||||
/// <inheritdoc />
|
||||
internal interface IMapChunkInternal : IMapChunk
|
||||
{
|
||||
List<Fixture> Fixtures { get; set; }
|
||||
|
||||
bool SuppressCollisionRegeneration { get; set; }
|
||||
|
||||
void RegenerateCollision();
|
||||
|
||||
/// <summary>
|
||||
/// The last game simulation tick that a tile on this chunk was modified.
|
||||
/// </summary>
|
||||
GameTick LastTileModifiedTick { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,25 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Shared.Map
|
||||
{
|
||||
internal interface IMapGridInternal : IMapGrid
|
||||
{
|
||||
GameTick LastTileModifiedTick { get; }
|
||||
|
||||
GameTick CurTick { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The total number of chunks contained on this grid.
|
||||
/// </summary>
|
||||
int ChunkCount { get; }
|
||||
|
||||
void NotifyTileChanged(in TileRef tileRef, in Tile oldTile);
|
||||
|
||||
void UpdateAABB();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the chunk at the given indices. If the chunk does not exist,
|
||||
/// then a new one is generated that is filled with empty space.
|
||||
@@ -17,7 +27,7 @@ namespace Robust.Shared.Map
|
||||
/// <param name="xIndex">The X index of the chunk in this grid.</param>
|
||||
/// <param name="yIndex">The Y index of the chunk in this grid.</param>
|
||||
/// <returns>The existing or new chunk.</returns>
|
||||
MapChunk GetChunk(int xIndex, int yIndex);
|
||||
IMapChunkInternal GetChunk(int xIndex, int yIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Removes the chunk with the specified origin.
|
||||
@@ -30,7 +40,7 @@ namespace Robust.Shared.Map
|
||||
/// </summary>
|
||||
/// <param name="chunkIndices">The indices of the chunk in this grid.</param>
|
||||
/// <returns>The existing or new chunk.</returns>
|
||||
MapChunk GetChunk(Vector2i chunkIndices);
|
||||
IMapChunkInternal GetChunk(Vector2i chunkIndices);
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether a chunk exists with the specified indices.
|
||||
@@ -41,35 +51,13 @@ namespace Robust.Shared.Map
|
||||
/// Returns all chunks in this grid. This will not generate new chunks.
|
||||
/// </summary>
|
||||
/// <returns>All chunks in the grid.</returns>
|
||||
IReadOnlyDictionary<Vector2i, MapChunk> GetMapChunks();
|
||||
IReadOnlyDictionary<Vector2i, IMapChunkInternal> GetMapChunks();
|
||||
|
||||
/// <summary>
|
||||
/// Returns all the <see cref="MapChunk"/> intersecting the worldAABB.
|
||||
/// Returns all the <see cref="IMapChunkInternal"/> intersecting the worldAABB.
|
||||
/// </summary>
|
||||
void GetMapChunks(Box2 worldAABB, out MapGrid.ChunkEnumerator enumerator);
|
||||
|
||||
/// <summary>
|
||||
/// Returns all the <see cref="MapChunk"/> intersecting the rotated world box.
|
||||
/// </summary>
|
||||
void GetMapChunks(Box2Rotated worldArea, out MapGrid.ChunkEnumerator enumerator);
|
||||
|
||||
/// <summary>
|
||||
/// Regenerates the chunk local bounds of this chunk.
|
||||
/// </summary>
|
||||
void RegenerateCollision(MapChunk mapChunk);
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the world space AABB for this chunk.
|
||||
/// </summary>
|
||||
Box2 CalcWorldAABB(MapChunk mapChunk);
|
||||
|
||||
/// <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>
|
||||
TileRef GetTileRef(MapChunk mapChunk, ushort xIndex, ushort yIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,13 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Shared.Map
|
||||
{
|
||||
/// <summary>
|
||||
/// This manages all of the grids in the world.
|
||||
/// </summary>
|
||||
public interface IMapManager : IPauseManager
|
||||
public interface IMapManager
|
||||
{
|
||||
/// <summary>
|
||||
/// The default <see cref="MapId" /> that is always available. Equivalent to SS13 Null space.
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Robust.Shared.Map
|
||||
|
||||
void OnComponentRemoved(MapGridComponent comp);
|
||||
|
||||
void ChunkRemoved(GridId gridId, MapChunk chunk);
|
||||
void ChunkRemoved(MapChunk chunk);
|
||||
|
||||
/// <summary>
|
||||
/// Raises the OnTileChanged event.
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Timing;
|
||||
@@ -8,63 +10,48 @@ using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Map
|
||||
{
|
||||
/// <summary>
|
||||
/// A square section of a <see cref="IMapGrid"/>.
|
||||
/// </summary>
|
||||
internal sealed class MapChunk
|
||||
/// <inheritdoc />
|
||||
internal sealed class MapChunk : IMapChunkInternal
|
||||
{
|
||||
/// <summary>
|
||||
/// New SnapGrid cells are allocated with this capacity.
|
||||
/// </summary>
|
||||
private const int SnapCellStartingCapacity = 1;
|
||||
|
||||
public GridId GridId => _grid.Index;
|
||||
|
||||
private readonly IMapGridInternal _grid;
|
||||
private readonly Vector2i _gridIndices;
|
||||
|
||||
private readonly Tile[,] _tiles;
|
||||
private readonly SnapGridCell[,] _snapGrid;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a tile is modified on this chunk.
|
||||
/// </summary>
|
||||
public event TileModifiedDelegate? TileModified;
|
||||
// We'll keep a running count of how many tiles are non-empty.
|
||||
// If this ever hits 0 then we know the chunk can be deleted.
|
||||
// The alternative is that every time we SetTile we iterate every tile in the chunk.
|
||||
internal int ValidTiles { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Keeps a running count of the number of filled tiles in this chunk.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This will always be between 1 and <see cref="ChunkSize"/>^2.
|
||||
/// </remarks>
|
||||
internal int FilledTiles { get; private set; }
|
||||
private Box2i _cachedBounds;
|
||||
|
||||
/// <summary>
|
||||
/// Chunk-local AABB of this chunk.
|
||||
/// </summary>
|
||||
public Box2i CachedBounds { get; set; }
|
||||
public List<Fixture> Fixtures { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Physics fixtures that make up this grid chunk.
|
||||
/// </summary>
|
||||
public List<Fixture> Fixtures { get; } = new();
|
||||
/// <inheritdoc />
|
||||
public GameTick LastTileModifiedTick { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The last game simulation tick that a tile on this chunk was modified.
|
||||
/// </summary>
|
||||
public GameTick LastTileModifiedTick { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Setting this property to <see langword="true"/> suppresses collision regeneration on the chunk until the
|
||||
/// property is set to <see langword="false"/>.
|
||||
/// </summary>
|
||||
public bool SuppressCollisionRegeneration { get; set; }
|
||||
/// <inheritdoc />
|
||||
public GameTick LastAnchoredModifiedTick { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs an instance of a MapGrid chunk.
|
||||
/// </summary>
|
||||
/// <param name="grid"></param>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
/// <param name="chunkSize"></param>
|
||||
public MapChunk(int x, int y, ushort chunkSize)
|
||||
public MapChunk(IMapGridInternal grid, int x, int y, ushort chunkSize)
|
||||
{
|
||||
_grid = grid;
|
||||
LastTileModifiedTick = grid.CurTick;
|
||||
_gridIndices = new Vector2i(x, y);
|
||||
ChunkSize = chunkSize;
|
||||
|
||||
@@ -72,33 +59,42 @@ namespace Robust.Shared.Map
|
||||
_snapGrid = new SnapGridCell[ChunkSize, ChunkSize];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The number of tiles per side of the square chunk.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public ushort ChunkSize { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The X index of this chunk inside the <see cref="IMapGrid"/>.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public int X => _gridIndices.X;
|
||||
|
||||
/// <summary>
|
||||
/// The Y index of this chunk inside the <see cref="IMapGrid"/>.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public int Y => _gridIndices.Y;
|
||||
|
||||
/// <summary>
|
||||
/// The positional indices of this chunk in the <see cref="IMapGrid"/>.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public Vector2i Indices => _gridIndices;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the tile at the given chunk indices.
|
||||
/// </summary>
|
||||
/// <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></returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">The index is less than or greater than the size of the chunk.</exception>
|
||||
/// <inheritdoc />
|
||||
public TileRef GetTileRef(ushort xIndex, ushort yIndex)
|
||||
{
|
||||
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.");
|
||||
|
||||
var indices = ChunkTileToGridTile(new Vector2i(xIndex, yIndex));
|
||||
return new TileRef(_grid.ParentMapId, _grid.Index, indices, _tiles[xIndex, yIndex]);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public TileRef GetTileRef(Vector2i indices)
|
||||
{
|
||||
if (indices.X >= ChunkSize || indices.X < 0 || indices.Y >= ChunkSize || indices.Y < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(indices), "Tile indices out of bounds.");
|
||||
|
||||
var chunkIndices = ChunkTileToGridTile(indices);
|
||||
return new TileRef(_grid.ParentMapId, _grid.Index, chunkIndices, _tiles[indices.X, indices.Y]);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Tile GetTile(ushort xIndex, ushort yIndex)
|
||||
{
|
||||
if (xIndex >= ChunkSize)
|
||||
@@ -110,12 +106,23 @@ namespace Robust.Shared.Map
|
||||
return _tiles[xIndex, yIndex];
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<TileRef> GetAllTiles(bool ignoreEmpty = true)
|
||||
{
|
||||
for (var x = 0; x < ChunkSize; x++)
|
||||
{
|
||||
for (var y = 0; y < ChunkSize; y++)
|
||||
{
|
||||
if (ignoreEmpty && _tiles[x, y].IsEmpty)
|
||||
continue;
|
||||
|
||||
var indices = ChunkTileToGridTile(new Vector2i(x, y));
|
||||
yield return new TileRef(_grid.ParentMapId, _grid.Index, indices.X, indices.Y, _tiles[x, y]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetTile(ushort xIndex, ushort yIndex, Tile tile)
|
||||
{
|
||||
if (xIndex >= ChunkSize)
|
||||
@@ -125,58 +132,81 @@ namespace Robust.Shared.Map
|
||||
throw new ArgumentOutOfRangeException(nameof(yIndex), "Tile indices out of bounds.");
|
||||
|
||||
// same tile, no point to continue
|
||||
if (_tiles[xIndex, yIndex] == tile)
|
||||
if (_tiles[xIndex, yIndex].TypeId == tile.TypeId)
|
||||
return;
|
||||
|
||||
var oldTile = _tiles[xIndex, yIndex];
|
||||
var oldFilledTiles = FilledTiles;
|
||||
var oldIsEmpty = _tiles[xIndex, yIndex].IsEmpty;
|
||||
var oldValidTiles = ValidTiles;
|
||||
|
||||
if (oldTile.IsEmpty != tile.IsEmpty)
|
||||
if (oldIsEmpty != tile.IsEmpty)
|
||||
{
|
||||
if (oldTile.IsEmpty)
|
||||
if (oldIsEmpty)
|
||||
{
|
||||
FilledTiles += 1;
|
||||
ValidTiles += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
FilledTiles -= 1;
|
||||
ValidTiles -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
var shapeChanged = oldFilledTiles != FilledTiles;
|
||||
DebugTools.Assert(FilledTiles >= 0);
|
||||
DebugTools.Assert(ValidTiles >= 0);
|
||||
var gridTile = ChunkTileToGridTile(new Vector2i(xIndex, yIndex));
|
||||
var newTileRef = new TileRef(_grid.ParentMapId, _grid.Index, gridTile, tile);
|
||||
var oldTile = _tiles[xIndex, yIndex];
|
||||
LastTileModifiedTick = _grid.CurTick;
|
||||
|
||||
_tiles[xIndex, yIndex] = tile;
|
||||
|
||||
var tileIndices = new Vector2i(xIndex, yIndex);
|
||||
TileModified?.Invoke(this, tileIndices, tile, oldTile, shapeChanged);
|
||||
// As the collision regeneration can potentially delete the chunk we'll notify of the tile changed first.
|
||||
_grid.NotifyTileChanged(newTileRef, oldTile);
|
||||
|
||||
if (!SuppressCollisionRegeneration && oldValidTiles != ValidTiles)
|
||||
{
|
||||
RegenerateCollision();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transforms Tile indices relative to the grid into tile indices relative to this chunk.
|
||||
/// Returns an enumerator that iterates through all grid tiles.
|
||||
/// </summary>
|
||||
/// <param name="gridTile">Tile indices relative to the grid.</param>
|
||||
/// <returns>Tile indices relative to this chunk.</returns>
|
||||
/// <returns></returns>
|
||||
public IEnumerator<TileRef> GetEnumerator()
|
||||
{
|
||||
for (var x = 0; x < ChunkSize; x++)
|
||||
{
|
||||
for (var y = 0; y < ChunkSize; y++)
|
||||
{
|
||||
if (_tiles[x, y].IsEmpty)
|
||||
continue;
|
||||
|
||||
var gridTile = ChunkTileToGridTile(new Vector2i(x, y));
|
||||
yield return new TileRef(_grid.ParentMapId, _grid.Index, gridTile.X, gridTile.Y, _tiles[x, y]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Vector2i GridTileToChunkTile(Vector2i gridTile)
|
||||
{
|
||||
var x = MathHelper.Mod(gridTile.X, ChunkSize);
|
||||
var y = MathHelper.Mod(gridTile.Y, ChunkSize);
|
||||
var size = ChunkSize;
|
||||
var x = MathHelper.Mod(gridTile.X, size);
|
||||
var y = MathHelper.Mod(gridTile.Y, size);
|
||||
return new Vector2i(x, y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Translates chunk tile indices to grid tile indices.
|
||||
/// </summary>
|
||||
/// <param name="chunkTile">The indices relative to the chunk origin.</param>
|
||||
/// <returns>The indices relative to the grid origin.</returns>
|
||||
/// <inheritdoc />
|
||||
public Vector2i ChunkTileToGridTile(Vector2i chunkTile)
|
||||
{
|
||||
return chunkTile + _gridIndices * ChunkSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the anchored cell at the given tile indices.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<EntityUid> GetSnapGridCell(ushort xCell, ushort yCell)
|
||||
{
|
||||
if (xCell >= ChunkSize)
|
||||
@@ -196,9 +226,7 @@ namespace Robust.Shared.Map
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an entity to the anchor cell at the given tile indices.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public void AddToSnapGridCell(ushort xCell, ushort yCell, EntityUid euid)
|
||||
{
|
||||
if (xCell >= ChunkSize)
|
||||
@@ -212,11 +240,10 @@ namespace Robust.Shared.Map
|
||||
|
||||
DebugTools.Assert(!cell.Center.Contains(euid));
|
||||
cell.Center.Add(euid);
|
||||
LastAnchoredModifiedTick = _grid.CurTick;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an entity from the anchor cell at the given tile indices.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public void RemoveFromSnapGridCell(ushort xCell, ushort yCell, EntityUid euid)
|
||||
{
|
||||
if (xCell >= ChunkSize)
|
||||
@@ -227,6 +254,93 @@ namespace Robust.Shared.Map
|
||||
|
||||
ref var cell = ref _snapGrid[xCell, yCell];
|
||||
cell.Center?.Remove(euid);
|
||||
LastAnchoredModifiedTick = _grid.CurTick;
|
||||
}
|
||||
|
||||
public IEnumerable<EntityUid> GetAllAnchoredEnts()
|
||||
{
|
||||
foreach (var cell in _snapGrid)
|
||||
{
|
||||
if (cell.Center is null)
|
||||
continue;
|
||||
|
||||
foreach (var euid in cell.Center)
|
||||
{
|
||||
yield return euid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void FastGetAllAnchoredEnts(EntityUidQueryCallback callback)
|
||||
{
|
||||
foreach (var cell in _snapGrid)
|
||||
{
|
||||
if (cell.Center is null)
|
||||
continue;
|
||||
|
||||
foreach (var euid in cell.Center)
|
||||
{
|
||||
callback(euid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool SuppressCollisionRegeneration { get; set; }
|
||||
|
||||
public void RegenerateCollision()
|
||||
{
|
||||
// Even if the chunk is still removed still need to make sure bounds are updated (for now...)
|
||||
if (ValidTiles == 0)
|
||||
{
|
||||
var grid = (IMapGridInternal) IoCManager.Resolve<IMapManager>().GetGrid(GridId);
|
||||
|
||||
grid.RemoveChunk(_gridIndices);
|
||||
}
|
||||
|
||||
// generate collision rects
|
||||
GridChunkPartition.PartitionChunk(this, out _cachedBounds, out var rectangles);
|
||||
|
||||
_grid.UpdateAABB();
|
||||
|
||||
// TryGet because unit tests YAY
|
||||
if (ValidTiles > 0 && EntitySystem.TryGet(out SharedGridFixtureSystem? system))
|
||||
system.RegenerateCollision(this, rectangles);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Box2i CalcLocalBounds()
|
||||
{
|
||||
return _cachedBounds;
|
||||
}
|
||||
|
||||
public Box2Rotated CalcWorldBounds(Vector2? gridPos = null, Angle? gridRot = null)
|
||||
{
|
||||
gridRot ??= _grid.WorldRotation;
|
||||
gridPos ??= _grid.WorldPosition;
|
||||
var worldPos = gridPos.Value + gridRot.Value.RotateVec(Indices * _grid.TileSize * ChunkSize);
|
||||
|
||||
var localBounds = CalcLocalBounds();
|
||||
var ts = _grid.TileSize;
|
||||
|
||||
var scaledLocalBounds = new Box2Rotated(new Box2(
|
||||
localBounds.Left * ts,
|
||||
localBounds.Bottom * ts,
|
||||
localBounds.Right * ts,
|
||||
localBounds.Top * ts).Translated(worldPos), gridRot.Value, worldPos);
|
||||
|
||||
return scaledLocalBounds;
|
||||
}
|
||||
|
||||
public Box2 CalcWorldAABB(Vector2? gridPos = null, Angle? gridRot = null)
|
||||
{
|
||||
var bounds = CalcWorldBounds(gridPos, gridRot);
|
||||
return bounds.CalcBoundingBox();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool CollidesWithChunk(Vector2i localIndices)
|
||||
{
|
||||
return _tiles[localIndices.X, localIndices.Y].TypeId != Tile.Empty.TypeId;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -241,13 +355,18 @@ namespace Robust.Shared.Map
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event delegate for <see cref="MapChunk.TileModified"/>.
|
||||
/// </summary>
|
||||
/// <param name="mapChunk">Chunk that the tile was on.</param>
|
||||
/// <param name="tileIndices">hunk Indices of the tile that was modified.</param>
|
||||
/// <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 sealed class RegenerateChunkCollisionEvent : EntityEventArgs
|
||||
{
|
||||
public MapChunk Chunk { get; }
|
||||
|
||||
public RegenerateChunkCollisionEvent(MapChunk chunk)
|
||||
{
|
||||
Chunk = chunk;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ChunkRemovedEvent : EntityEventArgs
|
||||
{
|
||||
public MapChunk Chunk = default!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,9 @@ namespace Robust.Shared.Map
|
||||
[ViewVariables]
|
||||
public GameTick LastTileModifiedTick { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public GameTick CurTick => _mapManager.GameTiming.CurTick;
|
||||
|
||||
/// <inheritdoc />
|
||||
[ViewVariables]
|
||||
public MapId ParentMapId { get; set; }
|
||||
@@ -42,7 +45,7 @@ namespace Robust.Shared.Map
|
||||
/// <summary>
|
||||
/// Grid chunks than make up this grid.
|
||||
/// </summary>
|
||||
private readonly Dictionary<Vector2i, MapChunk> _chunks = new();
|
||||
private readonly Dictionary<Vector2i, IMapChunkInternal> _chunks = new();
|
||||
|
||||
private readonly IMapManagerInternal _mapManager;
|
||||
private readonly IEntityManager _entityManager;
|
||||
@@ -56,6 +59,7 @@ namespace Robust.Shared.Map
|
||||
/// <param name="entityManager"></param>
|
||||
/// <param name="gridIndex">Index identifier of this grid.</param>
|
||||
/// <param name="chunkSize">The dimension of this square chunk.</param>
|
||||
/// <param name="snapSize">Distance in world units between the lines on the conceptual snap grid.</param>
|
||||
/// <param name="parentMapId">Parent map identifier.</param>
|
||||
internal MapGrid(IMapManagerInternal mapManager, IEntityManager entityManager, GridId gridIndex, ushort chunkSize, MapId parentMapId)
|
||||
{
|
||||
@@ -78,6 +82,8 @@ namespace Robust.Shared.Map
|
||||
[ViewVariables]
|
||||
public Box2 LocalBounds { get; private set; }
|
||||
|
||||
public bool SuppressCollisionRegeneration { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[ViewVariables]
|
||||
public ushort ChunkSize { get; }
|
||||
@@ -162,6 +168,40 @@ namespace Robust.Shared.Map
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Expands the AABB for this grid when a new tile is added. If the tile is already inside the existing AABB,
|
||||
/// nothing happens. If it is outside, the AABB is expanded to fit the new tile.
|
||||
/// </summary>
|
||||
public void UpdateAABB()
|
||||
{
|
||||
LocalBounds = new Box2();
|
||||
foreach (var chunk in _chunks.Values)
|
||||
{
|
||||
var chunkBounds = chunk.CalcLocalBounds();
|
||||
|
||||
if(chunkBounds.Size.Equals(Vector2i.Zero))
|
||||
continue;
|
||||
|
||||
if (LocalBounds.Size == Vector2.Zero)
|
||||
{
|
||||
var gridBounds = chunkBounds.Translated(chunk.Indices * chunk.ChunkSize);
|
||||
LocalBounds = gridBounds;
|
||||
}
|
||||
else
|
||||
{
|
||||
var gridBounds = chunkBounds.Translated(chunk.Indices * chunk.ChunkSize);
|
||||
LocalBounds = LocalBounds.Union(gridBounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void NotifyTileChanged(in TileRef tileRef, in Tile oldTile)
|
||||
{
|
||||
LastTileModifiedTick = _mapManager.GameTiming.CurTick;
|
||||
_mapManager.RaiseOnTileChanged(tileRef, oldTile);
|
||||
}
|
||||
|
||||
#region TileAccess
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -188,26 +228,7 @@ namespace Robust.Shared.Map
|
||||
}
|
||||
|
||||
var chunkTileIndices = output.GridTileToChunkTile(tileCoordinates);
|
||||
return GetTileRef(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>
|
||||
public 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(ParentMapId, Index, indices, mapChunk.GetTile(xIndex, yIndex));
|
||||
return output.GetTileRef((ushort)chunkTileIndices.X, (ushort)chunkTileIndices.Y);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -215,19 +236,10 @@ namespace Robust.Shared.Map
|
||||
{
|
||||
foreach (var kvChunk in _chunks)
|
||||
{
|
||||
var chunk = kvChunk.Value;
|
||||
for (ushort x = 0; x < ChunkSize; x++)
|
||||
foreach (var tileRef in kvChunk.Value)
|
||||
{
|
||||
for (ushort y = 0; y < ChunkSize; y++)
|
||||
{
|
||||
var tile = chunk.GetTile(x, y);
|
||||
|
||||
if (ignoreSpace && tile.IsEmpty)
|
||||
continue;
|
||||
|
||||
var (gridX, gridY) = new Vector2i(x, y) + chunk.Indices * ChunkSize;
|
||||
yield return new TileRef(ParentMapId, Index, gridX, gridY, tile);
|
||||
}
|
||||
if (!ignoreSpace || !tileRef.Tile.IsEmpty)
|
||||
yield return tileRef;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -249,7 +261,7 @@ namespace Robust.Shared.Map
|
||||
/// <inheritdoc />
|
||||
public void SetTiles(List<(Vector2i GridIndices, Tile Tile)> tiles)
|
||||
{
|
||||
var chunks = new HashSet<MapChunk>();
|
||||
var chunks = new HashSet<IMapChunkInternal>();
|
||||
|
||||
foreach (var (gridIndices, tile) in tiles)
|
||||
{
|
||||
@@ -262,11 +274,10 @@ namespace Robust.Shared.Map
|
||||
foreach (var chunk in chunks)
|
||||
{
|
||||
chunk.SuppressCollisionRegeneration = false;
|
||||
RegenerateCollision(chunk);
|
||||
chunk.RegenerateCollision();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<TileRef> GetTilesIntersecting(Box2Rotated worldArea, bool ignoreEmpty = true,
|
||||
Predicate<TileRef>? predicate = null)
|
||||
{
|
||||
@@ -305,7 +316,7 @@ namespace Robust.Shared.Map
|
||||
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);
|
||||
var tile = chunk.GetTileRef((ushort)chunkTile.X, (ushort)chunkTile.Y);
|
||||
|
||||
if (ignoreEmpty && tile.Tile.IsEmpty)
|
||||
continue;
|
||||
@@ -358,19 +369,18 @@ namespace Robust.Shared.Map
|
||||
public int ChunkCount => _chunks.Count;
|
||||
|
||||
/// <inheritdoc />
|
||||
public MapChunk GetChunk(int xIndex, int yIndex)
|
||||
public IMapChunkInternal GetChunk(int xIndex, int yIndex)
|
||||
{
|
||||
return GetChunk(new Vector2i(xIndex, yIndex));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RemoveChunk(Vector2i origin)
|
||||
{
|
||||
if (!_chunks.TryGetValue(origin, out var chunk)) return;
|
||||
|
||||
_chunks.Remove(origin);
|
||||
|
||||
_mapManager.ChunkRemoved(Index, chunk);
|
||||
_mapManager.ChunkRemoved((MapChunk) chunk);
|
||||
|
||||
if (_chunks.Count == 0)
|
||||
{
|
||||
@@ -379,15 +389,12 @@ namespace Robust.Shared.Map
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MapChunk GetChunk(Vector2i chunkIndices)
|
||||
public IMapChunkInternal GetChunk(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;
|
||||
newChunk.TileModified += OnTileModified;
|
||||
return _chunks[chunkIndices] = newChunk;
|
||||
return _chunks[chunkIndices] = new MapChunk(this, chunkIndices.X, chunkIndices.Y, ChunkSize);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -397,21 +404,21 @@ namespace Robust.Shared.Map
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyDictionary<Vector2i, MapChunk> GetMapChunks()
|
||||
public IReadOnlyDictionary<Vector2i, IMapChunkInternal> GetMapChunks()
|
||||
{
|
||||
return _chunks;
|
||||
}
|
||||
|
||||
internal struct ChunkEnumerator
|
||||
{
|
||||
private Dictionary<Vector2i, MapChunk> _chunks;
|
||||
private Dictionary<Vector2i, IMapChunkInternal> _chunks;
|
||||
private Vector2i _chunkLB;
|
||||
private Vector2i _chunkRT;
|
||||
|
||||
private int _xIndex;
|
||||
private int _yIndex;
|
||||
|
||||
internal ChunkEnumerator(Dictionary<Vector2i, MapChunk> chunks, Box2 localAABB, int chunkSize)
|
||||
internal ChunkEnumerator(Dictionary<Vector2i, IMapChunkInternal> chunks, Box2 localAABB, int chunkSize)
|
||||
{
|
||||
_chunks = chunks;
|
||||
|
||||
@@ -422,7 +429,7 @@ namespace Robust.Shared.Map
|
||||
_yIndex = _chunkLB.Y;
|
||||
}
|
||||
|
||||
public bool MoveNext([NotNullWhen(true)] out MapChunk? chunk)
|
||||
public bool MoveNext([NotNullWhen(true)] out IMapChunkInternal? chunk)
|
||||
{
|
||||
if (_yIndex > _chunkRT.Y)
|
||||
{
|
||||
@@ -449,14 +456,12 @@ namespace Robust.Shared.Map
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void GetMapChunks(Box2 worldAABB, out ChunkEnumerator enumerator)
|
||||
{
|
||||
var localArea = InvWorldMatrix.TransformBox(worldAABB);
|
||||
enumerator = new ChunkEnumerator(_chunks, localArea, ChunkSize);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void GetMapChunks(Box2Rotated worldArea, out ChunkEnumerator enumerator)
|
||||
{
|
||||
var matrix = InvWorldMatrix;
|
||||
@@ -578,7 +583,7 @@ namespace Robust.Shared.Map
|
||||
RemoveFromSnapGridCell(TileIndicesFor(coords), euid);
|
||||
}
|
||||
|
||||
private (MapChunk, Vector2i) ChunkAndOffsetForTile(Vector2i pos)
|
||||
private (IMapChunkInternal, Vector2i) ChunkAndOffsetForTile(Vector2i pos)
|
||||
{
|
||||
var gridChunkIndices = GridTileToChunkIndices(pos);
|
||||
var chunk = GetChunk(gridChunkIndices);
|
||||
@@ -751,7 +756,7 @@ namespace Robust.Shared.Map
|
||||
return false;
|
||||
|
||||
var cTileIndices = chunk.GridTileToChunkTile(indices);
|
||||
return chunk.GetTile((ushort) cTileIndices.X, (ushort) cTileIndices.Y).TypeId != Tile.Empty.TypeId;
|
||||
return chunk.CollidesWithChunk(cTileIndices);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -793,7 +798,7 @@ namespace Robust.Shared.Map
|
||||
}
|
||||
|
||||
var cTileIndices = chunk.GridTileToChunkTile(indices);
|
||||
tile = GetTileRef(chunk, (ushort)cTileIndices.X, (ushort)cTileIndices.Y);
|
||||
tile = chunk.GetTileRef(cTileIndices);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -810,81 +815,6 @@ namespace Robust.Shared.Map
|
||||
}
|
||||
|
||||
#endregion Transforms
|
||||
|
||||
/// <summary>
|
||||
/// Regenerates the chunk local bounds of this chunk.
|
||||
/// </summary>
|
||||
public void RegenerateCollision(MapChunk mapChunk)
|
||||
{
|
||||
// Even if the chunk is still removed still need to make sure bounds are updated (for now...)
|
||||
if (mapChunk.FilledTiles == 0)
|
||||
{
|
||||
RemoveChunk(mapChunk.Indices);
|
||||
}
|
||||
|
||||
// generate collision rectangles for this chunk based on filled tiles.
|
||||
GridChunkPartition.PartitionChunk(mapChunk, out var localBounds, out var rectangles);
|
||||
mapChunk.CachedBounds = localBounds;
|
||||
|
||||
LocalBounds = new Box2();
|
||||
foreach (var chunk in _chunks.Values)
|
||||
{
|
||||
var chunkBounds = chunk.CachedBounds;
|
||||
|
||||
if(chunkBounds.Size.Equals(Vector2i.Zero))
|
||||
continue;
|
||||
|
||||
if (LocalBounds.Size == Vector2.Zero)
|
||||
{
|
||||
var gridBounds = chunkBounds.Translated(chunk.Indices * chunk.ChunkSize);
|
||||
LocalBounds = gridBounds;
|
||||
}
|
||||
else
|
||||
{
|
||||
var gridBounds = chunkBounds.Translated(chunk.Indices * chunk.ChunkSize);
|
||||
LocalBounds = LocalBounds.Union(gridBounds);
|
||||
}
|
||||
}
|
||||
|
||||
// TryGet because unit tests YAY
|
||||
if (mapChunk.FilledTiles > 0 && _entityManager.EntitySysManager.TryGetEntitySystem(out SharedGridFixtureSystem? system))
|
||||
system.RegenerateCollision(GridEntityId, mapChunk, rectangles);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the world space AABB for this chunk.
|
||||
/// </summary>
|
||||
public Box2 CalcWorldAABB(MapChunk mapChunk)
|
||||
{
|
||||
var rotation = WorldRotation;
|
||||
var position = WorldPosition;
|
||||
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();
|
||||
}
|
||||
|
||||
private 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);
|
||||
var newTileRef = new TileRef(ParentMapId, Index, gridTile, newTile);
|
||||
mapChunk.LastTileModifiedTick = _mapManager.GameTiming.CurTick;
|
||||
LastTileModifiedTick = _mapManager.GameTiming.CurTick;
|
||||
_mapManager.RaiseOnTileChanged(newTileRef, oldTile);
|
||||
|
||||
if (shapeChanged && !mapChunk.SuppressCollisionRegeneration)
|
||||
{
|
||||
RegenerateCollision(mapChunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user