From da37a3a59dd83006dce521c9faeecce95647572a Mon Sep 17 00:00:00 2001
From: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com>
Date: Wed, 1 Oct 2025 12:25:57 -0400
Subject: [PATCH] Converts blob mobs to basic mobs (#29806)
* Converts blob mobs to basic mobs
* Linters
* Updatepaths
* Apply review changes
Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Signed-off-by: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com>
* New linters
* Intents
* Lints
* Fixes blobbernaut AI being active after being offered
* Linters
---------
Signed-off-by: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com>
Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
---
code/__DEFINES/ai/blackboard_defines.dm | 2 +
.../basic_subtrees/travel_to_point.dm | 17 ++++
code/modules/events/blob/blob_mobs.dm | 87 ++++++++++---------
code/modules/events/blob/blob_mobs_ai.dm | 44 ++++++++++
code/modules/events/blob/blob_powers.dm | 17 ++--
.../events/blob/blob_structures/factory.dm | 4 +-
code/modules/events/blob/overmind.dm | 6 +-
code/modules/mob/living/basic/basic_mob.dm | 12 +++
paradise.dme | 2 +
tools/UpdatePaths/Scripts/29806_blob_mobs.txt | 1 +
tools/ci/check_simplemob_additions.py | 3 -
11 files changed, 137 insertions(+), 58 deletions(-)
create mode 100644 code/datums/ai/basic_mobs/basic_subtrees/travel_to_point.dm
create mode 100644 code/modules/events/blob/blob_mobs_ai.dm
create mode 100644 tools/UpdatePaths/Scripts/29806_blob_mobs.txt
diff --git a/code/__DEFINES/ai/blackboard_defines.dm b/code/__DEFINES/ai/blackboard_defines.dm
index 48dbccf7fa3..c74daddbd25 100644
--- a/code/__DEFINES/ai/blackboard_defines.dm
+++ b/code/__DEFINES/ai/blackboard_defines.dm
@@ -211,6 +211,8 @@
#define BB_INCURSION_HOME_PORTAL_TARGET "BB_INCURSION_HOME_PORTAL_TARGET"
#define BB_PROWL_TARGET "BB_PROWL_TARGET"
+#define BB_TRAVEL_DESTINATION "BB_TRAVEL_DESTINATION"
+
// Giant Spiders
/// Our webbing target
#define BB_SPIDER_WEB_TARGET "BB_spider_web_target"
diff --git a/code/datums/ai/basic_mobs/basic_subtrees/travel_to_point.dm b/code/datums/ai/basic_mobs/basic_subtrees/travel_to_point.dm
new file mode 100644
index 00000000000..3f8f07ff9de
--- /dev/null
+++ b/code/datums/ai/basic_mobs/basic_subtrees/travel_to_point.dm
@@ -0,0 +1,17 @@
+/// Simply walk to a location
+/datum/ai_planning_subtree/travel_to_point
+ /// Blackboard key where we travel a place we walk to
+ var/location_key = BB_TRAVEL_DESTINATION
+ /// What do we do in order to travel
+ var/travel_behaviour = /datum/ai_behavior/travel_towards
+
+/datum/ai_planning_subtree/travel_to_point/select_behaviors(datum/ai_controller/controller, seconds_per_tick)
+ . = ..()
+ var/atom/target = controller.blackboard[location_key]
+ if(QDELETED(target))
+ return
+ controller.queue_behavior(travel_behaviour, location_key)
+ return SUBTREE_RETURN_FINISH_PLANNING
+
+/datum/ai_planning_subtree/travel_to_point/and_clear_target
+ travel_behaviour = /datum/ai_behavior/travel_towards/stop_on_arrival
diff --git a/code/modules/events/blob/blob_mobs.dm b/code/modules/events/blob/blob_mobs.dm
index 9ab14900917..c7a720fe54a 100644
--- a/code/modules/events/blob/blob_mobs.dm
+++ b/code/modules/events/blob/blob_mobs.dm
@@ -3,47 +3,50 @@
// BASE TYPE //
////////////////
-//Do not spawn
-/mob/living/simple_animal/hostile/blob
+// Do not spawn
+/mob/living/basic/blob
icon = 'icons/mob/blob.dmi'
pass_flags = PASSBLOB
- status_flags = NONE //No throwing blobspores into deep space to despawn, or throwing blobbernaughts, which are bigger than you.
+ status_flags = NONE // No throwing blobspores into deep space to despawn, or throwing blobbernaughts, which are bigger than you.
faction = list(ROLE_BLOB)
bubble_icon = "blob"
atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
- minbodytemp = 0
- maxbodytemp = 360
- universal_speak = TRUE //So mobs can understand them when a blob uses Blob Broadcast
+ melee_attack_cooldown_min = 1.5 SECONDS
+ melee_attack_cooldown_max = 2.5 SECONDS
+ minimum_survivable_temperature = 0
+ maximum_survivable_temperature = 360
+ universal_speak = TRUE // So mobs can understand them when a blob uses Blob Broadcast
sentience_type = SENTIENCE_OTHER
+ a_intent = INTENT_HARM
can_be_on_fire = TRUE
fire_damage = 3
var/mob/camera/blob/overmind = null
-/mob/living/simple_animal/hostile/blob/Initialize(mapload)
+/mob/living/basic/blob/Initialize(mapload)
. = ..()
GLOB.blob_minions |= src
-/mob/living/simple_animal/hostile/blob/Destroy()
+/mob/living/basic/blob/Destroy()
GLOB.blob_minions -= src
return ..()
-/mob/living/simple_animal/hostile/blob/proc/adjustcolors(a_color)
+/mob/living/basic/blob/proc/adjustcolors(a_color)
if(a_color)
color = a_color
-/mob/living/simple_animal/hostile/blob/blob_act()
+/mob/living/basic/blob/blob_act()
if(stat != DEAD && health < maxHealth)
for(var/i in 1 to 2)
- var/obj/effect/temp_visual/heal/H = new /obj/effect/temp_visual/heal(get_turf(src)) //hello yes you are being healed
+ var/obj/effect/temp_visual/heal/H = new /obj/effect/temp_visual/heal(get_turf(src)) // hello yes you are being healed
if(overmind)
H.color = overmind.blob_reagent_datum.complementary_color
- if(H.color == COMPLEMENTARY_COLOR_RIPPING_TENDRILS) //the colour define for ripping tendrils
- H.color = COLOR_HEALING_GREEN //bye red cross
+ if(H.color == COMPLEMENTARY_COLOR_RIPPING_TENDRILS) // the colour define for ripping tendrils
+ H.color = COLOR_HEALING_GREEN // bye red cross
else
H.color = COLOR_BLACK
adjustHealth(-maxHealth * 0.0125)
-/mob/living/simple_animal/hostile/blob/Process_Spacemove(movement_dir = 0, continuous_move = FALSE)
+/mob/living/basic/blob/Process_Spacemove(movement_dir = 0, continuous_move = FALSE)
// Use any nearby blob structures to allow space moves.
for(var/obj/structure/blob/B in range(1, src))
return TRUE
@@ -53,7 +56,7 @@
// BLOB SPORE //
////////////////
-/mob/living/simple_animal/hostile/blob/blobspore
+/mob/living/basic/blob/blobspore
name = "blob"
desc = "Some blob thing."
icon_state = "blobpod"
@@ -63,37 +66,38 @@
melee_damage_lower = 2
melee_damage_upper = 4
obj_damage = 20
- attacktext = "hits"
+ environment_smash = ENVIRONMENT_SMASH_STRUCTURES
+ attack_verb_continuous = "hits"
+ attack_verb_simple = "hit"
attack_sound = 'sound/weapons/genhit1.ogg'
initial_traits = list(TRAIT_FLYING)
speak_emote = list("pulses")
+ ai_controller = /datum/ai_controller/basic_controller/blob_spore
var/obj/structure/blob/factory/factory = null
var/list/human_overlays = list()
var/mob/living/carbon/human/oldguy
var/is_zombie = FALSE
-/mob/living/simple_animal/hostile/blob/blobspore/CanPass(atom/movable/mover, border_dir)
+/mob/living/basic/blob/blobspore/CanPass(atom/movable/mover, border_dir)
+ . = ..()
if(istype(mover, /obj/structure/blob))
return 1
return ..()
-/mob/living/simple_animal/hostile/blob/blobspore/Initialize(mapload, obj/structure/blob/factory/linked_node)
+/mob/living/basic/blob/blobspore/Initialize(mapload, obj/structure/blob/factory/linked_node)
. = ..()
if(istype(linked_node))
factory = linked_node
factory.spores += src
GLOB.spores_active++
-/mob/living/simple_animal/hostile/blob/blobspore/Life(seconds, times_fired)
+/mob/living/basic/blob/blobspore/melee_attack(mob/living/carbon/human/target, list/modifiers, ignore_cooldown)
+ . = ..()
+ if(!ishuman(target) || target.stat != DEAD)
+ return
+ zombify(target)
- if(!is_zombie && isturf(src.loc))
- for(var/mob/living/carbon/human/H in oview(src, 1)) //Only for corpse right next to/on same tile
- if(H.stat == DEAD || (!H.check_death_method() && H.health <= HEALTH_THRESHOLD_DEAD))
- Zombify(H)
- break
- ..()
-
-/mob/living/simple_animal/hostile/blob/blobspore/proc/Zombify(mob/living/carbon/human/H)
+/mob/living/basic/blob/blobspore/proc/zombify(mob/living/carbon/human/H)
if(!H.check_death_method())
H.death()
var/obj/item/organ/external/head/head_organ = H.get_organ("head")
@@ -101,7 +105,7 @@
if(H.wear_suit)
var/obj/item/clothing/suit/armor/A = H.wear_suit
if(A.armor && A.armor.getRating(MELEE))
- maxHealth += A.armor.getRating(MELEE) //That zombie's got armor, I want armor!
+ maxHealth += A.armor.getRating(MELEE) // That zombie's got armor, I want armor!
maxHealth += 40
health = maxHealth
name = "blob zombie"
@@ -121,7 +125,7 @@
oldguy = H
visible_message("The corpse of [H.name] suddenly rises!")
-/mob/living/simple_animal/hostile/blob/blobspore/death(gibbed)
+/mob/living/basic/blob/blobspore/death(gibbed)
// Only execute the below if we successfuly died
. = ..()
if(!.)
@@ -143,7 +147,7 @@
S.start()
qdel(src)
-/mob/living/simple_animal/hostile/blob/blobspore/Destroy()
+/mob/living/basic/blob/blobspore/Destroy()
if(factory)
factory.spores -= src
factory = null
@@ -154,12 +158,12 @@
return ..()
-/mob/living/simple_animal/hostile/blob/blobspore/update_icons()
+/mob/living/basic/blob/blobspore/update_icons()
..()
adjustcolors(overmind?.blob_reagent_datum?.complementary_color)
-/mob/living/simple_animal/hostile/blob/blobspore/adjustcolors(a_color)
+/mob/living/basic/blob/blobspore/adjustcolors(a_color)
color = a_color
if(is_zombie)
@@ -173,7 +177,7 @@
// BLOBBERNAUT //
/////////////////
-/mob/living/simple_animal/hostile/blob/blobbernaut
+/mob/living/basic/blob/blobbernaut
name = "blobbernaut"
desc = "Some HUGE blob thing."
icon_state = "blobbernaut"
@@ -184,7 +188,8 @@
melee_damage_lower = 10
melee_damage_upper = 15
obj_damage = 60
- attacktext = "hits"
+ attack_verb_continuous = "hits"
+ attack_verb_simple = "hit"
attack_sound = 'sound/effects/blobattack.ogg'
speak_emote = list("gurgles")
force_threshold = 10
@@ -193,11 +198,11 @@
see_in_dark = 8
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
move_resist = MOVE_FORCE_OVERPOWERING
- a_intent = INTENT_HARM
contains_xeno_organ = TRUE
surgery_container = /datum/xenobiology_surgery_container/blobbernaut
+ ai_controller = /datum/ai_controller/basic_controller/blobbernaut
-/mob/living/simple_animal/hostile/blob/blobbernaut/Initialize(mapload)
+/mob/living/basic/blob/blobbernaut/Initialize(mapload)
. = ..()
var/datum/action/innate/communicate_overmind_blob/overmind_chat = new
overmind_chat.Grant(src)
@@ -210,12 +215,12 @@
button_icon_state = "communicate"
/datum/action/innate/communicate_overmind_blob/Activate()
- var/mob/living/simple_animal/hostile/blob/blobbernaut/user = owner
+ var/mob/living/basic/blob/blobbernaut/user = owner
if(user.stat)
return
user.blob_talk()
-/mob/living/simple_animal/hostile/blob/blobbernaut/Life(seconds, times_fired)
+/mob/living/basic/blob/blobbernaut/Life(seconds, times_fired)
if(stat != DEAD && (getBruteLoss() || getFireLoss())) // Heal on blob structures
if(locate(/obj/structure/blob) in get_turf(src))
adjustBruteLoss(-0.25)
@@ -227,7 +232,7 @@
adjustFireLoss(0.2)
..()
-/mob/living/simple_animal/hostile/blob/blobbernaut/death(gibbed)
+/mob/living/basic/blob/blobbernaut/death(gibbed)
// Only execute the below if we successfully died
. = ..()
if(!.)
@@ -235,7 +240,7 @@
move_resist = null
flick("blobbernaut_death", src)
-/mob/living/simple_animal/hostile/blob/blobbernaut/proc/blob_talk()
+/mob/living/basic/blob/blobbernaut/proc/blob_talk()
var/message = tgui_input_text(usr, "Announce to the overmind", "Blob Telepathy")
var/rendered
var/follow_text
@@ -243,5 +248,5 @@
for(var/mob/M in GLOB.mob_list)
follow_text = isobserver(M) ? " ([ghost_follow_link(src, ghost = M)])" : ""
rendered = "Blob Telepathy, [name]([overmind])[follow_text] states, \"[message]\""
- if(isovermind(M) || isobserver(M) || istype(M, /mob/living/simple_animal/hostile/blob/blobbernaut))
+ if(isovermind(M) || isobserver(M) || istype(M, /mob/living/basic/blob/blobbernaut))
M.show_message(rendered, EMOTE_AUDIBLE)
diff --git a/code/modules/events/blob/blob_mobs_ai.dm b/code/modules/events/blob/blob_mobs_ai.dm
new file mode 100644
index 00000000000..45b7c0b5af1
--- /dev/null
+++ b/code/modules/events/blob/blob_mobs_ai.dm
@@ -0,0 +1,44 @@
+/**
+ * Extremely simple AI, this isn't a very smart boy
+ * Only notable quirk is that it uses JPS movement, simple avoidance would fail to realise it can path through blobs
+ */
+/datum/ai_controller/basic_controller/blobbernaut
+ blackboard = list(
+ BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
+ BB_TARGET_MINIMUM_STAT = UNCONSCIOUS,
+ )
+
+ ai_movement = /datum/ai_movement/jps
+ idle_behavior = /datum/idle_behavior/idle_random_walk
+ planning_subtrees = list(
+ /datum/ai_planning_subtree/simple_find_target,
+ /datum/ai_planning_subtree/attack_obstacle_in_path,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree,
+ )
+
+/**
+ * As blob zombie but will prioritise attacking corpses to zombify them
+ */
+/datum/ai_controller/basic_controller/blob_spore
+ blackboard = list(
+ BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
+ BB_TARGET_MINIMUM_STAT = UNCONSCIOUS,
+ )
+
+ ai_movement = /datum/ai_movement/jps
+ idle_behavior = /datum/idle_behavior/idle_random_walk
+ planning_subtrees = list(
+ /datum/ai_planning_subtree/find_and_hunt_target/corpses/human/blob,
+ /datum/ai_planning_subtree/travel_to_point/and_clear_target,
+ /datum/ai_planning_subtree/simple_find_target,
+ /datum/ai_planning_subtree/attack_obstacle_in_path,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree,
+ )
+
+/datum/ai_planning_subtree/find_and_hunt_target/corpses/human/blob
+
+/datum/ai_planning_subtree/find_and_hunt_target/corpses/human/blob/select_behaviors(datum/ai_controller/controller, seconds_per_tick)
+ var/mob/living/basic/blob/blobspore/spore = controller.pawn
+ if(spore.is_zombie)
+ return
+ return ..()
diff --git a/code/modules/events/blob/blob_powers.dm b/code/modules/events/blob/blob_powers.dm
index bf47cb83956..e3821ebf650 100644
--- a/code/modules/events/blob/blob_powers.dm
+++ b/code/modules/events/blob/blob_powers.dm
@@ -228,12 +228,11 @@
if(!can_buy(60))
return
- var/mob/living/simple_animal/hostile/blob/blobbernaut/blobber = new /mob/living/simple_animal/hostile/blob/blobbernaut (get_turf(B))
+ var/mob/living/basic/blob/blobbernaut/blobber = new /mob/living/basic/blob/blobbernaut (get_turf(B))
if(blobber)
qdel(B)
add_mob_to_overmind(blobber)
- blobber.AIStatus = AI_OFF
- blobber.LoseTarget()
+ blobber.ai_controller.set_ai_status(AI_STATUS_OFF)
spawn()
var/list/candidates = SSghost_spawns.poll_candidates("Do you want to play as a blobbernaut?", ROLE_BLOB, TRUE, 10 SECONDS, source = blobber, role_cleanname = "blobbernaut")
if(length(candidates) && !QDELETED(blobber))
@@ -244,7 +243,7 @@
to_chat(blobber, "You are a blobbernaut! You must assist all blob lifeforms in their mission to consume everything!")
to_chat(blobber, "You heal while standing on blob structures, however you will decay slowly if you are damaged outside of the blob.")
if(!blobber.ckey)
- blobber.AIStatus = AI_ON
+ blobber.ai_controller.set_ai_status(AI_STATUS_ON)
return
@@ -365,10 +364,10 @@
if(!length(surrounding_turfs))
return
- for(var/mob/living/simple_animal/hostile/blob/blobspore/BS in GLOB.alive_mob_list)
+ for(var/mob/living/basic/blob/blobspore/BS in GLOB.alive_mob_list)
if(isturf(BS.loc) && get_dist(BS, T) <= 35)
- BS.LoseTarget()
- BS.Goto(pick(surrounding_turfs), BS.move_to_delay)
+ BS.ai_controller.clear_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET)
+ BS.ai_controller.set_blackboard_key(BB_TRAVEL_DESTINATION, pick(surrounding_turfs))
return
/mob/camera/blob/verb/split_consciousness()
@@ -414,7 +413,7 @@
return
else
to_chat(usr, "You broadcast with your minions, [speak_text]")
- for(var/mob/living/simple_animal/hostile/blob_minion in blob_mobs)
+ for(var/mob/living/basic/blob_minion in blob_mobs)
if(blob_minion.stat == CONSCIOUS)
blob_minion.say(speak_text)
@@ -472,7 +471,7 @@
for(var/obj/structure/blob/BL in GLOB.blobs)
BL.adjustcolors(blob_reagent_datum.color)
- for(var/mob/living/simple_animal/hostile/blob/BLO in GLOB.blob_minions)
+ for(var/mob/living/basic/blob/BLO in GLOB.blob_minions)
BLO.adjustcolors(blob_reagent_datum.complementary_color)
to_chat(src, "Your reagent is now: [blob_reagent_datum.name] - [blob_reagent_datum.description]")
diff --git a/code/modules/events/blob/blob_structures/factory.dm b/code/modules/events/blob/blob_structures/factory.dm
index 047476ac579..c2aa113d154 100644
--- a/code/modules/events/blob/blob_structures/factory.dm
+++ b/code/modules/events/blob/blob_structures/factory.dm
@@ -10,7 +10,7 @@ GLOBAL_VAR_INIT(spores_active, 0)
var/spore_delay = 0
/obj/structure/blob/factory/Destroy()
- for(var/mob/living/simple_animal/hostile/blob/blobspore/spore in spores)
+ for(var/mob/living/basic/blob/blobspore/spore in spores)
if(spore.factory == src)
spore.factory = null
spores = null
@@ -23,7 +23,7 @@ GLOBAL_VAR_INIT(spores_active, 0)
return
flick("blob_factory_glow", src)
spore_delay = world.time + 10 SECONDS
- var/mob/living/simple_animal/hostile/blob/blobspore/BS = new/mob/living/simple_animal/hostile/blob/blobspore(src.loc, src)
+ var/mob/living/basic/blob/blobspore/BS = new/mob/living/basic/blob/blobspore(src.loc, src)
if(overmind)
overmind.add_mob_to_overmind(BS)
diff --git a/code/modules/events/blob/overmind.dm b/code/modules/events/blob/overmind.dm
index 87c10f1e3a0..2536406ffac 100644
--- a/code/modules/events/blob/overmind.dm
+++ b/code/modules/events/blob/overmind.dm
@@ -79,13 +79,13 @@
blob_talk(message)
-/mob/camera/blob/proc/add_mob_to_overmind(mob/living/simple_animal/hostile/blob/B)
+/mob/camera/blob/proc/add_mob_to_overmind(mob/living/basic/blob/B)
B.color = blob_reagent_datum?.complementary_color
B.overmind = src
blob_mobs += B
RegisterSignal(B, COMSIG_PARENT_QDELETING, PROC_REF(on_blob_mob_death))
-/mob/camera/blob/proc/on_blob_mob_death(mob/living/simple_animal/hostile/blob/B)
+/mob/camera/blob/proc/on_blob_mob_death(mob/living/basic/blob/B)
blob_mobs -= B
/mob/camera/blob/proc/blob_talk(message)
@@ -101,7 +101,7 @@
for(var/mob/M in GLOB.mob_list)
follow_text = isobserver(M) ? " ([ghost_follow_link(src, ghost=M)])" : ""
rendered = "Blob Telepathy, [name]([blob_reagent_datum.name])[follow_text] states, \"[message]\""
- if(isovermind(M) || isobserver(M) || istype(M, /mob/living/simple_animal/hostile/blob/blobbernaut))
+ if(isovermind(M) || isobserver(M) || istype(M, /mob/living/basic/blob/blobbernaut))
M.show_message(rendered, EMOTE_AUDIBLE)
/mob/camera/blob/blob_act(obj/structure/blob/B)
diff --git a/code/modules/mob/living/basic/basic_mob.dm b/code/modules/mob/living/basic/basic_mob.dm
index 406b3fd7cbb..a066d136a86 100644
--- a/code/modules/mob/living/basic/basic_mob.dm
+++ b/code/modules/mob/living/basic/basic_mob.dm
@@ -144,6 +144,10 @@ RESTRICT_TYPE(/mob/living/basic)
var/melee_attack_cooldown_min = 2 SECONDS
/// Upper bound for melee attack cooldown
var/melee_attack_cooldown_max = 2 SECONDS
+ /// Can this mob ignite?
+ var/can_be_on_fire = FALSE
+ /// How much fire damage does a mob take?
+ var/fire_damage = 2
/// Loot this mob drops on death.
var/list/loot = list()
@@ -207,6 +211,14 @@ RESTRICT_TYPE(/mob/living/basic)
/mob/living/basic/proc/apply_temperature_requirements()
AddElement(/datum/element/body_temperature, minimum_survivable_temperature, maximum_survivable_temperature, unsuitable_cold_damage, unsuitable_heat_damage)
+/mob/living/basic/handle_fire()
+ if(!can_be_on_fire)
+ return FALSE
+ . = ..()
+ if(!.)
+ return
+ adjustFireLoss(fire_damage) // Slowly start dying from being on fire
+
/mob/living/basic/vv_edit_var(vname, vval)
switch(vname)
if("atmos_requirements", "unsuitable_atmos_damage")
diff --git a/paradise.dme b/paradise.dme
index 1e1b88e96bb..4ff5bcba9d2 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -493,6 +493,7 @@
#include "code\datums\ai\basic_mobs\basic_subtrees\target_retaliate.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\targeted_mob_ability.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\tip_reaction.dm"
+#include "code\datums\ai\basic_mobs\basic_subtrees\travel_to_point.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\use_mob_ability.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\ventcrawl_find_target.dm"
#include "code\datums\ai\basic_mobs\basic_subtrees\ventcrawl_subtree.dm"
@@ -2131,6 +2132,7 @@
#include "code\modules\events\wallrot.dm"
#include "code\modules\events\wormholes.dm"
#include "code\modules\events\blob\blob_mobs.dm"
+#include "code\modules\events\blob\blob_mobs_ai.dm"
#include "code\modules\events\blob\blob_powers.dm"
#include "code\modules\events\blob\overmind.dm"
#include "code\modules\events\blob\theblob.dm"
diff --git a/tools/UpdatePaths/Scripts/29806_blob_mobs.txt b/tools/UpdatePaths/Scripts/29806_blob_mobs.txt
new file mode 100644
index 00000000000..0e0a5266e98
--- /dev/null
+++ b/tools/UpdatePaths/Scripts/29806_blob_mobs.txt
@@ -0,0 +1 @@
+/mob/living/simple_animal/hostile/blob/@SUBTYPES : /mob/living/basic/blob/@SUBTYPES{@OLD}
diff --git a/tools/ci/check_simplemob_additions.py b/tools/ci/check_simplemob_additions.py
index 93809441411..106fd087029 100644
--- a/tools/ci/check_simplemob_additions.py
+++ b/tools/ci/check_simplemob_additions.py
@@ -72,9 +72,6 @@ BURNDOWN_LIST = {
"/mob/living/simple_animal/hostile/asteroid/goliath/beast/ancient",
"/mob/living/simple_animal/hostile/asteroid/goliath/beast/tendril",
"/mob/living/simple_animal/hostile/asteroid/goliath/space",
- "/mob/living/simple_animal/hostile/blob",
- "/mob/living/simple_animal/hostile/blob/blobbernaut",
- "/mob/living/simple_animal/hostile/blob/blobspore",
"/mob/living/simple_animal/hostile/clockwork_construct",
"/mob/living/simple_animal/hostile/clockwork_construct/clockwork_marauder",
"/mob/living/simple_animal/hostile/clockwork_construct/clockwork_marauder/hostile",