diff --git a/code/__HELPERS/ai.dm b/code/__HELPERS/ai.dm
index 3b371ce8a67..f7357fe9c9a 100644
--- a/code/__HELPERS/ai.dm
+++ b/code/__HELPERS/ai.dm
@@ -103,3 +103,22 @@
continue
nearest_target = nearby_vehicle
return nearest_target
+
+/**
+ * This proc attempts to get an instance of an atom type within distance, with center as the center.
+ * Arguments
+ * * center - The center of the search
+ * * type - The type of atom we're looking for
+ * * distance - The distance we should search
+ * * list_to_search - The list to look through for the type
+ */
+/proc/cheap_get_atom(atom/center, type, distance, list/list_to_search)
+ var/turf/turf_center = get_turf(center)
+ if(!turf_center)
+ return
+ for(var/atom/near AS in list_to_search)
+ if(!istype(near, type))
+ continue
+ if(get_dist(turf_center, near) > distance)
+ continue
+ return near
diff --git a/code/modules/xenomorph/_xeno_structure.dm b/code/modules/xenomorph/_xeno_structure.dm
new file mode 100644
index 00000000000..cddad162cd9
--- /dev/null
+++ b/code/modules/xenomorph/_xeno_structure.dm
@@ -0,0 +1,59 @@
+/obj/structure/xeno
+ hit_sound = "alien_resin_break"
+ layer = RESIN_STRUCTURE_LAYER
+ resistance_flags = UNACIDABLE
+ ///Bitflags specific to xeno structures
+ var/xeno_structure_flags
+ ///Which hive(number) do we belong to?
+ var/hivenumber = XENO_HIVE_NORMAL
+
+/obj/structure/xeno/Initialize(mapload, _hivenumber)
+ . = ..()
+ if(!(xeno_structure_flags & IGNORE_WEED_REMOVAL))
+ RegisterSignal(loc, COMSIG_TURF_WEED_REMOVED, PROC_REF(weed_removed))
+ if(_hivenumber) ///because admins can spawn them
+ hivenumber = _hivenumber
+ LAZYADDASSOC(GLOB.xeno_structures_by_hive, hivenumber, src)
+ if(xeno_structure_flags & CRITICAL_STRUCTURE)
+ LAZYADDASSOC(GLOB.xeno_critical_structures_by_hive, hivenumber, src)
+
+/obj/structure/xeno/Destroy()
+ if(!locate(src) in GLOB.xeno_structures_by_hive[hivenumber]+GLOB.xeno_critical_structures_by_hive[hivenumber]) //The rest of the proc is pointless to look through if its not in the lists
+ stack_trace("[src] not found in the list of (potentially critical) xeno structures!") //We dont want to CRASH because that'd block deletion completely. Just trace it and continue.
+ return ..()
+ GLOB.xeno_structures_by_hive[hivenumber] -= src
+ if(xeno_structure_flags & CRITICAL_STRUCTURE)
+ GLOB.xeno_critical_structures_by_hive[hivenumber] -= src
+ return ..()
+
+/obj/structure/xeno/ex_act(severity)
+ take_damage(severity * 0.8, BRUTE, BOMB)
+
+/obj/structure/xeno/attack_hand(mob/living/user)
+ balloon_alert(user, "You only scrape at it")
+ return TRUE
+
+/obj/structure/xeno/flamer_fire_act(burnlevel)
+ take_damage(burnlevel / 3, BURN, FIRE)
+
+/obj/structure/xeno/fire_act()
+ take_damage(10, BURN, FIRE)
+
+/// Destroy the xeno structure when the weed it was on is destroyed
+/obj/structure/xeno/proc/weed_removed()
+ SIGNAL_HANDLER
+ var/obj/alien/weeds/found_weed = locate(/obj/alien/weeds) in loc
+ if(found_weed.obj_integrity <= 0)
+ obj_destruction(damage_flag = MELEE)
+ else
+ obj_destruction()
+
+/obj/structure/xeno/attack_alien(mob/living/carbon/xenomorph/xeno_attacker, damage_amount, damage_type, damage_flag, effects, armor_penetration, isrightclick)
+ if(!(HAS_TRAIT(xeno_attacker, TRAIT_VALHALLA_XENO) && xeno_attacker.a_intent == INTENT_HARM && (tgui_alert(xeno_attacker, "Are you sure you want to tear down [src]?", "Tear down [src]?", list("Yes","No"))) == "Yes"))
+ return ..()
+ if(!do_after(xeno_attacker, 3 SECONDS, NONE, src))
+ return
+ xeno_attacker.do_attack_animation(src, ATTACK_EFFECT_CLAW)
+ balloon_alert_to_viewers("\The [xeno_attacker] tears down \the [src]!", "We tear down \the [src].")
+ playsound(src, "alien_resin_break", 25)
+ take_damage(max_integrity) // Ensure its destroyed
diff --git a/code/modules/xenomorph/acidwell.dm b/code/modules/xenomorph/acidwell.dm
new file mode 100644
index 00000000000..3461ab16f41
--- /dev/null
+++ b/code/modules/xenomorph/acidwell.dm
@@ -0,0 +1,192 @@
+/obj/structure/xeno/acidwell
+ name = "acid well"
+ desc = "An acid well. It stores acid to put out fires."
+ icon = 'icons/Xeno/acid_pool.dmi'
+ plane = FLOOR_PLANE
+ icon_state = "well"
+ density = FALSE
+ opacity = FALSE
+ anchored = TRUE
+ max_integrity = 5
+
+ hit_sound = "alien_resin_move"
+ destroy_sound = "alien_resin_move"
+ ///How many charges of acid this well contains
+ var/charges = 1
+ ///If a xeno is charging this well
+ var/charging = FALSE
+ ///What xeno created this well
+ var/mob/living/carbon/xenomorph/creator = null
+
+/obj/structure/xeno/acidwell/Initialize(mapload, _creator)
+ . = ..()
+ creator = _creator
+ RegisterSignal(creator, COMSIG_QDELETING, PROC_REF(clear_creator))
+ update_icon()
+ var/static/list/connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_cross),
+ )
+ AddElement(/datum/element/connect_loc, connections)
+
+/obj/structure/xeno/acidwell/Destroy()
+ creator = null
+ return ..()
+
+///Signal handler for creator destruction to clear reference
+/obj/structure/xeno/acidwell/proc/clear_creator()
+ SIGNAL_HANDLER
+ creator = null
+
+/obj/structure/xeno/acidwell/obj_destruction(damage_amount, damage_type, damage_flag)
+ if(!QDELETED(creator) && creator.stat == CONSCIOUS && creator.z == z)
+ var/area/A = get_area(src)
+ if(A)
+ to_chat(creator, span_xenoannounce("You sense your acid well at [A.name] has been destroyed!") )
+
+ if(damage_amount || damage_flag) //Spawn the gas only if we actually get destroyed by damage
+ var/datum/effect_system/smoke_spread/xeno/acid/A = new(get_turf(src))
+ A.set_up(clamp(CEILING(charges*0.5, 1),0,3),src) //smoke scales with charges
+ A.start()
+ return ..()
+
+/obj/structure/xeno/acidwell/examine(mob/user)
+ . = ..()
+ if(!isxeno(user) && !isobserver(user))
+ return
+ . += span_xenonotice("An acid well made by [creator]. It currently has [charges]/[XENO_ACID_WELL_MAX_CHARGES] charges.")
+
+/obj/structure/xeno/acidwell/deconstruct(disassembled = TRUE)
+ visible_message(span_danger("[src] suddenly collapses!") )
+ return ..()
+
+/obj/structure/xeno/acidwell/update_icon()
+ . = ..()
+ set_light(charges , charges / 2, LIGHT_COLOR_GREEN)
+
+/obj/structure/xeno/acidwell/update_overlays()
+ . = ..()
+ if(!charges)
+ return
+ . += mutable_appearance(icon, "[charges]", alpha = src.alpha)
+ . += emissive_appearance(icon, "[charges]", alpha = src.alpha)
+
+/obj/structure/xeno/acidwell/flamer_fire_act(burnlevel) //Removes a charge of acid, but fire is extinguished
+ acid_well_fire_interaction()
+
+/obj/structure/xeno/acidwell/fire_act() //Removes a charge of acid, but fire is extinguished
+ acid_well_fire_interaction()
+
+///Handles fire based interactions with the acid well. Depletes 1 charge if there are any to extinguish all fires in the turf while producing acid smoke.
+/obj/structure/xeno/acidwell/proc/acid_well_fire_interaction()
+ if(!charges)
+ take_damage(50, BURN, FIRE)
+ return
+
+ charges--
+ update_icon()
+ var/turf/T = get_turf(src)
+ var/datum/effect_system/smoke_spread/xeno/acid/extuingishing/acid_smoke = new(T) //spawn acid smoke when charges are actually used
+ acid_smoke.set_up(0, src) //acid smoke in the immediate vicinity
+ acid_smoke.start()
+
+ for(var/obj/flamer_fire/F in T) //Extinguish all flames in turf
+ qdel(F)
+
+/obj/structure/xeno/acidwell/attackby(obj/item/I, mob/user, params)
+ if(!isxeno(user))
+ return ..()
+ attack_alien(user)
+
+/obj/structure/xeno/acidwell/attack_alien(mob/living/carbon/xenomorph/xeno_attacker, damage_amount = xeno_attacker.xeno_caste.melee_damage, damage_type = BRUTE, damage_flag = MELEE, effects = TRUE, armor_penetration = 0, isrightclick = FALSE)
+ if(xeno_attacker.a_intent == INTENT_HARM && (CHECK_BITFIELD(xeno_attacker.xeno_caste.caste_flags, CASTE_IS_BUILDER) || xeno_attacker == creator) ) //If we're a builder caste or the creator and we're on harm intent, deconstruct it.
+ balloon_alert(xeno_attacker, "Removing...")
+ if(!do_after(xeno_attacker, XENO_ACID_WELL_FILL_TIME, IGNORE_HELD_ITEM, src, BUSY_ICON_HOSTILE))
+ balloon_alert(xeno_attacker, "Stopped removing")
+ return
+ playsound(src, "alien_resin_break", 25)
+ deconstruct(TRUE, xeno_attacker)
+ return
+
+ if(charges >= 5)
+ balloon_alert(xeno_attacker, "Already full")
+ return
+ if(charging)
+ balloon_alert(xeno_attacker, "Already being filled")
+ return
+
+ if(xeno_attacker.plasma_stored < XENO_ACID_WELL_FILL_COST) //You need to have enough plasma to attempt to fill the well
+ balloon_alert(xeno_attacker, "Need [XENO_ACID_WELL_FILL_COST - xeno_attacker.plasma_stored] more plasma")
+ return
+
+ charging = TRUE
+
+ balloon_alert(xeno_attacker, "Refilling...")
+ if(!do_after(xeno_attacker, XENO_ACID_WELL_FILL_TIME, IGNORE_HELD_ITEM, src, BUSY_ICON_BUILD))
+ charging = FALSE
+ balloon_alert(xeno_attacker, "Aborted refilling")
+ return
+
+ if(xeno_attacker.plasma_stored < XENO_ACID_WELL_FILL_COST)
+ charging = FALSE
+ balloon_alert(xeno_attacker, "Need [XENO_ACID_WELL_FILL_COST - xeno_attacker.plasma_stored] more plasma")
+ return
+
+ xeno_attacker.plasma_stored -= XENO_ACID_WELL_FILL_COST
+ charges++
+ charging = FALSE
+ update_icon()
+ balloon_alert(xeno_attacker, "Now has [charges] / [XENO_ACID_WELL_MAX_CHARGES] charges")
+ to_chat(xeno_attacker,span_xenonotice("We add acid to [src]. It is currently has [charges] / [XENO_ACID_WELL_MAX_CHARGES] charges.") )
+
+/obj/structure/xeno/acidwell/proc/on_cross(datum/source, atom/movable/A, oldloc, oldlocs)
+ SIGNAL_HANDLER
+ if(CHECK_MULTIPLE_BITFIELDS(A.allow_pass_flags, HOVERING))
+ return
+ if(iscarbon(A))
+ HasProximity(A)
+
+/obj/structure/xeno/acidwell/HasProximity(atom/movable/AM)
+ if(!charges)
+ return
+ if(!isliving(AM))
+ return
+ var/mob/living/stepper = AM
+ if(stepper.stat == DEAD)
+ return
+
+ var/charges_used = 0
+
+ for(var/obj/item/explosive/grenade/sticky/sticky_bomb in stepper.contents)
+ if(charges_used >= charges)
+ break
+ if(sticky_bomb.stuck_to == stepper)
+ sticky_bomb.clean_refs()
+ sticky_bomb.forceMove(loc) // i'm not sure if this is even needed, but just to prevent possible bugs
+ visible_message(span_danger("[src] sizzles as [sticky_bomb] melts down in the acid."))
+ qdel(sticky_bomb)
+ charges_used ++
+
+ if(stepper.on_fire && (charges_used < charges))
+ stepper.ExtinguishMob()
+ charges_used ++
+
+ if(!isxeno(stepper))
+ stepper.next_move_slowdown += charges * 2 //Acid spray has slow down so this should too; scales with charges, Min 2 slowdown, Max 10
+ stepper.apply_damage(charges * 10, BURN, BODY_ZONE_PRECISE_L_FOOT, ACID, penetration = 33)
+ stepper.apply_damage(charges * 10, BURN, BODY_ZONE_PRECISE_R_FOOT, ACID, penetration = 33)
+ stepper.visible_message(span_danger("[stepper] is immersed in [src]'s acid!") , \
+ span_danger("We are immersed in [src]'s acid!") , null, 5)
+ playsound(stepper, "sound/bullets/acid_impact1.ogg", 10 * charges)
+ new /obj/effect/temp_visual/acid_bath(get_turf(stepper))
+ charges_used = charges //humans stepping on it empties it out
+
+ if(!charges_used)
+ return
+
+ var/datum/effect_system/smoke_spread/xeno/acid/extuingishing/acid_smoke
+ acid_smoke = new(get_turf(stepper)) //spawn acid smoke when charges are actually used
+ acid_smoke.set_up(0, src) //acid smoke in the immediate vicinity
+ acid_smoke.start()
+
+ charges -= charges_used
+ update_icon()
diff --git a/code/modules/xenomorph/jellypod.dm b/code/modules/xenomorph/jellypod.dm
new file mode 100644
index 00000000000..96c34be0088
--- /dev/null
+++ b/code/modules/xenomorph/jellypod.dm
@@ -0,0 +1,67 @@
+/obj/structure/xeno/resin_jelly_pod
+ name = "Resin jelly pod"
+ desc = "A large resin pod. Inside is a thick, viscous fluid that looks like it doesnt burn easily."
+ icon = 'icons/Xeno/resinpod.dmi'
+ icon_state = "resinpod"
+ density = FALSE
+ opacity = FALSE
+ anchored = TRUE
+ max_integrity = 250
+ layer = RESIN_STRUCTURE_LAYER
+ pixel_x = -16
+ pixel_y = -16
+ xeno_structure_flags = IGNORE_WEED_REMOVAL
+
+ hit_sound = "alien_resin_move"
+ destroy_sound = "alien_resin_move"
+ ///How many actual jellies the pod has stored
+ var/chargesleft = 0
+ ///Max amount of jellies the pod can hold
+ var/maxcharges = 10
+ ///Every 5 times this number seconds we will create a jelly
+ var/recharge_rate = 10
+ ///Countdown to the next time we generate a jelly
+ var/nextjelly = 0
+
+/obj/structure/xeno/resin_jelly_pod/Initialize(mapload, _hivenumber)
+ . = ..()
+ add_overlay(image(icon, "resinpod_inside", layer + 0.01, dir))
+ START_PROCESSING(SSslowprocess, src)
+
+/obj/structure/xeno/resin_jelly_pod/Destroy()
+ STOP_PROCESSING(SSslowprocess, src)
+ return ..()
+
+/obj/structure/xeno/resin_jelly_pod/examine(mob/user, distance, infix, suffix)
+ . = ..()
+ if(isxeno(user))
+ . += "It has [chargesleft] jelly globules remaining[datum_flags & DF_ISPROCESSING ? ", and will create a new jelly in [(recharge_rate-nextjelly)*5] seconds": " and seems latent"]."
+
+/obj/structure/xeno/resin_jelly_pod/process()
+ if(nextjelly <= recharge_rate)
+ nextjelly++
+ return
+ nextjelly = 0
+ chargesleft++
+ if(chargesleft >= maxcharges)
+ return PROCESS_KILL
+
+/obj/structure/xeno/resin_jelly_pod/attack_alien(mob/living/carbon/xenomorph/xeno_attacker, damage_amount = xeno_attacker.xeno_caste.melee_damage, damage_type = BRUTE, damage_flag = MELEE, effects = TRUE, armor_penetration = 0, isrightclick = FALSE)
+ if(xeno_attacker.status_flags & INCORPOREAL)
+ return FALSE
+
+ if((xeno_attacker.a_intent == INTENT_HARM && isxenohivelord(xeno_attacker)) || xeno_attacker.hivenumber != hivenumber)
+ balloon_alert(xeno_attacker, "Destroying...")
+ if(do_after(xeno_attacker, HIVELORD_TUNNEL_DISMANTLE_TIME, IGNORE_HELD_ITEM, src, BUSY_ICON_BUILD))
+ deconstruct(FALSE)
+ return
+
+ if(!chargesleft)
+ balloon_alert(xeno_attacker, "No jelly remaining")
+ to_chat(xeno_attacker, span_xenonotice("We reach into \the [src], but only find dregs of resin. We should wait some more.") )
+ return
+ balloon_alert(xeno_attacker, "Retrieved jelly")
+ new /obj/item/resin_jelly(loc)
+ chargesleft--
+ if(!(datum_flags & DF_ISPROCESSING) && (chargesleft < maxcharges))
+ START_PROCESSING(SSslowprocess, src)
diff --git a/code/modules/xenomorph/nest.dm b/code/modules/xenomorph/nest.dm
new file mode 100644
index 00000000000..52b388d1b02
--- /dev/null
+++ b/code/modules/xenomorph/nest.dm
@@ -0,0 +1,63 @@
+/obj/structure/bed/nest
+ var/force_nest = FALSE
+
+/obj/structure/bed/nest/structure
+ name = "thick alien nest"
+ desc = "A very thick nest, oozing with a thick sticky substance."
+ force_nest = TRUE
+ var/obj/structure/xeno/thick_nest/linked_structure
+
+/obj/structure/bed/nest/structure/Initialize(mapload, hive, obj/structure/xeno/thick_nest/to_link)
+ . = ..()
+ if(to_link)
+ linked_structure = to_link
+ max_integrity = linked_structure.max_integrity
+
+/obj/structure/bed/nest/structure/Destroy()
+ . = ..()
+ if(linked_structure)
+ linked_structure.pred_nest = null
+ QDEL_NULL(linked_structure)
+
+/obj/structure/bed/nest/structure/attack_hand(mob/user)
+ if(!isxeno(user))
+ to_chat(user, span_notice("The sticky resin is too strong for you to do anything to this nest"))
+ return FALSE
+ . = ..()
+
+/obj/structure/xeno/thick_nest
+ name = "thick resin nest"
+ desc = "A very thick nest, oozing with a thick sticky substance."
+ pixel_x = -8
+ pixel_y = -8
+ max_integrity = 400
+ mouse_opacity = MOUSE_OPACITY_ICON
+ icon = 'icons/Xeno/nest.dmi'
+ icon_state = "reinforced_nest"
+ layer = 2.5
+ var/obj/structure/bed/nest/structure/pred_nest
+
+/obj/structure/xeno/thick_nest/examine(mob/user)
+ . = ..()
+ if((isxeno(user) || isobserver(user)) && hivenumber)
+ . += "Used to secure formidable hosts."
+
+/obj/structure/xeno/thick_nest/Initialize(mapload, new_hivenumber)
+ . = ..()
+ if(new_hivenumber)
+ hivenumber = new_hivenumber
+
+ var/datum/hive_status/hive_ref = GLOB.hive_datums[hivenumber]
+ if(hive_ref)
+ hive_ref.thick_nests += src
+
+ pred_nest = new /obj/structure/bed/nest/structure(loc, hive_ref, src) // Nest cannot be destroyed unless the structure itself is destroyed
+
+/obj/structure/xeno/thick_nest/Destroy()
+ . = ..()
+
+ if(hivenumber)
+ GLOB.hive_datums[hivenumber].thick_nests -= src
+
+ pred_nest?.linked_structure = null
+ QDEL_NULL(pred_nest)
diff --git a/code/modules/xenomorph/plant.dm b/code/modules/xenomorph/plant.dm
new file mode 100644
index 00000000000..48e4da15369
--- /dev/null
+++ b/code/modules/xenomorph/plant.dm
@@ -0,0 +1,255 @@
+/obj/structure/xeno/plant
+ name = "Xeno Plant"
+ max_integrity = 5
+ icon = 'icons/Xeno/plants.dmi'
+ interaction_flags = INTERACT_CHECK_INCAPACITATED
+ ///The plant's icon once it's fully grown
+ var/mature_icon_state
+ ///Is the plant ready to be used ?
+ var/mature = FALSE
+ ///How long does it take for the plant to be useable
+ var/maturation_time = 2 MINUTES
+
+/obj/structure/xeno/plant/Initialize(mapload, _hivenumber)
+ . = ..()
+ addtimer(CALLBACK(src, PROC_REF(on_mature)), maturation_time)
+ SSminimaps.add_marker(src, MINIMAP_FLAG_XENO, image('icons/UI_icons/map_blips.dmi', null, "[mature_icon_state]"))
+
+/obj/structure/xeno/plant/can_interact(mob/user)
+ . = ..()
+ if(!.)
+ return FALSE
+ if(!mature && isxeno(user))
+ balloon_alert(user, "Not fully grown")
+ return FALSE
+
+/obj/structure/xeno/plant/update_icon_state()
+ . = ..()
+ icon_state = (mature) ? mature_icon_state : initial(icon_state)
+
+///Called whenever someone uses the plant, xeno or marine
+/obj/structure/xeno/plant/proc/on_use(mob/user)
+ mature = FALSE
+ update_icon()
+ addtimer(CALLBACK(src, PROC_REF(on_mature)), maturation_time)
+ return TRUE
+
+///Called when the plant reaches maturity
+/obj/structure/xeno/plant/proc/on_mature(mob/user)
+ playsound(src, "alien_resin_build", 25)
+ mature = TRUE
+ update_icon()
+
+/obj/structure/xeno/plant/attack_hand(mob/living/user)
+ if(!can_interact(user))
+ return ..()
+ return on_use(user)
+
+/obj/structure/xeno/plant/attack_alien(mob/living/carbon/xenomorph/xeno_attacker, damage_amount = xeno_attacker.xeno_caste.melee_damage, damage_type = BRUTE, damage_flag = MELEE, effects = TRUE, armor_penetration = 0, isrightclick = FALSE)
+ if((xeno_attacker.status_flags & INCORPOREAL))
+ return FALSE
+
+ if(xeno_attacker.a_intent == INTENT_HARM && isxenodrone(xeno_attacker))
+ balloon_alert(xeno_attacker, "Uprooted the plant")
+ xeno_attacker.do_attack_animation(src)
+ deconstruct(FALSE)
+ return FALSE
+ if(can_interact(xeno_attacker))
+ return on_use(xeno_attacker)
+ return TRUE
+
+/obj/structure/xeno/plant/heal_fruit
+ name = "life fruit"
+ desc = "It would almost be appetizing wasn't it for the green colour and the shifting fluids inside..."
+ icon_state = "heal_fruit_immature"
+ mature_icon_state = "heal_fruit"
+ ///Minimum amount of health recovered
+ var/healing_amount_min = 125
+ ///Maximum amount of health recovered, depends on the xeno's max health
+ var/healing_amount_max_health_scaling = 0.5
+
+/obj/structure/xeno/plant/heal_fruit/on_use(mob/user)
+ balloon_alert(user, "Consuming...")
+ if(!do_after(user, 2 SECONDS, IGNORE_HELD_ITEM, src))
+ return FALSE
+ if(!isxeno(user))
+ var/datum/effect_system/smoke_spread/xeno/acid/plant_explosion = new(get_turf(src))
+ plant_explosion.set_up(3,src)
+ plant_explosion.start()
+ visible_message(span_danger("[src] bursts, releasing toxic gas!"))
+ qdel(src)
+ return TRUE
+
+ var/mob/living/carbon/xenomorph/X = user
+ var/heal_amount = max(healing_amount_min, healing_amount_max_health_scaling * X.xeno_caste.max_health)
+ HEAL_XENO_DAMAGE(X, heal_amount, FALSE)
+ playsound(user, "alien_drool", 25)
+ balloon_alert(X, "Health restored")
+ to_chat(X, span_xenowarning("We feel a sudden soothing chill as [src] tends to our wounds."))
+
+ return ..()
+
+/obj/structure/xeno/plant/armor_fruit
+ name = "hard fruit"
+ desc = "The contents of this fruit are protected by a tough outer shell."
+ icon_state = "armor_fruit_immature"
+ mature_icon_state = "armor_fruit"
+ ///How much total sunder should we remove
+ var/sunder_removal = 30
+
+/obj/structure/xeno/plant/armor_fruit/on_use(mob/user)
+ balloon_alert(user, "Consuming...")
+ if(!do_after(user, 2 SECONDS, IGNORE_HELD_ITEM, src))
+ return FALSE
+ if(!isxeno(user))
+ var/turf/far_away_lands = get_turf(user)
+ for(var/x in 1 to 20)
+ var/turf/next_turf = get_step(far_away_lands, REVERSE_DIR(user.dir))
+ if(!next_turf)
+ break
+ far_away_lands = next_turf
+
+ user.throw_at(far_away_lands, 20, spin = TRUE)
+ to_chat(user, span_warning("[src] bursts, releasing a strong gust of pressurised gas!"))
+ if(ishuman(user))
+ var/mob/living/carbon/human/H = user
+ H.adjust_stagger(3 SECONDS)
+ H.apply_damage(30, BRUTE, "chest", BOMB)
+ qdel(src)
+ return TRUE
+
+ balloon_alert(user, "Armor restored")
+ to_chat(user, span_xenowarning("We shed our shattered scales as new ones grow to replace them!"))
+ var/mob/living/carbon/xenomorph/X = user
+ X.adjust_sunder(-sunder_removal)
+ playsound(user, "alien_drool", 25)
+ return ..()
+
+/obj/structure/xeno/plant/plasma_fruit
+ name = "power fruit"
+ desc = "A cyan fruit, beating like a creature's heart"
+ icon_state = "plasma_fruit_immature"
+ mature_icon_state = "plasma_fruit"
+ ///How much bonus plasma should we restore during the duration, 1 being 100% from base regen
+ var/bonus_regen = 1
+ ///How long should the buff last
+ var/duration = 1 MINUTES
+
+/obj/structure/xeno/plant/plasma_fruit/can_interact(mob/user)
+ . = ..()
+ if(!.)
+ return FALSE
+ if(!isxeno(user))
+ return
+ var/mob/living/carbon/xenomorph/X = user
+ if(X.has_status_effect(STATUS_EFFECT_PLASMA_SURGE))
+ balloon_alert(X, "Already increased plasma regen")
+ return FALSE
+
+/obj/structure/xeno/plant/plasma_fruit/on_use(mob/user)
+ balloon_alert(user, "Consuming...")
+ if(!do_after(user, 2 SECONDS, IGNORE_HELD_ITEM, src))
+ return FALSE
+ if(!isxeno(user))
+ visible_message(span_warning("[src] releases a sticky substance before spontaneously bursting into flames!"))
+ flame_radius(3, get_turf(src), colour = "green")
+ qdel(src)
+ return TRUE
+
+ var/mob/living/carbon/xenomorph/X = user
+ if(!(X.xeno_caste.can_flags & CASTE_CAN_BE_GIVEN_PLASMA))
+ to_chat(X, span_xenowarning("But our body rejects the fruit, we do not share the same plasma type!"))
+ return FALSE
+ X.apply_status_effect(/datum/status_effect/plasma_surge, X.xeno_caste.plasma_max, bonus_regen, duration)
+ balloon_alert(X, "Plasma restored")
+ to_chat(X, span_xenowarning("[src] Restores our plasma reserves, our organism is on overdrive!"))
+ playsound(user, "alien_drool", 25)
+ return ..()
+
+/obj/structure/xeno/plant/stealth_plant
+ name = "night shade"
+ desc = "A beautiful flower, what purpose it could serve to the alien hive is beyond you however..."
+ icon_state = "stealth_plant_immature"
+ mature_icon_state = "stealth_plant"
+ maturation_time = 4 MINUTES
+ ///The radius of the passive structure camouflage, requires line of sight
+ var/camouflage_range = 7
+ ///The range of the active stealth ability, does not require line of sight
+ var/active_camouflage_pulse_range = 10
+ ///How long should veil last
+ var/active_camouflage_duration = 20 SECONDS
+ ///How long until the plant can be activated again
+ var/cooldown = 2 MINUTES
+ ///Is the active ability veil on cooldown ?
+ var/on_cooldown = FALSE
+ ///The list of passively camouflaged structures
+ var/list/obj/structure/xeno/camouflaged_structures = list()
+ ////The list of actively camouflaged xenos by veil
+ var/list/mob/living/carbon/xenomorph/camouflaged_xenos = list()
+
+/obj/structure/xeno/plant/stealth_plant/on_mature(mob/user)
+ . = ..()
+ START_PROCESSING(SSslowprocess, src)
+
+/obj/structure/xeno/plant/stealth_plant/Destroy()
+ for(var/obj/structure/xeno/xeno_struct AS in camouflaged_structures)
+ xeno_struct.alpha = initial(xeno_struct.alpha)
+ unveil()
+ STOP_PROCESSING(SSslowprocess, src)
+ return ..()
+
+/obj/structure/xeno/plant/stealth_plant/process()
+ for(var/turf/tile AS in RANGE_TURFS(camouflage_range, loc))
+ for(var/obj/structure/xeno/xeno_struct in tile)
+ if(istype(xeno_struct, /obj/structure/xeno/plant) || !line_of_sight(src, xeno_struct)) //We don't hide plants
+ continue
+ camouflaged_structures.Add(xeno_struct)
+ xeno_struct.alpha = STEALTH_PLANT_PASSIVE_CAMOUFLAGE_ALPHA
+
+/obj/structure/xeno/plant/stealth_plant/can_interact(mob/user)
+ . = ..()
+ if(!.)
+ return FALSE
+ if(ishuman(user))
+ balloon_alert(user, "Nothing happens")
+ to_chat(user, span_notice("You caress [src]'s petals, nothing happens."))
+ return FALSE
+ if(on_cooldown)
+ balloon_alert(user, "Not ready yet")
+ to_chat(user, span_xenowarning("[src] soft light shimmers, we should give it more time to recover!"))
+ return FALSE
+
+/obj/structure/xeno/plant/stealth_plant/on_use(mob/user)
+ balloon_alert(user, "Shaking...")
+ if(!do_after(user, 2 SECONDS, IGNORE_HELD_ITEM, src))
+ return FALSE
+ visible_message(span_danger("[src] releases a burst of glowing pollen!"))
+ veil()
+ return TRUE
+
+///Hides all nearby xenos
+/obj/structure/xeno/plant/stealth_plant/proc/veil()
+ for(var/turf/tile in RANGE_TURFS(camouflage_range, loc))
+ for(var/mob/living/carbon/xenomorph/X in tile)
+ if(X.stat == DEAD || isxenohunter(X) || X.alpha != 255) //We don't mess with xenos capable of going stealth by themselves
+ continue
+ X.alpha = HUNTER_STEALTH_RUN_ALPHA
+ new /obj/effect/temp_visual/alien_fruit_eaten(get_turf(X))
+ balloon_alert(X, "We now blend in")
+ to_chat(X, span_xenowarning("The pollen from [src] reacts with our scales, we are blending with our surroundings!"))
+ camouflaged_xenos.Add(X)
+ on_cooldown = TRUE
+ addtimer(CALLBACK(src, PROC_REF(unveil)), active_camouflage_duration)
+ addtimer(CALLBACK(src, PROC_REF(ready)), cooldown)
+
+///Called when veil() can be used once again
+/obj/structure/xeno/plant/stealth_plant/proc/ready()
+ visible_message(span_danger("[src] petals shift in hue, it is ready to release more pollen."))
+ on_cooldown = FALSE
+
+///Reveals all xenos hidden by veil()
+/obj/structure/xeno/plant/stealth_plant/proc/unveil()
+ for(var/mob/living/carbon/xenomorph/X AS in camouflaged_xenos)
+ X.alpha = initial(X.alpha)
+ balloon_alert(X, "Effect wears off")
+ to_chat(X, span_xenowarning("The effect of [src] wears off!"))
diff --git a/code/modules/xenomorph/silo.dm b/code/modules/xenomorph/silo.dm
new file mode 100644
index 00000000000..ef3e9ac28ff
--- /dev/null
+++ b/code/modules/xenomorph/silo.dm
@@ -0,0 +1,159 @@
+/obj/structure/xeno/silo
+ name = "Resin silo"
+ icon = 'icons/Xeno/resin_silo.dmi'
+ icon_state = "weed_silo"
+ desc = "A slimy, oozy resin bed filled with foul-looking egg-like ...things."
+ bound_width = 96
+ bound_height = 96
+ max_integrity = 1000
+ resistance_flags = UNACIDABLE | DROPSHIP_IMMUNE | PLASMACUTTER_IMMUNE
+ xeno_structure_flags = IGNORE_WEED_REMOVAL|CRITICAL_STRUCTURE
+ plane = FLOOR_PLANE
+ ///How many larva points one silo produce in one minute
+ var/larva_spawn_rate = 0.5
+ var/turf/center_turf
+ var/number_silo
+ ///For minimap icon change if silo takes damage or nearby hostile
+ var/warning
+ COOLDOWN_DECLARE(silo_damage_alert_cooldown)
+ COOLDOWN_DECLARE(silo_proxy_alert_cooldown)
+
+/obj/structure/xeno/silo/Initialize(mapload, _hivenumber)
+ . = ..()
+ center_turf = get_step(src, NORTHEAST)
+ if(!istype(center_turf))
+ center_turf = loc
+
+ if(SSticker.mode?.flags_round_type & MODE_SILO_RESPAWN)
+ for(var/turfs in RANGE_TURFS(XENO_SILO_DETECTION_RANGE, src))
+ RegisterSignal(turfs, COMSIG_ATOM_ENTERED, PROC_REF(resin_silo_proxy_alert))
+
+ if(SSticker.mode?.flags_round_type & MODE_SILOS_SPAWN_MINIONS)
+ SSspawning.registerspawner(src, INFINITY, GLOB.xeno_ai_spawnable, 0, 0, null)
+ SSspawning.spawnerdata[src].required_increment = 2 * max(45 SECONDS, 3 MINUTES - SSmonitor.maximum_connected_players_count * SPAWN_RATE_PER_PLAYER)/SSspawning.wait
+ SSspawning.spawnerdata[src].max_allowed_mobs = max(1, MAX_SPAWNABLE_MOB_PER_PLAYER * SSmonitor.maximum_connected_players_count * 0.5)
+ update_minimap_icon()
+
+ return INITIALIZE_HINT_LATELOAD
+
+/obj/structure/xeno/silo/LateInitialize()
+ . = ..()
+ var/siloprefix = GLOB.hive_datums[hivenumber].name
+ number_silo = length(GLOB.xeno_resin_silos_by_hive[hivenumber]) + 1
+ name = "[siloprefix == "Normal" ? "" : "[siloprefix] "][name] [number_silo]"
+ LAZYADDASSOC(GLOB.xeno_resin_silos_by_hive, hivenumber, src)
+
+ if(!locate(/obj/alien/weeds) in center_turf)
+ new /obj/alien/weeds/node(center_turf)
+ if(GLOB.hive_datums[hivenumber])
+ RegisterSignals(GLOB.hive_datums[hivenumber], list(COMSIG_HIVE_XENO_MOTHER_PRE_CHECK, COMSIG_HIVE_XENO_MOTHER_CHECK), PROC_REF(is_burrowed_larva_host))
+ if(length(GLOB.xeno_resin_silos_by_hive[hivenumber]) == 1)
+ GLOB.hive_datums[hivenumber].give_larva_to_next_in_queue()
+ var/turf/tunnel_turf = get_step(center_turf, NORTH)
+ if(tunnel_turf.can_dig_xeno_tunnel())
+ var/obj/structure/xeno/tunnel/newt = new(tunnel_turf, hivenumber)
+ newt.tunnel_desc = "[AREACOORD_NO_Z(newt)]"
+ newt.name += " [name]"
+ if(GLOB.hive_datums[hivenumber])
+ SSticker.mode.update_silo_death_timer(GLOB.hive_datums[hivenumber])
+
+/obj/structure/xeno/silo/obj_destruction(damage_amount, damage_type, damage_flag)
+ if(GLOB.hive_datums[hivenumber])
+ INVOKE_NEXT_TICK(SSticker.mode, TYPE_PROC_REF(/datum/game_mode, update_silo_death_timer), GLOB.hive_datums[hivenumber]) // checks all silos next tick after this one is gone
+ UnregisterSignal(GLOB.hive_datums[hivenumber], list(COMSIG_HIVE_XENO_MOTHER_PRE_CHECK, COMSIG_HIVE_XENO_MOTHER_CHECK))
+ GLOB.hive_datums[hivenumber].xeno_message("A resin silo has been destroyed at [AREACOORD_NO_Z(src)]!", "xenoannounce", 5, FALSE,src.loc, 'sound/voice/alien_help2.ogg',FALSE , null, /atom/movable/screen/arrow/silo_damaged_arrow)
+ notify_ghosts("\ A resin silo has been destroyed at [AREACOORD_NO_Z(src)]!", source = get_turf(src), action = NOTIFY_JUMP)
+ playsound(loc,'sound/effects/alien_egg_burst.ogg', 75)
+ return ..()
+
+/obj/structure/xeno/silo/Destroy()
+ GLOB.xeno_resin_silos_by_hive[hivenumber] -= src
+
+ for(var/i in contents)
+ var/atom/movable/AM = i
+ AM.forceMove(get_step(center_turf, pick(CARDINAL_ALL_DIRS)))
+ center_turf = null
+
+ STOP_PROCESSING(SSslowprocess, src)
+ return ..()
+
+/obj/structure/xeno/silo/examine(mob/user)
+ . = ..()
+ var/current_integrity = (obj_integrity / max_integrity) * 100
+ switch(current_integrity)
+ if(0 to 20)
+ . += span_warning("It's barely holding, there's leaking oozes all around, and most eggs are broken. Yet it is not inert.")
+ if(20 to 40)
+ . += span_warning("It looks severely damaged, its movements slow.")
+ if(40 to 60)
+ . += span_warning("It's quite beat up, but it seems alive.")
+ if(60 to 80)
+ . += span_warning("It's slightly damaged, but still seems healthy.")
+ if(80 to 100)
+ . += span_info("It appears in good shape, pulsating healthily.")
+
+/obj/structure/xeno/silo/take_damage(damage_amount, damage_type, damage_flag, sound_effect, attack_dir, armour_penetration)
+ . = ..()
+
+ //We took damage, so it's time to start regenerating if we're not already processing
+ if(!CHECK_BITFIELD(datum_flags, DF_ISPROCESSING))
+ START_PROCESSING(SSslowprocess, src)
+
+ resin_silo_damage_alert()
+
+/obj/structure/xeno/silo/proc/resin_silo_damage_alert()
+ if(!COOLDOWN_CHECK(src, silo_damage_alert_cooldown))
+ return
+ warning = TRUE
+ update_minimap_icon()
+ GLOB.hive_datums[hivenumber].xeno_message("Our [name] at [AREACOORD_NO_Z(src)] is under attack! It has [obj_integrity]/[max_integrity] Health remaining.", "xenoannounce", 5, FALSE, src, 'sound/voice/alien_help1.ogg',FALSE, null, /atom/movable/screen/arrow/silo_damaged_arrow)
+ COOLDOWN_START(src, silo_damage_alert_cooldown, XENO_SILO_HEALTH_ALERT_COOLDOWN) //set the cooldown.
+ addtimer(CALLBACK(src, PROC_REF(clear_warning)), XENO_SILO_HEALTH_ALERT_COOLDOWN) //clear warning
+
+///Alerts the Hive when hostiles get too close to their resin silo
+/obj/structure/xeno/silo/proc/resin_silo_proxy_alert(datum/source, atom/movable/hostile, direction)
+ SIGNAL_HANDLER
+
+ if(!COOLDOWN_CHECK(src, silo_proxy_alert_cooldown)) //Proxy alert triggered too recently; abort
+ return
+
+ if(!isliving(hostile))
+ return
+
+ var/mob/living/living_triggerer = hostile
+ if(living_triggerer.stat == DEAD) //We don't care about the dead
+ return
+
+ if(isxeno(hostile))
+ var/mob/living/carbon/xenomorph/X = hostile
+ if(X.hive == GLOB.hive_datums[hivenumber]) //Trigger proxy alert only for hostile xenos
+ return
+
+ warning = TRUE
+ update_minimap_icon()
+ GLOB.hive_datums[hivenumber].xeno_message("Our [name] has detected a nearby hostile [hostile] at [get_area(hostile)] (X: [hostile.x], Y: [hostile.y]).", "xenoannounce", 5, FALSE, hostile, 'sound/voice/alien_help1.ogg', FALSE, null, /atom/movable/screen/arrow/leader_tracker_arrow)
+ COOLDOWN_START(src, silo_proxy_alert_cooldown, XENO_SILO_DETECTION_COOLDOWN) //set the cooldown.
+ addtimer(CALLBACK(src, PROC_REF(clear_warning)), XENO_SILO_DETECTION_COOLDOWN) //clear warning
+
+///Clears the warning for minimap if its warning for hostiles
+/obj/structure/xeno/silo/proc/clear_warning()
+ warning = FALSE
+ update_minimap_icon()
+
+/obj/structure/xeno/silo/process()
+ //Regenerate if we're at less than max integrity
+ if(obj_integrity < max_integrity)
+ obj_integrity = min(obj_integrity + 25, max_integrity) //Regen 5 HP per sec
+
+/obj/structure/xeno/silo/proc/is_burrowed_larva_host(datum/source, list/mothers, list/silos)
+ SIGNAL_HANDLER
+ if(GLOB.hive_datums[hivenumber])
+ silos += src
+
+///Change minimap icon if silo is under attack or not
+/obj/structure/xeno/silo/proc/update_minimap_icon()
+ SSminimaps.remove_marker(src)
+ SSminimaps.add_marker(src, MINIMAP_FLAG_XENO, image('icons/UI_icons/map_blips.dmi', null, "silo[warning ? "_warn" : "_passive"]", VERY_HIGH_FLOAT_LAYER)) // RU TGMC edit - map blips
+
+/obj/structure/xeno/silo/crash
+ resistance_flags = UNACIDABLE | DROPSHIP_IMMUNE | PLASMACUTTER_IMMUNE | INDESTRUCTIBLE
diff --git a/code/modules/xenomorph/spawner.dm b/code/modules/xenomorph/spawner.dm
new file mode 100644
index 00000000000..441780081b0
--- /dev/null
+++ b/code/modules/xenomorph/spawner.dm
@@ -0,0 +1,112 @@
+/obj/structure/xeno/spawner
+ icon = 'icons/Xeno/2x2building.dmi.dmi'
+ bound_width = 64
+ bound_height = 64
+ plane = FLOOR_PLANE
+ name = "spawner"
+ desc = "A slimy, oozy resin bed filled with foul-looking egg-like ...things."
+ icon_state = "spawner"
+ max_integrity = 500
+ resistance_flags = UNACIDABLE | DROPSHIP_IMMUNE
+ xeno_structure_flags = IGNORE_WEED_REMOVAL | CRITICAL_STRUCTURE
+ ///For minimap icon change if silo takes damage or nearby hostile
+ var/warning
+ COOLDOWN_DECLARE(spawner_damage_alert_cooldown)
+ COOLDOWN_DECLARE(spawner_proxy_alert_cooldown)
+ var/linked_minions = list()
+
+/obj/structure/xeno/spawner/Initialize(mapload, _hivenumber)
+ . = ..()
+ LAZYADDASSOC(GLOB.xeno_spawners_by_hive, hivenumber, src)
+ SSspawning.registerspawner(src, INFINITY, GLOB.xeno_ai_spawnable, 0, 0, CALLBACK(src, PROC_REF(on_spawn)))
+ SSspawning.spawnerdata[src].required_increment = max(45 SECONDS, 3 MINUTES - SSmonitor.maximum_connected_players_count * SPAWN_RATE_PER_PLAYER)/SSspawning.wait
+ SSspawning.spawnerdata[src].max_allowed_mobs = max(2, MAX_SPAWNABLE_MOB_PER_PLAYER * SSmonitor.maximum_connected_players_count)
+ set_light(2, 2, LIGHT_COLOR_GREEN)
+ for(var/turfs in RANGE_TURFS(XENO_SILO_DETECTION_RANGE, src))
+ RegisterSignal(turfs, COMSIG_ATOM_ENTERED, PROC_REF(spawner_proxy_alert))
+ update_minimap_icon()
+
+/obj/structure/xeno/spawner/examine(mob/user)
+ . = ..()
+ var/current_integrity = (obj_integrity / max_integrity) * 100
+ switch(current_integrity)
+ if(0 to 20)
+ . += span_warning("It's barely holding, there's leaking oozes all around, and most eggs are broken. Yet it is not inert.")
+ if(20 to 40)
+ . += span_warning("It looks severely damaged, its movements slow.")
+ if(40 to 60)
+ . += span_warning("It's quite beat up, but it seems alive.")
+ if(60 to 80)
+ . += span_warning("It's slightly damaged, but still seems healthy.")
+ if(80 to 100)
+ . += span_info("It appears in good shape, pulsating healthily.")
+
+
+/obj/structure/xeno/spawner/take_damage(damage_amount, damage_type, damage_flag, sound_effect, attack_dir, armour_penetration)
+ . = ..()
+ spawner_damage_alert()
+
+///Alert if spawner is receiving damage
+/obj/structure/xeno/spawner/proc/spawner_damage_alert()
+ if(!COOLDOWN_CHECK(src, spawner_damage_alert_cooldown))
+ warning = FALSE
+ return
+ warning = TRUE
+ update_minimap_icon()
+ GLOB.hive_datums[hivenumber].xeno_message("Our [name] at [AREACOORD_NO_Z(src)] is under attack! It has [obj_integrity]/[max_integrity] Health remaining.", "xenoannounce", 5, FALSE, src, 'sound/voice/alien_help1.ogg',FALSE, null, /atom/movable/screen/arrow/silo_damaged_arrow)
+ COOLDOWN_START(src, spawner_damage_alert_cooldown, XENO_SILO_HEALTH_ALERT_COOLDOWN) //set the cooldown.
+ addtimer(CALLBACK(src, PROC_REF(clear_warning)), XENO_SILO_DETECTION_COOLDOWN) //clear warning
+
+///Alerts the Hive when hostiles get too close to their spawner
+/obj/structure/xeno/spawner/proc/spawner_proxy_alert(datum/source, atom/movable/hostile, direction)
+ SIGNAL_HANDLER
+
+ if(!COOLDOWN_CHECK(src, spawner_proxy_alert_cooldown)) //Proxy alert triggered too recently; abort
+ warning = FALSE
+ return
+
+ if(!isliving(hostile))
+ return
+
+ var/mob/living/living_triggerer = hostile
+ if(living_triggerer.stat == DEAD) //We don't care about the dead
+ return
+
+ if(isxeno(hostile))
+ var/mob/living/carbon/xenomorph/X = hostile
+ if(X.hivenumber == hivenumber) //Trigger proxy alert only for hostile xenos
+ return
+
+ warning = TRUE
+ update_minimap_icon()
+ GLOB.hive_datums[hivenumber].xeno_message("Our [name] has detected a nearby hostile [hostile] at [get_area(hostile)] (X: [hostile.x], Y: [hostile.y]).", "xenoannounce", 5, FALSE, hostile, 'sound/voice/alien_help1.ogg', FALSE, null, /atom/movable/screen/arrow/leader_tracker_arrow)
+ COOLDOWN_START(src, spawner_proxy_alert_cooldown, XENO_SILO_DETECTION_COOLDOWN) //set the cooldown.
+ addtimer(CALLBACK(src, PROC_REF(clear_warning)), XENO_SILO_DETECTION_COOLDOWN) //clear warning
+
+///Clears the warning for minimap if its warning for hostiles
+/obj/structure/xeno/spawner/proc/clear_warning()
+ warning = FALSE
+ update_minimap_icon()
+
+/obj/structure/xeno/spawner/Destroy()
+ GLOB.xeno_spawners_by_hive[hivenumber] -= src
+ return ..()
+
+///Change minimap icon if spawner is under attack or not
+/obj/structure/xeno/spawner/proc/update_minimap_icon()
+ SSminimaps.remove_marker(src)
+ SSminimaps.add_marker(src, MINIMAP_FLAG_XENO, image('icons/UI_icons/map_blips.dmi', null, "spawner[warning ? "_warn" : "_passive"]", , ABOVE_FLOAT_LAYER)) // RU TGMC edit - map blips
+
+/obj/structure/xeno/spawner/proc/on_spawn(list/squad)
+ if(!isxeno(squad[length(squad)]))
+ CRASH("Xeno spawner somehow tried to spawn a non xeno (tried to spawn [squad[length(squad)]])")
+ var/mob/living/carbon/xenomorph/X = squad[length(squad)]
+ X.transfer_to_hive(hivenumber)
+ linked_minions = squad
+ if(hivenumber == XENO_HIVE_FALLEN) //snowflake so valhalla isnt filled with minions after you're done
+ RegisterSignal(src, COMSIG_QDELETING, PROC_REF(kill_linked_minions))
+
+/obj/structure/xeno/spawner/proc/kill_linked_minions()
+ for(var/mob/living/carbon/xenomorph/linked in linked_minions)
+ linked.death(TRUE)
+ UnregisterSignal(src, COMSIG_QDELETING)
diff --git a/code/modules/xenomorph/trap.dm b/code/modules/xenomorph/trap.dm
new file mode 100644
index 00000000000..f6180ad44ae
--- /dev/null
+++ b/code/modules/xenomorph/trap.dm
@@ -0,0 +1,211 @@
+/obj/structure/xeno/trap
+ desc = "It looks like a hiding hole."
+ name = "resin hole"
+ icon = 'icons/Xeno/Effects.dmi'
+ icon_state = "trap"
+ density = FALSE
+ opacity = FALSE
+ anchored = TRUE
+ max_integrity = 5
+ layer = RESIN_STRUCTURE_LAYER
+ destroy_sound = "alien_resin_break"
+ ///defines for trap type to trigger on activation
+ var/trap_type
+ ///The hugger inside our trap
+ var/obj/item/clothing/mask/facehugger/hugger = null
+ ///smoke effect to create when the trap is triggered
+ var/datum/effect_system/smoke_spread/smoke
+ ///connection list for huggers
+ var/static/list/listen_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(trigger_trap),
+ )
+
+/obj/structure/xeno/trap/Initialize(mapload, _hivenumber)
+ . = ..()
+ AddElement(/datum/element/connect_loc, listen_connections)
+
+/obj/structure/xeno/trap/ex_act(severity)
+ take_damage(severity, BRUTE, BOMB)
+
+/obj/structure/xeno/trap/update_icon_state()
+ . = ..()
+ switch(trap_type)
+ if(TRAP_HUGGER)
+ icon_state = "traphugger"
+ if(TRAP_SMOKE_NEURO)
+ icon_state = "trapneurogas"
+ if(TRAP_SMOKE_ACID)
+ icon_state = "trapacidgas"
+ if(TRAP_ACID_WEAK)
+ icon_state = "trapacidweak"
+ if(TRAP_ACID_NORMAL)
+ icon_state = "trapacid"
+ if(TRAP_ACID_STRONG)
+ icon_state = "trapacidstrong"
+ else
+ icon_state = "trap"
+
+/obj/structure/xeno/trap/obj_destruction(damage_amount, damage_type, damage_flag)
+ if((damage_amount || damage_flag) && hugger && loc)
+ trigger_trap()
+ return ..()
+
+/obj/structure/xeno/trap/proc/set_trap_type(new_trap_type)
+ if(new_trap_type == trap_type)
+ return
+ trap_type = new_trap_type
+ update_icon()
+
+/obj/structure/xeno/trap/examine(mob/user)
+ . = ..()
+ if(!isxeno(user))
+ return
+ . += "A hole for a little one to hide in ambush for or for spewing acid."
+ switch(trap_type)
+ if(TRAP_HUGGER)
+ . += "There's a little one inside."
+ if(TRAP_SMOKE_NEURO)
+ . += "There's pressurized neurotoxin inside."
+ if(TRAP_SMOKE_ACID)
+ . += "There's pressurized acid gas inside."
+ if(TRAP_ACID_WEAK)
+ . += "There's pressurized weak acid inside."
+ if(TRAP_ACID_NORMAL)
+ . += "There's pressurized normal acid inside."
+ if(TRAP_ACID_STRONG)
+ . += "There's strong pressurized acid inside."
+ else
+ . += "It's empty."
+
+/obj/structure/xeno/trap/flamer_fire_act(burnlevel)
+ hugger?.kill_hugger()
+ trigger_trap()
+ set_trap_type(null)
+
+/obj/structure/xeno/trap/fire_act()
+ hugger?.kill_hugger()
+ trigger_trap()
+ set_trap_type(null)
+
+///Triggers the hugger trap
+/obj/structure/xeno/trap/proc/trigger_trap(datum/source, atom/movable/AM, oldloc, oldlocs)
+ SIGNAL_HANDLER
+ if(!trap_type)
+ return
+ if(AM && (hivenumber == AM.get_xeno_hivenumber()))
+ return
+ playsound(src, "alien_resin_break", 25)
+ if(iscarbon(AM))
+ var/mob/living/carbon/crosser = AM
+ crosser.visible_message(span_warning("[crosser] trips on [src]!"), span_danger("You trip on [src]!"))
+ crosser.ParalyzeNoChain(4 SECONDS)
+ switch(trap_type)
+ if(TRAP_HUGGER)
+ if(!AM)
+ drop_hugger()
+ return
+ if(!iscarbon(AM))
+ return
+ var/mob/living/carbon/crosser = AM
+ if(!crosser.can_be_facehugged(hugger))
+ return
+ drop_hugger()
+ if(TRAP_SMOKE_NEURO, TRAP_SMOKE_ACID)
+ smoke.start()
+ if(TRAP_ACID_WEAK)
+ for(var/turf/acided AS in RANGE_TURFS(1, src))
+ new /obj/effect/xenomorph/spray/weak(acided, 8 SECONDS, XENO_WEAK_ACID_PUDDLE_DAMAGE)
+ if(TRAP_ACID_NORMAL)
+ for(var/turf/acided AS in RANGE_TURFS(1, src))
+ new /obj/effect/xenomorph/spray(acided, 10 SECONDS, XENO_DEFAULT_ACID_PUDDLE_DAMAGE)
+ if(TRAP_ACID_STRONG)
+ for(var/turf/acided AS in RANGE_TURFS(1, src))
+ new /obj/effect/xenomorph/spray/strong(acided, 12 SECONDS, XENO_HIGH_ACID_PUDDLE_DAMAGE)
+ xeno_message("A [trap_type] trap at [AREACOORD_NO_Z(src)] has been triggered!", "xenoannounce", 5, hivenumber, FALSE, get_turf(src), 'sound/voice/alien_talk2.ogg', FALSE, null, /atom/movable/screen/arrow/attack_order_arrow, COLOR_ORANGE, TRUE)
+ set_trap_type(null)
+
+/// Move the hugger out of the trap
+/obj/structure/xeno/trap/proc/drop_hugger()
+ hugger.forceMove(loc)
+ hugger.go_active(TRUE, TRUE) //Removes stasis
+ visible_message(span_warning("[hugger] gets out of [src]!") )
+ hugger = null
+ set_trap_type(null)
+
+/obj/structure/xeno/trap/attack_alien(mob/living/carbon/xenomorph/xeno_attacker, damage_amount = xeno_attacker.xeno_caste.melee_damage, damage_type = BRUTE, damage_flag = MELEE, effects = TRUE, armor_penetration = 0, isrightclick = FALSE)
+ if(xeno_attacker.status_flags & INCORPOREAL)
+ return FALSE
+
+ if(xeno_attacker.a_intent == INTENT_HARM)
+ return ..()
+ if(trap_type == TRAP_HUGGER)
+ if(!(xeno_attacker.xeno_caste.can_flags & CASTE_CAN_HOLD_FACEHUGGERS))
+ return
+ if(!hugger)
+ balloon_alert(xeno_attacker, "It is empty")
+ return
+ xeno_attacker.put_in_active_hand(hugger)
+ hugger.go_active(TRUE)
+ hugger = null
+ set_trap_type(null)
+ balloon_alert(xeno_attacker, "Removed facehugger")
+ return
+ var/datum/action/ability/activable/xeno/corrosive_acid/acid_action = locate(/datum/action/ability/activable/xeno/corrosive_acid) in xeno_attacker.actions
+ if(istype(xeno_attacker.ammo, /datum/ammo/xeno/boiler_gas))
+ var/datum/ammo/xeno/boiler_gas/boiler_glob = xeno_attacker.ammo
+ if(!boiler_glob.enhance_trap(src, xeno_attacker))
+ return
+ else if(acid_action)
+ if(!do_after(xeno_attacker, 2 SECONDS, NONE, src))
+ return
+ switch(acid_action.acid_type)
+ if(/obj/effect/xenomorph/acid/weak)
+ set_trap_type(TRAP_ACID_WEAK)
+ if(/obj/effect/xenomorph/acid)
+ set_trap_type(TRAP_ACID_NORMAL)
+ if(/obj/effect/xenomorph/acid/strong)
+ set_trap_type(TRAP_ACID_STRONG)
+ else
+ return // nothing happened!
+ playsound(xeno_attacker.loc, 'sound/effects/refill.ogg', 25, 1)
+ balloon_alert(xeno_attacker, "Filled with [trap_type]")
+
+/obj/structure/xeno/trap/attackby(obj/item/I, mob/user, params)
+ . = ..()
+
+ if(!istype(I, /obj/item/clothing/mask/facehugger) || !isxeno(user))
+ return
+ var/obj/item/clothing/mask/facehugger/FH = I
+ if(trap_type)
+ balloon_alert(user, "Already occupied")
+ return
+
+ if(FH.stat == DEAD)
+ balloon_alert(user, "Cannot insert facehugger")
+ return
+
+ user.transferItemToLoc(FH, src)
+ FH.go_idle(TRUE)
+ hugger = FH
+ set_trap_type(TRAP_HUGGER)
+ balloon_alert(user, "Inserted facehugger")
+
+//Sentient facehugger can get in the trap
+/obj/structure/xeno/trap/attack_facehugger(mob/living/carbon/xenomorph/facehugger/F, isrightclick = FALSE)
+ . = ..()
+ if(tgui_alert(F, "Do you want to get into the trap?", "Get inside the trap", list("Yes", "No")) != "Yes")
+ return
+
+ if(trap_type)
+ F.balloon_alert(F, "The trap is occupied")
+ return
+
+ var/obj/item/clothing/mask/facehugger/FH = new(src)
+ FH.go_idle(TRUE)
+ hugger = FH
+ set_trap_type(TRAP_HUGGER)
+
+ F.visible_message(span_xenowarning("[F] slides back into [src]."),span_xenonotice("You slides back into [src]."))
+ F.ghostize()
+ F.death(deathmessage = "get inside the trap", silent = TRUE)
+ qdel(F)
diff --git a/code/modules/xenomorph/tunnel.dm b/code/modules/xenomorph/tunnel.dm
new file mode 100644
index 00000000000..0eeaee13180
--- /dev/null
+++ b/code/modules/xenomorph/tunnel.dm
@@ -0,0 +1,181 @@
+/obj/structure/xeno/tunnel
+ name = "tunnel"
+ desc = "A tunnel entrance. Looks like it was dug by some kind of clawed beast."
+ icon = 'icons/Xeno/Effects.dmi'
+ icon_state = "hole"
+
+ density = FALSE
+ opacity = FALSE
+ anchored = TRUE
+ resistance_flags = UNACIDABLE|BANISH_IMMUNE
+ layer = RESIN_STRUCTURE_LAYER
+
+ max_integrity = 140
+
+ xeno_structure_flags = IGNORE_WEED_REMOVAL
+ ///Description added by the hivelord.
+ var/tunnel_desc = ""
+ ///What hivelord created that tunnel. Can be null
+ var/mob/living/carbon/xenomorph/hivelord/creator = null
+
+/obj/structure/xeno/tunnel/Initialize(mapload, _hivenumber)
+ . = ..()
+ LAZYADDASSOC(GLOB.xeno_tunnels_by_hive, hivenumber, src)
+ SSminimaps.add_marker(src, MINIMAP_FLAG_XENO, image('icons/UI_icons/map_blips.dmi', null, "xenotunnel", HIGH_FLOAT_LAYER)) // RU TGMC edit - map blips
+
+/obj/structure/xeno/tunnel/Destroy()
+ var/turf/drop_loc = get_turf(src)
+ for(var/atom/movable/thing AS in contents) //Empty the tunnel of contents
+ thing.forceMove(drop_loc)
+
+ if(!QDELETED(creator))
+ to_chat(creator, span_xenoannounce("You sense your [name] at [tunnel_desc] has been destroyed!") ) //Alert creator
+
+ xeno_message("Hive tunnel [name] at [tunnel_desc] has been destroyed!", "xenoannounce", 5, hivenumber) //Also alert hive because tunnels matter.
+
+ LAZYREMOVE(GLOB.xeno_tunnels_by_hive[hivenumber], src)
+ if(creator)
+ creator.tunnels -= src
+ creator = null
+
+ for(var/datum/atom_hud/xeno_tactical/xeno_tac_hud in GLOB.huds) //HUD clean up
+ xeno_tac_hud.remove_from_hud(src)
+ SSminimaps.remove_marker(src)
+
+ return ..()
+
+///Signal handler for creator destruction to clear reference
+/obj/structure/xeno/tunnel/proc/clear_creator()
+ SIGNAL_HANDLER
+ creator = null
+
+/obj/structure/xeno/tunnel/examine(mob/user)
+ . = ..()
+ if(!isxeno(user) && !isobserver(user))
+ return
+ if(tunnel_desc)
+ . += span_info("The Hivelord scent reads: \'[tunnel_desc]\'")
+
+/obj/structure/xeno/tunnel/deconstruct(disassembled = TRUE)
+ visible_message(span_danger("[src] suddenly collapses!") )
+ return ..()
+
+/obj/structure/xeno/tunnel/attackby(obj/item/I, mob/user, params)
+ if(!isxeno(user))
+ return ..()
+ attack_alien(user)
+
+/obj/structure/xeno/tunnel/attack_alien(mob/living/carbon/xenomorph/xeno_attacker, damage_amount = xeno_attacker.xeno_caste.melee_damage, damage_type = BRUTE, damage_flag = MELEE, effects = TRUE, armor_penetration = 0, isrightclick = FALSE)
+ if(!istype(xeno_attacker) || xeno_attacker.stat || xeno_attacker.lying_angle || xeno_attacker.status_flags & INCORPOREAL)
+ return
+
+ if(xeno_attacker.a_intent == INTENT_HARM && xeno_attacker == creator)
+ balloon_alert(xeno_attacker, "Filling in tunnel...")
+ if(do_after(xeno_attacker, HIVELORD_TUNNEL_DISMANTLE_TIME, IGNORE_HELD_ITEM, src, BUSY_ICON_BUILD))
+ deconstruct(FALSE)
+ return
+
+ if(xeno_attacker.anchored)
+ balloon_alert(xeno_attacker, "Cannot enter while immobile")
+ return FALSE
+
+ if(length(GLOB.xeno_tunnels_by_hive[hivenumber]) < 2)
+ balloon_alert(xeno_attacker, "No exit tunnel")
+ return FALSE
+
+ pick_a_tunnel(xeno_attacker)
+
+/obj/structure/xeno/tunnel/attack_larva(mob/living/carbon/xenomorph/larva/L) //So larvas can actually use tunnels
+ attack_alien(L)
+
+/obj/structure/xeno/tunnel/attack_ghost(mob/dead/observer/user)
+ . = ..()
+
+ var/list/obj/destinations = GLOB.xeno_tunnels_by_hive[hivenumber]
+ var/obj/structure/xeno/tunnel/targettunnel
+ if(LAZYLEN(destinations) > 2)
+ var/list/tunnel_assoc = list()
+ for(var/obj/D in destinations)
+ tunnel_assoc["X:[D.x], Y:[D.y] - \[[get_area(D)]\]"] = D
+ destinations = list()
+ for(var/d in tunnel_assoc)
+ destinations += d
+ var/input = tgui_input_list(user ,"Choose a tunnel to teleport to:" ,"Ghost Tunnel teleport" ,destinations ,null, 0)
+ if(!input)
+ return
+ targettunnel = tunnel_assoc[input]
+ if(!input)
+ return
+ else
+ //There are only 2 tunnels. Pick the other one.
+ for(var/P in destinations)
+ if(P != src)
+ targettunnel = P
+ if(!targettunnel || QDELETED(targettunnel) || !targettunnel.loc)
+ return
+ user.forceMove(get_turf(targettunnel))
+
+///Here we pick a tunnel to go to, then travel to that tunnel and peep out, confirming whether or not we want to emerge or go to another tunnel.
+/obj/structure/xeno/tunnel/proc/pick_a_tunnel(mob/living/carbon/xenomorph/M)
+ to_chat(M, span_notice("Select a tunnel to go to."))
+
+ var/atom/movable/screen/minimap/map = SSminimaps.fetch_minimap_object(z, MINIMAP_FLAG_XENO)
+ M.client.screen += map
+ var/list/polled_coords = map.get_coords_from_click(M)
+ M.client.screen -= map
+ if(!polled_coords)
+ return
+ var/turf/clicked_turf = locate(polled_coords[1], polled_coords[2], z)
+
+ ///We find the tunnel, looking within 10 tiles of where the user clicked, excluding src
+ var/obj/structure/xeno/tunnel/targettunnel = cheap_get_atom(clicked_turf, /obj/structure/xeno/tunnel, 10, GLOB.xeno_tunnels_by_hive[hivenumber] - src)
+ if(QDELETED(src)) //Make sure we still exist in the event the player keeps the interface open
+ return
+ if(!M.Adjacent(src) && M.loc != src) //Make sure we're close enough to our tunnel; either adjacent to or in one
+ return
+ if(QDELETED(targettunnel)) //Make sure our target destination still exists in the event the player keeps the interface open
+ if(M.loc == src) //If we're in the tunnel and cancelling out, spit us out.
+ M.forceMove(loc)
+ return
+ if(targettunnel == src)
+ balloon_alert(M, "We're already here")
+ if(M.loc == src) //If we're in the tunnel and cancelling out, spit us out.
+ M.forceMove(loc)
+ return
+ if(targettunnel.z != z)
+ balloon_alert(M, "Tunnel not connected")
+ if(M.loc == src) //If we're in the tunnel and cancelling out, spit us out.
+ M.forceMove(loc)
+ return
+ var/distance = get_dist(get_turf(src), get_turf(targettunnel))
+ var/tunnel_time = clamp(distance, HIVELORD_TUNNEL_MIN_TRAVEL_TIME, HIVELORD_TUNNEL_SMALL_MAX_TRAVEL_TIME)
+
+ if(M.mob_size == MOB_SIZE_BIG) //Big xenos take longer
+ tunnel_time = clamp(distance * 1.5, HIVELORD_TUNNEL_MIN_TRAVEL_TIME, HIVELORD_TUNNEL_LARGE_MAX_TRAVEL_TIME)
+ M.visible_message(span_xenonotice("[M] begins heaving their huge bulk down into \the [src].") , \
+ span_xenonotice("We begin heaving our monstrous bulk into \the [src] to [targettunnel.tunnel_desc].") )
+ else
+ M.visible_message(span_xenonotice("\The [M] begins crawling down into \the [src].") , \
+ span_xenonotice("We begin crawling down into \the [src] to [targettunnel.tunnel_desc].") )
+
+ if(isxenolarva(M)) //Larva can zip through near-instantly, they are wormlike after all
+ tunnel_time = 5
+
+ if(!do_after(M, tunnel_time, IGNORE_HELD_ITEM, src, BUSY_ICON_GENERIC))
+ balloon_alert(M, "Crawling interrupted")
+ return
+ if(!targettunnel || !isturf(targettunnel.loc)) //Make sure the end tunnel is still there
+ balloon_alert(M, "Tunnel ended unexpectedly")
+ return
+ M.forceMove(targettunnel)
+ var/double_check = tgui_alert(M, "Emerge here?", "Tunnel: [targettunnel]", list("Yes","Pick another tunnel"), 0)
+ if(M.loc != targettunnel) //double check that we're still in the tunnel in the event it gets destroyed while we still have the interface open
+ return
+ if(double_check == "Pick another tunnel")
+ return targettunnel.pick_a_tunnel(M)
+ M.forceMove(targettunnel.loc)
+ M.visible_message(span_xenonotice("\The [M] pops out of \the [src].") , \
+ span_xenonotice("We pop out through the other side!") )
+
+/obj/structure/xeno/tunnel/attack_facehugger(mob/living/carbon/xenomorph/facehugger/F, isrightclick = FALSE)
+ attack_alien(F)
diff --git a/code/modules/xenomorph/turret.dm b/code/modules/xenomorph/turret.dm
new file mode 100644
index 00000000000..11a6905b9a4
--- /dev/null
+++ b/code/modules/xenomorph/turret.dm
@@ -0,0 +1,272 @@
+/obj/structure/xeno/xeno_turret
+ icon = 'icons/Xeno/acidturret.dmi'
+ icon_state = XENO_TURRET_ACID_ICONSTATE
+ name = "acid turret"
+ desc = "A menacing looking construct of resin, it seems to be alive. It fires acid against intruders."
+ bound_width = 32
+ bound_height = 32
+ obj_integrity = 600
+ max_integrity = 1500
+ layer = ABOVE_MOB_LAYER
+ density = TRUE
+ resistance_flags = UNACIDABLE | DROPSHIP_IMMUNE |PORTAL_IMMUNE
+ xeno_structure_flags = IGNORE_WEED_REMOVAL|HAS_OVERLAY
+ allow_pass_flags = PASS_AIR|PASS_THROW
+ ///What kind of spit it uses
+ var/datum/ammo/ammo = /datum/ammo/xeno/acid/heavy/turret
+ ///Range of the turret
+ var/range = 7
+ ///Target of the turret
+ var/atom/hostile
+ ///Last target of the turret
+ var/atom/last_hostile
+ ///Potential list of targets found by scan
+ var/list/atom/potential_hostiles
+ ///Fire rate of the target in ticks
+ var/firerate = 5
+ ///The last time the sentry did a scan
+ var/last_scan_time
+ ///light color that gets set in initialize
+ var/light_initial_color = LIGHT_COLOR_GREEN
+ ///For minimap icon change if sentry is firing
+ var/firing
+
+///Change minimap icon if its firing or not firing
+/obj/structure/xeno/xeno_turret/proc/update_minimap_icon()
+ SSminimaps.remove_marker(src)
+ SSminimaps.add_marker(src, MINIMAP_FLAG_XENO, image('icons/UI_icons/map_blips.dmi', null, "xeno_turret[firing ? "_firing" : "_passive"]")) // RU TGMC edit - map blips
+
+/obj/structure/xeno/xeno_turret/Initialize(mapload, _hivenumber)
+ . = ..()
+ ammo = GLOB.ammo_list[ammo]
+ potential_hostiles = list()
+ LAZYADDASSOC(GLOB.xeno_resin_turrets_by_hive, hivenumber, src)
+ START_PROCESSING(SSobj, src)
+ AddComponent(/datum/component/automatedfire/xeno_turret_autofire, firerate)
+ RegisterSignal(src, COMSIG_AUTOMATIC_SHOOTER_SHOOT, PROC_REF(shoot))
+ RegisterSignal(SSdcs, COMSIG_GLOB_DROPSHIP_HIJACKED, PROC_REF(destroy_on_hijack))
+ if(light_initial_color)
+ set_light(2, 2, light_initial_color)
+ update_minimap_icon()
+ update_icon()
+
+///Signal handler to delete the turret when the alamo is hijacked
+/obj/structure/xeno/xeno_turret/proc/destroy_on_hijack()
+ SIGNAL_HANDLER
+ qdel(src)
+
+/obj/structure/xeno/xeno_turret/obj_destruction(damage_amount, damage_type, damage_flag)
+ if(damage_amount) //Spawn effects only if we actually get destroyed by damage
+ on_destruction()
+ return ..()
+
+/obj/structure/xeno/xeno_turret/proc/on_destruction()
+ var/datum/effect_system/smoke_spread/xeno/smoke = new /datum/effect_system/smoke_spread/xeno/acid(src)
+ smoke.set_up(1, get_turf(src))
+ smoke.start()
+
+/obj/structure/xeno/xeno_turret/Destroy()
+ GLOB.xeno_resin_turrets_by_hive[hivenumber] -= src
+ set_hostile(null)
+ set_last_hostile(null)
+ STOP_PROCESSING(SSobj, src)
+ playsound(loc,'sound/effects/xeno_turret_death.ogg', 70)
+ return ..()
+
+/obj/structure/xeno/xeno_turret/ex_act(severity)
+ take_damage(severity * 5, BRUTE, BOMB)
+
+/obj/structure/xeno/xeno_turret/flamer_fire_act(burnlevel)
+ take_damage(burnlevel * 2, BURN, FIRE)
+ ENABLE_BITFIELD(resistance_flags, ON_FIRE)
+
+/obj/structure/xeno/xeno_turret/fire_act()
+ take_damage(60, BURN, FIRE)
+ ENABLE_BITFIELD(resistance_flags, ON_FIRE)
+
+/obj/structure/xeno/xeno_turret/update_overlays()
+ . = ..()
+ if(!(xeno_structure_flags & HAS_OVERLAY))
+ return
+ if(obj_integrity <= max_integrity / 2)
+ . += image('icons/Xeno/acidturret.dmi', src, "+turret_damage")
+ if(CHECK_BITFIELD(resistance_flags, ON_FIRE))
+ . += image('icons/Xeno/acidturret.dmi', src, "+turret_on_fire")
+
+/obj/structure/xeno/xeno_turret/process()
+ //Turrets regen some HP, every 2 sec
+ if(obj_integrity < max_integrity)
+ obj_integrity = min(obj_integrity + TURRET_HEALTH_REGEN, max_integrity)
+ update_icon()
+ DISABLE_BITFIELD(resistance_flags, ON_FIRE)
+ if(world.time > last_scan_time + TURRET_SCAN_FREQUENCY)
+ scan()
+ last_scan_time = world.time
+ if(!length(potential_hostiles))
+ return
+ set_hostile(get_target())
+ if (!hostile)
+ if(last_hostile)
+ set_last_hostile(null)
+ return
+ if(!TIMER_COOLDOWN_CHECK(src, COOLDOWN_XENO_TURRETS_ALERT))
+ GLOB.hive_datums[hivenumber].xeno_message("Our [name] is attacking a nearby hostile [hostile] at [get_area(hostile)] (X: [hostile.x], Y: [hostile.y]).", "xenoannounce", 5, FALSE, hostile, 'sound/voice/alien_help1.ogg', FALSE, null, /atom/movable/screen/arrow/turret_attacking_arrow)
+ TIMER_COOLDOWN_START(src, COOLDOWN_XENO_TURRETS_ALERT, 20 SECONDS)
+ if(hostile != last_hostile)
+ set_last_hostile(hostile)
+ SEND_SIGNAL(src, COMSIG_AUTOMATIC_SHOOTER_START_SHOOTING_AT)
+
+/obj/structure/xeno/xeno_turret/attackby(obj/item/I, mob/living/user, params)
+ if(I.flags_item & NOBLUDGEON || !isliving(user))
+ return attack_hand(user)
+
+ user.changeNext_move(I.attack_speed)
+ user.do_attack_animation(src, used_item = I)
+
+ var/damage = I.force
+ var/multiplier = 1
+ if(I.damtype == BURN) //Burn damage deals extra vs resin structures (mostly welders).
+ multiplier += 1
+
+ if(istype(I, /obj/item/tool/pickaxe/plasmacutter) && !user.do_actions)
+ var/obj/item/tool/pickaxe/plasmacutter/P = I
+ if(P.start_cut(user, name, src, PLASMACUTTER_BASE_COST * PLASMACUTTER_VLOW_MOD))
+ multiplier += PLASMACUTTER_RESIN_MULTIPLIER
+ P.cut_apart(user, name, src, PLASMACUTTER_BASE_COST * PLASMACUTTER_VLOW_MOD)
+
+ damage *= max(0, multiplier)
+ take_damage(damage, BRUTE, MELEE)
+ playsound(src, "alien_resin_break", 25)
+
+///Signal handler for hard del of hostile
+/obj/structure/xeno/xeno_turret/proc/unset_hostile()
+ SIGNAL_HANDLER
+ hostile = null
+
+///Signal handler for hard del of last_hostile
+/obj/structure/xeno/xeno_turret/proc/unset_last_hostile()
+ SIGNAL_HANDLER
+ last_hostile = null
+
+///Setter for hostile with hard del in mind
+/obj/structure/xeno/xeno_turret/proc/set_hostile(_hostile)
+ if(hostile != _hostile)
+ hostile = _hostile
+ RegisterSignal(hostile, COMSIG_QDELETING, PROC_REF(unset_hostile))
+
+///Setter for last_hostile with hard del in mind
+/obj/structure/xeno/xeno_turret/proc/set_last_hostile(_last_hostile)
+ if(last_hostile)
+ UnregisterSignal(last_hostile, COMSIG_QDELETING)
+ last_hostile = _last_hostile
+
+///Look for the closest human in range and in light of sight. If no human is in range, will look for xenos of other hives
+/obj/structure/xeno/xeno_turret/proc/get_target()
+ var/distance = range + 0.5 //we add 0.5 so if a potential target is at range, it is accepted by the system
+ var/buffer_distance
+ var/list/turf/path = list()
+ for (var/atom/nearby_hostile AS in potential_hostiles)
+ if(isliving(nearby_hostile))
+ var/mob/living/nearby_living_hostile = nearby_hostile
+ if(nearby_living_hostile.stat == DEAD)
+ continue
+ if(HAS_TRAIT(nearby_hostile, TRAIT_TURRET_HIDDEN))
+ continue
+ buffer_distance = get_dist(nearby_hostile, src)
+ if (distance <= buffer_distance) //If we already found a target that's closer
+ continue
+ path = getline(src, nearby_hostile)
+ path -= get_turf(src)
+ if(!length(path)) //Can't shoot if it's on the same turf
+ continue
+ var/blocked = FALSE
+ for(var/turf/T AS in path)
+ if(IS_OPAQUE_TURF(T) || T.density && !(T.allow_pass_flags & PASS_PROJECTILE))
+ blocked = TRUE
+ break //LoF Broken; stop checking; we can't proceed further.
+
+ for(var/obj/machinery/MA in T)
+ if(MA.opacity || MA.density && !(MA.allow_pass_flags & PASS_PROJECTILE))
+ blocked = TRUE
+ break //LoF Broken; stop checking; we can't proceed further.
+
+ for(var/obj/structure/S in T)
+ if(S.opacity || S.density && !(S.allow_pass_flags & PASS_PROJECTILE))
+ blocked = TRUE
+ break //LoF Broken; stop checking; we can't proceed further.
+ if(!blocked)
+ distance = buffer_distance
+ . = nearby_hostile
+
+///Return TRUE if a possible target is near
+/obj/structure/xeno/xeno_turret/proc/scan()
+ potential_hostiles.Cut()
+ for (var/mob/living/carbon/human/nearby_human AS in cheap_get_humans_near(src, TURRET_SCAN_RANGE))
+ if(nearby_human.stat == DEAD)
+ continue
+ if(nearby_human.get_xeno_hivenumber() == hivenumber)
+ continue
+ potential_hostiles += nearby_human
+ for (var/mob/living/carbon/xenomorph/nearby_xeno AS in cheap_get_xenos_near(src, range))
+ if(GLOB.hive_datums[hivenumber] == nearby_xeno.hive)
+ continue
+ if(nearby_xeno.stat == DEAD)
+ continue
+ potential_hostiles += nearby_xeno
+ for(var/obj/vehicle/unmanned/vehicle AS in GLOB.unmanned_vehicles)
+ if(vehicle.z == z && get_dist(vehicle, src) <= range)
+ potential_hostiles += vehicle
+ for(var/obj/vehicle/sealed/mecha/mech AS in GLOB.mechas_list)
+ if(mech.z == z && get_dist(mech, src) <= range)
+ potential_hostiles += mech
+
+///Signal handler to make the turret shoot at its target
+/obj/structure/xeno/xeno_turret/proc/shoot()
+ SIGNAL_HANDLER
+ if(!hostile)
+ SEND_SIGNAL(src, COMSIG_AUTOMATIC_SHOOTER_STOP_SHOOTING_AT)
+ firing = FALSE
+ update_minimap_icon()
+ return
+ face_atom(hostile)
+ var/obj/projectile/newshot = new(loc)
+ newshot.generate_bullet(ammo)
+ newshot.def_zone = pick(GLOB.base_miss_chance)
+ newshot.fire_at(hostile, src, null, ammo.max_range, ammo.shell_speed)
+ if(istype(ammo, /datum/ammo/xeno/hugger))
+ var/datum/ammo/xeno/hugger/hugger_ammo = ammo
+ newshot.color = initial(hugger_ammo.hugger_type.color)
+ hugger_ammo.hivenumber = hivenumber
+ firing = TRUE
+ update_minimap_icon()
+
+/obj/structure/xeno/xeno_turret/sticky
+ name = "Sticky resin turret"
+ icon = 'icons/Xeno/acidturret.dmi'
+ icon_state = XENO_TURRET_STICKY_ICONSTATE
+ desc = "A menacing looking construct of resin, it seems to be alive. It fires resin against intruders."
+ light_initial_color = LIGHT_COLOR_PURPLE
+ ammo = /datum/ammo/xeno/sticky/turret
+ firerate = 5
+
+/obj/structure/xeno/xeno_turret/sticky/on_destruction()
+ for(var/i = 1 to 20) // maybe a bit laggy
+ var/obj/projectile/new_proj = new(src)
+ new_proj.generate_bullet(ammo)
+ new_proj.fire_at(null, src, range = rand(1, 4), angle = rand(1, 360), recursivity = TRUE)
+
+/obj/structure/xeno/xeno_turret/hugger_turret
+ name = "hugger turret"
+ icon_state = "hugger_turret"
+ desc = "A menacing looking construct of resin, it seems to be alive. It fires huggers against intruders."
+ obj_integrity = 400
+ max_integrity = 400
+ light_initial_color = LIGHT_COLOR_BROWN
+ ammo = /datum/ammo/xeno/hugger
+ firerate = 5 SECONDS
+
+/obj/structure/xeno/xeno_turret/hugger_turret/on_destruction()
+ for(var/i = 1 to 5)
+ var/obj/projectile/new_proj = new(src)
+ new_proj.generate_bullet(ammo)
+ new_proj.fire_at(null, src, range = rand(1, 3), angle = rand(1, 360), recursivity = TRUE)
diff --git a/code/modules/xenomorph/xeno_structures.dm b/code/modules/xenomorph/xeno_structures.dm
deleted file mode 100644
index 51991c3b656..00000000000
--- a/code/modules/xenomorph/xeno_structures.dm
+++ /dev/null
@@ -1,1731 +0,0 @@
-/obj/structure/xeno
- hit_sound = "alien_resin_break"
- layer = RESIN_STRUCTURE_LAYER
- resistance_flags = UNACIDABLE
- ///Bitflags specific to xeno structures
- var/xeno_structure_flags
- ///Which hive(number) do we belong to?
- var/hivenumber = XENO_HIVE_NORMAL
-
-/obj/structure/xeno/Initialize(mapload, _hivenumber)
- . = ..()
- if(!(xeno_structure_flags & IGNORE_WEED_REMOVAL))
- RegisterSignal(loc, COMSIG_TURF_WEED_REMOVED, PROC_REF(weed_removed))
- if(_hivenumber) ///because admins can spawn them
- hivenumber = _hivenumber
- LAZYADDASSOC(GLOB.xeno_structures_by_hive, hivenumber, src)
- if(xeno_structure_flags & CRITICAL_STRUCTURE)
- LAZYADDASSOC(GLOB.xeno_critical_structures_by_hive, hivenumber, src)
-
-/obj/structure/xeno/Destroy()
- if(!locate(src) in GLOB.xeno_structures_by_hive[hivenumber]+GLOB.xeno_critical_structures_by_hive[hivenumber]) //The rest of the proc is pointless to look through if its not in the lists
- stack_trace("[src] not found in the list of (potentially critical) xeno structures!") //We dont want to CRASH because that'd block deletion completely. Just trace it and continue.
- return ..()
- GLOB.xeno_structures_by_hive[hivenumber] -= src
- if(xeno_structure_flags & CRITICAL_STRUCTURE)
- GLOB.xeno_critical_structures_by_hive[hivenumber] -= src
- return ..()
-
-/obj/structure/xeno/ex_act(severity)
- take_damage(severity * 0.8, BRUTE, BOMB)
-
-/obj/structure/xeno/attack_hand(mob/living/user)
- balloon_alert(user, "You only scrape at it")
- return TRUE
-
-/obj/structure/xeno/flamer_fire_act(burnlevel)
- take_damage(burnlevel / 3, BURN, FIRE)
-
-/obj/structure/xeno/fire_act()
- take_damage(10, BURN, FIRE)
-
-/// Destroy the xeno structure when the weed it was on is destroyed
-/obj/structure/xeno/proc/weed_removed()
- SIGNAL_HANDLER
- var/obj/alien/weeds/found_weed = locate(/obj/alien/weeds) in loc
- if(found_weed.obj_integrity <= 0)
- obj_destruction(damage_flag = MELEE)
- else
- obj_destruction()
-
-/obj/structure/xeno/attack_alien(mob/living/carbon/xenomorph/xeno_attacker, damage_amount, damage_type, damage_flag, effects, armor_penetration, isrightclick)
- if(!(HAS_TRAIT(xeno_attacker, TRAIT_VALHALLA_XENO) && xeno_attacker.a_intent == INTENT_HARM && (tgui_alert(xeno_attacker, "Are you sure you want to tear down [src]?", "Tear down [src]?", list("Yes","No"))) == "Yes"))
- return ..()
- if(!do_after(xeno_attacker, 3 SECONDS, NONE, src))
- return
- xeno_attacker.do_attack_animation(src, ATTACK_EFFECT_CLAW)
- balloon_alert_to_viewers("\The [xeno_attacker] tears down \the [src]!", "We tear down \the [src].")
- playsound(src, "alien_resin_break", 25)
- take_damage(max_integrity) // Ensure its destroyed
-
-//Carrier trap
-/obj/structure/xeno/trap
- desc = "It looks like a hiding hole."
- name = "resin hole"
- icon = 'icons/Xeno/Effects.dmi'
- icon_state = "trap"
- density = FALSE
- opacity = FALSE
- anchored = TRUE
- max_integrity = 5
- layer = RESIN_STRUCTURE_LAYER
- destroy_sound = "alien_resin_break"
- ///defines for trap type to trigger on activation
- var/trap_type
- ///The hugger inside our trap
- var/obj/item/clothing/mask/facehugger/hugger = null
- ///smoke effect to create when the trap is triggered
- var/datum/effect_system/smoke_spread/smoke
- ///connection list for huggers
- var/static/list/listen_connections = list(
- COMSIG_ATOM_ENTERED = PROC_REF(trigger_trap),
- )
-
-/obj/structure/xeno/trap/Initialize(mapload, _hivenumber)
- . = ..()
- RegisterSignal(src, COMSIG_MOVABLE_SHUTTLE_CRUSH, PROC_REF(shuttle_crush))
- AddElement(/datum/element/connect_loc, listen_connections)
-
-/obj/structure/xeno/trap/ex_act(severity)
- take_damage(severity, BRUTE, BOMB)
-
-/obj/structure/xeno/trap/update_icon_state()
- . = ..()
- switch(trap_type)
- if(TRAP_HUGGER)
- icon_state = "traphugger"
- if(TRAP_SMOKE_NEURO)
- icon_state = "trapneurogas"
- if(TRAP_SMOKE_ACID)
- icon_state = "trapacidgas"
- if(TRAP_ACID_WEAK)
- icon_state = "trapacidweak"
- if(TRAP_ACID_NORMAL)
- icon_state = "trapacid"
- if(TRAP_ACID_STRONG)
- icon_state = "trapacidstrong"
- else
- icon_state = "trap"
-
-/obj/structure/xeno/trap/obj_destruction(damage_amount, damage_type, damage_flag)
- if((damage_amount || damage_flag) && hugger && loc)
- trigger_trap()
- return ..()
-
-/obj/structure/xeno/trap/proc/set_trap_type(new_trap_type)
- if(new_trap_type == trap_type)
- return
- trap_type = new_trap_type
- update_icon()
-
-///Ensures that no huggies will be released when the trap is crushed by a shuttle; no more trapping shuttles with huggies
-/obj/structure/xeno/trap/proc/shuttle_crush()
- SIGNAL_HANDLER
- qdel(src)
-
-/obj/structure/xeno/trap/examine(mob/user)
- . = ..()
- if(!isxeno(user))
- return
- . += "A hole for a little one to hide in ambush for or for spewing acid."
- switch(trap_type)
- if(TRAP_HUGGER)
- . += "There's a little one inside."
- if(TRAP_SMOKE_NEURO)
- . += "There's pressurized neurotoxin inside."
- if(TRAP_SMOKE_ACID)
- . += "There's pressurized acid gas inside."
- if(TRAP_ACID_WEAK)
- . += "There's pressurized weak acid inside."
- if(TRAP_ACID_NORMAL)
- . += "There's pressurized normal acid inside."
- if(TRAP_ACID_STRONG)
- . += "There's strong pressurized acid inside."
- else
- . += "It's empty."
-
-/obj/structure/xeno/trap/flamer_fire_act(burnlevel)
- hugger?.kill_hugger()
- trigger_trap()
- set_trap_type(null)
-
-/obj/structure/xeno/trap/fire_act()
- hugger?.kill_hugger()
- trigger_trap()
- set_trap_type(null)
-
-///Triggers the hugger trap
-/obj/structure/xeno/trap/proc/trigger_trap(datum/source, atom/movable/AM, oldloc, oldlocs)
- SIGNAL_HANDLER
- if(!trap_type)
- return
- if(AM && (hivenumber == AM.get_xeno_hivenumber()))
- return
- playsound(src, "alien_resin_break", 25)
- if(iscarbon(AM))
- var/mob/living/carbon/crosser = AM
- crosser.visible_message(span_warning("[crosser] trips on [src]!"), span_danger("You trip on [src]!"))
- crosser.ParalyzeNoChain(4 SECONDS)
- switch(trap_type)
- if(TRAP_HUGGER)
- if(!AM)
- drop_hugger()
- return
- if(!iscarbon(AM))
- return
- var/mob/living/carbon/crosser = AM
- if(!crosser.can_be_facehugged(hugger))
- return
- drop_hugger()
- if(TRAP_SMOKE_NEURO, TRAP_SMOKE_ACID)
- smoke.start()
- if(TRAP_ACID_WEAK)
- for(var/turf/acided AS in RANGE_TURFS(1, src))
- new /obj/effect/xenomorph/spray/weak(acided, 8 SECONDS, XENO_WEAK_ACID_PUDDLE_DAMAGE)
- if(TRAP_ACID_NORMAL)
- for(var/turf/acided AS in RANGE_TURFS(1, src))
- new /obj/effect/xenomorph/spray(acided, 10 SECONDS, XENO_DEFAULT_ACID_PUDDLE_DAMAGE)
- if(TRAP_ACID_STRONG)
- for(var/turf/acided AS in RANGE_TURFS(1, src))
- new /obj/effect/xenomorph/spray/strong(acided, 12 SECONDS, XENO_HIGH_ACID_PUDDLE_DAMAGE)
- xeno_message("A [trap_type] trap at [AREACOORD_NO_Z(src)] has been triggered!", "xenoannounce", 5, hivenumber, FALSE, get_turf(src), 'sound/voice/alien_talk2.ogg', FALSE, null, /atom/movable/screen/arrow/attack_order_arrow, COLOR_ORANGE, TRUE)
- set_trap_type(null)
-
-/// Move the hugger out of the trap
-/obj/structure/xeno/trap/proc/drop_hugger()
- hugger.forceMove(loc)
- hugger.go_active(TRUE, TRUE) //Removes stasis
- visible_message(span_warning("[hugger] gets out of [src]!") )
- hugger = null
- set_trap_type(null)
-
-/obj/structure/xeno/trap/attack_alien(mob/living/carbon/xenomorph/xeno_attacker, damage_amount = xeno_attacker.xeno_caste.melee_damage, damage_type = BRUTE, damage_flag = MELEE, effects = TRUE, armor_penetration = 0, isrightclick = FALSE)
- if(xeno_attacker.status_flags & INCORPOREAL)
- return FALSE
-
- if(xeno_attacker.a_intent == INTENT_HARM)
- return ..()
- if(trap_type == TRAP_HUGGER)
- if(!(xeno_attacker.xeno_caste.can_flags & CASTE_CAN_HOLD_FACEHUGGERS))
- return
- if(!hugger)
- balloon_alert(xeno_attacker, "It is empty")
- return
- xeno_attacker.put_in_active_hand(hugger)
- hugger.go_active(TRUE)
- hugger = null
- set_trap_type(null)
- balloon_alert(xeno_attacker, "Removed facehugger")
- return
- var/datum/action/ability/activable/xeno/corrosive_acid/acid_action = locate(/datum/action/ability/activable/xeno/corrosive_acid) in xeno_attacker.actions
- if(istype(xeno_attacker.ammo, /datum/ammo/xeno/boiler_gas))
- var/datum/ammo/xeno/boiler_gas/boiler_glob = xeno_attacker.ammo
- if(!boiler_glob.enhance_trap(src, xeno_attacker))
- return
- else if(acid_action)
- if(!do_after(xeno_attacker, 2 SECONDS, NONE, src))
- return
- switch(acid_action.acid_type)
- if(/obj/effect/xenomorph/acid/weak)
- set_trap_type(TRAP_ACID_WEAK)
- if(/obj/effect/xenomorph/acid)
- set_trap_type(TRAP_ACID_NORMAL)
- if(/obj/effect/xenomorph/acid/strong)
- set_trap_type(TRAP_ACID_STRONG)
- else
- return // nothing happened!
- playsound(xeno_attacker.loc, 'sound/effects/refill.ogg', 25, 1)
- balloon_alert(xeno_attacker, "Filled with [trap_type]")
-
-/obj/structure/xeno/trap/attackby(obj/item/I, mob/user, params)
- . = ..()
-
- if(!istype(I, /obj/item/clothing/mask/facehugger) || !isxeno(user))
- return
- var/obj/item/clothing/mask/facehugger/FH = I
- if(trap_type)
- balloon_alert(user, "Already occupied")
- return
-
- if(FH.stat == DEAD)
- balloon_alert(user, "Cannot insert facehugger")
- return
-
- user.transferItemToLoc(FH, src)
- FH.go_idle(TRUE)
- hugger = FH
- set_trap_type(TRAP_HUGGER)
- balloon_alert(user, "Inserted facehugger")
-
-//Sentient facehugger can get in the trap
-/obj/structure/xeno/trap/attack_facehugger(mob/living/carbon/xenomorph/facehugger/F, isrightclick = FALSE)
- . = ..()
- if(tgui_alert(F, "Do you want to get into the trap?", "Get inside the trap", list("Yes", "No")) != "Yes")
- return
-
- if(trap_type)
- F.balloon_alert(F, "The trap is occupied")
- return
-
- var/obj/item/clothing/mask/facehugger/FH = new(src)
- FH.go_idle(TRUE)
- hugger = FH
- set_trap_type(TRAP_HUGGER)
-
- F.visible_message(span_xenowarning("[F] slides back into [src]."),span_xenonotice("You slides back into [src]."))
- F.ghostize()
- F.death(deathmessage = "get inside the trap", silent = TRUE)
- qdel(F)
-
-/*
-TUNNEL
-*/
-/obj/structure/xeno/tunnel
- name = "tunnel"
- desc = "A tunnel entrance. Looks like it was dug by some kind of clawed beast."
- icon = 'icons/Xeno/Effects.dmi'
- icon_state = "hole"
-
- density = FALSE
- opacity = FALSE
- anchored = TRUE
- resistance_flags = UNACIDABLE|BANISH_IMMUNE
- layer = RESIN_STRUCTURE_LAYER
-
- max_integrity = 140
-
- hud_possible = list(XENO_TACTICAL_HUD)
- xeno_structure_flags = IGNORE_WEED_REMOVAL
- ///Description added by the hivelord.
- var/tunnel_desc = ""
- ///What hivelord created that tunnel. Can be null
- var/mob/living/carbon/xenomorph/hivelord/creator = null
-
-/obj/structure/xeno/tunnel/Initialize(mapload, _hivenumber)
- . = ..()
- LAZYADDASSOC(GLOB.xeno_tunnels_by_hive, hivenumber, src)
- prepare_huds()
- for(var/datum/atom_hud/xeno_tactical/xeno_tac_hud in GLOB.huds) //Add to the xeno tachud
- xeno_tac_hud.add_to_hud(src)
- hud_set_xeno_tunnel()
- SSminimaps.add_marker(src, MINIMAP_FLAG_XENO, image('icons/UI_icons/map_blips.dmi', null, "xenotunnel", HIGH_FLOAT_LAYER)) // RU TGMC edit - map blips
-
-/obj/structure/xeno/tunnel/Destroy()
- var/turf/drop_loc = get_turf(src)
- for(var/atom/movable/thing AS in contents) //Empty the tunnel of contents
- thing.forceMove(drop_loc)
-
- if(!QDELETED(creator))
- to_chat(creator, span_xenoannounce("You sense your [name] at [tunnel_desc] has been destroyed!") ) //Alert creator
-
- xeno_message("Hive tunnel [name] at [tunnel_desc] has been destroyed!", "xenoannounce", 5, hivenumber) //Also alert hive because tunnels matter.
-
- LAZYREMOVE(GLOB.xeno_tunnels_by_hive[hivenumber], src)
- if(creator)
- creator.tunnels -= src
- creator = null
-
- for(var/datum/atom_hud/xeno_tactical/xeno_tac_hud in GLOB.huds) //HUD clean up
- xeno_tac_hud.remove_from_hud(src)
- SSminimaps.remove_marker(src)
-
- return ..()
-
-///Signal handler for creator destruction to clear reference
-/obj/structure/xeno/tunnel/proc/clear_creator()
- SIGNAL_HANDLER
- creator = null
-
-/obj/structure/xeno/tunnel/examine(mob/user)
- . = ..()
- if(!isxeno(user) && !isobserver(user))
- return
- if(tunnel_desc)
- . += span_info("The Hivelord scent reads: \'[tunnel_desc]\'")
-
-/obj/structure/xeno/tunnel/deconstruct(disassembled = TRUE)
- visible_message(span_danger("[src] suddenly collapses!") )
- return ..()
-
-/obj/structure/xeno/tunnel/attackby(obj/item/I, mob/user, params)
- if(!isxeno(user))
- return ..()
- attack_alien(user)
-
-/obj/structure/xeno/tunnel/attack_alien(mob/living/carbon/xenomorph/xeno_attacker, damage_amount = xeno_attacker.xeno_caste.melee_damage, damage_type = BRUTE, damage_flag = MELEE, effects = TRUE, armor_penetration = 0, isrightclick = FALSE)
- if(!istype(xeno_attacker) || xeno_attacker.stat || xeno_attacker.lying_angle || xeno_attacker.status_flags & INCORPOREAL)
- return
-
- if(xeno_attacker.a_intent == INTENT_HARM && xeno_attacker == creator)
- balloon_alert(xeno_attacker, "Filling in tunnel...")
- if(do_after(xeno_attacker, HIVELORD_TUNNEL_DISMANTLE_TIME, IGNORE_HELD_ITEM, src, BUSY_ICON_BUILD))
- deconstruct(FALSE)
- return
-
- if(xeno_attacker.anchored)
- balloon_alert(xeno_attacker, "Cannot enter while immobile")
- return FALSE
-
- if(length(GLOB.xeno_tunnels_by_hive[hivenumber]) < 2)
- balloon_alert(xeno_attacker, "No exit tunnel")
- return FALSE
-
- pick_a_tunnel(xeno_attacker)
-
-/obj/structure/xeno/tunnel/attack_larva(mob/living/carbon/xenomorph/larva/L) //So larvas can actually use tunnels
- attack_alien(L)
-
-/obj/structure/xeno/tunnel/attack_ghost(mob/dead/observer/user)
- . = ..()
-
- var/list/obj/destinations = GLOB.xeno_tunnels_by_hive[hivenumber]
- var/obj/structure/xeno/tunnel/targettunnel
- if(LAZYLEN(destinations) > 2)
- var/list/tunnel_assoc = list()
- for(var/obj/D in destinations)
- tunnel_assoc["X:[D.x], Y:[D.y] - \[[get_area(D)]\]"] = D
- destinations = list()
- for(var/d in tunnel_assoc)
- destinations += d
- var/input = tgui_input_list(user ,"Choose a tunnel to teleport to:" ,"Ghost Tunnel teleport" ,destinations ,null, 0)
- if(!input)
- return
- targettunnel = tunnel_assoc[input]
- if(!input)
- return
- else
- //There are only 2 tunnels. Pick the other one.
- for(var/P in destinations)
- if(P != src)
- targettunnel = P
- if(!targettunnel || QDELETED(targettunnel) || !targettunnel.loc)
- return
- user.forceMove(get_turf(targettunnel))
-
-///Here we pick a tunnel to go to, then travel to that tunnel and peep out, confirming whether or not we want to emerge or go to another tunnel.
-/obj/structure/xeno/tunnel/proc/pick_a_tunnel(mob/living/carbon/xenomorph/M)
- var/obj/structure/xeno/tunnel/targettunnel = tgui_input_list(M, "Choose a tunnel to crawl to", "Tunnel", GLOB.xeno_tunnels_by_hive[hivenumber])
- if(QDELETED(src)) //Make sure we still exist in the event the player keeps the interface open
- return
- if(!M.Adjacent(src) && M.loc != src) //Make sure we're close enough to our tunnel; either adjacent to or in one
- return
- if(QDELETED(targettunnel)) //Make sure our target destination still exists in the event the player keeps the interface open
- balloon_alert(M, "Tunnel no longer exists")
- if(M.loc == src) //If we're in the tunnel and cancelling out, spit us out.
- M.forceMove(loc)
- return
- if(targettunnel == src)
- balloon_alert(M, "We're already here")
- if(M.loc == src) //If we're in the tunnel and cancelling out, spit us out.
- M.forceMove(loc)
- return
- if(targettunnel.z != z)
- balloon_alert(M, "Tunnel not connected")
- if(M.loc == src) //If we're in the tunnel and cancelling out, spit us out.
- M.forceMove(loc)
- return
- var/distance = get_dist(get_turf(src), get_turf(targettunnel))
- var/tunnel_time = clamp(distance, HIVELORD_TUNNEL_MIN_TRAVEL_TIME, HIVELORD_TUNNEL_SMALL_MAX_TRAVEL_TIME)
-
- if(M.mob_size == MOB_SIZE_BIG) //Big xenos take longer
- tunnel_time = clamp(distance * 1.5, HIVELORD_TUNNEL_MIN_TRAVEL_TIME, HIVELORD_TUNNEL_LARGE_MAX_TRAVEL_TIME)
- M.visible_message(span_xenonotice("[M] begins heaving their huge bulk down into \the [src].") , \
- span_xenonotice("We begin heaving our monstrous bulk into \the [src] to [targettunnel.tunnel_desc].") )
- else
- M.visible_message(span_xenonotice("\The [M] begins crawling down into \the [src].") , \
- span_xenonotice("We begin crawling down into \the [src] to [targettunnel.tunnel_desc].") )
-
- if(isxenolarva(M)) //Larva can zip through near-instantly, they are wormlike after all
- tunnel_time = 5
-
- if(!do_after(M, tunnel_time, IGNORE_HELD_ITEM, src, BUSY_ICON_GENERIC))
- balloon_alert(M, "Crawling interrupted")
- return
- if(!targettunnel || !isturf(targettunnel.loc)) //Make sure the end tunnel is still there
- balloon_alert(M, "Tunnel ended unexpectedly")
- return
- M.forceMove(targettunnel)
- var/double_check = tgui_alert(M, "Emerge here?", "Tunnel: [targettunnel]", list("Yes","Pick another tunnel"), 0)
- if(M.loc != targettunnel) //double check that we're still in the tunnel in the event it gets destroyed while we still have the interface open
- return
- if(double_check == "Pick another tunnel")
- return targettunnel.pick_a_tunnel(M)
- M.forceMove(targettunnel.loc)
- M.visible_message(span_xenonotice("\The [M] pops out of \the [src].") , \
- span_xenonotice("We pop out through the other side!") )
-
-///Makes sure the tunnel is visible to other xenos even through obscuration.
-/obj/structure/xeno/tunnel/proc/hud_set_xeno_tunnel()
- var/image/holder = hud_list[XENO_TACTICAL_HUD]
- if(!holder)
- return
- holder.icon = 'icons/mob/hud.dmi'
- holder.icon_state = "hudtraitor"
- hud_list[XENO_TACTICAL_HUD] = holder
-
-/obj/structure/xeno/tunnel/attack_facehugger(mob/living/carbon/xenomorph/facehugger/F, isrightclick = FALSE)
- attack_alien(F)
-
-//Resin Water Well
-/obj/structure/xeno/acidwell
- name = "acid well"
- desc = "An acid well. It stores acid to put out fires."
- icon = 'icons/Xeno/acid_pool.dmi'
- plane = FLOOR_PLANE
- icon_state = "well"
- density = FALSE
- opacity = FALSE
- anchored = TRUE
- max_integrity = 5
-
- hit_sound = "alien_resin_move"
- destroy_sound = "alien_resin_move"
- ///How many charges of acid this well contains
- var/charges = 1
- ///If a xeno is charging this well
- var/charging = FALSE
- ///What xeno created this well
- var/mob/living/carbon/xenomorph/creator = null
-
-/obj/structure/xeno/acidwell/Initialize(mapload, _creator)
- . = ..()
- creator = _creator
- RegisterSignal(creator, COMSIG_QDELETING, PROC_REF(clear_creator))
- update_icon()
- var/static/list/connections = list(
- COMSIG_ATOM_ENTERED = PROC_REF(on_cross),
- )
- AddElement(/datum/element/connect_loc, connections)
-
-/obj/structure/xeno/acidwell/Destroy()
- creator = null
- return ..()
-
-///Signal handler for creator destruction to clear reference
-/obj/structure/xeno/acidwell/proc/clear_creator()
- SIGNAL_HANDLER
- creator = null
-
-///Ensures that no acid gas will be released when the well is crushed by a shuttle
-/obj/structure/xeno/acidwell/proc/shuttle_crush()
- SIGNAL_HANDLER
- qdel(src)
-
-
-/obj/structure/xeno/acidwell/obj_destruction(damage_amount, damage_type, damage_flag)
- if(!QDELETED(creator) && creator.stat == CONSCIOUS && creator.z == z)
- var/area/A = get_area(src)
- if(A)
- to_chat(creator, span_xenoannounce("You sense your acid well at [A.name] has been destroyed!") )
-
- if(damage_amount || damage_flag) //Spawn the gas only if we actually get destroyed by damage
- var/datum/effect_system/smoke_spread/xeno/acid/A = new(get_turf(src))
- A.set_up(clamp(CEILING(charges*0.5, 1),0,3),src) //smoke scales with charges
- A.start()
- return ..()
-
-/obj/structure/xeno/acidwell/examine(mob/user)
- . = ..()
- if(!isxeno(user) && !isobserver(user))
- return
- . += span_xenonotice("An acid well made by [creator]. It currently has [charges]/[XENO_ACID_WELL_MAX_CHARGES] charges.")
-
-/obj/structure/xeno/acidwell/deconstruct(disassembled = TRUE)
- visible_message(span_danger("[src] suddenly collapses!") )
- return ..()
-
-/obj/structure/xeno/acidwell/update_icon()
- . = ..()
- set_light(charges , charges / 2, LIGHT_COLOR_GREEN)
-
-/obj/structure/xeno/acidwell/update_overlays()
- . = ..()
- if(!charges)
- return
- . += mutable_appearance(icon, "[charges]", alpha = src.alpha)
- . += emissive_appearance(icon, "[charges]", alpha = src.alpha)
-
-/obj/structure/xeno/acidwell/flamer_fire_act(burnlevel) //Removes a charge of acid, but fire is extinguished
- acid_well_fire_interaction()
-
-/obj/structure/xeno/acidwell/fire_act() //Removes a charge of acid, but fire is extinguished
- acid_well_fire_interaction()
-
-///Handles fire based interactions with the acid well. Depletes 1 charge if there are any to extinguish all fires in the turf while producing acid smoke.
-/obj/structure/xeno/acidwell/proc/acid_well_fire_interaction()
- if(!charges)
- take_damage(50, BURN, FIRE)
- return
-
- charges--
- update_icon()
- var/turf/T = get_turf(src)
- var/datum/effect_system/smoke_spread/xeno/acid/extuingishing/acid_smoke = new(T) //spawn acid smoke when charges are actually used
- acid_smoke.set_up(0, src) //acid smoke in the immediate vicinity
- acid_smoke.start()
-
- for(var/obj/flamer_fire/F in T) //Extinguish all flames in turf
- qdel(F)
-
-/obj/structure/xeno/acidwell/attackby(obj/item/I, mob/user, params)
- if(!isxeno(user))
- return ..()
- attack_alien(user)
-
-/obj/structure/xeno/acidwell/attack_alien(mob/living/carbon/xenomorph/xeno_attacker, damage_amount = xeno_attacker.xeno_caste.melee_damage, damage_type = BRUTE, damage_flag = MELEE, effects = TRUE, armor_penetration = 0, isrightclick = FALSE)
- if(xeno_attacker.a_intent == INTENT_HARM && (CHECK_BITFIELD(xeno_attacker.xeno_caste.caste_flags, CASTE_IS_BUILDER) || xeno_attacker == creator) ) //If we're a builder caste or the creator and we're on harm intent, deconstruct it.
- balloon_alert(xeno_attacker, "Removing...")
- if(!do_after(xeno_attacker, XENO_ACID_WELL_FILL_TIME, IGNORE_HELD_ITEM, src, BUSY_ICON_HOSTILE))
- balloon_alert(xeno_attacker, "Stopped removing")
- return
- playsound(src, "alien_resin_break", 25)
- deconstruct(TRUE, xeno_attacker)
- return
-
- if(charges >= 5)
- balloon_alert(xeno_attacker, "Already full")
- return
- if(charging)
- balloon_alert(xeno_attacker, "Already being filled")
- return
-
- if(xeno_attacker.plasma_stored < XENO_ACID_WELL_FILL_COST) //You need to have enough plasma to attempt to fill the well
- balloon_alert(xeno_attacker, "Need [XENO_ACID_WELL_FILL_COST - xeno_attacker.plasma_stored] more plasma")
- return
-
- charging = TRUE
-
- balloon_alert(xeno_attacker, "Refilling...")
- if(!do_after(xeno_attacker, XENO_ACID_WELL_FILL_TIME, IGNORE_HELD_ITEM, src, BUSY_ICON_BUILD))
- charging = FALSE
- balloon_alert(xeno_attacker, "Aborted refilling")
- return
-
- if(xeno_attacker.plasma_stored < XENO_ACID_WELL_FILL_COST)
- charging = FALSE
- balloon_alert(xeno_attacker, "Need [XENO_ACID_WELL_FILL_COST - xeno_attacker.plasma_stored] more plasma")
- return
-
- xeno_attacker.plasma_stored -= XENO_ACID_WELL_FILL_COST
- charges++
- charging = FALSE
- update_icon()
- balloon_alert(xeno_attacker, "Now has [charges] / [XENO_ACID_WELL_MAX_CHARGES] charges")
- to_chat(xeno_attacker,span_xenonotice("We add acid to [src]. It is currently has [charges] / [XENO_ACID_WELL_MAX_CHARGES] charges.") )
-
-/obj/structure/xeno/acidwell/proc/on_cross(datum/source, atom/movable/A, oldloc, oldlocs)
- SIGNAL_HANDLER
- if(CHECK_MULTIPLE_BITFIELDS(A.allow_pass_flags, HOVERING))
- return
- if(iscarbon(A))
- HasProximity(A)
-
-/obj/structure/xeno/acidwell/HasProximity(atom/movable/AM)
- if(!charges)
- return
- if(!isliving(AM))
- return
- var/mob/living/stepper = AM
- if(stepper.stat == DEAD)
- return
-
- var/charges_used = 0
-
- for(var/obj/item/explosive/grenade/sticky/sticky_bomb in stepper.contents)
- if(charges_used >= charges)
- break
- if(sticky_bomb.stuck_to == stepper)
- sticky_bomb.clean_refs()
- sticky_bomb.forceMove(loc) // i'm not sure if this is even needed, but just to prevent possible bugs
- visible_message(span_danger("[src] sizzles as [sticky_bomb] melts down in the acid."))
- qdel(sticky_bomb)
- charges_used ++
-
- if(stepper.on_fire && (charges_used < charges))
- stepper.ExtinguishMob()
- charges_used ++
-
- if(!isxeno(stepper))
- stepper.next_move_slowdown += charges * 2 //Acid spray has slow down so this should too; scales with charges, Min 2 slowdown, Max 10
- stepper.apply_damage(charges * 10, BURN, BODY_ZONE_PRECISE_L_FOOT, ACID, penetration = 33)
- stepper.apply_damage(charges * 10, BURN, BODY_ZONE_PRECISE_R_FOOT, ACID, penetration = 33)
- stepper.visible_message(span_danger("[stepper] is immersed in [src]'s acid!") , \
- span_danger("We are immersed in [src]'s acid!") , null, 5)
- playsound(stepper, "sound/bullets/acid_impact1.ogg", 10 * charges)
- new /obj/effect/temp_visual/acid_bath(get_turf(stepper))
- charges_used = charges //humans stepping on it empties it out
-
- if(!charges_used)
- return
-
- var/datum/effect_system/smoke_spread/xeno/acid/extuingishing/acid_smoke
- acid_smoke = new(get_turf(stepper)) //spawn acid smoke when charges are actually used
- acid_smoke.set_up(0, src) //acid smoke in the immediate vicinity
- acid_smoke.start()
-
- charges -= charges_used
- update_icon()
-
-/obj/structure/xeno/resin_jelly_pod
- name = "Resin jelly pod"
- desc = "A large resin pod. Inside is a thick, viscous fluid that looks like it doesnt burn easily."
- icon = 'icons/Xeno/resinpod.dmi'
- icon_state = "resinpod"
- density = FALSE
- opacity = FALSE
- anchored = TRUE
- max_integrity = 250
- layer = RESIN_STRUCTURE_LAYER
- pixel_x = -16
- pixel_y = -16
- xeno_structure_flags = IGNORE_WEED_REMOVAL
-
- hit_sound = "alien_resin_move"
- destroy_sound = "alien_resin_move"
- ///How many actual jellies the pod has stored
- var/chargesleft = 0
- ///Max amount of jellies the pod can hold
- var/maxcharges = 10
- ///Every 5 times this number seconds we will create a jelly
- var/recharge_rate = 10
- ///Countdown to the next time we generate a jelly
- var/nextjelly = 0
-
-/obj/structure/xeno/resin_jelly_pod/Initialize(mapload, _hivenumber)
- . = ..()
- add_overlay(image(icon, "resinpod_inside", layer + 0.01, dir))
- START_PROCESSING(SSslowprocess, src)
-
-/obj/structure/xeno/resin_jelly_pod/Destroy()
- STOP_PROCESSING(SSslowprocess, src)
- return ..()
-
-/obj/structure/xeno/resin_jelly_pod/examine(mob/user, distance, infix, suffix)
- . = ..()
- if(isxeno(user))
- . += "It has [chargesleft] jelly globules remaining[datum_flags & DF_ISPROCESSING ? ", and will create a new jelly in [(recharge_rate-nextjelly)*5] seconds": " and seems latent"]."
-
-/obj/structure/xeno/resin_jelly_pod/process()
- if(nextjelly <= recharge_rate)
- nextjelly++
- return
- nextjelly = 0
- chargesleft++
- if(chargesleft >= maxcharges)
- return PROCESS_KILL
-
-/obj/structure/xeno/resin_jelly_pod/attack_alien(mob/living/carbon/xenomorph/xeno_attacker, damage_amount = xeno_attacker.xeno_caste.melee_damage, damage_type = BRUTE, damage_flag = MELEE, effects = TRUE, armor_penetration = 0, isrightclick = FALSE)
- if(xeno_attacker.status_flags & INCORPOREAL)
- return FALSE
-
- if((xeno_attacker.a_intent == INTENT_HARM && isxenohivelord(xeno_attacker)) || xeno_attacker.hivenumber != hivenumber)
- balloon_alert(xeno_attacker, "Destroying...")
- if(do_after(xeno_attacker, HIVELORD_TUNNEL_DISMANTLE_TIME, IGNORE_HELD_ITEM, src, BUSY_ICON_BUILD))
- deconstruct(FALSE)
- return
-
- if(!chargesleft)
- balloon_alert(xeno_attacker, "No jelly remaining")
- to_chat(xeno_attacker, span_xenonotice("We reach into \the [src], but only find dregs of resin. We should wait some more.") )
- return
- balloon_alert(xeno_attacker, "Retrieved jelly")
- new /obj/item/resin_jelly(loc)
- chargesleft--
- if(!(datum_flags & DF_ISPROCESSING) && (chargesleft < maxcharges))
- START_PROCESSING(SSslowprocess, src)
-
-/obj/structure/xeno/silo
- name = "Resin silo"
- icon = 'icons/Xeno/resin_silo.dmi'
- icon_state = "weed_silo"
- desc = "A slimy, oozy resin bed filled with foul-looking egg-like ...things."
- bound_width = 96
- bound_height = 96
- max_integrity = 1000
- resistance_flags = UNACIDABLE | DROPSHIP_IMMUNE | PLASMACUTTER_IMMUNE
- xeno_structure_flags = IGNORE_WEED_REMOVAL|CRITICAL_STRUCTURE
- plane = FLOOR_PLANE
- ///How many larva points one silo produce in one minute
- var/larva_spawn_rate = 0.5
- var/turf/center_turf
- var/number_silo
- ///For minimap icon change if silo takes damage or nearby hostile
- var/warning
- COOLDOWN_DECLARE(silo_damage_alert_cooldown)
- COOLDOWN_DECLARE(silo_proxy_alert_cooldown)
-
-/obj/structure/xeno/silo/Initialize(mapload, _hivenumber)
- . = ..()
- center_turf = get_step(src, NORTHEAST)
- if(!istype(center_turf))
- center_turf = loc
-
- if(SSticker.mode?.flags_round_type & MODE_SILO_RESPAWN)
- for(var/turfs in RANGE_TURFS(XENO_SILO_DETECTION_RANGE, src))
- RegisterSignal(turfs, COMSIG_ATOM_ENTERED, PROC_REF(resin_silo_proxy_alert))
-
- if(SSticker.mode?.flags_round_type & MODE_SILOS_SPAWN_MINIONS)
- SSspawning.registerspawner(src, INFINITY, GLOB.xeno_ai_spawnable, 0, 0, null)
- SSspawning.spawnerdata[src].required_increment = 2 * max(45 SECONDS, 3 MINUTES - SSmonitor.maximum_connected_players_count * SPAWN_RATE_PER_PLAYER)/SSspawning.wait
- SSspawning.spawnerdata[src].max_allowed_mobs = max(1, MAX_SPAWNABLE_MOB_PER_PLAYER * SSmonitor.maximum_connected_players_count * 0.5)
- update_minimap_icon()
-
- return INITIALIZE_HINT_LATELOAD
-
-
-/obj/structure/xeno/silo/LateInitialize()
- . = ..()
- var/siloprefix = GLOB.hive_datums[hivenumber].name
- number_silo = length(GLOB.xeno_resin_silos_by_hive[hivenumber]) + 1
- name = "[siloprefix == "Normal" ? "" : "[siloprefix] "][name] [number_silo]"
- LAZYADDASSOC(GLOB.xeno_resin_silos_by_hive, hivenumber, src)
-
- if(!locate(/obj/alien/weeds) in center_turf)
- new /obj/alien/weeds/node(center_turf)
- if(GLOB.hive_datums[hivenumber])
- RegisterSignals(GLOB.hive_datums[hivenumber], list(COMSIG_HIVE_XENO_MOTHER_PRE_CHECK, COMSIG_HIVE_XENO_MOTHER_CHECK), PROC_REF(is_burrowed_larva_host))
- if(length(GLOB.xeno_resin_silos_by_hive[hivenumber]) == 1)
- GLOB.hive_datums[hivenumber].give_larva_to_next_in_queue()
- var/turf/tunnel_turf = get_step(center_turf, NORTH)
- if(tunnel_turf.can_dig_xeno_tunnel())
- var/obj/structure/xeno/tunnel/newt = new(tunnel_turf, hivenumber)
- newt.tunnel_desc = "[AREACOORD_NO_Z(newt)]"
- newt.name += " [name]"
- if(GLOB.hive_datums[hivenumber])
- SSticker.mode.update_silo_death_timer(GLOB.hive_datums[hivenumber])
-
-/obj/structure/xeno/silo/obj_destruction(damage_amount, damage_type, damage_flag)
- if(GLOB.hive_datums[hivenumber])
- INVOKE_NEXT_TICK(SSticker.mode, TYPE_PROC_REF(/datum/game_mode, update_silo_death_timer), GLOB.hive_datums[hivenumber]) // checks all silos next tick after this one is gone
- UnregisterSignal(GLOB.hive_datums[hivenumber], list(COMSIG_HIVE_XENO_MOTHER_PRE_CHECK, COMSIG_HIVE_XENO_MOTHER_CHECK))
- GLOB.hive_datums[hivenumber].xeno_message("A resin silo has been destroyed at [AREACOORD_NO_Z(src)]!", "xenoannounce", 5, FALSE,src.loc, 'sound/voice/alien_help2.ogg',FALSE , null, /atom/movable/screen/arrow/silo_damaged_arrow)
- notify_ghosts("\ A resin silo has been destroyed at [AREACOORD_NO_Z(src)]!", source = get_turf(src), action = NOTIFY_JUMP)
- playsound(loc,'sound/effects/alien_egg_burst.ogg', 75)
- return ..()
-
-/obj/structure/xeno/silo/Destroy()
- GLOB.xeno_resin_silos_by_hive[hivenumber] -= src
-
- for(var/i in contents)
- var/atom/movable/AM = i
- AM.forceMove(get_step(center_turf, pick(CARDINAL_ALL_DIRS)))
- center_turf = null
-
- STOP_PROCESSING(SSslowprocess, src)
- return ..()
-
-/obj/structure/xeno/silo/examine(mob/user)
- . = ..()
- var/current_integrity = (obj_integrity / max_integrity) * 100
- switch(current_integrity)
- if(0 to 20)
- . += span_warning("It's barely holding, there's leaking oozes all around, and most eggs are broken. Yet it is not inert.")
- if(20 to 40)
- . += span_warning("It looks severely damaged, its movements slow.")
- if(40 to 60)
- . += span_warning("It's quite beat up, but it seems alive.")
- if(60 to 80)
- . += span_warning("It's slightly damaged, but still seems healthy.")
- if(80 to 100)
- . += span_info("It appears in good shape, pulsating healthily.")
-
-
-/obj/structure/xeno/silo/take_damage(damage_amount, damage_type, damage_flag, sound_effect, attack_dir, armour_penetration)
- . = ..()
-
- //We took damage, so it's time to start regenerating if we're not already processing
- if(!CHECK_BITFIELD(datum_flags, DF_ISPROCESSING))
- START_PROCESSING(SSslowprocess, src)
-
- resin_silo_damage_alert()
-
-/obj/structure/xeno/silo/proc/resin_silo_damage_alert()
- if(!COOLDOWN_CHECK(src, silo_damage_alert_cooldown))
- return
- warning = TRUE
- update_minimap_icon()
- GLOB.hive_datums[hivenumber].xeno_message("Our [name] at [AREACOORD_NO_Z(src)] is under attack! It has [obj_integrity]/[max_integrity] Health remaining.", "xenoannounce", 5, FALSE, src, 'sound/voice/alien_help1.ogg',FALSE, null, /atom/movable/screen/arrow/silo_damaged_arrow)
- COOLDOWN_START(src, silo_damage_alert_cooldown, XENO_SILO_HEALTH_ALERT_COOLDOWN) //set the cooldown.
- addtimer(CALLBACK(src, PROC_REF(clear_warning)), XENO_SILO_HEALTH_ALERT_COOLDOWN) //clear warning
-
-///Alerts the Hive when hostiles get too close to their resin silo
-/obj/structure/xeno/silo/proc/resin_silo_proxy_alert(datum/source, atom/movable/hostile, direction)
- SIGNAL_HANDLER
-
- if(!COOLDOWN_CHECK(src, silo_proxy_alert_cooldown)) //Proxy alert triggered too recently; abort
- return
-
- if(!isliving(hostile))
- return
-
- var/mob/living/living_triggerer = hostile
- if(living_triggerer.stat == DEAD) //We don't care about the dead
- return
-
- if(isxeno(hostile))
- var/mob/living/carbon/xenomorph/X = hostile
- if(X.hive == GLOB.hive_datums[hivenumber]) //Trigger proxy alert only for hostile xenos
- return
-
- warning = TRUE
- update_minimap_icon()
- GLOB.hive_datums[hivenumber].xeno_message("Our [name] has detected a nearby hostile [hostile] at [get_area(hostile)] (X: [hostile.x], Y: [hostile.y]).", "xenoannounce", 5, FALSE, hostile, 'sound/voice/alien_help1.ogg', FALSE, null, /atom/movable/screen/arrow/leader_tracker_arrow)
- COOLDOWN_START(src, silo_proxy_alert_cooldown, XENO_SILO_DETECTION_COOLDOWN) //set the cooldown.
- addtimer(CALLBACK(src, PROC_REF(clear_warning)), XENO_SILO_DETECTION_COOLDOWN) //clear warning
-
-///Clears the warning for minimap if its warning for hostiles
-/obj/structure/xeno/silo/proc/clear_warning()
- warning = FALSE
- update_minimap_icon()
-
-/obj/structure/xeno/silo/process()
- //Regenerate if we're at less than max integrity
- if(obj_integrity < max_integrity)
- obj_integrity = min(obj_integrity + 25, max_integrity) //Regen 5 HP per sec
-
-/obj/structure/xeno/silo/proc/is_burrowed_larva_host(datum/source, list/mothers, list/silos)
- SIGNAL_HANDLER
- if(GLOB.hive_datums[hivenumber])
- silos += src
-
-///Change minimap icon if silo is under attack or not
-/obj/structure/xeno/silo/proc/update_minimap_icon()
- SSminimaps.remove_marker(src)
- SSminimaps.add_marker(src, MINIMAP_FLAG_XENO, image('icons/UI_icons/map_blips.dmi', null, "silo[warning ? "_warn" : "_passive"]", VERY_HIGH_FLOAT_LAYER)) // RU TGMC edit - map blips
-
-/obj/structure/xeno/silo/crash
- resistance_flags = UNACIDABLE | DROPSHIP_IMMUNE | PLASMACUTTER_IMMUNE | INDESTRUCTIBLE
-
-/obj/structure/xeno/xeno_turret
- icon = 'icons/Xeno/acidturret.dmi'
- icon_state = XENO_TURRET_ACID_ICONSTATE
- name = "acid turret"
- desc = "A menacing looking construct of resin, it seems to be alive. It fires acid against intruders."
- bound_width = 32
- bound_height = 32
- obj_integrity = 600
- max_integrity = 1500
- layer = ABOVE_MOB_LAYER
- density = TRUE
- resistance_flags = UNACIDABLE | DROPSHIP_IMMUNE |PORTAL_IMMUNE
- xeno_structure_flags = IGNORE_WEED_REMOVAL|HAS_OVERLAY
- allow_pass_flags = PASS_AIR|PASS_THROW
- ///What kind of spit it uses
- var/datum/ammo/ammo = /datum/ammo/xeno/acid/heavy/turret
- ///Range of the turret
- var/range = 7
- ///Target of the turret
- var/atom/hostile
- ///Last target of the turret
- var/atom/last_hostile
- ///Potential list of targets found by scan
- var/list/atom/potential_hostiles
- ///Fire rate of the target in ticks
- var/firerate = 5
- ///The last time the sentry did a scan
- var/last_scan_time
- ///light color that gets set in initialize
- var/light_initial_color = LIGHT_COLOR_GREEN
- ///For minimap icon change if sentry is firing
- var/firing
-
-///Change minimap icon if its firing or not firing
-/obj/structure/xeno/xeno_turret/proc/update_minimap_icon()
- SSminimaps.remove_marker(src)
- SSminimaps.add_marker(src, MINIMAP_FLAG_XENO, image('icons/UI_icons/map_blips.dmi', null, "xeno_turret[firing ? "_firing" : "_passive"]")) // RU TGMC edit - map blips
-
-/obj/structure/xeno/xeno_turret/Initialize(mapload, _hivenumber)
- . = ..()
- ammo = GLOB.ammo_list[ammo]
- potential_hostiles = list()
- LAZYADDASSOC(GLOB.xeno_resin_turrets_by_hive, hivenumber, src)
- START_PROCESSING(SSobj, src)
- AddComponent(/datum/component/automatedfire/xeno_turret_autofire, firerate)
- RegisterSignal(src, COMSIG_AUTOMATIC_SHOOTER_SHOOT, PROC_REF(shoot))
- RegisterSignal(SSdcs, COMSIG_GLOB_DROPSHIP_HIJACKED, PROC_REF(destroy_on_hijack))
- if(light_initial_color)
- set_light(2, 2, light_initial_color)
- update_minimap_icon()
- update_icon()
-
-///Signal handler to delete the turret when the alamo is hijacked
-/obj/structure/xeno/xeno_turret/proc/destroy_on_hijack()
- SIGNAL_HANDLER
- qdel(src)
-
-/obj/structure/xeno/xeno_turret/obj_destruction(damage_amount, damage_type, damage_flag)
- if(damage_amount) //Spawn effects only if we actually get destroyed by damage
- on_destruction()
- return ..()
-
-/obj/structure/xeno/xeno_turret/proc/on_destruction()
- var/datum/effect_system/smoke_spread/xeno/smoke = new /datum/effect_system/smoke_spread/xeno/acid(src)
- smoke.set_up(1, get_turf(src))
- smoke.start()
-
-/obj/structure/xeno/xeno_turret/Destroy()
- GLOB.xeno_resin_turrets_by_hive[hivenumber] -= src
- set_hostile(null)
- set_last_hostile(null)
- STOP_PROCESSING(SSobj, src)
- playsound(loc,'sound/effects/xeno_turret_death.ogg', 70)
- return ..()
-
-/obj/structure/xeno/xeno_turret/ex_act(severity)
- switch(severity)
- if(EXPLODE_DEVASTATE)
- take_damage(1500, BRUTE, BOMB)
- if(EXPLODE_HEAVY)
- take_damage(750, BRUTE, BOMB)
- if(EXPLODE_LIGHT)
- take_damage(300, BRUTE, BOMB)
-
-/obj/structure/xeno/xeno_turret/flamer_fire_act(burnlevel)
- take_damage(burnlevel * 2, BURN, FIRE)
- ENABLE_BITFIELD(resistance_flags, ON_FIRE)
-
-/obj/structure/xeno/xeno_turret/fire_act()
- take_damage(60, BURN, FIRE)
- ENABLE_BITFIELD(resistance_flags, ON_FIRE)
-
-/obj/structure/xeno/xeno_turret/update_overlays()
- . = ..()
- if(!(xeno_structure_flags & HAS_OVERLAY))
- return
- if(obj_integrity <= max_integrity / 2)
- . += image('icons/Xeno/acidturret.dmi', src, "+turret_damage")
- if(CHECK_BITFIELD(resistance_flags, ON_FIRE))
- . += image('icons/Xeno/acidturret.dmi', src, "+turret_on_fire")
-
-/obj/structure/xeno/xeno_turret/process()
- //Turrets regen some HP, every 2 sec
- if(obj_integrity < max_integrity)
- obj_integrity = min(obj_integrity + TURRET_HEALTH_REGEN, max_integrity)
- update_icon()
- DISABLE_BITFIELD(resistance_flags, ON_FIRE)
- if(world.time > last_scan_time + TURRET_SCAN_FREQUENCY)
- scan()
- last_scan_time = world.time
- if(!length(potential_hostiles))
- return
- set_hostile(get_target())
- if (!hostile)
- if(last_hostile)
- set_last_hostile(null)
- return
- if(!TIMER_COOLDOWN_CHECK(src, COOLDOWN_XENO_TURRETS_ALERT))
- GLOB.hive_datums[hivenumber].xeno_message("Our [name] is attacking a nearby hostile [hostile] at [get_area(hostile)] (X: [hostile.x], Y: [hostile.y]).", "xenoannounce", 5, FALSE, hostile, 'sound/voice/alien_help1.ogg', FALSE, null, /atom/movable/screen/arrow/turret_attacking_arrow)
- TIMER_COOLDOWN_START(src, COOLDOWN_XENO_TURRETS_ALERT, 20 SECONDS)
- if(hostile != last_hostile)
- set_last_hostile(hostile)
- SEND_SIGNAL(src, COMSIG_AUTOMATIC_SHOOTER_START_SHOOTING_AT)
-
-/obj/structure/xeno/xeno_turret/attackby(obj/item/I, mob/living/user, params)
- if(I.flags_item & NOBLUDGEON || !isliving(user))
- return attack_hand(user)
-
- user.changeNext_move(I.attack_speed)
- user.do_attack_animation(src, used_item = I)
-
- var/damage = I.force
- var/multiplier = 1
- if(I.damtype == BURN) //Burn damage deals extra vs resin structures (mostly welders).
- multiplier += 1
-
- if(istype(I, /obj/item/tool/pickaxe/plasmacutter) && !user.do_actions)
- var/obj/item/tool/pickaxe/plasmacutter/P = I
- if(P.start_cut(user, name, src, PLASMACUTTER_BASE_COST * PLASMACUTTER_VLOW_MOD))
- multiplier += PLASMACUTTER_RESIN_MULTIPLIER
- P.cut_apart(user, name, src, PLASMACUTTER_BASE_COST * PLASMACUTTER_VLOW_MOD)
-
- damage *= max(0, multiplier)
- take_damage(damage, BRUTE, MELEE)
- playsound(src, "alien_resin_break", 25)
-
-///Signal handler for hard del of hostile
-/obj/structure/xeno/xeno_turret/proc/unset_hostile()
- SIGNAL_HANDLER
- hostile = null
-
-///Signal handler for hard del of last_hostile
-/obj/structure/xeno/xeno_turret/proc/unset_last_hostile()
- SIGNAL_HANDLER
- last_hostile = null
-
-///Setter for hostile with hard del in mind
-/obj/structure/xeno/xeno_turret/proc/set_hostile(_hostile)
- if(hostile != _hostile)
- hostile = _hostile
- RegisterSignal(hostile, COMSIG_QDELETING, PROC_REF(unset_hostile))
-
-///Setter for last_hostile with hard del in mind
-/obj/structure/xeno/xeno_turret/proc/set_last_hostile(_last_hostile)
- if(last_hostile)
- UnregisterSignal(last_hostile, COMSIG_QDELETING)
- last_hostile = _last_hostile
-
-///Look for the closest human in range and in light of sight. If no human is in range, will look for xenos of other hives
-/obj/structure/xeno/xeno_turret/proc/get_target()
- var/distance = range + 0.5 //we add 0.5 so if a potential target is at range, it is accepted by the system
- var/buffer_distance
- var/list/turf/path = list()
- for (var/atom/nearby_hostile AS in potential_hostiles)
- if(isliving(nearby_hostile))
- var/mob/living/nearby_living_hostile = nearby_hostile
- if(nearby_living_hostile.stat == DEAD)
- continue
- if(HAS_TRAIT(nearby_hostile, TRAIT_TURRET_HIDDEN))
- continue
- buffer_distance = get_dist(nearby_hostile, src)
- if (distance <= buffer_distance) //If we already found a target that's closer
- continue
- path = getline(src, nearby_hostile)
- path -= get_turf(src)
- if(!length(path)) //Can't shoot if it's on the same turf
- continue
- var/blocked = FALSE
- for(var/turf/T AS in path)
- if(IS_OPAQUE_TURF(T) || T.density && !(T.allow_pass_flags & PASS_PROJECTILE))
- blocked = TRUE
- break //LoF Broken; stop checking; we can't proceed further.
-
- for(var/obj/machinery/MA in T)
- if(MA.opacity || MA.density && !(MA.allow_pass_flags & PASS_PROJECTILE))
- blocked = TRUE
- break //LoF Broken; stop checking; we can't proceed further.
-
- for(var/obj/structure/S in T)
- if(S.opacity || S.density && !(S.allow_pass_flags & PASS_PROJECTILE))
- blocked = TRUE
- break //LoF Broken; stop checking; we can't proceed further.
- if(!blocked)
- distance = buffer_distance
- . = nearby_hostile
-
-///Return TRUE if a possible target is near
-/obj/structure/xeno/xeno_turret/proc/scan()
- potential_hostiles.Cut()
- for (var/mob/living/carbon/human/nearby_human AS in cheap_get_humans_near(src, TURRET_SCAN_RANGE))
- if(nearby_human.stat == DEAD)
- continue
- if(nearby_human.get_xeno_hivenumber() == hivenumber)
- continue
- potential_hostiles += nearby_human
- for (var/mob/living/carbon/xenomorph/nearby_xeno AS in cheap_get_xenos_near(src, range))
- if(GLOB.hive_datums[hivenumber] == nearby_xeno.hive)
- continue
- if(nearby_xeno.stat == DEAD)
- continue
- potential_hostiles += nearby_xeno
- for(var/obj/vehicle/unmanned/vehicle AS in GLOB.unmanned_vehicles)
- if(vehicle.z == z && get_dist(vehicle, src) <= range)
- potential_hostiles += vehicle
- for(var/obj/vehicle/sealed/mecha/mech AS in GLOB.mechas_list)
- if(mech.z == z && get_dist(mech, src) <= range)
- potential_hostiles += mech
-
-///Signal handler to make the turret shoot at its target
-/obj/structure/xeno/xeno_turret/proc/shoot()
- SIGNAL_HANDLER
- if(!hostile)
- SEND_SIGNAL(src, COMSIG_AUTOMATIC_SHOOTER_STOP_SHOOTING_AT)
- firing = FALSE
- update_minimap_icon()
- return
- face_atom(hostile)
- var/obj/projectile/newshot = new(loc)
- newshot.generate_bullet(ammo)
- newshot.def_zone = pick(GLOB.base_miss_chance)
- newshot.fire_at(hostile, src, null, ammo.max_range, ammo.shell_speed)
- if(istype(ammo, /datum/ammo/xeno/hugger))
- var/datum/ammo/xeno/hugger/hugger_ammo = ammo
- newshot.color = initial(hugger_ammo.hugger_type.color)
- hugger_ammo.hivenumber = hivenumber
- firing = TRUE
- update_minimap_icon()
-
-/obj/structure/xeno/xeno_turret/sticky
- name = "Sticky resin turret"
- icon = 'icons/Xeno/acidturret.dmi'
- icon_state = XENO_TURRET_STICKY_ICONSTATE
- desc = "A menacing looking construct of resin, it seems to be alive. It fires resin against intruders."
- light_initial_color = LIGHT_COLOR_PURPLE
- ammo = /datum/ammo/xeno/sticky/turret
- firerate = 5
-
-/obj/structure/xeno/xeno_turret/sticky/on_destruction()
- for(var/i = 1 to 20) // maybe a bit laggy
- var/obj/projectile/new_proj = new(src)
- new_proj.generate_bullet(ammo)
- new_proj.fire_at(null, src, range = rand(1, 4), angle = rand(1, 360), recursivity = TRUE)
-
-/obj/structure/xeno/xeno_turret/hugger_turret
- name = "hugger turret"
- icon_state = "hugger_turret"
- desc = "A menacing looking construct of resin, it seems to be alive. It fires huggers against intruders."
- obj_integrity = 400
- max_integrity = 400
- light_initial_color = LIGHT_COLOR_BROWN
- ammo = /datum/ammo/xeno/hugger
- firerate = 5 SECONDS
-
-/obj/structure/xeno/xeno_turret/hugger_turret/on_destruction()
- for(var/i = 1 to 5)
- var/obj/projectile/new_proj = new(src)
- new_proj.generate_bullet(ammo)
- new_proj.fire_at(null, src, range = rand(1, 3), angle = rand(1, 360), recursivity = TRUE)
-
-/obj/structure/xeno/evotower
- name = "evolution tower"
- desc = "A sickly outcrop from the ground. It seems to ooze a strange chemical that shimmers and warps the ground around it."
- icon = 'icons/Xeno/2x2building.dmi'
- icon_state = "evotower"
- bound_width = 64
- bound_height = 64
- obj_integrity = 600
- max_integrity = 600
- xeno_structure_flags = CRITICAL_STRUCTURE
- ///boost amt to be added per tower per cycle
- var/boost_amount = 0.2
-
-/obj/structure/xeno/evotower/Initialize(mapload, _hivenumber)
- . = ..()
- GLOB.hive_datums[hivenumber].evotowers += src
- set_light(2, 2, LIGHT_COLOR_GREEN)
- SSminimaps.add_marker(src, MINIMAP_FLAG_XENO, image('icons/UI_icons/map_blips.dmi', null, "tower"))
-
-/obj/structure/xeno/evotower/Destroy()
- GLOB.hive_datums[hivenumber].evotowers -= src
- return ..()
-
-/obj/structure/xeno/evotower/ex_act(severity)
- take_damage(severity * 2.5, BRUTE, BOMB)
-
-/obj/structure/xeno/psychictower
- name = "Psychic Relay"
- desc = "A sickly outcrop from the ground. It seems to allow for more advanced growth of the Xenomorphs."
- icon = 'icons/Xeno/2x2building.dmi'
- icon_state = "maturitytower"
- bound_width = 64
- bound_height = 64
- obj_integrity = 400
- max_integrity = 400
- xeno_structure_flags = CRITICAL_STRUCTURE
-
-/obj/structure/xeno/psychictower/Initialize(mapload, _hivenumber)
- . = ..()
- GLOB.hive_datums[hivenumber].psychictowers += src
- set_light(2, 2, LIGHT_COLOR_GREEN)
- SSminimaps.add_marker(src, MINIMAP_FLAG_XENO, image('icons/UI_icons/map_blips.dmi', null, "tower"))
-
-/obj/structure/xeno/psychictower/Destroy()
- GLOB.hive_datums[hivenumber].psychictowers -= src
- return ..()
-
-/obj/structure/xeno/psychictower/ex_act(severity)
- take_damage(severity * 2.5, BRUTE, BOMB)
-
-/obj/structure/xeno/pherotower
- name = "Pheromone tower"
- desc = "A resin formation that looks like a small pillar. A faint, weird smell can be perceived from it."
- icon = 'icons/Xeno/1x1building.dmi'
- icon_state = "recoverytower"
- bound_width = 32
- bound_height = 32
- obj_integrity = 400
- max_integrity = 400
- xeno_structure_flags = CRITICAL_STRUCTURE
- ///The type of pheromone currently being emitted.
- var/datum/aura_bearer/current_aura
- ///Strength of pheromones given by this tower.
- var/aura_strength = 5
- ///Radius (in tiles) of the pheromones given by this tower.
- var/aura_radius = 32
-
-/obj/structure/xeno/pherotower/Initialize(mapload, _hivenumber)
- . = ..()
- SSminimaps.add_marker(src, MINIMAP_FLAG_XENO, image('icons/UI_icons/map_blips.dmi', null, "phero", ABOVE_FLOAT_LAYER)) // RU TGMC edit - map blips
- GLOB.hive_datums[hivenumber].pherotowers += src
-
-//Pheromone towers start off with recovery.
- current_aura = SSaura.add_emitter(src, AURA_XENO_RECOVERY, aura_radius, aura_strength, -1, FACTION_XENO, hivenumber)
- playsound(src, "alien_drool", 25)
- update_icon()
-
-/obj/structure/xeno/pherotower/ex_act(severity)
- take_damage(severity * 2.5, BRUTE, BOMB)
-
-/obj/structure/xeno/pherotower/Destroy()
- GLOB.hive_datums[hivenumber].pherotowers -= src
- return ..()
-
-// Clicking on the tower brings up a radial menu that allows you to select the type of pheromone that this tower will emit.
-/obj/structure/xeno/pherotower/attack_alien(mob/living/carbon/xenomorph/xeno_attacker, damage_amount = xeno_attacker.xeno_caste.melee_damage, damage_type = BRUTE, damage_flag = MELEE, effects = TRUE, armor_penetration = 0, isrightclick = FALSE)
- var/phero_choice = show_radial_menu(xeno_attacker, src, GLOB.pheromone_images_list, radius = 35, require_near = TRUE)
-
- if(!phero_choice)
- return
-
- QDEL_NULL(current_aura)
- current_aura = SSaura.add_emitter(src, phero_choice, aura_radius, aura_strength, -1, FACTION_XENO, hivenumber)
- balloon_alert(xeno_attacker, "[phero_choice]")
- playsound(src, "alien_drool", 25)
- update_icon()
-
-/obj/structure/xeno/pherotower/update_icon_state()
- . = ..()
- switch(current_aura.aura_types[1])
- if(AURA_XENO_RECOVERY)
- icon_state = "recoverytower"
- set_light(2, 2, LIGHT_COLOR_BLUE)
- if(AURA_XENO_WARDING)
- icon_state = "wardingtower"
- set_light(2, 2, LIGHT_COLOR_GREEN)
- if(AURA_XENO_FRENZY)
- icon_state = "frenzytower"
- set_light(2, 2, LIGHT_COLOR_RED)
-
-/obj/structure/xeno/pherotower/crash
- name = "Recovery tower"
- resistance_flags = RESIST_ALL
- xeno_structure_flags = IGNORE_WEED_REMOVAL | CRITICAL_STRUCTURE
-
-/obj/structure/xeno/pherotower/crash/attack_alien(isrightclick = FALSE)
- return
-
-/obj/structure/xeno/spawner
- icon = 'icons/Xeno/2x2building.dmi.dmi'
- bound_width = 64
- bound_height = 64
- plane = FLOOR_PLANE
- name = "spawner"
- desc = "A slimy, oozy resin bed filled with foul-looking egg-like ...things."
- icon_state = "spawner"
- max_integrity = 500
- resistance_flags = UNACIDABLE | DROPSHIP_IMMUNE
- xeno_structure_flags = IGNORE_WEED_REMOVAL | CRITICAL_STRUCTURE
- ///For minimap icon change if silo takes damage or nearby hostile
- var/warning
- COOLDOWN_DECLARE(spawner_damage_alert_cooldown)
- COOLDOWN_DECLARE(spawner_proxy_alert_cooldown)
- var/linked_minions = list()
-
-/obj/structure/xeno/spawner/Initialize(mapload, _hivenumber)
- . = ..()
- LAZYADDASSOC(GLOB.xeno_spawners_by_hive, hivenumber, src)
- SSspawning.registerspawner(src, INFINITY, GLOB.xeno_ai_spawnable, 0, 0, CALLBACK(src, PROC_REF(on_spawn)))
- SSspawning.spawnerdata[src].required_increment = max(45 SECONDS, 3 MINUTES - SSmonitor.maximum_connected_players_count * SPAWN_RATE_PER_PLAYER)/SSspawning.wait
- SSspawning.spawnerdata[src].max_allowed_mobs = max(2, MAX_SPAWNABLE_MOB_PER_PLAYER * SSmonitor.maximum_connected_players_count)
- set_light(2, 2, LIGHT_COLOR_GREEN)
- for(var/turfs in RANGE_TURFS(XENO_SILO_DETECTION_RANGE, src))
- RegisterSignal(turfs, COMSIG_ATOM_ENTERED, PROC_REF(spawner_proxy_alert))
- update_minimap_icon()
-
-/obj/structure/xeno/spawner/examine(mob/user)
- . = ..()
- var/current_integrity = (obj_integrity / max_integrity) * 100
- switch(current_integrity)
- if(0 to 20)
- . += span_warning("It's barely holding, there's leaking oozes all around, and most eggs are broken. Yet it is not inert.")
- if(20 to 40)
- . += span_warning("It looks severely damaged, its movements slow.")
- if(40 to 60)
- . += span_warning("It's quite beat up, but it seems alive.")
- if(60 to 80)
- . += span_warning("It's slightly damaged, but still seems healthy.")
- if(80 to 100)
- . += span_info("It appears in good shape, pulsating healthily.")
-
-
-/obj/structure/xeno/spawner/take_damage(damage_amount, damage_type, damage_flag, sound_effect, attack_dir, armour_penetration)
- . = ..()
- spawner_damage_alert()
-
-///Alert if spawner is receiving damage
-/obj/structure/xeno/spawner/proc/spawner_damage_alert()
- if(!COOLDOWN_CHECK(src, spawner_damage_alert_cooldown))
- warning = FALSE
- return
- warning = TRUE
- update_minimap_icon()
- GLOB.hive_datums[hivenumber].xeno_message("Our [name] at [AREACOORD_NO_Z(src)] is under attack! It has [obj_integrity]/[max_integrity] Health remaining.", "xenoannounce", 5, FALSE, src, 'sound/voice/alien_help1.ogg',FALSE, null, /atom/movable/screen/arrow/silo_damaged_arrow)
- COOLDOWN_START(src, spawner_damage_alert_cooldown, XENO_SILO_HEALTH_ALERT_COOLDOWN) //set the cooldown.
- addtimer(CALLBACK(src, PROC_REF(clear_warning)), XENO_SILO_DETECTION_COOLDOWN) //clear warning
-
-///Alerts the Hive when hostiles get too close to their spawner
-/obj/structure/xeno/spawner/proc/spawner_proxy_alert(datum/source, atom/movable/hostile, direction)
- SIGNAL_HANDLER
-
- if(!COOLDOWN_CHECK(src, spawner_proxy_alert_cooldown)) //Proxy alert triggered too recently; abort
- warning = FALSE
- return
-
- if(!isliving(hostile))
- return
-
- var/mob/living/living_triggerer = hostile
- if(living_triggerer.stat == DEAD) //We don't care about the dead
- return
-
- if(isxeno(hostile))
- var/mob/living/carbon/xenomorph/X = hostile
- if(X.hivenumber == hivenumber) //Trigger proxy alert only for hostile xenos
- return
-
- warning = TRUE
- update_minimap_icon()
- GLOB.hive_datums[hivenumber].xeno_message("Our [name] has detected a nearby hostile [hostile] at [get_area(hostile)] (X: [hostile.x], Y: [hostile.y]).", "xenoannounce", 5, FALSE, hostile, 'sound/voice/alien_help1.ogg', FALSE, null, /atom/movable/screen/arrow/leader_tracker_arrow)
- COOLDOWN_START(src, spawner_proxy_alert_cooldown, XENO_SILO_DETECTION_COOLDOWN) //set the cooldown.
- addtimer(CALLBACK(src, PROC_REF(clear_warning)), XENO_SILO_DETECTION_COOLDOWN) //clear warning
-
-///Clears the warning for minimap if its warning for hostiles
-/obj/structure/xeno/spawner/proc/clear_warning()
- warning = FALSE
- update_minimap_icon()
-
-/obj/structure/xeno/spawner/Destroy()
- GLOB.xeno_spawners_by_hive[hivenumber] -= src
- return ..()
-
-///Change minimap icon if spawner is under attack or not
-/obj/structure/xeno/spawner/proc/update_minimap_icon()
- SSminimaps.remove_marker(src)
- SSminimaps.add_marker(src, MINIMAP_FLAG_XENO, image('icons/UI_icons/map_blips.dmi', null, "spawner[warning ? "_warn" : "_passive"]", , ABOVE_FLOAT_LAYER)) // RU TGMC edit - map blips
-
-/obj/structure/xeno/spawner/proc/on_spawn(list/squad)
- if(!isxeno(squad[length(squad)]))
- CRASH("Xeno spawner somehow tried to spawn a non xeno (tried to spawn [squad[length(squad)]])")
- var/mob/living/carbon/xenomorph/X = squad[length(squad)]
- X.transfer_to_hive(hivenumber)
- linked_minions = squad
- if(hivenumber == XENO_HIVE_FALLEN) //snowflake so valhalla isnt filled with minions after you're done
- RegisterSignal(src, COMSIG_QDELETING, PROC_REF(kill_linked_minions))
-
-/obj/structure/xeno/spawner/proc/kill_linked_minions()
- for(var/mob/living/carbon/xenomorph/linked in linked_minions)
- linked.death(TRUE)
- UnregisterSignal(src, COMSIG_QDELETING)
-
-///Those structures need time to grow and are supposed to be extremely weak healh-wise
-/obj/structure/xeno/plant
- name = "Xeno Plant"
- max_integrity = 5
- icon = 'icons/Xeno/plants.dmi'
- interaction_flags = INTERACT_CHECK_INCAPACITATED
- ///The plant's icon once it's fully grown
- var/mature_icon_state
- ///Is the plant ready to be used ?
- var/mature = FALSE
- ///How long does it take for the plant to be useable
- var/maturation_time = 2 MINUTES
-
-/obj/structure/xeno/plant/Initialize(mapload, _hivenumber)
- . = ..()
- addtimer(CALLBACK(src, PROC_REF(on_mature)), maturation_time)
- SSminimaps.add_marker(src, MINIMAP_FLAG_XENO, image('icons/UI_icons/map_blips.dmi', null, "[mature_icon_state]"))
-
-/obj/structure/xeno/plant/can_interact(mob/user)
- . = ..()
- if(!.)
- return FALSE
- if(!mature && isxeno(user))
- balloon_alert(user, "Not fully grown")
- return FALSE
-
-/obj/structure/xeno/plant/update_icon_state()
- . = ..()
- icon_state = (mature) ? mature_icon_state : initial(icon_state)
-
-///Called whenever someone uses the plant, xeno or marine
-/obj/structure/xeno/plant/proc/on_use(mob/user)
- mature = FALSE
- update_icon()
- addtimer(CALLBACK(src, PROC_REF(on_mature)), maturation_time)
- return TRUE
-
-///Called when the plant reaches maturity
-/obj/structure/xeno/plant/proc/on_mature(mob/user)
- playsound(src, "alien_resin_build", 25)
- mature = TRUE
- update_icon()
-
-/obj/structure/xeno/plant/attack_hand(mob/living/user)
- if(!can_interact(user))
- return ..()
- return on_use(user)
-
-/obj/structure/xeno/plant/attack_alien(mob/living/carbon/xenomorph/xeno_attacker, damage_amount = xeno_attacker.xeno_caste.melee_damage, damage_type = BRUTE, damage_flag = MELEE, effects = TRUE, armor_penetration = 0, isrightclick = FALSE)
- if((xeno_attacker.status_flags & INCORPOREAL))
- return FALSE
-
- if(xeno_attacker.a_intent == INTENT_HARM && isxenodrone(xeno_attacker))
- balloon_alert(xeno_attacker, "Uprooted the plant")
- xeno_attacker.do_attack_animation(src)
- deconstruct(FALSE)
- return FALSE
- if(can_interact(xeno_attacker))
- return on_use(xeno_attacker)
- return TRUE
-
-/obj/structure/xeno/plant/heal_fruit
- name = "life fruit"
- desc = "It would almost be appetizing wasn't it for the green colour and the shifting fluids inside..."
- icon_state = "heal_fruit_immature"
- mature_icon_state = "heal_fruit"
- ///Minimum amount of health recovered
- var/healing_amount_min = 125
- ///Maximum amount of health recovered, depends on the xeno's max health
- var/healing_amount_max_health_scaling = 0.5
-
-/obj/structure/xeno/plant/heal_fruit/on_use(mob/user)
- balloon_alert(user, "Consuming...")
- if(!do_after(user, 2 SECONDS, IGNORE_HELD_ITEM, src))
- return FALSE
- if(!isxeno(user))
- var/datum/effect_system/smoke_spread/xeno/acid/plant_explosion = new(get_turf(src))
- plant_explosion.set_up(3,src)
- plant_explosion.start()
- visible_message(span_danger("[src] bursts, releasing toxic gas!"))
- qdel(src)
- return TRUE
-
- var/mob/living/carbon/xenomorph/X = user
- var/heal_amount = max(healing_amount_min, healing_amount_max_health_scaling * X.xeno_caste.max_health)
- HEAL_XENO_DAMAGE(X, heal_amount, FALSE)
- playsound(user, "alien_drool", 25)
- balloon_alert(X, "Health restored")
- to_chat(X, span_xenowarning("We feel a sudden soothing chill as [src] tends to our wounds."))
-
- return ..()
-
-/obj/structure/xeno/plant/armor_fruit
- name = "hard fruit"
- desc = "The contents of this fruit are protected by a tough outer shell."
- icon_state = "armor_fruit_immature"
- mature_icon_state = "armor_fruit"
- ///How much total sunder should we remove
- var/sunder_removal = 30
-
-/obj/structure/xeno/plant/armor_fruit/on_use(mob/user)
- balloon_alert(user, "Consuming...")
- if(!do_after(user, 2 SECONDS, IGNORE_HELD_ITEM, src))
- return FALSE
- if(!isxeno(user))
- var/turf/far_away_lands = get_turf(user)
- for(var/x in 1 to 20)
- var/turf/next_turf = get_step(far_away_lands, REVERSE_DIR(user.dir))
- if(!next_turf)
- break
- far_away_lands = next_turf
-
- user.throw_at(far_away_lands, 20, spin = TRUE)
- to_chat(user, span_warning("[src] bursts, releasing a strong gust of pressurised gas!"))
- if(ishuman(user))
- var/mob/living/carbon/human/H = user
- H.adjust_stagger(3 SECONDS)
- H.apply_damage(30, BRUTE, "chest", BOMB)
- qdel(src)
- return TRUE
-
- balloon_alert(user, "Armor restored")
- to_chat(user, span_xenowarning("We shed our shattered scales as new ones grow to replace them!"))
- var/mob/living/carbon/xenomorph/X = user
- X.adjust_sunder(-sunder_removal)
- playsound(user, "alien_drool", 25)
- return ..()
-
-/obj/structure/xeno/plant/plasma_fruit
- name = "power fruit"
- desc = "A cyan fruit, beating like a creature's heart"
- icon_state = "plasma_fruit_immature"
- mature_icon_state = "plasma_fruit"
- ///How much bonus plasma should we restore during the duration, 1 being 100% from base regen
- var/bonus_regen = 1
- ///How long should the buff last
- var/duration = 1 MINUTES
-
-/obj/structure/xeno/plant/plasma_fruit/can_interact(mob/user)
- . = ..()
- if(!.)
- return FALSE
- if(!isxeno(user))
- return
- var/mob/living/carbon/xenomorph/X = user
- if(X.has_status_effect(STATUS_EFFECT_PLASMA_SURGE))
- balloon_alert(X, "Already increased plasma regen")
- return FALSE
-
-/obj/structure/xeno/plant/plasma_fruit/on_use(mob/user)
- balloon_alert(user, "Consuming...")
- if(!do_after(user, 2 SECONDS, IGNORE_HELD_ITEM, src))
- return FALSE
- if(!isxeno(user))
- visible_message(span_warning("[src] releases a sticky substance before spontaneously bursting into flames!"))
- flame_radius(3, get_turf(src), colour = "green")
- qdel(src)
- return TRUE
-
- var/mob/living/carbon/xenomorph/X = user
- if(!(X.xeno_caste.can_flags & CASTE_CAN_BE_GIVEN_PLASMA))
- to_chat(X, span_xenowarning("But our body rejects the fruit, we do not share the same plasma type!"))
- return FALSE
- X.apply_status_effect(/datum/status_effect/plasma_surge, X.xeno_caste.plasma_max, bonus_regen, duration)
- balloon_alert(X, "Plasma restored")
- to_chat(X, span_xenowarning("[src] Restores our plasma reserves, our organism is on overdrive!"))
- playsound(user, "alien_drool", 25)
- return ..()
-
-/obj/structure/xeno/plant/stealth_plant
- name = "night shade"
- desc = "A beautiful flower, what purpose it could serve to the alien hive is beyond you however..."
- icon_state = "stealth_plant_immature"
- mature_icon_state = "stealth_plant"
- maturation_time = 4 MINUTES
- ///The radius of the passive structure camouflage, requires line of sight
- var/camouflage_range = 7
- ///The range of the active stealth ability, does not require line of sight
- var/active_camouflage_pulse_range = 10
- ///How long should veil last
- var/active_camouflage_duration = 20 SECONDS
- ///How long until the plant can be activated again
- var/cooldown = 2 MINUTES
- ///Is the active ability veil on cooldown ?
- var/on_cooldown = FALSE
- ///The list of passively camouflaged structures
- var/list/obj/structure/xeno/camouflaged_structures = list()
- ////The list of actively camouflaged xenos by veil
- var/list/mob/living/carbon/xenomorph/camouflaged_xenos = list()
-
-/obj/structure/xeno/plant/stealth_plant/on_mature(mob/user)
- . = ..()
- START_PROCESSING(SSslowprocess, src)
-
-/obj/structure/xeno/plant/stealth_plant/Destroy()
- for(var/obj/structure/xeno/xeno_struct AS in camouflaged_structures)
- xeno_struct.alpha = initial(xeno_struct.alpha)
- unveil()
- STOP_PROCESSING(SSslowprocess, src)
- return ..()
-
-/obj/structure/xeno/plant/stealth_plant/process()
- for(var/turf/tile AS in RANGE_TURFS(camouflage_range, loc))
- for(var/obj/structure/xeno/xeno_struct in tile)
- if(istype(xeno_struct, /obj/structure/xeno/plant) || !line_of_sight(src, xeno_struct)) //We don't hide plants
- continue
- camouflaged_structures.Add(xeno_struct)
- xeno_struct.alpha = STEALTH_PLANT_PASSIVE_CAMOUFLAGE_ALPHA
-
-/obj/structure/xeno/plant/stealth_plant/can_interact(mob/user)
- . = ..()
- if(!.)
- return FALSE
- if(ishuman(user))
- balloon_alert(user, "Nothing happens")
- to_chat(user, span_notice("You caress [src]'s petals, nothing happens."))
- return FALSE
- if(on_cooldown)
- balloon_alert(user, "Not ready yet")
- to_chat(user, span_xenowarning("[src] soft light shimmers, we should give it more time to recover!"))
- return FALSE
-
-/obj/structure/xeno/plant/stealth_plant/on_use(mob/user)
- balloon_alert(user, "Shaking...")
- if(!do_after(user, 2 SECONDS, IGNORE_HELD_ITEM, src))
- return FALSE
- visible_message(span_danger("[src] releases a burst of glowing pollen!"))
- veil()
- return TRUE
-
-///Hides all nearby xenos
-/obj/structure/xeno/plant/stealth_plant/proc/veil()
- for(var/turf/tile in RANGE_TURFS(camouflage_range, loc))
- for(var/mob/living/carbon/xenomorph/X in tile)
- if(X.stat == DEAD || isxenohunter(X) || X.alpha != 255) //We don't mess with xenos capable of going stealth by themselves
- continue
- X.alpha = HUNTER_STEALTH_RUN_ALPHA
- new /obj/effect/temp_visual/alien_fruit_eaten(get_turf(X))
- balloon_alert(X, "We now blend in")
- to_chat(X, span_xenowarning("The pollen from [src] reacts with our scales, we are blending with our surroundings!"))
- camouflaged_xenos.Add(X)
- on_cooldown = TRUE
- addtimer(CALLBACK(src, PROC_REF(unveil)), active_camouflage_duration)
- addtimer(CALLBACK(src, PROC_REF(ready)), cooldown)
-
-///Called when veil() can be used once again
-/obj/structure/xeno/plant/stealth_plant/proc/ready()
- visible_message(span_danger("[src] petals shift in hue, it is ready to release more pollen."))
- on_cooldown = FALSE
-
-///Reveals all xenos hidden by veil()
-/obj/structure/xeno/plant/stealth_plant/proc/unveil()
- for(var/mob/living/carbon/xenomorph/X AS in camouflaged_xenos)
- X.alpha = initial(X.alpha)
- balloon_alert(X, "Effect wears off")
- to_chat(X, span_xenowarning("The effect of [src] wears off!"))
-
-/obj/structure/xeno/thick_nest
- name = "thick resin nest"
- desc = "A very thick nest, oozing with a thick sticky substance."
- pixel_x = -8
- pixel_y = -8
- max_integrity = 400
- mouse_opacity = MOUSE_OPACITY_ICON
-
- icon = 'icons/Xeno/nest.dmi'
- icon_state = "reinforced_nest"
- layer = 2.5
-
- var/obj/structure/bed/nest/structure/pred_nest
-
-/obj/structure/xeno/thick_nest/examine(mob/user)
- . = ..()
- if((isxeno(user) || isobserver(user)) && hivenumber)
- . += "Used to secure formidable hosts."
-
-/obj/structure/xeno/thick_nest/Initialize(mapload, new_hivenumber)
- . = ..()
- if(new_hivenumber)
- hivenumber = new_hivenumber
-
- var/datum/hive_status/hive_ref = GLOB.hive_datums[hivenumber]
- if(hive_ref)
- hive_ref.thick_nests += src
-
- pred_nest = new /obj/structure/bed/nest/structure(loc, hive_ref, src) // Nest cannot be destroyed unless the structure itself is destroyed
-
-
-/obj/structure/xeno/thick_nest/Destroy()
- . = ..()
-
- if(hivenumber)
- GLOB.hive_datums[hivenumber].thick_nests -= src
-
- pred_nest?.linked_structure = null
- QDEL_NULL(pred_nest)
-
-/obj/structure/bed/nest
- var/force_nest = FALSE
-
-/obj/structure/bed/nest/structure
- name = "thick alien nest"
- desc = "A very thick nest, oozing with a thick sticky substance."
- force_nest = TRUE
- var/obj/structure/xeno/thick_nest/linked_structure
-
-/obj/structure/bed/nest/structure/Initialize(mapload, hive, obj/structure/xeno/thick_nest/to_link)
- . = ..()
- if(to_link)
- linked_structure = to_link
- max_integrity = linked_structure.max_integrity
-
-/obj/structure/bed/nest/structure/Destroy()
- . = ..()
- if(linked_structure)
- linked_structure.pred_nest = null
- QDEL_NULL(linked_structure)
-
-/obj/structure/bed/nest/structure/attack_hand(mob/user)
- if(!isxeno(user))
- to_chat(user, span_notice("The sticky resin is too strong for you to do anything to this nest"))
- return FALSE
- . = ..()
diff --git a/code/modules/xenomorph/xeno_towers.dm b/code/modules/xenomorph/xeno_towers.dm
new file mode 100644
index 00000000000..79bddc688df
--- /dev/null
+++ b/code/modules/xenomorph/xeno_towers.dm
@@ -0,0 +1,117 @@
+/obj/structure/xeno/evotower
+ name = "evolution tower"
+ desc = "A sickly outcrop from the ground. It seems to ooze a strange chemical that shimmers and warps the ground around it."
+ icon = 'icons/Xeno/2x2building.dmi'
+ icon_state = "evotower"
+ bound_width = 64
+ bound_height = 64
+ obj_integrity = 600
+ max_integrity = 600
+ xeno_structure_flags = CRITICAL_STRUCTURE
+ ///boost amt to be added per tower per cycle
+ var/boost_amount = 0.2
+
+/obj/structure/xeno/evotower/Initialize(mapload, _hivenumber)
+ . = ..()
+ GLOB.hive_datums[hivenumber].evotowers += src
+ set_light(2, 2, LIGHT_COLOR_GREEN)
+ SSminimaps.add_marker(src, MINIMAP_FLAG_XENO, image('icons/UI_icons/map_blips.dmi', null, "tower"))
+
+/obj/structure/xeno/evotower/Destroy()
+ GLOB.hive_datums[hivenumber].evotowers -= src
+ return ..()
+
+/obj/structure/xeno/evotower/ex_act(severity)
+ take_damage(severity * 2.5, BRUTE, BOMB)
+
+/obj/structure/xeno/psychictower
+ name = "Psychic Relay"
+ desc = "A sickly outcrop from the ground. It seems to allow for more advanced growth of the Xenomorphs."
+ icon = 'icons/Xeno/2x2building.dmi'
+ icon_state = "maturitytower"
+ bound_width = 64
+ bound_height = 64
+ obj_integrity = 400
+ max_integrity = 400
+ xeno_structure_flags = CRITICAL_STRUCTURE
+
+/obj/structure/xeno/psychictower/Initialize(mapload, _hivenumber)
+ . = ..()
+ GLOB.hive_datums[hivenumber].psychictowers += src
+ set_light(2, 2, LIGHT_COLOR_GREEN)
+ SSminimaps.add_marker(src, MINIMAP_FLAG_XENO, image('icons/UI_icons/map_blips.dmi', null, "tower"))
+
+/obj/structure/xeno/psychictower/Destroy()
+ GLOB.hive_datums[hivenumber].psychictowers -= src
+ return ..()
+
+/obj/structure/xeno/psychictower/ex_act(severity)
+ take_damage(severity * 2.5, BRUTE, BOMB)
+
+/obj/structure/xeno/pherotower
+ name = "Pheromone tower"
+ desc = "A resin formation that looks like a small pillar. A faint, weird smell can be perceived from it."
+ icon = 'icons/Xeno/1x1building.dmi'
+ icon_state = "recoverytower"
+ bound_width = 32
+ bound_height = 32
+ obj_integrity = 400
+ max_integrity = 400
+ xeno_structure_flags = CRITICAL_STRUCTURE
+ ///The type of pheromone currently being emitted.
+ var/datum/aura_bearer/current_aura
+ ///Strength of pheromones given by this tower.
+ var/aura_strength = 5
+ ///Radius (in tiles) of the pheromones given by this tower.
+ var/aura_radius = 32
+
+/obj/structure/xeno/pherotower/Initialize(mapload, _hivenumber)
+ . = ..()
+ SSminimaps.add_marker(src, MINIMAP_FLAG_XENO, image('icons/UI_icons/map_blips.dmi', null, "phero", ABOVE_FLOAT_LAYER)) // RU TGMC edit - map blips
+ GLOB.hive_datums[hivenumber].pherotowers += src
+
+//Pheromone towers start off with recovery.
+ current_aura = SSaura.add_emitter(src, AURA_XENO_RECOVERY, aura_radius, aura_strength, -1, FACTION_XENO, hivenumber)
+ playsound(src, "alien_drool", 25)
+ update_icon()
+
+/obj/structure/xeno/pherotower/ex_act(severity)
+ take_damage(severity * 2.5, BRUTE, BOMB)
+
+/obj/structure/xeno/pherotower/Destroy()
+ GLOB.hive_datums[hivenumber].pherotowers -= src
+ return ..()
+
+// Clicking on the tower brings up a radial menu that allows you to select the type of pheromone that this tower will emit.
+/obj/structure/xeno/pherotower/attack_alien(mob/living/carbon/xenomorph/xeno_attacker, damage_amount = xeno_attacker.xeno_caste.melee_damage, damage_type = BRUTE, damage_flag = MELEE, effects = TRUE, armor_penetration = 0, isrightclick = FALSE)
+ var/phero_choice = show_radial_menu(xeno_attacker, src, GLOB.pheromone_images_list, radius = 35, require_near = TRUE)
+
+ if(!phero_choice)
+ return
+
+ QDEL_NULL(current_aura)
+ current_aura = SSaura.add_emitter(src, phero_choice, aura_radius, aura_strength, -1, FACTION_XENO, hivenumber)
+ balloon_alert(xeno_attacker, "[phero_choice]")
+ playsound(src, "alien_drool", 25)
+ update_icon()
+
+/obj/structure/xeno/pherotower/update_icon_state()
+ . = ..()
+ switch(current_aura.aura_types[1])
+ if(AURA_XENO_RECOVERY)
+ icon_state = "recoverytower"
+ set_light(2, 2, LIGHT_COLOR_BLUE)
+ if(AURA_XENO_WARDING)
+ icon_state = "wardingtower"
+ set_light(2, 2, LIGHT_COLOR_GREEN)
+ if(AURA_XENO_FRENZY)
+ icon_state = "frenzytower"
+ set_light(2, 2, LIGHT_COLOR_RED)
+
+/obj/structure/xeno/pherotower/crash
+ name = "Recovery tower"
+ resistance_flags = RESIST_ALL
+ xeno_structure_flags = IGNORE_WEED_REMOVAL | CRITICAL_STRUCTURE
+
+/obj/structure/xeno/pherotower/crash/attack_alien(isrightclick = FALSE)
+ return
diff --git a/tgmc.dme b/tgmc.dme
index e1ba71445e0..3a8a1a005cd 100644
--- a/tgmc.dme
+++ b/tgmc.dme
@@ -2085,7 +2085,16 @@
#include "code\modules\vehicles\unmanned\unmanned_turrets.dm"
#include "code\modules\vehicles\unmanned\unmanned_vehicle.dm"
#include "code\modules\vehicles\unmanned\unmanned_vehicle_remote.dm"
-#include "code\modules\xenomorph\xeno_structures.dm"
+#include "code\modules\xenomorph\_xeno_structure.dm"
+#include "code\modules\xenomorph\acidwell.dm"
+#include "code\modules\xenomorph\jellypod.dm"
+#include "code\modules\xenomorph\nest.dm"
+#include "code\modules\xenomorph\plant.dm"
+#include "code\modules\xenomorph\silo.dm"
+#include "code\modules\xenomorph\trap.dm"
+#include "code\modules\xenomorph\tunnel.dm"
+#include "code\modules\xenomorph\turret.dm"
+#include "code\modules\xenomorph\xeno_towers.dm"
#include "code\ze_genesis_call\genesis_call.dm"
#include "interface\interface.dm"
#include "interface\menu.dm"