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"