basic ice whelps (#77493)

## About The Pull Request
i have refactored ice whelps into basic mobs. They are now the artistic
sort as theyll mark their territory by seeking out icy rocks and carve
out statues of theirselves using their claws to serve as a warning to
players/animals that this is dragon turf and theyll also go out of their
way to burn any trees in vicinity just for the hell of it. they are now
gruesome cannibals if they find a corpse of one of their kin near them
theyll go eat it for nurishment. AS for combat, they have a new ability
which allows them to release fire in all directions however theyll only
use this ability once their enraged meter is full. to make it fair ive
given them a new component which allows them to telegraph abilities and
only do them after a delay so players can react in time for it.

## Why It's Good For The Game
basic mob refactor

## Changelog
🆑
refactor: ice whelps have been refactored to basic mobs
add: ice whelps have a new dangerous ability which theyll use once their
enraged meter is full
/🆑
This commit is contained in:
SMOSMOSMOSMOSMO
2023-08-14 12:39:30 -06:00
committed by GitHub
parent c97804119c
commit 15c3cd2c97
13 changed files with 384 additions and 61 deletions
@@ -0,0 +1,88 @@
/mob/living/basic/mining/ice_whelp
name = "ice whelp"
desc = "The offspring of an ice drake, weak in comparison but still terrifying."
icon = 'icons/mob/simple/icemoon/icemoon_monsters.dmi'
icon_state = "ice_whelp"
icon_living = "ice_whelp"
icon_dead = "ice_whelp_dead"
mob_biotypes = MOB_ORGANIC|MOB_BEAST
mouse_opacity = MOUSE_OPACITY_ICON
butcher_results = list(
/obj/item/stack/ore/diamond = 3,
/obj/item/stack/sheet/animalhide/ashdrake = 1,
/obj/item/stack/sheet/bone = 10,
/obj/item/stack/sheet/sinew = 2,
)
crusher_loot = /obj/item/crusher_trophy/tail_spike
speed = 12
maxHealth = 300
health = 300
obj_damage = 40
armour_penetration = 20
melee_damage_lower = 20
melee_damage_upper = 20
attack_verb_continuous = "chomps"
attack_verb_simple = "chomp"
death_message = "collapses on its side."
death_sound = 'sound/magic/demon_dies.ogg'
attack_sound = 'sound/magic/demon_attack1.ogg'
move_force = MOVE_FORCE_VERY_STRONG
move_resist = MOVE_FORCE_VERY_STRONG
pull_force = MOVE_FORCE_VERY_STRONG
ai_controller = /datum/ai_controller/basic_controller/ice_whelp
///how much we will heal when cannibalizing a target
var/heal_on_cannibalize = 5
/mob/living/basic/mining/ice_whelp/Initialize(mapload)
. = ..()
ADD_TRAIT(src, TRAIT_NO_GLIDE, INNATE_TRAIT)
AddElement(/datum/element/footstep, FOOTSTEP_MOB_HEAVY)
AddComponent(/datum/component/basic_mob_ability_telegraph)
AddComponent(/datum/component/basic_mob_attack_telegraph, telegraph_duration = 0.6 SECONDS)
var/datum/action/cooldown/mob_cooldown/ice_breath/flamethrower = new(src)
var/datum/action/cooldown/mob_cooldown/ice_breathe_all_directions/wide_flames = new(src)
flamethrower.Grant(src)
wide_flames.Grant(src)
ai_controller.set_blackboard_key(BB_WHELP_WIDESPREAD_FIRE, wide_flames)
ai_controller.set_blackboard_key(BB_WHELP_STRAIGHTLINE_FIRE, flamethrower)
RegisterSignal(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(pre_attack))
/mob/living/basic/mining/ice_whelp/proc/pre_attack(mob/living/sculptor, atom/target)
SIGNAL_HANDLER
if(istype(target, /obj/structure/flora/rock/icy))
INVOKE_ASYNC(src, PROC_REF(create_sculpture), target)
return COMPONENT_HOSTILE_NO_ATTACK
if(!istype(target, src.type))
return
var/mob/living/victim = target
if(victim.stat != DEAD)
return
INVOKE_ASYNC(src, PROC_REF(cannibalize_victim), victim)
return COMPONENT_HOSTILE_NO_ATTACK
/mob/living/basic/mining/ice_whelp/proc/create_sculpture(atom/target)
balloon_alert(src, "sculpting...")
if(!do_after(src, 5 SECONDS, target = target))
return
var/obj/structure/statue/custom/dragon_statue = new(get_turf(target))
dragon_statue.set_visuals(src)
dragon_statue.name = "statue of [src]"
dragon_statue.desc = "Let this serve as a warning."
dragon_statue.set_anchored(TRUE)
qdel(target)
/mob/living/basic/mining/ice_whelp/proc/cannibalize_victim(mob/living/target)
balloon_alert(src, "devouring...")
if(!do_after(src, 5 SECONDS, target))
return
target.gib()
adjustBruteLoss(-1 * heal_on_cannibalize)
@@ -0,0 +1,36 @@
/datum/action/cooldown/mob_cooldown/ice_breath
name = "Ice Breath"
desc = "Fire a cold line of fire towards the enemy!"
button_icon = 'icons/effects/magic.dmi'
button_icon_state = "fireball"
cooldown_time = 3 SECONDS
melee_cooldown_time = 0 SECONDS
click_to_activate = TRUE
///the range of fire
var/fire_range = 4
/datum/action/cooldown/mob_cooldown/ice_breath/Activate(atom/target_atom)
var/turf/target_fire_turf = get_ranged_target_turf_direct(owner, target_atom, fire_range)
var/list/burn_turfs = get_line(owner, target_fire_turf) - get_turf(owner)
dragon_fire_line(owner, burn_turfs, frozen = TRUE)
StartCooldown()
return TRUE
/datum/action/cooldown/mob_cooldown/ice_breathe_all_directions
name = "Fire all directions"
desc = "Unleash lines of cold fire in all directions"
button_icon = 'icons/effects/fire.dmi'
button_icon_state = "1"
cooldown_time = 4 SECONDS
melee_cooldown_time = 0 SECONDS
click_to_activate = FALSE
///the range of fire
var/fire_range = 6
/datum/action/cooldown/mob_cooldown/ice_breathe_all_directions/Activate(atom/target_atom)
for(var/direction in GLOB.cardinals)
var/turf/target_fire_turf = get_ranged_target_turf(owner, direction, fire_range)
var/list/burn_turfs = get_line(owner, target_fire_turf) - get_turf(owner)
INVOKE_ASYNC(GLOBAL_PROC, GLOBAL_PROC_REF(dragon_fire_line), owner, burn_turfs, frozen = TRUE)
StartCooldown()
return TRUE
@@ -0,0 +1,182 @@
#define ENRAGE_ADDITION 25
/datum/ai_controller/basic_controller/ice_whelp
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/allow_items/goliath,
BB_WHELP_ENRAGED = 0,
)
ai_movement = /datum/ai_movement/basic_avoidance
idle_behavior = /datum/idle_behavior/idle_random_walk
planning_subtrees = list(
/datum/ai_planning_subtree/simple_find_target,
/datum/ai_planning_subtree/targeted_mob_ability/ice_whelp,
/datum/ai_planning_subtree/attack_obstacle_in_path,
/datum/ai_planning_subtree/basic_melee_attack_subtree,
/datum/ai_planning_subtree/sculpt_statues,
/datum/ai_planning_subtree/find_and_hunt_target/cannibalize,
/datum/ai_planning_subtree/burn_trees,
)
/datum/ai_planning_subtree/find_and_hunt_target/cannibalize
target_key = BB_TARGET_CANNIBAL
hunting_behavior = /datum/ai_behavior/cannibalize
finding_behavior = /datum/ai_behavior/find_hunt_target/dragon_corpse
hunt_targets = list(/mob/living/basic/mining/ice_whelp)
hunt_range = 10
/datum/ai_behavior/find_hunt_target/dragon_corpse
/datum/ai_behavior/find_hunt_target/dragon_corpse/valid_dinner(mob/living/source, mob/living/dinner, radius)
if(dinner.stat != DEAD)
return FALSE
if(dinner.pulledby) //someone already got him before us
return FALSE
return can_see(source, dinner, radius)
/datum/ai_behavior/cannibalize
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION
/datum/ai_behavior/cannibalize/setup(datum/ai_controller/controller, target_key)
. = ..()
var/atom/target = controller.blackboard[target_key]
if(QDELETED(target))
return FALSE
set_movement_target(controller, target)
/datum/ai_behavior/cannibalize/perform(seconds_per_tick, datum/ai_controller/controller, target_key, attack_key)
. = ..()
var/mob/living/basic/living_pawn = controller.pawn
var/mob/living/target = controller.blackboard[target_key]
if(QDELETED(target))
finish_action(controller, FALSE)
return
if(target.stat != DEAD || target.pulledby) //we were too slow
finish_action(controller, FALSE)
return
living_pawn.start_pulling(target)
living_pawn.melee_attack(target)
finish_action(controller, TRUE)
/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/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
var/obj/target = controller.blackboard[BB_TARGET_ROCK]
if(QDELETED(target))
controller.queue_behavior(/datum/ai_behavior/find_and_set, BB_TARGET_ROCK, /obj/structure/flora/rock/icy)
return
controller.queue_behavior(/datum/ai_behavior/sculpt_statue, BB_TARGET_ROCK)
return SUBTREE_RETURN_FINISH_PLANNING
/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)
. = ..()
var/atom/target = controller.blackboard[target_key]
var/mob/living/basic/living_pawn = controller.pawn
if(QDELETED(target))
finish_action(controller, FALSE, target_key)
return
living_pawn.melee_attack(target)
finish_action(controller, TRUE, target_key)
/datum/ai_behavior/sculpt_statue/finish_action(datum/ai_controller/controller, succeeded, target_key)
. = ..()
controller.clear_blackboard_key(target_key)
//subtree to use our attacks on the victim
/datum/ai_planning_subtree/targeted_mob_ability/ice_whelp
ability_key = BB_WHELP_STRAIGHTLINE_FIRE
use_ability_behaviour = /datum/ai_behavior/targeted_mob_ability/ice_whelp
finish_planning = FALSE
/datum/ai_behavior/targeted_mob_ability/ice_whelp
///key that stores how enraged we are
var/enraged_key = BB_WHELP_ENRAGED
///key that stores the ability we will use instead if we are fully enraged
var/secondary_ability_key = BB_WHELP_WIDESPREAD_FIRE
/datum/ai_behavior/targeted_mob_ability/ice_whelp/get_ability_to_use(datum/ai_controller/controller, ability_key)
var/enraged_value = controller.blackboard[enraged_key]
if(prob(enraged_value))
controller.set_blackboard_key(enraged_key, 0)
return controller.blackboard[secondary_ability_key]
controller.set_blackboard_key(enraged_key, enraged_value + ENRAGE_ADDITION)
return controller.blackboard[ability_key]
///subtree to look for trees and burn them with our flamethrower
/datum/ai_planning_subtree/burn_trees
/datum/ai_planning_subtree/burn_trees/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
var/datum/action/cooldown/using_action = controller.blackboard[BB_WHELP_STRAIGHTLINE_FIRE]
if (!using_action.IsAvailable())
return
var/obj/structure/target = controller.blackboard[BB_TARGET_TREE]
if(QDELETED(target))
controller.queue_behavior(/datum/ai_behavior/set_target_tree, BB_TARGET_TREE)
return
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
/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))
if(istype(possible_tree, /obj/structure/flora/tree/stump)) //no leaves to burn
continue
possible_trees += possible_tree
if(!length(possible_trees))
finish_action(controller, FALSE)
return
controller.set_blackboard_key(tree_key, pick(possible_trees))
finish_action(controller, TRUE)
/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 ENRAGE_ADDITION