From: SlamBamActionman <83650252+SlamBamActionman@users.noreply.github.com> Date: Wed, 3 Dec 2025 11:55:29 +0000 (+0100) Subject: Add status effect support to Traits, change PainNumbness to be a status effect (... X-Git-Url: https://git.smokeofanarchy.ru/gitweb.cgi?a=commitdiff_plain;h=d0a784b9e63c75c1c3e7c9207cc4a59657ca94ef;p=space-station-14.git Add status effect support to Traits, change PainNumbness to be a status effect (#41646) * Initial commit * Review comments * Jobify * Prototype(effect) --- diff --git a/Content.Client/UserInterface/Systems/DamageOverlays/DamageOverlayUiController.cs b/Content.Client/UserInterface/Systems/DamageOverlays/DamageOverlayUiController.cs index 20db76554d..f709df4b77 100644 --- a/Content.Client/UserInterface/Systems/DamageOverlays/DamageOverlayUiController.cs +++ b/Content.Client/UserInterface/Systems/DamageOverlays/DamageOverlayUiController.cs @@ -3,6 +3,7 @@ using Content.Shared.FixedPoint; using Content.Shared.Mobs; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; +using Content.Shared.StatusEffectNew; using Content.Shared.Traits.Assorted; using JetBrains.Annotations; using Robust.Client.Graphics; @@ -20,6 +21,7 @@ public sealed class DamageOverlayUiController : UIController [Dependency] private readonly IPlayerManager _playerManager = default!; [UISystemDependency] private readonly MobThresholdSystem _mobThresholdSystem = default!; + [UISystemDependency] private readonly StatusEffectsSystem _statusEffects = default!; private Overlays.DamageOverlay _overlay = default!; public override void Initialize() @@ -98,7 +100,7 @@ public sealed class DamageOverlayUiController : UIController FixedPoint2 painLevel = 0; _overlay.PainLevel = 0; - if (!EntityManager.HasComponent(entity)) + if (!_statusEffects.TryEffectsWithComp(entity, out _)) { foreach (var painDamageType in damageable.PainDamageGroups) { diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index 40f8a36dfa..4975a0b097 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -9,6 +9,7 @@ using Content.Shared.Implants; using Content.Shared.Implants.Components; using Content.Shared.NameModifier.EntitySystems; using Content.Shared.StatusEffect; +using Content.Shared.StatusEffectNew.Components; using Content.Shared.Storage; using Content.Shared.Storage.EntitySystems; using Content.Shared.Whitelist; @@ -36,6 +37,7 @@ public sealed partial class CloningSystem : SharedCloningSystem [Dependency] private readonly SharedStorageSystem _storage = default!; [Dependency] private readonly SharedSubdermalImplantSystem _subdermalImplant = default!; [Dependency] private readonly NameModifierSystem _nameMod = default!; + [Dependency] private readonly Shared.StatusEffectNew.StatusEffectsSystem _statusEffects = default!; //TODO: This system has to support both the old and new status effect systems, until the old is able to be fully removed. /// /// Spawns a clone of the given humanoid mob at the specified location or in nullspace. @@ -75,6 +77,10 @@ public sealed partial class CloningSystem : SharedCloningSystem if (settings.CopyImplants) CopyImplants(original, clone.Value, settings.CopyInternalStorage, settings.Whitelist, settings.Blacklist); + // Copy permanent status effects + if (settings.CopyStatusEffects) + CopyStatusEffects(original, clone.Value); + var originalName = _nameMod.GetBaseName(original); // Set the clone's name. The raised events will also adjust their PDA and ID card names. @@ -267,4 +273,33 @@ public sealed partial class CloningSystem : SharedCloningSystem } } + + /// + /// Scans all permanent status effects applied to the original entity and transfers them to the clone. + /// + public void CopyStatusEffects(Entity original, Entity target) + { + if (!Resolve(original, ref original.Comp, false)) + return; + + if (original.Comp.ActiveStatusEffects is null) + return; + + foreach (var effect in original.Comp.ActiveStatusEffects.ContainedEntities) + { + if (!TryComp(effect, out var effectComp)) + continue; + + //We are not interested in temporary effects, only permanent ones. + if (effectComp.EndEffectTime is not null) + continue; + + var effectProto = Prototype(effect); + + if (effectProto is null) + continue; + + _statusEffects.TrySetStatusEffectDuration(target, effectProto); + } + } } diff --git a/Content.Server/Jobs/ApplyStatusEffectSpecial.cs b/Content.Server/Jobs/ApplyStatusEffectSpecial.cs new file mode 100644 index 0000000000..b06ce114ba --- /dev/null +++ b/Content.Server/Jobs/ApplyStatusEffectSpecial.cs @@ -0,0 +1,27 @@ +using Content.Shared.Roles; +using Content.Shared.StatusEffectNew; +using JetBrains.Annotations; +using Robust.Shared.Prototypes; + +namespace Content.Server.Jobs; + +/// +/// Adds permanent status effects to the entity. +/// TODO: Move this, and other JobSpecials, from Server to Shared. +/// +[UsedImplicitly] +public sealed partial class ApplyStatusEffectSpecial : JobSpecial +{ + [DataField(required: true)] + public HashSet StatusEffects { get; private set; } = new(); + + public override void AfterEquip(EntityUid mob) + { + var entMan = IoCManager.Resolve(); + var statusSystem = entMan.System(); + foreach (var effect in StatusEffects) + { + statusSystem.TrySetStatusEffectDuration(mob, effect); + } + } +} diff --git a/Content.Server/Traits/TraitSystem.cs b/Content.Server/Traits/TraitSystem.cs index 9a634f8942..010cb334da 100644 --- a/Content.Server/Traits/TraitSystem.cs +++ b/Content.Server/Traits/TraitSystem.cs @@ -2,6 +2,7 @@ using Content.Shared.GameTicking; using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; using Content.Shared.Roles; +using Content.Shared.StatusEffectNew; using Content.Shared.Traits; using Content.Shared.Whitelist; using Robust.Shared.Prototypes; @@ -13,6 +14,7 @@ public sealed class TraitSystem : EntitySystem [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly SharedHandsSystem _sharedHandsSystem = default!; [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; + [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; public override void Initialize() { @@ -45,7 +47,14 @@ public sealed class TraitSystem : EntitySystem continue; // Add all components required by the prototype - EntityManager.AddComponents(args.Mob, traitPrototype.Components, false); + if (traitPrototype.Components.Count > 0) + EntityManager.AddComponents(args.Mob, traitPrototype.Components, false); + + // Add all JobSpecials required by the prototype + foreach (var special in traitPrototype.Specials) + { + special.AfterEquip(args.Mob); + } // Add item required by the trait if (traitPrototype.TraitGear == null) diff --git a/Content.Shared/Cloning/CloningSettingsPrototype.cs b/Content.Shared/Cloning/CloningSettingsPrototype.cs index b422f7188b..0b531561ca 100644 --- a/Content.Shared/Cloning/CloningSettingsPrototype.cs +++ b/Content.Shared/Cloning/CloningSettingsPrototype.cs @@ -50,6 +50,12 @@ public sealed partial class CloningSettingsPrototype : IPrototype, IInheritingPr [DataField] public bool CopyImplants = true; + /// + /// Should infinite status effects applied to an entity be copied or not? + /// + [DataField] + public bool CopyStatusEffects = true; + /// /// Whitelist for the equipment allowed to be copied. /// diff --git a/Content.Shared/Roles/JobSpecial.cs b/Content.Shared/Roles/JobSpecial.cs index 468e939836..8ebeb69a6d 100644 --- a/Content.Shared/Roles/JobSpecial.cs +++ b/Content.Shared/Roles/JobSpecial.cs @@ -1,7 +1,9 @@ namespace Content.Shared.Roles { /// - /// Provides special hooks for when jobs get spawned in/equipped. + /// Provides special hooks for when jobs get spawned in/equipped. + /// TODO: This is being/should be utilized by more than jobs, and is really just a way to assign components/implants/status effects upon spawning. Rename this class and its derivatives in the future! + /// TODO: Move derivatives from Server to Shared, probably. /// [ImplicitDataDefinitionForInheritors] public abstract partial class JobSpecial diff --git a/Content.Shared/StatusEffectNew/StatusEffectSystem.API.cs b/Content.Shared/StatusEffectNew/StatusEffectSystem.API.cs index ab6362746c..a65d4fe063 100644 --- a/Content.Shared/StatusEffectNew/StatusEffectSystem.API.cs +++ b/Content.Shared/StatusEffectNew/StatusEffectSystem.API.cs @@ -353,6 +353,7 @@ public sealed partial class StatusEffectsSystem /// /// Returns all status effects that have the specified component. /// + /// Returns true if any entity with the specified component is found. public bool TryEffectsWithComp(EntityUid? target, [NotNullWhen(true)] out HashSet>? effects) where T : IComponent { effects = null; diff --git a/Content.Shared/StatusEffectNew/StatusEffectSystem.Relay.cs b/Content.Shared/StatusEffectNew/StatusEffectSystem.Relay.cs index 3644bed45e..9b16aadff0 100644 --- a/Content.Shared/StatusEffectNew/StatusEffectSystem.Relay.cs +++ b/Content.Shared/StatusEffectNew/StatusEffectSystem.Relay.cs @@ -1,3 +1,5 @@ +using Content.Shared.Damage.Events; +using Content.Shared.Mobs.Events; using Content.Shared.Movement.Events; using Content.Shared.Movement.Systems; using Content.Shared.Rejuvenate; @@ -25,6 +27,9 @@ public sealed partial class StatusEffectsSystem SubscribeLocalEvent(RefRelayStatusEffectEvent); SubscribeLocalEvent(RefRelayStatusEffectEvent); + SubscribeLocalEvent(RelayStatusEffectEvent); + SubscribeLocalEvent(RelayStatusEffectEvent); + SubscribeLocalEvent(RelayStatusEffectEvent); } diff --git a/Content.Shared/Traits/Assorted/PainNumbnessComponent.cs b/Content.Shared/Traits/Assorted/PainNumbnessComponent.cs deleted file mode 100644 index 9ae72c6286..0000000000 --- a/Content.Shared/Traits/Assorted/PainNumbnessComponent.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Content.Shared.Dataset; -using Robust.Shared.GameStates; -using Robust.Shared.Prototypes; - -namespace Content.Shared.Traits.Assorted; - -[RegisterComponent, NetworkedComponent] -public sealed partial class PainNumbnessComponent : Component -{ - /// - /// The fluent string prefix to use when picking a random suffix - /// This is only active for those who have the pain numbness component - /// - [DataField] - public ProtoId ForceSayNumbDataset = "ForceSayNumbDataset"; -} diff --git a/Content.Shared/Traits/Assorted/PainNumbnessStatusEffectComponent.cs b/Content.Shared/Traits/Assorted/PainNumbnessStatusEffectComponent.cs new file mode 100644 index 0000000000..c5c340fd10 --- /dev/null +++ b/Content.Shared/Traits/Assorted/PainNumbnessStatusEffectComponent.cs @@ -0,0 +1,20 @@ +using Content.Shared.Dataset; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Traits.Assorted; + +/// +/// Hides the damage overlay and displays the health alert for the client controlling the entity as full. +/// Has to be applied as a status effect. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class PainNumbnessStatusEffectComponent : Component +{ + /// + /// The fluent string prefix to use when picking a random suffix upon taking damage. + /// This is only active for those who have the pain numbness status effect. Set to null to prevent changing. + /// + [DataField] + public ProtoId? ForceSayNumbDataset = "ForceSayNumbDataset"; +} diff --git a/Content.Shared/Traits/Assorted/PainNumbnessSystem.cs b/Content.Shared/Traits/Assorted/PainNumbnessSystem.cs index 3ded13300d..688354c161 100644 --- a/Content.Shared/Traits/Assorted/PainNumbnessSystem.cs +++ b/Content.Shared/Traits/Assorted/PainNumbnessSystem.cs @@ -2,6 +2,7 @@ using Content.Shared.Damage.Events; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Events; using Content.Shared.Mobs.Systems; +using Content.Shared.StatusEffectNew; namespace Content.Shared.Traits.Assorted; @@ -11,36 +12,37 @@ public sealed class PainNumbnessSystem : EntitySystem public override void Initialize() { - SubscribeLocalEvent(OnComponentInit); - SubscribeLocalEvent(OnComponentRemove); - SubscribeLocalEvent(OnChangeForceSay); - SubscribeLocalEvent(OnAlertSeverityCheck); + SubscribeLocalEvent(OnEffectApplied); + SubscribeLocalEvent(OnEffectRemoved); + SubscribeLocalEvent>(OnChangeForceSay); + SubscribeLocalEvent>(OnAlertSeverityCheck); } - private void OnComponentRemove(EntityUid uid, PainNumbnessComponent component, ComponentRemove args) + private void OnEffectApplied(Entity ent, ref StatusEffectAppliedEvent args) { - if (!HasComp(uid)) + if (!HasComp(args.Target)) return; - _mobThresholdSystem.VerifyThresholds(uid); + _mobThresholdSystem.VerifyThresholds(args.Target); } - private void OnComponentInit(EntityUid uid, PainNumbnessComponent component, ComponentInit args) + private void OnEffectRemoved(Entity ent, ref StatusEffectRemovedEvent args) { - if (!HasComp(uid)) + if (!HasComp(args.Target)) return; - _mobThresholdSystem.VerifyThresholds(uid); + _mobThresholdSystem.VerifyThresholds(args.Target); } - private void OnChangeForceSay(Entity ent, ref BeforeForceSayEvent args) + private void OnChangeForceSay(Entity ent, ref StatusEffectRelayedEvent args) { - args.Prefix = ent.Comp.ForceSayNumbDataset; + if (ent.Comp.ForceSayNumbDataset != null) + args.Args.Prefix = ent.Comp.ForceSayNumbDataset.Value; } - private void OnAlertSeverityCheck(Entity ent, ref BeforeAlertSeverityCheckEvent args) + private void OnAlertSeverityCheck(Entity ent, ref StatusEffectRelayedEvent args) { - if (args.CurrentAlert == "HumanHealth") - args.CancelUpdate = true; + if (args.Args.CurrentAlert == "HumanHealth") + args.Args.CancelUpdate = true; } } diff --git a/Content.Shared/Traits/TraitPrototype.cs b/Content.Shared/Traits/TraitPrototype.cs index c79d3cbf30..376c5d4ac9 100644 --- a/Content.Shared/Traits/TraitPrototype.cs +++ b/Content.Shared/Traits/TraitPrototype.cs @@ -1,3 +1,4 @@ +using Content.Shared.Roles; using Content.Shared.Whitelist; using Robust.Shared.Prototypes; @@ -39,9 +40,17 @@ public sealed partial class TraitPrototype : IPrototype /// /// The components that get added to the player, when they pick this trait. + /// NOTE: When implementing a new trait, it's preferable to add it as a status effect instead if possible. /// [DataField] - public ComponentRegistry Components { get; private set; } = default!; + [Obsolete("Use JobSpecial instead.")] + public ComponentRegistry Components { get; private set; } = new(); + + /// + /// Special effects applied to the player who takes this Trait. + /// + [DataField(serverOnly: true)] + public List Specials { get; private set; } = new(); /// /// Gear that is given to the player, when they pick this trait. diff --git a/Resources/Prototypes/Entities/Mobs/Player/clone.yml b/Resources/Prototypes/Entities/Mobs/Player/clone.yml index c8ab57df58..e9e4f04f10 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/clone.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/clone.yml @@ -23,7 +23,6 @@ - Muted - Narcolepsy - Pacified - - PainNumbness - Paracusia - PermanentBlindness - Snoring diff --git a/Resources/Prototypes/Entities/StatusEffects/body.yml b/Resources/Prototypes/Entities/StatusEffects/body.yml index 3765ebefd4..4c94804884 100644 --- a/Resources/Prototypes/Entities/StatusEffects/body.yml +++ b/Resources/Prototypes/Entities/StatusEffects/body.yml @@ -3,16 +3,27 @@ id: BloodstreamStatusEffectBase abstract: true components: - - type: StatusEffect - whitelist: - components: - - Bloodstream + - type: StatusEffect + whitelist: + components: + - Bloodstream - type: entity parent: [ BloodstreamStatusEffectBase ] id: StatusEffectBloodloss name: bloodloss components: - - type: StutteringAccent - - type: DrunkStatusEffect - - type: RejuvenateRemovedStatusEffect + - type: StutteringAccent + - type: DrunkStatusEffect + - type: RejuvenateRemovedStatusEffect + +- type: entity + parent: MobStatusEffectBase + id: PainNumbnessTraitStatusEffect + components: + - type: StatusEffect + whitelist: + components: + - MobState + - MobThresholds + - type: PainNumbnessStatusEffect diff --git a/Resources/Prototypes/Traits/disabilities.yml b/Resources/Prototypes/Traits/disabilities.yml index 51993d3dd1..de102b54ee 100644 --- a/Resources/Prototypes/Traits/disabilities.yml +++ b/Resources/Prototypes/Traits/disabilities.yml @@ -83,8 +83,10 @@ name: trait-painnumbness-name description: trait-painnumbness-desc category: Disabilities - components: - - type: PainNumbness + specials: + - !type:ApplyStatusEffectSpecial + statusEffects: + - PainNumbnessTraitStatusEffect - type: trait id: Hemophilia