mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-25 22:19:30 +01:00
Reworks lavaland tendrils into minibosses (#96186)
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
#define HEARTBEAT_NORMAL (1.8 SECONDS)
|
||||
#define HEARTBEAT_FAST (1 SECONDS)
|
||||
#define HEARTBEAT_FRANTIC (0.4 SECONDS)
|
||||
|
||||
GLOBAL_LIST_INIT(tendrils, list())
|
||||
|
||||
/mob/living/basic/mining/tendril
|
||||
name = "necropolis tendril"
|
||||
desc = "A vile tendril of corruption, originating deep underground."
|
||||
icon = 'icons/mob/simple/lavaland/tendril.dmi'
|
||||
icon_state = "tendril"
|
||||
icon_living = "tendril"
|
||||
pixel_w = -8
|
||||
base_pixel_w = -8
|
||||
status_flags = NONE
|
||||
mob_biotypes = MOB_ORGANIC | MOB_SKELETAL | MOB_MINING
|
||||
basic_mob_flags = DEL_ON_DEATH | IMMUNE_TO_FISTS
|
||||
mob_size = MOB_SIZE_HUGE
|
||||
maxHealth = 1200
|
||||
health = 1200
|
||||
|
||||
friendly_verb_continuous = "flails at"
|
||||
friendly_verb_simple = "flail at"
|
||||
speak_emote = list("resonates")
|
||||
obj_damage = 100
|
||||
melee_damage_lower = 20
|
||||
melee_damage_upper = 20
|
||||
sharpness = SHARP_POINTY
|
||||
wound_bonus = CANT_WOUND
|
||||
attack_sound = 'sound/items/weapons/pierce.ogg'
|
||||
attack_verb_continuous = "pierces through"
|
||||
attack_verb_simple = "pierce through"
|
||||
throw_blocked_message = "does nothing to the thick shell of"
|
||||
move_resist = INFINITY
|
||||
|
||||
light_range = 3
|
||||
light_power = 2
|
||||
light_color = LIGHT_COLOR_LAVA
|
||||
|
||||
ai_controller = /datum/ai_controller/basic_controller/tendril
|
||||
|
||||
/// Looping heartbeat sound
|
||||
var/datum/looping_sound/heartbeat/soundloop
|
||||
/// Melee attack ability to used in retaliation to melee strikes and whenever it manages to grab someone
|
||||
var/datum/action/cooldown/mob_cooldown/projectile_attack/tendril_melee/tendril_melee
|
||||
|
||||
/mob/living/basic/mining/tendril/Initialize(mapload)
|
||||
. = ..()
|
||||
GLOB.tendrils += src
|
||||
AddElement(/datum/element/ai_retaliate)
|
||||
AddElement(/datum/element/death_drops, /obj/structure/closet/crate/necropolis/tendril)
|
||||
AddComponent(/datum/component/ai_target_timer)
|
||||
AddComponent(/datum/component/gps, "Eerie Signal")
|
||||
AddComponent(/datum/component/basic_mob_attack_telegraph, display_telegraph_overlay = FALSE, telegraph_duration = 0.4 SECONDS)
|
||||
AddComponent(/datum/component/regenerator, regeneration_delay = 30 SECONDS, brute_per_second = 20)
|
||||
add_traits(list(TRAIT_BACKSTAB_IMMUNE, TRAIT_IMMOBILIZED), INNATE_TRAIT)
|
||||
|
||||
var/static/list/abilities = list(
|
||||
/datum/action/cooldown/mob_cooldown/projectile_attack/tendril_lash = BB_TENDRIL_LASH,
|
||||
/datum/action/cooldown/mob_cooldown/tendril_chaser = BB_TENDRIL_CHASER,
|
||||
/datum/action/cooldown/mob_cooldown/tendril_cross_spikes = BB_TENDRIL_SPIKES,
|
||||
)
|
||||
grant_actions_by_list(abilities)
|
||||
|
||||
tendril_melee = new(src)
|
||||
tendril_melee.Grant(src)
|
||||
AddComponent(/datum/component/revenge_ability, tendril_melee, targeting = GET_TARGETING_STRATEGY(ai_controller.blackboard[BB_TARGETING_STRATEGY]), max_range = 2, target_self = TRUE)
|
||||
|
||||
soundloop = new(src, start_immediately = FALSE)
|
||||
soundloop.mid_length = HEARTBEAT_NORMAL
|
||||
soundloop.pressure_affected = FALSE
|
||||
soundloop.start()
|
||||
update_appearance(UPDATE_OVERLAYS)
|
||||
|
||||
var/turf/our_turf = get_turf(src)
|
||||
for (var/turf/rock in range(4, src))
|
||||
var/dist = sqrt((rock.x - our_turf.x) ** 2 + (rock.y - our_turf.y) ** 2)
|
||||
if (dist > 4.5)
|
||||
continue
|
||||
|
||||
if (ismineralturf(rock))
|
||||
rock.ScrapeAway(null, CHANGETURF_IGNORE_AIR)
|
||||
|
||||
if (istype(rock, /turf/open/misc/asteroid) && prob(100 / sqrt(max(1, dist))))
|
||||
rock.ChangeTurf(/turf/open/indestructible/necropolis, null, CHANGETURF_IGNORE_AIR)
|
||||
|
||||
/mob/living/basic/mining/tendril/Destroy()
|
||||
GLOB.tendrils -= src
|
||||
QDEL_NULL(soundloop)
|
||||
|
||||
if(!SSachievements.achievements_enabled || (flags_1 & ADMIN_SPAWNED_1))
|
||||
return ..()
|
||||
|
||||
for(var/mob/living/killer in view(7, src))
|
||||
if(killer.stat || !killer.client)
|
||||
continue
|
||||
killer.client.give_award(/datum/award/score/tendril_score, killer)
|
||||
if (!length(GLOB.tendrils))
|
||||
killer.client.give_award(/datum/award/achievement/boss/tendril_exterminator, killer)
|
||||
|
||||
return ..()
|
||||
|
||||
/mob/living/basic/mining/tendril/update_overlays()
|
||||
. = ..()
|
||||
. += emissive_appearance(icon, "[icon_state]_e", src, effect_type = EMISSIVE_NO_BLOOM)
|
||||
. += emissive_appearance(icon, "[icon_state]_e_bloom", src, effect_type = EMISSIVE_BLOOM)
|
||||
|
||||
/mob/living/basic/mining/tendril/Life(seconds_per_tick)
|
||||
. = ..()
|
||||
update_heartbeat() // Just a single math op unless we need updating so its fine to put it here
|
||||
|
||||
/mob/living/basic/mining/tendril/updatehealth()
|
||||
. = ..()
|
||||
update_heartbeat()
|
||||
|
||||
/mob/living/basic/mining/tendril/proc/update_heartbeat()
|
||||
if (!soundloop)
|
||||
return
|
||||
|
||||
var/beat_rate = HEARTBEAT_NORMAL
|
||||
if (ai_controller?.blackboard[BB_BASIC_MOB_CURRENT_TARGET])
|
||||
beat_rate = round(HEARTBEAT_FRANTIC + health / maxHealth * (HEARTBEAT_FAST - HEARTBEAT_FRANTIC), 0.05 SECONDS)
|
||||
|
||||
if (beat_rate != soundloop.mid_length)
|
||||
soundloop.set_mid_length(beat_rate)
|
||||
|
||||
/mob/living/basic/mining/tendril/do_attack_animation(atom/attacked_atom, visual_effect_icon, obj/item/used_item, no_effect, fov_effect = TRUE, item_animation_override = null)
|
||||
if(!no_effect && (visual_effect_icon || used_item))
|
||||
do_item_attack_animation(attacked_atom, visual_effect_icon, used_item, animation_type = item_animation_override)
|
||||
|
||||
// Stabby visuals
|
||||
var/obj/effect/temp_visual/spike_stab/stab = new(get_turf(src))
|
||||
var/target_dir = get_dir(src, attacked_atom)
|
||||
stab.transform = matrix().Turn(dir2angle(target_dir) + rand(-7, 7))
|
||||
if (target_dir & NORTH)
|
||||
stab.pixel_z = 24
|
||||
else if (target_dir & SOUTH)
|
||||
stab.pixel_z = -24
|
||||
if (target_dir & EAST)
|
||||
stab.pixel_w = 24
|
||||
else if (target_dir & WEST)
|
||||
stab.pixel_w = -24
|
||||
|
||||
/obj/effect/temp_visual/spike_stab
|
||||
icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi'
|
||||
icon_state = "spike_small"
|
||||
duration = 0.4 SECONDS
|
||||
|
||||
/mob/living/basic/mining/tendril/proc/snatch_react()
|
||||
if (tendril_melee.IsAvailable())
|
||||
tendril_melee.Activate(warning = FALSE)
|
||||
|
||||
#undef HEARTBEAT_NORMAL
|
||||
#undef HEARTBEAT_FAST
|
||||
#undef HEARTBEAT_FRANTIC
|
||||
@@ -0,0 +1,358 @@
|
||||
/// Fires out two cross patterns of damaging tentacles which reel in anything they hit, then causes a followup attack
|
||||
/datum/action/cooldown/mob_cooldown/projectile_attack/tendril_lash
|
||||
name = "Tentacle Lash"
|
||||
desc = "Lash out with your tentacles in 8 directions, reeling in whatever you hit and unleashing a deadly followup attack afterwards."
|
||||
button_icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi'
|
||||
button_icon_state = "goliath_tentacle_wiggle"
|
||||
background_icon_state = "bg_demon"
|
||||
overlay_icon_state = "bg_demon_border"
|
||||
click_to_activate = FALSE
|
||||
cooldown_time = 8 SECONDS
|
||||
melee_cooldown_time = 0
|
||||
shared_cooldown = NONE
|
||||
projectile_type = /obj/projectile/tentacle_lash
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/projectile_attack/tendril_lash/Activate(atom/target)
|
||||
disable_cooldown_actions()
|
||||
|
||||
for (var/swipe_dir in GLOB.cardinals)
|
||||
var/turf/open/line_turf = get_step(owner, swipe_dir)
|
||||
for (var/i in 1 to projectile_type::range)
|
||||
if (!istype(line_turf) || line_turf.is_blocked_turf(exclude_mobs = TRUE))
|
||||
break
|
||||
var/obj/effect/temp_visual/telegraphing/line/telegraph = new(line_turf)
|
||||
telegraph.dir = swipe_dir
|
||||
line_turf = get_step(line_turf, swipe_dir)
|
||||
|
||||
SLEEP_CHECK_DEATH(0.8 SECONDS, owner)
|
||||
|
||||
for (var/swipe_dir in GLOB.cardinals)
|
||||
shoot_projectile(get_turf(owner), get_step(owner, swipe_dir), dir2angle(swipe_dir), owner)
|
||||
|
||||
SLEEP_CHECK_DEATH(1.6 SECONDS, owner)
|
||||
|
||||
for (var/swipe_dir in GLOB.diagonals)
|
||||
var/turf/open/line_turf = get_step(owner, swipe_dir)
|
||||
for (var/i in 1 to projectile_type::range)
|
||||
if (!istype(line_turf) || line_turf.is_blocked_turf(exclude_mobs = TRUE))
|
||||
break
|
||||
var/obj/effect/temp_visual/telegraphing/line/telegraph = new(line_turf)
|
||||
telegraph.dir = swipe_dir
|
||||
line_turf = get_step(line_turf, swipe_dir)
|
||||
|
||||
SLEEP_CHECK_DEATH(0.8 SECONDS, owner)
|
||||
|
||||
for (var/swipe_dir in GLOB.diagonals)
|
||||
shoot_projectile(get_turf(owner), get_step(owner, swipe_dir), dir2angle(swipe_dir), owner)
|
||||
|
||||
SLEEP_CHECK_DEATH(1.2 SECONDS, owner)
|
||||
StartCooldown()
|
||||
SLEEP_CHECK_DEATH(0.6 SECONDS, owner)
|
||||
enable_cooldown_actions()
|
||||
return TRUE
|
||||
|
||||
/obj/projectile/tentacle_lash
|
||||
name = "tentacle spike"
|
||||
icon_state = "tentacle_spike"
|
||||
pass_flags = PASSTABLE
|
||||
damage = 5 // +10 from the grab
|
||||
armor_flag = MELEE
|
||||
range = 7
|
||||
hit_prone_targets = TRUE
|
||||
hitsound = 'sound/effects/wounds/pierce1.ogg'
|
||||
sharpness = SHARP_POINTY
|
||||
/// Beam connecting us and the firer
|
||||
var/datum/beam/tentacle_beam = null
|
||||
/// Does this projectile persist and reel in targets?
|
||||
var/reel_in = TRUE
|
||||
/// How long does the projectile persist?
|
||||
var/duration = 1.2 SECONDS
|
||||
/// Damage dealt to targets who get snatched from entering the beam or being hit directly
|
||||
var/snatch_damage = 10
|
||||
|
||||
/obj/projectile/tentacle_lash/stab
|
||||
damage = 15
|
||||
range = 2
|
||||
duration = 0.2 SECONDS // Just for visual flair
|
||||
reel_in = FALSE
|
||||
|
||||
/obj/projectile/tentacle_lash/fire(fire_angle, atom/direct_target)
|
||||
. = ..()
|
||||
if (!firer)
|
||||
return
|
||||
tentacle_beam = Beam(firer, "goliath_tentacle", beam_type = (reel_in ? /obj/effect/ebeam/reacting : /obj/effect/ebeam), emissive = FALSE)
|
||||
if (reel_in)
|
||||
RegisterSignal(tentacle_beam, COMSIG_BEAM_ENTERED, PROC_REF(on_beam_entered))
|
||||
|
||||
/obj/projectile/tentacle_lash/Destroy()
|
||||
QDEL_NULL(tentacle_beam)
|
||||
return ..()
|
||||
|
||||
// Don't range out, stop and persist until we're done
|
||||
/obj/projectile/tentacle_lash/on_range()
|
||||
STOP_PROCESSING(SSprojectiles, src)
|
||||
// Reset to tile center
|
||||
pixel_x = 0
|
||||
pixel_y = 0
|
||||
QDEL_IN(src, duration)
|
||||
|
||||
/obj/projectile/tentacle_lash/prehit_pierce(atom/target)
|
||||
// Ye 'ole colossus cheese
|
||||
if (astype(target, /mob/living)?.stat == DEAD)
|
||||
return PROJECTILE_PIERCE_PHASE
|
||||
return ..()
|
||||
|
||||
/obj/projectile/tentacle_lash/on_hit(atom/target, blocked, pierce_hit)
|
||||
. = ..()
|
||||
if (!reel_in || !isliving(target) || blocked >= 100 || pierce_hit || . != BULLET_ACT_HIT)
|
||||
return
|
||||
snatch_target(target)
|
||||
|
||||
/obj/projectile/tentacle_lash/proc/on_beam_entered(datum/beam/source, obj/effect/ebeam/hit, atom/movable/entered)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if (!reel_in || entered == firer || !isliving(entered))
|
||||
return
|
||||
|
||||
var/mob/living/victim = entered
|
||||
if ((!firer || !firer.faction_check_atom(victim)) && victim.stat != DEAD)
|
||||
INVOKE_ASYNC(src, PROC_REF(snatch_target), entered)
|
||||
|
||||
/obj/projectile/tentacle_lash/proc/snatch_target(mob/living/victim)
|
||||
if (HAS_TRAIT(victim, TRAIT_TENTACLE_IMMUNE) || SEND_SIGNAL(victim, COMSIG_TENDRIL_TENTACLED_GRABBED) & COMPONENT_TENDRIL_CANCEL_TENTACLE_GRAB)
|
||||
return
|
||||
|
||||
to_chat(victim, span_userdanger("You're snatched by [firer]'s tentacles!"))
|
||||
victim.apply_damage(snatch_damage, BRUTE, BODY_ZONE_CHEST, wound_bonus = CANT_WOUND)
|
||||
if (QDELETED(victim))
|
||||
qdel(src)
|
||||
return
|
||||
var/snatch_callback = null
|
||||
if (istype(firer, /mob/living/basic/mining/tendril))
|
||||
var/mob/living/basic/mining/tendril/tendril = firer
|
||||
snatch_callback = CALLBACK(tendril, TYPE_PROC_REF(/mob/living/basic/mining/tendril, snatch_react))
|
||||
victim.throw_at(firer, initial(range), 1, firer, FALSE, gentle = TRUE, callback = snatch_callback)
|
||||
playsound(victim, hitsound, 50, -3, pressure_affected = FALSE)
|
||||
qdel(src)
|
||||
|
||||
/// An ability which makes spikes come out of the ground towards your target
|
||||
/datum/action/cooldown/mob_cooldown/tendril_chaser
|
||||
name = "Impaling Spikes"
|
||||
desc = "Send a spiked subterranean tendril chasing after your target."
|
||||
button_icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi'
|
||||
button_icon_state = "spikes_stabbing"
|
||||
background_icon_state = "bg_demon"
|
||||
overlay_icon_state = "bg_demon_border"
|
||||
cooldown_time = 8 SECONDS
|
||||
click_to_activate = TRUE
|
||||
shared_cooldown = NONE
|
||||
/// Lazy list of references to spike trails
|
||||
var/list/active_chasers
|
||||
/// Health percentage threshold at which we send out wide charsers after the main target
|
||||
var/wide_chaser_threshold = 0.7
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/tendril_chaser/Grant(mob/granted_to)
|
||||
. = ..()
|
||||
RegisterSignal(granted_to, COMSIG_MOB_ABILITY_STARTED, PROC_REF(on_ability_started))
|
||||
RegisterSignal(granted_to, COMSIG_MOB_ABILITY_FINISHED, PROC_REF(on_ability_finished))
|
||||
|
||||
// Clean up after ourselves
|
||||
/datum/action/cooldown/mob_cooldown/tendril_chaser/Remove(mob/removed_from)
|
||||
UnregisterSignal(removed_from, list(COMSIG_MOB_ABILITY_STARTED, COMSIG_MOB_ABILITY_FINISHED))
|
||||
QDEL_LIST(active_chasers)
|
||||
return ..()
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/tendril_chaser/proc/on_ability_started(mob/living/owner, datum/action/cooldown/activated)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
// Delete all of our chasers when our owner triggers cross spikes as to not cause guaranteed damage
|
||||
if (istype(activated, /datum/action/cooldown/mob_cooldown/tendril_cross_spikes))
|
||||
QDEL_LIST(active_chasers)
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/tendril_chaser/proc/on_ability_finished(mob/living/owner, datum/action/cooldown/activated)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if (istype(activated, /datum/action/cooldown/mob_cooldown/tendril_cross_spikes))
|
||||
ResetCooldown()
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/tendril_chaser/Activate(atom/target)
|
||||
. = ..()
|
||||
var/primary_type = /obj/effect/temp_visual/effect_trail/tendril_chaser
|
||||
if (isliving(owner))
|
||||
var/mob/living/as_living = owner
|
||||
if (as_living.health / as_living.maxHealth <= wide_chaser_threshold)
|
||||
primary_type = /obj/effect/temp_visual/effect_trail/tendril_chaser/wide_area
|
||||
|
||||
var/obj/effect/temp_visual/effect_trail/tendril_chaser/chaser = new primary_type(get_turf(owner), target)
|
||||
LAZYADD(active_chasers, WEAKREF(chaser))
|
||||
RegisterSignal(chaser, COMSIG_QDELETING, PROC_REF(on_chaser_destroyed))
|
||||
playsound(owner, 'sound/effects/magic/demon_attack1.ogg', vol = 100, vary = TRUE, pressure_affected = FALSE)
|
||||
|
||||
/// Remove a spike trail from our list of active trails
|
||||
/datum/action/cooldown/mob_cooldown/tendril_chaser/proc/on_chaser_destroyed(atom/chaser)
|
||||
SIGNAL_HANDLER
|
||||
LAZYREMOVE(active_chasers, WEAKREF(chaser))
|
||||
|
||||
/obj/effect/temp_visual/effect_trail/tendril_chaser
|
||||
duration = 10 SECONDS
|
||||
move_speed = 4
|
||||
spawned_effect = /obj/effect/temp_visual/emerging_ground_spike/tendril
|
||||
/// Do we spawn spikes around ourselves as well or only on our own turf?
|
||||
var/area_spawn = FALSE
|
||||
|
||||
/obj/effect/temp_visual/effect_trail/tendril_chaser/wide_area
|
||||
area_spawn = TRUE
|
||||
|
||||
/obj/effect/temp_visual/effect_trail/tendril_chaser/add_spawner()
|
||||
return
|
||||
|
||||
/obj/effect/temp_visual/effect_trail/tendril_chaser/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change)
|
||||
. = ..()
|
||||
if (!area_spawn)
|
||||
var/turf/spawn_turf = get_turf(src)
|
||||
if (!(locate(/obj/effect/temp_visual/emerging_ground_spike/tendril) in spawn_turf) && isopenturf(spawn_turf))
|
||||
new spawned_effect(spawn_turf)
|
||||
return
|
||||
|
||||
for (var/spawn_dir in GLOB.cardinals)
|
||||
if (spawn_dir & REVERSE_DIR(movement_dir))
|
||||
continue
|
||||
var/turf/spawn_loc = get_step(src, spawn_dir)
|
||||
if (!(locate(/obj/effect/temp_visual/emerging_ground_spike/tendril) in spawn_loc) && isopenturf(spawn_loc))
|
||||
new spawned_effect(spawn_loc)
|
||||
|
||||
/obj/effect/temp_visual/emerging_ground_spike/tendril
|
||||
icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi'
|
||||
icon_state = "spikes_stabbing"
|
||||
duration = 0.7 SECONDS
|
||||
position_variance = 3
|
||||
impale_damage = 10
|
||||
damage_blacklist_typecache = list(
|
||||
/mob/living/basic/mining/tendril,
|
||||
)
|
||||
impale_wound_bonus = CANT_WOUND
|
||||
// Have we hit someone yet?
|
||||
var/hit_loser = FALSE
|
||||
|
||||
/obj/effect/temp_visual/emerging_ground_spike/tendril/single
|
||||
icon_state = "spike"
|
||||
duration = 1 SECONDS
|
||||
harm_delay = 0.25 SECONDS
|
||||
position_variance = 5
|
||||
|
||||
/obj/effect/temp_visual/emerging_ground_spike/tendril/impale()
|
||||
. = ..()
|
||||
hit_loser |= .
|
||||
RegisterSignal(loc, COMSIG_ATOM_ENTERED, PROC_REF(on_entered))
|
||||
|
||||
/obj/effect/temp_visual/emerging_ground_spike/tendril/proc/on_entered(atom/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if (!isliving(arrived))
|
||||
return
|
||||
|
||||
if (harm_mob(arrived) && !hit_loser)
|
||||
playsound(src, 'sound/items/weapons/slice.ogg', vol = 50, vary = TRUE, pressure_affected = FALSE)
|
||||
hit_loser = TRUE
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/tendril_cross_spikes
|
||||
name = "Cross Spikes"
|
||||
desc = "Create a wave of spikes around yourself, impaling anyone caught in it."
|
||||
button_icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi'
|
||||
button_icon_state = "spike"
|
||||
background_icon_state = "bg_demon"
|
||||
overlay_icon_state = "bg_demon_border"
|
||||
click_to_activate = FALSE
|
||||
cooldown_time = 10 SECONDS
|
||||
melee_cooldown_time = 0
|
||||
shared_cooldown = NONE
|
||||
/// Health threshold at which we reduce the amount of empty spots on the ground
|
||||
var/health_threshold = 0.3
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/tendril_cross_spikes/Activate(atom/target)
|
||||
disable_cooldown_actions()
|
||||
spawn_spikes()
|
||||
SLEEP_CHECK_DEATH(0.4 SECONDS, owner)
|
||||
spawn_spikes(inverse = TRUE)
|
||||
StartCooldown()
|
||||
enable_cooldown_actions()
|
||||
return TRUE
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/tendril_cross_spikes/proc/spawn_spikes(inverse = FALSE)
|
||||
var/list/turf/spike_turfs = list()
|
||||
var/turf/owner_turf = get_turf(owner)
|
||||
|
||||
var/reduced_spawns = FALSE
|
||||
if (isliving(owner))
|
||||
var/mob/living/as_living = owner
|
||||
if (as_living.health / as_living.maxHealth <= health_threshold)
|
||||
reduced_spawns = TRUE
|
||||
|
||||
for (var/turf/open/target_turf in oview(7, owner_turf))
|
||||
if (sqrt((target_turf.x - owner_turf.x) ** 2 + (target_turf.y - owner_turf.y) ** 2) > 9.5) // big circle is a lie
|
||||
continue
|
||||
|
||||
if (reduced_spawns)
|
||||
if (abs(target_turf.x - owner_turf.x) % 2 == abs(target_turf.y - owner_turf.y + inverse) % 2)
|
||||
new /obj/effect/temp_visual/telegraphing/circle/short(target_turf)
|
||||
spike_turfs += target_turf
|
||||
continue
|
||||
|
||||
var/row_diff = inverse ? abs(target_turf.x - owner_turf.x) : abs(target_turf.y - owner_turf.y)
|
||||
var/column_diff = inverse ? abs(target_turf.y - owner_turf.y) : abs(target_turf.x - owner_turf.x)
|
||||
var/row = floor((row_diff + 1) / 3)
|
||||
if (row % 2 == 0)
|
||||
if (row_diff - row * 3 == 0)
|
||||
if (column_diff % 4 == 2)
|
||||
continue
|
||||
else
|
||||
if (column_diff % 4 != 0)
|
||||
continue
|
||||
else
|
||||
if (row_diff - row * 3 == 0)
|
||||
if (column_diff % 4 == 0)
|
||||
continue
|
||||
else
|
||||
if (column_diff % 4 != 2)
|
||||
continue
|
||||
|
||||
new /obj/effect/temp_visual/telegraphing/circle/short(target_turf)
|
||||
spike_turfs += target_turf
|
||||
|
||||
SLEEP_CHECK_DEATH(1 SECONDS, owner)
|
||||
|
||||
for (var/turf/open/to_spawn in spike_turfs)
|
||||
new /obj/effect/temp_visual/emerging_ground_spike/tendril/single(to_spawn)
|
||||
|
||||
SLEEP_CHECK_DEATH(0.8 SECONDS, owner)
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/projectile_attack/tendril_melee
|
||||
name = "Tentacle Stab"
|
||||
desc = "Stab nearby hostiles with long tentacles."
|
||||
button_icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi'
|
||||
button_icon_state = "goliath_tentacle_wiggle"
|
||||
background_icon_state = "bg_demon"
|
||||
overlay_icon_state = "bg_demon_border"
|
||||
click_to_activate = FALSE
|
||||
cooldown_time = 4 SECONDS
|
||||
melee_cooldown_time = 0
|
||||
shared_cooldown = NONE
|
||||
projectile_type = /obj/projectile/tentacle_lash/stab
|
||||
|
||||
/datum/action/cooldown/mob_cooldown/projectile_attack/tendril_melee/Activate(atom/target_atom, warning = TRUE)
|
||||
if (warning)
|
||||
for (var/stab_dir in GLOB.alldirs)
|
||||
var/turf/open/stab_turf = get_step(owner, stab_dir)
|
||||
if (!istype(stab_turf))
|
||||
continue
|
||||
var/obj/effect/temp_visual/telegraphing/line/short/telegraph = new(stab_turf)
|
||||
telegraph.dir = stab_dir
|
||||
|
||||
SLEEP_CHECK_DEATH(0.5 SECONDS, owner)
|
||||
|
||||
for (var/stab_dir in GLOB.alldirs)
|
||||
shoot_projectile(get_turf(owner), get_step(owner, stab_dir), firer = owner)
|
||||
|
||||
SLEEP_CHECK_DEATH(0.5 SECONDS, owner)
|
||||
StartCooldown()
|
||||
return TRUE
|
||||
@@ -0,0 +1,35 @@
|
||||
/datum/ai_controller/basic_controller/tendril
|
||||
blackboard = list(
|
||||
BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
|
||||
BB_TARGET_MINIMUM_STAT = HARD_CRIT,
|
||||
)
|
||||
|
||||
planning_subtrees = list(
|
||||
/datum/ai_planning_subtree/call_reinforcements/mining,
|
||||
/datum/ai_planning_subtree/target_retaliate/check_faction,
|
||||
/datum/ai_planning_subtree/simple_find_target,
|
||||
/datum/ai_planning_subtree/targeted_mob_ability/tendril_chaser,
|
||||
/datum/ai_planning_subtree/use_mob_ability/tendril_spikes,
|
||||
/datum/ai_planning_subtree/use_mob_ability/tendril_lash,
|
||||
)
|
||||
|
||||
/datum/ai_planning_subtree/targeted_mob_ability/tendril_chaser
|
||||
ability_key = BB_TENDRIL_CHASER
|
||||
operational_datums = list(/datum/component/ai_target_timer)
|
||||
finish_planning = FALSE
|
||||
|
||||
/datum/ai_planning_subtree/use_mob_ability/tendril_lash
|
||||
ability_key = BB_TENDRIL_LASH
|
||||
|
||||
/datum/ai_planning_subtree/use_mob_ability/tendril_lash/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
|
||||
if (!controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET])
|
||||
return FALSE
|
||||
return ..()
|
||||
|
||||
/datum/ai_planning_subtree/use_mob_ability/tendril_spikes
|
||||
ability_key = BB_TENDRIL_SPIKES
|
||||
|
||||
/datum/ai_planning_subtree/use_mob_ability/tendril_spikes/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
|
||||
if (!controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET])
|
||||
return FALSE
|
||||
return ..()
|
||||
@@ -45,6 +45,8 @@
|
||||
var/position_variance = 8
|
||||
/// Damage to deal on impale
|
||||
var/impale_damage = 15
|
||||
/// Wound bonus on impale
|
||||
var/impale_wound_bonus = 0
|
||||
/// Typecache of types of mobs not to damage
|
||||
var/list/damage_blacklist_typecache = list(
|
||||
/mob/living/basic/meteor_heart,
|
||||
@@ -67,14 +69,22 @@
|
||||
/obj/effect/temp_visual/emerging_ground_spike/proc/impale()
|
||||
if (!isturf(loc))
|
||||
return
|
||||
|
||||
var/hit_someone = FALSE
|
||||
for(var/mob/living/victim in loc)
|
||||
if (is_type_in_typecache(victim, damage_blacklist_typecache))
|
||||
continue
|
||||
hit_someone = TRUE
|
||||
var/target_zone = victim.resting ? BODY_ZONE_CHEST : pick_weight(standing_damage_zones)
|
||||
victim.apply_damage(impale_damage, damagetype = BRUTE, def_zone = target_zone, sharpness = SHARP_POINTY)
|
||||
if (harm_mob(victim))
|
||||
hit_someone = TRUE
|
||||
|
||||
if (hit_someone)
|
||||
playsound(src, 'sound/items/weapons/slice.ogg', vol = 50, vary = TRUE, pressure_affected = FALSE)
|
||||
else
|
||||
playsound(src, 'sound/misc/splort.ogg', vol = 25, vary = TRUE, pressure_affected = FALSE)
|
||||
return hit_someone
|
||||
|
||||
/obj/effect/temp_visual/emerging_ground_spike/proc/harm_mob(mob/living/victim)
|
||||
if (is_type_in_typecache(victim, damage_blacklist_typecache))
|
||||
return FALSE
|
||||
|
||||
var/target_zone = victim.resting ? BODY_ZONE_CHEST : pick_weight(standing_damage_zones)
|
||||
victim.apply_damage(impale_damage, damagetype = BRUTE, def_zone = target_zone, wound_bonus = impale_wound_bonus, sharpness = SHARP_POINTY)
|
||||
return TRUE
|
||||
|
||||
Reference in New Issue
Block a user