diff --git a/Content.Server/Chat/Systems/ChatSystem.Emote.cs b/Content.Server/Chat/Systems/ChatSystem.Emote.cs new file mode 100644 index 0000000000..a31cde8fa1 --- /dev/null +++ b/Content.Server/Chat/Systems/ChatSystem.Emote.cs @@ -0,0 +1,173 @@ +using Content.Shared.Chat.Prototypes; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; + +namespace Content.Server.Chat.Systems; + +// emotes using emote prototype +public partial class ChatSystem +{ + private readonly Dictionary _wordEmoteDict = new(); + + private void InitializeEmotes() + { + _prototypeManager.PrototypesReloaded += OnPrototypeReloadEmotes; + CacheEmotes(); + } + + private void ShutdownEmotes() + { + _prototypeManager.PrototypesReloaded -= OnPrototypeReloadEmotes; + } + + private void OnPrototypeReloadEmotes(PrototypesReloadedEventArgs obj) + { + CacheEmotes(); + } + + private void CacheEmotes() + { + _wordEmoteDict.Clear(); + var emotes = _prototypeManager.EnumeratePrototypes(); + foreach (var emote in emotes) + { + foreach (var word in emote.ChatTriggers) + { + var lowerWord = word.ToLower(); + if (_wordEmoteDict.ContainsKey(lowerWord)) + { + var existingId = _wordEmoteDict[lowerWord].ID; + var errMsg = $"Duplicate of emote word {lowerWord} in emotes {emote.ID} and {existingId}"; + Logger.Error(errMsg); + continue; + } + + _wordEmoteDict.Add(lowerWord, emote); + } + } + } + + /// + /// Makes selected entity to emote using and sends message to chat. + /// + /// The entity that is speaking + /// The id of emote prototype. Should has valid + /// Whether or not this message should appear in the chat window + /// Whether or not this message should appear in the chat window for out-of-range ghosts (which otherwise ignore range restrictions) + /// The name to use for the speaking entity. Usually this should just be modified via . If this is set, the event will not get raised. + public void TryEmoteWithChat(EntityUid source, string emoteId, bool hideChat = false, + bool hideGlobalGhostChat = false, string? nameOverride = null) + { + if (!_prototypeManager.TryIndex(emoteId, out var proto)) + return; + TryEmoteWithChat(source, proto, hideChat, hideGlobalGhostChat, nameOverride); + } + + /// + /// Makes selected entity to emote using and sends message to chat. + /// + /// The entity that is speaking + /// The emote prototype. Should has valid + /// Whether or not this message should appear in the chat window + /// Whether or not this message should appear in the chat window for out-of-range ghosts (which otherwise ignore range restrictions) + /// The name to use for the speaking entity. Usually this should just be modified via . If this is set, the event will not get raised. + public void TryEmoteWithChat(EntityUid source, EmotePrototype emote, bool hideChat = false, + bool hideGlobalGhostChat = false, string? nameOverride = null) + { + // check if proto has valid message for chat + if (emote.ChatMessages.Count != 0) + { + var action = _random.Pick(emote.ChatMessages); + SendEntityEmote(source, action, hideChat, hideGlobalGhostChat, nameOverride, false); + } + + // do the rest of emote event logic here + TryEmoteWithoutChat(source, emote); + } + + /// + /// Makes selected entity to emote using without sending any messages to chat. + /// + public void TryEmoteWithoutChat(EntityUid uid, string emoteId) + { + if (!_prototypeManager.TryIndex(emoteId, out var proto)) + return; + TryEmoteWithoutChat(uid, proto); + } + + /// + /// Makes selected entity to emote using without sending any messages to chat. + /// + public void TryEmoteWithoutChat(EntityUid uid, EmotePrototype proto) + { + if (!_actionBlocker.CanEmote(uid)) + return; + + InvokeEmoteEvent(uid, proto); + } + + /// + /// Tries to find and play relevant emote sound in emote sounds collection. + /// + /// True if emote sound was played. + public bool TryPlayEmoteSound(EntityUid uid, EmoteSoundsPrototype? proto, EmotePrototype emote) + { + return TryPlayEmoteSound(uid, proto, emote.ID); + } + + /// + /// Tries to find and play relevant emote sound in emote sounds collection. + /// + /// True if emote sound was played. + public bool TryPlayEmoteSound(EntityUid uid, EmoteSoundsPrototype? proto, string emoteId) + { + if (proto == null) + return false; + + // try to get specific sound for this emote + if (!proto.Sounds.TryGetValue(emoteId, out var sound)) + { + // no specific sound - check fallback + sound = proto.FallbackSound; + if (sound == null) + return false; + } + + // if general params for all sounds set - use them + var param = proto.GeneralParams ?? sound.Params; + _audio.PlayPvs(sound, uid, param); + return true; + } + + private void TryEmoteChatInput(EntityUid uid, string textInput) + { + var actionLower = textInput.ToLower(); + if (!_wordEmoteDict.TryGetValue(actionLower, out var emote)) + return; + + InvokeEmoteEvent(uid, emote); + } + + private void InvokeEmoteEvent(EntityUid uid, EmotePrototype proto) + { + var ev = new EmoteEvent(proto); + RaiseLocalEvent(uid, ref ev); + } +} + +/// +/// Raised by chat system when entity made some emote. +/// Use it to play sound, change sprite or something else. +/// +[ByRefEvent] +public struct EmoteEvent +{ + public bool Handled; + public readonly EmotePrototype Emote; + + public EmoteEvent(EmotePrototype emote) + { + Emote = emote; + Handled = false; + } +} diff --git a/Content.Server/Chat/Systems/ChatSystem.cs b/Content.Server/Chat/Systems/ChatSystem.cs index 7c9d2124cd..db35ca2009 100644 --- a/Content.Server/Chat/Systems/ChatSystem.cs +++ b/Content.Server/Chat/Systems/ChatSystem.cs @@ -52,6 +52,7 @@ public sealed partial class ChatSystem : SharedChatSystem [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly StationSystem _stationSystem = default!; [Dependency] private readonly MobStateSystem _mobStateSystem = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; public const int VoiceRange = 10; // how far voice goes in world units public const int WhisperRange = 2; // how far whisper goes in world units @@ -63,7 +64,9 @@ public sealed partial class ChatSystem : SharedChatSystem public override void Initialize() { + base.Initialize(); InitializeRadio(); + InitializeEmotes(); _configurationManager.OnValueChanged(CCVars.LoocEnabled, OnLoocEnabledChanged, true); _configurationManager.OnValueChanged(CCVars.DeadLoocEnabled, OnDeadLoocEnabledChanged, true); @@ -72,7 +75,9 @@ public sealed partial class ChatSystem : SharedChatSystem public override void Shutdown() { + base.Shutdown(); ShutdownRadio(); + ShutdownEmotes(); _configurationManager.UnsubValueChanged(CCVars.LoocEnabled, OnLoocEnabledChanged); } @@ -366,7 +371,8 @@ public sealed partial class ChatSystem : SharedChatSystem _adminLogger.Add(LogType.Chat, LogImpact.Low, $"Whisper from {ToPrettyString(source):user}, original: {originalMessage}, transformed: {message}."); } - private void SendEntityEmote(EntityUid source, string action, bool hideChat, bool hideGlobalGhostChat, string? nameOverride) + private void SendEntityEmote(EntityUid source, string action, bool hideChat, + bool hideGlobalGhostChat, string? nameOverride, bool checkEmote = true) { if (!_actionBlocker.CanEmote(source)) return; @@ -378,6 +384,8 @@ public sealed partial class ChatSystem : SharedChatSystem ("entityName", name), ("message", FormattedMessage.EscapeText(action))); + if (checkEmote) + TryEmoteChatInput(source, action); SendInVoiceRange(ChatChannel.Emotes, action, wrappedMessage, source, hideChat, hideGlobalGhostChat); _adminLogger.Add(LogType.Chat, LogImpact.Low, $"Emote from {ToPrettyString(source):user}: {action}"); } diff --git a/Content.Server/Chemistry/ReagentEffects/Emote.cs b/Content.Server/Chemistry/ReagentEffects/Emote.cs new file mode 100644 index 0000000000..10df99359d --- /dev/null +++ b/Content.Server/Chemistry/ReagentEffects/Emote.cs @@ -0,0 +1,33 @@ +using Content.Server.Chat.Systems; +using Content.Shared.Chat.Prototypes; +using Content.Shared.Chemistry.Reagent; +using JetBrains.Annotations; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Server.Chemistry.ReagentEffects; + +/// +/// Tries to force someone to emote (scream, laugh, etc). +/// +[UsedImplicitly] +public sealed class Emote : ReagentEffect +{ + [DataField("emote", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string? EmoteId; + + [DataField("showInChat")] + public bool ShowInChat; + + public override void Effect(ReagentEffectArgs args) + { + if (EmoteId == null) + return; + + var chatSys = args.EntityManager.System(); + if (ShowInChat) + chatSys.TryEmoteWithChat(args.SolutionEntity, EmoteId, hideGlobalGhostChat: true); + else + chatSys.TryEmoteWithoutChat(args.SolutionEntity, EmoteId); + + } +} diff --git a/Content.Server/Chemistry/ReagentEffects/Scream.cs b/Content.Server/Chemistry/ReagentEffects/Scream.cs deleted file mode 100644 index 27cb88851a..0000000000 --- a/Content.Server/Chemistry/ReagentEffects/Scream.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Content.Server.Speech; -using Content.Shared.Chemistry.Reagent; - -namespace Content.Server.Chemistry.ReagentEffects; - -/// -/// Forces someone to scream their lungs out. -/// -public sealed class Scream : ReagentEffect -{ - public override void Effect(ReagentEffectArgs args) - { - EntitySystem.Get().TryScream(args.SolutionEntity); - } -} diff --git a/Content.Server/Emoting/Components/BodyEmotesComponent.cs b/Content.Server/Emoting/Components/BodyEmotesComponent.cs new file mode 100644 index 0000000000..dba4cb4806 --- /dev/null +++ b/Content.Server/Emoting/Components/BodyEmotesComponent.cs @@ -0,0 +1,24 @@ +using Content.Server.Emoting.Systems; +using Content.Shared.Chat.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Server.Emoting.Components; + +/// +/// Component required for entities to be able to do body emotions (clap, flip, etc). +/// +[RegisterComponent] +[Access(typeof(BodyEmotesSystem))] +public sealed class BodyEmotesComponent : Component +{ + /// + /// Emote sounds prototype id for body emotes. + /// + [DataField("soundsId", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string? SoundsId; + + /// + /// Loaded emote sounds prototype used for body emotes. + /// + public EmoteSoundsPrototype? Sounds; +} diff --git a/Content.Server/Emoting/Systems/BodyEmotesSystem.cs b/Content.Server/Emoting/Systems/BodyEmotesSystem.cs new file mode 100644 index 0000000000..f2c44b94fa --- /dev/null +++ b/Content.Server/Emoting/Systems/BodyEmotesSystem.cs @@ -0,0 +1,48 @@ +using Content.Server.Chat.Systems; +using Content.Server.Emoting.Components; +using Content.Server.Hands.Components; +using Content.Shared.Chat.Prototypes; +using Robust.Shared.Prototypes; + +namespace Content.Server.Emoting.Systems; + +public sealed class BodyEmotesSystem : EntitySystem +{ + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly ChatSystem _chat = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnEmote); + } + + private void OnStartup(EntityUid uid, BodyEmotesComponent component, ComponentStartup args) + { + if (component.SoundsId == null) + return; + _proto.TryIndex(component.SoundsId, out component.Sounds); + } + + private void OnEmote(EntityUid uid, BodyEmotesComponent component, ref EmoteEvent args) + { + if (args.Handled) + return; + + var cat = args.Emote.Category; + if (cat.HasFlag(EmoteCategory.Hands)) + { + args.Handled = TryEmoteHands(uid, args.Emote, component); + } + } + + private bool TryEmoteHands(EntityUid uid, EmotePrototype emote, BodyEmotesComponent component) + { + // check that user actually has hands to do emote sound + if (!TryComp(uid, out HandsComponent? hands) || hands.Count <= 0) + return false; + + return _chat.TryPlayEmoteSound(uid, component.Sounds, emote); + } +} diff --git a/Content.Server/Humanoid/Systems/HumanoidAppearanceSystem.cs b/Content.Server/Humanoid/Systems/HumanoidAppearanceSystem.cs index cf4d20efee..fe5b98e7de 100644 --- a/Content.Server/Humanoid/Systems/HumanoidAppearanceSystem.cs +++ b/Content.Server/Humanoid/Systems/HumanoidAppearanceSystem.cs @@ -72,7 +72,7 @@ public sealed partial class HumanoidAppearanceSystem : SharedHumanoidAppearanceS } SetSpecies(uid, profile.Species, false, humanoid); - humanoid.Sex = profile.Sex; + SetSex(uid, profile.Sex, false, humanoid); humanoid.EyeColor = profile.Appearance.EyeColor; SetSkinColor(uid, profile.Appearance.SkinColor, false); @@ -121,7 +121,7 @@ public sealed partial class HumanoidAppearanceSystem : SharedHumanoidAppearanceS targetHumanoid.Species = sourceHumanoid.Species; targetHumanoid.SkinColor = sourceHumanoid.SkinColor; - targetHumanoid.Sex = sourceHumanoid.Sex; + SetSex(target, sourceHumanoid.Sex, false, targetHumanoid); targetHumanoid.CustomBaseLayers = new(sourceHumanoid.CustomBaseLayers); targetHumanoid.MarkingSet = new(sourceHumanoid.MarkingSet); diff --git a/Content.Server/Speech/Components/VocalComponent.cs b/Content.Server/Speech/Components/VocalComponent.cs index d30b29f055..c1b70d9b3e 100644 --- a/Content.Server/Speech/Components/VocalComponent.cs +++ b/Content.Server/Speech/Components/VocalComponent.cs @@ -1,41 +1,53 @@ +using Content.Server.Humanoid; +using Content.Server.Speech.EntitySystems; using Content.Shared.Actions; using Content.Shared.Actions.ActionTypes; +using Content.Shared.Chat.Prototypes; +using Content.Shared.Humanoid; using Robust.Shared.Audio; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary; namespace Content.Server.Speech.Components; /// -/// Component required for entities to be able to scream. +/// Component required for entities to be able to do vocal emotions. /// [RegisterComponent] +[Access(typeof(VocalSystem))] public sealed class VocalComponent : Component { - [DataField("maleScream")] - public SoundSpecifier MaleScream = new SoundCollectionSpecifier("MaleScreams"); + /// + /// Emote sounds prototype id for each sex (not gender). + /// Entities without considered to be . + /// + [DataField("sounds", customTypeSerializer: typeof(PrototypeIdValueDictionarySerializer))] + public Dictionary? Sounds; - [DataField("femaleScream")] - public SoundSpecifier FemaleScream = new SoundCollectionSpecifier("FemaleScreams"); - - [DataField("unsexedScream")] - public SoundSpecifier UnsexedScream = new SoundCollectionSpecifier("MaleScreams"); + [DataField("screamId", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string ScreamId = "Scream"; [DataField("wilhelm")] public SoundSpecifier Wilhelm = new SoundPathSpecifier("/Audio/Voice/Human/wilhelm_scream.ogg"); - [DataField("audioParams")] - public AudioParams AudioParams = AudioParams.Default.WithVolume(4f); - [DataField("wilhelmProbability")] public float WilhelmProbability = 0.01f; - public const float Variation = 0.125f; + [DataField("screamActionId", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string ScreamActionId = "Scream"; - [DataField("actionId", customTypeSerializer:typeof(PrototypeIdSerializer))] - public string ActionId = "Scream"; + [DataField("screamAction")] + public InstantAction? ScreamAction; - [DataField("action")] // must be a data-field to properly save cooldown when saving game state. - public InstantAction? ScreamAction = null; + /// + /// Currently loaded emote sounds prototype, based on entity sex. + /// Null if no valid prototype for entity sex was found. + /// + [ViewVariables] + public EmoteSoundsPrototype? EmoteSounds = null; } -public sealed class ScreamActionEvent : InstantActionEvent { }; +public sealed class ScreamActionEvent : InstantActionEvent +{ + +} diff --git a/Content.Server/Speech/EntitySystems/VocalSystem.cs b/Content.Server/Speech/EntitySystems/VocalSystem.cs new file mode 100644 index 0000000000..244b5124c2 --- /dev/null +++ b/Content.Server/Speech/EntitySystems/VocalSystem.cs @@ -0,0 +1,105 @@ +using Content.Server.Actions; +using Content.Server.Chat.Systems; +using Content.Server.Humanoid; +using Content.Server.Speech.Components; +using Content.Shared.Actions.ActionTypes; +using Content.Shared.Chat.Prototypes; +using Content.Shared.Humanoid; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; + +namespace Content.Server.Speech.EntitySystems; + +public sealed class VocalSystem : EntitySystem +{ + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly ChatSystem _chat = default!; + [Dependency] private readonly ActionsSystem _actions = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnSexChanged); + SubscribeLocalEvent(OnEmote); + SubscribeLocalEvent(OnScreamAction); + } + + private void OnMapInit(EntityUid uid, VocalComponent component, MapInitEvent args) + { + // try to add scream action when vocal comp added + if (_proto.TryIndex(component.ScreamActionId, out InstantActionPrototype? proto)) + { + component.ScreamAction = new InstantAction(proto); + _actions.AddAction(uid, component.ScreamAction, null); + } + + LoadSounds(uid, component); + } + + private void OnShutdown(EntityUid uid, VocalComponent component, ComponentShutdown args) + { + // remove scream action when component removed + if (component.ScreamAction != null) + { + _actions.RemoveAction(uid, component.ScreamAction); + } + } + + private void OnSexChanged(EntityUid uid, VocalComponent component, SexChangedEvent args) + { + LoadSounds(uid, component); + } + + private void OnEmote(EntityUid uid, VocalComponent component, ref EmoteEvent args) + { + if (args.Handled || !args.Emote.Category.HasFlag(EmoteCategory.Vocal)) + return; + + // snowflake case for wilhelm scream easter egg + if (args.Emote.ID == component.ScreamId) + { + args.Handled = TryPlayScreamSound(uid, component); + return; + } + + // just play regular sound based on emote proto + args.Handled = _chat.TryPlayEmoteSound(uid, component.EmoteSounds, args.Emote); + } + + private void OnScreamAction(EntityUid uid, VocalComponent component, ScreamActionEvent args) + { + if (args.Handled) + return; + + _chat.TryEmoteWithChat(uid, component.ScreamActionId); + args.Handled = true; + } + + private bool TryPlayScreamSound(EntityUid uid, VocalComponent component) + { + if (_random.Prob(component.WilhelmProbability)) + { + _audio.PlayPvs(component.Wilhelm, uid, component.Wilhelm.Params); + return true; + } + + return _chat.TryPlayEmoteSound(uid, component.EmoteSounds, component.ScreamId); + } + + private void LoadSounds(EntityUid uid, VocalComponent component, Sex? sex = null) + { + if (component.Sounds == null) + return; + + sex ??= CompOrNull(uid)?.Sex ?? Sex.Unsexed; + + if (!component.Sounds.TryGetValue(sex.Value, out var protoId)) + return; + _proto.TryIndex(protoId, out component.EmoteSounds); + } +} diff --git a/Content.Server/Speech/VocalSystem.cs b/Content.Server/Speech/VocalSystem.cs deleted file mode 100644 index 718bad6702..0000000000 --- a/Content.Server/Speech/VocalSystem.cs +++ /dev/null @@ -1,101 +0,0 @@ -using Content.Server.Humanoid; -using Content.Server.Popups; -using Content.Server.Speech.Components; -using Content.Shared.ActionBlocker; -using Content.Shared.Actions; -using Content.Shared.Actions.ActionTypes; -using Content.Shared.Humanoid; -using Content.Shared.Popups; -using Robust.Shared.Audio; -using Robust.Shared.Player; -using Robust.Shared.Prototypes; -using Robust.Shared.Random; - -namespace Content.Server.Speech; - -/// -/// Fer Screamin -/// -/// -/// Or I guess other vocalizations, like laughing. If fun is ever legalized on the station. -/// -public sealed class VocalSystem : EntitySystem -{ - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly IPrototypeManager _proto = default!; - [Dependency] private readonly SharedActionsSystem _actions = default!; - [Dependency] private readonly ActionBlockerSystem _blocker = default!; - [Dependency] private readonly PopupSystem _popupSystem = default!; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnActionPerform); - SubscribeLocalEvent(OnMapInit); - SubscribeLocalEvent(OnShutdown); - } - - private void OnMapInit(EntityUid uid, VocalComponent component, MapInitEvent args) - { - if (component.ScreamAction == null - && _proto.TryIndex(component.ActionId, out InstantActionPrototype? act)) - { - component.ScreamAction = new(act); - } - - if (component.ScreamAction != null) - _actions.AddAction(uid, component.ScreamAction, null); - } - - private void OnShutdown(EntityUid uid, VocalComponent component, ComponentShutdown args) - { - if (component.ScreamAction != null) - _actions.RemoveAction(uid, component.ScreamAction); - } - - private void OnActionPerform(EntityUid uid, VocalComponent component, ScreamActionEvent args) - { - if (args.Handled) - return; - - args.Handled = TryScream(uid, component); - } - - public bool TryScream(EntityUid uid, VocalComponent? component = null) - { - if (!Resolve(uid, ref component, false)) - return false; - - if (!_blocker.CanSpeak(uid)) - return false; - - var sex = CompOrNull(uid)?.Sex ?? Sex.Unsexed; - - if (_random.Prob(component.WilhelmProbability)) - { - SoundSystem.Play(component.Wilhelm.GetSound(), Filter.Pvs(uid), uid, component.AudioParams); - return true; - } - - var scale = (float) _random.NextGaussian(1, VocalComponent.Variation); - var pitchedParams = component.AudioParams.WithPitchScale(scale); - - switch (sex) - { - case Sex.Male: - SoundSystem.Play(component.MaleScream.GetSound(), Filter.Pvs(uid), uid, pitchedParams); - break; - case Sex.Female: - SoundSystem.Play(component.FemaleScream.GetSound(), Filter.Pvs(uid), uid, pitchedParams); - break; - default: - SoundSystem.Play(component.UnsexedScream.GetSound(), Filter.Pvs(uid), uid, pitchedParams); - break; - } - - _popupSystem.PopupEntity(Loc.GetString("scream-action-popup"), uid, PopupType.Medium); - - return true; - } -} diff --git a/Content.Server/Zombies/ActiveZombieComponent.cs b/Content.Server/Zombies/ActiveZombieComponent.cs index 0d79672bd1..3e18ac397d 100644 --- a/Content.Server/Zombies/ActiveZombieComponent.cs +++ b/Content.Server/Zombies/ActiveZombieComponent.cs @@ -23,6 +23,9 @@ public sealed class ActiveZombieComponent : Component [ViewVariables(VVAccess.ReadWrite)] public float RandomGroanAttempt = 5; + [ViewVariables(VVAccess.ReadWrite)] + public string GroanEmoteId = "Scream"; + [ViewVariables(VVAccess.ReadWrite)] public float LastDamageGroanCooldown = 0f; diff --git a/Content.Server/Zombies/ZombieSystem.cs b/Content.Server/Zombies/ZombieSystem.cs index 863d02a8aa..ebc9f07967 100644 --- a/Content.Server/Zombies/ZombieSystem.cs +++ b/Content.Server/Zombies/ZombieSystem.cs @@ -11,6 +11,9 @@ using Content.Server.Speech; using Content.Shared.Bed.Sleep; using Content.Shared.Chemistry.Components; using Content.Server.Chat.Systems; +using Content.Server.Emoting.Systems; +using Content.Server.Speech.EntitySystems; +using Content.Shared.Movement.Systems; using Content.Shared.Bed.Sleep; using Content.Shared.Damage; using Content.Shared.Disease.Events; @@ -30,7 +33,6 @@ namespace Content.Server.Zombies [Dependency] private readonly BloodstreamSystem _bloodstream = default!; [Dependency] private readonly ZombifyOnDeathSystem _zombify = default!; [Dependency] private readonly ServerInventorySystem _inv = default!; - [Dependency] private readonly VocalSystem _vocal = default!; [Dependency] private readonly ChatSystem _chat = default!; [Dependency] private readonly IPrototypeManager _protoManager = default!; [Dependency] private readonly IRobustRandom _robustRandom = default!; @@ -40,6 +42,10 @@ namespace Content.Server.Zombies { base.Initialize(); + SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnEmote, before: + new []{typeof(VocalSystem), typeof(BodyEmotesSystem)}); + SubscribeLocalEvent(OnMeleeHit); SubscribeLocalEvent(OnMobState); SubscribeLocalEvent(OnZombieCloning); @@ -54,6 +60,21 @@ namespace Content.Server.Zombies args.Cancelled = true; } + private void OnStartup(EntityUid uid, ZombieComponent component, ComponentStartup args) + { + if (component.EmoteSoundsId == null) + return; + _protoManager.TryIndex(component.EmoteSoundsId, out component.EmoteSounds); + } + + private void OnEmote(EntityUid uid, ZombieComponent component, ref EmoteEvent args) + { + // always play zombie emote sounds and ignore others + if (args.Handled) + return; + args.Handled = _chat.TryPlayEmoteSound(uid, component.EmoteSounds, args.Emote); + } + private void OnMobState(EntityUid uid, ZombieComponent component, MobStateChangedEvent args) { if (args.NewMobState == MobState.Alive) @@ -155,7 +176,7 @@ namespace Content.Server.Zombies // [automated maintainer groan] _chat.TrySendInGameICMessage(uid, "[automated zombie groan]", InGameICChatType.Speak, false); else - _vocal.TryScream(uid); + _chat.TryEmoteWithoutChat(uid, component.GroanEmoteId); component.LastDamageGroanCooldown = component.GroanCooldown; } diff --git a/Content.Server/Zombies/ZombifyOnDeathSystem.cs b/Content.Server/Zombies/ZombifyOnDeathSystem.cs index d2a8f6f71b..32272801dd 100644 --- a/Content.Server/Zombies/ZombifyOnDeathSystem.cs +++ b/Content.Server/Zombies/ZombifyOnDeathSystem.cs @@ -111,11 +111,6 @@ namespace Content.Server.Zombies var combat = AddComp(target); combat.IsInCombatMode = true; - var vocal = EnsureComp(target); - var scream = new SoundCollectionSpecifier ("ZombieScreams"); - vocal.FemaleScream = scream; - vocal.MaleScream = scream; - //This is the actual damage of the zombie. We assign the visual appearance //and range here because of stuff we'll find out later var melee = EnsureComp(target); diff --git a/Content.Shared/Chat/Prototypes/EmotePrototype.cs b/Content.Shared/Chat/Prototypes/EmotePrototype.cs new file mode 100644 index 0000000000..edd80f1a9a --- /dev/null +++ b/Content.Shared/Chat/Prototypes/EmotePrototype.cs @@ -0,0 +1,51 @@ +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + +namespace Content.Shared.Chat.Prototypes; + +/// +/// IC emotes (scream, smile, clapping, etc). +/// Entities can activate emotes by chat input or code. +/// +[Prototype("emote")] +public sealed class EmotePrototype : IPrototype +{ + [IdDataField] + public string ID { get; } = default!; + + /// + /// Different emote categories may be handled by different systems. + /// Also may be used for filtering. + /// + [DataField("category")] + public EmoteCategory Category = EmoteCategory.General; + + /// + /// Collection of words that will be sent to chat if emote activates. + /// Will be picked randomly from list. + /// + [DataField("chatMessages")] + public List ChatMessages = new(); + + /// + /// Trigger words for emote. Case independent. + /// When typed into players chat they will activate emote event. + /// All words should be unique across all emote prototypes. + /// + [DataField("chatTriggers")] + public HashSet ChatTriggers = new(); +} + +/// +/// IC emote category. Usually physical source of emote, +/// like hands, voice, face, etc. +/// +[Flags] +[Serializable, NetSerializable] +public enum EmoteCategory : byte +{ + Invalid = 0, + Vocal = 1 << 0, + Hands = 1 << 1, + General = byte.MaxValue +} diff --git a/Content.Shared/Chat/Prototypes/EmoteSoundsPrototype.cs b/Content.Shared/Chat/Prototypes/EmoteSoundsPrototype.cs new file mode 100644 index 0000000000..76f3459504 --- /dev/null +++ b/Content.Shared/Chat/Prototypes/EmoteSoundsPrototype.cs @@ -0,0 +1,36 @@ +using Robust.Shared.Audio; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary; + +namespace Content.Shared.Chat.Prototypes; + +/// +/// Sounds collection for each . +/// Different entities may use different sounds collections. +/// +[Prototype("emoteSounds")] +public sealed class EmoteSoundsPrototype : IPrototype +{ + [IdDataField] + public string ID { get; } = default!; + + /// + /// Optional fallback sound that will play if collection + /// doesn't have specific sound for this emote id. + /// + [DataField("sound")] + public SoundSpecifier? FallbackSound; + + /// + /// Optional audio params that will be applied to ALL sounds. + /// This will overwrite any params that may be set in sound specifiers. + /// + [DataField("params")] + public AudioParams? GeneralParams; + + /// + /// Collection of emote prototypes and their sounds. + /// + [DataField("sounds", customTypeSerializer: typeof(PrototypeIdDictionarySerializer))] + public Dictionary Sounds = new(); +} diff --git a/Content.Shared/Chat/SharedChatSystem.cs b/Content.Shared/Chat/SharedChatSystem.cs index d6df55537d..0d046e12b8 100644 --- a/Content.Shared/Chat/SharedChatSystem.cs +++ b/Content.Shared/Chat/SharedChatSystem.cs @@ -1,3 +1,5 @@ namespace Content.Shared.Chat; -public abstract class SharedChatSystem : EntitySystem {} +public abstract class SharedChatSystem : EntitySystem +{ +} diff --git a/Content.Shared/Humanoid/Sex.cs b/Content.Shared/Humanoid/Sex.cs index aeb85bdb54..ddb8cca036 100644 --- a/Content.Shared/Humanoid/Sex.cs +++ b/Content.Shared/Humanoid/Sex.cs @@ -12,4 +12,10 @@ namespace Content.Shared.Humanoid Female, Unsexed, } + + /// + /// Raised when entity has changed their sex. + /// This doesn't handle gender changes. + /// + public record struct SexChangedEvent(Sex OldSex, Sex NewSex); } diff --git a/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs b/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs index 7feb0da53d..1004a5bf54 100644 --- a/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs +++ b/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs @@ -197,4 +197,26 @@ public abstract class SharedHumanoidAppearanceSystem : EntitySystem if (sync) Dirty(humanoid); } + + /// + /// Set a humanoid mob's sex. This will not change their gender. + /// + /// The humanoid mob's UID. + /// The sex to set the mob to. + /// Whether to immediately synchronize this to the humanoid mob, or not. + /// Humanoid component of the entity + public void SetSex(EntityUid uid, Sex sex, bool sync = true, HumanoidAppearanceComponent? humanoid = null) + { + if (!Resolve(uid, ref humanoid) || humanoid.Sex == sex) + return; + + var oldSex = humanoid.Sex; + humanoid.Sex = sex; + RaiseLocalEvent(uid, new SexChangedEvent(oldSex, sex)); + + if (sync) + { + Dirty(humanoid); + } + } } diff --git a/Content.Shared/Zombies/ZombieComponent.cs b/Content.Shared/Zombies/ZombieComponent.cs index 25b2af24f2..19fe721b71 100644 --- a/Content.Shared/Zombies/ZombieComponent.cs +++ b/Content.Shared/Zombies/ZombieComponent.cs @@ -1,3 +1,4 @@ +using Content.Shared.Chat.Prototypes; using Content.Shared.Roles; using Content.Shared.Humanoid; using Robust.Shared.GameStates; @@ -80,5 +81,10 @@ namespace Content.Shared.Zombies /// [DataField("beforeZombifiedSkinColor")] public Color BeforeZombifiedSkinColor; + + [DataField("emoteId", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string? EmoteSoundsId = "Zombie"; + + public EmoteSoundsPrototype? EmoteSounds; } } diff --git a/Resources/Audio/Effects/Emotes/attributions.yml b/Resources/Audio/Effects/Emotes/attributions.yml new file mode 100644 index 0000000000..9d2b24022c --- /dev/null +++ b/Resources/Audio/Effects/Emotes/attributions.yml @@ -0,0 +1,15 @@ +- files: + - clap1.ogg + - clap2.ogg + - clap3.ogg + - clap4.ogg + license: "CC-BY-SA-3.0" + copyright: "Taken from tgstation at https://github.com/tgstation/tgstation/commit/e1142f20f5e4661cb6845cfcf2dd69f864d67432" + source: "https://github.com/tgstation/tgstation" +- files: + - snap1.ogg + - snap2.ogg + - snap3.ogg + license: "CC-BY-4.0" + copyright: "Finger Snaps Pack by Snapper4298. Converted from WAV to OGG." + source: "https://freesound.org/people/Snapper4298/packs/11176/" diff --git a/Resources/Audio/Effects/Emotes/clap1.ogg b/Resources/Audio/Effects/Emotes/clap1.ogg new file mode 100644 index 0000000000..da0f7eded7 Binary files /dev/null and b/Resources/Audio/Effects/Emotes/clap1.ogg differ diff --git a/Resources/Audio/Effects/Emotes/clap2.ogg b/Resources/Audio/Effects/Emotes/clap2.ogg new file mode 100644 index 0000000000..72e26d4a24 Binary files /dev/null and b/Resources/Audio/Effects/Emotes/clap2.ogg differ diff --git a/Resources/Audio/Effects/Emotes/clap3.ogg b/Resources/Audio/Effects/Emotes/clap3.ogg new file mode 100644 index 0000000000..7a72ab9bf3 Binary files /dev/null and b/Resources/Audio/Effects/Emotes/clap3.ogg differ diff --git a/Resources/Audio/Effects/Emotes/clap4.ogg b/Resources/Audio/Effects/Emotes/clap4.ogg new file mode 100644 index 0000000000..cdc533ca77 Binary files /dev/null and b/Resources/Audio/Effects/Emotes/clap4.ogg differ diff --git a/Resources/Audio/Effects/Emotes/snap1.ogg b/Resources/Audio/Effects/Emotes/snap1.ogg new file mode 100644 index 0000000000..4853e76cfd Binary files /dev/null and b/Resources/Audio/Effects/Emotes/snap1.ogg differ diff --git a/Resources/Audio/Effects/Emotes/snap2.ogg b/Resources/Audio/Effects/Emotes/snap2.ogg new file mode 100644 index 0000000000..31f97de89f Binary files /dev/null and b/Resources/Audio/Effects/Emotes/snap2.ogg differ diff --git a/Resources/Audio/Effects/Emotes/snap3.ogg b/Resources/Audio/Effects/Emotes/snap3.ogg new file mode 100644 index 0000000000..92fb5e6022 Binary files /dev/null and b/Resources/Audio/Effects/Emotes/snap3.ogg differ diff --git a/Resources/Locale/en-US/actions/actions/vocal.ftl b/Resources/Locale/en-US/actions/actions/vocal.ftl index 48252b5f27..97cf7fae94 100644 --- a/Resources/Locale/en-US/actions/actions/vocal.ftl +++ b/Resources/Locale/en-US/actions/actions/vocal.ftl @@ -1,2 +1,2 @@ action-name-scream = Scream -scream-action-popup = Screams! +action-description-scream = AAAAAAAAAAAAAAAAAAAAAAAAA diff --git a/Resources/Prototypes/Actions/types.yml b/Resources/Prototypes/Actions/types.yml index a5abc600b6..7786acec87 100644 --- a/Resources/Prototypes/Actions/types.yml +++ b/Resources/Prototypes/Actions/types.yml @@ -3,7 +3,7 @@ useDelay: 10 icon: Interface/Actions/scream.png name: action-name-scream - description: AAAAAAAAAAAAAAAAAAAAAAAAA + description: action-description-scream serverEvent: !type:ScreamActionEvent checkCanInteract: false diff --git a/Resources/Prototypes/Body/Parts/skeleton.yml b/Resources/Prototypes/Body/Parts/skeleton.yml index ad07d69043..42a1b83f65 100644 --- a/Resources/Prototypes/Body/Parts/skeleton.yml +++ b/Resources/Prototypes/Body/Parts/skeleton.yml @@ -54,8 +54,10 @@ - type: SkeletonAccent - type: Actions - type: Vocal - maleScream: /Audio/Voice/Skeleton/skeleton_scream.ogg - femaleScream: /Audio/Voice/Skeleton/skeleton_scream.ogg + sounds: + Male: Skeleton + Female: Skeleton + Unsexed: Skeleton - type: Emoting - type: Grammar attributes: diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index 23a0130615..7ff6fa01ca 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -839,10 +839,10 @@ types: Blunt: 0.1 - type: Vocal - # mice are gender neutral who cares - maleScream: /Audio/Animals/mouse_squeak.ogg - femaleScream: /Audio/Animals/mouse_squeak.ogg - unsexedScream: /Audio/Animals/mouse_squeak.ogg + sounds: + Male: Mouse + Female: Mouse + Unsexed: Mouse wilhelmProbability: 0.001 # TODO: Remove CombatMode when Prototype Composition is added - type: CombatMode diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml b/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml index e3cc555eff..53f3d93d1c 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml @@ -276,9 +276,10 @@ - Plague - TongueTwister - type: Vocal - # mice are gender neutral who cares - maleScream: /Audio/Animals/mouse_squeak.ogg - femaleScream: /Audio/Animals/mouse_squeak.ogg + sounds: + Male: Mouse + Female: Mouse + Unsexed: Mouse wilhelmProbability: 0.001 - type: GhostTakeoverAvailable makeSentient: true diff --git a/Resources/Prototypes/Entities/Mobs/Player/reptilian.yml b/Resources/Prototypes/Entities/Mobs/Player/reptilian.yml index 851a800252..4890c8b6a8 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/reptilian.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/reptilian.yml @@ -17,9 +17,6 @@ context: "human" - type: MobMover - type: InputMover - - type: Vocal - maleScream: /Audio/Voice/Reptilian/reptilian_scream.ogg - femaleScream: /Audio/Voice/Reptilian/reptilian_scream.ogg - type: Alerts - type: Eye - type: CameraRecoil diff --git a/Resources/Prototypes/Entities/Mobs/Player/skeleton.yml b/Resources/Prototypes/Entities/Mobs/Player/skeleton.yml index 3f60116835..054e50b921 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/skeleton.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/skeleton.yml @@ -14,9 +14,6 @@ context: "human" - type: MobMover - type: InputMover - - type: Vocal - maleScream: /Audio/Voice/Skeleton/skeleton_scream.ogg - femaleScream: /Audio/Voice/Skeleton/skeleton_scream.ogg - type: Alerts - type: Actions - type: Eye diff --git a/Resources/Prototypes/Entities/Mobs/Player/vox.yml b/Resources/Prototypes/Entities/Mobs/Player/vox.yml index 6b25447877..a74e95680c 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/vox.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/vox.yml @@ -17,9 +17,6 @@ context: "human" - type: MobMover - type: InputMover - - type: Vocal - maleScream: /Audio/Voice/Vox/shriek1.ogg - femaleScream: /Audio/Voice/Vox/shriek1.ogg - type: Alerts - type: Eye - type: CameraRecoil diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml index 40fe6e3d5b..92c10603d0 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/base.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml @@ -278,7 +278,13 @@ - type: Speech speechSounds: Alto - type: Vocal + sounds: + Male: MaleHuman + Female: FemaleHuman + Unsexed: MaleHuman - type: Emoting + - type: BodyEmotes + soundsId: GeneralBodyEmotes - type: Grammar attributes: proper: true diff --git a/Resources/Prototypes/Entities/Mobs/Species/reptilian.yml b/Resources/Prototypes/Entities/Mobs/Species/reptilian.yml index 0b66aac1ac..dc17cbfe1c 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/reptilian.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/reptilian.yml @@ -18,6 +18,11 @@ - type: LizardAccent - type: Speech speechSounds: Lizard + - type: Vocal + sounds: + Male: UnisexReptilian + Female: UnisexReptilian + Unsexed: UnisexReptilian - type: DiseaseCarrier diseaseResist: 0.1 - type: Damageable diff --git a/Resources/Prototypes/Entities/Mobs/Species/skeleton.yml b/Resources/Prototypes/Entities/Mobs/Species/skeleton.yml index 76993aad60..12847f6d42 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/skeleton.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/skeleton.yml @@ -49,6 +49,11 @@ 60: 0.9 80: 0.7 - type: Speech + - type: Vocal + sounds: + Male: Skeleton + Female: Skeleton + Unsexed: Skeleton - type: SkeletonAccent - type: Fixtures fixtures: diff --git a/Resources/Prototypes/Entities/Mobs/Species/vox.yml b/Resources/Prototypes/Entities/Mobs/Species/vox.yml index 7f27787625..151a4111de 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/vox.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/vox.yml @@ -101,6 +101,11 @@ spawned: - id: FoodMeatChicken amount: 5 + - type: Vocal + sounds: + Male: UnisexVox + Female: UnisexVox + Unsexed: UnisexVox - type: entity id: MobVoxDummy diff --git a/Resources/Prototypes/Reagents/medicine.yml b/Resources/Prototypes/Reagents/medicine.yml index f7260c4eb5..91dfe32f3e 100644 --- a/Resources/Prototypes/Reagents/medicine.yml +++ b/Resources/Prototypes/Reagents/medicine.yml @@ -422,7 +422,8 @@ key: Stutter component: StutteringAccent - !type:Jitter - - !type:Scream + - !type:Emote + emote: Scream probability: 0.2 - !type:PopupMessage type: Local diff --git a/Resources/Prototypes/Reagents/pyrotechnic.yml b/Resources/Prototypes/Reagents/pyrotechnic.yml index 482b02c38e..f4a2961464 100644 --- a/Resources/Prototypes/Reagents/pyrotechnic.yml +++ b/Resources/Prototypes/Reagents/pyrotechnic.yml @@ -116,7 +116,8 @@ - !type:FlammableReaction multiplier: 0.3 - !type:Ignite - - !type:Scream + - !type:Emote + emote: Scream probability: 0.2 - !type:PopupMessage messages: [ "clf3-it-burns", "clf3-get-away" ] diff --git a/Resources/Prototypes/Reagents/toxins.yml b/Resources/Prototypes/Reagents/toxins.yml index 820629bf6f..74bfa3d67f 100644 --- a/Resources/Prototypes/Reagents/toxins.yml +++ b/Resources/Prototypes/Reagents/toxins.yml @@ -134,7 +134,8 @@ damage: groups: Caustic: 0.5 - - !type:Scream + - !type:Emote + emote: Scream probability: 0.3 metabolisms: Poison: @@ -169,7 +170,8 @@ damage: groups: Caustic: 0.3 - - !type:Scream + - !type:Emote + emote: Scream probability: 0.2 metabolisms: Poison: @@ -211,7 +213,8 @@ damage: groups: Caustic: 0.1 - - !type:Scream + - !type:Emote + emote: Scream probability: 0.1 metabolisms: Poison: diff --git a/Resources/Prototypes/SoundCollections/emotes.yml b/Resources/Prototypes/SoundCollections/emotes.yml new file mode 100644 index 0000000000..c66b5f562c --- /dev/null +++ b/Resources/Prototypes/SoundCollections/emotes.yml @@ -0,0 +1,25 @@ +- type: soundCollection + id: MaleLaugh + files: + - /Audio/Voice/Human/manlaugh1.ogg + - /Audio/Voice/Human/manlaugh2.ogg + +- type: soundCollection + id: FemaleLaugh + files: + - /Audio/Voice/Human/womanlaugh.ogg + +- type: soundCollection + id: Claps + files: + - /Audio/Effects/Emotes/clap1.ogg + - /Audio/Effects/Emotes/clap2.ogg + - /Audio/Effects/Emotes/clap3.ogg + - /Audio/Effects/Emotes/clap4.ogg + +- type: soundCollection + id: Snaps + files: + - /Audio/Effects/Emotes/snap1.ogg + - /Audio/Effects/Emotes/snap2.ogg + - /Audio/Effects/Emotes/snap3.ogg diff --git a/Resources/Prototypes/Voice/speech_emote_sounds.yml b/Resources/Prototypes/Voice/speech_emote_sounds.yml new file mode 100644 index 0000000000..d731fc9d61 --- /dev/null +++ b/Resources/Prototypes/Voice/speech_emote_sounds.yml @@ -0,0 +1,70 @@ +# species +- type: emoteSounds + id: MaleHuman + params: + variation: 0.125 + sounds: + Scream: + collection: MaleScreams + Laugh: + collection: MaleLaugh + +- type: emoteSounds + id: FemaleHuman + params: + variation: 0.125 + sounds: + Scream: + collection: FemaleScreams + Laugh: + collection: FemaleLaugh + +- type: emoteSounds + id: UnisexReptilian + params: + variation: 0.125 + sounds: + Scream: + path: /Audio/Voice/Reptilian/reptilian_scream.ogg + Laugh: + path: /Audio/Animals/lizard_happy.ogg + +- type: emoteSounds + id: UnisexVox + sound: + path: /Audio/Voice/Vox/shriek1.ogg + params: + variation: 0.125 + +# body emotes +- type: emoteSounds + id: GeneralBodyEmotes + sounds: + Clap: + collection: Claps + Snap: + collection: Snaps + params: + volume: -6 + +# mobs +- type: emoteSounds + id: Zombie + sound: + collection: ZombieScreams + params: + variation: 0.125 + +- type: emoteSounds + id: Skeleton + sound: + path: /Audio/Voice/Skeleton/skeleton_scream.ogg + params: + variation: 0.125 + +- type: emoteSounds + id: Mouse + sound: + path: /Audio/Animals/mouse_squeak.ogg + params: + variation: 0.125 diff --git a/Resources/Prototypes/Voice/speech_emotes.yml b/Resources/Prototypes/Voice/speech_emotes.yml new file mode 100644 index 0000000000..d0298c66b3 --- /dev/null +++ b/Resources/Prototypes/Voice/speech_emotes.yml @@ -0,0 +1,66 @@ +# vocal emotes +- type: emote + id: Scream + category: Vocal + chatMessages: [screams, yells] + chatTriggers: + - scream + - screams + - screaming + - screamed + - shriek + - shrieks + - shrieking + - shrieked + - screech + - screechs + - screeching + - screeched + - yell + - yells + - yelled + - yelling + +- type: emote + id: Laugh + category: Vocal + chatMessages: [laughs] + chatTriggers: + - laugh + - laughs + - laughing + - laughed + - chuckle + - chuckles + - chuckled + - chuckling + - giggle + - giggles + - giggling + - giggled + +# hand emotes +- type: emote + id: Clap + category: Hands + chatMessages: [claps] + chatTriggers: + - clap + - claps + - clapping + - clapped + +- type: emote + id: Snap + category: Hands + chatMessages: [snaps fingers] + chatTriggers: + - snap + - snaps + - snapping + - snapped + - snap fingers + - snaps fingers + - snapping fingers + - snapped fingers +