Skip to content
This repository was archived by the owner on May 22, 2025. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions code/__DEFINES/traits.dm
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,8 @@
#define TRAIT_SHIFTY_EYES "shifty_eyes"
#define TRAIT_ANXIOUS "anxious"
#define TRAIT_SEE_REAGENTS "see_reagents"
#define TRAIT_DRINKSBLOOD "drinks_blood"


// common trait sources
#define TRAIT_GENERIC "generic"
Expand Down
93 changes: 93 additions & 0 deletions code/datums/diseases/advance/advance.dm
Original file line number Diff line number Diff line change
Expand Up @@ -479,3 +479,96 @@

/datum/disease/advance/proc/totalTransmittable()
return properties["transmittable"]

/datum/disease/advance/proc/random_disease_name(var/atom/diseasesource)//generates a name for a disease depending on its symptoms and where it comes from
var/list/prefixes = list("Spacer's ", "Space ", "Infectious ","Viral ", "The ", "[pick(GLOB.first_names)]'s ", "[pick(GLOB.last_names)]'s ", "Acute ")//prefixes that arent tacked to the body need spaces after the word
var/list/bodies = list(pick("[pick(GLOB.first_names)]", "[pick(GLOB.last_names)]"), "Space", "Disease", "Noun", "Cold", "Germ", "Virus")
var/list/suffixes = list("ism", "itis", "osis", "itosis", " #[rand(1,10000)]", "-[rand(1,100)]", "s", "y", "ovirus", " Bug", " Infection", " Disease", " Complex", " Syndrome", " Sickness") //suffixes that arent tacked directly on need spaces before the word
if(stealth >=2)
prefixes += "Crypto "
switch(max(resistance - (symptoms.len / 2), 1))
if(1)
suffixes += "-alpha"
if(2)
suffixes += "-beta"
if(3)
suffixes += "-gamma"
if(4)
suffixes += "-delta"
if(5)
suffixes += "-epsilon"
if(6)
suffixes += pick("-zeta", "-eta", "-theta", "-iota")
if(7)
suffixes += pick("-kappa", "-lambda")
if(8)
suffixes += pick("-mu", "-nu", "-xi", "-omicron")
if(9)
suffixes += pick("-pi", "-rho", "-sigma", "-tau")
if(10)
suffixes += pick("-upsilon", "-phi", "-chi", "-psi")
if(11 to INFINITY)
suffixes += "-omega"
prefixes += "Robust "
switch(transmission - symptoms.len)
if(-INFINITY to 2)
prefixes += "Bloodborne "
if(3)
prefixes += list("Mucous ", "Kissing ")
if(4)
prefixes += "Contact "
suffixes += " Flu"
if(5 to INFINITY)
prefixes += "Airborne "
suffixes += " Plague"
switch(severity)
if(-INFINITY to 0)
prefixes += "Altruistic "
if(1 to 2)
prefixes += "Benign "
if(3 to 4)
prefixes += "Malignant "
if(5)
prefixes += "Terminal "
bodies += "Death"
if(6 to INFINITY)
prefixes += "Deadly "
bodies += "Death"
if(diseasesource)
if(ishuman(diseasesource))
var/mob/living/carbon/human/H = diseasesource
prefixes += pick("[H.first_name()]'s", "[H.name]'s", "[H.job]'s", "[H.dna.species]'s")
bodies += pick("[H.first_name()]", "[H.job]", "[H.dna.species]")
if(islizard(H) || iscatperson(H))//add rat-origin prefixes to races that eat rats
prefixes += list("Vermin ", "Zoo", "Maintenance ")
bodies += list("Rat", "Maint")
else switch(diseasesource.type)
if(/mob/living/simple_animal/pet/hamster/vector)
prefixes += list("Vector's ", "Hamster ")
bodies += list("Freebie")
if(/obj/effect/decal/cleanable)
prefixes += list("Bloody ", "Maintenance ")
bodies += list("Maint")
if(/mob/living/simple_animal/mouse)
prefixes += list("Vermin ", "Zoo", "Maintenance ")
bodies += list("Rat", "Maint")
if(/obj/item/reagent_containers/syringe)
prefixes += list("Junkie ", "Maintenance ")
bodies += list("Needle", "Maint")
if(/obj/item/fugu_gland)
prefixes += "Wumbo"
if(/obj/item/organ/lungs)
prefixes += "Miasmic "
bodies += list("Stench", "Lung")
for(var/datum/symptom/Symptom as() in symptoms)
if(!Symptom.neutered)
prefixes += Symptom.prefixes
bodies += Symptom.bodies
suffixes += Symptom.suffixes
switch(rand(1, 3))
if(1)
return "[pick(prefixes)][pick(bodies)]"
if(2)
return "[pick(prefixes)][pick(bodies)][pick(suffixes)]"
if(3)
return "[pick(bodies)][pick(suffixes)]"
8 changes: 8 additions & 0 deletions code/datums/diseases/advance/presets.dm
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@
symptoms = list(new/datum/symptom/tumor,new/datum/symptom/sneeze,new/datum/symptom/fever,new/datum/symptom/shivering,new/datum/symptom/itching,new/datum/symptom/cough)
..()

