diff --git a/code/__DEFINES/ai.dm b/code/__DEFINES/ai.dm
index 9c9b2f98b88..6271b8aceaa 100644
--- a/code/__DEFINES/ai.dm
+++ b/code/__DEFINES/ai.dm
@@ -237,6 +237,23 @@
///How long have we spent with no target?
#define BB_TARGETLESS_TIME "BB_targetless_time"
+/// Key that holds a nearby vent that looks like it's a good place to hide
+#define BB_ENTRY_VENT_TARGET "BB_entry_vent_target"
+/// Key that holds a vent that we want to exit out of (when we're already in a pipenet)
+#define BB_EXIT_VENT_TARGET "BB_exit_vent_target"
+/// Do we plan on going inside a vent? Boolean.
+#define BB_CURRENTLY_TARGETTING_VENT "BB_currently_targetting_vent"
+/// How long should we wait before we try and enter a vent again?
+#define BB_VENTCRAWL_COOLDOWN "BB_ventcrawl_cooldown"
+/// The least amount of time (in seconds) we take to go through the vents.
+#define BB_LOWER_VENT_TIME_LIMIT "BB_lower_vent_time_limit"
+/// The most amount of time (in seconds) we take to go through the vents.
+#define BB_UPPER_VENT_TIME_LIMIT "BB_upper_vent_time_limit"
+/// How much time (in seconds) do we take until we completely go bust on vent pathing?
+#define BB_TIME_TO_GIVE_UP_ON_VENT_PATHING "BB_seconds_until_we_give_up_on_vent_pathing"
+/// The timer ID of the timer that makes us give up on vent pathing.
+#define BB_GIVE_UP_ON_VENT_PATHING_TIMER_ID "BB_give_up_on_vent_pathing_timer_id"
+
/// Is there something that scared us into being stationary? If so, hold the reference here
#define BB_STATIONARY_CAUSE "BB_thing_that_made_us_stationary"
///How long should we remain stationary for?
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index 2fb6a2f985f..633b219ba3d 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -1001,6 +1001,9 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
/// Basically, without this, COMSIG_IN_RANGE_OF_IRRADIATION won't fire once the object is irradiated.
#define TRAIT_BYPASS_EARLY_IRRADIATED_CHECK "radiation_bypass_early_irradiated_check"
+/// Simple trait that just holds if we came into growth from a specific mob type. Should hold a REF(src) to the type of mob that caused the growth, not anything else.
+#define TRAIT_WAS_EVOLVED "was_evolved_from_the_mob_we_hold_a_textref_to"
+
// Traits to heal for
/// This mob heals from carp rifts.
diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/ventcrawling.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/ventcrawling.dm
new file mode 100644
index 00000000000..f5e4f74a1fe
--- /dev/null
+++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/ventcrawling.dm
@@ -0,0 +1,138 @@
+/// We hop into the vents through a vent outlet, and then crawl around a bit. Jolly good times.
+/// This also assumes that we are on the turf that the vent outlet is on. If it isn't, shit.
+
+/// Warning: this was really snowflake code lifted from an obscure feature that likely has not been touched for over five years years.
+/// Something that isn't implemented is the ability to actually crawl through vents ourselves because I think that's just a waste of time for the same effect (instead of psuedo-teleportation, do REAL forceMoving)
+/// If you are seriously considering using this component, it would be a great idea to extend this proc to be more versatile/less overpowered - the mobs that currently implement this benefit the most
+/// since they are weak as shit with only five health. Up to you though, don't take what's written here as gospel.
+/datum/ai_behavior/crawl_through_vents
+ action_cooldown = 10 SECONDS
+
+/datum/ai_behavior/crawl_through_vents/setup(datum/ai_controller/controller, target_key)
+ action_cooldown = controller.blackboard[BB_VENTCRAWL_COOLDOWN] || initial(action_cooldown)
+ . = ..()
+ var/obj/machinery/atmospherics/components/unary/vent_pump/target = controller.blackboard[target_key] || controller.blackboard[BB_ENTRY_VENT_TARGET]
+ return istype(target) && isliving(controller.pawn) // only mobs can vent crawl in the current framework
+
+/datum/ai_behavior/crawl_through_vents/perform(seconds_per_tick, datum/ai_controller/controller, target_key)
+ . = ..()
+ var/obj/machinery/atmospherics/components/unary/vent_pump/entry_vent = controller.blackboard[target_key] || controller.blackboard[BB_ENTRY_VENT_TARGET]
+ var/mob/living/cached_pawn = controller.pawn
+ if(HAS_TRAIT(cached_pawn, TRAIT_MOVE_VENTCRAWLING) || !controller.blackboard[BB_CURRENTLY_TARGETTING_VENT] || !is_vent_valid(entry_vent))
+ return
+
+ if(!cached_pawn.can_enter_vent(entry_vent, provide_feedback = FALSE)) // we're an AI we scoff at feedback
+ finish_action(controller, FALSE, target_key) // "never enter a hole you can't get out of"
+ return
+
+ var/vent_we_exit_out_of = calculate_exit_vent(controller, target_key)
+ if(isnull(vent_we_exit_out_of)) // don't get into the vents if we can't get out of them, that's SILLY.
+ finish_action(controller, FALSE, target_key)
+ return
+
+ controller.set_blackboard_key(BB_CURRENTLY_TARGETTING_VENT, FALSE) // must be done here because we have a do_after sleep in handle_ventcrawl unfortunately and double dipping could lead to erroneous suicide pill calls.
+ cached_pawn.handle_ventcrawl(entry_vent)
+ if(!HAS_TRAIT(cached_pawn, TRAIT_MOVE_VENTCRAWLING)) //something failed and we ARE NOT IN THE VENT even though the earlier check said we were good to go! odd.
+ finish_action(controller, FALSE, target_key)
+ return
+
+ controller.set_blackboard_key(BB_EXIT_VENT_TARGET, vent_we_exit_out_of)
+
+ if(prob(50))
+ cached_pawn.visible_message(
+ span_warning("[src] scrambles into the ventilation ducts!"),
+ span_hear("You hear something scampering through the ventilation ducts."),
+ )
+
+ var/lower_vent_time_limit = controller.blackboard[BB_LOWER_VENT_TIME_LIMIT] // the least amount of time we spend in the vents
+ var/upper_vent_time_limit = controller.blackboard[BB_UPPER_VENT_TIME_LIMIT] // the most amount of time we spend in the vents
+
+ addtimer(CALLBACK(src, PROC_REF(exit_the_vents), controller), rand(lower_vent_time_limit, upper_vent_time_limit))
+ controller.set_blackboard_key(BB_GIVE_UP_ON_VENT_PATHING_TIMER_ID, addtimer(CALLBACK(src, PROC_REF(suicide_pill), controller), controller.blackboard[BB_TIME_TO_GIVE_UP_ON_VENT_PATHING], TIMER_STOPPABLE))
+
+/// Figure out an exit vent that we should head towards. If we don't have one, default to the entry vent. If they're all kaput, we die.
+/datum/ai_behavior/crawl_through_vents/proc/calculate_exit_vent(datum/ai_controller/controller, target_key)
+ var/obj/machinery/atmospherics/components/unary/vent_pump/returnable_vent
+ var/obj/machinery/atmospherics/components/unary/vent_pump/vent_we_entered_through = controller.blackboard[target_key] || controller.blackboard[BB_ENTRY_VENT_TARGET]
+
+ var/datum/pipeline/entry_vent_parent = vent_we_entered_through.parents[1]
+ var/list/potential_exits = list()
+
+ for(var/obj/machinery/atmospherics/components/unary/vent_pump/vent in entry_vent_parent.other_atmos_machines)
+ if(is_vent_valid(vent))
+ potential_exits.Add(vent)
+
+ if(length(potential_exits))
+ returnable_vent = pick(potential_exits)
+ return returnable_vent
+
+ // if we're here, we're in "what the flarp" mode... okay maybe we can default to the vent we entered in.
+ returnable_vent = vent_we_entered_through
+ if(is_vent_valid(vent_we_entered_through))
+ // AH WHAT THE FUCK. okay, maybe we're not inside the vents yet? let's return null and we can pick up on that based on the wider context of the proc that invokes it.
+ return null
+
+ return returnable_vent // we return null in case something yonked between then and now so it's all good man
+
+/// We've had enough horsing around in the vents, it's time to get out.
+/datum/ai_behavior/crawl_through_vents/proc/exit_the_vents(datum/ai_controller/controller, target_key)
+ var/obj/machinery/atmospherics/components/unary/vent_pump/emergency_vent // vent we will scramble to search for in case plan A is a bust (exit vent)
+ var/obj/machinery/atmospherics/components/unary/vent_pump/exit_vent = controller.blackboard[BB_EXIT_VENT_TARGET]
+ var/mob/living/living_pawn = controller.pawn
+
+ if(!HAS_TRAIT(living_pawn, TRAIT_MOVE_VENTCRAWLING) && isturf(get_turf(living_pawn))) // we're out of the vents, so no need to do an exit
+ finish_action(controller, TRUE, target_key) // assume that we got yeeted out somehow and call this so we can halt the suicide pill timer.
+ return
+
+ living_pawn.forceMove(exit_vent)
+ if(!living_pawn.can_enter_vent(exit_vent, provide_feedback = FALSE))
+ // oh shit, something happened while we were waiting on that timer. let's figure out a different way to get out of here.
+ emergency_vent = calculate_exit_vent(controller)
+ if(isnull(emergency_vent))
+ // it's joever. we cooked too hard.
+ suicide_pill(controller, target_key)
+ return
+
+ controller.set_blackboard_key(BB_EXIT_VENT_TARGET, emergency_vent) // assign and go again
+ addtimer(CALLBACK(src, PROC_REF(exit_the_vents), controller), (rand(controller.blackboard[BB_LOWER_VENT_TIME_LIMIT], controller.blackboard[BB_UPPER_VENT_TIME_LIMIT]) / 2)) // we're in danger mode, so scurry out at half the time it would normally take.
+ return
+
+ living_pawn.handle_ventcrawl(exit_vent)
+ if(HAS_TRAIT(living_pawn, TRAIT_MOVE_VENTCRAWLING)) // how'd we fail? what the fuck
+ stack_trace("We failed to exit the vents, even though we should have been fine? This is very weird.")
+ suicide_pill() // all of the prior checks say we should have definitely made it through, but we didn't. dammit.
+ return
+
+ finish_action(controller, TRUE, target_key) // we did it! we went into the vents and out of the vents. poggers.
+
+/// Incredibly stripped down version of the overarching `can_enter_vent` proc on `/mob, just meant for rapid rechecking of a vent. Will be TRUE if not blocked, FALSE otherwise.
+/datum/ai_behavior/crawl_through_vents/proc/is_vent_valid(obj/machinery/atmospherics/components/unary/vent_pump/checkable)
+ return !QDELETED(checkable) && !checkable.welded
+
+/// Aw fuck, we may have been bested somehow. Regardless of what we do, we can't exit through a vent! Let's end our misery and prevent useless endless calculations.
+/datum/ai_behavior/crawl_through_vents/proc/suicide_pill(datum/ai_controller/controller, target_key)
+ var/mob/living/living_pawn = controller.pawn
+
+ if(istype(living_pawn))
+ finish_action(controller, FALSE, target_key)
+
+ if(isnull(living_pawn.client)) // only call death if we don't have a client because maybe their natural intelligence can pick up where our AI calculations have failed
+ living_pawn.death(TRUE) // call gibbed as true because we are never coming back it is so fucking joever
+
+ return
+
+ if(QDELETED(living_pawn)) // we got deleted by some other means, just presume the action is a wash and get outta here
+ return
+
+ qdel(living_pawn) // failover, we really should've been caught in the istype() but lets just bow out of existing at this point
+
+
+/datum/ai_behavior/crawl_through_vents/finish_action(datum/ai_controller/controller, succeeded, target_key)
+ . = ..()
+
+ deltimer(controller.blackboard[BB_GIVE_UP_ON_VENT_PATHING_TIMER_ID])
+ controller.clear_blackboard_key(target_key)
+ controller.clear_blackboard_key(BB_ENTRY_VENT_TARGET)
+ controller.clear_blackboard_key(BB_EXIT_VENT_TARGET)
+ controller.set_blackboard_key(BB_CURRENTLY_TARGETTING_VENT, FALSE) // just in case
+
diff --git a/code/datums/ai/basic_mobs/basic_subtrees/opportunistic_ventcrawler.dm b/code/datums/ai/basic_mobs/basic_subtrees/opportunistic_ventcrawler.dm
new file mode 100644
index 00000000000..63a745305b2
--- /dev/null
+++ b/code/datums/ai/basic_mobs/basic_subtrees/opportunistic_ventcrawler.dm
@@ -0,0 +1,20 @@
+/// Opportunistically searches for and hides/scurries through vents.
+/datum/ai_planning_subtree/opportunistic_ventcrawler
+
+/datum/ai_planning_subtree/opportunistic_ventcrawler/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
+ if(HAS_TRAIT(controller.pawn, TRAIT_MOVE_VENTCRAWLING))
+ return SUBTREE_RETURN_FINISH_PLANNING // hold on let me cook
+
+ var/obj/machinery/atmospherics/components/unary/vent_pump/target = controller.blackboard[BB_ENTRY_VENT_TARGET]
+
+ if(QDELETED(target))
+ controller.queue_behavior(/datum/ai_behavior/find_and_set, BB_ENTRY_VENT_TARGET, /obj/machinery/atmospherics/components/unary/vent_pump) // keep looking otherwise they KILL US AND WE DIE
+ return
+
+ if(get_turf(controller.pawn) != get_turf(target))
+ controller.queue_behavior(/datum/ai_behavior/travel_towards, BB_ENTRY_VENT_TARGET)
+ return
+
+ controller.set_blackboard_key(BB_CURRENTLY_TARGETTING_VENT, TRUE)
+ controller.queue_behavior(/datum/ai_behavior/crawl_through_vents, BB_ENTRY_VENT_TARGET)
+ return SUBTREE_RETURN_FINISH_PLANNING // we are going into this vent... no distractions
diff --git a/code/datums/components/growth_and_differentiation.dm b/code/datums/components/growth_and_differentiation.dm
new file mode 100644
index 00000000000..8dd659bad81
--- /dev/null
+++ b/code/datums/components/growth_and_differentiation.dm
@@ -0,0 +1,125 @@
+/**
+ * ### Growth and Differentiation Component: Used to randomly "grow" a creature into a new entity over its lifespan.
+ *
+ * If we are passed a typepath, we will 100% grow into that type. However, if we are not passed a typepath, we will pick one from a subtype of the parent we were applied to!
+ *
+ * Used for spiderlings to turn them into giant spiders.
+ */
+
+/datum/component/growth_and_differentiation
+ /// What this mob turns into when fully grown.
+ var/growth_path
+ /// Failover for how much time we have until we fully grow. If passed as null, we eschew setting up the timer.
+ /// Remember: We can grow earlier than this if the randomness rolls turn out to be in our favor though!
+ var/growth_time
+ /// Integer - Probability we grow per SPT_PROB
+ var/growth_probability
+ /// Integer - The lower bound for the percentage we have to grow before we can differentiate.
+ var/lower_growth_value
+ /// Integer - The upper bound for the percentage we have to grow before we can differentiate.
+ var/upper_growth_value
+ /// Optional callback for checks to see if we're okay to grow.
+ var/datum/callback/optional_checks
+ /// Optional callback in case we wish to override the default grow() behavior. Assume we supersede the change_mob_type() call if we have this set.
+ var/datum/callback/optional_grow_behavior
+
+ /// ID for the failover timer.
+ var/timer_id
+ /// Percentage we have grown.
+ var/percent_grown = 0
+ /// Are we ready to grow? This is just in case we fail our checks and need to wait until the next tick.
+ /// We only really need this because we have two competing systems, the timer and the probability-based growth. When one succeeds, this component is considered successful in growth,
+ /// and will actively try to grow the mob (only barred by optional checks).
+ var/ready_to_grow = FALSE
+
+/datum/component/growth_and_differentiation/Initialize(growth_time, growth_path, growth_probability, lower_growth_value, upper_growth_value, optional_checks, optional_grow_behavior)
+ if(!isliving(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ src.growth_path = growth_path
+ src.growth_time = growth_time
+ src.growth_probability = growth_probability
+ src.lower_growth_value = lower_growth_value
+ src.upper_growth_value = upper_growth_value
+ src.optional_checks = optional_checks
+ src.optional_grow_behavior = optional_grow_behavior
+
+ // If we haven't started the round, we can't do timer stuff. Let's wait in case we're mapped in or something.
+ if(!SSticker.HasRoundStarted() && !isnull(growth_time))
+ RegisterSignal(SSticker, COMSIG_TICKER_ROUND_STARTING, PROC_REF(comp_on_round_start))
+ return
+
+ return setup_growth_tracking()
+
+/datum/component/growth_and_differentiation/Destroy(force, silent)
+ . = ..()
+ deltimer(timer_id)
+
+/datum/component/growth_and_differentiation/UnregisterFromParent()
+ UnregisterSignal(SSticker, COMSIG_TICKER_ROUND_STARTING)
+
+/// What we invoke when the round starts so we can set up our timer.
+/datum/component/growth_and_differentiation/proc/comp_on_round_start()
+ SIGNAL_HANDLER
+ setup_growth_tracking()
+ UnregisterSignal(SSticker, COMSIG_TICKER_ROUND_STARTING)
+
+/// Sets up the failover timer for certain growth.
+/datum/component/growth_and_differentiation/proc/setup_growth_tracking()
+ var/did_we_add_at_least_one_thing = FALSE
+
+ if(!isnull(growth_time))
+ timer_id = addtimer(CALLBACK(src, PROC_REF(grow), FALSE), growth_time, TIMER_STOPPABLE)
+ if(!isnull(timer_id)) // realistically shouldn't happen considering how hardy addtimer() is but you can never be too sure
+ did_we_add_at_least_one_thing = TRUE
+
+ if(!isnull(growth_probability))
+ START_PROCESSING(SSdcs, src)
+ did_we_add_at_least_one_thing = TRUE
+
+ if(!did_we_add_at_least_one_thing)
+ stack_trace("Growth and Differentiation Component: Neither growth time nor probability were set! This component is useless!")
+ return COMPONENT_INCOMPATIBLE // if we're invoked via COMSIG_TICKER_ROUND_STARTING this won't do anything (and shouldn't be invoked since we nullcheck growth_time before adding that signal anyways)
+
+ return null // just for explicitness's sake, if they ever change Component's Initialize to have more return values make sure this is the one for "Success!"
+
+/datum/component/growth_and_differentiation/process(seconds_per_tick) // check the prob we were passed in, and if we're lucky, grow!
+ if(ready_to_grow)
+ INVOKE_ASYNC(src, PROC_REF(grow), FALSE)
+ return
+
+ if(percent_grown >= 100)
+ ready_to_grow = TRUE
+ INVOKE_ASYNC(src, PROC_REF(grow), FALSE) // lets not waste any more of SSmobs time this tick.
+ return
+
+ if(SPT_PROB(growth_probability, seconds_per_tick))
+ percent_grown += rand(lower_growth_value, upper_growth_value)
+
+/// Grows the mob into its new form.
+/datum/component/growth_and_differentiation/proc/grow(silent)
+ if(!isnull(optional_checks) && !optional_checks.Invoke()) // we failed our checks somehow, but we're still ready to grow. Let's wait until next tick to see if our circumstances have changed.
+ ready_to_grow = TRUE
+ return
+
+ var/mob/living/old_mob = parent
+ if (old_mob.stat == DEAD)
+ qdel(src) // assume that we are priced out of growth once dead
+ return
+
+ STOP_PROCESSING(SSdcs, src)
+
+ if(!isnull(optional_grow_behavior)) // basically growth_path is OK to be null but only if we have an optional grow behavior.
+ optional_grow_behavior.Invoke()
+ return
+
+ var/mob/living/new_mob = growth_path
+ if(!istype(new_mob))
+ CRASH("Growth and Differentiation Component: Growth path was not a mob type! If you wanted to do something special, please put it in the optional_grow_behavior callback instead!")
+
+ var/new_mob_name = initial(new_mob.name)
+
+ if(!silent)
+ old_mob.visible_message(span_warning("[old_mob] grows into \a [new_mob_name]!"))
+
+ old_mob.change_mob_type(growth_path, old_mob.loc, new_name = new_mob_name, delete_old_mob = TRUE)
diff --git a/code/game/objects/effects/spiderwebs.dm b/code/game/objects/effects/spiderwebs.dm
index e0fbb078b86..7eb4838a542 100644
--- a/code/game/objects/effects/spiderwebs.dm
+++ b/code/game/objects/effects/spiderwebs.dm
@@ -101,132 +101,6 @@
else if(isprojectile(mover))
return prob(30)
-/obj/structure/spider/spiderling
- name = "spiderling"
- desc = "It never stays still for long."
- icon_state = "spiderling"
- anchored = FALSE
- layer = PROJECTILE_HIT_THRESHHOLD_LAYER
- max_integrity = 3
- var/amount_grown = 0
- var/grow_as = null
- var/obj/machinery/atmospherics/components/unary/vent_pump/entry_vent
- var/travelling_in_vent = 0
- var/directive = "" //Message from the mother
- var/list/faction = list(FACTION_SPIDER)
-
-/obj/structure/spider/spiderling/Destroy()
- new/obj/item/food/spiderling(get_turf(src))
- . = ..()
-
-/obj/structure/spider/spiderling/Initialize(mapload)
- . = ..()
- pixel_x = rand(6,-6)
- pixel_y = rand(6,-6)
- START_PROCESSING(SSobj, src)
- AddComponent(/datum/component/swarming)
-
-/obj/structure/spider/spiderling/hunter
- grow_as = /mob/living/basic/giant_spider/hunter
-
-/obj/structure/spider/spiderling/nurse
- grow_as = /mob/living/basic/giant_spider/nurse
-
-/obj/structure/spider/spiderling/midwife
- grow_as = /mob/living/basic/giant_spider/midwife
-
-/obj/structure/spider/spiderling/viper
- grow_as = /mob/living/basic/giant_spider/viper
-
-/obj/structure/spider/spiderling/tarantula
- grow_as = /mob/living/basic/giant_spider/tarantula
-
-/obj/structure/spider/spiderling/Bump(atom/user)
- if(istype(user, /obj/structure/table))
- forceMove(user.loc)
- else
- ..()
-
-/obj/structure/spider/spiderling/proc/cancel_vent_move()
- forceMove(entry_vent)
- entry_vent = null
-
-/obj/structure/spider/spiderling/proc/vent_move(obj/machinery/atmospherics/components/unary/vent_pump/exit_vent)
- if(QDELETED(exit_vent) || exit_vent.welded)
- cancel_vent_move()
- return
-
- forceMove(exit_vent)
- var/travel_time = round(get_dist(loc, exit_vent.loc) / 2)
- addtimer(CALLBACK(src, PROC_REF(do_vent_move), exit_vent, travel_time), travel_time)
-
-/obj/structure/spider/spiderling/proc/do_vent_move(obj/machinery/atmospherics/components/unary/vent_pump/exit_vent, travel_time)
- if(QDELETED(exit_vent) || exit_vent.welded)
- cancel_vent_move()
- return
-
- if(prob(50))
- audible_message(span_hear("You hear something scampering through the ventilation ducts."))
-
- addtimer(CALLBACK(src, PROC_REF(finish_vent_move), exit_vent), travel_time)
-
-/obj/structure/spider/spiderling/proc/finish_vent_move(obj/machinery/atmospherics/components/unary/vent_pump/exit_vent)
- if(QDELETED(exit_vent) || exit_vent.welded)
- cancel_vent_move()
- return
- forceMove(exit_vent.loc)
- entry_vent = null
-
-/obj/structure/spider/spiderling/process()
- if(travelling_in_vent)
- if(isturf(loc))
- travelling_in_vent = 0
- entry_vent = null
- else if(entry_vent)
- if(get_dist(src, entry_vent) <= 1)
- var/list/vents = list()
- var/datum/pipeline/entry_vent_parent = entry_vent.parents[1]
- for(var/obj/machinery/atmospherics/components/unary/vent_pump/temp_vent in entry_vent_parent.other_atmos_machines)
- vents.Add(temp_vent)
- if(!vents.len)
- entry_vent = null
- return
- var/obj/machinery/atmospherics/components/unary/vent_pump/exit_vent = pick(vents)
- if(prob(50))
- visible_message("[src] scrambles into the ventilation ducts!", \
- span_hear("You hear something scampering through the ventilation ducts."))
-
- addtimer(CALLBACK(src, PROC_REF(vent_move), exit_vent), rand(20,60))
-
- //=================
-
- else if(prob(33))
- var/list/nearby = oview(10, src)
- if(nearby.len)
- var/target_atom = pick(nearby)
- SSmove_manager.move_to(src, target_atom)
- if(prob(40))
- src.visible_message(span_notice("\The [src] skitters[pick(" away"," around","")]."))
- else if(prob(10))
- //ventcrawl!
- for(var/obj/machinery/atmospherics/components/unary/vent_pump/v in view(7,src))
- if(!v.welded)
- entry_vent = v
- SSmove_manager.move_to(src, entry_vent, 1)
- break
- if(isturf(loc))
- amount_grown += rand(0,2)
- if(amount_grown >= 100)
- if(!grow_as)
- if(prob(3))
- grow_as = pick(/mob/living/basic/giant_spider/tarantula, /mob/living/basic/giant_spider/viper, /mob/living/basic/giant_spider/midwife)
- else
- grow_as = pick(/mob/living/basic/giant_spider, /mob/living/basic/giant_spider/hunter, /mob/living/basic/giant_spider/nurse)
- var/mob/living/basic/giant_spider/S = new grow_as(src.loc)
- S.faction = faction.Copy()
- S.directive = directive
- qdel(src)
-
/obj/structure/spider/cocoon
name = "cocoon"
desc = "Something wrapped in silky spider web."
diff --git a/code/game/objects/items/weaponry.dm b/code/game/objects/items/weaponry.dm
index 05ae367297b..69110143d15 100644
--- a/code/game/objects/items/weaponry.dm
+++ b/code/game/objects/items/weaponry.dm
@@ -855,7 +855,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
/mob/living/simple_animal/butterfly,
/mob/living/basic/cockroach,
/obj/item/queen_bee,
- /obj/structure/spider/spiderling,
+ /mob/living/basic/spiderling,
/mob/living/simple_animal/hostile/ant,
/obj/effect/decal/cleanable/ants,
))
diff --git a/code/modules/antagonists/abductor/equipment/glands/spider.dm b/code/modules/antagonists/abductor/equipment/glands/spider.dm
index e5f5dc85245..52fe9a826b0 100644
--- a/code/modules/antagonists/abductor/equipment/glands/spider.dm
+++ b/code/modules/antagonists/abductor/equipment/glands/spider.dm
@@ -10,5 +10,5 @@
/obj/item/organ/internal/heart/gland/spiderman/activate()
to_chat(owner, span_warning("You feel something crawling in your skin."))
owner.faction |= FACTION_SPIDER
- var/obj/structure/spider/spiderling/S = new(owner.drop_location())
+ var/mob/living/basic/spiderling/S = new(owner.drop_location())
S.directive = "Protect your nest inside [owner.real_name]."
diff --git a/code/modules/hydroponics/hydroponics.dm b/code/modules/hydroponics/hydroponics.dm
index 1a770c6545f..50129f28a54 100644
--- a/code/modules/hydroponics/hydroponics.dm
+++ b/code/modules/hydroponics/hydroponics.dm
@@ -822,7 +822,7 @@
message_admins("[ADMIN_LOOKUPFLW(user)] last altered a hydro tray's contents which spawned spiderlings.")
user.log_message("last altered a hydro tray, which spiderlings spawned from.", LOG_GAME)
visible_message(span_warning("The pests seem to behave oddly..."))
- spawn_atom_to_turf(/obj/structure/spider/spiderling/hunter, src, 3, FALSE)
+ spawn_atom_to_turf(/mob/living/basic/spiderling/hunter, src, 3, FALSE)
else if(myseed)
visible_message(span_warning("The pests seem to behave oddly in [myseed.name] tray, but quickly settle down..."))
diff --git a/code/modules/mob/living/basic/space_fauna/giant_spider/giant_spider.dm b/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider.dm
similarity index 100%
rename from code/modules/mob/living/basic/space_fauna/giant_spider/giant_spider.dm
rename to code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider.dm
diff --git a/code/modules/mob/living/basic/space_fauna/giant_spider/giant_spider_ai.dm b/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_ai.dm
similarity index 100%
rename from code/modules/mob/living/basic/space_fauna/giant_spider/giant_spider_ai.dm
rename to code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_ai.dm
diff --git a/code/modules/mob/living/basic/space_fauna/giant_spider/spider_subtrees.dm b/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_subtrees.dm
similarity index 100%
rename from code/modules/mob/living/basic/space_fauna/giant_spider/spider_subtrees.dm
rename to code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_subtrees.dm
diff --git a/code/modules/mob/living/basic/space_fauna/giant_spider/spider_variants.dm b/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_variants.dm
similarity index 100%
rename from code/modules/mob/living/basic/space_fauna/giant_spider/spider_variants.dm
rename to code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_variants.dm
diff --git a/code/modules/mob/living/basic/space_fauna/giant_spider/spider_abilities/hivemind.dm b/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/hivemind.dm
similarity index 96%
rename from code/modules/mob/living/basic/space_fauna/giant_spider/spider_abilities/hivemind.dm
rename to code/modules/mob/living/basic/space_fauna/spider/spider_abilities/hivemind.dm
index 08a688c3d0b..7eb3126877d 100644
--- a/code/modules/mob/living/basic/space_fauna/giant_spider/spider_abilities/hivemind.dm
+++ b/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/hivemind.dm
@@ -62,7 +62,7 @@
if(!message)
return
var/my_message = span_spider("Command from [user]: [message]")
- for(var/mob/living/basic/giant_spider/spider as anything in GLOB.spidermobs)
+ for(var/mob/living/basic/spider as anything in GLOB.spidermobs)
to_chat(spider, my_message)
for(var/ghost in GLOB.dead_mob_list)
var/link = FOLLOW_LINK(ghost, user)
diff --git a/code/modules/mob/living/basic/space_fauna/giant_spider/spider_abilities/lay_eggs.dm b/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/lay_eggs.dm
similarity index 100%
rename from code/modules/mob/living/basic/space_fauna/giant_spider/spider_abilities/lay_eggs.dm
rename to code/modules/mob/living/basic/space_fauna/spider/spider_abilities/lay_eggs.dm
diff --git a/code/modules/mob/living/basic/space_fauna/giant_spider/spider_abilities/web.dm b/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/web.dm
similarity index 100%
rename from code/modules/mob/living/basic/space_fauna/giant_spider/spider_abilities/web.dm
rename to code/modules/mob/living/basic/space_fauna/spider/spider_abilities/web.dm
diff --git a/code/modules/mob/living/basic/space_fauna/giant_spider/spider_abilities/wrap.dm b/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/wrap.dm
similarity index 100%
rename from code/modules/mob/living/basic/space_fauna/giant_spider/spider_abilities/wrap.dm
rename to code/modules/mob/living/basic/space_fauna/spider/spider_abilities/wrap.dm
diff --git a/code/modules/mob/living/basic/space_fauna/spider/spiderlings/spiderling.dm b/code/modules/mob/living/basic/space_fauna/spider/spiderlings/spiderling.dm
new file mode 100644
index 00000000000..97904611e86
--- /dev/null
+++ b/code/modules/mob/living/basic/space_fauna/spider/spiderlings/spiderling.dm
@@ -0,0 +1,155 @@
+/**
+ * # Spiderlings
+ *
+ * Baby spiders that are generated through a variety of means (like botany for instance).
+ * Able to vent-crawl and eventually grow into a full fledged giant spider.
+ *
+ */
+/mob/living/basic/spiderling
+ name = "spiderling"
+ desc = "It never stays still for long."
+ icon_state = "spiderling"
+ icon_dead = "spiderling_dead"
+ faction = list(FACTION_SPIDER)
+ speed = 1
+ move_resist = INFINITY // YOU CAN'T HANDLE ME LET ME BE FREE LET ME BE FREE LET ME BE FREE
+ speak_emote = list("hisses")
+ basic_mob_flags = FLAMMABLE_MOB | DEL_ON_DEATH
+ mob_size = MOB_SIZE_TINY
+
+ unique_name = TRUE
+ gold_core_spawnable = HOSTILE_SPAWN // because of what we grow into!
+
+ // we have _some_ bite
+ melee_damage_lower = 2
+ melee_damage_upper = 4
+
+ health = 5 // very low.
+ maxHealth = 5
+ unsuitable_cold_damage = 4
+ unsuitable_heat_damage = 4
+ death_message = "lets out a final hiss..."
+
+ ai_controller = /datum/ai_controller/basic_controller/spiderling
+
+ // VERY red, to fit the eyes
+ lighting_cutoff_red = 22
+ lighting_cutoff_green = 5
+ lighting_cutoff_blue = 5
+
+ /// The mob we will grow into.
+ var/mob/living/basic/giant_spider/grow_as = null
+ /// The message that the mother left for our big strong selves.
+ var/directive = ""
+ /// Simple boolean that determines if we should apply the spider antag to the player if they possess this mob. TRUE by default since we're always going to evolve into a spider that will have an antagonistic role.
+ var/apply_spider_antag = TRUE
+
+/mob/living/basic/spiderling/Initialize(mapload)
+ . = ..()
+ // random placement since we're pretty small and to make the swarming component actually look like it's doing something when we have a buncha these fuckers
+ pixel_x = rand(6,-6)
+ pixel_y = rand(6,-6)
+
+ // the proc that handles passtable is nice but we should always be able to pass through table since we're so small so we can eschew adding that here
+ pass_flags |= PASSTABLE
+ ADD_TRAIT(src, TRAIT_PASSTABLE, INNATE_TRAIT)
+
+ ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT)
+ AddComponent(/datum/component/swarming)
+ AddElement(/datum/element/footstep, FOOTSTEP_MOB_CLAW, volume = 0.2) // they're small but you can hear 'em
+
+ // it's A-OKAY for grow_as to be null for the purposes of this component since we override that behavior anyhow.
+ AddComponent(\
+ /datum/component/growth_and_differentiation,\
+ growth_time = 10 MINUTES,\
+ growth_path = grow_as,\
+ growth_probability = 25,\
+ lower_growth_value = 1,\
+ upper_growth_value = 2,\
+ optional_checks = CALLBACK(src, PROC_REF(ready_to_grow)),\
+ optional_grow_behavior = CALLBACK(src, PROC_REF(grow_into_giant_spider))\
+ )
+
+ // keep in mind we have infinite range (the entire pipenet is our playground, it's just a matter of random choice as to where we end up) so lower and upper both have their gives and takes.
+ // but, also remember the more time we aren't in a vent, the more susceptible we are to dying to anything and everything.
+ // also remember we can't evolve if we're in a vent. lots to keep in mind when you set these variables.
+ ai_controller.set_blackboard_key(BB_LOWER_VENT_TIME_LIMIT, rand(9, 11) SECONDS)
+ ai_controller.set_blackboard_key(BB_UPPER_VENT_TIME_LIMIT, rand(12, 14) SECONDS)
+
+/mob/living/basic/spiderling/Destroy()
+ GLOB.spidermobs -= src
+ return ..()
+
+/mob/living/basic/spiderling/death(gibbed)
+ if(isturf(get_turf(loc)) && (basic_mob_flags & DEL_ON_DEATH || gibbed))
+ var/obj/item/food/spiderling/dead_spider = new(loc) // mmm yummy
+ dead_spider.name = name
+
+ return ..()
+
+/mob/living/basic/spiderling/Login() // this is only really here for admins dragging and dropping players into spiderlings, player control of spiderlings is otherwise unimplemented
+ . = ..()
+ if(!. || isnull(client))
+ return FALSE
+ basic_mob_flags &= ~DEL_ON_DEATH // we don't want to be deleted if we die while player controlled in case there's some revive schenanigans going on that can bring us back
+ GLOB.spidermobs[src] = TRUE
+ add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/player_spider_modifier, multiplicative_slowdown = -2) // let's pick up the tempo, we are meant to be fast after all
+ if (apply_spider_antag)
+ var/datum/antagonist/spider/spider_antag = new(directive)
+ mind.add_antag_datum(spider_antag)
+
+/mob/living/basic/spiderling/Logout()
+ . = ..()
+ remove_movespeed_modifier(/datum/movespeed_modifier/player_spider_modifier)
+
+/mob/living/basic/spiderling/mob_negates_gravity() // in case our sisters want to give us a helping hand
+ if(locate(/obj/structure/spider/stickyweb) in loc)
+ return TRUE
+ return ..()
+
+/mob/living/basic/spiderling/start_pulling(atom/movable/pulled_atom, state, force = move_force, supress_message = FALSE) // we're TOO FUCKING SMALL
+ return
+
+/// Checks to see if we're ready to grow, primarily if we are on solid ground and not in a vent or something.
+/// The component will automagically grow us when we return TRUE and that threshold has been met.
+/mob/living/basic/spiderling/proc/ready_to_grow()
+ if(isturf(loc))
+ return TRUE
+
+ return FALSE
+
+/// Actually grows the spiderling into a giant spider. We have to do a bunch of unique behavior that really can't be genericized, so we have to override the component in this manner.
+/mob/living/basic/spiderling/proc/grow_into_giant_spider()
+ if(isnull(grow_as))
+ if(prob(3))
+ grow_as = pick(/mob/living/basic/giant_spider/tarantula, /mob/living/basic/giant_spider/viper, /mob/living/basic/giant_spider/midwife)
+ else
+ grow_as = pick(/mob/living/basic/giant_spider, /mob/living/basic/giant_spider/hunter, /mob/living/basic/giant_spider/nurse)
+
+ var/mob/living/basic/giant_spider/grown = change_mob_type(grow_as, get_turf(src), initial(grow_as.name))
+ ADD_TRAIT(grown, TRAIT_WAS_EVOLVED, REF(src))
+ grown.faction = faction.Copy()
+ grown.directive = directive
+
+ qdel(src)
+
+/// Opportunistically hops in and out of vents, if it can find one. We aren't interested in attacking due to how weak we are, we gotta be quick and hidey.
+/datum/ai_controller/basic_controller/spiderling
+ blackboard = list(
+ BB_FLEE_TARGETTING_DATUM = new /datum/targetting_datum/basic/of_size/larger, // Run away from mobs bigger than we are
+ BB_BASIC_MOB_FLEEING = TRUE,
+ BB_VENTCRAWL_COOLDOWN = 20 SECONDS, // enough time to get splatted while we're out in the open.
+ BB_TIME_TO_GIVE_UP_ON_VENT_PATHING = 30 SECONDS,
+ )
+
+ ai_traits = STOP_MOVING_WHEN_PULLED
+ ai_movement = /datum/ai_movement/basic_avoidance
+ idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking
+
+ // We understand that vents are nice little hidey holes through epigenetic inheritance, so we'll use them.
+ planning_subtrees = list(
+ /datum/ai_planning_subtree/target_retaliate/to_flee,
+ /datum/ai_planning_subtree/flee_target/from_flee_key,
+ /datum/ai_planning_subtree/opportunistic_ventcrawler,
+ /datum/ai_planning_subtree/random_speech/insect,
+ )
diff --git a/code/modules/mob/living/basic/space_fauna/spider/spiderlings/spiderling_subtypes.dm b/code/modules/mob/living/basic/space_fauna/spider/spiderlings/spiderling_subtypes.dm
new file mode 100644
index 00000000000..7b8e8db811d
--- /dev/null
+++ b/code/modules/mob/living/basic/space_fauna/spider/spiderlings/spiderling_subtypes.dm
@@ -0,0 +1,25 @@
+// This whole file is just a container for the spiderling subtypes that actually differentiate into different giant spiders. None of them are particularly special as of now.
+
+/// Will differentiate into the base giant spider (known colloquially as the "guard" spider).
+/mob/living/basic/spiderling/guard
+ grow_as = /mob/living/basic/giant_spider
+
+/// Will differentiate into the "hunter" giant spider.
+/mob/living/basic/spiderling/hunter
+ grow_as = /mob/living/basic/giant_spider/hunter
+
+/// Will differentiate into the "nurse" giant spider.
+/mob/living/basic/spiderling/nurse
+ grow_as = /mob/living/basic/giant_spider/nurse
+
+/// Will differentiate into the "midwife" giant spider.
+/mob/living/basic/spiderling/midwife
+ grow_as = /mob/living/basic/giant_spider/midwife
+
+/// Will differentiate into the "viper" giant spider.
+/mob/living/basic/spiderling/viper
+ grow_as = /mob/living/basic/giant_spider/viper
+
+/// Will differentiate into the "tarantula" giant spider.
+/mob/living/basic/spiderling/tarantula
+ grow_as = /mob/living/basic/giant_spider/tarantula
diff --git a/code/modules/mob/living/ventcrawling.dm b/code/modules/mob/living/ventcrawling.dm
index 646659a6255..6632577873e 100644
--- a/code/modules/mob/living/ventcrawling.dm
+++ b/code/modules/mob/living/ventcrawling.dm
@@ -52,10 +52,12 @@
if(!can_enter_vent(ventcrawl_target))
return
+ var/has_client = !isnull(client) // clientless mobs can do this too! this is just stored in case the client disconnects while we sleep in do_after.
+
//Handle the exit here
if(HAS_TRAIT(src, TRAIT_MOVE_VENTCRAWLING) && istype(loc, /obj/machinery/atmospherics) && movement_type & VENTCRAWLING)
- visible_message(span_notice("[src] begins climbing out from the ventilation system...") ,span_notice("You begin climbing out from the ventilation system..."))
- if(!client)
+ visible_message(span_notice("[src] begins climbing out from the ventilation system..."), span_notice("You begin climbing out from the ventilation system..."))
+ if(has_client && isnull(client))
return
visible_message(span_notice("[src] scrambles out from the ventilation ducts!"),span_notice("You scramble out from the ventilation ducts."))
forceMove(ventcrawl_target.loc)
@@ -70,7 +72,7 @@
visible_message(span_notice("[src] begins climbing into the ventilation system...") ,span_notice("You begin climbing into the ventilation system..."))
if(!do_after(src, 2.5 SECONDS, target = ventcrawl_target, extra_checks = CALLBACK(src, PROC_REF(can_enter_vent), ventcrawl_target)))
return
- if(!client)
+ if(has_client && isnull(client))
return
ventcrawl_target.flick_overlay_static(image('icons/effects/vent_indicator.dmi', "insert", ABOVE_MOB_LAYER), 1 SECONDS)
visible_message(span_notice("[src] scrambles into the ventilation ducts!"),span_notice("You climb into the ventilation ducts."))
@@ -103,6 +105,8 @@
* We move first and then call update. Dont flip this around
*/
/mob/living/proc/update_pipe_vision(full_refresh = FALSE)
+ if(!isnull(ai_controller) && isnull(client)) // we don't care about pipe vision if we have an AI controller with no client (typically means we are clientless).
+ return
// Take away all the pipe images if we're not doing anything with em
if(isnull(client) || !HAS_TRAIT(src, TRAIT_MOVE_VENTCRAWLING) || !istype(loc, /obj/machinery/atmospherics) || !(movement_type & VENTCRAWLING))
diff --git a/code/modules/shuttle/supply.dm b/code/modules/shuttle/supply.dm
index 0708abf21c3..136be5afb43 100644
--- a/code/modules/shuttle/supply.dm
+++ b/code/modules/shuttle/supply.dm
@@ -2,7 +2,6 @@ GLOBAL_LIST_INIT(blacklisted_cargo_types, typecacheof(list(
/mob/living,
/obj/structure/blob,
/obj/effect/rune,
- /obj/structure/spider/spiderling,
/obj/item/disk/nuclear,
/obj/machinery/nuclearbomb,
/obj/item/beacon,
diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi
index df8c42cf461..4eb05b13b39 100644
Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ
diff --git a/icons/mob/simple/animal.dmi b/icons/mob/simple/animal.dmi
index 8875c1f27c6..71a1b944a74 100644
Binary files a/icons/mob/simple/animal.dmi and b/icons/mob/simple/animal.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index 97ce043d052..6124c0b6024 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -843,9 +843,11 @@
#include "code\datums\ai\basic_mobs\basic_ai_behaviors\targetting.dm"
#include "code\datums\ai\basic_mobs\basic_ai_behaviors\tipped_reaction.dm"
#include "code\datums\ai\basic_mobs\basic_ai_behaviors\travel_towards.dm"
+#include "code\datums\ai\basic_mobs\basic_ai_behaviors\ventcrawling.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\attack_obstacle_in_path.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\find_food.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\flee_target.dm"
+#include "code\datums\ai\basic_mobs\basic_subtrees\opportunistic_ventcrawler.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\simple_attack_target.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\simple_find_nearest_target_to_flee.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\simple_find_target.dm"
@@ -1000,6 +1002,7 @@
#include "code\datums\components\gps.dm"
#include "code\datums\components\grillable.dm"
#include "code\datums\components\ground_sinking.dm"
+#include "code\datums\components\growth_and_differentiation.dm"
#include "code\datums\components\gunpoint.dm"
#include "code\datums\components\hazard_area.dm"
#include "code\datums\components\healing_touch.dm"
@@ -4015,14 +4018,6 @@
#include "code\modules\mob\living\basic\space_fauna\carp\carp_controllers.dm"
#include "code\modules\mob\living\basic\space_fauna\carp\magicarp.dm"
#include "code\modules\mob\living\basic\space_fauna\carp\megacarp.dm"
-#include "code\modules\mob\living\basic\space_fauna\giant_spider\giant_spider.dm"
-#include "code\modules\mob\living\basic\space_fauna\giant_spider\giant_spider_ai.dm"
-#include "code\modules\mob\living\basic\space_fauna\giant_spider\spider_subtrees.dm"
-#include "code\modules\mob\living\basic\space_fauna\giant_spider\spider_variants.dm"
-#include "code\modules\mob\living\basic\space_fauna\giant_spider\spider_abilities\hivemind.dm"
-#include "code\modules\mob\living\basic\space_fauna\giant_spider\spider_abilities\lay_eggs.dm"
-#include "code\modules\mob\living\basic\space_fauna\giant_spider\spider_abilities\web.dm"
-#include "code\modules\mob\living\basic\space_fauna\giant_spider\spider_abilities\wrap.dm"
#include "code\modules\mob\living\basic\space_fauna\meteor_heart\chasing_spikes.dm"
#include "code\modules\mob\living\basic\space_fauna\meteor_heart\meteor_eyeball.dm"
#include "code\modules\mob\living\basic\space_fauna\meteor_heart\meteor_heart.dm"
@@ -4031,6 +4026,16 @@
#include "code\modules\mob\living\basic\space_fauna\netherworld\blankbody.dm"
#include "code\modules\mob\living\basic\space_fauna\netherworld\creature.dm"
#include "code\modules\mob\living\basic\space_fauna\netherworld\migo.dm"
+#include "code\modules\mob\living\basic\space_fauna\spider\giant_spider\giant_spider.dm"
+#include "code\modules\mob\living\basic\space_fauna\spider\giant_spider\giant_spider_ai.dm"
+#include "code\modules\mob\living\basic\space_fauna\spider\giant_spider\giant_spider_subtrees.dm"
+#include "code\modules\mob\living\basic\space_fauna\spider\giant_spider\giant_spider_variants.dm"
+#include "code\modules\mob\living\basic\space_fauna\spider\spider_abilities\hivemind.dm"
+#include "code\modules\mob\living\basic\space_fauna\spider\spider_abilities\lay_eggs.dm"
+#include "code\modules\mob\living\basic\space_fauna\spider\spider_abilities\web.dm"
+#include "code\modules\mob\living\basic\space_fauna\spider\spider_abilities\wrap.dm"
+#include "code\modules\mob\living\basic\space_fauna\spider\spiderlings\spiderling.dm"
+#include "code\modules\mob\living\basic\space_fauna\spider\spiderlings\spiderling_subtypes.dm"
#include "code\modules\mob\living\basic\space_fauna\statue\statue.dm"
#include "code\modules\mob\living\basic\space_fauna\wumborian_fugu\fugu_gland.dm"
#include "code\modules\mob\living\basic\space_fauna\wumborian_fugu\inflation.dm"
diff --git a/tools/UpdatePaths/Scripts/spiderlings_structures_to_mobs.txt b/tools/UpdatePaths/Scripts/spiderlings_structures_to_mobs.txt
new file mode 100644
index 00000000000..0c4683eef30
--- /dev/null
+++ b/tools/UpdatePaths/Scripts/spiderlings_structures_to_mobs.txt
@@ -0,0 +1 @@
+/obj/structure/spider/spiderling/@SUBTYPES : /mob/living/basic/spiderling/@SUBTYPES{@OLD}