Add voice mask implant (#41551)

* Add voice mask implant

* Remove voice mask

* Voice mask implant now  overrides your identity

* voice mask implant can now be extracted, when taking out the voice mask implant it now updates your name proplery

* Simplify logic
This commit is contained in:
beck-thompson
2025-12-06 18:35:46 -08:00
committed by GitHub
parent 49656df410
commit dd79254a0f
14 changed files with 136 additions and 24 deletions

View File

@@ -26,6 +26,12 @@ public sealed partial class VoiceMaskComponent : Component
[DataField]
public ProtoId<SpeechVerbPrototype>? VoiceMaskSpeechVerb;
/// <summary>
/// If true will override the users identity with whatever <see cref="VoiceMaskName"/> is.
/// </summary>
[DataField]
public bool OverrideIdentity;
/// <summary>
/// The action that gets displayed when the voice mask is equipped.
/// </summary>
@@ -38,3 +44,4 @@ public sealed partial class VoiceMaskComponent : Component
[DataField]
public EntityUid? ActionEntity;
}

View File

@@ -4,6 +4,9 @@ using Content.Shared.CCVar;
using Content.Shared.Chat;
using Content.Shared.Clothing;
using Content.Shared.Database;
using Content.Shared.IdentityManagement;
using Content.Shared.IdentityManagement.Components;
using Content.Shared.Implants;
using Content.Shared.Inventory;
using Content.Shared.Lock;
using Content.Shared.Popups;
@@ -26,6 +29,7 @@ public sealed partial class VoiceMaskSystem : EntitySystem
[Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly LockSystem _lock = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly IdentitySystem _identity = default!;
// CCVar.
private int _maxNameLength;
@@ -33,7 +37,11 @@ public sealed partial class VoiceMaskSystem : EntitySystem
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<VoiceMaskComponent, InventoryRelayedEvent<TransformSpeakerNameEvent>>(OnTransformSpeakerName);
SubscribeLocalEvent<VoiceMaskComponent, InventoryRelayedEvent<TransformSpeakerNameEvent>>(OnTransformSpeakerNameInventory);
SubscribeLocalEvent<VoiceMaskComponent, ImplantRelayEvent<TransformSpeakerNameEvent>>(OnTransformSpeakerNameImplant);
SubscribeLocalEvent<VoiceMaskComponent, ImplantRelayEvent<SeeIdentityAttemptEvent>>(OnSeeIdentityAttemptEvent);
SubscribeLocalEvent<VoiceMaskComponent, ImplantImplantedEvent>(OnImplantImplantedEvent);
SubscribeLocalEvent<VoiceMaskComponent, ImplantRemovedEvent>(OnImplantRemovedEventEvent);
SubscribeLocalEvent<VoiceMaskComponent, LockToggledEvent>(OnLockToggled);
SubscribeLocalEvent<VoiceMaskComponent, VoiceMaskChangeNameMessage>(OnChangeName);
SubscribeLocalEvent<VoiceMaskComponent, VoiceMaskChangeVerbMessage>(OnChangeVerb);
@@ -43,10 +51,30 @@ public sealed partial class VoiceMaskSystem : EntitySystem
Subs.CVar(_cfgManager, CCVars.MaxNameLength, value => _maxNameLength = value, true);
}
private void OnTransformSpeakerName(Entity<VoiceMaskComponent> entity, ref InventoryRelayedEvent<TransformSpeakerNameEvent> args)
private void OnTransformSpeakerNameInventory(Entity<VoiceMaskComponent> entity, ref InventoryRelayedEvent<TransformSpeakerNameEvent> args)
{
args.Args.VoiceName = GetCurrentVoiceName(entity);
args.Args.SpeechVerb = entity.Comp.VoiceMaskSpeechVerb ?? args.Args.SpeechVerb;
TransformVoice(entity, args.Args);
}
private void OnTransformSpeakerNameImplant(Entity<VoiceMaskComponent> entity, ref ImplantRelayEvent<TransformSpeakerNameEvent> args)
{
TransformVoice(entity, args.Event);
}
private void OnSeeIdentityAttemptEvent(Entity<VoiceMaskComponent> entity, ref ImplantRelayEvent<SeeIdentityAttemptEvent> args)
{
if (entity.Comp.OverrideIdentity)
args.Event.NameOverride = GetCurrentVoiceName(entity);
}
private void OnImplantImplantedEvent(Entity<VoiceMaskComponent> entity, ref ImplantImplantedEvent ev)
{
_identity.QueueIdentityUpdate(ev.Implanted);
}
private void OnImplantRemovedEventEvent(Entity<VoiceMaskComponent> entity, ref ImplantRemovedEvent ev)
{
_identity.QueueIdentityUpdate(ev.Implanted);
}
private void OnLockToggled(Entity<VoiceMaskComponent> ent, ref LockToggledEvent args)
@@ -79,6 +107,9 @@ public sealed partial class VoiceMaskSystem : EntitySystem
return;
}
var nameUpdatedEvent = new VoiceMaskNameUpdatedEvent(entity, entity.Comp.VoiceMaskName, message.Name);
RaiseLocalEvent(message.Actor, ref nameUpdatedEvent);
entity.Comp.VoiceMaskName = message.Name;
_adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(message.Actor):player} set voice of {ToPrettyString(entity):mask}: {entity.Comp.VoiceMaskName}");
@@ -123,5 +154,11 @@ public sealed partial class VoiceMaskSystem : EntitySystem
{
return entity.Comp.VoiceMaskName ?? Loc.GetString("voice-mask-default-name-override");
}
private void TransformVoice(Entity<VoiceMaskComponent> entity, TransformSpeakerNameEvent args)
{
args.VoiceName = GetCurrentVoiceName(entity);
args.SpeechVerb = entity.Comp.VoiceMaskSpeechVerb ?? args.SpeechVerb;
}
#endregion
}