/datum/disease/advance/vampirism
copy_type = /datum/disease/advance

/datum/disease/advance/necropolis/New()
name = "Hematophagy"
symptoms = list(/datum/symptom/vampirism)
..()


//Randomly generated Disease, for virus crates and events
/datum/disease/advance/random
Expand Down
232 changes: 232 additions & 0 deletions code/datums/diseases/advance/symptoms/heal.dm
Original file line number Diff line number Diff line change
Expand Up @@ -492,3 +492,235 @@
if(L.heal_damage(heal_amt/parts.len, heal_amt/parts.len, null, BODYPART_ORGANIC))
M.update_damage_overlays()
return 1


/datum/symptom/vampirism
name = "Hematophagy"
desc = "The host absorbs blood from external sources, and seemlessly reintegrates it into their own bloodstream, regardless of its bloodtype or how it was ingested. However, the virus also slowly consumes the host's blood"
stealth = 1
resistance = -2
stage_speed = 1
transmittable = 2
level = 9
symptom_delay_min = 1
symptom_delay_max = 1
bodies = list("Blood")
var/bloodpoints = 0
var/maxbloodpoints = 50
var/bloodtypearchive
var/bruteheal = FALSE
var/aggression = FALSE
var/vampire = FALSE
var/mob/living/carbon/human/bloodbag
threshold_descs = list(
"Transmission 4" = "Host appears to die when falling into a coma.",
"Transmission 6" = "The virus aggressively assimilates blood, resulting in contiguous blood pools being absorbed by the virus, as well as sucking blood out of open wounds of subjects in physical contact with the host.",
"Stage Speed 7" = "The virus grows more aggressive, assimilating blood and healing at a faster rate, but also draining the host's blood quicker",
)




/datum/symptom/vampirism/Start(datum/disease/advance/A)
if(!..())
return
if(A.totalTransmittable() >= 4)
bruteheal = TRUE
if(A.totalTransmittable() >= 6)
aggression = TRUE
maxbloodpoints += 50
if(A.totalStageSpeed() >= 7)
power += 1
if((A.totalStealth() >= 2) && (A.totalTransmittable() >= 6) && A.process_dead) //this is low transmission for 2 reasons: transmission is hard to raise, especially with stealth, and i dont want this to be obligated to be transmittable
vampire = TRUE
maxbloodpoints += 50
power += 1
if(ishuman(A.affected_mob) && A.affected_mob.get_blood_id() == /datum/reagent/blood)
var/mob/living/carbon/human/H = A.affected_mob
bloodtypearchive = H.dna.blood_type
H.dna.blood_type = "U"

