diff --git a/Content.Server/Traits/Assorted/NarcolepsyComponent.cs b/Content.Server/Traits/Assorted/NarcolepsyComponent.cs
new file mode 100644
index 0000000000..6cf153fd51
--- /dev/null
+++ b/Content.Server/Traits/Assorted/NarcolepsyComponent.cs
@@ -0,0 +1,22 @@
+namespace Content.Server.Traits.Assorted;
+
+///
+/// This is used for the narcolepsy trait.
+///
+[RegisterComponent, Access(typeof(NarcolepsySystem))]
+public sealed class NarcolepsyComponent : Component
+{
+ ///
+ /// The random time between incidents, (min, max).
+ ///
+ [DataField("timeBetweenIncidents", required: true)]
+ public Vector2 TimeBetweenIncidents { get; }
+
+ ///
+ /// The duration of incidents, (min, max).
+ ///
+ [DataField("durationOfIncident", required: true)]
+ public Vector2 DurationOfIncident { get; }
+
+ public float NextIncidentTime;
+}
diff --git a/Content.Server/Traits/Assorted/NarcolepsySystem.cs b/Content.Server/Traits/Assorted/NarcolepsySystem.cs
new file mode 100644
index 0000000000..0d4bb90710
--- /dev/null
+++ b/Content.Server/Traits/Assorted/NarcolepsySystem.cs
@@ -0,0 +1,53 @@
+using Content.Shared.Bed.Sleep;
+using Content.Shared.StatusEffect;
+using Robust.Shared.Random;
+
+namespace Content.Server.Traits.Assorted;
+
+///
+/// This handles narcolepsy, causing the affected to fall asleep uncontrollably at a random interval.
+///
+public sealed class NarcolepsySystem : EntitySystem
+{
+ private const string StatusEffectKey = "ForcedSleep"; // Same one used by N2O and other sleep chems.
+
+ [Dependency] private readonly StatusEffectsSystem _statusEffects = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+
+ ///
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(SetupNarcolepsy);
+ }
+
+ private void SetupNarcolepsy(EntityUid uid, NarcolepsyComponent component, ComponentStartup args)
+ {
+ component.NextIncidentTime =
+ _random.NextFloat(component.TimeBetweenIncidents.X, component.TimeBetweenIncidents.Y);
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ foreach (var narcolepsy in EntityQuery())
+ {
+ narcolepsy.NextIncidentTime -= frameTime;
+
+ if (narcolepsy.NextIncidentTime >= 0)
+ continue;
+
+ // Set the new time.
+ narcolepsy.NextIncidentTime +=
+ _random.NextFloat(narcolepsy.TimeBetweenIncidents.X, narcolepsy.TimeBetweenIncidents.Y);
+
+ var duration = _random.NextFloat(narcolepsy.DurationOfIncident.X, narcolepsy.DurationOfIncident.Y);
+
+ // Make sure the sleep time doesn't cut into the time to next incident.
+ narcolepsy.NextIncidentTime += duration;
+
+ _statusEffects.TryAddStatusEffect(narcolepsy.Owner, StatusEffectKey,
+ TimeSpan.FromSeconds(duration), false);
+ }
+ }
+}
diff --git a/Content.Server/Traits/Assorted/PacifistComponent.cs b/Content.Server/Traits/Assorted/PacifistComponent.cs
new file mode 100644
index 0000000000..095dc651fc
--- /dev/null
+++ b/Content.Server/Traits/Assorted/PacifistComponent.cs
@@ -0,0 +1,10 @@
+namespace Content.Server.Traits.Assorted;
+
+///
+/// This is used for enforcing pacifism.
+///
+[RegisterComponent]
+public sealed class PacifistComponent : Component
+{
+
+}
diff --git a/Content.Server/Traits/Assorted/PacifistSystem.cs b/Content.Server/Traits/Assorted/PacifistSystem.cs
new file mode 100644
index 0000000000..5d8b1f44db
--- /dev/null
+++ b/Content.Server/Traits/Assorted/PacifistSystem.cs
@@ -0,0 +1,17 @@
+using Content.Shared.CombatMode.Pacification;
+
+namespace Content.Server.Traits.Assorted;
+
+///
+/// This handles enforced pacifism.
+///
+public sealed class PacifistSystem : EntitySystem
+{
+ public override void Update(float frameTime)
+ {
+ foreach (var comp in EntityQuery())
+ {
+ EnsureComp(comp.Owner); // It's a status effect so just enforce it.
+ }
+ }
+}
diff --git a/Content.Server/Traits/Assorted/UncontrollableSnoughComponent.cs b/Content.Server/Traits/Assorted/UncontrollableSnoughComponent.cs
new file mode 100644
index 0000000000..bd36c6fa25
--- /dev/null
+++ b/Content.Server/Traits/Assorted/UncontrollableSnoughComponent.cs
@@ -0,0 +1,28 @@
+using Robust.Shared.Audio;
+
+namespace Content.Server.Traits.Assorted;
+
+///
+/// This is used for the occasional sneeze or cough.
+///
+[RegisterComponent]
+public sealed class UncontrollableSnoughComponent : Component
+{
+ ///
+ /// Message to play when snoughing.
+ ///
+ [DataField("snoughMessage")] public string SnoughMessage = "disease-sneeze";
+
+ ///
+ /// Sound to play when snoughing.
+ ///
+ [DataField("snoughSound")] public SoundSpecifier? SnoughSound;
+
+ ///
+ /// The random time between incidents, (min, max).
+ ///
+ [DataField("timeBetweenIncidents", required: true)]
+ public Vector2 TimeBetweenIncidents { get; }
+
+ public float NextIncidentTime;
+}
diff --git a/Content.Server/Traits/Assorted/UncontrollableSnoughSystem.cs b/Content.Server/Traits/Assorted/UncontrollableSnoughSystem.cs
new file mode 100644
index 0000000000..266dc5a8cc
--- /dev/null
+++ b/Content.Server/Traits/Assorted/UncontrollableSnoughSystem.cs
@@ -0,0 +1,52 @@
+using Content.Server.Disease;
+using Content.Shared.Audio;
+using Robust.Server.GameObjects;
+using Robust.Shared.Audio;
+using Robust.Shared.Player;
+using Robust.Shared.Random;
+
+namespace Content.Server.Traits.Assorted;
+
+///
+/// This handles making people randomly cough/sneeze without a disease.
+///
+public sealed class UncontrollableSnoughSystem : EntitySystem
+{
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly DiseaseSystem _diseaseSystem = default!;
+ [Dependency] private readonly AudioSystem _audioSystem = default!;
+
+ ///
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(SetupSnough);
+ }
+
+ private void SetupSnough(EntityUid uid, UncontrollableSnoughComponent component, ComponentStartup args)
+ {
+ component.NextIncidentTime =
+ _random.NextFloat(component.TimeBetweenIncidents.X, component.TimeBetweenIncidents.Y);
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ foreach (var snough in EntityQuery())
+ {
+ snough.NextIncidentTime -= frameTime;
+
+ if (snough.NextIncidentTime >= 0)
+ continue;
+
+ // Set the new time.
+ snough.NextIncidentTime +=
+ _random.NextFloat(snough.TimeBetweenIncidents.X, snough.TimeBetweenIncidents.Y);
+
+ if (snough.SnoughSound != null)
+ _audioSystem.PlayPvs(snough.SnoughSound, snough.Owner);
+
+ _diseaseSystem.SneezeCough(snough.Owner, null, snough.SnoughMessage, false);
+ }
+ }
+}
diff --git a/Content.Shared/Eye/Blinding/SharedBlindingSystem.cs b/Content.Shared/Eye/Blinding/SharedBlindingSystem.cs
index 3699df88bb..094d99f910 100644
--- a/Content.Shared/Eye/Blinding/SharedBlindingSystem.cs
+++ b/Content.Shared/Eye/Blinding/SharedBlindingSystem.cs
@@ -104,6 +104,8 @@ namespace Content.Shared.Eye.Blinding
}
blindable.Sources = Math.Max(blindable.Sources, 0);
+
+ Dirty(blindable);
}
public void AdjustEyeDamage(EntityUid uid, bool add, BlindableComponent? blindable = null)
diff --git a/Content.Shared/Traits/Assorted/PermanentBlindnessComponent.cs b/Content.Shared/Traits/Assorted/PermanentBlindnessComponent.cs
new file mode 100644
index 0000000000..2396ff3967
--- /dev/null
+++ b/Content.Shared/Traits/Assorted/PermanentBlindnessComponent.cs
@@ -0,0 +1,12 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Traits.Assorted;
+
+///
+/// This is used for making something blind forever.
+///
+[RegisterComponent, NetworkedComponent]
+public sealed class PermanentBlindnessComponent : Component
+{
+}
+
diff --git a/Content.Shared/Traits/Assorted/PermanentBlindnessSystem.cs b/Content.Shared/Traits/Assorted/PermanentBlindnessSystem.cs
new file mode 100644
index 0000000000..448433be39
--- /dev/null
+++ b/Content.Shared/Traits/Assorted/PermanentBlindnessSystem.cs
@@ -0,0 +1,41 @@
+using Content.Shared.Examine;
+using Content.Shared.Eye.Blinding;
+using Content.Shared.IdentityManagement;
+using Robust.Shared.Network;
+
+namespace Content.Shared.Traits.Assorted;
+
+///
+/// This handles permanent blindness, both the examine and the actual effect.
+///
+public sealed class PermanentBlindnessSystem : EntitySystem
+{
+ [Dependency] private readonly INetManager _net = default!;
+ [Dependency] private readonly SharedBlindingSystem _blinding = default!;
+
+ ///
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnStartup);
+ SubscribeLocalEvent(OnShutdown);
+ SubscribeLocalEvent(OnExamined);
+ }
+
+ private void OnExamined(EntityUid uid, PermanentBlindnessComponent component, ExaminedEvent args)
+ {
+ if (args.IsInDetailsRange && !_net.IsClient)
+ {
+ args.PushMarkup(Loc.GetString("permanent-blindness-trait-examined", ("target", Identity.Entity(uid, EntityManager))));
+ }
+ }
+
+ private void OnShutdown(EntityUid uid, PermanentBlindnessComponent component, ComponentShutdown args)
+ {
+ _blinding.AdjustBlindSources(uid, false);
+ }
+
+ private void OnStartup(EntityUid uid, PermanentBlindnessComponent component, ComponentStartup args)
+ {
+ _blinding.AdjustBlindSources(uid, true);
+ }
+}
diff --git a/Resources/Locale/en-US/traits/traits.ftl b/Resources/Locale/en-US/traits/traits.ftl
new file mode 100644
index 0000000000..08df78b1ac
--- /dev/null
+++ b/Resources/Locale/en-US/traits/traits.ftl
@@ -0,0 +1 @@
+permanent-blindness-trait-examined = [color=lightblue]{CAPITALIZE(POSS-ADJ($target))} eyes are glassy and unfocused. It doesn't seem like {SUBJECT($target)} can see you.[/color]
diff --git a/Resources/Prototypes/Traits/disabilities.yml b/Resources/Prototypes/Traits/disabilities.yml
new file mode 100644
index 0000000000..365b896608
--- /dev/null
+++ b/Resources/Prototypes/Traits/disabilities.yml
@@ -0,0 +1,19 @@
+- type: trait
+ id: Blindness
+ name: Blindness
+ components:
+ - type: PermanentBlindness
+
+- type: trait
+ id: Narcolepsy
+ name: Narcolepsy
+ components:
+ - type: Narcolepsy
+ timeBetweenIncidents: 300, 600
+ durationOfIncident: 10, 30
+
+- type: trait
+ id: Pacifist
+ name: Pacifist
+ components:
+ - type: Pacifist
diff --git a/Resources/Prototypes/Traits/inconveniences.yml b/Resources/Prototypes/Traits/inconveniences.yml
new file mode 100644
index 0000000000..dde4135a7d
--- /dev/null
+++ b/Resources/Prototypes/Traits/inconveniences.yml
@@ -0,0 +1,10 @@
+- type: trait
+ id: UncontrollableSneezing
+ name: Runny nose
+ components:
+ - type: UncontrollableSnough
+ snoughSound:
+ collection: Sneezes
+ params:
+ variation: 0.2
+ timeBetweenIncidents: 0.3, 300