diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index 608fcfcce20..11b03d7102a 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -218,3 +218,5 @@ #define hasorgans(A) (ishuman(A)) #define is_admin(user) (check_rights(R_ADMIN, 0, (user)) != 0) + +#define SLEEP_CHECK_DEATH(X) sleep(X); if(QDELETED(src) || stat == DEAD) return; \ No newline at end of file diff --git a/code/game/objects/effects/temporary_visuals/miscellaneous.dm b/code/game/objects/effects/temporary_visuals/miscellaneous.dm index 7f1683ba415..d6dbf1dddbb 100644 --- a/code/game/objects/effects/temporary_visuals/miscellaneous.dm +++ b/code/game/objects/effects/temporary_visuals/miscellaneous.dm @@ -140,6 +140,15 @@ ..() animate(src, alpha = 0, time = duration) +/obj/effect/temp_visual/decoy/fading/threesecond + duration = 40 + +/obj/effect/temp_visual/decoy/fading/fivesecond + duration = 50 + +/obj/effect/temp_visual/decoy/fading/halfsecond + duration = 5 + /obj/effect/temp_visual/fire icon = 'icons/goonstation/effects/fire.dmi' icon_state = "3" 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 d45e6f6ead8..db96f9fce96 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 @@ -34,7 +34,7 @@ Difficulty: Medium 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/energy/cleaving_saw, /obj/item/gun/energy/kinetic_accelerator, /obj/item/crusher_trophy/miner_eye) @@ -47,8 +47,12 @@ 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." death_sound = "bodyfall" + attack_action_types = list(/datum/action/innate/megafauna_attack/dash, + /datum/action/innate/megafauna_attack/kinetic_accelerator, + /datum/action/innate/megafauna_attack/transform_weapon) /obj/item/gps/internal/miner icon_state = null @@ -56,13 +60,52 @@ 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() +/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/Initialize(mapload) . = ..() - if(. && prob(12)) - INVOKE_ASYNC(src, .proc/dash) + miner_saw = new(src) + internal_gps = new/obj/item/gps/internal/miner(src) + + // Add a zone selection UI; otherwise the mob can't melee attack properly. + zone_sel = new /obj/screen/zone_sel() + +/datum/action/innate/megafauna_attack/dash + name = "Dash To Target" + icon_icon = 'icons/mob/actions/actions.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/energy/cleaving_saw/miner //nerfed saw because it is very murdery force = 6 @@ -79,14 +122,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) - internal_gps = new/obj/item/gps/internal/miner(src) - - // Add a zone selection UI; otherwise the mob can't melee attack properly. - zone_sel = new /obj/screen/zone_sel() - /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) @@ -104,16 +139,19 @@ Difficulty: Medium return FALSE return ..() -/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/ex_act(severity, target) +/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/ex_act(severity) if(dash()) 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)) @@ -132,8 +170,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) @@ -146,16 +182,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)) @@ -166,21 +196,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 @@ -189,7 +204,9 @@ Difficulty: Medium var/turf/own_turf = get_turf(src) if(!QDELETED(dash_target)) self_dist_to_target += get_dist(dash_target, own_turf) - for(var/turf/simulated/O in RANGE_TURFS(MINER_DASH_RANGE, own_turf)) + for(var/turf/O in RANGE_TURFS(MINER_DASH_RANGE, own_turf)) + if(O.density) + continue var/turf_dist_to_target = 0 if(!QDELETED(dash_target)) turf_dist_to_target += get_dist(dash_target, O) @@ -218,26 +235,29 @@ Difficulty: Medium var/turf/step_forward_turf = get_step(own_turf, get_cardinal_dir(own_turf, target_turf)) new /obj/effect/temp_visual/small_smoke/halfsecond(step_back_turf) new /obj/effect/temp_visual/small_smoke/halfsecond(step_forward_turf) - var/obj/effect/temp_visual/decoy/D = new /obj/effect/temp_visual/decoy(loc, src) - animate(D, alpha = 0, time = 5) + var/obj/effect/temp_visual/decoy/fading/halfsecond/D = new (own_turf, src) forceMove(step_back_turf) playsound(own_turf, 'sound/weapons/punchmiss.ogg', 40, 1, -1) 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) @@ -263,4 +283,12 @@ Difficulty: Medium sleep(4) animate(src, alpha = 0, time = 6, easing = EASE_OUT, flags = ANIMATION_PARALLEL) +/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 \ No newline at end of file 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 b618ff9e650..20df360a51a 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm @@ -32,10 +32,15 @@ mob_size = MOB_SIZE_LARGE layer = LARGE_MOB_LAYER //Looks weird with them slipping under mineral walls and cameras and shit otherwise mouse_opacity = MOUSE_OPACITY_OPAQUE // Easier to click on in melee, they're giant targets anyway + var/chosen_attack = 1 // chosen attack num + var/list/attack_action_types = list() /mob/living/simple_animal/hostile/megafauna/New() ..() apply_status_effect(STATUS_EFFECT_CRUSHERDAMAGETRACKING) + for(var/action_type in attack_action_types) + var/datum/action/innate/megafauna_attack/attack_action = new action_type() + attack_action.Grant(src) /mob/living/simple_animal/hostile/megafauna/Destroy() QDEL_NULL(internal_gps) @@ -113,3 +118,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) \ No newline at end of file diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs.dm deleted file mode 100644 index efc18895141..00000000000 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs.dm +++ /dev/null @@ -1,225 +0,0 @@ -/mob/living/simple_animal/hostile/asteroid/ - vision_range = 2 - 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) - unsuitable_atmos_damage = 15 - faction = list("mining") - weather_immunities = list("lava","ash") - obj_damage = 30 - environment_smash = 2 - minbodytemp = 0 - heat_damage_per_tick = 20 - response_help = "pokes" - response_disarm = "shoves" - response_harm = "strikes" - status_flags = 0 - a_intent = INTENT_HARM - var/throw_message = "bounces off of" - var/icon_aggro = null // for swapping to when we get aggressive - see_in_dark = 8 - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE - mob_size = MOB_SIZE_LARGE - -/mob/living/simple_animal/hostile/asteroid/Aggro() - ..() - icon_state = icon_aggro - -/mob/living/simple_animal/hostile/asteroid/LoseAggro() - ..() - if(stat == CONSCIOUS) - icon_state = icon_living - -/mob/living/simple_animal/hostile/asteroid/bullet_act(var/obj/item/projectile/P)//Reduces damage from most projectiles to curb off-screen kills - if(!stat) - Aggro() - if(P.damage < 30 && P.damage_type != BRUTE) - P.damage = (P.damage / 3) - visible_message("The [P] has a reduced effect on [src]!") - ..() - -/mob/living/simple_animal/hostile/asteroid/hitby(atom/movable/AM)//No floor tiling them to death, wiseguy - if(istype(AM, /obj/item)) - var/obj/item/T = AM - if(!stat) - Aggro() - if(T.throwforce <= 20) - visible_message("The [T.name] [src.throw_message] [src.name]!") - return - ..() - -/mob/living/simple_animal/hostile/asteroid/basilisk - name = "basilisk" - desc = "A territorial beast, covered in a thick shell that absorbs energy. Its stare causes victims to freeze from the inside." - icon = 'icons/mob/animal.dmi' - icon_state = "Basilisk" - icon_living = "Basilisk" - icon_aggro = "Basilisk_alert" - icon_dead = "Basilisk_dead" - icon_gib = "syndicate_gib" - move_to_delay = 20 - projectiletype = /obj/item/projectile/temp/basilisk - projectilesound = 'sound/weapons/pierce.ogg' - ranged = 1 - ranged_message = "stares" - ranged_cooldown_time = 30 - throw_message = "does nothing against the hard shell of" - vision_range = 2 - speed = 3 - maxHealth = 200 - health = 200 - harm_intent_damage = 5 - obj_damage = 60 - melee_damage_lower = 12 - melee_damage_upper = 12 - attacktext = "bites into" - a_intent = INTENT_HARM - speak_emote = list("chitters") - attack_sound = 'sound/weapons/bladeslice.ogg' - aggro_vision_range = 9 - idle_vision_range = 2 - turns_per_move = 5 - loot = list(/obj/item/stack/ore/diamond{layer = 4.1}, - /obj/item/stack/ore/diamond{layer = 4.1}) - -/obj/item/projectile/temp/basilisk - name = "freezing blast" - icon_state = "ice_2" - damage = 0 - damage_type = BURN - nodamage = 1 - flag = "energy" - temperature = 50 - -/mob/living/simple_animal/hostile/asteroid/basilisk/GiveTarget(var/new_target) - if(..()) //we have a target - if(isliving(target)) - var/mob/living/L = target - if(L.bodytemperature > 261) - L.bodytemperature = 261 - visible_message("The [src.name]'s stare chills [L.name] to the bone!") - -/mob/living/simple_animal/hostile/asteroid/basilisk/ex_act(severity) - switch(severity) - if(1.0) - gib() - if(2.0) - adjustBruteLoss(140) - if(3.0) - adjustBruteLoss(110) - -/mob/living/simple_animal/hostile/asteroid/goliath - name = "goliath" - desc = "A massive beast that uses long tentacles to ensare its prey, threatening them is not advised under any conditions." - icon = 'icons/mob/animal.dmi' - icon_state = "Goliath" - icon_living = "Goliath" - icon_aggro = "Goliath_alert" - icon_dead = "Goliath_dead" - icon_gib = "syndicate_gib" - attack_sound = 'sound/weapons/punch4.ogg' - mouse_opacity = MOUSE_OPACITY_OPAQUE - move_to_delay = 40 - ranged = 1 - ranged_cooldown = 2 //By default, start the Goliath with his cooldown off so that people can run away quickly on first sight - ranged_cooldown_time = 120 - friendly = "wails at" - speak_emote = list("bellows") - vision_range = 4 - speed = 3 - maxHealth = 300 - health = 300 - harm_intent_damage = 1 //Only the manliest of men can kill a Goliath with only their fists. - obj_damage = 100 - melee_damage_lower = 25 - melee_damage_upper = 25 - attacktext = "pulverizes" - attack_sound = 'sound/weapons/punch1.ogg' - throw_message = "does nothing to the rocky hide of the" - aggro_vision_range = 9 - idle_vision_range = 5 - move_force = MOVE_FORCE_VERY_STRONG - move_resist = MOVE_FORCE_VERY_STRONG - pull_force = MOVE_FORCE_VERY_STRONG - var/pre_attack = 0 - loot = list(/obj/item/stack/sheet/animalhide/goliath_hide{layer = ABOVE_MOB_LAYER}) - -/mob/living/simple_animal/hostile/asteroid/goliath/Life() - ..() - handle_preattack() - -/mob/living/simple_animal/hostile/asteroid/goliath/proc/handle_preattack() - if(ranged_cooldown <= world.time + ranged_cooldown_time * 0.25 && !pre_attack) - pre_attack++ - if(!pre_attack || stat || AIStatus == AI_IDLE) - return - icon_state = "Goliath_preattack" - -/mob/living/simple_animal/hostile/asteroid/goliath/revive() - move_force = MOVE_FORCE_VERY_STRONG - move_resist = MOVE_FORCE_VERY_STRONG - pull_force = MOVE_FORCE_VERY_STRONG - ..() - -/mob/living/simple_animal/hostile/asteroid/goliath/OpenFire() - var/tturf = get_turf(target) - if(get_dist(src, target) <= 7)//Screen range check, so you can't get tentacle'd offscreen - visible_message("The [src.name] digs its tentacles under [target.name]!") - new /obj/effect/goliath_tentacle/original(tturf) - ranged_cooldown = world.time + ranged_cooldown_time - icon_state = icon_aggro - pre_attack = 0 - return - -/mob/living/simple_animal/hostile/asteroid/goliath/adjustHealth(damage) - ranged_cooldown-- - handle_preattack() - ..() - -/mob/living/simple_animal/hostile/asteroid/goliath/Aggro() - vision_range = aggro_vision_range - handle_preattack() - if(icon_state != icon_aggro) - icon_state = icon_aggro - return - -/obj/effect/goliath_tentacle/ - name = "Goliath tentacle" - icon = 'icons/mob/animal.dmi' - icon_state = "Goliath_tentacle" - var/latched = 0 - -/obj/effect/goliath_tentacle/New() - var/turftype = get_turf(src) - if(istype(turftype, /turf/simulated/mineral)) - var/turf/simulated/mineral/M = turftype - M.gets_drilled() - spawn(20) - Trip() - -/obj/effect/goliath_tentacle/original - -/obj/effect/goliath_tentacle/original/New() - for(var/obj/effect/goliath_tentacle/original/O in loc)//No more GG NO RE from 2+ goliaths simultaneously tentacling you - if(O != src) - qdel(src) - var/list/directions = cardinal.Copy() - var/counter - for(counter = 1, counter <= 3, counter++) - var/spawndir = pick(directions) - directions -= spawndir - var/turf/T = get_step(src,spawndir) - new /obj/effect/goliath_tentacle(T) - ..() - - -/obj/effect/goliath_tentacle/proc/Trip() - for(var/mob/living/M in src.loc) - M.Stun(5) - M.adjustBruteLoss(rand(10,15)) - latched = 1 - if(src && M) - visible_message("The [src.name] grabs hold of [M.name]!") - if(!latched) - qdel(src) - else - spawn(50) - qdel(src) diff --git a/code/modules/mob/living/simple_animal/hostile/russian.dm b/code/modules/mob/living/simple_animal/hostile/russian.dm index a4f7cce2f51..0c5f2647ae8 100644 --- a/code/modules/mob/living/simple_animal/hostile/russian.dm +++ b/code/modules/mob/living/simple_animal/hostile/russian.dm @@ -33,6 +33,7 @@ ranged = 1 retreat_distance = 5 minimum_distance = 5 + projectilesound = 'sound/weapons/gunshots/gunshot.ogg' casingtype = /obj/item/ammo_casing/a357 loot = list(/obj/effect/mob_spawn/human/corpse/russian/ranged, /obj/item/gun/projectile/revolver/mateba) diff --git a/code/modules/mob/living/simple_animal/hostile/syndicate.dm b/code/modules/mob/living/simple_animal/hostile/syndicate.dm index 546fa3ad50d..d95c18dd831 100644 --- a/code/modules/mob/living/simple_animal/hostile/syndicate.dm +++ b/code/modules/mob/living/simple_animal/hostile/syndicate.dm @@ -262,6 +262,7 @@ minimum_distance = 5 icon_state = "syndicateranged" icon_living = "syndicateranged" + projectilesound = 'sound/weapons/gunshots/gunshot.ogg' casingtype = /obj/item/ammo_casing/c45 loot = list(/obj/effect/mob_spawn/human/corpse/syndicatesoldier, /obj/item/gun/projectile/automatic/c20r) diff --git a/icons/mob/actions/actions_animal.dmi b/icons/mob/actions/actions_animal.dmi new file mode 100644 index 00000000000..e8f2d4ada83 Binary files /dev/null and b/icons/mob/actions/actions_animal.dmi differ