// SPDX-FileCopyrightText: 2024 deltanedas <39013340+deltanedas@users.noreply.github.com> // SPDX-FileCopyrightText: 2024 deltanedas <@deltanedas:kde.org> // SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com> // // SPDX-License-Identifier: AGPL-3.0-or-later using Content.Shared._Shitmed.Autodoc.Components; using Content.Shared._Shitmed.Autodoc.Systems; using Content.Shared._Shitmed.Medical.Surgery; using Content.Shared.Body.Part; using Content.Shared.Hands.Components; using Content.Shared.Whitelist; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; namespace Content.Shared._Shitmed.Autodoc; [Serializable, NetSerializable, DataRecord] public sealed partial class AutodocProgram { public List Steps = new(); public bool SkipFailed; public string Title = string.Empty; } /// /// Something the autodoc can do during a program. /// [ImplicitDataDefinitionForInheritors] public partial interface IAutodocStep { /// /// Title of this step to display in the UI /// string Title { get; } /// /// Run the step, returning true if it is instantly complete and ready to go to the next step, or false if it needs to wait for something else. /// Should throw AutodocError for player-facing errors. /// bool Run(Entity ent, SharedAutodocSystem autodoc); /// /// Check that this step is valid, returning false if it isn't. /// bool Validate(Entity ent, SharedAutodocSystem autodoc) { return true; } } /// /// Perform a surgery including any prerequesites like opening an incision. /// [Serializable, NetSerializable] public sealed partial class SurgeryAutodocStep : IAutodocStep { /// /// The type of part to operate on. /// [DataField(required: true)] public BodyPartType Part; /// /// The symmetry required. If this is null then symmetry is not checked (operate on an arbitrary leg for example). /// [DataField] public BodyPartSymmetry? Symmetry; /// /// The ID of the surgery to perform. /// [DataField(required: true)] public EntProtoId Surgery; public string Title { get { var protoMan = IoCManager.Resolve(); var proto = protoMan.Index(Surgery); var part = Loc.GetString("autodoc-body-part-" + Part.ToString()); return Loc.GetString("autodoc-program-step-surgery", ("part", part), ("name", proto.Name)); } } bool IAutodocStep.Run(Entity ent, SharedAutodocSystem autodoc) { var patient = autodoc.GetPatientOrThrow((ent.Owner, ent.Comp1)); if (autodoc.FindPart(patient, Part, Symmetry) is not {} part) throw new AutodocError("body-part"); if (!autodoc.StartSurgery((ent.Owner, ent.Comp1), patient, part, Surgery)) throw new AutodocError("surgery-impossible"); return false; // wait for the surgery to be completed before going onto the next program step } bool IAutodocStep.Validate(Entity ent, SharedAutodocSystem autodoc) { return autodoc.IsSurgery(Surgery); } } /// /// Grab a specific item from storage, failing if it isn't found. /// [Serializable, NetSerializable] public sealed partial class GrabItemAutodocStep : IAutodocStep { /// /// The name that an item in storage must match to get grabbed. /// [DataField(required: true)] public string Name = string.Empty; public string Title => Loc.GetString("autodoc-program-step-grab-item", ("name", Name)); bool IAutodocStep.Validate(Entity ent, SharedAutodocSystem autodoc) { // client will never send a blank string for name return !string.IsNullOrEmpty(Name) && Name.Length <= 100; } bool IAutodocStep.Run(Entity ent, SharedAutodocSystem autodoc) { if (autodoc.FindItem(ent, Name) is not {} item) throw new AutodocError("item-unavailable"); autodoc.GrabItemOrThrow(ent, item); return true; } } /// /// Grab the first item that matches a whitelist, failing if none are found. /// [Serializable, NetSerializable] public abstract partial class GrabAnyItemAutodocStep : IAutodocStep { /// /// A whitelist that must be matched. /// public virtual EntityWhitelist Whitelist { get; } private EntityWhitelist? _whitelist; /// /// Name that represents the whitelist. /// public virtual LocId Name { get; } string IAutodocStep.Title => Loc.GetString("autodoc-program-step-grab-any", ("name", Loc.GetString(Name))); bool IAutodocStep.Run(Entity ent, SharedAutodocSystem autodoc) { if (autodoc.FindItem(ent, _whitelist ??= Whitelist) is not {} item) throw new AutodocError("item-unavailable"); autodoc.GrabItemOrThrow(ent, item); return true; } } [Serializable, NetSerializable] public sealed partial class GrabAnyOrganAutodocStep : GrabAnyItemAutodocStep { public override EntityWhitelist Whitelist => new EntityWhitelist() { Components = ["Organ"] }; public override LocId Name => "autodoc-item-organ"; } [Serializable, NetSerializable] public sealed partial class GrabAnyBodyPartAutodocStep : GrabAnyItemAutodocStep { public override EntityWhitelist Whitelist => new EntityWhitelist() { Components = ["BodyPart"] }; public override LocId Name => "autodoc-item-part"; } /// /// Store the held item in storage, failing if it can't be picked up. /// [Serializable, NetSerializable] public sealed partial class StoreItemAutodocStep : IAutodocStep { string IAutodocStep.Title => Loc.GetString("autodoc-program-step-store-item"); bool IAutodocStep.Run(Entity ent, SharedAutodocSystem autodoc) { autodoc.StoreItemOrThrow(ent); return true; } } /// /// Gives the held item a label, failing if there is no held item. /// [Serializable, NetSerializable] public sealed partial class SetLabelAutodocStep : IAutodocStep { [DataField(required: true)] public string Label = string.Empty; string IAutodocStep.Title => Loc.GetString("autodoc-program-step-set-label", ("label", Label)); bool IAutodocStep.Validate(Entity ent, SharedAutodocSystem autodoc) { // client will never send a blank string for label return !string.IsNullOrEmpty(Label) && Label.Length <= 20; } bool IAutodocStep.Run(Entity ent, SharedAutodocSystem autodoc) { var item = autodoc.GetHeldOrThrow(ent); autodoc.LabelItem(item, Label); return true; } } /// /// Waits a number of seconds before going onto the next step. /// [Serializable, NetSerializable] public sealed partial class WaitAutodocStep : IAutodocStep { [DataField(required: true)] public int Length; string IAutodocStep.Title => Loc.GetString("autodoc-program-step-wait", ("length", Length)); bool IAutodocStep.Validate(Entity ent, SharedAutodocSystem autodoc) { return Length > 0 && Length < 30; } bool IAutodocStep.Run(Entity ent, SharedAutodocSystem autodoc) { autodoc.Say(ent, Loc.GetString("autodoc-waiting")); autodoc.DelayUpdate(ent, TimeSpan.FromSeconds(Length)); return true; // Waiting is for surgery } }