diff --git a/code/__DEFINES/math.dm b/code/__DEFINES/math.dm
index c6ffc61495e..3f360d5bad7 100644
--- a/code/__DEFINES/math.dm
+++ b/code/__DEFINES/math.dm
@@ -32,6 +32,8 @@
#define ToDegrees(radians) ((radians) * 57.2957795) // 180 / Pi
#define ToRadians(degrees) ((degrees) * 0.0174532925) // Pi / 180
+#define SHORT_REAL_LIMIT 16777216
+
//"fancy" math for calculating time in ms from tick_usage percentage and the length of ticks
//percent_of_tick_used * (ticklag * 100(to convert to ms)) / 100(percent ratio)
//collapsed to percent_of_tick_used * tick_lag
diff --git a/code/__HELPERS/cmp.dm b/code/__HELPERS/cmp.dm
index 4b97b67e27e..0db57b27eae 100644
--- a/code/__HELPERS/cmp.dm
+++ b/code/__HELPERS/cmp.dm
@@ -31,6 +31,9 @@
/proc/cmp_subsystem_priority(datum/controller/subsystem/a, datum/controller/subsystem/b)
return a.priority - b.priority
+/proc/cmp_timer(datum/timedevent/a, datum/timedevent/b)
+ return a.timeToRun - b.timeToRun
+
/proc/cmp_qdel_item_time(datum/qdel_item/A, datum/qdel_item/B)
. = B.hard_delete_time - A.hard_delete_time
if(!.)
diff --git a/code/__HELPERS/qdel.dm b/code/__HELPERS/qdel.dm
index 8171dd66427..136e573a25e 100644
--- a/code/__HELPERS/qdel.dm
+++ b/code/__HELPERS/qdel.dm
@@ -1,5 +1,10 @@
-#define QDEL_IN(item, time) addtimer(GLOBAL_PROC, "qdel", time, FALSE, item)
+#define QDEL_IN(item, time) addtimer(CALLBACK(GLOBAL_PROC, .proc/qdel, item), time, TIMER_STOPPABLE)
+#define QDEL_IN_CLIENT_TIME(item, time) addtimer(CALLBACK(GLOBAL_PROC, .proc/qdel, item), time, TIMER_STOPPABLE | TIMER_CLIENT_TIME)
#define QDEL_NULL(item) if(item) { qdel(item); item = null }
#define QDEL_LIST(L) if(L) { for(var/I in L) qdel(I); L.Cut(); }
+#define QDEL_LIST_IN(L, time) addtimer(CALLBACK(GLOBAL_PROC, .proc/______qdel_list_wrapper, L), time, TIMER_STOPPABLE)
#define QDEL_LIST_ASSOC(L) if(L) { for(var/I in L) { qdel(L[I]); qdel(I); } L.Cut(); }
#define QDEL_LIST_ASSOC_VAL(L) if(L) { for(var/I in L) qdel(L[I]); L.Cut(); }
+
+/proc/______qdel_list_wrapper(list/L) //the underscores are to encourage people not to use this directly.
+ QDEL_LIST(L)
\ No newline at end of file
diff --git a/code/controllers/Processes/nano_mob_hunter.dm b/code/controllers/Processes/nano_mob_hunter.dm
index fb57b6e0bbf..dc5dcba805b 100644
--- a/code/controllers/Processes/nano_mob_hunter.dm
+++ b/code/controllers/Processes/nano_mob_hunter.dm
@@ -44,7 +44,7 @@ DECLARE_GLOBAL_CONTROLLER(mob_hunt, mob_hunt_server)
recover_time = 3000
if(recover_time > 0) //when provided with a negative or zero valued recover_time argument, the server won't auto-restart but can be manually rebooted still
//set a timer to automatically recover after recover_time has passed (can be manually restarted if you get impatient too)
- addtimer(src, "auto_recover", recover_time, TRUE)
+ addtimer(CALLBACK(src, .proc/auto_recover), recover_time, TIMER_UNIQUE)
/datum/controller/process/mob_hunt/proc/client_mob_update()
var/list/ex_players = list()
diff --git a/code/controllers/Processes/timer.dm b/code/controllers/Processes/timer.dm
deleted file mode 100644
index 4b42d69221d..00000000000
--- a/code/controllers/Processes/timer.dm
+++ /dev/null
@@ -1,100 +0,0 @@
-var/global/datum/controller/process/timer/timer_master
-
-/datum/controller/process/timer
- var/list/processing_timers = list()
- var/list/hashes = list()
-
-/datum/controller/process/timer/setup()
- name = "timer"
- schedule_interval = 1 //every 0.1 seconds--2 server ticks
- log_startup_progress("Timer process starting up.")
-
-/datum/controller/process/timer/statProcess()
- ..()
- stat(null, "[processing_timers.len] timers")
-
-/datum/controller/process/timer/doWork()
- if(!processing_timers.len)
- disabled = 1 //nothing to do, lets stop firing.
- return
- for(last_object in processing_timers)
- var/datum/timedevent/event = last_object
- if(!event.thingToCall || check_datum_qdeleted(event.thingToCall))
- qdel(event)
- if(event.timeToRun <= world.time)
- runevent(event)
- qdel(event)
- SCHECK
-
-DECLARE_GLOBAL_CONTROLLER(timer, timer_master)
-
-/datum/controller/process/timer/proc/runevent(datum/timedevent/event)
- set waitfor = 0
- if(event.thingToCall)
- if(event.thingToCall == GLOBAL_PROC && istext(event.procToCall))
- call("/proc/[event.procToCall]")(arglist(event.argList))
- else
- call(event.thingToCall, event.procToCall)(arglist(event.argList))
-
-/datum/timedevent
- var/thingToCall
- var/procToCall
- var/timeToRun
- var/argList
- var/id
- var/hash
- var/static/nextid = 1
-
-/datum/timedevent/New()
- id = nextid++
-
-/datum/timedevent/Destroy()
- timer_master.processing_timers -= src
- timer_master.hashes -= hash
- return QDEL_HINT_IWILLGC
-
-/proc/addtimer(thingToCall, procToCall, wait, unique = FALSE, ...)
- if(!timer_master) //can't run timers before the mc has been created
- return
- if(!thingToCall || !procToCall)
- return
- if(timer_master.disabled)
- timer_master.disabled = 0
-
- var/datum/timedevent/event = new()
- event.thingToCall = thingToCall
- event.procToCall = procToCall
- event.timeToRun = world.time + wait
- var/hashlist = args.Copy()
-
- hashlist[1] = "[thingToCall](\ref[thingToCall])"
- event.hash = jointext(hashlist, null)
- if(args.len > 4)
- event.argList = args.Copy(5)
-
- // Check for dupes if unique = 1.
- if(unique)
- var/datum/timedevent/hash_event = timer_master.hashes[event.hash]
- if(hash_event)
- return hash_event.id
- timer_master.hashes[event.hash] = event
- if(wait <= 0)
- timer_master.runevent(event)
- timer_master.hashes -= event.hash
- return
- // If we are unique (or we're not checking that), add the timer and return the id.
- timer_master.processing_timers += event
-
- return event.id
-
-/proc/deltimer(id)
- if(id == 0)
- // No event will correspond to an id of 0 - the timer does not exist
- // Save us a possibly expensive iteration through the timer list
- // This would probably be more efficient in general if we used an associative list instead
- return 0
- for(var/datum/timedevent/event in timer_master.processing_timers)
- if(event.id == id)
- qdel(event)
- return 1
- return 0
diff --git a/code/controllers/Processes/weather.dm b/code/controllers/Processes/weather.dm
index 1706b2232c0..919352313c6 100644
--- a/code/controllers/Processes/weather.dm
+++ b/code/controllers/Processes/weather.dm
@@ -37,7 +37,7 @@ var/global/datum/controller/process/weather/weather_master
var/datum/weather/W = pickweight(possible_weather_for_this_z)
run_weather(W.name)
eligible_zlevels -= Z
- addtimer(src, "make_z_eligible", rand(3000, 6000) + W.weather_duration_upper, TRUE, Z) //Around 5-10 minutes between weathers
+ addtimer(CALLBACK(src, .proc/make_z_eligible, Z), rand(3000, 6000) + W.weather_duration_upper, TIMER_UNIQUE) //Around 5-10 minutes between weathers
DECLARE_GLOBAL_CONTROLLER(weather, weather_master)
diff --git a/code/controllers/subsystem/timer.dm b/code/controllers/subsystem/timer.dm
new file mode 100644
index 00000000000..21fea2ee419
--- /dev/null
+++ b/code/controllers/subsystem/timer.dm
@@ -0,0 +1,518 @@
+#define BUCKET_LEN (world.fps*1*60) //how many ticks should we keep in the bucket. (1 minutes worth)
+#define BUCKET_POS(timer) ((round((timer.timeToRun - SStimer.head_offset) / world.tick_lag) % BUCKET_LEN) + 1)
+#define TIMER_MAX (world.time + TICKS2DS(min(BUCKET_LEN-(SStimer.practical_offset-DS2TICKS(world.time - SStimer.head_offset))-1, BUCKET_LEN-1)))
+#define TIMER_ID_MAX (2**24) //max float with integer precision
+
+SUBSYSTEM_DEF(timer)
+ name = "Timer"
+ wait = 1 //SS_TICKER subsystem, so wait is in ticks
+ init_order = INIT_ORDER_TIMER
+
+ flags = SS_TICKER|SS_NO_INIT
+
+ var/list/second_queue = list() //awe, yes, you've had first queue, but what about second queue? Contains: /datum/timedevent
+ var/list/hashes = list()
+
+ var/head_offset = 0 //world.time of the first entry in the the bucket.
+ var/practical_offset = 1 //index of the first non-empty item in the bucket.
+ var/bucket_resolution = 0 //world.tick_lag the bucket was designed for
+ var/bucket_count = 0 //how many timers are in the buckets
+
+ var/list/bucket_list = list() //list of buckets, each bucket holds every timer that has to run that byond tick.
+
+ var/list/timer_id_dict = list() //list of all active timers assoicated to their timer id (for easy lookup)
+
+ var/list/clienttime_timers = list() //special snowflake timers that run on fancy pansy "client time"
+
+ var/last_invoke_tick = 0
+ var/static/last_invoke_warning = 0
+ var/static/bucket_auto_reset = TRUE
+
+/datum/controller/subsystem/timer/PreInit()
+ bucket_list.len = BUCKET_LEN
+ head_offset = world.time
+ bucket_resolution = world.tick_lag
+
+/datum/controller/subsystem/timer/stat_entry(msg)
+ ..("B:[bucket_count] P:[length(second_queue)] H:[length(hashes)] C:[length(clienttime_timers)] S:[length(timer_id_dict)]")
+
+/datum/controller/subsystem/timer/fire(resumed = FALSE)
+ var/lit = last_invoke_tick
+ var/last_check = world.time - TIMER_NO_INVOKE_WARNING
+ var/list/bucket_list = src.bucket_list
+
+ if(!bucket_count)
+ last_invoke_tick = world.time
+
+ if(lit && lit < last_check && last_invoke_warning < last_check)
+ last_invoke_warning = world.time
+ var/msg = "No regular timers processed in the last [TIMER_NO_INVOKE_WARNING] ticks[bucket_auto_reset ? ", resetting buckets" : ""]!"
+ message_admins(msg)
+ WARNING(msg)
+ if(bucket_auto_reset)
+ bucket_resolution = 0
+
+ log_world("Timer bucket reset. world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]")
+ for(var/i in 1 to length(bucket_list))
+ var/datum/timedevent/bucket_head = bucket_list[i]
+ if(!bucket_head)
+ continue
+
+ log_world("Active timers at index [i]:")
+
+ var/datum/timedevent/bucket_node = bucket_head
+ var/anti_loop_check = 1000
+ do
+ log_world(get_timer_debug_string(bucket_node))
+ bucket_node = bucket_node.next
+ anti_loop_check--
+ while(bucket_node && bucket_node != bucket_head && anti_loop_check)
+ log_world("Active timers in the second_queue queue:")
+ for(var/I in second_queue)
+ log_world(get_timer_debug_string(I))
+
+ var/next_clienttime_timer_index = 0
+ var/len = length(clienttime_timers)
+
+ for(next_clienttime_timer_index in 1 to len)
+ if(MC_TICK_CHECK)
+ next_clienttime_timer_index--
+ break
+ var/datum/timedevent/ctime_timer = clienttime_timers[next_clienttime_timer_index]
+ if(ctime_timer.timeToRun > REALTIMEOFDAY)
+ next_clienttime_timer_index--
+ break
+
+ var/datum/callback/callBack = ctime_timer.callBack
+ if(!callBack)
+ clienttime_timers.Cut(next_clienttime_timer_index,next_clienttime_timer_index+1)
+ CRASH("Invalid timer: [get_timer_debug_string(ctime_timer)] world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset], REALTIMEOFDAY: [REALTIMEOFDAY]")
+
+ ctime_timer.spent = REALTIMEOFDAY
+ callBack.InvokeAsync()
+ qdel(ctime_timer)
+
+
+ if(next_clienttime_timer_index)
+ clienttime_timers.Cut(1,next_clienttime_timer_index+1)
+
+ if(MC_TICK_CHECK)
+ return
+
+ var/static/list/spent = list()
+ var/static/datum/timedevent/timer
+ if(practical_offset > BUCKET_LEN)
+ head_offset += TICKS2DS(BUCKET_LEN)
+ practical_offset = 1
+ resumed = FALSE
+
+ if((length(bucket_list) != BUCKET_LEN) || (world.tick_lag != bucket_resolution))
+ reset_buckets()
+ bucket_list = src.bucket_list
+ resumed = FALSE
+
+
+ if(!resumed)
+ timer = null
+
+ while(practical_offset <= BUCKET_LEN && head_offset + (practical_offset*world.tick_lag) <= world.time)
+ var/datum/timedevent/head = bucket_list[practical_offset]
+ if(!timer || !head || timer == head)
+ head = bucket_list[practical_offset]
+ timer = head
+ while(timer)
+ var/datum/callback/callBack = timer.callBack
+ if(!callBack)
+ bucket_resolution = null //force bucket recreation
+ CRASH("Invalid timer: [get_timer_debug_string(timer)] world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]")
+
+ if(!timer.spent)
+ spent += timer
+ timer.spent = world.time
+ callBack.InvokeAsync()
+ last_invoke_tick = world.time
+
+ if(MC_TICK_CHECK)
+ return
+
+ timer = timer.next
+ if(timer == head)
+ break
+
+
+ bucket_list[practical_offset++] = null
+
+ //we freed up a bucket, lets see if anything in second_queue needs to be shifted to that bucket.
+ var/i = 0
+ var/L = length(second_queue)
+ for(i in 1 to L)
+ timer = second_queue[i]
+ if(timer.timeToRun >= TIMER_MAX)
+ i--
+ break
+
+ if(timer.timeToRun < head_offset)
+ bucket_resolution = null //force bucket recreation
+ CRASH("[i] Invalid timer state: Timer in long run queue with a time to run less then head_offset. [get_timer_debug_string(timer)] world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]")
+
+ if(timer.callBack && !timer.spent)
+ timer.callBack.InvokeAsync()
+ spent += timer
+ bucket_count++
+ else if(!QDELETED(timer))
+ qdel(timer)
+ continue
+
+ if(timer.timeToRun < head_offset + TICKS2DS(practical_offset))
+ bucket_resolution = null //force bucket recreation
+ CRASH("[i] Invalid timer state: Timer in long run queue that would require a backtrack to transfer to short run queue. [get_timer_debug_string(timer)] world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]")
+ if(timer.callBack && !timer.spent)
+ timer.callBack.InvokeAsync()
+ spent += timer
+ bucket_count++
+ else if(!QDELETED(timer))
+ qdel(timer)
+ continue
+
+ bucket_count++
+ var/bucket_pos = max(1, BUCKET_POS(timer))
+
+ var/datum/timedevent/bucket_head = bucket_list[bucket_pos]
+ if(!bucket_head)
+ bucket_list[bucket_pos] = timer
+ timer.next = null
+ timer.prev = null
+ continue
+
+ if(!bucket_head.prev)
+ bucket_head.prev = bucket_head
+ timer.next = bucket_head
+ timer.prev = bucket_head.prev
+ timer.next.prev = timer
+ timer.prev.next = timer
+ if(i)
+ second_queue.Cut(1, i+1)
+
+ timer = null
+
+ bucket_count -= length(spent)
+
+ for(var/spent_timer in spent)
+ qdel(spent_timer)
+
+ spent.len = 0
+
+//formated this way to be runtime resistant
+/datum/controller/subsystem/timer/proc/get_timer_debug_string(datum/timedevent/TE)
+ . = "Timer: [TE]"
+ . += "Prev: [TE.prev ? TE.prev : "NULL"], Next: [TE.next ? TE.next : "NULL"]"
+ if(TE.spent)
+ . += ", SPENT([TE.spent])"
+ if(QDELETED(TE))
+ . += ", QDELETED"
+ if(!TE.callBack)
+ . += ", NO CALLBACK"
+
+/datum/controller/subsystem/timer/proc/reset_buckets()
+ var/list/bucket_list = src.bucket_list
+ var/list/alltimers = list()
+ //collect the timers currently in the bucket
+ for(var/bucket_head in bucket_list)
+ if(!bucket_head)
+ continue
+ var/datum/timedevent/bucket_node = bucket_head
+ do
+ alltimers += bucket_node
+ bucket_node = bucket_node.next
+ while(bucket_node && bucket_node != bucket_head)
+
+ bucket_list.len = 0
+ bucket_list.len = BUCKET_LEN
+
+ practical_offset = 1
+ bucket_count = 0
+ head_offset = world.time
+ bucket_resolution = world.tick_lag
+
+ alltimers += second_queue
+ if(!length(alltimers))
+ return
+
+ sortTim(alltimers, .proc/cmp_timer)
+
+ var/datum/timedevent/head = alltimers[1]
+
+ if(head.timeToRun < head_offset)
+ head_offset = head.timeToRun
+
+ var/new_bucket_count
+ var/i = 1
+ for(i in 1 to length(alltimers))
+ var/datum/timedevent/timer = alltimers[1]
+ if(!timer)
+ continue
+
+ var/bucket_pos = BUCKET_POS(timer)
+ if(timer.timeToRun >= TIMER_MAX)
+ i--
+ break
+
+
+ if(!timer.callBack || timer.spent)
+ WARNING("Invalid timer: [get_timer_debug_string(timer)] world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]")
+ if(timer.callBack)
+ qdel(timer)
+ continue
+
+ new_bucket_count++
+ var/datum/timedevent/bucket_head = bucket_list[bucket_pos]
+ if(!bucket_head)
+ bucket_list[bucket_pos] = timer
+ timer.next = null
+ timer.prev = null
+ continue
+
+ if(!bucket_head.prev)
+ bucket_head.prev = bucket_head
+ timer.next = bucket_head
+ timer.prev = bucket_head.prev
+ timer.next.prev = timer
+ timer.prev.next = timer
+ if(i)
+ alltimers.Cut(1, i+1)
+ second_queue = alltimers
+ bucket_count = new_bucket_count
+
+
+/datum/controller/subsystem/timer/Recover()
+ second_queue |= SStimer.second_queue
+ hashes |= SStimer.hashes
+ timer_id_dict |= SStimer.timer_id_dict
+ bucket_list |= SStimer.bucket_list
+
+/datum/timedevent
+ var/id
+ var/datum/callback/callBack
+ var/timeToRun
+ var/hash
+ var/list/flags
+ var/spent = 0 //time we ran the timer.
+ var/name //for easy debugging.
+ //cicular doublely linked list
+ var/datum/timedevent/next
+ var/datum/timedevent/prev
+
+/datum/timedevent/New(datum/callback/callBack, timeToRun, flags, hash)
+ var/static/nextid = 1
+ id = TIMER_ID_NULL
+ src.callBack = callBack
+ src.timeToRun = timeToRun
+ src.flags = flags
+ src.hash = hash
+
+ if(flags & TIMER_UNIQUE)
+ SStimer.hashes[hash] = src
+
+ if(flags & TIMER_STOPPABLE)
+ id = num2text(nextid, 100)
+ if(nextid >= SHORT_REAL_LIMIT)
+ nextid += min(1, 2**round(nextid/SHORT_REAL_LIMIT))
+ else
+ nextid++
+ SStimer.timer_id_dict[id] = src
+
+ name = "Timer: [id] (\ref[src]), TTR: [timeToRun], Flags: [jointext(bitfield2list(flags, list("TIMER_UNIQUE", "TIMER_OVERRIDE", "TIMER_CLIENT_TIME", "TIMER_STOPPABLE", "TIMER_NO_HASH_WAIT")), ", ")], callBack: \ref[callBack], callBack.object: [callBack.object]\ref[callBack.object]([getcallingtype()]), callBack.delegate:[callBack.delegate]([callBack.arguments ? callBack.arguments.Join(", ") : ""])"
+
+ if((timeToRun < world.time || timeToRun < SStimer.head_offset) && !(flags & TIMER_CLIENT_TIME))
+ CRASH("Invalid timer state: Timer created that would require a backtrack to run (addtimer would never let this happen): [SStimer.get_timer_debug_string(src)]")
+
+ if(callBack.object != GLOBAL_PROC)
+ LAZYADD(callBack.object.active_timers, src)
+
+
+ var/list/L
+
+ if(flags & TIMER_CLIENT_TIME)
+ L = SStimer.clienttime_timers
+ else if(timeToRun >= TIMER_MAX)
+ L = SStimer.second_queue
+
+
+ if(L)
+ //binary search sorted insert
+ var/cttl = length(L)
+ if(cttl)
+ var/left = 1
+ var/right = cttl
+ var/mid = (left+right) >> 1 //rounded divide by two for hedgehogs
+
+ var/datum/timedevent/item
+ while(left < right)
+ item = L[mid]
+ if(item.timeToRun <= timeToRun)
+ left = mid+1
+ else
+ right = mid
+ mid = (left+right) >> 1
+
+ item = L[mid]
+ mid = item.timeToRun > timeToRun ? mid : mid+1
+ L.Insert(mid, src)
+
+ else
+ L += src
+ return
+
+ //get the list of buckets
+ var/list/bucket_list = SStimer.bucket_list
+
+ //calculate our place in the bucket list
+ var/bucket_pos = BUCKET_POS(src)
+
+ //get the bucket for our tick
+ var/datum/timedevent/bucket_head = bucket_list[bucket_pos]
+ SStimer.bucket_count++
+ //empty bucket, we will just add ourselves
+ if(!bucket_head)
+ bucket_list[bucket_pos] = src
+ return
+ //other wise, lets do a simplified linked list add.
+ if(!bucket_head.prev)
+ bucket_head.prev = bucket_head
+ next = bucket_head
+ prev = bucket_head.prev
+ next.prev = src
+ prev.next = src
+
+/datum/timedevent/Destroy()
+ ..()
+ if(flags & TIMER_UNIQUE && hash)
+ SStimer.hashes -= hash
+
+ if(callBack && callBack.object && callBack.object != GLOBAL_PROC && callBack.object.active_timers)
+ callBack.object.active_timers -= src
+ UNSETEMPTY(callBack.object.active_timers)
+
+ callBack = null
+
+ if(flags & TIMER_STOPPABLE)
+ SStimer.timer_id_dict -= id
+
+ if(flags & TIMER_CLIENT_TIME)
+ if(!spent)
+ spent = world.time
+ SStimer.clienttime_timers -= src
+ return QDEL_HINT_IWILLGC
+
+ if(!spent)
+ spent = world.time
+ var/bucketpos = BUCKET_POS(src)
+ var/datum/timedevent/buckethead
+ var/list/bucket_list = SStimer.bucket_list
+ if(bucketpos > 0)
+ buckethead = bucket_list[bucketpos]
+
+ if(buckethead == src)
+ bucket_list[bucketpos] = next
+ SStimer.bucket_count--
+ else if(timeToRun < TIMER_MAX || next || prev)
+ SStimer.bucket_count--
+ else
+ var/l = length(SStimer.second_queue)
+ SStimer.second_queue -= src
+ if(l == length(SStimer.second_queue))
+ SStimer.bucket_count--
+
+ if(prev == next && next)
+ next.prev = null
+ prev.next = null
+ else
+ if(prev)
+ prev.next = next
+ if(next)
+ next.prev = prev
+ else
+ if(prev && prev.next == src)
+ prev.next = next
+ if(next && next.prev == src)
+ next.prev = prev
+ next = null
+ prev = null
+ return QDEL_HINT_IWILLGC
+
+/datum/timedevent/proc/getcallingtype()
+ . = "ERROR"
+ if(callBack.object == GLOBAL_PROC)
+ . = "GLOBAL_PROC"
+ else
+ . = "[callBack.object.type]"
+
+/proc/addtimer(datum/callback/callback, wait = 0, flags = 0)
+ if(!callback)
+ CRASH("addtimer called without a callback")
+
+ if(wait < 0)
+ stack_trace("addtimer called with a negative wait. Converting to 0")
+
+ //alot of things add short timers on themselves in their destroy, we ignore those cases
+ if(wait >= 1 && callback && callback.object && callback.object != GLOBAL_PROC && QDELETED(callback.object))
+ stack_trace("addtimer called with a callback assigned to a qdeleted object")
+
+ wait = max(wait, 0)
+
+ if(wait >= INFINITY)
+ CRASH("Attempted to create timer with INFINITY delay")
+
+ var/hash
+
+ if(flags & TIMER_UNIQUE)
+ var/list/hashlist
+ if(flags & TIMER_NO_HASH_WAIT)
+ hashlist = list(callback.object, "([callback.object.UID()])", callback.delegate, flags & TIMER_CLIENT_TIME)
+ else
+ hashlist = list(callback.object, "([callback.object.UID()])", callback.delegate, wait, flags & TIMER_CLIENT_TIME)
+ hashlist += callback.arguments
+ hash = hashlist.Join("|||||||")
+
+ var/datum/timedevent/hash_timer = SStimer.hashes[hash]
+ if(hash_timer)
+ if(hash_timer.spent) //it's pending deletion, pretend it doesn't exist.
+ hash_timer.hash = null //but keep it from accidentally deleting us
+ else
+ if(flags & TIMER_OVERRIDE)
+ hash_timer.hash = null //no need having it delete it's hash if we are going to replace it
+ qdel(hash_timer)
+ else
+ if(hash_timer.flags & TIMER_STOPPABLE)
+ . = hash_timer.id
+ return
+
+
+ var/timeToRun = world.time + wait
+ if(flags & TIMER_CLIENT_TIME)
+ timeToRun = REALTIMEOFDAY + wait
+
+ var/datum/timedevent/timer = new(callback, timeToRun, flags, hash)
+ return timer.id
+
+/proc/deltimer(id)
+ if(!id)
+ return FALSE
+ if(id == TIMER_ID_NULL)
+ CRASH("Tried to delete a null timerid. Use TIMER_STOPPABLE flag")
+ if(!istext(id))
+ if(istype(id, /datum/timedevent))
+ qdel(id)
+ return TRUE
+ //id is string
+ var/datum/timedevent/timer = SStimer.timer_id_dict[id]
+ if(timer && !timer.spent)
+ qdel(timer)
+ return TRUE
+ return FALSE
+
+
+#undef BUCKET_LEN
+#undef BUCKET_POS
+#undef TIMER_MAX
+#undef TIMER_ID_MAX
diff --git a/code/controllers/verbs.dm b/code/controllers/verbs.dm
index 73104f212ff..d5426b3d673 100644
--- a/code/controllers/verbs.dm
+++ b/code/controllers/verbs.dm
@@ -91,7 +91,7 @@
debug_variables(shuttle_master)
feedback_add_details("admin_verb","DShuttle")
if("Timer")
- debug_variables(timer_master)
+ debug_variables(SStimer)
feedback_add_details("admin_verb","DTimer")
if("Weather")
debug_variables(weather_master)
diff --git a/code/datums/datum.dm b/code/datums/datum.dm
index 3e8c11cc1e8..ae9a6df5b4f 100644
--- a/code/datums/datum.dm
+++ b/code/datums/datum.dm
@@ -1,5 +1,6 @@
/datum
var/gc_destroyed //Time when this object was destroyed.
+ var/list/active_timers //for SStimer
var/list/datum_components //for /datum/components
var/var_edited = FALSE //Warranty void if seal is broken
@@ -16,6 +17,14 @@
/datum/proc/Destroy(force = FALSE, ...)
tag = null
+ var/list/timers = active_timers
+ active_timers = null
+ for(var/thing in timers)
+ var/datum/timedevent/timer = thing
+ if(timer.spent)
+ continue
+ qdel(timer)
+
var/list/dc = datum_components
if(dc)
var/all_components = dc[/datum/component]
diff --git a/code/datums/looping_sounds/looping_sound.dm b/code/datums/looping_sounds/looping_sound.dm
index 4a5af0010e9..2cf79ad318b 100644
--- a/code/datums/looping_sounds/looping_sound.dm
+++ b/code/datums/looping_sounds/looping_sound.dm
@@ -65,7 +65,7 @@
return
if(!chance || prob(chance))
play(get_sound(looped))
- addtimer(src, "sound_loop", mid_length, FALSE, ++looped)
+ addtimer(CALLBACK(src, .proc/sound_loop, ++looped), mid_length)
/datum/looping_sound/proc/play(soundfile)
var/list/atoms_cache = output_atoms
@@ -93,7 +93,7 @@
if(start_sound)
play(start_sound)
start_wait = start_length
- addtimer(src, "sound_loop", start_wait)
+ addtimer(CALLBACK(src, .proc/sound_loop), start_wait)
/datum/looping_sound/proc/on_stop(looped)
if(end_sound)
diff --git a/code/datums/mind.dm b/code/datums/mind.dm
index 5fee7994a83..be35cf8cca8 100644
--- a/code/datums/mind.dm
+++ b/code/datums/mind.dm
@@ -1538,8 +1538,7 @@
H.update_inv_w_uniform(0,0)
add_attack_logs(missionary, current, "Converted to a zealot for [convert_duration/600] minutes")
- addtimer(src, "remove_zealot", convert_duration, FALSE, jumpsuit) //deconverts after the timer expires
-
+ addtimer(CALLBACK(src, .proc/remove_zealot, jumpsuit), convert_duration) //deconverts after the timer expires
return 1
/datum/mind/proc/remove_zealot(obj/item/clothing/under/jumpsuit = null)
diff --git a/code/datums/spells/ethereal_jaunt.dm b/code/datums/spells/ethereal_jaunt.dm
index 43c3d3a61cd..6e3ead3849e 100644
--- a/code/datums/spells/ethereal_jaunt.dm
+++ b/code/datums/spells/ethereal_jaunt.dm
@@ -26,7 +26,7 @@
if(!target.can_safely_leave_loc()) // No more brainmobs hopping out of their brains
to_chat(target, "You are somehow too bound to your current location to abandon it.")
continue
- addtimer(src, "do_jaunt", 0, FALSE, target)
+ INVOKE_ASYNC(src, .proc/do_jaunt, target)
/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/proc/do_jaunt(mob/living/target)
target.notransform = 1
diff --git a/code/datums/weather/weather.dm b/code/datums/weather/weather.dm
index c87b2b455e7..320cc137806 100644
--- a/code/datums/weather/weather.dm
+++ b/code/datums/weather/weather.dm
@@ -31,7 +31,7 @@
var/list/impacted_areas = list() //Areas to be affected by the weather, calculated when the weather begins
var/target_z = MAIN_STATION //The z-level to affect
var/list/protected_areas = list()//Areas that are protected and excluded from the affected areas.
-
+
var/overlay_layer = 10 //Since it's above everything else, this is the layer used by default. 2 is below mobs and walls if you need to use that.
var/aesthetic = FALSE //If the weather has no purpose other than looks
var/immunity_type = "storm" //Used by mobs to prevent them from being affected by the weather
@@ -70,7 +70,7 @@
to_chat(M, telegraph_message)
if(telegraph_sound)
M << sound(telegraph_sound)
- addtimer(src, "start", telegraph_duration)
+ addtimer(CALLBACK(src, .proc/start), telegraph_duration)
/datum/weather/proc/start()
if(stage >= MAIN_STAGE)
@@ -85,7 +85,7 @@
if(weather_sound)
M << sound(weather_sound)
weather_master.processing_weather |= src
- addtimer(src, "wind_down", weather_duration)
+ addtimer(CALLBACK(src, .proc/wind_down), weather_duration)
/datum/weather/proc/wind_down()
if(stage >= WIND_DOWN_STAGE)
@@ -100,7 +100,7 @@
if(end_sound)
M << sound(end_sound)
weather_master.processing_weather -= src
- addtimer(src, "end", end_duration)
+ addtimer(CALLBACK(src, .proc/end), end_duration)
/datum/weather/proc/end()
if(stage == END_STAGE)
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index d57199d5f91..1e931690333 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -242,7 +242,7 @@
/atom/proc/hitby(atom/movable/AM, skipcatch, hitpush, blocked)
if(density && !has_gravity(AM)) //thrown stuff bounces off dense stuff in no grav, unless the thrown stuff ends up inside what it hit(embedding, bola, etc...).
- addtimer(src, "hitby_react", 2, TRUE, AM)
+ addtimer(CALLBACK(src, .proc/hitby_react, AM), 2)
/atom/proc/hitby_react(atom/movable/AM)
if(AM && isturf(AM.loc))
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 3fdc402bb8a..55af5fae17b 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -176,11 +176,11 @@
/mob/living/forceMove(atom/destination)
if(buckled)
- addtimer(src, "check_buckled", 1, TRUE)
+ addtimer(CALLBACK(src, .proc/check_buckled), 1, TIMER_UNIQUE)
if(buckled_mob)
- addtimer(buckled_mob, "check_buckled", 1, TRUE)
+ addtimer(CALLBACK(buckled_mob, .proc/check_buckled), 1, TIMER_UNIQUE)
if(pulling)
- addtimer(src, "check_pull", 1, TRUE)
+ addtimer(CALLBACK(src, .proc/check_pull), 1, TIMER_UNIQUE)
. = ..()
if(client)
reset_perspective(destination)
diff --git a/code/game/gamemodes/changeling/powers/biodegrade.dm b/code/game/gamemodes/changeling/powers/biodegrade.dm
index 5e4b0e7fa12..3103416b9e4 100644
--- a/code/game/gamemodes/changeling/powers/biodegrade.dm
+++ b/code/game/gamemodes/changeling/powers/biodegrade.dm
@@ -18,7 +18,7 @@
return FALSE
user.visible_message("[user] vomits a glob of acid on \his [O]!", \
"We vomit acidic ooze onto our restraints!")
- addtimer(src, "dissolve_handcuffs", 30, FALSE, user, O)
+ addtimer(CALLBACK(src, .proc/dissolve_handcuffs, user, O), 30)
used = TRUE
if(user.wear_suit && user.wear_suit.breakouttime && !used)
@@ -27,7 +27,7 @@
return FALSE
user.visible_message("[user] vomits a glob of acid across the front of \his [S]!", \
"We vomit acidic ooze onto our straight jacket!")
- addtimer(src, "dissolve_straightjacket", 30, FALSE, user, S)
+ addtimer(CALLBACK(src, .proc/dissolve_straightjacket, user, S), 30)
used = TRUE
@@ -37,7 +37,7 @@
return FALSE
C.visible_message("[C]'s hinges suddenly begin to melt and run!")
to_chat(user, "We vomit acidic goop onto the interior of [C]!")
- addtimer(src, "open_closet", 70, FALSE, user, C)
+ addtimer(CALLBACK(src, .proc/open_closet, user, C), 70)
used = TRUE
if(istype(user.loc, /obj/structure/spider/cocoon) && !used)
@@ -46,7 +46,7 @@
return FALSE
C.visible_message("[src] shifts and starts to fall apart!")
to_chat(user, "We secrete acidic enzymes from our skin and begin melting our cocoon...")
- addtimer(src, "dissolve_cocoon", 25, FALSE, user, C) //Very short because it's just webs
+ addtimer(CALLBACK(src, .proc/dissolve_cocoon, user, C), 25) //Very short because it's just webs
used = TRUE
if(used)
diff --git a/code/game/gamemodes/changeling/powers/fakedeath.dm b/code/game/gamemodes/changeling/powers/fakedeath.dm
index 156e8ffb896..a166fd74502 100644
--- a/code/game/gamemodes/changeling/powers/fakedeath.dm
+++ b/code/game/gamemodes/changeling/powers/fakedeath.dm
@@ -17,7 +17,7 @@
user.status_flags |= FAKEDEATH //play dead
user.update_canmove()
- addtimer(src, "ready_to_regenerate", LING_FAKEDEATH_TIME, FALSE, user)
+ addtimer(CALLBACK(src, .proc/ready_to_regenerate, user), LING_FAKEDEATH_TIME)
feedback_add_details("changeling_powers","FD")
return 1
diff --git a/code/game/gamemodes/changeling/powers/fleshmend.dm b/code/game/gamemodes/changeling/powers/fleshmend.dm
index 08786b6d6db..56417ac4335 100644
--- a/code/game/gamemodes/changeling/powers/fleshmend.dm
+++ b/code/game/gamemodes/changeling/powers/fleshmend.dm
@@ -31,7 +31,7 @@
by quick repeated use!")
recent_uses++
- addtimer(src, "fleshmend", 0, FALSE, user)
+ INVOKE_ASYNC(src, .proc/fleshmend, user)
feedback_add_details("changeling_powers","RR")
return TRUE
diff --git a/code/game/gamemodes/malfunction/Malf_Modules.dm b/code/game/gamemodes/malfunction/Malf_Modules.dm
index 57ce00c92af..8f4b5dab95e 100644
--- a/code/game/gamemodes/malfunction/Malf_Modules.dm
+++ b/code/game/gamemodes/malfunction/Malf_Modules.dm
@@ -159,9 +159,8 @@
for(var/obj/machinery/door/D in airlocks)
if(!is_station_level(D.z))
continue
- spawn(0)
- D.hostile_lockdown(src)
- addtimer(D, "disable_lockdown", 900)
+ INVOKE_ASYNC(D, /obj/machinery/door.proc/hostile_lockdown, src)
+ addtimer(CALLBACK(D, /obj/machinery/door.proc/disable_lockdown), 900)
post_status("alert", "lockdown")
diff --git a/code/game/gamemodes/miniantags/borer/borer.dm b/code/game/gamemodes/miniantags/borer/borer.dm
index e9a830e7ed6..09d8a84539a 100644
--- a/code/game/gamemodes/miniantags/borer/borer.dm
+++ b/code/game/gamemodes/miniantags/borer/borer.dm
@@ -43,7 +43,8 @@
to_chat(B.host, "You feel the captive mind of [src] begin to resist your control.")
var/delay = (rand(350,450) + B.host.brainloss)
- addtimer(src, "return_control", delay, FALSE, B)
+ addtimer(CALLBACK(src, .proc/return_control, B), delay)
+
/mob/living/captive_brain/proc/return_control(mob/living/simple_animal/borer/B)
if(!B || !B.controlling)
@@ -534,7 +535,7 @@
leaving = TRUE
- addtimer(src, "let_go", 200)
+ addtimer(CALLBACK(src, .proc/let_go), 200)
/mob/living/simple_animal/borer/proc/let_go()
@@ -611,7 +612,7 @@
bonding = TRUE
var/delay = 300+(host.getBrainLoss()*5)
- addtimer(src, "assume_control", delay)
+ addtimer(CALLBACK(src, .proc/assume_control), delay)
/mob/living/simple_animal/borer/proc/assume_control()
if(!host || !src || controlling)
diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm
index baded609280..ce07e7bb920 100644
--- a/code/game/machinery/doors/airlock.dm
+++ b/code/game/machinery/doors/airlock.dm
@@ -118,7 +118,7 @@ About the new airlock wires panel:
/obj/machinery/door/airlock/initialize()
. = ..()
if(closeOtherId != null)
- addtimer(src, "update_other_id", 5)
+ addtimer(CALLBACK(src, .proc/update_other_id), 5)
if(glass)
airlock_material = "glass"
if(security_level > AIRLOCK_SECURITY_METAL)
@@ -226,13 +226,11 @@ About the new airlock wires panel:
/obj/machinery/door/airlock/proc/loseMainPower()
main_power_lost_until = mainPowerCablesCut() ? -1 : world.time + SecondsToTicks(60)
if(main_power_lost_until > 0)
- main_power_timer = addtimer(src, "regainMainPower", SecondsToTicks(60), 1)
-
+ main_power_timer = addtimer(CALLBACK(src, .proc/regainMainPower), SecondsToTicks(60), TIMER_UNIQUE | TIMER_STOPPABLE)
// If backup power is permanently disabled then activate in 10 seconds if possible, otherwise it's already enabled or a timer is already running
if(backup_power_lost_until == -1 && !backupPowerCablesCut())
backup_power_lost_until = world.time + SecondsToTicks(10)
- backup_power_timer = addtimer(src, "regainBackupPower", SecondsToTicks(10), 1)
-
+ backup_power_timer = addtimer(CALLBACK(src, .proc/regainBackupPower), SecondsToTicks(10), TIMER_UNIQUE | TIMER_STOPPABLE)
// Disable electricity if required
if(electrified_until && isAllPowerLoss())
electrify(0)
@@ -240,7 +238,7 @@ About the new airlock wires panel:
/obj/machinery/door/airlock/proc/loseBackupPower()
backup_power_lost_until = backupPowerCablesCut() ? -1 : world.time + SecondsToTicks(60)
if(backup_power_lost_until > 0)
- backup_power_timer = addtimer(src, "regainBackupPower", SecondsToTicks(60), 1)
+ backup_power_timer = addtimer(CALLBACK(src, .proc/regainBackupPower), SecondsToTicks(60), TIMER_UNIQUE | TIMER_STOPPABLE)
// Disable electricity if required
if(electrified_until && isAllPowerLoss())
@@ -288,8 +286,7 @@ About the new airlock wires panel:
message = "The door is now electrified [duration == -1 ? "permanently" : "for [duration] second\s"]."
electrified_until = duration == -1 ? -1 : world.time + SecondsToTicks(duration)
if(duration != -1)
- electrified_timer = addtimer(src, "electrify", SecondsToTicks(duration), 1, 0)
-
+ electrified_timer = addtimer(CALLBACK(src, .proc/electrify, 0), SecondsToTicks(duration), TIMER_UNIQUE | TIMER_STOPPABLE)
if(feedback && message)
to_chat(usr, message)
@@ -1080,8 +1077,7 @@ About the new airlock wires panel:
// The `addtimer` system has the advantage of being cancelable
if(autoclose)
- autoclose_timer = addtimer(src, "autoclose", normalspeed ? auto_close_time : auto_close_time_dangerous, unique = 1)
-
+ autoclose_timer = addtimer(CALLBACK(src, .proc/autoclose), normalspeed ? auto_close_time : auto_close_time_dangerous, TIMER_UNIQUE | TIMER_STOPPABLE)
return TRUE
/obj/machinery/door/airlock/close(forced=0, override = 0)
@@ -1098,7 +1094,7 @@ About the new airlock wires panel:
for(var/turf/turf in locs)
for(var/atom/movable/M in turf)
if(M.density && M != src) //something is blocking the door
- addtimer(src, "autoclose", 60)
+ addtimer(CALLBACK(src, .proc/autoclose), 60)
use_power(360) //360 W seems much more appropriate for an actuator moving an industrial door capable of crushing people
if(forced)
diff --git a/code/game/machinery/doors/airlock_types.dm b/code/game/machinery/doors/airlock_types.dm
index bff4bda660d..db78e8c82ef 100644
--- a/code/game/machinery/doors/airlock_types.dm
+++ b/code/game/machinery/doors/airlock_types.dm
@@ -150,13 +150,15 @@
/obj/machinery/door/airlock/uranium/New()
..()
- addtimer(src, "radiate", event_step)
+ addtimer(CALLBACK(src, .proc/radiate), event_step)
+
/obj/machinery/door/airlock/uranium/proc/radiate()
if(prob(50))
for(var/mob/living/L in range (3,src))
L.apply_effect(15,IRRADIATE,0)
- addtimer(src, "radiate", event_step)
+ addtimer(CALLBACK(src, .proc/radiate), event_step)
+
/obj/machinery/door/airlock/uranium/glass
opacity = 0
diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm
index 61a528ed93d..59297e04b30 100644
--- a/code/game/machinery/doors/door.dm
+++ b/code/game/machinery/doors/door.dm
@@ -279,7 +279,7 @@
// The `addtimer` system has the advantage of being cancelable
if(autoclose)
- autoclose_timer = addtimer(src, "autoclose", normalspeed ? auto_close_time : auto_close_time_dangerous, unique = 1)
+ autoclose_timer = addtimer(CALLBACK(src, .proc/autoclose), normalspeed ? auto_close_time : auto_close_time_dangerous, TIMER_UNIQUE | TIMER_STOPPABLE)
return TRUE
@@ -292,7 +292,7 @@
for(var/atom/movable/M in get_turf(src))
if(M.density && M != src) //something is blocking the door
if(autoclose)
- addtimer(src, "autoclose", 60)
+ addtimer(CALLBACK(src, .proc/autoclose), 60)
return
operating = TRUE
diff --git a/code/game/machinery/recycler.dm b/code/game/machinery/recycler.dm
index 13a2ba49ddd..d80a628927b 100644
--- a/code/game/machinery/recycler.dm
+++ b/code/game/machinery/recycler.dm
@@ -143,7 +143,7 @@ var/const/SAFETY_COOLDOWN = 100
safety_mode = 1
update_icon()
L.loc = loc
- addtimer(src, "reboot", SAFETY_COOLDOWN)
+ addtimer(CALLBACK(src, .proc/reboot), SAFETY_COOLDOWN)
/obj/machinery/recycler/proc/reboot()
playsound(loc, 'sound/machines/ping.ogg', 50, 0)
diff --git a/code/game/machinery/syndicatebomb.dm b/code/game/machinery/syndicatebomb.dm
index 48637f9e67f..a4c73cc8fac 100644
--- a/code/game/machinery/syndicatebomb.dm
+++ b/code/game/machinery/syndicatebomb.dm
@@ -438,7 +438,7 @@
chem_splash(get_turf(src), spread_range, list(reactants), temp_boost)
// Detonate it again in one second, until it's out of juice.
- addtimer(src, "detonate", 10)
+ addtimer(CALLBACK(src, .proc/detonate), 10)
// If it's not a time release bomb, do normal explosion
diff --git a/code/game/mecha/equipment/mecha_equipment.dm b/code/game/mecha/equipment/mecha_equipment.dm
index 98e7b6f342d..b990029423c 100644
--- a/code/game/mecha/equipment/mecha_equipment.dm
+++ b/code/game/mecha/equipment/mecha_equipment.dm
@@ -90,7 +90,7 @@
/obj/item/mecha_parts/mecha_equipment/proc/start_cooldown()
set_ready_state(0)
chassis.use_power(energy_drain)
- addtimer(src, "set_ready_state", equip_cooldown, FALSE, 1)
+ addtimer(CALLBACK(src, .proc/set_ready_state, 1), equip_cooldown)
/obj/item/mecha_parts/mecha_equipment/proc/do_after_cooldown(atom/target)
if(!chassis)
diff --git a/code/game/objects/effects/decals/Cleanable/humans.dm b/code/game/objects/effects/decals/Cleanable/humans.dm
index 02f89f6f0f6..f832b446cd5 100644
--- a/code/game/objects/effects/decals/Cleanable/humans.dm
+++ b/code/game/objects/effects/decals/Cleanable/humans.dm
@@ -44,7 +44,7 @@ var/global/list/image/splatter_cache=list()
if(B.blood_DNA)
blood_DNA |= B.blood_DNA.Copy()
qdel(B)
- dry_timer = addtimer(src, "dry", DRYING_TIME * (amount+1))
+ dry_timer = addtimer(CALLBACK(src, .proc/dry), DRYING_TIME * (amount+1), TIMER_STOPPABLE)
/obj/effect/decal/cleanable/blood/Destroy()
if(GAMEMODE_IS_CULT)
diff --git a/code/game/objects/effects/effect_system/effect_system.dm b/code/game/objects/effects/effect_system/effect_system.dm
index fa65008d307..7fb397297d7 100644
--- a/code/game/objects/effects/effect_system/effect_system.dm
+++ b/code/game/objects/effects/effect_system/effect_system.dm
@@ -51,7 +51,7 @@ would spawn and follow the beaker, even if it is carried or thrown.
for(var/i in 1 to number)
if(total_effects > 20)
return
- addtimer(src, "generate_effect", 0)
+ INVOKE_ASYNC(src, .proc/generate_effect)
/datum/effect_system/proc/generate_effect()
if(holder)
@@ -67,7 +67,7 @@ would spawn and follow the beaker, even if it is carried or thrown.
for(var/j in 1 to steps_amt)
sleep(5)
step(E,direction)
- addtimer(src, "decrement_total_effect", 20)
+ addtimer(CALLBACK(src, .proc/decrement_total_effect), 20)
/datum/effect_system/proc/decrement_total_effect()
total_effects--
diff --git a/code/game/objects/effects/effect_system/effects_explosion.dm b/code/game/objects/effects/effect_system/effects_explosion.dm
index aa3ecd8376b..5ad00157a02 100644
--- a/code/game/objects/effects/effect_system/effects_explosion.dm
+++ b/code/game/objects/effects/effect_system/effects_explosion.dm
@@ -58,4 +58,4 @@
/datum/effect_system/explosion/smoke/start()
..()
- addtimer(src, "create_smoke", 5)
+ addtimer(CALLBACK(src, .proc/create_smoke), 5)
diff --git a/code/game/objects/effects/effect_system/effects_smoke.dm b/code/game/objects/effects/effect_system/effects_smoke.dm
index 64421b35fe9..385067e0611 100644
--- a/code/game/objects/effects/effect_system/effects_smoke.dm
+++ b/code/game/objects/effects/effect_system/effects_smoke.dm
@@ -39,7 +39,7 @@
/obj/effect/particle_effect/smoke/proc/kill_smoke()
processing_objects.Remove(src)
- addtimer(src, "fade_out", 0)
+ INVOKE_ASYNC(src, .proc/fade_out)
QDEL_IN(src, 10)
/obj/effect/particle_effect/smoke/process()
@@ -67,7 +67,7 @@
if(C.smoke_delay)
return FALSE
C.smoke_delay++
- addtimer(src, "remove_smoke_delay", 10, FALSE, C)
+ addtimer(CALLBACK(src, .proc/remove_smoke_delay, C), 10)
return TRUE
/obj/effect/particle_effect/smoke/proc/remove_smoke_delay(mob/living/carbon/C)
diff --git a/code/game/objects/effects/glowshroom.dm b/code/game/objects/effects/glowshroom.dm
index 9ecafee37f0..4cf5e79cc77 100644
--- a/code/game/objects/effects/glowshroom.dm
+++ b/code/game/objects/effects/glowshroom.dm
@@ -72,7 +72,7 @@
else //if on the floor, glowshroom on-floor sprite
icon_state = "[base_icon_state]f"
- addtimer(src, "Spread", delay, FALSE)
+ addtimer(CALLBACK(src, .proc/Spread), delay)
/obj/structure/glowshroom/proc/Spread()
var/turf/ownturf = get_turf(src)
@@ -117,7 +117,7 @@
shrooms_planted++ //if we failed due to generation, don't try to plant one later
if(shrooms_planted < myseed.yield) //if we didn't get all possible shrooms planted, try again later
myseed.yield -= shrooms_planted
- addtimer(src, "Spread", delay, FALSE)
+ addtimer(CALLBACK(src, .proc/Spread), delay)
/obj/structure/glowshroom/proc/CalcDir(turf/location = loc)
var/direction = 16
diff --git a/code/game/objects/items/devices/flash.dm b/code/game/objects/items/devices/flash.dm
index 3f56932371d..0e2e3cc3785 100644
--- a/code/game/objects/items/devices/flash.dm
+++ b/code/game/objects/items/devices/flash.dm
@@ -208,7 +208,7 @@
to_chat(I.owner, "Your photon projector implant overheats and deactivates!")
I.Retract()
overheat = FALSE
- addtimer(src, "cooldown", flashcd * 2)
+ addtimer(CALLBACK(src, .proc/cooldown), flashcd * 2)
/obj/item/flash/armimplant/try_use_flash(mob/user = null)
if(overheat)
@@ -216,7 +216,7 @@
to_chat(I.owner, "Your photon projector is running too hot to be used again so quickly!")
return FALSE
overheat = TRUE
- addtimer(src, "cooldown", flashcd)
+ addtimer(CALLBACK(src, .proc/cooldown), flashcd)
playsound(src.loc, 'sound/weapons/flash.ogg', 100, 1)
update_icon(1)
return TRUE
diff --git a/code/game/objects/items/devices/radio/radio.dm b/code/game/objects/items/devices/radio/radio.dm
index d61aea153d7..e1ee7029439 100644
--- a/code/game/objects/items/devices/radio/radio.dm
+++ b/code/game/objects/items/devices/radio/radio.dm
@@ -270,7 +270,7 @@ var/global/list/default_medbay_channels = list(
universal_speak = 1
/mob/living/automatedannouncer/New()
- lifetime_timer = addtimer(src, "autocleanup", SecondsToTicks(10))
+ lifetime_timer = addtimer(CALLBACK(src, .proc/autocleanup), SecondsToTicks(10), TIMER_STOPPABLE)
..()
/mob/living/automatedannouncer/Destroy()
@@ -619,7 +619,7 @@ var/global/list/default_medbay_channels = list(
/obj/item/radio/emp_act(severity)
on = 0
disable_timer++
- addtimer(src, "enable_radio", rand(100, 200))
+ addtimer(CALLBACK(src, .proc/enable_radio), rand(100, 200))
if(listening)
visible_message("[src] buzzes violently!")
diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm
index 3382292bc3a..b321721b639 100644
--- a/code/game/objects/items/toys.dm
+++ b/code/game/objects/items/toys.dm
@@ -303,7 +303,7 @@
/obj/effect/decal/cleanable/ash/snappop_phoenix/New()
. = ..()
- addtimer(src, "respawn", respawn_time)
+ addtimer(CALLBACK(src, .proc/respawn), respawn_time)
/obj/effect/decal/cleanable/ash/snappop_phoenix/proc/respawn()
new /obj/item/toy/snappop/phoenix(get_turf(src))
diff --git a/code/game/objects/items/weapons/RCD.dm b/code/game/objects/items/weapons/RCD.dm
index b52a92112eb..fe132c107b0 100644
--- a/code/game/objects/items/weapons/RCD.dm
+++ b/code/game/objects/items/weapons/RCD.dm
@@ -385,7 +385,7 @@ RCD
buzz loudly!","[src] begins \
vibrating violently!")
// 5 seconds to get rid of it
- addtimer(src, "detonate_pulse_explode", 50)
+ addtimer(CALLBACK(src, .proc/detonate_pulse_explode), 50)
/obj/item/rcd/proc/detonate_pulse_explode()
explosion(src, 0, 0, 3, 1, flame_range = 1)
diff --git a/code/game/objects/items/weapons/explosives.dm b/code/game/objects/items/weapons/explosives.dm
index 381e6177bb2..6164ed01047 100644
--- a/code/game/objects/items/weapons/explosives.dm
+++ b/code/game/objects/items/weapons/explosives.dm
@@ -85,7 +85,7 @@
target.overlays += image_overlay
if(!nadeassembly)
to_chat(user, "You plant the bomb. Timer counting down from [det_time].")
- addtimer(src, "prime", det_time*10)
+ addtimer(CALLBACK(src, .proc/prime), det_time*10)
/obj/item/grenade/plastic/suicide_act(mob/user)
message_admins("[key_name_admin(user)](?) (FLW) suicided with [src.name] at ([user.x],[user.y],[user.z] - JMP)",0,1)
diff --git a/code/game/objects/items/weapons/grenades/chem_grenade.dm b/code/game/objects/items/weapons/grenades/chem_grenade.dm
index c6899d71c22..de85ec64a58 100644
--- a/code/game/objects/items/weapons/grenades/chem_grenade.dm
+++ b/code/game/objects/items/weapons/grenades/chem_grenade.dm
@@ -422,7 +422,7 @@
message_admins("grenade primed by an assembly, attached by [key_name_admin(M)](?) (FLW) and last touched by [key_name_admin(last)](?) (FLW) ([nadeassembly.a_left.name] and [nadeassembly.a_right.name]) at [A.name] (JMP).")
log_game("grenade primed by an assembly, attached by [key_name(M)] and last touched by [key_name(last)] ([nadeassembly.a_left.name] and [nadeassembly.a_right.name]) at [A.name] ([T.x], [T.y], [T.z])")
else
- addtimer(src, "prime", det_time)
+ addtimer(CALLBACK(src, .proc/prime), det_time)
var/turf/DT = get_turf(src)
var/area/DA = get_area(DT)
log_game("A grenade detonated at [DA.name] ([DT.x], [DT.y], [DT.z])")
diff --git a/code/game/objects/items/weapons/grenades/ghettobomb.dm b/code/game/objects/items/weapons/grenades/ghettobomb.dm
index d24f93736e5..d0dc2c8fe06 100644
--- a/code/game/objects/items/weapons/grenades/ghettobomb.dm
+++ b/code/game/objects/items/weapons/grenades/ghettobomb.dm
@@ -53,7 +53,7 @@
if(iscarbon(user))
var/mob/living/carbon/C = user
C.throw_mode_on()
- addtimer(src, "prime", det_time)
+ addtimer(CALLBACK(src, .proc/prime), det_time)
/obj/item/grenade/iedcasing/prime() //Blowing that can up
update_mob()
diff --git a/code/game/objects/items/weapons/legcuffs.dm b/code/game/objects/items/weapons/legcuffs.dm
index b238899676b..e6ba57ff8da 100644
--- a/code/game/objects/items/weapons/legcuffs.dm
+++ b/code/game/objects/items/weapons/legcuffs.dm
@@ -132,7 +132,7 @@
/obj/item/restraints/legcuffs/beartrap/energy/New()
..()
- addtimer(src, "dissipate", 100)
+ addtimer(CALLBACK(src, .proc/dissipate), 100)
/obj/item/restraints/legcuffs/beartrap/energy/proc/dissipate()
if(!ismob(loc))
diff --git a/code/game/objects/obj_defense.dm b/code/game/objects/obj_defense.dm
index 0738d57e8fb..fcd233318d5 100644
--- a/code/game/objects/obj_defense.dm
+++ b/code/game/objects/obj_defense.dm
@@ -162,7 +162,7 @@
being_shocked = TRUE
var/power_bounced = power * 0.5
tesla_zap(src, 3, power_bounced)
- addtimer(src, "reset_shocked", 10)
+ addtimer(CALLBACK(src, .proc/reset_shocked), 10)
/obj/proc/reset_shocked()
being_shocked = FALSE
diff --git a/code/game/objects/structures/barsign.dm b/code/game/objects/structures/barsign.dm
index 6c627bb40ce..d392deeec81 100644
--- a/code/game/objects/structures/barsign.dm
+++ b/code/game/objects/structures/barsign.dm
@@ -105,7 +105,7 @@
to_chat(user, "Nothing interesting happens!")
return
to_chat(user, "You emag the barsign. Takeover in progress...")
- addtimer(src, "post_emag", 100)
+ addtimer(CALLBACK(src, .proc/post_emag), 100)
/obj/structure/sign/barsign/proc/post_emag()
if(broken || emagged)
diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm
index 002bbb566d6..367be27b6dc 100644
--- a/code/game/objects/structures/tables_racks.dm
+++ b/code/game/objects/structures/tables_racks.dm
@@ -376,7 +376,7 @@
return
// Don't break if they're just flying past
if(AM.throwing)
- addtimer(src, "throw_check", 5, FALSE, AM)
+ addtimer(CALLBACK(src, .proc/throw_check, AM), 5)
else
check_break(AM)
diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm
index 8c473eab9e8..3ea23108662 100644
--- a/code/game/objects/structures/watercloset.dm
+++ b/code/game/objects/structures/watercloset.dm
@@ -343,9 +343,9 @@
mist_time = 70 //7 seconds on freezing temperature to disperse existing mist
if(watertemp == "boiling")
mist_time = 20 //2 seconds on boiling temperature to build up mist
- addtimer(src, "update_mist", mist_time)
+ addtimer(CALLBACK(src, .proc/update_mist), mist_time)
else
- addtimer(src, "update_mist", 250) //25 seconds for mist to disperse after being turned off
+ addtimer(CALLBACK(src, .proc/update_mist), 250) //25 seconds for mist to disperse after being turned off
/obj/machinery/shower/proc/update_mist()
if(on)
diff --git a/code/modules/arcade/mob_hunt/mob_avatar.dm b/code/modules/arcade/mob_hunt/mob_avatar.dm
index 42342e0693b..e4ea98a44a6 100644
--- a/code/modules/arcade/mob_hunt/mob_avatar.dm
+++ b/code/modules/arcade/mob_hunt/mob_avatar.dm
@@ -21,7 +21,7 @@
update_self()
forceMove(mob_info.spawn_point)
if(!mob_info.is_trap)
- addtimer(src, "despawn", mob_info.lifetime)
+ addtimer(CALLBACK(src, .proc/despawn), mob_info.lifetime)
/obj/effect/nanomob/proc/update_self()
if(!mob_info)
diff --git a/code/modules/awaymissions/mission_code/beach.dm b/code/modules/awaymissions/mission_code/beach.dm
index f285d4434da..4d2384ceb67 100644
--- a/code/modules/awaymissions/mission_code/beach.dm
+++ b/code/modules/awaymissions/mission_code/beach.dm
@@ -12,7 +12,7 @@
var/water_timer = 0
/obj/effect/waterfall/New()
- water_timer = addtimer(src, "drip", water_frequency)
+ water_timer = addtimer(CALLBACK(src, .proc/drip), water_frequency, TIMER_STOPPABLE)
/obj/effect/waterfall/Destroy()
if(water_timer)
@@ -25,4 +25,4 @@
W.dir = dir
spawn(1)
W.loc = get_step(W, dir)
- water_timer = addtimer(src, "drip", water_frequency)
\ No newline at end of file
+ water_timer = addtimer(CALLBACK(src, .proc/drip), water_frequency, TIMER_STOPPABLE)
\ No newline at end of file
diff --git a/code/modules/awaymissions/mission_code/spacehotel.dm b/code/modules/awaymissions/mission_code/spacehotel.dm
index d9299dfe061..e86123abc2e 100644
--- a/code/modules/awaymissions/mission_code/spacehotel.dm
+++ b/code/modules/awaymissions/mission_code/spacehotel.dm
@@ -238,7 +238,7 @@
return null
D.occupant = occupant
- D.roomtimer = addtimer(src, "process_room", PAY_INTERVAL, 0, roomid)
+ D.roomtimer = addtimer(CALLBACK(src, .proc/process_room, roomid), PAY_INTERVAL, TIMER_STOPPABLE)
vacant_rooms -= D
guests[occupant] = roomid
@@ -252,7 +252,7 @@
return
if(D.account.charge(100, transaction_purpose = "10 minutes", dest_name = name))
- D.roomtimer = addtimer(src, "process_room", PAY_INTERVAL, 0, roomid)
+ D.roomtimer = addtimer(CALLBACK(src, .proc/process_room, roomid), PAY_INTERVAL, TIMER_STOPPABLE)
else
force_checkout(roomid)
diff --git a/code/modules/flufftext/Dreaming.dm b/code/modules/flufftext/Dreaming.dm
index d01b5595d92..6a1d28eaf87 100644
--- a/code/modules/flufftext/Dreaming.dm
+++ b/code/modules/flufftext/Dreaming.dm
@@ -11,7 +11,7 @@
dream_images += pick_n_take(dreams)
dreaming++
for(var/i in 1 to dream_images.len)
- addtimer(src, "experience_dream", ((i - 1) * rand(30,60)), FALSE, dream_images[i], FALSE)
+ addtimer(CALLBACK(src, .proc/experience_dream, dream_images[i], FALSE), ((i - 1) * rand(30,60)))
return TRUE
@@ -33,7 +33,7 @@
dream_images += pick_n_take(nightmares)
nightmare++
for(var/i in 1 to dream_images.len)
- addtimer(src, "experience_dream", ((i - 1) * rand(30,60)), FALSE, nightmares[i], TRUE)
+ addtimer(CALLBACK(src, .proc/experience_dream, nightmares[i], TRUE), ((i - 1) * rand(30,60)))
return TRUE
/mob/living/carbon/proc/handle_dreams()
diff --git a/code/modules/flufftext/Hallucination.dm b/code/modules/flufftext/Hallucination.dm
index 621b8ec37ea..021db59f8a4 100644
--- a/code/modules/flufftext/Hallucination.dm
+++ b/code/modules/flufftext/Hallucination.dm
@@ -339,7 +339,7 @@ Gunshots/explosions/opening doors/less rare audio (done)
s.loc = get_step(get_turf(s), get_dir(s, target))
s.Show()
s.Eat()
- addtimer(src, "wake_and_restore", rand(50, 100))
+ addtimer(CALLBACK(src, .proc/wake_and_restore), rand(50, 100))
qdel(s)
/obj/effect/hallucination/simple/singularity
@@ -365,9 +365,9 @@ Gunshots/explosions/opening doors/less rare audio (done)
for(var/i=0,iERROR: APC access disabled, hack attempt canceled.")
- malfhacking = 0
- malfhack = null
+ deltimer(malfhacking)
+ // This proc handles cleanup of screen notifications and
+ // messenging the client
+ malfhacked(malfhack)
if(aiRestorePowerRoutine)
adjustOxyLoss(1)
diff --git a/code/modules/mob/living/simple_animal/bot/bot.dm b/code/modules/mob/living/simple_animal/bot/bot.dm
index c040fbe1ed2..986795ccd1e 100644
--- a/code/modules/mob/living/simple_animal/bot/bot.dm
+++ b/code/modules/mob/living/simple_animal/bot/bot.dm
@@ -193,6 +193,9 @@
bots_list -= src
QDEL_NULL(Radio)
QDEL_NULL(access_card)
+ if(reset_access_timer_id)
+ deltimer(reset_access_timer_id)
+ reset_access_timer_id = null
if(radio_controller && bot_filter)
radio_controller.remove_object(bot_core, control_freq)
QDEL_NULL(bot_core)
@@ -527,7 +530,7 @@ Pass a positive integer as an argument to override a bot's default speed.
turn_on() //Saves the AI the hassle of having to activate a bot manually.
access_card = all_access //Give the bot all-access while under the AI's command.
if(client)
- reset_access_timer_id = addtimer(src, "bot_reset", 600) //if the bot is player controlled, they get the extra access for a limited time
+ reset_access_timer_id = addtimer(CALLBACK (src, .proc/bot_reset), 600, TIMER_OVERRIDE|TIMER_STOPPABLE) //if the bot is player controlled, they get the extra access for a limited time
to_chat(src, "Priority waypoint set by [calling_ai] [caller]. Proceed to [end_area.name].
[path.len-1] meters to destination. You have been granted additional door access for 60 seconds.")
if(message)
to_chat(calling_ai, "[bicon(src)] [name] called to [end_area.name]. [path.len-1] meters to destination.")
diff --git a/code/modules/mob/living/simple_animal/hostile/mining/hivelord.dm b/code/modules/mob/living/simple_animal/hostile/mining/hivelord.dm
index e38ed7624ba..a026d33ed8d 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining/hivelord.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining/hivelord.dm
@@ -62,7 +62,7 @@
/obj/item/organ/internal/hivelord_core/New()
..()
- addtimer(src, "inert_check", 2400)
+ addtimer(CALLBACK(src, .proc/inert_check), 2400)
/obj/item/organ/internal/hivelord_core/proc/inert_check()
if(!owner && !preserved)
@@ -151,7 +151,7 @@
/mob/living/simple_animal/hostile/asteroid/hivelordbrood/New()
..()
- addtimer(src, "death", 100)
+ addtimer(CALLBACK(src, .proc/death), 100)
/mob/living/simple_animal/hostile/asteroid/hivelordbrood/blood
name = "blood brood"
diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/terror_spiders.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/terror_spiders.dm
index 2a1e0f17ef4..cd01006546c 100644
--- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/terror_spiders.dm
+++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/terror_spiders.dm
@@ -270,8 +270,8 @@ var/global/list/ts_spiderling_list = list()
else
ts_count_alive_station++
// after 30 seconds, assuming nobody took control of it yet, offer it to ghosts.
- addtimer(src, "CheckFaction", 150)
- addtimer(src, "announcetoghosts", 300)
+ addtimer(CALLBACK(src, .proc/CheckFaction), 150)
+ addtimer(CALLBACK(src, .proc/announcetoghosts), 300)
var/datum/atom_hud/U = huds[DATA_HUD_MEDICAL_ADVANCED]
U.add_hud_to(src)
diff --git a/code/modules/mob/living/simple_animal/hostile/venus_human_trap.dm b/code/modules/mob/living/simple_animal/hostile/venus_human_trap.dm
index 9e6396dbde0..57412eb5a0d 100644
--- a/code/modules/mob/living/simple_animal/hostile/venus_human_trap.dm
+++ b/code/modules/mob/living/simple_animal/hostile/venus_human_trap.dm
@@ -22,7 +22,7 @@
for(var/turf/T in anchors)
var/datum/beam/B = Beam(T, "vine", time=INFINITY, maxdistance=5, beam_type=/obj/effect/ebeam/vine)
B.sleep_time = 10 //these shouldn't move, so let's slow down updates to 1 second (any slower and the deletion of the vines would be too slow)
- addtimer(src, "bear_fruit", growth_time)
+ addtimer(CALLBACK(src, .proc/bear_fruit), growth_time)
/obj/structure/alien/resin/flower_bud_enemy/proc/bear_fruit()
visible_message("the plant has borne fruit!")
diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm
index 23c6e2a1f15..9c3d64ef4fc 100644
--- a/code/modules/power/apc.dm
+++ b/code/modules/power/apc.dm
@@ -1004,8 +1004,7 @@
return
to_chat(malf, "Beginning override of APC systems. This takes some time, and you cannot perform other actions during the process.")
malf.malfhack = src
- malf.malfhacking = addtimer(malf, "malfhacked", 600, FALSE, src)
-
+ malf.malfhacking = addtimer(CALLBACK(malf, /mob/living/silicon/ai/.proc/malfhacked, src), 600, TIMER_STOPPABLE)
var/obj/screen/alert/hackingapc/A
A = malf.throw_alert("hackingapc", /obj/screen/alert/hackingapc)
A.target = src
diff --git a/code/modules/power/tesla/coil.dm b/code/modules/power/tesla/coil.dm
index 8bca07ccd45..aa25105cba6 100644
--- a/code/modules/power/tesla/coil.dm
+++ b/code/modules/power/tesla/coil.dm
@@ -66,7 +66,7 @@
flick("coilhit", src)
playsound(src.loc, 'sound/magic/LightningShock.ogg', 100, 1, extrarange = 5)
tesla_zap(src, 5, power_produced)
- addtimer(src, "reset_shocked", 10)
+ addtimer(CALLBACK(src, .proc/reset_shocked), 10)
else
..()
diff --git a/code/modules/power/tesla/energy_ball.dm b/code/modules/power/tesla/energy_ball.dm
index f2ffbb9b857..9cfd526585e 100644
--- a/code/modules/power/tesla/energy_ball.dm
+++ b/code/modules/power/tesla/energy_ball.dm
@@ -83,7 +83,7 @@
energy_to_raise = energy_to_raise * 1.25
playsound(src.loc, 'sound/magic/lightning_chargeup.ogg', 100, 1, extrarange = 30)
- addtimer(src, "new_mini_ball", 100)
+ addtimer(CALLBACK(src, .proc/new_mini_ball), 100)
else if(energy < energy_to_lower && orbiting_balls.len)
energy_to_raise = energy_to_raise / 1.25
diff --git a/code/modules/research/rdconsole.dm b/code/modules/research/rdconsole.dm
index 05038017aec..50c8e1a4cf8 100644
--- a/code/modules/research/rdconsole.dm
+++ b/code/modules/research/rdconsole.dm
@@ -887,7 +887,7 @@ won't update every console in existence) but it's more of a hassle to do. Also,
//helper proc that guarantees the wait message will not freeze the UI
/obj/machinery/computer/rdconsole/proc/add_wait_message(message, delay)
wait_message = message
- wait_message_timer = addtimer(src, "clear_wait_message", delay, TRUE)
+ wait_message_timer = addtimer(CALLBACK(src, .proc/clear_wait_message), delay, TIMER_UNIQUE | TIMER_STOPPABLE)
// This is here to guarantee that we never lock the console, so long as the timer
// process is running
diff --git a/code/modules/shuttle/on_move.dm b/code/modules/shuttle/on_move.dm
index 342931a065c..88551f49096 100644
--- a/code/modules/shuttle/on_move.dm
+++ b/code/modules/shuttle/on_move.dm
@@ -17,18 +17,18 @@
return 0
/obj/machinery/door/airlock/onShuttleMove()
- . = ..()
- if(!.)
- return
- addtimer(src, "close", 0, TRUE, 0, 1)
- // Close any nearby airlocks as well
- for(var/obj/machinery/door/airlock/D in orange(1, src))
- addtimer(D, "close", 0, TRUE, 0, 1)
+ . = ..()
+ if(!.)
+ return
+ INVOKE_ASYNC(src, .proc/close, 0, 1)
+ // Close any nearby airlocks as well
+ for(var/obj/machinery/door/airlock/D in orange(1, src))
+ INVOKE_ASYNC(D, .proc/close, 0, 1)
/obj/machinery/door/airlock/onShuttleMove()
- . = ..()
- if(id_tag == "s_docking_airlock")
- addtimer(src, "lock", 0, TRUE)
+ . = ..()
+ if(id_tag == "s_docking_airlock")
+ INVOKE_ASYNC(src, .proc/lock)
/mob/onShuttleMove()
if(!move_on_shuttle)
@@ -57,7 +57,7 @@
/obj/machinery/door/airlock/postDock(obj/docking_port/stationary/S1)
. = ..()
if(!S1.lock_shuttle_doors && id_tag == "s_docking_airlock")
- addtimer(src, "unlock", 0, TRUE)
+ INVOKE_ASYNC(src, .proc/unlock)
// Shuttle Rotation //
/atom/proc/shuttleRotate(rotation)
diff --git a/code/modules/surgery/organs/augments_arms.dm b/code/modules/surgery/organs/augments_arms.dm
index 1290212a249..d98e40700df 100644
--- a/code/modules/surgery/organs/augments_arms.dm
+++ b/code/modules/surgery/organs/augments_arms.dm
@@ -290,7 +290,7 @@
if(H.nutrition >= NUTRITION_LEVEL_WELL_FED)
to_chat(user, "You are already fully charged!")
else
- addtimer(src, "powerdraw_loop", 0, TRUE, A, H)
+ INVOKE_ASYNC(src, .proc/powerdraw_loop, A, H)
else
to_chat(user, "There is no charge to draw from that APC.")
else
diff --git a/code/modules/surgery/organs/blood.dm b/code/modules/surgery/organs/blood.dm
index 88e8341d37c..9f732fdef4d 100644
--- a/code/modules/surgery/organs/blood.dm
+++ b/code/modules/surgery/organs/blood.dm
@@ -7,7 +7,7 @@
return
else
bleedsuppress = TRUE
- addtimer(src, "resume_bleeding", amount)
+ addtimer(CALLBACK(src, .proc/resume_bleeding), amount)
/mob/living/carbon/human/proc/resume_bleeding()
bleedsuppress = FALSE
diff --git a/code/modules/telesci/gps.dm b/code/modules/telesci/gps.dm
index c8ed52ec787..467f92d095e 100644
--- a/code/modules/telesci/gps.dm
+++ b/code/modules/telesci/gps.dm
@@ -26,7 +26,7 @@ var/list/GPS_list = list()
emped = 1
overlays -= "working"
overlays += "emp"
- addtimer(src, "reboot", 300)
+ addtimer(CALLBACK(src, .proc/reboot), 300)
/obj/item/gps/proc/reboot()
emped = FALSE
diff --git a/paradise.dme b/paradise.dme
index 911114e7912..8944ad71a48 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -200,7 +200,6 @@
#include "code\controllers\Processes\obj.dm"
#include "code\controllers\Processes\shuttles.dm"
#include "code\controllers\Processes\ticker.dm"
-#include "code\controllers\Processes\timer.dm"
#include "code\controllers\Processes\weather.dm"
#include "code\controllers\ProcessScheduler\core\process.dm"
#include "code\controllers\ProcessScheduler\core\processScheduler.dm"
@@ -212,6 +211,7 @@
#include "code\controllers\subsystem\spacedrift.dm"
#include "code\controllers\subsystem\sun.dm"
#include "code\controllers\subsystem\throwing.dm"
+#include "code\controllers\subsystem\timer.dm"
#include "code\datums\action.dm"
#include "code\datums\ai_law_sets.dm"
#include "code\datums\ai_laws.dm"