diff --git a/code/__HELPERS/_lists.dm b/code/__HELPERS/_lists.dm
index b25d1e58d5f..8ada0c20b83 100644
--- a/code/__HELPERS/_lists.dm
+++ b/code/__HELPERS/_lists.dm
@@ -20,6 +20,7 @@
#define LAZYLEN(L) length(L)
#define LAZYCLEARLIST(L) if(L) L.Cut()
#define SANITIZE_LIST(L) ( islist(L) ? L : list() )
+#define reverseList(L) reverseRange(L.Copy())
// binary search sorted insert
// IN: Object to be inserted
diff --git a/code/game/machinery/harvester.dm b/code/game/machinery/harvester.dm
new file mode 100644
index 00000000000..1531fc0c791
--- /dev/null
+++ b/code/game/machinery/harvester.dm
@@ -0,0 +1,171 @@
+/obj/machinery/harvester
+ name = "organ harvester"
+ desc = "An advanced machine used for harvesting organs and limbs from the deceased."
+ density = TRUE
+ icon = 'icons/obj/machines/harvester.dmi'
+ icon_state = "harvester"
+ verb_say = "states"
+ state_open = FALSE
+ idle_power_usage = 50
+ circuit = /obj/item/circuitboard/machine/harvester
+ light_color = LIGHT_COLOR_BLUE
+ var/interval = 20
+ var/harvesting = FALSE
+ var/list/operation_order = list() //Order of wich we harvest limbs.
+ var/allow_clothing = FALSE
+ var/allow_living = FALSE
+
+/obj/machinery/harvester/Initialize()
+ . = ..()
+ if(prob(1))
+ name = "auto-autopsy"
+
+/obj/machinery/harvester/RefreshParts()
+ interval = 0
+ var/max_time = 40
+ for(var/obj/item/stock_parts/micro_laser/L in component_parts)
+ max_time -= L.rating
+ interval = max(max_time,1)
+
+/obj/machinery/harvester/update_icon(warming_up)
+ if(warming_up)
+ icon_state = initial(icon_state)+"-charging"
+ return
+ if(state_open)
+ icon_state = initial(icon_state)+"-open"
+ else if(harvesting)
+ icon_state = initial(icon_state)+"-active"
+ else
+ icon_state = initial(icon_state)
+
+/obj/machinery/harvester/open_machine(drop = TRUE)
+ if(panel_open)
+ return
+ . = ..()
+ harvesting = FALSE
+
+/obj/machinery/harvester/attack_hand(mob/user)
+ if(state_open)
+ close_machine()
+ else if(!harvesting)
+ open_machine()
+
+/obj/machinery/harvester/AltClick(mob/user)
+ if(harvesting || !user || !isliving(user) || state_open)
+ return
+ if(can_harvest())
+ start_harvest()
+
+/obj/machinery/harvester/proc/can_harvest()
+ if(!powered(EQUIP) || state_open || !occupant || !iscarbon(occupant))
+ return
+ var/mob/living/carbon/C = occupant
+ if(!allow_clothing)
+ for(var/A in C.held_items + C.get_equipped_items())
+ if(!isitem(A))
+ continue
+ var/obj/item/I = A
+ if(!(I.item_flags & NODROP))
+ say("Subject may not have abiotic items on.")
+ playsound(src, 'sound/machines/buzz-sigh.ogg', 30, 1)
+ return
+ if(!(MOB_ORGANIC in C.mob_biotypes))
+ say("Subject is not organic.")
+ playsound(src, 'sound/machines/buzz-sigh.ogg', 30, 1)
+ return
+ if(!allow_living && !(C.stat == DEAD || C.has_trait(TRAIT_FAKEDEATH))) //I mean, the machines scanners arent advanced enough to tell you're alive
+ say("Subject is still alive.")
+ playsound(src, 'sound/machines/buzz-sigh.ogg', 30, 1)
+ return
+ return TRUE
+
+/obj/machinery/harvester/proc/start_harvest()
+ if(!occupant || !iscarbon(occupant))
+ return
+ var/mob/living/carbon/C = occupant
+ operation_order = reverseList(C.bodyparts) //Chest and head are first in bodyparts, so we invert it to make them suffer more
+ harvesting = TRUE
+ visible_message("The [name] begins warming up!")
+ update_icon(TRUE)
+ addtimer(CALLBACK(src, .proc/harvest), interval)
+
+/obj/machinery/harvester/proc/harvest()
+ update_icon()
+ if(!harvesting || state_open || !powered(EQUIP) || !occupant || !iscarbon(occupant))
+ return
+ playsound(src, 'sound/machines/juicer.ogg', 20, 1)
+ var/mob/living/carbon/C = occupant
+ if(!LAZYLEN(operation_order)) //The list is empty, so we're done here
+ end_harvesting()
+ return
+ var/turf/target
+ for(var/adir in list(EAST,NORTH,SOUTH,WEST))
+ var/turf/T = get_step(src,adir)
+ if(!T)
+ continue
+ if(istype(T, /turf/closed))
+ continue
+ target = T
+ break
+ if(!target)
+ target = get_turf(src)
+ for(var/obj/item/bodypart/BP in operation_order) //first we do non-essential limbs
+ BP.drop_limb()
+ C.emote("scream")
+ if(BP.body_zone != "chest")
+ BP.forceMove(target) //Move the limbs right next to it, except chest, that's a weird one
+ BP.drop_organs()
+ else
+ for(var/obj/item/organ/O in BP.dismember())
+ O.forceMove(target) //Some organs, like chest ones, are different so we need to manually move them
+ operation_order.Remove(BP)
+ break
+ use_power(5000)
+ addtimer(CALLBACK(src, .proc/harvest), interval)
+
+/obj/machinery/harvester/proc/end_harvesting()
+ harvesting = FALSE
+ open_machine()
+ say("Subject has been succesfuly harvested.")
+ playsound(src, 'sound/machines/microwave/microwave-end.ogg', 100, 0)
+
+/obj/machinery/harvester/screwdriver_act(mob/living/user, obj/item/I)
+ if(!state_open && !occupant)
+ if(default_deconstruction_screwdriver(user, "[initial(icon_state)]-o", initial(icon_state), I))
+ return
+
+/obj/machinery/harvester/crowbar_act(mob/living/user, obj/item/I)
+ if(default_pry_open(I))
+ return
+ if(default_deconstruction_crowbar(I))
+ return
+
+/obj/machinery/harvester/default_pry_open(obj/item/I) //wew
+ . = !(state_open || panel_open || (flags_1 & NODECONSTRUCT_1)) && I.tool_behaviour == TOOL_CROWBAR //We removed is_operational() here
+ if(.)
+ I.play_tool_sound(src, 50)
+ visible_message("[usr] pries open \the [src].", "You pry open [src].")
+ open_machine()
+
+/obj/machinery/harvester/emag_act(mob/user)
+ if(obj_flags & EMAGGED)
+ return
+ obj_flags |= EMAGGED
+ allow_living = TRUE
+ to_chat(user, "You overload [src]'s lifesign scanners.")
+
+/obj/machinery/harvester/container_resist(mob/living/user)
+ if(!harvesting)
+ visible_message("[occupant] emerges from [src]!",
+ "You climb out of [src]!")
+ open_machine()
+ else
+ to_chat(user,"[src] is active and can't be opened!") //rip
+
+/obj/machinery/harvester/Exited(atom/movable/user)
+ if (!state_open && user == occupant)
+ container_resist(user)
+
+/obj/machinery/harvester/relaymove(mob/user)
+ if (!state_open)
+ container_resist(user)
diff --git a/code/game/objects/items/circuitboards/machine_circuitboards.dm b/code/game/objects/items/circuitboards/machine_circuitboards.dm
index 8d91b3855af..26c46c7843e 100644
--- a/code/game/objects/items/circuitboards/machine_circuitboards.dm
+++ b/code/game/objects/items/circuitboards/machine_circuitboards.dm
@@ -921,4 +921,9 @@
/obj/item/circuitboard/machine/circulator
name = "Circulator/Heat Exchanger (Machine Board)"
build_path = /obj/machinery/atmospherics/components/binary/circulator
- req_components = list()
\ No newline at end of file
+ req_components = list()
+
+/obj/item/circuitboard/machine/harvester
+ name = "Harvester (Machine Board)"
+ build_path = /obj/machinery/harvester
+ req_components = list(/obj/item/stock_parts/micro_laser = 4)
\ No newline at end of file
diff --git a/code/modules/research/designs/machine_designs.dm b/code/modules/research/designs/machine_designs.dm
index 89f1eb85b99..9bcc38e559b 100644
--- a/code/modules/research/designs/machine_designs.dm
+++ b/code/modules/research/designs/machine_designs.dm
@@ -475,6 +475,14 @@
category = list("Medical Machinery")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
+/datum/design/board/harvester
+ name = "Machine Design (Organ Harvester Board)"
+ desc = "The circuit board for an organ harvester."
+ id = "harvester"
+ build_path = /obj/item/circuitboard/machine/harvester
+ category = list("Medical Machinery")
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
+
/datum/design/board/deepfryer
name = "Machine Design (Deep Fryer)"
desc = "The circuit board for a Deep Fryer."
diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm
index d4cc38e050b..3a9c6d70212 100644
--- a/code/modules/research/techweb/all_nodes.dm
+++ b/code/modules/research/techweb/all_nodes.dm
@@ -27,7 +27,7 @@
display_name = "Advanced Biotechnology"
description = "Advanced Biotechnology"
prereq_ids = list("biotech")
- design_ids = list("piercesyringe", "crewpinpointer", "smoke_machine", "plasmarefiller", "limbgrower", "defibrillator", "meta_beaker", "healthanalyzer_advanced")
+ design_ids = list("piercesyringe", "crewpinpointer", "smoke_machine", "plasmarefiller", "limbgrower", "defibrillator", "meta_beaker", "healthanalyzer_advanced","harvester")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
export_price = 5000
diff --git a/code/modules/surgery/bodyparts/dismemberment.dm b/code/modules/surgery/bodyparts/dismemberment.dm
index 7af309f0d93..cf315f9f7a3 100644
--- a/code/modules/surgery/bodyparts/dismemberment.dm
+++ b/code/modules/surgery/bodyparts/dismemberment.dm
@@ -51,7 +51,7 @@
return FALSE
if(C.has_trait(TRAIT_NODISMEMBER))
return FALSE
-
+ . = list()
var/organ_spilled = 0
var/turf/T = get_turf(C)
C.add_splatter_floor(T)
@@ -64,14 +64,15 @@
O.Remove(C)
O.forceMove(T)
organ_spilled = 1
+ . += X
if(cavity_item)
cavity_item.forceMove(T)
+ . += cavity_item
cavity_item = null
organ_spilled = 1
if(organ_spilled)
C.visible_message("[C]'s internal organs spill out onto the floor!")
- return 1
diff --git a/icons/obj/machines/harvester.dmi b/icons/obj/machines/harvester.dmi
new file mode 100644
index 00000000000..d6d9b01fc62
Binary files /dev/null and b/icons/obj/machines/harvester.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index 19a805fe38e..790a65bc949 100755
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -547,6 +547,7 @@
#include "code\game\machinery\flasher.dm"
#include "code\game\machinery\gulag_item_reclaimer.dm"
#include "code\game\machinery\gulag_teleporter.dm"
+#include "code\game\machinery\harvester.dm"
#include "code\game\machinery\hologram.dm"
#include "code\game\machinery\igniter.dm"
#include "code\game\machinery\iv_drip.dm"