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>
This commit is contained in:
PollardTheDragon
2025-10-01 16:25:57 +00:00
committed by GitHub
co-authored by Luc
parent 91a83e7056
commit da37a3a59d
11 changed files with 137 additions and 58 deletions
+2
View File
@@ -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"
@@ -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
+46 -41
View File
@@ -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("<span class='warning'>The corpse of [H.name] suddenly rises!</span>")
/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 = "<span class='blob'>Blob Telepathy, <span class='name'>[name]([overmind])</span>[follow_text] <span class='message'>states, \"[message]\"</span></span>"
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)
+44
View File
@@ -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 ..()
+8 -9
View File
@@ -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, "<span class='biggerdanger'>You are a blobbernaut! You must assist all blob lifeforms in their mission to consume everything!</span>")
to_chat(blobber, "<span class='danger'>You heal while standing on blob structures, however you will decay slowly if you are damaged outside of the blob.</span>")
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, <B>[speak_text]</B>")
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: <b><font color=\"[blob_reagent_datum.color]\">[blob_reagent_datum.name]</b></font> - [blob_reagent_datum.description]")
@@ -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)
+3 -3
View File
@@ -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 = "<span class='blob'>Blob Telepathy, <span class='name'>[name]([blob_reagent_datum.name])</span>[follow_text] <span class='message'>states, \"[message]\"</span></span>"
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)
@@ -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")
+2
View File
@@ -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"
@@ -0,0 +1 @@
/mob/living/simple_animal/hostile/blob/@SUBTYPES : /mob/living/basic/blob/@SUBTYPES{@OLD}
-3
View File
@@ -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",