]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Move ChatSystem.Emotes to shared (#40866)
authorslarticodefast <161409025+slarticodefast@users.noreply.github.com>
Mon, 13 Oct 2025 18:06:01 +0000 (20:06 +0200)
committerGitHub <noreply@github.com>
Mon, 13 Oct 2025 18:06:01 +0000 (18:06 +0000)
* move to shared

* entity effect to shared

* refactor: whitespaces+xml-doc typo fixups

* refactor: a little bit more of xml-doc typos fixups

---------

Co-authored-by: pa.pecherskij <pa.pecherskij@interfax.ru>
28 files changed:
Content.Server/Access/Systems/IdCardSystem.cs
Content.Server/Body/Systems/RespiratorSystem.cs
Content.Server/Chat/Commands/MeCommand.cs
Content.Server/Chat/Commands/SayCommand.cs
Content.Server/Chat/Commands/WhisperCommand.cs
Content.Server/Chat/Systems/AutoEmoteSystem.cs
Content.Server/Chat/Systems/ChatSystem.cs
Content.Server/Chat/Systems/EmoteOnDamageSystem.cs
Content.Server/Cluwne/CluwneSystem.cs
Content.Server/Emoting/Systems/BodyEmotesSystem.cs
Content.Server/EntityEffects/Effects/EmoteEntityEffectSystem.cs [deleted file]
Content.Server/Holopad/HolopadSystem.cs
Content.Server/Mobs/CritMobActionsSystem.cs
Content.Server/Speech/EmotesMenuSystem.cs
Content.Server/Speech/EntitySystems/MumbleAccentSystem.cs
Content.Server/Speech/EntitySystems/VocalSystem.cs
Content.Server/Speech/Muting/MutingSystem.cs
Content.Server/Teleportation/TeleportLocationsSystem.cs
Content.Server/Vocalization/Systems/VocalizationSystem.cs
Content.Server/Zombies/ZombieSystem.cs
Content.Shared/Chat/EmotesEvents.cs
Content.Shared/Chat/SharedChatSystem.Emote.cs [moved from Content.Server/Chat/Systems/ChatSystem.Emote.cs with 68% similarity]
Content.Shared/Chat/SharedChatSystem.cs
Content.Shared/Emoting/EmoteEvents.cs
Content.Shared/EntityEffects/Effects/EmoteEntityEffectSystem.cs [moved from Content.Shared/EntityEffects/Effects/EmoteEntityEffect.cs with 63% similarity]
Content.Shared/Inventory/InventorySystem.Relay.cs
Content.Shared/Speech/Components/EmoteBlockerComponent.cs [moved from Content.Server/Speech/Components/EmoteBlockerComponent.cs with 74% similarity]
Content.Shared/Speech/EntitySystems/EmoteBlockerSystem.cs [moved from Content.Server/Speech/EntitySystems/EmoteBlockerSystem.cs with 90% similarity]

index 0fef62d9708162e6052749a7aee18317703d405d..f317e88f0fc0954831e1238f6767ad0971b28d80 100644 (file)
@@ -6,6 +6,7 @@ using Content.Server.Popups;
 using Content.Shared.Access;
 using Content.Shared.Access.Components;
 using Content.Shared.Access.Systems;
+using Content.Shared.Chat;
 using Content.Shared.Database;
 using Content.Shared.Popups;
 using Robust.Shared.Prototypes;
index 2af7b24f265a1f19f8eecc7a9c1cd4c33669e962..ca3ee2c9e16946cfa6f737c6067dbbe1f0b64ac2 100644 (file)
@@ -8,6 +8,7 @@ using Content.Shared.Atmos;
 using Content.Shared.Body.Components;
 using Content.Shared.Body.Events;
 using Content.Shared.Body.Prototypes;
+using Content.Shared.Chat;
 using Content.Shared.Chemistry.Components;
 using Content.Shared.Chemistry.EntitySystems;
 using Content.Shared.Chemistry.Reagent;
index 36acfa7a69268824a250fdc5fec746412dd338cb..f5b845a2d740bde1ac870d191bfff5280749ae7b 100644 (file)
@@ -1,5 +1,6 @@
 using Content.Server.Chat.Systems;
 using Content.Shared.Administration;
+using Content.Shared.Chat;
 using Robust.Shared.Console;
 using Robust.Shared.Enums;
 
index 99ca4660f0587cb78b4ae7bb2dbd4a90ca90c250..199cd6909863dfc03bee6ebd891c83aff858f707 100644 (file)
@@ -1,5 +1,6 @@
 using Content.Server.Chat.Systems;
 using Content.Shared.Administration;
+using Content.Shared.Chat;
 using Robust.Shared.Console;
 using Robust.Shared.Enums;
 
index 0556dd8036143e531667aadfae1cc20323c4f08d..d31b21abb172584e289b0ef90ce280b61bcfb2ce 100644 (file)
@@ -1,5 +1,6 @@
 using Content.Server.Chat.Systems;
 using Content.Shared.Administration;
+using Content.Shared.Chat;
 using Robust.Shared.Console;
 using Robust.Shared.Enums;
 
index 1e1b7b903408e2f85b6fa4faa8e9e0f6731c5bd0..ebb22f6314eedd267f20f6939fc7f0f744a10696 100644 (file)
@@ -1,4 +1,5 @@
 using System.Linq;
+using Content.Shared.Chat;
 using Content.Shared.Chat.Prototypes;
 using Robust.Shared.Prototypes;
 using Robust.Shared.Random;
index b073cc0e697bb2655ee6977ef859aab2b87e12ff..4ec86df54bdb460c0a16e61adb997a449546c08c 100644 (file)
@@ -68,7 +68,7 @@ public sealed partial class ChatSystem : SharedChatSystem
     public override void Initialize()
     {
         base.Initialize();
-        CacheEmotes();
+
         Subs.CVar(_configurationManager, CCVars.LoocEnabled, OnLoocEnabledChanged, true);
         Subs.CVar(_configurationManager, CCVars.DeadLoocEnabled, OnDeadLoocEnabledChanged, true);
         Subs.CVar(_configurationManager, CCVars.CritLoocEnabled, OnCritLoocEnabledChanged, true);
@@ -563,7 +563,7 @@ public sealed partial class ChatSystem : SharedChatSystem
             }
     }
 
