From bade006302329444f1c229d129fe035dbfca6b5e Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Wed, 15 Apr 2026 21:26:22 -0500 Subject: [PATCH] DNR'd / playerless bodies risen as zombies are ghost controllable (though mindless bodies are weaker) (#95544) ## About The Pull Request - A body that revives as a zombie but has no player (IE never had a player or the player DNR'd) becomes is ghost controllable: A poll is immediately offered, and the body itself appears in the spawners menu. - Zombies created from **mindless** mobs (such as monkeys or roundstart morgue cadavers) are **Mindless Zombies**, a subtype of zombie which is weaker (heal slower, move slower, not guaranteed to infect on hit) ## Why It's Good For The Game - When zombies get rolling, there will no doubt be a few players that DNR or a few DNR'd bodies that get infected, and it results in a lot of zombies without players standing around, which is kinda lame. Giving ghosts the options to take over seems like a no-brains-er to me, keeps the horde a bit active even if some get decapitated. - However, one big problem this allows for is that it provides a clear vector for people spamming zombies out of hu-monkeys. Thus I changed mindless bodies (bodies which never had a player, even a DNR'd one (hu-monkeys)) to be easier to handle and far less deadly. That way traitors are still encouraged to infect players over npcs. ## Changelog :cl: Melbert add: If a playerless body (DNR'd or otherwise) is raised as a zombie, ghosts are given the ability to take control of it add: If a body that never had a player control it is risen as a zombie, they are risen as a weaker "mindless zombie" (moves and heals slower, deals less damage, infection is not guaranteed on hit) /:cl: --- code/__DEFINES/mobs.dm | 1 + .../datums/components/ghost_direct_control.dm | 11 ++-- .../modules/clothing/neck/horrific_necktie.dm | 6 +-- .../carbon/human/species_types/zombies.dm | 35 ++++++++++--- ...tum_species_zombie_infectious_mindless.png | Bin 0 -> 1072 bytes code/modules/zombie/items.dm | 14 ++++- code/modules/zombie/organs.dm | 48 +++++++++++++++--- 7 files changed, 93 insertions(+), 22 deletions(-) create mode 100644 code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_zombie_infectious_mindless.png 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 0000000000000000000000000000000000000000..d1c3bd1a6d215120a8f7fa4fe6c54fd312b43d5c GIT binary patch literal 1072 zcmV-01kd}4P)G5|&Z09yb6hX4R#0001B2}A$@d;kEHzqbGX|C0a!iHV7@u&^5& z8%O{E8X6ikI6;n&k{TKzR$61Tv$Jz{e|LL>QBPZJ001K+BM}r7A|@sn92}>rw$#_( ziHo8mBq=Q|Gt140004WQchCV=-0C=2JR&a84_w-Y6@%7{?OD!tS%+FJ>RWQ*r;NmRLOex6#a*U0* zI5Sc+(=$pSoZ^zil2jm5Nr{UyC9|j)q@Ig2ttc@!6~s2=QdV&Fa{-$O0Gj_84mqJ= zL;wH+=1D|BR9J=Wmg`d6Fc5|1i-b{1Ag(1_ssIOSLK5Ep`Fhq)879&y9r~xkR5K(F zXZQ0)k@u_+Uu_j-@mG3P%euyKZW(%2H~rm6A6c z;7+Qhsg>kzF$1)gcZ|LftyJH&Y}pc9B0%4;gifz?$L0&@8akIRjI{``)-ZZ+4K3Sq z`Gav@dx!UQ{$PI4GDDi+?ckjC*15s@q;7@*`NLos-2vc`kM~3b^d1B()2DRdogEMq zAP&|g0M44;^gaFzqX!7iqX-k?h*=+c6o9Qr=%~;ee*(}|1i*imZ-DP0VqF9u#5-XA z6zTYNmVA^D>7u%qZ8!k}PTcu%F14g@KkuRG<$d(T6-vwCtqs=tAXlr02mk_h(9k|g zt)d66l(Kysw@=SMx8q|gqX%l%w_9Xyw<#SFrJxENRX`sY-EulD+C=M;r{s@x`mH_g zk~k&50kfV31d%!5LIAifTaSm0F+>7!1*SS`BT5NmjITchIo=A z^Dp(PC4gq~H32%U%7SKua~0cy8KA>SvK&B>FJhjqs(24)1cvEu9LFcc0N$RT#<6?5 z1|S^}y6%E~liHRA=PYDfx%?9$9