diff --git a/Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs b/Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs
index 84b787a4ec..d60c978ccf 100644
--- a/Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs
+++ b/Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs
@@ -4,6 +4,7 @@ using Content.Shared.Audio;
using Content.Shared.CCVar;
using Content.Shared.GameTicking;
using Content.Shared.Random;
+using Content.Shared.Random.Rules;
using Robust.Client.GameObjects;
using Robust.Client.Player;
using Robust.Client.ResourceManagement;
diff --git a/Content.Shared/Audio/AmbientMusicPrototype.cs b/Content.Shared/Audio/AmbientMusicPrototype.cs
index 54f70f5728..219c41527d 100644
--- a/Content.Shared/Audio/AmbientMusicPrototype.cs
+++ b/Content.Shared/Audio/AmbientMusicPrototype.cs
@@ -1,4 +1,5 @@
using Content.Shared.Random;
+using Content.Shared.Random.Rules;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
diff --git a/Content.Shared/Random/Rules/AlwaysTrue.cs b/Content.Shared/Random/Rules/AlwaysTrue.cs
new file mode 100644
index 0000000000..98b81fae79
--- /dev/null
+++ b/Content.Shared/Random/Rules/AlwaysTrue.cs
@@ -0,0 +1,12 @@
+namespace Content.Shared.Random.Rules;
+
+///
+/// Always returns true. Used for fallbacks.
+///
+public sealed partial class AlwaysTrueRule : RulesRule
+{
+ public override bool Check(EntityManager entManager, EntityUid uid)
+ {
+ return !Inverted;
+ }
+}
diff --git a/Content.Shared/Random/Rules/GridInRange.cs b/Content.Shared/Random/Rules/GridInRange.cs
new file mode 100644
index 0000000000..8cbbef1cdc
--- /dev/null
+++ b/Content.Shared/Random/Rules/GridInRange.cs
@@ -0,0 +1,39 @@
+using System.Numerics;
+using Robust.Shared.Map;
+
+namespace Content.Shared.Random.Rules;
+
+///
+/// Returns true if on a grid or in range of one.
+///
+public sealed partial class GridInRangeRule : RulesRule
+{
+ [DataField]
+ public float Range = 10f;
+
+ public override bool Check(EntityManager entManager, EntityUid uid)
+ {
+ if (!entManager.TryGetComponent(uid, out TransformComponent? xform))
+ {
+ return false;
+ }
+
+ if (xform.GridUid != null)
+ {
+ return !Inverted;
+ }
+
+ var transform = entManager.System();
+ var mapManager = IoCManager.Resolve();
+
+ var worldPos = transform.GetWorldPosition(xform);
+ var gridRange = new Vector2(Range, Range);
+
+ foreach (var _ in mapManager.FindGridsIntersecting(xform.MapID, new Box2(worldPos - gridRange, worldPos + gridRange)))
+ {
+ return !Inverted;
+ }
+
+ return false;
+ }
+}
diff --git a/Content.Shared/Random/Rules/InSpace.cs b/Content.Shared/Random/Rules/InSpace.cs
new file mode 100644
index 0000000000..8163e1f6be
--- /dev/null
+++ b/Content.Shared/Random/Rules/InSpace.cs
@@ -0,0 +1,18 @@
+namespace Content.Shared.Random.Rules;
+
+///
+/// Returns true if the attached entity is in space.
+///
+public sealed partial class InSpaceRule : RulesRule
+{
+ public override bool Check(EntityManager entManager, EntityUid uid)
+ {
+ if (!entManager.TryGetComponent(uid, out TransformComponent? xform) ||
+ xform.GridUid != null)
+ {
+ return Inverted;
+ }
+
+ return !Inverted;
+ }
+}
diff --git a/Content.Shared/Random/Rules/NearbyAccess.cs b/Content.Shared/Random/Rules/NearbyAccess.cs
new file mode 100644
index 0000000000..2a8ad077c6
--- /dev/null
+++ b/Content.Shared/Random/Rules/NearbyAccess.cs
@@ -0,0 +1,77 @@
+using Content.Shared.Access;
+using Content.Shared.Access.Components;
+using Content.Shared.Access.Systems;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Random.Rules;
+
+///
+/// Checks for an entity nearby with the specified access.
+///
+public sealed partial class NearbyAccessRule : RulesRule
+{
+ // This exists because of door electronics contained inside doors.
+ ///
+ /// Does the access entity need to be anchored.
+ ///
+ [DataField]
+ public bool Anchored = true;
+
+ ///
+ /// Count of entities that need to be nearby.
+ ///
+ [DataField]
+ public int Count = 1;
+
+ [DataField(required: true)]
+ public List> Access = new();
+
+ [DataField]
+ public float Range = 10f;
+
+ public override bool Check(EntityManager entManager, EntityUid uid)
+ {
+ var xformQuery = entManager.GetEntityQuery();
+
+ if (!xformQuery.TryGetComponent(uid, out var xform) ||
+ xform.MapUid == null)
+ {
+ return false;
+ }
+
+ var transform = entManager.System();
+ var lookup = entManager.System();
+ var reader = entManager.System();
+
+ var found = false;
+ var worldPos = transform.GetWorldPosition(xform, xformQuery);
+ var count = 0;
+
+ // TODO: Update this when we get the callback version
+ var entities = new HashSet>();
+ lookup.GetEntitiesInRange(xform.MapID, worldPos, Range, entities);
+ foreach (var comp in entities)
+ {
+ if (!reader.AreAccessTagsAllowed(Access, comp) ||
+ Anchored &&
+ (!xformQuery.TryGetComponent(comp, out var compXform) ||
+ !compXform.Anchored))
+ {
+ continue;
+ }
+
+ count++;
+
+ if (count < Count)
+ continue;
+
+ found = true;
+ break;
+ }
+
+ if (!found)
+ return Inverted;
+
+ return !Inverted;
+ }
+}
diff --git a/Content.Shared/Random/Rules/NearbyComponents.cs b/Content.Shared/Random/Rules/NearbyComponents.cs
new file mode 100644
index 0000000000..13108e88d6
--- /dev/null
+++ b/Content.Shared/Random/Rules/NearbyComponents.cs
@@ -0,0 +1,71 @@
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Random.Rules;
+
+public sealed partial class NearbyComponentsRule : RulesRule
+{
+ ///
+ /// Does the entity need to be anchored.
+ ///
+ [DataField]
+ public bool Anchored;
+
+ [DataField]
+ public int Count;
+
+ [DataField(required: true)]
+ public ComponentRegistry Components = default!;
+
+ [DataField]
+ public float Range = 10f;
+
+ public override bool Check(EntityManager entManager, EntityUid uid)
+ {
+ var inRange = new HashSet>();
+ var xformQuery = entManager.GetEntityQuery();
+
+ if (!xformQuery.TryGetComponent(uid, out var xform) ||
+ xform.MapUid == null)
+ {
+ return false;
+ }
+
+ var transform = entManager.System();
+ var lookup = entManager.System();
+
+ var found = false;
+ var worldPos = transform.GetWorldPosition(xform);
+ var count = 0;
+
+ foreach (var compType in Components.Values)
+ {
+ inRange.Clear();
+ lookup.GetEntitiesInRange(compType.Component.GetType(), xform.MapID, worldPos, Range, inRange);
+ foreach (var comp in inRange)
+ {
+ if (Anchored &&
+ (!xformQuery.TryGetComponent(comp, out var compXform) ||
+ !compXform.Anchored))
+ {
+ continue;
+ }
+
+ count++;
+
+ if (count < Count)
+ continue;
+
+ found = true;
+ break;
+ }
+
+ if (found)
+ break;
+ }
+
+ if (!found)
+ return Inverted;
+
+ return !Inverted;
+ }
+}
diff --git a/Content.Shared/Random/Rules/NearbyEntities.cs b/Content.Shared/Random/Rules/NearbyEntities.cs
new file mode 100644
index 0000000000..0754750bc4
--- /dev/null
+++ b/Content.Shared/Random/Rules/NearbyEntities.cs
@@ -0,0 +1,58 @@
+using Content.Shared.Whitelist;
+
+namespace Content.Shared.Random.Rules;
+
+///
+/// Checks for entities matching the whitelist in range.
+/// This is more expensive than so prefer that!
+///
+public sealed partial class NearbyEntitiesRule : RulesRule
+{
+ ///
+ /// How many of the entity need to be nearby.
+ ///
+ [DataField]
+ public int Count = 1;
+
+ [DataField(required: true)]
+ public EntityWhitelist Whitelist = new();
+
+ [DataField]
+ public float Range = 10f;
+
+ public override bool Check(EntityManager entManager, EntityUid uid)
+ {
+ if (!entManager.TryGetComponent(uid, out TransformComponent? xform) ||
+ xform.MapUid == null)
+ {
+ return false;
+ }
+
+ var transform = entManager.System();
+ var lookup = entManager.System();
+ var whitelistSystem = entManager.System();
+
+ var found = false;
+ var worldPos = transform.GetWorldPosition(xform);
+ var count = 0;
+
+ foreach (var ent in lookup.GetEntitiesInRange(xform.MapID, worldPos, Range))
+ {
+ if (whitelistSystem.IsWhitelistFail(Whitelist, ent))
+ continue;
+
+ count++;
+
+ if (count < Count)
+ continue;
+
+ found = true;
+ break;
+ }
+
+ if (!found)
+ return Inverted;
+
+ return !Inverted;
+ }
+}
diff --git a/Content.Shared/Random/Rules/NearbyTilesPercent.cs b/Content.Shared/Random/Rules/NearbyTilesPercent.cs
new file mode 100644
index 0000000000..465ac8dc5c
--- /dev/null
+++ b/Content.Shared/Random/Rules/NearbyTilesPercent.cs
@@ -0,0 +1,79 @@
+using Content.Shared.Maps;
+using Robust.Shared.Map;
+using Robust.Shared.Map.Components;
+using Robust.Shared.Physics.Components;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Random.Rules;
+
+public sealed partial class NearbyTilesPercentRule : RulesRule
+{
+ ///
+ /// If there are anchored entities on the tile do we ignore the tile.
+ ///
+ [DataField]
+ public bool IgnoreAnchored;
+
+ [DataField(required: true)]
+ public float Percent;
+
+ [DataField(required: true)]
+ public List> Tiles = new();
+
+ [DataField]
+ public float Range = 10f;
+
+ public override bool Check(EntityManager entManager, EntityUid uid)
+ {
+ if (!entManager.TryGetComponent(uid, out TransformComponent? xform) ||
+ !entManager.TryGetComponent(xform.GridUid, out var grid))
+ {
+ return false;
+ }
+
+ var transform = entManager.System();
+ var tileDef = IoCManager.Resolve();
+
+ var physicsQuery = entManager.GetEntityQuery();
+ var tileCount = 0;
+ var matchingTileCount = 0;
+
+ foreach (var tile in grid.GetTilesIntersecting(new Circle(transform.GetWorldPosition(xform),
+ Range)))
+ {
+ // Only consider collidable anchored (for reasons some subfloor stuff has physics but non-collidable)
+ if (IgnoreAnchored)
+ {
+ var gridEnum = grid.GetAnchoredEntitiesEnumerator(tile.GridIndices);
+ var found = false;
+
+ while (gridEnum.MoveNext(out var ancUid))
+ {
+ if (!physicsQuery.TryGetComponent(ancUid, out var physics) ||
+ !physics.CanCollide)
+ {
+ continue;
+ }
+
+ found = true;
+ break;
+ }
+
+ if (found)
+ continue;
+ }
+
+ tileCount++;
+
+ if (!Tiles.Contains(tileDef[tile.Tile.TypeId].ID))
+ continue;
+
+ matchingTileCount++;
+ }
+
+ if (tileCount == 0 || matchingTileCount / (float) tileCount < Percent)
+ return Inverted;
+
+ return !Inverted;
+ }
+}
diff --git a/Content.Shared/Random/Rules/OnMapGrid.cs b/Content.Shared/Random/Rules/OnMapGrid.cs
new file mode 100644
index 0000000000..bea841872e
--- /dev/null
+++ b/Content.Shared/Random/Rules/OnMapGrid.cs
@@ -0,0 +1,19 @@
+namespace Content.Shared.Random.Rules;
+
+///
+/// Returns true if griduid and mapuid match (AKA on 'planet').
+///
+public sealed partial class OnMapGridRule : RulesRule
+{
+ public override bool Check(EntityManager entManager, EntityUid uid)
+ {
+ if (!entManager.TryGetComponent(uid, out TransformComponent? xform) ||
+ xform.GridUid != xform.MapUid ||
+ xform.MapUid == null)
+ {
+ return Inverted;
+ }
+
+ return !Inverted;
+ }
+}
diff --git a/Content.Shared/Random/Rules/RulesSystem.cs b/Content.Shared/Random/Rules/RulesSystem.cs
new file mode 100644
index 0000000000..1957beab51
--- /dev/null
+++ b/Content.Shared/Random/Rules/RulesSystem.cs
@@ -0,0 +1,39 @@
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Random.Rules;
+
+///
+/// Rules-based item selection. Can be used for any sort of conditional selection
+/// Every single condition needs to be true for this to be selected.
+/// e.g. "choose maintenance audio if 90% of tiles nearby are maintenance tiles"
+///
+[Prototype("rules")]
+public sealed partial class RulesPrototype : IPrototype
+{
+ [IdDataField] public string ID { get; } = string.Empty;
+
+ [DataField("rules", required: true)]
+ public List Rules = new();
+}
+
+[ImplicitDataDefinitionForInheritors]
+public abstract partial class RulesRule
+{
+ [DataField]
+ public bool Inverted;
+ public abstract bool Check(EntityManager entManager, EntityUid uid);
+}
+
+public sealed class RulesSystem : EntitySystem
+{
+ public bool IsTrue(EntityUid uid, RulesPrototype rules)
+ {
+ foreach (var rule in rules.Rules)
+ {
+ if (!rule.Check(EntityManager, uid))
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/Content.Shared/Random/RulesPrototype.cs b/Content.Shared/Random/RulesPrototype.cs
deleted file mode 100644
index 20961af8e7..0000000000
--- a/Content.Shared/Random/RulesPrototype.cs
+++ /dev/null
@@ -1,141 +0,0 @@
-using Content.Shared.Access;
-using Content.Shared.Maps;
-using Content.Shared.Whitelist;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
-
-namespace Content.Shared.Random;
-
-///
-/// Rules-based item selection. Can be used for any sort of conditional selection
-/// Every single condition needs to be true for this to be selected.
-/// e.g. "choose maintenance audio if 90% of tiles nearby are maintenance tiles"
-///
-[Prototype("rules")]
-public sealed partial class RulesPrototype : IPrototype
-{
- [IdDataField] public string ID { get; } = string.Empty;
-
- [DataField("rules", required: true)]
- public List Rules = new();
-}
-
-[ImplicitDataDefinitionForInheritors]
-public abstract partial class RulesRule
-{
-
-}
-
-///
-/// Returns true if the attached entity is in space.
-///
-public sealed partial class InSpaceRule : RulesRule
-{
-
-}
-
-///
-/// Checks for entities matching the whitelist in range.
-/// This is more expensive than so prefer that!
-///
-public sealed partial class NearbyEntitiesRule : RulesRule
-{
- ///
- /// How many of the entity need to be nearby.
- ///
- [DataField("count")]
- public int Count = 1;
-
- [DataField("whitelist", required: true)]
- public EntityWhitelist Whitelist = new();
-
- [DataField("range")]
- public float Range = 10f;
-}
-
-public sealed partial class NearbyTilesPercentRule : RulesRule
-{
- ///
- /// If there are anchored entities on the tile do we ignore the tile.
- ///
- [DataField("ignoreAnchored")] public bool IgnoreAnchored;
-
- [DataField("percent", required: true)]
- public float Percent;
-
- [DataField("tiles", required: true, customTypeSerializer:typeof(PrototypeIdListSerializer))]
- public List Tiles = new();
-
- [DataField("range")]
- public float Range = 10f;
-}
-
-///
-/// Always returns true. Used for fallbacks.
-///
-public sealed partial class AlwaysTrueRule : RulesRule
-{
-
-}
-
-///
-/// Returns true if on a grid or in range of one.
-///
-public sealed partial class GridInRangeRule : RulesRule
-{
- [DataField("range")]
- public float Range = 10f;
-
- [DataField("inverted")]
- public bool Inverted = false;
-}
-
-///
-/// Returns true if griduid and mapuid match (AKA on 'planet').
-///
-public sealed partial class OnMapGridRule : RulesRule
-{
-
-}
-
-///
-/// Checks for an entity nearby with the specified access.
-///
-public sealed partial class NearbyAccessRule : RulesRule
-{
- // This exists because of doorelectronics contained inside doors.
- ///
- /// Does the access entity need to be anchored.
- ///
- [DataField("anchored")]
- public bool Anchored = true;
-
- ///
- /// Count of entities that need to be nearby.
- ///
- [DataField("count")]
- public int Count = 1;
-
- [DataField("access", required: true)]
- public List> Access = new();
-
- [DataField("range")]
- public float Range = 10f;
-}
-
-public sealed partial class NearbyComponentsRule : RulesRule
-{
- ///
- /// Does the entity need to be anchored.
- ///
- [DataField("anchored")]
- public bool Anchored;
-
- [DataField("count")] public int Count;
-
- [DataField("components", required: true)]
- public ComponentRegistry Components = default!;
-
- [DataField("range")]
- public float Range = 10f;
-}
diff --git a/Content.Shared/Random/RulesSystem.cs b/Content.Shared/Random/RulesSystem.cs
deleted file mode 100644
index 58d67c268e..0000000000
--- a/Content.Shared/Random/RulesSystem.cs
+++ /dev/null
@@ -1,247 +0,0 @@
-using System.Numerics;
-using Content.Shared.Access.Components;
-using Content.Shared.Access.Systems;
-using Content.Shared.Whitelist;
-using Robust.Shared.Map;
-using Robust.Shared.Map.Components;
-using Robust.Shared.Physics.Components;
-
-namespace Content.Shared.Random;
-
-public sealed class RulesSystem : EntitySystem
-{
- [Dependency] private readonly IMapManager _mapManager = default!;
- [Dependency] private readonly ITileDefinitionManager _tileDef = default!;
- [Dependency] private readonly AccessReaderSystem _reader = default!;
- [Dependency] private readonly EntityLookupSystem _lookup = default!;
- [Dependency] private readonly SharedTransformSystem _transform = default!;
- [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
- public bool IsTrue(EntityUid uid, RulesPrototype rules)
- {
- var inRange = new HashSet>();
- foreach (var rule in rules.Rules)
- {
- switch (rule)
- {
- case AlwaysTrueRule:
- break;
- case GridInRangeRule griddy:
- {
- if (!TryComp(uid, out TransformComponent? xform))
- {
- return false;
- }
-
- if (xform.GridUid != null)
- {
- return !griddy.Inverted;
- }
-
- var worldPos = _transform.GetWorldPosition(xform);
- var gridRange = new Vector2(griddy.Range, griddy.Range);
-
- foreach (var _ in _mapManager.FindGridsIntersecting(
- xform.MapID,
- new Box2(worldPos - gridRange, worldPos + gridRange)))
- {
- return !griddy.Inverted;
- }
-
- break;
- }
- case InSpaceRule:
- {
- if (!TryComp(uid, out TransformComponent? xform) ||
- xform.GridUid != null)
- {
- return false;
- }
-
- break;
- }
- case NearbyAccessRule access:
- {
- var xformQuery = GetEntityQuery();
-
- if (!xformQuery.TryGetComponent(uid, out var xform) ||
- xform.MapUid == null)
- {
- return false;
- }
-
- var found = false;
- var worldPos = _transform.GetWorldPosition(xform, xformQuery);
- var count = 0;
-
- // TODO: Update this when we get the callback version
- var entities = new HashSet>();
- _lookup.GetEntitiesInRange(xform.MapID, worldPos, access.Range, entities);
- foreach (var comp in entities)
- {
- if (!_reader.AreAccessTagsAllowed(access.Access, comp) ||
- access.Anchored &&
- (!xformQuery.TryGetComponent(comp, out var compXform) ||
- !compXform.Anchored))
- {
- continue;
- }
-
- count++;
-
- if (count < access.Count)
- continue;
-
- found = true;
- break;
- }
-
- if (!found)
- return false;
-
- break;
- }
- case NearbyComponentsRule nearbyComps:
- {
- var xformQuery = GetEntityQuery();
-
- if (!xformQuery.TryGetComponent(uid, out var xform) ||
- xform.MapUid == null)
- {
- return false;
- }
-
- var found = false;
- var worldPos = _transform.GetWorldPosition(xform);
- var count = 0;
-
- foreach (var compType in nearbyComps.Components.Values)
- {
- inRange.Clear();
- _lookup.GetEntitiesInRange(compType.Component.GetType(), xform.MapID, worldPos, nearbyComps.Range, inRange);
- foreach (var comp in inRange)
- {
- if (nearbyComps.Anchored &&
- (!xformQuery.TryGetComponent(comp, out var compXform) ||
- !compXform.Anchored))
- {
- continue;
- }
-
- count++;
-
- if (count < nearbyComps.Count)
- continue;
-
- found = true;
- break;
- }
-
- if (found)
- break;
- }
-
- if (!found)
- return false;
-
- break;
- }
- case NearbyEntitiesRule entity:
- {
- if (!TryComp(uid, out TransformComponent? xform) ||
- xform.MapUid == null)
- {
- return false;
- }
-
- var found = false;
- var worldPos = _transform.GetWorldPosition(xform);
- var count = 0;
-
- foreach (var ent in _lookup.GetEntitiesInRange(xform.MapID, worldPos, entity.Range))
- {
- if (_whitelistSystem.IsWhitelistFail(entity.Whitelist, ent))
- continue;
-
- count++;
-
- if (count < entity.Count)
- continue;
-
- found = true;
- break;
- }
-
- if (!found)
- return false;
-
- break;
- }
- case NearbyTilesPercentRule tiles:
- {
- if (!TryComp(uid, out TransformComponent? xform) ||
- !TryComp(xform.GridUid, out var grid))
- {
- return false;
- }
-
- var physicsQuery = GetEntityQuery();
- var tileCount = 0;
- var matchingTileCount = 0;
-
- foreach (var tile in grid.GetTilesIntersecting(new Circle(_transform.GetWorldPosition(xform),
- tiles.Range)))
- {
- // Only consider collidable anchored (for reasons some subfloor stuff has physics but non-collidable)
- if (tiles.IgnoreAnchored)
- {
- var gridEnum = grid.GetAnchoredEntitiesEnumerator(tile.GridIndices);
- var found = false;
-
- while (gridEnum.MoveNext(out var ancUid))
- {
- if (!physicsQuery.TryGetComponent(ancUid, out var physics) ||
- !physics.CanCollide)
- {
- continue;
- }
-
- found = true;
- break;
- }
-
- if (found)
- continue;
- }
-
- tileCount++;
-
- if (!tiles.Tiles.Contains(_tileDef[tile.Tile.TypeId].ID))
- continue;
-
- matchingTileCount++;
- }
-
- if (tileCount == 0 || matchingTileCount / (float) tileCount < tiles.Percent)
- return false;
-
- break;
- }
- case OnMapGridRule:
- {
- if (!TryComp(uid, out TransformComponent? xform) ||
- xform.GridUid != xform.MapUid ||
- xform.MapUid == null)
- {
- return false;
- }
-
- break;
- }
- default:
- throw new NotImplementedException();
- }
- }
-
- return true;
- }
-}