]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Implanter draw rework (#32136)
authorSlamBamActionman <83650252+SlamBamActionman@users.noreply.github.com>
Thu, 27 Feb 2025 17:57:28 +0000 (18:57 +0100)
committerGitHub <noreply@github.com>
Thu, 27 Feb 2025 17:57:28 +0000 (18:57 +0100)
* Initial commit

* Clean-up

* Fix ftl, new damage

* ftl fix for real

* Updates based on feedback

* Child implant fix

* Make the UI only open when implanter is in draw mode

* Review fixes

* shunting

Content.Client/Implants/ImplanterSystem.cs
Content.Client/Implants/UI/DeimplantBoundUserInterface.cs [new file with mode: 0644]
Content.Client/Implants/UI/DeimplantChoiceWindow.xaml [new file with mode: 0644]
Content.Client/Implants/UI/DeimplantChoiceWindow.xaml.cs [new file with mode: 0644]
Content.Client/Implants/UI/ImplanterStatusControl.cs
Content.Server/Implants/ImplanterSystem.cs
Content.Shared/Implants/Components/ImplanterComponent.cs
Content.Shared/Implants/Components/SubdermalImplantComponent.cs
Content.Shared/Implants/SharedImplanterSystem.cs
Resources/Locale/en-US/implant/implant.ftl
Resources/Prototypes/Entities/Objects/Misc/implanters.yml

index 13a90f3e3913df3443a8246eb7f1a03217692d0f..cca09f5dad1d273371b2fab53c9a31c148447a85 100644 (file)
@@ -2,11 +2,15 @@
 using Content.Client.Items;
 using Content.Shared.Implants;
 using Content.Shared.Implants.Components;
+using Robust.Shared.Prototypes;
 
 namespace Content.Client.Implants;
 
 public sealed class ImplanterSystem : SharedImplanterSystem
 {
+    [Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!;
+    [Dependency] private readonly IPrototypeManager _proto = default!;
+
     public override void Initialize()
     {
         base.Initialize();
@@ -17,6 +21,18 @@ public sealed class ImplanterSystem : SharedImplanterSystem
 
     private void OnHandleImplanterState(EntityUid uid, ImplanterComponent component, ref AfterAutoHandleStateEvent args)
     {
+        if (_uiSystem.TryGetOpenUi<DeimplantBoundUserInterface>(uid, DeimplantUiKey.Key, out var bui))
+        {
+            Dictionary<string, string> implants = new();
+            foreach (var implant in component.DeimplantWhitelist)
+            {
+                if (_proto.TryIndex(implant, out var proto))
+                    implants.Add(proto.ID, proto.Name);
+            }
+
+            bui.UpdateState(implants, component.DeimplantChosen);
+        }
+
         component.UiUpdateNeeded = true;
     }
 }
diff --git a/Content.Client/Implants/UI/DeimplantBoundUserInterface.cs b/Content.Client/Implants/UI/DeimplantBoundUserInterface.cs
new file mode 100644 (file)
index 0000000..0857cdf
--- /dev/null
@@ -0,0 +1,35 @@
+using Content.Shared.Implants;
+using Robust.Client.UserInterface;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.Implants.UI;
+
+public sealed class DeimplantBoundUserInterface : BoundUserInterface
+{
+    [Dependency] private readonly IPrototypeManager _protomanager = default!;
+
+    [ViewVariables]
+    private DeimplantChoiceWindow? _window;
+
+    public DeimplantBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
+    {
+    }
+
+    protected override void Open()
+    {
+        base.Open();
+
+        _window = this.CreateWindow<DeimplantChoiceWindow>();
+
+        _window.OnImplantChange += implant => SendMessage(new DeimplantChangeVerbMessage(implant));
+    }
+    
+    public void UpdateState(Dictionary<string, string> implantList, string? implant)
+    {
+        if (_window != null)
+        {
+            _window.UpdateImplantList(implantList);
+            _window.UpdateState(implant);
+        }
+    }
+}
diff --git a/Content.Client/Implants/UI/DeimplantChoiceWindow.xaml b/Content.Client/Implants/UI/DeimplantChoiceWindow.xaml
new file mode 100644 (file)
index 0000000..f0de9f3
--- /dev/null
@@ -0,0 +1,12 @@
+<controls:FancyWindow xmlns="https://spacestation14.io"
+               xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
+               Title="{Loc 'implanter-set-draw-window'}"
+               MinSize="5 30">
+    <BoxContainer Orientation="Vertical" Margin="10 5">
+        <Label Text="{Loc 'implanter-set-draw-info'}" Margin="0 0 0 5"/>
+        <BoxContainer Orientation="Horizontal">
+            <Label Text="{Loc 'implanter-set-draw-type'}" Margin="0 0 5 0"/>
+            <OptionButton Name="ImplantSelector"/> <!-- Populated in LoadVerbs -->
+        </BoxContainer>
+    </BoxContainer>
+</controls:FancyWindow>
diff --git a/Content.Client/Implants/UI/DeimplantChoiceWindow.xaml.cs b/Content.Client/Implants/UI/DeimplantChoiceWindow.xaml.cs
new file mode 100644 (file)
index 0000000..a7ce50d
--- /dev/null
@@ -0,0 +1,53 @@
+using Content.Client.UserInterface.Controls;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.XAML;
+using System.Linq;
+
+namespace Content.Client.Implants.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class DeimplantChoiceWindow : FancyWindow
+{
+    public Action<string?>? OnImplantChange;
+
+    private Dictionary<string, string> _implants = new();
+
+    private string? _chosenImplant;
+
+    public DeimplantChoiceWindow()
+    {
+        RobustXamlLoader.Load(this);
+
+        ImplantSelector.OnItemSelected += args =>
+        {
+            OnImplantChange?.Invoke(_implants.ElementAt(args.Id).Key);
+            ImplantSelector.SelectId(args.Id);
+        };
+    }
+
+    public void UpdateImplantList(Dictionary<string, string> implants)
+    {
+        _implants = implants;
+        int i = 0;
+        ImplantSelector.Clear();
+        foreach (var implantDict in _implants)
+        {
+            ImplantSelector.AddItem(implantDict.Value, i);
+            i++;
+        }
+    }
+
+    public void UpdateState(string? implant)
+    {
+        _chosenImplant = implant;
+
+        for (int id = 0; id < ImplantSelector.ItemCount; id++)
+        {
+            if (_implants.ElementAt(id).Key.Equals(_chosenImplant))
+            {
+                ImplantSelector.SelectId(id);
+                break;
+            }
+        }
+    }
+}
index e2ffabd17d9e214b83437b79972959173fcb7c9b..569dd785d749ae7b47b39ae7e4f9ec80b466e460 100644 (file)
@@ -4,17 +4,20 @@ using Content.Client.UserInterface.Controls;
 using Content.Shared.Implants.Components;
 using Robust.Client.UserInterface;
 using Robust.Client.UserInterface.Controls;
+using Robust.Shared.Prototypes;
 using Robust.Shared.Timing;
 
 namespace Content.Client.Implants.UI;
 
 public sealed class ImplanterStatusControl : Control
 {
+    [Dependency] private readonly IPrototypeManager _prototype = default!;
     private readonly ImplanterComponent _parent;
     private readonly RichTextLabel _label;
 
     public ImplanterStatusControl(ImplanterComponent parent)
     {
+        IoCManager.InjectDependencies(this);
         _parent = parent;
         _label = new RichTextLabel { StyleClasses = { StyleNano.StyleClassItemStatus } };
         _label.MaxWidth = 350;
@@ -43,12 +46,25 @@ public sealed class ImplanterStatusControl : Control
             _ => Loc.GetString("injector-invalid-injector-toggle-mode")
         };
 
-        var implantName = _parent.ImplanterSlot.HasItem
-            ? _parent.ImplantData.Item1
-            : Loc.GetString("implanter-empty-text");
+        if (_parent.CurrentMode == ImplanterToggleMode.Draw)
+        {
+            string implantName = _parent.DeimplantChosen != null
+                ? (_prototype.TryIndex(_parent.DeimplantChosen.Value, out EntityPrototype? implantProto) ? implantProto.Name : Loc.GetString("implanter-empty-text"))
+                : Loc.GetString("implanter-empty-text");
+
+            _label.SetMarkup(Loc.GetString("implanter-label-draw",
+                    ("implantName", implantName),
+                    ("modeString", modeStringLocalized)));
+        }
+        else
+        {
+            var implantName = _parent.ImplanterSlot.HasItem
+                ? _parent.ImplantData.Item1
+                : Loc.GetString("implanter-empty-text");
 
-        _label.SetMarkup(Loc.GetString("implanter-label",
-                ("implantName", implantName),
-                ("modeString", modeStringLocalized)));
+            _label.SetMarkup(Loc.GetString("implanter-label-inject",
+                    ("implantName", implantName),
+                    ("modeString", modeStringLocalized)));
+        }
     }
 }
index 5efd5dc6fb32bfe3e056c5a3cdec974dc9645851..5023b1b3e4961c0b973ec6d3522456cd1908d77c 100644 (file)
@@ -68,8 +68,6 @@ public sealed partial class ImplanterSystem : SharedImplanterSystem
         args.Handled = true;
     }
 
-
-
     /// <summary>
     /// Attempt to implant someone else.
     /// </summary>
index 80330aa7e66d717f4477959f06343b0344d1376b..f956bd65028f1271720f86491656a21a2ca375de 100644 (file)
@@ -1,4 +1,5 @@
-using Content.Shared.Containers.ItemSlots;
+using Content.Shared.Containers.ItemSlots;
+using Content.Shared.Damage;
 using Content.Shared.Whitelist;
 using Robust.Shared.GameStates;
 using Robust.Shared.Prototypes;
@@ -49,7 +50,7 @@ public sealed partial class ImplanterComponent : Component
     /// </summary>
     [ViewVariables(VVAccess.ReadWrite)]
     [DataField]
-    public float DrawTime = 60f;
+    public float DrawTime = 25f;
 
     /// <summary>
     /// Good for single-use injectors
@@ -82,6 +83,30 @@ public sealed partial class ImplanterComponent : Component
     [DataField(required: true)]
     public ItemSlot ImplanterSlot = new();
 
+    /// <summary>
+    /// If true, the implanter may be used to remove all kinds of (deimplantable) implants without selecting any.
+    /// </summary>
+    [DataField]
+    public bool AllowDeimplantAll = false;
+
+    /// <summary>
+    /// The subdermal implants that may be removed via this implanter
+    /// </summary>
+    [DataField]
+    public List<EntProtoId> DeimplantWhitelist = new();
+
+    /// <summary>
+    /// The subdermal implants that may be removed via this implanter
+    /// </summary>
+    [DataField]
+    public DamageSpecifier DeimplantFailureDamage = new();
+
+    /// <summary>
+    /// Chosen implant to remove, if necessary.
+    /// </summary>
+    [AutoNetworkedField]
+    public EntProtoId? DeimplantChosen = null;
+
     public bool UiUpdateNeeded;
 }
 
