diff --git a/code/datums/action.dm b/code/datums/action.dm index 40237c1b25b..157a4fd37a1 100644 --- a/code/datums/action.dm +++ b/code/datums/action.dm @@ -706,11 +706,25 @@ small_icon = 'icons/mob/alien.dmi' small_icon_state = "alienq" -/datum/action/small_sprite/drake +/datum/action/small_sprite/megafauna + icon_icon = 'icons/mob/actions/actions_xeno.dmi' + button_icon_state = "smallqueen" + background_icon_state = "bg_alien" small_icon = 'icons/mob/lavaland/lavaland_monsters.dmi' + +/datum/action/small_sprite/megafauna/drake small_icon_state = "ash_whelp" -/datum/action/small_sprite/spacedragon +/datum/action/small_sprite/megafauna/colossus + small_icon_state = "Basilisk" + +/datum/action/small_sprite/megafauna/bubblegum + small_icon_state = "goliath2" + +/datum/action/small_sprite/megafauna/legion + small_icon_state = "dwarf_legion" + +/datum/action/small_sprite/megafauna/spacedragon small_icon = 'icons/mob/animal.dmi' small_icon_state = "carp" diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm index 08bbbcc5e3f..06172725192 100644 --- a/code/modules/admin/verbs/randomverbs.dm +++ b/code/modules/admin/verbs/randomverbs.dm @@ -1175,55 +1175,6 @@ Traitors and the like can also be revived with the previous role mostly intact. msg += "" src << browse(msg.Join(), "window=Player_playtime_check") -/obj/effect/temp_visual/fireball - icon = 'icons/obj/wizard.dmi' - icon_state = "fireball" - name = "fireball" - desc = "Get out of the way!" - layer = FLY_LAYER - randomdir = FALSE - duration = 9 - pixel_z = 270 - -/obj/effect/temp_visual/fireball/Initialize() - . = ..() - animate(src, pixel_z = 0, time = duration) - -/obj/effect/temp_visual/target - icon = 'icons/mob/actions/actions_items.dmi' - icon_state = "sniper_zoom" - layer = BELOW_MOB_LAYER - light_range = 2 - duration = 9 - -/obj/effect/temp_visual/target/ex_act() - return - -/obj/effect/temp_visual/target/Initialize(mapload, list/flame_hit) - . = ..() - INVOKE_ASYNC(src, .proc/fall, flame_hit) - -/obj/effect/temp_visual/target/proc/fall(list/flame_hit) - var/turf/T = get_turf(src) - playsound(T,'sound/magic/fleshtostone.ogg', 80, 1) - new /obj/effect/temp_visual/fireball(T) - sleep(duration) - if(ismineralturf(T)) - var/turf/closed/mineral/M = T - M.gets_drilled() - playsound(T, "explosion", 80, 1) - new /obj/effect/hotspot(T) - T.hotspot_expose(700, 50, 1) - for(var/mob/living/L in T.contents) - if(istype(L, /mob/living/simple_animal/hostile/megafauna/dragon)) - continue - if(islist(flame_hit) && !flame_hit[L]) - L.adjustFireLoss(40) - to_chat(L, "You're hit by the drake's fire breath!") - flame_hit[L] = TRUE - else - L.adjustFireLoss(10) //if we've already hit them, do way less damage - /datum/admins/proc/cmd_show_exp_panel(client/C) if(!check_rights(R_ADMIN)) return diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm index 1eec7328f4e..15fec5c922f 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm @@ -32,11 +32,11 @@ Difficulty: Medium light_color = "#E4C7C5" movement_type = GROUND speak_emote = list("roars") - speed = 1 + speed = 3 move_to_delay = 3 projectiletype = /obj/item/projectile/kinetic/miner projectilesound = 'sound/weapons/kenetic_accel.ogg' - ranged = 1 + ranged = TRUE ranged_cooldown_time = 16 pixel_x = -16 crusher_loot = list(/obj/item/melee/transforming/cleaving_saw, /obj/item/gun/energy/kinetic_accelerator, /obj/item/crusher_trophy/miner_eye) @@ -51,18 +51,56 @@ Difficulty: Medium var/dashing = FALSE var/dash_cooldown = 15 var/guidance = FALSE + var/transform_stop_attack = FALSE // stops the blood drunk miner from attacking after transforming his weapon until the next attack chain deathmessage = "falls to the ground, decaying into glowing particles." deathsound = "bodyfall" - do_footstep = TRUE + attack_action_types = list(/datum/action/innate/megafauna_attack/dash, + /datum/action/innate/megafauna_attack/kinetic_accelerator, + /datum/action/innate/megafauna_attack/transform_weapon) -/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/guidance - guidance = TRUE - -/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/hunter/AttackingTarget() +/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/Initialize() . = ..() - if(. && prob(12)) - INVOKE_ASYNC(src, .proc/dash) + miner_saw = new(src) + +/datum/action/innate/megafauna_attack/dash + name = "Dash To Target" + icon_icon = 'icons/mob/actions/actions_items.dmi' + button_icon_state = "sniper_zoom" + chosen_message = "You are now dashing to your target." + chosen_attack_num = 1 + +/datum/action/innate/megafauna_attack/kinetic_accelerator + name = "Fire Kinetic Accelerator" + icon_icon = 'icons/obj/guns/energy.dmi' + button_icon_state = "kineticgun" + chosen_message = "You are now shooting your kinetic accelerator." + chosen_attack_num = 2 + +/datum/action/innate/megafauna_attack/transform_weapon + name = "Transform Weapon" + icon_icon = 'icons/obj/lavaland/artefacts.dmi' + button_icon_state = "cleaving_saw" + chosen_message = "You are now transforming your weapon." + chosen_attack_num = 3 + +/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/OpenFire() + if(client) + switch(chosen_attack) + if(1) + dash(target) + if(2) + shoot_ka() + if(3) + transform_weapon() + return + + Goto(target, move_to_delay, minimum_distance) + if(get_dist(src, target) > MINER_DASH_RANGE && dash_cooldown <= world.time) + dash_attack() + else + shoot_ka() + transform_weapon() /obj/item/melee/transforming/cleaving_saw/miner //nerfed saw because it is very murdery force = 6 @@ -79,10 +117,6 @@ Difficulty: Medium icon_state = "ka_tracer" range = MINER_DASH_RANGE -/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/Initialize() - . = ..() - miner_saw = new(src) - /mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/adjustHealth(amount, updating_health = TRUE, forced = FALSE) var/adjustment_amount = amount * 0.1 if(world.time + adjustment_amount > next_move) @@ -90,10 +124,9 @@ Difficulty: Medium . = ..() /mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/death() - if(health > 0) - return - new /obj/effect/temp_visual/dir_setting/miner_death(loc, dir) - return ..() + . = ..() + if(.) + new /obj/effect/temp_visual/dir_setting/miner_death(loc, dir) /mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/Move(atom/newloc) if(dashing || (newloc && newloc.z == z && (islava(newloc) || ischasm(newloc)))) //we're not stupid! @@ -105,11 +138,14 @@ Difficulty: Medium return return ..() +/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/MeleeAction(patience = TRUE) + transform_stop_attack = FALSE + return ..() + /mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/AttackingTarget() - if(QDELETED(target)) - return - if(next_move > world.time || !Adjacent(target)) //some cheating - INVOKE_ASYNC(src, .proc/quick_attack_loop) + if(client) + transform_stop_attack = FALSE + if(QDELETED(target) || transform_stop_attack) return face_atom(target) if(isliving(target)) @@ -128,8 +164,6 @@ Difficulty: Medium miner_saw.melee_attack_chain(src, target) if(guidance) adjustHealth(-2) - transform_weapon() - INVOKE_ASYNC(src, .proc/quick_attack_loop) return TRUE /mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect) @@ -142,16 +176,10 @@ Difficulty: Medium . = ..() if(. && target && !targets_the_same) wander = TRUE - transform_weapon() - INVOKE_ASYNC(src, .proc/quick_attack_loop) -/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/OpenFire() - Goto(target, move_to_delay, minimum_distance) - if(get_dist(src, target) > MINER_DASH_RANGE && dash_cooldown <= world.time) - INVOKE_ASYNC(src, .proc/dash, target) - else - shoot_ka() - transform_weapon() +/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/proc/dash_attack() + INVOKE_ASYNC(src, .proc/dash, target) + shoot_ka() /mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/proc/shoot_ka() if(ranged_cooldown <= world.time && get_dist(src, target) <= MINER_DASH_RANGE && !Adjacent(target)) @@ -162,21 +190,6 @@ Difficulty: Medium Shoot(target) changeNext_move(CLICK_CD_RANGE) -//I'm still of the belief that this entire proc needs to be wiped from existence. -// do not take my touching of it to be endorsement of it. ~mso -/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/proc/quick_attack_loop() - while(!QDELETED(target) && next_move <= world.time) //this is done this way because next_move can change to be sooner while we sleep. - stoplag(1) - sleep((next_move - world.time) * 1.5) //but don't ask me what the fuck this is about - if(QDELETED(target)) - return - if(dashing || next_move > world.time || !Adjacent(target)) - if(dashing && next_move <= world.time) - next_move = world.time + 1 - INVOKE_ASYNC(src, .proc/quick_attack_loop) //lets try that again. - return - AttackingTarget() - /mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/proc/dash(atom/dash_target) if(world.time < dash_cooldown) return @@ -220,19 +233,23 @@ Difficulty: Medium dashing = TRUE alpha = 0 animate(src, alpha = 255, time = 5) - sleep(2) + SLEEP_CHECK_DEATH(2) D.forceMove(step_forward_turf) forceMove(target_turf) playsound(target_turf, 'sound/weapons/punchmiss.ogg', 40, 1, -1) - sleep(1) + SLEEP_CHECK_DEATH(1) dashing = FALSE - shoot_ka() return TRUE /mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/proc/transform_weapon() if(time_until_next_transform <= world.time) miner_saw.transform_cooldown = 0 miner_saw.transform_weapon(src, TRUE) + if(!miner_saw.active) + rapid_melee = 5 // 4 deci cooldown before changes, npcpool subsystem wait is 20, 20/4 = 5 + else + rapid_melee = 3 // same thing but halved (slightly rounded up) + transform_stop_attack = TRUE icon_state = "miner[miner_saw.active ? "_transformed":""]" icon_living = "miner[miner_saw.active ? "_transformed":""]" time_until_next_transform = world.time + rand(50, 100) @@ -264,4 +281,12 @@ Difficulty: Medium desc = "The sweet blood, oh, it sings to me." invisibility = 100 +/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/guidance + guidance = TRUE + +/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/hunter/AttackingTarget() + . = ..() + if(. && prob(12)) + INVOKE_ASYNC(src, .proc/dash) + #undef MINER_DASH_RANGE diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm index f1103b32bc0..2b038ec913d 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm @@ -1,3 +1,7 @@ +#define BUBBLEGUM_SMASH (health <= maxHealth*0.5) // angery +#define BUBBLEGUM_CAN_ENRAGE (enrage_till + (enrage_time * 2) <= world.time) +#define BUBBLEGUM_IS_ENRAGED (enrage_till > world.time) + /* BUBBLEGUM @@ -39,78 +43,32 @@ Difficulty: Hard armour_penetration = 40 melee_damage_lower = 40 melee_damage_upper = 40 - speed = 1 + speed = 5 move_to_delay = 5 retreat_distance = 5 minimum_distance = 5 rapid_melee = 8 // every 1/4 second melee_queue_distance = 20 // as far as possible really, need this because of blood warp - ranged = 1 + ranged = TRUE pixel_x = -32 - del_on_death = 1 + del_on_death = TRUE crusher_loot = list(/obj/structure/closet/crate/necropolis/bubblegum/crusher) loot = list(/obj/structure/closet/crate/necropolis/bubblegum) blood_volume = BLOOD_VOLUME_MAXIMUM //BLEED FOR ME - var/charging = 0 - var/enrage_till = null + var/charging = FALSE + var/enrage_till = 0 + var/enrage_time = 70 + var/revving_charge = FALSE internal_type = /obj/item/gps/internal/bubblegum medal_type = BOSS_MEDAL_BUBBLEGUM score_type = BUBBLEGUM_SCORE deathmessage = "sinks into a pool of blood, fleeing the battle. You've won, for now... " deathsound = 'sound/magic/enter_blood.ogg' - -/obj/item/gps/internal/bubblegum - icon_state = null - gpstag = "Bloody Signal" - desc = "You're not quite sure how a signal can be bloody." - invisibility = 100 - -/mob/living/simple_animal/hostile/megafauna/bubblegum/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE) - . = ..() - if(. > 0 && prob(25)) - var/obj/effect/decal/cleanable/blood/gibs/bubblegum/B = new /obj/effect/decal/cleanable/blood/gibs/bubblegum(loc) - if(prob(40)) - step(B, pick(GLOB.cardinals)) - else - B.setDir(pick(GLOB.cardinals)) - -/obj/effect/decal/cleanable/blood/gibs/bubblegum - name = "thick blood" - desc = "Thick, splattered blood." - random_icon_states = list("gib3", "gib5", "gib6") - bloodiness = 20 - -/obj/effect/decal/cleanable/blood/gibs/bubblegum/can_bloodcrawl_in() - return TRUE - -/mob/living/simple_animal/hostile/megafauna/bubblegum/OpenFire() - anger_modifier = CLAMP(((maxHealth - health)/60),0,20) - if(charging) - return - ranged_cooldown = world.time + 50 - if(!try_bloodattack()) - blood_warp() - - if(health > maxHealth * 0.5) - if(prob(50 + anger_modifier)) - charge(delay = 6) - charge(delay = 4) // The FitnessGram Pacer Test is a multistage aerobic capacity test that progressively gets more difficult as it continues. - charge(delay = 2) - SetRecoveryTime(15) - else - hallucination_charge_around(times = 6, delay = 10 - anger_modifier / 5) - SetRecoveryTime(10) - else - if(prob(50 - anger_modifier)) - hallucination_charge_around(times = 4, delay = 9) - hallucination_charge_around(times = 4, delay = 8) - hallucination_charge_around(times = 4, delay = 7) - SetRecoveryTime(15) - else - for(var/i = 1 to 5) - INVOKE_ASYNC(src, .proc/hallucination_charge_around, 2, 10, 2, 0) - sleep(5) - SetRecoveryTime(10) + attack_action_types = list(/datum/action/innate/megafauna_attack/triple_charge, + /datum/action/innate/megafauna_attack/hallucination_charge, + /datum/action/innate/megafauna_attack/hallucination_surround, + /datum/action/innate/megafauna_attack/blood_warp) + small_sprite_type = /datum/action/small_sprite/megafauna/bubblegum /mob/living/simple_animal/hostile/megafauna/bubblegum/Initialize() . = ..() @@ -118,64 +76,94 @@ Difficulty: Hard for(var/mob/living/simple_animal/hostile/megafauna/bubblegum/B in GLOB.mob_living_list) if(B != src) return INITIALIZE_HINT_QDEL //There can be only one - var/obj/effect/proc_holder/spell/bloodcrawl/bloodspell = new - AddSpell(bloodspell) - if(istype(loc, /obj/effect/dummy/phased_mob/slaughter)) - bloodspell.phased = TRUE -/mob/living/simple_animal/hostile/megafauna/bubblegum/grant_achievement(medaltype,scoretype) - . = ..() - if(.) - SSshuttle.shuttle_purchase_requirements_met |= "bubblegum" +/datum/action/innate/megafauna_attack/triple_charge + name = "Triple Charge" + icon_icon = 'icons/mob/actions/actions_items.dmi' + button_icon_state = "sniper_zoom" + chosen_message = "You are now triple charging at the target you click on." + chosen_attack_num = 1 -/mob/living/simple_animal/hostile/megafauna/bubblegum/do_attack_animation(atom/A, visual_effect_icon) - if(!charging) - ..() +/datum/action/innate/megafauna_attack/hallucination_charge + name = "Hallucination Charge" + icon_icon = 'icons/effects/bubblegum.dmi' + button_icon_state = "smack ya one" + chosen_message = "You are now charging with hallucinations at the target you click on." + chosen_attack_num = 2 -/mob/living/simple_animal/hostile/megafauna/bubblegum/AttackingTarget() - if(!charging) - . = ..() - if(.) - recovery_time = world.time + 20 // can only attack melee once every 2 seconds but rapid_melee gives higher priority +/datum/action/innate/megafauna_attack/hallucination_surround + name = "Surround Target" + icon_icon = 'icons/turf/walls/wall.dmi' + button_icon_state = "wall" + chosen_message = "You are now surrounding the target you click on with hallucinations." + chosen_attack_num = 3 -/mob/living/simple_animal/hostile/megafauna/bubblegum/bullet_act(obj/item/projectile/P) - if(is_enraged()) - visible_message("[src] deflects the projectile; [p_they()] can't be hit with ranged weapons while enraged!", "You deflect the projectile!") - playsound(src, pick('sound/weapons/bulletflyby.ogg', 'sound/weapons/bulletflyby2.ogg', 'sound/weapons/bulletflyby3.ogg'), 300, 1) - return 0 - return ..() +/datum/action/innate/megafauna_attack/blood_warp + name = "Blood Warp" + icon_icon = 'icons/effects/blood.dmi' + button_icon_state = "floor1" + chosen_message = "You are now warping to blood around your clicked position." + chosen_attack_num = 4 -/mob/living/simple_animal/hostile/megafauna/bubblegum/ex_act(severity, target) - if(severity >= EXPLODE_LIGHT) +/mob/living/simple_animal/hostile/megafauna/bubblegum/OpenFire() + if(charging) return - severity = EXPLODE_LIGHT // puny mortals - return ..() -/mob/living/simple_animal/hostile/megafauna/bubblegum/CanPass(atom/movable/mover, turf/target) - if(istype(mover, /mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination)) - return 1 - return ..() + anger_modifier = CLAMP(((maxHealth - health)/60),0,20) + enrage_time = initial(enrage_time) * CLAMP(anger_modifier / 20, 0.5, 1) + ranged_cooldown = world.time + 50 -/mob/living/simple_animal/hostile/megafauna/bubblegum/Goto(target, delay, minimum_distance) - if(!charging) - ..() + if(client) + switch(chosen_attack) + if(1) + triple_charge() + if(2) + hallucination_charge() + if(3) + surround_with_hallucinations() + if(4) + blood_warp() + return -/mob/living/simple_animal/hostile/megafauna/bubblegum/MoveToTarget(list/possible_targets) - if(!charging) - ..() + if(!try_bloodattack() || prob(25 + anger_modifier)) + blood_warp() -/mob/living/simple_animal/hostile/megafauna/bubblegum/Move() - if(charging) - new /obj/effect/temp_visual/decoy/fading(loc,src) - DestroySurroundings() - ..() + if(!BUBBLEGUM_SMASH) + if(prob(50 + anger_modifier)) + triple_charge() + else + slaughterlings() + else + if(prob(50 + anger_modifier)) + hallucination_charge() + else + surround_with_hallucinations() -/mob/living/simple_animal/hostile/megafauna/bubblegum/Moved() - new /obj/effect/decal/cleanable/blood(src.loc) - if(charging) - DestroySurroundings() - playsound(src, 'sound/effects/meteorimpact.ogg', 200, 1, 2, 1) - return ..() +/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/triple_charge() + charge(delay = 6) + charge(delay = 4) + charge(delay = 2) + SetRecoveryTime(15) + +/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/hallucination_charge() + if(!BUBBLEGUM_SMASH || prob(33)) + hallucination_charge_around(times = 6, delay = 8) + SetRecoveryTime(10) + else + hallucination_charge_around(times = 4, delay = 9) + hallucination_charge_around(times = 4, delay = 8) + hallucination_charge_around(times = 4, delay = 7) + triple_charge() + SetRecoveryTime(20) + +/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/surround_with_hallucinations() + for(var/i = 1 to 5) + INVOKE_ASYNC(src, .proc/hallucination_charge_around, 2, 8, 2, 0, 4) + if(ismob(target)) + charge(delay = 6) + else + SLEEP_CHECK_DEATH(6) + SetRecoveryTime(20) /mob/living/simple_animal/hostile/megafauna/bubblegum/proc/charge(var/atom/chargeat = target, var/delay = 3, var/chargepast = 2) if(!chargeat) @@ -188,34 +176,21 @@ Difficulty: Hard if(!T) return new /obj/effect/temp_visual/dragon_swoop/bubblegum(T) - charging = 1 + charging = TRUE + revving_charge = TRUE DestroySurroundings() walk(src, 0) setDir(dir) var/obj/effect/temp_visual/decoy/D = new /obj/effect/temp_visual/decoy(loc,src) animate(D, alpha = 0, color = "#FF0000", transform = matrix()*2, time = 3) - sleep(delay) + SLEEP_CHECK_DEATH(delay) + revving_charge = FALSE var/movespeed = 0.7 walk_towards(src, T, movespeed) - sleep(get_dist(src, T) * movespeed) + SLEEP_CHECK_DEATH(get_dist(src, T) * movespeed) walk(src, 0) // cancel the movement try_bloodattack() - charging = 0 - -/mob/living/simple_animal/hostile/megafauna/bubblegum/Bump(atom/A) - if(charging) - if(isturf(A) || isobj(A) && A.density) - A.ex_act(EXPLODE_HEAVY) - DestroySurroundings() - if(isliving(A)) - var/mob/living/L = A - L.visible_message("[src] slams into [L]!", "[src] tramples you into the ground!") - src.forceMove(get_turf(L)) - L.apply_damage(istype(src, /mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination) ? 15 : 30, BRUTE) - playsound(get_turf(L), 'sound/effects/meteorimpact.ogg', 100, 1) - shake_camera(L, 4, 3) - shake_camera(src, 2, 3) - ..() + charging = FALSE /mob/living/simple_animal/hostile/megafauna/bubblegum/proc/get_mobs_on_blood() var/list/targets = ListTargets() @@ -269,14 +244,14 @@ Difficulty: Hard new /obj/effect/temp_visual/bubblegum_hands/rightsmack(T) else new /obj/effect/temp_visual/bubblegum_hands/leftsmack(T) - sleep(4) + SLEEP_CHECK_DEATH(4) for(var/mob/living/L in T) if(!faction_check_mob(L)) to_chat(L, "[src] rends you!") playsound(T, attack_sound, 100, 1, -1) var/limb_to_hit = L.get_bodypart(pick(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG)) L.apply_damage(10, BRUTE, limb_to_hit, L.run_armor_check(limb_to_hit, "melee", null, null, armour_penetration)) - sleep(3) + SLEEP_CHECK_DEATH(3) /mob/living/simple_animal/hostile/megafauna/bubblegum/proc/bloodgrab(turf/T, handedness) if(handedness) @@ -285,7 +260,7 @@ Difficulty: Hard else new /obj/effect/temp_visual/bubblegum_hands/leftpaw(T) new /obj/effect/temp_visual/bubblegum_hands/leftthumb(T) - sleep(6) + SLEEP_CHECK_DEATH(6) for(var/mob/living/L in T) if(!faction_check_mob(L)) if(L.stat != CONSCIOUS) @@ -295,7 +270,226 @@ Difficulty: Hard L.forceMove(targetturf) playsound(targetturf, 'sound/magic/exit_blood.ogg', 100, 1, -1) addtimer(CALLBACK(src, .proc/devour, L), 2) - sleep(1) + SLEEP_CHECK_DEATH(1) + +/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/blood_warp() + if(Adjacent(target)) + return FALSE + var/list/can_jaunt = get_pools(get_turf(src), 1) + if(!can_jaunt.len) + return FALSE + + var/list/pools = get_pools(get_turf(target), 5) + var/list/pools_to_remove = get_pools(get_turf(target), 4) + pools -= pools_to_remove + if(!pools.len) + return FALSE + + var/obj/effect/temp_visual/decoy/DA = new /obj/effect/temp_visual/decoy(loc,src) + DA.color = "#FF0000" + var/oldtransform = DA.transform + DA.transform = matrix()*2 + animate(DA, alpha = 255, color = initial(DA.color), transform = oldtransform, time = 3) + SLEEP_CHECK_DEATH(3) + qdel(DA) + + var/obj/effect/decal/cleanable/blood/found_bloodpool + pools = get_pools(get_turf(target), 5) + pools_to_remove = get_pools(get_turf(target), 4) + pools -= pools_to_remove + if(pools.len) + shuffle_inplace(pools) + found_bloodpool = pick(pools) + if(found_bloodpool) + visible_message("[src] sinks into the blood...") + playsound(get_turf(src), 'sound/magic/enter_blood.ogg', 100, 1, -1) + forceMove(get_turf(found_bloodpool)) + playsound(get_turf(src), 'sound/magic/exit_blood.ogg', 100, 1, -1) + visible_message("And springs back out!") + blood_enrage() + return TRUE + return FALSE + +/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/be_aggressive() + if(BUBBLEGUM_IS_ENRAGED) + return TRUE + if(isliving(target)) + var/mob/living/livingtarget = target + return (livingtarget.stat != CONSCIOUS || !(livingtarget.mobility_flags & MOBILITY_STAND)) + return FALSE + +/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/get_retreat_distance() + return (be_aggressive() ? null : initial(retreat_distance)) + +/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/get_minimum_distance() + return (be_aggressive() ? 1 : initial(minimum_distance)) + +/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/update_approach() + retreat_distance = get_retreat_distance() + minimum_distance = get_minimum_distance() + +/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/blood_enrage() + if(!BUBBLEGUM_CAN_ENRAGE) + return FALSE + enrage_till = world.time + enrage_time + update_approach() + change_move_delay(3.75) + var/newcolor = rgb(149, 10, 10) + add_atom_colour(newcolor, TEMPORARY_COLOUR_PRIORITY) + var/datum/callback/cb = CALLBACK(src, .proc/blood_enrage_end) + addtimer(cb, enrage_time) + +/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/blood_enrage_end(var/newcolor = rgb(149, 10, 10)) + update_approach() + change_move_delay() + remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, newcolor) + +/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/change_move_delay(var/newmove = initial(move_to_delay)) + move_to_delay = newmove + set_varspeed(move_to_delay) + handle_automated_action() // need to recheck movement otherwise move_to_delay won't update until the next checking aka will be wrong speed for a bit + +/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/get_pools(turf/T, range) + . = list() + for(var/obj/effect/decal/cleanable/nearby in view(T, range)) + if(nearby.can_bloodcrawl_in()) + . += nearby + +/obj/effect/decal/cleanable/blood/bubblegum + bloodiness = 0 + +/obj/effect/decal/cleanable/blood/bubblegum/can_bloodcrawl_in() + return TRUE + +/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/hallucination_charge_around(var/times = 4, var/delay = 6, var/chargepast = 0, var/useoriginal = 1, var/radius) + var/startingangle = rand(1, 360) + if(!target) + return + var/turf/chargeat = get_turf(target) + var/srcplaced = FALSE + if(!radius) + radius = times + for(var/i = 1 to times) + var/ang = (startingangle + 360/times * i) + if(!chargeat) + return + var/turf/place = locate(chargeat.x + cos(ang) * radius, chargeat.y + sin(ang) * radius, chargeat.z) + if(!place) + continue + if(!nest || nest && nest.parent && get_dist(nest.parent, place) <= nest_range) + if(!srcplaced && useoriginal) + forceMove(place) + srcplaced = TRUE + continue + var/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/B = new /mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination(src.loc) + B.forceMove(place) + INVOKE_ASYNC(B, .proc/charge, chargeat, delay, chargepast) + if(useoriginal) + charge(chargeat, delay, chargepast) + +/obj/item/gps/internal/bubblegum + icon_state = null + gpstag = "Bloody Signal" + desc = "You're not quite sure how a signal can be bloody." + invisibility = 100 + +/mob/living/simple_animal/hostile/megafauna/bubblegum/devour(mob/living/L) + var/turf/death_turf = get_turf(L) + . = ..() + if(. && death_turf) + for(var/i in 1 to 3) + new /mob/living/simple_animal/hostile/asteroid/hivelordbrood/slaughter(death_turf) + +/mob/living/simple_animal/hostile/megafauna/bubblegum/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE) + . = ..() + if(. > 0 && prob(25)) + var/obj/effect/decal/cleanable/blood/gibs/bubblegum/B = new /obj/effect/decal/cleanable/blood/gibs/bubblegum(loc) + if(prob(40)) + step(B, pick(GLOB.cardinals)) + else + B.setDir(pick(GLOB.cardinals)) + +/obj/effect/decal/cleanable/blood/gibs/bubblegum + name = "thick blood" + desc = "Thick, splattered blood." + random_icon_states = list("gib3", "gib5", "gib6") + bloodiness = 20 + +/obj/effect/decal/cleanable/blood/gibs/bubblegum/can_bloodcrawl_in() + return TRUE + +/mob/living/simple_animal/hostile/megafauna/bubblegum/grant_achievement(medaltype,scoretype) + . = ..() + if(.) + SSshuttle.shuttle_purchase_requirements_met |= "bubblegum" + +/mob/living/simple_animal/hostile/megafauna/bubblegum/do_attack_animation(atom/A, visual_effect_icon) + if(!charging) + ..() + +/mob/living/simple_animal/hostile/megafauna/bubblegum/AttackingTarget() + if(!charging) + . = ..() + if(.) + recovery_time = world.time + 20 // can only attack melee once every 2 seconds but rapid_melee gives higher priority + +/mob/living/simple_animal/hostile/megafauna/bubblegum/bullet_act(obj/item/projectile/P) + if(BUBBLEGUM_IS_ENRAGED) + visible_message("[src] deflects the projectile; [p_they()] can't be hit with ranged weapons while enraged!", "You deflect the projectile!") + playsound(src, pick('sound/weapons/bulletflyby.ogg', 'sound/weapons/bulletflyby2.ogg', 'sound/weapons/bulletflyby3.ogg'), 300, 1) + return BULLET_ACT_BLOCK + return ..() + +/mob/living/simple_animal/hostile/megafauna/bubblegum/ex_act(severity, target) + if(severity >= EXPLODE_LIGHT) + return + severity = EXPLODE_LIGHT // puny mortals + return ..() + +/mob/living/simple_animal/hostile/megafauna/bubblegum/CanPass(atom/movable/mover, turf/target) + if(istype(mover, /mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination)) + return TRUE + return ..() + +/mob/living/simple_animal/hostile/megafauna/bubblegum/Goto(target, delay, minimum_distance) + if(!charging) + ..() + +/mob/living/simple_animal/hostile/megafauna/bubblegum/MoveToTarget(list/possible_targets) + if(!charging) + ..() + +/mob/living/simple_animal/hostile/megafauna/bubblegum/Move() + update_approach() + if(revving_charge) + return FALSE + if(charging) + new /obj/effect/temp_visual/decoy/fading(loc,src) + DestroySurroundings() + ..() + +/mob/living/simple_animal/hostile/megafauna/bubblegum/Moved(atom/OldLoc, Dir, Forced = FALSE) + if(Dir) + new /obj/effect/decal/cleanable/blood/bubblegum(src.loc) + if(charging) + DestroySurroundings() + playsound(src, 'sound/effects/meteorimpact.ogg', 200, 1, 2, 1) + return ..() + +/mob/living/simple_animal/hostile/megafauna/bubblegum/Bump(atom/A) + if(charging) + if(isturf(A) || isobj(A) && A.density) + A.ex_act(EXPLODE_HEAVY) + DestroySurroundings() + if(isliving(A)) + var/mob/living/L = A + L.visible_message("[src] slams into [L]!", "[src] tramples you into the ground!") + src.forceMove(get_turf(L)) + L.apply_damage(istype(src, /mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination) ? 15 : 30, BRUTE) + playsound(get_turf(L), 'sound/effects/meteorimpact.ogg', 100, 1) + shake_camera(L, 4, 3) + shake_camera(src, 2, 3) + ..() /obj/effect/temp_visual/dragon_swoop/bubblegum duration = 10 @@ -324,103 +518,6 @@ Difficulty: Hard /obj/effect/temp_visual/bubblegum_hands/leftsmack icon_state = "leftsmack" -/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/blood_warp() - if(Adjacent(target) || (enrage_till + 30 > world.time)) - return FALSE - var/list/can_jaunt = get_pools(get_turf(src), 1) - if(!can_jaunt.len) - return FALSE - - var/list/pools = get_pools(get_turf(target), 2) - var/list/pools_to_remove = get_pools(get_turf(target), 1) - pools -= pools_to_remove - if(!pools.len) - return FALSE - - var/obj/effect/temp_visual/decoy/DA = new /obj/effect/temp_visual/decoy(loc,src) - DA.color = "#FF0000" - var/oldtransform = DA.transform - DA.transform = matrix()*2 - animate(DA, alpha = 255, color = initial(DA.color), transform = oldtransform, time = 3) - sleep(3) - qdel(DA) - - var/obj/effect/decal/cleanable/blood/found_bloodpool - pools = get_pools(get_turf(target), 2) - pools_to_remove = get_pools(get_turf(target), 1) - pools -= pools_to_remove - if(pools.len) - shuffle_inplace(pools) - found_bloodpool = pick(pools) - if(found_bloodpool) - visible_message("[src] sinks into the blood...") - playsound(get_turf(src), 'sound/magic/enter_blood.ogg', 100, 1, -1) - forceMove(get_turf(found_bloodpool)) - playsound(get_turf(src), 'sound/magic/exit_blood.ogg', 100, 1, -1) - visible_message("And springs back out!") - blood_enrage() - return TRUE - return FALSE - -/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/blood_enrage(var/boost_time = 30) - enrage_till = world.time + boost_time - retreat_distance = null - minimum_distance = 1 - change_move_delay(3.75) - var/newcolor = rgb(149, 10, 10) - add_atom_colour(newcolor, TEMPORARY_COLOUR_PRIORITY) - var/datum/callback/cb = CALLBACK(src, .proc/blood_enrage_end) - addtimer(cb, boost_time) - -/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/blood_enrage_end(var/newcolor = rgb(149, 10, 10)) - retreat_distance = initial(retreat_distance) - minimum_distance = initial(minimum_distance) - change_move_delay() - remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, newcolor) - -/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/is_enraged() - return (enrage_till > world.time) - -/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/change_move_delay(var/newmove = initial(move_to_delay)) - move_to_delay = newmove - handle_automated_action() // need to recheck movement otherwise move_to_delay won't update until the next checking aka will be wrong speed for a bit - -/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/get_pools(turf/T, range) - . = list() - for(var/obj/effect/decal/cleanable/nearby in view(T, range)) - if(nearby.can_bloodcrawl_in()) - . += nearby - -/obj/effect/decal/cleanable/blood/bubblegum - bloodiness = 0 - -/obj/effect/decal/cleanable/blood/bubblegum/can_bloodcrawl_in() - return TRUE - -/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/hallucination_charge_around(var/times = 4, var/delay = 6, var/chargepast = 0, var/useoriginal = 1) - var/startingangle = rand(1, 360) - if(!target) - return - var/turf/chargeat = get_turf(target) - var/srcplaced = 0 - for(var/i = 1 to times) - var/ang = (startingangle + 360/times * i) - if(!chargeat) - return - var/turf/place = locate(chargeat.x + cos(ang) * times, chargeat.y + sin(ang) * times, chargeat.z) - if(!place) - continue - if(!nest || nest && nest.parent && get_dist(nest.parent, place) <= nest_range) - if(!srcplaced && useoriginal) - forceMove(place) - srcplaced = 1 - continue - var/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/B = new /mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination(src.loc) - B.forceMove(place) - INVOKE_ASYNC(B, .proc/charge, chargeat, delay, chargepast) - if(useoriginal) - charge(chargeat, delay, chargepast) - /mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination name = "bubblegum's hallucination" desc = "Is that really just a hallucination?" @@ -433,7 +530,7 @@ Difficulty: Hard score_type = null deathmessage = "Explodes into a pool of blood!" deathsound = 'sound/effects/splat.ogg' - true_spawn = 0 + true_spawn = FALSE /mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/Initialize() ..() @@ -449,7 +546,7 @@ Difficulty: Hard /mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/CanPass(atom/movable/mover, turf/target) if(istype(mover, /mob/living/simple_animal/hostile/megafauna/bubblegum)) // hallucinations should not be stopping bubblegum or eachother - return 1 + return TRUE return ..() /mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/Life() @@ -469,7 +566,7 @@ Difficulty: Hard /mob/living/simple_animal/hostile/megafauna/bubblegum/proc/slaughterlings() visible_message("[src] summons a shoal of slaughterlings!") - var/max_amount = 6 + var/max_amount = CLAMP(anger_modifier / 4, 3, 5) for(var/H in get_pools(get_turf(src), 1)) if(!max_amount) break @@ -492,5 +589,5 @@ Difficulty: Hard /mob/living/simple_animal/hostile/asteroid/hivelordbrood/slaughter/CanPass(atom/movable/mover, turf/target) if(istype(mover, /mob/living/simple_animal/hostile/megafauna/bubblegum)) - return 1 - return 0 + return TRUE + return FALSE diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm index 7e37e3b5e8b..059eaca94c4 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm @@ -37,11 +37,11 @@ Difficulty: Very Hard armour_penetration = 40 melee_damage_lower = 40 melee_damage_upper = 40 - speed = 1 + speed = 10 move_to_delay = 10 - ranged = 1 + ranged = TRUE pixel_x = -32 - del_on_death = 1 + del_on_death = TRUE internal_type = /obj/item/gps/internal/colossus medal_type = BOSS_MEDAL_COLOSSUS score_type = COLOSSUS_SCORE @@ -49,15 +49,56 @@ Difficulty: Very Hard loot = list(/obj/structure/closet/crate/necropolis/colossus) deathmessage = "disintegrates, leaving a glowing core in its wake." deathsound = 'sound/magic/demon_dies.ogg' + attack_action_types = list(/datum/action/innate/megafauna_attack/spiral_attack, + /datum/action/innate/megafauna_attack/aoe_attack, + /datum/action/innate/megafauna_attack/shotgun, + /datum/action/innate/megafauna_attack/alternating_cardinals) + small_sprite_type = /datum/action/small_sprite/megafauna/colossus -/mob/living/simple_animal/hostile/megafauna/colossus/devour(mob/living/L) - visible_message("[src] disintegrates [L]!") - L.dust() +/datum/action/innate/megafauna_attack/spiral_attack + name = "Spiral Shots" + icon_icon = 'icons/mob/actions/actions_items.dmi' + button_icon_state = "sniper_zoom" + chosen_message = "You are now firing in a spiral." + chosen_attack_num = 1 + +/datum/action/innate/megafauna_attack/aoe_attack + name = "All Directions" + icon_icon = 'icons/effects/effects.dmi' + button_icon_state = "at_shield2" + chosen_message = "You are now firing in all directions." + chosen_attack_num = 2 + +/datum/action/innate/megafauna_attack/shotgun + name = "Shotgun Fire" + icon_icon = 'icons/obj/guns/projectile.dmi' + button_icon_state = "shotgun" + chosen_message = "You are now firing shotgun shots where you aim." + chosen_attack_num = 3 + +/datum/action/innate/megafauna_attack/alternating_cardinals + name = "Alternating Shots" + icon_icon = 'icons/obj/guns/projectile.dmi' + button_icon_state = "pistol" + chosen_message = "You are now firing in alternating cardinal directions." + chosen_attack_num = 4 /mob/living/simple_animal/hostile/megafauna/colossus/OpenFire() anger_modifier = CLAMP(((maxHealth - health)/50),0,20) ranged_cooldown = world.time + 120 + if(client) + switch(chosen_attack) + if(1) + select_spiral_attack() + if(2) + random_shots() + if(3) + blast() + if(4) + alternating_dir_shots() + return + if(enrage(target)) if(move_to_delay == initial(move_to_delay)) visible_message("\"You can't dodge.\"") @@ -70,49 +111,14 @@ Difficulty: Very Hard move_to_delay = initial(move_to_delay) if(prob(20+anger_modifier)) //Major attack - telegraph() - - if(health < maxHealth/3) - INVOKE_ASYNC(src, .proc/double_spiral) - else - visible_message("\"Judgement.\"") - INVOKE_ASYNC(src, .proc/spiral_shoot, pick(TRUE, FALSE)) - + select_spiral_attack() else if(prob(20)) - ranged_cooldown = world.time + 30 random_shots() else if(prob(70)) - ranged_cooldown = world.time + 20 blast() else - ranged_cooldown = world.time + 40 - INVOKE_ASYNC(src, .proc/alternating_dir_shots) - -/obj/effect/temp_visual/at_shield - name = "anti-toolbox field" - desc = "A shimmering forcefield protecting the colossus." - icon = 'icons/effects/effects.dmi' - icon_state = "at_shield2" - layer = FLY_LAYER - light_range = 2 - duration = 8 - var/target - -/obj/effect/temp_visual/at_shield/Initialize(mapload, new_target) - . = ..() - target = new_target - INVOKE_ASYNC(src, /atom/movable/proc/orbit, target, 0, FALSE, 0, 0, FALSE, TRUE) - -/mob/living/simple_animal/hostile/megafauna/colossus/bullet_act(obj/item/projectile/P) - if(!stat) - var/obj/effect/temp_visual/at_shield/AT = new /obj/effect/temp_visual/at_shield(loc, src) - var/random_x = rand(-32, 32) - AT.pixel_x += random_x - - var/random_y = rand(0, 72) - AT.pixel_y += random_y - return ..() + alternating_dir_shots() /mob/living/simple_animal/hostile/megafauna/colossus/proc/enrage(mob/living/L) if(ishuman(L)) @@ -122,6 +128,7 @@ Difficulty: Very Hard . = TRUE /mob/living/simple_animal/hostile/megafauna/colossus/proc/alternating_dir_shots() + ranged_cooldown = world.time + 40 dir_shots(GLOB.diagonals) SLEEP_CHECK_DEATH(10) dir_shots(GLOB.cardinals) @@ -130,14 +137,21 @@ Difficulty: Very Hard SLEEP_CHECK_DEATH(10) dir_shots(GLOB.cardinals) +/mob/living/simple_animal/hostile/megafauna/colossus/proc/select_spiral_attack() + telegraph() + if(health < maxHealth/3) + return double_spiral() + visible_message("\"Judgement.\"") + return spiral_shoot() + /mob/living/simple_animal/hostile/megafauna/colossus/proc/double_spiral() visible_message("\"Die.\"") SLEEP_CHECK_DEATH(10) - INVOKE_ASYNC(src, .proc/spiral_shoot) + INVOKE_ASYNC(src, .proc/spiral_shoot, FALSE) INVOKE_ASYNC(src, .proc/spiral_shoot, TRUE) -/mob/living/simple_animal/hostile/megafauna/colossus/proc/spiral_shoot(negative = FALSE, counter_start = 8) +/mob/living/simple_animal/hostile/megafauna/colossus/proc/spiral_shoot(negative = pick(TRUE, FALSE), counter_start = 8) var/turf/start_turf = get_step(src, pick(GLOB.alldirs)) var/counter = counter_start for(var/i in 1 to 80) @@ -165,6 +179,7 @@ Difficulty: Very Hard P.fire(set_angle) /mob/living/simple_animal/hostile/megafauna/colossus/proc/random_shots() + ranged_cooldown = world.time + 30 var/turf/U = get_turf(src) playsound(U, 'sound/magic/clockwork/invoke_general.ogg', 300, 1, 5) for(var/T in RANGE_TURFS(12, U) - U) @@ -172,6 +187,7 @@ Difficulty: Very Hard shoot_projectile(T) /mob/living/simple_animal/hostile/megafauna/colossus/proc/blast(set_angle) + ranged_cooldown = world.time + 20 var/turf/target_turf = get_turf(target) playsound(src, 'sound/magic/clockwork/invoke_general.ogg', 200, 1, 2) newtonian_move(get_dir(target_turf, src)) @@ -198,6 +214,34 @@ Difficulty: Very Hard playsound(src, 'sound/magic/clockwork/narsie_attack.ogg', 200, 1) +/mob/living/simple_animal/hostile/megafauna/colossus/devour(mob/living/L) + visible_message("[src] disintegrates [L]!") + L.dust() + +/obj/effect/temp_visual/at_shield + name = "anti-toolbox field" + desc = "A shimmering forcefield protecting the colossus." + icon = 'icons/effects/effects.dmi' + icon_state = "at_shield2" + layer = FLY_LAYER + light_range = 2 + duration = 8 + var/target + +/obj/effect/temp_visual/at_shield/Initialize(mapload, new_target) + . = ..() + target = new_target + INVOKE_ASYNC(src, /atom/movable/proc/orbit, target, 0, FALSE, 0, 0, FALSE, TRUE) + +/mob/living/simple_animal/hostile/megafauna/colossus/bullet_act(obj/item/projectile/P) + if(!stat) + var/obj/effect/temp_visual/at_shield/AT = new /obj/effect/temp_visual/at_shield(loc, src) + var/random_x = rand(-32, 32) + AT.pixel_x += random_x + + var/random_y = rand(0, 72) + AT.pixel_y += random_y + return ..() /obj/item/projectile/colossus name ="death bolt" @@ -626,7 +670,7 @@ Difficulty: Very Hard obj_damage = 0 environment_smash = ENVIRONMENT_SMASH_NONE AIStatus = AI_OFF - stop_automated_movement = 1 + stop_automated_movement = TRUE var/heal_power = 5 /mob/living/simple_animal/hostile/lightgeist/Initialize() @@ -679,13 +723,13 @@ Difficulty: Very Hard /obj/machinery/anomalous_crystal/possessor/ActivationReaction(mob/user, method) if(..()) if(ishuman(user)) - var/mobcheck = 0 + var/mobcheck = FALSE for(var/mob/living/simple_animal/A in range(1, src)) if(A.melee_damage_upper > 5 || A.mob_size >= MOB_SIZE_LARGE || A.ckey || A.stat) break var/obj/structure/closet/stasis/S = new /obj/structure/closet/stasis(A) user.forceMove(S) - mobcheck = 1 + mobcheck = TRUE break if(!mobcheck) new /mob/living/simple_animal/cockroach(get_step(src,dir)) //Just in case there aren't any animals on the station, this will leave you with a terrible option to possess if you feel like it @@ -750,7 +794,7 @@ Difficulty: Very Hard invocation_type = "none" max_targets = 1 range = -1 - include_user = 1 + include_user = TRUE selection_type = "view" action_icon = 'icons/mob/actions/actions_spells.dmi' action_icon_state = "exit_possession" diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm index 7ff3014e3dd..24cc474304a 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm @@ -35,7 +35,6 @@ Difficulty: Medium desc = "Guardians of the necropolis." health = 2500 maxHealth = 2500 - spacewalk = TRUE attacktext = "chomps" attack_sound = 'sound/magic/demon_attack1.ogg' icon = 'icons/mob/lavaland/64x64megafauna.dmi' @@ -47,9 +46,9 @@ Difficulty: Medium armour_penetration = 40 melee_damage_lower = 40 melee_damage_upper = 40 - speed = 1 + speed = 5 move_to_delay = 5 - ranged = 1 + ranged = TRUE pixel_x = -16 crusher_loot = list(/obj/structure/closet/crate/necropolis/dragon/crusher) loot = list(/obj/structure/closet/crate/necropolis/dragon) @@ -62,71 +61,82 @@ Difficulty: Medium score_type = DRAKE_SCORE deathmessage = "collapses into a pile of bones, its flesh sloughing away." deathsound = 'sound/magic/demon_dies.ogg' - var/datum/action/small_sprite/smallsprite = new/datum/action/small_sprite/drake() - do_footstep = TRUE + attack_action_types = list(/datum/action/innate/megafauna_attack/fire_cone, + /datum/action/innate/megafauna_attack/fire_cone_meteors, + /datum/action/innate/megafauna_attack/mass_fire, + /datum/action/innate/megafauna_attack/lava_swoop) + small_sprite_type = /datum/action/small_sprite/megafauna/drake -/mob/living/simple_animal/hostile/megafauna/dragon/Initialize() - smallsprite.Grant(src) - . = ..() +/datum/action/innate/megafauna_attack/fire_cone + name = "Fire Cone" + icon_icon = 'icons/obj/wizard.dmi' + button_icon_state = "fireball" + chosen_message = "You are now shooting fire at your target." + chosen_attack_num = 1 -/mob/living/simple_animal/hostile/megafauna/dragon/death() - QDEL_NULL(internal) // so drake corpses don't have a gps signal - . = ..() +/datum/action/innate/megafauna_attack/fire_cone_meteors + name = "Fire Cone With Meteors" + icon_icon = 'icons/mob/actions/actions_items.dmi' + button_icon_state = "sniper_zoom" + chosen_message = "You are now shooting fire at your target and raining fire around you." + chosen_attack_num = 2 -/mob/living/simple_animal/hostile/megafauna/dragon/ex_act(severity, target) - if(severity == 3) - return - ..() +/datum/action/innate/megafauna_attack/mass_fire + name = "Mass Fire Attack" + icon_icon = 'icons/effects/fire.dmi' + button_icon_state = "1" + chosen_message = "You are now shooting mass fire at your target." + chosen_attack_num = 3 -/mob/living/simple_animal/hostile/megafauna/dragon/adjustHealth(amount, updating_health = TRUE, forced = FALSE) - if(!forced && (swooping & SWOOP_INVULNERABLE)) - return FALSE - return ..() - -/mob/living/simple_animal/hostile/megafauna/dragon/visible_message() - if(swooping & SWOOP_INVULNERABLE) //to suppress attack messages without overriding every single proc that could send a message saying we got hit - return - return ..() - -/mob/living/simple_animal/hostile/megafauna/dragon/AttackingTarget() - if(!swooping) - return ..() - -/mob/living/simple_animal/hostile/megafauna/dragon/DestroySurroundings() - if(!swooping) - ..() - -/mob/living/simple_animal/hostile/megafauna/dragon/Move() - if(!swooping) - ..() - -/mob/living/simple_animal/hostile/megafauna/dragon/Goto(target, delay, minimum_distance) - if(!swooping) - ..() +/datum/action/innate/megafauna_attack/lava_swoop + name = "Lava Swoop" + icon_icon = 'icons/effects/effects.dmi' + button_icon_state = "lavastaff_warn" + chosen_message = "You are now swooping and raining lava at your target." + chosen_attack_num = 4 /mob/living/simple_animal/hostile/megafauna/dragon/OpenFire() if(swooping) return + anger_modifier = CLAMP(((maxHealth - health)/50),0,20) ranged_cooldown = world.time + ranged_cooldown_time - if(prob(15 + anger_modifier) && !client) - if(health < maxHealth*0.5) - swoop_attack(lava_arena = TRUE) - else - lava_swoop() + if(client) + switch(chosen_attack) + if(1) + fire_cone(meteors = FALSE) + if(2) + fire_cone() + if(3) + mass_fire() + if(4) + lava_swoop() + return - else if(prob(10+anger_modifier) && !client) - if(health < maxHealth*0.5) - mass_fire() - else - fire_cone() + if(prob(15 + anger_modifier)) + lava_swoop() + + else if(prob(10+anger_modifier)) + shoot_fire_attack() else - if(prob(50) && !client) - INVOKE_ASYNC(src, .proc/lava_pools, 10, 2) fire_cone() +/mob/living/simple_animal/hostile/megafauna/dragon/proc/shoot_fire_attack() + if(health < maxHealth*0.5) + mass_fire() + else + fire_cone() + +/mob/living/simple_animal/hostile/megafauna/dragon/proc/fire_rain() + if(!target) + return + target.visible_message("Fire rains from the sky!") + for(var/turf/turf in range(9,get_turf(target))) + if(prob(11)) + new /obj/effect/temp_visual/target(turf) + /mob/living/simple_animal/hostile/megafauna/dragon/proc/lava_pools(var/amount, var/delay = 0.8) if(!target) return @@ -140,10 +150,11 @@ Difficulty: Medium SLEEP_CHECK_DEATH(delay) /mob/living/simple_animal/hostile/megafauna/dragon/proc/lava_swoop(var/amount = 30) + if(health < maxHealth * 0.5) + return swoop_attack(lava_arena = TRUE, swoop_cooldown = 60) INVOKE_ASYNC(src, .proc/lava_pools, amount) swoop_attack(FALSE, target, 1000) // longer cooldown until it gets reset below - if(QDELETED(src) || stat == DEAD) - return + SLEEP_CHECK_DEATH(0) fire_cone() if(health < maxHealth*0.5) SLEEP_CHECK_DEATH(10) @@ -153,8 +164,7 @@ Difficulty: Medium SetRecoveryTime(40) /mob/living/simple_animal/hostile/megafauna/dragon/proc/mass_fire(var/spiral_count = 12, var/range = 15, var/times = 3) - if(QDELETED(src) || stat == DEAD) - return + SLEEP_CHECK_DEATH(0) for(var/i = 1 to times) SetRecoveryTime(50) playsound(get_turf(src),'sound/magic/fireball.ogg', 200, 1) @@ -211,26 +221,24 @@ Difficulty: Medium return 1 // attack finished completely /mob/living/simple_animal/hostile/megafauna/dragon/proc/arena_escape_enrage() // you ran somehow / teleported away from my arena attack now i'm mad fucker - if(QDELETED(src) || stat == DEAD) - return //angry but helpless + SLEEP_CHECK_DEATH(0) SetRecoveryTime(80) visible_message("[src] starts to glow vibrantly as its wounds close up!") adjustBruteLoss(-250) // yeah you're gonna pay for that, don't run nerd add_atom_colour(rgb(255, 255, 0), TEMPORARY_COLOUR_PRIORITY) move_to_delay = move_to_delay / 2 light_range = 10 - sleep(10) // run. - if(!QDELETED(src)) - if(stat != DEAD) - mass_fire(20, 15, 3) - move_to_delay = initial(move_to_delay) - remove_atom_colour(TEMPORARY_COLOUR_PRIORITY) - light_range = initial(light_range) + SLEEP_CHECK_DEATH(10) // run. + mass_fire(20, 15, 3) + move_to_delay = initial(move_to_delay) + remove_atom_colour(TEMPORARY_COLOUR_PRIORITY) + light_range = initial(light_range) -/mob/living/simple_animal/hostile/megafauna/dragon/proc/fire_cone(var/atom/at = target) +/mob/living/simple_animal/hostile/megafauna/dragon/proc/fire_cone(var/atom/at = target, var/meteors = TRUE) playsound(get_turf(src),'sound/magic/fireball.ogg', 200, 1) - if(QDELETED(src) || stat == DEAD) // we dead no fire - return + SLEEP_CHECK_DEATH(0) + if(prob(50) && meteors) + INVOKE_ASYNC(src, .proc/fire_rain) var/range = 15 var/list/turfs = list() turfs = line_target(-40, range, at) @@ -253,6 +261,7 @@ Difficulty: Medium return (getline(src, T) - get_turf(src)) /mob/living/simple_animal/hostile/megafauna/dragon/proc/fire_line(var/list/turfs) + SLEEP_CHECK_DEATH(0) dragon_fire_line(src, turfs) //fire line keeps going even if dragon is deleted @@ -317,16 +326,15 @@ Difficulty: Medium animate(src, alpha = 100, transform = matrix()*0.7, time = 7) swooping |= SWOOP_INVULNERABLE mouse_opacity = MOUSE_OPACITY_TRANSPARENT - sleep(7) - //badmins please don't kill it while it's invulnerable + SLEEP_CHECK_DEATH(7) while(target && loc != get_turf(target)) forceMove(get_step(src, get_dir(src, target))) - sleep(0.5) + SLEEP_CHECK_DEATH(0.5) // Ash drake flies onto its target and rains fire down upon them - var/descentTime = 10; - var/lava_success = 1 + var/descentTime = 10 + var/lava_success = TRUE if(lava_arena) lava_success = lava_arena() @@ -341,7 +349,7 @@ Difficulty: Medium new /obj/effect/temp_visual/dragon_flight/end(loc, negative) new /obj/effect/temp_visual/dragon_swoop(loc) animate(src, alpha = 255, transform = oldtransform, descentTime) - sleep(descentTime) + SLEEP_CHECK_DEATH(descentTime) swooping &= ~SWOOP_INVULNERABLE mouse_opacity = initial(mouse_opacity) icon_state = "dragon" @@ -366,23 +374,46 @@ Difficulty: Medium shake_camera(M, 15, 1) density = TRUE - sleep(1) - if(QDELETED(src)) - return + SLEEP_CHECK_DEATH(1) swooping &= ~SWOOP_DAMAGEABLE SetRecoveryTime(swoop_cooldown) if(!lava_success) arena_escape_enrage() -/mob/living/simple_animal/hostile/megafauna/dragon/AltClickOn(atom/movable/A) - if(!istype(A)) +/mob/living/simple_animal/hostile/megafauna/dragon/death() + QDEL_NULL(internal) // so drake corpses don't have a gps signal + . = ..() + +/mob/living/simple_animal/hostile/megafauna/dragon/ex_act(severity, target) + if(severity == EXPLODE_LIGHT) return - if(player_cooldown >= world.time) - to_chat(src, "You need to wait [(player_cooldown - world.time) / 10] seconds before swooping again!") + ..() + +/mob/living/simple_animal/hostile/megafauna/dragon/adjustHealth(amount, updating_health = TRUE, forced = FALSE) + if(!forced && (swooping & SWOOP_INVULNERABLE)) + return FALSE + return ..() + +/mob/living/simple_animal/hostile/megafauna/dragon/visible_message() + if(swooping & SWOOP_INVULNERABLE) //to suppress attack messages without overriding every single proc that could send a message saying we got hit return - swoop_attack(FALSE, A) - lava_pools(10, 2) // less pools but longer delay before spawns - player_cooldown = world.time + 200 // needs seperate cooldown or cant use fire attacks + return ..() + +/mob/living/simple_animal/hostile/megafauna/dragon/AttackingTarget() + if(!swooping) + return ..() + +/mob/living/simple_animal/hostile/megafauna/dragon/DestroySurroundings() + if(!swooping) + ..() + +/mob/living/simple_animal/hostile/megafauna/dragon/Move() + if(!swooping) + ..() + +/mob/living/simple_animal/hostile/megafauna/dragon/Goto(target, delay, minimum_distance) + if(!swooping) + ..() /obj/item/gps/internal/dragon icon_state = null @@ -495,6 +526,55 @@ Difficulty: Medium else animate(src, pixel_x = -16, pixel_z = 0, time = 5) +obj/effect/temp_visual/fireball + icon = 'icons/obj/wizard.dmi' + icon_state = "fireball" + name = "fireball" + desc = "Get out of the way!" + layer = FLY_LAYER + randomdir = FALSE + duration = 9 + pixel_z = 270 + +/obj/effect/temp_visual/fireball/Initialize() + . = ..() + animate(src, pixel_z = 0, time = duration) + +/obj/effect/temp_visual/target + icon = 'icons/mob/actions/actions_items.dmi' + icon_state = "sniper_zoom" + layer = BELOW_MOB_LAYER + light_range = 2 + duration = 9 + +/obj/effect/temp_visual/target/ex_act() + return + +/obj/effect/temp_visual/target/Initialize(mapload, list/flame_hit) + . = ..() + INVOKE_ASYNC(src, .proc/fall, flame_hit) + +/obj/effect/temp_visual/target/proc/fall(list/flame_hit) + var/turf/T = get_turf(src) + playsound(T,'sound/magic/fleshtostone.ogg', 80, 1) + new /obj/effect/temp_visual/fireball(T) + sleep(duration) + if(ismineralturf(T)) + var/turf/closed/mineral/M = T + M.gets_drilled() + playsound(T, "explosion", 80, 1) + new /obj/effect/hotspot(T) + T.hotspot_expose(700, 50, 1) + for(var/mob/living/L in T.contents) + if(istype(L, /mob/living/simple_animal/hostile/megafauna/dragon)) + continue + if(islist(flame_hit) && !flame_hit[L]) + L.adjustFireLoss(40) + to_chat(L, "You're hit by the drake's fire breath!") + flame_hit[L] = TRUE + else + L.adjustFireLoss(10) //if we've already hit them, do way less damage + /mob/living/simple_animal/hostile/megafauna/dragon/lesser name = "lesser ash drake" maxHealth = 200 @@ -508,6 +588,17 @@ Difficulty: Medium loot = list() crusher_loot = list() butcher_results = list(/obj/item/stack/ore/diamond = 5, /obj/item/stack/sheet/sinew = 5, /obj/item/stack/sheet/bone = 30) + attack_action_types = list() + +/mob/living/simple_animal/hostile/megafauna/dragon/lesser/AltClickOn(atom/movable/A) + if(!istype(A)) + return + if(player_cooldown >= world.time) + to_chat(src, "You need to wait [(player_cooldown - world.time) / 10] seconds before swooping again!") + return + swoop_attack(FALSE, A) + lava_pools(10, 2) // less pools but longer delay before spawns + player_cooldown = world.time + 200 // needs seperate cooldown or cant use fire attacks /mob/living/simple_animal/hostile/megafauna/dragon/lesser/grant_achievement(medaltype,scoretype) return @@ -534,21 +625,21 @@ Difficulty: Medium move_resist = MOVE_FORCE_NORMAL pull_force = MOVE_FORCE_NORMAL deathmessage = "screeches as its wings turn to dust and it collapses on the floor, life estinguished." - var/datum/action/small_sprite/carpsprite = new/datum/action/small_sprite/spacedragon() + attack_action_types = list() + small_sprite_type = /datum/action/small_sprite/megafauna/spacedragon /mob/living/simple_animal/hostile/megafauna/dragon/space_dragon/grant_achievement(medaltype,scoretype) return /mob/living/simple_animal/hostile/megafauna/dragon/space_dragon/Initialize() - carpsprite.Grant(src) - mob_spell_list += new /obj/effect/proc_holder/spell/aoe_turf/repulse/spacedragon(src) + var/obj/effect/proc_holder/spell/aoe_turf/repulse/spacedragon/repulse_action = new /obj/effect/proc_holder/spell/aoe_turf/repulse/spacedragon(src) + repulse_action.action.Grant(src) + mob_spell_list += repulse_action . = ..() - smallsprite.Remove(src) /mob/living/simple_animal/hostile/megafauna/dragon/space_dragon/proc/fire_stream(var/atom/at = target) playsound(get_turf(src),'sound/magic/fireball.ogg', 200, 1) - if(QDELETED(src) || stat == DEAD) // we dead no fire - return + SLEEP_CHECK_DEATH(0) var/range = 20 var/list/turfs = list() turfs = line_target(0, range, at) @@ -584,3 +675,4 @@ Difficulty: Medium ..(targets, user, 60) /mob/living/simple_animal/hostile/megafauna/dragon/space_dragon/AltClickOn(atom/movable/A) + return \ No newline at end of file diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm index b530f68998e..4ac16ea3dd7 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm @@ -50,9 +50,9 @@ Difficulty: Hard armour_penetration = 50 melee_damage_lower = 15 melee_damage_upper = 15 - speed = 1 + speed = 10 move_to_delay = 10 - ranged = 1 + ranged = TRUE ranged_cooldown_time = 40 aggro_vision_range = 21 //so it can see to one side of the arena to the other loot = list(/obj/item/hierophant_club) @@ -63,6 +63,10 @@ Difficulty: Hard score_type = HIEROPHANT_SCORE del_on_death = TRUE deathsound = 'sound/magic/repulse.ogg' + attack_action_types = list(/datum/action/innate/megafauna_attack/blink, + /datum/action/innate/megafauna_attack/chaser_swarm, + /datum/action/innate/megafauna_attack/cross_blasts, + /datum/action/innate/megafauna_attack/blink_spam) var/burst_range = 3 //range on burst aoe var/beam_range = 5 //range on cross blast beams @@ -77,12 +81,301 @@ Difficulty: Hard var/list/kill_phrases = list("Wsyvgi sj irivkc xettih. Vitemvmrk...", "Irivkc wsyvgi jsyrh. Vitemvmrk...", "Jyip jsyrh. Egxmzexmrk vitemv gcgpiw...", "Kix fiex. Liepmrk...") var/list/target_phrases = list("Xevkix psgexih.", "Iriqc jsyrh.", "Eguymvih xevkix.") var/list/stored_nearby = list() // stores people nearby the hierophant when it enters the death animation - var/hierophant_dying = FALSE // if we're in the death animation and don't want to proc death repeatedly every time we take damage, do not set stat = dead or deathsounds and messages wont work /mob/living/simple_animal/hostile/megafauna/hierophant/Initialize() . = ..() spawned_beacon = new(loc) +/datum/action/innate/megafauna_attack/blink + name = "Blink To Target" + icon_icon = 'icons/mob/actions/actions_items.dmi' + button_icon_state = "sniper_zoom" + chosen_message = "You are now blinking to your target." + chosen_attack_num = 1 + +/datum/action/innate/megafauna_attack/chaser_swarm + name = "Chaser Swarm" + icon_icon = 'icons/effects/effects.dmi' + button_icon_state = "hierophant_squares_indefinite" + chosen_message = "You are firing a chaser swarm at your target." + chosen_attack_num = 2 + +/datum/action/innate/megafauna_attack/cross_blasts + name = "Cross Blasts" + icon_icon = 'icons/effects/effects.dmi' + button_icon_state = "hierophant_blast_indefinite" + chosen_message = "You are now firing cross blasts at your target." + chosen_attack_num = 3 + +/datum/action/innate/megafauna_attack/blink_spam + name = "Blink Chase" + icon_icon = 'icons/obj/lavaland/artefacts.dmi' + button_icon_state = "hierophant_club_ready_beacon" + chosen_message = "You are now repeatedly blinking at your target." + chosen_attack_num = 4 + +/mob/living/simple_animal/hostile/megafauna/hierophant/OpenFire() + if(blinking) + return + + calculate_rage() + var/blink_counter = 1 + round(anger_modifier * 0.08) + var/cross_counter = 1 + round(anger_modifier * 0.12) + + arena_trap(target) + ranged_cooldown = world.time + max(5, ranged_cooldown_time - anger_modifier * 0.75) //scale cooldown lower with high anger. + + var/target_slowness = 0 + var/mob/living/L + if(isliving(target)) + L = target + target_slowness += L.movement_delay() + if(client) + target_slowness += 1 + + target_slowness = max(target_slowness, 1) + chaser_speed = max(1, (3 - anger_modifier * 0.04) + ((target_slowness - 1) * 0.5)) + + if(client) + switch(chosen_attack) + if(1) + blink(target) + if(2) + chaser_swarm(blink_counter, target_slowness, cross_counter) + if(3) + cross_blast_spam(blink_counter, target_slowness, cross_counter) + if(4) + blink_spam(blink_counter, target_slowness, cross_counter) + return + + if(L?.stat == DEAD && !blinking && get_dist(src, L) > 2) + blink(L) + return + + if(prob(anger_modifier * 0.75)) //major ranged attack + var/list/possibilities = list() + if(cross_counter > 1) + possibilities += "cross_blast_spam" + if(get_dist(src, target) > 2) + possibilities += "blink_spam" + if(chaser_cooldown < world.time) + if(prob(anger_modifier * 2)) + possibilities = list("chaser_swarm") + else + possibilities += "chaser_swarm" + if(possibilities.len) + switch(pick(possibilities)) + if("blink_spam") //blink either once or multiple times. + blink_spam(blink_counter, target_slowness, cross_counter) + if("cross_blast_spam") //fire a lot of cross blasts at a target. + cross_blast_spam(blink_counter, target_slowness, cross_counter) + if("chaser_swarm") //fire four fucking chasers at a target and their friends. + chaser_swarm(blink_counter, target_slowness, cross_counter) + return + + if(chaser_cooldown < world.time) //if chasers are off cooldown, fire some! + var/obj/effect/temp_visual/hierophant/chaser/C = new /obj/effect/temp_visual/hierophant/chaser(loc, src, target, chaser_speed, FALSE) + chaser_cooldown = world.time + initial(chaser_cooldown) + if((prob(anger_modifier) || target.Adjacent(src)) && target != src) + var/obj/effect/temp_visual/hierophant/chaser/OC = new(loc, src, target, chaser_speed * 1.5, FALSE) + OC.moving = 4 + OC.moving_dir = pick(GLOB.cardinals - C.moving_dir) + + else if(prob(10 + (anger_modifier * 0.5)) && get_dist(src, target) > 2) + blink(target) + + else if(prob(70 - anger_modifier)) //a cross blast of some type + if(prob(anger_modifier * (2 / target_slowness)) && health < maxHealth * 0.5) //we're super angry do it at all dirs + INVOKE_ASYNC(src, .proc/blasts, target, GLOB.alldirs) + else if(prob(60)) + INVOKE_ASYNC(src, .proc/blasts, target, GLOB.cardinals) + else + INVOKE_ASYNC(src, .proc/blasts, target, GLOB.diagonals) + else //just release a burst of power + INVOKE_ASYNC(src, .proc/burst, get_turf(src)) + +/mob/living/simple_animal/hostile/megafauna/hierophant/proc/blink_spam(var/blink_counter, var/target_slowness, var/cross_counter) + ranged_cooldown = world.time + max(5, major_attack_cooldown - anger_modifier * 0.75) + if(health < maxHealth * 0.5 && blink_counter > 1) + visible_message("\"Mx ampp rsx iwgeti.\"") + var/oldcolor = color + animate(src, color = "#660099", time = 6) + SLEEP_CHECK_DEATH(6) + while(!QDELETED(target) && blink_counter) + if(loc == target.loc || loc == target) //we're on the same tile as them after about a second we can stop now + break + blink_counter-- + blinking = FALSE + blink(target) + blinking = TRUE + SLEEP_CHECK_DEATH(4 + target_slowness) + animate(src, color = oldcolor, time = 8) + addtimer(CALLBACK(src, /atom/proc/update_atom_colour), 8) + SLEEP_CHECK_DEATH(8) + blinking = FALSE + else + blink(target) + +/mob/living/simple_animal/hostile/megafauna/hierophant/proc/cross_blast_spam(var/blink_counter, var/target_slowness, var/cross_counter) + ranged_cooldown = world.time + max(5, major_attack_cooldown - anger_modifier * 0.75) + visible_message("\"Piezi mx rsalivi xs vyr.\"") + blinking = TRUE + var/oldcolor = color + animate(src, color = "#660099", time = 6) + SLEEP_CHECK_DEATH(6) + while(!QDELETED(target) && cross_counter) + cross_counter-- + if(prob(60)) + INVOKE_ASYNC(src, .proc/blasts, target, GLOB.cardinals) + else + INVOKE_ASYNC(src, .proc/blasts, target, GLOB.diagonals) + SLEEP_CHECK_DEATH(6 + target_slowness) + animate(src, color = oldcolor, time = 8) + addtimer(CALLBACK(src, /atom/proc/update_atom_colour), 8) + SLEEP_CHECK_DEATH(8) + blinking = FALSE + + +/mob/living/simple_animal/hostile/megafauna/hierophant/proc/chaser_swarm(var/blink_counter, var/target_slowness, var/cross_counter) + ranged_cooldown = world.time + max(5, major_attack_cooldown - anger_modifier * 0.75) + visible_message("\"Mx gerrsx lmhi.\"") + blinking = TRUE + var/oldcolor = color + animate(src, color = "#660099", time = 6) + SLEEP_CHECK_DEATH(6) + var/list/targets = ListTargets() + var/list/cardinal_copy = GLOB.cardinals.Copy() + while(targets.len && cardinal_copy.len) + var/mob/living/pickedtarget = pick(targets) + if(targets.len >= cardinal_copy.len) + pickedtarget = pick_n_take(targets) + if(!istype(pickedtarget) || pickedtarget.stat == DEAD) + pickedtarget = target + if(QDELETED(pickedtarget) || (istype(pickedtarget) && pickedtarget.stat == DEAD)) + break //main target is dead and we're out of living targets, cancel out + var/obj/effect/temp_visual/hierophant/chaser/C = new(loc, src, pickedtarget, chaser_speed, FALSE) + C.moving = 3 + C.moving_dir = pick_n_take(cardinal_copy) + SLEEP_CHECK_DEATH(8 + target_slowness) + chaser_cooldown = world.time + initial(chaser_cooldown) + animate(src, color = oldcolor, time = 8) + addtimer(CALLBACK(src, /atom/proc/update_atom_colour), 8) + SLEEP_CHECK_DEATH(8) + blinking = FALSE + +/mob/living/simple_animal/hostile/megafauna/hierophant/proc/blasts(mob/victim, var/list/directions = GLOB.cardinals) //fires cross blasts with a delay + var/turf/T = get_turf(victim) + if(!T) + return + if(directions == GLOB.cardinals) + new /obj/effect/temp_visual/hierophant/telegraph/cardinal(T, src) + else if(directions == GLOB.diagonals) + new /obj/effect/temp_visual/hierophant/telegraph/diagonal(T, src) + else + new /obj/effect/temp_visual/hierophant/telegraph(T, src) + playsound(T,'sound/effects/bin_close.ogg', 200, 1) + SLEEP_CHECK_DEATH(2) + new /obj/effect/temp_visual/hierophant/blast(T, src, FALSE) + for(var/d in directions) + INVOKE_ASYNC(src, .proc/blast_wall, T, d) + +/mob/living/simple_animal/hostile/megafauna/hierophant/proc/blast_wall(turf/T, set_dir) //make a wall of blasts beam_range tiles long + var/range = beam_range + var/turf/previousturf = T + var/turf/J = get_step(previousturf, set_dir) + for(var/i in 1 to range) + new /obj/effect/temp_visual/hierophant/blast(J, src, FALSE) + previousturf = J + J = get_step(previousturf, set_dir) + +/mob/living/simple_animal/hostile/megafauna/hierophant/proc/arena_trap(mob/victim) //trap a target in an arena + var/turf/T = get_turf(victim) + if(!istype(victim) || victim.stat == DEAD || !T || arena_cooldown > world.time) + return + if((istype(get_area(T), /area/ruin/unpowered/hierophant) || istype(get_area(src), /area/ruin/unpowered/hierophant)) && victim != src) + return + arena_cooldown = world.time + initial(arena_cooldown) + for(var/d in GLOB.cardinals) + INVOKE_ASYNC(src, .proc/arena_squares, T, d) + for(var/t in RANGE_TURFS(11, T)) + if(t && get_dist(t, T) == 11) + new /obj/effect/temp_visual/hierophant/wall(t, src) + new /obj/effect/temp_visual/hierophant/blast(t, src, FALSE) + if(get_dist(src, T) >= 11) //hey you're out of range I need to get closer to you! + INVOKE_ASYNC(src, .proc/blink, T) + +/mob/living/simple_animal/hostile/megafauna/hierophant/proc/arena_squares(turf/T, set_dir) //make a fancy effect extending from the arena target + var/turf/previousturf = T + var/turf/J = get_step(previousturf, set_dir) + for(var/i in 1 to 10) + var/obj/effect/temp_visual/hierophant/squares/HS = new(J) + HS.setDir(set_dir) + previousturf = J + J = get_step(previousturf, set_dir) + SLEEP_CHECK_DEATH(0.5) + +/mob/living/simple_animal/hostile/megafauna/hierophant/proc/blink(mob/victim) //blink to a target + if(blinking || !victim) + return + var/turf/T = get_turf(victim) + var/turf/source = get_turf(src) + new /obj/effect/temp_visual/hierophant/telegraph(T, src) + new /obj/effect/temp_visual/hierophant/telegraph(source, src) + playsound(T,'sound/magic/wand_teleport.ogg', 200, 1) + playsound(source,'sound/machines/airlockopen.ogg', 200, 1) + blinking = TRUE + SLEEP_CHECK_DEATH(2) //short delay before we start... + new /obj/effect/temp_visual/hierophant/telegraph/teleport(T, src) + new /obj/effect/temp_visual/hierophant/telegraph/teleport(source, src) + for(var/t in RANGE_TURFS(1, T)) + var/obj/effect/temp_visual/hierophant/blast/B = new(t, src, FALSE) + B.damage = 30 + for(var/t in RANGE_TURFS(1, source)) + var/obj/effect/temp_visual/hierophant/blast/B = new(t, src, FALSE) + B.damage = 30 + animate(src, alpha = 0, time = 2, easing = EASE_OUT) //fade out + SLEEP_CHECK_DEATH(1) + visible_message("[src] fades out!") + density = FALSE + SLEEP_CHECK_DEATH(2) + forceMove(T) + SLEEP_CHECK_DEATH(1) + animate(src, alpha = 255, time = 2, easing = EASE_IN) //fade IN + SLEEP_CHECK_DEATH(1) + density = TRUE + visible_message("[src] fades in!") + SLEEP_CHECK_DEATH(1) //at this point the blasts we made detonate + blinking = FALSE + +/mob/living/simple_animal/hostile/megafauna/hierophant/proc/melee_blast(mob/victim) //make a 3x3 blast around a target + if(!victim) + return + var/turf/T = get_turf(victim) + if(!T) + return + new /obj/effect/temp_visual/hierophant/telegraph(T, src) + playsound(T,'sound/effects/bin_close.ogg', 200, 1) + SLEEP_CHECK_DEATH(2) + for(var/t in RANGE_TURFS(1, T)) + new /obj/effect/temp_visual/hierophant/blast(t, src, FALSE) + +//expanding square +/proc/hierophant_burst(mob/caster, turf/original, burst_range, spread_speed = 0.5) + playsound(original,'sound/machines/airlockopen.ogg', 200, 1) + var/last_dist = 0 + for(var/t in spiral_range_turfs(burst_range, original)) + var/turf/T = t + if(!T) + continue + var/dist = get_dist(original, T) + if(dist > last_dist) + last_dist = dist + sleep(1 + min(burst_range - last_dist, 12) * spread_speed) //gets faster as it gets further out + new /obj/effect/temp_visual/hierophant/blast(T, caster, FALSE) + +/mob/living/simple_animal/hostile/megafauna/hierophant/proc/burst(turf/original, spread_speed) + hierophant_burst(src, original, burst_range, spread_speed) + /mob/living/simple_animal/hostile/megafauna/hierophant/Life() . = ..() if(. && spawned_beacon && !QDELETED(spawned_beacon) && !client) @@ -102,16 +395,17 @@ Difficulty: Hard visible_message("\"Vitemvw gsqtpixi. Stivexmsrep ijjmgmirgc gsqtvsqmwih.\"") /mob/living/simple_animal/hostile/megafauna/hierophant/death() - if(health > 0 || hierophant_dying) + if(health > 0 || stat == DEAD) return else - hierophant_dying = TRUE + stat = DEAD blinking = TRUE //we do a fancy animation, release a huge burst(), and leave our staff. visible_message("\"Mrmxmexmrk wipj-hiwxvygx wiuyirgi...\"") visible_message("[src] shrinks, releasing a massive burst of energy!") for(var/mob/living/L in view(7,src)) stored_nearby += L // store the people to grant the achievements to once we die hierophant_burst(null, get_turf(src), 10) + stat = CONSCIOUS // deathgasp wont run if dead, stupid ..(force_grant = stored_nearby) /mob/living/simple_animal/hostile/megafauna/hierophant/Destroy() @@ -142,7 +436,7 @@ Difficulty: Hard /mob/living/simple_animal/hostile/megafauna/hierophant/adjustHealth(amount, updating_health = TRUE, forced = FALSE) . = ..() - if(src && . > 0 && !blinking) + if(src && . && !blinking) wander = TRUE did_reset = FALSE @@ -191,260 +485,6 @@ Difficulty: Hard burst_range = initial(burst_range) + round(anger_modifier * 0.08) beam_range = initial(beam_range) + round(anger_modifier * 0.12) -/mob/living/simple_animal/hostile/megafauna/hierophant/OpenFire() - calculate_rage() - if(blinking) - return - - var/target_slowness = 0 - if(isliving(target)) - var/mob/living/L = target - if(!blinking && L.stat == DEAD && get_dist(src, L) > 2) - blink(L) - return - target_slowness += L.movement_delay() - target_slowness = max(target_slowness, 1) - chaser_speed = max(1, (3 - anger_modifier * 0.04) + ((target_slowness - 1) * 0.5)) - - arena_trap(target) - ranged_cooldown = world.time + max(5, ranged_cooldown_time - anger_modifier * 0.75) //scale cooldown lower with high anger. - - if(prob(anger_modifier * 0.75)) //major ranged attack - var/list/possibilities = list() - var/cross_counter = 1 + round(anger_modifier * 0.12) - if(cross_counter > 1) - possibilities += "cross_blast_spam" - if(get_dist(src, target) > 2) - possibilities += "blink_spam" - if(chaser_cooldown < world.time) - if(prob(anger_modifier * 2)) - possibilities = list("chaser_swarm") - else - possibilities += "chaser_swarm" - if(possibilities.len) - ranged_cooldown = world.time + max(5, major_attack_cooldown - anger_modifier * 0.75) //we didn't cancel out of an attack, use the higher cooldown - var/blink_counter = 1 + round(anger_modifier * 0.08) - switch(pick(possibilities)) - if("blink_spam") //blink either once or multiple times. - if(health < maxHealth * 0.5 && blink_counter > 1) - visible_message("\"Mx ampp rsx iwgeti.\"") - var/oldcolor = color - animate(src, color = "#660099", time = 6) - sleep(6) - while(health && !QDELETED(target) && blink_counter) - if(loc == target.loc || loc == target) //we're on the same tile as them after about a second we can stop now - break - blink_counter-- - blinking = FALSE - blink(target) - blinking = TRUE - sleep(4 + target_slowness) - animate(src, color = oldcolor, time = 8) - addtimer(CALLBACK(src, /atom/proc/update_atom_colour), 8) - sleep(8) - blinking = FALSE - else - blink(target) - if("cross_blast_spam") //fire a lot of cross blasts at a target. - visible_message("\"Piezi mx rsalivi xs vyr.\"") - blinking = TRUE - var/oldcolor = color - animate(src, color = "#660099", time = 6) - sleep(6) - while(health && !QDELETED(target) && cross_counter) - cross_counter-- - if(prob(60)) - INVOKE_ASYNC(src, .proc/cardinal_blasts, target) - else - INVOKE_ASYNC(src, .proc/diagonal_blasts, target) - sleep(6 + target_slowness) - animate(src, color = oldcolor, time = 8) - addtimer(CALLBACK(src, /atom/proc/update_atom_colour), 8) - sleep(8) - blinking = FALSE - if("chaser_swarm") //fire four fucking chasers at a target and their friends. - visible_message("\"Mx gerrsx lmhi.\"") - blinking = TRUE - var/oldcolor = color - animate(src, color = "#660099", time = 6) - sleep(6) - var/list/targets = ListTargets() - var/list/cardinal_copy = GLOB.cardinals.Copy() - while(health && targets.len && cardinal_copy.len) - var/mob/living/pickedtarget = pick(targets) - if(targets.len >= cardinal_copy.len) - pickedtarget = pick_n_take(targets) - if(!istype(pickedtarget) || pickedtarget.stat == DEAD) - pickedtarget = target - if(QDELETED(pickedtarget) || (istype(pickedtarget) && pickedtarget.stat == DEAD)) - break //main target is dead and we're out of living targets, cancel out - var/obj/effect/temp_visual/hierophant/chaser/C = new(loc, src, pickedtarget, chaser_speed, FALSE) - C.moving = 3 - C.moving_dir = pick_n_take(cardinal_copy) - sleep(8 + target_slowness) - chaser_cooldown = world.time + initial(chaser_cooldown) - animate(src, color = oldcolor, time = 8) - addtimer(CALLBACK(src, /atom/proc/update_atom_colour), 8) - sleep(8) - blinking = FALSE - return - - if(chaser_cooldown < world.time) //if chasers are off cooldown, fire some! - var/obj/effect/temp_visual/hierophant/chaser/C = new /obj/effect/temp_visual/hierophant/chaser(loc, src, target, chaser_speed, FALSE) - chaser_cooldown = world.time + initial(chaser_cooldown) - if((prob(anger_modifier) || target.Adjacent(src)) && target != src) - var/obj/effect/temp_visual/hierophant/chaser/OC = new(loc, src, target, chaser_speed * 1.5, FALSE) - OC.moving = 4 - OC.moving_dir = pick(GLOB.cardinals - C.moving_dir) - - else if(prob(10 + (anger_modifier * 0.5)) && get_dist(src, target) > 2) - blink(target) - - else if(prob(70 - anger_modifier)) //a cross blast of some type - if(prob(anger_modifier * (2 / target_slowness)) && health < maxHealth * 0.5) //we're super angry do it at all dirs - INVOKE_ASYNC(src, .proc/alldir_blasts, target) - else if(prob(60)) - INVOKE_ASYNC(src, .proc/cardinal_blasts, target) - else - INVOKE_ASYNC(src, .proc/diagonal_blasts, target) - else //just release a burst of power - INVOKE_ASYNC(src, .proc/burst, get_turf(src)) - -/mob/living/simple_animal/hostile/megafauna/hierophant/proc/diagonal_blasts(mob/victim) //fire diagonal cross blasts with a delay - var/turf/T = get_turf(victim) - if(!T) - return - new /obj/effect/temp_visual/hierophant/telegraph/diagonal(T, src) - playsound(T,'sound/effects/bin_close.ogg', 200, 1) - sleep(2) - new /obj/effect/temp_visual/hierophant/blast(T, src, FALSE) - for(var/d in GLOB.diagonals) - INVOKE_ASYNC(src, .proc/blast_wall, T, d) - -/mob/living/simple_animal/hostile/megafauna/hierophant/proc/cardinal_blasts(mob/victim) //fire cardinal cross blasts with a delay - var/turf/T = get_turf(victim) - if(!T) - return - new /obj/effect/temp_visual/hierophant/telegraph/cardinal(T, src) - playsound(T,'sound/effects/bin_close.ogg', 200, 1) - sleep(2) - new /obj/effect/temp_visual/hierophant/blast(T, src, FALSE) - for(var/d in GLOB.cardinals) - INVOKE_ASYNC(src, .proc/blast_wall, T, d) - -/mob/living/simple_animal/hostile/megafauna/hierophant/proc/alldir_blasts(mob/victim) //fire alldir cross blasts with a delay - var/turf/T = get_turf(victim) - if(!T) - return - new /obj/effect/temp_visual/hierophant/telegraph(T, src) - playsound(T,'sound/effects/bin_close.ogg', 200, 1) - sleep(2) - new /obj/effect/temp_visual/hierophant/blast(T, src, FALSE) - for(var/d in GLOB.alldirs) - INVOKE_ASYNC(src, .proc/blast_wall, T, d) - -/mob/living/simple_animal/hostile/megafauna/hierophant/proc/blast_wall(turf/T, set_dir) //make a wall of blasts beam_range tiles long - var/range = beam_range - var/turf/previousturf = T - var/turf/J = get_step(previousturf, set_dir) - for(var/i in 1 to range) - new /obj/effect/temp_visual/hierophant/blast(J, src, FALSE) - previousturf = J - J = get_step(previousturf, set_dir) - -/mob/living/simple_animal/hostile/megafauna/hierophant/proc/arena_trap(mob/victim) //trap a target in an arena - var/turf/T = get_turf(victim) - if(!istype(victim) || victim.stat == DEAD || !T || arena_cooldown > world.time) - return - if((istype(get_area(T), /area/ruin/unpowered/hierophant) || istype(get_area(src), /area/ruin/unpowered/hierophant)) && victim != src) - return - arena_cooldown = world.time + initial(arena_cooldown) - for(var/d in GLOB.cardinals) - INVOKE_ASYNC(src, .proc/arena_squares, T, d) - for(var/t in RANGE_TURFS(11, T)) - if(t && get_dist(t, T) == 11) - new /obj/effect/temp_visual/hierophant/wall(t, src) - new /obj/effect/temp_visual/hierophant/blast(t, src, FALSE) - if(get_dist(src, T) >= 11) //hey you're out of range I need to get closer to you! - INVOKE_ASYNC(src, .proc/blink, T) - -/mob/living/simple_animal/hostile/megafauna/hierophant/proc/arena_squares(turf/T, set_dir) //make a fancy effect extending from the arena target - var/turf/previousturf = T - var/turf/J = get_step(previousturf, set_dir) - for(var/i in 1 to 10) - var/obj/effect/temp_visual/hierophant/squares/HS = new(J) - HS.setDir(set_dir) - previousturf = J - J = get_step(previousturf, set_dir) - sleep(0.5) - -/mob/living/simple_animal/hostile/megafauna/hierophant/proc/blink(mob/victim) //blink to a target - if(blinking || !victim) - return - var/turf/T = get_turf(victim) - var/turf/source = get_turf(src) - new /obj/effect/temp_visual/hierophant/telegraph(T, src) - new /obj/effect/temp_visual/hierophant/telegraph(source, src) - playsound(T,'sound/magic/wand_teleport.ogg', 200, 1) - playsound(source,'sound/machines/airlockopen.ogg', 200, 1) - blinking = TRUE - sleep(2) //short delay before we start... - new /obj/effect/temp_visual/hierophant/telegraph/teleport(T, src) - new /obj/effect/temp_visual/hierophant/telegraph/teleport(source, src) - for(var/t in RANGE_TURFS(1, T)) - var/obj/effect/temp_visual/hierophant/blast/B = new(t, src, FALSE) - B.damage = 30 - for(var/t in RANGE_TURFS(1, source)) - var/obj/effect/temp_visual/hierophant/blast/B = new(t, src, FALSE) - B.damage = 30 - animate(src, alpha = 0, time = 2, easing = EASE_OUT) //fade out - sleep(1) - visible_message("[src] fades out!") - density = FALSE - sleep(2) - forceMove(T) - sleep(1) - animate(src, alpha = 255, time = 2, easing = EASE_IN) //fade IN - sleep(1) - density = TRUE - visible_message("[src] fades in!") - sleep(1) //at this point the blasts we made detonate - blinking = FALSE - -/mob/living/simple_animal/hostile/megafauna/hierophant/proc/melee_blast(mob/victim) //make a 3x3 blast around a target - if(!victim) - return - var/turf/T = get_turf(victim) - if(!T) - return - new /obj/effect/temp_visual/hierophant/telegraph(T, src) - playsound(T,'sound/effects/bin_close.ogg', 200, 1) - sleep(2) - for(var/t in RANGE_TURFS(1, T)) - new /obj/effect/temp_visual/hierophant/blast(t, src, FALSE) - -//expanding square -/proc/hierophant_burst(mob/caster, turf/original, burst_range, spread_speed = 0.5) - playsound(original,'sound/machines/airlockopen.ogg', 200, 1) - var/last_dist = 0 - for(var/t in spiral_range_turfs(burst_range, original)) - var/turf/T = t - if(!T) - continue - var/dist = get_dist(original, T) - if(dist > last_dist) - last_dist = dist - sleep(1 + min(burst_range - last_dist, 12) * spread_speed) //gets faster as it gets further out - new /obj/effect/temp_visual/hierophant/blast(T, caster, FALSE) - -/mob/living/simple_animal/hostile/megafauna/hierophant/proc/burst(turf/original, spread_speed) - hierophant_burst(src, original, burst_range, spread_speed) - -/mob/living/simple_animal/hostile/megafauna/hierophant/AltClickOn(atom/A) //player control handler(don't give this to a player holy fuck) - if(!istype(A) || get_dist(A, src) <= 2) - return - blink(A) - //Hierophant overlays /obj/effect/temp_visual/hierophant name = "vortex energy" diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm index 06a5c4e58db..c082542c1d8 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm @@ -20,7 +20,6 @@ Difficulty: Medium name = "Legion" health = 800 maxHealth = 800 - spacewalk = TRUE icon_state = "legion" icon_living = "legion" desc = "One of many." @@ -31,14 +30,14 @@ Difficulty: Medium armour_penetration = 50 melee_damage_lower = 25 melee_damage_upper = 25 - speed = 2 - ranged = 1 - del_on_death = 1 + speed = 5 + ranged = TRUE + del_on_death = TRUE retreat_distance = 5 minimum_distance = 5 ranged_cooldown_time = 20 var/size = 5 - var/charging = 0 + var/charging = FALSE internal_type = /obj/item/gps/internal/legion medal_type = BOSS_MEDAL_LEGION score_type = LEGION_SCORE @@ -47,9 +46,60 @@ Difficulty: Medium loot = list(/obj/item/stack/sheet/bone = 3) vision_range = 13 wander = FALSE - elimination = 1 + elimination = TRUE appearance_flags = 0 mouse_opacity = MOUSE_OPACITY_ICON + attack_action_types = list(/datum/action/innate/megafauna_attack/create_skull, + /datum/action/innate/megafauna_attack/charge_target) + small_sprite_type = /datum/action/small_sprite/megafauna/legion + +/datum/action/innate/megafauna_attack/create_skull + name = "Create Legion Skull" + icon_icon = 'icons/mob/lavaland/lavaland_monsters.dmi' + button_icon_state = "legion_head" + chosen_message = "You are now creating legion skulls." + chosen_attack_num = 1 + +/datum/action/innate/megafauna_attack/charge_target + name = "Charge Target" + icon_icon = 'icons/mob/actions/actions_items.dmi' + button_icon_state = "sniper_zoom" + chosen_message = "You are now charging at your target." + chosen_attack_num = 2 + +/mob/living/simple_animal/hostile/megafauna/legion/OpenFire(the_target) + if(charging) + return + ranged_cooldown = world.time + ranged_cooldown_time + + if(client) + switch(chosen_attack) + if(1) + create_legion_skull() + if(2) + charge_target() + return + + if(prob(75)) + create_legion_skull() + else + charge_target() + +/mob/living/simple_animal/hostile/megafauna/legion/proc/create_legion_skull() + var/mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion/A = new(loc) + A.GiveTarget(target) + A.friends = friends + A.faction = faction + +/mob/living/simple_animal/hostile/megafauna/legion/proc/charge_target() + visible_message("[src] charges!") + SpinAnimation(speed = 20, loops = 5) + ranged = FALSE + retreat_distance = 0 + minimum_distance = 0 + set_varspeed(0) + charging = TRUE + addtimer(CALLBACK(src, .proc/reset_charge), 50) /mob/living/simple_animal/hostile/megafauna/legion/GiveTarget(new_target) . = ..() @@ -69,30 +119,12 @@ Difficulty: Medium var/mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion/A = new(loc) A.infest(L) -/mob/living/simple_animal/hostile/megafauna/legion/OpenFire(the_target) - if(world.time >= ranged_cooldown && !charging) - if(prob(75)) - var/mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion/A = new(loc) - A.GiveTarget(target) - A.friends = friends - A.faction = faction - ranged_cooldown = world.time + ranged_cooldown_time - else - visible_message("[src] charges!") - SpinAnimation(speed = 20, loops = 5) - ranged = 0 - retreat_distance = 0 - minimum_distance = 0 - speed = 0 - charging = 1 - addtimer(CALLBACK(src, .proc/reset_charge), 50) - /mob/living/simple_animal/hostile/megafauna/legion/proc/reset_charge() - ranged = 1 + ranged = TRUE retreat_distance = 5 minimum_distance = 5 - speed = 2 - charging = 0 + set_varspeed(2) + charging = FALSE /mob/living/simple_animal/hostile/megafauna/legion/death() if(health > 0) @@ -130,7 +162,7 @@ Difficulty: Medium break if(last_legion) loot = list(/obj/item/staff/storm) - elimination = 0 + elimination = FALSE else if(prob(5)) loot = list(/obj/structure/closet/crate/necropolis/tendril) if(!true_spawn) diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm index ac335a90db7..4818adda9e2 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm @@ -3,6 +3,7 @@ desc = "Attack the weak point for massive damage." health = 1000 maxHealth = 1000 + spacewalk = TRUE a_intent = INTENT_HARM sentience_type = SENTIENCE_BOSS environment_smash = ENVIRONMENT_SMASH_RWALLS @@ -12,7 +13,7 @@ faction = list("mining", "boss") weather_immunities = list("lava","ash") movement_type = FLYING - robust_searching = 1 + robust_searching = TRUE ranged_ignores_vision = TRUE stat_attack = DEAD atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) @@ -35,8 +36,11 @@ var/obj/item/gps/internal var/internal_type var/recovery_time = 0 - var/true_spawn = 1 // if this is a megafauna that should grant achievements, or have a gps signal + var/true_spawn = TRUE // if this is a megafauna that should grant achievements, or have a gps signal var/nest_range = 10 + var/chosen_attack = 1 // chosen attack num + var/list/attack_action_types = list() + var/small_sprite_type /mob/living/simple_animal/hostile/megafauna/Initialize(mapload) . = ..() @@ -44,6 +48,12 @@ internal = new internal_type(src) apply_status_effect(STATUS_EFFECT_CRUSHERDAMAGETRACKING) ADD_TRAIT(src, TRAIT_NO_TELEPORT, MEGAFAUNA_TRAIT) + for(var/action_type in attack_action_types) + var/datum/action/innate/megafauna_attack/attack_action = new action_type() + attack_action.Grant(src) + if(small_sprite_type) + var/datum/action/small_sprite/small_action = new small_sprite_type() + small_action.Grant(src) /mob/living/simple_animal/hostile/megafauna/Destroy() QDEL_NULL(internal) @@ -109,13 +119,14 @@ /mob/living/simple_animal/hostile/megafauna/proc/devour(mob/living/L) if(!L) - return + return FALSE visible_message( "[src] devours [L]!", "You feast on [L], restoring your health!") if(!is_station_level(z) || client) //NPC monsters won't heal while on station adjustBruteLoss(-L.maxHealth/2) L.gib() + return TRUE /mob/living/simple_animal/hostile/megafauna/ex_act(severity, target) switch (severity) @@ -149,3 +160,21 @@ SSmedals.SetScore(BOSS_SCORE, C, 1) SSmedals.SetScore(score_type, C, 1) return TRUE + +/datum/action/innate/megafauna_attack + name = "Megafauna Attack" + icon_icon = 'icons/mob/actions/actions_animal.dmi' + button_icon_state = "" + var/mob/living/simple_animal/hostile/megafauna/M + var/chosen_message + var/chosen_attack_num = 0 + +/datum/action/innate/megafauna_attack/Grant(mob/living/L) + if(istype(L, /mob/living/simple_animal/hostile/megafauna)) + M = L + return ..() + return FALSE + +/datum/action/innate/megafauna_attack/Activate() + M.chosen_attack = chosen_attack_num + to_chat(M, chosen_message) diff --git a/icons/effects/blood.dmi b/icons/effects/blood.dmi index 3b59694b029..d16dcf7c30b 100644 Binary files a/icons/effects/blood.dmi and b/icons/effects/blood.dmi differ diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi index 4b6af5ab469..8eb1bb010fd 100644 Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