diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index 2e7a1894898..9d927697ceb 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -198,6 +198,7 @@ #define SPECIES_VAMPIRE "vampire" #define SPECIES_ZOMBIE "zombie" #define SPECIES_ZOMBIE_INFECTIOUS "memezombie" +#define SPECIES_ZOMBIE_INFECTIOUS_MINDLESS "mindless_memezombie" #define SPECIES_ZOMBIE_KROKODIL "krokodil_zombie" #define SPECIES_VOIDWALKER "voidwalker" diff --git a/code/datums/components/ghost_direct_control.dm b/code/datums/components/ghost_direct_control.dm index b43ad734974..79b2a3a3de8 100644 --- a/code/datums/components/ghost_direct_control.dm +++ b/code/datums/components/ghost_direct_control.dm @@ -12,6 +12,8 @@ var/datum/callback/after_assumed_control /// If we're currently awaiting the results of a ghost poll var/awaiting_ghosts = FALSE + /// Entry in the spawners menu, optional + var/joinable_mobs_title /datum/component/ghost_direct_control/Initialize( ban_type = ROLE_SENTIENCE, @@ -25,6 +27,7 @@ assumed_control_message = null, datum/callback/extra_control_checks, datum/callback/after_assumed_control, + joinable_mobs_title = null, ) . = ..() if (!isliving(parent)) @@ -34,9 +37,10 @@ src.assumed_control_message = assumed_control_message || "You are [parent]!" src.extra_control_checks = extra_control_checks src.after_assumed_control = after_assumed_control + src.joinable_mobs_title = joinable_mobs_title var/mob/mob_parent = parent - LAZYADD(GLOB.joinable_mobs[format_text("[initial(mob_parent.name)]")], mob_parent) + LAZYADDASSOCLIST(GLOB.joinable_mobs, joinable_mobs_title || format_text(initial(mob_parent.name)), mob_parent) if (poll_candidates) INVOKE_ASYNC(src, PROC_REF(request_ghost_control), poll_question, role_name || "[parent]", poll_length, poll_ignore_key, poll_announce_chosen, poll_chat_border_icon) @@ -56,10 +60,7 @@ after_assumed_control = null var/mob/mob_parent = parent - var/list/spawners = GLOB.joinable_mobs[format_text("[initial(mob_parent.name)]")] - LAZYREMOVE(spawners, mob_parent) - if(!LAZYLEN(spawners)) - GLOB.joinable_mobs -= format_text("[initial(mob_parent.name)]") + LAZYREMOVEASSOC(GLOB.joinable_mobs, joinable_mobs_title || format_text(initial(mob_parent.name)), mob_parent) return ..() /// Inform ghosts that they can possess this diff --git a/code/modules/clothing/neck/horrific_necktie.dm b/code/modules/clothing/neck/horrific_necktie.dm index bb96be364d7..81bb94f29f1 100644 --- a/code/modules/clothing/neck/horrific_necktie.dm +++ b/code/modules/clothing/neck/horrific_necktie.dm @@ -20,7 +20,7 @@ hears_us = null QDEL_LIST(possessed_souls) SSpoints_of_interest.remove_point_of_interest(src) - LAZYREMOVE(GLOB.joinable_mobs[format_text("[initial(name)]")], src) + LAZYREMOVEASSOC(GLOB.joinable_mobs, initial(name), src) return ..() /obj/item/clothing/neck/tie/disco/examine(mob/user) @@ -34,12 +34,12 @@ return if(user.client && (isnull(hears_us) || user == hears_us)) SSpoints_of_interest.make_point_of_interest(src) - LAZYADD(GLOB.joinable_mobs[format_text("[initial(name)]")], src) + LAZYADDASSOCLIST(GLOB.joinable_mobs, initial(name), src) /obj/item/clothing/neck/tie/disco/dropped(mob/living/user) if(!QDELETED(src)) SSpoints_of_interest.remove_point_of_interest(src) - LAZYREMOVE(GLOB.joinable_mobs[format_text("[initial(name)]")], src) + LAZYREMOVEASSOC(GLOB.joinable_mobs, initial(name), src) return ..() /obj/item/clothing/neck/tie/disco/attack_self(mob/living/user, modifiers) 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 55cec54346a..3053374e7d7 100644 --- a/code/modules/mob/living/carbon/human/species_types/zombies.dm +++ b/code/modules/mob/living/carbon/human/species_types/zombies.dm @@ -134,6 +134,10 @@ BODY_ZONE_R_LEG = /obj/item/bodypart/leg/right/zombie/infectious, ) + var/regen_time = 6 SECONDS + var/regen_amount = 0.5 + var/zombie_hand = /obj/item/mutant_hand/zombie + /datum/species/zombie/infectious/on_species_gain(mob/living/carbon/human/new_zombie, datum/species/old_species, pref_load, regenerate_icons) . = ..() new_zombie.set_combat_mode(TRUE) @@ -150,15 +154,15 @@ new_zombie.AddComponent( \ /datum/component/mutant_hands, \ - mutant_hand_path = /obj/item/mutant_hand/zombie, \ + mutant_hand_path = zombie_hand, \ ) new_zombie.AddComponent( \ /datum/component/regenerator, \ - regeneration_delay = 6 SECONDS, \ - brute_per_second = 0.5, \ - burn_per_second = 0.5, \ - tox_per_second = 0.5, \ - oxy_per_second = 0.25, \ + regeneration_delay = regen_time, \ + brute_per_second = regen_amount, \ + burn_per_second = regen_amount, \ + tox_per_second = regen_amount, \ + oxy_per_second = regen_amount * 0.5, \ heals_wounds = TRUE, \ ) @@ -179,6 +183,25 @@ if(!HAS_TRAIT(carbon_mob, TRAIT_CRITICAL_CONDITION) && SPT_PROB(2, seconds_per_tick)) playsound(carbon_mob, pick(spooks), 50, TRUE, 10) +// Weaker subtype - less healing, weaker attacks, etc +/datum/species/zombie/infectious/mindless + name = "Mindless Infectious Zombie" + id = SPECIES_ZOMBIE_INFECTIOUS_MINDLESS + regen_time = 10 SECONDS + regen_amount = 0.2 + zombie_hand = /obj/item/mutant_hand/zombie/weak + +/datum/species/zombie/infectious/mindless/on_species_gain(mob/living/carbon/human/new_zombie, datum/species/old_species, pref_load, regenerate_icons) + . = ..() + new_zombie.add_movespeed_modifier(/datum/movespeed_modifier/mindless_zombie) + +/datum/species/zombie/infectious/mindless/on_species_loss(mob/living/carbon/human/was_zombie, datum/species/new_species, pref_load) + . = ..() + was_zombie.remove_movespeed_modifier(/datum/movespeed_modifier/mindless_zombie) + +/datum/movespeed_modifier/mindless_zombie + multiplicative_slowdown = 0.75 + // Your skin falls off /datum/species/human/krokodil_addict name = "\improper Krokodil Human" diff --git a/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_zombie_infectious_mindless.png b/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_zombie_infectious_mindless.png new file mode 100644 index 00000000000..d1c3bd1a6d2 Binary files /dev/null and b/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_zombie_infectious_mindless.png differ diff --git a/code/modules/zombie/items.dm b/code/modules/zombie/items.dm index b47e334a65a..705257f63dd 100644 --- a/code/modules/zombie/items.dm +++ b/code/modules/zombie/items.dm @@ -10,17 +10,27 @@ wound_bonus = -30 exposed_wound_bonus = 15 sharpness = SHARP_EDGED + /// Chance of infection on hit + var/infect_chance = 100 /obj/item/mutant_hand/zombie/afterattack(atom/target, mob/user, list/modifiers, list/attack_modifiers) if(QDELETED(target)) return if(ishuman(target)) - try_to_zombie_infect(target, user, user.zone_selected) + try_to_zombie_infect(target, user, user.zone_selected, infect_chance) else if(isliving(target)) check_feast(target, user) -/proc/try_to_zombie_infect(mob/living/carbon/human/target, mob/living/user, def_zone = BODY_ZONE_CHEST) +/obj/item/mutant_hand/zombie/weak + force = 16 + demolition_mod = 1.33 + exposed_wound_bonus = 10 + infect_chance = 50 + +/proc/try_to_zombie_infect(mob/living/carbon/human/target, mob/living/user, def_zone = BODY_ZONE_CHEST, base_chance = 100) CHECK_DNA_AND_SPECIES(target) + if(!prob(base_chance)) + return // Can't zombify with no head if(!target.get_bodypart(BODY_ZONE_HEAD)) diff --git a/code/modules/zombie/organs.dm b/code/modules/zombie/organs.dm index 6f8f0f0a100..5ebe812a0ef 100644 --- a/code/modules/zombie/organs.dm +++ b/code/modules/zombie/organs.dm @@ -13,6 +13,8 @@ var/revive_time_max = 70 SECONDS var/timer_id + var/datum/weakref/ghost_poll + /obj/item/organ/zombie_infection/Initialize(mapload) . = ..() if(iscarbon(loc)) @@ -42,6 +44,7 @@ if(timer_id) deltimer(timer_id) UnregisterSignal(new_owner, COMSIG_LIVING_DEATH) + QDEL_NULL(ghost_poll) /obj/item/organ/zombie_infection/proc/organ_owner_died(mob/living/carbon/source, gibbed) SIGNAL_HANDLER @@ -84,7 +87,8 @@ if(!iszombie(owner)) old_species = owner.dna.species.type - target.set_species(/datum/species/zombie/infectious) + // mindless zombies are nerfed a bit to encourage infecting real players instead of random morgue bodies + target.set_species(isnull(target.mind) ? /datum/species/zombie/infectious/mindless : /datum/species/zombie/infectious) var/stand_up = (target.stat == DEAD) || (target.stat == UNCONSCIOUS) @@ -92,11 +96,43 @@ if(!target.heal_and_revive(0, span_danger("[target] suddenly convulses, as [target.p_they()][stand_up ? " stagger to [target.p_their()] feet and" : ""] gain a ravenous hunger in [target.p_their()] eyes!"))) return - to_chat(target, span_alien("You HUNGER!")) - to_chat(target, span_alertalien("You are now a zombie! Do not seek to be cured, do not help any non-zombies in any way, do not harm your zombie brethren and spread the disease by killing others. You are a creature of hunger and violence.")) - playsound(target, 'sound/effects/hallucinations/far_noise.ogg', 50, 1) - target.do_jitter_animation(living_transformation_time) - target.Stun(living_transformation_time) + zombie_welcome(target) + if(target.mind || target.client) + if(stand_up) + target.get_up() + return + + ghost_poll = WEAKREF(target.AddComponent( \ + /datum/component/ghost_direct_control, \ + ban_type = NONE, \ + poll_ignore_key = POLL_IGNORE_RECOVERED_CREW, \ + poll_question = "Do you want to be a [span_green("[isnull(target.mind) ? "mindless zombie" : "zombie"]")]?", \ + role_name = "zombie", \ + extra_control_checks = CALLBACK(src, PROC_REF(check_ghost_control_eligibility)), \ + after_assumed_control = CALLBACK(src, PROC_REF(after_ghost_control)), \ + joinable_mobs_title = "Zombies", \ + )) + +/obj/item/organ/zombie_infection/proc/check_ghost_control_eligibility(mob/candidate) + if(QDELETED(src) || QDELETED(owner)) + return FALSE + if(!iszombie(owner) || owner.stat == DEAD) + return FALSE + return TRUE + +/obj/item/organ/zombie_infection/proc/after_ghost_control(mob/candidate) + owner.Knockdown(living_transformation_time) + zombie_welcome(owner) + ghost_poll = null + +/obj/item/organ/zombie_infection/proc/zombie_welcome(mob/living/carbon/new_zombie) + if(new_zombie.client) + to_chat(new_zombie, span_alien("You HUNGER!")) + to_chat(new_zombie, span_alertalien("You are now a zombie! Do not seek to be cured, do not help any non-zombies in any way, do not harm your zombie brethren and spread the disease by killing others. You are a creature of hunger and violence.")) + playsound(new_zombie, 'sound/effects/hallucinations/far_noise.ogg', 50, 1) + + new_zombie.do_jitter_animation(living_transformation_time) + new_zombie.Stun(living_transformation_time) /obj/item/organ/zombie_infection/nodamage causes_damage = FALSE