Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Content.Server/Chat/Systems/ChatSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -731,7 +731,7 @@ private string SanitizeInGameOOCMessage(string message)
public string TransformSpeech(EntityUid sender, string message)
{
var ev = new TransformSpeechEvent(sender, message);
RaiseLocalEvent(ev);
RaiseLocalEvent(sender, ev, true);

return ev.Message;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Content.Server.Ghost.Roles.Components;
using Content.Server.RuntimeFun;
using Content.Server.Speech.Components;
using Content.Shared.EntityEffects;
using Content.Shared.EntityEffects.Effects;
Expand All @@ -23,6 +24,7 @@ protected override void Effect(Entity<MetaDataComponent> entity, ref EntityEffec
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
Expand Down
41 changes: 41 additions & 0 deletions Content.Server/RuntimeFun/SpeakOnExceptionComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using Content.Shared.Dataset;
using Robust.Shared.Prototypes;

namespace Content.Server.RuntimeFun;

/// <summary>
/// Entities with this component will speak everytime an error occurs. They will say the exception
/// </summary>
[RegisterComponent]
public sealed partial class SpeakOnExceptionComponent : Component
{
/// <summary>
/// The last log that was spoken, used to ensure you don't repeat logs
/// </summary>
[DataField]
public string? LastLog;

/// <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]
public TimeSpan? NextTimeCanSpeak;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to use TimeOffsetSerializer.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also added the auto pause fields which I think are also needed - let me know if they aren't (The documentation kina makes them sound like they should be added?)

}
93 changes: 93 additions & 0 deletions Content.Server/RuntimeFun/SpeakOnExceptionSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
using Content.Server.Chat.Systems;
using Content.Server.Speech;
using Content.Shared.Chat;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using Serilog.Events;

namespace Content.Server.RuntimeFun;

public sealed class SpeakOnExceptionSystem : EntitySystem
{
[Dependency] private readonly ILogManager _log = default!;
[Dependency] private readonly ChatSystem _chat = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IPrototypeManager _proto = 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, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<SpeakOnExceptionComponent, TransformSpeechEvent>(OnTransformSpeech, before: [ typeof(AccentSystem) ]);
}

private void OnMapInit(Entity<SpeakOnExceptionComponent> ent, ref MapInitEvent args)
{
ent.Comp.NextTimeCanSpeak = _timing.CurTime;

// Make sure you don't speak when spawned if an error already occured
ent.Comp.LastLog = _logHandler.LastLog;
}

public override void Update(float frameTime)
{
var log = _logHandler.LastLog;
if (log == null)
return;

var query = EntityQueryEnumerator<SpeakOnExceptionComponent>();

while (query.MoveNext(out var uid, out var comp))
{
if (_timing.CurTime >= comp.NextTimeCanSpeak && log != comp.LastLog)
{
_chat.TrySendInGameICMessage(uid, CensorMessage(comp), InGameICChatType.Speak, ChatTransmitRange.Normal, true);

comp.NextTimeCanSpeak += comp.SpeechCooldown;
}

// If the log changes when you're in cooldown, you still want to update the log so it won't trigger immediately
comp.LastLog = log;
}
}

private void OnTransformSpeech(Entity<SpeakOnExceptionComponent> ent, ref TransformSpeechEvent args)
{
if (_random.Prob(ent.Comp.ChanceSpeakNoAccent))
args.Cancel();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will apply to all speech, not just error messages, no?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep I think it will! Changed it due to #39734 (comment) - but apparently I did that for a reason but I got myself confused and got rid of it 😆

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not mentioned in the PR description and I do not think this is a good feature to have.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It now works proplerly - I personally think its fun.


private string CensorMessage(SpeakOnExceptionComponent comp)
{
return Loc.GetString(_random.Pick(_proto.Index(comp.Dataset).Values));
}

public override void Shutdown()
{
_log.RootSawmill.RemoveHandler(_logHandler);
}
}

// Log handler for SpeakOnException entities.
public sealed class SpeakOnExceptionLogHandler : ILogHandler
{
// Last error log that occured
public string? LastLog;

public void Log(string sawmillName, LogEvent message)
{
if (message.Exception == null)
return;

LastLog = message.Exception.Message;
}
}
3 changes: 3 additions & 0 deletions Content.Server/Speech/AccentSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ public override void Initialize()

private void AccentHandler(TransformSpeechEvent args)
{
if (args.Cancelled)
return;

var accentEvent = new AccentGetEvent(args.Sender, args.Message);

RaiseLocalEvent(args.Sender, accentEvent, true);
Expand Down
2 changes: 1 addition & 1 deletion Content.Shared/Chat/SharedChatEvents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public TransformSpeakerNameEvent(EntityUid sender, string name)
/// <summary>
/// Raised broadcast in order to transform speech.transmit
/// </summary>
public sealed class TransformSpeechEvent : EntityEventArgs
public sealed class TransformSpeechEvent : CancellableEntityEventArgs
{
public EntityUid Sender;
public string Message;
Expand Down
11 changes: 11 additions & 0 deletions Resources/Locale/en-US/speak-on-exception/speak-on-exception.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
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: :3
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
5 changes: 5 additions & 0 deletions Resources/Prototypes/Entities/Mobs/NPCs/animal_speech.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
- type: localizedDataset
id: ExceptionSpeechDataset
values:
prefix: exception-censored-
count: 11
4 changes: 4 additions & 0 deletions Resources/Prototypes/Entities/Mobs/NPCs/pets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@
attributes:
proper: true
gender: female
- type: SpeakOnException

- type: entity
name: Puppy Ian
Expand Down Expand Up @@ -186,6 +187,9 @@
- type: NpcFactionMember
factions:
- PetsNT
- type: SpeakOnException
speechCooldown: 2m
chanceSpeakNoAccent: 0.007
- type: Grammar
attributes:
proper: true
Expand Down
Loading