From 3ed206887e4a07f1d44a5c1361445d6eac86c111 Mon Sep 17 00:00:00 2001 From: slarticodefast <161409025+slarticodefast@users.noreply.github.com> Date: Mon, 13 Oct 2025 17:41:34 +0200 Subject: [PATCH] Predict DestructibleSystem, Part 1: IThresholdTrigger (#40876) part 1 --- .../DestructibleDamageGroupTest.cs | 8 +- .../DestructibleDamageTypeTest.cs | 6 +- .../DestructibleDestructionTest.cs | 3 +- .../DestructibleThresholdActivationTest.cs | 30 ++-- .../Destructible/DestructibleSystem.cs | 75 ++++++++-- .../Destructible/Thresholds/ActsFlags.cs | 4 - .../Thresholds/Behaviors/DoActsBehavior.cs | 4 +- .../Thresholds/DamageThreshold.cs | 131 ++++++------------ .../Destructible/Thresholds/ThresholdActs.cs | 13 -- .../Thresholds/Triggers/AndTrigger.cs | 28 ---- .../Thresholds/Triggers/DamageGroupTrigger.cs | 29 ---- .../Thresholds/Triggers/DamageTrigger.cs | 24 ---- .../Thresholds/Triggers/DamageTypeTrigger.cs | 27 ---- .../Thresholds/Triggers/IThresholdTrigger.cs | 18 --- .../Thresholds/Triggers/OrTrigger.cs | 28 ---- Content.Shared/Destructible/ThresholdActs.cs | 12 ++ .../Destructible/Triggers/AndTrigger.cs | 28 ++++ .../Triggers/DamageGroupTrigger.cs | 33 +++++ .../Destructible/Triggers/DamageTrigger.cs | 25 ++++ .../Triggers/DamageTypeTrigger.cs | 34 +++++ .../Triggers/IThresholdTrigger.cs | 27 ++++ .../Destructible/Triggers/OrTrigger.cs | 28 ++++ .../DamageGroupEntityConditionSystem.cs | 38 +++++ .../DamageTypeEntityConditionSystem.cs | 38 +++++ .../guidebook/entity-effects/conditions.ftl | 18 +++ 25 files changed, 410 insertions(+), 299 deletions(-) delete mode 100644 Content.Server/Destructible/Thresholds/ActsFlags.cs delete mode 100644 Content.Server/Destructible/Thresholds/ThresholdActs.cs delete mode 100644 Content.Server/Destructible/Thresholds/Triggers/AndTrigger.cs delete mode 100644 Content.Server/Destructible/Thresholds/Triggers/DamageGroupTrigger.cs delete mode 100644 Content.Server/Destructible/Thresholds/Triggers/DamageTrigger.cs delete mode 100644 Content.Server/Destructible/Thresholds/Triggers/DamageTypeTrigger.cs delete mode 100644 Content.Server/Destructible/Thresholds/Triggers/IThresholdTrigger.cs delete mode 100644 Content.Server/Destructible/Thresholds/Triggers/OrTrigger.cs create mode 100644 Content.Shared/Destructible/ThresholdActs.cs create mode 100644 Content.Shared/Destructible/Triggers/AndTrigger.cs create mode 100644 Content.Shared/Destructible/Triggers/DamageGroupTrigger.cs create mode 100644 Content.Shared/Destructible/Triggers/DamageTrigger.cs create mode 100644 Content.Shared/Destructible/Triggers/DamageTypeTrigger.cs create mode 100644 Content.Shared/Destructible/Triggers/IThresholdTrigger.cs create mode 100644 Content.Shared/Destructible/Triggers/OrTrigger.cs create mode 100644 Content.Shared/EntityConditions/Conditions/DamageGroupEntityConditionSystem.cs create mode 100644 Content.Shared/EntityConditions/Conditions/DamageTypeEntityConditionSystem.cs diff --git a/Content.IntegrationTests/Tests/Destructible/DestructibleDamageGroupTest.cs b/Content.IntegrationTests/Tests/Destructible/DestructibleDamageGroupTest.cs index 0da07ad5a1..3650fd69d7 100644 --- a/Content.IntegrationTests/Tests/Destructible/DestructibleDamageGroupTest.cs +++ b/Content.IntegrationTests/Tests/Destructible/DestructibleDamageGroupTest.cs @@ -1,10 +1,8 @@ -using Content.Server.Destructible.Thresholds.Triggers; using Content.Shared.Damage; using Content.Shared.Damage.Prototypes; +using Content.Shared.Destructible.Thresholds.Triggers; using Content.Shared.FixedPoint; using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Map; using Robust.Shared.Prototypes; using static Content.IntegrationTests.Tests.Destructible.DestructibleTestPrototypes; @@ -91,7 +89,7 @@ namespace Content.IntegrationTests.Tests.Destructible Assert.That(threshold.Trigger, Is.InstanceOf()); }); - var trigger = (AndTrigger) threshold.Trigger; + var trigger = (AndTrigger)threshold.Trigger; Assert.Multiple(() => { @@ -162,7 +160,7 @@ namespace Content.IntegrationTests.Tests.Destructible Assert.That(threshold.Trigger, Is.InstanceOf()); }); - trigger = (AndTrigger) threshold.Trigger; + trigger = (AndTrigger)threshold.Trigger; Assert.Multiple(() => { diff --git a/Content.IntegrationTests/Tests/Destructible/DestructibleDamageTypeTest.cs b/Content.IntegrationTests/Tests/Destructible/DestructibleDamageTypeTest.cs index ccd459668b..4b1dc1ab3b 100644 --- a/Content.IntegrationTests/Tests/Destructible/DestructibleDamageTypeTest.cs +++ b/Content.IntegrationTests/Tests/Destructible/DestructibleDamageTypeTest.cs @@ -1,6 +1,6 @@ -using Content.Server.Destructible.Thresholds.Triggers; using Content.Shared.Damage; using Content.Shared.Damage.Prototypes; +using Content.Shared.Destructible.Thresholds.Triggers; using Robust.Shared.GameObjects; using Robust.Shared.Prototypes; using static Content.IntegrationTests.Tests.Destructible.DestructibleTestPrototypes; @@ -86,7 +86,7 @@ namespace Content.IntegrationTests.Tests.Destructible Assert.That(threshold.Trigger, Is.InstanceOf()); }); - var trigger = (AndTrigger) threshold.Trigger; + var trigger = (AndTrigger)threshold.Trigger; Assert.Multiple(() => { @@ -154,7 +154,7 @@ namespace Content.IntegrationTests.Tests.Destructible Assert.That(threshold.Trigger, Is.InstanceOf()); }); - trigger = (AndTrigger) threshold.Trigger; + trigger = (AndTrigger)threshold.Trigger; Assert.Multiple(() => { diff --git a/Content.IntegrationTests/Tests/Destructible/DestructibleDestructionTest.cs b/Content.IntegrationTests/Tests/Destructible/DestructibleDestructionTest.cs index 4e169e7dfa..b1342eeafe 100644 --- a/Content.IntegrationTests/Tests/Destructible/DestructibleDestructionTest.cs +++ b/Content.IntegrationTests/Tests/Destructible/DestructibleDestructionTest.cs @@ -1,5 +1,4 @@ using System.Linq; -using Content.Server.Destructible.Thresholds; using Content.Server.Destructible.Thresholds.Behaviors; using Content.Shared.Damage; using Content.Shared.Damage.Prototypes; @@ -59,7 +58,7 @@ namespace Content.IntegrationTests.Tests.Destructible Assert.That(threshold.Behaviors, Has.Count.EqualTo(3)); }); - var spawnEntitiesBehavior = (SpawnEntitiesBehavior) threshold.Behaviors.Single(b => b is SpawnEntitiesBehavior); + var spawnEntitiesBehavior = (SpawnEntitiesBehavior)threshold.Behaviors.Single(b => b is SpawnEntitiesBehavior); Assert.Multiple(() => { diff --git a/Content.IntegrationTests/Tests/Destructible/DestructibleThresholdActivationTest.cs b/Content.IntegrationTests/Tests/Destructible/DestructibleThresholdActivationTest.cs index af86e406ef..fb3c40ec3c 100644 --- a/Content.IntegrationTests/Tests/Destructible/DestructibleThresholdActivationTest.cs +++ b/Content.IntegrationTests/Tests/Destructible/DestructibleThresholdActivationTest.cs @@ -2,12 +2,12 @@ using System.Linq; using Content.Server.Destructible; using Content.Server.Destructible.Thresholds; using Content.Server.Destructible.Thresholds.Behaviors; -using Content.Server.Destructible.Thresholds.Triggers; using Content.Shared.Damage; using Content.Shared.Damage.Prototypes; using Content.Shared.FixedPoint; -using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; +using Content.Shared.Destructible; +using Content.Shared.Destructible.Thresholds.Triggers; using Robust.Shared.GameObjects; using Robust.Shared.Prototypes; using static Content.IntegrationTests.Tests.Destructible.DestructibleTestPrototypes; @@ -99,9 +99,9 @@ namespace Content.IntegrationTests.Tests.Destructible // Check that it matches the YAML prototype Assert.That(threshold.Behaviors, Has.Count.EqualTo(3)); - var soundThreshold = (PlaySoundBehavior) threshold.Behaviors[0]; - var spawnThreshold = (SpawnEntitiesBehavior) threshold.Behaviors[1]; - var actsThreshold = (DoActsBehavior) threshold.Behaviors[2]; + var soundThreshold = (PlaySoundBehavior)threshold.Behaviors[0]; + var spawnThreshold = (SpawnEntitiesBehavior)threshold.Behaviors[1]; + var actsThreshold = (DoActsBehavior)threshold.Behaviors[2]; Assert.Multiple(() => { @@ -164,9 +164,9 @@ namespace Content.IntegrationTests.Tests.Destructible // Check that it matches the YAML prototype Assert.That(threshold.Behaviors, Has.Count.EqualTo(3)); - soundThreshold = (PlaySoundBehavior) threshold.Behaviors[0]; - spawnThreshold = (SpawnEntitiesBehavior) threshold.Behaviors[1]; - actsThreshold = (DoActsBehavior) threshold.Behaviors[2]; + soundThreshold = (PlaySoundBehavior)threshold.Behaviors[0]; + spawnThreshold = (SpawnEntitiesBehavior)threshold.Behaviors[1]; + actsThreshold = (DoActsBehavior)threshold.Behaviors[2]; // Check that it matches the YAML prototype Assert.Multiple(() => @@ -201,11 +201,11 @@ namespace Content.IntegrationTests.Tests.Destructible // Verify the first one, should be the lowest one (20) msg = sTestThresholdListenerSystem.ThresholdsReached[0]; - var trigger = (DamageTrigger) msg.Threshold.Trigger; + var trigger = (DamageTrigger)msg.Threshold.Trigger; Assert.Multiple(() => { Assert.That(trigger, Is.Not.Null); - Assert.That(trigger.Damage, Is.EqualTo(20)); + Assert.That(trigger.Damage, Is.EqualTo(FixedPoint2.New(20))); }); threshold = msg.Threshold; @@ -215,20 +215,20 @@ namespace Content.IntegrationTests.Tests.Destructible // Verify the second one, should be the highest one (50) msg = sTestThresholdListenerSystem.ThresholdsReached[1]; - trigger = (DamageTrigger) msg.Threshold.Trigger; + trigger = (DamageTrigger)msg.Threshold.Trigger; Assert.Multiple(() => { Assert.That(trigger, Is.Not.Null); - Assert.That(trigger.Damage, Is.EqualTo(50)); + Assert.That(trigger.Damage, Is.EqualTo(FixedPoint2.New(50))); }); threshold = msg.Threshold; Assert.That(threshold.Behaviors, Has.Count.EqualTo(3)); - soundThreshold = (PlaySoundBehavior) threshold.Behaviors[0]; - spawnThreshold = (SpawnEntitiesBehavior) threshold.Behaviors[1]; - actsThreshold = (DoActsBehavior) threshold.Behaviors[2]; + soundThreshold = (PlaySoundBehavior)threshold.Behaviors[0]; + spawnThreshold = (SpawnEntitiesBehavior)threshold.Behaviors[1]; + actsThreshold = (DoActsBehavior)threshold.Behaviors[2]; // Check that it matches the YAML prototype Assert.Multiple(() => diff --git a/Content.Server/Destructible/DestructibleSystem.cs b/Content.Server/Destructible/DestructibleSystem.cs index b4a79c6830..682baa04ca 100644 --- a/Content.Server/Destructible/DestructibleSystem.cs +++ b/Content.Server/Destructible/DestructibleSystem.cs @@ -6,7 +6,6 @@ using Content.Server.Body.Systems; using Content.Server.Construction; using Content.Server.Destructible.Thresholds; using Content.Server.Destructible.Thresholds.Behaviors; -using Content.Server.Destructible.Thresholds.Triggers; using Content.Server.Explosion.EntitySystems; using Content.Server.Fluids.EntitySystems; using Content.Server.Stack; @@ -14,6 +13,7 @@ using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Damage; using Content.Shared.Database; using Content.Shared.Destructible; +using Content.Shared.Destructible.Thresholds.Triggers; using Content.Shared.FixedPoint; using Content.Shared.Humanoid; using Content.Shared.Trigger.Systems; @@ -42,24 +42,24 @@ namespace Content.Server.Destructible [Dependency] public readonly PuddleSystem PuddleSystem = default!; [Dependency] public readonly SharedContainerSystem ContainerSystem = default!; [Dependency] public readonly IPrototypeManager PrototypeManager = default!; - [Dependency] public readonly IAdminLogManager _adminLogger = default!; + [Dependency] public readonly IAdminLogManager AdminLogger = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(Execute); + SubscribeLocalEvent(OnDamageChanged); } /// - /// Check if any thresholds were reached. if they were, execute them. + /// Check if any thresholds were reached. if they were, execute them. /// - public void Execute(EntityUid uid, DestructibleComponent component, DamageChangedEvent args) + private void OnDamageChanged(EntityUid uid, DestructibleComponent component, DamageChangedEvent args) { component.IsBroken = false; foreach (var threshold in component.Thresholds) { - if (threshold.Reached(args.Damageable, this)) + if (Triggered(threshold, (uid, args.Damageable))) { RaiseLocalEvent(uid, new DamageThresholdReached(component, threshold), true); @@ -82,18 +82,18 @@ namespace Content.Server.Destructible if (args.Origin != null) { - _adminLogger.Add(LogType.Damaged, + AdminLogger.Add(LogType.Damaged, logImpact, $"{ToPrettyString(args.Origin.Value):actor} caused {ToPrettyString(uid):subject} to trigger [{triggeredBehaviors}]"); } else { - _adminLogger.Add(LogType.Damaged, + AdminLogger.Add(LogType.Damaged, logImpact, $"Unknown damage source caused {ToPrettyString(uid):subject} to trigger [{triggeredBehaviors}]"); } - threshold.Execute(uid, this, EntityManager, args.Origin); + Execute(threshold, uid, args.Origin); } if (threshold.OldTriggered) @@ -108,6 +108,61 @@ namespace Content.Server.Destructible } } + /// + /// Check if the given threshold should trigger. + /// + public bool Triggered(DamageThreshold threshold, Entity owner) + { + if (threshold.Trigger == null) + return false; + + if (threshold.Triggered && threshold.TriggersOnce) + return false; + + if (threshold.OldTriggered) + { + threshold.OldTriggered = threshold.Trigger.Reached(owner, this); + return false; + } + + if (!threshold.Trigger.Reached(owner, this)) + return false; + + threshold.OldTriggered = true; + return true; + } + + /// + /// Check if the conditions for the given threshold are currently true. + /// + public bool Reached(DamageThreshold threshold, Entity owner) + { + if (threshold.Trigger == null) + return false; + + return threshold.Trigger.Reached(owner, this); + } + + /// + /// Triggers this threshold. + /// + /// The entity that owns this threshold. + /// The entity that caused this threshold to trigger. + public void Execute(DamageThreshold threshold, EntityUid owner, EntityUid? cause = null) + { + threshold.Triggered = true; + + foreach (var behavior in threshold.Behaviors) + { + // The owner has been deleted. We stop execution of behaviors here. + if (!Exists(owner)) + return; + + // TODO: Replace with EntityEffects. + behavior.Execute(owner, this, cause); + } + } + public bool TryGetDestroyedAt(Entity ent, [NotNullWhen(true)] out FixedPoint2? destroyedAt) { destroyedAt = null; @@ -145,7 +200,7 @@ namespace Content.Server.Destructible if (behavior is DoActsBehavior actBehavior && actBehavior.HasAct(ThresholdActs.Destruction | ThresholdActs.Breakage)) { - damageNeeded = Math.Min(damageNeeded.Float(), trigger.Damage); + damageNeeded = FixedPoint2.Min(damageNeeded, trigger.Damage); } } } diff --git a/Content.Server/Destructible/Thresholds/ActsFlags.cs b/Content.Server/Destructible/Thresholds/ActsFlags.cs deleted file mode 100644 index 5193499aa9..0000000000 --- a/Content.Server/Destructible/Thresholds/ActsFlags.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace Content.Server.Destructible.Thresholds -{ - public sealed class ActsFlags { } -} diff --git a/Content.Server/Destructible/Thresholds/Behaviors/DoActsBehavior.cs b/Content.Server/Destructible/Thresholds/Behaviors/DoActsBehavior.cs index bfebae8f92..1086be4958 100644 --- a/Content.Server/Destructible/Thresholds/Behaviors/DoActsBehavior.cs +++ b/Content.Server/Destructible/Thresholds/Behaviors/DoActsBehavior.cs @@ -1,4 +1,6 @@ -namespace Content.Server.Destructible.Thresholds.Behaviors +using Content.Shared.Destructible; + +namespace Content.Server.Destructible.Thresholds.Behaviors { [Serializable] [DataDefinition] diff --git a/Content.Server/Destructible/Thresholds/DamageThreshold.cs b/Content.Server/Destructible/Thresholds/DamageThreshold.cs index e180f5c45c..de4362d677 100644 --- a/Content.Server/Destructible/Thresholds/DamageThreshold.cs +++ b/Content.Server/Destructible/Thresholds/DamageThreshold.cs @@ -1,96 +1,43 @@ using Content.Server.Destructible.Thresholds.Behaviors; -using Content.Server.Destructible.Thresholds.Triggers; -using Content.Shared.Damage; +using Content.Shared.Destructible.Thresholds.Triggers; -namespace Content.Server.Destructible.Thresholds -{ - [DataDefinition] - public sealed partial class DamageThreshold - { - [DataField("behaviors")] - private List _behaviors = new(); - - /// - /// Whether or not this threshold was triggered in the previous call to - /// . - /// - [ViewVariables] public bool OldTriggered { get; private set; } - - /// - /// Whether or not this threshold has already been triggered. - /// - [DataField("triggered")] - public bool Triggered { get; private set; } - - /// - /// Whether or not this threshold only triggers once. - /// If false, it will trigger again once the entity is healed - /// and then damaged to reach this threshold once again. - /// It will not repeatedly trigger as damage rises beyond that. - /// - [DataField("triggersOnce")] - public bool TriggersOnce { get; set; } - - /// - /// The trigger that decides if this threshold has been reached. - /// - [DataField("trigger")] - public IThresholdTrigger? Trigger { get; set; } - - /// - /// Behaviors to activate once this threshold is triggered. - /// - [ViewVariables] public IReadOnlyList Behaviors => _behaviors; - - public bool Reached(DamageableComponent damageable, DestructibleSystem system) - { - if (Trigger == null) - { - return false; - } +namespace Content.Server.Destructible.Thresholds; - if (Triggered && TriggersOnce) - { - return false; - } - - if (OldTriggered) - { - OldTriggered = Trigger.Reached(damageable, system); - return false; - } - - if (!Trigger.Reached(damageable, system)) - { - return false; - } - - OldTriggered = true; - return true; - } - - /// - /// Triggers this threshold. - /// - /// The entity that owns this threshold. - /// - /// An instance of to get dependency and - /// system references from, if relevant. - /// - /// - /// - public void Execute(EntityUid owner, DestructibleSystem system, IEntityManager entityManager, EntityUid? cause) - { - Triggered = true; - - foreach (var behavior in Behaviors) - { - // The owner has been deleted. We stop execution of behaviors here. - if (!entityManager.EntityExists(owner)) - return; - - behavior.Execute(owner, system, cause); - } - } - } +[DataDefinition] +public sealed partial class DamageThreshold +{ + /// + /// Whether or not this threshold was triggered in the previous call to + /// . + /// + [ViewVariables] public bool OldTriggered; + + /// + /// Whether or not this threshold has already been triggered. + /// + [DataField] + public bool Triggered; + + /// + /// Whether or not this threshold only triggers once. + /// If false, it will trigger again once the entity is healed + /// and then damaged to reach this threshold once again. + /// It will not repeatedly trigger as damage rises beyond that. + /// + [DataField] + public bool TriggersOnce; + + /// + /// The condition that decides if this threshold has been reached. + /// Gets evaluated each time the entity's damage changes. + /// + [DataField] + public IThresholdTrigger? Trigger; + + /// + /// Behaviors to activate once this threshold is triggered. + /// TODO: Replace with EntityEffects. + /// + [DataField] + public List Behaviors = new(); } diff --git a/Content.Server/Destructible/Thresholds/ThresholdActs.cs b/Content.Server/Destructible/Thresholds/ThresholdActs.cs deleted file mode 100644 index d2ecd53742..0000000000 --- a/Content.Server/Destructible/Thresholds/ThresholdActs.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Robust.Shared.Serialization; - -namespace Content.Server.Destructible.Thresholds -{ - [Flags, FlagsFor(typeof(ActsFlags))] - [Serializable] - public enum ThresholdActs - { - None = 0, - Breakage, - Destruction - } -} diff --git a/Content.Server/Destructible/Thresholds/Triggers/AndTrigger.cs b/Content.Server/Destructible/Thresholds/Triggers/AndTrigger.cs deleted file mode 100644 index 70e0c3917c..0000000000 --- a/Content.Server/Destructible/Thresholds/Triggers/AndTrigger.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Content.Shared.Damage; - -namespace Content.Server.Destructible.Thresholds.Triggers -{ - /// - /// A trigger that will activate when all of its triggers have activated. - /// - [Serializable] - [DataDefinition] - public sealed partial class AndTrigger : IThresholdTrigger - { - [DataField("triggers")] - public List Triggers { get; set; } = new(); - - public bool Reached(DamageableComponent damageable, DestructibleSystem system) - { - foreach (var trigger in Triggers) - { - if (!trigger.Reached(damageable, system)) - { - return false; - } - } - - return true; - } - } -} diff --git a/Content.Server/Destructible/Thresholds/Triggers/DamageGroupTrigger.cs b/Content.Server/Destructible/Thresholds/Triggers/DamageGroupTrigger.cs deleted file mode 100644 index 09083105ca..0000000000 --- a/Content.Server/Destructible/Thresholds/Triggers/DamageGroupTrigger.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Content.Shared.Damage; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -using Content.Shared.Damage.Prototypes; - -namespace Content.Server.Destructible.Thresholds.Triggers -{ - /// - /// A trigger that will activate when the amount of damage received - /// of the specified class is above the specified threshold. - /// - [Serializable] - [DataDefinition] - public sealed partial class DamageGroupTrigger : IThresholdTrigger - { - [DataField("damageGroup", required: true, customTypeSerializer: typeof(PrototypeIdSerializer))] - public string DamageGroup { get; set; } = default!; - - /// - /// The amount of damage at which this threshold will trigger. - /// - [DataField("damage", required: true)] - public int Damage { get; set; } = default!; - - public bool Reached(DamageableComponent damageable, DestructibleSystem system) - { - return damageable.DamagePerGroup[DamageGroup] >= Damage; - } - } -} diff --git a/Content.Server/Destructible/Thresholds/Triggers/DamageTrigger.cs b/Content.Server/Destructible/Thresholds/Triggers/DamageTrigger.cs deleted file mode 100644 index ce0106ee2b..0000000000 --- a/Content.Server/Destructible/Thresholds/Triggers/DamageTrigger.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Content.Shared.Damage; - -namespace Content.Server.Destructible.Thresholds.Triggers -{ - /// - /// A trigger that will activate when the amount of damage received - /// is above the specified threshold. - /// - [Serializable] - [DataDefinition] - public sealed partial class DamageTrigger : IThresholdTrigger - { - /// - /// The amount of damage at which this threshold will trigger. - /// - [DataField("damage", required: true)] - public int Damage { get; set; } = default!; - - public bool Reached(DamageableComponent damageable, DestructibleSystem system) - { - return damageable.TotalDamage >= Damage; - } - } -} diff --git a/Content.Server/Destructible/Thresholds/Triggers/DamageTypeTrigger.cs b/Content.Server/Destructible/Thresholds/Triggers/DamageTypeTrigger.cs deleted file mode 100644 index ed9fd27ab7..0000000000 --- a/Content.Server/Destructible/Thresholds/Triggers/DamageTypeTrigger.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Content.Shared.Damage; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -using Content.Shared.Damage.Prototypes; - -namespace Content.Server.Destructible.Thresholds.Triggers -{ - /// - /// A trigger that will activate when the amount of damage received - /// of the specified type is above the specified threshold. - /// - [Serializable] - [DataDefinition] - public sealed partial class DamageTypeTrigger : IThresholdTrigger - { - [DataField("damageType", required:true, customTypeSerializer: typeof(PrototypeIdSerializer))] - public string DamageType { get; set; } = default!; - - [DataField("damage", required: true)] - public int Damage { get; set; } = default!; - - public bool Reached(DamageableComponent damageable, DestructibleSystem system) - { - return damageable.Damage.DamageDict.TryGetValue(DamageType, out var damageReceived) && - damageReceived >= Damage; - } - } -} diff --git a/Content.Server/Destructible/Thresholds/Triggers/IThresholdTrigger.cs b/Content.Server/Destructible/Thresholds/Triggers/IThresholdTrigger.cs deleted file mode 100644 index dfceb31c9b..0000000000 --- a/Content.Server/Destructible/Thresholds/Triggers/IThresholdTrigger.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Content.Shared.Damage; - -namespace Content.Server.Destructible.Thresholds.Triggers -{ - public interface IThresholdTrigger - { - /// - /// Checks if this trigger has been reached. - /// - /// The damageable component to check with. - /// - /// An instance of to pull - /// dependencies from, if any. - /// - /// true if this trigger has been reached, false otherwise. - bool Reached(DamageableComponent damageable, DestructibleSystem system); - } -} diff --git a/Content.Server/Destructible/Thresholds/Triggers/OrTrigger.cs b/Content.Server/Destructible/Thresholds/Triggers/OrTrigger.cs deleted file mode 100644 index 179141dbf8..0000000000 --- a/Content.Server/Destructible/Thresholds/Triggers/OrTrigger.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Content.Shared.Damage; - -namespace Content.Server.Destructible.Thresholds.Triggers -{ - /// - /// A trigger that will activate when any of its triggers have activated. - /// - [Serializable] - [DataDefinition] - public sealed partial class OrTrigger : IThresholdTrigger - { - [DataField("triggers")] - public List Triggers { get; private set; } = new(); - - public bool Reached(DamageableComponent damageable, DestructibleSystem system) - { - foreach (var trigger in Triggers) - { - if (trigger.Reached(damageable, system)) - { - return true; - } - } - - return false; - } - } -} diff --git a/Content.Shared/Destructible/ThresholdActs.cs b/Content.Shared/Destructible/ThresholdActs.cs new file mode 100644 index 0000000000..281d58092f --- /dev/null +++ b/Content.Shared/Destructible/ThresholdActs.cs @@ -0,0 +1,12 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.Destructible; + +[Flags] +[Serializable, NetSerializable] +public enum ThresholdActs : byte +{ + None = 0, + Breakage = 1 << 0, + Destruction = 1 << 1, +} diff --git a/Content.Shared/Destructible/Triggers/AndTrigger.cs b/Content.Shared/Destructible/Triggers/AndTrigger.cs new file mode 100644 index 0000000000..ebedeee479 --- /dev/null +++ b/Content.Shared/Destructible/Triggers/AndTrigger.cs @@ -0,0 +1,28 @@ +using Content.Shared.Damage; +using Robust.Shared.Serialization; + +namespace Content.Shared.Destructible.Thresholds.Triggers; + +/// +/// A trigger that will activate when all of its triggers have activated. +/// +[Serializable, NetSerializable] +[DataDefinition] +public sealed partial class AndTrigger : IThresholdTrigger +{ + [DataField] + public List Triggers = new(); + + public bool Reached(Entity damageable, SharedDestructibleSystem system) + { + foreach (var trigger in Triggers) + { + if (!trigger.Reached(damageable, system)) + { + return false; + } + } + + return true; + } +} diff --git a/Content.Shared/Destructible/Triggers/DamageGroupTrigger.cs b/Content.Shared/Destructible/Triggers/DamageGroupTrigger.cs new file mode 100644 index 0000000000..902198e33d --- /dev/null +++ b/Content.Shared/Destructible/Triggers/DamageGroupTrigger.cs @@ -0,0 +1,33 @@ +using Content.Shared.Damage; +using Content.Shared.FixedPoint; +using Content.Shared.Damage.Prototypes; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + +namespace Content.Shared.Destructible.Thresholds.Triggers; + +/// +/// A trigger that will activate when the amount of damage received +/// of the specified class is above the specified threshold. +/// +[Serializable, NetSerializable] +[DataDefinition] +public sealed partial class DamageGroupTrigger : IThresholdTrigger +{ + /// + /// The damage group to check for. + /// + [DataField(required: true)] + public ProtoId DamageGroup = default!; + + /// + /// The amount of damage at which this threshold will trigger. + /// + [DataField(required: true)] + public FixedPoint2 Damage = default!; + + public bool Reached(Entity damageable, SharedDestructibleSystem system) + { + return damageable.Comp.DamagePerGroup[DamageGroup] >= Damage; + } +} diff --git a/Content.Shared/Destructible/Triggers/DamageTrigger.cs b/Content.Shared/Destructible/Triggers/DamageTrigger.cs new file mode 100644 index 0000000000..9ec2060e5f --- /dev/null +++ b/Content.Shared/Destructible/Triggers/DamageTrigger.cs @@ -0,0 +1,25 @@ +using Content.Shared.Damage; +using Content.Shared.FixedPoint; +using Robust.Shared.Serialization; + +namespace Content.Shared.Destructible.Thresholds.Triggers; + +/// +/// A trigger that will activate when the total amount of damage received +/// is above the specified threshold. +/// +[Serializable, NetSerializable] +[DataDefinition] +public sealed partial class DamageTrigger : IThresholdTrigger +{ + /// + /// The amount of damage at which this threshold will trigger. + /// + [DataField(required: true)] + public FixedPoint2 Damage = default!; + + public bool Reached(Entity damageable, SharedDestructibleSystem system) + { + return damageable.Comp.TotalDamage >= Damage; + } +} diff --git a/Content.Shared/Destructible/Triggers/DamageTypeTrigger.cs b/Content.Shared/Destructible/Triggers/DamageTypeTrigger.cs new file mode 100644 index 0000000000..8f50b62791 --- /dev/null +++ b/Content.Shared/Destructible/Triggers/DamageTypeTrigger.cs @@ -0,0 +1,34 @@ +using Content.Shared.Damage; +using Content.Shared.Damage.Prototypes; +using Content.Shared.FixedPoint; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + +namespace Content.Shared.Destructible.Thresholds.Triggers; + +/// +/// A trigger that will activate when the amount of damage received +/// of the specified type is above the specified threshold. +/// +[Serializable, NetSerializable] +[DataDefinition] +public sealed partial class DamageTypeTrigger : IThresholdTrigger +{ + /// + /// The damage type to check for. + /// + [DataField(required: true)] + public ProtoId DamageType = default!; + + /// + /// The amount of damage at which this threshold will trigger. + /// + [DataField(required: true)] + public FixedPoint2 Damage = default!; + + public bool Reached(Entity damageable, SharedDestructibleSystem system) + { + return damageable.Comp.Damage.DamageDict.TryGetValue(DamageType, out var damageReceived) && + damageReceived >= Damage; + } +} diff --git a/Content.Shared/Destructible/Triggers/IThresholdTrigger.cs b/Content.Shared/Destructible/Triggers/IThresholdTrigger.cs new file mode 100644 index 0000000000..98c96a5bd5 --- /dev/null +++ b/Content.Shared/Destructible/Triggers/IThresholdTrigger.cs @@ -0,0 +1,27 @@ +using Content.Shared.Damage; + +namespace Content.Shared.Destructible.Thresholds.Triggers; + +/// +/// A condition for triggering a . +/// +/// +/// I decided against converting these into EntityEffectConditions for performance reasons +/// (although I did not do any benchmarks, so it might be fine). +/// Entity effects will raise a separate event for each entity and each condition, which can become a huge number +/// for cases like nuke explosions or shuttle collisions where there are lots of DamageChangedEvents at once. +/// IThresholdTriggers on the other hand are directly checked in a foreach loop without raising events. +/// And there are only few of these conditions, so there is only a minor amount of code duplication. +/// +public interface IThresholdTrigger +{ + /// + /// Checks if this trigger has been reached. + /// + /// The damageable component to check with. + /// + /// An instance of to pull dependencies from, if any. + /// + /// true if this trigger has been reached, false otherwise. + bool Reached(Entity damageable, SharedDestructibleSystem system); +} diff --git a/Content.Shared/Destructible/Triggers/OrTrigger.cs b/Content.Shared/Destructible/Triggers/OrTrigger.cs new file mode 100644 index 0000000000..3e379cac7f --- /dev/null +++ b/Content.Shared/Destructible/Triggers/OrTrigger.cs @@ -0,0 +1,28 @@ +using Content.Shared.Damage; +using Robust.Shared.Serialization; + +namespace Content.Shared.Destructible.Thresholds.Triggers; + +/// +/// A trigger that will activate when any of its triggers have activated. +/// +[Serializable, NetSerializable] +[DataDefinition] +public sealed partial class OrTrigger : IThresholdTrigger +{ + [DataField] + public List Triggers = new(); + + public bool Reached(Entity damageable, SharedDestructibleSystem system) + { + foreach (var trigger in Triggers) + { + if (trigger.Reached(damageable, system)) + { + return true; + } + } + + return false; + } +} diff --git a/Content.Shared/EntityConditions/Conditions/DamageGroupEntityConditionSystem.cs b/Content.Shared/EntityConditions/Conditions/DamageGroupEntityConditionSystem.cs new file mode 100644 index 0000000000..4fb0cdf43f --- /dev/null +++ b/Content.Shared/EntityConditions/Conditions/DamageGroupEntityConditionSystem.cs @@ -0,0 +1,38 @@ +using Content.Shared.Damage; +using Content.Shared.Damage.Prototypes; +using Content.Shared.FixedPoint; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityConditions.Conditions; + +/// +/// Returns true if this entity can take damage and if its damage of a given damage group is within a specified minimum and maximum. +/// +/// +public sealed partial class DamageGroupEntityConditionSystem : EntityConditionSystem +{ + protected override void Condition(Entity entity, ref EntityConditionEvent args) + { + var value = entity.Comp.DamagePerGroup[args.Condition.DamageGroup]; + args.Result = value >= args.Condition.Min && value <= args.Condition.Max; + } +} + +/// +public sealed partial class DamageGroupCondition : EntityConditionBase +{ + [DataField] + public FixedPoint2 Max = FixedPoint2.MaxValue; + + [DataField] + public FixedPoint2 Min = FixedPoint2.Zero; + + [DataField(required: true)] + public ProtoId DamageGroup; + + public override string EntityConditionGuidebookText(IPrototypeManager prototype) => + Loc.GetString("reagent-effect-condition-guidebook-group-damage", + ("max", Max == FixedPoint2.MaxValue ? int.MaxValue : Max.Float()), + ("min", Min.Float()), + ("type", prototype.Index(DamageGroup).LocalizedName)); +} diff --git a/Content.Shared/EntityConditions/Conditions/DamageTypeEntityConditionSystem.cs b/Content.Shared/EntityConditions/Conditions/DamageTypeEntityConditionSystem.cs new file mode 100644 index 0000000000..b451f5d258 --- /dev/null +++ b/Content.Shared/EntityConditions/Conditions/DamageTypeEntityConditionSystem.cs @@ -0,0 +1,38 @@ +using Content.Shared.Damage; +using Content.Shared.Damage.Prototypes; +using Content.Shared.FixedPoint; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityConditions.Conditions; + +/// +/// Returns true if this entity can take damage and if its damage of a given damage type is within a specified minimum and maximum. +/// +/// +public sealed partial class DamageTypeEntityConditionSystem : EntityConditionSystem +{ + protected override void Condition(Entity entity, ref EntityConditionEvent args) + { + var value = entity.Comp.Damage.DamageDict.GetValueOrDefault(args.Condition.DamageType); + args.Result = value >= args.Condition.Min && value <= args.Condition.Max; + } +} + +/// +public sealed partial class DamageTypeCondition : EntityConditionBase +{ + [DataField] + public FixedPoint2 Max = FixedPoint2.MaxValue; + + [DataField] + public FixedPoint2 Min = FixedPoint2.Zero; + + [DataField(required: true)] + public ProtoId DamageType; + + public override string EntityConditionGuidebookText(IPrototypeManager prototype) => + Loc.GetString("reagent-effect-condition-guidebook-type-damage", + ("max", Max == FixedPoint2.MaxValue ? int.MaxValue : Max.Float()), + ("min", Min.Float()), + ("type", prototype.Index(DamageType).LocalizedName)); +} diff --git a/Resources/Locale/en-US/guidebook/entity-effects/conditions.ftl b/Resources/Locale/en-US/guidebook/entity-effects/conditions.ftl index fe31dd62f8..63654ab08b 100644 --- a/Resources/Locale/en-US/guidebook/entity-effects/conditions.ftl +++ b/Resources/Locale/en-US/guidebook/entity-effects/conditions.ftl @@ -7,6 +7,24 @@ reagent-effect-condition-guidebook-total-damage = } } +reagent-effect-condition-guidebook-type-damage = + { $max -> + [2147483648] it has at least {NATURALFIXED($min, 2)} of {$type} damage + *[other] { $min -> + [0] it has at most {NATURALFIXED($max, 2)} of {$type} damage + *[other] it has between {NATURALFIXED($min, 2)} and {NATURALFIXED($max, 2)} of {$type} damage + } + } + +reagent-effect-condition-guidebook-group-damage = + { $max -> + [2147483648] it has at least {NATURALFIXED($min, 2)} of {$type} damage. + *[other] { $min -> + [0] it has at most {NATURALFIXED($max, 2)} of {$type} damage. + *[other] it has between {NATURALFIXED($min, 2)} and {NATURALFIXED($max, 2)} of {$type} damage + } + } + reagent-effect-condition-guidebook-total-hunger = { $max -> [2147483648] the target has at least {NATURALFIXED($min, 2)} total hunger -- 2.51.2