/datum/symptom/vampirism/Activate(datum/disease/advance/A)
if(!..())
return
var/mob/living/carbon/M = A.affected_mob
switch(A.stage)
if(1 to 4)
if(prob(5))
to_chat(M, "<span class='warning'>[pick("You feel a craving for iron?", "You feel very thirsty and theres a metallic taste in your mouth", "The thought of biting a neck crosses your mind...")]</span>")
if(5)
ADD_TRAIT(A.affected_mob, TRAIT_DRINKSBLOOD, DISEASE_TRAIT)
var/grabbedblood = succ(M) //before adding sucked blood to bloodpoints, immediately try to heal bloodloss
if(M.blood_volume < BLOOD_VOLUME_NORMAL && M.get_blood_id() == /datum/reagent/blood)
var/missing = BLOOD_VOLUME_NORMAL - M.blood_volume
var/inflated = grabbedblood * 4
M.blood_volume = min(M.blood_volume + inflated, BLOOD_VOLUME_NORMAL)
bloodpoints += round(max(0, (inflated - missing)/4))
else if((M.blood_volume >= BLOOD_VOLUME_NORMAL + 4) && (bloodpoints < maxbloodpoints))//so drinking blood accumulates bloodpoints
M.blood_volume = (M.blood_volume - 4)
bloodpoints += 1
else
bloodpoints += max(0, grabbedblood)
for(var/I in 1 to power)//power doesnt increase efficiency, just usage.
if(bloodpoints > 0)
if(ishuman(M))
var/mob/living/carbon/human/H = M
if(H.bleed_rate >= 2 && bruteheal && bloodpoints)
bloodpoints -= 1
H.bleed_rate = max(0, (H.bleed_rate - 2))
if(M.blood_volume < BLOOD_VOLUME_NORMAL && M.get_blood_id() == /datum/reagent/blood) //bloodloss is prioritized over healing brute
bloodpoints -= 1
M.blood_volume = max((M.blood_volume + 3 * power), BLOOD_VOLUME_NORMAL) //bloodpoints are valued at 4 units of blood volume per point, so this is diminished
else if(bruteheal && M.getBruteLoss())
bloodpoints -= 1
M.heal_overall_damage(2, required_status = BODYTYPE_ORGANIC)
if(prob(60) && !M.stat)
bloodpoints -- //you cant just accumulate blood and keep it as a battery of healing. the quicker the symptom is, the faster your bloodpoints decay
else if(prob(20) && M.blood_volume >= BLOOD_VOLUME_BAD)//the virus continues to extract blood if you dont have any stored up. higher probability due to BP value
M.blood_volume = (M.blood_volume - 1)

if(!bloodpoints && prob(3))
to_chat(M, "<span class='warning'>[pick("You need blood!".", "Your throat is dry...", "Your heart flutters alarmingly...")]</span>")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
to_chat(M, "<span class='warning'>[pick("You need blood!".", "Your throat is dry...", "Your heart flutters alarmingly...")]</span>")
to_chat(M, span_warning("[pick("You need blood!", "Your throat is dry...", "Your heart flutters alarmingly...")]"))


/datum/symptom/vampirism/End(datum/disease/advance/A)
. = ..()
REMOVE_TRAIT(A.affected_mob, TRAIT_DRINKSBLOOD, DISEASE_TRAIT)
if(bloodtypearchive && ishuman(A.affected_mob))
var/mob/living/carbon/human/H = A.affected_mob
H.dna.blood_type = bloodtypearchive

