Virology Update #3 (#17227)

* Base stuff

* Thresholds

* Cockroaches, adjustments

* Extrapolator + TGUI Update

* Adjustments

* Updoot

* Revert "Updoot"

This reverts commit 8c27a2525e.

* Gwuh

* test

* heals

* Genetic

* Holder and vomit

* Indents

* Compilable :)

* Various fixes

* Updates Symptoms

* Genevirus

* Neutered Symptoms

* Adjustments

* rads

* Extra symptoms

* Extra extra symptoms

* Adjustments, fixes, more symptoms

* Powder that makes the linter green

* Finishing touches?

* Fixup maps in TGM format

5041170ae1: maps/expedition_vr/beach/submaps/quarantineshuttle.dmm

Automatically commited by: tools\mapmerge2\fixup.py

* Last bits

* Defines

* Oxy heal

* I crave the green check

* fix

* Maps

* Macrophages to turf

* Fimxes

* Fixes :)

* Vomit

* Preset

* Fire desc

* SPELLING MISTAKE

* Extra stuffs

* types

Allows infecting children of a type

* feet

paws?

* fix

* Fixes

* Update

* Flags

* Update _disease.dm

* Infinite blood glitch (100% real)

* virus data properly carries over

* GODSPEED, KELENIUS

YOUR 10 YEAR TODO IS FINALLY DONE

* define

* U2

* Implicit

* Damn it Fleming

* oops

linter is kill

* Update _reagents.dm

* Extra check

* .

* ough

* fixes

* Small changes

---------