-    private void SendEntityEmote(
+    protected override void SendEntityEmote(
         EntityUid source,
         string action,
         ChatTransmitRange range,
@@ -975,18 +975,3 @@ public enum InGameOOCChatType : byte
     Looc,
     Dead
 }
-
-/// <summary>
-///     Controls transmission of chat.
-/// </summary>
-public enum ChatTransmitRange : byte
-{
-    /// Acts normal, ghosts can hear across the map, etc.
-    Normal,
-    /// Normal but ghosts are still range-limited.
-    GhostRangeLimit,
-    /// Hidden from the chat window.
-    HideChat,
-    /// Ghosts can't hear or see it at all. Regular players can if in-range.
-    NoGhosts
-}
index 878c517d9242b6e53da8bae44c246cc6853ceb7c..4afb885c4ce6c5614f06694f0bb7c7da0084bad5 100644 (file)
@@ -1,5 +1,6 @@
 namespace Content.Server.Chat.Systems;
 
+using Content.Shared.Chat;
 using Content.Shared.Chat.Prototypes;
 using Content.Shared.Damage;
 using Robust.Shared.Prototypes;
index e51a01a1d4761fe8b0a4e4f461734a29151ef8c4..ab13548e0448212367548509d4203e01c30aed8d 100644 (file)
@@ -1,20 +1,21 @@
-using Content.Server.Popups;
-using Content.Shared.Popups;
-using Content.Shared.Mobs;
 using Content.Server.Chat;
 using Content.Server.Chat.Systems;
-using Content.Server.Clothing.Systems;
-using Content.Shared.Chat.Prototypes;
-using Robust.Shared.Random;
-using Content.Shared.Stunnable;
-using Content.Shared.Damage;
-using Robust.Shared.Prototypes;
 using Content.Server.Emoting.Systems;
+using Content.Server.Clothing.Systems;
+using Content.Server.Popups;
 using Content.Server.Speech.EntitySystems;
+using Content.Shared.Chat;
+using Content.Shared.Chat.Prototypes;
+using Content.Shared.Clumsy;
 using Content.Shared.Cluwne;
-using Robust.Shared.Audio.Systems;
+using Content.Shared.Damage;
+using Content.Shared.Mobs;
 using Content.Shared.NameModifier.EntitySystems;
-using Content.Shared.Clumsy;
+using Content.Shared.Popups;
+using Content.Shared.Stunnable;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Random;
+using Robust.Shared.Prototypes;
 
 namespace Content.Server.Cluwne;
 
@@ -75,7 +76,7 @@ public sealed class CluwneSystem : EntitySystem
             EnsureComp<AutoEmoteComponent>(ent.Owner);
             _autoEmote.AddEmote(ent.Owner, ent.Comp.AutoEmoteId);
         }
