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