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 654a2de07e..bd53e32567 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; @@ -37,6 +38,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. @@ -76,6 +78,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. @@ -268,4 +274,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 5e7675b76b..8e062785e2 100644 --- a/Content.Server/Traits/TraitSystem.cs +++ b/Content.Server/Traits/TraitSystem.cs @@ -2,6 +2,7 @@ // 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 @@ // [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 @@ // 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); +// } // // // Begin DeltaV - Add overridden components // if(traitPrototype.OverriddenComponents != 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 caacaaff4a..91fcfccc8a 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; // DeltaV - Traits rework // using Robust.Shared.Prototypes; // using Content.Shared.Humanoid.Prototypes; // DeltaV - Trait species hiding @@ -40,11 +41,19 @@ // // /// // /// 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] +// [Obsolete("Use JobSpecial instead.")] // public ComponentRegistry Components { get; private set; } = default!; // // /// +// /// Special effects applied to the player who takes this Trait. +// /// +// [DataField(serverOnly: true)] +// public List Specials { get; private set; } = new(); +// +// /// // /// DeltaV - Components that get added to the player, overriding any existing instances of the component if they exist. // /// // [DataField] diff --git a/Resources/Prototypes/Entities/Mobs/Player/clone.yml b/Resources/Prototypes/Entities/Mobs/Player/clone.yml index 7a3f9d99cf..6b0af7e86b 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 488fdd7d8c..b9a8e1fd54 100644 --- a/Resources/Prototypes/Traits/disabilities.yml +++ b/Resources/Prototypes/Traits/disabilities.yml @@ -87,8 +87,10 @@ # name: trait-painnumbness-name # description: trait-painnumbness-desc # category: Disabilities -# components: -# - type: PainNumbness +# specials: +# - !type:ApplyStatusEffectSpecial +# statusEffects: +# - PainNumbnessTraitStatusEffect # #- type: trait # id: ImpairedMobility