From 266f9782282ea5bfd0c7828ccc1ef594d1deae6a Mon Sep 17 00:00:00 2001
From: beck-thompson <107373427+beck-thompson@users.noreply.github.com>
Date: Tue, 18 Aug 2026 20:13:53 +0000
Subject: [PATCH] Add xenoborg bio knife (#45152)
* bio knife
* fix licence
* fix damage override
* remove weird null
* Abstrac the methods
* add new icon
* add bool
* add hash set
---
.../Metabolism/MetabolizerSystem.cs | 38 ++++++++++
Content.Shared/Verbs/VerbCategory.cs | 3 +
.../BonusDamageOnMetabolismComponent.cs | 38 ++++++++++
.../BonusDamageOnMetabolismSystem.cs | 65 ++++++++++++++++++
Resources/Locale/en-US/verbs/verb-system.ftl | 1 +
.../Specific/Robotics/borg_modules.yml | 17 +++++
.../Entities/Objects/Weapons/Melee/knife.yml | 28 ++++++++
.../Actions/actions_borg.rsi/meta.json | 5 +-
.../actions_borg.rsi/xenoborg-bio-knife.png | Bin 0 -> 979 bytes
.../borgmodule.rsi/icon-xenoborg-bio.png | Bin 0 -> 293 bytes
.../Robotics/borgmodule.rsi/meta.json | 5 +-
.../Weapons/Melee/bioknife.rsi/icon.png | Bin 0 -> 428 bytes
.../Weapons/Melee/bioknife.rsi/meta.json | 14 ++++
13 files changed, 212 insertions(+), 2 deletions(-)
create mode 100644 Content.Shared/Weapons/Melee/Metabolizer/BonusDamageOnMetabolismComponent.cs
create mode 100644 Content.Shared/Weapons/Melee/Metabolizer/BonusDamageOnMetabolismSystem.cs
create mode 100644 Resources/Textures/Interface/Actions/actions_borg.rsi/xenoborg-bio-knife.png
create mode 100644 Resources/Textures/Objects/Specific/Robotics/borgmodule.rsi/icon-xenoborg-bio.png
create mode 100644 Resources/Textures/Objects/Weapons/Melee/bioknife.rsi/icon.png
create mode 100644 Resources/Textures/Objects/Weapons/Melee/bioknife.rsi/meta.json
diff --git a/Content.Shared/Metabolism/MetabolizerSystem.cs b/Content.Shared/Metabolism/MetabolizerSystem.cs
index 4df78e1a84..76e46a8a49 100644
--- a/Content.Shared/Metabolism/MetabolizerSystem.cs
+++ b/Content.Shared/Metabolism/MetabolizerSystem.cs
@@ -340,5 +340,43 @@ public sealed partial class MetabolizerSystem : EntitySystem
return ent.Comp.MetabolizerTypes.Add(metabolizer);
}
+
+ ///
+ /// Checks if the given organ has the given metabolizer.
+ ///
+ /// The organ we are checking.
+ /// The metabolizer we are checking for.
+ /// Returns true if the organ has the given metabolizer, false if the organ doesn't (this includes it not being a metabolizer at all)
+ public bool HasMetabolizer(Entity targetOrgan, ProtoId targetMetabolizer)
+ {
+ if (!Resolve(targetOrgan.Owner, ref targetOrgan.Comp, false))
+ return false;
+
+ if (targetOrgan.Comp.MetabolizerTypes is null)
+ return false;
+
+ if (!targetOrgan.Comp.MetabolizerTypes.Contains(targetMetabolizer))
+ return false;
+
+ return true;
+ }
+
+ ///
+ /// Checks if a given body has a metabolizer of the target type.
+ ///
+ /// The body we are checking for the metabolizer.
+ /// The metabolizer we are checking for.
+ ///
+ public bool BodyHasMetabolizer(EntityUid targetBody, ProtoId targetMetabolizer)
+ {
+ var metabolisms = _body.EnumerateOrgans(targetBody);
+ foreach (var organ in metabolisms)
+ {
+ if (HasMetabolizer((organ, organ.Comp2), targetMetabolizer))
+ return true;
+ }
+
+ return false;
+ }
}
diff --git a/Content.Shared/Verbs/VerbCategory.cs b/Content.Shared/Verbs/VerbCategory.cs
index c824858af4..6ca28af6c0 100644
--- a/Content.Shared/Verbs/VerbCategory.cs
+++ b/Content.Shared/Verbs/VerbCategory.cs
@@ -88,5 +88,8 @@ namespace Content.Shared.Verbs
public static readonly VerbCategory Adjust =
new("verb-categories-adjust", "/Textures/Interface/VerbIcons/screwdriver.png");
+
+ public static readonly VerbCategory Metabolizers =
+ new("verb-categories-select-target", "/Textures/Interface/VerbIcons/group.svg.192dpi.png");
}
}
diff --git a/Content.Shared/Weapons/Melee/Metabolizer/BonusDamageOnMetabolismComponent.cs b/Content.Shared/Weapons/Melee/Metabolizer/BonusDamageOnMetabolismComponent.cs
new file mode 100644
index 0000000000..4bc4817194
--- /dev/null
+++ b/Content.Shared/Weapons/Melee/Metabolizer/BonusDamageOnMetabolismComponent.cs
@@ -0,0 +1,38 @@
+using Content.Shared.Damage;
+using Content.Shared.Metabolism;
+using Content.Shared.Mobs;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Weapons.Melee.Metabolizer;
+
+///
+/// Deals bonus damage when hitting an entity with the selected metabolizer
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class BonusDamageOnMetabolismComponent : Component
+{
+ ///
+ /// List of metabolizers that cannot be selected.
+ ///
+ [DataField]
+ public List> ExcludedMetabolizers = new();
+
+ ///
+ /// The currently selected metabolizer, can be null for none.
+ ///
+ [DataField, AutoNetworkedField]
+ public ProtoId? SelectedMetabolizer;
+
+ ///
+ /// Amount of extra damage to deal when hitting an entity with the SelectedMetabolizer metabolism
+ ///
+ [DataField(required: true)]
+ public DamageSpecifier Damage = new();
+
+ ///
+ /// Will only do damage to mobs in one of these states. If null, doesn't care about the state of the mob.
+ ///
+ [DataField]
+ public HashSet ValidMobStates = [ MobState.Alive, MobState.Critical ];
+}
diff --git a/Content.Shared/Weapons/Melee/Metabolizer/BonusDamageOnMetabolismSystem.cs b/Content.Shared/Weapons/Melee/Metabolizer/BonusDamageOnMetabolismSystem.cs
new file mode 100644
index 0000000000..a68af6071f
--- /dev/null
+++ b/Content.Shared/Weapons/Melee/Metabolizer/BonusDamageOnMetabolismSystem.cs
@@ -0,0 +1,65 @@
+using System.Linq;
+using Content.Shared.Metabolism;
+using Content.Shared.Mobs.Components;
+using Content.Shared.Mobs.Systems;
+using Content.Shared.Verbs;
+using Content.Shared.Weapons.Melee.Events;
+
+namespace Content.Shared.Weapons.Melee.Metabolizer;
+
+public sealed partial class BonusDamageOnMetabolismSystem : EntitySystem
+{
+ [Dependency] private MetabolizerSystem _metabolizer = default!;
+
+ [SubscribeLocalEvent]
+ private void OnGetVerb(Entity ent, ref GetVerbsEvent args)
+ {
+ if (!args.CanAccess || !args.CanInteract)
+ return;
+
+ var allMetabolizers = ProtoMan.EnumeratePrototypes().ToList().OrderBy(x => Loc.GetString(x.LocalizedName));
+
+ byte index = 0;
+ foreach (var metabolizer in allMetabolizers)
+ {
+ if (ent.Comp.ExcludedMetabolizers.Contains(metabolizer))
+ continue;
+
+ var currIndex = index;
+ var verb = new Verb
+ {
+ Priority = currIndex,
+ Category = VerbCategory.Metabolizers,
+ Disabled = ent.Comp.SelectedMetabolizer == metabolizer,
+ Act = () =>
+ {
+ ent.Comp.SelectedMetabolizer = metabolizer;
+ Dirty(ent);
+ },
+ Text = Loc.GetString(metabolizer.LocalizedName),
+ };
+ args.Verbs.Add(verb);
+ index++;
+ }
+ }
+
+ [SubscribeLocalEvent]
+ private void OnSwingTrigger(Entity ent, ref MeleeHitEvent args)
+ {
+ if (ent.Comp.SelectedMetabolizer is null)
+ return;
+
+ foreach (var hitEntity in args.HitEntities)
+ {
+ if (TryComp(hitEntity, out var mobState) && !ent.Comp.ValidMobStates.Contains(mobState.CurrentState))
+ continue;
+
+ if (!_metabolizer.BodyHasMetabolizer(hitEntity, ent.Comp.SelectedMetabolizer.Value))
+ continue;
+
+ // Add the bonus damage and quit!
+ args.BonusDamage =+ ent.Comp.Damage;
+ return;
+ }
+ }
+}
diff --git a/Resources/Locale/en-US/verbs/verb-system.ftl b/Resources/Locale/en-US/verbs/verb-system.ftl
index cc9167d906..e396618ebe 100644
--- a/Resources/Locale/en-US/verbs/verb-system.ftl
+++ b/Resources/Locale/en-US/verbs/verb-system.ftl
@@ -29,6 +29,7 @@ verb-categories-select-type = Select Type
verb-categories-fax = Set Destination
verb-categories-power-level = Power Level
verb-categories-adjust = Adjust
+verb-categories-select-target = Set Target
verb-common-toggle-light = Toggle light
verb-common-close = Close
diff --git a/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml b/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml
index d0d8777036..449d533e6a 100644
--- a/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml
+++ b/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml
@@ -1827,6 +1827,23 @@
- type: BorgModuleIcon
icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-sword2-module }
+- type: entity
+ parent: [ BaseXenoborgModuleScout, BaseProviderBorgModule, BaseXenoborgContraband ]
+ id: XenoborgModuleBioKinfe
+ name: bio knife xenoborg module
+ description: Module with an bio knife.
+ components:
+ - type: Sprite
+ layers:
+ - state: xenoborg_scout
+ - state: icon-xenoborg-sword2
+ - type: ItemBorgModule
+ hands:
+ - item: XenoborgBioKnife
+ - item: JetpackXenoborg
+ - type: BorgModuleIcon
+ icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-bio-knife }
+
- type: entity
parent: [ BaseXenoborgModuleStealth, BaseProviderBorgModule, BaseXenoborgContraband ]
id: XenoborgModuleHypo
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/knife.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/knife.yml
index e10669f291..6faa52681b 100644
--- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/knife.yml
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/knife.yml
@@ -188,6 +188,34 @@
sprite: Objects/Weapons/Melee/kukri_knife.rsi
storedSprite: null
+- type: entity
+ parent: [ CombatKnife, BaseXenoborgContraband ]
+ id: XenoborgBioKnife
+ name: bio knife
+ description: Melds itself to specific metabolizers and will deal extra damage to those targets!
+ components:
+ - type: Sprite
+ sprite: Objects/Weapons/Melee/bioknife.rsi
+ state: icon
+ - type: MeleeWeapon
+ attackRate: 1.0
+ damage:
+ types:
+ Slash: 10
+ - type: Item
+ sprite: Objects/Weapons/Melee/bioknife.rsi
+ - type: BonusDamageOnMetabolism
+ selectedMetabolizer: Human
+ excludedMetabolizers:
+ - Vox # Their biology is resistant to this!
+ # The rest of these are just so we don't clog up the menu with useless items.
+ - Bloodsucker
+ - Dragon
+ - Changeling
+ damage:
+ types:
+ Poison: 10
+
- type: entity
parent: [ClothingHeadHatGreyFlatcap, BaseMinorContraband]
id: BladedFlatcapGrey
diff --git a/Resources/Textures/Interface/Actions/actions_borg.rsi/meta.json b/Resources/Textures/Interface/Actions/actions_borg.rsi/meta.json
index 72aa505acb..0f1c2a0c1c 100644
--- a/Resources/Textures/Interface/Actions/actions_borg.rsi/meta.json
+++ b/Resources/Textures/Interface/Actions/actions_borg.rsi/meta.json
@@ -1,7 +1,7 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
- "copyright": "Taken from vgstation at commit https://github.com/vgstation-coders/vgstation13/commit/cdbcb1e858b11f083994a7a269ed67ef5b452ce9, inflatable module by FungiFellow (GitHub), Module actions by Scarky0. chem, adv-chem, and adv-mining by mubururu_, xenoborg actions by Samuka-C (github), advclown by ThatGuyUSA. c20r and esword by RedBookcase on Github, artistry-module by Kittygyat, with help from TiniestShark, xenoborg-laser3-module and xenoborg-deflector-module by august-sun(GitHub), Xenoborg Nanite action by YoungThugSS14 (Gihutb)",
+ "copyright": "Taken from vgstation at commit https://github.com/vgstation-coders/vgstation13/commit/cdbcb1e858b11f083994a7a269ed67ef5b452ce9, inflatable module by FungiFellow (GitHub), Module actions by Scarky0. chem, adv-chem, and adv-mining by mubururu_, xenoborg actions by Samuka-C (github), advclown by ThatGuyUSA. c20r and esword by RedBookcase on Github, artistry-module by Kittygyat, with help from TiniestShark, xenoborg-laser3-module and xenoborg-deflector-module by august-sun(GitHub), Xenoborg Nanite action and xenoborg-bio-knife by YoungThugSS14 (Gihutb)",
"size": {
"x": 32,
"y": 32
@@ -193,6 +193,9 @@
{
"name":"xenoborg-sword2-module"
},
+ {
+ "name":"xenoborg-bio-knife"
+ },
{
"name":"xenoborg-tile-module"
},
diff --git a/Resources/Textures/Interface/Actions/actions_borg.rsi/xenoborg-bio-knife.png b/Resources/Textures/Interface/Actions/actions_borg.rsi/xenoborg-bio-knife.png
new file mode 100644
index 0000000000000000000000000000000000000000..55d07fe3a14864c25517160115f0e2cac03ee52d
GIT binary patch
literal 979
zcmZ`&Z%7ky82-((od~)Qei+o47EutyAOlw&%t9+GmPHU|ZABlV%$zM-x6@6FShmbh
z7TKDH(2~#zk+#^}q|~r{%
zTX*uhc+dLaklnf?cT;%jde0^OGt-{3aaoiuj7*t_5R07o9CCa5&Fbx)`PgWER(5%<
zS?q3JIkHt*y%UIKhF4&lNW+jP5#M~v4B2lFy{m5wJ$jBcj>f1r57E|nY=lwY8Dizb
z?s1}*n$o)IctA|mspv(6bt>ABhlalkAY2Kt5Qj|!f%TyymQH2OtgsVhlYl+Zc`_Y=*FxO>@r+8;1
zn(dAYrll&G9;5DAsYiU%pirBu}Sdhsg@u*LR@fw=TXml_0vK?-NY
zM%fi7(^pM%szG+|A8ynb5lbX7thBTu(;^I>6D>+XQ0i9%|WW9
zwa}VDGYKK9-6?1b(#q4jGEozyg>A9FGtv?Z$mSctPG!c+iSXYtK~x3?65#C
zkK&-aJ79qYs$U&*y=Deb)-|w)7?20k9oUqgfN
zd%vJ|H1;8x7xw9u&yWV}KQ74zvtxZ~5DB6!t1eZ=_8h~dHv9@7iaj>uMI!qv7D@oL
zyH~DhHo6ELZ1YY_fKRo&k(bTm;$au}aw$N^xBNo*Zo$N)UYm56f?^$9ARAHLGrE(_
zaBSJ?*nGc>Gi0q4@e?FN9O>)xYH623XbCAslBN=n9-i|!DogxO3Ljrb<&|Wc;|B9$
u$$O1_cYpNSwSk3J%W507^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10g0sLQ
zvY3H^TNs2H8D`Cq01C2~c>21szvAF!6VrUWEg%pmRPE{F7!q+fIYEMTae_!sQ=kDC
zn}ornRR_}(Pt+GCEAo7to_^mjr!(GpQ>D(5EQjrX0`EGrfxu3+%1urUoie@#-iaov
z4V^K)C#Fq)UA;-RAxBYlTCfPKLAPcv+a%$Kr#HWUFirXQzo$3L7pI=mfLO$|3FyYX
W@}Px$W=TXrR9J=Wl`(6=KorOSmTpo(N|xKYC~;6i2GHkL=N2DOO}3x?k#jH
zWat;@Cy=fZ7i-7vQYmJB4keOHB0ZD4xa1Fk+`UJB|Gbxb0V*9qL9n9e{!}-h*E_Gp
zW1csq_*%JvAXt&_FYxm0r(*s7Jtg-}bi#jrpLTEGY)n#cr3UiA0id=opc>w_>Tulw
zI5&mwLRkg!z?B&ojqcF3>cXB394cDS>0FXwTwy*7Q?dWxg$*R(Q5EL1PzsJBE!YQ-
zBm+m@3S_}ucOc}*z;Wm#WQ-92tk>T#3Bbtqgbg2uA
zF-Ckiri-&B*_J~9$n&NY|M6&r10dUSsNFKD-7*P4d2l`)6M*EUUvYR;_ENw{g)_z|
z>UkM(;pn7~+G}Q>N@b4XkM{?Bel4`avmQ>i