| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351 |
- using Content.Server.Atmos.Components;
- using Content.Server.Atmos.EntitySystems;
- using Content.Server.Shuttles.Components;
- using Content.Shared.Atmos;
- using Content.Shared.Maps;
- using Content.Shared.Spreader;
- using Content.Shared.Tag;
- using Robust.Shared.Collections;
- using Robust.Shared.Map;
- using Robust.Shared.Map.Components;
- using Robust.Shared.Prototypes;
- using Robust.Shared.Random;
- using Robust.Shared.Utility;
- namespace Content.Server.Spreader;
- /// <summary>
- /// Handles generic spreading logic, where one anchored entity spreads to neighboring tiles.
- /// </summary>
- public sealed class SpreaderSystem : EntitySystem
- {
- [Dependency] private readonly IPrototypeManager _prototype = default!;
- [Dependency] private readonly IRobustRandom _robustRandom = default!;
- [Dependency] private readonly SharedMapSystem _map = default!;
- [Dependency] private readonly TagSystem _tag = default!;
- /// <summary>
- /// Cached maximum number of updates per spreader prototype. This is applied per-grid.
- /// </summary>
- private Dictionary<string, int> _prototypeUpdates = default!;
- /// <summary>
- /// Remaining number of updates per grid & prototype.
- /// </summary>
- // TODO PERFORMANCE Assign each prototype to an index and convert dictionary to array
- private readonly Dictionary<EntityUid, Dictionary<string, int>> _gridUpdates = [];
- private EntityQuery<EdgeSpreaderComponent> _query;
- public const float SpreadCooldownSeconds = 1;
- private static readonly ProtoId<TagPrototype> IgnoredTag = "SpreaderIgnore";
- /// <inheritdoc/>
- public override void Initialize()
- {
- SubscribeLocalEvent<AirtightChanged>(OnAirtightChanged);
- SubscribeLocalEvent<GridInitializeEvent>(OnGridInit);
- SubscribeLocalEvent<PrototypesReloadedEventArgs>(OnPrototypeReload);
- SubscribeLocalEvent<EdgeSpreaderComponent, EntityTerminatingEvent>(OnTerminating);
- SetupPrototypes();
- _query = GetEntityQuery<EdgeSpreaderComponent>();
- }
- private void OnPrototypeReload(PrototypesReloadedEventArgs obj)
- {
- if (obj.WasModified<EdgeSpreaderPrototype>())
- SetupPrototypes();
- }
- private void SetupPrototypes()
- {
- _prototypeUpdates = [];
- foreach (var proto in _prototype.EnumeratePrototypes<EdgeSpreaderPrototype>())
- {
- _prototypeUpdates.Add(proto.ID, proto.UpdatesPerSecond);
- }
- }
- private void OnAirtightChanged(ref AirtightChanged ev)
- {
- ActivateSpreadableNeighbors(ev.Entity, ev.Position);
- }
- private void OnGridInit(GridInitializeEvent ev)
- {
- EnsureComp<SpreaderGridComponent>(ev.EntityUid);
- }
- private void OnTerminating(Entity<EdgeSpreaderComponent> entity, ref EntityTerminatingEvent args)
- {
- ActivateSpreadableNeighbors(entity);
- }
- /// <inheritdoc/>
- public override void Update(float frameTime)
- {
- // Check which grids are valid for spreading
- var spreadGrids = EntityQueryEnumerator<SpreaderGridComponent>();
- _gridUpdates.Clear();
- while (spreadGrids.MoveNext(out var uid, out var grid))
- {
- grid.UpdateAccumulator -= frameTime;
- if (grid.UpdateAccumulator > 0)
- continue;
- _gridUpdates[uid] = _prototypeUpdates.ShallowClone();
- grid.UpdateAccumulator += SpreadCooldownSeconds;
- }
- if (_gridUpdates.Count == 0)
- return;
- var query = EntityQueryEnumerator<ActiveEdgeSpreaderComponent>();
- var xforms = GetEntityQuery<TransformComponent>();
- var spreaderQuery = GetEntityQuery<EdgeSpreaderComponent>();
- var spreaders = new List<(EntityUid Uid, ActiveEdgeSpreaderComponent Comp)>(Count<ActiveEdgeSpreaderComponent>());
- // Build a list of all existing Edgespreaders, shuffle them
- while (query.MoveNext(out var uid, out var comp))
- {
- spreaders.Add((uid, comp));
- }
- _robustRandom.Shuffle(spreaders);
- // Remove the EdgeSpreaderComponent from any entity
- // that doesn't meet a few trivial prerequisites
- foreach (var (uid, comp) in spreaders)
- {
- // Get xform first, as entity may have been deleted due to interactions triggered by other spreaders.
- if (!xforms.TryGetComponent(uid, out var xform))
- continue;
- if (xform.GridUid == null)
- {
- RemComp(uid, comp);
- continue;
- }
- if (!_gridUpdates.TryGetValue(xform.GridUid.Value, out var groupUpdates))
- continue;
- if (!spreaderQuery.TryGetComponent(uid, out var spreader))
- {
- RemComp(uid, comp);
- continue;
- }
- if (!groupUpdates.TryGetValue(spreader.Id, out var updates) || updates < 1)
- continue;
- // Edge detection logic is to be handled
- // by the subscribing system, see KudzuSystem
- // for a simple example
- Spread(uid, xform, spreader.Id, ref updates);
- if (updates < 1)
- groupUpdates.Remove(spreader.Id);
- else
- groupUpdates[spreader.Id] = updates;
- }
- }
- private void Spread(EntityUid uid, TransformComponent xform, ProtoId<EdgeSpreaderPrototype> prototype, ref int updates)
- {
- GetNeighbors(uid, xform, prototype, out var freeTiles, out _, out var neighbors);
- var ev = new SpreadNeighborsEvent()
- {
- NeighborFreeTiles = freeTiles,
- Neighbors = neighbors,
- Updates = updates,
- };
- RaiseLocalEvent(uid, ref ev);
- updates = ev.Updates;
- }
- /// <summary>
- /// Gets the neighboring node data for the specified entity and the specified node group.
- /// </summary>
- public void GetNeighbors(EntityUid uid, TransformComponent comp, ProtoId<EdgeSpreaderPrototype> prototype, out ValueList<(MapGridComponent, TileRef)> freeTiles, out ValueList<Vector2i> occupiedTiles, out ValueList<EntityUid> neighbors)
- {
- freeTiles = [];
- occupiedTiles = [];
- neighbors = [];
- // TODO remove occupiedTiles -- its currently unused and just slows this method down.
- if (!_prototype.TryIndex(prototype, out var spreaderPrototype))
- return;
- if (!TryComp<MapGridComponent>(comp.GridUid, out var grid))
- return;
- var tile = _map.TileIndicesFor(comp.GridUid.Value, grid, comp.Coordinates);
- var spreaderQuery = GetEntityQuery<EdgeSpreaderComponent>();
- var airtightQuery = GetEntityQuery<AirtightComponent>();
- var dockQuery = GetEntityQuery<DockingComponent>();
- var xformQuery = GetEntityQuery<TransformComponent>();
- var blockedAtmosDirs = AtmosDirection.Invalid;
- // Due to docking ports they may not necessarily be opposite directions.
- var neighborTiles = new ValueList<(EntityUid entity, MapGridComponent grid, Vector2i Indices, AtmosDirection OtherDir, AtmosDirection OurDir)>();
- // Check if anything on our own tile blocking that direction.
- var ourEnts = _map.GetAnchoredEntitiesEnumerator(comp.GridUid.Value, grid, tile);
- while (ourEnts.MoveNext(out var ent))
- {
- // Spread via docks in a special-case.
- if (dockQuery.TryGetComponent(ent, out var dock) &&
- dock.Docked &&
- xformQuery.TryGetComponent(ent, out var xform) &&
- xformQuery.TryGetComponent(dock.DockedWith, out var dockedXform) &&
- TryComp<MapGridComponent>(dockedXform.GridUid, out var dockedGrid))
- {
- neighborTiles.Add((dockedXform.GridUid.Value, dockedGrid, _map.CoordinatesToTile(dockedXform.GridUid.Value, dockedGrid, dockedXform.Coordinates), xform.LocalRotation.ToAtmosDirection(), dockedXform.LocalRotation.ToAtmosDirection()));
- }
- // If we're on a blocked tile work out which directions we can go.
- if (!airtightQuery.TryGetComponent(ent, out var airtight) || !airtight.AirBlocked ||
- _tag.HasTag(ent.Value, IgnoredTag))
- {
- continue;
- }
- foreach (var value in new[] { AtmosDirection.North, AtmosDirection.East, AtmosDirection.South, AtmosDirection.West })
- {
- if ((value & airtight.AirBlockedDirection) == 0x0)
- continue;
- blockedAtmosDirs |= value;
- break;
- }
- break;
- }
- // Add the normal neighbors.
- for (var i = 0; i < 4; i++)
- {
- var atmosDir = (AtmosDirection) (1 << i);
- var neighborPos = tile.Offset(atmosDir);
- neighborTiles.Add((comp.GridUid.Value, grid, neighborPos, atmosDir, i.ToOppositeDir()));
- }
- foreach (var (neighborEnt, neighborGrid, neighborPos, ourAtmosDir, otherAtmosDir) in neighborTiles)
- {
- // This tile is blocked to that direction.
- if ((blockedAtmosDirs & ourAtmosDir) != 0x0)
- continue;
- if (!_map.TryGetTileRef(neighborEnt, neighborGrid, neighborPos, out var tileRef) || tileRef.Tile.IsEmpty)
- continue;
- if (spreaderPrototype.PreventSpreadOnSpaced && tileRef.Tile.IsSpace())
- continue;
- var directionEnumerator = _map.GetAnchoredEntitiesEnumerator(neighborEnt, neighborGrid, neighborPos);
- var occupied = false;
- while (directionEnumerator.MoveNext(out var ent))
- {
- if (!airtightQuery.TryGetComponent(ent, out var airtight) || !airtight.AirBlocked || _tag.HasTag(ent.Value, IgnoredTag))
- {
- continue;
- }
- if ((airtight.AirBlockedDirection & otherAtmosDir) == 0x0)
- continue;
- occupied = true;
- break;
- }
- if (occupied)
- continue;
- var oldCount = occupiedTiles.Count;
- directionEnumerator = _map.GetAnchoredEntitiesEnumerator(neighborEnt, neighborGrid, neighborPos);
- while (directionEnumerator.MoveNext(out var ent))
- {
- if (!spreaderQuery.TryGetComponent(ent, out var spreader))
- continue;
- if (spreader.Id != prototype)
- continue;
- neighbors.Add(ent.Value);
- occupiedTiles.Add(neighborPos);
- break;
- }
- if (oldCount == occupiedTiles.Count)
- freeTiles.Add((neighborGrid, tileRef));
- }
- }
- /// <summary>
- /// This function activates all spreaders that are adjacent to a given entity. This also activates other spreaders
- /// on the same tile as the current entity (for thin airtight entities like windoors).
- /// </summary>
- public void ActivateSpreadableNeighbors(EntityUid uid, (EntityUid Grid, Vector2i Tile)? position = null)
- {
- Vector2i tile;
- EntityUid ent;
- MapGridComponent? grid;
- if (position == null)
- {
- var transform = Transform(uid);
- if (!TryComp(transform.GridUid, out grid) || TerminatingOrDeleted(transform.GridUid.Value))
- return;
- tile = _map.TileIndicesFor(transform.GridUid.Value, grid, transform.Coordinates);
- ent = transform.GridUid.Value;
- }
- else
- {
- if (!TryComp(position.Value.Grid, out grid))
- return;
- (ent, tile) = position.Value;
- }
- var anchored = _map.GetAnchoredEntitiesEnumerator(ent, grid, tile);
- while (anchored.MoveNext(out var entity))
- {
- if (entity == ent)
- continue;
- DebugTools.Assert(Transform(entity.Value).Anchored);
- if (_query.HasComponent(ent) && !TerminatingOrDeleted(entity.Value))
- EnsureComp<ActiveEdgeSpreaderComponent>(entity.Value);
- }
- for (var i = 0; i < Atmospherics.Directions; i++)
- {
- var direction = (AtmosDirection) (1 << i);
- var adjacentTile = SharedMapSystem.GetDirection(tile, direction.ToDirection());
- anchored = _map.GetAnchoredEntitiesEnumerator(ent, grid, adjacentTile);
- while (anchored.MoveNext(out var entity))
- {
- DebugTools.Assert(Transform(entity.Value).Anchored);
- if (_query.HasComponent(ent) && !TerminatingOrDeleted(entity.Value))
- EnsureComp<ActiveEdgeSpreaderComponent>(entity.Value);
- }
- }
- }
- public bool RequiresFloorToSpread(EntProtoId<EdgeSpreaderComponent> spreader)
- {
- if (!_prototype.Index(spreader).TryGetComponent<EdgeSpreaderComponent>(out var spreaderComp, EntityManager.ComponentFactory))
- return false;
- return _prototype.Index(spreaderComp.Id).PreventSpreadOnSpaced;
- }
- }
|