/datum/symptom/vampirism/proc/succ(mob/living/carbon/M) //you dont need the blood reagent to suck blood. however, you need to have blood, or at least a shared blood reagent, for most of the other uses
var/gainedpoints = 0
if(bloodbag && !bloodbag.blood_volume) //we've exsanguinated them!
bloodbag = null
if(ishuman(M) && M.stat == DEAD && vampire)
var/mob/living/carbon/human/H = M
var/possibledist = power + 1
if(M.get_blood_id() != /datum/reagent/blood)
possibledist = 1
if(!(NOBLOOD in H.dna.species.species_traits)) //if you dont have blood, well... sucks to be you
H.setOxyLoss(0,0) //this is so a crit person still revives if suffocated
if(bloodpoints >= 200 && H.health > 0 && H.blood_volume >= BLOOD_VOLUME_NORMAL) //note that you need to actually need to heal, so a maxed out virus won't be bringing you back instantly in most cases. *even so*, if this needs to be nerfed ill do it in a heartbeat
H.revive(0)
H.visible_message("<span class='warning'>[H.name]'s skin takes on a rosy hue as they begin moving. They live again!</span>", "<span class='userdanger'>As your body fills with fresh blood, you feel your limbs once more, accompanied by an insatiable thirst for blood.</span>")
bloodpoints = 0
return 0
else if(bloodbag && bloodbag.blood_volume && (bloodbag.stat || bloodbag.bleed_rate))
if(get_dist(bloodbag, H) <= 1 && bloodbag.z == H.z)
var/amt = ((bloodbag.stat * 2) + 2) * power
var/excess = max(((min(amt, bloodbag.blood_volume) - (BLOOD_VOLUME_NORMAL - H.blood_volume)) / 2), 0)
H.blood_volume = min(H.blood_volume + min(amt, bloodbag.blood_volume), BLOOD_VOLUME_NORMAL)
bloodbag.blood_volume = max(bloodbag.blood_volume - amt, 0)
bloodpoints += max(excess, 0)
playsound(bloodbag.loc, 'sound/magic/exit_blood.ogg', 10, 1)
bloodbag.visible_message("<span class='warning'>Blood flows from [bloodbag.name]'s wounds into [H.name]'s corpse!</span>", "<span class='userdanger'>Blood flows from your wounds into [H.name]'s corpse!</span>")
else if(get_dist(bloodbag, H) >= possibledist) //they've been taken out of range.
bloodbag = null
return
else if(bloodpoints >= 2)
var/turf/T = H.loc
var/obj/effect/decal/cleanable/blood/influenceone = (locate(/obj/effect/decal/cleanable/blood) in H.loc)
if(!influenceone && bloodpoints >= 2)
H.add_splatter_floor(T)
playsound(T, 'sound/effects/splat.ogg', 50, 1)
bloodpoints -= 2
return 0
else
var/todir = get_dir(H, bloodbag)
var/targetloc = bloodbag.loc
var/dist = get_dist(H, bloodbag)
for(var/i=0 to dist)
T = get_step(T, todir)
todir = get_dir(T, bloodbag)
var/obj/effect/decal/cleanable/blood/influence = (locate(/obj/effect/decal/cleanable/blood) in T)
if(!influence && bloodpoints >= 2)
H.add_splatter_floor(T)
playsound(T, 'sound/effects/splat.ogg', 50, 1)
bloodpoints -= 2
return 0
else if(T == targetloc && bloodpoints >= 2)
bloodbag.throw_at(H, 1, 1)
bloodpoints -= 2
bloodbag.visible_message("<span class='warning'>A current of blood pushes [bloodbag.name] towards [H.name]'s corpse!</span>")
playsound(bloodbag.loc, 'sound/magic/exit_blood.ogg', 25, 1)
return 0
else
var/list/candidates = list()
for(var/mob/living/carbon/human/C in ohearers(min(bloodpoints/4, possibledist), H))
if(NOBLOOD in C.dna.species.species_traits)
continue
if(C.stat && C.blood_volume && C.get_blood_id() == H.get_blood_id())
candidates += C
for(var/prospect in candidates)
candidates[prospect] = 1
if(ishuman(prospect))
var/mob/living/carbon/human/candidate = prospect
candidates[prospect] += (candidate.stat - 1)
candidates[prospect] += (3 - get_dist(candidate, H)) * 2
candidates[prospect] += round(candidate.blood_volume / 150)
bloodbag = pickweight(candidates) //dont return here