-        
+
         EnsureComp<ClumsyComponent>(ent.Owner);
 
         var transformMessage = Loc.GetString(ent.Comp.TransformMessage, ("target", ent.Owner));
index aef79f1419b6b453784e31ca1f48f25433ace517..39217a8196349098d66059397f55574a25705df8 100644 (file)
@@ -1,5 +1,6 @@
 using Content.Server.Chat.Systems;
 using Content.Server.Emoting.Components;
+using Content.Shared.Chat;
 using Content.Shared.Chat.Prototypes;
 using Content.Shared.Hands.Components;
 using Robust.Shared.Prototypes;
diff --git a/Content.Server/EntityEffects/Effects/EmoteEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/EmoteEntityEffectSystem.cs
deleted file mode 100644 (file)
index 05ab857..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-using Content.Server.Chat.Systems;
-using Content.Shared.EntityEffects;
-using Content.Shared.EntityEffects.Effects;
-
-namespace Content.Server.EntityEffects.Effects;
-
-/// <summary>
-/// Makes this entity emote.
-/// </summary>
-/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
-public sealed partial class EmoteEntityEffectSystem : EntityEffectSystem<MetaDataComponent, Emote>
-{
-    [Dependency] private readonly ChatSystem _chat = default!;
-
-    protected override void Effect(Entity<MetaDataComponent> entity, ref EntityEffectEvent<Emote> args)
-    {
-        if (args.Effect.ShowInChat)
-            _chat.TryEmoteWithChat(entity, args.Effect.EmoteId, ChatTransmitRange.GhostRangeLimit, forceEmote: args.Effect.Force);
-        else
-            _chat.TryEmoteWithoutChat(entity, args.Effect.EmoteId);
-    }
-}
index 630141d0033db7490ba8b2ab5af4fbf4623cc5d5..c634d14f2f82fd31ba2faaeb1dcc245853ca1ddc 100644 (file)
@@ -4,6 +4,7 @@ using Content.Server.Power.EntitySystems;
 using Content.Server.Telephone;
 using Content.Shared.Access.Systems;
 using Content.Shared.Audio;
+using Content.Shared.Chat;
 using Content.Shared.Chat.TypingIndicator;
 using Content.Shared.Holopad;
 using Content.Shared.IdentityManagement;
index c266037a8ff292e79613e9a19874340fe9a5d043..4a16fa19a3dff1d4f954d7729296cf84e2d4158c 100644 (file)
@@ -1,7 +1,7 @@
 using Content.Server.Administration;
 using Content.Server.Chat.Systems;
 using Content.Server.Popups;
-using Content.Server.Speech.Muting;
+using Content.Shared.Chat;
 using Content.Shared.Mobs;
 using Content.Shared.Mobs.Components;
 using Content.Shared.Mobs.Systems;
index 5f91742eb128e69677c70e0a58f80a2a319bccde..6571587b91654c6014748dd3e3a83d50b68d2857 100644 (file)
@@ -1,5 +1,5 @@
-using Content.Server.Chat.Systems;
-using Content.Shared.Chat;
+using Content.Shared.Chat;
+using Content.Server.Chat.Systems;
 using Robust.Shared.Prototypes;
 
 namespace Content.Server.Speech;
index 27679c913467e6840cc4aab91e01c9e9f886fa54..868e5b73c1e8605dc841b24656843b6ec867f5fa 100644 (file)
@@ -1,5 +1,6 @@
 using Content.Server.Chat.Systems;
 using Content.Server.Speech.Components;
+using Content.Shared.Chat;
 using Content.Shared.Chat.Prototypes;
 using Content.Shared.Speech;
 using Content.Shared.Speech.Components;
index 275140ff5b35b159702bdad0eae172a3f5788822..2c831088980cc681ae9b1a896e5bab387760bf60 100644 (file)
@@ -1,12 +1,10 @@
 using Content.Server.Actions;
 using Content.Server.Chat.Systems;
-using Content.Server.Speech.Components;
+using Content.Shared.Chat;
 using Content.Shared.Chat.Prototypes;
-using Content.Shared.Cloning.Events;
 using Content.Shared.Humanoid;
 using Content.Shared.Speech;
 using Content.Shared.Speech.Components;
-using Robust.Shared.Audio;
 using Robust.Shared.Audio.Systems;
 using Robust.Shared.Prototypes;
 using Robust.Shared.Random;
index f588e2238d6fbe4d214dd8995d75a187349e2264..9e2a0602a4395c38c62b57b4764b7b7d58acf98c 100644 (file)
@@ -1,8 +1,7 @@
-using Content.Shared.Abilities.Mime;
-using Content.Server.Chat.Systems;
 using Content.Server.Popups;
