Skip to content

Commit eb77deb

Browse files
TayrtahnMilonPL
authored andcommitted
Let ghosts sometimes make certain devices say creepy things (#34368)
* Add SpookySpeaker component/system * Shuffle Boo action targets before trying to activate them * Add SpookySpeaker to vending machines * Fix chatcode eating messages starting with "..." * Add SpookySpeaker to recycler * Oops * Decrease speak probability for vending machines * Add spooky speaker to arcade machines (cherry picked from commit c6e4d198833ca5c69821a1d4b8108c2a2d94f1d0)
1 parent 6208d6c commit eb77deb

File tree

8 files changed

+135
-1
lines changed

8 files changed

+135
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using Content.Shared.Dataset;
2+
using Robust.Shared.Prototypes;
3+
4+
namespace Content.Server.Ghost.Components;
5+
6+
/// <summary>
7+
/// Causes this entity to react to ghost player using the "Boo!" action by speaking
8+
/// a randomly chosen message from a specified set.
9+
/// </summary>
10+
[RegisterComponent, AutoGenerateComponentPause]
11+
public sealed partial class SpookySpeakerComponent : Component
12+
{
13+
/// <summary>
14+
/// ProtoId of the LocalizedDataset to use for messages.
15+
/// </summary>
16+
[DataField(required: true)]
17+
public ProtoId<LocalizedDatasetPrototype> MessageSet;
18+
19+
/// <summary>
20+
/// Probability that this entity will speak if activated by a Boo action.
21+
/// This is so whole banks of entities don't trigger at the same time.
22+
/// </summary>
23+
[DataField]
24+
public float SpeakChance = 0.5f;
25+
26+
/// <summary>
27+
/// Minimum time that must pass after speaking before this entity can speak again.
28+
/// </summary>
29+
[DataField]
30+
public TimeSpan Cooldown = TimeSpan.FromSeconds(30);
31+
32+
/// <summary>
33+
/// Time when the cooldown will have elapsed and the entity can speak again.
34+
/// </summary>
35+
[DataField, AutoPausedField]
36+
public TimeSpan NextSpeakTime;
37+
}

Content.Server/Ghost/GhostSystem.cs

+5-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
using Robust.Shared.Physics.Systems;
3535
using Robust.Shared.Player;
3636
using Robust.Shared.Prototypes;
37+
using Robust.Shared.Random;
3738
using Robust.Shared.Timing;
3839

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

6769
private EntityQuery<GhostComponent> _ghostQuery;
6870
private EntityQuery<PhysicsComponent> _physicsQuery;
@@ -127,7 +129,9 @@ private void OnActionPerform(EntityUid uid, GhostComponent component, BooActionE
127129
if (args.Handled)
128130
return;
129131

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

132136
var booCounter = 0;
133137
foreach (var ent in entities)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using Content.Server.Chat.Systems;
2+
using Content.Server.Ghost.Components;
3+
using Content.Shared.Random.Helpers;
4+
using Robust.Shared.Prototypes;
5+
using Robust.Shared.Random;
6+
using Robust.Shared.Timing;
7+
8+
namespace Content.Server.Ghost;
9+
10+
public sealed class SpookySpeakerSystem : EntitySystem
11+
{
12+
[Dependency] private readonly IPrototypeManager _proto = default!;
13+
[Dependency] private readonly IRobustRandom _random = default!;
14+
[Dependency] private readonly IGameTiming _timing = default!;
15+
[Dependency] private readonly ChatSystem _chat = default!;
16+
17+
public override void Initialize()
18+
{
19+
base.Initialize();
20+
21+
SubscribeLocalEvent<SpookySpeakerComponent, GhostBooEvent>(OnGhostBoo);
22+
}
23+
24+
private void OnGhostBoo(Entity<SpookySpeakerComponent> entity, ref GhostBooEvent args)
25+
{
26+
// Only activate sometimes, so groups don't all trigger together
27+
if (!_random.Prob(entity.Comp.SpeakChance))
28+
return;
29+
30+
var curTime = _timing.CurTime;
31+
// Enforce a delay between messages to prevent spam
32+
if (curTime < entity.Comp.NextSpeakTime)
33+
return;
34+
35+
if (!_proto.TryIndex(entity.Comp.MessageSet, out var messages))
36+
return;
37+
38+
// Grab a random localized message from the set
39+
var message = _random.Pick(messages);
40+
// Chatcode moment: messages starting with '.' are considered radio messages unless prefixed with '>'
41+
// So this is a stupid trick to make the "...Oooo"-style messages work.
42+
message = '>' + message;
43+
// Say the message
44+
_chat.TrySendInGameICMessage(entity, message, InGameICChatType.Speak, hideChat: true);
45+
46+
// Set the delay for the next message
47+
entity.Comp.NextSpeakTime = curTime + entity.Comp.Cooldown;
48+
49+
args.Handled = true;
50+
}
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
spooky-speaker-generic-1 = ...ooOoooOOoooo...
2+
spooky-speaker-generic-2 = ...can anyone hear me...?
3+
spooky-speaker-generic-3 = ...join us...
4+
spooky-speaker-generic-4 = ...come play with us...
5+
spooky-speaker-generic-5 = KkkhhkhKhhkhkKk
6+
spooky-speaker-generic-6 = Khhggkkghkk
7+
spooky-speaker-generic-7 = khhkkkkKkhkkHk
8+
spooky-speaker-generic-8 = ...
9+
spooky-speaker-generic-9 = ...h-h-hello...?
10+
spooky-speaker-generic-10 = Bzzzt
11+
spooky-speaker-generic-11 = Weh
12+
spooky-speaker-generic-12 = TREMBLE, MORTALS!
13+
spooky-speaker-generic-13 = 4444444444
14+
spooky-speaker-generic-14 = ...I found you...
15+
16+
spooky-speaker-recycler-1 = I HUNGER
17+
spooky-speaker-recycler-2 = MORE! GIVE ME MORE!
18+
spooky-speaker-recycler-3 = FEED ME
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
- type: localizedDataset
2+
id: SpookySpeakerMessagesGeneric
3+
values:
4+
prefix: spooky-speaker-generic-
5+
count: 14
6+
7+
- type: localizedDataset
8+
id: SpookySpeakerMessagesRecycler
9+
values:
10+
prefix: spooky-speaker-recycler-
11+
count: 3

Resources/Prototypes/Entities/Structures/Machines/Computers/arcades.yml

+3
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@
3636
- type: Speech
3737
speechVerb: Robotic
3838
speechSounds: Vending
39+
- type: SpookySpeaker
40+
messageSet: SpookySpeakerMessagesGeneric
41+
speakChance: 0.2
3942
- type: Anchorable
4043
- type: Pullable
4144
- type: StaticPrice

Resources/Prototypes/Entities/Structures/Machines/recycler.yml

+7
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,10 @@
108108
interactFailureString: petting-failure-generic
109109
interactSuccessSound:
110110
path: /Audio/Items/drill_hit.ogg
111+
- type: SpookySpeaker
112+
speakChance: 0.1
113+
cooldown: 120
114+
messageSet: SpookySpeakerMessagesRecycler
115+
- type: Speech
116+
speechVerb: Robotic
117+
speechSounds: SyndieBorg

Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml

+3
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@
7878
- type: Speech
7979
speechVerb: Robotic
8080
speechSounds: Vending
81+
- type: SpookySpeaker
82+
messageSet: SpookySpeakerMessagesGeneric
83+
speakChance: 0.2
8184
- type: IntrinsicRadioReceiver
8285
- type: ActiveRadio
8386
channels:

0 commit comments

Comments
 (0)