From e8c58d1574daa3d728f74d0f338ef9162c45cd0c Mon Sep 17 00:00:00 2001 From: deltanedas <39013340+deltanedas@users.noreply.github.com> Date: Sat, 16 Sep 2023 07:07:36 +0100 Subject: [PATCH] Microwaved pais get scrambled name + randomly bricked (#19982) Co-authored-by: deltanedas <@deltanedas:kde.org> --- .../Ghost/Roles/ToggleableGhostRoleSystem.cs | 23 +++ Content.Server/PAI/PAISystem.cs | 149 ++++++++++++------ Content.Shared/PAI/PAIComponent.cs | 66 +++++--- Resources/Locale/en-US/pai/pai-system.ftl | 3 + 4 files changed, 166 insertions(+), 75 deletions(-) diff --git a/Content.Server/Ghost/Roles/ToggleableGhostRoleSystem.cs b/Content.Server/Ghost/Roles/ToggleableGhostRoleSystem.cs index 774de4e191..4adfdfc576 100644 --- a/Content.Server/Ghost/Roles/ToggleableGhostRoleSystem.cs +++ b/Content.Server/Ghost/Roles/ToggleableGhostRoleSystem.cs @@ -124,6 +124,7 @@ public sealed class ToggleableGhostRoleSystem : EntitySystem { if (component.Deleted || !HasComp(uid)) return; + RemCompDeferred(uid); RemCompDeferred(uid); _popup.PopupEntity(Loc.GetString(component.StopSearchVerbPopup), uid, args.User); @@ -133,4 +134,26 @@ public sealed class ToggleableGhostRoleSystem : EntitySystem args.Verbs.Add(verb); } } + + /// + /// If there is a player present, kicks it out. + /// If not, prevents future ghosts taking it. + /// No popups are made, but appearance is updated. + /// + public void Wipe(EntityUid uid) + { + if (TryComp(uid, out var mindContainer) && + mindContainer.HasMind && + _mind.TryGetMind(uid, out var mindId, out var mind)) + { + _mind.TransferTo(mindId, null, mind: mind); + } + + if (!HasComp(uid)) + return; + + RemCompDeferred(uid); + RemCompDeferred(uid); + UpdateAppearance(uid, ToggleableGhostRoleStatus.Off); + } } diff --git a/Content.Server/PAI/PAISystem.cs b/Content.Server/PAI/PAISystem.cs index a601bdea8b..d3dac3edaa 100644 --- a/Content.Server/PAI/PAISystem.cs +++ b/Content.Server/PAI/PAISystem.cs @@ -1,70 +1,119 @@ +using Content.Server.Ghost.Roles; +using Content.Server.Ghost.Roles.Components; using Content.Server.Instruments; +using Content.Server.Kitchen.Components; using Content.Shared.Interaction.Events; using Content.Shared.Mind.Components; using Content.Shared.PAI; +using Content.Shared.Popups; using Robust.Server.GameObjects; +using Robust.Shared.Random; +using System.Text; -namespace Content.Server.PAI +namespace Content.Server.PAI; + +public sealed class PAISystem : SharedPAISystem { - public sealed class PAISystem : SharedPAISystem + [Dependency] private readonly InstrumentSystem _instrumentSystem = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly MetaDataSystem _metaData = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly ToggleableGhostRoleSystem _toggleableGhostRole = default!; + + /// + /// Possible symbols that can be part of a scrambled pai's name. + /// + private static readonly char[] SYMBOLS = new[] { '#', '~', '-', '@', '&', '^', '%', '$', '*', ' '}; + + public override void Initialize() { - [Dependency] private readonly InstrumentSystem _instrumentSystem = default!; - [Dependency] private readonly MetaDataSystem _metaData = default!; + base.Initialize(); - public override void Initialize() + SubscribeLocalEvent(OnUseInHand); + SubscribeLocalEvent(OnMindAdded); + SubscribeLocalEvent(OnMindRemoved); + SubscribeLocalEvent(OnMicrowaved); + } + + private void OnUseInHand(EntityUid uid, PAIComponent component, UseInHandEvent args) + { + if (!TryComp(uid, out var mind) || !mind.HasMind) + component.LastUser = args.User; + } + + private void OnMindAdded(EntityUid uid, PAIComponent component, MindAddedMessage args) + { + if (component.LastUser == null) + return; + + // Ownership tag + var val = Loc.GetString("pai-system-pai-name", ("owner", component.LastUser)); + + // TODO Identity? People shouldn't dox-themselves by carrying around a PAI. + // But having the pda's name permanently be "old lady's PAI" is weird. + // Changing the PAI's identity in a way that ties it to the owner's identity also seems weird. + // Cause then you could remotely figure out information about the owner's equipped items. + + _metaData.SetEntityName(uid, val); + } + + private void OnMindRemoved(EntityUid uid, PAIComponent component, MindRemovedMessage args) + { + // Mind was removed, shutdown the PAI. + PAITurningOff(uid); + } + + private void OnMicrowaved(EntityUid uid, PAIComponent comp, BeingMicrowavedEvent args) + { + // name will always be scrambled whether it gets bricked or not, this is the reward + ScrambleName(uid, comp); + + // randomly brick it + if (_random.Prob(comp.BrickChance)) { - base.Initialize(); + _popup.PopupEntity(Loc.GetString(comp.BrickPopup), uid, PopupType.LargeCaution); + _toggleableGhostRole.Wipe(uid); + RemComp(uid); + RemComp(uid); + } + else + { + // you are lucky... + _popup.PopupEntity(Loc.GetString(comp.ScramblePopup), uid, PopupType.Large); + } + } - SubscribeLocalEvent(OnUseInHand); - SubscribeLocalEvent(OnMindAdded); - SubscribeLocalEvent(OnMindRemoved); + private void ScrambleName(EntityUid uid, PAIComponent comp) + { + // create a new random name + var len = _random.Next(6, 18); + var name = new StringBuilder(len); + for (int i = 0; i < len; i++) + { + name.Append(_random.Pick(SYMBOLS)); } - private void OnUseInHand(EntityUid uid, PAIComponent component, UseInHandEvent args) + // add 's pAI to the scrambled name + var val = Loc.GetString("pai-system-pai-name-raw", ("name", name.ToString())); + _metaData.SetEntityName(uid, val); + } + + public void PAITurningOff(EntityUid uid) + { + // Close the instrument interface if it was open + // before closing + if (HasComp(uid) && TryComp(uid, out var actor)) { - if (!TryComp(uid, out var mind) || !mind.HasMind) - component.LastUser = args.User; + _instrumentSystem.ToggleInstrumentUi(uid, actor.PlayerSession); } - private void OnMindAdded(EntityUid uid, PAIComponent component, MindAddedMessage args) + // Stop instrument + if (TryComp(uid, out var instrument)) _instrumentSystem.Clean(uid, instrument); + if (TryComp(uid, out var metadata)) { - if (component.LastUser == null) - return; - - // Ownership tag - var val = Loc.GetString("pai-system-pai-name", ("owner", component.LastUser)); - - // TODO Identity? People shouldn't dox-themselves by carrying around a PAI. - // But having the pda's name permanently be "old lady's PAI" is weird. - // Changing the PAI's identity in a way that ties it to the owner's identity also seems weird. - // Cause then you could remotely figure out information about the owner's equipped items. - - _metaData.SetEntityName(uid, val); - } - - private void OnMindRemoved(EntityUid uid, PAIComponent component, MindRemovedMessage args) - { - // Mind was removed, shutdown the PAI. - PAITurningOff(uid); - } - - public void PAITurningOff(EntityUid uid) - { - // Close the instrument interface if it was open - // before closing - if (HasComp(uid) && TryComp(uid, out var actor)) - { - _instrumentSystem.ToggleInstrumentUi(uid, actor.PlayerSession); - } - - // Stop instrument - if (TryComp(uid, out var instrument)) _instrumentSystem.Clean(uid, instrument); - if (TryComp(uid, out var metadata)) - { - var proto = metadata.EntityPrototype; - if (proto != null) - _metaData.SetEntityName(uid, proto.Name); - } + var proto = metadata.EntityPrototype; + if (proto != null) + _metaData.SetEntityName(uid, proto.Name); } } } diff --git a/Content.Shared/PAI/PAIComponent.cs b/Content.Shared/PAI/PAIComponent.cs index 9574007e7f..677b0b4d48 100644 --- a/Content.Shared/PAI/PAIComponent.cs +++ b/Content.Shared/PAI/PAIComponent.cs @@ -2,34 +2,50 @@ using Robust.Shared.GameStates; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -namespace Content.Shared.PAI +namespace Content.Shared.PAI; + +/// +/// pAIs, or Personal AIs, are essentially portable ghost role generators. +/// In their current implementation in SS14, they create a ghost role anyone can access, +/// and that a player can also "wipe" (reset/kick out player). +/// Theoretically speaking pAIs are supposed to use a dedicated "offer and select" system, +/// with the player holding the pAI being able to choose one of the ghosts in the round. +/// This seems too complicated for an initial implementation, though, +/// and there's not always enough players and ghost roles to justify it. +/// All logic in PAISystem. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class PAIComponent : Component { /// - /// pAIs, or Personal AIs, are essentially portable ghost role generators. - /// In their current implementation in SS14, they create a ghost role anyone can access, - /// and that a player can also "wipe" (reset/kick out player). - /// Theoretically speaking pAIs are supposed to use a dedicated "offer and select" system, - /// with the player holding the pAI being able to choose one of the ghosts in the round. - /// This seems too complicated for an initial implementation, though, - /// and there's not always enough players and ghost roles to justify it. - /// All logic in PAISystem. + /// The last person who activated this PAI. + /// Used for assigning the name. /// - [RegisterComponent, NetworkedComponent] - public sealed partial class PAIComponent : Component - { - /// - /// The last person who activated this PAI. - /// Used for assigning the name. - /// - [DataField("lastUSer"), ViewVariables(VVAccess.ReadWrite)] - public EntityUid? LastUser; + [DataField("lastUser"), ViewVariables(VVAccess.ReadWrite)] + public EntityUid? LastUser; - [DataField("midiActionId", serverOnly: true, - customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? MidiActionId = "ActionPAIPlayMidi"; + [DataField("midiActionId", serverOnly: true, + customTypeSerializer: typeof(PrototypeIdSerializer))] + public string? MidiActionId = "ActionPAIPlayMidi"; - [DataField("midiAction", serverOnly: true)] // server only, as it uses a server-BUI event !type - public EntityUid? MidiAction; - } + [DataField("midiAction", serverOnly: true)] // server only, as it uses a server-BUI event !type + public EntityUid? MidiAction; + + /// + /// When microwaved there is this chance to brick the pai, kicking out its player and preventing it from being used again. + /// + [DataField("brickChance")] + public float BrickChance = 0.5f; + + /// + /// Locale id for the popup shown when the pai gets bricked. + /// + [DataField("brickPopup")] + public string BrickPopup = "pai-system-brick-popup"; + + /// + /// Locale id for the popup shown when the pai is microwaved but does not get bricked. + /// + [DataField("scramblePopup")] + public string ScramblePopup = "pai-system-scramble-popup"; } - diff --git a/Resources/Locale/en-US/pai/pai-system.ftl b/Resources/Locale/en-US/pai/pai-system.ftl index 2a37ac93a9..d8ee6eaa08 100644 --- a/Resources/Locale/en-US/pai/pai-system.ftl +++ b/Resources/Locale/en-US/pai/pai-system.ftl @@ -17,4 +17,7 @@ pai-system-stop-searching-verb-text = Stop searching pai-system-stopped-searching = The device stopped searching for a pAI. pai-system-pai-name = { CAPITALIZE(THE($owner)) }'s pAI +pai-system-pai-name-raw = {$name}'s pAI +pai-system-brick-popup = The pAI's circuits loudly pop and fizzle out! +pai-system-scramble-popup = The pAI's circuits are overloaded with electricity!