diff --git a/code/__DEFINES/layers.dm b/code/__DEFINES/layers.dm
index fd61f4f1123b..6f76dae9769f 100644
--- a/code/__DEFINES/layers.dm
+++ b/code/__DEFINES/layers.dm
@@ -130,6 +130,9 @@
#define CAMERA_STATIC_LAYER 19
#define CAMERA_STATIC_RENDER_TARGET "CAMERA_STATIC_PLANE"
+///Wants to be part of the game plane, but also wants to draw above literally everything else
+#define HIGH_GAME_PLANE 30
+
//HUD layer defines
#define FULLSCREEN_PLANE 31
diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm
index f307aa952804..81ba9ec06dd2 100644
--- a/code/__HELPERS/mobs.dm
+++ b/code/__HELPERS/mobs.dm
@@ -242,7 +242,7 @@ GLOBAL_LIST_EMPTY(species_list)
return "unknown"
///Timed action involving two mobs, the user and the target.
-/proc/do_mob(mob/user , mob/target, time = 3 SECONDS, uninterruptible = FALSE, progress = TRUE, datum/callback/extra_checks = null, ignore_loc_change = FALSE)
+/proc/do_mob(mob/user , mob/target, time = 3 SECONDS, uninterruptible = FALSE, progress = TRUE, datum/callback/extra_checks = null, ignore_loc_change = FALSE, hidden = FALSE)
if(!user || !target)
return FALSE
@@ -262,8 +262,11 @@ GLOBAL_LIST_EMPTY(species_list)
LAZYADD(target.targeted_by, user)
var/holding = user.get_active_held_item()
var/datum/progressbar/progbar
+ var/datum/cogbar/cog
if (progress)
progbar = new(user, time, target)
+ if(!hidden && time >= 1 SECONDS)
+ cog = new(user)
var/endtime = world.time+time
var/starttime = world.time
@@ -292,6 +295,8 @@ GLOBAL_LIST_EMPTY(species_list)
break
if(!QDELETED(progbar))
progbar.end_progress()
+
+ cog?.remove()
if(!QDELETED(target))
LAZYREMOVE(user.do_afters, target)
LAZYREMOVE(target.targeted_by, user)
@@ -311,7 +316,7 @@ GLOBAL_LIST_EMPTY(species_list)
return ..()
///Timed action involving one mob user. Target is optional.
-/proc/do_after(mob/user, delay, needhand = TRUE, atom/target = null, progress = TRUE, datum/callback/extra_checks = null)
+/proc/do_after(mob/user, delay, needhand = TRUE, atom/target = null, progress = TRUE, datum/callback/extra_checks = null, hidden = FALSE)
if(!user)
return FALSE
@@ -342,9 +347,11 @@ GLOBAL_LIST_EMPTY(species_list)
delay *= user.do_after_coefficent()
var/datum/progressbar/progbar
+ var/datum/cogbar/cog
if(progress)
progbar = new(user, delay, target || user)
-
+ if(!hidden && delay >= 1 SECONDS)
+ cog = new(user)
var/endtime = world.time + delay
var/starttime = world.time
. = TRUE
@@ -389,6 +396,8 @@ GLOBAL_LIST_EMPTY(species_list)
if(!QDELETED(progbar))
progbar.end_progress()
+ cog?.remove()
+
if(!QDELETED(target))
LAZYREMOVE(user.do_afters, target)
LAZYREMOVE(target.targeted_by, user)
diff --git a/code/datums/cogbar.dm b/code/datums/cogbar.dm
new file mode 100644
index 000000000000..c03daa33a6ab
--- /dev/null
+++ b/code/datums/cogbar.dm
@@ -0,0 +1,88 @@
+#define COGBAR_ANIMATION_TIME (0.5 SECONDS)
+
+/**
+ * ### Cogbar
+ * Represents that the user is busy doing something.
+ */
+/datum/cogbar
+ /// Who's doing the thing
+ var/mob/user
+ /// The user client
+ var/client/user_client
+ /// The visible element to other players
+ var/obj/effect/overlay/vis/cog
+ /// The blank image that overlaps the cog - hides it from the source user
+ var/image/blank
+ /// The offset of the icon
+ //var/offset_y
+
+
+/datum/cogbar/New(mob/user)
+ src.user = user
+ src.user_client = user.client
+
+//Porting oversized icon offsets later, they have too many other unported dependencies. sorry zephyr
+ //var/list/icon_offsets = user.get_oversized_icon_offsets()
+ //offset_y = icon_offsets["y"]
+
+ add_cog_to_user()
+
+ RegisterSignal(user, COMSIG_PARENT_QDELETING, PROC_REF(on_user_delete))
+
+
+/datum/cogbar/Destroy()
+ if(user)
+ SSvis_overlays.remove_vis_overlay(user, user.managed_vis_overlays)
+ user_client?.images -= blank
+
+ user = null
+ user_client = null
+ cog = null
+ QDEL_NULL(blank)
+
+ return ..()
+
+
+/// Adds the cog to the user, visible by other players
+/datum/cogbar/proc/add_cog_to_user()
+ cog = SSvis_overlays.add_vis_overlay(user,
+ icon = 'icons/effects/progressbar.dmi',
+ iconstate = "cog",
+ plane = HIGH_GAME_PLANE,
+ add_appearance_flags = APPEARANCE_UI_IGNORE_ALPHA,
+ unique = TRUE,
+ alpha = 0,
+ )
+ cog.pixel_y = world.icon_size// + offset_y
+ animate(cog, alpha = 255, time = COGBAR_ANIMATION_TIME)
+
+ if(isnull(user_client))
+ return
+
+ blank = image('icons/blanks/32x32.dmi', cog, "nothing")
+ blank.plane = HIGH_GAME_PLANE
+ blank.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
+ blank.override = TRUE
+
+ user_client.images += blank
+
+
+/// Removes the cog from the user
+/datum/cogbar/proc/remove()
+ if(isnull(cog))
+ qdel(src)
+ return
+
+ animate(cog, alpha = 0, time = COGBAR_ANIMATION_TIME)
+
+ QDEL_IN(src, COGBAR_ANIMATION_TIME)
+
+
+/// When the user is deleted, remove the cog
+/datum/cogbar/proc/on_user_delete(datum/source)
+ SIGNAL_HANDLER
+
+ qdel(src)
+
+
+#undef COGBAR_ANIMATION_TIME
diff --git a/code/datums/progressbar.dm b/code/datums/progressbar.dm
index 25621a613eeb..7134d2e8ecef 100644
--- a/code/datums/progressbar.dm
+++ b/code/datums/progressbar.dm
@@ -32,7 +32,7 @@
return
goal = goal_number
bar_loc = target
- bar = image('icons/effects/progessbar.dmi', bar_loc, "prog_bar_0", HUD_LAYER)
+ bar = image('icons/effects/progressbar.dmi', bar_loc, "prog_bar_0", HUD_LAYER)
bar.plane = ABOVE_HUD_PLANE
bar.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
user = User
diff --git a/code/game/objects/structures/beds_chairs/alien_nest.dm b/code/game/objects/structures/beds_chairs/alien_nest.dm
index 532b4385f2cf..4f132b11af99 100644
--- a/code/game/objects/structures/beds_chairs/alien_nest.dm
+++ b/code/game/objects/structures/beds_chairs/alien_nest.dm
@@ -34,7 +34,7 @@
M.visible_message("[M.name] struggles to break free from the gelatinous resin!",\
"You struggle to break free from the gelatinous resin... (Stay still for two minutes.)",\
"You hear squelching...")
- if(!do_after(M, 1200, target = src))
+ if(!do_after(M, 1200, target = src, hidden = TRUE))
if(M && M.buckled)
to_chat(M, "You fail to unbuckle yourself!")
return
diff --git a/code/game/objects/structures/kitchen_spike.dm b/code/game/objects/structures/kitchen_spike.dm
index 65479f171334..d863b693604f 100644
--- a/code/game/objects/structures/kitchen_spike.dm
+++ b/code/game/objects/structures/kitchen_spike.dm
@@ -112,7 +112,7 @@
"You struggle to break free from [src], exacerbating your wounds! (Stay still for two minutes.)",\
"You hear a wet squishing noise..")
M.adjustBruteLoss(30)
- if(!do_after(M, 1200, target = src))
+ if(!do_after(M, 1200, target = src, hidden = TRUE))
if(M && M.buckled)
to_chat(M, "You fail to free yourself!")
return
diff --git a/code/modules/antagonists/nukeop/equipment/borgchameleon.dm b/code/modules/antagonists/nukeop/equipment/borgchameleon.dm
index ddc895060b0c..17cd8fd99bff 100644
--- a/code/modules/antagonists/nukeop/equipment/borgchameleon.dm
+++ b/code/modules/antagonists/nukeop/equipment/borgchameleon.dm
@@ -65,7 +65,7 @@
to_chat(user, "You activate \the [src].")
playsound(src, 'sound/effects/seedling_chargeup.ogg', 100, TRUE, -6)
apply_wibbly_filters(user)
- if (do_after(user, 50, target=user) && user.cell.use(activationCost))
+ if (do_after(user, 50, target=user, hidden = TRUE) && user.cell.use(activationCost))
playsound(src, 'sound/effects/bamf.ogg', 100, TRUE, -6)
to_chat(user, "You are now disguised as the Nanotrasen engineering borg \"[friendlyName]\".")
activate(user)
diff --git a/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm b/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm
index d0019eb19cc2..f3d6bb31abf4 100644
--- a/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm
+++ b/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm
@@ -126,7 +126,7 @@
if(istype(I, /obj/item/nuke_core_container))
var/obj/item/nuke_core_container/core_box = I
to_chat(user, "You start loading the plutonium core into [core_box]...")
- if(do_after(user,50,target=src))
+ if(do_after(user,50,target=src, hidden = TRUE))
if(core_box.load(core, user))
to_chat(user, "You load the plutonium core into [core_box].")
deconstruction_state = NUKESTATE_CORE_REMOVED
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm
index 8f547335e9e0..3705cb361d82 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm
@@ -273,7 +273,7 @@
user.visible_message("You see [user] kicking against the glass of [src]!", \
"You struggle inside [src], kicking the release with your foot... (this will take about [DisplayTimeText(breakout_time)].)", \
"You hear a thump from [src].")
- if(do_after(user, breakout_time, target = src))
+ if(do_after(user, breakout_time, target = src, hidden = TRUE))
if(!user || user.stat != CONSCIOUS || user.loc != src)
return
user.visible_message("[user] successfully broke out of [src]!", \
diff --git a/code/modules/clothing/shoes/_shoes.dm b/code/modules/clothing/shoes/_shoes.dm
index 7b2ded27e269..1b5f0ae58fae 100644
--- a/code/modules/clothing/shoes/_shoes.dm
+++ b/code/modules/clothing/shoes/_shoes.dm
@@ -157,7 +157,7 @@
if(HAS_TRAIT(user, TRAIT_CLUMSY)) // based clowns trained their whole lives for this
mod_time *= 0.75
- if(do_after(user, mod_time, needhand=TRUE, target=our_guy, extra_checks=CALLBACK(src, PROC_REF(still_shoed), our_guy)))
+ if(do_after(user, mod_time, needhand=TRUE, target=our_guy, extra_checks=CALLBACK(src, PROC_REF(still_shoed), our_guy), hidden = TRUE))
to_chat(user, "You [tied ? "untie" : "knot"] the laces on [loc]'s [src.name].")
if(tied == SHOES_UNTIED)
adjust_laces(SHOES_KNOTTED, user)
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index 5b316dad9cb6..36fd8e1e6704 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -255,7 +255,7 @@
buckle_cd = O.breakouttime
visible_message("[src] attempts to unbuckle [p_them()]self!", \
"You attempt to unbuckle yourself... (This will take around [round(buckle_cd/600,1)] minute\s, and you need to stay still.)")
- if(do_after(src, buckle_cd, 0, target = src))
+ if(do_after(src, buckle_cd, 0, target = src, hidden = TRUE))
if(!buckled)
return
buckled.user_unbuckle_mob(src,src)
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index ecad7982b83a..292047676575 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -247,7 +247,7 @@
else
return
- if(do_mob(usr, src, POCKET_STRIP_DELAY/delay_denominator)) //placing an item into the pocket is 4 times faster
+ if(do_mob(usr, src, POCKET_STRIP_DELAY/delay_denominator, hidden = TRUE)) //placing an item into the pocket is 4 times faster
if(pocket_item)
if(pocket_item == (pocket_id == ITEM_SLOT_RPOCKET ? r_store : l_store)) //item still in the pocket we search
dropItemToGround(pocket_item)
diff --git a/code/modules/ninja/suit/ninjaDrainAct.dm b/code/modules/ninja/suit/ninjaDrainAct.dm
index 2e3dac4fddbe..4939686ee9fd 100644
--- a/code/modules/ninja/suit/ninjaDrainAct.dm
+++ b/code/modules/ninja/suit/ninjaDrainAct.dm
@@ -41,7 +41,7 @@ They *could* go in their appropriate files, but this is supposed to be modular
drain = S.cell.maxcharge - S.cell.charge
maxcapacity = 1//Reached maximum battery capacity.
- if (do_after(H,10, target = src))
+ if (do_after(H,10, target = src, hidden = TRUE))
spark_system.start()
playsound(loc, "sparks", 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
cell.use(drain)
@@ -85,7 +85,7 @@ They *could* go in their appropriate files, but this is supposed to be modular
drain = S.cell.maxcharge - S.cell.charge
maxcapacity = 1
- if (do_after(H,10, target = src))
+ if (do_after(H,10, target = src, hidden = TRUE))
spark_system.start()
playsound(loc, "sparks", 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
charge -= drain
@@ -104,7 +104,7 @@ They *could* go in their appropriate files, but this is supposed to be modular
. = 0
if(charge)
- if(G.candrain && do_after(H,30, target = src))
+ if(G.candrain && do_after(H,30, target = src, hidden = TRUE))
. = charge
if(S.cell.charge + charge > S.cell.maxcharge)
S.cell.charge = S.cell.maxcharge
@@ -131,7 +131,7 @@ They *could* go in their appropriate files, but this is supposed to be modular
if(stored_research)
to_chat(H, "Copying files...")
- if(do_after(H, S.s_delay, target = src) && G.candrain && src)
+ if(do_after(H, S.s_delay, target = src, hidden = TRUE) && G.candrain && src)
stored_research.copy_research_to(S.stored_research)
to_chat(H, "Data analyzed. Process finished.")
@@ -148,7 +148,7 @@ They *could* go in their appropriate files, but this is supposed to be modular
if(stored_research)
to_chat(H, "Copying files...")
- if(do_after(H, S.s_delay, target = src) && G.candrain && src)
+ if(do_after(H, S.s_delay, target = src, hidden = TRUE) && G.candrain && src)
stored_research.copy_research_to(S.stored_research)
to_chat(H, "Data analyzed. Process finished.")
@@ -167,7 +167,7 @@ They *could* go in their appropriate files, but this is supposed to be modular
while(G.candrain && !maxcapacity && src)
drain = (round((rand(G.mindrain, G.maxdrain))/2))
var/drained = 0
- if(PN && do_after(H,10, target = src))
+ if(PN && do_after(H,10, target = src, hidden = TRUE))
drained = min(drain, delayed_surplus())
add_delayedload(drained)
if(drained < drain)//if no power on net, drain apcs
@@ -207,7 +207,7 @@ They *could* go in their appropriate files, but this is supposed to be modular
if(S.cell.charge + drain > S.cell.maxcharge)
drain = S.cell.maxcharge - S.cell.charge
maxcapacity = 1
- if (do_after(H,10, target = src))
+ if (do_after(H,10, target = src, hidden = TRUE))
spark_system.start()
playsound(loc, "sparks", 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
cell.use(drain)
@@ -235,7 +235,7 @@ They *could* go in their appropriate files, but this is supposed to be modular
if(S.cell.charge+drain > S.cell.maxcharge)
drain = S.cell.maxcharge - S.cell.charge
maxcapacity = 1
- if (do_after(H,10))
+ if (do_after(H,10, hidden = TRUE))
spark_system.start()
playsound(loc, "sparks", 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
cell.use(drain)
diff --git a/icons/effects/progessbar.dmi b/icons/effects/progessbar.dmi
deleted file mode 100644
index f055a07ba149..000000000000
Binary files a/icons/effects/progessbar.dmi and /dev/null differ
diff --git a/icons/effects/progressbar.dmi b/icons/effects/progressbar.dmi
new file mode 100644
index 000000000000..3eed14db704a
Binary files /dev/null and b/icons/effects/progressbar.dmi differ
diff --git a/shiptest.dme b/shiptest.dme
index 63aeabd3620c..45af85492296 100644
--- a/shiptest.dme
+++ b/shiptest.dme
@@ -394,6 +394,7 @@
#include "code\datums\changelog.dm"
#include "code\datums\chatmessage.dm"
#include "code\datums\cinematic.dm"
+#include "code\datums\cogbar.dm"
#include "code\datums\dash_weapon.dm"
#include "code\datums\datacore.dm"
#include "code\datums\datum.dm"
diff --git a/tgui/packages/tgui/interfaces/Cloner.js b/tgui/packages/tgui/interfaces/Cloner.js
index 6ee432e694d0..0719666642cc 100644
--- a/tgui/packages/tgui/interfaces/Cloner.js
+++ b/tgui/packages/tgui/interfaces/Cloner.js
@@ -26,7 +26,7 @@ export const Cloner = (props, context) => {
beakerContents={data.beakerContents}
/>
-