View File

@@ -37,4 +37,9 @@ public sealed class SeeIdentityAttemptEvent : CancellableEntityEventArgs, IInven
// cumulative coverage from each relayed slot
public IdentityBlockerCoverage TotalCoverage = IdentityBlockerCoverage.NONE;
/// <summary>
/// A specific name to override your identiy with.
/// </summary>
public string? NameOverride = null;
}

View File

@@ -47,10 +47,16 @@ public sealed class IdentityRepresentation
PresumedName = presumedName;
}
public string ToStringKnown(bool trueName)
/// <summary>
/// Get this identity as a string
/// </summary>
/// <param name="trueName">Should we show their "true" name or hide it?</param>
/// <param name="nameOverride">A "true name" override</param>
/// <returns></returns>
public string ToStringKnown(bool trueName, string? nameOverride)
{
return trueName
? TrueName
? nameOverride ?? TrueName
: PresumedName ?? ToStringUnknown();
}

View File

@@ -8,6 +8,7 @@ using Content.Shared.Humanoid;
using Content.Shared.IdentityManagement.Components;
using Content.Shared.Inventory;
using Content.Shared.Inventory.Events;
using Content.Shared.VoiceMask;
using Robust.Shared.Containers;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects.Components.Localization;
@@ -53,6 +54,7 @@ public sealed class IdentitySystem : EntitySystem
SubscribeLocalEvent<IdentityComponent, DidUnequipHandEvent>((uid, _, _) => QueueIdentityUpdate(uid));
SubscribeLocalEvent<IdentityComponent, WearerMaskToggledEvent>((uid, _, _) => QueueIdentityUpdate(uid));
SubscribeLocalEvent<IdentityComponent, EntityRenamedEvent>((uid, _, _) => QueueIdentityUpdate(uid));
SubscribeLocalEvent<IdentityComponent, VoiceMaskNameUpdatedEvent>((uid, _, _) => QueueIdentityUpdate(uid));
}
/// <summary>
@@ -197,7 +199,7 @@ public sealed class IdentitySystem : EntitySystem
var ev = new SeeIdentityAttemptEvent();
RaiseLocalEvent(target, ev);
return representation.ToStringKnown(!ev.Cancelled);
return representation.ToStringKnown(!ev.Cancelled, ev.NameOverride);
}
/// <summary>

View File

@@ -1,3 +1,5 @@
using Content.Shared.Chat;
using Content.Shared.IdentityManagement.Components;
using Content.Shared.Implants.Components;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
@@ -12,6 +14,8 @@ public abstract partial class SharedSubdermalImplantSystem
SubscribeLocalEvent<ImplantedComponent, MobStateChangedEvent>(RelayToImplantEvent);
SubscribeLocalEvent<ImplantedComponent, AfterInteractUsingEvent>(RelayToImplantEvent);
SubscribeLocalEvent<ImplantedComponent, SuicideEvent>(RelayToImplantEvent);
SubscribeLocalEvent<ImplantedComponent, TransformSpeakerNameEvent>(RelayToImplantEvent);
SubscribeLocalEvent<ImplantedComponent, SeeIdentityAttemptEvent>(RelayToImplantEvent);
}
/// <summary>

View File

