Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Egg morphers now generate facehuggers on their own [CODE BOUNTY] #7226

Merged
merged 9 commits into from
Oct 29, 2024
2 changes: 0 additions & 2 deletions code/datums/components/weed_food.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
145 changes: 52 additions & 93 deletions code/modules/cm_aliens/structures/special/egg_morpher.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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 <b>[stored_huggers] facehuggers within</b>, 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 <b>[COOLDOWN_SECONDSLEFT(src, spawn_cooldown)] seconds.</b>")
if(isxeno(user))
var/mob/living/carbon/xenomorph/xeno = user
if(xeno.caste_type == XENO_CASTE_CARRIER)
. += SPAN_NOTICE("<b>Using our Retrieve Egg ability, we can easily transfer our eggs into [src].</b>")

/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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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."))

Expand Down
2 changes: 1 addition & 1 deletion code/modules/mob/living/carbon/xenomorph/Facehuggers.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
48 changes: 44 additions & 4 deletions code/modules/mob/living/carbon/xenomorph/castes/Carrier.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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."))
Expand Down Expand Up @@ -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--
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]."))
Expand Down
8 changes: 6 additions & 2 deletions code/modules/mob/living/carbon/xenomorph/egg_item.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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."))
Expand All @@ -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
Expand Down
Loading