From bc0c8344cf2e00b46ec45f2e10daa4be282a5118 Mon Sep 17 00:00:00 2001
From: Contrabang <91113370+Contrabang@users.noreply.github.com>
Date: Thu, 30 May 2024 22:17:31 -0400
Subject: [PATCH] Adds Zombies (Admin-spawn) (#25381)
* fuck it good enough
* Apply suggestions from code review
Co-authored-by: Burzah <116982774+Burzah@users.noreply.github.com>
Signed-off-by: Contrabang <91113370+Contrabang@users.noreply.github.com>
* TRIPLE-REVIEW
* this should do it
* wow
* bruh
* FIX
* fix
* yeah
* sirryan review
* aaaaa
* Apply suggestions from code review
Co-authored-by: DGamerL <108773801+DGamerL@users.noreply.github.com>
Signed-off-by: Contrabang <91113370+Contrabang@users.noreply.github.com>
---------
Signed-off-by: Contrabang <91113370+Contrabang@users.noreply.github.com>
Co-authored-by: Burzah <116982774+Burzah@users.noreply.github.com>
Co-authored-by: DGamerL <108773801+DGamerL@users.noreply.github.com>
---
code/__DEFINES/color_defines.dm | 3 +
code/__DEFINES/gamemode.dm | 1 +
code/__DEFINES/hud.dm | 1 +
code/__DEFINES/surgery_defines.dm | 6 +-
code/__HELPERS/trait_helpers.dm | 10 +-
code/_globalvars/traits.dm | 7 +-
code/_onclick/click.dm | 2 +-
code/datums/atom_hud.dm | 3 +-
code/datums/components/zombie_regen.dm | 155 ++++++++++++++
code/datums/datumvars.dm | 4 +-
code/datums/diseases/_MobProcs.dm | 4 +-
code/datums/diseases/_disease.dm | 25 ++-
code/datums/diseases/zombie_virus.dm | 141 +++++++++++++
code/datums/emote.dm | 9 +-
code/datums/mind.dm | 51 +++++
code/game/data_huds.dm | 6 +-
code/game/gamemodes/objective.dm | 5 +
code/game/objects/items.dm | 2 +-
.../objects/items/stacks/medical_packs.dm | 2 +-
code/game/objects/items/weapons/manuals.dm | 156 ++++++++++++++
.../objects/items/weapons/storage/fancy.dm | 22 +-
.../objects/items/weapons/storage/lockbox.dm | 22 +-
code/game/objects/items/weapons/whetstone.dm | 2 +-
.../vampire/vampire_powers/vampire_powers.dm | 8 +
.../antagonists/zombie/datum_zombie.dm | 87 ++++++++
.../antagonists/zombie/zombie_spells.dm | 150 +++++++++++++
code/modules/martial_arts/martial.dm | 2 +-
code/modules/mob/inventory_procs.dm | 2 +
code/modules/mob/language.dm | 38 +++-
.../carbon/alien/special/alien_embryo.dm | 1 +
code/modules/mob/living/carbon/carbon_life.dm | 2 +-
code/modules/mob/living/carbon/examine.dm | 2 +
.../mob/living/carbon/human/human_damage.dm | 2 +-
.../mob/living/carbon/human/human_defense.dm | 3 +-
.../mob/living/carbon/human/human_emote.dm | 4 +-
.../mob/living/carbon/human/human_examine.dm | 2 +-
.../mob/living/carbon/human/human_mob.dm | 10 +
.../mob/living/carbon/human/human_organs.dm | 2 +
.../living/carbon/human/human_update_icons.dm | 55 +++--
.../living/carbon/human/species/_species.dm | 197 +++++++++++++-----
code/modules/mob/living/living_emote.dm | 2 +-
code/modules/mob/mob_grab.dm | 3 +
code/modules/paperwork/paper.dm | 44 ++++
.../modules/projectiles/projectile/bullets.dm | 5 +
.../reagents/chemistry/chemical_reaction.dm | 2 +
.../chemistry/reagents/food_reagents.dm | 6 +-
.../reagents/chemistry/reagents/medicine.dm | 47 +++++
.../reagents/chemistry/reagents_holder.dm | 2 +
.../reagents/chemistry/recipes/others.dm | 95 +++++++++
code/modules/surgery/organs/brain.dm | 2 +-
code/modules/surgery/organs/organ.dm | 2 +-
code/modules/surgery/organs/organ_external.dm | 14 +-
code/modules/surgery/organs/organ_icon.dm | 4 +-
code/modules/surgery/organs/organ_internal.dm | 2 +-
.../organs/subtypes/standard_organs.dm | 2 +-
icons/mob/hud/antaghud.dmi | Bin 1894 -> 1917 bytes
interface/skin.dmf | 2 +-
paradise.dme | 4 +
sound/voice/zombie_scream.ogg | Bin 0 -> 33164 bytes
.../tgui-panel/styles/tgchat/chat-dark.scss | 14 +-
.../tgui-panel/styles/tgchat/chat-light.scss | 14 +-
tgui/public/tgui-panel.bundle.css | 2 +-
62 files changed, 1326 insertions(+), 148 deletions(-)
create mode 100644 code/datums/components/zombie_regen.dm
create mode 100644 code/datums/diseases/zombie_virus.dm
create mode 100644 code/modules/antagonists/zombie/datum_zombie.dm
create mode 100644 code/modules/antagonists/zombie/zombie_spells.dm
create mode 100644 sound/voice/zombie_scream.ogg
diff --git a/code/__DEFINES/color_defines.dm b/code/__DEFINES/color_defines.dm
index 2f8c847c66e..0e24a46e279 100644
--- a/code/__DEFINES/color_defines.dm
+++ b/code/__DEFINES/color_defines.dm
@@ -153,3 +153,6 @@
#define COMPLEMENTARY_COLOR_CRYOGENIC_LIQUID "#a8b7df"
#define COMPLEMENTARY_COLOR_SORIUM "#a2a256"
#define COMPLEMENTARY_COLOR_TESLIUM_PASTE "#412968"
+
+/// Color for dead external organs/zombies
+#define COLORTONE_DEAD_EXT_ORGAN "#0A3200"
diff --git a/code/__DEFINES/gamemode.dm b/code/__DEFINES/gamemode.dm
index f0ec9ff81e3..fbc7c2b9113 100644
--- a/code/__DEFINES/gamemode.dm
+++ b/code/__DEFINES/gamemode.dm
@@ -54,6 +54,7 @@
#define SPECIAL_ROLE_XENOMORPH_DRONE "Xenomorph Drone"
#define SPECIAL_ROLE_XENOMORPH_SENTINEL "Xenomorph Sentinel"
#define SPECIAL_ROLE_XENOMORPH_LARVA "Xenomorph Larva"
+#define SPECIAL_ROLE_ZOMBIE "Zombie"
#define SPECIAL_ROLE_EVENTMISC "Event Role"
// Constants used by code which checks the status of nuclear blasts during a
diff --git a/code/__DEFINES/hud.dm b/code/__DEFINES/hud.dm
index 8b1670845eb..159c8a1bebd 100644
--- a/code/__DEFINES/hud.dm
+++ b/code/__DEFINES/hud.dm
@@ -56,6 +56,7 @@
#define DATA_HUD_ABDUCTOR 19
#define ANTAG_HUD_EVENTMISC 20
#define ANTAG_HUD_BLOB 21
+#define ANTAG_HUD_ZOMBIE 22
// Notification action types
#define NOTIFY_JUMP "jump"
diff --git a/code/__DEFINES/surgery_defines.dm b/code/__DEFINES/surgery_defines.dm
index 19f7894d838..7f56ab04da1 100644
--- a/code/__DEFINES/surgery_defines.dm
+++ b/code/__DEFINES/surgery_defines.dm
@@ -26,11 +26,13 @@
#define ORGAN_ORGANIC_OPEN 1
/// An organ that's encased, probably with bone, where that casing has been cut through.
#define ORGAN_ORGANIC_ENCASED_OPEN 2
+/// An organ that has been violently opened, likely via damage.
+#define ORGAN_ORGANIC_VIOLENT_OPEN 3
/// Synthetic organ that's been unscrewed.
-#define ORGAN_SYNTHETIC_LOOSENED 3
+#define ORGAN_SYNTHETIC_LOOSENED 4
/// Synthetic organ that's had its panel opened.
-#define ORGAN_SYNTHETIC_OPEN 4
+#define ORGAN_SYNTHETIC_OPEN 5
// Return defines for surgery steps
diff --git a/code/__HELPERS/trait_helpers.dm b/code/__HELPERS/trait_helpers.dm
index c418ede6a15..3e9cb31305d 100644
--- a/code/__HELPERS/trait_helpers.dm
+++ b/code/__HELPERS/trait_helpers.dm
@@ -150,8 +150,9 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_PACIFISM "pacifism"
#define TRAIT_IGNORESLOWDOWN "ignoreslow"
#define TRAIT_IGNOREDAMAGESLOWDOWN "ignoredamageslowdown"
-#define TRAIT_GOTTAGOFAST "gottagofast"
-#define TRAIT_GOTTAGONOTSOFAST "gottagonotsofast"
+#define TRAIT_GOTTAGOFAST "gottagofast" // -1 slowdown. Trait given by meth and similar substances for running fast.
+#define TRAIT_GOTTAGONOTSOFAST "gottagonotsofast" // -0.5 slowdown. Trait given by nuka cola and umbrae shade, for a slight speed/
+#define TRAIT_GOTTAGOSLOW "gottagoslow" // +1 slowdown. Trait given by being a zombie.
#define TRAIT_FAKEDEATH "fakedeath" //Makes the owner appear as dead to most forms of medical examination
#define TRAIT_XENO_HOST "xeno_host" //Tracks whether we're gonna be a baby alien's mummy.
#define TRAIT_SHOCKIMMUNE "shock_immunity"
@@ -227,6 +228,10 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_MEPHEDRONE_ADAPTED "mephedrone_adapted" // Trait that changes the ending effects of twitch leaving your system
#define TRAIT_NOKNOCKDOWNSLOWDOWN "noknockdownslowdown" //If this person has this trait, they are not slowed via knockdown, but they can be hit by bullets like a self knockdown
#define TRAIT_CAN_STRIP "can_strip" // This mob can strip other mobs.
+#define TRAIT_I_WANT_BRAINS "mob_zombie" // A general trait for tracking if the mob is a zombie.
+#define TRAIT_ABSTRACT_HANDS "abstract_hands" // Mobs with this trait can only pick up abstract items.
+#define TRAIT_SLOW_GRABBER "slow_grabber" // Adds a 1.5 * CLICK_CD_MELEE delay to upgrading into a aggressive grab.
+#define TRAIT_LANGUAGE_LOCKED "language_locked" // cant add/remove languages until removed (excludes babel because fuck everything i guess)
//***** MIND TRAITS *****/
#define TRAIT_HOLY "is_holy" // The mob is holy in regards to religion
@@ -299,6 +304,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define CULT_TRAIT "cult"
#define INNATE_TRAIT "innate"
#define VAMPIRE_TRAIT "vampire"
+#define ZOMBIE_TRAIT "zombie"
#define CHANGELING_TRAIT "changeling"
#define LYING_DOWN_TRAIT "lying_down"
#define SLIME_TRAIT "slime"
diff --git a/code/_globalvars/traits.dm b/code/_globalvars/traits.dm
index 3d3cf117030..69f976fca95 100644
--- a/code/_globalvars/traits.dm
+++ b/code/_globalvars/traits.dm
@@ -20,6 +20,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_IGNOREDAMAGESLOWDOWN" = TRAIT_IGNOREDAMAGESLOWDOWN,
"TRAIT_GOTTAGOFAST" = TRAIT_GOTTAGOFAST,
"TRAIT_GOTTAGONOTSOFAST" = TRAIT_GOTTAGONOTSOFAST,
+ "TRAIT_GOTTAGOSLOW" = TRAIT_GOTTAGOSLOW,
"TRAIT_FAKEDEATH" = TRAIT_FAKEDEATH,
"TRAIT_XENO_HOST" = TRAIT_XENO_HOST,
"TRAIT_SHOCKIMMUNE" = TRAIT_SHOCKIMMUNE,
@@ -90,7 +91,11 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_SCOPED" = TRAIT_SCOPED,
"TRAIT_MEPHEDRONE_ADAPTED" = TRAIT_MEPHEDRONE_ADAPTED,
"TRAIT_NOKNOCKDOWNSLOWDOWN" = TRAIT_NOKNOCKDOWNSLOWDOWN,
- "TRAIT_CAN_STRIP" = TRAIT_CAN_STRIP
+ "TRAIT_CAN_STRIP" = TRAIT_CAN_STRIP,
+ "TRAIT_I_WANT_BRAINS" = TRAIT_I_WANT_BRAINS,
+ "TRAIT_ABSTRACT_HANDS" = TRAIT_ABSTRACT_HANDS,
+ "TRAIT_SLOW_GRABBER" = TRAIT_SLOW_GRABBER,
+ "TRAIT_LANGUAGE_LOCKED" = TRAIT_LANGUAGE_LOCKED,
),
/datum/mind = list(
diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm
index ba631301ef2..8db2ec0a68d 100644
--- a/code/_onclick/click.dm
+++ b/code/_onclick/click.dm
@@ -51,7 +51,7 @@
if(QDELETED(A))
return
- if(client.click_intercept)
+ if(client?.click_intercept)
client.click_intercept.InterceptClickOn(src, params, A)
return
diff --git a/code/datums/atom_hud.dm b/code/datums/atom_hud.dm
index e79861e5877..90b777c04d8 100644
--- a/code/datums/atom_hud.dm
+++ b/code/datums/atom_hud.dm
@@ -23,7 +23,8 @@ GLOBAL_LIST_INIT(huds, list(
ANTAG_HUD_ABDUCTOR = new/datum/atom_hud/antag/hidden(),
DATA_HUD_ABDUCTOR = new/datum/atom_hud/abductor(),
ANTAG_HUD_EVENTMISC = new/datum/atom_hud/antag/hidden(),
- ANTAG_HUD_BLOB = new/datum/atom_hud/antag/hidden()
+ ANTAG_HUD_BLOB = new/datum/atom_hud/antag/hidden(),
+ ANTAG_HUD_ZOMBIE = new/datum/atom_hud/antag()
))
/datum/atom_hud
diff --git a/code/datums/components/zombie_regen.dm b/code/datums/components/zombie_regen.dm
new file mode 100644
index 00000000000..416f3deffa7
--- /dev/null
+++ b/code/datums/components/zombie_regen.dm
@@ -0,0 +1,155 @@
+
+/datum/component/zombie_regen/Initialize()
+ if(!ishuman(parent))
+ return COMPONENT_INCOMPATIBLE
+ START_PROCESSING(SSprocessing, src)
+
+/datum/component/zombie_regen/Destroy(force, silent)
+ STOP_PROCESSING(SSprocessing, src)
+ return ..()
+
+/datum/component/zombie_regen/process()
+ if(!ishuman(parent))
+ return
+ var/mob/living/carbon/human/zomboid = parent
+ if(zomboid.suiciding)
+ return
+ var/turf/current_turf = get_turf(zomboid)
+ var/healing_factor = max(1, 6 * (1 - current_turf.get_lumcount()))
+ if(zomboid.reagents.has_reagent("zombiecure3"))
+ healing_factor /= 3
+ if(zomboid.stat == DEAD)
+ healing_factor *= 2
+ zomboid.heal_overall_damage(healing_factor, healing_factor)
+ zomboid.adjustBrainLoss(-healing_factor)
+ if(zomboid.stat == DEAD && zomboid.getBruteLoss() <= 1 && zomboid.getFireLoss() <= 1 && (zomboid.timeofdeath + 15 SECONDS <= world.time))
+ var/datum/reagent/the_cure = zomboid.reagents.has_reagent("zombiecure4")
+ if(the_cure) // dead bodies dont process chemicals, so we gotta do it manually.
+ zomboid.reagents.remove_reagent("zombiecure4", the_cure.metabolization_rate * zomboid.metabolism_efficiency)
+ return
+ zombie_rejuv()
+ to_chat(zomboid, "We... Awaken...")
+
+ // If no client, but they were a player thats not SSD (debrained, revived but hasn't returned to body, etc)
+ if(zomboid.stat != CONSCIOUS || HAS_TRAIT(zomboid, TRAIT_HANDS_BLOCKED))
+ return
+ if(zomboid.client || isLivingSSD(zomboid))
+ return
+ if(zomboid.last_known_ckey && !zomboid.key) // make sure they were player inhabited and not admin ghosted
+ mindless_hunger()
+
+/datum/component/zombie_regen/proc/zombie_rejuv()
+ var/mob/living/carbon/human/zomboid = parent
+ zomboid.setToxLoss(0)
+ zomboid.setOxyLoss(0)
+ zomboid.setCloneLoss(0)
+ zomboid.setBrainLoss(0)
+ zomboid.setStaminaLoss(0)
+ zomboid.SetSleeping(0)
+ zomboid.SetParalysis(0, TRUE)
+ zomboid.SetStunned(0, TRUE)
+ zomboid.SetWeakened(0, TRUE)
+ zomboid.SetSlowed(0)
+ zomboid.SetImmobilized(0)
+ zomboid.SetKnockDown(0)
+ zomboid.SetLoseBreath(0)
+ zomboid.SetDizzy(0)
+ zomboid.SetJitter(0)
+ zomboid.SetStuttering(0)
+ zomboid.SetConfused(0)
+ zomboid.SetDrowsy(0)
+ zomboid.radiation = 0
+ zomboid.SetDruggy(0)
+ zomboid.SetHallucinate(0)
+ zomboid.bodytemperature = 310
+ zomboid.cure_blind()
+ zomboid.cure_nearsighted()
+ zomboid.CureMute()
+ zomboid.CureDeaf()
+ zomboid.CureTourettes()
+ zomboid.CureEpilepsy()
+ zomboid.CureCoughing()
+ zomboid.CureNervous()
+ zomboid.SetEyeBlind(0)
+ zomboid.SetEyeBlurry(0)
+ zomboid.SetDeaf(0)
+ zomboid.heal_overall_damage(1000, 1000)
+ zomboid.ExtinguishMob()
+ SEND_SIGNAL(zomboid, COMSIG_LIVING_CLEAR_STUNS)
+ zomboid.fire_stacks = 0
+ zomboid.on_fire = 0
+ zomboid.suiciding = 0
+ zomboid.set_nutrition(max(zomboid.nutrition, NUTRITION_LEVEL_HUNGRY))
+ if(zomboid.buckled) //Unbuckle the mob and clear the alerts.
+ zomboid.buckled.unbuckle_mob(src, force = TRUE)
+
+ var/datum/organ/heart/heart = zomboid.get_int_organ_datum(ORGAN_DATUM_HEART)
+ var/heart_type = zomboid.dna?.species?.has_organ["heart"]
+ if(!heart && heart_type)
+ var/obj/item/organ/internal/new_heart = new heart_type()
+ new_heart.insert(zomboid)
+
+ var/datum/organ/lungs/lungs = zomboid.get_int_organ_datum(ORGAN_DATUM_LUNGS)
+ var/lung_type = zomboid.dna?.species?.has_organ["lungs"]
+ if(!lungs && lung_type)
+ var/obj/item/organ/internal/new_lungs = new lung_type()
+ new_lungs.insert(zomboid)
+
+ zomboid.set_heartattack(FALSE)
+ zomboid.restore_blood()
+ zomboid.decaylevel = 0
+ zomboid.remove_all_embedded_objects()
+
+ zomboid.restore_all_organs()
+ if(zomboid.stat == DEAD)
+ zomboid.update_revive()
+ else if(zomboid.stat == UNCONSCIOUS)
+ zomboid.WakeUp()
+
+ zomboid.update_fire()
+ zomboid.regenerate_icons()
+ zomboid.restore_blood()
+ zomboid.update_eyes()
+
+ for(var/datum/disease/critical/crit in zomboid.viruses) // cure all crit conditions
+ crit.cure()
+
+/datum/component/zombie_regen/proc/mindless_hunger()
+ var/mob/living/carbon/human/zomboid = parent
+ var/list/targets = list()
+ for(var/mob/living/carbon/human/target in view(6, zomboid))
+ if(target.stat == CONSCIOUS && !HAS_TRAIT(target, TRAIT_I_WANT_BRAINS))
+ targets |= target
+ if(zomboid.Adjacent(target))
+ break // we're just gonna hit em
+
+ var/target
+ if(length(targets))
+ target = pick(targets)
+
+ if(zomboid.Adjacent(target) && safe_active_hand(zomboid))
+ zomboid.a_intent = INTENT_HARM
+ zomboid.ClickOn(target)
+ return
+
+ if(!target && prob(90)) // a small chance to wander
+ return
+
+ var/targetted_direction = pick(GLOB.cardinal)
+ if(target)
+ targetted_direction = get_dir(zomboid, target)
+
+ var/delay = zomboid.movement_delay()
+ if(IS_DIR_DIAGONAL(targetted_direction))
+ delay *= SQRT_2
+ zomboid.glide_for(delay)
+ step(zomboid, targetted_direction)
+
+/datum/component/zombie_regen/proc/safe_active_hand(mob/living/carbon/human/zomboid)
+ if(zomboid.get_organ("[zomboid.hand ? "l" : "r" ]_hand"))
+ return TRUE
+ zomboid.swap_hand()
+ if(zomboid.get_organ("[zomboid.hand ? "l" : "r" ]_hand"))
+ return TRUE
+ return FALSE
+
diff --git a/code/datums/datumvars.dm b/code/datums/datumvars.dm
index 6adc70e2d64..95f53268c90 100644
--- a/code/datums/datumvars.dm
+++ b/code/datums/datumvars.dm
@@ -1045,7 +1045,7 @@
to_chat(usr, "Mob doesn't exist anymore")
return
- if(H.add_language(new_language))
+ if(H.add_language(new_language, TRUE))
to_chat(usr, "Added [new_language] to [H].")
message_admins("[key_name_admin(usr)] has given [key_name_admin(H)] the language [new_language]")
log_admin("[key_name(usr)] has given [key_name(H)] the language [new_language]")
@@ -1073,7 +1073,7 @@
to_chat(usr, "Mob doesn't exist anymore")
return
- if(H.remove_language(rem_language.name))
+ if(H.remove_language(rem_language.name, TRUE))
to_chat(usr, "Removed [rem_language] from [H].")
message_admins("[key_name_admin(usr)] has removed language [rem_language] from [key_name_admin(H)]")
log_admin("[key_name(usr)] has removed language [rem_language] from [key_name(H)]")
diff --git a/code/datums/diseases/_MobProcs.dm b/code/datums/diseases/_MobProcs.dm
index 91873cf56a6..24ed6a56aec 100644
--- a/code/datums/diseases/_MobProcs.dm
+++ b/code/datums/diseases/_MobProcs.dm
@@ -8,7 +8,7 @@
/mob/proc/CanContractDisease(datum/disease/D)
- if(stat == DEAD)
+ if(stat == DEAD && !D.allow_dead)
return FALSE
if(D.GetDiseaseID() in resistances)
@@ -30,6 +30,7 @@
if(!CanContractDisease(D))
return 0
AddDisease(D)
+ return TRUE
/mob/proc/AddDisease(datum/disease/D, respect_carrier = FALSE)
@@ -127,6 +128,7 @@
if(passed)
AddDisease(D)
+ return passed
/**
diff --git a/code/datums/diseases/_disease.dm b/code/datums/diseases/_disease.dm
index c296db3f7e2..5cfe3ca970d 100644
--- a/code/datums/diseases/_disease.dm
+++ b/code/datums/diseases/_disease.dm
@@ -71,6 +71,8 @@ GLOBAL_LIST_INIT(diseases, subtypesof(/datum/disease))
var/list/required_organs = list()
var/needs_all_cures = TRUE
var/list/strain_data = list() //dna_spread special bullshit
+ /// Allow the virus to infect and process while the affected_mob is dead
+ var/allow_dead = FALSE
/datum/disease/Destroy()
affected_mob = null
@@ -87,23 +89,24 @@ GLOBAL_LIST_INIT(diseases, subtypesof(/datum/disease))
stage = min(stage, max_stages)
- if(!cure)
- if(prob(stage_prob))
- stage = min(stage + 1,max_stages)
- if(!discovered && stage >= CEILING(max_stages * discovery_threshold, 1)) // Once we reach a late enough stage, medical HUDs can pick us up even if we regress
- discovered = TRUE
- affected_mob.med_hud_set_status()
- else
- if(prob(cure_chance))
- stage = max(stage - 1, 1)
+ if(!cure && prob(stage_prob))
+ stage = min(stage + 1, max_stages)
+ if(!discovered && stage >= CEILING(max_stages * discovery_threshold, 1)) // Once we reach a late enough stage, medical HUDs can pick us up even if we regress
+ discovered = TRUE
+ affected_mob.med_hud_set_status()
+
+ return handle_cure_testing(cure)
+
+/datum/disease/proc/handle_cure_testing(has_cure = FALSE)
+ if(has_cure && prob(cure_chance))
+ stage = max(stage - 1, 1)
if(disease_flags & CURABLE)
- if(cure && prob(cure_chance))
+ if(has_cure && prob(cure_chance))
cure()
return FALSE
return TRUE
-
/datum/disease/proc/has_cure()
if(!(disease_flags & CURABLE))
return 0
diff --git a/code/datums/diseases/zombie_virus.dm b/code/datums/diseases/zombie_virus.dm
new file mode 100644
index 00000000000..d47742631e0
--- /dev/null
+++ b/code/datums/diseases/zombie_virus.dm
@@ -0,0 +1,141 @@
+/datum/disease/zombie
+ name = "Necrotizing Plague"
+ medical_name = "Advanced Resurrection Syndrome"
+ desc = "This virus infects humanoids and drives them insane with a hunger for flesh, along with possessing regenerative abilities."
+ max_stages = 7
+ spread_text = "Blood and Saliva"
+ spread_flags = BLOOD
+ cure_text = "Anti-plague viral solutions"
+ cures = list()
+ agent = ""
+ viable_mobtypes = list(/mob/living/carbon/human)
+ severity = BIOHAZARD
+ allow_dead = TRUE
+ disease_flags = CAN_CARRY
+ virus_heal_resistant = TRUE
+ stage_prob = 1
+ cure_chance = 20
+ /// How far this particular virus is in being cured (0-4)
+ var/cure_stage = 0
+
+/datum/disease/zombie/stage_act()
+ if(stage == 8)
+ // adminbus for immediate zombie
+ var/mob/living/carbon/human/H = affected_mob
+ if(!istype(H))
+ return FALSE
+ for(var/obj/item/organ/limb as anything in H.bodyparts)
+ if(!(limb.status & ORGAN_DEAD) && !limb.is_robotic())
+ limb.necrotize(TRUE, TRUE)
+
+ if(!..())
+ return FALSE
+ if(HAS_TRAIT(affected_mob, TRAIT_I_WANT_BRAINS) || affected_mob.mind?.has_antag_datum(/datum/antagonist/zombie))
+ handle_rot(TRUE)
+ stage = 7
+ return FALSE
+ switch(stage)
+ if(1) // cured by lvl 1 cure
+ if(prob(4))
+ to_chat(affected_mob, "[pick("Your scalp itches.", "Your skin feels flakey.")]")
+ else if(prob(5))
+ to_chat(affected_mob, "Your [pick("back", "arm", "leg", "elbow", "head")] itches.")
+ if(2)
+ if(prob(2))
+ to_chat(affected_mob, "Mucous runs down the back of your throat.")
+ else if(prob(5))
+ to_chat(affected_mob, "[pick("You feel hungry.", "You crave for something to eat.")]")
+ if(3) // cured by lvl 2 cure
+ if(prob(2))
+ affected_mob.emote("sneeze")
+ else if(prob(2))
+ affected_mob.emote("cough")
+ else if(prob(5))
+ to_chat(affected_mob, "[pick("So hungry...", "You'd kill someone for a bite of food...", "Hunger cramps seize you...")]")
+ if(prob(5))
+ affected_mob.adjustToxLoss(1)
+ if(4) // shows up on medhuds
+ if(prob(2))
+ affected_mob.emote("stare")
+ else if(prob(2))
+ affected_mob.emote("drool")
+ else if(prob(5))
+ to_chat(affected_mob, "You feel a cold sweat form.")
+ if(prob(25))
+ affected_mob.adjustToxLoss(1)
+ if(5, 6) // 5 is cured by lvl 3 cure. 6+ needs lvl 4 cure
+ var/turf/T = get_turf(affected_mob)
+ if(T.get_lumcount() >= 0.5)
+ if(prob(5))
+ to_chat(affected_mob, "Those lights seem bright. It stings.")
+ if(prob(25))
+ affected_mob.adjustFireLoss(2)
+ if(prob(2))
+ affected_mob.emote("drool")
+ if(stage == 6 && !affected_mob.reagents.has_reagent("zombiecure3")) // cure 3 can delay it, but not cure it
+ if(prob(10))
+ to_chat(affected_mob, "You feel your flesh rotting.")
+ handle_rot()
+ if(7)
+ if(!handle_rot(TRUE))
+ stage = 6
+ return
+
+
+
+/datum/disease/zombie/proc/handle_rot(forced = FALSE)
+ if(!prob(20) && !forced && affected_mob.stat != DEAD)
+ return FALSE
+ var/mob/living/carbon/human/H = affected_mob
+ if(!istype(H))
+ return FALSE
+ for(var/obj/item/organ/limb as anything in H.bodyparts)
+ if(!(limb.status & ORGAN_DEAD) && !limb.vital && !limb.is_robotic())
+ limb.necrotize()
+ return FALSE
+
+ for(var/obj/item/organ/limb as anything in H.bodyparts)
+ if(!(limb.status & ORGAN_DEAD) && !limb.is_robotic())
+ limb.necrotize(FALSE, TRUE)
+ return FALSE
+
+ if(!HAS_TRAIT(affected_mob, TRAIT_I_WANT_BRAINS))
+ affected_mob.AddComponent(/datum/component/zombie_regen)
+ ADD_TRAIT(affected_mob, TRAIT_I_WANT_BRAINS, ZOMBIE_TRAIT)
+ affected_mob.med_hud_set_health()
+ affected_mob.med_hud_set_status()
+ affected_mob.update_hands_hud()
+ H.update_body()
+ if(affected_mob.mind && !affected_mob.mind.has_antag_datum(/datum/antagonist/zombie))
+ affected_mob.mind.add_antag_datum(/datum/antagonist/zombie)
+ return TRUE
+
+
+/datum/disease/zombie/handle_cure_testing(has_cure = FALSE)
+ if(has_cure && prob(cure_chance))
+ stage = max(stage - 1, 0)
+
+ if(stage <= 0 && has_cure)
+ cure()
+ return FALSE
+ return TRUE
+
+/datum/disease/zombie/proc/update_cure_stage()
+ for(var/datum/reagent/zombie_cure/reag in affected_mob.reagents?.reagent_list)
+ cure_stage = max(cure_stage, reag.cure_level)
+ if(cure_stage)
+ var/stages = list("Stabilized", "Weakened", "Faltering", "Suppressed")
+ name = "[stages[cure_stage]] [initial(name)]"
+
+/datum/disease/zombie/has_cure()
+ update_cure_stage()
+ var/required_reagent = (stage + 1) / 2 // stage 1 can be cured by cure 1, stage 3 with 2, stage 5 with 3, stage 7 with 4
+ return cure_stage >= required_reagent
+
+/datum/disease/zombie/cure()
+ affected_mob.mind?.remove_antag_datum(/datum/antagonist/zombie)
+ REMOVE_TRAIT(affected_mob, TRAIT_I_WANT_BRAINS, ZOMBIE_TRAIT)
+ qdel(affected_mob.GetComponent(/datum/component/zombie_regen))
+ affected_mob.med_hud_set_health()
+ affected_mob.med_hud_set_status()
+ return ..()
diff --git a/code/datums/emote.dm b/code/datums/emote.dm
index 50e70232a35..a25d7469b29 100644
--- a/code/datums/emote.dm
+++ b/code/datums/emote.dm
@@ -273,9 +273,14 @@
if(age_based && ishuman(user))
var/mob/living/carbon/human/H = user
// Vary needs to be true as otherwise frequency changes get ignored deep within playsound_local :(
- playsound(user.loc, sound_path, sound_volume, TRUE, frequency = H.get_age_pitch(H.dna.species.max_age))
+ playsound(user.loc, sound_path, sound_volume, TRUE, frequency = H.get_age_pitch(H.dna.species.max_age) * alter_emote_pitch(user))
else
- playsound(user.loc, sound_path, sound_volume, vary)
+ playsound(user.loc, sound_path, sound_volume, TRUE, frequency = alter_emote_pitch(user))
+
+/datum/emote/proc/alter_emote_pitch(mob/user)
+ if(HAS_TRAIT(user, TRAIT_I_WANT_BRAINS))
+ return 0.7
+ return 1
/**
* Send an emote to runechat for all (listening) users in the vicinity.
diff --git a/code/datums/mind.dm b/code/datums/mind.dm
index c055734bbe5..d27f48c459b 100644
--- a/code/datums/mind.dm
+++ b/code/datums/mind.dm
@@ -417,6 +417,16 @@
. += _memory_edit_role_enabled(ROLE_ABDUCTOR)
+/datum/mind/proc/memory_edit_zombie(mob/living/H)
+ . = _memory_edit_header("zombie", list())
+ if(has_antag_datum(/datum/antagonist/zombie))
+ . += "no|ZOMBIE"
+ return
+ if(current.HasDisease(/datum/disease/zombie))
+ . += "NO|zombie|dis-infect"
+ else
+ . += "NO|zombie|infect"
+
/datum/mind/proc/memory_edit_eventmisc(mob/living/H)
. = _memory_edit_header("event", list())
if(src in SSticker.mode.eventmiscs)
@@ -546,6 +556,8 @@
sections["nuclear"] = memory_edit_nuclear(H)
/** Abductors **/
sections["abductor"] = memory_edit_abductor(H)
+ /** Zombies **/
+ sections["zombie"] = memory_edit_zombie(H)
sections["eventmisc"] = memory_edit_eventmisc(H)
/** TRAITOR ***/
sections["traitor"] = memory_edit_traitor()
@@ -1170,6 +1182,45 @@
log_admin("[key_name(usr)] has eventantag'ed [current].")
current.create_log(MISC_LOG, "[current] was made into an event antagonist by [key_name_admin(usr)]")
+ else if(href_list["zombie"])
+ switch(href_list["zombie"])
+ if("clear")
+ if(!has_antag_datum(/datum/antagonist/zombie))
+ return
+ remove_antag_datum(/datum/antagonist/zombie)
+ for(var/datum/disease/zombie/D in current.viruses)
+ D.cure()
+ if(ishuman(current))
+ var/mob/living/carbon/human/human_current = current
+ for(var/obj/item/organ/limb as anything in human_current.bodyparts)
+ if(limb.status & ORGAN_DEAD && !limb.is_robotic())
+ limb.status &= ~ORGAN_DEAD
+ human_current.update_body()
+ message_admins("[key_name_admin(usr)] has de-zombied'ed [key_name(current)].")
+ log_admin("[key_name(usr)] has de-zombied'ed [key_name(current)].")
+ if("zombie")
+ if(has_antag_datum(/datum/antagonist/zombie))
+ return
+ add_antag_datum(/datum/antagonist/zombie)
+ message_admins("[key_name_admin(usr)] has zombie'ed [key_name(current)].")
+ log_admin("[key_name(usr)] has zombie'ed [key_name(current)].")
+ current.create_log(MISC_LOG, "[key_name(current)] was made into an zombie by [key_name_admin(usr)]")
+ if("zombievirus")
+ if(has_antag_datum(/datum/antagonist/zombie) || !ishuman(current))
+ return
+ current.AddDisease(new /datum/disease/zombie)
+ message_admins("[key_name_admin(usr)] has given a zombie virus to [key_name(current)].")
+ log_admin("[key_name(usr)] has given a zombie virus to [key_name(current)].")
+ current.create_log(MISC_LOG, "[key_name(current)] was admin-infected with a zombie virus by [key_name_admin(usr)]")
+ if("zombievirusno")
+ if(has_antag_datum(/datum/antagonist/zombie))
+ return
+ for(var/datum/disease/zombie/D in current.viruses)
+ D.cure()
+ message_admins("[key_name_admin(usr)] has removed the zombie virus from [key_name(current)].")
+ log_admin("[key_name(usr)] has removed the zombie virus from [key_name(current)].")
+ current.create_log(MISC_LOG, "[key_name(current)] had their zombie virus admin-removed by [key_name_admin(usr)]")
+
else if(href_list["traitor"])
switch(href_list["traitor"])
if("clear")
diff --git a/code/game/data_huds.dm b/code/game/data_huds.dm
index 0d48b4f514e..488ee658f16 100644
--- a/code/game/data_huds.dm
+++ b/code/game/data_huds.dm
@@ -102,7 +102,7 @@
//helper for getting the appropriate health status
/proc/RoundHealth(mob/living/M)
- if(M.stat == DEAD || (HAS_TRAIT(M, TRAIT_FAKEDEATH)))
+ if(M.stat == DEAD || HAS_TRAIT(M, TRAIT_FAKEDEATH) || HAS_TRAIT(M, TRAIT_I_WANT_BRAINS))
return "health-100-dead" //what's our health? it doesn't matter, we're dead, or faking
var/maxi_health = M.maxHealth
@@ -187,6 +187,10 @@
if(ismachineperson(src))
holder = hud_list[DIAG_STAT_HUD]
+ if(HAS_TRAIT(src, TRAIT_I_WANT_BRAINS))
+ holder.icon_state = "hudflatline"
+ return
+
// To the right of health bar
if(stat == DEAD || HAS_TRAIT(src, TRAIT_FAKEDEATH))
var/revivable_state = "dead"
diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm
index b7f98ddc1ba..fe3d53e93f8 100644
--- a/code/game/gamemodes/objective.dm
+++ b/code/game/gamemodes/objective.dm
@@ -815,3 +815,8 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective)
explanation_text = "Wreak havoc upon the station as much you can. Send those wandless Nanotrasen scum a message!"
needs_target = FALSE
completed = TRUE
+
+/datum/objective/zombie
+ explanation_text = "Hunger grows within us, we need to feast on the brains of the uninfected. Scratch, bite, and spread the plague."
+ needs_target = FALSE
+ completed = TRUE
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index 1bbf0582be3..7cae3ad5848 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -305,7 +305,7 @@ GLOBAL_DATUM_INIT(welding_sparks, /mutable_appearance, mutable_appearance('icons
if(throwing)
throwing.finalize(FALSE)
if(loc == user)
- if(!user.unEquip(src, silent = TRUE))
+ if(HAS_TRAIT(user, TRAIT_I_WANT_BRAINS) || !user.unEquip(src, silent = TRUE))
return 0
if(flags & ABSTRACT)
diff --git a/code/game/objects/items/stacks/medical_packs.dm b/code/game/objects/items/stacks/medical_packs.dm
index 92d5ffed73f..ae50c811b8d 100644
--- a/code/game/objects/items/stacks/medical_packs.dm
+++ b/code/game/objects/items/stacks/medical_packs.dm
@@ -156,7 +156,7 @@
var/mob/living/carbon/human/H = M
var/obj/item/organ/external/affecting = H.get_organ(user.zone_selected)
for(var/obj/item/organ/external/E in H.bodyparts)
- if(E.open == ORGAN_ORGANIC_OPEN)
+ if(E.open >= ORGAN_ORGANIC_OPEN)
to_chat(user, "[E] is cut open, you'll need more than a bandage!")
return
affecting.germ_level = 0
diff --git a/code/game/objects/items/weapons/manuals.dm b/code/game/objects/items/weapons/manuals.dm
index d14a4c0e2b5..230b5e450b4 100644
--- a/code/game/objects/items/weapons/manuals.dm
+++ b/code/game/objects/items/weapons/manuals.dm
@@ -423,6 +423,162 @@