[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;
{
IoCManager.InjectDependencies(this);
- _statusEffects = _sysMan.GetEntitySystem<SharedStatusEffectsSystem>();
+ _statusEffects = _sysMan.GetEntitySystem<StatusEffectsSystem>();
_drowsinessShader = _prototypeManager.Index(Shader).InstanceUnique();
}
{
[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!;
[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;
{
IoCManager.InjectDependencies(this);
- _statusEffects = _sysMan.GetEntitySystem<SharedStatusEffectsSystem>();
+ _statusEffects = _sysMan.GetEntitySystem<StatusEffectsSystem>();
_rainbowShader = _prototypeManager.Index(Shader).InstanceUnique();
_config.OnValueChanged(CCVars.ReducedMotion, OnReducedMotionChanged, invokeImmediately: true);
+++ /dev/null
-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);
- }
- }
-}
-using Content.Server.StatusEffectNew;
using Content.Shared.Bed.Sleep;
using Content.Shared.Drowsiness;
using Content.Shared.StatusEffectNew;
+++ /dev/null
-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);
- }
- }
-}
-using Content.Server.StatusEffectNew;
using Content.Shared.Bed.Sleep;
+using Content.Shared.StatusEffectNew;
using Robust.Shared.Random;
namespace Content.Server.Traits.Assorted;
[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";
/// <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)
[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;
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!;
/// <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()
}
- [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)
{
/// 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,
/// 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)
{
/// <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)
{
/// <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)
{
/// <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
/// <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)
{
/// <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)
{
/// <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)
{
/// <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)
--- /dev/null
+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;
+}
/// 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
{
[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>
+using Robust.Shared.Containers;
using Robust.Shared.GameStates;
-using Robust.Shared.Serialization;
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;
}
--- /dev/null
+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);
+ }
+}
namespace Content.Shared.StatusEffectNew;
-public abstract partial class SharedStatusEffectsSystem
+public sealed partial class StatusEffectsSystem
{
/// <summary>
/// Increments duration of status effect by <see cref="duration"/>.
/// </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;
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 (!_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)
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)
if (!TryEffectsWithComp<T>(uid, out var status))
return false;
- time.Item2 = TimeSpan.Zero;
+ time.EndEffectTime = TimeSpan.Zero;
foreach (var effect in status)
{
return true;
}
- if (effect.Comp2.EndEffectTime > time.Item2)
+ if (effect.Comp2.EndEffectTime > time.EndEffectTime)
time = (effect.Owner, effect.Comp2.EndEffectTime);
}
return true;
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)
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)
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;
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;
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;
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);
}
{
// 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);
}
{
// 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);
}
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;
/// 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;
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);
}
}
- 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;
+
+ // 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;
- ShowAlertIfNeeded(effectComp);
+ 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)
effectComp.EndEffectTime = _timing.CurTime + duration;
Dirty(effect, effectComp);
-
- ShowAlertIfNeeded(effectComp);
}
private void UpdateStatusEffectTime(EntityUid effect, TimeSpan? duration)
}
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)
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))
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;
+ 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);
+ return true;
+ }
- var ev = new StatusEffectAppliedEvent(target);
- RaiseLocalEvent(effect, ref ev);
+ private void AddStatusEffectTime(EntityUid effect, TimeSpan delta)
+ {
+ if (!_effectQuery.TryComp(effect, out var effectComp))
+ return;
- return true;
+ // 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 ShowAlertIfNeeded(StatusEffectComponent effectComp)
+ private void SetStatusEffectEndTime(Entity<StatusEffectComponent?> ent, TimeSpan? endTime)
{
- 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.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);
}
}
/// </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);