diff --git a/_maps/map_files/Delta/delta.dmm b/_maps/map_files/Delta/delta.dmm index 9b7e943362f0..0791a5cfe9a1 100644 --- a/_maps/map_files/Delta/delta.dmm +++ b/_maps/map_files/Delta/delta.dmm @@ -1038,9 +1038,7 @@ /turf/simulated/floor/plasteel, /area/station/hallway/secondary/entry/east) "ait" = ( -/obj/machinery/economy/atm{ - pixel_y = -32 - }, +/obj/machinery/economy/atm/directional/south, /obj/machinery/atmospherics/pipe/simple/hidden/supply{ dir = 4 }, @@ -60021,9 +60019,7 @@ /obj/machinery/atmospherics/unary/vent_scrubber/on{ dir = 8 }, -/obj/machinery/economy/atm{ - pixel_x = 32 - }, +/obj/machinery/economy/atm/directional/east, /turf/simulated/floor/plasteel{ icon_state = "neutralcorner" }, @@ -64411,16 +64407,6 @@ /obj/item/crowbar/red, /turf/simulated/floor/plasteel/dark, /area/station/maintenance/apmaint) -"dSB" = ( -/obj/machinery/atmospherics/unary/portables_connector{ - dir = 8 - }, -/obj/machinery/atmospherics/portable/canister/air, -/obj/effect/turf_decal/stripes/line{ - dir = 10 - }, -/turf/simulated/floor/plasteel, -/area/station/hallway/secondary/exit) "dSD" = ( /obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{ dir = 4 @@ -67200,9 +67186,7 @@ /turf/simulated/floor/plasteel, /area/station/engineering/hardsuitstorage) "eEG" = ( -/obj/machinery/economy/atm{ - pixel_x = 32 - }, +/obj/machinery/economy/atm/directional/east, /obj/machinery/atmospherics/pipe/simple/hidden/supply, /obj/machinery/atmospherics/pipe/simple/hidden/scrubbers, /obj/structure/disposalpipe/segment, @@ -69068,9 +69052,7 @@ /turf/simulated/floor/plating, /area/station/maintenance/starboard) "fJp" = ( -/obj/machinery/economy/atm{ - pixel_y = 32 - }, +/obj/machinery/economy/atm/directional/north, /obj/machinery/atmospherics/pipe/simple/hidden/supply{ dir = 4 }, @@ -71544,6 +71526,16 @@ }, /turf/simulated/floor/plating, /area/station/command/office/rd) +"hli" = ( +/obj/machinery/atmospherics/unary/portables_connector{ + dir = 8 + }, +/obj/machinery/atmospherics/portable/canister/air, +/obj/effect/turf_decal/stripes/line{ + dir = 10 + }, +/turf/simulated/floor/plasteel, +/area/station/hallway/secondary/exit) "hly" = ( /obj/structure/table/wood, /turf/simulated/floor/plasteel/dark, @@ -74289,10 +74281,12 @@ icon_state = "bar" }, /area/station/service/kitchen) +"iVe" = ( +/obj/effect/spawner/airlock, +/turf/simulated/wall, +/area/station/service/chapel/office) "iVg" = ( -/obj/machinery/economy/atm{ - pixel_x = -32 - }, +/obj/machinery/economy/atm/directional/west, /turf/simulated/floor/plasteel{ dir = 1; icon_state = "neutralcorner" @@ -76136,15 +76130,6 @@ icon_state = "whiteblue" }, /area/station/medical/storage) -"kdg" = ( -/obj/structure/sign/vacuum{ - pixel_x = -32 - }, -/obj/effect/turf_decal/stripes/line{ - dir = 8 - }, -/turf/simulated/floor/plasteel, -/area/station/hallway/secondary/exit) "kdm" = ( /obj/machinery/power/apc/critical/directional/north, /obj/machinery/firealarm{ @@ -76245,6 +76230,15 @@ }, /turf/simulated/floor/plasteel/dark, /area/station/medical/morgue) +"kfC" = ( +/obj/structure/sign/vacuum{ + pixel_x = -32 + }, +/obj/effect/turf_decal/stripes/line{ + dir = 8 + }, +/turf/simulated/floor/plasteel, +/area/station/hallway/secondary/exit) "kfT" = ( /obj/structure/cable{ d2 = 4; @@ -77036,9 +77030,7 @@ /turf/simulated/floor/plating, /area/station/maintenance/library) "kGm" = ( -/obj/machinery/economy/atm{ - pixel_x = -32 - }, +/obj/machinery/economy/atm/directional/west, /turf/simulated/floor/plasteel{ dir = 1; icon_state = "neutralcorner" @@ -77686,9 +77678,7 @@ }, /area/station/service/hydroponics) "lbc" = ( -/obj/machinery/economy/atm{ - pixel_x = -32 - }, +/obj/machinery/economy/atm/directional/west, /obj/machinery/atmospherics/pipe/simple/hidden/scrubbers, /obj/machinery/atmospherics/pipe/simple/hidden/supply, /obj/structure/cable{ @@ -81729,7 +81719,7 @@ /turf/simulated/floor/plasteel, /area/station/engineering/atmos) "nsl" = ( -/obj/machinery/clonepod/biomass, +/obj/machinery/clonepod, /turf/simulated/floor/plasteel{ icon_state = "freezerfloor" }, @@ -91901,6 +91891,21 @@ }, /turf/simulated/floor/plasteel, /area/station/science/research) +"tjT" = ( +/obj/item/radio/intercom{ + name = "west bump"; + pixel_x = -28 + }, +/obj/machinery/newscaster{ + dir = 1; + name = "south bump"; + pixel_y = -28 + }, +/obj/effect/turf_decal/stripes/line{ + dir = 10 + }, +/turf/simulated/floor/plasteel, +/area/station/hallway/secondary/exit) "tkb" = ( /obj/machinery/atmospherics/pipe/simple/hidden/supply{ dir = 6 @@ -92940,21 +92945,6 @@ /obj/effect/spawner/window/reinforced/grilled, /turf/simulated/floor/plating, /area/station/security/prison/cell_block) -"tSp" = ( -/obj/item/radio/intercom{ - name = "west bump"; - pixel_x = -28 - }, -/obj/machinery/newscaster{ - dir = 1; - name = "south bump"; - pixel_y = -28 - }, -/obj/effect/turf_decal/stripes/line{ - dir = 10 - }, -/turf/simulated/floor/plasteel, -/area/station/hallway/secondary/exit) "tSt" = ( /obj/structure/table, /obj/item/reagent_containers/drinks/drinkingglass{ @@ -93386,7 +93376,7 @@ /turf/simulated/floor/plasteel/dark, /area/station/medical/surgery/observation) "udS" = ( -/obj/machinery/dna_scannernew, +/obj/machinery/clonescanner, /turf/simulated/floor/plasteel{ icon_state = "freezerfloor" }, @@ -93598,10 +93588,6 @@ icon_state = "darkred" }, /area/station/security/processing) -"uil" = ( -/obj/effect/spawner/airlock, -/turf/simulated/wall, -/area/station/service/chapel/office) "uit" = ( /obj/machinery/atmospherics/unary/vent_pump{ dir = 4; @@ -137312,7 +137298,7 @@ dIx dIx dIx dIx -uil +iVe abj aaa aaa @@ -137823,7 +137809,7 @@ dPM dkq dSs dSP -dSB +hli dIH dIH dIH @@ -138081,8 +138067,8 @@ dKo dXJ dKo dUj -kdg -tSp +kfC +tjT dIH abj aaa diff --git a/_maps/map_files/MetaStation/MetaStation.dmm b/_maps/map_files/MetaStation/MetaStation.dmm index dcb957cffa33..12991913a00b 100644 --- a/_maps/map_files/MetaStation/MetaStation.dmm +++ b/_maps/map_files/MetaStation/MetaStation.dmm @@ -10309,9 +10309,7 @@ /turf/simulated/floor/plating, /area/station/maintenance/fore) "aTi" = ( -/obj/machinery/economy/atm{ - pixel_y = -32 - }, +/obj/machinery/economy/atm/directional/south, /turf/simulated/floor/plasteel{ dir = 8; icon_state = "redcorner" @@ -24421,9 +24419,7 @@ c_tag = "Arrivals - Middle Arm"; dir = 1 }, -/obj/machinery/economy/atm{ - pixel_y = -32 - }, +/obj/machinery/economy/atm/directional/south, /obj/effect/turf_decal/stripes/line, /turf/simulated/floor/plasteel, /area/station/hallway/secondary/entry/east) @@ -37484,13 +37480,13 @@ /turf/simulated/floor/plating, /area/station/maintenance/medmaint) "cAj" = ( -/obj/machinery/clonepod/biomass, /obj/effect/turf_decal/stripes/line{ dir = 5 }, /obj/effect/turf_decal/siding{ dir = 5 }, +/obj/machinery/clonepod, /turf/simulated/floor/plasteel{ icon_state = "dark" }, @@ -39931,13 +39927,13 @@ /turf/simulated/floor/engine, /area/station/science/xenobiology) "cIR" = ( -/obj/machinery/dna_scannernew, /obj/effect/turf_decal/stripes/line{ dir = 1 }, /obj/effect/turf_decal/siding{ dir = 1 }, +/obj/machinery/clonescanner, /turf/simulated/floor/plasteel{ icon_state = "dark" }, @@ -42135,9 +42131,7 @@ }, /area/station/medical/surgery/secondary) "cRq" = ( -/obj/machinery/economy/atm{ - pixel_y = 32 - }, +/obj/machinery/economy/atm/directional/north, /obj/effect/turf_decal/stripes/line{ dir = 1 }, @@ -67055,6 +67049,16 @@ icon_state = "whiteblue" }, /area/station/security/permabrig) +"mgu" = ( +/obj/effect/turf_decal/stripes/white/line{ + dir = 1 + }, +/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers, +/obj/machinery/atmospherics/pipe/simple/hidden/supply, +/turf/simulated/floor/plasteel{ + icon_state = "darkgrey" + }, +/area/station/science/toxins/launch) "mgZ" = ( /obj/machinery/atmospherics/air_sensor{ autolink_id = "air_sensor"; @@ -68569,16 +68573,6 @@ icon_state = "greenblue" }, /area/station/service/hydroponics) -"mQW" = ( -/obj/effect/turf_decal/stripes/white/line{ - dir = 1 - }, -/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers, -/obj/machinery/atmospherics/pipe/simple/hidden/supply, -/turf/simulated/floor/plasteel{ - icon_state = "darkgrey" - }, -/area/station/science/toxins/launch) "mRj" = ( /obj/machinery/atmospherics/pipe/simple/hidden/supply{ dir = 9 @@ -73031,9 +73025,7 @@ }, /area/station/maintenance/asmaint) "oPi" = ( -/obj/machinery/economy/atm{ - pixel_x = -32 - }, +/obj/machinery/economy/atm/directional/west, /turf/simulated/floor/wood, /area/station/public/mrchangs) "oPO" = ( @@ -78974,9 +78966,7 @@ /turf/simulated/floor/engine, /area/station/engineering/control) "rmg" = ( -/obj/machinery/economy/atm{ - pixel_y = -32 - }, +/obj/machinery/economy/atm/directional/south, /obj/machinery/light, /turf/simulated/floor/plasteel{ icon_state = "neutralcorner" @@ -93428,9 +93418,7 @@ /area/station/service/chapel) "xKg" = ( /obj/structure/disposalpipe/segment, -/obj/machinery/economy/atm{ - pixel_x = 32 - }, +/obj/machinery/economy/atm/directional/east, /turf/simulated/floor/plasteel{ icon_state = "neutralcorner" }, @@ -128756,7 +128744,7 @@ wtQ jQZ eGn iXy -mQW +mgu tQW czx naq diff --git a/_maps/map_files/cerestation/cerestation.dmm b/_maps/map_files/cerestation/cerestation.dmm index 215350da6e95..55eff70d42ab 100644 --- a/_maps/map_files/cerestation/cerestation.dmm +++ b/_maps/map_files/cerestation/cerestation.dmm @@ -26883,9 +26883,7 @@ "cPP" = ( /obj/machinery/atmospherics/pipe/simple/hidden/supply, /obj/machinery/atmospherics/pipe/simple/hidden/scrubbers, -/obj/machinery/economy/atm{ - pixel_x = -32 - }, +/obj/machinery/economy/atm/directional/west, /turf/simulated/floor/plasteel{ dir = 1; icon_state = "neutralcorner" @@ -33915,9 +33913,7 @@ d2 = 8; icon_state = "4-8" }, -/obj/machinery/economy/atm{ - pixel_y = -32 - }, +/obj/machinery/economy/atm/directional/south, /obj/structure/disposalpipe/segment{ dir = 4; color = "#954535" @@ -36276,6 +36272,27 @@ icon_state = "darkgreenfull" }, /area/station/service/hydroponics) +"eua" = ( +/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{ + dir = 4 + }, +/obj/machinery/atmospherics/pipe/simple/hidden/supply{ + dir = 4 + }, +/obj/structure/disposalpipe/segment{ + dir = 4 + }, +/obj/structure/cable/orange{ + d1 = 4; + d2 = 8; + icon_state = "4-8" + }, +/obj/machinery/economy/atm/directional/north, +/turf/simulated/floor/plasteel{ + dir = 4; + icon_state = "bluecorner" + }, +/area/station/hallway/primary/fore/north) "eul" = ( /obj/structure/cable/orange{ d1 = 4; @@ -41165,7 +41182,7 @@ network = list("SS13","CMO") }, /obj/machinery/light, -/obj/machinery/dna_scannernew, +/obj/machinery/clonescanner, /turf/simulated/floor/plasteel{ icon_state = "whiteblue" }, @@ -43313,9 +43330,7 @@ d2 = 8; icon_state = "4-8" }, -/obj/machinery/economy/atm{ - pixel_y = 32 - }, +/obj/machinery/economy/atm/directional/north, /obj/effect/spawner/random_spawners/dirt_often, /turf/simulated/floor/plasteel{ dir = 4; @@ -43964,14 +43979,6 @@ }, /turf/simulated/floor/plasteel, /area/station/hallway/secondary/exit) -"hiN" = ( -/obj/machinery/economy/atm{ - pixel_y = -32 - }, -/turf/simulated/floor/plasteel{ - icon_state = "arrival" - }, -/area/station/hallway/secondary/entry/west) "hiU" = ( /obj/structure/reagent_dispensers/water_cooler, /turf/simulated/floor/plasteel{ @@ -54619,9 +54626,7 @@ /obj/machinery/atmospherics/pipe/simple/hidden/supply{ dir = 4 }, -/obj/machinery/economy/atm{ - pixel_y = -32 - }, +/obj/machinery/economy/atm/directional/south, /turf/simulated/floor/plasteel{ dir = 8; icon_state = "neutralcorner" @@ -54837,6 +54842,12 @@ }, /turf/simulated/floor/transparent/glass/reinforced, /area/station/maintenance/fsmaint) +"kEV" = ( +/obj/machinery/economy/atm/directional/south, +/turf/simulated/floor/plasteel{ + icon_state = "arrival" + }, +/area/station/hallway/secondary/entry/west) "kFj" = ( /obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{ dir = 10 @@ -58619,15 +58630,6 @@ }, /turf/simulated/floor/plating, /area/station/maintenance/fsmaint) -"lME" = ( -/obj/machinery/economy/atm{ - pixel_y = -32 - }, -/turf/simulated/floor/plasteel{ - dir = 8; - icon_state = "yellowcorner" - }, -/area/station/hallway/primary/central/north) "lMW" = ( /obj/effect/decal/cleanable/dirt, /turf/simulated/floor/plating, @@ -63310,29 +63312,6 @@ icon_state = "whiteblue" }, /area/station/medical/medbay) -"nqK" = ( -/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{ - dir = 4 - }, -/obj/machinery/atmospherics/pipe/simple/hidden/supply{ - dir = 4 - }, -/obj/structure/disposalpipe/segment{ - dir = 4 - }, -/obj/structure/cable/orange{ - d1 = 4; - d2 = 8; - icon_state = "4-8" - }, -/obj/machinery/economy/atm{ - pixel_y = 32 - }, -/turf/simulated/floor/plasteel{ - dir = 4; - icon_state = "bluecorner" - }, -/area/station/hallway/primary/fore/north) "nqO" = ( /obj/machinery/atmospherics/unary/portables_connector{ dir = 4 @@ -75765,6 +75744,13 @@ icon_state = "whiteblue" }, /area/station/medical/surgery/primary) +"rqJ" = ( +/obj/machinery/economy/atm/directional/south, +/turf/simulated/floor/plasteel{ + dir = 8; + icon_state = "yellowcorner" + }, +/area/station/hallway/primary/central/north) "rrp" = ( /obj/machinery/shower{ pixel_y = 20 @@ -93069,9 +93055,7 @@ dir = 4; color = "#954535" }, -/obj/machinery/economy/atm{ - pixel_y = -32 - }, +/obj/machinery/economy/atm/directional/south, /turf/simulated/floor/plasteel{ dir = 8; icon_state = "purplecorner" @@ -132406,7 +132390,7 @@ pKj aYJ eQQ bgf -lME +rqJ aYJ bhQ dcG @@ -134688,7 +134672,7 @@ dGA eDi djn dmA -nqK +eua vuY pwc fXC @@ -138140,7 +138124,7 @@ dbg sFj dbg eWu -hiN +kEV qHn rNK rNK diff --git a/_maps/map_files/cyberiad/cyberiad.dmm b/_maps/map_files/cyberiad/cyberiad.dmm index 73f571166df0..94c98dd40fa1 100644 --- a/_maps/map_files/cyberiad/cyberiad.dmm +++ b/_maps/map_files/cyberiad/cyberiad.dmm @@ -18714,9 +18714,7 @@ dir = 4 }, /obj/machinery/light, -/obj/machinery/economy/atm{ - pixel_y = -32 - }, +/obj/machinery/economy/atm/directional/south, /turf/simulated/floor/plasteel{ icon_state = "neutralcorner" }, @@ -21815,9 +21813,7 @@ /turf/simulated/floor/plasteel, /area/station/hallway/primary/central/ne) "bnu" = ( -/obj/machinery/economy/atm{ - pixel_x = 32 - }, +/obj/machinery/economy/atm/directional/east, /obj/machinery/camera{ c_tag = "Arrivals Hallway"; dir = 8 @@ -22881,9 +22877,7 @@ /turf/simulated/floor/wood, /area/station/service/bar) "bql" = ( -/obj/machinery/economy/atm{ - pixel_x = 32 - }, +/obj/machinery/economy/atm/directional/east, /turf/simulated/floor/plasteel, /area/station/hallway/primary/port/east) "bqm" = ( @@ -25163,9 +25157,7 @@ /turf/simulated/floor/carpet, /area/station/service/chapel) "bxE" = ( -/obj/machinery/economy/atm{ - pixel_x = 32 - }, +/obj/machinery/economy/atm/directional/east, /obj/item/kirbyplants, /turf/simulated/floor/wood, /area/station/service/bar) @@ -25858,9 +25850,7 @@ /turf/simulated/floor/wood, /area/station/public/vacant_office) "bAn" = ( -/obj/machinery/economy/atm{ - pixel_y = 32 - }, +/obj/machinery/economy/atm/directional/north, /turf/simulated/floor/plasteel, /area/station/hallway/primary/starboard/west) "bAo" = ( @@ -26128,9 +26118,7 @@ }, /area/station/supply/sorting) "bBq" = ( -/obj/machinery/economy/atm{ - pixel_x = -32 - }, +/obj/machinery/economy/atm/directional/west, /obj/structure/disposalpipe/segment, /turf/simulated/floor/plasteel{ dir = 8; @@ -27054,9 +27042,7 @@ /turf/simulated/floor/plasteel, /area/station/hallway/primary/starboard/east) "bFo" = ( -/obj/machinery/economy/atm{ - pixel_y = 32 - }, +/obj/machinery/economy/atm/directional/north, /obj/structure/cable{ d1 = 4; d2 = 8; @@ -28436,9 +28422,7 @@ }, /area/station/medical/chemistry) "bJP" = ( -/obj/machinery/economy/atm{ - pixel_y = -32 - }, +/obj/machinery/economy/atm/directional/south, /turf/simulated/floor/plasteel{ icon_state = "whitehall" }, @@ -32068,7 +32052,6 @@ /turf/simulated/floor/plating, /area/station/science/genetics) "bXO" = ( -/obj/machinery/dna_scannernew, /obj/machinery/light{ dir = 4 }, @@ -32076,6 +32059,7 @@ layer = 4; pixel_x = 32 }, +/obj/machinery/clonescanner, /turf/simulated/floor/plasteel{ icon_state = "freezerfloor" }, @@ -32421,9 +32405,7 @@ c_tag = "Cargo Bay Entrance"; dir = 4 }, -/obj/machinery/economy/atm{ - pixel_x = -32 - }, +/obj/machinery/economy/atm/directional/west, /turf/simulated/floor/plasteel{ dir = 8; icon_state = "browncorner" @@ -32998,10 +32980,10 @@ }, /area/station/medical/cloning) "caN" = ( -/obj/machinery/clonepod/biomass, /obj/machinery/atmospherics/unary/vent_scrubber/on{ dir = 8 }, +/obj/machinery/clonepod, /turf/simulated/floor/plasteel{ icon_state = "freezerfloor" }, @@ -33489,9 +33471,7 @@ /turf/simulated/floor/plasteel, /area/station/hallway/primary/central/se) "ccn" = ( -/obj/machinery/economy/atm{ - pixel_y = 32 - }, +/obj/machinery/economy/atm/directional/north, /turf/simulated/floor/plasteel, /area/station/hallway/primary/central/se) "cco" = ( @@ -51622,9 +51602,7 @@ }, /area/station/turret_protected/ai) "dnk" = ( -/obj/machinery/economy/atm{ - pixel_x = 32 - }, +/obj/machinery/economy/atm/directional/east, /turf/simulated/floor/plasteel, /area/station/hallway/primary/central/ne) "dnl" = ( diff --git a/_maps/map_files/generic/centcomm.dmm b/_maps/map_files/generic/centcomm.dmm index 0520abc37123..17bdeb7a24e3 100644 --- a/_maps/map_files/generic/centcomm.dmm +++ b/_maps/map_files/generic/centcomm.dmm @@ -3854,7 +3854,6 @@ /area/abductor_ship) "mX" = ( /obj/machinery/door/window{ - dir = 2; name = "Cockpit"; req_access_txt = "150" }, @@ -4139,15 +4138,11 @@ /turf/simulated/floor/plasteel/freezer, /area/syndicate_mothership) "nN" = ( -/obj/effect/turf_decal/stripes/corner{ - dir = 2 - }, +/obj/effect/turf_decal/stripes/corner, /turf/simulated/floor/plating/airless, /area/syndicate_mothership) "nO" = ( -/obj/effect/turf_decal/stripes/line{ - dir = 2 - }, +/obj/effect/turf_decal/stripes/line, /turf/simulated/floor/plating/airless, /area/syndicate_mothership) "nP" = ( @@ -4554,10 +4549,10 @@ /turf/simulated/floor/carpet/purple, /area/wizard_station) "pq" = ( -/obj/machinery/clonepod/upgraded, /obj/machinery/light/spot{ dir = 1 }, +/obj/machinery/clonepod/upgraded, /turf/simulated/floor/mineral/titanium, /area/shuttle/administration) "pr" = ( @@ -4908,8 +4903,7 @@ "qJ" = ( /obj/structure/table, /obj/machinery/door/window/classic/normal{ - name = "security checkpoint"; - dir = 2 + name = "security checkpoint" }, /turf/simulated/floor/carpet, /area/admin) @@ -5054,6 +5048,10 @@ /obj/structure/coatrack, /turf/simulated/floor/wood, /area/ghost_bar) +"rg" = ( +/obj/machinery/clonescanner, +/turf/simulated/floor/mineral/titanium, +/area/shuttle/administration) "rh" = ( /obj/machinery/economy/vending/liberationstation, /turf/simulated/floor/plasteel, @@ -5440,9 +5438,7 @@ /turf/simulated/floor/plasteel, /area/centcom/evac) "sq" = ( -/obj/machinery/economy/atm{ - pixel_y = 32 - }, +/obj/machinery/economy/atm/directional/north, /obj/structure/chair/comfy/shuttle{ dir = 8 }, @@ -7909,9 +7905,7 @@ /turf/simulated/floor/wood, /area/ghost_bar) "Cl" = ( -/obj/effect/turf_decal/stripes/line{ - dir = 2 - }, +/obj/effect/turf_decal/stripes/line, /turf/simulated/floor/plating/airless, /area/centcom/control) "Cm" = ( @@ -8482,7 +8476,6 @@ /area/holodeck/source_basketball) "Ej" = ( /obj/machinery/door/window/reinforced/normal{ - dir = 2; name = "Cell Door"; req_access_txt = "150" }, @@ -11383,9 +11376,7 @@ /obj/machinery/door/airlock/medical/glass{ name = "Escape Shuttle Infirmary" }, -/obj/effect/turf_decal/stripes/line{ - dir = 2 - }, +/obj/effect/turf_decal/stripes/line, /turf/simulated/floor/plasteel, /area/shuttle/escape) "Of" = ( @@ -14333,8 +14324,7 @@ "YJ" = ( /obj/machinery/door/window/classic/normal{ name = "Cell B"; - req_access_txt = "101"; - dir = 2 + req_access_txt = "101" }, /turf/simulated/floor/plasteel/dark, /area/admin) @@ -28039,7 +28029,7 @@ ZN ZN Ek ZG -km +rg lr lr Sx diff --git a/_maps/map_files/shuttles/admin_hospital.dmm b/_maps/map_files/shuttles/admin_hospital.dmm index 167f4c130b61..120d18bf47cf 100644 --- a/_maps/map_files/shuttles/admin_hospital.dmm +++ b/_maps/map_files/shuttles/admin_hospital.dmm @@ -97,7 +97,7 @@ /turf/simulated/floor/mineral/titanium, /area/shuttle/administration) "aB" = ( -/obj/machinery/dna_scannernew, +/obj/machinery/clonescanner, /turf/simulated/floor/mineral/titanium, /area/shuttle/administration) "aC" = ( @@ -585,12 +585,10 @@ /turf/simulated/floor/mineral/titanium, /area/shuttle/administration) "ip" = ( -/obj/machinery/clonepod{ - biomass = 300 - }, /obj/machinery/light{ dir = 1 }, +/obj/machinery/clonepod/upgraded, /turf/simulated/floor/mineral/titanium, /area/shuttle/administration) "kY" = ( diff --git a/_maps/map_files220/MetaStation/MetaStation.dmm b/_maps/map_files220/MetaStation/MetaStation.dmm index 95573b80c532..0ad1c6e5963a 100644 --- a/_maps/map_files220/MetaStation/MetaStation.dmm +++ b/_maps/map_files220/MetaStation/MetaStation.dmm @@ -36639,7 +36639,7 @@ /turf/simulated/floor/plating, /area/station/maintenance/medmaint) "cAj" = ( -/obj/machinery/clonepod/biomass, +/obj/machinery/clonepod, /obj/effect/turf_decal/stripes/line{ dir = 5 }, @@ -39158,7 +39158,7 @@ /turf/simulated/floor/engine, /area/station/science/xenobiology) "cIR" = ( -/obj/machinery/dna_scannernew, +/obj/machinery/clonescanner, /obj/effect/turf_decal/stripes/line{ dir = 1 }, diff --git a/_maps/map_files220/cyberiad/cyberiad.dmm b/_maps/map_files220/cyberiad/cyberiad.dmm index f368e4a33003..a8ae74c93856 100644 --- a/_maps/map_files220/cyberiad/cyberiad.dmm +++ b/_maps/map_files220/cyberiad/cyberiad.dmm @@ -25354,7 +25354,7 @@ /turf/simulated/wall/r_wall, /area/station/command/office/rd) "bYk" = ( -/obj/machinery/dna_scannernew, +/obj/machinery/clonescanner, /turf/simulated/floor/plasteel{ dir = 6; icon_state = "whiteblue" @@ -84446,7 +84446,7 @@ /turf/simulated/floor/wood/fancy, /area/station/legal/courtroom) "rWA" = ( -/obj/machinery/clonepod/biomass, +/obj/machinery/clonepod, /obj/machinery/alarm/directional/south, /turf/simulated/floor/plasteel{ icon_state = "whiteblue" diff --git a/_maps/map_files220/delta/delta.dmm b/_maps/map_files220/delta/delta.dmm index 6ad57061fb7f..638e5185005e 100644 --- a/_maps/map_files220/delta/delta.dmm +++ b/_maps/map_files220/delta/delta.dmm @@ -16998,7 +16998,7 @@ /turf/simulated/floor/plasteel, /area/station/engineering/atmos) "bvO" = ( -/obj/machinery/dna_scannernew, +/obj/machinery/clonescanner, /turf/simulated/floor/plasteel{ dir = 1; icon_state = "whiteblue" @@ -104740,7 +104740,7 @@ /obj/structure/window/reinforced{ dir = 4 }, -/obj/machinery/clonepod/biomass, +/obj/machinery/clonepod, /obj/machinery/light/small/directional/north, /turf/simulated/floor/plasteel{ icon_state = "dark" diff --git a/_maps/map_files220/generic/centcomm.dmm b/_maps/map_files220/generic/centcomm.dmm index 21fb2d4e6fb4..2172512c4561 100644 --- a/_maps/map_files220/generic/centcomm.dmm +++ b/_maps/map_files220/generic/centcomm.dmm @@ -5645,6 +5645,12 @@ /obj/machinery/door/airlock/centcom, /turf/simulated/floor/wood, /area/ghost_bar) +"dNN" = ( +/obj/machinery/clonescanner, +/turf/simulated/floor/plasteel/dark{ + icon_state = "barber" + }, +/area/shuttle/administration) "dOd" = ( /obj/machinery/atmospherics/unary/thermomachine/freezer/on, /obj/structure/light_fake{ @@ -19937,7 +19943,7 @@ }, /area/syndicate_mothership) "nGm" = ( -/obj/machinery/dna_scannernew, +/obj/machinery/clonescanner, /obj/structure/window/reinforced, /turf/simulated/floor/plasteel{ dir = 6; @@ -48440,7 +48446,7 @@ wCi wCi wCi wCi -ibG +dNN frK rkZ nfA diff --git a/code/__DEFINES/cloning_defines.dm b/code/__DEFINES/cloning_defines.dm new file mode 100644 index 000000000000..5b22af2017d8 --- /dev/null +++ b/code/__DEFINES/cloning_defines.dm @@ -0,0 +1,13 @@ +//Defines used to pass info between the scanner and cloning console +#define SCANNER_UNCLONEABLE_SPECIES "uncloneable" +#define SCANNER_HUSKED "husked" +#define SCANNER_ABSORBED "absorbed" +#define SCANNER_NO_SOUL "soulless" +#define SCANNER_BRAIN_ISSUE "suicide or missing brain" +#define SCANNER_MISC "miscellanious" +#define SCANNER_SUCCESSFUL "successful" + +//Defines used to make the return value of a get_cloning_cost() operation more readable +#define BIOMASS_COST 1 +#define SANGUINE_COST 2 +#define OSSEOUS_COST 3 diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm index 66e7af873a3e..6cf0868dd45f 100644 --- a/code/_onclick/hud/alert.dm +++ b/code/_onclick/hud/alert.dm @@ -817,3 +817,17 @@ so as to remain in compliance with the most up-to-date laws." master = null screen_loc = "" return ..() + +/// Gives the player the option to succumb while in critical condition +/atom/movable/screen/alert/succumb + name = "Succumb" + desc = "Shuffle off this mortal coil." + icon_state = "succumb" + +/atom/movable/screen/alert/succumb/Click() + if(!usr || !usr.client) + return + var/mob/living/living_owner = usr + if(!istype(usr)) + return + living_owner.do_succumb(TRUE) diff --git a/code/controllers/configuration/sections/gamemode_configuration.dm b/code/controllers/configuration/sections/gamemode_configuration.dm index d9be626883fa..1fc39b62217d 100644 --- a/code/controllers/configuration/sections/gamemode_configuration.dm +++ b/code/controllers/configuration/sections/gamemode_configuration.dm @@ -20,6 +20,8 @@ var/traitor_objectives_amount = 2 /// Enable player limits on gamemodes? Disabling can be useful for testing var/enable_gamemode_player_limit = TRUE + /// Should we generate random station traits at game start? + var/add_random_station_traits = TRUE // Dynamically setup a list of all gamemodes /datum/configuration_section/gamemode_configuration/New() @@ -51,6 +53,7 @@ CONFIG_LOAD_BOOL(prevent_mindshield_antags, data["prevent_mindshield_antag"]) CONFIG_LOAD_BOOL(disable_certain_round_early_end, data["disable_certain_round_early_end"]) CONFIG_LOAD_BOOL(enable_gamemode_player_limit, data["enable_gamemode_player_limit"]) + CONFIG_LOAD_BOOL(add_random_station_traits, data["add_random_station_traits"]) CONFIG_LOAD_NUM(traitor_objectives_amount, data["traitor_objective_amount"]) diff --git a/code/controllers/subsystem/SSdbcore.dm b/code/controllers/subsystem/SSdbcore.dm index 88624dc784ce..03d147e9aaca 100644 --- a/code/controllers/subsystem/SSdbcore.dm +++ b/code/controllers/subsystem/SSdbcore.dm @@ -382,6 +382,8 @@ SUBSYSTEM_DEF(dbcore) * * log_error - Do we want to log errors this creates? Disable this if you are running sensitive queries where you dont want errors logged in plain text (EG: Auth token stuff) */ /datum/db_query/proc/warn_execute(async = TRUE, log_error = TRUE) + if(!GLOB.configuration.database.enabled) + return . = Execute(async, log_error) if(!.) SSdbcore.total_errors++ diff --git a/code/controllers/subsystem/processing/SSstation.dm b/code/controllers/subsystem/processing/SSstation.dm index 62901a31c1e7..f177094db081 100644 --- a/code/controllers/subsystem/processing/SSstation.dm +++ b/code/controllers/subsystem/processing/SSstation.dm @@ -34,6 +34,9 @@ PROCESSING_SUBSYSTEM_DEF(station) return + if(!GLOB.configuration.gamemode.add_random_station_traits) + return + for(var/i in subtypesof(/datum/station_trait)) var/datum/station_trait/trait_typepath = i diff --git a/code/game/jobs/job/engineering_jobs.dm b/code/game/jobs/job/engineering_jobs.dm index 5aff1dc14a5b..8da9c1e36c65 100644 --- a/code/game/jobs/job/engineering_jobs.dm +++ b/code/game/jobs/job/engineering_jobs.dm @@ -13,7 +13,7 @@ access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS, ACCESS_TELEPORTER, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_ATMOSPHERICS, ACCESS_EMERGENCY_STORAGE, ACCESS_EVA, ACCESS_HEADS, ACCESS_CONSTRUCTION, ACCESS_SEC_DOORS, - ACCESS_CE, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_TCOMSAT, ACCESS_MINISAT, ACCESS_MINERAL_STOREROOM) + ACCESS_CE, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_TCOMSAT, ACCESS_MINISAT, ACCESS_MINERAL_STOREROOM, ACCESS_WEAPONS) minimal_player_age = 21 exp_map = list(EXP_TYPE_ENGINEERING = 1200) blacklisted_disabilities = list(DISABILITY_FLAG_BLIND, DISABILITY_FLAG_DEAF, DISABILITY_FLAG_MUTE, DISABILITY_FLAG_DIZZY, DISABILITY_FLAG_NERVOUS, DISABILITY_FLAG_LISP) diff --git a/code/game/jobs/job/medical_jobs.dm b/code/game/jobs/job/medical_jobs.dm index d623a4126edc..53ac12bd511b 100644 --- a/code/game/jobs/job/medical_jobs.dm +++ b/code/game/jobs/job/medical_jobs.dm @@ -26,7 +26,8 @@ ACCESS_RC_ANNOUNCE, ACCESS_SEC_DOORS, ACCESS_SURGERY, - ACCESS_VIROLOGY + ACCESS_VIROLOGY, + ACCESS_WEAPONS ) minimal_player_age = 21 exp_map = list(EXP_TYPE_MEDICAL = 1200) diff --git a/code/game/jobs/job/science_jobs.dm b/code/game/jobs/job/science_jobs.dm index 8168456ae837..93c136fd7784 100644 --- a/code/game/jobs/job/science_jobs.dm +++ b/code/game/jobs/job/science_jobs.dm @@ -33,7 +33,8 @@ ACCESS_TOX_STORAGE, ACCESS_TOX, ACCESS_XENOARCH, - ACCESS_XENOBIOLOGY + ACCESS_XENOBIOLOGY, + ACCESS_WEAPONS ) minimal_player_age = 21 blacklisted_disabilities = list(DISABILITY_FLAG_BLIND, DISABILITY_FLAG_DEAF, DISABILITY_FLAG_MUTE, DISABILITY_FLAG_DIZZY) diff --git a/code/game/jobs/job/supervisor.dm b/code/game/jobs/job/supervisor.dm index 2e3d051bba72..48c6eacb159e 100644 --- a/code/game/jobs/job/supervisor.dm +++ b/code/game/jobs/job/supervisor.dm @@ -282,7 +282,8 @@ ACCESS_MAINT_TUNNELS, ACCESS_RC_ANNOUNCE, ACCESS_SEC_DOORS, - ACCESS_SECURITY + ACCESS_SECURITY, + ACCESS_WEAPONS ) blacklisted_disabilities = list(DISABILITY_FLAG_DEAF, DISABILITY_FLAG_MUTE, DISABILITY_FLAG_NERVOUS, DISABILITY_FLAG_LISP) outfit = /datum/outfit/job/judge diff --git a/code/game/jobs/job/support.dm b/code/game/jobs/job/support.dm index 3f23637747c5..b48d3cd33429 100644 --- a/code/game/jobs/job/support.dm +++ b/code/game/jobs/job/support.dm @@ -27,6 +27,7 @@ ACCESS_RC_ANNOUNCE, ACCESS_SEC_DOORS, ACCESS_SUPPLY_SHUTTLE, + ACCESS_WEAPONS ) blacklisted_disabilities = list(DISABILITY_FLAG_BLIND, DISABILITY_FLAG_DEAF, DISABILITY_FLAG_MUTE, DISABILITY_FLAG_DIZZY) outfit = /datum/outfit/job/qm diff --git a/code/game/machinery/clonepod.dm b/code/game/machinery/clonepod.dm index 05ee54d5e37d..d07e5f3ba598 100644 --- a/code/game/machinery/clonepod.dm +++ b/code/game/machinery/clonepod.dm @@ -1,23 +1,58 @@ -//Cloning revival method. -//The pod handles the actual cloning while the computer manages the clone profiles +/// Reagents that can be inserted into the cloning pod (and not deleted by it). +#define VALID_REAGENTS list("sanguine_reagent", "osseous_reagent") + +/// Meats that can be used as biomass for the cloner. +#define VALID_BIOMASSABLES list(/obj/item/food/snacks/meat, \ + /obj/item/food/snacks/monstermeat, \ + /obj/item/food/snacks/carpmeat, \ + /obj/item/food/snacks/salmonmeat, \ + /obj/item/food/snacks/catfishmeat, \ + /obj/item/food/snacks/tofurkey) + +/// Internal organs the cloner will *never* accept for insertion. +#define FORBIDDEN_INTERNAL_ORGANS list(/obj/item/organ/internal/regenerative_core, \ + /obj/item/organ/internal/alien, \ + /obj/item/organ/internal/body_egg, \ + /obj/item/organ/internal/adamantine_resonator, \ + /obj/item/organ/internal/vocal_cords/colossus, \ + /obj/item/organ/internal/cyberimp, \ + /obj/item/organ/internal/brain, \ + /obj/item/organ/internal/cell, \ + /obj/item/organ/internal/eyes/optical_sensor, \ + /obj/item/organ/internal/ears/microphone) + +/// Internal organs the cloner will only accept when fully upgraded. +#define UPGRADE_LOCKED_ORGANS list(/obj/item/organ/internal/heart/gland, \ + /obj/item/organ/internal/heart/demon, \ + /obj/item/organ/internal/heart/cursed, \ + /obj/item/organ/internal/eyes/cybernetic/eyesofgod) + +/// Limbs that the cloner won't accept. +#define FORBIDDEN_LIMBS list(/obj/item/organ/external/head, \ + /obj/item/organ/external/chest, \ + /obj/item/organ/external/groin) //you can't even get chests and groins normally + +/// A list of robot parts for use later, so that you can put them straight into the cloner from the exosuit fabricator. +#define ALLOWED_ROBOT_PARTS list(/obj/item/robot_parts/r_arm, \ + /obj/item/robot_parts/l_arm, \ + /obj/item/robot_parts/r_leg, \ + /obj/item/robot_parts/l_leg) + + +//Balance tweaks go here vv +#define BIOMASS_BASE_COST 250 +#define MEAT_BIOMASS_VALUE 50 +//These ones are also used for dead limbs/organs +#define BIOMASS_NEW_LIMB_COST 100 +#define BIOMASS_NEW_ORGAN_COST 100 +#define BIOMASS_BURN_WOUND_COST 25 +//These next 3 are for every point of the respective damage type +#define BIOMASS_BRUTE_COST 0.5 +#define BIOMASS_BURN_COST 0.5 +#define BIOMASS_ORGAN_DAMAGE_COST 1 +#define SANGUINE_IB_COST 5 +#define OSSEOUS_BONE_COST 5 -//Potential replacement for genetics revives or something I dunno (?) - -#define BIOMASS_BASE_AMOUNT 50 // How much biomass a BIOMASSABLE item gives the cloning pod - -// Not a comprehensive list: Further PRs should add appropriate items here. -// Meat as usual, monstermeat covers goliath, xeno, spider, bear meat -GLOBAL_LIST_INIT(cloner_biomass_items, list(\ -/obj/item/food/snacks/meat,\ -/obj/item/food/snacks/monstermeat, -/obj/item/food/snacks/carpmeat, -/obj/item/food/snacks/salmonmeat, -/obj/item/food/snacks/catfishmeat, -/obj/item/food/snacks/tofurkey)) - -#define MINIMUM_HEAL_LEVEL 40 -#define CLONE_INITIAL_DAMAGE 190 -#define BRAIN_INITIAL_DAMAGE 90 // our minds are too feeble for 190 /obj/machinery/clonepod anchored = TRUE @@ -26,367 +61,520 @@ GLOBAL_LIST_INIT(cloner_biomass_items, list(\ density = TRUE icon = 'icons/obj/cloning.dmi' icon_state = "pod_idle" - req_access = list(ACCESS_MEDICAL) //For premature unlocking. - - var/mob/living/carbon/human/occupant - var/heal_level //The clone is released once its health reaches this level. - var/obj/machinery/computer/cloning/connected = null //So we remember the connected clone machine. - var/mess = FALSE //Need to clean out it if it's full of exploded clone. - var/attempting = FALSE //One clone attempt at a time thanks - var/biomass = 0 - var/speed_coeff - var/efficiency - - var/datum/mind/clonemind - var/grab_ghost_when = CLONER_MATURE_CLONE - - var/obj/item/radio/Radio - var/radio_announce = TRUE + //So that chemicals can be loaded into the pod. + container_type = OPENCONTAINER + /// The linked cloning console. + var/obj/machinery/computer/cloning/console + + /// Whether or not we're cloning someone. + var/currently_cloning = FALSE + /// The progress on the current clone. + /// Measured from 0-100, where 0-20 has no body, and 21-100 gradually builds on limbs every 10. (r_arm, r_hand, l_arm, l_hand, r_leg, r_foot, l_leg, l_foot) + var/clone_progress = 0 + /// A list of limbs which have not yet been grown by the cloner. + var/list/limbs_to_grow = list() + /// The limb we're currently growing. + var/current_limb + /// Flavor text to show on examine. + var/desc_flavor = "It doesn't seem to be doing anything right now." + /// The countdown. var/obj/effect/countdown/clonepod/countdown - - var/list/brine_types = list("corazone", "perfluorodecalin", "epinephrine", "salglu_solution") //stops heart attacks, heart failure, shock, and keeps their O2 levels normal - var/list/missing_organs = list() - var/organs_number = 0 - - light_color = LIGHT_COLOR_PURE_GREEN - -/obj/machinery/clonepod/power_change() - ..() //we don't check return here because we also care about the BROKEN flag - if(!(stat & (BROKEN|NOPOWER))) - set_light(2) - else - set_light(0) - -/obj/machinery/clonepod/biomass - biomass = CLONER_BIOMASS_REQUIRED + /// Whether or not the interface is locked. + var/locked = TRUE + req_access = list(ACCESS_MEDICAL) + + /// The speed at which we clone. Each processing cycle will advance clone_progress by this amount. + var/speed_modifier = 1 + /// Our price modifier, multiplied with the base cost to get the true cost. + var/price_modifier = 1.1 + /// Our storage modifier, which is used in calculating organ and biomass storage. + var/storage_modifier = 1 + + /// The cloner's biomass count. + var/biomass = 0 + /// How many organs we can store. This is calculated with the storage modifier in RefreshParts(). + var/organ_storage_capacity + /// How much biomass we can store. This is calculated at the same time as organ_storage_capacity. + var/biomass_storage_capacity + + /// The cloning_data datum which shows the patient's current status. + var/datum/cloning_data/patient_data + /// The cloning_data datum which shows the status we want the patient to be in. + var/datum/cloning_data/desired_data + /// Our patient. + var/mob/living/carbon/human/clone /obj/machinery/clonepod/Initialize(mapload) . = ..() + countdown = new(src) - Radio = new /obj/item/radio(src) - Radio.listening = FALSE - Radio.config(list("Medical" = 0)) - Radio.follow_target = src + if(!console && mapload) + console = pick(locate(/obj/machinery/computer/cloning, orange(5, src))) //again, there shouldn't be multiple consoles, mappers component_parts = list() component_parts += new /obj/item/circuitboard/clonepod(null) component_parts += new /obj/item/stock_parts/scanning_module(null) - component_parts += new /obj/item/stock_parts/scanning_module(null) + component_parts += new /obj/item/stock_parts/matter_bin(null) component_parts += new /obj/item/stock_parts/manipulator(null) component_parts += new /obj/item/stock_parts/manipulator(null) component_parts += new /obj/item/stack/sheet/glass(null) component_parts += new /obj/item/stack/cable_coil(null, 1) component_parts += new /obj/item/stack/cable_coil(null, 1) - RefreshParts() + component_parts += new /obj/item/reagent_containers/glass/beaker/large(null) + create_reagents(100) update_icon() + RefreshParts() + +/obj/machinery/clonepod/biomass/Initialize(mapload) + . = ..() + biomass = biomass_storage_capacity /obj/machinery/clonepod/upgraded/Initialize(mapload) . = ..() component_parts = list() component_parts += new /obj/item/circuitboard/clonepod(null) - component_parts += new /obj/item/stock_parts/scanning_module/phasic(null) - component_parts += new /obj/item/stock_parts/scanning_module/phasic(null) - component_parts += new /obj/item/stock_parts/manipulator/pico(null) - component_parts += new /obj/item/stock_parts/manipulator/pico(null) + component_parts += new /obj/item/stock_parts/scanning_module/triphasic(null) + component_parts += new /obj/item/stock_parts/matter_bin/bluespace(null) + component_parts += new /obj/item/stock_parts/manipulator/femto(null) + component_parts += new /obj/item/stock_parts/manipulator/femto(null) component_parts += new /obj/item/stack/sheet/glass(null) component_parts += new /obj/item/stack/cable_coil(null, 1) component_parts += new /obj/item/stack/cable_coil(null, 1) - biomass = CLONER_BIOMASS_REQUIRED + component_parts += new /obj/item/reagent_containers/glass/beaker/bluespace(null) + + update_icon() RefreshParts() + biomass = biomass_storage_capacity + reagents.add_reagent("sanguine_reagent", 150) + reagents.add_reagent("osseous_reagent", 150) + /obj/machinery/clonepod/Destroy() - if(connected) - connected.pods -= src - connected = null - if(clonemind) - UnregisterSignal(clonemind.current, COMSIG_LIVING_REVIVE) - UnregisterSignal(clonemind, COMSIG_MIND_TRANSER_TO) - QDEL_NULL(Radio) - QDEL_NULL(countdown) - QDEL_LIST_CONTENTS(missing_organs) - return ..() + if(console) + console.pods -= src + if(console.selected_pod == src && length(console.pods) > 0) + console.selected_pod = pick(console.pods) + else + console.selected_pod = null -/obj/machinery/clonepod/RefreshParts() - speed_coeff = 0 - efficiency = 0 - for(var/obj/item/stock_parts/scanning_module/S in component_parts) - efficiency += S.rating - for(var/obj/item/stock_parts/manipulator/P in component_parts) - speed_coeff += P.rating - heal_level = max(min((efficiency * 15) + 10, 100), MINIMUM_HEAL_LEVEL) - -//The return of data disks?? Just for transferring between genetics machine/cloning machine. -//TO-DO: Make the genetics machine accept them. -/obj/item/disk/data - name = "Genetics Data Disk" - icon_state = "datadisk0" //Gosh I hope syndies don't mistake them for the nuke disk. - var/datum/dna2/record/buf = null - var/read_only = FALSE //Well,it's still a floppy disk - -/obj/item/disk/data/proc/initialize() - buf = new - buf.dna=new - -/obj/item/disk/data/Destroy() - QDEL_NULL(buf) + QDEL_NULL(countdown) return ..() -/obj/item/disk/data/demo - name = "data disk - 'God Emperor of Mankind'" - read_only = TRUE - -/obj/item/disk/data/demo/New() - ..() - initialize() - buf.types=DNA2_BUF_UE|DNA2_BUF_UI - //data = "066000033000000000AF00330660FF4DB002690" - //data = "0C80C80C80C80C80C8000000000000161FBDDEF" - Farmer Jeff - buf.dna.real_name="God Emperor of Mankind" - buf.dna.unique_enzymes = md5(buf.dna.real_name) - buf.dna.UI=list(0x066,0x000,0x033,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0xAF0,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x033,0x066,0x0FF,0x4DB,0x002,0x690,0x000,0x000,0x000,0x328,0x045,0x5FC,0x053,0x035,0x035,0x035) - //buf.dna.UI=list(0x0C8,0x0C8,0x0C8,0x0C8,0x0C8,0x0C8,0x000,0x000,0x000,0x000,0x161,0xFBD,0xDEF) // Farmer Jeff - if(buf.dna.UI.len != DNA_UI_LENGTH) //If there's a disparity b/w the dna UI string lengths, 0-fill the extra blocks in this UI. - for(var/i in buf.dna.UI.len to DNA_UI_LENGTH) - buf.dna.UI += 0x000 - buf.dna.ResetSE() - buf.dna.UpdateUI() - -/obj/item/disk/data/monkey - name = "data disk - 'Mr. Muggles'" - read_only = 1 - -/obj/item/disk/data/monkey/New() - ..() - initialize() - buf.types=DNA2_BUF_SE - var/list/new_SE=list(0x098,0x3E8,0x403,0x44C,0x39F,0x4B0,0x59D,0x514,0x5FC,0x578,0x5DC,0x640,0x6A4) - for(var/i=new_SE.len;i<=DNA_SE_LENGTH;i++) - new_SE += rand(1,1024) - buf.dna.SE=new_SE - buf.dna.SetSEValueRange(GLOB.monkeyblock,0xDAC, 0xFFF) - -//Disk stuff. -/obj/item/disk/data/New() - ..() - var/diskcolor = pick(0,1,2) - icon_state = "datadisk[diskcolor]" - -/obj/item/disk/data/attack_self(mob/user as mob) - read_only = !read_only - to_chat(user, "You flip the write-protect tab to [read_only ? "protected" : "unprotected"].") - -/obj/item/disk/data/examine(mob/user) - . = ..() - . += "The write-protect tab is set to [read_only ? "protected" : "unprotected"]." - -//Clonepod - /obj/machinery/clonepod/examine(mob/user) . = ..() - if(mess) - . += "It's filled with blood and viscera. You swear you can see it moving..." - if(HAS_TRAIT(src, TRAIT_CMAGGED)) - . += "Yellow ooze is dripping out of the synthmeat storage chamber..." - if(!occupant || stat & (NOPOWER|BROKEN)) - return - if(occupant && occupant.stat != DEAD) - . += "Current clone cycle is [round(get_completion())]% complete." - -/obj/machinery/clonepod/return_air() //non-reactive air - RETURN_TYPE(/datum/gas_mixture) - var/datum/gas_mixture/GM = new - GM.nitrogen = MOLES_O2STANDARD + MOLES_N2STANDARD - GM.temperature = T20C - return GM - -/obj/machinery/clonepod/proc/get_completion() - . = (100 * ((occupant.health + 100) / (heal_level + 100))) + . += "[desc_flavor]" + . += "[src] is currently [locked ? "locked" : "unlocked"], and can be [locked ? "unlocked" : "locked"] by swiping an ID with medical access on it." -/obj/machinery/clonepod/attack_ai(mob/user) - return examine(user) - -//Radio Announcement - -/obj/machinery/clonepod/proc/announce_radio_message(message) - if(radio_announce) - Radio.autosay(message, name, "Medical", list(z)) - -//Start growing a human clone in the pod! -/obj/machinery/clonepod/proc/growclone(datum/dna2/record/R) - if(mess || attempting || panel_open || stat & (NOPOWER|BROKEN)) - return 0 - clonemind = locate(R.mind) - if(!istype(clonemind)) //not a mind - return 0 - if(clonemind.current && clonemind.current.stat != DEAD) //mind is associated with a non-dead body - return 0 - if(clonemind.active) //somebody is using that mind - if(ckey(clonemind.key) != R.ckey) - return 0 - if(clonemind.suicided) // and stay out! - malfunction(go_easy = 0) - return -1 // Flush the record - else - // get_ghost() will fail if they're unable to reenter their body - var/mob/dead/observer/G = clonemind.get_ghost() - if(!G) - return 0 +/obj/machinery/clonepod/RefreshParts() + speed_modifier = 0 //Since we have multiple manipulators, which affect this modifier, we reset here so we can just use += later + for(var/obj/item/stock_parts/SP as anything in component_parts) + if(istype(SP, /obj/item/stock_parts/matter_bin/)) //Matter bins for storage modifier + storage_modifier = round(10 * (SP.rating / 2)) //5 at tier 1, 10 at tier 2, 15 at tier 3, 20 at tier 4 + else if(istype(SP, /obj/item/stock_parts/scanning_module)) //Scanning modules for price modifier (more accurate scans = more efficient) + price_modifier = -(SP.rating / 10) + 1.2 //1.1 at tier 1, 1 at tier 2, 0.9 at tier 3, 0.8 at tier 4 + else if(istype(SP, /obj/item/stock_parts/manipulator)) //Manipulators for speed modifier + speed_modifier += SP.rating / 2 //1 at tier 1, 2 at tier 2, et cetera - if(biomass >= CLONER_BIOMASS_REQUIRED) - biomass -= CLONER_BIOMASS_REQUIRED - else - return 0 + for(var/obj/item/reagent_containers/glass/beaker/B in component_parts) + if(istype(B)) + reagents.maximum_volume = B.volume //The default cloning pod has a large beaker in it, so 100u. - attempting = TRUE //One at a time!! - countdown.start() + organ_storage_capacity = storage_modifier + biomass_storage_capacity = storage_modifier * 400 - if(!R.dna) - R.dna = new /datum/dna() - var/mob/living/carbon/human/H = new /mob/living/carbon/human(src) - H.set_species(R.dna.species.type) - occupant = H - if(!R.dna.real_name) //to prevent null names - R.dna.real_name = H.real_name - else - H.real_name = R.dna.real_name - - H.dna = R.dna.Clone() - - for(var/datum/language/L in R.languages) - H.add_language(L.name) - - domutcheck(H, MUTCHK_FORCED) //Ensures species that get powers by the species proc handle_dna keep them - - if(efficiency > 2 && efficiency < 5 && prob(25)) - randmutb(H) - if(efficiency > 5 && prob(20)) - randmutg(H) - if(efficiency < 3 && prob(50)) - randmutb(H) - - H.dna.UpdateSE() - H.dna.UpdateUI() - - H.sync_organ_dna(1) // It's literally a fresh body as you can get, so all organs properly belong to it - H.UpdateAppearance() - - check_brine() - //Get the clone body ready - maim_clone(H) - H.Paralyse(8 SECONDS) - - if(grab_ghost_when == CLONER_FRESH_CLONE) - clonemind.transfer_to(H) - H.ckey = R.ckey - var/message - message += "Consciousness slowly creeps over you as your body regenerates.
" - message += "So this is what cloning feels like?" - to_chat(H, "[message]") - else if(grab_ghost_when == CLONER_MATURE_CLONE) - to_chat(clonemind.current, "Your body is beginning to regenerate in a cloning pod. You will become conscious when it is complete.") - // Set up a soul link with the dead body to catch a revival - RegisterSignal(clonemind.current, COMSIG_LIVING_REVIVE, PROC_REF(occupant_got_revived)) - RegisterSignal(clonemind, COMSIG_MIND_TRANSER_TO, PROC_REF(occupant_got_revived)) - - SSblackbox.record_feedback("tally", "players_revived", 1, "cloned") - update_icon() +//Process +/obj/machinery/clonepod/process() - H.suiciding = FALSE - attempting = FALSE - return 1 + //Basically just isolate_reagent() with extra functionality. + for(var/datum/reagent/R as anything in reagents.reagent_list) + if(!(R.id in VALID_REAGENTS)) + reagents.del_reagent(R.id) + reagents.update_total() + atom_say("Purged contaminant from chemical storage.") -//Grow clones to maturity then kick them out. FREELOADERS -/obj/machinery/clonepod/process() + //Take in biomass. Mostly copied from the old cloning code var/show_message = FALSE for(var/obj/item/item in range(1, src)) - if(is_type_in_list(item, GLOB.cloner_biomass_items)) + if(is_type_in_list(item, VALID_BIOMASSABLES) && (biomass + MEAT_BIOMASS_VALUE <= biomass_storage_capacity)) qdel(item) - biomass += BIOMASS_BASE_AMOUNT + biomass += MEAT_BIOMASS_VALUE show_message = TRUE if(show_message) - visible_message("[src] sucks in and processes the nearby biomass.") + visible_message("[src] sucks in nearby biomass.") + + //If we're cloning someone, we haven't generated a list of limbs to grow, and we're before any possibility of not having any limbs left to grow. + if(currently_cloning && !length(limbs_to_grow) && clone_progress < 20) + for(var/limb in desired_data.limbs) + if(desired_data.limbs[limb][4]) + continue //We're not growing this limb, since in the desired state it's missing. + + var/obj/item/organ/external/limb_typepath = patient_data.genetic_info.species.has_limbs[limb]["path"] + if(initial(limb_typepath.vital)) //I hate everything about this check, but it sees if the current organ is vital.. + continue //and continues if it is, since the proc that creates the clone mob will make these all at once. + var/parent_organ_is_limb = FALSE + for(var/organ in desired_data.organs) + var/obj/item/organ/external/organ_typepath = patient_data.genetic_info.species.has_organ[organ] + if(!initial(organ_typepath.vital)) //I hate this check too. We loop through all the organs the cloned species should have. + continue //If it's not a vital organ, continue looping through the organs. + if(initial(organ_typepath.parent_organ) == limb) + parent_organ_is_limb = TRUE //If it's a vital organ, and belongs to the current limb, we don't want this limb. + break + if(parent_organ_is_limb) + continue + limbs_to_grow += limb //It's not supposed to be missing and it's not vital - so we'll be growing it. + limbs_to_grow = shuffle(limbs_to_grow) + + if(clone) + clone.Weaken(4 SECONDS) //make sure they stay in the pod + clone.setOxyLoss(0) //..and alive + + //Actually grow clones (this is the fun part of the proc!) + if(currently_cloning) + switch(clone_progress) + if(0 to 10) + desc_flavor = "You see muscle quickly growing on a ribcage and skull inside [src]." + clone_progress += speed_modifier + return + if(11 to 90) + clone_progress += speed_modifier + if(!clone) + create_clone() + return + + if(clone.cloneloss >= 25) + clone.adjustCloneLoss(-2) + return + + if(!current_limb) + if(!length(limbs_to_grow)) //if we meet all of the conditions to get here, there's nothing left to do in this section + desc_flavor = "You see muscle and fat filling out on [clone]'s body." + return + for(var/limb_candidate in limbs_to_grow) + var/obj/item/organ/external/LC = clone.dna.species.has_limbs[limb_candidate]["path"] + if(initial(LC.parent_organ) in limbs_to_grow) + continue //If we haven't grown this limb's parent organ yet, we don't want to grow it now. + current_limb = limb_candidate //If we have grown it, then we're good to grow it now. + limbs_to_grow -= limb_candidate + return + + if(!(current_limb in clone.bodyparts)) + if(get_stored_organ(current_limb)) + var/obj/item/organ/external/EO = get_stored_organ(current_limb) + desc_flavor = "You see [src] attaching \a [EO.name] to [clone]." + EO.replaced(clone) + current_limb = null + clone.adjustCloneLoss(4 / speed_modifier) + clone.regenerate_icons() + return + + var/list/EO_path = clone.dna.species.has_limbs[current_limb]["path"] + var/obj/item/organ/external/EO = new EO_path(clone) //Passing a human to a limb's New() proc automatically attaches it + desc_flavor = "You see \a [EO.name] growing from [clone]'[clone.p_s()] [EO.amputation_point]." + current_limb = null + EO.brute_dam = desired_data.limbs[EO.limb_name][1] + EO.burn_dam = desired_data.limbs[EO.limb_name][2] + EO.status = desired_data.limbs[EO.limb_name][3] + clone.adjustCloneLoss(4 / speed_modifier) + clone.regenerate_icons() + return + + if(91 to 100) + if(length(limbs_to_grow) || current_limb) //This shouldn't happen, but just in case.. (no more feetless clones) + clone_progress -= 5 + if(eject_clone()) + return + clone.adjustCloneLoss(-5 * speed_modifier) //rapidly heal clone damage + desc_flavor = "You see [src] finalizing the cloning process." + clone_progress += speed_modifier + return + if(101 to INFINITY) //this state can be reached with an upgraded cloner + if(eject_clone()) + return + clone.setCloneLoss(0) //get out of the pod!! + return + +//Clonepod-specific procs +//This just begins the cloning process. Called by the cloning console. +/obj/machinery/clonepod/proc/start_cloning(datum/cloning_data/_patient_data, datum/cloning_data/_desired_data) + currently_cloning = TRUE + patient_data = _patient_data + desired_data = _desired_data + + var/cost = get_cloning_cost(patient_data, desired_data) + biomass -= cost[BIOMASS_COST] + reagents.remove_reagent("sanguine_reagent", cost[SANGUINE_COST]) + reagents.remove_reagent("osseous_reagent", cost[OSSEOUS_COST]) - if(stat & NOPOWER) //Autoeject if power is lost - if(occupant) - go_out() - connected_message("Clone Ejected: Loss of power.") + countdown.start() + update_icon(UPDATE_ICON_STATE) - else if((occupant) && (occupant.loc == src)) - if((occupant.stat == DEAD) || (occupant.suiciding)) //Autoeject corpses and suiciding dudes. - announce_radio_message("The cloning of [occupant] has been aborted due to unrecoverable tissue failure.") - go_out() - connected_message("Clone Rejected: Deceased.") +//Creates the clone! Used once the cloning pod reaches ~10% completion. +/obj/machinery/clonepod/proc/create_clone() + clone = new /mob/living/carbon/human(src, patient_data.genetic_info.species.type) - else if(occupant.cloneloss > (100 - heal_level)) - occupant.Paralyse(8 SECONDS) + clone.change_dna(patient_data.genetic_info, FALSE, TRUE) - //Slowly get that clone healed and finished. - occupant.adjustCloneLoss(-((speed_coeff/2))) + for(var/obj/item/organ/external/limb in clone.bodyparts) + if(!(limb.limb_name in limbs_to_grow)) //if the limb was determined to be vital + var/active_limb_name = limb.limb_name + limb.brute_dam = desired_data.limbs[active_limb_name][1] + limb.burn_dam = desired_data.limbs[active_limb_name][2] + limb.status = desired_data.limbs[active_limb_name][3] + continue + if(length(limb.children)) //This doesn't support having a vital organ inside a child of a limb that itself isn't vital. + for(var/obj/item/organ/external/child in limb.children) //Future coders, if you want to add a species with its brain in its hand or something, change this + child.remove(null, TRUE) + qdel(child) + limb.remove(null, TRUE) + qdel(limb) + + for(var/candidate_for_insertion in desired_data.organs) + var/obj/item/organ/internal/organ = clone.get_int_organ(clone.dna.species.has_organ[candidate_for_insertion]) + + if(desired_data.organs[candidate_for_insertion][3]) //if it's desired for the organ to be missing.. + qdel(organ) //make it so + continue - // For human species that lack non-vital parts for some weird reason - if(organs_number) - var/progress = CLONE_INITIAL_DAMAGE - occupant.getCloneLoss() - progress += (100 - MINIMUM_HEAL_LEVEL) - var/milestone = CLONE_INITIAL_DAMAGE / organs_number -// Doing this as a #define so that the value can change when evaluated multiple times -#define INSTALLED (organs_number - length(missing_organs)) + if(get_stored_organ(candidate_for_insertion)) + var/obj/item/organ/internal/IO = get_stored_organ(candidate_for_insertion) + qdel(organ) + IO.insert(clone) //hotswap + continue - while((progress / milestone) > INSTALLED && length(missing_organs)) - var/obj/item/organ/I = pick_n_take(missing_organs) - I.safe_replace(occupant) + if(candidate_for_insertion == "heart") + continue //The heart is always cloned or replaced, remember? If we're not inserting a new one, we don't need to touch it. -#undef INSTALLED + organ.damage = desired_data.organs[candidate_for_insertion][1] + organ.status = desired_data.organs[candidate_for_insertion][2] - //Premature clones may have brain damage. - occupant.adjustBrainLoss(-((speed_coeff/20)*efficiency)) + clone.updatehealth("droplimb") + clone.regenerate_icons() - check_brine() + clone.set_heartattack(FALSE) //you are not allowed to die + clone.adjustCloneLoss(25) //to punish early ejects + clone.Weaken(4 SECONDS) - //Also heal some oxyloss ourselves just in case!! - occupant.adjustOxyLoss(-10) +//Ejects a clone. The force var ejects even if there's still clone damage. +/obj/machinery/clonepod/proc/eject_clone(force = FALSE) + if(!currently_cloning) + return FALSE - use_power(7500) //This might need tweaking. + if(!clone && force) + new /obj/effect/gibspawner/generic(get_turf(src), desired_data.genetic_info) + playsound(loc, 'sound/effects/splat.ogg', 50, TRUE) + reset_cloning() + return TRUE + + if(!clone.cloneloss) + clone.forceMove(loc) + var/datum/mind/patient_mind = locateUID(patient_data.mindUID) + patient_mind.transfer_to(clone) + clone.grab_ghost() + clone.update_revive() + to_chat(clone, "You remember nothing from the time that you were dead!") + to_chat(clone, "There's a bright flash of light, and you take your first breath once more.") + + reset_cloning() + return TRUE + + if(!force) + return FALSE - else if((occupant.cloneloss <= (100 - heal_level))) - connected_message("Cloning Process Complete.") - announce_radio_message("The cloning cycle of [occupant] is complete.") - go_out() + clone.forceMove(loc) + new /obj/effect/gibspawner/generic(get_turf(src), clone) + playsound(loc, 'sound/effects/splat.ogg', 50, TRUE) - else if((!occupant) || (occupant.loc != src)) - occupant = null - update_icon() - use_power(200) + var/datum/mind/patient_mind = locateUID(patient_data.mindUID) + patient_mind.transfer_to(clone) + clone.grab_ghost() + clone.update_revive() + to_chat(clone, "You remember nothing from the time that you were dead!") + to_chat(clone, "You're ripped out of blissful oblivion! You feel like shit.") -//Let's unlock this early I guess. Might be too early, needs tweaking. -/obj/machinery/clonepod/attackby(obj/item/I, mob/user, params) - if(exchange_parts(user, I)) + reset_cloning() + return TRUE + +//Helper proc for the above +/obj/machinery/clonepod/proc/reset_cloning() + currently_cloning = FALSE + clone = null + patient_data = null + desired_data = null + clone_progress = 0 + desc_flavor = initial(desc_flavor) + update_icon(UPDATE_ICON_STATE) + countdown.stop() + +//This gets the cost of cloning, in a list with the form (biomass, sanguine reagent, osseous reagent). +/obj/machinery/clonepod/proc/get_cloning_cost(datum/cloning_data/_patient_data, datum/cloning_data/_desired_data) + var/datum/cloning_data/p_data = _patient_data + var/datum/cloning_data/d_data = _desired_data + //Biomass, sanguine reagent, osseous reagent + var/list/cloning_cost = list((price_modifier * BIOMASS_BASE_COST), 0, 0) + + if(!istype(p_data) || !istype(d_data)) + return //this shouldn't happen but whatever + + for(var/limb in p_data.limbs) + + if(get_stored_organ(limb)) + continue //if we have a stored organ, we'll be replacing it - so no biomass or sanguine/osseous cost + + var/list/patient_limb_info = p_data.limbs[limb] + var/patient_limb_status = patient_limb_info[3] + + var/list/desired_limb_info = d_data.limbs[limb] + var/desired_limb_status = desired_limb_info[3] + + if(p_data.limbs[limb][4] && !d_data.limbs[limb][4]) //if the limb is missing on the patient and we want it to not be + cloning_cost[1] += BIOMASS_NEW_LIMB_COST * price_modifier + continue //then continue - since we're replacing the limb, we don't need to fix its damages + + if((patient_limb_status & ORGAN_DEAD) && !(desired_limb_status & ORGAN_DEAD)) //if the patient's limb is dead and we don't want it to be + cloning_cost[1] += BIOMASS_NEW_LIMB_COST * price_modifier + continue //as above + + var/brute_damage_diff = patient_limb_info[1] - desired_limb_info[1] + cloning_cost[1] += BIOMASS_BRUTE_COST * brute_damage_diff * price_modifier + + var/burn_damage_diff = patient_limb_info[2] - desired_limb_info[2] + cloning_cost[1] += BIOMASS_BURN_COST * burn_damage_diff * price_modifier + + if((patient_limb_status & ORGAN_BURNT) && !(desired_limb_status & ORGAN_BURNT)) //if the patient's limb has a burn wound and we don't want it to + cloning_cost[1] += BIOMASS_BURN_WOUND_COST * price_modifier + + if((patient_limb_status & ORGAN_INT_BLEEDING) && !(desired_limb_status & ORGAN_INT_BLEEDING)) //if the patient's limb has IB and we want it to not be + cloning_cost[2] += SANGUINE_IB_COST * price_modifier + + if((patient_limb_status & ORGAN_BROKEN) && !(desired_limb_status & ORGAN_BROKEN)) //if the patient's limb is broken and we want it to not be + cloning_cost[3] += OSSEOUS_BONE_COST * price_modifier + + for(var/organ in p_data.organs) + + if(organ == "heart") //The heart is always replaced in cloning. This is factored into the base biomass cost, so we don't add more here. + if(get_stored_organ(organ)) + cloning_cost[1] -= BIOMASS_NEW_ORGAN_COST //the cost of a new organ should ALWAYS be below the base cloning cost + continue + + if(get_stored_organ(organ)) + continue //if we can replace it, we will, so no need to do the rest of the loop + + var/list/patient_organ_info = p_data.organs[organ] + var/patient_organ_status = patient_organ_info[2] + + var/list/desired_organ_info = d_data.organs[organ] + var/desired_organ_status = desired_organ_info[2] + + if(patient_organ_info[3] && !desired_organ_info[3]) //If it's missing, and we don't want it to be, replace the organ + cloning_cost[1] += BIOMASS_NEW_ORGAN_COST * price_modifier + continue + + if((desired_organ_status & ORGAN_DEAD) && !(patient_organ_status & ORGAN_DEAD)) //if the patient's organ is dead and we want it to not be + cloning_cost[1] += BIOMASS_NEW_ORGAN_COST * price_modifier + continue //.. then continue, because if we replace the organ we don't need to fix its damages + + var/organ_damage_diff = patient_organ_info[1] - desired_organ_info[1] + cloning_cost[1] += BIOMASS_ORGAN_DAMAGE_COST * organ_damage_diff * price_modifier + + cloning_cost[1] = round(cloning_cost[1]) //no decimal-point amounts of biomass! + + return cloning_cost + +//insert an organ into storage +/obj/machinery/clonepod/proc/insert_organ(obj/item/organ/inserted, mob/inserter) + var/has_children = FALSE //Used for arms and legs + var/stored_organs + for(var/obj/item/organ/O in contents) + stored_organs++ + for(var/obj/item/robot_parts/R in contents) + stored_organs++ + + if(stored_organs >= organ_storage_capacity) + to_chat(inserter, "[src]'s organ storage is full!") return - if(I.GetID()) - if(!check_access(I)) - to_chat(user, "Access Denied.") + if(is_int_organ(inserted)) + if(is_type_in_list(inserted, FORBIDDEN_INTERNAL_ORGANS)) + to_chat(inserter, "[src] refuses [inserted].") + return + if(is_type_in_list(inserted, UPGRADE_LOCKED_ORGANS) && speed_modifier < 4) //if our manipulators aren't fully upgraded + to_chat(inserter, "[src] refuses [inserted].") return - if(!(occupant || mess)) - to_chat(user, "Error: Pod has no occupant.") + if(inserted.status & ORGAN_ROBOT && speed_modifier == 1) //if our manipulators aren't upgraded at all + to_chat(inserter, "[src] refuses [inserted].") + return + + if(isorgan(inserted)) + if(is_type_in_list(inserted, FORBIDDEN_LIMBS)) + to_chat(inserter, "[src] refuses [inserted].") return + var/obj/item/organ/external/EO = inserted + if(length(EO.children)) + if((stored_organs + 1 + length(EO.children)) > organ_storage_capacity) + to_chat(inserter, "You can't fit all of [inserted] into [src]'s organ storage!") + return + has_children = TRUE + for(var/obj/item/organ/external/child in EO.children) + child.forceMove(src) + child.parent = null + EO.children -= child + EO.compile_icon() + + if(is_type_in_list(inserted, ALLOWED_ROBOT_PARTS) && speed_modifier == 1) //if our manipulators aren't upgraded at all + to_chat(inserter, "[src] refuses [inserted].") + return + + if(ismob(inserted.loc)) + var/mob/M = inserted.loc + if(!M.get_active_hand() == inserted) + return //not sure how this would happen, but smartfridges check for it so + if(!M.drop_item()) + to_chat(inserter, "[inserted] is stuck to you!") + return + M.unEquip(inserted) + inserted.forceMove(src) + to_chat(inserter, "You insert [inserted] into [src]'s organ storage.") + SStgui.try_update_ui(inserter, src) + if(has_children) + visible_message("There's a crunching sound as [src] breaks down [inserted] into discrete parts.", "You hear a loud crunch.") + +/obj/machinery/clonepod/proc/get_stored_organ(organ) + for(var/obj/item/organ/external/EO in contents) + if(EO.limb_name == organ) + return EO + for(var/obj/item/organ/internal/IO in contents) + if(IO.organ_tag == organ) + return IO + for(var/obj/item/robot_parts/RP in contents) + if(organ in RP.part) + return RP + return FALSE + +//Attackby and x_acts +/obj/machinery/clonepod/attackby(obj/item/I, mob/user, params) + if(exchange_parts(user, I)) + return + + if(I.is_open_container()) + return + + if(istype(I, /obj/item/card/id) || istype(I, /obj/item/pda)) + if(allowed(user)) + locked = !locked + to_chat(user, "Access restriction is now [locked ? "enabled" : "disabled"].") else - connected_message("Authorized Ejection") - announce_radio_message("An authorized ejection of [(occupant) ? occupant.real_name : "the malfunctioning pod"] has occurred") - to_chat(user, "You force an emergency ejection.") - go_out() - -// A user can feed in biomass sources manually. - else if(is_type_in_list(I, GLOB.cloner_biomass_items)) - if(user.drop_item()) - to_chat(user, "[src] processes [I].") - biomass += BIOMASS_BASE_AMOUNT - qdel(I) - else - return ..() + to_chat(user, "Access denied.") + return + + if(is_int_organ(I) || isorgan(I) || is_type_in_list(I, ALLOWED_ROBOT_PARTS)) //fun fact, robot parts aren't organs! + insert_organ(I, user) + return + + return ..() /obj/machinery/clonepod/crowbar_act(mob/user, obj/item/I) . = TRUE @@ -403,7 +591,6 @@ GLOBAL_LIST_INIT(cloner_biomass_items, list(\ /obj/machinery/clonepod/screwdriver_act(mob/user, obj/item/I) . = TRUE - // These icon states don't really matter since we need to call update_icon() to handle panel open/closed overlays anyway. default_deconstruction_screwdriver(user, null, null, I) update_icon() @@ -411,119 +598,96 @@ GLOBAL_LIST_INIT(cloner_biomass_items, list(\ . = TRUE if(!I.use_tool(src, user, 0, volume = I.tool_volume)) return - if(occupant) - to_chat(user, "Can not do that while [src] is in use.") - return if(anchored) WRENCH_UNANCHOR_MESSAGE anchored = FALSE - connected.pods -= src - connected = null else WRENCH_ANCHOR_MESSAGE anchored = TRUE -/obj/machinery/clonepod/emag_act(user) - malfunction() - return TRUE +/obj/machinery/clonepod/attack_hand(mob/user) + . = ..() + add_fingerprint(user) -/obj/machinery/clonepod/cmag_act(mob/user) - if(HAS_TRAIT(src, TRAIT_CMAGGED)) + if(stat & (BROKEN|NOPOWER)) return - playsound(src, "sparks", 75, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) - to_chat(user, "A droplet of bananium ooze seeps into the synthmeat storage chamber...") - ADD_TRAIT(src, TRAIT_CMAGGED, CLOWN_EMAG) -//Put messages in the connected computer's temp var for display. -/obj/machinery/clonepod/proc/connected_message(message) - if((isnull(connected)) || (!istype(connected, /obj/machinery/computer/cloning))) - return FALSE - if(!message) - return FALSE + ui_interact(user) + +/obj/machinery/clonepod/attack_ai(mob/user) + return attack_hand(user) + +/obj/machinery/clonepod/attack_ghost(mob/user) + return attack_hand(user) - connected.temp = "[name] : [message]" - connected.updateUsrDialog() +/obj/machinery/clonepod/emag_act(user) + . = ..() + eject_clone(TRUE) return TRUE -/obj/machinery/clonepod/proc/go_out() - countdown.stop() - var/turf/T = get_turf(src) - if(mess) //Clean that mess and dump those gibs! - for(var/i in missing_organs) - var/obj/I = i - I.forceMove(T) - missing_organs.Cut() - mess = FALSE - new /obj/effect/gibspawner/generic(get_turf(src), occupant) - playsound(loc, 'sound/effects/splat.ogg', 50, 1) - update_icon() +/obj/machinery/clonepod/emp_act(severity) + if(prob(50)) + eject_clone(TRUE) + return ..() + +//TGUI +/obj/machinery/clonepod/ui_interact(mob/user, datum/tgui/ui = null) + if(stat & (NOPOWER|BROKEN)) return - if(!occupant) + if(!allowed(user) && locked && !isobserver(user)) + to_chat(user, "Access denied.") + if(ui) + ui.close() return - if(grab_ghost_when == CLONER_MATURE_CLONE) - UnregisterSignal(clonemind.current, COMSIG_LIVING_REVIVE) - UnregisterSignal(clonemind, COMSIG_MIND_TRANSER_TO) - clonemind.transfer_to(occupant) - occupant.grab_ghost() - to_chat(occupant, "You remember nothing from the time that you were dead!") - to_chat(occupant, "There is a bright flash!
\ - You feel like a new being.
") - if(HAS_TRAIT(src, TRAIT_CMAGGED)) - playsound(loc, 'sound/items/bikehorn.ogg', 50, 1) - occupant.dna.SetSEState(GLOB.clumsyblock, TRUE, FALSE) - occupant.dna.SetSEState(GLOB.comicblock, TRUE, FALSE) - singlemutcheck(occupant, GLOB.clumsyblock, MUTCHK_FORCED) - singlemutcheck(occupant, GLOB.comicblock, MUTCHK_FORCED) - occupant.dna.default_blocks.Add(GLOB.clumsyblock) //Until Genetics fixes you, this is your life now - occupant.dna.default_blocks.Add(GLOB.comicblock) - occupant.flash_eyes(visual = 1) - clonemind = null - - - QDEL_LIST_CONTENTS(missing_organs) - occupant.SetLoseBreath(0) // Stop friggin' dying, gosh damn - occupant.setOxyLoss(0) - for(var/datum/disease/critical/crit in occupant.viruses) - crit.cure() - occupant.forceMove(T) - occupant.update_body() - domutcheck(occupant) //Waiting until they're out before possible notransform. - occupant.special_post_clone_handling() - occupant = null - update_icon() + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "CloningPod", "Cloning Pod") + ui.open() + +/obj/machinery/clonepod/ui_data(mob/user) + var/list/data = list() + data["biomass"] = biomass + data["biomass_storage_capacity"] = biomass_storage_capacity + data["sanguine_reagent"] = reagents.get_reagent_amount("sanguine_reagent") + data["osseous_reagent"] = reagents.get_reagent_amount("osseous_reagent") + + var/list/organs_list + for(var/obj/item/organ/O in contents) + organs_list += list(list("name" = O.name, "ref" = O.UID())) + + data["organs"] = organs_list + data["currently_cloning"] = currently_cloning + + return data + +/obj/machinery/clonepod/ui_act(action, params, datum/tgui/ui) + if(..()) + return + switch(action) + if("eject_organ") + var/obj/item/organ/O = locateUID(params["organ_ref"]) + if(!istype(O)) //This shouldn't happen + return FALSE + if(!ui.user.put_in_hands(O)) + O.forceMove(loc) + return TRUE + if("purge_reagent") + if(params["reagent"]) + reagents.del_reagent(params["reagent"]) + return TRUE + if("remove_reagent") + if(params["reagent"]) + reagents.remove_reagent(params["reagent"], params["amount"]) + return TRUE -/obj/machinery/clonepod/proc/malfunction(go_easy = FALSE) - if(occupant) - connected_message("Critical Error!") - announce_radio_message("Critical error! Please contact a Thinktronic Systems technician, as your warranty may be affected.") - UnregisterSignal(clonemind.current, COMSIG_LIVING_REVIVE) - UnregisterSignal(clonemind, COMSIG_MIND_TRANSER_TO) - if(!go_easy) - if(occupant.mind != clonemind) - clonemind.transfer_to(occupant) - occupant.grab_ghost() // We really just want to make you suffer. - var/message - message += "Agony blazes across your consciousness as your body is torn apart.
" - message += "Is this what dying is like? Yes it is." - to_chat(occupant, "[message]") - SEND_SOUND(occupant, sound('sound/hallucinations/veryfar_noise.ogg', 0, 1, 50)) - QDEL_LIST_CONTENTS(missing_organs) - clonemind = null - spawn(40) - qdel(occupant) - - - playsound(loc, 'sound/machines/warning-buzzer.ogg', 50, 0) - mess = TRUE update_icon() +//Icon stuff /obj/machinery/clonepod/update_icon_state() - if(occupant && !(stat & NOPOWER)) + if(currently_cloning && !(stat & NOPOWER)) icon_state = "pod_cloning" - else if(mess) - icon_state = "pod_mess" else icon_state = "pod_idle" @@ -532,103 +696,20 @@ GLOBAL_LIST_INIT(cloner_biomass_items, list(\ if(panel_open) . += "panel_open" -/obj/machinery/clonepod/relaymove(mob/user) - if(user.stat == CONSCIOUS) - go_out() - -/obj/machinery/clonepod/emp_act(severity) - if(prob(100/(severity*efficiency))) malfunction() - ..() - -/obj/machinery/clonepod/ex_act(severity) - ..() - if(!QDELETED(src) && occupant) - go_out() - -/obj/machinery/clonepod/handle_atom_del(atom/A) - if(A == occupant) - occupant = null - countdown.stop() - -/obj/machinery/clonepod/deconstruct(disassembled = TRUE) - if(occupant) - go_out() - ..() - -/obj/machinery/clonepod/proc/occupant_got_revived() - // The old body's back in shape, time to ditch the cloning one - malfunction(go_easy = TRUE) - -/obj/machinery/clonepod/proc/maim_clone(mob/living/carbon/human/H) - QDEL_LIST_CONTENTS(missing_organs) - - H.setCloneLoss(CLONE_INITIAL_DAMAGE, FALSE) - H.setBrainLoss(BRAIN_INITIAL_DAMAGE) - - for(var/o in H.internal_organs) - var/obj/item/organ/O = o - if(!istype(O) || O.vital) - continue - - // Let's non-specially remove all non-vital organs - // What could possibly go wrong - var/obj/item/I = O.remove(H) - // Make this support stuff that turns into items when removed - I.forceMove(src) - missing_organs += I - - var/static/list/zones = list("r_arm", "l_arm", "r_leg", "l_leg") - for(var/zone in zones) - var/obj/item/organ/external/E = H.get_organ(zone) - var/obj/item/I = E.remove(H) - I.forceMove(src) - missing_organs += I - - organs_number = length(missing_organs) - H.updatehealth() - -/obj/machinery/clonepod/proc/check_brine() - // Clones are in a pickled bath of mild chemicals, keeping - // them alive, despite their lack of internal organs - for(var/bt in brine_types) - if(occupant.reagents.get_reagent_amount(bt) < 1) - occupant.reagents.add_reagent(bt, 1) - -/* - * Manual -- A big ol' manual. - */ - -/obj/item/paper/Cloning - name = "paper - 'H-87 Cloning Apparatus Manual" - info = {"

Getting Started

- Congratulations, your station has purchased the H-87 industrial cloning device!
- Using the H-87 is almost as simple as brain surgery! Simply insert the target humanoid into the scanning chamber and select the scan option to create a new profile!
- That's all there is to it!
- Notice, cloning system cannot scan inorganic life or small primates. Scan may fail if subject has suffered extreme brain damage.
-

Clone profiles may be viewed through the profiles menu. Scanning implants a complementary HEALTH MONITOR BIO-CHIP into the subject, which may be viewed from each profile. - Profile Deletion has been restricted to \[Station Head\] level access.

-

Cloning from a profile

- Cloning is as simple as pressing the CLONE option at the bottom of the desired profile.
- Per your company's EMPLOYEE PRIVACY RIGHTS agreement, the H-87 has been blocked from cloning crewmembers while they are still alive.
-
-

The provided CLONEPOD SYSTEM will produce the desired clone. Standard clone maturation times (With SPEEDCLONE technology) are roughly 90 seconds. - The cloning pod may be unlocked early with any \[Medical Researcher\] ID after initial maturation is complete.


- Please note that resulting clones may have a small DEVELOPMENTAL DEFECT as a result of genetic drift.
-

Profile Management

-

The H-87 (as well as your station's standard genetics machine) can accept STANDARD DATA DISKETTES. - These diskettes are used to transfer genetic information between machines and profiles. - A load/save dialog will become available in each profile if a disk is inserted.


- A good diskette is a great way to counter aforementioned genetic drift!
-
- This technology produced under license from Thinktronic Systems, LTD."} - -//SOME SCRAPS I GUESS -/* EMP grenade/spell effect - if(istype(A, /obj/machinery/clonepod)) - A:malfunction() -*/ - -#undef MINIMUM_HEAL_LEVEL -#undef BIOMASS_BASE_AMOUNT -#undef CLONE_INITIAL_DAMAGE -#undef BRAIN_INITIAL_DAMAGE +#undef VALID_REAGENTS +#undef VALID_BIOMASSABLES +#undef FORBIDDEN_INTERNAL_ORGANS +#undef UPGRADE_LOCKED_ORGANS +#undef FORBIDDEN_LIMBS +#undef ALLOWED_ROBOT_PARTS + +#undef BIOMASS_BASE_COST +#undef MEAT_BIOMASS_VALUE +#undef BIOMASS_NEW_LIMB_COST +#undef BIOMASS_NEW_ORGAN_COST +#undef BIOMASS_BURN_WOUND_COST +#undef BIOMASS_BRUTE_COST +#undef BIOMASS_BURN_COST +#undef BIOMASS_ORGAN_DAMAGE_COST +#undef SANGUINE_IB_COST +#undef OSSEOUS_BONE_COST diff --git a/code/game/machinery/clonescanner.dm b/code/game/machinery/clonescanner.dm new file mode 100644 index 000000000000..b644feb3182d --- /dev/null +++ b/code/game/machinery/clonescanner.dm @@ -0,0 +1,229 @@ +/// A limb that's not missing with no damages or flags. +#define HEALTHY_LIMB list(0, 0, 0, FALSE) +/// As above, but for organs. +#define HEALTHY_ORGAN list(0, 0, FALSE) + +/// A datum to store the information gained by scanning a patient OR the fixes to be made to their body. +/datum/cloning_data + /// The patient's name. + var/name + /// A reference to the patient's mind. + var/mindUID + + /// The patient's external organs (limbs) and their data, stored as an associated list of lists. + /// List format: limb = list(brute, burn, status, missing, name, max damage) + var/list/limbs = list() + + /// The patient's internal organs and their data, stored as an associated list of lists. + /// List format: organ = list(damage, status, missing, name, max damage, organ tag) + var/list/organs = list() + + /// The patient's DNA + var/datum/dna/genetic_info + +//this is mostly an example +/datum/cloning_data/healthy + + limbs = list( + "head" = HEALTHY_LIMB, + "torso" = HEALTHY_LIMB, + "groin" = HEALTHY_LIMB, + "r_arm" = HEALTHY_LIMB, + "r_hand" = HEALTHY_LIMB, + "l_arm" = HEALTHY_LIMB, + "l_hand" = HEALTHY_LIMB, + "r_leg" = HEALTHY_LIMB, + "r_foot" = HEALTHY_LIMB, + "l_leg" = HEALTHY_LIMB, + "l_foot" = HEALTHY_LIMB + ) + + organs = list( + "heart" = HEALTHY_ORGAN, + "lungs" = HEALTHY_ORGAN, + "liver" = HEALTHY_ORGAN, + "kidneys" = HEALTHY_ORGAN, + "brain" = HEALTHY_ORGAN, + "appendix" = HEALTHY_ORGAN, + "eyes" = HEALTHY_ORGAN + ) + +//The cloning scanner itself. +/obj/machinery/clonescanner + name = "cloning scanner" + desc = "An advanced machine that thoroughly scans the current state of a cadaver for use in cloning." + icon = 'icons/obj/cryogenic2.dmi' + icon_state = "scanner_open" + density = TRUE + anchored = TRUE + + /// The linked cloning console. + var/obj/machinery/computer/cloning/console + /// The tier of scan we can perform. Tier 2 parts and up can scan husks - or a tier 4 scanner and tier 1 laser. + var/scanning_tier + /// The scanner's occupant. + var/mob/living/carbon/human/occupant + /// The scanner's latest scan result + var/datum/cloning_data/last_scan + /// Whether or not we've tried to scan the current patient + var/has_scanned = FALSE + +/obj/machinery/clonescanner/Initialize(mapload) + . = ..() + + if(!console && mapload) //this could be varedited in in mapping, maybe? + console = pick(locate(/obj/machinery/computer/cloning, orange(5, src))) //yes, it's random, but there shouldn't be multiple consoles anyways + + component_parts = list() + component_parts += new /obj/item/circuitboard/clonescanner(null) + component_parts += new /obj/item/stock_parts/scanning_module(null) + component_parts += new /obj/item/stock_parts/micro_laser(null) + component_parts += new /obj/item/stack/sheet/glass(null) + component_parts += new /obj/item/stack/cable_coil(null, 1) + component_parts += new /obj/item/stack/cable_coil(null, 1) + update_icon() + RefreshParts() + +/obj/machinery/clonescanner/RefreshParts() + for(var/obj/item/stock_parts/SP in component_parts) + scanning_tier += SP.rating + +/obj/machinery/clonescanner/Destroy() + if(console) + console.scanner = null + return ..() + +/obj/machinery/clonescanner/MouseDrop_T(atom/movable/O, mob/user) + if(!(ishuman(user) || issilicon(user)) || user.incapacitated()) + return + if(!ishuman(O)) + return + var/mob/living/carbon/human/H = O + if(H.stat != DEAD) + to_chat(user, "You don't think it'd be wise to scan a living being.") + return TRUE + if(occupant) + to_chat(user, "[src] is already occupied!") + return TRUE + + to_chat(user, "You put [H] into the cloning scanner.") + insert_mob(H) + return TRUE + +/obj/machinery/clonescanner/AltClick(mob/user) + if(!occupant) + return + if(issilicon(user)) + remove_mob(occupant) + return + if(!Adjacent(user) || !ishuman(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) + return + remove_mob(occupant) + +/obj/machinery/clonescanner/relaymove(mob/user) + if(user.stat) + return + remove_mob(user) + +/obj/machinery/clonescanner/proc/try_scan(mob/living/carbon/human/scanned) + if(!scanned) + return + + occupant.notify_ghost_cloning() + + has_scanned = TRUE + + if(!scanned.dna || HAS_TRAIT(scanned, TRAIT_GENELESS)) + return SCANNER_MISC + if(HAS_TRAIT(scanned, TRAIT_BADDNA) && scanning_tier < 4) + return SCANNER_ABSORBED + if(HAS_TRAIT(scanned, TRAIT_HUSK) && scanning_tier < 4) + return SCANNER_HUSKED + if(NO_CLONESCAN in scanned.dna.species.species_traits) + return SCANNER_UNCLONEABLE_SPECIES + if(!scanned.ckey || !scanned.client || ischangeling(scanned)) + return SCANNER_NO_SOUL + if(scanned.suiciding || !scanned.get_int_organ(/obj/item/organ/internal/brain)) + return SCANNER_BRAIN_ISSUE + + return scan(scanned) + +/obj/machinery/clonescanner/proc/scan(mob/living/carbon/human/scanned) + var/datum/cloning_data/scan_result = new + + scan_result.name = scanned.dna.real_name + scan_result.mindUID = scanned.mind.UID() + scan_result.genetic_info = scanned.dna.Clone() + + for(var/limb in scanned.dna.species.has_limbs) + if(scanned.bodyparts_by_name[limb]) + var/obj/item/organ/external/active_limb = scanned.bodyparts_by_name[limb] + scan_result.limbs[limb] = list(active_limb.brute_dam, + active_limb.burn_dam, + active_limb.status, + FALSE, + active_limb.name, + active_limb.max_damage) + else + scan_result.limbs[limb] = list(0, 0, 0, TRUE, scanned.dna.species.has_limbs[limb]["descriptor"], 0) //no damage if it's missing! + + for(var/organ in scanned.dna.species.has_organ) + var/obj/item/organ/internal/active_organ = scanned.get_int_organ(scanned.dna.species.has_organ[organ]) //this is icky + if(!istype(active_organ)) + scan_result.organs[organ] = list(0, 0, TRUE, organ, 0, organ) + continue + + scan_result.organs[organ] = list(active_organ.damage, + active_organ.status, + FALSE, + active_organ.name, + active_organ.max_damage, + active_organ.organ_tag) + + last_scan = scan_result + return scan_result + +/obj/machinery/clonescanner/proc/insert_mob(mob/living/carbon/human/inserted) + if(!istype(inserted)) + return + inserted.forceMove(src) + occupant = inserted + update_icon(UPDATE_ICON_STATE) + +/obj/machinery/clonescanner/proc/remove_mob(mob/living/carbon/human/removed) + if(!istype(removed)) + return + removed.forceMove(get_turf(loc)) + occupant = null + update_scan_status() + update_icon(UPDATE_ICON_STATE) + +/obj/machinery/clonescanner/proc/update_scan_status() + last_scan = null + has_scanned = FALSE + +/obj/machinery/clonescanner/update_icon_state() + if(panel_open) + icon_state = "scanner" + (occupant ? "" : "_open") + "_maintenance" + return + if(stat & NOPOWER) + icon_state = "scanner" + return + if(!occupant) + icon_state = "scanner_open" + return + icon_state = "scanner_occupied" + return + + +/obj/machinery/clonescanner/multitool_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + if(!I.multitool_check_buffer(user)) + return + var/obj/item/multitool/M = I + M.set_multitool_buffer(user, src) + +#undef HEALTHY_LIMB +#undef HEALTHY_ORGAN diff --git a/code/game/machinery/computer/cloning.dm b/code/game/machinery/computer/cloning.dm index da24fb361686..668e88b84b24 100644 --- a/code/game/machinery/computer/cloning.dm +++ b/code/game/machinery/computer/cloning.dm @@ -1,5 +1,5 @@ -#define MENU_MAIN 1 -#define MENU_RECORDS 2 +#define TAB_MAIN 1 +#define TAB_DAMAGES_BREAKDOWN 2 /obj/machinery/computer/cloning name = "cloning console" @@ -7,116 +7,151 @@ icon_keyboard = "med_key" icon_screen = "dna" circuit = /obj/item/circuitboard/cloning - req_access = list(ACCESS_HEADS) //Only used for record deletion right now. - var/obj/machinery/dna_scannernew/scanner = null //Linked scanner. For scanning. - var/list/pods = null //Linked cloning pods. - var/list/temp = null - var/list/scantemp = null - var/menu = MENU_MAIN //Which menu screen to display - var/list/records = null - var/datum/dna2/record/active_record = null - var/loading = 0 // Nice loading text - var/autoprocess = 0 - var/obj/machinery/clonepod/selected_pod - // 0: Standard body scan - // 1: The "Best" scan available - var/scan_mode = 1 + req_access = list(ACCESS_MEDICAL) - light_color = LIGHT_COLOR_DARKBLUE + /// The currently-selected cloning pod. + var/obj/machinery/clonepod/selected_pod + /// A list of all linked cloning pods. + var/list/pods = list() + /// Our linked cloning scanner. + var/obj/machinery/clonescanner/scanner + /// Which tab we're currently on + var/tab = TAB_MAIN + /// What feedback to give for the most recent scan. + var/feedback + /// The desired outcome of the cloning process. + var/datum/cloning_data/desired_data + /// Whether the ID lock is on or off + var/locked = TRUE + + COOLDOWN_DECLARE(scancooldown) /obj/machinery/computer/cloning/Initialize(mapload) . = ..() - pods = list() - records = list() - set_scan_temp("Scanner ready.", "good") - updatemodules() + + if(length(pods) && scanner) + return + + if(!mapload) + return //cloning setups built by players need to be linked manually + + pods += locate(/obj/machinery/clonepod, orange(5, src)) + scanner = locate(/obj/machinery/clonescanner, orange(5, src)) + selected_pod = pick(pods) + + new /obj/item/book/manual/medical_cloning(get_turf(src)) //hopefully this stems the tide of mhelps during the TM... /obj/machinery/computer/cloning/Destroy() - releasecloner() + if(scanner) + scanner.console = null + if(pods) + for(var/obj/machinery/clonepod/P in pods) + P.console = null return ..() -/obj/machinery/computer/cloning/process() - if(!scanner || !pods.len || !autoprocess || stat & NOPOWER) +/obj/machinery/computer/cloning/examine(mob/user) + . = ..() + . += "[src] is currently [locked ? "locked" : "unlocked"], and can be [locked ? "unlocked" : "locked"] by swiping an ID with medical access on it." + +/obj/machinery/computer/cloning/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/card/id) || istype(I, /obj/item/pda)) + if(allowed(user)) + locked = !locked + to_chat(user, "Access restriction is now [locked ? "enabled" : "disabled"].") + else + to_chat(user, "Access denied.") + return + + if(!ismultitool(I)) + return ..() + + var/obj/item/multitool/M = I + if(!M.buffer) + to_chat(user, "[M]'[M.p_s()] buffer is empty!") return - if(scanner.occupant && can_autoprocess()) - scan_mob(scanner.occupant) + if(istype(M.buffer, /obj/machinery/clonepod)) + var/obj/machinery/clonepod/buffer_pod = M.buffer + if(buffer_pod.console == src) + to_chat(user, "[M.buffer] is already linked!") + return - if(!LAZYLEN(records)) + pods += M.buffer + buffer_pod.console = src + to_chat(user, "[M.buffer] was successfully added to the cloning pod array.") + if(!selected_pod) + selected_pod = buffer_pod return - for(var/obj/machinery/clonepod/pod in pods) - if(!(pod.occupant || pod.mess) && (pod.efficiency > 5)) - for(var/datum/dna2/record/R in records) - if(!(pod.occupant || pod.mess)) - if(pod.growclone(R)) - records.Remove(R) - -/obj/machinery/computer/cloning/proc/updatemodules() - src.scanner = findscanner() - releasecloner() - findcloner() - if(!selected_pod && pods.len) - selected_pod = pods[1] - -/obj/machinery/computer/cloning/proc/findscanner() - //Try to find scanner on adjacent tiles first - for(var/obj/machinery/dna_scannernew/scanner in orange(1, src)) - return scanner - - //Then look for a free one in the area - for(var/obj/machinery/dna_scannernew/S in get_area(src)) - return S - - return FALSE - -/obj/machinery/computer/cloning/proc/releasecloner() - for(var/obj/machinery/clonepod/P in pods) - P.connected = null - P.name = initial(P.name) - pods.Cut() - -/obj/machinery/computer/cloning/proc/findcloner() - var/num = 1 - for(var/obj/machinery/clonepod/P in get_area(src)) - if(!P.connected) - pods += P - P.connected = src - P.name = "[initial(P.name)] #[num++]" - -/obj/machinery/computer/cloning/attackby(obj/item/W as obj, mob/user as mob, params) - if(istype(W, /obj/item/multitool)) - var/obj/item/multitool/M = W - if(M.buffer && istype(M.buffer, /obj/machinery/clonepod)) - var/obj/machinery/clonepod/P = M.buffer - if(P && !(P in pods)) - pods += P - P.connected = src - P.name = "[initial(P.name)] #[pods.len]" - to_chat(user, "You connect [P] to [src].") - else - return ..() + if(istype(M.buffer, /obj/machinery/clonescanner)) + var/obj/machinery/clonescanner/buffer_scanner = M.buffer + if(scanner) + to_chat(user, "There's already a linked scanner!") + return + + scanner = buffer_scanner + buffer_scanner.console = src + to_chat(user, "[M.buffer] was successfully linked.") + return + to_chat(user, "[M.buffer] cannot be linked to [src].") + return -/obj/machinery/computer/cloning/attack_ai(mob/user as mob) +/obj/machinery/computer/cloning/attack_ai(mob/user) return attack_hand(user) -/obj/machinery/computer/cloning/attack_hand(mob/user as mob) +/obj/machinery/computer/cloning/attack_hand(mob/user) + . = ..() add_fingerprint(user) if(stat & (BROKEN|NOPOWER)) return - updatemodules() ui_interact(user) -/obj/machinery/computer/cloning/ui_state(mob/user) - return GLOB.default_state +/obj/machinery/computer/cloning/emag_act(mob/user) + . = ..() + if(!emagged) + emagged = TRUE + to_chat(user, "You short out the ID scanner on [src].") + else + to_chat(user, "[src]'s ID scanner is already broken!") + + return TRUE + +/obj/machinery/computer/cloning/proc/generate_healthy_data(datum/cloning_data/patient_data) + var/datum/cloning_data/desired_data = new + + for(var/limb in patient_data.limbs) + desired_data.limbs[limb] = list(0, + 0, + 0, + FALSE, + patient_data.limbs[limb][5], + patient_data.limbs[limb][6]) + + for(var/organ in patient_data.organs) + desired_data.organs[organ] = list(0, + 0, + FALSE, + patient_data.organs[organ][4], + patient_data.organs[organ][5]) + + return desired_data /obj/machinery/computer/cloning/ui_interact(mob/user, datum/tgui/ui = null) if(stat & (NOPOWER|BROKEN)) return + if(!allowed(user) && locked && !isobserver(user)) + to_chat(user, "Access denied.") + if(ui) + ui.close() + return + + var/datum/asset/simple/cloning/assets = get_asset_datum(/datum/asset/simple/cloning) + assets.send(user) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) ui = new(user, src, "CloningConsole", "Cloning Console") @@ -128,60 +163,73 @@ ) /obj/machinery/computer/cloning/ui_data(mob/user) - var/data[0] - data["menu"] = src.menu - data["scanner"] = sanitize("[src.scanner]") + var/list/data = list() - var/canpodautoprocess = 0 - if(pods.len) - data["numberofpods"] = src.pods.len + data["tab"] = tab - var/list/tempods[0] - for(var/obj/machinery/clonepod/pod in pods) - if(pod.efficiency > 5) - canpodautoprocess = 1 - - var/status = "idle" - if(pod.mess) - status = "mess" - else if(pod.occupant && !(pod.stat & NOPOWER)) - status = "cloning" - tempods.Add(list(list( - "pod" = "\ref[pod]", - "name" = sanitize(capitalize(pod.name)), - "biomass" = pod.biomass, - "status" = status, - "progress" = (pod.occupant && pod.occupant.stat != DEAD) ? pod.get_completion() : 0 - ))) - data["pods"] = tempods - - data["loading"] = loading - data["autoprocess"] = autoprocess - data["can_brainscan"] = can_brainscan() // You'll need tier 4s for this - data["scan_mode"] = scan_mode - - if(scanner && pods.len && ((scanner.scan_level > 2) || canpodautoprocess)) - data["autoallowed"] = 1 + if(scanner) + data["has_scanner"] = TRUE + else + data["has_scanner"] = FALSE + + if(scanner) + data["has_scanned"] = scanner.has_scanned else - data["autoallowed"] = 0 - if(src.scanner) - data["occupant"] = src.scanner.occupant - data["locked"] = src.scanner.locked - data["temp"] = temp - data["scantemp"] = scantemp - data["selected_pod"] = "\ref[selected_pod]" - var/list/temprecords[0] - for(var/datum/dna2/record/R in records) - var/tempRealName = R.dna.real_name - temprecords.Add(list(list("record" = "\ref[R]", "realname" = sanitize(tempRealName)))) - data["records"] = temprecords - - if(selected_pod && (selected_pod in pods) && selected_pod.biomass >= CLONER_BIOMASS_REQUIRED) - data["podready"] = 1 + data["has_scanned"] = FALSE + + if(scanner?.last_scan) + data["patient_limb_data"] = scanner.last_scan.limbs + var/list/allLimbs = list() + for(var/limb in scanner.last_scan.limbs) + allLimbs += limb + data["limb_list"] = allLimbs + + data["patient_organ_data"] = scanner.last_scan.organs + var/list/allOrgans = list() + for(var/organ in scanner.last_scan.organs) + allOrgans += organ + data["organ_list"] = allOrgans + + if(desired_data) + data["desired_limb_data"] = desired_data.limbs + data["desired_organ_data"] = desired_data.organs + + data["feedback"] = feedback + + if(feedback && feedback["color"] == "good") + data["scan_successful"] = TRUE else - data["podready"] = 0 + data["scan_successful"] = FALSE - data["modal"] = ui_modal_data(src) + if(scanner?.occupant) + data["scanner_has_patient"] = TRUE + else + data["scanner_has_patient"] = FALSE + + var/list/pod_data = list() + if(length(pods)) + for(var/obj/machinery/clonepod/pod in pods) + pod_data += list(list("uid" = pod.UID(), + "cloning" = pod.currently_cloning, + "clone_progress" = pod.clone_progress, + "biomass" = pod.biomass, + "biomass_storage_capacity" = pod.biomass_storage_capacity, + "sanguine_reagent" = pod.reagents.get_reagent_amount("sanguine_reagent"), + "osseous_reagent" = pod.reagents.get_reagent_amount("osseous_reagent"))) + + if(selected_pod) + data["selected_pod_data"] = list("biomass" = selected_pod.biomass, + "biomass_storage_capacity" = selected_pod.biomass_storage_capacity, + "sanguine_reagent" = selected_pod.reagents.get_reagent_amount("sanguine_reagent"), + "osseous_reagent" = selected_pod.reagents.get_reagent_amount("osseous_reagent"), + "max_reagent_capacity" = selected_pod.reagents.maximum_volume) + data["selected_pod_UID"] = selected_pod.UID() + if(scanner?.last_scan && desired_data) + var/list/costs = selected_pod.get_cloning_cost(scanner.last_scan, desired_data) + data["cloning_cost"] = costs + + data["pods"] = pod_data + data["pod_amount"] = length(pods) return data @@ -191,273 +239,119 @@ if(stat & (NOPOWER|BROKEN)) return - . = TRUE - switch(ui_modal_act(src, action, params)) - if(UI_MODAL_ANSWER) - if(params["id"] == "del_rec" && text2num(params["answer"]) && active_record) - if(!allowed(usr)) - set_temp("Access denied.", "danger") - return - - records.Remove(active_record) - qdel(active_record) - set_temp("Record deleted.", "success") - menu = MENU_RECORDS - return + var/datum/cloning_data/patient_data = scanner?.last_scan //For readability, mostly switch(action) + if("menu") + switch(text2num(params["tab"])) + if(TAB_MAIN) + tab = TAB_MAIN + scanner?.update_scan_status() + return TRUE + if(TAB_DAMAGES_BREAKDOWN) + tab = TAB_DAMAGES_BREAKDOWN + return TRUE + if("select_pod") + selected_pod = locateUID(params["uid"]) + return TRUE + if("clone") + var/cost = selected_pod.get_cloning_cost(scanner.last_scan, desired_data) + if(selected_pod.biomass < cost[BIOMASS_COST] || (selected_pod.reagents.get_reagent_amount("sanguine_reagent") < cost[SANGUINE_COST]) || selected_pod.reagents.get_reagent_amount("osseous_reagent") < cost[OSSEOUS_COST]) + feedback = list("text" = "The cloning operation is too expensive!", "color" = "bad") + else + selected_pod.start_cloning(scanner.last_scan, desired_data) + scanner?.update_scan_status() + feedback = list("text" = "Beginning cloning operation...", "color" = "good") + return TRUE if("scan") - if(!scanner || !scanner.occupant || loading) - return - set_scan_temp("Scanner ready.", "good") - loading = TRUE + if(!COOLDOWN_FINISHED(src, scancooldown)) + feedback = list("text" = "The scanning array is still calibrating! Please wait...", "color" = "average") + return TRUE - spawn(20) - if(can_brainscan() && scan_mode) - scan_mob(scanner.occupant, scan_brain = TRUE) - else - scan_mob(scanner.occupant) - loading = FALSE - SStgui.update_uis(src) - if("autoprocess") - autoprocess = text2num(params["on"]) > 0 - if("lock") - if(isnull(scanner) || !scanner.occupant) //No locking an open scanner. - return - scanner.locked = !scanner.locked - if("view_rec") - var/ref = params["ref"] - if(!length(ref)) - return - active_record = locate(ref) - if(istype(active_record)) - if(isnull(active_record.ckey)) - qdel(active_record) - set_temp("Error: Record corrupt.", "danger") - else - var/obj/item/bio_chip/health/H = null - if(active_record.implant) - H = locate(active_record.implant) - var/list/payload = list( - activerecord = "\ref[active_record]", - health = (H && istype(H)) ? H.sensehealth() : "", - realname = sanitize(active_record.dna.real_name), - unidentity = active_record.dna.uni_identity, - strucenzymes = active_record.dna.struc_enzymes, - ) - ui_modal_message(src, action, "", null, payload) - else - active_record = null - set_temp("Error: Record missing.", "danger") - if("del_rec") - if(!active_record) - return - ui_modal_boolean(src, action, "Please confirm that you want to delete the record:", yes_text = "Delete", no_text = "Cancel") - if("refresh") - SStgui.update_uis(src) - if("selectpod") - var/ref = params["ref"] - if(!length(ref)) - return - var/obj/machinery/clonepod/selected = locate(ref) - if(istype(selected) && (selected in pods)) - selected_pod = selected - if("clone") - var/ref = params["ref"] - if(!length(ref)) + if(!scanner.occupant) return - var/datum/dna2/record/C = locate(ref) - //Look for that player! They better be dead! - if(istype(C)) - ui_modal_clear(src) - //Can't clone without someone to clone. Or a pod. Or if the pod is busy. Or full of gibs. - if(!length(pods)) - set_temp("Error: No cloning pod detected.", "danger") + + COOLDOWN_START(src, scancooldown, 5 SECONDS) + var/scanner_result = scanner.try_scan(scanner.occupant) + switch(scanner_result) + if(SCANNER_MISC) + feedback = list("text" = "Unable to analyze patient's genetic sequence.", "color" = "bad") + if(SCANNER_UNCLONEABLE_SPECIES) + feedback = list("text" = "[scanner.occupant.dna.species.name_plural] cannot be scanned.", "color" = "bad") + if(SCANNER_HUSKED) + feedback = list("text" = "The patient is husked.", "color" = "bad") + if(SCANNER_ABSORBED) + feedback = list("text" = "The patient cannot be scanned due to a lack of biofluids.", "color" = "bad") + if(SCANNER_NO_SOUL) + feedback = list("text" = "Failed to sequence the patient's brain. Further attempts may succeed.", "color" = "average") + if(SCANNER_BRAIN_ISSUE) + feedback = list("text" = "The patient's brain is inactive or missing.", "color" = "bad") else - var/obj/machinery/clonepod/pod = selected_pod - var/cloneresult - if(!selected_pod) - set_temp("Error: No cloning pod selected.", "danger") - else if(pod.occupant) - set_temp("Error: The cloning pod is currently occupied.", "danger") - else if(pod.biomass < CLONER_BIOMASS_REQUIRED) - set_temp("Error: Not enough biomass.", "danger") - else if(pod.mess) - set_temp("Error: The cloning pod is malfunctioning.", "danger") - else if(!GLOB.configuration.general.enable_cloning) - set_temp("Error: Unable to initiate cloning cycle.", "danger") + var/datum/cloning_data/scan = scanner_result + if((scan.mindUID == patient_data?.mindUID) || (scan.mindUID == selected_pod?.patient_data?.mindUID)) + feedback = list("text" = "Patient has already been scanned.", "color" = "average") + return TRUE + feedback = list("text" = "Successfully scanned the patient.", "color" = "good") + desired_data = generate_healthy_data(scan) + return TRUE + if("fix_all") + desired_data = generate_healthy_data(scanner.last_scan) + return TRUE + if("fix_none") + desired_data = scanner.last_scan + return TRUE + if("toggle_limb_repair") + switch(params["type"]) + if("replace") + if(desired_data.limbs[params["limb"]][4]) + desired_data.limbs[params["limb"]][4] = FALSE else - cloneresult = pod.growclone(C) - if(cloneresult) - set_temp("Initiating cloning cycle...", "success") - records.Remove(C) - qdel(C) - menu = MENU_MAIN - else - set_temp("Error: Initialisation failure.", "danger") - else - set_temp("Error: Data corruption.", "danger") - if("menu") - menu = clamp(text2num(params["num"]), MENU_MAIN, MENU_RECORDS) - if("toggle_mode") - if(loading) - return - if(can_brainscan()) - scan_mode = !scan_mode - else - scan_mode = FALSE + desired_data.limbs[params["limb"]][4] = TRUE + if("damage") + if(desired_data.limbs[params["limb"]][1] || desired_data.limbs[params["limb"]][2]) + desired_data.limbs[params["limb"]][1] = 0 + desired_data.limbs[params["limb"]][2] = 0 + else + desired_data.limbs[params["limb"]][1] = patient_data.limbs[params["limb"]][1] + desired_data.limbs[params["limb"]][2] = patient_data.limbs[params["limb"]][2] + if("bone") + if(desired_data.limbs[params["limb"]][3] & ORGAN_BROKEN) + desired_data.limbs[params["limb"]][3] &= ~ORGAN_BROKEN + else + desired_data.limbs[params["limb"]][3] |= ORGAN_BROKEN + if("ib") + if(desired_data.limbs[params["limb"]][3] & ORGAN_INT_BLEEDING) + desired_data.limbs[params["limb"]][3] &= ~ORGAN_INT_BLEEDING + else + desired_data.limbs[params["limb"]][3] |= ORGAN_INT_BLEEDING + if("critburn") + if(desired_data.limbs[params["limb"]][3] & ORGAN_BURNT) + desired_data.limbs[params["limb"]][3] &= ~ORGAN_BURNT + else + desired_data.limbs[params["limb"]][3] |= ORGAN_BURNT + return TRUE + if("toggle_organ_repair") + switch(params["type"]) + if("replace") + if(desired_data.organs[params["organ"]][3] || desired_data.organs[params["organ"]][2]) + desired_data.organs[params["organ"]][3] = FALSE + desired_data.organs[params["organ"]][2] = 0 + else + desired_data.organs[params["organ"]][3] = patient_data.organs[params["organ"]][3] + desired_data.organs[params["organ"]][2] = patient_data.organs[params["organ"]][2] + if("damage") + if(desired_data.organs[params["organ"]][1]) + desired_data.organs[params["organ"]][1] = 0 + else + desired_data.organs[params["organ"]][1] = patient_data.organs[params["organ"]][1] + return TRUE if("eject") - if(usr.incapacitated() || !scanner || loading) - return - scanner.eject_occupant(usr) - scanner.add_fingerprint(usr) - if("cleartemp") - temp = null - else - return FALSE + if(scanner?.occupant) + scanner.remove_mob(scanner.occupant) + return TRUE - src.add_fingerprint(usr) -/obj/machinery/computer/cloning/proc/scan_mob(mob/living/carbon/human/subject as mob, scan_brain = 0) - if(stat & NOPOWER) - return - if(scanner.stat & (NOPOWER|BROKEN)) - return - if(scan_brain && !can_brainscan()) - return - if(isnull(subject) || (!(ishuman(subject))) || (!subject.dna)) - if(isalien(subject)) - set_scan_temp("Safety interlocks engaged. Nanotrasen Directive 7b forbids the cloning of biohazardous alien species.", "bad") - SStgui.update_uis(src) - return - // can add more conditions for specific non-human messages here - else - set_scan_temp("Subject species is not clonable.", "bad") - SStgui.update_uis(src) - return - if(NO_CLONESCAN in subject.dna.species.species_traits) - set_scan_temp("[subject.dna.species.name_plural] are not clonable. Alternative revival methods recommended.", "bad") - SStgui.update_uis(src) - return - if(subject.get_int_organ(/obj/item/organ/internal/brain)) - var/obj/item/organ/internal/brain/Brn = subject.get_int_organ(/obj/item/organ/internal/brain) - if(istype(Brn)) - if(Brn.dna.species.name == "Machine") - set_scan_temp("No organic tissue detected within subject. Alternative revival methods recommended.", "bad") - SStgui.update_uis(src) - return - if(NO_CLONESCAN in Brn.dna.species.species_traits) - set_scan_temp("[Brn.dna.species.name_plural] are not clonable. Alternative revival methods recommended.", "bad") - SStgui.update_uis(src) - return - if(!subject.get_int_organ(/obj/item/organ/internal/brain)) - set_scan_temp("No brain detected in subject.", "bad") - SStgui.update_uis(src) - return - if(subject.suiciding) - set_scan_temp("Subject has committed suicide and is not clonable.", "bad") - SStgui.update_uis(src) - return - if(HAS_TRAIT(subject, TRAIT_BADDNA) && src.scanner.scan_level < 2) - set_scan_temp("Insufficient level of biofluids detected within subject. Scanner upgrades may be required to improve scan capabilities.", "bad") - SStgui.update_uis(src) - return - if(HAS_TRAIT(subject, TRAIT_HUSK) && src.scanner.scan_level < 2) - set_scan_temp("Subject is husked. Treat condition or upgrade scanning module to proceed with scan.", "bad") - SStgui.update_uis(src) - return - if((!subject.ckey) || (!subject.client)) - set_scan_temp("Subject's brain is not responding. Further attempts after a short delay may succeed.", "bad") - SStgui.update_uis(src) - return - if(!isnull(find_record(subject.ckey))) - set_scan_temp("Subject already in database.") - SStgui.update_uis(src) - return - if(subject.stat != DEAD) - set_scan_temp("Subject is not dead.", "bad") - SStgui.update_uis(src) - return + add_fingerprint(usr) - for(var/obj/machinery/clonepod/pod in pods) - if(pod.occupant && pod.clonemind == subject.mind) - set_scan_temp("Subject already getting cloned.") - SStgui.update_uis(src) - return - - subject.dna.check_integrity() - - var/datum/dna2/record/R = new /datum/dna2/record() - R.ckey = subject.ckey - var/extra_info = "" - if(scan_brain) - var/obj/item/organ/B = subject.get_int_organ(/obj/item/organ/internal/brain) - B.dna.check_integrity() - R.dna=B.dna.Clone() - if(NO_CLONESCAN in R.dna.species.species_traits) - extra_info = "Proper genetic interface not found, defaulting to genetic data of the body." - R.dna.species = new subject.dna.species.type - R.id= copytext(md5(B.dna.real_name), 2, 6) - R.name=B.dna.real_name - else - R.dna=subject.dna.Clone() - R.id= copytext(md5(subject.real_name), 2, 6) - R.name=R.dna.real_name - - R.types=DNA2_BUF_UI|DNA2_BUF_UE|DNA2_BUF_SE - R.languages=subject.languages - //Add an implant if needed - var/obj/item/bio_chip/health/imp = locate(/obj/item/bio_chip/health, subject) - if(!imp) - imp = new /obj/item/bio_chip/health(subject) - imp.implant(subject) - R.implant = "\ref[imp]" - - if(!isnull(subject.mind)) //Save that mind so traitors can continue traitoring after cloning. - R.mind = "\ref[subject.mind]" - - src.records += R - set_scan_temp("Subject successfully scanned. [extra_info]", "good") - SStgui.update_uis(src) - -//Find a specific record by key. -/obj/machinery/computer/cloning/proc/find_record(find_key) - var/selected_record = null - for(var/datum/dna2/record/R in src.records) - if(R.ckey == find_key) - selected_record = R - break - return selected_record - -/obj/machinery/computer/cloning/proc/can_autoprocess() - return (scanner && scanner.scan_level > 2) - -/obj/machinery/computer/cloning/proc/can_brainscan() - return (scanner && scanner.scan_level > 3) - -/** - * Sets a temporary message to display to the user - * - * Arguments: - * * text - Text to display, null/empty to clear the message from the UI - * * style - The style of the message: (color name), info, success, warning, danger - */ -/obj/machinery/computer/cloning/proc/set_temp(text = "", style = "info", update_now = FALSE) - temp = list(text = text, style = style) - if(update_now) - SStgui.update_uis(src) - -/** - * Sets a temporary scan message to display to the user - * - * Arguments: - * * text - Text to display, null/empty to clear the message from the UI - * * color - The color of the message: (color name) - */ -/obj/machinery/computer/cloning/proc/set_scan_temp(text = "", color = "", update_now = FALSE) - scantemp = list(text = text, color = color) - if(update_now) - SStgui.update_uis(src) - -#undef MENU_MAIN -#undef MENU_RECORDS +#undef TAB_MAIN +#undef TAB_DAMAGES_BREAKDOWN diff --git a/code/game/machinery/constructable_frame.dm b/code/game/machinery/constructable_frame.dm index e4d89c6f7383..e3c9877aa5c3 100644 --- a/code/game/machinery/constructable_frame.dm +++ b/code/game/machinery/constructable_frame.dm @@ -824,19 +824,19 @@ to destroy them and players will be able to make replacements. origin_tech = "programming=2;biotech=2" req_components = list( /obj/item/stack/cable_coil = 2, - /obj/item/stock_parts/scanning_module = 2, + /obj/item/stock_parts/scanning_module = 1, + /obj/item/stock_parts/matter_bin = 1, /obj/item/stock_parts/manipulator = 2, /obj/item/stack/sheet/glass = 1) /obj/item/circuitboard/clonescanner board_name = "Cloning Scanner" icon_state = "medical" - build_path = /obj/machinery/dna_scannernew + build_path = /obj/machinery/clonescanner board_type = "machine" origin_tech = "programming=2;biotech=2" req_components = list( /obj/item/stock_parts/scanning_module = 1, - /obj/item/stock_parts/manipulator = 1, /obj/item/stock_parts/micro_laser = 1, /obj/item/stack/sheet/glass = 1, /obj/item/stack/cable_coil = 2,) diff --git a/code/game/machinery/cryopod.dm b/code/game/machinery/cryopod.dm index 02bc0b34b199..bd691273d8a0 100644 --- a/code/game/machinery/cryopod.dm +++ b/code/game/machinery/cryopod.dm @@ -329,11 +329,6 @@ I.forceMove(src) handle_contents(I) - for(var/obj/machinery/computer/cloning/cloner in GLOB.machines) - for(var/datum/dna2/record/R in cloner.records) - if(occupant.mind == locate(R.mind)) - cloner.records.Remove(R) - //Delete all items not on the preservation list. var/list/items = contents items -= occupant // Don't delete the occupant diff --git a/code/game/objects/items/weapons/batons.dm b/code/game/objects/items/weapons/batons.dm index 08e01fc96b6d..9d777f125a0e 100644 --- a/code/game/objects/items/weapons/batons.dm +++ b/code/game/objects/items/weapons/batons.dm @@ -143,7 +143,6 @@ desc = "A cane with special engraving on it. It seems well suited for fending off assailants..." icon_state = "cane_nt" item_state = "cane_nt" - needs_permit = FALSE /obj/item/melee/classic_baton/ntcane/get_crutch_efficiency() return 2 @@ -158,7 +157,6 @@ item_state = null slot_flags = SLOT_FLAG_BELT w_class = WEIGHT_CLASS_SMALL - needs_permit = FALSE on = FALSE /// Force when concealed var/force_off = 0 diff --git a/code/game/objects/items/weapons/disks.dm b/code/game/objects/items/weapons/disks.dm index 50f6c04919cc..b982a0d44e59 100644 --- a/code/game/objects/items/weapons/disks.dm +++ b/code/game/objects/items/weapons/disks.dm @@ -5,3 +5,70 @@ icon_state = "datadisk0" drop_sound = 'sound/items/handling/disk_drop.ogg' pickup_sound = 'sound/items/handling/disk_pickup.ogg' + +/obj/item/disk/data + name = "Cloning Data Disk" + icon_state = "datadisk0" //Gosh I hope syndies don't mistake them for the nuke disk. + var/datum/dna2/record/buf = null + var/read_only = FALSE //Well,it's still a floppy disk + +/obj/item/disk/data/proc/initialize_data() + buf = new + buf.dna = new + +/obj/item/disk/data/Destroy() + QDEL_NULL(buf) + return ..() + +/obj/item/disk/data/demo + name = "data disk - 'God Emperor of Mankind'" + read_only = TRUE + +/obj/item/disk/data/demo/New() + . = ..() + initialize_data() + buf.types = DNA2_BUF_UE|DNA2_BUF_UI + buf.dna.real_name = "God Emperor of Mankind" + buf.dna.unique_enzymes = md5(buf.dna.real_name) + buf.dna.UI = list(0x066,0x000,0x033,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0xAF0,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x033,0x066,0x0FF,0x4DB,0x002,0x690,0x000,0x000,0x000,0x328,0x045,0x5FC,0x053,0x035,0x035,0x035) + if(length(buf.dna.UI) != DNA_UI_LENGTH) //If there's a disparity b/w the dna UI string lengths, 0-fill the extra blocks in this UI. + for(var/i in length(buf.dna.UI) to DNA_UI_LENGTH) + buf.dna.UI += 0x000 + buf.dna.ResetSE() + buf.dna.UpdateUI() + +/obj/item/disk/data/monkey + name = "data disk - 'Mr. Muggles'" + read_only = 1 + +/obj/item/disk/data/monkey/New() + . = ..() + initialize_data() + buf.types = DNA2_BUF_SE + var/list/new_SE = list(0x098,0x3E8,0x403,0x44C,0x39F,0x4B0,0x59D,0x514,0x5FC,0x578,0x5DC,0x640,0x6A4) + for(var/i = length(new_SE); i <= DNA_SE_LENGTH; i++) + new_SE += rand(1, 1024) + buf.dna.SE = new_SE + buf.dna.SetSEValueRange(GLOB.monkeyblock, 0xDAC, 0xFFF) + +//Disk stuff. +/obj/item/disk/data/New() + . = ..() + var/diskcolor = pick(0, 1, 2) + icon_state = "datadisk[diskcolor]" + +/obj/item/disk/data/attack_self(mob/user) + read_only = !read_only + to_chat(user, "You flip the write-protect tab to [read_only ? "protected" : "unprotected"].") + +/obj/item/disk/data/examine(mob/user) + . = ..() + . += "The write-protect tab is set to [read_only ? "protected" : "unprotected"]." + +/obj/item/storage/box/disks + name = "Diskette Box" + icon_state = "disk_kit" + +/obj/item/storage/box/disks/populate_contents() + for(var/i in 1 to 7) + new /obj/item/disk/data(src) diff --git a/code/game/objects/items/weapons/manuals.dm b/code/game/objects/items/weapons/manuals.dm index 3e15b3b06e92..d14a4c0e2b5f 100644 --- a/code/game/objects/items/weapons/manuals.dm +++ b/code/game/objects/items/weapons/manuals.dm @@ -348,11 +348,11 @@ "}) /obj/item/book/manual/medical_cloning - name = "Cloning techniques of the 26th century" - desc = "A clinical explanation on how to resurrect your patients." - icon_state ="bookCloning" - author = "Medical Journal, volume 3" - title = "Cloning techniques of the 26th century" + name = "Introduction to Cloning" + desc = "A guide covering the basics of cloning." + icon_state = "bookCloning" + author = "Bioarchitect for the Pillars of Creation" //this is a valid nian name, right? + title = "Introduction to Cloning" pages = list({" @@ -366,60 +366,59 @@ -

How to Clone People

- So thereÂ’s 50 dead people lying on the floor, chairs are spinning like no tomorrow and you havenÂ’t the foggiest idea of what to do? Not to worry! This guide is intended to teach you how to clone people and how to do it right, in a simple step-by-step process! If at any point of the guide you have a mental meltdown, genetics probably isnÂ’t for you and you should get a job-change as soon as possible before youÂ’re sued for malpractice. +

How to Clone People

+ Picture the scene: there's five corpses surrounding you, the Chief Medical Officer is yelling something \ + about terror spiders into their radio, and you only graduated medical school last week. What are you supposed to do?
\ + Well, Thinktronics Corporation is proud to present you the new and improved TTC-5601 cloning device - capable of \ + economically resuscitating even the most damaged of bodies. What follows is a guide on how to use this newer model \ + of cloning machine and where it differs from previous models.
    -
  1. Acquire body
  2. -
  3. Strip body
  4. -
  5. Put body in cloning machine
  6. -
  7. Scan body
  8. -
  9. Clone body
  10. -
  11. Get clean Structurel Enzymes for the body
  12. -
  13. Put body in morgue
  14. -
  15. Await cloned body
  16. -
  17. Use the clean SW injector
  18. -
  19. Give person clothes back
  20. -
  21. Send person on their way
  22. +
  23. Preparation
  24. +
  25. Load into Cloning Scanner
  26. +
  27. Scan Patient
  28. +
  29. Configure Device
  30. +
  31. Clone
  32. +
  33. Finish Procedure
-

Step 1: Acquire body

- This is pretty much vital for the process because without a body, you cannot clone it. Usually, bodies will be brought to you, so you do not need to worry so much about this step. If you already have a body, great! Move on to the next step. - -

Step 2: Strip body

- The cloning machine does not like abiotic items. What this means is you canÂ’t clone anyone if theyÂ’re wearing clothes, so take all of it off. If itÂ’s just one person, itÂ’s courteous to put their possessions in the closet. If you have about seven people awaiting cloning, just leave the piles where they are, but donÂ’t mix them around and for GodÂ’s sake donÂ’t let people in to steal them. - -

Step 3: Put body in cloning machine

- Grab the body and then put it inside the DNA modifier. If you cannot do this, then you messed up at Step 2. Go back and check you took EVERYTHING off - a commonly missed item is their headset. - -

Step 4: Scan body

- Go onto the computer and scan the body by pressing ‘Scan - ’. If you’re successful, they will be added to the records (note that this can be done at any time, even with living people, so that they can be cloned without a body in the event that they are lying dead on port solars and didn‘t turn on their suit sensors)! If not, and it says “Error: Mental interface failure.”, then they have left their bodily confines and are one with the spirits. If this happens, just shout at them to get back in their body, click ‘Refresh‘ and try scanning them again. If there’s no success, threaten them with gibbing. Still no success? Skip over to Step 7 and don‘t continue after it, as you have an unresponsive body and it cannot be cloned. If you got “Error: Unable to locate valid genetic data.“, you are trying to clone a monkey - start over. - -

Step 5: Clone body

- Now that the body has a record, click ’View Records’, click the subject’s name, and then click ‘Clone’ to start the cloning process. Congratulations! You’re halfway there. Remember not to ‘Eject’ the cloning pod as this will kill the developing clone and you’ll have to start the process again. - -

Step 6: Get clean SEs for body

- Cloning is a finicky and unreliable process. Whilst it will most certainly bring someone back from the dead, they can have any number of nasty disabilities given to them during the cloning process! For this reason, you need to prepare a clean, defect-free Structural Enzyme (SE) injection for when they’re done. If you’re a competent Geneticist, you will already have one ready on your working computer. If, for any reason, you do not, then eject the body from the DNA modifier (NOT THE CLONING POD) and take it next door to the Genetics research room. Put the body in one of those DNA modifiers and then go onto the console. Go into View/Edit/Transfer Buffer, find an open slot and click “SE“ to save it. Then click ‘Injector’ to get the SEs in syringe form. Put this in your pocket or something for when the body is done. - -

Step 7: Put body in morgue

- Now that the cloning process has been initiated and you have some clean Structural Enzymes, you no longer need the body! Drag it to the morgue and tell the Chef over the radio that they have some fresh meat waiting for them in there. To put a body in a morgue bed, simply open the tray, grab the body, put it on the open tray, then close the tray again. Use one of the nearby pens to label the bed “CHEF MEAT” in order to avoid confusion. - -

Step 8: Await cloned body

- Now go back to the lab and wait for your patient to be cloned. It wonÂ’t be long now, I promise. - -

Step 9: Use the clean SE injector on person

- Has your body been cloned yet? Great! As soon as the guy pops out, grab your injector and jab it in them. Once youÂ’ve injected them, they now have clean Structural Enzymes and their defects, if any, will disappear in a short while. - -

Step 10: Give person clothes back

- Obviously the person will be naked after they have been cloned. Provided you werenÂ’t an irresponsible little shit, you should have protected their possessions from thieves and should be able to give them back to the patient. No matter how cruel you are, itÂ’s simply against protocol to force your patients to walk outside naked. - -

Step 11: Send person on their way

- Give the patient one last check-over - make sure they donÂ’t still have any defects and that they have all their possessions. Ask them how they died, if they know, so that you can report any foul play over the radio. Once youÂ’re done, your patient is ready to go back to work! Chances are they do not have Medbay access, so you should let them out of Genetics and the Medbay main entrance. - -

If youÂ’ve gotten this far, congratulations! You have mastered the art of cloning. Now, the real problem is how to resurrect yourself after that traitor had his way with you for cloning his target. - - - +

Step 1: Preparation

+ Your patient must be dead to clone them (as of the Cloning Regulatory Act of 2533). Therefore, make sure that \ + they are deceased before proceeding, and ideally try to revive them via defibrillation! If that fails, however, \ + you should strip them of their equipment and check to make sure the cloning pod is loaded with, at least, 250 \ + biomass.
+ If your patient has a lot of damage, it'll take a lot of biomass to clone them! If you do not have ample biomass \ + or simply want to conserve it, try to tend to the cadaver's wounds before proceeding. In addition, fixing broken \ + bones and internal bleeds via cloning will consume Osseous Reagent and Sanguine Reagent respectively - these are \ + both much harder to replenish than biomass, so consider being polite to your chemists and fixing these via \ + surgery instead. + +

Step 2: Load into Cloning Scanner

+ After stripping your patient, load them into the cloning device's scanning machine, as you would with any other \ + device. + +

Step 3: Scan Patient

+ Access the cloning device's terminal, then navigate to the Damage Configuration menu and click 'scan.' If you see \ + 'Scan Successful' - great! Move on to the next step. If not, the device will inform you about what went wrong with \ + the process. If it says it failed to sequence the patient's brain, try to scan them again in a few seconds. \ + +

Step 4: Configure Device

+ This step is where the TTC-5601 cloning device diverges from its earlier models. Its advanced systems allow you to \ + conserve resources and elect to not fix certain damages, or elect to fix all damages - all on the fly! Keep \ + in mind that if you're short on resources, you'll have to leave some damages on the patient in order to clone \ + them. Once you're done tweaking settings, proceed to the next step. + +

Step 5: Clone

+ This is the simplest, but most important step. Simply press 'Clone' in the Damage Configuration menu, and the machine \ + will begin the process of cloning. The process is fully automatic, so feel free to take care of other chores in the \ + meanwhile - like moving the scanned cadaver to the morgue. + +

Step 6: Finish Procedure

+ After cloning, the patient may be disoriented - help them to get their bearings and put on the gear you stripped from \ + their previous body. In addition, make sure to fix any wounds you may have left to save resources before sending them off. + +

Congratulations! You now know how to use the TTC-5601 model cloning device. Please direct any further questions you have \ + to your Chief Medical Officer. Warranty void if used on living people, changeling organisms, or cluwnes. "}) diff --git a/code/modules/antagonists/changeling/changeling_power.dm b/code/modules/antagonists/changeling/changeling_power.dm index abb6ed1f284e..b5d08685e4e3 100644 --- a/code/modules/antagonists/changeling/changeling_power.dm +++ b/code/modules/antagonists/changeling/changeling_power.dm @@ -83,9 +83,6 @@ if(HAS_TRAIT(user, TRAIT_FAKEDEATH) && !bypass_fake_death) to_chat(user, "We are incapacitated.") return FALSE - if(!cling.can_use_powers) - to_chat(owner, "Our cells are repairing themselves, we are unable to use our powers!") - return FALSE return TRUE // Transform the target to the chosen dna. Used in transform.dm and tiny_prick.dm (handy for changes since it's the same thing done twice) diff --git a/code/modules/antagonists/changeling/datum_changeling.dm b/code/modules/antagonists/changeling/datum_changeling.dm index a507ad5cfa36..f8c39e74fd9b 100644 --- a/code/modules/antagonists/changeling/datum_changeling.dm +++ b/code/modules/antagonists/changeling/datum_changeling.dm @@ -48,10 +48,6 @@ var/datum/action/changeling/sting/chosen_sting /// If the changeling is in the process of regenerating from their fake death. var/regenerating = FALSE - /// Did changeling use headslug? - var/headslugged = FALSE - /// Can you use abilities due to a recent revival? - var/can_use_powers = TRUE blurb_text_color = COLOR_PURPLE blurb_text_outline_width = 1 diff --git a/code/modules/antagonists/changeling/powers/absorb.dm b/code/modules/antagonists/changeling/powers/absorb.dm index eb2b08c67caa..1674c5729cfa 100644 --- a/code/modules/antagonists/changeling/powers/absorb.dm +++ b/code/modules/antagonists/changeling/powers/absorb.dm @@ -86,10 +86,6 @@ target_cling.absorbed_dna.len = 1 target_cling.absorbed_count = 0 - if(cling.headslugged) - cling.headslugged = FALSE - to_chat(user, "By absorbing [target], we are once again strong enough to turn into a headslug.") - cling.chem_charges = min(cling.chem_charges + 10, cling.chem_storage) cling.is_absorbing = FALSE diff --git a/code/modules/antagonists/changeling/powers/become_headslug.dm b/code/modules/antagonists/changeling/powers/become_headslug.dm index 06301121e983..c6e87864784d 100644 --- a/code/modules/antagonists/changeling/powers/become_headslug.dm +++ b/code/modules/antagonists/changeling/powers/become_headslug.dm @@ -1,24 +1,33 @@ /datum/action/changeling/headslug name = "Last Resort" - desc = "We sacrifice our current body in a moment of need, placing us in control of a vessel that can plant our likeness in a new host. Costs 20 chemicals. We will need to absorb someone to use this ability again." - helptext = "We will be placed in control of a small, fragile creature. We may attack a corpse like this to plant an egg which will slowly mature into a new form for us. This ability is available only once per absorption." + desc = "We sacrifice our current body in a moment of need, placing us in control of a vessel that can plant our likeness in a new host. Costs 20 chemicals." + helptext = "We will be placed in control of a small, fragile creature. We may attack a corpse like this to plant an egg which will slowly mature into a new form for us." button_icon_state = "last_resort" chemical_cost = 20 + dna_cost = 2 req_human = TRUE req_stat = DEAD bypass_fake_death = TRUE - power_type = CHANGELING_INNATE_POWER + power_type = CHANGELING_PURCHASABLE_POWER + category = /datum/changeling_power_category/defence /datum/action/changeling/headslug/try_to_sting(mob/user, mob/target) - if(cling.headslugged) - to_chat(user, "We need to absorb a humanoid to headslug again.") - return if(tgui_alert(user, "Are you sure you wish to do this? This action cannot be undone.", "Sting", list("Yes", "No")) != "Yes") return - cling.headslugged = TRUE ..() -/datum/action/changeling/headslug/sting_action(mob/user) +/datum/action/changeling/headslug/sting_action(mob/living/user) + user.Weaken(30 SECONDS) + user.do_jitter_animation(1000, -1) // jitter until they are gibbed + user.visible_message("A loud crack erupts from [user], followed by a hiss.") + playsound(get_turf(user), "bonebreak", 75, TRUE) + playsound(get_turf(user), 'sound/machines/hiss.ogg', 75, TRUE) + addtimer(CALLBACK(src, PROC_REF(become_headslug), user), 5 SECONDS) + var/matrix/M = user.transform + M.Scale(1.8, 1.2) + animate(user, time = 5 SECONDS, transform = M, easing = SINE_EASING) + +/datum/action/changeling/headslug/proc/become_headslug(mob/user) var/datum/mind/M = user.mind var/list/organs = user.get_organs_zone("head", 1) diff --git a/code/modules/antagonists/changeling/powers/revive.dm b/code/modules/antagonists/changeling/powers/revive.dm index f5fc56bb4959..20e4c7933302 100644 --- a/code/modules/antagonists/changeling/powers/revive.dm +++ b/code/modules/antagonists/changeling/powers/revive.dm @@ -11,10 +11,6 @@ if(HAS_TRAIT(user, TRAIT_UNREVIVABLE)) to_chat(user, "Something is preventing us from regenerating, we will need to revive at another point.") return FALSE - - cling.can_use_powers = FALSE - addtimer(VARSET_CALLBACK(cling, can_use_powers, TRUE), 10 SECONDS) - REMOVE_TRAIT(user, TRAIT_FAKEDEATH, CHANGELING_TRAIT) for(var/obj/item/grab/G in user.grabbed_by) var/mob/living/carbon/M = G.assailant diff --git a/code/modules/antagonists/traitor/contractor/datums/objective_contract.dm b/code/modules/antagonists/traitor/contractor/datums/objective_contract.dm index 112065566848..3544d65bfdf4 100644 --- a/code/modules/antagonists/traitor/contractor/datums/objective_contract.dm +++ b/code/modules/antagonists/traitor/contractor/datums/objective_contract.dm @@ -203,6 +203,9 @@ // We pick the target ourselves so we don't want the default behaviour. owning_contract.invalidate() +/datum/objective/contract/update_explanation_text() + return + /** * Assigns a randomly selected zone to the contract's selectable zone at the given difficulty. * diff --git a/code/modules/client/preference/character.dm b/code/modules/client/preference/character.dm index 4347a71176c3..8d9fd97599aa 100644 --- a/code/modules/client/preference/character.dm +++ b/code/modules/client/preference/character.dm @@ -110,17 +110,17 @@ real_name = random_name(gender, species) /datum/character_save/proc/save(client/C) - var/organlist - var/rlimblist + var/organ_list + var/rlimb_list var/playertitlelist var/gearlist var/markingcolourslist = list2params(m_colours) var/markingstyleslist = list2params(m_styles) if(!isemptylist(organ_data)) - organlist = list2params(organ_data) + organ_list = list2params(organ_data) if(!isemptylist(rlimb_data)) - rlimblist = list2params(rlimb_data) + rlimb_list = list2params(rlimb_data) if(!isemptylist(player_alt_titles)) playertitlelist = list2params(player_alt_titles) if(!isemptylist(loadout_gear)) @@ -177,8 +177,8 @@ gen_record=:gen_record, player_alt_titles=:playertitlelist, disabilities=:disabilities, - organ_data=:organlist, - rlimb_data=:rlimblist, + organ_data=:organ_list, + rlimb_data=:rlimb_list, nanotrasen_relation=:nanotrasen_relation, physique=:physique, height=:height, @@ -237,8 +237,8 @@ "gen_record" = gen_record, "playertitlelist" = (playertitlelist ? playertitlelist : ""), // This it intentnional. It wont work without it! "disabilities" = disabilities, - "organlist" = (organlist ? organlist : ""), - "rlimblist" = (rlimblist ? rlimblist : ""), + "organ_list" = (organ_list ? organ_list : ""), + "rlimb_list" = (rlimb_list ? rlimb_list : ""), "nanotrasen_relation" = nanotrasen_relation, "physique" = physique, "height" = height, @@ -318,7 +318,7 @@ :sec_record, :gen_record, :playertitlelist, - :disabilities, :organlist, :rlimblist, :nanotrasen_relation, :physique, :height, :speciesprefs, + :disabilities, :organ_list, :rlimb_list, :nanotrasen_relation, :physique, :height, :speciesprefs, :socks, :body_accessory, :gearlist, :autohiss_mode, :h_grad_style, :h_grad_offset, :h_grad_colour, :h_grad_alpha, :custom_emotes, :tts_seed) "}, list( @@ -366,8 +366,8 @@ "gen_record" = gen_record, "playertitlelist" = (playertitlelist ? playertitlelist : ""), // This it intentional. It wont work without it! "disabilities" = disabilities, - "organlist" = (organlist ? organlist : ""), - "rlimblist" = (rlimblist ? rlimblist : ""), + "organ_list" = (organ_list ? organ_list : ""), + "rlimb_list" = (rlimb_list ? rlimb_list : ""), "nanotrasen_relation" = nanotrasen_relation, "physique" = physique, "height" = height, diff --git a/code/modules/countdown/countdown.dm b/code/modules/countdown/countdown.dm index 34d8653ca99a..1d11da9dc5d9 100644 --- a/code/modules/countdown/countdown.dm +++ b/code/modules/countdown/countdown.dm @@ -93,9 +93,8 @@ var/obj/machinery/clonepod/C = attached_to if(!istype(C)) return - else if(C.occupant) - var/completion = round(C.get_completion()) - return completion + + return C.clone_progress /obj/effect/countdown/supermatter name = "supermatter damage" diff --git a/code/modules/economy/economy_machinery/atm.dm b/code/modules/economy/economy_machinery/atm.dm index d93e39c9e488..563375fd5a20 100644 --- a/code/modules/economy/economy_machinery/atm.dm +++ b/code/modules/economy/economy_machinery/atm.dm @@ -314,6 +314,8 @@ return . += "Yellow ooze is dripping from the card slot!" +MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/economy/atm, 30, 30) + #undef ATM_SCREEN_DEFAULT #undef ATM_SCREEN_SECURITY #undef ATM_SCREEN_TRANSFER diff --git a/code/modules/mob/living/brain/MMI.dm b/code/modules/mob/living/brain/MMI.dm index c70327e5ba98..5b0293c0bb23 100644 --- a/code/modules/mob/living/brain/MMI.dm +++ b/code/modules/mob/living/brain/MMI.dm @@ -1,6 +1,7 @@ /obj/item/mmi name = "\improper Man-Machine Interface" - desc = "The Warrior's bland acronym, MMI, obscures the true horror of this monstrosity." + desc = "A compact, highly portable self-contained life support system, capable of housing a single brain and allowing it to seamlessly interface with whatever it is installed into. \ + It can be installed into a cyborg shell, AI core, mech, spiderbot, or an Integrated Robotic Chassis' chest cavity." icon = 'icons/obj/assemblies.dmi' icon_state = "mmi_empty" w_class = WEIGHT_CLASS_NORMAL @@ -28,6 +29,15 @@ var/next_possible_ghost_ping //Used by syndie MMIs, stores the master's mind UID for later referencing var/master_uid = null + /// Extended description on examine_more + var/extended_desc = "Development of Man-Machine Interfaces can be dated all the way back to the late 20th century within the Sol system, but the first viable designs didn't emerge until 2408 when Zeng-Hu Pharmaceuticals and Bishop Cybernetics unveiled a co-developed unit and established dominance over the niche market that persists into the present day. \ + The brain is submerged in a preservation fluid rich in mannitol, mitocholide, dissolved oxygen (or functional equivalent in other species) as well as a carefully tuned mixture of nutrients, hormones, peptides, and various other essential substances produced by a specialised chemical synthesiser. \ + A non-invasive neural interface uses a combination of targeted magnetic pulses, micro-electric discharges, and a grid of highly sensitive EMF probes allow a two-way connection between the MMI and the brain. On-board microphones, cameras, and a speaker provide basic sensory input and a method of communication, which can be expanded with an optional radio upgrade. Any further functionality must be provided by whatever the MMI is installed into. \ + Brains housed inside an MMI are effectively biologically immortal, provided the unit remains powered." + +/obj/item/mmi/examine_more(mob/user) + . = ..() + . += extended_desc /obj/item/mmi/attackby(obj/item/O as obj, mob/user as mob, params) if(istype(O, /obj/item/organ/internal/brain/golem)) @@ -289,10 +299,14 @@ /obj/item/mmi/syndie name = "\improper Syndicate Man-Machine Interface" - desc = "Syndicate's own brand of MMI. Mindslaves any brain inserted into it for as long as it's in. Cyborgs made with this MMI will be slaved to the owner. Does not fit into NT AI cores." + desc = "The Syndicate's own brand of MMI. Mindslaves any brain inserted into it for as long as it's inside. Cyborgs, mechs, spiderbots, or IRCs made with this MMI will be slaved to the owner. Does not fit into NT AI cores. \ + Cyborgs will appear to be linked to an AI (if present). If someone attempts to detonate the cyborg, it will automatically block the attempt and then disconnect from the AI. No emagged equipment is provided." origin_tech = "biotech=4;programming=4;syndicate=2" syndiemmi = TRUE mmi_item_name = "Syndicate Man-Machine Interface" + extended_desc = "Before the development of the mindslave implant by Cybersun, they first prototyped the technology using test subjects in MMIs. The unfettered access given to the user's brain made the task of delivering the memetic payloads trivial, allowing Cybersun's R&D to perfect their brainwashing techniques before moving on to a miniaturised implant. \ + Whilst these specialty MMIs are rarely used owing to the far greater applicability and convenience of the mindslave implant, they do see occasional employment by undercover agents that wish to stealthily convert the AI-slaved cyborgs of Nanotrasen. \ + Just like the mindslave implant, these are extremely illegal in most regions of space. Simple possession (to say nothing of actual use) generally warrants a very long prison sentence." /obj/item/mmi/syndie/attackby(obj/item/O, mob/user, params) if(!master_uid && ishuman(user) && user.mind && istype(O,/obj/item/organ/internal/brain)) diff --git a/code/modules/mob/living/carbon/human/human_life.dm b/code/modules/mob/living/carbon/human/human_life.dm index 83fba4605632..ab18b3fd2b8b 100644 --- a/code/modules/mob/living/carbon/human/human_life.dm +++ b/code/modules/mob/living/carbon/human/human_life.dm @@ -662,6 +662,11 @@ healthdoll.overlays -= (cached_overlays - new_overlays) healthdoll.cached_healthdoll_overlays = new_overlays + if(health <= HEALTH_THRESHOLD_CRIT) + throw_alert("succumb", /atom/movable/screen/alert/succumb) + else + clear_alert("succumb") + #undef BODYPART_PAIN_REDUCTION /mob/living/carbon/human/proc/handle_nutrition_alerts() //This is a terrible abuse of the alert system; something like this should be a HUD element diff --git a/code/modules/mob/living/carbon/human/species/slimepeople.dm b/code/modules/mob/living/carbon/human/species/slimepeople.dm index 9a7cc5098eef..39c9e7f6bc34 100644 --- a/code/modules/mob/living/carbon/human/species/slimepeople.dm +++ b/code/modules/mob/living/carbon/human/species/slimepeople.dm @@ -139,8 +139,8 @@ for(var/l in H.bodyparts_by_name) var/obj/item/organ/external/E = H.bodyparts_by_name[l] if(!istype(E)) - var/list/limblist = H.dna.species.has_limbs[l] - var/obj/item/organ/external/limb = limblist["path"] + var/list/limb_list = H.dna.species.has_limbs[l] + var/obj/item/organ/external/limb = limb_list["path"] var/parent_organ = initial(limb.parent_organ) var/obj/item/organ/external/parentLimb = H.bodyparts_by_name[parent_organ] if(!istype(parentLimb)) diff --git a/code/modules/mob/living/death.dm b/code/modules/mob/living/death.dm index 211b99e16a6d..e071470af7d5 100644 --- a/code/modules/mob/living/death.dm +++ b/code/modules/mob/living/death.dm @@ -87,10 +87,14 @@ var/mob/M = P if((M.client?.prefs.toggles2 & PREFTOGGLE_2_DEATHMESSAGE) && (isobserver(M) || M.stat == DEAD)) to_chat(M, "[mind.name] has died at [area_name]. (JMP)") + if(last_words) + to_chat(M, "[p_their(TRUE)] last words were: \"[last_words]\"") if(SSticker && SSticker.mode) SSticker.mode.check_win() + clear_alert("succumb") + // u no we dead return TRUE diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 2f10c2ce2092..4f3933fffb2f 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -291,18 +291,30 @@ /mob/living/verb/succumb() set hidden = TRUE + // if you use the verb you better mean it + do_succumb(FALSE) + +/mob/living/proc/do_succumb(cancel_on_no_words) + if(stat == DEAD) + to_chat(src, "It's too late, you're already dead!") + return if(health >= HEALTH_THRESHOLD_CRIT) to_chat(src, "You are unable to succumb to death! This life continues!") return var/last_words = tgui_input_text(src, "Do you have any last words?", "Goodnight, Sweet Prince", encode = FALSE) + if(isnull(last_words) && cancel_on_no_words) + to_chat(src, "You decide you aren't quite ready to die.") + return + if(stat == DEAD) // cancel em out if they died while they had the message box up last_words = null if(!isnull(last_words)) create_log(MISC_LOG, "gave their final words, [last_words]") + src.last_words = last_words // sorry whisper(last_words) add_attack_logs(src, src, "[src] has [!isnull(last_words) ? "whispered [p_their()] final words" : "succumbed to death"] with [round(health, 0.1)] points of health!") diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index e58823c3d616..55324a2ef85b 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -97,6 +97,9 @@ var/datum/middleClickOverride/middleClickOverride = null + /// Famous last words -- if succumbing, what the user's last words were + var/last_words + /* Taste Vars */ diff --git a/code/modules/mob/living/living_life.dm b/code/modules/mob/living/living_life.dm index 59b1b5a7c043..f930e555c9e8 100644 --- a/code/modules/mob/living/living_life.dm +++ b/code/modules/mob/living/living_life.dm @@ -176,3 +176,7 @@ overlay_fullscreen("brute", /atom/movable/screen/fullscreen/brute, severity) else clear_fullscreen("brute") + if(health <= HEALTH_THRESHOLD_CRIT) + throw_alert("succumb", /atom/movable/screen/alert/succumb) + else + clear_alert("succumb") diff --git a/code/modules/mob/living/simple_animal/hostile/headslug.dm b/code/modules/mob/living/simple_animal/hostile/headslug.dm index 19137beaac9e..be34f9ff999a 100644 --- a/code/modules/mob/living/simple_animal/hostile/headslug.dm +++ b/code/modules/mob/living/simple_animal/hostile/headslug.dm @@ -1,5 +1,5 @@ -#define EGG_INCUBATION_DEAD_CYCLE 60 -#define EGG_INCUBATION_LIVING_CYCLE 120 +#define EGG_INCUBATION_DEAD_CYCLE 120 +#define EGG_INCUBATION_LIVING_CYCLE 200 /mob/living/simple_animal/hostile/headslug name = "headslug" desc = "Absolutely not de-beaked or harmless. Keep away from corpses." @@ -59,6 +59,9 @@ to_chat(src, "With our egg laid, our death approaches rapidly...") addtimer(CALLBACK(src, PROC_REF(death)), 25 SECONDS) +/mob/living/simple_animal/hostile/headslug/projectile_hit_check(obj/item/projectile/P) + return (stat || FALSE) + /obj/item/organ/internal/body_egg/changeling_egg name = "changeling egg" desc = "Twitching and disgusting." diff --git a/code/modules/projectiles/guns/energy/special_eguns.dm b/code/modules/projectiles/guns/energy/special_eguns.dm index f32812f922a8..50167d3573bc 100644 --- a/code/modules/projectiles/guns/energy/special_eguns.dm +++ b/code/modules/projectiles/guns/energy/special_eguns.dm @@ -158,6 +158,7 @@ item_state = "plasmacutter" modifystate = -1 origin_tech = "combat=1;materials=3;magnets=2;plasmatech=3;engineering=1" + needs_permit = FALSE ammo_type = list(/obj/item/ammo_casing/energy/plasma) fire_sound = 'sound/weapons/laser.ogg' usesound = 'sound/items/welder.ogg' diff --git a/code/modules/station_goals/bsa.dm b/code/modules/station_goals/bsa.dm index d368a88f0ef1..e80a76b43b05 100644 --- a/code/modules/station_goals/bsa.dm +++ b/code/modules/station_goals/bsa.dm @@ -376,8 +376,10 @@ var/list/options = gps_locators if(area_aim) options += target_all_areas ? SSmapping.ghostteleportlocs : SSmapping.teleportlocs - var/V = input(user,"Select target", "Select target",null) in options|null - target = options[V] + var/choose = tgui_input_list(user, "Select target", "Target", options) + if(!choose) + return + target = options[choose] /obj/machinery/computer/bsa_control/proc/get_target_name() if(isarea(target)) diff --git a/config/example/config.toml b/config/example/config.toml index 3a1e6f4271e0..357ef25d2435 100644 --- a/config/example/config.toml +++ b/config/example/config.toml @@ -261,6 +261,8 @@ disable_certain_round_early_end = false traitor_objective_amount = 2 # Enable player limits on gamemodes. Disable if testing enable_gamemode_player_limit = true +# Enable to generate zero or more random station traits on game start. +add_random_station_traits = false ################################################################ diff --git a/icons/mob/screen_alert.dmi b/icons/mob/screen_alert.dmi index e7f44d0f575c..54a953367e2e 100644 Binary files a/icons/mob/screen_alert.dmi and b/icons/mob/screen_alert.dmi differ diff --git a/icons/obj/cryogenic2.dmi b/icons/obj/cryogenic2.dmi index 6fcd3462abd6..a8ab1a9f3e98 100644 Binary files a/icons/obj/cryogenic2.dmi and b/icons/obj/cryogenic2.dmi differ diff --git a/icons/obj/terminals.dmi b/icons/obj/terminals.dmi index c749a2b1360c..846889e7773e 100644 Binary files a/icons/obj/terminals.dmi and b/icons/obj/terminals.dmi differ diff --git a/paradise.dme b/paradise.dme index 65d147c246c8..fb8b0c4c52ae 100644 --- a/paradise.dme +++ b/paradise.dme @@ -45,6 +45,7 @@ #include "code\__DEFINES\callbacks.dm" #include "code\__DEFINES\chat.dm" #include "code\__DEFINES\chat_box_defines.dm" +#include "code\__DEFINES\cloning_defines.dm" #include "code\__DEFINES\clothing_defines.dm" #include "code\__DEFINES\color_defines.dm" #include "code\__DEFINES\combat_defines.dm" @@ -779,6 +780,7 @@ #include "code\game\machinery\buttons.dm" #include "code\game\machinery\cell_charger.dm" #include "code\game\machinery\clonepod.dm" +#include "code\game\machinery\clonescanner.dm" #include "code\game\machinery\constructable_frame.dm" #include "code\game\machinery\cryopod.dm" #include "code\game\machinery\dance_machine.dm" diff --git a/tgui/packages/tgui/interfaces/BlueSpaceArtilleryControl.js b/tgui/packages/tgui/interfaces/BlueSpaceArtilleryControl.js index 3e766e746d7f..45af08e0e912 100644 --- a/tgui/packages/tgui/interfaces/BlueSpaceArtilleryControl.js +++ b/tgui/packages/tgui/interfaces/BlueSpaceArtilleryControl.js @@ -1,5 +1,5 @@ import { useBackend } from '../backend'; -import { Button, LabeledList, Section, Box, ProgressBar } from '../components'; +import { Button, LabeledList, Section, Stack } from '../components'; import { Window } from '../layouts'; export const BlueSpaceArtilleryControl = (props, context) => { @@ -25,44 +25,48 @@ export const BlueSpaceArtilleryControl = (props, context) => { ); } return ( - + -

- - {data.notice && ( - - {data.notice} - - )} - {alertStatus} - -
+ + +
+ + {data.notice && ( + + {data.notice} + + )} + {alertStatus} + +
+
+
); diff --git a/tgui/packages/tgui/interfaces/CloningConsole.js b/tgui/packages/tgui/interfaces/CloningConsole.js index 6844a380e0ab..aae7f9cc9519 100644 --- a/tgui/packages/tgui/interfaces/CloningConsole.js +++ b/tgui/packages/tgui/interfaces/CloningConsole.js @@ -1,405 +1,540 @@ -import { round } from 'common/math'; import { useBackend } from '../backend'; import { - Box, Button, - Stack, - Icon, LabeledList, - NoticeBox, ProgressBar, Section, + Box, Tabs, + Stack, + Collapsible, + Icon, } from '../components'; -import { COLORS } from '../constants'; -import { - ComplexModal, - modalRegisterBodyOverride, -} from '../interfaces/common/ComplexModal'; import { Window } from '../layouts'; import { resolveAsset } from '../assets'; -const viewRecordModalBodyOverride = (modal, context) => { - const { act, data } = useBackend(context); - const { activerecord, realname, health, unidentity, strucenzymes } = - modal.args; - const damages = health.split(' - '); - return ( -
- - {realname} - - {damages.length > 1 ? ( - <> - - {damages[0]} - -  |  - - {damages[2]} - -  |  - - {damages[3]} - -  |  - - {damages[1]} - - - ) : ( - Unknown - )} - - - {unidentity} - - - {strucenzymes} - - -
- ); -}; +const brokenFlag = 1 << 0; +const internalBleedingFlag = 1 << 5; +const burnWoundFlag = 1 << 7; export const CloningConsole = (props, context) => { const { act, data } = useBackend(context); - const { menu } = data; - modalRegisterBodyOverride('view_rec', viewRecordModalBodyOverride); + const { tab, has_scanner, pod_amount } = data; return ( - - - - - - - - -
- -
-
-
+ + +
+ + + {has_scanner ? 'Online' : 'Missing'} + + + {pod_amount} + + +
+ + act('menu', { tab: 1 })} + > + Main Menu + + act('menu', { tab: 2 })} + > + Damage Configuration + + +
+ +
); }; -const CloningConsoleNavigation = (props, context) => { - const { act, data } = useBackend(context); - const { menu } = data; - return ( - - - - act('menu', { - num: 1, - }) - } - > - Main - - - act('menu', { - num: 2, - }) - } - > - Records - - - - ); -}; - const CloningConsoleBody = (props, context) => { const { data } = useBackend(context); - const { menu } = data; + const { tab } = data; let body; - if (menu === 1) { + if (tab === 1) { body = ; - } else if (menu === 2) { - body = ; + } else if (tab === 2) { + body = ; } return body; }; const CloningConsoleMain = (props, context) => { const { act, data } = useBackend(context); - const { - loading, - scantemp, - occupant, - locked, - can_brainscan, - scan_mode, - numberofpods, - pods, - selected_pod, - } = data; - const isLocked = locked && !!occupant; + const { pods, pod_amount, selected_pod_UID } = data; return ( - <> -
- - Scanner Lock:  - -
-
- {numberofpods ? ( - pods.map((pod, i) => { - let podAction; - if (pod.status === 'cloning') { - podAction = ( - - {round(pod.progress, 0) + '%'} - - ); - } else if (pod.status === 'mess') { - podAction = ( - - ERROR - - ); - } else { - podAction = ( -
- + Select + + + + + + {!pod['cloning'] && ( + Pod is inactive. + )} + {!!pod['cloning'] && ( + + )} + + + + + {pod['biomass']}/ + {pod['biomass_storage_capacity'] + + ' (' + + (100 * pod['biomass']) / + pod['biomass_storage_capacity'] + + '%)'} + + + + {pod['sanguine_reagent']} + + + {pod['osseous_reagent']} + + + + + + ))} + ); }; -const CloningConsoleRecords = (props, context) => { +const CloningConsoleDamage = (props, context) => { const { act, data } = useBackend(context); - const { records } = data; - if (!records.length) { - return ( - - - -
- No records found. -
-
- ); - } + const { + selected_pod_data, + has_scanned, + scanner_has_patient, + feedback, + scan_successful, + cloning_cost, + has_scanner, + } = data; return ( - - {records.map((record, i) => ( - + + + } + > + {!has_scanned && ( + + {scanner_has_patient + ? 'No scan detected for current patient.' + : 'No patient is in the scanner.'} + + )} + {!!has_scanned && ( + {feedback['text']} + )} + +
+ + {(!scan_successful || !has_scanned) && ( + No valid scan detected. + )} + {!!scan_successful && !!has_scanned && ( + + + + + + + + + + + + + + selected_pod_data['biomass'] + ? 'bad' + : null + } + > + Biomass: {cloning_cost[0]}/ + {selected_pod_data['biomass']}/ + {selected_pod_data['biomass_storage_capacity']} + + + + + selected_pod_data['sanguine_reagent'] + ? 'bad' + : 'good' + } + > + Sanguine: {cloning_cost[1]}/ + {selected_pod_data['sanguine_reagent']}/ + {selected_pod_data['max_reagent_capacity']} + + + + selected_pod_data['osseous_reagent'] + ? 'bad' + : 'good' + } + > + Osseous: {cloning_cost[2]}/ + {selected_pod_data['osseous_reagent']}/ + {selected_pod_data['max_reagent_capacity']} + + + + + + + )} + +
+ + )} ); }; -const CloningConsoleTemp = (props, context) => { +const LimbsMenu = (props, context) => { const { act, data } = useBackend(context); - const { temp } = data; - if (!temp || !temp.text || temp.text.length <= 0) { - return; - } - - const tempProp = { [temp.style]: true }; + const { patient_limb_data, limb_list, desired_limb_data } = data; return ( - - - {temp.text} - -