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",