diff --git a/code/__defines/machinery.dm b/code/__defines/machinery.dm index dbe66b1243..37d495c3b6 100644 --- a/code/__defines/machinery.dm +++ b/code/__defines/machinery.dm @@ -161,3 +161,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/sound.dm b/code/__defines/sound.dm index f155f5c736..3c4567bef4 100644 --- a/code/__defines/sound.dm +++ b/code/__defines/sound.dm @@ -170,7 +170,7 @@ 'sound/ambience/foreboding/foreboding3.ogg',\ 'sound/ambience/foreboding/foreboding4.ogg',\ 'sound/ambience/foreboding/foreboding5.ogg',\ - 'sound/ambience/foreboding/foreboding6.ogg'\ + 'sound/ambience/maintenance/maintenance6.ogg'\ ) // Ambience heard when aboveground on Sif and not in a Point of Interest. diff --git a/code/__defines/tgui.dm b/code/__defines/tgui.dm index a6708c8bb7..7f5ceec8b9 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 0d6598d2f0..7a0d90d650 100644 --- a/code/_helpers/game.dm +++ b/code/_helpers/game.dm @@ -635,4 +635,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 871b4dd4bc..909b0792f6 100644 --- a/code/datums/datacore.dm +++ b/code/datums/datacore.dm @@ -319,6 +319,19 @@ var/global/list/PDA_Manifest = list() var/datum/job/J = SSjob.get_job(assignment) hidden = J?.offmap_spawn + /* Note: Due to cached_character_icon, a number of emergent properties occur due to the initialization + * order of readied-up vs latejoiners. Namely, latejoiners will get a uniform in their datacore picture, but readied-up will + * not. This is due to the fact that SSticker calls data_core.manifest_inject() inside of ticker/proc/create_characters(), + * but does not equip them until ticker/proc/equip_characters(), which is called later. So, this proc is literally called before + * they ever get their equipment, and so it can't get a picture of them in their equipment. + * Latejoiners do not have this problem, because /mob/new_player/proc/AttemptLateSpawn calls EquipRank() before it calls + * this proc, which means that they're already clothed by the time they get their picture taken here. + * The COMPILE_OVERLAYS() here is just to bypass SSoverlays taking for-fucking-ever to update the mob, since we're about to + * take a picture of them, we want all the overlays. + */ + COMPILE_OVERLAYS(H) + SSoverlays.queue -= H + var/id = generate_record_id() //General Record var/datum/data/record/G = CreateGeneralRecord(H, id, hidden) @@ -405,8 +418,8 @@ var/global/list/PDA_Manifest = list() var/icon/side if(H) var/icon/charicon = cached_character_icon(H) - front = icon(charicon, dir = SOUTH) - side = icon(charicon, dir = WEST) + front = icon(charicon, dir = SOUTH, frame = 1) + side = icon(charicon, dir = WEST, frame = 1) else // Sending null things through browse_rsc() makes a runtime and breaks the console trying to view the record. front = icon('html/images/no_image32.png') side = icon('html/images/no_image32.png') @@ -432,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/antagonist/antagonist_update.dm b/code/game/antagonist/antagonist_update.dm index 16c27f82c7..b118cb91db 100644 --- a/code/game/antagonist/antagonist_update.dm +++ b/code/game/antagonist/antagonist_update.dm @@ -15,7 +15,7 @@ spawn(3) var/mob/living/carbon/human/H = player.current if(istype(H)) - H.change_appearance(APPEARANCE_ALL, H.loc, H, species_whitelist = valid_species, state = z_state) + H.change_appearance(APPEARANCE_ALL, H, species_whitelist = valid_species, state = GLOB.tgui_self_state) return player.current /datum/antagonist/proc/update_access(var/mob/living/player) diff --git a/code/game/atoms.dm b/code/game/atoms.dm index f9b4d9dbc5..85ada387a6 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 @@ -589,3 +591,20 @@ >> "} + +/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 b9b8f6db8a..eb0ad7000c 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 TRUE if(!istype(usr.loc, /turf)) - return 0 // don't update uis + return TRUE if(!src || !src.connected) - return 0 // don't update uis + return TRUE if(irradiating) // Make sure that it isn't already irradiating someone... - return 0 // don't update uis + return TRUE 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 974ba754a6..ce2ae90bce 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 = -101 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 TRUE + 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 758ac976a0..c405ddf1f6 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) + var/printing_text = null /obj/machinery/bodyscanner/Initialize() . = ..() @@ -58,13 +60,14 @@ playsound(src, 'sound/machines/medbayscanner1.ogg', 50) // Beepboop you're being scanned. <3 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()) @@ -101,6 +104,7 @@ icon_state = "body_scanner_1" playsound(src, 'sound/machines/medbayscanner1.ogg', 50) // Beepboop you're being scanned. <3 add_fingerprint(user) + SStgui.update_uis(src) /obj/machinery/bodyscanner/relaymove(mob/user as mob) if(user.incapacitated()) @@ -125,7 +129,8 @@ occupant.client.perspective = MOB_PERSPECTIVE occupant.loc = src.loc occupant = null - icon_state = "body_scanner_0" + icon_state = "body_scanner_1" + SStgui.update_uis(src) return /obj/machinery/bodyscanner/ex_act(severity) @@ -159,10 +164,362 @@ else return +/obj/machinery/bodyscanner/tgui_host(mob/user) + if(user == occupant) + return src + return console ? console : src + +/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)) + icon_state = "body_scanner_1" + 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, + "overdose" = (R.overdose && R.volume > R.overdose) ? TRUE : FALSE, + ) + 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, + "overdose" = (R.overdose && R.volume > R.overdose) ? TRUE : FALSE, + ) + 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) + data["occupant"] = occupantData + + return data + +/obj/machinery/bodyscanner/tgui_act(action, params) + if(..()) + return TRUE + + . = 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.") + playsound(src, 'sound/machines/printer.ogg', 50, 1) + 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." + + return 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) var/delete var/temphtml name = "Body Scanner Console" @@ -173,7 +530,6 @@ anchored = 1 circuit = /obj/item/weapon/circuitboard/scanner_console var/printing = null - var/printing_text = null /obj/machinery/body_scanconsole/New() ..() @@ -202,6 +558,7 @@ else return attack_hand(user) + /obj/machinery/body_scanconsole/power_change() if(stat & BROKEN) icon_state = "body_scannerconsole-p" @@ -256,343 +613,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)) - 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, "overdose" = ((R.overdose && R.volume > R.overdose) ? "Overdose" : "")) - 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, "overdose" = ((R.overdose && R.volume > R.overdose) ? "Overdose" : "")) - 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) - - 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) diff --git a/code/game/machinery/cloning.dm b/code/game/machinery/cloning.dm index bbc1cdeebc..39b5748762 100644 --- a/code/game/machinery/cloning.dm +++ b/code/game/machinery/cloning.dm @@ -24,6 +24,7 @@ return selected #define CLONE_BIOMASS 60 +#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 5caf9edd6e..c965a54b15 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 TRUE + 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 / victim.maxHealth) * 100) <= healthAlarm) + atom_say("[round(((victim.health / victim.maxHealth) * 100))]% 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 30c15f6d04..cc05d63217 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,304 @@ 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 + + . = 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") + playsound(src, 'sound/machines/medbayscanner1.ogg', 100, 1) + 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 +431,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 +450,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 d3daba4b5e..f3c930fe78 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,216 @@ 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" = "\ref[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 TRUE - 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"]) + if(!istype(v)) + return FALSE + tgui_modal_message(src, "virus", "", null, v.fields["tgui_description"]) + 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 +310,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["header"]]
[c["text"]]
" + 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)) @@ -554,4 +500,7 @@ icon_keyboard = "laptop_key" icon_screen = "medlaptop" circuit = /obj/item/weapon/circuitboard/med_data/laptop - density = 0 \ No newline at end of file + density = 0 + +#undef FIELD +#undef MED_FIELD diff --git a/code/game/machinery/cryo.dm b/code/game/machinery/cryo.dm index ad77b32cc0..b1c6d68615 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 TRUE - 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 + 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 FALSE // don't update UIs attached to this object + go_out() + else + return FALSE add_fingerprint(usr) - return 1 // update UIs attached to this object + return TRUE /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 @@ -208,7 +189,7 @@ var/mob/M = grab.affecting qdel(grab) put_mob(M) - + return /obj/machinery/atmospherics/unary/cryo_cell/MouseDrop_T(var/mob/target, var/mob/user) //Allows borgs to put people into cryo without external assistance @@ -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/game/objects/items/weapons/cosmetics.dm b/code/game/objects/items/weapons/cosmetics.dm index b7b5628dcd..5e93f2d031 100644 --- a/code/game/objects/items/weapons/cosmetics.dm +++ b/code/game/objects/items/weapons/cosmetics.dm @@ -97,17 +97,16 @@ w_class = ITEMSIZE_TINY icon = 'icons/obj/items.dmi' icon_state = "trinketbox" - var/list/ui_users = list() + var/datum/tgui_module/appearance_changer/mirror/coskit/M + +/obj/item/weapon/makeover/Initialize() + . = ..() + M = new(src, null) /obj/item/weapon/makeover/attack_self(mob/living/carbon/user as mob) if(ishuman(user)) to_chat(user, "You flip open \the [src] and begin to adjust your appearance.") - var/datum/nano_module/appearance_changer/AC = ui_users[user] - if(!AC) - AC = new(src, user) - AC.name = "SalonPro Porta-Makeover Deluxe™" - ui_users[user] = AC - AC.ui_interact(user) + M.tgui_interact(user) var/mob/living/carbon/human/H = user var/obj/item/organ/internal/eyes/E = H.internal_organs_by_name[O_EYES] if(istype(E)) diff --git a/code/game/objects/structures/ghost_pods/human.dm b/code/game/objects/structures/ghost_pods/human.dm index 2eda9b6c37..246c4b3e65 100644 --- a/code/game/objects/structures/ghost_pods/human.dm +++ b/code/game/objects/structures/ghost_pods/human.dm @@ -121,7 +121,7 @@ H.adjustBruteLoss(rand(1,20)) if(allow_appearance_change) - H.change_appearance(APPEARANCE_ALL, H.loc, check_species_whitelist = 1) + H.change_appearance(APPEARANCE_ALL, H, check_species_whitelist = 1) visible_message("\The [src] [pick("gurgles", "seizes", "clangs")] before releasing \the [H]!") @@ -241,6 +241,6 @@ H.adjustBruteLoss(rand(1,20)) if(allow_appearance_change) - H.change_appearance(APPEARANCE_ALL, H.loc, check_species_whitelist = 1) + H.change_appearance(APPEARANCE_ALL, H, check_species_whitelist = 1) visible_message("\The [src] [pick("gurgles", "seizes", "clangs")] before releasing \the [H]!") diff --git a/code/game/objects/structures/mirror.dm b/code/game/objects/structures/mirror.dm index 4705b39276..1473c8fe90 100644 --- a/code/game/objects/structures/mirror.dm +++ b/code/game/objects/structures/mirror.dm @@ -7,10 +7,11 @@ density = 0 anchored = 1 var/shattered = 0 - var/list/ui_users = list() var/glass = 1 + var/datum/tgui_module/appearance_changer/mirror/M /obj/structure/mirror/New(var/loc, var/dir, var/building = 0, mob/user as mob) + M = new(src, null) if(building) glass = 0 icon_state = "mirror_frame" @@ -18,17 +19,16 @@ pixel_y = (dir & 3)? (dir == 1 ? -30 : 30) : 0 return +/obj/structure/mirror/Destroy() + QDEL_NULL(M) + . = ..() + /obj/structure/mirror/attack_hand(mob/user as mob) if(!glass) return if(shattered) return if(ishuman(user)) - var/datum/nano_module/appearance_changer/AC = ui_users[user] - if(!AC) - AC = new(src, user) - AC.name = "SalonPro Nano-Mirror™" - ui_users[user] = AC - AC.ui_interact(user) + M.tgui_interact(user) /obj/structure/mirror/proc/shatter() if(!glass) return diff --git a/code/modules/admin/verbs/change_appearance.dm b/code/modules/admin/verbs/change_appearance.dm index e547d03f84..2f7166a2c0 100644 --- a/code/modules/admin/verbs/change_appearance.dm +++ b/code/modules/admin/verbs/change_appearance.dm @@ -9,7 +9,7 @@ if(!H) return log_and_message_admins("is altering the appearance of [H].") - H.change_appearance(APPEARANCE_ALL, usr, usr, check_species_whitelist = 0, state = admin_state) + H.change_appearance(APPEARANCE_ALL, usr, check_species_whitelist = 0, state = GLOB.tgui_admin_state) feedback_add_details("admin_verb","CHAA") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! /client/proc/change_human_appearance_self() @@ -29,10 +29,10 @@ switch(alert("Do you wish for [H] to be allowed to select non-whitelisted races?","Alter Mob Appearance","Yes","No","Cancel")) if("Yes") log_and_message_admins("has allowed [H] to change [T.his] appearance, without whitelisting of races.") - H.change_appearance(APPEARANCE_ALL, H.loc, check_species_whitelist = 0) + H.change_appearance(APPEARANCE_ALL, H, check_species_whitelist = 0) if("No") log_and_message_admins("has allowed [H] to change [T.his] appearance, with whitelisting of races.") - H.change_appearance(APPEARANCE_ALL, H.loc, check_species_whitelist = 1) + H.change_appearance(APPEARANCE_ALL, H, check_species_whitelist = 1) feedback_add_details("admin_verb","CMAS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! /client/proc/editappear() diff --git a/code/modules/asset_cache/asset_list_items.dm b/code/modules/asset_cache/asset_list_items.dm index 6f6ab27934..1477d4c532 100644 --- a/code/modules/asset_cache/asset_list_items.dm +++ b/code/modules/asset_cache/asset_list_items.dm @@ -436,3 +436,37 @@ /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) diff --git a/code/modules/food/kitchen/smartfridge.dm b/code/modules/food/kitchen/smartfridge.dm index d76cb754ff..e527df91b9 100644 --- a/code/modules/food/kitchen/smartfridge.dm +++ b/code/modules/food/kitchen/smartfridge.dm @@ -213,17 +213,18 @@ is_off = "-off" // Fridge contents - switch(contents.len) - if(0) - add_overlay("empty[is_off]") - if(1 to 2) - add_overlay("[icon_contents]-1[is_off]") - if(3 to 5) - add_overlay("[icon_contents]-2[is_off]") - if(6 to 8) - add_overlay("[icon_contents]-3[is_off]") - else - add_overlay("[icon_contents]-4[is_off]") + if(contents) + switch(contents.len) + if(0) + add_overlay("empty[is_off]") + if(1 to 2) + add_overlay("[icon_contents]-1[is_off]") + if(3 to 5) + add_overlay("[icon_contents]-2[is_off]") + if(6 to 8) + add_overlay("[icon_contents]-3[is_off]") + else + add_overlay("[icon_contents]-4[is_off]") // Fridge top var/image/top = image(icon, "[icon_base]-top") @@ -241,7 +242,6 @@ user.visible_message("[user] [panel_open ? "opens" : "closes"] the maintenance panel of \the [src].", "You [panel_open ? "open" : "close"] the maintenance panel of \the [src].") playsound(src, O.usesound, 50, 1) update_icon() - SSnanoui.update_uis(src) return if(wrenchable && default_unfasten_wrench(user, O, 20)) @@ -261,7 +261,6 @@ stock(O) user.visible_message("[user] has added \the [O] to \the [src].", "You add \the [O] to \the [src].") - else if(istype(O, /obj/item/weapon/storage/bag)) var/obj/item/weapon/storage/bag/P = O var/plants_loaded = 0 @@ -307,11 +306,11 @@ var/datum/stored_item/item = new/datum/stored_item(src, O.type, O.name) item.add_product(O) item_records.Add(item) - SSnanoui.update_uis(src) + SStgui.update_uis(src) /obj/machinery/smartfridge/proc/vend(datum/stored_item/I) I.get_product(get_turf(src)) - SSnanoui.update_uis(src) + SStgui.update_uis(src) /obj/machinery/smartfridge/attack_ai(mob/user as mob) attack_hand(user) @@ -320,66 +319,59 @@ if(stat & (NOPOWER|BROKEN)) return wires.Interact(user) - ui_interact(user) + tgui_interact(user) -/******************* -* SmartFridge Menu -********************/ +/obj/machinery/smartfridge/tgui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "SmartVend", name) + ui.set_autoupdate(FALSE) + ui.open() -/obj/machinery/smartfridge/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - user.set_machine(src) +/obj/machinery/smartfridge/tgui_data(mob/user) + . = list() - var/data[0] - data["contents"] = null - data["electrified"] = seconds_electrified > 0 - data["shoot_inventory"] = shoot_inventory - data["locked"] = locked - data["secure"] = is_secure - - var/list/items[0] - for (var/i=1 to length(item_records)) + var/list/items = list() + for(var/i=1 to length(item_records)) var/datum/stored_item/I = item_records[i] var/count = I.get_amount() if(count > 0) - items.Add(list(list("display_name" = html_encode(capitalize(I.item_name)), "vend" = i, "quantity" = count))) + items.Add(list(list("name" = html_encode(capitalize(I.item_name)), "index" = i, "amount" = count))) - if(items.len > 0) - data["contents"] = items + .["contents"] = items + .["name"] = name + .["locked"] = locked + .["secure"] = is_secure - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open) - if(!ui) - ui = new(user, src, ui_key, "smartfridge.tmpl", src.name, 400, 500) - ui.set_initial_data(data) - ui.open() +/obj/machinery/smartfridge/tgui_act(action, params) + if(..()) + return TRUE -/obj/machinery/smartfridge/Topic(href, href_list) - if(..()) return 0 + add_fingerprint(usr) + switch(action) + if("Release") + var/amount = 0 + if(params["amount"]) + amount = params["amount"] + else + amount = input("How many items?", "How many items would you like to take out?", 1) as num|null + + if(QDELETED(src) || QDELETED(usr) || !usr.Adjacent(src)) + return FALSE + + var/index = text2num(params["index"]) + var/datum/stored_item/I = item_records[index] + var/count = I.get_amount() - var/mob/user = usr - var/datum/nanoui/ui = SSnanoui.get_open_ui(user, src, "main") + // Sanity check, there are probably ways to press the button when it shouldn't be possible. + if(count > 0) + if((count - amount) < 0) + amount = count + for(var/i = 1 to amount) + vend(I) - src.add_fingerprint(user) - - if(href_list["close"]) - user.unset_machine() - ui.close() - return 0 - - if(href_list["vend"]) - var/index = text2num(href_list["vend"]) - var/amount = text2num(href_list["amount"]) - var/datum/stored_item/I = item_records[index] - var/count = I.get_amount() - - // Sanity check, there are probably ways to press the button when it shouldn't be possible. - if(count > 0) - if((count - amount) < 0) - amount = count - for(var/i = 1 to amount) - vend(I) - - return 1 - return 0 + return TRUE + return FALSE /obj/machinery/smartfridge/proc/throw_item() var/obj/throw_item = null @@ -398,17 +390,18 @@ spawn(0) throw_item.throw_at(target,16,3,src) src.visible_message("[src] launches [throw_item.name] at [target.name]!") + SStgui.update_uis(src) return 1 /************************ * Secure SmartFridges *************************/ -/obj/machinery/smartfridge/secure/Topic(href, href_list) +/obj/machinery/smartfridge/secure/tgui_act(action, params) if(stat & (NOPOWER|BROKEN)) - return 0 + return TRUE if(usr.contents.Find(src) || (in_range(src, usr) && istype(loc, /turf))) - if(!allowed(usr) && !emagged && locked != -1 && href_list["vend"]) + if(!allowed(usr) && !emagged && locked != -1 && action == "Release") to_chat(usr, "Access denied.") - return 0 + return TRUE return ..() diff --git a/code/modules/mob/living/carbon/human/appearance.dm b/code/modules/mob/living/carbon/human/appearance.dm index fe1c6ca632..19be7d0865 100644 --- a/code/modules/mob/living/carbon/human/appearance.dm +++ b/code/modules/mob/living/carbon/human/appearance.dm @@ -1,7 +1,12 @@ -/mob/living/carbon/human/proc/change_appearance(var/flags = APPEARANCE_ALL_HAIR, var/location = src, var/mob/user = src, var/check_species_whitelist = 1, var/list/species_whitelist = list(), var/list/species_blacklist = list(), var/datum/topic_state/state = default_state) - var/datum/nano_module/appearance_changer/AC = new(location, src, check_species_whitelist, species_whitelist, species_blacklist) +/mob/living/carbon/human/proc/change_appearance(var/flags = APPEARANCE_ALL_HAIR, + var/mob/user = src, + var/check_species_whitelist = 1, + var/list/species_whitelist = list(), + var/list/species_blacklist = list(), + var/datum/tgui_state/state = GLOB.tgui_self_state) + var/datum/tgui_module/appearance_changer/AC = new(src, src, check_species_whitelist, species_whitelist, species_blacklist) AC.flags = flags - AC.ui_interact(user, state = state) + AC.tgui_interact(user, custom_state = state) /mob/living/carbon/human/proc/change_species(var/new_species) if(!new_species) diff --git a/code/modules/mob/say.dm b/code/modules/mob/say.dm index 2bb96bc608..1f32765447 100644 --- a/code/modules/mob/say.dm +++ b/code/modules/mob/say.dm @@ -114,7 +114,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/nano/modules/human_appearance.dm b/code/modules/nano/modules/human_appearance.dm deleted file mode 100644 index fd2ed900f2..0000000000 --- a/code/modules/nano/modules/human_appearance.dm +++ /dev/null @@ -1,191 +0,0 @@ -/datum/nano_module/appearance_changer - name = "Appearance Editor" - var/flags = APPEARANCE_ALL_HAIR - var/mob/living/carbon/human/owner = null - var/list/valid_species = list() - var/list/valid_hairstyles = list() - var/list/valid_facial_hairstyles = list() - - var/check_whitelist - var/list/whitelist - var/list/blacklist - -/datum/nano_module/appearance_changer/New(var/location, var/mob/living/carbon/human/H, var/check_species_whitelist = 1, var/list/species_whitelist = list(), var/list/species_blacklist = list()) - ..() - owner = H - src.check_whitelist = check_species_whitelist - src.whitelist = species_whitelist - src.blacklist = species_blacklist - -/datum/nano_module/appearance_changer/Topic(ref, href_list, var/datum/topic_state/state = default_state) - if(..()) - return 1 - - if(href_list["race"]) - if(can_change(APPEARANCE_RACE) && (href_list["race"] in valid_species)) - if(owner.change_species(href_list["race"])) - cut_and_generate_data() - return 1 - if(href_list["gender"]) - if(can_change(APPEARANCE_GENDER) && (href_list["gender"] in get_genders())) - if(owner.change_gender(href_list["gender"])) - cut_and_generate_data() - return 1 - if(href_list["gender_id"]) - if(can_change(APPEARANCE_GENDER) && (href_list["gender_id"] in all_genders_define_list)) - owner.identifying_gender = href_list["gender_id"] - return 1 - if(href_list["skin_tone"]) - if(can_change_skin_tone()) - var/new_s_tone = input(usr, "Choose your character's skin-tone:\n(Light 1 - 220 Dark)", "Skin Tone", -owner.s_tone + 35) as num|null - if(isnum(new_s_tone) && can_still_topic(state)) - new_s_tone = 35 - max(min( round(new_s_tone), 220),1) - return owner.change_skin_tone(new_s_tone) - if(href_list["skin_color"]) - if(can_change_skin_color()) - var/new_skin = input(usr, "Choose your character's skin colour: ", "Skin Color", rgb(owner.r_skin, owner.g_skin, owner.b_skin)) as color|null - if(new_skin && can_still_topic(state)) - var/r_skin = hex2num(copytext(new_skin, 2, 4)) - var/g_skin = hex2num(copytext(new_skin, 4, 6)) - var/b_skin = hex2num(copytext(new_skin, 6, 8)) - if(owner.change_skin_color(r_skin, g_skin, b_skin)) - update_dna() - return 1 - if(href_list["hair"]) - if(can_change(APPEARANCE_HAIR) && (href_list["hair"] in valid_hairstyles)) - if(owner.change_hair(href_list["hair"])) - update_dna() - return 1 - if(href_list["hair_color"]) - if(can_change(APPEARANCE_HAIR_COLOR)) - var/new_hair = input("Please select hair color.", "Hair Color", rgb(owner.r_hair, owner.g_hair, owner.b_hair)) as color|null - if(new_hair && can_still_topic(state)) - var/r_hair = hex2num(copytext(new_hair, 2, 4)) - var/g_hair = hex2num(copytext(new_hair, 4, 6)) - var/b_hair = hex2num(copytext(new_hair, 6, 8)) - if(owner.change_hair_color(r_hair, g_hair, b_hair)) - update_dna() - return 1 - if(href_list["facial_hair"]) - if(can_change(APPEARANCE_FACIAL_HAIR) && (href_list["facial_hair"] in valid_facial_hairstyles)) - if(owner.change_facial_hair(href_list["facial_hair"])) - update_dna() - return 1 - if(href_list["facial_hair_color"]) - if(can_change(APPEARANCE_FACIAL_HAIR_COLOR)) - var/new_facial = input("Please select facial hair color.", "Facial Hair Color", rgb(owner.r_facial, owner.g_facial, owner.b_facial)) as color|null - if(new_facial && can_still_topic(state)) - var/r_facial = hex2num(copytext(new_facial, 2, 4)) - var/g_facial = hex2num(copytext(new_facial, 4, 6)) - var/b_facial = hex2num(copytext(new_facial, 6, 8)) - if(owner.change_facial_hair_color(r_facial, g_facial, b_facial)) - update_dna() - return 1 - if(href_list["eye_color"]) - if(can_change(APPEARANCE_EYE_COLOR)) - var/new_eyes = input("Please select eye color.", "Eye Color", rgb(owner.r_eyes, owner.g_eyes, owner.b_eyes)) as color|null - if(new_eyes && can_still_topic(state)) - var/r_eyes = hex2num(copytext(new_eyes, 2, 4)) - var/g_eyes = hex2num(copytext(new_eyes, 4, 6)) - var/b_eyes = hex2num(copytext(new_eyes, 6, 8)) - if(owner.change_eye_color(r_eyes, g_eyes, b_eyes)) - update_dna() - return 1 - - return 0 - -/datum/nano_module/appearance_changer/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/datum/topic_state/state = default_state) - - if(!owner || !owner.species) - return - - generate_data(check_whitelist, whitelist, blacklist) - var/list/data = host.initial_data() - - data["specimen"] = owner.species.name - data["gender"] = owner.gender - data["gender_id"] = owner.identifying_gender - data["change_race"] = can_change(APPEARANCE_RACE) - if(data["change_race"]) - var/species[0] - for(var/specimen in valid_species) - species[++species.len] = list("specimen" = specimen) - data["species"] = species - - data["change_gender"] = can_change(APPEARANCE_GENDER) - if(data["change_gender"]) - var/genders[0] - for(var/gender in get_genders()) - genders[++genders.len] = list("gender_name" = gender2text(gender), "gender_key" = gender) - data["genders"] = genders - var/id_genders[0] - for(var/gender in all_genders_define_list) - id_genders[++id_genders.len] = list("gender_name" = gender2text(gender), "gender_key" = gender) - data["id_genders"] = id_genders - - - data["change_skin_tone"] = can_change_skin_tone() - data["change_skin_color"] = can_change_skin_color() - data["change_eye_color"] = can_change(APPEARANCE_EYE_COLOR) - data["change_hair"] = can_change(APPEARANCE_HAIR) - if(data["change_hair"]) - var/hair_styles[0] - for(var/hair_style in valid_hairstyles) - hair_styles[++hair_styles.len] = list("hairstyle" = hair_style) - data["hair_styles"] = hair_styles - data["hair_style"] = owner.h_style - - data["change_facial_hair"] = can_change(APPEARANCE_FACIAL_HAIR) - if(data["change_facial_hair"]) - var/facial_hair_styles[0] - for(var/facial_hair_style in valid_facial_hairstyles) - facial_hair_styles[++facial_hair_styles.len] = list("facialhairstyle" = facial_hair_style) - data["facial_hair_styles"] = facial_hair_styles - data["facial_hair_style"] = owner.f_style - - data["change_hair_color"] = can_change(APPEARANCE_HAIR_COLOR) - data["change_facial_hair_color"] = can_change(APPEARANCE_FACIAL_HAIR_COLOR) - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open) - if (!ui) - ui = new(user, src, ui_key, "appearance_changer.tmpl", "[src]", 800, 450, state = state) - ui.set_initial_data(data) - ui.open() - ui.set_auto_update(1) - -/datum/nano_module/appearance_changer/proc/update_dna() - if(owner && (flags & APPEARANCE_UPDATE_DNA)) - owner.update_dna() - -/datum/nano_module/appearance_changer/proc/can_change(var/flag) - return owner && (flags & flag) - -/datum/nano_module/appearance_changer/proc/can_change_skin_tone() - return owner && (flags & APPEARANCE_SKIN) && owner.species.appearance_flags & HAS_SKIN_TONE - -/datum/nano_module/appearance_changer/proc/can_change_skin_color() - return owner && (flags & APPEARANCE_SKIN) && owner.species.appearance_flags & HAS_SKIN_COLOR - -/datum/nano_module/appearance_changer/proc/cut_and_generate_data() - // Making the assumption that the available species remain constant - valid_facial_hairstyles.Cut() - valid_facial_hairstyles.Cut() - generate_data() - -/datum/nano_module/appearance_changer/proc/generate_data() - if(!owner) - return - if(!valid_species.len) - valid_species = owner.generate_valid_species(check_whitelist, whitelist, blacklist) - if(!valid_hairstyles.len || !valid_facial_hairstyles.len) - valid_hairstyles = owner.generate_valid_hairstyles(check_gender = 0) - valid_facial_hairstyles = owner.generate_valid_facial_hairstyles() - - -/datum/nano_module/appearance_changer/proc/get_genders() - var/datum/species/S = owner.species - var/list/possible_genders = S.genders - if(!owner.internal_organs_by_name["cell"]) - return possible_genders - possible_genders = possible_genders.Copy() - possible_genders |= NEUTER - return possible_genders \ No newline at end of file diff --git a/code/modules/paperwork/filingcabinet.dm b/code/modules/paperwork/filingcabinet.dm index cf1f251307..3d12e68534 100644 --- a/code/modules/paperwork/filingcabinet.dm +++ b/code/modules/paperwork/filingcabinet.dm @@ -36,12 +36,8 @@ to_chat(user, "You put [P] in [src].") user.drop_item() P.loc = src - icon_state = "[initial(icon_state)]-open" - flick("[initial(icon_state)]-open",src) - playsound(src, 'sound/bureaucracy/filingcabinet.ogg', 50, 1) - sleep(40) - icon_state = initial(icon_state) - updateUsrDialog() + open_animation() + SStgui.update_uis(src) else if(P.is_wrench()) playsound(src, P.usesound, 50, 1) anchored = !anchored @@ -65,20 +61,12 @@ to_chat(user, "\The [src] is empty.") return - user.set_machine(src) - var/dat = "
" - for(var/obj/item/P in src) - dat += "" - dat += "
[P.name]
" - user << browse("[name][dat]", "window=filingcabinet;size=350x300") - - return + tgui_interact(user) /obj/structure/filingcabinet/attack_tk(mob/user) if(anchored) - attack_self_tk(user) - else - ..() + return attack_self_tk(user) + return ..() /obj/structure/filingcabinet/attack_self_tk(mob/user) if(contents.len) @@ -91,20 +79,46 @@ return to_chat(user, "You find nothing in [src].") -/obj/structure/filingcabinet/Topic(href, href_list) - if(href_list["retrieve"]) - usr << browse("", "window=filingcabinet") // Close the menu +/obj/structure/filingcabinet/tgui_state(mob/user) + return GLOB.tgui_physical_state - //var/retrieveindex = text2num(href_list["retrieve"]) - var/obj/item/P = locate(href_list["retrieve"])//contents[retrieveindex] - if(istype(P) && (P.loc == src) && src.Adjacent(usr)) - usr.put_in_hands(P) - updateUsrDialog() - flick("[initial(icon_state)]-open",src) - playsound(src, 'sound/bureaucracy/filingcabinet.ogg', 50, 1) - spawn(0) - sleep(20) - icon_state = initial(icon_state) +/obj/structure/filingcabinet/tgui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "FileCabinet", name) + ui.set_autoupdate(FALSE) + ui.open() + +/obj/structure/filingcabinet/tgui_data(mob/user) + var/list/data = list() + + data["contents"] = list() + for(var/obj/item/P in src) + data["contents"].Add(list(list( + "name" = P.name, + "ref" = "\ref[P]", + ))) + + return data + +/obj/structure/filingcabinet/tgui_act(action, params) + if(..()) + return TRUE + + switch(action) + if("retrieve") + var/obj/item/P = locate(params["ref"]) + if(istype(P) && (P.loc == src) && usr.Adjacent(src)) + usr.put_in_hands(P) + open_animation() + SStgui.update_uis(src) + +/obj/structure/filingcabinet/proc/open_animation() + flick("[initial(icon_state)]-open",src) + playsound(src, 'sound/bureaucracy/filingcabinet.ogg', 50, 1) + spawn(0) + sleep(20) + icon_state = initial(icon_state) /* * Security Record Cabinets @@ -112,7 +126,6 @@ /obj/structure/filingcabinet/security var/virgin = 1 - /obj/structure/filingcabinet/security/proc/populate() if(virgin) for(var/datum/data/record/G in data_core.general) diff --git a/code/modules/reagents/Chemistry-Holder.dm b/code/modules/reagents/Chemistry-Holder.dm index b6a934a60d..8598d54115 100644 --- a/code/modules/reagents/Chemistry-Holder.dm +++ b/code/modules/reagents/Chemistry-Holder.dm @@ -144,6 +144,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 7a337c7666..39ddc4ae04 100644 --- a/code/modules/reagents/Chemistry-Machinery.dm +++ b/code/modules/reagents/Chemistry-Machinery.dm @@ -2,6 +2,14 @@ #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_UNITS_PER_BOTTLE 60 // Max amount of units in a bottle (it's volume) +#define MAX_CUSTOM_NAME_LEN 64 // Max length of a custom pill/condiment/whatever + @@ -24,11 +32,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 +56,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 +70,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 +96,420 @@ 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 + var/num = round(text2num(arguments["num"] || 1)) + if(!num) + return + arguments["num"] = num + var/amount_per_bottle = CLAMP(reagents.total_volume / num, 0, MAX_UNITS_PER_BOTTLE) + var/default_name = "[reagents.get_master_reagent_name()]" + var/bottles_text = num == 1 ? "new bottle" : "[num] new bottles" + tgui_modal_input(src, id, "Please name your [bottles_text] ([amount_per_bottle]u in bottle):", null, arguments, default_name, MAX_CUSTOM_NAME_LEN) + if("create_bottle_multiple") + if(condi || !reagents.total_volume) + return + tgui_modal_input(src, id, "Please enter the amount of bottles to make (max [MAX_MULTI_AMOUNT] at a time):", null, arguments, pillamount, 5) + 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 + 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_bottle = CLAMP(reagents.total_volume / count, 0, MAX_UNITS_PER_BOTTLE) + while(count--) + if(reagents.total_volume <= 0) + to_chat(usr, "Not enough reagents to create these bottles!") + return + 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, amount_per_bottle) + P.update_icon() + if("create_bottle_multiple") + if(condi || !reagents.total_volume) + return + tgui_act("modal_open", list("id" = "create_bottle", "arguments" = list("num" = answer)), ui, state) + 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 TRUE + + 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(get_turf(src)) + if(Adjacent(usr) && !issilicon(usr)) + usr.put_in_hands(loaded_pill_bottle) + 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 +549,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 @@ -444,94 +658,65 @@ 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 - visible_message("\The [usr] remove the container from \the [src].") - 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() @@ -549,7 +734,6 @@ // Reset the machine. spawn(60) inuse = 0 - interact(usr) // Process. for (var/obj/item/O in holdingitems) @@ -576,13 +760,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 /////////////// /////////////// @@ -648,4 +845,12 @@ 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_UNITS_PER_BOTTLE +#undef MAX_CUSTOM_NAME_LEN diff --git a/code/modules/reagents/dispenser/dispenser2.dm b/code/modules/reagents/dispenser/dispenser2.dm index 9663c68529..7f2fb13f40 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 TRUE -/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/surgery/implant.dm b/code/modules/surgery/implant.dm index 71f6c66fcd..87817c6e27 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 FALSE 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 391b83fe0c..300616499f 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 FALSE 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 c09d10bebe..071b44263b 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 FALSE + 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 FALSE + 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 FALSE + target.op_stage.current_organ = null var/list/removable_organs = list() @@ -417,6 +426,9 @@ if (!..()) return 0 + if(!istype(tool)) + return FALSE + target.op_stage.current_organ = null var/list/removable_organs = list() diff --git a/code/modules/tgui/external.dm b/code/modules/tgui/external.dm index 7e75f9343c..bb39c721d0 100644 --- a/code/modules/tgui/external.dm +++ b/code/modules/tgui/external.dm @@ -12,9 +12,10 @@ * * required user mob The mob who opened/is using the UI. * optional ui datum/tgui The UI to be updated, if it exists. + * optional parent_ui datum/tgui A parent UI that, when closed, closes this UI as well. */ -/datum/proc/tgui_interact(mob/user, datum/tgui/ui = null) +/datum/proc/tgui_interact(mob/user, datum/tgui/ui = null, datum/tgui/parent_ui = null) return FALSE // Not implemented. /** @@ -27,7 +28,7 @@ * * return list Data to be sent to the UI. */ -/datum/proc/tgui_data(mob/user) +/datum/proc/tgui_data(mob/user, datum/tgui/ui, datum/tgui_state/state) return list() // Not implemented. /** @@ -140,6 +141,26 @@ */ /datum/proc/tgui_close(mob/user) +/** + * verb + * + * Used by a client to fix broken TGUI windows caused by opening a UI window before assets load. + * Probably not very performant and forcibly destroys a bunch of windows, so it has some warnings attached. + * Conveniently, also allows devs to force a dev server reattach without relogging, since it yeets windows. + */ +/client/verb/tgui_fix_white() + set desc = "Only use this if you have a broken TGUI window occupying your screen!" + set name = "Fix TGUI" + set category = "OOC" + + if(alert(src, "Only use this verb if you have a white TGUI window stuck on your screen.", "Fix TGUI", "Continue", "Nevermind") != "Continue") + return + + SStgui.close_user_uis(mob) + if(alert(src, "Did that fix the problem?", "Fix TGUI", "Yes", "No") == "No") + SStgui.force_close_all_windows(mob) + alert(src, "UIs should be fixed now. If not, please cry to your nearest coder.", "Fix TGUI") + /** * verb * diff --git a/code/modules/tgui/modal.dm b/code/modules/tgui/modal.dm new file mode 100644 index 0000000000..5fb4c4cc74 --- /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/code/modules/tgui/modules/_base.dm b/code/modules/tgui/modules/_base.dm index 23b12b8bd1..e0d0a7c645 100644 --- a/code/modules/tgui/modules/_base.dm +++ b/code/modules/tgui/modules/_base.dm @@ -12,17 +12,26 @@ Code is pretty much ripped verbatim from nano modules, but with un-needed stuff var/list/using_access var/tgui_id + var/ntos = FALSE /datum/tgui_module/New(var/host) src.host = host + if(ntos) + tgui_id = "Ntos" + tgui_id /datum/tgui_module/tgui_host() - return host ? host : src + return host ? host.tgui_host() : src /datum/tgui_module/tgui_close(mob/user) if(host) host.tgui_close(user) +/datum/tgui_module/proc/check_eye(mob/user) + return -1 + +/datum/tgui_module/proc/can_still_topic(mob/user, datum/tgui_state/state) + return (tgui_status(user, state) == STATUS_INTERACTIVE) + /datum/tgui_module/proc/check_access(mob/user, access) if(!access) return 1 @@ -43,4 +52,70 @@ Code is pretty much ripped verbatim from nano modules, but with un-needed stuff if(access in I.access) return 1 - return 0 \ No newline at end of file + return 0 + +/datum/tgui_module/tgui_static_data() + . = ..() + + var/obj/item/modular_computer/host = tgui_host() + if(istype(host)) + . += host.get_header_data() + +/datum/tgui_module/tgui_act(action, params) + if(..()) + return TRUE + + var/obj/item/modular_computer/host = tgui_host() + if(istype(host)) + if(action == "PC_exit") + host.kill_program() + return TRUE + if(action == "PC_shutdown") + host.shutdown_computer() + return TRUE + if(action == "PC_minimize") + host.minimize_program(usr) + return TRUE + +// Just a nice little default interact in case the subtypes don't need any special behavior here +/datum/tgui_module/tgui_interact(mob/user, datum/tgui/ui = null) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, tgui_id, name) + ui.open() + +// This is a helper for anything that wants to render the map. +/datum/tgui_module/proc/get_plane_masters() + . = list() + // 'Utility' planes + . += new /obj/screen/plane_master/fullbright //Lighting system (lighting_overlay objects) + . += new /obj/screen/plane_master/lighting //Lighting system (but different!) + . += new /obj/screen/plane_master/ghosts //Ghosts! + . += new /obj/screen/plane_master{plane = PLANE_AI_EYE} //AI Eye! + + . += new /obj/screen/plane_master{plane = PLANE_CH_STATUS} //Status is the synth/human icon left side of medhuds + . += new /obj/screen/plane_master{plane = PLANE_CH_HEALTH} //Health bar + . += new /obj/screen/plane_master{plane = PLANE_CH_LIFE} //Alive-or-not icon + . += new /obj/screen/plane_master{plane = PLANE_CH_ID} //Job ID icon + . += new /obj/screen/plane_master{plane = PLANE_CH_WANTED} //Wanted status + . += new /obj/screen/plane_master{plane = PLANE_CH_IMPLOYAL} //Loyalty implants + . += new /obj/screen/plane_master{plane = PLANE_CH_IMPTRACK} //Tracking implants + . += new /obj/screen/plane_master{plane = PLANE_CH_IMPCHEM} //Chemical implants + . += new /obj/screen/plane_master{plane = PLANE_CH_SPECIAL} //"Special" role stuff + . += new /obj/screen/plane_master{plane = PLANE_CH_STATUS_OOC} //OOC status HUD + + . += new /obj/screen/plane_master{plane = PLANE_ADMIN1} //For admin use + . += new /obj/screen/plane_master{plane = PLANE_ADMIN2} //For admin use + . += new /obj/screen/plane_master{plane = PLANE_ADMIN3} //For admin use + + . += new /obj/screen/plane_master{plane = PLANE_MESONS} //Meson-specific things like open ceilings. + . += new /obj/screen/plane_master{plane = PLANE_BUILDMODE} //Things that only show up while in build mode + + // Real tangible stuff planes + . += new /obj/screen/plane_master/main{plane = TURF_PLANE} + . += new /obj/screen/plane_master/main{plane = OBJ_PLANE} + . += new /obj/screen/plane_master/main{plane = MOB_PLANE} + . += new /obj/screen/plane_master/cloaked //Cloaked atoms! + +/datum/tgui_module/proc/relaymove(mob/user, direction) + return FALSE diff --git a/code/modules/tgui/modules/appearance_changer.dm b/code/modules/tgui/modules/appearance_changer.dm new file mode 100644 index 0000000000..667fe7e0a0 --- /dev/null +++ b/code/modules/tgui/modules/appearance_changer.dm @@ -0,0 +1,358 @@ +/datum/tgui_module/appearance_changer + name = "Appearance Editor" + tgui_id = "AppearanceChanger" + var/flags = APPEARANCE_ALL_HAIR + var/mob/living/carbon/human/owner = null + var/list/valid_species = list() + var/list/valid_hairstyles = list() + var/list/valid_facial_hairstyles = list() + + var/check_whitelist + var/list/whitelist + var/list/blacklist + + var/customize_usr = FALSE + + // Stuff needed to render the map + var/map_name + var/obj/screen/map_view/cam_screen + var/list/cam_plane_masters + var/obj/screen/background/cam_background + var/obj/screen/skybox/local_skybox + // Needed for moving camera support + var/camera_diff_x = -1 + var/camera_diff_y = -1 + var/camera_diff_z = -1 + +/datum/tgui_module/appearance_changer/New( + var/host, + mob/living/carbon/human/H, + check_species_whitelist = 1, + list/species_whitelist = list(), + list/species_blacklist = list()) + . = ..() + + map_name = "appearance_changer_[REF(src)]_map" + // Initialize map objects + cam_screen = new + cam_screen.name = "screen" + cam_screen.assigned_map = map_name + cam_screen.del_on_map_removal = FALSE + cam_screen.screen_loc = "[map_name]:1,1" + + cam_plane_masters = get_plane_masters() + + for(var/plane in cam_plane_masters) + var/obj/screen/instance = plane + instance.assigned_map = map_name + instance.del_on_map_removal = FALSE + instance.screen_loc = "[map_name]:CENTER" + + local_skybox = new() + local_skybox.assigned_map = map_name + local_skybox.del_on_map_removal = FALSE + local_skybox.screen_loc = "[map_name]:CENTER,CENTER" + cam_plane_masters += local_skybox + + cam_background = new + cam_background.assigned_map = map_name + cam_background.del_on_map_removal = FALSE + reload_cameraview() + + owner = H + check_whitelist = check_species_whitelist + whitelist = species_whitelist + blacklist = species_blacklist + +/datum/tgui_module/appearance_changer/Destroy() + qdel(cam_screen) + QDEL_LIST(cam_plane_masters) + qdel(cam_background) + return ..() + +/datum/tgui_module/appearance_changer/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state) + if(..()) + return TRUE + + var/mob/living/carbon/human/target = owner + if(customize_usr) + if(!ishuman(usr)) + return TRUE + target = usr + + switch(action) + if("race") + if(can_change(APPEARANCE_RACE) && (params["race"] in valid_species)) + if(target.change_species(params["race"])) + cut_and_generate_data() + return 1 + if("gender") + if(can_change(APPEARANCE_GENDER) && (params["gender"] in get_genders())) + if(target.change_gender(params["gender"])) + cut_and_generate_data() + return 1 + if("gender_id") + if(can_change(APPEARANCE_GENDER) && (params["gender_id"] in all_genders_define_list)) + target.identifying_gender = params["gender_id"] + return 1 + if("skin_tone") + if(can_change_skin_tone()) + var/new_s_tone = input(usr, "Choose your character's skin-tone:\n(Light 1 - 220 Dark)", "Skin Tone", -target.s_tone + 35) as num|null + if(isnum(new_s_tone) && can_still_topic(usr, state)) + new_s_tone = 35 - max(min( round(new_s_tone), 220),1) + return target.change_skin_tone(new_s_tone) + if("skin_color") + if(can_change_skin_color()) + var/new_skin = input(usr, "Choose your character's skin colour: ", "Skin Color", rgb(target.r_skin, target.g_skin, target.b_skin)) as color|null + if(new_skin && can_still_topic(usr, state)) + var/r_skin = hex2num(copytext(new_skin, 2, 4)) + var/g_skin = hex2num(copytext(new_skin, 4, 6)) + var/b_skin = hex2num(copytext(new_skin, 6, 8)) + if(target.change_skin_color(r_skin, g_skin, b_skin)) + update_dna() + return 1 + if("hair") + if(can_change(APPEARANCE_HAIR) && (params["hair"] in valid_hairstyles)) + if(target.change_hair(params["hair"])) + update_dna() + return 1 + if("hair_color") + if(can_change(APPEARANCE_HAIR_COLOR)) + var/new_hair = input("Please select hair color.", "Hair Color", rgb(target.r_hair, target.g_hair, target.b_hair)) as color|null + if(new_hair && can_still_topic(usr, state)) + var/r_hair = hex2num(copytext(new_hair, 2, 4)) + var/g_hair = hex2num(copytext(new_hair, 4, 6)) + var/b_hair = hex2num(copytext(new_hair, 6, 8)) + if(target.change_hair_color(r_hair, g_hair, b_hair)) + update_dna() + return 1 + if("facial_hair") + if(can_change(APPEARANCE_FACIAL_HAIR) && (params["facial_hair"] in valid_facial_hairstyles)) + if(target.change_facial_hair(params["facial_hair"])) + update_dna() + return 1 + if("facial_hair_color") + if(can_change(APPEARANCE_FACIAL_HAIR_COLOR)) + var/new_facial = input("Please select facial hair color.", "Facial Hair Color", rgb(target.r_facial, target.g_facial, target.b_facial)) as color|null + if(new_facial && can_still_topic(usr, state)) + var/r_facial = hex2num(copytext(new_facial, 2, 4)) + var/g_facial = hex2num(copytext(new_facial, 4, 6)) + var/b_facial = hex2num(copytext(new_facial, 6, 8)) + if(target.change_facial_hair_color(r_facial, g_facial, b_facial)) + update_dna() + return 1 + if("eye_color") + if(can_change(APPEARANCE_EYE_COLOR)) + var/new_eyes = input("Please select eye color.", "Eye Color", rgb(target.r_eyes, target.g_eyes, target.b_eyes)) as color|null + if(new_eyes && can_still_topic(usr, state)) + var/r_eyes = hex2num(copytext(new_eyes, 2, 4)) + var/g_eyes = hex2num(copytext(new_eyes, 4, 6)) + var/b_eyes = hex2num(copytext(new_eyes, 6, 8)) + if(target.change_eye_color(r_eyes, g_eyes, b_eyes)) + update_dna() + return 1 + return FALSE + +/datum/tgui_module/appearance_changer/tgui_interact(mob/user, datum/tgui/ui = null, datum/tgui/parent_ui = null, datum/tgui_state/custom_state = GLOB.tgui_default_state) + var/mob/living/carbon/human/target = owner + if(customize_usr) + if(!ishuman(user)) + return TRUE + target = user + + if(!target || !target.species) + return + + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + reload_cameraview() + // Register map objects + user.client.register_map_obj(cam_screen) + for(var/plane in cam_plane_masters) + user.client.register_map_obj(plane) + user.client.register_map_obj(cam_background) + // Open UI + ui = new(user, src, tgui_id, name) + ui.open() + if(custom_state) + ui.set_state(custom_state) + +/datum/tgui_module/appearance_changer/tgui_data(mob/user, datum/tgui/ui, datum/tgui_state/state) + var/list/data = ..() + + generate_data(check_whitelist, whitelist, blacklist) + differential_check() + + var/mob/living/carbon/human/target = owner + if(customize_usr) + if(!ishuman(usr)) + return TRUE + target = usr + + data["name"] = target.name + data["specimen"] = target.species.name + data["gender"] = target.gender + data["gender_id"] = target.identifying_gender + data["change_race"] = can_change(APPEARANCE_RACE) + if(data["change_race"]) + var/species[0] + for(var/specimen in valid_species) + species[++species.len] = list("specimen" = specimen) + data["species"] = species + + data["change_gender"] = can_change(APPEARANCE_GENDER) + if(data["change_gender"]) + var/genders[0] + for(var/gender in get_genders()) + genders[++genders.len] = list("gender_name" = gender2text(gender), "gender_key" = gender) + data["genders"] = genders + var/id_genders[0] + for(var/gender in all_genders_define_list) + id_genders[++id_genders.len] = list("gender_name" = gender2text(gender), "gender_key" = gender) + data["id_genders"] = id_genders + + data["change_hair"] = can_change(APPEARANCE_HAIR) + if(data["change_hair"]) + var/hair_styles[0] + for(var/hair_style in valid_hairstyles) + hair_styles[++hair_styles.len] = list("hairstyle" = hair_style) + data["hair_styles"] = hair_styles + data["hair_style"] = target.h_style + + data["change_facial_hair"] = can_change(APPEARANCE_FACIAL_HAIR) + if(data["change_facial_hair"]) + var/facial_hair_styles[0] + for(var/facial_hair_style in valid_facial_hairstyles) + facial_hair_styles[++facial_hair_styles.len] = list("facialhairstyle" = facial_hair_style) + data["facial_hair_styles"] = facial_hair_styles + data["facial_hair_style"] = target.f_style + + data["change_skin_tone"] = can_change_skin_tone() + data["change_skin_color"] = can_change_skin_color() + if(data["change_skin_color"]) + data["skin_color"] = rgb(target.r_skin, target.g_skin, target.b_skin) + data["change_eye_color"] = can_change(APPEARANCE_EYE_COLOR) + if(data["change_eye_color"]) + data["eye_color"] = rgb(target.r_eyes, target.g_eyes, target.b_eyes) + data["change_hair_color"] = can_change(APPEARANCE_HAIR_COLOR) + if(data["change_hair_color"]) + data["hair_color"] = rgb(target.r_hair, target.g_hair, target.b_hair) + data["change_facial_hair_color"] = can_change(APPEARANCE_FACIAL_HAIR_COLOR) + if(data["change_facial_hair_color"]) + data["facial_hair_color"] = rgb(target.r_facial, target.g_facial, target.b_facial) + return data + +/datum/tgui_module/appearance_changer/tgui_static_data(mob/user) + var/list/data = ..() + data["mapRef"] = map_name + return data + +/datum/tgui_module/appearance_changer/proc/differential_check() + var/turf/T = get_turf(customize_usr ? tgui_host() : owner) + if(T) + var/new_x = T.x + var/new_y = T.y + var/new_z = T.z + if((new_x != camera_diff_x) || (new_y != camera_diff_y) || (new_z != camera_diff_z)) + reload_cameraview() + +/datum/tgui_module/appearance_changer/proc/reload_cameraview() + var/turf/camTurf = get_turf(customize_usr ? tgui_host() : owner) + if(!camTurf) + return + + camera_diff_x = camTurf.x + camera_diff_y = camTurf.y + camera_diff_z = camTurf.z + + var/list/visible_turfs = list() + for(var/turf/T in range(1, camTurf)) + visible_turfs += T + + cam_screen.vis_contents = visible_turfs + cam_background.icon_state = "clear" + cam_background.fill_rect(1, 1, 3, 3) + + local_skybox.cut_overlays() + local_skybox.add_overlay(SSskybox.get_skybox(get_z(camTurf))) + local_skybox.scale_to_view(3) + local_skybox.set_position("CENTER", "CENTER", (world.maxx>>1) - camTurf.x, (world.maxy>>1) - camTurf.y) + +/datum/tgui_module/appearance_changer/proc/update_dna() + var/mob/living/carbon/human/target = owner + if(customize_usr) + if(!ishuman(usr)) + return TRUE + target = usr + + if(target && (flags & APPEARANCE_UPDATE_DNA)) + target.update_dna() + +/datum/tgui_module/appearance_changer/proc/can_change(var/flag) + var/mob/living/carbon/human/target = owner + if(customize_usr) + if(!ishuman(usr)) + return TRUE + target = usr + + return target && (flags & flag) + +/datum/tgui_module/appearance_changer/proc/can_change_skin_tone() + var/mob/living/carbon/human/target = owner + if(customize_usr) + if(!ishuman(usr)) + return TRUE + target = usr + + return target && (flags & APPEARANCE_SKIN) && target.species.appearance_flags & HAS_SKIN_TONE + +/datum/tgui_module/appearance_changer/proc/can_change_skin_color() + var/mob/living/carbon/human/target = owner + if(customize_usr) + if(!ishuman(usr)) + return TRUE + target = usr + + return target && (flags & APPEARANCE_SKIN) && target.species.appearance_flags & HAS_SKIN_COLOR + +/datum/tgui_module/appearance_changer/proc/cut_and_generate_data() + // Making the assumption that the available species remain constant + valid_facial_hairstyles.Cut() + valid_facial_hairstyles.Cut() + generate_data() + +/datum/tgui_module/appearance_changer/proc/generate_data() + var/mob/living/carbon/human/target = owner + if(customize_usr) + if(!ishuman(usr)) + return TRUE + target = usr + if(!target) + return + if(!valid_species.len) + valid_species = target.generate_valid_species(check_whitelist, whitelist, blacklist) + if(!valid_hairstyles.len || !valid_facial_hairstyles.len) + valid_hairstyles = target.generate_valid_hairstyles(check_gender = 0) + valid_facial_hairstyles = target.generate_valid_facial_hairstyles() + +/datum/tgui_module/appearance_changer/proc/get_genders() + var/mob/living/carbon/human/target = owner + if(customize_usr) + if(!ishuman(usr)) + return TRUE + target = usr + var/datum/species/S = target.species + var/list/possible_genders = S.genders + if(!target.internal_organs_by_name["cell"]) + return possible_genders + possible_genders = possible_genders.Copy() + possible_genders |= NEUTER + return possible_genders + +/datum/tgui_module/appearance_changer/mirror + name = "SalonPro Nano-Mirror™" + flags = APPEARANCE_ALL_HAIR + customize_usr = TRUE + +/datum/tgui_module/appearance_changer/mirror/coskit + name = "SalonPro Porta-Makeover Deluxe™" \ No newline at end of file diff --git a/code/modules/tgui/modules/camera.dm b/code/modules/tgui/modules/camera.dm index 58a8293991..dbb3c7ae97 100644 --- a/code/modules/tgui/modules/camera.dm +++ b/code/modules/tgui/modules/camera.dm @@ -37,37 +37,7 @@ cam_screen.del_on_map_removal = FALSE cam_screen.screen_loc = "[map_name]:1,1" - cam_plane_masters = list() - - // 'Utility' planes - cam_plane_masters += new /obj/screen/plane_master/fullbright //Lighting system (lighting_overlay objects) - cam_plane_masters += new /obj/screen/plane_master/lighting //Lighting system (but different!) - cam_plane_masters += new /obj/screen/plane_master/ghosts //Ghosts! - cam_plane_masters += new /obj/screen/plane_master{plane = PLANE_AI_EYE} //AI Eye! - - cam_plane_masters += new /obj/screen/plane_master{plane = PLANE_CH_STATUS} //Status is the synth/human icon left side of medhuds - cam_plane_masters += new /obj/screen/plane_master{plane = PLANE_CH_HEALTH} //Health bar - cam_plane_masters += new /obj/screen/plane_master{plane = PLANE_CH_LIFE} //Alive-or-not icon - cam_plane_masters += new /obj/screen/plane_master{plane = PLANE_CH_ID} //Job ID icon - cam_plane_masters += new /obj/screen/plane_master{plane = PLANE_CH_WANTED} //Wanted status - cam_plane_masters += new /obj/screen/plane_master{plane = PLANE_CH_IMPLOYAL} //Loyalty implants - cam_plane_masters += new /obj/screen/plane_master{plane = PLANE_CH_IMPTRACK} //Tracking implants - cam_plane_masters += new /obj/screen/plane_master{plane = PLANE_CH_IMPCHEM} //Chemical implants - cam_plane_masters += new /obj/screen/plane_master{plane = PLANE_CH_SPECIAL} //"Special" role stuff - cam_plane_masters += new /obj/screen/plane_master{plane = PLANE_CH_STATUS_OOC} //OOC status HUD - - cam_plane_masters += new /obj/screen/plane_master{plane = PLANE_ADMIN1} //For admin use - cam_plane_masters += new /obj/screen/plane_master{plane = PLANE_ADMIN2} //For admin use - cam_plane_masters += new /obj/screen/plane_master{plane = PLANE_ADMIN3} //For admin use - - cam_plane_masters += new /obj/screen/plane_master{plane = PLANE_MESONS} //Meson-specific things like open ceilings. - cam_plane_masters += new /obj/screen/plane_master{plane = PLANE_BUILDMODE} //Things that only show up while in build mode - - // Real tangible stuff planes - cam_plane_masters += new /obj/screen/plane_master/main{plane = TURF_PLANE} - cam_plane_masters += new /obj/screen/plane_master/main{plane = OBJ_PLANE} - cam_plane_masters += new /obj/screen/plane_master/main{plane = MOB_PLANE} - cam_plane_masters += new /obj/screen/plane_master/cloaked //Cloaked atoms! + cam_plane_masters = get_plane_masters() for(var/plane in cam_plane_masters) var/obj/screen/instance = plane @@ -144,7 +114,7 @@ return data /datum/tgui_module/camera/tgui_static_data(mob/user) - var/list/data = list() + var/list/data = ..() data["mapRef"] = map_name var/list/cameras = get_available_cameras(user) data["cameras"] = list() @@ -160,7 +130,10 @@ /datum/tgui_module/camera/tgui_act(action, params) if(..()) - return + return TRUE + + if(action && !issilicon(usr)) + playsound(tgui_host(), "terminal_type", 50, 1) if(action == "switch_camera") var/c_tag = params["name"] @@ -168,11 +141,33 @@ var/obj/machinery/camera/C = cameras["[ckey(c_tag)]"] active_camera = C playsound(tgui_host(), get_sfx("terminal_type"), 25, FALSE) - reload_cameraview() - return TRUE + if(action == "pan") + var/dir = params["dir"] + var/turf/T = get_turf(active_camera) + for(var/i in 1 to 10) + T = get_step(T, dir) + if(T) + var/obj/machinery/camera/target + var/best_dist = INFINITY + + var/list/possible_cameras = get_available_cameras(usr) + for(var/obj/machinery/camera/C in get_area(T)) + if(!possible_cameras["[ckey(C.c_tag)]"]) + continue + var/dist = get_dist(C, T) + if(dist < best_dist) + best_dist = dist + target = C + + if(target) + active_camera = target + playsound(tgui_host(), get_sfx("terminal_type"), 25, FALSE) + reload_cameraview() + . = TRUE + /datum/tgui_module/camera/proc/differential_check() var/turf/T = get_turf(active_camera) if(T) @@ -284,33 +279,7 @@ // If/when that is done, just move all the PC_ specific data and stuff to the modular computers themselves // instead of copying this approach here. /datum/tgui_module/camera/ntos - tgui_id = "NtosCameraConsole" - -/datum/tgui_module/camera/ntos/tgui_state() - return GLOB.tgui_ntos_state - -/datum/tgui_module/camera/ntos/tgui_static_data() - . = ..() - - var/datum/computer_file/program/host = tgui_host() - if(istype(host) && host.computer) - . += host.computer.get_header_data() - -/datum/tgui_module/camera/ntos/tgui_act(action, params) - if(..()) - return - - var/datum/computer_file/program/host = tgui_host() - if(istype(host) && host.computer) - if(action == "PC_exit") - host.computer.kill_program() - return TRUE - if(action == "PC_shutdown") - host.computer.shutdown_computer() - return TRUE - if(action == "PC_minimize") - host.computer.minimize_program(usr) - return TRUE + ntos = TRUE // ERT Version provides some additional networks. /datum/tgui_module/camera/ntos/ert diff --git a/code/modules/tgui/modules/crew_monitor.dm b/code/modules/tgui/modules/crew_monitor.dm index ebd6f35263..3f0d8d92b5 100644 --- a/code/modules/tgui/modules/crew_monitor.dm +++ b/code/modules/tgui/modules/crew_monitor.dm @@ -5,6 +5,9 @@ /datum/tgui_module/crew_monitor/tgui_act(action, params, datum/tgui/ui) if(..()) return TRUE + + if(action && !issilicon(usr)) + playsound(tgui_host(), "terminal_type", 50, 1) var/turf/T = get_turf(usr) if(!T || !(T.z in using_map.player_levels)) @@ -21,7 +24,7 @@ return TRUE if("setZLevel") ui.set_map_z_level(params["mapZLevel"]) - SStgui.update_uis(src) + return TRUE /datum/tgui_module/crew_monitor/tgui_interact(mob/user, datum/tgui/ui = null) var/z = get_z(user) @@ -40,7 +43,7 @@ ui.open() -/datum/tgui_module/crew_monitor/tgui_data(mob/user, ui_key = "main", datum/tgui_state/state = GLOB.tgui_default_state) +/datum/tgui_module/crew_monitor/tgui_data(mob/user) var/data[0] data["isAI"] = isAI(user) @@ -56,33 +59,7 @@ return data /datum/tgui_module/crew_monitor/ntos - tgui_id = "NtosCrewMonitor" - -/datum/tgui_module/crew_monitor/ntos/tgui_state(mob/user) - return GLOB.tgui_ntos_state - -/datum/tgui_module/crew_monitor/ntos/tgui_static_data() - . = ..() - - var/datum/computer_file/program/host = tgui_host() - if(istype(host) && host.computer) - . += host.computer.get_header_data() - -/datum/tgui_module/crew_monitor/ntos/tgui_act(action, params) - if(..()) - return - - var/datum/computer_file/program/host = tgui_host() - if(istype(host) && host.computer) - if(action == "PC_exit") - host.computer.kill_program() - return TRUE - if(action == "PC_shutdown") - host.computer.shutdown_computer() - return TRUE - if(action == "PC_minimize") - host.computer.minimize_program(usr) - return TRUE + ntos = TRUE // Subtype for self_state /datum/tgui_module/crew_monitor/robot diff --git a/code/modules/tgui/states/default.dm b/code/modules/tgui/states/default.dm index 9ccc9c751e..ba8d132e0b 100644 --- a/code/modules/tgui/states/default.dm +++ b/code/modules/tgui/states/default.dm @@ -36,13 +36,29 @@ GLOBAL_DATUM_INIT(tgui_default_state, /datum/tgui_state/default, new) return STATUS_DISABLED // Otherwise they can keep the UI open. /mob/living/silicon/ai/default_can_use_tgui_topic(src_object) - . = shared_tgui_interaction(src_object) - if(. < STATUS_INTERACTIVE) + . = shared_tgui_interaction() + if(. != STATUS_INTERACTIVE) return - // The AI can interact with anything it can see nearby, or with cameras while wireless control is enabled. - if(!control_disabled && can_see(src_object)) + // Prevents the AI from using Topic on admin levels (by for example viewing through the court/thunderdome cameras) + // unless it's on the same level as the object it's interacting with. + var/turf/T = get_turf(src_object) + if(!T || !(z == T.z || (T.z in using_map.player_levels))) + return STATUS_CLOSE + + // If an object is in view then we can interact with it + if(src_object in view(client.view, src)) return STATUS_INTERACTIVE + + // If we're installed in a chassi, rather than transfered to an inteliCard or other container, then check if we have camera view + if(is_in_chassis()) + //stop AIs from leaving windows open and using then after they lose vision + if(cameranet && !cameranet.checkTurfVis(get_turf(src_object))) + return STATUS_CLOSE + return STATUS_INTERACTIVE + else if(get_dist(src_object, src) <= client.view) // View does not return what one would expect while installed in an inteliCard + return STATUS_INTERACTIVE + return STATUS_CLOSE /mob/living/simple_animal/default_can_use_tgui_topic(src_object) diff --git a/code/modules/tgui/tgui.dm b/code/modules/tgui/tgui.dm index 9af1296ab0..2f928ae6fa 100644 --- a/code/modules/tgui/tgui.dm +++ b/code/modules/tgui/tgui.dm @@ -34,8 +34,12 @@ var/status = STATUS_INTERACTIVE /// Topic state used to determine status/interactability. var/datum/tgui_state/state = null - // The map z-level to display. + /// The map z-level to display. var/map_z_level = 1 + /// The Parent UI + var/datum/tgui/parent_ui + /// Children of this UI + var/list/children = list() /** * public @@ -46,12 +50,13 @@ * required src_object datum The object or datum which owns the UI. * required interface string The interface used to render the UI. * optional title string The title of the UI. + * optional parent_ui datum/tgui The parent of this UI. * optional ui_x int Deprecated: Window width. * optional ui_y int Deprecated: Window height. * * return datum/tgui The requested UI. */ -/datum/tgui/New(mob/user, datum/src_object, interface, title, ui_x, ui_y) +/datum/tgui/New(mob/user, datum/src_object, interface, title, datum/tgui/parent_ui, ui_x, ui_y) src.user = user src.src_object = src_object src.window_key = "[REF(src_object)]-main" @@ -59,6 +64,9 @@ if(title) src.title = title src.state = src_object.tgui_state() + src.parent_ui = parent_ui + if(parent_ui) + parent_ui.children += src // Deprecated if(ui_x && ui_y) src.window_size = list(ui_x, ui_y) @@ -100,10 +108,13 @@ * * Close the UI, and all its children. */ -/datum/tgui/proc/close(can_be_suspended = TRUE) +/datum/tgui/proc/close(can_be_suspended = TRUE, logout = FALSE) if(closing) return closing = TRUE + for(var/datum/tgui/child in children) + child.close(can_be_suspended, logout) + children.Cut() // If we don't have window_id, open proc did not have the opportunity // to finish, therefore it's safe to skip this whole block. if(window) @@ -111,10 +122,11 @@ // and we want to keep them around, to allow user to read // the error message properly. window.release_lock() - window.close(can_be_suspended) + window.close(can_be_suspended, logout) src_object.tgui_close(user) SStgui.on_close(src) state = null + parent_ui = null qdel(src) /** @@ -209,7 +221,7 @@ "observer" = isobserver(user), ), ) - var/data = custom_data || with_data && src_object.tgui_data(user) + var/data = custom_data || with_data && src_object.tgui_data(user, src, state) if(data) json_data["data"] = data var/static_data = with_static_data && src_object.tgui_static_data(user) @@ -244,7 +256,7 @@ return // Update through a normal call to ui_interact if(status != STATUS_DISABLED && (autoupdate || force)) - src_object.tgui_interact(user, src) + src_object.tgui_interact(user, src, parent_ui) return // Update status only var/needs_update = process_status() @@ -262,6 +274,8 @@ /datum/tgui/proc/process_status() var/prev_status = status status = src_object.tgui_status(user, state) + if(parent_ui) + status = min(status, parent_ui.status) return prev_status != status /datum/tgui/proc/log_message(message) diff --git a/code/modules/tgui/tgui_window.dm b/code/modules/tgui/tgui_window.dm index 18cf715368..c78eda86d3 100644 --- a/code/modules/tgui/tgui_window.dm +++ b/code/modules/tgui/tgui_window.dm @@ -133,13 +133,19 @@ * * optional can_be_suspended bool */ -/datum/tgui_window/proc/close(can_be_suspended = TRUE) +/datum/tgui_window/proc/close(can_be_suspended = TRUE, logout = FALSE) if(!client) return if(can_be_suspended && can_be_suspended()) log_tgui(client, "[id]/close: suspending") status = TGUI_WINDOW_READY send_message("suspend") + // You would think that BYOND would null out client or make it stop passing istypes or, y'know, ANYTHING during + // logout, but nope! It appears to be perfectly valid to call winset by every means we can measure in Logout, + // and yet it causes a bad client runtime. To avoid that happening, we just have to know if we're in Logout or + // not. + if(!logout && client) + winset(client, null, "mapwindow.map.focus=true") return log_tgui(client, "[id]/close") release_lock() @@ -149,7 +155,8 @@ // to read the error message. if(!fatally_errored) client << browse(null, "window=[id]") - + if(!logout && client) + winset(client, null, "mapwindow.map.focus=true") /** * public * @@ -189,9 +196,9 @@ /datum/tgui_window/proc/send_asset(datum/asset/asset) if(!client || !asset) return - // if(istype(asset, /datum/asset/spritesheet)) - // var/datum/asset/spritesheet/spritesheet = asset - // send_message("asset/stylesheet", spritesheet.css_filename()) + if(istype(asset, /datum/asset/spritesheet)) + var/datum/asset/spritesheet/spritesheet = asset + send_message("asset/stylesheet", spritesheet.css_filename()) send_message("asset/mappings", asset.get_url_mappings()) sent_assets += list(asset) asset.send(client) diff --git a/code/modules/virus2/centrifuge.dm b/code/modules/virus2/centrifuge.dm index 36ec60e8cb..aaa65ce9e7 100644 --- a/code/modules/virus2/centrifuge.dm +++ b/code/modules/virus2/centrifuge.dm @@ -26,7 +26,7 @@ O.loc = src user.visible_message("[user] adds \a [O] to \the [src]!", "You add \a [O] to \the [src]!") - SSnanoui.update_uis(src) + SStgui.update_uis(src) src.attack_hand(user) @@ -36,27 +36,32 @@ icon_state = "centrifuge_moving" /obj/machinery/computer/centrifuge/attack_hand(var/mob/user as mob) - if(..()) return - ui_interact(user) + if(..()) + return + tgui_interact(user) -/obj/machinery/computer/centrifuge/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - user.set_machine(src) +/obj/machinery/computer/centrifuge/tgui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "IsolationCentrifuge", name) + ui.open() - var/data[0] +/obj/machinery/computer/centrifuge/tgui_data(mob/user) + var/list/data = list() data["antibodies"] = null - data["pathogens"] = null + data["pathogens"] = list() data["is_antibody_sample"] = null + data["busy"] = null + data["sample_inserted"] = !!sample - if (curing) + if(curing) data["busy"] = "Isolating antibodies..." - else if (isolating) + else if(isolating) data["busy"] = "Isolating pathogens..." else - data["sample_inserted"] = !!sample - - if (sample) + if(sample) var/datum/reagent/blood/B = locate(/datum/reagent/blood) in sample.reagents.reagent_list - if (B) + if(B) data["antibodies"] = antigens2string(B.data["antibodies"], none=null) var/list/pathogens[0] @@ -65,8 +70,7 @@ var/datum/disease2/disease/V = virus[ID] pathogens.Add(list(list("name" = V.name(), "spread_type" = V.spreadtype, "reference" = "\ref[V]"))) - if (pathogens.len > 0) - data["pathogens"] = pathogens + data["pathogens"] = pathogens else var/datum/reagent/antibodies/A = locate(/datum/reagent/antibodies) in sample.reagents.reagent_list @@ -74,103 +78,90 @@ data["antibodies"] = antigens2string(A.data["antibodies"], none=null) data["is_antibody_sample"] = 1 - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open) - if (!ui) - ui = new(user, src, ui_key, "isolation_centrifuge.tmpl", src.name, 400, 500) - ui.set_initial_data(data) - ui.open() + return data /obj/machinery/computer/centrifuge/process() ..() - if (stat & (NOPOWER|BROKEN)) return + if(stat & (NOPOWER|BROKEN)) return - if (curing) + if(curing) curing -= 1 - if (curing == 0) + if(curing == 0) cure() - if (isolating) + if(isolating) isolating -= 1 if(isolating == 0) isolate() -/obj/machinery/computer/centrifuge/Topic(href, href_list) - if (..()) return 1 +/obj/machinery/computer/centrifuge/tgui_act(action, params) + if(..()) + return TRUE var/mob/user = usr - var/datum/nanoui/ui = SSnanoui.get_open_ui(user, src, "main") + add_fingerprint(user) - src.add_fingerprint(user) - if (href_list["close"]) - user.unset_machine() - ui.close() - return 0 - - if (href_list["print"]) - print(user) - return 1 - - if(href_list["isolate"]) - var/datum/reagent/blood/B = locate(/datum/reagent/blood) in sample.reagents.reagent_list - if (B) - var/datum/disease2/disease/virus = locate(href_list["isolate"]) - virus2 = virus.getcopy() - isolating = 40 - update_icon() - return 1 - - switch(href_list["action"]) - if ("antibody") + switch(action) + if("print") + print(user) + . = TRUE + if("isolate") + var/datum/reagent/blood/B = locate(/datum/reagent/blood) in sample.reagents.reagent_list + if(B) + var/datum/disease2/disease/virus = locate(params["isolate"]) + virus2 = virus.getcopy() + isolating = 40 + update_icon() + . = TRUE + if("antibody") var/delay = 20 var/datum/reagent/blood/B = locate(/datum/reagent/blood) in sample.reagents.reagent_list - if (!B) + if(!B) state("\The [src] buzzes, \"No antibody carrier detected.\"", "blue") - return 1 + return TRUE var/has_toxins = locate(/datum/reagent/toxin) in sample.reagents.reagent_list var/has_radium = sample.reagents.has_reagent("radium") - if (has_toxins || has_radium) + if(has_toxins || has_radium) state("\The [src] beeps, \"Pathogen purging speed above nominal.\"", "blue") - if (has_toxins) + if(has_toxins) delay = delay/2 - if (has_radium) + if(has_radium) delay = delay/2 curing = round(delay) playsound(src, 'sound/machines/juicer.ogg', 50, 1) update_icon() - return 1 - + . = TRUE if("sample") if(sample) sample.loc = src.loc sample = null - return 1 + . = TRUE - return 0 /obj/machinery/computer/centrifuge/proc/cure() - if (!sample) return + if(!sample) return var/datum/reagent/blood/B = locate(/datum/reagent/blood) in sample.reagents.reagent_list - if (!B) return + if(!B) return var/list/data = list("antibodies" = B.data["antibodies"]) var/amt= sample.reagents.get_reagent_amount("blood") sample.reagents.remove_reagent("blood", amt) sample.reagents.add_reagent("antibodies", amt, data) - SSnanoui.update_uis(src) + SStgui.update_uis(src) update_icon() ping("\The [src] pings, \"Antibody isolated.\"") /obj/machinery/computer/centrifuge/proc/isolate() - if (!sample) return + if(!sample) return var/obj/item/weapon/virusdish/dish = new/obj/item/weapon/virusdish(loc) dish.virus2 = virus2 virus2 = null - SSnanoui.update_uis(src) + SStgui.update_uis(src) update_icon() ping("\The [src] pings, \"Pathogen isolated.\"") @@ -182,20 +173,20 @@ Sample: [sample.name]
"} - if (user) + if(user) P.info += "Generated By: [user.name]
" P.info += "
" var/datum/reagent/blood/B = locate(/datum/reagent/blood) in sample.reagents.reagent_list - if (B) + if(B) P.info += "Antibodies: " P.info += antigens2string(B.data["antibodies"]) P.info += "
" var/list/virus = B.data["virus2"] P.info += "Pathogens:
" - if (virus.len > 0) + if(virus.len > 0) for (var/ID in virus) var/datum/disease2/disease/V = virus[ID] P.info += "[V.name()]
" @@ -204,7 +195,7 @@ else var/datum/reagent/antibodies/A = locate(/datum/reagent/antibodies) in sample.reagents.reagent_list - if (A) + if(A) P.info += "The following antibodies have been isolated from the blood sample: " P.info += antigens2string(A.data["antibodies"]) P.info += "
" diff --git a/code/modules/virus2/disease2.dm b/code/modules/virus2/disease2.dm index 4681496aef..6d64f35039 100644 --- a/code/modules/virus2/disease2.dm +++ b/code/modules/virus2/disease2.dm @@ -238,6 +238,26 @@ var/global/list/virusDB = list() return r +/datum/disease2/disease/proc/get_tgui_info() + . = list( + "name" = name(), + "spreadtype" = spreadtype, + "antigen" = antigens2string(antigen), + "rate" = stageprob * 10, + "resistance" = resistance, + "species" = jointext(affected_species, ", "), + "symptoms" = list(), + "ref" = "\ref[src]", + ) + + for(var/datum/disease2/effectholder/E in effects) + .["symptoms"].Add(list(list( + "stage" = E.stage, + "name" = E.effect.name, + "strength" = "[E.multiplier >= 3 ? "Severe" : E.multiplier > 1 ? "Above Average" : "Average"]", + "aggressiveness" = E.chance * 15, + ))) + /datum/disease2/disease/proc/addToDB() if ("[uniqueID]" in virusDB) return 0 @@ -245,6 +265,8 @@ var/global/list/virusDB = list() v.fields["id"] = uniqueID v.fields["name"] = name() v.fields["description"] = get_info() + v.fields["tgui_description"] = get_tgui_info() + v.fields["tgui_description"]["record"] = "\ref[v]" v.fields["antigen"] = antigens2string(antigen) v.fields["spread type"] = spreadtype virusDB["[uniqueID]"] = v diff --git a/code/modules/virus2/diseasesplicer.dm b/code/modules/virus2/diseasesplicer.dm index 95657498a0..fb682f51d1 100644 --- a/code/modules/virus2/diseasesplicer.dm +++ b/code/modules/virus2/diseasesplicer.dm @@ -20,7 +20,7 @@ if(istype(I,/obj/item/weapon/virusdish)) var/mob/living/carbon/c = user - if (dish) + if(dish) to_chat(user, "\The [src] is already loaded.") return @@ -40,36 +40,46 @@ return src.attack_hand(user) /obj/machinery/computer/diseasesplicer/attack_hand(var/mob/user as mob) - if(..()) return - ui_interact(user) + if(..()) + return TRUE + tgui_interact(user) -/obj/machinery/computer/diseasesplicer/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - user.set_machine(src) +/obj/machinery/computer/diseasesplicer/tgui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "DiseaseSplicer", name) + ui.open() - var/data[0] +/obj/machinery/computer/diseasesplicer/tgui_data(mob/user) + var/list/data = list() data["dish_inserted"] = !!dish - data["growth"] = 0 - data["affected_species"] = null - if (memorybank) + data["buffer"] = null + if(memorybank) data["buffer"] = list("name" = (analysed ? memorybank.effect.name : "Unknown Symptom"), "stage" = memorybank.effect.stage) - if (species_buffer) + data["species_buffer"] = null + if(species_buffer) data["species_buffer"] = analysed ? jointext(species_buffer, ", ") : "Unknown Species" - if (splicing) + data["effects"] = null + data["info"] = null + data["growth"] = 0 + data["affected_species"] = null + data["busy"] = null + if(splicing) data["busy"] = "Splicing..." - else if (scanning) + else if(scanning) data["busy"] = "Scanning..." - else if (burning) + else if(burning) data["busy"] = "Copying data to disk..." - else if (dish) + else if(dish) data["growth"] = min(dish.growth, 100) - if (dish.virus2) - if (dish.virus2.affected_species) - data["affected_species"] = dish.analysed ? jointext(dish.virus2.affected_species, ", ") : "Unknown" + if(dish.virus2) + if(dish.virus2.affected_species) + data["affected_species"] = dish.analysed ? dish.virus2.affected_species : list() - if (dish.growth >= 50) + if(dish.growth >= 50) var/list/effects[0] for (var/datum/disease2/effectholder/e in dish.virus2.effects) effects.Add(list(list("name" = (dish.analysed ? e.effect.name : "Unknown"), "stage" = (e.stage), "reference" = "\ref[e]"))) @@ -81,11 +91,7 @@ else data["info"] = "No dish loaded." - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open) - if (!ui) - ui = new(user, src, ui_key, "disease_splicer.tmpl", src.name, 400, 600) - ui.set_initial_data(data) - ui.open() + return data /obj/machinery/computer/diseasesplicer/process() if(stat & (NOPOWER|BROKEN)) @@ -95,100 +101,94 @@ scanning -= 1 if(!scanning) ping("\The [src] pings, \"Analysis complete.\"") - SSnanoui.update_uis(src) + SStgui.update_uis(src) if(splicing) splicing -= 1 if(!splicing) ping("\The [src] pings, \"Splicing operation complete.\"") - SSnanoui.update_uis(src) + SStgui.update_uis(src) if(burning) burning -= 1 if(!burning) var/obj/item/weapon/diseasedisk/d = new /obj/item/weapon/diseasedisk(src.loc) d.analysed = analysed if(analysed) - if (memorybank) + if(memorybank) d.name = "[memorybank.effect.name] GNA disk (Stage: [memorybank.effect.stage])" d.effect = memorybank - else if (species_buffer) + else if(species_buffer) d.name = "[jointext(species_buffer, ", ")] GNA disk" d.species = species_buffer else - if (memorybank) + if(memorybank) d.name = "Unknown GNA disk (Stage: [memorybank.effect.stage])" d.effect = memorybank - else if (species_buffer) + else if(species_buffer) d.name = "Unknown Species GNA disk" d.species = species_buffer ping("\The [src] pings, \"Backup disk saved.\"") - SSnanoui.update_uis(src) + SStgui.update_uis(src) -/obj/machinery/computer/diseasesplicer/Topic(href, href_list) - if(..()) return 1 +/obj/machinery/computer/diseasesplicer/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state) + if(..()) + return TRUE var/mob/user = usr - var/datum/nanoui/ui = SSnanoui.get_open_ui(user, src, "main") + add_fingerprint(user) - src.add_fingerprint(user) + switch(action) + if("grab") + if(dish) + memorybank = locate(params["grab"]) + species_buffer = null + analysed = dish.analysed + dish = null + scanning = 10 + . = TRUE - if (href_list["close"]) - user.unset_machine() - ui.close() - return 0 + if("affected_species") + if(dish) + memorybank = null + species_buffer = dish.virus2.affected_species + analysed = dish.analysed + dish = null + scanning = 10 + . = TRUE - if (href_list["grab"]) - if (dish) - memorybank = locate(href_list["grab"]) - species_buffer = null - analysed = dish.analysed - dish = null - scanning = 10 - return 1 + if("eject") + if(dish) + dish.loc = src.loc + dish = null + . = TRUE - if (href_list["affected_species"]) - if (dish) - memorybank = null - species_buffer = dish.virus2.affected_species - analysed = dish.analysed - dish = null - scanning = 10 - return 1 + if("splice") + if(dish) + var/target = text2num(params["splice"]) // target = 1 to 4 for effects, 5 for species + if(memorybank && 0 < target && target <= 4) + if(target < memorybank.effect.stage) return // too powerful, catching this for href exploit prevention - if(href_list["eject"]) - if (dish) - dish.loc = src.loc - dish = null - return 1 + var/datum/disease2/effectholder/target_holder + var/list/illegal_types = list() + for(var/datum/disease2/effectholder/e in dish.virus2.effects) + if(e.stage == target) + target_holder = e + else + illegal_types += e.effect.type + if(memorybank.effect.type in illegal_types) return + target_holder.effect = memorybank.effect - if(href_list["splice"]) - if(dish) - var/target = text2num(href_list["splice"]) // target = 1 to 4 for effects, 5 for species - if(memorybank && 0 < target && target <= 4) - if(target < memorybank.effect.stage) return // too powerful, catching this for href exploit prevention + else if(species_buffer && target == 5) + dish.virus2.affected_species = species_buffer - var/datum/disease2/effectholder/target_holder - var/list/illegal_types = list() - for(var/datum/disease2/effectholder/e in dish.virus2.effects) - if(e.stage == target) - target_holder = e - else - illegal_types += e.effect.type - if(memorybank.effect.type in illegal_types) return - target_holder.effect = memorybank.effect + else + return - else if(species_buffer && target == 5) - dish.virus2.affected_species = species_buffer + splicing = 10 + dish.virus2.uniqueID = rand(0,10000) + . = TRUE - else - return + if("disk") + burning = 10 + . = TRUE - splicing = 10 - dish.virus2.uniqueID = rand(0,10000) - return 1 - - if(href_list["disk"]) - burning = 10 - return 1 - - return 0 diff --git a/code/modules/virus2/dishincubator.dm b/code/modules/virus2/dishincubator.dm index 3ebe688c78..f9ae91f9d8 100644 --- a/code/modules/virus2/dishincubator.dm +++ b/code/modules/virus2/dishincubator.dm @@ -30,7 +30,7 @@ O.loc = src user.visible_message("[user] adds \a [O] to \the [src]!", "You add \a [O] to \the [src]!") - SSnanoui.update_uis(src) + SStgui.update_uis(src) src.attack_hand(user) return @@ -46,17 +46,23 @@ O.loc = src user.visible_message("[user] adds \a [O] to \the [src]!", "You add \a [O] to \the [src]!") - SSnanoui.update_uis(src) + SStgui.update_uis(src) src.attack_hand(user) /obj/machinery/disease2/incubator/attack_hand(mob/user as mob) - if(stat & (NOPOWER|BROKEN)) return - ui_interact(user) + if(stat & (NOPOWER|BROKEN)) + return + tgui_interact(user) -/obj/machinery/disease2/incubator/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - user.set_machine(src) +/obj/machinery/disease2/incubator/tgui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "DishIncubator", name) + ui.set_autoupdate(FALSE) + ui.open() +/obj/machinery/disease2/incubator/tgui_data(mob/user) var/data[0] data["chemicals_inserted"] = !!beaker data["dish_inserted"] = !!dish @@ -74,23 +80,19 @@ data["can_breed_virus"] = null data["blood_already_infected"] = null - if (beaker) + if(beaker) var/datum/reagent/blood/B = locate(/datum/reagent/blood) in beaker.reagents.reagent_list data["can_breed_virus"] = dish && dish.virus2 && B - if (B) - if (!B.data["virus2"]) + if(B) + if(!B.data["virus2"]) B.data["virus2"] = list() var/list/virus = B.data["virus2"] for (var/ID in virus) data["blood_already_infected"] = virus[ID] - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open) - if (!ui) - ui = new(user, src, ui_key, "dish_incubator.tmpl", src.name, 400, 600) - ui.set_initial_data(data) - ui.open() + return data /obj/machinery/disease2/incubator/process() if(dish && on && dish.virus2) @@ -105,7 +107,7 @@ foodsupply -= 1 dish.growth += 3 - SSnanoui.update_uis(src) + SStgui.update_uis(src) if(radiation) if(radiation > 50 & prob(5)) @@ -118,90 +120,82 @@ else if(prob(5)) dish.virus2.minormutate() radiation -= 1 - SSnanoui.update_uis(src) + SStgui.update_uis(src) if(toxins && prob(5)) dish.virus2.infectionchance -= 1 - SSnanoui.update_uis(src) + SStgui.update_uis(src) if(toxins > 50) dish.growth = 0 dish.virus2 = null - SSnanoui.update_uis(src) + SStgui.update_uis(src) else if(!dish) on = 0 icon_state = "incubator" - SSnanoui.update_uis(src) + SStgui.update_uis(src) if(beaker) if(foodsupply < 100 && beaker.reagents.remove_reagent("virusfood",5)) if(foodsupply + 10 <= 100) foodsupply += 10 - SSnanoui.update_uis(src) + SStgui.update_uis(src) - if (locate(/datum/reagent/toxin) in beaker.reagents.reagent_list && toxins < 100) + if(locate(/datum/reagent/toxin) in beaker.reagents.reagent_list && toxins < 100) for(var/datum/reagent/toxin/T in beaker.reagents.reagent_list) toxins += max(T.strength,1) beaker.reagents.remove_reagent(T.id,1) if(toxins > 100) toxins = 100 break - SSnanoui.update_uis(src) + SStgui.update_uis(src) -/obj/machinery/disease2/incubator/Topic(href, href_list) - if (..()) return 1 +/obj/machinery/disease2/incubator/tgui_act(action, params) + if(..()) + return TRUE var/mob/user = usr - var/datum/nanoui/ui = SSnanoui.get_open_ui(user, src, "main") + add_fingerprint(user) + switch(action) + if("ejectchem") + if(beaker) + beaker.loc = src.loc + beaker = null + . = TRUE - src.add_fingerprint(user) + if("power") + if(dish) + on = !on + icon_state = on ? "incubator_on" : "incubator" + . = TRUE - if (href_list["close"]) - user.unset_machine() - ui.close() - return 0 + if("ejectdish") + if(dish) + dish.loc = src.loc + dish = null + . = TRUE - if (href_list["ejectchem"]) - if(beaker) - beaker.loc = src.loc - beaker = null - return 1 + if("rad") + radiation = min(100, radiation + 10) + . = TRUE - if (href_list["power"]) - if (dish) - on = !on - icon_state = on ? "incubator_on" : "incubator" - return 1 + if("flush") + radiation = 0 + toxins = 0 + foodsupply = 0 + . = TRUE - if (href_list["ejectdish"]) - if(dish) - dish.loc = src.loc - dish = null - return 1 + if("virus") + if(!dish) + return TRUE - if (href_list["rad"]) - radiation = min(100, radiation + 10) - return 1 + var/datum/reagent/blood/B = locate(/datum/reagent/blood) in beaker.reagents.reagent_list + if(!B) + return TRUE - if (href_list["flush"]) - radiation = 0 - toxins = 0 - foodsupply = 0 - return 1 + if(!B.data["virus2"]) + B.data["virus2"] = list() - if(href_list["virus"]) - if (!dish) - return 1 + var/list/virus = list("[dish.virus2.uniqueID]" = dish.virus2.getcopy()) + B.data["virus2"] += virus - var/datum/reagent/blood/B = locate(/datum/reagent/blood) in beaker.reagents.reagent_list - if (!B) - return 1 - - if (!B.data["virus2"]) - B.data["virus2"] = list() - - var/list/virus = list("[dish.virus2.uniqueID]" = dish.virus2.getcopy()) - B.data["virus2"] += virus - - ping("\The [src] pings, \"Injection complete.\"") - return 1 - - return 0 + ping("\The [src] pings, \"Injection complete.\"") + . = TRUE diff --git a/code/modules/virus2/isolator.dm b/code/modules/virus2/isolator.dm index d22b379ee1..d6fbc4fe99 100644 --- a/code/modules/virus2/isolator.dm +++ b/code/modules/virus2/isolator.dm @@ -1,8 +1,3 @@ -// UI menu navigation -#define HOME "home" -#define LIST "list" -#define ENTRY "entry" - /obj/machinery/disease2/isolator/ name = "pathogenic isolator" desc = "Used to isolate and identify diseases, allowing for comparison with a remote database." @@ -11,9 +6,7 @@ icon = 'icons/obj/virology.dmi' icon_state = "isolator" var/isolating = 0 - var/state = HOME var/datum/disease2/disease/virus2 = null - var/datum/data/record/entry = null var/obj/item/weapon/reagent_containers/syringe/sample = null /obj/machinery/disease2/isolator/update_icon() @@ -44,71 +37,57 @@ S.loc = src user.visible_message("[user] adds \a [O] to \the [src]!", "You add \a [O] to \the [src]!") - SSnanoui.update_uis(src) + SStgui.update_uis(src) update_icon() src.attack_hand(user) /obj/machinery/disease2/isolator/attack_hand(mob/user as mob) - if(stat & (NOPOWER|BROKEN)) return - ui_interact(user) + if(stat & (NOPOWER|BROKEN)) + return + tgui_interact(user) -/obj/machinery/disease2/isolator/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - user.set_machine(src) +/obj/machinery/disease2/isolator/tgui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "PathogenicIsolator", name) + ui.open() - var/data[0] + +/obj/machinery/disease2/isolator/tgui_data(mob/user) + var/list/data = list() data["syringe_inserted"] = !!sample data["isolating"] = isolating data["pathogen_pool"] = null - data["state"] = state - data["entry"] = entry - data["can_print"] = (state != HOME || sample) && !isolating + data["can_print"] = !isolating - switch (state) - if (HOME) - if (sample) - var/list/pathogen_pool[0] - for(var/datum/reagent/blood/B in sample.reagents.reagent_list) - var/list/virus = B.data["virus2"] - for (var/ID in virus) - var/datum/disease2/disease/V = virus[ID] - var/datum/data/record/R = null - if (ID in virusDB) - R = virusDB[ID] + var/list/pathogen_pool = list() + if(sample) + for(var/datum/reagent/blood/B in sample.reagents.reagent_list) + var/list/virus = B.data["virus2"] + for (var/ID in virus) + var/datum/disease2/disease/V = virus[ID] + var/datum/data/record/R = null + if (ID in virusDB) + R = virusDB[ID] - var/mob/living/carbon/human/D = B.data["donor"] - pathogen_pool.Add(list(list(\ - "name" = "[D.get_species()] [B.name]", \ - "dna" = B.data["blood_DNA"], \ - "unique_id" = V.uniqueID, \ - "reference" = "\ref[V]", \ - "is_in_database" = !!R, \ - "record" = "\ref[R]"))) + var/mob/living/carbon/human/D = B.data["donor"] + pathogen_pool.Add(list(list(\ + "name" = "[D.get_species()] [B.name]", \ + "dna" = B.data["blood_DNA"], \ + "unique_id" = V.uniqueID, \ + "reference" = "\ref[V]", \ + "is_in_database" = !!R, \ + "record" = "\ref[R]"))) + data["pathogen_pool"] = pathogen_pool - if (pathogen_pool.len > 0) - data["pathogen_pool"] = pathogen_pool - - if (LIST) - var/list/db[0] - for (var/ID in virusDB) - var/datum/data/record/r = virusDB[ID] - db.Add(list(list("name" = r.fields["name"], "record" = "\ref[r]"))) - - if (db.len > 0) - data["database"] = db - - if (ENTRY) - if (entry) - var/desc = entry.fields["description"] - data["entry"] = list(\ - "name" = entry.fields["name"], \ - "description" = replacetext(desc, "\n", "")) - - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open) - if (!ui) - ui = new(user, src, ui_key, "pathogenic_isolator.tmpl", src.name, 400, 500) - ui.set_initial_data(data) - ui.open() + var/list/db = list() + for(var/ID in virusDB) + var/datum/data/record/r = virusDB[ID] + db.Add(list(list("name" = r.fields["name"], "record" = "\ref[r]"))) + data["database"] = db + data["modal"] = tgui_modal_data(src) + return data /obj/machinery/disease2/isolator/process() if (isolating > 0) @@ -120,62 +99,54 @@ virus2 = null ping("\The [src] pings, \"Viral strain isolated.\"") - SSnanoui.update_uis(src) + SStgui.update_uis(src) update_icon() -/obj/machinery/disease2/isolator/Topic(href, href_list) - if (..()) return 1 +/obj/machinery/disease2/isolator/tgui_act(action, list/params) + if(..()) + return TRUE var/mob/user = usr - var/datum/nanoui/ui = SSnanoui.get_open_ui(user, src, "main") + add_fingerprint(user) + + . = TRUE + switch(tgui_modal_act(src, action, params)) + if(TGUI_MODAL_ANSWER) + return - src.add_fingerprint(user) + switch(action) + if("view_entry") + var/datum/data/record/v = locate(params["vir"]) + if(!istype(v)) + return FALSE + tgui_modal_message(src, "virus", "", null, v.fields["tgui_description"]) + return TRUE - if (href_list["close"]) - user.unset_machine() - ui.close() - return 0 + if("print") + print(user, params) + return TRUE - if (href_list[HOME]) - state = HOME - return 1 + if("isolate") + var/datum/disease2/disease/V = locate(params["isolate"]) + if (V) + virus2 = V + isolating = 20 + update_icon() + return TRUE - if (href_list[LIST]) - state = LIST - return 1 - - if (href_list[ENTRY]) - if (istype(locate(href_list["view"]), /datum/data/record)) - entry = locate(href_list["view"]) - - state = ENTRY - return 1 - - if (href_list["print"]) - print(user) - return 1 - - if(!sample) return 1 - - if (href_list["isolate"]) - var/datum/disease2/disease/V = locate(href_list["isolate"]) - if (V) - virus2 = V - isolating = 20 + if("eject") + if(!sample) + return FALSE + sample.forceMove(loc) + sample = null update_icon() - return 1 + return TRUE - if (href_list["eject"]) - sample.loc = src.loc - sample = null - update_icon() - return 1 - -/obj/machinery/disease2/isolator/proc/print(var/mob/user) +/obj/machinery/disease2/isolator/proc/print(mob/user, list/params) var/obj/item/weapon/paper/P = new /obj/item/weapon/paper(loc) - switch (state) - if (HOME) + switch(params["type"]) + if("patient_diagnosis") if (!sample) return P.name = "paper - Patient Diagnostic Report" P.info = {" @@ -207,7 +178,7 @@ Additional Notes:  "} - if (LIST) + if("virus_list") P.name = "paper - Virus List" P.info = {" [virology_letterhead("Virus List")] @@ -225,11 +196,14 @@ Additional Notes:  "} - if (ENTRY) + if("virus_record") + var/datum/data/record/v = locate(params["vir"]) + if(!istype(v)) + return FALSE P.name = "paper - Viral Profile" P.info = {" [virology_letterhead("Viral Profile")] - [entry.fields["description"]] + [v.fields["description"]]
Additional Notes:  "} diff --git a/html/font-awesome/css/all.min.css b/html/font-awesome/css/all.min.css index e7cdfffe75..a5e67e693d 100644 --- a/html/font-awesome/css/all.min.css +++ b/html/font-awesome/css/all.min.css @@ -1,5 +1,5 @@ /*! - * Font Awesome Free 5.9.0 by @fontawesome - https://fontawesome.com + * Font Awesome Free 5.14.0 by @fontawesome - https://fontawesome.com * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) */ -.fa,.fab,.fal,.far,.fas{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{animation:fa-spin 2s infinite linear}.fa-pulse{animation:fa-spin 1s infinite steps(8)}@keyframes fa-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";transform:scaleX(-1)}.fa-flip-vertical{transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{transform:scale(-1)}:root .fa-flip-both,:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2.5em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-500px:before{content:"\f26e"}.fa-accessible-icon:before{content:"\f368"}.fa-accusoft:before{content:"\f369"}.fa-acquisitions-incorporated:before{content:"\f6af"}.fa-ad:before{content:"\f641"}.fa-address-book:before{content:"\f2b9"}.fa-address-card:before{content:"\f2bb"}.fa-adjust:before{content:"\f042"}.fa-adn:before{content:"\f170"}.fa-adobe:before{content:"\f778"}.fa-adversal:before{content:"\f36a"}.fa-affiliatetheme:before{content:"\f36b"}.fa-air-freshener:before{content:"\f5d0"}.fa-airbnb:before{content:"\f834"}.fa-algolia:before{content:"\f36c"}.fa-align-center:before{content:"\f037"}.fa-align-justify:before{content:"\f039"}.fa-align-left:before{content:"\f036"}.fa-align-right:before{content:"\f038"}.fa-alipay:before{content:"\f642"}.fa-allergies:before{content:"\f461"}.fa-amazon:before{content:"\f270"}.fa-amazon-pay:before{content:"\f42c"}.fa-ambulance:before{content:"\f0f9"}.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-amilia:before{content:"\f36d"}.fa-anchor:before{content:"\f13d"}.fa-android:before{content:"\f17b"}.fa-angellist:before{content:"\f209"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-down:before{content:"\f107"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angry:before{content:"\f556"}.fa-angrycreative:before{content:"\f36e"}.fa-angular:before{content:"\f420"}.fa-ankh:before{content:"\f644"}.fa-app-store:before{content:"\f36f"}.fa-app-store-ios:before{content:"\f370"}.fa-apper:before{content:"\f371"}.fa-apple:before{content:"\f179"}.fa-apple-alt:before{content:"\f5d1"}.fa-apple-pay:before{content:"\f415"}.fa-archive:before{content:"\f187"}.fa-archway:before{content:"\f557"}.fa-arrow-alt-circle-down:before{content:"\f358"}.fa-arrow-alt-circle-left:before{content:"\f359"}.fa-arrow-alt-circle-right:before{content:"\f35a"}.fa-arrow-alt-circle-up:before{content:"\f35b"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-down:before{content:"\f063"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrows-alt:before{content:"\f0b2"}.fa-arrows-alt-h:before{content:"\f337"}.fa-arrows-alt-v:before{content:"\f338"}.fa-artstation:before{content:"\f77a"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asterisk:before{content:"\f069"}.fa-asymmetrik:before{content:"\f372"}.fa-at:before{content:"\f1fa"}.fa-atlas:before{content:"\f558"}.fa-atlassian:before{content:"\f77b"}.fa-atom:before{content:"\f5d2"}.fa-audible:before{content:"\f373"}.fa-audio-description:before{content:"\f29e"}.fa-autoprefixer:before{content:"\f41c"}.fa-avianex:before{content:"\f374"}.fa-aviato:before{content:"\f421"}.fa-award:before{content:"\f559"}.fa-aws:before{content:"\f375"}.fa-baby:before{content:"\f77c"}.fa-baby-carriage:before{content:"\f77d"}.fa-backspace:before{content:"\f55a"}.fa-backward:before{content:"\f04a"}.fa-bacon:before{content:"\f7e5"}.fa-balance-scale:before{content:"\f24e"}.fa-balance-scale-left:before{content:"\f515"}.fa-balance-scale-right:before{content:"\f516"}.fa-ban:before{content:"\f05e"}.fa-band-aid:before{content:"\f462"}.fa-bandcamp:before{content:"\f2d5"}.fa-barcode:before{content:"\f02a"}.fa-bars:before{content:"\f0c9"}.fa-baseball-ball:before{content:"\f433"}.fa-basketball-ball:before{content:"\f434"}.fa-bath:before{content:"\f2cd"}.fa-battery-empty:before{content:"\f244"}.fa-battery-full:before{content:"\f240"}.fa-battery-half:before{content:"\f242"}.fa-battery-quarter:before{content:"\f243"}.fa-battery-three-quarters:before{content:"\f241"}.fa-battle-net:before{content:"\f835"}.fa-bed:before{content:"\f236"}.fa-beer:before{content:"\f0fc"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-bell:before{content:"\f0f3"}.fa-bell-slash:before{content:"\f1f6"}.fa-bezier-curve:before{content:"\f55b"}.fa-bible:before{content:"\f647"}.fa-bicycle:before{content:"\f206"}.fa-biking:before{content:"\f84a"}.fa-bimobject:before{content:"\f378"}.fa-binoculars:before{content:"\f1e5"}.fa-biohazard:before{content:"\f780"}.fa-birthday-cake:before{content:"\f1fd"}.fa-bitbucket:before{content:"\f171"}.fa-bitcoin:before{content:"\f379"}.fa-bity:before{content:"\f37a"}.fa-black-tie:before{content:"\f27e"}.fa-blackberry:before{content:"\f37b"}.fa-blender:before{content:"\f517"}.fa-blender-phone:before{content:"\f6b6"}.fa-blind:before{content:"\f29d"}.fa-blog:before{content:"\f781"}.fa-blogger:before{content:"\f37c"}.fa-blogger-b:before{content:"\f37d"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-bold:before{content:"\f032"}.fa-bolt:before{content:"\f0e7"}.fa-bomb:before{content:"\f1e2"}.fa-bone:before{content:"\f5d7"}.fa-bong:before{content:"\f55c"}.fa-book:before{content:"\f02d"}.fa-book-dead:before{content:"\f6b7"}.fa-book-medical:before{content:"\f7e6"}.fa-book-open:before{content:"\f518"}.fa-book-reader:before{content:"\f5da"}.fa-bookmark:before{content:"\f02e"}.fa-bootstrap:before{content:"\f836"}.fa-border-all:before{content:"\f84c"}.fa-border-none:before{content:"\f850"}.fa-border-style:before{content:"\f853"}.fa-bowling-ball:before{content:"\f436"}.fa-box:before{content:"\f466"}.fa-box-open:before{content:"\f49e"}.fa-boxes:before{content:"\f468"}.fa-braille:before{content:"\f2a1"}.fa-brain:before{content:"\f5dc"}.fa-bread-slice:before{content:"\f7ec"}.fa-briefcase:before{content:"\f0b1"}.fa-briefcase-medical:before{content:"\f469"}.fa-broadcast-tower:before{content:"\f519"}.fa-broom:before{content:"\f51a"}.fa-brush:before{content:"\f55d"}.fa-btc:before{content:"\f15a"}.fa-buffer:before{content:"\f837"}.fa-bug:before{content:"\f188"}.fa-building:before{content:"\f1ad"}.fa-bullhorn:before{content:"\f0a1"}.fa-bullseye:before{content:"\f140"}.fa-burn:before{content:"\f46a"}.fa-buromobelexperte:before{content:"\f37f"}.fa-bus:before{content:"\f207"}.fa-bus-alt:before{content:"\f55e"}.fa-business-time:before{content:"\f64a"}.fa-buysellads:before{content:"\f20d"}.fa-calculator:before{content:"\f1ec"}.fa-calendar:before{content:"\f133"}.fa-calendar-alt:before{content:"\f073"}.fa-calendar-check:before{content:"\f274"}.fa-calendar-day:before{content:"\f783"}.fa-calendar-minus:before{content:"\f272"}.fa-calendar-plus:before{content:"\f271"}.fa-calendar-times:before{content:"\f273"}.fa-calendar-week:before{content:"\f784"}.fa-camera:before{content:"\f030"}.fa-camera-retro:before{content:"\f083"}.fa-campground:before{content:"\f6bb"}.fa-canadian-maple-leaf:before{content:"\f785"}.fa-candy-cane:before{content:"\f786"}.fa-cannabis:before{content:"\f55f"}.fa-capsules:before{content:"\f46b"}.fa-car:before{content:"\f1b9"}.fa-car-alt:before{content:"\f5de"}.fa-car-battery:before{content:"\f5df"}.fa-car-crash:before{content:"\f5e1"}.fa-car-side:before{content:"\f5e4"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-caret-square-down:before{content:"\f150"}.fa-caret-square-left:before{content:"\f191"}.fa-caret-square-right:before{content:"\f152"}.fa-caret-square-up:before{content:"\f151"}.fa-caret-up:before{content:"\f0d8"}.fa-carrot:before{content:"\f787"}.fa-cart-arrow-down:before{content:"\f218"}.fa-cart-plus:before{content:"\f217"}.fa-cash-register:before{content:"\f788"}.fa-cat:before{content:"\f6be"}.fa-cc-amazon-pay:before{content:"\f42d"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-apple-pay:before{content:"\f416"}.fa-cc-diners-club:before{content:"\f24c"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-cc-visa:before{content:"\f1f0"}.fa-centercode:before{content:"\f380"}.fa-centos:before{content:"\f789"}.fa-certificate:before{content:"\f0a3"}.fa-chair:before{content:"\f6c0"}.fa-chalkboard:before{content:"\f51b"}.fa-chalkboard-teacher:before{content:"\f51c"}.fa-charging-station:before{content:"\f5e7"}.fa-chart-area:before{content:"\f1fe"}.fa-chart-bar:before{content:"\f080"}.fa-chart-line:before{content:"\f201"}.fa-chart-pie:before{content:"\f200"}.fa-check:before{content:"\f00c"}.fa-check-circle:before{content:"\f058"}.fa-check-double:before{content:"\f560"}.fa-check-square:before{content:"\f14a"}.fa-cheese:before{content:"\f7ef"}.fa-chess:before{content:"\f439"}.fa-chess-bishop:before{content:"\f43a"}.fa-chess-board:before{content:"\f43c"}.fa-chess-king:before{content:"\f43f"}.fa-chess-knight:before{content:"\f441"}.fa-chess-pawn:before{content:"\f443"}.fa-chess-queen:before{content:"\f445"}.fa-chess-rook:before{content:"\f447"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-down:before{content:"\f078"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-chevron-up:before{content:"\f077"}.fa-child:before{content:"\f1ae"}.fa-chrome:before{content:"\f268"}.fa-chromecast:before{content:"\f838"}.fa-church:before{content:"\f51d"}.fa-circle:before{content:"\f111"}.fa-circle-notch:before{content:"\f1ce"}.fa-city:before{content:"\f64f"}.fa-clinic-medical:before{content:"\f7f2"}.fa-clipboard:before{content:"\f328"}.fa-clipboard-check:before{content:"\f46c"}.fa-clipboard-list:before{content:"\f46d"}.fa-clock:before{content:"\f017"}.fa-clone:before{content:"\f24d"}.fa-closed-captioning:before{content:"\f20a"}.fa-cloud:before{content:"\f0c2"}.fa-cloud-download-alt:before{content:"\f381"}.fa-cloud-meatball:before{content:"\f73b"}.fa-cloud-moon:before{content:"\f6c3"}.fa-cloud-moon-rain:before{content:"\f73c"}.fa-cloud-rain:before{content:"\f73d"}.fa-cloud-showers-heavy:before{content:"\f740"}.fa-cloud-sun:before{content:"\f6c4"}.fa-cloud-sun-rain:before{content:"\f743"}.fa-cloud-upload-alt:before{content:"\f382"}.fa-cloudscale:before{content:"\f383"}.fa-cloudsmith:before{content:"\f384"}.fa-cloudversify:before{content:"\f385"}.fa-cocktail:before{content:"\f561"}.fa-code:before{content:"\f121"}.fa-code-branch:before{content:"\f126"}.fa-codepen:before{content:"\f1cb"}.fa-codiepie:before{content:"\f284"}.fa-coffee:before{content:"\f0f4"}.fa-cog:before{content:"\f013"}.fa-cogs:before{content:"\f085"}.fa-coins:before{content:"\f51e"}.fa-columns:before{content:"\f0db"}.fa-comment:before{content:"\f075"}.fa-comment-alt:before{content:"\f27a"}.fa-comment-dollar:before{content:"\f651"}.fa-comment-dots:before{content:"\f4ad"}.fa-comment-medical:before{content:"\f7f5"}.fa-comment-slash:before{content:"\f4b3"}.fa-comments:before{content:"\f086"}.fa-comments-dollar:before{content:"\f653"}.fa-compact-disc:before{content:"\f51f"}.fa-compass:before{content:"\f14e"}.fa-compress:before{content:"\f066"}.fa-compress-arrows-alt:before{content:"\f78c"}.fa-concierge-bell:before{content:"\f562"}.fa-confluence:before{content:"\f78d"}.fa-connectdevelop:before{content:"\f20e"}.fa-contao:before{content:"\f26d"}.fa-cookie:before{content:"\f563"}.fa-cookie-bite:before{content:"\f564"}.fa-copy:before{content:"\f0c5"}.fa-copyright:before{content:"\f1f9"}.fa-couch:before{content:"\f4b8"}.fa-cpanel:before{content:"\f388"}.fa-creative-commons:before{content:"\f25e"}.fa-creative-commons-by:before{content:"\f4e7"}.fa-creative-commons-nc:before{content:"\f4e8"}.fa-creative-commons-nc-eu:before{content:"\f4e9"}.fa-creative-commons-nc-jp:before{content:"\f4ea"}.fa-creative-commons-nd:before{content:"\f4eb"}.fa-creative-commons-pd:before{content:"\f4ec"}.fa-creative-commons-pd-alt:before{content:"\f4ed"}.fa-creative-commons-remix:before{content:"\f4ee"}.fa-creative-commons-sa:before{content:"\f4ef"}.fa-creative-commons-sampling:before{content:"\f4f0"}.fa-creative-commons-sampling-plus:before{content:"\f4f1"}.fa-creative-commons-share:before{content:"\f4f2"}.fa-creative-commons-zero:before{content:"\f4f3"}.fa-credit-card:before{content:"\f09d"}.fa-critical-role:before{content:"\f6c9"}.fa-crop:before{content:"\f125"}.fa-crop-alt:before{content:"\f565"}.fa-cross:before{content:"\f654"}.fa-crosshairs:before{content:"\f05b"}.fa-crow:before{content:"\f520"}.fa-crown:before{content:"\f521"}.fa-crutch:before{content:"\f7f7"}.fa-css3:before{content:"\f13c"}.fa-css3-alt:before{content:"\f38b"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-cut:before{content:"\f0c4"}.fa-cuttlefish:before{content:"\f38c"}.fa-d-and-d:before{content:"\f38d"}.fa-d-and-d-beyond:before{content:"\f6ca"}.fa-dashcube:before{content:"\f210"}.fa-database:before{content:"\f1c0"}.fa-deaf:before{content:"\f2a4"}.fa-delicious:before{content:"\f1a5"}.fa-democrat:before{content:"\f747"}.fa-deploydog:before{content:"\f38e"}.fa-deskpro:before{content:"\f38f"}.fa-desktop:before{content:"\f108"}.fa-dev:before{content:"\f6cc"}.fa-deviantart:before{content:"\f1bd"}.fa-dharmachakra:before{content:"\f655"}.fa-dhl:before{content:"\f790"}.fa-diagnoses:before{content:"\f470"}.fa-diaspora:before{content:"\f791"}.fa-dice:before{content:"\f522"}.fa-dice-d20:before{content:"\f6cf"}.fa-dice-d6:before{content:"\f6d1"}.fa-dice-five:before{content:"\f523"}.fa-dice-four:before{content:"\f524"}.fa-dice-one:before{content:"\f525"}.fa-dice-six:before{content:"\f526"}.fa-dice-three:before{content:"\f527"}.fa-dice-two:before{content:"\f528"}.fa-digg:before{content:"\f1a6"}.fa-digital-ocean:before{content:"\f391"}.fa-digital-tachograph:before{content:"\f566"}.fa-directions:before{content:"\f5eb"}.fa-discord:before{content:"\f392"}.fa-discourse:before{content:"\f393"}.fa-divide:before{content:"\f529"}.fa-dizzy:before{content:"\f567"}.fa-dna:before{content:"\f471"}.fa-dochub:before{content:"\f394"}.fa-docker:before{content:"\f395"}.fa-dog:before{content:"\f6d3"}.fa-dollar-sign:before{content:"\f155"}.fa-dolly:before{content:"\f472"}.fa-dolly-flatbed:before{content:"\f474"}.fa-donate:before{content:"\f4b9"}.fa-door-closed:before{content:"\f52a"}.fa-door-open:before{content:"\f52b"}.fa-dot-circle:before{content:"\f192"}.fa-dove:before{content:"\f4ba"}.fa-download:before{content:"\f019"}.fa-draft2digital:before{content:"\f396"}.fa-drafting-compass:before{content:"\f568"}.fa-dragon:before{content:"\f6d5"}.fa-draw-polygon:before{content:"\f5ee"}.fa-dribbble:before{content:"\f17d"}.fa-dribbble-square:before{content:"\f397"}.fa-dropbox:before{content:"\f16b"}.fa-drum:before{content:"\f569"}.fa-drum-steelpan:before{content:"\f56a"}.fa-drumstick-bite:before{content:"\f6d7"}.fa-drupal:before{content:"\f1a9"}.fa-dumbbell:before{content:"\f44b"}.fa-dumpster:before{content:"\f793"}.fa-dumpster-fire:before{content:"\f794"}.fa-dungeon:before{content:"\f6d9"}.fa-dyalog:before{content:"\f399"}.fa-earlybirds:before{content:"\f39a"}.fa-ebay:before{content:"\f4f4"}.fa-edge:before{content:"\f282"}.fa-edit:before{content:"\f044"}.fa-egg:before{content:"\f7fb"}.fa-eject:before{content:"\f052"}.fa-elementor:before{content:"\f430"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-ello:before{content:"\f5f1"}.fa-ember:before{content:"\f423"}.fa-empire:before{content:"\f1d1"}.fa-envelope:before{content:"\f0e0"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-text:before{content:"\f658"}.fa-envelope-square:before{content:"\f199"}.fa-envira:before{content:"\f299"}.fa-equals:before{content:"\f52c"}.fa-eraser:before{content:"\f12d"}.fa-erlang:before{content:"\f39d"}.fa-ethereum:before{content:"\f42e"}.fa-ethernet:before{content:"\f796"}.fa-etsy:before{content:"\f2d7"}.fa-euro-sign:before{content:"\f153"}.fa-evernote:before{content:"\f839"}.fa-exchange-alt:before{content:"\f362"}.fa-exclamation:before{content:"\f12a"}.fa-exclamation-circle:before{content:"\f06a"}.fa-exclamation-triangle:before{content:"\f071"}.fa-expand:before{content:"\f065"}.fa-expand-arrows-alt:before{content:"\f31e"}.fa-expeditedssl:before{content:"\f23e"}.fa-external-link-alt:before{content:"\f35d"}.fa-external-link-square-alt:before{content:"\f360"}.fa-eye:before{content:"\f06e"}.fa-eye-dropper:before{content:"\f1fb"}.fa-eye-slash:before{content:"\f070"}.fa-facebook:before{content:"\f09a"}.fa-facebook-f:before{content:"\f39e"}.fa-facebook-messenger:before{content:"\f39f"}.fa-facebook-square:before{content:"\f082"}.fa-fan:before{content:"\f863"}.fa-fantasy-flight-games:before{content:"\f6dc"}.fa-fast-backward:before{content:"\f049"}.fa-fast-forward:before{content:"\f050"}.fa-fax:before{content:"\f1ac"}.fa-feather:before{content:"\f52d"}.fa-feather-alt:before{content:"\f56b"}.fa-fedex:before{content:"\f797"}.fa-fedora:before{content:"\f798"}.fa-female:before{content:"\f182"}.fa-fighter-jet:before{content:"\f0fb"}.fa-figma:before{content:"\f799"}.fa-file:before{content:"\f15b"}.fa-file-alt:before{content:"\f15c"}.fa-file-archive:before{content:"\f1c6"}.fa-file-audio:before{content:"\f1c7"}.fa-file-code:before{content:"\f1c9"}.fa-file-contract:before{content:"\f56c"}.fa-file-csv:before{content:"\f6dd"}.fa-file-download:before{content:"\f56d"}.fa-file-excel:before{content:"\f1c3"}.fa-file-export:before{content:"\f56e"}.fa-file-image:before{content:"\f1c5"}.fa-file-import:before{content:"\f56f"}.fa-file-invoice:before{content:"\f570"}.fa-file-invoice-dollar:before{content:"\f571"}.fa-file-medical:before{content:"\f477"}.fa-file-medical-alt:before{content:"\f478"}.fa-file-pdf:before{content:"\f1c1"}.fa-file-powerpoint:before{content:"\f1c4"}.fa-file-prescription:before{content:"\f572"}.fa-file-signature:before{content:"\f573"}.fa-file-upload:before{content:"\f574"}.fa-file-video:before{content:"\f1c8"}.fa-file-word:before{content:"\f1c2"}.fa-fill:before{content:"\f575"}.fa-fill-drip:before{content:"\f576"}.fa-film:before{content:"\f008"}.fa-filter:before{content:"\f0b0"}.fa-fingerprint:before{content:"\f577"}.fa-fire:before{content:"\f06d"}.fa-fire-alt:before{content:"\f7e4"}.fa-fire-extinguisher:before{content:"\f134"}.fa-firefox:before{content:"\f269"}.fa-first-aid:before{content:"\f479"}.fa-first-order:before{content:"\f2b0"}.fa-first-order-alt:before{content:"\f50a"}.fa-firstdraft:before{content:"\f3a1"}.fa-fish:before{content:"\f578"}.fa-fist-raised:before{content:"\f6de"}.fa-flag:before{content:"\f024"}.fa-flag-checkered:before{content:"\f11e"}.fa-flag-usa:before{content:"\f74d"}.fa-flask:before{content:"\f0c3"}.fa-flickr:before{content:"\f16e"}.fa-flipboard:before{content:"\f44d"}.fa-flushed:before{content:"\f579"}.fa-fly:before{content:"\f417"}.fa-folder:before{content:"\f07b"}.fa-folder-minus:before{content:"\f65d"}.fa-folder-open:before{content:"\f07c"}.fa-folder-plus:before{content:"\f65e"}.fa-font:before{content:"\f031"}.fa-font-awesome:before{content:"\f2b4"}.fa-font-awesome-alt:before{content:"\f35c"}.fa-font-awesome-flag:before{content:"\f425"}.fa-font-awesome-logo-full:before{content:"\f4e6"}.fa-fonticons:before{content:"\f280"}.fa-fonticons-fi:before{content:"\f3a2"}.fa-football-ball:before{content:"\f44e"}.fa-fort-awesome:before{content:"\f286"}.fa-fort-awesome-alt:before{content:"\f3a3"}.fa-forumbee:before{content:"\f211"}.fa-forward:before{content:"\f04e"}.fa-foursquare:before{content:"\f180"}.fa-free-code-camp:before{content:"\f2c5"}.fa-freebsd:before{content:"\f3a4"}.fa-frog:before{content:"\f52e"}.fa-frown:before{content:"\f119"}.fa-frown-open:before{content:"\f57a"}.fa-fulcrum:before{content:"\f50b"}.fa-funnel-dollar:before{content:"\f662"}.fa-futbol:before{content:"\f1e3"}.fa-galactic-republic:before{content:"\f50c"}.fa-galactic-senate:before{content:"\f50d"}.fa-gamepad:before{content:"\f11b"}.fa-gas-pump:before{content:"\f52f"}.fa-gavel:before{content:"\f0e3"}.fa-gem:before{content:"\f3a5"}.fa-genderless:before{content:"\f22d"}.fa-get-pocket:before{content:"\f265"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-ghost:before{content:"\f6e2"}.fa-gift:before{content:"\f06b"}.fa-gifts:before{content:"\f79c"}.fa-git:before{content:"\f1d3"}.fa-git-alt:before{content:"\f841"}.fa-git-square:before{content:"\f1d2"}.fa-github:before{content:"\f09b"}.fa-github-alt:before{content:"\f113"}.fa-github-square:before{content:"\f092"}.fa-gitkraken:before{content:"\f3a6"}.fa-gitlab:before{content:"\f296"}.fa-gitter:before{content:"\f426"}.fa-glass-cheers:before{content:"\f79f"}.fa-glass-martini:before{content:"\f000"}.fa-glass-martini-alt:before{content:"\f57b"}.fa-glass-whiskey:before{content:"\f7a0"}.fa-glasses:before{content:"\f530"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-globe:before{content:"\f0ac"}.fa-globe-africa:before{content:"\f57c"}.fa-globe-americas:before{content:"\f57d"}.fa-globe-asia:before{content:"\f57e"}.fa-globe-europe:before{content:"\f7a2"}.fa-gofore:before{content:"\f3a7"}.fa-golf-ball:before{content:"\f450"}.fa-goodreads:before{content:"\f3a8"}.fa-goodreads-g:before{content:"\f3a9"}.fa-google:before{content:"\f1a0"}.fa-google-drive:before{content:"\f3aa"}.fa-google-play:before{content:"\f3ab"}.fa-google-plus:before{content:"\f2b3"}.fa-google-plus-g:before{content:"\f0d5"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-wallet:before{content:"\f1ee"}.fa-gopuram:before{content:"\f664"}.fa-graduation-cap:before{content:"\f19d"}.fa-gratipay:before{content:"\f184"}.fa-grav:before{content:"\f2d6"}.fa-greater-than:before{content:"\f531"}.fa-greater-than-equal:before{content:"\f532"}.fa-grimace:before{content:"\f57f"}.fa-grin:before{content:"\f580"}.fa-grin-alt:before{content:"\f581"}.fa-grin-beam:before{content:"\f582"}.fa-grin-beam-sweat:before{content:"\f583"}.fa-grin-hearts:before{content:"\f584"}.fa-grin-squint:before{content:"\f585"}.fa-grin-squint-tears:before{content:"\f586"}.fa-grin-stars:before{content:"\f587"}.fa-grin-tears:before{content:"\f588"}.fa-grin-tongue:before{content:"\f589"}.fa-grin-tongue-squint:before{content:"\f58a"}.fa-grin-tongue-wink:before{content:"\f58b"}.fa-grin-wink:before{content:"\f58c"}.fa-grip-horizontal:before{content:"\f58d"}.fa-grip-lines:before{content:"\f7a4"}.fa-grip-lines-vertical:before{content:"\f7a5"}.fa-grip-vertical:before{content:"\f58e"}.fa-gripfire:before{content:"\f3ac"}.fa-grunt:before{content:"\f3ad"}.fa-guitar:before{content:"\f7a6"}.fa-gulp:before{content:"\f3ae"}.fa-h-square:before{content:"\f0fd"}.fa-hacker-news:before{content:"\f1d4"}.fa-hacker-news-square:before{content:"\f3af"}.fa-hackerrank:before{content:"\f5f7"}.fa-hamburger:before{content:"\f805"}.fa-hammer:before{content:"\f6e3"}.fa-hamsa:before{content:"\f665"}.fa-hand-holding:before{content:"\f4bd"}.fa-hand-holding-heart:before{content:"\f4be"}.fa-hand-holding-usd:before{content:"\f4c0"}.fa-hand-lizard:before{content:"\f258"}.fa-hand-middle-finger:before{content:"\f806"}.fa-hand-paper:before{content:"\f256"}.fa-hand-peace:before{content:"\f25b"}.fa-hand-point-down:before{content:"\f0a7"}.fa-hand-point-left:before{content:"\f0a5"}.fa-hand-point-right:before{content:"\f0a4"}.fa-hand-point-up:before{content:"\f0a6"}.fa-hand-pointer:before{content:"\f25a"}.fa-hand-rock:before{content:"\f255"}.fa-hand-scissors:before{content:"\f257"}.fa-hand-spock:before{content:"\f259"}.fa-hands:before{content:"\f4c2"}.fa-hands-helping:before{content:"\f4c4"}.fa-handshake:before{content:"\f2b5"}.fa-hanukiah:before{content:"\f6e6"}.fa-hard-hat:before{content:"\f807"}.fa-hashtag:before{content:"\f292"}.fa-hat-wizard:before{content:"\f6e8"}.fa-haykal:before{content:"\f666"}.fa-hdd:before{content:"\f0a0"}.fa-heading:before{content:"\f1dc"}.fa-headphones:before{content:"\f025"}.fa-headphones-alt:before{content:"\f58f"}.fa-headset:before{content:"\f590"}.fa-heart:before{content:"\f004"}.fa-heart-broken:before{content:"\f7a9"}.fa-heartbeat:before{content:"\f21e"}.fa-helicopter:before{content:"\f533"}.fa-highlighter:before{content:"\f591"}.fa-hiking:before{content:"\f6ec"}.fa-hippo:before{content:"\f6ed"}.fa-hips:before{content:"\f452"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-history:before{content:"\f1da"}.fa-hockey-puck:before{content:"\f453"}.fa-holly-berry:before{content:"\f7aa"}.fa-home:before{content:"\f015"}.fa-hooli:before{content:"\f427"}.fa-hornbill:before{content:"\f592"}.fa-horse:before{content:"\f6f0"}.fa-horse-head:before{content:"\f7ab"}.fa-hospital:before{content:"\f0f8"}.fa-hospital-alt:before{content:"\f47d"}.fa-hospital-symbol:before{content:"\f47e"}.fa-hot-tub:before{content:"\f593"}.fa-hotdog:before{content:"\f80f"}.fa-hotel:before{content:"\f594"}.fa-hotjar:before{content:"\f3b1"}.fa-hourglass:before{content:"\f254"}.fa-hourglass-end:before{content:"\f253"}.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-start:before{content:"\f251"}.fa-house-damage:before{content:"\f6f1"}.fa-houzz:before{content:"\f27c"}.fa-hryvnia:before{content:"\f6f2"}.fa-html5:before{content:"\f13b"}.fa-hubspot:before{content:"\f3b2"}.fa-i-cursor:before{content:"\f246"}.fa-ice-cream:before{content:"\f810"}.fa-icicles:before{content:"\f7ad"}.fa-icons:before{content:"\f86d"}.fa-id-badge:before{content:"\f2c1"}.fa-id-card:before{content:"\f2c2"}.fa-id-card-alt:before{content:"\f47f"}.fa-igloo:before{content:"\f7ae"}.fa-image:before{content:"\f03e"}.fa-images:before{content:"\f302"}.fa-imdb:before{content:"\f2d8"}.fa-inbox:before{content:"\f01c"}.fa-indent:before{content:"\f03c"}.fa-industry:before{content:"\f275"}.fa-infinity:before{content:"\f534"}.fa-info:before{content:"\f129"}.fa-info-circle:before{content:"\f05a"}.fa-instagram:before{content:"\f16d"}.fa-intercom:before{content:"\f7af"}.fa-internet-explorer:before{content:"\f26b"}.fa-invision:before{content:"\f7b0"}.fa-ioxhost:before{content:"\f208"}.fa-italic:before{content:"\f033"}.fa-itch-io:before{content:"\f83a"}.fa-itunes:before{content:"\f3b4"}.fa-itunes-note:before{content:"\f3b5"}.fa-java:before{content:"\f4e4"}.fa-jedi:before{content:"\f669"}.fa-jedi-order:before{content:"\f50e"}.fa-jenkins:before{content:"\f3b6"}.fa-jira:before{content:"\f7b1"}.fa-joget:before{content:"\f3b7"}.fa-joint:before{content:"\f595"}.fa-joomla:before{content:"\f1aa"}.fa-journal-whills:before{content:"\f66a"}.fa-js:before{content:"\f3b8"}.fa-js-square:before{content:"\f3b9"}.fa-jsfiddle:before{content:"\f1cc"}.fa-kaaba:before{content:"\f66b"}.fa-kaggle:before{content:"\f5fa"}.fa-key:before{content:"\f084"}.fa-keybase:before{content:"\f4f5"}.fa-keyboard:before{content:"\f11c"}.fa-keycdn:before{content:"\f3ba"}.fa-khanda:before{content:"\f66d"}.fa-kickstarter:before{content:"\f3bb"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-kiss:before{content:"\f596"}.fa-kiss-beam:before{content:"\f597"}.fa-kiss-wink-heart:before{content:"\f598"}.fa-kiwi-bird:before{content:"\f535"}.fa-korvue:before{content:"\f42f"}.fa-landmark:before{content:"\f66f"}.fa-language:before{content:"\f1ab"}.fa-laptop:before{content:"\f109"}.fa-laptop-code:before{content:"\f5fc"}.fa-laptop-medical:before{content:"\f812"}.fa-laravel:before{content:"\f3bd"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-laugh:before{content:"\f599"}.fa-laugh-beam:before{content:"\f59a"}.fa-laugh-squint:before{content:"\f59b"}.fa-laugh-wink:before{content:"\f59c"}.fa-layer-group:before{content:"\f5fd"}.fa-leaf:before{content:"\f06c"}.fa-leanpub:before{content:"\f212"}.fa-lemon:before{content:"\f094"}.fa-less:before{content:"\f41d"}.fa-less-than:before{content:"\f536"}.fa-less-than-equal:before{content:"\f537"}.fa-level-down-alt:before{content:"\f3be"}.fa-level-up-alt:before{content:"\f3bf"}.fa-life-ring:before{content:"\f1cd"}.fa-lightbulb:before{content:"\f0eb"}.fa-line:before{content:"\f3c0"}.fa-link:before{content:"\f0c1"}.fa-linkedin:before{content:"\f08c"}.fa-linkedin-in:before{content:"\f0e1"}.fa-linode:before{content:"\f2b8"}.fa-linux:before{content:"\f17c"}.fa-lira-sign:before{content:"\f195"}.fa-list:before{content:"\f03a"}.fa-list-alt:before{content:"\f022"}.fa-list-ol:before{content:"\f0cb"}.fa-list-ul:before{content:"\f0ca"}.fa-location-arrow:before{content:"\f124"}.fa-lock:before{content:"\f023"}.fa-lock-open:before{content:"\f3c1"}.fa-long-arrow-alt-down:before{content:"\f309"}.fa-long-arrow-alt-left:before{content:"\f30a"}.fa-long-arrow-alt-right:before{content:"\f30b"}.fa-long-arrow-alt-up:before{content:"\f30c"}.fa-low-vision:before{content:"\f2a8"}.fa-luggage-cart:before{content:"\f59d"}.fa-lyft:before{content:"\f3c3"}.fa-magento:before{content:"\f3c4"}.fa-magic:before{content:"\f0d0"}.fa-magnet:before{content:"\f076"}.fa-mail-bulk:before{content:"\f674"}.fa-mailchimp:before{content:"\f59e"}.fa-male:before{content:"\f183"}.fa-mandalorian:before{content:"\f50f"}.fa-map:before{content:"\f279"}.fa-map-marked:before{content:"\f59f"}.fa-map-marked-alt:before{content:"\f5a0"}.fa-map-marker:before{content:"\f041"}.fa-map-marker-alt:before{content:"\f3c5"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-markdown:before{content:"\f60f"}.fa-marker:before{content:"\f5a1"}.fa-mars:before{content:"\f222"}.fa-mars-double:before{content:"\f227"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mask:before{content:"\f6fa"}.fa-mastodon:before{content:"\f4f6"}.fa-maxcdn:before{content:"\f136"}.fa-medal:before{content:"\f5a2"}.fa-medapps:before{content:"\f3c6"}.fa-medium:before{content:"\f23a"}.fa-medium-m:before{content:"\f3c7"}.fa-medkit:before{content:"\f0fa"}.fa-medrt:before{content:"\f3c8"}.fa-meetup:before{content:"\f2e0"}.fa-megaport:before{content:"\f5a3"}.fa-meh:before{content:"\f11a"}.fa-meh-blank:before{content:"\f5a4"}.fa-meh-rolling-eyes:before{content:"\f5a5"}.fa-memory:before{content:"\f538"}.fa-mendeley:before{content:"\f7b3"}.fa-menorah:before{content:"\f676"}.fa-mercury:before{content:"\f223"}.fa-meteor:before{content:"\f753"}.fa-microchip:before{content:"\f2db"}.fa-microphone:before{content:"\f130"}.fa-microphone-alt:before{content:"\f3c9"}.fa-microphone-alt-slash:before{content:"\f539"}.fa-microphone-slash:before{content:"\f131"}.fa-microscope:before{content:"\f610"}.fa-microsoft:before{content:"\f3ca"}.fa-minus:before{content:"\f068"}.fa-minus-circle:before{content:"\f056"}.fa-minus-square:before{content:"\f146"}.fa-mitten:before{content:"\f7b5"}.fa-mix:before{content:"\f3cb"}.fa-mixcloud:before{content:"\f289"}.fa-mizuni:before{content:"\f3cc"}.fa-mobile:before{content:"\f10b"}.fa-mobile-alt:before{content:"\f3cd"}.fa-modx:before{content:"\f285"}.fa-monero:before{content:"\f3d0"}.fa-money-bill:before{content:"\f0d6"}.fa-money-bill-alt:before{content:"\f3d1"}.fa-money-bill-wave:before{content:"\f53a"}.fa-money-bill-wave-alt:before{content:"\f53b"}.fa-money-check:before{content:"\f53c"}.fa-money-check-alt:before{content:"\f53d"}.fa-monument:before{content:"\f5a6"}.fa-moon:before{content:"\f186"}.fa-mortar-pestle:before{content:"\f5a7"}.fa-mosque:before{content:"\f678"}.fa-motorcycle:before{content:"\f21c"}.fa-mountain:before{content:"\f6fc"}.fa-mouse-pointer:before{content:"\f245"}.fa-mug-hot:before{content:"\f7b6"}.fa-music:before{content:"\f001"}.fa-napster:before{content:"\f3d2"}.fa-neos:before{content:"\f612"}.fa-network-wired:before{content:"\f6ff"}.fa-neuter:before{content:"\f22c"}.fa-newspaper:before{content:"\f1ea"}.fa-nimblr:before{content:"\f5a8"}.fa-node:before{content:"\f419"}.fa-node-js:before{content:"\f3d3"}.fa-not-equal:before{content:"\f53e"}.fa-notes-medical:before{content:"\f481"}.fa-npm:before{content:"\f3d4"}.fa-ns8:before{content:"\f3d5"}.fa-nutritionix:before{content:"\f3d6"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-oil-can:before{content:"\f613"}.fa-old-republic:before{content:"\f510"}.fa-om:before{content:"\f679"}.fa-opencart:before{content:"\f23d"}.fa-openid:before{content:"\f19b"}.fa-opera:before{content:"\f26a"}.fa-optin-monster:before{content:"\f23c"}.fa-osi:before{content:"\f41a"}.fa-otter:before{content:"\f700"}.fa-outdent:before{content:"\f03b"}.fa-page4:before{content:"\f3d7"}.fa-pagelines:before{content:"\f18c"}.fa-pager:before{content:"\f815"}.fa-paint-brush:before{content:"\f1fc"}.fa-paint-roller:before{content:"\f5aa"}.fa-palette:before{content:"\f53f"}.fa-palfed:before{content:"\f3d8"}.fa-pallet:before{content:"\f482"}.fa-paper-plane:before{content:"\f1d8"}.fa-paperclip:before{content:"\f0c6"}.fa-parachute-box:before{content:"\f4cd"}.fa-paragraph:before{content:"\f1dd"}.fa-parking:before{content:"\f540"}.fa-passport:before{content:"\f5ab"}.fa-pastafarianism:before{content:"\f67b"}.fa-paste:before{content:"\f0ea"}.fa-patreon:before{content:"\f3d9"}.fa-pause:before{content:"\f04c"}.fa-pause-circle:before{content:"\f28b"}.fa-paw:before{content:"\f1b0"}.fa-paypal:before{content:"\f1ed"}.fa-peace:before{content:"\f67c"}.fa-pen:before{content:"\f304"}.fa-pen-alt:before{content:"\f305"}.fa-pen-fancy:before{content:"\f5ac"}.fa-pen-nib:before{content:"\f5ad"}.fa-pen-square:before{content:"\f14b"}.fa-pencil-alt:before{content:"\f303"}.fa-pencil-ruler:before{content:"\f5ae"}.fa-penny-arcade:before{content:"\f704"}.fa-people-carry:before{content:"\f4ce"}.fa-pepper-hot:before{content:"\f816"}.fa-percent:before{content:"\f295"}.fa-percentage:before{content:"\f541"}.fa-periscope:before{content:"\f3da"}.fa-person-booth:before{content:"\f756"}.fa-phabricator:before{content:"\f3db"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-phoenix-squadron:before{content:"\f511"}.fa-phone:before{content:"\f095"}.fa-phone-alt:before{content:"\f879"}.fa-phone-slash:before{content:"\f3dd"}.fa-phone-square:before{content:"\f098"}.fa-phone-square-alt:before{content:"\f87b"}.fa-phone-volume:before{content:"\f2a0"}.fa-photo-video:before{content:"\f87c"}.fa-php:before{content:"\f457"}.fa-pied-piper:before{content:"\f2ae"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-pied-piper-hat:before{content:"\f4e5"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-piggy-bank:before{content:"\f4d3"}.fa-pills:before{content:"\f484"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-p:before{content:"\f231"}.fa-pinterest-square:before{content:"\f0d3"}.fa-pizza-slice:before{content:"\f818"}.fa-place-of-worship:before{content:"\f67f"}.fa-plane:before{content:"\f072"}.fa-plane-arrival:before{content:"\f5af"}.fa-plane-departure:before{content:"\f5b0"}.fa-play:before{content:"\f04b"}.fa-play-circle:before{content:"\f144"}.fa-playstation:before{content:"\f3df"}.fa-plug:before{content:"\f1e6"}.fa-plus:before{content:"\f067"}.fa-plus-circle:before{content:"\f055"}.fa-plus-square:before{content:"\f0fe"}.fa-podcast:before{content:"\f2ce"}.fa-poll:before{content:"\f681"}.fa-poll-h:before{content:"\f682"}.fa-poo:before{content:"\f2fe"}.fa-poo-storm:before{content:"\f75a"}.fa-poop:before{content:"\f619"}.fa-portrait:before{content:"\f3e0"}.fa-pound-sign:before{content:"\f154"}.fa-power-off:before{content:"\f011"}.fa-pray:before{content:"\f683"}.fa-praying-hands:before{content:"\f684"}.fa-prescription:before{content:"\f5b1"}.fa-prescription-bottle:before{content:"\f485"}.fa-prescription-bottle-alt:before{content:"\f486"}.fa-print:before{content:"\f02f"}.fa-procedures:before{content:"\f487"}.fa-product-hunt:before{content:"\f288"}.fa-project-diagram:before{content:"\f542"}.fa-pushed:before{content:"\f3e1"}.fa-puzzle-piece:before{content:"\f12e"}.fa-python:before{content:"\f3e2"}.fa-qq:before{content:"\f1d6"}.fa-qrcode:before{content:"\f029"}.fa-question:before{content:"\f128"}.fa-question-circle:before{content:"\f059"}.fa-quidditch:before{content:"\f458"}.fa-quinscape:before{content:"\f459"}.fa-quora:before{content:"\f2c4"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-quran:before{content:"\f687"}.fa-r-project:before{content:"\f4f7"}.fa-radiation:before{content:"\f7b9"}.fa-radiation-alt:before{content:"\f7ba"}.fa-rainbow:before{content:"\f75b"}.fa-random:before{content:"\f074"}.fa-raspberry-pi:before{content:"\f7bb"}.fa-ravelry:before{content:"\f2d9"}.fa-react:before{content:"\f41b"}.fa-reacteurope:before{content:"\f75d"}.fa-readme:before{content:"\f4d5"}.fa-rebel:before{content:"\f1d0"}.fa-receipt:before{content:"\f543"}.fa-recycle:before{content:"\f1b8"}.fa-red-river:before{content:"\f3e3"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-alien:before{content:"\f281"}.fa-reddit-square:before{content:"\f1a2"}.fa-redhat:before{content:"\f7bc"}.fa-redo:before{content:"\f01e"}.fa-redo-alt:before{content:"\f2f9"}.fa-registered:before{content:"\f25d"}.fa-remove-format:before{content:"\f87d"}.fa-renren:before{content:"\f18b"}.fa-reply:before{content:"\f3e5"}.fa-reply-all:before{content:"\f122"}.fa-replyd:before{content:"\f3e6"}.fa-republican:before{content:"\f75e"}.fa-researchgate:before{content:"\f4f8"}.fa-resolving:before{content:"\f3e7"}.fa-restroom:before{content:"\f7bd"}.fa-retweet:before{content:"\f079"}.fa-rev:before{content:"\f5b2"}.fa-ribbon:before{content:"\f4d6"}.fa-ring:before{content:"\f70b"}.fa-road:before{content:"\f018"}.fa-robot:before{content:"\f544"}.fa-rocket:before{content:"\f135"}.fa-rocketchat:before{content:"\f3e8"}.fa-rockrms:before{content:"\f3e9"}.fa-route:before{content:"\f4d7"}.fa-rss:before{content:"\f09e"}.fa-rss-square:before{content:"\f143"}.fa-ruble-sign:before{content:"\f158"}.fa-ruler:before{content:"\f545"}.fa-ruler-combined:before{content:"\f546"}.fa-ruler-horizontal:before{content:"\f547"}.fa-ruler-vertical:before{content:"\f548"}.fa-running:before{content:"\f70c"}.fa-rupee-sign:before{content:"\f156"}.fa-sad-cry:before{content:"\f5b3"}.fa-sad-tear:before{content:"\f5b4"}.fa-safari:before{content:"\f267"}.fa-salesforce:before{content:"\f83b"}.fa-sass:before{content:"\f41e"}.fa-satellite:before{content:"\f7bf"}.fa-satellite-dish:before{content:"\f7c0"}.fa-save:before{content:"\f0c7"}.fa-schlix:before{content:"\f3ea"}.fa-school:before{content:"\f549"}.fa-screwdriver:before{content:"\f54a"}.fa-scribd:before{content:"\f28a"}.fa-scroll:before{content:"\f70e"}.fa-sd-card:before{content:"\f7c2"}.fa-search:before{content:"\f002"}.fa-search-dollar:before{content:"\f688"}.fa-search-location:before{content:"\f689"}.fa-search-minus:before{content:"\f010"}.fa-search-plus:before{content:"\f00e"}.fa-searchengin:before{content:"\f3eb"}.fa-seedling:before{content:"\f4d8"}.fa-sellcast:before{content:"\f2da"}.fa-sellsy:before{content:"\f213"}.fa-server:before{content:"\f233"}.fa-servicestack:before{content:"\f3ec"}.fa-shapes:before{content:"\f61f"}.fa-share:before{content:"\f064"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-share-square:before{content:"\f14d"}.fa-shekel-sign:before{content:"\f20b"}.fa-shield-alt:before{content:"\f3ed"}.fa-ship:before{content:"\f21a"}.fa-shipping-fast:before{content:"\f48b"}.fa-shirtsinbulk:before{content:"\f214"}.fa-shoe-prints:before{content:"\f54b"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-shopping-cart:before{content:"\f07a"}.fa-shopware:before{content:"\f5b5"}.fa-shower:before{content:"\f2cc"}.fa-shuttle-van:before{content:"\f5b6"}.fa-sign:before{content:"\f4d9"}.fa-sign-in-alt:before{content:"\f2f6"}.fa-sign-language:before{content:"\f2a7"}.fa-sign-out-alt:before{content:"\f2f5"}.fa-signal:before{content:"\f012"}.fa-signature:before{content:"\f5b7"}.fa-sim-card:before{content:"\f7c4"}.fa-simplybuilt:before{content:"\f215"}.fa-sistrix:before{content:"\f3ee"}.fa-sitemap:before{content:"\f0e8"}.fa-sith:before{content:"\f512"}.fa-skating:before{content:"\f7c5"}.fa-sketch:before{content:"\f7c6"}.fa-skiing:before{content:"\f7c9"}.fa-skiing-nordic:before{content:"\f7ca"}.fa-skull:before{content:"\f54c"}.fa-skull-crossbones:before{content:"\f714"}.fa-skyatlas:before{content:"\f216"}.fa-skype:before{content:"\f17e"}.fa-slack:before{content:"\f198"}.fa-slack-hash:before{content:"\f3ef"}.fa-slash:before{content:"\f715"}.fa-sleigh:before{content:"\f7cc"}.fa-sliders-h:before{content:"\f1de"}.fa-slideshare:before{content:"\f1e7"}.fa-smile:before{content:"\f118"}.fa-smile-beam:before{content:"\f5b8"}.fa-smile-wink:before{content:"\f4da"}.fa-smog:before{content:"\f75f"}.fa-smoking:before{content:"\f48d"}.fa-smoking-ban:before{content:"\f54d"}.fa-sms:before{content:"\f7cd"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-snowboarding:before{content:"\f7ce"}.fa-snowflake:before{content:"\f2dc"}.fa-snowman:before{content:"\f7d0"}.fa-snowplow:before{content:"\f7d2"}.fa-socks:before{content:"\f696"}.fa-solar-panel:before{content:"\f5ba"}.fa-sort:before{content:"\f0dc"}.fa-sort-alpha-down:before{content:"\f15d"}.fa-sort-alpha-down-alt:before{content:"\f881"}.fa-sort-alpha-up:before{content:"\f15e"}.fa-sort-alpha-up-alt:before{content:"\f882"}.fa-sort-amount-down:before{content:"\f160"}.fa-sort-amount-down-alt:before{content:"\f884"}.fa-sort-amount-up:before{content:"\f161"}.fa-sort-amount-up-alt:before{content:"\f885"}.fa-sort-down:before{content:"\f0dd"}.fa-sort-numeric-down:before{content:"\f162"}.fa-sort-numeric-down-alt:before{content:"\f886"}.fa-sort-numeric-up:before{content:"\f163"}.fa-sort-numeric-up-alt:before{content:"\f887"}.fa-sort-up:before{content:"\f0de"}.fa-soundcloud:before{content:"\f1be"}.fa-sourcetree:before{content:"\f7d3"}.fa-spa:before{content:"\f5bb"}.fa-space-shuttle:before{content:"\f197"}.fa-speakap:before{content:"\f3f3"}.fa-speaker-deck:before{content:"\f83c"}.fa-spell-check:before{content:"\f891"}.fa-spider:before{content:"\f717"}.fa-spinner:before{content:"\f110"}.fa-splotch:before{content:"\f5bc"}.fa-spotify:before{content:"\f1bc"}.fa-spray-can:before{content:"\f5bd"}.fa-square:before{content:"\f0c8"}.fa-square-full:before{content:"\f45c"}.fa-square-root-alt:before{content:"\f698"}.fa-squarespace:before{content:"\f5be"}.fa-stack-exchange:before{content:"\f18d"}.fa-stack-overflow:before{content:"\f16c"}.fa-stackpath:before{content:"\f842"}.fa-stamp:before{content:"\f5bf"}.fa-star:before{content:"\f005"}.fa-star-and-crescent:before{content:"\f699"}.fa-star-half:before{content:"\f089"}.fa-star-half-alt:before{content:"\f5c0"}.fa-star-of-david:before{content:"\f69a"}.fa-star-of-life:before{content:"\f621"}.fa-staylinked:before{content:"\f3f5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-steam-symbol:before{content:"\f3f6"}.fa-step-backward:before{content:"\f048"}.fa-step-forward:before{content:"\f051"}.fa-stethoscope:before{content:"\f0f1"}.fa-sticker-mule:before{content:"\f3f7"}.fa-sticky-note:before{content:"\f249"}.fa-stop:before{content:"\f04d"}.fa-stop-circle:before{content:"\f28d"}.fa-stopwatch:before{content:"\f2f2"}.fa-store:before{content:"\f54e"}.fa-store-alt:before{content:"\f54f"}.fa-strava:before{content:"\f428"}.fa-stream:before{content:"\f550"}.fa-street-view:before{content:"\f21d"}.fa-strikethrough:before{content:"\f0cc"}.fa-stripe:before{content:"\f429"}.fa-stripe-s:before{content:"\f42a"}.fa-stroopwafel:before{content:"\f551"}.fa-studiovinari:before{content:"\f3f8"}.fa-stumbleupon:before{content:"\f1a4"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-subscript:before{content:"\f12c"}.fa-subway:before{content:"\f239"}.fa-suitcase:before{content:"\f0f2"}.fa-suitcase-rolling:before{content:"\f5c1"}.fa-sun:before{content:"\f185"}.fa-superpowers:before{content:"\f2dd"}.fa-superscript:before{content:"\f12b"}.fa-supple:before{content:"\f3f9"}.fa-surprise:before{content:"\f5c2"}.fa-suse:before{content:"\f7d6"}.fa-swatchbook:before{content:"\f5c3"}.fa-swimmer:before{content:"\f5c4"}.fa-swimming-pool:before{content:"\f5c5"}.fa-symfony:before{content:"\f83d"}.fa-synagogue:before{content:"\f69b"}.fa-sync:before{content:"\f021"}.fa-sync-alt:before{content:"\f2f1"}.fa-syringe:before{content:"\f48e"}.fa-table:before{content:"\f0ce"}.fa-table-tennis:before{content:"\f45d"}.fa-tablet:before{content:"\f10a"}.fa-tablet-alt:before{content:"\f3fa"}.fa-tablets:before{content:"\f490"}.fa-tachometer-alt:before{content:"\f3fd"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-tape:before{content:"\f4db"}.fa-tasks:before{content:"\f0ae"}.fa-taxi:before{content:"\f1ba"}.fa-teamspeak:before{content:"\f4f9"}.fa-teeth:before{content:"\f62e"}.fa-teeth-open:before{content:"\f62f"}.fa-telegram:before{content:"\f2c6"}.fa-telegram-plane:before{content:"\f3fe"}.fa-temperature-high:before{content:"\f769"}.fa-temperature-low:before{content:"\f76b"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-tenge:before{content:"\f7d7"}.fa-terminal:before{content:"\f120"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-th:before{content:"\f00a"}.fa-th-large:before{content:"\f009"}.fa-th-list:before{content:"\f00b"}.fa-the-red-yeti:before{content:"\f69d"}.fa-theater-masks:before{content:"\f630"}.fa-themeco:before{content:"\f5c6"}.fa-themeisle:before{content:"\f2b2"}.fa-thermometer:before{content:"\f491"}.fa-thermometer-empty:before{content:"\f2cb"}.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-think-peaks:before{content:"\f731"}.fa-thumbs-down:before{content:"\f165"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbtack:before{content:"\f08d"}.fa-ticket-alt:before{content:"\f3ff"}.fa-times:before{content:"\f00d"}.fa-times-circle:before{content:"\f057"}.fa-tint:before{content:"\f043"}.fa-tint-slash:before{content:"\f5c7"}.fa-tired:before{content:"\f5c8"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-toilet:before{content:"\f7d8"}.fa-toilet-paper:before{content:"\f71e"}.fa-toolbox:before{content:"\f552"}.fa-tools:before{content:"\f7d9"}.fa-tooth:before{content:"\f5c9"}.fa-torah:before{content:"\f6a0"}.fa-torii-gate:before{content:"\f6a1"}.fa-tractor:before{content:"\f722"}.fa-trade-federation:before{content:"\f513"}.fa-trademark:before{content:"\f25c"}.fa-traffic-light:before{content:"\f637"}.fa-train:before{content:"\f238"}.fa-tram:before{content:"\f7da"}.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-trash:before{content:"\f1f8"}.fa-trash-alt:before{content:"\f2ed"}.fa-trash-restore:before{content:"\f829"}.fa-trash-restore-alt:before{content:"\f82a"}.fa-tree:before{content:"\f1bb"}.fa-trello:before{content:"\f181"}.fa-tripadvisor:before{content:"\f262"}.fa-trophy:before{content:"\f091"}.fa-truck:before{content:"\f0d1"}.fa-truck-loading:before{content:"\f4de"}.fa-truck-monster:before{content:"\f63b"}.fa-truck-moving:before{content:"\f4df"}.fa-truck-pickup:before{content:"\f63c"}.fa-tshirt:before{content:"\f553"}.fa-tty:before{content:"\f1e4"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-tv:before{content:"\f26c"}.fa-twitch:before{content:"\f1e8"}.fa-twitter:before{content:"\f099"}.fa-twitter-square:before{content:"\f081"}.fa-typo3:before{content:"\f42b"}.fa-uber:before{content:"\f402"}.fa-ubuntu:before{content:"\f7df"}.fa-uikit:before{content:"\f403"}.fa-umbrella:before{content:"\f0e9"}.fa-umbrella-beach:before{content:"\f5ca"}.fa-underline:before{content:"\f0cd"}.fa-undo:before{content:"\f0e2"}.fa-undo-alt:before{content:"\f2ea"}.fa-uniregistry:before{content:"\f404"}.fa-universal-access:before{content:"\f29a"}.fa-university:before{content:"\f19c"}.fa-unlink:before{content:"\f127"}.fa-unlock:before{content:"\f09c"}.fa-unlock-alt:before{content:"\f13e"}.fa-untappd:before{content:"\f405"}.fa-upload:before{content:"\f093"}.fa-ups:before{content:"\f7e0"}.fa-usb:before{content:"\f287"}.fa-user:before{content:"\f007"}.fa-user-alt:before{content:"\f406"}.fa-user-alt-slash:before{content:"\f4fa"}.fa-user-astronaut:before{content:"\f4fb"}.fa-user-check:before{content:"\f4fc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-clock:before{content:"\f4fd"}.fa-user-cog:before{content:"\f4fe"}.fa-user-edit:before{content:"\f4ff"}.fa-user-friends:before{content:"\f500"}.fa-user-graduate:before{content:"\f501"}.fa-user-injured:before{content:"\f728"}.fa-user-lock:before{content:"\f502"}.fa-user-md:before{content:"\f0f0"}.fa-user-minus:before{content:"\f503"}.fa-user-ninja:before{content:"\f504"}.fa-user-nurse:before{content:"\f82f"}.fa-user-plus:before{content:"\f234"}.fa-user-secret:before{content:"\f21b"}.fa-user-shield:before{content:"\f505"}.fa-user-slash:before{content:"\f506"}.fa-user-tag:before{content:"\f507"}.fa-user-tie:before{content:"\f508"}.fa-user-times:before{content:"\f235"}.fa-users:before{content:"\f0c0"}.fa-users-cog:before{content:"\f509"}.fa-usps:before{content:"\f7e1"}.fa-ussunnah:before{content:"\f407"}.fa-utensil-spoon:before{content:"\f2e5"}.fa-utensils:before{content:"\f2e7"}.fa-vaadin:before{content:"\f408"}.fa-vector-square:before{content:"\f5cb"}.fa-venus:before{content:"\f221"}.fa-venus-double:before{content:"\f226"}.fa-venus-mars:before{content:"\f228"}.fa-viacoin:before{content:"\f237"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-vial:before{content:"\f492"}.fa-vials:before{content:"\f493"}.fa-viber:before{content:"\f409"}.fa-video:before{content:"\f03d"}.fa-video-slash:before{content:"\f4e2"}.fa-vihara:before{content:"\f6a7"}.fa-vimeo:before{content:"\f40a"}.fa-vimeo-square:before{content:"\f194"}.fa-vimeo-v:before{content:"\f27d"}.fa-vine:before{content:"\f1ca"}.fa-vk:before{content:"\f189"}.fa-vnv:before{content:"\f40b"}.fa-voicemail:before{content:"\f897"}.fa-volleyball-ball:before{content:"\f45f"}.fa-volume-down:before{content:"\f027"}.fa-volume-mute:before{content:"\f6a9"}.fa-volume-off:before{content:"\f026"}.fa-volume-up:before{content:"\f028"}.fa-vote-yea:before{content:"\f772"}.fa-vr-cardboard:before{content:"\f729"}.fa-vuejs:before{content:"\f41f"}.fa-walking:before{content:"\f554"}.fa-wallet:before{content:"\f555"}.fa-warehouse:before{content:"\f494"}.fa-water:before{content:"\f773"}.fa-wave-square:before{content:"\f83e"}.fa-waze:before{content:"\f83f"}.fa-weebly:before{content:"\f5cc"}.fa-weibo:before{content:"\f18a"}.fa-weight:before{content:"\f496"}.fa-weight-hanging:before{content:"\f5cd"}.fa-weixin:before{content:"\f1d7"}.fa-whatsapp:before{content:"\f232"}.fa-whatsapp-square:before{content:"\f40c"}.fa-wheelchair:before{content:"\f193"}.fa-whmcs:before{content:"\f40d"}.fa-wifi:before{content:"\f1eb"}.fa-wikipedia-w:before{content:"\f266"}.fa-wind:before{content:"\f72e"}.fa-window-close:before{content:"\f410"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-windows:before{content:"\f17a"}.fa-wine-bottle:before{content:"\f72f"}.fa-wine-glass:before{content:"\f4e3"}.fa-wine-glass-alt:before{content:"\f5ce"}.fa-wix:before{content:"\f5cf"}.fa-wizards-of-the-coast:before{content:"\f730"}.fa-wolf-pack-battalion:before{content:"\f514"}.fa-won-sign:before{content:"\f159"}.fa-wordpress:before{content:"\f19a"}.fa-wordpress-simple:before{content:"\f411"}.fa-wpbeginner:before{content:"\f297"}.fa-wpexplorer:before{content:"\f2de"}.fa-wpforms:before{content:"\f298"}.fa-wpressr:before{content:"\f3e4"}.fa-wrench:before{content:"\f0ad"}.fa-x-ray:before{content:"\f497"}.fa-xbox:before{content:"\f412"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-y-combinator:before{content:"\f23b"}.fa-yahoo:before{content:"\f19e"}.fa-yammer:before{content:"\f840"}.fa-yandex:before{content:"\f413"}.fa-yandex-international:before{content:"\f414"}.fa-yarn:before{content:"\f7e3"}.fa-yelp:before{content:"\f1e9"}.fa-yen-sign:before{content:"\f157"}.fa-yin-yang:before{content:"\f6ad"}.fa-yoast:before{content:"\f2b1"}.fa-youtube:before{content:"\f167"}.fa-youtube-square:before{content:"\f431"}.fa-zhihu:before{content:"\f63f"}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:400;font-display:auto;src:url(fa-regular-400.eot);src:url(fa-regular-400.eot?#iefix) format("embedded-opentype"),url(fa-regular-400.woff) format("woff")}.far{font-weight:400}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:900;font-display:auto;src:url(fa-solid-900.eot);src:url(fa-solid-900.eot?#iefix) format("embedded-opentype"),url(fa-solid-900.woff) format("woff")}.fa,.far,.fas{font-family:"Font Awesome 5 Free"}.fa,.fas{font-weight:900} \ No newline at end of file +.fa,.fab,.fad,.fal,.far,.fas{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-webkit-transform:scaleY(-1);transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{-webkit-transform:scale(-1);transform:scale(-1)}:root .fa-flip-both,:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{-webkit-filter:none;filter:none}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2.5em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-500px:before{content:"\f26e"}.fa-accessible-icon:before{content:"\f368"}.fa-accusoft:before{content:"\f369"}.fa-acquisitions-incorporated:before{content:"\f6af"}.fa-ad:before{content:"\f641"}.fa-address-book:before{content:"\f2b9"}.fa-address-card:before{content:"\f2bb"}.fa-adjust:before{content:"\f042"}.fa-adn:before{content:"\f170"}.fa-adobe:before{content:"\f778"}.fa-adversal:before{content:"\f36a"}.fa-affiliatetheme:before{content:"\f36b"}.fa-air-freshener:before{content:"\f5d0"}.fa-airbnb:before{content:"\f834"}.fa-algolia:before{content:"\f36c"}.fa-align-center:before{content:"\f037"}.fa-align-justify:before{content:"\f039"}.fa-align-left:before{content:"\f036"}.fa-align-right:before{content:"\f038"}.fa-alipay:before{content:"\f642"}.fa-allergies:before{content:"\f461"}.fa-amazon:before{content:"\f270"}.fa-amazon-pay:before{content:"\f42c"}.fa-ambulance:before{content:"\f0f9"}.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-amilia:before{content:"\f36d"}.fa-anchor:before{content:"\f13d"}.fa-android:before{content:"\f17b"}.fa-angellist:before{content:"\f209"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-down:before{content:"\f107"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angry:before{content:"\f556"}.fa-angrycreative:before{content:"\f36e"}.fa-angular:before{content:"\f420"}.fa-ankh:before{content:"\f644"}.fa-app-store:before{content:"\f36f"}.fa-app-store-ios:before{content:"\f370"}.fa-apper:before{content:"\f371"}.fa-apple:before{content:"\f179"}.fa-apple-alt:before{content:"\f5d1"}.fa-apple-pay:before{content:"\f415"}.fa-archive:before{content:"\f187"}.fa-archway:before{content:"\f557"}.fa-arrow-alt-circle-down:before{content:"\f358"}.fa-arrow-alt-circle-left:before{content:"\f359"}.fa-arrow-alt-circle-right:before{content:"\f35a"}.fa-arrow-alt-circle-up:before{content:"\f35b"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-down:before{content:"\f063"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrows-alt:before{content:"\f0b2"}.fa-arrows-alt-h:before{content:"\f337"}.fa-arrows-alt-v:before{content:"\f338"}.fa-artstation:before{content:"\f77a"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asterisk:before{content:"\f069"}.fa-asymmetrik:before{content:"\f372"}.fa-at:before{content:"\f1fa"}.fa-atlas:before{content:"\f558"}.fa-atlassian:before{content:"\f77b"}.fa-atom:before{content:"\f5d2"}.fa-audible:before{content:"\f373"}.fa-audio-description:before{content:"\f29e"}.fa-autoprefixer:before{content:"\f41c"}.fa-avianex:before{content:"\f374"}.fa-aviato:before{content:"\f421"}.fa-award:before{content:"\f559"}.fa-aws:before{content:"\f375"}.fa-baby:before{content:"\f77c"}.fa-baby-carriage:before{content:"\f77d"}.fa-backspace:before{content:"\f55a"}.fa-backward:before{content:"\f04a"}.fa-bacon:before{content:"\f7e5"}.fa-bacteria:before{content:"\e059"}.fa-bacterium:before{content:"\e05a"}.fa-bahai:before{content:"\f666"}.fa-balance-scale:before{content:"\f24e"}.fa-balance-scale-left:before{content:"\f515"}.fa-balance-scale-right:before{content:"\f516"}.fa-ban:before{content:"\f05e"}.fa-band-aid:before{content:"\f462"}.fa-bandcamp:before{content:"\f2d5"}.fa-barcode:before{content:"\f02a"}.fa-bars:before{content:"\f0c9"}.fa-baseball-ball:before{content:"\f433"}.fa-basketball-ball:before{content:"\f434"}.fa-bath:before{content:"\f2cd"}.fa-battery-empty:before{content:"\f244"}.fa-battery-full:before{content:"\f240"}.fa-battery-half:before{content:"\f242"}.fa-battery-quarter:before{content:"\f243"}.fa-battery-three-quarters:before{content:"\f241"}.fa-battle-net:before{content:"\f835"}.fa-bed:before{content:"\f236"}.fa-beer:before{content:"\f0fc"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-bell:before{content:"\f0f3"}.fa-bell-slash:before{content:"\f1f6"}.fa-bezier-curve:before{content:"\f55b"}.fa-bible:before{content:"\f647"}.fa-bicycle:before{content:"\f206"}.fa-biking:before{content:"\f84a"}.fa-bimobject:before{content:"\f378"}.fa-binoculars:before{content:"\f1e5"}.fa-biohazard:before{content:"\f780"}.fa-birthday-cake:before{content:"\f1fd"}.fa-bitbucket:before{content:"\f171"}.fa-bitcoin:before{content:"\f379"}.fa-bity:before{content:"\f37a"}.fa-black-tie:before{content:"\f27e"}.fa-blackberry:before{content:"\f37b"}.fa-blender:before{content:"\f517"}.fa-blender-phone:before{content:"\f6b6"}.fa-blind:before{content:"\f29d"}.fa-blog:before{content:"\f781"}.fa-blogger:before{content:"\f37c"}.fa-blogger-b:before{content:"\f37d"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-bold:before{content:"\f032"}.fa-bolt:before{content:"\f0e7"}.fa-bomb:before{content:"\f1e2"}.fa-bone:before{content:"\f5d7"}.fa-bong:before{content:"\f55c"}.fa-book:before{content:"\f02d"}.fa-book-dead:before{content:"\f6b7"}.fa-book-medical:before{content:"\f7e6"}.fa-book-open:before{content:"\f518"}.fa-book-reader:before{content:"\f5da"}.fa-bookmark:before{content:"\f02e"}.fa-bootstrap:before{content:"\f836"}.fa-border-all:before{content:"\f84c"}.fa-border-none:before{content:"\f850"}.fa-border-style:before{content:"\f853"}.fa-bowling-ball:before{content:"\f436"}.fa-box:before{content:"\f466"}.fa-box-open:before{content:"\f49e"}.fa-box-tissue:before{content:"\e05b"}.fa-boxes:before{content:"\f468"}.fa-braille:before{content:"\f2a1"}.fa-brain:before{content:"\f5dc"}.fa-bread-slice:before{content:"\f7ec"}.fa-briefcase:before{content:"\f0b1"}.fa-briefcase-medical:before{content:"\f469"}.fa-broadcast-tower:before{content:"\f519"}.fa-broom:before{content:"\f51a"}.fa-brush:before{content:"\f55d"}.fa-btc:before{content:"\f15a"}.fa-buffer:before{content:"\f837"}.fa-bug:before{content:"\f188"}.fa-building:before{content:"\f1ad"}.fa-bullhorn:before{content:"\f0a1"}.fa-bullseye:before{content:"\f140"}.fa-burn:before{content:"\f46a"}.fa-buromobelexperte:before{content:"\f37f"}.fa-bus:before{content:"\f207"}.fa-bus-alt:before{content:"\f55e"}.fa-business-time:before{content:"\f64a"}.fa-buy-n-large:before{content:"\f8a6"}.fa-buysellads:before{content:"\f20d"}.fa-calculator:before{content:"\f1ec"}.fa-calendar:before{content:"\f133"}.fa-calendar-alt:before{content:"\f073"}.fa-calendar-check:before{content:"\f274"}.fa-calendar-day:before{content:"\f783"}.fa-calendar-minus:before{content:"\f272"}.fa-calendar-plus:before{content:"\f271"}.fa-calendar-times:before{content:"\f273"}.fa-calendar-week:before{content:"\f784"}.fa-camera:before{content:"\f030"}.fa-camera-retro:before{content:"\f083"}.fa-campground:before{content:"\f6bb"}.fa-canadian-maple-leaf:before{content:"\f785"}.fa-candy-cane:before{content:"\f786"}.fa-cannabis:before{content:"\f55f"}.fa-capsules:before{content:"\f46b"}.fa-car:before{content:"\f1b9"}.fa-car-alt:before{content:"\f5de"}.fa-car-battery:before{content:"\f5df"}.fa-car-crash:before{content:"\f5e1"}.fa-car-side:before{content:"\f5e4"}.fa-caravan:before{content:"\f8ff"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-caret-square-down:before{content:"\f150"}.fa-caret-square-left:before{content:"\f191"}.fa-caret-square-right:before{content:"\f152"}.fa-caret-square-up:before{content:"\f151"}.fa-caret-up:before{content:"\f0d8"}.fa-carrot:before{content:"\f787"}.fa-cart-arrow-down:before{content:"\f218"}.fa-cart-plus:before{content:"\f217"}.fa-cash-register:before{content:"\f788"}.fa-cat:before{content:"\f6be"}.fa-cc-amazon-pay:before{content:"\f42d"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-apple-pay:before{content:"\f416"}.fa-cc-diners-club:before{content:"\f24c"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-cc-visa:before{content:"\f1f0"}.fa-centercode:before{content:"\f380"}.fa-centos:before{content:"\f789"}.fa-certificate:before{content:"\f0a3"}.fa-chair:before{content:"\f6c0"}.fa-chalkboard:before{content:"\f51b"}.fa-chalkboard-teacher:before{content:"\f51c"}.fa-charging-station:before{content:"\f5e7"}.fa-chart-area:before{content:"\f1fe"}.fa-chart-bar:before{content:"\f080"}.fa-chart-line:before{content:"\f201"}.fa-chart-pie:before{content:"\f200"}.fa-check:before{content:"\f00c"}.fa-check-circle:before{content:"\f058"}.fa-check-double:before{content:"\f560"}.fa-check-square:before{content:"\f14a"}.fa-cheese:before{content:"\f7ef"}.fa-chess:before{content:"\f439"}.fa-chess-bishop:before{content:"\f43a"}.fa-chess-board:before{content:"\f43c"}.fa-chess-king:before{content:"\f43f"}.fa-chess-knight:before{content:"\f441"}.fa-chess-pawn:before{content:"\f443"}.fa-chess-queen:before{content:"\f445"}.fa-chess-rook:before{content:"\f447"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-down:before{content:"\f078"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-chevron-up:before{content:"\f077"}.fa-child:before{content:"\f1ae"}.fa-chrome:before{content:"\f268"}.fa-chromecast:before{content:"\f838"}.fa-church:before{content:"\f51d"}.fa-circle:before{content:"\f111"}.fa-circle-notch:before{content:"\f1ce"}.fa-city:before{content:"\f64f"}.fa-clinic-medical:before{content:"\f7f2"}.fa-clipboard:before{content:"\f328"}.fa-clipboard-check:before{content:"\f46c"}.fa-clipboard-list:before{content:"\f46d"}.fa-clock:before{content:"\f017"}.fa-clone:before{content:"\f24d"}.fa-closed-captioning:before{content:"\f20a"}.fa-cloud:before{content:"\f0c2"}.fa-cloud-download-alt:before{content:"\f381"}.fa-cloud-meatball:before{content:"\f73b"}.fa-cloud-moon:before{content:"\f6c3"}.fa-cloud-moon-rain:before{content:"\f73c"}.fa-cloud-rain:before{content:"\f73d"}.fa-cloud-showers-heavy:before{content:"\f740"}.fa-cloud-sun:before{content:"\f6c4"}.fa-cloud-sun-rain:before{content:"\f743"}.fa-cloud-upload-alt:before{content:"\f382"}.fa-cloudscale:before{content:"\f383"}.fa-cloudsmith:before{content:"\f384"}.fa-cloudversify:before{content:"\f385"}.fa-cocktail:before{content:"\f561"}.fa-code:before{content:"\f121"}.fa-code-branch:before{content:"\f126"}.fa-codepen:before{content:"\f1cb"}.fa-codiepie:before{content:"\f284"}.fa-coffee:before{content:"\f0f4"}.fa-cog:before{content:"\f013"}.fa-cogs:before{content:"\f085"}.fa-coins:before{content:"\f51e"}.fa-columns:before{content:"\f0db"}.fa-comment:before{content:"\f075"}.fa-comment-alt:before{content:"\f27a"}.fa-comment-dollar:before{content:"\f651"}.fa-comment-dots:before{content:"\f4ad"}.fa-comment-medical:before{content:"\f7f5"}.fa-comment-slash:before{content:"\f4b3"}.fa-comments:before{content:"\f086"}.fa-comments-dollar:before{content:"\f653"}.fa-compact-disc:before{content:"\f51f"}.fa-compass:before{content:"\f14e"}.fa-compress:before{content:"\f066"}.fa-compress-alt:before{content:"\f422"}.fa-compress-arrows-alt:before{content:"\f78c"}.fa-concierge-bell:before{content:"\f562"}.fa-confluence:before{content:"\f78d"}.fa-connectdevelop:before{content:"\f20e"}.fa-contao:before{content:"\f26d"}.fa-cookie:before{content:"\f563"}.fa-cookie-bite:before{content:"\f564"}.fa-copy:before{content:"\f0c5"}.fa-copyright:before{content:"\f1f9"}.fa-cotton-bureau:before{content:"\f89e"}.fa-couch:before{content:"\f4b8"}.fa-cpanel:before{content:"\f388"}.fa-creative-commons:before{content:"\f25e"}.fa-creative-commons-by:before{content:"\f4e7"}.fa-creative-commons-nc:before{content:"\f4e8"}.fa-creative-commons-nc-eu:before{content:"\f4e9"}.fa-creative-commons-nc-jp:before{content:"\f4ea"}.fa-creative-commons-nd:before{content:"\f4eb"}.fa-creative-commons-pd:before{content:"\f4ec"}.fa-creative-commons-pd-alt:before{content:"\f4ed"}.fa-creative-commons-remix:before{content:"\f4ee"}.fa-creative-commons-sa:before{content:"\f4ef"}.fa-creative-commons-sampling:before{content:"\f4f0"}.fa-creative-commons-sampling-plus:before{content:"\f4f1"}.fa-creative-commons-share:before{content:"\f4f2"}.fa-creative-commons-zero:before{content:"\f4f3"}.fa-credit-card:before{content:"\f09d"}.fa-critical-role:before{content:"\f6c9"}.fa-crop:before{content:"\f125"}.fa-crop-alt:before{content:"\f565"}.fa-cross:before{content:"\f654"}.fa-crosshairs:before{content:"\f05b"}.fa-crow:before{content:"\f520"}.fa-crown:before{content:"\f521"}.fa-crutch:before{content:"\f7f7"}.fa-css3:before{content:"\f13c"}.fa-css3-alt:before{content:"\f38b"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-cut:before{content:"\f0c4"}.fa-cuttlefish:before{content:"\f38c"}.fa-d-and-d:before{content:"\f38d"}.fa-d-and-d-beyond:before{content:"\f6ca"}.fa-dailymotion:before{content:"\e052"}.fa-dashcube:before{content:"\f210"}.fa-database:before{content:"\f1c0"}.fa-deaf:before{content:"\f2a4"}.fa-deezer:before{content:"\e077"}.fa-delicious:before{content:"\f1a5"}.fa-democrat:before{content:"\f747"}.fa-deploydog:before{content:"\f38e"}.fa-deskpro:before{content:"\f38f"}.fa-desktop:before{content:"\f108"}.fa-dev:before{content:"\f6cc"}.fa-deviantart:before{content:"\f1bd"}.fa-dharmachakra:before{content:"\f655"}.fa-dhl:before{content:"\f790"}.fa-diagnoses:before{content:"\f470"}.fa-diaspora:before{content:"\f791"}.fa-dice:before{content:"\f522"}.fa-dice-d20:before{content:"\f6cf"}.fa-dice-d6:before{content:"\f6d1"}.fa-dice-five:before{content:"\f523"}.fa-dice-four:before{content:"\f524"}.fa-dice-one:before{content:"\f525"}.fa-dice-six:before{content:"\f526"}.fa-dice-three:before{content:"\f527"}.fa-dice-two:before{content:"\f528"}.fa-digg:before{content:"\f1a6"}.fa-digital-ocean:before{content:"\f391"}.fa-digital-tachograph:before{content:"\f566"}.fa-directions:before{content:"\f5eb"}.fa-discord:before{content:"\f392"}.fa-discourse:before{content:"\f393"}.fa-disease:before{content:"\f7fa"}.fa-divide:before{content:"\f529"}.fa-dizzy:before{content:"\f567"}.fa-dna:before{content:"\f471"}.fa-dochub:before{content:"\f394"}.fa-docker:before{content:"\f395"}.fa-dog:before{content:"\f6d3"}.fa-dollar-sign:before{content:"\f155"}.fa-dolly:before{content:"\f472"}.fa-dolly-flatbed:before{content:"\f474"}.fa-donate:before{content:"\f4b9"}.fa-door-closed:before{content:"\f52a"}.fa-door-open:before{content:"\f52b"}.fa-dot-circle:before{content:"\f192"}.fa-dove:before{content:"\f4ba"}.fa-download:before{content:"\f019"}.fa-draft2digital:before{content:"\f396"}.fa-drafting-compass:before{content:"\f568"}.fa-dragon:before{content:"\f6d5"}.fa-draw-polygon:before{content:"\f5ee"}.fa-dribbble:before{content:"\f17d"}.fa-dribbble-square:before{content:"\f397"}.fa-dropbox:before{content:"\f16b"}.fa-drum:before{content:"\f569"}.fa-drum-steelpan:before{content:"\f56a"}.fa-drumstick-bite:before{content:"\f6d7"}.fa-drupal:before{content:"\f1a9"}.fa-dumbbell:before{content:"\f44b"}.fa-dumpster:before{content:"\f793"}.fa-dumpster-fire:before{content:"\f794"}.fa-dungeon:before{content:"\f6d9"}.fa-dyalog:before{content:"\f399"}.fa-earlybirds:before{content:"\f39a"}.fa-ebay:before{content:"\f4f4"}.fa-edge:before{content:"\f282"}.fa-edge-legacy:before{content:"\e078"}.fa-edit:before{content:"\f044"}.fa-egg:before{content:"\f7fb"}.fa-eject:before{content:"\f052"}.fa-elementor:before{content:"\f430"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-ello:before{content:"\f5f1"}.fa-ember:before{content:"\f423"}.fa-empire:before{content:"\f1d1"}.fa-envelope:before{content:"\f0e0"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-text:before{content:"\f658"}.fa-envelope-square:before{content:"\f199"}.fa-envira:before{content:"\f299"}.fa-equals:before{content:"\f52c"}.fa-eraser:before{content:"\f12d"}.fa-erlang:before{content:"\f39d"}.fa-ethereum:before{content:"\f42e"}.fa-ethernet:before{content:"\f796"}.fa-etsy:before{content:"\f2d7"}.fa-euro-sign:before{content:"\f153"}.fa-evernote:before{content:"\f839"}.fa-exchange-alt:before{content:"\f362"}.fa-exclamation:before{content:"\f12a"}.fa-exclamation-circle:before{content:"\f06a"}.fa-exclamation-triangle:before{content:"\f071"}.fa-expand:before{content:"\f065"}.fa-expand-alt:before{content:"\f424"}.fa-expand-arrows-alt:before{content:"\f31e"}.fa-expeditedssl:before{content:"\f23e"}.fa-external-link-alt:before{content:"\f35d"}.fa-external-link-square-alt:before{content:"\f360"}.fa-eye:before{content:"\f06e"}.fa-eye-dropper:before{content:"\f1fb"}.fa-eye-slash:before{content:"\f070"}.fa-facebook:before{content:"\f09a"}.fa-facebook-f:before{content:"\f39e"}.fa-facebook-messenger:before{content:"\f39f"}.fa-facebook-square:before{content:"\f082"}.fa-fan:before{content:"\f863"}.fa-fantasy-flight-games:before{content:"\f6dc"}.fa-fast-backward:before{content:"\f049"}.fa-fast-forward:before{content:"\f050"}.fa-faucet:before{content:"\e005"}.fa-fax:before{content:"\f1ac"}.fa-feather:before{content:"\f52d"}.fa-feather-alt:before{content:"\f56b"}.fa-fedex:before{content:"\f797"}.fa-fedora:before{content:"\f798"}.fa-female:before{content:"\f182"}.fa-fighter-jet:before{content:"\f0fb"}.fa-figma:before{content:"\f799"}.fa-file:before{content:"\f15b"}.fa-file-alt:before{content:"\f15c"}.fa-file-archive:before{content:"\f1c6"}.fa-file-audio:before{content:"\f1c7"}.fa-file-code:before{content:"\f1c9"}.fa-file-contract:before{content:"\f56c"}.fa-file-csv:before{content:"\f6dd"}.fa-file-download:before{content:"\f56d"}.fa-file-excel:before{content:"\f1c3"}.fa-file-export:before{content:"\f56e"}.fa-file-image:before{content:"\f1c5"}.fa-file-import:before{content:"\f56f"}.fa-file-invoice:before{content:"\f570"}.fa-file-invoice-dollar:before{content:"\f571"}.fa-file-medical:before{content:"\f477"}.fa-file-medical-alt:before{content:"\f478"}.fa-file-pdf:before{content:"\f1c1"}.fa-file-powerpoint:before{content:"\f1c4"}.fa-file-prescription:before{content:"\f572"}.fa-file-signature:before{content:"\f573"}.fa-file-upload:before{content:"\f574"}.fa-file-video:before{content:"\f1c8"}.fa-file-word:before{content:"\f1c2"}.fa-fill:before{content:"\f575"}.fa-fill-drip:before{content:"\f576"}.fa-film:before{content:"\f008"}.fa-filter:before{content:"\f0b0"}.fa-fingerprint:before{content:"\f577"}.fa-fire:before{content:"\f06d"}.fa-fire-alt:before{content:"\f7e4"}.fa-fire-extinguisher:before{content:"\f134"}.fa-firefox:before{content:"\f269"}.fa-firefox-browser:before{content:"\e007"}.fa-first-aid:before{content:"\f479"}.fa-first-order:before{content:"\f2b0"}.fa-first-order-alt:before{content:"\f50a"}.fa-firstdraft:before{content:"\f3a1"}.fa-fish:before{content:"\f578"}.fa-fist-raised:before{content:"\f6de"}.fa-flag:before{content:"\f024"}.fa-flag-checkered:before{content:"\f11e"}.fa-flag-usa:before{content:"\f74d"}.fa-flask:before{content:"\f0c3"}.fa-flickr:before{content:"\f16e"}.fa-flipboard:before{content:"\f44d"}.fa-flushed:before{content:"\f579"}.fa-fly:before{content:"\f417"}.fa-folder:before{content:"\f07b"}.fa-folder-minus:before{content:"\f65d"}.fa-folder-open:before{content:"\f07c"}.fa-folder-plus:before{content:"\f65e"}.fa-font:before{content:"\f031"}.fa-font-awesome:before{content:"\f2b4"}.fa-font-awesome-alt:before{content:"\f35c"}.fa-font-awesome-flag:before{content:"\f425"}.fa-font-awesome-logo-full:before{content:"\f4e6"}.fa-fonticons:before{content:"\f280"}.fa-fonticons-fi:before{content:"\f3a2"}.fa-football-ball:before{content:"\f44e"}.fa-fort-awesome:before{content:"\f286"}.fa-fort-awesome-alt:before{content:"\f3a3"}.fa-forumbee:before{content:"\f211"}.fa-forward:before{content:"\f04e"}.fa-foursquare:before{content:"\f180"}.fa-free-code-camp:before{content:"\f2c5"}.fa-freebsd:before{content:"\f3a4"}.fa-frog:before{content:"\f52e"}.fa-frown:before{content:"\f119"}.fa-frown-open:before{content:"\f57a"}.fa-fulcrum:before{content:"\f50b"}.fa-funnel-dollar:before{content:"\f662"}.fa-futbol:before{content:"\f1e3"}.fa-galactic-republic:before{content:"\f50c"}.fa-galactic-senate:before{content:"\f50d"}.fa-gamepad:before{content:"\f11b"}.fa-gas-pump:before{content:"\f52f"}.fa-gavel:before{content:"\f0e3"}.fa-gem:before{content:"\f3a5"}.fa-genderless:before{content:"\f22d"}.fa-get-pocket:before{content:"\f265"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-ghost:before{content:"\f6e2"}.fa-gift:before{content:"\f06b"}.fa-gifts:before{content:"\f79c"}.fa-git:before{content:"\f1d3"}.fa-git-alt:before{content:"\f841"}.fa-git-square:before{content:"\f1d2"}.fa-github:before{content:"\f09b"}.fa-github-alt:before{content:"\f113"}.fa-github-square:before{content:"\f092"}.fa-gitkraken:before{content:"\f3a6"}.fa-gitlab:before{content:"\f296"}.fa-gitter:before{content:"\f426"}.fa-glass-cheers:before{content:"\f79f"}.fa-glass-martini:before{content:"\f000"}.fa-glass-martini-alt:before{content:"\f57b"}.fa-glass-whiskey:before{content:"\f7a0"}.fa-glasses:before{content:"\f530"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-globe:before{content:"\f0ac"}.fa-globe-africa:before{content:"\f57c"}.fa-globe-americas:before{content:"\f57d"}.fa-globe-asia:before{content:"\f57e"}.fa-globe-europe:before{content:"\f7a2"}.fa-gofore:before{content:"\f3a7"}.fa-golf-ball:before{content:"\f450"}.fa-goodreads:before{content:"\f3a8"}.fa-goodreads-g:before{content:"\f3a9"}.fa-google:before{content:"\f1a0"}.fa-google-drive:before{content:"\f3aa"}.fa-google-pay:before{content:"\e079"}.fa-google-play:before{content:"\f3ab"}.fa-google-plus:before{content:"\f2b3"}.fa-google-plus-g:before{content:"\f0d5"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-wallet:before{content:"\f1ee"}.fa-gopuram:before{content:"\f664"}.fa-graduation-cap:before{content:"\f19d"}.fa-gratipay:before{content:"\f184"}.fa-grav:before{content:"\f2d6"}.fa-greater-than:before{content:"\f531"}.fa-greater-than-equal:before{content:"\f532"}.fa-grimace:before{content:"\f57f"}.fa-grin:before{content:"\f580"}.fa-grin-alt:before{content:"\f581"}.fa-grin-beam:before{content:"\f582"}.fa-grin-beam-sweat:before{content:"\f583"}.fa-grin-hearts:before{content:"\f584"}.fa-grin-squint:before{content:"\f585"}.fa-grin-squint-tears:before{content:"\f586"}.fa-grin-stars:before{content:"\f587"}.fa-grin-tears:before{content:"\f588"}.fa-grin-tongue:before{content:"\f589"}.fa-grin-tongue-squint:before{content:"\f58a"}.fa-grin-tongue-wink:before{content:"\f58b"}.fa-grin-wink:before{content:"\f58c"}.fa-grip-horizontal:before{content:"\f58d"}.fa-grip-lines:before{content:"\f7a4"}.fa-grip-lines-vertical:before{content:"\f7a5"}.fa-grip-vertical:before{content:"\f58e"}.fa-gripfire:before{content:"\f3ac"}.fa-grunt:before{content:"\f3ad"}.fa-guitar:before{content:"\f7a6"}.fa-gulp:before{content:"\f3ae"}.fa-h-square:before{content:"\f0fd"}.fa-hacker-news:before{content:"\f1d4"}.fa-hacker-news-square:before{content:"\f3af"}.fa-hackerrank:before{content:"\f5f7"}.fa-hamburger:before{content:"\f805"}.fa-hammer:before{content:"\f6e3"}.fa-hamsa:before{content:"\f665"}.fa-hand-holding:before{content:"\f4bd"}.fa-hand-holding-heart:before{content:"\f4be"}.fa-hand-holding-medical:before{content:"\e05c"}.fa-hand-holding-usd:before{content:"\f4c0"}.fa-hand-holding-water:before{content:"\f4c1"}.fa-hand-lizard:before{content:"\f258"}.fa-hand-middle-finger:before{content:"\f806"}.fa-hand-paper:before{content:"\f256"}.fa-hand-peace:before{content:"\f25b"}.fa-hand-point-down:before{content:"\f0a7"}.fa-hand-point-left:before{content:"\f0a5"}.fa-hand-point-right:before{content:"\f0a4"}.fa-hand-point-up:before{content:"\f0a6"}.fa-hand-pointer:before{content:"\f25a"}.fa-hand-rock:before{content:"\f255"}.fa-hand-scissors:before{content:"\f257"}.fa-hand-sparkles:before{content:"\e05d"}.fa-hand-spock:before{content:"\f259"}.fa-hands:before{content:"\f4c2"}.fa-hands-helping:before{content:"\f4c4"}.fa-hands-wash:before{content:"\e05e"}.fa-handshake:before{content:"\f2b5"}.fa-handshake-alt-slash:before{content:"\e05f"}.fa-handshake-slash:before{content:"\e060"}.fa-hanukiah:before{content:"\f6e6"}.fa-hard-hat:before{content:"\f807"}.fa-hashtag:before{content:"\f292"}.fa-hat-cowboy:before{content:"\f8c0"}.fa-hat-cowboy-side:before{content:"\f8c1"}.fa-hat-wizard:before{content:"\f6e8"}.fa-hdd:before{content:"\f0a0"}.fa-head-side-cough:before{content:"\e061"}.fa-head-side-cough-slash:before{content:"\e062"}.fa-head-side-mask:before{content:"\e063"}.fa-head-side-virus:before{content:"\e064"}.fa-heading:before{content:"\f1dc"}.fa-headphones:before{content:"\f025"}.fa-headphones-alt:before{content:"\f58f"}.fa-headset:before{content:"\f590"}.fa-heart:before{content:"\f004"}.fa-heart-broken:before{content:"\f7a9"}.fa-heartbeat:before{content:"\f21e"}.fa-helicopter:before{content:"\f533"}.fa-highlighter:before{content:"\f591"}.fa-hiking:before{content:"\f6ec"}.fa-hippo:before{content:"\f6ed"}.fa-hips:before{content:"\f452"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-history:before{content:"\f1da"}.fa-hockey-puck:before{content:"\f453"}.fa-holly-berry:before{content:"\f7aa"}.fa-home:before{content:"\f015"}.fa-hooli:before{content:"\f427"}.fa-hornbill:before{content:"\f592"}.fa-horse:before{content:"\f6f0"}.fa-horse-head:before{content:"\f7ab"}.fa-hospital:before{content:"\f0f8"}.fa-hospital-alt:before{content:"\f47d"}.fa-hospital-symbol:before{content:"\f47e"}.fa-hospital-user:before{content:"\f80d"}.fa-hot-tub:before{content:"\f593"}.fa-hotdog:before{content:"\f80f"}.fa-hotel:before{content:"\f594"}.fa-hotjar:before{content:"\f3b1"}.fa-hourglass:before{content:"\f254"}.fa-hourglass-end:before{content:"\f253"}.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-start:before{content:"\f251"}.fa-house-damage:before{content:"\f6f1"}.fa-house-user:before{content:"\e065"}.fa-houzz:before{content:"\f27c"}.fa-hryvnia:before{content:"\f6f2"}.fa-html5:before{content:"\f13b"}.fa-hubspot:before{content:"\f3b2"}.fa-i-cursor:before{content:"\f246"}.fa-ice-cream:before{content:"\f810"}.fa-icicles:before{content:"\f7ad"}.fa-icons:before{content:"\f86d"}.fa-id-badge:before{content:"\f2c1"}.fa-id-card:before{content:"\f2c2"}.fa-id-card-alt:before{content:"\f47f"}.fa-ideal:before{content:"\e013"}.fa-igloo:before{content:"\f7ae"}.fa-image:before{content:"\f03e"}.fa-images:before{content:"\f302"}.fa-imdb:before{content:"\f2d8"}.fa-inbox:before{content:"\f01c"}.fa-indent:before{content:"\f03c"}.fa-industry:before{content:"\f275"}.fa-infinity:before{content:"\f534"}.fa-info:before{content:"\f129"}.fa-info-circle:before{content:"\f05a"}.fa-instagram:before{content:"\f16d"}.fa-instagram-square:before{content:"\e055"}.fa-intercom:before{content:"\f7af"}.fa-internet-explorer:before{content:"\f26b"}.fa-invision:before{content:"\f7b0"}.fa-ioxhost:before{content:"\f208"}.fa-italic:before{content:"\f033"}.fa-itch-io:before{content:"\f83a"}.fa-itunes:before{content:"\f3b4"}.fa-itunes-note:before{content:"\f3b5"}.fa-java:before{content:"\f4e4"}.fa-jedi:before{content:"\f669"}.fa-jedi-order:before{content:"\f50e"}.fa-jenkins:before{content:"\f3b6"}.fa-jira:before{content:"\f7b1"}.fa-joget:before{content:"\f3b7"}.fa-joint:before{content:"\f595"}.fa-joomla:before{content:"\f1aa"}.fa-journal-whills:before{content:"\f66a"}.fa-js:before{content:"\f3b8"}.fa-js-square:before{content:"\f3b9"}.fa-jsfiddle:before{content:"\f1cc"}.fa-kaaba:before{content:"\f66b"}.fa-kaggle:before{content:"\f5fa"}.fa-key:before{content:"\f084"}.fa-keybase:before{content:"\f4f5"}.fa-keyboard:before{content:"\f11c"}.fa-keycdn:before{content:"\f3ba"}.fa-khanda:before{content:"\f66d"}.fa-kickstarter:before{content:"\f3bb"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-kiss:before{content:"\f596"}.fa-kiss-beam:before{content:"\f597"}.fa-kiss-wink-heart:before{content:"\f598"}.fa-kiwi-bird:before{content:"\f535"}.fa-korvue:before{content:"\f42f"}.fa-landmark:before{content:"\f66f"}.fa-language:before{content:"\f1ab"}.fa-laptop:before{content:"\f109"}.fa-laptop-code:before{content:"\f5fc"}.fa-laptop-house:before{content:"\e066"}.fa-laptop-medical:before{content:"\f812"}.fa-laravel:before{content:"\f3bd"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-laugh:before{content:"\f599"}.fa-laugh-beam:before{content:"\f59a"}.fa-laugh-squint:before{content:"\f59b"}.fa-laugh-wink:before{content:"\f59c"}.fa-layer-group:before{content:"\f5fd"}.fa-leaf:before{content:"\f06c"}.fa-leanpub:before{content:"\f212"}.fa-lemon:before{content:"\f094"}.fa-less:before{content:"\f41d"}.fa-less-than:before{content:"\f536"}.fa-less-than-equal:before{content:"\f537"}.fa-level-down-alt:before{content:"\f3be"}.fa-level-up-alt:before{content:"\f3bf"}.fa-life-ring:before{content:"\f1cd"}.fa-lightbulb:before{content:"\f0eb"}.fa-line:before{content:"\f3c0"}.fa-link:before{content:"\f0c1"}.fa-linkedin:before{content:"\f08c"}.fa-linkedin-in:before{content:"\f0e1"}.fa-linode:before{content:"\f2b8"}.fa-linux:before{content:"\f17c"}.fa-lira-sign:before{content:"\f195"}.fa-list:before{content:"\f03a"}.fa-list-alt:before{content:"\f022"}.fa-list-ol:before{content:"\f0cb"}.fa-list-ul:before{content:"\f0ca"}.fa-location-arrow:before{content:"\f124"}.fa-lock:before{content:"\f023"}.fa-lock-open:before{content:"\f3c1"}.fa-long-arrow-alt-down:before{content:"\f309"}.fa-long-arrow-alt-left:before{content:"\f30a"}.fa-long-arrow-alt-right:before{content:"\f30b"}.fa-long-arrow-alt-up:before{content:"\f30c"}.fa-low-vision:before{content:"\f2a8"}.fa-luggage-cart:before{content:"\f59d"}.fa-lungs:before{content:"\f604"}.fa-lungs-virus:before{content:"\e067"}.fa-lyft:before{content:"\f3c3"}.fa-magento:before{content:"\f3c4"}.fa-magic:before{content:"\f0d0"}.fa-magnet:before{content:"\f076"}.fa-mail-bulk:before{content:"\f674"}.fa-mailchimp:before{content:"\f59e"}.fa-male:before{content:"\f183"}.fa-mandalorian:before{content:"\f50f"}.fa-map:before{content:"\f279"}.fa-map-marked:before{content:"\f59f"}.fa-map-marked-alt:before{content:"\f5a0"}.fa-map-marker:before{content:"\f041"}.fa-map-marker-alt:before{content:"\f3c5"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-markdown:before{content:"\f60f"}.fa-marker:before{content:"\f5a1"}.fa-mars:before{content:"\f222"}.fa-mars-double:before{content:"\f227"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mask:before{content:"\f6fa"}.fa-mastodon:before{content:"\f4f6"}.fa-maxcdn:before{content:"\f136"}.fa-mdb:before{content:"\f8ca"}.fa-medal:before{content:"\f5a2"}.fa-medapps:before{content:"\f3c6"}.fa-medium:before{content:"\f23a"}.fa-medium-m:before{content:"\f3c7"}.fa-medkit:before{content:"\f0fa"}.fa-medrt:before{content:"\f3c8"}.fa-meetup:before{content:"\f2e0"}.fa-megaport:before{content:"\f5a3"}.fa-meh:before{content:"\f11a"}.fa-meh-blank:before{content:"\f5a4"}.fa-meh-rolling-eyes:before{content:"\f5a5"}.fa-memory:before{content:"\f538"}.fa-mendeley:before{content:"\f7b3"}.fa-menorah:before{content:"\f676"}.fa-mercury:before{content:"\f223"}.fa-meteor:before{content:"\f753"}.fa-microblog:before{content:"\e01a"}.fa-microchip:before{content:"\f2db"}.fa-microphone:before{content:"\f130"}.fa-microphone-alt:before{content:"\f3c9"}.fa-microphone-alt-slash:before{content:"\f539"}.fa-microphone-slash:before{content:"\f131"}.fa-microscope:before{content:"\f610"}.fa-microsoft:before{content:"\f3ca"}.fa-minus:before{content:"\f068"}.fa-minus-circle:before{content:"\f056"}.fa-minus-square:before{content:"\f146"}.fa-mitten:before{content:"\f7b5"}.fa-mix:before{content:"\f3cb"}.fa-mixcloud:before{content:"\f289"}.fa-mixer:before{content:"\e056"}.fa-mizuni:before{content:"\f3cc"}.fa-mobile:before{content:"\f10b"}.fa-mobile-alt:before{content:"\f3cd"}.fa-modx:before{content:"\f285"}.fa-monero:before{content:"\f3d0"}.fa-money-bill:before{content:"\f0d6"}.fa-money-bill-alt:before{content:"\f3d1"}.fa-money-bill-wave:before{content:"\f53a"}.fa-money-bill-wave-alt:before{content:"\f53b"}.fa-money-check:before{content:"\f53c"}.fa-money-check-alt:before{content:"\f53d"}.fa-monument:before{content:"\f5a6"}.fa-moon:before{content:"\f186"}.fa-mortar-pestle:before{content:"\f5a7"}.fa-mosque:before{content:"\f678"}.fa-motorcycle:before{content:"\f21c"}.fa-mountain:before{content:"\f6fc"}.fa-mouse:before{content:"\f8cc"}.fa-mouse-pointer:before{content:"\f245"}.fa-mug-hot:before{content:"\f7b6"}.fa-music:before{content:"\f001"}.fa-napster:before{content:"\f3d2"}.fa-neos:before{content:"\f612"}.fa-network-wired:before{content:"\f6ff"}.fa-neuter:before{content:"\f22c"}.fa-newspaper:before{content:"\f1ea"}.fa-nimblr:before{content:"\f5a8"}.fa-node:before{content:"\f419"}.fa-node-js:before{content:"\f3d3"}.fa-not-equal:before{content:"\f53e"}.fa-notes-medical:before{content:"\f481"}.fa-npm:before{content:"\f3d4"}.fa-ns8:before{content:"\f3d5"}.fa-nutritionix:before{content:"\f3d6"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-oil-can:before{content:"\f613"}.fa-old-republic:before{content:"\f510"}.fa-om:before{content:"\f679"}.fa-opencart:before{content:"\f23d"}.fa-openid:before{content:"\f19b"}.fa-opera:before{content:"\f26a"}.fa-optin-monster:before{content:"\f23c"}.fa-orcid:before{content:"\f8d2"}.fa-osi:before{content:"\f41a"}.fa-otter:before{content:"\f700"}.fa-outdent:before{content:"\f03b"}.fa-page4:before{content:"\f3d7"}.fa-pagelines:before{content:"\f18c"}.fa-pager:before{content:"\f815"}.fa-paint-brush:before{content:"\f1fc"}.fa-paint-roller:before{content:"\f5aa"}.fa-palette:before{content:"\f53f"}.fa-palfed:before{content:"\f3d8"}.fa-pallet:before{content:"\f482"}.fa-paper-plane:before{content:"\f1d8"}.fa-paperclip:before{content:"\f0c6"}.fa-parachute-box:before{content:"\f4cd"}.fa-paragraph:before{content:"\f1dd"}.fa-parking:before{content:"\f540"}.fa-passport:before{content:"\f5ab"}.fa-pastafarianism:before{content:"\f67b"}.fa-paste:before{content:"\f0ea"}.fa-patreon:before{content:"\f3d9"}.fa-pause:before{content:"\f04c"}.fa-pause-circle:before{content:"\f28b"}.fa-paw:before{content:"\f1b0"}.fa-paypal:before{content:"\f1ed"}.fa-peace:before{content:"\f67c"}.fa-pen:before{content:"\f304"}.fa-pen-alt:before{content:"\f305"}.fa-pen-fancy:before{content:"\f5ac"}.fa-pen-nib:before{content:"\f5ad"}.fa-pen-square:before{content:"\f14b"}.fa-pencil-alt:before{content:"\f303"}.fa-pencil-ruler:before{content:"\f5ae"}.fa-penny-arcade:before{content:"\f704"}.fa-people-arrows:before{content:"\e068"}.fa-people-carry:before{content:"\f4ce"}.fa-pepper-hot:before{content:"\f816"}.fa-percent:before{content:"\f295"}.fa-percentage:before{content:"\f541"}.fa-periscope:before{content:"\f3da"}.fa-person-booth:before{content:"\f756"}.fa-phabricator:before{content:"\f3db"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-phoenix-squadron:before{content:"\f511"}.fa-phone:before{content:"\f095"}.fa-phone-alt:before{content:"\f879"}.fa-phone-slash:before{content:"\f3dd"}.fa-phone-square:before{content:"\f098"}.fa-phone-square-alt:before{content:"\f87b"}.fa-phone-volume:before{content:"\f2a0"}.fa-photo-video:before{content:"\f87c"}.fa-php:before{content:"\f457"}.fa-pied-piper:before{content:"\f2ae"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-pied-piper-hat:before{content:"\f4e5"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-square:before{content:"\e01e"}.fa-piggy-bank:before{content:"\f4d3"}.fa-pills:before{content:"\f484"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-p:before{content:"\f231"}.fa-pinterest-square:before{content:"\f0d3"}.fa-pizza-slice:before{content:"\f818"}.fa-place-of-worship:before{content:"\f67f"}.fa-plane:before{content:"\f072"}.fa-plane-arrival:before{content:"\f5af"}.fa-plane-departure:before{content:"\f5b0"}.fa-plane-slash:before{content:"\e069"}.fa-play:before{content:"\f04b"}.fa-play-circle:before{content:"\f144"}.fa-playstation:before{content:"\f3df"}.fa-plug:before{content:"\f1e6"}.fa-plus:before{content:"\f067"}.fa-plus-circle:before{content:"\f055"}.fa-plus-square:before{content:"\f0fe"}.fa-podcast:before{content:"\f2ce"}.fa-poll:before{content:"\f681"}.fa-poll-h:before{content:"\f682"}.fa-poo:before{content:"\f2fe"}.fa-poo-storm:before{content:"\f75a"}.fa-poop:before{content:"\f619"}.fa-portrait:before{content:"\f3e0"}.fa-pound-sign:before{content:"\f154"}.fa-power-off:before{content:"\f011"}.fa-pray:before{content:"\f683"}.fa-praying-hands:before{content:"\f684"}.fa-prescription:before{content:"\f5b1"}.fa-prescription-bottle:before{content:"\f485"}.fa-prescription-bottle-alt:before{content:"\f486"}.fa-print:before{content:"\f02f"}.fa-procedures:before{content:"\f487"}.fa-product-hunt:before{content:"\f288"}.fa-project-diagram:before{content:"\f542"}.fa-pump-medical:before{content:"\e06a"}.fa-pump-soap:before{content:"\e06b"}.fa-pushed:before{content:"\f3e1"}.fa-puzzle-piece:before{content:"\f12e"}.fa-python:before{content:"\f3e2"}.fa-qq:before{content:"\f1d6"}.fa-qrcode:before{content:"\f029"}.fa-question:before{content:"\f128"}.fa-question-circle:before{content:"\f059"}.fa-quidditch:before{content:"\f458"}.fa-quinscape:before{content:"\f459"}.fa-quora:before{content:"\f2c4"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-quran:before{content:"\f687"}.fa-r-project:before{content:"\f4f7"}.fa-radiation:before{content:"\f7b9"}.fa-radiation-alt:before{content:"\f7ba"}.fa-rainbow:before{content:"\f75b"}.fa-random:before{content:"\f074"}.fa-raspberry-pi:before{content:"\f7bb"}.fa-ravelry:before{content:"\f2d9"}.fa-react:before{content:"\f41b"}.fa-reacteurope:before{content:"\f75d"}.fa-readme:before{content:"\f4d5"}.fa-rebel:before{content:"\f1d0"}.fa-receipt:before{content:"\f543"}.fa-record-vinyl:before{content:"\f8d9"}.fa-recycle:before{content:"\f1b8"}.fa-red-river:before{content:"\f3e3"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-alien:before{content:"\f281"}.fa-reddit-square:before{content:"\f1a2"}.fa-redhat:before{content:"\f7bc"}.fa-redo:before{content:"\f01e"}.fa-redo-alt:before{content:"\f2f9"}.fa-registered:before{content:"\f25d"}.fa-remove-format:before{content:"\f87d"}.fa-renren:before{content:"\f18b"}.fa-reply:before{content:"\f3e5"}.fa-reply-all:before{content:"\f122"}.fa-replyd:before{content:"\f3e6"}.fa-republican:before{content:"\f75e"}.fa-researchgate:before{content:"\f4f8"}.fa-resolving:before{content:"\f3e7"}.fa-restroom:before{content:"\f7bd"}.fa-retweet:before{content:"\f079"}.fa-rev:before{content:"\f5b2"}.fa-ribbon:before{content:"\f4d6"}.fa-ring:before{content:"\f70b"}.fa-road:before{content:"\f018"}.fa-robot:before{content:"\f544"}.fa-rocket:before{content:"\f135"}.fa-rocketchat:before{content:"\f3e8"}.fa-rockrms:before{content:"\f3e9"}.fa-route:before{content:"\f4d7"}.fa-rss:before{content:"\f09e"}.fa-rss-square:before{content:"\f143"}.fa-ruble-sign:before{content:"\f158"}.fa-ruler:before{content:"\f545"}.fa-ruler-combined:before{content:"\f546"}.fa-ruler-horizontal:before{content:"\f547"}.fa-ruler-vertical:before{content:"\f548"}.fa-running:before{content:"\f70c"}.fa-rupee-sign:before{content:"\f156"}.fa-rust:before{content:"\e07a"}.fa-sad-cry:before{content:"\f5b3"}.fa-sad-tear:before{content:"\f5b4"}.fa-safari:before{content:"\f267"}.fa-salesforce:before{content:"\f83b"}.fa-sass:before{content:"\f41e"}.fa-satellite:before{content:"\f7bf"}.fa-satellite-dish:before{content:"\f7c0"}.fa-save:before{content:"\f0c7"}.fa-schlix:before{content:"\f3ea"}.fa-school:before{content:"\f549"}.fa-screwdriver:before{content:"\f54a"}.fa-scribd:before{content:"\f28a"}.fa-scroll:before{content:"\f70e"}.fa-sd-card:before{content:"\f7c2"}.fa-search:before{content:"\f002"}.fa-search-dollar:before{content:"\f688"}.fa-search-location:before{content:"\f689"}.fa-search-minus:before{content:"\f010"}.fa-search-plus:before{content:"\f00e"}.fa-searchengin:before{content:"\f3eb"}.fa-seedling:before{content:"\f4d8"}.fa-sellcast:before{content:"\f2da"}.fa-sellsy:before{content:"\f213"}.fa-server:before{content:"\f233"}.fa-servicestack:before{content:"\f3ec"}.fa-shapes:before{content:"\f61f"}.fa-share:before{content:"\f064"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-share-square:before{content:"\f14d"}.fa-shekel-sign:before{content:"\f20b"}.fa-shield-alt:before{content:"\f3ed"}.fa-shield-virus:before{content:"\e06c"}.fa-ship:before{content:"\f21a"}.fa-shipping-fast:before{content:"\f48b"}.fa-shirtsinbulk:before{content:"\f214"}.fa-shoe-prints:before{content:"\f54b"}.fa-shopify:before{content:"\e057"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-shopping-cart:before{content:"\f07a"}.fa-shopware:before{content:"\f5b5"}.fa-shower:before{content:"\f2cc"}.fa-shuttle-van:before{content:"\f5b6"}.fa-sign:before{content:"\f4d9"}.fa-sign-in-alt:before{content:"\f2f6"}.fa-sign-language:before{content:"\f2a7"}.fa-sign-out-alt:before{content:"\f2f5"}.fa-signal:before{content:"\f012"}.fa-signature:before{content:"\f5b7"}.fa-sim-card:before{content:"\f7c4"}.fa-simplybuilt:before{content:"\f215"}.fa-sink:before{content:"\e06d"}.fa-sistrix:before{content:"\f3ee"}.fa-sitemap:before{content:"\f0e8"}.fa-sith:before{content:"\f512"}.fa-skating:before{content:"\f7c5"}.fa-sketch:before{content:"\f7c6"}.fa-skiing:before{content:"\f7c9"}.fa-skiing-nordic:before{content:"\f7ca"}.fa-skull:before{content:"\f54c"}.fa-skull-crossbones:before{content:"\f714"}.fa-skyatlas:before{content:"\f216"}.fa-skype:before{content:"\f17e"}.fa-slack:before{content:"\f198"}.fa-slack-hash:before{content:"\f3ef"}.fa-slash:before{content:"\f715"}.fa-sleigh:before{content:"\f7cc"}.fa-sliders-h:before{content:"\f1de"}.fa-slideshare:before{content:"\f1e7"}.fa-smile:before{content:"\f118"}.fa-smile-beam:before{content:"\f5b8"}.fa-smile-wink:before{content:"\f4da"}.fa-smog:before{content:"\f75f"}.fa-smoking:before{content:"\f48d"}.fa-smoking-ban:before{content:"\f54d"}.fa-sms:before{content:"\f7cd"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-snowboarding:before{content:"\f7ce"}.fa-snowflake:before{content:"\f2dc"}.fa-snowman:before{content:"\f7d0"}.fa-snowplow:before{content:"\f7d2"}.fa-soap:before{content:"\e06e"}.fa-socks:before{content:"\f696"}.fa-solar-panel:before{content:"\f5ba"}.fa-sort:before{content:"\f0dc"}.fa-sort-alpha-down:before{content:"\f15d"}.fa-sort-alpha-down-alt:before{content:"\f881"}.fa-sort-alpha-up:before{content:"\f15e"}.fa-sort-alpha-up-alt:before{content:"\f882"}.fa-sort-amount-down:before{content:"\f160"}.fa-sort-amount-down-alt:before{content:"\f884"}.fa-sort-amount-up:before{content:"\f161"}.fa-sort-amount-up-alt:before{content:"\f885"}.fa-sort-down:before{content:"\f0dd"}.fa-sort-numeric-down:before{content:"\f162"}.fa-sort-numeric-down-alt:before{content:"\f886"}.fa-sort-numeric-up:before{content:"\f163"}.fa-sort-numeric-up-alt:before{content:"\f887"}.fa-sort-up:before{content:"\f0de"}.fa-soundcloud:before{content:"\f1be"}.fa-sourcetree:before{content:"\f7d3"}.fa-spa:before{content:"\f5bb"}.fa-space-shuttle:before{content:"\f197"}.fa-speakap:before{content:"\f3f3"}.fa-speaker-deck:before{content:"\f83c"}.fa-spell-check:before{content:"\f891"}.fa-spider:before{content:"\f717"}.fa-spinner:before{content:"\f110"}.fa-splotch:before{content:"\f5bc"}.fa-spotify:before{content:"\f1bc"}.fa-spray-can:before{content:"\f5bd"}.fa-square:before{content:"\f0c8"}.fa-square-full:before{content:"\f45c"}.fa-square-root-alt:before{content:"\f698"}.fa-squarespace:before{content:"\f5be"}.fa-stack-exchange:before{content:"\f18d"}.fa-stack-overflow:before{content:"\f16c"}.fa-stackpath:before{content:"\f842"}.fa-stamp:before{content:"\f5bf"}.fa-star:before{content:"\f005"}.fa-star-and-crescent:before{content:"\f699"}.fa-star-half:before{content:"\f089"}.fa-star-half-alt:before{content:"\f5c0"}.fa-star-of-david:before{content:"\f69a"}.fa-star-of-life:before{content:"\f621"}.fa-staylinked:before{content:"\f3f5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-steam-symbol:before{content:"\f3f6"}.fa-step-backward:before{content:"\f048"}.fa-step-forward:before{content:"\f051"}.fa-stethoscope:before{content:"\f0f1"}.fa-sticker-mule:before{content:"\f3f7"}.fa-sticky-note:before{content:"\f249"}.fa-stop:before{content:"\f04d"}.fa-stop-circle:before{content:"\f28d"}.fa-stopwatch:before{content:"\f2f2"}.fa-stopwatch-20:before{content:"\e06f"}.fa-store:before{content:"\f54e"}.fa-store-alt:before{content:"\f54f"}.fa-store-alt-slash:before{content:"\e070"}.fa-store-slash:before{content:"\e071"}.fa-strava:before{content:"\f428"}.fa-stream:before{content:"\f550"}.fa-street-view:before{content:"\f21d"}.fa-strikethrough:before{content:"\f0cc"}.fa-stripe:before{content:"\f429"}.fa-stripe-s:before{content:"\f42a"}.fa-stroopwafel:before{content:"\f551"}.fa-studiovinari:before{content:"\f3f8"}.fa-stumbleupon:before{content:"\f1a4"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-subscript:before{content:"\f12c"}.fa-subway:before{content:"\f239"}.fa-suitcase:before{content:"\f0f2"}.fa-suitcase-rolling:before{content:"\f5c1"}.fa-sun:before{content:"\f185"}.fa-superpowers:before{content:"\f2dd"}.fa-superscript:before{content:"\f12b"}.fa-supple:before{content:"\f3f9"}.fa-surprise:before{content:"\f5c2"}.fa-suse:before{content:"\f7d6"}.fa-swatchbook:before{content:"\f5c3"}.fa-swift:before{content:"\f8e1"}.fa-swimmer:before{content:"\f5c4"}.fa-swimming-pool:before{content:"\f5c5"}.fa-symfony:before{content:"\f83d"}.fa-synagogue:before{content:"\f69b"}.fa-sync:before{content:"\f021"}.fa-sync-alt:before{content:"\f2f1"}.fa-syringe:before{content:"\f48e"}.fa-table:before{content:"\f0ce"}.fa-table-tennis:before{content:"\f45d"}.fa-tablet:before{content:"\f10a"}.fa-tablet-alt:before{content:"\f3fa"}.fa-tablets:before{content:"\f490"}.fa-tachometer-alt:before{content:"\f3fd"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-tape:before{content:"\f4db"}.fa-tasks:before{content:"\f0ae"}.fa-taxi:before{content:"\f1ba"}.fa-teamspeak:before{content:"\f4f9"}.fa-teeth:before{content:"\f62e"}.fa-teeth-open:before{content:"\f62f"}.fa-telegram:before{content:"\f2c6"}.fa-telegram-plane:before{content:"\f3fe"}.fa-temperature-high:before{content:"\f769"}.fa-temperature-low:before{content:"\f76b"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-tenge:before{content:"\f7d7"}.fa-terminal:before{content:"\f120"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-th:before{content:"\f00a"}.fa-th-large:before{content:"\f009"}.fa-th-list:before{content:"\f00b"}.fa-the-red-yeti:before{content:"\f69d"}.fa-theater-masks:before{content:"\f630"}.fa-themeco:before{content:"\f5c6"}.fa-themeisle:before{content:"\f2b2"}.fa-thermometer:before{content:"\f491"}.fa-thermometer-empty:before{content:"\f2cb"}.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-think-peaks:before{content:"\f731"}.fa-thumbs-down:before{content:"\f165"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbtack:before{content:"\f08d"}.fa-ticket-alt:before{content:"\f3ff"}.fa-tiktok:before{content:"\e07b"}.fa-times:before{content:"\f00d"}.fa-times-circle:before{content:"\f057"}.fa-tint:before{content:"\f043"}.fa-tint-slash:before{content:"\f5c7"}.fa-tired:before{content:"\f5c8"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-toilet:before{content:"\f7d8"}.fa-toilet-paper:before{content:"\f71e"}.fa-toilet-paper-slash:before{content:"\e072"}.fa-toolbox:before{content:"\f552"}.fa-tools:before{content:"\f7d9"}.fa-tooth:before{content:"\f5c9"}.fa-torah:before{content:"\f6a0"}.fa-torii-gate:before{content:"\f6a1"}.fa-tractor:before{content:"\f722"}.fa-trade-federation:before{content:"\f513"}.fa-trademark:before{content:"\f25c"}.fa-traffic-light:before{content:"\f637"}.fa-trailer:before{content:"\e041"}.fa-train:before{content:"\f238"}.fa-tram:before{content:"\f7da"}.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-trash:before{content:"\f1f8"}.fa-trash-alt:before{content:"\f2ed"}.fa-trash-restore:before{content:"\f829"}.fa-trash-restore-alt:before{content:"\f82a"}.fa-tree:before{content:"\f1bb"}.fa-trello:before{content:"\f181"}.fa-tripadvisor:before{content:"\f262"}.fa-trophy:before{content:"\f091"}.fa-truck:before{content:"\f0d1"}.fa-truck-loading:before{content:"\f4de"}.fa-truck-monster:before{content:"\f63b"}.fa-truck-moving:before{content:"\f4df"}.fa-truck-pickup:before{content:"\f63c"}.fa-tshirt:before{content:"\f553"}.fa-tty:before{content:"\f1e4"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-tv:before{content:"\f26c"}.fa-twitch:before{content:"\f1e8"}.fa-twitter:before{content:"\f099"}.fa-twitter-square:before{content:"\f081"}.fa-typo3:before{content:"\f42b"}.fa-uber:before{content:"\f402"}.fa-ubuntu:before{content:"\f7df"}.fa-uikit:before{content:"\f403"}.fa-umbraco:before{content:"\f8e8"}.fa-umbrella:before{content:"\f0e9"}.fa-umbrella-beach:before{content:"\f5ca"}.fa-underline:before{content:"\f0cd"}.fa-undo:before{content:"\f0e2"}.fa-undo-alt:before{content:"\f2ea"}.fa-uniregistry:before{content:"\f404"}.fa-unity:before{content:"\e049"}.fa-universal-access:before{content:"\f29a"}.fa-university:before{content:"\f19c"}.fa-unlink:before{content:"\f127"}.fa-unlock:before{content:"\f09c"}.fa-unlock-alt:before{content:"\f13e"}.fa-unsplash:before{content:"\e07c"}.fa-untappd:before{content:"\f405"}.fa-upload:before{content:"\f093"}.fa-ups:before{content:"\f7e0"}.fa-usb:before{content:"\f287"}.fa-user:before{content:"\f007"}.fa-user-alt:before{content:"\f406"}.fa-user-alt-slash:before{content:"\f4fa"}.fa-user-astronaut:before{content:"\f4fb"}.fa-user-check:before{content:"\f4fc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-clock:before{content:"\f4fd"}.fa-user-cog:before{content:"\f4fe"}.fa-user-edit:before{content:"\f4ff"}.fa-user-friends:before{content:"\f500"}.fa-user-graduate:before{content:"\f501"}.fa-user-injured:before{content:"\f728"}.fa-user-lock:before{content:"\f502"}.fa-user-md:before{content:"\f0f0"}.fa-user-minus:before{content:"\f503"}.fa-user-ninja:before{content:"\f504"}.fa-user-nurse:before{content:"\f82f"}.fa-user-plus:before{content:"\f234"}.fa-user-secret:before{content:"\f21b"}.fa-user-shield:before{content:"\f505"}.fa-user-slash:before{content:"\f506"}.fa-user-tag:before{content:"\f507"}.fa-user-tie:before{content:"\f508"}.fa-user-times:before{content:"\f235"}.fa-users:before{content:"\f0c0"}.fa-users-cog:before{content:"\f509"}.fa-users-slash:before{content:"\e073"}.fa-usps:before{content:"\f7e1"}.fa-ussunnah:before{content:"\f407"}.fa-utensil-spoon:before{content:"\f2e5"}.fa-utensils:before{content:"\f2e7"}.fa-vaadin:before{content:"\f408"}.fa-vector-square:before{content:"\f5cb"}.fa-venus:before{content:"\f221"}.fa-venus-double:before{content:"\f226"}.fa-venus-mars:before{content:"\f228"}.fa-viacoin:before{content:"\f237"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-vial:before{content:"\f492"}.fa-vials:before{content:"\f493"}.fa-viber:before{content:"\f409"}.fa-video:before{content:"\f03d"}.fa-video-slash:before{content:"\f4e2"}.fa-vihara:before{content:"\f6a7"}.fa-vimeo:before{content:"\f40a"}.fa-vimeo-square:before{content:"\f194"}.fa-vimeo-v:before{content:"\f27d"}.fa-vine:before{content:"\f1ca"}.fa-virus:before{content:"\e074"}.fa-virus-slash:before{content:"\e075"}.fa-viruses:before{content:"\e076"}.fa-vk:before{content:"\f189"}.fa-vnv:before{content:"\f40b"}.fa-voicemail:before{content:"\f897"}.fa-volleyball-ball:before{content:"\f45f"}.fa-volume-down:before{content:"\f027"}.fa-volume-mute:before{content:"\f6a9"}.fa-volume-off:before{content:"\f026"}.fa-volume-up:before{content:"\f028"}.fa-vote-yea:before{content:"\f772"}.fa-vr-cardboard:before{content:"\f729"}.fa-vuejs:before{content:"\f41f"}.fa-walking:before{content:"\f554"}.fa-wallet:before{content:"\f555"}.fa-warehouse:before{content:"\f494"}.fa-water:before{content:"\f773"}.fa-wave-square:before{content:"\f83e"}.fa-waze:before{content:"\f83f"}.fa-weebly:before{content:"\f5cc"}.fa-weibo:before{content:"\f18a"}.fa-weight:before{content:"\f496"}.fa-weight-hanging:before{content:"\f5cd"}.fa-weixin:before{content:"\f1d7"}.fa-whatsapp:before{content:"\f232"}.fa-whatsapp-square:before{content:"\f40c"}.fa-wheelchair:before{content:"\f193"}.fa-whmcs:before{content:"\f40d"}.fa-wifi:before{content:"\f1eb"}.fa-wikipedia-w:before{content:"\f266"}.fa-wind:before{content:"\f72e"}.fa-window-close:before{content:"\f410"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-windows:before{content:"\f17a"}.fa-wine-bottle:before{content:"\f72f"}.fa-wine-glass:before{content:"\f4e3"}.fa-wine-glass-alt:before{content:"\f5ce"}.fa-wix:before{content:"\f5cf"}.fa-wizards-of-the-coast:before{content:"\f730"}.fa-wolf-pack-battalion:before{content:"\f514"}.fa-won-sign:before{content:"\f159"}.fa-wordpress:before{content:"\f19a"}.fa-wordpress-simple:before{content:"\f411"}.fa-wpbeginner:before{content:"\f297"}.fa-wpexplorer:before{content:"\f2de"}.fa-wpforms:before{content:"\f298"}.fa-wpressr:before{content:"\f3e4"}.fa-wrench:before{content:"\f0ad"}.fa-x-ray:before{content:"\f497"}.fa-xbox:before{content:"\f412"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-y-combinator:before{content:"\f23b"}.fa-yahoo:before{content:"\f19e"}.fa-yammer:before{content:"\f840"}.fa-yandex:before{content:"\f413"}.fa-yandex-international:before{content:"\f414"}.fa-yarn:before{content:"\f7e3"}.fa-yelp:before{content:"\f1e9"}.fa-yen-sign:before{content:"\f157"}.fa-yin-yang:before{content:"\f6ad"}.fa-yoast:before{content:"\f2b1"}.fa-youtube:before{content:"\f167"}.fa-youtube-square:before{content:"\f431"}.fa-zhihu:before{content:"\f63f"}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:400;font-display:auto;src:url(fa-regular-400.eot);src:url(fa-regular-400.eot?#iefix) format("embedded-opentype"),url(fa-regular-400.woff) format("woff")}.far{font-weight:400}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:900;font-display:auto;src:url(fa-solid-900.eot);src:url(fa-solid-900.eot?#iefix) format("embedded-opentype"),url(fa-solid-900.woff) format("woff")}.fa,.far,.fas{font-family:"Font Awesome 5 Free"}.fa,.fas{font-weight:900} \ No newline at end of file diff --git a/html/font-awesome/css/v4-shims.min.css b/html/font-awesome/css/v4-shims.min.css index 5f3fdc598c..ee29a2c92d 100644 --- a/html/font-awesome/css/v4-shims.min.css +++ b/html/font-awesome/css/v4-shims.min.css @@ -1,5 +1,5 @@ /*! - * Font Awesome Free 5.9.0 by @fontawesome - https://fontawesome.com + * Font Awesome Free 5.14.0 by @fontawesome - https://fontawesome.com * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) */ -.fa.fa-glass:before{content:"\f000"}.fa.fa-meetup{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-star-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-star-o:before{content:"\f005"}.fa.fa-close:before,.fa.fa-remove:before{content:"\f00d"}.fa.fa-gear:before{content:"\f013"}.fa.fa-trash-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-trash-o:before{content:"\f2ed"}.fa.fa-file-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-o:before{content:"\f15b"}.fa.fa-clock-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-clock-o:before{content:"\f017"}.fa.fa-arrow-circle-o-down{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-arrow-circle-o-down:before{content:"\f358"}.fa.fa-arrow-circle-o-up{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-arrow-circle-o-up:before{content:"\f35b"}.fa.fa-play-circle-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-play-circle-o:before{content:"\f144"}.fa.fa-repeat:before,.fa.fa-rotate-right:before{content:"\f01e"}.fa.fa-refresh:before{content:"\f021"}.fa.fa-list-alt{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-dedent:before{content:"\f03b"}.fa.fa-video-camera:before{content:"\f03d"}.fa.fa-picture-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-picture-o:before{content:"\f03e"}.fa.fa-photo{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-photo:before{content:"\f03e"}.fa.fa-image{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-image:before{content:"\f03e"}.fa.fa-pencil:before{content:"\f303"}.fa.fa-map-marker:before{content:"\f3c5"}.fa.fa-pencil-square-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-pencil-square-o:before{content:"\f044"}.fa.fa-share-square-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-share-square-o:before{content:"\f14d"}.fa.fa-check-square-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-check-square-o:before{content:"\f14a"}.fa.fa-arrows:before{content:"\f0b2"}.fa.fa-times-circle-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-times-circle-o:before{content:"\f057"}.fa.fa-check-circle-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-check-circle-o:before{content:"\f058"}.fa.fa-mail-forward:before{content:"\f064"}.fa.fa-eye,.fa.fa-eye-slash{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-warning:before{content:"\f071"}.fa.fa-calendar:before{content:"\f073"}.fa.fa-arrows-v:before{content:"\f338"}.fa.fa-arrows-h:before{content:"\f337"}.fa.fa-bar-chart{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-bar-chart:before{content:"\f080"}.fa.fa-bar-chart-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-bar-chart-o:before{content:"\f080"}.fa.fa-facebook-square,.fa.fa-twitter-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-gears:before{content:"\f085"}.fa.fa-thumbs-o-up{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-thumbs-o-up:before{content:"\f164"}.fa.fa-thumbs-o-down{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-thumbs-o-down:before{content:"\f165"}.fa.fa-heart-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-heart-o:before{content:"\f004"}.fa.fa-sign-out:before{content:"\f2f5"}.fa.fa-linkedin-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-linkedin-square:before{content:"\f08c"}.fa.fa-thumb-tack:before{content:"\f08d"}.fa.fa-external-link:before{content:"\f35d"}.fa.fa-sign-in:before{content:"\f2f6"}.fa.fa-github-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-lemon-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-lemon-o:before{content:"\f094"}.fa.fa-square-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-square-o:before{content:"\f0c8"}.fa.fa-bookmark-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-bookmark-o:before{content:"\f02e"}.fa.fa-facebook,.fa.fa-twitter{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-facebook:before{content:"\f39e"}.fa.fa-facebook-f{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-facebook-f:before{content:"\f39e"}.fa.fa-github{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-credit-card{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-feed:before{content:"\f09e"}.fa.fa-hdd-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hdd-o:before{content:"\f0a0"}.fa.fa-hand-o-right{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-o-right:before{content:"\f0a4"}.fa.fa-hand-o-left{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-o-left:before{content:"\f0a5"}.fa.fa-hand-o-up{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-o-up:before{content:"\f0a6"}.fa.fa-hand-o-down{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-o-down:before{content:"\f0a7"}.fa.fa-arrows-alt:before{content:"\f31e"}.fa.fa-group:before{content:"\f0c0"}.fa.fa-chain:before{content:"\f0c1"}.fa.fa-scissors:before{content:"\f0c4"}.fa.fa-files-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-files-o:before{content:"\f0c5"}.fa.fa-floppy-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-floppy-o:before{content:"\f0c7"}.fa.fa-navicon:before,.fa.fa-reorder:before{content:"\f0c9"}.fa.fa-google-plus,.fa.fa-google-plus-square,.fa.fa-pinterest,.fa.fa-pinterest-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-google-plus:before{content:"\f0d5"}.fa.fa-money{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-money:before{content:"\f3d1"}.fa.fa-unsorted:before{content:"\f0dc"}.fa.fa-sort-desc:before{content:"\f0dd"}.fa.fa-sort-asc:before{content:"\f0de"}.fa.fa-linkedin{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-linkedin:before{content:"\f0e1"}.fa.fa-rotate-left:before{content:"\f0e2"}.fa.fa-legal:before{content:"\f0e3"}.fa.fa-dashboard:before,.fa.fa-tachometer:before{content:"\f3fd"}.fa.fa-comment-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-comment-o:before{content:"\f075"}.fa.fa-comments-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-comments-o:before{content:"\f086"}.fa.fa-flash:before{content:"\f0e7"}.fa.fa-clipboard,.fa.fa-paste{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-paste:before{content:"\f328"}.fa.fa-lightbulb-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-lightbulb-o:before{content:"\f0eb"}.fa.fa-exchange:before{content:"\f362"}.fa.fa-cloud-download:before{content:"\f381"}.fa.fa-cloud-upload:before{content:"\f382"}.fa.fa-bell-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-bell-o:before{content:"\f0f3"}.fa.fa-cutlery:before{content:"\f2e7"}.fa.fa-file-text-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-text-o:before{content:"\f15c"}.fa.fa-building-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-building-o:before{content:"\f1ad"}.fa.fa-hospital-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hospital-o:before{content:"\f0f8"}.fa.fa-tablet:before{content:"\f3fa"}.fa.fa-mobile-phone:before,.fa.fa-mobile:before{content:"\f3cd"}.fa.fa-circle-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-circle-o:before{content:"\f111"}.fa.fa-mail-reply:before{content:"\f3e5"}.fa.fa-github-alt{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-folder-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-folder-o:before{content:"\f07b"}.fa.fa-folder-open-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-folder-open-o:before{content:"\f07c"}.fa.fa-smile-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-smile-o:before{content:"\f118"}.fa.fa-frown-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-frown-o:before{content:"\f119"}.fa.fa-meh-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-meh-o:before{content:"\f11a"}.fa.fa-keyboard-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-keyboard-o:before{content:"\f11c"}.fa.fa-flag-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-flag-o:before{content:"\f024"}.fa.fa-mail-reply-all:before{content:"\f122"}.fa.fa-star-half-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-star-half-o:before{content:"\f089"}.fa.fa-star-half-empty{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-star-half-empty:before{content:"\f089"}.fa.fa-star-half-full{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-star-half-full:before{content:"\f089"}.fa.fa-code-fork:before{content:"\f126"}.fa.fa-chain-broken:before{content:"\f127"}.fa.fa-shield:before{content:"\f3ed"}.fa.fa-calendar-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-calendar-o:before{content:"\f133"}.fa.fa-css3,.fa.fa-html5,.fa.fa-maxcdn{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-ticket:before{content:"\f3ff"}.fa.fa-minus-square-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-minus-square-o:before{content:"\f146"}.fa.fa-level-up:before{content:"\f3bf"}.fa.fa-level-down:before{content:"\f3be"}.fa.fa-pencil-square:before{content:"\f14b"}.fa.fa-external-link-square:before{content:"\f360"}.fa.fa-compass{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-caret-square-o-down{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-caret-square-o-down:before{content:"\f150"}.fa.fa-toggle-down{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-toggle-down:before{content:"\f150"}.fa.fa-caret-square-o-up{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-caret-square-o-up:before{content:"\f151"}.fa.fa-toggle-up{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-toggle-up:before{content:"\f151"}.fa.fa-caret-square-o-right{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-caret-square-o-right:before{content:"\f152"}.fa.fa-toggle-right{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-toggle-right:before{content:"\f152"}.fa.fa-eur:before,.fa.fa-euro:before{content:"\f153"}.fa.fa-gbp:before{content:"\f154"}.fa.fa-dollar:before,.fa.fa-usd:before{content:"\f155"}.fa.fa-inr:before,.fa.fa-rupee:before{content:"\f156"}.fa.fa-cny:before,.fa.fa-jpy:before,.fa.fa-rmb:before,.fa.fa-yen:before{content:"\f157"}.fa.fa-rouble:before,.fa.fa-rub:before,.fa.fa-ruble:before{content:"\f158"}.fa.fa-krw:before,.fa.fa-won:before{content:"\f159"}.fa.fa-bitcoin,.fa.fa-btc{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-bitcoin:before{content:"\f15a"}.fa.fa-file-text:before{content:"\f15c"}.fa.fa-sort-alpha-asc:before{content:"\f15d"}.fa.fa-sort-alpha-desc:before{content:"\f15e"}.fa.fa-sort-amount-asc:before{content:"\f160"}.fa.fa-sort-amount-desc:before{content:"\f161"}.fa.fa-sort-numeric-asc:before{content:"\f162"}.fa.fa-sort-numeric-desc:before{content:"\f163"}.fa.fa-xing,.fa.fa-xing-square,.fa.fa-youtube,.fa.fa-youtube-play,.fa.fa-youtube-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-youtube-play:before{content:"\f167"}.fa.fa-adn,.fa.fa-bitbucket,.fa.fa-bitbucket-square,.fa.fa-dropbox,.fa.fa-flickr,.fa.fa-instagram,.fa.fa-stack-overflow{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-bitbucket-square:before{content:"\f171"}.fa.fa-tumblr,.fa.fa-tumblr-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-long-arrow-down:before{content:"\f309"}.fa.fa-long-arrow-up:before{content:"\f30c"}.fa.fa-long-arrow-left:before{content:"\f30a"}.fa.fa-long-arrow-right:before{content:"\f30b"}.fa.fa-android,.fa.fa-apple,.fa.fa-dribbble,.fa.fa-foursquare,.fa.fa-gittip,.fa.fa-gratipay,.fa.fa-linux,.fa.fa-skype,.fa.fa-trello,.fa.fa-windows{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-gittip:before{content:"\f184"}.fa.fa-sun-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-sun-o:before{content:"\f185"}.fa.fa-moon-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-moon-o:before{content:"\f186"}.fa.fa-pagelines,.fa.fa-renren,.fa.fa-stack-exchange,.fa.fa-vk,.fa.fa-weibo{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-arrow-circle-o-right{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-arrow-circle-o-right:before{content:"\f35a"}.fa.fa-arrow-circle-o-left{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-arrow-circle-o-left:before{content:"\f359"}.fa.fa-caret-square-o-left{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-caret-square-o-left:before{content:"\f191"}.fa.fa-toggle-left{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-toggle-left:before{content:"\f191"}.fa.fa-dot-circle-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-dot-circle-o:before{content:"\f192"}.fa.fa-vimeo-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-try:before,.fa.fa-turkish-lira:before{content:"\f195"}.fa.fa-plus-square-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-plus-square-o:before{content:"\f0fe"}.fa.fa-openid,.fa.fa-slack,.fa.fa-wordpress{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-bank:before,.fa.fa-institution:before{content:"\f19c"}.fa.fa-mortar-board:before{content:"\f19d"}.fa.fa-delicious,.fa.fa-digg,.fa.fa-drupal,.fa.fa-google,.fa.fa-joomla,.fa.fa-pied-piper-alt,.fa.fa-pied-piper-pp,.fa.fa-reddit,.fa.fa-reddit-square,.fa.fa-stumbleupon,.fa.fa-stumbleupon-circle,.fa.fa-yahoo{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-spoon:before{content:"\f2e5"}.fa.fa-behance,.fa.fa-behance-square,.fa.fa-steam,.fa.fa-steam-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-automobile:before{content:"\f1b9"}.fa.fa-cab:before{content:"\f1ba"}.fa.fa-envelope-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-envelope-o:before{content:"\f0e0"}.fa.fa-deviantart,.fa.fa-soundcloud{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-file-pdf-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-pdf-o:before{content:"\f1c1"}.fa.fa-file-word-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-word-o:before{content:"\f1c2"}.fa.fa-file-excel-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-excel-o:before{content:"\f1c3"}.fa.fa-file-powerpoint-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-powerpoint-o:before{content:"\f1c4"}.fa.fa-file-image-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-image-o:before{content:"\f1c5"}.fa.fa-file-photo-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-photo-o:before{content:"\f1c5"}.fa.fa-file-picture-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-picture-o:before{content:"\f1c5"}.fa.fa-file-archive-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-archive-o:before{content:"\f1c6"}.fa.fa-file-zip-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-zip-o:before{content:"\f1c6"}.fa.fa-file-audio-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-audio-o:before{content:"\f1c7"}.fa.fa-file-sound-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-sound-o:before{content:"\f1c7"}.fa.fa-file-video-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-video-o:before{content:"\f1c8"}.fa.fa-file-movie-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-movie-o:before{content:"\f1c8"}.fa.fa-file-code-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-code-o:before{content:"\f1c9"}.fa.fa-codepen,.fa.fa-jsfiddle,.fa.fa-vine{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-life-bouy,.fa.fa-life-ring{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-life-bouy:before{content:"\f1cd"}.fa.fa-life-buoy{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-life-buoy:before{content:"\f1cd"}.fa.fa-life-saver{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-life-saver:before{content:"\f1cd"}.fa.fa-support{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-support:before{content:"\f1cd"}.fa.fa-circle-o-notch:before{content:"\f1ce"}.fa.fa-ra,.fa.fa-rebel{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-ra:before{content:"\f1d0"}.fa.fa-resistance{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-resistance:before{content:"\f1d0"}.fa.fa-empire,.fa.fa-ge{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-ge:before{content:"\f1d1"}.fa.fa-git,.fa.fa-git-square,.fa.fa-hacker-news,.fa.fa-y-combinator-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-y-combinator-square:before{content:"\f1d4"}.fa.fa-yc-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-yc-square:before{content:"\f1d4"}.fa.fa-qq,.fa.fa-tencent-weibo,.fa.fa-wechat,.fa.fa-weixin{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-wechat:before{content:"\f1d7"}.fa.fa-send:before{content:"\f1d8"}.fa.fa-paper-plane-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-paper-plane-o:before{content:"\f1d8"}.fa.fa-send-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-send-o:before{content:"\f1d8"}.fa.fa-circle-thin{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-circle-thin:before{content:"\f111"}.fa.fa-header:before{content:"\f1dc"}.fa.fa-sliders:before{content:"\f1de"}.fa.fa-futbol-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-futbol-o:before{content:"\f1e3"}.fa.fa-soccer-ball-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-soccer-ball-o:before{content:"\f1e3"}.fa.fa-slideshare,.fa.fa-twitch,.fa.fa-yelp{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-newspaper-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-newspaper-o:before{content:"\f1ea"}.fa.fa-cc-amex,.fa.fa-cc-discover,.fa.fa-cc-mastercard,.fa.fa-cc-paypal,.fa.fa-cc-stripe,.fa.fa-cc-visa,.fa.fa-google-wallet,.fa.fa-paypal{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-bell-slash-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-bell-slash-o:before{content:"\f1f6"}.fa.fa-trash:before{content:"\f2ed"}.fa.fa-copyright{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-eyedropper:before{content:"\f1fb"}.fa.fa-area-chart:before{content:"\f1fe"}.fa.fa-pie-chart:before{content:"\f200"}.fa.fa-line-chart:before{content:"\f201"}.fa.fa-angellist,.fa.fa-ioxhost,.fa.fa-lastfm,.fa.fa-lastfm-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-cc{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-cc:before{content:"\f20a"}.fa.fa-ils:before,.fa.fa-shekel:before,.fa.fa-sheqel:before{content:"\f20b"}.fa.fa-meanpath{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-meanpath:before{content:"\f2b4"}.fa.fa-buysellads,.fa.fa-connectdevelop,.fa.fa-dashcube,.fa.fa-forumbee,.fa.fa-leanpub,.fa.fa-sellsy,.fa.fa-shirtsinbulk,.fa.fa-simplybuilt,.fa.fa-skyatlas{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-diamond{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-diamond:before{content:"\f3a5"}.fa.fa-intersex:before{content:"\f224"}.fa.fa-facebook-official{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-facebook-official:before{content:"\f09a"}.fa.fa-pinterest-p,.fa.fa-whatsapp{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-hotel:before{content:"\f236"}.fa.fa-medium,.fa.fa-viacoin,.fa.fa-y-combinator,.fa.fa-yc{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-yc:before{content:"\f23b"}.fa.fa-expeditedssl,.fa.fa-opencart,.fa.fa-optin-monster{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-battery-4:before,.fa.fa-battery:before{content:"\f240"}.fa.fa-battery-3:before{content:"\f241"}.fa.fa-battery-2:before{content:"\f242"}.fa.fa-battery-1:before{content:"\f243"}.fa.fa-battery-0:before{content:"\f244"}.fa.fa-object-group,.fa.fa-object-ungroup,.fa.fa-sticky-note-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-sticky-note-o:before{content:"\f249"}.fa.fa-cc-diners-club,.fa.fa-cc-jcb{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-clone,.fa.fa-hourglass-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hourglass-o:before{content:"\f254"}.fa.fa-hourglass-1:before{content:"\f251"}.fa.fa-hourglass-2:before{content:"\f252"}.fa.fa-hourglass-3:before{content:"\f253"}.fa.fa-hand-rock-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-rock-o:before{content:"\f255"}.fa.fa-hand-grab-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-grab-o:before{content:"\f255"}.fa.fa-hand-paper-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-paper-o:before{content:"\f256"}.fa.fa-hand-stop-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-stop-o:before{content:"\f256"}.fa.fa-hand-scissors-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-scissors-o:before{content:"\f257"}.fa.fa-hand-lizard-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-lizard-o:before{content:"\f258"}.fa.fa-hand-spock-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-spock-o:before{content:"\f259"}.fa.fa-hand-pointer-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-pointer-o:before{content:"\f25a"}.fa.fa-hand-peace-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-peace-o:before{content:"\f25b"}.fa.fa-registered{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-chrome,.fa.fa-creative-commons,.fa.fa-firefox,.fa.fa-get-pocket,.fa.fa-gg,.fa.fa-gg-circle,.fa.fa-internet-explorer,.fa.fa-odnoklassniki,.fa.fa-odnoklassniki-square,.fa.fa-opera,.fa.fa-safari,.fa.fa-tripadvisor,.fa.fa-wikipedia-w{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-television:before{content:"\f26c"}.fa.fa-500px,.fa.fa-amazon,.fa.fa-contao{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-calendar-plus-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-calendar-plus-o:before{content:"\f271"}.fa.fa-calendar-minus-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-calendar-minus-o:before{content:"\f272"}.fa.fa-calendar-times-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-calendar-times-o:before{content:"\f273"}.fa.fa-calendar-check-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-calendar-check-o:before{content:"\f274"}.fa.fa-map-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-map-o:before{content:"\f279"}.fa.fa-commenting:before{content:"\f4ad"}.fa.fa-commenting-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-commenting-o:before{content:"\f4ad"}.fa.fa-houzz,.fa.fa-vimeo{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-vimeo:before{content:"\f27d"}.fa.fa-black-tie,.fa.fa-edge,.fa.fa-fonticons,.fa.fa-reddit-alien{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-credit-card-alt:before{content:"\f09d"}.fa.fa-codiepie,.fa.fa-fort-awesome,.fa.fa-mixcloud,.fa.fa-modx,.fa.fa-product-hunt,.fa.fa-scribd,.fa.fa-usb{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-pause-circle-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-pause-circle-o:before{content:"\f28b"}.fa.fa-stop-circle-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-stop-circle-o:before{content:"\f28d"}.fa.fa-bluetooth,.fa.fa-bluetooth-b,.fa.fa-envira,.fa.fa-gitlab,.fa.fa-wheelchair-alt,.fa.fa-wpbeginner,.fa.fa-wpforms{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-wheelchair-alt:before{content:"\f368"}.fa.fa-question-circle-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-question-circle-o:before{content:"\f059"}.fa.fa-volume-control-phone:before{content:"\f2a0"}.fa.fa-asl-interpreting:before{content:"\f2a3"}.fa.fa-deafness:before,.fa.fa-hard-of-hearing:before{content:"\f2a4"}.fa.fa-glide,.fa.fa-glide-g{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-signing:before{content:"\f2a7"}.fa.fa-first-order,.fa.fa-google-plus-official,.fa.fa-pied-piper,.fa.fa-snapchat,.fa.fa-snapchat-ghost,.fa.fa-snapchat-square,.fa.fa-themeisle,.fa.fa-viadeo,.fa.fa-viadeo-square,.fa.fa-yoast{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-google-plus-official:before{content:"\f2b3"}.fa.fa-google-plus-circle{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-google-plus-circle:before{content:"\f2b3"}.fa.fa-fa,.fa.fa-font-awesome{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-fa:before{content:"\f2b4"}.fa.fa-handshake-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-handshake-o:before{content:"\f2b5"}.fa.fa-envelope-open-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-envelope-open-o:before{content:"\f2b6"}.fa.fa-linode{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-address-book-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-address-book-o:before{content:"\f2b9"}.fa.fa-vcard:before{content:"\f2bb"}.fa.fa-address-card-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-address-card-o:before{content:"\f2bb"}.fa.fa-vcard-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-vcard-o:before{content:"\f2bb"}.fa.fa-user-circle-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-user-circle-o:before{content:"\f2bd"}.fa.fa-user-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-user-o:before{content:"\f007"}.fa.fa-id-badge{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-drivers-license:before{content:"\f2c2"}.fa.fa-id-card-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-id-card-o:before{content:"\f2c2"}.fa.fa-drivers-license-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-drivers-license-o:before{content:"\f2c2"}.fa.fa-free-code-camp,.fa.fa-quora,.fa.fa-telegram{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-thermometer-4:before,.fa.fa-thermometer:before{content:"\f2c7"}.fa.fa-thermometer-3:before{content:"\f2c8"}.fa.fa-thermometer-2:before{content:"\f2c9"}.fa.fa-thermometer-1:before{content:"\f2ca"}.fa.fa-thermometer-0:before{content:"\f2cb"}.fa.fa-bathtub:before,.fa.fa-s15:before{content:"\f2cd"}.fa.fa-window-maximize,.fa.fa-window-restore{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-times-rectangle:before{content:"\f410"}.fa.fa-window-close-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-window-close-o:before{content:"\f410"}.fa.fa-times-rectangle-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-times-rectangle-o:before{content:"\f410"}.fa.fa-bandcamp,.fa.fa-eercast,.fa.fa-etsy,.fa.fa-grav,.fa.fa-imdb,.fa.fa-ravelry{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-eercast:before{content:"\f2da"}.fa.fa-snowflake-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-snowflake-o:before{content:"\f2dc"}.fa.fa-spotify,.fa.fa-superpowers,.fa.fa-wpexplorer{font-family:"Font Awesome 5 Brands";font-weight:400} \ No newline at end of file +.fa.fa-glass:before{content:"\f000"}.fa.fa-meetup{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-star-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-star-o:before{content:"\f005"}.fa.fa-close:before,.fa.fa-remove:before{content:"\f00d"}.fa.fa-gear:before{content:"\f013"}.fa.fa-trash-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-trash-o:before{content:"\f2ed"}.fa.fa-file-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-o:before{content:"\f15b"}.fa.fa-clock-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-clock-o:before{content:"\f017"}.fa.fa-arrow-circle-o-down{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-arrow-circle-o-down:before{content:"\f358"}.fa.fa-arrow-circle-o-up{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-arrow-circle-o-up:before{content:"\f35b"}.fa.fa-play-circle-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-play-circle-o:before{content:"\f144"}.fa.fa-repeat:before,.fa.fa-rotate-right:before{content:"\f01e"}.fa.fa-refresh:before{content:"\f021"}.fa.fa-list-alt{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-dedent:before{content:"\f03b"}.fa.fa-video-camera:before{content:"\f03d"}.fa.fa-picture-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-picture-o:before{content:"\f03e"}.fa.fa-photo{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-photo:before{content:"\f03e"}.fa.fa-image{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-image:before{content:"\f03e"}.fa.fa-pencil:before{content:"\f303"}.fa.fa-map-marker:before{content:"\f3c5"}.fa.fa-pencil-square-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-pencil-square-o:before{content:"\f044"}.fa.fa-share-square-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-share-square-o:before{content:"\f14d"}.fa.fa-check-square-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-check-square-o:before{content:"\f14a"}.fa.fa-arrows:before{content:"\f0b2"}.fa.fa-times-circle-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-times-circle-o:before{content:"\f057"}.fa.fa-check-circle-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-check-circle-o:before{content:"\f058"}.fa.fa-mail-forward:before{content:"\f064"}.fa.fa-expand:before{content:"\f424"}.fa.fa-compress:before{content:"\f422"}.fa.fa-eye,.fa.fa-eye-slash{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-warning:before{content:"\f071"}.fa.fa-calendar:before{content:"\f073"}.fa.fa-arrows-v:before{content:"\f338"}.fa.fa-arrows-h:before{content:"\f337"}.fa.fa-bar-chart{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-bar-chart:before{content:"\f080"}.fa.fa-bar-chart-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-bar-chart-o:before{content:"\f080"}.fa.fa-facebook-square,.fa.fa-twitter-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-gears:before{content:"\f085"}.fa.fa-thumbs-o-up{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-thumbs-o-up:before{content:"\f164"}.fa.fa-thumbs-o-down{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-thumbs-o-down:before{content:"\f165"}.fa.fa-heart-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-heart-o:before{content:"\f004"}.fa.fa-sign-out:before{content:"\f2f5"}.fa.fa-linkedin-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-linkedin-square:before{content:"\f08c"}.fa.fa-thumb-tack:before{content:"\f08d"}.fa.fa-external-link:before{content:"\f35d"}.fa.fa-sign-in:before{content:"\f2f6"}.fa.fa-github-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-lemon-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-lemon-o:before{content:"\f094"}.fa.fa-square-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-square-o:before{content:"\f0c8"}.fa.fa-bookmark-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-bookmark-o:before{content:"\f02e"}.fa.fa-facebook,.fa.fa-twitter{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-facebook:before{content:"\f39e"}.fa.fa-facebook-f{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-facebook-f:before{content:"\f39e"}.fa.fa-github{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-credit-card{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-feed:before{content:"\f09e"}.fa.fa-hdd-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hdd-o:before{content:"\f0a0"}.fa.fa-hand-o-right{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-o-right:before{content:"\f0a4"}.fa.fa-hand-o-left{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-o-left:before{content:"\f0a5"}.fa.fa-hand-o-up{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-o-up:before{content:"\f0a6"}.fa.fa-hand-o-down{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-o-down:before{content:"\f0a7"}.fa.fa-arrows-alt:before{content:"\f31e"}.fa.fa-group:before{content:"\f0c0"}.fa.fa-chain:before{content:"\f0c1"}.fa.fa-scissors:before{content:"\f0c4"}.fa.fa-files-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-files-o:before{content:"\f0c5"}.fa.fa-floppy-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-floppy-o:before{content:"\f0c7"}.fa.fa-navicon:before,.fa.fa-reorder:before{content:"\f0c9"}.fa.fa-google-plus,.fa.fa-google-plus-square,.fa.fa-pinterest,.fa.fa-pinterest-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-google-plus:before{content:"\f0d5"}.fa.fa-money{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-money:before{content:"\f3d1"}.fa.fa-unsorted:before{content:"\f0dc"}.fa.fa-sort-desc:before{content:"\f0dd"}.fa.fa-sort-asc:before{content:"\f0de"}.fa.fa-linkedin{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-linkedin:before{content:"\f0e1"}.fa.fa-rotate-left:before{content:"\f0e2"}.fa.fa-legal:before{content:"\f0e3"}.fa.fa-dashboard:before,.fa.fa-tachometer:before{content:"\f3fd"}.fa.fa-comment-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-comment-o:before{content:"\f075"}.fa.fa-comments-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-comments-o:before{content:"\f086"}.fa.fa-flash:before{content:"\f0e7"}.fa.fa-clipboard,.fa.fa-paste{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-paste:before{content:"\f328"}.fa.fa-lightbulb-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-lightbulb-o:before{content:"\f0eb"}.fa.fa-exchange:before{content:"\f362"}.fa.fa-cloud-download:before{content:"\f381"}.fa.fa-cloud-upload:before{content:"\f382"}.fa.fa-bell-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-bell-o:before{content:"\f0f3"}.fa.fa-cutlery:before{content:"\f2e7"}.fa.fa-file-text-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-text-o:before{content:"\f15c"}.fa.fa-building-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-building-o:before{content:"\f1ad"}.fa.fa-hospital-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hospital-o:before{content:"\f0f8"}.fa.fa-tablet:before{content:"\f3fa"}.fa.fa-mobile-phone:before,.fa.fa-mobile:before{content:"\f3cd"}.fa.fa-circle-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-circle-o:before{content:"\f111"}.fa.fa-mail-reply:before{content:"\f3e5"}.fa.fa-github-alt{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-folder-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-folder-o:before{content:"\f07b"}.fa.fa-folder-open-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-folder-open-o:before{content:"\f07c"}.fa.fa-smile-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-smile-o:before{content:"\f118"}.fa.fa-frown-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-frown-o:before{content:"\f119"}.fa.fa-meh-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-meh-o:before{content:"\f11a"}.fa.fa-keyboard-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-keyboard-o:before{content:"\f11c"}.fa.fa-flag-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-flag-o:before{content:"\f024"}.fa.fa-mail-reply-all:before{content:"\f122"}.fa.fa-star-half-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-star-half-o:before{content:"\f089"}.fa.fa-star-half-empty{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-star-half-empty:before{content:"\f089"}.fa.fa-star-half-full{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-star-half-full:before{content:"\f089"}.fa.fa-code-fork:before{content:"\f126"}.fa.fa-chain-broken:before{content:"\f127"}.fa.fa-shield:before{content:"\f3ed"}.fa.fa-calendar-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-calendar-o:before{content:"\f133"}.fa.fa-css3,.fa.fa-html5,.fa.fa-maxcdn{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-ticket:before{content:"\f3ff"}.fa.fa-minus-square-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-minus-square-o:before{content:"\f146"}.fa.fa-level-up:before{content:"\f3bf"}.fa.fa-level-down:before{content:"\f3be"}.fa.fa-pencil-square:before{content:"\f14b"}.fa.fa-external-link-square:before{content:"\f360"}.fa.fa-compass{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-caret-square-o-down{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-caret-square-o-down:before{content:"\f150"}.fa.fa-toggle-down{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-toggle-down:before{content:"\f150"}.fa.fa-caret-square-o-up{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-caret-square-o-up:before{content:"\f151"}.fa.fa-toggle-up{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-toggle-up:before{content:"\f151"}.fa.fa-caret-square-o-right{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-caret-square-o-right:before{content:"\f152"}.fa.fa-toggle-right{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-toggle-right:before{content:"\f152"}.fa.fa-eur:before,.fa.fa-euro:before{content:"\f153"}.fa.fa-gbp:before{content:"\f154"}.fa.fa-dollar:before,.fa.fa-usd:before{content:"\f155"}.fa.fa-inr:before,.fa.fa-rupee:before{content:"\f156"}.fa.fa-cny:before,.fa.fa-jpy:before,.fa.fa-rmb:before,.fa.fa-yen:before{content:"\f157"}.fa.fa-rouble:before,.fa.fa-rub:before,.fa.fa-ruble:before{content:"\f158"}.fa.fa-krw:before,.fa.fa-won:before{content:"\f159"}.fa.fa-bitcoin,.fa.fa-btc{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-bitcoin:before{content:"\f15a"}.fa.fa-file-text:before{content:"\f15c"}.fa.fa-sort-alpha-asc:before{content:"\f15d"}.fa.fa-sort-alpha-desc:before{content:"\f881"}.fa.fa-sort-amount-asc:before{content:"\f160"}.fa.fa-sort-amount-desc:before{content:"\f884"}.fa.fa-sort-numeric-asc:before{content:"\f162"}.fa.fa-sort-numeric-desc:before{content:"\f886"}.fa.fa-xing,.fa.fa-xing-square,.fa.fa-youtube,.fa.fa-youtube-play,.fa.fa-youtube-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-youtube-play:before{content:"\f167"}.fa.fa-adn,.fa.fa-bitbucket,.fa.fa-bitbucket-square,.fa.fa-dropbox,.fa.fa-flickr,.fa.fa-instagram,.fa.fa-stack-overflow{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-bitbucket-square:before{content:"\f171"}.fa.fa-tumblr,.fa.fa-tumblr-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-long-arrow-down:before{content:"\f309"}.fa.fa-long-arrow-up:before{content:"\f30c"}.fa.fa-long-arrow-left:before{content:"\f30a"}.fa.fa-long-arrow-right:before{content:"\f30b"}.fa.fa-android,.fa.fa-apple,.fa.fa-dribbble,.fa.fa-foursquare,.fa.fa-gittip,.fa.fa-gratipay,.fa.fa-linux,.fa.fa-skype,.fa.fa-trello,.fa.fa-windows{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-gittip:before{content:"\f184"}.fa.fa-sun-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-sun-o:before{content:"\f185"}.fa.fa-moon-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-moon-o:before{content:"\f186"}.fa.fa-pagelines,.fa.fa-renren,.fa.fa-stack-exchange,.fa.fa-vk,.fa.fa-weibo{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-arrow-circle-o-right{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-arrow-circle-o-right:before{content:"\f35a"}.fa.fa-arrow-circle-o-left{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-arrow-circle-o-left:before{content:"\f359"}.fa.fa-caret-square-o-left{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-caret-square-o-left:before{content:"\f191"}.fa.fa-toggle-left{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-toggle-left:before{content:"\f191"}.fa.fa-dot-circle-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-dot-circle-o:before{content:"\f192"}.fa.fa-vimeo-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-try:before,.fa.fa-turkish-lira:before{content:"\f195"}.fa.fa-plus-square-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-plus-square-o:before{content:"\f0fe"}.fa.fa-openid,.fa.fa-slack,.fa.fa-wordpress{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-bank:before,.fa.fa-institution:before{content:"\f19c"}.fa.fa-mortar-board:before{content:"\f19d"}.fa.fa-delicious,.fa.fa-digg,.fa.fa-drupal,.fa.fa-google,.fa.fa-joomla,.fa.fa-pied-piper-alt,.fa.fa-pied-piper-pp,.fa.fa-reddit,.fa.fa-reddit-square,.fa.fa-stumbleupon,.fa.fa-stumbleupon-circle,.fa.fa-yahoo{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-spoon:before{content:"\f2e5"}.fa.fa-behance,.fa.fa-behance-square,.fa.fa-steam,.fa.fa-steam-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-automobile:before{content:"\f1b9"}.fa.fa-envelope-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-envelope-o:before{content:"\f0e0"}.fa.fa-deviantart,.fa.fa-soundcloud,.fa.fa-spotify{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-file-pdf-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-pdf-o:before{content:"\f1c1"}.fa.fa-file-word-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-word-o:before{content:"\f1c2"}.fa.fa-file-excel-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-excel-o:before{content:"\f1c3"}.fa.fa-file-powerpoint-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-powerpoint-o:before{content:"\f1c4"}.fa.fa-file-image-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-image-o:before{content:"\f1c5"}.fa.fa-file-photo-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-photo-o:before{content:"\f1c5"}.fa.fa-file-picture-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-picture-o:before{content:"\f1c5"}.fa.fa-file-archive-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-archive-o:before{content:"\f1c6"}.fa.fa-file-zip-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-zip-o:before{content:"\f1c6"}.fa.fa-file-audio-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-audio-o:before{content:"\f1c7"}.fa.fa-file-sound-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-sound-o:before{content:"\f1c7"}.fa.fa-file-video-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-video-o:before{content:"\f1c8"}.fa.fa-file-movie-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-movie-o:before{content:"\f1c8"}.fa.fa-file-code-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-file-code-o:before{content:"\f1c9"}.fa.fa-codepen,.fa.fa-jsfiddle,.fa.fa-vine{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-life-bouy,.fa.fa-life-ring{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-life-bouy:before{content:"\f1cd"}.fa.fa-life-buoy{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-life-buoy:before{content:"\f1cd"}.fa.fa-life-saver{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-life-saver:before{content:"\f1cd"}.fa.fa-support{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-support:before{content:"\f1cd"}.fa.fa-circle-o-notch:before{content:"\f1ce"}.fa.fa-ra,.fa.fa-rebel{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-ra:before{content:"\f1d0"}.fa.fa-resistance{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-resistance:before{content:"\f1d0"}.fa.fa-empire,.fa.fa-ge{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-ge:before{content:"\f1d1"}.fa.fa-git,.fa.fa-git-square,.fa.fa-hacker-news,.fa.fa-y-combinator-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-y-combinator-square:before{content:"\f1d4"}.fa.fa-yc-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-yc-square:before{content:"\f1d4"}.fa.fa-qq,.fa.fa-tencent-weibo,.fa.fa-wechat,.fa.fa-weixin{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-wechat:before{content:"\f1d7"}.fa.fa-send:before{content:"\f1d8"}.fa.fa-paper-plane-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-paper-plane-o:before{content:"\f1d8"}.fa.fa-send-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-send-o:before{content:"\f1d8"}.fa.fa-circle-thin{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-circle-thin:before{content:"\f111"}.fa.fa-header:before{content:"\f1dc"}.fa.fa-sliders:before{content:"\f1de"}.fa.fa-futbol-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-futbol-o:before{content:"\f1e3"}.fa.fa-soccer-ball-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-soccer-ball-o:before{content:"\f1e3"}.fa.fa-slideshare,.fa.fa-twitch,.fa.fa-yelp{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-newspaper-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-newspaper-o:before{content:"\f1ea"}.fa.fa-cc-amex,.fa.fa-cc-discover,.fa.fa-cc-mastercard,.fa.fa-cc-paypal,.fa.fa-cc-stripe,.fa.fa-cc-visa,.fa.fa-google-wallet,.fa.fa-paypal{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-bell-slash-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-bell-slash-o:before{content:"\f1f6"}.fa.fa-trash:before{content:"\f2ed"}.fa.fa-copyright{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-eyedropper:before{content:"\f1fb"}.fa.fa-area-chart:before{content:"\f1fe"}.fa.fa-pie-chart:before{content:"\f200"}.fa.fa-line-chart:before{content:"\f201"}.fa.fa-angellist,.fa.fa-ioxhost,.fa.fa-lastfm,.fa.fa-lastfm-square{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-cc{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-cc:before{content:"\f20a"}.fa.fa-ils:before,.fa.fa-shekel:before,.fa.fa-sheqel:before{content:"\f20b"}.fa.fa-meanpath{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-meanpath:before{content:"\f2b4"}.fa.fa-buysellads,.fa.fa-connectdevelop,.fa.fa-dashcube,.fa.fa-forumbee,.fa.fa-leanpub,.fa.fa-sellsy,.fa.fa-shirtsinbulk,.fa.fa-simplybuilt,.fa.fa-skyatlas{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-diamond{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-diamond:before{content:"\f3a5"}.fa.fa-intersex:before{content:"\f224"}.fa.fa-facebook-official{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-facebook-official:before{content:"\f09a"}.fa.fa-pinterest-p,.fa.fa-whatsapp{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-hotel:before{content:"\f236"}.fa.fa-medium,.fa.fa-viacoin,.fa.fa-y-combinator,.fa.fa-yc{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-yc:before{content:"\f23b"}.fa.fa-expeditedssl,.fa.fa-opencart,.fa.fa-optin-monster{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-battery-4:before,.fa.fa-battery:before{content:"\f240"}.fa.fa-battery-3:before{content:"\f241"}.fa.fa-battery-2:before{content:"\f242"}.fa.fa-battery-1:before{content:"\f243"}.fa.fa-battery-0:before{content:"\f244"}.fa.fa-object-group,.fa.fa-object-ungroup,.fa.fa-sticky-note-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-sticky-note-o:before{content:"\f249"}.fa.fa-cc-diners-club,.fa.fa-cc-jcb{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-clone,.fa.fa-hourglass-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hourglass-o:before{content:"\f254"}.fa.fa-hourglass-1:before{content:"\f251"}.fa.fa-hourglass-2:before{content:"\f252"}.fa.fa-hourglass-3:before{content:"\f253"}.fa.fa-hand-rock-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-rock-o:before{content:"\f255"}.fa.fa-hand-grab-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-grab-o:before{content:"\f255"}.fa.fa-hand-paper-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-paper-o:before{content:"\f256"}.fa.fa-hand-stop-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-stop-o:before{content:"\f256"}.fa.fa-hand-scissors-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-scissors-o:before{content:"\f257"}.fa.fa-hand-lizard-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-lizard-o:before{content:"\f258"}.fa.fa-hand-spock-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-spock-o:before{content:"\f259"}.fa.fa-hand-pointer-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-pointer-o:before{content:"\f25a"}.fa.fa-hand-peace-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-hand-peace-o:before{content:"\f25b"}.fa.fa-registered{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-chrome,.fa.fa-creative-commons,.fa.fa-firefox,.fa.fa-get-pocket,.fa.fa-gg,.fa.fa-gg-circle,.fa.fa-internet-explorer,.fa.fa-odnoklassniki,.fa.fa-odnoklassniki-square,.fa.fa-opera,.fa.fa-safari,.fa.fa-tripadvisor,.fa.fa-wikipedia-w{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-television:before{content:"\f26c"}.fa.fa-500px,.fa.fa-amazon,.fa.fa-contao{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-calendar-plus-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-calendar-plus-o:before{content:"\f271"}.fa.fa-calendar-minus-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-calendar-minus-o:before{content:"\f272"}.fa.fa-calendar-times-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-calendar-times-o:before{content:"\f273"}.fa.fa-calendar-check-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-calendar-check-o:before{content:"\f274"}.fa.fa-map-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-map-o:before{content:"\f279"}.fa.fa-commenting:before{content:"\f4ad"}.fa.fa-commenting-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-commenting-o:before{content:"\f4ad"}.fa.fa-houzz,.fa.fa-vimeo{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-vimeo:before{content:"\f27d"}.fa.fa-black-tie,.fa.fa-edge,.fa.fa-fonticons,.fa.fa-reddit-alien{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-credit-card-alt:before{content:"\f09d"}.fa.fa-codiepie,.fa.fa-fort-awesome,.fa.fa-mixcloud,.fa.fa-modx,.fa.fa-product-hunt,.fa.fa-scribd,.fa.fa-usb{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-pause-circle-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-pause-circle-o:before{content:"\f28b"}.fa.fa-stop-circle-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-stop-circle-o:before{content:"\f28d"}.fa.fa-bluetooth,.fa.fa-bluetooth-b,.fa.fa-envira,.fa.fa-gitlab,.fa.fa-wheelchair-alt,.fa.fa-wpbeginner,.fa.fa-wpforms{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-wheelchair-alt:before{content:"\f368"}.fa.fa-question-circle-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-question-circle-o:before{content:"\f059"}.fa.fa-volume-control-phone:before{content:"\f2a0"}.fa.fa-asl-interpreting:before{content:"\f2a3"}.fa.fa-deafness:before,.fa.fa-hard-of-hearing:before{content:"\f2a4"}.fa.fa-glide,.fa.fa-glide-g{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-signing:before{content:"\f2a7"}.fa.fa-first-order,.fa.fa-google-plus-official,.fa.fa-pied-piper,.fa.fa-snapchat,.fa.fa-snapchat-ghost,.fa.fa-snapchat-square,.fa.fa-themeisle,.fa.fa-viadeo,.fa.fa-viadeo-square,.fa.fa-yoast{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-google-plus-official:before{content:"\f2b3"}.fa.fa-google-plus-circle{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-google-plus-circle:before{content:"\f2b3"}.fa.fa-fa,.fa.fa-font-awesome{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-fa:before{content:"\f2b4"}.fa.fa-handshake-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-handshake-o:before{content:"\f2b5"}.fa.fa-envelope-open-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-envelope-open-o:before{content:"\f2b6"}.fa.fa-linode{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-address-book-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-address-book-o:before{content:"\f2b9"}.fa.fa-vcard:before{content:"\f2bb"}.fa.fa-address-card-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-address-card-o:before{content:"\f2bb"}.fa.fa-vcard-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-vcard-o:before{content:"\f2bb"}.fa.fa-user-circle-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-user-circle-o:before{content:"\f2bd"}.fa.fa-user-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-user-o:before{content:"\f007"}.fa.fa-id-badge{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-drivers-license:before{content:"\f2c2"}.fa.fa-id-card-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-id-card-o:before{content:"\f2c2"}.fa.fa-drivers-license-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-drivers-license-o:before{content:"\f2c2"}.fa.fa-free-code-camp,.fa.fa-quora,.fa.fa-telegram{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-thermometer-4:before,.fa.fa-thermometer:before{content:"\f2c7"}.fa.fa-thermometer-3:before{content:"\f2c8"}.fa.fa-thermometer-2:before{content:"\f2c9"}.fa.fa-thermometer-1:before{content:"\f2ca"}.fa.fa-thermometer-0:before{content:"\f2cb"}.fa.fa-bathtub:before,.fa.fa-s15:before{content:"\f2cd"}.fa.fa-window-maximize,.fa.fa-window-restore{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-times-rectangle:before{content:"\f410"}.fa.fa-window-close-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-window-close-o:before{content:"\f410"}.fa.fa-times-rectangle-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-times-rectangle-o:before{content:"\f410"}.fa.fa-bandcamp,.fa.fa-eercast,.fa.fa-etsy,.fa.fa-grav,.fa.fa-imdb,.fa.fa-ravelry{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-eercast:before{content:"\f2da"}.fa.fa-snowflake-o{font-family:"Font Awesome 5 Free";font-weight:400}.fa.fa-snowflake-o:before{content:"\f2dc"}.fa.fa-superpowers,.fa.fa-wpexplorer{font-family:"Font Awesome 5 Brands";font-weight:400}.fa.fa-cab:before{content:"\f1ba"} \ No newline at end of file diff --git a/html/font-awesome/webfonts/fa-regular-400.eot b/html/font-awesome/webfonts/fa-regular-400.eot index d62be2fad8..479b32cecc 100644 Binary files a/html/font-awesome/webfonts/fa-regular-400.eot and b/html/font-awesome/webfonts/fa-regular-400.eot differ diff --git a/html/font-awesome/webfonts/fa-regular-400.woff b/html/font-awesome/webfonts/fa-regular-400.woff index 43b1a9ae49..c390c60e2b 100644 Binary files a/html/font-awesome/webfonts/fa-regular-400.woff and b/html/font-awesome/webfonts/fa-regular-400.woff differ diff --git a/html/font-awesome/webfonts/fa-solid-900.eot b/html/font-awesome/webfonts/fa-solid-900.eot index c77baa8d46..52883b93c8 100644 Binary files a/html/font-awesome/webfonts/fa-solid-900.eot and b/html/font-awesome/webfonts/fa-solid-900.eot differ diff --git a/html/font-awesome/webfonts/fa-solid-900.woff b/html/font-awesome/webfonts/fa-solid-900.woff index 77c1786227..aff125d658 100644 Binary files a/html/font-awesome/webfonts/fa-solid-900.woff and b/html/font-awesome/webfonts/fa-solid-900.woff differ diff --git a/nano/templates/adv_med.tmpl b/nano/templates/adv_med.tmpl deleted file mode 100644 index 6309f73285..0000000000 --- a/nano/templates/adv_med.tmpl +++ /dev/null @@ -1,267 +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:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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
-
- {{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}}{{:value.overdose}}
- {{else}} -
No blood reagents detected.
- {{/if}} -

Stomach Reagents

- {{if data.occupant.ingested}} - - {{for data.occupant.ingested}} - - - - - - {{/for}} -
{{:value.name}}:{{:value.amount}}{{:value.overdose}}
- {{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/appearance_changer.tmpl b/nano/templates/appearance_changer.tmpl deleted file mode 100644 index 1111add5c5..0000000000 --- a/nano/templates/appearance_changer.tmpl +++ /dev/null @@ -1,86 +0,0 @@ -{{if data.change_race}} -
-
- Species: -
-
- {{for data.species}} - {{:helper.link(value.specimen, null, { 'race' : value.specimen}, null, data.specimen == value.specimen ? 'selected' : null)}} - {{/for}} -
-
-{{/if}} - -{{if data.change_gender}} -
-
- Biological Gender: -
-
- {{for data.genders}} - {{:helper.link(value.gender_name, null, { 'gender' : value.gender_key}, null, data.gender == value.gender_key ? 'selected' : null)}} - {{/for}} -
- -
-
- Gender Identity: -
-
- {{for data.id_genders}} - {{:helper.link(value.gender_name, null, { 'gender_id' : value.gender_key}, null, data.gender_id == value.gender_key ? 'selected' : null)}} - {{/for}} -
-
-{{/if}} - -{{if data.change_eye_color || data.change_skin_tone || data.change_skin_color || data.change_hair_color || data.change_facial_hair_color}} -
-
- Colors: -
-
- {{if data.change_eye_color}} - {{:helper.link('Change eye color', null, { 'eye_color' : 1})}} - {{/if}} - {{if data.change_skin_tone}} - {{:helper.link('Change skin tone', null, { 'skin_tone' : 1})}} - {{/if}} - {{if data.change_skin_color}} - {{:helper.link('Change skin color', null, { 'skin_color' : 1})}} - {{/if}} - {{if data.change_hair_color}} - {{:helper.link('Change hair color', null, { 'hair_color' : 1})}} - {{/if}} - {{if data.change_facial_hair_color}} - {{:helper.link('Change facial hair color', null, { 'facial_hair_color' : 1})}} - {{/if}} -
-
-{{/if}} - -{{if data.change_hair}} -
-
- Hair styles: -
-
- {{for data.hair_styles}} - {{:helper.link(value.hairstyle, null, { 'hair' : value.hairstyle}, null, data.hair_style == value.hairstyle ? 'selected' : null)}} - {{/for}} -
-
-{{/if}} - -{{if data.change_facial_hair}} -
-
- Facial hair styles: -
-
- {{for data.facial_hair_styles}} - {{:helper.link(value.facialhairstyle, null, { 'facial_hair' : value.facialhairstyle}, null, data.facial_hair_style == value.facialhairstyle ? 'selected' : null)}} - {{/for}} -
-
-{{/if}} diff --git a/nano/templates/chem_disp.tmpl b/nano/templates/chem_disp.tmpl deleted file mode 100644 index eaacb6a1dd..0000000000 --- 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 f1b981ba6e..0000000000 --- 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 a927fcc4d9..0000000000 --- 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 e8302b07df..0000000000 --- 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/disease_splicer.tmpl b/nano/templates/disease_splicer.tmpl deleted file mode 100644 index d2e874afe6..0000000000 --- a/nano/templates/disease_splicer.tmpl +++ /dev/null @@ -1,125 +0,0 @@ -
-
- {{:helper.link('Close', 'gear', {'close' : 1}, null, 'fixedLeft')}} -
-
- -{{if data.busy}} -
The Splicer is currently busy.
-
-
{{:data.busy}}
-
-

- Thank you for your patience! -

-{{else}} -
-

Virus Dish

-
-
- {{:helper.link('Eject Dish', 'eject', { 'eject' : 1 }, data.dish_inserted ? null : 'disabled')}} -
- -
-
- Growth Density: -
-
- {{:helper.displayBar(data.growth, 0, 100, (data.growth >= 50) ? 'good' : data.growth >= 25 ? 'average' : 'bad', data.growth + '%' )}} -
-
- -
-
- {{if !data.info}} -
- Symptoms: -
- {{/if}} -
- {{if data.info}} - {{:data.info}} - {{else}} - {{for data.effects}} -
-
- ({{:value.stage}}) {{:value.name}} - {{if value.badness > 1}} - Dangerous - {{/if}} -
-
- {{/for}} - {{/if}} -
-
- {{if data.affected_species && !data.info}} -
-
- Affected Species: -
-
- {{:data.affected_species}} -
-
- {{/if}} -
- {{if data.effects}} -
- CAUTION: Reverse engineering will destroy the viral sample. -
-
-
- Reverse Engineering: -
-
- {{for data.effects}} - {{:helper.link(value.stage, 'transferthick-e-w', { 'grab' : value.reference })}} - {{/for}} -
-
- {{:helper.link('Species', 'transferthick-e-w', { 'affected_species' : 1 })}} -
-
- {{/if}} - -
-

Storage

-
- -
-
- Memory Buffer: -
-
- {{if data.buffer}} - {{:data.buffer.name}} ({{:data.buffer.stage}}) - {{else}} - {{if data.species_buffer}} - {{:data.species_buffer}} - {{else}} - Empty - {{/if}} - {{/if}} -
-
- {{:helper.link('Save To Disk', 'disk', { 'disk' : 1 }, (data.buffer || data.species_buffer) ? null : 'disabled')}} - {{if data.species_buffer}} - {{:helper.link('Splice Species', 'pencil', { 'splice' : 5 }, (data.species_buffer && !data.info) ? null : 'disabled')}} - {{else data.buffer}} - {{:helper.link('Splice #1', 'pencil', { 'splice' : 1 }, data.buffer.stage > 1 ? 'disabled' : null)}} - {{:helper.link('Splice #2', 'pencil', { 'splice' : 2 }, data.buffer.stage > 2 ? 'disabled' : null)}} - {{:helper.link('Splice #3', 'pencil', { 'splice' : 3 }, data.buffer.stage > 3 ? 'disabled' : null)}} - {{:helper.link('Splice #4', 'pencil', { 'splice' : 4 }, data.buffer.stage > 4 ? 'disabled' : null)}} - {{/if}} -{{/if}} - - - - - - - - - - diff --git a/nano/templates/dish_incubator.tmpl b/nano/templates/dish_incubator.tmpl deleted file mode 100644 index ae887e6986..0000000000 --- a/nano/templates/dish_incubator.tmpl +++ /dev/null @@ -1,123 +0,0 @@ -
-
- {{:helper.link('Close', 'gear', {'close' : '1'}, null, 'fixedLeft')}} -
-
- -
-

Environmental Conditions

-
-
-
- Power: -
-
- {{:helper.link('On', 'power', { 'power' : 1 }, !data.dish_inserted ? 'disabled' : data.on ? 'selected' : null)}}{{:helper.link('Off', 'close', { 'power' : 1 }, data.on ? null : 'selected')}} -
-
-
- {{:helper.link('Add Radiation', 'radiation', {'rad' : 1})}} - {{:helper.link('Flush System', 'trash', {'flush' : 1}, data.system_in_use ? null : 'disabled')}} -
- -
-
-
- Virus Food: -
-
- {{:helper.displayBar(data.food_supply, 0, 100, 'good', data.food_supply)}} -
-
-
-
- Radiation Level: -
-
- {{:helper.displayBar(data.radiation, 0, 100, (data.radiation >= 50) ? 'bad' : (data.growth >= 25) ? 'average' : 'good')}} -
- {{:helper.formatNumber(data.radiation * 10000)}} µSv -
-
-
-
- Toxicity: -
-
- {{:helper.displayBar(data.toxins, 0, 100, (data.toxins >= 50) ? 'bad' : (data.toxins >= 25) ? 'average' : 'good', data.toxins + '%')}} -
-
-
- -
-

Chemicals

-
-
- {{:helper.link('Eject Chemicals', 'eject', { 'ejectchem' : 1 }, data.chemicals_inserted ? null : 'disabled')}} - {{:helper.link('Breed Virus', 'circle-arrow-s', { 'virus' : 1 }, data.can_breed_virus ? null : 'disabled')}} -
- -{{if data.chemicals_inserted}} -
-
- Volume: -
-
- {{:helper.displayBar(data.chemical_volume, 0, data.max_chemical_volume, 'good', data.chemical_volume + ' / ' + data.max_chemical_volume)}} -
-
-
-
- Breeding Environment: -
-
- - {{:!data.dish_inserted ? 'N/A' : data.can_breed_virus ? 'Suitable' : 'No hemolytic samples detected'}} - - {{if data.blood_already_infected}} -
- CAUTION: Viral infection detected in blood sample. - {{/if}} -
-
-{{else}} -
- No chemicals inserted. -
-{{/if}} - -
-

Virus Dish

-
-
- {{:helper.link('Eject Dish', 'eject', {'ejectdish' : 1}, data.dish_inserted ? null : 'disabled')}} -
- -{{if data.dish_inserted}} - {{if data.virus}} -
-
- Growth Density: -
-
- {{:helper.displayBar(data.growth, 0, 100, (data.growth >= 50) ? 'good' : (data.growth >= 25) ? 'average' : 'bad', data.growth + '%' )}} -
-
-
-
- Infection Rate: -
-
- {{:data.analysed ? data.infection_rate : "Unknown"}} -
-
- {{else}} -
- No virus detected. -
- {{/if}} -{{else}} -
- No dish loaded. -
-{{/if}} diff --git a/nano/templates/dna_modifier.tmpl b/nano/templates/dna_modifier.tmpl deleted file mode 100644 index 9a2804ba98..0000000000 --- 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/isolation_centrifuge.tmpl b/nano/templates/isolation_centrifuge.tmpl deleted file mode 100644 index 4ff0272df0..0000000000 --- a/nano/templates/isolation_centrifuge.tmpl +++ /dev/null @@ -1,81 +0,0 @@ -
- {{:helper.link('Close', 'gear', {'close' : 1}, null, 'fixedLeft')}} - {{:helper.link('Print', 'print', { 'print' : 1 }, data.antibodies || data.pathogens ? null : 'disabled', 'fixedLeft')}} -
- -{{if data.busy}} -
The Centrifuge is currently busy.
-
-
{{:data.busy}}
-
-

- Thank you for your patience! -

-{{else}} -
-

{{:data.is_antibody_sample ? 'Antibody Sample' : 'Blood Sample'}}

-
-
- {{:helper.link('Eject Vial', 'eject', { 'action' : 'sample' }, data.sample_inserted ? null : 'disabled')}} -
- {{if data.sample_inserted}} - {{if data.antibodies || data.pathogens}} -
- {{if data.antibodies}} -
-
- Antibodies: -
-
- {{:data.antibodies}} -
-
- {{/if}} - {{if data.pathogens}} -
-
- Pathogens: -
-
- {{for data.pathogens}} -
- {{:value.name}} ({{:value.spread_type}}) -
- {{/for}} -
-
- {{/if}} -
- {{else}} -
- No antibodies or viral strains detected. -
- {{/if}} - {{else}} -
- No vial detected. -
- {{/if}} - {{if data.antibodies && !data.is_antibody_sample}} -
-
- Isolate Antibodies: -
-
- {{:helper.link(data.antibodies, 'pencil', { 'action' : 'antibody' })}} -
-
- {{/if}} - {{if data.pathogens}} -
-
- Isolate Strain: -
-
- {{for data.pathogens}} - {{:helper.link(value.name, 'pencil', { 'isolate' : value.reference })}} - {{/for}} -
-
- {{/if}} -{{/if}} diff --git a/nano/templates/operating.tmpl b/nano/templates/operating.tmpl deleted file mode 100644 index c53bd94c65..0000000000 --- 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/pathogenic_isolator.tmpl b/nano/templates/pathogenic_isolator.tmpl deleted file mode 100644 index 69411d858f..0000000000 --- a/nano/templates/pathogenic_isolator.tmpl +++ /dev/null @@ -1,107 +0,0 @@ -
-

Menu

-
-
- {{if !data.isolating}} - {{:helper.link('Home', 'home', {'home' : 1}, data.state == 'home' ? 'disabled' : null, 'fixedLeft')}} - {{:helper.link('List', 'note', {'list' : 1}, data.state == 'list' ? 'disabled' : null, 'fixedLeft')}} - {{:helper.link('Pathogen', 'folder-open', {'entry' : 1}, data.state == 'entry' ? 'disabled' : null, 'fixedLeft')}} - {{/if}} -
- {{:helper.link('Close', 'gear', {'close' : 1}, null, 'fixedLeft')}} - {{:helper.link('Print', 'print', { 'print' : 1 }, data.can_print ? null : 'disabled', 'fixedLeft')}} -
- -{{if data.isolating}} -
The Isolator is currently busy.
-
-
Isolating pathogens...
-
-

- Thank you for your patience! -

-{{else}} - {{if data.state =="home"}} -
-

Blood Sample

-
-
- {{:helper.link('Eject Syringe', 'eject', { 'eject' : 1 }, data.syringe_inserted ? null : 'disabled')}} -
- - {{if data.syringe_inserted}} -
- Pathogens: - {{if data.pathogen_pool}} - {{for data.pathogen_pool}} -
- {{:index + 1}}. #{{:value.unique_id}} {{:value.is_in_database ? "(Analysed)" : ""}}
- {{:value.name}}: {{:value.dna}} -
- {{/for}} - {{else}} - No pathogens detected. - {{/if}} -
- {{else}} -
- No syringe loaded. -
- {{/if}} - {{if data.pathogen_pool}} -
-
- Isolate Pathogens: -
-
- {{for data.pathogen_pool}} - {{:helper.link('#' + value.unique_id, 'pencil', { 'isolate' : value.reference }, null, 'fixedLeft')}} - {{/for}} -
-
-
-
- Database Lookup: -
-
- {{for data.pathogen_pool}} - {{if value.is_in_database}} - {{:helper.link('#' + value.unique_id, 'info', { 'entry' : 1, 'view' : value.record }, null, 'fixedLeft')}} - {{/if}} - {{/for}} -
-
- {{/if}} - {{else}} - {{if data.state == "list"}} -
-

View Database

-
-
- {{if data.database}} - {{for data.database}} -
-
{{:value.name}}
- {{:helper.link('Details', 'circle-arrow-s', { 'entry' : 1, 'view' : value.record }, null, 'fixedLeft')}} -
- {{/for}} - {{else}} - The viral database is empty. - {{/if}} -
- {{else}} - {{if data.state == "entry"}} - {{if data.entry}} -
-

{{:data.entry.name}}

-
-
- {{:data.entry.description}} -
- {{else}} - No virus selected. - {{/if}} - {{/if}} - {{/if}} - {{/if}} -{{/if}} diff --git a/nano/templates/sleeper.tmpl b/nano/templates/sleeper.tmpl deleted file mode 100644 index 885a1c025c..0000000000 --- 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/smartfridge.tmpl b/nano/templates/smartfridge.tmpl deleted file mode 100644 index cc25d760ce..0000000000 --- a/nano/templates/smartfridge.tmpl +++ /dev/null @@ -1,36 +0,0 @@ -
- {{:helper.link('Close', 'gear', {'close' : 1}, null, 'fixedLeft')}} -
- -
-

Storage

- {{if data.secure}} - - {{:data.locked == -1 ? "Sec.re ACC_** //):securi_nt.diag=>##'or 1=1'%($..." : "Secure Access: Please have your identification ready."}} - - {{/if}} -
-
- {{if data.contents}} - {{for data.contents}} -
- {{:value.display_name}} ({{:value.quantity}} available) -
Vend: 
{{:helper.link('x1', 'circle-arrow-s', { "vend" : value.vend, "amount" : 1 }, null, 'statusValue')}} - {{if value.quantity >= 5}} - {{:helper.link('x5', 'circle-arrow-s', { "vend" : value.vend, "amount" : 5 }, null, 'statusValue')}} - {{/if}} - {{if value.quantity >= 10}} - {{:helper.link('x10', 'circle-arrow-s', { "vend" : value.vend, "amount" : 10 }, null, 'statusValue')}} - {{/if}} - {{if value.quantity >= 25}} - {{:helper.link('x25', 'circle-arrow-s', { "vend" : value.vend, "amount" : 25 }, null, 'statusValue')}} - {{/if}} - {{if value.quantity > 1}} - {{:helper.link('All', 'circle-arrow-s', { "vend" : value.vend, "amount" : value.quantity }, null, 'statusValue')}} - {{/if}} -
- {{/for}} - {{else}} - No products loaded. - {{/if}} -
diff --git a/polaris.dme b/polaris.dme index 1424445368..ca2cac4f3c 100644 --- a/polaris.dme +++ b/polaris.dme @@ -2506,7 +2506,6 @@ #include "code\modules\nano\interaction\remote.dm" #include "code\modules\nano\interaction\self.dm" #include "code\modules\nano\interaction\zlevel.dm" -#include "code\modules\nano\modules\human_appearance.dm" #include "code\modules\nano\modules\law_manager.dm" #include "code\modules\nano\modules\nano_module.dm" #include "code\modules\organs\blood.dm" @@ -2936,10 +2935,12 @@ #include "code\modules\tables\update_triggers.dm" #include "code\modules\tension\tension.dm" #include "code\modules\tgui\external.dm" +#include "code\modules\tgui\modal.dm" #include "code\modules\tgui\states.dm" #include "code\modules\tgui\tgui.dm" #include "code\modules\tgui\tgui_window.dm" #include "code\modules\tgui\modules\_base.dm" +#include "code\modules\tgui\modules\appearance_changer.dm" #include "code\modules\tgui\modules\camera.dm" #include "code\modules\tgui\modules\crew_monitor.dm" #include "code\modules\tgui\states\admin.dm"