Component for clothes to suppress emotes and scream action in general, and the muzzle to suppress vocal emotes in particular (#32588)
* Component for clothes to suppress scream noise GaggedComponent + AddGaggedClothingComponent and relevant systems to make them work. Currently only stifles the scream _action_, not all emotes because if a mime can silently emote, so can gagged you! * fix comments * swap to inventory relay and make it more general such that specific emotes or emotes of a given category can be blocked * power gloves shouldn't block snapping * easy fixes * blockable emote event * pr comments, switch to using emote event mostly * pr comments add beforeEmoteEvent add emote blocker name to popup maybe some other stuff, I forget * get rid of emoteevent's source because I don't need it anymore * smol clean * formatting, style, and one minor thing where having a muzzle in your pocket would gag you
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
using System.Collections.Frozen;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Chat.Prototypes;
|
||||
using Content.Shared.Emoting;
|
||||
using Content.Shared.Speech;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -10,6 +12,8 @@ namespace Content.Server.Chat.Systems;
|
||||
// emotes using emote prototype
|
||||
public partial class ChatSystem
|
||||
{
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
|
||||
private FrozenDictionary<string, EmotePrototype> _wordEmoteDict = FrozenDictionary<string, EmotePrototype>.Empty;
|
||||
|
||||
protected override void OnPrototypeReload(PrototypesReloadedEventArgs obj)
|
||||
@@ -51,7 +55,8 @@ public partial class ChatSystem
|
||||
/// <param name="range">Conceptual range of transmission, if it shows in the chat window, if it shows to far-away ghosts or ghosts at all...</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>
|
||||
/// <param name="forceEmote">Bypasses whitelist/blacklist/availibility checks for if the entity can use this emote</param>
|
||||
public void TryEmoteWithChat(
|
||||
/// <returns>True if an emote was performed. False if the emote is unvailable, cancelled, etc.</returns>
|
||||
public bool TryEmoteWithChat(
|
||||
EntityUid source,
|
||||
string emoteId,
|
||||
ChatTransmitRange range = ChatTransmitRange.Normal,
|
||||
@@ -62,8 +67,8 @@ public partial class ChatSystem
|
||||
)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex<EmotePrototype>(emoteId, out var proto))
|
||||
return;
|
||||
TryEmoteWithChat(source, proto, range, hideLog: hideLog, nameOverride, ignoreActionBlocker: ignoreActionBlocker, forceEmote: forceEmote);
|
||||
return false;
|
||||
return TryEmoteWithChat(source, proto, range, hideLog: hideLog, nameOverride, ignoreActionBlocker: ignoreActionBlocker, forceEmote: forceEmote);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -76,7 +81,8 @@ public partial class ChatSystem
|
||||
/// <param name="range">Conceptual range of transmission, if it shows in the chat window, if it shows to far-away ghosts or ghosts at all...</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>
|
||||
/// <param name="forceEmote">Bypasses whitelist/blacklist/availibility checks for if the entity can use this emote</param>
|
||||
public void TryEmoteWithChat(
|
||||
/// <returns>True if an emote was performed. False if the emote is unvailable, cancelled, etc.</returns>
|
||||
public bool TryEmoteWithChat(
|
||||
EntityUid source,
|
||||
EmotePrototype emote,
|
||||
ChatTransmitRange range = ChatTransmitRange.Normal,
|
||||
@@ -84,43 +90,46 @@ public partial class ChatSystem
|
||||
string? nameOverride = null,
|
||||
bool ignoreActionBlocker = false,
|
||||
bool forceEmote = false
|
||||
)
|
||||
)
|
||||
{
|
||||
if (!forceEmote && !AllowedToUseEmote(source, emote))
|
||||
return;
|
||||
return false;
|
||||
|
||||
var didEmote = TryEmoteWithoutChat(source, emote, ignoreActionBlocker);
|
||||
|
||||
// check if proto has valid message for chat
|
||||
if (emote.ChatMessages.Count != 0)
|
||||
if (didEmote && emote.ChatMessages.Count != 0)
|
||||
{
|
||||
// not all emotes are loc'd, but for the ones that are we pass in entity
|
||||
var action = Loc.GetString(_random.Pick(emote.ChatMessages), ("entity", source));
|
||||
SendEntityEmote(source, action, range, nameOverride, hideLog: hideLog, checkEmote: false, ignoreActionBlocker: ignoreActionBlocker);
|
||||
}
|
||||
|
||||
// do the rest of emote event logic here
|
||||
TryEmoteWithoutChat(source, emote, ignoreActionBlocker);
|
||||
return didEmote;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes selected entity to emote using <see cref="EmotePrototype"/> without sending any messages to chat.
|
||||
/// </summary>
|
||||
public void TryEmoteWithoutChat(EntityUid uid, string emoteId, bool ignoreActionBlocker = false)
|
||||
/// <returns>True if an emote was performed. False if the emote is unvailable, cancelled, etc.</returns>
|
||||
public bool TryEmoteWithoutChat(EntityUid uid, string emoteId, bool ignoreActionBlocker = false)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex<EmotePrototype>(emoteId, out var proto))
|
||||
return;
|
||||
return false;
|
||||
|
||||
TryEmoteWithoutChat(uid, proto, ignoreActionBlocker);
|
||||
return TryEmoteWithoutChat(uid, proto, ignoreActionBlocker);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes selected entity to emote using <see cref="EmotePrototype"/> without sending any messages to chat.
|
||||
/// </summary>
|
||||
public void TryEmoteWithoutChat(EntityUid uid, EmotePrototype proto, bool ignoreActionBlocker = false)
|
||||
/// <returns>True if an emote was performed. False if the emote is unvailable, cancelled, etc.</returns>
|
||||
public bool TryEmoteWithoutChat(EntityUid uid, EmotePrototype proto, bool ignoreActionBlocker = false)
|
||||
{
|
||||
if (!_actionBlocker.CanEmote(uid) && !ignoreActionBlocker)
|
||||
return;
|
||||
return false;
|
||||
|
||||
InvokeEmoteEvent(uid, proto);
|
||||
return TryInvokeEmoteEvent(uid, proto);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -160,17 +169,17 @@ public partial class ChatSystem
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="textInput"></param>
|
||||
private void TryEmoteChatInput(EntityUid uid, string textInput)
|
||||
/// <returns>True if the chat message should be displayed (because the emote was explicitly cancelled), false if it should not be.</returns>
|
||||
private bool TryEmoteChatInput(EntityUid uid, string textInput)
|
||||
{
|
||||
var actionTrimmedLower = TrimPunctuation(textInput.ToLower());
|
||||
if (!_wordEmoteDict.TryGetValue(actionTrimmedLower, out var emote))
|
||||
return;
|
||||
return true;
|
||||
|
||||
if (!AllowedToUseEmote(uid, emote))
|
||||
return;
|
||||
return true;
|
||||
|
||||
InvokeEmoteEvent(uid, emote);
|
||||
return;
|
||||
return TryInvokeEmoteEvent(uid, emote);
|
||||
|
||||
static string TrimPunctuation(string textInput)
|
||||
{
|
||||
@@ -220,11 +229,50 @@ public partial class ChatSystem
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private void InvokeEmoteEvent(EntityUid uid, EmotePrototype proto)
|
||||
/// <summary>
|
||||
/// Creates and raises <see cref="BeforeEmoteEvent"/> and then <see cref="EmoteEvent"/> to let other systems do things like play audio.
|
||||
/// In the case that the Before event is cancelled, EmoteEvent will NOT be raised, and will optionally show a message to the player
|
||||
/// explaining why the emote didn't happen.
|
||||
/// </summary>
|
||||
/// <param name="uid">The entity which is emoting</param>
|
||||
/// <param name="proto">The emote which is being performed</param>
|
||||
/// <returns>True if the emote was performed, false otherwise.</returns>
|
||||
private bool TryInvokeEmoteEvent(EntityUid uid, EmotePrototype proto)
|
||||
{
|
||||
var beforeEv = new BeforeEmoteEvent(uid, proto);
|
||||
RaiseLocalEvent(uid, ref beforeEv);
|
||||
|
||||
if (beforeEv.Cancelled)
|
||||
{
|
||||
if (beforeEv.Blocker != null)
|
||||
{
|
||||
_popupSystem.PopupEntity(
|
||||
Loc.GetString(
|
||||
"chat-system-emote-cancelled-blocked",
|
||||
("emote", Loc.GetString(proto.Name).ToLower()),
|
||||
("blocker", beforeEv.Blocker.Value)
|
||||
),
|
||||
uid,
|
||||
uid
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
_popupSystem.PopupEntity(
|
||||
Loc.GetString("chat-system-emote-cancelled-generic",
|
||||
("emote", Loc.GetString(proto.Name).ToLower())),
|
||||
uid,
|
||||
uid
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
var ev = new EmoteEvent(proto);
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,9 +281,8 @@ public partial class ChatSystem
|
||||
/// Use it to play sound, change sprite or something else.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public struct EmoteEvent
|
||||
public sealed class EmoteEvent : HandledEntityEventArgs
|
||||
{
|
||||
public bool Handled;
|
||||
public readonly EmotePrototype Emote;
|
||||
|
||||
public EmoteEvent(EmotePrototype emote)
|
||||
|
||||
Reference in New Issue
Block a user