diff --git a/Content.Client/_Goobstation/Silicon/AiEarlyLeave/StationAiEarlyLeaveEui.cs b/Content.Client/_Goobstation/Silicon/AiEarlyLeave/StationAiEarlyLeaveEui.cs new file mode 100644 index 0000000000..a1e1a76673 --- /dev/null +++ b/Content.Client/_Goobstation/Silicon/AiEarlyLeave/StationAiEarlyLeaveEui.cs @@ -0,0 +1,55 @@ +using Content.Client.Eui; +using JetBrains.Annotations; +using Robust.Client.Graphics; + +using Content.Shared._Goobstation.Silicon.AiEarlyLeave; + +namespace Content.Client._Goobstation.Silicon.AiEarlyLeave; + +[UsedImplicitly] +public sealed class StationAiEarlyLeaveEui : BaseEui +{ + private readonly StationAiEarlyLeaveMenu _menu; + private bool _sentResponse; + + public StationAiEarlyLeaveEui() + { + _menu = new StationAiEarlyLeaveMenu(); + + _menu.DenyButton.OnPressed += _ => + { + SendResponse(false); // DeltaV + _menu.Close(); + }; + + _menu.ConfirmButton.OnPressed += _ => + { + SendResponse(true); // DeltaV + _menu.Close(); + }; + } + // Start of DeltaV Changes + private void SendResponse(bool confirmed) + { + if (_sentResponse) + return; + + _sentResponse = true; + SendMessage(new StationAiEarlyLeaveMessage(confirmed)); + } + + public override void Opened() + { + IoCManager.Resolve().RequestWindowAttention(); + _menu.OpenCentered(); + } + // End of DeltaV Changes + public override void Closed() + { + base.Closed(); + + SendResponse(false); // DeltaV + _menu.Close(); + } + +} diff --git a/Content.Client/_Goobstation/Silicon/AiEarlyLeave/StationAiEarlyLeaveMenu.cs b/Content.Client/_Goobstation/Silicon/AiEarlyLeave/StationAiEarlyLeaveMenu.cs new file mode 100644 index 0000000000..e8b5d74f05 --- /dev/null +++ b/Content.Client/_Goobstation/Silicon/AiEarlyLeave/StationAiEarlyLeaveMenu.cs @@ -0,0 +1,58 @@ +using System.Numerics; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; +using static Robust.Client.UserInterface.Controls.BoxContainer; + +namespace Content.Client._Goobstation.Silicon.AiEarlyLeave; + +public sealed class StationAiEarlyLeaveMenu : DefaultWindow +{ + public readonly Button DenyButton; + public readonly Button ConfirmButton; + + public StationAiEarlyLeaveMenu() + { + Title = Loc.GetString("station-ai-earlyleave-menu-title"); + Contents.AddChild(new BoxContainer + { + Orientation = LayoutOrientation.Vertical, + Children = + { + new BoxContainer + { + Orientation = LayoutOrientation.Vertical, + Children = + { + (new Label() + { + Text = Loc.GetString("station-ai-earlyleave-menu-text") + }), + new BoxContainer + { + Orientation = LayoutOrientation.Horizontal, + Align = AlignMode.Center, + Children = + { + (ConfirmButton = new Button + { + Text = Loc.GetString("station-ai-earlyleave-menu-confirm") + }), + + (new Control() + { + MinSize = new Vector2(20, 0) + }), + + (DenyButton = new Button + { + Text = Loc.GetString("station-ai-earlyleave-menu-deny") + }) + } + } + } + } + } + }); + } +} diff --git a/Content.Server/_Goobstation/Silicon/AiEarlyLeave/StationAiEarlyLeaveEui.cs b/Content.Server/_Goobstation/Silicon/AiEarlyLeave/StationAiEarlyLeaveEui.cs new file mode 100644 index 0000000000..941f0b40d5 --- /dev/null +++ b/Content.Server/_Goobstation/Silicon/AiEarlyLeave/StationAiEarlyLeaveEui.cs @@ -0,0 +1,41 @@ +using Content.Server.EUI; +using Content.Shared.Eui; +using Robust.Shared.Network; + +using Content.Server.Silicons.StationAi; +using Content.Shared._Goobstation.Silicon.AiEarlyLeave; +using Content.Shared.Silicons.StationAi; + +namespace Content.Server._Goobstation.Silicon.AiEarlyLeave; + +public sealed class StationAiEarlyLeaveEui : BaseEui +{ + private readonly Entity _aiCore; + private readonly EntityUid _ai; + private readonly NetUserId _userId; + private readonly StationAiEarlyLeaveSystem _leaveSystem; + + public StationAiEarlyLeaveEui(Entity aiCore, EntityUid ai, NetUserId userId, StationAiEarlyLeaveSystem leaveSystem) + { + _aiCore = aiCore; + _ai = ai; + _userId = userId; + _leaveSystem = leaveSystem; + } + + public override void HandleMessage(EuiMessageBase msg) + { + base.HandleMessage(msg); + + if (msg is not StationAiEarlyLeaveMessage choice || + !choice.Confirmed) + { + Close(); + return; + } + + _leaveSystem.EarlyLeave(_aiCore, _ai, _userId); + + Close(); + } +} diff --git a/Content.Server/_Goobstation/Silicon/AiEarlyLeave/StationAiEarlyLeaveSystem.cs b/Content.Server/_Goobstation/Silicon/AiEarlyLeave/StationAiEarlyLeaveSystem.cs new file mode 100644 index 0000000000..e7ce54751a --- /dev/null +++ b/Content.Server/_Goobstation/Silicon/AiEarlyLeave/StationAiEarlyLeaveSystem.cs @@ -0,0 +1,68 @@ +using Content.Shared._Goobstation.Silicon.AiEarlyLeave; +using Content.Shared._Goobstation.Silicon.Components; +using Linguini.Bundle.Errors; + +using Content.Server.Chat.Systems; +using Robust.Shared.Player; +using Content.Server.EUI; +using Robust.Shared.Network; +using Content.Server.Station.Components; +using Content.Server.Station.Systems; +using Content.Shared.Silicons.StationAi; + +namespace Content.Server._Goobstation.Silicon.AiEarlyLeave; + +public sealed class StationAiEarlyLeaveSystem : SharedStationAiEarlyLeaveSystem +{ + [Dependency] private readonly ChatSystem _chat = default!; + [Dependency] private readonly ISharedPlayerManager _player = default!; + [Dependency] private readonly EuiManager _euiManager = default!; + [Dependency] private readonly StationJobsSystem _jobs = default!; + [Dependency] private readonly StationSystem _station = default!; + + protected override void RequestEarlyLeave(Entity aiCore, EntityUid insertedAi) + { + if (!_player.TryGetSessionByEntity(insertedAi, out var aiSession)) + return; + + if (aiSession == null) + return; + + _euiManager.OpenEui(new StationAiEarlyLeaveEui(aiCore, insertedAi, aiSession.UserId, this), aiSession); + } + + public void EarlyLeave(Entity aiCore, EntityUid insertedAi, NetUserId userId) + { + var station = _station.GetOwningStation(insertedAi); + + // removes all of player's jobs on all stations + foreach (var uniqueStation in _station.GetStationsSet()) + { + if (!TryComp(uniqueStation, out var stationJobs)) + continue; + + if (!_jobs.TryGetPlayerJobs(uniqueStation, userId, out var jobs, stationJobs)) + continue; + + foreach (var job in jobs) + { + _jobs.TryAdjustJobSlot(uniqueStation, job, 1, clamp: true); + } + + _jobs.TryRemovePlayerJobs(uniqueStation, userId, stationJobs); + } + + if (station is not { }) + return; + // Start of DeltaV Changes + var message = Loc.GetString( + "station-ai-earlyleave-announcement", + ("character", Name(insertedAi)), + ("entity", insertedAi) + ); + + _chat.DispatchStationAnnouncement(insertedAi, message, Loc.GetString("station-ai-earlyleave-announcement-sender")); + // End of DeltaV Changes + QueueDel(insertedAi); + } +} diff --git a/Content.Shared/_Goobstation/Silicon/AiEarlyLeave/SharedStationAiEarlyLeaveSystem.cs b/Content.Shared/_Goobstation/Silicon/AiEarlyLeave/SharedStationAiEarlyLeaveSystem.cs new file mode 100644 index 0000000000..a190512728 --- /dev/null +++ b/Content.Shared/_Goobstation/Silicon/AiEarlyLeave/SharedStationAiEarlyLeaveSystem.cs @@ -0,0 +1,51 @@ +using Content.Shared.Verbs; +using Content.Shared._Goobstation.Silicon.Components; +using Content.Shared.Silicons.StationAi; +using Robust.Shared.Containers; +using Robust.Shared.Utility; + +namespace Content.Shared._Goobstation.Silicon.AiEarlyLeave; + +public abstract partial class SharedStationAiEarlyLeaveSystem : EntitySystem +{ + [Dependency] private readonly SharedContainerSystem _containers = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent>(OnGetVerbs); + } + + private EntityUid? GetInsertedAI(Entity ent) + { + if (!_containers.TryGetContainer(ent.Owner, StationAiHolderComponent.Container, out var container) + || container.ContainedEntities.Count != 1) + return null; + + return container.ContainedEntities[0]; + } + + private void OnGetVerbs(Entity ent, ref GetVerbsEvent args) + { + if (!TryComp(ent.Owner, out var aiCoreComp)) + return; + + var aiCore = new Entity(ent.Owner, aiCoreComp); + + if (GetInsertedAI(aiCore) is { } insertedAi && insertedAi == args.User) + { + args.Verbs.Add(new Verb() + { + Text = Loc.GetString("station-ai-earlyleave-button"), + Act = () => RequestEarlyLeave(aiCore, insertedAi), + Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/Spare/poweronoff.svg.192dpi.png")), + }); + } + } + + protected virtual void RequestEarlyLeave(Entity aiCore, EntityUid insertedAi) + { + + } +} diff --git a/Content.Shared/_Goobstation/Silicon/AiEarlyLeave/StationAiEarlyLeaveMessage.cs b/Content.Shared/_Goobstation/Silicon/AiEarlyLeave/StationAiEarlyLeaveMessage.cs new file mode 100644 index 0000000000..a04d5a3f14 --- /dev/null +++ b/Content.Shared/_Goobstation/Silicon/AiEarlyLeave/StationAiEarlyLeaveMessage.cs @@ -0,0 +1,15 @@ +using Content.Shared.Eui; +using Robust.Shared.Serialization; + +namespace Content.Shared._Goobstation.Silicon.AiEarlyLeave; + +[Serializable, NetSerializable] +public sealed class StationAiEarlyLeaveMessage : EuiMessageBase +{ + public readonly bool Confirmed; + + public StationAiEarlyLeaveMessage(bool confirmed) + { + Confirmed = confirmed; + } +} diff --git a/Content.Shared/_Goobstation/Silicon/Components/StationAiEarlyLeaveComponent.cs b/Content.Shared/_Goobstation/Silicon/Components/StationAiEarlyLeaveComponent.cs new file mode 100644 index 0000000000..e3ad8e6ec0 --- /dev/null +++ b/Content.Shared/_Goobstation/Silicon/Components/StationAiEarlyLeaveComponent.cs @@ -0,0 +1,15 @@ +using Content.Shared.Silicons.Bots; +using Robust.Shared.GameObjects; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; + +namespace Content.Shared._Goobstation.Silicon.Components; + +/// +/// Grants a verb allowing early leave / cryo functionality. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class StationAiEarlyLeaveComponent : Component +{ + +} diff --git a/Resources/Locale/en-US/_Goobstation/silicon/station_ai.ftl b/Resources/Locale/en-US/_Goobstation/silicon/station_ai.ftl new file mode 100644 index 0000000000..a2cd4a5df2 --- /dev/null +++ b/Resources/Locale/en-US/_Goobstation/silicon/station_ai.ftl @@ -0,0 +1,7 @@ +station-ai-earlyleave-button = AI shutdown +station-ai-earlyleave-menu-title = AI Shutdown +station-ai-earlyleave-menu-text = Are you sure? You will be removed from the round and your job slot will be opened. +station-ai-earlyleave-menu-confirm = Confirm +station-ai-earlyleave-menu-deny = Deny +station-ai-earlyleave-announcement = {$character} (Station AI) is shutting down and entering hibernation! +station-ai-earlyleave-announcement-sender = AI Core diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index 362ad14442..1b90d2b0bd 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -275,6 +275,7 @@ batteryRechargeEfficiency: 0 # Setting to zero until the light flickering issue associated with dynamic power loads is fixed - type: StationAiCore - type: StationAiVision + - type: StationAiEarlyLeave # Goobstation - type: InteractionOutline - type: Sprite snapCardinals: true