Files
Bubberstation/code/datums/actions/mobs/projectileattack.dm
SkyratBot aa74179ecf [MIRROR] Titan's Final Lament - Colossus and hierophant crusher trophy rework [MDB IGNORE] (#13478)
* Titan's Final Lament - Colossus and hierophant crusher trophy rework (#66793)

This is somewhat a port of BeeStation/BeeStation-Hornet#6805 with slight changes, credits to Rukofamicom for the original PR.

Kinetic crusher is supposed to be a high risk high reward weapon, but with current hiero wall trophy it just ends up with the player cheesing mob and boss AIs. To fix this, here we change hiero trophy to spawn a hierophant chaser instead of a wall, which will increase player's DPS without offering them any protection.

However, this creates another issue: right now colossus is unbeatable with crusher without cheesing it with the wall due to it's shotgun attack which kills and dusts you point blank or 1 tile away, which is extremely unfair and unfun. To fix this, I add what should've been done from the start - make colossus telegraph it's attacks before starting them. Player has 1.5 seconds to react(unlike 3 seconds in the original PR which made the fight much easier) and dodge the attack or at least get away from the colossus to have a chance to survive.

Since this does make the fight significantly easier, colossus gets a special final attack during which it shouts "Perish" and uses different attacks. Unlike in the original PR, he can only use it once, so it's not as deadly and bullshit. This should make the fight more epic and similar to other bossfights which get cool attacks near the end of the battle.

Goodbye 4 GBP, you served me well.

* Titan's Final Lament - Colossus and hierophant crusher trophy rework

Co-authored-by: SmArtKar <44720187+SmArtKar@users.noreply.github.com>
2022-05-10 01:34:38 +01:00

328 lines
13 KiB
Plaintext

