diff --git a/code/__defines/machinery.dm b/code/__defines/machinery.dm index 53e47d9acfe..b8afc5cb720 100644 --- a/code/__defines/machinery.dm +++ b/code/__defines/machinery.dm @@ -163,3 +163,8 @@ if (!(DATUM.datum_flags & DF_ISPROCESSING)) {\ #define START_PROCESSING_POWER_OBJECT(Datum) START_PROCESSING_IN_LIST(Datum, global.processing_power_items) #define STOP_PROCESSING_POWER_OBJECT(Datum) STOP_PROCESSING_IN_LIST(Datum, global.processing_power_items) + +// Computer login types +#define LOGIN_TYPE_NORMAL 1 +#define LOGIN_TYPE_AI 2 +#define LOGIN_TYPE_ROBOT 3 \ No newline at end of file diff --git a/code/__defines/tgui.dm b/code/__defines/tgui.dm index a6708c8bb7a..3d706a4e0fa 100644 --- a/code/__defines/tgui.dm +++ b/code/__defines/tgui.dm @@ -16,4 +16,14 @@ /// Get a window id based on the provided pool index #define TGUI_WINDOW_ID(index) "tgui-window-[index]" /// Get a pool index of the provided window id -#define TGUI_WINDOW_INDEX(window_id) text2num(copytext(window_id, 13)) \ No newline at end of file +#define TGUI_WINDOW_INDEX(window_id) text2num(copytext(window_id, 13)) + +/// Max length for Modal Input +#define TGUI_MODAL_INPUT_MAX_LENGTH 1024 +/// Max length for Modal Input for names +#define TGUI_MODAL_INPUT_MAX_LENGTH_NAME 64 // Names for generally anything don't go past 32, let alone 64. + +#define TGUI_MODAL_OPEN 1 +#define TGUI_MODAL_DELEGATE 2 +#define TGUI_MODAL_ANSWER 3 +#define TGUI_MODAL_CLOSE 4 \ No newline at end of file diff --git a/code/_helpers/game.dm b/code/_helpers/game.dm index 194ec86d3d9..40dbaf49cbe 100644 --- a/code/_helpers/game.dm +++ b/code/_helpers/game.dm @@ -636,4 +636,62 @@ datum/projectile_data min(list_x), min(list_y), max(list_x), - max(list_y)) \ No newline at end of file + max(list_y)) + +// Will recursively loop through an atom's contents and check for mobs, then it will loop through every atom in that atom's contents. +// It will keep doing this until it checks every content possible. This will fix any problems with mobs, that are inside objects, +// being unable to hear people due to being in a box within a bag. + +/proc/recursive_mob_check(var/atom/O, var/list/L = list(), var/recursion_limit = 3, var/client_check = 1, var/sight_check = 1, var/include_radio = 1) + + //GLOB.debug_mob += O.contents.len + if(!recursion_limit) + return L + for(var/atom/A in O.contents) + + if(ismob(A)) + var/mob/M = A + if(client_check && !M.client) + L |= recursive_mob_check(A, L, recursion_limit - 1, client_check, sight_check, include_radio) + continue + if(sight_check && !isInSight(A, O)) + continue + L |= M + //log_world("[recursion_limit] = [M] - [get_turf(M)] - ([M.x], [M.y], [M.z])") + + else if(include_radio && istype(A, /obj/item/radio)) + if(sight_check && !isInSight(A, O)) + continue + L |= A + + if(isobj(A) || ismob(A)) + L |= recursive_mob_check(A, L, recursion_limit - 1, client_check, sight_check, include_radio) + return L + +// The old system would loop through lists for a total of 5000 per function call, in an empty server. +// This new system will loop at around 1000 in an empty server. + +/proc/get_mobs_in_view(var/R, var/atom/source, var/include_clientless = FALSE) + // Returns a list of mobs in range of R from source. Used in radio and say code. + + var/turf/T = get_turf(source) + var/list/hear = list() + + if(!T) + return hear + + var/list/range = hear(R, T) + + for(var/atom/A in range) + if(ismob(A)) + var/mob/M = A + if(M.client || include_clientless) + hear += M + //log_world("Start = [M] - [get_turf(M)] - ([M.x], [M.y], [M.z])") + else if(istype(A, /obj/item/radio)) + hear += A + + if(isobj(A) || ismob(A)) + hear |= recursive_mob_check(A, hear, 3, 1, 0, 1) + + return hear \ No newline at end of file diff --git a/code/datums/datacore.dm b/code/datums/datacore.dm index be5f6b33db3..ff483432edd 100644 --- a/code/datums/datacore.dm +++ b/code/datums/datacore.dm @@ -445,6 +445,8 @@ var/global/list/PDA_Manifest = list() G.fields["religion"] = "Unknown" G.fields["photo_front"] = front G.fields["photo_side"] = side + G.fields["photo-south"] = "'data:image/png;base64,[icon2base64(front)]'" + G.fields["photo-west"] = "'data:image/png;base64,[icon2base64(side)]'" G.fields["notes"] = "No notes found." if(hidden) hidden_general += G diff --git a/code/game/atoms.dm b/code/game/atoms.dm index b1d994d3a36..1229a0a9cb6 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -12,6 +12,8 @@ var/throwpass = 0 var/germ_level = GERM_LEVEL_AMBIENT // The higher the germ level, the more germ on the atom. var/simulated = 1 //filter for actions - used by lighting overlays + var/atom_say_verb = "says" + var/bubble_icon = "normal" ///what icon the atom uses for speechbubbles var/fluorescent // Shows up under a UV light. var/last_bumped = 0 @@ -610,3 +612,20 @@ "} var/turf/T = get_turf(src) . += "
[ADMIN_COORDJMP(T)]" + +/atom/proc/atom_say(message) + if(!message) + return + var/list/speech_bubble_hearers = list() + for(var/mob/M in get_mobs_in_view(7, src)) + M.show_message("[src] [atom_say_verb], \"[message]\"", 2, null, 1) + if(M.client) + speech_bubble_hearers += M.client + + if(length(speech_bubble_hearers)) + var/image/I = image('icons/mob/talk.dmi', src, "[bubble_icon][say_test(message)]", FLY_LAYER) + I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA + INVOKE_ASYNC(GLOBAL_PROC, /.proc/flick_overlay, I, speech_bubble_hearers, 30) + +/atom/proc/speech_bubble(bubble_state = "", bubble_loc = src, list/bubble_recipients = list()) + return diff --git a/code/game/dna/dna_modifier.dm b/code/game/dna/dna_modifier.dm index b9b8f6db8a9..a72c92d47c3 100644 --- a/code/game/dna/dna_modifier.dm +++ b/code/game/dna/dna_modifier.dm @@ -5,6 +5,11 @@ #define DNA2_BUF_UE 2 #define DNA2_BUF_SE 4 +#define PAGE_UI "ui" +#define PAGE_SE "se" +#define PAGE_BUFFER "buffer" +#define PAGE_REJUVENATORS "rejuvenators" + //list("data" = null, "owner" = null, "label" = null, "type" = null, "ue" = 0), /datum/dna2/record var/datum/dna/dna = null @@ -38,6 +43,22 @@ ser["type"] = "se" return ser +/datum/dna2/record/proc/copy() + var/datum/dna2/record/newrecord = new /datum/dna2/record + newrecord.dna = dna.Clone() + newrecord.types = types + newrecord.name = name + newrecord.mind = mind + newrecord.ckey = ckey + newrecord.languages = languages + newrecord.implant = implant + newrecord.flavor = flavor + newrecord.gender = gender + newrecord.body_descriptors = body_descriptors.Copy() + newrecord.genetic_modifiers = genetic_modifiers.Copy() + return newrecord + + /////////////////////////// DNA MACHINES /obj/machinery/dna_scannernew name = "\improper DNA modifier" @@ -55,13 +76,28 @@ var/mob/living/carbon/occupant = null var/obj/item/weapon/reagent_containers/glass/beaker = null var/opened = 0 + var/damage_coeff + var/scan_level + var/precision_coeff /obj/machinery/dna_scannernew/Initialize() . = ..() default_apply_parts() + RefreshParts() + +/obj/machinery/dna_scannernew/RefreshParts() + scan_level = 0 + damage_coeff = 0 + precision_coeff = 0 + for(var/obj/item/weapon/stock_parts/scanning_module/P in component_parts) + scan_level += P.rating + for(var/obj/item/weapon/stock_parts/manipulator/P in component_parts) + precision_coeff = P.rating + for(var/obj/item/weapon/stock_parts/micro_laser/P in component_parts) + damage_coeff = P.rating /obj/machinery/dna_scannernew/relaymove(mob/user as mob) - if (user.stat) + if(user.stat) return src.go_out() return @@ -71,7 +107,7 @@ set category = "Object" set name = "Eject DNA Scanner" - if (usr.stat != 0) + if(usr.stat != 0) return eject_occupant() @@ -98,15 +134,15 @@ set category = "Object" set name = "Enter DNA Scanner" - if (usr.stat != 0) + if(usr.stat != 0) return - if (!ishuman(usr) && !issmall(usr)) //Make sure they're a mob that has dna + if(!ishuman(usr) && !issmall(usr)) //Make sure they're a mob that has dna to_chat(usr, "Try as you might, you can not climb up into the scanner.") return - if (src.occupant) + if(src.occupant) to_chat(usr, "The scanner is already occupied!") return - if (usr.abiotic()) + if(usr.abiotic()) to_chat(usr, "The subject cannot have abiotic items on.") return usr.stop_pulling() @@ -116,7 +152,7 @@ src.occupant = usr src.icon_state = "scanner_1" src.add_fingerprint(usr) - return + SStgui.update_uis(src) /obj/machinery/dna_scannernew/attackby(var/obj/item/weapon/item as obj, var/mob/user as mob) if(istype(item, /obj/item/weapon/reagent_containers/glass)) @@ -128,10 +164,11 @@ user.drop_item() item.loc = src user.visible_message("\The [user] adds \a [item] to \the [src]!", "You add \a [item] to \the [src]!") + SStgui.update_uis(src) return else if(istype(item, /obj/item/organ/internal/brain)) - if (src.occupant) + if(src.occupant) to_chat(user, "The scanner is already occupied!") return var/obj/item/organ/internal/brain/brain = item @@ -141,19 +178,20 @@ put_in(brain.brainmob) src.add_fingerprint(user) user.visible_message("\The [user] adds \a [item] to \the [src]!", "You add \a [item] to \the [src]!") + SStgui.update_uis(src) return else to_chat(user, "\The [brain] is not acceptable for genetic sampling!") - else if (!istype(item, /obj/item/weapon/grab)) + else if(!istype(item, /obj/item/weapon/grab)) return var/obj/item/weapon/grab/G = item - if (!ismob(G.affecting)) + if(!ismob(G.affecting)) return - if (src.occupant) + if(src.occupant) to_chat(user, "The scanner is already occupied!") return - if (G.affecting.abiotic()) + if(G.affecting.abiotic()) to_chat(user, "The subject cannot have abiotic items on.") return put_in(G.affecting) @@ -180,12 +218,12 @@ if(ghost.mind == M.mind) to_chat(ghost, "Your corpse has been placed into a cloning scanner. Return to your body if you want to be resurrected/cloned! (Verbs -> Ghost -> Re-enter corpse)") break - return + SStgui.update_uis(src) /obj/machinery/dna_scannernew/proc/go_out() - if ((!( src.occupant ) || src.locked)) + if((!( src.occupant ) || src.locked)) return - if (src.occupant.client) + if(src.occupant.client) src.occupant.client.eye = src.occupant.client.mob src.occupant.client.perspective = MOB_PERSPECTIVE if(istype(occupant,/mob/living/carbon/brain)) @@ -198,7 +236,7 @@ src.occupant.loc = src.loc src.occupant = null src.icon_state = "scanner_0" - return + SStgui.update_uis(src) /obj/machinery/dna_scannernew/ex_act(severity) switch(severity) @@ -211,7 +249,7 @@ qdel(src) return if(2.0) - if (prob(50)) + if(prob(50)) for(var/atom/movable/A as mob|obj in src) A.loc = src.loc ex_act(severity) @@ -220,7 +258,7 @@ qdel(src) return if(3.0) - if (prob(25)) + if(prob(25)) for(var/atom/movable/A as mob|obj in src) A.loc = src.loc ex_act(severity) @@ -251,21 +289,20 @@ var/injector_ready = 0 //Quick fix for issue 286 (screwdriver the screen twice to restore injector) -Pete var/obj/machinery/dna_scannernew/connected = null var/obj/item/weapon/disk/data/disk = null - var/selected_menu_key = null + var/selected_menu_key = PAGE_UI anchored = 1 use_power = USE_POWER_IDLE idle_power_usage = 10 active_power_usage = 400 - var/waiting_for_user_input=0 // Fix for #274 (Mash create block injector without answering dialog to make unlimited injectors) - N3X /obj/machinery/computer/scan_consolenew/attackby(obj/item/I as obj, mob/user as mob) - if (istype(I, /obj/item/weapon/disk/data)) //INSERT SOME diskS - if (!src.disk) + if(istype(I, /obj/item/weapon/disk/data)) //INSERT SOME diskS + if(!src.disk) user.drop_item() I.loc = src src.disk = I to_chat(user, "You insert [I].") - SSnanoui.update_uis(src) // update all UIs attached to src + SStgui.update_uis(src) // update all UIs attached to src return else ..() @@ -279,7 +316,7 @@ qdel(src) return if(2.0) - if (prob(50)) + if(prob(50)) //SN src = null qdel(src) return @@ -315,35 +352,28 @@ /obj/machinery/computer/scan_consolenew/process() //not really used right now if(stat & (NOPOWER|BROKEN)) return - if (!( src.status )) //remove this + if(!( src.status )) //remove this return return */ /obj/machinery/computer/scan_consolenew/attack_ai(user as mob) src.add_hiddenprint(user) - ui_interact(user) + tgui_interact(user) /obj/machinery/computer/scan_consolenew/attack_hand(user as mob) if(!..()) - ui_interact(user) - - /** - * The ui_interact proc is used to open and update Nano UIs - * If ui_interact is not used then the UI will not update correctly - * ui_interact is currently defined for /atom/movable (which is inherited by /obj and /mob) - * - * @param user /mob The mob who is interacting with this ui - * @param ui_key string A string key to use for this ui. Allows for multiple unique uis on one obj/mob (defaut value "main") - * @param ui /datum/nanoui This parameter is passed by the nanoui process() proc when updating an open ui - * - * @return nothing - */ -/obj/machinery/computer/scan_consolenew/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) + tgui_interact(user) +/obj/machinery/computer/scan_consolenew/tgui_interact(mob/user, datum/tgui/ui) if(!connected || user == connected.occupant || user.stat) return + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "DNAModifier", name) + ui.open() +/obj/machinery/computer/scan_consolenew/tgui_data(mob/user) // this is the data which will be sent to the ui var/data[0] data["selectedMenuKey"] = selected_menu_key @@ -355,7 +385,7 @@ data["hasDisk"] = disk ? 1 : 0 var/diskData[0] - if (!disk || !disk.buf) + if(!disk || !disk.buf) diskData["data"] = null diskData["owner"] = null diskData["label"] = null @@ -383,7 +413,7 @@ data["selectedUITargetHex"] = selected_ui_target_hex var/occupantData[0] - if (!src.connected.occupant || !src.connected.occupant.dna) + if(!src.connected.occupant || !src.connected.occupant.dna) occupantData["name"] = null occupantData["stat"] = null occupantData["isViableSubject"] = null @@ -398,7 +428,7 @@ occupantData["name"] = connected.occupant.real_name occupantData["stat"] = connected.occupant.stat occupantData["isViableSubject"] = 1 - if (NOCLONE in connected.occupant.mutations || !src.connected.occupant.dna) + if(NOCLONE in connected.occupant.mutations || !src.connected.occupant.dna) occupantData["isViableSubject"] = 0 occupantData["health"] = connected.occupant.health occupantData["maxHealth"] = connected.occupant.maxHealth @@ -414,423 +444,357 @@ data["beakerVolume"] = 0 if(connected.beaker) data["beakerLabel"] = connected.beaker.label_text ? connected.beaker.label_text : null - if (connected.beaker.reagents && connected.beaker.reagents.reagent_list.len) + if(connected.beaker.reagents && connected.beaker.reagents.reagent_list.len) for(var/datum/reagent/R in connected.beaker.reagents.reagent_list) data["beakerVolume"] += R.volume - // update the ui if it exists, returns null if no ui is passed/found - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open) - if (!ui) - // the ui does not exist, so we'll create a new() one - // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm - ui = new(user, src, ui_key, "dna_modifier.tmpl", "DNA Modifier Console", 660, 700) - // when the ui is first opened this is the data it will use - ui.set_initial_data(data) - // open the new ui window - ui.open() - // auto update every Master Controller tick - ui.set_auto_update(1) + // Transfer modal information if there is one + data["modal"] = tgui_modal_data(src) -/obj/machinery/computer/scan_consolenew/Topic(href, href_list) + return data + +/obj/machinery/computer/scan_consolenew/tgui_act(action, params) if(..()) - return 0 // don't update uis + return FALSE // don't update uis if(!istype(usr.loc, /turf)) - return 0 // don't update uis + return FALSE // don't update uis if(!src || !src.connected) - return 0 // don't update uis + return FALSE // don't update uis if(irradiating) // Make sure that it isn't already irradiating someone... - return 0 // don't update uis + return FALSE // don't update uis add_fingerprint(usr) - if (href_list["selectMenuKey"]) - selected_menu_key = href_list["selectMenuKey"] - return 1 // return 1 forces an update to all Nano uis attached to src + if(tgui_act_modal(action, params)) + return TRUE - if (href_list["toggleLock"]) - if ((src.connected && src.connected.occupant)) - src.connected.locked = !( src.connected.locked ) - return 1 // return 1 forces an update to all Nano uis attached to src - - if (href_list["pulseRadiation"]) - irradiating = src.radiation_duration - var/lock_state = src.connected.locked - src.connected.locked = 1//lock it - SSnanoui.update_uis(src) // update all UIs attached to src - - sleep(10*src.radiation_duration) // sleep for radiation_duration seconds - - irradiating = 0 - - if (!src.connected.occupant) - return 1 // return 1 forces an update to all Nano uis attached to src - - if (prob(95)) - if(prob(75)) - randmutb(src.connected.occupant) - else - randmuti(src.connected.occupant) - else - if(prob(95)) - randmutg(src.connected.occupant) - else - randmuti(src.connected.occupant) - - src.connected.occupant.apply_effect(((src.radiation_intensity*3)+src.radiation_duration*3), IRRADIATE, check_protection = 0) - src.connected.locked = lock_state - return 1 // return 1 forces an update to all Nano uis attached to src - - if (href_list["radiationDuration"]) - if (text2num(href_list["radiationDuration"]) > 0) - if (src.radiation_duration < 20) - src.radiation_duration += 2 - else - if (src.radiation_duration > 2) - src.radiation_duration -= 2 - return 1 // return 1 forces an update to all Nano uis attached to src - - if (href_list["radiationIntensity"]) - if (text2num(href_list["radiationIntensity"]) > 0) - if (src.radiation_intensity < 10) - src.radiation_intensity++ - else - if (src.radiation_intensity > 1) - src.radiation_intensity-- - return 1 // return 1 forces an update to all Nano uis attached to src - - //////////////////////////////////////////////////////// - - if (href_list["changeUITarget"] && text2num(href_list["changeUITarget"]) > 0) - if (src.selected_ui_target < 15) - src.selected_ui_target++ - src.selected_ui_target_hex = src.selected_ui_target - switch(selected_ui_target) - if(10) - src.selected_ui_target_hex = "A" - if(11) - src.selected_ui_target_hex = "B" - if(12) - src.selected_ui_target_hex = "C" - if(13) - src.selected_ui_target_hex = "D" - if(14) - src.selected_ui_target_hex = "E" - if(15) - src.selected_ui_target_hex = "F" - else - src.selected_ui_target = 0 - src.selected_ui_target_hex = 0 - return 1 // return 1 forces an update to all Nano uis attached to src - - if (href_list["changeUITarget"] && text2num(href_list["changeUITarget"]) < 1) - if (src.selected_ui_target > 0) - src.selected_ui_target-- - src.selected_ui_target_hex = src.selected_ui_target - switch(selected_ui_target) - if(10) - src.selected_ui_target_hex = "A" - if(11) - src.selected_ui_target_hex = "B" - if(12) - src.selected_ui_target_hex = "C" - if(13) - src.selected_ui_target_hex = "D" - if(14) - src.selected_ui_target_hex = "E" - else - src.selected_ui_target = 15 - src.selected_ui_target_hex = "F" - return 1 // return 1 forces an update to all Nano uis attached to src - - if (href_list["selectUIBlock"] && href_list["selectUISubblock"]) // This chunk of code updates selected block / sub-block based on click - var/select_block = text2num(href_list["selectUIBlock"]) - var/select_subblock = text2num(href_list["selectUISubblock"]) - if ((select_block <= DNA_UI_LENGTH) && (select_block >= 1)) - src.selected_ui_block = select_block - if ((select_subblock <= DNA_BLOCK_SIZE) && (select_subblock >= 1)) - src.selected_ui_subblock = select_subblock - return 1 // return 1 forces an update to all Nano uis attached to src - - if (href_list["pulseUIRadiation"]) - var/block = src.connected.occupant.dna.GetUISubBlock(src.selected_ui_block,src.selected_ui_subblock) - - irradiating = src.radiation_duration - var/lock_state = src.connected.locked - src.connected.locked = 1//lock it - SSnanoui.update_uis(src) // update all UIs attached to src - - sleep(10*src.radiation_duration) // sleep for radiation_duration seconds - - irradiating = 0 - - if (!src.connected.occupant) - return 1 - - if (prob((80 + (src.radiation_duration / 2)))) - block = miniscrambletarget(num2text(selected_ui_target), src.radiation_intensity, src.radiation_duration) - src.connected.occupant.dna.SetUISubBlock(src.selected_ui_block,src.selected_ui_subblock,block) - src.connected.occupant.UpdateAppearance() - src.connected.occupant.apply_effect((src.radiation_intensity+src.radiation_duration), IRRADIATE, check_protection = 0) - else - if (prob(20+src.radiation_intensity)) - randmutb(src.connected.occupant) - domutcheck(src.connected.occupant,src.connected) - else - randmuti(src.connected.occupant) - src.connected.occupant.UpdateAppearance() - src.connected.occupant.apply_effect(((src.radiation_intensity*2)+src.radiation_duration), IRRADIATE, check_protection = 0) - src.connected.locked = lock_state - return 1 // return 1 forces an update to all Nano uis attached to src - - //////////////////////////////////////////////////////// - - if (href_list["injectRejuvenators"]) - if (!connected.occupant) - return 0 - var/inject_amount = round(text2num(href_list["injectRejuvenators"]), 5) // round to nearest 5 - if (inject_amount < 0) // Since the user can actually type the commands himself, some sanity checking - inject_amount = 0 - if (inject_amount > 50) - inject_amount = 50 - connected.beaker.reagents.trans_to_mob(connected.occupant, inject_amount, CHEM_BLOOD) - return 1 // return 1 forces an update to all Nano uis attached to src - - //////////////////////////////////////////////////////// - - if (href_list["selectSEBlock"] && href_list["selectSESubblock"]) // This chunk of code updates selected block / sub-block based on click (se stands for strutural enzymes) - var/select_block = text2num(href_list["selectSEBlock"]) - var/select_subblock = text2num(href_list["selectSESubblock"]) - if ((select_block <= DNA_SE_LENGTH) && (select_block >= 1)) - src.selected_se_block = select_block - if ((select_subblock <= DNA_BLOCK_SIZE) && (select_subblock >= 1)) - src.selected_se_subblock = select_subblock - //testing("User selected block [selected_se_block] (sent [select_block]), subblock [selected_se_subblock] (sent [select_block]).") - return 1 // return 1 forces an update to all Nano uis attached to src - - if (href_list["pulseSERadiation"]) - var/block = src.connected.occupant.dna.GetSESubBlock(src.selected_se_block,src.selected_se_subblock) - //var/original_block=block - //testing("Irradiating SE block [src.selected_se_block]:[src.selected_se_subblock] ([block])...") - - irradiating = src.radiation_duration - var/lock_state = src.connected.locked - src.connected.locked = 1 //lock it - SSnanoui.update_uis(src) // update all UIs attached to src - - sleep(10*src.radiation_duration) // sleep for radiation_duration seconds - - irradiating = 0 - - if(src.connected.occupant) - if (prob((80 + (src.radiation_duration / 2)))) - // FIXME: Find out what these corresponded to and change them to the WHATEVERBLOCK they need to be. - //if ((src.selected_se_block != 2 || src.selected_se_block != 12 || src.selected_se_block != 8 || src.selected_se_block || 10) && prob (20)) - var/real_SE_block=selected_se_block - block = miniscramble(block, src.radiation_intensity, src.radiation_duration) - if(prob(20)) - if (src.selected_se_block > 1 && src.selected_se_block < DNA_SE_LENGTH/2) - real_SE_block++ - else if (src.selected_se_block > DNA_SE_LENGTH/2 && src.selected_se_block < DNA_SE_LENGTH) - real_SE_block-- - - //testing("Irradiated SE block [real_SE_block]:[src.selected_se_subblock] ([original_block] now [block]) [(real_SE_block!=selected_se_block) ? "(SHIFTED)":""]!") - connected.occupant.dna.SetSESubBlock(real_SE_block,selected_se_subblock,block) - src.connected.occupant.apply_effect((src.radiation_intensity+src.radiation_duration), IRRADIATE, check_protection = 0) - domutcheck(src.connected.occupant,src.connected) - else - src.connected.occupant.apply_effect(((src.radiation_intensity*2)+src.radiation_duration), IRRADIATE, check_protection = 0) - if (prob(80-src.radiation_duration)) - //testing("Random bad mut!") - randmutb(src.connected.occupant) - domutcheck(src.connected.occupant,src.connected) - else - randmuti(src.connected.occupant) - //testing("Random identity mut!") - src.connected.occupant.UpdateAppearance() - src.connected.locked = lock_state - return 1 // return 1 forces an update to all Nano uis attached to src - - if(href_list["ejectBeaker"]) - if(connected.beaker) - var/obj/item/weapon/reagent_containers/glass/B = connected.beaker - B.loc = connected.loc - connected.beaker = null - return 1 - - if(href_list["ejectOccupant"]) - connected.eject_occupant() - return 1 - - // Transfer Buffer Management - if(href_list["bufferOption"]) - var/bufferOption = href_list["bufferOption"] - - // These bufferOptions do not require a bufferId - if (bufferOption == "wipeDisk") - if ((isnull(src.disk)) || (src.disk.read_only)) - //src.temphtml = "Invalid disk. Please try again." - return 0 - - src.disk.buf=null - //src.temphtml = "Data saved." - return 1 - - if (bufferOption == "ejectDisk") - if (!src.disk) + . = TRUE + switch(action) + if("selectMenuKey") + var/key = params["key"] + if(!(key in list(PAGE_UI, PAGE_SE, PAGE_BUFFER, PAGE_REJUVENATORS))) return - src.disk.loc = get_turf(src) - src.disk = null - return 1 + selected_menu_key = key + if("toggleLock") + if(connected && connected.occupant) + connected.locked = !(connected.locked) - // All bufferOptions from here on require a bufferId - if (!href_list["bufferId"]) - return 0 + if("pulseRadiation") + irradiating = radiation_duration + var/lock_state = connected.locked + connected.locked = TRUE //lock it - var/bufferId = text2num(href_list["bufferId"]) - - if (bufferId < 1 || bufferId > 3) - return 0 // Not a valid buffer id - - if (bufferOption == "saveUI") - if(src.connected.occupant && src.connected.occupant.dna) - var/datum/dna2/record/databuf=new - databuf.types = DNA2_BUF_UE - databuf.dna = src.connected.occupant.dna.Clone() - if(ishuman(connected.occupant)) - var/mob/living/carbon/human/H = connected.occupant - databuf.dna.real_name = H.dna.real_name - databuf.gender = H.gender - databuf.body_descriptors = H.descriptors - databuf.name = "Unique Identifier" - src.buffers[bufferId] = databuf - return 1 - - if (bufferOption == "saveUIAndUE") - if(src.connected.occupant && src.connected.occupant.dna) - var/datum/dna2/record/databuf=new - databuf.types = DNA2_BUF_UI|DNA2_BUF_UE - databuf.dna = src.connected.occupant.dna.Clone() - if(ishuman(connected.occupant)) - var/mob/living/carbon/human/H = connected.occupant - databuf.dna.real_name = H.dna.real_name - databuf.gender = H.gender - databuf.body_descriptors = H.descriptors - databuf.name = "Unique Identifier + Unique Enzymes" - src.buffers[bufferId] = databuf - return 1 - - if (bufferOption == "saveSE") - if(src.connected.occupant && src.connected.occupant.dna) - var/datum/dna2/record/databuf=new - databuf.types = DNA2_BUF_SE - databuf.dna = src.connected.occupant.dna.Clone() - if(ishuman(connected.occupant)) - var/mob/living/carbon/human/H = connected.occupant - databuf.dna.real_name = H.dna.real_name - databuf.gender = H.gender - databuf.body_descriptors = H.descriptors - databuf.name = "Structural Enzymes" - src.buffers[bufferId] = databuf - return 1 - - if (bufferOption == "clear") - src.buffers[bufferId]=new /datum/dna2/record() - return 1 - - if (bufferOption == "changeLabel") - var/datum/dna2/record/buf = src.buffers[bufferId] - var/text = sanitize(input(usr, "New Label:", "Edit Label", buf.name) as text|null, MAX_NAME_LEN) - buf.name = text - src.buffers[bufferId] = buf - return 1 - - if (bufferOption == "transfer") - if (!src.connected.occupant || (NOCLONE in src.connected.occupant.mutations) || !src.connected.occupant.dna) - return - - irradiating = 2 - var/lock_state = src.connected.locked - src.connected.locked = 1//lock it - SSnanoui.update_uis(src) // update all UIs attached to src - - sleep(10*2) // sleep for 2 seconds + SStgui.update_uis(src) // update all UIs attached to src + sleep(10 * radiation_duration) // sleep for radiation_duration seconds irradiating = 0 - src.connected.locked = lock_state + connected.locked = lock_state - var/datum/dna2/record/buf = src.buffers[bufferId] + if(!connected.occupant) + return - if ((buf.types & DNA2_BUF_UI)) - if ((buf.types & DNA2_BUF_UE)) - src.connected.occupant.real_name = buf.dna.real_name - src.connected.occupant.name = buf.dna.real_name - if(ishuman(connected.occupant)) - var/mob/living/carbon/human/H = connected.occupant - H.gender = buf.gender - H.descriptors = buf.body_descriptors - src.connected.occupant.UpdateAppearance(buf.dna.UI.Copy()) - else if (buf.types & DNA2_BUF_SE) - src.connected.occupant.dna.SE = buf.dna.SE - src.connected.occupant.dna.UpdateSE() - if(ishuman(connected.occupant)) - var/mob/living/carbon/human/H = connected.occupant - H.gender = buf.gender - H.descriptors = buf.body_descriptors - domutcheck(src.connected.occupant,src.connected) - src.connected.occupant.apply_effect(rand(20,50), IRRADIATE, check_protection = 0) - return 1 - - if (bufferOption == "createInjector") - if (src.injector_ready || waiting_for_user_input) - - var/success = 1 - var/obj/item/weapon/dnainjector/I = new /obj/item/weapon/dnainjector - var/datum/dna2/record/buf = src.buffers[bufferId] - if(href_list["createBlockInjector"]) - waiting_for_user_input=1 - var/list/selectedbuf - if(buf.types & DNA2_BUF_SE) - selectedbuf=buf.dna.SE - else - selectedbuf=buf.dna.UI - var/blk = input(usr,"Select Block","Block") in all_dna_blocks(selectedbuf) - success = setInjectorBlock(I,blk,buf) + if(prob(95)) + if(prob(75)) + randmutb(connected.occupant) else - I.buf = buf - waiting_for_user_input=0 - if(success) - I.loc = src.loc - I.name += " ([buf.name])" - //src.temphtml = "Injector created." - src.injector_ready = 0 - spawn(300) - src.injector_ready = 1 - //else - //src.temphtml = "Error in injector creation." - //else - //src.temphtml = "Replicator not ready yet." - return 1 + randmuti(connected.occupant) + else + if(prob(95)) + randmutg(connected.occupant) + else + randmuti(connected.occupant) - if (bufferOption == "loadDisk") - if ((isnull(src.disk)) || (!src.disk.buf)) - //src.temphtml = "Invalid disk. Please try again." - return 0 + connected.occupant.apply_effect(((radiation_intensity*3)+radiation_duration*3), IRRADIATE, check_protection = 0) + if("radiationDuration") + radiation_duration = clamp(text2num(params["value"]), 1, 20) + if("radiationIntensity") + radiation_intensity = clamp(text2num(params["value"]), 1, 10) + //////////////////////////////////////////////////////// + if("changeUITarget") + selected_ui_target = clamp(text2num(params["value"]), 1, 15) + selected_ui_target_hex = num2text(selected_ui_target, 1, 16) + if("selectUIBlock") // This chunk of code updates selected block / sub-block based on click + var/select_block = text2num(params["block"]) + var/select_subblock = text2num(params["subblock"]) + if(!select_block || !select_subblock) + return - src.buffers[bufferId]=src.disk.buf - //src.temphtml = "Data loaded." - return 1 + selected_ui_block = clamp(select_block, 1, DNA_UI_LENGTH) + selected_ui_subblock = clamp(select_subblock, 1, DNA_BLOCK_SIZE) + if("pulseUIRadiation") + var/block = connected.occupant.dna.GetUISubBlock(selected_ui_block,selected_ui_subblock) - if (bufferOption == "saveDisk") - if ((isnull(src.disk)) || (src.disk.read_only)) - //src.temphtml = "Invalid disk. Please try again." - return 0 + irradiating = radiation_duration + var/lock_state = connected.locked + connected.locked = TRUE //lock it - var/datum/dna2/record/buf = src.buffers[bufferId] + SStgui.update_uis(src) // update all UIs attached to src + sleep(10 * radiation_duration) // sleep for radiation_duration seconds - src.disk.buf = buf - src.disk.name = "data disk - '[buf.dna.real_name]'" - //src.temphtml = "Data saved." - return 1 + irradiating = 0 + connected.locked = lock_state + + if(!connected.occupant) + return + + if(prob((80 + (radiation_duration / 2)))) + block = miniscrambletarget(num2text(selected_ui_target), radiation_intensity, radiation_duration) + connected.occupant.dna.SetUISubBlock(selected_ui_block,selected_ui_subblock,block) + connected.occupant.UpdateAppearance() + connected.occupant.apply_effect((radiation_intensity+radiation_duration), IRRADIATE, check_protection = 0) + else + if(prob(20 + radiation_intensity)) + randmutb(connected.occupant) + domutcheck(connected.occupant,connected) + else + randmuti(connected.occupant) + connected.occupant.UpdateAppearance() + connected.occupant.apply_effect(((radiation_intensity*2)+radiation_duration), IRRADIATE, check_protection = 0) + //////////////////////////////////////////////////////// + if("injectRejuvenators") + if(!connected.occupant || !connected.beaker) + return + var/inject_amount = clamp(round(text2num(params["amount"]), 5), 0, 50) // round to nearest 5 and clamp to 0-50 + if(!inject_amount) + return + connected.beaker.reagents.trans_to_mob(connected.occupant, inject_amount, CHEM_BLOOD) + //////////////////////////////////////////////////////// + if("selectSEBlock") // This chunk of code updates selected block / sub-block based on click (se stands for strutural enzymes) + var/select_block = text2num(params["block"]) + var/select_subblock = text2num(params["subblock"]) + if(!select_block || !select_subblock) + return + + selected_se_block = clamp(select_block, 1, DNA_SE_LENGTH) + selected_se_subblock = clamp(select_subblock, 1, DNA_BLOCK_SIZE) + if("pulseSERadiation") + var/block = connected.occupant.dna.GetSESubBlock(selected_se_block,selected_se_subblock) + //var/original_block=block + //testing("Irradiating SE block [selected_se_block]:[selected_se_subblock] ([block])...") + + irradiating = radiation_duration + var/lock_state = connected.locked + connected.locked = TRUE //lock it + + SStgui.update_uis(src) // update all UIs attached to src + sleep(10 * radiation_duration) // sleep for radiation_duration seconds + + irradiating = 0 + connected.locked = lock_state + + if(connected.occupant) + if(prob((80 + (radiation_duration / 2)))) + // FIXME: Find out what these corresponded to and change them to the WHATEVERBLOCK they need to be. + //if((selected_se_block != 2 || selected_se_block != 12 || selected_se_block != 8 || selected_se_block || 10) && prob (20)) + var/real_SE_block=selected_se_block + block = miniscramble(block, radiation_intensity, radiation_duration) + if(prob(20)) + if(selected_se_block > 1 && selected_se_block < DNA_SE_LENGTH/2) + real_SE_block++ + else if(selected_se_block > DNA_SE_LENGTH/2 && selected_se_block < DNA_SE_LENGTH) + real_SE_block-- + + //testing("Irradiated SE block [real_SE_block]:[selected_se_subblock] ([original_block] now [block]) [(real_SE_block!=selected_se_block) ? "(SHIFTED)":""]!") + connected.occupant.dna.SetSESubBlock(real_SE_block,selected_se_subblock,block) + connected.occupant.apply_effect((radiation_intensity+radiation_duration), IRRADIATE, check_protection = 0) + domutcheck(connected.occupant,connected) + else + connected.occupant.apply_effect(((radiation_intensity*2)+radiation_duration), IRRADIATE, check_protection = 0) + if (prob(80-radiation_duration)) + //testing("Random bad mut!") + randmutb(connected.occupant) + domutcheck(connected.occupant,connected) + else + randmuti(connected.occupant) + //testing("Random identity mut!") + connected.occupant.UpdateAppearance() + if("ejectBeaker") + if(connected.beaker) + var/obj/item/weapon/reagent_containers/glass/B = connected.beaker + B.loc = connected.loc + connected.beaker = null + if("ejectOccupant") + connected.eject_occupant() + // Transfer Buffer Management + if("bufferOption") + var/bufferOption = params["option"] + var/bufferId = text2num(params["id"]) + if(bufferId < 1 || bufferId > 3) // Not a valid buffer id + return + + var/datum/dna2/record/buffer = buffers[bufferId] + switch(bufferOption) + if("saveUI") + if(connected.occupant && connected.occupant.dna) + var/datum/dna2/record/databuf=new + databuf.types = DNA2_BUF_UI // DNA2_BUF_UE + databuf.dna = connected.occupant.dna.Clone() + if(ishuman(connected.occupant)) + var/mob/living/carbon/human/H = connected.occupant + databuf.dna.real_name = H.dna.real_name + databuf.gender = H.gender + databuf.body_descriptors = H.descriptors + databuf.name = "Unique Identifier" + buffers[bufferId] = databuf + if("saveUIAndUE") + if(connected.occupant && connected.occupant.dna) + var/datum/dna2/record/databuf=new + databuf.types = DNA2_BUF_UI|DNA2_BUF_UE + databuf.dna = connected.occupant.dna.Clone() + if(ishuman(connected.occupant)) + var/mob/living/carbon/human/H = connected.occupant + databuf.dna.real_name = H.dna.real_name + databuf.gender = H.gender + databuf.body_descriptors = H.descriptors + databuf.name = "Unique Identifier + Unique Enzymes" + buffers[bufferId] = databuf + if("saveSE") + if(connected.occupant && connected.occupant.dna) + var/datum/dna2/record/databuf=new + databuf.types = DNA2_BUF_SE + databuf.dna = connected.occupant.dna.Clone() + if(ishuman(connected.occupant)) + var/mob/living/carbon/human/H = connected.occupant + databuf.dna.real_name = H.dna.real_name + databuf.gender = H.gender + databuf.body_descriptors = H.descriptors + databuf.name = "Structural Enzymes" + buffers[bufferId] = databuf + if("clear") + buffers[bufferId] = new /datum/dna2/record() + if("changeLabel") + tgui_modal_input(src, "changeBufferLabel", "Please enter the new buffer label:", null, list("id" = bufferId), buffer.name, TGUI_MODAL_INPUT_MAX_LENGTH_NAME) + if("transfer") + if(!connected.occupant || (NOCLONE in connected.occupant.mutations) || !connected.occupant.dna) + return + + irradiating = 2 + var/lock_state = connected.locked + connected.locked = 1//lock it + + SStgui.update_uis(src) // update all UIs attached to src + sleep(2 SECONDS) // sleep for 2 seconds + + irradiating = 0 + connected.locked = lock_state + + var/datum/dna2/record/buf = buffers[bufferId] + + if((buf.types & DNA2_BUF_UI)) + if((buf.types & DNA2_BUF_UE)) + connected.occupant.real_name = buf.dna.real_name + connected.occupant.name = buf.dna.real_name + if(ishuman(connected.occupant)) + var/mob/living/carbon/human/H = connected.occupant + H.gender = buf.gender + H.descriptors = buf.body_descriptors + connected.occupant.UpdateAppearance(buf.dna.UI.Copy()) + else if(buf.types & DNA2_BUF_SE) + connected.occupant.dna.SE = buf.dna.SE + connected.occupant.dna.UpdateSE() + if(ishuman(connected.occupant)) + var/mob/living/carbon/human/H = connected.occupant + H.gender = buf.gender + H.descriptors = buf.body_descriptors + domutcheck(connected.occupant,connected) + connected.occupant.apply_effect(rand(20,50), IRRADIATE, check_protection = 0) + if("createInjector") + if(!injector_ready) + return + if(text2num(params["block"]) > 0) + var/list/choices = all_dna_blocks((buffer.types & DNA2_BUF_SE) ? buffer.dna.SE : buffer.dna.UI) + tgui_modal_choice(src, "createInjectorBlock", "Please select the block to create an injector from:", null, list("id" = bufferId), null, choices) + else + create_injector(bufferId, TRUE) + if("loadDisk") + if(isnull(disk) || disk.read_only) + return + buffers[bufferId] = disk.buf.copy() + if("saveDisk") + if(isnull(disk) || disk.read_only) + return + var/datum/dna2/record/buf = buffers[bufferId] + disk.buf = buf.copy() + disk.name = "data disk - '[buf.dna.real_name]'" + + if("wipeDisk") + if(isnull(disk) || disk.read_only) + return + disk.buf = null + if("ejectDisk") + if(!disk) + return + disk.forceMove(get_turf(src)) + disk = null + +/** + * Creates a blank injector with the name of the buffer at the given buffer_id + * + * Arguments: + * * buffer_id - The ID of the buffer + * * copy_buffer - Whether the injector should copy the buffer contents + */ +/obj/machinery/computer/scan_consolenew/proc/create_injector(buffer_id, copy_buffer = FALSE) + if(buffer_id < 1 || buffer_id > length(buffers)) + return + + // Cooldown + injector_ready = FALSE + addtimer(CALLBACK(src, .proc/injector_cooldown_finish), 30 SECONDS) + + // Create it + var/datum/dna2/record/buf = buffers[buffer_id] + var/obj/item/weapon/dnainjector/I = new() + I.forceMove(loc) + I.name += " ([buf.name])" + if(copy_buffer) + I.buf = buf.copy() + return I + +/** + * Called when the injector creation cooldown finishes + */ +/obj/machinery/computer/scan_consolenew/proc/injector_cooldown_finish() + injector_ready = TRUE + +/** + * Called in tgui_act() to process modal actions + * + * Arguments: + * * action - The action passed by tgui + * * params - The params passed by tgui + */ +/obj/machinery/computer/scan_consolenew/proc/tgui_act_modal(action, params) + . = TRUE + var/id = params["id"] // The modal's ID + var/list/arguments = istext(params["arguments"]) ? json_decode(params["arguments"]) : params["arguments"] + switch(tgui_modal_act(src, action, params)) + if(TGUI_MODAL_ANSWER) + var/answer = params["answer"] + switch(id) + if("createInjectorBlock") + var/buffer_id = text2num(arguments["id"]) + if(buffer_id < 1 || buffer_id > length(buffers)) + return + var/datum/dna2/record/buf = buffers[buffer_id] + var/obj/item/weapon/dnainjector/I = create_injector(buffer_id) + setInjectorBlock(I, answer, buf.copy()) + if("changeBufferLabel") + var/buffer_id = text2num(arguments["id"]) + if(buffer_id < 1 || buffer_id > length(buffers)) + return + var/datum/dna2/record/buf = buffers[buffer_id] + buf.name = answer + buffers[buffer_id] = buf + else + return FALSE + else + return FALSE -/////////////////////////// DNA MACHINES +#undef PAGE_UI +#undef PAGE_SE +#undef PAGE_BUFFER +#undef PAGE_REJUVENATORS + +/////////////////////////// DNA MACHINES \ No newline at end of file diff --git a/code/game/machinery/Sleeper.dm b/code/game/machinery/Sleeper.dm index c19873fd3d8..242bdf89ad5 100644 --- a/code/game/machinery/Sleeper.dm +++ b/code/game/machinery/Sleeper.dm @@ -51,7 +51,7 @@ return if(sleeper) - return ui_interact(user) + return tgui_interact(user) /obj/machinery/sleep_console/attackby(var/obj/item/I, var/mob/user) if(computer_deconstruction_screwdriver(user, I)) @@ -66,97 +66,21 @@ else icon_state = initial(icon_state) -/obj/machinery/sleep_console/ui_interact(var/mob/user, var/ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/datum/topic_state/state = outside_state) - var/data[0] - - var/obj/machinery/sleeper/S = sleeper - var/mob/living/carbon/human/occupant = sleeper.occupant - - data["power"] = S.stat & (NOPOWER|BROKEN) ? 0 : 1 - - var/list/reagents = list() - for(var/T in S.available_chemicals) - var/list/reagent = list() - reagent["id"] = T - reagent["name"] = S.available_chemicals[T] - if(occupant) - reagent["amount"] = occupant.reagents.get_reagent_amount(T) - reagents += list(reagent) - data["reagents"] = reagents.Copy() - - if(occupant) - data["occupant"] = 1 - switch(occupant.stat) - if(CONSCIOUS) - data["stat"] = "Conscious" - if(UNCONSCIOUS) - data["stat"] = "Unconscious" - if(DEAD) - data["stat"] = "Dead" - data["health"] = occupant.health - data["maxHealth"] = occupant.getMaxHealth() - if(iscarbon(occupant)) - var/mob/living/carbon/C = occupant - data["pulse"] = C.get_pulse(GETPULSE_TOOL) - data["brute"] = occupant.getBruteLoss() - data["burn"] = occupant.getFireLoss() - data["oxy"] = occupant.getOxyLoss() - data["tox"] = occupant.getToxLoss() - else - data["occupant"] = 0 - if(S.beaker) - data["beaker"] = S.beaker.reagents.get_free_space() - else - data["beaker"] = -1 - data["filtering"] = S.filtering - data["pump"] = S.pumping - - var/stasis_level_name = "Error!" - for(var/N in S.stasis_choices) - if(S.stasis_choices[N] == S.stasis_level) - stasis_level_name = N - break - data["stasis"] = stasis_level_name - - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open) +/obj/machinery/sleep_console/tgui_interact(mob/user, datum/tgui/ui = null) + ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, ui_key, "sleeper.tmpl", "Sleeper UI", 600, 600, state = state) - ui.set_initial_data(data) + ui = new(user, src, "Sleeper", "Sleeper") ui.open() - ui.set_auto_update(1) -/obj/machinery/sleep_console/Topic(href, href_list) - if(..()) - return 1 +/obj/machinery/sleep_console/tgui_data(mob/user) + if(sleeper) + return sleeper.tgui_data(user) + return null - var/obj/machinery/sleeper/S = sleeper - - if(usr == S.occupant) - to_chat(usr, "You can't reach the controls from the inside.") - return - - add_fingerprint(usr) - - if(href_list["eject"]) - S.go_out() - if(href_list["beaker"]) - S.remove_beaker() - if(href_list["sleeper_filter"]) - if(S.filtering != text2num(href_list["sleeper_filter"])) - S.toggle_filter() - if(href_list["pump"]) - if(S.pumping != text2num(href_list["pump"])) - S.toggle_pump() - if(href_list["chemical"] && href_list["amount"]) - if(S.occupant && S.occupant.stat != DEAD) - if(href_list["chemical"] in S.available_chemicals) // Your hacks are bad and you should feel bad - S.inject_chemical(usr, href_list["chemical"], text2num(href_list["amount"])) - if(href_list["change_stasis"]) - var/new_stasis = input("Levels deeper than 50% stasis level will render the patient unconscious.","Stasis Level") as null|anything in S.stasis_choices - if(new_stasis && CanUseTopic(usr, default_state) == STATUS_INTERACTIVE) - S.stasis_level = S.stasis_choices[new_stasis] - - return 1 +/obj/machinery/sleep_console/tgui_act(action, params, datum/tgui/ui, datum/tgui_state/state) + if(sleeper) + return sleeper.tgui_act(action, params, ui, state) + return FALSE /obj/machinery/sleeper name = "sleeper" @@ -169,12 +93,19 @@ var/mob/living/carbon/human/occupant = null var/list/available_chemicals = list() var/list/base_chemicals = list("inaprovaline" = "Inaprovaline", "paracetamol" = "Paracetamol", "anti_toxin" = "Dylovene", "dexalin" = "Dexalin") + var/amounts = list(5, 10) var/obj/item/weapon/reagent_containers/glass/beaker = null var/filtering = 0 var/pumping = 0 + // Currently never changes. On Paradise, max_chem and min_health are based on the matter bins in the sleeper. + var/max_chem = 20 + var/initial_bin_rating = 1 + var/min_health = -25 var/obj/machinery/sleep_console/console var/stasis_level = 0 //Every 'this' life ticks are applied to the mob (when life_ticks%stasis_level == 1) var/stasis_choices = list("Complete (1%)" = 100, "Deep (10%)" = 10, "Moderate (20%)" = 5, "Light (50%)" = 2, "None (100%)" = 0) + var/controls_inside = FALSE + var/auto_eject_dead = FALSE use_power = USE_POWER_IDLE idle_power_usage = 15 @@ -184,6 +115,7 @@ . = ..() beaker = new /obj/item/weapon/reagent_containers/glass/beaker/large(src) default_apply_parts() + update_icon() /obj/machinery/sleeper/Destroy() if(console) @@ -232,14 +164,187 @@ available_chemicals += new_chemicals return -/obj/machinery/sleeper/Initialize() - . = ..() - update_icon() +/obj/machinery/sleeper/attack_hand(var/mob/user) + if(!controls_inside) + return FALSE + + if(user == occupant) + tgui_interact(user) + +/obj/machinery/sleeper/tgui_interact(mob/user, datum/tgui/ui = null) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Sleeper", "Sleeper") + ui.open() + +/obj/machinery/sleeper/tgui_data(mob/user) + var/data[0] + data["amounts"] = amounts + data["hasOccupant"] = occupant ? 1 : 0 + var/occupantData[0] + // var/crisis = 0 + if(occupant) + occupantData["name"] = occupant.name + occupantData["stat"] = occupant.stat + occupantData["health"] = occupant.health + occupantData["maxHealth"] = occupant.maxHealth + occupantData["minHealth"] = config.health_threshold_dead + occupantData["bruteLoss"] = occupant.getBruteLoss() + occupantData["oxyLoss"] = occupant.getOxyLoss() + occupantData["toxLoss"] = occupant.getToxLoss() + occupantData["fireLoss"] = occupant.getFireLoss() + occupantData["paralysis"] = occupant.paralysis + occupantData["hasBlood"] = 0 + occupantData["bodyTemperature"] = occupant.bodytemperature + occupantData["maxTemp"] = 1000 // If you get a burning vox armalis into the sleeper, congratulations + // Because we can put simple_animals in here, we need to do something tricky to get things working nice + occupantData["temperatureSuitability"] = 0 // 0 is the baseline + if(ishuman(occupant) && occupant.species) + // I wanna do something where the bar gets bluer as the temperature gets lower + // For now, I'll just use the standard format for the temperature status + var/datum/species/sp = occupant.species + if(occupant.bodytemperature < sp.cold_level_3) + occupantData["temperatureSuitability"] = -3 + else if(occupant.bodytemperature < sp.cold_level_2) + occupantData["temperatureSuitability"] = -2 + else if(occupant.bodytemperature < sp.cold_level_1) + occupantData["temperatureSuitability"] = -1 + else if(occupant.bodytemperature > sp.heat_level_3) + occupantData["temperatureSuitability"] = 3 + else if(occupant.bodytemperature > sp.heat_level_2) + occupantData["temperatureSuitability"] = 2 + else if(occupant.bodytemperature > sp.heat_level_1) + occupantData["temperatureSuitability"] = 1 + else if(isanimal(occupant)) + var/mob/living/simple_mob/silly = occupant + if(silly.bodytemperature < silly.minbodytemp) + occupantData["temperatureSuitability"] = -3 + else if(silly.bodytemperature > silly.maxbodytemp) + occupantData["temperatureSuitability"] = 3 + // Blast you, imperial measurement system + occupantData["btCelsius"] = occupant.bodytemperature - T0C + occupantData["btFaren"] = ((occupant.bodytemperature - T0C) * (9.0/5.0))+ 32 + + + // crisis = (occupant.health < min_health) + // I'm not sure WHY you'd want to put a simple_animal in a sleeper, but precedent is precedent + // Runtime is aptly named, isn't she? + if(ishuman(occupant) && !(NO_BLOOD in occupant.species.flags) && occupant.vessel) + occupantData["pulse"] = occupant.get_pulse(GETPULSE_TOOL) + occupantData["hasBlood"] = 1 + var/blood_volume = round(occupant.vessel.get_reagent_amount("blood")) + occupantData["bloodLevel"] = blood_volume + occupantData["bloodMax"] = occupant.species.blood_volume + occupantData["bloodPercent"] = round(100*(blood_volume/occupant.species.blood_volume), 0.01) //copy pasta ends here + + occupantData["bloodType"] = occupant.dna.b_type + + data["occupant"] = occupantData + data["maxchem"] = max_chem + data["minhealth"] = min_health + data["dialysis"] = filtering + data["stomachpumping"] = pumping + data["auto_eject_dead"] = auto_eject_dead + if(beaker) + data["isBeakerLoaded"] = 1 + if(beaker.reagents) + data["beakerMaxSpace"] = beaker.reagents.maximum_volume + data["beakerFreeSpace"] = beaker.reagents.get_free_space() + else + data["beakerMaxSpace"] = 0 + data["beakerFreeSpace"] = 0 + else + data["isBeakerLoaded"] = FALSE + + + var/stasis_level_name = "Error!" + for(var/N in stasis_choices) + if(stasis_choices[N] == stasis_level) + stasis_level_name = N + break + data["stasis"] = stasis_level_name + + var/chemicals[0] + for(var/re in available_chemicals) + var/datum/reagent/temp = SSchemistry.chemical_reagents[re] + if(temp) + var/reagent_amount = 0 + var/pretty_amount + var/injectable = occupant ? 1 : 0 + var/overdosing = 0 + var/caution = 0 // To make things clear that you're coming close to an overdose + // if(crisis && !(temp.id in emergency_chems)) + // injectable = 0 + + if(occupant && occupant.reagents) + reagent_amount = occupant.reagents.get_reagent_amount(temp.id) + // If they're mashing the highest concentration, they get one warning + if(temp.overdose && reagent_amount + 10 > (temp.overdose * occupant?.species.chemOD_threshold)) + caution = 1 + if(temp.overdose && reagent_amount > (temp.overdose * occupant?.species.chemOD_threshold)) + overdosing = 1 + + pretty_amount = round(reagent_amount, 0.05) + + chemicals.Add(list(list("title" = temp.name, "id" = temp.id, "commands" = list("chemical" = temp.id), "occ_amount" = reagent_amount, "pretty_amount" = pretty_amount, "injectable" = injectable, "overdosing" = overdosing, "od_warning" = caution))) + data["chemicals"] = chemicals + return data + + +/obj/machinery/sleeper/tgui_act(action, params, datum/tgui/ui, datum/tgui_state/state) + if(..()) + return + if(!controls_inside && usr == occupant) + return + if(panel_open) + to_chat(usr, "Close the maintenance panel first.") + return + + . = TRUE + switch(action) + if("chemical") + if(!occupant) + return + if(occupant.stat == DEAD) + var/datum/gender/G = gender_datums[occupant.get_visible_gender()] + to_chat(usr, "This person has no life to preserve anymore. Take [G.him] to a department capable of reanimating [G.him].") + return + var/chemical = params["chemid"] + var/amount = text2num(params["amount"]) + if(!length(chemical) || amount <= 0) + return + if(occupant.health > min_health) //|| (chemical in emergency_chems)) + inject_chemical(usr, chemical, amount) + else + to_chat(usr, "This person is not in good enough condition for sleepers to be effective! Use another means of treatment, such as cryogenics!") + if("removebeaker") + remove_beaker() + if("togglefilter") + toggle_filter() + if("togglepump") + toggle_pump() + if("ejectify") + go_out() + if("changestasis") + var/new_stasis = input("Levels deeper than 50% stasis level will render the patient unconscious.","Stasis Level") as null|anything in stasis_choices + if(new_stasis) + stasis_level = stasis_choices[new_stasis] + if("auto_eject_dead_on") + auto_eject_dead = TRUE + if("auto_eject_dead_off") + auto_eject_dead = FALSE + else + return FALSE + add_fingerprint(usr) /obj/machinery/sleeper/process() if(stat & (NOPOWER|BROKEN)) return if(occupant) + if(auto_eject_dead && occupant.stat == DEAD) + playsound(loc, 'sound/machines/buzz-sigh.ogg', 40) + go_out() + return occupant.Stasis(stasis_level) if(filtering > 0) @@ -404,9 +509,11 @@ /obj/machinery/sleeper/proc/inject_chemical(var/mob/living/user, var/chemical, var/amount) if(stat & (BROKEN|NOPOWER)) return + if(!(amount in amounts)) + return if(occupant && occupant.reagents) - if(occupant.reagents.get_reagent_amount(chemical) + amount <= 20) + if(occupant.reagents.get_reagent_amount(chemical) + amount <= max_chem) use_power(amount * CHEM_SYNTH_ENERGY) occupant.reagents.add_reagent(chemical, amount) to_chat(user, "Occupant now has [occupant.reagents.get_reagent_amount(chemical)] units of [available_chemicals[chemical]] in their bloodstream.") diff --git a/code/game/machinery/adv_med.dm b/code/game/machinery/adv_med.dm index 482988caf27..8d06be107eb 100644 --- a/code/game/machinery/adv_med.dm +++ b/code/game/machinery/adv_med.dm @@ -1,7 +1,7 @@ // Pretty much everything here is stolen from the dna scanner FYI /obj/machinery/bodyscanner - var/mob/living/carbon/occupant + var/mob/living/carbon/human/occupant var/locked name = "Body Scanner" icon = 'icons/obj/Cryogenic2.dmi' @@ -14,6 +14,8 @@ active_power_usage = 10000 //10 kW. It's a big all-body scanner. light_color = "#00FF00" var/obj/machinery/body_scanconsole/console + var/known_implants = list(/obj/item/weapon/implant/health, /obj/item/weapon/implant/chem, /obj/item/weapon/implant/death_alarm, /obj/item/weapon/implant/loyalty, /obj/item/weapon/implant/tracking, /obj/item/weapon/implant/language, /obj/item/weapon/implant/language/eal, /obj/item/weapon/implant/backup, /obj/item/device/nif) //VOREStation Add - Backup Implant, NIF + var/printing_text = null /obj/machinery/bodyscanner/Initialize() . = ..() @@ -57,13 +59,14 @@ update_icon() //icon_state = "body_scanner_1" //VOREStation Edit - Health display for consoles with light and such. add_fingerprint(user) qdel(G) + SStgui.update_uis(src) if(!occupant) if(default_deconstruction_screwdriver(user, G)) return if(default_deconstruction_crowbar(user, G)) return -/obj/machinery/bodyscanner/MouseDrop_T(mob/living/carbon/O, mob/user as mob) +/obj/machinery/bodyscanner/MouseDrop_T(mob/living/carbon/human/O, mob/user as mob) if(!istype(O)) return 0 //not a mob if(user.incapacitated()) @@ -99,6 +102,7 @@ occupant = O update_icon() //icon_state = "body_scanner_1" //VOREStation Edit - Health display for consoles with light and such. add_fingerprint(user) + SStgui.update_uis(src) /obj/machinery/bodyscanner/relaymove(mob/user as mob) if(user.incapacitated()) @@ -124,6 +128,7 @@ occupant.loc = src.loc occupant = null update_icon() //icon_state = "body_scanner_1" //VOREStation Edit - Health display for consoles with light and such. + SStgui.update_uis(src) return /obj/machinery/bodyscanner/ex_act(severity) @@ -157,10 +162,349 @@ else return +/obj/machinery/bodyscanner/tgui_interact(mob/user, datum/tgui/ui = null) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "BodyScanner", "Body Scanner") + ui.open() + +/obj/machinery/bodyscanner/tgui_data(mob/user) + var/list/data = list() + + data["occupied"] = occupant ? TRUE : FALSE + + var/occupantData[0] + if(occupant && ishuman(occupant)) + update_icon() //VOREStation Edit - Health display for consoles with light and such. + var/mob/living/carbon/human/H = occupant + occupantData["name"] = H.name + occupantData["stat"] = H.stat + occupantData["health"] = H.health + occupantData["maxHealth"] = H.getMaxHealth() + + occupantData["hasVirus"] = H.virus2.len + + occupantData["bruteLoss"] = H.getBruteLoss() + occupantData["oxyLoss"] = H.getOxyLoss() + occupantData["toxLoss"] = H.getToxLoss() + occupantData["fireLoss"] = H.getFireLoss() + + occupantData["radLoss"] = H.radiation + occupantData["cloneLoss"] = H.getCloneLoss() + occupantData["brainLoss"] = H.getBrainLoss() + occupantData["paralysis"] = H.paralysis + occupantData["paralysisSeconds"] = round(H.paralysis / 4) + occupantData["bodyTempC"] = H.bodytemperature-T0C + occupantData["bodyTempF"] = (((H.bodytemperature-T0C) * 1.8) + 32) + + occupantData["hasBorer"] = H.has_brain_worms() + + var/bloodData[0] + if(H.vessel) + var/blood_volume = round(H.vessel.get_reagent_amount("blood")) + var/blood_max = H.species.blood_volume + bloodData["volume"] = blood_volume + bloodData["percent"] = round(((blood_volume / blood_max)*100)) + + occupantData["blood"] = bloodData + + var/reagentData[0] + if(H.reagents.reagent_list.len >= 1) + for(var/datum/reagent/R in H.reagents.reagent_list) + reagentData[++reagentData.len] = list("name" = R.name, "amount" = R.volume) + else + reagentData = null + + occupantData["reagents"] = reagentData + + var/ingestedData[0] + if(H.ingested.reagent_list.len >= 1) + for(var/datum/reagent/R in H.ingested.reagent_list) + ingestedData[++ingestedData.len] = list("name" = R.name, "amount" = R.volume) + else + ingestedData = null + + occupantData["ingested"] = ingestedData + + var/extOrganData[0] + for(var/obj/item/organ/external/E in H.organs) + var/organData[0] + organData["name"] = E.name + organData["open"] = E.open + organData["germ_level"] = E.germ_level + organData["bruteLoss"] = E.brute_dam + organData["fireLoss"] = E.burn_dam + organData["totalLoss"] = E.brute_dam + E.burn_dam + organData["maxHealth"] = E.max_damage + organData["bruised"] = E.min_bruised_damage + organData["broken"] = E.min_broken_damage + + var/implantData[0] + for(var/obj/I in E.implants) + var/implantSubData[0] + implantSubData["name"] = I.name + if(is_type_in_list(I, known_implants)) + implantSubData["known"] = 1 + + implantData.Add(list(implantSubData)) + + organData["implants"] = implantData + organData["implants_len"] = implantData.len + + var/organStatus[0] + if(E.status & ORGAN_DESTROYED) + organStatus["destroyed"] = 1 + if(E.status & ORGAN_BROKEN) + organStatus["broken"] = E.broken_description + if(E.robotic >= ORGAN_ROBOT) + organStatus["robotic"] = 1 + if(E.splinted) + organStatus["splinted"] = 1 + if(E.status & ORGAN_BLEEDING) + organStatus["bleeding"] = 1 + if(E.status & ORGAN_DEAD) + organStatus["dead"] = 1 + + organData["status"] = organStatus + + if(istype(E, /obj/item/organ/external/chest) && H.is_lung_ruptured()) + organData["lungRuptured"] = 1 + + for(var/datum/wound/W in E.wounds) + if(W.internal) + organData["internalBleeding"] = 1 + break + + extOrganData.Add(list(organData)) + + occupantData["extOrgan"] = extOrganData + + var/intOrganData[0] + for(var/obj/item/organ/I in H.internal_organs) + var/organData[0] + organData["name"] = I.name + if(I.status & ORGAN_ASSISTED) + organData["desc"] = "Assisted" + else if(I.robotic >= ORGAN_ROBOT) + organData["desc"] = "Mechanical" + else + organData["desc"] = null + organData["germ_level"] = I.germ_level + organData["damage"] = I.damage + organData["maxHealth"] = I.max_damage + organData["bruised"] = I.min_bruised_damage + organData["broken"] = I.min_broken_damage + organData["robotic"] = (I.robotic >= ORGAN_ROBOT) + organData["dead"] = (I.status & ORGAN_DEAD) + + intOrganData.Add(list(organData)) + + occupantData["intOrgan"] = intOrganData + + occupantData["blind"] = (H.sdisabilities & BLIND) + occupantData["nearsighted"] = (H.disabilities & NEARSIGHTED) + occupantData = attempt_vr(src, "get_occupant_data_vr", list(occupantData, H)) //VOREStation Insert + data["occupant"] = occupantData + + return data + +/obj/machinery/bodyscanner/tgui_act(action, params) + if(..()) + return + + . = TRUE + switch(action) + if("ejectify") + eject() + if("print_p") + var/atom/target = console ? console : src + visible_message("[target] rattles and prints out a sheet of paper.") + var/obj/item/weapon/paper/P = new /obj/item/weapon/paper(get_turf(target)) + var/name = occupant ? occupant.name : "Unknown" + P.info = "
Body Scan - [name]