index 09ef05e48a8f2198144b5655ade28eacdc73cfd8..bd0ff09678bbb74e99f67f48c8587a082080018a 100644 (file)
@@ -49,6 +49,13 @@ public sealed partial class SubdermalImplantComponent : Component
     /// </summary>
     [DataField]
     public EntityWhitelist? Blacklist;
+    
+    /// <summary>
+    /// If set, this ProtoId is used when attempting to draw the implant instead.
+    /// Useful if the implant is a child to another implant and you don't want to differentiate between them when drawing.
+    /// </summary>
+    [DataField]
+    public EntProtoId? DrawableProtoIdOverride;
 }
 
 /// <summary>
index 6f394fb932a41f0986d9ce1ff4e2429fc45a1d00..1b0ca7fecc7f40357fa68f6574e4be737aba819f 100644 (file)
@@ -1,14 +1,18 @@
 using System.Diagnostics.CodeAnalysis;
 using System.Linq;
 using Content.Shared.Containers.ItemSlots;
+using Content.Shared.Damage;
 using Content.Shared.DoAfter;
 using Content.Shared.Examine;
 using Content.Shared.Forensics;
 using Content.Shared.IdentityManagement;
 using Content.Shared.Implants.Components;
+using Content.Shared.Interaction.Events;
 using Content.Shared.Popups;
