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
🆑
qol: Autosurgeons now can insert toolkit implants into your left arm
when used in the left hand/with left arm zone selected
/🆑
This commit is contained in:
SmArtKar
2025-03-16 19:27:30 +01:00
committed by GitHub
parent 621b350fad
commit 359e0180a0
6 changed files with 59 additions and 62 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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,8 +200,14 @@ INITIALIZE_IMMEDIATE(/obj/item/organ)
/// Returns a line to be displayed regarding valid insertion zones
/obj/item/organ/proc/zones_tip()
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()
START_PROCESSING(SSobj, src)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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]