-using Content.Server.Speech.Components;
 using Content.Server.Speech.EntitySystems;
+using Content.Shared.Abilities.Mime;
+using Content.Shared.Chat;
 using Content.Shared.Chat.Prototypes;
 using Content.Shared.Puppet;
 using Content.Shared.Speech;
index 14211d967235e840a15ca7787222d76f1a8d7858..edda0859ee1b04e28fa8d16f96b7eb03656a0818 100644 (file)
@@ -1,4 +1,5 @@
 using Content.Server.Chat.Systems;
+using Content.Shared.Chat;
 using Content.Shared.Teleportation;
 using Content.Shared.Teleportation.Components;
 using Content.Shared.Teleportation.Systems;
index 49dfaf428198218f1eb91268e190fb550e65fc7b..8801e057b7ec4451271c6b86fd6d33f5606b5a71 100644 (file)
@@ -2,6 +2,7 @@ using Content.Server.Chat.Systems;
 using Content.Server.Power.Components;
 using Content.Server.Vocalization.Components;
 using Content.Shared.ActionBlocker;
+using Content.Shared.Chat;
 using Robust.Shared.Random;
 using Robust.Shared.Timing;
 
index c182d53f44c37f4b12eda11997734cdf041b6f04..b8f2bd56ed5b3bc8d01a1e3d60861e0319da4071 100644 (file)
@@ -10,6 +10,7 @@ using Content.Shared.Anomaly.Components;
 using Content.Shared.Armor;
 using Content.Shared.Bed.Sleep;
 using Content.Shared.Cloning.Events;
+using Content.Shared.Chat;
 using Content.Shared.Damage;
 using Content.Shared.Humanoid;
 using Content.Shared.Inventory;
index 4479f8b2ab92b9a35563df020c4e310f34faa36a..196bbb43bdacca6cb336b1713b4406fc759ac6ab 100644 (file)
@@ -1,9 +1,49 @@
-using Content.Shared.Chat.Prototypes;
+using Content.Shared.Chat.Prototypes;
+using Content.Shared.Inventory;
 using Robust.Shared.Prototypes;
 using Robust.Shared.Serialization;
 
 namespace Content.Shared.Chat;
 
