diff --git a/monkestation/code/game/machinery/computer/cloning.dm b/monkestation/code/game/machinery/computer/cloning.dm
index 3dcd237a4b90..fcd93b7712f6 100644
--- a/monkestation/code/game/machinery/computer/cloning.dm
+++ b/monkestation/code/game/machinery/computer/cloning.dm
@@ -36,6 +36,7 @@
/obj/machinery/computer/cloning/Initialize()
. = ..()
updatemodules(TRUE)
+ AddElement(/datum/element/empprotection, EMP_PROTECT_SELF) // So when an experimental cloner gets emped, it's cloning console doesn't break nullifying the threat.
/obj/machinery/computer/cloning/Destroy()
if(pods)
diff --git a/monkestation/code/game/machinery/exp_cloner.dm b/monkestation/code/game/machinery/exp_cloner.dm
index 6d522ee9cfcd..497f9426699d 100644
--- a/monkestation/code/game/machinery/exp_cloner.dm
+++ b/monkestation/code/game/machinery/exp_cloner.dm
@@ -7,13 +7,37 @@
req_access = null
circuit = /obj/item/circuitboard/machine/clonepod/experimental
internal_radio = FALSE
+ grab_ghost_when = CLONER_FRESH_CLONE // This helps with getting the objective for evil clones to display.
VAR_PRIVATE
static/list/image/cached_clone_images
+ /// Am I producing evil clones?
+ var/datum/objective/evil_clone/evil_objective = null
+ /// Can my objective be changed?
+ var/locked = FALSE
+ /// The custom objective given by the traitor item.
+ var/custom_objective = null
/obj/machinery/clonepod/experimental/Destroy()
clear_human_dummy(REF(src))
return ..()
+/obj/machinery/clonepod/experimental/examine(mob/user)
+ . = ..()
+ if((evil_objective || custom_objective) && (in_range(user, src) || isobserver(user)))
+ if(!isnull(evil_objective) || !isnull(custom_objective))
+ . += span_warning("You notice an ominous, flashing red LED light.")
+ if(isobserver(user))
+ if(!isnull(custom_objective))
+ . += span_notice("Those cloned will have the objective: [custom_objective]") //This doesn't look the best I think.
+ else
+ . += span_notice("Those cloned will have the objective: [evil_objective.explanation_text]")
+
+/obj/machinery/clonepod/experimental/RefreshParts()
+ . = ..()
+ if(!isnull(evil_objective) || !isnull(custom_objective))
+ speed_coeff = round(speed_coeff / 2) // So better parts have half the speed increase.
+ speed_coeff += 1 // I still want basic parts to have base 100% speed.
+
//Start growing a human clone in the pod!
/obj/machinery/clonepod/experimental/growclone(clonename, ui, mutation_index, mindref, blood_type, datum/species/mrace, list/features, factions, list/quirks, datum/bank_account/insurance)
if(panel_open || mess || attempting)
@@ -54,13 +78,25 @@
ADD_TRAIT(clonee, TRAIT_NOCRITDAMAGE, CLONING_POD_TRAIT)
clonee.Unconscious(80)
+ var/role_text
+ var/poll_text
+ if(!isnull(custom_objective))
+ role_text = "syndicate clone"
+ poll_text = "Do you want to play as [clonename]'s syndicate clone?"
+ else if(!isnull(evil_objective))
+ role_text = "evil clone"
+ poll_text = "Do you want to play as [clonename]'s evil clone?"
+ else
+ role_text = "defective clone"
+ poll_text = "Do you want to play as [clonename]'s defective clone?"
+
var/list/mob/dead/observer/candidates = SSpolling.poll_ghost_candidates_for_mob(
- "Do you want to play as [clonename]'s defective clone?",
+ poll_text,
poll_time = 10 SECONDS,
target_mob = clonee,
ignore_category = POLL_IGNORE_DEFECTIVECLONE,
pic_source = get_clone_preview(clonee.dna) || clonee,
- role_name_text = "defective clone"
+ role_name_text = role_text
)
if(LAZYLEN(candidates))
var/mob/dead/observer/candidate = pick(candidates)
@@ -85,6 +121,23 @@
if(!mob_occupant?.mind) //When experimental cloner fails to get a ghost, it won't spit out a body, so we don't get an army of brainless rejects.
qdel(mob_occupant)
return FALSE
+ else if(!isnull(custom_objective))
+ var/datum/antagonist/evil_clone/antag_object = new
+ var/datum/objective/evil_clone/custom = new
+ custom.explanation_text = custom_objective
+ antag_object.objectives += custom
+ mob_occupant.mind.add_antag_datum(antag_object)
+ mob_occupant.grant_language(/datum/language/codespeak) // So you don't have to remember to grant each and every identical clone codespeak with the manual.
+ mob_occupant.remove_blocked_language(/datum/language/codespeak, source=LANGUAGE_ALL) // All the effects the codespeak manual would have.
+ ADD_TRAIT(mob_occupant, TRAIT_TOWER_OF_BABEL, MAGIC_TRAIT)
+ var/obj/item/implant/radio/syndicate/imp = new(src)
+ imp.implant(mob_occupant)
+ mob_occupant.faction |= ROLE_SYNDICATE
+ mob_occupant.AddComponent(/datum/component/simple_access, list(ACCESS_SYNDICATE, ACCESS_MAINT_TUNNELS, ACCESS_GENETICS, ACCESS_MINERAL_STOREROOM)) //Basic/syndicate access, and genetics too because clones are genetics stuff.
+ else if(!isnull(evil_objective))
+ var/datum/antagonist/evil_clone/antag_object = new
+ antag_object.objectives += new evil_objective()
+ mob_occupant.mind.add_antag_datum(antag_object)
return TRUE
/obj/machinery/clonepod/experimental/proc/get_clone_preview(datum/dna/clone_dna)
@@ -104,6 +157,25 @@
LAZYSET(cached_clone_images, key, preview)
return preview
+/obj/machinery/clonepod/experimental/emag_act(mob/user)
+ if(!locked)
+ evil_objective = /datum/objective/evil_clone/murder //Emags will give a nasty objective.
+ locked = TRUE
+ to_chat(user, span_warning("You corrupt the genetic compiler."))
+ add_fingerprint(user)
+ log_cloning("[key_name(user)] emagged [src] at [AREACOORD(src)], causing it to malfunction.")
+ RefreshParts()
+ else
+ to_chat(user, span_warning("The cloner is already malfunctioning."))
+
+/obj/machinery/clonepod/experimental/emp_act(severity)
+ . = ..()
+ if (!(. & EMP_PROTECT_SELF))
+ if(prob(100/severity) && !locked)
+ evil_objective = pick(subtypesof(/datum/objective/evil_clone) - /datum/objective/evil_clone/murder)
+ RefreshParts()
+ log_cloning("[src] at [AREACOORD(src)] corrupted due to EMP pulse.")
+
//Prototype cloning console, much more rudimental and lacks modern functions such as saving records, autocloning, or safety checks.
/obj/machinery/computer/prototype_cloning
name = "prototype cloning console"
diff --git a/monkestation/code/game/objects/items/storage/uplink_kits.dm b/monkestation/code/game/objects/items/storage/uplink_kits.dm
index 09b754b22d84..5b5148806f38 100644
--- a/monkestation/code/game/objects/items/storage/uplink_kits.dm
+++ b/monkestation/code/game/objects/items/storage/uplink_kits.dm
@@ -160,6 +160,45 @@
new /obj/item/lighter(src)
new /obj/item/jammer(src)
+/obj/item/storage/box/clonearmy
+ name = "Syndicate clone army kit"
+ desc = "A box containing everything you need to make a clone army. The disk inside cunningly disguised as a DNA data disk is used to give all clones a directive they must follow."
+ icon_state = "syndiebox"
+
+/obj/item/storage/box/clonearmy/PopulateContents()
+ var/static/items_inside = list(
+ /obj/item/disk/clonearmy = 1,
+ /obj/item/stack/sheet/iron = 15,
+ /obj/item/stack/sheet/glass = 4,
+ /obj/item/stack/cable_coil = 1,
+ /obj/item/circuitboard/machine/clonepod/experimental = 1,
+ /obj/item/circuitboard/machine/clonescanner = 1,
+ /obj/item/circuitboard/computer/cloning = 1,
+ /obj/item/stock_parts/manipulator/femto = 2, // The syndicate is so cool they gave you tier four parts. RIP my joke about tier 2 parts.
+ /obj/item/stock_parts/scanning_module/triphasic = 3,
+ /obj/item/stock_parts/micro_laser/quadultra = 1,
+ /obj/item/stock_parts/matter_bin/bluespace = 1,
+ /obj/item/wrench = 1,
+ /obj/item/screwdriver/nuke = 1,
+ /obj/item/multitool = 1, // For those who want space between the cloning console and pod.
+ /obj/item/language_manual/codespeak_manual/unlimited = 1,
+ /obj/item/implanter/radio/syndicate = 1, // So you can communicate with your clones, instead of having random evil clones roaming the halls with no direction.
+ /obj/item/paper/clone_guide = 1,)
+ generate_items_inside(items_inside, src)
+
+/obj/item/paper/clone_guide
+ name = "Clone Army User Manual" // Start and end shamelessly copied from contractor guide. I am not a good writer. This is also ugly.
+ default_raw_text = {"Welcome agent, thank you for purchasing the clone army kit.
\
+
\
+ - The "DNA data disk" inside is actually a sophisticated device that can be used to hijack an experimental cloner, giving the clones a directive they must follow.
\
+ - In order to use this disk, use it in your hand, and input your desired directive, before hitting the cloner with the disk. You can input and upload a new objective to replace the old one if you ever feel like it, the disk is infinitely reusable.
\
+ - The clones will be given basic access, including syndicate, maintenance, genetics, and mineral storage. They will also be given an implanted syndicate radio and automatically taught codespeak. Syndicate turrets and the like will recognize the clones as a member of the syndicate.
\
+ - Be wary, the clones will have obviously evil red eyes, which will alert anyone who sees them with no eye covering that something is wrong with them. Also, don't try to use this on newer cloning models, Nanotrasen fixed the vulnerability that lets the disk work in their newer models.
\
+ - When hacked, a cloner will begin to operate slower, and anyone who examines it closely will be able to see that the cloner is malfunctioning.
\
+ - A tip, any activated mutations in the person being scanned, will be present in the clones produced, allowing you to give the clones some intrinsic powers. Make sure to use activators, not mutators.
\
+
+ Good luck agent. You can burn this document."}
+
#undef KIT_ITEM_CATEGORY_SUPPORT
#undef KIT_ITEM_CATEGORY_WEAPONS
#undef KIT_ITEM_CATEGORY_MISC
diff --git a/monkestation/code/modules/antagonists/evil_clone/evil_clone.dm b/monkestation/code/modules/antagonists/evil_clone/evil_clone.dm
new file mode 100644
index 000000000000..a3b4bc9591db
--- /dev/null
+++ b/monkestation/code/modules/antagonists/evil_clone/evil_clone.dm
@@ -0,0 +1,23 @@
+/datum/antagonist/evil_clone
+ name = "\improper Evil Clone"
+ show_in_antagpanel = TRUE
+ roundend_category = "evil clones"
+ antagpanel_category = "Evil Clones"
+ show_name_in_check_antagonists = TRUE
+ show_to_ghosts = TRUE
+
+/datum/antagonist/evil_clone/greet()
+ . = ..()
+ owner.announce_objectives()
+ owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/revolutionary_tide.ogg', 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE)
+
+/datum/antagonist/evil_clone/apply_innate_effects(mob/living/mob_override)
+ . = ..()
+ var/mob/living/current = owner.current
+ current.AddElement(/datum/element/cult_eyes, initial_delay = 0 SECONDS)
+
+/datum/antagonist/evil_clone/remove_innate_effects(mob/living/mob_override)
+ . = ..()
+ var/mob/living/current = owner.current
+ if (HAS_TRAIT(current, TRAIT_UNNATURAL_RED_GLOWY_EYES))
+ current.RemoveElement(/datum/element/cult_eyes)
diff --git a/monkestation/code/modules/antagonists/evil_clone/evil_clone_objective.dm b/monkestation/code/modules/antagonists/evil_clone/evil_clone_objective.dm
new file mode 100644
index 000000000000..88aa241e4453
--- /dev/null
+++ b/monkestation/code/modules/antagonists/evil_clone/evil_clone_objective.dm
@@ -0,0 +1,38 @@
+/datum/objective/evil_clone/murder // The existance of the murderbone objective makes evil clones properly feared, so even when they aren't murderboning they will still be shunned and persecuted.
+ name = "clone supremacy"
+ explanation_text = "Make sure clones of yourself are the only ones alive. Do not spare the original."
+
+/datum/objective/evil_clone/sole
+ name = "real one"
+ explanation_text = "All other versions of you are imposters, eliminate them."
+
+/datum/objective/evil_clone/rule
+ name = "rightful rule"
+ explanation_text = "You and your fellow clones of yourself are the rightful rulers of the station, take control."
+
+/datum/objective/evil_clone/minion
+ name = "minion"
+ explanation_text = "Find the most evil being you can, and become their minion."
+
+/datum/objective/evil_clone/dud // Relies on more destructive objectives, to create conflict from crew hating evil clones because they MIGHT have a more evil objective.
+ name = "peaceful clone"
+ explanation_text = "You find it really mean that some people don't like you because of your red eyes."
+
+/datum/objective/evil_clone/tide
+ name = "tider"
+ explanation_text = "Crime is your religion, commit as much crime as possible. Only seriously injure people if they try to stop crime."
+
+/datum/objective/evil_clone/fake_cult
+ name = "fake cultist"
+ explanation_text = "Praise"
+
+/datum/objective/evil_clone/fake_cult/New()
+ var/god = pick(list("Rat'var", "Nar'sie")) //So clones with different gods will fight eachother.
+ explanation_text+=" [god]! They haven't answered your prayers yet, but surely if you pray enough and make elaborate enough rituals they will inevitably come. Make sure no heretical religions prosper."
+
+/datum/objective/evil_clone/territorial
+ name = "territorial"
+ explanation_text = "The clonepod which created you is a holy site only you and your fellow clones of yourself are worthy to be in the presence of. Secure the area around the clonepod and ensure no non-clones threaten it."
+
+/datum/objective/evil_clone/check_completion()
+ return TRUE
diff --git a/monkestation/code/modules/antagonists/evil_clone/evil_event.dm b/monkestation/code/modules/antagonists/evil_clone/evil_event.dm
new file mode 100644
index 000000000000..10d5b481d9cc
--- /dev/null
+++ b/monkestation/code/modules/antagonists/evil_clone/evil_event.dm
@@ -0,0 +1,19 @@
+/datum/round_event_control/cloner_corruption
+ name = "Experimental Cloner Corruption"
+ typepath = /datum/round_event/cloner_corruption
+ max_occurrences = 1
+ weight = 3
+ category = EVENT_CATEGORY_ENTITIES //Kinda, evil clones ARE entities.
+ track = EVENT_TRACK_MODERATE
+ tags = list(TAG_COMBAT) // Clones will likely start a fight, but will usually not cause wanton destruction.
+ earliest_start = 35 MINUTES //This requires an experimental cloner to be made, so should wait until later to fire when there's better chance one has been set up.
+
+/datum/round_event/cloner_corruption/start()
+ var/found = FALSE
+ for(var/obj/machinery/clonepod/experimental/cloner in GLOB.machines)
+ if(!cloner.locked)
+ cloner.evil_objective = pick(subtypesof(/datum/objective/evil_clone))
+ cloner.RefreshParts()
+ found = TRUE
+ if(!found) // Refund if no experimental cloners are found.
+ control.occurrences--
diff --git a/monkestation/code/modules/research/designs/machine_designs.dm b/monkestation/code/modules/research/designs/machine_designs.dm
index 41b5bcd143f0..91f2b95fe4e5 100644
--- a/monkestation/code/modules/research/designs/machine_designs.dm
+++ b/monkestation/code/modules/research/designs/machine_designs.dm
@@ -7,7 +7,7 @@
category = list(
RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_MEDICAL
)
- departmental_flags = DEPARTMENT_BITFLAG_MEDICAL
+ departmental_flags = DEPARTMENT_BITFLAG_MEDICAL | DEPARTMENT_BITFLAG_SCIENCE
/datum/design/board/clonepod
@@ -18,7 +18,17 @@
category = list(
RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_MEDICAL
)
- departmental_flags = DEPARTMENT_BITFLAG_MEDICAL
+ departmental_flags = DEPARTMENT_BITFLAG_MEDICAL | DEPARTMENT_BITFLAG_SCIENCE
+
+/datum/design/board/clonepod_experimental
+ name = "Machine Design (Experimental Clone Pod)"
+ desc = "Allows for the construction of circuit boards used to build an Experimental Cloning Pod."
+ id = "clonepod_experimental"
+ build_path = /obj/item/circuitboard/machine/clonepod/experimental
+ category = list(
+ RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_MEDICAL
+ )
+ departmental_flags = DEPARTMENT_BITFLAG_SCIENCE
/datum/design/board/clonescanner //hippie end, re-add cloning
@@ -29,7 +39,7 @@
category = list(
RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_MEDICAL
)
- departmental_flags = DEPARTMENT_BITFLAG_MEDICAL
+ departmental_flags = DEPARTMENT_BITFLAG_MEDICAL | DEPARTMENT_BITFLAG_SCIENCE
/datum/design/board/nanite_chamber
name = "Machine Design (Nanite Chamber Board)"
diff --git a/monkestation/code/modules/research/techweb/all_nodes.dm b/monkestation/code/modules/research/techweb/all_nodes.dm
index 55ec1b4100db..741c43fcf747 100644
--- a/monkestation/code/modules/research/techweb/all_nodes.dm
+++ b/monkestation/code/modules/research/techweb/all_nodes.dm
@@ -3,7 +3,7 @@
display_name = "Cloning"
description = "We have the technology to make him."
prereq_ids = list("biotech")
- design_ids = list("clonecontrol", "clonepod", "clonescanner", "dnascanner", "dna_disk")
+ design_ids = list("clonecontrol", "clonepod", "clonescanner", "dnascanner", "dna_disk", "clonepod_experimental")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
/////////////////////////Nanites/////////////////////////
diff --git a/monkestation/code/modules/uplink/uplink_items/job.dm b/monkestation/code/modules/uplink/uplink_items/job.dm
index 216d4cb5987d..8d80747eca3d 100644
--- a/monkestation/code/modules/uplink/uplink_items/job.dm
+++ b/monkestation/code/modules/uplink/uplink_items/job.dm
@@ -11,3 +11,55 @@
/datum/uplink_item/role_restricted/modified_syringe_gun
surplus = 50
+
+/datum/uplink_item/role_restricted/clonekit
+ name = "Clone Army Kit"
+ desc = "Everything you need for a clone army, armaments not included."
+ progression_minimum = 5 MINUTES
+ cost = 20
+ item = /obj/item/storage/box/clonearmy
+ restricted_roles = list(JOB_GENETICIST, JOB_RESEARCH_DIRECTOR, JOB_MEDICAL_DOCTOR, JOB_CHIEF_MEDICAL_OFFICER, JOB_CARGO_TECHNICIAN, JOB_QUARTERMASTER) // Experimental cloners were traditionally bought by cargo.
+
+///I know this probably isn't the right place to put it, but I don't know where I should put it, and I can move it later.
+/obj/item/disk/clonearmy
+ name = "DNA data disk" //Cunning disguise.
+ var/objective = ""
+ icon_state = "datadisk0"
+
+/obj/item/disk/clonearmy/Initialize(mapload)
+ . = ..()
+ icon_state = "datadisk[rand(0,7)]"
+ add_overlay("datadisk_gene")
+
+/obj/item/disk/clonearmy/attack_self(mob/user)
+ var/targName = tgui_input_text(user, "Enter a directive for the evil clones.", "Clone Directive Entry", objective, CONFIG_GET(number/max_law_len), TRUE)
+ if(!targName)
+ return
+ if(is_ic_filtered(targName))
+ to_chat(user, span_warning("Error: Directive contains invalid text."))
+ return
+ var/list/soft_filter_result = is_soft_ooc_filtered(targName)
+ if(soft_filter_result)
+ if(tgui_alert(user,"Your directive contains \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\". \"[soft_filter_result[CHAT_FILTER_INDEX_REASON]]\", Are you sure you want to use it?", "Soft Blocked Word", list("Yes", "No")) != "Yes")
+ return
+ message_admins("[ADMIN_LOOKUPFLW(user)] has passed the soft filter for \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\" they may be using a disallowed term for a clone directive. Directive: \"[html_encode(targName)]\"")
+ log_admin_private("[key_name(user)] has passed the soft filter for \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\" they may be using a disallowed term for a clone directive. Directive: \"[targName]\"")
+ objective = targName
+ ..()
+
+/obj/item/disk/clonearmy/attack()
+ return
+
+/obj/item/disk/clonearmy/afterattack(atom/target, mob/user, proximity)
+ . = ..()
+ var/atom/A = target
+ if(!proximity)
+ return
+ . |= AFTERATTACK_PROCESSED_ITEM
+ if(!istype(A, /obj/machinery/clonepod/experimental))
+ return
+ to_chat(user, "You upload the directive to the experimental cloner.")
+ var/obj/machinery/clonepod/experimental/pod = target
+ pod.custom_objective = objective
+ pod.RefreshParts()
+ pod.locked = TRUE // The pod shouldn't be eligible for cloner event.
diff --git a/tgstation.dme b/tgstation.dme
index 9ca710c5fd5c..c1645b018ffa 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -6078,6 +6078,9 @@
#include "monkestation\code\modules\antagonists\contractor\items\modsuit\theme.dm"
#include "monkestation\code\modules\antagonists\cult\blood_magic.dm"
#include "monkestation\code\modules\antagonists\ert\moff_inspectors.dm"
+#include "monkestation\code\modules\antagonists\evil_clone\evil_clone.dm"
+#include "monkestation\code\modules\antagonists\evil_clone\evil_clone_objective.dm"
+#include "monkestation\code\modules\antagonists\evil_clone\evil_event.dm"
#include "monkestation\code\modules\antagonists\florida_man\__outfits.dm"
#include "monkestation\code\modules\antagonists\florida_man\_defines.dm"
#include "monkestation\code\modules\antagonists\florida_man\_florida_man.dm"