Audible emotes (#12708)
Co-authored-by: Visne <39844191+Visne@users.noreply.github.com>
This commit is contained in:
173
Content.Server/Chat/Systems/ChatSystem.Emote.cs
Normal file
173
Content.Server/Chat/Systems/ChatSystem.Emote.cs
Normal file
@@ -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<string, EmotePrototype> _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<EmotePrototype>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes selected entity to emote using <see cref="EmotePrototype"/> and sends message to chat.
|
||||
/// </summary>
|
||||
/// <param name="source">The entity that is speaking</param>
|
||||
/// <param name="emoteId">The id of emote prototype. Should has valid <see cref="EmotePrototype.ChatMessages"/></param>
|
||||
/// <param name="hideChat">Whether or not this message should appear in the chat window</param>
|
||||
/// <param name="hideGlobalGhostChat">Whether or not this message should appear in the chat window for out-of-range ghosts (which otherwise ignore range restrictions)</param>
|
||||
/// <param name="nameOverride">The name to use for the speaking entity. Usually this should just be modified via <see cref="TransformSpeakerNameEvent"/>. If this is set, the event will not get raised.</param>
|
||||
public void TryEmoteWithChat(EntityUid source, string emoteId, bool hideChat = false,
|
||||
bool hideGlobalGhostChat = false, string? nameOverride = null)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex<EmotePrototype>(emoteId, out var proto))
|
||||
return;
|
||||
TryEmoteWithChat(source, proto, hideChat, hideGlobalGhostChat, nameOverride);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes selected entity to emote using <see cref="EmotePrototype"/> and sends message to chat.
|
||||
/// </summary>
|
||||
/// <param name="source">The entity that is speaking</param>
|
||||
/// <param name="emote">The emote prototype. Should has valid <see cref="EmotePrototype.ChatMessages"/></param>
|
||||
/// <param name="hideChat">Whether or not this message should appear in the chat window</param>
|
||||
/// <param name="hideGlobalGhostChat">Whether or not this message should appear in the chat window for out-of-range ghosts (which otherwise ignore range restrictions)</param>
|
||||
/// <param name="nameOverride">The name to use for the speaking entity. Usually this should just be modified via <see cref="TransformSpeakerNameEvent"/>. If this is set, the event will not get raised.</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes selected entity to emote using <see cref="EmotePrototype"/> without sending any messages to chat.
|
||||
/// </summary>
|
||||
public void TryEmoteWithoutChat(EntityUid uid, string emoteId)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex<EmotePrototype>(emoteId, out var proto))
|
||||
return;
|
||||
TryEmoteWithoutChat(uid, proto);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes selected entity to emote using <see cref="EmotePrototype"/> without sending any messages to chat.
|
||||
/// </summary>
|
||||
public void TryEmoteWithoutChat(EntityUid uid, EmotePrototype proto)
|
||||
{
|
||||
if (!_actionBlocker.CanEmote(uid))
|
||||
return;
|
||||
|
||||
InvokeEmoteEvent(uid, proto);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find and play relevant emote sound in emote sounds collection.
|
||||
/// </summary>
|
||||
/// <returns>True if emote sound was played.</returns>
|
||||
public bool TryPlayEmoteSound(EntityUid uid, EmoteSoundsPrototype? proto, EmotePrototype emote)
|
||||
{
|
||||
return TryPlayEmoteSound(uid, proto, emote.ID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find and play relevant emote sound in emote sounds collection.
|
||||
/// </summary>
|
||||
/// <returns>True if emote sound was played.</returns>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised by chat system when entity made some emote.
|
||||
/// Use it to play sound, change sprite or something else.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public struct EmoteEvent
|
||||
{
|
||||
public bool Handled;
|
||||
public readonly EmotePrototype Emote;
|
||||
|
||||
public EmoteEvent(EmotePrototype emote)
|
||||
{
|
||||
Emote = emote;
|
||||
Handled = false;
|
||||
}
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
|
||||
33
Content.Server/Chemistry/ReagentEffects/Emote.cs
Normal file
33
Content.Server/Chemistry/ReagentEffects/Emote.cs
Normal file
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Tries to force someone to emote (scream, laugh, etc).
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed class Emote : ReagentEffect
|
||||
{
|
||||
[DataField("emote", customTypeSerializer: typeof(PrototypeIdSerializer<EmotePrototype>))]
|
||||
public string? EmoteId;
|
||||
|
||||
[DataField("showInChat")]
|
||||
public bool ShowInChat;
|
||||
|
||||
public override void Effect(ReagentEffectArgs args)
|
||||
{
|
||||
if (EmoteId == null)
|
||||
return;
|
||||
|
||||
var chatSys = args.EntityManager.System<ChatSystem>();
|
||||
if (ShowInChat)
|
||||
chatSys.TryEmoteWithChat(args.SolutionEntity, EmoteId, hideGlobalGhostChat: true);
|
||||
else
|
||||
chatSys.TryEmoteWithoutChat(args.SolutionEntity, EmoteId);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
using Content.Server.Speech;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
|
||||
namespace Content.Server.Chemistry.ReagentEffects;
|
||||
|
||||
/// <summary>
|
||||
/// Forces someone to scream their lungs out.
|
||||
/// </summary>
|
||||
public sealed class Scream : ReagentEffect
|
||||
{
|
||||
public override void Effect(ReagentEffectArgs args)
|
||||
{
|
||||
EntitySystem.Get<VocalSystem>().TryScream(args.SolutionEntity);
|
||||
}
|
||||
}
|
||||
24
Content.Server/Emoting/Components/BodyEmotesComponent.cs
Normal file
24
Content.Server/Emoting/Components/BodyEmotesComponent.cs
Normal file
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Component required for entities to be able to do body emotions (clap, flip, etc).
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[Access(typeof(BodyEmotesSystem))]
|
||||
public sealed class BodyEmotesComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Emote sounds prototype id for body emotes.
|
||||
/// </summary>
|
||||
[DataField("soundsId", customTypeSerializer: typeof(PrototypeIdSerializer<EmoteSoundsPrototype>))]
|
||||
public string? SoundsId;
|
||||
|
||||
/// <summary>
|
||||
/// Loaded emote sounds prototype used for body emotes.
|
||||
/// </summary>
|
||||
public EmoteSoundsPrototype? Sounds;
|
||||
}
|
||||
48
Content.Server/Emoting/Systems/BodyEmotesSystem.cs
Normal file
48
Content.Server/Emoting/Systems/BodyEmotesSystem.cs
Normal file
@@ -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<BodyEmotesComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<BodyEmotesComponent, EmoteEvent>(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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Component required for entities to be able to scream.
|
||||
/// Component required for entities to be able to do vocal emotions.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[Access(typeof(VocalSystem))]
|
||||
public sealed class VocalComponent : Component
|
||||
{
|
||||
[DataField("maleScream")]
|
||||
public SoundSpecifier MaleScream = new SoundCollectionSpecifier("MaleScreams");
|
||||
/// <summary>
|
||||
/// Emote sounds prototype id for each sex (not gender).
|
||||
/// Entities without <see cref="HumanoidComponent"/> considered to be <see cref="Sex.Unsexed"/>.
|
||||
/// </summary>
|
||||
[DataField("sounds", customTypeSerializer: typeof(PrototypeIdValueDictionarySerializer<Sex, EmoteSoundsPrototype>))]
|
||||
public Dictionary<Sex, string>? Sounds;
|
||||
|
||||
[DataField("femaleScream")]
|
||||
public SoundSpecifier FemaleScream = new SoundCollectionSpecifier("FemaleScreams");
|
||||
|
||||
[DataField("unsexedScream")]
|
||||
public SoundSpecifier UnsexedScream = new SoundCollectionSpecifier("MaleScreams");
|
||||
[DataField("screamId", customTypeSerializer: typeof(PrototypeIdSerializer<EmotePrototype>))]
|
||||
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<InstantActionPrototype>))]
|
||||
public string ScreamActionId = "Scream";
|
||||
|
||||
[DataField("actionId", customTypeSerializer:typeof(PrototypeIdSerializer<InstantActionPrototype>))]
|
||||
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;
|
||||
/// <summary>
|
||||
/// Currently loaded emote sounds prototype, based on entity sex.
|
||||
/// Null if no valid prototype for entity sex was found.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public EmoteSoundsPrototype? EmoteSounds = null;
|
||||
}
|
||||
|
||||
public sealed class ScreamActionEvent : InstantActionEvent { };
|
||||
public sealed class ScreamActionEvent : InstantActionEvent
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
105
Content.Server/Speech/EntitySystems/VocalSystem.cs
Normal file
105
Content.Server/Speech/EntitySystems/VocalSystem.cs
Normal file
@@ -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<VocalComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<VocalComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<VocalComponent, SexChangedEvent>(OnSexChanged);
|
||||
SubscribeLocalEvent<VocalComponent, EmoteEvent>(OnEmote);
|
||||
SubscribeLocalEvent<VocalComponent, ScreamActionEvent>(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<HumanoidAppearanceComponent>(uid)?.Sex ?? Sex.Unsexed;
|
||||
|
||||
if (!component.Sounds.TryGetValue(sex.Value, out var protoId))
|
||||
return;
|
||||
_proto.TryIndex(protoId, out component.EmoteSounds);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Fer Screamin
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Or I guess other vocalizations, like laughing. If fun is ever legalized on the station.
|
||||
/// </remarks>
|
||||
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<VocalComponent, ScreamActionEvent>(OnActionPerform);
|
||||
SubscribeLocalEvent<VocalComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<VocalComponent, ComponentShutdown>(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<HumanoidAppearanceComponent>(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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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<ZombieComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<ZombieComponent, EmoteEvent>(OnEmote, before:
|
||||
new []{typeof(VocalSystem), typeof(BodyEmotesSystem)});
|
||||
|
||||
SubscribeLocalEvent<ZombieComponent, MeleeHitEvent>(OnMeleeHit);
|
||||
SubscribeLocalEvent<ZombieComponent, MobStateChangedEvent>(OnMobState);
|
||||
SubscribeLocalEvent<ZombieComponent, CloningEvent>(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;
|
||||
}
|
||||
|
||||
@@ -111,11 +111,6 @@ namespace Content.Server.Zombies
|
||||
var combat = AddComp<CombatModeComponent>(target);
|
||||
combat.IsInCombatMode = true;
|
||||
|
||||
var vocal = EnsureComp<VocalComponent>(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<MeleeWeaponComponent>(target);
|
||||
|
||||
51
Content.Shared/Chat/Prototypes/EmotePrototype.cs
Normal file
51
Content.Shared/Chat/Prototypes/EmotePrototype.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Chat.Prototypes;
|
||||
|
||||
/// <summary>
|
||||
/// IC emotes (scream, smile, clapping, etc).
|
||||
/// Entities can activate emotes by chat input or code.
|
||||
/// </summary>
|
||||
[Prototype("emote")]
|
||||
public sealed class EmotePrototype : IPrototype
|
||||
{
|
||||
[IdDataField]
|
||||
public string ID { get; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Different emote categories may be handled by different systems.
|
||||
/// Also may be used for filtering.
|
||||
/// </summary>
|
||||
[DataField("category")]
|
||||
public EmoteCategory Category = EmoteCategory.General;
|
||||
|
||||
/// <summary>
|
||||
/// Collection of words that will be sent to chat if emote activates.
|
||||
/// Will be picked randomly from list.
|
||||
/// </summary>
|
||||
[DataField("chatMessages")]
|
||||
public List<string> ChatMessages = new();
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[DataField("chatTriggers")]
|
||||
public HashSet<string> ChatTriggers = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// IC emote category. Usually physical source of emote,
|
||||
/// like hands, voice, face, etc.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
[Serializable, NetSerializable]
|
||||
public enum EmoteCategory : byte
|
||||
{
|
||||
Invalid = 0,
|
||||
Vocal = 1 << 0,
|
||||
Hands = 1 << 1,
|
||||
General = byte.MaxValue
|
||||
}
|
||||
36
Content.Shared/Chat/Prototypes/EmoteSoundsPrototype.cs
Normal file
36
Content.Shared/Chat/Prototypes/EmoteSoundsPrototype.cs
Normal file
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Sounds collection for each <see cref="EmotePrototype"/>.
|
||||
/// Different entities may use different sounds collections.
|
||||
/// </summary>
|
||||
[Prototype("emoteSounds")]
|
||||
public sealed class EmoteSoundsPrototype : IPrototype
|
||||
{
|
||||
[IdDataField]
|
||||
public string ID { get; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Optional fallback sound that will play if collection
|
||||
/// doesn't have specific sound for this emote id.
|
||||
/// </summary>
|
||||
[DataField("sound")]
|
||||
public SoundSpecifier? FallbackSound;
|
||||
|
||||
/// <summary>
|
||||
/// Optional audio params that will be applied to ALL sounds.
|
||||
/// This will overwrite any params that may be set in sound specifiers.
|
||||
/// </summary>
|
||||
[DataField("params")]
|
||||
public AudioParams? GeneralParams;
|
||||
|
||||
/// <summary>
|
||||
/// Collection of emote prototypes and their sounds.
|
||||
/// </summary>
|
||||
[DataField("sounds", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<SoundSpecifier, EmotePrototype>))]
|
||||
public Dictionary<string, SoundSpecifier> Sounds = new();
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
namespace Content.Shared.Chat;
|
||||
|
||||
public abstract class SharedChatSystem : EntitySystem {}
|
||||
public abstract class SharedChatSystem : EntitySystem
|
||||
{
|
||||
}
|
||||
|
||||
@@ -12,4 +12,10 @@ namespace Content.Shared.Humanoid
|
||||
Female,
|
||||
Unsexed,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised when entity has changed their sex.
|
||||
/// This doesn't handle gender changes.
|
||||
/// </summary>
|
||||
public record struct SexChangedEvent(Sex OldSex, Sex NewSex);
|
||||
}
|
||||
|
||||
@@ -197,4 +197,26 @@ public abstract class SharedHumanoidAppearanceSystem : EntitySystem
|
||||
if (sync)
|
||||
Dirty(humanoid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a humanoid mob's sex. This will not change their gender.
|
||||
/// </summary>
|
||||
/// <param name="uid">The humanoid mob's UID.</param>
|
||||
/// <param name="sex">The sex to set the mob to.</param>
|
||||
/// <param name="sync">Whether to immediately synchronize this to the humanoid mob, or not.</param>
|
||||
/// <param name="humanoid">Humanoid component of the entity</param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
/// </summary>
|
||||
[DataField("beforeZombifiedSkinColor")]
|
||||
public Color BeforeZombifiedSkinColor;
|
||||
|
||||
[DataField("emoteId", customTypeSerializer: typeof(PrototypeIdSerializer<EmoteSoundsPrototype>))]
|
||||
public string? EmoteSoundsId = "Zombie";
|
||||
|
||||
public EmoteSoundsPrototype? EmoteSounds;
|
||||
}
|
||||
}
|
||||
|
||||
15
Resources/Audio/Effects/Emotes/attributions.yml
Normal file
15
Resources/Audio/Effects/Emotes/attributions.yml
Normal file
@@ -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/"
|
||||
BIN
Resources/Audio/Effects/Emotes/clap1.ogg
Normal file
BIN
Resources/Audio/Effects/Emotes/clap1.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/Effects/Emotes/clap2.ogg
Normal file
BIN
Resources/Audio/Effects/Emotes/clap2.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/Effects/Emotes/clap3.ogg
Normal file
BIN
Resources/Audio/Effects/Emotes/clap3.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/Effects/Emotes/clap4.ogg
Normal file
BIN
Resources/Audio/Effects/Emotes/clap4.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/Effects/Emotes/snap1.ogg
Normal file
BIN
Resources/Audio/Effects/Emotes/snap1.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/Effects/Emotes/snap2.ogg
Normal file
BIN
Resources/Audio/Effects/Emotes/snap2.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/Effects/Emotes/snap3.ogg
Normal file
BIN
Resources/Audio/Effects/Emotes/snap3.ogg
Normal file
Binary file not shown.
@@ -1,2 +1,2 @@
|
||||
action-name-scream = Scream
|
||||
scream-action-popup = Screams!
|
||||
action-description-scream = AAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -101,6 +101,11 @@
|
||||
spawned:
|
||||
- id: FoodMeatChicken
|
||||
amount: 5
|
||||
- type: Vocal
|
||||
sounds:
|
||||
Male: UnisexVox
|
||||
Female: UnisexVox
|
||||
Unsexed: UnisexVox
|
||||
|
||||
- type: entity
|
||||
id: MobVoxDummy
|
||||
|
||||
@@ -422,7 +422,8 @@
|
||||
key: Stutter
|
||||
component: StutteringAccent
|
||||
- !type:Jitter
|
||||
- !type:Scream
|
||||
- !type:Emote
|
||||
emote: Scream
|
||||
probability: 0.2
|
||||
- !type:PopupMessage
|
||||
type: Local
|
||||
|
||||
@@ -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" ]
|
||||
|
||||
@@ -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:
|
||||
|
||||
25
Resources/Prototypes/SoundCollections/emotes.yml
Normal file
25
Resources/Prototypes/SoundCollections/emotes.yml
Normal file
@@ -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
|
||||
70
Resources/Prototypes/Voice/speech_emote_sounds.yml
Normal file
70
Resources/Prototypes/Voice/speech_emote_sounds.yml
Normal file
@@ -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
|
||||
66
Resources/Prototypes/Voice/speech_emotes.yml
Normal file
66
Resources/Prototypes/Voice/speech_emotes.yml
Normal file
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user