+/// <summary>
+/// An event raised just before an emote is performed, providing systems with an opportunity to cancel the emote's performance.
+/// </summary>
+[ByRefEvent]
+public sealed class BeforeEmoteEvent(EntityUid source, EmotePrototype emote)
+    : CancellableEntityEventArgs, IInventoryRelayEvent
+{
+    public readonly EntityUid Source = source;
+    public readonly EmotePrototype Emote = emote;
+
+    /// <summary>
+    /// The equipment that is blocking emoting. Should only be non-null if the event was canceled.
+    /// </summary>
+    public EntityUid? Blocker = null;
+
+    public SlotFlags TargetSlots => SlotFlags.WITHOUT_POCKET;
+}
+
+/// <summary>
+/// Raised by the chat system when an entity made some emote.
+/// Use it to play sound, change sprite or something else.
+/// </summary>
+[ByRefEvent]
+public record struct EmoteEvent(EmotePrototype Emote)
+{
+    /// <summary>
+    /// The used emote.
+    /// </summary>
+    public EmotePrototype Emote = Emote;
+
+    /// <summary>
+    /// If this message has already been "handled" by a previous system.
+    /// </summary>
+    public bool Handled;
+}
+
+/// <summary>
+/// Sent by the client when requesting the server to play a specific emote selected from the emote radial menu.
+/// </summary>
 [Serializable, NetSerializable]
 public sealed class PlayEmoteMessage(ProtoId<EmotePrototype> protoId) : EntityEventArgs
 {
similarity index 68%
rename from Content.Server/Chat/Systems/ChatSystem.Emote.cs
rename to Content.Shared/Chat/SharedChatSystem.Emote.cs
index ee891e087064b4afc945a8a26834b457b1a32746..d92a02a9de2b05b1e528aa16becb7fe5e315cbfc 100644 (file)
@@ -1,28 +1,15 @@
 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;
 using Robust.Shared.Random;
 
-namespace Content.Server.Chat.Systems;
+namespace Content.Shared.Chat;
 
-// emotes using emote prototype
-public partial class ChatSystem
+public abstract partial class SharedChatSystem
 {
-    [Dependency] private readonly PopupSystem _popupSystem = default!;
-
     private FrozenDictionary<string, EmotePrototype> _wordEmoteDict = FrozenDictionary<string, EmotePrototype>.Empty;
 
-    protected override void OnPrototypeReload(PrototypesReloadedEventArgs obj)
-    {
-        base.OnPrototypeReload(obj);
-        if (obj.WasModified<EmotePrototype>())
-            CacheEmotes();
-    }
-
     private void CacheEmotes()
     {
         var dict = new Dictionary<string, EmotePrototype>();
@@ -47,15 +34,19 @@ public partial class ChatSystem
     }
 
     /// <summary>
-    ///     Makes selected entity to emote using <see cref="EmotePrototype"/> and sends message to chat.
+    /// Makes the selected entity emote using the given <see cref="EmotePrototype"/> and sends a 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="hideLog">Whether or not this message should appear in the adminlog window</param>
+    /// <param name="emoteId">The id of emote prototype. Should have valid <see cref="EmotePrototype.ChatMessages"/></param>
+    /// <param name="hideLog">Whether this message should appear in the adminlog window, or not.</param>
     /// <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="ignoreActionBlocker">Whether emote action blocking should be ignored or not.</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>
-    /// <returns>True if an emote was performed. False if the emote is unvailable, cancelled, etc.</returns>
+    /// <returns>True if an emote was performed. False if the emote is unavailable, cancelled, etc.</returns>
     public bool TryEmoteWithChat(
         EntityUid source,
         string emoteId,
@@ -64,24 +55,28 @@ public partial class ChatSystem
         string? nameOverride = null,
         bool ignoreActionBlocker = false,
         bool forceEmote = false
-        )
+    )
     {
-        if (!_prototypeManager.TryIndex<EmotePrototype>(emoteId, out var proto))
+        if (!_prototypeManager.Resolve<EmotePrototype>(emoteId, out var proto))
             return false;
+
         return TryEmoteWithChat(source, proto, range, hideLog: hideLog, nameOverride, ignoreActionBlocker: ignoreActionBlocker, forceEmote: forceEmote);
     }
 
     /// <summary>
-    ///     Makes selected entity to emote using <see cref="EmotePrototype"/> and sends message to chat.
+    /// Makes the selected entity emote using the given <see cref="EmotePrototype"/> and sends a 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="hideLog">Whether or not this message should appear in the adminlog window</param>
-    /// <param name="hideChat">Whether or not this message should appear in the chat window</param>
+    /// <param name="source">The entity that is speaking.</param>
+    /// <param name="emote">The emote prototype. Should have valid <see cref="EmotePrototype.ChatMessages"/>.</param>
+    /// <param name="hideLog">Whether this message should appear in the adminlog window or not.</param>
+    /// <param name="ignoreActionBlocker">Whether emote action blocking should be ignored or not.</param>
     /// <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="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>
-    /// <returns>True if an emote was performed. False if the emote is unvailable, cancelled, etc.</returns>
+    /// <returns>True if an emote was performed. False if the emote is unavailable, cancelled, etc.</returns>
     public bool TryEmoteWithChat(
         EntityUid source,
         EmotePrototype emote,
@@ -109,21 +104,21 @@ public partial class ChatSystem
     }
 
     /// <summary>
-    ///     Makes selected entity to emote using <see cref="EmotePrototype"/> without sending any messages to chat.
+    /// Makes the selected entity emote using the given <see cref="EmotePrototype"/> without sending any messages to chat.
     /// </summary>
-    /// <returns>True if an emote was performed. False if the emote is unvailable, cancelled, etc.</returns>
+    /// <returns>True if an emote was performed. False if the emote is unavailable, cancelled, etc.</returns>
     public bool TryEmoteWithoutChat(EntityUid uid, string emoteId, bool ignoreActionBlocker = false)
     {
-        if (!_prototypeManager.TryIndex<EmotePrototype>(emoteId, out var proto))
+        if (!_prototypeManager.Resolve<EmotePrototype>(emoteId, out var proto))
             return false;
 
         return TryEmoteWithoutChat(uid, proto, ignoreActionBlocker);
     }
 
     /// <summary>
-    ///     Makes selected entity to emote using <see cref="EmotePrototype"/> without sending any messages to chat.
+    /// Makes the selected entity emote using the given <see cref="EmotePrototype"/> without sending any messages to chat.
     /// </summary>
-    /// <returns>True if an emote was performed. False if the emote is unvailable, cancelled, etc.</returns>
+    /// <returns>True if an emote was performed. False if the emote is unavailable, cancelled, etc.</returns>
     public bool TryEmoteWithoutChat(EntityUid uid, EmotePrototype proto, bool ignoreActionBlocker = false)
     {
         if (!_actionBlocker.CanEmote(uid) && !ignoreActionBlocker)
@@ -133,7 +128,7 @@ public partial class ChatSystem
     }
 
     /// <summary>
-    ///     Tries to find and play relevant emote sound in emote sounds collection.
+    /// Tries to find and play the relevant emote sound in an emote sounds collection.
     /// </summary>
     /// <returns>True if emote sound was played.</returns>
     public bool TryPlayEmoteSound(EntityUid uid, EmoteSoundsPrototype? proto, EmotePrototype emote, AudioParams? audioParams = null)
@@ -142,7 +137,7 @@ public partial class ChatSystem
     }
 
     /// <summary>
