mirror of
https://github.com/DeltaV-Station/Delta-v.git
synced 2026-09-29 23:57:29 +00:00
Migrate Chronic Pain to the New Status Effect System (#5180)
* Renamed InPain to ChronicPain. Changed references. * Updated meds to use the name status effect system for chronic pain suppression. Standardized popup chances for all meds. * Migrated ChronicPain to the new Status effect system. Basically re-wrote it and re-organized it. * Cleanup and tweaks. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fixed cloning traits for pain. * Addressing Milon-san's feedback uwu * Apply suggestions from code review Co-authored-by: Tobias Berger <toby@tobot.dev> Signed-off-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com> * Turned floats into TimeStamps. * Derp * Forgot the trait lol --------- Signed-off-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tobias Berger <toby@tobot.dev>
This commit is contained in:
co-authored by
Tobias Berger
pre-commit-ci[bot]
parent
989b978712
commit
8f130cb0bb
@@ -0,0 +1,73 @@
|
||||
using Content.Client._DV.ChronicPain.Overlays;
|
||||
using Content.Shared._DV.ChronicPain.Components;
|
||||
using Content.Shared._DV.ChronicPain.EntitySystems;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Client._DV.ChronicPain.EntitySystems;
|
||||
|
||||
public sealed class ChronicPainSystem : SharedChronicPainSystem
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlayMan = default!;
|
||||
[Dependency] private readonly ISharedPlayerManager _playerManager = default!;
|
||||
|
||||
private ChronicPainOverlay _overlay = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_overlay = new();
|
||||
}
|
||||
|
||||
|
||||
protected override void OnChronicPainInit(Entity<ChronicPainComponent> entity, ref ComponentInit args)
|
||||
{
|
||||
if (entity.Owner != _playerManager.LocalEntity)
|
||||
return;
|
||||
|
||||
_overlayMan.AddOverlay(_overlay);
|
||||
}
|
||||
|
||||
protected override void OnChronicPainShutdown(Entity<ChronicPainComponent> entity, ref ComponentShutdown args)
|
||||
{
|
||||
if (entity.Owner != _playerManager.LocalEntity)
|
||||
return;
|
||||
|
||||
_overlayMan.RemoveOverlay(_overlay);
|
||||
}
|
||||
|
||||
protected override void OnPlayerAttached(Entity<ChronicPainComponent> entity, ref LocalPlayerAttachedEvent args)
|
||||
{
|
||||
if (!IsChronicPainSuppressed((entity.Owner, entity.Comp)))
|
||||
_overlayMan.AddOverlay(_overlay);
|
||||
}
|
||||
|
||||
protected override void OnPlayerDetached(Entity<ChronicPainComponent> entity, ref LocalPlayerDetachedEvent args)
|
||||
{
|
||||
_overlayMan.RemoveOverlay(_overlay);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
// Handle showing/hiding overlay based on suppression status
|
||||
if (_playerManager.LocalEntity is not { } player)
|
||||
return;
|
||||
|
||||
if (!TryComp<ChronicPainComponent>(player, out var comp))
|
||||
return;
|
||||
|
||||
var isSuppressed = IsChronicPainSuppressed((player, comp));
|
||||
|
||||
if (isSuppressed && _overlayMan.TryGetOverlay<ChronicPainOverlay>(out var overlay))
|
||||
_overlayMan.RemoveOverlay(overlay);
|
||||
|
||||
if (isSuppressed) // If its suppressed and we don't have an overlay, just return
|
||||
return;
|
||||
|
||||
if (!isSuppressed && !_overlayMan.HasOverlay<ChronicPainOverlay>())
|
||||
_overlayMan.AddOverlay(_overlay);
|
||||
}
|
||||
}
|
||||
+5
-10
@@ -1,36 +1,31 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared._DV.Pain;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client._DV.Overlays;
|
||||
namespace Content.Client._DV.ChronicPain.Overlays;
|
||||
|
||||
public sealed partial class PainOverlay : Overlay
|
||||
public sealed partial class ChronicPainOverlay : Overlay
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly IEntityManager _entity = default!;
|
||||
|
||||
public override bool RequestScreenTexture => true;
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
private readonly ShaderInstance _painShader;
|
||||
private readonly ProtoId<ShaderPrototype> _shaderProto = "ChromaticAberration";
|
||||
|
||||
public PainOverlay()
|
||||
public ChronicPainOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_painShader = _prototype.Index(_shaderProto).Instance().Duplicate();
|
||||
_painShader = _prototype.Index(_shaderProto).InstanceUnique();
|
||||
}
|
||||
|
||||
protected override bool BeforeDraw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (_player.LocalEntity is not { Valid: true } player
|
||||
|| !_entity.HasComponent<PainComponent>(player))
|
||||
{
|
||||
if (_player.LocalEntity is not { Valid: true })
|
||||
return false;
|
||||
}
|
||||
|
||||
return base.BeforeDraw(in args);
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
using Content.Shared._DV.Pain;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Client._DV.Overlays;
|
||||
|
||||
public sealed partial class PainSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlayMan = default!;
|
||||
[Dependency] private readonly ISharedPlayerManager _playerMan = default!;
|
||||
|
||||
private PainOverlay _overlay = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<PainComponent, ComponentInit>(OnPainInit);
|
||||
SubscribeLocalEvent<PainComponent, ComponentShutdown>(OnPainShutdown);
|
||||
SubscribeLocalEvent<PainComponent, LocalPlayerAttachedEvent>(OnPlayerAttached);
|
||||
SubscribeLocalEvent<PainComponent, LocalPlayerDetachedEvent>(OnPlayerDetached);
|
||||
|
||||
_overlay = new();
|
||||
}
|
||||
|
||||
private void OnPainInit(Entity<PainComponent> ent, ref ComponentInit args)
|
||||
{
|
||||
if (ent.Owner == _playerMan.LocalEntity && !ent.Comp.Suppressed)
|
||||
_overlayMan.AddOverlay(_overlay);
|
||||
}
|
||||
|
||||
private void OnPainShutdown(Entity<PainComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
if (ent.Owner == _playerMan.LocalEntity)
|
||||
_overlayMan.RemoveOverlay(_overlay);
|
||||
}
|
||||
|
||||
private void OnPlayerAttached(Entity<PainComponent> ent, ref LocalPlayerAttachedEvent args)
|
||||
{
|
||||
if (!ent.Comp.Suppressed)
|
||||
_overlayMan.AddOverlay(_overlay);
|
||||
}
|
||||
|
||||
private void OnPlayerDetached(Entity<PainComponent> ent, ref LocalPlayerDetachedEvent args)
|
||||
{
|
||||
_overlayMan.RemoveOverlay(_overlay);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
// Handle showing/hiding overlay based on suppression status
|
||||
if (_playerMan.LocalEntity is not { } player)
|
||||
return;
|
||||
|
||||
if (!TryComp<PainComponent>(player, out var comp))
|
||||
return;
|
||||
|
||||
if (comp.Suppressed && _overlayMan.HasOverlay<PainOverlay>())
|
||||
_overlayMan.RemoveOverlay(_overlay);
|
||||
else if (!comp.Suppressed && !_overlayMan.HasOverlay<PainOverlay>())
|
||||
_overlayMan.AddOverlay(_overlay);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
using Content.Shared._DV.Pain;
|
||||
|
||||
namespace Content.Client._DV.Pain;
|
||||
|
||||
public sealed class PainSystem : SharedPainSystem;
|
||||
@@ -0,0 +1,5 @@
|
||||
using Content.Shared._DV.ChronicPain.EntitySystems;
|
||||
|
||||
namespace Content.Server._DV.ChronicPain.EntitySystems;
|
||||
|
||||
public sealed class ChronicPainSystem : SharedChronicPainSystem;
|
||||
@@ -1,5 +0,0 @@
|
||||
using Content.Shared._DV.Pain;
|
||||
|
||||
namespace Content.Server._DV.Pain;
|
||||
|
||||
public sealed class PainSystem : SharedPainSystem;
|
||||
@@ -0,0 +1,62 @@
|
||||
using Content.Shared.Dataset;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Shared._DV.ChronicPain.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This component is used to mark people who have the chronic pain trait.
|
||||
///
|
||||
/// If someone is currently the chronic pain status effect // TODO
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[AutoGenerateComponentState, AutoGenerateComponentPause]
|
||||
public sealed partial class ChronicPainComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// If pain is trying to be suppressed and no suppression time is given, it should default to this time.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan DefaultSuppressionTime = TimeSpan.FromSeconds(30);
|
||||
|
||||
/// <summary>
|
||||
/// The default suppression time on map init, so people don't have to eat a pill right away.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan DefaultSuppressionTimeOnInit = TimeSpan.FromMinutes(5);
|
||||
|
||||
/// <summary>
|
||||
/// When to next update this component
|
||||
/// </summary>
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
[AutoPausedField]
|
||||
[AutoNetworkedField]
|
||||
public TimeSpan NextUpdateTime;
|
||||
|
||||
/// <summary>
|
||||
/// When to show the next pain effect popup.
|
||||
/// </summary>
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
[AutoPausedField]
|
||||
[AutoNetworkedField]
|
||||
public TimeSpan NextPopupTime;
|
||||
|
||||
/// <summary>
|
||||
/// The dataset of pain effect messages to display.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<LocalizedDatasetPrototype> DatasetPrototype = "PainEffects";
|
||||
|
||||
/// <summary>
|
||||
/// Minimum time between pain popups.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan MinimumPopupDelay = TimeSpan.FromSeconds(5);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum time between pain popups.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan MaximumPopupDelay = TimeSpan.FromSeconds(40);
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
using Content.Shared.StatusEffectNew.Components;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared._DV.ChronicPain.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Prevents the Chronic Pain overlay and popups from showing up. Use only in conjunction with <see cref="StatusEffectComponent"/>, on the status effect entity.
|
||||
/// </summary>
|
||||
[NetworkedComponent, RegisterComponent]
|
||||
public sealed partial class ChronicPainSuppressedStatusEffectComponent : Component;
|
||||
@@ -0,0 +1,122 @@
|
||||
using Content.Shared._DV.ChronicPain.Components;
|
||||
using Content.Shared.StatusEffectNew;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Shared._DV.ChronicPain.EntitySystems;
|
||||
|
||||
public abstract partial class SharedChronicPainSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] protected readonly IPrototypeManager ProtoManager = default!;
|
||||
[Dependency] protected readonly IRobustRandom RobustRandom = default!;
|
||||
[Dependency] protected readonly SharedPopupSystem Popup = default!;
|
||||
[Dependency] protected readonly StatusEffectsSystem StatusEffects = default!;
|
||||
public static readonly EntProtoId ChronicPainStatusEffect = "StatusEffectChronicPainSuppressed";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ChronicPainComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<ChronicPainComponent, ComponentInit>(OnChronicPainInit);
|
||||
SubscribeLocalEvent<ChronicPainComponent, ComponentShutdown>(OnChronicPainShutdown);
|
||||
SubscribeLocalEvent<ChronicPainComponent, LocalPlayerAttachedEvent>(OnPlayerAttached);
|
||||
SubscribeLocalEvent<ChronicPainComponent, LocalPlayerDetachedEvent>(OnPlayerDetached);
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public bool IsChronicPainSuppressed(Entity<ChronicPainComponent?> entity)
|
||||
{
|
||||
// If they don't have the Chronic Pain trait, technically their pain was suppressed
|
||||
if (!Resolve(entity, ref entity.Comp, false))
|
||||
return true;
|
||||
|
||||
return StatusEffects.HasEffectComp<ChronicPainSuppressedStatusEffectComponent>(entity.Owner);
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public bool TrySuppressChronicPain(Entity<ChronicPainComponent?> entity, TimeSpan? duration)
|
||||
{
|
||||
// If they don't have the Chronic Pain trait, technically their pain was suppressed
|
||||
if (!Resolve(entity, ref entity.Comp, false))
|
||||
return true;
|
||||
|
||||
if (!duration.HasValue)
|
||||
duration = entity.Comp.DefaultSuppressionTime;
|
||||
|
||||
return StatusEffects.TryAddStatusEffectDuration(entity.Owner, ChronicPainStatusEffect, duration.Value);
|
||||
}
|
||||
|
||||
protected void OnMapInit(Entity<ChronicPainComponent> entity, ref MapInitEvent args)
|
||||
{
|
||||
entity.Comp.NextUpdateTime = _timing.CurTime;
|
||||
entity.Comp.NextPopupTime = _timing.CurTime;
|
||||
}
|
||||
|
||||
protected virtual void OnChronicPainInit(Entity<ChronicPainComponent> entity, ref ComponentInit args)
|
||||
{
|
||||
// Give the player a bit of time before they have to take a pill
|
||||
if (TrySuppressChronicPain((entity.Owner, entity.Comp), entity.Comp.DefaultSuppressionTimeOnInit))
|
||||
entity.Comp.NextUpdateTime = _timing.CurTime + entity.Comp.DefaultSuppressionTimeOnInit;
|
||||
}
|
||||
|
||||
protected virtual void OnChronicPainShutdown(Entity<ChronicPainComponent> entity, ref ComponentShutdown args)
|
||||
{
|
||||
StatusEffects.TryRemoveStatusEffect(entity, ChronicPainStatusEffect);
|
||||
}
|
||||
|
||||
protected virtual void OnPlayerAttached(Entity<ChronicPainComponent> entity, ref LocalPlayerAttachedEvent args)
|
||||
{
|
||||
// Used by the client to add the overlay if not suppressed
|
||||
}
|
||||
|
||||
protected virtual void OnPlayerDetached(Entity<ChronicPainComponent> entity, ref LocalPlayerDetachedEvent args)
|
||||
{
|
||||
// Used by the client to get rid of the overlay
|
||||
}
|
||||
|
||||
protected void ShowPainPopup(Entity<ChronicPainComponent> entity)
|
||||
{
|
||||
// Don't notify
|
||||
if (IsChronicPainSuppressed((entity.Owner, entity.Comp)))
|
||||
return;
|
||||
|
||||
if (!ProtoManager.TryIndex(entity.Comp.DatasetPrototype, out var dataset))
|
||||
return;
|
||||
|
||||
var effects = dataset.Values;
|
||||
if (effects.Count == 0)
|
||||
return;
|
||||
|
||||
var effect = RobustRandom.Pick(effects);
|
||||
Popup.PopupPredicted(Loc.GetString(effect), entity, entity);
|
||||
|
||||
// Set next popup time
|
||||
var delay = RobustRandom.Next(entity.Comp.MinimumPopupDelay, entity.Comp.MaximumPopupDelay);
|
||||
entity.Comp.NextPopupTime = _timing.CurTime + delay;
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var curTime = _timing.CurTime;
|
||||
var query = EntityQueryEnumerator<ChronicPainComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var component))
|
||||
{
|
||||
if (curTime < component.NextUpdateTime)
|
||||
continue;
|
||||
|
||||
if (curTime >= component.NextPopupTime)
|
||||
ShowPainPopup((uid, component));
|
||||
|
||||
component.NextUpdateTime = curTime + TimeSpan.FromSeconds(5);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
using Content.Shared._DV.Pain;
|
||||
using Content.Shared.EntityEffects;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared._DV.EntityEffects.Effects.Pain;
|
||||
|
||||
// TODO: When Pain is moved to new Status, make this use StatusEffectsContainerComponent.
|
||||
/// <summary>
|
||||
/// Applies the pain status effect for an amount of time based on how much of the painful reagent is in the system.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="EntityEffectSystem{T, TEffect}"/>
|
||||
public sealed partial class InPainEntityEffectSystem : EntityEffectSystem<StatusEffectsComponent, InPain>
|
||||
{
|
||||
[Dependency] private readonly SharedPainSystem _pain = default!;
|
||||
protected override void Effect(Entity<StatusEffectsComponent> entity, ref EntityEffectEvent<InPain> args)
|
||||
{
|
||||
var painTime = args.Effect.Time * args.Scale;
|
||||
|
||||
_pain.TryApplyPain(entity, painTime);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="EntityEffect"/>
|
||||
public sealed partial class InPain : EntityEffectBase<InPain>
|
||||
{
|
||||
/// <summary>
|
||||
/// How long should the addiction be per 1u of the reagent.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float Time = 5f;
|
||||
|
||||
public override string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
|
||||
=> Loc.GetString("reagent-effect-guidebook-addicted", ("chance", Probability));
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
using Content.Shared._DV.Pain;
|
||||
using Content.Shared.EntityEffects;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared._DV.EntityEffects.Effects.Pain;
|
||||
|
||||
// TODO: When Pain is moved to new Status, make this use StatusEffectsContainerComponent.
|
||||
/// <summary>
|
||||
/// Supresses pain based on how much of the pain suppressing reagent is in the system.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="EntityEffectSystem{T, TEffect}"/>
|
||||
public sealed partial class SuppressPainEntityEffectSystem : EntityEffectSystem<StatusEffectsComponent, SuppressPain>
|
||||
{
|
||||
[Dependency] private readonly SharedPainSystem _pain = default!;
|
||||
protected override void Effect(Entity<StatusEffectsComponent> entity, ref EntityEffectEvent<SuppressPain> args)
|
||||
{
|
||||
var suppressionTime = args.Effect.Time * args.Scale;
|
||||
|
||||
_pain.TrySuppressPain(entity, suppressionTime, args.Effect.Level);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="EntityEffect"/>
|
||||
public sealed partial class SuppressPain : EntityEffectBase<SuppressPain>
|
||||
{
|
||||
/// <summary>
|
||||
/// How long should the pain suppression last for each metabolism cycle
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float Time = 30f;
|
||||
|
||||
/// <summary>
|
||||
/// The strength level of the pain suppression.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public PainSuppressionLevel Level = PainSuppressionLevel.Normal;
|
||||
|
||||
public override string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
|
||||
=> Loc.GetString("reagent-effect-guidebook-pain-suppression",
|
||||
("chance", Probability),
|
||||
("level", Level.ToString().ToLowerInvariant()));
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
using Content.Shared.Dataset;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Shared._DV.Pain;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[AutoGenerateComponentState, AutoGenerateComponentPause]
|
||||
public sealed partial class PainComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether pain effects are currently suppressed by painkillers
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool Suppressed;
|
||||
|
||||
/// <summary>
|
||||
/// The current level of pain suppression
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public PainSuppressionLevel CurrentSuppressionLevel = PainSuppressionLevel.Normal;
|
||||
|
||||
/// <summary>
|
||||
/// The last time painkillers were administered
|
||||
/// </summary>
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
[AutoPausedField]
|
||||
public TimeSpan LastPainkillerTime;
|
||||
|
||||
/// <summary>
|
||||
/// When the pain suppression effect ends
|
||||
/// </summary>
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
[AutoPausedField]
|
||||
public TimeSpan SuppressionEndTime;
|
||||
|
||||
/// <summary>
|
||||
/// When to next update this component
|
||||
/// </summary>
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
[AutoPausedField]
|
||||
public TimeSpan NextUpdateTime;
|
||||
|
||||
/// <summary>
|
||||
/// When to show the next pain effect popup
|
||||
/// </summary>
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
[AutoPausedField]
|
||||
public TimeSpan NextPopupTime;
|
||||
|
||||
/// <summary>
|
||||
/// The dataset of pain effect messages to display
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<LocalizedDatasetPrototype> DatasetPrototype = "PainEffects";
|
||||
|
||||
/// <summary>
|
||||
/// Minimum time between pain popups in seconds
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float MinimumPopupDelay = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum time between pain popups in seconds
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float MaximumPopupDelay = 40f;
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
using Content.Shared.StatusEffect;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared._DV.Pain;
|
||||
|
||||
public abstract class SharedPainSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly StatusEffectsSystem _statusEffects = default!;
|
||||
|
||||
public ProtoId<StatusEffectPrototype> StatusEffectKey = "InPain";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<PainComponent, MapInitEvent>(OnMapInit);
|
||||
}
|
||||
|
||||
public void TryApplyPain(EntityUid uid, float painTime, StatusEffectsComponent? status = null)
|
||||
{
|
||||
if (!Resolve(uid, ref status, false))
|
||||
return;
|
||||
|
||||
if (!_statusEffects.HasStatusEffect(uid, StatusEffectKey, status))
|
||||
{
|
||||
_statusEffects.TryAddStatusEffect<PainComponent>(uid, StatusEffectKey, TimeSpan.FromSeconds(painTime), true, status);
|
||||
}
|
||||
else
|
||||
{
|
||||
_statusEffects.TryAddTime(uid, StatusEffectKey, TimeSpan.FromSeconds(painTime), status);
|
||||
}
|
||||
}
|
||||
|
||||
public void TrySuppressPain(EntityUid uid, float duration, PainSuppressionLevel level = PainSuppressionLevel.Normal)
|
||||
{
|
||||
if (!TryComp<PainComponent>(uid, out var comp))
|
||||
return;
|
||||
|
||||
UpdatePainSuppression((uid, comp), duration, level);
|
||||
}
|
||||
|
||||
protected void UpdatePainSuppression(Entity<PainComponent> ent, float duration, PainSuppressionLevel level)
|
||||
{
|
||||
var curTime = _timing.CurTime;
|
||||
var newEndTime = curTime + TimeSpan.FromSeconds(duration);
|
||||
|
||||
// Only update if this would extend the suppression
|
||||
if (newEndTime <= ent.Comp.SuppressionEndTime)
|
||||
return;
|
||||
|
||||
ent.Comp.LastPainkillerTime = curTime;
|
||||
ent.Comp.SuppressionEndTime = newEndTime;
|
||||
UpdateSuppressed(ent);
|
||||
}
|
||||
|
||||
private void UpdateSuppressed(Entity<PainComponent> ent)
|
||||
{
|
||||
ent.Comp.Suppressed = (_timing.CurTime < ent.Comp.SuppressionEndTime);
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
|
||||
private void OnMapInit(Entity<PainComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
ent.Comp.NextUpdateTime = _timing.CurTime;
|
||||
ent.Comp.NextPopupTime = _timing.CurTime;
|
||||
}
|
||||
|
||||
private void ShowPainPopup(Entity<PainComponent> ent)
|
||||
{
|
||||
if (!_prototype.TryIndex(ent.Comp.DatasetPrototype, out var dataset))
|
||||
return;
|
||||
|
||||
var effects = dataset.Values;
|
||||
if (effects.Count == 0)
|
||||
return;
|
||||
|
||||
var effect = _random.Pick(effects);
|
||||
_popup.PopupEntity(Loc.GetString(effect), ent, ent);
|
||||
|
||||
// Set next popup time
|
||||
var delay = _random.NextFloat(ent.Comp.MinimumPopupDelay, ent.Comp.MaximumPopupDelay);
|
||||
ent.Comp.NextPopupTime = _timing.CurTime + TimeSpan.FromSeconds(delay);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var curTime = _timing.CurTime;
|
||||
var query = EntityQueryEnumerator<PainComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var component))
|
||||
{
|
||||
if (curTime < component.NextUpdateTime)
|
||||
continue;
|
||||
|
||||
var ent = new Entity<PainComponent>(uid, component);
|
||||
|
||||
if (component.Suppressed)
|
||||
{
|
||||
UpdateSuppressed(ent);
|
||||
}
|
||||
else if (curTime >= component.NextPopupTime)
|
||||
{
|
||||
ShowPainPopup(ent);
|
||||
}
|
||||
component.NextUpdateTime = curTime + TimeSpan.FromSeconds(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Used by the StatusEffect
|
||||
public enum PainSuppressionLevel : byte
|
||||
{
|
||||
Mild,
|
||||
Normal,
|
||||
Strong,
|
||||
}
|
||||
@@ -9,9 +9,3 @@ reagent-effect-guidebook-addiction-suppression =
|
||||
[1] Suppresses
|
||||
*[other] suppress
|
||||
} active addictions
|
||||
|
||||
reagent-effect-guidebook-pain-suppression =
|
||||
{ $chance ->
|
||||
[1] Suppresses
|
||||
*[other] suppress
|
||||
} pain at { $level } strength
|
||||
|
||||
@@ -19,7 +19,7 @@ trait-ultravision-desc = Whether through custom bionic eyes, random mutation,
|
||||
|
||||
trait-deuteranopia-name = Deuteranopia
|
||||
trait-deuteranopia-desc = Whether through custom bionic eyes, random mutation,
|
||||
or being a Vulpkanin, you have red–green colour blindness.
|
||||
or being a Vulpkanin, you have red-green colour blindness.
|
||||
|
||||
trait-hushed-name = Hushed
|
||||
trait-hushed-desc = You are unable to speak louder than a whisper.
|
||||
@@ -27,8 +27,8 @@ trait-hushed-desc = You are unable to speak louder than a whisper.
|
||||
trait-uncloneable-name = Uncloneable
|
||||
trait-uncloneable-desc = You cannot be cloned.
|
||||
|
||||
trait-inpain-name = Chronic pain
|
||||
trait-inpain-desc = You’re constantly in discomfort. You need painkillers to function.
|
||||
trait-chronic-pain-name = Chronic pain
|
||||
trait-chronic-pain-desc = You're constantly in discomfort. You need painkillers to function.
|
||||
|
||||
trait-addicted-name = Addicted
|
||||
trait-addicted-desc = You crave the substance, and your thoughts keep drifting back to it. Without it, you feel incomplete, anxious, and on edge.
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
- Hushed
|
||||
- IrishAccent
|
||||
- MobsterAccent
|
||||
- Pain
|
||||
- ChronicPain
|
||||
- ScottishAccent
|
||||
- UltraVision
|
||||
- Unborgable
|
||||
|
||||
@@ -136,8 +136,7 @@
|
||||
# Begin DeltaV additions
|
||||
- Addicted # Psych med addictions system
|
||||
- SuppressAddiction # Psych med addictions system
|
||||
- InPain # Pain system
|
||||
- SuppressPain # Pain system
|
||||
- ChronicPain # Pain system
|
||||
- Anesthesia # Anesthesia
|
||||
- PainNumbness # Pain numbness status effect
|
||||
- ScrambledSpeech
|
||||
|
||||
@@ -222,6 +222,10 @@
|
||||
reagent: THC
|
||||
min: 0.25
|
||||
- !type:SuppressAddiction
|
||||
- !type:ModifyStatusEffect # suppresses chronic pain mildly
|
||||
effectProto: StatusEffectChronicPainSuppressed
|
||||
time: 25 # 4.16 minutes of pain suppression per 5u (5u / 0.5 * 25 / 60)
|
||||
type: Add
|
||||
# END DeltaV
|
||||
|
||||
- type: reagent
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
effects:
|
||||
- !type:PopupMessage
|
||||
type: Local
|
||||
visualType: Medium
|
||||
visualType: Small
|
||||
messages:
|
||||
- "reagent-effect-antidepressant-mild1"
|
||||
- "reagent-effect-antidepressant-mild2"
|
||||
@@ -58,13 +58,13 @@
|
||||
effects:
|
||||
- !type:PopupMessage
|
||||
type: Local
|
||||
visualType: Large
|
||||
visualType: Medium
|
||||
messages:
|
||||
- "reagent-effect-antidepressant-normal1"
|
||||
- "reagent-effect-antidepressant-normal2"
|
||||
- "reagent-effect-antidepressant-normal3"
|
||||
- "reagent-effect-antidepressant-normal4"
|
||||
probability: 0.045
|
||||
probability: 0.035
|
||||
conditions:
|
||||
- !type:ReagentCondition
|
||||
reagent: Neurozenium
|
||||
@@ -128,13 +128,13 @@
|
||||
- !type:SuppressAddiction
|
||||
- !type:PopupMessage
|
||||
type: Local
|
||||
visualType: Large
|
||||
visualType: Medium
|
||||
messages:
|
||||
- "reagent-effect-antidepressant-strong1"
|
||||
- "reagent-effect-antidepressant-strong2"
|
||||
- "reagent-effect-antidepressant-strong3"
|
||||
- "reagent-effect-antidepressant-strong4"
|
||||
probability: 0.055
|
||||
probability: 0.035
|
||||
conditions:
|
||||
- !type:ReagentCondition
|
||||
reagent: Blissifylovene
|
||||
@@ -218,7 +218,7 @@
|
||||
Poison: 0.5
|
||||
- !type:PopupMessage
|
||||
type: Local
|
||||
visualType: Medium
|
||||
visualType: Large
|
||||
messages:
|
||||
- "reagent-effect-antidepressant-fade" # A decent chance to notify the player when their meds run out, but could happen silently.
|
||||
probability: 0.2
|
||||
@@ -242,7 +242,7 @@
|
||||
effects:
|
||||
- !type:PopupMessage
|
||||
type: Local
|
||||
visualType: Medium
|
||||
visualType: Small
|
||||
messages:
|
||||
- "reagent-effect-anxietymed-mild1"
|
||||
- "reagent-effect-anxietymed-mild2"
|
||||
@@ -259,7 +259,7 @@
|
||||
amount: -1.2
|
||||
- !type:PopupMessage
|
||||
type: Local
|
||||
visualType: Small
|
||||
visualType: Large
|
||||
messages:
|
||||
- "reagent-effect-anxietymed-fade" # A decent chance to notify the player when their meds run out, but could happen silently.
|
||||
probability: 0.35
|
||||
@@ -282,14 +282,14 @@
|
||||
effects:
|
||||
- !type:PopupMessage
|
||||
type: Local
|
||||
visualType: Large
|
||||
visualType: Small
|
||||
messages:
|
||||
- "reagent-effect-anxietymed-normal1"
|
||||
- "reagent-effect-anxietymed-normal2"
|
||||
- "reagent-effect-anxietymed-normal3"
|
||||
- "reagent-effect-anxietymed-normal4"
|
||||
- "reagent-effect-anxietymed-normal5"
|
||||
probability: 0.04
|
||||
probability: 0.035
|
||||
conditions:
|
||||
- !type:ReagentCondition
|
||||
reagent: Tranquinase
|
||||
@@ -341,7 +341,7 @@
|
||||
Poison: 1
|
||||
- !type:PopupMessage
|
||||
type: Local
|
||||
visualType: Small
|
||||
visualType: Large
|
||||
messages:
|
||||
- "reagent-effect-anxietymed-fade" # A decent chance to notify the player when their meds run out, but could happen silently.
|
||||
probability: 0.32
|
||||
@@ -364,7 +364,7 @@
|
||||
effects:
|
||||
- !type:PopupMessage
|
||||
type: Local
|
||||
visualType: Large
|
||||
visualType: Small
|
||||
messages:
|
||||
- "reagent-effect-anxietymed-strong1"
|
||||
- "reagent-effect-anxietymed-strong2"
|
||||
@@ -373,7 +373,7 @@
|
||||
- "reagent-effect-anxietymed-strong5"
|
||||
- "reagent-effect-anxietymed-strong6"
|
||||
- "reagent-effect-anxietymed-strong7"
|
||||
probability: 0.04
|
||||
probability: 0.035
|
||||
conditions:
|
||||
- !type:ReagentCondition
|
||||
reagent: Equilibrazine
|
||||
@@ -425,7 +425,7 @@
|
||||
amount: -1.5
|
||||
- !type:PopupMessage
|
||||
type: Local
|
||||
visualType: Small
|
||||
visualType: Large
|
||||
messages:
|
||||
- "reagent-effect-anxietymed-fade" # A decent chance to notify the player when their meds run out, but could happen silently.
|
||||
probability: 0.2
|
||||
@@ -446,17 +446,19 @@
|
||||
Medicine:
|
||||
metabolismRate : 0.02
|
||||
effects:
|
||||
- !type:SuppressPain
|
||||
level: Mild
|
||||
- !type:ModifyStatusEffect
|
||||
effectProto: StatusEffectChronicPainSuppressed
|
||||
time: 2 # less effective than soretizone - 8.3 minutes of pain suppression per 5u (5u / 0.02 * 2 / 60)
|
||||
type: Update
|
||||
- !type:PopupMessage
|
||||
type: Local
|
||||
visualType: Medium
|
||||
visualType: Small
|
||||
messages:
|
||||
- "reagent-effect-painkiller-mild1"
|
||||
- "reagent-effect-painkiller-mild2"
|
||||
- "reagent-effect-painkiller-mild3"
|
||||
- "reagent-effect-painkiller-mild4"
|
||||
probability: 0.08
|
||||
probability: 0.035
|
||||
- !type:Vomit
|
||||
probability: 0.4
|
||||
conditions:
|
||||
@@ -483,7 +485,7 @@
|
||||
min: 2
|
||||
- !type:PopupMessage
|
||||
type: Local
|
||||
visualType: Small
|
||||
visualType: Large
|
||||
messages:
|
||||
- "reagent-effect-painkiller-fade" # A decent chance to notify the player when their meds run out
|
||||
probability: 0.35
|
||||
@@ -504,18 +506,20 @@
|
||||
Medicine:
|
||||
metabolismRate : 0.01
|
||||
effects:
|
||||
- !type:SuppressPain
|
||||
level: Normal
|
||||
- !type:ModifyStatusEffect
|
||||
effectProto: StatusEffectChronicPainSuppressed
|
||||
time: 1.5 # 12.5 minutes of pain suppression per 5u (5u / 0.01 * 1.5 / 60)
|
||||
type: Add
|
||||
- !type:SuppressAddiction
|
||||
- !type:PopupMessage
|
||||
type: Local
|
||||
visualType: Large
|
||||
visualType: Small
|
||||
messages:
|
||||
- "reagent-effect-painkiller-normal1"
|
||||
- "reagent-effect-painkiller-normal2"
|
||||
- "reagent-effect-painkiller-normal3"
|
||||
- "reagent-effect-painkiller-normal4"
|
||||
probability: 0.085
|
||||
probability: 0.035
|
||||
- !type:AdjustReagent
|
||||
conditions:
|
||||
- !type:ReagentCondition
|
||||
@@ -531,7 +535,7 @@
|
||||
min: 9
|
||||
- !type:PopupMessage
|
||||
type: Local
|
||||
visualType: Medium # stronger painkiller = more noticable fading
|
||||
visualType: Large # stronger painkiller = more noticable fading
|
||||
messages:
|
||||
- "reagent-effect-painkiller-fade" # A decent chance to notify the player when their meds run out
|
||||
probability: 0.4
|
||||
@@ -552,12 +556,14 @@
|
||||
Medicine:
|
||||
metabolismRate : 0.01
|
||||
effects:
|
||||
- !type:SuppressPain
|
||||
level: Strong
|
||||
- !type:ModifyStatusEffect
|
||||
effectProto: StatusEffectChronicPainSuppressed
|
||||
time: 3 # More effective than soretizone - 25 minutes of pain suppression per 5u (5u / 0.01 * 3 / 60)
|
||||
type: Update
|
||||
- !type:SuppressAddiction
|
||||
- !type:PopupMessage
|
||||
type: Local
|
||||
visualType: Large
|
||||
visualType: Small
|
||||
messages:
|
||||
- "reagent-effect-painkiller-strong1"
|
||||
- "reagent-effect-painkiller-strong2"
|
||||
@@ -565,7 +571,7 @@
|
||||
- "reagent-effect-painkiller-strong4"
|
||||
- "reagent-effect-painkiller-strong5"
|
||||
- "reagent-effect-painkiller-strong6"
|
||||
probability: 0.1
|
||||
probability: 0.35
|
||||
- !type:AdjustReagent
|
||||
conditions:
|
||||
- !type:ReagentCondition
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
# TODO: Migrate the following OLD status effects
|
||||
# TODO: Migrate the following OLD status effects located in Resources/Prototypes/_DV/status_effects.yml
|
||||
|
||||
# * addicted
|
||||
# *
|
||||
|
||||
#- type: entity
|
||||
# parent: MobStatusEffectDebuff
|
||||
# id: StatusEffectAddicted
|
||||
# name: addicted
|
||||
# components:
|
||||
# - type: AddictedStatusEffect
|
||||
- type: entity
|
||||
parent: MobStatusEffectDebuff
|
||||
id: StatusEffectChronicPainSuppressed
|
||||
name: chronic pain suppression
|
||||
components:
|
||||
- type: ChronicPainSuppressedStatusEffect
|
||||
|
||||
@@ -15,14 +15,14 @@
|
||||
minDurationOfIncident: 10
|
||||
|
||||
- type: trait
|
||||
id: InPain
|
||||
name: trait-inpain-name
|
||||
description: trait-inpain-desc
|
||||
id: ChronicPain
|
||||
name: trait-chronic-pain-name
|
||||
description: trait-chronic-pain-desc
|
||||
category: Medical
|
||||
effects:
|
||||
- !type:AddCompsEffect
|
||||
components:
|
||||
- type: Pain
|
||||
- type: ChronicPain
|
||||
- !type:SpawnItemInHandEffect
|
||||
item: PillCanisterSoretizone
|
||||
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
# DO NOT ADD ANY NEW STATUS EFFECTS HERE.
|
||||
|
||||
# Use the new status effects system.
|
||||
|
||||
- type: statusEffect
|
||||
id: Addicted
|
||||
|
||||
- type: statusEffect
|
||||
id: SuppressAddiction
|
||||
|
||||
- type: statusEffect
|
||||
id: InPain
|
||||
|
||||
- type: statusEffect
|
||||
id: SuppressPain
|
||||
|
||||
- type: statusEffect
|
||||
id: Anesthesia
|
||||
|
||||
|
||||
Reference in New Issue
Block a user