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