diff --git a/code/datums/components/weed_food.dm b/code/datums/components/weed_food.dm index ce6fe35e4a28..4c8d6112fde2 100644 --- a/code/datums/components/weed_food.dm +++ b/code/datums/components/weed_food.dm @@ -228,8 +228,6 @@ UnregisterSignal(parent_buckle, COMSIG_OBJ_AFTER_BUCKLE) parent_buckle = null - if(parent_mob.is_xeno_grabbable()) - return FALSE if(!(parent_mob.status_flags & PERMANENTLY_DEAD)) var/mob/living/carbon/human/parent_human = parent_mob if(istype(parent_human) && !parent_human.undefibbable) diff --git a/code/modules/cm_aliens/structures/special/egg_morpher.dm b/code/modules/cm_aliens/structures/special/egg_morpher.dm index 9a740133b1cc..abed439db582 100644 --- a/code/modules/cm_aliens/structures/special/egg_morpher.dm +++ b/code/modules/cm_aliens/structures/special/egg_morpher.dm @@ -3,24 +3,30 @@ //Eggmorpher - Basically a big reusable egg /obj/effect/alien/resin/special/eggmorph name = XENO_STRUCTURE_EGGMORPH - desc = "A disgusting, organic processor that reeks of rotting flesh. Capable of melting even bones into something far more useful." + desc = "A disgusting biomass generator that reeks of rotting flesh. Capable of producing facehuggers on its own." icon_state = "eggmorph" health = 300 - var/last_spawned = 0 - var/spawn_cooldown = 20 SECONDS + appearance_flags = KEEP_TOGETHER + layer = FACEHUGGER_LAYER + + ///How many huggers are stored in the egg morpher currently. var/stored_huggers = 0 - var/huggers_to_grow = 0 - var/huggers_per_corpse = 6 - var/huggers_to_grow_max = 12 + ///Max amount of huggers that can be stored in the egg morpoher. + var/huggers_max_amount = 12 + ///Max amount of huggers that can grow by itself. + var/huggers_to_grow_max = 6 + ///How many huggers are reserved from observers. var/huggers_reserved = 0 - var/mob/captured_mob + ///Datum used for mob detection. var/datum/shape/range_bounds + ///How long it takes to generate one facehugger. + var/spawn_cooldown_length = 120 SECONDS + COOLDOWN_DECLARE(spawn_cooldown) - appearance_flags = KEEP_TOGETHER - layer = FACEHUGGER_LAYER /obj/effect/alien/resin/special/eggmorph/Initialize(mapload, hive_ref) . = ..() + COOLDOWN_START(src, spawn_cooldown, spawn_cooldown_length) range_bounds = SQUARE(x, y, EGGMORPG_RANGE) /obj/effect/alien/resin/special/eggmorph/Destroy() @@ -34,127 +40,76 @@ F = new(loc, linked_hive.hivenumber) step_away(F,src,1) - vis_contents.Cut() - QDEL_NULL(captured_mob) range_bounds = null - . = ..() /obj/effect/alien/resin/special/eggmorph/get_examine_text(mob/user) . = ..() if(isxeno(user) || isobserver(user)) - . += "It has [stored_huggers] facehuggers within, with [huggers_to_grow] more to grow (reserved: [huggers_reserved])." - if(isxeno(user) || isobserver(user)) + . += SPAN_NOTICE("\nIt has [stored_huggers] facehuggers within, with [max(0, huggers_to_grow_max - stored_huggers)] more to grow and a total capacity of [huggers_max_amount] facehuggers (reserved: [huggers_reserved]).") + var/current_hugger_count = linked_hive.get_current_playable_facehugger_count(); - . += "There are currently [SPAN_NOTICE("[current_hugger_count]")] facehuggers in the hive. The hive can support a total of [SPAN_NOTICE("[linked_hive.playable_hugger_limit]")] facehuggers at present." - -/obj/effect/alien/resin/special/eggmorph/attackby(obj/item/I, mob/user) - if(istype(I, /obj/item/grab)) - if(!isxeno(user)) return - var/obj/item/grab/G = I - if(iscarbon(G.grabbed_thing)) - var/mob/living/carbon/M = G.grabbed_thing - if(M.buckled) - to_chat(user, SPAN_XENOWARNING("Unbuckle first!")) - return - if(ishuman(M)) - var/mob/living/carbon/human/H = M - if(H.is_revivable()) - to_chat(user, SPAN_XENOWARNING("This one is not suitable yet!")) - return - if(isxeno(M)) - return - if(M == captured_mob) - to_chat(user, SPAN_XENOWARNING("[src] is already digesting [M]!")) - return - if(huggers_to_grow + stored_huggers >= huggers_to_grow_max) - to_chat(user, SPAN_XENOWARNING("\The [src] is already full! Using this one now would be a waste...")) - return - if(!do_after(user, 10, INTERRUPT_ALL|BEHAVIOR_IMMOBILE, BUSY_ICON_GENERIC)) - return - visible_message(SPAN_DANGER("\The [src] churns as it begins digest \the [M], spitting out foul-smelling fumes!")) - playsound(src, "alien_drool", 25) - if(captured_mob) - //Get rid of what we have there, we're overwriting it - qdel(captured_mob) - captured_mob = M - captured_mob.setDir(SOUTH) - captured_mob.moveToNullspace() - var/matrix/MX = matrix() - captured_mob.apply_transform(MX) - captured_mob.pixel_x = 16 - captured_mob.pixel_y = 16 - vis_contents += captured_mob - user.stop_pulling() // Automatically remove the grab - huggers_to_grow += huggers_per_corpse - update_icon() + . += SPAN_NOTICE("There are currently [current_hugger_count] facehuggers in the hive. The hive can support a total of [linked_hive.playable_hugger_limit] facehuggers at present.") + if(stored_huggers < huggers_to_grow_max) + . += SPAN_NOTICE("It'll grow another facehugger in [COOLDOWN_SECONDSLEFT(src, spawn_cooldown)] seconds.") + if(isxeno(user)) + var/mob/living/carbon/xenomorph/xeno = user + if(xeno.caste_type == XENO_CASTE_CARRIER) + . += SPAN_NOTICE("Using our Retrieve Egg ability, we can easily transfer our eggs into [src].") + +/obj/effect/alien/resin/special/eggmorph/attackby(obj/item/item, mob/user) + if(!isxeno(user)) return - if(istype(I, /obj/item/clothing/mask/facehugger)) - var/obj/item/clothing/mask/facehugger/F = I - if(F.stat != DEAD) - if(stored_huggers >= huggers_to_grow_max) + + if(istype(item, /obj/item/clothing/mask/facehugger)) + var/obj/item/clothing/mask/facehugger/hugger = item + if(hugger.stat != DEAD) + if(stored_huggers >= huggers_max_amount) to_chat(user, SPAN_XENOWARNING("\The [src] is full of children.")) return if(user) - visible_message(SPAN_XENOWARNING("[user] slides [F] back into \the [src]."), \ + visible_message(SPAN_XENOWARNING("[user] slides [hugger] back into \the [src]."), \ SPAN_XENONOTICE("You place the child back into \the [src].")) - user.temp_drop_inv_item(F) + user.temp_drop_inv_item(hugger) else - visible_message(SPAN_XENOWARNING("[F] crawls back into \the [src]!")) - stored_huggers = min(huggers_to_grow_max, stored_huggers + 1) - qdel(F) + visible_message(SPAN_XENOWARNING("[hugger] crawls back into \the [src]!")) + stored_huggers = min(huggers_max_amount, stored_huggers + 1) + qdel(hugger) else to_chat(user, SPAN_XENOWARNING("This child is dead.")) return + //refill egg morpher from an egg - if(istype(I, /obj/item/xeno_egg)) - var/obj/item/xeno_egg/egg = I - if(stored_huggers >= huggers_to_grow_max) + if(istype(item, /obj/item/xeno_egg)) + var/obj/item/xeno_egg/egg = item + if(stored_huggers >= huggers_max_amount) to_chat(user, SPAN_XENOWARNING("\The [src] is full of children.")) return if(user) visible_message(SPAN_XENOWARNING("[user] slides a facehugger out of \the [egg] into \the [src]."), \ SPAN_XENONOTICE("You place the child from an egg into \the [src].")) user.temp_drop_inv_item(egg) - stored_huggers = min(huggers_to_grow_max, stored_huggers + 1) + stored_huggers = min(huggers_max_amount, stored_huggers + 1) playsound(src.loc, "sound/effects/alien_egg_move.ogg", 25) qdel(egg) return - return ..(I, user) + + return ..(item, user) /obj/effect/alien/resin/special/eggmorph/update_icon() ..() appearance_flags |= KEEP_TOGETHER overlays.Cut() underlays.Cut() - if(captured_mob) - var/image/J = new(icon = icon, icon_state = "[icon_state]", layer = captured_mob.layer + 0.1) - overlays += J - var/image/I = new(icon = icon, icon_state = "[icon_state]_overlay", layer = captured_mob.layer + 0.2) - overlays += I underlays += "[icon_state]_underlay" /obj/effect/alien/resin/special/eggmorph/process() check_facehugger_target() - if(!linked_hive || !captured_mob || world.time < (last_spawned + spawn_cooldown)) + if(!linked_hive || !COOLDOWN_FINISHED(src, spawn_cooldown) || stored_huggers == huggers_to_grow_max) return - last_spawned = world.time - if(huggers_to_grow > 0) - huggers_to_grow-- + COOLDOWN_START(src, spawn_cooldown, spawn_cooldown_length) + if(stored_huggers < huggers_to_grow_max) stored_huggers = min(huggers_to_grow_max, stored_huggers + 1) - if(huggers_to_grow <= 0) - visible_message(SPAN_DANGER("\The [src] groans as its contents are reduced to nothing!")) - vis_contents.Cut() - - for(var/atom/movable/A in captured_mob.contents_recursive()) // Get rid of any intel objects so we don't delete them - if(isitem(A)) - var/obj/item/item = A - if(item.is_objective && item.unacidable) - item.forceMove(get_step(loc, pick(GLOB.alldirs))) - item.mouse_opacity = initial(item.mouse_opacity) - - QDEL_NULL(captured_mob) - update_icon() /obj/effect/alien/resin/special/eggmorph/proc/check_facehugger_target() if(!range_bounds) @@ -191,6 +146,10 @@ if(!linked_hive || (M.hivenumber != linked_hive.hivenumber)) return ..(M) if(stored_huggers) + //this way another hugger doesn't immediately spawn after we pick one up + if(stored_huggers == huggers_to_grow_max) + COOLDOWN_START(src, spawn_cooldown, spawn_cooldown_length) + to_chat(M, SPAN_XENONOTICE("You retrieve a child.")) stored_huggers = max(0, stored_huggers - 1) var/obj/item/clothing/mask/facehugger/hugger = new(loc, linked_hive.hivenumber) @@ -228,7 +187,7 @@ to_chat(usr, SPAN_WARNING("This belongs to another Hive! Yuck!")) return - morpher.huggers_reserved = tgui_input_number(usr, "How many facehuggers would you like to keep safe from Observers wanting to join as facehuggers?", "How many to reserve?", 0, morpher.huggers_to_grow_max, morpher.huggers_reserved) + morpher.huggers_reserved = tgui_input_number(usr, "How many facehuggers would you like to keep safe from Observers wanting to join as facehuggers?", "How many to reserve?", 0, morpher.huggers_max_amount, morpher.huggers_reserved) to_chat(usr, SPAN_XENONOTICE("You reserved [morpher.huggers_reserved] facehuggers for your sisters.")) diff --git a/code/modules/mob/living/carbon/xenomorph/Facehuggers.dm b/code/modules/mob/living/carbon/xenomorph/Facehuggers.dm index 9a87f10d74a3..d497ce38f05c 100644 --- a/code/modules/mob/living/carbon/xenomorph/Facehuggers.dm +++ b/code/modules/mob/living/carbon/xenomorph/Facehuggers.dm @@ -408,7 +408,7 @@ qdel(src) return var/obj/effect/alien/resin/special/eggmorph/M = locate() in loc - if(istype(M) && M.stored_huggers < M.huggers_to_grow_max) + if(istype(M) && M.stored_huggers < M.huggers_max_amount) visible_message(SPAN_XENOWARNING("[src] crawls back into [M]!")) M.stored_huggers++ qdel(src) diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/general_powers.dm b/code/modules/mob/living/carbon/xenomorph/abilities/general_powers.dm index eb7ac8bde612..19b20fd370fc 100644 --- a/code/modules/mob/living/carbon/xenomorph/abilities/general_powers.dm +++ b/code/modules/mob/living/carbon/xenomorph/abilities/general_powers.dm @@ -654,6 +654,11 @@ if(!spacecheck(X, T, structure_template)) return FALSE + if((choice == XENO_STRUCTURE_EGGMORPH) && locate(/obj/structure/flora/grass/tallgrass) in T) + to_chat(X, SPAN_WARNING("The tallgrass is preventing us from building the egg morpher!")) + qdel(structure_template) + return FALSE + if(!do_after(X, XENO_STRUCTURE_BUILD_TIME, INTERRUPT_NO_NEEDHAND|BEHAVIOR_IMMOBILE, BUSY_ICON_BUILD)) return FALSE diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Carrier.dm b/code/modules/mob/living/carbon/xenomorph/castes/Carrier.dm index d46bfce6bf71..a2a1a29db2c2 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Carrier.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Carrier.dm @@ -215,7 +215,7 @@ if(huggers_max > 0 && huggers_cur < huggers_max) if(F.stat != DEAD && !F.sterile) huggers_cur++ - to_chat(src, SPAN_NOTICE("We store the facehugger and carry it for safekeeping. Now sheltering: [huggers_cur] / [huggers_max].")) + to_chat(src, SPAN_NOTICE("We take a facehugger and carry it for safekeeping. Now sheltering: [huggers_cur] / [huggers_max].")) update_icons() qdel(F) else @@ -237,9 +237,9 @@ huggers_cur += huggers_to_transfer morpher.stored_huggers -= huggers_to_transfer if(huggers_to_transfer == 1) - to_chat(src, SPAN_NOTICE("We store one facehugger and carry it for safekeeping. Now sheltering: [huggers_cur] / [huggers_max].")) + to_chat(src, SPAN_NOTICE("We take one facehugger and carry it for safekeeping. Now sheltering: [huggers_cur] / [huggers_max].")) else - to_chat(src, SPAN_NOTICE("We store [huggers_to_transfer] facehuggers and carry them for safekeeping. Now sheltering: [huggers_cur] / [huggers_max].")) + to_chat(src, SPAN_NOTICE("We take [huggers_to_transfer] facehuggers and carry them for safekeeping. Now sheltering: [huggers_cur] / [huggers_max].")) update_icons() else to_chat(src, SPAN_WARNING("We can't carry more facehuggers on you.")) @@ -350,11 +350,15 @@ store_egg(E) return + if(istype(T, /obj/effect/alien/resin/special/eggmorph)) + store_eggs_into_egg_morpher(T) + return + var/obj/item/xeno_egg/E = get_active_hand() if(!E) //empty active hand //if no hugger in active hand, we take one from our storage if(eggs_cur <= 0) - to_chat(src, SPAN_WARNING("We don't have any egg to use!")) + to_chat(src, SPAN_WARNING("We don't have any eggs to use!")) return E = new(src, hivenumber) eggs_cur-- @@ -367,6 +371,42 @@ to_chat(src, SPAN_WARNING("We need an empty hand to grab one of our stored eggs!")) return +/mob/living/carbon/xenomorph/carrier/proc/store_eggs_into_egg_morpher(obj/effect/alien/resin/special/eggmorph/morpher) + if(action_busy) + return FALSE + + if(!morpher_safety_checks(morpher)) + return + + visible_message(SPAN_XENOWARNING("[src] starts placing facehuggers into [morpher] from their eggs..."), SPAN_XENONOTICE("We start placing children into [morpher] from our eggs...")) + while(eggs_cur > 0) + if(!morpher_safety_checks(morpher)) + return + + if(!do_after(src, 0.75 SECONDS, INTERRUPT_ALL, BUSY_ICON_GENERIC)) + to_chat(src, SPAN_WARNING("We stop filling [morpher] with our children.")) + return + + playsound(src.loc, "sound/effects/alien_egg_move.ogg", 20, TRUE) + morpher.stored_huggers = min(morpher.huggers_max_amount, morpher.stored_huggers + 1) + eggs_cur-- + to_chat(src, SPAN_XENONOTICE("We slide one of the children out of an egg and place them into [morpher]. Now sheltering: [eggs_cur] / [eggs_max].")) + +/mob/living/carbon/xenomorph/carrier/proc/morpher_safety_checks(obj/effect/alien/resin/special/eggmorph/morpher) + if(morpher.linked_hive && (morpher.linked_hive.hivenumber != hivenumber)) + to_chat(src, SPAN_WARNING("That egg morpher is tainted!")) + return FALSE + + if(morpher.stored_huggers == morpher.huggers_max_amount) + to_chat(src, SPAN_WARNING("[morpher] is full of children!")) + return FALSE + + if(eggs_cur < 1) + to_chat(src, SPAN_WARNING("We don't have any eggs left!")) + return FALSE + + return TRUE + /mob/living/carbon/xenomorph/carrier/attack_ghost(mob/dead/observer/user) . = ..() //Do a view printout as needed just in case the observer doesn't want to join as a Hugger but wants info join_as_facehugger_from_this(user) diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Facehugger.dm b/code/modules/mob/living/carbon/xenomorph/castes/Facehugger.dm index 17ec90a96bda..43c5b78514af 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Facehugger.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Facehugger.dm @@ -124,7 +124,7 @@ if(morpher.linked_hive.hivenumber != hivenumber) to_chat(src, SPAN_XENOWARNING("This isn't your hive's eggmorpher!")) return - if(morpher.stored_huggers >= morpher.huggers_to_grow_max) + if(morpher.stored_huggers >= morpher.huggers_max_amount) to_chat(src, SPAN_XENOWARNING("\The [morpher] is already full of children.")) return visible_message(SPAN_WARNING("\The [src] climbs back into \the [morpher]."), SPAN_XENONOTICE("You climb into \the [morpher].")) diff --git a/code/modules/mob/living/carbon/xenomorph/egg_item.dm b/code/modules/mob/living/carbon/xenomorph/egg_item.dm index a9d00519b691..598c4a70c777 100644 --- a/code/modules/mob/living/carbon/xenomorph/egg_item.dm +++ b/code/modules/mob/living/carbon/xenomorph/egg_item.dm @@ -12,6 +12,8 @@ black_market_value = 35 var/hivenumber = XENO_HIVE_NORMAL var/flags_embryo = NO_FLAGS + ///The objects in this list will be skipped when checking for obstrucing objects. + var/static/list/object_whitelist = list(/obj/structure/machinery/light, /obj/structure/machinery/light_construct) /obj/item/xeno_egg/Initialize(mapload, hive) pixel_x = rand(-3,3) @@ -90,7 +92,7 @@ if(!user.hive) to_chat(user, SPAN_XENOWARNING("Your hive cannot procreate.")) return - if(!user.check_alien_construction(T)) + if(!user.check_alien_construction(T, ignore_nest = TRUE)) return if(!user.check_plasma(30)) return @@ -121,6 +123,8 @@ return for(var/obj/object in T.contents) + if(is_type_in_list(object, object_whitelist)) + continue var/obj/effect/alien/egg/xeno_egg = /obj/effect/alien/egg if(object.layer > initial(xeno_egg.layer)) to_chat(user, SPAN_XENOWARNING("[src] cannot be planted below objects that would obscure it.")) @@ -135,7 +139,7 @@ plant_time = 10 if(!do_after(user, plant_time, INTERRUPT_ALL|BEHAVIOR_IMMOBILE, BUSY_ICON_BUILD)) return - if(!user.check_alien_construction(T)) + if(!user.check_alien_construction(T, ignore_nest = TRUE)) return if(!user.check_plasma(30)) return