diff --git a/code/__HELPERS/trait_helpers.dm b/code/__HELPERS/trait_helpers.dm index 3c97894faa3..3455f302935 100644 --- a/code/__HELPERS/trait_helpers.dm +++ b/code/__HELPERS/trait_helpers.dm @@ -265,6 +265,9 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai ///An item that can be pointed at mobs, while on non-help intent. #define TRAIT_CAN_POINT_WITH "can_point_with" +///An organ that was inserted into a dead mob, that has not been revived yet +#define TRAIT_ORGAN_INSERTED_WHILE_DEAD "organ_inserted_while_dead" + // // common trait sources #define TRAIT_GENERIC "generic" diff --git a/code/datums/uplink_items/uplink_general.dm b/code/datums/uplink_items/uplink_general.dm index ee1a2b44af6..162c2e18527 100644 --- a/code/datums/uplink_items/uplink_general.dm +++ b/code/datums/uplink_items/uplink_general.dm @@ -619,7 +619,13 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item)) item = /obj/item/storage/box/syndie_kit/teleporter cost = 40 - +/datum/uplink_item/device_tools/organ_extractor + name = "Organ Extractor" + desc = "A device that can remove organs or cybernetic implants from a target, and stores them inside. \ + Stored organs can be implanted into the user, or into other targets. Synthesizes chemicals to keep the organs fresh." + reference = "OREX" + item = /obj/item/organ_extractor + cost = 20 //Space Suits and Hardsuits /datum/uplink_item/suits diff --git a/code/game/objects/items/weapons/storage/belt.dm b/code/game/objects/items/weapons/storage/belt.dm index 6a612dba6c5..20be9c3c465 100644 --- a/code/game/objects/items/weapons/storage/belt.dm +++ b/code/game/objects/items/weapons/storage/belt.dm @@ -194,7 +194,8 @@ /obj/item/wrench/medical, /obj/item/handheld_defibrillator, /obj/item/reagent_containers/applicator, - /obj/item/geiger_counter + /obj/item/geiger_counter, + /obj/item/organ_extractor ) /obj/item/storage/belt/medical/surgery diff --git a/code/modules/surgery/organs/organ.dm b/code/modules/surgery/organs/organ.dm index 80cc843d18c..5760a51ebaf 100644 --- a/code/modules/surgery/organs/organ.dm +++ b/code/modules/surgery/organs/organ.dm @@ -139,6 +139,8 @@ return TRUE if(is_found_within(/obj/machinery/clonepod)) return TRUE + if(is_found_within(/obj/item/organ_extractor)) + return TRUE if(isturf(loc)) if(world.time - last_freezer_update_time > freezer_update_period) // I don't want to loop through everything in the tile constantly, especially since it'll be a pile of organs diff --git a/code/modules/surgery/organs/organ_extractor.dm b/code/modules/surgery/organs/organ_extractor.dm new file mode 100644 index 00000000000..b02ff3633bb --- /dev/null +++ b/code/modules/surgery/organs/organ_extractor.dm @@ -0,0 +1,156 @@ +/obj/item/organ_extractor + name = "organ extractor" + desc = "A device that can remove organs from a target, and store them inside. Stored organs can be implanted into the user. Synthesizes chemicals to keep the organ fresh." + icon = 'icons/obj/surgery.dmi' + icon_state = "organ_extractor" + item_state = "" + w_class = WEIGHT_CLASS_SMALL + origin_tech = "biotech=6;materials=5;syndicate=2" + var/obj/item/organ/internal/storedorgan + +/obj/item/organ_extractor/examine(mob/user) + . = ..() + if(storedorgan) + . += "It has [storedorgan] floating around inside the jar!" + . += "You can Screwdriver [src] to try to adjust [storedorgan]'s configuration." + . += "You can Wrench [src] to eject [storedorgan]!" + +/obj/item/organ_extractor/screwdriver_act(mob/living/user, obj/item/I) + if(storedorgan) + return storedorgan.screwdriver_act(user, I) + +/obj/item/organ_extractor/wrench_act(mob/living/user, obj/item/I) + if(storedorgan) + to_chat(user, "You unwrench the jar, and [storedorgan] falls onto the floor!") + storedorgan.forceMove(get_turf(user)) + storedorgan = null + playsound(src, 'sound/effects/splat.ogg', 50, TRUE) + overlays.Cut() + return TRUE + +/obj/item/organ_extractor/emag_act(mob/user) + if(storedorgan) + storedorgan.emag_act(user) + +/obj/item/organ_extractor/emp_act(severity) + if(storedorgan) + storedorgan.emp_act(severity) + +/obj/item/organ_extractor/attack_self(mob/user) + insert_organ(user, user) + +/obj/item/organ_extractor/attack(mob/living/M, mob/living/user, def_zone) + if(in_use) + to_chat(user, "[src] is already busy!") + return + if(!iscarbon(M)) + to_chat(user, "ERROR: [M] has no organs to harvest!") + return + var/mob/living/carbon/C = M + if(!length(C.client_mobs_in_contents)) //Basically, we don't want someone putting organs in monkeys then extracting from it. Has to be someone who had a client in the past + to_chat(user, "ERROR: [C] has no soul trace to assist in targeting the drill bit!") + return + if(!IS_HORIZONTAL(C) && C.stat == CONSCIOUS) + to_chat(user, "ERROR: [C] is not restrained, and may move during the operation! Correction required.") + return + if(storedorgan) + to_chat(user, "NOTICE: Internal organ deteced. Beginning insertion procedure!") + insert_organ(user, C) + return + in_use = TRUE + var/obj/item/chosen_organ = tgui_input_list(user, "Please select an organ for removal", "Organ Selection", C.internal_organs) + if(!chosen_organ || !user.Adjacent(C)) + in_use = FALSE + return + if(!istype(chosen_organ, /obj/item/organ/internal)) //Saftey first + to_chat(user, "ERROR: [chosen_organ] is not valid for removal for unknown reasons!") + in_use = FALSE + return + if(istype(chosen_organ, /obj/item/organ/internal/brain/mmi_holder)) //This breaks shit + to_chat(user, "ERROR: [chosen_organ] is too big for the holding tank and would damage [src] too much!") + in_use = FALSE + return + if(HAS_TRAIT(chosen_organ, TRAIT_ORGAN_INSERTED_WHILE_DEAD)) + to_chat(user, "ERROR: [chosen_organ] was inserted when [C] was dead, and has no soul trace to lock onto!") + in_use = FALSE + return + var/obj/item/organ/internal/internal_organ = chosen_organ + var/drilled_organ = internal_organ.parent_organ + user.visible_message("[user] activates [src] and begins to drill into [C]!", "You level the extractor at [M] and hold down the trigger.") + to_chat(C, "You feel a lot of pain as [user] drills into your [drilled_organ]!") + playsound(get_turf(user), 'sound/weapons/circsawhit.ogg', 75, TRUE) + C.apply_damage(15, BRUTE, drilled_organ) + if(!do_after_once(user, 12 SECONDS, target = C))// Slightly longer than stamina crit, at least cuff and buckle them to a pipe or something + to_chat(user, "ERROR: Process interrupted!") + in_use = FALSE + return + if(!internal_organ || !istype(internal_organ) || !(internal_organ.owner == C)) //Organ got deleted / moved somewhere else? + to_chat(user, "ERROR: unable to find the desired organ!") + in_use = FALSE + return + user.visible_message("[user] removes [internal_organ] from [C]!", "You remove [internal_organ] from [C] as it gets sucked into [src]'s internal container!") + playsound(get_turf(user), 'sound/weapons/circsawhit.ogg', 75, TRUE) + C.apply_damage(10, BRUTE, drilled_organ) + internal_organ.remove(C) + internal_organ.forceMove(src) + storedorgan = internal_organ + storedorgan.rejuvenate() //Organ gets dumped into the internal mito tank, heals it up. And nanites for robotic organs, I guess. + in_use = FALSE + var/organ_x = internal_organ.pixel_x + var/organ_y = internal_organ.pixel_y + internal_organ.pixel_x = 2 + internal_organ.pixel_y = -2 + var/image/img = image("icon" = internal_organ, "layer" = FLOAT_LAYER) + var/matrix/MA = matrix(transform) + MA.Scale(0.66, 0.66) + img.transform = MA + img.plane = FLOAT_PLANE + overlays += img + internal_organ.pixel_x = organ_x + internal_organ.pixel_y = organ_y + overlays += "organ_extractor_2" //should look nicer for transparent stuff. + +/obj/item/organ_extractor/proc/insert_organ(mob/user, mob/our_target) + if(!storedorgan) + to_chat(user, "[src] currently has no organ stored.") + return + if(in_use) + to_chat(user, "[src] is already busy!") + return + var/user_is_target = FALSE + if(user == our_target) + user_is_target = TRUE + if(istype(storedorgan, /obj/item/organ/internal/brain) && user_is_target) + to_chat(user, "It would be really stupid to replace your brain with another one.") + return + if(!iscarbon(our_target)) + return + var/mob/living/carbon/C = our_target + in_use = TRUE + user.visible_message("[user] activates [src] and begins to drill into [C]!", "You level the extractor at [user_is_target ? "yourself" : C] and hold down the trigger.") + var/drilled_organ = storedorgan.parent_organ + C.apply_damage(5, BRUTE, drilled_organ) + playsound(get_turf(C), 'sound/weapons/circsawhit.ogg', 50, TRUE) + if(user_is_target) + if(!do_after_once(C, 7 SECONDS, target = C)) + to_chat(user, "ERROR: Process interrupted!") + in_use = FALSE + return + else + if(!do_after_once(C, 12 SECONDS, target = C)) + to_chat(user, "ERROR: Process interrupted!") + in_use = FALSE + return + C.apply_damage(10, BRUTE, drilled_organ) + var/obj/item/organ/internal/replaced = C.get_organ_slot(storedorgan.slot) + if(replaced) //Lets not destroy someones brain fully by putting someone elses brain in that slot. + replaced.remove(C) + replaced.forceMove(get_turf(src)) + storedorgan.insert(C) + playsound(get_turf(C), 'sound/weapons/circsawhit.ogg', 50, TRUE) + storedorgan = null + in_use = FALSE + overlays.Cut() + if(ishuman(C)) + var/mob/living/carbon/human/H = C + H.set_heartattack(FALSE) //Otherwise you die if you try to do an organic heart, very funny, very bad diff --git a/code/modules/surgery/organs/organ_internal.dm b/code/modules/surgery/organs/organ_internal.dm index 9f624bc55c5..988fb825dfa 100644 --- a/code/modules/surgery/organs/organ_internal.dm +++ b/code/modules/surgery/organs/organ_internal.dm @@ -71,6 +71,10 @@ if(vital) M.update_stat("Vital organ inserted") STOP_PROCESSING(SSobj, src) + if(owner.stat == DEAD) + ADD_TRAIT(src, TRAIT_ORGAN_INSERTED_WHILE_DEAD, "[UID()]") + RegisterSignal(owner, COMSIG_LIVING_DEFIBBED, PROC_REF(on_revival)) + // Removes the given organ from its owner. // Returns the removed object, which is usually just itself @@ -79,6 +83,9 @@ if(!owner) stack_trace("\'remove\' called on [src] without an owner! Mob: [M], [atom_loc_line(M)]") SEND_SIGNAL(owner, COMSIG_CARBON_LOSE_ORGAN) + REMOVE_TRAIT(src, TRAIT_ORGAN_INSERTED_WHILE_DEAD, "[UID()]") + UnregisterSignal(owner, COMSIG_LIVING_DEFIBBED) + owner = null if(M) M.internal_organs -= src @@ -395,3 +402,8 @@ if(germ_level >= INFECTION_LEVEL_TWO) if(prob(3)) //about once every 30 seconds receive_damage(1, silent = prob(30)) + +/obj/item/organ/internal/proc/on_revival() //The goal of this proc / trait is to prevent one implanting organs in a corpse, in order to remove them with the organ extractor. Has to be legitimently implanted, or on just a living person, which has risk + SIGNAL_HANDLER + REMOVE_TRAIT(src, TRAIT_ORGAN_INSERTED_WHILE_DEAD, "[UID()]") + UnregisterSignal(owner, COMSIG_LIVING_DEFIBBED) diff --git a/icons/obj/surgery.dmi b/icons/obj/surgery.dmi index 492a2dbc999..0aec30495a1 100644 Binary files a/icons/obj/surgery.dmi and b/icons/obj/surgery.dmi differ diff --git a/paradise.dme b/paradise.dme index 768f714573a..3fbe62c257b 100644 --- a/paradise.dme +++ b/paradise.dme @@ -2757,6 +2757,7 @@ #include "code\modules\surgery\organs\mmi_holder.dm" #include "code\modules\surgery\organs\organ.dm" #include "code\modules\surgery\organs\organ_external.dm" +#include "code\modules\surgery\organs\organ_extractor.dm" #include "code\modules\surgery\organs\organ_helpers.dm" #include "code\modules\surgery\organs\organ_icon.dm" #include "code\modules\surgery\organs\organ_internal.dm"