diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.Impact.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.Impact.cs index 8adb02eb065..017aa804fd7 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.Impact.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.Impact.cs @@ -25,11 +25,16 @@ using Robust.Shared.Random; using System.Numerics; +using Content.Server._Mono.Cleanup; +using Content.Shared._Mono.CCVar; + namespace Content.Server.Shuttles.Systems; // shuttle impact damage ported from Goobstation (AGPLv3) with agreement of all coders involved public sealed partial class ShuttleSystem { + [Dependency] private readonly SpaceCleanupSystem _sweep = default!; + private bool _enabled; private float _minimumImpactInertia; private float _minimumImpactVelocity; @@ -45,6 +50,11 @@ public sealed partial class ShuttleSystem // this doesn't update if plating mass is changed but edgecase private float _platingMass; + // Mono + private float _sweepAggression; + private float _sweepDelay; + private float _sweepRadius; + private const float _sparkChance = 0.2f; // shuttle mass to consider the neutral point for inertia scaling private const float _baseShuttleMass = 50f; @@ -86,6 +96,11 @@ private void InitializeImpact() Subs.CVar(_cfg, CCVars.ImpactMassBias, value => _massBias = value, true); Subs.CVar(_cfg, CCVars.ImpactInertiaScaling, value => _inertiaScaling = value, true); + // Mono + Subs.CVar(_cfg, MonoCVars.ImpactSweepAggression, val => _sweepAggression = val, true); + Subs.CVar(_cfg, MonoCVars.ImpactSweepDelay, val => _sweepDelay = val, true); + Subs.CVar(_cfg, MonoCVars.ImpactSweepRadius, val => _sweepRadius = val, true); + _platingMass = _protoManager.Index(_platingId).Mass; } @@ -192,7 +207,12 @@ private void OnShuttleCollide(EntityUid uid, ShuttleComponent component, ref Sta var impact = LogImpact.High; // if impact isn't tiny, log it as extreme if (toUsEnergy + toOtherEnergy > 2f * _tileBreakEnergyMultiplier * _platingMass) + { impact = LogImpact.Extreme; + + // Mono - also queue cleanup sweeps + _sweep.QueueSweep(ourPoint, TimeSpan.FromSeconds(_sweepDelay), _sweepRadius, _sweepAggression); + } // TODO: would be nice for it to also log who is piloting the grid(s) if (CheckShouldLog(args.OurEntity) && CheckShouldLog(args.OtherEntity)) _logger.Add(LogType.ShuttleImpact, impact, $"Shuttle impact of {ToPrettyString(args.OurEntity)} with {ToPrettyString(args.OtherEntity)} at {worldPoint}"); @@ -210,10 +230,10 @@ private void OnShuttleCollide(EntityUid uid, ShuttleComponent component, ref Sta && TryComp(ShipShieldedComponent.Source, out var ShipShieldEmitterComponent) ) toUsEnergy *= ShipShieldEmitterComponent.CollisionResistanceMultiplier; - + if (TryComp(args.OtherEntity, out var OtherShipShieldedComponent) //Other ship collision resistance && TryComp(OtherShipShieldedComponent.Source, out var OtherShipShieldEmitterComponent) - ) + ) toOtherEnergy *= OtherShipShieldEmitterComponent.CollisionResistanceMultiplier; // Mono Edit end @@ -260,35 +280,58 @@ private void ThrowEntitiesOnGrid(EntityUid gridUid, TransformComponent xform, Ve var knockdownTime = TimeSpan.FromSeconds(5); var minsq = _minThrowVelocity * _minThrowVelocity; - // iterate all entities on the grid - // TODO: only iterate non-static entities - var childEnumerator = xform.ChildEnumerator; - while (childEnumerator.MoveNext(out var uid)) - { - // don't throw static bodies - if (!_physicsQuery.TryGetComponent(uid, out var physics) || (physics.BodyType & BodyType.Static) != 0) - continue; + // iterate all dynamic entities on the grid + if (!TryComp(gridUid, out var lookup) || !_gridQuery.TryComp(gridUid, out var gridComp)) + return; + + var gridBox = gridComp.LocalAABB; + List> list = new(); + HashSet processed = new(); + var state = (list, processed, _physicsQuery); + lookup.DynamicTree.QueryAabb(ref state, GridQueryCallback, gridBox, true); + lookup.SundriesTree.QueryAabb(ref state, GridQueryCallback, gridBox, true); + + foreach (var ent in list) + { // don't throw if buckled - if (_buckle.IsBuckled(uid, _buckleQuery.CompOrNull(uid))) + if (_buckle.IsBuckled(ent, _buckleQuery.CompOrNull(ent))) continue; // don't throw them if they have magboots - if (movedByPressureQuery.TryComp(uid, out var moved) && !moved.Enabled) + if (movedByPressureQuery.TryComp(ent, out var moved) && !moved.Enabled) continue; if (direction.LengthSquared() > minsq) { - _stuns.TryKnockdown(uid, knockdownTime, true); - _throwing.TryThrow(uid, direction, physics, Transform(uid), _projQuery, direction.Length(), playSound: false); + _stuns.TryKnockdown(ent.Owner, knockdownTime, true); + _throwing.TryThrow(ent, direction, ent.Comp, Transform(ent), _projQuery, direction.Length(), playSound: false); } else { - _physics.ApplyLinearImpulse(uid, direction * physics.Mass, body: physics); + _physics.ApplyLinearImpulse(ent, direction * ent.Comp.Mass, body: ent.Comp); } } } + private static bool GridQueryCallback( + ref (List> List, HashSet Processed, EntityQuery PhysicsQuery) state, + in EntityUid uid) + { + if (state.Processed.Add(uid) && state.PhysicsQuery.TryComp(uid, out var body)) + state.List.Add((uid, body)); + + return true; + } + + private static bool GridQueryCallback( + ref (List> List, HashSet Processed, EntityQuery PhysicsQuery) state, + in FixtureProxy proxy) + { + var owner = proxy.Entity; + return GridQueryCallback(ref state, in owner); + } + /// /// Structure to hold impact tile processing data for batch processing /// diff --git a/Content.Server/_Mono/Cleanup/BaseCleanupSystem.cs b/Content.Server/_Mono/Cleanup/BaseCleanupSystem.cs index c5d5f8285d8..691d9edf430 100644 --- a/Content.Server/_Mono/Cleanup/BaseCleanupSystem.cs +++ b/Content.Server/_Mono/Cleanup/BaseCleanupSystem.cs @@ -53,13 +53,7 @@ public override void Update(float frameTime) if (!ShouldEntityCleanup(uid)) continue; - var coord = Transform(uid).Coordinates; - var world = _transform.ToMapCoordinates(coord); - if (_doLog) - Log.Info($"Cleanup deleting entity {ToPrettyString(uid)} at {coord} (world {world})"); - - _delCount += 1; - QueueDel(uid); + CleanupEnt(uid); } return; } @@ -90,5 +84,16 @@ public override void Update(float frameTime) Log.Debug($"Ran cleanup queue, found: {_checkQueue.Count}, deleting over {_cleanupDeferDuration}"); } + protected void CleanupEnt(EntityUid uid) + { + var coord = Transform(uid).Coordinates; + var world = _transform.ToMapCoordinates(coord); + if (_doLog) + Log.Info($"Cleanup deleting entity {ToPrettyString(uid)} at {coord} (world {world})"); + + _delCount += 1; + QueueDel(uid); + } + protected abstract bool ShouldEntityCleanup(EntityUid uid); } diff --git a/Content.Server/_Mono/Cleanup/SpaceCleanupSystem.cs b/Content.Server/_Mono/Cleanup/SpaceCleanupSystem.cs index 5f06d1438bd..b6954900900 100644 --- a/Content.Server/_Mono/Cleanup/SpaceCleanupSystem.cs +++ b/Content.Server/_Mono/Cleanup/SpaceCleanupSystem.cs @@ -11,6 +11,7 @@ using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Collision.Shapes; using Robust.Shared.Physics.Systems; +using Robust.Shared.Timing; using System.Reflection; namespace Content.Server._Mono.Cleanup; @@ -21,12 +22,15 @@ namespace Content.Server._Mono.Cleanup; public sealed class SpaceCleanupSystem : BaseCleanupSystem { [Dependency] private readonly CleanupHelperSystem _cleanup = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly IGameTiming _timing = default!; private object _manifold = default!; private MethodInfo _testOverlap = default!; + [Dependency] private readonly PricingSystem _pricing = default!; [Dependency] private readonly SharedMapSystem _map = default!; [Dependency] private readonly SharedPhysicsSystem _physics = default!; - [Dependency] private readonly PricingSystem _pricing = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; private float _maxDistance; private float _maxGridDistance; @@ -39,6 +43,9 @@ public sealed class SpaceCleanupSystem : BaseCleanupSystem private EntityQuery _mindQuery; private EntityQuery _physQuery; + private List<(EntityCoordinates Coord, TimeSpan Time, float Radius, float Aggression)> _sweepQueue = new(); + private HashSet> _sweepEnts = new(); + public override void Initialize() { base.Initialize(); @@ -68,21 +75,28 @@ public override void Initialize() } protected override bool ShouldEntityCleanup(EntityUid uid) + { + return ShouldEntityCleanup(uid, 1f); + } + + private bool ShouldEntityCleanup(EntityUid uid, float aggression) { var xform = Transform(uid); var isStuck = false; + var price = 0f; + return !_gridQuery.HasComp(uid) && (xform.ParentUid == xform.MapUid // don't delete if on grid || (isStuck |= GetWallStuck((uid, xform)))) // or wall-stuck && !_htnQuery.HasComp(uid) // handled by MobCleanupSystem && !_immuneQuery.HasComp(uid) // handled by GridCleanupSystem && !_mindQuery.HasComp(uid) // no deleting anything that can have a mind - should be handled by MobCleanupSystem anyway - && _pricing.GetPrice(uid) <= _maxPrice + && (price = (float)_pricing.GetPrice(uid)) <= _maxPrice && (isStuck - || !_cleanup.HasNearbyGrids(xform.Coordinates, _maxGridDistance) - && !_cleanup.HasNearbyPlayers(xform.Coordinates, _maxDistance)); + || !_cleanup.HasNearbyGrids(xform.Coordinates, _maxGridDistance * aggression * MathF.Sqrt(price / _maxPrice)) + && !_cleanup.HasNearbyPlayers(xform.Coordinates, _maxDistance * aggression * MathF.Sqrt(price / _maxPrice))); } private bool GetWallStuck(Entity ent) @@ -133,4 +147,35 @@ private bool GetWallStuck(Entity ent) return false; } + + public void QueueSweep(EntityCoordinates coordinates, TimeSpan time, float radius, float aggression) + { + _sweepQueue.Add((coordinates, time, radius, aggression)); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + for (int i = _sweepQueue.Count - 1; i >= 0; i--) + { + var (coord, time, radius, aggression) = _sweepQueue[i]; + + if (_timing.CurTime < time) + continue; + + _sweepQueue.RemoveAt(i); + if (!coord.IsValid(EntityManager)) + continue; + + _sweepEnts.Clear(); + _lookup.GetEntitiesInRange(_transform.ToMapCoordinates(coord), radius, _sweepEnts, LookupFlags.Dynamic | LookupFlags.Approximate | LookupFlags.Sundries); + + foreach (var (uid, body) in _sweepEnts) + { + if (ShouldEntityCleanup(uid, aggression)) + CleanupEnt(uid); + } + } + } } diff --git a/Content.Shared/_Mono/CCVar/CCVars.Mono.cs b/Content.Shared/_Mono/CCVar/CCVars.Mono.cs index b3cfbdd04be..51f1614ee75 100644 --- a/Content.Shared/_Mono/CCVar/CCVars.Mono.cs +++ b/Content.Shared/_Mono/CCVar/CCVars.Mono.cs @@ -26,7 +26,7 @@ public sealed partial class MonoCVars /// Don't delete non-grids at most this close to a grid. /// public static readonly CVarDef CleanupMaxGridDistance = - CVarDef.Create("mono.cleanup.max_grid_distance", 20.0f, CVar.SERVERONLY); + CVarDef.Create("mono.cleanup.max_grid_distance", 30.0f, CVar.SERVERONLY); /// /// How far away from any players can a mob be until it gets cleaned up. @@ -68,7 +68,25 @@ public sealed partial class MonoCVars /// How much can a spaced entity at most be worth for it to be cleaned up. /// public static readonly CVarDef SpaceCleanupMaxValue = - CVarDef.Create("mono.cleanup.space.max_value", 10000.0f, CVar.SERVERONLY); + CVarDef.Create("mono.cleanup.space.max_value", 3000.0f, CVar.SERVERONLY); + + /// + /// After a shuttle impact, how aggressively to sweep. Makes sweep more willing to delete items close to grids or players. + /// + public static readonly CVarDef ImpactSweepAggression = + CVarDef.Create("mono.cleanup.impact.aggression", 0.1f, CVar.SERVERONLY); + + /// + /// After a shuttle impact, in how much after the impact to perform the sweep. + /// + public static readonly CVarDef ImpactSweepDelay = + CVarDef.Create("mono.cleanup.impact.delay", 5.0f, CVar.SERVERONLY); + + /// + /// After a shuttle impact, in how much of a radius to immediately sweep for loose items. + /// + public static readonly CVarDef ImpactSweepRadius = + CVarDef.Create("mono.cleanup.impact.radius", 60.0f, CVar.SERVERONLY); #endregion diff --git a/Resources/Prototypes/Entities/Objects/Tools/cable_coils.yml b/Resources/Prototypes/Entities/Objects/Tools/cable_coils.yml index c53860dbed3..24323f64248 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/cable_coils.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/cable_coils.yml @@ -58,6 +58,18 @@ guides: - VoltageNetworks - Power + # Mono + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 150 + behaviors: + - !type:DoActsBehavior + acts: [ "Destruction" ] + - type: Damageable + damageContainer: Inorganic + damageModifierSet: Metallic - type: entity id: CableHVStack diff --git a/Resources/Prototypes/Entities/Structures/Power/cables.yml b/Resources/Prototypes/Entities/Structures/Power/cables.yml index 08cb557225e..178e67c45b7 100644 --- a/Resources/Prototypes/Entities/Structures/Power/cables.yml +++ b/Resources/Prototypes/Entities/Structures/Power/cables.yml @@ -79,7 +79,7 @@ thresholds: - trigger: !type:DamageTrigger - damage: 500 + damage: 400 behaviors: #excess damage (nuke?). avoid computational cost of spawning entities. - !type:DoActsBehavior acts: [ "Destruction" ] @@ -138,7 +138,7 @@ thresholds: - trigger: !type:DamageTrigger - damage: 500 + damage: 350 behaviors: #excess damage (nuke?). avoid computational cost of spawning entities. - !type:DoActsBehavior acts: [ "Destruction" ] @@ -193,7 +193,7 @@ thresholds: - trigger: !type:DamageTrigger - damage: 500 + damage: 250 behaviors: #excess damage (nuke?). avoid computational cost of spawning entities. - !type:DoActsBehavior acts: [ "Destruction" ] diff --git a/Resources/Prototypes/_Mono/Entities/Structures/walls.yml b/Resources/Prototypes/_Mono/Entities/Structures/walls.yml index 5767d4a9c9d..7c679fa93c2 100644 --- a/Resources/Prototypes/_Mono/Entities/Structures/walls.yml +++ b/Resources/Prototypes/_Mono/Entities/Structures/walls.yml @@ -18,7 +18,7 @@ thresholds: - trigger: !type:DamageTrigger - damage: 2000 + damage: 1100 behaviors: - !type:DoActsBehavior acts: [ "Destruction" ] @@ -270,7 +270,7 @@ thresholds: - trigger: !type:DamageTrigger - damage: 10000 # Mono - wall buff + damage: 10100 # Mono - wall buff behaviors: - !type:DoActsBehavior acts: [ "Destruction" ] @@ -308,6 +308,12 @@ components: - type: Destructible thresholds: + - trigger: + !type:DamageTrigger + damage: 15100 + behaviors: + - !type:DoActsBehavior + acts: [ "Destruction" ] - trigger: !type:DamageTrigger damage: 15000 @@ -335,6 +341,12 @@ components: - type: Destructible thresholds: + - trigger: + !type:DamageTrigger + damage: 10100 + behaviors: + - !type:DoActsBehavior + acts: [ "Destruction" ] - trigger: !type:DamageTrigger damage: 10000 @@ -401,6 +413,12 @@ density: 500 # No ram-meta - type: Destructible thresholds: + - trigger: + !type:DamageTrigger + damage: 2100 + behaviors: + - !type:DoActsBehavior + acts: [ "Destruction" ] - trigger: !type:DamageTrigger damage: 2000