From d315286ec3d20e1712368be0757f8f3dd368b03f Mon Sep 17 00:00:00 2001 From: Jacquerel Date: Tue, 29 Apr 2025 15:35:02 +0100 Subject: [PATCH] Readds Cloning (#90754) --- .../SpaceRuins/cloning_facility.dmm | 586 ++++++++++++++++++ code/__DEFINES/dcs/signals/signals_cloner.dm | 2 + code/__DEFINES/role_preferences.dm | 2 + code/_globalvars/lists/poll_ignore.dm | 1 + .../datums/components/ghost_direct_control.dm | 1 - code/datums/quirks/_quirk.dm | 5 +- code/datums/quirks/neutral_quirks/gamer.dm | 2 +- code/datums/quirks/positive_quirks/chipped.dm | 2 +- code/datums/ruins/space.dm | 12 +- .../experimental_cloner_computer.dm | 170 +++++ .../experimental_cloner_fuckup.dm | 164 +++++ .../experimental_cloner_pod.dm | 177 ++++++ .../experimental_cloner_scanner.dm | 166 +++++ .../experimental_cloning_record.dm | 83 +++ .../circuitboards/computer_circuitboards.dm | 5 + .../machines/machine_circuitboards.dm | 22 + .../antagonists/evil_clone/evil_clone.dm | 67 ++ .../ruins/spaceruin_code/cloning_lab.dm | 35 ++ code/modules/mob/living/status_procs.dm | 4 +- icons/obj/machines/cloning.dmi | Bin 21876 -> 23421 bytes tgstation.dme | 8 + .../tgui/interfaces/ExperimentalCloner.tsx | 143 +++++ 22 files changed, 1647 insertions(+), 10 deletions(-) create mode 100644 _maps/RandomRuins/SpaceRuins/cloning_facility.dmm create mode 100644 code/__DEFINES/dcs/signals/signals_cloner.dm create mode 100644 code/game/machinery/experimental_cloner/experimental_cloner_computer.dm create mode 100644 code/game/machinery/experimental_cloner/experimental_cloner_fuckup.dm create mode 100644 code/game/machinery/experimental_cloner/experimental_cloner_pod.dm create mode 100644 code/game/machinery/experimental_cloner/experimental_cloner_scanner.dm create mode 100644 code/game/machinery/experimental_cloner/experimental_cloning_record.dm create mode 100644 code/modules/antagonists/evil_clone/evil_clone.dm create mode 100644 code/modules/mapfluff/ruins/spaceruin_code/cloning_lab.dm create mode 100644 tgui/packages/tgui/interfaces/ExperimentalCloner.tsx diff --git a/_maps/RandomRuins/SpaceRuins/cloning_facility.dmm b/_maps/RandomRuins/SpaceRuins/cloning_facility.dmm new file mode 100644 index 00000000000..b2d0e007685 --- /dev/null +++ b/_maps/RandomRuins/SpaceRuins/cloning_facility.dmm @@ -0,0 +1,586 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/turf/template_noop, +/area/template_noop) +"b" = ( +/turf/closed/wall/r_wall, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"c" = ( +/obj/effect/spawner/structure/window/reinforced, +/turf/open/floor/iron, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"d" = ( +/turf/closed/wall/r_wall/rust, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"e" = ( +/obj/machinery/defibrillator_mount/loaded, +/turf/closed/wall/r_wall, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"f" = ( +/obj/effect/spawner/structure/window/reinforced, +/turf/open/floor/iron/airless, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"g" = ( +/obj/structure/table, +/obj/item/flashlight/lamp, +/obj/effect/decal/cleanable/cobweb, +/obj/effect/turf_decal/tile/blue{ + dir = 1 + }, +/obj/effect/turf_decal/tile/blue, +/obj/effect/turf_decal/tile/blue{ + dir = 4 + }, +/obj/effect/turf_decal/tile/blue{ + dir = 8 + }, +/turf/open/floor/iron/white, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"h" = ( +/obj/structure/table, +/obj/item/pen, +/obj/item/paper/fluff/ruins/exp_cloning/log, +/obj/effect/turf_decal/tile/blue{ + dir = 1 + }, +/obj/effect/turf_decal/tile/blue, +/obj/effect/turf_decal/tile/blue{ + dir = 4 + }, +/obj/effect/turf_decal/tile/blue{ + dir = 8 + }, +/turf/open/floor/iron/white, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"i" = ( +/obj/effect/turf_decal/tile/blue{ + dir = 1 + }, +/obj/effect/turf_decal/tile/blue, +/obj/effect/turf_decal/tile/blue{ + dir = 4 + }, +/obj/effect/turf_decal/tile/blue{ + dir = 8 + }, +/obj/machinery/computer/experimental_cloner, +/turf/open/floor/iron/white, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"j" = ( +/obj/item/paper/fluff/ruins/exp_cloning/manual, +/obj/effect/turf_decal/tile/blue{ + dir = 1 + }, +/obj/effect/turf_decal/tile/blue, +/obj/effect/turf_decal/tile/blue{ + dir = 4 + }, +/obj/effect/turf_decal/tile/blue{ + dir = 8 + }, +/obj/structure/table, +/turf/open/floor/iron/white, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"k" = ( +/obj/effect/turf_decal/tile/blue{ + dir = 1 + }, +/obj/effect/turf_decal/tile/blue, +/obj/effect/turf_decal/tile/blue{ + dir = 4 + }, +/obj/effect/turf_decal/tile/blue{ + dir = 8 + }, +/obj/machinery/experimental_cloner, +/turf/open/floor/iron/white, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"l" = ( +/obj/effect/turf_decal/tile/blue{ + dir = 1 + }, +/obj/effect/turf_decal/tile/blue, +/obj/effect/turf_decal/tile/blue{ + dir = 4 + }, +/obj/effect/turf_decal/tile/blue{ + dir = 8 + }, +/obj/structure/showcase/horrific_experiment, +/turf/open/floor/iron/white, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"m" = ( +/obj/effect/decal/cleanable/cobweb/cobweb2, +/obj/effect/turf_decal/tile/blue{ + dir = 1 + }, +/obj/effect/turf_decal/tile/blue, +/obj/effect/turf_decal/tile/blue{ + dir = 4 + }, +/obj/effect/turf_decal/tile/blue{ + dir = 8 + }, +/obj/machinery/light/small/directional/north, +/turf/open/floor/iron/white, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"n" = ( +/obj/structure/sign/nanotrasen, +/turf/closed/wall, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"o" = ( +/obj/machinery/vending/snack/teal, +/obj/effect/decal/cleanable/dirt, +/turf/open/floor/iron/airless, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"p" = ( +/obj/effect/decal/cleanable/dirt, +/turf/open/floor/iron/airless, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"q" = ( +/obj/structure/sign/directions/science{ + dir = 8 + }, +/turf/closed/wall/r_wall, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"r" = ( +/obj/effect/decal/remains/human, +/obj/effect/decal/cleanable/blood/old, +/obj/item/organ/ears/cat, +/turf/open/floor/iron, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"s" = ( +/obj/machinery/iv_drip, +/turf/open/floor/iron, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"t" = ( +/obj/structure/bed, +/obj/item/bedsheet/medical, +/mob/living/basic/living_limb_flesh, +/obj/machinery/light/small/directional/north, +/turf/open/floor/iron, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"v" = ( +/obj/effect/turf_decal/tile/blue{ + dir = 4 + }, +/turf/open/floor/iron/white, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"w" = ( +/obj/structure/chair{ + dir = 1 + }, +/obj/effect/turf_decal/tile/blue{ + dir = 1 + }, +/obj/effect/turf_decal/tile/blue{ + dir = 4 + }, +/turf/open/floor/iron/white, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"x" = ( +/obj/effect/turf_decal/tile/blue{ + dir = 1 + }, +/obj/effect/turf_decal/tile/blue{ + dir = 4 + }, +/turf/open/floor/iron/white, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"y" = ( +/obj/structure/chair{ + dir = 1 + }, +/obj/effect/turf_decal/tile/blue{ + dir = 1 + }, +/obj/effect/turf_decal/tile/blue, +/obj/effect/turf_decal/tile/blue{ + dir = 4 + }, +/obj/effect/turf_decal/tile/blue{ + dir = 8 + }, +/turf/open/floor/iron/white, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"z" = ( +/obj/effect/turf_decal/tile/blue{ + dir = 1 + }, +/turf/open/floor/iron/white, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"A" = ( +/obj/structure/sign/departments/science, +/turf/closed/wall, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"B" = ( +/turf/open/floor/iron/airless, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"C" = ( +/turf/open/floor/plating/airless, +/area/space/nearstation) +"D" = ( +/obj/structure/chair/comfy{ + dir = 8 + }, +/turf/open/floor/iron, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"E" = ( +/turf/open/floor/iron, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"F" = ( +/obj/machinery/door/airlock/research/glass, +/obj/effect/turf_decal/stripes/line{ + dir = 1 + }, +/obj/effect/turf_decal/stripes/line, +/turf/open/floor/iron, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"G" = ( +/obj/effect/turf_decal/stripes/line{ + dir = 1 + }, +/obj/effect/turf_decal/stripes/line, +/turf/open/floor/iron, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"H" = ( +/obj/machinery/door/airlock/research/glass, +/obj/effect/turf_decal/stripes/line, +/obj/effect/turf_decal/stripes/line{ + dir = 1 + }, +/turf/open/floor/iron, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"I" = ( +/obj/effect/turf_decal/stripes/line{ + dir = 8 + }, +/turf/open/floor/iron/white, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"J" = ( +/turf/open/floor/iron/white, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"K" = ( +/obj/machinery/door/airlock/research/glass, +/turf/open/floor/iron/airless, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"M" = ( +/obj/structure/broken_flooring{ + dir = 8; + icon_state = "plating"; + name = "broken plating" + }, +/turf/template_noop, +/area/space/nearstation) +"N" = ( +/obj/item/book/random, +/turf/open/floor/iron, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"O" = ( +/obj/structure/bookcase/random/fiction, +/obj/machinery/light/small/directional/south, +/turf/open/floor/iron, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"P" = ( +/obj/structure/table/glass, +/obj/structure/bedsheetbin, +/obj/machinery/light/cold/directional/south, +/turf/open/floor/iron/white, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"Q" = ( +/obj/structure/table/glass, +/obj/item/disk/design_disk/limbs/felinid, +/turf/open/floor/iron/white, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"R" = ( +/obj/structure/table/glass, +/obj/item/storage/medkit/regular, +/obj/item/healthanalyzer{ + desc = "A prototype hand-held body scanner able to distinguish vital signs of the subject."; + name = "prototype health analyzer" + }, +/turf/open/floor/iron/white, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"S" = ( +/obj/structure/table/glass, +/obj/item/storage/box/syringes, +/turf/open/floor/iron/white, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"T" = ( +/turf/closed/wall, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"U" = ( +/obj/effect/turf_decal/tile/blue{ + dir = 1 + }, +/obj/effect/turf_decal/tile/blue{ + dir = 4 + }, +/obj/effect/decal/cleanable/vomit/old, +/turf/open/floor/iron/white, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"V" = ( +/turf/closed/wall/r_wall/rust, +/area/space/nearstation) +"W" = ( +/obj/machinery/experimental_cloner_scanner, +/turf/open/floor/iron/white, +/area/ruin/space/has_grav/powered/ancient_shuttle) +"X" = ( +/obj/item/gps/spaceruin, +/turf/open/floor/plating/airless, +/area/space/nearstation) + +(1,1,1) = {" +a +a +a +a +a +a +a +a +a +"} +(2,1,1) = {" +a +a +a +a +a +a +a +a +a +"} +(3,1,1) = {" +a +a +c +c +c +c +c +a +a +"} +(4,1,1) = {" +a +a +c +r +D +N +c +a +a +"} +(5,1,1) = {" +a +a +c +s +E +E +c +a +a +"} +(6,1,1) = {" +a +a +d +t +E +O +d +a +a +"} +(7,1,1) = {" +a +a +d +b +F +d +d +a +a +"} +(8,1,1) = {" +a +a +a +b +G +b +a +a +a +"} +(9,1,1) = {" +a +a +d +d +H +b +b +a +a +"} +(10,1,1) = {" +a +b +b +I +I +I +c +a +a +"} +(11,1,1) = {" +a +c +g +v +J +J +c +a +a +"} +(12,1,1) = {" +a +c +h +w +J +W +d +a +a +"} +(13,1,1) = {" +a +d +i +x +J +P +b +a +a +"} +(14,1,1) = {" +a +b +j +y +J +Q +c +a +a +"} +(15,1,1) = {" +a +b +k +U +J +R +c +a +a +"} +(16,1,1) = {" +a +e +l +x +J +S +d +a +a +"} +(17,1,1) = {" +a +d +m +z +J +J +d +a +a +"} +(18,1,1) = {" +a +d +n +A +K +T +b +a +a +"} +(19,1,1) = {" +a +f +o +B +p +X +V +a +a +"} +(20,1,1) = {" +a +f +p +p +C +C +M +a +a +"} +(21,1,1) = {" +a +d +q +C +C +M +a +a +a +"} +(22,1,1) = {" +a +a +b +B +M +a +a +a +a +"} +(23,1,1) = {" +a +a +a +a +a +a +a +a +a +"} diff --git a/code/__DEFINES/dcs/signals/signals_cloner.dm b/code/__DEFINES/dcs/signals/signals_cloner.dm new file mode 100644 index 00000000000..5474570d5c2 --- /dev/null +++ b/code/__DEFINES/dcs/signals/signals_cloner.dm @@ -0,0 +1,2 @@ +/// Sent from [/obj/machinery/experimental_cloner_scanner/proc/complete_scan] : (/datum/experimental_cloning_record/new_record) +#define COMSIG_CLONER_SCAN_SUCCESSFUL "cloner_scan_successful" diff --git a/code/__DEFINES/role_preferences.dm b/code/__DEFINES/role_preferences.dm index 2b96c668de2..da975f90ea5 100644 --- a/code/__DEFINES/role_preferences.dm +++ b/code/__DEFINES/role_preferences.dm @@ -60,6 +60,7 @@ #define ROLE_BRAINWASHED "Brainwashed Victim" #define ROLE_DEATHSQUAD "Deathsquad" #define ROLE_DRONE "Drone" +#define ROLE_EVIL_CLONE "Evil Clone" #define ROLE_EMAGGED_BOT "Malfunctioning Bot" #define ROLE_HIVE "Hivemind Host" //Role removed, left here for safety. #define ROLE_HYPNOTIZED "Hypnotized Victim" @@ -77,6 +78,7 @@ #define ROLE_SENTIENCE "Sentience Potion Spawn" #define ROLE_SOULTRAPPED_HERETIC "Soultrapped Heretic" #define ROLE_SYNDICATE "Syndicate" +#define ROLE_EXPERIMENTAL_CLONER "Experimental Cloner" #define ROLE_CLOWN_OPERATIVE "Clown Operative" #define ROLE_FREE_GOLEM "Free Golem" diff --git a/code/_globalvars/lists/poll_ignore.dm b/code/_globalvars/lists/poll_ignore.dm index 7663bfa2a52..f4d21f95c0b 100644 --- a/code/_globalvars/lists/poll_ignore.dm +++ b/code/_globalvars/lists/poll_ignore.dm @@ -38,6 +38,7 @@ #define POLL_IGNORE_SYNDICATE "syndicate" #define POLL_IGNORE_VENUSHUMANTRAP "venus_human_trap" #define POLL_IGNORE_RECOVERED_CREW "recovered_crew" +#define POLL_IGNORE_EXPERIMENTAL_CLONER "experimental_cloner" GLOBAL_LIST_INIT(poll_ignore_desc, list( POLL_IGNORE_ACADEMY_WIZARD = "Academy Wizard Defender", diff --git a/code/datums/components/ghost_direct_control.dm b/code/datums/components/ghost_direct_control.dm index 8d9e632b059..b43ad734974 100644 --- a/code/datums/components/ghost_direct_control.dm +++ b/code/datums/components/ghost_direct_control.dm @@ -80,7 +80,6 @@ var/mob/chosen_one = SSpolling.poll_ghosts_for_target( question = poll_question, check_jobban = ban_type, - role = ban_type, poll_time = poll_length, checked_target = parent, ignore_category = poll_ignore_key, diff --git a/code/datums/quirks/_quirk.dm b/code/datums/quirks/_quirk.dm index fc01f7ca3dc..9529cf2befe 100644 --- a/code/datums/quirks/_quirk.dm +++ b/code/datums/quirks/_quirk.dm @@ -66,7 +66,7 @@ * * new_holder - The mob to add this quirk to. * * quirk_transfer - If this is being added to the holder as part of a quirk transfer. Quirks can use this to decide not to spawn new items or apply any other one-time effects. */ -/datum/quirk/proc/add_to_holder(mob/living/new_holder, quirk_transfer = FALSE, client/client_source) +/datum/quirk/proc/add_to_holder(mob/living/new_holder, quirk_transfer = FALSE, client/client_source, unique = TRUE) if(!new_holder) CRASH("Quirk attempted to be added to null mob.") @@ -100,7 +100,8 @@ if(!quirk_transfer) if(gain_text) to_chat(quirk_holder, gain_text) - add_unique(client_source) + if (unique) + add_unique(client_source) if(quirk_holder.client) post_add() diff --git a/code/datums/quirks/neutral_quirks/gamer.dm b/code/datums/quirks/neutral_quirks/gamer.dm index 9055b09ce7f..20aca2f3de5 100644 --- a/code/datums/quirks/neutral_quirks/gamer.dm +++ b/code/datums/quirks/neutral_quirks/gamer.dm @@ -21,7 +21,7 @@ RegisterSignal(quirk_holder, COMSIG_MOB_LOST_VIDEOGAME, PROC_REF(lost_game)) RegisterSignal(quirk_holder, COMSIG_MOB_PLAYED_VIDEOGAME, PROC_REF(gamed)) -/datum/quirk/gamer/add_unique(client/client_source) +/datum/quirk/gamer/post_add() // The gamer starts off quelled gaming_withdrawal_timer = addtimer(CALLBACK(src, PROC_REF(enter_withdrawal)), GAMING_WITHDRAWAL_TIME, TIMER_STOPPABLE) diff --git a/code/datums/quirks/positive_quirks/chipped.dm b/code/datums/quirks/positive_quirks/chipped.dm index 26b4883f9b3..a1af49f9bc5 100644 --- a/code/datums/quirks/positive_quirks/chipped.dm +++ b/code/datums/quirks/positive_quirks/chipped.dm @@ -21,7 +21,7 @@ associated_typepath = /datum/quirk/chipped customization_options = list(/datum/preference/choiced/chipped) -/datum/quirk/chipped/add_to_holder(mob/living/new_holder, quirk_transfer, client/client_source) +/datum/quirk/chipped/add_to_holder(mob/living/new_holder, quirk_transfer, client/client_source, unique = TRUE) var/chip_pref = client_source?.prefs?.read_preference(/datum/preference/choiced/chipped) if(isnull(chip_pref)) diff --git a/code/datums/ruins/space.dm b/code/datums/ruins/space.dm index 1af8c04997c..d2b81b413d0 100644 --- a/code/datums/ruins/space.dm +++ b/code/datums/ruins/space.dm @@ -528,21 +528,21 @@ BUBBERSTATION REMOVAL END */ /datum/map_template/ruin/space/commsbuoy id = "commsbuoy" suffix = "commsbuoy_lowtech.dmm" - name = "Kosmokomm Communications Buoy" + name = "Space-Ruin Kosmokomm Communications Buoy" description = "One of the SSC's many Comms Buoys, acting as a broadcaster, receiver and relay for interstellar communications. Due to the \ shoddy tech available, it does not enable local communications." /datum/map_template/ruin/space/commsbuoy_pirate id = "commsbuoy_pirate" suffix = "commsbuoy_pirate.dmm" - name = "Pirated Communications Buoy" + name = "Space-Ruin Pirated Communications Buoy" description = "A Comms Buoy satellite that has been hijacked by local criminal elements, acting as a broadcaster, receiver and relay for \ evil interstellar communications. Due to the shoddy tech available, it does not enable local communications." /datum/map_template/ruin/space/commsbuoy_nt id = "commsbuoy_nt" suffix = "commsbuoy_nt.dmm" - name = "Nanotrasen Model-7 Communications Buoy" + name = "Space-Ruin Nanotrasen Model-7 Communications Buoy" description = "One of Nanotrasen's highly advanced Communication Buoys. Besides acting as a broadcaster, receiver and relay for interstellar \ communications, the satellite also includes a Local-Network array and two multi-function satellite dishes, providing the local sector with \ connectivity - as long as you have your Employee ID handy. Though, this one has been reported to have some recent malfunctions." @@ -552,3 +552,9 @@ BUBBERSTATION REMOVAL END */ suffix = "film_studio.dmm" name = "Space-Ruin Space Sector 13 Local Channel" description = "A local TV station serving areas surrounding Space Sector 13, shows including but not limited to: talk shows, operas, stage plays and more!" + +/datum/map_template/ruin/space/experimental_cloning + id = "cloning_facility" + suffix = "cloning_facility.dmm" + name = "Space-Ruin Experimental Cloning Facility" + description = "A fragment of a research station containing a somewhat functional cloning device." diff --git a/code/game/machinery/experimental_cloner/experimental_cloner_computer.dm b/code/game/machinery/experimental_cloner/experimental_cloner_computer.dm new file mode 100644 index 00000000000..6e1b0542080 --- /dev/null +++ b/code/game/machinery/experimental_cloner/experimental_cloner_computer.dm @@ -0,0 +1,170 @@ +/// Computer which starts the experimental cloning process +/obj/machinery/computer/experimental_cloner + name = "experimental cloner control console" + desc = "It scans DNA structures." + circuit = /obj/item/circuitboard/computer/experimental_cloner + icon_screen = "crew" + icon_keyboard = "med_key" + light_color = LIGHT_COLOR_GREEN + interaction_flags_click = ALLOW_SILICON_REACH + /// Our current stored cloning record + var/datum/experimental_cloning_record/stored_record + /// Scanner we save a test subject from + var/obj/machinery/experimental_cloner_scanner/input + /// Pod we print someone into + var/obj/machinery/experimental_cloner/output + /// Whether we should automatically try to connect to nearby machines + var/find_connections = FALSE + +/obj/machinery/computer/experimental_cloner/Initialize(mapload, obj/item/circuitboard/circuit) + . = ..() + find_connections = mapload + +/obj/machinery/computer/experimental_cloner/post_machine_initialize() + . = ..() + if (find_connections) + connect_nearby_machines() + +/// Find nearby associated machinery and link up +/obj/machinery/computer/experimental_cloner/proc/connect_nearby_machines() + var/list/stuff_in_range = range(5, src) + + var/obj/machinery/experimental_cloner_scanner/scanner = locate() in stuff_in_range + if (!isnull(scanner)) + link_scanner(scanner) + var/obj/machinery/experimental_cloner/pod = locate() in stuff_in_range + if (!isnull(pod)) + link_pod(pod) + +/obj/machinery/computer/experimental_cloner/multitool_act(mob/living/user, obj/item/multitool/multi_tool) + . = NONE + if (machine_stat & BROKEN || isnull(multi_tool.buffer)) + return + + if (istype(multi_tool.buffer, /obj/machinery/experimental_cloner_scanner)) + unlink_scanner() + link_scanner(multi_tool.buffer) + balloon_alert(user, "scanner linked") + to_chat(user, span_notice("You link \the [multi_tool.buffer] with \the [src].")) + return ITEM_INTERACT_SUCCESS + if (istype(multi_tool.buffer, /obj/machinery/experimental_cloner)) + unlink_pod() + link_pod(multi_tool.buffer) + balloon_alert(user, "pod linked") + to_chat(user, span_notice("You link \the [multi_tool.buffer] with \the [src].")) + return ITEM_INTERACT_SUCCESS + +/obj/machinery/computer/experimental_cloner/ui_interact(mob/user, datum/tgui/ui) + . = ..() + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "ExperimentalCloner") + ui.open() + +/// Link up with a scanner to scan people +/obj/machinery/computer/experimental_cloner/proc/link_scanner(obj/machinery/experimental_cloner_scanner/scanner) + RegisterSignal(scanner, COMSIG_QDELETING, PROC_REF(unlink_scanner)) + RegisterSignal(scanner, COMSIG_CLONER_SCAN_SUCCESSFUL, PROC_REF(on_scan_complete)) + input = scanner + +/// Store the record made by scanning someone +/obj/machinery/computer/experimental_cloner/proc/on_scan_complete(obj/machinery/experimental_cloner_scanner/scanner, datum/experimental_cloning_record/record) + SIGNAL_HANDLER + stored_record = record + +/// Release held references on deletion +/obj/machinery/computer/experimental_cloner/proc/unlink_scanner() + SIGNAL_HANDLER + if (!input) + return + UnregisterSignal(input, list(COMSIG_CLONER_SCAN_SUCCESSFUL, COMSIG_QDELETING)) + input = null + +/// Link up with a pod to print people +/obj/machinery/computer/experimental_cloner/proc/link_pod(obj/machinery/experimental_cloner/pod) + RegisterSignal(pod, COMSIG_QDELETING, PROC_REF(unlink_pod)) + output = pod + +/// Release held references on deletion +/obj/machinery/computer/experimental_cloner/proc/unlink_pod() + SIGNAL_HANDLER + if (!output) + return + UnregisterSignal(output, COMSIG_QDELETING) + output = null + +/obj/machinery/computer/experimental_cloner/ui_data(mob/user) + var/list/data = list() + + var/mob/living/carbon/human/scanner_occupant = input?.occupant + var/cloning_time_left = !output || output.awaiting_ghost ? 0 : timeleft(output.running_timer) + + data["record_name"] = stored_record?.name + data["record_species"] = stored_record?.dna.species?.name + data["linked_scanner"] = !!input + data["scanner_occupant"] = scanner_occupant?.name + data["scanner_species"] = scanner_occupant?.dna?.species?.name + data["is_scanning"] = !!input?.scanning + data["linked_pod"] = !!output + data["is_cloning"] = !!output?.running + data["cloning_name"] = output?.loaded_record?.name + data["cloning_species"] = output?.loaded_record?.dna?.species?.name + data["cloning_progress"] = output? ((output.cloning_time - cloning_time_left) / output.cloning_time) * 100 : 0 + + return data + +/obj/machinery/computer/experimental_cloner/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(.) + return + + switch(action) + if ("clear_record") + if (isnull(stored_record)) + return TRUE + stored_record = null + playsound(src, 'sound/machines/ping.ogg', vol = 100) + return TRUE + if ("start_scan") + if (isnull(input)) + balloon_alert(ui.user, "no linked scanner!") + playsound(src, 'sound/machines/buzz/buzz-two.ogg', 50, TRUE) + return TRUE + if (isnull(input.occupant)) + balloon_alert(ui.user, "scanner empty!") + playsound(src, 'sound/machines/buzz/buzz-two.ogg', 50, TRUE) + return TRUE + if (!iscarbon(input.occupant)) + balloon_alert(ui.user, "invalid subject!") + playsound(src, 'sound/machines/buzz/buzz-two.ogg', 50, TRUE) + return TRUE + var/mob/living/carbon/carbon_occupant = input.occupant + if (!carbon_occupant.has_dna()) + balloon_alert(ui.user, "invalid subject!") + playsound(src, 'sound/machines/buzz/buzz-two.ogg', 50, TRUE) + return TRUE + if (input.scanning) + balloon_alert(ui.user, "scanner busy!") + playsound(src, 'sound/machines/buzz/buzz-two.ogg', 50, TRUE) + return TRUE + + log_combat(ui.user, input.occupant, "irradiated via experimental clone scan") + input.start_scan() + return TRUE + if ("start_clone") + if (isnull(output)) + balloon_alert(ui.user, "no linked pod!") + playsound(src, 'sound/machines/buzz/buzz-two.ogg', 50, TRUE) + return TRUE + if (output.running) + balloon_alert(ui.user, "pod busy!") + playsound(src, 'sound/machines/buzz/buzz-two.ogg', 50, TRUE) + return TRUE + if (isnull(stored_record)) + balloon_alert(ui.user, "no stored DNA!") + playsound(src, 'sound/machines/buzz/buzz-two.ogg', 50, TRUE) + return TRUE + + ui.user.log_message("began growing an experimental clone of [stored_record.name]", LOG_GAME, log_globally = TRUE) + output.start_cloning(stored_record) + return TRUE diff --git a/code/game/machinery/experimental_cloner/experimental_cloner_fuckup.dm b/code/game/machinery/experimental_cloner/experimental_cloner_fuckup.dm new file mode 100644 index 00000000000..6002ee0476f --- /dev/null +++ b/code/game/machinery/experimental_cloner/experimental_cloner_fuckup.dm @@ -0,0 +1,164 @@ +#define CLONER_FAILURE_COMMON 3 +#define CLONER_FAILURE_RARE 1 + +/// Things we can do to mess up the clones +/datum/experimental_cloner_fuckup + /// How likely is it? + var/weight + +/// Can we actually mutate this thing? +/datum/experimental_cloner_fuckup/proc/is_valid(species_type) + return TRUE + +/// What do we do to it +/datum/experimental_cloner_fuckup/proc/apply_to_mob(mob/living/carbon/human/victim) + return + +/// What do we do after someone has taken over the mob +/datum/experimental_cloner_fuckup/proc/post_emerged(mob/living/victim) + return + +/// Become cat +/datum/experimental_cloner_fuckup/felinise + weight = CLONER_FAILURE_COMMON + +/datum/experimental_cloner_fuckup/felinise/is_valid(species_type) + return !ispath(species_type, /datum/species/human/felinid) + +/datum/experimental_cloner_fuckup/felinise/apply_to_mob(mob/living/carbon/human/victim) + if (is_species(victim, /datum/species/human)) + victim.set_species(/datum/species/human/felinid) + else // I think this is funnier + var/obj/item/organ/tail/cat/new_tail = new(victim) + var/obj/item/organ/ears/cat/new_ears = new(victim) + var/obj/item/organ/tongue/cat/new_tongue = new(victim) + + new_tail.replace_into(victim) + new_ears.replace_into(victim) + new_tongue.replace_into(victim) + +/// Bald +/datum/experimental_cloner_fuckup/bald + weight = CLONER_FAILURE_COMMON + +/datum/experimental_cloner_fuckup/bald/apply_to_mob(mob/living/carbon/human/victim) + victim.set_facial_hairstyle("Shaved", update = FALSE) + victim.set_hairstyle("Bald", update = TRUE) + +/datum/experimental_cloner_fuckup/bald/is_valid(species_type) + return !is_path_in_list(/datum/species/human, /datum/species/ethereal) + +/// Give a brain trauma or two +/datum/experimental_cloner_fuckup/brain_trauma + weight = CLONER_FAILURE_COMMON + +/datum/experimental_cloner_fuckup/brain_trauma/apply_to_mob(mob/living/carbon/human/victim) + victim.gain_trauma_type() + if (prob(50)) + return + victim.gain_trauma_type() + +/// Roll the limb and organ dice a couple times +/datum/experimental_cloner_fuckup/scramble + weight = CLONER_FAILURE_COMMON + +/datum/experimental_cloner_fuckup/scramble/apply_to_mob(mob/living/carbon/human/victim) + victim.bioscramble("your fucked up genes") + if (prob(50)) + return + victim.bioscramble("your fucked up genes") + if (prob(75)) + return + victim.bioscramble("your fucked up genes") + +/// Mess with the genes +/datum/experimental_cloner_fuckup/mutate + weight = CLONER_FAILURE_COMMON + +/datum/experimental_cloner_fuckup/mutate/apply_to_mob(mob/living/carbon/human/victim) + victim.easy_random_mutate(NEGATIVE + MINOR_NEGATIVE) + if (prob(50)) + return + victim.easy_random_mutate(NEGATIVE + MINOR_NEGATIVE) + if (prob(75)) + return + victim.easy_random_mutate(NEGATIVE + MINOR_NEGATIVE) + +/// Makes your limbs mad at you +/datum/experimental_cloner_fuckup/lively_flesh + weight = CLONER_FAILURE_COMMON + +/datum/experimental_cloner_fuckup/lively_flesh/apply_to_mob(mob/living/carbon/human/victim) + var/list/valid_zones = GLOB.limb_zones.Copy() + for (var/i in 1 to (rand(1, 3))) + var/target_zone = pick_n_take(valid_zones) + + var/part_type + switch(target_zone) + if(BODY_ZONE_L_ARM) + part_type = /obj/item/bodypart/arm/left/flesh + if(BODY_ZONE_R_ARM) + part_type = /obj/item/bodypart/arm/right/flesh + if(BODY_ZONE_L_LEG) + part_type = /obj/item/bodypart/leg/left/flesh + if(BODY_ZONE_R_LEG) + part_type = /obj/item/bodypart/leg/right/flesh + + var/obj/item/bodypart/old_bodypart = victim.get_bodypart(target_zone) + var/obj/item/bodypart/new_bodypart = new part_type() + new_bodypart.replace_limb(victim, TRUE) + qdel(old_bodypart) + +/// Contaminated sample +/datum/experimental_cloner_fuckup/fly_mishap + weight = CLONER_FAILURE_COMMON + +/datum/experimental_cloner_fuckup/fly_mishap/apply_to_mob(mob/living/carbon/human/victim) + victim.set_species(/datum/species/fly) + +/// Return to monkey +/datum/experimental_cloner_fuckup/monkey + weight = CLONER_FAILURE_RARE + +/datum/experimental_cloner_fuckup/monkey/post_emerged(mob/living/carbon/victim) + victim.visible_message(\ + span_boldwarning("[victim]'s hair begins to grow rapidly!"),\ + span_boldwarning("As you emerge from the pod, all the hair on your body starts to grow!")) + victim.monkeyize() + +/// No skin +/datum/experimental_cloner_fuckup/skeletised + weight = CLONER_FAILURE_RARE + +/datum/experimental_cloner_fuckup/skeletised/post_emerged(mob/living/victim) + victim.emote("scream") + victim.visible_message(\ + span_boldwarning("[victim]'s flesh slithers off in a disgusting heap!"),\ + span_boldwarning("As you emerge from the pod, your skin slithers off onto the ground!")) + victim.set_species(/datum/species/skeleton) + new /obj/effect/gibspawner/human/bodypartless(victim.drop_location()) + +/// Become a psyker, possibly the worst fate on this list +/datum/experimental_cloner_fuckup/psykerize + weight = CLONER_FAILURE_RARE + +/datum/experimental_cloner_fuckup/total_failure/post_emerged(mob/living/carbon/human/victim) + victim.slow_psykerize() + +/// Just fuck me up +/datum/experimental_cloner_fuckup/total_failure + weight = CLONER_FAILURE_RARE + +/datum/experimental_cloner_fuckup/total_failure/post_emerged(mob/living/victim) + victim.emote("scream") + victim.visible_message(\ + span_boldwarning("[victim] collapses bonelessly into a writhing heap of flesh!"),\ + span_boldwarning("As you emerge from the pod, your boneless flesh collapses into a writhing heap!")) + var/mob/living/basic/fleshblob/blob = new(victim.drop_location()) + blob.name = victim.real_name + blob.real_name = victim.real_name + victim.mind?.transfer_to(blob, TRUE) + victim.gib() + +#undef CLONER_FAILURE_COMMON +#undef CLONER_FAILURE_RARE diff --git a/code/game/machinery/experimental_cloner/experimental_cloner_pod.dm b/code/game/machinery/experimental_cloner/experimental_cloner_pod.dm new file mode 100644 index 00000000000..5866b9d1824 --- /dev/null +++ b/code/game/machinery/experimental_cloner/experimental_cloner_pod.dm @@ -0,0 +1,177 @@ +/// How likely are we to do something weird to the clone? Persists between construct/deconstruct +GLOBAL_VAR_INIT(experimental_cloner_fuckup_chance, 50) + +/// Machine which experimentally clones people you scanned with the experimental cloner scanner +/obj/machinery/experimental_cloner + name = "experimental cloning pod" + desc = "An early prototype of the currently-outlawed cloning pods used by Nanotrasen executives. I wonder if it still works?" + icon = 'icons/obj/machines/cloning.dmi' + icon_state = "pod_0" + base_icon_state = "pod" + density = TRUE + circuit = /obj/item/circuitboard/machine/experimental_cloner + use_power = NO_POWER_USE + processing_flags = START_PROCESSING_MANUALLY + /// How likely are we to produce evil clones? + var/evil_chance = 2 + /// Are we cooking? + var/running = FALSE + /// Are we waiting for candidates? + var/awaiting_ghost = FALSE + /// Data for mob we're about to produce + var/datum/experimental_cloning_record/loaded_record + /// Sound to play while cooking + var/datum/looping_sound/oven/sound_loop + /// How long it takes to bake a new man + var/cloning_time = 1 MINUTES + /// Time until we're done cooking + var/running_timer + +/obj/machinery/experimental_cloner/Initialize(mapload) + . = ..() + sound_loop = new(src, FALSE) + +/obj/machinery/experimental_cloner/power_change() + . = ..() + if (machine_stat & NOPOWER && running) + fail_growing() + +/obj/machinery/experimental_cloner/examine(mob/user) + . = ..() + if (running) + . += span_notice("You can see a shape forming in the murky liquid.") + +/obj/machinery/experimental_cloner/update_icon_state() + . = ..() + icon_state = "[base_icon_state]_[running]" + +/obj/machinery/experimental_cloner/on_deconstruction(disassembled) + if (running) + new /obj/effect/gibspawner/human(drop_location()) + +/obj/machinery/experimental_cloner/welder_act(mob/living/user, obj/item/tool) + if (user.combat_mode) + return NONE + + if (!tool.tool_start_check(user, amount = 5)) + return ITEM_INTERACT_BLOCKING + to_chat(user, span_notice("You start slicing \the [src] apart.")) + if(!tool.use_tool(src, user, 6 SECONDS, amount = 5, volume = 50)) + return ITEM_INTERACT_BLOCKING + deconstruct(disassembled = TRUE) + to_chat(user, span_notice("You slice \the [src] apart.")) + return ITEM_INTERACT_SUCCESS + +/obj/machinery/experimental_cloner/multitool_act(mob/living/user, obj/item/multitool/tool) + tool.set_buffer(src) + balloon_alert(user, "frequency stored") + return ITEM_INTERACT_SUCCESS + +/// Start growing a guy +/obj/machinery/experimental_cloner/proc/start_cloning(datum/experimental_cloning_record/to_create) + if (!to_create || running) + return + running = TRUE + loaded_record = to_create // We'll need this later + update_use_power(ACTIVE_POWER_USE) + update_appearance(UPDATE_ICON_STATE) + sound_loop.start() + running_timer = addtimer(CALLBACK(src, PROC_REF(finish_cloning)), cloning_time, TIMER_STOPPABLE | TIMER_DELETE_ME) + +/// This one didn't make it +/obj/machinery/experimental_cloner/proc/fail_growing() + new /obj/effect/gibspawner/human(drop_location()) + playsound(src, 'sound/machines/toilet_flush.ogg', vol = 40, vary = TRUE) + deltimer(running_timer) + on_finished() + +/// Stuff to do when we stop processing +/obj/machinery/experimental_cloner/proc/on_finished() + loaded_record = null + running = FALSE + update_use_power(NO_POWER_USE) + update_appearance(UPDATE_ICON_STATE) + sound_loop.stop() + +/// Produce a man and poll for ghosts +/obj/machinery/experimental_cloner/proc/finish_cloning() + var/datum/experimental_cloner_fuckup/mistake = get_cloning_mistake() + + var/mob/living/result = create_result_mob() + result.mind_initialize() + mistake?.apply_to_mob(result) + playsound(src, 'sound/machines/microwave/microwave-end.ogg', vol = 100) + + RegisterSignals(result, list(COMSIG_MOVABLE_MOVED, COMSIG_QDELETING, COMSIG_LIVING_DEATH), PROC_REF(on_clone_failed)) + + awaiting_ghost = TRUE + var/mob/chosen_one = SSpolling.poll_ghosts_for_target( + check_jobban = ROLE_RECOVERED_CREW, + poll_time = 10 SECONDS, + checked_target = result, + ignore_category = POLL_IGNORE_EXPERIMENTAL_CLONER, + alert_pic = result, + role_name_text = "Clone of [loaded_record.name]", + announce_chosen = TRUE, + ) + + awaiting_ghost = FALSE + if(QDELETED(result)) + return // We'll assume the signal will handle ending the cloning process + if(isnull(chosen_one)) + on_clone_failed(result) + return + + GLOB.experimental_cloner_fuckup_chance += rand(1, 5) // Each success gets more unstable + + chosen_one.log_message("took control of experimental clone of [result].", LOG_GAME) + result.PossessByPlayer(chosen_one.ckey) + to_chat(chosen_one, span_boldnotice("You are [loaded_record.name]! You aren't quite sure where you are or how you got here, though.")) + var/policy = get_policy(ROLE_EXPERIMENTAL_CLONER) + if (policy) + to_chat(chosen_one, span_notice(policy)) + + UnregisterSignal(result, list(COMSIG_MOVABLE_MOVED, COMSIG_QDELETING, COMSIG_LIVING_DEATH)) + result.forceMove(drop_location()) + if (prob(evil_chance)) + message_admins("[ADMIN_LOOKUPFLW(result)] has become an evil clone tasked to kill everyone else called '[result.real_name]'.") + result.mind?.add_antag_datum(/datum/antagonist/evil_clone) + + mistake?.post_emerged(result) + on_finished() + +/// Return either nothing or something weird to happen to our clone +/obj/machinery/experimental_cloner/proc/get_cloning_mistake() + if (!prob(GLOB.experimental_cloner_fuckup_chance)) + return + + var/list/all_types = subtypesof(/datum/experimental_cloner_fuckup) + var/list/weighted_types = list() + for (var/type in all_types) + var/datum/experimental_cloner_fuckup/new_type = new type() + if (!new_type.is_valid(loaded_record.dna.species.type)) + qdel(type) + continue + weighted_types[new_type] = new_type.weight + + return pick_weight(weighted_types) + +/// Return a mob for our tube to produce +/obj/machinery/experimental_cloner/proc/create_result_mob() + var/mob/living/carbon/human/new_clone = new(src) + loaded_record.apply_profile(new_clone) + + if (prob(75)) + var/static/list/permitted_heights = list(HUMAN_HEIGHT_SHORTEST, HUMAN_HEIGHT_SHORT, HUMAN_HEIGHT_MEDIUM, HUMAN_HEIGHT_TALL, HUMAN_HEIGHT_TALLER, HUMAN_HEIGHT_TALLEST) + new_clone.dna.remove_mutation(/datum/mutation/human/dwarfism) + new_clone.set_mob_height(pick(permitted_heights - loaded_record.height)) // To differentiate the clones + + return new_clone + +/// Somehow our clone exited the cloner or was deleted before we were ready or we just didn't find any ghosts +/obj/machinery/experimental_cloner/proc/on_clone_failed(mob/living/clone) + SIGNAL_HANDLER + UnregisterSignal(clone, list(COMSIG_MOVABLE_MOVED, COMSIG_QDELETING, COMSIG_LIVING_DEATH)) + if (!QDELETED(clone)) + qdel(clone) // Fuck you then + fail_growing() diff --git a/code/game/machinery/experimental_cloner/experimental_cloner_scanner.dm b/code/game/machinery/experimental_cloner/experimental_cloner_scanner.dm new file mode 100644 index 00000000000..e1e541e19f9 --- /dev/null +++ b/code/game/machinery/experimental_cloner/experimental_cloner_scanner.dm @@ -0,0 +1,166 @@ +/// Machine that you put someone in to scan them for the experimental cloner +/obj/machinery/experimental_cloner_scanner + name = "experimental cloning scanner" + desc = "An old prototype DNA scanner, compatible with an experimental cloning setup." + icon = 'icons/obj/machines/cloning.dmi' + icon_state = "scanner" + base_icon_state = "scanner" + density = TRUE + obj_flags = BLOCKS_CONSTRUCTION + interaction_flags_mouse_drop = NEED_DEXTERITY + occupant_typecache = list(/mob/living, /obj/item/bodypart/head, /obj/item/organ/brain) + circuit = /obj/item/circuitboard/machine/experimental_cloner_scanner + use_power = NO_POWER_USE + processing_flags = START_PROCESSING_MANUALLY + /// No exit while it's running + var/locked = FALSE + /// Are we trying to scan someone? + var/scanning = FALSE + /// How long does scanning take? + var/scan_time = 10 SECONDS + /// How long does breaking out take? + var/breakout_time = 3 SECONDS + /// Sound to play while scanning + var/datum/looping_sound/microwave/soundloop + /// Timer storing our scanning progress + var/scan_timer + /// Time to wait between telling people they can't get out + COOLDOWN_DECLARE(message_cooldown) + +/obj/machinery/experimental_cloner_scanner/Initialize(mapload) + . = ..() + soundloop = new (src) + +/// Scan the occupant, eventually producing a [/datum/experimental_cloning_record]. Returns FALSE if unsuccessful. +/obj/machinery/experimental_cloner_scanner/proc/start_scan() + if (machine_stat & BROKEN || machine_stat & NOPOWER || isnull(occupant)) + playsound(src, 'sound/machines/scanner/scanbuzz.ogg', vol = 100) + return FALSE + + SSradiation.irradiate(occupant) + scanning = TRUE + locked = TRUE + update_use_power(ACTIVE_POWER_USE) + playsound(src, 'sound/machines/closet/closet_unlock.ogg', vol = 100) + soundloop.start() + scan_timer = addtimer(CALLBACK(src, PROC_REF(complete_scan)), scan_time, TIMER_STOPPABLE | TIMER_DELETE_ME) + +/// Successfully produce a scan record +/obj/machinery/experimental_cloner_scanner/proc/complete_scan() + if (isnull(occupant)) + fail_scan() + return + var/datum/experimental_cloning_record/new_record = new() + new_record.create_profile(occupant) + SEND_SIGNAL(src, COMSIG_CLONER_SCAN_SUCCESSFUL, new_record) + + on_scan_stopped() + +/// There's nobody in the tank, so nothing to scan +/obj/machinery/experimental_cloner_scanner/proc/fail_scan() + playsound(src, 'sound/machines/scanner/scanbuzz.ogg', vol = 100) + on_scan_stopped() + +/// Generic stuff to do when we're done scanning +/obj/machinery/experimental_cloner_scanner/proc/on_scan_stopped() + update_use_power(NO_POWER_USE) + scanning = FALSE + if (locked) + playsound(src, 'sound/machines/closet/closet_unlock.ogg', vol = 100) + locked = FALSE + soundloop.stop() + deltimer(scan_timer) + +/obj/machinery/experimental_cloner_scanner/power_change() + . = ..() + if (machine_stat & NOPOWER && scanning) + fail_scan() + +/obj/machinery/experimental_cloner_scanner/open_machine(drop, density_to_set) + . = ..() + if (scanning) + fail_scan() + +/obj/machinery/experimental_cloner_scanner/update_icon_state() + //no power or maintenance + if (machine_stat & (NOPOWER|BROKEN)) + icon_state = "[base_icon_state][state_open ? "_open" : null]_unpowered" + return ..() + + //running and someone in there + if (occupant) + icon_state = "[base_icon_state]_occupied" + return ..() + + //running + icon_state = "[base_icon_state][state_open ? "_open" : null]" + return ..() + +/obj/machinery/experimental_cloner_scanner/container_resist_act(mob/living/user) + if (!locked) + open_machine() + return + + user.changeNext_move(CLICK_CD_BREAKOUT) + user.last_special = world.time + CLICK_CD_BREAKOUT + user.visible_message(span_notice("You see [user] kicking against the door of [src]!"), \ + span_notice("You lean on the back of [src] and start pushing the door open..."), \ + span_hear("You hear a metallic creaking from [src].")) + + balloon_alert(user, "breaking out...") + if (!do_after(user,(breakout_time), target = src)) + return + if (!user || user.stat != CONSCIOUS || user.loc != src || state_open || !locked) + return + + locked = FALSE + user.visible_message(span_warning("[user] successfully broke out of [src]!"), \ + span_notice("You successfully break out of [src]!")) + open_machine() + +/obj/machinery/experimental_cloner_scanner/relaymove(mob/living/user, direction) + if (user.stat || locked) + if (COOLDOWN_FINISHED(src, message_cooldown)) + COOLDOWN_START(src, message_cooldown, breakout_time) + balloon_alert(user, "door locked!") + container_resist_act(user) + return + open_machine() + +/obj/machinery/experimental_cloner_scanner/interact(mob/user) + toggle_open(user) + +/obj/machinery/experimental_cloner_scanner/mouse_drop_receive(atom/target, mob/user, params) + if (!iscarbon(target)) + return + close_machine(target) + +/// Try opening the machine if it's not locked +/obj/machinery/experimental_cloner_scanner/proc/toggle_open(mob/user) + if (state_open) + close_machine() + return + + if (locked) + balloon_alert(user, "it's locked!") + return + + open_machine() + +/obj/machinery/experimental_cloner_scanner/welder_act(mob/living/user, obj/item/tool) + if (user.combat_mode) + return NONE + + if (!tool.tool_start_check(user, amount = 5)) + return ITEM_INTERACT_BLOCKING + to_chat(user, span_notice("You start slicing \the [src] apart.")) + if(!tool.use_tool(src, user, 6 SECONDS, amount = 5, volume = 50)) + return ITEM_INTERACT_BLOCKING + deconstruct(disassembled = TRUE) + to_chat(user, span_notice("You slice \the [src] apart.")) + return ITEM_INTERACT_SUCCESS + +/obj/machinery/experimental_cloner_scanner/multitool_act(mob/living/user, obj/item/multitool/tool) + tool.set_buffer(src) + balloon_alert(user, "frequency stored") + return ITEM_INTERACT_SUCCESS diff --git a/code/game/machinery/experimental_cloner/experimental_cloning_record.dm b/code/game/machinery/experimental_cloner/experimental_cloning_record.dm new file mode 100644 index 00000000000..71a000059e6 --- /dev/null +++ b/code/game/machinery/experimental_cloner/experimental_cloning_record.dm @@ -0,0 +1,83 @@ +/// Data we store to clone someone +/datum/experimental_cloning_record + /// The name of the cloned subject + var/name + /// The DNA of the cloned subject + var/datum/dna/dna + /// The types of the quirks of the cloned subject + var/list/quirks + /// The types of the brain traumas of the cloned subject at time of scanning + var/list/brain_traumas + /// The age of the cloned subject + var/age + /// The bodytype of the cloned subject + var/physique + /// The athletics level of the cloned subject + var/athletics_level + /// The TTS voice of the cloned subject + var/voice + /// The TTS voice filter of the cloned subject + var/voice_filter + /// The height of the cloned subject + var/height + +/// Record data from a human subject +/datum/experimental_cloning_record/proc/create_profile(mob/living/carbon/human/subject) + name = subject.real_name + dna = new subject.dna.type() + subject.dna.copy_dna(dna) + age = subject.age + physique = subject.physique + athletics_level = subject.mind?.get_skill_level(/datum/skill/athletics) || SKILL_LEVEL_NONE + height = subject.mob_height + + quirks = list() + for(var/datum/quirk/quirk as anything in subject.quirks) + LAZYADD(quirks, quirk.type) + + brain_traumas = list() + for(var/datum/brain_trauma/trauma as anything in subject.get_traumas()) + LAZYADD(brain_traumas, trauma.type) + + voice = subject.voice + voice_filter = subject.voice_filter + +/// Duplicates our data +/datum/experimental_cloning_record/proc/create_copy() + var/datum/experimental_cloning_record/new_record = new() + new_record.name = name + new_record.dna = new dna.type() + dna.copy_dna(new_record.dna) + new_record.age = age + new_record.physique = physique + new_record.athletics_level = athletics_level + new_record.quirks = quirks.Copy() + new_record.brain_traumas = brain_traumas.Copy() + new_record.voice = voice + new_record.voice_filter = voice_filter + return new_record + +/// Apply record data to a clone +/datum/experimental_cloning_record/proc/apply_profile(mob/living/carbon/human/subject) + subject.name = name + subject.real_name = name + subject.age = age + subject.physique = physique + subject.mind?.set_level(/datum/skill/athletics, athletics_level, silent = TRUE) + subject.voice = voice + subject.voice_filter = voice_filter + + dna.transfer_identity(subject, TRUE) + + for (var/quirk_type as anything in quirks) + subject.add_quirk(quirk_type, add_unique = FALSE) + + for (var/trauma_type as anything in brain_traumas) + subject.gain_trauma(trauma_type) + + for (var/obj/item/bodypart/limb as anything in subject.bodyparts) + limb.update_limb(is_creating = TRUE) + + subject.updateappearance(mutcolor_update = TRUE) + subject.domutcheck() + subject.regenerate_icons() diff --git a/code/game/objects/items/circuitboards/computer_circuitboards.dm b/code/game/objects/items/circuitboards/computer_circuitboards.dm index e77e36a0dac..98d738235bd 100644 --- a/code/game/objects/items/circuitboards/computer_circuitboards.dm +++ b/code/game/objects/items/circuitboards/computer_circuitboards.dm @@ -405,6 +405,11 @@ greyscale_colors = CIRCUIT_COLOR_MEDICAL build_path = /obj/machinery/computer/pandemic +/obj/item/circuitboard/computer/experimental_cloner + name = "Experimental Cloner Control Console" + greyscale_colors = CIRCUIT_COLOR_MEDICAL + build_path = /obj/machinery/computer/experimental_cloner + //Science /obj/item/circuitboard/computer/aifixer diff --git a/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm b/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm index 976daeb1a34..108d320e53d 100644 --- a/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm +++ b/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm @@ -1158,6 +1158,28 @@ /obj/item/stack/cable_coil = 2, ) +/obj/item/circuitboard/machine/experimental_cloner_scanner + name = "Experimental Cloning Scanner" + greyscale_colors = CIRCUIT_COLOR_MEDICAL + build_path = /obj/machinery/experimental_cloner_scanner + req_components = list( + /datum/stock_part/scanning_module = 1, + /datum/stock_part/matter_bin = 1, + /datum/stock_part/micro_laser = 1, + /obj/item/stack/sheet/glass = 1, + /obj/item/stack/cable_coil = 2 + ) + +/obj/item/circuitboard/machine/experimental_cloner + name = "Experimental Cloning Pod" + greyscale_colors = CIRCUIT_COLOR_MEDICAL + build_path = /obj/machinery/experimental_cloner + req_components = list( + /datum/stock_part/matter_bin = 1, + /obj/item/stack/cable_coil = 1, + /obj/item/stack/sheet/glass = 4 + ) + /obj/item/circuitboard/machine/mechpad name = "Mecha Orbital Pad" greyscale_colors = CIRCUIT_COLOR_SCIENCE diff --git a/code/modules/antagonists/evil_clone/evil_clone.dm b/code/modules/antagonists/evil_clone/evil_clone.dm new file mode 100644 index 00000000000..3d6521d97da --- /dev/null +++ b/code/modules/antagonists/evil_clone/evil_clone.dm @@ -0,0 +1,67 @@ +/// Antag datum associated with the experimental cloner +/datum/antagonist/evil_clone + name = "\improper Evil Clone" + stinger_sound = 'sound/music/antag/hypnotized.ogg' + job_rank = ROLE_EVIL_CLONE + roundend_category = "evil clones" + show_in_antagpanel = TRUE + antagpanel_category = ANTAG_GROUP_CREW + show_name_in_check_antagonists = TRUE + count_against_dynamic_roll_chance = FALSE + +/datum/antagonist/evil_clone/on_gain() + if (owner.current) + name = "[owner.current.real_name] Prime" + forge_objectives() + return ..() + +/datum/antagonist/evil_clone/greet() + if(silent) + return + play_stinger() + var/mob/living/current_mob = owner.current + if (current_mob) + to_chat(current_mob, span_big("You are [current_mob.real_name].")) + to_chat(current_mob, span_hypnophrase("You are the only [current_mob.real_name].")) + to_chat(current_mob, span_boldwarning("Anyone else pretending to be [current_mob.real_name] must be punished.")) + owner.announce_objectives() + +/datum/antagonist/evil_clone/forge_objectives() + var/datum/objective/accept_no_substitutes/objective = new + objective.owner = owner + objective.set_target_name(owner.current?.real_name) + objectives += objective + +/// Kill everyone with the same name as you +/datum/objective/accept_no_substitutes + name = "kill all clones" + explanation_text = "Ensure that nobody with a particular name that you don't remember remains alive." + admin_grantable = TRUE + /// What name do we want to expunge? + var/target_name + +/// Set the name to check for +/datum/objective/accept_no_substitutes/proc/set_target_name(new_name) + target_name = new_name + explanation_text = "Ensure that nobody with the name [target_name] remains alive." + +/datum/objective/accept_no_substitutes/check_completion() + if (!target_name) + return FALSE // Well we forgot to check for a name + + for (var/mob/living/someone as anything in GLOB.player_list) // We will generously not include people who logged out or ghosted + if (!istype(someone)) + continue + if (someone.stat == DEAD) + continue + if (someone == owner.current) + continue + if (someone.real_name == target_name) + return FALSE + + return TRUE + +/datum/objective/accept_no_substitutes/admin_edit(mob/admin) + admin_simple_target_pick(admin) + if (target.current) + set_target_name(target.current.real_name) diff --git a/code/modules/mapfluff/ruins/spaceruin_code/cloning_lab.dm b/code/modules/mapfluff/ruins/spaceruin_code/cloning_lab.dm new file mode 100644 index 00000000000..02451a25e4b --- /dev/null +++ b/code/modules/mapfluff/ruins/spaceruin_code/cloning_lab.dm @@ -0,0 +1,35 @@ +/obj/item/paper/fluff/ruins/exp_cloning/manual + name = "paper - 'H-11 Cloning Apparatus Manual" + default_raw_text = "

Getting Started

\ + Congratulations, you are testing the H-11 experimental cloning device!
\ + Using the H-11 is almost as simple as brain surgery! Simply insert the target humanoid into the scanning chamber and create a copy of their DNA, then select the clone option to initiate cloning!
\ + 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.
\ +

The provided CLONEPOD SYSTEM will produce the desired clone. Standard clone maturation times are roughly 120 seconds. \ + The cloning pod will automatically dispose of subjects whose brains fail to develop properly.


\ + Please note that resulting clones may have DEVELOPMENTAL DEFECTS as a result of genetic drift. We hope to reduce this through further testing.
\ + Clones may also experience memory loss and radical changes in personality as a result of the cloning process.

\ +
\ + This technology produced under license from Thinktronic Systems, LTD." + +/obj/item/paper/fluff/ruins/exp_cloning/log + name = "experiment log" + default_raw_text = "

Day 1

\ + We are very excited to be part of the first crew of the SC Irmanda!
\ + This ship is made to test an innovative FTL technology. I had some concerns at first, \ + but the engineers assure me that it is safe and there is absolutely no risk of the external wings breaking off from the acceleration.
\ + We've been tasked with testing the latest model of the Thinktronic Cloning Pod. We'll stay in dock for a week before launching, but we're going to get started right away. \ + If the engine is as fast as they say, we might not have the time to run all the routine tests on the cloned subject!
\ +
\ +

Day 2

\ + We cloned an unknown corpse that was given to us by the medical crew. The genetic replication is good enough to let the subject survive outside of the pod, \ + but the cellular damage remains a concern for his long-term survival. For safety we will be keeping him in quarantine.
\ + We left him some books, but clearly we were too optimistic about his mental faculties. His brain seems to suffer from the same cloning decay that was caused by \ + the previous models. We will run further tests to see if there are improvements.
\ +

Day 4

\ + It seems we'll be launching even sooner than expected! Apparently the press is starting to lose interest, so we have to cut short the pre-flight checks \ + and give them something to talk about. Hopefully this will end up with increased funding...
\ + The crew has all been invited to the main hall, where we have seats for the initial FTL acceleration. Unfortunately the clone cannot leave the quarantine room \ + without risking infection, so we will strap him into the bed and hope for the best. We can grow another clone if anything goes wrong, anyway. \ +
\ + Professor Galen Linkovich" diff --git a/code/modules/mob/living/status_procs.dm b/code/modules/mob/living/status_procs.dm index 76e80454e8d..2335c320357 100644 --- a/code/modules/mob/living/status_procs.dm +++ b/code/modules/mob/living/status_procs.dm @@ -450,14 +450,14 @@ * * Returns TRUE on success, FALSE on failure (already has the quirk, etc) */ -/mob/living/proc/add_quirk(datum/quirk/quirktype, client/override_client) +/mob/living/proc/add_quirk(datum/quirk/quirktype, client/override_client, add_unique = TRUE) if(has_quirk(quirktype)) return FALSE var/qname = initial(quirktype.name) if(!SSquirks || !SSquirks.quirks[qname]) return FALSE var/datum/quirk/quirk = new quirktype() - if(quirk.add_to_holder(new_holder = src, client_source = override_client)) + if(quirk.add_to_holder(new_holder = src, client_source = override_client, unique = add_unique)) return TRUE qdel(quirk) return FALSE diff --git a/icons/obj/machines/cloning.dmi b/icons/obj/machines/cloning.dmi index 07815e366deeb386bfda0c0a4c206c423b83ec87..97808e916792b4af7c56ef32959af48b73091137 100644 GIT binary patch literal 23421 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 J3LWQL{{ { + const { act, data } = useBackend(); + const { + linked_scanner, + linked_pod, + record_name, + record_species, + scanner_occupant, + scanner_species, + is_scanning, + is_cloning, + cloning_name, + cloning_species, + cloning_progress, + } = data; + + return ( + + +
act('clear_record')} + disabled={!record_name} + tooltipPosition="bottom-start" + > + Clear + + } + > + {record_name ? ( + + {record_name} ({record_species ?? 'Unknown'}) + + ) : ( + 'No stored DNA on record.' + )} +
+ + +
+ {linked_scanner ? ( + + + Current Occupant: + {scanner_occupant ?? 'None'} + {scanner_occupant && ( + + Occupant Species: + {scanner_species ?? 'Unknown'} + +
+
+ +
+ {linked_pod ? ( + + {is_cloning ? ( + + Currently Cloning: + + {cloning_name} ({cloning_species ?? 'Unknown'}) + + + + + {cloning_progress === 100 && ( + Beginning neural kickstart... + )} + + ) : ( + +
+
+
+
+
+ ); +};