Disease Outbreak: Advanced v3 (#73092)

## About The Pull Request
PR to replace https://github.com/tgstation/tgstation/pull/73001

Revamps the Disease Outbreak: Advanced random event to be more
consistent in actually generating random diseases.

This generates random viruses based on rolling severity, symptoms, and
transmissibility separately and ensures the virus created through RNG is
valid and transmissible.

Revamps menu for admin spawned DO:A events. Custom outbreak with manual
symptom selection is available in the Secrets panel.

Parameters for generating the viruses can be tweaked using the defines
in the event file.
## Why It's Good For The Game
Current DO:A code has a tendency to generate viruses that aren't
actually transmissible, be it through choosing too many symptoms or
having a transmissibility that's too low to allow spread.
## Changelog
🆑 LT3
refactor: Rewrite of random event Disease Outbreak: Advanced. Listen to
those level 7 biohazard warnings!
admin: Fully customizable build-your-own virus is available in the admin
Secrets panel, for those people you really hate
/🆑

---------

Co-authored-by: san7890 <the@san7890.com>
This commit is contained in:
lessthanthree
2023-02-04 23:27:44 -07:00
committed by GitHub
co-authored by san7890
parent dd0b1cced3
commit d28e26ddcd
2 changed files with 271 additions and 48 deletions
+13
View File
@@ -499,3 +499,16 @@
/datum/disease/advance/proc/totalTransmittable()
return properties["transmittable"]
/**
* Make virus visible to heath scanners
*/
/datum/disease/advance/proc/MakeVisible()
visibility_flags &= ~HIDDEN_SCANNER
for(var/datum/disease/advance/virus in SSdisease.active_diseases)
if(!virus.id)
stack_trace("Advanced virus ID is empty or null!")
return
if(virus.id == id)
virus.visibility_flags &= ~HIDDEN_SCANNER
+258 -48
View File
@@ -1,3 +1,24 @@
/// Advanced virus lower limit for symptoms
#define ADV_MIN_SYMPTOMS 3
/// Advanced virus upper limit for symptoms
#define ADV_MAX_SYMPTOMS 4
/// How long the virus stays hidden before announcement
#define ADV_ANNOUNCE_DELAY 75
/// Numerical define for medium severity advanced virus
#define ADV_DISEASE_MEDIUM 4
/// Numerical define for harmful severity advanced virus
#define ADV_DISEASE_HARMFUL 5
/// Numerical define for dangerous severity advanced virus
#define ADV_DISEASE_DANGEROUS 7
/// Percentile for low severity advanced virus
#define ADV_RNG_LOW 30
/// Percentile for mid severity advanced virus
#define ADV_RNG_MID 85
/// Percentile for low transmissibility advanced virus
#define ADV_SPREAD_LOW 30
/// Percentile for mid transmissibility advanced virus
#define ADV_SPREAD_MID 90
/datum/round_event_control/disease_outbreak
name = "Disease Outbreak: Classic"
typepath = /datum/round_event/disease_outbreak
@@ -5,7 +26,7 @@
min_players = 10
weight = 5
category = EVENT_CATEGORY_HEALTH
description = "A 'classic' virus will infect some members of the crew." //These are the ones with PERSONALITY
description = "A 'classic' virus will infect some members of the crew."
admin_setup = /datum/event_admin_setup/disease_outbreak
///Disease recipient candidates
var/list/disease_candidates = list()
@@ -35,8 +56,31 @@
continue
disease_candidates += candidate
/datum/event_admin_setup/disease_outbreak
///Admin selected disease, to be passed down to the round_event
var/virus_type
/// Checks for candidates. Returns false if there isn't enough
/datum/event_admin_setup/disease_outbreak/proc/candidate_check()
var/datum/round_event_control/disease_outbreak/disease_control = event_control
disease_control.generate_candidates() //can_spawn_event() is bypassed by admin_setup, so this makes sure that the candidates are still generated
return length(disease_control.disease_candidates)
/datum/event_admin_setup/disease_outbreak/prompt_admins()
var/candidate_count = candidate_check()
if(!candidate_count)
tgui_alert(usr, "There are no candidates eligible to recieve a disease!", "Error")
return ADMIN_CANCEL_EVENT
tgui_alert(usr, "[candidate_count] candidates found!", "Disease Outbreak")
if(tgui_alert(usr, "Select a specific disease?", "Sickening behavior", list("Yes", "No")) == "Yes")
virus_type = tgui_input_list(usr, "Warning: Some of these are EXTREMELY dangerous.","Bacteria Hysteria", subtypesof(/datum/disease))
/datum/event_admin_setup/disease_outbreak/apply_to_event(datum/round_event/disease_outbreak/event)
event.virus_type = virus_type
/datum/round_event/disease_outbreak
announce_when = 120
announce_when = ADV_ANNOUNCE_DELAY
///The disease type we will be spawning
var/datum/disease/virus_type
///Disease recipient candidates, passed from the round_event_control object
@@ -46,7 +90,7 @@
priority_announce("Confirmed outbreak of level 7 viral biohazard aboard [station_name()]. All personnel must contain the outbreak.", "Biohazard Alert", ANNOUNCER_OUTBREAK7)
/datum/round_event/disease_outbreak/setup()
announce_when = rand(60, 180)
announce_when = ADV_ANNOUNCE_DELAY
/datum/round_event/disease_outbreak/start()
var/datum/round_event_control/disease_outbreak/disease_event = control
@@ -83,81 +127,247 @@
name = "Disease Outbreak: Advanced"
typepath = /datum/round_event/disease_outbreak/advanced
category = EVENT_CATEGORY_HEALTH
description = "An 'advanced' disease will infect some members of the crew." //These are the ones that get viro lynched!
weight = 10
description = "An 'advanced' disease will infect some members of the crew."
admin_setup = /datum/event_admin_setup/disease_outbreak/advanced
/datum/event_admin_setup/disease_outbreak/advanced
///Admin selected custom severity rating for the event
var/chosen_severity
///Admin selected custom value for the maximum symptoms this virus should have
var/chosen_max_symptoms
/**
* Admin virus customization
*
* If the admin wishes, give them the opportunity to select the severity and number of symptoms.
*/
/datum/event_admin_setup/disease_outbreak/advanced/prompt_admins()
var/candidate_count = candidate_check()
if(!candidate_count)
tgui_alert(usr, "There are no candidates eligible to recieve a disease!", "Error")
return ADMIN_CANCEL_EVENT
tgui_alert(usr, "[candidate_count] candidates found!", "Disease Outbreak")
if(tgui_alert(usr,"Customize your virus?", "Glorified Debug Tool", list("Yes", "No")) == "Yes")
chosen_severity = tgui_alert(usr, "Pick a severity!", "In the event of an airborne virus, try not to breathe.", list("Medium", "Harmful", "Dangerous"))
switch(chosen_severity)
if("Medium")
chosen_severity = ADV_DISEASE_MEDIUM
if("Harmful")
chosen_severity = ADV_DISEASE_HARMFUL
if("Dangerous")
chosen_severity = ADV_DISEASE_DANGEROUS
else
return ADMIN_CANCEL_EVENT
chosen_max_symptoms = tgui_input_number(usr, "How many symptoms do you want your virus to have?", "A pox upon ye!", 4, 15)
else
chosen_severity = null
chosen_max_symptoms = null
if(tgui_alert(usr,"Are you happy with your selections?", "Epidemic warning, Standby!", list("Yes", "Cancel")) != "Yes")
return ADMIN_CANCEL_EVENT
/datum/event_admin_setup/disease_outbreak/advanced/apply_to_event(datum/round_event/disease_outbreak/advanced/event)
event.max_severity = chosen_severity
event.max_symptoms = chosen_max_symptoms
/datum/round_event/disease_outbreak/advanced
///Number of symptoms for our virus
var/max_severity
//Maximum symptoms for our virus
var/max_symptoms
/**
* Generate virus base values
*
* Generates a virus with either the admin selected parameters for severity and symptoms
* or if it was not selected, randomly pick between the MIX and MAX configured in the defines.
*/
/datum/round_event/disease_outbreak/advanced/start()
var/datum/round_event_control/disease_outbreak/advanced/disease_event = control
afflicted += disease_event.disease_candidates
disease_event.disease_candidates.Cut() //Clean the list after use
disease_event.disease_candidates.Cut()
if(!max_symptoms)
max_symptoms = 3 + max(FLOOR((world.time - control.earliest_start)/6000, 1),0) //3 symptoms at 20 minutes, plus 1 per 10 minutes.
max_symptoms = clamp(max_symptoms, 3, 8) //Capping the virus symptoms prevents the event from becoming "smite one poor player with an -12 transmission hell virus" after a certain round length.
max_symptoms = rand(ADV_MIN_SYMPTOMS, ADV_MAX_SYMPTOMS)
if(!max_severity)
max_severity = 3 + max(FLOOR((world.time - control.earliest_start)/6000, 1),0) //Max severity doesn't need clamping
var/rng_severity = rand(1, 100)
if(rng_severity < ADV_RNG_LOW)
max_severity = ADV_DISEASE_MEDIUM
var/datum/disease/advance/advanced_disease = new /datum/disease/advance/random(max_symptoms, max_severity)
else if(rng_severity < ADV_RNG_MID)
max_severity = ADV_DISEASE_HARMFUL
var/list/name_symptoms = list() //for feedback
for(var/datum/symptom/new_symptom in advanced_disease.symptoms)
else
max_severity = ADV_DISEASE_DANGEROUS
var/datum/disease/advance/advanced_disease = new /datum/disease/advance/random/event(max_symptoms, max_severity)
var/list/name_symptoms = list()
for(var/datum/symptom/new_symptom as anything in advanced_disease.symptoms)
name_symptoms += new_symptom.name
var/mob/living/carbon/human/victim = pick_n_take(afflicted)
if(victim.ForceContractDisease(advanced_disease, FALSE))
message_admins("An event has triggered a random advanced virus outbreak on [ADMIN_LOOKUPFLW(victim)]! It has these symptoms: [english_list(name_symptoms)]")
log_game("An event has triggered a random advanced virus outbreak on [key_name(victim)]! It has these symptoms: [english_list(name_symptoms)].")
message_admins("An event has triggered a random advanced virus outbreak on [ADMIN_LOOKUPFLW(victim)]! It has these symptoms: [english_list(name_symptoms)]. It is transmissable via [advanced_disease.spread_text].")
log_game("An event has triggered a random advanced virus outbreak on [key_name(victim)]! It has these symptoms: [english_list(name_symptoms)]. It is transmissable via [advanced_disease.spread_text].")
announce_to_ghosts(victim)
else
log_game("An event attempted to trigger a random advanced virus outbreak on [key_name(victim)], but failed.")
/datum/event_admin_setup/disease_outbreak
///Admin selected disease, to be passed down to the round_event
var/virus_type
/datum/disease/advance/random/event
name = "Experimental Disease"
copy_type = /datum/disease/advance
/// Checks for candidates. Returns false if there isn't enough
/datum/event_admin_setup/disease_outbreak/proc/candidate_check()
var/datum/round_event_control/disease_outbreak/disease_control = event_control
disease_control.generate_candidates() //can_spawn_event() is bypassed by admin_setup, so this makes sure that the candidates are still generated
return length(disease_control.disease_candidates)
/datum/round_event/disease_outbreak/advance/setup()
announce_when = ADV_ANNOUNCE_DELAY
/datum/event_admin_setup/disease_outbreak/prompt_admins()
var/candidate_count = candidate_check()
if(!candidate_check())
tgui_alert(usr, "There are no candidates eligible to recieve a disease!", "Error")
return ADMIN_CANCEL_EVENT
tgui_alert(usr, "[candidate_count] candidates found!", "Disease Outbreak")
/**
* Generate advanced virus
*
* Uses the parameters to create a list of symptoms, picking from various severities
* Viral Evolution and Eternal Youth are special modifiers, so we roll separately.
*/
if(tgui_alert(usr, "Select a specific disease?", "Sickening behavior", list("Yes", "No")) == "Yes")
virus_type = tgui_input_list(usr, "Warning: Some of these are EXTREMELY dangerous.","Bacteria Hysteria", subtypesof(/datum/disease))
/datum/disease/advance/random/event/New(max_symptoms, max_severity)
var/list/datum/symptom/possible_symptoms = list(
/datum/symptom/beard,
/datum/symptom/chills,
/datum/symptom/choking,
/datum/symptom/confusion,
/datum/symptom/cough,
/datum/symptom/deafness,
/datum/symptom/dizzy,
/datum/symptom/fever,
/datum/symptom/headache,
/datum/symptom/itching,
/datum/symptom/shedding,
/datum/symptom/sneeze,
/datum/symptom/vomit,
/datum/symptom/weight_loss,
)
/datum/event_admin_setup/disease_outbreak/apply_to_event(datum/round_event/disease_outbreak/event)
event.virus_type = virus_type
switch(max_severity)
if(ADV_DISEASE_HARMFUL)
possible_symptoms += list(
/datum/symptom/disfiguration,
/datum/symptom/hallucigen,
/datum/symptom/polyvitiligo,
/datum/symptom/visionloss,
)
/datum/event_admin_setup/disease_outbreak/advanced
///Admin selected custom severity rating for the event
var/max_severity
///Admin selected custom value for the maximum symptoms this virus should have
var/max_symptoms
if(ADV_DISEASE_DANGEROUS)
possible_symptoms += list(
/datum/symptom/alkali,
/datum/symptom/asphyxiation,
/datum/symptom/fire,
/datum/symptom/flesh_death,
/datum/symptom/flesh_eating,
/datum/symptom/narcolepsy,
/datum/symptom/voice_change,
)
/datum/event_admin_setup/disease_outbreak/advanced/prompt_admins()
var/candidate_count = candidate_check()
if(!candidate_check())
tgui_alert(usr, "There are no candidates eligible to recieve a disease!", "Error")
return ADMIN_CANCEL_EVENT
tgui_alert(usr, "[candidate_count] candidates found!", "Disease Outbreak")
for(var/i in 1 to max_symptoms)
var/datum/symptom/chosen_symptom = pick_n_take(possible_symptoms)
if(chosen_symptom)
var/datum/symptom/new_symptom = new chosen_symptom
symptoms += new_symptom
if(tgui_alert(usr,"Customize your virus?", "Glorified Debug Tool", list("Yes", "No")) == "Yes")
max_severity = tgui_input_number(usr, "Select a custom severity for your virus!", "Plague Incorporation!", 3, 8)
max_symptoms = tgui_input_number(usr, "How many symptoms do you want your virus to have?", "A pox upon ye!", 3, 15)
if(prob(50))
symptoms += new /datum/symptom/viralevolution
/datum/event_admin_setup/disease_outbreak/advanced/apply_to_event(datum/round_event/disease_outbreak/advanced/event)
event.max_severity = max_severity
event.max_symptoms = max_symptoms
if(prob(50))
symptoms += new /datum/symptom/youth
Refresh()
name = "Sample #[rand(1, 9999)]"
/**
* Assign virus properties
*
* Now that we've picked our symptoms and severity, we determine the other stats
* (Stage Speed, Resistance, Transmissibility)
* The LOW/MID percentiles can be adjusted in the defines.
* If the virus is severity DANGEROUS we do not hide it from health scanners at event start.
* If the virus is airborne, also don't hide it.
*/
/datum/disease/advance/random/event/AssignProperties()
var/transmissibility = rand(1, 100)
addtimer(CALLBACK(src, PROC_REF(MakeVisible)), ((ADV_ANNOUNCE_DELAY * 2) - 10) SECONDS)
if(length(properties))
spreading_modifier = max(CEILING(0.4 * properties["transmittable"], 1), 1)
cure_chance = clamp(7.5 - (0.5 * properties["resistance"]), 5, 10) // Can be between 5 and 10
stage_prob = max(0.5 * properties["stage_rate"], 1)
SetSeverity(properties["severity"])
visibility_flags |= HIDDEN_SCANNER
switch(severity)
if("Dangerous", "BIOHAZARD")
visibility_flags &= ~HIDDEN_SCANNER
GenerateCure(properties)
if(transmissibility < ADV_SPREAD_LOW)
SetSpread(DISEASE_SPREAD_CONTACT_FLUIDS)
else if(transmissibility < ADV_SPREAD_MID)
SetSpread(DISEASE_SPREAD_CONTACT_SKIN)
else
SetSpread(DISEASE_SPREAD_AIRBORNE)
visibility_flags &= ~HIDDEN_SCANNER
else
CRASH("Advanced virus properties were empty or null!")
/**
* Set the transmission methods on the generated virus
*
* Apply the transmission methods we rolled in the AssignProperties proc
*/
/datum/disease/advance/random/event/SetSpread(spread_id)
switch(spread_id)
if(DISEASE_SPREAD_CONTACT_FLUIDS)
spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_FLUIDS
spread_text = "fluids"
if(DISEASE_SPREAD_CONTACT_SKIN)
spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_FLUIDS | DISEASE_SPREAD_CONTACT_SKIN
spread_text = "skin contact"
if(DISEASE_SPREAD_AIRBORNE)
spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_FLUIDS | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_AIRBORNE
spread_text = "respiration"
/**
* Determine the cure
*
* Rolls one of six possible cure groups, then selects a cure from it and applies it to the virus.
*/
/datum/disease/advance/random/event/GenerateCure()
if(!length(properties))
stack_trace("Advanced virus properties were empty or null!")
return
var/res = rand(1, 6)
if(res == oldres)
return
cures = list(pick(advance_cures[res]))
oldres = res
// Get the cure name from the cure_id
var/datum/reagent/cure = GLOB.chemical_reagents_list[cures[1]]
cure_text = cure.name
#undef ADV_MIN_SYMPTOMS
#undef ADV_MAX_SYMPTOMS
#undef ADV_ANNOUNCE_DELAY
#undef ADV_DISEASE_MEDIUM
#undef ADV_DISEASE_HARMFUL
#undef ADV_DISEASE_DANGEROUS
#undef ADV_RNG_LOW
#undef ADV_RNG_MID
#undef ADV_SPREAD_LOW
#undef ADV_SPREAD_MID