diff --git a/code/__DEFINES/antagonists.dm b/code/__DEFINES/antagonists.dm
index 6400d4231af6..0bed41cbf60b 100644
--- a/code/__DEFINES/antagonists.dm
+++ b/code/__DEFINES/antagonists.dm
@@ -127,3 +127,9 @@
#define IS_VASSAL(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/vassal))
#define IS_MONSTERHUNTER(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/monsterhunter))
+//Zombie defines
+#define SMOKER "Smoker"
+#define RUNNER "Runner"
+#define SPITTER "Spitter"
+#define JUGGERNAUT "Juggernaut"
+#define BRAINY "Brainy"
diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm
index af48d4c11a74..1033aeaa05a4 100644
--- a/code/__DEFINES/is_helpers.dm
+++ b/code/__DEFINES/is_helpers.dm
@@ -72,8 +72,6 @@ GLOBAL_LIST_INIT(turfs_without_ground, typecacheof(list(
#define isslimeperson(A) (is_species(A, /datum/species/jelly/slime))
#define isluminescent(A) (is_species(A, /datum/species/jelly/luminescent))
#define iszombie(A) (is_species(A, /datum/species/zombie))
-#define isinfectedzombie(A) (is_species(A, /datum/species/zombie/infectious/gamemode))
-#define isspitter(A) (is_species(A, /datum/species/zombie/infectious/gamemode/spitter))
#define isskeleton(A) (is_species(A, /datum/species/skeleton))
#define ismoth(A) (is_species(A, /datum/species/moth))
#define ishumanbasic(A) (is_species(A, /datum/species/human))
@@ -87,6 +85,14 @@ GLOBAL_LIST_INIT(turfs_without_ground, typecacheof(list(
#define isandroid(A) (is_species(A, /datum/species/android))
#define isdummy(A) (istype(A, /mob/living/carbon/human/dummy))
+//zombie gamemode mobs and species
+#define isinfectedzombie(A) (is_species(A, /datum/species/zombie/infectious/gamemode))
+#define issmoker(A) (is_species(A, /datum/species/zombie/infectious/gamemode/smoker))
+#define isrunner(A) (is_species(A, /datum/species/zombie/infectious/gamemode/runner))
+#define isspitter(A) (is_species(A, /datum/species/zombie/infectious/gamemode/spitter))
+#define isjuggernaut(A) (is_species(A, /datum/species/zombie/infectious/gamemode/juggernaut))
+#define isbrainy(A) (istype(A, /mob/living/simple_animal/horror/brainy))
+
//more carbon mobs
#define ismonkey(A) (istype(A, /mob/living/carbon/monkey))
diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm
index fef4e566b9ed..bf5654651150 100644
--- a/code/__DEFINES/status_effects.dm
+++ b/code/__DEFINES/status_effects.dm
@@ -125,6 +125,10 @@
#define STATUS_EFFECT_BRAZIL_PENANCE /datum/status_effect/brazil_penance //controls heretic sacrifice
+#define STATUS_EFFECT_ZOMBIE_ACID /datum/status_effect/zombie_acid //spitter zombie ability acid effect
+
+#define STATUS_EFFECT_ZOMBIE_ROT /datum/status_effect/zombie_rot //prevents healing from happening + slowly damages you
+
#define STATUS_EFFECT_EXHUMED /datum/status_effect/exhumed //controls the rate of aides reviving
/////////////
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index ee69e73fe874..bee400444588 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -231,6 +231,8 @@
#define TRAIT_BADMAIL "badmail" //Your mail is going to be worse than average
#define TRAIT_SHORT_TELOMERES "short_telomeres" //You cannot be CLOONED
#define TRAIT_LONG_TELOMERES "long_telomeres" //You get CLOONED faster!!!
+#define TRAIT_DODGE "dodge" //projectile pierce
+#define TRAIT_NO_HEAL "no_heal" //exactly what you think
/// This person is crying
#define TRAIT_CRYING "crying"
@@ -352,6 +354,7 @@
#define HOLDER_TRAIT "holder_trait"
#define SINFULDEMON_TRAIT "sinfuldemon"
#define CHANGESTING_TRAIT "changesting"
+#define ZOMBIE_TRAIT "zombie"
#define POSIBRAIN_TRAIT "positrait"
///Traits given by station traits
diff --git a/code/__DEFINES/zombies.dm b/code/__DEFINES/zombies.dm
new file mode 100644
index 000000000000..c84273f1287b
--- /dev/null
+++ b/code/__DEFINES/zombies.dm
@@ -0,0 +1,13 @@
+///Flags for each class
+#define SMOKER_BITFLAG (1<<0)
+#define RUNNER_BITFLAG (1<<1)
+#define SPITTER_BITFLAG (1<<2)
+#define JUGGERNAUT_BITFLAG (1<<3)
+#define BRAINY_BITFLAG (1<<4)
+
+///Upgrades for all classes.
+#define SECTOR_COMMON "sector_common"
+///Upgrades for a specific class.
+#define SECTOR_CLASS "sector_class"
+///Upgrades that fit under other conditions.
+#define SECTOR_SPECIAL "sector_special"
diff --git a/code/__DEFINES/{yogs_defines}/is_helpers.dm b/code/__DEFINES/{yogs_defines}/is_helpers.dm
index 2d0791cb8c73..c4b22cc5253e 100644
--- a/code/__DEFINES/{yogs_defines}/is_helpers.dm
+++ b/code/__DEFINES/{yogs_defines}/is_helpers.dm
@@ -18,3 +18,7 @@
#define is_syndicate(M) (istype(M, /mob/living) && is_traitor(M) || is_blood_brother(M) || is_nukeop(M) || is_infiltrator(M))
#define isspacepod(A) (istype(A, /obj/spacepod))
+
+#define IS_INFECTED(M) (isliving(M) && M.mind?.has_antag_datum(/datum/antagonist/zombie))
+
+#define IS_SPECIALINFECTED(M) (isliving(M) && M.mind?.has_antag_datum(/datum/antagonist/zombie/special))
diff --git a/code/_onclick/hud/zombie.dm b/code/_onclick/hud/zombie.dm
new file mode 100644
index 000000000000..d7a24cd8bcfc
--- /dev/null
+++ b/code/_onclick/hud/zombie.dm
@@ -0,0 +1,6 @@
+/atom/movable/screen/zombie_infection
+ name = "infection"
+ icon = 'icons/mob/zombie_base.dmi'
+ icon_state = "zombie_counter"
+ screen_loc = ui_lingchemdisplay
+ invisibility = INVISIBILITY_ABSTRACT
diff --git a/code/datums/components/zombie_infection.dm b/code/datums/components/zombie_infection.dm
new file mode 100644
index 000000000000..5abc531b2c7f
--- /dev/null
+++ b/code/datums/components/zombie_infection.dm
@@ -0,0 +1,54 @@
+/datum/component/zombie_infection
+ //see code\modules\zombie\items.dm
+ var/scaled_infection
+ var/infection_chance
+ var/organ_to_insert
+
+/datum/component/zombie_infection/Initialize(scaled_infection = FALSE, infection_chance = 100, organ_to_insert = /obj/item/organ/zombie_infection)
+ if(!isitem(parent))
+ return COMPONENT_INCOMPATIBLE
+ src.scaled_infection = scaled_infection
+ src.infection_chance = infection_chance
+ src.organ_to_insert = organ_to_insert
+
+/datum/component/zombie_infection/RegisterWithParent()
+ RegisterSignal(parent, COMSIG_ITEM_AFTERATTACK, .proc/spread_infection)
+
+/datum/component/zombie_infection/UnregisterFromParent()
+ UnregisterSignal(parent, COMSIG_ITEM_AFTERATTACK)
+
+/datum/component/zombie_infection/proc/spread_infection(obj/item/source, atom/target, mob/user, proximity_flag, click_parameters)
+ if(!proximity_flag)
+ return
+
+ if(!isliving(target))
+ return
+
+ if(ishuman(target))
+ var/mob/living/carbon/human/H = target
+ var/obj/item/bodypart/L = H.get_bodypart(check_zone(user.zone_selected))
+ if(H.health <= HEALTH_THRESHOLD_FULLCRIT || (L && L.status != BODYPART_ROBOTIC))//no more infecting via metal limbs unless they're in hard crit and probably going to die
+ var/flesh_wound = ran_zone(user.zone_selected)
+ if(scaled_infection)
+ var/scaled_infection = ((100 - H.getarmor(flesh_wound, MELEE))/100) ^ 1.5 //20 armor -> 71.5% chance of infection
+ if(prob(infection_chance * scaled_infection)) //40 armor -> 46.5% chance
+ try_to_zombie_infect(target, organ_to_insert) //60 armor -> 25.3% chance
+ else //80 armor -> 8.9% chance
+ if(prob(infection_chance - H.getarmor(flesh_wound, MELEE)))
+ try_to_zombie_infect(target, organ_to_insert)
+ else
+ check_feast(target, user)
+
+/datum/component/zombie_infection/proc/check_feast(mob/living/target, mob/living/user)
+ if(target.stat != DEAD)
+ return
+ var/hp_gained = target.maxHealth
+ target.gib()
+ // zero as argument for no instant health update
+ user.adjustBruteLoss(-hp_gained, 0)
+ user.adjustToxLoss(-hp_gained, 0)
+ user.adjustFireLoss(-hp_gained, 0)
+ user.adjustCloneLoss(-hp_gained, 0)
+ user.updatehealth()
+ user.adjustOrganLoss(ORGAN_SLOT_BRAIN, -hp_gained) // Zom Bee gibbers "BRAAAAISNSs!1!"
+ user.set_nutrition(min(user.nutrition + hp_gained, NUTRITION_LEVEL_FULL))
diff --git a/code/datums/status_effects/debuffs.dm b/code/datums/status_effects/debuffs.dm
index c176adc015f9..7956bad198e2 100644
--- a/code/datums/status_effects/debuffs.dm
+++ b/code/datums/status_effects/debuffs.dm
@@ -1218,6 +1218,103 @@
to_chat(tamer, span_notice("[M] is now friendly after exposure to the flowers!"))
. = ..()
+/datum/status_effect/zombie_acid
+ id = "zombie_acid"
+ status_type = STATUS_EFFECT_UNIQUE
+ examine_text = span_warning("SUBJECTPRONOUN is covered with a repugnant green goo, throwing water should help a bit.")
+ tick_interval = 1 SECONDS
+ alert_type = /atom/movable/screen/alert/status_effect/zombie_acid
+ var/mutable_appearance/acid_overlay
+ COOLDOWN_DECLARE(message_cooldown)
+ var/cooldown = 10 SECONDS
+
+/datum/status_effect/zombie_acid/on_apply()
+ acid_overlay = mutable_appearance('icons/effects/effects.dmi', "acid")
+ owner.add_overlay(acid_overlay)
+ return TRUE
+
+/datum/status_effect/zombie_acid/tick()
+ . = ..()
+ RegisterSignal(owner, list(COMSIG_MOVABLE_BUMP, COMSIG_MOVABLE_CROSSED), .proc/check_destroy)
+ if(get_turf(owner) == /turf/open/indestructible/sound/pool || owner.stat == DEAD)
+ owner.visible_message("The acid washes off of [owner]!")
+ qdel(src)
+ owner.adjustFireLoss(1.25)
+ if(COOLDOWN_FINISHED(src, message_cooldown))
+ to_chat(owner, span_danger("The acid burns you badly!"))
+ COOLDOWN_START(src, message_cooldown, cooldown)
+
+/datum/status_effect/zombie_acid/proc/check_destroy(mob/user, atom/cleaner)
+ if(istype(cleaner, /obj/machinery/shower))
+ var/obj/machinery/shower/S
+ if(S.on)
+ qdel(src)
+ if(istype(cleaner, /obj/effect/particle_effect/water))
+ qdel(src)
+
+/datum/status_effect/zombie_acid/Destroy()
+ UnregisterSignal(owner, list(COMSIG_MOVABLE_BUMP, COMSIG_MOVABLE_CROSSED))
+ QDEL_NULL(acid_overlay)
+ . = ..()
+
+/atom/movable/screen/alert/status_effect/zombie_acid
+ name = "Zombie Acid"
+ desc = "ACID! ACID! I NEED WATER, WASH IT OFF!"
+ icon_state = "zombie_acid"
+ alerttooltipstyle = "cult"
+
+/datum/status_effect/zombie_rot
+ id = "rot"
+ status_type = STATUS_EFFECT_UNIQUE
+ tick_interval = -1
+ alert_type = /atom/movable/screen/alert/status_effect/zombie_rot
+ examine_text = span_warning("SUBJECTPRONOUN has black, dead tissue all over!")
+ tick_interval = 1 SECONDS
+ var/mutable_appearance/rot_overlay
+
+/datum/status_effect/zombie_rot/on_apply()
+ rot_overlay = mutable_appearance('icons/effects/effects.dmi', "rot", -UNDER_SUIT_LAYER)
+ owner.add_overlay(rot_overlay)
+ ADD_TRAIT(owner, TRAIT_NO_HEAL, ZOMBIE_TRAIT) //fucked up
+ return TRUE
+
+/datum/status_effect/zombie_rot/tick()
+ . = ..()
+ if(owner.stat == DEAD)
+ owner.visible_message("[owner]s skin completely falls off!")
+ qdel(src)
+ RegisterSignal(owner, COMSIG_PARENT_ATTACKBY, .proc/check_items)
+ owner.adjustBruteLoss(2)
+
+/datum/status_effect/zombie_rot/proc/check_items(datum/source, obj/item/the_item, mob/living/user)
+ if(!the_item.get_sharpness() == SHARP_EDGED)
+ to_chat(owner, span_warning("You need a sharper item!"))
+ return
+ if(!do_after(user, 7 SECONDS, owner))
+ if(user == owner)
+ to_chat(owner, span_warning("You lose focus and cut yourself!"))
+ else
+ to_chat(user, span_warning("You lose focus and cut [owner]s skin badly!"))
+ owner.adjustBruteLoss(10)
+ return
+ if(user == owner)
+ to_chat(owner, span_notice("You cut the rot off yourself!"))
+ else
+ to_chat(user, span_notice("You cut the rot off of [owner]s body!"))
+ qdel(src)
+
+/datum/status_effect/zombie_rot/Destroy()
+ UnregisterSignal(owner, COMSIG_PARENT_ATTACKBY)
+ QDEL_NULL(rot_overlay)
+ REMOVE_TRAIT(owner, TRAIT_NO_HEAL, ZOMBIE_TRAIT)
+ . = ..()
+
+/atom/movable/screen/alert/status_effect/zombie_rot
+ name = "Rotting"
+ desc = "Your body is starting to necrose, you'll need to cut the rotten parts off with something sharp!"
+ icon_state = "rot"
+ alerttooltipstyle = "cult"
+
/datum/status_effect/exhumed
id = "exhume"
tick_interval = -1
diff --git a/code/game/gamemodes/zombie/zombie.dm b/code/game/gamemodes/zombie/zombie_conquer.dm
similarity index 65%
rename from code/game/gamemodes/zombie/zombie.dm
rename to code/game/gamemodes/zombie/zombie_conquer.dm
index 9f5f5a9de68a..7061ef7497b9 100644
--- a/code/game/gamemodes/zombie/zombie.dm
+++ b/code/game/gamemodes/zombie/zombie_conquer.dm
@@ -2,10 +2,6 @@
GLOBAL_LIST_EMPTY(zombies)
-/proc/isinfected(mob/living/M)
- return istype(M) && M.mind && M.mind.has_antag_datum(/datum/antagonist/zombie)
-
-
/datum/game_mode/zombie
name = "zombie"
config_tag = "zombie"
@@ -57,27 +53,7 @@ GLOBAL_LIST_EMPTY(zombies)
if(people_to_infect.len >= required_enemies)
return TRUE
- else
- setup_error = "Not enough zombie candidates."
- return FALSE
-
-/datum/game_mode/zombie/proc/can_evolve_tier_2()
- var/count = 0
- for(var/Z in GLOB.zombies)
- var/datum/mind/zombie = Z
- if(!zombie.current)
- continue
- var/mob/living/carbon/human/H = zombie.current
- if(H)
- if(H.stat == DEAD || QDELETED(H))
- continue
- if(istype(H.dna.species, /datum/species/zombie/infectious/gamemode/necromancer))
- count++
- else if(istype(H.dna.species, /datum/species/zombie/infectious/gamemode/coordinator))
- count++
-
- if(count < actual_roundstart_zombies)
- return TRUE
+ setup_error = "Not enough zombie candidates."
return FALSE
/datum/game_mode/zombie/post_setup()
@@ -85,15 +61,12 @@ GLOBAL_LIST_EMPTY(zombies)
main_team.setup_objectives()
- for(var/M in people_to_infect)
- var/datum/mind/minds = M
- var/datum/antagonist/zombie/antag = add_zombie(minds)
- if(!istype(antag))
- continue
+ for(var/datum/mind/minds in people_to_infect)
+ var/datum/antagonist/zombie/special/antag = add_zombie(minds)
actual_roundstart_zombies++
antag.start_timer()
- addtimer(CALLBACK(src, .proc/call_shuttle), 60 MINUTES) //Shuttle called after 1 hour if it hasn't been
+ addtimer(CALLBACK(src, .proc/call_shuttle), 1 HOURS) //Shuttle called after 1 hour if it hasn't been
. = ..()
/datum/game_mode/zombie/proc/call_shuttle()
@@ -119,8 +92,8 @@ GLOBAL_LIST_EMPTY(zombies)
return FALSE
/datum/game_mode/zombie/generate_report()
- return "We have lost contact with some local mining outposts. Our rescue teams have found nothing but decaying and rotting corpses. On one of the ships, a body in the morgue 'woke' up and started attacking the crew \
- People seem to 'turn' when attacked by these... Creatures.. We currently estimate their threat level to be VERY HIGH. If the virus somehow makes it onboard your station, send a report to Central Command immediately.\
- The only way to truly kill them is to chop their heads off. We have spotted abnormal evolutions amongst the creatures, suggesting that they have the ability to adapt to the people fighting them. Keep your guard up crew."
+ return "After an unknown alien encounter, we have lost contact with some local mining outposts. Our rescue teams have found nothing but decaying and rotting corpses. On one of the ships, a body in the morgue 'woke' up and started attacking the crew \
+ People seem to 'turn' when attacked by these... Creatures.. We currently estimate their threat level to be VERY HIGH. If they somehow makes it onboard your station, send a report to Central Command immediately.\
+ The only way to truly kill them is to incinerate or severely mangle them. We have spotted abnormal evolutions amongst the creatures, suggesting that they have the ability to adapt to the people fighting them. Keep your guard up crew."
#undef ZOMBIE_SCALING_COEFFICIENT
diff --git a/code/game/gamemodes/zombie/zombie_hud.dm b/code/game/gamemodes/zombie/zombie_hud.dm
new file mode 100644
index 000000000000..639aa237533e
--- /dev/null
+++ b/code/game/gamemodes/zombie/zombie_hud.dm
@@ -0,0 +1,30 @@
+/datum/hud
+ var/atom/movable/screen/zombie_infection/infection_display
+
+/datum/hud/New(mob/owner, ui_style = 'icons/mob/screen_midnight.dmi')
+ . = ..()
+ infection_display = new /atom/movable/screen/zombie_infection
+
+/datum/hud/human/New(mob/living/carbon/human/owner, ui_style = 'icons/mob/screen_midnight.dmi')
+ . = ..()
+ infection_display = new /atom/movable/screen/zombie_infection
+ infodisplay += infection_display
+
+/datum/hud/Destroy()
+ . = ..()
+ infection_display = null
+
+//////////////////////////// for brainy simplemob //////////////////////////
+
+/datum/hud/zombie
+ ui_style = 'icons/mob/screen_midnight.dmi'
+
+/datum/hud/zombie/New(mob/owner)
+ . = ..()
+ infection_display = new /atom/movable/screen/zombie_infection
+ infodisplay += infection_display
+
+/datum/hud/zombie/Destroy()
+ . = ..()
+ QDEL_NULL(infection_display)
+
diff --git a/code/game/gamemodes/zombie/zombie_invasion.dm b/code/game/gamemodes/zombie/zombie_invasion.dm
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/code/game/objects/effects/alien_acid.dm b/code/game/objects/effects/alien_acid.dm
index 18974a9a01aa..968a4c328abf 100644
--- a/code/game/objects/effects/alien_acid.dm
+++ b/code/game/objects/effects/alien_acid.dm
@@ -91,3 +91,30 @@
visible_message(span_warning("[target] is struggling to withstand the acid!"))
if(4)
visible_message(span_warning("[target] begins to crumble under the acid!"))
+
+/obj/effect/acid_puddle
+ gender = PLURAL
+ name = "acid puddle"
+ desc = "Burbling corrosive stuff."
+ icon_state = "acid_puddle"
+ density = FALSE
+ opacity = 0
+ anchored = TRUE
+ resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF
+ layer = ABOVE_NORMAL_TURF_LAYER
+ var/uses = 2
+
+/obj/effect/acid_puddle/Crossed(AM as mob|obj)
+ . = ..()
+ if(isliving(AM))
+ var/mob/living/L = AM
+ if(iszombie(L))
+ return
+ L.apply_status_effect(STATUS_EFFECT_ZOMBIE_ACID)
+ L.adjustFireLoss(10)
+ to_chat(L, span_warning("As you step into the acid puddle, it splashes all over you!"))
+ uses--
+ if(!uses)
+ visible_message(span_warning("The [src] dries up!"))
+ new /obj/effect/decal/cleanable/greenglow(get_turf(src))
+ qdel(src)
diff --git a/code/modules/antagonists/changeling/powers/headcrab.dm b/code/modules/antagonists/changeling/powers/headcrab.dm
index 0f70a2d6e6fa..85a856757145 100644
--- a/code/modules/antagonists/changeling/powers/headcrab.dm
+++ b/code/modules/antagonists/changeling/powers/headcrab.dm
@@ -34,7 +34,7 @@
H.confused += 3
for(var/mob/living/silicon/S in range(2,user))
to_chat(S, span_userdanger("Your sensors are disabled by a shower of blood!"))
- S.Paralyze(60)
+ S.Paralyze(6 SECONDS)
var/turf = get_turf(user)
var/mob/living/simple_animal/horror/H = user.has_horror_inside()
H?.leave_victim()
@@ -42,10 +42,10 @@
. = TRUE
sleep(0.5 SECONDS) // So it's not killed in explosion
var/mob/living/simple_animal/hostile/headcrab/crab = new(turf)
- for(var/obj/item/organ/I in organs)
- I.forceMove(crab)
- crab.origin = M
- if(crab.origin)
- crab.origin.active = 1
- crab.origin.transfer_to(crab)
+ for(var/obj/item/organ/head_organs in organs)
+ head_organs.forceMove(crab)
+ crab.stored_mind = M
+ if(crab.stored_mind)
+ crab.stored_mind.active = 1
+ crab.stored_mind.transfer_to(crab)
to_chat(crab, span_warning("You burst out of the remains of your former body in a shower of gore!"))
diff --git a/code/modules/antagonists/horror/horror.dm b/code/modules/antagonists/horror/horror.dm
index 70ec3d113a63..851e16853f0b 100644
--- a/code/modules/antagonists/horror/horror.dm
+++ b/code/modules/antagonists/horror/horror.dm
@@ -10,6 +10,7 @@
melee_damage_lower = 10
melee_damage_upper = 10
see_in_dark = 8
+ lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
stop_automated_movement = TRUE
attacktext = "bites"
speak_emote = list("gurgles")
@@ -43,6 +44,10 @@
var/list/horrorupgrades = list()
//list storing what items we have to un-glue when stopping mind control
var/list/clothing = list()
+ //default abilities
+ var/list/abilities_to_give = list(/datum/action/innate/horror/mutate, /datum/action/innate/horror/seek_soul, /datum/action/innate/horror/consume_soul,
+ /datum/action/innate/horror/talk_to_host, /datum/action/innate/horror/freeze_victim, /datum/action/innate/horror/toggle_hide, /datum/action/innate/horror/talk_to_brain,
+ /datum/action/innate/horror/take_control, /datum/action/innate/horror/leave_body, /datum/action/innate/horror/make_chems, /datum/action/innate/horror/give_back_control)
var/bonding = FALSE
var/controlling = FALSE
@@ -50,7 +55,7 @@
var/chem_regen_rate = 2
var/used_freeze
var/used_target
- var/horror_chems = list(/datum/horror_chem/epinephrine,/datum/horror_chem/mannitol,/datum/horror_chem/bicaridine,/datum/horror_chem/kelotane,/datum/horror_chem/charcoal)
+ var/horror_chems = list(/datum/horror_chem/epinephrine, /datum/horror_chem/mannitol, /datum/horror_chem/bicaridine, /datum/horror_chem/kelotane, /datum/horror_chem/charcoal)
var/leaving = FALSE
var/hiding = FALSE
@@ -61,18 +66,8 @@
..()
real_name = "[pick(GLOB.horror_names)]"
- //default abilities
- add_ability(/datum/action/innate/horror/mutate)
- add_ability(/datum/action/innate/horror/seek_soul)
- add_ability(/datum/action/innate/horror/consume_soul)
- add_ability(/datum/action/innate/horror/talk_to_host)
- add_ability(/datum/action/innate/horror/freeze_victim)
- add_ability(/datum/action/innate/horror/toggle_hide)
- add_ability(/datum/action/innate/horror/talk_to_brain)
- add_ability(/datum/action/innate/horror/take_control)
- add_ability(/datum/action/innate/horror/leave_body)
- add_ability(/datum/action/innate/horror/make_chems)
- add_ability(/datum/action/innate/horror/give_back_control)
+ for(var/datum/action/innate/ability as anything in abilities_to_give)
+ add_ability(ability)
RefreshAbilities()
var/datum/atom_hud/hud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED]
@@ -89,10 +84,9 @@
/mob/living/simple_animal/horror/examine(mob/user) // Return a more... positive description when the examiner is themselves an eldritch horror.
if(user == src) // Hey, that's me!
return list("[icon2html(src, user)] That's [src.real_name], \a [initial(src.name)].","I'm so beautiful!")
- else if(ishorror(user))
+ if(ishorror(user))
return list("[get_examine_string(user, TRUE)].","What a handsome rogue.")
- else
- return ..()
+ return ..()
//Yogs end
/mob/living/simple_animal/horror/AltClickOn(atom/A)
@@ -553,7 +547,7 @@
return
if(controlling)
- detatch()
+ detach()
forceMove(get_turf(victim))
@@ -736,7 +730,7 @@
bonding = FALSE
return
else
- RegisterSignal(victim, COMSIG_MOB_APPLY_DAMAGE, .proc/hit_detatch)
+ RegisterSignal(victim, COMSIG_MOB_APPLY_DAMAGE, .proc/hit_detach)
log_game("[src]/([src.ckey]) assumed control of [victim]/([victim.ckey] with eldritch powers.")
to_chat(src, span_warning("You plunge your probosci deep into the cortex of the host brain, interfacing directly with [victim.p_their()] nervous system.")) // Yogs -- pronouns
to_chat(victim, span_userdanger("You feel a strange shifting sensation behind your eyes as an alien consciousness displaces yours."))
@@ -774,22 +768,22 @@
var/mob/living/simple_animal/horror/B = has_horror_inside()
if(B && B.host_brain)
to_chat(src, span_danger("You withdraw your probosci, releasing control of [B.host_brain]"))
- B.detatch()
+ B.detach()
//Check for brain worms in head.
/mob/proc/has_horror_inside()
- for(var/I in contents)
- if(ishorror(I))
- return I
+ for(var/mob/living/simple_animal/horror/parasite in contents)
+ if(ishorror(parasite) || isbrainy(parasite))
+ return parasite
-/mob/living/simple_animal/horror/proc/hit_detatch()
+/mob/living/simple_animal/horror/proc/hit_detach()
if(victim.health <= 75)
- detatch()
+ detach()
to_chat(src, span_warning("It appears that [victim]s brain detected danger, and hastily took over."))
to_chat(victim, span_danger("Your body is under attack, you unconsciously forced your brain to immediately take over!"))
-/mob/living/simple_animal/horror/proc/detatch()
+/mob/living/simple_animal/horror/proc/detach()
if(!victim || !controlling)
return
diff --git a/code/modules/antagonists/horror/horror_abilities_and_upgrades.dm b/code/modules/antagonists/horror/horror_abilities_and_upgrades.dm
index 8ee4a8bd1ac1..02279d856c8c 100644
--- a/code/modules/antagonists/horror/horror_abilities_and_upgrades.dm
+++ b/code/modules/antagonists/horror/horror_abilities_and_upgrades.dm
@@ -6,13 +6,13 @@
var/blacklisted = FALSE //If the ability can't be mutated
var/soul_price = 0 //How much souls the ability costs to buy; if this is 0, it isn't listed on the catalog
var/chemical_cost = 0 //How much chemicals the ability costs to use
- var/mob/living/simple_animal/horror/B //Horror holding the ability
+ var/mob/living/simple_animal/horror/horror_owner //Horror holding the ability
var/category //category for when the ability is active, "horror" is for creature, "infest" is during infestation, "controlling" is when a horror is controlling a body
/datum/action/innate/horror/IsAvailable()
- if(!B)
+ if(!horror_owner)
return
- if(!B.has_chemicals(chemical_cost))
+ if(!horror_owner.has_chemicals(chemical_cost))
return
. = ..()
@@ -25,7 +25,7 @@
/datum/action/innate/horror/mutate/Activate()
to_chat(usr, span_velvet(span_bold("You focus on mutating your body...")))
- B.ui_interact(usr)
+ horror_owner.ui_interact(usr)
return TRUE
/datum/action/innate/horror/seek_soul
@@ -36,7 +36,7 @@
category = list("horror","infest")
/datum/action/innate/horror/seek_soul/Activate()
- B.SearchTarget()
+ horror_owner.SearchTarget()
/datum/action/innate/horror/consume_soul
name = "Consume soul"
@@ -46,7 +46,7 @@
category = list("infest")
/datum/action/innate/horror/consume_soul/Activate()
- B.ConsumeSoul()
+ horror_owner.ConsumeSoul()
/datum/action/innate/horror/talk_to_host
name = "Converse with Host"
@@ -56,7 +56,7 @@
category = list("infest")
/datum/action/innate/horror/talk_to_host/Activate()
- B.Communicate()
+ horror_owner.Communicate()
/datum/action/innate/horror/toggle_hide
name = "Toggle Hide"
@@ -66,8 +66,8 @@
category = list("horror")
/datum/action/innate/horror/toggle_hide/Activate()
- B.hide()
- button_icon_state = "horror_hiding_[B.hiding ? "true" : "false"]"
+ horror_owner.hide()
+ button_icon_state = "horror_hiding_[horror_owner.hiding ? "true" : "false"]"
UpdateButtonIcon()
/datum/action/innate/horror/talk_to_horror
@@ -94,7 +94,7 @@
category = list("control")
/datum/action/innate/horror/talk_to_brain/Activate()
- B.victim.trapped_mind_comm()
+ horror_owner.victim.trapped_mind_comm()
/datum/action/innate/horror/take_control
name = "Assume Control"
@@ -104,7 +104,7 @@
category = list("infest")
/datum/action/innate/horror/take_control/Activate()
- B.bond_brain()
+ horror_owner.bond_brain()
/datum/action/innate/horror/give_back_control
name = "Release Control"
@@ -114,7 +114,7 @@
category = list("control")
/datum/action/innate/horror/give_back_control/Activate()
- B.victim.release_control()
+ horror_owner.victim.release_control()
/datum/action/innate/horror/leave_body
name = "Release Host"
@@ -124,7 +124,7 @@
category = list("infest")
/datum/action/innate/horror/leave_body/Activate()
- B.release_victim()
+ horror_owner.release_victim()
/datum/action/innate/horror/make_chems
name = "Secrete chemicals"
@@ -135,7 +135,7 @@
category = list("infest")
/datum/action/innate/horror/make_chems/Activate()
- B.secrete_chemicals()
+ horror_owner.secrete_chemicals()
/datum/action/innate/horror/freeze_victim
name = "Knockdown victim"
@@ -145,12 +145,12 @@
category = list("horror")
/datum/action/innate/horror/freeze_victim/Activate()
- B.freeze_victim()
+ horror_owner.freeze_victim()
UpdateButtonIcon()
addtimer(CALLBACK(src, .proc/UpdateButtonIcon), 150)
/datum/action/innate/horror/freeze_victim/IsAvailable()
- if(world.time - B.used_freeze < 150)
+ if(world.time - horror_owner.used_freeze < 150)
return FALSE
else
return ..()
@@ -166,7 +166,7 @@
soul_price = 2
/datum/action/innate/horror/tentacle/IsAvailable()
- if(!active && !B.has_chemicals(chemical_cost))
+ if(!active && !horror_owner.has_chemicals(chemical_cost))
return
return ..()
@@ -180,24 +180,24 @@
/datum/action/innate/horror/tentacle/process()
..()
- active = locate(/obj/item/horrortentacle) in B.victim
+ active = locate(/obj/item/horrortentacle) in horror_owner.victim
UpdateButtonIcon()
/datum/action/innate/horror/tentacle/Activate()
- B.use_chemicals(50)
- B.victim.visible_message(span_warning("[B.victim]'s arm contorts into tentacles!"), span_notice("Your arm transforms into a giant tentacle. Examine it to see possible uses."))
- playsound(B.victim, 'sound/effects/blobattack.ogg', 30, 1)
- to_chat(B, span_warning("You transform [B.victim]'s arm into a tentacle!"))
+ horror_owner.use_chemicals(50)
+ horror_owner.victim.visible_message(span_warning("[horror_owner.victim]'s arm contorts into tentacles!"), span_notice("Your arm transforms into a giant tentacle. Examine it to see possible uses."))
+ playsound(horror_owner.victim, 'sound/effects/blobattack.ogg', 30, 1)
+ to_chat(horror_owner, span_warning("You transform [horror_owner.victim]'s arm into a tentacle!"))
var/obj/item/horrortentacle/T = new
- B.victim.put_in_hands(T)
+ horror_owner.victim.put_in_hands(T)
return TRUE
/datum/action/innate/horror/tentacle/Deactivate()
- B.victim.visible_message(span_warning("[B.victim]'s tentacle transforms back!"), span_notice("Your tentacle disappears!"))
- playsound(B.victim, 'sound/effects/blobattack.ogg', 30, 1)
- to_chat(B, span_warning("You transform [B.victim]'s arm back."))
- for(var/obj/item/horrortentacle/T in B.victim)
+ horror_owner.victim.visible_message(span_warning("[horror_owner.victim]'s tentacle transforms back!"), span_notice("Your tentacle disappears!"))
+ playsound(horror_owner.victim, 'sound/effects/blobattack.ogg', 30, 1)
+ to_chat(horror_owner, span_warning("You transform [horror_owner.victim]'s arm back."))
+ for(var/obj/item/horrortentacle/T in horror_owner.victim)
qdel(T)
return TRUE
@@ -210,7 +210,7 @@
var/transferring = FALSE
/datum/action/innate/horror/transfer_host/proc/is_transferring(var/mob/living/carbon/C)
- return transferring && C.Adjacent(B.victim)
+ return transferring && C.Adjacent(horror_owner.victim)
/datum/action/innate/horror/transfer_host/Activate()
if(transferring)
@@ -219,16 +219,16 @@
return
var/list/choices = list()
- for(var/mob/living/carbon/C in range(1,B.victim))
- if(C!=B.victim && C.Adjacent(B.victim))
+ for(var/mob/living/carbon/C in range(1, horror_owner.victim))
+ if(C != horror_owner.victim && C.Adjacent(horror_owner.victim))
choices += C
if(!choices.len)
return
var/mob/living/carbon/C = choices.len > 1 ? input(owner,"Who do you wish to infest?") in null|choices : choices[1]
- if(!C || !B)
+ if(!C || !horror_owner)
return
- if(!C.Adjacent(B.victim))
+ if(!C.Adjacent(horror_owner.victim))
return
var/obj/item/bodypart/head/head = C.get_bodypart(BODY_ZONE_HEAD)
if(!head)
@@ -241,20 +241,20 @@
if(!hasbrain)
to_chat(owner, span_warning("[C] doesn't have a brain!"))
return
- if((!C.key || !C.mind) && C != B.target.current)
+ if((!C.key || !C.mind) && C != horror_owner.target.current)
to_chat(owner, span_warning("[C]'s mind seems unresponsive. Try someone else!"))
return
if(C.has_horror_inside())
to_chat(owner, span_warning("[C] is already infested!"))
return
- to_chat(owner, span_warning("You move your tentacles away from [B.victim] and begin to transfer to [C]..."))
+ to_chat(owner, span_warning("You move your tentacles away from [horror_owner.victim] and begin to transfer to [C]..."))
var/delay = 30 SECONDS
var/silent
- if(B.victim.pulling != C)
+ if(horror_owner.victim.pulling != C)
silent = TRUE
else
- switch(B.victim.grab_state)
+ switch(horror_owner.victim.grab_state)
if(GRAB_PASSIVE)
delay = 20 SECONDS
if(GRAB_AGGRESSIVE)
@@ -265,18 +265,18 @@
delay = 3 SECONDS
transferring = TRUE
- if(!do_after(B.victim, delay, C, extra_checks = CALLBACK(src, .proc/is_transferring, C), stayStill = FALSE))
+ if(!do_after(horror_owner.victim, delay, C, extra_checks = CALLBACK(src, .proc/is_transferring, C), stayStill = FALSE))
to_chat(owner, span_warning("As [C] moves away, your transfer gets interrupted!"))
transferring = FALSE
return
transferring = FALSE
- if(!C || !B || !C.Adjacent(B.victim))
+ if(!C || !horror_owner || !C.Adjacent(horror_owner.victim))
return
- B.leave_victim()
- B.Infect(C)
+ horror_owner.leave_victim()
+ horror_owner.Infect(C)
if(!silent)
to_chat(C, span_warning("Something slimy wiggles into your ear!"))
- playsound(B, 'sound/effects/blobattack.ogg', 30, 1)
+ playsound(horror_owner, 'sound/effects/blobattack.ogg', 30, 1)
/datum/action/innate/horror/jumpstart_host
name = "Revive Host"
@@ -286,7 +286,7 @@
soul_price = 2
/datum/action/innate/horror/jumpstart_host/Activate()
- B.jumpstart()
+ horror_owner.jumpstart()
/datum/action/innate/horror/view_memory
name = "View Memory"
@@ -296,7 +296,7 @@
soul_price = 1
/datum/action/innate/horror/view_memory/Activate()
- B.view_memory()
+ horror_owner.view_memory()
/datum/action/innate/horror/chameleon
name = "Chameleon Skin"
@@ -306,8 +306,8 @@
soul_price = 1
/datum/action/innate/horror/chameleon/Activate()
- B.go_invisible()
- button_icon_state = "horror_sneak_[B.invisible ? "true" : "false"]"
+ horror_owner.go_invisible()
+ button_icon_state = "horror_sneak_[horror_owner.invisible ? "true" : "false"]"
UpdateButtonIcon()
/datum/action/innate/horror/lube_spill
@@ -320,20 +320,20 @@
var/cooldown = 0
/datum/action/innate/horror/lube_spill/IsAvailable()
- if(cooldown > world.time || !B.has_chemicals(chemical_cost) || !B.can_use_ability())
+ if(cooldown > world.time || !horror_owner.has_chemicals(chemical_cost) || !horror_owner.can_use_ability())
return
return ..()
/datum/action/innate/horror/lube_spill/Activate()
- B.use_chemicals(chemical_cost)
+ horror_owner.use_chemicals(chemical_cost)
cooldown = world.time + 10 SECONDS
UpdateButtonIcon()
addtimer(CALLBACK(src, .proc/UpdateButtonIcon), 10 SECONDS)
- B.visible_message(span_warning("[B] spins and throws some sort of substance!"), span_notice("Your flail oily substance around you!"))
- flick("horror_spin", B)
- playsound(B, 'sound/effects/blobattack.ogg', 25, 1)
- for(var/turf/open/t in range(1, B))
- if(prob(60) && B.Adjacent(t))
+ horror_owner.visible_message(span_warning("[horror_owner] spins and throws some sort of substance!"), span_notice("Your flail oily substance around you!"))
+ flick("horror_spin", horror_owner)
+ playsound(horror_owner, 'sound/effects/blobattack.ogg', 25, TRUE)
+ for(var/turf/open/t in range(1, horror_owner))
+ if(prob(60) && horror_owner.Adjacent(t))
t.MakeSlippery(TURF_WET_LUBE, 50)
return TRUE
diff --git a/code/modules/antagonists/horror/horror_datums.dm b/code/modules/antagonists/horror/horror_datums.dm
index a6342c23738a..241b6339a968 100644
--- a/code/modules/antagonists/horror/horror_datums.dm
+++ b/code/modules/antagonists/horror/horror_datums.dm
@@ -327,6 +327,10 @@
try_resist()
/mob/living/captive_brain/proc/try_resist()
+ if(isbrainy(H))
+ var/mob/living/simple_animal/horror/brainy/B = H
+ if(B.full_control)
+ return
var/delay = rand(20 SECONDS,30 SECONDS)
if(H.horrorupgrades["deep_control"])
delay += rand(20 SECONDS,30 SECONDS)
@@ -339,7 +343,7 @@
return
to_chat(src, span_userdanger("With an immense exertion of will, you regain control of your body!"))
to_chat(H.victim, span_danger("You feel control of the host brain ripped from your grasp, and retract your probosci before the wild neural impulses can damage you."))
- H.detatch()
+ H.detach()
/datum/antagonist/horror/get_preview_icon()
var/icon/horror_icon = icon('icons/mob/animal.dmi', "horror_preview")
diff --git a/code/modules/antagonists/horror/horror_mutate.dm b/code/modules/antagonists/horror/horror_mutate.dm
index 97cb19c20572..6fdfbd217b2a 100644
--- a/code/modules/antagonists/horror/horror_mutate.dm
+++ b/code/modules/antagonists/horror/horror_mutate.dm
@@ -11,7 +11,7 @@
if(has_ability(typepath))
return
var/datum/action/innate/horror/action = new typepath
- action.B = src
+ action.horror_owner = src
horrorabilities += action
RefreshAbilities()
to_chat(src, span_velvet("You have mutated the [action.name]."))
diff --git a/code/modules/antagonists/zombie/abilities/_abilities.dm b/code/modules/antagonists/zombie/abilities/_abilities.dm
new file mode 100644
index 000000000000..28bdd6a7e5d3
--- /dev/null
+++ b/code/modules/antagonists/zombie/abilities/_abilities.dm
@@ -0,0 +1,356 @@
+//this is mostly kind of bloodsucker abilities code since the base for powers there works very well
+#define ZOMBIE_TOGGLEABLE "toggleable" //self explainable
+#define ZOMBIE_FIREABLE "fireable" //same as above
+#define ZOMBIE_INSTANT "instant" //yeah
+
+/datum/action/innate/zombie
+ name = "Putrid"
+ desc = "make a bug report if you see this."
+ background_icon_state = "bg_zombie"
+ icon_icon = 'icons/mob/actions/actions_zombie.dmi'
+ buttontooltipstyle = "cult"
+ COOLDOWN_DECLARE(zombie_ability_cooldown)
+ var/obj/effect/proc_holder/zombie/zombie_ph //proc_holder as in the one that holds the proc (it makes no sense)
+ var/ability_type = null
+ var/cooldown = 0
+ var/fireable_power_usable = FALSE
+ var/range = 99
+ var/custom_mouse_icon = null
+
+/datum/action/innate/zombie/New()
+ . = ..()
+ if(ability_type == ZOMBIE_FIREABLE)
+ zombie_ph = new()
+ zombie_ph.linked_power = src
+ UpdateDesc()
+
+/datum/action/innate/zombie/proc/UpdateDesc() //we ARE DOING THIS because i love information
+ desc = initial(desc)
+ if(cooldown)
+ desc += "
COOLDOWN: [name] has a cooldown of [cooldown / 10] seconds."
+
+/datum/action/innate/zombie/Grant(mob/M)
+ if(IS_INFECTED(M))
+ var/datum/antagonist/zombie/zombie_owner = M.mind?.has_antag_datum(/datum/antagonist/zombie)
+ zombie_owner.zombie_abilities += src
+ . = ..()
+
+/datum/action/innate/zombie/Remove(mob/M)
+ if(IS_INFECTED(M))
+ var/datum/antagonist/zombie/zombie_owner = M.mind?.has_antag_datum(/datum/antagonist/zombie)
+ zombie_owner.zombie_abilities -= src
+ . = ..()
+
+/datum/action/innate/zombie/Destroy()
+ if(active)
+ STOP_PROCESSING(SSfastprocess, src)
+ . = ..()
+
+/datum/action/innate/zombie/Trigger()
+ if(!..())
+ return FALSE
+
+ if(ability_type == ZOMBIE_FIREABLE)
+ var/mob/living/user = owner
+ if(user.ranged_ability)
+ user.ranged_ability.remove_ranged_ability()
+ zombie_ph.add_ranged_ability(user)
+ UpdateButtonIcon()
+ return TRUE
+
+/datum/action/innate/zombie/IsAvailable()
+ if(!COOLDOWN_FINISHED(src, zombie_ability_cooldown))
+ return FALSE
+ if(!IS_INFECTED(owner))
+ return FALSE
+ . = ..()
+
+/datum/action/innate/zombie/Activate()
+ switch(ability_type)
+ if(ZOMBIE_TOGGLEABLE)
+ START_PROCESSING(SSfastprocess, src)
+ if(ZOMBIE_FIREABLE)
+ fireable_power_usable = TRUE
+ check_flags += AB_CHECK_CONSCIOUS
+ owner?.client?.mouse_override_icon = custom_mouse_icon
+ if(ZOMBIE_INSTANT)
+ StartCooldown()
+ return
+ active = TRUE
+
+/datum/action/innate/zombie/Deactivate(forced = FALSE)
+ switch(ability_type)
+ if(ZOMBIE_TOGGLEABLE)
+ STOP_PROCESSING(SSfastprocess, src)
+ if(ZOMBIE_FIREABLE)
+ zombie_ph.remove_ranged_ability()
+ fireable_power_usable = FALSE
+ active = FALSE
+ owner?.client?.mouse_override_icon = null
+ owner?.update_mouse_pointer()
+ UpdateButtonIcon()
+ if(!forced && ability_type == ZOMBIE_FIREABLE)
+ return
+ StartCooldown()
+
+/datum/action/innate/zombie/proc/StartCooldown()
+ button.color = rgb(128,0,0,128)
+ button.alpha = 100
+ COOLDOWN_START(src, zombie_ability_cooldown, cooldown)
+ addtimer(CALLBACK(src, .proc/alpha_in), cooldown)
+ UpdateDesc() //qol
+
+/datum/action/innate/zombie/proc/alpha_in()
+ button.color = rgb(255,255,255,255)
+ button.alpha = 255
+
+/datum/action/innate/zombie/UpdateButtonIcon(force = FALSE)
+ background_icon_state = active ? initial(background_icon_state) + "_on" : initial(background_icon_state)
+ . = ..()
+
+/datum/action/innate/zombie/proc/IsTargetable(atom/target_atom)
+ if(!(target_atom in view(range, owner)))
+ if(range > 1)
+ to_chat(owner, span_warning("Target out of range."))
+ return FALSE
+ return istype(target_atom)
+
+/datum/action/innate/zombie/proc/StartFiringAbility(atom/target_atom) //where we do initial checks and end
+ if(target_atom == owner)
+ return
+
+ if(!fireable_power_usable)
+ return
+
+ if(!IsTargetable(target_atom))
+ return
+
+ UseFireableAbility(target_atom)
+ Deactivate(TRUE)
+
+/datum/action/innate/zombie/proc/UseFireableAbility(atom/target_atom)
+ log_combat(owner, target_atom, "used zombie ability [name] on")
+
+/obj/effect/proc_holder/zombie
+ var/datum/action/innate/zombie/linked_power
+
+/obj/effect/proc_holder/zombie/InterceptClickOn(mob/living/caller, params, atom/targeted_atom)
+ return linked_power.StartFiringAbility(targeted_atom)
+
+///Default abilities that all roundstart special zombies get. They need use infection points to be used and provide a significant bonus.
+/datum/action/innate/zombie/default
+ var/cost = 0
+ var/constant_cost = 0
+
+/datum/action/innate/zombie/default/UpdateDesc()
+ . = ..()
+ if(cost || constant_cost)
+ desc += "
COST: name costs [cost] to activate [cost ? "and" : "but"] consumes [constant_cost] to maintain active."
+
+/datum/action/innate/zombie/default/IsAvailable()
+ var/datum/antagonist/zombie/zombie_owner = owner.mind?.has_antag_datum(/datum/antagonist/zombie)
+ if(zombie_owner?.infection < cost)
+ return FALSE
+ . = ..()
+
+/datum/action/innate/zombie/default/Activate()
+ var/datum/antagonist/zombie/zombie_owner = owner.mind?.has_antag_datum(/datum/antagonist/zombie)
+ if(zombie_owner.zombie_mutations["reduced_cost"] || ability_type != ZOMBIE_INSTANT)
+ cost = initial(cost) / 2
+ if(ability_type == ZOMBIE_TOGGLEABLE || ability_type == ZOMBIE_INSTANT)
+ if(!zombie_owner.manage_infection(cost)) //failsafe
+ to_chat(owner, span_warning("You don't have enough infection points to activate this ability!"))
+ return
+ . = ..()
+
+/datum/action/innate/zombie/default/UseFireableAbility()
+ . = ..()
+ var/datum/antagonist/zombie/zombie_owner = owner.mind?.has_antag_datum(/datum/antagonist/zombie)
+ if(!zombie_owner.manage_infection(cost))
+ to_chat(owner, span_warning("You don't have enough infection points to activate this ability!"))
+ Deactivate()
+ return
+
+/datum/action/innate/zombie/default/process()
+ UpdateButtonIcon()
+ var/datum/antagonist/zombie/zombie_owner = owner.mind?.has_antag_datum(/datum/antagonist/zombie)
+ if(!zombie_owner.manage_infection(constant_cost))
+ to_chat(owner, span_warning("You don't have enough infection points to maintain this ability active!"))
+ Deactivate()
+ return
+
+/datum/action/innate/zombie/menu
+ name = "Menu"
+ desc = ""
+ button_icon_state = "adaptation_menu"
+ ability_type = ZOMBIE_INSTANT
+
+/datum/action/innate/zombie/menu/Activate()
+ var/datum/antagonist/zombie/zombie_owner = owner.mind?.has_antag_datum(/datum/antagonist/zombie)
+ zombie_owner.ui_interact(owner)
+ . = ..()
+
+/datum/action/innate/zombie/zomb
+ name = "Zombify!"
+ desc = "Initiate the infection, and kill this host.. THIS ACTION IS INSTANT."
+ button_icon_state = "zombify"
+ ability_type = ZOMBIE_INSTANT
+
+/datum/action/innate/zombie/zomb/Activate(forced = FALSE)
+ var/mob/living/carbon/human/H = owner
+ var/datum/antagonist/zombie/zombie_owner = H.mind?.has_antag_datum(/datum/antagonist/zombie)
+ if(!forced)
+ if(tgui_alert(H, "Are you sure you want to kill yourself, and revive as a zombie some time after?", "Confirmation", list("Yes", "No")) == "No")
+ return FALSE
+
+ if(!H.getorganslot(ORGAN_SLOT_ZOMBIE))
+ var/obj/item/organ/zombie_infection/gamemode/special/ZI = new()
+ ZI.Insert(H)
+
+ H.mind?.zombified = TRUE
+ H.death()
+ zombie_owner.zombify.Remove(H)
+ zombie_owner.zombified = TRUE
+ zombie_owner.uncuff.Grant(H)
+ zombie_owner.advance_mutation_tier()
+ . = ..()
+
+/datum/action/innate/zombie/uncuff
+ name = "Break Free!"
+ desc = "Breaks you free from handcuffs."
+ button_icon_state = "uncuff"
+
+/datum/action/innate/zombie/uncuff/Activate(forced = FALSE)
+ var/mob/living/carbon/human/H = usr
+ H.uncuff()
+
+
+/datum/action/innate/zombie/talk
+ name = "Chat"
+ desc = "Chat with your fellow infected."
+ button_icon_state = "zombie_comms"
+
+/datum/action/innate/zombie/talk/Activate()
+ var/input = stripped_input(usr, "Please choose a message to tell to the other zombies.", "Infected Communications", "")
+ if(!input || !IsAvailable())
+ return
+
+ talk(usr, input)
+
+/datum/action/innate/zombie/talk/proc/talk(mob/living/user, message)
+ var/my_message
+ if(!message)
+ return
+ my_message = span_cult("(Zombie) [findtextEx(user.name, user.real_name) ? user.name : "[user.real_name] (as [user.name])"]: [message]")
+ for(var/i in GLOB.player_list)
+ var/mob/M = i
+ if(IS_INFECTED(M))
+ to_chat(M, my_message)
+ else if(M in GLOB.dead_mob_list)
+ var/link = FOLLOW_LINK(M, user)
+ to_chat(M, "[link] [my_message]")
+
+ user.log_talk(message, LOG_SAY, tag="zombie")
+
+/datum/action/innate/zombie/choose_class
+ name = "Evolve"
+ desc = "Evolve into a special class."
+ button_icon_state = "evolve"
+ ability_type = ZOMBIE_INSTANT
+
+/datum/action/innate/zombie/choose_class/Activate()
+ var/list/zombie_classes = list(SMOKER, RUNNER, SPITTER, JUGGERNAUT) //brainy is currently broken
+ var/selected = null
+ var/list/radial_menu = list()
+ for(var/I in zombie_classes)
+ radial_menu[I] = image('icons/mob/zombie_base.dmi', icon_state = "[I]")
+ selected = show_radial_menu(owner, owner, radial_menu, tooltips = TRUE)
+ if(!selected || !IsAvailable())
+ return
+ if(!isinfectedzombie(owner))
+ return
+ evolve(selected)
+ . = ..()
+
+/datum/action/innate/zombie/choose_class/IsAvailable(forced = FALSE)
+ if(!IS_INFECTED(owner))
+ return
+ var/datum/antagonist/zombie/Z = locate() in owner.mind.antag_datums
+
+ if(Z.class_chosen && !forced)
+ return FALSE
+ return ..()
+
+/datum/action/innate/zombie/choose_class/proc/evolve(class)
+ var/mob/living/carbon/human/H = owner
+ var/datum/antagonist/zombie/zombie_owner = owner?.mind?.has_antag_datum(/datum/antagonist/zombie)
+ if(!IS_INFECTED(H))
+ return
+ var/list/class_powers = list()
+ switch(class)
+ if(SMOKER)
+ class_powers = list(new /datum/action/innate/zombie/default/smoke, new /datum/action/innate/zombie/scorch)
+ H.set_species(/datum/species/zombie/infectious/gamemode/smoker)
+ if(RUNNER)
+ class_powers = list(new /datum/action/innate/zombie/default/endure, new /datum/action/innate/zombie/parkour)
+ H.set_species(/datum/species/zombie/infectious/gamemode/runner)
+ to_chat(owner, span_warning("You can now run, and your movement speed is considerably faster. You do less damage and can take less damage though."))
+ if(SPITTER)
+ class_powers = list(new /datum/action/innate/zombie/default/overclock, new /datum/action/innate/zombie/default/spit)
+ H.set_species(/datum/species/zombie/infectious/gamemode/spitter)
+ to_chat(owner, span_warning("You can now click on walls and doors, and cover them in acid! You are weaker in combat though."))
+ if(JUGGERNAUT)
+ class_powers = list(new /datum/action/innate/zombie/default/rage, new /datum/action/innate/zombie/charge)
+ H.set_species(/datum/species/zombie/infectious/gamemode/juggernaut)
+ to_chat(owner, span_warning("You can now take quite a beating, and heal a bit slower."))
+ if(BRAINY)
+ var/mob/living/simple_animal/horror/brainy/B = new(get_turf(owner))
+ B?.zombie_owner = zombie_owner
+ owner.mind.transfer_to(B)
+ qdel(owner)
+ if(class_powers.len)
+ for(var/datum/action/innate/zombie/ability as anything in class_powers)
+ ability.Grant(owner)
+
+ owner.visible_message(span_danger("[owner] suddenly convulses, as [owner.p_they()] evolve into a [class]!"), span_alien("You have evolved into a [class]"))
+ playsound(owner.loc, 'sound/hallucinations/far_noise.ogg', 50, TRUE)
+ Remove(owner)
+ zombie_owner.class_chosen = class
+ if(ishuman(owner))
+ H.do_jitter_animation(15)
+
+/datum/action/innate/zombie/last_resort
+ name = "Evolve"
+ desc = "Evolve into a special class."
+ button_icon_state = "evolve"
+ ability_type = ZOMBIE_INSTANT
+
+/datum/action/innate/zombie/last_resort/Activate()
+ if(tgui_alert(owner, "Are you sure you want to violently leave your host?", "Horror Hotline", list("Yes", "No")) == "No")
+ return
+ var/mob/living/carbon/host_body = owner
+ var/mob/living/simple_animal/hostile/headcrab/horrorworm/escape = new(get_turf(owner))
+ owner.mind.transfer_to(escape)
+ var/obj/item/organ/brain/brain = host_body.getorganslot(ORGAN_SLOT_BRAIN) //from /obj/item/gun/ballistic/suicide_act
+ var/turf/T = get_turf(host_body)
+ host_body.visible_message(span_warning("[host_body]'s blows up, spraying blood everywhere!"))
+ var/turf/target = get_ranged_target_turf(host_body, turn(host_body.dir, 180), 3)
+ brain.Remove(host_body)
+ brain.forceMove(T)
+ var/datum/callback/gibspawner = CALLBACK(GLOBAL_PROC, /proc/spawn_atom_to_turf, /obj/effect/gibspawner/generic, brain, 1, FALSE, host_body)
+ brain.throw_at(target, 3, 1, callback=gibspawner)
+ . = ..()
+
+/datum/action/innate/zombie/bite //common zombie W
+ name = "Rotten Bite"
+ desc = "Charges your mouth with a powerful agent."
+ button_icon_state = "bite"
+ ability_type = ZOMBIE_FIREABLE
+
+/datum/action/innate/zombie/bite/IsTargetable(atom/target_atom)
+ return isliving(target_atom)
+
+/datum/action/innate/zombie/bite/UseFireableAbility()
+ . = ..()
+ var/mob/living/le_target = target
+ le_target.apply_status_effect(STATUS_EFFECT_ZOMBIE_ROT)
diff --git a/code/modules/antagonists/zombie/abilities/acid.dm b/code/modules/antagonists/zombie/abilities/acid.dm
deleted file mode 100644
index 7fbd6ce89e44..000000000000
--- a/code/modules/antagonists/zombie/abilities/acid.dm
+++ /dev/null
@@ -1,51 +0,0 @@
-/obj/effect/proc_holder/zombie/acid
- name = "Corrosive Acid"
- desc = "Drench an object in acid, destroying it over time."
- action_icon_state = "alien_acid"
- cooldown_time = 2.5 MINUTES
-
-/obj/effect/proc_holder/zombie/acid/on_gain(mob/living/carbon/user)
- user.verbs.Add(/mob/living/carbon/proc/spitter_zombie_acid)
-
-/obj/effect/proc_holder/zombie/acid/on_lose(mob/living/carbon/user)
- user.verbs.Remove(/mob/living/carbon/proc/spitter_zombie_acid)
-
-/obj/effect/proc_holder/zombie/acid/proc/corrode(atom/target,mob/living/carbon/user = usr)
- if(target in oview(1, user))
- if(target.acid_act(200, 100))
- user.visible_message(span_alertalien("[user] vomits globs of vile stuff all over [target]. It begins to sizzle and melt under the bubbling mess of acid!"))
- return TRUE
- else
- to_chat(user, span_noticealien("You cannot dissolve this object."))
-
-
- return FALSE
- else
- to_chat(src, span_noticealien("[target] is too far away."))
- return FALSE
-
-
-/obj/effect/proc_holder/zombie/acid/fire(mob/living/carbon/user)
- var/O = input("Select what to dissolve:", "Dissolve", null) as obj|turf in oview(1,user)
- if(!O || user.incapacitated())
- return FALSE
- if(corrode(O, user))
- return ..()
-
-
-/mob/living/carbon/proc/spitter_zombie_acid(O as obj|turf in oview(1)) // right click menu verb ugh
- set name = "Corrosive Acid"
-
- if(!isinfected(usr))
- return
- var/mob/living/carbon/user = usr
- var/obj/effect/proc_holder/zombie/acid/A = locate() in user.abilities
- if(!A)
- return
-
- if(!A.ready)
- to_chat(user, span_userdanger("Please wait. You will be able to use this ability in [(A.cooldown_ends - world.time) / 10] seconds"))
- return
-
- if(A.corrode(O, user))
- A.start_cooldown()
diff --git a/code/modules/antagonists/zombie/abilities/adrenaline.dm b/code/modules/antagonists/zombie/abilities/adrenaline.dm
deleted file mode 100644
index 91af48f75d45..000000000000
--- a/code/modules/antagonists/zombie/abilities/adrenaline.dm
+++ /dev/null
@@ -1,28 +0,0 @@
-/obj/effect/proc_holder/zombie/adrenaline
- name = "Adrenaline Boost"
- desc = "Makes you able to sprint for a second or two!"
- action_icon = 'icons/mob/actions/actions_changeling.dmi'
- action_icon_state = "adrenaline"
- cooldown_time = 3 MINUTES
- var/reagent_amount = 3
-
-
-/obj/effect/proc_holder/zombie/adrenaline/proc/add_reagent(mob/living/carbon/user)
- user.SetSleeping(0)
- user.SetUnconscious(0)
- user.SetStun(0)
- user.SetKnockdown(0)
- user.SetImmobilized(0)
- user.SetParalyzed(0)
- user.reagents.add_reagent(/datum/reagent/medicine/changelinghaste, reagent_amount)
- user.adjustStaminaLoss(-75)
- return TRUE
-
-
-
-/obj/effect/proc_holder/zombie/adrenaline/fire(mob/living/carbon/user)
- if(user.incapacitated())
- return FALSE
-
- if(add_reagent(user))
- return ..()
\ No newline at end of file
diff --git a/code/modules/antagonists/zombie/abilities/brainy.dm b/code/modules/antagonists/zombie/abilities/brainy.dm
new file mode 100644
index 000000000000..ce984dfb8d28
--- /dev/null
+++ b/code/modules/antagonists/zombie/abilities/brainy.dm
@@ -0,0 +1,265 @@
+/mob/living/simple_animal/horror/brainy
+ name = "brainy" //tis
+ desc = "An open head on what looks like 4 measly legs, what the hell?"
+ icon_state = "brainy"
+ icon_living = "brainy"
+ icon_dead = "brainy_dead"
+ icon_gib = "brainy_gib"
+ health = 100
+ maxHealth = 100
+ speed = -1.1
+ see_in_dark = 10
+ pass_flags = PASSTABLE | PASSGRILLE
+ abilities_to_give = list(/datum/action/innate/zombie/pounce, /datum/action/innate/horror/talk_to_host, /datum/action/innate/horror/toggle_hide,
+ /datum/action/innate/horror/talk_to_brain, /datum/action/innate/horror/take_control, /datum/action/innate/horror/leave_body,
+ /datum/action/innate/horror/give_back_control)
+ hud_type = /datum/hud/zombie
+ var/leaping = FALSE
+ var/full_control = FALSE
+ var/datum/antagonist/zombie/zombie_owner = null
+
+/mob/living/simple_animal/horror/brainy/Initialize()
+ . = ..()
+ real_name = name
+ RefreshAbilities(TRUE)
+ update_horror_hud()
+
+/* /mob/living/simple_animal/horror/brainy/update_horror_hud() //i simply cannot get this to work
+ var/datum/hud/zombie/Z = hud_used
+ var/atom/movable/screen/counter = Z.infection_display
+ counter.invisibility = 0
+ if(zombie_owner.class_chosen)
+ counter.add_overlay("overlay_[zombie_owner.class_chosen]")
+ counter.maptext = "
[round(zombie_owner.infection, 1)]
" */
+
+/mob/living/simple_animal/horror/brainy/update_icons()
+ icon_state = leaping ? initial(icon_state) + "_leap" : initial(icon_state)
+
+/mob/living/simple_animal/horror/brainy/RefreshAbilities(on_spawn = FALSE)
+ if(on_spawn)
+ GrantHorrorActions()
+ if(!zombie_owner)
+ return
+ . = ..()
+
+/mob/living/simple_animal/horror/brainy/add_ability(typepath)
+ if(has_ability(typepath))
+ return
+ var/datum/action/ability = new typepath
+ horrorabilities += ability
+ if(istype(ability, /datum/action/innate/horror))
+ var/datum/action/innate/horror/action = ability
+ action.horror_owner = src //this surprisingly took a while
+ RefreshAbilities()
+
+/mob/living/simple_animal/horror/brainy/has_ability(typepath)
+ for(var/datum/action/ability as anything in zombie_owner?.zombie_abilities)
+ if(istype(ability, typepath))
+ return ability
+ return ..()
+
+/mob/living/simple_animal/horror/brainy/regenerate_chemicals(amt)
+ if(!IS_SPECIALINFECTED(src))
+ return
+ zombie_owner?.manage_infection(-amt)
+
+/mob/living/simple_animal/horror/brainy/has_chemicals(amt)
+ return zombie_owner?.infection >= amt
+
+/mob/living/simple_animal/horror/brainy/use_chemicals(amt)
+ return zombie_owner?.manage_infection(amt)
+
+/mob/living/simple_animal/horror/brainy/detach()
+ if(full_control && victim.stat != DEAD)
+ victim.death()
+ animate(victim, color = initial(color), time = 1 SECONDS)
+ victim.adjustOrganLoss(ORGAN_SLOT_BRAIN, 5, 60)
+ . = ..()
+
+/mob/living/simple_animal/horror/brainy/GrantHorrorActions()
+ . = ..()
+ var/list/abilities_to_grant = list(new /datum/action/innate/zombie/pounce) //hardcoded like this to prevent shitton of vars but if you want to refactor it go ahead
+ for(var/datum/action/ability as anything in abilities_to_grant)
+ ability.Grant(src)
+
+/mob/living/simple_animal/horror/brainy/RemoveHorrorActions()
+ . = ..()
+ var/list/abilities_to_remove = list(/datum/action/innate/zombie/pounce)
+ for(var/datum/action/ability as anything in zombie_owner?.zombie_abilities)
+ if(is_type_in_list(ability, abilities_to_remove))
+ ability.Remove(src)
+
+/mob/living/simple_animal/horror/brainy/GrantInfestActions()
+ . = ..()
+ var/list/abilities_to_grant = list(new /datum/action/innate/zombie/default/takeover, new /datum/action/innate/zombie/default/infest, new /datum/action/innate/zombie/morph)
+ for(var/datum/action/ability as anything in abilities_to_grant)
+ ability.Grant(src)
+
+/mob/living/simple_animal/horror/brainy/RemoveInfestActions()
+ . = ..()
+ var/list/abilities_to_remove = list(/datum/action/innate/zombie/default/takeover, /datum/action/innate/zombie/default/infest, /datum/action/innate/zombie/morph)
+ for(var/datum/action/ability as anything in zombie_owner?.zombie_abilities)
+ if(is_type_in_list(ability, abilities_to_remove))
+ ability.Remove(src)
+
+/mob/living/simple_animal/horror/brainy/GrantControlActions()
+ . = ..()
+ var/list/abilities_to_grant = list(new /datum/action/innate/zombie/morph)
+ for(var/datum/action/ability as anything in abilities_to_grant)
+ ability.Grant(victim)
+
+/mob/living/simple_animal/horror/brainy/RemoveControlActions()
+ . = ..()
+ var/list/abilities_to_remove = list(/datum/action/innate/zombie/morph)
+ for(var/datum/action/ability as anything in zombie_owner?.zombie_abilities)
+ if(is_type_in_list(ability, abilities_to_remove))
+ ability.Remove(victim)
+
+////////////////////////////////////////////////////////////////////////////ABILITY LINE BREAKER////////////////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////->IF YOU WANT TO ADD NEW ABILITIES REMEMBER TO ADD THEM TO THE LISTS ABOVE<-//////////////////////////////////////////////////////
+/datum/action/innate/horror/UpdateButtonIcon(force = FALSE)
+ icon_icon = isbrainy(horror_owner) ? 'icons/mob/actions/actions_zombie.dmi' : initial(icon_icon)
+ background_icon_state = isbrainy(horror_owner) ? "bg_zombie" : initial(background_icon_state)
+ . = ..()
+
+/datum/action/innate/zombie/pounce
+ name = "Pounce"
+ desc = "Jump in a single direction, squeezing through what you can and knocking down what you can't."
+ button_icon_state = "pounce"
+ ability_type = ZOMBIE_FIREABLE
+ cooldown = 7.5 SECONDS
+ custom_mouse_icon = 'icons/effects/mouse_pointers/zombie_pounce.dmi'
+
+/datum/action/innate/zombie/pounce/Trigger()
+ if(isbrainy(owner))
+ var/mob/living/simple_animal/horror/brainy/B = owner
+ B.leaping = !B.leaping
+ B.update_icons()
+ . = ..()
+
+/datum/action/innate/zombie/pounce/IsTargetable(atom/target_atom)
+ return isliving(target_atom) || isopenturf(target_atom) || istype(target_atom, /obj/machinery/door)
+
+/datum/action/innate/zombie/pounce/UseFireableAbility(atom/target_atom)
+ . = ..()
+ var/mob/living/L = owner
+ owner.throw_at(get_turf(target_atom), 7, 1, owner, FALSE, callback = CALLBACK(src, .proc/throw_end))
+ L.Immobilize(30 SECONDS)
+ RegisterSignal(L, COMSIG_MOVABLE_BUMP, .proc/pounce_end, override = TRUE)
+
+/datum/action/innate/zombie/pounce/proc/pounce_end(mob/living/user, atom/victim)
+ if(isliving(victim))
+ var/mob/living/L = victim
+ to_chat(L, span_danger("You feel something hit your head, dropping you to the ground!"))
+ L.Immobilize(5 SECONDS)
+ L.Knockdown(10 SECONDS)
+ if(isclosedturf(victim))
+ throw_end()
+ if(isstructure(victim) || ismachinery(victim))
+ var/obj/O = victim
+ if(istype(O, /obj/machinery/door))
+ if(istype(O, /obj/machinery/door/window))
+ var/obj/machinery/door/D = O
+ D.open()
+ owner.forceMove(get_turf(O)) //plop
+ O.take_damage(10)
+ user.adjustBruteLoss(2.5) //hurtie
+ throw_end()
+
+/datum/action/innate/zombie/pounce/proc/throw_end()
+ var/mob/living/L = owner
+ UnregisterSignal(L, COMSIG_MOVABLE_BUMP)
+ L.SetImmobilized(0)
+ if(isbrainy(owner))
+ var/mob/living/simple_animal/horror/brainy/brainy_owner = owner
+ brainy_owner.leaping = FALSE
+ owner.update_icons()
+
+/datum/action/innate/zombie/default/takeover
+ name = "Brain Hijacking"
+ desc = "Immediately takes control of your current hosts, but permanently rots their brain, rotting them from inside and exposing your presence."
+ button_icon_state = "takeover"
+ ability_type = ZOMBIE_INSTANT
+ cost = 100
+ var/doing_it = FALSE
+
+/datum/action/innate/zombie/default/takeover/Activate()
+ if(doing_it)
+ return
+ doing_it = TRUE
+ if(tgui_alert(owner, "Doing this will give you immediate full control but slowly rot your host, are you sure you wanna do this?", "Saving grace", list("Yes", "No")) != "Yes")
+ return
+ if(!isbrainy(owner))
+ return
+ var/mob/living/simple_animal/horror/brainy/brainy_owner = owner
+ if(!brainy_owner.victim)
+ to_chat(owner, "[brainy_owner.victim] has no mind!") //for now
+ return
+ . = ..() //this comes here so you don't actually pay if you don't activate the ability
+ brainy_owner.full_control = TRUE
+ to_chat(brainy_owner, span_warning("You mush your host's brain with your claws. [brainy_owner.victim.p_their(TRUE)] corpse will rot quickly."))
+ rot(brainy_owner.victim)
+ brainy_owner.assume_control()
+
+/datum/action/innate/zombie/default/takeover/proc/rot(mob/living/victim)
+ animate(victim, color = rgb(140, 163, 46), time = 20 MINUTES)
+ addtimer(CALLBACK(victim, .proc/brain_failure, victim), 2.5 MINUTES)
+ addtimer(CALLBACK(src, .proc/protude), 2.5 MINUTES)
+
+/datum/action/innate/zombie/default/takeover/proc/brain_failure(mob/living/victim)
+ if(victim?.stat != DEAD)
+ victim.death()
+
+/datum/action/innate/zombie/default/takeover/proc/protude()
+ var/mutable_appearance/brainy = mutable_appearance('icons/mob/zombie_base.dmi', "brainy_head")
+ var/mob/living/simple_animal/horror/brainy/brainy_owner = owner
+ if(ishuman(brainy_owner.victim))
+ brainy_owner.victim.add_overlay(brainy)
+
+/datum/action/innate/zombie/default/infest
+ name = "Release Spores"
+ desc = "Inject spores into the hosts body, slowly converting them into a mindless zombie."
+ button_icon_state = "infest"
+ ability_type = ZOMBIE_INSTANT
+ cost = 20
+
+/datum/action/innate/zombie/default/infest/Activate()
+ . = ..()
+ var/mob/living/simple_animal/horror/brainy/brainy_owner = owner
+ try_to_zombie_infect(brainy_owner.victim, /obj/item/organ/zombie_infection/gamemode)
+ to_chat(brainy_owner.victim, span_danger("You feel a sharp pain in the back of your skull."))
+
+/datum/action/innate/zombie/morph
+ name = "Shapeshift Mass"
+ desc = "Adapt whatever weapon your host's holding into their cells, making them replicatable at will."
+ button_icon_state = "morph"
+ ability_type = ZOMBIE_TOGGLEABLE
+ cooldown = 10 SECONDS
+ var/obj/item/gun/brainy/linked_flesh_gun = null
+
+/datum/action/innate/zombie/morph/Activate()
+ var/mob/living/simple_animal/horror/brainy/brainy_owner = isbrainy(owner) ? owner : owner.has_horror_inside()
+ if(!ishuman(brainy_owner.victim))
+ return
+ var/mob/living/carbon/human/H = isbrainy(owner) ? brainy_owner.victim : owner
+ var/list/available_guns = list(/obj/item/gun/ballistic/shotgun/automatic/combat, /obj/item/gun/energy/laser) //add here
+ var/list/radial_menu = list()
+ var/obj/item/weapon = H.get_active_held_item() || H.get_inactive_held_item()
+ var/obj/item/selected_replicant
+ for(var/I in available_guns)
+ var/obj/item/item_options = available_guns[I]
+ radial_menu[I] = image(initial(item_options.icon), src, initial(item_options.icon_state))
+ selected_replicant = show_radial_menu(owner, owner, radial_menu, tooltips = TRUE)
+ if(weapon && !H.dropItemToGround(weapon))
+ to_chat(owner, span_warning("[weapon] is stuck to your [brainy_owner.controlling ? "" : "hosts"] hand!"))
+ return
+ linked_flesh_gun = new /obj/item/gun/brainy(get_turf(owner))
+ linked_flesh_gun.mimic(selected_replicant)
+ H.put_in_hands(linked_flesh_gun)
+ playsound(get_turf(owner), 'sound/effects/blobattack.ogg', 30, TRUE)
+ . = ..()
+
+/datum/action/innate/zombie/morph/Deactivate()
+ QDEL_NULL(linked_flesh_gun)
+ playsound(get_turf(owner), 'sound/effects/blobattack.ogg', 30, TRUE)
+ . = ..()
diff --git a/code/modules/antagonists/zombie/abilities/horror_worm.dm b/code/modules/antagonists/zombie/abilities/horror_worm.dm
new file mode 100644
index 000000000000..b71fdccdb785
--- /dev/null
+++ b/code/modules/antagonists/zombie/abilities/horror_worm.dm
@@ -0,0 +1,48 @@
+/mob/living/simple_animal/hostile/headcrab/horrorworm
+ name = "eldritch worm"
+ desc = "A severed purple tentacle, it seems to wiggle..?"
+ icon_state = "horrorworm"
+ icon_living = "horrorworm"
+ icon_dead = "horrorworm_dead"
+ health = 100
+ maxHealth = 100
+ obj_damage = 30
+ see_in_dark = 8
+ lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
+ egg = /obj/item/organ/body_egg/antag_egg/zombie_egg
+
+/mob/living/simple_animal/hostile/headcrab/horrorworm/AltClickOn(atom/A) //aditional way to infect people
+ . = ..()
+ if(!iscarbon(A))
+ return
+ var/mob/living/carbon/victim = A
+ if(loc == victim)
+ to_chat(src, span_notice("You move out of [victim]."))
+ forceMove(get_turf(victim))
+ return
+ visible_message(span_italics("[src] starts crawling into [victim]."), span_italics("You start wiggling inside [victim]...")) //is this weird?
+ if(!do_after(src, 3 SECONDS, victim))
+ to_chat(src, span_warning("You were interrupted and lost the entryway!"))
+ return
+ if(!egg_lain && !ismonkey(victim))
+ forceMove(victim)
+ victim.stomach_contents += src //making space
+ egg = new(victim)
+ egg.Insert(victim)
+ to_chat(src, span_notice("You sucessfully infect [victim] with an egg!")) //infection through outside means kills you, meanwhile this gives the player more agency for being risky
+ egg_lain = TRUE
+
+/obj/item/organ/body_egg/antag_egg/zombie_egg
+ name = "pulsating ooze"
+ desc = "It beats like a heart, spraying black goo when it does."
+ incubation_time = 30 //we need this to be faster
+
+/obj/item/organ/body_egg/antag_egg/zombie_egg/Pop()
+ owner.adjustBruteLoss(200) //destroyed
+ try_to_zombie_infect(owner, /obj/item/organ/zombie_infection/gamemode)
+ owner.spawn_gibs()
+ var/mob/living/carbon/human/new_human = new(get_turf(owner))
+ new_human.set_species(/datum/species/zombie/infectious/gamemode)
+ if(fetus_mind && (fetus_mind.current ? (fetus_mind.current.stat == DEAD) : fetus_mind.get_ghost()))
+ fetus_mind.transfer_to(new_human)
+ new_human.key = fetus_mind.key
diff --git a/code/modules/antagonists/zombie/abilities/necromance.dm b/code/modules/antagonists/zombie/abilities/necromance.dm
index 40437112e988..30716c1478e3 100644
--- a/code/modules/antagonists/zombie/abilities/necromance.dm
+++ b/code/modules/antagonists/zombie/abilities/necromance.dm
@@ -1,4 +1,4 @@
-/obj/effect/proc_holder/zombie/necromance
+/* /obj/effect/proc_holder/zombie/necromance
name = "Summon a Minion"
desc = "Summons a zombie to help you."
action_icon = 'icons/mob/actions/actions_cult.dmi'
@@ -57,4 +57,4 @@
return FALSE
necromance()
- return ..()
\ No newline at end of file
+ return ..() */
diff --git a/code/modules/antagonists/zombie/abilities/runner.dm b/code/modules/antagonists/zombie/abilities/runner.dm
new file mode 100644
index 000000000000..9dd9ed423451
--- /dev/null
+++ b/code/modules/antagonists/zombie/abilities/runner.dm
@@ -0,0 +1,91 @@
+/////////////////\\\\\\\\\\\\\\
+//// \\\\
+//// STARTING ABILITIES \\\\
+//// \\\\
+////\\\\\\\\\\\\///////////\\\\
+
+/datum/action/innate/zombie/default/endure
+ name = "Endure"
+ desc = "Harden your muscles, gaining increased vitality, but restricting arm movement."
+ button_icon_state = "endure"
+ ability_type = ZOMBIE_TOGGLEABLE
+ cost = 5
+ constant_cost = 2.5
+
+/datum/action/innate/zombie/default/endure/Activate()
+ . = ..()
+ ADD_TRAIT(owner, TRAIT_IGNOREDAMAGESLOWDOWN, ZOMBIE_TRAIT)
+ ADD_TRAIT(owner, TRAIT_PACIFISM, ZOMBIE_TRAIT)
+ var/datum/antagonist/zombie/zombie_owner = owner.mind?.has_antag_datum(/datum/antagonist/zombie)
+ if(zombie_owner.zombie_mutations["better_reflexes"])
+ ADD_TRAIT(owner, TRAIT_DODGE, ZOMBIE_TRAIT)
+
+/datum/action/innate/zombie/default/endure/Deactivate()
+ . = ..()
+ REMOVE_TRAIT(owner, TRAIT_IGNOREDAMAGESLOWDOWN, ZOMBIE_TRAIT)
+ REMOVE_TRAIT(owner, TRAIT_PACIFISM, ZOMBIE_TRAIT)
+ var/datum/antagonist/zombie/zombie_owner = owner.mind?.has_antag_datum(/datum/antagonist/zombie)
+ if(zombie_owner.zombie_mutations["better_reflexes"])
+ REMOVE_TRAIT(owner, TRAIT_DODGE, ZOMBIE_TRAIT)
+
+/datum/action/innate/zombie/parkour
+ name = "Parkour"
+ desc = "Prepare your legs to instantly dive over any low standing structures."
+ button_icon_state = "parkour"
+ ability_type = ZOMBIE_TOGGLEABLE
+ cooldown = 15 SECONDS
+ /// Jumping over 5 obstacles will automatically disable Parkour, 10 with the leg_response upgrade.
+ var/obstacles_jumped = 0
+
+/datum/action/innate/zombie/parkour/Activate()
+ . = ..()
+ RegisterSignal(owner, COMSIG_MOVABLE_BUMP, .proc/Parkour)
+
+/datum/action/innate/zombie/parkour/process()
+ var/datum/antagonist/zombie/zombie_owner = owner.mind?.has_antag_datum(/datum/antagonist/zombie)
+ var/max_obstacles = (zombie_owner.zombie_mutations["leg_response"]) ? 10 : 5
+ if(obstacles_jumped == max_obstacles)
+ Deactivate()
+
+/datum/action/innate/zombie/parkour/proc/Parkour(mob/user, atom/affected)
+ var/list/bumpable_atoms = list(/obj/structure/table, /obj/structure/barricade, /obj/structure/destructible/cult, /obj/structure/altar_of_gods)
+ if(is_type_in_list(affected, bumpable_atoms))
+ user.emote("flip")
+ user.forceMove(get_turf(affected))
+ obstacles_jumped++
+
+/datum/action/innate/zombie/parkour/Deactivate()
+ . = ..()
+ obstacles_jumped = 0
+ UnregisterSignal(owner, COMSIG_MOVABLE_BUMP)
+
+/////////////////\\\\\\\\\\\\\\
+//// \\\\
+//// PURCHASABLE ABILITIES \\\\
+//// \\\\
+////\\\\\\\\\\\\///////////\\\\
+
+/datum/action/innate/zombie/pickpocket
+ name = "Pickpocket"
+ desc = "Gain increased neuron and muscle communication, allowing for you to slowly strip pockets and slap away peoples items from their hands."
+ button_icon_state = "pickpocket"
+ ability_type = ZOMBIE_TOGGLEABLE
+ cooldown = 20 SECONDS
+
+/datum/action/innate/zombie/pickpocket/process()
+ var/people_pocketed = 0
+ if(people_pocketed == 5)
+ Deactivate()
+ for(var/mob/living/carbon/C in view(1, owner))
+ if(C == owner)
+ continue
+ if(INTERACTING_WITH(owner, C))
+ continue
+ C.dropItemToGround(C.get_active_held_item())
+ people_pocketed++
+ if(ishuman(C))
+ var/mob/living/carbon/human/H = C
+ if(!do_after(owner, 2.5 SECONDS, H))
+ continue
+ var/obj/item/pocket_item = H.r_store || H.l_store
+ H.dropItemToGround(pocket_item)
diff --git a/code/modules/antagonists/zombie/abilities/smoker.dm b/code/modules/antagonists/zombie/abilities/smoker.dm
new file mode 100644
index 000000000000..b2fc002d6520
--- /dev/null
+++ b/code/modules/antagonists/zombie/abilities/smoker.dm
@@ -0,0 +1,76 @@
+/////////////////\\\\\\\\\\\\\\
+//// \\\\
+//// STARTING ABILITIES \\\\
+//// \\\\
+////\\\\\\\\\\\\///////////\\\\
+
+/datum/action/innate/zombie/default/smoke
+ name = "Spew Smoke"
+ desc = "Uses your enhanced lungs to spew smoke in the tiles close to you, halving projectile damage."
+ button_icon_state = "smoke"
+ ability_type = ZOMBIE_TOGGLEABLE
+ constant_cost = 5
+ cost = 10
+ ///Used in code\modules\mob\living\carbon\human\species_types\zombies.dm to handle Smoker infection regeneration.
+ var/last_fired
+
+/obj/effect/particle_effect/smoke/bad/zombie/New()
+ . = ..()
+ add_atom_colour("#104410", FIXED_COLOUR_PRIORITY)
+
+/datum/action/innate/zombie/default/smoke/process()
+ . = ..()
+ do_smoke(2, get_turf(owner), /obj/effect/particle_effect/smoke/bad/zombie) // halves proj damage
+ last_fired = world.time
+
+/datum/action/innate/zombie/scorch
+ name = "Scorch Skin"
+ desc = "Using highly volatile gases in your system you are able to make your claws exceptionally hot, dealing stamina and brun damage.\n\
+ Targeting the mouth will mute your target for 6 seconds. \n\
+ Targeting the eyes will deal additional eye damage."
+ button_icon_state = "scorch"
+ ability_type = ZOMBIE_FIREABLE
+ cooldown = 15 SECONDS
+ custom_mouse_icon = 'icons/effects/mouse_pointers/zombie_scorch.dmi'
+
+/datum/action/innate/zombie/scorch/IsTargetable(atom/target_atom)
+ return isliving(target_atom)
+
+/datum/action/innate/zombie/scorch/UseFireableAbility(atom/target_atom)
+ . = ..()
+ if(iscarbon(target_atom))
+ var/mob/living/carbon/C = target_atom
+ var/selected_zone = owner.zone_selected
+ var/list/viable_zones = list(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_PRECISE_MOUTH, BODY_ZONE_PRECISE_EYES)
+ owner.do_attack_animation(C, ATTACK_EFFECT_PUNCH)
+ playsound(usr.loc, "sound/weapons/sear.ogg", 50, TRUE)
+ C.apply_damage(20, BURN, selected_zone)
+ if(viable_zones.Find(selected_zone))
+ C.apply_damage(30, STAMINA, selected_zone)
+ if(selected_zone == BODY_ZONE_PRECISE_MOUTH)
+ C.silent += 6
+ if(selected_zone == BODY_ZONE_PRECISE_EYES)
+ var/obj/item/organ/eyes/eyes = C.getorganslot(ORGAN_SLOT_EYES)
+ eyes.applyOrganDamage(5)
+
+/////////////////\\\\\\\\\\\\\\
+//// \\\\
+//// PURCHASABLE ABILITIES \\\\
+//// \\\\
+////\\\\\\\\\\\\///////////\\\\
+
+/datum/action/innate/zombie/swing
+ name = "Throw Tongue"
+ desc = "Throws your tongue on a person or tile, swinging you at it."
+ button_icon_state = "swing"
+ ability_type = ZOMBIE_FIREABLE
+ cooldown = 10 SECONDS
+ range = 7
+
+/datum/action/innate/zombie/swing/IsTargetable(atom/target_atom)
+ return isopenturf(target_atom) || iscarbon(target_atom)
+
+/datum/action/innate/zombie/swing/UseFireableAbility(atom/target_atom)
+ . = ..()
+ owner.Beam(target_atom, "smokertongue", time = 0.5 SECONDS, maxdistance = range)
+ owner.throw_at(target_atom, range, 2)
diff --git a/code/modules/antagonists/zombie/abilities/spit.dm b/code/modules/antagonists/zombie/abilities/spit.dm
deleted file mode 100644
index e244411adfba..000000000000
--- a/code/modules/antagonists/zombie/abilities/spit.dm
+++ /dev/null
@@ -1,58 +0,0 @@
-/obj/effect/proc_holder/zombie/spit
- name = "Spit Neurotoxin"
- desc = "Spits neurotoxin at someone, paralyzing them for a short time."
- action_icon_state = "alien_neurotoxin_0"
- active = FALSE
- cooldown_time = 1 MINUTES
-
-
-/obj/effect/proc_holder/zombie/spit/fire(mob/living/carbon/user)
- if(active)
- remove_ranged_ability(span_notice("You close your neurotoxin reserves."))
- else
- add_ranged_ability(user, span_notice("You open your neurotoxin reserves. Left-click to fire at a target!"), TRUE)
-
-/obj/effect/proc_holder/zombie/spit/update_icon()
- action.button_icon_state = "alien_neurotoxin_[active]"
- action.UpdateButtonIcon()
-
-/obj/effect/proc_holder/zombie/spit/InterceptClickOn(mob/living/caller, params, atom/target)
- if(..())
- return FALSE
- if(!isinfected(ranged_ability_user) || ranged_ability_user.stat != CONSCIOUS)
- remove_ranged_ability()
- return FALSE
-
- var/mob/living/carbon/user = ranged_ability_user
-
- if(!ready)
- to_chat(user, span_warning("You cannot currently spit. You can spit again in [(cooldown_ends - world.time) / 10] seconds"))
- remove_ranged_ability()
- return FALSE
-
- var/turf/T = user.loc
- var/turf/U = get_step(user, user.dir) // Get the tile infront of the move, based on their direction
- if(!U || !T)
- return FALSE
-
- user.visible_message("[user] spits neurotoxin!", span_alertalien("You spit neurotoxin."))
- var/obj/item/projectile/bullet/neurotoxin/spitter/A = new /obj/item/projectile/bullet/neurotoxin/spitter(user.loc)
- A.preparePixelProjectile(target, user, params)
- A.fire()
- user.newtonian_move(get_dir(U, T))
- start_cooldown()
-
- return TRUE
-
-/obj/item/projectile/bullet/neurotoxin/spitter
- name = "neurotoxin spit"
- icon_state = "neurotoxin"
- damage = 2
- damage_type = TOX
- paralyze = 50
-
-/obj/item/projectile/bullet/neurotoxin/spitter/on_hit(atom/target, blocked = FALSE)
- if(isinfected(target))
- paralyze = 0
- nodamage = TRUE
- return ..()
diff --git a/code/modules/antagonists/zombie/abilities/spitter.dm b/code/modules/antagonists/zombie/abilities/spitter.dm
new file mode 100644
index 000000000000..90c572054ead
--- /dev/null
+++ b/code/modules/antagonists/zombie/abilities/spitter.dm
@@ -0,0 +1,124 @@
+/////////////////\\\\\\\\\\\\\\
+//// \\\\
+//// STARTING ABILITIES \\\\
+//// \\\\
+////\\\\\\\\\\\\///////////\\\\
+
+/datum/action/innate/zombie/default/overclock
+ name = "Overclock Heart"
+ desc = "Overwork your heart, generating more acid in the short term but slowly losing when you get too tired. It is hard to get a grip on."
+ button_icon_state = "overclock"
+ ability_type = ZOMBIE_TOGGLEABLE
+ var/tiredness = 0
+
+/datum/action/innate/zombie/default/overclock/Activate()
+ if(tiredness)
+ to_chat(owner, span_warning("You are still too tired to overwork your heart again."))
+ return
+ . = ..()
+ var/datum/antagonist/zombie/zombie_owner = owner.mind.has_antag_datum(/datum/antagonist/zombie)
+ for(var/datum/action/innate/zombie/ability as anything in zombie_owner.zombie_abilities)
+ if(ability?.cooldown <= 5 SECONDS)
+ continue
+ ability.cooldown -= 5 SECONDS
+
+ if(!isspitter(owner))
+ return
+ var/mob/living/carbon/human/H = owner
+ var/datum/species/zombie/infectious/gamemode/spitter/spitter = H.dna?.species
+ spitter.infection_regen = initial(spitter.infection_regen) * 2
+ for(var/obj/item/zombie_hand/gamemode/spitter/zombie_hand in H.contents)
+ zombie_hand.cooldown = initial(zombie_hand.cooldown) / 2
+
+/datum/action/innate/zombie/default/overclock/process()
+ . = ..()
+ tiredness += 0.01
+ var/datum/antagonist/zombie/zombie_owner = owner.mind.has_antag_datum(/datum/antagonist/zombie)
+ for(var/datum/action/innate/zombie/ability as anything in zombie_owner.zombie_abilities)
+ ability?.cooldown += tiredness / 20
+ if(!isspitter(owner))
+ return
+ var/mob/living/carbon/human/H = owner
+ var/datum/species/zombie/infectious/gamemode/spitter/spitter = H.dna.species
+ spitter.infection_regen -= tiredness / 20
+ for(var/obj/item/zombie_hand/gamemode/spitter/zombie_hand in H.contents)
+ zombie_hand.cooldown = initial(zombie_hand.cooldown) / 2
+
+/datum/action/innate/zombie/default/overclock/Deactivate()
+ . = ..()
+ var/datum/antagonist/zombie/zombie_owner = owner.mind.has_antag_datum(/datum/antagonist/zombie)
+ for(var/datum/action/innate/zombie/ability as anything in zombie_owner.zombie_abilities)
+ ability?.cooldown = initial(ability.cooldown)
+ if(!isspitter(owner))
+ return
+ var/mob/living/carbon/human/H = owner
+ var/datum/species/zombie/infectious/gamemode/spitter/spitter = H.dna?.species
+ spitter.infection_regen = initial(spitter.infection_regen)
+ for(var/obj/item/zombie_hand/gamemode/spitter/zombie_hand in H.contents)
+ zombie_hand.cooldown = initial(zombie_hand.cooldown)
+ addtimer(CALLBACK(src, .proc/heal_tiredness), 5 SECONDS)
+
+/datum/action/innate/zombie/default/overclock/proc/heal_tiredness()
+ tiredness = 0
+
+/datum/action/innate/zombie/default/spit //most spitter abilities are default since you pay to use them instead of paying to use your actual default action
+ name = "Spit Acid"
+ desc = "Use acid to make neurotoxin to spit at a target, paralyzing them for a while."
+ button_icon_state = "spit"
+ ability_type = ZOMBIE_FIREABLE
+ cooldown = 15 SECONDS
+ cost = 20
+ custom_mouse_icon = 'icons/effects/mouse_pointers/zombie_spit.dmi'
+
+/datum/action/innate/zombie/default/spit/UseFireableAbility(atom/target_atom)
+ . = ..()
+ if(!isturf(owner.loc))
+ return
+ var/turf/current_turf = get_turf(owner)
+ var/turf/directional_turf = get_step(owner, owner.dir)
+
+ owner.visible_message(span_danger("[owner] spits a blob of neurotoxin!"), span_warning("You spit neurotoxin[isliving(target_atom) ? " at [target_atom]" : ""]."))
+ var/obj/item/projectile/bullet/neurotoxin/A = new /obj/item/projectile/bullet/neurotoxin(get_turf(owner))
+ A.preparePixelProjectile(target_atom, owner)
+ A.firer = owner
+ A.fire()
+ owner.newtonian_move(get_dir(directional_turf, current_turf))
+
+/////////////////\\\\\\\\\\\\\\
+//// \\\\
+//// PURCHASABLE ABILITIES \\\\
+//// \\\\
+////\\\\\\\\\\\\///////////\\\\
+
+/datum/action/innate/zombie/default/bubble
+ name = "Bubble Bomb"
+ desc = "Spill acid in the area around you."
+ button_icon_state = "bubble"
+ ability_type = ZOMBIE_INSTANT
+ cooldown = 20 SECONDS
+ cost = 50
+
+/datum/action/innate/zombie/default/bubble/Activate()
+ . = ..()
+ for(var/atom/movable/thing in oview(1, owner))
+ thing.acid_act(100, 50)
+
+/datum/action/innate/zombie/default/puke
+ name = "Puke Puddle"
+ desc = "Puke a puddle of powerful acid on someone or something, coating anyone that walks over it."
+ button_icon_state = "puke"
+ ability_type = ZOMBIE_FIREABLE
+ cooldown = 10 SECONDS
+ cost = 100 //pricey!
+ custom_mouse_icon = 'icons/effects/mouse_pointers/vomit.dmi'
+
+/datum/action/innate/zombie/default/puke/IsTargetable(atom/target_atom)
+ return isopenturf(target_atom) || isliving(target_atom)
+
+/datum/action/innate/zombie/default/puke/UseFireableAbility(atom/target_atom)
+ . = ..()
+ if(isliving(target_atom))
+ var/mob/living/L = target_atom
+ L.apply_status_effect(STATUS_EFFECT_ZOMBIE_ACID)
+ if(isopenturf(target_atom))
+ new /obj/effect/acid_puddle(get_turf(target_atom))
diff --git a/code/modules/antagonists/zombie/abilities/tank.dm b/code/modules/antagonists/zombie/abilities/tank.dm
index 6b0e69e3b6ed..5f3cedfcabc5 100644
--- a/code/modules/antagonists/zombie/abilities/tank.dm
+++ b/code/modules/antagonists/zombie/abilities/tank.dm
@@ -1,29 +1,197 @@
-/obj/effect/proc_holder/zombie/tank
- name = "Tank"
- desc = "Gives you a moderate armor boost for a few seconds. Heals 60% of your brute and fire damage."
- action_icon = 'icons/mob/actions/actions_changeling.dmi'
- action_icon_state = "fake_death"
- cooldown_time = 2.5 MINUTES
- var/duration = 30 SECONDS
- var/armor_boost = 12.5
- var/heal = 0.6
-
-
-/obj/effect/proc_holder/zombie/tank/proc/run_ability(mob/living/carbon/human/user)
- addtimer(CALLBACK(src, .proc/stop, user), duration)
- user.dna.species.armor += armor_boost
- user.adjustBruteLoss(-(heal * user.getBruteLoss()))
- user.adjustFireLoss(-(heal * user.getFireLoss()))
- return TRUE
-
-/obj/effect/proc_holder/zombie/tank/proc/stop(mob/living/carbon/human/user)
-
- user.dna.species.armor -= armor_boost
- to_chat(user, span_userdanger("Your armor boost has ended!"))
-
-/obj/effect/proc_holder/zombie/tank/fire(mob/living/carbon/user)
- if(user.incapacitated())
- return FALSE
-
- if(run_ability(user))
- return ..()
+/////////////////\\\\\\\\\\\\\\
+//// \\\\
+//// STARTING ABILITIES \\\\
+//// \\\\
+////\\\\\\\\\\\\///////////\\\\
+
+/datum/action/innate/zombie/default/rage
+ name = "Unyielding Rage"
+ desc = "Unleash the damage you have suffered into raw power.\n\
+ Transforms your charge ability into something else..."
+ button_icon_state = "rage"
+ ability_type = ZOMBIE_TOGGLEABLE
+ cost = 2
+ constant_cost = 0.1
+
+/datum/action/innate/zombie/default/rage/Activate()
+ . = ..()
+ var/previous_cooldown = FALSE
+ var/datum/antagonist/zombie/zombie_owner = owner.mind?.has_antag_datum(/datum/antagonist/zombie)
+ for(var/datum/action/innate/zombie/charge/C in zombie_owner.zombie_abilities)
+ if(!COOLDOWN_FINISHED(C, zombie_ability_cooldown))
+ previous_cooldown = TRUE
+ var/datum/action/innate/zombie/default/smash/S = new
+ S.Grant(owner)
+ C.Remove(owner)
+ if(previous_cooldown)
+ S.StartCooldown()
+
+/datum/action/innate/zombie/default/rage/Deactivate()
+ . = ..()
+ var/previous_cooldown = FALSE
+ var/datum/antagonist/zombie/zombie_owner = owner.mind?.has_antag_datum(/datum/antagonist/zombie)
+ for(var/datum/action/innate/zombie/default/smash/S in zombie_owner.zombie_abilities)
+ var/datum/action/innate/zombie/charge/C = new
+ if(!COOLDOWN_FINISHED(S, zombie_ability_cooldown))
+ previous_cooldown = TRUE
+ C.Grant(owner)
+ S.Remove(owner)
+ if(previous_cooldown)
+ C.StartCooldown()
+
+/datum/action/innate/zombie/default/smash
+ name = "Destroying Smash"
+ desc = "Slam down on your foes, your huge arm allowing you to crater them!"
+ button_icon_state = "smash"
+ ability_type = ZOMBIE_FIREABLE
+ cooldown = 10 SECONDS
+ cost = 20
+ range = 5
+
+/datum/action/innate/zombie/default/smash/IsTargetable(atom/target_atom)
+ return isturf(target_atom) || isliving(target_atom) || isstructure(target_atom)
+
+/datum/action/innate/zombie/default/smash/UseFireableAbility(atom/target_atom)
+ . = ..()
+ var/turf/target_turf = get_turf(target_atom)
+ if(!owner.throw_at(target_turf))
+ owner.forceMove(target_turf)
+ playsound(target_turf, 'sound/effects/bang.ogg', 30, TRUE, -1)
+ for(var/atom/movable/thing in view(1, target_turf))
+ if(thing == owner)
+ continue
+ if(isliving(thing))
+ var/mob/living/L = thing
+ if(get_turf(L) == get_turf(src))
+ L.adjustBruteLoss(30)
+ L.Knockdown(2 SECONDS)
+ else
+ L.adjustBruteLoss(20)
+ owner.do_attack_animation(L, ATTACK_EFFECT_SMASH)
+ var/send_dir = get_dir(owner, L)
+ var/turf/turf_thrown_at = get_ranged_target_turf(L, send_dir, 5)
+ L.throw_at(turf_thrown_at, 5, TRUE, owner)
+ if(isstructure(thing) || ismachinery(thing))
+ var/obj/O = thing
+ O?.take_damage(100, BRUTE, MELEE, 1)
+
+/datum/action/innate/zombie/charge
+ name = "Unstopabble Charge"
+ desc = "Charge at a single direction, obliterating all things in your way."
+ button_icon_state = "charge"
+ ability_type = ZOMBIE_FIREABLE
+ cooldown = 15 SECONDS
+ range = 5
+ var/speed_override
+
+/datum/action/innate/zombie/charge/IsTargetable(atom/target_atom)
+ return isturf(target_atom) || isliving(target_atom) || target_atom.loc != owner.loc
+
+/datum/action/innate/zombie/charge/UseFireableAbility(atom/target_atom)
+ . = ..()
+ //most from code\modules\antagonists\bloodsuckers\powers\targeted\haste.dm
+ var/mob/living/user = owner
+ RegisterSignal(user, COMSIG_MOVABLE_BUMP, .proc/Obliterate)
+ var/turf/targeted_turf = isturf(target_atom) ? target_atom : get_turf(target_atom)
+ // Pulled? Not anymore.
+ user.pulledby?.stop_pulling()
+ // Go to target turf
+ // DO NOT USE WALK TO.
+ playsound(get_turf(owner), 'sound/weapons/punchmiss.ogg', 25, 1, -1)
+ var/safety = get_dist(user, targeted_turf) * 3 + 1
+ var/consequetive_failures = 0
+ var/speed = isnull(speed_override)? world.tick_lag : speed_override
+ while(--safety && (get_turf(user) != targeted_turf))
+ var/success = step_towards(user, targeted_turf) //This does not try to go around obstacles.
+ if(!success)
+ success = step_to(user, targeted_turf) //this does
+ if(!success)
+ if(++consequetive_failures >= 3) //if 3 steps don't work
+ break //just stop
+ else
+ consequetive_failures = 0
+ if(user.incapacitated(ignore_restraints = TRUE, ignore_grab = TRUE)) //actually down? stop.
+ break
+ if(success) //don't sleep if we failed to move.
+ sleep(speed)
+ UnregisterSignal(user, COMSIG_MOVABLE_BUMP)
+
+/datum/action/innate/zombie/charge/proc/Obliterate(mob/living/zource, atom/affected)
+ if(isliving(affected))
+ var/mob/living/L = affected
+ zource.do_attack_animation(target, ATTACK_EFFECT_SMASH)
+ L.adjustBruteLoss(30)
+ L.throw_at((get_ranged_target_turf(L, get_dir(zource, L), 10)), 10, TRUE, zource)
+ if(isobj(affected))
+ var/obj/O = affected
+ if(istype(affected, /obj/machinery/door))
+ var/obj/machinery/door/D = O
+ if(!D.open())
+ D.obj_break(BRUTE)
+ D.take_damage(10)
+ if(O.density)
+ O.obj_break(BRUTE)
+ if(iswallturf(affected))
+ var/turf/closed/wall/W = affected
+ W.dismantle_wall(1)
+
+/////////////////\\\\\\\\\\\\\\
+//// \\\\
+//// PURCHASABLE ABILITIES \\\\
+//// \\\\
+////\\\\\\\\\\\\///////////\\\\
+
+/datum/action/innate/zombie/jab
+ name = "Quick Jab"
+ desc = "Prepare your fist for a special punch, charged with extra power.\n\
+ Violently damages all organisms and structures alike, may nothing block your path."
+ button_icon_state = "jab"
+ ability_type = ZOMBIE_FIREABLE
+ cooldown = 15 SECONDS
+ range = 1
+
+/datum/action/innate/zombie/jab/IsTargetable(atom/target_atom)
+ return iswallturf(target_atom) || isliving(target_atom) || isstructure(target_atom) || ismachinery(target_atom)
+
+//code\modules\antagonists\bloodsuckers\powers\targeted\brawn.dm
+/datum/action/innate/zombie/jab/UseFireableAbility(atom/target_atom)
+ . = ..()
+ var/mob/living/user = owner
+ // Target Type: Mob
+ if(isliving(target_atom))
+ var/mob/living/target = target_atom
+ // Knockdown!
+ target.visible_message(
+ span_danger("A roaring sound echoes as [user] punches [target], sending [target.p_them()] away!"), \
+ span_userdanger("[user] viciously pounds you with his fist, sending you flying!"),
+ )
+ target.Knockdown(10 SECONDS)
+ // Attack!
+ to_chat(owner, span_warning("You jab [target]!"))
+ playsound(get_turf(target), 'sound/weapons/punch4.ogg', 60, TRUE, -1)
+ user.do_attack_animation(target, ATTACK_EFFECT_SMASH)
+ var/obj/item/bodypart/affecting = target.get_bodypart(ran_zone(target.zone_selected))
+ target.apply_damage(20, BRUTE, affecting)
+ // Knockback
+ var/send_dir = get_dir(owner, target)
+ var/turf/turf_thrown_at = get_ranged_target_turf(target, send_dir, 5)
+ owner.newtonian_move(send_dir) // Bounce back in 0 G
+ target.throw_at(turf_thrown_at, 5, TRUE, owner)
+ // Target Type: Cyborg (Also gets the effects above)
+ if(issilicon(target))
+ target.emp_act(EMP_HEAVY)
+ playsound(get_turf(user), 'sound/effects/grillehit.ogg', 80, TRUE, -1)
+ // Target Type: Wall
+ if(iswallturf(target_atom))
+ var/turf/closed/wall/the_wall = target_atom
+ var/time_to_tear = 1.5 SECONDS
+ if(istype(the_wall, /turf/closed/wall/r_wall))
+ time_to_tear = 2.5 SECONDS
+ to_chat(owner, span_warning("You prepare to tear down [the_wall]..."))
+ if(!do_mob(user, the_wall, time_to_tear))
+ return FALSE
+ if(the_wall.Adjacent(user))
+ the_wall.visible_message(span_danger("[the_wall] breaks into ruble as [user] bashes it!"))
+ user.do_attack_animation(the_wall, ATTACK_EFFECT_SMASH)
+ playsound(get_turf(the_wall), 'sound/effects/bang.ogg', 30, TRUE, -1)
+ the_wall.dismantle_wall(1)
diff --git a/code/modules/antagonists/zombie/abilities/uncuff.dm b/code/modules/antagonists/zombie/abilities/uncuff.dm
deleted file mode 100644
index 4b3a39eb66eb..000000000000
--- a/code/modules/antagonists/zombie/abilities/uncuff.dm
+++ /dev/null
@@ -1,8 +0,0 @@
-/datum/action/innate/zombie/uncuff
- name = "Break Free!"
- desc = "Breaks you free from handcuffs."
- button_icon_state = "biodegrade"
-
-/datum/action/innate/zombie/uncuff/Activate(forced = FALSE)
- var/mob/living/carbon/human/H = usr
- H.uncuff()
diff --git a/code/modules/antagonists/zombie/mutations/_mutations.dm b/code/modules/antagonists/zombie/mutations/_mutations.dm
new file mode 100644
index 000000000000..016c6474e663
--- /dev/null
+++ b/code/modules/antagonists/zombie/mutations/_mutations.dm
@@ -0,0 +1,87 @@
+/*
+* Defines on _DEFINES/zombies.dm
+*/
+
+/datum/zombie_mutation
+ var/sector = SECTOR_COMMON
+ var/name = "sse=wellpsl"
+ var/id = ""
+ var/desc = "make a bug report if you see this"
+ var/mutation_cost = 0
+ var/owned = FALSE
+ var/datum/antagonist/zombie/zombie_owner
+ var/owner_class
+
+/*
+* See darkspawn_upgrade.dm for these
+*/
+/datum/zombie_mutation/New(zombie_datum)
+ ..()
+ zombie_owner = zombie_datum
+
+/datum/zombie_mutation/proc/on_purchase()
+ if(sector == SECTOR_CLASS)
+ switch(zombie_owner.class_chosen)
+ if(null)
+ return FALSE
+ if(SMOKER)
+ if(!(owner_class & SMOKER_BITFLAG))
+ return FALSE
+ if(RUNNER)
+ if(!(owner_class & RUNNER_BITFLAG))
+ return FALSE
+ if(SPITTER)
+ if(!(owner_class & SPITTER_BITFLAG))
+ return FALSE
+ if(JUGGERNAUT)
+ if(!(owner_class & JUGGERNAUT_BITFLAG))
+ return FALSE
+ if(BRAINY)
+ if(!(owner_class & BRAINY_BITFLAG))
+ return FALSE
+ if(!zombie_owner)
+ return FALSE
+ apply_effects()
+ qdel(src)
+ return TRUE
+
+/datum/zombie_mutation/proc/apply_effects()
+ zombie_owner.mutation_rate++
+
+//These are common abilities between all classes
+
+/datum/zombie_mutation/additional_infection
+ name = "Storage Glands"
+ id = "additional_infection"
+ desc = "Gain additional 20 max infection points."
+ mutation_cost = 1
+
+/datum/zombie_mutation/additional_infection/apply_effects()
+ . = ..()
+ zombie_owner.infection_max += 20
+ zombie_owner.update_infection_hud() //just incase
+
+/datum/zombie_mutation/less_infection_usage //these passive ones are checked during the actual proc of the abilities to see if they are present to reduce costs and whatnot
+ name = "Efficient Chemical Management"
+ id = "reduced_cost"
+ desc = "Halves the cost of infection points to first use non instant abilities."
+ mutation_cost = 1
+
+/datum/zombie_mutation/last_resort
+ sector = SECTOR_CLASS
+ name = "Explosive Glands"
+ id = "reduced_cost"
+ desc = "Gains the escape ability, which kills your current but makes you into a tumor carrying worm."
+ mutation_cost = 2
+ owner_class = SMOKER_BITFLAG | RUNNER_BITFLAG | SPITTER_BITFLAG | JUGGERNAUT_BITFLAG
+
+/datum/zombie_mutation/last_resort/apply_effects()
+ . = ..()
+ var/datum/action/innate/zombie/last_resort/L = new
+ L.Grant(zombie_owner.owner?.current)
+
+/datum/zombie_mutation/instant_infection
+ name = "Fungal Growth"
+ id = "instant_infection"
+ desc = "Infecting someone now instantly revives them."
+ mutation_cost = 4
diff --git a/code/modules/antagonists/zombie/mutations/runner.dm b/code/modules/antagonists/zombie/mutations/runner.dm
new file mode 100644
index 000000000000..4cde019686ce
--- /dev/null
+++ b/code/modules/antagonists/zombie/mutations/runner.dm
@@ -0,0 +1,28 @@
+/datum/zombie_mutation/leg_response
+ sector = SECTOR_CLASS
+ name = "Harder Leg Tissue"
+ id = "leg_response"
+ desc = "Doubles your possible Parkour jumps."
+ owner_class = RUNNER_BITFLAG
+ mutation_cost = 1
+
+/datum/zombie_mutation/pick_pocket
+ sector = SECTOR_CLASS
+ name = "Faster Neuron Response"
+ id = "pick_pocket"
+ desc = "Gain the Pickpocket ability."
+ owner_class = RUNNER_BITFLAG
+ mutation_cost = 1
+
+/datum/zombie_mutation/pick_pocket/apply_effects()
+ . = ..()
+ var/datum/action/innate/zombie/pickpocket/P = new
+ P.Grant(zombie_owner.owner?.current)
+
+/datum/zombie_mutation/better_reflexes
+ sector = SECTOR_CLASS
+ name = "Better Brain Reaction"
+ id = "better_reflexes"
+ desc = "Bolt makes you immune to projectiles."
+ owner_class = RUNNER_BITFLAG
+ mutation_cost = 2
diff --git a/code/modules/antagonists/zombie/mutations/smoker.dm b/code/modules/antagonists/zombie/mutations/smoker.dm
new file mode 100644
index 000000000000..4c0ae31c811a
--- /dev/null
+++ b/code/modules/antagonists/zombie/mutations/smoker.dm
@@ -0,0 +1,28 @@
+/datum/zombie_mutation/tongue_range
+ sector = SECTOR_CLASS
+ name = "Tongue Size"
+ id = "big_tongue"
+ desc = "Increases effective tongue range by 2."
+ owner_class = SMOKER_BITFLAG
+ mutation_cost = 1
+
+/datum/zombie_mutation/tongue_stun
+ sector = SECTOR_CLASS
+ name = "Tongue Spikes"
+ id = "stickier_tongue"
+ desc = "Increases stun time by 1 second on non-targets interacting with the tongue."
+ owner_class = SMOKER_BITFLAG
+ mutation_cost = 1
+
+/datum/zombie_mutation/tongue_swing
+ sector = SECTOR_CLASS
+ name = "Tongue Myocytes"
+ id = "strong_tongue"
+ desc = "Gain the Tongue Swing ability."
+ owner_class = SMOKER_BITFLAG
+ mutation_cost = 2
+
+/datum/zombie_mutation/tongue_swing/apply_effects()
+ . = ..()
+ var/datum/action/innate/zombie/swing/S = new
+ S.Grant(zombie_owner.owner?.current)
diff --git a/code/modules/antagonists/zombie/mutations/spitter.dm b/code/modules/antagonists/zombie/mutations/spitter.dm
new file mode 100644
index 000000000000..ea2717fcfd6b
--- /dev/null
+++ b/code/modules/antagonists/zombie/mutations/spitter.dm
@@ -0,0 +1,33 @@
+/datum/zombie_mutation/stronger_acid
+ sector = SECTOR_CLASS
+ name = "Effecient Glands"
+ id = "stronger_acid"
+ desc = "Increases acid effectiveness on walls."
+ owner_class = SPITTER_BITFLAG
+ mutation_cost = 1
+
+/datum/zombie_mutation/bubble_blood
+ sector = SECTOR_CLASS
+ name = "Lipid Sacs"
+ id = "stronger_acid"
+ desc = "Gain the Bubble ability."
+ owner_class = SPITTER_BITFLAG
+ mutation_cost = 1
+
+/datum/zombie_mutation/bubble_blood/apply_effects()
+ . = ..()
+ var/datum/action/innate/zombie/default/bubble/B = new
+ B.Grant(zombie_owner.owner?.current)
+
+/datum/zombie_mutation/acid_puke
+ sector = SECTOR_CLASS
+ name = "Reinforced Esophagus"
+ id = "stronger_acid"
+ desc = "Gain the Puke ability."
+ owner_class = SPITTER_BITFLAG
+ mutation_cost = 2
+
+/datum/zombie_mutation/acid_puke/apply_effects()
+ . = ..()
+ var/datum/action/innate/zombie/default/puke/P = new
+ P.Grant(zombie_owner.owner?.current)
diff --git a/code/modules/antagonists/zombie/mutations/tank.dm b/code/modules/antagonists/zombie/mutations/tank.dm
new file mode 100644
index 000000000000..6849379aeb71
--- /dev/null
+++ b/code/modules/antagonists/zombie/mutations/tank.dm
@@ -0,0 +1,34 @@
+/datum/zombie_mutation/strong_arm
+ sector = SECTOR_CLASS
+ name = "Muscular Fibers"
+ desc = "Increased damage resistence on defense mode."
+ id = "strong_arm"
+ owner_class = JUGGERNAUT_BITFLAG
+ mutation_cost = 1
+
+/datum/zombie_mutation/jab
+ sector = SECTOR_CLASS
+ name = "Neuron Repathing"
+ desc = "Gain the Quick Jab ability."
+ id = "quick_jab"
+ owner_class = JUGGERNAUT_BITFLAG
+ mutation_cost = 1
+
+/datum/zombie_mutation/jab/apply_effects()
+ . = ..()
+ var/datum/action/innate/zombie/jab/J = new
+ J.Grant(zombie_owner.owner?.current)
+
+/datum/zombie_mutation/increased_rage
+ sector = SECTOR_CLASS
+ name = "Adapted Pain Receptors"
+ desc = "Doubled Rage gain on damage."
+ id = "increased_rage"
+ owner_class = JUGGERNAUT_BITFLAG
+ mutation_cost = 2
+
+/datum/zombie_mutation/increased_rage/apply_effects()
+ . = ..()
+ var/mob/living/carbon/human/user = zombie_owner.owner?.current
+ var/datum/species/zombie/infectious/gamemode/juggernaut/jugg = user?.dna?.species
+ jugg.infection_regen = 2.5
diff --git a/code/modules/antagonists/zombie/objectives.dm b/code/modules/antagonists/zombie/objectives.dm
new file mode 100644
index 000000000000..c97f35d79a4b
--- /dev/null
+++ b/code/modules/antagonists/zombie/objectives.dm
@@ -0,0 +1,28 @@
+/datum/objective/zombie
+ name = "zombie_objective"
+
+/datum/objective/zombie/New()
+ update_explanation_text()
+ . = ..()
+
+/datum/objective/zombie/infect
+ name = "infect"
+ var/player_count
+ var/people_to_infect
+
+/datum/objective/zombie/infect/New()
+ player_count = SSticker.mode.current_players[CURRENT_LIVING_PLAYERS].len
+ people_to_infect = round(rand(player_count / 3, player_count / 2), 1)
+ . = ..()
+
+/datum/objective/zombie/infect/check_completion()
+ if(LAZYLEN(GLOB.zombies) > people_to_infect)
+ return TRUE
+ return FALSE
+
+/datum/objective/zombie/infect/update_explanation_text()
+ ..()
+ var/player_count = SSticker.mode.current_players[CURRENT_LIVING_PLAYERS].len
+ var/people_to_infect = round(rand(player_count / 3, player_count / 2), 1) // example: 36 players -> 12 and 18 -> about 15 people || 48 players -> 16 and 24 -> about 20 people
+ explanation_text = "Infect atleast [people_to_infect] people in total. This account for other zombies infects"
+
diff --git a/code/modules/antagonists/zombie/zombie.dm b/code/modules/antagonists/zombie/zombie.dm
deleted file mode 100644
index beba12770422..000000000000
--- a/code/modules/antagonists/zombie/zombie.dm
+++ /dev/null
@@ -1,443 +0,0 @@
-#define TIER_2_TIME 4500
-
-/datum/antagonist/zombie
- name = "Zombie"
- roundend_category = "zombies"
- antagpanel_category = "Zombie"
-
- var/datum/action/innate/zombie/zomb/zombify = new
-
- var/datum/action/innate/zombie/talk/talk_action = new
-
- var/datum/action/innate/zombie/choose_class/evolution = new
-
- var/datum/action/innate/zombie/choose_class/tier2/evolution2 = new
-
- //EVOLUTION
- var/evolutionTime = 0 //When can we evolve?
-
- //GENERAL ABILITIES
- var/datum/action/innate/zombie/uncuff/uncuff = new
-
- //SPITTER ABILITIES
- var/obj/effect/proc_holder/zombie/spit/spit
- var/obj/effect/proc_holder/zombie/acid/acid
-
- //Necromancer
- var/obj/effect/proc_holder/zombie/necromance/necro
-
- //Runner
- var/obj/effect/proc_holder/zombie/adrenaline/adren
-
- //Juggernaut
- var/obj/effect/proc_holder/zombie/tank/tank
-
- job_rank = ROLE_ZOMBIE
-
- var/datum/team/zombie/team
- var/hud_type = "zombie"
-
- var/class_chosen = FALSE
- var/class_chosen_2 = FALSE
-
- var/spit_cooldown = 0
-
- var/zombified = FALSE
-
- var/evolution_ready = FALSE
-
-
-/datum/antagonist/zombie/create_team(datum/team/zombie/new_team)
- if(!new_team)
- for(var/HU in GLOB.zombies)
- var/datum/antagonist/zombie/H = HU
- if(!H.owner)
- continue
- if(H.team)
- team = H.team
- return
- team = new /datum/team/zombie
- team.setup_objectives()
- return
- if(!istype(new_team))
- stack_trace("Wrong team type passed to [type] initialization.")
- team = new_team
-
-/datum/antagonist/zombie/proc/add_objectives()
- objectives |= team.objectives
-
-/datum/antagonist/zombie/Destroy()
- QDEL_NULL(zombify)
- return ..()
-
-
-/datum/antagonist/zombie/greet()
- to_chat(owner.current, "You have been infected with a zombie virus! In 15 minutes you will be able to turn into a zombie...")
- to_chat(owner.current, "Use the button at the top of the screen (When it appears) to activate the infection. It will kill you, but you will rise as a zombie shortly after!") //Yogs
- owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/ling_aler.ogg', 100, FALSE, pressure_affected = FALSE)//subject to change
- owner.announce_objectives()
-
-/datum/antagonist/zombie/on_gain()
- . = ..()
- var/mob/living/current = owner.current
- add_objectives()
- GLOB.zombies += owner
-
- current.log_message("has been made a zombie!", LOG_ATTACK, color="#960000")
-
- var/datum/atom_hud/antag/zombie_hud = GLOB.huds[ANTAG_HUD_ZOMBIE]
- zombie_hud.join_hud(current)
- set_antag_hud(current, hud_type)
-
-
-/datum/antagonist/zombie/apply_innate_effects()
- . = ..()
- var/mob/living/current = owner.current
- current.faction |= "zombies"
- talk_action.Grant(current)
-
-/datum/antagonist/zombie/remove_innate_effects()
- . = ..()
- var/mob/living/current = owner.current
- current.faction -= "zombies"
- talk_action.Remove(current)
-
-
-/datum/antagonist/zombie/on_removal()
- GLOB.zombies -= owner
-
- var/datum/atom_hud/antag/zombie_hud = GLOB.huds[ANTAG_HUD_ZOMBIE]
- zombie_hud.leave_hud(owner.current)
- set_antag_hud(owner.current, null)
- . = ..()
-
-
-/datum/antagonist/zombie/proc/start_timer()
- addtimer(CALLBACK(src, .proc/add_button_timed), 15 MINUTES)
-
-/datum/antagonist/zombie/proc/add_button_timed()
- zombify.Grant(owner.current)
- to_chat(owner.current, span_userdanger("You can now turn into a zombie! The ability INSTANTLY kills you, and starts the process of turning into a zombie. IN 5 MINUTES YOU WILL FORCIBLY BE ZOMBIFIED IF YOU HAVEN'T."))
- addtimer(CALLBACK(src, .proc/force_zombify), 5 MINUTES)
-
-/datum/antagonist/zombie/proc/force_zombify()
- if(!zombified)
- zombify.Activate()
-
-/datum/antagonist/zombie/admin_add(datum/mind/new_owner,mob/admin)
- new_owner.add_antag_datum(src)
- message_admins("[key_name_admin(admin)] has zombied'ed [key_name_admin(new_owner)].")
- log_admin("[key_name(admin)] has zombied'ed [key_name(new_owner)].")
- start_timer()
-
-
-/datum/antagonist/zombie/get_admin_commands()
- . = ..()
- .["Give Button"] = CALLBACK(src,.proc/admin_give_button)
- .["Remove Button"] = CALLBACK(src,.proc/remove_button)
-
-/datum/antagonist/zombie/proc/admin_give_button(mob/admin)
- zombify.Grant(owner.current)
-
-/datum/antagonist/zombie/proc/remove_button(mob/admin)
- zombify.Remove(owner.current)
-
-/datum/antagonist/zombie/proc/start_evolution_2()
- addtimer(CALLBACK(src, .proc/finish_evolution_2), TIER_2_TIME)
-
-/datum/antagonist/zombie/proc/finish_evolution_2()
- evolution_ready = TRUE
- evolution2.Grant(owner.current)
- to_chat(owner.current, span_userdanger("You can now evolve into a Tier 2 zombie! There can only be tier 2 zombies equal to the amount of starting zombies!"))
-
-/datum/team/zombie
- name = "Zombies"
-
-/datum/team/zombie/proc/setup_objectives()
-
- var/datum/objective/custom/obj = new()
- obj.name = "Escape on the shuttle, while gathering as many infected as possible!"
- obj.explanation_text = "Escape on the shuttle, while gathering as many infected as possible!"
- obj.completed = TRUE
- objectives += obj
-
-
-/datum/team/zombie/proc/zombies_on_shuttle()
- for(var/mob/living/carbon/human/H in GLOB.alive_mob_list)
- if(isinfected(H) && (H.onCentCom() || H.onSyndieBase()))
- return TRUE
- return FALSE
-
-/datum/team/zombie/roundend_report()
- var/list/parts = list()
- if(zombies_on_shuttle())
- parts += "BRAINS! The zombies have made it to CentCom!"
- else
- parts += "Target destroyed. The crew has stopped the zombies!"
-
-
- if(members.len)
- parts += span_header("The zombies were:")
- parts += printplayerlist(members)
-
- return "[parts.Join("
")]
"
-
-/datum/action/innate/zombie
- icon_icon = 'icons/mob/actions/actions_changeling.dmi'
- background_icon_state = "bg_demon"
- buttontooltipstyle = "cult"
- check_flags = AB_CHECK_RESTRAINED|AB_CHECK_STUN|AB_CHECK_CONSCIOUS
-
-/datum/action/innate/zombie/IsAvailable()
- if(!isinfected(owner))
- return FALSE
- return ..()
-
-/datum/action/innate/zombie/zomb
- name = "Zombify!"
- desc = "Initiate the infection, and kill this host.. THIS ACTION IS INSTANT."
- button_icon_state = "chameleon_skin"
-
-/datum/action/innate/zombie/zomb/Activate(forced = FALSE)
- var/mob/living/carbon/human/H = usr
- var/datum/antagonist/zombie/Z = locate() in owner.mind.antag_datums
- if(!forced)
- if(alert(H, "Are you sure you want to kill yourself, and revive as a zombie some time after?", "Confirmation", "Yes", "No") == "No")
- return FALSE
-
- if(!H.getorganslot(ORGAN_SLOT_ZOMBIE))
- var/obj/item/organ/zombie_infection/gamemode/ZI = new()
- ZI.Insert(H)
-
- if(H.mind)
- H.mind.zombified = TRUE
- H.death()
- Z.zombify.Remove(H)
- Z.zombified = TRUE
- Z.uncuff.Grant(H)
- Z.evolutionTime = TIER_2_TIME + world.time
- Z.start_evolution_2()
-
-
-
-
-/datum/action/innate/zombie/talk
- name = "Chat"
- desc = "Chat with your fellow infected."
- icon_icon = 'icons/mob/actions/actions_cult.dmi'
- button_icon_state = "cult_comms"
-
-/datum/action/innate/zombie/talk/Activate()
- var/input = stripped_input(usr, "Please choose a message to tell to the other zombies.", "Infected Communications", "")
- if(!input || !IsAvailable())
- return
-
- talk(usr, input)
-
-/datum/action/innate/zombie/talk/proc/talk(mob/living/user, message)
- var/my_message
- if(!message)
- return
- var/title = "Zombie"
- var/span = "cult"
- my_message = "\[[title]] [findtextEx(user.name, user.real_name) ? user.name : "[user.real_name] (as [user.name])"]: [message]"
- for(var/i in GLOB.player_list)
- var/mob/M = i
- if(isinfected(M))
- to_chat(M, my_message)
- else if(M in GLOB.dead_mob_list)
- var/link = FOLLOW_LINK(M, user)
- to_chat(M, "[link] [my_message]")
-
- user.log_talk(message, LOG_SAY, tag="zombie")
-
-/datum/action/innate/zombie/choose_class
- name = "Evolve"
- desc = "Evolve into a special class."
- icon_icon = 'icons/mob/actions/actions_cult.dmi'
- button_icon_state = "cultfist"
-
-/datum/action/innate/zombie/choose_class/Activate()
- var/selected = input(usr, "Choose a class to evolve into", "Evolution") as null|anything in list("Runner", "Juggernaut", "Spitter")
- if(!selected || !IsAvailable())
- return
- if(!isinfectedzombie(owner))
- return
- evolve(selected)
-
-/datum/action/innate/zombie/choose_class/IsAvailable(forced = FALSE)
- if(!isinfected(owner))
- return
- var/datum/antagonist/zombie/Z = locate() in owner.mind.antag_datums
-
- if(Z.class_chosen && !forced)
- return FALSE
- return ..()
-
-/datum/action/innate/zombie/choose_class/proc/evolve(class)
- var/mob/living/carbon/human/H = owner
- var/datum/antagonist/zombie/Z = locate() in owner.mind.antag_datums
- if(!isinfected(H))
- return
- switch(class)
- if("Runner")
- H.set_species(/datum/species/zombie/infectious/gamemode/runner)
- Z.adren = new()
- H.AddAbility(Z.adren)
- to_chat(owner, span_warning("You can now run, and your movement speed is considerably faster. You do less damage and can take less damage though."))
- if("Juggernaut")
- H.set_species(/datum/species/zombie/infectious/gamemode/juggernaut)
- Z.tank = new()
- H.AddAbility(Z.tank)
- to_chat(owner, span_warning("You can now take quite a beating, and heal a bit slower."))
- if("Spitter")
- H.set_species(/datum/species/zombie/infectious/gamemode/spitter)
- Z.spit = new()
- Z.acid = new()
- H.AddAbility(Z.spit)
- H.AddAbility(Z.acid)
- to_chat(owner, span_warning("You can now right click on walls and doors, and cover them in acid! You are weaker in combat though."))
-
- owner.visible_message(span_danger("[owner] suddenly convulses, as [owner.p_they()] evolve into a [class]!"), span_alien("You have evolved into a [class]"))
- playsound(owner.loc, 'sound/hallucinations/far_noise.ogg', 50, 1)
- H.do_jitter_animation(15)
- Z.evolution.Remove(H)
- Z.class_chosen = TRUE
-
-/datum/action/innate/zombie/choose_class/tier2
- name = "Evolve - Tier 2"
- desc = "Evolve into a Tier 2 special class."
- icon_icon = 'icons/mob/actions/actions_cult.dmi'
- button_icon_state = "cultfist"
-
-/datum/action/innate/zombie/choose_class/tier2/IsAvailable()
- if(!isinfected(owner))
- return
- var/datum/antagonist/zombie/Z = locate() in owner.mind.antag_datums
-
- if(Z.class_chosen_2)
- return FALSE
-
- if(!Z.class_chosen)
- return FALSE
- return ..(TRUE)
-
-/datum/action/innate/zombie/choose_class/tier2/Activate()
- var/selected = input(usr, "Choose a class to evolve into", "Evolution") as null|anything in list("Necromancer")
- if(!selected || !IsAvailable())
- return
- if(!isinfectedzombie(owner))
- return
-
- var/datum/game_mode/zombie/mode = SSticker.mode
- if(!mode.can_evolve_tier_2())
- to_chat(usr, span_userdanger("There are currently too many tier 2 zombies. Please wait."))
- return
- evolve(selected)
-
-/datum/action/innate/zombie/choose_class/tier2/evolve(class)
- var/mob/living/carbon/human/H = owner
- var/datum/antagonist/zombie/Z = locate() in owner.mind.antag_datums
- if(!isinfected(H))
- return
- switch(class)
- if("Necromancer")
- H.set_species(/datum/species/zombie/infectious/gamemode/necromancer)
- Z.necro = new()
- H.AddAbility(Z.necro)
- to_chat(owner, span_warning("You can now run, and your movement speed is considerably faster. You do less damage and can take less damage though."))
- if("Coordinator")
- H.set_species(/datum/species/zombie/infectious/gamemode/coordinator)
- to_chat(owner, span_warning("You can now communicate with the horde!"))
-
-
- if(Z.spit)
- H.RemoveAbility(Z.spit)
- if(Z.acid)
- H.RemoveAbility(Z.acid)
- if(Z.adren)
- H.RemoveAbility(Z.adren)
- if(Z.tank)
- H.RemoveAbility(Z.tank)
-
-
- owner.visible_message(span_danger("[owner] suddenly convulses, as [owner.p_they()] evolve into a [class]!"), span_alien("You have evolved into a [class]"))
- playsound(owner.loc, 'sound/hallucinations/far_noise.ogg', 50, 1)
- H.do_jitter_animation(15)
- Z.evolution2.Remove(H)
- Z.class_chosen_2 = TRUE
-
-/obj/effect/proc_holder/zombie
- name = "Zombie Power"
- panel = "Zombie"
- has_action = TRUE
- base_action = /datum/action/spell_action
- action_icon = 'icons/mob/actions/actions_xeno.dmi'
- action_icon_state = "spell_default"
- action_background_icon_state = "bg_alien"
- var/ready = TRUE
- var/cooldown_ends = 0
- var/cooldown_time = 1 SECONDS
-
- var/silent = TRUE //Do you have to be conscious to use this?
-
-/obj/effect/proc_holder/zombie/Initialize()
- . = ..()
- action = new(src)
-
-/obj/effect/proc_holder/zombie/Click()
- if(!iscarbon(usr))
- return TRUE
- var/mob/living/carbon/user = usr
- if(can_cast(user))
- fire(user)
- return TRUE
-
-/obj/effect/proc_holder/zombie/on_gain(mob/living/carbon/user)
- return
-
-/obj/effect/proc_holder/zombie/on_lose(mob/living/carbon/user)
- return
-
-/obj/effect/proc_holder/zombie/fire(mob/living/carbon/user)
- start_cooldown()
- return TRUE
-
-/obj/effect/proc_holder/zombie/get_panel_text()
- . = ..()
- if((cooldown_ends - world.time) > 0)
- return "[(cooldown_ends - world.time) / 10] seconds"
-
-/obj/effect/proc_holder/zombie/proc/can_cast(mob/living/carbon/user)
- if(!isinfected(user))
- return FALSE
-
- if(user.stat)
- if(!silent)
- to_chat(user, span_userdanger("You must be conscious to do this."))
- return FALSE
- if(!ready)
- to_chat(user, span_userdanger("You can use this ability in [(cooldown_ends - world.time) / 10] seconds."))
-
- return ready
-
-/obj/effect/proc_holder/zombie/proc/reset_cooldown()
- ready = TRUE
-
-/obj/effect/proc_holder/zombie/proc/start_cooldown()
- addtimer(CALLBACK(src, .proc/reset_cooldown), cooldown_time)
- cooldown_ends = world.time + cooldown_time
- ready = FALSE
-
-#undef TIER_2_TIME
-
-/datum/antagonist/zombie/get_preview_icon()
- var/mob/living/carbon/human/dummy/consistent/zombiedummy = new
-
- zombiedummy.set_species(/datum/species/zombie)
-
- var/icon/zombie_icon = render_preview_outfit(null, zombiedummy)
-
- qdel(zombiedummy)
-
- return finish_preview_icon(zombie_icon)
diff --git a/code/modules/antagonists/zombie/zombie_conquer.dm b/code/modules/antagonists/zombie/zombie_conquer.dm
new file mode 100644
index 000000000000..ce907a28beda
--- /dev/null
+++ b/code/modules/antagonists/zombie/zombie_conquer.dm
@@ -0,0 +1,348 @@
+#define MUTATION_TIER_0 0
+#define MUTATION_TIER_1 2
+#define MUTATION_TIER_2 4 //currently unimplemented
+#define MUTATION_TIER_3 8
+
+/datum/antagonist/zombie
+ name = "Zombie"
+ roundend_category = "zombies"
+ antagpanel_category = "Zombie"
+
+ var/datum/action/innate/zombie/zomb/zombify = new
+
+ var/datum/action/innate/zombie/talk/talk_action = new
+
+ var/datum/action/innate/zombie/menu/ui_interact = new
+
+ var/datum/action/innate/zombie/bite/bite_power = new
+
+ //EVOLUTION
+ var/evolutionTime = 0 //When can we evolve?
+
+ //GENERAL ABILITIES
+ var/datum/action/innate/zombie/uncuff/uncuff = new
+
+ job_rank = ROLE_ZOMBIE
+
+ var/datum/team/zombie/team
+ var/hud_type = "zombie"
+
+ var/class_chosen = null //class chosen on first evolve
+ var/class_chosen_2 = null //class chosen on second evolve, currently unused
+
+ var/zombified = FALSE //if have become a zombie or not
+
+ var/mutation_rate = 0 //progress to new evolution
+
+ var/mutation_points = 0 //points to buy new abilities and upgrades
+
+ var/infection = 100 //points used on abilities
+
+ var/infection_max = 100 //max you can have of infection
+
+ var/current_tier = MUTATION_TIER_0 //current mutation tier (based on evolve)
+
+ var/list/zombie_abilities = list() //abilities
+ var/list/zombie_mutations = list() //upgrades
+
+/datum/antagonist/zombie/special
+ name = "Special Zombie"
+
+/datum/antagonist/zombie/create_team(datum/team/zombie/new_team)
+ if(!new_team)
+ for(var/HU in GLOB.zombies)
+ var/datum/antagonist/zombie/H = HU
+ if(!H.owner)
+ continue
+ if(H.team)
+ team = H.team
+ return
+ team = new /datum/team/zombie
+ team.setup_objectives()
+ return
+ if(!istype(new_team))
+ stack_trace("Wrong team type passed to [type] initialization.")
+ team = new_team
+
+/datum/antagonist/zombie/proc/add_objectives()
+ objectives |= team.objectives
+
+/datum/antagonist/zombie/Destroy()
+ QDEL_NULL(zombify)
+ return ..()
+
+/datum/antagonist/zombie/greet()
+ to_chat(owner.current, "You have infected a suitlabe host! In 10 minutes you will be able to transform [owner.current.p_them()] into a potent zombie.")
+ to_chat(owner.current, "Use the button at the top of the screen (When it appears) to activate the infection. It will kill you, but you will rise as a zombie shortly after!") //Yogs
+ owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/ling_aler.ogg', 100, FALSE, pressure_affected = FALSE)//subject to change
+ owner.announce_objectives()
+
+/datum/antagonist/zombie/on_gain()
+ . = ..()
+ var/mob/living/current = owner.current
+ add_objectives()
+ GLOB.zombies += owner
+
+ current.log_message("has been made a zombie!", LOG_ATTACK, color="#960000")
+
+ var/datum/atom_hud/antag/zombie_hud = GLOB.huds[ANTAG_HUD_ZOMBIE]
+ zombie_hud.join_hud(current)
+ set_antag_hud(current, hud_type)
+ owner.current.hud_used.infection_display.invisibility = 0
+ update_infection_hud()
+ if(!IS_SPECIALINFECTED(owner.current))
+ bite_power.Grant(owner.current)
+
+/datum/antagonist/zombie/on_removal()
+ GLOB.zombies -= owner
+
+ var/datum/atom_hud/antag/zombie_hud = GLOB.huds[ANTAG_HUD_ZOMBIE]
+ zombie_hud.leave_hud(owner.current)
+ set_antag_hud(owner.current, null)
+ owner.current.hud_used.infection_display.invisibility = initial(owner.current.hud_used.infection_display.invisibility)
+ owner.current.hud_used.infection_display.maptext = ""
+ if(!IS_SPECIALINFECTED(owner.current))
+ bite_power.Remove(owner.current)
+ . = ..()
+
+/datum/antagonist/zombie/apply_innate_effects()
+ . = ..()
+ var/mob/living/current = owner.current
+ current.faction |= "zombies"
+ ui_interact.Grant(current)
+ talk_action.Grant(current)
+
+/datum/antagonist/zombie/remove_innate_effects()
+ . = ..()
+ var/mob/living/current = owner.current
+ current.faction -= "zombies"
+ ui_interact.Remove(current)
+ talk_action.Remove(current)
+
+/*
+* from yogstation\code\modules\antagonists\darkspawn\darkspawn.dm
+*/
+/datum/antagonist/zombie/proc/has_mutation(id)
+ return zombie_mutations[id]
+
+/datum/antagonist/zombie/proc/add_mutation(id, silent, no_cost)
+ if(has_mutation(id))
+ return
+ for(var/datum/zombie_mutation/mutation_data as anything in subtypesof(/datum/zombie_mutation))
+ if(initial(mutation_data.id) == id)
+ var/datum/zombie_mutation/mutation = new mutation_data(src)
+ zombie_mutations[id] = TRUE
+ if(!silent)
+ to_chat(owner.current, span_notice("You have adapted \"[mutation.name]\"."))
+ if(!no_cost)
+ mutation_points = max(0, mutation_points - initial(mutation.mutation_cost))
+ mutation.on_purchase()
+
+/*
+* + for spending, - for gaining
+*/
+/datum/antagonist/zombie/proc/manage_infection(amt)
+ if(infection < amt)
+ return FALSE
+ infection -= amt
+ update_infection_hud()
+ return TRUE
+
+/datum/antagonist/zombie/proc/manage_mutation(amt)
+ if(mutation_points < amt)
+ return FALSE
+ mutation_points -= amt
+ return TRUE
+
+/datum/antagonist/zombie/proc/advance_progress()
+ mutation_rate++
+ switch(mutation_rate)
+ if(MUTATION_TIER_1)
+ var/datum/action/innate/zombie/choose_class/evolution = new
+ evolution.Grant(owner.current)
+
+/datum/antagonist/zombie/proc/advance_mutation_tier()
+ var/new_tier
+ switch(current_tier)
+ if(MUTATION_TIER_0)
+ new_tier = MUTATION_TIER_1
+ if(MUTATION_TIER_1)
+ new_tier = MUTATION_TIER_2
+ if(MUTATION_TIER_2)
+ new_tier = MUTATION_TIER_3
+ if(MUTATION_TIER_3)
+ return
+ current_tier = new_tier
+
+
+/datum/antagonist/zombie/proc/update_infection_hud()
+ var/atom/movable/screen/counter = owner?.current?.hud_used?.infection_display
+ if(!counter)
+ return
+ if(class_chosen)
+ counter.add_overlay("overlay_[class_chosen]")
+ counter.maptext = "[round(infection, 1)]
"
+
+/datum/antagonist/zombie/ui_state(mob/user)
+ return GLOB.always_state
+
+/datum/antagonist/zombie/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "EvolutionMenu", "Host Genetic Management")
+ ui.open()
+
+/datum/antagonist/zombie/ui_data(mob/user)
+ var/list/data = list()
+ var/list/abilities = list()
+ var/list/mutations = list()
+ var/mob/living/carbon/human/H = user
+ data["name"] = user.real_name
+ data["mutation_rate"] = mutation_rate
+ data["mutation_points"] = mutation_points
+ data["current_status"] = user.stat
+ data["current_host"] = ishuman(user) ? H.dna.species : user.type
+ data["class"] = class_chosen
+
+ for(var/datum/action/innate/zombie/ability_raw as anything in subtypesof(/datum/action/innate/zombie) - /datum/action/innate/zombie/default) //exclude these, get the subtypes
+ if(!locate(ability_raw) in zombie_abilities)
+ continue
+ if(!length(initial(ability_raw.name)) || !length(initial(ability_raw.desc)))
+ continue
+ var/list/ability_processed = list()
+ ability_processed["type"] = initial(ability_raw.ability_type)
+ ability_processed["name"] = initial(ability_raw.name)
+ ability_processed["desc"] = initial(ability_raw.desc)
+ ability_processed["is_default"] = istype(ability_raw, /datum/action/innate/zombie/default)
+ var/datum/action/innate/zombie/default/default_ability_raw = ability_raw
+ ability_processed["cost"] = istype(ability_raw, /datum/action/innate/zombie/default) ? "Cost: [initial(default_ability_raw.cost)]" : ""
+ ability_processed["constant_cost"] = istype(ability_raw, /datum/action/innate/zombie/default) ? "Constant Cost: [initial(default_ability_raw.constant_cost)]" : ""
+
+ abilities += list(ability_processed)
+ data["abilities"] += abilities
+
+ for(var/datum/zombie_mutation/mutation_raw as anything in subtypesof(/datum/zombie_mutation)) //exclude this, get the subtypes
+ if(initial(mutation_raw.sector) != SECTOR_COMMON)
+ switch(class_chosen)
+ if(null)
+ continue
+ if(SMOKER)
+ if(!(initial(mutation_raw.owner_class) & SMOKER_BITFLAG))
+ continue
+ if(RUNNER)
+ if(!(initial(mutation_raw.owner_class) & RUNNER_BITFLAG))
+ continue
+ if(SPITTER)
+ if(!(initial(mutation_raw.owner_class) & SPITTER_BITFLAG))
+ continue
+ if(JUGGERNAUT)
+ if(!(initial(mutation_raw.owner_class) & JUGGERNAUT_BITFLAG))
+ continue
+ if(BRAINY)
+ if(!(initial(mutation_raw.owner_class) & BRAINY_BITFLAG))
+ continue
+ var/list/mutation_processed = list()
+ mutation_processed["name"] = initial(mutation_raw.name)
+ mutation_processed["id"] = initial(mutation_raw.id)
+ mutation_processed["desc"] = initial(mutation_raw.desc)
+ mutation_processed["sector"] = initial(mutation_raw.sector)
+ mutation_processed["mutation_cost"] = initial(mutation_raw.mutation_cost)
+ mutation_processed["owned"] = zombie_mutations[initial(mutation_raw.id)]
+ mutation_processed["can_purchase"] = !mutation_processed["owned"] && mutation_points >= initial(mutation_raw.mutation_cost)
+
+ mutations += list(mutation_processed)
+
+ data["mutations"] += mutations
+
+ /////////////////////////////////////////
+ ///// Write Your Lore/ /////
+ ///// Info Here Section /////
+ /////////////////////////////////////////
+
+ data["info_abilities"] = ""
+ return data
+
+/datum/antagonist/zombie/ui_act(action, params)
+ if(..())
+ return
+ if(action == "upgrade")
+ add_mutation(params["id"])
+
+/datum/antagonist/zombie/proc/start_timer()
+ addtimer(CALLBACK(src, .proc/add_button_timed), 10 MINUTES)
+
+/datum/antagonist/zombie/proc/add_button_timed()
+ if(zombified)
+ return
+ zombify.Grant(owner.current)
+ to_chat(owner.current, span_userdanger("You can now turn into a zombie! The ability INSTANTLY kills you, and starts the process of turning into a zombie. IN 5 MINUTES YOU WILL FORCIBLY BE ZOMBIFIED IF YOU HAVEN'T."))
+ addtimer(CALLBACK(src, .proc/force_zombify), 5 MINUTES)
+
+/datum/antagonist/zombie/proc/force_zombify()
+ if(!zombified)
+ zombify.Activate()
+
+/datum/antagonist/zombie/admin_add(datum/mind/new_owner, mob/admin)
+ new_owner.add_antag_datum(src)
+ message_admins("[key_name_admin(admin)] has zombied'ed [key_name_admin(new_owner)].")
+ log_admin("[key_name(admin)] has zombied'ed [key_name(new_owner)].")
+ start_timer()
+
+
+/datum/antagonist/zombie/get_admin_commands()
+ . = ..()
+ .["Give Transform Button"] = CALLBACK(src, .proc/admin_give_button)
+ .["Remove Transform Button"] = CALLBACK(src, .proc/remove_button)
+ .["Give Mutation Points"] = CALLBACK(src, .proc/manage_mutation, -1)
+ .["Advance Mutation Progress"] = CALLBACK(src, .proc/advance_progress)
+
+/datum/antagonist/zombie/proc/admin_give_button(mob/admin)
+ zombify.Grant(owner.current)
+
+/datum/antagonist/zombie/proc/remove_button(mob/admin)
+ zombify.Remove(owner.current)
+
+/datum/antagonist/zombie/get_preview_icon()
+ var/mob/living/carbon/human/dummy/consistent/zombiedummy = new
+
+ zombiedummy.set_species(/datum/species/zombie)
+
+ var/icon/zombie_icon = render_preview_outfit(null, zombiedummy)
+
+ qdel(zombiedummy)
+
+ return finish_preview_icon(zombie_icon)
+
+/datum/team/zombie
+ name = "Zombies"
+
+/datum/team/zombie/proc/setup_objectives()
+
+ var/datum/objective/zombie/infect/obj = new()
+ objectives += obj
+
+/datum/team/zombie/proc/zombies_on_shuttle()
+ for(var/mob/living/carbon/human/H in GLOB.alive_mob_list)
+ if(IS_INFECTED(H) && (H.onCentCom() || H.onSyndieBase()))
+ return TRUE
+ return FALSE
+
+/datum/team/zombie/roundend_report()
+ var/list/parts = list()
+ var/datum/objective/zombie/infect/obj = LAZYFIND(objectives, /datum/objective/zombie/infect)
+ if(obj.completed)
+ parts += "The zombies were sucessful[zombies_on_shuttle() ? " and made it to CentCom" : span_redtext(" but didn't make it to CentCom")]! "
+ else
+ parts += "Target destroyed. The crew has stopped the zombies!"
+
+
+ if(LAZYLEN(members))
+ parts += span_header("The zombies were:")
+ parts += printplayerlist(members)
+
+ return "[parts.Join("
")]
"
+
+#undef MUTATION_TIER_0
+#undef MUTATION_TIER_1
+#undef MUTATION_TIER_2
+#undef MUTATION_TIER_3
diff --git a/code/modules/antagonists/zombie/zombie_invasion.dm b/code/modules/antagonists/zombie/zombie_invasion.dm
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/code/modules/cargo/exports/organs.dm b/code/modules/cargo/exports/organs.dm
index 7c5d7c19c3c6..1342fc4da7ef 100644
--- a/code/modules/cargo/exports/organs.dm
+++ b/code/modules/cargo/exports/organs.dm
@@ -64,7 +64,7 @@
/datum/export/organ/alien/changeling_egg
cost = 50000 // Holy. Fuck.
unit_name = "changeling egg"
- export_types = list(/obj/item/organ/body_egg/changeling_egg)
+ export_types = list(/obj/item/organ/body_egg/antag_egg/changeling_egg)
/datum/export/organ/hivelord
diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm
index bc093d8e6658..390b4136aeba 100644
--- a/code/modules/client/client_defines.dm
+++ b/code/modules/client/client_defines.dm
@@ -81,6 +81,8 @@
var/mouse_up_icon = null
///used to make a special mouse cursor, this one for mouse up icon
var/mouse_down_icon = null
+ ///used to override the mouse cursor so it doesnt get reset
+ var/mouse_override_icon = null
///Used for ip intel checking to identify evaders, disabled because of issues with traffic
var/ip_intel = "Disabled"
diff --git a/code/modules/mob/living/carbon/alien/special/alien_embryo.dm b/code/modules/mob/living/carbon/alien/special/alien_embryo.dm
index 3cfd2f02a1e9..713ad4777a27 100644
--- a/code/modules/mob/living/carbon/alien/special/alien_embryo.dm
+++ b/code/modules/mob/living/carbon/alien/special/alien_embryo.dm
@@ -9,7 +9,7 @@
/// Are we bursting out of the poor sucker who's the xeno mom?
var/bursting = FALSE
/// How long does it take to advance one stage? Growth time * 5 = how long till we make a Larva!
- var/growth_time = 60 SECONDS
+ var/growth_time = 1 MINUTES
/obj/item/organ/body_egg/alien_embryo/Initialize()
. = ..()
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 0489b91b3d4c..9e79b0d4f71e 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -84,7 +84,7 @@
. += "Hivemind Vessels: [hivemind.hive_size] (+[hivemind.size_mod])"
. += "Psychic Link Duration: [(hivemind.track_bonus + TRACKER_DEFAULT_TIME)/10] seconds"
var/mob/living/simple_animal/horror/H = has_horror_inside()
- if(H && H.controlling)
+ if(H?.controlling)
. += ""
. += "Horror chemicals: [H.chemicals]"
@@ -105,8 +105,10 @@
var/datum/antagonist/zombie/zombie = mind.has_antag_datum(/datum/antagonist/zombie)
if(zombie)
- if((zombie.evolutionTime - world.time) > 0)
- . += "Time to Tier 2 Evolution: [(zombie.evolutionTime - world.time) / 10] seconds"
+ if(isspitter(src) && locate(/datum/action/innate/zombie/default/overclock) in zombie.zombie_abilities)
+ var/datum/action/innate/zombie/default/overclock/Over = locate(/datum/action/innate/zombie/default/overclock) in zombie.zombie_abilities
+ . += ""
+ . += "Current Muscle Stamina: [(Over.tiredness)]"
//NINJACODE
diff --git a/code/modules/mob/living/carbon/human/species_types/zombies.dm b/code/modules/mob/living/carbon/human/species_types/zombies.dm
index bc7ac579f7b3..73382aa7e306 100644
--- a/code/modules/mob/living/carbon/human/species_types/zombies.dm
+++ b/code/modules/mob/living/carbon/human/species_types/zombies.dm
@@ -1,4 +1,4 @@
-#define REGENERATION_DELAY 60 // After taking damage, how long it takes for automatic regeneration to begin
+#define REGENERATION_DELAY 6 SECONDS // After taking damage, how long it takes for automatic regeneration to begin
/datum/species/zombie
// 1spooky
@@ -7,7 +7,7 @@
say_mod = "moans"
sexes = FALSE
meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/zombie
- species_traits = list(NOBLOOD,NOZOMBIE,NOTRANSSTING,HAS_FLESH,HAS_BONE, AGENDER)
+ species_traits = list(NOBLOOD,NOZOMBIE,NOTRANSSTING,HAS_FLESH,HAS_BONE,AGENDER)
inherent_traits = list(TRAIT_STABLELIVER, TRAIT_STABLEHEART, TRAIT_RESISTCOLD ,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_RADIMMUNE,TRAIT_EASYDISMEMBER,TRAIT_EASILY_WOUNDED,TRAIT_LIMBATTACHMENT,TRAIT_NOBREATH,TRAIT_NODEATH,TRAIT_FAKEDEATH)
inherent_biotypes = list(MOB_UNDEAD, MOB_HUMANOID)
mutanttongue = /obj/item/organ/tongue/zombie
@@ -53,7 +53,7 @@
speedmod = 1.6
mutanteyes = /obj/item/organ/eyes/night_vision/zombie
var/heal_rate = 1
- var/regen_cooldown = 0
+ COOLDOWN_DECLARE(regen_cooldown)
changesource_flags = MIRROR_BADMIN | WABBAJACK | ERT_SPAWN
/datum/species/zombie/infectious/check_roundstart_eligible()
@@ -61,12 +61,12 @@
/datum/species/zombie/infectious/spec_stun(mob/living/carbon/human/H,amount)
- . = min(20, amount)
+ . = min(2 SECONDS, amount)
/datum/species/zombie/infectious/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked, mob/living/carbon/human/H, wound_bonus = 0, bare_wound_bonus = 0, sharpness = SHARP_NONE)
. = ..()
if(.)
- regen_cooldown = world.time + REGENERATION_DELAY
+ COOLDOWN_START(src, regen_cooldown, REGENERATION_DELAY)
/datum/species/zombie/infectious/spec_life(mob/living/carbon/C)
. = ..()
@@ -74,7 +74,7 @@
//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)
+ if(COOLDOWN_FINISHED(src, regen_cooldown))
var/heal_amt = heal_rate
if(C.InCritical())
heal_amt *= 2
@@ -91,7 +91,7 @@
/datum/species/zombie/infectious/spec_death(mob/living/carbon/C)
. = ..()
var/obj/item/organ/zombie_infection/infection
- infection = C.getorganslot(ORGAN_SLOT_ZOMBIE)
+ infection = C?.getorganslot(ORGAN_SLOT_ZOMBIE)
if(infection)
qdel(infection)
@@ -107,7 +107,6 @@
infection = new()
infection.Insert(C)
-
// Your skin falls off
/datum/species/krokodil_addict
name = "Human"
@@ -128,27 +127,145 @@
burnmod = 0.925
speedmod = 1.45
mutanthands = /obj/item/zombie_hand/gamemode
- inherent_traits = list(TRAIT_RESISTCOLD, TRAIT_RESISTHIGHPRESSURE, TRAIT_RESISTLOWPRESSURE, TRAIT_RESISTDAMAGESLOWDOWN, TRAIT_STABLELIVER, TRAIT_STABLEHEART,
- TRAIT_RADIMMUNE, TRAIT_LIMBATTACHMENT, TRAIT_NOBREATH, TRAIT_NODEATH, TRAIT_FAKEDEATH, TRAIT_NOHUNGER, TRAIT_RESISTHEAT, TRAIT_SHOCKIMMUNE, TRAIT_PUSHIMMUNE, TRAIT_STUNIMMUNE, TRAIT_BADDNA, TRAIT_EASILY_WOUNDED, TRAIT_EASYDISMEMBER)
- no_equip = list(SLOT_WEAR_MASK, SLOT_GLASSES, SLOT_HEAD)
+ inherent_traits = list(
+ TRAIT_RESISTCOLD, TRAIT_RESISTHIGHPRESSURE, TRAIT_RESISTLOWPRESSURE,
+ TRAIT_RESISTDAMAGESLOWDOWN, TRAIT_STABLELIVER, TRAIT_STABLEHEART,
+ TRAIT_RADIMMUNE, TRAIT_LIMBATTACHMENT, TRAIT_NOBREATH,
+ TRAIT_NODEATH, TRAIT_FAKEDEATH, TRAIT_NOHUNGER,
+ TRAIT_RESISTHEAT, TRAIT_SHOCKIMMUNE, TRAIT_PUSHIMMUNE,
+ TRAIT_STUNIMMUNE, TRAIT_BADDNA, TRAIT_EASILY_WOUNDED, TRAIT_EASYDISMEMBER)
+ var/infection_regen = 0 //to set for special zombies
+
+/datum/species/zombie/infectious/gamemode/harm(mob/living/carbon/human/user, mob/living/carbon/human/target, datum/martial_art/attacker_style) //you don't wanna get too close
+ . = ..()
+ if(prob(30))
+ target.apply_status_effect(STATUS_EFFECT_ZOMBIE_ROT) //get fucked
+
+/datum/species/zombie/infectious/gamemode/spec_AltClickOn(atom/A, mob/living/carbon/human/H)
+ if(!IS_SPECIALINFECTED(H)) //only special ones
+ return
+ var/datum/antagonist/zombie/zombie_owner = H.mind?.has_antag_datum(/datum/antagonist/zombie)
+ if(!ishuman(A))
+ return
+ var/mob/living/carbon/human/target = A
+ if(INTERACTING_WITH(H, target))
+ to_chat(H, span_warning("You are too busy to infect [target.p_them()]!"))
+ return
+ to_chat(H, span_notice("You start inserting spores into [target]s ear canal..."))
+ var/infect_time = (6 SECONDS / max(target.stat, 1)) // alive = 6, soft crit = 6, unconcious = 3, dead = 2
+ if(!do_after(H, infect_time, target))
+ to_chat(H, span_warning("You were interrupted and lost the entryway!"))
+ return
+ to_chat(H, span_notice("You sucessfully infect [target]!"))
+ zombie_owner.advance_progress()
+ var/obj/item/organ/zombie_infection/gamemode/le_organ
+ if(zombie_owner.has_mutation("instant_infection"))
+ le_organ = /obj/item/organ/zombie_infection/gamemode/instant
+ try_to_zombie_infect(target, le_organ)
+
+/datum/species/zombie/infectious/gamemode/on_species_gain(mob/living/carbon/human/H)
+ if(subtypesof(/datum/species/zombie/infectious/gamemode)) // we use general ones for general zombies
+ H.draw_yogs_parts(TRUE) //to not fill the general one with zombe sprites (human_parts --> mutant_parts)
+ inherent_traits = list(
+ TRAIT_RESISTCOLD, TRAIT_RESISTHIGHPRESSURE, TRAIT_RESISTLOWPRESSURE,
+ TRAIT_RESISTDAMAGESLOWDOWN, TRAIT_STABLELIVER, TRAIT_STABLEHEART,
+ TRAIT_RADIMMUNE, TRAIT_LIMBATTACHMENT, TRAIT_NOBREATH,
+ TRAIT_NODEATH, TRAIT_FAKEDEATH, TRAIT_NOHUNGER,
+ TRAIT_RESISTHEAT, TRAIT_SHOCKIMMUNE, TRAIT_PUSHIMMUNE,
+ TRAIT_STUNIMMUNE, TRAIT_BADDNA, TRAIT_EASILY_WOUNDED, TRAIT_NODISMEMBER) //devilish
+ . = ..()
+
+/datum/species/zombie/infectious/gamemode/on_species_loss(mob/living/carbon/human/H)
+ if(subtypesof(/datum/species/zombie/infectious/gamemode))
+ H.draw_yogs_parts(FALSE) //returns us to general file
+ inherent_traits = list(
+ TRAIT_RESISTCOLD, TRAIT_RESISTHIGHPRESSURE, TRAIT_RESISTLOWPRESSURE,
+ TRAIT_RESISTDAMAGESLOWDOWN, TRAIT_STABLELIVER, TRAIT_STABLEHEART,
+ TRAIT_RADIMMUNE, TRAIT_LIMBATTACHMENT, TRAIT_NOBREATH,
+ TRAIT_NODEATH, TRAIT_FAKEDEATH, TRAIT_NOHUNGER,
+ TRAIT_RESISTHEAT, TRAIT_SHOCKIMMUNE, TRAIT_PUSHIMMUNE,
+ TRAIT_STUNIMMUNE, TRAIT_BADDNA, TRAIT_EASILY_WOUNDED, TRAIT_EASYDISMEMBER) //update it
+ . = ..()
/datum/species/zombie/infectious/gamemode/runner
+ limbs_id = "runner"
mutanthands = /obj/item/zombie_hand/gamemode/runner
- armor = 10 // 110 damage to KO a zombie, which kills it
+ armor = 15 // 110 damage to KO a zombie, which kills it
speedmod = 0.45
brutemod = 1
+ infection_regen = 0.5 //per move
/datum/species/zombie/infectious/gamemode/juggernaut
+ limbs_id = "juggernaut"
mutanthands = /obj/item/zombie_hand/gamemode/tank
- armor = 30 // 135 damage to KO a zombie, which kills it
+ armor = 35 // 135 damage to KO a zombie, which kills it
brutemod = 0.75
speedmod = 1.3
heal_rate = 1.20
+ infection_regen = 5 //inversely proportional to how much you actually get, so less = better, per damage
/datum/species/zombie/infectious/gamemode/spitter
- armor = 5 // 110 damage to KO a zombie, which kills it
+ limbs_id = "spitter"
+ mutanthands = /obj/item/zombie_hand/gamemode/spitter
+ armor = 20 // 120 damage to KO a zombie, which kills it
brutemod = 1
burnmod = 1
+ infection_regen = 2 //passive
+
+///Delay for infection points to regenerate after deactivating the ability.
+#define SMOKER_REGEN_DELAY 5 SECONDS
+
+/datum/species/zombie/infectious/gamemode/smoker
+ limbs_id = "smoker"
+ mutanthands = /obj/item/zombie_hand/gamemode/smoker
+ armor = 15
+ infection_regen = 2.5 //passive
+
+/*
+* Smokers and Spitters regenerate infection points naturally.
+*/
+/datum/species/zombie/infectious/gamemode/smoker/spec_life(mob/living/carbon/C)
+ . = ..()
+ if(!IS_INFECTED(C))
+ return
+ var/datum/antagonist/zombie/Z = C.mind.has_antag_datum(/datum/antagonist/zombie)
+ var/datum/action/innate/zombie/default/smoke/ability = locate() in Z.zombie_abilities
+ if(ability?.last_fired > world.time - SMOKER_REGEN_DELAY)
+ return
+ if(Z.infection_max - Z.infection >= infection_regen)
+ Z.manage_infection(-infection_regen)
+ else
+ Z.manage_infection(Z.infection - Z.infection_max)
+
+/datum/species/zombie/infectious/gamemode/spitter/spec_life(mob/living/carbon/C)
+ . = ..()
+ if(!IS_INFECTED(C))
+ return
+ var/datum/antagonist/zombie/Z = C.mind.has_antag_datum(/datum/antagonist/zombie)
+ if(Z.infection_max - Z.infection >= infection_regen)
+ Z.manage_infection(-infection_regen)
+ else
+ Z.manage_infection(Z.infection - Z.infection_max)
+
+#undef SMOKER_REGEN_DELAY
+
+/*
+* Juggs gain infection points when harmed.
+*/
+/datum/species/zombie/infectious/gamemode/juggernaut/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked, mob/living/carbon/human/H, wound_bonus = 0, bare_wound_bonus = 0, sharpness = FALSE)
+ var/obj/item/zombie_hand/gamemode/tank/hand = H.get_active_held_item() || H.get_inactive_held_item()
+ if(!hand.is_stub && hand.defensive_mode && def_zone != BODY_ZONE_L_ARM && prob(35))
+ def_zone = BODY_ZONE_L_ARM //35% chance to arm to take damage if defensive
+ . = ..()
+ if(!.)
+ return
+ if(damagetype == TOX) //cheeky bastard, do not consume plasma!
+ return
+ var/datum/antagonist/zombie/Z = H.mind.has_antag_datum(/datum/antagonist/zombie)
+ if(Z.infection_max - Z.infection >= damage / infection_regen)
+ Z.manage_infection(-damage / infection_regen)
+ else
+ Z.manage_infection(Z.infection - Z.infection_max)
/datum/species/zombie/infectious/gamemode/spec_stun(mob/living/carbon/human/H,amount)
. = 0
@@ -158,8 +275,25 @@
return
. = ..()
if(.)
- regen_cooldown = world.time + REGENERATION_DELAY
+ COOLDOWN_START(src, regen_cooldown, REGENERATION_DELAY)
+
+/*
+* Runners gain infection points on move.
+*/
+/datum/species/zombie/infectious/gamemode/runner/on_species_gain(mob/living/carbon/human/H)
+ RegisterSignal(H, COMSIG_MOVABLE_MOVED, .proc/on_move)
+ . = ..()
+
+/datum/species/zombie/infectious/gamemode/on_species_loss(mob/living/carbon/human/H)
+ UnregisterSignal(H, COMSIG_MOVABLE_MOVED)
+ . = ..()
+/datum/species/zombie/infectious/gamemode/runner/proc/on_move(mob/living/carbon/human/H)
+ var/datum/antagonist/zombie/Z = H.mind.has_antag_datum(/datum/antagonist/zombie)
+ if(Z.infection_max - Z.infection >= infection_regen)
+ Z.manage_infection(-infection_regen)
+ else
+ Z.manage_infection(Z.infection - Z.infection_max)
/datum/species/zombie/infectious/gamemode/coordinator
armor = 17
diff --git a/code/modules/mob/living/damage_procs.dm b/code/modules/mob/living/damage_procs.dm
index d5a2b0de3012..b01e5452cd75 100644
--- a/code/modules/mob/living/damage_procs.dm
+++ b/code/modules/mob/living/damage_procs.dm
@@ -151,6 +151,8 @@
return bruteloss
/mob/living/proc/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE, required_status)
+ if(amount < 0 && HAS_TRAIT(src, TRAIT_NO_HEAL))
+ return FALSE
if(!forced && (status_flags & GODMODE))
return FALSE
bruteloss = clamp((bruteloss + (amount * CONFIG_GET(number/damage_multiplier))), 0, maxHealth * 2)
@@ -162,6 +164,8 @@
return oxyloss
/mob/living/proc/adjustOxyLoss(amount, updating_health = TRUE, forced = FALSE)
+ if(amount < 0 && HAS_TRAIT(src, TRAIT_NO_HEAL))
+ return FALSE
if(!forced && (status_flags & GODMODE))
return FALSE
oxyloss = clamp((oxyloss + (amount * CONFIG_GET(number/damage_multiplier))), 0, maxHealth * 2)
@@ -170,6 +174,8 @@
return amount
/mob/living/proc/setOxyLoss(amount, updating_health = TRUE, forced = FALSE)
+ if(amount < 0 && HAS_TRAIT(src, TRAIT_NO_HEAL))
+ return FALSE
if(status_flags & GODMODE)
return 0
oxyloss = amount
@@ -181,6 +187,8 @@
return toxloss
/mob/living/proc/adjustToxLoss(amount, updating_health = TRUE, forced = FALSE)
+ if(amount < 0 && HAS_TRAIT(src, TRAIT_NO_HEAL))
+ return FALSE
if(!forced && (status_flags & GODMODE))
return FALSE
toxloss = clamp((toxloss + (amount * CONFIG_GET(number/damage_multiplier))), 0, maxHealth * 2)
@@ -189,6 +197,8 @@
return amount
/mob/living/proc/setToxLoss(amount, updating_health = TRUE, forced = FALSE)
+ if(amount < 0 && HAS_TRAIT(src, TRAIT_NO_HEAL))
+ return FALSE
if(!forced && (status_flags & GODMODE))
return FALSE
toxloss = amount
@@ -200,6 +210,8 @@
return fireloss
/mob/living/proc/adjustFireLoss(amount, updating_health = TRUE, forced = FALSE)
+ if(amount < 0 && HAS_TRAIT(src, TRAIT_NO_HEAL))
+ return FALSE
if(!forced && (status_flags & GODMODE))
return FALSE
fireloss = clamp((fireloss + (amount * CONFIG_GET(number/damage_multiplier))), 0, maxHealth * 2)
@@ -211,6 +223,8 @@
return cloneloss
/mob/living/proc/adjustCloneLoss(amount, updating_health = TRUE, forced = FALSE)
+ if(amount < 0 && HAS_TRAIT(src, TRAIT_NO_HEAL))
+ return FALSE
if(!forced && (status_flags & GODMODE))
return FALSE
cloneloss = clamp((cloneloss + (amount * CONFIG_GET(number/damage_multiplier))), 0, maxHealth * 2)
@@ -219,6 +233,8 @@
return amount
/mob/living/proc/setCloneLoss(amount, updating_health = TRUE, forced = FALSE)
+ if(amount < 0 && HAS_TRAIT(src, TRAIT_NO_HEAL))
+ return FALSE
if(!forced && (status_flags & GODMODE))
return FALSE
cloneloss = amount
diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm
index 69680ca1a816..c028031d5958 100644
--- a/code/modules/mob/living/living_defense.dm
+++ b/code/modules/mob/living/living_defense.dm
@@ -49,6 +49,8 @@
return FALSE
/mob/living/proc/on_hit(obj/item/projectile/P)
+ if(HAS_TRAIT(src, TRAIT_DODGE))
+ return BULLET_ACT_FORCE_PIERCE
return BULLET_ACT_HIT
/mob/living/bullet_act(obj/item/projectile/P, def_zone)
diff --git a/code/modules/mob/living/simple_animal/hostile/headcrab.dm b/code/modules/mob/living/simple_animal/hostile/headcrab.dm
index fe1bd9d846cd..9c31d1f299be 100644
--- a/code/modules/mob/living/simple_animal/hostile/headcrab.dm
+++ b/code/modules/mob/living/simple_animal/hostile/headcrab.dm
@@ -1,5 +1,3 @@
-#define EGG_INCUBATION_TIME 120
-
/mob/living/simple_animal/hostile/headcrab
name = "headslug"
desc = "Absolutely not de-beaked or harmless. Keep away from corpses."
@@ -21,22 +19,20 @@
environment_smash = ENVIRONMENT_SMASH_NONE
speak_emote = list("squeaks")
ventcrawler = VENTCRAWLER_ALWAYS
- var/datum/mind/origin
- var/egg_lain = 0
+ var/datum/mind/stored_mind = null //mind that is stored on creation
+ var/egg_lain = FALSE
+ var/obj/item/organ/body_egg/antag_egg/egg = /obj/item/organ/body_egg/antag_egg/changeling_egg //what eggo to use
gold_core_spawnable = NO_SPAWN //yogs
/mob/living/simple_animal/hostile/headcrab/proc/Infect(mob/living/carbon/victim)
- var/obj/item/organ/body_egg/changeling_egg/egg = new(victim)
+ egg = new egg(victim)
egg.Insert(victim)
- if(origin)
- egg.origin = origin
- else if(mind) // Let's make this a feature
- egg.origin = mind
- for(var/obj/item/organ/I in src)
- I.forceMove(egg)
+ for(var/obj/item/organ/head_organs in src)
+ head_organs.forceMove(egg)
+ egg.fetus_mind = stored_mind ? stored_mind : mind //prioritize the stored one
visible_message(span_warning("[src] plants something in [victim]'s flesh!"), \
span_danger("We inject our egg into [victim]'s body!"))
- egg_lain = 1
+ egg_lain = TRUE
/mob/living/simple_animal/hostile/headcrab/AttackingTarget()
. = ..()
@@ -49,41 +45,47 @@
return
Infect(target)
to_chat(src, span_userdanger("With our egg laid, our death approaches rapidly..."))
- addtimer(CALLBACK(src, .proc/death), 100)
+ addtimer(CALLBACK(src, .proc/death), 10 SECONDS)
-/obj/item/organ/body_egg/changeling_egg
- name = "changeling egg"
- desc = "Twitching and disgusting."
- var/datum/mind/origin
+/obj/item/organ/body_egg/antag_egg
+ name = "le antag egg"
+ desc = "Greentexting and redtexting."
+ var/datum/mind/fetus_mind
var/time
+ var/incubation_time = 120 //this shit takes roughly 5 minutes
-/obj/item/organ/body_egg/changeling_egg/egg_process()
- // Changeling eggs grow in dead people
+/obj/item/organ/body_egg/antag_egg/egg_process()
+ // eggs grow in people
time++
- if(time >= EGG_INCUBATION_TIME)
+ if(time >= incubation_time)
Pop()
Remove(owner)
qdel(src)
-/obj/item/organ/body_egg/changeling_egg/proc/Pop()
+/obj/item/organ/body_egg/antag_egg/proc/Pop()
+ return
+
+/obj/item/organ/body_egg/antag_egg/changeling_egg
+ name = "changeling egg"
+ desc = "Twitching and disgusting."
+
+/obj/item/organ/body_egg/antag_egg/changeling_egg/Pop()
var/mob/living/carbon/monkey/M = new(owner)
owner.stomach_contents += M // Yogs -- Yogs vorecode
for(var/obj/item/organ/I in src)
I.Insert(M, 1)
- if(origin && (origin.current ? (origin.current.stat == DEAD) : origin.get_ghost()))
- origin.transfer_to(M)
- var/datum/antagonist/changeling/C = origin.has_antag_datum(/datum/antagonist/changeling)
+ if(fetus_mind && (fetus_mind.current ? (fetus_mind.current.stat == DEAD) : fetus_mind.get_ghost()))
+ fetus_mind.transfer_to(M)
+ var/datum/antagonist/changeling/C = fetus_mind.has_antag_datum(/datum/antagonist/changeling)
if(!C)
- C = origin.add_antag_datum(/datum/antagonist/changeling/xenobio)
+ C = fetus_mind.add_antag_datum(/datum/antagonist/changeling/xenobio)
if(C.can_absorb_dna(owner))
C.add_new_profile(owner)
var/datum/action/changeling/humanform/hf = new
C.purchasedpowers += hf
C.regain_powers()
- M.key = origin.key
+ M.key = fetus_mind.key
owner.gib()
-
-#undef EGG_INCUBATION_TIME
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index bcaec997b825..4b57e159a52b 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -1143,17 +1143,16 @@
///Update the mouse pointer of the attached client in this mob
/mob/proc/update_mouse_pointer()
- if (!client)
+ if(!client)
return
client.mouse_pointer_icon = initial(client.mouse_pointer_icon)
- if (ismecha(loc))
- var/obj/mecha/M = loc
- if(M.mouse_pointer)
- client.mouse_pointer_icon = M.mouse_pointer
- else if (istype(loc, /obj/vehicle/sealed))
+ if(istype(loc, /obj/vehicle/sealed))
var/obj/vehicle/sealed/E = loc
if(E.mouse_pointer)
client.mouse_pointer_icon = E.mouse_pointer
+ if(client.mouse_override_icon)
+ client.mouse_pointer_icon = client.mouse_override_icon
+
///This mob is abile to read books
diff --git a/code/modules/projectiles/guns/energy/laser.dm b/code/modules/projectiles/guns/energy/laser.dm
index 35eea472bb4d..94ca8ac62a21 100644
--- a/code/modules/projectiles/guns/energy/laser.dm
+++ b/code/modules/projectiles/guns/energy/laser.dm
@@ -2,7 +2,7 @@
name = "laser gun"
desc = "A basic energy-based laser gun that fires concentrated beams of light which pass through glass and thin metal."
icon_state = "laser"
- item_state = LASER
+ item_state = "laser"
w_class = WEIGHT_CLASS_NORMAL
materials = list(/datum/material/iron=2000)
ammo_type = list(/obj/item/ammo_casing/energy/lasergun)
@@ -83,7 +83,7 @@
name = "accelerator laser cannon"
desc = "An advanced laser cannon that does more damage the farther away the target is."
icon_state = "lasercannon"
- item_state = LASER
+ item_state = "laser"
w_class = WEIGHT_CLASS_BULKY
force = 10
flags_1 = CONDUCT_1
diff --git a/code/modules/surgery/organs/organ_internal.dm b/code/modules/surgery/organs/organ_internal.dm
index 27a42b7ed1b6..33307c2bce3c 100644
--- a/code/modules/surgery/organs/organ_internal.dm
+++ b/code/modules/surgery/organs/organ_internal.dm
@@ -33,7 +33,7 @@
///Do we effect the appearance of our mob. Used to save time in preference code
var/visual = TRUE
-/obj/item/organ/proc/Insert(mob/living/carbon/M, special = 0, drop_if_replaced = TRUE,special_zone = null)
+/obj/item/organ/proc/Insert(mob/living/carbon/M, special = FALSE, drop_if_replaced = TRUE,special_zone = null)
if(!iscarbon(M) || owner == M)
return
@@ -42,7 +42,7 @@
var/obj/item/organ/replaced = M.getorganslot(slot)
if(replaced && !special_zone)
- replaced.Remove(M, special = 1)
+ replaced.Remove(M, special = TRUE)
if(drop_if_replaced)
replaced.forceMove(get_turf(M))
else
diff --git a/code/modules/zombie/items.dm b/code/modules/zombie/items.dm
index 3f9303242006..4ec8fa277053 100644
--- a/code/modules/zombie/items.dm
+++ b/code/modules/zombie/items.dm
@@ -6,10 +6,10 @@
child-safe caps on bottles."
item_flags = ABSTRACT | DROPDEL
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
- icon = 'icons/effects/blood.dmi'
- icon_state = "bloodhand_left"
- var/icon_left = "bloodhand_left"
- var/icon_right = "bloodhand_right"
+ icon = 'icons/obj/zombie.dmi'
+ icon_state = "blood_hand_l"
+ var/icon_left = "blood_hand_l"
+ var/icon_right = "blood_hand_r"
hitsound = 'sound/hallucinations/growl1.ogg'
force = 21 // Just enough to break airlocks with melee attacks
sharpness = SHARP_EDGED
@@ -24,6 +24,9 @@
. = ..()
ADD_TRAIT(src, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT)
+/obj/item/zombie_hand/ComponentInitialize()
+ AddComponent(/datum/component/zombie_infection, scaled_infect_chance, infect_chance, inserted_organ)
+
/obj/item/zombie_hand/equipped(mob/user, slot)
. = ..()
//these are intentionally inverted
@@ -44,41 +47,24 @@
//only the finest brain surgery tool, doesn't even need to check for surgical
if(!force)
- playsound(loc, 'sound/weapons/tap.ogg', get_clamped_volume(), 1, -1)
+ playsound(loc, 'sound/weapons/tap.ogg', get_clamped_volume(), TRUE, -1)
else if(hitsound)
playsound(loc, hitsound, get_clamped_volume(), 1, -1)
M.lastattacker = user.real_name
M.lastattackerckey = user.ckey
-
if(force)
M.last_damage = name
-
user.do_attack_animation(M)
- var/notBlocked = M.attacked_by(src, user)
+ var/blocked = M.attacked_by(src, user)
log_combat(user, M, "attacked", src.name, "(INTENT: [uppertext(user.a_intent)]) (DAMTYPE: [uppertext(damtype)])")
add_fingerprint(user)
take_damage(rand(weapon_stats[DAMAGE_LOW], weapon_stats[DAMAGE_HIGH]), sound_effect = FALSE)
- if(!notBlocked)
- return
- else if(isliving(M))
- if(ishuman(M))
- var/mob/living/carbon/human/H = M
- var/obj/item/bodypart/L = H.get_bodypart(check_zone(user.zone_selected))
- if(H.health <= HEALTH_THRESHOLD_FULLCRIT || (L && L.status != BODYPART_ROBOTIC))//no more infecting via metal limbs unless they're in hard crit and probably going to die
- var/flesh_wound = ran_zone(user.zone_selected)
- if(scaled_infect_chance)
- var/scaled_infection = ((100 - H.getarmor(flesh_wound, MELEE))/100) ^ 1.5 //20 armor -> 71.5% chance of infection
- if(prob(infect_chance * scaled_infection)) //40 armor -> 46.5% chance
- try_to_zombie_infect(M, inserted_organ) //60 armor -> 25.3% chance
- else //80 armor -> 8.9% chance
- if(prob(infect_chance - H.getarmor(flesh_wound, MELEE)))
- try_to_zombie_infect(M, inserted_organ)
- else
- check_feast(M, user)
+ if(blocked)
+ return
/proc/try_to_zombie_infect(mob/living/carbon/human/target, organ)
CHECK_DNA_AND_SPECIES(target)
@@ -105,34 +91,349 @@
O.dismember()
return (BRUTELOSS)
-/obj/item/zombie_hand/proc/check_feast(mob/living/target, mob/living/user)
- if(target.stat == DEAD)
- var/hp_gained = target.maxHealth
- target.gib()
- // zero as argument for no instant health update
- user.adjustBruteLoss(-hp_gained, 0)
- user.adjustToxLoss(-hp_gained, 0)
- user.adjustFireLoss(-hp_gained, 0)
- user.adjustCloneLoss(-hp_gained, 0)
- user.updatehealth()
- user.adjustOrganLoss(ORGAN_SLOT_BRAIN, -hp_gained) // Zom Bee gibbers "BRAAAAISNSs!1!"
- user.set_nutrition(min(user.nutrition + hp_gained, NUTRITION_LEVEL_FULL))
-
/obj/item/zombie_hand/gamemode
+ desc = "A zombie's claw is its primary tool, capable of infecting \n\
+ humans, butchering all other living things to \n\
+ sustain the zombie, smashing open airlock doors and opening \n\
+ child-safe caps on bottles."
inserted_organ = /obj/item/organ/zombie_infection/gamemode
- infect_chance = 70
- scaled_infect_chance = TRUE
+ ///See code\modules\mob\living\carbon\human\species_types\zombies.dm
+ infect_chance = 0
force = 15
var/door_open_modifier = 1
+/obj/item/zombie_hand/gamemode/examine()
+ . = ..()
+ if(!isliving(loc))
+ return
+ var/mob/living/holder = loc
+ if(IS_SPECIALINFECTED(holder))
+ . += span_boldnotice("You can release spores into people with Alt-click, infecting them.")
+
/obj/item/zombie_hand/gamemode/runner
force = 10
- infect_chance = 35
- door_open_modifier = 1.1
+ door_open_modifier = 1.5
+
+/obj/item/zombie_hand/gamemode/runner/afterattack(atom/target, mob/user, proximity) //sharp bone and stuff
+ . = ..()
+ if(!proximity)
+ return
-/obj/item/zombie_hand/gamemode/tank
- door_open_modifier = 0.8
+ if(!isliving(target))
+ return
+
+ var/mob/living/victim = target
+ var/datum/status_effect/saw_bleed/bloodletting/bleed_stack = victim.has_status_effect(STATUS_EFFECT_BLOODLETTING)
+ if(!bleed_stack)
+ bleed_stack = victim.apply_status_effect(STATUS_EFFECT_BLOODLETTING)
+ else
+ bleed_stack.add_bleed(2) //5 times
/obj/item/zombie_hand/gamemode/necro
force = 7
infect_chance = 30
+
+/obj/item/zombie_hand/gamemode/tank // see /obj/item/melee/flesh_maul
+ name = "zombie maul"
+ desc = "A marvelous show of craftsmanship.\n\
+ Made from your host's other appendages, this monster of a claw is sturdy but also very able."
+ icon_state = "jugger_hand"
+ icon_left = "jugger_hand"
+ icon_right = "jugger_stub"
+ lefthand_file = 'icons/mob/inhands/antag/zombie_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/antag/zombie_righthand.dmi'
+ w_class = WEIGHT_CLASS_HUGE
+ tool_behaviour = TOOL_MINING
+ weapon_stats = list(SWING_SPEED = 2, ENCUMBRANCE = 1, ENCUMBRANCE_TIME = 20, REACH = 2, DAMAGE_LOW = 0, DAMAGE_HIGH = 0)
+ force = 35
+ armour_penetration = 10
+ wound_bonus = 40 //steroids
+ hitsound = "swing_hit"
+ attack_verb = list("smashed", "slammed", "crushed", "whacked")
+ sharpness = SHARP_NONE
+ var/is_stub = FALSE
+ var/defensive_mode = FALSE
+ door_open_modifier = 0.5
+
+/obj/item/zombie_hand/gamemode/tank/examine()
+ . = ..()
+ if(is_stub)
+ . = "A remnant of what once was a feeble arm remains. Though its sacrifice wasn't in vain."
+ return
+ . += span_bold("It is currently on [defensive_mode ? "defense, defending your upper body from damage, but also slowing you down." : "offense, reducing your protection but allowing more movement."]")
+
+/obj/item/zombie_hand/gamemode/tank/equipped(mob/user)
+ . = ..()
+ var/i = user.get_held_index_of_item(src)
+ if(!(i % 2))
+ return
+ var/obj/item/zombie_hand/gamemode/tank/stub = src
+ stub.name = "zombie stub"
+ stub.is_stub = TRUE
+ stub.force = 10
+ stub.armour_penetration = 60
+ stub.weapon_stats = list(SWING_SPEED = 1, ENCUMBRANCE = 0, ENCUMBRANCE_TIME = 0, REACH = 1, DAMAGE_LOW = 0, DAMAGE_HIGH = 0)
+ stub.w_class = WEIGHT_CLASS_SMALL
+ stub.tool_behaviour = NONE
+
+/obj/item/zombie_hand/gamemode/tank/afterattack(atom/target, mob/user, proximity)
+ . = ..()
+ if(!proximity)
+ return
+
+ if(is_stub)
+ return
+
+ if(iscarbon(target))
+ var/mob/living/carbon/C = target
+ C.add_movespeed_modifier("zombie maul", update = TRUE, priority = 101, multiplicative_slowdown = 1)
+ addtimer(CALLBACK(C, /mob.proc/remove_movespeed_modifier, "zombie maul"), 2 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE)
+ to_chat(target, span_danger("You are staggered from the blow!"))
+
+ if(iscyborg(target))
+ var/mob/living/silicon/robot/R = target
+ R.Paralyze(1 SECONDS)
+
+ if(isstructure(target) || ismachinery(target))
+ var/obj/structure/S = target
+ var/structure_damage = S.max_integrity
+ var/make_sound = TRUE
+ if(istype(target, /obj/structure/window) || istype(target, /obj/structure/grille))
+ structure_damage *= 2
+ make_sound = FALSE
+ if(ismachinery(target) || istype(target, /obj/structure/door_assembly))
+ structure_damage *= 0.5
+ if(istype(target, /obj/machinery/door/airlock))
+ structure_damage = 29
+ if(istype(target, /obj/structure/table))
+ var/obj/structure/table/T = target
+ T.deconstruct(FALSE)
+ return
+ if(!isnull(target))
+ S.take_damage(structure_damage, BRUTE, MELEE, FALSE)
+ if(make_sound)
+ playsound(src, 'sound/effects/bang.ogg', 50, TRUE)
+
+/obj/item/zombie_hand/gamemode/tank/attack_self(mob/user)
+ . = ..()
+ if(is_stub)
+ return
+
+ var/mob/living/carbon/human/zombie = user
+ var/datum/antagonist/zombie/zombie_owner = user.mind?.has_antag_datum(/datum/antagonist/zombie)
+ defensive_mode = !defensive_mode
+ if(defensive_mode)
+ var/damage_mod = 0.75
+ if(locate("strong_arm") in zombie_owner.zombie_abilities)
+ damage_mod = 0.6
+ zombie.physiology.brute_mod = damage_mod
+ zombie.physiology.burn_mod = damage_mod
+ zombie.add_movespeed_modifier("zombie defense", update = TRUE, priority = 101, multiplicative_slowdown = 1)
+ else
+ zombie.physiology.brute_mod = initial(zombie.physiology.brute_mod)
+ zombie.physiology.burn_mod = initial(zombie.physiology.burn_mod)
+ zombie.remove_movespeed_modifier("zombie defense")
+ update_icon()
+
+/obj/item/zombie_hand/gamemode/tank/update_icon()
+ if(is_stub)
+ return
+ icon_state = defensive_mode ? "jugger_hand_d" : initial(icon_state)
+
+/obj/effect/ebeam/smokertongue
+ name = "rotten tongue"
+ mouse_opacity = MOUSE_OPACITY_ICON
+ desc = "A gross, slimy tongue. It's sticky to the touch."
+
+/obj/effect/ebeam/smokertongue/stickier/Crossed(atom/movable/AM)
+ . = ..()
+ if(isliving(AM))
+ var/mob/living/L = AM
+ L.Paralyze(1 SECONDS)
+
+/obj/effect/ebeam/smokertongue/Crossed(atom/movable/AM)
+ . = ..()
+ if(isliving(AM))
+ var/mob/living/L = AM
+ if(isinfectedzombie(L))
+ return
+ L.Paralyze(1.5 SECONDS)
+ to_chat(L, span_warning("You get caught in the smoker's tongue!"))
+
+/obj/item/zombie_hand/gamemode/smoker
+ desc = "The smoker's claw has an unique ability to incapacitate targets, \
+ clicking on a far away mob will tongue them and bring them closer to you slowly. \
+ You can't damage someone until you fully incapacitate then, \
+ but you deal more damage and can quickly tear someone down."
+ force = 25
+ var/datum/beam/tongue = null
+ var/tongue_range = 6
+ var/obj/item/zombie_hand/gamemode/smoker/sister_hand = null
+
+/obj/item/zombie_hand/gamemode/smoker/Initialize()
+ . = ..()
+ for(var/obj/item/zombie_hand/gamemode/smoker/S in loc)
+ if(S != src)
+ sister_hand = S
+ S.sister_hand = src
+
+/obj/item/zombie_hand/gamemode/smoker/attack(mob/living/target, mob/living/user)
+ if(iscarbon(target))
+ var/mob/living/carbon/C = target
+ if(C.getStaminaLoss() >= 100)
+ damtype = BRUTE
+ else
+ damtype = STAMINA //choke them out, please
+ else
+ damtype = BRUTE
+ . = ..()
+
+/obj/item/zombie_hand/gamemode/smoker/afterattack(atom/target, mob/user, proximity)
+ if(proximity || tongue)
+ return ..()
+ if(iscarbon(target))
+ var/datum/antagonist/zombie/zombie_owner = user?.mind?.has_antag_datum(/datum/antagonist/zombie)
+ if(zombie_owner.zombie_mutations["big_tongue"])
+ tongue_range = initial(tongue_range) + 2
+ tongue = user.Beam(target, "smokertongue", time = INFINITY, maxdistance = tongue_range, beam_type = \
+ (zombie_owner.zombie_mutations["stickier_tongue"]) ? /obj/effect/ebeam/smokertongue/stickier : /obj/effect/ebeam/smokertongue)
+ sister_hand?.tongue = TRUE //only one tongue! //i messed up
+ RegisterSignal(user, COMSIG_LIVING_BIOLOGICAL_LIFE, .proc/pull_tongue_to, TRUE)
+
+/obj/item/zombie_hand/gamemode/smoker/proc/pull_tongue_to()
+ if(!tongue?.target && !tongue?.origin)
+ QDEL_NULL(tongue)
+ return
+ if(istype(tongue.target, /atom/movable))
+ var/atom/movable/AM = tongue.target
+ if(!AM.anchored)
+ step(AM, get_dir(AM, tongue.origin))
+ if(get_dist(tongue.origin, tongue.target) <= 1)
+ QDEL_NULL(tongue)
+
+/obj/item/zombie_hand/gamemode/spitter
+ desc = "A zombie's claw is its primary tool, capable of infecting \
+ humans, butchering all other living things to \
+ sustain the zombie, smashing open airlock doors and opening \
+ child-safe caps on bottles." ///// <-------------------------
+ icon_state = "spitter_hand_l_on"
+ icon_left = "spitter_hand_l_on"
+ icon_right = "spitter_hand_r_on"
+ COOLDOWN_DECLARE(acid_splatter_cooldown)
+ var/cooldown = 30 SECONDS
+
+/obj/item/zombie_hand/gamemode/spitter/examine()
+ . = ..()
+ . += COOLDOWN_FINISHED(src, acid_splatter_cooldown) ? span_notice("It is dripping with acid!") : span_warning("Requires [COOLDOWN_TIMELEFT(src, acid_splatter_cooldown) / 10] more seconds to refill.")
+
+/obj/item/zombie_hand/gamemode/spitter/afterattack(atom/target, mob/user, proximity)
+ . = ..()
+ if(!proximity)
+ return
+
+ if(!COOLDOWN_FINISHED(src, acid_splatter_cooldown))
+ return
+
+ if(isclosedturf(target) || isobj(target))
+ var/acid_ratio_to_use = 250
+ var/strength = 300
+ var/datum/antagonist/zombie/zombie_owner = user.mind?.has_antag_datum(/datum/antagonist/zombie)
+ if(zombie_owner.zombie_mutations["stronger_acid"])
+ acid_ratio_to_use = 400
+ strength = 450
+ if(!target.acid_act(acid_ratio_to_use, strength))
+ to_chat(user, span_warning("You can't dissolve [target]."))
+ return
+ user.visible_message(span_danger("[user] smears acid onto [target], causing it to melt!"))
+ COOLDOWN_START(src, acid_splatter_cooldown, cooldown)
+ addtimer(CALLBACK(src, .proc/on_cooldown_end, user), cooldown)
+ update_icon(user)
+
+/obj/item/zombie_hand/gamemode/spitter/update_icon(mob/user)
+ var/i = user.get_held_index_of_item(src)
+ if(!(i % 2))
+ icon_state = acid_splatter_cooldown <= world.time ? initial(icon_left) : "spitter_hand_l" // o
+ else // )
+ icon_state = acid_splatter_cooldown <= world.time ? initial(icon_right) : "spitter_hand_r" // o
+
+/obj/item/zombie_hand/gamemode/spitter/proc/on_cooldown_end(mob/user)
+ update_icon(user)
+ to_chat(user, span_notice("Acid drips down your fingertips, you are ready to melt something again!"))
+
+/obj/item/gun/brainy //where we go crazy
+ name = "pulsating mass"
+ item_flags = DROPDEL
+ resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
+ icon = 'icons/obj/zombie.dmi'
+ lefthand_file = 'icons/mob/inhands/antag/zombie_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/antag/zombie_righthand.dmi'
+ icon_state = "mass"
+ item_state = "mass"
+ hitsound = 'sound/hallucinations/growl1.ogg'
+ force = 21
+ sharpness = SHARP_EDGED
+ wound_bonus = -30
+ bare_wound_bonus = 15
+ damtype = BRUTE
+ pin = null
+ no_pin_required = TRUE
+ var/obj/item/gun/current_copy = null
+
+/obj/item/gun/brainy/Initialize()
+ . = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT)
+
+/obj/item/gun/brainy/ComponentInitialize()
+ AddComponent(/datum/component/zombie_infection, FALSE, 0, /obj/item/organ/zombie_infection/gamemode)
+
+/obj/item/gun/brainy/equipped(mob/user, slot)
+ . = ..()
+ var/i = user.get_held_index_of_item(src)
+ if(!(i % 2))
+ icon_state = initial(icon_state) + !(i % 2) ? "_l" : "_r"
+
+/obj/item/gun/brainy/proc/mimic(obj/item/gun/target)
+ if(!target)
+ reset()
+ return
+ current_copy = new target(src)
+ fire_sound = target.fire_sound
+ vary_fire_sound = target.vary_fire_sound
+ fire_sound_volume = target.fire_sound_volume
+ dry_fire_sound = target.dry_fire_sound
+ update_icon()
+
+/obj/item/gun/brainy/proc/reset()
+ QDEL_NULL(current_copy)
+ fire_sound = initial(fire_sound)
+ vary_fire_sound = initial(vary_fire_sound)
+ fire_sound_volume = initial(fire_sound_volume)
+ dry_fire_sound = initial(dry_fire_sound)
+ update_icon()
+
+/obj/item/gun/brainy/update_icon()
+ icon_state = initial(icon_state)
+ if(!current_copy)
+ return
+ if(istype(current_copy, /obj/item/gun/energy))
+ icon_state = initial(icon_state) + "_laser"
+ if(istype(current_copy, /obj/item/gun/ballistic))
+ icon_state = initial(icon_state) + "_ballistic"
+
+/obj/item/gun/brainy/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0)
+ if(!user.mind?.has_antag_datum(/datum/antagonist/zombie))
+ to_chat(user, span_warning("You have no idea how this works."))
+ // REMEMBER TO ADD RETURN
+ var/datum/antagonist/zombie/zombie_owner = user.mind.has_antag_datum(/datum/antagonist/zombie)
+ if(!zombie_owner.manage_infection(5))
+ to_chat(user, span_warning("You don't have enough points to fire this!"))
+ return
+ return current_copy?.process_fire(target, user)
+
+/obj/item/gun/brainy/process_chamber()
+ if(ismob(loc))
+ var/mob/M = loc
+ if(M?.mind?.has_antag_datum(/datum/antagonist/zombie))
+ var/datum/antagonist/zombie/zombie_owner = M.mind.has_antag_datum(/datum/antagonist/zombie)
+ zombie_owner?.infection -= 5
+ return current_copy?.process_chamber()
diff --git a/code/modules/zombie/organs.dm b/code/modules/zombie/organs.dm
index 4503c7538bc9..8cf7cfb0eba1 100644
--- a/code/modules/zombie/organs.dm
+++ b/code/modules/zombie/organs.dm
@@ -10,8 +10,8 @@
var/living_transformation_time = 30
var/converts_living = FALSE
- var/revive_time_min = 450
- var/revive_time_max = 700
+ var/revive_time_min = 45 SECONDS
+ var/revive_time_max = 70 SECONDS
var/timer_id
///damage dealt per second
@@ -64,7 +64,7 @@
return
if(!owner.getorgan(/obj/item/organ/brain))
return
- if(isipc(owner))
+ if(!IS_SPECIALINFECTED(owner) && isipc(owner))
return
if(!iszombie(owner))
to_chat(owner, "You can feel your heart stopping, but something isn't right... \
@@ -108,6 +108,10 @@
/obj/item/organ/zombie_infection/gamemode
damage_caused = 3
+/obj/item/organ/zombie_infection/gamemode/instant
+ revive_time_min = 2 SECONDS
+ revive_time_max = 4 SECONDS
+
/obj/item/organ/zombie_infection/gamemode/zombify()
timer_id = null
owner.grab_ghost()
@@ -135,16 +139,19 @@
owner.Stun(living_transformation_time)
- if(!isinfected(owner)) //Makes them the *actual* antag, instead of just a zombie.
+ if(!IS_INFECTED(owner)) //Makes them the *actual* antag, instead of just a zombie.
var/datum/game_mode/zombie/GM = SSticker.mode
if(!istype(GM))
return
GM.add_zombie(owner.mind)
- var/datum/antagonist/zombie/Z = locate() in owner.mind.antag_datums
- if(!Z.evolution.owner)
- Z.evolution.Grant(owner)
-
if(owner.handcuffed)
var/obj/O = owner.get_item_by_slot(SLOT_HANDCUFFED)
qdel(O)
+
+/obj/item/organ/zombie_infection/gamemode/special/zombify() //for special zombies
+ . = ..()
+ var/datum/antagonist/zombie/Z = owner?.mind?.has_antag_datum(/datum/antagonist/zombie)
+ if(!Z.class_chosen)
+ var/datum/action/innate/zombie/choose_class/evolve = new
+ evolve.Grant(owner)
diff --git a/icons/effects/beam.dmi b/icons/effects/beam.dmi
index fbe6182716b7..df2b3634c7b8 100644
Binary files a/icons/effects/beam.dmi and b/icons/effects/beam.dmi differ
diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi
index 9b01d956f32a..e53e3888e9e5 100644
Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ
diff --git a/icons/effects/mouse_pointers/vomit.dmi b/icons/effects/mouse_pointers/vomit.dmi
new file mode 100644
index 000000000000..62f2d12ee505
Binary files /dev/null and b/icons/effects/mouse_pointers/vomit.dmi differ
diff --git a/icons/effects/mouse_pointers/zombie_pounce.dmi b/icons/effects/mouse_pointers/zombie_pounce.dmi
new file mode 100644
index 000000000000..5f16e7e38f57
Binary files /dev/null and b/icons/effects/mouse_pointers/zombie_pounce.dmi differ
diff --git a/icons/effects/mouse_pointers/zombie_scorch.dmi b/icons/effects/mouse_pointers/zombie_scorch.dmi
new file mode 100644
index 000000000000..13c905e98082
Binary files /dev/null and b/icons/effects/mouse_pointers/zombie_scorch.dmi differ
diff --git a/icons/effects/mouse_pointers/zombie_spit.dmi b/icons/effects/mouse_pointers/zombie_spit.dmi
new file mode 100644
index 000000000000..41e7823e7e76
Binary files /dev/null and b/icons/effects/mouse_pointers/zombie_spit.dmi differ
diff --git a/icons/mob/actions/actions_horror.dmi b/icons/mob/actions/actions_horror.dmi
index 3d0286a0e5e9..4a761649e0eb 100644
Binary files a/icons/mob/actions/actions_horror.dmi and b/icons/mob/actions/actions_horror.dmi differ
diff --git a/icons/mob/actions/actions_zombie.dmi b/icons/mob/actions/actions_zombie.dmi
new file mode 100644
index 000000000000..cd5f30845afb
Binary files /dev/null and b/icons/mob/actions/actions_zombie.dmi differ
diff --git a/icons/mob/actions/backgrounds.dmi b/icons/mob/actions/backgrounds.dmi
index 39831fb86615..3e3929954a73 100644
Binary files a/icons/mob/actions/backgrounds.dmi and b/icons/mob/actions/backgrounds.dmi differ
diff --git a/icons/mob/animal.dmi b/icons/mob/animal.dmi
index 4b5ec0990bf2..ca1c59f487c3 100644
Binary files a/icons/mob/animal.dmi and b/icons/mob/animal.dmi differ
diff --git a/icons/mob/inhands/antag/zombie_lefthand.dmi b/icons/mob/inhands/antag/zombie_lefthand.dmi
new file mode 100644
index 000000000000..d1fba4436dc8
Binary files /dev/null and b/icons/mob/inhands/antag/zombie_lefthand.dmi differ
diff --git a/icons/mob/inhands/antag/zombie_righthand.dmi b/icons/mob/inhands/antag/zombie_righthand.dmi
new file mode 100644
index 000000000000..3b495076cab2
Binary files /dev/null and b/icons/mob/inhands/antag/zombie_righthand.dmi differ
diff --git a/icons/mob/zombie_base.dmi b/icons/mob/zombie_base.dmi
new file mode 100644
index 000000000000..16de5409ef83
Binary files /dev/null and b/icons/mob/zombie_base.dmi differ
diff --git a/icons/obj/zombie.dmi b/icons/obj/zombie.dmi
new file mode 100644
index 000000000000..88c534cd00d5
Binary files /dev/null and b/icons/obj/zombie.dmi differ
diff --git a/tgui/packages/tgui/index.js b/tgui/packages/tgui/index.js
index d96fc98552c6..2570a333b6ab 100644
--- a/tgui/packages/tgui/index.js
+++ b/tgui/packages/tgui/index.js
@@ -23,6 +23,7 @@ import './styles/themes/paper.scss';
import './styles/themes/retro.scss';
import './styles/themes/syndicate.scss';
import './styles/themes/admintickets.scss';
+import './styles/themes/zombie.scss';
import { perf } from 'common/perf';
import { setupHotReloading } from 'tgui-dev-server/link/client.cjs';
diff --git a/tgui/packages/tgui/interfaces/EvolutionMenu.js b/tgui/packages/tgui/interfaces/EvolutionMenu.js
new file mode 100644
index 000000000000..647be06a8dc9
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/EvolutionMenu.js
@@ -0,0 +1,148 @@
+import { useBackend, useSharedState } from '../backend';
+import { Button, LabeledList, Collapsible, Box, ProgressBar, Section, NoticeBox, Tabs, Flex } from '../components';
+import { Fragment } from 'inferno';
+import { Window } from '../layouts';
+
+export const EvolutionMenu = (props, context) => {
+ const [tab, setTab] = useSharedState(context, 'tab', 1);
+ return (
+
+
+
+ setTab(1)}>
+ Host Status
+
+ setTab(2)}>
+ Host Genetic Management
+
+ setTab(3)}>
+ Research Matrix
+
+
+ {tab === 1 && (
+
+ )}
+ {tab === 2 && (
+
+ )}
+ {tab === 3 && (
+
+ )}
+
+
+ );
+};
+
+const HostStatus = (props, context) => {
+ const { data } = useBackend(context);
+ const {
+ mutation_rate,
+ } = data;
+ return (
+
+ );
+};
+
+const GeneticManagement = (props, context) => {
+ const { act, data } = useBackend(context);
+ const {
+ mutations,
+ mutation_points,
+ } = data;
+ return (
+
+ Available Points: {mutation_points}
+
+ {mutations.map(mutation => (
+
+
+ {mutation.desc}
+ Cost to unlock: {mutation.mutation_cost}
+
+ ))}
+
+
+ );
+};
+
+const ResearchMatrix = (props, context) => {
+ const { data } = useBackend(context);
+ const {
+ info_abilities,
+ abilities,
+ } = data;
+ return (
+
+
+
+ {`As a frail piece of a former eldritch horror,
+ you cannot fully control your targets without damaging their brains nor suck their souls.
+ As a means to fully rebuild yourself you have decided to extract DNA from the cognizant beings resided on the station,
+ using your adaptative form to produce spores to help you on your harvest.
+ * Your objective as a zombie is to infect as many people as possible, using Alt-click to slowly infect an alive person, and rapidly infect a crit/sleep or dead one.
+ * In the top right you are able to use your communicate ability to talk with your fellow monsters to coordinate hatching spots and possible class combinations.
+ * After zombifying, you'll be equipped with the evolution button, which lets you choose beetween 5 classes, all focused on one type of playstyle.
+ * These classes grant you different passives, unlocks new abilities to mutate and perks to your claws that can be explained further by examining them.`}
+
+
+
+
+ {info_abilities}
+
+ {abilities.map(ability => (
+
+ {ability.desc}
+ {ability.cost}
+ {ability.constant_cost}
+
+ ))}
+
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/styles/themes/zombie.scss b/tgui/packages/tgui/styles/themes/zombie.scss
new file mode 100644
index 000000000000..18b29d6b72c5
--- /dev/null
+++ b/tgui/packages/tgui/styles/themes/zombie.scss
@@ -0,0 +1,59 @@
+/**
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
+ */
+
+@use 'sass:color';
+@use 'sass:meta';
+
+@use '../colors.scss' with (
+ $primary: #6b0000,
+ $fg-map-keys: (),
+ $bg-map-keys: (),
+);
+@use '../base.scss' with (
+ $color-bg: #005c14,
+ $color-bg-grad-spread: 6%,
+);
+
+.theme-zombie {
+ // Atomic classes
+ @include meta.load-css('../atomic/color.scss');
+
+ // Components
+ @include meta.load-css('../components/Button.scss', $with: (
+ 'color-default': colors.$primary,
+ 'color-disabled': #363636,
+ 'color-selected': #740909,
+ 'color-caution': #be6209,
+ 'color-danger': #9a9d00,
+ ));
+ @include meta.load-css('../components/Input.scss', $with: (
+ 'border-color': #151615,
+ ));
+ @include meta.load-css('../components/NoticeBox.scss', $with: (
+ 'background-color': #0c6101,
+ ));
+ @include meta.load-css('../components/NumberInput.scss', $with: (
+ 'border-color': #610000,
+ ));
+ @include meta.load-css('../components/ProgressBar.scss', $with: (
+ 'background-color': #aa0000
+ ));
+ @include meta.load-css('../components/Section.scss');
+ @include meta.load-css('../components/Tooltip.scss', $with: (
+ 'background-color': #960000,
+ ));
+
+ // Layouts
+ @include meta.load-css('../layouts/Layout.scss');
+ @include meta.load-css('../layouts/Window.scss');
+ @include meta.load-css('../layouts/TitleBar.scss', $with: (
+ 'background-color': #003605,
+ ));
+
+ .Layout__content {
+ background-image: none;
+ }
+}
+
\ No newline at end of file
diff --git a/yogstation.dme b/yogstation.dme
index 93c535d603cf..11f263e57903 100644
--- a/yogstation.dme
+++ b/yogstation.dme
@@ -127,6 +127,7 @@
#include "code\__DEFINES\wall_dents.dm"
#include "code\__DEFINES\wires.dm"
#include "code\__DEFINES\wounds.dm"
+#include "code\__DEFINES\zombies.dm"
#include "code\__DEFINES\dcs\flags.dm"
#include "code\__DEFINES\dcs\helpers.dm"
#include "code\__DEFINES\dcs\signals\signal_species.dm"
@@ -296,6 +297,7 @@
#include "code\_onclick\hud\screen_objects.dm"
#include "code\_onclick\hud\slime.dm"
#include "code\_onclick\hud\swarmer.dm"
+#include "code\_onclick\hud\zombie.dm"
#include "code\controllers\admin.dm"
#include "code\controllers\controller.dm"
#include "code\controllers\failsafe.dm"
@@ -522,6 +524,7 @@
#include "code\datums\components\waddling.dm"
#include "code\datums\components\wearertargeting.dm"
#include "code\datums\components\wet_floor.dm"
+#include "code\datums\components\zombie_infection.dm"
#include "code\datums\components\crafting\antag.dm"
#include "code\datums\components\crafting\crafting.dm"
#include "code\datums\components\crafting\guncrafting.dm"
@@ -789,7 +792,8 @@
#include "code\game\gamemodes\traitor\traitor.dm"
#include "code\game\gamemodes\wizard\raginmages.dm"
#include "code\game\gamemodes\wizard\wizard.dm"
-#include "code\game\gamemodes\zombie\zombie.dm"
+#include "code\game\gamemodes\zombie\zombie_conquer.dm"
+#include "code\game\gamemodes\zombie\zombie_hud.dm"
#include "code\game\golf\golf.dm"
#include "code\game\machinery\_machinery.dm"
#include "code\game\machinery\ai_slipper.dm"
@@ -1733,13 +1737,21 @@
#include "code\modules\antagonists\wizard\equipment\soulstone.dm"
#include "code\modules\antagonists\wizard\equipment\spellbook.dm"
#include "code\modules\antagonists\xeno\xeno.dm"
-#include "code\modules\antagonists\zombie\zombie.dm"
-#include "code\modules\antagonists\zombie\abilities\acid.dm"
-#include "code\modules\antagonists\zombie\abilities\adrenaline.dm"
+#include "code\modules\antagonists\zombie\objectives.dm"
+#include "code\modules\antagonists\zombie\zombie_conquer.dm"
+#include "code\modules\antagonists\zombie\abilities\_abilities.dm"
+#include "code\modules\antagonists\zombie\abilities\brainy.dm"
+#include "code\modules\antagonists\zombie\abilities\horror_worm.dm"
#include "code\modules\antagonists\zombie\abilities\necromance.dm"
-#include "code\modules\antagonists\zombie\abilities\spit.dm"
+#include "code\modules\antagonists\zombie\abilities\runner.dm"
+#include "code\modules\antagonists\zombie\abilities\smoker.dm"
+#include "code\modules\antagonists\zombie\abilities\spitter.dm"
#include "code\modules\antagonists\zombie\abilities\tank.dm"
-#include "code\modules\antagonists\zombie\abilities\uncuff.dm"
+#include "code\modules\antagonists\zombie\mutations\_mutations.dm"
+#include "code\modules\antagonists\zombie\mutations\runner.dm"
+#include "code\modules\antagonists\zombie\mutations\smoker.dm"
+#include "code\modules\antagonists\zombie\mutations\spitter.dm"
+#include "code\modules\antagonists\zombie\mutations\tank.dm"
#include "code\modules\assembly\assembly.dm"
#include "code\modules\assembly\bomb.dm"
#include "code\modules\assembly\doorcontrol.dm"
diff --git a/yogstation/icons/mob/mutant_bodyparts.dmi b/yogstation/icons/mob/mutant_bodyparts.dmi
index 24e542bbaad4..7269404dfed8 100644
Binary files a/yogstation/icons/mob/mutant_bodyparts.dmi and b/yogstation/icons/mob/mutant_bodyparts.dmi differ
diff --git a/yogstation/icons/mob/screen_alert.dmi b/yogstation/icons/mob/screen_alert.dmi
index e9b484b95c42..683177e0ca1c 100644
Binary files a/yogstation/icons/mob/screen_alert.dmi and b/yogstation/icons/mob/screen_alert.dmi differ