[alias]"
- temp_html += "
[mut_desc]
"
- if(active && !storage_slot)
- if(HM?.can_chromosome && (HM in viable_occupant.dna.mutations))
- var/i = viable_occupant.dna.mutations.Find(HM)
- var/chromosome_name = "
----"
- if(HM.chromosome_name)
- chromosome_name = HM.chromosome_name
- temp_html += "
Chromosome status: [chromosome_name]
"
- temp_html += "
Sequence:
"
- if(!scrambled)
- for(var/block in 1 to A.blocks)
- var/whole_sequence = get_valid_gene_string(mutation)
- var/sequence = copytext(whole_sequence, 1+(block-1)*(DNA_SEQUENCE_LENGTH*2),(DNA_SEQUENCE_LENGTH*2*block+1))
- temp_html += "
"
- for(var/i in 1 to DNA_SEQUENCE_LENGTH)
- var/num = 1+(i-1)*2
- var/genenum = num+(DNA_SEQUENCE_LENGTH*2*(block-1))
- temp_html += " | "
- temp_html += "
"
- for(var/i in 1 to DNA_SEQUENCE_LENGTH)
- temp_html += "| | "
- temp_html += "
"
- for(var/i in 1 to DNA_SEQUENCE_LENGTH)
- var/num = i*2
- var/genenum = num+(DNA_SEQUENCE_LENGTH*2*(block-1))
- temp_html += " | "
- temp_html += "
"
- temp_html += "
"
- else
- temp_html = "
Sequence unreadable due to unpredictable mutation.
"
- if((active || storage_slot) && (injectorready < world.time) && !scrambled)
- temp_html += "
Print Activator"
- temp_html += "
Print Mutator"
- else
- temp_html += "
Print Activator"
- temp_html += "
Print Mutator"
- temp_html += "
"
- if(storage_slot)
- temp_html += "
Delete"
- if((LAZYLEN(stored_mutations) < max_storage) && diskette && !diskette.read_only)
- temp_html += "
Export"
- else
- temp_html += "
Export"
- temp_html += "
Back"
- else if(active && !scrambled)
- temp_html += "
Store"
- temp_html += "
Adv. Injector"
- if(extra || scrambled)
- temp_html += "
Nullify"
- else
- temp_html += "
Nullify"
- temp_html += "
"
- return temp_html
+ // 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["radStrength"] = radstrength
+ data["radDuration"] = radduration
+ data["stdDevStr"] = radstrength * RADIATION_STRENGTH_MULTIPLIER
+ switch(RADIATION_ACCURACY_MULTIPLIER / (radduration + (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 %"
-/obj/machinery/computer/scan_consolenew/Topic(href, href_list)
+ data["isViableSubject"] = is_viable_occupant
+ if(is_viable_occupant)
+ data["subjectName"] = scanner_occupant.name
+ data["subjectStatus"] = scanner_occupant.stat
+ data["subjectHealth"] = scanner_occupant.health
+ data["subjectRads"] = scanner_occupant.radiation/(RAD_MOB_SAFE/100)
+ data["subjectEnzymes"] = scanner_occupant.dna.unique_enzymes
+ data["isMonkey"] = ismonkey(scanner_occupant)
+ data["subjectUNI"] = scanner_occupant.dna.uni_identity
+ data["storage"]["occupant"] = tgui_occupant_mutations
+ //data["subjectMutations"] = tgui_occupant_mutations
+ else
+ data["subjectName"] = null
+ data["subjectStatus"] = null
+ data["subjectHealth"] = null
+ data["subjectRads"] = null
+ data["subjectEnzymes"] = null
+ //data["subjectMutations"] = 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["scrambleSeconds"] = time_to_scramble
+ data["jokerSeconds"] = time_to_joker
+ data["injectorSeconds"] = time_to_injector
+ data["isPulsingRads"] = is_pulsing_rads
+ data["radPulseSeconds"] = time_to_pulse
+
+ 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["mutationCapacity"] = max_storage - LAZYLEN(stored_mutations)
+ //data["mutationStorage"] = tgui_console_mutations
+ data["storage"]["console"] = tgui_console_mutations
+ data["chromoCapacity"] = max_chromosomes - LAZYLEN(stored_chromosomes)
+ 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, var/list/params)
if(..())
- return
- if(!isturf(usr.loc))
- return
- if(!((isturf(loc) && in_range(src, usr)) || hasSiliconAccessInArea(usr)))
- return
- if(current_screen == "working")
- return
+ return TRUE
+
+ . = TRUE
add_fingerprint(usr)
usr.set_machine(src)
- var/mob/living/carbon/viable_occupant = get_viable_occupant()
+ 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
- //Basic Tasks///////////////////////////////////////////
- var/num = round(text2num(href_list["num"]))
- var/last_change
- switch(href_list["task"])
- if("togglelock")
- if(connected)
- connected.locked = !connected.locked
- if("toggleopen")
- if(connected)
- connected.toggle_open(usr)
- if("setduration")
- if(!num)
- num = round(input(usr, "Choose pulse duration:", "Input an Integer", null) as num|null)
- if(num)
- radduration = WRAP(num, 1, RADIATION_DURATION_MAX+1)
- if("setstrength")
- if(!num)
- num = round(input(usr, "Choose pulse strength:", "Input an Integer", null) as num|null)
- if(num)
- radstrength = WRAP(num, 1, RADIATION_STRENGTH_MAX+1)
- if("screen")
- current_screen = href_list["text"]
- if("scramble")
- if(viable_occupant && (scrambleready < world.time))
- viable_occupant.dna.remove_all_mutations(list(MUT_NORMAL, MUT_EXTRA))
- viable_occupant.dna.generate_dna_blocks()
- scrambleready = world.time + SCRAMBLE_TIMEOUT
- to_chat(usr,"
DNA scrambled.")
- viable_occupant.radiation += RADIATION_STRENGTH_MULTIPLIER*50/(connected.damage_coeff ** 2)
- if("setbufferlabel")
- var/text = sanitize(input(usr, "Input a new label:", "Input a Text", null) as text|null)
- if(num && text)
- num = CLAMP(num, 1, NUMBER_OF_BUFFERS)
- var/list/buffer_slot = buffer[num]
- if(istype(buffer_slot))
- buffer_slot["label"] = text
- if("setbuffer")
- if(num && viable_occupant)
- num = CLAMP(num, 1, NUMBER_OF_BUFFERS)
- buffer[num] = list(
- "label"="Buffer[num]:[viable_occupant.real_name]",
- "UI"=viable_occupant.dna.uni_identity,
- "UE"=viable_occupant.dna.unique_enzymes,
- "name"=viable_occupant.real_name,
- "blood_type"=viable_occupant.dna.blood_type
- )
- if("clearbuffer")
- if(num)
- num = CLAMP(num, 1, NUMBER_OF_BUFFERS)
- var/list/buffer_slot = buffer[num]
- if(istype(buffer_slot))
- buffer_slot.Cut()
- if("transferbuffer")
- if(num && viable_occupant)
- switch(href_list["text"]) //Numbers are this high because other way upgrading laser is just not worth the hassle, and i cant think of anything better to inmrove
- if("ui")
- apply_buffer(SCANNER_ACTION_UI,num)
- if("ue")
- apply_buffer(SCANNER_ACTION_UE,num)
- if("mixed")
- apply_buffer(SCANNER_ACTION_MIXED,num)
- if("injector")
- if(num && injectorready < world.time)
- num = CLAMP(num, 1, NUMBER_OF_BUFFERS)
- var/list/buffer_slot = buffer[num]
- if(istype(buffer_slot))
- var/obj/item/dnainjector/timed/I
- switch(href_list["text"])
- if("ui")
- if(buffer_slot["UI"])
- I = new /obj/item/dnainjector/timed(loc)
- I.fields = list("UI"=buffer_slot["UI"])
- if(connected)
- I.damage_coeff = connected.damage_coeff
- if("ue")
- if(buffer_slot["name"] && buffer_slot["UE"] && buffer_slot["blood_type"])
- 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(connected)
- I.damage_coeff = connected.damage_coeff
- if("mixed")
- if(buffer_slot["UI"] && buffer_slot["name"] && buffer_slot["UE"] && buffer_slot["blood_type"])
- I = new /obj/item/dnainjector/timed(loc)
- I.fields = list("UI"=buffer_slot["UI"],"name"=buffer_slot["name"], "UE"=buffer_slot["UE"], "blood_type"=buffer_slot["blood_type"])
- if(connected)
- I.damage_coeff = connected.damage_coeff
- if(I)
- injectorready = world.time + INJECTOR_TIMEOUT
- if("loaddisk")
- if(num && diskette && diskette.fields)
- num = CLAMP(num, 1, NUMBER_OF_BUFFERS)
- buffer[num] = diskette.fields.Copy()
- if("savedisk")
- if(num && diskette && !diskette.read_only)
- num = CLAMP(num, 1, NUMBER_OF_BUFFERS)
- var/list/buffer_slot = buffer[num]
- if(istype(buffer_slot))
- diskette.name = "data disk \[[buffer_slot["label"]]\]"
- diskette.fields = buffer_slot.Copy()
- if("ejectdisk")
- if(diskette)
- diskette.forceMove(drop_location())
- diskette = null
- if("setdelayed")
- if(num)
- delayed_action = list("action"=text2num(href_list["delayaction"]),"buffer"=num)
- if("pulseui")
- if(num && viable_occupant && connected)
- radduration = WRAP(radduration, 1, RADIATION_DURATION_MAX+1)
- radstrength = WRAP(radstrength, 1, RADIATION_STRENGTH_MAX+1)
+ // Toggle the door open/closed status on attached DNA Scanner
+ if("toggle_door")
+ // GUARD CHECK - Scanner still connected and operational?
+ if(!scanner_operational())
+ return
- var/locked_state = connected.locked
- connected.locked = TRUE
+ connected_scanner.toggle_open(usr)
+ return
- current_screen = "working"
- ui_interact(usr)
+ // Toggle the door bolts on the attached DNA Scanner
+ if("toggle_lock")
+ // GUARD CHECK - Scanner still connected and operational?
+ if(!scanner_operational())
+ return
- sleep(radduration*10)
- current_screen = "ui"
+ connected_scanner.locked = !connected_scanner.locked
+ return
- if(viable_occupant && connected && connected.occupant==viable_occupant)
- viable_occupant.radiation += (RADIATION_IRRADIATION_MULTIPLIER*radduration*radstrength)/(connected.damage_coeff ** 2) //Read comment in "transferbuffer" section above for explanation
- switch(href_list["task"]) //Same thing as there but values are even lower, on best part they are about 0.0*, effectively no damage
- if("pulseui")
- var/len = length_char(viable_occupant.dna.uni_identity)
- num = WRAP(num, 1, len+1)
- num = randomize_radiation_accuracy(num, radduration + (connected.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/block = round((num-1)/DNA_BLOCK_SIZE)+1
- var/subblock = num - block*DNA_BLOCK_SIZE
- last_change = "UI #[block]-[subblock]; "
+ // 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
- var/hex = copytext_char(viable_occupant.dna.uni_identity, num, num+1)
- last_change += "[hex]"
- hex = scramble(hex, radstrength, radduration)
- last_change += "->[hex]"
+ 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,"
DNA scrambled.")
+ scanner_occupant.radiation += RADIATION_STRENGTH_MULTIPLIER*50/(connected_scanner.damage_coeff ** 2)
+ return
- viable_occupant.dna.uni_identity = copytext_char(viable_occupant.dna.uni_identity, 1, num) + hex + copytext_char(viable_occupant.dna.uni_identity, num + 1)
- viable_occupant.updateappearance(mutations_overlay_update=1)
+ // 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
+
+ // 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_char(defaultseq, 1, genepos) + newgene + copytext_char(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 rads
+ sequence = copytext_char(sequence, 1, genepos) + newgene + copytext_char(sequence, genepos + 1)
+ scanner_occupant.dna.mutation_index[path] = sequence
+ scanner_occupant.radiation += RADIATION_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
+
+ // 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 radiation 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
- current_screen = "mainmenu"
-
- if(connected)
- connected.locked = locked_state
- if("inspect")
- if(viable_occupant)
- var/list/mutations = get_mutation_list(TRUE)
- if(current_mutation == mutations[num])
- current_mutation = null
+ 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 radiation 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
- current_mutation = mutations[num]
+ 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
+
+ // GUARD CHECK - Is mutation storage full?
+ if(LAZYLEN(stored_mutations) >= max_storage)
+ to_chat(usr,"
Mutation storage is full.")
+ return
+
+ 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()
+ A.copy_mutation(HM)
+ stored_mutations += A
+ to_chat(usr,"
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,"
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,"
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()
+ A.copy_mutation(HM)
+ diskette.mutations += A
+ to_chat(usr,"
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("inspectstorage")
- current_storage = num
- current_screen = "info"
- if("savemut")
- if(viable_occupant)
- var/succes
- if(LAZYLEN(stored_mutations) < max_storage)
- var/mutation = text2path(href_list["path"])
- if(ispath(mutation, /datum/mutation/human)) //sanity checks
- var/datum/mutation/human/HM = viable_occupant.dna.get_mutation(mutation)
- if(HM)
- var/datum/mutation/human/A = new HM.type()
- A.copy_mutation(HM)
- succes = TRUE
- stored_mutations += A
- to_chat(usr,"
Mutation succesfully stored.")
- if(!succes) //we can exactly return here
- to_chat(usr,"
Mutation storage is full.")
- if("deletemut")
- var/datum/mutation/human/HM = stored_mutations[num]
if(HM)
stored_mutations.Remove(HM)
qdel(HM)
- current_screen = "mutations"
- if("activator")
- if(injectorready < world.time)
- var/mutation = text2path(href_list["path"])
- if(ispath(mutation, /datum/mutation/human))
- var/datum/mutation/human/HM = get_valid_mutation(mutation)
- if(HM)
- var/obj/item/dnainjector/activator/I = new /obj/item/dnainjector/activator(loc)
- I.add_mutations += new HM.type (copymut = HM)
- I.name = "[HM.name] activator"
- I.research = TRUE
- if(connected)
- I.damage_coeff = connected.damage_coeff*4
- injectorready = world.time + INJECTOR_TIMEOUT * (1 - 0.1 * connected.precision_coeff) //precision_coeff being the matter bin rating
- else
- injectorready = world.time + INJECTOR_TIMEOUT
- if("mutator")
- if(injectorready < world.time)
- var/mutation = text2path(href_list["path"])
- if(ispath(mutation, /datum/mutation/human))
- var/datum/mutation/human/HM = get_valid_mutation(mutation)
- if(HM)
- var/obj/item/dnainjector/activator/I = new /obj/item/dnainjector/activator(loc)
- I.add_mutations += new HM.type (copymut = HM)
- I.doitanyway = TRUE
- I.name = "[HM.name] injector"
- if(connected)
- I.damage_coeff = connected.damage_coeff
- injectorready = world.time + INJECTOR_TIMEOUT * 5 * (1 - 0.1 * connected.precision_coeff)
- else
- injectorready = world.time + INJECTOR_TIMEOUT * 5
- if("advinjector")
- var/selection = href_list["injector"]
- if(injectorready < world.time)
- if(injector_selection.Find(selection))
- var/list/true_selection = injector_selection[selection]
- if(LAZYLEN(injector_selection))
- var/obj/item/dnainjector/activator/I = new /obj/item/dnainjector/activator(loc)
- for(var/A in true_selection)
- var/datum/mutation/human/HM = A
- I.add_mutations += new HM.type (copymut = HM)
- I.doitanyway = TRUE
- I.name = "Advanced [selection] injector"
- if(connected)
- I.damage_coeff = connected.damage_coeff
- injectorready = world.time + INJECTOR_TIMEOUT * 8 * (1 - 0.1 * connected.precision_coeff)
- else
- injectorready = world.time + INJECTOR_TIMEOUT * 8
- if("nullify")
- if(viable_occupant)
- var/datum/mutation/human/A = viable_occupant.dna.get_mutation(current_mutation)
- if(A && (!viable_occupant.dna.mutation_in_sequence(current_mutation) || A.scrambled))
- viable_occupant.dna.remove_mutation(current_mutation)
- current_screen = "mainmenu"
- current_mutation = null
- if("pulsegene")
- if(current_screen != "info")
- var/path = text2path(href_list["path"])
- if(viable_occupant && num && (path in viable_occupant.dna.mutation_index))
- var/list/genes = list("A","T","G","C","X")
- if(jokerready < world.time)
- genes += "JOKER"
- var/sequence = GET_GENE_STRING(path, viable_occupant.dna)
- var/original = sequence[num]
- var/new_gene = input("From [original] to-", "New block", original) as null|anything in genes
- if(!new_gene)
- new_gene = original
- if(viable_occupant == get_viable_occupant()) //No cheesing
- if((new_gene == "JOKER") && (jokerready < world.time))
- var/true_genes = GET_SEQUENCE(current_mutation)
- new_gene = true_genes[num]
- jokerready = world.time + JOKER_TIMEOUT - (JOKER_UPGRADE * (connected.precision_coeff-1))
- sequence = copytext(sequence, 1, num) + new_gene + copytext(sequence, num+1, length(sequence)+1)
- viable_occupant.dna.mutation_index[path] = sequence
- viable_occupant.radiation += RADIATION_STRENGTH_MULTIPLIER/connected.damage_coeff
- viable_occupant.domutcheck()
- if("exportdiskmut")
- if(diskette && !diskette.read_only)
- var/path = text2path(href_list["path"])
- if(ispath(path, /datum/mutation/human))
- var/datum/mutation/human/A = get_valid_mutation(path)
- if(A && diskette && (LAZYLEN(diskette.mutations) < diskette.max_mutations))
- var/datum/mutation/human/HM = new A.type()
- diskette.mutations += HM
- HM.copy_mutation(A)
- to_chat(usr, "
Succesfully written [A.name] to [diskette.name].")
- if("deletediskmut")
- if(diskette && !diskette.read_only)
- if(num && (LAZYLEN(diskette.mutations) >= num))
- var/datum/mutation/human/A = diskette.mutations[num]
- diskette.mutations.Remove(A)
- qdel(A)
- if("importdiskmut")
- if(diskette && (LAZYLEN(diskette.mutations) >= num))
- if(LAZYLEN(stored_mutations) < max_storage)
- var/datum/mutation/human/A = diskette.mutations[num]
- var/datum/mutation/human/HM = new A.type()
- HM.copy_mutation(A)
- stored_mutations += HM
- to_chat(usr,"
Succesfully written [A.name] to storage.")
- if("combine")
- if(num && (LAZYLEN(stored_mutations) >= num))
- if(LAZYLEN(stored_mutations) < max_storage)
- var/datum/mutation/human/A = stored_mutations[num]
- var/path = A.type
- if(combine)
- var/result_path = get_mixed_mutation(combine, path)
- if(result_path)
- stored_mutations += new result_path()
- to_chat(usr, "Succes! New mutation has been added to storage")
- discover(result_path)
- combine = null
- else
- to_chat(usr, "Failed. No mutation could be created.")
- combine = null
- else
- combine = path
- to_chat(usr,"Selected [A.name] for combining")
- else
- to_chat(usr, "Not enough space to store potential mutation.")
- if("ejectchromosome")
- if(LAZYLEN(stored_chromosomes) <= num)
- var/obj/item/chromosome/CM = stored_chromosomes[num]
- CM.forceMove(drop_location())
- adjust_item_drop_location(CM)
- stored_chromosomes -= CM
- if("applychromosome")
- if(viable_occupant && (LAZYLEN(viable_occupant.dna.mutations) <= num))
- var/datum/mutation/human/HM = viable_occupant.dna.mutations[num]
- var/list/chromosomes = list()
- for(var/obj/item/chromosome/CM in stored_chromosomes)
- if(CM.can_apply(HM))
- chromosomes += CM
- if(chromosomes.len)
- var/obj/item/chromosome/CM = input("Select a chromosome to apply", "Apply Chromosome") as null|anything in sortNames(chromosomes)
- if(CM)
- to_chat(usr, "You apply [CM] to [HM.name].")
- stored_chromosomes -= CM
- CM.apply(HM)
- if("expand_advinjector")
- var/mutation = text2path(href_list["path"])
- var/datum/mutation/human/HM = get_valid_mutation(mutation)
- if(HM && LAZYLEN(injector_selection))
- var/which_injector = input(usr, "Select Adv. Injector", "Advanced Injectors") as null|anything in injector_selection
- if(injector_selection.Find(which_injector))
- var/list/true_selection = injector_selection[which_injector]
- var/total_instability
- for(var/B in true_selection)
- var/datum/mutation/human/mootacion = B
- total_instability += mootacion.instability
- total_instability += HM.instability
- if((total_instability > max_injector_instability) || (true_selection.len + 1) > max_injector_mutations)
- to_chat(usr, "Adding more mutations would make the advanced injector too unstable!")
- else
- true_selection += HM //reminder that this works. because I keep forgetting this works
- if("remove_from_advinjector")
- var/mutation = text2path(href_list["path"])
- var/selection = href_list["injector"]
- if(injector_selection.Find(selection))
- var/list/true_selection = injector_selection[selection]
- for(var/B in true_selection)
- var/datum/mutation/human/HM = B
- if(HM.type == mutation)
- true_selection -= HM
- break
+ return
- if("remove_advinjector")
- var/selection = href_list["injector"]
- for(selection in injector_selection)
- if(selection == selection)
- injector_selection.Remove(selection)
+ // 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
- if("add_advinjector")
- if(LAZYLEN(injector_selection) < max_injector_selections)
- var/new_selection = input(usr, "Enter Adv. Injector name", "Advanced Injectors") as text|null
- if(new_selection && !(new_selection in injector_selection))
- injector_selection[new_selection] = list()
+ // 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,"Disk is set to read only mode.")
+ return
+ var/bref = params["mutref"]
+ var/datum/mutation/human/HM = get_mut_by_ref(bref, SEARCH_DISKETTE)
- ui_interact(usr,last_change)
+ if(HM)
+ diskette.mutations.Remove(HM)
+ qdel(HM)
-/obj/machinery/computer/scan_consolenew/proc/scramble(input,rs,rd) //hexadecimal genetics. dont confuse with scramble button
+ 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 - Make sure mutation storage isn't full. If it is, we won't
+ // be able to store the new combo mutation
+ if(LAZYLEN(stored_mutations) >= max_storage)
+ to_chat(usr,"Mutation storage is full.")
+ 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
+ stored_mutations += new result_path()
+ to_chat(usr, "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,"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,"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, "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"]))
+ radstrength = WRAP(value, 1, RADIATION_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"]))
+ radduration = WRAP(value, 1, RADIATION_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,"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,"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.uni_identity,
+ "UE"=scanner_occupant.dna.unique_enzymes,
+ "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")
+ // GUARD CHECK - This code shouldn't even be callable without a diskette
+ // inserted. Unexpected result
+ if(!diskette)
+ return
+
+ diskette.forceMove(drop_location())
+ diskette = null
+ 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
+ // "mixed" - Combination of both ue and ui
+ if("makeup_injector")
+ // 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,"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 radiation 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,"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 radiation 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["blood_type"])
+ to_chat(usr,"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"], "blood_type"=buffer_slot["blood_type"])
+
+ // If there is a connected scanner, we can use its upgrades to reduce
+ // the radiation 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
+ // "mixed" - Combination of both ue and ui
+ if("makeup_apply")
+ // 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)
+ 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
+ // "mixed" - Combination of both ue and ui
+ 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["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 and index to pulse. This is then managed
+ // later on in process()
+ var/len = length_char(scanner_occupant.dna.uni_identity)
+ rad_pulse_timer = world.time + (radduration*10)
+ rad_pulse_index = WRAP(text2num(params["index"]), 1, len+1)
+ begin_processing()
+ return
+
+ // Cancels the delayed action - In this context it is not the radiation
+ // 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 radiation 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,"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,"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,"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 rad increase since we'll be using it in all the possible
+ // operations
+ var/rad_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,"Genetic data corrupted, unable to apply genetic data.")
+ return FALSE
+ scanner_occupant.dna.uni_identity = buffer_slot["UI"]
+ scanner_occupant.updateappearance(mutations_overlay_update=1)
+ scanner_occupant.radiation += rad_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,"Genetic data corrupted, unable to apply genetic data.")
+ return FALSE
+ 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.radiation += rad_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["blood_type"])
+ to_chat(usr,"Genetic data corrupted, unable to apply genetic data.")
+ return FALSE
+ scanner_occupant.dna.uni_identity = buffer_slot["UI"]
+ scanner_occupant.updateappearance(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.radiation += rad_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()
+ if(!connected_scanner)
+ return FALSE
+
+ return (connected_scanner && 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 radiation bursts, so radiation 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_RADIMMUNE) && !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())
+ connected_scanner = test_scanner
+ connected_scanner.linked_console = src
+ 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))
+ connected_scanner = broken_scanner
+ connected_scanner.linked_console = src
+
+/**
+ * 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())
+ var/type = delayed_action["type"]
+ var/buffer_slot = delayed_action["buffer_slot"]
+ if(apply_genetic_makeup(type, buffer_slot))
+ to_chat(connected_scanner.occupant, "[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 radiation pulse action ongoing, we want to stop this.
+ // Imagine it being like a microwave stopping when you open the door.
+ rad_pulse_index = 0
+ rad_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=1, i <= NUMBER_OF_BUFFERS, i++)
+ 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
+ * * radduration - Duration of intended radiation pulse
+ * * number_of_blocks - Number of individual data blocks in the pulsed enzyme
+ */
+/obj/machinery/computer/scan_consolenew/proc/randomize_radiation_accuracy(position, radduration, number_of_blocks)
+ var/val = round(gaussian(0, RADIATION_ACCURACY_MULTIPLIER/radduration) + 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 radiation 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*RADIATION_STRENGTH_MULTIPLIER)
if(ran == 0)
@@ -930,98 +1941,48 @@
ran = -round(-ran) //positive, so ceiling it
return num2hex(WRAP(hex2num(input)+ran, 0, 16**length), length)
-/obj/machinery/computer/scan_consolenew/proc/randomize_radiation_accuracy(position, radduration, number_of_blocks)
- var/val = round(gaussian(0, RADIATION_ACCURACY_MULTIPLIER/radduration) + position, 1)
- return WRAP(val, 1, number_of_blocks+1)
+ /**
+ * Performs the enzyme radiation pulse.
+ *
+ * Donor code from previous DNA Console iteration. Called from process() when
+ * there is a radiation pulse in progress. Ends processing.
+ */
+/obj/machinery/computer/scan_consolenew/proc/rad_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())
+ rad_pulse_index = 0
+ end_processing()
+ return
-/obj/machinery/computer/scan_consolenew/proc/get_viable_occupant()
- var/mob/living/carbon/viable_occupant = null
- if(connected)
- viable_occupant = connected.occupant
- if(!istype(viable_occupant) || !viable_occupant.dna || HAS_TRAIT_NOT_FROM(viable_occupant, TRAIT_RADIMMUNE,BLOODSUCKER_TRAIT) || HAS_TRAIT(viable_occupant, TRAIT_NOCLONE))
- viable_occupant = null
- return viable_occupant
+ var/len = length_char(scanner_occupant.dna.uni_identity)
+ var/num = randomize_radiation_accuracy(rad_pulse_index, radduration + (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 = copytext_char(scanner_occupant.dna.uni_identity, num, num+1)
+ hex = scramble(hex, radstrength, radduration)
-/obj/machinery/computer/scan_consolenew/proc/apply_buffer(action,buffer_num)
- buffer_num = CLAMP(buffer_num, 1, NUMBER_OF_BUFFERS)
- var/list/buffer_slot = buffer[buffer_num]
- var/mob/living/carbon/viable_occupant = get_viable_occupant()
- if(istype(buffer_slot))
- viable_occupant.radiation += rand(100/(connected.damage_coeff ** 2),250/(connected.damage_coeff ** 2))
- //15 and 40 are just magic numbers that were here before so i didnt touch them, they are initial boundaries of damage
- //Each laser level reduces damage by lvl^2, so no effect on 1 lvl, 4 times less damage on 2 and 9 times less damage on 3
- //Numbers are this high because other way upgrading laser is just not worth the hassle, and i cant think of anything better to inmrove
- switch(action)
- if(SCANNER_ACTION_UI)
- if(buffer_slot["UI"])
- viable_occupant.dna.uni_identity = buffer_slot["UI"]
- viable_occupant.updateappearance(mutations_overlay_update=1)
- if(SCANNER_ACTION_UE)
- if(buffer_slot["name"] && buffer_slot["UE"] && buffer_slot["blood_type"])
- viable_occupant.real_name = buffer_slot["name"]
- viable_occupant.name = buffer_slot["name"]
- viable_occupant.dna.unique_enzymes = buffer_slot["UE"]
- viable_occupant.dna.blood_type = buffer_slot["blood_type"]
- if(SCANNER_ACTION_MIXED)
- if(buffer_slot["UI"])
- viable_occupant.dna.uni_identity = buffer_slot["UI"]
- viable_occupant.updateappearance(mutations_overlay_update=1)
- if(buffer_slot["name"] && buffer_slot["UE"] && buffer_slot["blood_type"])
- viable_occupant.real_name = buffer_slot["name"]
- viable_occupant.name = buffer_slot["name"]
- viable_occupant.dna.unique_enzymes = buffer_slot["UE"]
- viable_occupant.dna.blood_type = buffer_slot["blood_type"]
+ scanner_occupant.dna.uni_identity = copytext_char(scanner_occupant.dna.uni_identity, 1, num) + hex + copytext_char(scanner_occupant.dna.uni_identity, num + 1)
+ scanner_occupant.updateappearance(mutations_overlay_update=1)
-/obj/machinery/computer/scan_consolenew/proc/on_scanner_close()
- if(delayed_action && get_viable_occupant())
- to_chat(connected.occupant, "[src] activates!")
- apply_buffer(delayed_action["action"],delayed_action["buffer"])
- delayed_action = null //or make it stick + reset button ?
+ rad_pulse_index = 0
+ end_processing()
+ return
-/obj/machinery/computer/scan_consolenew/proc/get_valid_mutation(mutation)
- var/mob/living/carbon/C = get_viable_occupant()
- if(C)
- var/datum/mutation/human/HM = C.dna.get_mutation(mutation)
- if(HM)
- return HM
- for(var/datum/mutation/human/A in stored_mutations)
- if(A.type == mutation)
- return A
+/**
+ * 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"
-/obj/machinery/computer/scan_consolenew/proc/get_mutation_list(include_storage) //Returns a list of the mutation index types and any extra mutations
- var/mob/living/carbon/viable_occupant = get_viable_occupant()
- var/list/paths = list()
- if(viable_occupant)
- for(var/A in viable_occupant.dna.mutation_index)
- paths += A
- for(var/datum/mutation/human/A in viable_occupant.dna.mutations)
- if(A.class == MUT_EXTRA)
- paths += A.type
- if(include_storage)
- for(var/datum/mutation/human/A in stored_mutations)
- paths += A.type
- return paths
-
-/obj/machinery/computer/scan_consolenew/proc/get_valid_gene_string(mutation)
- var/mob/living/carbon/C = get_viable_occupant()
- if(C && (mutation in C.dna.mutation_index))
- return GET_GENE_STRING(mutation, C.dna)
- else if(C && (LAZYLEN(C.dna.mutations)))
- for(var/datum/mutation/human/A in C.dna.mutations)
- if(A.type == mutation)
- return GET_SEQUENCE(mutation)
- for(var/datum/mutation/human/A in stored_mutations)
- if(A.type == mutation)
- return GET_SEQUENCE(mutation)
-
-/obj/machinery/computer/scan_consolenew/proc/discover(mutation)
- if(stored_research && !(mutation in stored_research.discovered_mutations))
- stored_research.discovered_mutations += mutation
- return TRUE
-/////////////////////////// DNA MACHINES
#undef INJECTOR_TIMEOUT
#undef NUMBER_OF_BUFFERS
+#undef SCRAMBLE_TIMEOUT
+#undef JOKER_TIMEOUT
+#undef JOKER_UPGRADE
#undef RADIATION_STRENGTH_MAX
#undef RADIATION_STRENGTH_MULTIPLIER
@@ -1031,11 +1992,9 @@
#undef RADIATION_IRRADIATION_MULTIPLIER
-#undef SCANNER_ACTION_SE
-#undef SCANNER_ACTION_UI
-#undef SCANNER_ACTION_UE
-#undef SCANNER_ACTION_MIXED
+#undef STATUS_TRANSFORMING
-//#undef BAD_MUTATION_DIFFICULTY
-//#undef GOOD_MUTATION_DIFFICULTY
-//#undef OP_MUTATION_DIFFICULTY
+#undef SEARCH_OCCUPANT
+#undef SEARCH_STORED
+#undef SEARCH_DISKETTE
+#undef SEARCH_ADV_INJ
diff --git a/code/game/machinery/dna_scanner.dm b/code/game/machinery/dna_scanner.dm
index 4b2ba85d11..7b45bba4e2 100644
--- a/code/game/machinery/dna_scanner.dm
+++ b/code/game/machinery/dna_scanner.dm
@@ -1,20 +1,21 @@
/obj/machinery/dna_scannernew
name = "\improper DNA scanner"
desc = "It scans DNA structures."
- icon = 'icons/obj/Cryogenic2.dmi'
+ icon = 'icons/obj/machines/cloning.dmi'
icon_state = "scanner"
density = TRUE
use_power = IDLE_POWER_USE
idle_power_usage = 50
active_power_usage = 300
occupant_typecache = list(/mob/living, /obj/item/bodypart/head, /obj/item/organ/brain)
- circuit = /obj/item/circuitboard/machine/clonescanner
+ circuit = /obj/item/circuitboard/machine/dnascanner
var/locked = FALSE
var/damage_coeff
var/scan_level
var/precision_coeff
var/message_cooldown
var/breakout_time = 1200
+ var/obj/machinery/computer/scan_consolenew/linked_console = null
/obj/machinery/dna_scannernew/RefreshParts()
scan_level = 0
@@ -22,8 +23,8 @@
precision_coeff = 0
for(var/obj/item/stock_parts/scanning_module/P in component_parts)
scan_level += P.rating
- for(var/obj/item/stock_parts/matter_bin/P in component_parts)
- precision_coeff = P.rating
+ for(var/obj/item/stock_parts/matter_bin/M in component_parts)
+ precision_coeff = M.rating
for(var/obj/item/stock_parts/micro_laser/P in component_parts)
damage_coeff = P.rating
@@ -31,11 +32,8 @@
. = ..()
if(in_range(user, src) || isobserver(user))
. += "The status display reads: Radiation pulse accuracy increased by factor [precision_coeff**2].
Radiation pulse damage decreased by factor [damage_coeff**2]."
- if(scan_level >= 3)
- . += "Scanner has been upgraded to support autoprocessing."
/obj/machinery/dna_scannernew/update_icon_state()
-
//no power or maintenance
if(stat & (NOPOWER|BROKEN))
icon_state = initial(icon_state)+ (state_open ? "_open" : "") + "_unpowered"
@@ -53,10 +51,6 @@
//running
icon_state = initial(icon_state)+ (state_open ? "_open" : "")
-/obj/machinery/dna_scannernew/power_change()
- ..()
- update_icon()
-
/obj/machinery/dna_scannernew/proc/toggle_open(mob/user)
if(panel_open)
to_chat(user, "Close the maintenance panel first.")
@@ -80,7 +74,7 @@
user.last_special = world.time + CLICK_CD_BREAKOUT
user.visible_message("You see [user] kicking against the door of [src]!", \
"You lean on the back of [src] and start pushing the door open... (this will take about [DisplayTimeText(breakout_time)].)", \
- "You hear a metallic creaking from [src].")
+ "You hear a metallic creaking from [src].")
if(do_after(user,(breakout_time), target = src))
if(!user || user.stat != CONSCIOUS || user.loc != src || state_open || !locked)
return
@@ -96,33 +90,28 @@
return C
return null
-/obj/machinery/dna_scannernew/close_machine(atom/movable/target)
+/obj/machinery/dna_scannernew/close_machine(mob/living/carbon/user)
if(!state_open)
return FALSE
- ..(target)
-
- // search for ghosts, if the corpse is empty and the scanner is connected to a cloner
- var/mob/living/mob_occupant = get_mob_or_brainmob(occupant)
- if(istype(mob_occupant))
- if(locate_computer(/obj/machinery/computer/cloning))
- if(!mob_occupant.suiciding && !(HAS_TRAIT(mob_occupant, TRAIT_NOCLONE)) && !mob_occupant.hellbound)
- mob_occupant.notify_ghost_cloning("Your corpse has been placed into a cloning scanner. Re-enter your corpse if you want to be cloned!", source = src)
+ ..(user)
// DNA manipulators cannot operate on severed heads or brains
- if(isliving(occupant))
- var/obj/machinery/computer/scan_consolenew/console = locate_computer(/obj/machinery/computer/scan_consolenew)
- if(console)
- console.on_scanner_close()
+ if(iscarbon(occupant))
+ if(linked_console)
+ linked_console.on_scanner_close()
return TRUE
/obj/machinery/dna_scannernew/open_machine()
- if(state_open || panel_open)
+ if(state_open)
return FALSE
..()
+ if(linked_console)
+ linked_console.on_scanner_open()
+
return TRUE
/obj/machinery/dna_scannernew/relaymove(mob/user as mob)
@@ -133,51 +122,49 @@
return
open_machine()
-/obj/machinery/dna_scannernew/screwdriver_act(mob/living/user, obj/item/I)
- . = TRUE
- if(..())
- return
- if(occupant)
- to_chat(user, "[src] is currently occupied!")
- return
- if(state_open)
- to_chat(user, "[src] must be closed to [panel_open ? "close" : "open"] its maintenance hatch!")
- return
- if(default_deconstruction_screwdriver(user, icon_state, icon_state, I)) //sent icon_state is irrelevant...
- update_icon() //..since we're updating the icon here, since the scanner can be unpowered when opened/closed
- return
- return FALSE
+/obj/machinery/dna_scannernew/attackby(obj/item/I, mob/user, params)
-/obj/machinery/dna_scannernew/wrench_act(mob/living/user, obj/item/I)
- . = ..()
- if(default_change_direction_wrench(user, I))
- return TRUE
+ if(!occupant && default_deconstruction_screwdriver(user, icon_state, icon_state, I))//sent icon_state is irrelevant...
+ update_icon()//..since we're updating the icon here, since the scanner can be unpowered when opened/closed
+ return
-/obj/machinery/dna_scannernew/crowbar_act(mob/living/user, obj/item/I)
- . = ..()
if(default_pry_open(I))
- return TRUE
- if(default_deconstruction_crowbar(I))
- return TRUE
+ return
-/obj/machinery/dna_scannernew/default_pry_open(obj/item/I) //wew
- . = !(state_open || panel_open || (flags_1 & NODECONSTRUCT_1)) && I.tool_behaviour == TOOL_CROWBAR
- if(.)
- I.play_tool_sound(src, 50)
- visible_message("[usr] pries open [src].", "You pry open [src].")
- open_machine()
+ if(default_deconstruction_crowbar(I))
+ return
+
+ return ..()
/obj/machinery/dna_scannernew/interact(mob/user)
toggle_open(user)
-/obj/machinery/dna_scannernew/AltClick(mob/user)
- . = ..()
- if(!user.canUseTopic(src, !hasSiliconAccessInArea(user)))
- return
- interact(user)
- return TRUE
-
/obj/machinery/dna_scannernew/MouseDrop_T(mob/target, mob/user)
- if(user.stat || user.lying || !Adjacent(user) || !user.Adjacent(target) || !iscarbon(target) || !user.IsAdvancedToolUser())
+ var/mob/living/L = user
+ if(user.stat || (isliving(user) && (!(L.mobility_flags & MOBILITY_STAND) || !(L.mobility_flags & MOBILITY_UI))) || !Adjacent(user) || !user.Adjacent(target) || !iscarbon(target) || !user.IsAdvancedToolUser())
return
close_machine(target)
+
+
+//Just for transferring between genetics machines.
+/obj/item/disk/data
+ name = "DNA data disk"
+ icon_state = "datadisk0" //Gosh I hope syndies don't mistake them for the nuke disk.
+ var/list/genetic_makeup_buffer = list()
+ var/list/fields = list()
+ var/list/mutations = list()
+ var/max_mutations = 6
+ var/read_only = FALSE //Well,it's still a floppy disk
+
+/obj/item/disk/data/Initialize()
+ . = ..()
+ icon_state = "datadisk[rand(0,6)]"
+ add_overlay("datadisk_gene")
+
+/obj/item/disk/data/attack_self(mob/user)
+ read_only = !read_only
+ to_chat(user, "You flip the write-protect tab to [read_only ? "protected" : "unprotected"].")
+
+/obj/item/disk/data/examine(mob/user)
+ . = ..()
+ . += "The write-protect tab is set to [read_only ? "protected" : "unprotected"]."
diff --git a/modular_citadel/interface/skin.dmf b/modular_citadel/interface/skin.dmf
index 025a53bdca..faf583a74b 100644
--- a/modular_citadel/interface/skin.dmf
+++ b/modular_citadel/interface/skin.dmf
@@ -45,7 +45,11 @@ menu "menu"
command = "hotkeys-help"
category = "&Help"
saved-params = "is-checked"
-
+ elem
+ name = "&Hotkeys"
+ command = "hotkeys-help"
+ category = "&Help"
+ saved-params = "is-checked"
window "mainwindow"
elem "mainwindow"
@@ -285,11 +289,23 @@ window "statwindow"
tab-background-color = #242424
prefix-color = #e0e0e0
suffix-color = #e0e0e0
-
+
+window "popupwindow"
+ elem "popupwindow"
+ type = MAIN
+ pos = 281,0
+ size = 120x120
+ anchor1 = none
+ anchor2 = none
+ background-color = none
+ is-visible = false
+ saved-params = "pos;size;is-minimized;is-maximized"
+ statusbar = false
+ can-resize = false
window "preferences_window"
elem "preferences_window"
type = MAIN
- pos = 372,0
+ pos = 281,0
size = 1280x1000
anchor1 = none
anchor2 = none
diff --git a/tgstation.dme b/tgstation.dme
index 525cf1ed9e..ad39ed6ae6 100755
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -217,6 +217,7 @@
#include "code\_onclick\hud\hud.dm"
#include "code\_onclick\hud\human.dm"
#include "code\_onclick\hud\lavaland_elite.dm"
+#include "code\_onclick\hud\map_popups.dm"
#include "code\_onclick\hud\monkey.dm"
#include "code\_onclick\hud\movable_screen_objects.dm"
#include "code\_onclick\hud\parallax.dm"