-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;
Assert.That(threshold.Trigger, Is.InstanceOf<AndTrigger>());
});
- var trigger = (AndTrigger) threshold.Trigger;
+ var trigger = (AndTrigger)threshold.Trigger;
Assert.Multiple(() =>
{
Assert.That(threshold.Trigger, Is.InstanceOf<AndTrigger>());
});
- trigger = (AndTrigger) threshold.Trigger;
+ trigger = (AndTrigger)threshold.Trigger;
Assert.Multiple(() =>
{
-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;
Assert.That(threshold.Trigger, Is.InstanceOf<AndTrigger>());
});
- var trigger = (AndTrigger) threshold.Trigger;
+ var trigger = (AndTrigger)threshold.Trigger;
Assert.Multiple(() =>
{
Assert.That(threshold.Trigger, Is.InstanceOf<AndTrigger>());
});
- trigger = (AndTrigger) threshold.Trigger;
+ trigger = (AndTrigger)threshold.Trigger;
Assert.Multiple(() =>
{
using System.Linq;
-using Content.Server.Destructible.Thresholds;
using Content.Server.Destructible.Thresholds.Behaviors;
using Content.Shared.Damage;
using Content.Shared.Damage.Prototypes;
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(() =>
{
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;
// 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(() =>
{
// 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(() =>
// 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;
// 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(() =>
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;
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;
[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<DestructibleComponent, DamageChangedEvent>(Execute);
+ SubscribeLocalEvent<DestructibleComponent, DamageChangedEvent>(OnDamageChanged);
}
/// <summary>
- /// Check if any thresholds were reached. if they were, execute them.
+ /// Check if any thresholds were reached. if they were, execute them.
/// </summary>
- 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);
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)
}
}
+ /// <summary>
+ /// Check if the given threshold should trigger.
+ /// </summary>
+ public bool Triggered(DamageThreshold threshold, Entity<DamageableComponent> 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;
+ }
+
+ /// <summary>
+ /// Check if the conditions for the given threshold are currently true.
+ /// </summary>
+ public bool Reached(DamageThreshold threshold, Entity<DamageableComponent> owner)
+ {
+ if (threshold.Trigger == null)
+ return false;
+
+ return threshold.Trigger.Reached(owner, this);
+ }
+
+ /// <summary>
+ /// Triggers this threshold.
+ /// </summary>
+ /// <param name="owner">The entity that owns this threshold.</param>
+ /// <param name="cause">The entity that caused this threshold to trigger.</param>
+ 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<DestructibleComponent?> ent, [NotNullWhen(true)] out FixedPoint2? destroyedAt)
{
destroyedAt = null;
if (behavior is DoActsBehavior actBehavior &&
actBehavior.HasAct(ThresholdActs.Destruction | ThresholdActs.Breakage))
{
- damageNeeded = Math.Min(damageNeeded.Float(), trigger.Damage);
+ damageNeeded = FixedPoint2.Min(damageNeeded, trigger.Damage);
}
}
}
+++ /dev/null
-namespace Content.Server.Destructible.Thresholds
-{
- public sealed class ActsFlags { }
-}
-namespace Content.Server.Destructible.Thresholds.Behaviors
+using Content.Shared.Destructible;
+
+namespace Content.Server.Destructible.Thresholds.Behaviors
{
[Serializable]
[DataDefinition]
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<IThresholdBehavior> _behaviors = new();
-
- /// <summary>
- /// Whether or not this threshold was triggered in the previous call to
- /// <see cref="Reached"/>.
- /// </summary>
- [ViewVariables] public bool OldTriggered { get; private set; }
-
- /// <summary>
- /// Whether or not this threshold has already been triggered.
- /// </summary>
- [DataField("triggered")]
- public bool Triggered { get; private set; }
-
- /// <summary>
- /// 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.
- /// </summary>
- [DataField("triggersOnce")]
- public bool TriggersOnce { get; set; }
-
- /// <summary>
- /// The trigger that decides if this threshold has been reached.
- /// </summary>
- [DataField("trigger")]
- public IThresholdTrigger? Trigger { get; set; }
-
- /// <summary>
- /// Behaviors to activate once this threshold is triggered.
- /// </summary>
- [ViewVariables] public IReadOnlyList<IThresholdBehavior> 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;
- }
-
- /// <summary>
- /// Triggers this threshold.
- /// </summary>
- /// <param name="owner">The entity that owns this threshold.</param>
- /// <param name="system">
- /// An instance of <see cref="DestructibleSystem"/> to get dependency and
- /// system references from, if relevant.
- /// </param>
- /// <param name="entityManager"></param>
- /// <param name="cause"></param>
- 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
+{
+ /// <summary>
+ /// Whether or not this threshold was triggered in the previous call to
+ /// <see cref="Reached"/>.
+ /// </summary>
+ [ViewVariables] public bool OldTriggered;
+
+ /// <summary>
+ /// Whether or not this threshold has already been triggered.
+ /// </summary>
+ [DataField]
+ public bool Triggered;
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ [DataField]
+ public bool TriggersOnce;
+
+ /// <summary>
+ /// The condition that decides if this threshold has been reached.
+ /// Gets evaluated each time the entity's damage changes.
+ /// </summary>
+ [DataField]
+ public IThresholdTrigger? Trigger;
+
+ /// <summary>
+ /// Behaviors to activate once this threshold is triggered.
+ /// TODO: Replace with EntityEffects.
+ /// </summary>
+ [DataField]
+ public List<IThresholdBehavior> Behaviors = new();
}
+++ /dev/null
-using Robust.Shared.Serialization;
-
-namespace Content.Server.Destructible.Thresholds
-{
- [Flags, FlagsFor(typeof(ActsFlags))]
- [Serializable]
- public enum ThresholdActs
- {
- None = 0,
- Breakage,
- Destruction
- }
-}
+++ /dev/null
-using Content.Shared.Damage;
-
-namespace Content.Server.Destructible.Thresholds.Triggers
-{
- /// <summary>
- /// A trigger that will activate when all of its triggers have activated.
- /// </summary>
- [Serializable]
- [DataDefinition]
- public sealed partial class AndTrigger : IThresholdTrigger
- {
- [DataField("triggers")]
- public List<IThresholdTrigger> 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;
- }
- }
-}
+++ /dev/null
-using Content.Shared.Damage;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-using Content.Shared.Damage.Prototypes;
-
-namespace Content.Server.Destructible.Thresholds.Triggers
-{
- /// <summary>
- /// A trigger that will activate when the amount of damage received
- /// of the specified class is above the specified threshold.
- /// </summary>
- [Serializable]
- [DataDefinition]
- public sealed partial class DamageGroupTrigger : IThresholdTrigger
- {
- [DataField("damageGroup", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<DamageGroupPrototype>))]
- public string DamageGroup { get; set; } = default!;
-
- /// <summary>
- /// The amount of damage at which this threshold will trigger.
- /// </summary>
- [DataField("damage", required: true)]
- public int Damage { get; set; } = default!;
-
- public bool Reached(DamageableComponent damageable, DestructibleSystem system)
- {
- return damageable.DamagePerGroup[DamageGroup] >= Damage;
- }
- }
-}
+++ /dev/null
-using Content.Shared.Damage;
-
-namespace Content.Server.Destructible.Thresholds.Triggers
-{
- /// <summary>
- /// A trigger that will activate when the amount of damage received
- /// is above the specified threshold.
- /// </summary>
- [Serializable]
- [DataDefinition]
- public sealed partial class DamageTrigger : IThresholdTrigger
- {
- /// <summary>
- /// The amount of damage at which this threshold will trigger.
- /// </summary>
- [DataField("damage", required: true)]
- public int Damage { get; set; } = default!;
-
- public bool Reached(DamageableComponent damageable, DestructibleSystem system)
- {
- return damageable.TotalDamage >= Damage;
- }
- }
-}
+++ /dev/null
-using Content.Shared.Damage;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-using Content.Shared.Damage.Prototypes;
-
-namespace Content.Server.Destructible.Thresholds.Triggers
-{
- /// <summary>
- /// A trigger that will activate when the amount of damage received
- /// of the specified type is above the specified threshold.
- /// </summary>
- [Serializable]
- [DataDefinition]
- public sealed partial class DamageTypeTrigger : IThresholdTrigger
- {
- [DataField("damageType", required:true, customTypeSerializer: typeof(PrototypeIdSerializer<DamageTypePrototype>))]
- 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;
- }
- }
-}
+++ /dev/null
-using Content.Shared.Damage;
-
-namespace Content.Server.Destructible.Thresholds.Triggers
-{
- public interface IThresholdTrigger
- {
- /// <summary>
- /// Checks if this trigger has been reached.
- /// </summary>
- /// <param name="damageable">The damageable component to check with.</param>
- /// <param name="system">
- /// An instance of <see cref="DestructibleSystem"/> to pull
- /// dependencies from, if any.
- /// </param>
- /// <returns>true if this trigger has been reached, false otherwise.</returns>
- bool Reached(DamageableComponent damageable, DestructibleSystem system);
- }
-}
+++ /dev/null
-using Content.Shared.Damage;
-
-namespace Content.Server.Destructible.Thresholds.Triggers
-{
- /// <summary>
- /// A trigger that will activate when any of its triggers have activated.
- /// </summary>
- [Serializable]
- [DataDefinition]
- public sealed partial class OrTrigger : IThresholdTrigger
- {
- [DataField("triggers")]
- public List<IThresholdTrigger> 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;
- }
- }
-}
--- /dev/null
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Destructible;
+
+[Flags]
+[Serializable, NetSerializable]
+public enum ThresholdActs : byte
+{
+ None = 0,
+ Breakage = 1 << 0,
+ Destruction = 1 << 1,
+}
--- /dev/null
+using Content.Shared.Damage;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Destructible.Thresholds.Triggers;
+
+/// <summary>
+/// A trigger that will activate when all of its triggers have activated.
+/// </summary>
+[Serializable, NetSerializable]
+[DataDefinition]
+public sealed partial class AndTrigger : IThresholdTrigger
+{
+ [DataField]
+ public List<IThresholdTrigger> Triggers = new();
+
+ public bool Reached(Entity<DamageableComponent> damageable, SharedDestructibleSystem system)
+ {
+ foreach (var trigger in Triggers)
+ {
+ if (!trigger.Reached(damageable, system))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
--- /dev/null
+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;
+
+/// <summary>
+/// A trigger that will activate when the amount of damage received
+/// of the specified class is above the specified threshold.
+/// </summary>
+[Serializable, NetSerializable]
+[DataDefinition]
+public sealed partial class DamageGroupTrigger : IThresholdTrigger
+{
+ /// <summary>
+ /// The damage group to check for.
+ /// </summary>
+ [DataField(required: true)]
+ public ProtoId<DamageGroupPrototype> DamageGroup = default!;
+
+ /// <summary>
+ /// The amount of damage at which this threshold will trigger.
+ /// </summary>
+ [DataField(required: true)]
+ public FixedPoint2 Damage = default!;
+
+ public bool Reached(Entity<DamageableComponent> damageable, SharedDestructibleSystem system)
+ {
+ return damageable.Comp.DamagePerGroup[DamageGroup] >= Damage;
+ }
+}
--- /dev/null
+using Content.Shared.Damage;
+using Content.Shared.FixedPoint;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Destructible.Thresholds.Triggers;
+
+/// <summary>
+/// A trigger that will activate when the total amount of damage received
+/// is above the specified threshold.
+/// </summary>
+[Serializable, NetSerializable]
+[DataDefinition]
+public sealed partial class DamageTrigger : IThresholdTrigger
+{
+ /// <summary>
+ /// The amount of damage at which this threshold will trigger.
+ /// </summary>
+ [DataField(required: true)]
+ public FixedPoint2 Damage = default!;
+
+ public bool Reached(Entity<DamageableComponent> damageable, SharedDestructibleSystem system)
+ {
+ return damageable.Comp.TotalDamage >= Damage;
+ }
+}
--- /dev/null
+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;
+
+/// <summary>
+/// A trigger that will activate when the amount of damage received
+/// of the specified type is above the specified threshold.
+/// </summary>
+[Serializable, NetSerializable]
+[DataDefinition]
+public sealed partial class DamageTypeTrigger : IThresholdTrigger
+{
+ /// <summary>
+ /// The damage type to check for.
+ /// </summary>
+ [DataField(required: true)]
+ public ProtoId<DamageTypePrototype> DamageType = default!;
+
+ /// <summary>
+ /// The amount of damage at which this threshold will trigger.
+ /// </summary>
+ [DataField(required: true)]
+ public FixedPoint2 Damage = default!;
+
+ public bool Reached(Entity<DamageableComponent> damageable, SharedDestructibleSystem system)
+ {
+ return damageable.Comp.Damage.DamageDict.TryGetValue(DamageType, out var damageReceived) &&
+ damageReceived >= Damage;
+ }
+}
--- /dev/null
+using Content.Shared.Damage;
+
+namespace Content.Shared.Destructible.Thresholds.Triggers;
+
+/// <summary>
+/// A condition for triggering a <see cref="DamageThreshold">.
+/// </summary>
+/// <remarks>
+/// 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.
+/// </remarks>
+public interface IThresholdTrigger
+{
+ /// <summary>
+ /// Checks if this trigger has been reached.
+ /// </summary>
+ /// <param name="damageable">The damageable component to check with.</param>
+ /// <param name="system">
+ /// An instance of <see cref="SharedDestructibleSystem"/> to pull dependencies from, if any.
+ /// </param>
+ /// <returns>true if this trigger has been reached, false otherwise.</returns>
+ bool Reached(Entity<DamageableComponent> damageable, SharedDestructibleSystem system);
+}
--- /dev/null
+using Content.Shared.Damage;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Destructible.Thresholds.Triggers;
+
+/// <summary>
+/// A trigger that will activate when any of its triggers have activated.
+/// </summary>
+[Serializable, NetSerializable]
+[DataDefinition]
+public sealed partial class OrTrigger : IThresholdTrigger
+{
+ [DataField]
+ public List<IThresholdTrigger> Triggers = new();
+
+ public bool Reached(Entity<DamageableComponent> damageable, SharedDestructibleSystem system)
+ {
+ foreach (var trigger in Triggers)
+ {
+ if (trigger.Reached(damageable, system))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
--- /dev/null
+using Content.Shared.Damage;
+using Content.Shared.Damage.Prototypes;
+using Content.Shared.FixedPoint;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityConditions.Conditions;
+
+/// <summary>
+/// Returns true if this entity can take damage and if its damage of a given damage group is within a specified minimum and maximum.
+/// </summary>
+/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
+public sealed partial class DamageGroupEntityConditionSystem : EntityConditionSystem<DamageableComponent, DamageGroupCondition>
+{
+ protected override void Condition(Entity<DamageableComponent> entity, ref EntityConditionEvent<DamageGroupCondition> args)
+ {
+ var value = entity.Comp.DamagePerGroup[args.Condition.DamageGroup];
+ args.Result = value >= args.Condition.Min && value <= args.Condition.Max;
+ }
+}
+
+/// <inheritdoc cref="EntityCondition"/>
+public sealed partial class DamageGroupCondition : EntityConditionBase<DamageGroupCondition>
+{
+ [DataField]
+ public FixedPoint2 Max = FixedPoint2.MaxValue;
+
+ [DataField]
+ public FixedPoint2 Min = FixedPoint2.Zero;
+
+ [DataField(required: true)]
+ public ProtoId<DamageGroupPrototype> 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));
+}
--- /dev/null
+using Content.Shared.Damage;
+using Content.Shared.Damage.Prototypes;
+using Content.Shared.FixedPoint;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityConditions.Conditions;
+
+/// <summary>
+/// Returns true if this entity can take damage and if its damage of a given damage type is within a specified minimum and maximum.
+/// </summary>
+/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
+public sealed partial class DamageTypeEntityConditionSystem : EntityConditionSystem<DamageableComponent, DamageTypeCondition>
+{
+ protected override void Condition(Entity<DamageableComponent> entity, ref EntityConditionEvent<DamageTypeCondition> args)
+ {
+ var value = entity.Comp.Damage.DamageDict.GetValueOrDefault(args.Condition.DamageType);
+ args.Result = value >= args.Condition.Min && value <= args.Condition.Max;
+ }
+}
+
+/// <inheritdoc cref="EntityCondition"/>
+public sealed partial class DamageTypeCondition : EntityConditionBase<DamageTypeCondition>
+{
+ [DataField]
+ public FixedPoint2 Max = FixedPoint2.MaxValue;
+
+ [DataField]
+ public FixedPoint2 Min = FixedPoint2.Zero;
+
+ [DataField(required: true)]
+ public ProtoId<DamageTypePrototype> 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));
+}
}
}
+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