]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Microwaved pais get scrambled name + randomly bricked (#19982)
authordeltanedas <39013340+deltanedas@users.noreply.github.com>
Sat, 16 Sep 2023 06:07:36 +0000 (07:07 +0100)
committerGitHub <noreply@github.com>
Sat, 16 Sep 2023 06:07:36 +0000 (16:07 +1000)
Co-authored-by: deltanedas <@deltanedas:kde.org>
Content.Server/Ghost/Roles/ToggleableGhostRoleSystem.cs
Content.Server/PAI/PAISystem.cs
Content.Shared/PAI/PAIComponent.cs
Resources/Locale/en-US/pai/pai-system.ftl

index 774de4e191e3a5e72f2d66e4dc82943c05f14ebb..4adfdfc576a20af14e1f0afa4496501f8dacf276 100644 (file)
@@ -124,6 +124,7 @@ public sealed class ToggleableGhostRoleSystem : EntitySystem
                 {
                     if (component.Deleted || !HasComp<GhostTakeoverAvailableComponent>(uid))
                         return;
+
                     RemCompDeferred<GhostTakeoverAvailableComponent>(uid);
                     RemCompDeferred<GhostRoleComponent>(uid);
                     _popup.PopupEntity(Loc.GetString(component.StopSearchVerbPopup), uid, args.User);
@@ -133,4 +134,26 @@ public sealed class ToggleableGhostRoleSystem : EntitySystem
             args.Verbs.Add(verb);
         }
     }
+
+    /// <summary>
+    /// If there is a player present, kicks it out.
+    /// If not, prevents future ghosts taking it.
+    /// No popups are made, but appearance is updated.
+    /// </summary>
+    public void Wipe(EntityUid uid)
+    {
+        if (TryComp<MindContainerComponent>(uid, out var mindContainer) &&
+            mindContainer.HasMind &&
+            _mind.TryGetMind(uid, out var mindId, out var mind))
+        {
+            _mind.TransferTo(mindId, null, mind: mind);
+        }
+
+        if (!HasComp<GhostTakeoverAvailableComponent>(uid))
+            return;
+
+        RemCompDeferred<GhostTakeoverAvailableComponent>(uid);
+        RemCompDeferred<GhostRoleComponent>(uid);
+        UpdateAppearance(uid, ToggleableGhostRoleStatus.Off);
+    }
 }
index a601bdea8b3f9547fb17824f6fa695ceb778ad44..d3dac3edaacb423af5bc8689b697317918a3a81e 100644 (file)
+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!;
+
+    /// <summary>
+    /// Possible symbols that can be part of a scrambled pai's name.
+    /// </summary>
+    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()
-        {
-            base.Initialize();
+        SubscribeLocalEvent<PAIComponent, UseInHandEvent>(OnUseInHand);
+        SubscribeLocalEvent<PAIComponent, MindAddedMessage>(OnMindAdded);
+        SubscribeLocalEvent<PAIComponent, MindRemovedMessage>(OnMindRemoved);
+        SubscribeLocalEvent<PAIComponent, BeingMicrowavedEvent>(OnMicrowaved);
+    }
 
-            SubscribeLocalEvent<PAIComponent, UseInHandEvent>(OnUseInHand);
-            SubscribeLocalEvent<PAIComponent, MindAddedMessage>(OnMindAdded);
-            SubscribeLocalEvent<PAIComponent, MindRemovedMessage>(OnMindRemoved);
-        }
+    private void OnUseInHand(EntityUid uid, PAIComponent component, UseInHandEvent args)
+    {
+        if (!TryComp<MindContainerComponent>(uid, out var mind) || !mind.HasMind)
+            component.LastUser = args.User;
+    }
 
-        private void OnUseInHand(EntityUid uid, PAIComponent component, UseInHandEvent args)
-        {
-            if (!TryComp<MindContainerComponent>(uid, out var mind) || !mind.HasMind)
-                component.LastUser = args.User;
-        }
+    private void OnMindAdded(EntityUid uid, PAIComponent component, MindAddedMessage args)
+    {
+        if (component.LastUser == null)
+            return;
 
-        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));
 
