ice demon basic mobs (#78539)

## About The Pull Request
ice demons are now basic mobs. they still have their behavior where they
can teleport around the player, run away from him and shoot him. they
now also have a new ability they will only use when they are on their
last legs, they will spawn weak and slow afterimage clones of
theirselves to attack the player. damaging these clones will also damage
the original ice demons. ice demons can also now be very easily
countered as they are very afraid of fires. they will run away from you
if they see u holding a lit torch/flare/welding tool and while running
away they will freeze the floors around them to try to slip u to stop u
from chasing them. ice demons now also get a new unique trophy! this
trophy will summon 2 friendly spirits that will help you kill ur target,
but these spirits will dissappear after a very short while.


https://github.com/tgstation/tgstation/assets/138636438/6a48fb15-f447-441a-91c6-48ca120dc22c



## Why It's Good For The Game
## Changelog
🆑
refactor: ice demons have been refactored into basic mbos. please report
any bugs
add: ice demons now have a unique trophy
/🆑
This commit is contained in:
Ben10Omintrix
2023-10-08 03:04:35 +01:00
committed by GitHub
parent d6285f9366
commit 032419f30f
18 changed files with 523 additions and 111 deletions
@@ -0,0 +1,89 @@
/mob/living/basic/mining/ice_demon
name = "demonic watcher"
desc = "A creature formed entirely out of ice, bluespace energy emanates from inside of it."
icon = 'icons/mob/simple/icemoon/icemoon_monsters.dmi'
icon_state = "ice_demon"
icon_living = "ice_demon"
icon_gib = "syndicate_gib"
mob_biotypes = MOB_ORGANIC|MOB_BEAST
mouse_opacity = MOUSE_OPACITY_ICON
basic_mob_flags = DEL_ON_DEATH
speed = 2
maxHealth = 150
health = 150
obj_damage = 40
melee_damage_lower = 15
melee_damage_upper = 15
attack_verb_continuous = "slices"
attack_verb_simple = "slice"
attack_sound = 'sound/weapons/bladeslice.ogg'
attack_vis_effect = ATTACK_EFFECT_SLASH
move_force = MOVE_FORCE_VERY_STRONG
move_resist = MOVE_FORCE_VERY_STRONG
pull_force = MOVE_FORCE_VERY_STRONG
crusher_loot = /obj/item/crusher_trophy/ice_demon_cube
ai_controller = /datum/ai_controller/basic_controller/ice_demon
death_message = "fades as the energies that tied it to this world dissipate."
death_sound = 'sound/magic/demon_dies.ogg'
/mob/living/basic/mining/ice_demon/Initialize(mapload)
. = ..()
var/datum/action/cooldown/mob_cooldown/slippery_ice_floors/ice_floor = new(src)
ice_floor.Grant(src)
ai_controller.set_blackboard_key(BB_DEMON_SLIP_ABILITY, ice_floor)
var/datum/action/cooldown/mob_cooldown/ice_demon_teleport/demon_teleport = new(src)
demon_teleport.Grant(src)
ai_controller.set_blackboard_key(BB_DEMON_TELEPORT_ABILITY, demon_teleport)
var/datum/action/cooldown/spell/conjure/create_afterimages/afterimage = new(src)
afterimage.Grant(src)
ai_controller.set_blackboard_key(BB_DEMON_CLONE_ABILITY, afterimage)
AddComponent(\
/datum/component/ranged_attacks,\
projectile_type = /obj/projectile/temp/ice_demon,\
projectile_sound = 'sound/weapons/pierce.ogg',\
)
var/static/list/death_loot = list(/obj/item/stack/ore/bluespace_crystal = 3)
AddElement(/datum/element/death_drops, death_loot)
AddElement(/datum/element/simple_flying)
/mob/living/basic/mining/ice_demon/death(gibbed)
if(prob(5))
new /obj/item/raw_anomaly_core/bluespace(loc)
return ..()
/mob/living/basic/mining/demon_afterimage
name = "afterimage demonic watcher"
desc = "Is this some sort of illusion?"
icon = 'icons/mob/simple/icemoon/icemoon_monsters.dmi'
icon_state = "ice_demon"
icon_living = "ice_demon"
icon_gib = "syndicate_gib"
mob_biotypes = MOB_ORGANIC|MOB_BEAST
mouse_opacity = MOUSE_OPACITY_ICON
basic_mob_flags = DEL_ON_DEATH
speed = 5
maxHealth = 20
health = 20
melee_damage_lower = 5
melee_damage_upper = 5
attack_verb_continuous = "slices"
attack_verb_simple = "slice"
attack_sound = 'sound/weapons/bladeslice.ogg'
alpha = 80
ai_controller = /datum/ai_controller/basic_controller/ice_demon/afterimage
///how long do we exist for
var/existence_period = 15 SECONDS
/mob/living/basic/mining/demon_afterimage/Initialize(mapload)
. = ..()
AddElement(/datum/element/simple_flying)
AddElement(/datum/element/temporary_atom, life_time = existence_period)
///afterimage subtypes summoned by the crusher
/mob/living/basic/mining/demon_afterimage/crusher
speed = 2
health = 60
maxHealth = 60
melee_damage_lower = 10
melee_damage_upper = 10
existence_period = 7 SECONDS
@@ -0,0 +1,117 @@
/obj/projectile/temp/ice_demon
name = "ice blast"
icon_state = "ice_2"
damage = 5
damage_type = BURN
armor_flag = ENERGY
speed = 1
pixel_speed_multiplier = 0.25
temperature = -75
/datum/action/cooldown/mob_cooldown/ice_demon_teleport
name = "Bluespace Teleport"
desc = "Teleport towards a destination target!"
button_icon = 'icons/obj/ore.dmi'
button_icon_state = "bluespace_crystal"
cooldown_time = 3 SECONDS
melee_cooldown_time = 0 SECONDS
///time delay before teleport
var/time_delay = 0.5 SECONDS
/datum/action/cooldown/mob_cooldown/ice_demon_teleport/Activate(atom/target_atom)
if(isclosedturf(get_turf(target_atom)))
owner.balloon_alert(owner, "blocked!")
return FALSE
animate(owner, transform = matrix().Scale(0.8), time = time_delay, easing = SINE_EASING)
addtimer(CALLBACK(src, PROC_REF(teleport_to_turf), target_atom), time_delay)
StartCooldown()
return TRUE
/datum/action/cooldown/mob_cooldown/ice_demon_teleport/proc/teleport_to_turf(atom/target)
animate(owner, transform = matrix(), time = 0.5 SECONDS, easing = SINE_EASING)
do_teleport(teleatom = owner, destination = target, channel = TELEPORT_CHANNEL_BLUESPACE, forced = TRUE)
/datum/action/cooldown/mob_cooldown/slippery_ice_floors
name = "Iced Floors"
desc = "Summon slippery ice floors all around!"
button_icon = 'icons/turf/floors/ice_turf.dmi'
button_icon_state = "ice_turf-6"
cooldown_time = 2 SECONDS
click_to_activate = FALSE
melee_cooldown_time = 0 SECONDS
///perimeter we will spawn the iced floors on
var/radius = 1
///intervals we will spawn the ice floors in
var/spread_duration = 0.2 SECONDS
/datum/action/cooldown/mob_cooldown/slippery_ice_floors/Activate(atom/target_atom)
for(var/i in 0 to radius)
var/list/list_of_turfs = border_diamond_range_turfs(owner, i)
addtimer(CALLBACK(src, PROC_REF(spawn_icy_floors), list_of_turfs), i * spread_duration)
StartCooldown()
return TRUE
/datum/action/cooldown/mob_cooldown/slippery_ice_floors/proc/spawn_icy_floors(list/list_of_turfs)
if(!length(list_of_turfs))
return
for(var/turf/location in list_of_turfs)
if(isnull(location))
continue
if(isclosedturf(location) || isspaceturf(location))
continue
new /obj/effect/temp_visual/slippery_ice(location)
/obj/effect/temp_visual/slippery_ice
name = "slippery acid"
icon = 'icons/turf/floors/ice_turf.dmi'
icon_state = "ice_turf-6"
layer = BELOW_MOB_LAYER
plane = GAME_PLANE
anchored = TRUE
duration = 3 SECONDS
alpha = 100
/// how long does it take for the effect to phase in
var/phase_in_period = 2 SECONDS
/obj/effect/temp_visual/slippery_ice/Initialize(mapload)
. = ..()
animate(src, alpha = 160, time = phase_in_period)
animate(alpha = 0, time = duration - phase_in_period) /// slowly fade out of existence
addtimer(CALLBACK(src, PROC_REF(add_slippery_component), phase_in_period)) //only become slippery after we phased in
/obj/effect/temp_visual/slippery_ice/proc/add_slippery_component()
AddComponent(/datum/component/slippery, 2 SECONDS)
/datum/action/cooldown/spell/conjure/create_afterimages
name = "Create After Images"
button_icon = 'icons/mob/simple/icemoon/icemoon_monsters.dmi'
button_icon_state = "ice_demon"
spell_requirements = NONE
cooldown_time = 1 MINUTES
summon_type = list(/mob/living/basic/mining/demon_afterimage)
summon_radius = 1
summon_amount = 2
///max number of after images
var/max_afterimages = 2
///How many clones do we have summoned
var/number_of_afterimages = 0
/datum/action/cooldown/spell/conjure/create_afterimages/can_cast_spell(feedback = TRUE)
. = ..()
if(!.)
return FALSE
if(number_of_afterimages >= max_afterimages)
return FALSE
return TRUE
/datum/action/cooldown/spell/conjure/create_afterimages/post_summon(atom/summoned_object, atom/cast_on)
var/mob/living/basic/created_copy = summoned_object
created_copy.AddComponent(/datum/component/joint_damage, overlord_mob = owner)
RegisterSignals(created_copy, list(COMSIG_QDELETING, COMSIG_LIVING_DEATH), PROC_REF(delete_copy))
number_of_afterimages++
/datum/action/cooldown/spell/conjure/create_afterimages/proc/delete_copy(mob/source)
SIGNAL_HANDLER
UnregisterSignal(source, list(COMSIG_QDELETING, COMSIG_LIVING_DEATH))
number_of_afterimages--
@@ -0,0 +1,118 @@
/datum/ai_controller/basic_controller/ice_demon
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic,
BB_LIST_SCARY_ITEMS = list(
/obj/item/weldingtool,
/obj/item/flashlight/flare,
),
BB_BASIC_MOB_FLEEING = TRUE,
BB_MINIMUM_DISTANCE_RANGE = 3,
)
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/flee_target/ice_demon,
/datum/ai_planning_subtree/ranged_skirmish/ice_demon,
/datum/ai_planning_subtree/maintain_distance/cover_minimum_distance/ice_demon,
/datum/ai_planning_subtree/teleport_away_from_target,
/datum/ai_planning_subtree/find_and_hunt_target/teleport_destination,
/datum/ai_planning_subtree/targeted_mob_ability/summon_afterimages,
)
/datum/ai_planning_subtree/maintain_distance/cover_minimum_distance/ice_demon
maximum_distance = 7
/datum/ai_planning_subtree/teleport_away_from_target
ability_key = BB_DEMON_TELEPORT_ABILITY
/datum/ai_planning_subtree/find_and_hunt_target/teleport_destination
target_key = BB_TELEPORT_DESTINATION
hunting_behavior = /datum/ai_behavior/hunt_target/use_ability_on_target/demon_teleport
finding_behavior = /datum/ai_behavior/find_valid_teleport_location
hunt_targets = list(/turf/open)
hunt_range = 3
finish_planning = FALSE
/datum/ai_planning_subtree/find_and_hunt_target/teleport_destination/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
if(!controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET))
return
if(controller.blackboard_key_exists(BB_ESCAPE_DESTINATION))
controller.clear_blackboard_key(BB_TELEPORT_DESTINATION)
return
var/datum/action/cooldown/ability = controller.blackboard[BB_DEMON_TELEPORT_ABILITY]
if(!ability?.IsAvailable())
return
return ..()
/datum/ai_behavior/find_valid_teleport_location
/datum/ai_behavior/find_valid_teleport_location/perform(seconds_per_tick, datum/ai_controller/controller, hunting_target_key, types_to_hunt, hunt_range)
. = ..()
var/mob/living/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
var/list/possible_turfs = list()
if(QDELETED(target))
finish_action(controller, FALSE)
return
for(var/turf/open/potential_turf in oview(hunt_range, target)) //we check for turfs around the target
if(potential_turf.is_blocked_turf())
continue
if(!can_see(target, potential_turf, hunt_range))
continue
possible_turfs += potential_turf
if(!length(possible_turfs))
finish_action(controller, FALSE)
return
controller.set_blackboard_key(hunting_target_key, pick(possible_turfs))
finish_action(controller, TRUE)
/datum/ai_behavior/hunt_target/use_ability_on_target/demon_teleport
hunt_cooldown = 2 SECONDS
ability_key = BB_DEMON_TELEPORT_ABILITY
behavior_flags = NONE
/datum/ai_planning_subtree/targeted_mob_ability/summon_afterimages
ability_key = BB_DEMON_CLONE_ABILITY
/datum/ai_planning_subtree/targeted_mob_ability/summon_afterimages/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
var/mob/living/living_pawn = controller.pawn
if(living_pawn.health / living_pawn.maxHealth > 0.5) //only use this ability when under half health
return
return ..()
/datum/ai_planning_subtree/flee_target/ice_demon
/datum/ai_planning_subtree/flee_target/ice_demon/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
if(QDELETED(target))
return
if(!iscarbon(target))
return
var/mob/living/carbon/human_target = target
for(var/obj/held_item in human_target.held_items)
if(!is_type_in_list(held_item, controller.blackboard[BB_LIST_SCARY_ITEMS]))
continue
if(!held_item.light_on)
continue
var/datum/action/cooldown/slip_ability = controller.blackboard[BB_DEMON_SLIP_ABILITY]
if(slip_ability?.IsAvailable())
controller.queue_behavior(/datum/ai_behavior/use_mob_ability, BB_DEMON_SLIP_ABILITY)
return ..()
/datum/ai_planning_subtree/ranged_skirmish/ice_demon
min_range = 0
/datum/ai_controller/basic_controller/ice_demon/afterimage
planning_subtrees = list(
/datum/ai_planning_subtree/simple_find_target,
/datum/ai_planning_subtree/flee_target/ice_demon, //even the afterimages are afraid of flames!
/datum/ai_planning_subtree/basic_melee_attack_subtree,
)