-    ///     Tries to find and play relevant emote sound in emote sounds collection.
+    /// Tries to find and play the relevant emote sound in an emote sounds collection.
     /// </summary>
     /// <returns>True if emote sound was played.</returns>
     public bool TryPlayEmoteSound(EntityUid uid, EmoteSoundsPrototype? proto, string emoteId, AudioParams? audioParams = null)
@@ -167,44 +162,27 @@ public partial class ChatSystem
     /// <summary>
     /// Checks if a valid emote was typed, to play sounds and etc and invokes an event.
     /// </summary>
-    /// <param name="uid"></param>
-    /// <param name="textInput"></param>
+    /// <param name="source">The entity that is speaking</param>
+    /// <param name="textInput">Formatted emote message.</param>
     /// <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)
+    protected bool TryEmoteChatInput(EntityUid source, string textInput)
     {
         var actionTrimmedLower = TrimPunctuation(textInput.ToLower());
         if (!_wordEmoteDict.TryGetValue(actionTrimmedLower, out var emote))
             return true;
 
-        if (!AllowedToUseEmote(uid, emote))
+        if (!AllowedToUseEmote(source, emote))
             return true;
 
-        return TryInvokeEmoteEvent(uid, emote);
+        return TryInvokeEmoteEvent(source, emote);
 
-        static string TrimPunctuation(string textInput)
-        {
-            var trimEnd = textInput.Length;
-            while (trimEnd > 0 && char.IsPunctuation(textInput[trimEnd - 1]))
-            {
-                trimEnd--;
-            }
-
-            var trimStart = 0;
-            while (trimStart < trimEnd && char.IsPunctuation(textInput[trimStart]))
-            {
-                trimStart++;
-            }
-
-            return textInput[trimStart..trimEnd];
-        }
     }
     /// <summary>
-    /// Checks if we can use this emote based on the emotes whitelist, blacklist, and availibility to the entity.
+    /// Checks if we can use this emote based on the emotes whitelist, blacklist, and availability to the entity.
     /// </summary>
     /// <param name="source">The entity that is speaking</param>
     /// <param name="emote">The emote being used</param>
