diff --git a/code/__DEFINES/cooldowns.dm b/code/__DEFINES/cooldowns.dm
index deee90e4424..edf68a16109 100644
--- a/code/__DEFINES/cooldowns.dm
+++ b/code/__DEFINES/cooldowns.dm
@@ -59,9 +59,9 @@
#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"
+#define MOB_SHARED_COOLDOWN_1 (1<<0)
+#define MOB_SHARED_COOLDOWN_2 (1<<1)
+#define MOB_SHARED_COOLDOWN_3 (1<<2)
//TIMER COOLDOWN MACROS
diff --git a/code/datums/actions/action.dm b/code/datums/actions/action.dm
index af7d6fed2fb..a6b51e17970 100644
--- a/code/datums/actions/action.dm
+++ b/code/datums/actions/action.dm
@@ -16,6 +16,8 @@
/// This is who currently owns the action, and most often, this is who is using the action if it is triggered
/// This can be the same as "target" but is not ALWAYS the same - this is set and unset with Grant() and Remove()
var/mob/owner
+ /// If False, the owner of this action does not get a hud and cannot activate it on their own
+ var/owner_has_control = TRUE
/// Flags that will determine of the owner / user of the action can... use the action
var/check_flags = NONE
/// The style the button's tooltips appear to be
@@ -88,7 +90,8 @@
if(check_flags & AB_CHECK_LYING)
RegisterSignal(owner, COMSIG_LIVING_SET_BODY_POSITION, .proc/update_icon_on_signal)
- GiveAction(grant_to)
+ if(owner_has_control)
+ GiveAction(grant_to)
/// Remove the passed mob from being owner of our action
/datum/action/proc/Remove(mob/remove_from)
diff --git a/code/datums/actions/cooldown_action.dm b/code/datums/actions/cooldown_action.dm
index f0a43fab105..cc8a0595869 100644
--- a/code/datums/actions/cooldown_action.dm
+++ b/code/datums/actions/cooldown_action.dm
@@ -1,3 +1,5 @@
+#define COOLDOWN_NO_DISPLAY_TIME (180 SECONDS)
+
/// Preset for an action that has a cooldown.
/datum/action/cooldown
check_flags = NONE
@@ -9,6 +11,10 @@
var/panel
/// The default cooldown applied when StartCooldown() is called
var/cooldown_time = 0
+ /// The default melee cooldown applied after the ability ends
+ var/melee_cooldown_time
+ /// The actual next time the owner of this action can melee
+ var/next_melee_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
@@ -19,8 +25,19 @@
var/click_cd_override = CLICK_CD_CLICK_ABILITY
/// If TRUE, we will unset after using our click intercept. Requires click_to_activate
var/unset_after_click = TRUE
- /// Shares cooldowns with other cooldown abilities of the same value, not active if null
+ /// Shares cooldowns with other abiliies, bitflag
var/shared_cooldown
+ /// List of prerequisite actions that are used in this sequenced ability, you cannot put other sequenced abilities in this
+ var/list/sequence_actions
+ /// List of prerequisite actions that have been initialized
+ var/list/initialized_actions
+
+/datum/action/cooldown/New(Target, original = TRUE)
+ ..()
+ if(isnull(melee_cooldown_time))
+ melee_cooldown_time = cooldown_time
+ if(original)
+ create_sequence_actions()
/datum/action/cooldown/CreateButton()
var/atom/movable/screen/movable/action_button/button = ..()
@@ -31,27 +48,67 @@
button.maptext_height = 12
return button
+/datum/action/cooldown/Destroy()
+ QDEL_LIST(initialized_actions)
+ return ..()
+
+/datum/action/cooldown/Grant(mob/granted_to)
+ . = ..()
+ if(!owner)
+ return
+ UpdateButtons()
+ if(next_use_time > world.time)
+ START_PROCESSING(SSfastprocess, src)
+ RegisterSignal(granted_to, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, .proc/handle_melee_attack)
+ for(var/datum/action/cooldown/ability as anything in initialized_actions)
+ ability.Grant(granted_to)
+
+/datum/action/cooldown/Remove(mob/removed_from)
+ UnregisterSignal(removed_from, COMSIG_HOSTILE_PRE_ATTACKINGTARGET)
+ if(click_to_activate && removed_from.click_intercept == src)
+ unset_click_ability(removed_from, refund_cooldown = FALSE)
+ for(var/datum/action/cooldown/ability as anything in initialized_actions)
+ ability.Remove(removed_from)
+ return ..()
+
/datum/action/cooldown/IsAvailable()
return ..() && (next_use_time <= world.time)
-/datum/action/cooldown/Remove(mob/living/remove_from)
- if(click_to_activate && remove_from.click_intercept == src)
- unset_click_ability(remove_from, refund_cooldown = FALSE)
- return ..()
+/// Initializes any sequence actions
+/datum/action/cooldown/proc/create_sequence_actions()
+ if(!LAZYLEN(sequence_actions))
+ return
+ // remove existing actions if any
+ QDEL_LIST(initialized_actions)
+ initialized_actions = list()
+ for(var/type_path in sequence_actions)
+ var/datum/action/cooldown/ability = new type_path(target, original = FALSE)
+ // prevents clients from using the individual abilities in sequences (this stops it from being added to mob actions when granted as well)
+ ability.owner_has_control = FALSE
+ // [ability] = delay
+ initialized_actions[ability] = sequence_actions[type_path]
/// 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)
+/datum/action/cooldown/proc/StartCooldown(override_cooldown_time, override_melee_cooldown_time)
// "Shared cooldowns" covers actions which are not the same type,
// but have the same cooldown group and are on the same mob
if(shared_cooldown)
for(var/datum/action/cooldown/shared_ability in owner.actions - src)
- if(shared_cooldown != shared_ability.shared_cooldown)
+ if(!(shared_cooldown & shared_ability.shared_cooldown))
continue
- shared_ability.StartCooldownSelf(override_cooldown_time)
+ if(isnum(override_cooldown_time))
+ shared_ability.StartCooldownSelf(override_cooldown_time)
+ else
+ shared_ability.StartCooldownSelf(cooldown_time)
StartCooldownSelf(override_cooldown_time)
+ if(isnum(override_melee_cooldown_time))
+ next_melee_use_time = world.time + override_melee_cooldown_time
+ else
+ next_melee_use_time = world.time + melee_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)
@@ -108,7 +165,6 @@
// And if we reach here, the action was complete successfully
if(unset_after_click)
- StartCooldown()
unset_click_ability(caller, refund_cooldown = FALSE)
caller.next_click = world.time + click_cd_override
@@ -118,14 +174,47 @@
/datum/action/cooldown/proc/PreActivate(atom/target)
if(SEND_SIGNAL(owner, COMSIG_MOB_ABILITY_STARTED, src) & COMPONENT_BLOCK_ABILITY_START)
return
+ StartCooldown(360 SECONDS, 360 SECONDS)
. = Activate(target)
// There is a possibility our action (or owner) is qdeleted in Activate().
if(!QDELETED(src) && !QDELETED(owner))
SEND_SIGNAL(owner, COMSIG_MOB_ABILITY_FINISHED, src)
-/// To be implemented by subtypes
+/// To be implemented by subtypes (if not generic)
/datum/action/cooldown/proc/Activate(atom/target)
- return
+ var/total_delay = 0
+ for(var/datum/action/cooldown/ability as anything in initialized_actions)
+ if(LAZYLEN(ability.initialized_actions) > 0)
+ ability.initialized_actions = list()
+ addtimer(CALLBACK(ability, .proc/Activate, target), total_delay)
+ total_delay += initialized_actions[ability]
+ StartCooldown()
+
+/datum/action/cooldown/UpdateButton(atom/movable/screen/movable/action_button/button, status_only = FALSE, force = FALSE)
+ . = ..()
+ if(!button)
+ return
+ var/time_left = max(next_use_time - world.time, 0)
+ if(text_cooldown && time_left < COOLDOWN_NO_DISPLAY_TIME)
+ button.maptext = MAPTEXT("[round(time_left/10, 0.1)]")
+ if(!owner || time_left == 0 || time_left >= COOLDOWN_NO_DISPLAY_TIME)
+ button.maptext = ""
+ if(IsAvailable() && (button.our_hud.mymob.click_intercept == src))
+ button.color = COLOR_GREEN
+
+/// Cancels melee attacks if they are on cooldown.
+/datum/action/cooldown/proc/handle_melee_attack(mob/source, mob/target)
+ SIGNAL_HANDLER
+ if(next_melee_use_time > world.time)
+ return COMPONENT_HOSTILE_NO_ATTACK
+
+/datum/action/cooldown/process()
+ if(!owner || (next_use_time - world.time) <= 0)
+ UpdateButtons()
+ STOP_PROCESSING(SSfastprocess, src)
+ return
+
+ UpdateButtons()
/**
* Set our action as the click override on the passed mob.
@@ -156,34 +245,6 @@
UpdateButtons()
return TRUE
-/datum/action/cooldown/UpdateButton(atom/movable/screen/movable/action_button/button, status_only = FALSE, force = FALSE)
- . = ..()
- if(!button)
- return
- var/time_left = max(next_use_time - world.time, 0)
- if(text_cooldown)
- button.maptext = MAPTEXT("[round(time_left/10, 0.1)]")
- if(!owner || time_left == 0)
- button.maptext = ""
- if(IsAvailable() && (button.our_hud.mymob.click_intercept == src))
- button.color = COLOR_GREEN
-
-/datum/action/cooldown/process()
- if(!owner || (next_use_time - world.time) <= 0)
- UpdateButtons()
- STOP_PROCESSING(SSfastprocess, src)
- return
-
- UpdateButtons()
-
-/datum/action/cooldown/Grant(mob/M)
- ..()
- if(!owner)
- return
- UpdateButtons()
- if(next_use_time > world.time)
- START_PROCESSING(SSfastprocess, src)
-
/// Formats the action to be returned to the stat panel.
/datum/action/cooldown/proc/set_statpanel_format()
if(!panel)
diff --git a/code/datums/actions/mobs/blood_warp.dm b/code/datums/actions/mobs/blood_warp.dm
index c48a5598293..6f405096fc2 100644
--- a/code/datums/actions/mobs/blood_warp.dm
+++ b/code/datums/actions/mobs/blood_warp.dm
@@ -4,7 +4,6 @@
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
@@ -13,7 +12,6 @@
var/remove_inner_pools = TRUE
/datum/action/cooldown/mob_cooldown/blood_warp/Activate(atom/target_atom)
- StartCooldown(10 SECONDS)
blood_warp(target_atom)
StartCooldown()
diff --git a/code/datums/actions/mobs/charge.dm b/code/datums/actions/mobs/charge.dm
index 4d69e28aa71..529b9b0acc8 100644
--- a/code/datums/actions/mobs/charge.dm
+++ b/code/datums/actions/mobs/charge.dm
@@ -21,24 +21,7 @@
/// List of charging mobs
var/list/charging = list()
-/datum/action/cooldown/mob_cooldown/charge/New(Target, delay, past, distance, speed, damage, destroy)
- . = ..()
- if(!isnull(delay))
- charge_delay = delay
- if(!isnull(past))
- charge_past = past
- if(!isnull(distance))
- charge_distance = distance
- if(!isnull(speed))
- charge_speed = speed
- if(!isnull(damage))
- charge_damage = damage
- if(!isnull(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()
@@ -85,6 +68,7 @@
// Yes this is disgusting. But we need to queue this stuff, and this code just isn't setup to support that right now. So gotta do it with sleeps
sleep(time_to_hit + charge_speed)
+ charger.setDir(dir)
return TRUE
@@ -242,7 +226,6 @@
/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)
diff --git a/code/datums/actions/mobs/dash.dm b/code/datums/actions/mobs/dash.dm
index a7196ea866c..1ba39b4d893 100644
--- a/code/datums/actions/mobs/dash.dm
+++ b/code/datums/actions/mobs/dash.dm
@@ -8,11 +8,8 @@
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()
diff --git a/code/datums/actions/mobs/fire_breath.dm b/code/datums/actions/mobs/fire_breath.dm
index f3df8f86df1..86eea24ecf4 100644
--- a/code/datums/actions/mobs/fire_breath.dm
+++ b/code/datums/actions/mobs/fire_breath.dm
@@ -12,7 +12,6 @@
var/ice_breath = FALSE
/datum/action/cooldown/mob_cooldown/fire_breath/Activate(atom/target_atom)
- StartCooldown(10 SECONDS)
attack_sequence(target_atom)
StartCooldown()
diff --git a/code/datums/actions/mobs/lava_swoop.dm b/code/datums/actions/mobs/lava_swoop.dm
index d9649d225a6..e704bcd511d 100644
--- a/code/datums/actions/mobs/lava_swoop.dm
+++ b/code/datums/actions/mobs/lava_swoop.dm
@@ -23,7 +23,6 @@
REMOVE_TRAIT(M, TRAIT_NOFIRE, REF(src))
/datum/action/cooldown/mob_cooldown/lava_swoop/Activate(atom/target_atom)
- StartCooldown(30 SECONDS)
attack_sequence(target_atom)
StartCooldown()
diff --git a/code/datums/actions/mobs/meteors.dm b/code/datums/actions/mobs/meteors.dm
index 02da70c7b5e..e7da91de609 100644
--- a/code/datums/actions/mobs/meteors.dm
+++ b/code/datums/actions/mobs/meteors.dm
@@ -4,10 +4,8 @@
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()
diff --git a/code/datums/actions/mobs/mobcooldown.dm b/code/datums/actions/mobs/mobcooldown.dm
index 3e4add136a1..dc59def0e2d 100644
--- a/code/datums/actions/mobs/mobcooldown.dm
+++ b/code/datums/actions/mobs/mobcooldown.dm
@@ -4,7 +4,7 @@
button_icon_state = "sniper_zoom"
desc = "Click this ability to attack."
check_flags = AB_CHECK_CONSCIOUS
- cooldown_time = 1.5 SECONDS
+ cooldown_time = 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
index 6649e72bd42..ad5b117145e 100644
--- a/code/datums/actions/mobs/projectileattack.dm
+++ b/code/datums/actions/mobs/projectileattack.dm
@@ -17,17 +17,7 @@
/// The multiplier to the projectiles speed (a value of 2 makes it twice as slow, 0.5 makes it twice as fast)
var/projectile_speed_multiplier = 1
-/datum/action/cooldown/mob_cooldown/projectile_attack/New(Target, projectile, homing, spread)
- . = ..()
- if(projectile)
- projectile_type = projectile
- if(homing)
- has_homing = homing
- if(spread)
- default_projectile_spread = spread
-
/datum/action/cooldown/mob_cooldown/projectile_attack/Activate(atom/target_atom)
- StartCooldown(10 SECONDS)
attack_sequence(owner, target_atom)
StartCooldown()
@@ -78,6 +68,10 @@
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/direct
+ shot_count = 40
+ default_projectile_spread = 5
+
/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/shrapnel
name = "Shrapnel Fire"
icon_icon = 'icons/mob/actions/actions_items.dmi'
@@ -91,7 +85,7 @@
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/shrapnel_spread = 60
var/break_time = 2 SECONDS
/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/shrapnel/attack_sequence(mob/living/firer, atom/target)
@@ -108,6 +102,11 @@
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/rapid_fire/shrapnel/strong
+ name = "Strong Shrapnel Fire"
+ shot_count = 16
+ shot_delay = 0.5 SECONDS
+
/datum/action/cooldown/mob_cooldown/projectile_attack/spiral_shots
name = "Spiral Shots"
icon_icon = 'icons/mob/actions/actions_items.dmi'
@@ -181,11 +180,6 @@
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)
@@ -216,6 +210,13 @@
fire_shotgun(firer, target, pattern)
SLEEP_CHECK_DEATH(0.8 SECONDS, firer)
+/datum/action/cooldown/mob_cooldown/projectile_attack/shotgun_blast/pattern/circular
+ name = "Circular Shotgun Fire"
+ shot_angles = list(list(0, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330), list(-30, -15, 0, 15, 30))
+
+/datum/action/cooldown/mob_cooldown/projectile_attack/shotgun_blast/pattern/circular/complete
+ shot_angles = list(list(-180, -140, -100, -60, -20, 20, 60, 100, 140), list(-160, -120, -80, -40, 0, 40, 80, 120, 160))
+
/datum/action/cooldown/mob_cooldown/projectile_attack/dir_shots
name = "Directional Shots"
icon_icon = 'icons/obj/guns/ballistic.dmi'
@@ -226,11 +227,9 @@
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)
+/datum/action/cooldown/mob_cooldown/projectile_attack/dir_shots/New(Target)
. = ..()
- if(dirs)
- firing_directions = dirs
- else
+ if(!firing_directions)
firing_directions = GLOB.alldirs.Copy()
/datum/action/cooldown/mob_cooldown/projectile_attack/dir_shots/attack_sequence(mob/living/firer, atom/target)
@@ -286,7 +285,6 @@
cooldown_time = 2.5 SECONDS
/datum/action/cooldown/mob_cooldown/projectile_attack/colossus_final/Activate(atom/target_atom)
- StartCooldown(30 SECONDS)
attack_sequence(owner, target_atom)
StartCooldown()
Remove(owner)
@@ -296,7 +294,7 @@
if(istype(firer, /mob/living/simple_animal/hostile/megafauna/colossus))
colossus = firer
colossus.say("Perish.", spans = list("colossus", "yell"))
-
+
var/finale_counter = 10
for(var/i in 1 to 20)
if(finale_counter > 4 && colossus)
diff --git a/code/datums/actions/mobs/sequences/dash_attack.dm b/code/datums/actions/mobs/sequences/dash_attack.dm
new file mode 100644
index 00000000000..8d0a2cc3444
--- /dev/null
+++ b/code/datums/actions/mobs/sequences/dash_attack.dm
@@ -0,0 +1,11 @@
+/datum/action/cooldown/mob_cooldown/dash_attack
+ name = "Dashing And Attacking"
+ icon_icon = 'icons/mob/actions/actions_items.dmi'
+ button_icon_state = "sniper_zoom"
+ desc = "Allows you to dash and fire at a target simultaneously."
+ cooldown_time = 3 SECONDS
+ shared_cooldown = MOB_SHARED_COOLDOWN_2
+ sequence_actions = list(
+ /datum/action/cooldown/mob_cooldown/dash = 0.1 SECONDS,
+ /datum/action/cooldown/mob_cooldown/projectile_attack/kinetic_accelerator = 0,
+ )
diff --git a/code/datums/actions/mobs/sequences/projectile.dm b/code/datums/actions/mobs/sequences/projectile.dm
new file mode 100644
index 00000000000..670ee2417ff
--- /dev/null
+++ b/code/datums/actions/mobs/sequences/projectile.dm
@@ -0,0 +1,10 @@
+/datum/action/cooldown/mob_cooldown/direct_and_aoe
+ name = "Direct And AoE Firing"
+ icon_icon = 'icons/mob/actions/actions_items.dmi'
+ button_icon_state = "sniper_zoom"
+ desc = "Allows you to shoot directly at a target while also firing around you."
+ cooldown_time = 12 SECONDS
+ sequence_actions = list(
+ /datum/action/cooldown/mob_cooldown/dash = 0.1 SECONDS,
+ /datum/action/cooldown/mob_cooldown/projectile_attack/kinetic_accelerator = 0,
+ )
diff --git a/code/datums/actions/mobs/transform_weapon.dm b/code/datums/actions/mobs/transform_weapon.dm
index 6dad130da22..6fb0ac3515e 100644
--- a/code/datums/actions/mobs/transform_weapon.dm
+++ b/code/datums/actions/mobs/transform_weapon.dm
@@ -9,9 +9,8 @@
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))
+ StartCooldown(rand(cooldown_time, max_cooldown_time), 0)
/datum/action/cooldown/mob_cooldown/transform_weapon/proc/do_transform(atom/target)
if(!istype(owner, /mob/living/simple_animal/hostile/megafauna/blood_drunk_miner))
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index f97196a2951..59f8f60d04e 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -127,6 +127,7 @@ GLOBAL_LIST_INIT(admin_verbs_fun, list(
/client/proc/smite,
/client/proc/admin_away,
/client/proc/add_mob_ability,
+ /client/proc/remove_mob_ability,
/datum/admins/proc/station_traits_panel,
/client/proc/spawn_pollution, // SKYRAT EDIT ADDITION
/client/proc/spawn_liquid, // SKYRAT EDIT ADDITION
diff --git a/code/modules/admin/verbs/adminevents.dm b/code/modules/admin/verbs/adminevents.dm
index ba07eb7858b..902c84c62ed 100644
--- a/code/modules/admin/verbs/adminevents.dm
+++ b/code/modules/admin/verbs/adminevents.dm
@@ -310,19 +310,78 @@
if(!holder)
return
- if(!holder.marked_datum || !istype(holder.marked_datum, /mob/living))
+ if(!isliving(holder.marked_datum))
+ to_chat(usr, span_warning("Error: Please mark a mob to add actions to it."))
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)
+ var/list/all_mob_actions = sort_list(subtypesof(/datum/action/cooldown/mob_cooldown), /proc/cmp_typepaths_asc)
+
+ var/ability_type = tgui_input_list(usr, "Choose an ability", "Ability", all_mob_actions)
if(!ability_type)
return
- var/datum/action/cooldown/mob_cooldown/add_ability = new ability_type()
+ var/datum/action/cooldown/mob_cooldown/add_ability
+
+ var/make_sequence = tgui_alert(usr, "Would you like this action to be a sequence of multiple abilities?", "Sequence Ability", list("Yes", "No"))
+ if(make_sequence == "Yes")
+ add_ability = new /datum/action/cooldown/mob_cooldown(marked_mob)
+ add_ability.sequence_actions = list()
+ while(!isnull(ability_type))
+ var/ability_delay = tgui_input_number(usr, "Enter the delay in seconds before the next ability in the sequence is used", "Ability Delay", 2)
+ if(isnull(ability_delay) || ability_delay < 0)
+ ability_delay = 0
+ add_ability.sequence_actions[ability_type] = ability_delay * 1 SECONDS
+ ability_type = tgui_input_list(usr, "Choose a new sequence ability", "Sequence Ability", all_mob_actions)
+ var/ability_cooldown = tgui_input_number(usr, "Enter the sequence abilities cooldown in seconds", "Ability Cooldown", 2)
+ if(isnull(ability_cooldown) || ability_cooldown < 0)
+ ability_cooldown = 2
+ add_ability.cooldown_time = ability_cooldown * 1 SECONDS
+ var/ability_melee_cooldown = tgui_input_number(usr, "Enter the abilities melee cooldown in seconds", "Melee Cooldown", 2)
+ if(isnull(ability_melee_cooldown) || ability_melee_cooldown < 0)
+ ability_melee_cooldown = 2
+ add_ability.melee_cooldown_time = ability_melee_cooldown * 1 SECONDS
+ add_ability.name = tgui_input_text(usr, "Choose ability name", "Ability name", "Generic Ability")
+ add_ability.create_sequence_actions()
+ else
+ add_ability = new ability_type(marked_mob)
+
+ if(isnull(marked_mob))
+ return
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!
+
+/client/proc/remove_mob_ability()
+ set category = "Admin.Events"
+ set name = "Remove Mob Ability"
+ set desc = "Removes an ability from marked mob."
+
+ if(!holder)
+ return
+
+ if(!isliving(holder.marked_datum))
+ to_chat(usr, span_warning("Error: Please mark a mob to remove actions from it."))
+ return
+
+ var/mob/living/marked_mob = holder.marked_datum
+
+ var/list/all_mob_actions = list()
+ for(var/datum/action/cooldown/mob_cooldown/ability in marked_mob.actions)
+ all_mob_actions.Add(ability)
+
+ var/datum/action/cooldown/mob_cooldown/ability = tgui_input_list(usr, "Remove an ability", "Ability", all_mob_actions)
+
+ if(!ability)
+ return
+
+ var/ability_name = ability.name
+ QDEL_NULL(ability)
+
+ message_admins("[key_name_admin(usr)] removed ability [ability_name] from mob [marked_mob].")
+ log_admin("[key_name(usr)] removed mob ability [ability_name] from mob [marked_mob].")
+ SSblackbox.record_feedback("tally", "admin_verb", 1, "Remove 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/carbon/alien/humanoid/alien_powers.dm b/code/modules/mob/living/carbon/alien/humanoid/alien_powers.dm
index 862e4190390..99e77c345ee 100644
--- a/code/modules/mob/living/carbon/alien/humanoid/alien_powers.dm
+++ b/code/modules/mob/living/carbon/alien/humanoid/alien_powers.dm
@@ -13,6 +13,7 @@ Doesn't work on other aliens/AI.*/
icon_icon = 'icons/mob/actions/actions_xeno.dmi'
button_icon_state = "spell_default"
check_flags = AB_CHECK_CONSCIOUS
+ melee_cooldown_time = 0 SECONDS
/// How much plasma this action uses.
var/plasma_cost = 0
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 0e58e5398a6..0b4e408163b 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
@@ -56,6 +56,8 @@ Difficulty: Medium
var/datum/action/cooldown/mob_cooldown/dash/dash
/// Kinetic accelerator ability
var/datum/action/cooldown/mob_cooldown/projectile_attack/kinetic_accelerator/kinetic_accelerator
+ /// Dash Attack Ability
+ var/datum/action/cooldown/mob_cooldown/dash_attack/dash_attack
/// Transform weapon ability
var/datum/action/cooldown/mob_cooldown/transform_weapon/transform_weapon
@@ -65,14 +67,17 @@ Difficulty: Medium
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()
+ dash_attack = new /datum/action/cooldown/mob_cooldown/dash_attack()
transform_weapon = new /datum/action/cooldown/mob_cooldown/transform_weapon()
dash.Grant(src)
kinetic_accelerator.Grant(src)
+ dash_attack.Grant(src)
transform_weapon.Grant(src)
/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/Destroy()
QDEL_NULL(dash)
QDEL_NULL(kinetic_accelerator)
+ QDEL_NULL(dash_attack)
QDEL_NULL(transform_weapon)
return ..()
@@ -81,10 +86,8 @@ Difficulty: Medium
return
Goto(target, move_to_delay, minimum_distance)
- if(get_dist(src, target) > 4)
- if(dash.Trigger(target = target))
- kinetic_accelerator.StartCooldown(0)
- kinetic_accelerator.Trigger(target = target)
+ if(get_dist(src, target) > 4 && dash_attack.IsAvailable())
+ dash_attack.Trigger(target = target)
else
kinetic_accelerator.Trigger(target = target)
transform_weapon.Trigger(target = target)
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 7140399ec91..cf89353ca3f 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
@@ -49,19 +49,31 @@ Difficulty: Extremely Hard
var/enraging = FALSE
/// Frost orbs ability
var/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/shrapnel/frost_orbs
+ /// Hard version of frost orbs
+ var/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/shrapnel/strong/hard_frost_orbs
/// Snowball Machine gun Ability
var/datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/snowball_machine_gun
+ /// Hard Snowball Machine gun Ability
+ var/datum/action/cooldown/mob_cooldown/direct_and_aoe/hard_snowball_machine_gun
/// Ice Shotgun Ability
var/datum/action/cooldown/mob_cooldown/projectile_attack/shotgun_blast/pattern/ice_shotgun
+ /// Hard Ice Shotgun Ability
+ var/datum/action/cooldown/mob_cooldown/projectile_attack/shotgun_blast/pattern/circular/hard_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()
+ hard_frost_orbs = new /datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire/shrapnel/strong()
snowball_machine_gun = new /datum/action/cooldown/mob_cooldown/projectile_attack/rapid_fire()
+ hard_snowball_machine_gun = new /datum/action/cooldown/mob_cooldown/direct_and_aoe()
ice_shotgun = new /datum/action/cooldown/mob_cooldown/projectile_attack/shotgun_blast/pattern()
+ hard_ice_shotgun = new /datum/action/cooldown/mob_cooldown/projectile_attack/shotgun_blast/pattern/circular()
frost_orbs.Grant(src)
+ hard_frost_orbs.Grant(src)
snowball_machine_gun.Grant(src)
+ hard_snowball_machine_gun.Grant(src)
ice_shotgun.Grant(src)
+ hard_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_MOB_ABILITY_STARTED, .proc/start_attack)
@@ -71,8 +83,11 @@ Difficulty: Extremely Hard
/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner/Destroy()
QDEL_NULL(frost_orbs)
+ QDEL_NULL(hard_frost_orbs)
QDEL_NULL(snowball_machine_gun)
+ QDEL_NULL(hard_snowball_machine_gun)
QDEL_NULL(ice_shotgun)
+ QDEL_NULL(hard_ice_shotgun)
return ..()
/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner/OpenFire()
@@ -84,33 +99,19 @@ Difficulty: Extremely Hard
switch(chosen_attack)
if(1)
if(easy_attack)
- frost_orbs.shot_count = 8
- frost_orbs.shot_delay = 10
frost_orbs.Trigger(target = target)
else
- frost_orbs.shot_count = 16
- frost_orbs.shot_delay = 5
- frost_orbs.Trigger(target = target)
+ hard_frost_orbs.Trigger(target = target)
if(2)
if(easy_attack)
- snowball_machine_gun.shot_count = 60
- snowball_machine_gun.default_projectile_spread = 45
- snowball_machine_gun.Trigger(target = 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 = target)
+ else
+ hard_snowball_machine_gun.Trigger(target = target)
if(3)
if(easy_attack)
- // 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 = target)
else
- 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 = target)
+ hard_ice_shotgun.Trigger(target = target)
/// Pre-ability usage stuff
/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner/proc/start_attack(mob/living/owner, datum/action/cooldown/activated)
diff --git a/code/modules/mob/living/simple_animal/hostile/regalrat.dm b/code/modules/mob/living/simple_animal/hostile/regalrat.dm
index da55ced5181..e4cc8daa76d 100644
--- a/code/modules/mob/living/simple_animal/hostile/regalrat.dm
+++ b/code/modules/mob/living/simple_animal/hostile/regalrat.dm
@@ -30,14 +30,14 @@
///Whether or not the regal rat is already opening an airlock
var/opening_airlock = FALSE
///The spell that the rat uses to generate miasma
- var/datum/action/cooldown/domain
+ var/datum/action/cooldown/domain/domain
///The Spell that the rat uses to recruit/convert more rats.
- var/datum/action/cooldown/riot
+ var/datum/action/cooldown/riot/riot
/mob/living/simple_animal/hostile/regalrat/Initialize(mapload)
. = ..()
- domain = new /datum/action/cooldown/domain
- riot = new /datum/action/cooldown/riot
+ domain = new(src)
+ riot = new(src)
domain.Grant(src)
riot.Grant(src)
AddElement(/datum/element/waddling)
@@ -48,6 +48,7 @@
. = ..()
QDEL_NULL(domain)
QDEL_NULL(riot)
+ return ..()
/mob/living/simple_animal/hostile/regalrat/proc/get_player()
var/list/mob/dead/observer/candidates = poll_ghost_candidates("Do you want to play as the Royal Rat, cheesey be their crown?", ROLE_SENTIENCE, FALSE, 100, POLL_IGNORE_SENTIENCE_POTION)
@@ -207,6 +208,7 @@
desc = "Corrupts this area to be more suitable for your rat army."
check_flags = AB_CHECK_CONSCIOUS
cooldown_time = 6 SECONDS
+ melee_cooldown_time = 0 SECONDS
icon_icon = 'icons/mob/actions/actions_animal.dmi'
background_icon_state = "bg_clock"
button_icon_state = "coffer"
@@ -242,7 +244,7 @@
button_icon_state = "riot"
background_icon_state = "bg_clock"
cooldown_time = 8 SECONDS
- ///Checks to see if there are any nearby mice. Does not count Rats.
+ melee_cooldown_time = 0 SECONDS
/datum/action/cooldown/riot/proc/riot()
var/cap = CONFIG_GET(number/ratcap)
diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/clown.dm b/code/modules/mob/living/simple_animal/hostile/retaliate/clown.dm
index 9d0970ebe01..0f599e28551 100644
--- a/code/modules/mob/living/simple_animal/hostile/retaliate/clown.dm
+++ b/code/modules/mob/living/simple_animal/hostile/retaliate/clown.dm
@@ -477,6 +477,7 @@
icon_icon = 'icons/mob/actions/actions_animal.dmi'
button_icon_state = "regurgitate"
check_flags = AB_CHECK_CONSCIOUS
+ melee_cooldown_time = 0 SECONDS
click_to_activate = TRUE
/datum/action/cooldown/regurgitate/set_click_ability(mob/on_who)
diff --git a/code/modules/mob/living/simple_animal/hostile/vatbeast.dm b/code/modules/mob/living/simple_animal/hostile/vatbeast.dm
index 2198eabea03..7b4fb51e3cb 100644
--- a/code/modules/mob/living/simple_animal/hostile/vatbeast.dm
+++ b/code/modules/mob/living/simple_animal/hostile/vatbeast.dm
@@ -49,6 +49,7 @@
button_icon_state = "tentacle_slap"
check_flags = AB_CHECK_CONSCIOUS
cooldown_time = 12 SECONDS
+ melee_cooldown_time = 0 SECONDS
click_to_activate = TRUE
ranged_mousepointer = 'icons/effects/mouse_pointers/supplypod_target.dmi'
diff --git a/code/modules/spells/spell.dm b/code/modules/spells/spell.dm
index 5fdf82a9a79..bf68828466b 100644
--- a/code/modules/spells/spell.dm
+++ b/code/modules/spells/spell.dm
@@ -47,6 +47,7 @@
button_icon_state = "spell_default"
check_flags = AB_CHECK_CONSCIOUS
panel = "Spells"
+ melee_cooldown_time = 0 SECONDS
/// The sound played on cast.
var/sound = null
diff --git a/tgstation.dme b/tgstation.dme
index 5587c1940be..af1ea054265 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -718,6 +718,8 @@
#include "code\datums\actions\mobs\projectileattack.dm"
#include "code\datums\actions\mobs\small_sprite.dm"
#include "code\datums\actions\mobs\transform_weapon.dm"
+#include "code\datums\actions\mobs\sequences\dash_attack.dm"
+#include "code\datums\actions\mobs\sequences\projectile.dm"
#include "code\datums\ai\_ai_behavior.dm"
#include "code\datums\ai\_ai_controller.dm"
#include "code\datums\ai\_ai_planning_subtree.dm"