Exception now meows when an error occurs on the server (#39734)

* Exception now meows when an error occurs

* tweaks

* Add to real mouse

* 1984 😔

* 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
This commit is contained in:
beck-thompson 2026-08-16 09:07:42 +00:00 • committed by GitHub
parent c9a78a0208
commit b9607e39b0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 166 additions and 0 deletions

View file

@ -14,6 +14,7 @@
<PackageReference Include="NetCord" />
<PackageReference Include="prometheus-net" />
<PackageReference Include="Microsoft.Extensions.ObjectPool" />
<PackageReference Include="Serilog" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Content.Packaging\Content.Packaging.csproj" />

View file

@ -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<ReplacementAccentComponent>(entity);
// TODO: Make MonkeyAccent a replacement accent and remove MonkeyAccent code-smell.
RemComp<MonkeyAccentComponent>(entity);
RemComp<SpeakOnExceptionComponent>(entity);
}
// Stops from adding a ghost role to things like people who already have a mind

View file

@ -0,0 +1,41 @@
using Content.Shared.Dataset;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Server.RuntimeFun;
/// <summary>
/// Entities with this component will speak from a dataset everytime an error occurs in an interval.
/// </summary>
[RegisterComponent, AutoGenerateComponentPause]
public sealed partial class SpeakOnExceptionComponent : Component
{
/// <summary>
/// Minimum time between error speech events.
/// </summary>
[DataField]
public TimeSpan SpeechCooldown = TimeSpan.FromMinutes(1);
/// <summary>
/// The chance to speak without an accent.
/// </summary>
[DataField]
public float ChanceSpeakNoAccent = 0.005f;
/// <summary>
/// Localized dataset used when speaking.
/// </summary>
[DataField]
public ProtoId<LocalizedDatasetPrototype> Dataset = "ExceptionSpeechDataset";
/// <summary>
/// The next time the entity can say another error.
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
public TimeSpan? NextTimeCanSpeak;
/// <summary>
/// If this component is currently trying to block accents from working.
/// </summary>
public bool BlockAccent;
}

View file

@ -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;
/// <summary>
/// System for the <see cref="SpeakOnExceptionComponent"/>. Deals with getting the latest error log and making
/// entities with that component speak.
/// </summary>
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<SpeakOnExceptionComponent, TransformSpeechEvent>(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<SpeakOnExceptionComponent>();
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<SpeakOnExceptionComponent> ent, ref MapInitEvent args)
{
ent.Comp.NextTimeCanSpeak = _timing.CurTime;
}
private void OnTransformSpeech(Entity<SpeakOnExceptionComponent> 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;
}
}
}

View file

@ -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()

View file

@ -0,0 +1,5 @@
- type: localizedDataset
id: ExceptionSpeechDataset
values:
prefix: exception-censored-
count: 14

View file

@ -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