-    /// <returns></returns>
-    private bool AllowedToUseEmote(EntityUid source, EmotePrototype emote)
+    public bool AllowedToUseEmote(EntityUid source, EmotePrototype emote)
     {
         // If emote is in AllowedEmotes, it will bypass whitelist and blacklist
         if (TryComp<SpeechComponent>(source, out var speech) &&
@@ -214,8 +192,8 @@ public partial class ChatSystem
         }
 
         // Check the whitelist and blacklist
-        if (_whitelistSystem.IsWhitelistFail(emote.Whitelist, source) ||
-            _whitelistSystem.IsBlacklistPass(emote.Blacklist, source))
+        if (_whitelist.IsWhitelistFail(emote.Whitelist, source) ||
+            _whitelist.IsBlacklistPass(emote.Blacklist, source))
         {
             return false;
         }
@@ -244,9 +222,13 @@ public partial class ChatSystem
 
         if (beforeEv.Cancelled)
         {
+            // Chat is not predicted anyways, so no need to predict this popup either.
+            if (_net.IsClient)
+                return false;
+
             if (beforeEv.Blocker != null)
             {
-                _popupSystem.PopupEntity(
+                _popup.PopupEntity(
                     Loc.GetString(
                         "chat-system-emote-cancelled-blocked",
                         ("emote", Loc.GetString(proto.Name).ToLower()),
@@ -258,7 +240,7 @@ public partial class ChatSystem
             }
             else
             {
-                _popupSystem.PopupEntity(
+                _popup.PopupEntity(
                     Loc.GetString("chat-system-emote-cancelled-generic",
                         ("emote", Loc.GetString(proto.Name).ToLower())),
                     uid,
@@ -274,20 +256,21 @@ public partial class ChatSystem
 
         return true;
     }
-}
-
-/// <summary>
-///     Raised by chat system when entity made some emote.
-///     Use it to play sound, change sprite or something else.
-/// </summary>
-[ByRefEvent]
-public sealed class EmoteEvent : HandledEntityEventArgs
-{
-    public readonly EmotePrototype Emote;
 
-    public EmoteEvent(EmotePrototype emote)
+    private string TrimPunctuation(string textInput)
     {
-        Emote = emote;
-        Handled = false;
+        var trimEnd = textInput.Length;
+        while (trimEnd > 0 && char.IsPunctuation(textInput[trimEnd - 1]))
+        {
+            trimEnd--;
+        }
+
+        var trimStart = 0;
+        while (trimStart < trimEnd && char.IsPunctuation(textInput[trimStart]))
+        {
+            trimStart++;
+        }
+
+        return textInput[trimStart..trimEnd];
     }
 }
index d9f7f5fc57be02fe88c555a48b5565e34afbe5dc..eca84249c82951b08222827b1b7f88b4762ffb48 100644 (file)
@@ -1,15 +1,21 @@
 using System.Collections.Frozen;
 using System.Text.RegularExpressions;
+using Content.Shared.ActionBlocker;
+using Content.Shared.Chat.Prototypes;
 using Content.Shared.Popups;
 using Content.Shared.Radio;
 using Content.Shared.Speech;
+using Content.Shared.Whitelist;
 using Robust.Shared.Audio;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Network;
 using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
 using Robust.Shared.Utility;
 
 namespace Content.Shared.Chat;
 
-public abstract class SharedChatSystem : EntitySystem
+public abstract partial class SharedChatSystem : EntitySystem
 {
     public const char RadioCommonPrefix = ';';
     public const char RadioChannelPrefix = ':';
@@ -38,6 +44,11 @@ public abstract class SharedChatSystem : EntitySystem
 
     [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
     [Dependency] private readonly SharedPopupSystem _popup = default!;
+    [Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
+    [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
+    [Dependency] private readonly SharedAudioSystem _audio = default!;
+    [Dependency] private readonly IRobustRandom _random = default!;
+    [Dependency] private readonly INetManager _net = default!;
 
     /// <summary>
     /// Cache of the keycodes for faster lookup.
@@ -47,15 +58,21 @@ public abstract class SharedChatSystem : EntitySystem
     public override void Initialize()
     {
         base.Initialize();
+
         DebugTools.Assert(_prototypeManager.HasIndex(CommonChannel));
+
         SubscribeLocalEvent<PrototypesReloadedEventArgs>(OnPrototypeReload);
         CacheRadios();
+        CacheEmotes();
     }
 
     protected virtual void OnPrototypeReload(PrototypesReloadedEventArgs obj)
     {
         if (obj.WasModified<RadioChannelPrototype>())
             CacheRadios();
+
+        if (obj.WasModified<EmotePrototype>())
+            CacheEmotes();
     }
 
     private void CacheRadios()
@@ -293,4 +310,31 @@ public abstract class SharedChatSystem : EntitySystem
         tagStart += tag.Length + 2;
         return rawmsg.Substring(tagStart, tagEnd - tagStart);
     }
+
+    protected virtual void SendEntityEmote(
+        EntityUid source,
+        string action,
+        ChatTransmitRange range,
+        string? nameOverride,
+        bool hideLog = false,
+        bool checkEmote = true,
+        bool ignoreActionBlocker = false,
+        NetUserId? author = null
+        )
+    { }
+}
+
+/// <summary>
+/// Controls transmission of chat.
+/// </summary>
+public enum ChatTransmitRange : byte
+{
+    /// Acts normal, ghosts can hear across the map, etc.
+    Normal,
+    /// Normal but ghosts are still range-limited.
+    GhostRangeLimit,
+    /// Hidden from the chat window.
+    HideChat,
+    /// Ghosts can't hear or see it at all. Regular players can if in-range.
+    NoGhosts
 }
index ea3073f336e62bdd0c1c02708379547600f716b1..931395ee7e108024b041c6db5df29d978bab4a90 100644 (file)
@@ -1,27 +1,6 @@
-using Content.Shared.Chat.Prototypes;
-using Content.Shared.Inventory;
-
-namespace Content.Shared.Emoting;
+namespace Content.Shared.Emoting;
 
 public sealed class EmoteAttemptEvent(EntityUid uid) : CancellableEntityEventArgs
 {
     public EntityUid Uid { get; } = uid;
 }
-
-/// <summary>
-/// An event raised just before an emote is performed, providing systems with an opportunity to cancel the emote's performance.
-/// </summary>
-[ByRefEvent]
-public sealed class BeforeEmoteEvent(EntityUid source, EmotePrototype emote)
-    : CancellableEntityEventArgs, IInventoryRelayEvent
-{
-    public readonly EntityUid Source = source;
-    public readonly EmotePrototype Emote = emote;
-
-    /// <summary>
-    ///     The equipment that is blocking emoting. Should only be non-null if the event was canceled.
-    /// </summary>
-    public EntityUid? Blocker = null;
-
-    public SlotFlags TargetSlots => SlotFlags.WITHOUT_POCKET;
-}
similarity index 63%
rename from Content.Shared/EntityEffects/Effects/EmoteEntityEffect.cs
rename to Content.Shared/EntityEffects/Effects/EmoteEntityEffectSystem.cs
index 1fcfc703ced03f75bf82317e7fedf78674586004..d8474e4163151c5292a5f93bd05090d0080a5500 100644 (file)
@@ -1,8 +1,26 @@
-using Content.Shared.Chat.Prototypes;
+using Content.Shared.Chat;
+using Content.Shared.Chat.Prototypes;
 using Robust.Shared.Prototypes;
 
 namespace Content.Shared.EntityEffects.Effects;
 
+/// <summary>
+/// Makes this entity emote.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class EmoteEntityEffectSystem : EntityEffectSystem<MetaDataComponent, Emote>
+{
+    [Dependency] private readonly SharedChatSystem _chat = default!;
+
+    protected override void Effect(Entity<MetaDataComponent> entity, ref EntityEffectEvent<Emote> args)
+    {
+        if (args.Effect.ShowInChat)
+            _chat.TryEmoteWithChat(entity, args.Effect.EmoteId, ChatTransmitRange.GhostRangeLimit, forceEmote: args.Effect.Force);
+        else
+            _chat.TryEmoteWithoutChat(entity, args.Effect.EmoteId);
+    }
+}
+
 /// <inheritdoc cref="EntityEffect"/>
 public sealed partial class Emote : EntityEffectBase<Emote>
 {
index 5109930a2ddb0b8173f91c1586708fd3bef6045f..392234297cb90b132d6e58acce7eee0676b0b298 100644 (file)
@@ -8,7 +8,6 @@ using Content.Shared.Contraband;
 using Content.Shared.Damage;
 using Content.Shared.Damage.Events;
 using Content.Shared.Electrocution;
-using Content.Shared.Emoting;
 using Content.Shared.Explosion;
 using Content.Shared.Eye.Blinding.Systems;
 using Content.Shared.Flash;
similarity index 74%
rename from Content.Server/Speech/Components/EmoteBlockerComponent.cs
rename to Content.Shared/Speech/Components/EmoteBlockerComponent.cs
index 603fd1b2588b9ee34a3bd0f5957d6d129b80851c..95263a2595f97fb91db9ddda96676832816ab8ef 100644 (file)
@@ -1,24 +1,25 @@
 using Content.Shared.Chat.Prototypes;
+using Robust.Shared.GameStates;
 using Robust.Shared.Prototypes;
 
-namespace Content.Server.Speech.Components;
+namespace Content.Shared.Speech.Components;
 
 /// <summary>
 /// Suppresses emotes with the given categories or ID.
 /// Additionally, if the Scream Emote would be blocked, also blocks the Scream Action.
 /// </summary>
-[RegisterComponent]
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
 public sealed partial class EmoteBlockerComponent : Component
 {
     /// <summary>
     /// Which categories of emotes are blocked by this component.
     /// </summary>
-    [DataField]
+    [DataField, AutoNetworkedField]
     public HashSet<EmoteCategory> BlocksCategories = [];
 
     /// <summary>
     /// IDs of which specific emotes are blocked by this component.
     /// </summary>
-    [DataField]
+    [DataField, AutoNetworkedField]
     public HashSet<ProtoId<EmotePrototype>> BlocksEmotes = [];
 }
similarity index 90%
rename from Content.Server/Speech/EntitySystems/EmoteBlockerSystem.cs
rename to Content.Shared/Speech/EntitySystems/EmoteBlockerSystem.cs
index 3599268359500ba9ddc71305c66d7e44caf2279b..e2c9728fbc5f23c6931514fcb37215e294c4b3dc 100644 (file)
@@ -1,8 +1,8 @@
-using Content.Server.Speech.Components;
-using Content.Shared.Emoting;
+using Content.Shared.Chat;
 using Content.Shared.Inventory;
+using Content.Shared.Speech.Components;
 
-namespace Content.Server.Speech.EntitySystems;
+namespace Content.Shared.Speech.EntitySystems;
 
 public sealed class EmoteBlockerSystem : EntitySystem
 {