Co-authored-by: Cameron Lennox <killer65311@gmail.com>
This commit is contained in:
Guti
2025-04-21 06:12:15 -04:00
committed by GitHub
co-authored by Cameron Lennox
parent a0d1eae5d1
commit 640cab345e
121 changed files with 3669 additions and 1903 deletions
@@ -125,7 +125,7 @@ var/global/list/image/splatter_cache=list()
if(viruses)
for(var/datum/disease/D in viruses)
if(D.IsSpreadByTouch())
perp.ContractDisease(D)
perp.ContractDisease(D, BP_R_FOOT)
amount--
@@ -156,7 +156,7 @@ var/global/list/image/splatter_cache=list()
if(viruses)
for(var/datum/disease/D in viruses)
if(D.IsSpreadByTouch())
user.ContractDisease(D)
user.ContractDisease(D, BP_R_HAND)
/obj/effect/decal/cleanable/blood/splatter
random_icon_states = list("mgibbl1", "mgibbl2", "mgibbl3", "mgibbl4", "mgibbl5")
@@ -287,7 +287,9 @@ var/global/list/image/splatter_cache=list()
return
if(viruses)
for(var/datum/disease/D in viruses)
perp.ContractDisease(D)
if(D.spread_flags & (DISEASE_SPREAD_SPECIAL | DISEASE_SPREAD_NON_CONTAGIOUS))
continue
perp.ContractDisease(D, BP_R_FOOT)
/obj/effect/decal/cleanable/mucus/attack_hand(mob/living/carbon/human/perp)
if(perp.is_incorporeal())
@@ -296,7 +298,9 @@ var/global/list/image/splatter_cache=list()
return
if(viruses)
for(var/datum/disease/D in viruses)
perp.ContractDisease(D)
if(D.spread_flags & (DISEASE_SPREAD_SPECIAL | DISEASE_SPREAD_NON_CONTAGIOUS))
continue
perp.ContractDisease(D, BP_R_HAND)
/obj/effect/decal/cleanable/vomit/Crossed(mob/living/carbon/human/perp)
if(perp.is_incorporeal())
@@ -305,15 +309,31 @@ var/global/list/image/splatter_cache=list()
return
if(viruses)
for(var/datum/disease/D in viruses)
perp.ContractDisease(D)
if(D.spread_flags & (DISEASE_SPREAD_SPECIAL | DISEASE_SPREAD_NON_CONTAGIOUS))
continue
perp.ContractDisease(D, BP_R_FOOT)
/obj/effect/decal/cleanable/vomit/Crossed(mob/living/carbon/human/perp)
/obj/effect/decal/cleanable/vomit/attack_hand(mob/living/carbon/human/perp)
if(perp.is_incorporeal())
return
if(!istype(perp))
return
if(viruses)
for(var/datum/disease/D in viruses)
perp.ContractDisease(D)
if(D.spread_flags & (DISEASE_SPREAD_SPECIAL | DISEASE_SPREAD_NON_CONTAGIOUS))
continue
perp.ContractDisease(D, BP_R_HAND)
/obj/effect/decal/cleanable/mucus/extrapolator_act(mob/living/user, obj/item/extrapolator/extrapolator, dry_run)
. = ..()
EXTRAPOLATOR_ACT_ADD_DISEASES(., viruses)
/obj/effect/decal/cleanable/vomit/extrapolator_act(mob/living/user, obj/item/extrapolator/extrapolator, dry_run)
. = ..()
EXTRAPOLATOR_ACT_ADD_DISEASES(., viruses)
/obj/effect/decal/cleanable/blood/extrapolator_act(mob/living/user, obj/item/extrapolator/extrapolator, dry_run)
. = ..()
EXTRAPOLATOR_ACT_ADD_DISEASES(., viruses)
#undef DRYING_TIME
@@ -123,6 +123,19 @@
random_icon_states = list("vomit_1", "vomit_2", "vomit_3", "vomit_4")
var/list/datum/disease/viruses = list()
/obj/effect/decal/cleanable/vomit/old
name = "crusty dried vomit"
desc = "You try not to look at the chunks, and fail."
/obj/effect/decal/cleanable/vomit/old/Initialize(mapload, list/datum/disease/diseases)
. = ..()
icon_state += "-old"
if(length(diseases))
viruses += diseases
if(prob(95))
var/datum/disease/advance/new_disease = new /datum/disease/advance/random(rand(2, 5), rand(7, 9), 4)
src.viruses += new_disease
/obj/effect/decal/cleanable/tomato_smudge
name = "tomato smudge"
desc = "It's red."
@@ -0,0 +1,264 @@
/obj/item/extrapolator
name = "virus extrapolator"
icon = 'icons/obj/device.dmi'
icon_state = "extrapolator_scan"
desc = "A bulky scanning device, used to extract genetic material of potential pathogens."
item_flags = NOBLUDGEON
slot_flags = SLOT_BELT
w_class = ITEMSIZE_NORMAL
/// Whether the extrapolator is curently in use
var/using = FALSE
/// Whether the extrapolator is curently in SCAN or EXTRACT mode
var/scan = TRUE
/// The scanning module installed in the extrapolator. Used to determine extraction speed, and the stealthiest virus that's possible to extract.
var/obj/item/stock_parts/scanning_module/scanner
/// A list of advance IDs that this extrapolator has already extracted.
var/list/extracted_ids = list()
/// How long it takes for the extrapolator to extract a virus.
var/extract_time = 10 SECONDS
/// How long it tkaes for the extrapolator to isolate a symptom.
var/isolate_time = 15 SECONDS
/// The extrapolator can extract any virus with a stealth below this value.
var/maximum_stealth = 3
/// The extrapolator can extract any symptom with a stealth below this value.
var/maximum_level = 5
/// The typepath of the default scanning module that will generate in the extrapolator, if it starts with none.
var/default_scanning_module = /obj/item/stock_parts/scanning_module
/// Cooldown for when the extrapolator can be used next.
COOLDOWN_DECLARE(usage_cooldown)
/obj/item/extrapolator/Initialize(mapload, obj/item/stock_parts/scanning_module/starting_scanner)
. = ..()
starting_scanner = starting_scanner || default_scanning_module
if(ispath(starting_scanner, /obj/item/stock_parts/scanning_module))
scanner = new starting_scanner(src)
else if(istype(starting_scanner))
starting_scanner.forceMove(src)
scanner = starting_scanner
refresh_parts()
/obj/item/extrapolator/attackby(obj/item/item, mob/user, params)
if(istype(item, /obj/item/stock_parts/scanning_module))
if(!scanner)
user.drop_item()
item.loc = src
scanner = item
to_chat(user, span_notice("You install \the [scanner] in [src]."))
refresh_parts()
else
to_chat(user, span_notice("[src] already has \the [scanner] installed."))
return
if(item.has_tool_quality(TOOL_SCREWDRIVER) && scanner)
if(!scanner) // You never know
to_chat(user, span_warning("\The [src] has no scanner to remove!"))
return FALSE
to_chat(user, span_notice("You remove \the [scanner] from \the [src]."))
scanner.forceMove(drop_location())
scanner = null
playsound(src, item.usesound, 50, 1)
return TRUE
/obj/item/extrapolator/attack_self(mob/user)
. = ..()
playsound(src, 'sound/machines/click.ogg', 50, TRUE)
if(scan)
icon_state = "extrapolator_sample"
scan = FALSE
to_chat(user, span_notice("You remove the probe from the device and set it to EXTRACT."))
else
icon_state = "extrapolator_scan"
scan = TRUE
to_chat(user, span_notice("You put the probe back into the device and set it to SCAN."))
/obj/item/extrapolator/examine(mob/user)
. = ..()
if(in_range(user, src) || isobserver(user))
if(!scanner)
. += span_notice("The scanner is missing.")
else
. += span_notice("A class " + span_bold("[scanner.rating]") + " scanning module is installed. It is <i>screwed</i> in place.")
. += span_notice("Can detect diseases below stealth " + span_bold("[maximum_stealth]") + ".")
. += span_notice("Can extract diseases in " + span_bold("[DisplayTimeText(extract_time)]") + ".")
. += span_notice("Can isolate symptoms <b>[maximum_level >= 9 ? "of any level" : "below level [maximum_level]"]</b>, in <b>[DisplayTimeText(isolate_time)]</b>.")
/obj/item/extrapolator/proc/refresh_parts()
if(!scanner)
return
var/effective_scanner_rating = scanner.rating +1
extract_time = (10 SECONDS) / effective_scanner_rating
isolate_time = (15 SECONDS) / effective_scanner_rating
maximum_stealth = scanner.rating + 2
maximum_level = scanner.rating + 5
/obj/item/extrapolator/attack(atom/AM, mob/living/user)
return
/obj/item/extrapolator/afterattack(atom/target, mob/user, proximity_flag, click_parameters)
. = ..()
if(!proximity_flag && !scan)
return
if(using)
to_chat(user, span_warning("[icon2html(src, user)] The extrapolator is already in use."))
return
if(!COOLDOWN_FINISHED(src, usage_cooldown))
to_chat(user, span_warning("[icon2html(src, user)] The extrapolator is still recharging!"))
return
if(scanner)
var/list/result = target?.extrapolator_act(user, src, dry_run = TRUE)
var/list/diseases = result && result[EXTRAPOLATOR_RESULT_DISEASES]
if(!length(diseases))
var/list/atom/targets = find_valid_targets(user, target)
var/target_amt = length(targets)
if(target_amt)
target = target_amt > 1 ? tgui_input_list(user, "Select object to analyze", "Viral Extrapolation", targets, default = targets[1]) : targets[1]
if(target)
result = target.extrapolator_act(user, src, dry_run = TRUE)
diseases = result && result[EXTRAPOLATOR_RESULT_DISEASES]
if(!target)
return
if(!length(diseases))
if(scan)
to_chat(user, span_notice("[icon2html(src, user)] \The [src] fails to return any data."))
else
to_chat(user, span_notice("[icon2html(src, user)] \The [src]'s probe detects no diseases."))
return
if(EXTRAPOLATOR_ACT_CHECK(result, EXTRAPOLATOR_ACT_PRIORITY_SPECIAL))
// extrapolator_act did some sort of special behavior, we don't need to do anything further
return
if(scan)
scan(user, target)
else
extrapolate(user, target)
else
to_chat(user, span_warning("The extrapolator has no scanner installed!"))
/obj/item/extrapolator/proc/find_valid_targets(mob/living/user, atom/target)
. = list()
var/turf/target_turf = get_turf(target)
if(!target_turf)
return
for(var/atom/target_to_try in target_turf.contents - target)
var/list/result = target_to_try.extrapolator_act(user, src, dry_run = TRUE)
if(length(result[EXTRAPOLATOR_RESULT_DISEASES]))
. += target_to_try
/obj/item/extrapolator/proc/scan(mob/living/user, atom/target)
. = TRUE
var/list/result = target?.extrapolator_act(user, target)
var/list/diseases = result[EXTRAPOLATOR_RESULT_DISEASES]
var/show_message = FALSE
if(!length(diseases))
return FALSE
if(EXTRAPOLATOR_ACT_CHECK(result, EXTRAPOLATOR_ACT_PRIORITY_SPECIAL))
return
var/list/message = list()
if(length(diseases))
message += span_boldnotice("[costly_icon2html(target, user)] [target] scan results")
message += span_boldnotice("[icon2html(src, user)] \The [src] detects the following diseases:")
for(var/datum/disease/disease in diseases)
if(istype(disease, /datum/disease/advance))
var/datum/disease/advance/advance_disease = disease
if(advance_disease.stealth >= maximum_stealth)
continue
show_message = TRUE
var/list/properties
if(global_flag_check(advance_disease.virus_modifiers, CARRIER))
LAZYADD(properties, "carrier")
message += "<b>[advance_disease.name]</b>[LAZYLEN(properties) ? " ([properties.Join(", ")])" : ""], stage [advance_disease.stage]/5"
if(extracted_ids[advance_disease.GetDiseaseID()])
message += "This virus has been extracted by \the [src] previously."
message += "[advance_disease.name] has the following symptoms:"
for(var/datum/symptom/symptom in advance_disease.symptoms)
message += "[symptom.name]"
else
message += span_info("<b>[disease.name]</b>, stage [disease.stage]/[disease.max_stages].")
if(show_message)
to_chat(user, examine_block(jointext(message, "\n")), avoid_highlighting = TRUE, trailing_newline = FALSE, type = MESSAGE_TYPE_INFO)
else
to_chat(user, span_notice("[icon2html(src, user)] \The [src] fails to return any data."))
/obj/item/extrapolator/proc/extrapolate(mob/living/user, atom/target, isolate = FALSE)
. = FALSE
var/list/result = target?.extrapolator_act(user, target)
var/list/diseases = result[EXTRAPOLATOR_RESULT_DISEASES]
if(!length(diseases))
return
if(EXTRAPOLATOR_ACT_CHECK(result, EXTRAPOLATOR_ACT_PRIORITY_SPECIAL)) // hardcoded "we handled this ourselves" response
return TRUE
if(EXTRAPOLATOR_ACT_CHECK(result, EXTRAPOLATOR_ACT_PRIORITY_ISOLATE))
isolate = TRUE
var/list/advance_diseases = list()
for(var/datum/disease/advance/candidate in diseases)
if(candidate.stealth >= maximum_stealth)
continue
advance_diseases += candidate
if(!length(advance_diseases))
to_chat(user, span_warning("[icon2html(src, user)] There are no valid diseases to make a culture from."))
return
var/datum/disease/advance/target_disease = length(advance_diseases) > 1 ? tgui_input_list(user, "Select disease to extract", "Viral Extraction", advance_diseases, default = advance_diseases[1]) : advance_diseases[1]
if(!target_disease)
return
using = TRUE
// I'll see about this...
// var/choice = tgui_alert(user, "What would you like to isolate?", "Isolate", list("Symptom", "Disease"))
// if(choice == "Symptom")
//. = isolate_symptom(user, target, target_disease)
// else
. = isolate_disease(user, target, target_disease)
using = FALSE
/obj/item/extrapolator/proc/isolate_symptom(mob/living/user, atom/target, datum/disease/advance/target_disease)
. = FALSE
var/list/symptoms = list()
for(var/datum/symptom/symptom in target_disease.symptoms)
if(symptom.level <= maximum_level)
symptoms += symptom
continue
if(!length(symptoms))
to_chat(user, span_warning("[icon2html(src, user)] There are no symptoms that could be isolated.."))
return
var/datum/symptom/chosen = length(symptoms) > 1 ? tgui_input_list(user, "Select symptom to isolate", "Symptom Extraction", symptoms, default = symptoms[1]) : symptoms[1]
if(!chosen)
return
user.visible_message(span_notice("[user] slots [target] into [src], which begins to whir and beep!"), span_notice("[icon2html(src, user)] You begin isolating " + span_bold("[chosen.name]") + " from [target]..."),)
var/datum/disease/advance/symptom_holder = new
symptom_holder.name = chosen.name
symptom_holder.symptoms += chosen
symptom_holder.Finalize()
symptom_holder.Refresh()
if(do_after(user, extract_time, target = target))
create_culture(user, symptom_holder, target)
return TRUE
/obj/item/extrapolator/proc/isolate_disease(mob/living/user, atom/target, datum/disease/advance/target_disease, timer = 10 SECONDS)
. = FALSE
user.visible_message(span_notice("[user] begins to thoroughly scan [target] with [src]..."), \
span_notice("[icon2html(src, user)] You begin isolating " + span_bold("[target_disease.name]") + " from [target]..."))
if(do_after(user, isolate_time, target = target))
create_culture(user, target_disease, target)
return TRUE
/obj/item/extrapolator/proc/create_culture(mob/living/user, datum/disease/advance/disease)
. = FALSE
var/datum/disease/advance/D = disease.Copy()
var/list/data = list("viruses" = list(disease))
if(user.get_active_hand() != src)
to_chat(user, span_warning("The extrapolator must be held in your active hand to work!"))
return
var/obj/item/reagent_containers/glass/beaker/vial/culture_bottle = new(user.drop_location())
culture_bottle.name = "[D.name] culture bottle"
culture_bottle.desc = "A small bottle. Contains [D.agent] culture in synthblood medium."
culture_bottle.reagents.add_reagent(REAGENT_ID_BLOOD, 5, data)
user.put_in_hands(culture_bottle)
playsound(src, 'sound/machines/ping.ogg', vol = 30, vary = TRUE)
COOLDOWN_START(src, usage_cooldown, 1 SECONDS)
extracted_ids[D.GetDiseaseID()] = TRUE
return TRUE
/obj/item/extrapolator/tier4
default_scanning_module = /obj/item/stock_parts/scanning_module/hyper
/obj/item/extrapolator/tier5
default_scanning_module = /obj/item/stock_parts/scanning_module/omni
@@ -99,7 +99,7 @@
dat += span_bold("Genetic Damage") + " - Utilize cryogenic pod with appropriate chemicals (i.e. Cryoxadone) and below 70 K, or give Rezadone.<br>"
if(bone)
dat += span_bold("Bone fracture") + " - Splint damaged area. Treat with bone repair surgery or Osteodaxon after treating brute damage.<br>"
if(M.viruses && M.viruses.len)
if(M.IsInfected())
for(var/datum/disease/D in M.GetViruses())
if(D.visibility_flags & HIDDEN_SCANNER)
continue
@@ -257,14 +257,11 @@
else
dat += span_warning("Unknown substance[(unknown > 1)?"s":""] found in subject's dermis.")
dat += "<br>"
if(LAZYLEN(C.viruses))
if(C.IsInfected())
for (var/datum/disease/virus in C.GetViruses())
if(virus.visibility_flags & HIDDEN_SCANNER || virus.visibility_flags & HIDDEN_PANDEMIC)
continue
virus.discovered = TRUE
dat += span_warning("Warning: [virus.name] detected in subject's blood.")
dat += "<br>"
dat += span_warning("Severity: [virus.severity]")
dat += span_alert(span_bold("Warning: [virus.form] detected in subject's blood."))
dat += "<br>"
if (M.getCloneLoss())
dat += span_warning("Subject appears to have been imperfectly cloned.")
@@ -247,7 +247,8 @@
/obj/item/sleevemate,
/obj/item/mass_spectrometer,
/obj/item/surgical,
/obj/item/clothing/mask/chewable/candy/lolli // VOREstation edit end
/obj/item/clothing/mask/chewable/candy/lolli,
/obj/item/extrapolator
)
/obj/item/storage/belt/medical/emt
+3 -3
View File
@@ -98,11 +98,11 @@
addtimer(VARSET_CALLBACK(src, icon_state, "holo_medical"), 10 SECONDS, TIMER_DELETE_ME)
/obj/structure/holosign/barrier/medical/proc/CheckHuman(mob/living/carbon/human/H)
if(istype(H.get_species(), SPECIES_XENOCHIMERA))
if(H.get_species() == SPECIES_XENOCHIMERA)
return FALSE
if(H.GetViruses())
for(var/datum/disease/D in H.GetViruses())
if(D.severity == NONTHREAT)
for(var/datum/disease/D in H.GetSpreadableViruses())
if(D.danger == DISEASE_POSITIVE || D.danger == DISEASE_BENEFICIAL)
continue
return FALSE
return TRUE