+using Content.Shared.Verbs;
 using Content.Shared.Whitelist;
 using Robust.Shared.Containers;
+using Robust.Shared.Prototypes;
 using Robust.Shared.Serialization;
 using Robust.Shared.Utility;
 
@@ -21,6 +25,9 @@ public abstract class SharedImplanterSystem : EntitySystem
     [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
     [Dependency] private readonly SharedPopupSystem _popup = default!;
     [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
+    [Dependency] private readonly DamageableSystem _damageableSystem = default!;
+    [Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!;
+    [Dependency] private readonly IPrototypeManager _proto = default!;
 
     public override void Initialize()
     {
@@ -29,6 +36,10 @@ public abstract class SharedImplanterSystem : EntitySystem
         SubscribeLocalEvent<ImplanterComponent, ComponentInit>(OnImplanterInit);
         SubscribeLocalEvent<ImplanterComponent, EntInsertedIntoContainerMessage>(OnEntInserted);
         SubscribeLocalEvent<ImplanterComponent, ExaminedEvent>(OnExamine);
+
+        SubscribeLocalEvent<ImplanterComponent, UseInHandEvent>(OnUseInHand);
+        SubscribeLocalEvent<ImplanterComponent, GetVerbsEvent<InteractionVerb>>(OnVerb);
+        SubscribeLocalEvent<ImplanterComponent, DeimplantChangeVerbMessage>(OnSelected);
     }
 
     private void OnImplanterInit(EntityUid uid, ImplanterComponent component, ComponentInit args)
@@ -37,6 +48,10 @@ public abstract class SharedImplanterSystem : EntitySystem
             component.ImplanterSlot.StartingItem = component.Implant;
 
         _itemSlots.AddItemSlot(uid, ImplanterComponent.ImplanterSlotId, component.ImplanterSlot);
+
+        component.DeimplantChosen ??= component.DeimplantWhitelist.FirstOrNull();
+
+        Dirty(uid, component);
     }
 
     private void OnEntInserted(EntityUid uid, ImplanterComponent component, EntInsertedIntoContainerMessage args)
@@ -56,10 +71,49 @@ public abstract class SharedImplanterSystem : EntitySystem
     {
         if (!TryComp<ImplantedComponent>(target, out var implanted))
             return false;
-
         var implantPrototype = Prototype(implant);
         return implanted.ImplantContainer.ContainedEntities.Any(entity => Prototype(entity) == implantPrototype);
     }
+
+    private void OnVerb(EntityUid uid, ImplanterComponent component, GetVerbsEvent<InteractionVerb> args)
+    {
+        if (!args.CanAccess || !args.CanInteract)
+            return;
+
+        if (component.CurrentMode == ImplanterToggleMode.Draw)
+        {
+            args.Verbs.Add(new InteractionVerb()
+            {
+                Text = Loc.GetString("implanter-set-draw-verb"),
+                Act = () => TryOpenUi(uid, args.User, component)
+            });
+        }
+    }
+
+    private void OnUseInHand(EntityUid uid, ImplanterComponent? component, UseInHandEvent args)
+    {
+        if (!Resolve(uid, ref component))
+            return;
+
+        if (component.CurrentMode == ImplanterToggleMode.Draw)
+            TryOpenUi(uid, args.User, component);
+    }
+
+    private void OnSelected(EntityUid uid, ImplanterComponent component, DeimplantChangeVerbMessage args)
+    {
+        component.DeimplantChosen = args.Implant;
+        SetSelectedDeimplant(uid, args.Implant, component: component);
+    }
+
+    private void TryOpenUi(EntityUid uid, EntityUid user, ImplanterComponent? component = null)
+    {
+        if (!Resolve(uid, ref component))
+            return;
+        _uiSystem.TryToggleUi(uid, DeimplantUiKey.Key, user);
+        component.DeimplantChosen ??= component.DeimplantWhitelist.FirstOrNull();
+        Dirty(uid, component);
+    }
+
     //Instantly implant something and add all necessary components and containers.
     //Set to draw mode if not implant only
     public void Implant(EntityUid user, EntityUid target, EntityUid implanter, ImplanterComponent component)
@@ -142,42 +196,105 @@ public abstract class SharedImplanterSystem : EntitySystem
         {
             var implantCompQuery = GetEntityQuery<SubdermalImplantComponent>();
 
-            foreach (var implant in implantContainer.ContainedEntities)
+            if (component.AllowDeimplantAll)
             {
-                if (!implantCompQuery.TryGetComponent(implant, out var implantComp))
-                    continue;
-
-                //Don't remove a permanent implant and look for the next that can be drawn
-                if (!_container.CanRemove(implant, implantContainer))
+                foreach (var implant in implantContainer.ContainedEntities)
                 {
-                    var implantName = Identity.Entity(implant, EntityManager);
-                    var targetName = Identity.Entity(target, EntityManager);
-                    var failedPermanentMessage = Loc.GetString("implanter-draw-failed-permanent",
-                        ("implant", implantName), ("target", targetName));
-                    _popup.PopupEntity(failedPermanentMessage, target, user);
+                    if (!implantCompQuery.TryGetComponent(implant, out var implantComp))
+                        continue;
+
+                    //Don't remove a permanent implant and look for the next that can be drawn
+                    if (!_container.CanRemove(implant, implantContainer))
+                    {
+                        DrawPermanentFailurePopup(implant, target, user);
+                        permanentFound = implantComp.Permanent;
+                        continue;
+                    }
+
+                    DrawImplantIntoImplanter(implanter, target, implant, implantContainer, implanterContainer, implantComp);
                     permanentFound = implantComp.Permanent;
-                    continue;
-                }
-
-                _container.Remove(implant, implantContainer);
-                implantComp.ImplantedEntity = null;
-                _container.Insert(implant, implanterContainer);
-                permanentFound = implantComp.Permanent;
 
-                var ev = new TransferDnaEvent { Donor = target, Recipient = implanter };
-                RaiseLocalEvent(target, ref ev);
+                    //Break so only one implant is drawn
+                    break;
+                }
 
-                //Break so only one implant is drawn
-                break;
+                if (component.CurrentMode == ImplanterToggleMode.Draw && !component.ImplantOnly && !permanentFound)
+                    ImplantMode(implanter, component);
             }
+            else
+            {
+                EntityUid? implant = null;
+                var implants = implantContainer.ContainedEntities;
+                foreach (var implantEntity in implants)
+                {
+                    if (TryComp<SubdermalImplantComponent>(implantEntity, out var subdermalComp))
+                    {
+                        if (component.DeimplantChosen == subdermalComp.DrawableProtoIdOverride ||
+                            (Prototype(implantEntity) != null && component.DeimplantChosen == Prototype(implantEntity)!))
+                            implant = implantEntity;
+                    }
+                }
 
-            if (component.CurrentMode == ImplanterToggleMode.Draw && !component.ImplantOnly && !permanentFound)
-                ImplantMode(implanter, component);
+                if (implant != null && implantCompQuery.TryGetComponent(implant, out var implantComp))
+                {
+                    //Don't remove a permanent implant
+                    if (!_container.CanRemove(implant.Value, implantContainer))
+                    {
+                        DrawPermanentFailurePopup(implant.Value, target, user);
+                        permanentFound = implantComp.Permanent;
+
+                    }
+                    else
+                    {
+                        DrawImplantIntoImplanter(implanter, target, implant.Value, implantContainer, implanterContainer, implantComp);
+                        permanentFound = implantComp.Permanent;
+                    }
+
+                    if (component.CurrentMode == ImplanterToggleMode.Draw && !component.ImplantOnly && !permanentFound)
+                        ImplantMode(implanter, component);
+                }
+                else
+                {
+                    DrawCatastrophicFailure(implanter, component, user);
+                }
+            }
 
             Dirty(implanter, component);
+
+        }
+        else
+        {
+            DrawCatastrophicFailure(implanter, component, user);
         }
     }
 
