Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Let ghosts sometimes make certain devices say creepy things #34368

Merged
merged 9 commits into from
Jan 11, 2025
37 changes: 37 additions & 0 deletions Content.Server/Ghost/Components/SpookySpeakerComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using Content.Shared.Dataset;
using Robust.Shared.Prototypes;

namespace Content.Server.Ghost.Components;

/// <summary>
/// Causes this entity to react to ghost player using the "Boo!" action by speaking
/// a randomly chosen message from a specified set.
/// </summary>
[RegisterComponent, AutoGenerateComponentPause]
public sealed partial class SpookySpeakerComponent : Component
{
/// <summary>
/// ProtoId of the LocalizedDataset to use for messages.
/// </summary>
[DataField(required: true)]
public ProtoId<LocalizedDatasetPrototype> MessageSet;

/// <summary>
/// Probability that this entity will speak if activated by a Boo action.
/// This is so whole banks of entities don't trigger at the same time.
/// </summary>
[DataField]
public float SpeakChance = 0.5f;

/// <summary>
/// Minimum time that must pass after speaking before this entity can speak again.
/// </summary>
[DataField]
public TimeSpan Cooldown = TimeSpan.FromSeconds(30);

/// <summary>
/// Time when the cooldown will have elapsed and the entity can speak again.
/// </summary>
[DataField, AutoPausedField]
public TimeSpan NextSpeakTime;
}
6 changes: 5 additions & 1 deletion Content.Server/Ghost/GhostSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
using Robust.Shared.Physics.Systems;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;

namespace Content.Server.Ghost
Expand Down Expand Up @@ -63,6 +64,7 @@ public sealed class GhostSystem : SharedGhostSystem
[Dependency] private readonly GameTicker _gameTicker = default!;
[Dependency] private readonly DamageableSystem _damageable = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly IRobustRandom _random = default!;

private EntityQuery<GhostComponent> _ghostQuery;
private EntityQuery<PhysicsComponent> _physicsQuery;
Expand Down Expand Up @@ -127,7 +129,9 @@ private void OnActionPerform(EntityUid uid, GhostComponent component, BooActionE
if (args.Handled)
return;

var entities = _lookup.GetEntitiesInRange(args.Performer, component.BooRadius);
var entities = _lookup.GetEntitiesInRange(args.Performer, component.BooRadius).ToList();
// Shuffle the possible targets so we don't favor any particular entities
_random.Shuffle(entities);

var booCounter = 0;
foreach (var ent in entities)
Expand Down
51 changes: 51 additions & 0 deletions Content.Server/Ghost/SpookySpeakerSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using Content.Server.Chat.Systems;
using Content.Server.Ghost.Components;
using Content.Shared.Random.Helpers;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;

namespace Content.Server.Ghost;

public sealed class SpookySpeakerSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly ChatSystem _chat = default!;

public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<SpookySpeakerComponent, GhostBooEvent>(OnGhostBoo);
}

private void OnGhostBoo(Entity<SpookySpeakerComponent> entity, ref GhostBooEvent args)
{
// Only activate sometimes, so groups don't all trigger together
if (!_random.Prob(entity.Comp.SpeakChance))
return;

var curTime = _timing.CurTime;
// Enforce a delay between messages to prevent spam
if (curTime < entity.Comp.NextSpeakTime)
return;

if (!_proto.TryIndex(entity.Comp.MessageSet, out var messages))
return;

// Grab a random localized message from the set
var message = _random.Pick(messages);
// Chatcode moment: messages starting with '.' are considered radio messages unless prefixed with '>'
// So this is a stupid trick to make the "...Oooo"-style messages work.
message = '>' + message;
// Say the message
_chat.TrySendInGameICMessage(entity, message, InGameICChatType.Speak, hideChat: true);

// Set the delay for the next message
entity.Comp.NextSpeakTime = curTime + entity.Comp.Cooldown;

args.Handled = true;
}
}
18 changes: 18 additions & 0 deletions Resources/Locale/en-US/ghost/spooky-speaker.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
spooky-speaker-generic-1 = ...ooOoooOOoooo...
spooky-speaker-generic-2 = ...can anyone hear me...?
spooky-speaker-generic-3 = ...join us...
spooky-speaker-generic-4 = ...come play with us...
spooky-speaker-generic-5 = KkkhhkhKhhkhkKk
spooky-speaker-generic-6 = Khhggkkghkk
spooky-speaker-generic-7 = khhkkkkKkhkkHk
spooky-speaker-generic-8 = ...
spooky-speaker-generic-9 = ...h-h-hello...?
spooky-speaker-generic-10 = Bzzzt
spooky-speaker-generic-11 = Weh
spooky-speaker-generic-12 = TREMBLE, MORTALS!
spooky-speaker-generic-13 = 4444444444
spooky-speaker-generic-14 = ...I found you...
spooky-speaker-recycler-1 = I HUNGER
spooky-speaker-recycler-2 = MORE! GIVE ME MORE!
spooky-speaker-recycler-3 = FEED ME
11 changes: 11 additions & 0 deletions Resources/Prototypes/Datasets/spooky_speakers.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
- type: localizedDataset
id: SpookySpeakerMessagesGeneric
values:
prefix: spooky-speaker-generic-
count: 14

- type: localizedDataset
id: SpookySpeakerMessagesRecycler
values:
prefix: spooky-speaker-recycler-
count: 3
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@
- type: Speech
speechVerb: Robotic
speechSounds: Vending
- type: SpookySpeaker
messageSet: SpookySpeakerMessagesGeneric
speakChance: 0.2
- type: Anchorable
- type: Pullable
- type: StaticPrice
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,10 @@
interactFailureString: petting-failure-generic
interactSuccessSound:
path: /Audio/Items/drill_hit.ogg
- type: SpookySpeaker
speakChance: 0.1
cooldown: 120
messageSet: SpookySpeakerMessagesRecycler
- type: Speech
speechVerb: Robotic
speechSounds: SyndieBorg
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@
- type: Speech
speechVerb: Robotic
speechSounds: Vending
- type: SpookySpeaker
messageSet: SpookySpeakerMessagesGeneric
speakChance: 0.2
- type: IntrinsicRadioReceiver
- type: ActiveRadio
channels:
Expand Down
Loading