" + P.info += "Time of scan: [worldtime2stationtime(world.time)]

" + P.info += "[generate_printing_text()]" + P.info += "

Notes:
" + P.name = "Body Scan - [name] ([worldtime2stationtime(world.time)]" + else + return FALSE + +/obj/machinery/bodyscanner/proc/generate_printing_text() + var/dat = "" + + dat = "Occupant Statistics:
" //Blah obvious + if(istype(occupant)) //is there REALLY someone in there? + var/t1 + switch(occupant.stat) // obvious, see what their status is + if(0) + t1 = "Conscious" + if(1) + t1 = "Unconscious" + else + t1 = "*dead*" + dat += " (occupant.getMaxHealth() / 2) ? "blue" : "red"]>\tHealth %: [(occupant.health / occupant.getMaxHealth())*100], ([t1])
" + + if(occupant.virus2.len) + dat += "Viral pathogen detected in blood stream.
" + + var/extra_font = null + extra_font = "" + dat += "[extra_font]\t-Brute Damage %: [occupant.getBruteLoss()]
" + + extra_font = "" + dat += "[extra_font]\t-Respiratory Damage %: [occupant.getOxyLoss()]
" + + extra_font = "" + dat += "[extra_font]\t-Toxin Content %: [occupant.getToxLoss()]
" + + extra_font = "" + dat += "[extra_font]\t-Burn Severity %: [occupant.getFireLoss()]
" + + extra_font = "" + dat += "[extra_font]\tRadiation Level %: [occupant.radiation]
" + + extra_font = "" + dat += "[extra_font]\tGenetic Tissue Damage %: [occupant.getCloneLoss()]
" + + extra_font = "" + dat += "[extra_font]\tApprox. Brain Damage %: [occupant.getBrainLoss()]
" + + dat += "Paralysis Summary %: [occupant.paralysis] ([round(occupant.paralysis / 4)] seconds left!)
" + dat += "Body Temperature: [occupant.bodytemperature-T0C]°C ([occupant.bodytemperature*1.8-459.67]°F)
" + + dat += "
" + + if(occupant.has_brain_worms()) + dat += "Large growth detected in frontal lobe, possibly cancerous. Surgical removal is recommended.
" + + if(occupant.vessel) + var/blood_volume = round(occupant.vessel.get_reagent_amount("blood")) + var/blood_max = occupant.species.blood_volume + var/blood_percent = blood_volume / blood_max + blood_percent *= 100 + + extra_font = " 448 ? "blue" : "red"]>" + dat += "[extra_font]\tBlood Level %: [blood_percent] ([blood_volume] units)
" + + if(occupant.reagents) + for(var/datum/reagent/R in occupant.reagents.reagent_list) + dat += "Reagent: [R.name], Amount: [R.volume]
" + + if(occupant.ingested) + for(var/datum/reagent/R in occupant.ingested.reagent_list) + dat += "Stomach: [R.name], Amount: [R.volume]
" + + dat += "
" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + + for(var/obj/item/organ/external/e in occupant.organs) + dat += "" + var/AN = "" + var/open = "" + var/infected = "" + var/robot = "" + var/imp = "" + var/bled = "" + var/splint = "" + var/internal_bleeding = "" + var/lung_ruptured = "" + var/o_dead = "" + for(var/datum/wound/W in e.wounds) if(W.internal) + internal_bleeding = "
Internal bleeding" + break + if(istype(e, /obj/item/organ/external/chest) && occupant.is_lung_ruptured()) + lung_ruptured = "Lung ruptured:" + if(e.splinted) + splint = "Splinted:" + if(e.status & ORGAN_BLEEDING) + bled = "Bleeding:" + if(e.status & ORGAN_BROKEN) + AN = "[e.broken_description]:" + if(e.robotic >= ORGAN_ROBOT) + robot = "Prosthetic:" + if(e.status & ORGAN_DEAD) + o_dead = "Necrotic:" + if(e.open) + open = "Open:" + switch (e.germ_level) + if (INFECTION_LEVEL_ONE to INFECTION_LEVEL_ONE + 200) + infected = "Mild Infection:" + if (INFECTION_LEVEL_ONE + 200 to INFECTION_LEVEL_ONE + 300) + infected = "Mild Infection+:" + if (INFECTION_LEVEL_ONE + 300 to INFECTION_LEVEL_ONE + 400) + infected = "Mild Infection++:" + if (INFECTION_LEVEL_TWO to INFECTION_LEVEL_TWO + 200) + infected = "Acute Infection:" + if (INFECTION_LEVEL_TWO + 200 to INFECTION_LEVEL_TWO + 300) + infected = "Acute Infection+:" + if (INFECTION_LEVEL_TWO + 300 to INFECTION_LEVEL_THREE - 50) + infected = "Acute Infection++:" + if (INFECTION_LEVEL_THREE -49 to INFINITY) + infected = "Gangrene Detected:" + + var/unknown_body = 0 + for(var/I in e.implants) + if(is_type_in_list(I,known_implants)) + imp += "[I] implanted:" + else + unknown_body++ + + if(unknown_body) + imp += "Unknown body present:" + if(!AN && !open && !infected & !imp) + AN = "None:" + if(!(e.status & ORGAN_DESTROYED)) + dat += "" + else + dat += "" + dat += "" + for(var/obj/item/organ/i in occupant.internal_organs) + var/mech = "" + var/i_dead = "" + if(i.status & ORGAN_ASSISTED) + mech = "Assisted:" + if(i.robotic >= ORGAN_ROBOT) + mech = "Mechanical:" + if(i.status & ORGAN_DEAD) + i_dead = "Necrotic:" + var/infection = "None" + switch (i.germ_level) + if (INFECTION_LEVEL_ONE to INFECTION_LEVEL_ONE + 200) + infection = "Mild Infection:" + if (INFECTION_LEVEL_ONE + 200 to INFECTION_LEVEL_ONE + 300) + infection = "Mild Infection+:" + if (INFECTION_LEVEL_ONE + 300 to INFECTION_LEVEL_ONE + 400) + infection = "Mild Infection++:" + if (INFECTION_LEVEL_TWO to INFECTION_LEVEL_TWO + 200) + infection = "Acute Infection:" + if (INFECTION_LEVEL_TWO + 200 to INFECTION_LEVEL_TWO + 300) + infection = "Acute Infection+:" + if (INFECTION_LEVEL_TWO + 300 to INFECTION_LEVEL_THREE - 50) + infection = "Acute Infection++:" + if (INFECTION_LEVEL_THREE -49 to INFINITY) + infection = "Necrosis Detected:" + + dat += "" + dat += "" + dat += "" + dat += "
OrganBurn DamageBrute DamageOther Wounds
[e.name][e.burn_dam][e.brute_dam][robot][bled][AN][splint][open][infected][imp][internal_bleeding][lung_ruptured][o_dead][e.name]--Not Found
[i.name]N/A[i.damage][infection]:[mech][i_dead]
" + if(occupant.sdisabilities & BLIND) + dat += "Cataracts detected.
" + if(occupant.disabilities & NEARSIGHTED) + dat += "Retinal misalignment detected.
" + else + dat += "\The [src] is empty." + + printing_text = dat + //Body Scan Console /obj/machinery/body_scanconsole var/obj/machinery/bodyscanner/scanner - var/known_implants = list(/obj/item/weapon/implant/health, /obj/item/weapon/implant/chem, /obj/item/weapon/implant/death_alarm, /obj/item/weapon/implant/loyalty, /obj/item/weapon/implant/tracking, /obj/item/weapon/implant/language, /obj/item/weapon/implant/language/eal, /obj/item/weapon/implant/backup, /obj/item/device/nif) //VOREStation Add - Backup Implant, NIF var/delete var/temphtml name = "Body Scanner Console" @@ -171,7 +515,6 @@ anchored = 1 circuit = /obj/item/weapon/circuitboard/scanner_console var/printing = null - var/printing_text = null /obj/machinery/body_scanconsole/New() ..() @@ -257,344 +600,9 @@ to_chat(user, "Scanner not found!") return - if (scanner.panel_open) + if(scanner.panel_open) to_chat(user, "Close the maintenance panel first.") return if(scanner) - return ui_interact(user) - -/obj/machinery/body_scanconsole/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - var/data[0] - - data["connected"] = scanner ? 1 : 0 - - if(scanner) - data["occupied"] = scanner.occupant ? 1 : 0 - - var/occupantData[0] - if(scanner.occupant && ishuman(scanner.occupant)) - update_icon() //VOREStation Edit - Health display for consoles with light and such. - var/mob/living/carbon/human/H = scanner.occupant - occupantData["name"] = H.name - occupantData["stat"] = H.stat - occupantData["health"] = H.health - occupantData["maxHealth"] = H.getMaxHealth() - - occupantData["hasVirus"] = H.virus2.len - - occupantData["bruteLoss"] = H.getBruteLoss() - occupantData["oxyLoss"] = H.getOxyLoss() - occupantData["toxLoss"] = H.getToxLoss() - occupantData["fireLoss"] = H.getFireLoss() - - occupantData["radLoss"] = H.radiation - occupantData["cloneLoss"] = H.getCloneLoss() - occupantData["brainLoss"] = H.getBrainLoss() - occupantData["paralysis"] = H.paralysis - occupantData["paralysisSeconds"] = round(H.paralysis / 4) - occupantData["bodyTempC"] = H.bodytemperature-T0C - occupantData["bodyTempF"] = (((H.bodytemperature-T0C) * 1.8) + 32) - - occupantData["hasBorer"] = H.has_brain_worms() - - var/bloodData[0] - if(H.vessel) - var/blood_volume = round(H.vessel.get_reagent_amount("blood")) - var/blood_max = H.species.blood_volume - bloodData["volume"] = blood_volume - bloodData["percent"] = round(((blood_volume / blood_max)*100)) - - occupantData["blood"] = bloodData - - var/reagentData[0] - if(H.reagents.reagent_list.len >= 1) - for(var/datum/reagent/R in H.reagents.reagent_list) - reagentData[++reagentData.len] = list("name" = R.name, "amount" = R.volume) - else - reagentData = null - - occupantData["reagents"] = reagentData - - var/ingestedData[0] - if(H.ingested.reagent_list.len >= 1) - for(var/datum/reagent/R in H.ingested.reagent_list) - ingestedData[++ingestedData.len] = list("name" = R.name, "amount" = R.volume) - else - ingestedData = null - - occupantData["ingested"] = ingestedData - - var/extOrganData[0] - for(var/obj/item/organ/external/E in H.organs) - var/organData[0] - organData["name"] = E.name - organData["open"] = E.open - organData["germ_level"] = E.germ_level - organData["bruteLoss"] = E.brute_dam - organData["fireLoss"] = E.burn_dam - - var/implantData[0] - for(var/obj/I in E.implants) - var/implantSubData[0] - implantSubData["name"] = I.name - if(is_type_in_list(I, known_implants)) - implantSubData["known"] = 1 - - implantData.Add(list(implantSubData)) - - organData["implants"] = implantData - organData["implants_len"] = implantData.len - - var/organStatus[0] - if(E.status & ORGAN_DESTROYED) - organStatus["destroyed"] = 1 - if(E.status & ORGAN_BROKEN) - organStatus["broken"] = E.broken_description - if(E.robotic >= ORGAN_ROBOT) - organStatus["robotic"] = 1 - if(E.splinted) - organStatus["splinted"] = 1 - if(E.status & ORGAN_BLEEDING) - organStatus["bleeding"] = 1 - if(E.status & ORGAN_DEAD) - organStatus["dead"] = 1 - for(var/datum/wound/W in E.wounds) - if(W.internal) - organStatus["internalBleeding"] = 1 - break - - organData["status"] = organStatus - - if(istype(E, /obj/item/organ/external/chest) && H.is_lung_ruptured()) - organData["lungRuptured"] = 1 - - extOrganData.Add(list(organData)) - - occupantData["extOrgan"] = extOrganData - - var/intOrganData[0] - for(var/obj/item/organ/I in H.internal_organs) - var/organData[0] - organData["name"] = I.name - if(I.status & ORGAN_ASSISTED) - organData["desc"] = "Assisted" - else if(I.robotic >= ORGAN_ROBOT) - organData["desc"] = "Mechanical" - else - organData["desc"] = null - organData["germ_level"] = I.germ_level - organData["damage"] = I.damage - - intOrganData.Add(list(organData)) - - occupantData["intOrgan"] = intOrganData - - occupantData["blind"] = (H.sdisabilities & BLIND) - occupantData["nearsighted"] = (H.disabilities & NEARSIGHTED) - occupantData = attempt_vr(scanner,"get_occupant_data_vr",list(occupantData,H)) //VOREStation Insert - data["occupant"] = occupantData - - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open) - if(!ui) - ui = new(user, src, ui_key, "adv_med.tmpl", "Body Scanner", 690, 800) - ui.set_initial_data(data) - ui.open() - ui.set_auto_update(1) - - -/obj/machinery/body_scanconsole/Topic(href, href_list) - if(..()) - return 1 - - if (href_list["print_p"]) - generate_printing_text() - - if (!(printing) && printing_text) - printing = 1 - visible_message("\The [src] rattles and prints out a sheet of paper.") - var/obj/item/weapon/paper/P = new /obj/item/weapon/paper(loc) - P.info = "
Body Scan - [href_list["name"]]