if(bloodpoints >= maxbloodpoints)
return 0
if(ishuman(M) && aggression) //first, try to suck those the host is actively grabbing
var/mob/living/carbon/human/H = M
if(H.pulling && ishuman(H.pulling)) //grabbing is handled with the disease instead of the component, so the component doesn't have to be processed
var/mob/living/carbon/human/C = H.pulling
if(!C.bleed_rate && vampire && C.can_inject() && H.grab_state && C.get_blood_id() == H.get_blood_id() && !(NOBLOOD in C.dna.species.species_traits))//aggressive grab as a "vampire" starts the target bleeding
C.bleed_rate += 1
C.visible_message("<span class='warning'>Wounds open on [C.name]'s skin as [H.name] grips them tightly!</span>", "<span class='userdanger'>You begin bleeding at [H.name]'s touch!</span>")
if(C.blood_volume && C.can_inject() &&(C.bleed_rate && (!C.bleedsuppress || vampire )) && C.get_blood_id() == H.get_blood_id() && !(NOBLOOD in C.dna.species.species_traits))
var/amt = (H.grab_state + C.stat + 2) * power
if(C.blood_volume)
var/excess = max(((min(amt, C.blood_volume) - (BLOOD_VOLUME_NORMAL - H.blood_volume)) / 4), 0)
H.blood_volume = min(H.blood_volume + min(amt, C.blood_volume), BLOOD_VOLUME_NORMAL)
C.blood_volume = max(C.blood_volume - amt, 0)
gainedpoints = CLAMP(excess, 0, maxbloodpoints - bloodpoints)
C.visible_message("<span class='warning'>Blood flows from [C.name]'s wounds into [H.name]!</span>", "<span class='userdanger'>Blood flows from your wounds into [H.name]!</span>")
playsound(C.loc, 'sound/magic/exit_blood.ogg', 25, 1)
return gainedpoints
if(locate(/obj/effect/decal/cleanable/blood) in M.loc)
var/obj/effect/decal/cleanable/blood/initialstain = (locate(/obj/effect/decal/cleanable/blood) in M.loc)
var/list/stains = list()
var/suckamt = power + 1
if(aggression)
for(var/obj/effect/decal/cleanable/blood/contiguousstain in orange(1, M))
if(suckamt)
suckamt --
stains += contiguousstain
if(suckamt)
suckamt --
stains += initialstain
for(var/obj/effect/decal/cleanable/blood/stain in stains) //this doesnt use switch(type) because that doesnt check subtypes
if(istype(stain, /obj/effect/decal/cleanable/blood/gibs/old))
gainedpoints += 3
qdel(stain)
else if(istype(stain, /obj/effect/decal/cleanable/blood/old))
gainedpoints += 1
qdel(stain)
else if(istype(stain, /obj/effect/decal/cleanable/blood/gibs))
gainedpoints += 5
qdel(stain)
else if(istype(stain, /obj/effect/decal/cleanable/blood/footprints) || istype(stain, /obj/effect/decal/cleanable/blood/tracks) || istype(stain, /obj/effect/decal/cleanable/blood/drip))
qdel(stain)//these types of stain are generally very easy to make, we don't use these
else if(istype(stain, /obj/effect/decal/cleanable/blood))
gainedpoints += 2
qdel(stain)
if(gainedpoints)
playsound(M.loc, 'sound/magic/exit_blood.ogg', 50, 1)
M.visible_message("<span class='warning'>Blood flows from the floor into [M.name]!</span>", "<span class='warning'>You consume the errant blood</span>")
return CLAMP(gainedpoints, 0, maxbloodpoints - bloodpoints)
if(ishuman(M) && aggression)//finally, attack mobs touching the host.
var/mob/living/carbon/human/H = M
for(var/mob/living/carbon/human/C in ohearers(1, H))
if(NOBLOOD in C.dna.species.species_traits)
continue
if((C.pulling && C.pulling == H) || (C.loc == H.loc) && C.bleed_rate && C.get_blood_id() == H.get_blood_id())
var/amt = (2 * power)
if(C.blood_volume)
var/excess = max(((min(amt, C.blood_volume) - (BLOOD_VOLUME_NORMAL - H.blood_volume)) / 4 * power), 0)
H.blood_volume = min(H.blood_volume + min(amt, C.blood_volume), BLOOD_VOLUME_NORMAL)
C.blood_volume = max(C.blood_volume - amt, 0)
gainedpoints += CLAMP(excess, 0, maxbloodpoints - bloodpoints)
C.visible_message("<span class='warning'>Blood flows from [C.name]'s wounds into [H.name]!</span>", "<span class='userdanger'>Blood flows from your wounds into [H.name]!</span>")
return CLAMP(gainedpoints, 0, maxbloodpoints - bloodpoints)
5 changes: 5 additions & 0 deletions code/modules/reagents/reagent_containers/bottle.dm
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,11 @@
icon_state = "mortar"
spawned_disease = /datum/disease/advance/necropolis

/obj/item/reagent_containers/glass/bottle/vampirism
name = "Hematophagy culture bottle"
desc = "A small bottle. Contains a sample of Hematophagy."
spawned_disease = /datum/disease/advance/vampirism

//Oldstation.dmm chemical storage bottles

/obj/item/reagent_containers/glass/bottle/hydrogen
Expand Down
8 changes: 8 additions & 0 deletions code/modules/uplink/uplink_items.dm
Original file line number Diff line number Diff line change
Expand Up @@ -1940,6 +1940,14 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
restricted_roles = list("Assistant")
surplus = 0

/datum/uplink_item/role_restricted/necroseed
name = "The Vampire Virus (should this be the name in the uplink)"
desc = "PLACEHOLDEr."
item = /obj/item/reagent_containers/glass/bottle/vampirism
cost = 6
restricted_roles = list("Virologist")
surplus = 0

/datum/uplink_item/role_restricted/oldtoolboxclean
name = "Ancient Toolbox"
desc = "An iconic toolbox design notorious with Assistants everywhere, this design was especially made to become more robust the more telecrystals it has inside it! Tools and insulated gloves included."
Expand Down