From b92823ee463071f05a5ff7088bf49e20922492ab Mon Sep 17 00:00:00 2001 From: Whoneedspacee Date: Tue, 19 Jul 2022 19:08:05 -0400 Subject: [PATCH] Mob Ability Sequences Again Again (I swear this time) (#68382) Adds the option for cooldown action sequences which has a list of sequence actions, the owner of the sequence doesn't have direct control over any of the actions in the sequence but is still considered the owner of the sequence actions, the main ability simply handles calling whatever those sub actions should do. Adds melee cooldown times to cooldown actions. Cooldown actions now use bitflags so an ability can share its cooldown to multiple other types of abilities Cooldown times are now handled by the base activate proc, cleaning up copy paste of StartCooldown() Reworks the admin verb for adding cooldown abilities to where it has significantly more customization (and tells you to mark the mob you want to modify instead of just doing nothing) and also allows for creation of the new sequences, allowing entirely new and unseen abilities to be formed out of existing ones. Also adds a verb for removal of mob abilities. --- code/__DEFINES/cooldowns.dm | 6 +- code/datums/actions/action.dm | 5 +- code/datums/actions/cooldown_action.dm | 139 +++++++++++++----- code/datums/actions/mobs/blood_warp.dm | 2 - code/datums/actions/mobs/charge.dm | 19 +-- code/datums/actions/mobs/dash.dm | 3 - code/datums/actions/mobs/fire_breath.dm | 1 - code/datums/actions/mobs/lava_swoop.dm | 1 - code/datums/actions/mobs/meteors.dm | 2 - code/datums/actions/mobs/mobcooldown.dm | 2 +- code/datums/actions/mobs/projectileattack.dm | 43 +++--- .../actions/mobs/sequences/dash_attack.dm | 11 ++ .../actions/mobs/sequences/projectile.dm | 10 ++ code/datums/actions/mobs/transform_weapon.dm | 3 +- code/modules/admin/admin_verbs.dm | 1 + code/modules/admin/verbs/adminevents.dm | 66 ++++++++- .../hostile/megafauna/blood_drunk_miner.dm | 11 +- .../hostile/megafauna/demonic_frost_miner.dm | 37 ++--- tgstation.dme | 2 + 19 files changed, 244 insertions(+), 120 deletions(-) create mode 100644 code/datums/actions/mobs/sequences/dash_attack.dm create mode 100644 code/datums/actions/mobs/sequences/projectile.dm 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 c20b4dbe943..7b7555a5ce9 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 @@ -87,7 +89,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..4523dbb0530 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,14 @@ 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 +228,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 +286,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 +295,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 2c325bde329..ccad4aacc19 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -108,6 +108,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, )) GLOBAL_PROTECT(admin_verbs_fun) diff --git a/code/modules/admin/verbs/adminevents.dm b/code/modules/admin/verbs/adminevents.dm index ba07eb7858b..8696077d832 100644 --- a/code/modules/admin/verbs/adminevents.dm +++ b/code/modules/admin/verbs/adminevents.dm @@ -310,19 +310,79 @@ 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/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/tgstation.dme b/tgstation.dme index d19a5bc4172..b5231fa191d 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -655,6 +655,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"