@@ -169,7 +169,14 @@ public abstract partial class SharedSubdermalImplantSystem : EntitySystem
[ByRefEvent]
public readonly record struct ImplantImplantedEvent
{
/// <summary>
/// The implant itself
/// </summary>
public readonly EntityUid Implant;
/// <summary>
/// The entity getting implanted
/// </summary>
public readonly EntityUid Implanted;
public ImplantImplantedEvent(EntityUid implant, EntityUid implanted)

View File

@@ -6,3 +6,11 @@ public sealed partial class VoiceMaskSetNameEvent : InstantActionEvent
{
}
/// <summary>
/// Raised on an entity when their voice masks name is updated
/// </summary>
/// <param name="VoiceMaskUid">Uid of the voice mask</param>
/// <param name="OldName">The old name</param>
/// <param name="NewName">The new name</param>
[ByRefEvent]
public readonly record struct VoiceMaskNameUpdatedEvent(EntityUid VoiceMaskUid, string? OldName, string NewName);

View File

@@ -171,9 +171,6 @@ uplink-binary-translator-key-desc = Lets you tap into the silicons' binary chann
uplink-hypopen-name = Hypopen
uplink-hypopen-desc = A chemical hypospray disguised as a pen, capable of instantly injecting up to 10u of reagents. Starts empty.
uplink-voice-mask-name = Voice Mask
uplink-voice-mask-desc = A gas mask that lets you adjust your voice to whoever you can think of. Also utilizes cutting-edge chameleon technology.
uplink-clothing-eyes-hud-syndicate-name = Syndicate Visor
uplink-clothing-eyes-hud-syndicate-desc = The syndicate's professional head-up display, designed for better detection of humanoids and their subsequent elimination.
@@ -226,6 +223,9 @@ uplink-micro-bomb-implanter-desc = Explode on death or manual activation with th
uplink-radio-implanter-name = Radio Implanter
uplink-radio-implanter-desc = Implants a Syndicate radio, allowing covert communication without a headset.
uplink-voice-mask-implanter-name = Voice Mask Implanter
uplink-voice-mask-implanter-desc = Modifies your vocal cords to be able to sound like anyone you could imagine.
# Bundles
uplink-observation-kit-name = Observation Kit
uplink-observation-kit-desc = Includes surveillance camera monitor board and security hud disguised as sunglasses.

View File

@@ -488,3 +488,11 @@
icon: Interface/Actions/shop.png
- type: InstantAction
event: !type:IntrinsicStoreActionEvent
- type: entity
parent: ActionChangeVoiceMask
id: ActionChangeVoiceMaskImplant
components:
- type: Action
icon: { sprite: Interface/Actions/voice-mask.rsi, state: icon }
itemIconStyle: BigAction

View File

@@ -83,7 +83,7 @@
- EncryptionKeyStationMaster
- CyberPen
- BriefcaseThiefBribingBundleFilled
- ClothingMaskGasVoiceChameleon
- VoiceMaskImplanter
#- todo Chameleon Stamp
- type: thiefBackpackSet

View File

@@ -1567,6 +1567,19 @@
categories:
- UplinkImplants
- type: listing
id: UplinkVoiceMaskImplant
name: uplink-voice-mask-implanter-name
description: uplink-voice-mask-implanter-desc
icon: { sprite: Interface/Actions/voice-mask.rsi, state: icon }
productEntity: VoiceMaskImplanter
discountCategory: usualDiscounts
discountDownTo:
Telecrystal: 1
cost:
Telecrystal: 2
categories:
- UplinkImplants
# Wearables
@@ -1583,19 +1596,6 @@
categories:
- UplinkWearables
- type: listing
id: UplinkVoiceMask
name: uplink-voice-mask-name
description: uplink-voice-mask-desc
productEntity: ClothingMaskGasVoiceChameleon
discountCategory: usualDiscounts
discountDownTo:
Telecrystal: 1
cost:
Telecrystal: 2
categories:
- UplinkWearables
- type: listing
id: UplinkHolster
name: uplink-holster-name

View File

@@ -47,6 +47,7 @@
- FakeMindShieldImplant
- RadioImplant
- ChameleonControllerImplant
- VoiceMaskImplant
deimplantFailureDamage:
types:
Cellular: 50
@@ -256,6 +257,14 @@
- type: Implanter
implant: ChameleonControllerImplant
- type: entity
id: VoiceMaskImplanter
name: voice mask implanter
parent: BaseImplantOnlyImplanterSyndi
components:
- type: Implanter
implant: VoiceMaskImplant
#Nuclear Operative/Special implanters
- type: entity

View File

@@ -239,6 +239,25 @@
enum.ChameleonControllerKey.Key:
type: ChameleonControllerBoundUserInterface
- type: entity
parent: BaseSubdermalImplant
id: VoiceMaskImplant
name: voice mask implant
description: This implant allows you to change your voice at will.
categories: [ HideSpawnMenu ]
components:
- type: SubdermalImplant
implantAction: ActionChangeVoiceMaskImplant
- type: VoiceMask
overrideIdentity: true
- type: UserInterface
interfaces:
enum.VoiceMaskUIKey.Key:
type: VoiceMaskBoundUserInterface
- type: Tag
tags:
- SubdermalImplant
#Nuclear Operative/Special Exclusive implants
- type: entity