+    private void DrawPermanentFailurePopup(EntityUid implant, EntityUid target, EntityUid user)
+    {
+        var implantName = Identity.Entity(implant, EntityManager);
+        var targetName = Identity.Entity(target, EntityManager);
+        var failedPermanentMessage = Loc.GetString("implanter-draw-failed-permanent",
+            ("implant", implantName), ("target", targetName));
+        _popup.PopupEntity(failedPermanentMessage, target, user);
+    }
+
+    private void DrawImplantIntoImplanter(EntityUid implanter, EntityUid target, EntityUid implant, BaseContainer implantContainer, ContainerSlot implanterContainer, SubdermalImplantComponent implantComp)
+    {
+        _container.Remove(implant, implantContainer);
+        implantComp.ImplantedEntity = null;
+        _container.Insert(implant, implanterContainer);
+
+        var ev = new TransferDnaEvent { Donor = target, Recipient = implanter };
+        RaiseLocalEvent(target, ref ev);
+    }
+
+    private void DrawCatastrophicFailure(EntityUid implanter, ImplanterComponent component, EntityUid user)
+    {
+        _damageableSystem.TryChangeDamage(user, component.DeimplantFailureDamage, ignoreResistances: true, origin: implanter);
+        var userName = Identity.Entity(user, EntityManager);
+        var failedCatastrophicallyMessage = Loc.GetString("implanter-draw-failed-catastrophically", ("user", userName));
+        _popup.PopupEntity(failedCatastrophicallyMessage, user, PopupType.MediumCaution);
+    }
+
     private void ImplantMode(EntityUid uid, ImplanterComponent component)
     {
         component.CurrentMode = ImplanterToggleMode.Inject;
@@ -216,6 +333,17 @@ public abstract class SharedImplanterSystem : EntitySystem
         else
             _appearance.SetData(uid, ImplanterVisuals.Full, implantFound, appearance);
     }
