Files
Bubberstation/code/game/machinery/computer/dna_console.dm
SkyratBot a94325afe4 [MIRROR] Replacing more C-style for loops with the faster, traditional ones. [MDB IGNORE] (#9560)
* Replacing more C-style for loops with the faster, traditional ones. (#62908)

* Replacing more C-style for loops with the faster, traditional ones.

Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com>
2021-11-19 20:37:13 +00:00

2258 lines
87 KiB
Plaintext

/// Base timeout for creating mutation activators and other injectors
#define INJECTOR_TIMEOUT 100
/// Maximum number of genetic makeup storage slots in DNA Console
#define NUMBER_OF_BUFFERS 3
/// Timeout for DNA Scramble in DNA Consoles
#define SCRAMBLE_TIMEOUT 600
/// Timeout for using the Joker feature to solve a gene in DNA Console
#define JOKER_TIMEOUT 12000
/// How much time DNA Scanner upgrade tiers remove from JOKER_TIMEOUT
#define JOKER_UPGRADE 3000
/// Maximum value for genetic damage strength when pulsing enzymes
#define GENETIC_DAMAGE_STRENGTH_MAX 15
/// Larger multipliers will affect the range of values when pulsing enzymes
#define GENETIC_DAMAGE_STRENGTH_MULTIPLIER 1
/// Maximum value for the genetic damage pulse duration when pulsing enzymes
#define GENETIC_DAMAGE_DURATION_MAX 30
/// Large values reduce pulse accuracy and may pulse other enzymes than selected
#define GENETIC_DAMAGE_ACCURACY_MULTIPLIER 3
/// Special status indicating a scanner occupant is transforming eg. from monkey to human
#define STATUS_TRANSFORMING 4
/// Multiplier for how much genetic damage received from DNA Console functionality
#define GENETIC_DAMAGE_IRGENETIC_DAMAGE_MULTIPLIER 1
/// Flag for the mutation ref search system. Search will include scanner occupant
#define SEARCH_OCCUPANT 1
/// Flag for the mutation ref search system. Search will include console storage
#define SEARCH_STORED 2
/// Flag for the mutation ref search system. Search will include diskette storage
#define SEARCH_DISKETTE 4
/// Flag for the mutation ref search system. Search will include advanced injector mutations
#define SEARCH_ADV_INJ 8
/// The base cooldown of the ability to copy enzymes and genetic makeup to people.
#define ENZYME_COPY_BASE_COOLDOWN (60 SECONDS)
#define GENETIC_DAMAGE_PULSE_UNIQUE_IDENTITY "ui"
#define GENETIC_DAMAGE_PULSE_UNIQUE_FEATURES "uf"
/obj/machinery/computer/scan_consolenew
name = "DNA Console"
desc = "Scan DNA."
icon_screen = "dna"
icon_keyboard = "med_key"
density = TRUE
circuit = /obj/item/circuitboard/computer/scan_consolenew
use_power = IDLE_POWER_USE
idle_power_usage = 10
active_power_usage = 400
light_color = LIGHT_COLOR_BLUE
/// Link to the techweb's stored research. Used to retrieve stored mutations
var/datum/techweb/stored_research
/// Duration for enzyme genetic damage pulses
var/pulse_duration = 2
/// Strength for enzyme genetic damage pulses
var/pulse_strength = 1
/// Maximum number of enzymes we can store
var/list/genetic_makeup_buffer[NUMBER_OF_BUFFERS]
/// List of all mutations stored on the DNA Console
var/list/stored_mutations = list()
/// List of all chromosomes stored in the DNA Console
var/list/stored_chromosomes = list()
/// Assoc list of all advanced injectors. Keys are injector names. Values are lists of mutations.
var/list/list/injector_selection = list()
/// Maximum number of advanced injectors that DNA Consoles store
var/max_injector_selections = 2
/// Maximum number of mutation that an advanced injector can store
var/max_injector_mutations = 10
/// Maximum total instability of all combined mutations allowed on an advanced injector
var/max_injector_instability = 50
/// World time when injectors are ready to be printed
var/injectorready = 0
/// World time when JOKER algorithm can be used in DNA Consoles
var/jokerready = 0
/// World time when Scramble can be used in DNA Consoles
var/scrambleready = 0
/// Currently stored genetic data diskette
var/obj/item/disk/data/diskette = null
/// Current delayed action, used for delayed enzyme transfer on scanner door close
var/list/delayed_action = null
/// Index of the enzyme being modified during delayed enzyme pulse operations
var/genetic_damage_pulse_index = 0
/// World time when the enzyme pulse should complete
var/genetic_damage_pulse_timer = 0
/// Which dna string to edit with the pulse
var/genetic_damage_pulse_type
/// Cooldown for the genetic makeup transfer actions.
COOLDOWN_DECLARE(enzyme_copy_timer)
/// Used for setting tgui data - Whether the connected DNA Scanner is usable
var/can_use_scanner = FALSE
/// Used for setting tgui data - Whether the current DNA Scanner occupant is viable for genetic modification
var/is_viable_occupant = FALSE
/// Used for setting tgui data - Whether Scramble DNA is ready
var/is_scramble_ready = FALSE
/// Used for setting tgui data - Whether JOKER algorithm is ready
var/is_joker_ready = FALSE
/// Used for setting tgui data - Whether injectors are ready to be printed
var/is_injector_ready = FALSE
/// Used for setting tgui data - Is CRISPR ready?
var/is_crispr_ready = FALSE
/// Used for setting tgui data - Wheher an enzyme pulse operation is ongoing
var/is_pulsing = FALSE
/// Used for setting tgui data - Time until scramble is ready
var/time_to_scramble = 0
/// Used for setting tgui data - Time until joker is ready
var/time_to_joker = 0
/// Used for setting tgui data - Time until injectors are ready
var/time_to_injector = 0
/// Used for setting tgui data - Time until the enzyme pulse is complete
var/time_to_pulse = 0
/// Currently connected DNA Scanner
var/obj/machinery/dna_scannernew/connected_scanner = null
/// Current DNA Scanner occupant
var/mob/living/carbon/scanner_occupant = null
/// Used for setting tgui data - List of occupant mutations
var/list/tgui_occupant_mutations = list()
/// Used for setting tgui data - List of DNA Console stored mutations
var/list/tgui_console_mutations = list()
/// Used for setting tgui data - List of diskette stored mutations
var/list/tgui_diskette_mutations = list()
/// Used for setting tgui data - List of DNA Console chromosomes
var/list/tgui_console_chromosomes = list()
/// Used for setting tgui data - List of occupant mutations
var/list/tgui_genetic_makeup = list()
/// Used for setting tgui data - List of occupant mutations
var/list/tgui_advinjector_mutations = list()
/// State of tgui view, i.e. which tab is currently active, or which genome we're currently looking at.
var/list/list/tgui_view_state = list()
///Counter for CRISPR charges
var/crispr_charges = 0
/obj/machinery/computer/scan_consolenew/process()
. = ..()
// This is for pulsing the UI element with genetic damage as part of genetic makeup
// If genetic_damage_pulse_index > 0 then it means we're attempting a pulse
if((genetic_damage_pulse_index > 0) && (genetic_damage_pulse_timer <= world.time) && (genetic_damage_pulse_type == GENETIC_DAMAGE_PULSE_UNIQUE_IDENTITY || genetic_damage_pulse_type == GENETIC_DAMAGE_PULSE_UNIQUE_FEATURES))
genetic_damage_pulse()
return
/obj/machinery/computer/scan_consolenew/attackby(obj/item/I, mob/user, params)
// Store chromosomes in the console if there's room
if (istype(I, /obj/item/chromosome))
I.forceMove(src)
stored_chromosomes += I
to_chat(user, span_notice("You insert [I]."))
return
// Insert data disk if console disk slot is empty
// Swap data disk if there is one already a disk in the console
if (istype(I, /obj/item/disk/data)) //INSERT SOME DISKETTES
// Insert disk into DNA Console
if (!user.transferItemToLoc(I,src))
return
// If insertion was successful and there's already a diskette in the console, eject the old one.
if(diskette)
eject_disk(user)
// Set the new diskette.
diskette = I
to_chat(user, span_notice("You insert [I]."))
return
// Recycle non-activator used injectors
// Turn activator used injectors (aka research injectors) to chromosomes
if(istype(I, /obj/item/dnainjector/activator))
var/obj/item/dnainjector/activator/A = I
if(A.used)
to_chat(user,span_notice("Recycled [I]."))
if(A.research && A.filled)
if(prob(60))
var/c_typepath = generate_chromosome()
var/obj/item/chromosome/CM = new c_typepath (src)
stored_chromosomes += CM
to_chat(user,span_notice("[capitalize(CM.name)] added to storage."))
else
to_chat(user, span_notice("There was not enough genetic data to extract a viable chromosome."))
if(A.crispr_charge)
crispr_charges++
qdel(I)
return
return ..()
/obj/machinery/computer/scan_consolenew/AltClick(mob/user)
// Make sure the user can interact with the machine.
. = ..()
if(!can_interact(user))
return
if(!user.canUseTopic(src, !issilicon(user)))
return
eject_disk(user)
/obj/machinery/computer/scan_consolenew/Initialize(mapload)
. = ..()
// Connect with a nearby DNA Scanner on init
connect_to_scanner()
// Set appropriate ready timers and limits for machines functions
injectorready = world.time + INJECTOR_TIMEOUT
scrambleready = world.time + SCRAMBLE_TIMEOUT
jokerready = world.time + JOKER_TIMEOUT
COOLDOWN_START(src, enzyme_copy_timer, ENZYME_COPY_BASE_COOLDOWN)
// Set the default tgui state
set_default_state()
// Link machine with research techweb. Used for discovering and accessing
// already discovered mutations
stored_research = SSresearch.science_tech
/obj/machinery/computer/scan_consolenew/ui_interact(mob/user, datum/tgui/ui)
// Most of ui_interact is spent setting variables for passing to the tgui
// interface.
// We can also do some general state processing here too as it's a good
// indication that a player is using the console.
var/scanner_op = scanner_operational()
var/can_modify_occ = can_modify_occupant()
// Check for connected AND operational scanner.
if(scanner_op)
can_use_scanner = TRUE
else
can_use_scanner = FALSE
set_connected_scanner(null)
is_viable_occupant = FALSE
// Check for a viable occupant in the scanner.
if(can_modify_occ)
is_viable_occupant = TRUE
else
is_viable_occupant = FALSE
// Populates various buffers for passing to tgui
build_mutation_list(can_modify_occ)
build_genetic_makeup_list()
// Populate variables for passing to tgui interface
is_scramble_ready = (scrambleready < world.time)
time_to_scramble = round((scrambleready - world.time)/10)
is_joker_ready = (jokerready < world.time)
time_to_joker = round((jokerready - world.time)/10)
is_injector_ready = (injectorready < world.time)
time_to_injector = round((injectorready - world.time)/10)
is_pulsing = ((genetic_damage_pulse_index > 0) && (genetic_damage_pulse_timer > world.time))
time_to_pulse = round((genetic_damage_pulse_timer - world.time)/10)
is_crispr_ready = (crispr_charges > 0)
// Attempt to update tgui ui, open and update if needed.
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "DnaConsole")
ui.open()
/obj/machinery/computer/scan_consolenew/ui_assets()
. = ..() || list()
. += get_asset_datum(/datum/asset/simple/genetics)
/obj/machinery/computer/scan_consolenew/ui_data(mob/user)
var/list/data = list()
data["view"] = tgui_view_state
data["storage"] = list()
// This block of code generates the huge data structure passed to the tgui
// interface for displaying all the various bits of console/scanner data
// Should all be very self-explanatory
data["isScannerConnected"] = can_use_scanner
if(can_use_scanner)
data["scannerOpen"] = connected_scanner.state_open
data["scannerLocked"] = connected_scanner.locked
data["pulseStrength"] = pulse_strength
data["pulseDuration"] = pulse_duration
data["stdDevStr"] = pulse_strength * GENETIC_DAMAGE_STRENGTH_MULTIPLIER
switch(GENETIC_DAMAGE_ACCURACY_MULTIPLIER / (pulse_duration + (connected_scanner.precision_coeff ** 2))) //hardcoded values from a z-table for a normal distribution
if(0 to 0.25)
data["stdDevAcc"] = ">95 %"
if(0.25 to 0.5)
data["stdDevAcc"] = "68-95 %"
if(0.5 to 0.75)
data["stdDevAcc"] = "55-68 %"
else
data["stdDevAcc"] = "<38 %"
data["isViableSubject"] = is_viable_occupant
if(is_viable_occupant)
data["subjectName"] = scanner_occupant.name
if(scanner_occupant.transformation_timer)
data["subjectStatus"] = STATUS_TRANSFORMING
else
data["subjectStatus"] = scanner_occupant.stat
data["subjectHealth"] = scanner_occupant.health
data["subjectEnzymes"] = scanner_occupant.dna.unique_enzymes
data["isMonkey"] = ismonkey(scanner_occupant)
data["subjectUNI"] = scanner_occupant.dna.unique_identity
data["subjectUF"] = scanner_occupant.dna.unique_features
data["storage"]["occupant"] = tgui_occupant_mutations
var/datum/component/genetic_damage/genetic_damage = scanner_occupant.GetComponent(/datum/component/genetic_damage)
data["subjectDamage"] = genetic_damage ? round((genetic_damage.total_damage / genetic_damage.minimum_before_damage) * 100, 0.1) : 0
else
data["subjectName"] = null
data["subjectStatus"] = null
data["subjectHealth"] = null
data["subjectDamage"] = null
data["subjectEnzymes"] = null
data["storage"]["occupant"] = null
data["hasDelayedAction"] = (delayed_action != null)
data["isScrambleReady"] = is_scramble_ready
data["isJokerReady"] = is_joker_ready
data["isInjectorReady"] = is_injector_ready
data["isCrisprReady"] = is_crispr_ready
data["crisprCharges"] = crispr_charges
data["scrambleSeconds"] = time_to_scramble
data["jokerSeconds"] = time_to_joker
data["injectorSeconds"] = time_to_injector
data["isPulsing"] = is_pulsing
data["timeToPulse"] = time_to_pulse
data["geneticMakeupCooldown"] = COOLDOWN_TIMELEFT(src, enzyme_copy_timer) / 10
if(diskette != null)
data["hasDisk"] = TRUE
data["diskCapacity"] = diskette.max_mutations - LAZYLEN(diskette.mutations)
data["diskReadOnly"] = diskette.read_only
//data["diskMutations"] = tgui_diskette_mutations
data["storage"]["disk"] = tgui_diskette_mutations
data["diskHasMakeup"] = (LAZYLEN(diskette.genetic_makeup_buffer) > 0)
data["diskMakeupBuffer"] = diskette.genetic_makeup_buffer.Copy()
else
data["hasDisk"] = FALSE
data["diskCapacity"] = 0
data["diskReadOnly"] = TRUE
//data["diskMutations"] = null
data["storage"]["disk"] = null
data["diskHasMakeup"] = FALSE
data["diskMakeupBuffer"] = null
//data["mutationStorage"] = tgui_console_mutations
data["storage"]["console"] = tgui_console_mutations
data["chromoStorage"] = tgui_console_chromosomes
data["makeupCapacity"] = NUMBER_OF_BUFFERS
data["makeupStorage"] = tgui_genetic_makeup
//data["advInjectors"] = tgui_advinjector_mutations
data["storage"]["injector"] = tgui_advinjector_mutations
data["maxAdvInjectors"] = max_injector_selections
return data
/obj/machinery/computer/scan_consolenew/ui_act(action, list/params)
. = ..()
if(.)
return
. = TRUE
add_fingerprint(usr)
usr.set_machine(src)
switch(action)
// Connect this DNA Console to a nearby DNA Scanner
// Usually only activate as an option if there is no connected scanner
if("connect_scanner")
connect_to_scanner()
return
// Toggle the door open/closed status on attached DNA Scanner
if("toggle_door")
// GUARD CHECK - Scanner still connected and operational?
if(!scanner_operational())
return
connected_scanner.toggle_open(usr)
return
// Toggle the door bolts on the attached DNA Scanner
if("toggle_lock")
// GUARD CHECK - Scanner still connected and operational?
if(!scanner_operational())
return
connected_scanner.locked = !connected_scanner.locked
return
// Scramble scanner occupant's DNA
if("scramble_dna")
// GUARD CHECK - Can we genetically modify the occupant? Includes scanner
// operational guard checks.
// GUARD CHECK - Is scramble DNA actually ready?
if(!can_modify_occupant() || !(scrambleready < world.time))
return
scanner_occupant.dna.remove_all_mutations(list(MUT_NORMAL, MUT_EXTRA))
scanner_occupant.dna.generate_dna_blocks()
scrambleready = world.time + SCRAMBLE_TIMEOUT
to_chat(usr,span_notice("DNA scrambled."))
scanner_occupant.AddComponent(/datum/component/genetic_damage, GENETIC_DAMAGE_STRENGTH_MULTIPLIER*50/(connected_scanner.damage_coeff ** 2))
return
// Check whether a specific mutation is eligible for discovery within the
// scanner occupant
// This is additionally done when a mutation's tab is selected in the tgui
// interface. This is because some mutations, such as Monkified on monkeys,
// are infact completed by default but not yet discovered. Likewise, all
// mutations can have their sequence completed while Monkified is still an
// active mutation and thus won't immediately be discovered but could be
// discovered when Monkified is removed
// ---------------------------------------------------------------------- //
// params["alias"] - Alias of a mutation. The alias is the "hidden" name of
// the mutation, for example "Mutation 5" or "Mutation 33"
if("check_discovery")
// GUARD CHECK - Can we genetically modify the occupant? Includes scanner
// operational guard checks.
if(!can_modify_occupant())
return
// GUARD CHECK - Have we somehow cheekily swapped occupants? This is
// unexpected.
if(!(scanner_occupant == connected_scanner.occupant))
return
check_discovery(params["alias"])
return
// Check all mutations of the occupant and check if any are discovered.
// This is called when the Genetic Sequencer is selected. It'll do things
// like immediately discover Monkified without needing to click through
// the mutation tabs and handle cases where mutations are solved but not
// discovered due to the Monkified mutation being active then removed.
if("all_check_discovery")
// GUARD CHECK - Can we genetically modify the occupant? Includes scanner
// operational guard checks.
if(!can_modify_occupant())
return
// GUARD CHECK - Have we somehow cheekily swapped occupants? This is
// unexpected.
if(!(scanner_occupant == connected_scanner.occupant))
return
// Go over all standard mutations and check if they've been discovered.
for(var/mutation_type in scanner_occupant.dna.mutation_index)
var/datum/mutation/human/HM = GET_INITIALIZED_MUTATION(mutation_type)
check_discovery(HM.alias)
return
// Set a gene in a mutation's genetic sequence. Will also check for mutations
// discovery as part of the process.
// ---------------------------------------------------------------------- //
// params["alias"] - Alias of a mutation. The alias is the "hidden" name of
// the mutation, for example "Mutation 5" or "Mutation 33"
// params["gene"] - The letter of the new gene
// params["pos"] - The BYOND index of the letter in the gene sequence to be
// changed. Expects a text string from TGUI and will convert to a number
if("pulse_gene")
// GUARD CHECK - Can we genetically modify the occupant? Includes scanner
// operational guard checks.
if(!can_modify_occupant())
return
// GUARD CHECK - Have we somehow cheekily swapped occupants? This is
// unexpected.
if(!(scanner_occupant == connected_scanner.occupant))
return
// GUARD CHECK - Is the occupant currently undergoing some form of
// transformation? If so, we don't want to be pulsing genes.
if(scanner_occupant.transformation_timer)
to_chat(usr,span_warning("Gene pulse failed: The scanner occupant undergoing a transformation."))
return
// Resolve mutation's BYOND path from the alias
var/alias = params["alias"]
var/path = GET_MUTATION_TYPE_FROM_ALIAS(alias)
// Make sure the occupant still has this mutation
if(!(path in scanner_occupant.dna.mutation_index))
return
// Resolve BYOND path to genome sequence of scanner occupant
var/sequence = GET_GENE_STRING(path, scanner_occupant.dna)
var/newgene = params["gene"]
var/genepos = text2num(params["pos"])
// If the new gene is J, this means we're dealing with a JOKER
// GUARD CHECK - Is JOKER actually ready?
if((newgene == "J") && (jokerready < world.time))
var/truegenes = GET_SEQUENCE(path)
newgene = truegenes[genepos]
jokerready = world.time + JOKER_TIMEOUT - (JOKER_UPGRADE * (connected_scanner.precision_coeff-1))
// If the gene is an X, we want to update the default genes with the new
// X to allow highlighting logic to work on the tgui interface.
if(newgene == "X")
var/defaultseq = scanner_occupant.dna.default_mutation_genes[path]
defaultseq = copytext(defaultseq, 1, genepos) + newgene + copytext(defaultseq, genepos + 1)
scanner_occupant.dna.default_mutation_genes[path] = defaultseq
// Copy genome to scanner occupant and do some basic mutation checks as
// we've increased the occupant genetic damage
sequence = copytext(sequence, 1, genepos) + newgene + copytext(sequence, genepos + 1)
scanner_occupant.dna.mutation_index[path] = sequence
scanner_occupant.AddComponent(/datum/component/genetic_damage, GENETIC_DAMAGE_STRENGTH_MULTIPLIER/connected_scanner.damage_coeff)
scanner_occupant.domutcheck()
// GUARD CHECK - Modifying genetics can lead to edge cases where the
// scanner occupant is qdel'd and replaced with a different entity.
// Examples of this include adding/removing the Monkified mutation which
// qdels the previous entity and creates a brand new one in its place.
// We should redo all of our occupant modification checks again, although
// it is less than ideal.
if(!can_modify_occupant())
return
// Check if we cracked a mutation
check_discovery(alias)
return
// Apply a chromosome to a specific mutation.
// ---------------------------------------------------------------------- //
// params["mutref"] - ATOM Ref of specific mutation to apply the chromo to
// params["chromo"] - Name of the chromosome to apply to the mutation
if("apply_chromo")
// GUARD CHECK - Can we genetically modify the occupant? Includes scanner
// operational guard checks.
if(!can_modify_occupant())
return
// GUARD CHECK - Have we somehow cheekily swapped occupants? This is
// unexpected.
if(!(scanner_occupant == connected_scanner.occupant))
return
var/bref = params["mutref"]
// GUARD CHECK - Only search occupant for this specific ref, since your
// can only apply chromosomes to mutations occupants.
var/datum/mutation/human/HM = get_mut_by_ref(bref, SEARCH_OCCUPANT)
// GUARD CHECK - This should not be possible. Unexpected result
if(!HM)
return
// Look through our stored chromos and compare names to find a
// stored chromo we can apply.
for(var/obj/item/chromosome/CM in stored_chromosomes)
if(CM.can_apply(HM) && (CM.name == params["chromo"]))
stored_chromosomes -= CM
CM.apply(HM)
return
// Attempt overwriting Base DNA : The pairs are instead the top row vs the top row of the new code.
// So AA means the AT pair stays the same, AT means AT becomes TA. This requires both knowing the
// solved full DNA of the subject mutation and the full DNA of the replacement genes. Applies probable disease
// of probable strengths as well. If you mess it up, you might end up getting undesirable genes, including
// unstable DNA. This could lead to permanent monkey. When you get it right, some will be swapped out, on a
// probability scale.
// ---------------------------------------------------------------------- //
// params["mutref"] - ATOM Ref of specific mutation to swap out
// params["source"] - The source the request came from.
// Expected results:
// "occupant" - From genetic sequencer
// "console" - From DNA Console storage
// "disk" - From inserted diskette
if("crispr")
// GUARD CHECK - Can we genetically modify the occupant? Includes scanner
// operational guard checks.
if(!can_modify_occupant())
return
// GUARD CHECK - Have we somehow cheekily swapped occupants? This is
// unexpected.
if(scanner_occupant != connected_scanner.occupant)
return
//GUARD CHECK
//Make sure there's charges available.
if(crispr_charges < 1)
return
var/search_flags = 0
// Only continue if applying to occupant - all replacements in-vitro.
switch(params["source"])
if("occupant")
if(can_modify_occupant())
search_flags |= SEARCH_OCCUPANT
if("console")
search_flags |= SEARCH_STORED
return
if("disk")
search_flags |= SEARCH_DISKETTE
return
//Currently selected mutation
var/bref = params["mutref"]
//Valid gene-pairs
var/at_str = "AT"
var/cg_str = "CG"
// GUARD CHECK - Only search occupant for this specific ref, since you
// can only CRISPR existing mutations in a target
var/datum/mutation/human/target_mutation = get_mut_by_ref(bref, search_flags)
// Prompt for modifier string
var/new_sequence_input = input(usr, "Enter replacement sequence (or nothing to cancel)", "Replace inherent gene","")
// Drop out if the string is the wrong length
if(length(new_sequence_input) != 32)
return
//Generate the original and new gene sequences from the CRISPR string
//vars to hold the 2 sequences
var/old_sequence
var/new_sequence
//Unzip the modification string
for(var/i = 1 to length(new_sequence_input))
var/char = new_sequence_input[i]
var/pair_str
var/new_pair
//figure out which pair type the character belongs to
pair_str = ((at_str[1] == char || at_str[2] == char) ? at_str : ((cg_str[1] == char || cg_str[2] == char) ? cg_str : null))
//Valid pair from character
new_pair = (pair_str ? char + (pair_str[1]==char?pair_str[2]:pair_str[1]) : null)
// every second letter in the sequence represents a valid pair of the new sequence, otherwise it belongs to old
if(new_pair)
if(i%2==0)
new_sequence+=new_pair
else
old_sequence+=new_pair
else
return //drop out, no pair
//decrement CRISPR charge
crispr_charges--
//Apply sequence
if(new_sequence)
//to hold the found mutation, if found
var/datum/mutation/human/matched_mutation = null
//Go through all sequences for matching gene, and set the mutation
for (var/M in subtypesof(/datum/mutation/human))
var/true_sequence = GET_SEQUENCE(M)
if (new_sequence == true_sequence)
matched_mutation = M
//First check is for the more-likely, weaker random virus. Second is for a tougher one. There's a chance both checks fail and you get nothing.
//This change was to bring it more in line with what I originally imagined, that the virus risk was from the virus misbehaving somehow - it
//should be a "sometimes" thing, not an "always" thing, but risky enough to force the need for precautions to isolate the subject
if(prob(60))
var/datum/disease/advance/random/random_disease = new /datum/disease/advance/random(2,2)
random_disease.try_infect(scanner_occupant, FALSE)
else if (prob(30))
var/datum/disease/advance/random/random_disease = new /datum/disease/advance/random(3,4)
random_disease.try_infect(scanner_occupant, FALSE)
//Instantiate list to hold resulting mutation_index
var/mutation_data[0]
//Start with the bad mutation, overwrite with the desired mutation if it passes the check
//assures BAD END is the natural state if things go wrong
//I think this should be like with viruses, probability cascade or switch/case on random?
var/result_mutation = ACIDFLESH
//If we found the replacement mutation
if(matched_mutation)
//and the old sequence matches the real sequence of the old mutation
if(old_sequence == GET_SEQUENCE(target_mutation.type))
//Set the replacement mutation to the desired mutation
result_mutation = matched_mutation
//Remove the current active mutations - let's say doing this triggers DNA repair or something
//This is admittedly because I couldn't figure out how to only remove the targeted mutation
//Not touching MUT_EXTRA will hopefully leave the added mutations alone
scanner_occupant.dna.remove_all_mutations(list(MUT_NORMAL))
//Add the resulting mutation to the active mutations
scanner_occupant.dna.add_mutation(result_mutation,MUT_NORMAL, 0)
//Rebuild the mutation_index into mutation_data, replacing the sequence entry with the solved
//entry for the result mutation
for(var/mutation_type in scanner_occupant.dna.mutation_index)
if(mutation_type == target_mutation.type)
mutation_data[result_mutation] = new_sequence
else
mutation_data[mutation_type]=scanner_occupant.dna.mutation_index[mutation_type]
//Overwrite the mutation_index list with the rebuild mutation_data
scanner_occupant.dna.mutation_index = mutation_data
//Not sure what this does but it seems to be a sanity check and this needs a sanity check
scanner_occupant.domutcheck()
return
// Print any type of standard injector, limited right now to activators that
// activate a dormant mutation and mutators that forcibly create a new
// MUT_EXTRA mutation
// ---------------------------------------------------------------------- //
// params["mutref"] - ATOM Ref of specific mutation to create an injector of
// params["is_activator"] - Is this an "Activator" style injector, also
// referred to as a "Research" type. Expects a string with 0 or 1, which
// then gets converted to a number.
// params["source"] - The source the request came from.
// Expected results:
// "occupant" - From genetic sequencer
// "console" - From DNA Console storage
// "disk" - From inserted diskette
if("print_injector")
// Because printing mutators and activators share a bunch of code,
// it makes sense to keep them both together and set unique vars
// later in the code
// As a side note, because mutations can contain unique metadata,
// this system uses BYOND Atom Refs to safely and accurately
// identify mutations from big ol' lists
// GUARD CHECK - Is the injector actually ready?
if(world.time < injectorready)
return
var/search_flags = 0
switch(params["source"])
if("occupant")
// GUARD CHECK - Make sure we can modify the occupant before we
// attempt to search them for any given mutation refs. This could
// lead to no search flags being passed to get_mut_by_ref and this
// is intended functionality to prevent any cheese or abuse
if(can_modify_occupant())
search_flags |= SEARCH_OCCUPANT
if("console")
search_flags |= SEARCH_STORED
if("disk")
search_flags |= SEARCH_DISKETTE
var/bref = params["mutref"]
var/datum/mutation/human/HM = get_mut_by_ref(bref, search_flags)
// GUARD CHECK - This should not be possible. Unexpected result
if(!HM)
return
// Create a new DNA Injector and add the appropriate mutations to it
var/obj/item/dnainjector/activator/I = new /obj/item/dnainjector/activator(loc)
I.add_mutations += new HM.type(copymut = HM)
var/is_activator = text2num(params["is_activator"])
// Activators are also called "research" injectors and are used to create
// chromosomes by recycling at the DNA Console
if(is_activator)
I.name = "[HM.name] activator"
I.research = TRUE
// If there's an operational connected scanner, we can use its upgrades
// to improve our injector's genetic damage generation
if(scanner_operational())
I.damage_coeff = connected_scanner.damage_coeff*4
injectorready = world.time + INJECTOR_TIMEOUT * (1 - 0.1 * connected_scanner.precision_coeff)
else
injectorready = world.time + INJECTOR_TIMEOUT
else
I.name = "[HM.name] mutator"
I.doitanyway = TRUE
// If there's an operational connected scanner, we can use its upgrades
// to improve our injector's genetic damage generation
if(scanner_operational())
I.damage_coeff = connected_scanner.damage_coeff
injectorready = world.time + INJECTOR_TIMEOUT * 5 * (1 - 0.1 * connected_scanner.precision_coeff)
else
injectorready = world.time + INJECTOR_TIMEOUT * 5
return
// Save a mutation to the console's storage buffer.
// ---------------------------------------------------------------------- //
// params["mutref"] - ATOM Ref of specific mutation to store
// params["source"] - The source the request came from.
// Expected results:
// "occupant" - From genetic sequencer
// "disk" - From inserted diskette
if("save_console")
var/search_flags = 0
switch(params["source"])
if("occupant")
// GUARD CHECK - Make sure we can modify the occupant before we
// attempt to search them for any given mutation refs. This could
// lead to no search flags being passed to get_mut_by_ref and this
// is intended functionality to prevent any cheese or abuse
if(can_modify_occupant())
search_flags |= SEARCH_OCCUPANT
if("disk")
search_flags |= SEARCH_DISKETTE
var/bref = params["mutref"]
var/datum/mutation/human/HM = get_mut_by_ref(bref, search_flags)
// GUARD CHECK - This should not be possible. Unexpected result
if(!HM)
return
// Saving temporary or unobtainable mutations leads to gratuitous abuse
if(HM.class == MUT_OTHER)
say("ERROR: This mutation is anomalous, and cannot be saved.")
return
var/datum/mutation/human/A = new HM.type(MUT_EXTRA, null, HM)
stored_mutations += A
to_chat(usr,span_notice("Mutation successfully stored."))
return
// Save a mutation to the diskette's storage buffer.
// ---------------------------------------------------------------------- //
// params["mutref"] - ATOM Ref of specific mutation to store
// params["source"] - The source the request came from
// Expected results:
// "occupant" - From genetic sequencer
// "console" - From DNA Console storage
if("save_disk")
// GUARD CHECK - This code shouldn't even be callable without a diskette
// inserted. Unexpected result
if(!diskette)
return
// GUARD CHECK - Make sure the disk is not full
if(LAZYLEN(diskette.mutations) >= diskette.max_mutations)
to_chat(usr,span_warning("Disk storage is full."))
return
// GUARD CHECK - Make sure the disk isn't set to read only, as we're
// attempting to write to it
if(diskette.read_only)
to_chat(usr,span_warning("Disk is set to read only mode."))
return
var/search_flags = 0
switch(params["source"])
if("occupant")
// GUARD CHECK - Make sure we can modify the occupant before we
// attempt to search them for any given mutation refs. This could
// lead to no search flags being passed to get_mut_by_ref and this
// is intended functionality to prevent any cheese or abuse
if(can_modify_occupant())
search_flags |= SEARCH_OCCUPANT
if("console")
search_flags |= SEARCH_STORED
var/bref = params["mutref"]
var/datum/mutation/human/HM = get_mut_by_ref(bref, search_flags)
// GUARD CHECK - This should not be possible. Unexpected result
if(!HM)
return
var/datum/mutation/human/A = new HM.type(MUT_EXTRA, null, HM)
diskette.mutations += A
to_chat(usr,span_notice("Mutation successfully stored to disk."))
return
// Completely removes a MUT_EXTRA mutation or mutation with corrupt gene
// sequence from the scanner occupant
// ---------------------------------------------------------------------- //
// params["mutref"] - ATOM Ref of specific mutation to nullify
if("nullify")
// GUARD CHECK - Can we genetically modify the occupant? Includes scanner
// operational guard checks.
if(!can_modify_occupant())
return
var/bref = params["mutref"]
var/datum/mutation/human/HM = get_mut_by_ref(bref, SEARCH_OCCUPANT)
// GUARD CHECK - This should not be possible. Unexpected result
if(!HM)
return
// GUARD CHECK - Nullify should only be used on scrambled or "extra"
// mutations.
if(!HM.scrambled && !(HM.class == MUT_EXTRA))
return
scanner_occupant.dna.remove_mutation(HM.type)
return
// Deletes saved mutation from console buffer.
// ---------------------------------------------------------------------- //
// params["mutref"] - ATOM Ref of specific mutation to delete
if("delete_console_mut")
var/bref = params["mutref"]
var/datum/mutation/human/HM = get_mut_by_ref(bref, SEARCH_STORED)
if(HM)
stored_mutations.Remove(HM)
qdel(HM)
return
// Deletes saved mutation from disk buffer.
// ---------------------------------------------------------------------- //
// params["mutref"] - ATOM Ref of specific mutation to delete
if("delete_disk_mut")
// GUARD CHECK - This code shouldn't even be callable without a diskette
// inserted. Unexpected result
if(!diskette)
return
// GUARD CHECK - Make sure the disk isn't set to read only, as we're
// attempting to write to it (via deletion)
if(diskette.read_only)
to_chat(usr,span_warning("Disk is set to read only mode."))
return
var/bref = params["mutref"]
var/datum/mutation/human/HM = get_mut_by_ref(bref, SEARCH_DISKETTE)
if(HM)
diskette.mutations.Remove(HM)
qdel(HM)
return
// Ejects a stored chromosome from the DNA Console
// ---------------------------------------------------------------------- //
// params["chromo"] - Text string of the chromosome name
if("eject_chromo")
var/chromname = params["chromo"]
for(var/obj/item/chromosome/CM in stored_chromosomes)
if(chromname == CM.name)
CM.forceMove(drop_location())
adjust_item_drop_location(CM)
stored_chromosomes -= CM
return
return
// Combines two mutations from the console to try and create a new mutation
// ---------------------------------------------------------------------- //
// params["firstref"] - ATOM Ref of first mutation for combination
// params["secondref"] - ATOM Ref of second mutation for combination
// mutation
if("combine_console")
// GUARD CHECK - We're running a research-type operation. If, for some
// reason, somehow the DNA Console has been disconnected from the research
// network - Or was never in it to begin with - don't proceed
if(!stored_research)
return
var/first_bref = params["firstref"]
var/second_bref = params["secondref"]
// GUARD CHECK - Find the source and destination mutations on the console
// and make sure they actually exist.
var/datum/mutation/human/source_mut = get_mut_by_ref(first_bref, SEARCH_STORED | SEARCH_DISKETTE)
if(!source_mut)
return
var/datum/mutation/human/dest_mut = get_mut_by_ref(second_bref, SEARCH_STORED | SEARCH_DISKETTE)
if(!dest_mut)
return
// Attempt to mix the two mutations to get a new type
var/result_path = get_mixed_mutation(source_mut.type, dest_mut.type)
if(!result_path)
return
// If we got a new type, add it to our storage
stored_mutations += new result_path()
to_chat(usr, span_boldnotice("Success! New mutation has been added to console storage."))
// If it's already discovered, end here. Otherwise, add it to the list of
// discovered mutations.
// We've already checked for stored_research earlier
if(result_path in stored_research.discovered_mutations)
return
var/datum/mutation/human/HM = GET_INITIALIZED_MUTATION(result_path)
stored_research.discovered_mutations += result_path
say("Successfully mutated [HM.name].")
return
// Combines two mutations from the disk to try and create a new mutation
// ---------------------------------------------------------------------- //
// params["firstref"] - ATOM Ref of first mutation for combination
// params["secondref"] - ATOM Ref of second mutation for combination
// mutation
if("combine_disk")
// GUARD CHECK - This code shouldn't even be callable without a diskette
// inserted. Unexpected result
if(!diskette)
return
// GUARD CHECK - Make sure the disk is not full.
if(LAZYLEN(diskette.mutations) >= diskette.max_mutations)
to_chat(usr,span_warning("Disk storage is full."))
return
// GUARD CHECK - Make sure the disk isn't set to read only, as we're
// attempting to write to it
if(diskette.read_only)
to_chat(usr,span_warning("Disk is set to read only mode."))
return
// GUARD CHECK - We're running a research-type operation. If, for some
// reason, somehow the DNA Console has been disconnected from the research
// network - Or was never in it to begin with - don't proceed
if(!stored_research)
return
var/first_bref = params["firstref"]
var/second_bref = params["secondref"]
// GUARD CHECK - Find the source and destination mutations on the console
// and make sure they actually exist.
var/datum/mutation/human/source_mut = get_mut_by_ref(first_bref, SEARCH_STORED | SEARCH_DISKETTE)
if(!source_mut)
return
var/datum/mutation/human/dest_mut = get_mut_by_ref(second_bref, SEARCH_STORED | SEARCH_DISKETTE)
if(!dest_mut)
return
// Attempt to mix the two mutations to get a new type
var/result_path = get_mixed_mutation(source_mut.type, dest_mut.type)
if(!result_path)
return
// If we got a new type, add it to our storage
diskette.mutations += new result_path()
to_chat(usr, span_boldnotice("Success! New mutation has been added to the disk."))
// If it's already discovered, end here. Otherwise, add it to the list of
// discovered mutations
// We've already checked for stored_research earlier
if(result_path in stored_research.discovered_mutations)
return
var/datum/mutation/human/HM = GET_INITIALIZED_MUTATION(result_path)
stored_research.discovered_mutations += result_path
say("Successfully mutated [HM.name].")
return
// Sets the Genetic Makeup pulse strength.
// ---------------------------------------------------------------------- //
// params["val"] - New strength value as text string, converted to number
// later on in code
if("set_pulse_strength")
var/value = round(text2num(params["val"]))
pulse_strength = WRAP(value, 1, GENETIC_DAMAGE_STRENGTH_MAX+1)
return
// Sets the Genetic Makeup pulse duration
// ---------------------------------------------------------------------- //
// params["val"] - New strength value as text string, converted to number
// later on in code
if("set_pulse_duration")
var/value = round(text2num(params["val"]))
pulse_duration = WRAP(value, 1, GENETIC_DAMAGE_DURATION_MAX+1)
return
// Saves Genetic Makeup information to disk
// ---------------------------------------------------------------------- //
// params["index"] - The BYOND index of the console genetic makeup buffer to
// copy to disk
if("save_makeup_disk")
// GUARD CHECK - This code shouldn't even be callable without a diskette
// inserted. Unexpected result
if(!diskette)
return
// GUARD CHECK - Make sure the disk isn't set to read only, as we're
// attempting to write to it
if(diskette.read_only)
to_chat(usr,span_warning("Disk is set to read only mode."))
return
// Convert the index to a number and clamp within the array range
var/buffer_index = text2num(params["index"])
buffer_index = clamp(buffer_index, 1, NUMBER_OF_BUFFERS)
var/list/buffer_slot = genetic_makeup_buffer[buffer_index]
// GUARD CHECK - This should not be possible to activate on a buffer slot
// that doesn't have any genetic data. Unexpected result
if(!istype(buffer_slot))
return
diskette.genetic_makeup_buffer = buffer_slot.Copy()
return
// Loads Genetic Makeup from disk to a console buffer
// ---------------------------------------------------------------------- //
// params["index"] - The BYOND index of the console genetic makeup buffer to
// copy to. Expected as text string, converted to number later
if("load_makeup_disk")
// GUARD CHECK - This code shouldn't even be callable without a diskette
// inserted. Unexpected result
if(!diskette)
return
// GUARD CHECK - This should not be possible to activate on a diskette
// that doesn't have any genetic data. Unexpected result
if(LAZYLEN(diskette.genetic_makeup_buffer) == 0)
return
// Convert the index to a number and clamp within the array range, then
// copy the data from the disk to that buffer
var/buffer_index = text2num(params["index"])
buffer_index = clamp(buffer_index, 1, NUMBER_OF_BUFFERS)
genetic_makeup_buffer[buffer_index] = diskette.genetic_makeup_buffer.Copy()
return
// Deletes genetic makeup buffer from the inserted diskette
if("del_makeup_disk")
// GUARD CHECK - This code shouldn't even be callable without a diskette
// inserted. Unexpected result
if(!diskette)
return
// GUARD CHECK - Make sure the disk isn't set to read only, as we're
// attempting to write (via deletion) to it
if(diskette.read_only)
to_chat(usr,span_warning("Disk is set to read only mode."))
return
diskette.genetic_makeup_buffer.Cut()
return
// Saves the scanner occupant's genetic makeup to a given console buffer
// ---------------------------------------------------------------------- //
// params["index"] - The BYOND index of the console genetic makeup buffer to
// save the new genetic data to. Expected as text string, converted to
// number later
if("save_makeup_console")
// GUARD CHECK - Can we genetically modify the occupant? Includes scanner
// operational guard checks.
if(!can_modify_occupant())
return
// Convert the index to a number and clamp within the array range, then
// copy the data from the disk to that buffer
var/buffer_index = text2num(params["index"])
buffer_index = clamp(buffer_index, 1, NUMBER_OF_BUFFERS)
// Set the new information
genetic_makeup_buffer[buffer_index] = list(
"label"="Slot [buffer_index]:[scanner_occupant.real_name]",
"UI"=scanner_occupant.dna.unique_identity,
"UE"=scanner_occupant.dna.unique_enzymes,
"UF"=scanner_occupant.dna.unique_features,
"name"=scanner_occupant.real_name,
"blood_type"=scanner_occupant.dna.blood_type)
return
// Deleted genetic makeup data from a console buffer slot
// ---------------------------------------------------------------------- //
// params["index"] - The BYOND index of the console genetic makeup buffer to
// delete the genetic data from. Expected as text string, converted to
// number later
if("del_makeup_console")
// Convert the index to a number and clamp within the array range, then
// copy the data from the disk to that buffer
var/buffer_index = text2num(params["index"])
buffer_index = clamp(buffer_index, 1, NUMBER_OF_BUFFERS)
var/list/buffer_slot = genetic_makeup_buffer[buffer_index]
// GUARD CHECK - This shouldn't be possible to execute this on a null
// buffer. Unexpected resut
if(!istype(buffer_slot))
return
genetic_makeup_buffer[buffer_index] = null
return
// Eject stored diskette from console
if("eject_disk")
eject_disk(usr)
return
// Create a Genetic Makeup injector. These injectors are timed and thus are
// only temporary
// ---------------------------------------------------------------------- //
// params["index"] - The BYOND index of the console genetic makeup buffer to
// create the makeup injector from. Expected as text string, converted to
// number later
// params["type"] - Type of injector to create
// Expected results:
// "ue" - Unique Enzyme, changes name and blood type
// "ui" - Unique Identity, changes looks
// "uf" - Unique Features, changes mutant bodyparts and mutcolors
// "mixed" - Combination of both ue and ui
if("makeup_injector")
if(!COOLDOWN_FINISHED(src, enzyme_copy_timer))
return
// Convert the index to a number and clamp within the array range, then
// copy the data from the disk to that buffer
var/buffer_index = text2num(params["index"])
buffer_index = clamp(buffer_index, 1, NUMBER_OF_BUFFERS)
var/list/buffer_slot = genetic_makeup_buffer[buffer_index]
// GUARD CHECK - This shouldn't be possible to execute this on a null
// buffer. Unexpected resut
if(!istype(buffer_slot))
return
var/type = params["type"]
var/obj/item/dnainjector/timed/I
switch(type)
if("ui")
// GUARD CHECK - There's currently no way to save partial genetic data.
// However, if this is the case, we can't make a complete injector and
// this catches that edge case
if(!buffer_slot["UI"])
to_chat(usr,span_warning("Genetic data corrupted, unable to create injector."))
return
I = new /obj/item/dnainjector/timed(loc)
I.fields = list("UI"=buffer_slot["UI"])
// If there is a connected scanner, we can use its upgrades to reduce
// the genetic damage generated by this injector
if(scanner_operational())
I.damage_coeff = connected_scanner.damage_coeff
if("ue")
// GUARD CHECK - There's currently no way to save partial genetic data.
// However, if this is the case, we can't make a complete injector and
// this catches that edge case
if(!buffer_slot["name"] || !buffer_slot["UE"] || !buffer_slot["blood_type"])
to_chat(usr,span_warning("Genetic data corrupted, unable to create injector."))
return
I = new /obj/item/dnainjector/timed(loc)
I.fields = list("name"=buffer_slot["name"], "UE"=buffer_slot["UE"], "blood_type"=buffer_slot["blood_type"])
// If there is a connected scanner, we can use its upgrades to reduce
// the genetic damage generated by this injector
if(scanner_operational())
I.damage_coeff = connected_scanner.damage_coeff
if("uf")
// GUARD CHECK - There's currently no way to save partial genetic data.
// However, if this is the case, we can't make a complete injector and
// this catches that edge case
if(!buffer_slot["name"] || !buffer_slot["UF"] || !buffer_slot["blood_type"])
to_chat(usr,"<span class='warning'>Genetic data corrupted, unable to create injector.</span>")
return
I = new /obj/item/dnainjector/timed(loc)
I.fields = list("name"=buffer_slot["name"], "UF"=buffer_slot["UF"])
// If there is a connected scanner, we can use its upgrades to reduce
// the genetic damage generated by this injector
if(scanner_operational())
I.damage_coeff = connected_scanner.damage_coeff
if("mixed")
// GUARD CHECK - There's currently no way to save partial genetic data.
// However, if this is the case, we can't make a complete injector and
// this catches that edge case
if(!buffer_slot["UI"] || !buffer_slot["name"] || !buffer_slot["UE"] || !buffer_slot["UF"] || !buffer_slot["blood_type"])
to_chat(usr,span_warning("Genetic data corrupted, unable to create injector."))
return
I = new /obj/item/dnainjector/timed(loc)
I.fields = list("UI"=buffer_slot["UI"],"name"=buffer_slot["name"], "UE"=buffer_slot["UE"], "UF"=buffer_slot["UF"], "blood_type"=buffer_slot["blood_type"])
// If there is a connected scanner, we can use its upgrades to reduce
// the genetic damage generated by this injector
if(scanner_operational())
I.damage_coeff = connected_scanner.damage_coeff
// If we successfully created an injector, don't forget to set the new
// ready timer.
if(I)
injectorready = world.time + INJECTOR_TIMEOUT
return
// Applies a genetic makeup buffer to the scanner occupant
// ---------------------------------------------------------------------- //
// params["index"] - The BYOND index of the console genetic makeup buffer to
// apply to the scanner occupant. Expected as text string, converted to
// number later
// params["type"] - Type of genetic makeup copy to implement
// Expected results:
// "ue" - Unique Enzyme, changes name and blood type
// "ui" - Unique Identity, changes looks
// "uf" - Unique Features, changes mutant bodyparts and mutcolors
// "mixed" - Combination of ue, ui, and uf
if("makeup_apply")
// GUARD CHECK - Can we genetically modify the occupant? Includes scanner
// operational guard checks.
if(!can_modify_occupant())
return
if(!COOLDOWN_FINISHED(src, enzyme_copy_timer))
return
// Convert the index to a number and clamp within the array range, then
// copy the data from the disk to that buffer
var/buffer_index = text2num(params["index"])
buffer_index = clamp(buffer_index, 1, NUMBER_OF_BUFFERS)
var/list/buffer_slot = genetic_makeup_buffer[buffer_index]
// GUARD CHECK - This shouldn't be possible to execute this on a null
// buffer. Unexpected resut
if(!istype(buffer_slot))
return
var/type = params["type"]
apply_genetic_makeup(type, buffer_slot)
return
// Applies a genetic makeup buffer to the next scanner occupant. This sets
// some code that will run when the connected DNA Scanner door is next
// closed
// This allows people to self-modify their genetic makeup, as tgui
// interfaces can not be accessed while inside the DNA Scanner and genetic
// makeup injectors are only temporary
// ---------------------------------------------------------------------- //
// params["index"] - The BYOND index of the console genetic makeup buffer to
// apply to the scanner occupant. Expected as text string, converted to
// number later
// params["type"] - Type of genetic makeup copy to implement
// Expected results:
// "ue" - Unique Enzyme, changes name and blood type
// "ui" - Unique Identity, changes looks
// "uf" - Unique Features, changes mutant bodyparts and mutcolors
// "mixed" - Combination of ue, ui, and uf
if("makeup_delay")
// Convert the index to a number and clamp within the array range, then
// copy the data from the disk to that buffer
var/buffer_index = text2num(params["index"])
buffer_index = clamp(buffer_index, 1, NUMBER_OF_BUFFERS)
var/list/buffer_slot = genetic_makeup_buffer[buffer_index]
// GUARD CHECK - This shouldn't be possible to execute this on a null
// buffer. Unexpected resut
if(!istype(buffer_slot))
return
var/type = params["type"]
// Set the delayed action. The next time the scanner door is closed,
// unless this is cancelled in the UI, the action will happen
delayed_action = list("type" = type, "buffer_slot" = buffer_slot)
return
// Attempts to modify the indexed element of the Unique Identity string
// This is a time delayed action that is handled in process()
// ---------------------------------------------------------------------- //
// params["type"] - Type of genetic makeup string to edit
// Expected results:
// "ui" - Unique Identity, changes looks
// "uf" - Unique Features, changes mutant bodyparts and mutcolors
// params["index"] - The BYOND index of the Unique Identity string to
// attempt to modify
if("makeup_pulse")
// GUARD CHECK - Can we genetically modify the occupant? Includes scanner
// operational guard checks.
if(!can_modify_occupant())
return
// Set the appropriate timer, string, and index to pulse. This is then managed
// later on in process()
var/type = params["type"]
genetic_damage_pulse_type = type
var/len
switch(type)
if("ui")
len = length(scanner_occupant.dna.unique_identity)
if("uf")
len = length(scanner_occupant.dna.unique_features)
genetic_damage_pulse_timer = world.time + (pulse_duration*10)
genetic_damage_pulse_index = WRAP(text2num(params["index"]), 1, len+1)
begin_processing()
return
// Cancels the delayed action - In this context it is not the genetic damage
// pulse from "makeup_pulse", which can not be cancelled. It is instead
// the delayed genetic transfer from "makeup_delay"
if("cancel_delay")
delayed_action = null
return
// Creates a new advanced injector storage buffer in the console
// ---------------------------------------------------------------------- //
// params["name"] - The name to apply to the new injector
if("new_adv_inj")
// GUARD CHECK - Make sure we can make a new injector. This code should
// not be called if we're already maxed out and this is an Unexpected
// result
if(!(LAZYLEN(injector_selection) < max_injector_selections))
return
// GUARD CHECK - Sanitise and trim the proposed name. This prevents HTML
// injection and equivalent as tgui input is not stripped
var/inj_name = params["name"]
inj_name = trim(sanitize(inj_name))
// GUARD CHECK - If the name is null or blank, or the name is already in
// the list of advanced injectors, we want to reject it as we can't have
// duplicate named advanced injectors
if(!inj_name || (inj_name in injector_selection))
return
injector_selection[inj_name] = list()
return
// Deleted an advanced injector storage buffer from the console
// ---------------------------------------------------------------------- //
// params["name"] - The name of the injector to delete
if("del_adv_inj")
var/inj_name = params["name"]
// GUARD CHECK - If the name is null or blank, reject.
// GUARD CHECK - If the name isn't in the list of advanced injectors, we
// want to reject this as it shouldn't be possible ever do this.
// Unexpected result
if(!inj_name || !(inj_name in injector_selection))
return
injector_selection.Remove(inj_name)
return
// Creates an injector from an advanced injector buffer
// ---------------------------------------------------------------------- //
// params["name"] - The name of the injector to print
if("print_adv_inj")
// As a side note, because mutations can contain unique metadata,
// this system uses BYOND Atom Refs to safely and accurately
// identify mutations from big ol' lists.
// GUARD CHECK - Is the injector actually ready?
if(world.time < injectorready)
return
var/inj_name = params["name"]
// GUARD CHECK - If the name is null or blank, reject.
// GUARD CHECK - If the name isn't in the list of advanced injectors, we
// want to reject this as it shouldn't be possible ever do this.
// Unexpected result
if(!inj_name || !(inj_name in injector_selection))
return
var/list/injector = injector_selection[inj_name]
var/obj/item/dnainjector/activator/I = new /obj/item/dnainjector/activator(loc)
// Run through each mutation in our Advanced Injector and add them to a
// new injector
for(var/A in injector)
var/datum/mutation/human/HM = A
I.add_mutations += new HM.type(copymut=HM)
// Force apply any mutations, this is functionality similar to mutators
I.doitanyway = TRUE
I.name = "Advanced [inj_name] injector"
// If there's an operational connected scanner, we can use its upgrades
// to improve our injector's genetic damage generation
if(scanner_operational())
I.damage_coeff = connected_scanner.damage_coeff
injectorready = world.time + INJECTOR_TIMEOUT * 8 * (1 - 0.1 * connected_scanner.precision_coeff)
else
injectorready = world.time + INJECTOR_TIMEOUT * 8
return
// Adds a mutation to an advanced injector
// ---------------------------------------------------------------------- //
// params["mutref"] - ATOM Ref of specific mutation to add to the injector
// params["advinj"] - Name of the advanced injector to add the mutation to
if("add_advinj_mut")
// GUARD CHECK - Can we genetically modify the occupant? Includes scanner
// operational guard checks.
// This is needed because this operation can only be completed from the
// genetic sequencer.
if(!can_modify_occupant())
return
var/adv_inj = params["advinj"]
// GUARD CHECK - Make sure our advanced injector actually exists. This
// should not be possible. Unexpected result
if(!(adv_inj in injector_selection))
return
// GUARD CHECK - Make sure we limit the number of mutations appropriately
if(LAZYLEN(injector_selection[adv_inj]) >= max_injector_mutations)
to_chat(usr,span_warning("Advanced injector mutation storage is full."))
return
var/mut_source = params["source"]
var/search_flag = 0
switch(mut_source)
if("disk")
search_flag = SEARCH_DISKETTE
if("occupant")
search_flag = SEARCH_OCCUPANT
if("console")
search_flag = SEARCH_STORED
if(!search_flag)
return
var/bref = params["mutref"]
// We've already made sure we can modify the occupant, so this is safe to
// call
var/datum/mutation/human/HM = get_mut_by_ref(bref, search_flag)
// GUARD CHECK - This should not be possible. Unexpected result
if(!HM)
return
// We want to make sure we stick within the instability limit.
// We start with the instability of the mutation we're intending to add.
var/instability_total = HM.instability
// We then add the instabilities of all other mutations in the injector,
// remembering to apply the Stabilizer chromosome modifiers
for(var/datum/mutation/human/I in injector_selection[adv_inj])
instability_total += I.instability * GET_MUTATION_STABILIZER(I)
// If this would take us over the max instability, we inform the user.
if(instability_total > max_injector_instability)
to_chat(usr,span_warning("Extra mutation would make the advanced injector too instable."))
return
// If we've got here, all our checks are passed and we can successfully
// add the mutation to the advanced injector.
var/datum/mutation/human/A = new HM.type()
A.copy_mutation(HM)
injector_selection[adv_inj] += A
to_chat(usr,span_notice("Mutation successfully added to advanced injector."))
return
// Deletes a mutation from an advanced injector
// ---------------------------------------------------------------------- //
// params["mutref"] - ATOM Ref of specific mutation to del from the injector
if("delete_injector_mut")
var/bref = params["mutref"]
var/datum/mutation/human/HM = get_mut_by_ref(bref, SEARCH_ADV_INJ)
// GUARD CHECK - This should not be possible. Unexpected result
if(!HM)
return
// Check Advanced Injectors to find and remove the mutation
for(var/I in injector_selection)
if(injector_selection["[I]"].Remove(HM))
qdel(HM)
return
return
// Sets a new tgui view state
// ---------------------------------------------------------------------- //
// params["id"] - Key for the state to set
// params[...] - Every other element is used to set state variables
if("set_view")
for (var/key in params)
if(key == "src")
continue
tgui_view_state[key] = params[key]
return TRUE
return FALSE
/**
* Applies the enzyme buffer to the current scanner occupant
*
* Applies the type of a specific genetic makeup buffer to the current scanner
* occupant
*
* Arguments:
* * type - "ui"/"ue"/"mixed" - Which part of the enzyme buffer to apply
* * buffer_slot - Index of the enzyme buffer to apply
*/
/obj/machinery/computer/scan_consolenew/proc/apply_genetic_makeup(type, buffer_slot)
// Note - This proc is only called from code that has already performed the
// necessary occupant guard checks. If you call this code yourself, please
// apply can_modify_occupant() or equivalent checks first.
// Pre-calc the damage increase since we'll be using it in all the possible
// operations
var/damage_increase = rand(100/(connected_scanner.damage_coeff ** 2),250/(connected_scanner.damage_coeff ** 2))
switch(type)
if("ui")
// GUARD CHECK - There's currently no way to save partial genetic data.
// However, if this is the case, we can't make a complete injector and
// this catches that edge case
if(!buffer_slot["UI"])
to_chat(usr,span_warning("Genetic data corrupted, unable to apply genetic data."))
return FALSE
COOLDOWN_START(src, enzyme_copy_timer, ENZYME_COPY_BASE_COOLDOWN)
scanner_occupant.dna.unique_identity = buffer_slot["UI"]
scanner_occupant.updateappearance(mutations_overlay_update=1)
scanner_occupant.AddComponent(/datum/component/genetic_damage, damage_increase)
scanner_occupant.domutcheck()
return TRUE
if("uf")
// GUARD CHECK - There's currently no way to save partial genetic data.
// However, if this is the case, we can't make a complete injector and
// this catches that edge case
if(!buffer_slot["UF"])
to_chat(usr,"<span class='warning'>Genetic data corrupted, unable to apply genetic data.</span>")
return FALSE
COOLDOWN_START(src, enzyme_copy_timer, ENZYME_COPY_BASE_COOLDOWN)
scanner_occupant.dna.unique_features = buffer_slot["UF"]
scanner_occupant.updateappearance(mutcolor_update=1, mutations_overlay_update=1)
scanner_occupant.AddComponent(/datum/component/genetic_damage, damage_increase)
scanner_occupant.domutcheck()
return TRUE
if("ue")
// GUARD CHECK - There's currently no way to save partial genetic data.
// However, if this is the case, we can't make a complete injector and
// this catches that edge case
if(!buffer_slot["name"] || !buffer_slot["UE"] || !buffer_slot["blood_type"])
to_chat(usr,span_warning("Genetic data corrupted, unable to apply genetic data."))
return FALSE
COOLDOWN_START(src, enzyme_copy_timer, ENZYME_COPY_BASE_COOLDOWN)
scanner_occupant.real_name = buffer_slot["name"]
scanner_occupant.name = buffer_slot["name"]
scanner_occupant.dna.unique_enzymes = buffer_slot["UE"]
scanner_occupant.dna.blood_type = buffer_slot["blood_type"]
scanner_occupant.AddComponent(/datum/component/genetic_damage, damage_increase)
scanner_occupant.domutcheck()
return TRUE
if("mixed")
// GUARD CHECK - There's currently no way to save partial genetic data.
// However, if this is the case, we can't make a complete injector and
// this catches that edge case
if(!buffer_slot["UI"] || !buffer_slot["name"] || !buffer_slot["UE"] || !buffer_slot["UF"] || !buffer_slot["blood_type"])
to_chat(usr,span_warning("Genetic data corrupted, unable to apply genetic data."))
return FALSE
COOLDOWN_START(src, enzyme_copy_timer, ENZYME_COPY_BASE_COOLDOWN)
scanner_occupant.dna.unique_identity = buffer_slot["UI"]
scanner_occupant.dna.unique_features = buffer_slot["UF"]
scanner_occupant.updateappearance(mutcolor_update=1, mutations_overlay_update=1)
scanner_occupant.real_name = buffer_slot["name"]
scanner_occupant.name = buffer_slot["name"]
scanner_occupant.dna.unique_enzymes = buffer_slot["UE"]
scanner_occupant.dna.blood_type = buffer_slot["blood_type"]
scanner_occupant.AddComponent(/datum/component/genetic_damage, damage_increase)
scanner_occupant.domutcheck()
return TRUE
return FALSE
/**
* Checks if there is a connected DNA Scanner that is operational
*/
/obj/machinery/computer/scan_consolenew/proc/scanner_operational()
return connected_scanner?.is_operational
/**
* Checks if there is a valid DNA Scanner occupant for genetic modification
*
* Checks if there is a valid subject in the DNA Scanner that can be genetically
* modified. Will set the scanner occupant var as part of this check.
* Requires that the scanner can be operated and will return early if it can't
*/
/obj/machinery/computer/scan_consolenew/proc/can_modify_occupant()
// GUARD CHECK - We always want to perform the scanner operational check as
// part of checking if we can modify the occupant.
// We can never modify the occupant of a broken scanner.
if(!scanner_operational())
return FALSE
if(!connected_scanner.occupant)
return FALSE
scanner_occupant = connected_scanner.occupant
// Check validity of occupent for DNA Modification
// DNA Modification:
// requires DNA
// this DNA can not be bad
// is done via genetic damage bursts, so genetic damage immune carbons are not viable
// And the DNA Scanner itself must have a valid scan level
if(scanner_occupant.has_dna() && !HAS_TRAIT(scanner_occupant, TRAIT_GENELESS) && !HAS_TRAIT(scanner_occupant, TRAIT_BADDNA) || (connected_scanner.scan_level == 3))
return TRUE
return FALSE
/**
* Checks for adjacent DNA scanners and connects when it finds a viable one
*
* Seearches cardinal directions in order. Stops when it finds a viable DNA Scanner.
* Will connect to a broken scanner if no functional scanner is available.
* Links itself to the DNA Scanner to receive door open and close events.
*/
/obj/machinery/computer/scan_consolenew/proc/connect_to_scanner()
var/obj/machinery/dna_scannernew/test_scanner = null
var/obj/machinery/dna_scannernew/broken_scanner = null
// Look in each cardinal direction and try and find a DNA Scanner
// If you find a DNA Scanner, check to see if it broken or working
// If it's working, set the current scanner and return early
// If it's not working, remember it anyway as a broken scanner
for(var/direction in GLOB.cardinals)
test_scanner = locate(/obj/machinery/dna_scannernew, get_step(src, direction))
if(!isnull(test_scanner))
if(test_scanner.is_operational)
set_connected_scanner(test_scanner)
return
else
broken_scanner = test_scanner
// Ultimately, if we have a broken scanner, we'll attempt to connect to it as
// a fallback case, but the code above will prefer a working scanner
if(!isnull(broken_scanner))
set_connected_scanner(broken_scanner)
/**
* Called by connected DNA Scanners when their doors close.
*
* Sets the new scanner occupant and completes delayed enzyme transfer if one
* is queued.
*/
/obj/machinery/computer/scan_consolenew/proc/on_scanner_close()
// Set the appropriate occupant now the scanner is closed
if(connected_scanner.occupant)
scanner_occupant = connected_scanner.occupant
else
scanner_occupant = null
// If we have a delayed action - In this case the only delayed action is
// applying a genetic makeup buffer the next time the DNA Scanner is closed -
// we want to perform it.
// GUARD CHECK - Make sure we can modify the occupant, apply_genetic_makeup()
// assumes we've already done this.
if(delayed_action && can_modify_occupant() && COOLDOWN_FINISHED(src, enzyme_copy_timer))
var/type = delayed_action["type"]
var/buffer_slot = delayed_action["buffer_slot"]
if(apply_genetic_makeup(type, buffer_slot))
to_chat(connected_scanner.occupant, span_notice("[src] activates!"))
delayed_action = null
/**
* Called by connected DNA Scanners when their doors open.
*
* Clears enzyme pulse operations, stops processing and nulls the current
* scanner occupant var.
*/
/obj/machinery/computer/scan_consolenew/proc/on_scanner_open()
// If we had a genetic damage pulse action ongoing, we want to stop this.
// Imagine it being like a microwave stopping when you open the door.
genetic_damage_pulse_index = 0
genetic_damage_pulse_timer = 0
end_processing()
scanner_occupant = null
/**
* Builds the genetic makeup list which will be sent to tgui interface.
*/
/obj/machinery/computer/scan_consolenew/proc/build_genetic_makeup_list()
// No code will ever null this list, we can safely Cut it.
tgui_genetic_makeup.Cut()
for(var/i in 1 to NUMBER_OF_BUFFERS)
if(genetic_makeup_buffer[i])
tgui_genetic_makeup["[i]"] = genetic_makeup_buffer[i].Copy()
else
tgui_genetic_makeup["[i]"] = null
/**
* Builds the genetic makeup list which will be sent to tgui interface.
*
* Will iterate over the connected scanner occupant, DNA Console, inserted
* diskette and chromosomes and any advanced injectors, building the main data
* structures which get passed to the tgui interface.
*/
/obj/machinery/computer/scan_consolenew/proc/build_mutation_list(can_modify_occ)
// No code will ever null these lists. We can safely Cut them.
tgui_occupant_mutations.Cut()
tgui_diskette_mutations.Cut()
tgui_console_mutations.Cut()
tgui_console_chromosomes.Cut()
tgui_advinjector_mutations.Cut()
// ------------------------------------------------------------------------ //
// GUARD CHECK - Can we genetically modify the occupant? This check will have
// previously included checks to make sure the DNA Scanner is still
// operational
if(can_modify_occ)
// ---------------------------------------------------------------------- //
// Start cataloguing all mutations that the occupant has by default
for(var/mutation_type in scanner_occupant.dna.mutation_index)
var/datum/mutation/human/HM = GET_INITIALIZED_MUTATION(mutation_type)
var/list/mutation_data = list()
var/text_sequence = scanner_occupant.dna.mutation_index[mutation_type]
var/default_sequence = scanner_occupant.dna.default_mutation_genes[mutation_type]
var/discovered = (stored_research && (mutation_type in stored_research.discovered_mutations))
mutation_data["Alias"] = HM.alias
mutation_data["Sequence"] = text_sequence
mutation_data["DefaultSeq"] = default_sequence
mutation_data["Discovered"] = discovered
mutation_data["Source"] = "occupant"
// We only want to pass this information along to the tgui interface if
// the mutation has been discovered. Prevents people being able to cheese
// or "hack" their way to figuring out what undiscovered mutations are
if(discovered)
mutation_data["Name"] = HM.name
mutation_data["Description"] = HM.desc
mutation_data["Instability"] = HM.instability * GET_MUTATION_STABILIZER(HM)
mutation_data["Quality"] = HM.quality
// Assume the mutation is normal unless assigned otherwise.
var/mut_class = MUT_NORMAL
// Check if the mutation is currently activated. If it is, we can add even
// MORE information to send to tgui.
var/datum/mutation/human/A = scanner_occupant.dna.get_mutation(mutation_type)
if(A)
mutation_data["Active"] = TRUE
mutation_data["Scrambled"] = A.scrambled
mutation_data["Class"] = A.class
mut_class = A.class
mutation_data["CanChromo"] = A.can_chromosome
mutation_data["ByondRef"] = REF(A)
mutation_data["Type"] = A.type
if(A.can_chromosome)
mutation_data["ValidChromos"] = jointext(A.valid_chrom_list, ", ")
mutation_data["AppliedChromo"] = A.chromosome_name
mutation_data["ValidStoredChromos"] = build_chrom_list(A)
else
mutation_data["Active"] = FALSE
mutation_data["Scrambled"] = FALSE
mutation_data["Class"] = MUT_NORMAL
// Technically NONE of these mutations should be MUT_EXTRA but this will
// catch any weird edge cases
// Assign icons by priority - MUT_EXTRA will ALSO be discovered, so it
// has a higher priority for icon/image assignment
if (mut_class == MUT_EXTRA)
mutation_data["Image"] = "dna_extra.gif"
else if(discovered)
mutation_data["Image"] = "dna_discovered.gif"
else
mutation_data["Image"] = "dna_undiscovered.gif"
tgui_occupant_mutations += list(mutation_data)
// ---------------------------------------------------------------------- //
// Now get additional/"extra" mutations that they shouldn't have by default
for(var/datum/mutation/human/HM in scanner_occupant.dna.mutations)
// If it's in the mutation index array, we've already catalogued this
// mutation and can safely skip over it. It really shouldn't be, but this
// will catch any weird edge cases
if(HM.type in scanner_occupant.dna.mutation_index)
continue
var/list/mutation_data = list()
var/text_sequence = GET_SEQUENCE(HM.type)
// These will all be active mutations. They're added by injector and their
// sequencing code can't be changed. They can only be nullified, which
// completely removes them.
var/datum/mutation/human/A = GET_INITIALIZED_MUTATION(HM.type)
mutation_data["Alias"] = A.alias
mutation_data["Sequence"] = text_sequence
mutation_data["Discovered"] = TRUE
mutation_data["Quality"] = HM.quality
mutation_data["Source"] = "occupant"
mutation_data["Name"] = HM.name
mutation_data["Description"] = HM.desc
mutation_data["Instability"] = HM.instability * GET_MUTATION_STABILIZER(HM)
mutation_data["Active"] = TRUE
mutation_data["Scrambled"] = HM.scrambled
mutation_data["Class"] = HM.class
mutation_data["CanChromo"] = HM.can_chromosome
mutation_data["ByondRef"] = REF(HM)
mutation_data["Type"] = HM.type
if(HM.can_chromosome)
mutation_data["ValidChromos"] = jointext(HM.valid_chrom_list, ", ")
mutation_data["AppliedChromo"] = HM.chromosome_name
mutation_data["ValidStoredChromos"] = build_chrom_list(HM)
// Nothing in this list should be undiscovered. Technically nothing
// should be anything but EXTRA. But we're just handling some edge cases.
if (HM.class == MUT_EXTRA)
mutation_data["Image"] = "dna_extra.gif"
else
mutation_data["Image"] = "dna_discovered.gif"
tgui_occupant_mutations += list(mutation_data)
// ------------------------------------------------------------------------ //
// Build the list of mutations stored within the DNA Console
for(var/datum/mutation/human/HM in stored_mutations)
var/list/mutation_data = list()
var/datum/mutation/human/A = GET_INITIALIZED_MUTATION(HM.type)
mutation_data["Alias"] = A.alias
mutation_data["Name"] = HM.name
mutation_data["Source"] = "console"
mutation_data["Active"] = TRUE
mutation_data["Description"] = HM.desc
mutation_data["Instability"] = HM.instability * GET_MUTATION_STABILIZER(HM)
mutation_data["ByondRef"] = REF(HM)
mutation_data["Type"] = HM.type
mutation_data["CanChromo"] = HM.can_chromosome
if(HM.can_chromosome)
mutation_data["ValidChromos"] = jointext(HM.valid_chrom_list, ", ")
mutation_data["AppliedChromo"] = HM.chromosome_name
mutation_data["ValidStoredChromos"] = build_chrom_list(HM)
tgui_console_mutations += list(mutation_data)
// ------------------------------------------------------------------------ //
// Build the list of chromosomes stored within the DNA Console
var/chrom_index = 1
for(var/obj/item/chromosome/CM in stored_chromosomes)
var/list/chromo_data = list()
chromo_data["Name"] = CM.name
chromo_data["Description"] = CM.desc
chromo_data["Index"] = chrom_index
tgui_console_chromosomes += list(chromo_data)
++chrom_index
// ------------------------------------------------------------------------ //
// Build the list of mutations stored on any inserted diskettes
if(diskette)
for(var/datum/mutation/human/HM in diskette.mutations)
var/list/mutation_data = list()
var/datum/mutation/human/A = GET_INITIALIZED_MUTATION(HM.type)
mutation_data["Alias"] = A.alias
mutation_data["Name"] = HM.name
mutation_data["Active"] = TRUE
//mutation_data["Sequence"] = GET_SEQUENCE(HM.type)
mutation_data["Source"] = "disk"
mutation_data["Description"] = HM.desc
mutation_data["Instability"] = HM.instability * GET_MUTATION_STABILIZER(HM)
mutation_data["ByondRef"] = REF(HM)
mutation_data["Type"] = HM.type
mutation_data["CanChromo"] = HM.can_chromosome
if(HM.can_chromosome)
mutation_data["ValidChromos"] = jointext(HM.valid_chrom_list, ", ")
mutation_data["AppliedChromo"] = HM.chromosome_name
mutation_data["ValidStoredChromos"] = build_chrom_list(HM)
tgui_diskette_mutations += list(mutation_data)
// ------------------------------------------------------------------------ //
// Build the list of mutations stored within any Advanced Injectors
if(LAZYLEN(injector_selection))
for(var/I in injector_selection)
var/list/mutations = list()
for(var/datum/mutation/human/HM in injector_selection[I])
var/list/mutation_data = list()
var/datum/mutation/human/A = GET_INITIALIZED_MUTATION(HM.type)
mutation_data["Alias"] = A.alias
mutation_data["Name"] = HM.name
mutation_data["Active"] = TRUE
//mutation_data["Sequence"] = GET_SEQUENCE(HM.type)
mutation_data["Source"] = "injector"
mutation_data["Description"] = HM.desc
mutation_data["Instability"] = HM.instability * GET_MUTATION_STABILIZER(HM)
mutation_data["ByondRef"] = REF(HM)
mutation_data["Type"] = HM.type
if(HM.can_chromosome)
mutation_data["AppliedChromo"] = HM.chromosome_name
mutations += list(mutation_data)
tgui_advinjector_mutations += list(list(
"name" = "[I]",
"mutations" = mutations,
))
/**
* Takes any given chromosome and calculates chromosome compatibility
*
* Will iterate over the stored chromosomes in the DNA Console and will check
* whether it can be applied to the supplied mutation. Then returns a list of
* names of chromosomes that were compatible.
*
* Arguments:
* * mutation - The mutation to check chromosome compatibility with
*/
/obj/machinery/computer/scan_consolenew/proc/build_chrom_list(mutation)
var/list/chromosomes = list()
for(var/obj/item/chromosome/CM in stored_chromosomes)
if(CM.can_apply(mutation))
chromosomes += CM.name
return chromosomes
/**
* Checks whether a mutation alias has been discovered
*
* Checks whether a given mutation's genetic sequence has been completed and
* discovers it if appropriate
*
* Arguments:
* * alias - Alias of the mutation to check (ie "Mutation 51" or "Mutation 12")
*/
/obj/machinery/computer/scan_consolenew/proc/check_discovery(alias)
// Note - All code paths that call this have already done checks on the
// current occupant to prevent cheese and other abuses. If you call this
// proc please also do the following checks first:
// if(!can_modify_occupant())
// return
// if(!(scanner_occupant == connected_scanner.occupant))
// return
// Turn the alias ("Mutation 1", "Mutation 35") into a mutation path
var/path = GET_MUTATION_TYPE_FROM_ALIAS(alias)
// Check to see if this mutation is in the active mutation list. If it isn't,
// then the mutation isn't eligible for discovery. If it is but is scrambled,
// then the mutation isn't eligible for discovery. Finally, check if the
// mutation is in discovered mutations - If it isn't, add it to discover.
var/datum/mutation/human/M = scanner_occupant.dna.get_mutation(path)
if(!M)
return FALSE
if(M.scrambled)
return FALSE
if(stored_research && !(path in stored_research.discovered_mutations))
var/datum/mutation/human/HM = GET_INITIALIZED_MUTATION(path)
stored_research.discovered_mutations += path
say("Successfully discovered [HM.name].")
return TRUE
return FALSE
/**
* Find a mutation from various storage locations via ATOM ref
*
* Takes an ATOM Ref and searches the appropriate mutation buffers and storage
* vars to try and find the associated mutation.
*
* Arguments:
* * ref - ATOM ref of the mutation to locate
* * target_flags - Flags for storage mediums to search, see #defines
*/
/obj/machinery/computer/scan_consolenew/proc/get_mut_by_ref(ref, target_flags)
var/mutation
// Assume the occupant is valid and the check has been carried out before
// calling this proc with the relevant flags.
if(target_flags & SEARCH_OCCUPANT)
mutation = (locate(ref) in scanner_occupant.dna.mutations)
if(mutation)
return mutation
if(target_flags & SEARCH_STORED)
mutation = (locate(ref) in stored_mutations)
if(mutation)
return mutation
if(diskette && (target_flags & SEARCH_DISKETTE))
mutation = (locate(ref) in diskette.mutations)
if(mutation)
return mutation
if(injector_selection && (target_flags & SEARCH_ADV_INJ))
for(var/I in injector_selection)
mutation = (locate(ref) in injector_selection["[I]"])
if(mutation)
return mutation
return null
/**
* Creates a randomised accuracy value for the enzyme pulse functionality.
*
* Donor code from previous DNA Console iteration.
*
* Arguments:
* * position - Index of the intended enzyme element to pulse
* * pulse_duration - Duration of intended genetic damage pulse
* * number_of_blocks - Number of individual data blocks in the pulsed enzyme
*/
/obj/machinery/computer/scan_consolenew/proc/randomize_GENETIC_DAMAGE_accuracy(position, pulse_duration, number_of_blocks)
var/val = round(gaussian(0, GENETIC_DAMAGE_ACCURACY_MULTIPLIER/pulse_duration) + position, 1)
return WRAP(val, 1, number_of_blocks+1)
/**
* Scrambles an enzyme element value for the enzyme pulse functionality.
*
* Donor code from previous DNA Console iteration.
*
* Arguments:
* * input - Enzyme identity element to scramble, expected hex value
* * rs - Strength of genetic damage pulse, increases the range of possible outcomes
*/
/obj/machinery/computer/scan_consolenew/proc/scramble(input,rs)
var/length = length(input)
var/ran = gaussian(0, rs*GENETIC_DAMAGE_STRENGTH_MULTIPLIER)
if(ran == 0)
ran = pick(-1,1) //hacky, statistically should almost never happen. 0-chance makes people mad though
else if(ran < 0)
ran = round(ran) //negative, so floor it
else
ran = -round(-ran) //positive, so ceiling it
return num2hex(WRAP(hex2num(input)+ran, 0, 16**length), length)
/**
* Performs the enzyme genetic damage pulse.
*
* Donor code from previous DNA Console iteration. Called from process() when
* there is a genetic damage pulse in progress. Ends processing.
*/
/obj/machinery/computer/scan_consolenew/proc/genetic_damage_pulse()
// GUARD CHECK - Can we genetically modify the occupant? Includes scanner
// operational guard checks.
// If we can't, abort the procedure.
if(!can_modify_occupant() || (genetic_damage_pulse_type != GENETIC_DAMAGE_PULSE_UNIQUE_IDENTITY && genetic_damage_pulse_type != GENETIC_DAMAGE_PULSE_UNIQUE_FEATURES))
genetic_damage_pulse_index = 0
end_processing()
return
var/len
switch(genetic_damage_pulse_type)
if(GENETIC_DAMAGE_PULSE_UNIQUE_IDENTITY)
len = length(scanner_occupant.dna.unique_identity)
if(GENETIC_DAMAGE_PULSE_UNIQUE_FEATURES)
len = length(scanner_occupant.dna.unique_features)
var/num = randomize_GENETIC_DAMAGE_accuracy(genetic_damage_pulse_index, pulse_duration + (connected_scanner.precision_coeff ** 2), len) //Each manipulator level above 1 makes randomization as accurate as selected time + manipulator lvl^2 //Value is this high for the same reason as with laser - not worth the hassle of upgrading if the bonus is low
var/hex
switch(genetic_damage_pulse_type)
if(GENETIC_DAMAGE_PULSE_UNIQUE_IDENTITY)
hex = copytext(scanner_occupant.dna.unique_identity, num, num+1)
if(GENETIC_DAMAGE_PULSE_UNIQUE_FEATURES)
hex = copytext(scanner_occupant.dna.unique_features, num, num+1)
hex = scramble(hex, pulse_strength, pulse_duration)
switch(genetic_damage_pulse_type)
if(GENETIC_DAMAGE_PULSE_UNIQUE_IDENTITY)
scanner_occupant.dna.unique_identity = copytext(scanner_occupant.dna.unique_identity, 1, num) + hex + copytext(scanner_occupant.dna.unique_identity, num + 1)
if(GENETIC_DAMAGE_PULSE_UNIQUE_FEATURES)
scanner_occupant.dna.unique_features = copytext(scanner_occupant.dna.unique_features, 1, num) + hex + copytext(scanner_occupant.dna.unique_features, num + 1)
scanner_occupant.updateappearance(mutcolor_update=1, mutations_overlay_update=1)
genetic_damage_pulse_index = 0
genetic_damage_pulse_type = null
end_processing()
return
/**
* Sets the default state for the tgui interface.
*/
/obj/machinery/computer/scan_consolenew/proc/set_default_state()
tgui_view_state["consoleMode"] = "storage"
tgui_view_state["storageMode"] = "console"
tgui_view_state["storageConsSubMode"] = "mutations"
tgui_view_state["storageDiskSubMode"] = "mutations"
/**
* Ejects the DNA Disk from the console.
*
* Will insert into the user's hand if possible, otherwise will drop it at the
* console's location.
*
* Arguments:
* * user - The mob that is attempting to eject the diskette.
*/
/obj/machinery/computer/scan_consolenew/proc/eject_disk(mob/user)
// Check for diskette.
if(!diskette)
return
to_chat(user, span_notice("You eject [diskette] from [src]."))
// Reset the state to console storage.
tgui_view_state["storageMode"] = "console"
// If the disk shouldn't pop into the user's hand for any reason, drop it on the console instead.
if(!istype(user) || !Adjacent(user) || !user.put_in_active_hand(diskette))
diskette.forceMove(drop_location())
diskette = null
/obj/machinery/computer/scan_consolenew/proc/set_connected_scanner(new_scanner)
if(connected_scanner)
UnregisterSignal(connected_scanner, COMSIG_PARENT_QDELETING)
if(connected_scanner.linked_console == src)
connected_scanner.set_linked_console(null)
connected_scanner = new_scanner
if(connected_scanner)
RegisterSignal(connected_scanner, COMSIG_PARENT_QDELETING, .proc/react_to_scanner_del)
connected_scanner.set_linked_console(src)
/obj/machinery/computer/scan_consolenew/proc/react_to_scanner_del(datum/source)
SIGNAL_HANDLER
set_connected_scanner(null)
#undef ENZYME_COPY_BASE_COOLDOWN
#undef INJECTOR_TIMEOUT
#undef NUMBER_OF_BUFFERS
#undef SCRAMBLE_TIMEOUT
#undef JOKER_TIMEOUT
#undef JOKER_UPGRADE
#undef GENETIC_DAMAGE_STRENGTH_MAX
#undef GENETIC_DAMAGE_STRENGTH_MULTIPLIER
#undef GENETIC_DAMAGE_DURATION_MAX
#undef GENETIC_DAMAGE_ACCURACY_MULTIPLIER
#undef GENETIC_DAMAGE_IRGENETIC_DAMAGE_MULTIPLIER
#undef STATUS_TRANSFORMING
#undef SEARCH_OCCUPANT
#undef SEARCH_STORED
#undef SEARCH_DISKETTE
#undef SEARCH_ADV_INJ