diff --git a/code/__DEFINES/cooldowns.dm b/code/__DEFINES/cooldowns.dm index a2210386ad5..e75ae1ccf89 100644 --- a/code/__DEFINES/cooldowns.dm +++ b/code/__DEFINES/cooldowns.dm @@ -55,6 +55,11 @@ // mob cooldowns #define COOLDOWN_YAWN_PROPAGATION "yawn_propagation_cooldown" +//Shared cooldowns for actions +#define MOB_SHARED_COOLDOWN_1 "mob_shared_cooldown_1" +#define MOB_SHARED_COOLDOWN_2 "mob_shared_cooldown_2" +#define MOB_SHARED_COOLDOWN_3 "mob_shared_cooldown_3" + //TIMER COOLDOWN MACROS #define COMSIG_CD_STOP(cd_index) "cooldown_[cd_index]" diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_abilities.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_abilities.dm new file mode 100644 index 00000000000..6af44ed10f6 --- /dev/null +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_abilities.dm @@ -0,0 +1,21 @@ +// Mob ability signals + +/// from base of /datum/action/cooldown/proc/PreActivate(): (datum/action/cooldown/activated) +#define COMSIG_ABILITY_STARTED "mob_ability_base_started" + #define COMPONENT_BLOCK_ABILITY_START (1<<0) +/// from base of /datum/action/cooldown/proc/PreActivate(): (datum/action/cooldown/finished) +#define COMSIG_ABILITY_FINISHED "mob_ability_base_finished" + +/// from base of /datum/action/cooldown/mob_cooldown/blood_warp/proc/blood_warp(): () +#define COMSIG_BLOOD_WARP "mob_ability_blood_warp" +/// from base of /datum/action/cooldown/mob_cooldown/charge/proc/do_charge(): () +#define COMSIG_STARTED_CHARGE "mob_ability_charge_started" +/// from base of /datum/action/cooldown/mob_cooldown/charge/proc/on_bump(): (atom/target) +#define COMSIG_BUMPED_CHARGE "mob_ability_charge_bumped" + #define COMPONENT_OVERRIDE_CHARGE_BUMP (1<<0) +/// from base of /datum/action/cooldown/mob_cooldown/charge/proc/do_charge(): () +#define COMSIG_FINISHED_CHARGE "mob_ability_charge_finished" +/// from base of /datum/action/cooldown/mob_cooldown/lava_swoop/proc/swoop_attack(): () +#define COMSIG_SWOOP_INVULNERABILITY_STARTED "mob_swoop_invulnerability_started" +/// from base of /datum/action/cooldown/mob_cooldown/lava_swoop/proc/swoop_attack(): () +#define COMSIG_LAVA_ARENA_FAILED "mob_lava_arena_failed" diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index ee4111562ce..bfb0e1ebcc3 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -405,7 +405,14 @@ // Bit mask of possible return values by can_defib that would result in a revivable patient #define DEFIB_REVIVABLE_STATES (DEFIB_FAIL_NO_HEART | DEFIB_FAIL_FAILING_HEART | DEFIB_FAIL_HUSK | DEFIB_FAIL_TISSUE_DAMAGE | DEFIB_FAIL_FAILING_BRAIN | DEFIB_POSSIBLE) -#define SLEEP_CHECK_DEATH(X) sleep(X); if(QDELETED(src) || stat == DEAD) return; +#define SLEEP_CHECK_DEATH(X, A) \ + sleep(X); \ + if(QDELETED(A)) return; \ + if(ismob(A)) { \ + var/mob/sleep_check_death_mob = A; \ + if(sleep_check_death_mob.stat == DEAD) return; \ + } + #define DOING_INTERACTION(user, interaction_key) (LAZYACCESS(user.do_afters, interaction_key)) #define DOING_INTERACTION_LIMIT(user, interaction_key, max_interaction_count) ((LAZYACCESS(user.do_afters, interaction_key) || 0) >= max_interaction_count) diff --git a/code/datums/action.dm b/code/datums/action.dm index 0ee300da23d..c25c46943c8 100644 --- a/code/datums/action.dm +++ b/code/datums/action.dm @@ -145,7 +145,7 @@ button.color = transparent_when_unavailable ? rgb(128,0,0,128) : rgb(128,0,0) else button.color = rgb(255,255,255,255) - return 1 + return TRUE /datum/action/proc/ApplyIcon(atom/movable/screen/movable/action_button/current_button, force = FALSE) if(icon_icon && button_icon_state && ((current_button.button_icon_state != button_icon_state) || force)) @@ -663,8 +663,16 @@ /datum/action/cooldown check_flags = NONE transparent_when_unavailable = FALSE + // The default cooldown applied when StartCooldown() is called var/cooldown_time = 0 + // The actual next time this ability can be used var/next_use_time = 0 + // Whether or not you want the cooldown for the ability to display in text form + var/text_cooldown = TRUE + // Setting for intercepting clicks before activating the ability + var/click_to_activate = FALSE + // Shares cooldowns with other cooldown abilities of the same value, not active if null + var/shared_cooldown /datum/action/cooldown/New() ..() @@ -675,25 +683,84 @@ button.maptext_height = 12 /datum/action/cooldown/IsAvailable() - return next_use_time <= world.time + return ..() && (next_use_time <= world.time) -/datum/action/cooldown/proc/StartCooldown() - next_use_time = world.time + cooldown_time - button.maptext = MAPTEXT("[round(cooldown_time/10, 0.1)]") +/// Starts a cooldown time to be shared with similar abilities, will use default cooldown time if an override is not specified +/datum/action/cooldown/proc/StartCooldown(override_cooldown_time) + if(shared_cooldown) + for(var/datum/action/cooldown/shared_ability in owner.actions - src) + if(shared_cooldown == shared_ability.shared_cooldown) + if(isnum(override_cooldown_time)) + shared_ability.StartCooldownSelf(override_cooldown_time) + else + shared_ability.StartCooldownSelf(cooldown_time) + StartCooldownSelf(override_cooldown_time) + +/// Starts a cooldown time for this ability only, will use default cooldown time if an override is not specified +/datum/action/cooldown/proc/StartCooldownSelf(override_cooldown_time) + if(isnum(override_cooldown_time)) + next_use_time = world.time + override_cooldown_time + else + next_use_time = world.time + cooldown_time UpdateButtonIcon() START_PROCESSING(SSfastprocess, src) -/datum/action/cooldown/process() +/datum/action/cooldown/Trigger(atom/target) + . = ..() + if(!.) + return if(!owner) + return FALSE + if(click_to_activate) + if(target) + // For automatic / mob handling + return InterceptClickOn(owner, null, target) + if(owner.click_intercept == src) + owner.click_intercept = null + else + owner.click_intercept = src + for(var/datum/action/cooldown/ability in owner.actions) + ability.UpdateButtonIcon() + return TRUE + return PreActivate(owner) + +/// Intercepts client owner clicks to activate the ability +/datum/action/cooldown/proc/InterceptClickOn(mob/living/caller, params, atom/target) + if(!IsAvailable()) + return FALSE + if(!target) + return FALSE + PreActivate(target) + caller.click_intercept = null + return TRUE + +/// For signal calling +/datum/action/cooldown/proc/PreActivate(atom/target) + if(SEND_SIGNAL(owner, COMSIG_ABILITY_STARTED, src) & COMPONENT_BLOCK_ABILITY_START) + return + . = Activate(target) + SEND_SIGNAL(owner, COMSIG_ABILITY_FINISHED, src) + +/// To be implemented by subtypes +/datum/action/cooldown/proc/Activate(atom/target) + return + +/datum/action/cooldown/UpdateButtonIcon(status_only = FALSE, force = FALSE) + . = ..() + var/time_left = max(next_use_time - world.time, 0) + if(button) + if(text_cooldown) + button.maptext = MAPTEXT("[round(time_left/10, 0.1)]") + if(!owner || time_left == 0) button.maptext = "" + if(IsAvailable() && owner.click_intercept == src) + button.color = COLOR_GREEN + +/datum/action/cooldown/process() + var/time_left = max(next_use_time - world.time, 0) + if(!owner || time_left == 0) STOP_PROCESSING(SSfastprocess, src) - var/timeleft = max(next_use_time - world.time, 0) - if(timeleft == 0) - button.maptext = "" - UpdateButtonIcon() - STOP_PROCESSING(SSfastprocess, src) - else - button.maptext = MAPTEXT("[round(timeleft/10, 0.1)]") + UpdateButtonIcon() /datum/action/cooldown/Grant(mob/M) ..() diff --git a/code/datums/actions/mobs/blood_warp.dm b/code/datums/actions/mobs/blood_warp.dm new file mode 100644 index 00000000000..c48a5598293 --- /dev/null +++ b/code/datums/actions/mobs/blood_warp.dm @@ -0,0 +1,72 @@ +/datum/action/cooldown/mob_cooldown/blood_warp + name = "Blood Warp" + icon_icon = 'icons/effects/blood.dmi' + button_icon_state = "floor1" + desc = "Allows you to teleport to blood at a clicked position." + cooldown_time = 0 + shared_cooldown = MOB_SHARED_COOLDOWN_1 + /// The range of turfs to try to jaunt to from around the target + var/pick_range = 5 + /// The range of turfs if a client is using this ability + var/client_pick_range = 0 + /// Whether or not to remove the inside of our radius from the possible pools to jaunt to + var/remove_inner_pools = TRUE + +/datum/action/cooldown/mob_cooldown/blood_warp/Activate(atom/target_atom) + StartCooldown(10 SECONDS) + blood_warp(target_atom) + StartCooldown() + +/datum/action/cooldown/mob_cooldown/blood_warp/proc/blood_warp(atom/target) + if(owner.Adjacent(target)) + return FALSE + var/list/can_jaunt = get_bloodcrawlable_pools(get_turf(owner), 1) + if(!can_jaunt.len) + return FALSE + + var/chosen_pick_range = get_pick_range() + var/list/pools = get_bloodcrawlable_pools(get_turf(target), chosen_pick_range) + if(remove_inner_pools) + var/list/pools_to_remove = get_bloodcrawlable_pools(get_turf(target), chosen_pick_range - 1) + pools -= pools_to_remove + if(!pools.len) + return FALSE + + var/obj/effect/temp_visual/decoy/DA = new /obj/effect/temp_visual/decoy(owner.loc, owner) + 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(0.3 SECONDS, owner) + qdel(DA) + + var/obj/effect/decal/cleanable/blood/found_bloodpool + pools = get_bloodcrawlable_pools(get_turf(target), chosen_pick_range) + if(remove_inner_pools) + var/list/pools_to_remove = get_bloodcrawlable_pools(get_turf(target), chosen_pick_range - 1) + pools -= pools_to_remove + if(pools.len) + shuffle_inplace(pools) + found_bloodpool = pick(pools) + if(found_bloodpool) + owner.visible_message("[owner] sinks into the blood...") + playsound(get_turf(owner), 'sound/magic/enter_blood.ogg', 100, TRUE, -1) + owner.forceMove(get_turf(found_bloodpool)) + playsound(get_turf(owner), 'sound/magic/exit_blood.ogg', 100, TRUE, -1) + owner.visible_message("And springs back out!") + SEND_SIGNAL(owner, COMSIG_BLOOD_WARP) + return TRUE + return FALSE + +/datum/action/cooldown/mob_cooldown/blood_warp/proc/get_pick_range() + if(owner.client) + return client_pick_range + return pick_range + +/proc/get_bloodcrawlable_pools(turf/T, range) + if(range < 0) + return list() + . = list() + for(var/obj/effect/decal/cleanable/nearby in view(T, range)) + if(nearby.can_bloodcrawl_in()) + . += nearby diff --git a/code/datums/actions/mobs/charge.dm b/code/datums/actions/mobs/charge.dm new file mode 100644 index 00000000000..58bb762c575 --- /dev/null +++ b/code/datums/actions/mobs/charge.dm @@ -0,0 +1,277 @@ +/datum/action/cooldown/mob_cooldown/charge + name = "Charge" + icon_icon = 'icons/mob/actions/actions_items.dmi' + button_icon_state = "sniper_zoom" + desc = "Allows you to charge at a chosen position." + cooldown_time = 1.5 SECONDS + /// Delay before the charge actually occurs + var/charge_delay = 0.3 SECONDS + /// The amount of turfs we move past the target + var/charge_past = 2 + /// The maximum distance we can charge + var/charge_distance = 50 + /// The sleep time before moving in deciseconds while charging + var/charge_speed = 0.5 + /// The damage the charger does when bumping into something + var/charge_damage = 30 + /// If we destroy objects while charging + var/destroy_objects = TRUE + /// Associative boolean list of chargers that are currently charging + var/list/charging = list() + /// Associative list of chargers and their hit targets + var/list/already_hit = list() + /// Associative direction list of chargers that lets our move signal know how we are supposed to move + var/list/next_move_allowed = list() + +/datum/action/cooldown/mob_cooldown/charge/New(Target, delay, past, distance, speed, damage, destroy) + . = ..() + if(delay) + charge_delay = delay + if(past) + charge_past = past + if(distance) + charge_distance = distance + if(speed) + charge_speed = speed + if(damage) + charge_damage = damage + if(destroy) + destroy_objects = destroy + +/datum/action/cooldown/mob_cooldown/charge/Activate(atom/target_atom) + // start pre-cooldown so that the ability can't come up while the charge is happening + StartCooldown(10 SECONDS) + charge_sequence(owner, target_atom, charge_delay, charge_past) + StartCooldown() + +/datum/action/cooldown/mob_cooldown/charge/proc/charge_sequence(atom/movable/charger, atom/target_atom, delay, past) + do_charge(owner, target_atom, charge_delay, charge_past) + +/datum/action/cooldown/mob_cooldown/charge/proc/do_charge(atom/movable/charger, atom/target_atom, delay, past) + if(!target_atom || target_atom == owner) + return + var/chargeturf = get_turf(target_atom) + if(!chargeturf) + return + charger.setDir(get_dir(charger, target_atom)) + var/turf/T = get_ranged_target_turf(chargeturf, charger.dir, past) + if(!T) + return + SEND_SIGNAL(owner, COMSIG_STARTED_CHARGE) + RegisterSignal(charger, COMSIG_MOVABLE_BUMP, .proc/on_bump) + RegisterSignal(charger, COMSIG_MOVABLE_PRE_MOVE, .proc/on_move) + RegisterSignal(charger, COMSIG_MOVABLE_MOVED, .proc/on_moved) + charging[charger] = TRUE + already_hit[charger] = list() + DestroySurroundings(charger) + charger.setDir(get_dir(charger, target_atom)) + do_charge_indicator(charger, T) + SLEEP_CHECK_DEATH(delay, charger) + var/distance = min(get_dist(charger, T), charge_distance) + for(var/i in 1 to distance) + // Prevents movement from the user during the charge + SLEEP_CHECK_DEATH(charge_speed, charger) + next_move_allowed[charger] = get_dir(charger, T) + step_towards(charger, T) + next_move_allowed.Remove(charger) + UnregisterSignal(charger, COMSIG_MOVABLE_BUMP) + UnregisterSignal(charger, COMSIG_MOVABLE_PRE_MOVE) + UnregisterSignal(charger, COMSIG_MOVABLE_MOVED) + charging.Remove(charger) + already_hit.Remove(charger) + SEND_SIGNAL(owner, COMSIG_FINISHED_CHARGE) + return TRUE + +/datum/action/cooldown/mob_cooldown/charge/proc/do_charge_indicator(atom/charger, atom/charge_target) + var/turf/target_turf = get_turf(charge_target) + if(!target_turf) + return + new /obj/effect/temp_visual/dragon_swoop/bubblegum(target_turf) + var/obj/effect/temp_visual/decoy/D = new /obj/effect/temp_visual/decoy(charger.loc, charger) + animate(D, alpha = 0, color = "#FF0000", transform = matrix()*2, time = 3) + +/datum/action/cooldown/mob_cooldown/charge/proc/on_move(atom/source, atom/new_loc) + SIGNAL_HANDLER + var/expected_dir = next_move_allowed[source] + if(!expected_dir) + return COMPONENT_MOVABLE_BLOCK_PRE_MOVE + var/real_dir = get_dir(source, new_loc) + if(!(expected_dir & real_dir)) + return COMPONENT_MOVABLE_BLOCK_PRE_MOVE + // Disable the flag for the direction we moved (this is so diagonal movements can be fully completed) + next_move_allowed[source] = expected_dir & ~real_dir + if(charging[source]) + new /obj/effect/temp_visual/decoy/fading(source.loc, source) + INVOKE_ASYNC(src, .proc/DestroySurroundings, source) + +/datum/action/cooldown/mob_cooldown/charge/proc/on_moved(atom/source) + SIGNAL_HANDLER + if(charging[source]) + playsound(source, 'sound/effects/meteorimpact.ogg', 200, TRUE, 2, TRUE) + INVOKE_ASYNC(src, .proc/DestroySurroundings, source) + +/datum/action/cooldown/mob_cooldown/charge/proc/DestroySurroundings(atom/movable/charger) + if(!destroy_objects) + return + if(!isanimal(charger)) + return + for(var/dir in GLOB.cardinals) + var/turf/T = get_step(charger, dir) + if(QDELETED(T)) + continue + if(T.Adjacent(charger)) + if(iswallturf(T) || ismineralturf(T)) + if(!isanimal(charger)) + SSexplosions.medturf += T + continue + T.attack_animal(charger) + continue + for(var/obj/O in T.contents) + if(!O.Adjacent(charger)) + continue + if((ismachinery(O) || isstructure(O)) && O.density && !O.IsObscured()) + if(!isanimal(charger)) + SSexplosions.med_mov_atom += target + break + O.attack_animal(charger) + break + +/datum/action/cooldown/mob_cooldown/charge/proc/on_bump(atom/movable/source, atom/target) + SIGNAL_HANDLER + if(SEND_SIGNAL(owner, COMSIG_BUMPED_CHARGE, target) & COMPONENT_OVERRIDE_CHARGE_BUMP) + return + if(charging[source]) + if(owner == target) + return + if(isturf(target) || isobj(target) && target.density) + if(isobj(target)) + SSexplosions.med_mov_atom += target + else + SSexplosions.medturf += target + INVOKE_ASYNC(src, .proc/DestroySurroundings, source) + hit_target(source, target, charge_damage) + +/datum/action/cooldown/mob_cooldown/charge/proc/hit_target(atom/movable/source, atom/target, damage_dealt) + var/list/hit_things = already_hit[source] + if(!isliving(target) || hit_things.Find(target)) + return + hit_things.Add(target) + var/mob/living/living_target = target + living_target.visible_message("[source] slams into [living_target]!", "[source] tramples you into the ground!") + source.forceMove(get_turf(living_target)) + living_target.apply_damage(damage_dealt, BRUTE, wound_bonus = CANT_WOUND) + playsound(get_turf(living_target), 'sound/effects/meteorimpact.ogg', 100, TRUE) + shake_camera(living_target, 4, 3) + shake_camera(source, 2, 3) + +/datum/action/cooldown/mob_cooldown/charge/basic_charge + name = "Basic Charge" + cooldown_time = 6 SECONDS + charge_distance = 4 + +/datum/action/cooldown/mob_cooldown/charge/basic_charge/do_charge_indicator(atom/charger, atom/charge_target) + charger.Shake(15, 15, 1 SECONDS) + +/datum/action/cooldown/mob_cooldown/charge/basic_charge/hit_target(atom/movable/source, atom/target, damage_dealt) + if(!isliving(target)) + if(target.density && !target.CanPass(source, get_dir(target, source))) + source.visible_message(span_danger("[source] smashes into [target]!")) + if(isliving(source)) + var/mob/living/living_source = source + living_source.Stun(6, ignore_canstun = TRUE) + return + var/mob/living/living_target = target + var/blocked = FALSE + if(ishuman(living_target)) + var/mob/living/carbon/human/human_target = living_target + if(human_target.check_shields(source, 0, "the [source.name]", attack_type = LEAP_ATTACK)) + blocked = TRUE + if(!blocked) + living_target.visible_message(span_danger("[source] charges on [living_target]!"), span_userdanger("[source] charges into you!")) + living_target.Knockdown(6) + else + if(isliving(source)) + var/mob/living/living_source = source + living_source.Stun(6, ignore_canstun = TRUE) + +/datum/action/cooldown/mob_cooldown/charge/triple_charge + name = "Triple Charge" + desc = "Allows you to charge three times at a chosen position." + charge_delay = 0.6 SECONDS + +/datum/action/cooldown/mob_cooldown/charge/triple_charge/charge_sequence(atom/movable/charger, atom/target_atom, delay, past) + for(var/i in 0 to 2) + do_charge(owner, target_atom, charge_delay - 2 * i, charge_past) + +/datum/action/cooldown/mob_cooldown/charge/hallucination_charge + name = "Hallucination Charge" + icon_icon = 'icons/effects/bubblegum.dmi' + button_icon_state = "smack ya one" + desc = "Allows you to create hallucinations that charge around your target." + cooldown_time = 2 SECONDS + charge_delay = 0.6 SECONDS + /// The damage the hallucinations in our charge do + var/hallucination_damage = 15 + /// Check to see if we are enraged, enraged ability does more + var/enraged = FALSE + /// Check to see if we should spawn blood + var/spawn_blood = FALSE + +/datum/action/cooldown/mob_cooldown/charge/hallucination_charge/charge_sequence(atom/movable/charger, atom/target_atom, delay, past) + if(!enraged) + hallucination_charge(target_atom, 6, 8, 0, 6, TRUE) + StartCooldown(cooldown_time * 0.5) + return + for(var/i in 0 to 2) + hallucination_charge(target_atom, 4, 9 - 2 * i, 0, 4, TRUE) + for(var/i in 0 to 2) + do_charge(owner, target_atom, charge_delay - 2 * i, charge_past) + +/datum/action/cooldown/mob_cooldown/charge/hallucination_charge/do_charge(atom/movable/charger, atom/target_atom, delay, past) + . = ..() + if(charger != owner) + qdel(charger) + +/datum/action/cooldown/mob_cooldown/charge/hallucination_charge/proc/hallucination_charge(atom/target_atom, clone_amount, delay, past, radius, use_self) + var/starting_angle = rand(1, 360) + if(!radius) + return + var/angle_difference = 360 / clone_amount + var/self_placed = FALSE + for(var/i = 1 to clone_amount) + var/angle = (starting_angle + angle_difference * i) + var/turf/place = locate(target_atom.x + cos(angle) * radius, target_atom.y + sin(angle) * radius, target_atom.z) + if(!place) + continue + if(use_self && !self_placed) + owner.forceMove(place) + self_placed = TRUE + continue + var/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/our_clone = new /mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination(place) + our_clone.appearance = owner.appearance + our_clone.name = "[owner]'s hallucination" + our_clone.alpha = 127.5 + our_clone.move_through_mob = owner + our_clone.spawn_blood = spawn_blood + INVOKE_ASYNC(src, .proc/do_charge, our_clone, target_atom, delay, past) + if(use_self) + do_charge(owner, target_atom, delay, past) + +/datum/action/cooldown/mob_cooldown/charge/hallucination_charge/hit_target(atom/movable/source, atom/A, damage_dealt) + var/applied_damage = charge_damage + if(source != owner) + applied_damage = hallucination_damage + . = ..(source, A, applied_damage) + +/datum/action/cooldown/mob_cooldown/charge/hallucination_charge/hallucination_surround + name = "Surround Target" + icon_icon = 'icons/turf/walls/wall.dmi' + button_icon_state = "wall-0" + desc = "Allows you to create hallucinations that charge around your target." + charge_delay = 0.6 SECONDS + charge_past = 2 + +/datum/action/cooldown/mob_cooldown/charge/hallucination_charge/hallucination_surround/charge_sequence(atom/movable/charger, atom/target_atom, delay, past) + for(var/i in 0 to 4) + hallucination_charge(target_atom, 2, 8, 2, 2, FALSE) + do_charge(owner, target_atom, charge_delay, charge_past) diff --git a/code/datums/actions/mobs/dash.dm b/code/datums/actions/mobs/dash.dm new file mode 100644 index 00000000000..a7196ea866c --- /dev/null +++ b/code/datums/actions/mobs/dash.dm @@ -0,0 +1,62 @@ +/datum/action/cooldown/mob_cooldown/dash + name = "Dash" + icon_icon = 'icons/mob/actions/actions_items.dmi' + button_icon_state = "sniper_zoom" + desc = "Allows you to dash towards a position." + cooldown_time = 1.5 SECONDS + /// The range of the dash + var/dash_range = 4 + /// The distance you will be from the target after you dash + var/pick_range = 5 + /// The pick range if a client is using this ability + var/client_pick_range = 0 + +/datum/action/cooldown/mob_cooldown/dash/Activate(atom/target_atom) + StartCooldown(10 SECONDS) + dash_to(target_atom) + StartCooldown() + +/datum/action/cooldown/mob_cooldown/dash/proc/dash_to(atom/dash_target) + var/list/accessable_turfs = list() + var/self_dist_to_target = 0 + var/turf/own_turf = get_turf(owner) + if(!QDELETED(dash_target)) + self_dist_to_target += get_dist(dash_target, own_turf) + for(var/turf/open/check_turf in RANGE_TURFS(dash_range, own_turf)) + var/turf_dist_to_target = 0 + if(!QDELETED(dash_target)) + turf_dist_to_target += get_dist(dash_target, check_turf) + if(get_dist(owner, check_turf) >= dash_range && turf_dist_to_target <= self_dist_to_target && !islava(check_turf) && !ischasm(check_turf)) + var/valid = TRUE + for(var/turf/T in get_line(own_turf, check_turf)) + if(T.is_blocked_turf(TRUE)) + valid = FALSE + continue + if(valid) + accessable_turfs[check_turf] = turf_dist_to_target + var/turf/target_turf + if(!QDELETED(dash_target)) + var/closest_dist = dash_range + for(var/t in accessable_turfs) + if(accessable_turfs[t] < closest_dist) + closest_dist = accessable_turfs[t] + for(var/t in accessable_turfs) + if(accessable_turfs[t] != closest_dist) + accessable_turfs -= t + if(!LAZYLEN(accessable_turfs)) + return + target_turf = pick(accessable_turfs) + var/turf/step_back_turf = get_step(target_turf, get_cardinal_dir(target_turf, own_turf)) + 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/fading/halfsecond/D = new (own_turf, owner) + owner.forceMove(step_back_turf) + playsound(own_turf, 'sound/weapons/punchmiss.ogg', 40, TRUE, -1) + owner.alpha = 0 + animate(owner, alpha = 255, time = 5) + SLEEP_CHECK_DEATH(0.2 SECONDS, owner) + D.forceMove(step_forward_turf) + owner.forceMove(target_turf) + playsound(target_turf, 'sound/weapons/punchmiss.ogg', 40, TRUE, -1) + SLEEP_CHECK_DEATH(0.1 SECONDS, owner) diff --git a/code/datums/actions/mobs/fire_breath.dm b/code/datums/actions/mobs/fire_breath.dm new file mode 100644 index 00000000000..f3df8f86df1 --- /dev/null +++ b/code/datums/actions/mobs/fire_breath.dm @@ -0,0 +1,63 @@ +/datum/action/cooldown/mob_cooldown/fire_breath + name = "Fire Breath" + icon_icon = 'icons/obj/wizard.dmi' + button_icon_state = "fireball" + desc = "Allows you to shoot fire towards a target." + cooldown_time = 3 SECONDS + /// The range of the fire + var/fire_range = 15 + /// The sound played when you use this ability + var/fire_sound = 'sound/magic/fireball.ogg' + /// If the fire should be icey fire + var/ice_breath = FALSE + +/datum/action/cooldown/mob_cooldown/fire_breath/Activate(atom/target_atom) + StartCooldown(10 SECONDS) + attack_sequence(target_atom) + StartCooldown() + +/datum/action/cooldown/mob_cooldown/fire_breath/proc/attack_sequence(atom/target) + playsound(owner.loc, fire_sound, 200, TRUE) + fire_line(target, 0) + +/datum/action/cooldown/mob_cooldown/fire_breath/proc/fire_line(atom/target, offset) + SLEEP_CHECK_DEATH(0, owner) + var/list/turfs = line_target(offset, fire_range, target) + dragon_fire_line(owner, turfs, ice_breath) + +/datum/action/cooldown/mob_cooldown/fire_breath/proc/line_target(offset, range, atom/target) + if(!target) + return + var/turf/T = get_ranged_target_turf_direct(owner, target, range, offset) + return (get_line(owner, T) - get_turf(owner)) + +/datum/action/cooldown/mob_cooldown/fire_breath/cone + name = "Fire Cone" + desc = "Allows you to shoot fire towards a target with surrounding lines of fire." + /// The angles relative to the target that shoot lines of fire + var/list/angles = list(-40, 0, 40) + +/datum/action/cooldown/mob_cooldown/fire_breath/cone/attack_sequence(atom/target) + playsound(owner.loc, fire_sound, 200, TRUE) + for(var/offset in angles) + INVOKE_ASYNC(src, .proc/fire_line, target, offset) + +/datum/action/cooldown/mob_cooldown/fire_breath/mass_fire + name = "Mass Fire" + icon_icon = 'icons/effects/fire.dmi' + button_icon_state = "1" + desc = "Allows you to shoot fire in all directions." + cooldown_time = 3 SECONDS + +/datum/action/cooldown/mob_cooldown/fire_breath/mass_fire/attack_sequence(atom/target) + shoot_mass_fire(target, 12, 2.5 SECONDS, 3) + +/datum/action/cooldown/mob_cooldown/fire_breath/mass_fire/proc/shoot_mass_fire(atom/target, spiral_count, delay_time, times) + SLEEP_CHECK_DEATH(0, owner) + for(var/i = 1 to times) + playsound(owner.loc, fire_sound, 200, TRUE) + var/increment = 360 / spiral_count + for(var/j = 1 to spiral_count) + INVOKE_ASYNC(src, .proc/fire_line, target, j * increment + i * increment / 2) + SLEEP_CHECK_DEATH(delay_time, owner) + diff --git a/code/datums/actions/mobs/lava_swoop.dm b/code/datums/actions/mobs/lava_swoop.dm new file mode 100644 index 00000000000..66bab829400 --- /dev/null +++ b/code/datums/actions/mobs/lava_swoop.dm @@ -0,0 +1,240 @@ +#define SWOOP_HEIGHT 270 //how high up swoops go, in pixels +#define SWOOP_DIRECTION_CHANGE_RANGE 5 //the range our x has to be within to not change the direction we slam from + +/datum/action/cooldown/mob_cooldown/lava_swoop + name = "Lava Swoop" + icon_icon = 'icons/effects/effects.dmi' + button_icon_state = "lavastaff_warn" + desc = "Allows you to chase a target while raining lava down." + cooldown_time = 4 SECONDS + /// Check to see if we are enraged + var/enraged = FALSE + /// Check if we are currently swooping + var/swooping = FALSE + +/datum/action/cooldown/mob_cooldown/lava_swoop/Grant(mob/M) + . = ..() + ADD_TRAIT(M, TRAIT_LAVA_IMMUNE, src) + ADD_TRAIT(M, TRAIT_NOFIRE, src) + +/datum/action/cooldown/mob_cooldown/lava_swoop/Remove(mob/M) + . = ..() + REMOVE_TRAIT(M, TRAIT_LAVA_IMMUNE, src) + REMOVE_TRAIT(M, TRAIT_NOFIRE, src) + +/datum/action/cooldown/mob_cooldown/lava_swoop/Activate(atom/target_atom) + StartCooldown(30 SECONDS) + attack_sequence(target_atom) + StartCooldown() + +/datum/action/cooldown/mob_cooldown/lava_swoop/proc/attack_sequence(atom/target) + if(enraged) + swoop_attack(target, TRUE) + return + INVOKE_ASYNC(src, .proc/lava_pools, target) + swoop_attack(target) + +/datum/action/cooldown/mob_cooldown/lava_swoop/proc/swoop_attack(atom/target, lava_arena = FALSE) + if(swooping || !target) + return + // stop swooped target movement + //RegisterSignal(charger, COMSIG_MOVABLE_PRE_MOVE, .proc/on_move) + swooping = TRUE + owner.set_density(FALSE) + owner.visible_message(span_boldwarning("[owner] swoops up high!")) + + var/negative + var/initial_x = owner.x + if(target.x < initial_x) //if the target's x is lower than ours, swoop to the left + negative = TRUE + else if(target.x > initial_x) + negative = FALSE + else if(target.x == initial_x) //if their x is the same, pick a direction + negative = prob(50) + var/obj/effect/temp_visual/dragon_flight/F = new /obj/effect/temp_visual/dragon_flight(owner.loc, negative) + + negative = !negative //invert it for the swoop down later + + var/oldtransform = owner.transform + owner.alpha = 255 + animate(owner, alpha = 204, transform = matrix()*0.9, time = 3, easing = BOUNCE_EASING) + for(var/i in 1 to 3) + sleep(1) + if(QDELETED(owner) || owner.stat == DEAD) //we got hit and died, rip us + qdel(F) + if(owner.stat == DEAD) + swooping = FALSE + animate(owner, alpha = 255, transform = oldtransform, time = 0, flags = ANIMATION_END_NOW) //reset immediately + return + animate(owner, alpha = 100, transform = matrix()*0.7, time = 7) + owner.status_flags |= GODMODE + SEND_SIGNAL(owner, COMSIG_SWOOP_INVULNERABILITY_STARTED) + + owner.mouse_opacity = MOUSE_OPACITY_TRANSPARENT + SLEEP_CHECK_DEATH(7, owner) + + while(target && owner.loc != get_turf(target)) + owner.forceMove(get_step(owner, get_dir(owner, target))) + SLEEP_CHECK_DEATH(0.5, owner) + + // Ash drake flies onto its target and rains fire down upon them + var/descentTime = 10 + var/lava_success = TRUE + if(lava_arena) + lava_success = lava_arena(target) + + + //ensure swoop direction continuity. + if(negative) + if(ISINRANGE(owner.x, initial_x + 1, initial_x + SWOOP_DIRECTION_CHANGE_RANGE)) + negative = FALSE + else + if(ISINRANGE(owner.x, initial_x - SWOOP_DIRECTION_CHANGE_RANGE, initial_x - 1)) + negative = TRUE + new /obj/effect/temp_visual/dragon_flight/end(owner.loc, negative) + new /obj/effect/temp_visual/dragon_swoop(owner.loc) + animate(owner, alpha = 255, transform = oldtransform, descentTime) + SLEEP_CHECK_DEATH(descentTime, owner) + owner.mouse_opacity = initial(owner.mouse_opacity) + playsound(owner.loc, 'sound/effects/meteorimpact.ogg', 200, TRUE) + for(var/mob/living/L in orange(1, owner) - owner) + if(L.stat) + owner.visible_message(span_warning("[owner] slams down on [L], crushing [L.p_them()]!")) + L.gib() + else + L.adjustBruteLoss(75) + if(L && !QDELETED(L)) // Some mobs are deleted on death + var/throw_dir = get_dir(owner, L) + if(L.loc == owner.loc) + throw_dir = pick(GLOB.alldirs) + var/throwtarget = get_edge_target_turf(owner, throw_dir) + L.throw_at(throwtarget, 3) + owner.visible_message(span_warning("[L] is thrown clear of [owner]!")) + for(var/obj/vehicle/sealed/mecha/M in orange(1, owner)) + M.take_damage(75, BRUTE, MELEE, 1) + + for(var/mob/M in range(7, owner)) + shake_camera(M, 15, 1) + + owner.set_density(TRUE) + SLEEP_CHECK_DEATH(1, owner) + swooping = FALSE + if(!lava_success) + SEND_SIGNAL(owner, COMSIG_LAVA_ARENA_FAILED) + owner.status_flags &= ~GODMODE + +/datum/action/cooldown/mob_cooldown/lava_swoop/proc/lava_pools(atom/target, amount = 30, delay = 0.8) + if(!target) + return + target.visible_message(span_boldwarning("Lava starts to pool up around you!")) + + while(amount > 0) + if(QDELETED(target)) + break + var/turf/TT = get_turf(target) + var/turf/T = pick(RANGE_TURFS(1,TT)) + var/obj/effect/temp_visual/lava_warning/LW = new /obj/effect/temp_visual/lava_warning(T, 60) // longer reset time for the lava + LW.owner = owner + amount-- + SLEEP_CHECK_DEATH(delay, owner) + +/datum/action/cooldown/mob_cooldown/lava_swoop/proc/lava_arena(atom/target) + if(!target || !isliving(target)) + return + target.visible_message(span_boldwarning("[owner] encases you in an arena of fire!")) + var/amount = 3 + var/turf/center = get_turf(owner) + var/list/walled = RANGE_TURFS(3, center) - RANGE_TURFS(2, center) + var/list/drakewalls = list() + for(var/turf/T in walled) + drakewalls += new /obj/effect/temp_visual/drakewall(T) // no people with lava immunity can just run away from the attack for free + var/list/indestructible_turfs = list() + for(var/turf/T in RANGE_TURFS(2, center)) + if(istype(T, /turf/open/indestructible)) + continue + if(!istype(T, /turf/closed/indestructible)) + T.ChangeTurf(/turf/open/floor/plating/asteroid/basalt/lava_land_surface, flags = CHANGETURF_INHERIT_AIR) + else + indestructible_turfs += T + SLEEP_CHECK_DEATH(1 SECONDS, owner) // give them a bit of time to realize what attack is actually happening + + var/list/turfs = RANGE_TURFS(2, center) + var/list/mobs_with_clients = list() + while(amount > 0) + var/list/empty = indestructible_turfs.Copy() // can't place safe turfs on turfs that weren't changed to be open + var/any_attack = FALSE + for(var/turf/T in turfs) + for(var/mob/living/L in T.contents) + if(L.client) + empty += pick(((RANGE_TURFS(2, L) - RANGE_TURFS(1, L)) & turfs) - empty) // picks a turf within 2 of the creature not outside or in the shield + any_attack = TRUE + mobs_with_clients |= L + for(var/obj/vehicle/sealed/mecha/M in T.contents) + empty += pick(((RANGE_TURFS(2, M) - RANGE_TURFS(1, M)) & turfs) - empty) + any_attack = TRUE + if(!any_attack) // nothing to attack in the arena, time for enraged attack if we still have a cliented target. + for(var/obj/effect/temp_visual/drakewall/D in drakewalls) + qdel(D) + for(var/a in mobs_with_clients) + var/mob/living/L = a + if(!QDELETED(L) && L.client) + return FALSE + return TRUE + for(var/turf/T in turfs) + if(!(T in empty)) + new /obj/effect/temp_visual/lava_warning(T) + else if(!istype(T, /turf/closed/indestructible)) + new /obj/effect/temp_visual/lava_safe(T) + amount-- + SLEEP_CHECK_DEATH(2.4 SECONDS, owner) + return TRUE // attack finished completely + +/obj/effect/temp_visual/dragon_swoop + name = "certain death" + desc = "Don't just stand there, move!" + icon = 'icons/effects/96x96.dmi' + icon_state = "landing" + layer = BELOW_MOB_LAYER + pixel_x = -32 + pixel_y = -32 + color = "#FF0000" + duration = 10 + +/obj/effect/temp_visual/dragon_flight + icon = 'icons/mob/lavaland/64x64megafauna.dmi' + icon_state = "dragon" + layer = ABOVE_ALL_MOB_LAYER + pixel_x = -16 + duration = 10 + randomdir = FALSE + +/obj/effect/temp_visual/dragon_flight/Initialize(mapload, negative) + . = ..() + INVOKE_ASYNC(src, .proc/flight, negative) + +/obj/effect/temp_visual/dragon_flight/proc/flight(negative) + if(negative) + animate(src, pixel_x = -SWOOP_HEIGHT*0.1, pixel_z = SWOOP_HEIGHT*0.15, time = 3, easing = BOUNCE_EASING) + else + animate(src, pixel_x = SWOOP_HEIGHT*0.1, pixel_z = SWOOP_HEIGHT*0.15, time = 3, easing = BOUNCE_EASING) + sleep(3) + icon_state = "swoop" + if(negative) + animate(src, pixel_x = -SWOOP_HEIGHT, pixel_z = SWOOP_HEIGHT, time = 7) + else + animate(src, pixel_x = SWOOP_HEIGHT, pixel_z = SWOOP_HEIGHT, time = 7) + +/obj/effect/temp_visual/dragon_flight/end + pixel_x = SWOOP_HEIGHT + pixel_z = SWOOP_HEIGHT + duration = 10 + +/obj/effect/temp_visual/dragon_flight/end/flight(negative) + if(negative) + pixel_x = -SWOOP_HEIGHT + animate(src, pixel_x = -16, pixel_z = 0, time = 5) + else + animate(src, pixel_x = -16, pixel_z = 0, time = 5) + +#undef SWOOP_HEIGHT +#undef SWOOP_DIRECTION_CHANGE_RANGE diff --git a/code/datums/actions/mobs/meteors.dm b/code/datums/actions/mobs/meteors.dm new file mode 100644 index 00000000000..02da70c7b5e --- /dev/null +++ b/code/datums/actions/mobs/meteors.dm @@ -0,0 +1,21 @@ +/datum/action/cooldown/mob_cooldown/meteors + name = "Meteors" + icon_icon = 'icons/mob/actions/actions_items.dmi' + button_icon_state = "sniper_zoom" + desc = "Allows you to rain meteors down around yourself." + cooldown_time = 3 SECONDS + shared_cooldown = MOB_SHARED_COOLDOWN_1 + +/datum/action/cooldown/mob_cooldown/meteors/Activate(atom/target_atom) + StartCooldown(10 SECONDS) + create_meteors(target_atom) + StartCooldown() + +/datum/action/cooldown/mob_cooldown/meteors/proc/create_meteors(atom/target) + if(!target) + return + target.visible_message(span_boldwarning("Fire rains from the sky!")) + var/turf/targetturf = get_turf(target) + for(var/turf/turf as anything in RANGE_TURFS(9,targetturf)) + if(prob(11)) + new /obj/effect/temp_visual/target(turf) diff --git a/code/datums/actions/mobs/mobcooldown.dm b/code/datums/actions/mobs/mobcooldown.dm new file mode 100644 index 00000000000..3e4add136a1 --- /dev/null +++ b/code/datums/actions/mobs/mobcooldown.dm @@ -0,0 +1,10 @@ +/datum/action/cooldown/mob_cooldown + name = "Standard Mob Cooldown Ability" + icon_icon = 'icons/mob/actions/actions_items.dmi' + button_icon_state = "sniper_zoom" + desc = "Click this ability to attack." + check_flags = AB_CHECK_CONSCIOUS + cooldown_time = 1.5 SECONDS + text_cooldown = TRUE + click_to_activate = TRUE + shared_cooldown = MOB_SHARED_COOLDOWN_1 diff --git a/code/datums/actions/mobs/projectileattack.dm b/code/datums/actions/mobs/projectileattack.dm new file mode 100644 index 00000000000..dba06851d30 --- /dev/null +++ b/code/datums/actions/mobs/projectileattack.dm @@ -0,0 +1,252 @@ +/datum/action/cooldown/mob_cooldown/projectile_attack + name = "Projectile Attack" + icon_icon = 'icons/mob/actions/actions_items.dmi' + button_icon_state = "sniper_zoom" + desc = "Fires a set of projectiles at a selected target." + cooldown_time = 1.5 SECONDS + /// The type of the projectile to be fired + var/projectile_type + /// The sound played when a projectile is fired + var/projectile_sound + /// If the projectile should home in on its target + var/has_homing = FALSE + /// The turning speed if there is homing + var/homing_turn_speed = 30 + /// The variance in the projectiles direction + var/default_projectile_spread = 0 + /// The multiplier to the projectiles speed (a value of 2 makes it twice as slow, 0.5 makes it twice as fast) + var/projectile_speed_multiplier = 1 + +/datum/action/cooldown/mob_cooldown/projectile_attack/New(Target, projectile, homing, spread) + . = ..() + if(projectile) + projectile_type = projectile + if(homing) + has_homing = homing + if(spread) + default_projectile_spread = spread + +/datum/action/cooldown/mob_cooldown/projectile_attack/Activate(atom/target_atom) + StartCooldown(100) + attack_sequence(owner, target_atom) + StartCooldown() + +/datum/action/cooldown/mob_cooldown/projectile_attack/proc/attack_sequence(mob/living/firer, atom/target) + shoot_projectile(firer, target, null, firer, rand(-default_projectile_spread, default_projectile_spread), null) + +/datum/action/cooldown/mob_cooldown/projectile_attack/proc/shoot_projectile(atom/origin, atom/target, set_angle, mob/firer, projectile_spread, speed_multiplier, override_projectile_type, override_homing) + var/turf/startloc = get_turf(origin) + var/turf/endloc = get_turf(target) + if(!startloc || !endloc) + return + var/obj/projectile/our_projectile + if(override_projectile_type) + our_projectile = new override_projectile_type(startloc) + else + our_projectile = new projectile_type(startloc) + if(!isnum(speed_multiplier)) + speed_multiplier = projectile_speed_multiplier + our_projectile.speed *= speed_multiplier + our_projectile.preparePixelProjectile(endloc, startloc, null, projectile_spread) + our_projectile.firer = firer + if(target) + our_projectile.original = target + if(override_homing == null && has_homing || override_homing) + our_projectile.homing_turn_speed = homing_turn_speed + our_projectile.set_homing_target(target) + if(isnum(set_angle)) + our_projectile.fire(set_angle) + return + our_projectile.fire() + return our_projectile + +/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire + name = "Rapid Fire" + icon_icon = 'icons/obj/guns/energy.dmi' + button_icon_state = "kineticgun" + desc = "Fires projectiles repeatedly at a given target." + cooldown_time = 1.5 SECONDS + projectile_type = /obj/projectile/colossus/snowball + default_projectile_spread = 45 + /// Total shot count + var/shot_count = 60 + /// Delay between shots + var/shot_delay = 0.1 SECONDS + +/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/attack_sequence(mob/living/firer, atom/target) + for(var/i in 1 to shot_count) + shoot_projectile(firer, target, null, firer, rand(-default_projectile_spread, default_projectile_spread), null) + SLEEP_CHECK_DEATH(shot_delay, src) + +/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/shrapnel + name = "Shrapnel Fire" + icon_icon = 'icons/mob/actions/actions_items.dmi' + button_icon_state = "sniper_zoom" + desc = "Fires projectiles that will split into shrapnel after a period of time." + cooldown_time = 6 SECONDS + projectile_type = /obj/projectile/colossus/frost_orb + has_homing = TRUE + default_projectile_spread = 180 + shot_count = 8 + shot_delay = 1 SECONDS + var/shrapnel_projectile_type = /obj/projectile/colossus/ice_blast + var/shrapnel_angles = list(0, 60, 120, 180, 240, 300) + var/shrapnel_spread = 10 + var/break_time = 2 SECONDS + +/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/shrapnel/attack_sequence(mob/living/firer, atom/target) + for(var/i in 1 to shot_count) + var/obj/projectile/to_explode = shoot_projectile(firer, target, null, firer, rand(-default_projectile_spread, default_projectile_spread), null) + addtimer(CALLBACK(src, .proc/explode_into_shrapnel, firer, target, to_explode), break_time) + SLEEP_CHECK_DEATH(shot_delay, src) + +/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/shrapnel/proc/explode_into_shrapnel(mob/living/firer, atom/target, obj/projectile/to_explode) + if(!to_explode) + return + for(var/angle in shrapnel_angles) + // no speed multiplier for shrapnel + shoot_projectile(to_explode, target, angle + rand(-shrapnel_spread, shrapnel_spread), firer, null, 1, shrapnel_projectile_type, FALSE) + qdel(to_explode) + +/datum/action/cooldown/mob_cooldown/projectile_attack/spiral_shots + name = "Spiral Shots" + icon_icon = 'icons/mob/actions/actions_items.dmi' + button_icon_state = "sniper_zoom" + desc = "Fires projectiles in a spiral pattern." + cooldown_time = 3 SECONDS + projectile_type = /obj/projectile/colossus + projectile_sound = 'sound/magic/clockwork/invoke_general.ogg' + /// Whether or not the attack is the enraged form + var/enraged = FALSE + +/datum/action/cooldown/mob_cooldown/projectile_attack/spiral_shots/attack_sequence(mob/living/firer, atom/target) + if(enraged) + SLEEP_CHECK_DEATH(1 SECONDS, firer) + INVOKE_ASYNC(src, .proc/create_spiral_attack, firer, target, TRUE) + create_spiral_attack(firer, target, FALSE) + return + create_spiral_attack(firer, target) + +/datum/action/cooldown/mob_cooldown/projectile_attack/spiral_shots/proc/create_spiral_attack(mob/living/firer, atom/target, negative = pick(TRUE, FALSE)) + var/counter = 8 + for(var/i in 1 to 80) + if(negative) + counter-- + else + counter++ + if(counter > 16) + counter = 1 + if(counter < 1) + counter = 16 + shoot_projectile(firer, target, counter * 22.5, firer, null, null) + playsound(get_turf(firer), projectile_sound, 20, TRUE) + SLEEP_CHECK_DEATH(0.1 SECONDS, firer) + +/datum/action/cooldown/mob_cooldown/projectile_attack/random_aoe + name = "All Directions" + icon_icon = 'icons/effects/effects.dmi' + button_icon_state = "at_shield2" + desc = "Fires projectiles in all directions." + cooldown_time = 3 SECONDS + projectile_type = /obj/projectile/colossus + projectile_sound = 'sound/magic/clockwork/invoke_general.ogg' + +/datum/action/cooldown/mob_cooldown/projectile_attack/random_aoe/attack_sequence(mob/living/firer, atom/target) + var/turf/U = get_turf(firer) + playsound(U, projectile_sound, 300, TRUE, 5) + for(var/i in 1 to 32) + shoot_projectile(firer, target, rand(0, 360), firer, null, null) + +/datum/action/cooldown/mob_cooldown/projectile_attack/shotgun_blast + name = "Shotgun Fire" + icon_icon = 'icons/obj/guns/ballistic.dmi' + button_icon_state = "shotgun" + desc = "Fires projectiles in a shotgun pattern." + cooldown_time = 2 SECONDS + projectile_type = /obj/projectile/colossus + projectile_sound = 'sound/magic/clockwork/invoke_general.ogg' + var/list/shot_angles = list(12.5, 7.5, 2.5, -2.5, -7.5, -12.5) + +/datum/action/cooldown/mob_cooldown/projectile_attack/shotgun_blast/New(Target, projectile, homing, spread, list/angles) + . = ..() + if(angles) + shot_angles = angles + +/datum/action/cooldown/mob_cooldown/projectile_attack/shotgun_blast/attack_sequence(mob/living/firer, atom/target) + fire_shotgun(firer, target, shot_angles) + +/datum/action/cooldown/mob_cooldown/projectile_attack/shotgun_blast/proc/fire_shotgun(mob/living/firer, atom/target, list/chosen_angles) + playsound(firer, projectile_sound, 200, TRUE, 2) + for(var/spread in chosen_angles) + shoot_projectile(firer, target, null, firer, spread, null) + +/datum/action/cooldown/mob_cooldown/projectile_attack/shotgun_blast/pattern + name = "Alternating Shotgun Fire" + desc = "Fires projectiles in an alternating shotgun pattern." + projectile_type = /obj/projectile/colossus/ice_blast + projectile_sound = null + shot_angles = list(list(-40, -20, 0, 20, 40), list(-30, -10, 10, 30)) + var/shot_count = 5 + +/datum/action/cooldown/mob_cooldown/projectile_attack/shotgun_blast/pattern/attack_sequence(mob/living/firer, atom/target) + for(var/i in 1 to shot_count) + var/list/pattern = shot_angles[i % length(shot_angles) + 1] // changing patterns + fire_shotgun(firer, target, pattern) + SLEEP_CHECK_DEATH(0.8 SECONDS, firer) + +/datum/action/cooldown/mob_cooldown/projectile_attack/dir_shots + name = "Directional Shots" + icon_icon = 'icons/obj/guns/ballistic.dmi' + button_icon_state = "pistol" + desc = "Fires projectiles in specific directions." + cooldown_time = 4 SECONDS + projectile_type = /obj/projectile/colossus + projectile_sound = 'sound/magic/clockwork/invoke_general.ogg' + var/list/firing_directions + +/datum/action/cooldown/mob_cooldown/projectile_attack/dir_shots/New(Target, projectile, homing, spread, list/dirs) + . = ..() + if(dirs) + firing_directions = dirs + else + firing_directions = GLOB.alldirs.Copy() + +/datum/action/cooldown/mob_cooldown/projectile_attack/dir_shots/attack_sequence(mob/living/firer, atom/target) + fire_in_directions(firer, target, firing_directions) + +/datum/action/cooldown/mob_cooldown/projectile_attack/dir_shots/proc/fire_in_directions(mob/living/firer, atom/target, list/dirs) + if(!islist(dirs)) + dirs = GLOB.alldirs.Copy() + playsound(firer, projectile_sound, 200, TRUE, 2) + for(var/d in dirs) + var/turf/E = get_step(firer, d) + shoot_projectile(firer, E, null, firer, null, null) + +/datum/action/cooldown/mob_cooldown/projectile_attack/dir_shots/alternating + name = "Alternating Shots" + desc = "Fires projectiles in alternating directions." + +/datum/action/cooldown/mob_cooldown/projectile_attack/dir_shots/alternating/attack_sequence(mob/living/firer, atom/target) + fire_in_directions(firer, target, GLOB.diagonals) + SLEEP_CHECK_DEATH(1 SECONDS, firer) + fire_in_directions(firer, target, GLOB.cardinals) + SLEEP_CHECK_DEATH(1 SECONDS, firer) + fire_in_directions(firer, target, GLOB.diagonals) + SLEEP_CHECK_DEATH(1 SECONDS, firer) + fire_in_directions(firer, target, GLOB.cardinals) + +/datum/action/cooldown/mob_cooldown/projectile_attack/kinetic_accelerator + name = "Fire Kinetic Accelerator" + icon_icon = 'icons/obj/guns/energy.dmi' + button_icon_state = "kineticgun" + desc = "Fires a kinetic accelerator projectile at the target." + cooldown_time = 1.5 SECONDS + projectile_type = /obj/projectile/kinetic/miner + projectile_sound = 'sound/weapons/kenetic_accel.ogg' + +/datum/action/cooldown/mob_cooldown/projectile_attack/kinetic_accelerator/Activate(atom/target_atom) + . = ..() + playsound(owner, projectile_sound, 200, TRUE, 2) + owner.visible_message(span_danger("[owner] fires the proto-kinetic accelerator!")) + owner.face_atom(target_atom) + new /obj/effect/temp_visual/dir_setting/firing_effect(owner.loc, owner.dir) diff --git a/code/datums/actions/mobs/transform_weapon.dm b/code/datums/actions/mobs/transform_weapon.dm new file mode 100644 index 00000000000..6dad130da22 --- /dev/null +++ b/code/datums/actions/mobs/transform_weapon.dm @@ -0,0 +1,27 @@ +/datum/action/cooldown/mob_cooldown/transform_weapon + name = "Transform Weapon" + icon_icon = 'icons/obj/lavaland/artefacts.dmi' + button_icon_state = "cleaving_saw" + desc = "Transform weapon into a different state." + cooldown_time = 5 SECONDS + shared_cooldown = MOB_SHARED_COOLDOWN_2 + /// The max possible cooldown, cooldown is random between the default cooldown time and this + var/max_cooldown_time = 10 SECONDS + +/datum/action/cooldown/mob_cooldown/transform_weapon/Activate(atom/target_atom) + StartCooldown(100) + do_transform(target_atom) + StartCooldown(rand(cooldown_time, max_cooldown_time)) + +/datum/action/cooldown/mob_cooldown/transform_weapon/proc/do_transform(atom/target) + if(!istype(owner, /mob/living/simple_animal/hostile/megafauna/blood_drunk_miner)) + return + var/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/BDM = owner + var/obj/item/melee/cleaving_saw/miner/miner_saw = BDM.miner_saw + miner_saw.attack_self(owner) + if(!miner_saw.is_open) + BDM.rapid_melee = 5 // 4 deci cooldown before changes, npcpool subsystem wait is 20, 20/4 = 5 + else + BDM.rapid_melee = 3 // same thing but halved (slightly rounded up) + BDM.icon_state = "miner[miner_saw.is_open ? "_transformed":""]" + BDM.icon_living = "miner[miner_saw.is_open ? "_transformed":""]" diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 2796b3d46db..e555e084635 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -117,6 +117,7 @@ GLOBAL_LIST_INIT(admin_verbs_fun, list( /client/proc/show_tip, /client/proc/smite, /client/proc/admin_away, + /client/proc/add_mob_ability, /datum/admins/proc/station_traits_panel, /client/proc/spawn_pollution, // SKYRAT EDIT ADDITION )) diff --git a/code/modules/admin/verbs/adminevents.dm b/code/modules/admin/verbs/adminevents.dm index 1680748bc31..39d00db8503 100644 --- a/code/modules/admin/verbs/adminevents.dm +++ b/code/modules/admin/verbs/adminevents.dm @@ -505,3 +505,29 @@ message_admins("[key_name_admin(usr)] started weather of type [weather_type] on the z-level [z_level].") log_admin("[key_name(usr)] started weather of type [weather_type] on the z-level [z_level].") SSblackbox.record_feedback("tally", "admin_verb", 1, "Run Weather") + +/client/proc/add_mob_ability() + set category = "Admin.Events" + set name = "Add Mob Ability" + set desc = "Adds an ability to a marked mob." + + if(!holder) + return + + if(!holder.marked_datum || !istype(holder.marked_datum, /mob/living)) + return + + var/mob/living/marked_mob = holder.marked_datum + + var/ability_type = input("Choose an ability", "Ability") as null|anything in sort_list(subtypesof(/datum/action/cooldown/mob_cooldown), /proc/cmp_typepaths_asc) + + if(!ability_type) + return + + var/datum/action/cooldown/mob_cooldown/add_ability = new ability_type() + add_ability.Grant(marked_mob) + + message_admins("[key_name_admin(usr)] added mob ability [ability_type] to mob [marked_mob].") + log_admin("[key_name(usr)] added mob ability [ability_type] to mob [marked_mob].") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Add Mob Ability") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + diff --git a/code/modules/mob/living/simple_animal/guardian/types/lightning.dm b/code/modules/mob/living/simple_animal/guardian/types/lightning.dm index 6ee6e1ac0e3..36319ae0b90 100644 --- a/code/modules/mob/living/simple_animal/guardian/types/lightning.dm +++ b/code/modules/mob/living/simple_animal/guardian/types/lightning.dm @@ -49,7 +49,7 @@ successfulshocks = 0 if(shockallchains()) successfulshocks++ - SLEEP_CHECK_DEATH(3) + SLEEP_CHECK_DEATH(3, src) /mob/living/simple_animal/hostile/guardian/beam/Recall() . = ..() diff --git a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm index d9766da229b..534994e530f 100644 --- a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm +++ b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm @@ -191,17 +191,25 @@ status_flags = NONE mob_size = MOB_SIZE_LARGE gold_core_spawnable = NO_SPAWN - charger = TRUE - charge_distance = 4 menu_description = "Tank spider variant with an enormous amount of health and damage, but is very slow when not on webbing. It also has a charge ability to close distance with a target after a small windup. Does not inject toxin." - ///Whether or not the tarantula is currently walking on webbing. + /// Whether or not the tarantula is currently walking on webbing. var/silk_walking = TRUE + /// Charging ability + var/datum/action/cooldown/mob_cooldown/charge/basic_charge/charge -/mob/living/simple_animal/hostile/giant_spider/tarantula/ranged_secondary_attack(atom/target, modifiers) - if(COOLDOWN_FINISHED(src, charge_cooldown)) - INVOKE_ASYNC(src, /mob/living/simple_animal/hostile/.proc/enter_charge, target) - else - to_chat(src, span_notice("Your charge is still on cooldown!")) +/mob/living/simple_animal/hostile/giant_spider/tarantula/Initialize(mapload) + . = ..() + charge = new /datum/action/cooldown/mob_cooldown/charge/basic_charge() + charge.Grant(src) + +/mob/living/simple_animal/hostile/giant_spider/tarantula/Destroy() + QDEL_NULL(charge) + return ..() + +/mob/living/simple_animal/hostile/giant_spider/tarantula/OpenFire() + if(client) + return + charge.Trigger(target) /mob/living/simple_animal/hostile/giant_spider/tarantula/Moved(atom/oldloc, dir) . = ..() diff --git a/code/modules/mob/living/simple_animal/hostile/hostile.dm b/code/modules/mob/living/simple_animal/hostile/hostile.dm index 6dccdd30ee5..c58be9125e0 100644 --- a/code/modules/mob/living/simple_animal/hostile/hostile.dm +++ b/code/modules/mob/living/simple_animal/hostile/hostile.dm @@ -54,19 +54,6 @@ var/lose_patience_timer_id //id for a timer to call LoseTarget(), used to stop mobs fixating on a target they can't reach var/lose_patience_timeout = 300 //30 seconds by default, so there's no major changes to AI behaviour, beyond actually bailing if stuck forever - ///When a target is found, will the mob attempt to charge at it's target? - var/charger = FALSE - ///Tracks if the target is actively charging. - var/charge_state = FALSE - ///In a charge, how many tiles will the charger travel? - var/charge_distance = 3 - ///How often can the charging mob actually charge? Effects the cooldown between charges. - var/charge_frequency = 6 SECONDS - ///If the mob is charging, how long will it stun it's target on success, and itself on failure? - var/knockdown_time = 3 SECONDS - ///Declares a cooldown for potential charges right off the bat. - COOLDOWN_DECLARE(charge_cooldown) - /mob/living/simple_animal/hostile/Initialize(mapload) . = ..() wanted_objects = typecacheof(wanted_objects) @@ -295,9 +282,6 @@ if(ranged) //We ranged? Shoot at em if(!target.Adjacent(target_from) && ranged_cooldown <= world.time) //But make sure they're not in range for a melee attack and our range attack is off cooldown OpenFire(target) - if(charger && (target_distance > minimum_distance) && (target_distance <= charge_distance))//Attempt to close the distance with a charge. - enter_charge(target) - return TRUE if(!Process_Spacemove()) //Drifting walk(src,0) return 1 @@ -604,71 +588,6 @@ else if (M.loc.type in hostile_machines) . += M.loc -/** - * Proc that handles a charge attack windup for a mob. - */ -/mob/living/simple_animal/hostile/proc/enter_charge(atom/target) - if(charge_state || !(COOLDOWN_FINISHED(src, charge_cooldown))) - return FALSE - if(!can_charge_target(target)) - return FALSE - Shake(15, 15, 1 SECONDS) - addtimer(CALLBACK(src, .proc/handle_charge_target, target), 1.5 SECONDS, TIMER_STOPPABLE) - -/** - * Proc that checks if the mob can charge attack. - */ -/mob/living/simple_animal/hostile/proc/can_charge_target(atom/target) - if(stat == DEAD || body_position == LYING_DOWN || HAS_TRAIT(src, TRAIT_IMMOBILIZED) || !has_gravity() || !target.has_gravity()) - return FALSE - return TRUE - -/** - * Proc that throws the mob at the target after the windup. - */ -/mob/living/simple_animal/hostile/proc/handle_charge_target(atom/target) - if(!can_charge_target(target)) - return FALSE - charge_state = TRUE - throw_at(target, charge_distance, 1, src, FALSE, TRUE, callback = CALLBACK(src, .proc/charge_end)) - COOLDOWN_START(src, charge_cooldown, charge_frequency) - return TRUE - -/** - * Proc that handles a charge attack after it's concluded. - */ -/mob/living/simple_animal/hostile/proc/charge_end() - charge_state = FALSE - -/** - * Proc that handles the charge impact of the charging mob. - */ -/mob/living/simple_animal/hostile/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - if(!charge_state) - return ..() - - if(hit_atom) - if(isliving(hit_atom)) - var/mob/living/L = hit_atom - var/blocked = FALSE - if(ishuman(hit_atom)) - var/mob/living/carbon/human/H = hit_atom - if(H.check_shields(src, 0, "the [name]", attack_type = LEAP_ATTACK)) - blocked = TRUE - if(!blocked) - L.visible_message(span_danger("[src] charges on [L]!"), span_userdanger("[src] charges into you!")) - L.Knockdown(knockdown_time) - else - Stun((knockdown_time * 2), ignore_canstun = TRUE) - charge_end() - else if(hit_atom.density && !hit_atom.CanPass(src, get_dir(hit_atom, src))) - visible_message(span_danger("[src] smashes into [hit_atom]!")) - Stun((knockdown_time * 2), ignore_canstun = TRUE) - - if(charge_state) - charge_state = FALSE - update_icons() - /mob/living/simple_animal/hostile/proc/get_targets_from() var/atom/target_from = targets_from.resolve() if(!target_from) 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 b3dc904d35a..7bb0e4abc13 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 @@ -1,5 +1,3 @@ -#define MINER_DASH_RANGE 4 - /* BLOOD-DRUNK MINER @@ -35,8 +33,6 @@ Difficulty: Medium speak_emote = list("roars") speed = 3 move_to_delay = 3 - projectiletype = /obj/projectile/kinetic/miner - projectilesound = 'sound/weapons/kenetic_accel.ogg' ranged = TRUE ranged_cooldown_time = 1.6 SECONDS pixel_x = -16 @@ -51,74 +47,47 @@ Difficulty: Medium crusher_achievement_type = /datum/award/achievement/boss/blood_miner_crusher score_achievement_type = /datum/award/score/blood_miner_score var/obj/item/melee/cleaving_saw/miner/miner_saw - var/time_until_next_transform = 0 - var/dashing = FALSE - var/dash_cooldown = 0 - var/dash_cooldown_time = 1.5 SECONDS 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" footstep_type = FOOTSTEP_MOB_HEAVY - attack_action_types = list(/datum/action/innate/megafauna_attack/dash, - /datum/action/innate/megafauna_attack/kinetic_accelerator, - /datum/action/innate/megafauna_attack/transform_weapon) move_force = MOVE_FORCE_NORMAL //Miner beeing able to just move structures like bolted doors and glass looks kinda strange + /// Dash ability + var/datum/action/cooldown/mob_cooldown/dash/dash + /// Kinetic accelerator ability + var/datum/action/cooldown/mob_cooldown/projectile_attack/kinetic_accelerator/kinetic_accelerator + /// Transform weapon ability + var/datum/action/cooldown/mob_cooldown/transform_weapon/transform_weapon /mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/Initialize(mapload) . = ..() miner_saw = new(src) ADD_TRAIT(src, TRAIT_NO_FLOATING_ANIM, INNATE_TRAIT) + dash = new /datum/action/cooldown/mob_cooldown/dash() + kinetic_accelerator = new /datum/action/cooldown/mob_cooldown/projectile_attack/kinetic_accelerator() + transform_weapon = new /datum/action/cooldown/mob_cooldown/transform_weapon() + dash.Grant(src) + kinetic_accelerator.Grant(src) + transform_weapon.Grant(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/update_cooldowns(list/cooldown_updates, ignore_staggered = FALSE) - . = ..() - if(cooldown_updates[COOLDOWN_UPDATE_SET_DASH]) - dash_cooldown = world.time + cooldown_updates[COOLDOWN_UPDATE_SET_DASH] - if(cooldown_updates[COOLDOWN_UPDATE_ADD_DASH]) - dash_cooldown += cooldown_updates[COOLDOWN_UPDATE_ADD_DASH] - if(cooldown_updates[COOLDOWN_UPDATE_SET_TRANSFORM]) - time_until_next_transform = world.time + cooldown_updates[COOLDOWN_UPDATE_SET_TRANSFORM] - if(cooldown_updates[COOLDOWN_UPDATE_ADD_TRANSFORM]) - time_until_next_transform += cooldown_updates[COOLDOWN_UPDATE_ADD_TRANSFORM] +/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/Destroy() + QDEL_NULL(dash) + QDEL_NULL(kinetic_accelerator) + QDEL_NULL(transform_weapon) + return ..() /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() + if(get_dist(src, target) > 4) + if(dash.Trigger(target)) + kinetic_accelerator.StartCooldown(0) + kinetic_accelerator.Trigger(target) else - shoot_ka() - transform_weapon() + kinetic_accelerator.Trigger(target) + transform_weapon.Trigger(target) /obj/item/melee/cleaving_saw/miner //nerfed saw because it is very murdery force = 6 @@ -133,7 +102,7 @@ Difficulty: Medium damage = 20 speed = 0.9 icon_state = "ka_tracer" - range = MINER_DASH_RANGE + range = 4 /mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/adjustHealth(amount, updating_health = TRUE, forced = FALSE) var/adjustment_amount = amount * 0.1 @@ -147,23 +116,17 @@ Difficulty: Medium 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! + if(newloc && newloc.z == z && (islava(newloc) || ischasm(newloc))) //we're not stupid! return FALSE return ..() /mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/ex_act(severity, target) - if(dash()) + if(dash.Trigger(target)) return FALSE 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(client) - transform_stop_attack = FALSE - if(QDELETED(target) || transform_stop_attack) + if(QDELETED(target)) return face_atom(target) if(isliving(target)) @@ -195,82 +158,6 @@ Difficulty: Medium if(. && target && !targets_the_same) wander = TRUE -/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)) - update_cooldowns(list(COOLDOWN_UPDATE_SET_RANGED = ranged_cooldown_time), ignore_staggered = TRUE) - visible_message(span_danger("[src] fires the proto-kinetic accelerator!")) - face_atom(target) - new /obj/effect/temp_visual/dir_setting/firing_effect(loc, dir) - Shoot(target) - changeNext_move(CLICK_CD_RANGE) - -/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/proc/dash(atom/dash_target) - if(world.time < dash_cooldown) - return - var/list/accessable_turfs = list() - var/self_dist_to_target = 0 - var/turf/own_turf = get_turf(src) - if(!QDELETED(dash_target)) - self_dist_to_target += get_dist(dash_target, own_turf) - for(var/turf/open/O in RANGE_TURFS(MINER_DASH_RANGE, own_turf)) - var/turf_dist_to_target = 0 - if(!QDELETED(dash_target)) - turf_dist_to_target += get_dist(dash_target, O) - if(get_dist(src, O) >= MINER_DASH_RANGE && turf_dist_to_target <= self_dist_to_target && !islava(O) && !ischasm(O)) - var/valid = TRUE - for(var/turf/T in get_line(own_turf, O)) - if(T.is_blocked_turf(TRUE)) - valid = FALSE - continue - if(valid) - accessable_turfs[O] = turf_dist_to_target - var/turf/target_turf - if(!QDELETED(dash_target)) - var/closest_dist = MINER_DASH_RANGE - for(var/t in accessable_turfs) - if(accessable_turfs[t] < closest_dist) - closest_dist = accessable_turfs[t] - for(var/t in accessable_turfs) - if(accessable_turfs[t] != closest_dist) - accessable_turfs -= t - if(!LAZYLEN(accessable_turfs)) - return - update_cooldowns(list(COOLDOWN_UPDATE_SET_DASH = dash_cooldown_time)) - target_turf = pick(accessable_turfs) - var/turf/step_back_turf = get_step(target_turf, get_cardinal_dir(target_turf, own_turf)) - 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/fading/halfsecond/D = new (own_turf, src) - forceMove(step_back_turf) - playsound(own_turf, 'sound/weapons/punchmiss.ogg', 40, TRUE, -1) - dashing = TRUE - alpha = 0 - animate(src, alpha = 255, time = 5) - SLEEP_CHECK_DEATH(2) - D.forceMove(step_forward_turf) - forceMove(target_turf) - playsound(target_turf, 'sound/weapons/punchmiss.ogg', 40, TRUE, -1) - SLEEP_CHECK_DEATH(1) - dashing = FALSE - return TRUE - -/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/proc/transform_weapon() - if(time_until_next_transform <= world.time) - miner_saw.attack_self(src) - if(!miner_saw.is_open) - 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.is_open ? "_transformed":""]" - icon_living = "miner[miner_saw.is_open ? "_transformed":""]" - update_cooldowns(list(COOLDOWN_UPDATE_SET_TRANSFORM = rand(5 SECONDS, 10 SECONDS))) - /obj/effect/temp_visual/dir_setting/miner_death icon_state = "miner_death" duration = 15 @@ -298,7 +185,7 @@ Difficulty: Medium /mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/hunter/AttackingTarget() . = ..() if(. && prob(12)) - INVOKE_ASYNC(src, .proc/dash) + INVOKE_ASYNC(dash, /datum/action/proc/Trigger, target) /mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/doom name = "hostile-environment miner" @@ -306,6 +193,7 @@ Difficulty: Medium speed = 8 move_to_delay = 8 ranged_cooldown_time = 0.8 SECONDS - dash_cooldown = 0.8 SECONDS -#undef MINER_DASH_RANGE +/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/doom/Initialize(mapload) + . = ..() + dash.cooldown_time = 0.8 SECONDS 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 13366eb88b7..de4737b8df7 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm @@ -59,55 +59,51 @@ Difficulty: Hard 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 = FALSE - var/enrage_till = 0 - var/enrage_time = 7 SECONDS - var/revving_charge = FALSE gps_name = "Bloody Signal" achievement_type = /datum/award/achievement/boss/bubblegum_kill crusher_achievement_type = /datum/award/achievement/boss/bubblegum_crusher score_achievement_type = /datum/award/score/bubblegum_score - deathmessage = "sinks into a pool of blood, fleeing the battle. You've won, for now... " deathsound = 'sound/magic/enter_blood.ogg' - 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 faction = list("mining", "boss", "hell") + /// Check to see if we should spawn blood + var/spawn_blood = TRUE + /// Actual time where enrage ends + var/enrage_till = 0 + /// Duration of enrage ability + var/enrage_time = 7 SECONDS + /// Triple charge ability + var/datum/action/cooldown/mob_cooldown/charge/triple_charge/triple_charge + /// Hallucination charge ability + var/datum/action/cooldown/mob_cooldown/charge/hallucination_charge/hallucination_charge + /// Hallucination charge surround ability + var/datum/action/cooldown/mob_cooldown/charge/hallucination_charge/hallucination_surround/hallucination_charge_surround + /// Blood warp ability + var/datum/action/cooldown/mob_cooldown/blood_warp/blood_warp -/mob/living/simple_animal/hostile/megafauna/bubblegum/Initialize(mapload) +/mob/living/simple_animal/hostile/megafauna/bubblegum/Initialize() . = ..() ADD_TRAIT(src, TRAIT_NO_FLOATING_ANIM, INNATE_TRAIT) + triple_charge = new /datum/action/cooldown/mob_cooldown/charge/triple_charge() + hallucination_charge = new /datum/action/cooldown/mob_cooldown/charge/hallucination_charge() + hallucination_charge_surround = new /datum/action/cooldown/mob_cooldown/charge/hallucination_charge/hallucination_surround() + blood_warp = new /datum/action/cooldown/mob_cooldown/blood_warp() + triple_charge.Grant(src) + hallucination_charge.Grant(src) + hallucination_charge_surround.Grant(src) + blood_warp.Grant(src) + hallucination_charge.spawn_blood = TRUE + hallucination_charge_surround.spawn_blood = TRUE + RegisterSignal(src, COMSIG_BLOOD_WARP, .proc/blood_enrage) + RegisterSignal(src, COMSIG_FINISHED_CHARGE, .proc/after_charge) -/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 - -/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 - -/datum/action/innate/megafauna_attack/hallucination_surround - name = "Surround Target" - icon_icon = 'icons/turf/walls/wall.dmi' - button_icon_state = "wall-0" - chosen_message = "You are now surrounding the target you click on with hallucinations." - chosen_attack_num = 3 - -/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/Destroy() + QDEL_NULL(triple_charge) + QDEL_NULL(hallucination_charge) + QDEL_NULL(hallucination_charge_surround) + QDEL_NULL(blood_warp) + return ..() /mob/living/simple_animal/hostile/megafauna/bubblegum/update_cooldowns(list/cooldown_updates, ignore_staggered = FALSE) . = ..() @@ -117,94 +113,25 @@ Difficulty: Hard enrage_till += cooldown_updates[COOLDOWN_UPDATE_ADD_ENRAGE] /mob/living/simple_animal/hostile/megafauna/bubblegum/OpenFire() - if(charging) - return - - anger_modifier = clamp(((maxHealth - health)/60),0,20) - enrage_time = initial(enrage_time) * clamp(anger_modifier / 20, 0.5, 1) - update_cooldowns(list(COOLDOWN_UPDATE_SET_RANGED = 5 SECONDS)) - if(client) - switch(chosen_attack) - if(1) - triple_charge() - if(2) - hallucination_charge() - if(3) - surround_with_hallucinations() - if(4) - blood_warp() return if(!try_bloodattack() || prob(25 + anger_modifier)) - blood_warp() + blood_warp.Trigger(target) if(!BUBBLEGUM_SMASH) - triple_charge() + triple_charge.Trigger(target) else if(prob(50 + anger_modifier)) - hallucination_charge() + hallucination_charge.Trigger(target) else - surround_with_hallucinations() + hallucination_charge_surround.Trigger(target) -/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/triple_charge() - charge(delay = 6) - charge(delay = 4) - charge(delay = 2) - update_cooldowns(list(COOLDOWN_UPDATE_SET_MELEE = 1.5 SECONDS, COOLDOWN_UPDATE_SET_RANGED = 1.5 SECONDS)) - -/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/hallucination_charge() - if(!BUBBLEGUM_SMASH || prob(33)) - hallucination_charge_around(times = 6, delay = 8) - update_cooldowns(list(COOLDOWN_UPDATE_SET_MELEE = 1 SECONDS, COOLDOWN_UPDATE_SET_RANGED = 1 SECONDS)) - else - hallucination_charge_around(times = 4, delay = 9) - hallucination_charge_around(times = 4, delay = 8) - hallucination_charge_around(times = 4, delay = 7) - triple_charge() - update_cooldowns(list(COOLDOWN_UPDATE_SET_MELEE = 2 SECONDS, COOLDOWN_UPDATE_SET_RANGED = 2 SECONDS)) - -/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) - update_cooldowns(list(COOLDOWN_UPDATE_SET_MELEE = 2 SECONDS, COOLDOWN_UPDATE_SET_RANGED = 2 SECONDS)) - -/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/charge(atom/chargeat = target, delay = 3, chargepast = 2) - if(!chargeat) - return - var/chargeturf = get_turf(chargeat) - if(!chargeturf) - return - var/dir = get_dir(src, chargeturf) - var/turf/T = get_ranged_target_turf(chargeturf, dir, chargepast) - if(!T) - return - new /obj/effect/temp_visual/dragon_swoop/bubblegum(T) - 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_CHECK_DEATH(delay) - revving_charge = FALSE - var/movespeed = 0.7 - walk_towards(src, T, movespeed) - SLEEP_CHECK_DEATH(get_dist(src, T) * movespeed) - walk(src, 0) // cancel the movement - try_bloodattack() - charging = FALSE - -/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/get_mobs_on_blood() - var/list/targets = ListTargets() +/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/get_mobs_on_blood(mob/target) + var/list/targets = list(target) . = list() for(var/mob/living/L in targets) - var/list/bloodpool = get_pools(get_turf(L), 0) + var/list/bloodpool = get_bloodcrawlable_pools(get_turf(L), 0) if(bloodpool.len && (!faction_check_mob(L) || L.stat == DEAD)) . += L @@ -243,7 +170,7 @@ Difficulty: Hard bloodsmack(target_two_turf, handedness) if(target_one) - var/list/pools = get_pools(get_turf(target_one), 0) + var/list/pools = get_bloodcrawlable_pools(get_turf(target_one), 0) if(pools.len) target_one_turf = get_turf(target_one) if(target_one_turf) @@ -253,7 +180,7 @@ Difficulty: Hard bloodsmack(target_one_turf, !handedness) if(!target_two && target_one) - var/list/poolstwo = get_pools(get_turf(target_one), 0) + var/list/poolstwo = get_bloodcrawlable_pools(get_turf(target_one), 0) if(poolstwo.len) target_one_turf = get_turf(target_one) if(target_one_turf) @@ -267,14 +194,14 @@ Difficulty: Hard new /obj/effect/temp_visual/bubblegum_hands/rightsmack(T) else new /obj/effect/temp_visual/bubblegum_hands/leftsmack(T) - SLEEP_CHECK_DEATH(4) + SLEEP_CHECK_DEATH(4, src) for(var/mob/living/L in T) if(!faction_check_mob(L)) to_chat(L, span_userdanger("[src] rends you!")) playsound(T, attack_sound, 100, TRUE, -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), wound_bonus = CANT_WOUND) - SLEEP_CHECK_DEATH(3) + SLEEP_CHECK_DEATH(3, src) /mob/living/simple_animal/hostile/megafauna/bubblegum/proc/bloodgrab(turf/T, handedness) if(handedness) @@ -283,7 +210,7 @@ Difficulty: Hard else new /obj/effect/temp_visual/bubblegum_hands/leftpaw(T) new /obj/effect/temp_visual/bubblegum_hands/leftthumb(T) - SLEEP_CHECK_DEATH(6) + SLEEP_CHECK_DEATH(6, src) for(var/mob/living/L in T) if(!faction_check_mob(L)) if(L.stat != CONSCIOUS) @@ -293,45 +220,8 @@ Difficulty: Hard L.forceMove(targetturf) playsound(targetturf, 'sound/magic/exit_blood.ogg', 100, TRUE, -1) addtimer(CALLBACK(src, .proc/devour, L), 2) - SLEEP_CHECK_DEATH(1) + SLEEP_CHECK_DEATH(1, src) -/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(span_danger("[src] sinks into the blood...")) - playsound(get_turf(src), 'sound/magic/enter_blood.ogg', 100, TRUE, -1) - forceMove(get_turf(found_bloodpool)) - playsound(get_turf(src), 'sound/magic/exit_blood.ogg', 100, TRUE, -1) - visible_message(span_danger("And springs back out!")) - blood_enrage() - return TRUE - return FALSE /mob/living/simple_animal/hostile/megafauna/bubblegum/proc/be_aggressive() @@ -339,7 +229,6 @@ Difficulty: Hard return TRUE return isliving(target) && HAS_TRAIT(target, TRAIT_INCAPACITATED) - /mob/living/simple_animal/hostile/megafauna/bubblegum/proc/get_retreat_distance() return (be_aggressive() ? null : initial(retreat_distance)) @@ -351,15 +240,20 @@ Difficulty: Hard minimum_distance = get_minimum_distance() /mob/living/simple_animal/hostile/megafauna/bubblegum/proc/blood_enrage() + SIGNAL_HANDLER if(!BUBBLEGUM_CAN_ENRAGE) return FALSE - update_cooldowns(list(COOLDOWN_UPDATE_SET_ENRAGE = enrage_time)) + enrage_till = world.time + enrage_time update_approach() - change_move_delay(3.75) + INVOKE_ASYNC(src, .proc/change_move_delay, 3.75) add_atom_colour(COLOR_BUBBLEGUM_RED, 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/after_charge() + SIGNAL_HANDLER + try_bloodattack() + /mob/living/simple_animal/hostile/megafauna/bubblegum/proc/blood_enrage_end() update_approach() change_move_delay() @@ -370,46 +264,11 @@ Difficulty: Hard 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(times = 4, delay = 6, chargepast = 0, useoriginal = 1, 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) - /mob/living/simple_animal/hostile/megafauna/bubblegum/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE) . = ..() + anger_modifier = clamp(((maxHealth - health)/60),0,20) + enrage_time = initial(enrage_time) * clamp(anger_modifier / 20, 0.5, 1) + hallucination_charge.enraged = BUBBLEGUM_SMASH 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)) @@ -417,29 +276,15 @@ Difficulty: Hard 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(!(flags_1 & ADMIN_SPAWNED_1)) SSshuttle.shuttle_purchase_requirements_met[SHUTTLE_UNLOCK_BUBBLEGUM] = TRUE -/mob/living/simple_animal/hostile/megafauna/bubblegum/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect) - if(!charging) - ..() - /mob/living/simple_animal/hostile/megafauna/bubblegum/AttackingTarget() - if(!charging) - . = ..() - if(.) - update_cooldowns(list(COOLDOWN_UPDATE_ADD_MELEE = 2 SECONDS)) // can only attack melee once every 2 seconds but rapid_melee gives higher priority + . = ..() + 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/projectile/P) if(BUBBLEGUM_IS_ENRAGED) @@ -455,53 +300,70 @@ Difficulty: Hard severity = EXPLODE_LIGHT // puny mortals return ..() -/mob/living/simple_animal/hostile/megafauna/bubblegum/CanAllowThrough(atom/movable/mover, border_dir) - . = ..() - if(istype(mover, /mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination)) - return TRUE - -/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) + . = ..() + if(spawn_blood) new /obj/effect/decal/cleanable/blood/bubblegum(src.loc) - if(charging) - DestroySurroundings() playsound(src, 'sound/effects/meteorimpact.ogg', 200, TRUE, 2, TRUE) - return ..() -/mob/living/simple_animal/hostile/megafauna/bubblegum/Bump(atom/A) - if(charging) - if(isturf(A) || isobj(A) && A.density) - if(isobj(A)) - SSexplosions.med_mov_atom += A - else - SSexplosions.medturf += A - DestroySurroundings() - if(isliving(A)) - var/mob/living/L = A - L.visible_message(span_danger("[src] slams into [L]!"), span_userdanger("[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, wound_bonus = CANT_WOUND) - playsound(get_turf(L), 'sound/effects/meteorimpact.ogg', 100, TRUE) - shake_camera(L, 4, 3) - shake_camera(src, 2, 3) - ..() +/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination + name = "bubblegum's hallucination" + desc = "Is that really just a hallucination?" + health = 1 + maxHealth = 1 + alpha = 127.5 + crusher_loot = null + loot = null + achievement_type = null + crusher_achievement_type = null + score_achievement_type = null + deathmessage = "Explodes into a pool of blood!" + deathsound = 'sound/effects/splat.ogg' + true_spawn = FALSE + var/move_through_mob + +/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/Initialize() + . = ..() + toggle_ai(AI_OFF) + +/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/Destroy() + if(spawn_blood) + new /obj/effect/decal/cleanable/blood(get_turf(src)) + . = ..() + +/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/Life(delta_time = SSMOBS_DT, times_fired) + return + +/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE) + return + +/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/OpenFire() + return + +/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/AttackingTarget() + return + +/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/try_bloodattack() + return + +/obj/effect/decal/cleanable/blood/bubblegum + bloodiness = 0 + +/obj/effect/decal/cleanable/blood/bubblegum/can_bloodcrawl_in() + return TRUE + +/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 /obj/effect/temp_visual/dragon_swoop/bubblegum duration = 10 @@ -529,50 +391,3 @@ Difficulty: Hard /obj/effect/temp_visual/bubblegum_hands/leftsmack icon_state = "leftsmack" - -/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination - name = "bubblegum's hallucination" - desc = "Is that really just a hallucination?" - health = 1 - maxHealth = 1 - alpha = 127.5 - crusher_loot = null - loot = null - achievement_type = null - crusher_achievement_type = null - score_achievement_type = null - deathmessage = "Explodes into a pool of blood!" - deathsound = 'sound/effects/splat.ogg' - true_spawn = FALSE - -/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/Initialize(mapload) - . = ..() - toggle_ai(AI_OFF) - -/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/charge(atom/chargeat = target, delay = 3, chargepast = 2) - ..() - qdel(src) - -/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/Destroy() - new /obj/effect/decal/cleanable/blood(get_turf(src)) - . = ..() - -/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/CanAllowThrough(atom/movable/mover, border_dir) - . = ..() - if(istype(mover, /mob/living/simple_animal/hostile/megafauna/bubblegum)) // hallucinations should not be stopping bubblegum or eachother - return TRUE - -/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/Life(delta_time = SSMOBS_DT, times_fired) - return - -/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE) - return - -/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/OpenFire() - return - -/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/AttackingTarget() - return - -/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/try_bloodattack() - return 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 27fbb7aaaa3..8a80de57b68 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm @@ -1,3 +1,5 @@ +#define COLOSSUS_ENRAGED (health < maxHealth/3) + /** * COLOSSUS * @@ -51,80 +53,83 @@ 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 + /// Spiral shots ability + var/datum/action/cooldown/mob_cooldown/projectile_attack/spiral_shots/spiral_shots + /// Random shots ablity + var/datum/action/cooldown/mob_cooldown/projectile_attack/random_aoe/random_shots + /// Shotgun blast ability + var/datum/action/cooldown/mob_cooldown/projectile_attack/shotgun_blast/shotgun_blast + /// Directional shots ability + var/datum/action/cooldown/mob_cooldown/projectile_attack/dir_shots/alternating/dir_shots /mob/living/simple_animal/hostile/megafauna/colossus/Initialize(mapload) . = ..() ADD_TRAIT(src, TRAIT_NO_FLOATING_ANIM, INNATE_TRAIT) //we don't want this guy to float, messes up his animations. + spiral_shots = new /datum/action/cooldown/mob_cooldown/projectile_attack/spiral_shots() + random_shots = new /datum/action/cooldown/mob_cooldown/projectile_attack/random_aoe() + shotgun_blast = new /datum/action/cooldown/mob_cooldown/projectile_attack/shotgun_blast() + dir_shots = new /datum/action/cooldown/mob_cooldown/projectile_attack/dir_shots/alternating() + spiral_shots.Grant(src) + random_shots.Grant(src) + shotgun_blast.Grant(src) + dir_shots.Grant(src) + RegisterSignal(src, COMSIG_ABILITY_STARTED, .proc/start_attack) + RegisterSignal(src, COMSIG_ABILITY_FINISHED, .proc/finished_attack) -/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 = 'modular_skyrat/modules/fixing_missing_icons/ballistic.dmi' //skyrat edit - 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 = 'modular_skyrat/modules/fixing_missing_icons/ballistic.dmi' //skyrat edit - 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/Destroy() + QDEL_NULL(spiral_shots) + QDEL_NULL(random_shots) + QDEL_NULL(shotgun_blast) + QDEL_NULL(dir_shots) + return ..() /mob/living/simple_animal/hostile/megafauna/colossus/OpenFire() anger_modifier = clamp(((maxHealth - health)/50),0,20) - update_cooldowns(list(COOLDOWN_UPDATE_SET_RANGED = 12 SECONDS)) 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(span_colossus("\"You can't dodge.\"")) - update_cooldowns(list(COOLDOWN_UPDATE_SET_RANGED = 3 SECONDS)) + ranged_cooldown = world.time + 30 telegraph() - dir_shots(GLOB.alldirs) + dir_shots.fire_in_directions(src, target, GLOB.alldirs) move_to_delay = 3 return else move_to_delay = initial(move_to_delay) if(prob(20+anger_modifier)) //Major attack - select_spiral_attack() + spiral_shots.Trigger(target) else if(prob(20)) - random_shots() + random_shots.Trigger(target) else if(prob(70)) - blast() + shotgun_blast.Trigger(target) else - alternating_dir_shots() + dir_shots.Trigger(target) + +/mob/living/simple_animal/hostile/megafauna/colossus/proc/telegraph() + for(var/mob/M in range(10,src)) + if(M.client) + flash_color(M.client, "#C80000", 1) + shake_camera(M, 4, 3) + playsound(src, 'sound/magic/clockwork/narsie_attack.ogg', 200, TRUE) + +/mob/living/simple_animal/hostile/megafauna/colossus/proc/start_attack(mob/living/owner, datum/action/cooldown/activated) + SIGNAL_HANDLER + if(activated == spiral_shots) + spiral_shots.enraged = COLOSSUS_ENRAGED + telegraph() + icon_state = "eva_attack" + visible_message(COLOSSUS_ENRAGED ? span_colossus("\"Die.\"") : span_colossus("\"Judgement.\"")) + +/mob/living/simple_animal/hostile/megafauna/colossus/proc/finished_attack(mob/living/owner, datum/action/cooldown/finished) + SIGNAL_HANDLER + if(finished == spiral_shots) + icon_state = initial(icon_state) /mob/living/simple_animal/hostile/megafauna/colossus/proc/enrage(mob/living/L) if(ishuman(L)) @@ -135,95 +140,6 @@ if (is_species(H, /datum/species/golem/sand)) . = TRUE -/mob/living/simple_animal/hostile/megafauna/colossus/proc/alternating_dir_shots() - update_cooldowns(list(COOLDOWN_UPDATE_SET_RANGED = 4 SECONDS)) - dir_shots(GLOB.diagonals) - SLEEP_CHECK_DEATH(10) - dir_shots(GLOB.cardinals) - SLEEP_CHECK_DEATH(10) - dir_shots(GLOB.diagonals) - SLEEP_CHECK_DEATH(10) - dir_shots(GLOB.cardinals) - -/mob/living/simple_animal/hostile/megafauna/colossus/proc/select_spiral_attack() - telegraph() - icon_state = "eva_attack" - if(health < maxHealth/3) - return double_spiral() - visible_message(span_colossus("\"Judgement.\"")) - return spiral_shoot() - -/mob/living/simple_animal/hostile/megafauna/colossus/proc/double_spiral() - visible_message(span_colossus("\"Die.\"")) - - SLEEP_CHECK_DEATH(10) - 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 = 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) - if(negative) - counter-- - else - counter++ - if(counter > 16) - counter = 1 - if(counter < 1) - counter = 16 - shoot_projectile(start_turf, counter * 22.5) - playsound(get_turf(src), 'sound/magic/clockwork/invoke_general.ogg', 20, TRUE) - SLEEP_CHECK_DEATH(1) - icon_state = initial(icon_state) - -/mob/living/simple_animal/hostile/megafauna/colossus/proc/shoot_projectile(turf/marker, set_angle) - if(!isnum(set_angle) && (!marker || marker == loc)) - return - var/turf/startloc = get_turf(src) - var/obj/projectile/P = new /obj/projectile/colossus(startloc) - P.preparePixelProjectile(marker, startloc) - P.firer = src - if(target) - P.original = target - P.fire(set_angle) - -/mob/living/simple_animal/hostile/megafauna/colossus/proc/random_shots() - update_cooldowns(list(COOLDOWN_UPDATE_SET_RANGED = 3 SECONDS)) - var/turf/U = get_turf(src) - playsound(U, 'sound/magic/clockwork/invoke_general.ogg', 300, TRUE, 5) - for(var/T in RANGE_TURFS(12, U) - U) - if(prob(5)) - shoot_projectile(T) - -/mob/living/simple_animal/hostile/megafauna/colossus/proc/blast(set_angle) - update_cooldowns(list(COOLDOWN_UPDATE_SET_RANGED = 2 SECONDS)) - var/turf/target_turf = get_turf(target) - playsound(src, 'sound/magic/clockwork/invoke_general.ogg', 200, TRUE, 2) - newtonian_move(get_dir(target_turf, src)) - var/angle_to_target = get_angle(src, target_turf) - if(isnum(set_angle)) - angle_to_target = set_angle - var/static/list/colossus_shotgun_shot_angles = list(12.5, 7.5, 2.5, -2.5, -7.5, -12.5) - for(var/i in colossus_shotgun_shot_angles) - shoot_projectile(target_turf, angle_to_target + i) - -/mob/living/simple_animal/hostile/megafauna/colossus/proc/dir_shots(list/dirs) - if(!islist(dirs)) - dirs = GLOB.alldirs.Copy() - playsound(src, 'sound/magic/clockwork/invoke_general.ogg', 200, TRUE, 2) - for(var/d in dirs) - var/turf/E = get_step(src, d) - shoot_projectile(E) - -/mob/living/simple_animal/hostile/megafauna/colossus/proc/telegraph() - for(var/mob/M in range(10,src)) - if(M.client) - flash_color(M.client, "#C80000", 1) - shake_camera(M, 4, 3) - playsound(src, 'sound/magic/clockwork/narsie_attack.ogg', 200, TRUE) - - /mob/living/simple_animal/hostile/megafauna/colossus/devour(mob/living/L) visible_message(span_colossus("[src] disintegrates [L]!")) L.dust() diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm index 7021c7c4a22..d6eaee8cf15 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm @@ -1,3 +1,4 @@ +#define FROST_MINER_SHOULD_ENRAGE (health <= maxHealth*0.25 && !enraged) GLOBAL_LIST_EMPTY(frost_miner_prisms) /* @@ -42,58 +43,40 @@ Difficulty: Extremely Hard deathmessage = "falls to the ground, decaying into plasma particles." deathsound = "bodyfall" footstep_type = FOOTSTEP_MOB_HEAVY - attack_action_types = list(/datum/action/innate/megafauna_attack/frost_orbs, - /datum/action/innate/megafauna_attack/snowball_machine_gun, - /datum/action/innate/megafauna_attack/ice_shotgun) - /// Modifies the speed of the projectiles the demonic frost miner shoots out - var/projectile_speed_multiplier = 1 /// If the demonic frost miner is in its enraged state var/enraged = FALSE /// If the demonic frost miner is currently transforming to its enraged state var/enraging = FALSE + /// Frost orbs ability + var/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/shrapnel/frost_orbs + /// Snowball Machine gun Ability + var/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/snowball_machine_gun + /// Ice Shotgun Ability + var/datum/action/cooldown/mob_cooldown/projectile_attack/shotgun_blast/pattern/ice_shotgun /mob/living/simple_animal/hostile/megafauna/demonic_frost_miner/Initialize(mapload) . = ..() + frost_orbs = new /datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/shrapnel() + snowball_machine_gun = new /datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire() + ice_shotgun = new /datum/action/cooldown/mob_cooldown/projectile_attack/shotgun_blast/pattern() + frost_orbs.Grant(src) + snowball_machine_gun.Grant(src) + ice_shotgun.Grant(src) for(var/obj/structure/frost_miner_prism/prism_to_set in GLOB.frost_miner_prisms) prism_to_set.set_prism_light(LIGHT_COLOR_BLUE, 5) + RegisterSignal(src, COMSIG_ABILITY_STARTED, .proc/start_attack) AddElement(/datum/element/knockback, 7, FALSE, TRUE) AddElement(/datum/element/lifesteal, 50) ADD_TRAIT(src, TRAIT_NO_FLOATING_ANIM, INNATE_TRAIT) -/datum/action/innate/megafauna_attack/frost_orbs - name = "Fire Frost Orbs" - icon_icon = 'icons/mob/actions/actions_items.dmi' - button_icon_state = "sniper_zoom" - chosen_message = "You are now sending out frost orbs to track in on a target." - chosen_attack_num = 1 - -/datum/action/innate/megafauna_attack/snowball_machine_gun - name = "Fire Snowball Machine Gun" - icon_icon = 'icons/obj/guns/energy.dmi' - button_icon_state = "kineticgun" - chosen_message = "You are now firing a snowball machine gun at a target." - chosen_attack_num = 2 - -/datum/action/innate/megafauna_attack/ice_shotgun - name = "Fire Ice Shotgun" - icon_icon = 'modular_skyrat/modules/fixing_missing_icons/ballistic.dmi' //skyrat edit - button_icon_state = "shotgun" - chosen_message = "You are now firing shotgun ice blasts." - chosen_attack_num = 3 +/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner/Destroy() + QDEL_NULL(frost_orbs) + QDEL_NULL(snowball_machine_gun) + QDEL_NULL(ice_shotgun) + return ..() /mob/living/simple_animal/hostile/megafauna/demonic_frost_miner/OpenFire() - check_enraged() - projectile_speed_multiplier = 1 - enraged * 0.5 - update_cooldowns(list(COOLDOWN_UPDATE_SET_MELEE = 10 SECONDS, COOLDOWN_UPDATE_SET_RANGED = 10 SECONDS)) - if(client) - switch(chosen_attack) - if(1) - frost_orbs() - if(2) - snowball_machine_gun() - if(3) - ice_shotgun() return var/easy_attack = prob(80 - enraged * 40) @@ -101,20 +84,107 @@ Difficulty: Extremely Hard switch(chosen_attack) if(1) if(easy_attack) - frost_orbs(10, 8) + frost_orbs.shot_count = 8 + frost_orbs.shot_delay = 10 + frost_orbs.Trigger(target) else - frost_orbs(5, 16) + frost_orbs.shot_count = 16 + frost_orbs.shot_delay = 5 + frost_orbs.Trigger(target) if(2) if(easy_attack) - snowball_machine_gun() - else - INVOKE_ASYNC(src, .proc/ice_shotgun, 5, list(list(-180, -140, -100, -60, -20, 20, 60, 100, 140), list(-160, -120, -80, -40, 0, 40, 80, 120, 160))) - snowball_machine_gun(5 * 8, 5) + snowball_machine_gun.shot_count = 60 + snowball_machine_gun.default_projectile_spread = 45 + snowball_machine_gun.Trigger(target) + else if(ice_shotgun.IsAvailable()) + ice_shotgun.shot_angles = list(list(-180, -140, -100, -60, -20, 20, 60, 100, 140), list(-160, -120, -80, -40, 0, 40, 80, 120, 160)) + INVOKE_ASYNC(ice_shotgun, /datum/action/proc/Trigger, target) + snowball_machine_gun.shot_count = 5 * 8 + snowball_machine_gun.default_projectile_spread = 5 + snowball_machine_gun.StartCooldown(0) + snowball_machine_gun.Trigger(target) if(3) if(easy_attack) - ice_shotgun() + // static lists? remind me later + ice_shotgun.shot_angles = list(list(-40, -20, 0, 20, 40), list(-30, -10, 10, 30)) + ice_shotgun.Trigger(target) else - ice_shotgun(5, list(list(0, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330), list(-30, -15, 0, 15, 30))) + ice_shotgun.shot_angles = list(list(0, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330), list(-30, -15, 0, 15, 30)) + ice_shotgun.Trigger(target) + +/// Pre-ability usage stuff +/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner/proc/start_attack(mob/living/owner, datum/action/cooldown/activated) + SIGNAL_HANDLER + if(enraging) + return COMPONENT_BLOCK_ABILITY_START + if(FROST_MINER_SHOULD_ENRAGE) + INVOKE_ASYNC(src, .proc/check_enraged) + return COMPONENT_BLOCK_ABILITY_START + var/projectile_speed_multiplier = 1 - enraged * 0.5 + frost_orbs.projectile_speed_multiplier = projectile_speed_multiplier + snowball_machine_gun.projectile_speed_multiplier = projectile_speed_multiplier + ice_shotgun.projectile_speed_multiplier = projectile_speed_multiplier + +/// Checks if the demonic frost miner is ready to be enraged +/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner/proc/check_enraged() + if(!FROST_MINER_SHOULD_ENRAGE) + return + update_cooldowns(list(COOLDOWN_UPDATE_SET_MELEE = 8 SECONDS, COOLDOWN_UPDATE_SET_RANGED = 8 SECONDS)) + frost_orbs.StartCooldown(8 SECONDS) + adjustHealth(-maxHealth) + enraged = TRUE + enraging = TRUE + animate(src, pixel_y = pixel_y + 96, time = 100, easing = ELASTIC_EASING) + spin(100, 10) + SLEEP_CHECK_DEATH(60, src) + playsound(src, 'sound/effects/explosion3.ogg', 100, TRUE) + icon_state = "demonic_miner_phase2" + animate(src, pixel_y = pixel_y - 96, time = 8, flags = ANIMATION_END_NOW) + spin(8, 2) + for(var/obj/structure/frost_miner_prism/prism_to_set in GLOB.frost_miner_prisms) + prism_to_set.set_prism_light(LIGHT_COLOR_PURPLE, 5) + SLEEP_CHECK_DEATH(8, src) + for(var/mob/living/L in viewers(src)) + shake_camera(L, 3, 2) + playsound(src, 'sound/effects/meteorimpact.ogg', 100, TRUE) + ADD_TRAIT(src, TRAIT_MOVE_FLYING, FROSTMINER_ENRAGE_TRAIT) + enraging = FALSE + adjustHealth(-maxHealth) + +/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner/ex_act(severity, target) + adjustBruteLoss(-30 * severity) + visible_message(span_danger("[src] absorbs the explosion!"), span_userdanger("You absorb the explosion!")) + +/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner/Goto(target, delay, minimum_distance) + if(enraging) + return + return ..() + +/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner/MoveToTarget(list/possible_targets) + if(enraging) + return + return ..() + +/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner/Move() + if(enraging) + return + return ..() + +/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner/death(gibbed, list/force_grant) + if(health > 0) + return + var/turf/T = get_turf(src) + var/loot = rand(1, 3) + for(var/obj/structure/frost_miner_prism/prism_to_set in GLOB.frost_miner_prisms) + prism_to_set.set_prism_light(COLOR_GRAY, 1) + switch(loot) + if(1) + new /obj/item/resurrection_crystal(T) + if(2) + new /obj/item/clothing/shoes/winterboots/ice_boots/ice_trail(T) + if(3) + new /obj/item/pickaxe/drill/jackhammer/demonic(T) + return ..() /obj/projectile/colossus/frost_orb name = "frost orb" @@ -152,139 +222,6 @@ Difficulty: Extremely Hard if(isturf(target) || isobj(target)) target.ex_act(EXPLODE_HEAVY) -/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner/ex_act(severity, target) - adjustBruteLoss(-30 * severity) - visible_message(span_danger("[src] absorbs the explosion!"), span_userdanger("You absorb the explosion!")) - -/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner/Goto(target, delay, minimum_distance) - if(enraging) - return - return ..() - -/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner/MoveToTarget(list/possible_targets) - if(enraging) - return - return ..() - -/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner/Move() - if(enraging) - return - return ..() - -/// Shoots out homing frost orbs that explode into ice blast projectiles after a couple seconds -/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner/proc/frost_orbs(added_delay = 10, shoot_times = 8) - for(var/i in 1 to shoot_times) - var/turf/startloc = get_turf(src) - var/turf/endloc = get_turf(target) - if(!endloc) - break - var/obj/projectile/colossus/frost_orb/P = new(startloc) - P.preparePixelProjectile(endloc, startloc) - P.firer = src - if(target) - P.original = target - P.set_homing_target(target) - P.fire(rand(0, 360)) - addtimer(CALLBACK(P, /obj/projectile/colossus/frost_orb/proc/orb_explosion, projectile_speed_multiplier), 20) // make the orbs home in after a second - SLEEP_CHECK_DEATH(added_delay) - update_cooldowns(list(COOLDOWN_UPDATE_SET_MELEE = 4 SECONDS, COOLDOWN_UPDATE_SET_RANGED = 6 SECONDS)) - -/// Called when the orb is exploding, shoots out projectiles -/obj/projectile/colossus/frost_orb/proc/orb_explosion(projectile_speed_multiplier) - for(var/i in 0 to 5) - var/angle = i * 60 - var/turf/startloc = get_turf(src) - var/turf/endloc = get_turf(original) - if(!startloc || !endloc) - break - var/obj/projectile/colossus/ice_blast/P = new(startloc) - P.speed *= projectile_speed_multiplier - P.preparePixelProjectile(endloc, startloc, null, angle + rand(-10, 10)) - P.firer = firer - if(original) - P.original = original - P.fire() - qdel(src) - -/// Shoots out snowballs with a random spread -/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner/proc/snowball_machine_gun(shots = 60, spread = 45) - for(var/i in 1 to shots) - var/turf/startloc = get_turf(src) - var/turf/endloc = get_turf(target) - if(!endloc) - break - var/obj/projectile/P = new /obj/projectile/colossus/snowball(startloc) - P.speed *= projectile_speed_multiplier - P.preparePixelProjectile(endloc, startloc, null, rand(-spread, spread)) - P.firer = src - if(target) - P.original = target - P.fire() - SLEEP_CHECK_DEATH(1) - update_cooldowns(list(COOLDOWN_UPDATE_SET_MELEE = 1.5 SECONDS, COOLDOWN_UPDATE_SET_RANGED = 1.5 SECONDS)) - -/// Shoots out ice blasts in a shotgun like pattern -/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner/proc/ice_shotgun(shots = 5, list/patterns = list(list(-40, -20, 0, 20, 40), list(-30, -10, 10, 30))) - for(var/i in 1 to shots) - var/list/pattern = patterns[i % length(patterns) + 1] // alternating patterns - for(var/spread in pattern) - var/turf/startloc = get_turf(src) - var/turf/endloc = get_turf(target) - if(!endloc) - break - var/obj/projectile/P = new /obj/projectile/colossus/ice_blast(startloc) - P.speed *= projectile_speed_multiplier - P.preparePixelProjectile(endloc, startloc, null, spread) - P.firer = src - if(target) - P.original = target - P.fire() - SLEEP_CHECK_DEATH(8) - update_cooldowns(list(COOLDOWN_UPDATE_SET_MELEE = 1.5 SECONDS, COOLDOWN_UPDATE_SET_RANGED = 2 SECONDS)) - -/// Checks if the demonic frost miner is ready to be enraged -/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner/proc/check_enraged() - if(enraged) - return - if(health > maxHealth*0.25) - return - update_cooldowns(list(COOLDOWN_UPDATE_SET_MELEE = 8 SECONDS, COOLDOWN_UPDATE_SET_RANGED = 8 SECONDS)) - adjustHealth(-maxHealth) - enraged = TRUE - enraging = TRUE - animate(src, pixel_y = pixel_y + 96, time = 100, easing = ELASTIC_EASING) - spin(100, 10) - SLEEP_CHECK_DEATH(60) - playsound(src, 'sound/effects/explosion3.ogg', 100, TRUE) - icon_state = "demonic_miner_phase2" - animate(src, pixel_y = pixel_y - 96, time = 8, flags = ANIMATION_END_NOW) - spin(8, 2) - for(var/obj/structure/frost_miner_prism/prism_to_set in GLOB.frost_miner_prisms) - prism_to_set.set_prism_light(LIGHT_COLOR_PURPLE, 5) - SLEEP_CHECK_DEATH(8) - for(var/mob/living/L in viewers(src)) - shake_camera(L, 3, 2) - playsound(src, 'sound/effects/meteorimpact.ogg', 100, TRUE) - ADD_TRAIT(src, TRAIT_MOVE_FLYING, FROSTMINER_ENRAGE_TRAIT) - enraging = FALSE - adjustHealth(-maxHealth) - -/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner/death(gibbed, list/force_grant) - if(health > 0) - return - var/turf/T = get_turf(src) - var/loot = rand(1, 3) - for(var/obj/structure/frost_miner_prism/prism_to_set in GLOB.frost_miner_prisms) - prism_to_set.set_prism_light(COLOR_GRAY, 1) - switch(loot) - if(1) - new /obj/item/resurrection_crystal(T) - if(2) - new /obj/item/clothing/shoes/winterboots/ice_boots/ice_trail(T) - if(3) - new /obj/item/pickaxe/drill/jackhammer/demonic(T) - return ..() - /obj/item/resurrection_crystal name = "resurrection crystal" desc = "When used by anything holding it, this crystal gives them a second chance at life if they die." 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 0d4773a685a..26d47ba42c0 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm @@ -1,13 +1,12 @@ -#define DRAKE_SWOOP_HEIGHT 270 //how high up drakes go, in pixels -#define DRAKE_SWOOP_DIRECTION_CHANGE_RANGE 5 //the range our x has to be within to not change the direction we slam from - -#define SWOOP_DAMAGEABLE 1 -#define SWOOP_INVULNERABLE 2 - ///used whenever the drake generates a hotspot #define DRAKE_FIRE_TEMP 500 ///used whenever the drake generates a hotspot #define DRAKE_FIRE_EXPOSURE 50 +///used to see if the drake is enraged or not +#define DRAKE_ENRAGED (health < maxHealth*0.5) + +#define SWOOP_DAMAGEABLE 1 +#define SWOOP_INVULNERABLE 2 /*£ * @@ -72,211 +71,100 @@ deathmessage = "collapses into a pile of bones, its flesh sloughing away." deathsound = 'sound/magic/demon_dies.ogg' footstep_type = FOOTSTEP_MOB_HEAVY - 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 + /// Fire cone ability + var/datum/action/cooldown/mob_cooldown/fire_breath/cone/fire_cone + /// Meteors ability + var/datum/action/cooldown/mob_cooldown/meteors/meteors + /// Mass fire ability + var/datum/action/cooldown/mob_cooldown/fire_breath/mass_fire/mass_fire + /// Lava swoop ability + var/datum/action/cooldown/mob_cooldown/lava_swoop/lava_swoop -/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/Initialize() + . = ..() + fire_cone = new /datum/action/cooldown/mob_cooldown/fire_breath/cone() + meteors = new /datum/action/cooldown/mob_cooldown/meteors() + mass_fire = new /datum/action/cooldown/mob_cooldown/fire_breath/mass_fire() + lava_swoop = new /datum/action/cooldown/mob_cooldown/lava_swoop() + fire_cone.Grant(src) + meteors.Grant(src) + mass_fire.Grant(src) + lava_swoop.Grant(src) + RegisterSignal(src, COMSIG_ABILITY_STARTED, .proc/start_attack) + RegisterSignal(src, COMSIG_ABILITY_FINISHED, .proc/finished_attack) + RegisterSignal(src, COMSIG_SWOOP_INVULNERABILITY_STARTED, .proc/swoop_invulnerability_started) + RegisterSignal(src, COMSIG_LAVA_ARENA_FAILED, .proc/on_arena_fail) -/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 - -/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 - -/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/Destroy() + QDEL_NULL(fire_cone) + QDEL_NULL(meteors) + QDEL_NULL(mass_fire) + QDEL_NULL(lava_swoop) + return ..() /mob/living/simple_animal/hostile/megafauna/dragon/OpenFire() if(swooping) return - anger_modifier = clamp(((maxHealth - health)/50),0,20) - update_cooldowns(list(COOLDOWN_UPDATE_SET_RANGED = ranged_cooldown_time), ignore_staggered = TRUE) - if(client) - switch(chosen_attack) - if(1) - fire_cone(meteors = FALSE) - if(2) - fire_cone() - if(3) - mass_fire() - if(4) - lava_swoop() return if(prob(15 + anger_modifier)) - lava_swoop() - - else if(prob(10+anger_modifier)) - shoot_fire_attack() - else - 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) + if(DRAKE_ENRAGED) + // Lava Arena + lava_swoop.Trigger(target) + return + // Lava Pools + if(lava_swoop.Trigger(target)) + SLEEP_CHECK_DEATH(0, src) + fire_cone.StartCooldown(0) + fire_cone.Trigger(target) + meteors.StartCooldown(0) + INVOKE_ASYNC(meteors, /datum/action/proc/Trigger, target) + return + else if(prob(10+anger_modifier) && DRAKE_ENRAGED) + mass_fire.Trigger(target) return - target.visible_message(span_boldwarning("Fire rains from the sky!")) - var/turf/targetturf = get_turf(target) - for(var/turf/turf as anything in RANGE_TURFS(9,targetturf)) - if(prob(11)) - new /obj/effect/temp_visual/target(turf) + if(fire_cone.Trigger(target)) + if(prob(50)) + meteors.StartCooldown(0) + meteors.Trigger(target) -/mob/living/simple_animal/hostile/megafauna/dragon/proc/lava_pools(amount, delay = 0.8) - if(!target) - return - target.visible_message(span_boldwarning("Lava starts to pool up around you!")) +/mob/living/simple_animal/hostile/megafauna/dragon/proc/start_attack(mob/living/owner, datum/action/cooldown/activated) + SIGNAL_HANDLER + if(activated == lava_swoop) + icon_state = "shadow" + swooping = SWOOP_DAMAGEABLE - while(amount > 0) - if(QDELETED(target)) - break - var/turf/TT = get_turf(target) - var/turf/T = pick(RANGE_TURFS(1,TT)) - new /obj/effect/temp_visual/lava_warning(T, 60) // longer reset time for the lava - amount-- - SLEEP_CHECK_DEATH(delay) +/mob/living/simple_animal/hostile/megafauna/dragon/proc/swoop_invulnerability_started() + SIGNAL_HANDLER + swooping = SWOOP_INVULNERABLE -/mob/living/simple_animal/hostile/megafauna/dragon/proc/lava_swoop(amount = 30) - if(health < maxHealth * 0.5) - return swoop_attack(lava_arena = TRUE, swoop_cooldown = 6 SECONDS) - INVOKE_ASYNC(src, .proc/lava_pools, amount) - swoop_attack(FALSE, target, 1000) // longer cooldown until it gets reset below - SLEEP_CHECK_DEATH(0) - fire_cone() - if(health < maxHealth*0.5) - SLEEP_CHECK_DEATH(10) - fire_cone() - SLEEP_CHECK_DEATH(10) - fire_cone() - update_cooldowns(list(COOLDOWN_UPDATE_SET_MELEE = 4 SECONDS, COOLDOWN_UPDATE_SET_RANGED = 4 SECONDS)) +/mob/living/simple_animal/hostile/megafauna/dragon/proc/finished_attack(mob/living/owner, datum/action/cooldown/finished) + SIGNAL_HANDLER + if(finished == lava_swoop) + icon_state = initial(icon_state) + swooping = NONE -/mob/living/simple_animal/hostile/megafauna/dragon/proc/mass_fire(spiral_count = 12, range = 15, times = 3) - SLEEP_CHECK_DEATH(0) - for(var/i = 1 to times) - update_cooldowns(list(COOLDOWN_UPDATE_SET_MELEE = 5 SECONDS, COOLDOWN_UPDATE_SET_RANGED = 5 SECONDS)) - playsound(get_turf(src),'sound/magic/fireball.ogg', 200, TRUE) - var/increment = 360 / spiral_count - for(var/j = 1 to spiral_count) - var/list/turfs = line_target(j * increment + i * increment / 2, range, src) - INVOKE_ASYNC(src, .proc/fire_line, turfs) - SLEEP_CHECK_DEATH(25) - update_cooldowns(list(COOLDOWN_UPDATE_SET_MELEE = 3 SECONDS, COOLDOWN_UPDATE_SET_RANGED = 3 SECONDS)) - -/mob/living/simple_animal/hostile/megafauna/dragon/proc/lava_arena() - if(!target) - return - target.visible_message(span_boldwarning("[src] encases you in an arena of fire!")) - var/amount = 3 - var/turf/center = get_turf(target) - var/list/walled = RANGE_TURFS(3, center) - RANGE_TURFS(2, center) - var/list/drakewalls = list() - for(var/turf/T in walled) - drakewalls += new /obj/effect/temp_visual/drakewall(T) // no people with lava immunity can just run away from the attack for free - var/list/indestructible_turfs = list() - for(var/turf/T in RANGE_TURFS(2, center)) - if(istype(T, /turf/open/indestructible)) - continue - if(!istype(T, /turf/closed/indestructible)) - T.ChangeTurf(/turf/open/floor/plating/asteroid/basalt/lava_land_surface, flags = CHANGETURF_INHERIT_AIR) - else - indestructible_turfs += T - SLEEP_CHECK_DEATH(10) // give them a bit of time to realize what attack is actually happening - - var/list/turfs = RANGE_TURFS(2, center) - var/list/mobs_with_clients = list() - while(amount > 0) - var/list/empty = indestructible_turfs.Copy() // can't place safe turfs on turfs that weren't changed to be open - var/any_attack = FALSE - for(var/turf/T in turfs) - for(var/mob/living/L in T.contents) - if(L.client) - empty += pick(((RANGE_TURFS(2, L) - RANGE_TURFS(1, L)) & turfs) - empty) // picks a turf within 2 of the creature not outside or in the shield - any_attack = TRUE - mobs_with_clients |= L - for(var/obj/vehicle/sealed/mecha/M in T.contents) - empty += pick(((RANGE_TURFS(2, M) - RANGE_TURFS(1, M)) & turfs) - empty) - any_attack = TRUE - if(!any_attack) // nothing to attack in the arena, time for enraged attack if we still have a cliented target. - for(var/obj/effect/temp_visual/drakewall/D in drakewalls) - qdel(D) - for(var/a in mobs_with_clients) - var/mob/living/L = a - if(!QDELETED(L) && L.client) - return FALSE - return TRUE - for(var/turf/T in turfs) - if(!(T in empty)) - new /obj/effect/temp_visual/lava_warning(T) - else if(!istype(T, /turf/closed/indestructible)) - new /obj/effect/temp_visual/lava_safe(T) - amount-- - SLEEP_CHECK_DEATH(24) - return TRUE // attack finished completely +/mob/living/simple_animal/hostile/megafauna/dragon/proc/on_arena_fail() + SIGNAL_HANDLER + INVOKE_ASYNC(src, .proc/arena_escape_enrage) /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 - SLEEP_CHECK_DEATH(0) - update_cooldowns(list(COOLDOWN_UPDATE_SET_MELEE = 8 SECONDS, COOLDOWN_UPDATE_SET_RANGED = 8 SECONDS)) + SLEEP_CHECK_DEATH(0, src) visible_message(span_boldwarning("[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 set_light_range(10) - SLEEP_CHECK_DEATH(10) // run. - mass_fire(20, 15, 3) + SLEEP_CHECK_DEATH(5 SECONDS, src) // run. + mass_fire.Activate(target) + mass_fire.StartCooldown(8 SECONDS) move_to_delay = initial(move_to_delay) remove_atom_colour(TEMPORARY_COLOUR_PRIORITY) set_light_range(initial(light_range)) -/mob/living/simple_animal/hostile/megafauna/dragon/proc/fire_cone(atom/at = target, meteors = TRUE) - playsound(get_turf(src),'sound/magic/fireball.ogg', 200, TRUE) - 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) - INVOKE_ASYNC(src, .proc/fire_line, turfs) - turfs = line_target(0, range, at) - INVOKE_ASYNC(src, .proc/fire_line, turfs) - turfs = line_target(40, range, at) - INVOKE_ASYNC(src, .proc/fire_line, turfs) - -/mob/living/simple_animal/hostile/megafauna/dragon/proc/line_target(offset, range, atom/at = target) - if(!at) - return - var/turf/T = get_ranged_target_turf_direct(src, at, range, offset) - return (get_line(src, T) - get_turf(src)) - -/mob/living/simple_animal/hostile/megafauna/dragon/proc/fire_line(list/turfs) - SLEEP_CHECK_DEATH(0) - dragon_fire_line(src, turfs) - //fire line keeps going even if dragon is deleted /proc/dragon_fire_line(atom/source, list/turfs, frozen = FALSE) var/list/hit_list = list() @@ -307,105 +195,14 @@ M.take_damage(45, BRUTE, MELEE, 1) sleep(1.5) -/mob/living/simple_animal/hostile/megafauna/dragon/proc/swoop_attack(lava_arena = FALSE, atom/movable/manual_target, swoop_cooldown = 30) - if(stat || swooping) - return - if(manual_target) - GiveTarget(manual_target) - if(!target) - return - stop_automated_movement = TRUE - swooping |= SWOOP_DAMAGEABLE - set_density(FALSE) - icon_state = "shadow" - visible_message(span_boldwarning("[src] swoops up high!")) - - var/negative - var/initial_x = x - if(target.x < initial_x) //if the target's x is lower than ours, swoop to the left - negative = TRUE - else if(target.x > initial_x) - negative = FALSE - else if(target.x == initial_x) //if their x is the same, pick a direction - negative = prob(50) - var/obj/effect/temp_visual/dragon_flight/F = new /obj/effect/temp_visual/dragon_flight(loc, negative) - - negative = !negative //invert it for the swoop down later - - var/oldtransform = transform - alpha = 255 - animate(src, alpha = 204, transform = matrix()*0.9, time = 3, easing = BOUNCE_EASING) - for(var/i in 1 to 3) - sleep(1) - if(QDELETED(src) || stat == DEAD) //we got hit and died, rip us - qdel(F) - if(stat == DEAD) - swooping &= ~SWOOP_DAMAGEABLE - animate(src, alpha = 255, transform = oldtransform, time = 0, flags = ANIMATION_END_NOW) //reset immediately - return - animate(src, alpha = 100, transform = matrix()*0.7, time = 7) - swooping |= SWOOP_INVULNERABLE - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - SLEEP_CHECK_DEATH(7) - - while(target && loc != get_turf(target)) - forceMove(get_step(src, get_dir(src, target))) - SLEEP_CHECK_DEATH(0.5) - - // Ash drake flies onto its target and rains fire down upon them - var/descentTime = 10 - var/lava_success = TRUE - if(lava_arena) - lava_success = lava_arena() - - - //ensure swoop direction continuity. - if(negative) - if(ISINRANGE(x, initial_x + 1, initial_x + DRAKE_SWOOP_DIRECTION_CHANGE_RANGE)) - negative = FALSE - else - if(ISINRANGE(x, initial_x - DRAKE_SWOOP_DIRECTION_CHANGE_RANGE, initial_x - 1)) - negative = TRUE - 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_CHECK_DEATH(descentTime) - swooping &= ~SWOOP_INVULNERABLE - mouse_opacity = initial(mouse_opacity) - icon_state = "dragon" - playsound(loc, 'sound/effects/meteorimpact.ogg', 200, TRUE) - for(var/mob/living/L in orange(1, src)) - if(L.stat) - visible_message(span_warning("[src] slams down on [L], crushing [L.p_them()]!")) - L.gib() - else - L.adjustBruteLoss(75) - if(L && !QDELETED(L)) // Some mobs are deleted on death - var/throw_dir = get_dir(src, L) - if(L.loc == loc) - throw_dir = pick(GLOB.alldirs) - var/throwtarget = get_edge_target_turf(src, throw_dir) - L.throw_at(throwtarget, 3) - visible_message(span_warning("[L] is thrown clear of [src]!")) - for(var/obj/vehicle/sealed/mecha/M in orange(1, src)) - M.take_damage(75, BRUTE, MELEE, 1) - - for(var/mob/M in range(7, src)) - shake_camera(M, 15, 1) - - set_density(TRUE) - SLEEP_CHECK_DEATH(1) - swooping &= ~SWOOP_DAMAGEABLE - update_cooldowns(list(COOLDOWN_UPDATE_SET_MELEE = swoop_cooldown, COOLDOWN_UPDATE_SET_RANGED = swoop_cooldown)) - if(!lava_success) - arena_escape_enrage() - /mob/living/simple_animal/hostile/megafauna/dragon/ex_act(severity, target) if(severity <= EXPLODE_LIGHT) return FALSE return ..() /mob/living/simple_animal/hostile/megafauna/dragon/adjustHealth(amount, updating_health = TRUE, forced = FALSE) + anger_modifier = clamp(((maxHealth - health)/60),0,20) + lava_swoop.enraged = DRAKE_ENRAGED if(!forced && (swooping & SWOOP_INVULNERABLE)) return FALSE return ..() @@ -436,6 +233,7 @@ layer = BELOW_MOB_LAYER light_range = 2 duration = 13 + var/mob/owner /obj/effect/temp_visual/lava_warning/Initialize(mapload, reset_time = 10) . = ..() @@ -449,7 +247,7 @@ sleep(duration) playsound(T,'sound/magic/fireball.ogg', 200, TRUE) - for(var/mob/living/L in T.contents) + for(var/mob/living/L in T.contents - owner) if(istype(L, /mob/living/simple_animal/hostile/megafauna/dragon)) continue L.adjustFireLoss(10) @@ -485,53 +283,6 @@ light_range = 2 duration = 13 -/obj/effect/temp_visual/dragon_swoop - name = "certain death" - desc = "Don't just stand there, move!" - icon = 'icons/effects/96x96.dmi' - icon_state = "landing" - layer = BELOW_MOB_LAYER - pixel_x = -32 - pixel_y = -32 - color = "#FF0000" - duration = 10 - -/obj/effect/temp_visual/dragon_flight - icon = 'icons/mob/lavaland/64x64megafauna.dmi' - icon_state = "dragon" - layer = ABOVE_ALL_MOB_LAYER - pixel_x = -16 - duration = 10 - randomdir = FALSE - -/obj/effect/temp_visual/dragon_flight/Initialize(mapload, negative) - . = ..() - INVOKE_ASYNC(src, .proc/flight, negative) - -/obj/effect/temp_visual/dragon_flight/proc/flight(negative) - if(negative) - animate(src, pixel_x = -DRAKE_SWOOP_HEIGHT*0.1, pixel_z = DRAKE_SWOOP_HEIGHT*0.15, time = 3, easing = BOUNCE_EASING) - else - animate(src, pixel_x = DRAKE_SWOOP_HEIGHT*0.1, pixel_z = DRAKE_SWOOP_HEIGHT*0.15, time = 3, easing = BOUNCE_EASING) - sleep(3) - icon_state = "swoop" - if(negative) - animate(src, pixel_x = -DRAKE_SWOOP_HEIGHT, pixel_z = DRAKE_SWOOP_HEIGHT, time = 7) - else - animate(src, pixel_x = DRAKE_SWOOP_HEIGHT, pixel_z = DRAKE_SWOOP_HEIGHT, time = 7) - -/obj/effect/temp_visual/dragon_flight/end - pixel_x = DRAKE_SWOOP_HEIGHT - pixel_z = DRAKE_SWOOP_HEIGHT - duration = 10 - -/obj/effect/temp_visual/dragon_flight/end/flight(negative) - if(negative) - pixel_x = -DRAKE_SWOOP_HEIGHT - animate(src, pixel_x = -16, pixel_z = 0, time = 5) - else - animate(src, pixel_x = -16, pixel_z = 0, time = 5) - /obj/effect/temp_visual/fireball icon = 'icons/obj/wizard.dmi' icon_state = "fireball" @@ -593,15 +344,19 @@ 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, span_warning("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 + 20 SECONDS // needs separate cooldown or cant use fire attacks +/mob/living/simple_animal/hostile/megafauna/dragon/lesser/Initialize() + . = ..() + fire_cone.Remove(src) + meteors.Remove(src) + mass_fire.Remove(src) + lava_swoop.cooldown_time = 20 SECONDS + +/mob/living/simple_animal/hostile/megafauna/dragon/lesser/adjustHealth(amount, updating_health = TRUE, forced = FALSE) + . = ..() + lava_swoop.enraged = FALSE /mob/living/simple_animal/hostile/megafauna/dragon/lesser/grant_achievement(medaltype,scoretype) return + +#undef SWOOP_DAMAGEABLE +#undef SWOOP_INVULNERABLE 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 fcbfca23244..b3cbdf65e7b 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm @@ -220,7 +220,7 @@ Difficulty: Hard visible_message(span_hierophant("\"Mx ampp rsx iwgeti.\"")) var/oldcolor = color animate(src, color = "#660099", time = 6) - SLEEP_CHECK_DEATH(6) + SLEEP_CHECK_DEATH(6, src) 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 @@ -228,10 +228,10 @@ Difficulty: Hard blinking = FALSE blink(target) blinking = TRUE - SLEEP_CHECK_DEATH(4 + target_slowness) + SLEEP_CHECK_DEATH(4 + target_slowness, src) animate(src, color = oldcolor, time = 8) addtimer(CALLBACK(src, /atom/proc/update_atom_colour), 8) - SLEEP_CHECK_DEATH(8) + SLEEP_CHECK_DEATH(8, src) blinking = FALSE else blink(target) @@ -242,17 +242,17 @@ Difficulty: Hard blinking = TRUE var/oldcolor = color animate(src, color = "#660099", time = 6) - SLEEP_CHECK_DEATH(6) + SLEEP_CHECK_DEATH(6, src) 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) + SLEEP_CHECK_DEATH(6 + target_slowness, src) animate(src, color = oldcolor, time = 8) addtimer(CALLBACK(src, /atom/proc/update_atom_colour), 8) - SLEEP_CHECK_DEATH(8) + SLEEP_CHECK_DEATH(8, src) blinking = FALSE @@ -262,7 +262,7 @@ Difficulty: Hard blinking = TRUE var/oldcolor = color animate(src, color = "#660099", time = 6) - SLEEP_CHECK_DEATH(6) + SLEEP_CHECK_DEATH(6, src) var/list/targets = ListTargets() var/list/cardinal_copy = GLOB.cardinals.Copy() while(targets.len && cardinal_copy.len) @@ -276,11 +276,11 @@ Difficulty: Hard 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) + SLEEP_CHECK_DEATH(8 + target_slowness, src) update_cooldowns(list(COOLDOWN_UPDATE_SET_CHASER = chaser_cooldown_time)) animate(src, color = oldcolor, time = 8) addtimer(CALLBACK(src, /atom/proc/update_atom_colour), 8) - SLEEP_CHECK_DEATH(8) + SLEEP_CHECK_DEATH(8, src) blinking = FALSE /mob/living/simple_animal/hostile/megafauna/hierophant/proc/blasts(mob/victim, list/directions = GLOB.cardinals) //fires cross blasts with a delay @@ -294,7 +294,7 @@ Difficulty: Hard else new /obj/effect/temp_visual/hierophant/telegraph(T, src) playsound(T,'sound/effects/bin_close.ogg', 200, TRUE) - SLEEP_CHECK_DEATH(2) + SLEEP_CHECK_DEATH(2, src) new /obj/effect/temp_visual/hierophant/blast/damaging(T, src, FALSE) for(var/d in directions) INVOKE_ASYNC(src, .proc/blast_wall, T, d) @@ -332,7 +332,7 @@ Difficulty: Hard HS.setDir(set_dir) previousturf = J J = get_step(previousturf, set_dir) - SLEEP_CHECK_DEATH(0.5) + SLEEP_CHECK_DEATH(0.5, src) /mob/living/simple_animal/hostile/megafauna/hierophant/proc/blink(mob/victim) //blink to a target if(blinking || !victim) @@ -344,7 +344,7 @@ Difficulty: Hard playsound(T,'sound/magic/wand_teleport.ogg', 200, TRUE) playsound(source,'sound/machines/airlockopen.ogg', 200, TRUE) blinking = TRUE - SLEEP_CHECK_DEATH(2) //short delay before we start... + SLEEP_CHECK_DEATH(2, src) //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)) @@ -354,17 +354,17 @@ Difficulty: Hard var/obj/effect/temp_visual/hierophant/blast/damaging/B = new(t, src, FALSE) B.damage = 30 animate(src, alpha = 0, time = 2, easing = EASE_OUT) //fade out - SLEEP_CHECK_DEATH(1) + SLEEP_CHECK_DEATH(1, src) visible_message(span_hierophant_warning("[src] fades out!")) set_density(FALSE) - SLEEP_CHECK_DEATH(2) + SLEEP_CHECK_DEATH(2, src) forceMove(T) - SLEEP_CHECK_DEATH(1) + SLEEP_CHECK_DEATH(1, src) animate(src, alpha = 255, time = 2, easing = EASE_IN) //fade IN - SLEEP_CHECK_DEATH(1) + SLEEP_CHECK_DEATH(1, src) set_density(TRUE) visible_message(span_hierophant_warning("[src] fades in!")) - SLEEP_CHECK_DEATH(1) //at this point the blasts we made detonate + SLEEP_CHECK_DEATH(1, src) //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 @@ -375,7 +375,7 @@ Difficulty: Hard return new /obj/effect/temp_visual/hierophant/telegraph(T, src) playsound(T,'sound/effects/bin_close.ogg', 200, TRUE) - SLEEP_CHECK_DEATH(2) + SLEEP_CHECK_DEATH(2, src) for(var/t in RANGE_TURFS(1, T)) new /obj/effect/temp_visual/hierophant/blast/damaging(t, src, FALSE) diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/wendigo.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/wendigo.dm index 29848183f61..15cbaea6893 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/wendigo.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/wendigo.dm @@ -206,17 +206,17 @@ Difficulty: Hard /mob/living/simple_animal/hostile/megafauna/wendigo/proc/shockwave_scream() can_move = FALSE COOLDOWN_START(src, scream_cooldown, scream_cooldown_time) - SLEEP_CHECK_DEATH(5) + SLEEP_CHECK_DEATH(5, src) playsound(loc, 'sound/magic/demon_dies.ogg', 600, FALSE, 10) animate(src, pixel_z = rand(5, 15), time = 1, loop = 20) animate(pixel_z = 0, time = 1) for(var/mob/living/dizzy_target in get_hearers_in_view(7, src) - src) dizzy_target.Dizzy(6) to_chat(dizzy_target, span_danger("The wendigo screams loudly!")) - SLEEP_CHECK_DEATH(1 SECONDS) + SLEEP_CHECK_DEATH(1 SECONDS, src) spiral_attack() update_cooldowns(list(COOLDOWN_UPDATE_SET_MELEE = 3 SECONDS, COOLDOWN_UPDATE_SET_RANGED = 3 SECONDS)) - SLEEP_CHECK_DEATH(3 SECONDS) + SLEEP_CHECK_DEATH(3 SECONDS, src) can_move = TRUE /// Shoots shockwave projectiles in a random preset pattern @@ -236,7 +236,7 @@ Difficulty: Hard shockwave.firer = src shockwave.speed = 3 - WENDIGO_ENRAGED shockwave.fire(angle) - SLEEP_CHECK_DEATH(6 - WENDIGO_ENRAGED * 2) + SLEEP_CHECK_DEATH(6 - WENDIGO_ENRAGED * 2, src) if("Spiral") var/shots_spiral = WENDIGO_SPIRAL_SHOTCOUNT var/angle_to_target = get_angle(src, target) @@ -250,7 +250,7 @@ Difficulty: Hard shockwave.firer = src shockwave.damage = 15 shockwave.fire(angle) - SLEEP_CHECK_DEATH(1) + SLEEP_CHECK_DEATH(1, src) if("Wave") var/shots_per = WENDIGO_WAVE_SHOTCOUNT var/difference = 360 / shots_per @@ -264,7 +264,7 @@ Difficulty: Hard shockwave.speed = 8 shockwave.wave_speed = 10 * wave_direction shockwave.fire(angle) - SLEEP_CHECK_DEATH(2) + SLEEP_CHECK_DEATH(2, src) /mob/living/simple_animal/hostile/megafauna/wendigo/death(gibbed, list/force_grant) if(health > 0) diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/ice_demon.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/ice_demon.dm index 7c6cf6d4de1..f36397d2d1d 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/ice_demon.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/ice_demon.dm @@ -69,7 +69,7 @@ return ..() var/turf/end = pick(possible_ends) do_teleport(src, end, 0, channel=TELEPORT_CHANNEL_BLUESPACE, forced = TRUE) - SLEEP_CHECK_DEATH(8) + SLEEP_CHECK_DEATH(8, src) return ..() /mob/living/simple_animal/hostile/asteroid/ice_demon/Life(delta_time = SSMOBS_DT, times_fired) diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/lobstrosity.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/lobstrosity.dm index c0ddea59c12..e4ac66719e9 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/lobstrosity.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/lobstrosity.dm @@ -28,18 +28,26 @@ weather_immunities = list(TRAIT_SNOWSTORM_IMMUNE) vision_range = 5 aggro_vision_range = 7 - charger = TRUE - charge_distance = 4 butcher_results = list(/obj/item/food/meat/crab = 2, /obj/item/stack/sheet/bone = 2) robust_searching = TRUE footstep_type = FOOTSTEP_MOB_CLAW gold_core_spawnable = HOSTILE_SPAWN + /// Charging ability + var/datum/action/cooldown/mob_cooldown/charge/basic_charge/charge -/mob/living/simple_animal/hostile/asteroid/lobstrosity/ranged_secondary_attack(atom/target, modifiers) - if(COOLDOWN_FINISHED(src, charge_cooldown)) - INVOKE_ASYNC(src, /mob/living/simple_animal/hostile/.proc/enter_charge, target) - else - to_chat(src, span_notice("Your charge is still on cooldown!")) +/mob/living/simple_animal/hostile/asteroid/lobstrosity/Initialize(mapload) + . = ..() + charge = new /datum/action/cooldown/mob_cooldown/charge/basic_charge() + charge.Grant(src) + +/mob/living/simple_animal/hostile/asteroid/lobstrosity/Destroy() + QDEL_NULL(charge) + return ..() + +/mob/living/simple_animal/hostile/asteroid/lobstrosity/OpenFire() + if(client) + return + charge.Trigger(target) /mob/living/simple_animal/hostile/asteroid/lobstrosity/lava name = "tropical lobstrosity" diff --git a/modular_skyrat/master_files/code/modules/awaymission/mission_code/black_mesa.dm b/modular_skyrat/master_files/code/modules/awaymission/mission_code/black_mesa.dm index 8ad9dc05b4f..d475c1ebb2f 100644 --- a/modular_skyrat/master_files/code/modules/awaymission/mission_code/black_mesa.dm +++ b/modular_skyrat/master_files/code/modules/awaymission/mission_code/black_mesa.dm @@ -109,7 +109,6 @@ minbodytemp = 0 maxbodytemp = 1500 - charger = TRUE loot = list(/obj/item/stack/sheet/bluespace_crystal) alert_sounds = list( 'modular_skyrat/master_files/sound/blackmesa/houndeye/he_alert1.ogg', @@ -118,15 +117,23 @@ 'modular_skyrat/master_files/sound/blackmesa/houndeye/he_alert4.ogg', 'modular_skyrat/master_files/sound/blackmesa/houndeye/he_alert5.ogg' ) + /// Charging ability + var/datum/action/cooldown/mob_cooldown/charge/basic_charge/charge -/mob/living/simple_animal/hostile/blackmesa/xen/houndeye/enter_charge(atom/target) - playsound(src, pick(list( - 'modular_skyrat/master_files/sound/blackmesa/houndeye/charge3.ogg', - 'modular_skyrat/master_files/sound/blackmesa/houndeye/charge3.ogg', - 'modular_skyrat/master_files/sound/blackmesa/houndeye/charge3.ogg' - )), 100) +/mob/living/simple_animal/hostile/blackmesa/xen/houndeye/Initialize(mapload) + . = ..() + charge = new /datum/action/cooldown/mob_cooldown/charge/basic_charge() + charge.Grant(src) + +/mob/living/simple_animal/hostile/blackmesa/xen/houndeye/Destroy() + QDEL_NULL(charge) return ..() +/mob/living/simple_animal/hostile/blackmesa/xen/houndeye/OpenFire() + if(client) + return + charge.Trigger(target) + /mob/living/simple_animal/hostile/blackmesa/xen/headcrab name = "headcrab" desc = "Don't let it latch onto your hea-... hey, that's kinda cool." @@ -149,23 +156,29 @@ melee_damage_upper = 17 attack_sound = 'sound/weapons/bite.ogg' gold_core_spawnable = HOSTILE_SPAWN - charger = TRUE - charge_frequency = 3 SECONDS loot = list(/obj/item/stack/sheet/bone) alert_sounds = list( 'modular_skyrat/master_files/sound/blackmesa/headcrab/alert1.ogg' ) var/is_zombie = FALSE var/mob/living/carbon/human/oldguy + /// Charging ability + var/datum/action/cooldown/mob_cooldown/charge/basic_charge/charge -/mob/living/simple_animal/hostile/blackmesa/xen/headcrab/handle_charge_target(atom/target) - playsound(src, pick(list( - 'modular_skyrat/master_files/sound/blackmesa/headcrab/attack1.ogg', - 'modular_skyrat/master_files/sound/blackmesa/headcrab/attack2.ogg', - 'modular_skyrat/master_files/sound/blackmesa/headcrab/attack3.ogg' - )), 100) +/mob/living/simple_animal/hostile/blackmesa/xen/headcrab/Initialize(mapload) + . = ..() + charge = new /datum/action/cooldown/mob_cooldown/charge/basic_charge() + charge.Grant(src) + +/mob/living/simple_animal/hostile/blackmesa/xen/headcrab/Destroy() + QDEL_NULL(charge) return ..() +/mob/living/simple_animal/hostile/blackmesa/xen/headcrab/OpenFire() + if(client) + return + charge.Trigger(target) + /mob/living/simple_animal/hostile/blackmesa/xen/headcrab/death(gibbed) . = ..() playsound(src, pick(list( @@ -208,7 +221,6 @@ oldguy = zombified_human update_appearance() visible_message(span_warning("The corpse of [zombified_human.name] suddenly rises!")) - charger = FALSE return TRUE /mob/living/simple_animal/hostile/blackmesa/xen/headcrab/death(gibbed) diff --git a/modular_skyrat/modules/gladiator/code/modules/mob/living/simple_animal/hostile/megafauna/markedone.dm b/modular_skyrat/modules/gladiator/code/modules/mob/living/simple_animal/hostile/megafauna/markedone.dm index 888d68cbde9..bdcc723197e 100644 --- a/modular_skyrat/modules/gladiator/code/modules/mob/living/simple_animal/hostile/megafauna/markedone.dm +++ b/modular_skyrat/modules/gladiator/code/modules/mob/living/simple_animal/hostile/megafauna/markedone.dm @@ -255,7 +255,7 @@ visible_message(span_userdanger("[src] lifts his ancient blade, and prepares to spin!")) spinning = TRUE animate(src, color = "#ff6666", 10) - SLEEP_CHECK_DEATH(5) + SLEEP_CHECK_DEATH(5, src) var/list/spinningturfs = list() var/current_angle = 360 while(current_angle > 0) @@ -291,7 +291,7 @@ face_atom(target) visible_message(span_userdanger("[src] lifts his arm, and prepares to charge!")) animate(src, color = "#ff6666", 3) - SLEEP_CHECK_DEATH(4) + SLEEP_CHECK_DEATH(4, src) face_atom(target) minimum_distance = 0 charging = TRUE @@ -310,7 +310,7 @@ /mob/living/simple_animal/hostile/megafauna/gladiator/proc/teleport(atom/target) var/turf/targeted = get_step(target, target.dir) new /obj/effect/temp_visual/small_smoke/halfsecond(get_turf(src)) - SLEEP_CHECK_DEATH(4) + SLEEP_CHECK_DEATH(4, src) if(istype(targeted) && !ischasm(targeted) && !istype(targeted, /turf/open/openspace)) new /obj/effect/temp_visual/small_smoke/halfsecond(targeted) forceMove(targeted) diff --git a/tgstation.dme b/tgstation.dme index a7042862376..7a956902468 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -230,6 +230,7 @@ #include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_movable.dm" #include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_movement.dm" #include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_x_act.dm" +#include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_abilities.dm" #include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_carbon.dm" #include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_living.dm" #include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_main.dm" @@ -604,6 +605,15 @@ #include "code\datums\achievements\misc_scores.dm" #include "code\datums\achievements\skill_achievements.dm" #include "code\datums\actions\beam_rifle.dm" +#include "code\datums\actions\mobs\blood_warp.dm" +#include "code\datums\actions\mobs\charge.dm" +#include "code\datums\actions\mobs\dash.dm" +#include "code\datums\actions\mobs\fire_breath.dm" +#include "code\datums\actions\mobs\lava_swoop.dm" +#include "code\datums\actions\mobs\meteors.dm" +#include "code\datums\actions\mobs\mobcooldown.dm" +#include "code\datums\actions\mobs\projectileattack.dm" +#include "code\datums\actions\mobs\transform_weapon.dm" #include "code\datums\ai\_ai_behavior.dm" #include "code\datums\ai\_ai_controller.dm" #include "code\datums\ai\_ai_planning_subtree.dm"