diff --git a/Content.Server/Atmos/EntitySystems/AirtightSystem.cs b/Content.Server/Atmos/EntitySystems/AirtightSystem.cs index 9a97c88035..f899ffb2e8 100644 --- a/Content.Server/Atmos/EntitySystems/AirtightSystem.cs +++ b/Content.Server/Atmos/EntitySystems/AirtightSystem.cs @@ -21,7 +21,7 @@ namespace Content.Server.Atmos.EntitySystems SubscribeLocalEvent(OnAirtightShutdown); SubscribeLocalEvent(OnAirtightPositionChanged); SubscribeLocalEvent(OnAirtightReAnchor); - SubscribeLocalEvent(OnAirtightRotated); + SubscribeLocalEvent(OnAirtightMoved); } private void OnAirtightInit(EntityUid uid, AirtightComponent airtight, ComponentInit args) @@ -31,7 +31,7 @@ namespace Content.Server.Atmos.EntitySystems if (airtight.FixAirBlockedDirectionInitialize) { var moveEvent = new MoveEvent(uid, default, default, Angle.Zero, xform.LocalRotation, xform, false); - if (AirtightRotate(uid, airtight, ref moveEvent)) + if (AirtightMove(uid, airtight, ref moveEvent)) return; } @@ -78,19 +78,20 @@ namespace Content.Server.Atmos.EntitySystems } } - private void OnAirtightRotated(EntityUid uid, AirtightComponent airtight, ref MoveEvent ev) + private void OnAirtightMoved(EntityUid uid, AirtightComponent airtight, ref MoveEvent ev) { - AirtightRotate(uid, airtight, ref ev); + AirtightMove(uid, airtight, ref ev); } - private bool AirtightRotate(EntityUid uid, AirtightComponent airtight, ref MoveEvent ev) + private bool AirtightMove(EntityUid uid, AirtightComponent airtight, ref MoveEvent ev) { if (!airtight.RotateAirBlocked || airtight.InitialAirBlockedDirection == (int)AtmosDirection.Invalid) return false; airtight.CurrentAirBlockedDirection = (int) Rotate((AtmosDirection)airtight.InitialAirBlockedDirection, ev.NewRotation); + var pos = airtight.LastPosition; UpdatePosition(airtight, ev.Component); - var airtightEv = new AirtightChanged(uid, airtight); + var airtightEv = new AirtightChanged(uid, airtight, pos); RaiseLocalEvent(uid, ref airtightEv, true); return true; } @@ -100,11 +101,13 @@ namespace Content.Server.Atmos.EntitySystems if (airtight.AirBlocked == airblocked) return; - if (!Resolve(airtight.Owner, ref xform)) return; + if (!Resolve(uid, ref xform)) + return; + var pos = airtight.LastPosition; airtight.AirBlocked = airblocked; UpdatePosition(airtight, xform); - var airtightEv = new AirtightChanged(uid, airtight); + var airtightEv = new AirtightChanged(uid, airtight, pos); RaiseLocalEvent(uid, ref airtightEv, true); } @@ -154,5 +157,6 @@ namespace Content.Server.Atmos.EntitySystems } [ByRefEvent] - public readonly record struct AirtightChanged(EntityUid Entity, AirtightComponent Airtight); + public readonly record struct AirtightChanged(EntityUid Entity, AirtightComponent Airtight, + (EntityUid Grid, Vector2i Tile) Position); } diff --git a/Content.Server/Fluids/EntitySystems/PuddleSystem.cs b/Content.Server/Fluids/EntitySystems/PuddleSystem.cs index 614e685456..1fb56a7aa1 100644 --- a/Content.Server/Fluids/EntitySystems/PuddleSystem.cs +++ b/Content.Server/Fluids/EntitySystems/PuddleSystem.cs @@ -103,7 +103,7 @@ public sealed partial class PuddleSystem : SharedPuddleSystem if (overflow.Volume == FixedPoint2.Zero) { - RemCompDeferred(uid); + RemCompDeferred(uid); return; } @@ -137,7 +137,7 @@ public sealed partial class PuddleSystem : SharedPuddleSystem continue; args.Updates--; - EnsureComp(neighbor); + EnsureComp(neighbor); if (args.Updates <= 0) break; @@ -145,7 +145,7 @@ public sealed partial class PuddleSystem : SharedPuddleSystem if (overflow.Volume == FixedPoint2.Zero) { - RemCompDeferred(uid); + RemCompDeferred(uid); return; } } @@ -168,7 +168,7 @@ public sealed partial class PuddleSystem : SharedPuddleSystem break; } - RemCompDeferred(uid); + RemCompDeferred(uid); return; } @@ -192,7 +192,7 @@ public sealed partial class PuddleSystem : SharedPuddleSystem if (!_solutionContainerSystem.TryAddSolution(neighbor, neighborSolution, split)) continue; - EnsureComp(neighbor); + EnsureComp(neighbor); args.Updates--; if (args.Updates <= 0) @@ -438,7 +438,7 @@ public sealed partial class PuddleSystem : SharedPuddleSystem if (checkForOverflow && IsOverflowing(puddleUid, puddleComponent)) { - EnsureComp(puddleUid); + EnsureComp(puddleUid); } if (!sound) @@ -638,7 +638,7 @@ public sealed partial class PuddleSystem : SharedPuddleSystem if (TryAddSolution(ent.Value, solution, sound, puddleComponent: puddle)) { - EnsureComp(ent.Value); + EnsureComp(ent.Value); } puddleUid = ent.Value; @@ -650,7 +650,7 @@ public sealed partial class PuddleSystem : SharedPuddleSystem EnsureComp(puddleUid); if (TryAddSolution(puddleUid, solution, sound)) { - EnsureComp(puddleUid); + EnsureComp(puddleUid); } return true; } diff --git a/Content.Server/Fluids/EntitySystems/SmokeSystem.cs b/Content.Server/Fluids/EntitySystems/SmokeSystem.cs index a192b39e56..b3590fec40 100644 --- a/Content.Server/Fluids/EntitySystems/SmokeSystem.cs +++ b/Content.Server/Fluids/EntitySystems/SmokeSystem.cs @@ -1,23 +1,16 @@ using System.Linq; -using Content.Server.Administration.Logs; -using Content.Server.Body.Components; -using Content.Server.Body.Systems; using Content.Server.Chemistry.Components; using Content.Server.Chemistry.EntitySystems; using Content.Server.Chemistry.ReactionEffects; using Content.Server.Spreader; -using Content.Shared.Chemistry; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Reaction; -using Content.Shared.Chemistry.Reagent; -using Content.Shared.Coordinates.Helpers; -using Content.Shared.Database; using Content.Shared.FixedPoint; using Content.Shared.Smoking; -using Robust.Shared.Spawners; using Robust.Server.GameObjects; using Robust.Shared.Map; using Robust.Shared.Prototypes; +using Robust.Shared.Random; using Robust.Shared.Spawners; using Robust.Shared.Timing; using TimedDespawnComponent = Robust.Shared.Spawners.TimedDespawnComponent; @@ -36,6 +29,7 @@ public sealed class SmokeSystem : EntitySystem [Dependency] private readonly AppearanceSystem _appearance = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly SolutionContainerSystem _solutionSystem = default!; + [Dependency] private readonly IRobustRandom _random = default!; /// public override void Initialize() @@ -45,15 +39,6 @@ public sealed class SmokeSystem : EntitySystem SubscribeLocalEvent(OnReactionAttempt); SubscribeLocalEvent(OnSmokeSpread); SubscribeLocalEvent(OnSmokeDissipate); - SubscribeLocalEvent(OnSpreadUpdateRate); - } - - private void OnSpreadUpdateRate(ref SpreadGroupUpdateRate ev) - { - if (ev.Name != "smoke") - return; - - ev.UpdatesPerSecond = 8; } private void OnSmokeDissipate(EntityUid uid, SmokeDissipateSpawnComponent component, ref TimedDespawnEvent args) @@ -68,11 +53,10 @@ public sealed class SmokeSystem : EntitySystem private void OnSmokeSpread(EntityUid uid, SmokeComponent component, ref SpreadNeighborsEvent args) { - if (component.SpreadAmount == 0 || - !_solutionSystem.TryGetSolution(uid, SmokeComponent.SolutionName, out var solution) || - args.NeighborFreeTiles.Count == 0) + if (component.SpreadAmount == 0 + || !_solutionSystem.TryGetSolution(uid, SmokeComponent.SolutionName, out var solution)) { - RemCompDeferred(uid); + RemCompDeferred(uid); return; } @@ -80,68 +64,62 @@ public sealed class SmokeSystem : EntitySystem if (prototype == null) { - RemCompDeferred(uid); + RemCompDeferred(uid); return; } TryComp(uid, out var timer); + _appearance.TryGetData(uid, SmokeVisuals.Color, out var color); - var smokePerSpread = component.SpreadAmount / args.NeighborFreeTiles.Count; - component.SpreadAmount -= smokePerSpread; - + // wtf is the logic behind any of this. + var smokePerSpread = 1 + component.SpreadAmount / Math.Max(1, args.NeighborFreeTiles.Count); foreach (var neighbor in args.NeighborFreeTiles) { var coords = neighbor.Grid.GridTileToLocal(neighbor.Tile); - var ent = Spawn(prototype.ID, coords.SnapToGrid()); + var ent = Spawn(prototype.ID, coords); var neighborSmoke = EnsureComp(ent); - neighborSmoke.SpreadAmount = Math.Max(0, smokePerSpread - 1); + neighborSmoke.SpreadAmount = Math.Max(0, smokePerSpread - 2); // why - 2? who the fuck knows. + component.SpreadAmount--; args.Updates--; // Listen this is the old behaviour iunno Start(ent, neighborSmoke, solution.Clone(), timer?.Lifetime ?? 10f); - if (_appearance.TryGetData(uid, SmokeVisuals.Color, out var color)) - { + if (color != null) _appearance.SetData(ent, SmokeVisuals.Color, color); - } - // Only 1 spread then ig? - if (smokePerSpread == 0) + if (component.SpreadAmount == 0) { - component.SpreadAmount--; - - if (component.SpreadAmount == 0) - { - RemCompDeferred(uid); - break; - } + RemCompDeferred(uid); + break; } if (args.Updates <= 0) break; } - // Give our spread to neighbor tiles. - if (args.NeighborFreeTiles.Count == 0 && args.Neighbors.Count > 0 && component.SpreadAmount > 0) + if (args.NeighborFreeTiles.Count > 0 || args.Neighbors.Count == 0 || component.SpreadAmount < 1) + return; + + // We have no more neighbours to spread to. So instead we will randomly distribute our volume to neighbouring smoke tiles. + + var smokeQuery = GetEntityQuery(); + + _random.Shuffle(args.Neighbors); + foreach (var neighbor in args.Neighbors) { - var smokeQuery = GetEntityQuery(); + if (!smokeQuery.TryGetComponent(neighbor, out var smoke)) + continue; - foreach (var neighbor in args.Neighbors) + smoke.SpreadAmount++; + args.Updates--; + component.SpreadAmount--; + EnsureComp(neighbor); + + if (component.SpreadAmount == 0) { - if (!smokeQuery.TryGetComponent(neighbor, out var smoke)) - continue; - - smoke.SpreadAmount++; - args.Updates--; - - if (component.SpreadAmount == 0) - { - RemCompDeferred(uid); - break; - } - - if (args.Updates <= 0) - break; + RemCompDeferred(uid); + break; } } } @@ -248,7 +226,7 @@ public sealed class SmokeSystem : EntitySystem public void Start(EntityUid uid, SmokeComponent component, Solution solution, float duration) { TryAddSolution(uid, component, solution); - EnsureComp(uid); + EnsureComp(uid); var timer = EnsureComp(uid); timer.Lifetime = duration; } diff --git a/Content.Server/NodeContainer/NodeGroups/NodeGroupFactory.cs b/Content.Server/NodeContainer/NodeGroups/NodeGroupFactory.cs index 1af938da83..abebfd1a90 100644 --- a/Content.Server/NodeContainer/NodeGroups/NodeGroupFactory.cs +++ b/Content.Server/NodeContainer/NodeGroups/NodeGroupFactory.cs @@ -61,7 +61,6 @@ namespace Content.Server.NodeContainer.NodeGroups AMEngine, Pipe, WireNet, - Spreader, /// /// Group used by the TEG. diff --git a/Content.Server/Spreader/ActiveEdgeSpreaderComponent.cs b/Content.Server/Spreader/ActiveEdgeSpreaderComponent.cs new file mode 100644 index 0000000000..86d59e737b --- /dev/null +++ b/Content.Server/Spreader/ActiveEdgeSpreaderComponent.cs @@ -0,0 +1,10 @@ +namespace Content.Server.Spreader; + +/// +/// Added to entities being considered for spreading via . +/// This needs to be manually added and removed. +/// +[RegisterComponent] +public sealed partial class ActiveEdgeSpreaderComponent : Component +{ +} diff --git a/Content.Server/Spreader/EdgeSpreaderComponent.cs b/Content.Server/Spreader/EdgeSpreaderComponent.cs index d0e18fccc2..aa1bf47f7a 100644 --- a/Content.Server/Spreader/EdgeSpreaderComponent.cs +++ b/Content.Server/Spreader/EdgeSpreaderComponent.cs @@ -1,10 +1,14 @@ +using Content.Shared.Spreader; +using Robust.Shared.Prototypes; + namespace Content.Server.Spreader; /// -/// Added to entities being considered for spreading via . -/// This needs to be manually added and removed. +/// Entity capable of becoming cloning and replicating itself to adjacent edges. See /// [RegisterComponent, Access(typeof(SpreaderSystem))] public sealed partial class EdgeSpreaderComponent : Component { + [DataField(required:true)] + public ProtoId Id; } diff --git a/Content.Server/Spreader/GrowingKudzuComponent.cs b/Content.Server/Spreader/GrowingKudzuComponent.cs index 98d34593d6..d2cd6d6404 100644 --- a/Content.Server/Spreader/GrowingKudzuComponent.cs +++ b/Content.Server/Spreader/GrowingKudzuComponent.cs @@ -5,12 +5,6 @@ namespace Content.Server.Spreader; [RegisterComponent, Access(typeof(KudzuSystem))] public sealed partial class GrowingKudzuComponent : Component { - /// - /// At level 3 spreading can occur; prior to that we have a chance of increasing our growth level and changing our sprite. - /// - [DataField("growthLevel")] - public int GrowthLevel = 1; - /// /// The next time kudzu will try to tick its growth level. /// diff --git a/Content.Server/Spreader/KudzuComponent.cs b/Content.Server/Spreader/KudzuComponent.cs index afdf510f05..36b1796b83 100644 --- a/Content.Server/Spreader/KudzuComponent.cs +++ b/Content.Server/Spreader/KudzuComponent.cs @@ -8,6 +8,12 @@ namespace Content.Server.Spreader; [RegisterComponent] public sealed partial class KudzuComponent : Component { + /// + /// At level 3 spreading can occur; prior to that we have a chance of increasing our growth level and changing our sprite. + /// + [DataField] + public int GrowthLevel = 1; + /// /// Chance to spread whenever an edge spread is possible. /// diff --git a/Content.Server/Spreader/KudzuSystem.cs b/Content.Server/Spreader/KudzuSystem.cs index db88226d79..b59569b4e1 100644 --- a/Content.Server/Spreader/KudzuSystem.cs +++ b/Content.Server/Spreader/KudzuSystem.cs @@ -2,6 +2,7 @@ using Content.Shared.Damage; using Content.Shared.Spreader; using Robust.Shared.Random; using Robust.Shared.Timing; +using Robust.Shared.Utility; namespace Content.Server.Spreader; @@ -13,7 +14,7 @@ public sealed class KudzuSystem : EntitySystem [Dependency] private readonly DamageableSystem _damageable = default!; [ValidatePrototypeId] - private const string KudzuGroup = "kudzu"; + private const string KudzuGroup = "Kudzu"; /// public override void Initialize() @@ -21,7 +22,6 @@ public sealed class KudzuSystem : EntitySystem SubscribeLocalEvent(SetupKudzu); SubscribeLocalEvent(OnKudzuSpread); SubscribeLocalEvent(OnKudzuUnpaused); - SubscribeLocalEvent(OnKudzuUpdateRate); SubscribeLocalEvent(OnDamageChanged); } @@ -36,59 +36,50 @@ public sealed class KudzuSystem : EntitySystem if (!TryComp(uid, out growing)) { growing = AddComp(uid); - growing.GrowthLevel = 3; + component.GrowthLevel = 3; } - growing.GrowthLevel = Math.Max(1, growing.GrowthLevel - growthDamage); + component.GrowthLevel = Math.Max(1, component.GrowthLevel - growthDamage); if (EntityManager.TryGetComponent(uid, out var appearance)) { - _appearance.SetData(uid, KudzuVisuals.GrowthLevel, growing.GrowthLevel, appearance); + _appearance.SetData(uid, KudzuVisuals.GrowthLevel, component.GrowthLevel, appearance); } } } private void OnKudzuSpread(EntityUid uid, KudzuComponent component, ref SpreadNeighborsEvent args) { - if (TryComp(uid, out var growing) && growing.GrowthLevel < 3) - { + if (component.GrowthLevel < 3) return; - } if (args.NeighborFreeTiles.Count == 0) { - RemCompDeferred(uid); - return; - } - - var prototype = MetaData(uid).EntityPrototype?.ID; - - if (prototype == null) - { - RemCompDeferred(uid); + RemCompDeferred(uid); return; } if (!_robustRandom.Prob(component.SpreadChance)) return; + var prototype = MetaData(uid).EntityPrototype?.ID; + + if (prototype == null) + { + RemCompDeferred(uid); + return; + } + foreach (var neighbor in args.NeighborFreeTiles) { var neighborUid = Spawn(prototype, neighbor.Grid.GridTileToLocal(neighbor.Tile)); - EnsureComp(neighborUid); + DebugTools.Assert(HasComp(neighborUid)); + DebugTools.Assert(HasComp(neighborUid)); + DebugTools.Assert(Comp(neighborUid).Id == KudzuGroup); args.Updates--; - if (args.Updates <= 0) return; } } - private void OnKudzuUpdateRate(ref SpreadGroupUpdateRate args) - { - if (args.Name != KudzuGroup) - return; - - args.UpdatesPerSecond = 1; - } - private void OnKudzuUnpaused(EntityUid uid, GrowingKudzuComponent component, ref EntityUnpausedEvent args) { component.NextTick += args.PausedTime; @@ -109,24 +100,30 @@ public sealed class KudzuSystem : EntitySystem public override void Update(float frameTime) { var appearanceQuery = GetEntityQuery(); - var query = EntityQueryEnumerator(); + var query = EntityQueryEnumerator(); + var kudzuQuery = GetEntityQuery(); + var damageableQuery = GetEntityQuery(); var curTime = _timing.CurTime; - while (query.MoveNext(out var uid, out var grow, out var kudzu)) + while (query.MoveNext(out var uid, out var grow)) { if (grow.NextTick > curTime) - { continue; - } grow.NextTick = curTime + TimeSpan.FromSeconds(0.5); + if (!kudzuQuery.TryGetComponent(uid, out var kudzu)) + { + RemCompDeferred(uid, grow); + continue; + } + if (!_robustRandom.Prob(kudzu.GrowthTickChance)) { continue; } - if (TryComp(uid, out var damage)) + if (damageableQuery.TryGetComponent(uid, out var damage)) { if (damage.TotalDamage > 1.0) { @@ -146,17 +143,17 @@ public sealed class KudzuSystem : EntitySystem } } - grow.GrowthLevel += 1; + kudzu.GrowthLevel += 1; - if (grow.GrowthLevel >= 3) + if (kudzu.GrowthLevel >= 3) { // why cache when you can simply cease to be? Also saves a bit of memory/time. - RemCompDeferred(uid); + RemCompDeferred(uid, grow); } if (appearanceQuery.TryGetComponent(uid, out var appearance)) { - _appearance.SetData(uid, KudzuVisuals.GrowthLevel, grow.GrowthLevel, appearance); + _appearance.SetData(uid, KudzuVisuals.GrowthLevel, kudzu.GrowthLevel, appearance); } } } diff --git a/Content.Server/Spreader/SpreadGroupUpdateRate.cs b/Content.Server/Spreader/SpreadGroupUpdateRate.cs deleted file mode 100644 index 091c6e5ad5..0000000000 --- a/Content.Server/Spreader/SpreadGroupUpdateRate.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Content.Server.Spreader; - -/// -/// Raised every tick to determine how many updates a particular spreading node group is allowed. -/// -[ByRefEvent] -public record struct SpreadGroupUpdateRate(string Name, int UpdatesPerSecond = 16); diff --git a/Content.Server/Spreader/SpreaderNode.cs b/Content.Server/Spreader/SpreaderNode.cs deleted file mode 100644 index 0c8a5c8ee8..0000000000 --- a/Content.Server/Spreader/SpreaderNode.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Content.Server.NodeContainer; -using Content.Server.NodeContainer.EntitySystems; -using Content.Server.NodeContainer.Nodes; -using Robust.Shared.Map.Components; - -namespace Content.Server.Spreader; - -/// -/// Handles the node for . -/// Functions as a generic tile-based entity spreader for systems such as puddles or smoke. -/// -public sealed partial class SpreaderNode : Node -{ - // [Dependency] private readonly NodeContainerSystem _nodeContainer = default!; - - /// - public override IEnumerable GetReachableNodes(TransformComponent xform, EntityQuery nodeQuery, EntityQuery xformQuery, - MapGridComponent? grid, IEntityManager entMan) - { - if (grid == null) - yield break; - - entMan.System().GetNeighbors(xform.Owner, Name, out _, out _, out var neighbors); - - var _nodeContainer = entMan.System(); - - foreach (var neighbor in neighbors) - { - if (!nodeQuery.TryGetComponent(neighbor, out var nodeContainer) || - !_nodeContainer.TryGetNode(nodeContainer, Name, out var neighborNode)) - { - continue; - } - - yield return neighborNode; - } - } -} diff --git a/Content.Server/Spreader/SpreaderNodeGroup.cs b/Content.Server/Spreader/SpreaderNodeGroup.cs deleted file mode 100644 index a94ad83b0a..0000000000 --- a/Content.Server/Spreader/SpreaderNodeGroup.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Content.Server.NodeContainer.NodeGroups; -using Content.Server.NodeContainer.Nodes; - -namespace Content.Server.Spreader; - -[NodeGroup(NodeGroupID.Spreader)] -public sealed class SpreaderNodeGroup : BaseNodeGroup -{ - private IEntityManager _entManager = default!; - - /// - public override void Initialize(Node sourceNode, IEntityManager entMan) - { - base.Initialize(sourceNode, entMan); - _entManager = entMan; - } - - /// - public override void RemoveNode(Node node) - { - base.RemoveNode(node); - - foreach (var neighborNode in node.ReachableNodes) - { - if (_entManager.Deleted(neighborNode.Owner)) - continue; - - _entManager.EnsureComponent(neighborNode.Owner); - } - } -} diff --git a/Content.Server/Spreader/SpreaderSystem.cs b/Content.Server/Spreader/SpreaderSystem.cs index e73fb11507..b6ebaf739b 100644 --- a/Content.Server/Spreader/SpreaderSystem.cs +++ b/Content.Server/Spreader/SpreaderSystem.cs @@ -1,8 +1,5 @@ using Content.Server.Atmos.Components; using Content.Server.Atmos.EntitySystems; -using Content.Server.NodeContainer; -using Content.Server.NodeContainer.EntitySystems; -using Content.Server.NodeContainer.NodeGroups; using Content.Server.Shuttles.Components; using Content.Shared.Atmos; using Content.Shared.Spreader; @@ -13,6 +10,7 @@ using Robust.Shared.Map.Components; using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Timing; +using Robust.Shared.Utility; namespace Content.Server.Spreader; @@ -25,11 +23,20 @@ public sealed class SpreaderSystem : EntitySystem [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly IRobustRandom _robustRandom = default!; - [Dependency] private readonly NodeContainerSystem _nodeContainer = default!; - private static readonly TimeSpan SpreadCooldown = TimeSpan.FromSeconds(1); + private static readonly TimeSpan SpreadCooldown = TimeSpan.FromSeconds(SpreadCooldownSeconds); - private readonly List _spreaderGroups = new(); + /// + /// Cached maximum number of updates per spreader prototype. This is applied per-grid. + /// + private Dictionary _prototypeUpdates = default!; + + /// + /// Remaining number of updates per grid & prototype. + /// + private Dictionary> _gridUpdates = new(); + + private const float SpreadCooldownSeconds = 1; [ValidatePrototypeId] private const string IgnoredTag = "SpreaderIgnore"; @@ -42,6 +49,7 @@ public sealed class SpreaderSystem : EntitySystem SubscribeLocalEvent(OnGridUnpaused); + SubscribeLocalEvent(OnTerminating); SetupPrototypes(); _prototype.PrototypesReloaded += OnPrototypeReload; } @@ -62,21 +70,20 @@ public sealed class SpreaderSystem : EntitySystem private void SetupPrototypes() { - _spreaderGroups.Clear(); - - foreach (var id in _prototype.EnumeratePrototypes()) + _prototypeUpdates = new Dictionary(); + foreach (var proto in _prototype.EnumeratePrototypes()) { - _spreaderGroups.Add(id.ID); + _prototypeUpdates.Add(proto.ID, proto.UpdatesPerSecond); } } private void OnAirtightChanged(ref AirtightChanged ev) { - var neighbors = GetNeighbors(ev.Entity, ev.Airtight); + var neighbors = GetSpreadableNeighbors(ev.Entity, ev.Airtight, ev.Position); foreach (var neighbor in neighbors) { - EnsureComp(neighbor); + EnsureComp(neighbor); } } @@ -87,7 +94,17 @@ public sealed class SpreaderSystem : EntitySystem private void OnGridInit(GridInitializeEvent ev) { - var comp = EnsureComp(ev.EntityUid); + EnsureComp(ev.EntityUid); + } + + private void OnTerminating(EntityUid uid, EdgeSpreaderComponent component, ref EntityTerminatingEvent args) + { + var neighbors = GetSpreadableNeighbors(uid); + + foreach (var neighbor in neighbors) + { + EnsureComp(neighbor); + } } /// @@ -96,31 +113,26 @@ public sealed class SpreaderSystem : EntitySystem var curTime = _timing.CurTime; // Check which grids are valid for spreading - var spreadable = new ValueList(); var spreadGrids = EntityQueryEnumerator(); + _gridUpdates.Clear(); while (spreadGrids.MoveNext(out var uid, out var grid)) { if (grid.NextUpdate > curTime) continue; - spreadable.Add(uid); + _gridUpdates[uid] = _prototypeUpdates.ShallowClone(); grid.NextUpdate += SpreadCooldown; } - if (spreadable.Count == 0) + if (_gridUpdates.Count == 0) return; - var query = EntityQueryEnumerator(); - var nodeQuery = GetEntityQuery(); - var xformQuery = GetEntityQuery(); - var gridQuery = GetEntityQuery(); + var query = EntityQueryEnumerator(); + var xforms = GetEntityQuery(); + var spreaderQuery = GetEntityQuery(); - // Each INode group has a certain number of updates - // allowed per SpreadCooldown - var groupUpdates = new Dictionary(); - - var spreaders = new List<(EntityUid Uid, EdgeSpreaderComponent Comp)>(Count()); + var spreaders = new List<(EntityUid Uid, ActiveEdgeSpreaderComponent Comp)>(Count()); // Build a list of all existing Edgespreaders, shuffle them while (query.MoveNext(out var uid, out var comp)) @@ -134,64 +146,43 @@ public sealed class SpreaderSystem : EntitySystem // that doesn't meet a few trivial prerequisites foreach (var (uid, comp) in spreaders) { - if (!xformQuery.TryGetComponent(uid, out var xform) || - xform.GridUid == null || - !gridQuery.HasComponent(xform.GridUid.Value)) + // 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) { - RemCompDeferred(uid); + RemComp(uid, comp); continue; } - foreach (var sGroup in _spreaderGroups) + if (!_gridUpdates.TryGetValue(xform.GridUid.Value, out var groupUpdates)) + continue; + + if (!spreaderQuery.TryGetComponent(uid, out var spreader)) { - // Get the NodeContainer and Node from every EdgeSpreader entity found - if (!nodeQuery.TryGetComponent(uid, out var nodeContainer)) - { - RemCompDeferred(uid); - continue; - } - - if (!_nodeContainer.TryGetNode(nodeContainer, sGroup, out var node)) - { - continue; - } - - // Not allowed this tick? - if (node.NodeGroup == null || - !spreadable.Contains(xform.GridUid.Value)) - { - continue; - } - - // Try get an integer update rate associated with a node group, - // getting it instead from the spreader itself on failure - if (!groupUpdates.TryGetValue(node.NodeGroup, out var updates)) - { - var spreadEv = new SpreadGroupUpdateRate(node.Name); - RaiseLocalEvent(ref spreadEv); - updates = (int) (spreadEv.UpdatesPerSecond * SpreadCooldown / TimeSpan.FromSeconds(1)); - } - - // "updates" integer dictates the amount of nodes that - // are to be spawned around a NodeGroup - if (updates <= 0) - { - continue; - } - - // Edge detection logic is to be handled - // by the subscribing system, see KudzuSystem - // for a simple example - - Spread(uid, node, node.NodeGroup, ref updates); - groupUpdates[node.NodeGroup] = updates; + 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, SpreaderNode node, INodeGroup group, ref int updates) + private void Spread(EntityUid uid, TransformComponent xform, string prototype, ref int updates) { - GetNeighbors(uid, node.Name, out var freeTiles, out _, out var neighbors); + GetNeighbors(uid, xform, prototype, out var freeTiles, out _, out var neighbors); var ev = new SpreadNeighborsEvent() { @@ -207,20 +198,19 @@ public sealed class SpreaderSystem : EntitySystem /// /// Gets the neighboring node data for the specified entity and the specified node group. /// - public void GetNeighbors(EntityUid uid, string groupName, out ValueList<(MapGridComponent Grid, Vector2i Tile)> freeTiles, out ValueList occupiedTiles, out ValueList neighbors) + public void GetNeighbors(EntityUid uid, TransformComponent transform, string prototype, out ValueList<(MapGridComponent Grid, Vector2i Tile)> freeTiles, out ValueList occupiedTiles, out ValueList neighbors) { + // TODO remove occupiedTiles -- its currently unused and just slows this method down. + DebugTools.Assert(_prototype.HasIndex(prototype)); freeTiles = new ValueList<(MapGridComponent Grid, Vector2i Tile)>(); occupiedTiles = new ValueList(); neighbors = new ValueList(); - if (!EntityManager.TryGetComponent(uid, out var transform)) - return; - if (!_mapManager.TryGetGrid(transform.GridUid, out var grid)) return; var tile = grid.TileIndicesFor(transform.Coordinates); - var nodeQuery = GetEntityQuery(); + var spreaderQuery = GetEntityQuery(); var airtightQuery = GetEntityQuery(); var dockQuery = GetEntityQuery(); var xformQuery = GetEntityQuery(); @@ -309,10 +299,10 @@ public sealed class SpreaderSystem : EntitySystem while (directionEnumerator.MoveNext(out var ent)) { - if (!nodeQuery.TryGetComponent(ent, out var nodeContainer)) + if (!spreaderQuery.TryGetComponent(ent, out var spreader)) continue; - if (!nodeContainer.Nodes.ContainsKey(groupName)) + if (spreader.Id != prototype) continue; neighbors.Add(ent.Value); @@ -325,23 +315,38 @@ public sealed class SpreaderSystem : EntitySystem } } - public List GetNeighbors(EntityUid uid, AirtightComponent comp) + /// + /// Given an entity, this returns a list of all adjacent entities with a . + /// + public List GetSpreadableNeighbors(EntityUid uid, AirtightComponent? comp = null, + (EntityUid Grid, Vector2i Tile)? position = null) { + Resolve(uid, ref comp, false); var neighbors = new List(); - if (!EntityManager.TryGetComponent(uid, out var transform)) - return neighbors; // how did we get here? + Vector2i tile; + MapGridComponent? grid; - if (!_mapManager.TryGetGrid(transform.GridUid, out var grid)) - return neighbors; + if (position == null) + { + var transform = Transform(uid); + if (!_mapManager.TryGetGrid(transform.GridUid, out grid)) + return neighbors; + tile = grid.TileIndicesFor(transform.Coordinates); + } + else + { + if (!_mapManager.TryGetGrid(position.Value.Grid, out grid)) + return neighbors; + tile = position.Value.Tile; + } - var tile = grid.TileIndicesFor(transform.Coordinates); - var nodeQuery = GetEntityQuery(); + var spreaderQuery = GetEntityQuery(); for (var i = 0; i < Atmospherics.Directions; i++) { var direction = (AtmosDirection) (1 << i); - if (!comp.AirBlockedDirection.IsFlagSet(direction)) + if (comp != null && !comp.AirBlockedDirection.IsFlagSet(direction)) continue; var directionEnumerator = @@ -349,17 +354,9 @@ public sealed class SpreaderSystem : EntitySystem while (directionEnumerator.MoveNext(out var ent)) { - if (!nodeQuery.TryGetComponent(ent, out var nodeContainer)) - continue; - - foreach (var name in _spreaderGroups) - { - if (!nodeContainer.Nodes.ContainsKey(name)) - continue; - + DebugTools.Assert(Transform(ent.Value).Anchored); + if (spreaderQuery.HasComponent(ent)) neighbors.Add(ent.Value); - break; - } } } diff --git a/Content.Shared/Spreader/EdgeSpreaderPrototype.cs b/Content.Shared/Spreader/EdgeSpreaderPrototype.cs index 2fab110fdb..a1e31da3ca 100644 --- a/Content.Shared/Spreader/EdgeSpreaderPrototype.cs +++ b/Content.Shared/Spreader/EdgeSpreaderPrototype.cs @@ -9,4 +9,5 @@ namespace Content.Shared.Spreader; public sealed class EdgeSpreaderPrototype : IPrototype { [IdDataField] public string ID { get; } = string.Empty; + [DataField(required:true)] public int UpdatesPerSecond; } diff --git a/Resources/Prototypes/Entities/Effects/chemistry_effects.yml b/Resources/Prototypes/Entities/Effects/chemistry_effects.yml index def26ec826..10e2f1f7db 100644 --- a/Resources/Prototypes/Entities/Effects/chemistry_effects.yml +++ b/Resources/Prototypes/Entities/Effects/chemistry_effects.yml @@ -13,12 +13,9 @@ - type: Transform anchored: true - type: Smoke - - type: NodeContainer - nodes: - smoke: - !type:SpreaderNode - nodeGroupID: Spreader + - type: ActiveEdgeSpreader - type: EdgeSpreader + id: Smoke - type: SolutionContainerManager solutions: solutionArea: @@ -64,12 +61,9 @@ layer: - SlipLayer - type: Smoke - - type: NodeContainer - nodes: - smoke: - !type:SpreaderNode - nodeGroupID: Spreader + - type: ActiveEdgeSpreader - type: EdgeSpreader + id: Smoke - type: SolutionContainerManager solutions: solutionArea: diff --git a/Resources/Prototypes/Entities/Effects/puddle.yml b/Resources/Prototypes/Entities/Effects/puddle.yml index 09e10a688b..090533acb8 100644 --- a/Resources/Prototypes/Entities/Effects/puddle.yml +++ b/Resources/Prototypes/Entities/Effects/puddle.yml @@ -144,13 +144,10 @@ puddle: { maxVol: 1000 } - type: Puddle - type: Appearance + - type: ActiveEdgeSpreader - type: EdgeSpreader + id: Puddle - type: StepTrigger - - type: NodeContainer - nodes: - puddle: - !type:SpreaderNode - nodeGroupID: Spreader - type: Drink delay: 3 transferAmount: 1 diff --git a/Resources/Prototypes/Entities/Objects/Misc/kudzu.yml b/Resources/Prototypes/Entities/Objects/Misc/kudzu.yml index 1ff3ee0fc3..48b0068d94 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/kudzu.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/kudzu.yml @@ -79,12 +79,9 @@ ignoreWhitelist: components: - IgnoreKudzu + - type: ActiveEdgeSpreader - type: EdgeSpreader - - type: NodeContainer - nodes: - kudzu: - !type:SpreaderNode - nodeGroupID: Spreader + id: Kudzu - type: Food requiredStomachs: 2 # ruminants have 4 stomachs but i dont care to give them literally 4 stomachs. 2 is good delay: 0.5 @@ -186,12 +183,9 @@ - type: GrowingKudzu growthTickChance: 0.3 - type: AtmosExposed + - type: ActiveEdgeSpreader - type: EdgeSpreader - - type: NodeContainer - nodes: - kudzu: - !type:SpreaderNode - nodeGroupID: Spreader + id: Kudzu - type: SlowContacts walkSpeedModifier: 0.3 sprintSpeedModifier: 0.3 diff --git a/Resources/Prototypes/edge_spreaders.yml b/Resources/Prototypes/edge_spreaders.yml index 4cd1ada1c1..061932c706 100644 --- a/Resources/Prototypes/edge_spreaders.yml +++ b/Resources/Prototypes/edge_spreaders.yml @@ -1,8 +1,11 @@ - type: edgeSpreader - id: kudzu + id: Kudzu + updatesPerSecond: 1 - type: edgeSpreader - id: puddle + id: Puddle + updatesPerSecond: 16 - type: edgeSpreader - id: smoke \ No newline at end of file + id: Smoke + updatesPerSecond: 8