diff --git a/beestation.dme b/beestation.dme
index e2ee4144549e0..d4a20c8e28840 100644
--- a/beestation.dme
+++ b/beestation.dme
@@ -71,6 +71,7 @@
#include "code\__DEFINES\directional.dm"
#include "code\__DEFINES\diseases.dm"
#include "code\__DEFINES\DNA.dm"
+#include "code\__DEFINES\do_afters.dm"
#include "code\__DEFINES\dye_keys.dm"
#include "code\__DEFINES\dynamic.dm"
#include "code\__DEFINES\economy.dm"
@@ -174,7 +175,6 @@
#include "code\__DEFINES\tgs.dm"
#include "code\__DEFINES\tgui.dm"
#include "code\__DEFINES\time.dm"
-#include "code\__DEFINES\timed_action.dm"
#include "code\__DEFINES\tools.dm"
#include "code\__DEFINES\toys.dm"
#include "code\__DEFINES\traitor.dm"
@@ -319,6 +319,7 @@
#include "code\_globalvars\lists\ambience.dm"
#include "code\_globalvars\lists\client.dm"
#include "code\_globalvars\lists\flavor_misc.dm"
+#include "code\_globalvars\lists\icons.dm"
#include "code\_globalvars\lists\jobs.dm"
#include "code\_globalvars\lists\maintenance_loot.dm"
#include "code\_globalvars\lists\mapping.dm"
@@ -514,6 +515,7 @@
#include "code\datums\callback.dm"
#include "code\datums\chat_payload.dm"
#include "code\datums\chatmessage.dm"
+#include "code\datums\cogbar.dm"
#include "code\datums\cinematic.dm"
#include "code\datums\dash_weapon.dm"
#include "code\datums\datacore.dm"
diff --git a/code/__DEFINES/do_afters.dm b/code/__DEFINES/do_afters.dm
new file mode 100644
index 0000000000000..456cf94040048
--- /dev/null
+++ b/code/__DEFINES/do_afters.dm
@@ -0,0 +1,4 @@
+#define DOAFTER_SOURCE_SURGERY "doafter_surgery"
+#define DOAFTER_SOURCE_MECHADRILL "doafter_mechadrill"
+#define DOAFTER_SOURCE_SURVIVALPEN "doafter_survivalpen"
+#define DOAFTER_SOURCE_GETTING_UP "doafter_gettingup"
diff --git a/code/__DEFINES/flags.dm b/code/__DEFINES/flags.dm
index f9aab906c789c..382efe82e4f2c 100644
--- a/code/__DEFINES/flags.dm
+++ b/code/__DEFINES/flags.dm
@@ -202,6 +202,11 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204
else if(!HAS_TRAIT(x, TRAIT_KEEP_TOGETHER))\
x.appearance_flags &= ~KEEP_TOGETHER
+//religious_tool flags
+#define RELIGION_TOOL_INVOKE (1<<0)
+#define RELIGION_TOOL_SACRIFICE (1<<1)
+#define RELIGION_TOOL_SECTSELECT (1<<2)
+
//dir macros
///Returns true if the dir is diagonal, false otherwise
#define ISDIAGONALDIR(d) (d&(d-1))
@@ -216,7 +221,12 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204
///Turns the dir by 180 degrees
#define DIRFLIP(d) turn(d, 180)
-//religious_tool flags
-#define RELIGION_TOOL_INVOKE (1<<0)
-#define RELIGION_TOOL_SACRIFICE (1<<1)
-#define RELIGION_TOOL_SECTSELECT (1<<2)
+// timed_action_flags parameter for `/proc/do_after`
+/// Can do the action even if mob moves location
+#define IGNORE_USER_LOC_CHANGE (1<<0)
+/// Can do the action even if the target moves location
+#define IGNORE_TARGET_LOC_CHANGE (1<<1)
+/// Can do the action even if the item is no longer being held
+#define IGNORE_HELD_ITEM (1<<2)
+/// Can do the action even if the mob is incapacitated (ex. handcuffed)
+#define IGNORE_INCAPACITATED (1<<3)
diff --git a/code/__DEFINES/layers.dm b/code/__DEFINES/layers.dm
index 74a138a72b8cd..775be9c434153 100644
--- a/code/__DEFINES/layers.dm
+++ b/code/__DEFINES/layers.dm
@@ -139,13 +139,17 @@
///AI Camera Static
#define CAMERA_STATIC_PLANE 200
+///Anything that wants to be part of the game plane, but also wants to draw above literally everything else
+#define HIGH_GAME_PLANE 499
+
+#define FULLSCREEN_PLANE 500
+
///Popup Chat Messages
#define RUNECHAT_PLANE 650
/// Plane for balloon text (text that fades up)
#define BALLOON_CHAT_PLANE 651
///--------------- FULLSCREEN IMAGES ------------
-#define FULLSCREEN_PLANE 500
#define FLASH_LAYER 1
#define FULLSCREEN_LAYER 2
#define UI_DAMAGE_LAYER 3
diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm
index 651d5cbf9e3b7..456fb03f21738 100644
--- a/code/__DEFINES/mobs.dm
+++ b/code/__DEFINES/mobs.dm
@@ -401,7 +401,11 @@ GLOBAL_LIST_INIT(available_random_trauma_list, list(
#define HUMAN_CARRY_SLOWDOWN 0.35
#define SLEEP_CHECK_DEATH(X) sleep(X); if(QDELETED(src) || stat == DEAD) return;
-#define INTERACTING_WITH(X, Y) (Y in X.do_afters)
+
+#define DOING_INTERACTION(user, interaction_key) (LAZYACCESS(user.do_afters, interaction_key))
+#define DOING_INTERACTION_LIMIT(user, interaction_key, max_interaction_count) ((LAZYACCESS(user.do_afters, interaction_key) || 0) >= max_interaction_count)
+#define DOING_INTERACTION_WITH_TARGET(user, target) (LAZYACCESS(user.do_afters, target))
+#define DOING_INTERACTION_WITH_TARGET_LIMIT(user, target, max_interaction_count) ((LAZYACCESS(user.do_afters, target) || 0) >= max_interaction_count)
#define SILENCE_RANGED_MESSAGE (1<<0)
diff --git a/code/__DEFINES/time.dm b/code/__DEFINES/time.dm
index e0beecbd2b4b6..44710fbcf9bc1 100644
--- a/code/__DEFINES/time.dm
+++ b/code/__DEFINES/time.dm
@@ -33,13 +33,19 @@ When using time2text(), please use "DDD" to find the weekday. Refrain from using
*/
-#define MONDAY "Mon"
-#define TUESDAY "Tue"
-#define WEDNESDAY "Wed"
-#define THURSDAY "Thu"
-#define FRIDAY "Fri"
-#define SATURDAY "Sat"
-#define SUNDAY "Sun"
+#define MONDAY "Mon"
+#define TUESDAY "Tue"
+#define WEDNESDAY "Wed"
+#define THURSDAY "Thu"
+#define FRIDAY "Fri"
+#define SATURDAY "Sat"
+#define SUNDAY "Sun"
+
+#define INFINITE -1 // -1 is commonly used to indicate an infinite time duration
+
+#define MILLISECONDS *0.01
+
+#define DECISECONDS *1 //the base unit all of these defines are scaled by, because byond uses that as a unit of measurement for some fucking reason
#define SECONDS *10
@@ -49,8 +55,6 @@ When using time2text(), please use "DDD" to find the weekday. Refrain from using
#define TICKS *world.tick_lag
-#define MILLISECONDS * 0.01
-
#define DS2TICKS(DS) ((DS)/world.tick_lag)
#define TICKS2DS(T) ((T) TICKS)
diff --git a/code/__DEFINES/timed_action.dm b/code/__DEFINES/timed_action.dm
deleted file mode 100644
index 5ae956caadae7..0000000000000
--- a/code/__DEFINES/timed_action.dm
+++ /dev/null
@@ -1,12 +0,0 @@
-// timed_action_flags parameter for 'proc/do_after'
-#define IGNORE_TARGET_IN_DOAFTERS (1<<0)
-#define IGNORE_USER_LOC_CHANGE (1<<1)
-#define IGNORE_TARGET_LOC_CHANGE (1<<2)
-#define IGNORE_HELD_ITEM (1<<3)
-#define IGNORE_INCAPACITATED (1<<4)
-#define REQUIRE_ADJACENCY (1<<5)
-#define IGNORE_RESTRAINED (1<<6)
-
-// Combined parameters for ease of use
-#define UNINTERRUPTIBLE IGNORE_TARGET_IN_DOAFTERS|IGNORE_USER_LOC_CHANGE|IGNORE_TARGET_LOC_CHANGE|IGNORE_HELD_ITEM|IGNORE_INCAPACITATED
-#define UNINTERRUPTIBLE_CONSCIOUS IGNORE_TARGET_IN_DOAFTERS|IGNORE_USER_LOC_CHANGE|IGNORE_TARGET_LOC_CHANGE|IGNORE_HELD_ITEM
diff --git a/code/__HELPERS/atoms.dm b/code/__HELPERS/atoms.dm
index c2b3b89de2d64..890d74ff68dfb 100644
--- a/code/__HELPERS/atoms.dm
+++ b/code/__HELPERS/atoms.dm
@@ -350,3 +350,15 @@ B --><-- A
return get_step(ref, base_dir)
*/
+
+/// Returns an x and y value require to reverse the transformations made to center an oversized icon
+/atom/proc/get_oversized_icon_offsets()
+ if (pixel_x == 0 && pixel_y == 0)
+ return list("x" = 0, "y" = 0)
+ var/list/icon_dimensions = get_icon_dimensions(icon)
+ var/icon_width = icon_dimensions["width"]
+ var/icon_height = icon_dimensions["height"]
+ return list(
+ "x" = icon_width > world.icon_size && pixel_x != 0 ? (icon_width - world.icon_size) * 0.5 : 0,
+ "y" = icon_height > world.icon_size && pixel_y != 0 ? (icon_height - world.icon_size) * 0.5 : 0,
+ )
diff --git a/code/__HELPERS/icons.dm b/code/__HELPERS/icons.dm
index d6a4b15c867f7..618a038cef531 100644
--- a/code/__HELPERS/icons.dm
+++ b/code/__HELPERS/icons.dm
@@ -1459,3 +1459,10 @@ GLOBAL_LIST_EMPTY(friendly_animal_types)
animate(src, pixel_x = pixel_x + shiftx, pixel_y = pixel_y + shifty, time = 0.2, loop = duration)
pixel_x = initialpixelx
pixel_y = initialpixely
+
+/// Returns a list containing the width and height of an icon file
+/proc/get_icon_dimensions(icon_path)
+ if (isnull(GLOB.icon_dimensions[icon_path]))
+ var/icon/my_icon = icon(icon_path)
+ GLOB.icon_dimensions[icon_path] = list("width" = my_icon.Width(), "height" = my_icon.Height())
+ return GLOB.icon_dimensions[icon_path]
diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm
index 17fd243d2adf0..fdc3db8ae5502 100644
--- a/code/__HELPERS/mobs.dm
+++ b/code/__HELPERS/mobs.dm
@@ -259,13 +259,19 @@ GLOBAL_LIST_EMPTY(species_list)
* * progress - if TRUE, a progress bar is displayed.
* * extra_checks - a callback that can be used to add extra checks to the do_after. Returning false in this callback will cancel the do_after.
*/
-/proc/do_after(mob/user, delay = 3 SECONDS, atom/target, timed_action_flags = NONE, progress = TRUE, datum/callback/extra_checks)
+/proc/do_after(mob/user, delay, atom/target, timed_action_flags = NONE, progress = TRUE, datum/callback/extra_checks, interaction_key, max_interact_count = 1, hidden = FALSE)
if(!user)
return FALSE
+ if(!isnum(delay))
+ CRASH("do_after was passed a non-number delay: [delay || "null"].")
- if(target)
- LAZYADD(user.do_afters, target)
- LAZYADD(target.targeted_by, user)
+ if(!interaction_key && target)
+ interaction_key = target //Use the direct ref to the target
+ if(interaction_key) //Do we have a interaction_key now?
+ var/current_interaction_count = LAZYACCESS(user.do_afters, interaction_key) || 0
+ if(current_interaction_count >= max_interact_count) //We are at our peak
+ return
+ LAZYSET(user.do_afters, interaction_key, current_interaction_count + 1)
var/atom/user_loc = user.loc
var/atom/target_loc = target?.loc
@@ -279,11 +285,14 @@ GLOBAL_LIST_EMPTY(species_list)
delay *= user.cached_multiplicative_actions_slowdown
var/datum/progressbar/progbar
+ var/datum/cogbar/cog
+
if(progress)
- if(target) // the progress bar needs a target, so if we don't have one just pass it the user.
- progbar = new(user, delay, target)
- else
- progbar = new(user, delay, user)
+ if(user.client)
+ progbar = new(user, delay, target || user)
+
+ if(!hidden && delay >= 1 SECONDS)
+ cog = new(user)
var/endtime = world.time + delay
var/starttime = world.time
@@ -291,48 +300,34 @@ GLOBAL_LIST_EMPTY(species_list)
while(world.time < endtime)
stoplag(1)
- if(QDELETED(user))
- . = FALSE
- break
-
- if(progress)
+ if(!QDELETED(progbar))
progbar.update(world.time - starttime)
if(drifting && SSmove_manager.processing_on(user, SSspacedrift))
drifting = FALSE
user_loc = user.loc
- // Check flags
- if(!(timed_action_flags & IGNORE_USER_LOC_CHANGE) && !drifting && user.loc != user_loc)
- . = FALSE
-
- if(!(timed_action_flags & IGNORE_HELD_ITEM) && user.get_active_held_item() != holding)
+ if(QDELETED(user) \
+ || (!(timed_action_flags & IGNORE_USER_LOC_CHANGE) && !drifting && user.loc != user_loc) \
+ || (!(timed_action_flags & IGNORE_HELD_ITEM) && user.get_active_held_item() != holding) \
+ || (!(timed_action_flags & IGNORE_INCAPACITATED) && HAS_TRAIT(user, TRAIT_INCAPACITATED)) \
+ || (extra_checks && !extra_checks.Invoke()))
. = FALSE
+ break
- if(!(timed_action_flags & IGNORE_INCAPACITATED) && user.incapacitated(ignore_restraints = (timed_action_flags & IGNORE_RESTRAINED)))
- . = FALSE
-
-
- if(extra_checks && !extra_checks.Invoke())
- . = FALSE
-
- // If we have a target, we check for them moving here. We don't care about it if we're drifting or we ignore target loc change
- if(!(timed_action_flags & IGNORE_TARGET_LOC_CHANGE) && !drifting)
- if(target_loc && user != target && (QDELETED(target) || target_loc != target.loc))
- . = FALSE
-
- if(target && !(timed_action_flags & IGNORE_TARGET_IN_DOAFTERS) && !(target in user.do_afters))
+ if(target && (user != target) && \
+ (QDELETED(target) \
+ || (!(timed_action_flags & IGNORE_TARGET_LOC_CHANGE) && target.loc != target_loc)))
. = FALSE
-
- if(!.)
break
if(!QDELETED(progbar))
progbar.end_progress()
- if(!QDELETED(target))
- LAZYREMOVE(user.do_afters, target)
- LAZYREMOVE(target.targeted_by, user)
+ cog?.remove()
+
+ if(interaction_key)
+ LAZYREMOVE(user.do_afters, interaction_key)
/proc/is_species(A, species_datum)
. = FALSE
diff --git a/code/__HELPERS/turfs.dm b/code/__HELPERS/turfs.dm
index 409f2b65925af..5a932b02c8ada 100644
--- a/code/__HELPERS/turfs.dm
+++ b/code/__HELPERS/turfs.dm
@@ -193,9 +193,9 @@ Turf and target are separate in case you want to teleport some distance from a t
var/pixel_y_offset = checked_atom.pixel_y + atom_matrix.get_y_shift()
//Irregular objects
- var/icon/checked_atom_icon = icon(checked_atom.icon, checked_atom.icon_state)
- var/checked_atom_icon_height = checked_atom_icon.Height()
- var/checked_atom_icon_width = checked_atom_icon.Width()
+ var/list/icon_dimensions = get_icon_dimensions(checked_atom.icon)
+ var/checked_atom_icon_height = icon_dimensions["width"]
+ var/checked_atom_icon_width = icon_dimensions["height"]
if(checked_atom_icon_height != world.icon_size || checked_atom_icon_width != world.icon_size)
pixel_x_offset += ((checked_atom_icon_width / world.icon_size) - 1) * (world.icon_size * 0.5)
pixel_y_offset += ((checked_atom_icon_height / world.icon_size) - 1) * (world.icon_size * 0.5)
diff --git a/code/_globalvars/lists/icons.dm b/code/_globalvars/lists/icons.dm
new file mode 100644
index 0000000000000..ff60e6bc8d928
--- /dev/null
+++ b/code/_globalvars/lists/icons.dm
@@ -0,0 +1,2 @@
+/// Cache of the width and height of icon files, to avoid repeating the same expensive operation
+GLOBAL_LIST_EMPTY(icon_dimensions)
diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm
index 9d37fe82d2d63..00dbc1033c5a9 100644
--- a/code/_onclick/hud/screen_objects.dm
+++ b/code/_onclick/hud/screen_objects.dm
@@ -676,7 +676,7 @@
screen_loc = ui_mood
/atom/movable/screen/splash
- icon = 'icons/blank_title.png'
+ icon = 'icons/blanks/blank_title.png'
icon_state = ""
screen_loc = "1,1"
plane = SPLASHSCREEN_PLANE
diff --git a/code/datums/cogbar.dm b/code/datums/cogbar.dm
new file mode 100644
index 0000000000000..f5c8be304262e
--- /dev/null
+++ b/code/datums/cogbar.dm
@@ -0,0 +1,87 @@
+#define COGBAR_ANIMATION_TIME 5 DECISECONDS
+
+/**
+ * ### 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
+
+ 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/components/butchering.dm b/code/datums/components/butchering.dm
index d281865081aa4..91498313a68b3 100644
--- a/code/datums/components/butchering.dm
+++ b/code/datums/components/butchering.dm
@@ -52,6 +52,9 @@
Butcher(user, M)
/datum/component/butchering/proc/startNeckSlice(obj/item/source, mob/living/carbon/human/H, mob/living/user)
+ if(DOING_INTERACTION_WITH_TARGET(user, H))
+ to_chat(user, "You're already interacting with [H]!")
+ return
user.visible_message("[user] is slitting [H]'s throat!", \
"You start slicing [H]'s throat!", \
"You hear a cutting noise!")
diff --git a/code/datums/elements/art.dm b/code/datums/elements/art.dm
index 3f9a5e9e5a610..89fa8ab822634 100644
--- a/code/datums/elements/art.dm
+++ b/code/datums/elements/art.dm
@@ -39,7 +39,7 @@
SIGNAL_HANDLER
if(!isliving(user))
return
- if(!INTERACTING_WITH(user, source))
+ if(!DOING_INTERACTION_WITH_TARGET(user, source))
INVOKE_ASYNC(src, PROC_REF(appraise), source, user) //Do not sleep the proc.
/datum/element/art/proc/appraise(atom/source, mob/user)
diff --git a/code/datums/greyscale/_greyscale_config.dm b/code/datums/greyscale/_greyscale_config.dm
index 807187a048209..57247b1df2bb7 100644
--- a/code/datums/greyscale/_greyscale_config.dm
+++ b/code/datums/greyscale/_greyscale_config.dm
@@ -189,9 +189,9 @@
/// Reads layer configurations to take out some useful overall information
/datum/greyscale_config/proc/ReadMetadata()
- var/icon/source = icon(icon_file)
- height = source.Height()
- width = source.Width()
+ var/list/icon_dimensions = get_icon_dimensions(icon_file)
+ height = icon_dimensions["width"]
+ width = icon_dimensions["height"]
var/list/datum/greyscale_layer/all_layers = list()
for(var/state in icon_states)
diff --git a/code/datums/progressbar.dm b/code/datums/progressbar.dm
index e9d1f11857ccd..87e8bbf06b4c6 100644
--- a/code/datums/progressbar.dm
+++ b/code/datums/progressbar.dm
@@ -16,12 +16,17 @@
var/last_progress = 0
///Variable to ensure smooth visual stacking on multiple progress bars.
var/listindex = 0
-
+ ///The type of our last value for bar_loc, for debugging
+ var/location_type
+ ///Where to draw the progress bar above the icon
+ var/offset_y
/datum/progressbar/New(mob/User, goal_number, atom/target)
. = ..()
if (!istype(target))
- EXCEPTION("Invalid target given")
+ stack_trace("Invalid target [target] passed in")
+ qdel(src)
+ return
if(QDELETED(User) || !istype(User))
stack_trace("/datum/progressbar created with [isnull(User) ? "null" : "invalid"] user")
qdel(src)
@@ -32,7 +37,13 @@
return
goal = goal_number
bar_loc = target
- bar = image('icons/effects/progessbar.dmi', bar_loc, "prog_bar_0")
+ location_type = bar_loc.type
+
+ var/list/icon_offsets = target.get_oversized_icon_offsets()
+ var/offset_x = icon_offsets["x"]
+ offset_y = icon_offsets["y"]
+
+ bar = image('icons/effects/progressbar.dmi', bar_loc, "prog_bar_0", pixel_x = offset_x)
bar.plane = ABOVE_HUD_PLANE
bar.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
user = User
@@ -58,8 +69,8 @@
continue
progress_bar.listindex--
- progress_bar.bar.pixel_y = 32 + (PROGRESSBAR_HEIGHT * (progress_bar.listindex - 1))
- var/dist_to_travel = 32 + (PROGRESSBAR_HEIGHT * (progress_bar.listindex - 1)) - PROGRESSBAR_HEIGHT
+ progress_bar.bar.pixel_y = world.icon_size + offset_y + (PROGRESSBAR_HEIGHT * (progress_bar.listindex - 1))
+ var/dist_to_travel = world.icon_size + offset_y + (PROGRESSBAR_HEIGHT * (progress_bar.listindex - 1)) - PROGRESSBAR_HEIGHT
animate(progress_bar.bar, pixel_y = dist_to_travel, time = PROGRESSBAR_ANIMATION_TIME, easing = SINE_EASING)
LAZYREMOVEASSOC(user.progressbars, bar_loc, src)
@@ -114,7 +125,7 @@
bar.pixel_y = 0
bar.alpha = 0
user_client.images += bar
- animate(bar, pixel_y = 32 + (PROGRESSBAR_HEIGHT * (listindex - 1)), alpha = 255, time = PROGRESSBAR_ANIMATION_TIME, easing = SINE_EASING)
+ animate(bar, pixel_y = world.icon_size + offset_y + (PROGRESSBAR_HEIGHT * (listindex - 1)), alpha = 255, time = PROGRESSBAR_ANIMATION_TIME, easing = SINE_EASING)
///Updates the progress bar image visually.
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 7cac70720da31..68aa937e65197 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -121,9 +121,6 @@
///A string of hex format colors to be used by greyscale sprites, ex: "#0054aa#badcff"
var/greyscale_colors
- ///Mobs that are currently do_after'ing this atom, to be cleared from on Destroy()
- var/list/targeted_by
-
///AI controller that controls this atom. type on init, then turned into an instance during runtime
var/datum/ai_controller/ai_controller
@@ -308,12 +305,6 @@
QDEL_NULL(light)
QDEL_NULL(ai_controller)
- for(var/i in targeted_by)
- var/mob/M = i
- LAZYREMOVE(M.do_afters, src)
-
- targeted_by = null
-
return ..()
/atom/proc/handle_ricochet(obj/projectile/P)
diff --git a/code/game/machinery/fat_sucker.dm b/code/game/machinery/fat_sucker.dm
index a0891e05a326b..d3324cb3a4898 100644
--- a/code/game/machinery/fat_sucker.dm
+++ b/code/game/machinery/fat_sucker.dm
@@ -86,7 +86,7 @@
user.visible_message("You see [user] kicking against the door of [src]!", \
"You lean on the back of [src] and start pushing the door open... (this will take about [DisplayTimeText(breakout_time)].)", \
"You hear a metallic creaking 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 || state_open)
return
free_exit = TRUE
diff --git a/code/game/objects/items/RPD.dm b/code/game/objects/items/RPD.dm
index b701a0509ba5d..b4789e00df317 100644
--- a/code/game/objects/items/RPD.dm
+++ b/code/game/objects/items/RPD.dm
@@ -461,7 +461,7 @@ GLOBAL_LIST_INIT(fluid_duct_recipes, list(
var/queued_p_flipped = p_flipped
//Unwrench pipe before we build one over/paint it, but only if we're not already running a do_after on it already to prevent a potential runtime.
- if((mode & DESTROY_MODE) && (upgrade_flags & RPD_UPGRADE_UNWRENCH) && istype(attack_target, /obj/machinery/atmospherics) && !(attack_target in user.do_afters))
+ if((mode & DESTROY_MODE) && (upgrade_flags & RPD_UPGRADE_UNWRENCH) && istype(attack_target, /obj/machinery/atmospherics) && !(DOING_INTERACTION_WITH_TARGET(user, attack_target)))
attack_target.wrench_act(user, src)
return
diff --git a/code/game/objects/items/cardboard_cutouts.dm b/code/game/objects/items/cardboard_cutouts.dm
index 08c093bd55c4b..671b9e66510c9 100644
--- a/code/game/objects/items/cardboard_cutouts.dm
+++ b/code/game/objects/items/cardboard_cutouts.dm
@@ -88,7 +88,7 @@
var/new_appearance = input(user, "Choose a new appearance for [src].", "26th Century Deception") as null|anything in sort_list(possible_appearances)
if(!new_appearance || !crayon || !user.canUseTopic(src, BE_CLOSE))
return
- if(!do_after(user, 10, src, progress = TRUE))
+ if(!do_after(user, 1 SECONDS, src, timed_action_flags = IGNORE_HELD_ITEM))
return
user.visible_message("[user] gives [src] a new look.", "Voila! You give [src] a new look.")
crayon.use_charges(1)
diff --git a/code/game/objects/items/miscellaneous.dm b/code/game/objects/items/miscellaneous.dm
index 8b6483a41d13a..941d4d0b973c8 100644
--- a/code/game/objects/items/miscellaneous.dm
+++ b/code/game/objects/items/miscellaneous.dm
@@ -358,7 +358,7 @@
user.last_special = world.time + CLICK_CD_BREAKOUT
to_chat(user, "You claw at the fabric of [src], trying to tear it open...")
to_chat(loc, "Someone starts trying to break free of [src]!")
- if(!do_after(user, 100, target = src))
+ if(!do_after(user, 10 SECONDS, src, timed_action_flags = (IGNORE_TARGET_LOC_CHANGE|IGNORE_HELD_ITEM)))
to_chat(loc, "The pressure subsides. It seems that they've stopped resisting...")
return
loc.visible_message("[user] suddenly appears in front of [loc]!", "[user] breaks free of [src]!")
diff --git a/code/game/objects/items/stacks/sheets/organic/cloths.dm b/code/game/objects/items/stacks/sheets/organic/cloths.dm
index 86b38a29b3a9a..429261d76c9a1 100644
--- a/code/game/objects/items/stacks/sheets/organic/cloths.dm
+++ b/code/game/objects/items/stacks/sheets/organic/cloths.dm
@@ -20,7 +20,7 @@ Various Cloths
merge_type = /obj/item/stack/sheet/cotton
drop_sound = 'sound/items/handling/cloth_drop.ogg'
pickup_sound = 'sound/items/handling/cloth_pickup.ogg'
- var/pull_effort = 30
+ var/pull_effort = 10
var/loom_result = /obj/item/stack/sheet/cotton/cloth
/obj/item/stack/sheet/cotton/cloth
@@ -48,7 +48,6 @@ Various Cloths
singular_name = "raw durathread ball"
icon_state = "sheet-durathreadraw"
merge_type = /obj/item/stack/sheet/cotton/durathread
- pull_effort = 70
loom_result = /obj/item/stack/sheet/cotton/cloth/durathread
/obj/item/stack/sheet/cotton/cloth/durathread
diff --git a/code/game/objects/structures/beds_chairs/alien_nest.dm b/code/game/objects/structures/beds_chairs/alien_nest.dm
index 3d45d9b684f02..ffd9c15fcfdac 100644
--- a/code/game/objects/structures/beds_chairs/alien_nest.dm
+++ b/code/game/objects/structures/beds_chairs/alien_nest.dm
@@ -36,7 +36,7 @@
"[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, timed_action_flags = IGNORE_RESTRAINED))
+ if(!do_after(M, 1200, target = src))
if(M?.buckled)
to_chat(M, "You fail to unbuckle yourself!")
return
diff --git a/code/game/objects/structures/gym/weight_machine_action.dm b/code/game/objects/structures/gym/weight_machine_action.dm
index ea272e0a7bfa5..80491aa50ba88 100644
--- a/code/game/objects/structures/gym/weight_machine_action.dm
+++ b/code/game/objects/structures/gym/weight_machine_action.dm
@@ -11,7 +11,7 @@
var/obj/structure/weightmachine/weightpress
/datum/action/push_weights/IsAvailable(feedback = FALSE)
- if(INTERACTING_WITH(owner, weightpress))
+ if(DOING_INTERACTION_WITH_TARGET(owner, weightpress))
return FALSE
return TRUE
diff --git a/code/game/objects/structures/kitchen_spike.dm b/code/game/objects/structures/kitchen_spike.dm
index 7baea226b00e2..b09db580d42d3 100644
--- a/code/game/objects/structures/kitchen_spike.dm
+++ b/code/game/objects/structures/kitchen_spike.dm
@@ -116,7 +116,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, timed_action_flags = IGNORE_RESTRAINED))
+ if(!do_after(M, 1200, target = src))
if(M && M.buckled)
to_chat(M, "You fail to free yourself!")
return
diff --git a/code/game/objects/structures/loom.dm b/code/game/objects/structures/loom.dm
index 28ff5a8de732f..e2c3b89099137 100644
--- a/code/game/objects/structures/loom.dm
+++ b/code/game/objects/structures/loom.dm
@@ -31,11 +31,9 @@
user.show_message("You need at least [FABRIC_PER_SHEET] units of fabric before using this.", MSG_VISUAL)
return FALSE
user.show_message("You start weaving \the [W.name] through the loom..", MSG_VISUAL)
- if(W.use_tool(src, user, W.pull_effort))
- if(W.amount >= FABRIC_PER_SHEET)
- new W.loom_result(drop_location())
- W.use(FABRIC_PER_SHEET)
- user.show_message("You weave \the [W.name] into a workable fabric.", MSG_VISUAL)
+ while(W.use_tool(src, user, W.pull_effort) && W.use(FABRIC_PER_SHEET))
+ new W.loom_result(drop_location())
+ user.show_message("You weave \the [W.name] into a workable fabric.", MSG_VISUAL)
return TRUE
#undef FABRIC_PER_SHEET
diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm
index 31b25602d0288..fa87801fe4dca 100644
--- a/code/game/objects/structures/watercloset.dm
+++ b/code/game/objects/structures/watercloset.dm
@@ -38,7 +38,7 @@
if(open)
GM.visible_message("[user] starts to give [GM] a swirlie!", "[user] starts to give you a swirlie...")
swirlie = GM
- if(do_after(user, 30, target = src, timed_action_flags = IGNORE_HELD_ITEM))
+ if(do_after(user, 3 SECONDS, target = src, timed_action_flags = IGNORE_HELD_ITEM))
GM.visible_message("[user] gives [GM] a swirlie!", "[user] gives you a swirlie!", "You hear a toilet flushing.")
if(iscarbon(GM))
var/mob/living/carbon/C = GM
diff --git a/code/game/turfs/closed/_closed.dm b/code/game/turfs/closed/_closed.dm
index 739d65c64f941..b207df9636cd6 100644
--- a/code/game/turfs/closed/_closed.dm
+++ b/code/game/turfs/closed/_closed.dm
@@ -61,7 +61,7 @@
/turf/closed/indestructible/splashscreen
name = "Space Station 13"
- icon = 'icons/blank_title.png'
+ icon = 'icons/blanks/blank_title.png'
icon_state = ""
layer = FLY_LAYER
bullet_bounce_sound = null
diff --git a/code/modules/antagonists/changeling/powers/absorb.dm b/code/modules/antagonists/changeling/powers/absorb.dm
index 7313a5989a885..8004936244458 100644
--- a/code/modules/antagonists/changeling/powers/absorb.dm
+++ b/code/modules/antagonists/changeling/powers/absorb.dm
@@ -45,7 +45,7 @@
target.take_overall_damage(40)
SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("Absorb DNA", "[i]"))
- if(!do_after(user, 15 SECONDS, target))
+ if(!do_after(user, 15 SECONDS, target, hidden = TRUE))
to_chat(user, "Our absorption of [target] has been interrupted!")
changeling.isabsorbing = 0
return
diff --git a/code/modules/antagonists/heretic/heretic_antag.dm b/code/modules/antagonists/heretic/heretic_antag.dm
index 65e1aa44d4c56..929e2c61d0354 100644
--- a/code/modules/antagonists/heretic/heretic_antag.dm
+++ b/code/modules/antagonists/heretic/heretic_antag.dm
@@ -300,7 +300,7 @@
drawing_rune = TRUE
target_turf.balloon_alert(user, "You start drawing a rune")
- if(!do_after(user, drawing_time, target = target_turf, extra_checks = additional_checks))
+ if(!do_after(user, drawing_time, target_turf, extra_checks = additional_checks, hidden = TRUE))
target_turf.balloon_alert(user, "Interrupted")
drawing_rune = FALSE
return
diff --git a/code/modules/antagonists/heretic/influences.dm b/code/modules/antagonists/heretic/influences.dm
index 55b385b30e4e8..a9bd29314e3f3 100644
--- a/code/modules/antagonists/heretic/influences.dm
+++ b/code/modules/antagonists/heretic/influences.dm
@@ -266,7 +266,7 @@
balloon_alert(user, "You begin draining the influence")
RegisterSignal(user, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine))
- if(!do_after(user, 10 SECONDS, target = src))
+ if(!do_after(user, 10 SECONDS, src, hidden = TRUE))
being_drained = FALSE
balloon_alert(user, "Interrupted")
UnregisterSignal(user, COMSIG_PARENT_EXAMINE)
diff --git a/code/modules/antagonists/heretic/magic/manse_link.dm b/code/modules/antagonists/heretic/magic/manse_link.dm
index fcdd62467d944..a06921680d40f 100644
--- a/code/modules/antagonists/heretic/magic/manse_link.dm
+++ b/code/modules/antagonists/heretic/magic/manse_link.dm
@@ -23,7 +23,7 @@
to_chat(originator, "You begin linking [target]'s mind to yours...")
to_chat(target, "You feel your mind being pulled... connected... intertwined with the very fabric of reality...")
- if(!do_after(originator, 6 SECONDS, target = target))
+ if(!do_after(originator, 6 SECONDS, target = target, hidden = TRUE))
revert_cast()
return
if(!originator.link_mob(target))
diff --git a/code/modules/antagonists/nukeop/equipment/borgchameleon.dm b/code/modules/antagonists/nukeop/equipment/borgchameleon.dm
index 95d2a93d716fc..40a18da86b351 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, 5 SECONDS, target = user, hidden = TRUE) && user.cell.use(activationCost))
playsound(src, 'sound/effects/bamf.ogg', 100, 1, -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 9f79aaf5d78c7..f392b5e6d5475 100644
--- a/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm
+++ b/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm
@@ -133,7 +133,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, 5 SECONDS, 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 5c20e4d4fe45a..1ca085aa06543 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm
@@ -290,7 +290,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/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index 4673766d135c7..2145493661263 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -457,9 +457,8 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
if (!istype(target))
return
- var/icon/I = icon(target.icon,target.icon_state,target.dir)
-
- var/orbitsize = (I.Width()+I.Height())*0.5
+ var/list/icon_dimensions = get_icon_dimensions(target.icon)
+ var/orbitsize = (icon_dimensions["width"] + icon_dimensions["height"]) * 0.5
orbitsize -= (orbitsize/world.icon_size)*(world.icon_size*0.25)
var/rot_seg
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index b82416e450c2b..cff7810008b43 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -224,7 +224,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, target = src, timed_action_flags = IGNORE_HELD_ITEM|IGNORE_RESTRAINED))
+ if(do_after(src, buckle_cd, target = src, timed_action_flags = IGNORE_HELD_ITEM))
if(!buckled)
return
buckled.user_unbuckle_mob(src,src)
@@ -275,7 +275,7 @@
if(!cuff_break)
visible_message("[src] attempts to remove [I]!")
to_chat(src, "You attempt to remove [I]... (This will take around [DisplayTimeText(breakouttime)] and you need to stand still.)")
- if(do_after(src, breakouttime, target = src, timed_action_flags = IGNORE_HELD_ITEM|IGNORE_RESTRAINED))
+ if(do_after(src, breakouttime, target = src, timed_action_flags = IGNORE_HELD_ITEM))
clear_cuffs(I, cuff_break)
else
to_chat(src, "You fail to remove [I]!")
@@ -284,7 +284,7 @@
breakouttime = 50
visible_message("[src] is trying to break [I]!")
to_chat(src, "You attempt to break [I]... (This will take around 5 seconds and you need to stand still.)")
- if(do_after(src, breakouttime, target = src, timed_action_flags = IGNORE_HELD_ITEM|IGNORE_RESTRAINED))
+ if(do_after(src, breakouttime, target = src, timed_action_flags = IGNORE_HELD_ITEM))
clear_cuffs(I, cuff_break)
else
to_chat(src, "You fail to break [I]!")
diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm
index 6c0a149f894cc..4552738cead20 100644
--- a/code/modules/mob/living/carbon/carbon_defines.dm
+++ b/code/modules/mob/living/carbon/carbon_defines.dm
@@ -67,7 +67,6 @@
var/obj/halitem
var/hal_screwyhud = SCREWYHUD_NONE
var/next_hallucination = 0
- var/cpr_time = 1 //CPR cooldown.
var/damageoverlaytemp = 0
var/drunkenness = 0 //Overall drunkenness - check handle_alcohol() in life.dm for effects
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 9d7d6ad030128..c8d06fd0ed649 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -547,46 +547,72 @@
to_chat(src, "\The [S] pulls \the [hand] from your grip!")
rad_act(current_size * 3)
-/mob/living/carbon/human/proc/do_cpr(mob/living/carbon/C)
- CHECK_DNA_AND_SPECIES(C)
+#define CPR_PANIC_SPEED (0.8 SECONDS)
- if(C.stat == DEAD || (HAS_TRAIT(C, TRAIT_FAKEDEATH)))
- to_chat(src, "[C.name] is dead!")
+/// Performs CPR on the target after a delay.
+/mob/living/carbon/human/proc/do_cpr(mob/living/carbon/target)
+ if(target == src)
return
- if(is_mouth_covered())
- to_chat(src, "Remove your mask first!")
- return 0
- if(C.is_mouth_covered())
- to_chat(src, "Remove [p_their()] mask first!")
- return 0
-
- if(C.cpr_time < world.time + 30)
- visible_message("[src] is trying to perform CPR on [C.name]!", \
- "You try to perform CPR on [C.name]... Hold still!")
- if(!do_after(src, target = C))
- to_chat(src, "You fail to perform CPR on [C]!")
- return 0
-
- var/they_breathe = !HAS_TRAIT(C, TRAIT_NOBREATH)
- var/they_lung = C.getorganslot(ORGAN_SLOT_LUNGS)
-
- if(C.health > C.crit_threshold)
- return
- src.visible_message("[src] performs CPR on [C.name]!", "You perform CPR on [C.name].")
+ var/panicking = FALSE
+
+ do
+ CHECK_DNA_AND_SPECIES(target)
+
+ if (DOING_INTERACTION_WITH_TARGET(src,target))
+ return FALSE
+
+ if (target.stat == DEAD || HAS_TRAIT(target, TRAIT_FAKEDEATH))
+ to_chat(src, "[target.name] is dead!")
+ return FALSE
+
+ if (is_mouth_covered())
+ to_chat(src, "Remove your mask first!")
+ return FALSE
+
+ if (target.is_mouth_covered())
+ to_chat(src, "Remove [p_their()] mask first!")
+ return FALSE
+
+ if (!getorganslot(ORGAN_SLOT_LUNGS))
+ to_chat(src, "You have no lungs to breathe with, so you cannot perform CPR!")
+ return FALSE
+
+ if (HAS_TRAIT(src, TRAIT_NOBREATH))
+ to_chat(src, "You do not breathe, so you cannot perform CPR!")
+ return FALSE
+
+ visible_message("[src] is trying to perform CPR on [target.name]!", \
+ "You try to perform CPR on [target.name]... Hold still!")
+
+ if (!do_after(src, delay = panicking ? CPR_PANIC_SPEED : (3 SECONDS), target = target))
+ to_chat(src, "You fail to perform CPR on [target]!")
+ return FALSE
+
+ if (target.health > target.crit_threshold)
+ return FALSE
+
+ visible_message("[src] performs CPR on [target.name]!", "You perform CPR on [target.name].")
SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "perform_cpr", /datum/mood_event/perform_cpr)
- C.cpr_time = world.time
- log_combat(src, C, "CPRed")
-
- if(they_breathe && they_lung)
- var/suff = min(C.getOxyLoss(), 7)
- C.adjustOxyLoss(-suff)
- C.updatehealth()
- to_chat(C, "You feel a breath of fresh air enter your lungs. It feels good.")
- else if(they_breathe && !they_lung)
- to_chat(C, "You feel a breath of fresh air, but you don't feel any better.")
+ log_combat(src, target, "CPRed")
+
+ if (HAS_TRAIT(target, TRAIT_NOBREATH))
+ to_chat(target, "You feel a breath of fresh air... which is a sensation you don't recognise...")
+ else if (!target.getorganslot(ORGAN_SLOT_LUNGS))
+ to_chat(target, "You feel a breath of fresh air... but you don't feel any better...")
else
- to_chat(C, "You feel a breath of fresh air, which is a sensation you don't recognise.")
+ target.adjustOxyLoss(-min(target.getOxyLoss(), 7))
+ to_chat(target, "You feel a breath of fresh air enter your lungs... It feels good...")
+
+ if (target.health <= target.crit_threshold)
+ if (!panicking)
+ to_chat(src, "[target] still isn't up! You try harder!")
+ panicking = TRUE
+ else
+ panicking = FALSE
+ while (panicking)
+
+#undef CPR_PANIC_SPEED
/mob/living/carbon/human/cuff_resist(obj/item/I)
if(HAS_TRAIT(src, TRAIT_FAST_CUFF_REMOVAL))
diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm
index 5a107d674b4b1..851c5809ef8ef 100644
--- a/code/modules/mob/living/carbon/human/species.dm
+++ b/code/modules/mob/living/carbon/human/species.dm
@@ -1537,15 +1537,7 @@ GLOBAL_LIST_EMPTY(features_by_species)
log_combat(user, target, "shaken")
return 1
else
- var/we_breathe = !HAS_TRAIT(user, TRAIT_NOBREATH)
- var/we_lung = user.getorganslot(ORGAN_SLOT_LUNGS)
-
- if(we_breathe && we_lung)
- user.do_cpr(target)
- else if(we_breathe && !we_lung)
- to_chat(user, "You have no lungs to breathe with, so you cannot perform CPR.")
- else
- to_chat(user, "You do not breathe, so you cannot perform CPR.")
+ user.do_cpr(target)
/datum/species/proc/grab(mob/living/carbon/human/user, mob/living/carbon/human/target, datum/martial_art/attacker_style)
if(target.check_block())
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 d9493fe6a43c0..10ceb64ec82e3 100644
--- a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
@@ -109,7 +109,7 @@
H.notransform = TRUE
- if(do_after(owner, delay=60, target=owner, progress=TRUE, timed_action_flags = IGNORE_HELD_ITEM))
+ if(do_after(owner, delay = 6 SECONDS, target = owner, timed_action_flags = IGNORE_HELD_ITEM))
if(H.blood_volume >= BLOOD_VOLUME_SLIME_SPLIT)
make_dupe()
else
diff --git a/code/modules/mob/living/carbon/human/species_types/vampire.dm b/code/modules/mob/living/carbon/human/species_types/vampire.dm
index 50a4022c48485..d1d0d17bdb2a1 100644
--- a/code/modules/mob/living/carbon/human/species_types/vampire.dm
+++ b/code/modules/mob/living/carbon/human/species_types/vampire.dm
@@ -167,7 +167,7 @@
to_chat(victim, "[H] tries to bite you, but recoils in disgust!")
to_chat(H, "[victim] reeks of garlic! you can't bring yourself to drain such tainted blood.")
return
- if(!do_after(H, 30, target = victim))
+ if(!do_after(H, 3 SECONDS, target = victim, hidden = TRUE))
return
var/blood_volume_difference = BLOOD_VOLUME_MAXIMUM - H.blood_volume //How much capacity we have left to absorb blood
var/drained_blood = min(victim.blood_volume, VAMP_DRAIN_AMOUNT, blood_volume_difference)
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 3567a0a6a0432..a87ea489d1b93 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -492,7 +492,7 @@
if(!resting)
set_resting(TRUE, FALSE)
else
- if(do_after(src, 10, target = src, timed_action_flags = IGNORE_RESTRAINED | IGNORE_HELD_ITEM | IGNORE_USER_LOC_CHANGE))
+ if(do_after(src, 1 SECONDS, target = src, timed_action_flags = (IGNORE_USER_LOC_CHANGE|IGNORE_TARGET_LOC_CHANGE|IGNORE_HELD_ITEM)))
set_resting(FALSE, FALSE)
else
to_chat(src, "You fail to get up.")
@@ -911,7 +911,7 @@
who.visible_message("[src] tries to remove [who]'s [what.name].", \
"[src] tries to remove your [what.name].")
what.add_fingerprint(src)
- if(do_after(src, what.strip_delay, who))
+ if(do_after(src, what.strip_delay, who, interaction_key = what))
if(what && Adjacent(who))
if(islist(where))
var/list/L = where
diff --git a/code/modules/mob/living/simple_animal/bot/medbot.dm b/code/modules/mob/living/simple_animal/bot/medbot.dm
index b9fb425e6d425..5061a6dea8b65 100644
--- a/code/modules/mob/living/simple_animal/bot/medbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/medbot.dm
@@ -551,6 +551,9 @@ GLOBAL_VAR(medibot_unique_id_gen)
return FALSE // we shouldn't get random TRUE cases
/mob/living/simple_animal/bot/medbot/attack_hand(mob/living/carbon/human/H)
+ if(DOING_INTERACTION_WITH_TARGET(H, src))
+ to_chat(H, "You're already interacting with [src].")
+ return
if(H.a_intent == INTENT_DISARM && !tipped)
H.visible_message("[H] begins tipping over [src].", "You begin tipping over [src]...")
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 10b6ed5d1be45..bba7e25d1ea11 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -523,7 +523,7 @@
return FALSE
//you can only queue up one examine on something at a time
- if(examined_thing in do_afters)
+ if(DOING_INTERACTION_WITH_TARGET(src, examined_thing))
return FALSE
to_chat(src, "You start feeling around for something...")
diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm
index 0a39af6912142..cfc61c89a73f1 100644
--- a/code/modules/mob/mob_defines.dm
+++ b/code/modules/mob/mob_defines.dm
@@ -205,7 +205,7 @@
///List of progress bars this mob is currently seeing for actions
var/list/progressbars = null //for stacking do_after bars
- ///For storing what do_after's someone has, in case we want to restrict them to only one of a certain do_after at a time
+ ///For storing what do_after's someone has, key = string, value = amount of interactions of that type happening.
var/list/do_afters
///Allows a datum to intercept all click calls this mob is the source of
diff --git a/code/modules/ninja/suit/ninjaDrainAct.dm b/code/modules/ninja/suit/ninjaDrainAct.dm
index bec385c68cc8a..c532194f5f979 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, 1 SECONDS, target = src, hidden = TRUE))
spark_system.start()
playsound(loc, "sparks", 50, 1)
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, 1 SECONDS, target = src, hidden = TRUE))
spark_system.start()
playsound(loc, "sparks", 50, 1)
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, 3 SECONDS, target = src, hidden = TRUE))
. = charge
if(S.cell.charge + charge > S.cell.maxcharge)
S.cell.charge = S.cell.maxcharge
@@ -168,7 +168,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, 1 SECONDS, target = src, hidden = TRUE))
drained = min(drain, delayed_surplus())
add_delayedload(drained)
if(drained < drain)//if no power on net, drain apcs
@@ -208,7 +208,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, 1 SECONDS, target = src, hidden = TRUE))
spark_system.start()
playsound(loc, "sparks", 50, 1)
cell.use(drain)
@@ -235,10 +235,10 @@ They *could* go in their appropriate files, but this is supposed to be modular
drain = cell.charge
if(S.cell.charge+drain > S.cell.maxcharge)
drain = S.cell.maxcharge - S.cell.charge
- maxcapacity = 1
- if (do_after(H,10))
+ maxcapacity = TRUE
+ if (do_after(H, 1 SECONDS, hidden = TRUE))
spark_system.start()
- playsound(loc, "sparks", 50, 1)
+ playsound(loc, "sparks", 50, TRUE)
cell.use(drain)
S.cell.give(drain)
. += drain
diff --git a/code/modules/power/tesla/energy_ball.dm b/code/modules/power/tesla/energy_ball.dm
index f1a2d9f89c515..8e343b58aca4e 100644
--- a/code/modules/power/tesla/energy_ball.dm
+++ b/code/modules/power/tesla/energy_ball.dm
@@ -123,9 +123,9 @@
var/obj/effect/energy_ball/EB = new(loc, 0, TRUE)
EB.transform *= rand(30, 70) * 0.01
- var/icon/I = icon(icon,icon_state,dir)
+ var/list/icon_dimensions = get_icon_dimensions(icon)
- var/orbitsize = (I.Width() + I.Height()) * rand(40, 80) * 0.01
+ var/orbitsize = (icon_dimensions["width"] + icon_dimensions["height"]) * rand(40, 80) * 0.01
orbitsize -= (orbitsize / world.icon_size) * (world.icon_size * 0.25)
EB.orbit(src, orbitsize, pick(FALSE, TRUE), rand(10, 25), pick(3, 4, 5, 6, 36))
diff --git a/code/modules/spells/spell_types/lesserlichdom.dm b/code/modules/spells/spell_types/lesserlichdom.dm
index 93cd0c16a9ce1..93b83a64cad30 100644
--- a/code/modules/spells/spell_types/lesserlichdom.dm
+++ b/code/modules/spells/spell_types/lesserlichdom.dm
@@ -49,7 +49,7 @@
playsound(user, 'sound/effects/pope_entry.ogg', 100)
- if(!do_after(M, 50, target=marked_item, timed_action_flags = IGNORE_HELD_ITEM))
+ if(!do_after(M, 5 SECONDS, target = marked_item, timed_action_flags = IGNORE_HELD_ITEM))
to_chat(M, "Your soul snaps back to your body as you stop ensouling [marked_item]!")
return
diff --git a/code/modules/spells/spell_types/lightning.dm b/code/modules/spells/spell_types/lightning.dm
index f6f139c041d70..c14df503cf91b 100644
--- a/code/modules/spells/spell_types/lightning.dm
+++ b/code/modules/spells/spell_types/lightning.dm
@@ -28,7 +28,7 @@
halo = halo || mutable_appearance('icons/effects/effects.dmi', "electricity", EFFECTS_LAYER)
user.add_overlay(halo)
playsound(get_turf(user), Snd, 50, 0)
- if(do_after(user, 10 SECONDS, timed_action_flags = UNINTERRUPTIBLE))
+ if(do_after(user, 10 SECONDS, timed_action_flags = (IGNORE_USER_LOC_CHANGE|IGNORE_HELD_ITEM)))
if(ready && cast_check(skipcharge=1))
choose_targets()
else
diff --git a/code/modules/vehicles/mecha/equipment/mecha_equipment.dm b/code/modules/vehicles/mecha/equipment/mecha_equipment.dm
index 30f1bc54abf0f..b2382d97b7b77 100644
--- a/code/modules/vehicles/mecha/equipment/mecha_equipment.dm
+++ b/code/modules/vehicles/mecha/equipment/mecha_equipment.dm
@@ -97,12 +97,12 @@
chassis.use_power(energy_drain)
return TRUE
-/obj/item/mecha_parts/mecha_equipment/proc/do_after_cooldown(atom/target, mob/user)
+/obj/item/mecha_parts/mecha_equipment/proc/do_after_cooldown(atom/target, mob/user, interaction_key)
if(!chassis)
return
var/C = chassis.loc
chassis.use_power(energy_drain)
- . = do_after(user, equip_cooldown, target=target)
+ . = do_after(user, equip_cooldown, target=target, interaction_key = interaction_key)
if(!chassis || chassis.loc != C || src != chassis.selected || !(get_dir(chassis, target)&chassis.dir))
return FALSE
diff --git a/code/modules/vehicles/mecha/equipment/tools/mining_tools.dm b/code/modules/vehicles/mecha/equipment/tools/mining_tools.dm
index e7d5ca4806860..7318bc74edab5 100644
--- a/code/modules/vehicles/mecha/equipment/tools/mining_tools.dm
+++ b/code/modules/vehicles/mecha/equipment/tools/mining_tools.dm
@@ -27,18 +27,22 @@
/obj/item/mecha_parts/mecha_equipment/drill/action(mob/source, atom/target, params)
if(!action_checks(target))
return
- if(isspaceturf(target))
+
+ if(isspaceturf(target) || !(isliving(target) || isobj(target) || isturf(target)))
return
+
if(isobj(target))
var/obj/target_obj = target
- if(target_obj.resistance_flags & UNACIDABLE)
+ if(target_obj.resistance_flags & (UNACIDABLE | INDESTRUCTIBLE))
return
- target.visible_message("[chassis] starts to drill [target].", \
- "[chassis] starts to drill [target]...", \
- "You hear drilling.")
// You can't drill harder by clicking more.
- if(!(target in source.do_afters) && do_after_cooldown(target, source))
+ if(!DOING_INTERACTION_WITH_TARGET(source, target) && do_after_cooldown(target, source, DOAFTER_SOURCE_MECHADRILL))
+
+ target.visible_message("[chassis] starts to drill [target].", \
+ "[chassis] starts to drill [target]...", \
+ "You hear drilling.")
+
log_message("Started drilling [target]", LOG_MECHA)
if(isturf(target))
var/turf/T = target
@@ -47,13 +51,15 @@
while(do_after_mecha(target, source, drill_delay))
if(isliving(target))
drill_mob(target, source)
- playsound(src,'sound/weapons/drill.ogg',40,1)
+ playsound(src,'sound/weapons/drill.ogg',40,TRUE)
else if(isobj(target))
var/obj/O = target
O.take_damage(15, BRUTE, 0, FALSE, get_dir(chassis, target))
- playsound(src,'sound/weapons/drill.ogg',40,1)
- else
- return
+ playsound(src,'sound/weapons/drill.ogg',40,TRUE)
+
+ if(QDELETED(target))
+ break
+
return ..()
/turf/proc/drill_act(obj/item/mecha_parts/mecha_equipment/drill/drill, mob/user)
diff --git a/icons/blanks/32x32.dmi b/icons/blanks/32x32.dmi
new file mode 100644
index 0000000000000..6c4f2b33e0fee
Binary files /dev/null and b/icons/blanks/32x32.dmi differ
diff --git a/icons/blanks/96x96.dmi b/icons/blanks/96x96.dmi
new file mode 100644
index 0000000000000..d79e60c111abf
Binary files /dev/null and b/icons/blanks/96x96.dmi differ
diff --git a/icons/blank_title.png b/icons/blanks/blank_title.png
similarity index 100%
rename from icons/blank_title.png
rename to icons/blanks/blank_title.png
diff --git a/icons/effects/progressbar.dmi b/icons/effects/progressbar.dmi
new file mode 100644
index 0000000000000..3eed14db704a7
Binary files /dev/null and b/icons/effects/progressbar.dmi differ