Ambient music rules refactor (#29921)

* refactor

* dutypo
This commit is contained in:
Ed
2024-07-14 16:58:48 +03:00
committed by GitHub
parent 7d9653fff7
commit 1c74ffb8e4
13 changed files with 414 additions and 388 deletions

View File

@@ -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;

View File

@@ -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;

View File

@@ -0,0 +1,12 @@
namespace Content.Shared.Random.Rules;
/// <summary>
/// Always returns true. Used for fallbacks.
/// </summary>
public sealed partial class AlwaysTrueRule : RulesRule
{
public override bool Check(EntityManager entManager, EntityUid uid)
{
return !Inverted;
}
}

View File

@@ -0,0 +1,39 @@
using System.Numerics;
using Robust.Shared.Map;
namespace Content.Shared.Random.Rules;
/// <summary>
/// Returns true if on a grid or in range of one.
/// </summary>
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<SharedTransformSystem>();
var mapManager = IoCManager.Resolve<IMapManager>();
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;
}
}

View File

@@ -0,0 +1,18 @@
namespace Content.Shared.Random.Rules;
/// <summary>
/// Returns true if the attached entity is in space.
/// </summary>
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;
}
}

View File

@@ -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;
/// <summary>
/// Checks for an entity nearby with the specified access.
/// </summary>
public sealed partial class NearbyAccessRule : RulesRule
{
// This exists because of door electronics contained inside doors.
/// <summary>
/// Does the access entity need to be anchored.
/// </summary>
[DataField]
public bool Anchored = true;
/// <summary>
/// Count of entities that need to be nearby.
/// </summary>
[DataField]
public int Count = 1;
[DataField(required: true)]
public List<ProtoId<AccessLevelPrototype>> Access = new();
[DataField]
public float Range = 10f;
public override bool Check(EntityManager entManager, EntityUid uid)
{
var xformQuery = entManager.GetEntityQuery<TransformComponent>();
if (!xformQuery.TryGetComponent(uid, out var xform) ||
xform.MapUid == null)
{
return false;
}
var transform = entManager.System<SharedTransformSystem>();
var lookup = entManager.System<EntityLookupSystem>();
var reader = entManager.System<AccessReaderSystem>();
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<Entity<AccessReaderComponent>>();
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;
}
}

View File

@@ -0,0 +1,71 @@
using Robust.Shared.Prototypes;
namespace Content.Shared.Random.Rules;
public sealed partial class NearbyComponentsRule : RulesRule
{
/// <summary>
/// Does the entity need to be anchored.
/// </summary>
[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<Entity<IComponent>>();
var xformQuery = entManager.GetEntityQuery<TransformComponent>();
if (!xformQuery.TryGetComponent(uid, out var xform) ||
xform.MapUid == null)
{
return false;
}
var transform = entManager.System<SharedTransformSystem>();
var lookup = entManager.System<EntityLookupSystem>();
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;
}
}

View File

@@ -0,0 +1,58 @@
using Content.Shared.Whitelist;
namespace Content.Shared.Random.Rules;
/// <summary>
/// Checks for entities matching the whitelist in range.
/// This is more expensive than <see cref="NearbyComponentsRule"/> so prefer that!
/// </summary>
public sealed partial class NearbyEntitiesRule : RulesRule
{
/// <summary>
/// How many of the entity need to be nearby.
/// </summary>
[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<SharedTransformSystem>();
var lookup = entManager.System<EntityLookupSystem>();
var whitelistSystem = entManager.System<EntityWhitelistSystem>();
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;
}
}

View File

@@ -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
{
/// <summary>
/// If there are anchored entities on the tile do we ignore the tile.
/// </summary>
[DataField]
public bool IgnoreAnchored;
[DataField(required: true)]
public float Percent;
[DataField(required: true)]
public List<ProtoId<ContentTileDefinition>> Tiles = new();
[DataField]
public float Range = 10f;
public override bool Check(EntityManager entManager, EntityUid uid)
{
if (!entManager.TryGetComponent(uid, out TransformComponent? xform) ||
!entManager.TryGetComponent<MapGridComponent>(xform.GridUid, out var grid))
{
return false;
}
var transform = entManager.System<SharedTransformSystem>();
var tileDef = IoCManager.Resolve<ITileDefinitionManager>();
var physicsQuery = entManager.GetEntityQuery<PhysicsComponent>();
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;
}
}

