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.
\ + + 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"