From b9607e39b0d26d100bbac8d8a15e6aad8ab6cd60 Mon Sep 17 00:00:00 2001
From: beck-thompson <107373427+beck-thompson@users.noreply.github.com>
Date: Sun, 16 Aug 2026 09:07:42 +0000
Subject: [PATCH] Exception now meows when an error occurs on the server
(#39734)
* Exception now meows when an error occurs
* tweaks
* Add to real mouse
* 1984 :pensive:
* Sussy entity effect
* Comment
* More errors
* Other fixes
* opps
* fixes
* cleanup
* Cleanup
* Remove saving last message
* runtime as well!
* Make logger private, clean up error has occured to boolean instead of string
* Add auto pause and time offset serializer
* Properly block accent when speaking (sometimes)
* Add package
* Fix
* Review
* fix
---
Content.Server/Content.Server.csproj | 1 +
.../Effects/MakeSentientEntityEffectSystem.cs | 2 +
.../RuntimeFun/SpeakOnExceptionComponent.cs | 41 ++++++++
.../RuntimeFun/SpeakOnExceptionSystem.cs | 96 +++++++++++++++++++
.../speak-on-exception/speak-on-exception.ftl | 14 +++
.../Entities/Mobs/NPCs/animal_speech.yml | 5 +
.../Prototypes/Entities/Mobs/NPCs/pets.yml | 7 ++
7 files changed, 166 insertions(+)
create mode 100644 Content.Server/RuntimeFun/SpeakOnExceptionComponent.cs
create mode 100644 Content.Server/RuntimeFun/SpeakOnExceptionSystem.cs
create mode 100644 Resources/Locale/en-US/speak-on-exception/speak-on-exception.ftl
create mode 100644 Resources/Prototypes/Entities/Mobs/NPCs/animal_speech.yml
diff --git a/Content.Server/Content.Server.csproj b/Content.Server/Content.Server.csproj
index 298ad4ea0a..66d0b9ef2f 100644
--- a/Content.Server/Content.Server.csproj
+++ b/Content.Server/Content.Server.csproj
@@ -14,6 +14,7 @@
+
diff --git a/Content.Server/EntityEffects/Effects/MakeSentientEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/MakeSentientEntityEffectSystem.cs
index b1e2703995..c19f62742c 100644
--- a/Content.Server/EntityEffects/Effects/MakeSentientEntityEffectSystem.cs
+++ b/Content.Server/EntityEffects/Effects/MakeSentientEntityEffectSystem.cs
@@ -1,4 +1,5 @@
using Content.Server.Ghost.Roles.Components;
+using Content.Server.RuntimeFun;
using Content.Shared.EntityEffects;
using Content.Shared.EntityEffects.Effects;
using Content.Shared.Mind.Components;
@@ -23,6 +24,7 @@ public sealed partial class MakeSentientEntityEffectSystem : EntityEffectSystem<
RemComp(entity);
// TODO: Make MonkeyAccent a replacement accent and remove MonkeyAccent code-smell.
RemComp(entity);
+ RemComp(entity);
}
// Stops from adding a ghost role to things like people who already have a mind
diff --git a/Content.Server/RuntimeFun/SpeakOnExceptionComponent.cs b/Content.Server/RuntimeFun/SpeakOnExceptionComponent.cs
new file mode 100644
index 0000000000..16fb955040
--- /dev/null
+++ b/Content.Server/RuntimeFun/SpeakOnExceptionComponent.cs
@@ -0,0 +1,41 @@
+using Content.Shared.Dataset;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Server.RuntimeFun;
+
+///
+/// Entities with this component will speak from a dataset everytime an error occurs in an interval.
+///
+[RegisterComponent, AutoGenerateComponentPause]
+public sealed partial class SpeakOnExceptionComponent : Component
+{
+ ///
+ /// Minimum time between error speech events.
+ ///
+ [DataField]
+ public TimeSpan SpeechCooldown = TimeSpan.FromMinutes(1);
+
+ ///
+ /// The chance to speak without an accent.
+ ///
+ [DataField]
+ public float ChanceSpeakNoAccent = 0.005f;
+
+ ///
+ /// Localized dataset used when speaking.
+ ///
+ [DataField]
+ public ProtoId Dataset = "ExceptionSpeechDataset";
+
+ ///
+ /// The next time the entity can say another error.
+ ///
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
+ public TimeSpan? NextTimeCanSpeak;
+
+ ///
+ /// If this component is currently trying to block accents from working.
+ ///
+ public bool BlockAccent;
+}
diff --git a/Content.Server/RuntimeFun/SpeakOnExceptionSystem.cs b/Content.Server/RuntimeFun/SpeakOnExceptionSystem.cs
new file mode 100644
index 0000000000..da425d9535
--- /dev/null
+++ b/Content.Server/RuntimeFun/SpeakOnExceptionSystem.cs
@@ -0,0 +1,96 @@
+using Content.Server.Chat.Systems;
+using Content.Shared.Chat;
+using Content.Shared.Speech.EntitySystems;
+using Robust.Shared.Random;
+using Robust.Shared.Timing;
+using Serilog.Events;
+
+namespace Content.Server.RuntimeFun;
+
+///
+/// System for the . Deals with getting the latest error log and making
+/// entities with that component speak.
+///
+public sealed partial class SpeakOnExceptionSystem : EntitySystem
+{
+ [Dependency] private IGameTiming _timing = default!;
+ [Dependency] private ILogManager _log = default!;
+ [Dependency] private IRobustRandom _random = default!;
+ [Dependency] private ChatSystem _chat = default!;
+
+ // Special log handler that just saves the latest error.
+ private SpeakOnExceptionLogHandler _logHandler = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ _logHandler = new SpeakOnExceptionLogHandler();
+ _log.RootSawmill.AddHandler(_logHandler);
+
+ SubscribeLocalEvent(OnTransformSpeech, before: [ typeof(AccentSystem) ]);
+ }
+
+ public override void Shutdown()
+ {
+ _log.RootSawmill.RemoveHandler(_logHandler);
+ }
+
+ public override void Update(float frameTime)
+ {
+ if (!_logHandler.ErrorHasOccured)
+ return;
+
+ _logHandler.ErrorHasOccured = false;
+
+ var query = EntityQueryEnumerator();
+
+ while (query.MoveNext(out var uid, out var comp))
+ {
+ if (_timing.CurTime < comp.NextTimeCanSpeak)
+ continue;
+
+ if (_random.Prob(comp.ChanceSpeakNoAccent))
+ comp.BlockAccent = true;
+
+ _chat.TrySendInGameICMessage(uid, CensorMessage(comp), InGameICChatType.Speak, ChatTransmitRange.Normal, true);
+
+ comp.BlockAccent = false;
+
+ comp.NextTimeCanSpeak += comp.SpeechCooldown;
+ }
+ }
+
+ [SubscribeLocalEvent]
+ private void OnMapInit(Entity ent, ref MapInitEvent args)
+ {
+ ent.Comp.NextTimeCanSpeak = _timing.CurTime;
+ }
+
+ private void OnTransformSpeech(Entity ent, ref TransformSpeechEvent args)
+ {
+ if (ent.Comp.BlockAccent)
+ args.Cancel();
+ }
+
+ private string CensorMessage(SpeakOnExceptionComponent comp)
+ {
+ return Loc.GetString(_random.Pick(ProtoMan.Index(comp.Dataset).Values));
+ }
+
+ // Log handler for SpeakOnException entities.
+ private sealed class SpeakOnExceptionLogHandler : ILogHandler
+ {
+ // Gets set to true if an error ever occurs - reset this to false if you want to see if another error has occurred!
+ public bool ErrorHasOccured;
+
+ public void Log(string sawmillName, LogEvent message)
+ {
+ if (message.Exception == null)
+ return;
+
+ ErrorHasOccured = true;
+ }
+ }
+}
+
diff --git a/Resources/Locale/en-US/speak-on-exception/speak-on-exception.ftl b/Resources/Locale/en-US/speak-on-exception/speak-on-exception.ftl
new file mode 100644
index 0000000000..d67d960bfe
--- /dev/null
+++ b/Resources/Locale/en-US/speak-on-exception/speak-on-exception.ftl
@@ -0,0 +1,14 @@
+exception-censored-1 = called `Option::unwrap()` on a `None` value`
+exception-censored-2 = SyntaxError: invalid syntax
+exception-censored-3 = java.lang.NullPointerException: Cannot invoke "com.spacestation14.ss14.Cat.meow(java.lang.String)" because the return value of "com.spacestation14.robusttoolbox.Utils.getCatSound()" is null
+exception-censored-4 = Error: Can't open display: x3
+exception-censored-5 = Segmentation fault (core dumped)
+exception-censored-6 = cat-speech-dataset-120
+exception-censored-7 = Can't resolve "Robust.Shared.GameObjects.MetaDataComponent" on entity 5233063!
+exception-censored-8 = Uncaught ReferenceError: meow is not defined
+exception-censored-9 = TabError: inconsistent use of tabs and spaces in indentation
+exception-censored-10 = code.dm:3:error: meow: undefined proc
+exception-censored-11 = Failed to wipe mind: Exception
+exception-censored-12 = Guru Meditation: #0000000F.0000ACA7
+exception-censored-13 = Unexpected token 'w'
+exception-censored-14 = Operation timed out: AwaitNap()
diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animal_speech.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animal_speech.yml
new file mode 100644
index 0000000000..3eab487c85
--- /dev/null
+++ b/Resources/Prototypes/Entities/Mobs/NPCs/animal_speech.yml
@@ -0,0 +1,5 @@
+- type: localizedDataset
+ id: ExceptionSpeechDataset
+ values:
+ prefix: exception-censored-
+ count: 14
diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml
index 9b0cb337f8..74aa2bf7ec 100644
--- a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml
+++ b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml
@@ -156,6 +156,7 @@
attributes:
proper: true
gender: female
+ - type: SpeakOnException
- type: entity
parent: [ MobCorgiPuppy, BaseSlicingRefinable ]
@@ -197,6 +198,9 @@
- VimPilot
- type: StealTarget
stealGroup: AnimalNamedCat
+ - type: SpeakOnException
+ speechCooldown: 3m
+ chanceSpeakNoAccent: 0.01
- type: entity
parent: [ BaseUniquePet, MobCatCalico ]
@@ -207,6 +211,9 @@
- type: NpcFactionMember
factions:
- PetsNT
+ - type: SpeakOnException
+ speechCooldown: 2m
+ chanceSpeakNoAccent: 0.007
- type: Grammar
attributes:
proper: true