diff --git a/Content.Client/Entry/IgnoredComponents.cs b/Content.Client/Entry/IgnoredComponents.cs index 720bdcd2e4..f23e624d57 100644 --- a/Content.Client/Entry/IgnoredComponents.cs +++ b/Content.Client/Entry/IgnoredComponents.cs @@ -65,7 +65,6 @@ namespace Content.Client.Entry "Paper", "Write", "Bloodstream", - "PAI", "TransformableContainer", "Mind", "StorageFill", diff --git a/Content.Client/PAI/PAISystem.cs b/Content.Client/PAI/PAISystem.cs new file mode 100644 index 0000000000..a363d7d7d9 --- /dev/null +++ b/Content.Client/PAI/PAISystem.cs @@ -0,0 +1,8 @@ +using Content.Shared.PAI; + +namespace Content.Client.PAI +{ + public class PAISystem : SharedPAISystem + { + } +} diff --git a/Content.Server/PAI/PAISystem.cs b/Content.Server/PAI/PAISystem.cs index 79d6838555..5c5aabd409 100644 --- a/Content.Server/PAI/PAISystem.cs +++ b/Content.Server/PAI/PAISystem.cs @@ -14,16 +14,7 @@ using Robust.Shared.Player; namespace Content.Server.PAI { - /// - /// pAIs, or Personal AIs, are essentially portable ghost role generators. - /// In their current implementation, 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. - /// - public class PAISystem : EntitySystem + public class PAISystem : SharedPAISystem { [Dependency] private readonly PopupSystem _popupSystem = default!; @@ -78,6 +69,9 @@ namespace Content.Server.PAI return; } + // Ownership tag + component.Owner.Name = Loc.GetString("pai-system-pai-name", ("owner", args.User)); + var ghostFinder = EntityManager.EnsureComponent(uid); ghostFinder.RoleName = Loc.GetString("pai-system-role-name"); @@ -90,7 +84,7 @@ namespace Content.Server.PAI private void OnMindRemoved(EntityUid uid, PAIComponent component, MindRemovedMessage args) { // Mind was removed, shutdown the PAI. - UpdatePAIAppearance(uid, PAIStatus.Off); + PAITurningOff(uid); } private void OnMindAdded(EntityUid uid, PAIComponent pai, MindAddedMessage args) @@ -101,6 +95,17 @@ namespace Content.Server.PAI UpdatePAIAppearance(uid, PAIStatus.On); } + private void PAITurningOff(EntityUid uid) + { + UpdatePAIAppearance(uid, PAIStatus.Off); + if (EntityManager.TryGetComponent(uid, out var metadata)) + { + var proto = metadata.EntityPrototype; + if (proto != null) + metadata.EntityName = proto.Name; + } + } + private void UpdatePAIAppearance(EntityUid uid, PAIStatus status) { if (EntityManager.TryGetComponent(uid, out var appearance)) @@ -128,7 +133,7 @@ namespace Content.Server.PAI { EntityManager.RemoveComponent(uid); _popupSystem.PopupEntity(Loc.GetString("pai-system-wiped-device"), uid, Filter.Entities(args.User.Uid)); - UpdatePAIAppearance(uid, PAIStatus.Off); + PAITurningOff(uid); } }; args.Verbs.Add(verb); @@ -144,7 +149,7 @@ namespace Content.Server.PAI { EntityManager.RemoveComponent(uid); _popupSystem.PopupEntity(Loc.GetString("pai-system-stopped-searching"), uid, Filter.Entities(args.User.Uid)); - UpdatePAIAppearance(uid, PAIStatus.Off); + PAITurningOff(uid); } }; args.Verbs.Add(verb); diff --git a/Content.Server/PAI/PAIComponent.cs b/Content.Shared/PAI/PAIComponent.cs similarity index 84% rename from Content.Server/PAI/PAIComponent.cs rename to Content.Shared/PAI/PAIComponent.cs index 4b8c572586..ad255783da 100644 --- a/Content.Server/PAI/PAIComponent.cs +++ b/Content.Shared/PAI/PAIComponent.cs @@ -1,6 +1,8 @@ using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; -namespace Content.Server.PAI +namespace Content.Shared.PAI { /// /// pAIs, or Personal AIs, are essentially portable ghost role generators. @@ -12,7 +14,7 @@ namespace Content.Server.PAI /// and there's not always enough players and ghost roles to justify it. /// All logic in PAISystem. /// - [RegisterComponent] + [RegisterComponent, NetworkedComponent] public class PAIComponent : Component { public override string Name => "PAI"; diff --git a/Content.Shared/PAI/PAIShared.cs b/Content.Shared/PAI/PAIShared.cs deleted file mode 100644 index 6820733f42..0000000000 --- a/Content.Shared/PAI/PAIShared.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using Robust.Shared.GameObjects; -using Robust.Shared.Serialization; -using Robust.Shared.Serialization.Manager.Attributes; -using Robust.Shared.ViewVariables; - -namespace Content.Shared.PAI -{ - [Serializable, NetSerializable] - public enum PAIVisuals : byte - { - Status - } - - [Serializable, NetSerializable] - public enum PAIStatus : byte - { - Off, - Searching, - On - } -} - diff --git a/Content.Shared/PAI/SharedPAISystem.cs b/Content.Shared/PAI/SharedPAISystem.cs new file mode 100644 index 0000000000..2d7d2f7ee0 --- /dev/null +++ b/Content.Shared/PAI/SharedPAISystem.cs @@ -0,0 +1,74 @@ +using System; +using Content.Shared.DragDrop; +using Content.Shared.Emoting; +using Content.Shared.Interaction.Events; +using Content.Shared.Item; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.ViewVariables; + +namespace Content.Shared.PAI +{ + /// + /// pAIs, or Personal AIs, are essentially portable ghost role generators. + /// In their current implementation, 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. + /// + public abstract class SharedPAISystem : EntitySystem + { + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnUseAttempt); + SubscribeLocalEvent(OnInteractAttempt); + SubscribeLocalEvent(OnAttackAttempt); + SubscribeLocalEvent(OnDropAttempt); + SubscribeLocalEvent(OnPickupAttempt); + } + + private void OnUseAttempt(EntityUid uid, PAIComponent component, UseAttemptEvent args) + { + args.Cancel(); + } + + private void OnInteractAttempt(EntityUid uid, PAIComponent component, InteractionAttemptEvent args) + { + args.Cancel(); + } + + private void OnAttackAttempt(EntityUid uid, PAIComponent component, AttackAttemptEvent args) + { + args.Cancel(); + } + + private void OnDropAttempt(EntityUid uid, PAIComponent component, DropAttemptEvent args) + { + args.Cancel(); + } + + private void OnPickupAttempt(EntityUid uid, PAIComponent component, PickupAttemptEvent args) + { + args.Cancel(); + } + } + + [Serializable, NetSerializable] + public enum PAIVisuals : byte + { + Status + } + + [Serializable, NetSerializable] + public enum PAIStatus : byte + { + Off, + Searching, + On + } +} + diff --git a/Resources/Locale/en-US/pai/pai-system.ftl b/Resources/Locale/en-US/pai/pai-system.ftl index 1406e9b5fd..bb03dd051e 100644 --- a/Resources/Locale/en-US/pai/pai-system.ftl +++ b/Resources/Locale/en-US/pai/pai-system.ftl @@ -13,3 +13,5 @@ pai-system-wiped-device = The pAI was wiped from the device. 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 +