using System.Diagnostics.CodeAnalysis; using System.Linq; using Content.Server.Fluids.Components; using Content.Shared; using Content.Shared.Directions; using Content.Shared.Physics; using JetBrains.Annotations; using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Physics; using Robust.Shared.Utility; using Robust.Shared.Physics.Components; using Robust.Shared.Timing; namespace Content.Server.Fluids.EntitySystems; /// /// Component that governs overflowing puddles. Controls how Puddles spread and updat /// [UsedImplicitly] public sealed class FluidSpreaderSystem : EntitySystem { [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly PuddleSystem _puddleSystem = default!; /// /// Adds an overflow component to the map data component tracking overflowing puddles /// /// EntityUid of overflowing puddle /// Optional PuddleComponent /// Optional TransformComponent public void AddOverflowingPuddle(EntityUid puddleUid, PuddleComponent? puddle = null, TransformComponent? xform = null) { if (!Resolve(puddleUid, ref puddle, ref xform, false) || xform.MapUid == null) return; var mapId = xform.MapUid.Value; EntityManager.EnsureComponent(mapId, out var component); component.Puddles.Add(puddleUid); } public override void Update(float frameTime) { base.Update(frameTime); Span exploreDirections = stackalloc Direction[] { Direction.North, Direction.East, Direction.South, Direction.West, }; var puddles = new List(4); var puddleQuery = GetEntityQuery(); var xFormQuery = GetEntityQuery(); foreach (var fluidMapData in EntityQuery()) { if (fluidMapData.Puddles.Count == 0 || _gameTiming.CurTime <= fluidMapData.GoalTime) continue; var newIteration = new HashSet(); foreach (var puddleUid in fluidMapData.Puddles) { if (!puddleQuery.TryGetComponent(puddleUid, out var puddle) || !xFormQuery.TryGetComponent(puddleUid, out var transform) || !_mapManager.TryGetGrid(transform.GridUid, out var mapGrid)) continue; puddles.Clear(); var pos = transform.Coordinates; var totalVolume = _puddleSystem.CurrentVolume(puddle.Owner, puddle); exploreDirections.Shuffle(); foreach (var direction in exploreDirections) { var newPos = pos.Offset(direction); if (CheckTile(puddle.Owner, puddle, newPos, mapGrid, out var puddleComponent)) { puddles.Add(puddleComponent); totalVolume += _puddleSystem.CurrentVolume(puddleComponent.Owner, puddleComponent); } } _puddleSystem.EqualizePuddles(puddle.Owner, puddles, totalVolume, newIteration, puddle); } fluidMapData.Puddles.Clear(); fluidMapData.Puddles.UnionWith(newIteration); fluidMapData.UpdateGoal(_gameTiming.CurTime); } } /// /// Check a tile is valid for solution allocation. /// /// Entity Uid of original puddle /// PuddleComponent attached to srcUid /// at which to check tile /// helper param needed to extract entities /// either found or newly created PuddleComponent. /// true if tile is empty or occupied by a non-overflowing puddle (or a puddle close to being overflowing) private bool CheckTile(EntityUid srcUid, PuddleComponent srcPuddle, EntityCoordinates pos, MapGridComponent mapGrid, [NotNullWhen(true)] out PuddleComponent? puddle) { if (!mapGrid.TryGetTileRef(pos, out var tileRef) || tileRef.Tile.IsEmpty) { puddle = null; return false; } var puddleCurrentVolume = _puddleSystem.CurrentVolume(srcUid, srcPuddle); foreach (var entity in mapGrid.GetAnchoredEntities(pos)) { // If this is valid puddle check if we spread to it. if (TryComp(entity, out PuddleComponent? existingPuddle)) { // If current puddle has more volume than current we skip that field if (_puddleSystem.CurrentVolume(existingPuddle.Owner, existingPuddle) >= puddleCurrentVolume) { puddle = null; return false; } puddle = existingPuddle; return true; } // if not puddle is this tile blocked by an object like wall or door if (TryComp(entity, out PhysicsComponent? physComponent) && physComponent.CanCollide && (physComponent.CollisionLayer & (int) CollisionGroup.MobMask) != 0) { puddle = null; return false; } } puddle = _puddleSystem.SpawnPuddle(srcUid, pos, srcPuddle); return true; } }