/datum/action/cooldown/mob_cooldown/projectile_attack
name = "Projectile Attack"
icon_icon = 'icons/mob/actions/actions_items.dmi'
button_icon_state = "sniper_zoom"
desc = "Fires a set of projectiles at a selected target."
cooldown_time = 1.5 SECONDS
/// The type of the projectile to be fired
var/projectile_type
/// The sound played when a projectile is fired
var/projectile_sound
/// If the projectile should home in on its target
var/has_homing = FALSE
/// The turning speed if there is homing
var/homing_turn_speed = 30
/// The variance in the projectiles direction
var/default_projectile_spread = 0
/// The multiplier to the projectiles speed (a value of 2 makes it twice as slow, 0.5 makes it twice as fast)
var/projectile_speed_multiplier = 1
/datum/action/cooldown/mob_cooldown/projectile_attack/New(Target, projectile, homing, spread)
. = ..()
if(projectile)
projectile_type = projectile
if(homing)
has_homing = homing
if(spread)
default_projectile_spread = spread
/datum/action/cooldown/mob_cooldown/projectile_attack/Activate(atom/target_atom)
StartCooldown(10 SECONDS)
attack_sequence(owner, target_atom)
StartCooldown()
/datum/action/cooldown/mob_cooldown/projectile_attack/proc/attack_sequence(mob/living/firer, atom/target)
shoot_projectile(firer, target, null, firer, rand(-default_projectile_spread, default_projectile_spread), null)
/datum/action/cooldown/mob_cooldown/projectile_attack/proc/shoot_projectile(atom/origin, atom/target, set_angle, mob/firer, projectile_spread, speed_multiplier, override_projectile_type, override_homing)
var/turf/startloc = get_turf(origin)
var/turf/endloc = get_turf(target)
if(!startloc || !endloc)
return
var/obj/projectile/our_projectile
if(override_projectile_type)
our_projectile = new override_projectile_type(startloc)
else
our_projectile = new projectile_type(startloc)
if(!isnum(speed_multiplier))
speed_multiplier = projectile_speed_multiplier
our_projectile.speed *= speed_multiplier
our_projectile.preparePixelProjectile(endloc, startloc, null, projectile_spread)
our_projectile.firer = firer
if(target)
our_projectile.original = target
if(override_homing == null && has_homing || override_homing)
our_projectile.homing_turn_speed = homing_turn_speed
our_projectile.set_homing_target(target)
if(isnum(set_angle))
our_projectile.fire(set_angle)
return
our_projectile.fire()
return our_projectile
/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire
name = "Rapid Fire"
icon_icon = 'icons/obj/guns/energy.dmi'
button_icon_state = "kineticgun"
desc = "Fires projectiles repeatedly at a given target."
cooldown_time = 1.5 SECONDS
projectile_type = /obj/projectile/colossus/snowball
default_projectile_spread = 45
/// Total shot count
var/shot_count = 60
/// Delay between shots
var/shot_delay = 0.1 SECONDS
/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/attack_sequence(mob/living/firer, atom/target)
for(var/i in 1 to shot_count)
shoot_projectile(firer, target, null, firer, rand(-default_projectile_spread, default_projectile_spread), null)
SLEEP_CHECK_DEATH(shot_delay, src)
/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/shrapnel
name = "Shrapnel Fire"
icon_icon = 'icons/mob/actions/actions_items.dmi'
button_icon_state = "sniper_zoom"
desc = "Fires projectiles that will split into shrapnel after a period of time."
cooldown_time = 6 SECONDS
projectile_type = /obj/projectile/colossus/frost_orb
has_homing = TRUE
default_projectile_spread = 180
shot_count = 8
shot_delay = 1 SECONDS
var/shrapnel_projectile_type = /obj/projectile/colossus/ice_blast
var/shrapnel_angles = list(0, 60, 120, 180, 240, 300)
var/shrapnel_spread = 10
var/break_time = 2 SECONDS
/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/shrapnel/attack_sequence(mob/living/firer, atom/target)
for(var/i in 1 to shot_count)
var/obj/projectile/to_explode = shoot_projectile(firer, target, null, firer, rand(-default_projectile_spread, default_projectile_spread), null)
addtimer(CALLBACK(src, .proc/explode_into_shrapnel, firer, target, to_explode), break_time)
SLEEP_CHECK_DEATH(shot_delay, src)
/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/shrapnel/proc/explode_into_shrapnel(mob/living/firer, atom/target, obj/projectile/to_explode)
if(!to_explode)
return
for(var/angle in shrapnel_angles)
// no speed multiplier for shrapnel
shoot_projectile(to_explode, target, angle + rand(-shrapnel_spread, shrapnel_spread), firer, null, 1, shrapnel_projectile_type, FALSE)
qdel(to_explode)
/datum/action/cooldown/mob_cooldown/projectile_attack/spiral_shots
name = "Spiral Shots"
icon_icon = 'icons/mob/actions/actions_items.dmi'
button_icon_state = "sniper_zoom"
desc = "Fires projectiles in a spiral pattern."
cooldown_time = 3 SECONDS
projectile_type = /obj/projectile/colossus
projectile_sound = 'sound/magic/clockwork/invoke_general.ogg'
/// Whether or not the attack is the enraged form
var/enraged = FALSE
/datum/action/cooldown/mob_cooldown/projectile_attack/spiral_shots/attack_sequence(mob/living/firer, atom/target)
if(enraged)
SLEEP_CHECK_DEATH(1 SECONDS, firer)
INVOKE_ASYNC(src, .proc/create_spiral_attack, firer, target, TRUE)
create_spiral_attack(firer, target, FALSE)
return
create_spiral_attack(firer, target)
/datum/action/cooldown/mob_cooldown/projectile_attack/spiral_shots/proc/create_spiral_attack(mob/living/firer, atom/target, negative = pick(TRUE, FALSE))
var/counter = 8
for(var/i in 1 to 80)
if(negative)
counter--
else
counter++
if(counter > 16)
counter = 1
if(counter < 1)
counter = 16
shoot_projectile(firer, target, counter * 22.5, firer, null, null)
playsound(get_turf(firer), projectile_sound, 20, TRUE)
SLEEP_CHECK_DEATH(0.1 SECONDS, firer)
/datum/action/cooldown/mob_cooldown/projectile_attack/spiral_shots/colossus
cooldown_time = 1.5 SECONDS
/datum/action/cooldown/mob_cooldown/projectile_attack/spiral_shots/colossus/Activate(atom/target_atom)
SLEEP_CHECK_DEATH(1.5 SECONDS, owner)
return ..()
/datum/action/cooldown/mob_cooldown/projectile_attack/random_aoe
name = "All Directions"
icon_icon = 'icons/effects/effects.dmi'
button_icon_state = "at_shield2"
desc = "Fires projectiles in all directions."
cooldown_time = 3 SECONDS
projectile_type = /obj/projectile/colossus
projectile_sound = 'sound/magic/clockwork/invoke_general.ogg'
/datum/action/cooldown/mob_cooldown/projectile_attack/random_aoe/attack_sequence(mob/living/firer, atom/target)
var/turf/U = get_turf(firer)
playsound(U, projectile_sound, 300, TRUE, 5)
for(var/i in 1 to 32)
shoot_projectile(firer, target, rand(0, 360), firer, null, null)
/datum/action/cooldown/mob_cooldown/projectile_attack/random_aoe/colossus
cooldown_time = 1.5 SECONDS
/datum/action/cooldown/mob_cooldown/projectile_attack/random_aoe/colossus/Activate(atom/target_atom)
SLEEP_CHECK_DEATH(1.5 SECONDS, owner)
return ..()
/datum/action/cooldown/mob_cooldown/projectile_attack/shotgun_blast
name = "Shotgun Fire"
icon_icon = 'icons/obj/guns/ballistic.dmi'
button_icon_state = "shotgun"
desc = "Fires projectiles in a shotgun pattern."
cooldown_time = 2 SECONDS
projectile_type = /obj/projectile/colossus
projectile_sound = 'sound/magic/clockwork/invoke_general.ogg'
var/list/shot_angles = list(12.5, 7.5, 2.5, -2.5, -7.5, -12.5)
/datum/action/cooldown/mob_cooldown/projectile_attack/shotgun_blast/New(Target, projectile, homing, spread, list/angles)
. = ..()
if(angles)
shot_angles = angles
/datum/action/cooldown/mob_cooldown/projectile_attack/shotgun_blast/attack_sequence(mob/living/firer, atom/target)
fire_shotgun(firer, target, shot_angles)
/datum/action/cooldown/mob_cooldown/projectile_attack/shotgun_blast/proc/fire_shotgun(mob/living/firer, atom/target, list/chosen_angles)
playsound(firer, projectile_sound, 200, TRUE, 2)
for(var/spread in chosen_angles)
shoot_projectile(firer, target, null, firer, spread, null)
/datum/action/cooldown/mob_cooldown/projectile_attack/shotgun_blast/colossus
cooldown_time = 0.5 SECONDS
/datum/action/cooldown/mob_cooldown/projectile_attack/shotgun_blast/colossus/Activate(atom/target_atom)
SLEEP_CHECK_DEATH(1.5 SECONDS, owner)
return ..()
/datum/action/cooldown/mob_cooldown/projectile_attack/shotgun_blast/pattern
name = "Alternating Shotgun Fire"
desc = "Fires projectiles in an alternating shotgun pattern."
projectile_type = /obj/projectile/colossus/ice_blast
projectile_sound = null
shot_angles = list(list(-40, -20, 0, 20, 40), list(-30, -10, 10, 30))
var/shot_count = 5
/datum/action/cooldown/mob_cooldown/projectile_attack/shotgun_blast/pattern/attack_sequence(mob/living/firer, atom/target)
for(var/i in 1 to shot_count)
var/list/pattern = shot_angles[i % length(shot_angles) + 1] // changing patterns
fire_shotgun(firer, target, pattern)
SLEEP_CHECK_DEATH(0.8 SECONDS, firer)
/datum/action/cooldown/mob_cooldown/projectile_attack/dir_shots
name = "Directional Shots"
icon_icon = 'icons/obj/guns/ballistic.dmi'
button_icon_state = "pistol"
desc = "Fires projectiles in specific directions."
cooldown_time = 4 SECONDS
projectile_type = /obj/projectile/colossus
projectile_sound = 'sound/magic/clockwork/invoke_general.ogg'
var/list/firing_directions
/datum/action/cooldown/mob_cooldown/projectile_attack/dir_shots/New(Target, projectile, homing, spread, list/dirs)
. = ..()
if(dirs)
firing_directions = dirs
else
firing_directions = GLOB.alldirs.Copy()
/datum/action/cooldown/mob_cooldown/projectile_attack/dir_shots/attack_sequence(mob/living/firer, atom/target)
fire_in_directions(firer, target, firing_directions)
/datum/action/cooldown/mob_cooldown/projectile_attack/dir_shots/proc/fire_in_directions(mob/living/firer, atom/target, list/dirs)
if(!islist(dirs))
dirs = GLOB.alldirs.Copy()
playsound(firer, projectile_sound, 200, TRUE, 2)
for(var/d in dirs)
var/turf/E = get_step(firer, d)
shoot_projectile(firer, E, null, firer, null, null)
/datum/action/cooldown/mob_cooldown/projectile_attack/dir_shots/alternating
name = "Alternating Shots"
desc = "Fires projectiles in alternating directions."
/datum/action/cooldown/mob_cooldown/projectile_attack/dir_shots/alternating/attack_sequence(mob/living/firer, atom/target)
fire_in_directions(firer, target, GLOB.diagonals)
SLEEP_CHECK_DEATH(1 SECONDS, firer)
fire_in_directions(firer, target, GLOB.cardinals)
SLEEP_CHECK_DEATH(1 SECONDS, firer)
fire_in_directions(firer, target, GLOB.diagonals)
SLEEP_CHECK_DEATH(1 SECONDS, firer)
fire_in_directions(firer, target, GLOB.cardinals)
/datum/action/cooldown/mob_cooldown/projectile_attack/dir_shots/alternating/colossus
cooldown_time = 2.5 SECONDS
/datum/action/cooldown/mob_cooldown/projectile_attack/dir_shots/alternating/colossus/Activate(atom/target_atom)
SLEEP_CHECK_DEATH(1.5 SECONDS, owner)
return ..()
/datum/action/cooldown/mob_cooldown/projectile_attack/kinetic_accelerator
name = "Fire Kinetic Accelerator"
icon_icon = 'icons/obj/guns/energy.dmi'
button_icon_state = "kineticgun"
desc = "Fires a kinetic accelerator projectile at the target."
cooldown_time = 1.5 SECONDS
projectile_type = /obj/projectile/kinetic/miner
projectile_sound = 'sound/weapons/kenetic_accel.ogg'
/datum/action/cooldown/mob_cooldown/projectile_attack/kinetic_accelerator/Activate(atom/target_atom)
. = ..()
playsound(owner, projectile_sound, 200, TRUE, 2)
owner.visible_message(span_danger("[owner] fires the proto-kinetic accelerator!"))
owner.face_atom(target_atom)
new /obj/effect/temp_visual/dir_setting/firing_effect(owner.loc, owner.dir)
/datum/action/cooldown/mob_cooldown/projectile_attack/colossus_final
name = "Titan's Finale"
desc = "A single-use ability that shoots a large amount of projectiles around you."
cooldown_time = 2.5 SECONDS
/datum/action/cooldown/mob_cooldown/projectile_attack/colossus_final/Activate(atom/target_atom)
StartCooldown(30 SECONDS)
attack_sequence(owner, target_atom)
StartCooldown()
Remove(owner)
/datum/action/cooldown/mob_cooldown/projectile_attack/colossus_final/attack_sequence(mob/living/firer, atom/target)
var/mob/living/simple_animal/hostile/megafauna/colossus/colossus
if(istype(firer, /mob/living/simple_animal/hostile/megafauna/colossus))
colossus = firer
colossus.say("Perish.", spans = list("colossus", "yell"))
var/finale_counter = 10
for(var/i in 1 to 20)
if(finale_counter > 4 && colossus)
colossus.telegraph()
colossus.shotgun_blast.attack_sequence(firer, target)
if(finale_counter > 1)
finale_counter -= 1
var/turf/start_turf = get_turf(firer)
for(var/turf/target_turf in RANGE_TURFS(12, start_turf))
if(prob(min(finale_counter, 2)) && target_turf != get_turf(firer))
shoot_projectile(firer, target_turf, null, firer, null, null)
SLEEP_CHECK_DEATH(finale_counter + 1, firer)
for(var/i in 1 to 3)
if(colossus)
colossus.telegraph()
colossus.random_shots.attack_sequence(firer, target)
finale_counter += 6
SLEEP_CHECK_DEATH(finale_counter, firer)
for(var/i in 1 to 3)
if(colossus)
colossus.telegraph()
colossus.dir_shots.attack_sequence(firer, target)
SLEEP_CHECK_DEATH(1 SECONDS, firer)