+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);
}
}
}
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";
+}