From fe2145d3b967ad3b123313e82b3f0a6ef5c5fecf Mon Sep 17 00:00:00 2001 From: beck-thompson <107373427+beck-thompson@users.noreply.github.com> Date: Thu, 26 Sep 2024 09:55:59 -0700 Subject: [PATCH] Voice Mask refactor (#30798) * First commit * Added base.Initialize() * Voice wire fix (Electricty name) * Various minor cleanups * Localized default voice mask name * Added VoiceOverride stuff * Removed unused stuff * Typo * Better localized stuff * Typo / spelling stuff / comments * Blessed --- .../VoiceMask/VoiceMaskBoundUserInterface.cs | 1 + .../VoiceMaskNameChangeWindow.xaml.cs | 4 +- Content.Server/Chat/Systems/ChatSystem.cs | 24 +--- .../Radio/EntitySystems/RadioDeviceSystem.cs | 3 +- .../Radio/EntitySystems/RadioSystem.cs | 16 +-- .../Speech/Components/ListenWireAction.cs | 65 +++------ .../Components/VoiceOverrideComponent.cs | 35 +++++ .../EntitySystems/VoiceOverrideSystem.cs | 22 +++ .../SurveillanceCameraSpeakerSystem.cs | 3 +- .../VoiceMask/VoiceMaskComponent.cs | 39 ++++-- .../VoiceMask/VoiceMaskSystem.Equip.cs | 49 ------- Content.Server/VoiceMask/VoiceMaskSystem.cs | 128 ++++++++---------- .../VoiceMask/VoiceMaskerComponent.cs | 20 --- Content.Shared/Chat/SharedChatEvents.cs | 24 ++++ .../Inventory/InventorySystem.Relay.cs | 2 + Resources/Locale/en-US/voice-mask.ftl | 2 + .../Entities/Clothing/Masks/specific.yml | 6 +- .../Prototypes/Entities/Mobs/Species/base.yml | 2 - .../Entities/Mobs/Species/slime.yml | 2 - .../Structures/Wallmounts/intercom.yml | 3 + 20 files changed, 214 insertions(+), 236 deletions(-) create mode 100644 Content.Server/Speech/Components/VoiceOverrideComponent.cs create mode 100644 Content.Server/Speech/EntitySystems/VoiceOverrideSystem.cs delete mode 100644 Content.Server/VoiceMask/VoiceMaskSystem.Equip.cs delete mode 100644 Content.Server/VoiceMask/VoiceMaskerComponent.cs create mode 100644 Content.Shared/Chat/SharedChatEvents.cs diff --git a/Content.Client/VoiceMask/VoiceMaskBoundUserInterface.cs b/Content.Client/VoiceMask/VoiceMaskBoundUserInterface.cs index 891804674d..e76ca1cf8f 100644 --- a/Content.Client/VoiceMask/VoiceMaskBoundUserInterface.cs +++ b/Content.Client/VoiceMask/VoiceMaskBoundUserInterface.cs @@ -22,6 +22,7 @@ public sealed class VoiceMaskBoundUserInterface : BoundUserInterface _window = this.CreateWindow(); _window.ReloadVerbs(_protomanager); + _window.AddVerbs(); _window.OnNameChange += OnNameSelected; _window.OnVerbChange += verb => SendMessage(new VoiceMaskChangeVerbMessage(verb)); diff --git a/Content.Client/VoiceMask/VoiceMaskNameChangeWindow.xaml.cs b/Content.Client/VoiceMask/VoiceMaskNameChangeWindow.xaml.cs index 0dc41f807a..7ca4dd4b95 100644 --- a/Content.Client/VoiceMask/VoiceMaskNameChangeWindow.xaml.cs +++ b/Content.Client/VoiceMask/VoiceMaskNameChangeWindow.xaml.cs @@ -31,8 +31,6 @@ public sealed partial class VoiceMaskNameChangeWindow : FancyWindow OnVerbChange?.Invoke((string?) args.Button.GetItemMetadata(args.Id)); SpeechVerbSelector.SelectId(args.Id); }; - - AddVerbs(); } public void ReloadVerbs(IPrototypeManager proto) @@ -44,7 +42,7 @@ public sealed partial class VoiceMaskNameChangeWindow : FancyWindow _verbs.Sort((a, b) => a.Item1.CompareTo(b.Item1)); } - private void AddVerbs() + public void AddVerbs() { SpeechVerbSelector.Clear(); diff --git a/Content.Server/Chat/Systems/ChatSystem.cs b/Content.Server/Chat/Systems/ChatSystem.cs index fb84c785d2..24937ea4b9 100644 --- a/Content.Server/Chat/Systems/ChatSystem.cs +++ b/Content.Server/Chat/Systems/ChatSystem.cs @@ -4,7 +4,6 @@ using System.Text; using Content.Server.Administration.Logs; using Content.Server.Administration.Managers; using Content.Server.Chat.Managers; -using Content.Server.Examine; using Content.Server.GameTicking; using Content.Server.Players.RateLimiting; using Content.Server.Speech.Components; @@ -18,13 +17,10 @@ using Content.Shared.Chat; using Content.Shared.Database; using Content.Shared.Examine; using Content.Shared.Ghost; -using Content.Shared.Humanoid; using Content.Shared.IdentityManagement; -using Content.Shared.Interaction; using Content.Shared.Mobs.Systems; using Content.Shared.Players; using Content.Shared.Radio; -using Content.Shared.Speech; using Content.Shared.Whitelist; using Robust.Server.Player; using Robust.Shared.Audio; @@ -440,9 +436,9 @@ public sealed partial class ChatSystem : SharedChatSystem { var nameEv = new TransformSpeakerNameEvent(source, Name(source)); RaiseLocalEvent(source, nameEv); - name = nameEv.Name; + name = nameEv.VoiceName; // Check for a speech verb override - if (nameEv.SpeechVerb != null && _prototypeManager.TryIndex(nameEv.SpeechVerb, out var proto)) + if (nameEv.SpeechVerb != null && _prototypeManager.TryIndex(nameEv.SpeechVerb, out var proto)) speech = proto; } @@ -514,7 +510,7 @@ public sealed partial class ChatSystem : SharedChatSystem { var nameEv = new TransformSpeakerNameEvent(source, Name(source)); RaiseLocalEvent(source, nameEv); - name = nameEv.Name; + name = nameEv.VoiceName; } name = FormattedMessage.EscapeText(name); @@ -911,20 +907,6 @@ public record ExpandICChatRecipientsEvent(EntityUid Source, float VoiceRange, Di { } -public sealed class TransformSpeakerNameEvent : EntityEventArgs -{ - public EntityUid Sender; - public string Name; - public string? SpeechVerb; - - public TransformSpeakerNameEvent(EntityUid sender, string name, string? speechVerb = null) - { - Sender = sender; - Name = name; - SpeechVerb = speechVerb; - } -} - /// /// Raised broadcast in order to transform speech.transmit /// diff --git a/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs b/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs index c977fbc048..c8867744a4 100644 --- a/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs +++ b/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs @@ -11,6 +11,7 @@ using Content.Shared.Examine; using Content.Shared.Interaction; using Content.Shared.Power; using Content.Shared.Radio; +using Content.Shared.Chat; using Content.Shared.Radio.Components; using Robust.Shared.Prototypes; @@ -213,7 +214,7 @@ public sealed class RadioDeviceSystem : EntitySystem var name = Loc.GetString("speech-name-relay", ("speaker", Name(uid)), - ("originalName", nameEv.Name)); + ("originalName", nameEv.VoiceName)); // log to chat so people can identity the speaker/source, but avoid clogging ghost chat if there are many radios _chat.TrySendInGameICMessage(uid, args.Message, InGameICChatType.Whisper, ChatTransmitRange.GhostRangeLimit, nameOverride: name, checkRadioPrefix: false); diff --git a/Content.Server/Radio/EntitySystems/RadioSystem.cs b/Content.Server/Radio/EntitySystems/RadioSystem.cs index 3ad101e62d..bdc368fa93 100644 --- a/Content.Server/Radio/EntitySystems/RadioSystem.cs +++ b/Content.Server/Radio/EntitySystems/RadioSystem.cs @@ -2,7 +2,6 @@ using Content.Server.Administration.Logs; using Content.Server.Chat.Systems; using Content.Server.Power.Components; using Content.Server.Radio.Components; -using Content.Server.VoiceMask; using Content.Shared.Chat; using Content.Shared.Database; using Content.Shared.Radio; @@ -78,20 +77,15 @@ public sealed class RadioSystem : EntitySystem if (!_messages.Add(message)) return; - var name = TryComp(messageSource, out VoiceMaskComponent? mask) && mask.Enabled - ? mask.VoiceName - : MetaData(messageSource).EntityName; + var evt = new TransformSpeakerNameEvent(messageSource, MetaData(messageSource).EntityName); + RaiseLocalEvent(messageSource, evt); + var name = evt.VoiceName; name = FormattedMessage.EscapeText(name); SpeechVerbPrototype speech; - if (mask != null - && mask.Enabled - && mask.SpeechVerb != null - && _prototype.TryIndex(mask.SpeechVerb, out var proto)) - { - speech = proto; - } + if (evt.SpeechVerb != null && _prototype.TryIndex(evt.SpeechVerb, out var evntProto)) + speech = evntProto; else speech = _chat.GetSpeechVerb(messageSource, message); diff --git a/Content.Server/Speech/Components/ListenWireAction.cs b/Content.Server/Speech/Components/ListenWireAction.cs index 68d2201862..b8b1c19e84 100644 --- a/Content.Server/Speech/Components/ListenWireAction.cs +++ b/Content.Server/Speech/Components/ListenWireAction.cs @@ -1,9 +1,12 @@ -using Content.Server.Speech.Components; using Content.Server.Chat.Systems; -using Content.Server.VoiceMask; +using Content.Shared.Radio; +using Content.Server.Radio.Components; +using Content.Server.Radio.EntitySystems; +using Content.Server.Speech.Components; using Content.Server.Wires; -using Content.Shared.Speech; using Content.Shared.Wires; +using Content.Shared.Speech; +using Robust.Shared.Prototypes; namespace Content.Server.Speech; @@ -11,17 +14,13 @@ public sealed partial class ListenWireAction : BaseToggleWireAction { private WiresSystem _wires = default!; private ChatSystem _chat = default!; + private RadioSystem _radio = default!; + private IPrototypeManager _protoMan = default!; /// /// Length of the gibberish string sent when pulsing the wire /// private const int NoiseLength = 16; - - /// - /// Identifier of the SpeechVerbPrototype to use when pulsing the wire - /// - [ValidatePrototypeId] - private const string SpeechVerb = "Electricity"; public override Color Color { get; set; } = Color.Green; public override string Name { get; set; } = "wire-name-listen"; @@ -37,6 +36,8 @@ public sealed partial class ListenWireAction : BaseToggleWireAction _wires = EntityManager.System(); _chat = EntityManager.System(); + _radio = EntityManager.System(); + _protoMan = IoCManager.Resolve(); } public override StatusLightState? GetLightState(Wire wire) { @@ -72,46 +73,20 @@ public sealed partial class ListenWireAction : BaseToggleWireAction if (!GetValue(wire.Owner) || !IsPowered(wire.Owner)) return; - // We have to use a valid euid in the ListenEvent. The user seems - // like a sensible choice, but we need to mask their name. - - // Save the user's existing voicemask if they have one - var oldEnabled = true; - var oldVoiceName = Loc.GetString("wire-listen-pulse-error-name"); - string? oldSpeechVerb = null; - if (EntityManager.TryGetComponent(user, out var oldMask)) - { - oldEnabled = oldMask.Enabled; - oldVoiceName = oldMask.VoiceName; - oldSpeechVerb = oldMask.SpeechVerb; - } - - // Give the user a temporary voicemask component - var mask = EntityManager.EnsureComponent(user); - mask.Enabled = true; - mask.VoiceName = Loc.GetString("wire-listen-pulse-identifier"); - mask.SpeechVerb = SpeechVerb; - var chars = Loc.GetString("wire-listen-pulse-characters").ToCharArray(); var noiseMsg = _chat.BuildGibberishString(chars, NoiseLength); - var attemptEv = new ListenAttemptEvent(wire.Owner); - EntityManager.EventBus.RaiseLocalEvent(wire.Owner, attemptEv); - if (!attemptEv.Cancelled) - { - var ev = new ListenEvent(noiseMsg, user); - EntityManager.EventBus.RaiseLocalEvent(wire.Owner, ev); - } + if (!EntityManager.TryGetComponent(wire.Owner, out var radioMicroPhoneComp)) + return; - // Remove the voicemask component, or set it back to what it was before - if (oldMask == null) - EntityManager.RemoveComponent(user, mask); - else - { - mask.Enabled = oldEnabled; - mask.VoiceName = oldVoiceName; - mask.SpeechVerb = oldSpeechVerb; - } + if (!EntityManager.TryGetComponent(wire.Owner, out var voiceOverrideComp)) + return; + + // The reason for the override is to make the voice sound like its coming from electrity rather than the intercom. + voiceOverrideComp.NameOverride = Loc.GetString("wire-listen-pulse-identifier"); + voiceOverrideComp.Enabled = true; + _radio.SendRadioMessage(wire.Owner, noiseMsg, _protoMan.Index(radioMicroPhoneComp.BroadcastChannel), wire.Owner); + voiceOverrideComp.Enabled = false; base.Pulse(user, wire); } diff --git a/Content.Server/Speech/Components/VoiceOverrideComponent.cs b/Content.Server/Speech/Components/VoiceOverrideComponent.cs new file mode 100644 index 0000000000..349babc948 --- /dev/null +++ b/Content.Server/Speech/Components/VoiceOverrideComponent.cs @@ -0,0 +1,35 @@ +using Content.Shared.Speech; +using Robust.Shared.Prototypes; + +namespace Content.Server.Speech.Components; + +/// +/// Will change the voice of the entity that has the component (e.g radio and speech). +/// +/// +/// Before using this component, please take a look at the the TransformSpeakerNameEvent (and the inventory relay version). +/// Depending on what you're doing, it could be a better choice! +/// +[RegisterComponent] +public sealed partial class VoiceOverrideComponent : Component +{ + /// + /// The name that will be used instead of an entities default one. + /// Uses the localized version of the string and if null wont do anything. + /// + [DataField] + public string? NameOverride = null; + + /// + /// The verb that will be used insteand of an entities default one. + /// If null, the defaut will be used. + /// + [DataField] + public ProtoId? SpeechVerbOverride = null; + + /// + /// If true, the override values (if they are not null) will be applied. + /// + [DataField] + public bool Enabled = true; +} diff --git a/Content.Server/Speech/EntitySystems/VoiceOverrideSystem.cs b/Content.Server/Speech/EntitySystems/VoiceOverrideSystem.cs new file mode 100644 index 0000000000..daaad099d6 --- /dev/null +++ b/Content.Server/Speech/EntitySystems/VoiceOverrideSystem.cs @@ -0,0 +1,22 @@ +using Content.Shared.Chat; +using Content.Server.Speech.Components; + +namespace Content.Server.Speech.EntitySystems; + +public sealed partial class VoiceOverrideSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnTransformSpeakerName); + } + + private void OnTransformSpeakerName(Entity entity, ref TransformSpeakerNameEvent args) + { + if (!entity.Comp.Enabled) + return; + + args.VoiceName = entity.Comp.NameOverride ?? args.VoiceName; + args.SpeechVerb = entity.Comp.SpeechVerbOverride ?? args.SpeechVerb; + } +} diff --git a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSpeakerSystem.cs b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSpeakerSystem.cs index 0e694a801e..581ac19719 100644 --- a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSpeakerSystem.cs +++ b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSpeakerSystem.cs @@ -1,6 +1,7 @@ using Content.Server.Chat.Systems; using Content.Server.Speech; using Content.Shared.Speech; +using Content.Shared.Chat; using Robust.Shared.Audio.Systems; using Robust.Shared.Timing; @@ -48,7 +49,7 @@ public sealed class SurveillanceCameraSpeakerSystem : EntitySystem RaiseLocalEvent(args.Speaker, nameEv); var name = Loc.GetString("speech-name-relay", ("speaker", Name(uid)), - ("originalName", nameEv.Name)); + ("originalName", nameEv.VoiceName)); // log to chat so people can identity the speaker/source, but avoid clogging ghost chat if there are many radios _chatSystem.TrySendInGameICMessage(uid, args.Message, InGameICChatType.Speak, ChatTransmitRange.GhostRangeLimit, nameOverride: name); diff --git a/Content.Server/VoiceMask/VoiceMaskComponent.cs b/Content.Server/VoiceMask/VoiceMaskComponent.cs index d0c9200300..d3116f94db 100644 --- a/Content.Server/VoiceMask/VoiceMaskComponent.cs +++ b/Content.Server/VoiceMask/VoiceMaskComponent.cs @@ -3,21 +3,38 @@ using Robust.Shared.Prototypes; namespace Content.Server.VoiceMask; +/// +/// This component is for voice mask items! Adding this component to clothing will give the the voice mask UI +/// and allow the wearer to change their voice and verb at will. +/// +/// +/// DO NOT use this if you do not want the interface. +/// The VoiceOverrideSystem is probably what your looking for (Or you might have to make something similar)! +/// [RegisterComponent] public sealed partial class VoiceMaskComponent : Component { - [DataField] - [ViewVariables(VVAccess.ReadWrite)] - public bool Enabled = true; - - [DataField] - [ViewVariables(VVAccess.ReadWrite)] - public string VoiceName = "Unknown"; - /// - /// If EnableSpeechVerbModification is true, overrides the speech verb used when this entity speaks. + /// The name that will override an entities default name. If null, it will use the default override. /// [DataField] - [ViewVariables(VVAccess.ReadWrite)] - public ProtoId? SpeechVerb; + public string? VoiceMaskName = null; + + /// + /// The speech verb that will override an entities default one. If null, it will use the entities default verb. + /// + [DataField] + public ProtoId? VoiceMaskSpeechVerb; + + /// + /// The action that gets displayed when the voice mask is equipped. + /// + [DataField] + public EntProtoId Action = "ActionChangeVoiceMask"; + + /// + /// Reference to the action. + /// + [DataField] + public EntityUid? ActionEntity; } diff --git a/Content.Server/VoiceMask/VoiceMaskSystem.Equip.cs b/Content.Server/VoiceMask/VoiceMaskSystem.Equip.cs deleted file mode 100644 index b97c47ceef..0000000000 --- a/Content.Server/VoiceMask/VoiceMaskSystem.Equip.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Content.Server.Actions; -using Content.Shared.Clothing; -using Content.Shared.Inventory; - -namespace Content.Server.VoiceMask; - -// This partial deals with equipment, i.e., the syndicate voice mask. -public sealed partial class VoiceMaskSystem -{ - [Dependency] private readonly InventorySystem _inventory = default!; - [Dependency] private readonly ActionsSystem _actions = default!; - - private const string MaskSlot = "mask"; - - private void OnEquip(EntityUid uid, VoiceMaskerComponent component, ClothingGotEquippedEvent args) - { - var user = args.Wearer; - var comp = EnsureComp(user); - comp.VoiceName = component.LastSetName; - comp.SpeechVerb = component.LastSpeechVerb; - - _actions.AddAction(user, ref component.ActionEntity, component.Action, uid); - } - - private void OnUnequip(EntityUid uid, VoiceMaskerComponent compnent, ClothingGotUnequippedEvent args) - { - RemComp(args.Wearer); - } - - private VoiceMaskerComponent? TryGetMask(EntityUid user) - { - if (!HasComp(user) || !_inventory.TryGetSlotEntity(user, MaskSlot, out var maskEntity)) - return null; - - return CompOrNull(maskEntity); - } - - private void TrySetLastKnownName(EntityUid user, string name) - { - if (TryGetMask(user) is {} comp) - comp.LastSetName = name; - } - - private void TrySetLastSpeechVerb(EntityUid user, string? verb) - { - if (TryGetMask(user) is {} comp) - comp.LastSpeechVerb = verb; - } -} diff --git a/Content.Server/VoiceMask/VoiceMaskSystem.cs b/Content.Server/VoiceMask/VoiceMaskSystem.cs index df972b9a14..47ea98d2cc 100644 --- a/Content.Server/VoiceMask/VoiceMaskSystem.cs +++ b/Content.Server/VoiceMask/VoiceMaskSystem.cs @@ -1,113 +1,103 @@ -using Content.Server.Administration.Logs; -using Content.Server.Chat.Systems; -using Content.Server.Popups; +using Content.Shared.Actions; +using Content.Shared.Administration.Logs; +using Content.Shared.Chat; using Content.Shared.Clothing; using Content.Shared.Database; +using Content.Shared.Inventory; using Content.Shared.Popups; using Content.Shared.Preferences; using Content.Shared.Speech; using Content.Shared.VoiceMask; -using Robust.Server.GameObjects; -using Robust.Shared.Player; using Robust.Shared.Prototypes; namespace Content.Server.VoiceMask; public sealed partial class VoiceMaskSystem : EntitySystem { - [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; - [Dependency] private readonly PopupSystem _popupSystem = default!; - [Dependency] private readonly IAdminLogManager _adminLogger = default!; + [Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!; + [Dependency] private readonly SharedPopupSystem _popupSystem = default!; + [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; public override void Initialize() { - SubscribeLocalEvent(OnSpeakerNameTransform); + base.Initialize(); + SubscribeLocalEvent>(OnTransformSpeakerName); SubscribeLocalEvent(OnChangeName); SubscribeLocalEvent(OnChangeVerb); - SubscribeLocalEvent(OnMaskToggled); - SubscribeLocalEvent(OnEquip); - SubscribeLocalEvent(OnUnequip); - SubscribeLocalEvent(OnSetName); - // SubscribeLocalEvent>(GetVerbs); + SubscribeLocalEvent(OnEquip); + SubscribeLocalEvent(OpenUI); } - private void OnSetName(VoiceMaskSetNameEvent ev) + private void OnTransformSpeakerName(Entity entity, ref InventoryRelayedEvent args) { - OpenUI(ev.Performer); + args.Args.VoiceName = GetCurrentVoiceName(entity); + args.Args.SpeechVerb = entity.Comp.VoiceMaskSpeechVerb ?? args.Args.SpeechVerb; } - private void OnChangeName(EntityUid uid, VoiceMaskComponent component, VoiceMaskChangeNameMessage message) + #region User inputs from UI + private void OnChangeVerb(Entity entity, ref VoiceMaskChangeVerbMessage msg) + { + if (msg.Verb is { } id && !_proto.HasIndex(id)) + return; + + entity.Comp.VoiceMaskSpeechVerb = msg.Verb; + // verb is only important to metagamers so no need to log as opposed to name + + _popupSystem.PopupEntity(Loc.GetString("voice-mask-popup-success"), entity, msg.Actor); + + UpdateUI(entity); + } + + private void OnChangeName(Entity entity, ref VoiceMaskChangeNameMessage message) { if (message.Name.Length > HumanoidCharacterProfile.MaxNameLength || message.Name.Length <= 0) { - _popupSystem.PopupEntity(Loc.GetString("voice-mask-popup-failure"), uid, message.Actor, PopupType.SmallCaution); + _popupSystem.PopupEntity(Loc.GetString("voice-mask-popup-failure"), entity, message.Actor, PopupType.SmallCaution); return; } - component.VoiceName = message.Name; - _adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(message.Actor):player} set voice of {ToPrettyString(uid):mask}: {component.VoiceName}"); + entity.Comp.VoiceMaskName = message.Name; + _adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(message.Actor):player} set voice of {ToPrettyString(entity):mask}: {entity.Comp.VoiceMaskName}"); - _popupSystem.PopupEntity(Loc.GetString("voice-mask-popup-success"), uid, message.Actor); + _popupSystem.PopupEntity(Loc.GetString("voice-mask-popup-success"), entity, message.Actor); - TrySetLastKnownName(uid, message.Name); + UpdateUI(entity); + } + #endregion - UpdateUI(uid, component); + #region UI + private void OnEquip(EntityUid uid, VoiceMaskComponent component, ClothingGotEquippedEvent args) + { + _actions.AddAction(args.Wearer, ref component.ActionEntity, component.Action, uid); } - private void OnChangeVerb(Entity ent, ref VoiceMaskChangeVerbMessage msg) + private void OpenUI(VoiceMaskSetNameEvent ev) { - if (msg.Verb is {} id && !_proto.HasIndex(id)) + var maskEntity = ev.Action.Comp.Container; + + if (!TryComp(maskEntity, out var voiceMaskComp)) return; - ent.Comp.SpeechVerb = msg.Verb; - // verb is only important to metagamers so no need to log as opposed to name - - _popupSystem.PopupEntity(Loc.GetString("voice-mask-popup-success"), ent, msg.Actor); - - TrySetLastSpeechVerb(ent, msg.Verb); - - UpdateUI(ent, ent.Comp); - } - - private void OnSpeakerNameTransform(EntityUid uid, VoiceMaskComponent component, TransformSpeakerNameEvent args) - { - if (component.Enabled) - { - /* - args.Name = _idCard.TryGetIdCard(uid, out var card) && !string.IsNullOrEmpty(card.FullName) - ? card.FullName - : Loc.GetString("voice-mask-unknown"); - */ - - args.Name = component.VoiceName; - if (component.SpeechVerb != null) - args.SpeechVerb = component.SpeechVerb; - } - } - - private void OnMaskToggled(Entity ent, ref WearerMaskToggledEvent args) - { - ent.Comp.Enabled = !args.IsToggled; - } - - private void OpenUI(EntityUid player) - { - if (!_uiSystem.HasUi(player, VoiceMaskUIKey.Key)) + if (!_uiSystem.HasUi(maskEntity.Value, VoiceMaskUIKey.Key)) return; - _uiSystem.OpenUi(player, VoiceMaskUIKey.Key, player); - UpdateUI(player); + _uiSystem.OpenUi(maskEntity.Value, VoiceMaskUIKey.Key, ev.Performer); + UpdateUI((maskEntity.Value, voiceMaskComp)); } - private void UpdateUI(EntityUid owner, VoiceMaskComponent? component = null) + private void UpdateUI(Entity entity) { - if (!Resolve(owner, ref component)) - { - return; - } - - if (_uiSystem.HasUi(owner, VoiceMaskUIKey.Key)) - _uiSystem.SetUiState(owner, VoiceMaskUIKey.Key, new VoiceMaskBuiState(component.VoiceName, component.SpeechVerb)); + if (_uiSystem.HasUi(entity, VoiceMaskUIKey.Key)) + _uiSystem.SetUiState(entity.Owner, VoiceMaskUIKey.Key, new VoiceMaskBuiState(GetCurrentVoiceName(entity), entity.Comp.VoiceMaskSpeechVerb)); } + #endregion + + #region Helper functions + private string GetCurrentVoiceName(Entity entity) + { + return entity.Comp.VoiceMaskName ?? Loc.GetString("voice-mask-default-name-override"); + } + #endregion } diff --git a/Content.Server/VoiceMask/VoiceMaskerComponent.cs b/Content.Server/VoiceMask/VoiceMaskerComponent.cs deleted file mode 100644 index afea5877df..0000000000 --- a/Content.Server/VoiceMask/VoiceMaskerComponent.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Content.Shared.Speech; -using Robust.Shared.Prototypes; - -namespace Content.Server.VoiceMask; - -[RegisterComponent] -public sealed partial class VoiceMaskerComponent : Component -{ - [DataField] - public string LastSetName = "Unknown"; - - [DataField] - public ProtoId? LastSpeechVerb; - - [DataField] - public EntProtoId Action = "ActionChangeVoiceMask"; - - [DataField] - public EntityUid? ActionEntity; -} diff --git a/Content.Shared/Chat/SharedChatEvents.cs b/Content.Shared/Chat/SharedChatEvents.cs new file mode 100644 index 0000000000..c187fd59a8 --- /dev/null +++ b/Content.Shared/Chat/SharedChatEvents.cs @@ -0,0 +1,24 @@ +using Content.Shared.Speech; +using Robust.Shared.Prototypes; +using Content.Shared.Inventory; + +namespace Content.Shared.Chat; + +/// +/// This event should be sent everytime an entity talks (Radio, local chat, etc...). +/// The event is sent to both the entity itself, and all clothing (For stuff like voice masks). +/// +public sealed class TransformSpeakerNameEvent : EntityEventArgs, IInventoryRelayEvent +{ + public SlotFlags TargetSlots { get; } = SlotFlags.WITHOUT_POCKET; + public EntityUid Sender; + public string VoiceName; + public ProtoId? SpeechVerb; + + public TransformSpeakerNameEvent(EntityUid sender, string name) + { + Sender = sender; + VoiceName = name; + SpeechVerb = null; + } +} diff --git a/Content.Shared/Inventory/InventorySystem.Relay.cs b/Content.Shared/Inventory/InventorySystem.Relay.cs index fc300b24af..c910a9ae77 100644 --- a/Content.Shared/Inventory/InventorySystem.Relay.cs +++ b/Content.Shared/Inventory/InventorySystem.Relay.cs @@ -15,6 +15,7 @@ using Content.Shared.Slippery; using Content.Shared.Strip.Components; using Content.Shared.Temperature; using Content.Shared.Verbs; +using Content.Shared.Chat; namespace Content.Shared.Inventory; @@ -31,6 +32,7 @@ public partial class InventorySystem SubscribeLocalEvent(RelayInventoryEvent); SubscribeLocalEvent(RelayInventoryEvent); SubscribeLocalEvent(RelayInventoryEvent); + SubscribeLocalEvent(RelayInventoryEvent); // by-ref events SubscribeLocalEvent(RefRelayInventoryEvent); diff --git a/Resources/Locale/en-US/voice-mask.ftl b/Resources/Locale/en-US/voice-mask.ftl index 2f5acefee4..f3740cdafb 100644 --- a/Resources/Locale/en-US/voice-mask.ftl +++ b/Resources/Locale/en-US/voice-mask.ftl @@ -1,3 +1,5 @@ +voice-mask-default-name-override = Unknown + voice-mask-name-change-window = Voice Mask Name Change voice-mask-name-change-info = Type in the name you want to mimic. voice-mask-name-change-speech-style = Speech style diff --git a/Resources/Prototypes/Entities/Clothing/Masks/specific.yml b/Resources/Prototypes/Entities/Clothing/Masks/specific.yml index 64a1adcebd..90c648c9d8 100644 --- a/Resources/Prototypes/Entities/Clothing/Masks/specific.yml +++ b/Resources/Prototypes/Entities/Clothing/Masks/specific.yml @@ -29,10 +29,14 @@ id: ClothingMaskGasVoiceChameleon suffix: Voice Mask, Chameleon components: - - type: VoiceMasker + - type: VoiceMask - type: HideLayerClothing slots: - Snout + - type: UserInterface + interfaces: + enum.VoiceMaskUIKey.Key: + type: VoiceMaskBoundUserInterface - type: entity parent: ClothingMaskBase diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml index 5cebd2cf85..100a0ced84 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/base.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml @@ -183,8 +183,6 @@ - type: Stripping - type: UserInterface interfaces: - enum.VoiceMaskUIKey.Key: - type: VoiceMaskBoundUserInterface enum.HumanoidMarkingModifierKey.Key: type: HumanoidMarkingModifierBoundUserInterface enum.StrippingUiKey.Key: diff --git a/Resources/Prototypes/Entities/Mobs/Species/slime.yml b/Resources/Prototypes/Entities/Mobs/Species/slime.yml index 23d6833839..56acf52fb7 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/slime.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/slime.yml @@ -28,8 +28,6 @@ interfaces: enum.StorageUiKey.Key: type: StorageBoundUserInterface - enum.VoiceMaskUIKey.Key: - type: VoiceMaskBoundUserInterface enum.HumanoidMarkingModifierKey.Key: type: HumanoidMarkingModifierBoundUserInterface enum.StrippingUiKey.Key: diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/intercom.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/intercom.yml index ef3546981f..d44bbb9647 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/intercom.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/intercom.yml @@ -24,6 +24,9 @@ - type: Intercom - type: Speech speechVerb: Robotic + - type: VoiceOverride # This is for the wire that makes an electricity zapping noise. + speechVerbOverride: Electricity + enabled: false - type: ExtensionCableReceiver - type: Clickable - type: InteractionOutline