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"