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"