+
+    public void SetSelectedDeimplant(EntityUid uid, string? implant, ImplanterComponent? component = null)
+    {
+        if (!Resolve(uid, ref component, false))
+            return;
+
+        if (implant != null && _proto.TryIndex(implant, out EntityPrototype? proto))
+            component.DeimplantChosen = proto;
+
+        Dirty(uid, component);
+    }
 }
 
 [Serializable, NetSerializable]
@@ -243,3 +371,39 @@ public sealed class AddImplantAttemptEvent : CancellableEntityEventArgs
         Implanter = implanter;
     }
 }
+
+
+[Serializable, NetSerializable]
+public sealed class DeimplantBuiState : BoundUserInterfaceState
+{
+    public readonly string? Implant;
+
+    public Dictionary<string, string> ImplantList;
+
+    public DeimplantBuiState(string? implant, Dictionary<string, string> implantList)
+    {
+        Implant = implant;
+        ImplantList = implantList;
+    }
+}
+
+
+/// <summary>
+/// Change the chosen implanter in the UI.
+/// </summary>
+[Serializable, NetSerializable]
+public sealed class DeimplantChangeVerbMessage : BoundUserInterfaceMessage
+{
+    public readonly string? Implant;
+
+    public DeimplantChangeVerbMessage(string? implant)
+    {
+        Implant = implant;
+    }
+}
+
+[Serializable, NetSerializable]
+public enum DeimplantUiKey : byte
+{
+    Key
+}
index 073757f53c7858e0f46db91fc2fa77b3aaee07d7..8cddef4c81e0698281cb83df525ac80a02be0794 100644 (file)
@@ -4,15 +4,24 @@ implanter-component-implanting-target = {$user} is trying to implant you with so
 implanter-component-implant-failed = The {$implant} cannot be given to {$target}!
 implanter-draw-failed-permanent = The {$implant} in {$target} is fused with { OBJECT($target) } and cannot be removed!
 implanter-draw-failed = You tried to remove an implant but found nothing.
