diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm
index f4cda43da86..7779528a974 100644
--- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm
+++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm
@@ -49,6 +49,10 @@
/// from base of /datum/view_data/proc/afterViewChange() : (view)
#define COMSIG_VIEWDATA_UPDATE "viewdata_update"
+/// Sent from /proc/do_after if someone starts a do_after action bar.
+#define COMSIG_DO_AFTER_BEGAN "mob_do_after_began"
+/// Sent from /proc/do_after once a do_after action completes, whether via the bar filling or via interruption.
+#define COMSIG_DO_AFTER_ENDED "mob_do_after_ended"
///from mind/transfer_to. Sent to the receiving mob.
#define COMSIG_MOB_MIND_TRANSFERRED_INTO "mob_mind_transferred_into"
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index defcbb25fbd..f6452fb3074 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -422,7 +422,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
/// Whether or not orbiting is blocked or not
#define TRAIT_ORBITING_FORBIDDEN "orbiting_forbidden"
-/// Whether a spider's consumed this mob
+/// Trait applied to mob/living to mark that spiders should not gain further enriched eggs from eating their corpse.
#define TRAIT_SPIDER_CONSUMED "spider_consumed"
/// Whether we're sneaking, from the alien sneak ability.
/// Maybe worth generalizing into a general "is sneaky" / "is stealth" trait in the future.
diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm
index 9ebf62be3d4..b2d60995ce7 100644
--- a/code/__HELPERS/mobs.dm
+++ b/code/__HELPERS/mobs.dm
@@ -342,6 +342,8 @@ GLOBAL_LIST_EMPTY(species_list)
if(progress)
progbar = new(user, delay, target || user)
+ SEND_SIGNAL(user, COMSIG_DO_AFTER_BEGAN)
+
var/endtime = world.time + delay
var/starttime = world.time
. = TRUE
@@ -380,7 +382,7 @@ GLOBAL_LIST_EMPTY(species_list)
if(interaction_key)
LAZYREMOVE(user.do_afters, interaction_key)
-
+ SEND_SIGNAL(user, COMSIG_DO_AFTER_ENDED)
///Timed action involving at least one mob user and a list of targets. interaction_key is the assoc key under which the do_after is capped under, and the max interaction count is how many of this interaction you can do at once.
/proc/do_after_mob(mob/user, list/targets, time = 3 SECONDS, timed_action_flags = NONE, progress = TRUE, datum/callback/extra_checks, interaction_key, max_interact_count = 1)
diff --git a/code/__HELPERS/roundend.dm b/code/__HELPERS/roundend.dm
index e2997205b48..5182ceee7d5 100644
--- a/code/__HELPERS/roundend.dm
+++ b/code/__HELPERS/roundend.dm
@@ -656,8 +656,8 @@
if(owner && GLOB.common_report && SSticker.current_state == GAME_STATE_FINISHED)
SSticker.show_roundend_report(owner.client)
-/datum/action/report/IsAvailable()
- return 1
+/datum/action/report/IsAvailable(feedback = FALSE)
+ return TRUE
/datum/action/report/Topic(href,href_list)
if(usr != owner)
diff --git a/code/controllers/subsystem/vote.dm b/code/controllers/subsystem/vote.dm
index 194e4e3e1eb..d3d639f6b29 100644
--- a/code/controllers/subsystem/vote.dm
+++ b/code/controllers/subsystem/vote.dm
@@ -311,7 +311,7 @@ SUBSYSTEM_DEF(vote)
name = "Vote!"
button_icon_state = "vote"
-/datum/action/vote/IsAvailable()
+/datum/action/vote/IsAvailable(feedback = FALSE)
return TRUE // Democracy is always available to the free people
/datum/action/vote/Trigger(trigger_flags)
diff --git a/code/datums/actions/action.dm b/code/datums/actions/action.dm
index a6b51e17970..e8974d303a3 100644
--- a/code/datums/actions/action.dm
+++ b/code/datums/actions/action.dm
@@ -123,25 +123,36 @@
/// Actually triggers the effects of the action.
/// Called when the on-screen button is clicked, for example.
/datum/action/proc/Trigger(trigger_flags)
- if(!IsAvailable())
+ if(!IsAvailable(feedback = TRUE))
return FALSE
if(SEND_SIGNAL(src, COMSIG_ACTION_TRIGGER, src) & COMPONENT_ACTION_BLOCK_TRIGGER)
return FALSE
return TRUE
-/// Whether our action is currently available to use or not
-/datum/action/proc/IsAvailable()
+/**
+ * Whether our action is currently available to use or not
+ * * feedback - If true this is being called to check if we have any messages to show to the owner
+ */
+/datum/action/proc/IsAvailable(feedback = FALSE)
if(!owner)
return FALSE
if((check_flags & AB_CHECK_HANDS_BLOCKED) && HAS_TRAIT(owner, TRAIT_HANDS_BLOCKED))
+ if (feedback)
+ owner.balloon_alert(owner, "hands blocked!")
return FALSE
if((check_flags & AB_CHECK_IMMOBILE) && HAS_TRAIT(owner, TRAIT_IMMOBILIZED))
+ if (feedback)
+ owner.balloon_alert(owner, "can't move!")
return FALSE
if((check_flags & AB_CHECK_LYING) && isliving(owner))
- var/mob/living/action_user = owner
- if(action_user.body_position == LYING_DOWN)
+ var/mob/living/action_owner = owner
+ if(action_owner.body_position == LYING_DOWN)
+ if (feedback)
+ owner.balloon_alert(owner, "must stand up!")
return FALSE
if((check_flags & AB_CHECK_CONSCIOUS) && owner.stat != CONSCIOUS)
+ if (feedback)
+ owner.balloon_alert(owner, "unconscious!")
return FALSE
return TRUE
diff --git a/code/datums/actions/cooldown_action.dm b/code/datums/actions/cooldown_action.dm
index 1005cc4def6..1126ee6e6d0 100644
--- a/code/datums/actions/cooldown_action.dm
+++ b/code/datums/actions/cooldown_action.dm
@@ -71,7 +71,7 @@
ability.Remove(removed_from)
return ..()
-/datum/action/cooldown/IsAvailable()
+/datum/action/cooldown/IsAvailable(feedback = FALSE)
return ..() && (next_use_time <= world.time)
/// Initializes any sequence actions
@@ -160,7 +160,7 @@
/// Intercepts client owner clicks to activate the ability
/datum/action/cooldown/proc/InterceptClickOn(mob/living/caller, params, atom/target)
- if(!IsAvailable())
+ if(!IsAvailable(feedback = TRUE))
return FALSE
if(!target)
return FALSE
diff --git a/code/datums/actions/innate_action.dm b/code/datums/actions/innate_action.dm
index 422ec8ad56f..08e67678857 100644
--- a/code/datums/actions/innate_action.dm
+++ b/code/datums/actions/innate_action.dm
@@ -66,7 +66,7 @@
/// Handles whenever a mob clicks on something
/datum/action/innate/proc/InterceptClickOn(mob/living/caller, params, atom/clicked_on)
- if(!IsAvailable())
+ if(!IsAvailable(feedback = TRUE))
unset_ranged_ability(caller)
return FALSE
if(!clicked_on)
diff --git a/code/datums/actions/items/organ_action.dm b/code/datums/actions/items/organ_action.dm
index 19a8f700373..367c00d656a 100644
--- a/code/datums/actions/items/organ_action.dm
+++ b/code/datums/actions/items/organ_action.dm
@@ -2,7 +2,7 @@
name = "Organ Action"
check_flags = AB_CHECK_CONSCIOUS
-/datum/action/item_action/organ_action/IsAvailable()
+/datum/action/item_action/organ_action/IsAvailable(feedback = FALSE)
var/obj/item/organ/attached_organ = target
if(!attached_organ.owner)
return FALSE
diff --git a/code/datums/actions/items/toggles.dm b/code/datums/actions/items/toggles.dm
index 1af240a6b02..92804adedf6 100644
--- a/code/datums/actions/items/toggles.dm
+++ b/code/datums/actions/items/toggles.dm
@@ -74,7 +74,7 @@
/datum/action/item_action/jetpack_stabilization
name = "Toggle Jetpack Stabilization"
-/datum/action/item_action/jetpack_stabilization/IsAvailable()
+/datum/action/item_action/jetpack_stabilization/IsAvailable(feedback = FALSE)
var/obj/item/tank/jetpack/linked_jetpack = target
if(!istype(linked_jetpack) || !linked_jetpack.on)
return FALSE
diff --git a/code/datums/actions/items/vortex_recall.dm b/code/datums/actions/items/vortex_recall.dm
index 943da403e7a..625c04a4199 100644
--- a/code/datums/actions/items/vortex_recall.dm
+++ b/code/datums/actions/items/vortex_recall.dm
@@ -4,7 +4,7 @@
icon_icon = 'icons/mob/actions/actions_items.dmi'
button_icon_state = "vortex_recall"
-/datum/action/item_action/vortex_recall/IsAvailable()
+/datum/action/item_action/vortex_recall/IsAvailable(feedback = FALSE)
var/area/current_area = get_area(target)
if(!current_area || current_area.area_flags & NOTELEPORT)
return FALSE
diff --git a/code/datums/components/mind_linker.dm b/code/datums/components/mind_linker.dm
index 6e852f6507a..7e35b73893e 100644
--- a/code/datums/components/mind_linker.dm
+++ b/code/datums/components/mind_linker.dm
@@ -185,7 +185,7 @@
button_icon_state = linker.speech_action_icon_state
background_icon_state = linker.speech_action_background_icon_state
-/datum/action/innate/linked_speech/IsAvailable()
+/datum/action/innate/linked_speech/IsAvailable(feedback = FALSE)
return ..() && (owner.stat != DEAD)
/datum/action/innate/linked_speech/Activate()
diff --git a/code/datums/dash_weapon.dm b/code/datums/dash_weapon.dm
index 71f77908b2e..a156efc17e8 100644
--- a/code/datums/dash_weapon.dm
+++ b/code/datums/dash_weapon.dm
@@ -23,8 +23,15 @@
/// What effect should we play when we phase out (at the source turf)
var/phaseout = /obj/effect/temp_visual/dir_setting/ninja/phase/out
-/datum/action/innate/dash/IsAvailable()
- return ..() && (current_charges > 0)
+/datum/action/innate/dash/IsAvailable(feedback = FALSE)
+ . = ..()
+ if (!.)
+ return FALSE
+ if (current_charges <= 0)
+ if (feedback)
+ owner.balloon_alert(owner, "no charges!")
+ return FALSE
+ return TRUE
/datum/action/innate/dash/Activate()
var/obj/item/dashing_item = target
@@ -35,8 +42,7 @@
/// Teleports user to target using do_teleport. Returns TRUE if teleport successful, FALSE otherwise.
/datum/action/innate/dash/proc/teleport(mob/user, atom/target)
- if(!IsAvailable())
- user.balloon_alert(user, "no charges!")
+ if(!IsAvailable(feedback = TRUE))
return FALSE
var/turf/current_turf = get_turf(user)
diff --git a/code/modules/antagonists/_common/antag_datum.dm b/code/modules/antagonists/_common/antag_datum.dm
index c2c7f09c322..e692d154e62 100644
--- a/code/modules/antagonists/_common/antag_datum.dm
+++ b/code/modules/antagonists/_common/antag_datum.dm
@@ -494,7 +494,7 @@ GLOBAL_LIST_EMPTY(antagonists)
target.ui_interact(owner)
-/datum/action/antag_info/IsAvailable()
+/datum/action/antag_info/IsAvailable(feedback = FALSE)
if(!target)
stack_trace("[type] was used without a target antag datum!")
return FALSE
diff --git a/code/modules/antagonists/cult/blood_magic.dm b/code/modules/antagonists/cult/blood_magic.dm
index 309b70be59f..859f4c9f346 100644
--- a/code/modules/antagonists/cult/blood_magic.dm
+++ b/code/modules/antagonists/cult/blood_magic.dm
@@ -11,7 +11,7 @@
qdel(X)
..()
-/datum/action/innate/cult/blood_magic/IsAvailable()
+/datum/action/innate/cult/blood_magic/IsAvailable(feedback = FALSE)
if(!IS_CULTIST(owner))
return FALSE
return ..()
@@ -117,7 +117,7 @@
hand_magic = null
..()
-/datum/action/innate/cult/blood_spell/IsAvailable()
+/datum/action/innate/cult/blood_spell/IsAvailable(feedback = FALSE)
if(!IS_CULTIST(owner) || owner.incapacitated() || !charges)
return FALSE
return ..()
diff --git a/code/modules/antagonists/cult/cult_comms.dm b/code/modules/antagonists/cult/cult_comms.dm
index 7cc77a0520b..1f9d77a512c 100644
--- a/code/modules/antagonists/cult/cult_comms.dm
+++ b/code/modules/antagonists/cult/cult_comms.dm
@@ -7,7 +7,7 @@
check_flags = AB_CHECK_HANDS_BLOCKED|AB_CHECK_IMMOBILE|AB_CHECK_CONSCIOUS
ranged_mousepointer = 'icons/effects/mouse_pointers/cult_target.dmi'
-/datum/action/innate/cult/IsAvailable()
+/datum/action/innate/cult/IsAvailable(feedback = FALSE)
if(!IS_CULTIST(owner))
return FALSE
return ..()
@@ -17,14 +17,14 @@
desc = "Whispered words that all cultists can hear.
Warning:Nearby non-cultists can still hear you."
button_icon_state = "cult_comms"
-/datum/action/innate/cult/comm/IsAvailable()
+/datum/action/innate/cult/comm/IsAvailable(feedback = FALSE)
if(isshade(owner) && IS_CULTIST(owner))
return TRUE
return ..()
/datum/action/innate/cult/comm/Activate()
var/input = tgui_input_text(usr, "Message to tell to the other acolytes", "Voice of Blood")
- if(!input || !IsAvailable())
+ if(!input || !IsAvailable(feedback = TRUE))
return
var/list/filter_result = CAN_BYPASS_FILTER(usr) ? null : is_ic_filtered(input)
@@ -68,7 +68,7 @@
name = "Spiritual Communion"
desc = "Conveys a message from the spirit realm that all cultists can hear."
-/datum/action/innate/cult/comm/spirit/IsAvailable()
+/datum/action/innate/cult/comm/spirit/IsAvailable(feedback = FALSE)
if(IS_CULTIST(owner.mind.current))
return TRUE
return ..()
@@ -89,7 +89,7 @@
name = "Assert Leadership"
button_icon_state = "cultvote"
-/datum/action/innate/cult/mastervote/IsAvailable()
+/datum/action/innate/cult/mastervote/IsAvailable(feedback = FALSE)
var/datum/antagonist/cult/C = owner.mind.has_antag_datum(/datum/antagonist/cult,TRUE)
if(!C || C.cult_team.cult_vote_called || !ishuman(owner))
return FALSE
@@ -157,7 +157,7 @@
to_chat(B.current,span_cultlarge("[Nominee] has won the cult's support and is now their master. Follow [Nominee.p_their()] orders to the best of your ability!"))
return TRUE
-/datum/action/innate/cult/master/IsAvailable()
+/datum/action/innate/cult/master/IsAvailable(feedback = FALSE)
if(!owner.mind || !owner.mind.has_antag_datum(/datum/antagonist/cult/master) || GLOB.cult_narsie)
return FALSE
return ..()
@@ -246,7 +246,7 @@
/// The actual cooldown tracked of the action
COOLDOWN_DECLARE(cult_mark_cooldown)
-/datum/action/innate/cult/master/cultmark/IsAvailable()
+/datum/action/innate/cult/master/cultmark/IsAvailable(feedback = FALSE)
return ..() && COOLDOWN_FINISHED(src, cult_mark_cooldown)
/datum/action/innate/cult/master/cultmark/InterceptClickOn(mob/caller, params, atom/clicked_on)
@@ -293,7 +293,7 @@
/// The actual cooldown tracked of the action
COOLDOWN_DECLARE(cult_mark_cooldown)
-/datum/action/innate/cult/ghostmark/IsAvailable()
+/datum/action/innate/cult/ghostmark/IsAvailable(feedback = FALSE)
return ..() && isobserver(owner)
/datum/action/innate/cult/ghostmark/Activate()
@@ -372,7 +372,7 @@
/// The actual cooldown tracked of the action
COOLDOWN_DECLARE(pulse_cooldown)
-/datum/action/innate/cult/master/pulse/IsAvailable()
+/datum/action/innate/cult/master/pulse/IsAvailable(feedback = FALSE)
return ..() && COOLDOWN_FINISHED(src, pulse_cooldown)
/datum/action/innate/cult/master/pulse/InterceptClickOn(mob/living/caller, params, atom/clicked_on)
diff --git a/code/modules/antagonists/cult/cult_items.dm b/code/modules/antagonists/cult/cult_items.dm
index 1f463daa1ee..0bc37ba5369 100644
--- a/code/modules/antagonists/cult/cult_items.dm
+++ b/code/modules/antagonists/cult/cult_items.dm
@@ -133,7 +133,7 @@ Striking a noncultist, however, will tear their flesh."}
phasein = /obj/effect/temp_visual/dir_setting/cult/phase
phaseout = /obj/effect/temp_visual/dir_setting/cult/phase/out
-/datum/action/innate/dash/cult/IsAvailable()
+/datum/action/innate/dash/cult/IsAvailable(feedback = FALSE)
if(IS_CULTIST(owner) && current_charges)
return TRUE
else
diff --git a/code/modules/antagonists/cult/rune_spawn_action.dm b/code/modules/antagonists/cult/rune_spawn_action.dm
index fe10b3a8193..de9cdda3570 100644
--- a/code/modules/antagonists/cult/rune_spawn_action.dm
+++ b/code/modules/antagonists/cult/rune_spawn_action.dm
@@ -14,7 +14,7 @@
var/obj/effect/temp_visual/cult/rune_spawn/rune_center_type
var/rune_color
-/datum/action/innate/cult/create_rune/IsAvailable()
+/datum/action/innate/cult/create_rune/IsAvailable(feedback = FALSE)
if(!rune_type || cooldown > world.time)
return FALSE
return ..()
diff --git a/code/modules/antagonists/heretic/heretic_living_heart.dm b/code/modules/antagonists/heretic/heretic_living_heart.dm
index df726afa79b..3fa3a40c250 100644
--- a/code/modules/antagonists/heretic/heretic_living_heart.dm
+++ b/code/modules/antagonists/heretic/heretic_living_heart.dm
@@ -87,7 +87,7 @@
return ..()
-/datum/action/cooldown/track_target/IsAvailable()
+/datum/action/cooldown/track_target/IsAvailable(feedback = FALSE)
. = ..()
if(!.)
return
diff --git a/code/modules/antagonists/heretic/magic/expand_sight.dm b/code/modules/antagonists/heretic/magic/expand_sight.dm
index be93270c740..6c2782be276 100644
--- a/code/modules/antagonists/heretic/magic/expand_sight.dm
+++ b/code/modules/antagonists/heretic/magic/expand_sight.dm
@@ -10,7 +10,7 @@
/// A cooldown for the last time we toggled it, to prevent spam.
COOLDOWN_DECLARE(last_toggle)
-/datum/action/innate/expand_sight/IsAvailable()
+/datum/action/innate/expand_sight/IsAvailable(feedback = FALSE)
return ..() && COOLDOWN_FINISHED(src, last_toggle)
/datum/action/innate/expand_sight/Activate()
diff --git a/code/modules/antagonists/heretic/structures/carving_knife.dm b/code/modules/antagonists/heretic/structures/carving_knife.dm
index 2e6a1772314..6c642a97132 100644
--- a/code/modules/antagonists/heretic/structures/carving_knife.dm
+++ b/code/modules/antagonists/heretic/structures/carving_knife.dm
@@ -142,7 +142,7 @@
return ..()
-/datum/action/item_action/rune_shatter/IsAvailable()
+/datum/action/item_action/rune_shatter/IsAvailable(feedback = FALSE)
. = ..()
if(!.)
return
diff --git a/code/modules/antagonists/traitor/equipment/Malf_Modules.dm b/code/modules/antagonists/traitor/equipment/Malf_Modules.dm
index 4cac9564a67..e4885c2a57e 100644
--- a/code/modules/antagonists/traitor/equipment/Malf_Modules.dm
+++ b/code/modules/antagonists/traitor/equipment/Malf_Modules.dm
@@ -65,7 +65,7 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module))
else
owner_AI = owner
-/datum/action/innate/ai/IsAvailable()
+/datum/action/innate/ai/IsAvailable(feedback = FALSE)
. = ..()
if(owner_AI && owner_AI.malf_cooldown > world.time)
return
diff --git a/code/modules/clothing/chameleon.dm b/code/modules/clothing/chameleon.dm
index 923513e5550..3447d6be48b 100644
--- a/code/modules/clothing/chameleon.dm
+++ b/code/modules/clothing/chameleon.dm
@@ -6,7 +6,7 @@
button_icon_state = "random"
/datum/action/item_action/chameleon/drone/randomise/Trigger(trigger_flags)
- if(!IsAvailable())
+ if(!IsAvailable(feedback = TRUE))
return
// Damn our lack of abstract interfeces
@@ -33,7 +33,7 @@
button_icon_state = "drone_camogear_mask"
/datum/action/item_action/chameleon/drone/togglehatmask/Trigger(trigger_flags)
- if(!IsAvailable())
+ if(!IsAvailable(feedback = TRUE))
return
// No point making the code more complicated if no non-drone
@@ -91,12 +91,12 @@
return select_outfit(owner)
/datum/action/chameleon_outfit/proc/select_outfit(mob/user)
- if(!user || !IsAvailable())
+ if(!user || !IsAvailable(feedback = TRUE))
return FALSE
var/selected = tgui_input_list(user, "Select outfit to change into", "Chameleon Outfit", outfit_options)
if(isnull(selected))
return FALSE
- if(!IsAvailable() || QDELETED(src) || QDELETED(user))
+ if(!IsAvailable(feedback = TRUE) || QDELETED(src) || QDELETED(user))
return FALSE
if(isnull(outfit_options[selected]))
return FALSE
@@ -244,7 +244,7 @@
atom_target.icon = initial(picked_item.icon)
/datum/action/item_action/chameleon/change/Trigger(trigger_flags)
- if(!IsAvailable())
+ if(!IsAvailable(feedback = TRUE))
return
select_look(owner)
diff --git a/code/modules/mining/lavaland/tendril_loot.dm b/code/modules/mining/lavaland/tendril_loot.dm
index 1e444161a72..2fd23748013 100644
--- a/code/modules/mining/lavaland/tendril_loot.dm
+++ b/code/modules/mining/lavaland/tendril_loot.dm
@@ -796,7 +796,7 @@
cooldown_time = 45 SECONDS
ranged_mousepointer = 'icons/effects/mouse_pointers/scan_target.dmi'
-/datum/action/cooldown/scan/IsAvailable()
+/datum/action/cooldown/scan/IsAvailable(feedback = FALSE)
return ..() && isliving(owner)
/datum/action/cooldown/scan/Activate(atom/scanned)
diff --git a/code/modules/mob/living/carbon/alien/adult/alien_powers.dm b/code/modules/mob/living/carbon/alien/adult/alien_powers.dm
index eecae4638a9..4f1e711adba 100644
--- a/code/modules/mob/living/carbon/alien/adult/alien_powers.dm
+++ b/code/modules/mob/living/carbon/alien/adult/alien_powers.dm
@@ -18,7 +18,7 @@ Doesn't work on other aliens/AI.*/
/// How much plasma this action uses.
var/plasma_cost = 0
-/datum/action/cooldown/alien/IsAvailable()
+/datum/action/cooldown/alien/IsAvailable(feedback = FALSE)
. = ..()
if(!.)
return FALSE
@@ -61,7 +61,7 @@ Doesn't work on other aliens/AI.*/
/// The type of structure the action makes on use
var/obj/structure/made_structure_type
-/datum/action/cooldown/alien/make_structure/IsAvailable()
+/datum/action/cooldown/alien/make_structure/IsAvailable(feedback = FALSE)
. = ..()
if(!.)
return FALSE
@@ -135,7 +135,7 @@ Doesn't work on other aliens/AI.*/
return FALSE
var/to_whisper = tgui_input_text(owner, title = "Alien Whisper")
- if(QDELETED(chosen_recipient) || QDELETED(src) || QDELETED(owner) || !IsAvailable() || !to_whisper)
+ if(QDELETED(chosen_recipient) || QDELETED(src) || QDELETED(owner) || !IsAvailable(feedback = TRUE) || !to_whisper)
return FALSE
if(chosen_recipient.can_block_magic(MAGIC_RESISTANCE_MIND, charge_cost = 0))
to_chat(owner, span_warning("As you reach into [chosen_recipient]'s mind, you are stopped by a mental blockage. It seems you've been foiled."))
@@ -176,7 +176,7 @@ Doesn't work on other aliens/AI.*/
return FALSE
var/amount = tgui_input_number(owner, "Amount", "Transfer Plasma to [donation_target]", max_value = carbon_owner.getPlasma())
- if(QDELETED(donation_target) || QDELETED(src) || QDELETED(owner) || !IsAvailable() || isnull(amount) || amount <= 0)
+ if(QDELETED(donation_target) || QDELETED(src) || QDELETED(owner) || !IsAvailable(feedback = TRUE) || isnull(amount) || amount <= 0)
return FALSE
if(get_dist(owner, donation_target) > 1)
@@ -243,7 +243,7 @@ Doesn't work on other aliens/AI.*/
button_icon_state = "alien_neurotoxin_0"
plasma_cost = 50
-/datum/action/cooldown/alien/acid/neurotoxin/IsAvailable()
+/datum/action/cooldown/alien/acid/neurotoxin/IsAvailable(feedback = FALSE)
if(owner.is_muzzled())
return FALSE
if(!isturf(owner.loc))
@@ -327,7 +327,7 @@ Doesn't work on other aliens/AI.*/
/datum/action/cooldown/alien/make_structure/resin/Activate(atom/target)
var/choice = tgui_input_list(owner, "Select a shape to build", "Resin building", structures)
- if(isnull(choice) || QDELETED(src) || QDELETED(owner) || !check_for_duplicate() || !IsAvailable())
+ if(isnull(choice) || QDELETED(src) || QDELETED(owner) || !check_for_duplicate() || !IsAvailable(feedback = TRUE))
return FALSE
var/obj/structure/choice_path = structures[choice]
diff --git a/code/modules/mob/living/carbon/alien/adult/caste/drone.dm b/code/modules/mob/living/carbon/alien/adult/caste/drone.dm
index 3d00692b869..1d9404fed4b 100644
--- a/code/modules/mob/living/carbon/alien/adult/caste/drone.dm
+++ b/code/modules/mob/living/carbon/alien/adult/caste/drone.dm
@@ -22,7 +22,7 @@
button_icon_state = "alien_evolve_drone"
plasma_cost = 500
-/datum/action/cooldown/alien/evolve_to_praetorian/IsAvailable()
+/datum/action/cooldown/alien/evolve_to_praetorian/IsAvailable(feedback = FALSE)
. = ..()
if(!.)
return FALSE
diff --git a/code/modules/mob/living/carbon/alien/adult/caste/praetorian.dm b/code/modules/mob/living/carbon/alien/adult/caste/praetorian.dm
index d8fa91cc61d..df3c9da6618 100644
--- a/code/modules/mob/living/carbon/alien/adult/caste/praetorian.dm
+++ b/code/modules/mob/living/carbon/alien/adult/caste/praetorian.dm
@@ -29,7 +29,7 @@
button_icon_state = "alien_evolve_praetorian"
plasma_cost = 500
-/datum/action/cooldown/alien/evolve_to_queen/IsAvailable()
+/datum/action/cooldown/alien/evolve_to_queen/IsAvailable(feedback = FALSE)
. = ..()
if(!.)
return FALSE
diff --git a/code/modules/mob/living/carbon/alien/adult/queen.dm b/code/modules/mob/living/carbon/alien/adult/queen.dm
index 8d3cc37eaee..c6fe38ac2a9 100644
--- a/code/modules/mob/living/carbon/alien/adult/queen.dm
+++ b/code/modules/mob/living/carbon/alien/adult/queen.dm
@@ -97,7 +97,7 @@
.[PANEL_DISPLAY_STATUS] = "PLASMA - [promotion_plasma_cost]"
-/datum/action/cooldown/alien/promote/IsAvailable()
+/datum/action/cooldown/alien/promote/IsAvailable(feedback = FALSE)
. = ..()
if(!.)
return FALSE
diff --git a/code/modules/mob/living/carbon/alien/larva/powers.dm b/code/modules/mob/living/carbon/alien/larva/powers.dm
index 246c47a2bb1..e8b3c0b2e8a 100644
--- a/code/modules/mob/living/carbon/alien/larva/powers.dm
+++ b/code/modules/mob/living/carbon/alien/larva/powers.dm
@@ -29,7 +29,7 @@
button_icon_state = "alien_evolve_larva"
plasma_cost = 0
-/datum/action/cooldown/alien/larva_evolve/IsAvailable()
+/datum/action/cooldown/alien/larva_evolve/IsAvailable(feedback = FALSE)
. = ..()
if(!.)
return FALSE
@@ -83,7 +83,7 @@
caste_options["Drone"] = drone
var/alien_caste = show_radial_menu(owner, owner, caste_options, radius = 38, require_near = TRUE, tooltips = TRUE)
- if(QDELETED(src) || QDELETED(owner) || !IsAvailable() || isnull(alien_caste))
+ if(QDELETED(src) || QDELETED(owner) || !IsAvailable(feedback = TRUE) || isnull(alien_caste))
return
var/mob/living/carbon/alien/adult/new_xeno
diff --git a/code/modules/mob/living/carbon/human/species_types/golems.dm b/code/modules/mob/living/carbon/human/species_types/golems.dm
index ce27d5e11af..b22ecf0799b 100644
--- a/code/modules/mob/living/carbon/human/species_types/golems.dm
+++ b/code/modules/mob/living/carbon/human/species_types/golems.dm
@@ -540,7 +540,7 @@
///Set to true upon action activation to prevent spamming teleport callbacks while the first is still occurring.
var/is_charging = FALSE
-/datum/action/innate/unstable_teleport/IsAvailable()
+/datum/action/innate/unstable_teleport/IsAvailable(feedback = FALSE)
. = ..()
if(!.)
return
diff --git a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
index 0d027704091..1a5d3ed7ef2 100644
--- a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
@@ -105,7 +105,7 @@
icon_icon = 'icons/mob/actions/actions_slime.dmi'
background_icon_state = "bg_alien"
-/datum/action/innate/regenerate_limbs/IsAvailable()
+/datum/action/innate/regenerate_limbs/IsAvailable(feedback = FALSE)
. = ..()
if(!.)
return
@@ -224,7 +224,7 @@
icon_icon = 'icons/mob/actions/actions_slime.dmi'
background_icon_state = "bg_alien"
-/datum/action/innate/split_body/IsAvailable()
+/datum/action/innate/split_body/IsAvailable(feedback = FALSE)
. = ..()
if(!.)
return
@@ -585,7 +585,7 @@
background_icon_state = "bg_alien"
var/activation_type = SLIME_ACTIVATE_MINOR
-/datum/action/innate/use_extract/IsAvailable()
+/datum/action/innate/use_extract/IsAvailable(feedback = FALSE)
if(..())
var/datum/species/jelly/luminescent/species = target
if(species && species.current_extract && (world.time > species.extract_cooldown))
@@ -712,7 +712,7 @@
stack_trace("[name] ([type]) was instantiated on a non-mind_linker target, this doesn't work.")
qdel(src)
-/datum/action/innate/link_minds/IsAvailable()
+/datum/action/innate/link_minds/IsAvailable(feedback = FALSE)
. = ..()
if(!.)
return
diff --git a/code/modules/mob/living/silicon/robot/robot_model.dm b/code/modules/mob/living/silicon/robot/robot_model.dm
index aa37fa950c6..6162d8bbba2 100644
--- a/code/modules/mob/living/silicon/robot/robot_model.dm
+++ b/code/modules/mob/living/silicon/robot/robot_model.dm
@@ -409,7 +409,7 @@
. = ..()
wash_audio = new(owner)
-/datum/action/toggle_buffer/IsAvailable()
+/datum/action/toggle_buffer/IsAvailable(feedback = FALSE)
if(!iscyborg(owner))
return FALSE
return ..()
diff --git a/code/modules/mob/living/simple_animal/bot/vibebot.dm b/code/modules/mob/living/simple_animal/bot/vibebot.dm
index f1769cd9883..59b01b1399a 100644
--- a/code/modules/mob/living/simple_animal/bot/vibebot.dm
+++ b/code/modules/mob/living/simple_animal/bot/vibebot.dm
@@ -64,7 +64,7 @@
icon_icon = 'icons/mob/actions/actions_minor_antag.dmi'
button_icon_state = "funk"
-/datum/action/innate/vibe/IsAvailable()
+/datum/action/innate/vibe/IsAvailable(feedback = FALSE)
. = ..()
if(!.)
return FALSE
diff --git a/code/modules/mob/living/simple_animal/hostile/bosses/paperwizard.dm b/code/modules/mob/living/simple_animal/hostile/bosses/paperwizard.dm
index c746625110a..75fa33ec49f 100644
--- a/code/modules/mob/living/simple_animal/hostile/bosses/paperwizard.dm
+++ b/code/modules/mob/living/simple_animal/hostile/bosses/paperwizard.dm
@@ -47,7 +47,7 @@
///How many minions we should spawn
var/minions_to_summon = 3
-/datum/action/boss/wizard_summon_minions/IsAvailable()
+/datum/action/boss/wizard_summon_minions/IsAvailable(feedback = FALSE)
. = ..()
if(!.)
return FALSE
diff --git a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm
index a0738b1deca..1552bf6a318 100644
--- a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm
+++ b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm
@@ -155,13 +155,13 @@
return ..()
var/mob/living/simple_animal/hostile/giant_spider/hurt_spider = target
if(hurt_spider == src)
- to_chat(src, span_warning("You don't have the dexerity to wrap your own wounds."))
+ balloon_alert(src, "can't heal yourself!")
return
if(hurt_spider.health >= hurt_spider.maxHealth)
- to_chat(src, span_warning("You can't find any wounds to wrap up."))
+ balloon_alert(src, "not hurt!")
return
if(hurt_spider.stat == DEAD)
- to_chat(src, span_warning("You're a nurse, not a miracle worker."))
+ balloon_alert(src, "they're dead!")
return
visible_message(
span_notice("[src] begins wrapping the wounds of [hurt_spider]."),
@@ -310,25 +310,32 @@
check_flags = AB_CHECK_CONSCIOUS
button_icon_state = "lay_web"
-/datum/action/innate/spider/lay_web/IsAvailable()
+/datum/action/innate/spider/lay_web/Grant(mob/grant_to)
+ . = ..()
+ if (!owner)
+ return
+ RegisterSignal(owner, list(COMSIG_MOVABLE_MOVED, COMSIG_DO_AFTER_BEGAN, COMSIG_DO_AFTER_ENDED), .proc/update_icon_on_signal)
+
+/datum/action/innate/spider/lay_web/Remove(mob/removed_from)
+ . = ..()
+ UnregisterSignal(removed_from, list(COMSIG_MOVABLE_MOVED, COMSIG_DO_AFTER_BEGAN, COMSIG_DO_AFTER_ENDED))
+
+/datum/action/innate/spider/lay_web/IsAvailable(feedback = FALSE)
. = ..()
if(!.)
return FALSE
-
- if(DOING_INTERACTION(owner, INTERACTION_SPIDER_KEY))
- return FALSE
if(!isspider(owner))
return FALSE
-
+ if(DOING_INTERACTION(owner, INTERACTION_SPIDER_KEY))
+ return FALSE
+ if(!isturf(owner.loc))
+ return FALSE
var/mob/living/simple_animal/hostile/giant_spider/spider = owner
var/obj/structure/spider/stickyweb/web = locate() in get_turf(spider)
if(web && (!spider.web_sealer || istype(web, /obj/structure/spider/stickyweb/sealed)))
- to_chat(owner, span_warning("There's already a web here!"))
+ if (feedback)
+ owner.balloon_alert(owner, "already webbed!")
return FALSE
-
- if(!isturf(spider.loc))
- return FALSE
-
return TRUE
/datum/action/innate/spider/lay_web/Activate()
@@ -348,19 +355,21 @@
spider.stop_automated_movement = TRUE
- if(do_after(spider, 4 SECONDS * spider.web_speed, target = spider_turf))
+ if(do_after(spider, 4 SECONDS * spider.web_speed, target = spider_turf, interaction_key = INTERACTION_SPIDER_KEY))
if(spider.loc == spider_turf)
if(web)
qdel(web)
new /obj/structure/spider/stickyweb/sealed(spider_turf)
new /obj/structure/spider/stickyweb(spider_turf)
+ UpdateButtons()
spider.stop_automated_movement = FALSE
/datum/action/cooldown/wrap
name = "Wrap"
desc = "Wrap something or someone in a cocoon. If it's a human or similar species, \
- you'll also consume them, allowing you to lay enriched eggs."
+ you'll also consume them, allowing you to lay enriched eggs. \
+ Activate this ability and then click on an adjacent target to begin wrapping them."
background_icon_state = "bg_alien"
icon_icon = 'icons/mob/actions/actions_animal.dmi'
button_icon_state = "wrap_0"
@@ -370,7 +379,17 @@
/// The time it takes to wrap something.
var/wrap_time = 5 SECONDS
-/datum/action/cooldown/wrap/IsAvailable()
+/datum/action/cooldown/wrap/Grant(mob/grant_to)
+ . = ..()
+ if (!owner)
+ return
+ RegisterSignal(owner, list(COMSIG_DO_AFTER_BEGAN, COMSIG_DO_AFTER_ENDED), .proc/update_icon_on_signal)
+
+/datum/action/cooldown/wrap/Remove(mob/removed_from)
+ . = ..()
+ UnregisterSignal(removed_from, list(COMSIG_DO_AFTER_BEGAN, COMSIG_DO_AFTER_ENDED))
+
+/datum/action/cooldown/wrap/IsAvailable(feedback = FALSE)
. = ..()
if(!.)
return FALSE
@@ -385,8 +404,8 @@
if(!.)
return
- to_chat(on_who, span_notice("You prepare to wrap something in a cocoon. Left-click your target to start wrapping!"))
- button_icon_state = "wrap_0"
+ on_who.balloon_alert(on_who, "prepared to wrap")
+ button_icon_state = "wrap_1"
UpdateButtons()
/datum/action/cooldown/wrap/unset_click_ability(mob/on_who, refund_cooldown = TRUE)
@@ -394,9 +413,9 @@
if(!.)
return
- if(refund_cooldown)
- to_chat(on_who, span_notice("You no longer prepare to wrap something in a cocoon."))
- button_icon_state = "wrap_1"
+ if (refund_cooldown)
+ on_who.balloon_alert(on_who, "wrap cancelled")
+ button_icon_state = "wrap_0"
UpdateButtons()
/datum/action/cooldown/wrap/Activate(atom/to_wrap)
@@ -433,30 +452,34 @@
animal_owner.stop_automated_movement = TRUE
if(do_after(owner, wrap_time, target = to_wrap, interaction_key = INTERACTION_SPIDER_KEY))
- var/obj/structure/spider/cocoon/casing = new(to_wrap.loc)
- if(isliving(to_wrap))
- var/mob/living/living_wrapped = to_wrap
- // if they're not dead, you can consume them anyway
- if(ishuman(living_wrapped) && (living_wrapped.stat != DEAD || !HAS_TRAIT(living_wrapped, TRAIT_SPIDER_CONSUMED)))
- var/datum/action/innate/spider/lay_eggs/enriched/egg_power = locate() in owner.actions
- if(egg_power)
- egg_power.charges++
- egg_power.UpdateButtons()
- owner.visible_message(
- span_danger("[owner] sticks a proboscis into [living_wrapped] and sucks a viscous substance out."),
- span_notice("You suck the nutriment out of [living_wrapped], feeding you enough to lay a cluster of enriched eggs."),
- )
-
- living_wrapped.death() //you just ate them, they're dead.
- else
- to_chat(owner, span_warning("[living_wrapped] cannot sate your hunger!"))
-
- to_wrap.forceMove(casing)
- if(to_wrap.density || ismob(to_wrap))
- casing.icon_state = pick("cocoon_large1", "cocoon_large2", "cocoon_large3")
-
+ wrap_target(to_wrap)
if(istype(animal_owner))
- animal_owner.stop_automated_movement = TRUE
+ animal_owner.stop_automated_movement = FALSE
+
+/datum/action/cooldown/wrap/proc/wrap_target(atom/movable/to_wrap)
+ var/obj/structure/spider/cocoon/casing = new(to_wrap.loc)
+ if(isliving(to_wrap))
+ var/mob/living/living_wrapped = to_wrap
+ // You get a point every time you consume a living player, even if they've been consumed before.
+ // You only get a point for any individual corpse once, so you can't keep breaking it out and eating it again.
+ if(ishuman(living_wrapped) && (living_wrapped.stat != DEAD || !HAS_TRAIT(living_wrapped, TRAIT_SPIDER_CONSUMED)))
+ var/datum/action/innate/spider/lay_eggs/enriched/egg_power = locate() in owner.actions
+ if(egg_power)
+ egg_power.charges++
+ egg_power.UpdateButtons()
+ owner.visible_message(
+ span_danger("[owner] sticks a proboscis into [living_wrapped] and sucks a viscous substance out."),
+ span_notice("You suck the nutriment out of [living_wrapped], feeding you enough to lay a cluster of enriched eggs."),
+ )
+ ADD_TRAIT(living_wrapped, TRAIT_SPIDER_CONSUMED, TRAIT_GENERIC)
+ living_wrapped.death() //you just ate them, they're dead.
+ log_combat(owner, living_wrapped, "spider cocooned")
+ else
+ to_chat(owner, span_warning("[living_wrapped] is not edible!"))
+
+ to_wrap.forceMove(casing)
+ if(to_wrap.density || ismob(to_wrap))
+ casing.icon_state = pick("cocoon_large1", "cocoon_large2", "cocoon_large3")
/datum/action/innate/spider/lay_eggs
name = "Lay Eggs"
@@ -468,24 +491,32 @@
///The type of egg we create
var/egg_type = /obj/effect/mob_spawn/ghost_role/spider
-/datum/action/innate/spider/lay_eggs/IsAvailable()
+/datum/action/innate/spider/lay_eggs/Grant(mob/grant_to)
+ . = ..()
+ if (!owner)
+ return
+ RegisterSignal(owner, list(COMSIG_MOVABLE_MOVED, COMSIG_DO_AFTER_BEGAN, COMSIG_DO_AFTER_ENDED), .proc/update_icon_on_signal)
+
+/datum/action/innate/spider/lay_eggs/Remove(mob/removed_from)
+ . = ..()
+ UnregisterSignal(removed_from, list(COMSIG_MOVABLE_MOVED, COMSIG_DO_AFTER_BEGAN, COMSIG_DO_AFTER_ENDED))
+
+/datum/action/innate/spider/lay_eggs/IsAvailable(feedback = FALSE)
. = ..()
if(!.)
return FALSE
-
if(!isspider(owner))
return FALSE
- var/obj/structure/spider/eggcluster/eggs = locate() in get_turf(owner)
- if(eggs)
- to_chat(owner, span_warning("There is already a cluster of eggs here!"))
- return FALSE
if(DOING_INTERACTION(owner, INTERACTION_SPIDER_KEY))
return FALSE
-
+ var/obj/structure/spider/eggcluster/eggs = locate() in get_turf(owner)
+ if(eggs)
+ if (feedback)
+ owner.balloon_alert(owner, "already eggs here!")
+ return FALSE
return TRUE
/datum/action/innate/spider/lay_eggs/Activate()
-
owner.visible_message(
span_notice("[owner] begins to lay a cluster of eggs."),
span_notice("You begin to lay a cluster of eggs."),
@@ -496,14 +527,19 @@
if(do_after(owner, egg_lay_time, target = get_turf(owner), interaction_key = INTERACTION_SPIDER_KEY))
var/obj/structure/spider/eggcluster/eggs = locate() in get_turf(owner)
- if(!eggs || !isturf(spider.loc))
- var/obj/effect/mob_spawn/ghost_role/spider/new_eggs = new egg_type(get_turf(spider))
- new_eggs.directive = spider.directive
- new_eggs.faction = spider.faction
- UpdateButtons(TRUE)
-
+ if(eggs)
+ owner.balloon_alert(owner, "already eggs here!")
+ else
+ lay_egg()
+ UpdateButtons(TRUE)
spider.stop_automated_movement = FALSE
+/datum/action/innate/spider/lay_eggs/proc/lay_egg()
+ var/mob/living/simple_animal/hostile/giant_spider/spider = owner
+ var/obj/effect/mob_spawn/ghost_role/spider/new_eggs = new egg_type(get_turf(owner))
+ new_eggs.directive = spider.directive
+ new_eggs.faction = spider.faction
+
/datum/action/innate/spider/lay_eggs/enriched
name = "Lay Enriched Eggs"
desc = "Lay a cluster of eggs, which will soon grow into a greater spider. Requires you drain a human per cluster of these eggs."
@@ -512,8 +548,19 @@
/// How many charges we have to make eggs
var/charges = 0
-/datum/action/innate/spider/lay_eggs/enriched/IsAvailable()
- return ..() && (charges > 0)
+/datum/action/innate/spider/lay_eggs/enriched/IsAvailable(feedback = FALSE)
+ . = ..()
+ if (!.)
+ return FALSE
+ if (charges <= 0)
+ if (feedback)
+ owner.balloon_alert(owner, "must feed first!")
+ return FALSE
+ return TRUE
+
+/datum/action/innate/spider/lay_eggs/enriched/lay_egg()
+ charges--
+ return ..()
/datum/action/innate/spider/set_directive
name = "Set Directive"
@@ -521,14 +568,14 @@
check_flags = AB_CHECK_CONSCIOUS
button_icon_state = "directive"
-/datum/action/innate/spider/set_directive/IsAvailable()
+/datum/action/innate/spider/set_directive/IsAvailable(feedback = FALSE)
return ..() && isspider(owner)
/datum/action/innate/spider/set_directive/Activate()
var/mob/living/simple_animal/hostile/giant_spider/spider = owner
spider.directive = tgui_input_text(spider, "Enter the new directive", "Create directive", "[spider.directive]")
- if(isnull(spider.directive) || QDELETED(src) || QDELETED(owner) || !IsAvailable())
+ if(isnull(spider.directive) || QDELETED(src) || QDELETED(owner) || !IsAvailable(feedback = TRUE))
return FALSE
message_admins("[ADMIN_LOOKUPFLW(owner)] set its directive to: '[spider.directive]'.")
@@ -541,12 +588,12 @@
check_flags = AB_CHECK_CONSCIOUS
button_icon_state = "command"
-/datum/action/innate/spider/comm/IsAvailable()
+/datum/action/innate/spider/comm/IsAvailable(feedback = FALSE)
return ..() && istype(owner, /mob/living/simple_animal/hostile/giant_spider/midwife)
/datum/action/innate/spider/comm/Trigger(trigger_flags)
var/input = tgui_input_text(owner, "Input a command for your legions to follow.", "Command")
- if(!input || QDELETED(src) || QDELETED(owner) || !IsAvailable())
+ if(!input || QDELETED(src) || QDELETED(owner) || !IsAvailable(feedback = TRUE))
return FALSE
spider_command(owner, input)
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
index 01a58badbc2..fbf51441f16 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
@@ -616,7 +616,7 @@
icon_icon = 'icons/mob/actions/actions_spells.dmi'
button_icon_state = "exit_possession"
-/datum/action/exit_possession/IsAvailable()
+/datum/action/exit_possession/IsAvailable(feedback = FALSE)
return ..() && isfloorturf(owner.loc)
/datum/action/exit_possession/Trigger(trigger_flags)
diff --git a/code/modules/mob/living/simple_animal/hostile/ooze.dm b/code/modules/mob/living/simple_animal/hostile/ooze.dm
index fdc5caf11c5..d0c139e2332 100644
--- a/code/modules/mob/living/simple_animal/hostile/ooze.dm
+++ b/code/modules/mob/living/simple_animal/hostile/ooze.dm
@@ -145,7 +145,7 @@
///Mob needs to have enough nutrition
-/datum/action/cooldown/metabolicboost/IsAvailable()
+/datum/action/cooldown/metabolicboost/IsAvailable(feedback = FALSE)
. = ..()
var/mob/living/simple_animal/hostile/ooze/ooze = owner
if(!.)
@@ -435,7 +435,7 @@
ooze.adjust_ooze_nutrition(-30)
///Mob needs to have enough nutrition
-/datum/action/cooldown/gel_cocoon/IsAvailable()
+/datum/action/cooldown/gel_cocoon/IsAvailable(feedback = FALSE)
. = ..()
if(!.)
return
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 336f052d7ce..35dfdc169c7 100644
--- a/code/modules/mob/living/simple_animal/hostile/retaliate/clown.dm
+++ b/code/modules/mob/living/simple_animal/hostile/retaliate/clown.dm
@@ -500,7 +500,7 @@
on_who.icon_state = initial(on_who.icon_state)
on_who.update_appearance(UPDATE_ICON)
-/datum/action/cooldown/regurgitate/IsAvailable()
+/datum/action/cooldown/regurgitate/IsAvailable(feedback = FALSE)
. = ..()
if(!.)
return FALSE
diff --git a/code/modules/mob/living/simple_animal/slime/powers.dm b/code/modules/mob/living/simple_animal/slime/powers.dm
index 7c509530e2b..cbf17702b43 100644
--- a/code/modules/mob/living/simple_animal/slime/powers.dm
+++ b/code/modules/mob/living/simple_animal/slime/powers.dm
@@ -11,7 +11,7 @@
background_icon_state = "bg_alien"
var/needs_growth = NO_GROWTH_NEEDED
-/datum/action/innate/slime/IsAvailable()
+/datum/action/innate/slime/IsAvailable(feedback = FALSE)
. = ..()
if(!.)
return
diff --git a/code/modules/mod/mod_actions.dm b/code/modules/mod/mod_actions.dm
index a0890ffafe1..01ab9d56e25 100644
--- a/code/modules/mod/mod_actions.dm
+++ b/code/modules/mod/mod_actions.dm
@@ -30,7 +30,7 @@
return ..()
/datum/action/item_action/mod/Trigger(trigger_flags)
- if(!IsAvailable())
+ if(!IsAvailable(feedback = TRUE))
return FALSE
var/obj/item/mod/control/mod = target
if(mod.malfunctioning && prob(75))
diff --git a/code/modules/research/xenobiology/crossbreeding/_clothing.dm b/code/modules/research/xenobiology/crossbreeding/_clothing.dm
index 69c9097b3b4..76fca921f12 100644
--- a/code/modules/research/xenobiology/crossbreeding/_clothing.dm
+++ b/code/modules/research/xenobiology/crossbreeding/_clothing.dm
@@ -65,7 +65,7 @@ Slimecrossing Armor
button_icon_state = "prismcolor"
/datum/action/item_action/change_prism_colour/Trigger(trigger_flags)
- if(!IsAvailable())
+ if(!IsAvailable(feedback = TRUE))
return
var/obj/item/clothing/glasses/prism_glasses/glasses = target
var/new_color = input(owner, "Choose the lens color:", "Color change",glasses.glasses_color) as color|null
@@ -79,7 +79,7 @@ Slimecrossing Armor
button_icon_state = "lightprism"
/datum/action/item_action/place_light_prism/Trigger(trigger_flags)
- if(!IsAvailable())
+ if(!IsAvailable(feedback = TRUE))
return
var/obj/item/clothing/glasses/prism_glasses/glasses = target
if(locate(/obj/structure/light_prism) in get_turf(owner))
diff --git a/code/modules/spells/spell.dm b/code/modules/spells/spell.dm
index eea91cb3e63..b3f18fc1313 100644
--- a/code/modules/spells/spell.dm
+++ b/code/modules/spells/spell.dm
@@ -123,19 +123,8 @@
return ..()
-/datum/action/cooldown/spell/IsAvailable()
- return ..() && can_cast_spell(feedback = FALSE)
-
-/datum/action/cooldown/spell/Trigger(trigger_flags, atom/target)
- // We implement this can_cast_spell check before the parent call of Trigger()
- // to allow people to click unavailable abilities to get a feedback chat message
- // about why the ability is unavailable.
- // It is otherwise redundant, however, as IsAvailable() checks can_cast_spell as well.
- if(!can_cast_spell())
- UpdateButtons(TRUE)
- return FALSE
-
- return ..()
+/datum/action/cooldown/spell/IsAvailable(feedback = FALSE)
+ return ..() && can_cast_spell(feedback)
/datum/action/cooldown/spell/set_click_ability(mob/on_who)
if(SEND_SIGNAL(on_who, COMSIG_MOB_SPELL_ACTIVATED, src) & SPELL_CANCEL_CAST)
diff --git a/code/modules/spells/spell_types/pointed/swap.dm b/code/modules/spells/spell_types/pointed/swap.dm
index 8d9c7fb058d..65344df99a6 100644
--- a/code/modules/spells/spell_types/pointed/swap.dm
+++ b/code/modules/spells/spell_types/pointed/swap.dm
@@ -31,7 +31,7 @@
/datum/action/cooldown/spell/pointed/swap/InterceptClickOn(mob/living/caller, params, atom/click_target)
if(LAZYACCESS(params2list(params), RIGHT_CLICK))
- if(!IsAvailable())
+ if(!IsAvailable(feedback = TRUE))
return FALSE
if(!target)
return FALSE
diff --git a/code/modules/spells/spell_types/teleport/teleport.dm b/code/modules/spells/spell_types/teleport/teleport.dm
index a6ce6d1b9a5..1dfcf6f2608 100644
--- a/code/modules/spells/spell_types/teleport/teleport.dm
+++ b/code/modules/spells/spell_types/teleport/teleport.dm
@@ -38,7 +38,7 @@
invocation_says_area = FALSE
-/datum/action/cooldown/spell/teleport/area_teleport/wizard/scroll/IsAvailable()
+/datum/action/cooldown/spell/teleport/area_teleport/wizard/scroll/IsAvailable(feedback = FALSE)
return ..() && owner.is_holding(target)
/datum/action/cooldown/spell/teleport/area_teleport/wizard/scroll/before_cast(atom/cast_on)
diff --git a/code/modules/surgery/organs/vocal_cords.dm b/code/modules/surgery/organs/vocal_cords.dm
index ffef66ef99c..58b32075c14 100644
--- a/code/modules/surgery/organs/vocal_cords.dm
+++ b/code/modules/surgery/organs/vocal_cords.dm
@@ -33,7 +33,7 @@
icon_state = "adamantine_cords"
/datum/action/item_action/organ_action/use/adamantine_vocal_cords/Trigger(trigger_flags)
- if(!IsAvailable())
+ if(!IsAvailable(feedback = TRUE))
return
var/message = tgui_input_text(owner, "Resonate a message to all nearby golems", "Resonate")
if(!message)
@@ -72,25 +72,29 @@
..()
cords = target
-/datum/action/item_action/organ_action/colossus/IsAvailable()
- if(world.time < cords.next_command)
- return FALSE
+/datum/action/item_action/organ_action/colossus/IsAvailable(feedback = FALSE)
if(!owner)
return FALSE
+ if(world.time < cords.next_command)
+ if (feedback)
+ owner.balloon_alert(owner, "wait [DisplayTimeText(cords.next_command - world.time)]!")
+ return FALSE
if(isliving(owner))
var/mob/living/living = owner
if(!living.can_speak())
+ if (feedback)
+ owner.balloon_alert(owner, "can't speak!")
return FALSE
if(check_flags & AB_CHECK_CONSCIOUS)
if(owner.stat)
+ if (feedback)
+ owner.balloon_alert(owner, "unconscious!")
return FALSE
return TRUE
/datum/action/item_action/organ_action/colossus/Trigger(trigger_flags)
. = ..()
- if(!IsAvailable())
- if(world.time < cords.next_command)
- to_chat(owner, span_notice("You must wait [DisplayTimeText(cords.next_command - world.time)] before Speaking again."))
+ if(!.)
return
var/command = tgui_input_text(owner, "Speak with the Voice of God", "Command")
if(!command)