diff --git a/code/__defines/_compile_options.dm b/code/__defines/_compile_options.dm
index a674ec62be..50ff3fbf2b 100644
--- a/code/__defines/_compile_options.dm
+++ b/code/__defines/_compile_options.dm
@@ -22,3 +22,21 @@
#define USING_MAP_DATUM /datum/map
#define MAP_OVERRIDE 1
#endif
+
+///Used to find the sources of harddels, quite laggy, don't be surpised if it freezes your client for a good while
+//#define REFERENCE_TRACKING
+#ifdef REFERENCE_TRACKING
+
+///Should we be logging our findings or not
+#define REFERENCE_TRACKING_LOG
+
+///Used for doing dry runs of the reference finder, to test for feature completeness
+//#define REFERENCE_TRACKING_DEBUG
+
+///Run a lookup on things hard deleting by default.
+//#define GC_FAILURE_HARD_LOOKUP
+#ifdef GC_FAILURE_HARD_LOOKUP
+#define FIND_REF_NO_CHECK_TICK
+#endif //ifdef GC_FAILURE_HARD_LOOKUP
+
+#endif //ifdef REFERENCE_TRACKING
diff --git a/code/__defines/flags.dm b/code/__defines/flags.dm
index 8ac15ebc53..0d06544a7e 100644
--- a/code/__defines/flags.dm
+++ b/code/__defines/flags.dm
@@ -34,6 +34,7 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204
#define PHORONGUARD (1<<5) // Does not get contaminated by phoron.
#define NOREACT (1<<6) // Reagents don't react inside this container.
#define OVERLAY_QUEUED (1<<7)// Atom queued to SSoverlay for COMPILE_OVERLAYS
+#define IS_BUSY (1<<8) // Atom has a TASK_TARGET_EXCLUSIVE do_after with it as the target.
//Flags for items (equipment) - Used in /obj/item/var/item_flags
#define THICKMATERIAL (1<<0) // Prevents syringes, parapens and hyposprays if equipped to slot_suit or slot_head.
@@ -49,3 +50,8 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204
#define PASSGRILLE (1<<2)
#define PASSBLOB (1<<3)
#define PASSMOB (1<<4)
+
+// Flags for do_after/do_mob exclusivity.
+#define TASK_TARGET_EXCLUSIVE (1<<1)
+#define TASK_USER_EXCLUSIVE (1<<2)
+#define TASK_ALL_EXCLUSIVE TASK_TARGET_EXCLUSIVE | TASK_USER_EXCLUSIVE
\ No newline at end of file
diff --git a/code/__defines/qdel.dm b/code/__defines/qdel.dm
index 7374438901..12002dc46e 100644
--- a/code/__defines/qdel.dm
+++ b/code/__defines/qdel.dm
@@ -1,22 +1,36 @@
//defines that give qdel hints. these can be given as a return in destory() or by calling
-#define QDEL_HINT_QUEUE 0 //qdel should queue the object for deletion.
-#define QDEL_HINT_LETMELIVE 1 //qdel should let the object live after calling destory.
-#define QDEL_HINT_IWILLGC 2 //functionally the same as the above. qdel should assume the object will gc on its own, and not check it.
-#define QDEL_HINT_HARDDEL 3 //qdel should assume this object won't gc, and queue a hard delete using a hard reference.
-#define QDEL_HINT_HARDDEL_NOW 4 //qdel should assume this object won't gc, and hard del it post haste.
-#define QDEL_HINT_FINDREFERENCE 5 //functionally identical to QDEL_HINT_QUEUE if TESTING is not enabled in _compiler_options.dm.
- //if TESTING is enabled, qdel will call this object's find_references() verb.
-//defines for the gc_destroyed var
+/// `qdel` should queue the object for deletion.
+#define QDEL_HINT_QUEUE 0
+/// `qdel` should let the object live after calling [/atom/proc/Destroy].
+#define QDEL_HINT_LETMELIVE 1
+/// Functionally the same as the above. `qdel` should assume the object will gc on its own, and not check it.
+#define QDEL_HINT_IWILLGC 2
+/// Qdel should assume this object won't GC, and queue a hard delete using a hard reference.
+#define QDEL_HINT_HARDDEL 3
+// Qdel should assume this object won't gc, and hard delete it posthaste.
+#define QDEL_HINT_HARDDEL_NOW 4
-#define GC_QUEUE_PREQUEUE 1
-#define GC_QUEUE_CHECK 2
-#define GC_QUEUE_HARDDELETE 3
-#define GC_QUEUE_COUNT 3 //increase this when adding more steps.
+#ifdef REFERENCE_TRACKING
+/** If REFERENCE_TRACKING is enabled, qdel will call this object's find_references() verb.
+ *
+ * Functionally identical to [QDEL_HINT_QUEUE] if [GC_FAILURE_HARD_LOOKUP] is not enabled in _compiler_options.dm.
+*/
+#define QDEL_HINT_FINDREFERENCE 5
+/// Behavior as [QDEL_HINT_FINDREFERENCE], but only if the GC fails and a hard delete is forced.
+#define QDEL_HINT_IFFAIL_FINDREFERENCE 6
+#endif
-#define GC_QUEUED_FOR_QUEUING -1
-#define GC_QUEUED_FOR_HARD_DEL -2
-#define GC_CURRENTLY_BEING_QDELETED -3
+#define GC_QUEUE_CHECK 1
+#define GC_QUEUE_HARDDELETE 2
+#define GC_QUEUE_COUNT 2 //increase this when adding more steps.
+
+#define QDEL_ITEM_ADMINS_WARNED (1<<0) //! Set when admins are told about lag causing qdels in this type.
+#define QDEL_ITEM_SUSPENDED_FOR_LAG (1<<1) //! Set when a type can no longer be hard deleted on failure because of lag it causes while this happens.
+
+// Defines for the [gc_destroyed][/datum/var/gc_destroyed] var.
+#define GC_QUEUED_FOR_QUEUING -1
+#define GC_CURRENTLY_BEING_QDELETED -2
#define QDELING(X) (X.gc_destroyed)
#define QDELETED(X) (!X || X.gc_destroyed)
diff --git a/code/__defines/subsystems.dm b/code/__defines/subsystems.dm
index e5fb740a8e..1d09ecc799 100644
--- a/code/__defines/subsystems.dm
+++ b/code/__defines/subsystems.dm
@@ -54,6 +54,7 @@ var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_G
// The numbers just define the ordering, they are meaningless otherwise.
#define INIT_ORDER_WEBHOOKS 50
#define INIT_ORDER_SQLITE 40
+#define INIT_ORDER_GARBAGE 39
#define INIT_ORDER_MEDIA_TRACKS 38 // Gotta get that lobby music up, yo
#define INIT_ORDER_INPUT 37
#define INIT_ORDER_CHEMISTRY 35
diff --git a/code/_helpers/logging.dm b/code/_helpers/logging.dm
index ba13bb5b48..91908eb514 100644
--- a/code/_helpers/logging.dm
+++ b/code/_helpers/logging.dm
@@ -96,7 +96,6 @@
speaker.dialogue_log += "([time_stamp()]) ([speaker]/[speaker.client]) SAY: - [text]"
GLOB.round_text_log += "([time_stamp()]) ([speaker]/[speaker.client]) SAY: - [text]"
-
/proc/log_emote(text, mob/speaker)
if (config.log_emote)
WRITE_LOG(diary, "EMOTE: [speaker.simple_info_line()]: [html_decode(text)]")
@@ -128,7 +127,6 @@
speaker.dialogue_log += "([time_stamp()]) ([speaker]/[speaker.client]) DEADSAY: - [text]"
GLOB.round_text_log += "([time_stamp()]) ([speaker]/[speaker.client]) DEADSAY: - [text]"
-
/proc/log_ghostemote(text, mob/speaker)
if (config.log_emote)
WRITE_LOG(diary, "DEADEMOTE: [speaker.simple_info_line()]: [html_decode(text)]")
@@ -144,7 +142,6 @@
speaker.dialogue_log += "([time_stamp()]) ([speaker]/[speaker.client]) MSG: - [text]"
GLOB.round_text_log += "([time_stamp()]) ([speaker]/[speaker.client]) MSG: - [text]"
-
/proc/log_to_dd(text)
to_world_log(text) //this comes before the config check because it can't possibly runtime
if(config.log_world_output)
@@ -168,6 +165,12 @@
/proc/log_unit_test(text)
to_world_log("## UNIT_TEST: [text]")
+#ifdef REFERENCE_TRACKING_LOG
+#define log_reftracker(msg) log_world("## REF SEARCH [msg]")
+#else
+#define log_reftracker(msg)
+#endif
+
/proc/log_tgui(user_or_client, text)
var/entry = ""
if(!user_or_client)
diff --git a/code/_helpers/mobs.dm b/code/_helpers/mobs.dm
index 684aa40dae..6640f3670d 100644
--- a/code/_helpers/mobs.dm
+++ b/code/_helpers/mobs.dm
@@ -144,12 +144,15 @@ Proc for attack log creation, because really why not
/proc/do_mob(mob/user , mob/target, time = 30, target_zone = 0, uninterruptible = FALSE, progress = TRUE, ignore_movement = FALSE, exclusive = FALSE)
if(!user || !target)
- return 0
+ return FALSE
if(!time)
- return 1 //Done!
+ return TRUE //Done!
if(user.status_flags & DOING_TASK)
to_chat(user, "You're in the middle of doing something else already.")
- return 0 //Performing an exclusive do_after or do_mob already
+ return FALSE //Performing an exclusive do_after or do_mob already
+ if(target?.flags & IS_BUSY)
+ to_chat(user, "Someone is already doing something with \the [target].")
+ return FALSE
var/user_loc = user.loc
var/target_loc = target.loc
@@ -161,8 +164,10 @@ Proc for attack log creation, because really why not
var/endtime = world.time+time
var/starttime = world.time
- if(exclusive)
+ if(exclusive & TASK_USER_EXCLUSIVE)
user.status_flags |= DOING_TASK
+ if(target && exclusive & TASK_TARGET_EXCLUSIVE)
+ target.flags |= IS_BUSY
. = TRUE
while (world.time < endtime)
@@ -195,20 +200,26 @@ Proc for attack log creation, because really why not
. = FALSE
break
- if(exclusive)
+ if(exclusive & TASK_USER_EXCLUSIVE)
user.status_flags &= ~DOING_TASK
+ if(exclusive & TASK_TARGET_EXCLUSIVE)
+ target?.status_flags &= ~IS_BUSY
if (progbar)
qdel(progbar)
/proc/do_after(mob/user, delay, atom/target = null, needhand = TRUE, progress = TRUE, incapacitation_flags = INCAPACITATION_DEFAULT, ignore_movement = FALSE, max_distance = null, exclusive = FALSE)
if(!user)
- return 0
+ return FALSE
if(!delay)
- return 1 //Okay. Done.
+ return TRUE //Okay. Done.
if(user.status_flags & DOING_TASK)
to_chat(user, "You're in the middle of doing something else already.")
- return 0 //Performing an exclusive do_after or do_mob already
+ return FALSE //Performing an exclusive do_after or do_mob already
+ if(target?.flags & IS_BUSY)
+ to_chat(user, "Someone is already doing something with \the [target].")
+ return FALSE
+
var/atom/target_loc = null
if(target)
target_loc = target.loc
@@ -230,10 +241,13 @@ Proc for attack log creation, because really why not
var/endtime = world.time + delay
var/starttime = world.time
- if(exclusive)
+ if(exclusive & TASK_USER_EXCLUSIVE)
user.status_flags |= DOING_TASK
+
+ if(target && exclusive & TASK_TARGET_EXCLUSIVE)
+ target.flags |= IS_BUSY
- . = 1
+ . = TRUE
while (world.time < endtime)
stoplag(1)
if(progress)
@@ -269,8 +283,10 @@ Proc for attack log creation, because really why not
. = FALSE
break
- if(exclusive)
+ if(exclusive & TASK_USER_EXCLUSIVE)
user.status_flags &= ~DOING_TASK
+ if(target & exclusive & TASK_TARGET_EXCLUSIVE)
+ target.flags &= ~IS_BUSY
if(progbar)
qdel(progbar)
diff --git a/code/_helpers/time.dm b/code/_helpers/time.dm
index 167af12ab1..5712cb27c7 100644
--- a/code/_helpers/time.dm
+++ b/code/_helpers/time.dm
@@ -1,3 +1,6 @@
+#define MILISECOND * 0.01
+#define MILLISECONDS * 0.01
+
#define SECOND *10
#define SECONDS *10
@@ -20,6 +23,10 @@
#define TICKS2DS(T) ((T) TICKS) // Convert ticks to deciseconds
#define DS2NEARESTTICK(DS) TICKS2DS(-round(-(DS2TICKS(DS))))
+#define MS2DS(T) ((T) MILLISECONDS)
+
+#define DS2MS(T) ((T) * 100)
+
var/world_startup_time
/proc/get_game_time()
@@ -44,7 +51,7 @@ var/next_station_date_change = 1 DAY
#define duration2stationtime(time) time2text(station_time_in_ds + time, "hh:mm")
#define worldtime2stationtime(time) time2text(GLOB.roundstart_hour HOURS + time, "hh:mm")
-#define round_duration_in_ds (GLOB.round_start_time ? world.time - GLOB.round_start_time : 0)
+#define round_duration_in_ds (GLOB.round_start_time ? REALTIMEOFDAY - GLOB.round_start_time : 0)
#define station_time_in_ds (GLOB.roundstart_hour HOURS + round_duration_in_ds)
/proc/stationtime2text()
@@ -56,9 +63,7 @@ var/next_station_date_change = 1 DAY
next_station_date_change += 1 DAY
update_time = TRUE
if(!station_date || update_time)
- var/extra_days = round(station_time_in_ds / (1 DAY)) DAYS
- var/timeofday = world.timeofday + extra_days
- station_date = num2text((text2num(time2text(timeofday, "YYYY"))+544)) + "-" + time2text(timeofday, "MM-DD") //YW EDIT
+ station_date = num2text((text2num(time2text(REALTIMEOFDAY, "YYYY"))+544)) + "-" + time2text(REALTIMEOFDAY, "MM-DD") //YW EDIT
return station_date
//ISO 8601
@@ -96,7 +101,7 @@ var/last_round_duration = 0
GLOBAL_VAR_INIT(round_start_time, 0)
/hook/roundstart/proc/start_timer()
- GLOB.round_start_time = world.time
+ GLOB.round_start_time = REALTIMEOFDAY
return 1
/proc/roundduration2text()
@@ -121,7 +126,8 @@ GLOBAL_VAR_INIT(round_start_time, 0)
/var/rollovercheck_last_timeofday = 0
/proc/update_midnight_rollover()
if (world.timeofday < rollovercheck_last_timeofday) //TIME IS GOING BACKWARDS!
- return midnight_rollovers++
+ midnight_rollovers += 1
+ rollovercheck_last_timeofday = world.timeofday
return midnight_rollovers
//Increases delay as the server gets more overloaded,
diff --git a/code/controllers/subsystems/garbage.dm b/code/controllers/subsystems/garbage.dm
index 9da198fdaf..422fa6743f 100644
--- a/code/controllers/subsystems/garbage.dm
+++ b/code/controllers/subsystems/garbage.dm
@@ -1,33 +1,32 @@
-//
-// Garbage Collector Subsystem - Implements qdel() and the GC queue
-//
SUBSYSTEM_DEF(garbage)
name = "Garbage"
priority = FIRE_PRIORITY_GARBAGE
wait = 2 SECONDS
flags = SS_POST_FIRE_TIMING|SS_BACKGROUND|SS_NO_INIT
runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY
+ init_order = INIT_ORDER_GARBAGE
- var/list/collection_timeout = list(0, 2 MINUTES, 10 SECONDS) // deciseconds to wait before moving something up in the queue to the next level
+ var/list/collection_timeout = list(2 MINUTES, 10 SECONDS) // deciseconds to wait before moving something up in the queue to the next level
- var/delslasttick = 0 // number of del()'s we've done this tick
- var/gcedlasttick = 0 // number of things that gc'ed last tick
+ //Stat tracking
+ var/delslasttick = 0 // number of del()'s we've done this tick
+ var/gcedlasttick = 0 // number of things that gc'ed last tick
var/totaldels = 0
var/totalgcs = 0
- var/highest_del_time = 0
- var/highest_del_tickusage = 0
+ var/highest_del_ms = 0
+ var/highest_del_type_string = ""
var/list/pass_counts
var/list/fail_counts
- var/list/items = list() // Holds our qdel_item statistics datums
+ var/list/items = list() // Holds our qdel_item statistics datums
- // List of Queues
- // Each queue is a list of refID's of things that should be garbage collected
- // refID's are associated with the time at which they time out and need to be manually del()
- // we do this so we aren't constantly locating them and preventing them from being gc'd
+ //Queue
var/list/queues
+ #ifdef REFERENCE_TRACKING
+ var/list/reference_find_on_fail = list()
+ #endif
/datum/controller/subsystem/garbage/PreInit()
@@ -57,7 +56,7 @@ SUBSYSTEM_DEF(garbage)
msg += "TGR:[round((totalgcs/(totaldels+totalgcs))*100, 0.01)]%"
msg += " P:[pass_counts.Join(",")]"
msg += "|F:[fail_counts.Join(",")]"
- ..(msg)
+ return ..()
/datum/controller/subsystem/garbage/Shutdown()
//Adds the del() log to the qdel log file
@@ -68,13 +67,18 @@ SUBSYSTEM_DEF(garbage)
for(var/path in items)
var/datum/qdel_item/I = items[path]
dellog += "Path: [path]"
+ if (I.qdel_flags & QDEL_ITEM_SUSPENDED_FOR_LAG)
+ dellog += "\tSUSPENDED FOR LAG"
if (I.failures)
dellog += "\tFailures: [I.failures]"
dellog += "\tqdel() Count: [I.qdels]"
dellog += "\tDestroy() Cost: [I.destroy_time]ms"
if (I.hard_deletes)
- dellog += "\tTotal Hard Deletes [I.hard_deletes]"
+ dellog += "\tTotal Hard Deletes: [I.hard_deletes]"
dellog += "\tTime Spent Hard Deleting: [I.hard_delete_time]ms"
+ dellog += "\tHighest Time Spent Hard Deleting: [I.hard_delete_max]ms"
+ if (I.hard_deletes_over_threshold)
+ dellog += "\tHard Deletes Over Threshold: [I.hard_deletes_over_threshold]"
if (I.slept_destroy)
dellog += "\tSleeps: [I.slept_destroy]"
if (I.no_respect_force)
@@ -85,42 +89,19 @@ SUBSYSTEM_DEF(garbage)
/datum/controller/subsystem/garbage/fire()
//the fact that this resets its processing each fire (rather then resume where it left off) is intentional.
- var/queue = GC_QUEUE_PREQUEUE
+ var/queue = GC_QUEUE_CHECK
while (state == SS_RUNNING)
switch (queue)
- if (GC_QUEUE_PREQUEUE)
- HandlePreQueue()
- queue = GC_QUEUE_PREQUEUE+1
if (GC_QUEUE_CHECK)
HandleQueue(GC_QUEUE_CHECK)
queue = GC_QUEUE_CHECK+1
if (GC_QUEUE_HARDDELETE)
HandleQueue(GC_QUEUE_HARDDELETE)
+ if (state == SS_PAUSED) //make us wait again before the next run.
+ state = SS_RUNNING
break
- if (state == SS_PAUSED) //make us wait again before the next run.
- state = SS_RUNNING
-
-//If you see this proc high on the profile, what you are really seeing is the garbage collection/soft delete overhead in byond.
-//Don't attempt to optimize, not worth the effort.
-/datum/controller/subsystem/garbage/proc/HandlePreQueue()
- var/list/tobequeued = queues[GC_QUEUE_PREQUEUE]
- var/static/count = 0
- if (count)
- var/c = count
- count = 0 //so if we runtime on the Cut, we don't try again.
- tobequeued.Cut(1,c+1)
-
- for (var/ref in tobequeued)
- count++
- Queue(ref, GC_QUEUE_PREQUEUE+1)
- if (MC_TICK_CHECK)
- break
- if (count)
- tobequeued.Cut(1,count+1)
- count = 0
-
/datum/controller/subsystem/garbage/proc/HandleQueue(level = GC_QUEUE_CHECK)
if (level == GC_QUEUE_CHECK)
delslasttick = 0
@@ -137,18 +118,21 @@ SUBSYSTEM_DEF(garbage)
lastlevel = level
- for (var/refID in queue)
- if (!refID)
+ //We do this rather then for(var/refID in queue) because that sort of for loop copies the whole list.
+ //Normally this isn't expensive, but the gc queue can grow to 40k items, and that gets costly/causes overrun.
+ for (var/i in 1 to length(queue))
+ var/list/L = queue[i]
+ if (length(L) < 2)
count++
if (MC_TICK_CHECK)
- break
+ return
continue
- var/GCd_at_time = queue[refID]
+ var/GCd_at_time = L[1]
if(GCd_at_time > cut_off_time)
break // Everything else is newer, skip them
count++
-
+ var/refID = L[2]
var/datum/D
D = locate(refID)
@@ -156,50 +140,74 @@ SUBSYSTEM_DEF(garbage)
++gcedlasttick
++totalgcs
pass_counts[level]++
+ #ifdef REFERENCE_TRACKING
+ reference_find_on_fail -= refID //It's deleted we don't care anymore.
+ #endif
if (MC_TICK_CHECK)
- break
+ return
continue
// Something's still referring to the qdel'd object.
fail_counts[level]++
+
+ #ifdef REFERENCE_TRACKING
+ var/ref_searching = FALSE
+ #endif
+
switch (level)
if (GC_QUEUE_CHECK)
+ #ifdef REFERENCE_TRACKING
+ if(reference_find_on_fail[refID])
+ INVOKE_ASYNC(D, /datum/proc/find_references)
+ ref_searching = TRUE
#ifdef GC_FAILURE_HARD_LOOKUP
- D.find_references()
+ else
+ INVOKE_ASYNC(D, /datum/proc/find_references)
+ ref_searching = TRUE
+ #endif
+ reference_find_on_fail -= refID
#endif
var/type = D.type
var/datum/qdel_item/I = items[type]
- var/extrainfo = "--"
- if(istype(D,/image))
- var/image/img = D
- var/icon/ico = img.icon
- extrainfo = "L:[img.loc] -- I:[ico] -- IS:[img.icon_state] --"
- testing("GC: -- \ref[D] | [type] was unable to be GC'd [extrainfo]")
+
+ log_world("## TESTING: GC: -- \ref[D] | [type] was unable to be GC'd --")
+ #ifdef TESTING
+ for(var/c in GLOB.admins) //Using testing() here would fill the logs with ADMIN_VV garbage
+ var/client/admin = c
+ if(!check_rights_for(admin, R_ADMIN))
+ continue
+ to_chat(admin, "## TESTING: GC: -- [ADMIN_VV(D)] | [type] was unable to be GC'd --")
+ #endif
I.failures++
+
+ if (I.qdel_flags & QDEL_ITEM_SUSPENDED_FOR_LAG)
+ #ifdef REFERENCE_TRACKING
+ if(ref_searching)
+ return //ref searching intentionally cancels all further fires while running so things that hold references don't end up getting deleted, so we want to return here instead of continue
+ #endif
+ continue
if (GC_QUEUE_HARDDELETE)
HardDelete(D)
if (MC_TICK_CHECK)
- break
+ return
continue
Queue(D, level+1)
+ #ifdef REFERENCE_TRACKING
+ if(ref_searching)
+ return
+ #endif
+
if (MC_TICK_CHECK)
- break
+ return
if (count)
queue.Cut(1,count+1)
count = 0
-/datum/controller/subsystem/garbage/proc/PreQueue(datum/D)
- if (D.gc_destroyed == GC_CURRENTLY_BEING_QDELETED)
- queues[GC_QUEUE_PREQUEUE] += D
- D.gc_destroyed = GC_QUEUED_FOR_QUEUING
-
/datum/controller/subsystem/garbage/proc/Queue(datum/D, level = GC_QUEUE_CHECK)
if (isnull(D))
return
- if (D.gc_destroyed == GC_QUEUED_FOR_HARD_DEL)
- level = GC_QUEUE_HARDDELETE
if (level > GC_QUEUE_COUNT)
HardDelete(D)
return
@@ -208,89 +216,88 @@ SUBSYSTEM_DEF(garbage)
D.gc_destroyed = gctime
var/list/queue = queues[level]
- if (queue[refid])
- queue -= refid // Removing any previous references that were GC'd so that the current object will be at the end of the list.
- queue[refid] = gctime
+ queue[++queue.len] = list(gctime, refid) // not += for byond reasons
//this is mainly to separate things profile wise.
/datum/controller/subsystem/garbage/proc/HardDelete(datum/D)
- var/time = world.timeofday
- var/tick = TICK_USAGE
- var/ticktime = world.time
++delslasttick
++totaldels
var/type = D.type
var/refID = "\ref[D]"
+ var/tick_usage = TICK_USAGE
del(D)
-
- tick = (TICK_USAGE-tick+((world.time-ticktime)/world.tick_lag*100))
+ tick_usage = TICK_USAGE_TO_MS(tick_usage)
var/datum/qdel_item/I = items[type]
-
I.hard_deletes++
- I.hard_delete_time += TICK_DELTA_TO_MS(tick)
+ I.hard_delete_time += tick_usage
+ if (tick_usage > I.hard_delete_max)
+ I.hard_delete_max = tick_usage
+ if (tick_usage > highest_del_ms)
+ highest_del_ms = tick_usage
+ highest_del_type_string = "[type]"
+ var/time = MS2DS(tick_usage)
- if (tick > highest_del_tickusage)
- highest_del_tickusage = tick
- time = world.timeofday - time
- if (!time && TICK_DELTA_TO_MS(tick) > 1)
- time = TICK_DELTA_TO_MS(tick)/100
- if (time > highest_del_time)
- highest_del_time = time
- if (time > 20) //VOREStation Edit
- log_game("Error: [type]([refID]) took longer than 2 seconds to delete (took [time/10] seconds to delete)") //VOREStation Edit
- message_admins("Error: [type]([refID]) took longer than 2 seconds to delete (took [time/10] seconds to delete).") //VOREStation Edit
+ if (time > 0.1 SECONDS)
postpone(time)
-
-/datum/controller/subsystem/garbage/proc/HardQueue(datum/D)
- if (D.gc_destroyed == GC_CURRENTLY_BEING_QDELETED)
- queues[GC_QUEUE_PREQUEUE] += D
- D.gc_destroyed = GC_QUEUED_FOR_HARD_DEL
+ var/threshold = 0.5 // Default, make a config
+ if (threshold && (time > threshold SECONDS))
+ if (!(I.qdel_flags & QDEL_ITEM_ADMINS_WARNED))
+ log_and_message_admins("Error: [type]([refID]) took longer than [threshold] seconds to delete (took [round(time/10, 0.1)] seconds to delete)")
+ I.qdel_flags |= QDEL_ITEM_ADMINS_WARNED
+ I.hard_deletes_over_threshold++
+ var/overrun_limit = 0 // Default, make a config
+ if (overrun_limit && I.hard_deletes_over_threshold >= overrun_limit)
+ I.qdel_flags |= QDEL_ITEM_SUSPENDED_FOR_LAG
/datum/controller/subsystem/garbage/Recover()
if (istype(SSgarbage.queues))
for (var/i in 1 to SSgarbage.queues.len)
queues[i] |= SSgarbage.queues[i]
-
+/// Qdel Item: Holds statistics on each type that passes thru qdel
/datum/qdel_item
- var/name = ""
- var/qdels = 0 //Total number of times it's passed thru qdel.
- var/destroy_time = 0 //Total amount of milliseconds spent processing this type's Destroy()
- var/failures = 0 //Times it was queued for soft deletion but failed to soft delete.
- var/hard_deletes = 0 //Different from failures because it also includes QDEL_HINT_HARDDEL deletions
- var/hard_delete_time = 0//Total amount of milliseconds spent hard deleting this type.
- var/no_respect_force = 0//Number of times it's not respected force=TRUE
- var/no_hint = 0 //Number of times it's not even bother to give a qdel hint
- var/slept_destroy = 0 //Number of times it's slept in its destroy
+ var/name = "" //!Holds the type as a string for this type
+ var/qdels = 0 //!Total number of times it's passed thru qdel.
+ var/destroy_time = 0 //!Total amount of milliseconds spent processing this type's Destroy()
+ var/failures = 0 //!Times it was queued for soft deletion but failed to soft delete.
+ var/hard_deletes = 0 //!Different from failures because it also includes QDEL_HINT_HARDDEL deletions
+ var/hard_delete_time = 0 //!Total amount of milliseconds spent hard deleting this type.
+ var/hard_delete_max = 0 //!Highest time spent hard_deleting this in ms.
+ var/hard_deletes_over_threshold = 0 //!Number of times hard deletes took longer than the configured threshold
+ var/no_respect_force = 0 //!Number of times it's not respected force=TRUE
+ var/no_hint = 0 //!Number of times it's not even bother to give a qdel hint
+ var/slept_destroy = 0 //!Number of times it's slept in its destroy
+ var/qdel_flags = 0 //!Flags related to this type's trip thru qdel.
/datum/qdel_item/New(mytype)
name = "[mytype]"
-// Should be treated as a replacement for the 'del' keyword.
-// Datums passed to this will be given a chance to clean up references to allow the GC to collect them.
-/proc/qdel(datum/D, force=FALSE)
+/// Should be treated as a replacement for the 'del' keyword.
+///
+/// Datums passed to this will be given a chance to clean up references to allow the GC to collect them.
+/proc/qdel(datum/D, force=FALSE, ...)
if(!istype(D))
del(D)
return
+
var/datum/qdel_item/I = SSgarbage.items[D.type]
if (!I)
I = SSgarbage.items[D.type] = new /datum/qdel_item(D.type)
I.qdels++
-
if(isnull(D.gc_destroyed))
- if(SEND_SIGNAL(D, COMSIG_PARENT_PREQDELETED, force)) // Give the components a chance to prevent their parent from being deleted
+ if (SEND_SIGNAL(D, COMSIG_PARENT_PREQDELETED, force)) // Give the components a chance to prevent their parent from being deleted
return
D.gc_destroyed = GC_CURRENTLY_BEING_QDELETED
var/start_time = world.time
var/start_tick = world.tick_usage
SEND_SIGNAL(D, COMSIG_PARENT_QDELETING, force) // Let the (remaining) components know about the result of Destroy
- var/hint = D.Destroy(force) // Let our friend know they're about to get fucked up.
+ var/hint = D.Destroy(arglist(args.Copy(2))) // Let our friend know they're about to get fucked up.
if(world.time != start_time)
I.slept_destroy++
else
@@ -298,12 +305,12 @@ SUBSYSTEM_DEF(garbage)
if(!D)
return
switch(hint)
- if (QDEL_HINT_QUEUE) //qdel should queue the object for deletion.
- SSgarbage.PreQueue(D)
+ if (QDEL_HINT_QUEUE) //qdel should queue the object for deletion.
+ SSgarbage.Queue(D)
if (QDEL_HINT_IWILLGC)
D.gc_destroyed = world.time
return
- if (QDEL_HINT_LETMELIVE) //qdel should let the object live after calling destory.
+ if (QDEL_HINT_LETMELIVE) //qdel should let the object live after calling destory.
if(!force)
D.gc_destroyed = null //clear the gc variable (important!)
return
@@ -311,7 +318,7 @@ SUBSYSTEM_DEF(garbage)
// indicates the objects Destroy() does not respect force
#ifdef TESTING
if(!I.no_respect_force)
- crash_with("[D.type] has been force deleted, but is \
+ testing("WARNING: [D.type] has been force deleted, but is \
returning an immortal QDEL_HINT, indicating it does \
not respect the force flag for qdel(). It has been \
placed in the queue, further instances of this type \
@@ -319,136 +326,25 @@ SUBSYSTEM_DEF(garbage)
#endif
I.no_respect_force++
- SSgarbage.PreQueue(D)
- if (QDEL_HINT_HARDDEL) //qdel should assume this object won't gc, and queue a hard delete using a hard reference to save time from the locate()
- SSgarbage.HardQueue(D)
- if (QDEL_HINT_HARDDEL_NOW) //qdel should assume this object won't gc, and hard del it post haste.
+ SSgarbage.Queue(D)
+ if (QDEL_HINT_HARDDEL) //qdel should assume this object won't gc, and queue a hard delete
+ SSgarbage.Queue(D, GC_QUEUE_HARDDELETE)
+ if (QDEL_HINT_HARDDEL_NOW) //qdel should assume this object won't gc, and hard del it post haste.
SSgarbage.HardDelete(D)
- if (QDEL_HINT_FINDREFERENCE)//qdel will, if TESTING is enabled, display all references to this object, then queue the object for deletion.
- SSgarbage.PreQueue(D)
- #ifdef TESTING
+ #ifdef REFERENCE_TRACKING
+ if (QDEL_HINT_FINDREFERENCE) //qdel will, if REFERENCE_TRACKING is enabled, display all references to this object, then queue the object for deletion.
+ SSgarbage.Queue(D)
D.find_references()
- #endif
+ if (QDEL_HINT_IFFAIL_FINDREFERENCE) //qdel will, if REFERENCE_TRACKING is enabled and the object fails to collect, display all references to this object.
+ SSgarbage.Queue(D)
+ SSgarbage.reference_find_on_fail["\ref[D]"] = TRUE
+ #endif
else
#ifdef TESTING
if(!I.no_hint)
- crash_with("[D.type] is not returning a qdel hint. It is being placed in the queue. Further instances of this type will also be queued.")
+ testing("WARNING: [D.type] is not returning a qdel hint. It is being placed in the queue. Further instances of this type will also be queued.")
#endif
I.no_hint++
- SSgarbage.PreQueue(D)
+ SSgarbage.Queue(D)
else if(D.gc_destroyed == GC_CURRENTLY_BEING_QDELETED)
CRASH("[D.type] destroy proc was called multiple times, likely due to a qdel loop in the Destroy logic")
-
-#ifdef TESTING
-
-/datum/verb/find_refs()
- set category = "Debug"
- set name = "Find References"
- set background = 1
- set src in world
-
- find_references(FALSE)
-
-/datum/proc/find_references(skip_alert)
- running_find_references = type
- if(usr && usr.client)
- if(usr.client.running_find_references)
- testing("CANCELLED search for references to a [usr.client.running_find_references].")
- usr.client.running_find_references = null
- running_find_references = null
- //restart the garbage collector
- SSgarbage.can_fire = 1
- SSgarbage.next_fire = world.time + world.tick_lag
- return
-
- if(!skip_alert)
- if(tgui_alert(usr, "Running this will lock everything up for about 5 minutes. Would you like to begin the search?", "Find References", list("Yes", "No")) == "No")
- running_find_references = null
- return
-
- //this keeps the garbage collector from failing to collect objects being searched for in here
- SSgarbage.can_fire = 0
-
- if(usr && usr.client)
- usr.client.running_find_references = type
-
- testing("Beginning search for references to a [type].")
- last_find_references = world.time
-
- // DoSearchVar(GLOB) // If we ever implement GLOB this would be the place.
- for(var/datum/thing in world) //atoms (don't beleive it's lies)
- DoSearchVar(thing, "World -> [thing]")
-
- for (var/datum/thing) //datums
- DoSearchVar(thing, "World -> [thing]")
-
- for (var/client/thing) //clients
- DoSearchVar(thing, "World -> [thing]")
-
- testing("Completed search for references to a [type].")
- if(usr && usr.client)
- usr.client.running_find_references = null
- running_find_references = null
-
- //restart the garbage collector
- SSgarbage.can_fire = 1
- SSgarbage.next_fire = world.time + world.tick_lag
-
-/datum/verb/qdel_then_find_references()
- set category = "Debug"
- set name = "qdel() then Find References"
- set background = 1
- set src in world
-
- qdel(src)
- if(!running_find_references)
- find_references(TRUE)
-
-/datum/proc/DoSearchVar(X, Xname, recursive_limit = 64)
- if(usr && usr.client && !usr.client.running_find_references)
- return
- if (!recursive_limit)
- return
-
- if(istype(X, /datum))
- var/datum/D = X
- if(D.last_find_references == last_find_references)
- return
-
- D.last_find_references = last_find_references
- var/list/L = D.vars
-
- for(var/varname in L)
- if (varname == "vars")
- continue
- var/variable = L[varname]
-
- if(variable == src)
- testing("Found [src.type] \ref[src] in [D.type]'s [varname] var. [Xname]")
-
- else if(islist(variable))
- DoSearchVar(variable, "[Xname] -> list", recursive_limit-1)
-
- else if(islist(X))
- var/normal = IS_NORMAL_LIST(X)
- for(var/I in X)
- if (I == src)
- testing("Found [src.type] \ref[src] in list [Xname].")
-
- else if (I && !isnum(I) && normal && X[I] == src)
- testing("Found [src.type] \ref[src] in list [Xname]\[[I]\]")
-
- else if (islist(I))
- DoSearchVar(I, "[Xname] -> list", recursive_limit-1)
-
-#ifndef FIND_REF_NO_CHECK_TICK
- CHECK_TICK
-#endif
-
-#endif
-
-
-/image/Destroy()
- ..()
- loc = null
- return QDEL_HINT_QUEUE
diff --git a/code/controllers/subsystems/overlays.dm b/code/controllers/subsystems/overlays.dm
index 16662cc042..92400e495b 100644
--- a/code/controllers/subsystems/overlays.dm
+++ b/code/controllers/subsystems/overlays.dm
@@ -10,9 +10,9 @@ SUBSYSTEM_DEF(overlays)
var/list/overlay_icon_state_caches // Cache thing
var/list/overlay_icon_cache // Cache thing
-var/global/image/stringbro = new() // Temporarily super-global because of BYOND init order dumbness.
-var/global/image/iconbro = new() // Temporarily super-global because of BYOND init order dumbness.
-var/global/image/appearance_bro = new() // Temporarily super-global because of BYOND init order dumbness.
+ var/static/image/stringbro
+ var/static/image/iconbro
+ var/static/image/appearance_bro
/datum/controller/subsystem/overlays/PreInit()
overlay_icon_state_caches = list()
@@ -20,6 +20,10 @@ var/global/image/appearance_bro = new() // Temporarily super-global because of B
queue = list()
stats = list()
+ stringbro = new()
+ iconbro = new()
+ appearance_bro = new()
+
/datum/controller/subsystem/overlays/Initialize()
fire(mc_check = FALSE)
..()
@@ -71,12 +75,12 @@ var/global/image/appearance_bro = new() // Temporarily super-global because of B
var/cached_appearance = cached_icon["[iconstate]"]
if (cached_appearance)
return cached_appearance
- stringbro.icon = icon
- stringbro.icon_state = iconstate
+ SSoverlays.stringbro.icon = icon
+ SSoverlays.stringbro.icon_state = iconstate
if (!cached_icon) //not using the macro to save an associated lookup
cached_icon = list()
icon_states_cache[icon] = cached_icon
- var/cached_appearance = stringbro.appearance
+ var/cached_appearance = SSoverlays.stringbro.appearance
cached_icon["[iconstate]"] = cached_appearance
return cached_appearance
@@ -85,8 +89,8 @@ var/global/image/appearance_bro = new() // Temporarily super-global because of B
var/list/icon_cache = SSoverlays.overlay_icon_cache
. = icon_cache[icon]
if (!.)
- iconbro.icon = icon
- . = iconbro.appearance
+ SSoverlays.iconbro.icon = icon
+ . = SSoverlays.iconbro.appearance
icon_cache[icon] = .
/atom/proc/build_appearance_list(old_overlays)
@@ -106,11 +110,11 @@ var/global/image/appearance_bro = new() // Temporarily super-global because of B
var/atom/A = overlay
if (A.flags & OVERLAY_QUEUED)
COMPILE_OVERLAYS(A)
- appearance_bro.appearance = overlay //this works for images and atoms too!
+ SSoverlays.appearance_bro.appearance = overlay //this works for images and atoms too!
if(!ispath(overlay))
var/image/I = overlay
- appearance_bro.dir = I.dir
- new_overlays += appearance_bro.appearance
+ SSoverlays.appearance_bro.dir = I.dir
+ new_overlays += SSoverlays.appearance_bro.appearance
return new_overlays
#define NOT_QUEUED_ALREADY (!(flags & OVERLAY_QUEUED))
diff --git a/code/datums/datum.dm b/code/datums/datum.dm
index 721beeaf4c..205156e0a2 100644
--- a/code/datums/datum.dm
+++ b/code/datums/datum.dm
@@ -13,9 +13,13 @@
var/weakref/weakref // Holder of weakref instance pointing to this datum
var/datum_flags = NONE
-#ifdef TESTING
+#ifdef REFERENCE_TRACKING
var/tmp/running_find_references
var/tmp/last_find_references = 0
+ #ifdef REFERENCE_TRACKING_DEBUG
+ ///Stores info about where refs are found, used for sanity checks and testing
+ var/list/found_refs
+ #endif
#endif
// Default implementation of clean-up code.
diff --git a/code/datums/reference_tracking.dm b/code/datums/reference_tracking.dm
new file mode 100644
index 0000000000..3154f64740
--- /dev/null
+++ b/code/datums/reference_tracking.dm
@@ -0,0 +1,140 @@
+#ifdef REFERENCE_TRACKING
+
+/datum/proc/find_references(skip_alert)
+ running_find_references = type
+ if(usr?.client)
+ if(usr.client.running_find_references)
+ log_reftracker("CANCELLED search for references to a [usr.client.running_find_references].")
+ usr.client.running_find_references = null
+ running_find_references = null
+ //restart the garbage collector
+ SSgarbage.can_fire = TRUE
+ SSgarbage.next_fire = world.time + world.tick_lag
+ return
+
+ if(!skip_alert && tgui_alert(usr,"Running this will lock everything up for about 5 minutes. Would you like to begin the search?", "Find References", list("Yes", "No")) != "Yes")
+ running_find_references = null
+ return
+
+ //this keeps the garbage collector from failing to collect objects being searched for in here
+ SSgarbage.can_fire = FALSE
+
+ if(usr?.client)
+ usr.client.running_find_references = type
+
+ log_reftracker("Beginning search for references to a [type].")
+
+ var/starting_time = world.time
+
+ //Time to search the whole game for our ref
+ DoSearchVar(GLOB, "GLOB") //globals
+ log_reftracker("Finished searching globals")
+
+ for(var/datum/thing in world) //atoms (don't beleive its lies)
+ DoSearchVar(thing, "World -> [thing.type]", search_time = starting_time)
+ log_reftracker("Finished searching atoms")
+
+ for(var/datum/thing) //datums
+ DoSearchVar(thing, "Datums -> [thing.type]", search_time = starting_time)
+ log_reftracker("Finished searching datums")
+
+ //Warning, attempting to search clients like this will cause crashes if done on live. Watch yourself
+ for(var/client/thing) //clients
+ DoSearchVar(thing, "Clients -> [thing.type]", search_time = starting_time)
+ log_reftracker("Finished searching clients")
+
+ log_reftracker("Completed search for references to a [type].")
+
+ if(usr?.client)
+ usr.client.running_find_references = null
+ running_find_references = null
+
+ //restart the garbage collector
+ SSgarbage.can_fire = TRUE
+ SSgarbage.next_fire = world.time + world.tick_lag
+
+/datum/proc/DoSearchVar(potential_container, container_name, recursive_limit = 64, search_time = world.time)
+ #ifdef REFERENCE_TRACKING_DEBUG
+ if(!found_refs)
+ found_refs = list()
+ #endif
+
+ if(usr?.client && !usr.client.running_find_references)
+ return
+
+ if(!recursive_limit)
+ log_reftracker("Recursion limit reached. [container_name]")
+ return
+
+ //Check each time you go down a layer. This makes it a bit slow, but it won't effect the rest of the game at all
+ #ifndef FIND_REF_NO_CHECK_TICK
+ CHECK_TICK
+ #endif
+
+ if(istype(potential_container, /datum))
+ var/datum/datum_container = potential_container
+ if(datum_container.last_find_references == search_time)
+ return
+
+ datum_container.last_find_references = search_time
+ var/list/vars_list = datum_container.vars
+
+ for(var/varname in vars_list)
+ #ifndef FIND_REF_NO_CHECK_TICK
+ CHECK_TICK
+ #endif
+ if (varname == "vars" || varname == "vis_locs") //Fun fact, vis_locs don't count for references
+ continue
+ var/variable = vars_list[varname]
+
+ if(variable == src)
+ #ifdef REFERENCE_TRACKING_DEBUG
+ found_refs[varname] = TRUE
+ #endif
+ log_reftracker("Found [type] \ref[src] in [datum_container.type]'s \ref[datum_container] [varname] var. [container_name]")
+ continue
+
+ if(islist(variable))
+ DoSearchVar(variable, "[container_name] \ref[datum_container] -> [varname] (list)", recursive_limit - 1, search_time)
+
+ else if(islist(potential_container))
+ var/normal = IS_NORMAL_LIST(potential_container)
+ var/list/potential_cache = potential_container
+ for(var/element_in_list in potential_cache)
+ #ifndef FIND_REF_NO_CHECK_TICK
+ CHECK_TICK
+ #endif
+ //Check normal entrys
+ if(element_in_list == src)
+ #ifdef REFERENCE_TRACKING_DEBUG
+ found_refs[potential_cache] = TRUE
+ #endif
+ log_reftracker("Found [type] \ref[src] in list [container_name].")
+ continue
+
+ var/assoc_val = null
+ if(!isnum(element_in_list) && normal)
+ assoc_val = potential_cache[element_in_list]
+ //Check assoc entrys
+ if(assoc_val == src)
+ #ifdef REFERENCE_TRACKING_DEBUG
+ found_refs[potential_cache] = TRUE
+ #endif
+ log_reftracker("Found [type] \ref[src] in list [container_name]\[[element_in_list]\]")
+ continue
+ //We need to run both of these checks, since our object could be hiding in either of them
+ //Check normal sublists
+ if(islist(element_in_list))
+ DoSearchVar(element_in_list, "[container_name] -> [element_in_list] (list)", recursive_limit - 1, search_time)
+ //Check assoc sublists
+ if(islist(assoc_val))
+ DoSearchVar(potential_container[element_in_list], "[container_name]\[[element_in_list]\] -> [assoc_val] (list)", recursive_limit - 1, search_time)
+
+/proc/qdel_and_find_ref_if_fail(datum/thing_to_del, force = FALSE)
+ thing_to_del.qdel_and_find_ref_if_fail(force)
+
+/datum/proc/qdel_and_find_ref_if_fail(force = FALSE)
+ SSgarbage.reference_find_on_fail["\ref[src]"] = TRUE
+ qdel(src, force)
+
+#endif
diff --git a/code/game/objects/items/devices/denecrotizer_vr.dm b/code/game/objects/items/devices/denecrotizer_vr.dm
index 5881598550..8aeaba5b0d 100644
--- a/code/game/objects/items/devices/denecrotizer_vr.dm
+++ b/code/game/objects/items/devices/denecrotizer_vr.dm
@@ -145,7 +145,7 @@
return FALSE
if(!target.mind)
user.visible_message("[user] gently presses [src] to [target]...", runemessage = "presses [src] to [target]")
- if(do_after(user, revive_time, exclusive = 1, target = target))
+ if(do_after(user, revive_time, exclusive = TASK_USER_EXCLUSIVE, target = target))
target.faction = user.faction
target.revivedby = user.name
target.ghostjoin = 1
@@ -166,7 +166,7 @@
/obj/item/device/denecrotizer/proc/ghostjoin_rez(mob/living/simple_mob/target, mob/living/user)
user.visible_message("[user] gently presses [src] to [target]...", runemessage = "presses [src] to [target]")
- if(do_after(user, revive_time, exclusive = 1, target = target))
+ if(do_after(user, revive_time, exclusive = TASK_ALL_EXCLUSIVE, target = target))
target.faction = user.faction
target.revivedby = user.name
target.ai_holder.returns_home = FALSE
@@ -190,7 +190,7 @@
/obj/item/device/denecrotizer/proc/basic_rez(mob/living/simple_mob/target, mob/living/user) //so medical can have a way to bring back people's pets or whatever, does not change any settings about the mob or offer it to ghosts.
user.visible_message("[user] presses [src] to [target]...", runemessage = "presses [src] to [target]")
- if(do_after(user, revive_time, exclusive = 1, target = target))
+ if(do_after(user, revive_time, exclusive = TASK_ALL_EXCLUSIVE, target = target))
target.revive()
target.sight = initial(target.sight)
target.see_in_dark = initial(target.see_in_dark)
diff --git a/code/game/objects/items/stacks/medical.dm b/code/game/objects/items/stacks/medical.dm
index 629a383433..f065ec3ea1 100644
--- a/code/game/objects/items/stacks/medical.dm
+++ b/code/game/objects/items/stacks/medical.dm
@@ -408,7 +408,7 @@
to_chat(user, "You can't apply a splint to the arm you're using!")
return
user.visible_message("[user] starts to apply \the [src] to their [limb].", "You start to apply \the [src] to your [limb].", "You hear something being wrapped.")
- if(do_after(user, 50, M, exclusive = TRUE))
+ if(do_after(user, 50, M, exclusive = TASK_USER_EXCLUSIVE))
if(affecting.splinted)
to_chat(user, "[M]'s [limb] is already splinted!")
return
diff --git a/code/game/objects/items/stacks/nanopaste.dm b/code/game/objects/items/stacks/nanopaste.dm
index 10c85fa7e4..ba5db2b33b 100644
--- a/code/game/objects/items/stacks/nanopaste.dm
+++ b/code/game/objects/items/stacks/nanopaste.dm
@@ -16,7 +16,7 @@
if (istype(M,/mob/living/silicon/robot)) //Repairing cyborgs
var/mob/living/silicon/robot/R = M
if (R.getBruteLoss() || R.getFireLoss())
- if(do_after(user, 7 * toolspeed, exclusive = TRUE))
+ if(do_after(user, 7 * toolspeed, R, exclusive = TASK_ALL_EXCLUSIVE))
R.adjustBruteLoss(-15)
R.adjustFireLoss(-15)
R.updatehealth()
@@ -51,9 +51,9 @@
else if(can_use(1))
user.setClickCooldown(user.get_attack_speed(src))
if(S.open >= 2)
- if(do_after(user, 5 * toolspeed, exclusive = TRUE))
+ if(do_after(user, 5 * toolspeed, S, exclusive = TASK_ALL_EXCLUSIVE))
S.heal_damage(restoration_internal, restoration_internal, robo_repair = 1)
- else if(do_after(user, 5 * toolspeed, exclusive = TRUE))
+ else if(do_after(user, 5 * toolspeed, S, exclusive = TASK_ALL_EXCLUSIVE))
S.heal_damage(restoration_external,restoration_external, robo_repair =1)
H.updatehealth()
use(1)
diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm
index e80afbef41..d5d1fe42d1 100644
--- a/code/game/objects/items/stacks/stack.dm
+++ b/code/game/objects/items/stacks/stack.dm
@@ -56,14 +56,17 @@
icon_state = "[initial(icon_state)]_3"
item_state = initial(icon_state)
+/obj/item/stack/proc/get_examine_string()
+ if(!uses_charge)
+ return "There [src.amount == 1 ? "is" : "are"] [src.amount] [src.singular_name]\s in the stack."
+ else
+ return "There is enough charge for [get_amount()]."
+
/obj/item/stack/examine(mob/user)
. = ..()
if(Adjacent(user))
- if(!uses_charge)
- . += "There are [src.amount] [src.singular_name]\s in the stack."
- else
- . += "There is enough charge for [get_amount()]."
+ . += get_examine_string()
/obj/item/stack/attack_self(mob/user)
tgui_interact(user)
diff --git a/code/game/objects/items/weapons/chewables.dm b/code/game/objects/items/weapons/chewables.dm
index 186263897b..2ab2c95c72 100644
--- a/code/game/objects/items/weapons/chewables.dm
+++ b/code/game/objects/items/weapons/chewables.dm
@@ -252,7 +252,7 @@
/obj/item/clothing/mask/chewable/candy/pocky //ADDITION 04/17/2021
name = "chocolate pocky"
desc = "A chocolate-coated biscuit stick."
- icon_state = "pocky"
+ icon_state = "pockystick"
item_state = "pocky"
/obj/item/clothing/mask/chewable/candy/pocky/process()
diff --git a/code/game/objects/items/weapons/material/kitchen.dm b/code/game/objects/items/weapons/material/kitchen.dm
index 349c212499..1d32db4888 100644
--- a/code/game/objects/items/weapons/material/kitchen.dm
+++ b/code/game/objects/items/weapons/material/kitchen.dm
@@ -15,24 +15,44 @@
edge = TRUE
force_divisor = 0.1 // 6 when wielded with hardness 60 (steel)
thrown_force_divisor = 0.25 // 5 when thrown with weight 20 (steel)
- var/weakref/loaded //Weakref for currently loaded food object.
+ var/scoop_volume = 5
+ var/loaded // Name for currently loaded food object.
+ var/loaded_color // Color for currently loaded food object.
-/obj/item/weapon/material/kitchen/utensil/New()
- ..()
+/obj/item/weapon/material/kitchen/utensil/Initialize()
+ . = ..()
if (prob(60))
src.pixel_y = rand(0, 4)
- create_reagents(5)
- return
+ create_reagents(scoop_volume)
/obj/item/weapon/material/kitchen/utensil/update_icon()
. = ..()
cut_overlays()
- var/obj/item/weapon/reagent_containers/food/snacks/eaten = loaded?.resolve()
- if(eaten)
+ if(loaded)
var/image/I = new(icon, "loadedfood")
- I.color = eaten.filling_color
+ I.color = loaded_color
add_overlay(I)
+/obj/item/weapon/material/kitchen/utensil/proc/load_food(var/mob/user, var/obj/item/weapon/reagent_containers/food/snacks/loading)
+ if (reagents.total_volume > 0)
+ to_chat(user, SPAN_DANGER("There is already something on \the [src]."))
+ return
+ if (!loading?.reagents?.total_volume)
+ to_chat(user, SPAN_NOTICE("Nothing to scoop up in \the [loading]!"))
+
+
+ loaded = "\the [loading]"
+ user.visible_message( \
+ "\The [user] scoops up some of [loaded] with \the [src]!",
+ SPAN_NOTICE("You scoop up some of [loaded] with \the [src]!")
+ )
+ loading.bitecount++
+ loading.reagents.trans_to_obj(src, min(loading.reagents.total_volume, scoop_volume))
+ loaded_color = loading.filling_color
+ if (loading.reagents.total_volume <= 0)
+ qdel(loading)
+ update_icon()
+
/obj/item/weapon/material/kitchen/utensil/attack(mob/living/carbon/M as mob, mob/living/carbon/user as mob)
if(!istype(M))
return ..()
@@ -46,19 +66,18 @@
return ..()
if (loaded && reagents.total_volume > 0)
- var/atom/movable/eaten = loaded?.resolve()
reagents.trans_to_mob(M, reagents.total_volume, CHEM_INGEST)
- if(eaten)
- if(M == user)
- if(!M.can_eat(eaten))
- return
- M.visible_message(SPAN_NOTICE("\The [user] eats some of \the [eaten] with \the [src]."))
- else
- user.visible_message(SPAN_WARNING("\The [user] begins to feed \the [M]!"))
- if(!(M.can_force_feed(user, eaten) && do_mob(user, M, 5 SECONDS)))
- return
- M.visible_message(SPAN_NOTICE("\The [user] feeds some of \the [eaten] to \the [M] with \the [src]."))
- playsound(src,'sound/items/eatfood.ogg', rand(10,40), 1)
+ if(M == user)
+ if(!M.can_eat(loaded))
+ return
+ M.visible_message("\The [user] eats some of [loaded] with \the [src].")
+ else
+ user.visible_message(SPAN_WARNING("\The [user] begins to feed \the [M]!"))
+ if(!(M.can_force_feed(user, loaded) && do_mob(user, M, 5 SECONDS)))
+ return
+ M.visible_message("\The [user] feeds some of [loaded] to \the [M] with \the [src].")
+ playsound(src,'sound/items/eatfood.ogg', rand(10,40), 1)
+ loaded = null
update_icon()
return
else
diff --git a/code/game/objects/items/weapons/storage/pouches.dm b/code/game/objects/items/weapons/storage/pouches.dm
index 38d0660894..24a81f4112 100644
--- a/code/game/objects/items/weapons/storage/pouches.dm
+++ b/code/game/objects/items/weapons/storage/pouches.dm
@@ -23,7 +23,7 @@
if(user.get_active_hand() == src || user.get_inactive_hand() == src)
return TRUE // Skip delay
- if(insert_delay && !do_after(user, 2 SECONDS, src, needhand = TRUE, exclusive = TRUE))
+ if(insert_delay && !do_after(user, 2 SECONDS, src, needhand = TRUE, exclusive = TASK_USER_EXCLUSIVE))
return FALSE // Moved or whatever
if(W in src)
@@ -36,7 +36,7 @@
if(user.get_active_hand() == src || user.get_inactive_hand() == src)
return TRUE // Skip delay
- if(remove_delay && !do_after(user, 2 SECONDS, src, needhand = TRUE, exclusive = TRUE))
+ if(remove_delay && !do_after(user, 2 SECONDS, src, needhand = TRUE, exclusive = TASK_USER_EXCLUSIVE))
return FALSE // Moved or whatever
if(W in src)
diff --git a/code/game/objects/structures/door_assembly.dm b/code/game/objects/structures/door_assembly.dm
index 5a786cacd8..2b57235299 100644
--- a/code/game/objects/structures/door_assembly.dm
+++ b/code/game/objects/structures/door_assembly.dm
@@ -176,7 +176,7 @@
playsound(src, WT.usesound, 50, 1)
if(istext(glass))
user.visible_message("[user] welds the [glass] plating off the airlock assembly.", "You start to weld the [glass] plating off the airlock assembly.")
- if(do_after(user, 40 * WT.toolspeed))
+ if(do_after(user, 4 SECONDS * WT.toolspeed, src, exclusive = TASK_ALL_EXCLUSIVE))
if(!src || !WT.isOn()) return
to_chat(user, "You welded the [glass] plating off!")
var/M = text2path("/obj/item/stack/material/[glass]")
@@ -184,14 +184,14 @@
glass = 0
else if(glass == 1)
user.visible_message("[user] welds the glass panel out of the airlock assembly.", "You start to weld the glass panel out of the airlock assembly.")
- if(do_after(user, 40 * WT.toolspeed))
+ if(do_after(user, 4 SECONDS * WT.toolspeed, src, exclusive = TASK_ALL_EXCLUSIVE))
if(!src || !WT.isOn()) return
to_chat(user, "You welded the glass panel out!")
new /obj/item/stack/material/glass/reinforced(src.loc)
glass = 0
else if(!anchored)
user.visible_message("[user] dissassembles the airlock assembly.", "You start to dissassemble the airlock assembly.")
- if(do_after(user, 40 * WT.toolspeed))
+ if(do_after(user, 4 SECONDS * WT.toolspeed, src, exclusive = TASK_ALL_EXCLUSIVE))
if(!src || !WT.isOn()) return
to_chat(user, "You dissasembled the airlock assembly!")
new /obj/item/stack/material/steel(src.loc, 4)
@@ -207,7 +207,7 @@
else
user.visible_message("[user] begins securing the airlock assembly to the floor.", "You starts securing the airlock assembly to the floor.")
- if(do_after(user, 40 * W.toolspeed))
+ if(do_after(user, 4 SECONDS * W.toolspeed, src, exclusive = TASK_ALL_EXCLUSIVE))
if(!src) return
to_chat(user, "You [anchored? "un" : ""]secured the airlock assembly!")
anchored = !anchored
@@ -218,7 +218,7 @@
to_chat(user, "You need one length of coil to wire the airlock assembly.")
return
user.visible_message("[user] wires the airlock assembly.", "You start to wire the airlock assembly.")
- if(do_after(user, 40) && state == 0 && anchored)
+ if(do_after(user, 4 SECONDS, src, exclusive = TASK_ALL_EXCLUSIVE) && state == 0 && anchored)
if (C.use(1))
src.state = 1
to_chat(user, "You wire the airlock.")
@@ -227,7 +227,7 @@
playsound(src, W.usesound, 100, 1)
user.visible_message("[user] cuts the wires from the airlock assembly.", "You start to cut the wires from airlock assembly.")
- if(do_after(user, 40 * W.toolspeed))
+ if(do_after(user, 4 SECONDS * W.toolspeed, src, exclusive = TASK_ALL_EXCLUSIVE))
if(!src) return
to_chat(user, "You cut the airlock wires.!")
new/obj/item/stack/cable_coil(src.loc, 1)
@@ -237,7 +237,7 @@
playsound(src, W.usesound, 100, 1)
user.visible_message("[user] installs the electronics into the airlock assembly.", "You start to install electronics into the airlock assembly.")
- if(do_after(user, 40))
+ if(do_after(user, 4 SECONDS, src, exclusive = TASK_ALL_EXCLUSIVE))
if(!src) return
user.drop_item()
W.loc = src
@@ -255,7 +255,7 @@
playsound(src, W.usesound, 100, 1)
user.visible_message("\The [user] starts removing the electronics from the airlock assembly.", "You start removing the electronics from the airlock assembly.")
- if(do_after(user, 40 * W.toolspeed))
+ if(do_after(user, 4 SECONDS * W.toolspeed, src, exclusive = TASK_ALL_EXCLUSIVE))
if(!src) return
to_chat(user, "You removed the airlock electronics!")
src.state = 1
@@ -270,7 +270,7 @@
if(material_name == "rglass")
playsound(src, 'sound/items/Crowbar.ogg', 100, 1)
user.visible_message("[user] adds [S.name] to the airlock assembly.", "You start to install [S.name] into the airlock assembly.")
- if(do_after(user, 40) && !glass)
+ if(do_after(user, 4 SECONDS, src, exclusive = TASK_ALL_EXCLUSIVE) && !glass)
if (S.use(1))
to_chat(user, "You installed reinforced glass windows into the airlock assembly.")
glass = 1
@@ -282,7 +282,7 @@
if(S.get_amount() >= 2)
playsound(src, 'sound/items/Crowbar.ogg', 100, 1)
user.visible_message("[user] adds [S.name] to the airlock assembly.", "You start to install [S.name] into the airlock assembly.")
- if(do_after(user, 40) && !glass)
+ if(do_after(user, 4 SECONDS, src, exclusive = TASK_ALL_EXCLUSIVE) && !glass)
if (S.use(2))
to_chat(user, "You installed [material_display_name(material_name)] plating into the airlock assembly.")
glass = material_name
@@ -291,7 +291,7 @@
playsound(src, W.usesound, 100, 1)
to_chat(user, "Now finishing the airlock.")
- if(do_after(user, 40 * W.toolspeed))
+ if(do_after(user, 4 SECONDS * W.toolspeed, src, exclusive = TASK_ALL_EXCLUSIVE))
if(!src) return
to_chat(user, "You finish the airlock!")
var/path
diff --git a/code/modules/food/food/snacks.dm b/code/modules/food/food/snacks.dm
index 5d2c6f3ca9..61679424bd 100644
--- a/code/modules/food/food/snacks.dm
+++ b/code/modules/food/food/snacks.dm
@@ -193,24 +193,7 @@
// Eating with forks
if(istype(W,/obj/item/weapon/material/kitchen/utensil))
var/obj/item/weapon/material/kitchen/utensil/U = W
- if(!U.reagents)
- U.create_reagents(5)
-
- if (U.reagents.total_volume > 0)
- to_chat(user, "You already have something on your [U].")
- return
-
- user.visible_message( \
- "[user] scoops up some [src] with \the [U]!", \
- "You scoop up some [src] with \the [U]!" \
- )
-
- bitecount++
-
- reagents.trans_to_obj(U, min(reagents.total_volume,5))
-
- if (reagents.total_volume <= 0)
- qdel(src)
+ U.load_food(user, src)
return
if (is_sliceable())
diff --git a/code/modules/food/glass/bottle.dm b/code/modules/food/glass/bottle.dm
index f76dac86bf..28eec3c219 100644
--- a/code/modules/food/glass/bottle.dm
+++ b/code/modules/food/glass/bottle.dm
@@ -53,10 +53,10 @@
add_overlay(filling)
if (!is_open_container())
- add_overlay("lid_[initial(icon_state)]")
+ add_overlay("lid_[icon_state]")
if (label_text)
- add_overlay("label_[initial(icon_state)]")
+ add_overlay("label_[icon_state]")
/obj/item/weapon/reagent_containers/glass/bottle/inaprovaline
name = "inaprovaline bottle"
diff --git a/code/modules/materials/materials/_materials.dm b/code/modules/materials/materials/_materials.dm
index 890eab5722..83cf22271d 100644
--- a/code/modules/materials/materials/_materials.dm
+++ b/code/modules/materials/materials/_materials.dm
@@ -165,6 +165,7 @@ var/list/name_to_material
var/flags = 0 // Various status modifiers.
var/sheet_singular_name = "sheet"
var/sheet_plural_name = "sheets"
+ var/sheet_collective_name = "stack"
var/is_fusion_fuel
// Shards/tables/structures
diff --git a/code/modules/materials/materials/organic/wood.dm b/code/modules/materials/materials/organic/wood.dm
index f573e7d6d5..25ea25ab58 100644
--- a/code/modules/materials/materials/organic/wood.dm
+++ b/code/modules/materials/materials/organic/wood.dm
@@ -67,10 +67,12 @@
/datum/material/wood/log
name = MAT_LOG
+ display_name = "wood" // will lead to "wood log"
icon_base = "log"
stack_type = /obj/item/stack/material/log
- sheet_singular_name = null
- sheet_plural_name = "pile"
+ sheet_singular_name = "log"
+ sheet_plural_name = "logs"
+ sheet_collective_name = "pile"
pass_stack_colors = TRUE
supply_conversion_value = 3 //YW EDIT: logs worth more points
@@ -81,6 +83,7 @@
/datum/material/wood/log/sif
name = MAT_SIFLOG
+ display_name = "alien wood"
icon_colour = "#0099cc" // Cyan-ish
stack_origin_tech = list(TECH_MATERIAL = 2, TECH_BIO = 2)
stack_type = /obj/item/stack/material/log/sif
\ No newline at end of file
diff --git a/code/modules/materials/sheets/_sheets.dm b/code/modules/materials/sheets/_sheets.dm
index ddc815bbae..ee359c863b 100644
--- a/code/modules/materials/sheets/_sheets.dm
+++ b/code/modules/materials/sheets/_sheets.dm
@@ -54,13 +54,18 @@
if(amount>1)
name = "[material.use_name] [material.sheet_plural_name]"
- desc = "A stack of [material.use_name] [material.sheet_plural_name]."
+ desc = "A [material.sheet_collective_name] of [material.use_name] [material.sheet_plural_name]."
gender = PLURAL
else
name = "[material.use_name] [material.sheet_singular_name]"
desc = "A [material.sheet_singular_name] of [material.use_name]."
gender = NEUTER
+/obj/item/stack/material/get_examine_string()
+ if(!uses_charge)
+ return "There [amount == 1 ? "is" : "are"] [amount] [material.sheet_singular_name]\s in the [material.sheet_collective_name]."
+ return ..()
+
/obj/item/stack/material/use(var/used)
. = ..()
update_strings()
diff --git a/code/modules/mob/holder.dm b/code/modules/mob/holder.dm
index ae307bd131..088a7dda78 100644
--- a/code/modules/mob/holder.dm
+++ b/code/modules/mob/holder.dm
@@ -102,38 +102,137 @@ var/list/holder_mob_icon_cache = list()
/obj/item/weapon/holder/diona
origin_tech = list(TECH_MAGNET = 3, TECH_BIO = 5)
slot_flags = SLOT_HEAD | SLOT_OCLOTHING | SLOT_HOLSTER
+ item_state = "diona"
/obj/item/weapon/holder/drone
origin_tech = list(TECH_MAGNET = 3, TECH_ENGINEERING = 5)
+ item_state = "repairbot"
/obj/item/weapon/holder/drone/swarm
origin_tech = list(TECH_MAGNET = 6, TECH_ENGINEERING = 7, TECH_PRECURSOR = 2, TECH_ARCANE = 1)
+ item_state = "constructiondrone"
/obj/item/weapon/holder/pai
origin_tech = list(TECH_DATA = 2)
+/obj/item/weapon/holder/pai/Initialize(mapload, mob/held)
+ . = ..()
+ item_state = held.icon_state
+
/obj/item/weapon/holder/mouse
w_class = ITEMSIZE_TINY
+/obj/item/weapon/holder/pai/Initialize(mapload, mob/held)
+ . = ..()
+ item_state = held.icon_state
+
/obj/item/weapon/holder/possum
origin_tech = list(TECH_BIO = 2)
+ item_state = "possum"
/obj/item/weapon/holder/possum/poppy
origin_tech = list(TECH_BIO = 2, TECH_ENGINEERING = 4)
+ item_state = "poppy"
/obj/item/weapon/holder/cat
origin_tech = list(TECH_BIO = 2)
+ item_state = "cat"
/obj/item/weapon/holder/cat/runtime
origin_tech = list(TECH_BIO = 2, TECH_DATA = 4)
+/obj/item/weapon/holder/cat/cak
+ origin_tech = list(TECH_BIO = 2)
+ item_state = "cak"
+
+/obj/item/weapon/holder/cat/bluespace
+ origin_tech = list(TECH_BIO = 2, TECH_BLUESPACE = 6)
+ item_state = "bscat"
+
+/obj/item/weapon/holder/cat/spacecat
+ origin_tech = list(TECH_BIO = 2, TECH_MATERIAL = 4)
+ item_state = "spacecat"
+
+/obj/item/weapon/holder/cat/original
+ origin_tech = list(TECH_BIO = 2, TECH_BLUESPACE = 4)
+ item_state = "original"
+
+/obj/item/weapon/holder/cat/breadcat
+ origin_tech = list(TECH_BIO = 2)
+ item_state = "breadcat"
+
+/obj/item/weapon/holder/corgi
+ origin_tech = list(TECH_BIO = 2)
+ item_state = "corgi"
+
+/obj/item/weapon/holder/lisa
+ origin_tech = list(TECH_BIO = 2)
+ item_state = "lisa"
+
+/obj/item/weapon/holder/old_corgi
+ origin_tech = list(TECH_BIO = 2)
+ item_state = "old_corgi"
+
+/obj/item/weapon/holder/void_puppy
+ origin_tech = list(TECH_BIO = 2, TECH_BLUESPACE = 3)
+ item_state = "void_puppy"
+
+/obj/item/weapon/holder/narsian
+ origin_tech = list(TECH_BIO = 2, TECH_ILLEGAL = 3)
+ item_state = "narsian"
+
+/obj/item/weapon/holder/bullterrier
+ origin_tech = list(TECH_BIO = 2)
+ item_state = "bullterrier"
+
+/obj/item/weapon/holder/fox
+ origin_tech = list(TECH_BIO = 2)
+ item_state = "fox"
+
+/obj/item/weapon/holder/pug
+ origin_tech = list(TECH_BIO = 2)
+ item_state = "pug"
+
+/obj/item/weapon/holder/sloth
+ origin_tech = list(TECH_BIO = 2)
+ item_state = "sloth"
+
/obj/item/weapon/holder/borer
origin_tech = list(TECH_BIO = 6)
+ item_state = "brainslug"
/obj/item/weapon/holder/leech
color = "#003366"
origin_tech = list(TECH_BIO = 5, TECH_PHORON = 2)
+/obj/item/weapon/holder/cat/fluff/tabiranth
+ name = "Spirit"
+ desc = "A small, inquisitive feline, who constantly seems to investigate his surroundings."
+ gender = MALE
+ icon_state = "kitten"
+ w_class = ITEMSIZE_SMALL
+
+/obj/item/weapon/holder/cat/kitten
+ icon_state = "kitten"
+ w_class = ITEMSIZE_SMALL
+
+/obj/item/weapon/holder/cat/fluff/bones
+ name = "Bones"
+ desc = "It's Bones! Meow."
+ gender = MALE
+ icon_state = "cat3"
+
+/obj/item/weapon/holder/bird
+ name = "bird"
+ desc = "It's a bird!"
+ icon_state = null
+ item_icons = null
+ w_class = ITEMSIZE_SMALL
+
+/obj/item/weapon/holder/bird/Initialize()
+ . = ..()
+ held_mob?.lay_down()
+
/obj/item/weapon/holder/fish
attack_verb = list("fished", "disrespected", "smacked", "smackereled")
hitsound = 'sound/effects/slime_squish.ogg'
diff --git a/code/modules/mob/living/carbon/human/human_helpers.dm b/code/modules/mob/living/carbon/human/human_helpers.dm
index 81c6605e4a..3e5dce2658 100644
--- a/code/modules/mob/living/carbon/human/human_helpers.dm
+++ b/code/modules/mob/living/carbon/human/human_helpers.dm
@@ -8,7 +8,7 @@
return 1
if(feedback)
if(status[1] == HUMAN_EATING_NO_MOUTH)
- to_chat(src, "Where do you intend to put \the [food]? You don't have a mouth!")
+ to_chat(src, "Where do you intend to put [food]? You don't have a mouth!")
else if(status[1] == HUMAN_EATING_BLOCKED_MOUTH)
to_chat(src, "\The [status[2]] is in the way!")
return 0
@@ -19,7 +19,7 @@
return 1
if(feedback)
if(status[1] == HUMAN_EATING_NO_MOUTH)
- to_chat(feeder, "Where do you intend to put \the [food]? \The [src] doesn't have a mouth!")
+ to_chat(feeder, "Where do you intend to put [food]? \The [src] doesn't have a mouth!")
else if(status[1] == HUMAN_EATING_BLOCKED_MOUTH)
to_chat(feeder, "\The [status[2]] is in the way!")
return 0
diff --git a/code/modules/mob/living/carbon/human/species/station/station_special_abilities_vr.dm b/code/modules/mob/living/carbon/human/species/station/station_special_abilities_vr.dm
index f34a18af3e..7194e4f1fc 100644
--- a/code/modules/mob/living/carbon/human/species/station/station_special_abilities_vr.dm
+++ b/code/modules/mob/living/carbon/human/species/station/station_special_abilities_vr.dm
@@ -910,7 +910,7 @@
to_chat(src, "You can't weave here!")
return
- if(do_after(src, desired_result.time, exclusive = TRUE))
+ if(do_after(src, desired_result.time, exclusive = TASK_USER_EXCLUSIVE))
if(desired_result.cost > species.silk_reserve)
to_chat(src, "You don't have enough silk to weave that!")
return
@@ -969,7 +969,7 @@
to_chat(src, "You can't weave here!")
return
- if(do_after(src, desired_result.time, exclusive = TRUE))
+ if(do_after(src, desired_result.time, exclusive = TASK_USER_EXCLUSIVE))
if(desired_result.cost > species.silk_reserve)
to_chat(src, "You don't have enough silk to weave that!")
return
diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/passive/possum.dm b/code/modules/mob/living/simple_mob/subtypes/animal/passive/possum.dm
index 71e8c3331a..614a15a5b3 100644
--- a/code/modules/mob/living/simple_mob/subtypes/animal/passive/possum.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/animal/passive/possum.dm
@@ -111,6 +111,7 @@
real_name = "opossum"
tt_desc = "Didelphis astrum"
desc = "It's an opossum, a small scavenging marsupial."
+ icon = 'icons/mob/pets.dmi'
icon_state = "possum"
item_state = "possum"
icon_living = "possum"
diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/pets/bird.dm b/code/modules/mob/living/simple_mob/subtypes/animal/pets/bird.dm
index d5ecc67d9f..5741727f69 100644
--- a/code/modules/mob/living/simple_mob/subtypes/animal/pets/bird.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/animal/pets/bird.dm
@@ -38,17 +38,6 @@
emote_hear = list("chirps","caws")
emote_see = list("shakes their head", "ruffles their feathers")
-/obj/item/weapon/holder/bird
- name = "bird"
- desc = "It's a bird!"
- icon_state = null
- item_icons = null
- w_class = ITEMSIZE_SMALL
-
-/obj/item/weapon/holder/bird/Initialize()
- . = ..()
- held_mob?.lay_down()
-
// Subtypes for birbs.
/mob/living/simple_mob/animal/passive/bird/black_bird
name = "common blackbird"
diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/pets/cat.dm b/code/modules/mob/living/simple_mob/subtypes/animal/pets/cat.dm
index 3c8cd73f09..2c203844e3 100644
--- a/code/modules/mob/living/simple_mob/subtypes/animal/pets/cat.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/animal/pets/cat.dm
@@ -31,6 +31,7 @@ var/list/_cat_default_emotes = list(
name = "cat"
desc = "A domesticated, feline pet. Has a tendency to adopt crewmembers."
tt_desc = "E Felis silvestris catus"
+ icon = 'icons/mob/pets.dmi'
icon_state = "cat2"
item_state = "cat2"
@@ -151,15 +152,8 @@ var/list/_cat_default_emotes = list(
return ..()
/mob/living/simple_mob/animal/passive/cat/black
- icon_state = "cat"
- item_state = "cat"
-
-// Leaving this here for now.
-/obj/item/weapon/holder/cat/fluff/bones
- name = "Bones"
- desc = "It's Bones! Meow."
- gender = MALE
icon_state = "cat3"
+ item_state = "cat3"
/mob/living/simple_mob/animal/passive/cat/bones
name = "Bones"
@@ -170,10 +164,85 @@ var/list/_cat_default_emotes = list(
named = TRUE
holder_type = /obj/item/weapon/holder/cat/fluff/bones
-// VOREStation Edit - Adds generic tactical kittens
-/obj/item/weapon/holder/cat/kitten
- icon_state = "kitten"
- w_class = ITEMSIZE_SMALL
+// SPARKLY
+/mob/living/simple_mob/animal/passive/cat/bluespace
+ name = "bluespace cat"
+ desc = "Shiny cat, shiny cat, it's not your fault."
+ tt_desc = "E Felis silvestris argentum"
+ icon_state = "bscat"
+ icon_living = "bscat"
+ icon_rest = null
+ icon_dead = null
+ makes_dirt = 0
+ holder_type = /obj/item/weapon/holder/cat/bluespace
+
+/mob/living/simple_mob/animal/passive/cat/bluespace/death()
+ animate(src, alpha = 0, color = "#0000FF", time = 0.5 SECOND)
+ spawn(0.5 SECOND)
+ qdel(src)
+
+/mob/living/simple_mob/animal/passive/cat/bread
+ name = "bread cat"
+ desc = "Brought lunch to work."
+ tt_desc = "E Felis silvestris breadinum"
+ icon_state = "breadcat"
+ icon_living = "breadcat"
+ icon_rest = "breadcat_rest"
+ icon_dead = "breadcat_dead"
+ //icon_sit = "breadcat_sit"
+ makes_dirt = 0
+ holder_type = /obj/item/weapon/holder/cat/breadcat
+
+/mob/living/simple_mob/animal/passive/cat/original
+ name = "original cat"
+ desc = "Donut steal."
+ tt_desc = "E Felis silvestris originalis"
+ icon_state = "original"
+ icon_living = "original"
+ icon_rest = "original_rest"
+ icon_dead = "original_dead"
+ //icon_sit = "original_sit"
+ makes_dirt = 0
+ holder_type = /obj/item/weapon/holder/cat/original
+
+/mob/living/simple_mob/animal/passive/cat/cak
+ name = "cak"
+ desc = "Optimal combination of things?"
+ tt_desc = "E Felis silvestris dessertus"
+ icon_state = "cak"
+ icon_living = "cak"
+ icon_rest = "cak_rest"
+ icon_dead = "cak_dead"
+ //icon_sit = "cak_sit"
+ makes_dirt = 0
+ holder_type = /obj/item/weapon/holder/cat/cak
+
+/mob/living/simple_mob/animal/passive/cat/space
+ name = "space cat"
+ desc = "Did someone write a song about this cat?"
+ tt_desc = "E Felis silvestris stellaris"
+ icon_state = "spacecat"
+ icon_living = "spacecat"
+ icon_rest = "spacecat_rest"
+ icon_dead = "spacecat_dead"
+ //icon_sit = "spacecat_sit"
+ holder_type = /obj/item/weapon/holder/cat/spacecat
+ makes_dirt = 0
+
+ minbodytemp = 0 // Minimum "okay" temperature in kelvin
+ maxbodytemp = 900 // Maximum of above
+ heat_damage_per_tick = 3 // Amount of damage applied if animal's body temperature is higher than maxbodytemp
+ cold_damage_per_tick = 2 // Same as heat_damage_per_tick, only if the bodytemperature it's lower than minbodytemp
+
+ min_oxy = 0 // Oxygen in moles, minimum, 0 is 'no minimum'
+ max_oxy = 0 // Oxygen in moles, maximum, 0 is 'no maximum'
+ min_tox = 0 // Phoron min
+ max_tox = 0 // Phoron max
+ min_co2 = 0 // CO2 min
+ max_co2 = 0 // CO2 max
+ min_n2 = 0 // N2 min
+ max_n2 = 0 // N2 max
+ unsuitable_atoms_damage = 2 // This damage is taken when atmos doesn't fit all the requirements above
/datum/say_list/cat
speak = list("Meow!","Esp!","Purr!","HSSSSS")
diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/pets/cat_vr.dm b/code/modules/mob/living/simple_mob/subtypes/animal/pets/cat_vr.dm
index cac9780d9b..42f30cc3d8 100644
--- a/code/modules/mob/living/simple_mob/subtypes/animal/pets/cat_vr.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/animal/pets/cat_vr.dm
@@ -31,13 +31,6 @@
"Your form begins to slowly soften and break apart, rounding out Runtime's swollen belly. The carnivorous cat rumbles and purrs happily at the feeling of such a filling meal.")
// Ascian's Tactical Kitten
-/obj/item/weapon/holder/cat/fluff/tabiranth
- name = "Spirit"
- desc = "A small, inquisitive feline, who constantly seems to investigate his surroundings."
- gender = MALE
- icon_state = "kitten"
- w_class = ITEMSIZE_SMALL
-
/mob/living/simple_mob/animal/passive/cat/tabiranth
name = "Spirit"
desc = "A small, inquisitive feline, who constantly seems to investigate his surroundings."
diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/pets/dog.dm b/code/modules/mob/living/simple_mob/subtypes/animal/pets/dog.dm
index 2a579487ed..6903a68d8d 100644
--- a/code/modules/mob/living/simple_mob/subtypes/animal/pets/dog.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/animal/pets/dog.dm
@@ -3,6 +3,7 @@
real_name = "dog"
desc = "It's a dog."
tt_desc = "E Canis lupus familiaris"
+ icon = 'icons/mob/pets.dmi'
icon_state = "corgi"
icon_living = "corgi"
icon_dead = "corgi_dead"
@@ -87,6 +88,7 @@
icon_state = "corgi"
icon_living = "corgi"
icon_dead = "corgi_dead"
+ holder_type = /obj/item/weapon/holder/corgi
/mob/living/simple_mob/animal/passive/dog/corgi/puppy
name = "corgi puppy"
@@ -95,6 +97,7 @@
icon_state = "puppy"
icon_living = "puppy"
icon_dead = "puppy_dead"
+ holder_type = /obj/item/weapon/holder/corgi
//pupplies cannot wear anything.
/mob/living/simple_mob/animal/passive/dog/corgi/puppy/Topic(href, href_list)
@@ -121,6 +124,7 @@
var/turns_since_scan = 0
var/obj/movement_target
makes_dirt = FALSE //VOREStation edit: no more dirt
+ holder_type = /obj/item/weapon/holder/corgi
/mob/living/simple_mob/animal/passive/dog/corgi/Ian/Life()
..()
@@ -185,6 +189,7 @@
response_harm = "kicks"
var/turns_since_scan = 0
var/puppies = 0
+ holder_type = /obj/item/weapon/holder/lisa
//Lisa already has a cute bow!
/mob/living/simple_mob/animal/passive/dog/corgi/Lisa/Topic(href, href_list)
@@ -224,6 +229,35 @@
set_dir(i)
sleep(1)
+//NARSIAN HAS COME
+/mob/living/simple_mob/animal/passive/dog/corgi/narsian
+ name = "Nars-Ian"
+ desc = "It's a corgi???"
+ icon_state = "narsian"
+ icon_living = "narsian"
+ icon_rest = "narsian_rest"
+ icon_dead = "narsian_dead"
+
+ makes_dirt = FALSE
+ holder_type = /obj/item/weapon/holder/narsian
+
+/mob/living/simple_mob/animal/passive/dog/void_puppy
+ name = "void puppy"
+ desc = "My stars!"
+ icon_state = "void_puppy"
+ icon_living = "void_puppy"
+ icon_dead = "void_puppy_dead"
+ holder_type = /obj/item/weapon/holder/void_puppy
+
+/mob/living/simple_mob/animal/passive/dog/bullterrier
+ name = "bull terrier"
+ desc = "It's a bull terrier."
+ icon_state = "bullterrier"
+ icon_living = "bullterrier"
+ icon_dead = "bullterrier_dead"
+ icon_rest = null
+ holder_type = /obj/item/weapon/holder/bullterrier
+
// Tamaskans
/mob/living/simple_mob/animal/passive/dog/tamaskan
name = "tamaskan"
@@ -240,7 +274,6 @@
desc = "It's a tamaskan, the name Spice can be found on its collar."
// Brittany Spaniel
-
/mob/living/simple_mob/animal/passive/dog/brittany
name = "brittany"
real_name = "brittany"
diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/pets/fox_vr.dm b/code/modules/mob/living/simple_mob/subtypes/animal/pets/fox_vr.dm
index 63e7f3fbab..f7524b7be4 100644
--- a/code/modules/mob/living/simple_mob/subtypes/animal/pets/fox_vr.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/animal/pets/fox_vr.dm
@@ -7,7 +7,7 @@
icon_living = "fox2"
icon_dead = "fox2_dead"
icon_rest = "fox2_rest"
- icon = 'icons/mob/fox_vr.dmi'
+ icon = 'icons/mob/pets.dmi'
movement_cooldown = 0.5
see_in_dark = 6
@@ -28,6 +28,7 @@
say_list_type = /datum/say_list/fox
ai_holder_type = /datum/ai_holder/simple_mob/fox
+ holder_type = /obj/item/weapon/holder/fox
var/turns_since_scan = 0
var/mob/flee_target
@@ -224,7 +225,7 @@
name = "syndi-fox"
desc = "It's a DASTARDLY fox! The horror! Call the shuttle!"
tt_desc = "Vulpes malus"
- icon = 'icons/mob/fox_vr.dmi'
+ icon = 'icons/mob/pets.dmi'
icon_state = "syndifox"
icon_living = "syndifox"
icon_dead = "syndifox_dead"
diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/softdog.dm b/code/modules/mob/living/simple_mob/subtypes/vore/softdog.dm
index 61147bf9b4..2d06fc187a 100644
--- a/code/modules/mob/living/simple_mob/subtypes/vore/softdog.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/vore/softdog.dm
@@ -252,7 +252,7 @@
return ..()
if(M.a_intent == I_HELP)
M.visible_message("[M] pets [src].", runemessage = "pets [src]")
- if(do_after(M, 30 SECONDS, exclusive = 1, target = src))
+ if(do_after(M, 30 SECONDS, exclusive = TASK_USER_EXCLUSIVE, target = src))
faction = M.faction
revive()
sight = initial(sight)
diff --git a/code/modules/overmap/sectors.dm b/code/modules/overmap/sectors.dm
index cce1f94cce..f9d41780f8 100644
--- a/code/modules/overmap/sectors.dm
+++ b/code/modules/overmap/sectors.dm
@@ -203,7 +203,7 @@
admin_chat_message(message = "Overmap panic button hit on z[z] ([name]) by '[user?.ckey || "Unknown"]'", color = "#FF2222") //VOREStation Add
var/message = "This is an automated distress signal from a MIL-DTL-93352-compliant beacon transmitting on [PUB_FREQ*0.1]kHz. \
- This beacon was launched from '[name]'. I can provide this additional information to rescuers: [get_distress_info()]. \
+ This beacon was launched from '[initial(name)]'. I can provide this additional information to rescuers: [get_distress_info()]. \
Per the Interplanetary Convention on Space SAR, those receiving this message must attempt rescue, \
or relay the message to those who can. This message will repeat one time in 5 minutes. Thank you for your urgent assistance."
@@ -224,7 +224,7 @@
return "\[X:[x], Y:[y]\]"
/obj/effect/overmap/visitable/proc/distress_update()
- var/message = "This is the final message from the distress beacon launched from '[name]'. I can provide this additional information to rescuers: [get_distress_info()]. \
+ var/message = "This is the final message from the distress beacon launched from '[initial(name)]'. I can provide this additional information to rescuers: [get_distress_info()]. \
Please render assistance under your obligations per the Interplanetary Convention on Space SAR, or relay this message to a party who can. Thank you for your urgent assistance."
for(var/zlevel in levels_for_distress)
diff --git a/code/modules/vore/eating/living_vr.dm b/code/modules/vore/eating/living_vr.dm
index fe9a02adc1..0a430cf044 100644
--- a/code/modules/vore/eating/living_vr.dm
+++ b/code/modules/vore/eating/living_vr.dm
@@ -521,7 +521,7 @@
prey.ai_holder?.react_to_attack(user)
//Timer and progress bar
- if(!do_after(user, swallow_time, prey, exclusive = TRUE))
+ if(!do_after(user, swallow_time, prey, exclusive = TASK_USER_EXCLUSIVE))
return FALSE // Prey escpaed (or user disabled) before timer expired.
// If we got this far, nom successful! Announce it!
@@ -798,7 +798,7 @@
playsound(src, 'sound/items/eatfood.ogg', rand(10,50), 1)
var/T = (istype(M) ? M.hardness/40 : 1) SECONDS //1.5 seconds to eat a sheet of metal. 2.5 for durasteel and diamond & 1 by default (applies to some ores like raw carbon, slag, etc.
to_chat(src, "You start crunching on [I] with your powerful jaws, attempting to tear it apart...")
- if(do_after(feeder, T, ignore_movement = TRUE, exclusive = TRUE)) //Eat on the move, but not multiple things at once.
+ if(do_after(feeder, T, ignore_movement = TRUE, exclusive = TASK_ALL_EXCLUSIVE)) //Eat on the move, but not multiple things at once.
if(feeder != src)
to_chat(feeder, "You feed [I] to [src].")
log_admin("VORE: [feeder] fed [src] [I].")
diff --git a/code/unit_tests/material_tests.dm b/code/unit_tests/material_tests.dm
new file mode 100644
index 0000000000..e3c80b6cd3
--- /dev/null
+++ b/code/unit_tests/material_tests.dm
@@ -0,0 +1,19 @@
+/datum/unit_test/materials_shall_have_names
+ name = "MATERIALS: Materials Shall Have All Names"
+
+/datum/unit_test/materials_shall_have_names/start_test()
+ var/list/failures = list()
+ populate_material_list()
+ for(var/name in global.name_to_material)
+ var/datum/material/mat = global.name_to_material[name]
+ if(!mat)
+ continue // how did we get here?
+ if(!mat.display_name || !mat.use_name || !mat.sheet_singular_name || !mat.sheet_plural_name || !mat.sheet_collective_name)
+ failures[name] = mat.type
+
+ if(length(failures))
+ fail("[length(failures)] material\s had missing name strings: [english_list(failures)].")
+ else
+ pass("All materials had all their name strings.")
+
+ return TRUE
diff --git a/icons/mob/animal.dmi b/icons/mob/animal.dmi
index 47ebeb11fa..fd99eed3f0 100644
Binary files a/icons/mob/animal.dmi and b/icons/mob/animal.dmi differ
diff --git a/icons/mob/eyes.dmi b/icons/mob/eyes.dmi
index 0be51e7c5c..70113e3d8c 100644
Binary files a/icons/mob/eyes.dmi and b/icons/mob/eyes.dmi differ
diff --git a/icons/mob/fox_vr.dmi b/icons/mob/fox_vr.dmi
deleted file mode 100644
index 96b5164df5..0000000000
Binary files a/icons/mob/fox_vr.dmi and /dev/null differ
diff --git a/icons/mob/items/lefthand_holder.dmi b/icons/mob/items/lefthand_holder.dmi
index 3003e5b5a6..e7c1da2fd3 100644
Binary files a/icons/mob/items/lefthand_holder.dmi and b/icons/mob/items/lefthand_holder.dmi differ
diff --git a/icons/mob/items/lefthand_melee.dmi b/icons/mob/items/lefthand_melee.dmi
index 6889aa22b1..1aca98f66f 100644
Binary files a/icons/mob/items/lefthand_melee.dmi and b/icons/mob/items/lefthand_melee.dmi differ
diff --git a/icons/mob/items/righthand_holder.dmi b/icons/mob/items/righthand_holder.dmi
index a29bb598ed..cb874e6181 100644
Binary files a/icons/mob/items/righthand_holder.dmi and b/icons/mob/items/righthand_holder.dmi differ
diff --git a/icons/mob/items/righthand_melee.dmi b/icons/mob/items/righthand_melee.dmi
index 931b902bbe..b4a1a9f341 100644
Binary files a/icons/mob/items/righthand_melee.dmi and b/icons/mob/items/righthand_melee.dmi differ
diff --git a/icons/mob/pets.dmi b/icons/mob/pets.dmi
new file mode 100644
index 0000000000..aefb954350
Binary files /dev/null and b/icons/mob/pets.dmi differ
diff --git a/icons/mob/species/teshari/eyes.dmi b/icons/mob/species/teshari/eyes.dmi
index 250a56d4f9..fafb8b62fc 100644
Binary files a/icons/mob/species/teshari/eyes.dmi and b/icons/mob/species/teshari/eyes.dmi differ
diff --git a/icons/mob/species/vox/eyes.dmi b/icons/mob/species/vox/eyes.dmi
index 384fc15d99..e021abfd67 100644
Binary files a/icons/mob/species/vox/eyes.dmi and b/icons/mob/species/vox/eyes.dmi differ
diff --git a/icons/obj/chemical.dmi b/icons/obj/chemical.dmi
index ed4dab3407..ddba37210a 100644
Binary files a/icons/obj/chemical.dmi and b/icons/obj/chemical.dmi differ
diff --git a/icons/obj/clothing/glasses.dmi b/icons/obj/clothing/glasses.dmi
index 93d368d42c..768235110b 100644
Binary files a/icons/obj/clothing/glasses.dmi and b/icons/obj/clothing/glasses.dmi differ
diff --git a/icons/obj/clothing/masks.dmi b/icons/obj/clothing/masks.dmi
index d7daacb379..5bde3b32d4 100644
Binary files a/icons/obj/clothing/masks.dmi and b/icons/obj/clothing/masks.dmi differ
diff --git a/icons/obj/reagentfillings.dmi b/icons/obj/reagentfillings.dmi
index a0088064db..91d596a570 100644
Binary files a/icons/obj/reagentfillings.dmi and b/icons/obj/reagentfillings.dmi differ
diff --git a/icons/obj/telescience.dmi b/icons/obj/telescience.dmi
index 34d42f100f..28da1b8e73 100644
Binary files a/icons/obj/telescience.dmi and b/icons/obj/telescience.dmi differ
diff --git a/icons/obj/weapons.dmi b/icons/obj/weapons.dmi
index 47a4d21d24..a8b57870bf 100644
Binary files a/icons/obj/weapons.dmi and b/icons/obj/weapons.dmi differ
diff --git a/vorestation.dme b/vorestation.dme
index ed36b9ce12..35e96502c1 100644
--- a/vorestation.dme
+++ b/vorestation.dme
@@ -335,6 +335,7 @@
#include "code\datums\organs.dm"
#include "code\datums\position_point_vector.dm"
#include "code\datums\progressbar.dm"
+#include "code\datums\reference_tracking.dm"
#include "code\datums\riding.dm"
#include "code\datums\soul_link.dm"
#include "code\datums\sun.dm"
@@ -4122,6 +4123,7 @@
#include "code\unit_tests\language_tests.dm"
#include "code\unit_tests\loadout_tests.dm"
#include "code\unit_tests\map_tests.dm"
+#include "code\unit_tests\material_tests.dm"
#include "code\unit_tests\mob_tests.dm"
#include "code\unit_tests\recipe_tests.dm"
#include "code\unit_tests\research_tests.dm"