Crashed Vulpkanin Ship - Lavaland Ruin (#32110)

* Charon's Revolver

* Dog tags

* Ash Whelps

* Ash walkers and resist adjustment

* Some providence stuff

* Minor wording adjustment

* Desc change

* Ruin, salvage, and ruin assets

* linters

* Mo linters

* quick

* Various fixes

* Variety of fixes

* Update

* Naming

* FIX
This commit is contained in:
PollardTheDragon
2026-06-24 18:37:10 -04:00
committed by GitHub
parent 9d76cfee8e
commit 4d5b0e1286
28 changed files with 5968 additions and 2 deletions
@@ -0,0 +1,103 @@
/mob/living/basic/ash_walker
name = "ash walker"
desc = "These reptillian creatures appear to be related to the Unathi, but seem significantly less evolved."
icon = 'icons/mob/simple_human.dmi'
icon_state = "ashwalker"
icon_living = "ashwalker"
icon_dead = "ashwalker_dead" // Does not actually exist. del_on_death.
icon_gib = "ashwalker_gib" // Does not actually exist. del_on_death.
mob_biotypes = MOB_ORGANIC | MOB_HUMANOID
speed = -0.8
harm_intent_damage = 5
melee_damage_lower = 10
melee_damage_upper = 15
melee_attack_cooldown_min = 1 SECONDS
melee_attack_cooldown_max = 1.5 SECONDS
attack_verb_continuous = "stabs"
attack_verb_simple = "stab"
attack_sound = 'sound/weapons/bladeslice.ogg'
a_intent = INTENT_HARM
ai_controller = /datum/ai_controller/basic_controller/simple/ash_walker
faction = list("ashwalker")
weather_immunities = list("lava","ash")
unsuitable_atmos_damage = 0
minimum_survivable_temperature = 0
maximum_survivable_temperature = INFINITY
loot = list(/obj/effect/mob_spawn/human/corpse/ashwalker,
/obj/item/spear,
/obj/item/stack/ore/iron{layer = ABOVE_MOB_LAYER})
basic_mob_flags = DEL_ON_DEATH
sentience_type = SENTIENCE_OTHER
step_type = FOOTSTEP_MOB_SHOE
/mob/living/basic/ash_walker/Initialize(mapload)
. = ..()
add_language("Sinta'unathi")
set_default_language(GLOB.all_languages["Sinta'unathi"])
gender = pick(MALE, FEMALE)
AddElement(/datum/element/ai_retaliate)
/mob/living/basic/ash_walker/tough
name = "ash walker hunter"
icon_state = "ashwalker_hunter"
icon_living = "ashwalker_hunter"
damage_coeff = list(BRUTE = 0.9, BURN = 0.6, TOX = 1, CLONE = 1, STAMINA = 1, OXY = 1)
melee_damage_lower = 15
melee_damage_upper = 20
loot = list(/obj/effect/mob_spawn/human/corpse/ashwalker,
/obj/item/spear,
/obj/item/stack/ore/plasma{layer = ABOVE_MOB_LAYER})
/mob/living/basic/ash_walker/veteran
name = "ash walker slayer"
icon_state = "ashwalker_veteran"
icon_living = "ashwalker_veteran"
damage_coeff = list(BRUTE = 0.8, BURN = 0.4, TOX = 1, CLONE = 1, STAMINA = 1, OXY = 1)
armor_penetration_flat = 15
melee_damage_lower = 15
melee_damage_upper = 20
loot = list(/obj/effect/mob_spawn/human/corpse/ashwalker,
/obj/item/spear,
/obj/item/stack/ore/silver{layer = ABOVE_MOB_LAYER})
/mob/living/basic/ash_walker/elite
name = "ash walker dragonslayer"
icon_state = "ashwalker_elite"
icon_living = "ashwalker_elite"
damage_coeff = list(BRUTE = 0.5, BURN = 0, TOX = 1, CLONE = 1, STAMINA = 1, OXY = 1)
armor_penetration_flat = 25
melee_damage_lower = 20
melee_damage_upper = 25
loot = list(/obj/effect/mob_spawn/human/corpse/ashwalker,
/obj/item/spear,
/obj/item/stack/ore/gold{layer = ABOVE_MOB_LAYER})
/datum/ai_controller/basic_controller/simple/ash_walker
blackboard = list(
BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
BB_TARGET_MINIMUM_STAT = UNCONSCIOUS,
)
planning_subtrees = list(
/datum/ai_planning_subtree/generic_resist,
/datum/ai_planning_subtree/random_speech/ash_walker,
/datum/ai_planning_subtree/target_retaliate,
/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/prowl/lavaland,
)
/datum/ai_planning_subtree/random_speech/ash_walker
speech_chance = 10
emote_see = list("grunts.", "hisses.", "inspects their claws.")
speak = list(
"As the necropolis wills.",
"It's been too long since we've had a good hunt.",
"Don't sleep in the shade too long. The cold dreams will find you.",
"I heard the dust singing across the rocks today.",
"The young ones think the lava fields are a brave place. They will find good hunts there.",
"Did you see that star fall?",
"Sometimes, I feel like they're watching me.",
"I heard someone got a goliath the other day. Delicious.",
"Think we'll see an offworlder today?",
"Words are cheap.")
@@ -0,0 +1,123 @@
/mob/living/basic/mining/ash_whelp
name = "ash whelp"
desc = "The offspring of an ash drake, weak in comparison but still terrifying."
icon_state = "ash_whelp"
icon_living = "ash_whelp"
icon_dead = "ash_whelp_dead"
butcher_results = list(
/obj/item/stack/ore/gold = 3,
/obj/item/stack/sheet/animalhide/ashdrake = 1,
/obj/item/stack/sheet/bone = 10,
/obj/item/stack/sheet/sinew = 2,
)
crusher_loot = list(/obj/item/crusher_trophy/tail_spike)
speed = 12
maxHealth = 300
health = 300
obj_damage = 40
armor_penetration_flat = 20
melee_damage_lower = 20
melee_damage_upper = 25
response_help_continuous = "pets"
response_help_simple = "pet"
attack_verb_continuous = "chomps"
attack_verb_simple = "chomp"
throw_blocked_message = "does nothing to the scaled hide of the"
death_message = "collapses on its side."
death_sound = 'sound/misc/demon_dies.ogg'
attack_sound = 'sound/misc/demon_attack1.ogg'
faction = list("mining")
move_force = MOVE_FORCE_STRONG
move_resist = MOVE_FORCE_STRONG
pull_force = MOVE_FORCE_STRONG
ai_controller = /datum/ai_controller/basic_controller/ash_whelp
var/list/innate_actions = list(
/datum/action/cooldown/mob_cooldown/fire_breath = BB_WHELP_STRAIGHTLINE_FIRE,
/datum/action/cooldown/mob_cooldown/fire_breath/ice/eruption/fire = BB_WHELP_WIDESPREAD_FIRE,
)
/// How much we will heal when consuming a target
var/heal_on_cannibalize = 5
/mob/living/basic/mining/ash_whelp/Initialize(mapload)
. = ..()
AddElement(/datum/element/ai_retaliate)
AddComponent(/datum/component/ai_target_timer)
AddComponent(/datum/component/footstep, FOOTSTEP_MOB_HEAVY)
grant_actions_by_list(innate_actions)
/mob/living/basic/mining/ash_whelp/early_melee_attack(atom/target, list/modifiers, ignore_cooldown)
. = ..()
if(istype(target, /obj/structure/flora/ash/rock))
create_sculpture(target)
return FALSE
if(!istype(target, type))
return TRUE
var/mob/living/victim = target
if(victim.stat != DEAD)
return TRUE
if(istype(target, /mob/living/basic/mining/ash_whelp))
cannibalize_victim(victim)
return FALSE
return FALSE
/// Carve a stone into a beautiful self-portrait
/mob/living/basic/mining/ash_whelp/proc/create_sculpture(atom/target)
visible_message("[src] starts to sculpt [target].")
if(!do_after(src, 5 SECONDS, target = target))
return
var/obj/structure/whelp_statue/dragon_statue = new(get_turf(target))
dragon_statue.name = "statue of [src]"
dragon_statue.desc = "Let this serve as a warning."
qdel(target)
/// Gib and consume our fellow ash drakes
/mob/living/basic/mining/ash_whelp/proc/cannibalize_victim(mob/living/target)
start_pulling(target)
visible_message("[src] starts to consume [target].")
if(!do_after(src, 5 SECONDS, target))
return
target.gib()
adjustBruteLoss(-1 * heal_on_cannibalize)
/// Ice whelp, the cold variant of ash whelps.
/mob/living/basic/mining/ash_whelp/ice
name = "ice whelp"
desc = "The offspring of an ash drake, weak in comparison but still terrifying. This one exudes an icy aura."
icon_state = "ice_whelp"
icon_living = "ice_whelp"
icon_dead = "ice_whelp_dead"
innate_actions = list(
/datum/action/cooldown/mob_cooldown/fire_breath/ice = BB_WHELP_STRAIGHTLINE_FIRE,
/datum/action/cooldown/mob_cooldown/fire_breath/ice/eruption = BB_WHELP_WIDESPREAD_FIRE,
)
/// Ash whelp statue formed by them carving a rock
/obj/structure/whelp_statue
name = "statue of an ash whelp"
desc = "A basalt sculpture of a proud and regal drake. Its eyes are six glowing gemstones."
icon = 'icons/obj/structures/statues.dmi'
icon_state = "drake"
density = TRUE
anchored = TRUE
integrity_failure = 100
layer = EDGED_TURF_LAYER
/obj/structure/whelp_statue/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir)
. = ..()
update_icon(UPDATE_ICON_STATE)
/obj/structure/whelp_statue/update_icon_state()
if(broken)
icon_state = "drake_headless"
return
icon_state = "drake"
@@ -0,0 +1,209 @@
/datum/action/cooldown/mob_cooldown/fire_breath
name = "Fire Breath"
button_icon_state = "fireball"
desc = "Breathe a line of flames towards the target."
cooldown_time = 3 SECONDS
/// The range of the fire
var/fire_range = 15
/// The sound played when you use this ability
var/fire_sound = 'sound/magic/fireball.ogg'
/// Time to wait between spawning each fire turf
var/fire_delay = 0.15 SECONDS
/// How hot is our fire
var/fire_temperature = 1000
/// 'How much' fire do we expose the turf to?
var/fire_volume = 50
/// How much damage do you take when engulfed?
var/fire_damage = 20
/// How much damage to mechs take when engulfed?
var/mech_damage = 45
/datum/action/cooldown/mob_cooldown/fire_breath/Activate(atom/target_atom)
disable_cooldown_actions()
attack_sequence(target_atom)
StartCooldown()
enable_cooldown_actions()
return TRUE
/// Apply our specific fire breathing shape, in proc form so we can override it in subtypes
/datum/action/cooldown/mob_cooldown/fire_breath/proc/attack_sequence(atom/target)
playsound(owner.loc, fire_sound, 200, TRUE)
fire_line(target)
/// Breathe fire in a line towards the target, optionally rotated at an offset from the target
/datum/action/cooldown/mob_cooldown/fire_breath/proc/fire_line(atom/target, offset)
if(isnull(target))
return
var/turf/target_turf = get_ranged_target_turf_direct(owner, target, fire_range, offset)
var/list/turfs = get_line(owner, target_turf) - get_turf(owner)
INVOKE_ASYNC(src, PROC_REF(progressive_fire_line), turfs)
/// Creates fire with a delay on the list of targeted turfs
/datum/action/cooldown/mob_cooldown/fire_breath/proc/progressive_fire_line(list/burn_turfs)
if(QDELETED(owner) || owner.stat == DEAD)
return
// Guys we have already hit, no double dipping
var/list/hit_list = list(owner) // also don't burn ourselves
for(var/turf/target_turf in burn_turfs)
if(target_turf.is_blocked_turf(exclude_mobs = TRUE))
return
burn_turf(target_turf, hit_list, owner)
sleep(fire_delay)
/// Finally spawn the actual fire, spawns the fire hotspot in case you want to recolour it or something
/datum/action/cooldown/mob_cooldown/fire_breath/proc/burn_turf(turf/fire_turf, list/hit_list, mob/living/source)
var/obj/effect/hotspot/hotspot = new /obj/effect/hotspot/fake(fire_turf)
hotspot.temperature = fire_temperature
hotspot.recolor()
fire_turf.hotspot_expose(fire_temperature, fire_volume)
for(var/mob/living/barbecued in fire_turf.contents)
if(barbecued in hit_list)
continue
hit_list |= barbecued
on_burn_mob(barbecued, source)
for(var/obj/mecha/robotron in fire_turf.contents)
if(robotron in hit_list)
continue
hit_list |= robotron
robotron.take_damage(mech_damage, BURN, FIRE)
return
/// Do something unpleasant to someone we set on fire
/datum/action/cooldown/mob_cooldown/fire_breath/proc/on_burn_mob(mob/living/barbecued, mob/living/source)
if(fire_temperature <= TCMB)
barbecued.apply_status_effect(/datum/status_effect/ice_block_talisman, 3 SECONDS)
to_chat(barbecued, SPAN_USERDANGER("You're frozen solid by [source]'s icy breath!"))
else
to_chat(barbecued, SPAN_USERDANGER("You are burned by [source]'s fire breath!"))
barbecued.adjustFireLoss(fire_damage)
/// Shoot three lines of fire in a sort of fork pattern approximating a cone
/datum/action/cooldown/mob_cooldown/fire_breath/cone
name = "Fire Cone"
desc = "Breathe several lines of fire directed at a target."
/// The angles relative to the target that shoot lines of fire
var/list/angles = list(-40, 0, 40)
/datum/action/cooldown/mob_cooldown/fire_breath/cone/attack_sequence(atom/target)
playsound(owner.loc, fire_sound, 200, TRUE)
for(var/offset in angles)
fire_line(target, offset)
/// Shoot fire in a whole bunch of directions
/datum/action/cooldown/mob_cooldown/fire_breath/mass_fire
name = "Mass Fire"
button_icon = 'icons/effects/fire.dmi'
button_icon_state = "light"
desc = "Breathe flames in all directions."
cooldown_time = 10.5 SECONDS
click_to_activate = FALSE
/// How many fire lines do we produce to turn a full circle?
var/sectors = 12
/// How long do we wait between each spin?
var/breath_delay = 2.5 SECONDS
/// How many full circles do we perform?
var/total_spins = 3
/datum/action/cooldown/mob_cooldown/fire_breath/mass_fire/Activate(atom/target_atom)
target_atom = get_step(owner, owner.dir) // Just shoot it forwards, we don't need to click on someone forthis one
return ..()
/datum/action/cooldown/mob_cooldown/fire_breath/mass_fire/attack_sequence(atom/target)
var/queued_spins = 0
for(var/i in 1 to total_spins)
var/delay = queued_spins * breath_delay
queued_spins++
addtimer(CALLBACK(src, PROC_REF(fire_spin), target, queued_spins), delay)
/// Breathe fire in a circle, with a slight angle offset based on which of our several circles it is
/datum/action/cooldown/mob_cooldown/fire_breath/mass_fire/proc/fire_spin(target, spin_count)
if(QDELETED(owner) || owner.stat == DEAD)
return // Too dead to spin
playsound(owner.loc, fire_sound, 200, TRUE)
var/angle_increment = 360 / sectors
var/additional_offset = spin_count * angle_increment / 2
for(var/i in 1 to sectors)
fire_line(target, (angle_increment * i) + (additional_offset))
/// Breathe "fire" in a line (it's freezing cold)
/datum/action/cooldown/mob_cooldown/fire_breath/ice
name = "Ice Breath"
desc = "Fire a cold line of fire towards the enemy!"
cooldown_time = 6 SECONDS
fire_range = 7
fire_damage = 10
fire_delay = 0.85 DECISECONDS
fire_temperature = TCMB
/// Time to warn people about what we are doing
var/forecast_delay = 0.6 SECONDS
/// What turf are we aiming at?
var/turf/target_turf
/// Overlay we show when we're about to fire
var/image/forecast_overlay
/// Icon state used foroverlay
var/forecast_overlay_state = "ice_whelp_telegraph_dir"
/datum/action/cooldown/mob_cooldown/fire_breath/ice/New(Target, original)
. = ..()
forecast_overlay = image('icons/mob/lavaland/lavaland_monsters.dmi', forecast_overlay_state)
/// Apply our specific fire breathing shape, in proc form so we can override it in subtypes
/datum/action/cooldown/mob_cooldown/fire_breath/ice/attack_sequence(atom/target)
target_turf = get_turf(target)
INVOKE_ASYNC(src, PROC_REF(attack_forecast))
/// Charge up before we breathe fire
/datum/action/cooldown/mob_cooldown/fire_breath/ice/proc/attack_forecast()
owner.face_atom(target_turf)
owner.Shake(pixelshiftx = 1, pixelshifty = 0, duration = forecast_delay)
forecast_overlay.dir = (get_dir(owner, target_turf))
owner.add_overlay(forecast_overlay)
var/succeeded = do_after(owner, delay = forecast_delay, target = owner, hidden = TRUE)
owner.cut_overlay(forecast_overlay)
if(succeeded)
playsound(owner.loc, fire_sound, 200, TRUE)
breath_attack()
/// Actually breathe fire
/datum/action/cooldown/mob_cooldown/fire_breath/ice/proc/breath_attack()
owner.face_atom(target_turf)
fire_line(target_turf)
target_turf = null
/// Breathe really cold fire an area around them
/datum/action/cooldown/mob_cooldown/fire_breath/ice/eruption
name = "Ice Eruption"
desc = "Unleash cold fire in all directions"
button_icon = 'icons/effects/fire.dmi'
button_icon_state = "light"
click_to_activate = FALSE
fire_range = 3
forecast_delay = 1 SECONDS
fire_delay = 1.5 DECISECONDS
forecast_overlay_state = "ice_whelp_telegraph_all"
/datum/action/cooldown/mob_cooldown/fire_breath/ice/eruption/breath_attack()
target_turf = null
var/list/hit_list = list(owner)
var/list/nearby_turfs = list()
for(var/turf/simulated/floor/target_turf in circle_range(owner, fire_range))
if(target_turf.is_blocked_turf(exclude_mobs = TRUE))
continue
var/turf_dist = get_dist(owner, target_turf)
if(turf_dist == 0)
continue
LAZYADDASSOCLIST(nearby_turfs, "[turf_dist]", target_turf)
for(var/i in 1 to fire_range)
for(var/turf/simulated/floor/kindling as anything in nearby_turfs["[i]"])
burn_turf(kindling, hit_list, owner)
sleep(fire_delay)
///Fire subtype forash whelps
/datum/action/cooldown/mob_cooldown/fire_breath/ice/eruption/fire
name = "Eruption"
fire_temperature = 1000
@@ -0,0 +1,163 @@
/// We won't use fire breath unless we have had the same target forthis long
#define MIN_TIME_TO_BREATH_WEAPON 2 SECONDS
/datum/ai_controller/basic_controller/ash_whelp
blackboard = list(
BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
BB_TARGET_MINIMUM_STAT = UNCONSCIOUS,
BB_AGGRO_RANGE = 5,
)
ai_movement = /datum/ai_movement/basic_avoidance
idle_behavior = /datum/idle_behavior/idle_random_walk
planning_subtrees = list(
/datum/ai_planning_subtree/generic_resist,
/datum/ai_planning_subtree/simple_find_target,
/datum/ai_planning_subtree/targeted_mob_ability/ash_whelp,
/datum/ai_planning_subtree/attack_obstacle_in_path,
/datum/ai_planning_subtree/basic_melee_attack_subtree/ash_whelp,
/datum/ai_planning_subtree/sculpt_statues,
/datum/ai_planning_subtree/find_and_hunt_target/corpses/ash_whelp,
/datum/ai_planning_subtree/burn_trees,
/datum/ai_planning_subtree/find_and_hunt_target/prowl/lavaland,
)
/// Cancel melee attacks when we have our breath weapon
/datum/ai_planning_subtree/basic_melee_attack_subtree/ash_whelp
melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/ash_whelp
/// Cancel melee attacks when we have our breath weapon
/datum/ai_behavior/basic_melee_attack/ash_whelp
/datum/ai_behavior/basic_melee_attack/ash_whelp/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key)
var/datum/action/cooldown/breath_weapon = controller.blackboard[BB_TARGETED_ACTION]
if(breath_weapon?.IsAvailable())
return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED
return ..()
/// Find other tasty dragons
/datum/ai_planning_subtree/find_and_hunt_target/corpses/ash_whelp
target_key = BB_TARGET_CANNIBAL
finding_behavior = /datum/ai_behavior/find_hunt_target/corpses/dragon_corpse
hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/dragon_cannibalise
hunt_targets = list(/mob/living/basic/mining/ash_whelp)
hunt_range = 10
/datum/ai_behavior/find_hunt_target/corpses/dragon_corpse
/datum/ai_behavior/find_hunt_target/corpses/dragon_corpse/valid_dinner(mob/living/source, mob/living/dinner, radius)
if(dinner.pulledby) // someone already got him before us
return FALSE
return ..()
/// Eat other dragons
/datum/ai_behavior/hunt_target/interact_with_target/dragon_cannibalise
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION
/datum/ai_behavior/hunt_target/interact_with_target/dragon_cannibalise/perform(seconds_per_tick, datum/ai_controller/controller, target_key, attack_key)
var/mob/living/target = controller.blackboard[target_key]
if(QDELETED(target) || target.stat != DEAD || target.pulledby) // we were too slow
return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED
return ..()
/datum/ai_behavior/cannibalize/finish_action(datum/ai_controller/controller, succeeded, target_key)
. = ..()
controller.clear_blackboard_key(target_key)
/// Subtree to find icy rocks and create sculptures out of them
/datum/ai_planning_subtree/sculpt_statues
/datum/ai_planning_subtree/sculpt_statues/select_behaviors(datum/ai_controller/controller, seconds_per_tick)
if(controller.blackboard_key_exists(BB_TARGET_ROCK))
controller.queue_behavior(/datum/ai_behavior/sculpt_statue, BB_TARGET_ROCK)
return SUBTREE_RETURN_FINISH_PLANNING
controller.queue_behavior(/datum/ai_behavior/find_and_set, BB_TARGET_ROCK, /obj/structure/flora/ash/rock)
/datum/ai_behavior/sculpt_statue
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION
action_cooldown = 5 MINUTES
/datum/ai_behavior/sculpt_statue/setup(datum/ai_controller/controller, target_key)
. = ..()
var/obj/target = controller.blackboard[target_key]
if(QDELETED(target))
return FALSE
set_movement_target(controller, target)
/datum/ai_behavior/sculpt_statue/perform(seconds_per_tick, datum/ai_controller/controller, target_key)
. = ..()
if(!controller.ai_interact(target = target_key, intent = INTENT_HELP))
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
/datum/ai_behavior/sculpt_statue/finish_action(datum/ai_controller/controller, succeeded, target_key)
. = ..()
controller.clear_blackboard_key(target_key)
/// Select appropriate ability based on range
/datum/ai_planning_subtree/targeted_mob_ability/ash_whelp
operational_datums = list(/datum/component/ai_target_timer)
/datum/ai_planning_subtree/targeted_mob_ability/ash_whelp/select_behaviors(datum/ai_controller/controller, seconds_per_tick)
var/mob/living/target = controller.blackboard[target_key]
if(isnull(target))
return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED
var/time_on_target = controller.blackboard[BB_BASIC_MOB_HAS_TARGET_TIME] || 0
if(time_on_target < MIN_TIME_TO_BREATH_WEAPON)
return // We need to spend some time acquiring our target first
var/dist_to_target = get_dist(controller.pawn, target)
var/datum/action/cooldown/mob_cooldown/fire_breath/short_range_ability = controller.blackboard[BB_WHELP_WIDESPREAD_FIRE]
if(isnull(short_range_ability) || dist_to_target > short_range_ability.fire_range)
ability_key = BB_WHELP_STRAIGHTLINE_FIRE
return ..()
ability_key = BB_WHELP_WIDESPREAD_FIRE
return ..()
/// subtree to look fortrees and burn them with our flamethrower
/datum/ai_planning_subtree/burn_trees
/datum/ai_planning_subtree/burn_trees/select_behaviors(datum/ai_controller/controller, seconds_per_tick)
var/datum/action/cooldown/using_action = controller.blackboard[BB_WHELP_STRAIGHTLINE_FIRE]
if(!using_action?.IsAvailable())
return
if(controller.blackboard_key_exists(BB_TARGET_TREE))
controller.queue_behavior(/datum/ai_behavior/targeted_mob_ability/and_clear_target/burn_trees, BB_WHELP_STRAIGHTLINE_FIRE, BB_TARGET_TREE)
return SUBTREE_RETURN_FINISH_PLANNING
controller.queue_behavior(/datum/ai_behavior/set_target_tree, BB_TARGET_TREE)
/datum/ai_behavior/set_target_tree
/datum/ai_behavior/set_target_tree/perform(seconds_per_tick, datum/ai_controller/controller, tree_key)
. = ..()
var/mob/living_pawn = controller.pawn
var/list/possible_trees = list()
for(var/obj/structure/flora/tree/possible_tree in oview(9, living_pawn))
possible_trees += possible_tree
if(!length(possible_trees))
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED
controller.set_blackboard_key(tree_key, pick(possible_trees))
return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED
/datum/ai_behavior/targeted_mob_ability/and_clear_target/burn_trees
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION
required_distance = 2
action_cooldown = 2 MINUTES
/datum/ai_behavior/targeted_mob_ability/and_clear_target/burn_trees/setup(datum/ai_controller/controller, ability_key, target_key)
. = ..()
var/obj/target = controller.blackboard[target_key]
if(QDELETED(target))
return FALSE
set_movement_target(controller, target)
#undef MIN_TIME_TO_BREATH_WEAPON