From 359e0180a02e85602161678934d94524a7cb9e5f Mon Sep 17 00:00:00 2001 From: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Date: Sun, 16 Mar 2025 19:27:30 +0100 Subject: [PATCH] Generalizes multi-slot implants and makes autosurgeons support them (#89845) ## About The Pull Request Generalized code from #89479 to be applicable to all organs and made autosurgeons respect which hand you're using them in (and what zone you have selected) ## Why It's Good For The Game Allows us to add leg implants with similar behavior easier, gets of placeholder types for left arm slots, and you'd ***expect*** to be able to use autosurgeons to insert implants anywhere but your right arm. ## Changelog :cl: qol: Autosurgeons now can insert toolkit implants into your left arm when used in the left hand/with left arm zone selected /:cl: --- code/__DEFINES/inventory.dm | 4 +- .../lavaland/mining_loot/cursed_katana.dm | 3 +- code/modules/surgery/organs/_organ.dm | 10 ++- code/modules/surgery/organs/autosurgeon.dm | 17 ++++- .../organs/internal/cyberimp/augments_arms.dm | 72 +++++-------------- code/modules/surgery/organs/organ_movement.dm | 15 ++++ 6 files changed, 59 insertions(+), 62 deletions(-) diff --git a/code/__DEFINES/inventory.dm b/code/__DEFINES/inventory.dm index 68689fe86a2..e33056d5158 100644 --- a/code/__DEFINES/inventory.dm +++ b/code/__DEFINES/inventory.dm @@ -135,9 +135,9 @@ DEFINE_BITFIELD(no_equip_flags, list( //defines for the index of hands #define LEFT_HANDS 1 #define RIGHT_HANDS 2 -/// Checks if the value is "left" - same as ISEVEN, but used primarily for hand or foot index contexts +/// Checks if the value is "right" - same as ISEVEN, but used primarily for hand or foot index contexts #define IS_RIGHT_INDEX(value) (value % 2 == 0) -/// Checks if the value is "right" - same as ISODD, but used primarily for hand or foot index contexts +/// Checks if the value is "left" - same as ISODD, but used primarily for hand or foot index contexts #define IS_LEFT_INDEX(value) (value % 2 != 0) //flags for female outfits: How much the game can safely "take off" the uniform without it looking weird diff --git a/code/modules/mining/lavaland/mining_loot/cursed_katana.dm b/code/modules/mining/lavaland/mining_loot/cursed_katana.dm index ad7cbeb8a54..c7b9fd340e8 100644 --- a/code/modules/mining/lavaland/mining_loot/cursed_katana.dm +++ b/code/modules/mining/lavaland/mining_loot/cursed_katana.dm @@ -20,8 +20,7 @@ to_chat(user, span_userdanger("The mass goes up your arm and goes inside it!")) playsound(user, 'sound/effects/magic/demon_consume.ogg', 50, TRUE) var/index = user.get_held_index_of_item(src) - zone = (index == LEFT_HANDS ? BODY_ZONE_L_ARM : BODY_ZONE_R_ARM) - SetSlotFromZone() + swap_zone(IS_LEFT_INDEX(index) ? BODY_ZONE_L_ARM : BODY_ZONE_R_ARM) user.temporarilyRemoveItemFromInventory(src, TRUE) Insert(user) diff --git a/code/modules/surgery/organs/_organ.dm b/code/modules/surgery/organs/_organ.dm index fb4e3562550..d7d009da7d4 100644 --- a/code/modules/surgery/organs/_organ.dm +++ b/code/modules/surgery/organs/_organ.dm @@ -66,6 +66,8 @@ var/list/organ_effects /// String displayed when the organ has decayed. var/failing_desc = "has decayed for too long, and has turned a sickly color. It probably won't work without repairs." + /// Assoc list of alternate zones where this can organ be slotted to organ slot for that zone + var/list/valid_zones = null // Players can look at prefs before atoms SS init, and without this // they would not be able to see external organs, such as moth wings. @@ -198,7 +200,13 @@ INITIALIZE_IMMEDIATE(/obj/item/organ) /// Returns a line to be displayed regarding valid insertion zones /obj/item/organ/proc/zones_tip() - return span_notice("It should be inserted in the [parse_zone(zone)].") + if (!valid_zones) + return span_notice("It should be inserted in the [parse_zone(zone)].") + + var/list/fit_zones = list() + for (var/valid_zone in valid_zones) + fit_zones += parse_zone(valid_zone) + return span_notice("It should be inserted in the [english_list(fit_zones, and_text = " or ")].") ///Used as callbacks by object pooling /obj/item/organ/proc/exit_wardrobe() diff --git a/code/modules/surgery/organs/autosurgeon.dm b/code/modules/surgery/organs/autosurgeon.dm index dce035d3c5d..258fab3a634 100644 --- a/code/modules/surgery/organs/autosurgeon.dm +++ b/code/modules/surgery/organs/autosurgeon.dm @@ -90,7 +90,22 @@ span_notice("You press a button on [src] as it plunges into your body."), ) - stored_organ.Insert(target)//insert stored organ into the user + if (stored_organ.valid_zones && user.get_held_index_of_item(src)) + var/list/checked_zones = list(user.zone_selected) + if (IS_RIGHT_INDEX(user.get_held_index_of_item(src))) + checked_zones += list(BODY_ZONE_R_ARM, BODY_ZONE_R_LEG) + else + checked_zones += list(BODY_ZONE_L_ARM, BODY_ZONE_L_LEG) + + for (var/check_zone in checked_zones) + if (stored_organ.valid_zones[check_zone]) + stored_organ.swap_zone(check_zone) + break + + if (!stored_organ.Insert(target)) // insert stored organ into the user + balloon_alert(user, "insertion failed!") + return + stored_organ = null name = initial(name) //get rid of the organ in the name playsound(target.loc, 'sound/items/weapons/circsawhit.ogg', 50, vary = TRUE) diff --git a/code/modules/surgery/organs/internal/cyberimp/augments_arms.dm b/code/modules/surgery/organs/internal/cyberimp/augments_arms.dm index 8142ca8488e..41fa9babe7a 100644 --- a/code/modules/surgery/organs/internal/cyberimp/augments_arms.dm +++ b/code/modules/surgery/organs/internal/cyberimp/augments_arms.dm @@ -2,9 +2,14 @@ name = "arm-mounted implant" desc = "You shouldn't see this! Adminhelp and report this as an issue on github!" zone = BODY_ZONE_R_ARM + slot = ORGAN_SLOT_RIGHT_ARM_AUG icon_state = "toolkit_generic" w_class = WEIGHT_CLASS_SMALL actions_types = list(/datum/action/item_action/organ_action/toggle) + valid_zones = list( + BODY_ZONE_R_ARM = ORGAN_SLOT_RIGHT_ARM_AUG, + BODY_ZONE_L_ARM = ORGAN_SLOT_LEFT_ARM_AUG, + ) ///A ref for the arm we're taking up. Mostly for the unregister signal upon removal var/obj/hand //A list of typepaths to create and insert into ourself on init @@ -17,10 +22,6 @@ var/extend_sound = 'sound/vehicles/mecha/mechmove03.ogg' /// Sound played when retracting var/retract_sound = 'sound/vehicles/mecha/mechmove03.ogg' - /// Organ slot that the implant occupies for the right arm - var/right_arm_organ_slot = ORGAN_SLOT_RIGHT_ARM_AUG - /// Organ slot that the implant occupies for the left arm - var/left_arm_organ_slot = ORGAN_SLOT_LEFT_ARM_AUG /obj/item/organ/cyberimp/arm/Initialize(mapload) . = ..() @@ -32,8 +33,6 @@ var/atom/new_item = new typepath(src) items_list += WEAKREF(new_item) - SetSlotFromZone() - /obj/item/organ/cyberimp/arm/Destroy() hand = null active_item = null @@ -48,28 +47,6 @@ /datum/action/item_action/organ_action/toggle/toolkit desc = "You can also activate your empty hand or the tool in your hand to open the tools radial menu." -/obj/item/organ/cyberimp/arm/proc/SetSlotFromZone() - switch(zone) - if(BODY_ZONE_L_ARM) - slot = left_arm_organ_slot - if(BODY_ZONE_R_ARM) - slot = right_arm_organ_slot - else - CRASH("Invalid zone for [type]") - update_appearance() - -/obj/item/organ/cyberimp/arm/pre_surgical_insertion(mob/living/user, mob/living/carbon/new_owner, target_zone) - // Ensure that in case we're somehow placed elsewhere (HARS-esque bs) we don't break our zone - if (target_zone != BODY_ZONE_R_ARM && target_zone != BODY_ZONE_L_ARM) - return FALSE - - zone = target_zone - SetSlotFromZone() - return ..() - -/obj/item/organ/cyberimp/arm/zones_tip() - return span_notice("It should be inserted in the [parse_zone(right_arm_organ_slot)] or [parse_zone(left_arm_organ_slot)].") - /obj/item/organ/cyberimp/arm/on_mob_insert(mob/living/carbon/arm_owner) . = ..() RegisterSignal(arm_owner, COMSIG_CARBON_POST_ATTACH_LIMB, PROC_REF(on_limb_attached)) @@ -134,8 +111,8 @@ active_item.resistance_flags = active_item::resistance_flags if(owner) owner.visible_message( - span_notice("[owner] retracts [active_item] back into [owner.p_their()] [zone == BODY_ZONE_R_ARM ? "right" : "left"] arm."), - span_notice("[active_item] snaps back into your [zone == BODY_ZONE_R_ARM ? "right" : "left"] arm."), + span_notice("[owner] retracts [active_item] back into [owner.p_their()] [parse_zone(zone)]."), + span_notice("[active_item] snaps back into your [parse_zone(zone)]."), span_hear("You hear a short mechanical noise."), ) @@ -154,13 +131,12 @@ return active_item = augment - active_item.resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF ADD_TRAIT(active_item, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT) active_item.slot_flags = null active_item.set_custom_materials(null) - var/side = zone == BODY_ZONE_R_ARM? RIGHT_HANDS : LEFT_HANDS + var/side = zone == BODY_ZONE_R_ARM ? RIGHT_HANDS : LEFT_HANDS var/hand = owner.get_empty_held_index_for_side(side) if(hand) owner.put_in_hand(active_item, hand) @@ -180,8 +156,8 @@ for(var/i in failure_message) to_chat(owner, i) return - owner.visible_message(span_notice("[owner] extends [active_item] from [owner.p_their()] [zone == BODY_ZONE_R_ARM ? "right" : "left"] arm."), - span_notice("You extend [active_item] from your [zone == BODY_ZONE_R_ARM ? "right" : "left"] arm."), + owner.visible_message(span_notice("[owner] extends [active_item] from [owner.p_their()] [parse_zone(zone)]."), + span_notice("You extend [active_item] from your [parse_zone(zone)]."), span_hear("You hear a short mechanical noise.")) playsound(get_turf(owner), extend_sound, 50, TRUE) @@ -217,40 +193,32 @@ else Retract() - /obj/item/organ/cyberimp/arm/gun/emp_act(severity) . = ..() if(. & EMP_PROTECT_SELF) return if(prob(30/severity) && owner && !(organ_flags & ORGAN_FAILING)) Retract() - owner.visible_message(span_danger("A loud bang comes from [owner]\'s [zone == BODY_ZONE_R_ARM ? "right" : "left"] arm!")) + owner.visible_message(span_danger("A loud bang comes from [owner]\'s [parse_zone(zone)]!")) playsound(get_turf(owner), 'sound/items/weapons/flashbang.ogg', 100, TRUE) - to_chat(owner, span_userdanger("You feel an explosion erupt inside your [zone == BODY_ZONE_R_ARM ? "right" : "left"] arm as your implant breaks!")) + to_chat(owner, span_userdanger("You feel an explosion erupt inside your [parse_zone(zone)] as your implant breaks!")) owner.adjust_fire_stacks(20) owner.ignite_mob() owner.adjustFireLoss(25) organ_flags |= ORGAN_FAILING - /obj/item/organ/cyberimp/arm/gun/laser name = "arm-mounted laser implant" desc = "A variant of the arm cannon implant that fires lethal laser beams. The cannon emerges from the subject's arm and remains inside when not in use." icon_state = "arm_laser" items_to_create = list(/obj/item/gun/energy/laser/mounted/augment) -/obj/item/organ/cyberimp/arm/gun/laser/l - zone = BODY_ZONE_L_ARM - /obj/item/organ/cyberimp/arm/gun/taser name = "arm-mounted taser implant" desc = "A variant of the arm cannon implant that fires electrodes and disabler shots. The cannon emerges from the subject's arm and remains inside when not in use." icon_state = "arm_taser" items_to_create = list(/obj/item/gun/energy/e_gun/advtaser/mounted) -/obj/item/organ/cyberimp/arm/gun/taser/l - zone = BODY_ZONE_L_ARM - /obj/item/organ/cyberimp/arm/toolset name = "integrated toolset implant" desc = "A stripped-down version of the engineering cyborg toolset, designed to be installed on subject's arm. Contain advanced versions of every tool." @@ -265,9 +233,6 @@ /obj/item/multitool/cyborg, ) -/obj/item/organ/cyberimp/arm/toolset/l - zone = BODY_ZONE_L_ARM - //The order of the item list for this implant is not alphabetized due to it actually affecting how it shows up playerside when opening the implant /obj/item/organ/cyberimp/arm/paperwork name = "integrated paperwork implant" @@ -284,9 +249,6 @@ /obj/item/stamp/denied, ) -/obj/item/organ/cyberimp/arm/paperwork/l - zone = BODY_ZONE_L_ARM - /obj/item/organ/cyberimp/arm/paperwork/emag_act(mob/user, obj/item/card/emag/emag_card) for(var/datum/weakref/created_item in items_list) var/obj/potential_tool = created_item.resolve() @@ -317,7 +279,6 @@ desc = "A cybernetic implant that allows the user to project a healing beam from their hand." items_to_create = list(/obj/item/gun/medbeam) - /obj/item/organ/cyberimp/arm/flash name = "integrated high-intensity photon projector" //Why not desc = "An integrated projector mounted onto a user's arm that is able to be used as a powerful flash." @@ -407,8 +368,10 @@ zone = BODY_ZONE_R_ARM slot = ORGAN_SLOT_RIGHT_ARM_MUSCLE - right_arm_organ_slot = ORGAN_SLOT_RIGHT_ARM_MUSCLE - left_arm_organ_slot = ORGAN_SLOT_LEFT_ARM_MUSCLE + valid_zones = list( + BODY_ZONE_R_ARM = ORGAN_SLOT_RIGHT_ARM_MUSCLE, + BODY_ZONE_L_ARM = ORGAN_SLOT_LEFT_ARM_MUSCLE, + ) actions_types = list() @@ -433,9 +396,6 @@ ///Tracks how soon we can perform another slam attack COOLDOWN_DECLARE(slam_cooldown) -/obj/item/organ/cyberimp/arm/strongarm/l - zone = BODY_ZONE_L_ARM - /obj/item/organ/cyberimp/arm/strongarm/Initialize(mapload) . = ..() AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/strongarm) diff --git a/code/modules/surgery/organs/organ_movement.dm b/code/modules/surgery/organs/organ_movement.dm index 53842739a5c..1f0bc726910 100644 --- a/code/modules/surgery/organs/organ_movement.dm +++ b/code/modules/surgery/organs/organ_movement.dm @@ -290,4 +290,19 @@ /// Proc that gets called when someone starts surgically inserting the organ /obj/item/organ/proc/pre_surgical_insertion(mob/living/user, mob/living/carbon/new_owner, target_zone) + if (!valid_zones) + return TRUE + + // Ensure that in case we're somehow placed elsewhere (HARS-esque bs) we don't break our zone + if (!valid_zones[target_zone]) + return FALSE + + swap_zone(target_zone) return TRUE + +/// Readjusts the organ to fit into a different body zone/slot +/obj/item/organ/proc/swap_zone(target_zone) + if (!valid_zones[target_zone]) + CRASH("[src]'s ([type]) swap_zone was called with invalid zone [target_zone]") + zone = target_zone + slot = valid_zones[zone]