diff --git a/code/datums/memory/_memory.dm b/code/datums/memory/_memory.dm index f8b4a705950..b465e6ef85a 100644 --- a/code/datums/memory/_memory.dm +++ b/code/datums/memory/_memory.dm @@ -255,7 +255,9 @@ /mob/living/basic/cow/wisdom, /mob/living/basic/giant_spider, /mob/living/basic/giant_spider/hunter, + /mob/living/basic/headslug, /mob/living/basic/killer_tomato, + /mob/living/basic/lizard, /mob/living/basic/mouse, /mob/living/basic/pet/dog/breaddog, /mob/living/basic/pet/dog/corgi, @@ -269,8 +271,6 @@ /mob/living/simple_animal/hostile/bear, /mob/living/simple_animal/hostile/blob/blobbernaut/independent, /mob/living/simple_animal/hostile/gorilla, - /mob/living/simple_animal/hostile/headcrab, - /mob/living/basic/lizard, /mob/living/simple_animal/hostile/megafauna/dragon/lesser, /mob/living/simple_animal/hostile/morph, /mob/living/simple_animal/hostile/mushroom, diff --git a/code/modules/antagonists/changeling/headslug_eggs.dm b/code/modules/antagonists/changeling/headslug_eggs.dm new file mode 100644 index 00000000000..08733a7e6af --- /dev/null +++ b/code/modules/antagonists/changeling/headslug_eggs.dm @@ -0,0 +1,43 @@ +#define EGG_INCUBATION_TIME (4 MINUTES) + +/// The actual organ that the changeling headslug implants into a dead host. +/obj/item/organ/internal/body_egg/changeling_egg + name = "changeling egg" + desc = "Twitching and disgusting." + /// The mind of the original changeling that gave forth to the headslug mob. + var/datum/mind/origin + /// Tracks how long the egg has been growing. + var/time = 0 + +/obj/item/organ/internal/body_egg/changeling_egg/egg_process(seconds_per_tick, times_fired) + // Changeling eggs grow in dead people + time += seconds_per_tick * 10 + if(time >= EGG_INCUBATION_TIME) + pop() + +/// Once the egg is fully grown, we gib the host and spawn a monkey (with the changeling's player controlling it). Very descriptive proc name. +/obj/item/organ/internal/body_egg/changeling_egg/proc/pop() + var/mob/living/carbon/human/spawned_monkey = new(owner) + spawned_monkey.set_species(/datum/species/monkey) + + for(var/obj/item/organ/insertable in src) + insertable.Insert(spawned_monkey, 1) + + if(origin && (origin.current ? (origin.current.stat == DEAD) : origin.get_ghost())) + origin.transfer_to(spawned_monkey) + spawned_monkey.key = origin.key + var/datum/antagonist/changeling/changeling_datum = origin.has_antag_datum(/datum/antagonist/changeling) + if(!changeling_datum) + changeling_datum = origin.add_antag_datum(/datum/antagonist/changeling/headslug) + if(changeling_datum.can_absorb_dna(owner)) + changeling_datum.add_new_profile(owner) + + var/datum/action/changeling/lesserform/transform = new() + changeling_datum.purchased_powers[transform.type] = transform + changeling_datum.regain_powers() + + owner.investigate_log("has been gibbed by a changeling egg burst.", INVESTIGATE_DEATHS) + owner.gib() + qdel(src) + +#undef EGG_INCUBATION_TIME diff --git a/code/modules/antagonists/changeling/powers/headcrab.dm b/code/modules/antagonists/changeling/powers/headcrab.dm index 45ab93e69f9..d881111c8ee 100644 --- a/code/modules/antagonists/changeling/powers/headcrab.dm +++ b/code/modules/antagonists/changeling/powers/headcrab.dm @@ -40,13 +40,10 @@ addtimer(CALLBACK(src, PROC_REF(spawn_headcrab), stored_mind, user_turf, organs), 3 SECONDS) /datum/action/changeling/headcrab/proc/spawn_headcrab(datum/mind/stored_mind, turf/spawn_location, list/organs) - var/mob/living/simple_animal/hostile/headcrab/crab = new(spawn_location) + var/mob/living/basic/headslug/crab = new(spawn_location) for(var/obj/item/organ/I in organs) I.forceMove(crab) - crab.origin = stored_mind - if(!crab.origin) - return - crab.origin.active = TRUE - crab.origin.transfer_to(crab) + + stored_mind.transfer_to(crab) spawn_location.transfer_observers_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/clothing/shoes/cowboy.dm b/code/modules/clothing/shoes/cowboy.dm index c0ea9898692..e6f02264d35 100644 --- a/code/modules/clothing/shoes/cowboy.dm +++ b/code/modules/clothing/shoes/cowboy.dm @@ -56,7 +56,7 @@ if(contents.len >= max_occupants) to_chat(user, span_warning("[src] are full!")) return - if(istype(target, /mob/living/simple_animal/hostile/retaliate/snake) || istype(target, /mob/living/simple_animal/hostile/headcrab) || islarva(target)) + if(istype(target, /mob/living/simple_animal/hostile/retaliate/snake) || istype(target, /mob/living/basic/headslug) || islarva(target)) target.forceMove(src) to_chat(user, span_notice("[target] slithers into [src].")) diff --git a/code/modules/mob/living/basic/space_fauna/headslug.dm b/code/modules/mob/living/basic/space_fauna/headslug.dm new file mode 100644 index 00000000000..faa934e3314 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/headslug.dm @@ -0,0 +1,92 @@ +/** + * ## Headslugs + * + * Player-controlled slugs that arise from a changeling ability in order to live on in an extremely limited capacity until they can find a suitable corpse to inhabit. + */ +/mob/living/basic/headslug + name = "headslug" + desc = "A small, slug-like creature with a large, gaping maw. It's covered in a thick, slimy mucus." + icon_state = "headslug" + icon_living = "headslug" + icon_dead = "headslug_dead" + gender = NEUTER + health = 50 + maxHealth = 50 + melee_damage_lower = 5 + melee_damage_upper = 5 + attack_verb_continuous = "chomps" + attack_verb_simple = "chomp" + attack_sound = 'sound/weapons/bite.ogg' + attack_vis_effect = ATTACK_EFFECT_BITE + faction = list(FACTION_CREATURE) + obj_damage = 0 + environment_smash = ENVIRONMENT_SMASH_NONE + speak_emote = list("squeaks") + + ai_controller = /datum/ai_controller/basic_controller/headslug + + /// Set to true once we've implanted our egg + var/egg_lain = FALSE + +/mob/living/basic/headslug/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) + RegisterSignal(src, COMSIG_HOSTILE_POST_ATTACKINGTARGET, PROC_REF(check_and_implant)) + +/mob/living/basic/headslug/Destroy() + UnregisterSignal(src, COMSIG_HOSTILE_POST_ATTACKINGTARGET) + return ..() + +/mob/living/basic/headslug/examine(mob/user) + . = ..() + if(isnull(client)) + . += span_notice("It appears to be moving around listlessly.") + else + . += span_warning("It's moving around intelligently!") + +/// Signal Handler proc that runs on every attack and checks to see if this is a valid target for implantation. If so, it implants the egg and starts the countdown to death. +/mob/living/basic/headslug/proc/check_and_implant(mob/living/basic/attacker, atom/target) + SIGNAL_HANDLER + + if (egg_lain || !iscarbon(target) || ismonkey(target)) + return + + var/mob/living/carbon/victim = target + if(victim.stat == DEAD) + return + if(HAS_TRAIT(victim, TRAIT_XENO_HOST)) + target.balloon_alert(src, "already pregnant!") // Maybe the worst balloon alert in the codebase + return + + if(!infect(victim)) + target.balloon_alert(src, "failed to implant egg!") + stack_trace("[key] in [src] failed to implant egg in [victim], despite all checks suggesting it should have worked!") + return + + egg_lain = TRUE + to_chat(src, span_userdanger("With our egg laid, our death approaches rapidly...")) + addtimer(CALLBACK(src, PROC_REF(death)), 10 SECONDS) + +/// Simply infects the target corpse with our changeling eggs. This shouldn't fail, because all checks should have been done in check_and_implant() +/// Just to be super-duper safe to the player, we do return TRUE if all goes well and read that value in check_and_implant() to be nice to the player. +/mob/living/basic/headslug/proc/infect(mob/living/carbon/victim) + var/obj/item/organ/internal/body_egg/changeling_egg/egg = new(victim) + egg.Insert(victim) + + egg.origin = mind + + for(var/obj/item/organ/target in src) + target.forceMove(egg) + + visible_message( + span_warning("[src] plants something in [victim]'s flesh!"), + span_danger("We inject our egg into [victim]'s body!"), + ) + + return TRUE + +/// This is a bit neutered since these aren't intended to exist outside of player control, but it's a bit weird to just have these guys be completely stationary. +/// No attacking or anything like that, though. Just something so they seem alive. +/datum/ai_controller/basic_controller/headslug + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 57a04a3aff5..2421bbbba6e 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -1445,6 +1445,7 @@ /mob/living/basic/cow, /mob/living/basic/giant_spider, /mob/living/basic/giant_spider/hunter, + /mob/living/basic/headslug, /mob/living/basic/killer_tomato, /mob/living/basic/lizard, /mob/living/basic/mouse, @@ -1460,7 +1461,6 @@ /mob/living/simple_animal/hostile/bear, /mob/living/simple_animal/hostile/blob/blobbernaut/independent, /mob/living/simple_animal/hostile/gorilla, - /mob/living/simple_animal/hostile/headcrab, /mob/living/simple_animal/hostile/megafauna/dragon/lesser, /mob/living/simple_animal/hostile/morph, /mob/living/simple_animal/hostile/mushroom, diff --git a/code/modules/mob/living/simple_animal/hostile/headcrab.dm b/code/modules/mob/living/simple_animal/hostile/headcrab.dm deleted file mode 100644 index fcf02fd1293..00000000000 --- a/code/modules/mob/living/simple_animal/hostile/headcrab.dm +++ /dev/null @@ -1,113 +0,0 @@ -#define EGG_INCUBATION_TIME (4 MINUTES) - -/mob/living/simple_animal/hostile/headcrab - name = "headslug" - desc = "Absolutely not de-beaked or harmless. Keep away from corpses." - icon_state = "headcrab" - icon_living = "headcrab" - icon_dead = "headcrab_dead" - gender = NEUTER - health = 50 - maxHealth = 50 - melee_damage_lower = 5 - melee_damage_upper = 5 - attack_verb_continuous = "chomps" - attack_verb_simple = "chomp" - attack_sound = 'sound/weapons/bite.ogg' - attack_vis_effect = ATTACK_EFFECT_BITE - faction = list(FACTION_CREATURE) - robust_searching = 1 - stat_attack = DEAD - obj_damage = 0 - environment_smash = ENVIRONMENT_SMASH_NONE - speak_emote = list("squeaks") - /// The mind to transfer to our egg when it hatches - var/datum/mind/origin - /// Set to true once we've implanted our egg - var/egg_lain = FALSE - -/mob/living/simple_animal/hostile/headcrab/Initialize(mapload) - . = ..() - ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) - -/mob/living/simple_animal/hostile/headcrab/proc/Infect(mob/living/carbon/victim) - var/obj/item/organ/internal/body_egg/changeling_egg/egg = new(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) - visible_message(span_warning("[src] plants something in [victim]'s flesh!"), \ - span_danger("We inject our egg into [victim]'s body!")) - egg_lain = TRUE - -/mob/living/simple_animal/hostile/headcrab/AttackingTarget() - . = ..() - if (!. || egg_lain || !iscarbon(target) || ismonkey(target)) - return - var/mob/living/carbon/victim = target - if(victim.stat != DEAD) - return - if(HAS_TRAIT(victim, TRAIT_XENO_HOST)) - target.balloon_alert(src, "already pregnant!") // Maybe the worst balloon alert in the codebase - return - Infect(target) - to_chat(src, span_userdanger("With our egg laid, our death approaches rapidly...")) - addtimer(CALLBACK(src, PROC_REF(death)), 10 SECONDS) - -/obj/item/organ/internal/body_egg/changeling_egg - name = "changeling egg" - desc = "Twitching and disgusting." - var/datum/mind/origin - var/time = 0 - -/obj/item/organ/internal/body_egg/changeling_egg/egg_process(seconds_per_tick, times_fired) - // Changeling eggs grow in dead people - time += seconds_per_tick * 10 - if(time >= EGG_INCUBATION_TIME) - Pop() - Remove(owner) - qdel(src) - -/obj/item/organ/internal/body_egg/changeling_egg/proc/Pop() - // SKYRAT EDIT START - var/mob/living/carbon/human/species/monkey/spawned_monkey = new(owner) - var/datum/dna/current_dna = spawned_monkey.dna - for(var/key in current_dna.mutant_bodyparts) - LAZYSET(current_dna.mutant_bodyparts, key, "None") - for(var/key in current_dna.body_markings) - LAZYSET(current_dna.body_markings, key, null) - if(current_dna.features["body_size"]) - LAZYSET(current_dna.features, "body_size", 1) - if(current_dna.features["legs"]) - LAZYREMOVE(current_dna.features, "legs") - spawned_monkey.set_species(/datum/species/monkey) - for(var/scar in spawned_monkey.all_scars) - var/datum/scar/iter_scar = scar - if(iter_scar.fake) - qdel(iter_scar) - spawned_monkey.regenerate_icons() - // SKYRAT EDIT END - - for(var/obj/item/organ/I in src) - I.Insert(spawned_monkey, 1) - - if(origin && (origin.current ? (origin.current.stat == DEAD) : origin.get_ghost())) - origin.transfer_to(spawned_monkey) - spawned_monkey.key = origin.key - var/datum/antagonist/changeling/changeling_datum = origin.has_antag_datum(/datum/antagonist/changeling) - if(!changeling_datum) - changeling_datum = origin.add_antag_datum(/datum/antagonist/changeling/headslug) - if(changeling_datum.can_absorb_dna(owner)) - changeling_datum.add_new_profile(owner) - - var/datum/action/changeling/lesserform/transform = new() - changeling_datum.purchased_powers[transform.type] = transform - changeling_datum.regain_powers() - - owner.investigate_log("has been gibbed by a changeling egg burst.", INVESTIGATE_DEATHS) - owner.gib() - -#undef EGG_INCUBATION_TIME diff --git a/code/modules/mob_spawn/corpses/nonhuman_corpses.dm b/code/modules/mob_spawn/corpses/nonhuman_corpses.dm index 2c80a3abdc4..3b8877b3e8a 100644 --- a/code/modules/mob_spawn/corpses/nonhuman_corpses.dm +++ b/code/modules/mob_spawn/corpses/nonhuman_corpses.dm @@ -48,10 +48,10 @@ /// Dead headcrab for changeling-themed ruins /obj/effect/mob_spawn/corpse/headcrab - mob_type = /mob/living/simple_animal/hostile/headcrab + mob_type = /mob/living/basic/headslug icon = 'icons/mob/simple/animal.dmi' - icon_state = "headcrab_dead" + icon_state = "headslug_dead" -/obj/effect/mob_spawn/corpse/headcrab/special(mob/living/simple_animal/hostile/headcrab/crab) +/obj/effect/mob_spawn/corpse/headcrab/special(mob/living/basic/headslug/crab) . = ..() crab.egg_lain = TRUE // Prevents using mad science to become a changeling diff --git a/code/modules/unit_tests/simple_animal_freeze.dm b/code/modules/unit_tests/simple_animal_freeze.dm index 042057ff1b4..91b52e93bf3 100644 --- a/code/modules/unit_tests/simple_animal_freeze.dm +++ b/code/modules/unit_tests/simple_animal_freeze.dm @@ -156,7 +156,6 @@ /mob/living/simple_animal/hostile/guardian/ranged, /mob/living/simple_animal/hostile/guardian/standard, /mob/living/simple_animal/hostile/guardian/support, - /mob/living/simple_animal/hostile/headcrab, /mob/living/simple_animal/hostile/heretic_summon, /mob/living/simple_animal/hostile/heretic_summon/armsy, /mob/living/simple_animal/hostile/heretic_summon/armsy/prime, diff --git a/icons/mob/simple/animal.dmi b/icons/mob/simple/animal.dmi index edc6d9ccaf1..c234ee08441 100644 Binary files a/icons/mob/simple/animal.dmi and b/icons/mob/simple/animal.dmi differ diff --git a/tgstation.dme b/tgstation.dme index c0df8673148..894dea21058 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -2595,6 +2595,7 @@ #include "code\modules\antagonists\changeling\changeling.dm" #include "code\modules\antagonists\changeling\changeling_power.dm" #include "code\modules\antagonists\changeling\fallen_changeling.dm" +#include "code\modules\antagonists\changeling\headslug_eggs.dm" #include "code\modules\antagonists\changeling\powers\absorb.dm" #include "code\modules\antagonists\changeling\powers\adrenaline.dm" #include "code\modules\antagonists\changeling\powers\augmented_eyesight.dm" @@ -4052,6 +4053,7 @@ #include "code\modules\mob\living\basic\space_fauna\faithless.dm" #include "code\modules\mob\living\basic\space_fauna\garden_gnome.dm" #include "code\modules\mob\living\basic\space_fauna\ghost.dm" +#include "code\modules\mob\living\basic\space_fauna\headslug.dm" #include "code\modules\mob\living\basic\space_fauna\killer_tomato.dm" #include "code\modules\mob\living\basic\space_fauna\lightgeist.dm" #include "code\modules\mob\living\basic\space_fauna\carp\carp.dm" @@ -4297,7 +4299,6 @@ #include "code\modules\mob\living\simple_animal\hostile\blobspore.dm" #include "code\modules\mob\living\simple_animal\hostile\dark_wizard.dm" #include "code\modules\mob\living\simple_animal\hostile\eyeballs.dm" -#include "code\modules\mob\living\simple_animal\hostile\headcrab.dm" #include "code\modules\mob\living\simple_animal\hostile\heretic_monsters.dm" #include "code\modules\mob\living\simple_animal\hostile\hivebot.dm" #include "code\modules\mob\living\simple_animal\hostile\hostile.dm"