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
This commit is contained in:
37
Content.Server/Ghost/Components/SpookySpeakerComponent.cs
Normal file
37
Content.Server/Ghost/Components/SpookySpeakerComponent.cs
Normal file
@@ -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;
|
||||
}
|
||||
@@ -34,6 +34,7 @@ using Robust.Shared.Physics.Components;
|
||||
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
|
||||
@@ -63,6 +64,7 @@ namespace Content.Server.Ghost
|
||||
[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;
|
||||
@@ -127,7 +129,9 @@ namespace Content.Server.Ghost
|
||||
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)
|
||||
|
||||
51
Content.Server/Ghost/SpookySpeakerSystem.cs
Normal file
51
Content.Server/Ghost/SpookySpeakerSystem.cs
Normal file
@@ -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
Resources/Locale/en-US/ghost/spooky-speaker.ftl
Normal file
18
Resources/Locale/en-US/ghost/spooky-speaker.ftl
Normal file
@@ -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
Resources/Prototypes/Datasets/spooky_speakers.yml
Normal file
11
Resources/Prototypes/Datasets/spooky_speakers.yml
Normal file
@@ -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
|
||||
@@ -36,6 +36,9 @@
|
||||
- type: Speech
|
||||
speechVerb: Robotic
|
||||
speechSounds: Vending
|
||||
- type: SpookySpeaker
|
||||
messageSet: SpookySpeakerMessagesGeneric
|
||||
speakChance: 0.2
|
||||
- type: Anchorable
|
||||
- type: Pullable
|
||||
- type: StaticPrice
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -78,6 +78,9 @@
|
||||
- type: Speech
|
||||
speechVerb: Robotic
|
||||
speechSounds: Vending
|
||||
- type: SpookySpeaker
|
||||
messageSet: SpookySpeakerMessagesGeneric
|
||||
speakChance: 0.2
|
||||
- type: IntrinsicRadioReceiver
|
||||
- type: ActiveRadio
|
||||
channels:
|
||||
|
||||
Reference in New Issue
Block a user