--- /dev/null
+using Content.Shared.Mobs;
+using Content.Shared.FixedPoint;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Damage.Components;
+
+/// <summary>
+/// Passively damages the entity on a specified interval.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+public sealed partial class PassiveDamageComponent : Component
+{
+ /// <summary>
+ /// The entitys' states that passive damage will apply in
+ /// </summary>
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public List<MobState> AllowedStates = new();
+
+ /// <summary>
+ /// Damage / Healing per interval dealt to the entity every interval
+ /// </summary>
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public DamageSpecifier Damage = new();
+
+ /// <summary>
+ /// Delay between damage events in seconds
+ /// </summary>
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public float Interval = 1f;
+
+ /// <summary>
+ /// The maximum HP the damage will be given to. If 0, disabled.
+ /// </summary>
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public FixedPoint2 DamageCap = 0;
+
+ [DataField("nextDamage", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
+ public TimeSpan NextDamage = TimeSpan.Zero;
+}
--- /dev/null
+using Content.Shared.Damage.Components;
+using Content.Shared.Mobs.Systems;
+using Content.Shared.Mobs.Components;
+using Content.Shared.FixedPoint;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Damage;
+
+public sealed class PassiveDamageSystem : EntitySystem
+{
+ [Dependency] private readonly DamageableSystem _damageable = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly MobStateSystem _mobState = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<PassiveDamageComponent, MapInitEvent>(OnPendingMapInit);
+ }
+
+ private void OnPendingMapInit(EntityUid uid, PassiveDamageComponent component, MapInitEvent args)
+ {
+ component.NextDamage = _timing.CurTime + TimeSpan.FromSeconds(1f);
+ }
+
+ // Every tick, attempt to damage entities
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+ var curTime = _timing.CurTime;
+
+ // Go through every entity with the component
+ var query = EntityQueryEnumerator<PassiveDamageComponent, DamageableComponent, MobStateComponent>();
+ while (query.MoveNext(out var uid, out var comp, out var damage, out var mobState))
+ {
+ // Make sure they're up for a damage tick
+ if (comp.NextDamage > curTime)
+ continue;
+
+ if (comp.DamageCap != 0 && damage.TotalDamage >= comp.DamageCap)
+ continue;
+
+ // Set the next time they can take damage
+ comp.NextDamage = curTime + TimeSpan.FromSeconds(1f);
+
+ // Damage them
+ foreach (var allowedState in comp.AllowedStates)
+ {
+ if(allowedState == mobState.CurrentState)
+ _damageable.TryChangeDamage(uid, comp.Damage, true, false, damage);
+ }
+ }
+ }
+}