using System.Diagnostics.CodeAnalysis; using System.Linq; using Content.Server.Fluids.Components; using Content.Shared; using Content.Shared.Directions; using Content.Shared.Maps; using Content.Shared.Physics; using JetBrains.Annotations; using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Physics; using Robust.Shared.Physics.Systems; 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!; [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly SharedPhysicsSystem _physics = 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(puddleUid, puddle); exploreDirections.Shuffle(); foreach (var direction in exploreDirections) { var newPos = pos.Offset(direction); if (CheckTile(puddleUid, puddle, newPos, mapGrid, puddleQuery, out var uid, out var component)) { puddles.Add(component); totalVolume += _puddleSystem.CurrentVolume(uid.Value, component); } } _puddleSystem.EqualizePuddles(puddleUid, 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 dstPos, MapGridComponent mapGrid, EntityQuery puddleQuery, [NotNullWhen(true)] out EntityUid? newPuddleUid, [NotNullWhen(true)] out PuddleComponent? newPuddleComp) { if (!mapGrid.TryGetTileRef(dstPos, out var tileRef) || tileRef.Tile.IsEmpty) { newPuddleUid = null; newPuddleComp = null; return false; } // check if puddle can spread there at all var dstMap = dstPos.ToMap(EntityManager, _transform); var dst = dstMap.Position; var src = Transform(srcUid).MapPosition.Position; var dir = src - dst; var ray = new CollisionRay(dst, dir.Normalized, (int) (CollisionGroup.Impassable | CollisionGroup.HighImpassable)); var mapId = dstMap.MapId; var results = _physics.IntersectRay(mapId, ray, dir.Length, returnOnFirstHit: true); if (results.Any()) { newPuddleUid = null; newPuddleComp = null; return false; } var puddleCurrentVolume = _puddleSystem.CurrentVolume(srcUid, srcPuddle); foreach (var entity in dstPos.GetEntitiesInTile()) { if (puddleQuery.TryGetComponent(entity, out var existingPuddle)) { if (_puddleSystem.CurrentVolume(entity, existingPuddle) >= puddleCurrentVolume) { newPuddleUid = null; newPuddleComp = null; return false; } newPuddleUid = entity; newPuddleComp = existingPuddle; return true; } } _puddleSystem.SpawnPuddle(srcUid, dstPos, srcPuddle, out var uid, out var comp); newPuddleUid = uid; newPuddleComp = comp; return true; } }