refactor: rework the new status effect system to use containers (#38915)

This commit is contained in:
Perry Fraser
2025-07-12 12:49:58 -04:00
committed by GitHub
parent ad34d88a49
commit dbfe05d5cc
18 changed files with 244 additions and 214 deletions

View File

@@ -17,7 +17,7 @@ public sealed class DrowsinessOverlay : Overlay
[Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IEntitySystemManager _sysMan = default!; [Dependency] private readonly IEntitySystemManager _sysMan = default!;
[Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IGameTiming _timing = default!;
private readonly SharedStatusEffectsSystem _statusEffects = default!; private readonly StatusEffectsSystem _statusEffects = default!;
public override OverlaySpace Space => OverlaySpace.WorldSpace; public override OverlaySpace Space => OverlaySpace.WorldSpace;
public override bool RequestScreenTexture => true; public override bool RequestScreenTexture => true;
@@ -33,7 +33,7 @@ public sealed class DrowsinessOverlay : Overlay
{ {
IoCManager.InjectDependencies(this); IoCManager.InjectDependencies(this);
_statusEffects = _sysMan.GetEntitySystem<SharedStatusEffectsSystem>(); _statusEffects = _sysMan.GetEntitySystem<StatusEffectsSystem>();
_drowsinessShader = _prototypeManager.Index(Shader).InstanceUnique(); _drowsinessShader = _prototypeManager.Index(Shader).InstanceUnique();
} }

View File

@@ -10,7 +10,7 @@ public sealed class DrowsinessSystem : SharedDrowsinessSystem
{ {
[Dependency] private readonly IPlayerManager _player = default!; [Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IOverlayManager _overlayMan = default!; [Dependency] private readonly IOverlayManager _overlayMan = default!;
[Dependency] private readonly SharedStatusEffectsSystem _statusEffects = default!; [Dependency] private readonly StatusEffectsSystem _statusEffects = default!;
private DrowsinessOverlay _overlay = default!; private DrowsinessOverlay _overlay = default!;

View File

@@ -20,7 +20,7 @@ public sealed class RainbowOverlay : Overlay
[Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IEntitySystemManager _sysMan = default!; [Dependency] private readonly IEntitySystemManager _sysMan = default!;
[Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IGameTiming _timing = default!;
private readonly SharedStatusEffectsSystem _statusEffects = default!; private readonly StatusEffectsSystem _statusEffects = default!;
public override OverlaySpace Space => OverlaySpace.WorldSpace; public override OverlaySpace Space => OverlaySpace.WorldSpace;
public override bool RequestScreenTexture => true; public override bool RequestScreenTexture => true;
@@ -41,7 +41,7 @@ public sealed class RainbowOverlay : Overlay
{ {
IoCManager.InjectDependencies(this); IoCManager.InjectDependencies(this);
_statusEffects = _sysMan.GetEntitySystem<SharedStatusEffectsSystem>(); _statusEffects = _sysMan.GetEntitySystem<StatusEffectsSystem>();
_rainbowShader = _prototypeManager.Index(Shader).InstanceUnique(); _rainbowShader = _prototypeManager.Index(Shader).InstanceUnique();
_config.OnValueChanged(CCVars.ReducedMotion, OnReducedMotionChanged, invokeImmediately: true); _config.OnValueChanged(CCVars.ReducedMotion, OnReducedMotionChanged, invokeImmediately: true);

View File

@@ -1,50 +0,0 @@
using Content.Shared.StatusEffectNew;
using Content.Shared.StatusEffectNew.Components;
using Robust.Shared.Collections;
using Robust.Shared.GameStates;
namespace Content.Client.StatusEffectNew;
/// <inheritdoc/>
public sealed partial class ClientStatusEffectsSystem : SharedStatusEffectsSystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<StatusEffectContainerComponent, ComponentHandleState>(OnHandleState);
}
private void OnHandleState(Entity<StatusEffectContainerComponent> ent, ref ComponentHandleState args)
{
if (args.Current is not StatusEffectContainerComponentState state)
return;
var toRemove = new ValueList<EntityUid>();
foreach (var effect in ent.Comp.ActiveStatusEffects)
{
if (state.ActiveStatusEffects.Contains(GetNetEntity(effect)))
continue;
toRemove.Add(effect);
}
foreach (var effect in toRemove)
{
ent.Comp.ActiveStatusEffects.Remove(effect);
var ev = new StatusEffectRemovedEvent(ent);
RaiseLocalEvent(effect, ref ev);
}
foreach (var effect in state.ActiveStatusEffects)
{
var effectUid = GetEntity(effect);
if (ent.Comp.ActiveStatusEffects.Contains(effectUid))
continue;
ent.Comp.ActiveStatusEffects.Add(effectUid);
var ev = new StatusEffectAppliedEvent(ent);
RaiseLocalEvent(effectUid, ref ev);
}
}
}

View File

@@ -1,4 +1,3 @@
using Content.Server.StatusEffectNew;
using Content.Shared.Bed.Sleep; using Content.Shared.Bed.Sleep;
using Content.Shared.Drowsiness; using Content.Shared.Drowsiness;
using Content.Shared.StatusEffectNew; using Content.Shared.StatusEffectNew;

View File

@@ -1,23 +0,0 @@
using Content.Shared.StatusEffectNew;
using Content.Shared.StatusEffectNew.Components;
namespace Content.Server.StatusEffectNew;
/// <inheritdoc/>
public sealed partial class StatusEffectsSystem : SharedStatusEffectsSystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<StatusEffectContainerComponent, ComponentShutdown>(OnContainerShutdown);
}
private void OnContainerShutdown(Entity<StatusEffectContainerComponent> ent, ref ComponentShutdown args)
{
foreach (var effect in ent.Comp.ActiveStatusEffects)
{
QueueDel(effect);
}
}
}

View File

@@ -1,5 +1,5 @@
using Content.Server.StatusEffectNew;
using Content.Shared.Bed.Sleep; using Content.Shared.Bed.Sleep;
using Content.Shared.StatusEffectNew;
using Robust.Shared.Random; using Robust.Shared.Random;
namespace Content.Server.Traits.Assorted; namespace Content.Server.Traits.Assorted;

View File

@@ -38,8 +38,8 @@ public sealed partial class SleepingSystem : EntitySystem
[Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedEmitSoundSystem _emitSound = default!; [Dependency] private readonly SharedEmitSoundSystem _emitSound = default!;
[Dependency] private readonly StatusEffectsSystem _statusEffectOld = default!; [Dependency] private readonly StatusEffect.StatusEffectsSystem _statusEffectOld = default!;
[Dependency] private readonly SharedStatusEffectsSystem _statusEffectNew = default!; [Dependency] private readonly StatusEffectNew.StatusEffectsSystem _statusEffectNew = default!;
public static readonly EntProtoId SleepActionId = "ActionSleep"; public static readonly EntProtoId SleepActionId = "ActionSleep";
public static readonly EntProtoId WakeActionId = "ActionWake"; public static readonly EntProtoId WakeActionId = "ActionWake";

View File

@@ -34,7 +34,7 @@ public sealed partial class ModifyStatusEffect : EntityEffect
/// <inheritdoc /> /// <inheritdoc />
public override void Effect(EntityEffectBaseArgs args) public override void Effect(EntityEffectBaseArgs args)
{ {
var statusSys = args.EntityManager.EntitySysManager.GetEntitySystem<SharedStatusEffectsSystem>(); var statusSys = args.EntityManager.EntitySysManager.GetEntitySystem<StatusEffectsSystem>();
var time = Time; var time = Time;
if (args is EntityEffectReagentArgs reagentArgs) if (args is EntityEffectReagentArgs reagentArgs)

View File

@@ -16,7 +16,7 @@ public sealed class SSDIndicatorSystem : EntitySystem
[Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedStatusEffectsSystem _statusEffects = default!; [Dependency] private readonly StatusEffectsSystem _statusEffects = default!;
private bool _icSsdSleep; private bool _icSsdSleep;
private float _icSsdSleepTime; private float _icSsdSleepTime;

View File

@@ -9,7 +9,7 @@ using Robust.Shared.Utility;
namespace Content.Shared.StatusEffect namespace Content.Shared.StatusEffect
{ {
[Obsolete("Migration to Content.Shared.StatusEffectNew.SharedStatusEffectsSystem is required")] [Obsolete("Migration to Content.Shared.StatusEffectNew.StatusEffectsSystem is required")]
public sealed class StatusEffectsSystem : EntitySystem public sealed class StatusEffectsSystem : EntitySystem
{ {
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
@@ -106,7 +106,7 @@ namespace Content.Shared.StatusEffect
/// <param name="status">The status effects component to change, if you already have it.</param> /// <param name="status">The status effects component to change, if you already have it.</param>
/// <returns>False if the effect could not be added or the component already exists, true otherwise.</returns> /// <returns>False if the effect could not be added or the component already exists, true otherwise.</returns>
/// <typeparam name="T">The component type to add and remove from the entity.</typeparam> /// <typeparam name="T">The component type to add and remove from the entity.</typeparam>
[Obsolete("Migration to Content.Shared.StatusEffectNew.SharedStatusEffectsSystem is required")] [Obsolete("Migration to Content.Shared.StatusEffectNew.StatusEffectsSystem is required")]
public bool TryAddStatusEffect<T>(EntityUid uid, string key, TimeSpan time, bool refresh, public bool TryAddStatusEffect<T>(EntityUid uid, string key, TimeSpan time, bool refresh,
StatusEffectsComponent? status = null) StatusEffectsComponent? status = null)
where T : IComponent, new() where T : IComponent, new()
@@ -126,7 +126,7 @@ namespace Content.Shared.StatusEffect
} }
[Obsolete("Migration to Content.Shared.StatusEffectNew.SharedStatusEffectsSystem is required")] [Obsolete("Migration to Content.Shared.StatusEffectNew.StatusEffectsSystem is required")]
public bool TryAddStatusEffect(EntityUid uid, string key, TimeSpan time, bool refresh, string component, public bool TryAddStatusEffect(EntityUid uid, string key, TimeSpan time, bool refresh, string component,
StatusEffectsComponent? status = null) StatusEffectsComponent? status = null)
{ {
@@ -166,7 +166,7 @@ namespace Content.Shared.StatusEffect
/// If the effect already exists, it will simply replace the cooldown with the new one given. /// If the effect already exists, it will simply replace the cooldown with the new one given.
/// If you want special 'effect merging' behavior, do it your own damn self! /// If you want special 'effect merging' behavior, do it your own damn self!
/// </remarks> /// </remarks>
[Obsolete("Migration to Content.Shared.StatusEffectNew.SharedStatusEffectsSystem is required")] [Obsolete("Migration to Content.Shared.StatusEffectNew.StatusEffectsSystem is required")]
public bool TryAddStatusEffect(EntityUid uid, public bool TryAddStatusEffect(EntityUid uid,
string key, string key,
TimeSpan time, TimeSpan time,
@@ -260,7 +260,7 @@ namespace Content.Shared.StatusEffect
/// Obviously this doesn't automatically clear any effects a status effect might have. /// Obviously this doesn't automatically clear any effects a status effect might have.
/// That's up to the removed component to handle itself when it's removed. /// That's up to the removed component to handle itself when it's removed.
/// </remarks> /// </remarks>
[Obsolete("Migration to Content.Shared.StatusEffectNew.SharedStatusEffectsSystem is required")] [Obsolete("Migration to Content.Shared.StatusEffectNew.StatusEffectsSystem is required")]
public bool TryRemoveStatusEffect(EntityUid uid, string key, public bool TryRemoveStatusEffect(EntityUid uid, string key,
StatusEffectsComponent? status = null, bool remComp = true) StatusEffectsComponent? status = null, bool remComp = true)
{ {
@@ -304,7 +304,7 @@ namespace Content.Shared.StatusEffect
/// <param name="uid">The entity to remove effects from.</param> /// <param name="uid">The entity to remove effects from.</param>
/// <param name="status">The status effects component to change, if you already have it.</param> /// <param name="status">The status effects component to change, if you already have it.</param>
/// <returns>False if any status effects failed to be removed, true if they all did.</returns> /// <returns>False if any status effects failed to be removed, true if they all did.</returns>
[Obsolete("Migration to Content.Shared.StatusEffectNew.SharedStatusEffectsSystem is required")] [Obsolete("Migration to Content.Shared.StatusEffectNew.StatusEffectsSystem is required")]
public bool TryRemoveAllStatusEffects(EntityUid uid, public bool TryRemoveAllStatusEffects(EntityUid uid,
StatusEffectsComponent? status = null) StatusEffectsComponent? status = null)
{ {
@@ -328,7 +328,7 @@ namespace Content.Shared.StatusEffect
/// <param name="uid">The entity to check on.</param> /// <param name="uid">The entity to check on.</param>
/// <param name="key">The status effect ID to check for</param> /// <param name="key">The status effect ID to check for</param>
/// <param name="status">The status effect component, should you already have it.</param> /// <param name="status">The status effect component, should you already have it.</param>
[Obsolete("Migration to Content.Shared.StatusEffectNew.SharedStatusEffectsSystem is required")] [Obsolete("Migration to Content.Shared.StatusEffectNew.StatusEffectsSystem is required")]
public bool HasStatusEffect(EntityUid uid, string key, public bool HasStatusEffect(EntityUid uid, string key,
StatusEffectsComponent? status = null) StatusEffectsComponent? status = null)
{ {
@@ -346,7 +346,7 @@ namespace Content.Shared.StatusEffect
/// <param name="uid">The entity to check on.</param> /// <param name="uid">The entity to check on.</param>
/// <param name="key">The status effect ID to check for</param> /// <param name="key">The status effect ID to check for</param>
/// <param name="status">The status effect component, should you already have it.</param> /// <param name="status">The status effect component, should you already have it.</param>
[Obsolete("Migration to Content.Shared.StatusEffectNew.SharedStatusEffectsSystem is required")] [Obsolete("Migration to Content.Shared.StatusEffectNew.StatusEffectsSystem is required")]
public bool CanApplyEffect(EntityUid uid, string key, StatusEffectsComponent? status = null) public bool CanApplyEffect(EntityUid uid, string key, StatusEffectsComponent? status = null)
{ {
// don't log since stuff calling this prolly doesn't care if we don't actually have it // don't log since stuff calling this prolly doesn't care if we don't actually have it
@@ -373,7 +373,7 @@ namespace Content.Shared.StatusEffect
/// <param name="key">The status effect to add time to.</param> /// <param name="key">The status effect to add time to.</param>
/// <param name="time">The amount of time to add.</param> /// <param name="time">The amount of time to add.</param>
/// <param name="status">The status effect component, should you already have it.</param> /// <param name="status">The status effect component, should you already have it.</param>
[Obsolete("Migration to Content.Shared.StatusEffectNew.SharedStatusEffectsSystem is required")] [Obsolete("Migration to Content.Shared.StatusEffectNew.StatusEffectsSystem is required")]
public bool TryAddTime(EntityUid uid, string key, TimeSpan time, public bool TryAddTime(EntityUid uid, string key, TimeSpan time,
StatusEffectsComponent? status = null) StatusEffectsComponent? status = null)
{ {
@@ -405,7 +405,7 @@ namespace Content.Shared.StatusEffect
/// <param name="key">The status effect to remove time from.</param> /// <param name="key">The status effect to remove time from.</param>
/// <param name="time">The amount of time to add.</param> /// <param name="time">The amount of time to add.</param>
/// <param name="status">The status effect component, should you already have it.</param> /// <param name="status">The status effect component, should you already have it.</param>
[Obsolete("Migration to Content.Shared.StatusEffectNew.SharedStatusEffectsSystem is required")] [Obsolete("Migration to Content.Shared.StatusEffectNew.StatusEffectsSystem is required")]
public bool TryRemoveTime(EntityUid uid, string key, TimeSpan time, public bool TryRemoveTime(EntityUid uid, string key, TimeSpan time,
StatusEffectsComponent? status = null) StatusEffectsComponent? status = null)
{ {
@@ -441,7 +441,7 @@ namespace Content.Shared.StatusEffect
/// <remarks> /// <remarks>
/// Not used internally; just sets it itself. /// Not used internally; just sets it itself.
/// </remarks> /// </remarks>
[Obsolete("Migration to Content.Shared.StatusEffectNew.SharedStatusEffectsSystem is required")] [Obsolete("Migration to Content.Shared.StatusEffectNew.StatusEffectsSystem is required")]
public bool TrySetTime(EntityUid uid, string key, TimeSpan time, public bool TrySetTime(EntityUid uid, string key, TimeSpan time,
StatusEffectsComponent? status = null) StatusEffectsComponent? status = null)
{ {
@@ -465,7 +465,7 @@ namespace Content.Shared.StatusEffect
/// <param name="time">Out var for the time, if it exists.</param> /// <param name="time">Out var for the time, if it exists.</param>
/// <param name="status">The status effects component to use, if any.</param> /// <param name="status">The status effects component to use, if any.</param>
/// <returns>False if the status effect was not active, true otherwise.</returns> /// <returns>False if the status effect was not active, true otherwise.</returns>
[Obsolete("Migration to Content.Shared.StatusEffectNew.SharedStatusEffectsSystem is required")] [Obsolete("Migration to Content.Shared.StatusEffectNew.StatusEffectsSystem is required")]
public bool TryGetTime(EntityUid uid, string key, public bool TryGetTime(EntityUid uid, string key,
[NotNullWhen(true)] out (TimeSpan, TimeSpan)? time, [NotNullWhen(true)] out (TimeSpan, TimeSpan)? time,
StatusEffectsComponent? status = null) StatusEffectsComponent? status = null)

View File

@@ -0,0 +1,26 @@
using Content.Shared.Alert;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared.StatusEffectNew.Components;
/// <summary>
/// Used in conjunction with <see cref="StatusEffectComponent"/> to display an alert when the status effect is present.
/// </summary>
[RegisterComponent, NetworkedComponent]
[EntityCategory("StatusEffects")]
public sealed partial class StatusEffectAlertComponent : Component
{
/// <summary>
/// Status effect indication for the player.
/// </summary>
[DataField]
public ProtoId<AlertPrototype> Alert;
/// <summary>
/// If the status effect has a set end time and this is true, a duration
/// indicator will be displayed with the alert.
/// </summary>
[DataField]
public bool ShowDuration = true;
}

View File

@@ -11,7 +11,7 @@ namespace Content.Shared.StatusEffectNew.Components;
/// Provides a link between the effect and the affected entity, and some data common to all status effects. /// Provides a link between the effect and the affected entity, and some data common to all status effects.
/// </summary> /// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause] [RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
[Access(typeof(SharedStatusEffectsSystem))] [Access(typeof(StatusEffectsSystem))]
[EntityCategory("StatusEffects")] [EntityCategory("StatusEffects")]
public sealed partial class StatusEffectComponent : Component public sealed partial class StatusEffectComponent : Component
{ {
@@ -21,12 +21,6 @@ public sealed partial class StatusEffectComponent : Component
[DataField, AutoNetworkedField] [DataField, AutoNetworkedField]
public EntityUid? AppliedTo; public EntityUid? AppliedTo;
/// <summary>
/// Status effect indication for the player. If Null, no Alert will be displayed.
/// </summary>
[DataField]
public ProtoId<AlertPrototype>? Alert;
/// <summary> /// <summary>
/// When this effect will end. If Null, the effect lasts indefinitely. /// When this effect will end. If Null, the effect lasts indefinitely.
/// </summary> /// </summary>

View File

@@ -1,5 +1,5 @@
using Robust.Shared.Containers;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
namespace Content.Shared.StatusEffectNew.Components; namespace Content.Shared.StatusEffectNew.Components;
@@ -9,15 +9,14 @@ namespace Content.Shared.StatusEffectNew.Components;
/// Can be used for tracking currently applied status effects. /// Can be used for tracking currently applied status effects.
/// </summary> /// </summary>
[RegisterComponent, NetworkedComponent] [RegisterComponent, NetworkedComponent]
[Access(typeof(SharedStatusEffectsSystem))] [Access(typeof(StatusEffectsSystem))]
public sealed partial class StatusEffectContainerComponent : Component public sealed partial class StatusEffectContainerComponent : Component
{ {
[DataField] public const string ContainerId = "status-effects";
public HashSet<EntityUid> ActiveStatusEffects = new();
}
[Serializable, NetSerializable] /// <summary>
public sealed class StatusEffectContainerComponentState(HashSet<NetEntity> activeStatusEffects) : ComponentState /// The actual container holding references to the active status effects
{ /// </summary>
public readonly HashSet<NetEntity> ActiveStatusEffects = activeStatusEffects; [ViewVariables]
public Container? ActiveStatusEffects;
} }

View File

@@ -0,0 +1,62 @@
using Content.Shared.Alert;
using Content.Shared.StatusEffectNew.Components;
using Robust.Shared.Timing;
namespace Content.Shared.StatusEffectNew;
/// <summary>
/// Handles displaying status effects that should show an alert, optionally with a duration.
/// </summary>
public sealed class StatusEffectAlertSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly AlertsSystem _alerts = default!;
private EntityQuery<StatusEffectComponent> _effectQuery;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<StatusEffectAlertComponent, StatusEffectAppliedEvent>(OnStatusEffectApplied);
SubscribeLocalEvent<StatusEffectAlertComponent, StatusEffectRemovedEvent>(OnStatusEffectRemoved);
SubscribeLocalEvent<StatusEffectAlertComponent, StatusEffectEndTimeUpdatedEvent>(OnEndTimeUpdated);
_effectQuery = GetEntityQuery<StatusEffectComponent>();
}
private void OnStatusEffectApplied(Entity<StatusEffectAlertComponent> ent, ref StatusEffectAppliedEvent args)
{
if (!_effectQuery.TryComp(ent, out var effectComp))
return;
RefreshAlert(ent, args.Target, effectComp.EndEffectTime);
}
private void OnStatusEffectRemoved(Entity<StatusEffectAlertComponent> ent, ref StatusEffectRemovedEvent args)
{
_alerts.ClearAlert(args.Target, ent.Comp.Alert);
}
private void OnEndTimeUpdated(Entity<StatusEffectAlertComponent> ent, ref StatusEffectEndTimeUpdatedEvent args)
{
RefreshAlert(ent, args.Target, args.EndTime);
}
private void RefreshAlert(Entity<StatusEffectAlertComponent> ent, EntityUid target, TimeSpan? endTime)
{
(TimeSpan Start, TimeSpan End)? cooldown = null;
// Make sure the start time of the alert cooldown is still accurate
// This ensures the progress wheel doesn't "reset" every duration change.
if (ent.Comp.ShowDuration
&& endTime is not null
&& _alerts.TryGet(ent.Comp.Alert, out var alert))
{
_alerts.TryGetAlertState(target, alert.AlertKey, out var alertState);
cooldown = (alertState.Cooldown?.Item1 ?? _timing.CurTime, endTime.Value);
}
_alerts.ShowAlert(target, ent.Comp.Alert, cooldown: cooldown);
}
}

View File

@@ -4,7 +4,7 @@ using Robust.Shared.Prototypes;
namespace Content.Shared.StatusEffectNew; namespace Content.Shared.StatusEffectNew;
public abstract partial class SharedStatusEffectsSystem public sealed partial class StatusEffectsSystem
{ {
/// <summary> /// <summary>
/// Increments duration of status effect by <see cref="duration"/>. /// Increments duration of status effect by <see cref="duration"/>.
@@ -103,28 +103,22 @@ public abstract partial class SharedStatusEffectsSystem
/// </summary> /// </summary>
public bool TryRemoveStatusEffect(EntityUid target, EntProtoId effectProto) public bool TryRemoveStatusEffect(EntityUid target, EntProtoId effectProto)
{ {
if (_net.IsClient) //We cant remove the effect on the client (we need someone more robust at networking than me)
return false;
if (!_containerQuery.TryComp(target, out var container)) if (!_containerQuery.TryComp(target, out var container))
return false; return false;
foreach (var effect in container.ActiveStatusEffects) foreach (var effect in container.ActiveStatusEffects?.ContainedEntities ?? [])
{ {
var meta = MetaData(effect); var meta = MetaData(effect);
if (meta.EntityPrototype is not null && meta.EntityPrototype == effectProto)
{
if (!_effectQuery.TryComp(effect, out var effectComp))
return false;
var ev = new StatusEffectRemovedEvent(target); if (meta.EntityPrototype is null
RaiseLocalEvent(effect, ref ev); || meta.EntityPrototype != effectProto)
continue;
QueueDel(effect); if (!_effectQuery.HasComp(effect))
container.ActiveStatusEffects.Remove(effect); return false;
Dirty(target, container);
return true; PredictedQueueDel(effect);
} return true;
} }
return false; return false;
@@ -138,7 +132,7 @@ public abstract partial class SharedStatusEffectsSystem
if (!_containerQuery.TryComp(target, out var container)) if (!_containerQuery.TryComp(target, out var container))
return false; return false;
foreach (var effect in container.ActiveStatusEffects) foreach (var effect in container.ActiveStatusEffects?.ContainedEntities ?? [])
{ {
var meta = MetaData(effect); var meta = MetaData(effect);
if (meta.EntityPrototype is not null && meta.EntityPrototype == effectProto) if (meta.EntityPrototype is not null && meta.EntityPrototype == effectProto)
@@ -157,7 +151,7 @@ public abstract partial class SharedStatusEffectsSystem
if (!_containerQuery.TryComp(target, out var container)) if (!_containerQuery.TryComp(target, out var container))
return false; return false;
foreach (var e in container.ActiveStatusEffects) foreach (var e in container.ActiveStatusEffects?.ContainedEntities ?? [])
{ {
var meta = MetaData(e); var meta = MetaData(e);
if (meta.EntityPrototype is not null && meta.EntityPrototype == effectProto) if (meta.EntityPrototype is not null && meta.EntityPrototype == effectProto)
@@ -188,7 +182,7 @@ public abstract partial class SharedStatusEffectsSystem
if (!Resolve(uid, ref container)) if (!Resolve(uid, ref container))
return false; return false;
foreach (var effect in container.ActiveStatusEffects) foreach (var effect in container.ActiveStatusEffects?.ContainedEntities ?? [])
{ {
var meta = MetaData(effect); var meta = MetaData(effect);
if (meta.EntityPrototype is not null && meta.EntityPrototype == effectProto) if (meta.EntityPrototype is not null && meta.EntityPrototype == effectProto)
@@ -218,7 +212,7 @@ public abstract partial class SharedStatusEffectsSystem
if (!TryEffectsWithComp<T>(uid, out var status)) if (!TryEffectsWithComp<T>(uid, out var status))
return false; return false;
time.Item2 = TimeSpan.Zero; time.EndEffectTime = TimeSpan.Zero;
foreach (var effect in status) foreach (var effect in status)
{ {
@@ -228,7 +222,7 @@ public abstract partial class SharedStatusEffectsSystem
return true; return true;
} }
if (effect.Comp2.EndEffectTime > time.Item2) if (effect.Comp2.EndEffectTime > time.EndEffectTime)
time = (effect.Owner, effect.Comp2.EndEffectTime); time = (effect.Owner, effect.Comp2.EndEffectTime);
} }
return true; return true;
@@ -249,7 +243,7 @@ public abstract partial class SharedStatusEffectsSystem
if (!_containerQuery.TryComp(uid, out var container)) if (!_containerQuery.TryComp(uid, out var container))
return false; return false;
foreach (var effect in container.ActiveStatusEffects) foreach (var effect in container.ActiveStatusEffects?.ContainedEntities ?? [])
{ {
var meta = MetaData(effect); var meta = MetaData(effect);
if (meta.EntityPrototype is not null && meta.EntityPrototype == effectProto) if (meta.EntityPrototype is not null && meta.EntityPrototype == effectProto)
@@ -273,7 +267,7 @@ public abstract partial class SharedStatusEffectsSystem
if (!_containerQuery.TryComp(uid, out var container)) if (!_containerQuery.TryComp(uid, out var container))
return false; return false;
foreach (var effect in container.ActiveStatusEffects) foreach (var effect in container.ActiveStatusEffects?.ContainedEntities ?? [])
{ {
var meta = MetaData(effect); var meta = MetaData(effect);
if (meta.EntityPrototype is not null && meta.EntityPrototype == effectProto) if (meta.EntityPrototype is not null && meta.EntityPrototype == effectProto)
@@ -293,7 +287,7 @@ public abstract partial class SharedStatusEffectsSystem
if (!_containerQuery.TryComp(target, out var container)) if (!_containerQuery.TryComp(target, out var container))
return false; return false;
foreach (var effect in container.ActiveStatusEffects) foreach (var effect in container.ActiveStatusEffects?.ContainedEntities ?? [])
{ {
if (HasComp<T>(effect)) if (HasComp<T>(effect))
return true; return true;
@@ -311,7 +305,7 @@ public abstract partial class SharedStatusEffectsSystem
if (!_containerQuery.TryComp(target, out var container)) if (!_containerQuery.TryComp(target, out var container))
return false; return false;
foreach (var effect in container.ActiveStatusEffects) foreach (var effect in container.ActiveStatusEffects?.ContainedEntities ?? [])
{ {
if (!_effectQuery.TryComp(effect, out var statusComp)) if (!_effectQuery.TryComp(effect, out var statusComp))
continue; continue;
@@ -338,7 +332,7 @@ public abstract partial class SharedStatusEffectsSystem
if (!_containerQuery.TryComp(target, out var container)) if (!_containerQuery.TryComp(target, out var container))
return false; return false;
foreach (var effect in container.ActiveStatusEffects) foreach (var effect in container.ActiveStatusEffects?.ContainedEntities ?? [])
{ {
if (!HasComp<T>(effect)) if (!HasComp<T>(effect))
continue; continue;

View File

@@ -3,20 +3,20 @@ using Robust.Shared.Player;
namespace Content.Shared.StatusEffectNew; namespace Content.Shared.StatusEffectNew;
public abstract partial class SharedStatusEffectsSystem public sealed partial class StatusEffectsSystem
{ {
protected void InitializeRelay() private void InitializeRelay()
{ {
SubscribeLocalEvent<StatusEffectContainerComponent, LocalPlayerAttachedEvent>(RelayStatusEffectEvent); SubscribeLocalEvent<StatusEffectContainerComponent, LocalPlayerAttachedEvent>(RelayStatusEffectEvent);
SubscribeLocalEvent<StatusEffectContainerComponent, LocalPlayerDetachedEvent>(RelayStatusEffectEvent); SubscribeLocalEvent<StatusEffectContainerComponent, LocalPlayerDetachedEvent>(RelayStatusEffectEvent);
} }
protected void RefRelayStatusEffectEvent<T>(EntityUid uid, StatusEffectContainerComponent component, ref T args) where T : struct private void RefRelayStatusEffectEvent<T>(EntityUid uid, StatusEffectContainerComponent component, ref T args) where T : struct
{ {
RelayEvent((uid, component), ref args); RelayEvent((uid, component), ref args);
} }
protected void RelayStatusEffectEvent<T>(EntityUid uid, StatusEffectContainerComponent component, T args) where T : class private void RelayStatusEffectEvent<T>(EntityUid uid, StatusEffectContainerComponent component, T args) where T : class
{ {
RelayEvent((uid, component), args); RelayEvent((uid, component), args);
} }
@@ -25,7 +25,7 @@ public abstract partial class SharedStatusEffectsSystem
{ {
// this copies the by-ref event if it is a struct // this copies the by-ref event if it is a struct
var ev = new StatusEffectRelayedEvent<T>(args); var ev = new StatusEffectRelayedEvent<T>(args);
foreach (var activeEffect in statusEffect.Comp.ActiveStatusEffects) foreach (var activeEffect in statusEffect.Comp.ActiveStatusEffects?.ContainedEntities ?? [])
{ {
RaiseLocalEvent(activeEffect, ref ev); RaiseLocalEvent(activeEffect, ref ev);
} }
@@ -37,7 +37,7 @@ public abstract partial class SharedStatusEffectsSystem
{ {
// this copies the by-ref event if it is a struct // this copies the by-ref event if it is a struct
var ev = new StatusEffectRelayedEvent<T>(args); var ev = new StatusEffectRelayedEvent<T>(args);
foreach (var activeEffect in statusEffect.Comp.ActiveStatusEffects) foreach (var activeEffect in statusEffect.Comp.ActiveStatusEffects?.ContainedEntities ?? [])
{ {
RaiseLocalEvent(activeEffect, ref ev); RaiseLocalEvent(activeEffect, ref ev);
} }

View File

@@ -1,9 +1,7 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Content.Shared.Alert;
using Content.Shared.StatusEffectNew.Components; using Content.Shared.StatusEffectNew.Components;
using Content.Shared.Whitelist; using Content.Shared.Whitelist;
using Robust.Shared.GameStates; using Robust.Shared.Containers;
using Robust.Shared.Network;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Timing; using Robust.Shared.Timing;
@@ -13,15 +11,12 @@ namespace Content.Shared.StatusEffectNew;
/// This system controls status effects, their lifetime, and provides an API for adding them to entities, /// This system controls status effects, their lifetime, and provides an API for adding them to entities,
/// removing them from entities, or getting information about current effects on entities. /// removing them from entities, or getting information about current effects on entities.
/// </summary> /// </summary>
public abstract partial class SharedStatusEffectsSystem : EntitySystem public sealed partial class StatusEffectsSystem : EntitySystem
{ {
[Dependency] private readonly AlertsSystem _alerts = default!;
[Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!; [Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
[Dependency] private readonly IPrototypeManager _proto = default!; [Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IComponentFactory _compFactory = default!;
[Dependency] private readonly INetManager _net = default!;
private EntityQuery<StatusEffectContainerComponent> _containerQuery; private EntityQuery<StatusEffectContainerComponent> _containerQuery;
private EntityQuery<StatusEffectComponent> _effectQuery; private EntityQuery<StatusEffectComponent> _effectQuery;
@@ -32,20 +27,15 @@ public abstract partial class SharedStatusEffectsSystem : EntitySystem
InitializeRelay(); InitializeRelay();
SubscribeLocalEvent<StatusEffectComponent, StatusEffectAppliedEvent>(OnStatusEffectApplied); SubscribeLocalEvent<StatusEffectContainerComponent, ComponentInit>(OnStatusContainerInit);
SubscribeLocalEvent<StatusEffectComponent, StatusEffectRemovedEvent>(OnStatusEffectRemoved); SubscribeLocalEvent<StatusEffectContainerComponent, ComponentShutdown>(OnStatusContainerShutdown);
SubscribeLocalEvent<StatusEffectContainerComponent, EntInsertedIntoContainerMessage>(OnEntityInserted);
SubscribeLocalEvent<StatusEffectContainerComponent, ComponentGetState>(OnGetState); SubscribeLocalEvent<StatusEffectContainerComponent, EntRemovedFromContainerMessage>(OnEntityRemoved);
_containerQuery = GetEntityQuery<StatusEffectContainerComponent>(); _containerQuery = GetEntityQuery<StatusEffectContainerComponent>();
_effectQuery = GetEntityQuery<StatusEffectComponent>(); _effectQuery = GetEntityQuery<StatusEffectComponent>();
} }
private void OnGetState(Entity<StatusEffectContainerComponent> ent, ref ComponentGetState args)
{
args.State = new StatusEffectContainerComponentState(GetNetEntitySet(ent.Comp.ActiveStatusEffects));
}
public override void Update(float frameTime) public override void Update(float frameTime)
{ {
base.Update(frameTime); base.Update(frameTime);
@@ -70,15 +60,59 @@ public abstract partial class SharedStatusEffectsSystem : EntitySystem
} }
} }
private void AddStatusEffectTime(EntityUid effect, TimeSpan delta) private void OnStatusContainerInit(Entity<StatusEffectContainerComponent> ent, ref ComponentInit args)
{ {
if (!_effectQuery.TryComp(effect, out var effectComp)) ent.Comp.ActiveStatusEffects =
_container.EnsureContainer<Container>(ent, StatusEffectContainerComponent.ContainerId);
// We show the contents of the container to allow status effects to have visible sprites.
ent.Comp.ActiveStatusEffects.ShowContents = true;
}
private void OnStatusContainerShutdown(Entity<StatusEffectContainerComponent> ent, ref ComponentShutdown args)
{
if (ent.Comp.ActiveStatusEffects is { } container)
_container.ShutdownContainer(container);
}
private void OnEntityInserted(Entity<StatusEffectContainerComponent> ent, ref EntInsertedIntoContainerMessage args)
{
if (args.Container.ID != StatusEffectContainerComponent.ContainerId)
return; return;
effectComp.EndEffectTime += delta; if (!TryComp<StatusEffectComponent>(args.Entity, out var statusComp))
Dirty(effect, effectComp); return;
ShowAlertIfNeeded(effectComp); // Make sure AppliedTo is set correctly so events can rely on it
if (statusComp.AppliedTo != ent)
{
statusComp.AppliedTo = ent;
Dirty(args.Entity, statusComp);
}
var ev = new StatusEffectAppliedEvent(ent);
RaiseLocalEvent(args.Entity, ref ev);
}
private void OnEntityRemoved(Entity<StatusEffectContainerComponent> ent, ref EntRemovedFromContainerMessage args)
{
if (args.Container.ID != StatusEffectContainerComponent.ContainerId)
return;
if (!TryComp<StatusEffectComponent>(args.Entity, out var statusComp))
return;
var ev = new StatusEffectRemovedEvent(ent);
RaiseLocalEvent(args.Entity, ref ev);
// Clear AppliedTo after events are handled so event handlers can use it.
if (statusComp.AppliedTo == null)
return;
// Why not just delete it? Well, that might end up being best, but this
// could theoretically allow for moving status effects from one entity
// to another. That might be good to have for polymorphs or something.
statusComp.AppliedTo = null;
Dirty(args.Entity, statusComp);
} }
private void SetStatusEffectTime(EntityUid effect, TimeSpan? duration) private void SetStatusEffectTime(EntityUid effect, TimeSpan? duration)
@@ -97,8 +131,6 @@ public abstract partial class SharedStatusEffectsSystem : EntitySystem
effectComp.EndEffectTime = _timing.CurTime + duration; effectComp.EndEffectTime = _timing.CurTime + duration;
Dirty(effect, effectComp); Dirty(effect, effectComp);
ShowAlertIfNeeded(effectComp);
} }
private void UpdateStatusEffectTime(EntityUid effect, TimeSpan? duration) private void UpdateStatusEffectTime(EntityUid effect, TimeSpan? duration)
@@ -122,24 +154,6 @@ public abstract partial class SharedStatusEffectsSystem : EntitySystem
} }
Dirty(effect, effectComp); Dirty(effect, effectComp);
ShowAlertIfNeeded(effectComp);
}
private void OnStatusEffectApplied(Entity<StatusEffectComponent> ent, ref StatusEffectAppliedEvent args)
{
StatusEffectComponent statusEffect = ent;
ShowAlertIfNeeded(statusEffect);
}
private void OnStatusEffectRemoved(Entity<StatusEffectComponent> ent, ref StatusEffectRemovedEvent args)
{
if (ent.Comp.AppliedTo is null)
return;
if (ent.Comp is { AppliedTo: not null, Alert: not null })
_alerts.ClearAlert(ent.Comp.AppliedTo.Value, ent.Comp.Alert.Value);
} }
private bool CanAddStatusEffect(EntityUid uid, EntProtoId effectProto) private bool CanAddStatusEffect(EntityUid uid, EntProtoId effectProto)
@@ -147,7 +161,7 @@ public abstract partial class SharedStatusEffectsSystem : EntitySystem
if (!_proto.TryIndex(effectProto, out var effectProtoData)) if (!_proto.TryIndex(effectProto, out var effectProtoData))
return false; return false;
if (!effectProtoData.TryGetComponent<StatusEffectComponent>(out var effectProtoComp, _compFactory)) if (!effectProtoData.TryGetComponent<StatusEffectComponent>(out var effectProtoComp, Factory))
return false; return false;
if (!_whitelist.CheckBoth(uid, effectProtoComp.Blacklist, effectProtoComp.Whitelist)) if (!_whitelist.CheckBoth(uid, effectProtoComp.Blacklist, effectProtoComp.Whitelist))
@@ -181,43 +195,50 @@ public abstract partial class SharedStatusEffectsSystem : EntitySystem
if (!CanAddStatusEffect(target, effectProto)) if (!CanAddStatusEffect(target, effectProto))
return false; return false;
var container = EnsureComp<StatusEffectContainerComponent>(target); EnsureComp<StatusEffectContainerComponent>(target);
// And only if all checks passed we spawn the effect
if (!PredictedTrySpawnInContainer(effectProto,
target,
StatusEffectContainerComponent.ContainerId,
out var effect))
return false;
//And only if all checks passed we spawn the effect
var effect = PredictedSpawnAttachedTo(effectProto, Transform(target).Coordinates);
_transform.SetParent(effect, target);
if (!_effectQuery.TryComp(effect, out var effectComp)) if (!_effectQuery.TryComp(effect, out var effectComp))
return false; return false;
statusEffect = effect; statusEffect = effect;
SetStatusEffectEndTime((effect.Value, effectComp), _timing.CurTime + duration);
if (duration != null)
effectComp.EndEffectTime = _timing.CurTime + duration;
container.ActiveStatusEffects.Add(effect);
effectComp.AppliedTo = target;
Dirty(target, container);
Dirty(effect, effectComp);
var ev = new StatusEffectAppliedEvent(target);
RaiseLocalEvent(effect, ref ev);
return true; return true;
} }
private void ShowAlertIfNeeded(StatusEffectComponent effectComp) private void AddStatusEffectTime(EntityUid effect, TimeSpan delta)
{ {
if (effectComp is { AppliedTo: not null, Alert: not null }) if (!_effectQuery.TryComp(effect, out var effectComp))
{ return;
(TimeSpan, TimeSpan)? cooldown = effectComp.EndEffectTime is null
? null // If we don't have an end time set, we want to just make the status effect end in delta time from now.
: (_timing.CurTime, effectComp.EndEffectTime.Value); SetStatusEffectEndTime((effect, effectComp), (effectComp.EndEffectTime ?? _timing.CurTime) + delta);
_alerts.ShowAlert( }
effectComp.AppliedTo.Value,
effectComp.Alert.Value, private void SetStatusEffectEndTime(Entity<StatusEffectComponent?> ent, TimeSpan? endTime)
cooldown: cooldown {
); if (!_effectQuery.Resolve(ent, ref ent.Comp))
} return;
if (ent.Comp.EndEffectTime == endTime)
return;
ent.Comp.EndEffectTime = endTime;
if (ent.Comp.AppliedTo is not { } appliedTo)
return; // Not much we can do!
var ev = new StatusEffectEndTimeUpdatedEvent(appliedTo, endTime);
RaiseLocalEvent(ent, ref ev);
Dirty(ent);
} }
} }
@@ -238,3 +259,11 @@ public readonly record struct StatusEffectRemovedEvent(EntityUid Target);
/// </summary> /// </summary>
[ByRefEvent] [ByRefEvent]
public record struct BeforeStatusEffectAddedEvent(EntProtoId Effect, bool Cancelled = false); public record struct BeforeStatusEffectAddedEvent(EntProtoId Effect, bool Cancelled = false);
/// <summary>
/// Raised on an effect entity when its <see cref="StatusEffectComponent.EndEffectTime"/> is updated in any way.
/// </summary>
/// <param name="Target">The entity the effect is attached to.</param>
/// <param name="EndTime">The new end time of the status effect, included for convenience.</param>
[ByRefEvent]
public record struct StatusEffectEndTimeUpdatedEvent(EntityUid Target, TimeSpan? EndTime);