View File

@@ -0,0 +1,19 @@
namespace Content.Shared.Random.Rules;
/// <summary>
/// Returns true if griduid and mapuid match (AKA on 'planet').
/// </summary>
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;
}
}

View File

@@ -0,0 +1,39 @@
using Robust.Shared.Prototypes;
namespace Content.Shared.Random.Rules;
/// <summary>
/// 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"
/// </summary>
[Prototype("rules")]
public sealed partial class RulesPrototype : IPrototype
{
[IdDataField] public string ID { get; } = string.Empty;
[DataField("rules", required: true)]
public List<RulesRule> 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;
}
}

View File

@@ -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;
/// <summary>
/// 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"
/// </summary>
[Prototype("rules")]
public sealed partial class RulesPrototype : IPrototype
{
[IdDataField] public string ID { get; } = string.Empty;
[DataField("rules", required: true)]
public List<RulesRule> Rules = new();
}
[ImplicitDataDefinitionForInheritors]
public abstract partial class RulesRule
{
}
/// <summary>
/// Returns true if the attached entity is in space.
/// </summary>
public sealed partial class InSpaceRule : RulesRule
{
}
/// <summary>
/// Checks for entities matching the whitelist in range.
/// This is more expensive than <see cref="NearbyComponentsRule"/> so prefer that!
/// </summary>
public sealed partial class NearbyEntitiesRule : RulesRule
{
/// <summary>
/// How many of the entity need to be nearby.
/// </summary>
[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
{
/// <summary>
/// If there are anchored entities on the tile do we ignore the tile.
/// </summary>
[DataField("ignoreAnchored")] public bool IgnoreAnchored;
[DataField("percent", required: true)]
public float Percent;
[DataField("tiles", required: true, customTypeSerializer:typeof(PrototypeIdListSerializer<ContentTileDefinition>))]
public List<string> Tiles = new();
[DataField("range")]
public float Range = 10f;
}
/// <summary>
/// Always returns true. Used for fallbacks.
/// </summary>
public sealed partial class AlwaysTrueRule : RulesRule
{
}
/// <summary>
/// Returns true if on a grid or in range of one.
/// </summary>
public sealed partial class GridInRangeRule : RulesRule
{
[DataField("range")]
public float Range = 10f;
[DataField("inverted")]
public bool Inverted = false;
}
/// <summary>
/// Returns true if griduid and mapuid match (AKA on 'planet').
/// </summary>
public sealed partial class OnMapGridRule : RulesRule
{
}
/// <summary>
/// Checks for an entity nearby with the specified access.
/// </summary>
public sealed partial class NearbyAccessRule : RulesRule
{
// This exists because of doorelectronics contained inside doors.
/// <summary>
/// Does the access entity need to be anchored.
/// </summary>
[DataField("anchored")]
public bool Anchored = true;
/// <summary>
/// Count of entities that need to be nearby.
/// </summary>
[DataField("count")]
public int Count = 1;
[DataField("access", required: true)]
public List<ProtoId<AccessLevelPrototype>> Access = new();
[DataField("range")]
public float Range = 10f;
}
public sealed partial class NearbyComponentsRule : RulesRule
{
/// <summary>
/// Does the entity need to be anchored.
/// </summary>
[DataField("anchored")]
public bool Anchored;
[DataField("count")] public int Count;
[DataField("components", required: true)]
public ComponentRegistry Components = default!;
[DataField("range")]
public float Range = 10f;
}

View File

@@ -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<Entity<IComponent>>();
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<TransformComponent>();
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<Entity<AccessReaderComponent>>();
_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<TransformComponent>();
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<MapGridComponent>(xform.GridUid, out var grid))
{
return false;
}
var physicsQuery = GetEntityQuery<PhysicsComponent>();
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;
}
}