" - P.info += "Time of scan: [worldtime2stationtime(world.time)]

" - P.info += "[printing_text]" - P.info += "

Notes:
" - P.name = "Body Scan - [href_list["name"]] ([worldtime2stationtime(world.time)])" - printing = null - printing_text = null - -/obj/machinery/body_scanconsole/proc/generate_printing_text() - var/dat = "" - - if(scanner) - var/mob/living/carbon/human/occupant = scanner.occupant - dat = "Occupant Statistics:
" //Blah obvious - if(istype(occupant)) //is there REALLY someone in there? - var/t1 - switch(occupant.stat) // obvious, see what their status is - if(0) - t1 = "Conscious" - if(1) - t1 = "Unconscious" - else - t1 = "*dead*" - dat += " (occupant.getMaxHealth() / 2) ? "blue" : "red"]>\tHealth %: [(occupant.health / occupant.getMaxHealth())*100], ([t1])
" - - if(occupant.virus2.len) - dat += "Viral pathogen detected in blood stream.
" - - var/extra_font = null - extra_font = "" - dat += "[extra_font]\t-Brute Damage %: [occupant.getBruteLoss()]
" - - extra_font = "" - dat += "[extra_font]\t-Respiratory Damage %: [occupant.getOxyLoss()]
" - - extra_font = "" - dat += "[extra_font]\t-Toxin Content %: [occupant.getToxLoss()]
" - - extra_font = "" - dat += "[extra_font]\t-Burn Severity %: [occupant.getFireLoss()]
" - - extra_font = "" - dat += "[extra_font]\tRadiation Level %: [occupant.radiation]
" - - extra_font = "" - dat += "[extra_font]\tGenetic Tissue Damage %: [occupant.getCloneLoss()]
" - - extra_font = "" - dat += "[extra_font]\tApprox. Brain Damage %: [occupant.getBrainLoss()]
" - - dat += "Paralysis Summary %: [occupant.paralysis] ([round(occupant.paralysis / 4)] seconds left!)
" - dat += "Body Temperature: [occupant.bodytemperature-T0C]°C ([occupant.bodytemperature*1.8-459.67]°F)
" - - dat += "
" - - if(occupant.has_brain_worms()) - dat += "Large growth detected in frontal lobe, possibly cancerous. Surgical removal is recommended.
" - - if(occupant.vessel) - var/blood_volume = round(occupant.vessel.get_reagent_amount("blood")) - var/blood_max = occupant.species.blood_volume - var/blood_percent = blood_volume / blood_max - blood_percent *= 100 - - extra_font = " 448 ? "blue" : "red"]>" - dat += "[extra_font]\tBlood Level %: [blood_percent] ([blood_volume] units)
" - - if(occupant.reagents) - for(var/datum/reagent/R in occupant.reagents.reagent_list) - dat += "Reagent: [R.name], Amount: [R.volume]
" - - if(occupant.ingested) - for(var/datum/reagent/R in occupant.ingested.reagent_list) - dat += "Stomach: [R.name], Amount: [R.volume]
" - - dat += "
" - dat += "" - dat += "" - dat += "" - dat += "" - dat += "" - dat += "" - - for(var/obj/item/organ/external/e in occupant.organs) - dat += "" - var/AN = "" - var/open = "" - var/infected = "" - var/robot = "" - var/imp = "" - var/bled = "" - var/splint = "" - var/internal_bleeding = "" - var/lung_ruptured = "" - var/o_dead = "" - for(var/datum/wound/W in e.wounds) if(W.internal) - internal_bleeding = "
Internal bleeding" - break - if(istype(e, /obj/item/organ/external/chest) && occupant.is_lung_ruptured()) - lung_ruptured = "Lung ruptured:" - if(e.splinted) - splint = "Splinted:" - if(e.status & ORGAN_BLEEDING) - bled = "Bleeding:" - if(e.status & ORGAN_BROKEN) - AN = "[e.broken_description]:" - if(e.robotic >= ORGAN_ROBOT) - robot = "Prosthetic:" - if(e.status & ORGAN_DEAD) - o_dead = "Necrotic:" - if(e.open) - open = "Open:" - switch (e.germ_level) - if (INFECTION_LEVEL_ONE to INFECTION_LEVEL_ONE + 200) - infected = "Mild Infection:" - if (INFECTION_LEVEL_ONE + 200 to INFECTION_LEVEL_ONE + 300) - infected = "Mild Infection+:" - if (INFECTION_LEVEL_ONE + 300 to INFECTION_LEVEL_ONE + 400) - infected = "Mild Infection++:" - if (INFECTION_LEVEL_TWO to INFECTION_LEVEL_TWO + 200) - infected = "Acute Infection:" - if (INFECTION_LEVEL_TWO + 200 to INFECTION_LEVEL_TWO + 300) - infected = "Acute Infection+:" - if (INFECTION_LEVEL_TWO + 300 to INFECTION_LEVEL_THREE - 50) - infected = "Acute Infection++:" - if (INFECTION_LEVEL_THREE -49 to INFINITY) - infected = "Gangrene Detected:" - - var/unknown_body = 0 - for(var/I in e.implants) - if(is_type_in_list(I,known_implants)) - imp += "[I] implanted:" - else - unknown_body++ - - if(unknown_body) - imp += "Unknown body present:" - if(!AN && !open && !infected & !imp) - AN = "None:" - if(!(e.status & ORGAN_DESTROYED)) - dat += "" - else - dat += "" - dat += "" - for(var/obj/item/organ/i in occupant.internal_organs) - var/mech = "" - var/i_dead = "" - if(i.status & ORGAN_ASSISTED) - mech = "Assisted:" - if(i.robotic >= ORGAN_ROBOT) - mech = "Mechanical:" - if(i.status & ORGAN_DEAD) - i_dead = "Necrotic:" - var/infection = "None" - switch (i.germ_level) - if (INFECTION_LEVEL_ONE to INFECTION_LEVEL_ONE + 200) - infection = "Mild Infection:" - if (INFECTION_LEVEL_ONE + 200 to INFECTION_LEVEL_ONE + 300) - infection = "Mild Infection+:" - if (INFECTION_LEVEL_ONE + 300 to INFECTION_LEVEL_ONE + 400) - infection = "Mild Infection++:" - if (INFECTION_LEVEL_TWO to INFECTION_LEVEL_TWO + 200) - infection = "Acute Infection:" - if (INFECTION_LEVEL_TWO + 200 to INFECTION_LEVEL_TWO + 300) - infection = "Acute Infection+:" - if (INFECTION_LEVEL_TWO + 300 to INFECTION_LEVEL_THREE - 50) - infection = "Acute Infection++:" - if (INFECTION_LEVEL_THREE -49 to INFINITY) - infection = "Necrosis Detected:" - - dat += "" - dat += "" - dat += "" - dat += "
OrganBurn DamageBrute DamageOther Wounds
[e.name][e.burn_dam][e.brute_dam][robot][bled][AN][splint][open][infected][imp][internal_bleeding][lung_ruptured][o_dead][e.name]--Not Found
[i.name]N/A[i.damage][infection]:[mech][i_dead]
" - if(occupant.sdisabilities & BLIND) - dat += "Cataracts detected.
" - if(occupant.disabilities & NEARSIGHTED) - dat += "Retinal misalignment detected.
" - else - dat += "\The [src] is empty." - else - dat = " Error: No Body Scanner connected." - - printing_text = dat + return scanner.tgui_interact(user) \ No newline at end of file diff --git a/code/game/machinery/adv_med_vr.dm b/code/game/machinery/adv_med_vr.dm index 6c11b6cf34e..c7bcf0d0657 100644 --- a/code/game/machinery/adv_med_vr.dm +++ b/code/game/machinery/adv_med_vr.dm @@ -7,7 +7,7 @@ icon_state = "scanner_terminal_off" density = 1 -/obj/machinery/bodyscanner/proc/get_occupant_data_vr(list/incoming,mob/living/carbon/human/H) +/obj/machinery/bodyscanner/proc/get_occupant_data_vr(list/incoming, mob/living/carbon/human/H) var/humanprey = 0 var/livingprey = 0 var/objectprey = 0 diff --git a/code/game/machinery/cloning.dm b/code/game/machinery/cloning.dm index 2c6a5d34e62..c4b368b8d3e 100644 --- a/code/game/machinery/cloning.dm +++ b/code/game/machinery/cloning.dm @@ -24,6 +24,7 @@ return selected #define CLONE_BIOMASS 30 //VOREstation Edit +#define MINIMUM_HEAL_LEVEL 40 /obj/machinery/clonepod name = "cloning pod" @@ -46,6 +47,9 @@ var/list/containers = list() // Beakers for our liquid biomass var/container_limit = 3 // How many beakers can the machine hold? + var/speed_coeff + var/efficiency + /obj/machinery/clonepod/Initialize() . = ..() default_apply_parts() @@ -291,13 +295,16 @@ /obj/machinery/clonepod/RefreshParts() ..() - var/rating = 0 - for(var/obj/item/weapon/stock_parts/P in component_parts) - if(istype(P, /obj/item/weapon/stock_parts/scanning_module) || istype(P, /obj/item/weapon/stock_parts/manipulator)) - rating += P.rating + speed_coeff = 0 + efficiency = 0 + for(var/obj/item/weapon/stock_parts/scanning_module/S in component_parts) + efficiency += S.rating + for(var/obj/item/weapon/stock_parts/manipulator/P in component_parts) + speed_coeff += P.rating + heal_level = max(min((efficiency * 15) + 10, 100), MINIMUM_HEAL_LEVEL) - heal_level = rating * 10 - 20 - heal_rate = round(rating / 4) +/obj/machinery/clonepod/proc/get_completion() + . = (100 * ((occupant.health + 100) / (heal_level + 100))) /obj/machinery/clonepod/verb/eject() set name = "Eject Cloner" diff --git a/code/game/machinery/computer/Operating.dm b/code/game/machinery/computer/Operating.dm index 5caf9edd6e4..cf041a74fc0 100644 --- a/code/game/machinery/computer/Operating.dm +++ b/code/game/machinery/computer/Operating.dm @@ -1,4 +1,4 @@ -//This file was auto-corrected by findeclaration.exe on 25.5.2012 20:42:31 +#define OP_COMPUTER_COOLDOWN 60 /obj/machinery/computer/operating name = "patient monitoring console" @@ -8,66 +8,304 @@ icon_keyboard = "med_key" icon_screen = "crew" circuit = /obj/item/weapon/circuitboard/operating - var/mob/living/carbon/human/victim = null var/obj/machinery/optable/table = null + var/mob/living/carbon/human/victim = null + var/verbose = 1 //general speaker toggle + var/patientName = null + var/oxyAlarm = 30 //oxy damage at which the computer will beep + var/choice = 0 //just for going into and out of the options menu + var/healthAnnounce = 1 //healther announcer toggle + var/crit = 1 //crit beeping toggle + var/nextTick = OP_COMPUTER_COOLDOWN + var/healthAlarm = 50 + var/oxy = 1 //oxygen beeping toggle /obj/machinery/computer/operating/New() ..() for(var/direction in list(NORTH,EAST,SOUTH,WEST)) table = locate(/obj/machinery/optable, get_step(src, direction)) - if (table) + if(table) table.computer = src break +/obj/machinery/computer/operating/Destroy() + if(table) + table.computer = null + table = null + if(victim) + victim = null + return ..() + /obj/machinery/computer/operating/attack_ai(mob/user) add_fingerprint(user) if(stat & (BROKEN|NOPOWER)) return - ui_interact(user) + tgui_interact(user) /obj/machinery/computer/operating/attack_hand(mob/user) add_fingerprint(user) if(stat & (BROKEN|NOPOWER)) return - ui_interact(user) + tgui_interact(user) -/** - * Display the NanoUI window for the operating computer. - * - * See NanoUI documentation for details. - */ -/obj/machinery/computer/operating/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - user.set_machine(src) - - var/list/data = list() - var/list/victim_ui = list() - - if(table && (table.check_victim())) - victim = table.victim - - victim_ui = list("real_name" = victim.real_name, "age" = victim.age, "b_type" = victim.b_type, "health" = victim.health, - "brute" = victim.getBruteLoss(), "tox" = src.victim.getToxLoss(), "burn" = victim.getFireLoss(), "oxy" = victim.getOxyLoss(), - "stat" = (victim.stat ? "Non-Responsive" : "Stable"), "pulse" = victim.get_pulse(GETPULSE_TOOL)) - else - victim = null - victim_ui = null - - data["table"] = table - data["victim"] = victim_ui - - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open) - if (!ui) - ui = new(user, src, ui_key, "operating.tmpl", src.name, 380, 400) - ui.set_initial_data(data) +/obj/machinery/computer/operating/tgui_interact(mob/user, datum/tgui/ui = null) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "OperatingComputer", "Patient Monitor") ui.open() - ui.set_auto_update(5) -/obj/machinery/computer/operating/Topic(href, href_list) +/obj/machinery/computer/operating/tgui_data(mob/user) + var/data[0] + var/mob/living/carbon/human/occupant + if(table) + occupant = table.victim + data["hasOccupant"] = occupant ? 1 : 0 + var/occupantData[0] + + if(occupant) + occupantData["name"] = occupant.name + occupantData["stat"] = occupant.stat + occupantData["health"] = occupant.health + occupantData["maxHealth"] = occupant.maxHealth + occupantData["minHealth"] = config.health_threshold_dead + occupantData["bruteLoss"] = occupant.getBruteLoss() + occupantData["oxyLoss"] = occupant.getOxyLoss() + occupantData["toxLoss"] = occupant.getToxLoss() + occupantData["fireLoss"] = occupant.getFireLoss() + occupantData["paralysis"] = occupant.paralysis + occupantData["hasBlood"] = 0 + occupantData["bodyTemperature"] = occupant.bodytemperature + occupantData["maxTemp"] = 1000 // If you get a burning vox armalis into the sleeper, congratulations + // Because we can put simple_animals in here, we need to do something tricky to get things working nice + occupantData["temperatureSuitability"] = 0 // 0 is the baseline + if(ishuman(occupant) && occupant.species) + // I wanna do something where the bar gets bluer as the temperature gets lower + // For now, I'll just use the standard format for the temperature status + var/datum/species/sp = occupant.species + if(occupant.bodytemperature < sp.cold_level_3) + occupantData["temperatureSuitability"] = -3 + else if(occupant.bodytemperature < sp.cold_level_2) + occupantData["temperatureSuitability"] = -2 + else if(occupant.bodytemperature < sp.cold_level_1) + occupantData["temperatureSuitability"] = -1 + else if(occupant.bodytemperature > sp.heat_level_3) + occupantData["temperatureSuitability"] = 3 + else if(occupant.bodytemperature > sp.heat_level_2) + occupantData["temperatureSuitability"] = 2 + else if(occupant.bodytemperature > sp.heat_level_1) + occupantData["temperatureSuitability"] = 1 + else if(isanimal(occupant)) + var/mob/living/simple_mob/silly = occupant + if(silly.bodytemperature < silly.minbodytemp) + occupantData["temperatureSuitability"] = -3 + else if(silly.bodytemperature > silly.maxbodytemp) + occupantData["temperatureSuitability"] = 3 + // Blast you, imperial measurement system + occupantData["btCelsius"] = occupant.bodytemperature - T0C + occupantData["btFaren"] = ((occupant.bodytemperature - T0C) * (9.0/5.0))+ 32 + + if(ishuman(occupant) && !(NO_BLOOD in occupant.species.flags) && occupant.vessel) + occupantData["pulse"] = occupant.get_pulse(GETPULSE_TOOL) + occupantData["hasBlood"] = 1 + var/blood_volume = round(occupant.vessel.get_reagent_amount("blood")) + occupantData["bloodLevel"] = blood_volume + occupantData["bloodMax"] = occupant.species.blood_volume + occupantData["bloodPercent"] = round(100*(blood_volume/occupant.species.blood_volume), 0.01) //copy pasta ends here + + occupantData["bloodType"] = occupant.dna.b_type + occupantData["surgery"] = build_surgery_list(user) + + data["occupant"] = occupantData + data["verbose"]=verbose + data["oxyAlarm"]=oxyAlarm + data["choice"]=choice + data["health"]=healthAnnounce + data["crit"]=crit + data["healthAlarm"]=healthAlarm + data["oxy"]=oxy + + return data + +/obj/machinery/computer/operating/tgui_act(action, params) if(..()) - return 1 - if ((usr.contents.Find(src) || (in_range(src, usr) && istype(src.loc, /turf))) || (istype(usr, /mob/living/silicon))) + return + if((usr.contents.Find(src) || (in_range(src, usr) && istype(src.loc, /turf))) || (istype(usr, /mob/living/silicon))) usr.set_machine(src) - src.add_fingerprint(usr) - SSnanoui.update_uis(src) \ No newline at end of file + . = TRUE + switch(action) + if("verboseOn") + verbose = TRUE + if("verboseOff") + verbose = FALSE + if("healthOn") + healthAnnounce = TRUE + if("healthOff") + healthAnnounce = FALSE + if("critOn") + crit = TRUE + if("critOff") + crit = FALSE + if("oxyOn") + oxy = TRUE + if("oxyOff") + oxy = FALSE + if("oxy_adj") + oxyAlarm = clamp(text2num(params["new"]), -100, 100) + if("choiceOn") + choice = TRUE + if("choiceOff") + choice = FALSE + if("health_adj") + healthAlarm = clamp(text2num(params["new"]), -100, 100) + else + return FALSE + +/obj/machinery/computer/operating/process() + if(table && table.check_victim()) + if(verbose) + if(patientName!=table.victim.name) + patientName=table.victim.name + atom_say("New patient detected, loading stats") + victim = table.victim + atom_say("[victim.real_name], [victim.dna.b_type] blood, [victim.stat ? "Non-Responsive" : "Awake"]") + SStgui.update_uis(src) + if(nextTick < world.time) + nextTick=world.time + OP_COMPUTER_COOLDOWN + if(crit && victim.health <= -50 ) + playsound(src.loc, 'sound/machines/defib_success.ogg', 50, 0) + if(oxy && victim.getOxyLoss()>oxyAlarm) + playsound(src.loc, 'sound/machines/defib_safetyOff.ogg', 50, 0) + if(healthAnnounce && victim.health <= healthAlarm) + atom_say("[round(victim.health)]% health.") + +// Surgery Helpers +/obj/machinery/computer/operating/proc/build_surgery_list(mob/user) + if(!istype(victim)) + return null + + . = list() + + for(var/limb in victim.organs_by_name) + var/obj/item/organ/external/E = victim.organs_by_name[limb] + if(E && E.open) + . += list(list("name" = E.name, "currentStage" = find_stage(E), "nextSteps" = find_next_steps(user, limb))) + +/** + * This proc is actually hell. I hate the surgery system Polaris uses. + * Basically, surgery is completely stateless, and what "stage" we're on is just dependent + * on the current state of 5 separate variables that determine what stages we can perform + * next. + * + * So, here's a little guide to understand this proc: + * Surgery is broken down into 5 different variables: + * `open`, + * `stage`, + * `cavity`, + * `burn_stage`, + * and `brute_stage`. + * Naturally, the values assigned to these don't use defines or names or anything, they're just magic numbers. + * So, we have to figure out ourselves what we should call each value. + * Open can be 4 values, and represents the "openness" of the surgery site. + * 1 = Cut Open. + * 2 = Retracted. + * 2.5 = Bones cut. + * 3 = Bones spread. + * Stage can be 3 values, and represents the progress in fixing broken bones + * 0 = Closed, can be either "we're done" or "we haven't started" FFS. + * 1 = Bones glued. + * 2 = Bones set. + * Cavity is just representing the cavity implant surgeries, and can be 2 values. + * 0 = Cavity Closed + * 1 = Cavity Open + * burn_stage and brute_stage are literally only used for repairing brute/burn damage to limbs + * I have no idea why you would ever perform these surgeries, given that Bicaradine and Kelotane exist. + * So I'm not even going to bother trying to represent them here. Fuck it. + */ +/obj/machinery/computer/operating/proc/find_stage(var/obj/item/organ/external/E) + . = "None." + switch(E.open) + if(1) + . = "Incision made." + if(2) + . = "Surgical site opened." + switch(E.stage) + // if(0) // Nothing. + if(1) + . = "Surgical site opened; Bones glued." + if(2) + . = "Surgical site opened; Bones set." + switch(E.cavity) + if(1) + . = "Surgical site opened; Cavity open." + if(2.5) // WHY IS THIS A FLOAT. WHY? + . = "Bones cut." + switch(E.stage) + // if(0) // Nothing. + if(1) + . = "Bones cut; Bones glued." + if(2) + . = "Bones cut; Bones set." + if(3) + . = "Bones retracted." + switch(E.stage) + // if(0) // Nothing. + if(1) + . = "Bones retracted; Bones glued." + if(2) + . = "Bones retracted; Bones reset." + switch(E.cavity) + if(1) + . = "Bones retracted; Cavity open." + +/** + * This converts a typepath into a pretty name. + * As best as it can, anyways. + */ +/proc/pretty_type(var/datum/A) + var/typeStr = "[A.type]" + . = copytext(typeStr, findlasttext(typeStr, "/") + 1, length(typeStr) + 1) + . = capitalize(replacetext(., "_", " ")) + +/proc/get_surgery_steps_without_basetypes() + var/static/list/good_surgeries = list() + if(LAZYLEN(good_surgeries)) + return good_surgeries + var/static/list/banned_surgery_steps = list( + /datum/surgery_step, + /datum/surgery_step/generic, + /datum/surgery_step/open_encased, + /datum/surgery_step/repairflesh, + /datum/surgery_step/face, + /datum/surgery_step/cavity, + /datum/surgery_step/limb, + /datum/surgery_step/brainstem, + ) + good_surgeries = surgery_steps + for(var/datum/surgery_step/S in good_surgeries) + if(S.type in banned_surgery_steps) + good_surgeries -= S + if(!LAZYLEN(S.allowed_tools)) + good_surgeries -= S + return good_surgeries + +/** + * Funnily enough, this proc is actually considerably less awful than find_stage. + * All we have to do is check what surgeries can be done, like surgery mechanics themselves do. + * Then, build a string telling the user what they can do next. + */ +/obj/machinery/computer/operating/proc/find_next_steps(mob/user, zone) + . = list() + for(var/datum/surgery_step/S in get_surgery_steps_without_basetypes()) + if(S.can_use(user, victim, zone, null) && S.is_valid_target(victim)) + var/allowed_tools_by_name = list() + for(var/tool in S.allowed_tools) + // Exempt ghetto tools. + if(S.allowed_tools[tool] < 100) + continue + var/obj/tool_path = tool + allowed_tools_by_name += capitalize(initial(tool_path.name)) + // Please for the love of all that is holy, someone make surgery steps + // have names so I don't have to do this stupid pretty_type shit. + . += "[pretty_type(S)]: [english_list(allowed_tools_by_name)]" diff --git a/code/game/machinery/computer/cloning.dm b/code/game/machinery/computer/cloning.dm index 30c15f6d04a..07fac6fb0d2 100644 --- a/code/game/machinery/computer/cloning.dm +++ b/code/game/machinery/computer/cloning.dm @@ -1,34 +1,64 @@ +#define MENU_MAIN 1 +#define MENU_RECORDS 2 + /obj/machinery/computer/cloning - name = "cloning control console" - desc = "Used to start cloning cycles, as well as manage clone records." + name = "cloning console" + icon = 'icons/obj/computer.dmi' icon_keyboard = "med_key" icon_screen = "dna" - light_color = "#315ab4" circuit = /obj/item/weapon/circuitboard/cloning req_access = list(access_heads) //Only used for record deletion right now. var/obj/machinery/dna_scannernew/scanner = null //Linked scanner. For scanning. - var/list/pods = list() //Linked cloning pods. - var/temp = "" - var/scantemp = "Scanner unoccupied" - var/menu = 1 //Which menu screen to display - var/list/records = list() + var/list/pods = null //Linked cloning pods. + var/list/temp = null + var/list/scantemp = null + var/menu = MENU_MAIN //Which menu screen to display + var/list/records = null var/datum/dna2/record/active_record = null var/obj/item/weapon/disk/data/diskette = null //Mostly so the geneticist can steal everything. var/loading = 0 // Nice loading text + var/autoprocess = 0 + var/obj/machinery/clonepod/selected_pod + // 0: Standard body scan + // 1: The "Best" scan available + var/scan_mode = 1 + light_color = "#315ab4" /obj/machinery/computer/cloning/Initialize() - . = ..() + ..() + pods = list() + records = list() + set_scan_temp("Scanner ready.", "good") updatemodules() /obj/machinery/computer/cloning/Destroy() releasecloner() - ..() + return ..() + +/obj/machinery/computer/cloning/process() + if(!scanner || !pods.len || !autoprocess || stat & NOPOWER) + return + + if(scanner.occupant && can_autoprocess()) + scan_mob(scanner.occupant) + + if(!LAZYLEN(records)) + return + + for(var/obj/machinery/clonepod/pod in pods) + if(!(pod.occupant || pod.mess) && (pod.efficiency > 5)) + for(var/datum/dna2/record/R in records) + if(!(pod.occupant || pod.mess)) + if(pod.growclone(R)) + records.Remove(R) /obj/machinery/computer/cloning/proc/updatemodules() scanner = findscanner() releasecloner() findcloner() + if(!selected_pod && pods.len) + selected_pod = pods[1] /obj/machinery/computer/cloning/proc/findscanner() var/obj/machinery/dna_scannernew/scannerf = null @@ -36,16 +66,15 @@ //Try to find scanner on adjacent tiles first for(dir in list(NORTH,EAST,SOUTH,WEST)) scannerf = locate(/obj/machinery/dna_scannernew, get_step(src, dir)) - if (scannerf) + if(scannerf) return scannerf //Then look for a free one in the area if(!scannerf) - var/area/A = get_area(src) - for(var/obj/machinery/dna_scannernew/S in A.get_contents()) + for(var/obj/machinery/dna_scannernew/S in get_area(src)) return S - return + return 0 /obj/machinery/computer/cloning/proc/releasecloner() for(var/obj/machinery/clonepod/P in pods) @@ -55,21 +84,20 @@ /obj/machinery/computer/cloning/proc/findcloner() var/num = 1 - var/area/A = get_area(src) - for(var/obj/machinery/clonepod/P in A.get_contents()) + for(var/obj/machinery/clonepod/P in get_area(src)) if(!P.connected) pods += P P.connected = src P.name = "[initial(P.name)] #[num++]" -/obj/machinery/computer/cloning/attackby(obj/item/W as obj, mob/user as mob) - if (istype(W, /obj/item/weapon/disk/data)) //INSERT SOME DISKETTES - if (!diskette) +/obj/machinery/computer/cloning/attackby(obj/item/W as obj, mob/user as mob, params) + if(istype(W, /obj/item/weapon/disk/data)) //INSERT SOME DISKETTES + if(!diskette) user.drop_item() W.loc = src diskette = W to_chat(user, "You insert [W].") - updateUsrDialog() + SStgui.update_uis(src) return else if(istype(W, /obj/item/device/multitool)) var/obj/item/device/multitool/M = W @@ -79,18 +107,8 @@ P.connected = src P.name = "[initial(P.name)] #[pods.len]" to_chat(user, "You connect [P] to [src].") - - else if (menu == 4 && (istype(W, /obj/item/weapon/card/id) || istype(W, /obj/item/device/pda))) - if(check_access(W)) - records.Remove(active_record) - qdel(active_record) - temp = "Record deleted." - menu = 2 - else - temp = "Access Denied." else - ..() - return + return ..() /obj/machinery/computer/cloning/attack_ai(mob/user as mob) return attack_hand(user) @@ -103,233 +121,303 @@ return updatemodules() + tgui_interact(user) - ui_interact(user) +/obj/machinery/computer/cloning/resleeving/ui_assets(mob/user) + return list( + get_asset_datum(/datum/asset/cloning) + ) -/obj/machinery/computer/cloning/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - user.set_machine(src) - - var/data[0] - - var/records_list_ui[0] - for(var/datum/dna2/record/R in records) - records_list_ui[++records_list_ui.len] = list("ckey" = R.ckey, "name" = R.dna.real_name) - - var/pods_list_ui[0] - for(var/obj/machinery/clonepod/pod in pods) - pods_list_ui[++pods_list_ui.len] = list("pod" = pod, "biomass" = pod.get_biomass()) - - if(pods) - data["pods"] = pods_list_ui - else - data["pods"] = null - - if(records) - data["records"] = records_list_ui - else - data["records"] = null - - if(active_record) - data["activeRecord"] = list("ckey" = active_record.ckey, "real_name" = active_record.dna.real_name, \ - "ui" = active_record.dna.uni_identity, "se" = active_record.dna.struc_enzymes) - else - data["activeRecord"] = null - - data["menu"] = menu - data["connected"] = scanner - data["podsLen"] = pods.len - data["loading"] = loading - if(!scanner.occupant) - scantemp = "" - data["scantemp"] = scantemp - data["occupant"] = scanner.occupant - data["locked"] = scanner.locked - data["diskette"] = diskette - data["temp"] = temp - - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open) - if (!ui) - ui = new(user, src, ui_key, "cloning.tmpl", src.name, 400, 450) - ui.set_initial_data(data) - ui.open() - ui.set_auto_update(5) - -/obj/machinery/computer/cloning/Topic(href, href_list) - if(..()) - return 1 - - if(loading) +/obj/machinery/computer/cloning/tgui_interact(mob/user, datum/tgui/ui = null) + if(stat & (NOPOWER|BROKEN)) return - if ((href_list["scan"]) && (!isnull(scanner))) - scantemp = "" + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "CloningConsole", "Cloning Console") + ui.open() - loading = 1 +/obj/machinery/computer/cloning/tgui_data(mob/user) + var/data[0] + data["menu"] = menu + data["scanner"] = sanitize("[scanner]") - spawn(20) - scan_mob(scanner.occupant) + var/canpodautoprocess = 0 + if(pods.len) + data["numberofpods"] = pods.len - loading = 0 + var/list/tempods[0] + for(var/obj/machinery/clonepod/pod in pods) + if(pod.efficiency > 5) + canpodautoprocess = 1 - //No locking an open scanner. - else if ((href_list["lock"]) && (!isnull(scanner))) - if ((!scanner.locked) && (scanner.occupant)) - scanner.locked = 1 - else - scanner.locked = 0 + var/status = "idle" + if(pod.mess) + status = "mess" + else if(pod.occupant && !(pod.stat & NOPOWER)) + status = "cloning" + tempods.Add(list(list( + "pod" = "\ref[pod]", + "name" = sanitize(capitalize(pod.name)), + "biomass" = pod.get_biomass(), + "status" = status, + "progress" = (pod.occupant && pod.occupant.stat != DEAD) ? pod.get_completion() : 0 + ))) + data["pods"] = tempods - else if ((href_list["eject"]) && (!isnull(scanner))) - if ((!scanner.locked) && (scanner.occupant)) - scanner.eject_occupant() + data["loading"] = loading + data["autoprocess"] = autoprocess + data["can_brainscan"] = can_brainscan() // You'll need tier 4s for this + data["scan_mode"] = scan_mode - else if (href_list["view_rec"]) - active_record = find_record(href_list["view_rec"]) - if(istype(active_record,/datum/dna2/record)) - if ((isnull(active_record.ckey))) - qdel(active_record) - temp = "ERROR: Record Corrupt" - else - menu = 3 - else - active_record = null - temp = "Record missing." + if(scanner && pods.len && ((scanner.scan_level > 2) || canpodautoprocess)) + data["autoallowed"] = 1 + else + data["autoallowed"] = 0 + if(scanner) + data["occupant"] = scanner.occupant + data["locked"] = scanner.locked + data["temp"] = temp + data["scantemp"] = scantemp + data["disk"] = diskette + data["selected_pod"] = "\ref[selected_pod]" + var/list/temprecords[0] + for(var/datum/dna2/record/R in records) + var tempRealName = R.dna.real_name + temprecords.Add(list(list("record" = "\ref[R]", "realname" = sanitize(tempRealName)))) + data["records"] = temprecords - else if (href_list["del_rec"]) - if ((!active_record) || (menu < 3)) - return - if (menu == 3) //If we are viewing a record, confirm deletion - temp = "Delete record?" - menu = 4 + if(selected_pod && (selected_pod in pods) && selected_pod.get_biomass() >= CLONE_BIOMASS) + data["podready"] = 1 + else + data["podready"] = 0 - else if (href_list["disk"]) //Load or eject. - switch(href_list["disk"]) - if("load") - if ((isnull(diskette)) || isnull(diskette.buf)) - temp = "Load error." + data["modal"] = tgui_modal_data(src) + + return data + +/obj/machinery/computer/cloning/tgui_act(action, params) + if(..()) + return + + . = TRUE + switch(tgui_modal_act(src, action, params)) + if(TGUI_MODAL_ANSWER) + if(params["id"] == "del_rec" && active_record) + var/obj/item/weapon/card/id/C = usr.get_active_hand() + if(!istype(C) && !istype(C, /obj/item/device/pda)) + set_temp("ID not in hand.", "danger") return - if (isnull(active_record)) - temp = "Record error." - menu = 1 - return - - active_record = diskette.buf - - temp = "Load successful." - if("eject") - if (!isnull(diskette)) - diskette.loc = loc - diskette = null - - else if (href_list["save_disk"]) //Save to disk! - if ((isnull(diskette)) || (diskette.read_only) || (isnull(active_record))) - temp = "Save error." - - // DNA2 makes things a little simpler. - diskette.buf = active_record - diskette.buf.types = 0 - switch(href_list["save_disk"]) //Save as Ui/Ui+Ue/Se - if("ui") - diskette.buf.types = DNA2_BUF_UI - if("ue") - diskette.buf.types = DNA2_BUF_UI | DNA2_BUF_UE - if("se") - diskette.buf.types = DNA2_BUF_SE - diskette.name = "data disk - '[active_record.dna.real_name]'" - temp = "Save \[[href_list["save_disk"]]\] successful." - - else if (href_list["refresh"]) - updateUsrDialog() - - else if (href_list["clone"]) - var/datum/dna2/record/C = find_record(href_list["clone"]) - //Look for that player! They better be dead! - if(istype(C)) - //Can't clone without someone to clone. Or a pod. Or if the pod is busy. Or full of gibs. - if(!LAZYLEN(pods)) - temp = "Error: No clone pods detected." - else - var/obj/machinery/clonepod/pod = pods[1] - if (pods.len > 1) - pod = input(usr,"Select a cloning pod to use", "Pod selection") as anything in pods - if(pod.occupant) - temp = "Error: Clonepod is currently occupied." - else if(pod.get_biomass() < CLONE_BIOMASS) - temp = "Error: Not enough biomass." - else if(pod.mess) - temp = "Error: Clonepod malfunction." - else if(!config.revival_cloning) - temp = "Error: Unable to initiate cloning cycle." - else if(pod.growclone(C)) - temp = "Initiating cloning cycle..." - records.Remove(C) - qdel(C) - menu = 1 + if(check_access(C)) + records.Remove(active_record) + qdel(active_record) + set_temp("Record deleted.", "success") + menu = MENU_RECORDS else + set_temp("Access denied.", "danger") + return - var/mob/selected = find_dead_player("[C.ckey]") - selected << 'sound/machines/chime.ogg' //probably not the best sound but I think it's reasonable - var/answer = alert(selected,"Do you want to return to life?","Cloning","Yes","No") - if(answer != "No" && pod.growclone(C)) - temp = "Initiating cloning cycle..." - records.Remove(C) - qdel(C) - menu = 1 + switch(action) + if("scan") + if(!scanner || !scanner.occupant || loading) + return + set_scan_temp("Scanner ready.", "good") + loading = TRUE + + spawn(20) + if(can_brainscan() && scan_mode) + scan_mob(scanner.occupant, scan_brain = TRUE) + else + scan_mob(scanner.occupant) + loading = FALSE + SStgui.update_uis(src) + if("autoprocess") + autoprocess = text2num(params["on"]) > 0 + if("lock") + if(isnull(scanner) || !scanner.occupant) //No locking an open scanner. + return + scanner.locked = !scanner.locked + if("view_rec") + var/ref = params["ref"] + if(!length(ref)) + return + active_record = locate(ref) + if(istype(active_record)) + if(isnull(active_record.ckey)) + qdel(active_record) + set_temp("Error: Record corrupt.", "danger") + else + var/obj/item/weapon/implant/health/H = null + if(active_record.implant) + H = locate(active_record.implant) + var/list/payload = list( + activerecord = "\ref[active_record]", + health = (H && istype(H)) ? H.sensehealth() : "", + realname = sanitize(active_record.dna.real_name), + unidentity = active_record.dna.uni_identity, + strucenzymes = active_record.dna.struc_enzymes, + ) + tgui_modal_message(src, action, "", null, payload) + else + active_record = null + set_temp("Error: Record missing.", "danger") + if("del_rec") + if(!active_record) + return + tgui_modal_boolean(src, action, "Please confirm that you want to delete the record by holding your ID and pressing Delete:", yes_text = "Delete", no_text = "Cancel") + if("disk") // Disk management. + if(!length(params["option"])) + return + switch(params["option"]) + if("load") + if(isnull(diskette) || isnull(diskette.buf)) + set_temp("Error: The disk's data could not be read.", "danger") + return + else if(isnull(active_record)) + set_temp("Error: No active record was found.", "danger") + menu = MENU_MAIN + return + + active_record = diskette.buf + set_temp("Successfully loaded from disk.", "success") + if("save") + if(isnull(diskette) || diskette.read_only || isnull(active_record)) + set_temp("Error: The data could not be saved.", "danger") + return + + // DNA2 makes things a little simpler. + var/types + switch(params["savetype"]) // Save as Ui/Ui+Ue/Se + if("ui") + types = DNA2_BUF_UI + if("ue") + types = DNA2_BUF_UI|DNA2_BUF_UE + if("se") + types = DNA2_BUF_SE + else + set_temp("Error: Invalid save format.", "danger") + return + diskette.buf = active_record + diskette.buf.types = types + diskette.name = "data disk - '[active_record.dna.real_name]'" + set_temp("Successfully saved to disk.", "success") + if("eject") + if(!isnull(diskette)) + diskette.loc = loc + diskette = null + if("refresh") + SStgui.update_uis(src) + if("selectpod") + var/ref = params["ref"] + if(!length(ref)) + return + var/obj/machinery/clonepod/selected = locate(ref) + if(istype(selected) && (selected in pods)) + selected_pod = selected + if("clone") + var/ref = params["ref"] + if(!length(ref)) + return + var/datum/dna2/record/C = locate(ref) + //Look for that player! They better be dead! + if(istype(C)) + tgui_modal_clear(src) + //Can't clone without someone to clone. Or a pod. Or if the pod is busy. Or full of gibs. + if(!length(pods)) + set_temp("Error: No cloning pod detected.", "danger") + else + var/obj/machinery/clonepod/pod = selected_pod + var/cloneresult + if(!selected_pod) + set_temp("Error: No cloning pod selected.", "danger") + else if(pod.occupant) + set_temp("Error: The cloning pod is currently occupied.", "danger") + else if(pod.get_biomass() < CLONE_BIOMASS) + set_temp("Error: Not enough biomass.", "danger") + else if(pod.mess) + set_temp("Error: The cloning pod is malfunctioning.", "danger") + else if(!config.revival_cloning) + set_temp("Error: Unable to initiate cloning cycle.", "danger") else - temp = "Initiating cloning cycle...
Error: Post-initialisation failed. Cloning cycle aborted." - + cloneresult = pod.growclone(C) + if(cloneresult) + set_temp("Initiating cloning cycle...", "success") + records.Remove(C) + qdel(C) + menu = MENU_MAIN + else + set_temp("Error: Initialisation failure.", "danger") + else + set_temp("Error: Data corruption.", "danger") + if("menu") + menu = clamp(text2num(params["num"]), MENU_MAIN, MENU_RECORDS) + if("toggle_mode") + if(loading) + return + if(can_brainscan()) + scan_mode = !scan_mode + else + scan_mode = FALSE + if("eject") + if(usr.incapacitated() || !scanner || loading) + return + scanner.eject_occupant(usr) + scanner.add_fingerprint(usr) + if("cleartemp") + temp = null else - temp = "Error: Data corruption." + return FALSE - else if (href_list["menu"]) - menu = href_list["menu"] - temp = "" - scantemp = "" - - SSnanoui.update_uis(src) add_fingerprint(usr) -/obj/machinery/computer/cloning/proc/scan_mob(mob/living/carbon/human/subject as mob) - var/brain_skip = 0 - if (istype(subject, /mob/living/carbon/brain)) //Brain scans. - brain_skip = 1 - if ((isnull(subject)) || (!(ishuman(subject)) && !brain_skip) || (!subject.dna)) - scantemp = "Error: Unable to locate valid genetic data." +/obj/machinery/computer/cloning/proc/scan_mob(mob/living/carbon/human/subject as mob, var/scan_brain = 0) + if(stat & NOPOWER) return - if (!subject.has_brain() && !brain_skip) - if(istype(subject, /mob/living/carbon/human)) + if(scanner.stat & (NOPOWER|BROKEN)) + return + if(scan_brain && !can_brainscan()) + return + if(isnull(subject) || (!(ishuman(subject))) || (!subject.dna)) + if(isalien(subject)) + set_scan_temp("Xenomorphs are not scannable.", "bad") + SStgui.update_uis(src) + return + // can add more conditions for specific non-human messages here + else + set_scan_temp("Subject species is not scannable.", "bad") + SStgui.update_uis(src) + return + if(!subject.has_brain()) + if(ishuman(subject)) var/mob/living/carbon/human/H = subject if(H.should_have_organ("brain")) - scantemp = "Error: No signs of intelligence detected." + set_scan_temp("No brain detected in subject.", "bad") else - scantemp = "Error: No signs of intelligence detected." + set_scan_temp("No brain detected in subject.", "bad") + SStgui.update_uis(src) + return + if(subject.suiciding) + set_scan_temp("Subject has committed suicide and is not scannable.", "bad") + SStgui.update_uis(src) + return + if((!subject.ckey) || (!subject.client)) + set_scan_temp("Subject's brain is not responding. Further attempts after a short delay may succeed.", "bad") + SStgui.update_uis(src) + return + if((NOCLONE in subject.mutations)) + set_scan_temp("Subject has incompatible genetic mutations.", "bad") + SStgui.update_uis(src) + return + if(!isnull(find_record(subject.ckey))) + set_scan_temp("Subject already in database.") + SStgui.update_uis(src) return - if(subject.isSynthetic()) - scantemp = "Error: Majority of subject is non-organic." - return - if (subject.suiciding) - scantemp = "Error: Subject's brain is not responding to scanning stimuli." - return - if (NOCLONE in subject.mutations) - scantemp = "Error: Mental interface failure." - return - if (subject.species && subject.species.flags & NO_SCAN && !brain_skip) - scantemp = "Error: Mental interface failure." - return - for(var/modifier_type in subject.modifiers) //Can't be cloned, even if they had a previous scan - if(istype(modifier_type, /datum/modifier/no_clone)) - scantemp = "Error: Mental interface failure." + for(var/obj/machinery/clonepod/pod in pods) + if(pod.occupant && pod.occupant.mind == subject.mind) + set_scan_temp("Subject already getting cloned.") + SStgui.update_uis(src) return - if ((!subject.ckey) || (!subject.client)) - scantemp = "Error: Mental interface failure." - if(subject.stat == DEAD && subject.mind && subject.mind.key) // If they're dead and not in their body, tell them to get in it. - var/mob/observer/dead/ghost = subject.get_ghost() - if(ghost) - ghost.notify_revive("Someone is trying to scan your body in the cloner. Re-enter your body if you want to be revived!", 'sound/effects/genetics.ogg', source = src) - return - if (!isnull(find_record(subject.ckey))) - scantemp = "Subject already in database." - return subject.dna.check_integrity() @@ -342,10 +430,7 @@ R.languages = subject.languages R.gender = subject.gender R.body_descriptors = subject.descriptors - if(!brain_skip) //Brains don't have flavor text. - R.flavor = subject.flavor_texts.Copy() - else - R.flavor = list() + R.flavor = subject.flavor_texts.Copy() for(var/datum/modifier/mod in subject.modifiers) if(mod.flags & MODIFIER_GENETIC) R.genetic_modifiers.Add(mod.type) @@ -364,13 +449,47 @@ R.mind = "\ref[subject.mind]" records += R - scantemp = "Subject successfully scanned." + set_scan_temp("Subject successfully scanned.", "good") + SStgui.update_uis(src) //Find a specific record by key. /obj/machinery/computer/cloning/proc/find_record(var/find_key) var/selected_record = null for(var/datum/dna2/record/R in records) - if (R.ckey == find_key) + if(R.ckey == find_key) selected_record = R break return selected_record + +/obj/machinery/computer/cloning/proc/can_autoprocess() + return (scanner && scanner.scan_level > 2) + +/obj/machinery/computer/cloning/proc/can_brainscan() + return (scanner && scanner.scan_level > 3) + +/** + * Sets a temporary message to display to the user + * + * Arguments: + * * text - Text to display, null/empty to clear the message from the UI + * * style - The style of the message: (color name), info, success, warning, danger + */ +/obj/machinery/computer/cloning/proc/set_temp(text = "", style = "info", update_now = FALSE) + temp = list(text = text, style = style) + if(update_now) + SStgui.update_uis(src) + +/** + * Sets a temporary scan message to display to the user + * + * Arguments: + * * text - Text to display, null/empty to clear the message from the UI + * * color - The color of the message: (color name) + */ +/obj/machinery/computer/cloning/proc/set_scan_temp(text = "", color = "", update_now = FALSE) + scantemp = list(text = text, color = color) + if(update_now) + SStgui.update_uis(src) + +#undef MENU_MAIN +#undef MENU_RECORDS \ No newline at end of file diff --git a/code/game/machinery/computer/medical.dm b/code/game/machinery/computer/medical.dm index 79257147a0a..40e8152340f 100644 --- a/code/game/machinery/computer/medical.dm +++ b/code/game/machinery/computer/medical.dm @@ -1,4 +1,11 @@ -//This file was auto-corrected by findeclaration.exe on 25.5.2012 20:42:31 +#define MED_DATA_R_LIST 2 // Record list +#define MED_DATA_MAINT 3 // Records maintenance +#define MED_DATA_RECORD 4 // Record +#define MED_DATA_V_DATA 5 // Virus database +#define MED_DATA_MEDBOT 6 // Medbot monitor + +#define FIELD(N, V, E) list(field = N, value = V, edit = E) +#define MED_FIELD(N, V, E, LB) list(field = N, value = V, edit = E, line_break = LB) /obj/machinery/computer/med_data//TODO:SANITY name = "medical records console" @@ -14,9 +21,50 @@ var/screen = null var/datum/data/record/active1 = null var/datum/data/record/active2 = null - var/a_id = null - var/temp = null + var/list/temp = null var/printing = null + // The below are used to make modal generation more convenient + var/static/list/field_edit_questions + var/static/list/field_edit_choices + + +/obj/machinery/computer/med_data/Initialize() + ..() + field_edit_questions = list( + // General + "sex" = "Please select new sex:", + "age" = "Please input new age:", + "fingerprint" = "Please input new fingerprint hash:", + "p_stat" = "Please select new physical status:", + "m_stat" = "Please select new mental status:", + // Medical + "id_gender" = "Please select new gender identity:", + "blood_type" = "Please select new blood type:", + "b_dna" = "Please input new DNA:", + "mi_dis" = "Please input new minor disabilities:", + "mi_dis_d" = "Please summarize minor disabilities:", + "ma_dis" = "Please input new major disabilities:", + "ma_dis_d" = "Please summarize major disabilities:", + "alg" = "Please input new allergies:", + "alg_d" = "Please summarize allergies:", + "cdi" = "Please input new current diseases:", + "cdi_d" = "Please summarize current diseases:", + "notes" = "Please input new important notes:", + ) + field_edit_choices = list( + // General + "sex" = all_genders_text_list, + "p_stat" = list("*Deceased*", "*SSD*", "Active", "Physically Unfit", "Disabled"), + "m_stat" = list("*Insane*", "*Unstable*", "*Watch*", "Stable"), + // Medical + "id_gender" = all_genders_text_list, + "blood_type" = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O+", "O-"), + ) + +/obj/machinery/computer/med_data/Destroy() + active1 = null + active2 = null + return ..() /obj/machinery/computer/med_data/verb/eject_id() set category = "Object" @@ -40,396 +88,223 @@ O.loc = src scan = O to_chat(user, "You insert \the [O].") + tgui_interact(user) else ..() /obj/machinery/computer/med_data/attack_ai(user as mob) - return src.attack_hand(user) + return attack_hand(user) /obj/machinery/computer/med_data/attack_hand(mob/user as mob) if(..()) return - var/dat = list() - if (src.temp) - dat += text("[src.temp]

Clear Screen") - else - dat += text("Confirm Identity: []
", src, (src.scan ? text("[]", src.scan.name) : "----------")) - if (src.authenticated) - switch(src.screen) - if(1.0) - dat += {" -Search Records -
List Records -
-
Virus Database -
Medbot Tracking -
-
Record Maintenance -
{Log Out}
-"} - if(2.0) - dat += "Record List:
" - if(!isnull(data_core.general)) - for(var/datum/data/record/R in sortRecord(data_core.general)) - dat += text("[]: []
", src, R, R.fields["id"], R.fields["name"]) - //Foreach goto(132) - dat += text("
Back", src) - if(3.0) - dat += text("Records Maintenance
\nBackup To Disk
\nUpload From disk
\nDelete All Records
\n
\nBack", src, src, src, src) - if(4.0) - var/icon/front = active1.fields["photo_front"] - var/icon/side = active1.fields["photo_side"] - user << browse_rsc(front, "front.png") - user << browse_rsc(side, "side.png") - dat += "
Medical Record

" - if ((istype(src.active1, /datum/data/record) && data_core.general.Find(src.active1))) - dat += "
Name: [active1.fields["name"]] \ - ID: [active1.fields["id"]]
\n \ - Entity Classification: [active1.fields["brain_type"]]
\n \ - Sex: [active1.fields["sex"]]
\n" - if ((istype(src.active2, /datum/data/record) && data_core.medical.Find(src.active2))) - dat += "Gender identity: [active2.fields["id_gender"]]
" + add_fingerprint(user) + tgui_interact(user) + + +/obj/machinery/computer/med_data/tgui_interact(mob/user, datum/tgui/ui = null) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "MedicalRecords", "Medical Records") // 800, 380 + ui.open() + ui.set_autoupdate(FALSE) + + +/obj/machinery/computer/med_data/tgui_data(mob/user) + var/data[0] + data["temp"] = temp + data["scan"] = scan ? scan.name : null + data["authenticated"] = authenticated + data["rank"] = rank + data["screen"] = screen + data["printing"] = printing + data["isAI"] = isAI(user) + data["isRobot"] = isrobot(user) + if(authenticated) + switch(screen) + if(MED_DATA_R_LIST) + if(!isnull(data_core.general)) + var/list/records = list() + data["records"] = records + for(var/datum/data/record/R in sortRecord(data_core.general)) + records[++records.len] = list("ref" = "\ref[R]", "id" = R.fields["id"], "name" = R.fields["name"]) + if(MED_DATA_RECORD) + var/list/general = list() + data["general"] = general + if(istype(active1, /datum/data/record) && data_core.general.Find(active1)) + var/list/fields = list() + general["fields"] = fields + fields[++fields.len] = FIELD("Name", active1.fields["name"], null) + fields[++fields.len] = FIELD("ID", active1.fields["id"], null) + fields[++fields.len] = FIELD("Sex", active1.fields["sex"], "sex") + fields[++fields.len] = FIELD("Age", active1.fields["age"], "age") + fields[++fields.len] = FIELD("Fingerprint", active1.fields["fingerprint"], "fingerprint") + fields[++fields.len] = FIELD("Physical Status", active1.fields["p_stat"], "p_stat") + fields[++fields.len] = FIELD("Mental Status", active1.fields["m_stat"], "m_stat") + var/list/photos = list() + general["photos"] = photos + photos[++photos.len] = active1.fields["photo-south"] + photos[++photos.len] = active1.fields["photo-west"] + general["has_photos"] = (active1.fields["photo-south"] || active1.fields["photo-west"] ? 1 : 0) + general["empty"] = 0 + else + general["empty"] = 1 + + var/list/medical = list() + data["medical"] = medical + if(istype(active2, /datum/data/record) && data_core.medical.Find(active2)) + var/list/fields = list() + medical["fields"] = fields + fields[++fields.len] = MED_FIELD("Gender identity", active2.fields["id_gender"], "id_gender", TRUE) + fields[++fields.len] = MED_FIELD("Blood Type", active2.fields["b_type"], "blood_type", FALSE) + fields[++fields.len] = MED_FIELD("DNA", active2.fields["b_dna"], "b_dna", TRUE) + fields[++fields.len] = MED_FIELD("Brain Type", active2.fields["brain_type"], "brain_type", TRUE) + fields[++fields.len] = MED_FIELD("Important Notes", active2.fields["notes"], "notes", TRUE) + if(!active2.fields["comments"] || !islist(active2.fields["comments"])) + active2.fields["comments"] = list() + medical["comments"] = active2.fields["comments"] + medical["empty"] = 0 + else + medical["empty"] = 1 + if(MED_DATA_V_DATA) + data["virus"] = list() + for(var/ID in virusDB) + var/datum/data/record/v = virusDB[ID] + data["virus"] += list(list("name" = v.fields["name"], "D" = v)) + if(MED_DATA_MEDBOT) + data["medbots"] = list() + for(var/mob/living/bot/medbot/M in mob_list) + if(M.z != z) + continue + var/turf/T = get_turf(M) + if(T) + var/medbot = list() + var/area/A = get_area(T) + medbot["name"] = M.name + medbot["area"] = A.name + medbot["x"] = T.x + medbot["y"] = T.y + medbot["on"] = M.on + if(!isnull(M.reagent_glass) && M.use_beaker) + medbot["use_beaker"] = 1 + medbot["total_volume"] = M.reagent_glass.reagents.total_volume + medbot["maximum_volume"] = M.reagent_glass.reagents.maximum_volume else - dat += "Gender identity: Unknown
" - dat += "Age: [active1.fields["age"]]
\n \ - Fingerprint: [active1.fields["fingerprint"]]
\n \ - Physical Status: [active1.fields["p_stat"]]
\n \ - Mental Status: [active1.fields["m_stat"]]
\ - Photo:
" - else - dat += "General Record Lost!
" - if ((istype(src.active2, /datum/data/record) && data_core.medical.Find(src.active2))) - dat += text("
\n
Medical Data

\nBlood Type: []
\nDNA: []
\n
\nMinor Disabilities: []
\nDetails: []
\n
\nMajor Disabilities: []
\nDetails: []
\n
\nAllergies: []
\nDetails: []
\n
\nCurrent Diseases: [] (per disease info placed in log/comment section)
\nDetails: []
\n
\nImportant Notes:
\n\t[]
\n
\n
Comments/Log

", src, src.active2.fields["b_type"], src, src.active2.fields["b_dna"], src, src.active2.fields["mi_dis"], src, src.active2.fields["mi_dis_d"], src, src.active2.fields["ma_dis"], src, src.active2.fields["ma_dis_d"], src, src.active2.fields["alg"], src, src.active2.fields["alg_d"], src, src.active2.fields["cdi"], src, src.active2.fields["cdi_d"], src, decode(src.active2.fields["notes"])) - var/counter = 1 - while(src.active2.fields[text("com_[]", counter)]) - dat += text("[]
Delete Entry

", src.active2.fields[text("com_[]", counter)], src, counter) - counter++ - dat += text("Add Entry

", src) - dat += text("Delete Record (Medical Only)

", src) - else - dat += "Medical Record Lost!
" - dat += text("New Record

") - dat += text("\nPrint Record
\nBack
", src, src) - if(5.0) - dat += "
Virus Database
" - for (var/ID in virusDB) - var/datum/data/record/v = virusDB[ID] - dat += "
[v.fields["name"]]" + medbot["use_beaker"] = 0 + data["medbots"] += list(medbot) - dat += "
Back" - if(6.0) - dat += "
Medical Robot Monitor
" - dat += "Back" - dat += "
Medical Robots:" - var/bdat = null - for(var/mob/living/bot/medbot/M in mob_list) + data["modal"] = tgui_modal_data(src) + return data - if(M.z != src.z) continue //only find medibots on the same z-level as the computer - var/turf/bl = get_turf(M) - if(bl) //if it can't find a turf for the medibot, then it probably shouldn't be showing up - bdat += "[M.name] - \[[bl.x],[bl.y]\] - [M.on ? "Online" : "Offline"]
" - if((!isnull(M.reagent_glass)) && M.use_beaker) - bdat += "Reservoir: \[[M.reagent_glass.reagents.total_volume]/[M.reagent_glass.reagents.maximum_volume]\]
" - else - bdat += "Using Internal Synthesizer.
" - if(!bdat) - dat += "
None detected
" - else - dat += "
[bdat]" - - else - else - dat += text("{Log In}", src) - dat = jointext(dat,null) - user << browse(text("Medical Records[]", dat), "window=med_rec") - onclose(user, "med_rec") - return - -/obj/machinery/computer/med_data/Topic(href, href_list) +/obj/machinery/computer/med_data/tgui_act(action, params) if(..()) - return 1 + return - if (!( data_core.general.Find(src.active1) )) - src.active1 = null + if(!data_core.general.Find(active1)) + active1 = null + if(!data_core.medical.Find(active2)) + active2 = null - if (!( data_core.medical.Find(src.active2) )) - src.active2 = null - - if ((usr.contents.Find(src) || (in_range(src, usr) && istype(src.loc, /turf))) || (istype(usr, /mob/living/silicon))) - usr.set_machine(src) - - if (href_list["temp"]) - src.temp = null - - if (href_list["scan"]) - if (src.scan) - - if(ishuman(usr)) - scan.loc = usr.loc - - if(!usr.get_active_hand()) - usr.put_in_hands(scan) - - scan = null - - else - src.scan.loc = src.loc - src.scan = null + . = TRUE + if(tgui_act_modal(action, params)) + return + switch(action) + if("cleartemp") + temp = null + if("scan") + if(scan) + scan.forceMove(loc) + if(ishuman(usr) && !usr.get_active_hand()) + usr.put_in_hands(scan) + scan = null else var/obj/item/I = usr.get_active_hand() - if (istype(I, /obj/item/weapon/card/id)) + if(istype(I, /obj/item/weapon/card/id)) usr.drop_item() - I.loc = src - src.scan = I - - else if (href_list["logout"]) - src.authenticated = null - src.screen = null - src.active1 = null - src.active2 = null - - else if (href_list["login"]) - - if (istype(usr, /mob/living/silicon/ai)) - src.active1 = null - src.active2 = null - src.authenticated = usr.name - src.rank = "AI" - src.screen = 1 - - else if (istype(usr, /mob/living/silicon/robot)) - src.active1 = null - src.active2 = null - src.authenticated = usr.name + I.forceMove(src) + scan = I + if("login") + var/login_type = text2num(params["login_type"]) + if(login_type == LOGIN_TYPE_NORMAL && istype(scan)) + if(check_access(scan)) + authenticated = scan.registered_name + rank = scan.assignment + else if(login_type == LOGIN_TYPE_AI && isAI(usr)) + authenticated = usr.name + rank = "AI" + else if(login_type == LOGIN_TYPE_ROBOT && isrobot(usr)) + authenticated = usr.name var/mob/living/silicon/robot/R = usr - src.rank = "[R.modtype] [R.braintype]" - src.screen = 1 + rank = "[R.modtype] [R.braintype]" + if(authenticated) + active1 = null + active2 = null + screen = MED_DATA_R_LIST + else + . = FALSE - else if (istype(src.scan, /obj/item/weapon/card/id)) - src.active1 = null - src.active2 = null + if(.) + return - if (src.check_access(src.scan)) - src.authenticated = src.scan.registered_name - src.rank = src.scan.assignment - src.screen = 1 - - if (src.authenticated) - - if(href_list["screen"]) - src.screen = text2num(href_list["screen"]) - if(src.screen < 1) - src.screen = 1 - - src.active1 = null - src.active2 = null - - if(href_list["vir"]) - var/datum/data/record/v = locate(href_list["vir"]) - src.temp = "
GNAv2 based virus lifeform V-[v.fields["id"]]
" - src.temp += "
Name: [v.fields["name"]]" - src.temp += "
Antigen: [v.fields["antigen"]]" - src.temp += "
Spread: [v.fields["spread type"]] " - src.temp += "
Details:
[v.fields["description"]]" - - if (href_list["del_all"]) - src.temp = text("Are you sure you wish to delete all records?
\n\tYes
\n\tNo
", src, src) - - if (href_list["del_all2"]) + if(authenticated) + . = TRUE + switch(action) + if("logout") + if(scan) + scan.forceMove(loc) + if(ishuman(usr) && !usr.get_active_hand()) + usr.put_in_hands(scan) + scan = null + authenticated = null + screen = null + active1 = null + active2 = null + if("screen") + screen = clamp(text2num(params["screen"]) || 0, MED_DATA_R_LIST, MED_DATA_MEDBOT) + active1 = null + active2 = null + if("vir") + var/datum/data/record/v = locate(params["vir"]) + var/list/payload = list( + id = v.fields["id"], + name = v.fields["name"], + max_stages = "Unknown", + spread_text = v.fields["spread type"], + cure = v.fields["antigen"], + desc = v.fields["description"], + severity = "Unknown" + ); + tgui_modal_message(src, "virus", "", null, payload) + if("del_all") for(var/datum/data/record/R in data_core.medical) - //R = null qdel(R) - //Foreach goto(494) - src.temp = "All records deleted." - - if (href_list["field"]) - var/a1 = src.active1 - var/a2 = src.active2 - switch(href_list["field"]) - if("fingerprint") - if (istype(src.active1, /datum/data/record)) - var/t1 = sanitize(input("Please input fingerprint hash:", "Med. records", src.active1.fields["fingerprint"], null) as text) - if ((!( t1 ) || !( src.authenticated ) || usr.stat || usr.restrained() || (!in_range(src, usr) && (!istype(usr, /mob/living/silicon))) || src.active1 != a1)) - return - src.active1.fields["fingerprint"] = t1 - if("sex") - if (istype(src.active1, /datum/data/record)) - src.active1.fields["sex"] = next_in_list(src.active1.fields["sex"], all_genders_text_list) - if("id_gender") - if (istype(src.active2, /datum/data/record)) - src.active2.fields["id_gender"] = next_in_list(src.active2.fields["id_gender"], all_genders_text_list) - if("age") - if (istype(src.active1, /datum/data/record)) - var/t1 = input("Please input age:", "Med. records", src.active1.fields["age"], null) as num - if ((!( t1 ) || !( src.authenticated ) || usr.stat || usr.restrained() || (!in_range(src, usr) && (!istype(usr, /mob/living/silicon))) || src.active1 != a1)) - return - src.active1.fields["age"] = t1 - if("mi_dis") - if (istype(src.active2, /datum/data/record)) - var/t1 = sanitize(input("Please input minor disabilities list:", "Med. records", src.active2.fields["mi_dis"], null) as text) - if ((!( t1 ) || !( src.authenticated ) || usr.stat || usr.restrained() || (!in_range(src, usr) && (!istype(usr, /mob/living/silicon))) || src.active2 != a2)) - return - src.active2.fields["mi_dis"] = t1 - if("mi_dis_d") - if (istype(src.active2, /datum/data/record)) - var/t1 = sanitize(input("Please summarize minor dis.:", "Med. records", src.active2.fields["mi_dis_d"], null) as message) - if ((!( t1 ) || !( src.authenticated ) || usr.stat || usr.restrained() || (!in_range(src, usr) && (!istype(usr, /mob/living/silicon))) || src.active2 != a2)) - return - src.active2.fields["mi_dis_d"] = t1 - if("ma_dis") - if (istype(src.active2, /datum/data/record)) - var/t1 = sanitize(input("Please input major diabilities list:", "Med. records", src.active2.fields["ma_dis"], null) as text) - if ((!( t1 ) || !( src.authenticated ) || usr.stat || usr.restrained() || (!in_range(src, usr) && (!istype(usr, /mob/living/silicon))) || src.active2 != a2)) - return - src.active2.fields["ma_dis"] = t1 - if("ma_dis_d") - if (istype(src.active2, /datum/data/record)) - var/t1 = sanitize(input("Please summarize major dis.:", "Med. records", src.active2.fields["ma_dis_d"], null) as message) - if ((!( t1 ) || !( src.authenticated ) || usr.stat || usr.restrained() || (!in_range(src, usr) && (!istype(usr, /mob/living/silicon))) || src.active2 != a2)) - return - src.active2.fields["ma_dis_d"] = t1 - if("alg") - if (istype(src.active2, /datum/data/record)) - var/t1 = sanitize(input("Please state allergies:", "Med. records", src.active2.fields["alg"], null) as text) - if ((!( t1 ) || !( src.authenticated ) || usr.stat || usr.restrained() || (!in_range(src, usr) && (!istype(usr, /mob/living/silicon))) || src.active2 != a2)) - return - src.active2.fields["alg"] = t1 - if("alg_d") - if (istype(src.active2, /datum/data/record)) - var/t1 = sanitize(input("Please summarize allergies:", "Med. records", src.active2.fields["alg_d"], null) as message) - if ((!( t1 ) || !( src.authenticated ) || usr.stat || usr.restrained() || (!in_range(src, usr) && (!istype(usr, /mob/living/silicon))) || src.active2 != a2)) - return - src.active2.fields["alg_d"] = t1 - if("cdi") - if (istype(src.active2, /datum/data/record)) - var/t1 = sanitize(input("Please state diseases:", "Med. records", src.active2.fields["cdi"], null) as text) - if ((!( t1 ) || !( src.authenticated ) || usr.stat || usr.restrained() || (!in_range(src, usr) && (!istype(usr, /mob/living/silicon))) || src.active2 != a2)) - return - src.active2.fields["cdi"] = t1 - if("cdi_d") - if (istype(src.active2, /datum/data/record)) - var/t1 = sanitize(input("Please summarize diseases:", "Med. records", src.active2.fields["cdi_d"], null) as message) - if ((!( t1 ) || !( src.authenticated ) || usr.stat || usr.restrained() || (!in_range(src, usr) && (!istype(usr, /mob/living/silicon))) || src.active2 != a2)) - return - src.active2.fields["cdi_d"] = t1 - if("notes") - if (istype(src.active2, /datum/data/record)) - var/t1 = sanitize(input("Please summarize notes:", "Med. records", html_decode(src.active2.fields["notes"]), null) as message, extra = 0, max_length = MAX_RECORD_LENGTH) - if ((!( t1 ) || !( src.authenticated ) || usr.stat || usr.restrained() || (!in_range(src, usr) && (!istype(usr, /mob/living/silicon))) || src.active2 != a2)) - return - src.active2.fields["notes"] = t1 - if("p_stat") - if (istype(src.active1, /datum/data/record)) - src.temp = text("Physical Condition:
\n\t*Deceased*
\n\t*SSD*
\n\tActive
\n\tPhysically Unfit
\n\tDisabled
", src, src, src, src, src) - if("m_stat") - if (istype(src.active1, /datum/data/record)) - src.temp = text("Mental Condition:
\n\t*Insane*
\n\t*Unstable*
\n\t*Watch*
\n\tStable
", src, src, src, src) - if("b_type") - if (istype(src.active2, /datum/data/record)) - src.temp = text("Blood Type:
\n\tA- A+
\n\tB- B+
\n\tAB- AB+
\n\tO- O+
", src, src, src, src, src, src, src, src) - if("b_dna") - if (istype(src.active2, /datum/data/record)) - var/t1 = sanitize(input("Please input DNA hash:", "Med. records", src.active2.fields["b_dna"], null) as text) - if ((!( t1 ) || !( src.authenticated ) || usr.stat || usr.restrained() || (!in_range(src, usr) && (!istype(usr, /mob/living/silicon))) || src.active2 != a2)) - return - src.active2.fields["b_dna"] = t1 - if("vir_name") - var/datum/data/record/v = locate(href_list["edit_vir"]) - if (v) - var/t1 = sanitize(input("Please input pathogen name:", "VirusDB", v.fields["name"], null) as text) - if ((!( t1 ) || !( src.authenticated ) || usr.stat || usr.restrained() || (!in_range(src, usr) && (!istype(usr, /mob/living/silicon))) || src.active1 != a1)) - return - v.fields["name"] = t1 - if("vir_desc") - var/datum/data/record/v = locate(href_list["edit_vir"]) - if (v) - var/t1 = sanitize(input("Please input information about pathogen:", "VirusDB", v.fields["description"], null) as message) - if ((!( t1 ) || !( src.authenticated ) || usr.stat || usr.restrained() || (!in_range(src, usr) && (!istype(usr, /mob/living/silicon))) || src.active1 != a1)) - return - v.fields["description"] = t1 - else - - if (href_list["p_stat"]) - if (src.active1) - switch(href_list["p_stat"]) - if("deceased") - src.active1.fields["p_stat"] = "*Deceased*" - if("ssd") - src.active1.fields["p_stat"] = "*SSD*" - if("active") - src.active1.fields["p_stat"] = "Active" - if("unfit") - src.active1.fields["p_stat"] = "Physically Unfit" - if("disabled") - src.active1.fields["p_stat"] = "Disabled" - if(PDA_Manifest.len) - PDA_Manifest.Cut() - - if (href_list["m_stat"]) - if (src.active1) - switch(href_list["m_stat"]) - if("insane") - src.active1.fields["m_stat"] = "*Insane*" - if("unstable") - src.active1.fields["m_stat"] = "*Unstable*" - if("watch") - src.active1.fields["m_stat"] = "*Watch*" - if("stable") - src.active1.fields["m_stat"] = "Stable" - - - if (href_list["b_type"]) - if (src.active2) - switch(href_list["b_type"]) - if("an") - src.active2.fields["b_type"] = "A-" - if("bn") - src.active2.fields["b_type"] = "B-" - if("abn") - src.active2.fields["b_type"] = "AB-" - if("on") - src.active2.fields["b_type"] = "O-" - if("ap") - src.active2.fields["b_type"] = "A+" - if("bp") - src.active2.fields["b_type"] = "B+" - if("abp") - src.active2.fields["b_type"] = "AB+" - if("op") - src.active2.fields["b_type"] = "O+" - - - if (href_list["del_r"]) - if (src.active2) - src.temp = text("Are you sure you wish to delete the record (Medical Portion Only)?
\n\tYes
\n\tNo
", src, src) - - if (href_list["del_r2"]) - if (src.active2) - //src.active2 = null - qdel(src.active2) - - if (href_list["d_rec"]) - var/datum/data/record/R = locate(href_list["d_rec"]) - var/datum/data/record/M = locate(href_list["d_rec"]) - if (!( data_core.general.Find(R) )) - src.temp = "Record Not Found!" + set_temp("All medical records deleted.") + if("del_r") + if(active2) + set_temp("Medical record deleted.") + qdel(active2) + if("d_rec") + var/datum/data/record/general_record = locate(params["d_rec"] || "") + if(!data_core.general.Find(general_record)) + set_temp("Record not found.", "danger") return - for(var/datum/data/record/E in data_core.medical) - if ((E.fields["name"] == R.fields["name"] || E.fields["id"] == R.fields["id"])) - M = E - else - //Foreach continue //goto(2540) - src.active1 = R - src.active2 = M - src.screen = 4 - if (href_list["new"]) - if ((istype(src.active1, /datum/data/record) && !( istype(src.active2, /datum/data/record) ))) - var/datum/data/record/R = new /datum/data/record( ) - R.fields["name"] = src.active1.fields["name"] - R.fields["id"] = src.active1.fields["id"] - R.name = text("Medical Record #[]", R.fields["id"]) + var/datum/data/record/medical_record + for(var/datum/data/record/M in data_core.medical) + if(M.fields["name"] == general_record.fields["name"] && M.fields["id"] == general_record.fields["id"]) + medical_record = M + break + + active1 = general_record + active2 = medical_record + screen = MED_DATA_RECORD + if("new") + if(istype(active1, /datum/data/record) && !istype(active2, /datum/data/record)) + var/datum/data/record/R = new /datum/data/record() + R.fields["name"] = active1.fields["name"] + R.fields["id"] = active1.fields["id"] + R.name = "Medical Record #[R.fields["id"]]" R.fields["b_type"] = "Unknown" R.fields["b_dna"] = "Unknown" R.fields["mi_dis"] = "None" @@ -442,79 +317,157 @@ R.fields["cdi_d"] = "No diseases have been diagnosed at the moment." R.fields["notes"] = "No notes." data_core.medical += R - src.active2 = R - src.screen = 4 - - if (href_list["add_c"]) - if (!( istype(src.active2, /datum/data/record) )) + active2 = R + screen = MED_DATA_RECORD + set_temp("Medical record created.", "success") + if("del_c") + var/index = text2num(params["del_c"] || "") + if(!index || !istype(active2, /datum/data/record)) return - var/a2 = src.active2 - var/t1 = sanitize(input("Add Comment:", "Med. records", null, null) as message) - if ((!( t1 ) || !( src.authenticated ) || usr.stat || usr.restrained() || (!in_range(src, usr) && (!istype(usr, /mob/living/silicon))) || src.active2 != a2)) - return - var/counter = 1 - while(src.active2.fields[text("com_[]", counter)]) - counter++ - src.active2.fields[text("com_[counter]")] = text("Made by [authenticated] ([rank]) on [time2text(world.realtime, "DDD MMM DD")] [stationtime2text()], [game_year]
[t1]") - if (href_list["del_c"]) - if ((istype(src.active2, /datum/data/record) && src.active2.fields[text("com_[]", href_list["del_c"])])) - src.active2.fields[text("com_[]", href_list["del_c"])] = "Deleted" - - if (href_list["search"]) - var/t1 = input("Search String: (Name, DNA, or ID)", "Med. records", null, null) as text - if ((!( t1 ) || usr.stat || !( src.authenticated ) || usr.restrained() || ((!in_range(src, usr)) && (!istype(usr, /mob/living/silicon))))) + var/list/comments = active2.fields["comments"] + index = clamp(index, 1, length(comments)) + if(comments[index]) + comments.Cut(index, index + 1) + if("search") + active1 = null + active2 = null + var/t1 = lowertext(params["t1"] || "") + if(!length(t1)) return - src.active1 = null - src.active2 = null - t1 = lowertext(t1) + for(var/datum/data/record/R in data_core.medical) - if ((lowertext(R.fields["name"]) == t1 || t1 == lowertext(R.fields["id"]) || t1 == lowertext(R.fields["b_dna"]))) - src.active2 = R + if(t1 == lowertext(R.fields["name"]) || t1 == lowertext(R.fields["id"]) || t1 == lowertext(R.fields["b_dna"])) + active2 = R + break + if(!active2) + set_temp("Medical record not found. You must enter the person's exact name, ID or DNA.", "danger") + return + for(var/datum/data/record/E in data_core.general) + if(E.fields["name"] == active2.fields["name"] && E.fields["id"] == active2.fields["id"]) + active1 = E + break + screen = MED_DATA_RECORD + if("print_p") + if(!printing) + printing = TRUE + // playsound(loc, 'sound/goonstation/machines/printer_dotmatrix.ogg', 50, TRUE) + SStgui.update_uis(src) + addtimer(CALLBACK(src, .proc/print_finish), 5 SECONDS) + else + return FALSE + +/** + * Called in tgui_act() to process modal actions + * + * Arguments: + * * action - The action passed by tgui + * * params - The params passed by tgui + */ +/obj/machinery/computer/med_data/proc/tgui_act_modal(action, params) + . = TRUE + var/id = params["id"] // The modal's ID + var/list/arguments = istext(params["arguments"]) ? json_decode(params["arguments"]) : params["arguments"] + switch(tgui_modal_act(src, action, params)) + if(TGUI_MODAL_OPEN) + switch(id) + if("edit") + var/field = arguments["field"] + if(!length(field) || !field_edit_questions[field]) + return + var/question = field_edit_questions[field] + var/choices = field_edit_choices[field] + if(length(choices)) + tgui_modal_choice(src, id, question, arguments = arguments, value = arguments["value"], choices = choices) else - //Foreach continue //goto(3229) - if (!( src.active2 )) - src.temp = text("Could not locate record [].", t1) + tgui_modal_input(src, id, question, arguments = arguments, value = arguments["value"]) + if("add_c") + tgui_modal_input(src, id, "Please enter your message:") else - for(var/datum/data/record/E in data_core.general) - if ((E.fields["name"] == src.active2.fields["name"] || E.fields["id"] == src.active2.fields["id"])) - src.active1 = E - else - //Foreach continue //goto(3334) - src.screen = 4 + return FALSE + if(TGUI_MODAL_ANSWER) + var/answer = params["answer"] + switch(id) + if("edit") + var/field = arguments["field"] + if(!length(field) || !field_edit_questions[field]) + return + var/list/choices = field_edit_choices[field] + if(length(choices) && !(answer in choices)) + return - if (href_list["print_p"]) - if (!( src.printing )) - src.printing = 1 - var/datum/data/record/record1 = null - var/datum/data/record/record2 = null - if ((istype(src.active1, /datum/data/record) && data_core.general.Find(src.active1))) - record1 = active1 - if ((istype(src.active2, /datum/data/record) && data_core.medical.Find(src.active2))) - record2 = active2 - sleep(50) - var/obj/item/weapon/paper/P = new /obj/item/weapon/paper( src.loc ) - P.info = "
Medical Record

" - if (record1) - P.info += text("Name: [] ID: []
\nSex: []
\nAge: []
\nFingerprint: []
\nPhysical Status: []
\nMental Status: []
", record1.fields["name"], record1.fields["id"], record1.fields["sex"], record1.fields["age"], record1.fields["fingerprint"], record1.fields["p_stat"], record1.fields["m_stat"]) - P.name = text("Medical Record ([])", record1.fields["name"]) - else - P.info += "General Record Lost!
" - P.name = "Medical Record" - if (record2) - P.info += text("
\n
Medical Data

\nBlood Type: []
\nDNA: []
\n
\nMinor Disabilities: []
\nDetails: []
\n
\nMajor Disabilities: []
\nDetails: []
\n
\nAllergies: []
\nDetails: []
\n
\nCurrent Diseases: [] (per disease info placed in log/comment section)
\nDetails: []
\n
\nImportant Notes:
\n\t[]
\n
\n
Comments/Log

", record2.fields["b_type"], record2.fields["b_dna"], record2.fields["mi_dis"], record2.fields["mi_dis_d"], record2.fields["ma_dis"], record2.fields["ma_dis_d"], record2.fields["alg"], record2.fields["alg_d"], record2.fields["cdi"], record2.fields["cdi_d"], decode(record2.fields["notes"])) - var/counter = 1 - while(record2.fields[text("com_[]", counter)]) - P.info += text("[]
", record2.fields[text("com_[]", counter)]) - counter++ - else - P.info += "Medical Record Lost!
" - P.info += "" - src.printing = null + if(field == "age") + answer = text2num(answer) - src.add_fingerprint(usr) - src.updateUsrDialog() - return + if(istype(active2) && (field in active2.fields)) + active2.fields[field] = answer + else if(istype(active1) && (field in active1.fields)) + active1.fields[field] = answer + if("add_c") + if(!length(answer) || !istype(active2) || !length(authenticated)) + return + active2.fields["comments"] += list(list( + header = "Made by [authenticated] ([rank]) at [worldtime2stationtime(world.time)]", + text = answer + )) + else + return FALSE + else + return FALSE + + +/** + * Called when the print timer finishes + */ +/obj/machinery/computer/med_data/proc/print_finish() + var/obj/item/weapon/paper/P = new(loc) + P.info = "
Medical Record

" + if(istype(active1, /datum/data/record) && data_core.general.Find(active1)) + P.info += {"Name: [active1.fields["name"]] ID: [active1.fields["id"]] +
\nSex: [active1.fields["sex"]] +
\nAge: [active1.fields["age"]] +
\nFingerprint: [active1.fields["fingerprint"]] +
\nPhysical Status: [active1.fields["p_stat"]] +
\nMental Status: [active1.fields["m_stat"]]
"} + else + P.info += "General Record Lost!
" + if(istype(active2, /datum/data/record) && data_core.medical.Find(active2)) + P.info += {"
\n
Medical Data
+
\nGender Identity: [active2.fields["id_gender"]] +
\nBlood Type: [active2.fields["b_type"]] +
\nDNA: [active2.fields["b_dna"]]
\n +
\nMinor Disabilities: [active2.fields["mi_dis"]] +
\nDetails: [active2.fields["mi_dis_d"]]
\n +
\nMajor Disabilities: [active2.fields["ma_dis"]] +
\nDetails: [active2.fields["ma_dis_d"]]
\n +
\nAllergies: [active2.fields["alg"]] +
\nDetails: [active2.fields["alg_d"]]
\n +
\nCurrent Diseases: [active2.fields["cdi"]] (per disease info placed in log/comment section) +
\nDetails: [active2.fields["cdi_d"]]
\n +
\nImportant Notes: +
\n\t[active2.fields["notes"]]
\n +
\n +
Comments/Log

"} + for(var/c in active2.fields["comments"]) + P.info += "[c]
" + else + P.info += "Medical Record Lost!
" + P.info += "" + P.name = "paper - 'Medical Record: [active1.fields["name"]]'" + printing = FALSE + SStgui.update_uis(src) + +/** + * Sets a temporary message to display to the user + * + * Arguments: + * * text - Text to display, null/empty to clear the message from the UI + * * style - The style of the message: (color name), info, success, warning, danger, virus + */ +/obj/machinery/computer/med_data/proc/set_temp(text = "", style = "info", update_now = FALSE) + temp = list(text = text, style = style) + if(update_now) + SStgui.update_uis(src) /obj/machinery/computer/med_data/emp_act(severity) if(stat & (BROKEN|NOPOWER)) diff --git a/code/game/machinery/cryo.dm b/code/game/machinery/cryo.dm index 9db22b92702..7f2c21baeb7 100644 --- a/code/game/machinery/cryo.dm +++ b/code/game/machinery/cryo.dm @@ -82,29 +82,30 @@ if(occupant == user && !user.stat) go_out() +/obj/machinery/atmospherics/unary/cryo_cell/attack_ghost(mob/user) + tgui_interact(user) + /obj/machinery/atmospherics/unary/cryo_cell/attack_hand(mob/user) - ui_interact(user) - - /** - * The ui_interact proc is used to open and update Nano UIs - * If ui_interact is not used then the UI will not update correctly - * ui_interact is currently defined for /atom/movable (which is inherited by /obj and /mob) - * - * @param user /mob The mob who is interacting with this ui - * @param ui_key string A string key to use for this ui. Allows for multiple unique uis on one obj/mob (defaut value "main") - * @param ui /datum/nanoui This parameter is passed by the nanoui process() proc when updating an open ui - * - * @return nothing - */ -/obj/machinery/atmospherics/unary/cryo_cell/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - - if(user == occupant || user.stat) + if(user == occupant) return + if(panel_open) + to_chat(usr, "Close the maintenance panel first.") + return + + tgui_interact(user) + +/obj/machinery/atmospherics/unary/cryo_cell/tgui_interact(mob/user, datum/tgui/ui = null) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Cryo", "Cryo Cell") // 520, 470 + ui.open() + +/obj/machinery/atmospherics/unary/cryo_cell/tgui_data(mob/user) // this is the data which will be sent to the ui var/data[0] data["isOperating"] = on - data["hasOccupant"] = occupant ? 1 : 0 + data["hasOccupant"] = occupant ? TRUE : FALSE var/occupantData[0] if(occupant) @@ -127,14 +128,7 @@ else if(air_contents.temperature > 225) data["cellTemperatureStatus"] = "average" - data["isBeakerLoaded"] = beaker ? 1 : 0 - /* // Removing beaker contents list from front-end, replacing with a total remaining volume - var beakerContents[0] - if(beaker && beaker.reagents && beaker.reagents.reagent_list.len) - for(var/datum/reagent/R in beaker.reagents.reagent_list) - beakerContents.Add(list(list("name" = R.name, "volume" = R.volume))) // list in a list because Byond merges the first list... - data["beakerContents"] = beakerContents - */ + data["isBeakerLoaded"] = beaker ? TRUE : FALSE data["beakerLabel"] = null data["beakerVolume"] = 0 if(beaker) @@ -143,47 +137,33 @@ for(var/datum/reagent/R in beaker.reagents.reagent_list) data["beakerVolume"] += R.volume - // update the ui if it exists, returns null if no ui is passed/found - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open) - if(!ui) - // the ui does not exist, so we'll create a new() one - // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm - ui = new(user, src, ui_key, "cryo.tmpl", "Cryo Cell Control System", 520, 410) - // when the ui is first opened this is the data it will use - ui.set_initial_data(data) - // open the new ui window - ui.open() - // auto update every Master Controller tick - ui.set_auto_update(1) + return data -/obj/machinery/atmospherics/unary/cryo_cell/Topic(href, href_list) - if(usr == occupant) - return 0 // don't update UIs attached to this object +/obj/machinery/atmospherics/unary/cryo_cell/tgui_act(action, params) + if(..() || usr == occupant) + return - if(..()) - return 0 // don't update UIs attached to this object - - if(href_list["switchOn"]) - on = 1 - update_icon() - - if(href_list["switchOff"]) - on = 0 - update_icon() - - if(href_list["ejectBeaker"]) - if(beaker) - beaker.loc = get_step(src.loc, SOUTH) - beaker = null + . = TRUE + switch(action) + if("switchOn") + on = 1 update_icon() - - if(href_list["ejectOccupant"]) - if(!occupant || isslime(usr) || ispAI(usr)) - return 0 // don't update UIs attached to this object - go_out() + if("switchOff") + on = 0 + update_icon() + if("ejectBeaker") + if(beaker) + beaker.loc = get_step(src.loc, SOUTH) + beaker = null + update_icon() + if("ejectOccupant") + if(!occupant || isslime(usr) || ispAI(usr)) + return 0 // don't update UIs attached to this object + go_out() + else + return FALSE add_fingerprint(usr) - return 1 // update UIs attached to this object /obj/machinery/atmospherics/unary/cryo_cell/attackby(var/obj/item/weapon/G as obj, var/mob/user as mob) if(istype(G, /obj/item/weapon/reagent_containers/glass)) @@ -195,6 +175,7 @@ user.drop_item() G.loc = src user.visible_message("[user] adds \a [G] to \the [src]!", "You add \a [G] to \the [src]!") + SStgui.update_uis(src) update_icon() else if(istype(G, /obj/item/weapon/grab)) var/obj/item/weapon/grab/grab = G @@ -292,7 +273,9 @@ occupant = null current_heat_capacity = initial(current_heat_capacity) update_use_power(USE_POWER_IDLE) + SStgui.update_uis(src) return + /obj/machinery/atmospherics/unary/cryo_cell/proc/put_mob(mob/living/carbon/M as mob) if(stat & (NOPOWER|BROKEN)) to_chat(usr, "The cryo cell is not functioning.") @@ -326,6 +309,7 @@ // M.metabslow = 1 add_fingerprint(usr) update_icon() + SStgui.update_uis(src) return 1 /obj/machinery/atmospherics/unary/cryo_cell/verb/move_eject() diff --git a/code/modules/asset_cache/asset_list_items.dm b/code/modules/asset_cache/asset_list_items.dm index 2b90000ea0c..64b76184e5c 100644 --- a/code/modules/asset_cache/asset_list_items.dm +++ b/code/modules/asset_cache/asset_list_items.dm @@ -443,3 +443,48 @@ /datum/asset/nanoui/send(client) send_asset_list(client, common) + +//Pill sprites for UIs +/datum/asset/chem_master + var/assets = list() + var/verify = FALSE + +/datum/asset/chem_master/register() + for(var/i = 1 to 24) + assets["pill[i].png"] = icon('icons/obj/chemical.dmi', "pill[i]") + + for(var/i = 1 to 4) + assets["bottle-[i].png"] = icon('icons/obj/chemical.dmi', "bottle-[i]") + + for(var/asset_name in assets) + register_asset(asset_name, assets[asset_name]) + +/datum/asset/chem_master/send(client) + send_asset_list(client, assets, verify) + +//Cloning pod sprites for UIs +/datum/asset/cloning + var/assets = list() + var/verify = FALSE + +/datum/asset/cloning/register() + assets["pod_idle.gif"] = icon('icons/obj/cloning.dmi', "pod_idle") + assets["pod_cloning.gif"] = icon('icons/obj/cloning.dmi', "pod_cloning") + assets["pod_mess.gif"] = icon('icons/obj/cloning.dmi', "pod_mess") + for(var/asset_name in assets) + register_asset(asset_name, assets[asset_name]) + +/datum/asset/cloning/send(client) + send_asset_list(client, assets, verify) + +// VOREStation Add +/datum/asset/cloning/resleeving +/datum/asset/cloning/resleeving/register() + // This intentionally does not call the parent. Duplicate assets are not allowed. + assets["sleeve_empty.gif"] = icon('icons/obj/machines/implantchair.dmi', "implantchair") + assets["sleeve_occupied.gif"] = icon('icons/obj/machines/implantchair.dmi', "implantchair_on") + assets["synthprinter.gif"] = icon('icons/obj/machines/synthpod.dmi', "pod_0") + assets["synthprinter_working.gif"] = icon('icons/obj/machines/synthpod.dmi', "pod_1") + for(var/asset_name in assets) + register_asset(asset_name, assets[asset_name]) +// VOREStation Add End \ No newline at end of file diff --git a/code/modules/mob/say.dm b/code/modules/mob/say.dm index 7a20d0ee8e6..7a8f2412f2b 100644 --- a/code/modules/mob/say.dm +++ b/code/modules/mob/say.dm @@ -118,7 +118,7 @@ return get_turf(src) -/mob/proc/say_test(var/text) +/proc/say_test(var/text) var/ending = copytext(text, length(text)) if(ending == "?") return "1" diff --git a/code/modules/reagents/Chemistry-Holder.dm b/code/modules/reagents/Chemistry-Holder.dm index 33f666e6f38..11f6d01cdb9 100644 --- a/code/modules/reagents/Chemistry-Holder.dm +++ b/code/modules/reagents/Chemistry-Holder.dm @@ -140,6 +140,13 @@ crash_with("[my_atom] attempted to add a reagent called '[id]' which doesn't exist. ([usr])") return 0 +/datum/reagents/proc/isolate_reagent(reagent) + for(var/A in reagent_list) + var/datum/reagent/R = A + if(R.id != reagent) + del_reagent(R.id) + update_total() + /datum/reagents/proc/remove_reagent(var/id, var/amount, var/safety = 0) if(!isnum(amount)) return 0 diff --git a/code/modules/reagents/Chemistry-Machinery.dm b/code/modules/reagents/Chemistry-Machinery.dm index 4b9a892881a..517385757ba 100644 --- a/code/modules/reagents/Chemistry-Machinery.dm +++ b/code/modules/reagents/Chemistry-Machinery.dm @@ -2,6 +2,13 @@ #define LIQUID 2 #define GAS 3 +#define MAX_PILL_SPRITE 24 //max icon state of the pill sprites +#define MAX_BOTTLE_SPRITE 4 //max icon state of the pill sprites +#define MAX_MULTI_AMOUNT 20 // Max number of pills/patches that can be made at once +#define MAX_UNITS_PER_PILL 60 // Max amount of units in a pill +#define MAX_UNITS_PER_PATCH 60 // Max amount of units in a patch +#define MAX_CUSTOM_NAME_LEN 64 // Max length of a custom pill/condiment/whatever + @@ -24,11 +31,11 @@ var/condi = 0 var/useramount = 15 // Last used amount var/pillamount = 10 - var/bottlesprite = "1" - var/pillsprite = "1" + var/list/bottle_styles + var/bottlesprite = 1 + var/pillsprite = 1 var/max_pill_count = 20 - var/tab = "home" - var/analyze_data[0] + var/printing = FALSE flags = OPENCONTAINER clicksound = "button" @@ -48,6 +55,9 @@ qdel(src) return +/obj/machinery/chem_master/update_icon() + icon_state = "mixer[beaker ? "1" : "0"]" + /obj/machinery/chem_master/attackby(var/obj/item/weapon/B as obj, var/mob/user as mob) if(istype(B, /obj/item/weapon/reagent_containers/glass) || istype(B, /obj/item/weapon/reagent_containers/food)) @@ -59,7 +69,7 @@ user.drop_item() B.loc = src to_chat(user, "You add \the [B] to the machine.") - icon_state = "mixer1" + update_icon() else if(istype(B, /obj/item/weapon/storage/pill_bottle)) @@ -85,247 +95,395 @@ if(stat & BROKEN) return user.set_machine(src) - ui_interact(user) + tgui_interact(user) + +/obj/machinery/chem_master/ui_assets(mob/user) + return list( + get_asset_datum(/datum/asset/chem_master), + ) + +/obj/machinery/chem_master/tgui_interact(mob/user, datum/tgui/ui = null) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "ChemMaster", name) + ui.open() /** * Display the NanoUI window for the chem master. * * See NanoUI documentation for details. */ -/obj/machinery/chem_master/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - user.set_machine(src) - +/obj/machinery/chem_master/tgui_data(mob/user) var/list/data = list() - data["tab"] = tab + data["condi"] = condi + data["loaded_pill_bottle"] = !!loaded_pill_bottle if(loaded_pill_bottle) - data["pillBottle"] = list("total" = loaded_pill_bottle.contents.len, "max" = loaded_pill_bottle.max_storage_space) - else - data["pillBottle"] = null + data["loaded_pill_bottle_name"] = loaded_pill_bottle.name + data["loaded_pill_bottle_contents_len"] = loaded_pill_bottle.contents.len + data["loaded_pill_bottle_storage_slots"] = loaded_pill_bottle.max_storage_space + data["beaker"] = !!beaker if(beaker) - var/datum/reagents/R = beaker.reagents - var/ui_reagent_beaker_list[0] - for(var/datum/reagent/G in R.reagent_list) - ui_reagent_beaker_list[++ui_reagent_beaker_list.len] = list("name" = G.name, "volume" = G.volume, "description" = G.description, "id" = G.id) + var/list/beaker_reagents_list = list() + data["beaker_reagents"] = beaker_reagents_list + for(var/datum/reagent/R in beaker.reagents.reagent_list) + beaker_reagents_list[++beaker_reagents_list.len] = list("name" = R.name, "volume" = R.volume, "description" = R.description, "id" = R.id) - data["beaker"] = list("total_volume" = R.total_volume, "reagent_list" = ui_reagent_beaker_list) - else - data["beaker"] = null - - if(reagents.total_volume) - var/ui_reagent_list[0] - for(var/datum/reagent/N in reagents.reagent_list) - ui_reagent_list[++ui_reagent_list.len] = list("name" = N.name, "volume" = N.volume, "description" = N.description, "id" = N.id) - - data["reagents"] = list("total_volume" = reagents.total_volume, "reagent_list" = ui_reagent_list) - else - data["reagents"] = null + var/list/buffer_reagents_list = list() + data["buffer_reagents"] = buffer_reagents_list + for(var/datum/reagent/R in reagents.reagent_list) + buffer_reagents_list[++buffer_reagents_list.len] = list("name" = R.name, "volume" = R.volume, "id" = R.id, "description" = R.description) + data["pillsprite"] = pillsprite + data["bottlesprite"] = bottlesprite data["mode"] = mode + data["printing"] = printing - if(analyze_data) - data["analyzeData"] = list("name" = analyze_data["name"], "desc" = analyze_data["desc"], "blood_type" = analyze_data["blood_type"], "blood_DNA" = analyze_data["blood_DNA"]) - else - data["analyzeData"] = null + // Transfer modal information if there is one + data["modal"] = tgui_modal_data(src) - data["pillSprite"] = pillsprite - data["bottleSprite"] = bottlesprite + return data - var/P[24] //how many pill sprites there are. Sprites are taken from chemical.dmi and can be found in nano/images/pill.png - for(var/i = 1 to P.len) - P[i] = i - data["pillSpritesAmount"] = P +/** + * Called in tgui_act() to process modal actions + * + * Arguments: + * * action - The action passed by tgui + * * params - The params passed by tgui + */ +/obj/machinery/chem_master/proc/tgui_act_modal(action, params, datum/tgui/ui, datum/tgui_state/state) + . = TRUE + var/id = params["id"] // The modal's ID + var/list/arguments = istext(params["arguments"]) ? json_decode(params["arguments"]) : params["arguments"] + switch(tgui_modal_act(src, action, params)) + if(TGUI_MODAL_OPEN) + switch(id) + if("analyze") + var/idx = text2num(arguments["idx"]) || 0 + var/from_beaker = text2num(arguments["beaker"]) || FALSE + var/reagent_list = from_beaker ? beaker.reagents.reagent_list : reagents.reagent_list + if(idx < 1 || idx > length(reagent_list)) + return - data["bottleSpritesAmount"] = list(1, 2, 3, 4) //how many bottle sprites there are. Sprites are taken from chemical.dmi and can be found in nano/images/pill.png + var/datum/reagent/R = reagent_list[idx] + var/list/result = list("idx" = idx, "name" = R.name, "desc" = R.description) + if(!condi && istype(R, /datum/reagent/blood)) + var/datum/reagent/blood/B = R + result["blood_type"] = B.data["blood_type"] + result["blood_dna"] = B.data["blood_DNA"] - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open) - if (!ui) - ui = new(user, src, ui_key, "chem_master.tmpl", src.name, 575, 400) - ui.set_initial_data(data) - ui.open() - ui.set_auto_update(5) + arguments["analysis"] = result + tgui_modal_message(src, id, "", null, arguments) + // if("change_pill_bottle_style") + // if(!loaded_pill_bottle) + // return + // if(!pill_bottle_wrappers) + // pill_bottle_wrappers = list( + // "CLEAR" = "Default", + // COLOR_RED = "Red", + // COLOR_GREEN = "Green", + // COLOR_PALE_BTL_GREEN = "Pale green", + // COLOR_BLUE = "Blue", + // COLOR_CYAN_BLUE = "Light blue", + // COLOR_TEAL = "Teal", + // COLOR_YELLOW = "Yellow", + // COLOR_ORANGE = "Orange", + // COLOR_PINK = "Pink", + // COLOR_MAROON = "Brown" + // ) + // var/current = pill_bottle_wrappers[loaded_pill_bottle.wrapper_color] || "Default" + // tgui_modal_choice(src, id, "Please select a pill bottle wrapper:", null, arguments, current, pill_bottle_wrappers) + if("addcustom") + if(!beaker || !beaker.reagents.total_volume) + return + tgui_modal_input(src, id, "Please enter the amount to transfer to buffer:", null, arguments, useramount) + if("removecustom") + if(!reagents.total_volume) + return + tgui_modal_input(src, id, "Please enter the amount to transfer to [mode ? "beaker" : "disposal"]:", null, arguments, useramount) + if("create_condi_pack") + if(!condi || !reagents.total_volume) + return + tgui_modal_input(src, id, "Please name your new condiment pack:", null, arguments, reagents.get_master_reagent_name(), MAX_CUSTOM_NAME_LEN) + if("create_pill") + if(condi || !reagents.total_volume) + return + var/num = round(text2num(arguments["num"] || 1)) + if(!num) + return + arguments["num"] = num + var/amount_per_pill = CLAMP(reagents.total_volume / num, 0, MAX_UNITS_PER_PILL) + var/default_name = "[reagents.get_master_reagent_name()] ([amount_per_pill]u)" + var/pills_text = num == 1 ? "new pill" : "[num] new pills" + tgui_modal_input(src, id, "Please name your [pills_text]:", null, arguments, default_name, MAX_CUSTOM_NAME_LEN) + if("create_pill_multiple") + if(condi || !reagents.total_volume) + return + tgui_modal_input(src, id, "Please enter the amount of pills to make (max [MAX_MULTI_AMOUNT] at a time):", null, arguments, pillamount, 5) + if("change_pill_style") + var/list/choices = list() + for(var/i = 1 to MAX_PILL_SPRITE) + choices += "pill[i].png" + tgui_modal_bento(src, id, "Please select the new style for pills:", null, arguments, pillsprite, choices) + if("create_patch") + if(condi || !reagents.total_volume) + return + var/num = round(text2num(arguments["num"] || 1)) + if(!num) + return + arguments["num"] = num + var/amount_per_patch = CLAMP(reagents.total_volume / num, 0, MAX_UNITS_PER_PATCH) + var/default_name = "[reagents.get_master_reagent_name()] ([amount_per_patch]u)" + var/patches_text = num == 1 ? "new patch" : "[num] new patches" + tgui_modal_input(src, id, "Please name your [patches_text]:", null, arguments, default_name, MAX_CUSTOM_NAME_LEN) + if("create_patch_multiple") + if(condi || !reagents.total_volume) + return + tgui_modal_input(src, id, "Please enter the amount of patches to make (max [MAX_MULTI_AMOUNT] at a time):", null, arguments, pillamount, 5) + if("create_bottle") + if(condi || !reagents.total_volume) + return + tgui_modal_input(src, id, "Please name your bottle:", null, arguments, reagents.get_master_reagent_name(), MAX_CUSTOM_NAME_LEN) + if("change_bottle_style") + var/list/choices = list() + for(var/i = 1 to MAX_BOTTLE_SPRITE) + choices += "bottle-[i].png" + tgui_modal_bento(src, id, "Please select the new style for bottles:", null, arguments, bottlesprite, choices) + else + return FALSE + if(TGUI_MODAL_ANSWER) + var/answer = params["answer"] + switch(id) + // if("change_pill_bottle_style") + // if(!pill_bottle_wrappers || !loaded_pill_bottle) // wat? + // return + // var/color = "CLEAR" + // for(var/col in pill_bottle_wrappers) + // var/col_name = pill_bottle_wrappers[col] + // if(col_name == answer) + // color = col + // break + // if(length(color) && color != "CLEAR") + // loaded_pill_bottle.wrapper_color = color + // loaded_pill_bottle.apply_wrap() + // else + // loaded_pill_bottle.wrapper_color = null + // loaded_pill_bottle.cut_overlays() + if("addcustom") + var/amount = isgoodnumber(text2num(answer)) + if(!amount || !arguments["id"]) + return + tgui_act("add", list("id" = arguments["id"], "amount" = amount), ui, state) + if("removecustom") + var/amount = isgoodnumber(text2num(answer)) + if(!amount || !arguments["id"]) + return + tgui_act("remove", list("id" = arguments["id"], "amount" = amount), ui, state) + if("create_condi_pack") + if(!condi || !reagents.total_volume) + return + if(!length(answer)) + answer = reagents.get_master_reagent_name() + var/obj/item/weapon/reagent_containers/pill/P = new(loc) + P.name = "[answer] pack" + P.desc = "A small condiment pack. The label says it contains [answer]." + P.icon_state = "bouilloncube"//Reskinned monkey cube + reagents.trans_to_obj(P, 10) + if("create_pill") + if(condi || !reagents.total_volume) + return + var/count = CLAMP(round(text2num(arguments["num"]) || 0), 0, MAX_MULTI_AMOUNT) + if(!count) + return -/obj/machinery/chem_master/Topic(href, href_list) - if(stat & (BROKEN|NOPOWER)) return - if(usr.stat || usr.restrained()) return - if(!in_range(src, usr)) return + if(!length(answer)) + answer = reagents.get_master_reagent_name() + var/amount_per_pill = CLAMP(reagents.total_volume / count, 0, MAX_UNITS_PER_PILL) + while(count--) + if(reagents.total_volume <= 0) + to_chat(usr, "Not enough reagents to create these pills!") + return - src.add_fingerprint(usr) + var/obj/item/weapon/reagent_containers/pill/P = new(loc) + P.name = "[answer] pill" + P.pixel_x = rand(-7, 7) // Random position + P.pixel_y = rand(-7, 7) + P.icon_state = "pill[pillsprite]" + if(P.icon_state in list("pill1", "pill2", "pill3", "pill4")) // if using greyscale, take colour from reagent + P.color = reagents.get_color() + reagents.trans_to_obj(P, amount_per_pill) + // Load the pills in the bottle if there's one loaded + if(istype(loaded_pill_bottle) && length(loaded_pill_bottle.contents) < loaded_pill_bottle.max_storage_space) + P.forceMove(loaded_pill_bottle) + if("create_pill_multiple") + if(condi || !reagents.total_volume) + return + tgui_act("modal_open", list("id" = "create_pill", "arguments" = list("num" = answer)), ui, state) + if("change_pill_style") + var/new_style = CLAMP(text2num(answer) || 0, 0, MAX_PILL_SPRITE) + if(!new_style) + return + pillsprite = new_style + if("create_patch") + if(condi || !reagents.total_volume) + return + var/count = CLAMP(round(text2num(arguments["num"]) || 0), 0, MAX_MULTI_AMOUNT) + if(!count) + return + + if(!length(answer)) + answer = reagents.get_master_reagent_name() + var/amount_per_patch = CLAMP(reagents.total_volume / count, 0, MAX_UNITS_PER_PATCH) + // var/is_medical_patch = chemical_safety_check(reagents) + while(count--) + if(reagents.total_volume <= 0) + to_chat(usr, "Not enough reagents to create these patches!") + return + + var/obj/item/weapon/reagent_containers/pill/patch/P = new(loc) + P.name = "[answer] patch" + P.pixel_x = rand(-7, 7) // random position + P.pixel_y = rand(-7, 7) + reagents.trans_to_obj(P, amount_per_patch) + // if(is_medical_patch) + // P.instant_application = TRUE + // P.icon_state = "bandaid_med" + if("create_patch_multiple") + if(condi || !reagents.total_volume) + return + tgui_act("modal_open", list("id" = "create_patch", "arguments" = list("num" = answer)), ui, state) + if("create_bottle") + if(condi || !reagents.total_volume) + return + + if(!length(answer)) + answer = reagents.get_master_reagent_name() + var/obj/item/weapon/reagent_containers/glass/bottle/P = new(loc) + P.name = "[answer] bottle" + P.pixel_x = rand(-7, 7) // random position + P.pixel_y = rand(-7, 7) + P.icon_state = "bottle-[bottlesprite]" || "bottle-1" + reagents.trans_to_obj(P, 60) + P.update_icon() + if("change_bottle_style") + var/new_style = CLAMP(text2num(answer) || 0, 0, MAX_BOTTLE_SPRITE) + if(!new_style) + return + bottlesprite = new_style + else + return FALSE + else + return FALSE + +/obj/machinery/chem_master/tgui_act(action, params, datum/tgui/ui, datum/tgui_state/state) + if(..()) + return + + if(tgui_act_modal(action, params, ui, state)) + return TRUE + + add_fingerprint(usr) usr.set_machine(src) - if(href_list["tab_select"]) - tab = href_list["tab_select"] - - if (href_list["ejectp"]) - if(loaded_pill_bottle) - loaded_pill_bottle.forceMove(get_turf(src)) - - if(Adjacent(usr)) - usr.put_in_hands(loaded_pill_bottle) - - loaded_pill_bottle = null - - if(beaker) - var/datum/reagents/R = beaker.reagents - if (tab == "analyze") - analyze_data["name"] = href_list["name"] - analyze_data["desc"] = href_list["desc"] - if(!condi) - if(href_list["name"] == "Blood") - var/datum/reagent/blood/G - for(var/datum/reagent/F in R.reagent_list) - if(F.name == href_list["name"]) - G = F - break - analyze_data["name"] = G.name - analyze_data["blood_type"] = G.data["blood_type"] - analyze_data["blood_DNA"] = G.data["blood_DNA"] - - else if (href_list["add"]) - - if(href_list["amount"]) - var/id = href_list["add"] - var/amount = CLAMP((text2num(href_list["amount"])), 0, 200) - R.trans_id_to(src, id, amount) - - else if (href_list["addcustom"]) - - var/id = href_list["addcustom"] - useramount = input("Select the amount to transfer.", 30, useramount) as num - useramount = CLAMP(useramount, 0, 200) - src.Topic(null, list("amount" = "[useramount]", "add" = "[id]")) - - else if (href_list["remove"]) - - if(href_list["amount"]) - var/id = href_list["remove"] - var/amount = CLAMP((text2num(href_list["amount"])), 0, 200) - if(mode) - reagents.trans_id_to(beaker, id, amount) - else - reagents.remove_reagent(id, amount) - - - else if (href_list["removecustom"]) - - var/id = href_list["removecustom"] - useramount = input("Select the amount to transfer.", 30, useramount) as num - useramount = CLAMP(useramount, 0, 200) - src.Topic(null, list("amount" = "[useramount]", "remove" = "[id]")) - - else if (href_list["toggle"]) + . = TRUE + switch(action) + if("toggle") mode = !mode - - else if (href_list["eject"]) - if(beaker) - beaker.forceMove(get_turf(src)) - - if(Adjacent(usr)) // So the AI doesn't get a beaker somehow. - usr.put_in_hands(beaker) - - beaker = null - reagents.clear_reagents() - icon_state = "mixer0" - else if (href_list["createpill"] || href_list["createpill_multiple"]) - var/count = 1 - - if(reagents.total_volume/count < 1) //Sanity checking. + if("ejectp") + if(loaded_pill_bottle) + loaded_pill_bottle.forceMove(loc) + loaded_pill_bottle = null + if("print") + if(printing || condi) return - if (href_list["createpill_multiple"]) - count = input("Select the number of pills to make.", "Max [max_pill_count]", pillamount) as null|num - if(!count) //Covers 0 and cancel - return - count = CLAMP(round(count), 1, max_pill_count) // Fix decimals input and clamp to reasonable amounts - - if(reagents.total_volume/count < 1) //Sanity checking. + var/idx = text2num(params["idx"]) || 0 + var/from_beaker = text2num(params["beaker"]) || FALSE + var/reagent_list = from_beaker ? beaker.reagents.reagent_list : reagents.reagent_list + if(idx < 1 || idx > length(reagent_list)) return - var/amount_per_pill = reagents.total_volume/count - if (amount_per_pill > 60) amount_per_pill = 60 + var/datum/reagent/R = reagent_list[idx] - var/pill_cube = "pill" - if(condi)//For the condimaster - pill_cube = "cube" + printing = TRUE + visible_message("[src] rattles and prints out a sheet of paper.") + // playsound(loc, 'sound/goonstation/machines/printer_dotmatrix.ogg', 50, 1) + + var/obj/item/weapon/paper/P = new /obj/item/weapon/paper(loc) + P.info = "
Chemical Analysis

