diff --git a/code/datums/action.dm b/code/datums/action.dm
index 53d4359d77f1e..6c06d9248a794 100644
--- a/code/datums/action.dm
+++ b/code/datums/action.dm
@@ -15,7 +15,19 @@
var/button_icon_state = "default" //And this is the state for the action icon
var/mob/owner
- var/has_cooldown_timer = FALSE
+ /// The time at which the cooldown timer will end
+ VAR_PRIVATE/cooldown_timer_end = 0
+ /// Overlay currently applied to this action
+ VAR_PRIVATE/mutable_appearance/timer_overlay
+
+ /// Timer icon file
+ VAR_PROTECTED/timer_icon = 'icons/effects/cooldown.dmi'
+ /// Icon state for the timer icon
+ VAR_PROTECTED/timer_icon_state_active = "second"
+
+ /// Set this to disable auto-processing on cooldown for things that add custom behaviours like spells.
+ /// Avoid this if possible.
+ var/override_cooldown_behaviour = FALSE
/datum/action/New(Target)
link_to(Target)
@@ -66,9 +78,6 @@
M.client.screen += button
button.locked = M.client.prefs.read_player_preference(/datum/preference/toggle/buttons_locked) || button.id ? M.client.prefs.action_buttons_screen_locs["[name]_[button.id]"] : FALSE //even if it's not defaultly locked we should remember we locked it before
button.moved = button.id ? M.client.prefs.action_buttons_screen_locs["[name]_[button.id]"] : FALSE
- var/obj/effect/proc_holder/spell/spell_proc_holder = button.linked_action.target
- if(istype(spell_proc_holder) && spell_proc_holder.text_overlay)
- M.client.images += spell_proc_holder.text_overlay
M.update_action_buttons()
else
Remove(owner)
@@ -101,6 +110,8 @@
/datum/action/proc/IsAvailable()
if(!owner)
return FALSE
+ if (cooldown_timer_end && world.time < cooldown_timer_end)
+ return FALSE
if((check_flags & AB_CHECK_HANDS_BLOCKED) && HAS_TRAIT(owner, TRAIT_HANDS_BLOCKED))
return FALSE
if((check_flags & AB_CHECK_INCAPACITATED) && HAS_TRAIT(owner, TRAIT_INCAPACITATED))
@@ -113,6 +124,9 @@
return FALSE
return TRUE
+/datum/action/process(delta_time)
+ UpdateButtonIcon(TRUE, FALSE)
+
/datum/action/proc/UpdateButtonIcon(status_only = FALSE, force = FALSE)
if(!button)
return FALSE
@@ -132,8 +146,13 @@
if(button.icon_state != background_icon_state)
button.icon_state = background_icon_state
ApplyIcon(button, force)
+ if(cooldown_timer_end)
+ if (cooldown_timer_end >= world.time)
+ update_cooldown_icon()
+ else
+ finish_cooldown()
if(!IsAvailable())
- button.color = has_cooldown_timer ? rgb(219, 219, 219, 255) : transparent_when_unavailable ? rgb(128,0,0,128) : rgb(128,0,0)
+ button.color = cooldown_timer_end ? rgb(219, 219, 219, 255) : transparent_when_unavailable ? rgb(128,0,0,128) : rgb(128,0,0)
else
button.color = rgb(255,255,255,255)
return TRUE
@@ -143,11 +162,53 @@
current_button.cut_overlays()
current_button.add_overlay(mutable_appearance(icon_icon, button_icon_state))
current_button.button_icon_state = button_icon_state
+ update_cooldown_icon(TRUE)
/datum/action/proc/OnUpdatedIcon()
SIGNAL_HANDLER
UpdateButtonIcon()
+//===Timer animation===
+
+/datum/action/proc/set_cooldown(duration)
+ if(!button)
+ return
+
+ cooldown_timer_end = world.time + duration
+ if (!timer_overlay)
+ timer_overlay = mutable_appearance(timer_icon, timer_icon_state_active)
+ timer_overlay.alpha = 180
+ timer_overlay.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
+ timer_overlay.maptext_width = 64
+ timer_overlay.maptext_height = 64
+ timer_overlay.maptext_x = -8
+ timer_overlay.maptext_y = -6
+
+ UpdateButtonIcon(TRUE, FALSE)
+
+ if (!override_cooldown_behaviour)
+ START_PROCESSING(SSprocessing, src)
+
+/datum/action/proc/update_cooldown_icon(force = FALSE)
+ if(!button || !timer_overlay)
+ return
+ var/new_maptext = "
[FLOOR((cooldown_timer_end - world.time)/10, 1)]"
+ if (new_maptext != timer_overlay.maptext || force)
+ button.cut_overlay(timer_overlay)
+ timer_overlay.maptext = new_maptext
+ button.add_overlay(timer_overlay)
+
+/datum/action/proc/finish_cooldown()
+ if(!button || !cooldown_timer_end)
+ return
+ button.cut_overlay(timer_overlay)
+ QDEL_NULL(timer_overlay)
+ cooldown_timer_end = null
+ UpdateButtonIcon(TRUE, FALSE)
+
+ if (!override_cooldown_behaviour)
+ STOP_PROCESSING(SSprocessing, src)
+
//Presets for item actions
/datum/action/item_action
check_flags = AB_CHECK_HANDS_BLOCKED|AB_CHECK_INCAPACITATED|AB_CHECK_CONSCIOUS
@@ -189,6 +250,7 @@
I.plane = FLOAT_PLANE //^ what that guy said
current_button.cut_overlays()
current_button.add_overlay(I)
+ update_cooldown_icon(TRUE)
I.layer = old_layer
I.plane = old_plane
current_button.appearance_cache = I.appearance
@@ -587,6 +649,7 @@
/datum/action/spell_action
check_flags = NONE
background_icon_state = "bg_spell"
+ override_cooldown_behaviour = TRUE
/datum/action/spell_action/New(Target)
..()
@@ -659,51 +722,6 @@
/datum/action/innate/proc/Deactivate()
return
-//Preset for an action with a cooldown
-
-/datum/action/cooldown
- check_flags = NONE
- transparent_when_unavailable = FALSE
- var/cooldown_time = 0
- var/next_use_time = 0
-
-/datum/action/cooldown/New()
- ..()
- button.maptext = ""
- button.maptext_x = 8
- button.maptext_y = 0
- button.maptext_width = 24
- button.maptext_height = 12
-
-/datum/action/cooldown/IsAvailable()
- return next_use_time <= world.time
-
-/datum/action/cooldown/proc/StartCooldown()
- next_use_time = world.time + cooldown_time
- button.maptext = MAPTEXT("[round(cooldown_time/10, 0.1)]")
- UpdateButtonIcon()
- START_PROCESSING(SSfastprocess, src)
-
-/datum/action/cooldown/process()
- if(!owner)
- button.maptext = ""
- return PROCESS_KILL
- var/timeleft = max(next_use_time - world.time, 0)
- if(timeleft == 0)
- button.maptext = ""
- UpdateButtonIcon()
- return PROCESS_KILL
- else
- button.maptext = MAPTEXT("[round(timeleft/10, 0.1)]")
-
-/datum/action/cooldown/Grant(mob/M)
- ..()
- if(owner)
- UpdateButtonIcon()
- if(next_use_time > world.time)
- START_PROCESSING(SSfastprocess, src)
-
-
//Stickmemes
/datum/action/item_action/stickmen
name = "Summon Stick Minions"
@@ -816,6 +834,7 @@
target.plane = FLOAT_PLANE //^ what that guy said
current_button.cut_overlays()
current_button.add_overlay(target)
+ update_cooldown_icon(TRUE)
target.layer = old_layer
target.plane = old_plane
current_button.appearance_cache = target.appearance
diff --git a/code/modules/mob/living/carbon/human/species_types/psyphoza.dm b/code/modules/mob/living/carbon/human/species_types/psyphoza.dm
index 5c60a5e976f13..82557ea6ffcea 100644
--- a/code/modules/mob/living/carbon/human/species_types/psyphoza.dm
+++ b/code/modules/mob/living/carbon/human/species_types/psyphoza.dm
@@ -190,17 +190,9 @@
if(!(locate(/datum/action/change_psychic_auto) in owner.actions))
auto_action = new(src)
auto_action.Grant(M)
- ///Start auto timer
- addtimer(CALLBACK(src, PROC_REF(auto_sense)), auto_cooldown)
-
-/datum/action/item_action/organ_action/psychic_highlight/IsAvailable()
- if(has_cooldown_timer)
- return FALSE
- return ..()
/datum/action/item_action/organ_action/psychic_highlight/Trigger()
- . = ..()
- if(has_cooldown_timer || !owner || !check_head())
+ if(!..() || !owner || !check_head())
return
//Reveal larger area of sense
dim_overlay()
@@ -209,14 +201,8 @@
if(BS)
for(var/mob/living/L in urange(9, owner, 1))
BS.highlight_object(L, "mob", L.dir)
- has_cooldown_timer = TRUE
- UpdateButtonIcon()
- addtimer(CALLBACK(src, PROC_REF(finish_cooldown)), cooldown + sense_time)
-
-/datum/action/item_action/organ_action/psychic_highlight/UpdateButtonIcon(status_only = FALSE, force = FALSE)
- . = ..()
- if(!IsAvailable())
- button.color = transparent_when_unavailable ? rgb(128,0,0,128) : rgb(128,0,0) //Overwrite this line from the original to support my fucked up use
+ set_cooldown(cooldown + sense_time)
+ return TRUE
/datum/action/item_action/organ_action/psychic_highlight/proc/remove()
owner?.clear_fullscreen("psychic_highlight")
@@ -231,14 +217,11 @@
if(!QDELETED(auto_action))
qdel(auto_action)
-/datum/action/item_action/organ_action/psychic_highlight/proc/auto_sense()
+/datum/action/item_action/organ_action/psychic_highlight/finish_cooldown()
+ . = ..()
+ // Auto-retrigger
if(auto_sense)
Trigger()
- addtimer(CALLBACK(src, PROC_REF(auto_sense)), auto_cooldown)
-
-/datum/action/item_action/organ_action/psychic_highlight/proc/finish_cooldown()
- has_cooldown_timer = FALSE
- UpdateButtonIcon()
//Allows user to see images through walls - mostly for if this action is added to something without xray
/datum/action/item_action/organ_action/psychic_highlight/proc/toggle_eyes_fowards()
@@ -464,6 +447,7 @@
/datum/action/change_psychic_auto/Trigger()
. = ..()
psychic_action?.auto_sense = !psychic_action?.auto_sense
+ psychic_action?.Trigger()
UpdateButtonIcon()
/datum/action/change_psychic_auto/IsAvailable()
diff --git a/code/modules/mob/living/simple_animal/hostile/goose.dm b/code/modules/mob/living/simple_animal/hostile/goose.dm
index c44136d588143..0b35345df6a23 100644
--- a/code/modules/mob/living/simple_animal/hostile/goose.dm
+++ b/code/modules/mob/living/simple_animal/hostile/goose.dm
@@ -57,7 +57,7 @@
var/vomiting = FALSE
var/vomitCoefficient = 1
var/vomitTimeBonus = 0
- var/datum/action/cooldown/vomit/goosevomit
+ var/datum/action/vomit/goosevomit
/mob/living/simple_animal/hostile/retaliate/goose/vomit/Initialize(mapload)
. = ..()
@@ -178,14 +178,13 @@
if (tasty)
feed(tasty)
-/datum/action/cooldown/vomit
+/datum/action/vomit
name = "Vomit"
check_flags = AB_CHECK_CONSCIOUS
button_icon_state = "vomit"
icon_icon = 'icons/mob/animal.dmi'
- cooldown_time = 250
-/datum/action/cooldown/vomit/Trigger()
+/datum/action/vomit/Trigger()
if(!..())
return FALSE
if(!istype(owner, /mob/living/simple_animal/hostile/retaliate/goose/vomit))
@@ -195,6 +194,7 @@
vomit.vomit_prestart(vomit.vomitTimeBonus + 25)
vomit.vomitCoefficient = 1
vomit.vomitTimeBonus = 0
+ set_cooldown(25 SECONDS)
return TRUE
#undef GOOSE_SATIATED
diff --git a/code/modules/mob/living/simple_animal/hostile/space_dragon.dm b/code/modules/mob/living/simple_animal/hostile/space_dragon.dm
index eba7425a5995a..702656c3e4469 100644
--- a/code/modules/mob/living/simple_animal/hostile/space_dragon.dm
+++ b/code/modules/mob/living/simple_animal/hostile/space_dragon.dm
@@ -72,7 +72,7 @@
/// Whether space dragon is swallowing a body currently
var/is_swallowing = FALSE
/// The cooldown ability to use wing gust
- var/datum/action/cooldown/gust_attack/gust
+ var/datum/action/gust_attack/gust
/// The ability to make your sprite smaller
var/datum/action/small_sprite/space_dragon/small_sprite
/// The color of the space dragon.
@@ -456,15 +456,14 @@
var/link = FOLLOW_LINK(S, src)
to_chat(S, "[link] [rendered]")
-/datum/action/cooldown/gust_attack
+/datum/action/gust_attack
name = "Gust Attack"
desc = "Use your wings to knock back foes with gusts of air, pushing them away and stunning them. Using this too often will leave you vulnerable for longer periods of time."
background_icon_state = "bg_default"
icon_icon = 'icons/hud/actions/actions_space_dragon.dmi'
button_icon_state = "gust_attack"
- cooldown_time = 5 SECONDS // the ability takes up around 2-3 seconds
-/datum/action/cooldown/gust_attack/Trigger()
+/datum/action/gust_attack/Trigger()
if(!..() || !istype(owner, /mob/living/simple_animal/hostile/space_dragon))
return FALSE
var/mob/living/simple_animal/hostile/space_dragon/S = owner
@@ -474,7 +473,7 @@
S.icon_state = "spacedragon_gust"
S.update_dragon_overlay()
S.useGust(TRUE)
- StartCooldown()
+ set_cooldown(5 SECONDS)
return TRUE
#undef DARKNESS_THRESHOLD
diff --git a/code/modules/ninja/energy_katana.dm b/code/modules/ninja/energy_katana.dm
index e1e7b7c15f154..ea2ce4bb7e084 100644
--- a/code/modules/ninja/energy_katana.dm
+++ b/code/modules/ninja/energy_katana.dm
@@ -39,6 +39,16 @@
/obj/item/energy_katana/afterattack(atom/target, mob/user, proximity_flag, click_parameters)
. = ..()
+ if (ishuman(user))
+ var/mob/living/carbon/human/human_user = user
+ if(istype(human_user.wear_suit, /obj/item/clothing/suit/space/space_ninja))
+ var/obj/item/clothing/suit/space/space_ninja/ninja_suit = human_user.wear_suit
+ if (ninja_suit.stealth)
+ ninja_suit.cancel_stealth()
+ else
+ return
+ else
+ return
if(dash_toggled)
jaunt.Teleport(user, target)
if(proximity_flag && (isobj(target) || issilicon(target)))
diff --git a/code/modules/ninja/suit/n_suit_verbs/ninja_stealth.dm b/code/modules/ninja/suit/n_suit_verbs/ninja_stealth.dm
index 1c3fbd814701d..d32264e4df67e 100644
--- a/code/modules/ninja/suit/n_suit_verbs/ninja_stealth.dm
+++ b/code/modules/ninja/suit/n_suit_verbs/ninja_stealth.dm
@@ -6,6 +6,7 @@ Contents:
*/
+#define STEALTH_COOLDOWN 30 SECONDS
/obj/item/clothing/suit/space/space_ninja/proc/toggle_stealth()
var/mob/living/carbon/human/U = affecting
@@ -14,6 +15,10 @@ Contents:
if(stealth)
cancel_stealth()
else
+ var/datum/action/item_action/ninja_stealth/stealth_action = locate() in actions
+ if (!stealth_action.IsAvailable())
+ U.balloon_alert(U, "Stealth not ready.")
+ return
if(cell.charge <= 0)
to_chat(U, "You don't have enough power to enable Stealth!")
return
@@ -32,12 +37,15 @@ Contents:
animate(U, alpha = 255, time = 15)
U.visible_message("[U.name] appears from thin air!", \
"You are now visible.")
+ var/datum/action/item_action/ninja_stealth/stealth_action = locate() in actions
+ stealth_action.set_cooldown(STEALTH_COOLDOWN)
return 1
return 0
-
/obj/item/clothing/suit/space/space_ninja/proc/stealth()
if(!s_busy)
toggle_stealth()
else
to_chat(affecting, "Stealth does not appear to work!")
+
+#undef STEALTH_COOLDOWN
diff --git a/code/modules/spells/__DEFINES/spell.dm b/code/modules/spells/__DEFINES/spell.dm
index 0764f970e3f89..3242e35b64c73 100644
--- a/code/modules/spells/__DEFINES/spell.dm
+++ b/code/modules/spells/__DEFINES/spell.dm
@@ -139,12 +139,6 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
var/overlay_icon_state = "spell"
var/overlay_lifespan = 0
- var/mutable_appearance/timer_overlay
- var/mutable_appearance/text_overlay
- var/timer_overlay_active = FALSE
- var/timer_icon = 'icons/effects/cooldown.dmi'
- var/timer_icon_state_active = "second"
-
var/sparks_spread = 0
var/sparks_amt = 0 //cropped at 10
var/smoke_spread = 0 //1 - harmless, 2 - harmful
@@ -299,7 +293,6 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
charge_counter = charge_max
/obj/effect/proc_holder/spell/Destroy()
- end_timer_animation()
qdel(action)
return ..()
@@ -316,22 +309,23 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
/obj/effect/proc_holder/spell/proc/start_recharge()
recharging = TRUE
- begin_timer_animation()
+ action.set_cooldown(charge_max)
+ START_PROCESSING(SSfastprocess, src)
/obj/effect/proc_holder/spell/process(delta_time)
if(recharging && charge_type == "recharge" && (charge_counter < charge_max))
charge_counter += delta_time * 10
- update_timer_animation()
+ action.set_cooldown(charge_max - charge_counter)
if(charge_counter >= charge_max)
- end_timer_animation()
- action.UpdateButtonIcon()
+ action.finish_cooldown()
charge_counter = charge_max
recharging = FALSE
+ return PROCESS_KILL
else
- end_timer_animation()
- action.UpdateButtonIcon()
+ action.finish_cooldown()
charge_counter = charge_max
recharging = FALSE
+ return PROCESS_KILL
/obj/effect/proc_holder/spell/proc/perform(list/targets, recharge = TRUE, mob/user = usr) //if recharge is started is important for the trigger spells
if(!cast_check())
@@ -402,9 +396,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
charge_counter++
if("holdervar")
adjust_var(user, holder_var_type, -holder_var_amount)
- end_timer_animation()
- if(action)
- action.UpdateButtonIcon()
+ action.finish_cooldown()
/obj/effect/proc_holder/spell/proc/adjust_var(mob/living/target = usr, type, amount) //handles the adjustment of the var when the spell is used. has some hardcoded types
if (!istype(target))
@@ -612,60 +604,6 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
return FALSE
return TRUE
-//===Timer animation===
-
-/obj/effect/proc_holder/spell/update_icon()
- . = ..()
- if(timer_overlay_active && !recharging)
- end_timer_animation()
- if(action)
- action.UpdateButtonIcon()
-
-/obj/effect/proc_holder/spell/proc/begin_timer_animation()
- if(!(action?.button) || timer_overlay_active)
- return
-
- timer_overlay_active = TRUE
- timer_overlay = mutable_appearance(timer_icon, timer_icon_state_active)
- timer_overlay.alpha = 180
-
- if(!text_overlay)
- text_overlay = image(loc = action.button)
- text_overlay.maptext_width = 64
- text_overlay.maptext_height = 64
- text_overlay.maptext_x = -8
- text_overlay.maptext_y = -6
- text_overlay.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
-
- if(action.owner?.client)
- action.owner.client.images += text_overlay
-
- action.button.add_overlay(timer_overlay)
- action.has_cooldown_timer = TRUE
- update_timer_animation()
-
- START_PROCESSING(SSfastprocess, src)
-
-/obj/effect/proc_holder/spell/proc/update_timer_animation()
- //Update map text (todo)
- if(!(action?.button))
- return
- text_overlay.maptext = "[FLOOR((charge_max-charge_counter)/10, 1)]"
-
-/obj/effect/proc_holder/spell/proc/end_timer_animation()
- if(!(action?.button) || !timer_overlay_active)
- return
- timer_overlay_active = FALSE
- if(action.owner?.client)
- action.owner.client.images -= text_overlay
- action.button.cut_overlays(timer_overlay)
- timer_overlay = null
- qdel(text_overlay)
- text_overlay = null
- action.has_cooldown_timer = FALSE
-
- STOP_PROCESSING(SSfastprocess, src)
-
//=====================
/obj/effect/proc_holder/spell/self //Targets only the caster. Good for buffs and heals, but probably not wise for fireballs (although they usually fireball themselves anyway, honke)