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 IEntitySystemManager _sysMan = 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 bool RequestScreenTexture => true;
@@ -33,7 +33,7 @@ public sealed class DrowsinessOverlay : Overlay
{
IoCManager.InjectDependencies(this);
_statusEffects = _sysMan.GetEntitySystem<SharedStatusEffectsSystem>();
_statusEffects = _sysMan.GetEntitySystem<StatusEffectsSystem>();
_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 IOverlayManager _overlayMan = default!;
[Dependency] private readonly SharedStatusEffectsSystem _statusEffects = default!;
[Dependency] private readonly StatusEffectsSystem _statusEffects = 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 IEntitySystemManager _sysMan = 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 bool RequestScreenTexture => true;
@@ -41,7 +41,7 @@ public sealed class RainbowOverlay : Overlay
{
IoCManager.InjectDependencies(this);
_statusEffects = _sysMan.GetEntitySystem<SharedStatusEffectsSystem>();
_statusEffects = _sysMan.GetEntitySystem<StatusEffectsSystem>();
_rainbowShader = _prototypeManager.Index(Shader).InstanceUnique();
_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.Drowsiness;
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.StatusEffectNew;
using Robust.Shared.Random;
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 SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedEmitSoundSystem _emitSound = default!;
[Dependency] private readonly StatusEffectsSystem _statusEffectOld = default!;
[Dependency] private readonly SharedStatusEffectsSystem _statusEffectNew = default!;
[Dependency] private readonly StatusEffect.StatusEffectsSystem _statusEffectOld = default!;
[Dependency] private readonly StatusEffectNew.StatusEffectsSystem _statusEffectNew = default!;
public static readonly EntProtoId SleepActionId = "ActionSleep";
public static readonly EntProtoId WakeActionId = "ActionWake";

View File

@@ -34,7 +34,7 @@ public sealed partial class ModifyStatusEffect : EntityEffect
/// <inheritdoc />
public override void Effect(EntityEffectBaseArgs args)
{
var statusSys = args.EntityManager.EntitySysManager.GetEntitySystem<SharedStatusEffectsSystem>();
var statusSys = args.EntityManager.EntitySysManager.GetEntitySystem<StatusEffectsSystem>();
var time = Time;
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 IGameTiming _timing = default!;
[Dependency] private readonly SharedStatusEffectsSystem _statusEffects = default!;
[Dependency] private readonly StatusEffectsSystem _statusEffects = default!;
private bool _icSsdSleep;
private float _icSsdSleepTime;

View File

@@ -9,7 +9,7 @@ using Robust.Shared.Utility;
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
{
[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>
/// <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>
[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,
StatusEffectsComponent? status = null)
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,
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 you want special 'effect merging' behavior, do it your own damn self!
/// </remarks>
[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,
@@ -260,7 +260,7 @@ namespace Content.Shared.StatusEffect
/// 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.
/// </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,
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="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>
[Obsolete("Migration to Content.Shared.StatusEffectNew.SharedStatusEffectsSystem is required")]
[Obsolete("Migration to Content.Shared.StatusEffectNew.StatusEffectsSystem is required")]
public bool TryRemoveAllStatusEffects(EntityUid uid,
StatusEffectsComponent? status = null)
{
@@ -328,7 +328,7 @@ namespace Content.Shared.StatusEffect
/// <param name="uid">The entity to check on.</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>
[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,
StatusEffectsComponent? status = null)
{
@@ -346,7 +346,7 @@ namespace Content.Shared.StatusEffect
/// <param name="uid">The entity to check on.</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>
[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)
{
// 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="time">The amount of time to add.</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,
StatusEffectsComponent? status = null)
{
@@ -405,7 +405,7 @@ namespace Content.Shared.StatusEffect
/// <param name="key">The status effect to remove time from.</param>
/// <param name="time">The amount of time to add.</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,
StatusEffectsComponent? status = null)
{
@@ -441,7 +441,7 @@ namespace Content.Shared.StatusEffect
/// <remarks>
/// Not used internally; just sets it itself.
/// </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,
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="status">The status effects component to use, if any.</param>
/// <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,
[NotNullWhen(true)] out (TimeSpan, TimeSpan)? time,
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.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
[Access(typeof(SharedStatusEffectsSystem))]
[Access(typeof(StatusEffectsSystem))]
[EntityCategory("StatusEffects")]
public sealed partial class StatusEffectComponent : Component
{
@@ -21,12 +21,6 @@ public sealed partial class StatusEffectComponent : Component
[DataField, AutoNetworkedField]
public EntityUid? AppliedTo;
/// <summary>
/// Status effect indication for the player. If Null, no Alert will be displayed.
/// </summary>
[DataField]
public ProtoId<AlertPrototype>? Alert;
/// <summary>
/// When this effect will end. If Null, the effect lasts indefinitely.
/// </summary>

View File

@@ -1,5 +1,5 @@
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
namespace Content.Shared.StatusEffectNew.Components;
@@ -9,15 +9,14 @@ namespace Content.Shared.StatusEffectNew.Components;
/// Can be used for tracking currently applied status effects.
/// </summary>
[RegisterComponent, NetworkedComponent]
[Access(typeof(SharedStatusEffectsSystem))]
[Access(typeof(StatusEffectsSystem))]
public sealed partial class StatusEffectContainerComponent : Component
{
[DataField]
public HashSet<EntityUid> ActiveStatusEffects = new();
}
public const string ContainerId = "status-effects";
[Serializable, NetSerializable]
public sealed class StatusEffectContainerComponentState(HashSet<NetEntity> activeStatusEffects) : ComponentState
{
public readonly HashSet<NetEntity> ActiveStatusEffects = activeStatusEffects;
/// <summary>
/// The actual container holding references to the active status effects
/// </summary>
[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;
public abstract partial class SharedStatusEffectsSystem
public sealed partial class StatusEffectsSystem
{
/// <summary>
/// Increments duration of status effect by <see cref="duration"/>.
@@ -103,28 +103,22 @@ public abstract partial class SharedStatusEffectsSystem
/// </summary>
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))
return false;
foreach (var effect in container.ActiveStatusEffects)
foreach (var effect in container.ActiveStatusEffects?.ContainedEntities ?? [])
{
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);
RaiseLocalEvent(effect, ref ev);
if (meta.EntityPrototype is null
|| meta.EntityPrototype != effectProto)
continue;
QueueDel(effect);
container.ActiveStatusEffects.Remove(effect);
Dirty(target, container);
return true;
}
if (!_effectQuery.HasComp(effect))
return false;
PredictedQueueDel(effect);
return true;
}
return false;
@@ -138,7 +132,7 @@ public abstract partial class SharedStatusEffectsSystem
if (!_containerQuery.TryComp(target, out var container))
return false;
foreach (var effect in container.ActiveStatusEffects)
foreach (var effect in container.ActiveStatusEffects?.ContainedEntities ?? [])
{
var meta = MetaData(effect);
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))
return false;
foreach (var e in container.ActiveStatusEffects)
foreach (var e in container.ActiveStatusEffects?.ContainedEntities ?? [])
{
var meta = MetaData(e);
if (meta.EntityPrototype is not null && meta.EntityPrototype == effectProto)
@@ -188,7 +182,7 @@ public abstract partial class SharedStatusEffectsSystem
if (!Resolve(uid, ref container))
return false;
foreach (var effect in container.ActiveStatusEffects)
foreach (var effect in container.ActiveStatusEffects?.ContainedEntities ?? [])
{
var meta = MetaData(effect);
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))
return false;
time.Item2 = TimeSpan.Zero;
time.EndEffectTime = TimeSpan.Zero;
foreach (var effect in status)
{
@@ -228,7 +222,7 @@ public abstract partial class SharedStatusEffectsSystem
return true;
}
if (effect.Comp2.EndEffectTime > time.Item2)
if (effect.Comp2.EndEffectTime > time.EndEffectTime)
time = (effect.Owner, effect.Comp2.EndEffectTime);
}
return true;
@@ -249,7 +243,7 @@ public abstract partial class SharedStatusEffectsSystem
if (!_containerQuery.TryComp(uid, out var container))
return false;
foreach (var effect in container.ActiveStatusEffects)
foreach (var effect in container.ActiveStatusEffects?.ContainedEntities ?? [])
{
var meta = MetaData(effect);
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))
return false;
foreach (var effect in container.ActiveStatusEffects)
foreach (var effect in container.ActiveStatusEffects?.ContainedEntities ?? [])
{
var meta = MetaData(effect);
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))
return false;
foreach (var effect in container.ActiveStatusEffects)
foreach (var effect in container.ActiveStatusEffects?.ContainedEntities ?? [])
{
if (HasComp<T>(effect))
return true;
@@ -311,7 +305,7 @@ public abstract partial class SharedStatusEffectsSystem
if (!_containerQuery.TryComp(target, out var container))
return false;
foreach (var effect in container.ActiveStatusEffects)
foreach (var effect in container.ActiveStatusEffects?.ContainedEntities ?? [])
{
if (!_effectQuery.TryComp(effect, out var statusComp))
continue;
@@ -338,7 +332,7 @@ public abstract partial class SharedStatusEffectsSystem
if (!_containerQuery.TryComp(target, out var container))
return false;
foreach (var effect in container.ActiveStatusEffects)
foreach (var effect in container.ActiveStatusEffects?.ContainedEntities ?? [])
{
if (!HasComp<T>(effect))
continue;

View File

@@ -3,20 +3,20 @@ using Robust.Shared.Player;
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, 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);
}
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);
}
@@ -25,7 +25,7 @@ public abstract partial class SharedStatusEffectsSystem
{
// this copies the by-ref event if it is a struct
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);
}
@@ -37,7 +37,7 @@ public abstract partial class SharedStatusEffectsSystem
{
// this copies the by-ref event if it is a struct
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);
}

View File

@@ -1,9 +1,7 @@
using System.Diagnostics.CodeAnalysis;
using Content.Shared.Alert;
using Content.Shared.StatusEffectNew.Components;
using Content.Shared.Whitelist;
using Robust.Shared.GameStates;
using Robust.Shared.Network;
using Robust.Shared.Containers;
using Robust.Shared.Prototypes;
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,
/// removing them from entities, or getting information about current effects on entities.
/// </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 SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelist = 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<StatusEffectComponent> _effectQuery;
@@ -32,20 +27,15 @@ public abstract partial class SharedStatusEffectsSystem : EntitySystem
InitializeRelay();
SubscribeLocalEvent<StatusEffectComponent, StatusEffectAppliedEvent>(OnStatusEffectApplied);
SubscribeLocalEvent<StatusEffectComponent, StatusEffectRemovedEvent>(OnStatusEffectRemoved);
SubscribeLocalEvent<StatusEffectContainerComponent, ComponentGetState>(OnGetState);
SubscribeLocalEvent<StatusEffectContainerComponent, ComponentInit>(OnStatusContainerInit);
SubscribeLocalEvent<StatusEffectContainerComponent, ComponentShutdown>(OnStatusContainerShutdown);
SubscribeLocalEvent<StatusEffectContainerComponent, EntInsertedIntoContainerMessage>(OnEntityInserted);
SubscribeLocalEvent<StatusEffectContainerComponent, EntRemovedFromContainerMessage>(OnEntityRemoved);
_containerQuery = GetEntityQuery<StatusEffectContainerComponent>();
_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)
{
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;
effectComp.EndEffectTime += delta;
Dirty(effect, effectComp);
if (!TryComp<StatusEffectComponent>(args.Entity, out var statusComp))
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)
@@ -97,8 +131,6 @@ public abstract partial class SharedStatusEffectsSystem : EntitySystem
effectComp.EndEffectTime = _timing.CurTime + duration;
Dirty(effect, effectComp);
ShowAlertIfNeeded(effectComp);
}
private void UpdateStatusEffectTime(EntityUid effect, TimeSpan? duration)
@@ -122,24 +154,6 @@ public abstract partial class SharedStatusEffectsSystem : EntitySystem
}
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)
@@ -147,7 +161,7 @@ public abstract partial class SharedStatusEffectsSystem : EntitySystem
if (!_proto.TryIndex(effectProto, out var effectProtoData))
return false;
if (!effectProtoData.TryGetComponent<StatusEffectComponent>(out var effectProtoComp, _compFactory))
if (!effectProtoData.TryGetComponent<StatusEffectComponent>(out var effectProtoComp, Factory))
return false;
if (!_whitelist.CheckBoth(uid, effectProtoComp.Blacklist, effectProtoComp.Whitelist))
@@ -181,43 +195,50 @@ public abstract partial class SharedStatusEffectsSystem : EntitySystem
if (!CanAddStatusEffect(target, effectProto))
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))
return false;
statusEffect = effect;
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);
SetStatusEffectEndTime((effect.Value, effectComp), _timing.CurTime + duration);
return true;
}
private void ShowAlertIfNeeded(StatusEffectComponent effectComp)
private void AddStatusEffectTime(EntityUid effect, TimeSpan delta)
{
if (effectComp is { AppliedTo: not null, Alert: not null })
{
(TimeSpan, TimeSpan)? cooldown = effectComp.EndEffectTime is null
? null
: (_timing.CurTime, effectComp.EndEffectTime.Value);
_alerts.ShowAlert(
effectComp.AppliedTo.Value,
effectComp.Alert.Value,
cooldown: cooldown
);
}
if (!_effectQuery.TryComp(effect, out var effectComp))
return;
// If we don't have an end time set, we want to just make the status effect end in delta time from now.
SetStatusEffectEndTime((effect, effectComp), (effectComp.EndEffectTime ?? _timing.CurTime) + delta);
}
private void SetStatusEffectEndTime(Entity<StatusEffectComponent?> ent, TimeSpan? endTime)
{
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>
[ByRefEvent]
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);