" + P.info += "Time of analysis: [worldtime2stationtime(world.time)]

" + P.info += "Chemical name: [R.name]
" + if(istype(R, /datum/reagent/blood)) + var/datum/reagent/blood/B = R + P.info += "Description: N/A
Blood Type: [B.data["blood_type"]]
DNA: [B.data["blood_DNA"]]" else - pill_cube = "pill" + P.info += "Description: [R.description]" + P.info += "

Notes:
" + P.name = "Chemical Analysis - [R.name]" + spawn(50) + printing = FALSE + else + . = FALSE - var/name = sanitizeSafe(input(usr,"Name:","Name your [pill_cube]!","[reagents.get_master_reagent_name()] ([amount_per_pill]u)") as null|text, MAX_NAME_LEN) + if(. || !beaker) + return - if(!name) //Blank name (sanitized to nothing, or left empty) or cancel + . = TRUE + var/datum/reagents/R = beaker.reagents + switch(action) + if("add") + var/id = params["id"] + var/amount = text2num(params["amount"]) + if(!id || !amount) return - - - if(reagents.total_volume/count < 1) //Sanity checking. + R.trans_id_to(src, id, amount) + if("remove") + var/id = params["id"] + var/amount = text2num(params["amount"]) + if(!id || !amount) return - while(count-- > 0) // Will definitely eventually stop. - var/obj/item/weapon/reagent_containers/pill/P = new/obj/item/weapon/reagent_containers/pill(src.loc) - if(!name) name = reagents.get_master_reagent_name() - P.name = "[name] pill" - P.pixel_x = rand(-7, 7) //random position - P.pixel_y = rand(-7, 7) - if(!condi) //If normal - P.icon_state = "pill"+pillsprite - else //If condi is on - P.icon_state = "bouilloncube"//Reskinned monkey cube - P.desc = "A dissolvable cube." - - if(P.icon_state in list("pill1", "pill2", "pill3", "pill4")) // if using greyscale, take colour from reagent - P.color = reagents.get_color() - - reagents.trans_to_obj(P,amount_per_pill) - if(src.loaded_pill_bottle) - if(loaded_pill_bottle.contents.len < loaded_pill_bottle.max_storage_space) - P.loc = loaded_pill_bottle - - else if (href_list["createbottle"]) - if(!condi) - var/name = sanitizeSafe(input(usr,"Name:","Name your bottle!",reagents.get_master_reagent_name()), MAX_NAME_LEN) - var/obj/item/weapon/reagent_containers/glass/bottle/P = new/obj/item/weapon/reagent_containers/glass/bottle(src.loc) - if(!name) name = reagents.get_master_reagent_name() - P.name = "[name] bottle" - P.pixel_x = rand(-7, 7) //random position - P.pixel_y = rand(-7, 7) - P.icon_state = "bottle-"+bottlesprite - reagents.trans_to_obj(P,60) - P.update_icon() + if(mode) + reagents.trans_id_to(beaker, id, amount) else - var/obj/item/weapon/reagent_containers/food/condiment/P = new/obj/item/weapon/reagent_containers/food/condiment(src.loc) - reagents.trans_to_obj(P,50) - - else if (href_list["createpatch"]) - if(reagents.total_volume < 1) //Sanity checking. + reagents.remove_reagent(id, amount) + if("eject") + if(!beaker) return - - var/name = sanitizeSafe(input(usr,"Name:","Name your patch!","[reagents.get_master_reagent_name()] ([round(reagents.total_volume)]u)") as null|text, MAX_NAME_LEN) - - if(!name) //Blank name (sanitized to nothing, or left empty) or cancel + beaker.forceMove(get_turf(src)) + if(Adjacent(usr) && !issilicon(usr)) + usr.put_in_hands(beaker) + beaker = null + reagents.clear_reagents() + update_icon() + if("create_condi_bottle") + if(!condi || !reagents.total_volume) return + var/obj/item/weapon/reagent_containers/food/condiment/P = new(loc) + reagents.trans_to_obj(P, 50) + else + return FALSE - if(reagents.total_volume < 1) //Sanity checking. - return - var/obj/item/weapon/reagent_containers/pill/patch/P = new/obj/item/weapon/reagent_containers/pill/patch(src.loc) - if(!name) name = reagents.get_master_reagent_name() - P.name = "[name] patch" - P.pixel_x = rand(-7, 7) //random position - P.pixel_y = rand(-7, 7) +/obj/machinery/chem_master/attack_ai(mob/user) + return attack_hand(user) - reagents.trans_to_obj(P, 60) - if(src.loaded_pill_bottle) - if(loaded_pill_bottle.contents.len < loaded_pill_bottle.max_storage_space) - P.loc = loaded_pill_bottle +/obj/machinery/chem_master/proc/isgoodnumber(num) + if(isnum(num)) + if(num > 200) + num = 200 + else if(num < 0) + num = 1 + return num + else + return FALSE - else if(href_list["pill_sprite"]) - pillsprite = href_list["pill_sprite"] - else if(href_list["bottle_sprite"]) - bottlesprite = href_list["bottle_sprite"] - - SSnanoui.update_uis(src) - -/obj/machinery/chem_master/attack_ai(mob/user as mob) - return src.attack_hand(user) +// /obj/machinery/chem_master/proc/chemical_safety_check(datum/reagents/R) +// var/all_safe = TRUE +// for(var/datum/reagent/A in R.reagent_list) +// if(!GLOB.safe_chem_list.Find(A.id)) +// all_safe = FALSE +// return all_safe /obj/machinery/chem_master/condimaster name = "CondiMaster 3000" @@ -365,11 +523,41 @@ /obj/item/stack/material/glass/phoronglass = list("platinum", "silicon", "silicon", "silicon"), //5 platinum, 15 silicon, ) + var/static/radial_examine = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_examine") + var/static/radial_eject = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_eject") + var/static/radial_grind = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_grind") + // var/static/radial_juice = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_juice") + // var/static/radial_mix = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_mix") + /obj/machinery/reagentgrinder/Initialize() . = ..() beaker = new /obj/item/weapon/reagent_containers/glass/beaker/large(src) default_apply_parts() +/obj/machinery/reagentgrinder/examine(mob/user) + . = ..() + if(!in_range(user, src) && !issilicon(user) && !isobserver(user)) + . += "You're too far away to examine [src]'s contents and display!" + return + + if(inuse) + . += "\The [src] is operating." + return + + if(beaker || length(holdingitems)) + . += "\The [src] contains:" + if(beaker) + . += "- \A [beaker]." + for(var/i in holdingitems) + var/obj/item/O = i + . += "- \A [O.name]." + + if(!(stat & (NOPOWER|BROKEN))) + . += "The status display reads:\n" + if(beaker) + for(var/datum/reagent/R in beaker.reagents.reagent_list) + . += "- [R.volume] units of [R.name]." + /obj/machinery/reagentgrinder/update_icon() icon_state = "juicer"+num2text(!isnull(beaker)) return @@ -450,93 +638,66 @@ user.remove_from_mob(O) O.loc = src holdingitems += O - src.updateUsrDialog() return 0 +/obj/machinery/reagentgrinder/AltClick(mob/user) + . = ..() + if(user.incapacitated() || !Adjacent(user)) + return + replace_beaker(user) + /obj/machinery/reagentgrinder/attack_hand(mob/user as mob) - user.set_machine(src) interact(user) -/obj/machinery/reagentgrinder/interact(mob/user as mob) // The microwave Menu - var/is_chamber_empty = 0 - var/is_beaker_ready = 0 - var/processing_chamber = "" - var/beaker_contents = "" - var/dat = "" +/obj/machinery/reagentgrinder/interact(mob/user as mob) // The microwave Menu //I am reasonably certain that this is not a microwave + if(inuse || user.incapacitated()) + return - if(!inuse) - for (var/obj/item/O in holdingitems) - processing_chamber += "\A [O.name]
" + var/list/options = list() - if (!processing_chamber) - is_chamber_empty = 1 - processing_chamber = "Nothing." - if (!beaker) - beaker_contents = "No beaker attached.
" - else - is_beaker_ready = 1 - beaker_contents = "The beaker contains:
" - var/anything = 0 - for(var/datum/reagent/R in beaker.reagents.reagent_list) - anything = 1 - beaker_contents += "[R.volume] - [R.name]
" - if(!anything) - beaker_contents += "Nothing
" + if(beaker || length(holdingitems)) + options["eject"] = radial_eject + if(isAI(user)) + if(stat & NOPOWER) + return + options["examine"] = radial_examine - dat = {" - Processing chamber contains:
- [processing_chamber]
- [beaker_contents]
- "} - if (is_beaker_ready && !is_chamber_empty && !(stat & (NOPOWER|BROKEN))) - dat += "Process the reagents
" - if(holdingitems && holdingitems.len > 0) - dat += "Eject the reagents
" - if (beaker) - dat += "Detach the beaker
" + // if there is no power or it's broken, the procs will fail but the buttons will still show + if(length(holdingitems)) + options["grind"] = radial_grind + + var/choice + + if(length(options) < 1) + return + if(length(options) == 1) + for(var/key in options) + choice = key else - dat += "Please wait..." - user << browse("All-In-One Grinder[dat]", "window=reagentgrinder") - onclose(user, "reagentgrinder") - return + choice = show_radial_menu(user, src, options, require_near = !issilicon(user)) - -/obj/machinery/reagentgrinder/Topic(href, href_list) - if(..()) + // post choice verification + if(inuse || (isAI(user) && stat & NOPOWER) || user.incapacitated()) return - usr.set_machine(src) - switch(href_list["action"]) - if ("grind") - grind() + + switch(choice) if("eject") - eject() - if ("detach") - detach() - src.updateUsrDialog() - return + eject(user) + if("grind") + grind(user) + if("examine") + examine(user) -/obj/machinery/reagentgrinder/proc/detach() - - if (usr.stat != 0) +/obj/machinery/reagentgrinder/proc/eject(mob/user) + if(user.incapacitated()) return - if (!beaker) - return - beaker.loc = src.loc - beaker = null - update_icon() - -/obj/machinery/reagentgrinder/proc/eject() - - if (usr.stat != 0) - return - if (!holdingitems || holdingitems.len == 0) - return - for(var/obj/item/O in holdingitems) O.loc = src.loc holdingitems -= O holdingitems.Cut() + if(beaker) + replace_beaker(user) /obj/machinery/reagentgrinder/proc/grind() @@ -554,7 +715,6 @@ // Reset the machine. spawn(60) inuse = 0 - interact(usr) // Process. for (var/obj/item/O in holdingitems) @@ -581,13 +741,26 @@ continue if(O.reagents) - O.reagents.trans_to(beaker, min(O.reagents.total_volume, remaining_volume)) + O.reagents.trans_to_obj(beaker, min(O.reagents.total_volume, remaining_volume)) if(O.reagents.total_volume == 0) holdingitems -= O qdel(O) if (beaker.reagents.total_volume >= beaker.reagents.maximum_volume) break +/obj/machinery/reagentgrinder/proc/replace_beaker(mob/living/user, obj/item/weapon/reagent_containers/new_beaker) + if(!user) + return FALSE + if(beaker) + if(!user.incapacitated() && Adjacent(user)) + user.put_in_hands(beaker) + else + beaker.forceMove(drop_location()) + beaker = null + if(new_beaker) + beaker = new_beaker + update_icon() + return TRUE /////////////// /////////////// @@ -653,4 +826,11 @@ to_chat(user, span("notice", "Scanning of \the [I] complete.")) analyzing = FALSE update_icon() - return \ No newline at end of file + return + +#undef MAX_PILL_SPRITE +#undef MAX_BOTTLE_SPRITE +#undef MAX_MULTI_AMOUNT +#undef MAX_UNITS_PER_PILL +#undef MAX_UNITS_PER_PATCH +#undef MAX_CUSTOM_NAME_LEN \ No newline at end of file diff --git a/code/modules/reagents/dispenser/dispenser2.dm b/code/modules/reagents/dispenser/dispenser2.dm index 9663c685296..1fdf6a24afe 100644 --- a/code/modules/reagents/dispenser/dispenser2.dm +++ b/code/modules/reagents/dispenser/dispenser2.dm @@ -68,12 +68,12 @@ C.loc = src cartridges[C.label] = C cartridges = sortAssoc(cartridges) - SSnanoui.update_uis(src) + SStgui.update_uis(src) /obj/machinery/chemical_dispenser/proc/remove_cartridge(label) . = cartridges[label] cartridges -= label - SSnanoui.update_uis(src) + SStgui.update_uis(src) /obj/machinery/chemical_dispenser/attackby(obj/item/weapon/W, mob/user) if(W.is_wrench()) @@ -119,25 +119,26 @@ user.drop_from_inventory(RC) RC.loc = src to_chat(user, "You set \the [RC] on \the [src].") - SSnanoui.update_uis(src) // update all UIs attached to src - else return ..() -/obj/machinery/chemical_dispenser/ui_interact(mob/user, ui_key = "main",var/datum/nanoui/ui = null, var/force_open = 1) - if(stat & (BROKEN|NOPOWER)) return - if(user.stat || user.restrained()) return +/obj/machinery/chemical_dispenser/tgui_interact(mob/user, datum/tgui/ui = null) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "ChemDispenser", ui_title) // 390, 655 + ui.open() - // this is the data which will be sent to the ui +/obj/machinery/chemical_dispenser/tgui_data(mob/user) var/data[0] data["amount"] = amount data["isBeakerLoaded"] = container ? 1 : 0 data["glass"] = accept_drinking - var beakerD[0] + + var/beakerContents[0] if(container && container.reagents && container.reagents.reagent_list.len) for(var/datum/reagent/R in container.reagents.reagent_list) - beakerD[++beakerD.len] = list("name" = R.name, "volume" = R.volume) - data["beakerContents"] = beakerD + beakerContents.Add(list(list("name" = R.name, "id" = R.id, "volume" = R.volume))) // list in a list because Byond merges the first list... + data["beakerContents"] = beakerContents if(container) data["beakerCurrentVolume"] = container.reagents.total_volume @@ -146,50 +147,59 @@ data["beakerCurrentVolume"] = null data["beakerMaxVolume"] = null - var chemicals[0] + var/chemicals[0] for(var/label in cartridges) var/obj/item/weapon/reagent_containers/chem_disp_cartridge/C = cartridges[label] - chemicals[++chemicals.len] = list("label" = label, "amount" = C.reagents.total_volume) + chemicals.Add(list(list("title" = label, "id" = label, "amount" = C.reagents.total_volume))) // list in a list because Byond merges the first list... data["chemicals"] = chemicals + return data - // update the ui if it exists, returns null if no ui is passed/found - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open) - if(!ui) - ui = new(user, src, ui_key, "chem_disp.tmpl", ui_title, 390, 680) - ui.set_initial_data(data) - ui.open() +/obj/machinery/chemical_dispenser/tgui_act(action, params) + if(..()) + return -/obj/machinery/chemical_dispenser/Topic(href, href_list) - if(stat & (NOPOWER|BROKEN)) - return 0 // don't update UIs attached to this object + . = TRUE + switch(action) + if("amount") + amount = clamp(round(text2num(params["amount"]), 1), 0, 120) // round to nearest 1 and clamp 0 - 120 + if("dispense") + var/label = params["reagent"] + if(cartridges[label] && container && container.is_open_container()) + var/obj/item/weapon/reagent_containers/chem_disp_cartridge/C = cartridges[label] + playsound(src, 'sound/machines/reagent_dispense.ogg', 25, 1) + C.reagents.trans_to(container, amount) + if("remove") + var/amount = text2num(params["amount"]) + if(!container || !amount) + return + var/datum/reagents/R = container.reagents + var/id = params["reagent"] + if(amount > 0) + R.remove_reagent(id, amount) + else if(amount == -1) // Isolate + R.isolate_reagent(id) + if("ejectBeaker") + if(container) + container.forceMove(get_turf(src)) - if(href_list["amount"]) - amount = round(text2num(href_list["amount"]), 1) // round to nearest 1 - amount = max(0, min(120, amount)) // Since the user can actually type the commands himself, some sanity checking + if(Adjacent(usr)) // So the AI doesn't get a beaker somehow. + usr.put_in_hands(container) - else if(href_list["dispense"]) - var/label = href_list["dispense"] - if(cartridges[label] && container && container.is_open_container()) - var/obj/item/weapon/reagent_containers/chem_disp_cartridge/C = cartridges[label] - playsound(src, 'sound/machines/reagent_dispense.ogg', 25, 1) - C.reagents.trans_to(container, amount) - - else if(href_list["ejectBeaker"]) - if(container) - container.forceMove(get_turf(src)) - - if(Adjacent(usr)) // So the AI doesn't get a beaker somehow. - usr.put_in_hands(container) - - container = null + container = null + else + return FALSE add_fingerprint(usr) - return 1 // update UIs attached to this object -/obj/machinery/chemical_dispenser/attack_ai(mob/user as mob) - src.attack_hand(user) - -/obj/machinery/chemical_dispenser/attack_hand(mob/user as mob) +/obj/machinery/chemical_dispenser/attack_ghost(mob/user) if(stat & BROKEN) return - ui_interact(user) + tgui_interact(user) + +/obj/machinery/chemical_dispenser/attack_ai(mob/user) + attack_hand(user) + +/obj/machinery/chemical_dispenser/attack_hand(mob/user) + if(stat & BROKEN) + return + tgui_interact(user) diff --git a/code/modules/resleeving/computers.dm b/code/modules/resleeving/computers.dm index 535c18c7a6b..0a7600acf04 100644 --- a/code/modules/resleeving/computers.dm +++ b/code/modules/resleeving/computers.dm @@ -1,3 +1,7 @@ +#define MENU_MAIN 1 +#define MENU_BODY 2 +#define MENU_MIND 3 + /obj/machinery/computer/transhuman/resleeving name = "resleeving control console" catalogue_data = list(/datum/category_item/catalogue/information/organization/khi, @@ -7,19 +11,25 @@ light_color = "#315ab4" circuit = /obj/item/weapon/circuitboard/resleeving_control req_access = list(access_heads) //Only used for record deletion right now. - var/list/pods = list() //Linked grower pods. - var/list/spods = list() - var/list/sleevers = list() //Linked resleeving booths. - var/temp = "" - var/menu = 1 //Which menu screen to display + var/list/pods = null //Linked grower pods. + var/list/spods = null + var/list/sleevers = null //Linked resleeving booths. + var/list/temp = null + var/menu = MENU_MAIN //Which menu screen to display var/datum/transhuman/body_record/active_br = null var/datum/transhuman/mind_record/active_mr = null var/organic_capable = 1 var/synthetic_capable = 1 var/obj/item/weapon/disk/transcore/disk + var/obj/machinery/clonepod/transhuman/selected_pod + var/obj/machinery/transhuman/synthprinter/selected_printer + var/obj/machinery/transhuman/resleever/selected_sleever /obj/machinery/computer/transhuman/resleeving/Initialize() . = ..() + pods = list() + spods = list() + sleevers = list() updatemodules() /obj/machinery/computer/transhuman/resleeving/Destroy() @@ -85,7 +95,6 @@ user.unEquip(W) W.forceMove(get_turf(src)) // Drop on top of us active_br = new /datum/transhuman/body_record(brDisk.stored) // Loads a COPY! - menu = 4 to_chat(user, "\The [src] loads the body record from \the [W] before ejecting it.") attack_hand(user) else @@ -103,282 +112,357 @@ return updatemodules() + tgui_interact(user) - ui_interact(user) +/obj/machinery/computer/transhuman/resleeving/ui_assets(mob/user) + return list( + get_asset_datum(/datum/asset/cloning), + get_asset_datum(/datum/asset/cloning/resleeving), + ) -/obj/machinery/computer/transhuman/resleeving/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - user.set_machine(src) +/obj/machinery/computer/transhuman/resleeving/tgui_interact(mob/user, datum/tgui/ui = null) + if(stat & (NOPOWER|BROKEN)) + return + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "ResleevingConsole", "Resleeving Console") + ui.open() + +/obj/machinery/computer/transhuman/resleeving/tgui_data(mob/user) var/data[0] + data["menu"] = menu + + var/list/temppods[0] + for(var/obj/machinery/clonepod/transhuman/pod in pods) + var/status = "idle" + if(pod.mess) + status = "mess" + else if(pod.occupant && !(pod.stat & NOPOWER)) + status = "cloning" + temppods.Add(list(list( + "pod" = "\ref[pod]", + "name" = sanitize(capitalize(pod.name)), + "biomass" = pod.get_biomass(), + "status" = status, + "progress" = (pod.occupant && pod.occupant.stat != DEAD) ? pod.get_completion() : 0 + ))) + data["pods"] = temppods.Copy() + temppods.Cut() + for(var/obj/machinery/transhuman/synthprinter/spod in spods) + temppods.Add(list(list( + "spod" = "\ref[spod]", + "name" = sanitize(capitalize(spod.name)), + "busy" = spod.busy, + "steel" = spod.stored_material[DEFAULT_WALL_MATERIAL], + "glass" = spod.stored_material["glass"] + ))) + data["spods"] = temppods.Copy() + temppods.Cut() + + for(var/obj/machinery/transhuman/resleever/resleever in sleevers) + temppods.Add(list(list( + "sleever" = "\ref[resleever]", + "name" = sanitize(capitalize(resleever.name)), + "occupied" = !!resleever.occupant, + "occupant" = resleever.occupant ? resleever.occupant.real_name : "None" + ))) + data["sleevers"] = temppods.Copy() + temppods.Cut() + + data["coredumped"] = SStranscore.core_dumped + data["emergency"] = disk + data["temp"] = temp + data["selected_pod"] = "\ref[selected_pod]" + data["selected_printer"] = "\ref[selected_printer]" + data["selected_sleever"] = "\ref[selected_sleever]" + var/bodyrecords_list_ui[0] for(var/N in SStranscore.body_scans) var/datum/transhuman/body_record/BR = SStranscore.body_scans[N] bodyrecords_list_ui[++bodyrecords_list_ui.len] = list("name" = N, "recref" = "\ref[BR]") + data["bodyrecords"] = bodyrecords_list_ui var/mindrecords_list_ui[0] for(var/N in SStranscore.backed_up) var/datum/transhuman/mind_record/MR = SStranscore.backed_up[N] mindrecords_list_ui[++mindrecords_list_ui.len] = list("name" = N, "recref" = "\ref[MR]") + data["mindrecords"] = mindrecords_list_ui - var/pods_list_ui[0] - for(var/obj/machinery/clonepod/transhuman/pod in pods) - pods_list_ui[++pods_list_ui.len] = list("pod" = pod, "biomass" = pod.get_biomass()) + data["modal"] = tgui_modal_data(src) + return data - var/spods_list_ui[0] - for(var/obj/machinery/transhuman/synthprinter/spod in spods) - spods_list_ui[++spods_list_ui.len] = list("spod" = spod, "steel" = spod.stored_material[DEFAULT_WALL_MATERIAL], "glass" = spod.stored_material["glass"]) - - var/sleevers_list_ui[0] - for(var/obj/machinery/transhuman/resleever/resleever in sleevers) - sleevers_list_ui[++sleevers_list_ui.len] = list("sleever" = resleever, "occupant" = resleever.occupant ? resleever.occupant.real_name : "None") - - if(pods) - data["pods"] = pods_list_ui - else - data["pods"] = null - - if(spods) - data["spods"] = spods_list_ui - else - data["spods"] = null - - if(sleevers) - data["sleevers"] = sleevers_list_ui - else - data["pods"] = null - - if(bodyrecords_list_ui.len) - data["bodyrecords"] = bodyrecords_list_ui - else - data["bodyrecords"] = null - - if(mindrecords_list_ui.len) - data["mindrecords"] = mindrecords_list_ui - else - data["mindrecords"] = null - - - if(active_br) - var/can_grow_active = 1 - if(!synthetic_capable && active_br.synthetic) //Disqualified due to being synthetic in an organic only. - can_grow_active = 0 - else if(!organic_capable && !active_br.synthetic) //Disqualified for the opposite. - can_grow_active = 0 - else if(!synthetic_capable && !organic_capable) //What have you done?? - can_grow_active = 0 - else if(active_br.toocomplex) - can_grow_active = 0 - - data["activeBodyRecord"] = list("real_name" = active_br.mydna.name, \ - "speciesname" = active_br.speciesname ? active_br.speciesname : active_br.mydna.dna.species, \ - "gender" = active_br.bodygender, \ - "synthetic" = active_br.synthetic ? "Yes" : "No", \ - "locked" = active_br.locked ? "Low" : "High", \ - "cando" = can_grow_active, - "booc" = active_br.body_oocnotes) - else - data["activeRecord"] = null - - if(active_mr) - var/can_sleeve_current = 1 - if(!sleevers.len) - can_sleeve_current = 0 - data["activeMindRecord"] = list("charname" = active_mr.mindname, \ - "obviously_dead" = active_mr.dead_state == MR_DEAD ? "Past-due" : "Current", \ - "cando" = can_sleeve_current, - "mooc" = active_mr.mind_oocnotes) - else - data["activeMindRecord"] = null - - - data["menu"] = menu - data["podsLen"] = pods.len - data["spodsLen"] = spods.len - data["sleeversLen"] = sleevers.len - data["temp"] = temp - data["coredumped"] = SStranscore.core_dumped - data["emergency"] = disk ? 1 : 0 - - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open) - if (!ui) - ui = new(user, src, ui_key, "sleever.tmpl", "Resleeving Control Console", 400, 450) - ui.set_initial_data(data) - ui.open() - ui.set_auto_update(5) - -/obj/machinery/computer/transhuman/resleeving/Topic(href, href_list) +/obj/machinery/computer/transhuman/resleeving/tgui_act(action, params) if(..()) - return 1 + return - else if (href_list["view_brec"]) - active_br = locate(href_list["view_brec"]) - if(active_br && istype(active_br.mydna)) - menu = 4 - else - active_br = null - temp = "ERROR: Record missing." + . = TRUE + switch(tgui_modal_act(src, action, params)) + if(TGUI_MODAL_ANSWER) + // if(params["id"] == "del_rec" && active_record) + // var/obj/item/weapon/card/id/C = usr.get_active_hand() + // if(!istype(C) && !istype(C, /obj/item/device/pda)) + // set_temp("ID not in hand.", "bad") + // return + // if(check_access(C)) + // records.Remove(active_record) + // qdel(active_record) + // set_temp("Record deleted.", "success") + // menu = MENU_RECORDS + // else + // set_temp("Access denied.", "bad") + return - else if (href_list["view_mrec"]) - active_mr = locate(href_list["view_mrec"]) - if(active_mr && istype(active_mr)) - menu = 5 - else - active_mr = null - temp = "ERROR: Record missing." - - else if (href_list["boocnotes"]) - menu = 6 - - else if (href_list["moocnotes"]) - menu = 7 - - else if (href_list["refresh"]) - updateUsrDialog() - - else if (href_list["coredump"]) - if(disk) - SStranscore.core_dump(disk) - sleep(5) - visible_message("\The [src] spits out \the [disk].") + switch(action) + if("view_b_rec") + var/ref = params["ref"] + if(!length(ref)) + return + active_br = locate(ref) + if(istype(active_br)) + if(isnull(active_br.ckey)) + qdel(active_br) + set_temp("Error: Record corrupt.", "bad") + else + var/can_grow_active = 1 + if(!synthetic_capable && active_br.synthetic) //Disqualified due to being synthetic in an organic only. + can_grow_active = 0 + set_temp("Error: Cannot grow [active_br.mydna.name] due to lack of synthfabs.", "bad") + else if(!organic_capable && !active_br.synthetic) //Disqualified for the opposite. + can_grow_active = 0 + set_temp("Error: Cannot grow [active_br.mydna.name] due to lack of cloners.", "bad") + else if(!synthetic_capable && !organic_capable) //What have you done?? + can_grow_active = 0 + set_temp("Error: Cannot grow [active_br.mydna.name] due to lack of synthfabs and cloners.", "bad") + else if(active_br.toocomplex) + can_grow_active = 0 + set_temp("Error: Cannot grow [active_br.mydna.name] due to species complexity.", "bad") + var/list/payload = list( + activerecord = "\ref[active_br]", + realname = sanitize(active_br.mydna.name), + species = active_br.speciesname ? active_br.speciesname : active_br.mydna.dna.species, + sex = active_br.bodygender, + mind_compat = active_br.locked ? "Low" : "High", + synthetic = active_br.synthetic, + oocnotes = active_br.body_oocnotes ? active_br.body_oocnotes : "None", + can_grow_active = can_grow_active, + ) + tgui_modal_message(src, action, "", null, payload) + else + active_br = null + set_temp("Error: Record missing.", "bad") + if("view_m_rec") + var/ref = params["ref"] + if(!length(ref)) + return + active_mr = locate(ref) + if(istype(active_mr)) + if(isnull(active_mr.ckey)) + qdel(active_mr) + set_temp("Error: Record corrupt.", "bad") + else + var/can_sleeve_active = 1 + if(!LAZYLEN(sleevers)) + can_sleeve_active = 0 + set_temp("Error: Cannot sleeve due to no sleevers.", "bad") + if(!selected_sleever) + can_sleeve_active = 0 + set_temp("Error: Cannot sleeve due to no selected sleever.", "bad") + if(selected_sleever && !selected_sleever.occupant) + can_sleeve_active = 0 + set_temp("Error: Cannot sleeve due to lack of sleever occupant.", "bad") + var/list/payload = list( + activerecord = "\ref[active_mr]", + realname = sanitize(active_mr.mindname), + obviously_dead = active_mr.dead_state == MR_DEAD ? "Past-due" : "Current", + oocnotes = active_mr.mind_oocnotes ? active_mr.mind_oocnotes : "None.", + can_sleeve_active = can_sleeve_active, + ) + tgui_modal_message(src, action, "", null, payload) + else + active_mr = null + set_temp("Error: Record missing.", "bad") + if("coredump") + if(disk) + SStranscore.core_dump(disk) + sleep(5) + visible_message("\The [src] spits out \the [disk].") + disk.forceMove(get_turf(src)) + disk = null + if("ejectdisk") disk.forceMove(get_turf(src)) disk = null - else if (href_list["ejectdisk"]) - disk.forceMove(get_turf(src)) - disk = null - - else if (href_list["create"]) - if(istype(active_br)) - //Tried to grow a synth but no synth pods. - if(active_br.synthetic && !spods.len) - temp = "Error: No SynthFabs detected." - //Tried to grow an organic but no growpods. - else if(!active_br.synthetic && !pods.len) - temp = "Error: No growpods detected." - //We have the machines. We can rebuild them. Probably. - else - //We're cloning a synth. - if(active_br.synthetic) - var/obj/machinery/transhuman/synthprinter/spod = spods[1] - if (spods.len > 1) - spod = input(usr,"Select a SynthFab to use", "Printer selection") as anything in spods - - //Already doing someone. - if(spod.busy) - temp = "Error: SynthFab is currently busy." - - //Not enough steel or glass - else if(spod.stored_material[DEFAULT_WALL_MATERIAL] < spod.body_cost) - temp = "Error: Not enough [DEFAULT_WALL_MATERIAL] in SynthFab." - else if(spod.stored_material["glass"] < spod.body_cost) - temp = "Error: Not enough glass in SynthFab." - - //Gross pod (broke mid-cloning or something). - else if(spod.broken) - temp = "Error: SynthFab malfunction." - - //Do the cloning! - else if(spod.print(active_br)) - temp = "Initiating printing cycle..." - menu = 1 - else - temp = "Initiating printing cycle...
Error: Post-initialisation failed. Printing cycle aborted." - - //We're cloning an organic. + if("create") + if(istype(active_br)) + //Tried to grow a synth but no synth pods. + if(active_br.synthetic && !spods.len) + set_temp("Error: No SynthFabs detected.", "bad") + //Tried to grow an organic but no growpods. + else if(!active_br.synthetic && !pods.len) + set_temp("Error: No growpods detected.", "Bad") + //We have the machines. We can rebuild them. Probably. else - var/obj/machinery/clonepod/transhuman/pod = pods[1] - if (pods.len > 1) - pod = input(usr,"Select a growing pod to use", "Pod selection") as anything in pods + //We're cloning a synth. + if(active_br.synthetic) + var/obj/machinery/transhuman/synthprinter/spod = selected_printer + if(!istype(spod)) + set_temp("Error: No SynthFab selected.", "bad") + return - //Already doing someone. - if(pod.occupant) - temp = "Error: Growpod is currently occupied." + //Already doing someone. + if(spod.busy) + set_temp("Error: SynthFab is currently busy.", "bad") + return - //Not enough materials. - else if(pod.get_biomass() < CLONE_BIOMASS) - temp = "Error: Not enough biomass." + //Not enough steel or glass + else if(spod.stored_material[DEFAULT_WALL_MATERIAL] < spod.body_cost) + set_temp("Error: Not enough [DEFAULT_WALL_MATERIAL] in SynthFab.", "bad") + return + else if(spod.stored_material["glass"] < spod.body_cost) + set_temp("Error: Not enough glass in SynthFab.", "bad") + return - //Gross pod (broke mid-cloning or something). - else if(pod.mess) - temp = "Error: Growpod malfunction." + //Gross pod (broke mid-cloning or something). + else if(spod.broken) + set_temp("Error: SynthFab malfunction.", "bad") + return - //Disabled in config. - else if(!config.revival_cloning) - temp = "Error: Unable to initiate growing cycle." + //Do the cloning! + else if(spod.print(active_br)) + set_temp("Initiating printing cycle...", "good") + menu = 1 + else + set_temp("Initiating printing cycle... Error: Post-initialisation failed. Printing cycle aborted.", "bad") + return - //Do the cloning! - else if(pod.growclone(active_br)) - temp = "Initiating growing cycle..." - menu = 1 + //We're cloning an organic. else - temp = "Initiating growing cycle...
Error: Post-initialisation failed. Growing cycle aborted." + var/obj/machinery/clonepod/transhuman/pod = selected_pod + if(!istype(pod)) + set_temp("Error: No clonepod selected.", "bad") + return - //The body record is broken somehow. - else - temp = "Error: Data corruption." + //Already doing someone. + if(pod.occupant) + set_temp("Error: Growpod is currently occupied.", "bad") + return - else if (href_list["sleeve"]) - if(istype(active_mr)) - if(!sleevers.len) - temp = "Error: No sleevers detected." + //Not enough materials. + else if(pod.get_biomass() < CLONE_BIOMASS) + set_temp("Error: Not enough biomass.", "bad") + return + + //Gross pod (broke mid-cloning or something). + else if(pod.mess) + set_temp("Error: Growpod malfunction.", "bad") + return + + //Disabled in config. + else if(!config.revival_cloning) + set_temp("Error: Unable to initiate growing cycle.", "bad") + return + + //Do the cloning! + else if(pod.growclone(active_br)) + set_temp("Initiating growing cycle...", "good") + menu = 1 + else + set_temp("Initiating growing cycle... Error: Post-initialisation failed. Growing cycle aborted.", "bad") + return + + //The body record is broken somehow. else - var/mode = text2num(href_list["sleeve"]) - var/override - var/obj/machinery/transhuman/resleever/sleever = sleevers[1] - if (sleevers.len > 1) - sleever = input(usr,"Select a resleeving pod to use", "Resleever selection") as anything in sleevers + set_temp("Error: Data corruption.", "bad") + return - switch(mode) - if(1) //Body resleeving - //No body to sleeve into. - if(!sleever.occupant) - temp = "Error: Resleeving pod is not occupied." + if("sleeve") + if(istype(active_mr)) + if(!sleevers.len) + set_temp("Error: No sleevers detected.", "bad") + else + var/mode = text2num(params["mode"]) + var/override + var/obj/machinery/transhuman/resleever/sleever = selected_sleever + if(!istype(sleever)) + set_temp("Error: No resleeving pod selected.", "bad") + return - //OOC body lock thing. - if(sleever.occupant.resleeve_lock && active_mr.ckey != sleever.occupant.resleeve_lock) - temp = "Error: Mind incompatible with body." + switch(mode) + if(1) //Body resleeving + //No body to sleeve into. + if(!sleever.occupant) + set_temp("Error: Resleeving pod is not occupied.", "bad") + return - var/list/subtargets = list() - for(var/mob/living/carbon/human/H in sleever.occupant) - if(H.resleeve_lock && active_mr.ckey != H.resleeve_lock) - continue - subtargets += H - if(subtargets.len) - var/oc_sanity = sleever.occupant - override = input(usr,"Multiple bodies detected. Select target for resleeving of [active_mr.mindname] manually. Sleeving of primary body is unsafe with sub-contents, and is not listed.", "Resleeving Target") as null|anything in subtargets - if(!override || oc_sanity != sleever.occupant || !(override in sleever.occupant)) - temp = "Error: Target selection aborted." + //OOC body lock thing. + if(sleever.occupant.resleeve_lock && active_mr.ckey != sleever.occupant.resleeve_lock) + set_temp("Error: Mind incompatible with body.", "bad") + return - if(2) //Card resleeving - if(sleever.sleevecards <= 0) - temp = "Error: No available cards in resleever." + var/list/subtargets = list() + for(var/mob/living/carbon/human/H in sleever.occupant) + if(H.resleeve_lock && active_mr.ckey != H.resleeve_lock) + continue + subtargets += H + if(subtargets.len) + var/oc_sanity = sleever.occupant + override = input(usr,"Multiple bodies detected. Select target for resleeving of [active_mr.mindname] manually. Sleeving of primary body is unsafe with sub-contents, and is not listed.", "Resleeving Target") as null|anything in subtargets + if(!override || oc_sanity != sleever.occupant || !(override in sleever.occupant)) + set_temp("Error: Target selection aborted.", "bad") + return - //Body to sleeve into, but mind is in another living body. - if(active_mr.mind_ref.current && active_mr.mind_ref.current.stat < DEAD) //Mind is in a body already that's alive - var/answer = alert(active_mr.mind_ref.current,"Someone is attempting to restore a backup of your mind. Do you want to abandon this body, and move there? You MAY suffer memory loss! (Same rules as CMD apply)","Resleeving","No","Yes") + if(2) //Card resleeving + if(sleever.sleevecards <= 0) + set_temp("Error: No available cards in resleever.", "bad") + return - //They declined to be moved. - if(answer == "No") - temp = "Initiating resleeving...
Error: Post-initialisation failed. Resleeving cycle aborted." + //Body to sleeve into, but mind is in another living body. + if(active_mr.mind_ref.current && active_mr.mind_ref.current.stat < DEAD) //Mind is in a body already that's alive + var/answer = alert(active_mr.mind_ref.current,"Someone is attempting to restore a backup of your mind. Do you want to abandon this body, and move there? You MAY suffer memory loss! (Same rules as CMD apply)","Resleeving","No","Yes") + + //They declined to be moved. + if(answer == "No") + set_temp("Initiating resleeving... Error: Post-initialisation failed. Resleeving cycle aborted.", "bad") + menu = MENU_MAIN + return TRUE + + //They were dead, or otherwise available. + if(!temp) + sleever.putmind(active_mr,mode,override) + set_temp("Initiating resleeving...") menu = 1 - - //They were dead, or otherwise available. - if(!temp) - sleever.putmind(active_mr,mode,override) - temp = "Initiating resleeving..." - menu = 1 - - //IDK but it broke somehow. + return + if("refresh") + SStgui.update_uis(src) + if("selectpod") + var/ref = params["ref"] + if(!length(ref)) + return + var/obj/machinery/clonepod/selected = locate(ref) + if(istype(selected) && (selected in pods)) + selected_pod = selected + if("selectprinter") + var/ref = params["ref"] + if(!length(ref)) + return + var/obj/machinery/transhuman/synthprinter/selected = locate(ref) + if(istype(selected) && (selected in spods)) + selected_printer = selected + if("selectsleever") + var/ref = params["ref"] + if(!length(ref)) + return + var/obj/machinery/transhuman/resleever/selected = locate(ref) + if(istype(selected) && (selected in sleevers)) + selected_sleever = selected + if("menu") + menu = clamp(text2num(params["num"]), MENU_MAIN, MENU_MIND) + if("cleartemp") + temp = null else - temp = "Error: Data corruption." - - else if (href_list["menu"]) - menu = href_list["menu"] - temp = "" - - SSnanoui.update_uis(src) - add_fingerprint(usr) + return FALSE // In here because only relevant to computer /obj/item/weapon/cmo_disk_holder @@ -409,3 +493,19 @@ item_state = "card-id" w_class = ITEMSIZE_SMALL var/datum/transhuman/mind_record/list/stored = list() + +/** + * Sets a temporary message to display to the user + * + * Arguments: + * * text - Text to display, null/empty to clear the message from the UI + * * style - The style of the message: (color name), info, success, warning, danger + */ +/obj/machinery/computer/transhuman/resleeving/proc/set_temp(text = "", style = "info", update_now = FALSE) + temp = list(text = text, style = style) + if(update_now) + SStgui.update_uis(src) + +#undef MENU_MAIN +#undef MENU_BODY +#undef MENU_MIND \ No newline at end of file diff --git a/code/modules/resleeving/machines.dm b/code/modules/resleeving/machines.dm index d6505931146..706a70bedcf 100644 --- a/code/modules/resleeving/machines.dm +++ b/code/modules/resleeving/machines.dm @@ -162,6 +162,11 @@ return +/obj/machinery/clonepod/transhuman/get_completion() + if(occupant) + return 100 * ((occupant.health + abs(config.health_threshold_dead)) / (occupant.maxHealth + abs(config.health_threshold_dead))) + return 0 + //Synthetic version /obj/machinery/transhuman/synthprinter name = "SynthFab 3000" @@ -437,28 +442,40 @@ sickness_duration = (45 - (total_rating-4)*1.875) MINUTES // 45 minutes default, 30 minutes with max non-anomaly upgrades, 15 minutes with max anomaly ones /obj/machinery/transhuman/resleever/attack_hand(mob/user as mob) - user.set_machine(src) - var/health_text = "" - var/mind_text = "" - if(src.occupant) - if(src.occupant.stat >= DEAD) - health_text = "DEAD" - else if(src.occupant.health < 0) - health_text = "[round(src.occupant.health,0.1)]" - else - health_text = "[round(src.occupant.health,0.1)]" + tgui_interact(user) - if(src.occupant.mind) - mind_text = "Mind present: [occupant.mind.name]" - else - mind_text = "Mind absent." +/obj/machinery/transhuman/resleever/tgui_interact(mob/user, datum/tgui/ui = null) + if(stat & (NOPOWER|BROKEN)) + return - var/dat ="Resleever Status
" - dat +="Current occupant: [src.occupant ? "
Name: [src.occupant]
Health: [health_text]
" : "None"]
" - dat +="Mind status: [mind_text]
" - user.set_machine(src) - user << browse(dat, "window=resleever") - onclose(user, "resleever") + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "ResleevingPod", "Resleever") + ui.open() + +/obj/machinery/transhuman/resleever/tgui_data(mob/user) + var/list/data = list() + + data["occupied"] = !!occupant + if(occupant) + data["name"] = occupant.name + data["health"] = occupant.health + data["maxHealth"] = occupant.maxHealth + data["stat"] = occupant.stat + data["mindStatus"] = !!occupant.mind + data["mindName"] = occupant.mind?.name + + if(occupant.has_modifier_of_type(/datum/modifier/resleeving_sickness) || occupant.has_modifier_of_type(/datum/modifier/faux_resleeving_sickness)) + data["resleeveSick"] = TRUE + else + data["resleeveSick"] = FALSE + + if(occupant.confused || occupant.eye_blurry) + data["initialSick"] = TRUE + else + data["initialSick"] = FALSE + + return data /obj/machinery/transhuman/resleever/attackby(obj/item/W as obj, mob/user as mob) src.add_fingerprint(user) diff --git a/code/modules/surgery/implant.dm b/code/modules/surgery/implant.dm index 276af95ce87..acbc8655dc0 100644 --- a/code/modules/surgery/implant.dm +++ b/code/modules/surgery/implant.dm @@ -119,6 +119,8 @@ max_duration = 100 /datum/surgery_step/cavity/place_item/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool) + if(!istype(tool)) + return 0 if(..()) var/obj/item/organ/external/affected = target.get_organ(target_zone) if(istype(user,/mob/living/silicon/robot)) diff --git a/code/modules/surgery/limb_reattach.dm b/code/modules/surgery/limb_reattach.dm index 391b83fe0c9..9ed0ce520da 100644 --- a/code/modules/surgery/limb_reattach.dm +++ b/code/modules/surgery/limb_reattach.dm @@ -28,6 +28,8 @@ max_duration = 70 /datum/surgery_step/limb/attach/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool) + if(!istype(tool)) + return 0 var/obj/item/organ/external/E = tool var/obj/item/organ/external/P = target.organs_by_name[E.parent_organ] var/obj/item/organ/external/affected = target.get_organ(target_zone) @@ -117,7 +119,7 @@ max_duration = 100 /datum/surgery_step/limb/mechanize/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool) - if(..()) + if(..() && istype(tool)) var/obj/item/robot_parts/p = tool if (p.part) if (!(target_zone in p.part)) diff --git a/code/modules/surgery/organs_internal.dm b/code/modules/surgery/organs_internal.dm index c09d10bebe6..a6a9a87386f 100644 --- a/code/modules/surgery/organs_internal.dm +++ b/code/modules/surgery/organs_internal.dm @@ -164,6 +164,9 @@ if (!..()) return 0 + if(!istype(tool)) + return 0 + var/obj/item/organ/external/affected = target.get_organ(target_zone) if(!(affected && !(affected.robotic >= ORGAN_ROBOT))) @@ -227,6 +230,9 @@ if (!..()) return 0 + if(!istype(tool)) + return 0 + target.op_stage.current_organ = null var/list/removable_organs = list() @@ -281,7 +287,7 @@ var/obj/item/organ/internal/O = tool var/obj/item/organ/external/affected = target.get_organ(target_zone) - if(!affected) + if(!affected || !istype(O)) return var/organ_compatible @@ -361,6 +367,9 @@ if (!..()) return 0 + if(!istype(tool)) + return 0 + target.op_stage.current_organ = null var/list/removable_organs = list() @@ -417,6 +426,9 @@ if (!..()) return 0 + if(!istype(tool)) + return 0 + target.op_stage.current_organ = null var/list/removable_organs = list() diff --git a/code/modules/tgui/modal.dm b/code/modules/tgui/modal.dm new file mode 100644 index 00000000000..5fb4c4cc743 --- /dev/null +++ b/code/modules/tgui/modal.dm @@ -0,0 +1,370 @@ +/** + * tgui modals + * + * Allows creation of modals within tgui. + */ + +GLOBAL_LIST(tgui_modals) + +/** + * Call this from a proc that is called in tgui_act() to process modal actions + * + * Example: /obj/machinery/chem_master/proc/tgui_act_modal + * You can then switch based on the return value and show different + * modals depending on the answer. + * Arguments: + * * source - The source datum + * * action - The called action + * * params - The params to the action + */ +/datum/proc/tgui_modal_act(datum/source = src, action = "", params) + ASSERT(istype(source)) + + . = null + switch(action) + if("modal_open") // Params: id, arguments + return TGUI_MODAL_OPEN + if("modal_answer") // Params: id, answer, arguments + params["answer"] = tgui_modal_preprocess_answer(source, params["answer"]) + if(tgui_modal_answer(source, params["id"], params["answer"])) // If there's a current modal with a delegate that returned TRUE, no need to continue + . = TGUI_MODAL_DELEGATE + else + . = TGUI_MODAL_ANSWER + tgui_modal_clear(source) + if("modal_close") // Params: id + tgui_modal_clear(source) + return TGUI_MODAL_CLOSE + +/** + * Call this from tgui_data() to return modal information if needed + + * Arguments: + * * source - The source datum + */ +/datum/proc/tgui_modal_data(datum/source = src) + ASSERT(istype(source)) + + var/datum/tgui_modal/current = LAZYACCESS(GLOB.tgui_modals, REF(source)) + if(!current) + return null + + return current.to_data() + +/** + * Clears the current modal for a given datum + * + * Arguments: + * * source - The source datum + */ +/datum/proc/tgui_modal_clear(datum/source = src) + ASSERT(istype(source)) + + LAZYINITLIST(GLOB.tgui_modals) + var/datum/tgui_modal/previous = GLOB.tgui_modals[REF(source)] + if(!previous) + return FALSE + + for(var/i in 1 to length(GLOB.tgui_modals)) + var/key = GLOB.tgui_modals[i] + if(previous == GLOB.tgui_modals[key]) + GLOB.tgui_modals.Cut(i, i + 1) + break + + SStgui.update_uis(source) + return TRUE + +/** + * Opens a message TGUI modal + * + * Arguments: + * * source - The source datum + * * id - The ID of the modal + * * text - The text to display above the answers + * * delegate - The proc to call when closed + * * arguments - List of arguments passed to and from JS (mostly useful for chaining modals) + */ +/datum/proc/tgui_modal_message(datum/source = src, id, text = "Default modal message", delegate, arguments) + ASSERT(length(id)) + + var/datum/tgui_modal/modal = new(id, text, delegate, arguments) + return tgui_modal_new(source, modal) + +/** + * Opens a text input TGUI modal + * + * Arguments: + * * source - The source datum + * * id - The ID of the modal + * * text - The text to display above the answers + * * delegate - The proc to call when submitted + * * arguments - List of arguments passed to and from JS (mostly useful for chaining modals) + * * value - The default value of the input + * * max_length - The maximum char length of the input + */ +/datum/proc/tgui_modal_input(datum/source = src, id, text = "Default modal message", delegate, arguments, value = "", max_length = TGUI_MODAL_INPUT_MAX_LENGTH) + ASSERT(length(id)) + ASSERT(max_length > 0) + + var/datum/tgui_modal/input/modal = new(id, text, delegate, arguments, value, max_length) + return tgui_modal_new(source, modal) + +/** + * Opens a dropdown input TGUI modal + * + * Internally checks if the answer is in the list of choices. + * Arguments: + * * source - The source datum + * * id - The ID of the modal + * * text - The text to display above the answers + * * delegate - The proc to call when submitted + * * arguments - List of arguments passed to and from JS (mostly useful for chaining modals) + * * value - The default value of the dropdown + * * choices - The list of available choices in the dropdown + */ +/datum/proc/tgui_modal_choice(datum/source = src, id, text = "Default modal message", delegate, arguments, value = "", choices) + ASSERT(length(id)) + + var/datum/tgui_modal/input/choice/modal = new(id, text, delegate, arguments, value, choices) + return tgui_modal_new(source, modal) + +/** + * Opens a bento input TGUI modal + * + * Internally checks if the answer is in the list of choices. + * Arguments: + * * source - The source datum + * * id - The ID of the modal + * * text - The text to display above the answers + * * delegate - The proc to call when submitted + * * arguments - List of arguments passed to and from JS (mostly useful for chaining modals) + * * value - The default value of the bento + * * choices - The list of available choices in the bento + */ +/datum/proc/tgui_modal_bento(datum/source = src, id, text = "Default modal message", delegate, arguments, value, choices) + ASSERT(length(id)) + + var/datum/tgui_modal/input/bento/modal = new(id, text, delegate, arguments, value, choices) + return tgui_modal_new(source, modal) + +/** + * Opens a yes/no TGUI modal + * + * Arguments: + * * source - The source datum + * * id - The ID of the modal + * * text - The text to display above the answers + * * delegate - The proc to call when "Yes" is pressed + * * delegate_no - The proc to call when "No" is pressed + * * arguments - List of arguments passed to and from JS (mostly useful for chaining modals) + * * yes_text - The text to show in the "Yes" button + * * no_text - The text to show in the "No" button + */ +/datum/proc/tgui_modal_boolean(datum/source = src, id, text = "Default modal message", delegate, delegate_no, arguments, yes_text = "Yes", no_text = "No") + ASSERT(length(id)) + + var/datum/tgui_modal/boolean/modal = new(id, text, delegate, delegate_no, arguments, yes_text, no_text) + return tgui_modal_new(source, modal) + +/** + * Registers a given modal to a source. Private. + * + * Arguments: + * * source - The source datum + * * modal - The datum/tgui_modal to register + * * replace_previous - Whether any modal currently assigned to source should be replaced + * * instant_update - Whether the changes should reflect immediately + */ +/datum/proc/tgui_modal_new(datum/source = src, datum/tgui_modal/modal = null, replace_previous = TRUE, instant_update = TRUE) + ASSERT(istype(source)) + ASSERT(istype(modal)) + + var/datum/tgui_modal/previous = LAZYACCESS(GLOB.tgui_modals, REF(source)) + if(previous && !replace_previous) + return FALSE + + modal.owning_source = source + + // Previous one should get GC'd + LAZYSET(GLOB.tgui_modals, REF(source), modal) + if(instant_update) + SStgui.update_uis(source) + return TRUE + +/** + * Calls the source's currently assigned modal's (if there is one) on_answer() proc. Private. + * + * Arguments: + * * source - The source datum + * * id - The ID of the modal + * * answer - The provided answer + */ +/datum/proc/tgui_modal_answer(datum/source = src, id, answer = "") + ASSERT(istype(source)) + + var/datum/tgui_modal/current = LAZYACCESS(GLOB.tgui_modals, REF(source)) + if(!current) + return FALSE + + return current.on_answer(answer) + +/** + * Passes an answer from JS through the modal's proc. + * + * Used namely for cutting the text short if it's longer + * than an input modal's max_length. + * Arguments: + * * source - The source datum + * * answer - The provided answer + */ +/datum/proc/tgui_modal_preprocess_answer(datum/source = src, answer = "") + ASSERT(istype(source)) + + var/datum/tgui_modal/current = LAZYACCESS(GLOB.tgui_modals, REF(source)) + if(!current) + return answer + + return current.preprocess_answer(answer) + +/** + * Modal datum (contains base information for a modal) + */ +/datum/tgui_modal + var/datum/owning_source + var/id + var/text + var/delegate + var/list/arguments + var/modal_type = "message" + +/datum/tgui_modal/New(id, text, delegate, list/arguments) + src.id = id + src.text = text + src.delegate = delegate + src.arguments = arguments + +/** + * Called when it's time to pre-process the answer before using it + * + * Arguments: + * * answer - The answer, a nullable text + */ +/datum/tgui_modal/proc/preprocess_answer(answer) + return reject_bad_text(answer, TGUI_MODAL_INPUT_MAX_LENGTH) // bleh + +/** + * Called when a modal receives an answer + * + * Arguments: + * * answer - The answer, a nullable text + */ +/datum/tgui_modal/proc/on_answer(answer) + if(delegate) + return call(owning_source, delegate)(answer, arguments) + return FALSE + +/** + * Creates a list that describes a modal visually to be passed to JS + */ +/datum/tgui_modal/proc/to_data() + . = list() + .["id"] = id + .["text"] = text + .["args"] = arguments || list() + .["type"] = modal_type + +/** + * Input modal - has a text entry that can be used to enter an answer + */ +/datum/tgui_modal/input + modal_type = "input" + var/value + var/max_length + +/datum/tgui_modal/input/New(id, text, delegate, list/arguments, value, max_length) + ..(id, text, delegate, arguments) + src.value = value + src.max_length = max_length + +/datum/tgui_modal/input/preprocess_answer(answer) + . = ..(answer) + if(length(answer) > max_length) + . = copytext(., 1, max_length + 1) + +/datum/tgui_modal/input/to_data() + . = ..() + .["value"] = value + +/** + * Choice modal - has a dropdown menu that can be used to select an answer + */ +/datum/tgui_modal/input/choice + modal_type = "choice" + var/choices + +/datum/tgui_modal/input/choice/New(id, text, delegate, list/arguments, value, choices) + ..(id, text, delegate, arguments, value, TGUI_MODAL_INPUT_MAX_LENGTH) // Max length doesn't really matter in dropdowns, but whatever + src.choices = choices + +/datum/tgui_modal/input/choice/on_answer(answer) + if(answer in choices) // Make sure the answer is actually in our choices! + return ..(answer, arguments) + return FALSE + +/datum/tgui_modal/input/choice/to_data() + . = ..() + .["choices"] = choices + +/** + * Bento modal - Similar to choice, it displays the choices in a grid of images + * + * The returned answer is the index of the choice. + */ +/datum/tgui_modal/input/bento + modal_type = "bento" + var/choices + +/datum/tgui_modal/input/bento/New(id, text, delegate, list/arguments, value, choices) + ..(id, text, delegate, arguments, text2num(value), TGUI_MODAL_INPUT_MAX_LENGTH) // Max length doesn't really matter in here, but whatever + src.choices = choices + +/datum/tgui_modal/input/bento/preprocess_answer(answer) + return text2num(answer) || 0 + +/datum/tgui_modal/input/bento/on_answer(answer) + if(answer >= 1 && answer <= length(choices)) // Make sure the answer index is actually in our indexes! + return ..(answer, arguments) + return FALSE + +/datum/tgui_modal/input/bento/to_data() + . = ..() + .["choices"] = choices + +/** + * Boolean modal - has yes/no buttons that do different actions depending on which is pressed + */ +/datum/tgui_modal/boolean + modal_type = "boolean" + var/delegate_no + var/yes_text + var/no_text + +/datum/tgui_modal/boolean/New(id, text, delegate, delegate_no, list/arguments, yes_text, no_text) + ..(id, text, delegate, arguments) + src.delegate_no = delegate_no + src.yes_text = yes_text + src.no_text = no_text + +/datum/tgui_modal/boolean/preprocess_answer(answer) + return text2num(answer) || FALSE + +/datum/tgui_modal/boolean/on_answer(answer) + if(answer) + return ..(answer, arguments) + else if(delegate_no) + return call(owning_source, delegate_no)(arguments) + return FALSE + +/datum/tgui_modal/boolean/to_data() + . = ..() + .["yes_text"] = yes_text + .["no_text"] = no_text diff --git a/nano/templates/adv_med.tmpl b/nano/templates/adv_med.tmpl deleted file mode 100644 index 2cd4d576c0c..00000000000 --- a/nano/templates/adv_med.tmpl +++ /dev/null @@ -1,290 +0,0 @@ - -{{if !data.occupied}} -

No occupant detected.

-{{else}} -

Occupant Data:

-
-
- Name: -
-
- {{:data.occupant.name}} -
-
-
-
- Health: -
- {{:helper.displayBar(data.occupant.health, 0, data.occupant.maxHealth, (data.occupant.health >= 50) ? 'good' : (data.occupant.health >= 25) ? 'average' : 'bad')}} -
- {{:helper.round(data.occupant.health / data.occupant.maxHealth)*100}}% -
-
-
- Status: -
-
- {{if data.occupant.stat==0}} - Stable - {{else data.occupant.stat==1}} - Non-Responsive - {{else}} - Dead - {{/if}} -
-
- {{:helper.link('Print', 'document', {'print_p' : 1, 'name' : data.occupant.name})}} -

Damage:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {{if data.occupant.humanPrey}} - - - - - {{/if}} - {{if data.occupant.livingPrey}} - - - - - {{/if}} - {{if data.occupant.objectPrey}} - - - - - {{/if}} - - -
Brute:{{:data.occupant.bruteLoss}}Brain:{{:data.occupant.brainLoss}}
Burn:{{:data.occupant.fireLoss}}Radiation:{{:data.occupant.radLoss}}
Oxygen:{{:data.occupant.oxyLoss}}Genetic:{{:data.occupant.cloneLoss}}
Toxins:{{:data.occupant.toxLoss}}Paralysis:{{:data.occupant.paralysis}}% ({{:data.occupant.paralysisSeconds}} seconds left!)
Body Temperature:{{:helper.round(data.occupant.bodyTempC*10)/10}}°C, {{:helper.round(data.occupant.bodyTempF*10)/10}}°F
Body Weight:{{:helper.round(data.occupant.weight)}} lbs, {{:helper.round( - data.occupant.weight/2.20463)}} kgs
Foreign Humanoids:{{:data.occupant.humanPrey}}
Foreign Creatures:{{:data.occupant.livingPrey}}
Foreign Objects:{{:data.occupant.objectPrey}}
-
- {{if data.occupant.hasVirus}} -
- Viral pathogen detected in blood stream. -
- {{/if}} - {{if data.occupant.hasBorer}} -
- Large growth detected in frontal lobe, possibly cancerous. Surgical removal is recommended. -
- {{/if}} - {{if data.occupant.blind}} -
Pupils unresponsive.
- {{/if}} - {{if data.occupant.nearsighted}} -
Retinal Misalignment Detected
- {{/if}} -

Blood

- - - - - - - -
Volume:{{:data.occupant.blood.volume}}Percent:{{:data.occupant.blood.percent}}%
-

Blood Reagents

- {{if data.occupant.reagents}} - - {{for data.occupant.reagents}} - - - - - {{/for}} -
{{:value.name}}:{{:value.amount}}
- {{else}} -
No blood reagents detected.
- {{/if}} -

Stomach Reagents

- {{if data.occupant.ingested}} - - {{for data.occupant.ingested}} - - - - - {{/for}} -
{{:value.name}}:{{:value.amount}}
- {{else}} -
No stomach reagents detected.
- {{/if}} -

External Organs

-
- {{for data.occupant.extOrgan}} -
- {{if value.status.destroyed}} -
- {{:value.name}} - DESTROYED -
- {{else}} -
-
- {{:value.name}} -
-
-
- {{if value.status.broken}} - {{:value.status.broken}} - {{else value.status.splinted}} - Splinted - {{else value.status.robotic}} - Robotic - {{/if}} -
-
-  Brute/Burn -
-
- {{:value.bruteLoss}}/{{:value.fireLoss}} -
-
-  Injuries -
-
- {{if !value.status.bleeding}} - {{if !value.status.internalBleeding}} - No Injuries Detected - {{else}} - Internal Bleeding Detected - {{/if}} - {{else}} - {{if value.status.internalBleeding}} -
Internal Bleeding Detected. External Bleeding Detected.
- {{else}} - External Bleeding Detected - {{/if}} - {{/if}} -
- {{if value.germ_level > 100}} -
-  Infection -
-
- {{if value.germ_level < 300}} - Mild Infection - {{else value.germ_level < 400}} - Mild Infection+ - {{else value.germ_level < 500}} - Mild Infection++ - {{else value.germ_level < 700}} - Acute Infection - {{else value.germ_level < 800}} - Acute Infection+ - {{else value.germ_level < 950}} - Acute Infection++ - {{else value.germ_level >= 950}} - Gangrene Detected - {{/if}} -
- {{/if}} - {{if value.status.dead}} -
-  Necrosis -
-
- Necrotic Tissue Present -
- {{/if}} - {{if value.open}} -
-  Operation Status -
-
- Open Incision -
- {{/if}} - {{if value.implants_len}} -
-  Implants -
- {{for value.implants :impValue:impindex}} -
-   {{:impValue.known ? impValue.name : "Unknown"}} -
- {{/for}} - {{/if}} - {{/if}} -
- {{/for}} -
-

Internal Organs

-
- {{for data.occupant.intOrgan}} -
-
- {{:value.name}} -
-
- {{:value.desc != null ? value.desc : ""}} -
-
-
- {{if value.germ_level > 100}} -
-  Infection -
-
- {{if value.germ_level < 300}} - Mild Infection - {{else value.germ_level < 400}} - Mild Infection+ - {{else value.germ_level < 500}} - Mild Infection++ - {{else value.germ_level < 700}} - Acute Infection - {{else value.germ_level < 800}} - Acute Infection+ - {{else value.germ_level < 950}} - Acute Infection++ - {{else value.germ_level >= 950}} - Necrosis Detected - {{/if}} -
- {{/if}} -
-  Damage -
-
- {{:value.damage}} -
-
- {{/for}} -
-{{/if}} \ No newline at end of file diff --git a/nano/templates/chem_disp.tmpl b/nano/templates/chem_disp.tmpl deleted file mode 100644 index eaacb6a1dd5..00000000000 --- a/nano/templates/chem_disp.tmpl +++ /dev/null @@ -1,76 +0,0 @@ - -
-
- Dispense: -
-
- {{:helper.link('5', 'gear', {'amount' : 5}, (data.amount == 5) ? 'selected' : null)}} - {{:helper.link('10', 'gear', {'amount' : 10}, (data.amount == 10) ? 'selected' : null)}} - {{:helper.link('20', 'gear', {'amount' : 20}, (data.amount == 20) ? 'selected' : null)}} - {{:helper.link('30', 'gear', {'amount' : 30}, (data.amount == 30) ? 'selected' : null)}} - {{:helper.link('40', 'gear', {'amount' : 40}, (data.amount == 40) ? 'selected' : null)}} -

- {{:helper.link('--', '', {'amount' : data.amount-10})}} - {{:helper.link('-', '', {'amount' : data.amount-1})}} -
{{:data.amount}}
- {{:helper.link('+', '', {'amount' : data.amount+1})}} - {{:helper.link('++', '', {'amount' : data.amount+10})}} -
-
-
 
-
-
- {{if data.chemicals.length}} - {{for data.chemicals}} - {{:helper.link(value.label + " ("+value.amount+")", 'circle-arrow-s', {"dispense":value.label}, null, 'fixedLeftWide')}} - {{/for}} - {{else}} - No cartridges installed! - {{/if}} -
-
-
 
-
-
- {{if data.glass}} - Glass - {{else}} - Beaker - {{/if}} Contents -
-
- {{:helper.link(data.glass ? 'Eject Glass' : 'Eject Beaker', 'eject', {'ejectBeaker' : 1}, data.isBeakerLoaded ? null : 'disabled', 'floatRight')}} -
-
-
-
-
- {{if data.isBeakerLoaded}} - Volume: {{:data.beakerCurrentVolume}} / {{:data.beakerMaxVolume}}
- {{for data.beakerContents}} - {{:value.volume}} units of {{:value.name}}
- {{empty}} - - {{if data.glass}} - Glass - {{else}} - Beaker - {{/if}} is empty - - {{/for}} - {{else}} - - No - {{if data.glass}} - Glass - {{else}} - Beaker - {{/if}} loaded - - {{/if}} -
-
-
diff --git a/nano/templates/chem_master.tmpl b/nano/templates/chem_master.tmpl deleted file mode 100644 index f1b981ba6ec..00000000000 --- a/nano/templates/chem_master.tmpl +++ /dev/null @@ -1,123 +0,0 @@ - -{{if data.tab == 'home'}} -
-
{{:helper.link(data.pillBottle ? 'Eject Pill Bottle' : 'No pill bottle inserted', 'eject', {'ejectp' : 1}, data.pillBottle ? null : 'linkOff')}}
- - {{if data.pillBottle}} -
{{:data.pillBottle.total}} / {{:data.pillBottle.max}}
- {{/if}} -
-
{{:helper.link(data.beaker ? 'Eject Beaker and Clear Buffer' : 'Please insert beaker', 'eject', {'eject' : 1}, data.beaker ? null : 'linkOff')}}
- - {{if data.beaker}} - {{if data.beaker.total_volume}} - Add to buffer: - {{for data.beaker.reagent_list}} -
-
{{:value.name}}
-
{{:value.volume}} Units
-
-
- {{:helper.link('Analyze', 'signal-diag', {'tab_select' : 'analyze', 'desc' : value.description, 'name' : value.name})}} - {{:helper.link('1', 'plus', {'add' : value.id, 'amount' : 1})}} - {{:helper.link('5', 'plus', {'add' : value.id, 'amount' : 5})}} - {{:helper.link('10', 'plus', {'add' : value.id, 'amount' : 10})}} - {{:helper.link('30', 'plus', {'add' : value.id, 'amount' : 30})}} - {{:helper.link('60', 'plus', {'add' : value.id, 'amount' : 60})}} - {{:helper.link('All', 'plus', {'add' : value.id, 'amount' : value.volume})}} - {{:helper.link('Custom', 'plus', {'addcustom' : value.id})}} -
- {{/for}} - {{else}} - Beaker is empty. - {{/if}} -
-
-
Transfer to
- {{:helper.link(!data.mode ? 'disposal' : 'beaker', null, {'toggle' : 1})}} -
- {{if data.reagents}} - {{if data.reagents.total_volume}} - {{for data.reagents.reagent_list}} -
-
{{:value.name}}
-
{{:value.volume}} Units
-
-
- {{:helper.link('Analyze', 'signal-diag', {'tab_select' : 'analyze', 'desc' : value.description, 'name' : value.name})}} - {{:helper.link('1', 'minus', {'remove' : value.id, 'amount' : 1})}} - {{:helper.link('5', 'minus', {'remove' : value.id, 'amount' : 5})}} - {{:helper.link('10', 'minus', {'remove' : value.id, 'amount' : 10})}} - {{:helper.link('30', 'minus', {'remove' : value.id, 'amount' : 30})}} - {{:helper.link('60', 'minus', {'remove' : value.id, 'amount' : 60})}} - {{:helper.link('All', 'minus', {'remove' : value.id, 'amount' : value.volume})}} - {{:helper.link('Custom', 'minus', {'removecustom' : value.id})}} -
- {{/for}} - {{/if}} - {{else}} - Empty - {{/if}} - {{if !data.condi}} -
-
- {{:helper.link('Create pill (60 units max)', null, {'createpill' : 1})}} - {{:helper.link('Create multiple pills', null, {'createpill_multiple' : 1})}} - {{:helper.link('Create bottle (60 units max)', null, {'createbottle' : 1})}} - {{:helper.link('Create patch (60 units max)', null, {'createpatch' : 1})}} -
-
- {{:helper.link('', 'pill pill' + data.pillSprite, {'tab_select' : 'pill'}, null, 'link32')}} - {{:helper.link('', 'pill bottle' + data.bottleSprite, {'tab_select' : 'bottle'}, null, 'link32')}} -
- {{else}} -
-
- {{:helper.link('Create bottle (50 units max)', null, {'createbottle' : 1})}} - {{:helper.link('Create bouillon cube (60 units max)', null, {'createpill' : 1})}} -
- {{/if}} - {{/if}} - -{{else data.tab == 'analyze'}} - {{if !data.condi}} -

Chemical Info:

- {{else}} -

Condiment Info:

- {{/if}} -
-
Name:
-
{{:data.analyzeData.name}}
-
- {{if data.analyzeData.name == 'Blood'}} -
-
Blood Type:
-
{{:data.analyzeData.blood_type}}
-
-
-
DNA:
-
{{:data.analyzeData.blood_DNA}}
-
- {{else}} -
-
Description:
-
{{:data.analyzeData.desc}}
-
- {{/if}} - {{:helper.link('Back', 'arrowreturn-1-w', {'tab_select' : 'home'})}} - -{{else data.tab == 'pill'}} - {{for data.pillSpritesAmount}} - {{:helper.link('', 'pill pill' + value, {'pill_sprite' : value}, null, data.pillSprite == value ? 'linkOn link32' : 'link32')}} - {{/for}} -

{{:helper.link('Return', 'arrowreturn-1-w', {'tab_select' : 'home'})}}
- -{{else data.tab == 'bottle'}} - {{for data.bottleSpritesAmount}} - {{:helper.link('', 'pill bottle' + value, {'bottle_sprite' : value}, null, data.bottleSprite == value ? 'linkOn link32' : 'link32')}} - {{/for}} -

{{:helper.link('Return', 'arrowreturn-1-w', {'tab_select' : 'home'})}}
-{{/if}} \ No newline at end of file diff --git a/nano/templates/cloning.tmpl b/nano/templates/cloning.tmpl deleted file mode 100644 index a927fcc4d9c..00000000000 --- a/nano/templates/cloning.tmpl +++ /dev/null @@ -1,104 +0,0 @@ - - -{{:data.temp}} - -{{if data.menu == 1}} - -

Modules

-
- {{if data.connected}} - DNA scanner found. - {{else}} - DNA scanner not found. - {{/if}} -
-
- {{if data.podsLen > 0}} - {{:data.podsLen}} cloning vat\s found. - {{else}} - No cloning vats found. - {{/if}} -
- - -

Scanner Functions

-
- {{if data.loading}} - Scanning... - {{else}} - {{:data.scantemp}} - {{/if}} -
-
- {{if data.connected}} -
- {{:helper.link(data.occupant ? 'Scan - ' + data.occupant : 'Scanner unoccupied', 'play', {'scan' : 1}, data.occupant ? null : 'linkOff')}} -
-
- {{:helper.link(data.locked ? 'Unlock' : 'Lock', data.locked ? 'locked' : 'unlocked', {'lock' : 1}, null, data.locked ? 'redButton' : null)}} -
-
- {{:helper.link('Eject', 'eject', {'eject' : 1}, data.occupant ? null : 'linkOff')}} -
- {{else}} - No scanner connected! - {{/if}} -
- - {{if data.podsLen}} - {{for data.pods}} -
{{:value.pod}}, biomass: {{:value.biomass}}
- {{/for}} - {{/if}} - - -

Database Functions

-
- {{:helper.link('View Records', 'list', {'menu' : 2})}} - {{:helper.link('Eject Disk', 'eject', {'disk' : 'eject'}, data.diskette ? null : 'linkOff')}} -
- -{{else data.menu == 2}} -

Current records

- {{:helper.link('Back', 'arrowreturn-1-w', {'menu' : 1})}} -
- {{for data.records}} - {{:helper.link(value.name, 'document', {'view_rec' : value.ckey})}} - {{/for}} -
- -{{else data.menu == 3}} -

Selected Record

-
{{:helper.link('Back', 'arrowreturn-1-w', {'menu' : 2})}}
- - {{if data.activeRecord}} - {{:helper.link('Delete Record', 'trash', {'del_rec' : 1}, null, 'redButton')}} -
-
Name:
-
{{:data.activeRecord.real_name}}
-
- - {{:helper.link('Load from disk', 'transfer-e-w', {'disk' : 'load'}, data.disk ? null : 'linkOff')}} - -
-
Save:
- {{:helper.link('UI + UE', 'disk', {'save_disk' : 'ue'}, data.disk ? null : 'linkOff')}} - {{:helper.link('UI', 'disk', {'save_disk' : 'ui'}, data.disk ? null : 'linkOff')}} - {{:helper.link('SE', 'disk', {'save_disk' : 'se'}, data.disk ? null : 'linkOff')}} -
- - {{:helper.link('Clone', 'play', {'clone' : data.activeRecord.ckey}, data.podsLen ? null : 'linkOff')}} - - {{else}} -
ERROR: Record not found.
- {{/if}} - -{{else data.menu == 4}} - {{:data.temp}} -

Confirm Record Deletion

-
Scan card to confirm.
- {{:helper.link('Cancel', 'cancel', {'menu' : 3})}} -{{/if}} \ No newline at end of file diff --git a/nano/templates/cryo.tmpl b/nano/templates/cryo.tmpl deleted file mode 100644 index e8302b07df9..00000000000 --- a/nano/templates/cryo.tmpl +++ /dev/null @@ -1,98 +0,0 @@ - -

Cryo Cell Status

- -
- {{if !data.hasOccupant}} -
Cell Unoccupied
- {{else}} -
- {{:data.occupant.name}} =>  - {{if data.occupant.stat == 0}} - Conscious - {{else data.occupant.stat == 1}} - Unconscious - {{else}} - DEAD - {{/if}} -
- - {{if data.occupant.stat < 2}} -
-
Health:
- {{if data.occupant.health >= 0}} - {{:helper.displayBar(data.occupant.health, 0, data.occupant.maxHealth, 'good')}} - {{else}} - {{:helper.displayBar(data.occupant.health, 0, data.occupant.minHealth, 'average alignRight')}} - {{/if}} -
{{:helper.round(data.occupant.health)}}
-
- -
-
=> Brute Damage:
- {{:helper.displayBar(data.occupant.bruteLoss, 0, data.occupant.maxHealth, 'bad')}} -
{{:helper.round(data.occupant.bruteLoss)}}
-
- -
-
=> Resp. Damage:
- {{:helper.displayBar(data.occupant.oxyLoss, 0, data.occupant.maxHealth, 'bad')}} -
{{:helper.round(data.occupant.oxyLoss)}}
-
- -
-
=> Toxin Damage:
- {{:helper.displayBar(data.occupant.toxLoss, 0, data.occupant.maxHealth, 'bad')}} -
{{:helper.round(data.occupant.toxLoss)}}
-
- -
-
=> Burn Severity:
- {{:helper.displayBar(data.occupant.fireLoss, 0, data.occupant.maxHealth, 'bad')}} -
{{:helper.round(data.occupant.fireLoss)}}
-
- {{/if}} - {{/if}} -
-
-
Cell Temperature:
- {{:data.cellTemperature}} K -
-
-
- -

Cryo Cell Operation

-
-
- Cryo Cell Status: -
-
- {{:helper.link('On', 'power', {'switchOn' : 1}, data.isOperating ? 'selected' : null)}}{{:helper.link('Off', 'close', {'switchOff' : 1}, data.isOperating ? null : 'selected')}} -
-
- {{:helper.link('Eject Occupant', 'arrowreturnthick-1-s', {'ejectOccupant' : 1}, data.hasOccupant ? null : 'disabled')}} -
-
-
 
-
-
- Beaker: -
-
- {{if data.isBeakerLoaded}} - {{:data.beakerLabel ? data.beakerLabel : 'No label'}}
- {{if data.beakerVolume}} - {{:data.beakerVolume}} units remaining
- {{else}} - Beaker is empty - {{/if}} - {{else}} - No beaker loaded - {{/if}} -
-
- {{:helper.link('Eject Beaker', 'eject', {'ejectBeaker' : 1}, data.isBeakerLoaded ? null : 'disabled')}} -
-
\ No newline at end of file diff --git a/nano/templates/dna_modifier.tmpl b/nano/templates/dna_modifier.tmpl deleted file mode 100644 index 9a2804ba987..00000000000 --- a/nano/templates/dna_modifier.tmpl +++ /dev/null @@ -1,318 +0,0 @@ - -

Status

- -
- {{if !data.hasOccupant}} -
Cell Unoccupied
- {{else}} -
- {{:data.occupant.name}} =>  - {{if data.occupant.stat == 0}} - Conscious - {{else data.occupant.stat == 1}} - Unconscious - {{else}} - DEAD - {{/if}} -
- - {{if !data.occupant.isViableSubject || !data.occupant.uniqueIdentity || !data.occupant.structuralEnzymes}} -
- The occupant's DNA structure is ruined beyond recognition, please insert a subject with an intact DNA structure. -
- {{else data.occupant.stat < 2}} -
-
Health:
- {{if data.occupant.health >= 0}} - {{:helper.displayBar(data.occupant.health, 0, data.occupant.maxHealth, 'good')}} - {{else}} - {{:helper.displayBar(data.occupant.health, 0, data.occupant.minHealth, 'average alignRight')}} - {{/if}} -
{{:helper.round(data.occupant.health)}}
-
- -
-
Radiation:
- {{:helper.displayBar(data.occupant.radiationLevel, 0, 100, 'average')}} -
{{:helper.round(data.occupant.radiationLevel)}}
-
- -
-
Unique Enzymes:
-
{{:data.occupant.uniqueEnzymes ? data.occupant.uniqueEnzymes : 'Unknown'}}
-
- - - {{/if}} - {{/if}} -
-
- -

Operations

-
- {{:helper.link('Modify U.I.', 'link', {'selectMenuKey' : 'ui'}, data.selectedMenuKey == 'ui' ? 'selected' : null)}} - {{:helper.link('Modify S.E.', 'link', {'selectMenuKey' : 'se'}, data.selectedMenuKey == 'se' ? 'selected' : null)}} - {{:helper.link('Transfer Buffers', 'disk', {'selectMenuKey' : 'buffer'}, data.selectedMenuKey == 'buffer' ? 'selected' : null)}} - {{:helper.link('Rejuvenators', 'plusthick', {'selectMenuKey' : 'rejuvenators'}, data.selectedMenuKey == 'rejuvenators' ? 'selected' : null)}} -
- -
 
- -{{if !data.selectedMenuKey || data.selectedMenuKey == 'ui'}} -

Modify Unique Identifier

- {{:helper.displayDNABlocks(data.occupant.uniqueIdentity, data.selectedUIBlock, data.selectedUISubBlock, data.dnaBlockSize, 'UI')}} -
-
-
- Target: -
-
- {{:helper.link('-', null, {'changeUITarget' : 0}, (data.selectedUITarget > 0) ? null : 'disabled')}} -
 {{:data.selectedUITargetHex}} 
- {{:helper.link('+', null, {'changeUITarget' : 1}, (data.selectedUITarget < 15) ? null : 'disabled')}} -
-
-
-
- {{:helper.link('Irradiate Block', 'radiation', {'pulseUIRadiation' : 1}, !data.occupant.isViableSubject ? 'disabled' : null)}} -
-
-{{else data.selectedMenuKey == 'se'}} -

Modify Structural Enzymes

- {{:helper.displayDNABlocks(data.occupant.structuralEnzymes, data.selectedSEBlock, data.selectedSESubBlock, data.dnaBlockSize, 'SE')}} -
-
-
- {{:helper.link('Irradiate Block', 'radiation', {'pulseSERadiation' : 1}, !data.occupant.isViableSubject ? 'disabled' : null)}} -
-
-{{else data.selectedMenuKey == 'buffer'}} -

Transfer Buffers

- {{for data.buffers}} -

Buffer {{:(index + 1)}}

-
-
-
- Load Data: -
-
- {{:helper.link('Subject U.I.', 'link', {'bufferOption' : 'saveUI', 'bufferId' : (index + 1)}, !data.hasOccupant ? 'disabled' : null)}} - {{:helper.link('Subject U.I. + U.E.', 'link', {'bufferOption' : 'saveUIAndUE', 'bufferId' : (index + 1)}, !data.hasOccupant ? 'disabled' : null)}} - {{:helper.link('Subject S.E.', 'link', {'bufferOption' : 'saveSE', 'bufferId' : (index + 1)}, !data.hasOccupant ? 'disabled' : null)}} - {{:helper.link('From Disk', 'disk', {'bufferOption' : 'loadDisk', 'bufferId' : (index + 1)}, (!data.hasDisk || !data.disk.data) ? 'disabled' : null)}} -
-
- {{if value.data}} -
-
- Label: -
-
- {{:helper.link(value.label, 'document-b', {'bufferOption' : 'changeLabel', 'bufferId' : (index + 1)})}} -
-
-
-
- Subject: -
-
- {{:value.owner ? value.owner : 'Unknown'}} -
-
-
-
- Stored Data: -
-
- {{:value.data == 'ui' ? 'Unique Identifiers' : 'Structural Enzymes'}} - {{:value.ue ? ' + Unique Enzymes' : ''}} -
-
- {{else}} -
-
- This buffer is empty. -
-
- {{/if}} -
-
- Options: -
-
- {{:helper.link('Clear', 'trash', {'bufferOption' : 'clear', 'bufferId' : (index + 1)}, !value.data ? 'disabled' : null)}} - {{:helper.link('Injector', data.isInjectorReady ? 'pencil' : 'clock', {'bufferOption' : 'createInjector', 'bufferId' : (index + 1)}, (!data.isInjectorReady || !value.data) ? 'disabled' : null)}} - {{:helper.link('Block Injector', data.isInjectorReady ? 'pencil' : 'clock', {'bufferOption' : 'createInjector', 'bufferId' : (index + 1), 'createBlockInjector' : 1}, (!data.isInjectorReady || !value.data) ? 'disabled' : null)}} - {{:helper.link('Transfer', 'radiation', {'bufferOption' : 'transfer', 'bufferId' : (index + 1)}, (!data.hasOccupant || !value.data) ? 'disabled' : null)}} - {{:helper.link('Save To Disk', 'disk', {'bufferOption' : 'saveDisk', 'bufferId' : (index + 1)}, (!value.data || !data.hasDisk) ? 'disabled' : null)}} -
-
-
- {{/for}} - -

Data Disk

-
- {{if data.hasDisk}} - {{if data.disk.data}} -
-
- Label: -
-
- {{:data.disk.label ? data.disk.label : 'No Label'}} -
-
-
-
- Subject: -
-
- {{:data.disk.owner ? data.disk.owner : 'Unknown'}} -
-
-
-
- Stored Data: -
-
- {{:data.disk.data == 'ui' ? 'Unique Identifiers' : 'Structural Enzymes'}} - {{:data.disk.ue ? ' + Unique Enzymes' : ''}} -
-
- {{else}} -
-
- Disk is blank. -
-
- {{/if}} - {{else}} -
-
- No disk inserted. -
-
- {{/if}} -
-
- Options: -
-
- {{:helper.link('Wipe Disk', 'trash', {'bufferOption' : 'wipeDisk'}, (!data.hasDisk || !data.disk.data) ? 'disabled' : null)}} - {{:helper.link('Eject Disk', 'eject', {'bufferOption' : 'ejectDisk'}, !data.hasDisk ? 'disabled' : null)}} -
-
-
-{{else data.selectedMenuKey == 'rejuvenators'}} -

Rejuvenators

-
-
- Inject: -
-
- {{:helper.link('5', 'pencil', {'injectRejuvenators' : 5}, (!data.hasOccupant || !data.beakerVolume) ? 'disabled' : null)}} - {{:helper.link('10', 'pencil', {'injectRejuvenators' : 10}, (!data.hasOccupant || !data.beakerVolume) ? 'disabled' : null)}} - {{:helper.link('20', 'pencil', {'injectRejuvenators' : 20}, (!data.hasOccupant || !data.beakerVolume) ? 'disabled' : null)}} - {{:helper.link('30', 'pencil', {'injectRejuvenators' : 30}, (!data.hasOccupant || !data.beakerVolume) ? 'disabled' : null)}} - {{:helper.link('50', 'pencil', {'injectRejuvenators' : 50}, (!data.hasOccupant || !data.beakerVolume) ? 'disabled' : null)}} -
-
-
 
-
-
- Beaker: -
-
- {{if data.isBeakerLoaded}} - {{:data.beakerLabel ? data.beakerLabel : 'No label'}}
- {{if data.beakerVolume}} - {{:data.beakerVolume}} units remaining
- {{else}} - Beaker is empty - {{/if}} - {{else}} - No beaker loaded - {{/if}} -
-
- {{:helper.link('Eject Beaker', 'eject', {'ejectBeaker' : 1}, data.isBeakerLoaded ? null : 'disabled')}} -
-
-{{/if}} - -
 
- -{{if !data.selectedMenuKey || data.selectedMenuKey == 'ui' || data.selectedMenuKey == 'se'}} -

Radiation Emitter Settings

-
-
- Intensity: -
-
- {{:helper.link('-', null, {'radiationIntensity' : 0}, (data.radiationIntensity > 1) ? null : 'disabled')}} -
 {{:data.radiationIntensity}} 
- {{:helper.link('+', null, {'radiationIntensity' : 1}, (data.radiationIntensity < 10) ? null : 'disabled')}} -
-
-
-
- Duration: -
-
- {{:helper.link('-', null, {'radiationDuration' : 0}, (data.radiationDuration > 2) ? null : 'disabled')}} -
 {{:data.radiationDuration}} 
- {{:helper.link('+', null, {'radiationDuration' : 1}, (data.radiationDuration < 20) ? null : 'disabled')}} -
-
-
-
-   -
-
- {{:helper.link('Pulse Radiation', 'radiation', {'pulseRadiation' : 1}, !data.hasOccupant ? 'disabled' : null)}} -
-
-{{/if}} - -
 
- -
- -
-
- Occupant: -
-
- {{:helper.link('Eject Occupant', 'eject', {'ejectOccupant' : 1}, data.locked || !data.hasOccupant || data.irradiating ? 'disabled' : null)}} -
-
-
-
- Door Lock: -
-
- {{:helper.link('Engaged', 'locked', {'toggleLock' : 1}, data.locked ? 'selected' : !data.hasOccupant ? 'disabled' : null)}} - {{:helper.link('Disengaged', 'unlocked', {'toggleLock' : 1}, !data.locked ? 'selected' : data.irradiating ? 'disabled' : null)}} -
-
- -{{if data.irradiating}} -
-
-

Irradiating Subject

-

For {{:data.irradiating}} seconds.

-
-
-{{/if}} - diff --git a/nano/templates/operating.tmpl b/nano/templates/operating.tmpl deleted file mode 100644 index c53bd94c651..00000000000 --- a/nano/templates/operating.tmpl +++ /dev/null @@ -1,47 +0,0 @@ - - -{{if data.table}} -

Patient Information:

- {{if data.victim}} -
-
Name:
-
{{:data.victim.real_name}}
- -
Age:
-
{{:data.victim.age}}
- -
Blood Type:
-
{{:data.victim.b_type}}
-
-
-
-
Health:
-
{{:data.victim.health}}
- -
Brute Damage:
-
{{:data.victim.brute}}
- -
Toxins Damage:
-
{{:data.victim.tox}}
- -
Fire Damage:
-
{{:data.victim.burn}}
- -
Suffocation Damage:
-
{{:data.victim.oxy}}
- -
Patient Status:
-
{{:data.victim.stat}}
- -
Heartbeat Rate:
-
{{:data.victim.pulse}}
-
- {{else}} - No Patient Detected - {{/if}} -{{else}} - No Table Detected -{{/if}} diff --git a/nano/templates/sleeper.tmpl b/nano/templates/sleeper.tmpl deleted file mode 100644 index 885a1c025c1..00000000000 --- a/nano/templates/sleeper.tmpl +++ /dev/null @@ -1,102 +0,0 @@ -

Sleeper

-{{if !data.power}} -
-
- NO POWER -
- {{if data.occupant}} -
- {{:helper.link("Eject occupant", null, {'eject' : 0})}} -
- {{/if}} -{{else}} - {{if data.occupant}} -
-
- Occupant status: -
-
- Health: {{:helper.round(data.health / data.maxHealth)*100}}% ({{:data.stat}}). -
-
- Pulse: -
-
- {{:data.pulse}} -
-
- Brute damage: -
-
- {{:helper.displayBar(data.brute, 0, 100, (data.brute <= 25) ? 'good' : (data.brute <= 50) ? 'average' : 'bad')}}{{:helper.round(data.brute)}} -
-
- Burn severity: -
-
- {{:helper.displayBar(data.burn, 0, 100, (data.burn <= 25) ? 'good' : (data.burn <= 50) ? 'average' : 'bad')}}{{:helper.round(data.burn)}} -
-
- Respiratory damage: -
-
- {{:helper.displayBar(data.oxy, 0, 100, (data.oxy <= 25) ? 'good' : (data.oxy <= 50) ? 'average' : 'bad')}}{{:helper.round(data.oxy)}} -
-
- Toxin content: -
-
- {{:helper.displayBar(data.tox, 0, 100, (data.tox <= 25) ? 'good' : (data.tox <= 50) ? 'average' : 'bad')}}{{:helper.round(data.tox)}} -
-
- {{:helper.link(data.filtering ? "Dialysis active" : "Dialysis inactive", null, {'sleeper_filter' : !data.filtering})}} -
-
- {{:helper.link(data.pump ? "Stomach pump active" : "Stomach pump inactive", null, {'pump' : !data.pump})}} -
-
- {{:helper.link("Eject occupant", null, {'eject' : 0})}} -
-
- {{else}} -
- No occupant. -
- {{/if}} -
- {{for data.reagents}} -
- {{:value.name}} -
-
- {{if data.occupant}}Occupant: {{:value.amount}} units{{/if}} - {{:helper.link('Inject 5', null, {'chemical' : value.id, 'amount' : 5}, data.occupant ? null : 'disabled')}}{{:helper.link('Inject 10', null, {'chemical' : value.id, 'amount' : 10}, data.occupant ? null : 'disabled')}} -
- {{/for}} -
- {{if data.beaker != -1}} -
-
- Beaker: -
-
- {{:data.beaker}} units of free space remaining. - {{:helper.link("Eject", null, {'beaker' : 0})}} -
-
- {{else}} -
-
- No beaker inserted. -
-
- {{/if}} -
-
- Stasis Level: -
-
- {{:helper.link(data.stasis, null, {'change_stasis' : 1})}} -
-
-{{/if}} diff --git a/nano/templates/sleever.tmpl b/nano/templates/sleever.tmpl deleted file mode 100644 index 89b41551a1b..00000000000 --- a/nano/templates/sleever.tmpl +++ /dev/null @@ -1,170 +0,0 @@ -{{if data.coredumped}} -
- TransCore dump complete. Disk ejected. -
-{{else data.emergency}} -
- !!WARNING!!
Dump Disk Inserted! This will transfer all minds to the dump disk, and the TransCore will be made unusable until post-shift maintenance! This should only be used in emergencies!

-
- {{:helper.link('DUMP CORE', 'radiation', {'coredump' : data.emergency}, null, 'redButton')}} - {{:helper.link('Eject Disk', 'eject', {'ejectdisk' : data.emergency})}} -{{else}} - {{:data.temp}} - - - {{if data.menu == 1}} -

Resleeving Control

-
-
- -
- {{if data.podsLen > 0}} - {{:data.podsLen}} growing vats found. - {{/if}} -
- -
- {{if data.spodsLen > 0}} - {{:data.spodsLen}} SyntFabs found. - {{/if}} -
- -
- {{if data.sleeversLen > 0}} - {{:data.sleeversLen}} resleeving pods found. - {{/if}} -
- - {{if data.podsLen}} - {{for data.pods}} -
{{:value.pod}}, biomass: {{:value.biomass}}
- {{/for}} - {{/if}} - - {{if data.spodsLen}} - {{for data.spods}} -
{{:value.spod}}, S/G: {{:value.steel}}/{{:value.glass}}
- {{/for}} - {{/if}} - - {{if data.sleeversLen}} - {{for data.sleevers}} -
{{:value.sleever}}, occupant: {{:value.occupant}}
- {{/for}} - {{/if}} - - -

Database Functions

-
- {{:helper.link('View Body Records', 'list', {'menu' : 2})}} -
-
- {{:helper.link('View Mind Records', 'list', {'menu' : 3})}} -
- - - {{else data.menu == 2}} -

Current body records

- {{:helper.link('Back', 'arrowreturn-1-w', {'menu' : 1})}} -
- {{for data.bodyrecords}} - {{:helper.link(value.name, 'document', {'view_brec' : value.recref})}} - {{/for}} -
- - - {{else data.menu == 3}} -

Current mind records

- {{:helper.link('Back', 'arrowreturn-1-w', {'menu' : 1})}} -
- {{for data.mindrecords}} - {{:helper.link(value.name, 'document', {'view_mrec' : value.recref})}} - {{/for}} -
- - - {{else data.menu == 4}} -

Selected Body Record

-
{{:helper.link('Back', 'arrowreturn-1-w', {'menu' : 2})}}
- - {{if data.activeBodyRecord}} -
-
Name:
-
{{:data.activeBodyRecord.real_name}}
-
-
-
Species:
-
{{:data.activeBodyRecord.speciesname}}
-
-
-
Bio. Sex:
-
{{:data.activeBodyRecord.gender}}
-
-
-
Mind compat.:
-
{{:data.activeBodyRecord.locked}}
-
-
-
Synthetic:
-
{{:data.activeBodyRecord.synthetic}}
-
-
-
OOC Notes:
-
{{:helper.link('View', null, {'boocnotes' : data.activeBodyRecord.booc}, data.activeBodyRecord.booc ? null : 'linkOff')}}
-
- - {{:helper.link('Create', 'play', {'create' : data.activeBodyRecord.real_name}, data.activeBodyRecord.cando ? null : 'linkOff')}} - - {{else}} -
ERROR: Record not found.
- {{/if}} - - - {{else data.menu == 5}} -

Selected Mind Record

-
{{:helper.link('Back', 'arrowreturn-1-w', {'menu' : 3})}}
- - {{if data.activeMindRecord}} -
-
Name:
-
{{:data.activeMindRecord.charname}}
-
-
-
Backup Status:
-
{{:data.activeMindRecord.obviously_dead}}
-
-
-
OOC Notes:
-
{{:helper.link('View', null, {'moocnotes' : data.activeMindRecord.mooc}, data.activeMindRecord.mooc ? null : 'linkOff')}}
-
- {{:helper.link('Sleeve', 'play', {'sleeve' : 1}, data.activeMindRecord.cando ? null : 'linkOff')}} - {{:helper.link('Card', 'play', {'sleeve' : 2}, data.activeMindRecord.cando ? null : 'linkOff')}} - - {{else}} -
ERROR: Record not found.
- {{/if}} - - - {{else data.menu == 6}} -

Body OOC Notes (This is OOC!)

-
{{:helper.link('Back', 'arrowreturn-1-w', {'menu' : 4})}}
- {{if data.activeBodyRecord}} -
Notes:
-
{{:data.activeBodyRecord.booc}}
- {{else}} -
ERROR: Record not found.
- {{/if}} - - - {{else data.menu == 7}} -

Mind OOC Notes (This is OOC!)

-
{{:helper.link('Back', 'arrowreturn-1-w', {'menu' : 5})}}
- {{if data.activeMindRecord}} -
Notes:
-
{{:data.activeMindRecord.mooc}}
- {{else}} -
ERROR: Record not found.
- {{/if}} - - {{/if}} -{{/if}} \ No newline at end of file diff --git a/tgui/packages/tgui/components/Box.js b/tgui/packages/tgui/components/Box.js index 7e8aca3c12b..a53fa1e8fda 100644 --- a/tgui/packages/tgui/components/Box.js +++ b/tgui/packages/tgui/components/Box.js @@ -73,7 +73,10 @@ const mapColorPropTo = attrName => (style, value) => { const styleMapperByPropName = { // Direct mapping + display: mapRawPropTo('display'), position: mapRawPropTo('position'), + float: mapRawPropTo('float'), + clear: mapRawPropTo('clear'), overflow: mapRawPropTo('overflow'), overflowX: mapRawPropTo('overflow-x'), overflowY: mapRawPropTo('overflow-y'), @@ -99,6 +102,8 @@ const styleMapperByPropName = { opacity: mapRawPropTo('opacity'), textAlign: mapRawPropTo('text-align'), verticalAlign: mapRawPropTo('vertical-align'), + textTransform: mapRawPropTo('text-transform'), + wordWrap: mapRawPropTo('word-wrap'), // Boolean props inline: mapBooleanPropTo('display', 'inline-block'), bold: mapBooleanPropTo('font-weight', 'bold'), @@ -136,6 +141,18 @@ const styleMapperByPropName = { color: mapColorPropTo('color'), textColor: mapColorPropTo('color'), backgroundColor: mapColorPropTo('background-color'), + // Flex props + order: mapRawPropTo('order'), + flexDirection: mapRawPropTo('flex-direction'), + flexGrow: mapRawPropTo('flex-grow'), + flexShrink: mapRawPropTo('flex-shrink'), + flexWrap: mapRawPropTo('flex-wrap'), + flexFlow: mapRawPropTo('flex-flow'), + flexBasis: mapRawPropTo('flex-basis'), + flex: mapRawPropTo('flex'), + alignItems: mapRawPropTo('align-items'), + justifyContent: mapRawPropTo('justify-content'), + alignSelf: mapRawPropTo('align-self'), // Utility props fillPositionedParent: (style, value) => { if (value) { @@ -151,6 +168,9 @@ const styleMapperByPropName = { export const computeBoxProps = props => { const computedProps = {}; const computedStyles = {}; + if (props.double) { + computedStyles["transform"] = "scale(2);"; + } // Compute props for (let propName of Object.keys(props)) { if (propName === 'style') { diff --git a/tgui/packages/tgui/components/Flex.js b/tgui/packages/tgui/components/Flex.js index 02d2fac3148..1a65d9a3ea9 100644 --- a/tgui/packages/tgui/components/Flex.js +++ b/tgui/packages/tgui/components/Flex.js @@ -13,9 +13,11 @@ export const computeFlexProps = props => { direction, wrap, align, + alignContent, justify, inline, spacing = 0, + spacingPrecise = 0, ...rest } = props; return { @@ -28,6 +30,7 @@ export const computeFlexProps = props => { ), inline && 'Flex--inline', spacing > 0 && 'Flex--spacing--' + spacing, + spacingPrecise > 0 && 'Flex--spacingPrecise--' + spacingPrecise, className, ]), style: { @@ -35,6 +38,7 @@ export const computeFlexProps = props => { 'flex-direction': direction, 'flex-wrap': wrap, 'align-items': align, + 'align-content': alignContent, 'justify-content': justify, }, ...rest, diff --git a/tgui/packages/tgui/components/Input.js b/tgui/packages/tgui/components/Input.js index 3c73d88a8fd..b7f2dcb1f15 100644 --- a/tgui/packages/tgui/components/Input.js +++ b/tgui/packages/tgui/components/Input.js @@ -76,6 +76,11 @@ export class Input extends Component { const input = this.inputRef.current; if (input) { input.value = toInputValue(nextValue); + if (this.props.autofocus) { + input.focus(); + input.selectionStart = 0; + input.selectionEnd = input.value.length; + } } } @@ -104,6 +109,7 @@ export class Input extends Component { value, maxLength, placeholder, + autofocus, ...boxProps } = props; // Box props diff --git a/tgui/packages/tgui/components/Knob.js b/tgui/packages/tgui/components/Knob.js index 4861e71bf3e..3bfa471da14 100644 --- a/tgui/packages/tgui/components/Knob.js +++ b/tgui/packages/tgui/components/Knob.js @@ -40,6 +40,7 @@ export const Knob = props => { size, bipolar, children, + popUpPosition, ...rest } = props; return ( @@ -107,7 +108,10 @@ export const Knob = props => {
{dragging && ( -
+
{displayElement}
)} diff --git a/tgui/packages/tgui/components/LabeledList.js b/tgui/packages/tgui/components/LabeledList.js index e2b515014a2..43dbd0d5a32 100644 --- a/tgui/packages/tgui/components/LabeledList.js +++ b/tgui/packages/tgui/components/LabeledList.js @@ -20,6 +20,7 @@ export const LabeledListItem = props => { labelColor = 'label', color, textAlign, + verticalAlign, buttons, content, children, @@ -33,6 +34,7 @@ export const LabeledListItem = props => { { as="td" color={color} textAlign={textAlign} + verticalAlign={verticalAlign} className={classes([ 'LabeledList__cell', 'LabeledList__content', diff --git a/tgui/packages/tgui/components/Modal.js b/tgui/packages/tgui/components/Modal.js index 916150de402..ee700ebd0e5 100644 --- a/tgui/packages/tgui/components/Modal.js +++ b/tgui/packages/tgui/components/Modal.js @@ -6,10 +6,21 @@ export const Modal = props => { const { className, children, + onEnter, ...rest } = props; + let handleKeyDown; + if (onEnter) { + handleKeyDown = e => { + let key = e.which || e.keyCode; + if (key === 13) { + onEnter(e); + } + }; + } return ( - +
{ level = 1, buttons, fill, + stretchContents, + noTopPadding, children, + scrollable, + flexGrow, ...rest } = props; const hasTitle = !isFalsy(title) || !isFalsy(buttons); @@ -25,6 +29,7 @@ export const Section = props => { 'Section', 'Section--level--' + level, fill && 'Section--fill', + flexGrow && 'Section--flex', className, ...computeBoxClassName(rest), ])} @@ -40,7 +45,11 @@ export const Section = props => {
)} {hasContent && ( -
+
{children}
)} diff --git a/tgui/packages/tgui/interfaces/BodyScanner.js b/tgui/packages/tgui/interfaces/BodyScanner.js new file mode 100644 index 00000000000..fda651e5a0f --- /dev/null +++ b/tgui/packages/tgui/interfaces/BodyScanner.js @@ -0,0 +1,467 @@ +import { round } from 'common/math'; +import { Fragment } from 'inferno'; +import { useBackend } from "../backend"; +import { AnimatedNumber, Box, Button, Flex, Icon, LabeledList, ProgressBar, Section, Table, Tooltip } from "../components"; +import { Window } from "../layouts"; + +import { createLogger } from '../logging'; + +const debugBodyScannerLogger = createLogger('debugBodyScanner'); + +const stats = [ + ['good', 'Alive'], + ['average', 'Unconscious'], + ['bad', 'DEAD'], +]; + +const abnormalities = [ + ['hasBorer', 'bad', occupant => 'Large growth detected in frontal lobe,' + + ' possibly cancerous. Surgical removal is recommended.'], + ['hasVirus', 'bad', occupant => 'Viral pathogen detected in blood stream.'], + ['blind', 'average', occupant => 'Cataracts detected.'], + ['colourblind', 'average', occupant => + 'Photoreceptor abnormalities detected.'], + ['nearsighted', 'average', occupant => 'Retinal misalignment detected.'], + /* VOREStation Add */ + ['humanPrey', 'average', occupant => { + return 'Foreign Humanoid(s) detected: ' + occupant.humanPrey; + }], + ['livingPrey', 'average', occupant => { + return 'Foreign Creature(s) detected: ' + occupant.livingPrey; + }], + ['objectPrey', 'average', occupant => { + return 'Foreign Object(s) detected: ' + occupant.objectPrey; + }], + /* VOREStation Add End */ +]; + +const damages = [ + ['Respiratory', 'oxyLoss'], + ['Brain', 'brainLoss'], + ['Toxin', 'toxLoss'], + ['Radioactive', 'radLoss'], + ['Brute', 'bruteLoss'], + ['Genetic', 'cloneLoss'], + ['Burn', 'fireLoss'], + ['Paralysis', 'paralysis'], +]; + +const damageRange = { + average: [0.25, 0.5], + bad: [0.5, Infinity], +}; + +const mapTwoByTwo = (a, c) => { + let result = []; + for (let i = 0; i < a.length; i += 2) { + result.push(c(a[i], a[i + 1], i)); + } + return result; +}; + +const reduceOrganStatus = A => { + return A.length > 0 + ? A.reduce((a, s) => + a === null + ? s : ( + + {a} + {!!s && ( + + {s} + {s.length > 0 &&
} +
+ )} +
+ )) + : null; +}; + +const germStatus = i => { + if (i > 100) { + if (i < 300) { return "mild infection"; } + if (i < 400) { return "mild infection+"; } + if (i < 500) { return "mild infection++"; } + if (i < 700) { return "acute infection"; } + if (i < 800) { return "acute infection+"; } + if (i < 900) { return "acute infection++"; } + if (i >= 900) { return "septic"; } + } + + return ""; +}; + +export const BodyScanner = (props, context) => { + const { data } = useBackend(context); + const { + occupied, + occupant = {}, + } = data; + const body = occupied ? ( + + ) : ( + + ); + return ( + + + {body} + + + ); +}; + +const BodyScannerMain = props => { + const { + occupant, + } = props; + return ( + + + + + + + + ); +}; + +const BodyScannerMainOccupant = (props, context) => { + const { act, data } = useBackend(context); + const { + occupant, + } = data; + return ( +
+ + + + )}> + + + {occupant.name} + + + + + + {stats[occupant.stat][1]} + + + °C,  + °F + + {/* VOREStation Add */} + + {round(data.occupant.weight) + "lbs, " + + round(data.occupant.weight/2.20463) + "kgs"} + + {/* VOREStation Add End */} + +
+ ); +}; + +const BodyScannerMainAbnormalities = props => { + const { + occupant, + } = props; + + let hasAbnormalities = occupant.hasBorer + || occupant.blind + || occupant.colourblind + || occupant.nearsighted + || occupant.hasVirus; + + /* VOREStation Add */ + hasAbnormalities = hasAbnormalities + || occupant.humanPrey + || occupant.livingPrey + || occupant.objectPrey; + /* VOREStation Add End */ + + if (!hasAbnormalities) { + return ( +
+ + No abnormalities found. + +
+ ); + } + + return ( +
+ {abnormalities.map((a, i) => { + if (occupant[a[0]]) { + return ( + + {a[2](occupant)} + + ); + } + })} +
+ ); +}; + +const BodyScannerMainDamage = props => { + const { + occupant, + } = props; + return ( +
+ + {mapTwoByTwo(damages, (d1, d2, i) => ( + + + + {d1[0]}: + + + {!!d2 && d2[0] + ":"} + + + + + + + + {!!d2 && ( + + )} + + + + ))} +
+
+ ); +}; + +const BodyScannerMainDamageBar = props => { + return ( + + {round(props.value, 0)} + + ); +}; + +const BodyScannerMainOrgansExternal = props => { + if (props.organs.length === 0) { + return ( +
+ + N/A + +
+ ); + } + + return ( +
+ + + + Name + + + Damage + + + Injuries + + + {props.organs.map((o, i) => ( + + + {o.name} + + + 0 && "0.5rem"} + value={o.totalLoss / 100} + ranges={damageRange}> + + {!!o.bruteLoss && ( + + + {round(o.bruteLoss, 0)}  + + )} + {!!o.fireLoss && ( + + + {round(o.fireLoss, 0)} + + )} + + + {round(o.totalLoss, 0)} + + + + + + {reduceOrganStatus([ + o.internalBleeding && "Internal bleeding", + o.lungRuptured && "Ruptured lung", + o.destroyed && "Destroyed", + !!o.status.broken && o.status.broken, + germStatus(o.germ_level), + !!o.open && "Open incision", + ])} + + + {reduceOrganStatus([ + !!o.status.splinted && "Splinted", + !!o.status.robotic && "Robotic", + !!o.status.dead && ( + + DEAD + + ), + ])} + {reduceOrganStatus(o.implants.map( + s => s.known + ? s.name + : "Unknown object" + ))} + + + + ))} +
+
+ ); +}; + +const BodyScannerMainOrgansInternal = props => { + if (props.organs.length === 0) { + return ( +
+ + N/A + +
+ ); + } + + return ( +
+ + + + Name + + + Damage + + + Injuries + + + {props.organs.map((o, i) => ( + + + {o.name} + + + 0 && "0.5rem"} + ranges={damageRange}> + {round(o.damage, 0)} + + + + + {reduceOrganStatus([ + germStatus(o.germ_level), + ])} + + + {reduceOrganStatus([ + (o.robotic === 1) && "Robotic", + (o.robotic === 2) && "Assisted", + !!o.dead && ( + + DEAD + + ), + ])} + + + + ))} +
+
+ ); +}; + +const BodyScannerEmpty = () => { + return ( +
+ + +
+ No occupant detected. +
+
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/ChemDispenser.js b/tgui/packages/tgui/interfaces/ChemDispenser.js new file mode 100644 index 00000000000..9de924b1a03 --- /dev/null +++ b/tgui/packages/tgui/interfaces/ChemDispenser.js @@ -0,0 +1,172 @@ +import { Fragment } from 'inferno'; +import { useBackend } from "../backend"; +import { Box, Button, Flex, LabeledList, ProgressBar, Slider, Section } from "../components"; +import { BeakerContents } from "../interfaces/common/BeakerContents"; +import { Window } from "../layouts"; + +const dispenseAmounts = [5, 10, 20, 30, 40]; +const removeAmounts = [1, 5, 10]; + +export const ChemDispenser = (props, context) => { + return ( + + + + + + + + ); +}; + +const ChemDispenserSettings = (properties, context) => { + const { act, data } = useBackend(context); + const { + amount, + } = data; + return ( +
+ + + + {dispenseAmounts.map((a, i) => ( + +
+ ); +}; + +const ChemDispenserChemicals = (properties, context) => { + const { act, data } = useBackend(context); + const { + chemicals = [], + } = data; + const flexFillers = []; + for (let i = 0; i < (chemicals.length + 1) % 3; i++) { + flexFillers.push(true); + } + return ( +
+ + {chemicals.map((c, i) => ( + +
+ ); +}; + +const ChemDispenserBeaker = (properties, context) => { + const { act, data } = useBackend(context); + const { + isBeakerLoaded, + beakerCurrentVolume, + beakerMaxVolume, + beakerContents = [], + } = data; + return ( +
+ {!!isBeakerLoaded && ( + + {beakerCurrentVolume} / {beakerMaxVolume} units + + )} +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/ChemMaster.js b/tgui/packages/tgui/interfaces/ChemMaster.js new file mode 100644 index 00000000000..2ef1b6f9636 --- /dev/null +++ b/tgui/packages/tgui/interfaces/ChemMaster.js @@ -0,0 +1,402 @@ +import { Fragment } from 'inferno'; +import { useBackend } from "../backend"; +import { Box, Button, Flex, Icon, LabeledList, Section } from "../components"; +import { Window } from "../layouts"; +import { BeakerContents } from './common/BeakerContents'; +import { ComplexModal, modalOpen, modalRegisterBodyOverride } from './common/ComplexModal'; + +const transferAmounts = [1, 5, 10]; +const bottleStyles = [ + "bottle.png", + "small_bottle.png", + "wide_bottle.png", + "round_bottle.png", + "reagent_bottle.png", +]; + +const analyzeModalBodyOverride = (modal, context) => { + const { act, data } = useBackend(context); + const result = modal.args.analysis; + return ( +
+ + + + {result.name} + + + {(result.desc || "").length > 0 ? result.desc : "N/A"} + + {result.blood_type && ( + + + {result.blood_type} + + + {result.blood_dna} + + + )} + {!data.condi && ( +
+ ); +}; + +export const ChemMaster = (props, context) => { + const { data } = useBackend(context); + const { + condi, + beaker, + beaker_reagents = [], + buffer_reagents = [], + mode, + } = data; + return ( + + + + 0} + /> + + 0} + /> + + + + ); +}; + +const ChemMasterBeaker = (props, context) => { + const { act } = useBackend(context); + const { + beaker, + beakerReagents, + bufferNonEmpty, + } = props; + return ( +
act('eject')} + /> + :
+ ); +}; + +const ChemMasterBuffer = (props, context) => { + const { act } = useBackend(context); + const { + mode, + bufferReagents = [], + } = props; + return ( +
+ Transferring to  +
+ ); +}; + +const ChemMasterProduction = (props, context) => { + const { act } = useBackend(context); + if (!props.bufferNonEmpty) { + return ( +
+ + +
+ Buffer is empty. +
+
+
+ ); + } + + return ( +
+ {!props.isCondiment ? ( + + ) : ( + + )} +
+ ); +}; + +const ChemMasterProductionChemical = (props, context) => { + const { act, data } = useBackend(context); + return ( + + + + + + + + + ); +}; + +const ChemMasterProductionCondiment = (props, context) => { + const { act } = useBackend(context); + return ( + + + )}> + {hasOccupant ? ( + + + {occupant.name || "Unknown"} + + + 0 ? 'good' : 'average'}> + + + + + {statNames[occupant.stat][1]} + + + + {' K'} + + + {(damageTypes.map(damageType => ( + + + + + + )))} + + ) : ( + + +
+ No occupant detected. +
+
+ )} + +
act('ejectBeaker')} + disabled={!isBeakerLoaded}> + Eject Beaker + + )}> + + + + + + K + + + + + +
+
+ ); +}; + +const CryoBeaker = (props, context) => { + const { act, data } = useBackend(context); + const { + isBeakerLoaded, + beakerLabel, + beakerVolume, + } = data; + if (isBeakerLoaded) { + return ( + + {beakerLabel + ? beakerLabel + : ( + + No label + + )} + + {beakerVolume ? ( + Math.round(v) + " units remaining"} + /> + ) : "Beaker is empty"} + + + ); + } else { + return ( + + No beaker loaded + + ); + } +}; diff --git a/tgui/packages/tgui/interfaces/DNAModifier.js b/tgui/packages/tgui/interfaces/DNAModifier.js new file mode 100644 index 00000000000..2b95dbfd455 --- /dev/null +++ b/tgui/packages/tgui/interfaces/DNAModifier.js @@ -0,0 +1,714 @@ +import { Fragment } from 'inferno'; +import { useBackend } from "../backend"; +import { Box, Button, Dimmer, Flex, Icon, Knob, LabeledList, ProgressBar, Section, Tabs } from "../components"; +import { Window } from "../layouts"; +import { ComplexModal } from './common/ComplexModal'; + +const stats = [ + ['good', 'Alive'], + ['average', 'Unconscious'], + ['bad', 'DEAD'], +]; + +const operations = [ + ['ui', 'Modify U.I.', 'dna'], + ['se', 'Modify S.E.', 'dna'], + ['buffer', 'Transfer Buffers', 'syringe'], + ['rejuvenators', 'Rejuvenators', 'flask'], +]; + +const rejuvenatorsDoses = [5, 10, 20, 30, 50]; + +export const DNAModifier = (props, context) => { + const { act, data } = useBackend(context); + const { + irradiating, + dnaBlockSize, + occupant, + } = data; + context.dnaBlockSize = dnaBlockSize; + context.isDNAInvalid = !occupant.isViableSubject + || !occupant.uniqueIdentity + || !occupant.structuralEnzymes; + let radiatingModal; + if (irradiating) { + radiatingModal = ; + } + return ( + + + {radiatingModal} + + + + + + ); +}; + +const DNAModifierOccupant = (props, context) => { + const { act, data } = useBackend(context); + const { + locked, + hasOccupant, + occupant, + } = data; + return ( +
+ + Door Lock: + +
+ ); +}; + +const DNAModifierMain = (props, context) => { + const { act, data } = useBackend(context); + const { + selectedMenuKey, + hasOccupant, + occupant, + } = data; + if (!hasOccupant) { + return ( +
+ + +
+ No occupant in DNA modifier. +
+
+
+ ); + } else if (context.isDNAInvalid) { + return ( +
+ + +
+ No operation possible on this subject. +
+
+
+ ); + } + let body; + if (selectedMenuKey === "ui") { + body = ( + + + + + ); + } else if (selectedMenuKey === "se") { + body = ( + + + + + ); + } else if (selectedMenuKey === "buffer") { + body = ; + } else if (selectedMenuKey === "rejuvenators") { + body = ; + } + return ( +
+ + {operations.map((op, i) => ( + act('selectMenuKey', { key: op[0] })}> + + {op[1]} + + ))} + + {body} +
+ ); +}; + +const DNAModifierMainUI = (props, context) => { + const { act, data } = useBackend(context); + const { + selectedUIBlock, + selectedUISubBlock, + selectedUITarget, + occupant, + } = data; + return ( +
+ + + + value.toString(16).toUpperCase()} + ml="0" + onChange={(e, val) => act('changeUITarget', { value: val })} + /> + + +
+ ); +}; + +const DNAModifierMainSE = (props, context) => { + const { act, data } = useBackend(context); + const { + selectedSEBlock, + selectedSESubBlock, + occupant, + } = data; + return ( +
+ +
+ ); +}; + +const DNAModifierMainRadiationEmitter = (props, context) => { + const { act, data } = useBackend(context); + const { + radiationIntensity, + radiationDuration, + } = data; + return ( +
+ + + act('radiationIntensity', { value: val })} + /> + + + act('radiationDuration', { value: val })} + /> + + +
+ ); +}; + +const DNAModifierMainBuffers = (props, context) => { + const { act, data } = useBackend(context); + const { + buffers, + } = data; + let bufferElements = buffers.map((buffer, i) => ( + + )); + return ( + +
+ {bufferElements} +
+ +
+ ); +}; + +const DNAModifierMainBuffersElement = (props, context) => { + const { act, data } = useBackend(context); + const { + id, + name, + buffer, + } = props; + const isInjectorReady = data.isInjectorReady; + const realName = name + (buffer.data ? ' - ' + buffer.label : ''); + return ( + +
+ act('bufferOption', { + option: 'clear', + id: id, + })} + /> +
+
+ ); +}; + +const DNAModifierMainBuffersDisk = (props, context) => { + const { act, data } = useBackend(context); + const { + hasDisk, + disk, + } = data; + return ( +
+ act('wipeDisk')} + /> +
+ ); +}; + +const DNAModifierMainRejuvenators = (props, context) => { + const { act, data } = useBackend(context); + const { + isBeakerLoaded, + beakerVolume, + beakerLabel, + } = data; + return ( +
act('ejectBeaker')} + /> + }> + {isBeakerLoaded ? ( + + + {rejuvenatorsDoses.map((a, i) => ( +
+ ); +}; + +const DNAModifierIrradiating = (props, context) => { + return ( + +
+ +

+ +  Irradiating occupant  + +

+
+ +

+ For {props.duration} second{props.duration === 1 ? "" : "s"} +

+
+
+ ); +}; + +const DNAModifierBlocks = (props, context) => { + const { act, data } = useBackend(context); + const { + dnaString, + selectedBlock, + selectedSubblock, + blockSize, + action, + } = props; + + const characters = dnaString.split(''); + let curBlock = 0; + let dnaBlocks = []; + for (let block = 0; block < characters.length; block += blockSize) { + const realBlock = block / blockSize + 1; + let subBlocks = []; + for (let subblock = 0; subblock < blockSize; subblock++) { + const realSubblock = subblock + 1; + subBlocks.push( + + + ))} + + ); + } else if (type === "boolean") { + modalFooter = ( + +