diff --git a/Content.Client/Options/UI/Tabs/MiscTab.xaml b/Content.Client/Options/UI/Tabs/MiscTab.xaml
index fbd6fc908e6..b06a53731ac 100644
--- a/Content.Client/Options/UI/Tabs/MiscTab.xaml
+++ b/Content.Client/Options/UI/Tabs/MiscTab.xaml
@@ -45,6 +45,12 @@ SPDX-License-Identifier: AGPL-3.0-or-later
+
+
+
diff --git a/Content.Client/Options/UI/Tabs/MiscTab.xaml.cs b/Content.Client/Options/UI/Tabs/MiscTab.xaml.cs
index 10a22191d8c..33d67ec861c 100644
--- a/Content.Client/Options/UI/Tabs/MiscTab.xaml.cs
+++ b/Content.Client/Options/UI/Tabs/MiscTab.xaml.cs
@@ -140,6 +140,7 @@ public MiscTab()
Control.AddOptionCheckBox(CCVars.ChatFancyNameBackground, FancyNameBackgroundsCheckBox);
Control.AddOptionCheckBox(CCVars.StaticStorageUI, StaticStorageUI);
+ Control.AddOptionCheckBox(CCVars.OmuRadiationVisualsEnabled, RadiationVisualsCheckBox);
Control.Initialize();
}
}
diff --git a/Content.Client/Radiation/RadiationVisualOverlay.cs b/Content.Client/Radiation/RadiationVisualOverlay.cs
new file mode 100644
index 00000000000..d82d1fa9038
--- /dev/null
+++ b/Content.Client/Radiation/RadiationVisualOverlay.cs
@@ -0,0 +1,218 @@
+using Content.Shared.CCVar;
+using Robust.Shared.Configuration;
+using Robust.Shared.IoC;
+// SPDX-FileCopyrightText: 2026 puntsss
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Robust.Client.Graphics;
+using Robust.Shared.Enums;
+using Robust.Shared.Maths;
+using Robust.Shared.Timing;
+using System.Numerics;
+
+namespace Content.Client.Radiation;
+
+public sealed class RadiationVisualOverlay : Overlay
+{
+ private readonly IGameTiming _timing;
+
+ private float _currentIntensity;
+ private float _targetIntensity;
+ private TimeSpan _endTime;
+ private TimeSpan _lastDrawTime;
+
+ public override OverlaySpace Space => OverlaySpace.ScreenSpace;
+
+ public RadiationVisualOverlay(IGameTiming timing)
+ {
+ _timing = timing;
+ }
+
+ public void Show(float intensity, TimeSpan duration)
+ {
+ _targetIntensity = Math.Clamp(intensity, 0f, 1f);
+ _endTime = _timing.CurTime + duration;
+ }
+
+ protected override void Draw(in OverlayDrawArgs args)
+ {
+ if (!IoCManager.Resolve().GetCVar(CCVars.OmuRadiationVisualsEnabled))
+ return;
+
+ var deltaSeconds = _lastDrawTime == default
+ ? 0f
+ : Math.Clamp((float) (_timing.CurTime - _lastDrawTime).TotalSeconds, 0f, 0.1f);
+
+ _lastDrawTime = _timing.CurTime;
+
+ var wantedIntensity = _timing.CurTime < _endTime ? _targetIntensity : 0f;
+ var speed = wantedIntensity > _currentIntensity ? 18f : 2.5f;
+ _currentIntensity = MathHelper.Lerp(_currentIntensity, wantedIntensity, Math.Clamp(deltaSeconds * speed, 0f, 1f));
+
+ if (_currentIntensity <= 0.005f)
+ return;
+
+ var handle = args.ScreenHandle;
+ var bounds = args.ViewportBounds;
+ var width = bounds.Width;
+ var height = bounds.Height;
+ var intensity = _currentIntensity;
+
+ var seed = (int) (_timing.CurTime.TotalMilliseconds / 8);
+ var random = new Random(seed);
+
+ var dimAlpha = Math.Clamp(0.004f + 0.020f * intensity, 0.004f, 0.030f);
+ handle.DrawRect(bounds, new Color(0f, 0f, 0f, dimAlpha));
+
+ var deadPixels = (int) (65 + 250 * intensity);
+ for (var i = 0; i < deadPixels; i++)
+ {
+ var x = bounds.Left + (float) random.NextDouble() * width;
+ var y = bounds.Top + (float) random.NextDouble() * height;
+
+ float w;
+ float h;
+ var r = random.NextDouble();
+ if (r < 0.80)
+ {
+ w = 1f + (float) random.NextDouble() * (1.3f + 0.8f * intensity);
+ h = 1f + (float) random.NextDouble() * (1.3f + 0.8f * intensity);
+ }
+ else if (r < 0.95)
+ {
+ w = 1.5f + (float) random.NextDouble() * (2.0f + 1.3f * intensity);
+ h = 1.5f + (float) random.NextDouble() * (2.0f + 1.3f * intensity);
+ }
+ else
+ {
+ w = 3f + (float) random.NextDouble() * (4f + 3f * intensity);
+ h = 1.5f + (float) random.NextDouble() * (2.5f + 2f * intensity);
+ }
+
+ var alpha = 0.18f + 0.40f * intensity * (0.45f + (float) random.NextDouble() * 0.75f);
+ handle.DrawRect(UIBox2.FromDimensions(new Vector2(x, y), new Vector2(w, h)),
+ new Color(0f, 0f, 0f, Math.Clamp(alpha, 0f, 0.70f)));
+ }
+
+ var hotPixels = (int) (24 + 120 * intensity);
+ for (var i = 0; i < hotPixels; i++)
+ {
+ var x = bounds.Left + (float) random.NextDouble() * width;
+ var y = bounds.Top + (float) random.NextDouble() * height;
+ var roll = random.NextDouble();
+
+ float w;
+ float h;
+ if (roll < 0.84)
+ {
+ w = 0.8f + (float) random.NextDouble() * (1.1f + 1.1f * intensity);
+ h = w;
+ }
+ else
+ {
+ w = 2f + (float) random.NextDouble() * (4f + 8f * intensity);
+ h = 1f + (float) random.NextDouble() * 1.1f;
+ }
+
+ var alpha = 0.14f + 0.28f * intensity * (0.40f + (float) random.NextDouble() * 0.80f);
+ var shade = 0.84f + (float) random.NextDouble() * 0.16f;
+ handle.DrawRect(UIBox2.FromDimensions(new Vector2(x, y), new Vector2(w, h)),
+ new Color(shade, shade, shade, Math.Clamp(alpha, 0f, 0.44f)));
+ }
+
+ // More noticeable blueish-grey sensor specks.
+ var blueGreyDots = (int) (18 + 80 * intensity);
+ for (var i = 0; i < blueGreyDots; i++)
+ {
+ var x = bounds.Left + (float) random.NextDouble() * width;
+ var y = bounds.Top + (float) random.NextDouble() * height;
+ var size = 1f + (float) random.NextDouble() * (1.8f + 1.7f * intensity);
+ var alpha = 0.16f + 0.24f * intensity * (0.45f + (float) random.NextDouble() * 0.80f);
+ var color = new Color(0.60f, 0.68f, 0.80f, Math.Clamp(alpha, 0f, 0.34f));
+ handle.DrawRect(UIBox2.FromDimensions(new Vector2(x, y), new Vector2(size, size)), color);
+ }
+
+ var burstCount = (int) (4 + 16 * intensity);
+ for (var i = 0; i < burstCount; i++)
+ {
+ var centerX = bounds.Left + (float) random.NextDouble() * width;
+ var centerY = bounds.Top + (float) random.NextDouble() * height;
+ var radius = 5f + (float) random.NextDouble() * (10f + 18f * intensity);
+ var dots = random.Next(5, 12);
+
+ for (var j = 0; j < dots; j++)
+ {
+ var x = centerX + ((float) random.NextDouble() - 0.5f) * radius;
+ var y = centerY + ((float) random.NextDouble() - 0.5f) * radius;
+ var size = 1f + (float) random.NextDouble() * (1.6f + 1.8f * intensity);
+ var alpha = 0.16f + 0.32f * intensity;
+ var roll = random.NextDouble();
+
+ Color color;
+ if (roll < 0.52)
+ color = new Color(0f, 0f, 0f, alpha);
+ else if (roll < 0.78)
+ color = new Color(1f, 1f, 1f, alpha * 0.82f);
+ else
+ color = new Color(0.60f, 0.68f, 0.80f, alpha * 0.72f);
+
+ handle.DrawRect(UIBox2.FromDimensions(new Vector2(x, y), new Vector2(size, size)), color);
+ }
+ }
+
+ var streakCount = (int) (3 + 10 * intensity);
+ for (var i = 0; i < streakCount; i++)
+ {
+ var horizontal = random.NextDouble() < 0.72;
+ var bright = random.NextDouble() < 0.55;
+ var shade = bright ? 1f : 0f;
+ var baseAlpha = 0.08f + 0.18f * intensity;
+
+ if (horizontal)
+ {
+ var x = bounds.Left + (float) random.NextDouble() * width;
+ var y = bounds.Top + (float) random.NextDouble() * height;
+ var w = 4f + (float) random.NextDouble() * (10f + 18f * intensity);
+ var h = 1f + (float) random.NextDouble() * 0.9f;
+ handle.DrawRect(UIBox2.FromDimensions(new Vector2(x, y), new Vector2(w, h)),
+ new Color(shade, shade, shade, baseAlpha));
+ handle.DrawRect(UIBox2.FromDimensions(new Vector2(x + w, y), new Vector2(w * 0.6f, h)),
+ new Color(shade, shade, shade, baseAlpha * 0.35f));
+ }
+ else
+ {
+ var x = bounds.Left + (float) random.NextDouble() * width;
+ var y = bounds.Top + (float) random.NextDouble() * height;
+ var w = 1f + (float) random.NextDouble() * 0.9f;
+ var h = 4f + (float) random.NextDouble() * (10f + 18f * intensity);
+ handle.DrawRect(UIBox2.FromDimensions(new Vector2(x, y), new Vector2(w, h)),
+ new Color(shade, shade, shade, baseAlpha));
+ handle.DrawRect(UIBox2.FromDimensions(new Vector2(x, y + h), new Vector2(w, h * 0.6f)),
+ new Color(shade, shade, shade, baseAlpha * 0.35f));
+ }
+ }
+
+ if (intensity >= 0.24f)
+ {
+ var dropoutCount = (int) (1 + 5 * intensity);
+ for (var i = 0; i < dropoutCount; i++)
+ {
+ var x = bounds.Left + (float) random.NextDouble() * width;
+ var y = bounds.Top + (float) random.NextDouble() * height;
+ var w = 5f + (float) random.NextDouble() * (12f + 22f * intensity);
+ var h = 2f + (float) random.NextDouble() * (4f + 8f * intensity);
+ var alpha = 0.04f + 0.08f * intensity;
+ handle.DrawRect(UIBox2.FromDimensions(new Vector2(x, y), new Vector2(w, h)),
+ new Color(0f, 0f, 0f, alpha));
+ }
+ }
+
+ if (intensity >= 0.20f)
+ {
+ var flicker = (float) (Math.Sin(_timing.CurTime.TotalSeconds * 37f) + 1f) / 2f;
+ var flickerAlpha = flicker * 0.022f * intensity;
+ handle.DrawRect(bounds, new Color(0f, 0f, 0f, flickerAlpha));
+ }
+ }
+}
diff --git a/Content.Client/Radiation/RadiationVisualSystem.cs b/Content.Client/Radiation/RadiationVisualSystem.cs
new file mode 100644
index 00000000000..d032add845a
--- /dev/null
+++ b/Content.Client/Radiation/RadiationVisualSystem.cs
@@ -0,0 +1,40 @@
+// SPDX-FileCopyrightText: 2026 puntsss
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using Content.Shared.Radiation.Events;
+using Robust.Client.Graphics;
+using Robust.Shared.Timing;
+
+namespace Content.Client.Radiation;
+
+public sealed class RadiationVisualSystem : EntitySystem
+{
+ [Dependency] private readonly IOverlayManager _overlayManager = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+
+ private RadiationVisualOverlay? _overlay;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ _overlay = new RadiationVisualOverlay(_timing);
+ _overlayManager.AddOverlay(_overlay);
+
+ SubscribeNetworkEvent(OnRadiationVisuals);
+ }
+
+ public override void Shutdown()
+ {
+ base.Shutdown();
+
+ if (_overlay != null)
+ _overlayManager.RemoveOverlay(_overlay);
+ }
+
+ private void OnRadiationVisuals(RadiationVisualsEvent ev)
+ {
+ _overlay?.Show(ev.Intensity, TimeSpan.FromSeconds(ev.Duration));
+ }
+}
diff --git a/Content.Server/Omu/RadiationEffects/OmuRadiationSicknessSystem.cs b/Content.Server/Omu/RadiationEffects/OmuRadiationSicknessSystem.cs
new file mode 100644
index 00000000000..a04e696eaee
--- /dev/null
+++ b/Content.Server/Omu/RadiationEffects/OmuRadiationSicknessSystem.cs
@@ -0,0 +1,393 @@
+
+using System.Reflection;
+using System.Linq;
+using Content.Shared.CCVar;
+using Content.Shared.Popups;
+using Content.Shared.Radiation.Events;
+using Robust.Shared.Configuration;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+using Robust.Shared.Timing;
+
+namespace Content.Server.Omu.RadiationEffects;
+
+public sealed class OmuRadiationSicknessSystem : EntitySystem
+{
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
+ [Dependency] private readonly IConfigurationManager _cfg = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly IPrototypeManager _proto = default!;
+
+ private readonly Dictionary _nextMinorMessage = new();
+ private readonly Dictionary _nextMajorEffect = new();
+
+ private static readonly string[] MinorMessages =
+ {
+ "You taste metal in your mouth.",
+ "Your stomach twists.",
+ "You feel nauseous...",
+ "Your skin prickles.",
+ "You feel a strange warmth under your skin.",
+ "Your head pounds.",
+ "Your vision swims for a moment.",
+ "Your hands tremble.",
+ "Your throat feels dry.",
+ "You feel suddenly weak.",
+ "You feel your skin burning.",
+ "Your heart races, and then slows.",
+ "A wave of sickness rolls through you.",
+ "Your mouth fills with a bitter taste.",
+ "You feel like something is very wrong.",
+ "A cold sweat breaks over your skin.",
+ "Your bones ache.",
+ "Your guts churn.",
+ "You feel lightheaded.",
+ "Your vision flickers with static."
+ };
+
+ private static readonly string[] MajorMessages =
+ {
+ "Your stomach lurches violently!",
+ "You feel like you are going to throw up!",
+ "The world spins around you!",
+ "Your knees almost give out!",
+ "A hot, sick feeling crawls through your body!",
+ "Your body rejects something inside you!",
+ "Your vision blurs as radiation sickness takes hold!",
+ "You stagger as nausea washes over you!",
+ "Your body feels poisoned!"
+ };
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent(OnIrradiated);
+ }
+
+ private void OnIrradiated(OnIrradiatedEvent args)
+ {
+ if (!_cfg.GetCVar(CCVars.OmuRadiationSicknessEnabled))
+ return;
+
+ if (!TryGetTarget(args, out var target))
+ return;
+
+ if (!Exists(target))
+ return;
+
+ var severity = GetSeverity(args);
+ if (severity < 0.01f)
+ severity = 0.25f;
+
+ var now = _timing.CurTime;
+
+ if (!_nextMinorMessage.TryGetValue(target, out var nextMsg) || now >= nextMsg)
+ {
+ _nextMinorMessage[target] = now + TimeSpan.FromSeconds(_random.NextFloat(4f, 8f));
+ Popup(target, _random.Pick(MinorMessages));
+ }
+
+ var chance = Math.Clamp(0.08f + severity * 0.18f, 0.08f, 0.55f);
+
+ if (_nextMajorEffect.TryGetValue(target, out var nextMajor) && now < nextMajor)
+ return;
+
+ if (!_random.Prob(chance))
+ return;
+
+ _nextMajorEffect[target] = now + TimeSpan.FromSeconds(_random.NextFloat(8f, 16f));
+
+ Popup(target, _random.Pick(MajorMessages));
+ TrySpawnVomit(target);
+ TryApplyDrunkDizzyStutter(target);
+ }
+
+ private void Popup(EntityUid target, string message)
+ {
+ try { _popup.PopupEntity(message, target, target); }
+ catch { }
+ }
+
+ private void TrySpawnVomit(EntityUid target)
+ {
+ foreach (var proto in new[] { "PuddleVomit", "VomitPuddle", "Vomit", "PuddleWater" })
+ {
+ try
+ {
+ if (!_proto.HasIndex(proto))
+ continue;
+
+ Spawn(proto, Transform(target).Coordinates);
+ return;
+ }
+ catch { }
+ }
+ }
+
+ private void TryApplyDrunkDizzyStutter(EntityUid target)
+ {
+ TryApplyStatusEffectByReflection(target, "Drunk", TimeSpan.FromSeconds(20));
+ TryApplyStatusEffectByReflection(target, "Dizzy", TimeSpan.FromSeconds(16));
+ TryApplyStatusEffectByReflection(target, "Stutter", TimeSpan.FromSeconds(12));
+
+ foreach (var componentName in new[]
+ {
+ "DrunkComponent",
+ "DizzyComponent",
+ "DizzinessComponent",
+ "StutteringComponent",
+ "StutterComponent"
+ })
+ {
+ var type = FindType(componentName);
+ if (type != null)
+ TryAddComponentByType(target, type);
+ }
+ }
+
+ private void TryApplyStatusEffectByReflection(EntityUid uid, string effectId, TimeSpan duration)
+ {
+ try
+ {
+ var statusSystemType = AppDomain.CurrentDomain.GetAssemblies()
+ .SelectMany(SafeTypes)
+ .FirstOrDefault(t => t.Name.Contains("StatusEffectsSystem"));
+
+ if (statusSystemType == null)
+ return;
+
+ var iocType = FindType("IoCManager");
+ var resolve = iocType?.GetMethods(BindingFlags.Public | BindingFlags.Static)
+ .FirstOrDefault(m => m.Name == "Resolve" && m.IsGenericMethodDefinition);
+
+ var systemManagerType = AppDomain.CurrentDomain.GetAssemblies()
+ .SelectMany(SafeTypes)
+ .FirstOrDefault(t => t.Name.Contains("IEntitySystemManager"));
+
+ if (resolve == null || systemManagerType == null)
+ return;
+
+ var mgr = resolve.MakeGenericMethod(systemManagerType).Invoke(null, Array.Empty