diff --git a/code/__DEFINES/genetics.dm b/code/__DEFINES/genetics.dm
index 7213ecc3118..c7b055879f1 100644
--- a/code/__DEFINES/genetics.dm
+++ b/code/__DEFINES/genetics.dm
@@ -1,6 +1,8 @@
// String identifiers for associative list lookup
+#define CHECK_DNA_AND_SPECIES(C) if((!(C.dna)) || (!(C.dna.species))) return
+
#define MUTCHK_FORCED 1
// mob/var/list/mutations
@@ -22,7 +24,6 @@
///////////////////////////////////////
// MUTATIONS
///////////////////////////////////////
-
// Generic mutations:
#define TK 1
#define COLDRES 2
@@ -149,4 +150,5 @@
#define RESISTHOT 16
#define RESISTCOLD 17
#define NO_EXAMINE 18
-#define CAN_WINGDINGS 19
\ No newline at end of file
+#define CAN_WINGDINGS 19
+#define NOZOMBIE 20
\ No newline at end of file
diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm
index c4478f62837..943878821c3 100644
--- a/code/__DEFINES/is_helpers.dm
+++ b/code/__DEFINES/is_helpers.dm
@@ -4,6 +4,7 @@
// Mobs
#define ismegafauna(A) istype(A, /mob/living/simple_animal/hostile/megafauna)
+#define iszombie(A) (is_species(A, /datum/species/zombie))
//Simple animals
#define isshade(A) (istype(A, /mob/living/simple_animal/shade))
diff --git a/code/_globalvars/lists/objects.dm b/code/_globalvars/lists/objects.dm
index ad3798a010e..f383ba3fc33 100644
--- a/code/_globalvars/lists/objects.dm
+++ b/code/_globalvars/lists/objects.dm
@@ -16,6 +16,7 @@ GLOBAL_LIST_INIT(prisoncomputer_list, list())
GLOBAL_LIST_INIT(celltimers_list, list()) // list of all cell timers
GLOBAL_LIST_INIT(cell_logs, list())
GLOBAL_LIST_INIT(navigation_computers, list())
+GLOBAL_LIST_INIT(zombie_infection_list, list())
GLOBAL_LIST_INIT(all_areas, list())
GLOBAL_LIST_INIT(machines, list())
diff --git a/code/datums/datumvars.dm b/code/datums/datumvars.dm
index e8339f81945..138c670290a 100644
--- a/code/datums/datumvars.dm
+++ b/code/datums/datumvars.dm
@@ -738,22 +738,7 @@
if(!istype(M))
to_chat(usr, "This can only be used on instances of type /mob")
return
- to_chat(M, "Control of your mob has been offered to dead players.")
- log_admin("[key_name(usr)] has offered control of ([key_name(M)]) to ghosts.")
- var/minhours = input(usr, "Minimum hours required to play [M]?", "Set Min Hrs", 10) as num
- message_admins("[key_name_admin(usr)] has offered control of ([key_name_admin(M)]) to ghosts with [minhours] hrs playtime")
- var/list/mob/dead/observer/candidates = pollCandidates("Do you want to play as [M.real_name ? M.real_name : M.name]?", poll_time = 100, min_hours = minhours)
- var/mob/dead/observer/theghost = null
-
- if(candidates.len)
- theghost = pick(candidates)
- to_chat(M, "Your mob has been taken over by a ghost!")
- message_admins("[key_name_admin(theghost)] has taken control of ([key_name_admin(M)])")
- M.ghostize()
- M.key = theghost.key
- else
- to_chat(M, "There were no ghosts willing to take control.")
- message_admins("No ghosts were willing to take control of [key_name_admin(M)])")
+ offer_control(M)
else if(href_list["delete"])
if(!check_rights(R_DEBUG, 0))
diff --git a/code/datums/uplink_item.dm b/code/datums/uplink_item.dm
index 354543e2b85..0cb6a31770c 100644
--- a/code/datums/uplink_item.dm
+++ b/code/datums/uplink_item.dm
@@ -190,6 +190,14 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
cost = 1
job = list("Clown")
+/datum/uplink_item/stealthy_weapons/romerol_kit
+ name = "Romerol"
+ reference = "ROM"
+ desc = "A highly experimental bioterror agent which creates dormant nodules to be etched into the grey matter of the brain. On death, these nodules take control of the dead body, causing limited revivification, along with slurred speech, aggression, and the ability to infect others with this agent."
+ item = /obj/item/storage/box/syndie_kit/romerol
+ cost = 25
+ cant_discount = TRUE
+
//mime
/datum/uplink_item/jobspecific/caneshotgun
name = "Cane Shotgun and Assassination Shells"
@@ -646,6 +654,7 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
/datum/uplink_item/dangerous/guardian
name = "Holoparasites"
+ reference = "HPA"
desc = "Though capable of near sorcerous feats via use of hardlight holograms and nanomachines, they require an organic host as a home base and source of fuel. \
The holoparasites are unable to incoporate themselves to changeling and vampire agents."
item = /obj/item/storage/box/syndie_kit/guardian
diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm
index 836c51d5a2c..42ebb351e42 100644
--- a/code/game/gamemodes/objective.dm
+++ b/code/game/gamemodes/objective.dm
@@ -66,7 +66,7 @@ var/list/potential_theft_objectives = subtypesof(/datum/theft_objective) - /datu
/datum/objective/assassinate/check_completion()
if(target && target.current)
- if(target.current.stat == DEAD)
+ if(target.current.stat == DEAD || iszombie(target))
return 1
if(issilicon(target.current) || isbrain(target.current)) //Borgs/brains/AIs count as dead for traitor objectives. --NeoFite
return 1
@@ -110,7 +110,7 @@ var/list/potential_theft_objectives = subtypesof(/datum/theft_objective) - /datu
/datum/objective/maroon/check_completion()
if(target && target.current)
- if(target.current.stat == DEAD)
+ if(target.current.stat == DEAD || iszombie(target))
return 1
if(!target.current.ckey)
return 1
@@ -167,7 +167,7 @@ var/list/potential_theft_objectives = subtypesof(/datum/theft_objective) - /datu
if(!target) //If it's a free objective.
return 1
if(target.current)
- if(target.current.stat == DEAD)
+ if(target.current.stat == DEAD || iszombie(target))
return 0
if(issilicon(target.current))
return 0
@@ -262,7 +262,7 @@ var/list/potential_theft_objectives = subtypesof(/datum/theft_objective) - /datu
return 0
if(isbrain(owner.current))
return 0
- if(!owner.current || owner.current.stat == DEAD)
+ if(!owner.current || owner.current.stat == DEAD || iszombie(owner))
return 0
if(SSticker.force_ending) //This one isn't their fault, so lets just assume good faith
return 1
@@ -317,7 +317,7 @@ var/list/potential_theft_objectives = subtypesof(/datum/theft_objective) - /datu
explanation_text = "Die a glorious death."
/datum/objective/die/check_completion()
- if(!owner.current || owner.current.stat == DEAD || isbrain(owner.current))
+ if(!owner.current || owner.current.stat == DEAD || isbrain(owner.current) || iszombie(owner))
return 1
if(issilicon(owner.current) && owner.current != owner.original)
return 1
diff --git a/code/game/objects/items/weapons/storage/uplink_kits.dm b/code/game/objects/items/weapons/storage/uplink_kits.dm
index a8f0af13b98..6b68ef19c4c 100644
--- a/code/game/objects/items/weapons/storage/uplink_kits.dm
+++ b/code/game/objects/items/weapons/storage/uplink_kits.dm
@@ -121,6 +121,19 @@
desc = "A sleek, sturdy box"
icon_state = "box_of_doom"
+/obj/item/storage/box/syndie_kit/romerol
+ name = "Romerol Kit"
+ desc = "A box containing a deadly virus capable of reanimating dead as zombies."
+ max_w_class = WEIGHT_CLASS_NORMAL
+ can_hold = list(/obj/item/reagent_containers/glass/bottle/romerol,/obj/item/reagent_containers/syringe,/obj/item/reagent_containers/dropper)
+
+/obj/item/storage/box/syndie_kit/romerol/New()
+ ..()
+ new /obj/item/reagent_containers/glass/bottle/romerol(src)
+ new /obj/item/reagent_containers/syringe(src)
+ new /obj/item/reagent_containers/dropper(src)
+ return
+
/obj/item/storage/box/syndie_kit/space
name = "Boxed Space Suit and Helmet"
can_hold = list(/obj/item/clothing/suit/space/syndicate/black/red, /obj/item/clothing/head/helmet/space/syndicate/black/red, /obj/item/tank/emergency_oxygen/syndi, /obj/item/clothing/mask/gas/syndicate)
diff --git a/code/modules/mob/language.dm b/code/modules/mob/language.dm
index 9391f24035a..525421ed186 100644
--- a/code/modules/mob/language.dm
+++ b/code/modules/mob/language.dm
@@ -762,6 +762,16 @@
desc = "Bark bark bark."
key = "vu"
+/datum/language/zombie
+ name = "Zombie"
+ desc = "BRAAAAAAINS!"
+ speech_verb = "moans"
+ whisper_verb = "mutters"
+ exclaim_verb = "wails"
+ colour = "zombie"
+ key = "zom"
+ syllables = list("BRAAAAAAAAAAAAAAAAINS", "BRAAINS", "BRAINS")
+
/mob/proc/grant_all_languages()
for(var/la in GLOB.all_languages)
add_language(la)
diff --git a/code/modules/mob/living/carbon/human/human_damage.dm b/code/modules/mob/living/carbon/human/human_damage.dm
index 3d4aadf5746..762df8d3b8b 100644
--- a/code/modules/mob/living/carbon/human/human_damage.dm
+++ b/code/modules/mob/living/carbon/human/human_damage.dm
@@ -344,56 +344,5 @@ This function restores all organs.
//Handle BRUTE and BURN damage
handle_suit_punctures(damagetype, damage)
-
- blocked = (100 - blocked) / 100
- if(blocked <= 0)
- return 0
-
- var/obj/item/organ/external/organ = null
- if(isorgan(def_zone))
- organ = def_zone
- else
- if(!def_zone)
- def_zone = ran_zone(def_zone)
- organ = get_organ(check_zone(def_zone))
- if(!organ)
- return 0
-
- damage = damage * blocked
-
- switch(damagetype)
- if(BRUTE)
- damageoverlaytemp = 20
- if(dna.species)
- damage = damage * dna.species.brute_mod
-
- if(organ.receive_damage(damage, 0, sharp, used_weapon))
- UpdateDamageIcon()
-
- if(LAssailant && ishuman(LAssailant)) //superheros still get the comical hit markers
- var/mob/living/carbon/human/H = LAssailant
- if(H.mind && H.mind in (SSticker.mode.superheroes || SSticker.mode.supervillains || SSticker.mode.greyshirts))
- var/list/attack_bubble_recipients = list()
- var/mob/living/user
- for(var/mob/O in viewers(user, src))
- if(O.client && O.has_vision(information_only=TRUE))
- attack_bubble_recipients.Add(O.client)
- spawn(0)
- var/image/dmgIcon = image('icons/effects/hit_blips.dmi', src, "dmg[rand(1,2)]",MOB_LAYER+1)
- dmgIcon.pixel_x = (!lying) ? rand(-3,3) : rand(-11,12)
- dmgIcon.pixel_y = (!lying) ? rand(-11,9) : rand(-10,1)
- flick_overlay(dmgIcon, attack_bubble_recipients, 9)
-
-
- if(BURN)
- damageoverlaytemp = 20
-
- if(dna.species)
- damage = damage * dna.species.burn_mod
-
- if(organ.receive_damage(0, damage, sharp, used_weapon))
- UpdateDamageIcon()
-
- // Will set our damageoverlay icon to the next level, which will then be set back to the normal level the next mob.Life().
- updatehealth("apply damage")
- return 1
+ //Handle species apply_damage procs
+ return dna.species.apply_damage(damage, damagetype, def_zone, blocked, src, sharp, used_weapon)
diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm
index d6c667b1b74..fe048fb8137 100644
--- a/code/modules/mob/living/carbon/human/life.dm
+++ b/code/modules/mob/living/carbon/human/life.dm
@@ -14,7 +14,6 @@
handle_heartbeat()
handle_drunk()
dna.species.handle_life(src)
-
if(!client)
dna.species.handle_npc(src)
diff --git a/code/modules/mob/living/carbon/human/species/_species.dm b/code/modules/mob/living/carbon/human/species/_species.dm
index 3e295eb9b4c..de541f483e3 100644
--- a/code/modules/mob/living/carbon/human/species/_species.dm
+++ b/code/modules/mob/living/carbon/human/species/_species.dm
@@ -52,9 +52,10 @@
var/brain_mod = 1 // Brain damage damage reduction/amplification
var/stamina_mod = 1
var/stun_mod = 1 // If a species is more/less impacated by stuns/weakens/paralysis
-
+ var/speedmod = 0 // this affects the race's speed. positive numbers make it move slower, negative numbers make it move faster
+ var/armor = 0 // overall defense for the race... or less defense, if it's negative.
var/blood_damage_type = OXY //What type of damage does this species take if it's low on blood?
-
+ var/obj/item/mutanthands
var/total_health = 100
var/punchdamagelow = 0 //lowest possible punch damage
var/punchdamagehigh = 9 //highest possible punch damage
@@ -308,6 +309,63 @@
/datum/species/proc/handle_death(mob/living/carbon/human/H) //Handles any species-specific death events (such as dionaea nymph spawns).
return
+/datum/species/proc/spec_death(gibbed, mob/living/carbon/human/C)
+ return
+
+/datum/species/proc/apply_damage(damage = 0, damagetype = BRUTE, def_zone = null, blocked = 0, mob/living/carbon/human/H, sharp = 0, obj/used_weapon = null)
+ blocked = (100 - blocked) / 100
+ if(blocked <= 0)
+ return 0
+
+ var/obj/item/organ/external/organ = null
+ if(isorgan(def_zone))
+ organ = def_zone
+ else
+ if(!def_zone)
+ def_zone = ran_zone(def_zone)
+ organ = H.get_organ(check_zone(def_zone))
+ if(!organ)
+ return 0
+
+ damage = damage * blocked
+
+ switch(damagetype)
+ if(BRUTE)
+ H.damageoverlaytemp = 20
+ damage = damage * brute_mod
+
+ if(organ.receive_damage(damage, 0, sharp, used_weapon))
+ H.UpdateDamageIcon()
+
+ if(H.LAssailant && ishuman(H.LAssailant)) //superheros still get the comical hit markers
+ var/mob/living/carbon/human/A = H.LAssailant
+ if(A.mind && A.mind in (SSticker.mode.superheroes || SSticker.mode.supervillains || SSticker.mode.greyshirts))
+ var/list/attack_bubble_recipients = list()
+ var/mob/living/user
+ for(var/mob/O in viewers(user, src))
+ if(O.client && O.has_vision(information_only=TRUE))
+ attack_bubble_recipients.Add(O.client)
+ spawn(0)
+ var/image/dmgIcon = image('icons/effects/hit_blips.dmi', src, "dmg[rand(1,2)]",MOB_LAYER+1)
+ dmgIcon.pixel_x = (!H.lying) ? rand(-3,3) : rand(-11,12)
+ dmgIcon.pixel_y = (!H.lying) ? rand(-11,9) : rand(-10,1)
+ flick_overlay(dmgIcon, attack_bubble_recipients, 9)
+
+
+ if(BURN)
+ H.damageoverlaytemp = 20
+ damage = damage * burn_mod
+
+ if(organ.receive_damage(0, damage, sharp, used_weapon))
+ H.UpdateDamageIcon()
+
+ // Will set our damageoverlay icon to the next level, which will then be set back to the normal level the next mob.Life().
+ H.updatehealth("apply damage")
+ return 1
+
+/datum/species/proc/spec_stun(mob/living/carbon/human/H,amount)
+ return
+
/datum/species/proc/spec_electrocute_act(mob/living/carbon/human/H, shock_damage, obj/source, siemens_coeff = 1, safety = FALSE, override = FALSE, tesla_shock = FALSE, illusion = FALSE, stun = TRUE)
return
diff --git a/code/modules/mob/living/carbon/human/species/zombies.dm b/code/modules/mob/living/carbon/human/species/zombies.dm
new file mode 100644
index 00000000000..6ff30c54a67
--- /dev/null
+++ b/code/modules/mob/living/carbon/human/species/zombies.dm
@@ -0,0 +1,91 @@
+#define REGENERATION_DELAY 60 // After taking damage, how long it takes for automatic regeneration to begin
+
+/datum/species/zombie
+ // 1spooky
+ name = "High-Functioning Zombie"
+ name_plural = "High-Functioning Zombies"
+ icobase = 'icons/mob/human_races/r_zombie.dmi'
+ deform = 'icons/mob/human_races/r_def_zombie.dmi'
+ dies_at_threshold = TRUE
+ language = "Zombie"
+ species_traits = list(NO_BLOOD,NOZOMBIE,NOTRANSSTING,NO_BREATHE, RADIMMUNE, NO_SCAN)
+ var/static/list/spooks = list('sound/hallucinations/growl1.ogg','sound/hallucinations/growl2.ogg','sound/hallucinations/growl3.ogg','sound/hallucinations/veryfar_noise.ogg','sound/hallucinations/wail.ogg')
+ warning_low_pressure = 50
+ hazard_low_pressure = 0
+ flesh_color = "#00FF00" // for green examine text
+ bodyflags = HAS_SKIN_COLOR
+ dietflags = DIET_CARN
+ has_organ = list(
+ "heart" = /obj/item/organ/internal/heart,
+ "lungs" = /obj/item/organ/internal/lungs,
+ "liver" = /obj/item/organ/internal/liver,
+ "kidneys" = /obj/item/organ/internal/kidneys,
+ "brain" = /obj/item/organ/internal/brain,
+ "appendix" = /obj/item/organ/internal/appendix,
+ "eyes" = /obj/item/organ/internal/eyes,
+ "ears" = /obj/item/organ/internal/ears,
+ "zombie_infection" = /obj/item/organ/internal/zombie_infection)
+
+
+/datum/species/zombie/infectious
+ name = "Infectious Zombie"
+ mutanthands = /obj/item/zombie_hand
+ icobase = 'icons/mob/human_races/r_zombie.dmi'
+ deform = 'icons/mob/human_races/r_def_zombie.dmi'
+ armor = 20 // 120 damage to KO a zombie, which kills it
+ speedmod = 1.6
+ default_language = "Zombie"
+ var/heal_rate = 1
+ var/regen_cooldown = 0
+
+/datum/species/zombie/infectious/spec_stun(mob/living/carbon/human/H, amount)
+ . = min(20, amount)
+
+/datum/species/zombie/infectious/apply_damage(damage = 0, damagetype = BRUTE, def_zone = null, blocked = 0, sharp = 0, obj/used_weapon = null)
+ . = ..()
+ if(damage)
+ regen_cooldown = world.time + REGENERATION_DELAY
+
+/datum/species/zombie/infectious/handle_life(mob/living/carbon/human/C)
+ . = ..()
+ //Zombies never actually die, they just fall down until they regenerate enough to rise back up.
+ //They must be restrained, beheaded or gibbed to stop being a threat.
+ if(regen_cooldown < world.time)
+ var/heal_amt = heal_rate
+ if(C.InCritical())
+ heal_amt *= 2
+ C.heal_overall_damage(heal_amt,heal_amt)
+ C.adjustToxLoss(-heal_amt)
+ if(!C.InCritical() && prob(4))
+ playsound(C, pick(spooks), 50, TRUE, 10)
+
+//Congrats you somehow died so hard you stopped being a zombie
+/datum/species/zombie/infectious/spec_death(gibbed, mob/living/carbon/C)
+ . = ..()
+ var/obj/item/organ/internal/zombie_infection/infection
+ infection = C.get_organ_slot("zombie_infection")
+ if(infection)
+ qdel(infection)
+
+/datum/species/zombie/infectious/on_species_gain(mob/living/carbon/human/C, datum/species/old_species)
+ . = ..()
+ // Deal with the source of this zombie corruption
+ // Infection organ needs to be handled separately from mutant_organs
+ // because it persists through species transitions
+ if(mutanthands)
+ C.drop_l_hand()
+ C.drop_r_hand()
+ C.put_in_hands(new mutanthands())
+ C.put_in_hands(new mutanthands())
+ var/obj/item/organ/internal/zombie_infection/infection
+ infection = C.get_organ_slot("zombie_infection")
+ if(!infection)
+ infection = new()
+ infection.insert(C)
+
+/datum/species/zombie/infectious/on_species_loss(mob/living/carbon/human/C, datum/species/old_species)
+ QDEL_NULL(C.r_hand)
+ QDEL_NULL(C.l_hand)
+ return ..()
+
+#undef REGENERATION_DELAY
diff --git a/code/modules/mob/living/carbon/human/status_procs.dm b/code/modules/mob/living/carbon/human/status_procs.dm
index 379eb183292..818e15f26e8 100644
--- a/code/modules/mob/living/carbon/human/status_procs.dm
+++ b/code/modules/mob/living/carbon/human/status_procs.dm
@@ -5,21 +5,29 @@
. = ..()
/mob/living/carbon/human/SetStunned(amount, updating = 1, force = 0)
- if(dna.species)
+ if(dna.species.stun_mod)
amount = amount * dna.species.stun_mod
+ else
+ amount = dna.species.spec_stun(src,amount)
return ..()
/mob/living/carbon/human/SetWeakened(amount, updating = 1, force = 0)
- if(dna.species)
+ if(dna.species.stun_mod)
amount = amount * dna.species.stun_mod
+ else
+ amount = dna.species.spec_stun(src,amount)
return ..()
/mob/living/carbon/human/SetParalysis(amount, updating = 1, force = 0)
- if(dna.species)
+ if(dna.species.stun_mod)
amount = amount * dna.species.stun_mod
+ else
+ amount = dna.species.spec_stun(src,amount)
return ..()
/mob/living/carbon/human/SetSleeping(amount, updating = 1, no_alert = FALSE)
- if(dna.species)
+ if(dna.species.stun_mod)
amount = amount * dna.species.stun_mod
- return ..()
\ No newline at end of file
+ else
+ amount = dna.species.spec_stun(src,amount)
+ return ..()
diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm
index 0a0e8422dbf..ec81cc33950 100644
--- a/code/modules/mob/mob_helpers.dm
+++ b/code/modules/mob/mob_helpers.dm
@@ -113,6 +113,24 @@
return U.sensor_mode
return SUIT_SENSOR_OFF
+/proc/offer_control(mob/M)
+ to_chat(M, "Control of your mob has been offered to dead players.")
+ log_admin("[key_name(usr)] has offered control of ([key_name(M)]) to ghosts.")
+ var/minhours = input(usr, "Minimum hours required to play [M]?", "Set Min Hrs", 10) as num
+ message_admins("[key_name_admin(usr)] has offered control of ([key_name_admin(M)]) to ghosts with [minhours] hrs playtime")
+ var/list/mob/dead/observer/candidates = pollCandidates("Do you want to play as [M.real_name ? M.real_name : M.name]?", poll_time = 100, min_hours = minhours)
+ var/mob/dead/observer/theghost = null
+
+ if(LAZYLEN(candidates))
+ theghost = pick(candidates)
+ to_chat(M, "Your mob has been taken over by a ghost!")
+ message_admins("[key_name_admin(theghost)] has taken control of ([key_name_admin(M)])")
+ M.ghostize()
+ M.key = theghost.key
+ else
+ to_chat(M, "There were no ghosts willing to take control.")
+ message_admins("No ghosts were willing to take control of [key_name_admin(M)])")
+
/proc/check_zone(zone)
if(!zone) return "chest"
switch(zone)
diff --git a/code/modules/mob/new_player/sprite_accessories/sprite_accessories.dm b/code/modules/mob/new_player/sprite_accessories/sprite_accessories.dm
index d4834eccc80..476861861a3 100644
--- a/code/modules/mob/new_player/sprite_accessories/sprite_accessories.dm
+++ b/code/modules/mob/new_player/sprite_accessories/sprite_accessories.dm
@@ -48,7 +48,7 @@
var/gender = NEUTER //Determines if the accessory will be skipped or included in random hair generations
// Restrict some styles to specific species
- var/list/species_allowed = list("Human", "Slime People")
+ var/list/species_allowed = list("Human", "Slime People", "Infectious Zombie", "High-Functioning Zombie")
var/list/sprite_sheets = list() //For accessories common across species but need to use 'fitted' sprites (like underwear). e.g. list("Vox" = 'icons/mob/species/vox/iconfile.dmi')
var/list/models_allowed = list() //Specifies which, if any, hairstyles or markings can be accessed by which prosthetics. Should equal the manufacturing company name in robolimbs.dm.
var/list/heads_allowed = null //Specifies which, if any, alt heads a head marking, hairstyle or facial hair style is compatible with.
diff --git a/code/modules/reagents/chemistry/reagents/toxins.dm b/code/modules/reagents/chemistry/reagents/toxins.dm
index 10c216ca60c..7a060a93996 100644
--- a/code/modules/reagents/chemistry/reagents/toxins.dm
+++ b/code/modules/reagents/chemistry/reagents/toxins.dm
@@ -207,6 +207,25 @@
return ..()
+datum/reagent/romerol
+ name = "romerol"
+ // the REAL zombie powder
+ id = "romerol"
+ description = "Romerol is a highly experimental bioterror agent \
+ which causes dormant nodules to be etched into the grey matter of \
+ the subject. These nodules only become active upon death of the \
+ host, upon which, the secondary structures activate and take control \
+ of the host body."
+ color = "#123524" // RGB (18, 53, 36)
+ metabolization_rate = INFINITY
+ can_synth = FALSE
+ taste_description = "CAAAARL"
+
+/datum/reagent/romerol/reaction_mob(mob/living/carbon/human/H)
+ // Silently add the zombie infection organ to be activated upon death
+ new /obj/item/organ/internal/zombie_infection(H)
+ ..()
+
/datum/reagent/stable_mutagen/on_tick()
var/datum/reagent/blood/B = locate() in holder.reagent_list
if(B && islist(B.data) && !data)
diff --git a/code/modules/reagents/reagent_containers/bottle.dm b/code/modules/reagents/reagent_containers/bottle.dm
index 5845b0be15c..b9080bf3d12 100644
--- a/code/modules/reagents/reagent_containers/bottle.dm
+++ b/code/modules/reagents/reagent_containers/bottle.dm
@@ -12,6 +12,11 @@
container_type = OPENCONTAINER
volume = 30
+/obj/item/reagent_containers/glass/bottle/romerol
+ name = "romerol bottle"
+ desc = "A small bottle of Romerol. The REAL zombie powder."
+ list_reagents = list("romerol" = 30)
+
/obj/item/reagent_containers/glass/bottle/on_reagent_change()
update_icon()
diff --git a/code/modules/shuttle/emergency.dm b/code/modules/shuttle/emergency.dm
index 0efd49b0e54..953c0e6682c 100644
--- a/code/modules/shuttle/emergency.dm
+++ b/code/modules/shuttle/emergency.dm
@@ -175,7 +175,7 @@
/obj/docking_port/mobile/emergency/proc/is_hijacked()
for(var/mob/living/player in GLOB.player_list)
if(player.mind)
- if(player.stat != DEAD)
+ if(player.stat != DEAD || !iszombie(player))
if(issilicon(player)) //Borgs are technically dead anyways
continue
if(isanimal(player)) //Poly does not own the shuttle
diff --git a/code/modules/zombie/items.dm b/code/modules/zombie/items.dm
new file mode 100644
index 00000000000..3ba12518dd2
--- /dev/null
+++ b/code/modules/zombie/items.dm
@@ -0,0 +1,67 @@
+/obj/item/zombie_hand
+ name = "zombie claw"
+ desc = "A zombie's claw is its primary tool, capable of infecting \
+ unconscious or dead humans, butchering all other living things to \
+ sustain the zombie, forcing open airlock doors and opening \
+ child-safe caps on bottles."
+ flags = NODROP|ABSTRACT|DROPDEL
+ icon = 'icons/effects/blood.dmi'
+ icon_state = "bloodhand_left"
+ var/icon_left = "bloodhand_left"
+ var/icon_right = "bloodhand_right"
+ hitsound = 'sound/hallucinations/growl1.ogg'
+ force = 25
+ damtype = "brute"
+
+/obj/item/zombie_hand/equipped(mob/user, slot)
+ . = ..()
+ switch(slot)
+ // Yes, these intentionally don't match
+ if(slot_l_hand)
+ icon_state = icon_right
+ if(slot_r_hand)
+ icon_state = icon_left
+
+/obj/item/zombie_hand/afterattack(atom/target, mob/user, proximity_flag)
+ . = ..()
+ if(!proximity_flag)
+ return
+
+ else if(isliving(target))
+ if(ishuman(target))
+ check_infection(target, user)
+ else
+ check_feast(target, user)
+
+/obj/item/zombie_hand/proc/check_infection(var/mob/living/carbon/human/target, var/mob/user)
+ CHECK_DNA_AND_SPECIES(target)
+
+ if(NOZOMBIE in target.dna.species.species_traits)
+ // cannot infect any NOZOMBIE subspecies (such as high functioning
+ // zombies)
+ return
+
+ var/obj/item/organ/internal/zombie_infection/infection
+ infection = target.get_organ_slot("zombie_infection")
+ if(!infection)
+ if (target.InCritical() || (!target.check_death_method() && target.health <= HEALTH_THRESHOLD_DEAD))
+ infection = new(target)
+
+/obj/item/zombie_hand/proc/check_feast(mob/living/target, mob/living/user)
+ if(target.stat == DEAD)
+ var/hp_gained = target.maxHealth
+ target.gib()
+ user.adjustBruteLoss(-hp_gained)
+ user.adjustToxLoss(-hp_gained)
+ user.adjustFireLoss(-hp_gained)
+ user.adjustCloneLoss(-hp_gained)
+ user.adjustBrainLoss(-hp_gained) // Zom Bee gibbers "BRAAAAISNSs!1!"
+
+/obj/item/zombie_hand/suicide_act(mob/living/carbon/human/user)
+ user.visible_message("[user] is ripping [user.p_their()] brains out! It looks like [user.p_theyre()] trying to commit suicide!")
+ if(ishuman(user))
+ var/mob/living/carbon/human/L = user
+ var/obj/item/organ/external/O = L.get_organ("head")
+ if(O)
+ O.droplimb()
+ return (BRUTELOSS)
\ No newline at end of file
diff --git a/code/modules/zombie/organs.dm b/code/modules/zombie/organs.dm
new file mode 100644
index 00000000000..76bce6ead3d
--- /dev/null
+++ b/code/modules/zombie/organs.dm
@@ -0,0 +1,93 @@
+/obj/item/organ/internal/zombie_infection
+ name = "festering ooze"
+ desc = "A black web of pus and viscera."
+ parent_organ = "head"
+ slot = "zombie_infection"
+ icon_state = "blacktumor"
+ var/converts_living = FALSE
+ var/causes_damage = TRUE
+ var/datum/species/old_species = /datum/species/human
+ var/living_transformation_time = 3
+ var/revive_time_min = 450
+ var/revive_time_max = 700
+ var/timer_id
+ var/dont_delete = FALSE // to prevent set_species proc in zombify from deleting the infection organ immediately
+
+/obj/item/organ/internal/zombie_infection/New()
+ . = ..()
+ if(ishuman(owner) && !ismachine(owner) && !iszombie(owner.dna.species))
+ old_species = owner.dna.species
+ GLOB.zombie_infection_list += src
+
+/obj/item/organ/internal/zombie_infection/Destroy()
+ GLOB.zombie_infection_list -= src
+ . = ..()
+
+/obj/item/organ/internal/zombie_infection/insert(mob/living/carbon/human/M, special = 0)
+ . = ..()
+ START_PROCESSING(SSobj, src)
+
+/obj/item/organ/internal/zombie_infection/remove(mob/living/carbon/human/M, special = 0)
+ STOP_PROCESSING(SSobj, src)
+ if(iszombie(M) && old_species && !dont_delete)
+ M.set_species(old_species)
+ if(timer_id)
+ deltimer(timer_id)
+ ..()
+
+/obj/item/organ/internal/zombie_infection/on_find(mob/living/finder)
+ to_chat(finder,"Inside the head is a disgusting black \
+ web of pus and viscera, bound tightly around the brain like some \
+ biological harness.")
+
+/obj/item/organ/internal/zombie_infection/process()
+ if(!ishuman(owner)) // We do not support monkey or xeno zombies. Yet.
+ qdel(src)
+ return
+ if(owner.suiciding)
+ return
+ if(owner.stat != DEAD && !converts_living)
+ return
+ if(causes_damage && !iszombie(owner) && owner.stat != DEAD)
+ owner.adjustToxLoss(1)
+ if (prob(10))
+ to_chat(owner, "You feel sick...")
+ if(!owner.get_int_organ(/obj/item/organ/internal/brain))
+ return
+ if(!iszombie(owner) && !timer_id)
+ to_chat(owner, "You can feel your heart stopping, but something isn't right... \
+ life has not abandoned your broken form. You can only feel a deep and immutable hunger that \
+ not even death can stop, you will rise again!")
+ var/revive_time = rand(revive_time_min, revive_time_max)
+ var/flags = TIMER_STOPPABLE
+ timer_id = addtimer(CALLBACK(src, .proc/zombify), revive_time, flags)
+
+/obj/item/organ/internal/zombie_infection/proc/zombify()
+ timer_id = null
+ if(owner.stat != DEAD && !converts_living)
+ return
+
+ if(!iszombie(owner))
+ old_species = owner.dna.species
+ dont_delete = TRUE // make sure the remove code above does not run when the species code replaces all the organs
+ owner.set_species(/datum/species/zombie/infectious)
+ for(var/thing in owner.viruses) // cure any new crit viruses
+ var/datum/disease/D = thing
+ D.cure(0)
+ dont_delete = FALSE
+ //Fully heal the zombie's damage the first time they rise
+ owner.setToxLoss(0)
+ owner.setOxyLoss(0)
+ owner.setBrainLoss(0)
+ owner.setCloneLoss(0)
+ owner.SetLoseBreath(0)
+ owner.heal_overall_damage(INFINITY,INFINITY,TRUE,TRUE,FALSE)
+
+ if(owner.update_revive())
+ owner.grab_ghost()
+
+ owner.visible_message("[owner] suddenly convulses, as [owner.p_they()] stagger to [owner.p_their()] feet and gain a ravenous hunger in [owner.p_their()] eyes!", "You HUNGER!")
+ playsound(owner.loc, 'sound/hallucinations/far_noise.ogg', 50, 1)
+ owner.do_jitter_animation(living_transformation_time)
+ owner.Stun(living_transformation_time)
+ to_chat(owner, "You are now a zombie! Do not seek to be cured, do not help any non-zombies in any way, do not harm your zombie brethren and spread the disease by killing others. You are a creature of hunger and violence.")
diff --git a/goon/browserassets/css/browserOutput-dark.css b/goon/browserassets/css/browserOutput-dark.css
index b19cc07275f..426493dfc88 100644
--- a/goon/browserassets/css/browserOutput-dark.css
+++ b/goon/browserassets/css/browserOutput-dark.css
@@ -336,6 +336,7 @@ h1.alert, h2.alert {color: #FFF;}
.skrell {color: #00CED1;}
.solcom {color: #8282FB;}
.com_srus {color: #7c4848;}
+.zombie {color: #ff0000;}
.soghun {color: #228B22;}
.changeling {color: #800080;}
.vox {color: #AA00AA;}
@@ -389,7 +390,6 @@ h1.alert, h2.alert {color: #FFF;}
.whisper {font-style:italic;color:#CCCCCC;}
/* Recruiting stuff */
.recruit {color: #5c00e6; font-weight: bold; font-style: italic;}
-"}
.memo {color: #638500; text-align: center;}
.memoedit {text-align: center; font-size: 75%;}
diff --git a/goon/browserassets/css/browserOutput.css b/goon/browserassets/css/browserOutput.css
index 3b77bb1f97e..07ae914de62 100644
--- a/goon/browserassets/css/browserOutput.css
+++ b/goon/browserassets/css/browserOutput.css
@@ -333,6 +333,7 @@ h1.alert, h2.alert {color: #000000;}
.skrell {color: #00CED1;}
.solcom {color: #22228B;}
.com_srus {color: #7c4848;}
+.zombie {color: #ff0000;}
.soghun {color: #228B22;}
.changeling {color: #800080;}
.vox {color: #AA00AA;}
@@ -386,7 +387,6 @@ h1.alert, h2.alert {color: #000000;}
.whisper {font-style:italic;color:#333333;}
/* Recruiting stuff */
.recruit {color: #5c00e6; font-weight: bold; font-style: italic;}
-"}
.memo {color: #638500; text-align: center;}
.memoedit {text-align: center; font-size: 75%;}
diff --git a/icons/mob/human_races/r_def_zombie.dmi b/icons/mob/human_races/r_def_zombie.dmi
new file mode 100644
index 00000000000..007eb3f31f3
Binary files /dev/null and b/icons/mob/human_races/r_def_zombie.dmi differ
diff --git a/icons/mob/human_races/r_zombie.dmi b/icons/mob/human_races/r_zombie.dmi
new file mode 100644
index 00000000000..007eb3f31f3
Binary files /dev/null and b/icons/mob/human_races/r_zombie.dmi differ
diff --git a/paradise.dme b/paradise.dme
index 03edc0adbcf..5175a37e57d 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -1829,6 +1829,7 @@
#include "code\modules\mob\living\carbon\human\species\vox.dm"
#include "code\modules\mob\living\carbon\human\species\vulpkanin.dm"
#include "code\modules\mob\living\carbon\human\species\wryn.dm"
+#include "code\modules\mob\living\carbon\human\species\zombies.dm"
#include "code\modules\mob\living\carbon\slime\death.dm"
#include "code\modules\mob\living\carbon\slime\emote.dm"
#include "code\modules\mob\living\carbon\slime\examine.dm"
@@ -2470,6 +2471,8 @@
#include "code\modules\vr\vr_controller.dm"
#include "code\modules\vr\vr_goggles.dm"
#include "code\modules\vr\vr_outfits.dm"
+#include "code\modules\zombie\items.dm"
+#include "code\modules\zombie\organs.dm"
#include "goon\code\datums\browserOutput.dm"
#include "interface\interface.dm"
#include "interface\skin.dmf"