using Content.Shared.Mobs.Components;
using Content.Shared.Pointing;
using Content.Shared.Popups;
+using Content.Shared.Rejuvenate;
using Content.Shared.Slippery;
using Content.Shared.Sound;
using Content.Shared.Sound.Components;
SubscribeLocalEvent<SleepingComponent, DamageChangedEvent>(OnDamageChanged);
SubscribeLocalEvent<SleepingComponent, EntityZombifiedEvent>(OnZombified);
SubscribeLocalEvent<SleepingComponent, MobStateChangedEvent>(OnMobStateChanged);
- SubscribeLocalEvent<SleepingComponent, MapInitEvent>(OnMapInit);
+ SubscribeLocalEvent<SleepingComponent, ComponentInit>(OnCompInit);
SubscribeLocalEvent<SleepingComponent, SpeakAttemptEvent>(OnSpeakAttempt);
SubscribeLocalEvent<SleepingComponent, CanSeeAttemptEvent>(OnSeeAttempt);
SubscribeLocalEvent<SleepingComponent, PointAttemptEvent>(OnPointAttempt);
SubscribeLocalEvent<SleepingComponent, InteractHandEvent>(OnInteractHand);
SubscribeLocalEvent<SleepingComponent, StunEndAttemptEvent>(OnStunEndAttempt);
SubscribeLocalEvent<SleepingComponent, StandUpAttemptEvent>(OnStandUpAttempt);
+ SubscribeLocalEvent<SleepingComponent, RejuvenateEvent>(OnRejuvenate);
SubscribeLocalEvent<ForcedSleepingStatusEffectComponent, StatusEffectAppliedEvent>(OnStatusEffectApplied);
SubscribeLocalEvent<SleepingComponent, UnbuckleAttemptEvent>(OnUnbuckleAttempt);
RemComp<SpamEmitSoundComponent>(ent);
}
- private void OnMapInit(Entity<SleepingComponent> ent, ref MapInitEvent args)
+ private void OnCompInit(Entity<SleepingComponent> ent, ref ComponentInit args)
{
var ev = new SleepStateChangedEvent(true);
RaiseLocalEvent(ent, ref ev);
args.Cancelled = true;
}
+ private void OnRejuvenate(Entity<SleepingComponent> ent, ref RejuvenateEvent args)
+ {
+ TryWaking((ent.Owner, ent.Comp), true);
+ }
+
private void OnExamined(Entity<SleepingComponent> ent, ref ExaminedEvent args)
{
if (args.IsInDetailsRange)
[DataField]
public float Time = 2.0f;
+ /// <summary>
+ /// Delay before the effect starts. If another effect is added with a shorter delay, it takes precedence.
+ /// </summary>
+ [DataField]
+ public float Delay = 0f;
+
/// <remarks>
/// true - refresh status effect time (update to greater value), false - accumulate status effect time.
/// </remarks>
{
case StatusEffectMetabolismType.Add:
if (Refresh)
- statusSys.TryUpdateStatusEffectDuration(args.TargetEntity, EffectProto, duration);
+ statusSys.TryUpdateStatusEffectDuration(args.TargetEntity, EffectProto, duration, Delay > 0 ? TimeSpan.FromSeconds(Delay) : null);
else
- statusSys.TryAddStatusEffectDuration(args.TargetEntity, EffectProto, duration);
+ statusSys.TryAddStatusEffectDuration(args.TargetEntity, EffectProto, duration, Delay > 0 ? TimeSpan.FromSeconds(Delay) : null);
break;
case StatusEffectMetabolismType.Remove:
statusSys.TryAddTime(args.TargetEntity, EffectProto, -duration);
break;
case StatusEffectMetabolismType.Set:
- statusSys.TrySetStatusEffectDuration(args.TargetEntity, EffectProto, duration);
+ statusSys.TrySetStatusEffectDuration(args.TargetEntity, EffectProto, duration, TimeSpan.FromSeconds(Delay));
break;
}
}
/// <inheritdoc />
- protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
- => Loc.GetString(
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
+ Delay > 0
+ ? Loc.GetString(
+ "reagent-effect-guidebook-status-effect-delay",
+ ("chance", Probability),
+ ("type", Type),
+ ("time", Time),
+ ("key", prototype.Index(EffectProto).Name),
+ ("delay", Delay))
+ : Loc.GetString(
"reagent-effect-guidebook-status-effect",
("chance", Probability),
("type", Type),
/// Marker component for all status effects - every status effect entity should have it.
/// Provides a link between the effect and the affected entity, and some data common to all status effects.
/// </summary>
-[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(fieldDeltas: true), AutoGenerateComponentPause]
[Access(typeof(StatusEffectsSystem))]
[EntityCategory("StatusEffects")]
public sealed partial class StatusEffectComponent : Component
[DataField, AutoNetworkedField]
public EntityUid? AppliedTo;
+ /// <summary>
+ /// When this effect will start. Set to Timespan.Zero to start the effect immediately.
+ /// </summary>
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField, AutoNetworkedField]
+ public TimeSpan StartEffectTime;
+
/// <summary>
/// When this effect will end. If Null, the effect lasts indefinitely.
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField, AutoNetworkedField]
public TimeSpan? EndEffectTime;
+ /// <summary>
+ /// If true, this status effect has been applied. Used to ensure that <see cref="StatusEffectAppliedEvent"/> only fires once.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool Applied;
+
/// <summary>
/// Whitelist, by which it is determined whether this status effect can be imposed on a particular entity.
/// </summary>
/// <param name="target">The target entity to which the effect should be added.</param>
/// <param name="effectProto">ProtoId of the status effect entity. Make sure it has StatusEffectComponent on it.</param>
/// <param name="duration">Duration of status effect. Leave null and the effect will be permanent until it is removed using <c>TryRemoveStatusEffect</c>.</param>
+ /// <param name="delay">The delay of the effect. If a start time already exists, the closest time takes precedence. Leave null for the effect to be instant.</param>
/// <param name="statusEffect">The EntityUid of the status effect we have just created or null if it doesn't exist.</param>
/// <returns>True if effect exists and its duration is set properly, false in case effect cannot be applied.</returns>
public bool TryAddStatusEffectDuration(
EntityUid target,
EntProtoId effectProto,
[NotNullWhen(true)] out EntityUid? statusEffect,
- TimeSpan duration
+ TimeSpan duration,
+ TimeSpan? delay = null
)
{
if (duration == TimeSpan.Zero)
// We check to make sure time is greater than zero here because sometimes you want to use TryAddStatusEffect to remove duration instead...
if (!TryGetStatusEffect(target, effectProto, out statusEffect))
- return TryAddStatusEffect(target, effectProto, out statusEffect, duration);
+ return TryAddStatusEffect(target, effectProto, out statusEffect, duration, delay);
AddStatusEffectTime(statusEffect.Value, duration);
+ UpdateStatusEffectDelay(statusEffect.Value, delay);
return true;
}
- ///<inheritdoc cref="TryAddStatusEffectDuration(EntityUid,EntProtoId,out EntityUid?,TimeSpan)"/>
- public bool TryAddStatusEffectDuration(EntityUid target, EntProtoId effectProto, TimeSpan duration)
+ ///<inheritdoc cref="TryAddStatusEffectDuration(EntityUid,EntProtoId,out EntityUid?,TimeSpan,TimeSpan?)"/>
+ public bool TryAddStatusEffectDuration(EntityUid target, EntProtoId effectProto, TimeSpan duration, TimeSpan? delay = null)
{
- return TryAddStatusEffectDuration(target, effectProto, out _, duration);
+ return TryAddStatusEffectDuration(target, effectProto, out _, duration, delay);
}
/// <summary>
/// <param name="target">The target entity to which the effect should be added.</param>
/// <param name="effectProto">ProtoId of the status effect entity. Make sure it has StatusEffectComponent on it.</param>
/// <param name="duration">Duration of status effect. Leave null and the effect will be permanent until it is removed using <c>TryRemoveStatusEffect</c>.</param>
+ /// <param name="delay">The delay of the effect. If a start time already exists, the closest time takes precedence. Leave null for the effect to be instant.</param>
/// <param name="statusEffect">The EntityUid of the status effect we have just created or null if it doesn't exist.</param>
/// <returns>True if effect exists and its duration is set properly, false in case effect cannot be applied.</returns>
public bool TrySetStatusEffectDuration(
EntityUid target,
EntProtoId effectProto,
[NotNullWhen(true)] out EntityUid? statusEffect,
- TimeSpan? duration = null
+ TimeSpan? duration = null,
+ TimeSpan? delay = null
)
{
if (duration <= TimeSpan.Zero)
}
if (!TryGetStatusEffect(target, effectProto, out statusEffect))
- return TryAddStatusEffect(target, effectProto, out statusEffect, duration);
+ return TryAddStatusEffect(target, effectProto, out statusEffect, duration, delay);
- SetStatusEffectEndTime(statusEffect.Value, duration);
+ if (!_effectQuery.TryComp(statusEffect, out var statusEffectComponent))
+ return false;
+
+ var endTime = delay == null || statusEffectComponent.Applied ? _timing.CurTime + duration : _timing.CurTime + delay + duration;
+ SetStatusEffectEndTime(statusEffect.Value, endTime);
+ UpdateStatusEffectDelay(statusEffect.Value, delay);
return true;
}
- /// <inheritdoc cref="TrySetStatusEffectDuration(EntityUid,EntProtoId,out EntityUid?,TimeSpan?)"/>
- public bool TrySetStatusEffectDuration(EntityUid target, EntProtoId effectProto, TimeSpan? duration = null)
+ /// <inheritdoc cref="TrySetStatusEffectDuration(EntityUid,EntProtoId,out EntityUid?,TimeSpan?,TimeSpan?)"/>
+ public bool TrySetStatusEffectDuration(EntityUid target, EntProtoId effectProto, TimeSpan? duration = null, TimeSpan? delay = null)
{
- return TrySetStatusEffectDuration(target, effectProto, out _, duration);
+ return TrySetStatusEffectDuration(target, effectProto, out _, duration, delay);
}
/// <summary>
/// <param name="target">The target entity to which the effect should be added.</param>
/// <param name="effectProto">ProtoId of the status effect entity. Make sure it has StatusEffectComponent on it.</param>
/// <param name="duration">Duration of status effect. Leave null and the effect will be permanent until it is removed using <c>TryRemoveStatusEffect</c>.</param>
+ /// <param name="delay">The delay of the effect. If a start time already exists, the closest time takes precedence. Leave null for the effect to be instant.</param>
/// <param name="statusEffect">The EntityUid of the status effect we have just created or null if it doesn't exist.</param>
/// <returns>True if effect exists and its duration is set properly, false in case effect cannot be applied.</returns>
public bool TryUpdateStatusEffectDuration(
EntityUid target,
EntProtoId effectProto,
[NotNullWhen(true)] out EntityUid? statusEffect,
- TimeSpan? duration = null
+ TimeSpan? duration = null,
+ TimeSpan? delay = null
)
{
if (duration <= TimeSpan.Zero)
}
if (!TryGetStatusEffect(target, effectProto, out statusEffect))
- return TryAddStatusEffect(target, effectProto, out statusEffect, duration);
+ return TryAddStatusEffect(target, effectProto, out statusEffect, duration, delay);
+
+ if (!_effectQuery.TryComp(statusEffect, out var statusEffectComponent))
+ return false;
- UpdateStatusEffectTime(statusEffect.Value, duration);
+ var endTime = delay == null || statusEffectComponent.Applied ? duration : delay + duration;
+ UpdateStatusEffectTime(statusEffect.Value, endTime);
+ UpdateStatusEffectDelay(statusEffect.Value, delay);
return true;
}
- /// <inheritdoc cref="TryUpdateStatusEffectDuration(EntityUid,EntProtoId,out EntityUid?,TimeSpan?)"/>
- public bool TryUpdateStatusEffectDuration(EntityUid target, EntProtoId effectProto, TimeSpan? duration = null)
+ /// <inheritdoc cref="TryUpdateStatusEffectDuration(EntityUid,EntProtoId,out EntityUid?,TimeSpan?,TimeSpan?)"/>
+ public bool TryUpdateStatusEffectDuration(EntityUid target, EntProtoId effectProto, TimeSpan? duration = null, TimeSpan? delay = null)
{
- return TryUpdateStatusEffectDuration(target, effectProto, out _, duration);
+ return TryUpdateStatusEffectDuration(target, effectProto, out _, duration, delay);
}
/// <summary>
public bool TryGetTime(
EntityUid uid,
EntProtoId effectProto,
- out (EntityUid EffectEnt, TimeSpan? EndEffectTime) time,
+ out (EntityUid EffectEnt, TimeSpan? EndEffectTime, TimeSpan? StartEffectTime) time,
StatusEffectContainerComponent? container = null
)
{
if (!_effectQuery.TryComp(effect, out var effectComp))
return false;
- time = (effect, effectComp.EndEffectTime);
+ time = (effect, effectComp.EndEffectTime, effectComp.StartEffectTime);
return true;
}
}
var query = EntityQueryEnumerator<StatusEffectComponent>();
while (query.MoveNext(out var ent, out var effect))
{
+ TryApplyStatusEffect((ent, effect));
+
if (effect.EndEffectTime is null)
continue;
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)
PredictedQueueDel(ent.Owner);
}
+ /// <summary>
+ /// Applies the status effect, i.e. starts it after it has been added. Ensures delayed start times trigger when they should.
+ /// </summary>
+ /// <param name="statusEffectEnt">The status effect entity.</param>
+ /// <returns>Returns true if the effect is applied.</returns>
+ private bool TryApplyStatusEffect(Entity<StatusEffectComponent> statusEffectEnt)
+ {
+ if (!statusEffectEnt.Comp.Applied &&
+ statusEffectEnt.Comp.AppliedTo != null &&
+ _timing.CurTime >= statusEffectEnt.Comp.StartEffectTime)
+ {
+ var ev = new StatusEffectAppliedEvent(statusEffectEnt.Comp.AppliedTo.Value);
+ RaiseLocalEvent(statusEffectEnt, ref ev);
+
+ statusEffectEnt.Comp.Applied = true;
+
+ DirtyField(statusEffectEnt, statusEffectEnt.Comp, nameof(StatusEffectComponent.StartEffectTime));
+ return true;
+ }
+
+ return false;
+ }
+
public bool CanAddStatusEffect(EntityUid uid, EntProtoId effectProto)
{
if (!_proto.Resolve(effectProto, out var effectProtoData))
/// <param name="target">The target entity to which the effect should be added.</param>
/// <param name="effectProto">ProtoId of the status effect entity. Make sure it has StatusEffectComponent on it.</param>
/// <param name="duration">Duration of status effect. Leave null and the effect will be permanent until it is removed using <c>TryRemoveStatusEffect</c>.</param>
+ /// <param name="delay">The delay of the effect. Leave null and the effect will be immediate.</param>
/// <param name="statusEffect">The EntityUid of the status effect we have just created or null if we couldn't create one.</param>
private bool TryAddStatusEffect(
EntityUid target,
EntProtoId effectProto,
[NotNullWhen(true)] out EntityUid? statusEffect,
- TimeSpan? duration = null
+ TimeSpan? duration = null,
+ TimeSpan? delay = null
)
{
statusEffect = null;
return false;
statusEffect = effect;
- SetStatusEffectEndTime((effect.Value, effectComp), _timing.CurTime + duration);
+
+ var endTime = delay == null ? _timing.CurTime + duration : _timing.CurTime + delay + duration;
+ SetStatusEffectEndTime((effect.Value, effectComp), endTime);
+ var startTime = delay == null ? TimeSpan.Zero : _timing.CurTime + delay.Value;
+ SetStatusEffectStartTime(effect.Value, startTime);
+
+ TryApplyStatusEffect((effect.Value, effectComp));
return true;
}
SetStatusEffectEndTime(effect, newEndTime);
}
+ private void UpdateStatusEffectDelay(Entity<StatusEffectComponent?> effect, TimeSpan? delay)
+ {
+ if (!_effectQuery.Resolve(effect, ref effect.Comp))
+ return;
+
+ // It's already started!
+ if (_timing.CurTime >= effect.Comp.StartEffectTime)
+ return;
+
+ var newStartTime = TimeSpan.Zero;
+
+ if (delay is not null)
+ {
+ // Don't update time to a smaller timespan...
+ newStartTime = _timing.CurTime + delay.Value;
+ if (effect.Comp.StartEffectTime < newStartTime)
+ return;
+ }
+
+ SetStatusEffectStartTime(effect, newStartTime);
+ }
+
private void AddStatusEffectTime(Entity<StatusEffectComponent?> effect, TimeSpan delta)
{
if (!_effectQuery.Resolve(effect, ref effect.Comp))
var ev = new StatusEffectEndTimeUpdatedEvent(appliedTo, endTime);
RaiseLocalEvent(ent, ref ev);
- Dirty(ent);
+ DirtyField(ent, ent.Comp, nameof(StatusEffectComponent.EndEffectTime));
+ }
+
+ private void SetStatusEffectStartTime(Entity<StatusEffectComponent?> ent, TimeSpan startTime)
+ {
+ if (!_effectQuery.Resolve(ent, ref ent.Comp))
+ return;
+
+ if (ent.Comp.StartEffectTime == startTime)
+ return;
+
+ ent.Comp.StartEffectTime = startTime;
+
+ if (ent.Comp.AppliedTo is not { } appliedTo)
+ return; // Not much we can do!
+
+ var ev = new StatusEffectStartTimeUpdatedEvent(appliedTo, startTime);
+ RaiseLocalEvent(ent, ref ev);
+
+ DirtyField(ent, ent.Comp, nameof(StatusEffectComponent.StartEffectTime));
}
}
/// <param name="EndTime">The new end time of the status effect, included for convenience.</param>
[ByRefEvent]
public record struct StatusEffectEndTimeUpdatedEvent(EntityUid Target, TimeSpan? EndTime);
+
+/// <summary>
+/// Raised on an effect entity when its <see cref="StatusEffectComponent.StartEffectTime"/> is updated in any way.
+/// </summary>
+/// <param name="Target">The entity the effect is attached to.</param>
+/// <param name="StartTime">The new start time of the status effect, included for convenience.</param>
+[ByRefEvent]
+public record struct StatusEffectStartTimeUpdatedEvent(EntityUid Target, TimeSpan? StartTime);
} {NATURALFIXED($time, 3)} {MANY("second", $time)} of {LOC($key)}
}
+reagent-effect-guidebook-status-effect-delay =
+ { $type ->
+ [add] { $chance ->
+ [1] Causes
+ *[other] cause
+ } {LOC($key)} for at least {NATURALFIXED($time, 3)} {MANY("second", $time)} with accumulation
+ *[set] { $chance ->
+ [1] Causes
+ *[other] cause
+ } {LOC($key)} for at least {NATURALFIXED($time, 3)} {MANY("second", $time)} without accumulation
+ [remove]{ $chance ->
+ [1] Removes
+ *[other] remove
+ } {NATURALFIXED($time, 3)} {MANY("second", $time)} of {LOC($key)}
+ } after a {NATURALFIXED($delay, 3)} second delay
+
reagent-effect-guidebook-set-solution-temperature-effect =
{ $chance ->
[1] Sets
min: 8
effectProto: StatusEffectForcedSleeping
time: 3
+ delay: 6
type: Add
- type: reagent