diff --git a/Content.Server/_EE/FootPrint/FootPrintsSystem.cs b/Content.Server/_EE/FootPrint/FootPrintsSystem.cs index b30a721b93b..f751698ac08 100644 --- a/Content.Server/_EE/FootPrint/FootPrintsSystem.cs +++ b/Content.Server/_EE/FootPrint/FootPrintsSystem.cs @@ -1,4 +1,6 @@ -using Content.Server.Atmos.Components; +using System.Linq; +using System.Numerics; // DeltaV +using Content.Server.Atmos.Components; using Content.Shared._EE.FootPrint; using Content.Shared.Inventory; using Content.Shared.Mobs; @@ -7,7 +9,9 @@ // using Content.Shared.Standing; using Content.Shared.Chemistry.Components.SolutionManager; using Content.Shared.Chemistry.EntitySystems; +using Content.Shared.GameTicking; // DeltaV using Robust.Shared.Map; +using Robust.Shared.Map.Components; // DeltaV using Robust.Shared.Random; namespace Content.Server._EE.FootPrint; @@ -21,6 +25,16 @@ public sealed class FootPrintsSystem : EntitySystem [Dependency] private readonly SharedSolutionContainerSystem _solution = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly SharedMapSystem _mapSystem = default!; // DeltaV + + // DeltaV - Max amount of footprints per tile + // Not stored on the component because its convenient here + private const int MaxFootprintsPerTile = 2; + + /// + /// DeltaV: Dictionary tracking footprints per tile using tile coordinates as key + /// + private readonly Dictionary> _footprintsPerTile = new(); private EntityQuery _transformQuery; private EntityQuery _mobThresholdQuery; @@ -38,6 +52,10 @@ public override void Initialize() SubscribeLocalEvent(OnStartupComponent); SubscribeLocalEvent(OnMove); + SubscribeLocalEvent(OnFootPrintInit); // DeltaV + SubscribeLocalEvent(OnFootPrintRemove); // DeltaV + SubscribeLocalEvent(OnGridTerminating); // DeltaV + SubscribeLocalEvent(OnRoundEnd); // DeltaV } private void OnStartupComponent(EntityUid uid, FootPrintsComponent component, ComponentStartup args) @@ -62,7 +80,12 @@ private void OnMove(EntityUid uid, FootPrintsComponent component, ref MoveEvent component.RightStep = !component.RightStep; - var entity = Spawn(component.StepProtoId, CalcCoords(gridUid, component, transform, dragging)); + // DeltaV - Check if we've hit the footprint limit for this tile before spawning + var coords = CalcCoords(gridUid, component, transform, dragging); + if (!ShouldCreateNewFootprint(coords)) + return; + + var entity = Spawn(component.StepProtoId, coords); var footPrintComponent = EnsureComp(entity); footPrintComponent.PrintOwner = uid; @@ -92,6 +115,119 @@ private void OnMove(EntityUid uid, FootPrintsComponent component, ref MoveEvent _solution.TryAddReagent(footPrintComponent.Solution.Value, component.ReagentToTransfer, 1, out _); } + /// + /// DeltaV: Checks if a new footprint can be created at the specified coordinates based on the per-tile limit. + /// + /// True if a new footprint can be created, false if the tile is full or invalid + private bool ShouldCreateNewFootprint(EntityCoordinates coords) + { + if (!coords.IsValid(EntityManager)) + return false; + + var mapCoords = _transform.ToMapCoordinates(coords); + + if (!_map.TryFindGridAt(mapCoords, out var gridUid, out var grid)) + return false; + + var tilePos = _mapSystem.CoordinatesToTile(gridUid, grid, coords); + return !_footprintsPerTile.TryGetValue(tilePos, out var footprints) || footprints.Count < MaxFootprintsPerTile; + } + + /// + /// DeltaV: Handles the initialization of a footprint component. + /// + private void OnFootPrintInit(Entity ent, ref ComponentInit args) + { + if (!TryGetTilePos(ent.Owner, out var tilePos)) + return; + + if (!_footprintsPerTile.TryGetValue(tilePos, out var footprints)) + { + footprints = new Queue(); + _footprintsPerTile[tilePos] = footprints; + } + + footprints.Enqueue(ent); + + // If we've exceeded the limit, remove the oldest footprint + if (footprints.Count > MaxFootprintsPerTile) + { + var oldestFootprint = footprints.Dequeue(); + if (Exists(oldestFootprint)) + QueueDel(oldestFootprint); + } + } + + /// + /// DeltaV: Handles cleanup when a footprint component is removed. + /// + private void OnFootPrintRemove(Entity ent, ref ComponentRemove args) + { + if (!TryGetTilePos(ent.Owner, out var tilePos)) + return; + + if (_footprintsPerTile.TryGetValue(tilePos, out var footprints)) + { + // Create a new queue without the removed footprint + var newQueue = new Queue(footprints.Where(x => x != ent.Owner)); + if (newQueue.Count > 0) + _footprintsPerTile[tilePos] = newQueue; + else + _footprintsPerTile.Remove(tilePos); + } + } + + /// + /// DeltaV: Handles cleanup when a grid is being terminated, removing all footprint tracking data from that grid. + /// + private void OnGridTerminating(Entity ent, ref EntityTerminatingEvent args) + { + // Find and remove all footprints that belong to this grid's tiles + var toRemove = new List(); + + foreach (var (pos, footprints) in _footprintsPerTile) + { + // Convert position to map coordinates to check if it belongs to this grid + var mapCoords = _transform.ToMapCoordinates(new EntityCoordinates(ent, + new Vector2(pos.X * ent.Comp.TileSize, pos.Y * ent.Comp.TileSize))); + + if (_map.TryFindGridAt(mapCoords, out var gridUid, out _) && gridUid == ent.Owner) + { + toRemove.Add(pos); + } + } + + foreach (var pos in toRemove) + { + _footprintsPerTile.Remove(pos); + } + } + + /// + /// DeltaV: Attempts to get the tile position for a given entity. + /// + private bool TryGetTilePos(EntityUid uid, out Vector2i tilePos) + { + tilePos = default; + + var coords = new EntityCoordinates(Transform(uid).ParentUid, Transform(uid).LocalPosition); + var mapCoords = _transform.ToMapCoordinates(coords); + + if (!_map.TryFindGridAt(mapCoords, out var gridUid, out var grid)) + return false; + + tilePos = _mapSystem.CoordinatesToTile(gridUid, grid, coords); + return true; + } + + /// + /// DeltaV: Clean up the dict on round end + /// + private void OnRoundEnd(RoundRestartCleanupEvent ev) + { + _footprintsPerTile.Clear(); + } + private EntityCoordinates CalcCoords(EntityUid uid, FootPrintsComponent component, TransformComponent transform, bool state) { if (state)