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
This commit is contained in:
@@ -22,6 +22,7 @@ public sealed class VoiceMaskBoundUserInterface : BoundUserInterface
|
|||||||
|
|
||||||
_window = this.CreateWindow<VoiceMaskNameChangeWindow>();
|
_window = this.CreateWindow<VoiceMaskNameChangeWindow>();
|
||||||
_window.ReloadVerbs(_protomanager);
|
_window.ReloadVerbs(_protomanager);
|
||||||
|
_window.AddVerbs();
|
||||||
|
|
||||||
_window.OnNameChange += OnNameSelected;
|
_window.OnNameChange += OnNameSelected;
|
||||||
_window.OnVerbChange += verb => SendMessage(new VoiceMaskChangeVerbMessage(verb));
|
_window.OnVerbChange += verb => SendMessage(new VoiceMaskChangeVerbMessage(verb));
|
||||||
|
|||||||
@@ -31,8 +31,6 @@ public sealed partial class VoiceMaskNameChangeWindow : FancyWindow
|
|||||||
OnVerbChange?.Invoke((string?) args.Button.GetItemMetadata(args.Id));
|
OnVerbChange?.Invoke((string?) args.Button.GetItemMetadata(args.Id));
|
||||||
SpeechVerbSelector.SelectId(args.Id);
|
SpeechVerbSelector.SelectId(args.Id);
|
||||||
};
|
};
|
||||||
|
|
||||||
AddVerbs();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReloadVerbs(IPrototypeManager proto)
|
public void ReloadVerbs(IPrototypeManager proto)
|
||||||
@@ -44,7 +42,7 @@ public sealed partial class VoiceMaskNameChangeWindow : FancyWindow
|
|||||||
_verbs.Sort((a, b) => a.Item1.CompareTo(b.Item1));
|
_verbs.Sort((a, b) => a.Item1.CompareTo(b.Item1));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddVerbs()
|
public void AddVerbs()
|
||||||
{
|
{
|
||||||
SpeechVerbSelector.Clear();
|
SpeechVerbSelector.Clear();
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ using System.Text;
|
|||||||
using Content.Server.Administration.Logs;
|
using Content.Server.Administration.Logs;
|
||||||
using Content.Server.Administration.Managers;
|
using Content.Server.Administration.Managers;
|
||||||
using Content.Server.Chat.Managers;
|
using Content.Server.Chat.Managers;
|
||||||
using Content.Server.Examine;
|
|
||||||
using Content.Server.GameTicking;
|
using Content.Server.GameTicking;
|
||||||
using Content.Server.Players.RateLimiting;
|
using Content.Server.Players.RateLimiting;
|
||||||
using Content.Server.Speech.Components;
|
using Content.Server.Speech.Components;
|
||||||
@@ -18,13 +17,10 @@ using Content.Shared.Chat;
|
|||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
using Content.Shared.Examine;
|
using Content.Shared.Examine;
|
||||||
using Content.Shared.Ghost;
|
using Content.Shared.Ghost;
|
||||||
using Content.Shared.Humanoid;
|
|
||||||
using Content.Shared.IdentityManagement;
|
using Content.Shared.IdentityManagement;
|
||||||
using Content.Shared.Interaction;
|
|
||||||
using Content.Shared.Mobs.Systems;
|
using Content.Shared.Mobs.Systems;
|
||||||
using Content.Shared.Players;
|
using Content.Shared.Players;
|
||||||
using Content.Shared.Radio;
|
using Content.Shared.Radio;
|
||||||
using Content.Shared.Speech;
|
|
||||||
using Content.Shared.Whitelist;
|
using Content.Shared.Whitelist;
|
||||||
using Robust.Server.Player;
|
using Robust.Server.Player;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
@@ -440,9 +436,9 @@ public sealed partial class ChatSystem : SharedChatSystem
|
|||||||
{
|
{
|
||||||
var nameEv = new TransformSpeakerNameEvent(source, Name(source));
|
var nameEv = new TransformSpeakerNameEvent(source, Name(source));
|
||||||
RaiseLocalEvent(source, nameEv);
|
RaiseLocalEvent(source, nameEv);
|
||||||
name = nameEv.Name;
|
name = nameEv.VoiceName;
|
||||||
// Check for a speech verb override
|
// Check for a speech verb override
|
||||||
if (nameEv.SpeechVerb != null && _prototypeManager.TryIndex<SpeechVerbPrototype>(nameEv.SpeechVerb, out var proto))
|
if (nameEv.SpeechVerb != null && _prototypeManager.TryIndex(nameEv.SpeechVerb, out var proto))
|
||||||
speech = proto;
|
speech = proto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -514,7 +510,7 @@ public sealed partial class ChatSystem : SharedChatSystem
|
|||||||
{
|
{
|
||||||
var nameEv = new TransformSpeakerNameEvent(source, Name(source));
|
var nameEv = new TransformSpeakerNameEvent(source, Name(source));
|
||||||
RaiseLocalEvent(source, nameEv);
|
RaiseLocalEvent(source, nameEv);
|
||||||
name = nameEv.Name;
|
name = nameEv.VoiceName;
|
||||||
}
|
}
|
||||||
name = FormattedMessage.EscapeText(name);
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Raised broadcast in order to transform speech.transmit
|
/// Raised broadcast in order to transform speech.transmit
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ using Content.Shared.Examine;
|
|||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
using Content.Shared.Power;
|
using Content.Shared.Power;
|
||||||
using Content.Shared.Radio;
|
using Content.Shared.Radio;
|
||||||
|
using Content.Shared.Chat;
|
||||||
using Content.Shared.Radio.Components;
|
using Content.Shared.Radio.Components;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
@@ -213,7 +214,7 @@ public sealed class RadioDeviceSystem : EntitySystem
|
|||||||
|
|
||||||
var name = Loc.GetString("speech-name-relay",
|
var name = Loc.GetString("speech-name-relay",
|
||||||
("speaker", Name(uid)),
|
("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
|
// 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);
|
_chat.TrySendInGameICMessage(uid, args.Message, InGameICChatType.Whisper, ChatTransmitRange.GhostRangeLimit, nameOverride: name, checkRadioPrefix: false);
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ using Content.Server.Administration.Logs;
|
|||||||
using Content.Server.Chat.Systems;
|
using Content.Server.Chat.Systems;
|
||||||
using Content.Server.Power.Components;
|
using Content.Server.Power.Components;
|
||||||
using Content.Server.Radio.Components;
|
using Content.Server.Radio.Components;
|
||||||
using Content.Server.VoiceMask;
|
|
||||||
using Content.Shared.Chat;
|
using Content.Shared.Chat;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
using Content.Shared.Radio;
|
using Content.Shared.Radio;
|
||||||
@@ -78,20 +77,15 @@ public sealed class RadioSystem : EntitySystem
|
|||||||
if (!_messages.Add(message))
|
if (!_messages.Add(message))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var name = TryComp(messageSource, out VoiceMaskComponent? mask) && mask.Enabled
|
var evt = new TransformSpeakerNameEvent(messageSource, MetaData(messageSource).EntityName);
|
||||||
? mask.VoiceName
|
RaiseLocalEvent(messageSource, evt);
|
||||||
: MetaData(messageSource).EntityName;
|
|
||||||
|
|
||||||
|
var name = evt.VoiceName;
|
||||||
name = FormattedMessage.EscapeText(name);
|
name = FormattedMessage.EscapeText(name);
|
||||||
|
|
||||||
SpeechVerbPrototype speech;
|
SpeechVerbPrototype speech;
|
||||||
if (mask != null
|
if (evt.SpeechVerb != null && _prototype.TryIndex(evt.SpeechVerb, out var evntProto))
|
||||||
&& mask.Enabled
|
speech = evntProto;
|
||||||
&& mask.SpeechVerb != null
|
|
||||||
&& _prototype.TryIndex<SpeechVerbPrototype>(mask.SpeechVerb, out var proto))
|
|
||||||
{
|
|
||||||
speech = proto;
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
speech = _chat.GetSpeechVerb(messageSource, message);
|
speech = _chat.GetSpeechVerb(messageSource, message);
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
using Content.Server.Speech.Components;
|
|
||||||
using Content.Server.Chat.Systems;
|
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.Server.Wires;
|
||||||
using Content.Shared.Speech;
|
|
||||||
using Content.Shared.Wires;
|
using Content.Shared.Wires;
|
||||||
|
using Content.Shared.Speech;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
namespace Content.Server.Speech;
|
namespace Content.Server.Speech;
|
||||||
|
|
||||||
@@ -11,17 +14,13 @@ public sealed partial class ListenWireAction : BaseToggleWireAction
|
|||||||
{
|
{
|
||||||
private WiresSystem _wires = default!;
|
private WiresSystem _wires = default!;
|
||||||
private ChatSystem _chat = default!;
|
private ChatSystem _chat = default!;
|
||||||
|
private RadioSystem _radio = default!;
|
||||||
|
private IPrototypeManager _protoMan = default!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Length of the gibberish string sent when pulsing the wire
|
/// Length of the gibberish string sent when pulsing the wire
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const int NoiseLength = 16;
|
private const int NoiseLength = 16;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Identifier of the SpeechVerbPrototype to use when pulsing the wire
|
|
||||||
/// </summary>
|
|
||||||
[ValidatePrototypeId<SpeechVerbPrototype>]
|
|
||||||
private const string SpeechVerb = "Electricity";
|
|
||||||
public override Color Color { get; set; } = Color.Green;
|
public override Color Color { get; set; } = Color.Green;
|
||||||
public override string Name { get; set; } = "wire-name-listen";
|
public override string Name { get; set; } = "wire-name-listen";
|
||||||
|
|
||||||
@@ -37,6 +36,8 @@ public sealed partial class ListenWireAction : BaseToggleWireAction
|
|||||||
|
|
||||||
_wires = EntityManager.System<WiresSystem>();
|
_wires = EntityManager.System<WiresSystem>();
|
||||||
_chat = EntityManager.System<ChatSystem>();
|
_chat = EntityManager.System<ChatSystem>();
|
||||||
|
_radio = EntityManager.System<RadioSystem>();
|
||||||
|
_protoMan = IoCManager.Resolve<IPrototypeManager>();
|
||||||
}
|
}
|
||||||
public override StatusLightState? GetLightState(Wire wire)
|
public override StatusLightState? GetLightState(Wire wire)
|
||||||
{
|
{
|
||||||
@@ -72,46 +73,20 @@ public sealed partial class ListenWireAction : BaseToggleWireAction
|
|||||||
if (!GetValue(wire.Owner) || !IsPowered(wire.Owner))
|
if (!GetValue(wire.Owner) || !IsPowered(wire.Owner))
|
||||||
return;
|
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<VoiceMaskComponent>(user, out var oldMask))
|
|
||||||
{
|
|
||||||
oldEnabled = oldMask.Enabled;
|
|
||||||
oldVoiceName = oldMask.VoiceName;
|
|
||||||
oldSpeechVerb = oldMask.SpeechVerb;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Give the user a temporary voicemask component
|
|
||||||
var mask = EntityManager.EnsureComponent<VoiceMaskComponent>(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 chars = Loc.GetString("wire-listen-pulse-characters").ToCharArray();
|
||||||
var noiseMsg = _chat.BuildGibberishString(chars, NoiseLength);
|
var noiseMsg = _chat.BuildGibberishString(chars, NoiseLength);
|
||||||
|
|
||||||
var attemptEv = new ListenAttemptEvent(wire.Owner);
|
if (!EntityManager.TryGetComponent<RadioMicrophoneComponent>(wire.Owner, out var radioMicroPhoneComp))
|
||||||
EntityManager.EventBus.RaiseLocalEvent(wire.Owner, attemptEv);
|
return;
|
||||||
if (!attemptEv.Cancelled)
|
|
||||||
{
|
|
||||||
var ev = new ListenEvent(noiseMsg, user);
|
|
||||||
EntityManager.EventBus.RaiseLocalEvent(wire.Owner, ev);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the voicemask component, or set it back to what it was before
|
if (!EntityManager.TryGetComponent<VoiceOverrideComponent>(wire.Owner, out var voiceOverrideComp))
|
||||||
if (oldMask == null)
|
return;
|
||||||
EntityManager.RemoveComponent(user, mask);
|
|
||||||
else
|
// 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");
|
||||||
mask.Enabled = oldEnabled;
|
voiceOverrideComp.Enabled = true;
|
||||||
mask.VoiceName = oldVoiceName;
|
_radio.SendRadioMessage(wire.Owner, noiseMsg, _protoMan.Index<RadioChannelPrototype>(radioMicroPhoneComp.BroadcastChannel), wire.Owner);
|
||||||
mask.SpeechVerb = oldSpeechVerb;
|
voiceOverrideComp.Enabled = false;
|
||||||
}
|
|
||||||
|
|
||||||
base.Pulse(user, wire);
|
base.Pulse(user, wire);
|
||||||
}
|
}
|
||||||
|
|||||||
35
Content.Server/Speech/Components/VoiceOverrideComponent.cs
Normal file
35
Content.Server/Speech/Components/VoiceOverrideComponent.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
using Content.Shared.Speech;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Server.Speech.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Will change the voice of the entity that has the component (e.g radio and speech).
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 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!
|
||||||
|
/// </remarks>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed partial class VoiceOverrideComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public string? NameOverride = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The verb that will be used insteand of an entities default one.
|
||||||
|
/// If null, the defaut will be used.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public ProtoId<SpeechVerbPrototype>? SpeechVerbOverride = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If true, the override values (if they are not null) will be applied.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public bool Enabled = true;
|
||||||
|
}
|
||||||
22
Content.Server/Speech/EntitySystems/VoiceOverrideSystem.cs
Normal file
22
Content.Server/Speech/EntitySystems/VoiceOverrideSystem.cs
Normal file
@@ -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<VoiceOverrideComponent, TransformSpeakerNameEvent>(OnTransformSpeakerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTransformSpeakerName(Entity<VoiceOverrideComponent> entity, ref TransformSpeakerNameEvent args)
|
||||||
|
{
|
||||||
|
if (!entity.Comp.Enabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
args.VoiceName = entity.Comp.NameOverride ?? args.VoiceName;
|
||||||
|
args.SpeechVerb = entity.Comp.SpeechVerbOverride ?? args.SpeechVerb;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using Content.Server.Chat.Systems;
|
using Content.Server.Chat.Systems;
|
||||||
using Content.Server.Speech;
|
using Content.Server.Speech;
|
||||||
using Content.Shared.Speech;
|
using Content.Shared.Speech;
|
||||||
|
using Content.Shared.Chat;
|
||||||
using Robust.Shared.Audio.Systems;
|
using Robust.Shared.Audio.Systems;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
@@ -48,7 +49,7 @@ public sealed class SurveillanceCameraSpeakerSystem : EntitySystem
|
|||||||
RaiseLocalEvent(args.Speaker, nameEv);
|
RaiseLocalEvent(args.Speaker, nameEv);
|
||||||
|
|
||||||
var name = Loc.GetString("speech-name-relay", ("speaker", Name(uid)),
|
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
|
// 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);
|
_chatSystem.TrySendInGameICMessage(uid, args.Message, InGameICChatType.Speak, ChatTransmitRange.GhostRangeLimit, nameOverride: name);
|
||||||
|
|||||||
@@ -3,21 +3,38 @@ using Robust.Shared.Prototypes;
|
|||||||
|
|
||||||
namespace Content.Server.VoiceMask;
|
namespace Content.Server.VoiceMask;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 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)!
|
||||||
|
/// </remarks>
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public sealed partial class VoiceMaskComponent : Component
|
public sealed partial class VoiceMaskComponent : Component
|
||||||
{
|
{
|
||||||
[DataField]
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
public bool Enabled = true;
|
|
||||||
|
|
||||||
[DataField]
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
public string VoiceName = "Unknown";
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 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.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[DataField]
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
public string? VoiceMaskName = null;
|
||||||
public ProtoId<SpeechVerbPrototype>? SpeechVerb;
|
|
||||||
|
/// <summary>
|
||||||
|
/// The speech verb that will override an entities default one. If null, it will use the entities default verb.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public ProtoId<SpeechVerbPrototype>? VoiceMaskSpeechVerb;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The action that gets displayed when the voice mask is equipped.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public EntProtoId Action = "ActionChangeVoiceMask";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reference to the action.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public EntityUid? ActionEntity;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<VoiceMaskComponent>(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<VoiceMaskComponent>(args.Wearer);
|
|
||||||
}
|
|
||||||
|
|
||||||
private VoiceMaskerComponent? TryGetMask(EntityUid user)
|
|
||||||
{
|
|
||||||
if (!HasComp<VoiceMaskComponent>(user) || !_inventory.TryGetSlotEntity(user, MaskSlot, out var maskEntity))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return CompOrNull<VoiceMaskerComponent>(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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,113 +1,103 @@
|
|||||||
using Content.Server.Administration.Logs;
|
using Content.Shared.Actions;
|
||||||
using Content.Server.Chat.Systems;
|
using Content.Shared.Administration.Logs;
|
||||||
using Content.Server.Popups;
|
using Content.Shared.Chat;
|
||||||
using Content.Shared.Clothing;
|
using Content.Shared.Clothing;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
|
using Content.Shared.Inventory;
|
||||||
using Content.Shared.Popups;
|
using Content.Shared.Popups;
|
||||||
using Content.Shared.Preferences;
|
using Content.Shared.Preferences;
|
||||||
using Content.Shared.Speech;
|
using Content.Shared.Speech;
|
||||||
using Content.Shared.VoiceMask;
|
using Content.Shared.VoiceMask;
|
||||||
using Robust.Server.GameObjects;
|
|
||||||
using Robust.Shared.Player;
|
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
namespace Content.Server.VoiceMask;
|
namespace Content.Server.VoiceMask;
|
||||||
|
|
||||||
public sealed partial class VoiceMaskSystem : EntitySystem
|
public sealed partial class VoiceMaskSystem : EntitySystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
[Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!;
|
||||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||||
|
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
SubscribeLocalEvent<VoiceMaskComponent, TransformSpeakerNameEvent>(OnSpeakerNameTransform);
|
base.Initialize();
|
||||||
|
SubscribeLocalEvent<VoiceMaskComponent, InventoryRelayedEvent<TransformSpeakerNameEvent>>(OnTransformSpeakerName);
|
||||||
SubscribeLocalEvent<VoiceMaskComponent, VoiceMaskChangeNameMessage>(OnChangeName);
|
SubscribeLocalEvent<VoiceMaskComponent, VoiceMaskChangeNameMessage>(OnChangeName);
|
||||||
SubscribeLocalEvent<VoiceMaskComponent, VoiceMaskChangeVerbMessage>(OnChangeVerb);
|
SubscribeLocalEvent<VoiceMaskComponent, VoiceMaskChangeVerbMessage>(OnChangeVerb);
|
||||||
SubscribeLocalEvent<VoiceMaskComponent, WearerMaskToggledEvent>(OnMaskToggled);
|
SubscribeLocalEvent<VoiceMaskComponent, ClothingGotEquippedEvent>(OnEquip);
|
||||||
SubscribeLocalEvent<VoiceMaskerComponent, ClothingGotEquippedEvent>(OnEquip);
|
SubscribeLocalEvent<VoiceMaskSetNameEvent>(OpenUI);
|
||||||
SubscribeLocalEvent<VoiceMaskerComponent, ClothingGotUnequippedEvent>(OnUnequip);
|
|
||||||
SubscribeLocalEvent<VoiceMaskSetNameEvent>(OnSetName);
|
|
||||||
// SubscribeLocalEvent<VoiceMaskerComponent, GetVerbsEvent<AlternativeVerb>>(GetVerbs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSetName(VoiceMaskSetNameEvent ev)
|
private void OnTransformSpeakerName(Entity<VoiceMaskComponent> entity, ref InventoryRelayedEvent<TransformSpeakerNameEvent> 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<VoiceMaskComponent> entity, ref VoiceMaskChangeVerbMessage msg)
|
||||||
if (message.Name.Length > HumanoidCharacterProfile.MaxNameLength || message.Name.Length <= 0)
|
|
||||||
{
|
|
||||||
_popupSystem.PopupEntity(Loc.GetString("voice-mask-popup-failure"), uid, 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}");
|
|
||||||
|
|
||||||
_popupSystem.PopupEntity(Loc.GetString("voice-mask-popup-success"), uid, message.Actor);
|
|
||||||
|
|
||||||
TrySetLastKnownName(uid, message.Name);
|
|
||||||
|
|
||||||
UpdateUI(uid, component);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnChangeVerb(Entity<VoiceMaskComponent> ent, ref VoiceMaskChangeVerbMessage msg)
|
|
||||||
{
|
{
|
||||||
if (msg.Verb is { } id && !_proto.HasIndex<SpeechVerbPrototype>(id))
|
if (msg.Verb is { } id && !_proto.HasIndex<SpeechVerbPrototype>(id))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ent.Comp.SpeechVerb = msg.Verb;
|
entity.Comp.VoiceMaskSpeechVerb = msg.Verb;
|
||||||
// verb is only important to metagamers so no need to log as opposed to name
|
// 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);
|
_popupSystem.PopupEntity(Loc.GetString("voice-mask-popup-success"), entity, msg.Actor);
|
||||||
|
|
||||||
TrySetLastSpeechVerb(ent, msg.Verb);
|
UpdateUI(entity);
|
||||||
|
|
||||||
UpdateUI(ent, ent.Comp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSpeakerNameTransform(EntityUid uid, VoiceMaskComponent component, TransformSpeakerNameEvent args)
|
private void OnChangeName(Entity<VoiceMaskComponent> entity, ref VoiceMaskChangeNameMessage message)
|
||||||
{
|
{
|
||||||
if (component.Enabled)
|
if (message.Name.Length > HumanoidCharacterProfile.MaxNameLength || message.Name.Length <= 0)
|
||||||
{
|
|
||||||
/*
|
|
||||||
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<VoiceMaskComponent> ent, ref WearerMaskToggledEvent args)
|
|
||||||
{
|
|
||||||
ent.Comp.Enabled = !args.IsToggled;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OpenUI(EntityUid player)
|
|
||||||
{
|
|
||||||
if (!_uiSystem.HasUi(player, VoiceMaskUIKey.Key))
|
|
||||||
return;
|
|
||||||
|
|
||||||
_uiSystem.OpenUi(player, VoiceMaskUIKey.Key, player);
|
|
||||||
UpdateUI(player);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateUI(EntityUid owner, VoiceMaskComponent? component = null)
|
|
||||||
{
|
|
||||||
if (!Resolve(owner, ref component))
|
|
||||||
{
|
{
|
||||||
|
_popupSystem.PopupEntity(Loc.GetString("voice-mask-popup-failure"), entity, message.Actor, PopupType.SmallCaution);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_uiSystem.HasUi(owner, VoiceMaskUIKey.Key))
|
entity.Comp.VoiceMaskName = message.Name;
|
||||||
_uiSystem.SetUiState(owner, VoiceMaskUIKey.Key, new VoiceMaskBuiState(component.VoiceName, component.SpeechVerb));
|
_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"), entity, message.Actor);
|
||||||
|
|
||||||
|
UpdateUI(entity);
|
||||||
}
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region UI
|
||||||
|
private void OnEquip(EntityUid uid, VoiceMaskComponent component, ClothingGotEquippedEvent args)
|
||||||
|
{
|
||||||
|
_actions.AddAction(args.Wearer, ref component.ActionEntity, component.Action, uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenUI(VoiceMaskSetNameEvent ev)
|
||||||
|
{
|
||||||
|
var maskEntity = ev.Action.Comp.Container;
|
||||||
|
|
||||||
|
if (!TryComp<VoiceMaskComponent>(maskEntity, out var voiceMaskComp))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!_uiSystem.HasUi(maskEntity.Value, VoiceMaskUIKey.Key))
|
||||||
|
return;
|
||||||
|
|
||||||
|
_uiSystem.OpenUi(maskEntity.Value, VoiceMaskUIKey.Key, ev.Performer);
|
||||||
|
UpdateUI((maskEntity.Value, voiceMaskComp));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateUI(Entity<VoiceMaskComponent> entity)
|
||||||
|
{
|
||||||
|
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<VoiceMaskComponent> entity)
|
||||||
|
{
|
||||||
|
return entity.Comp.VoiceMaskName ?? Loc.GetString("voice-mask-default-name-override");
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<SpeechVerbPrototype>? LastSpeechVerb;
|
|
||||||
|
|
||||||
[DataField]
|
|
||||||
public EntProtoId Action = "ActionChangeVoiceMask";
|
|
||||||
|
|
||||||
[DataField]
|
|
||||||
public EntityUid? ActionEntity;
|
|
||||||
}
|
|
||||||
24
Content.Shared/Chat/SharedChatEvents.cs
Normal file
24
Content.Shared/Chat/SharedChatEvents.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
using Content.Shared.Speech;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Content.Shared.Inventory;
|
||||||
|
|
||||||
|
namespace Content.Shared.Chat;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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).
|
||||||
|
/// </summary>
|
||||||
|
public sealed class TransformSpeakerNameEvent : EntityEventArgs, IInventoryRelayEvent
|
||||||
|
{
|
||||||
|
public SlotFlags TargetSlots { get; } = SlotFlags.WITHOUT_POCKET;
|
||||||
|
public EntityUid Sender;
|
||||||
|
public string VoiceName;
|
||||||
|
public ProtoId<SpeechVerbPrototype>? SpeechVerb;
|
||||||
|
|
||||||
|
public TransformSpeakerNameEvent(EntityUid sender, string name)
|
||||||
|
{
|
||||||
|
Sender = sender;
|
||||||
|
VoiceName = name;
|
||||||
|
SpeechVerb = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ using Content.Shared.Slippery;
|
|||||||
using Content.Shared.Strip.Components;
|
using Content.Shared.Strip.Components;
|
||||||
using Content.Shared.Temperature;
|
using Content.Shared.Temperature;
|
||||||
using Content.Shared.Verbs;
|
using Content.Shared.Verbs;
|
||||||
|
using Content.Shared.Chat;
|
||||||
|
|
||||||
namespace Content.Shared.Inventory;
|
namespace Content.Shared.Inventory;
|
||||||
|
|
||||||
@@ -31,6 +32,7 @@ public partial class InventorySystem
|
|||||||
SubscribeLocalEvent<InventoryComponent, ModifyChangedTemperatureEvent>(RelayInventoryEvent);
|
SubscribeLocalEvent<InventoryComponent, ModifyChangedTemperatureEvent>(RelayInventoryEvent);
|
||||||
SubscribeLocalEvent<InventoryComponent, GetDefaultRadioChannelEvent>(RelayInventoryEvent);
|
SubscribeLocalEvent<InventoryComponent, GetDefaultRadioChannelEvent>(RelayInventoryEvent);
|
||||||
SubscribeLocalEvent<InventoryComponent, RefreshNameModifiersEvent>(RelayInventoryEvent);
|
SubscribeLocalEvent<InventoryComponent, RefreshNameModifiersEvent>(RelayInventoryEvent);
|
||||||
|
SubscribeLocalEvent<InventoryComponent, TransformSpeakerNameEvent>(RelayInventoryEvent);
|
||||||
|
|
||||||
// by-ref events
|
// by-ref events
|
||||||
SubscribeLocalEvent<InventoryComponent, GetExplosionResistanceEvent>(RefRelayInventoryEvent);
|
SubscribeLocalEvent<InventoryComponent, GetExplosionResistanceEvent>(RefRelayInventoryEvent);
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
voice-mask-default-name-override = Unknown
|
||||||
|
|
||||||
voice-mask-name-change-window = Voice Mask Name Change
|
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-info = Type in the name you want to mimic.
|
||||||
voice-mask-name-change-speech-style = Speech style
|
voice-mask-name-change-speech-style = Speech style
|
||||||
|
|||||||
@@ -29,10 +29,14 @@
|
|||||||
id: ClothingMaskGasVoiceChameleon
|
id: ClothingMaskGasVoiceChameleon
|
||||||
suffix: Voice Mask, Chameleon
|
suffix: Voice Mask, Chameleon
|
||||||
components:
|
components:
|
||||||
- type: VoiceMasker
|
- type: VoiceMask
|
||||||
- type: HideLayerClothing
|
- type: HideLayerClothing
|
||||||
slots:
|
slots:
|
||||||
- Snout
|
- Snout
|
||||||
|
- type: UserInterface
|
||||||
|
interfaces:
|
||||||
|
enum.VoiceMaskUIKey.Key:
|
||||||
|
type: VoiceMaskBoundUserInterface
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: ClothingMaskBase
|
parent: ClothingMaskBase
|
||||||
|
|||||||
@@ -183,8 +183,6 @@
|
|||||||
- type: Stripping
|
- type: Stripping
|
||||||
- type: UserInterface
|
- type: UserInterface
|
||||||
interfaces:
|
interfaces:
|
||||||
enum.VoiceMaskUIKey.Key:
|
|
||||||
type: VoiceMaskBoundUserInterface
|
|
||||||
enum.HumanoidMarkingModifierKey.Key:
|
enum.HumanoidMarkingModifierKey.Key:
|
||||||
type: HumanoidMarkingModifierBoundUserInterface
|
type: HumanoidMarkingModifierBoundUserInterface
|
||||||
enum.StrippingUiKey.Key:
|
enum.StrippingUiKey.Key:
|
||||||
|
|||||||
@@ -28,8 +28,6 @@
|
|||||||
interfaces:
|
interfaces:
|
||||||
enum.StorageUiKey.Key:
|
enum.StorageUiKey.Key:
|
||||||
type: StorageBoundUserInterface
|
type: StorageBoundUserInterface
|
||||||
enum.VoiceMaskUIKey.Key:
|
|
||||||
type: VoiceMaskBoundUserInterface
|
|
||||||
enum.HumanoidMarkingModifierKey.Key:
|
enum.HumanoidMarkingModifierKey.Key:
|
||||||
type: HumanoidMarkingModifierBoundUserInterface
|
type: HumanoidMarkingModifierBoundUserInterface
|
||||||
enum.StrippingUiKey.Key:
|
enum.StrippingUiKey.Key:
|
||||||
|
|||||||
@@ -24,6 +24,9 @@
|
|||||||
- type: Intercom
|
- type: Intercom
|
||||||
- type: Speech
|
- type: Speech
|
||||||
speechVerb: Robotic
|
speechVerb: Robotic
|
||||||
|
- type: VoiceOverride # This is for the wire that makes an electricity zapping noise.
|
||||||
|
speechVerbOverride: Electricity
|
||||||
|
enabled: false
|
||||||
- type: ExtensionCableReceiver
|
- type: ExtensionCableReceiver
|
||||||
- type: Clickable
|
- type: Clickable
|
||||||
- type: InteractionOutline
|
- type: InteractionOutline
|
||||||
|
|||||||
Reference in New Issue
Block a user