+implanter-draw-failed-catastrophically = The implanter finds nothing and catastrophically fails, shunting genetic material into {$user}'s hand!
 implanter-component-implant-already = {$target} already has the {$implant}!
 
 ## UI
+implanter-set-draw-verb = Set Implant Draw
+implanter-set-draw-window = Set Implant Draw
+implanter-set-draw-info = Select the implant type this implanter should remove:
+implanter-set-draw-type = Implant type:
+
 implanter-draw-text = Draw
 implanter-inject-text = Inject
 
 implanter-empty-text = Empty
 
-implanter-label = [color=green]{$implantName}[/color]
+implanter-label-inject = [color=green]{$implantName}[/color]
+    Mode: [color=white]{$modeString}[/color]
+
+implanter-label-draw = [color=red]{$implantName}[/color]
     Mode: [color=white]{$modeString}[/color]
 
 implanter-contained-implant-text = [color=green]{$desc}[/color]
index d40f48dc7d906101b10362d0b31dc19a1ed553ee..58aefc93fd88b9084f097bbcf8173a5a2da20436 100644 (file)
@@ -2,7 +2,7 @@
 
 - type: entity
   name: implanter
-  description: A syringe exclusively designed for the injection and extraction of subdermal implants.
+  description: A syringe exclusively designed for the injection and extraction of subdermal implants. Use care when extracting implants, as incorrect draw settings may injure the user.
   id: BaseImplanter
   parent: BaseItem
   abstract: true
         whitelist:
           tags:
             - SubdermalImplant
+      allowDeimplantAll: false
+      deimplantWhitelist:
+      - SadTromboneImplant
+      - LightImplant
+      - BikeHornImplant
+      - TrackingImplant
+      - StorageImplant
+      - FreedomImplant
+      - UplinkImplant
+      - EmpImplant
+      - ScramImplant
+      - DnaScramblerImplant
+      - MicroBombImplant
+      - MacroBombImplant
+      - DeathAcidifierImplant
+      - DeathRattleImplant
+      - MindShieldImplant
+      deimplantFailureDamage:
+        types:
+          Cellular: 50
+          Heat: 10
     - type: Sprite
       sprite: Objects/Specific/Medical/implanter.rsi
       state: implanter0
           implantOnly:
             True: {state: broken}
             False: {state: implanter0}
+    - type: UserInterface
+      interfaces:
+        enum.DeimplantUiKey.Key:
+          type: DeimplantBoundUserInterface
 
 - type: entity
   id: Implanter