-            // 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.
 
-            // 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);
+    }
 
-            _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))
+        {
+            _popup.PopupEntity(Loc.GetString(comp.BrickPopup), uid, PopupType.LargeCaution);
+            _toggleableGhostRole.Wipe(uid);
+            RemComp<PAIComponent>(uid);
+            RemComp<ToggleableGhostRoleComponent>(uid);
+        }
+        else
+        {
+            // you are lucky...
+            _popup.PopupEntity(Loc.GetString(comp.ScramblePopup), uid, PopupType.Large);
+        }
+    }
+
+    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 OnMindRemoved(EntityUid uid, PAIComponent component, MindRemovedMessage 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<ActiveInstrumentComponent>(uid) && TryComp<ActorComponent>(uid, out var actor))
         {
-            // Mind was removed, shutdown the PAI.
-            PAITurningOff(uid);
+            _instrumentSystem.ToggleInstrumentUi(uid, actor.PlayerSession);
         }
 
-        public void PAITurningOff(EntityUid uid)
+        //  Stop instrument
+        if (TryComp<InstrumentComponent>(uid, out var instrument)) _instrumentSystem.Clean(uid, instrument);
+        if (TryComp<MetaDataComponent>(uid, out var metadata))
         {
-            //  Close the instrument interface if it was open
-            //  before closing
-            if (HasComp<ActiveInstrumentComponent>(uid) && TryComp<ActorComponent>(uid, out var actor))
-            {
-                _instrumentSystem.ToggleInstrumentUi(uid, actor.PlayerSession);
-            }
-
-            //  Stop instrument
-            if (TryComp<InstrumentComponent>(uid, out var instrument)) _instrumentSystem.Clean(uid, instrument);
-            if (TryComp<MetaDataComponent>(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);
         }
     }
 }
index 9574007e7f7216a4b9a4beeb67e45d0dfd01ad3e..677b0b4d485e565e517ca9cf2601ad0cde0b1103 100644 (file)
@@ -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;
+
+/// <summary>
+/// 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.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+public sealed partial class PAIComponent : Component
 {
     /// <summary>
-    /// 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.
     /// </summary>
-    [RegisterComponent, NetworkedComponent]
-    public sealed partial class PAIComponent : Component
-    {
-        /// <summary>
-        /// The last person who activated this PAI.
-        /// Used for assigning the name.
-        /// </summary>
-        [DataField("lastUSer"), ViewVariables(VVAccess.ReadWrite)]
-        public EntityUid? LastUser;
+    [DataField("lastUser"), ViewVariables(VVAccess.ReadWrite)]
+    public EntityUid? LastUser;
 
-        [DataField("midiActionId", serverOnly: true,
-            customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
-        public string? MidiActionId = "ActionPAIPlayMidi";
+    [DataField("midiActionId", serverOnly: true,
+        customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
+    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;
+
+    /// <summary>
+    /// When microwaved there is this chance to brick the pai, kicking out its player and preventing it from being used again.
+    /// </summary>
+    [DataField("brickChance")]
+    public float BrickChance = 0.5f;
 
+    /// <summary>
+    /// Locale id for the popup shown when the pai gets bricked.
+    /// </summary>
+    [DataField("brickPopup")]
+    public string BrickPopup = "pai-system-brick-popup";
+
+    /// <summary>
+    /// Locale id for the popup shown when the pai is microwaved but does not get bricked.
+    /// </summary>
+    [DataField("scramblePopup")]
+    public string ScramblePopup = "pai-system-scramble-popup";
+}
index 2a37ac93a91badf47b4148861685bd635deba9db..d8ee6eaa086f8e6c1a7397f52d440dc6b9690d16 100644 (file)
@@ -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!