mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-23 21:18:37 +01:00
[MIRROR] Refactors pandemic machine [MDB IGNORE] (#15363)
* Refactors pandemic machine * Update text.dm Co-authored-by: Jeremiah <42397676+jlsnow301@users.noreply.github.com> Co-authored-by: Gandalf <9026500+Gandalf2k15@users.noreply.github.com>
This commit is contained in:
co-authored by
Jeremiah
Gandalf
parent
136f1fa910
commit
bab710c04c
@@ -22,15 +22,15 @@
|
||||
|
||||
|
||||
///returns nothing with an alert instead of the message if it contains something in the ic filter, and sanitizes normally if the name is fine. It returns nothing so it backs out of the input the same way as if you had entered nothing.
|
||||
/proc/sanitize_name(t,allow_numbers=FALSE)
|
||||
if(is_ic_filtered(t) || is_soft_ic_filtered(t))
|
||||
/proc/sanitize_name(target, allow_numbers = FALSE, cap_after_symbols = TRUE)
|
||||
if(is_ic_filtered(target) || is_soft_ic_filtered(target))
|
||||
tgui_alert(usr, "You cannot set a name that contains a word prohibited in IC chat!")
|
||||
return ""
|
||||
var/r = reject_bad_name(t,allow_numbers=allow_numbers,strict=TRUE)
|
||||
if(!r)
|
||||
var/result = reject_bad_name(target, allow_numbers = allow_numbers, strict = TRUE, cap_after_symbols = cap_after_symbols)
|
||||
if(!result)
|
||||
tgui_alert(usr, "Invalid name.")
|
||||
return ""
|
||||
return sanitize(r)
|
||||
return sanitize(result)
|
||||
|
||||
|
||||
/// Runs byond's html encoding sanitization proc, after replacing new-lines and tabs for the # character.
|
||||
@@ -150,9 +150,9 @@
|
||||
*
|
||||
* * strict - return null immidiately instead of filtering out
|
||||
* * allow_numbers - allows numbers and common special characters - used for silicon/other weird things names
|
||||
* * cap_after_symbols - words like Bob's will be capitalized to Bob'S by default. False is good for titles.
|
||||
*/
|
||||
///proc/reject_bad_name(t_in, allow_numbers = FALSE, max_length = MAX_NAME_LEN, ascii_only = TRUE, strict = FALSE) //ORIGINAL
|
||||
/proc/reject_bad_name(t_in, allow_numbers = TRUE, max_length = MAX_NAME_LEN, ascii_only = TRUE, strict = FALSE) //SKYRAT EDIT CHANGE
|
||||
/proc/reject_bad_name(t_in, allow_numbers = TRUE, max_length = MAX_NAME_LEN, ascii_only = TRUE, strict = FALSE, cap_after_symbols = TRUE) // SKYRAT EDIT CHANGE
|
||||
if(!t_in)
|
||||
return //Rejects the input if it is null
|
||||
|
||||
@@ -179,7 +179,7 @@
|
||||
|
||||
// a .. z
|
||||
if(97 to 122) //Lowercase Letters
|
||||
if(last_char_group == NO_CHARS_DETECTED || last_char_group == SPACES_DETECTED || last_char_group == SYMBOLS_DETECTED) //start of a word
|
||||
if(last_char_group == NO_CHARS_DETECTED || last_char_group == SPACES_DETECTED || cap_after_symbols && last_char_group == SYMBOLS_DETECTED) //start of a word
|
||||
char = uppertext(char)
|
||||
number_of_alphanumeric++
|
||||
last_char_group = LETTERS_DETECTED
|
||||
@@ -192,7 +192,6 @@
|
||||
continue
|
||||
number_of_alphanumeric++
|
||||
last_char_group = NUMBERS_DETECTED
|
||||
|
||||
// ' - .
|
||||
if(39,45,46) //Common name punctuation
|
||||
if(last_char_group == NO_CHARS_DETECTED)
|
||||
|
||||
@@ -86,3 +86,22 @@
|
||||
///Overload for running after processing.
|
||||
/datum/symptom/proc/OnRemove(datum/disease/advance/A)
|
||||
return
|
||||
|
||||
/**
|
||||
* Returns a list for all of the traits of this symptom.
|
||||
*
|
||||
*
|
||||
* @returns {list} symptom - The desired symptoms as a list.
|
||||
*/
|
||||
/datum/symptom/proc/get_symptom_data()
|
||||
var/list/data = list()
|
||||
data["name"] = name
|
||||
data["desc"] = desc
|
||||
data["stealth"] = stealth
|
||||
data["resistance"] = resistance
|
||||
data["stage_speed"] = stage_speed
|
||||
data["transmission"] = transmittable
|
||||
data["level"] = level
|
||||
data["neutered"] = neutered
|
||||
data["threshold_desc"] = threshold_descs
|
||||
return data
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#define MAIN_SCREEN 1
|
||||
#define SYMPTOM_DETAILS 2
|
||||
|
||||
/obj/machinery/computer/pandemic
|
||||
name = "PanD.E.M.I.C 2200"
|
||||
@@ -12,8 +10,11 @@
|
||||
resistance_flags = ACID_PROOF
|
||||
circuit = /obj/item/circuitboard/computer/pandemic
|
||||
|
||||
/// Whether the pandemic is ready to make another culture/vaccine
|
||||
var/wait
|
||||
/// The currently selected symptom
|
||||
var/datum/symptom/selected_symptom
|
||||
/// The inserted beaker
|
||||
var/obj/item/reagent_containers/beaker
|
||||
|
||||
/obj/machinery/computer/pandemic/Initialize(mapload)
|
||||
@@ -56,85 +57,25 @@
|
||||
update_appearance()
|
||||
return ..()
|
||||
|
||||
/obj/machinery/computer/pandemic/proc/get_by_index(thing, index)
|
||||
if(!beaker || !beaker.reagents)
|
||||
return
|
||||
var/datum/reagent/blood/B = locate() in beaker.reagents.reagent_list
|
||||
if(B?.data[thing])
|
||||
return B.data[thing][index]
|
||||
|
||||
/obj/machinery/computer/pandemic/proc/get_virus_id_by_index(index)
|
||||
var/datum/disease/D = get_by_index("viruses", index)
|
||||
if(D)
|
||||
return D.GetDiseaseID()
|
||||
|
||||
/obj/machinery/computer/pandemic/proc/get_viruses_data(datum/reagent/blood/B)
|
||||
. = list()
|
||||
var/list/V = B.get_diseases()
|
||||
var/index = 1
|
||||
for(var/virus in V)
|
||||
var/datum/disease/D = virus
|
||||
if(!istype(D) || D.visibility_flags & HIDDEN_PANDEMIC)
|
||||
continue
|
||||
|
||||
var/list/this = list()
|
||||
this["name"] = D.name
|
||||
if(istype(D, /datum/disease/advance))
|
||||
var/datum/disease/advance/A = D
|
||||
var/disease_name = SSdisease.get_disease_name(A.GetDiseaseID())
|
||||
this["can_rename"] = ((disease_name == "Unknown") && A.mutable)
|
||||
this["name"] = disease_name
|
||||
this["is_adv"] = TRUE
|
||||
this["symptoms"] = list()
|
||||
for(var/symptom in A.symptoms)
|
||||
var/datum/symptom/S = symptom
|
||||
var/list/this_symptom = list()
|
||||
this_symptom = get_symptom_data(S)
|
||||
this["symptoms"] += list(this_symptom)
|
||||
this["resistance"] = A.totalResistance()
|
||||
this["stealth"] = A.totalStealth()
|
||||
this["stage_speed"] = A.totalStageSpeed()
|
||||
this["transmission"] = A.totalTransmittable()
|
||||
this["index"] = index++
|
||||
this["agent"] = D.agent
|
||||
this["description"] = D.desc || "none"
|
||||
this["spread"] = D.spread_text || "none"
|
||||
this["cure"] = D.cure_text || "none"
|
||||
|
||||
. += list(this)
|
||||
|
||||
/obj/machinery/computer/pandemic/proc/get_symptom_data(datum/symptom/S)
|
||||
. = list()
|
||||
var/list/this = list()
|
||||
this["name"] = S.name
|
||||
this["desc"] = S.desc
|
||||
this["stealth"] = S.stealth
|
||||
this["resistance"] = S.resistance
|
||||
this["stage_speed"] = S.stage_speed
|
||||
this["transmission"] = S.transmittable
|
||||
this["level"] = S.level
|
||||
this["neutered"] = S.neutered
|
||||
this["threshold_desc"] = S.threshold_descs
|
||||
. += this
|
||||
|
||||
/obj/machinery/computer/pandemic/proc/get_resistance_data(datum/reagent/blood/B)
|
||||
. = list()
|
||||
if(!islist(B.data["resistances"]))
|
||||
return
|
||||
var/list/resistances = B.data["resistances"]
|
||||
for(var/id in resistances)
|
||||
var/list/this = list()
|
||||
var/datum/disease/D = SSdisease.archive_diseases[id]
|
||||
if(D)
|
||||
this["id"] = id
|
||||
this["name"] = D.name
|
||||
|
||||
. += list(this)
|
||||
|
||||
/obj/machinery/computer/pandemic/proc/reset_replicator_cooldown()
|
||||
wait = FALSE
|
||||
/obj/machinery/computer/pandemic/attackby(obj/item/held_item, mob/user, params)
|
||||
if(!istype(held_item, /obj/item/reagent_containers) || held_item.item_flags & ABSTRACT || !held_item.is_open_container())
|
||||
return ..()
|
||||
. = TRUE //no afterattack
|
||||
if(machine_stat & (NOPOWER|BROKEN))
|
||||
return ..()
|
||||
if(beaker)
|
||||
balloon_alert(user, "pandemic full")
|
||||
return ..()
|
||||
if(!user.transferItemToLoc(held_item, src))
|
||||
return ..()
|
||||
beaker = held_item
|
||||
balloon_alert(user, "beaker loaded")
|
||||
update_appearance()
|
||||
playsound(src, 'sound/machines/ping.ogg', 30, TRUE)
|
||||
SStgui.update_uis(src)
|
||||
|
||||
/obj/machinery/computer/pandemic/on_deconstruction()
|
||||
eject_beaker()
|
||||
. = ..()
|
||||
|
||||
/obj/machinery/computer/pandemic/update_icon_state()
|
||||
icon_state = "[base_icon_state][beaker ? 1 : 0][(machine_stat & BROKEN) ? "_b" : (powered() ? null : "_nopower")]"
|
||||
@@ -145,42 +86,36 @@
|
||||
if(wait)
|
||||
. += "waitlight"
|
||||
|
||||
/obj/machinery/computer/pandemic/proc/eject_beaker()
|
||||
if(beaker)
|
||||
try_put_in_hand(beaker, usr)
|
||||
beaker = null
|
||||
update_appearance()
|
||||
|
||||
/obj/machinery/computer/pandemic/ui_interact(mob/user, datum/tgui/ui)
|
||||
. = ..()
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "Pandemic", name)
|
||||
ui.open()
|
||||
ui.set_autoupdate(FALSE)
|
||||
|
||||
/obj/machinery/computer/pandemic/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
data["is_ready"] = !wait
|
||||
if(beaker)
|
||||
data["has_beaker"] = TRUE
|
||||
data["beaker"] = list(
|
||||
"volume" = round(beaker.reagents?.total_volume, 0.01) || 0,
|
||||
"capacity" = beaker.volume,
|
||||
)
|
||||
var/datum/reagent/blood/B = locate() in beaker.reagents.reagent_list
|
||||
if(B)
|
||||
data["has_blood"] = TRUE
|
||||
data["blood"] = list()
|
||||
data["blood"]["dna"] = B.data["blood_DNA"] || "none"
|
||||
data["blood"]["type"] = B.data["blood_type"] || "none"
|
||||
data["viruses"] = get_viruses_data(B)
|
||||
data["resistances"] = get_resistance_data(B)
|
||||
else
|
||||
data["has_blood"] = FALSE
|
||||
else
|
||||
if(!beaker)
|
||||
data["has_beaker"] = FALSE
|
||||
data["has_blood"] = FALSE
|
||||
|
||||
return data
|
||||
data["has_beaker"] = TRUE
|
||||
data["beaker"] = list(
|
||||
"volume" = round(beaker.reagents?.total_volume, 0.01) || 0,
|
||||
"capacity" = beaker.volume,
|
||||
)
|
||||
var/datum/reagent/blood/blood = locate() in beaker.reagents.reagent_list
|
||||
if(!blood)
|
||||
data["has_blood"] = FALSE
|
||||
return data
|
||||
data["has_blood"] = TRUE
|
||||
data["blood"] = list()
|
||||
data["blood"]["dna"] = blood.data["blood_DNA"] || "none"
|
||||
data["blood"]["type"] = blood.data["blood_type"] || "none"
|
||||
data["viruses"] = get_viruses_data(blood)
|
||||
data["resistances"] = get_resistance_data(blood)
|
||||
return data
|
||||
|
||||
/obj/machinery/computer/pandemic/ui_act(action, params)
|
||||
@@ -190,80 +125,218 @@
|
||||
switch(action)
|
||||
if("eject_beaker")
|
||||
eject_beaker()
|
||||
. = TRUE
|
||||
return TRUE
|
||||
if("empty_beaker")
|
||||
if(beaker)
|
||||
beaker.reagents.clear_reagents()
|
||||
. = TRUE
|
||||
return TRUE
|
||||
if("empty_eject_beaker")
|
||||
if(beaker)
|
||||
beaker.reagents.clear_reagents()
|
||||
eject_beaker()
|
||||
. = TRUE
|
||||
return TRUE
|
||||
if("rename_disease")
|
||||
var/id = get_virus_id_by_index(text2num(params["index"]))
|
||||
var/datum/disease/advance/A = SSdisease.archive_diseases[id]
|
||||
if(!A.mutable)
|
||||
return
|
||||
if(A)
|
||||
var/new_name = sanitize_name(html_encode(params["name"]), allow_numbers = TRUE)
|
||||
if(!new_name || ..())
|
||||
return
|
||||
A.AssignName(new_name)
|
||||
. = TRUE
|
||||
rename_disease(params["index"], params["name"])
|
||||
return TRUE
|
||||
if("create_culture_bottle")
|
||||
if (wait)
|
||||
return
|
||||
var/id = get_virus_id_by_index(text2num(params["index"]))
|
||||
var/datum/disease/advance/A = SSdisease.archive_diseases[id]
|
||||
if(!istype(A) || !A.mutable)
|
||||
to_chat(usr, span_warning("ERROR: Cannot replicate virus strain."))
|
||||
return
|
||||
use_power(active_power_usage)
|
||||
A = A.Copy()
|
||||
var/list/data = list("viruses" = list(A))
|
||||
var/obj/item/reagent_containers/glass/bottle/B = new(drop_location())
|
||||
B.name = "[A.name] culture bottle"
|
||||
B.desc = "A small bottle. Contains [A.agent] culture in synthblood medium."
|
||||
B.reagents.add_reagent(/datum/reagent/blood, 20, data)
|
||||
wait = TRUE
|
||||
update_appearance()
|
||||
var/turf/source_turf = get_turf(src)
|
||||
log_virus("A culture bottle was printed for the virus [A.admin_details()] at [loc_name(source_turf)] by [key_name(usr)]")
|
||||
addtimer(CALLBACK(src, .proc/reset_replicator_cooldown), 50)
|
||||
. = TRUE
|
||||
return FALSE
|
||||
create_culture_bottle(params["index"])
|
||||
return TRUE
|
||||
if("create_vaccine_bottle")
|
||||
if (wait)
|
||||
return
|
||||
use_power(active_power_usage)
|
||||
var/id = params["index"]
|
||||
var/datum/disease/D = SSdisease.archive_diseases[id]
|
||||
var/obj/item/reagent_containers/glass/bottle/B = new(drop_location())
|
||||
B.name = "[D.name] vaccine bottle"
|
||||
B.reagents.add_reagent(/datum/reagent/vaccine, 15, list(id))
|
||||
wait = TRUE
|
||||
update_appearance()
|
||||
addtimer(CALLBACK(src, .proc/reset_replicator_cooldown), 200)
|
||||
. = TRUE
|
||||
return FALSE
|
||||
create_vaccine_bottle(params["index"])
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
/**
|
||||
* Creates a culture bottle (ie: replicates) of the the specified disease.
|
||||
*
|
||||
* @param {number} index - The index of the disease to replicate.
|
||||
*
|
||||
* @returns {boolean} - Success or failure.
|
||||
*/
|
||||
/obj/machinery/computer/pandemic/proc/create_culture_bottle(index)
|
||||
var/id = get_virus_id_by_index(text2num(index))
|
||||
var/datum/disease/advance/adv_disease = SSdisease.archive_diseases[id]
|
||||
if(!istype(adv_disease) || !adv_disease.mutable)
|
||||
to_chat(usr, span_warning("ERROR: Cannot replicate virus strain."))
|
||||
return FALSE
|
||||
use_power(active_power_usage)
|
||||
adv_disease = adv_disease.Copy()
|
||||
var/list/data = list("viruses" = list(adv_disease))
|
||||
var/obj/item/reagent_containers/glass/bottle/bottle = new(drop_location())
|
||||
bottle.name = "[adv_disease.name] culture bottle"
|
||||
bottle.desc = "A small bottle. Contains [adv_disease.agent] culture in synthblood medium."
|
||||
bottle.reagents.add_reagent(/datum/reagent/blood, 20, data)
|
||||
wait = TRUE
|
||||
update_appearance()
|
||||
var/turf/source_turf = get_turf(src)
|
||||
log_virus("A culture bottle was printed for the virus [adv_disease.admin_details()] at [loc_name(source_turf)] by [key_name(usr)]")
|
||||
addtimer(CALLBACK(src, .proc/reset_replicator_cooldown), 5 SECONDS)
|
||||
return TRUE
|
||||
|
||||
/obj/machinery/computer/pandemic/attackby(obj/item/I, mob/user, params)
|
||||
if(istype(I, /obj/item/reagent_containers) && !(I.item_flags & ABSTRACT) && I.is_open_container())
|
||||
. = TRUE //no afterattack
|
||||
if(machine_stat & (NOPOWER|BROKEN))
|
||||
return
|
||||
if(beaker)
|
||||
to_chat(user, span_warning("A container is already loaded into [src]!"))
|
||||
return
|
||||
if(!user.transferItemToLoc(I, src))
|
||||
return
|
||||
/**
|
||||
* Creates a vaccine bottle for the specified disease.
|
||||
*
|
||||
* @param {number} index - The index of the disease to replicate.
|
||||
*
|
||||
* @returns {boolean} - Success or failure.
|
||||
*/
|
||||
/obj/machinery/computer/pandemic/proc/create_vaccine_bottle(index)
|
||||
use_power(active_power_usage)
|
||||
var/id = index
|
||||
var/datum/disease/disease = SSdisease.archive_diseases[id]
|
||||
var/obj/item/reagent_containers/glass/bottle/bottle = new(drop_location())
|
||||
bottle.name = "[disease.name] vaccine bottle"
|
||||
bottle.reagents.add_reagent(/datum/reagent/vaccine, 15, list(id))
|
||||
wait = TRUE
|
||||
update_appearance()
|
||||
addtimer(CALLBACK(src, .proc/reset_replicator_cooldown), 20 SECONDS)
|
||||
return TRUE
|
||||
|
||||
beaker = I
|
||||
to_chat(user, span_notice("You insert [I] into [src]."))
|
||||
update_appearance()
|
||||
else
|
||||
return ..()
|
||||
/**
|
||||
* Supporting proc to eject a beaker from the machine.
|
||||
*
|
||||
* Places it in hand if possible.
|
||||
*
|
||||
* @returns {boolean} - Success or failure.
|
||||
*/
|
||||
/obj/machinery/computer/pandemic/proc/eject_beaker()
|
||||
if(!beaker)
|
||||
return FALSE
|
||||
try_put_in_hand(beaker, usr)
|
||||
beaker = null
|
||||
update_appearance()
|
||||
return TRUE
|
||||
|
||||
/obj/machinery/computer/pandemic/on_deconstruction()
|
||||
eject_beaker()
|
||||
. = ..()
|
||||
/**
|
||||
* Displays a thing if it exists within the contents of a beaker.
|
||||
*
|
||||
* @param {any} thing - The key to look for.
|
||||
*
|
||||
* @param {any} index - Nested objects within the thing.
|
||||
*
|
||||
* @returns {any | boolean} The thing found or FALSE if unsuccessful.
|
||||
*/
|
||||
/obj/machinery/computer/pandemic/proc/get_by_index(thing, index)
|
||||
if(!beaker || !beaker.reagents)
|
||||
return FALSE
|
||||
var/datum/reagent/blood/blood = locate() in beaker.reagents.reagent_list
|
||||
if(blood?.data[thing])
|
||||
return blood.data[thing][index]
|
||||
return FALSE
|
||||
|
||||
/**
|
||||
* Gets resistances of a given blood sample as a list
|
||||
*
|
||||
* @param {reagent/blood} blood - The sample.
|
||||
*
|
||||
* @returns {list} - The resistances.
|
||||
*/
|
||||
/obj/machinery/computer/pandemic/proc/get_resistance_data(datum/reagent/blood/blood)
|
||||
var/list/data = list()
|
||||
if(!islist(blood.data["resistances"]))
|
||||
return data
|
||||
var/list/resistances = blood.data["resistances"]
|
||||
for(var/id in resistances)
|
||||
var/list/resistance = list()
|
||||
var/datum/disease/disease = SSdisease.archive_diseases[id]
|
||||
if(disease)
|
||||
resistance["id"] = id
|
||||
resistance["name"] = disease.name
|
||||
data += list(resistance)
|
||||
return data
|
||||
|
||||
/**
|
||||
* A very hefty proc that I am not proud to see.
|
||||
*
|
||||
* Given a blood sample, this proc will return a list of viruses that are present in the sample.
|
||||
*
|
||||
* Contains traits, symptoms, thresholds etc.
|
||||
*
|
||||
* @param {reagent/blood} blood - The sample to analyze.
|
||||
*
|
||||
* @returns {list} - A list of virus info present in the sample.
|
||||
*/
|
||||
/obj/machinery/computer/pandemic/proc/get_viruses_data(datum/reagent/blood/blood)
|
||||
var/list/data = list()
|
||||
var/list/viruses = blood.get_diseases()
|
||||
var/index = 1
|
||||
for(var/datum/disease/disease as anything in viruses)
|
||||
if(!istype(disease) || disease.visibility_flags & HIDDEN_PANDEMIC)
|
||||
continue
|
||||
var/list/traits = list()
|
||||
traits["agent"] = disease.agent
|
||||
traits["cure"] = disease.cure_text || "none"
|
||||
traits["description"] = disease.desc || "none"
|
||||
traits["index"] = index++
|
||||
traits["name"] = disease.name
|
||||
traits["spread"] = disease.spread_text || "none"
|
||||
if(!istype(disease, /datum/disease/advance)) // Advanced diseases get more info
|
||||
continue
|
||||
var/datum/disease/advance/adv_disease = disease
|
||||
var/disease_name = SSdisease.get_disease_name(adv_disease.GetDiseaseID())
|
||||
traits["can_rename"] = ((disease_name == "Unknown") && adv_disease.mutable)
|
||||
traits["is_adv"] = TRUE
|
||||
traits["name"] = disease_name
|
||||
traits["resistance"] = adv_disease.totalResistance()
|
||||
traits["stage_speed"] = adv_disease.totalStageSpeed()
|
||||
traits["stealth"] = adv_disease.totalStealth()
|
||||
traits["symptoms"] = list()
|
||||
for(var/datum/symptom/symptom as anything in adv_disease.symptoms)
|
||||
var/list/this_symptom = list()
|
||||
this_symptom = symptom.get_symptom_data()
|
||||
traits["symptoms"] += list(this_symptom)
|
||||
traits["transmission"] = adv_disease.totalTransmittable()
|
||||
data += list(traits)
|
||||
return data
|
||||
|
||||
/**
|
||||
* Gets the ID of the virus by its index in the list of viruses.
|
||||
*
|
||||
* @param {number} index - The index of the virus in the list of viruses.
|
||||
*
|
||||
* @returns {string | boolean} - The ID of the virus or FALSE if unable
|
||||
* to find the virus.
|
||||
*/
|
||||
/obj/machinery/computer/pandemic/proc/get_virus_id_by_index(index)
|
||||
var/datum/disease/disease = get_by_index("viruses", index)
|
||||
if(!disease)
|
||||
return FALSE
|
||||
return disease.GetDiseaseID()
|
||||
|
||||
/**
|
||||
* Renames an advanced disease after running it through sanitize_name().
|
||||
*
|
||||
* @param {string} id - The ID of the disease to rename.
|
||||
*
|
||||
* @param {string} name - The new name of the disease.
|
||||
*
|
||||
* @returns {boolean} - Success or failure.
|
||||
*/
|
||||
/obj/machinery/computer/pandemic/proc/rename_disease(index, name)
|
||||
var/id = get_virus_id_by_index(text2num(index))
|
||||
var/datum/disease/advance/adv_disease = SSdisease.archive_diseases[id]
|
||||
if(!adv_disease.mutable)
|
||||
return FALSE
|
||||
if(adv_disease)
|
||||
var/new_name = sanitize_name(name, allow_numbers = TRUE, cap_after_symbols = FALSE)
|
||||
if(!new_name)
|
||||
return FALSE
|
||||
adv_disease.AssignName(new_name)
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
/**
|
||||
* Allows a user to create another vaccine/culture bottle again.
|
||||
*
|
||||
* @returns {boolean} - Success or failure.
|
||||
*/
|
||||
/obj/machinery/computer/pandemic/proc/reset_replicator_cooldown()
|
||||
wait = FALSE
|
||||
SStgui.update_uis(src)
|
||||
update_appearance()
|
||||
playsound(src, 'sound/machines/ping.ogg', 30, TRUE)
|
||||
return TRUE
|
||||
|
||||
@@ -1,538 +0,0 @@
|
||||
import { capitalizeFirst } from 'common/string';
|
||||
import { BooleanLike } from 'common/react';
|
||||
import { useBackend, useLocalState } from 'tgui/backend';
|
||||
import { Box, Button, Collapsible, Input, LabeledList, NoticeBox, ProgressBar, Section, Stack, Tabs, Tooltip } from 'tgui/components';
|
||||
import { Window } from 'tgui/layouts';
|
||||
|
||||
type PandemicContext = {
|
||||
beaker?: Beaker;
|
||||
blood?: Blood;
|
||||
has_beaker: BooleanLike;
|
||||
has_blood: BooleanLike;
|
||||
is_ready: BooleanLike;
|
||||
resistances?: Resistance[];
|
||||
viruses?: Virus[];
|
||||
};
|
||||
|
||||
type Beaker = {
|
||||
volume: number;
|
||||
capacity: number;
|
||||
};
|
||||
|
||||
type Blood = {
|
||||
dna: string;
|
||||
type: string;
|
||||
};
|
||||
|
||||
type Resistance = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
type Virus = {
|
||||
name: string;
|
||||
can_rename: BooleanLike;
|
||||
is_adv: BooleanLike;
|
||||
symptoms?: Symptom[];
|
||||
resistance: number;
|
||||
stealth: number;
|
||||
stage_speed: number;
|
||||
transmission: number;
|
||||
index: number;
|
||||
agent: string;
|
||||
description: string;
|
||||
spread: string;
|
||||
cure: string;
|
||||
};
|
||||
|
||||
type VirusDisplayProps = {
|
||||
virus: Virus;
|
||||
};
|
||||
|
||||
type VirusInfoProps = {
|
||||
virus: Virus;
|
||||
};
|
||||
|
||||
type TabsProps = {
|
||||
tab: number;
|
||||
tabHandler: (tab: number) => void;
|
||||
};
|
||||
|
||||
type Symptom = {
|
||||
name: string;
|
||||
desc: string;
|
||||
stealth: number;
|
||||
resistance: number;
|
||||
stage_speed: number;
|
||||
transmission: number;
|
||||
level: number;
|
||||
neutered: BooleanLike;
|
||||
threshold_desc: Threshold[];
|
||||
};
|
||||
|
||||
type SymptomDisplayProps = {
|
||||
symptoms: Symptom[];
|
||||
};
|
||||
|
||||
type SymptomInfoProps = {
|
||||
symptom: Symptom;
|
||||
};
|
||||
|
||||
type Threshold = {
|
||||
label: string;
|
||||
descr: string;
|
||||
};
|
||||
|
||||
type ThresholdDisplayProps = {
|
||||
thresholds: Threshold[];
|
||||
};
|
||||
|
||||
export const Pandemic = (props, context) => {
|
||||
const { data } = useBackend<PandemicContext>(context);
|
||||
const { has_beaker, has_blood } = data;
|
||||
|
||||
return (
|
||||
<Window width={650} height={500}>
|
||||
<Window.Content>
|
||||
<Stack fill vertical>
|
||||
<Stack.Item>
|
||||
<BeakerDisplay />
|
||||
</Stack.Item>
|
||||
{!!has_beaker && !!has_blood && (
|
||||
<Stack.Item grow>
|
||||
<SpecimenDisplay />
|
||||
</Stack.Item>
|
||||
)}
|
||||
</Stack>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
/** Displays loaded container info, if it exists */
|
||||
const BeakerDisplay = (props, context) => {
|
||||
const { act, data } = useBackend<PandemicContext>(context);
|
||||
const { has_beaker, beaker, has_blood } = data;
|
||||
const cant_empty = !has_beaker || !beaker?.volume;
|
||||
let content;
|
||||
if (!has_beaker) {
|
||||
content = <NoticeBox>No beaker loaded.</NoticeBox>;
|
||||
} else if (!beaker?.volume) {
|
||||
content = <NoticeBox>Beaker is empty.</NoticeBox>;
|
||||
} else if (!has_blood) {
|
||||
content = <NoticeBox>No blood sample loaded.</NoticeBox>;
|
||||
} else {
|
||||
content = (
|
||||
<Stack vertical>
|
||||
<Stack.Item>
|
||||
<BeakerInfoDisplay />
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<AntibodyInfoDisplay />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Section
|
||||
title="Beaker"
|
||||
buttons={
|
||||
<>
|
||||
<Button
|
||||
icon="times"
|
||||
content="Empty and Eject"
|
||||
color="bad"
|
||||
disabled={cant_empty}
|
||||
onClick={() => act('empty_eject_beaker')}
|
||||
/>
|
||||
<Button
|
||||
icon="trash"
|
||||
content="Empty"
|
||||
disabled={cant_empty}
|
||||
onClick={() => act('empty_beaker')}
|
||||
/>
|
||||
<Button
|
||||
icon="eject"
|
||||
content="Eject"
|
||||
disabled={!has_beaker}
|
||||
onClick={() => act('eject_beaker')}
|
||||
/>
|
||||
</>
|
||||
}>
|
||||
{content}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
/** Displays info about the blood type, beaker capacity - volume */
|
||||
const BeakerInfoDisplay = (props, context) => {
|
||||
const { data } = useBackend<PandemicContext>(context);
|
||||
const { beaker, blood } = data;
|
||||
if (!beaker || !blood) {
|
||||
return <NoticeBox>No beaker loaded</NoticeBox>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Stack.Item grow={2}>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="DNA">
|
||||
{capitalizeFirst(blood.dna)}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Type">
|
||||
{capitalizeFirst(blood.type)}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow={2}>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Container">
|
||||
<ProgressBar
|
||||
color="darkred"
|
||||
value={beaker.volume}
|
||||
minValue={0}
|
||||
maxValue={beaker.capacity}
|
||||
ranges={{
|
||||
'good': [beaker.capacity * 0.85, beaker.capacity],
|
||||
'average': [beaker.capacity * 0.25, beaker.capacity * 0.85],
|
||||
'bad': [0, beaker.capacity * 0.25],
|
||||
}}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
/** If antibodies are present, returns buttons to create vaccines */
|
||||
const AntibodyInfoDisplay = (props, context) => {
|
||||
const { act, data } = useBackend<PandemicContext>(context);
|
||||
const { is_ready, resistances = [] } = data;
|
||||
if (!resistances) {
|
||||
return <NoticeBox>Nothing detected</NoticeBox>;
|
||||
}
|
||||
|
||||
return (
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Antibodies">
|
||||
{!resistances.length
|
||||
? 'None'
|
||||
: resistances.map((resistance) => {
|
||||
return (
|
||||
<Button
|
||||
key={resistance.name}
|
||||
icon="eye-dropper"
|
||||
disabled={!is_ready}
|
||||
tooltip="Creates a vaccine bottle."
|
||||
onClick={() =>
|
||||
act('create_vaccine_bottle', {
|
||||
index: resistance.id,
|
||||
})
|
||||
}>
|
||||
{`${resistance.name}`}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
);
|
||||
};
|
||||
|
||||
/** Displays info for the loaded blood, if any */
|
||||
const SpecimenDisplay = (props, context) => {
|
||||
const { act, data } = useBackend<PandemicContext>(context);
|
||||
const [tab, setTab] = useLocalState(context, 'tab', 0);
|
||||
const { is_ready, viruses = [] } = data;
|
||||
const virus = viruses[tab];
|
||||
const setTabHandler = (index: number) => {
|
||||
setTab(index);
|
||||
};
|
||||
if (!viruses?.length || !virus) {
|
||||
return <NoticeBox>Nothing detected.</NoticeBox>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Section
|
||||
fill
|
||||
scrollable
|
||||
title="Specimen"
|
||||
buttons={
|
||||
<Stack>
|
||||
{viruses.length > 1 && (
|
||||
<Stack.Item>
|
||||
<VirusTabs tab={tab} tabHandler={setTabHandler} />
|
||||
</Stack.Item>
|
||||
)}
|
||||
<Stack.Item>
|
||||
<Button
|
||||
icon="flask"
|
||||
content="Create culture bottle"
|
||||
disabled={!is_ready}
|
||||
onClick={() =>
|
||||
act('create_culture_bottle', {
|
||||
index: virus.index,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
}>
|
||||
<Stack fill vertical>
|
||||
<Stack.Item>
|
||||
<VirusDisplay virus={virus} />
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
{virus?.symptoms && <SymptomDisplay symptoms={virus.symptoms} />}
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
/** Virus Tab display - changes the tab for virus info
|
||||
* Whenever the tab changes, the virus info is updated
|
||||
*/
|
||||
const VirusTabs = (props: TabsProps, context) => {
|
||||
const { data } = useBackend<PandemicContext>(context);
|
||||
const { tab, tabHandler } = props;
|
||||
const { viruses = [] } = data;
|
||||
|
||||
return (
|
||||
<Tabs>
|
||||
{viruses.map((virus, index) => {
|
||||
return (
|
||||
<Tabs.Tab
|
||||
selected={tab === index}
|
||||
onClick={() => tabHandler(index)}
|
||||
key={virus.name}>
|
||||
{virus.name}
|
||||
</Tabs.Tab>
|
||||
);
|
||||
})}
|
||||
</Tabs>
|
||||
);
|
||||
};
|
||||
|
||||
/** Displays info about the virus. Child elements display
|
||||
* the virus's traits and descriptions.
|
||||
*/
|
||||
const VirusDisplay = (props: VirusDisplayProps) => {
|
||||
const { virus } = props;
|
||||
|
||||
return (
|
||||
<Stack fill>
|
||||
<Stack.Item grow={3}>
|
||||
<VirusTextInfo virus={virus} />
|
||||
</Stack.Item>
|
||||
{virus.is_adv && (
|
||||
<>
|
||||
<Stack.Divider />
|
||||
<Stack.Item grow={1}>
|
||||
<VirusTraitInfo virus={virus} />
|
||||
</Stack.Item>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
/** Displays the description, name and other info for the virus. */
|
||||
const VirusTextInfo = (props: VirusInfoProps, context) => {
|
||||
const { act } = useBackend<PandemicContext>(context);
|
||||
const { virus } = props;
|
||||
|
||||
return (
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Name">
|
||||
{virus.can_rename ? (
|
||||
<Input
|
||||
placeholder="Input a name"
|
||||
value={virus.name === 'Unknown' ? '' : virus.name}
|
||||
onChange={(_, value) =>
|
||||
act('rename_disease', {
|
||||
index: virus.index,
|
||||
name: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<Box color="bad">{virus.name}</Box>
|
||||
)}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Description">
|
||||
{virus.description}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Agent">
|
||||
{capitalizeFirst(virus.agent)}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Spread">{virus.spread}</LabeledList.Item>
|
||||
<LabeledList.Item label="Possible Cure">{virus.cure}</LabeledList.Item>
|
||||
</LabeledList>
|
||||
);
|
||||
};
|
||||
|
||||
/** Displays the traits of the virus. This could be iterated over
|
||||
* with object.keys but you would need a helper function for the tooltips.
|
||||
* I would rather hard code it here.
|
||||
*/
|
||||
const VirusTraitInfo = (props: VirusInfoProps) => {
|
||||
const { virus } = props;
|
||||
|
||||
return (
|
||||
<Section title="Statistics">
|
||||
<LabeledList>
|
||||
<Tooltip content="Decides the cure complexity.">
|
||||
<LabeledList.Item
|
||||
color={GetColor(virus.resistance)}
|
||||
label="Resistance">
|
||||
{virus.resistance}
|
||||
</LabeledList.Item>
|
||||
</Tooltip>
|
||||
<Tooltip content="Symptomic progression.">
|
||||
<LabeledList.Item
|
||||
color={GetColor(virus.stage_speed)}
|
||||
label="Stage speed">
|
||||
{virus.stage_speed}
|
||||
</LabeledList.Item>
|
||||
</Tooltip>
|
||||
<Tooltip content="Detection difficulty from medical equipment.">
|
||||
<LabeledList.Item color={GetColor(virus.stealth)} label="Stealth">
|
||||
{virus.stealth}
|
||||
</LabeledList.Item>
|
||||
</Tooltip>
|
||||
<Tooltip content="Decides the spread type.">
|
||||
<LabeledList.Item
|
||||
color={GetColor(virus.transmission)}
|
||||
label="Transmissibility">
|
||||
{virus.transmission}
|
||||
</LabeledList.Item>
|
||||
</Tooltip>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
/** Similar to the virus info display.
|
||||
* Returns info about symptoms as collapsibles.
|
||||
*/
|
||||
const SymptomDisplay = (props: SymptomDisplayProps) => {
|
||||
const { symptoms = [] } = props;
|
||||
if (!symptoms || !symptoms.length) {
|
||||
return <NoticeBox>No symptoms detected.</NoticeBox>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Section fill title="Symptoms">
|
||||
{symptoms.map((symptom) => {
|
||||
return (
|
||||
<Collapsible key={symptom.name} title={symptom.name}>
|
||||
<Stack fill>
|
||||
<Stack.Item grow={3}>
|
||||
{symptom.desc}
|
||||
<ThresholdDisplay thresholds={symptom.threshold_desc} />
|
||||
</Stack.Item>
|
||||
<Stack.Divider />
|
||||
<Stack.Item grow={1}>
|
||||
<SymptomTraitInfo symptom={symptom} />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Collapsible>
|
||||
);
|
||||
})}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
/** Displays the numerical trait modifiers for a virus symptom */
|
||||
const SymptomTraitInfo = (props: SymptomInfoProps) => {
|
||||
const { symptom } = props;
|
||||
|
||||
return (
|
||||
<Section title="Modifiers">
|
||||
<LabeledList>
|
||||
<Tooltip content="Rarity of the symptom.">
|
||||
<LabeledList.Item color={GetColor(symptom.level)} label="Level">
|
||||
{symptom.level}
|
||||
</LabeledList.Item>
|
||||
</Tooltip>
|
||||
<Tooltip content="Decides the cure complexity.">
|
||||
<LabeledList.Item
|
||||
color={GetColor(symptom.resistance)}
|
||||
label="Resistance">
|
||||
{symptom.resistance}
|
||||
</LabeledList.Item>
|
||||
</Tooltip>
|
||||
<Tooltip content="Symptomic progression.">
|
||||
<LabeledList.Item
|
||||
color={GetColor(symptom.stage_speed)}
|
||||
label="Stage Speed">
|
||||
{symptom.stage_speed}
|
||||
</LabeledList.Item>
|
||||
</Tooltip>
|
||||
<Tooltip content="Detection difficulty from medical equipment.">
|
||||
<LabeledList.Item color={GetColor(symptom.stealth)} label="Stealth">
|
||||
{symptom.stealth}
|
||||
</LabeledList.Item>
|
||||
</Tooltip>
|
||||
<Tooltip content="Decides the spread type.">
|
||||
<LabeledList.Item
|
||||
color={GetColor(symptom.transmission)}
|
||||
label="Transmission">
|
||||
{symptom.transmission}
|
||||
</LabeledList.Item>
|
||||
</Tooltip>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
/** Displays threshold data */
|
||||
const ThresholdDisplay = (props: ThresholdDisplayProps) => {
|
||||
const { thresholds = [] } = props;
|
||||
let convertedThresholds: Threshold[] = [];
|
||||
// Converts obj of obj => array of thresholds
|
||||
// I'm sure there's a more succinct way to do this
|
||||
Object.entries(thresholds).map((label) => {
|
||||
return convertedThresholds.push({
|
||||
label: label[0],
|
||||
descr: label[1].toString(),
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<Section mt={1} title="Thresholds">
|
||||
{!convertedThresholds.length ? (
|
||||
<NoticeBox>None</NoticeBox>
|
||||
) : (
|
||||
<LabeledList>
|
||||
{convertedThresholds.map((threshold) => {
|
||||
return (
|
||||
<LabeledList.Item key={threshold.label} label={threshold.label}>
|
||||
{threshold.descr}
|
||||
</LabeledList.Item>
|
||||
);
|
||||
})}
|
||||
</LabeledList>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
/** Gives a color gradient based on the severity of the symptom. */
|
||||
const GetColor = (severity: number) => {
|
||||
if (severity <= -10) {
|
||||
return 'blue';
|
||||
} else if (severity <= -5) {
|
||||
return 'darkturquoise';
|
||||
} else if (severity <= 0) {
|
||||
return 'green';
|
||||
} else if (severity <= 7) {
|
||||
return 'yellow';
|
||||
} else if (severity <= 13) {
|
||||
return 'orange';
|
||||
} else {
|
||||
return 'bad';
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,135 @@
|
||||
import { capitalizeFirst } from 'common/string';
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Button, LabeledList, NoticeBox, ProgressBar, Section, Stack } from 'tgui/components';
|
||||
import { Data } from './types';
|
||||
|
||||
/** Displays loaded container info, if it exists */
|
||||
export const BeakerDisplay = (props, context) => {
|
||||
const { act, data } = useBackend<Data>(context);
|
||||
const { has_beaker, beaker, has_blood } = data;
|
||||
const cant_empty = !has_beaker || !beaker?.volume;
|
||||
let content;
|
||||
if (!has_beaker) {
|
||||
content = <NoticeBox>No beaker loaded.</NoticeBox>;
|
||||
} else if (!beaker?.volume) {
|
||||
content = <NoticeBox>Beaker is empty.</NoticeBox>;
|
||||
} else if (!has_blood) {
|
||||
content = <NoticeBox>No blood sample loaded.</NoticeBox>;
|
||||
} else {
|
||||
content = (
|
||||
<Stack vertical>
|
||||
<Stack.Item>
|
||||
<Info />
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Antibodies />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Section
|
||||
title="Beaker"
|
||||
buttons={
|
||||
<>
|
||||
<Button
|
||||
icon="times"
|
||||
content="Empty and Eject"
|
||||
color="bad"
|
||||
disabled={cant_empty}
|
||||
onClick={() => act('empty_eject_beaker')}
|
||||
/>
|
||||
<Button
|
||||
icon="trash"
|
||||
content="Empty"
|
||||
disabled={cant_empty}
|
||||
onClick={() => act('empty_beaker')}
|
||||
/>
|
||||
<Button
|
||||
icon="eject"
|
||||
content="Eject"
|
||||
disabled={!has_beaker}
|
||||
onClick={() => act('eject_beaker')}
|
||||
/>
|
||||
</>
|
||||
}>
|
||||
{content}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
/** Displays info about the blood type, beaker capacity - volume */
|
||||
const Info = (props, context) => {
|
||||
const { data } = useBackend<Data>(context);
|
||||
const { beaker, blood } = data;
|
||||
if (!beaker || !blood) {
|
||||
return <NoticeBox>No beaker loaded</NoticeBox>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Stack.Item grow={2}>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="DNA">
|
||||
{capitalizeFirst(blood.dna)}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Type">
|
||||
{capitalizeFirst(blood.type)}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow={2}>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Container">
|
||||
<ProgressBar
|
||||
color="darkred"
|
||||
value={beaker.volume}
|
||||
minValue={0}
|
||||
maxValue={beaker.capacity}
|
||||
ranges={{
|
||||
'good': [beaker.capacity * 0.85, beaker.capacity],
|
||||
'average': [beaker.capacity * 0.25, beaker.capacity * 0.85],
|
||||
'bad': [0, beaker.capacity * 0.25],
|
||||
}}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
/** If antibodies are present, returns buttons to create vaccines */
|
||||
const Antibodies = (props, context) => {
|
||||
const { act, data } = useBackend<Data>(context);
|
||||
const { is_ready, resistances = [] } = data;
|
||||
if (!resistances) {
|
||||
return <NoticeBox>Nothing detected</NoticeBox>;
|
||||
}
|
||||
|
||||
return (
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Antibodies">
|
||||
{!resistances.length
|
||||
? 'None'
|
||||
: resistances.map((resistance) => {
|
||||
return (
|
||||
<Button
|
||||
key={resistance.name}
|
||||
icon="eye-dropper"
|
||||
disabled={!is_ready}
|
||||
tooltip="Creates a vaccine bottle."
|
||||
onClick={() =>
|
||||
act('create_vaccine_bottle', {
|
||||
index: resistance.id,
|
||||
})
|
||||
}>
|
||||
{`${resistance.name}`}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,69 @@
|
||||
import { useBackend, useLocalState } from 'tgui/backend';
|
||||
import { Button, NoticeBox, Section, Stack, Tabs } from 'tgui/components';
|
||||
import { Data } from './types';
|
||||
import { SymptomDisplay } from './Symptom';
|
||||
import { VirusDisplay } from './Virus';
|
||||
|
||||
export const SpecimenDisplay = (props, context) => {
|
||||
const { data } = useBackend<Data>(context);
|
||||
const { viruses = [] } = data;
|
||||
const [tab, setTab] = useLocalState(context, 'tab', 0);
|
||||
const virus = viruses[tab];
|
||||
|
||||
return (
|
||||
<Section fill scrollable title="Specimen" buttons={<Buttons />}>
|
||||
{!virus ? (
|
||||
<NoticeBox success>Nothing detected.</NoticeBox>
|
||||
) : (
|
||||
<Stack fill vertical>
|
||||
<Stack.Item>
|
||||
<VirusDisplay virus={virus} />
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
{virus?.symptoms && <SymptomDisplay symptoms={virus.symptoms} />}
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const Buttons = (props, context) => {
|
||||
const { act, data } = useBackend<Data>(context);
|
||||
const { is_ready, viruses = [] } = data;
|
||||
const [tab, setTab] = useLocalState(context, 'tab', 0);
|
||||
const virus = viruses[tab];
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
{viruses.length > 1 && (
|
||||
<Stack.Item>
|
||||
<Tabs>
|
||||
{viruses.map((virus, index) => {
|
||||
return (
|
||||
<Tabs.Tab
|
||||
selected={tab === index}
|
||||
onClick={() => setTab(index)}
|
||||
key={index}>
|
||||
{virus.name}
|
||||
</Tabs.Tab>
|
||||
);
|
||||
})}
|
||||
</Tabs>
|
||||
</Stack.Item>
|
||||
)}
|
||||
<Stack.Item>
|
||||
<Button
|
||||
icon="flask"
|
||||
content="Create culture bottle"
|
||||
disabled={!is_ready}
|
||||
onClick={() =>
|
||||
act('create_culture_bottle', {
|
||||
index: virus.index,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,99 @@
|
||||
import { Collapsible, LabeledList, NoticeBox, Section, Stack, Tooltip } from 'tgui/components';
|
||||
import { getColor } from './helpers';
|
||||
import { Threshold } from './types';
|
||||
|
||||
/**
|
||||
* Similar to the virus info display.
|
||||
* Returns info about symptoms as collapsibles.
|
||||
*/
|
||||
export const SymptomDisplay = (props, context) => {
|
||||
const { symptoms = [] } = props;
|
||||
if (!symptoms?.length) {
|
||||
return <NoticeBox>No symptoms detected.</NoticeBox>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Section fill title="Symptoms">
|
||||
{symptoms.map((symptom) => {
|
||||
const { name, desc, threshold_desc } = symptom;
|
||||
return (
|
||||
<Collapsible key={name} title={name}>
|
||||
<Stack fill>
|
||||
<Stack.Item grow={3}>
|
||||
{desc}
|
||||
<Thresholds thresholds={threshold_desc} />
|
||||
</Stack.Item>
|
||||
<Stack.Divider />
|
||||
<Stack.Item grow={1}>
|
||||
<Traits symptom={symptom} />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Collapsible>
|
||||
);
|
||||
})}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
/** Displays threshold data */
|
||||
const Thresholds = (props, context) => {
|
||||
const { thresholds = [] } = props;
|
||||
let convertedThresholds = Object.entries<Threshold>(thresholds);
|
||||
|
||||
return (
|
||||
<Section mt={1} title="Thresholds">
|
||||
{!convertedThresholds.length ? (
|
||||
<NoticeBox>None</NoticeBox>
|
||||
) : (
|
||||
<LabeledList>
|
||||
{convertedThresholds.map(([label, descr], index) => {
|
||||
return (
|
||||
<LabeledList.Item key={index} label={label}>
|
||||
{descr}
|
||||
</LabeledList.Item>
|
||||
);
|
||||
})}
|
||||
</LabeledList>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
/** Displays the numerical trait modifiers for a virus symptom */
|
||||
const Traits = (props, context) => {
|
||||
const {
|
||||
symptom: { level, resistance, stage_speed, stealth, transmission },
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Section title="Modifiers">
|
||||
<LabeledList>
|
||||
<Tooltip content="Rarity of the symptom.">
|
||||
<LabeledList.Item color={getColor(level)} label="Level">
|
||||
{level}
|
||||
</LabeledList.Item>
|
||||
</Tooltip>
|
||||
<Tooltip content="Decides the cure complexity.">
|
||||
<LabeledList.Item color={getColor(resistance)} label="Resistance">
|
||||
{resistance}
|
||||
</LabeledList.Item>
|
||||
</Tooltip>
|
||||
<Tooltip content="Symptomic progression.">
|
||||
<LabeledList.Item color={getColor(stage_speed)} label="Stage Speed">
|
||||
{stage_speed}
|
||||
</LabeledList.Item>
|
||||
</Tooltip>
|
||||
<Tooltip content="Detection difficulty from medical equipment.">
|
||||
<LabeledList.Item color={getColor(stealth)} label="Stealth">
|
||||
{stealth}
|
||||
</LabeledList.Item>
|
||||
</Tooltip>
|
||||
<Tooltip content="Decides the spread type.">
|
||||
<LabeledList.Item color={getColor(transmission)} label="Transmission">
|
||||
{transmission}
|
||||
</LabeledList.Item>
|
||||
</Tooltip>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,104 @@
|
||||
import { capitalizeFirst, decodeHtmlEntities } from 'common/string';
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Box, Input, LabeledList, Section, Stack, Tooltip } from 'tgui/components';
|
||||
import { getColor } from './helpers';
|
||||
import { Data } from './types';
|
||||
|
||||
/**
|
||||
* Displays info about the virus. Child elements display
|
||||
* the virus's traits and descriptions.
|
||||
*/
|
||||
export const VirusDisplay = (props, context) => {
|
||||
const { virus } = props;
|
||||
|
||||
return (
|
||||
<Stack fill>
|
||||
<Stack.Item grow={3}>
|
||||
<Info virus={virus} />
|
||||
</Stack.Item>
|
||||
{virus.is_adv && (
|
||||
<>
|
||||
<Stack.Divider />
|
||||
<Stack.Item grow={1}>
|
||||
<Traits virus={virus} />
|
||||
</Stack.Item>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
/** Displays the description, name and other info for the virus. */
|
||||
const Info = (props, context) => {
|
||||
const { act } = useBackend<Data>(context);
|
||||
const {
|
||||
virus: { agent, can_rename, cure, description, index, name, spread },
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Name">
|
||||
{can_rename ? (
|
||||
<Input
|
||||
placeholder="Input a name"
|
||||
value={name === 'Unknown' ? '' : name}
|
||||
onChange={(_, value) =>
|
||||
act('rename_disease', {
|
||||
index: index,
|
||||
name: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<Box color="bad">{decodeHtmlEntities(name)}</Box>
|
||||
)}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Description">{description}</LabeledList.Item>
|
||||
<LabeledList.Item label="Agent">
|
||||
{capitalizeFirst(agent)}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Spread">{spread}</LabeledList.Item>
|
||||
<LabeledList.Item label="Possible Cure">{cure}</LabeledList.Item>
|
||||
</LabeledList>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Displays the traits of the virus. This could be iterated over
|
||||
* with object.keys but you would need a helper function for the tooltips.
|
||||
* I would rather hard code it here.
|
||||
*/
|
||||
const Traits = (props, context) => {
|
||||
const {
|
||||
virus: { resistance, stage_speed, stealth, transmission },
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Section title="Statistics">
|
||||
<LabeledList>
|
||||
<Tooltip content="Decides the cure complexity.">
|
||||
<LabeledList.Item color={getColor(resistance)} label="Resistance">
|
||||
{resistance}
|
||||
</LabeledList.Item>
|
||||
</Tooltip>
|
||||
<Tooltip content="Symptomic progression.">
|
||||
<LabeledList.Item color={getColor(stage_speed)} label="Stage speed">
|
||||
{stage_speed}
|
||||
</LabeledList.Item>
|
||||
</Tooltip>
|
||||
<Tooltip content="Detection difficulty from medical equipment.">
|
||||
<LabeledList.Item color={getColor(stealth)} label="Stealth">
|
||||
{stealth}
|
||||
</LabeledList.Item>
|
||||
</Tooltip>
|
||||
<Tooltip content="Decides the spread type.">
|
||||
<LabeledList.Item
|
||||
color={getColor(transmission)}
|
||||
label="Transmissibility">
|
||||
{transmission}
|
||||
</LabeledList.Item>
|
||||
</Tooltip>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
/** Gives a color gradient based on the severity of the symptom. */
|
||||
export const getColor = (severity: number) => {
|
||||
if (severity <= -10) {
|
||||
return 'blue';
|
||||
} else if (severity <= -5) {
|
||||
return 'darkturquoise';
|
||||
} else if (severity <= 0) {
|
||||
return 'green';
|
||||
} else if (severity <= 7) {
|
||||
return 'yellow';
|
||||
} else if (severity <= 13) {
|
||||
return 'orange';
|
||||
} else {
|
||||
return 'bad';
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Stack } from 'tgui/components';
|
||||
import { Window } from 'tgui/layouts';
|
||||
import { Data } from './types';
|
||||
import { BeakerDisplay } from './Beaker';
|
||||
import { SpecimenDisplay } from './Specimen';
|
||||
|
||||
export const Pandemic = (props, context) => {
|
||||
const { data } = useBackend<Data>(context);
|
||||
const { has_beaker, has_blood } = data;
|
||||
|
||||
return (
|
||||
<Window width={650} height={500}>
|
||||
<Window.Content>
|
||||
<Stack fill vertical>
|
||||
<Stack.Item>
|
||||
<BeakerDisplay />
|
||||
</Stack.Item>
|
||||
{!!has_beaker && !!has_blood && (
|
||||
<Stack.Item grow>
|
||||
<SpecimenDisplay />
|
||||
</Stack.Item>
|
||||
)}
|
||||
</Stack>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,59 @@
|
||||
import { BooleanLike } from 'common/react';
|
||||
|
||||
export type Data = {
|
||||
beaker?: Beaker;
|
||||
blood?: Blood;
|
||||
has_beaker: BooleanLike;
|
||||
has_blood: BooleanLike;
|
||||
is_ready: BooleanLike;
|
||||
resistances?: Resistance[];
|
||||
viruses?: Virus[];
|
||||
};
|
||||
|
||||
type Beaker = {
|
||||
volume: number;
|
||||
capacity: number;
|
||||
};
|
||||
|
||||
type Blood = {
|
||||
dna: string;
|
||||
type: string;
|
||||
};
|
||||
|
||||
type Resistance = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
type Virus = {
|
||||
name: string;
|
||||
can_rename: BooleanLike;
|
||||
is_adv: BooleanLike;
|
||||
symptoms?: Symptom[];
|
||||
resistance: number;
|
||||
stealth: number;
|
||||
stage_speed: number;
|
||||
transmission: number;
|
||||
index: number;
|
||||
agent: string;
|
||||
description: string;
|
||||
spread: string;
|
||||
cure: string;
|
||||
};
|
||||
|
||||
export type Symptom = {
|
||||
name: string;
|
||||
desc: string;
|
||||
stealth: number;
|
||||
resistance: number;
|
||||
stage_speed: number;
|
||||
transmission: number;
|
||||
level: number;
|
||||
neutered: BooleanLike;
|
||||
threshold_desc: Threshold[];
|
||||
};
|
||||
|
||||
export type Threshold = {
|
||||
label: string;
|
||||
descr: string;
|
||||
};
|
||||
Reference in New Issue
Block a user