diff --git a/code/__defines/_compile_options.dm b/code/__defines/_compile_options.dm index d2f61e13dca5..0c60ba0a5ddb 100644 --- a/code/__defines/_compile_options.dm +++ b/code/__defines/_compile_options.dm @@ -1,3 +1,16 @@ // The default value for all uses of set background. Set background can cause gradual lag and is recommended you only turn this on if necessary. // 1 will enable set background. 0 will disable set background. #define BACKGROUND_ENABLED 0 + +// If REFTRACK_IN_CI is defined, the reftracker will run in CI. +#define REFTRACK_IN_CI +#if defined(REFTRACK_IN_CI) && defined(UNIT_TEST) && !defined(SPACEMAN_DMM) +#define REFTRACKING_ENABLED +#define GC_FAILURE_HARD_LOOKUP +#define FIND_REF_NO_CHECK_TICK +#endif + +// parity with previous behavior where TESTING enabled reftracking +#ifdef TESTING +#define REFTRACKING_ENABLED +#endif \ No newline at end of file diff --git a/code/__defines/qdel.dm b/code/__defines/qdel.dm index 48a9f0e09738..fa9cf0a8debe 100644 --- a/code/__defines/qdel.dm +++ b/code/__defines/qdel.dm @@ -5,8 +5,8 @@ #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. +#define QDEL_HINT_FINDREFERENCE 5 //functionally identical to QDEL_HINT_QUEUE if REFTRACKING_ENABLED is not enabled in _compiler_options.dm. + //if REFTRACKING_ENABLED is enabled, qdel will call this object's find_references() verb. #define QDEL_HINT_IFFAIL_FINDREFERENCE 6 //Above but only if gc fails. //defines for the gc_destroyed var @@ -15,6 +15,12 @@ #define GC_QUEUE_HARDDELETE 3 #define GC_QUEUE_COUNT 3 //increase this when adding more steps. +// Defines for the ssgarbage queue items +#define GC_QUEUE_ITEM_QUEUE_TIME 1 //! Time this item entered the queue +#define GC_QUEUE_ITEM_REF 2 //! Ref to the item +#define GC_QUEUE_ITEM_GCD_DESTROYED 3 //! Item's gc_destroyed var value. Used to detect ref reuse. +#define GC_QUEUE_ITEM_INDEX_COUNT 3 //! Number of item indexes, used for allocating the nested lists. Don't forget to increase this if you add a new queue item index + #define GC_QUEUED_FOR_HARD_DEL -1 #define GC_CURRENTLY_BEING_QDELETED -2 diff --git a/code/_onclick/hud/ai_hud.dm b/code/_onclick/hud/ai_hud.dm index b8edcf540c4c..fa4e2200bb63 100644 --- a/code/_onclick/hud/ai_hud.dm +++ b/code/_onclick/hud/ai_hud.dm @@ -11,70 +11,70 @@ screen_loc = ui_ai_core name = "AI Core" icon_state = "ai_core" - proc_path = TYPE_PROC_REF(/mob/living/silicon/ai, core) + proc_path = /mob/living/silicon/ai/proc/core /decl/ai_hud/ai_announcement screen_loc = ui_ai_announcement name = "AI Announcement" icon_state = "announcement" - proc_path = TYPE_PROC_REF(/mob/living/silicon/ai, ai_announcement) + proc_path = /mob/living/silicon/ai/proc/ai_announcement /decl/ai_hud/ai_cam_track screen_loc = ui_ai_cam_track name = "Track With Camera" icon_state = "track" - proc_path = TYPE_PROC_REF(/mob/living/silicon/ai, ai_camera_track) + proc_path = /mob/living/silicon/ai/proc/ai_camera_track input_procs = list(/mob/living/silicon/ai/proc/trackable_mobs = (AI_BUTTON_PROC_BELONGS_TO_CALLER|AI_BUTTON_INPUT_REQUIRES_SELECTION)) /decl/ai_hud/ai_cam_light screen_loc = ui_ai_cam_light name = "Toggle Camera Lights" icon_state = "camera_light" - proc_path = TYPE_PROC_REF(/mob/living/silicon/ai, toggle_camera_light) + proc_path = /mob/living/silicon/ai/proc/toggle_camera_light /decl/ai_hud/ai_cam_change_channel screen_loc = ui_ai_cam_change_channel name = "Jump to Camera Channel" icon_state = "camera" - proc_path = TYPE_PROC_REF(/mob/living/silicon/ai, ai_channel_change) + proc_path = /mob/living/silicon/ai/proc/ai_channel_change input_procs = list(/mob/living/silicon/ai/proc/get_camera_channel_list = (AI_BUTTON_PROC_BELONGS_TO_CALLER|AI_BUTTON_INPUT_REQUIRES_SELECTION)) /decl/ai_hud/ai_sensor screen_loc = ui_ai_sensor name = "Set Sensor Mode" icon_state = "ai_sensor" - proc_path = TYPE_PROC_REF(/mob/living/silicon/ai, sensor_mode) + proc_path = /mob/living/silicon/ai/proc/sensor_mode /decl/ai_hud/ai_manifest screen_loc = ui_ai_crew_manifest name = "Show Crew Manifest" icon_state = "manifest" - proc_path = TYPE_PROC_REF(/mob/living/silicon/ai, run_program) + proc_path = /mob/living/silicon/ai/proc/run_program input_args = list("crewmanifest") /decl/ai_hud/ai_take_image screen_loc = ui_ai_take_image name = "Toggle Camera Mode" icon_state = "take_picture" - proc_path = TYPE_PROC_REF(/mob/living/silicon/ai, ai_take_image) + proc_path = /mob/living/silicon/ai/proc/ai_take_image /decl/ai_hud/ai_view_images screen_loc = ui_ai_view_images name = "View Images" icon_state = "view_images" - proc_path = TYPE_PROC_REF(/mob/living/silicon/ai, ai_view_images) + proc_path = /mob/living/silicon/ai/proc/ai_view_images /decl/ai_hud/ai_laws screen_loc = ui_ai_state_laws name = "State Laws" icon_state = "state_laws" - proc_path = TYPE_PROC_REF(/mob/living/silicon/ai, ai_checklaws) + proc_path = /mob/living/silicon/ai/proc/ai_checklaws /decl/ai_hud/ai_call_shuttle screen_loc = ui_ai_call_shuttle name = "Call Shuttle" icon_state = "call_shuttle" - proc_path = TYPE_PROC_REF(/mob/living/silicon/ai, ai_call_shuttle) + proc_path = /mob/living/silicon/ai/proc/ai_call_shuttle /decl/ai_hud/ai_up screen_loc = ui_ai_up @@ -92,53 +92,53 @@ screen_loc = ui_ai_color name = "Change Floor Color" icon_state = "ai_floor" - proc_path = TYPE_PROC_REF(/mob/living/silicon/ai, change_floor) + proc_path = /mob/living/silicon/ai/proc/change_floor /decl/ai_hud/ai_hologram screen_loc = ui_ai_holo_change name = "Change Hologram" icon_state = "ai_holo_change" - proc_path = TYPE_PROC_REF(/mob/living/silicon/ai, ai_hologram_change) + proc_path = /mob/living/silicon/ai/proc/ai_hologram_change /decl/ai_hud/ai_crew_monitor screen_loc = ui_ai_crew_mon name = "Crew Monitor" icon_state = "crew_monitor" - proc_path = TYPE_PROC_REF(/mob/living/silicon/ai, run_program) + proc_path = /mob/living/silicon/ai/proc/run_program input_args = list("sensormonitor") /decl/ai_hud/ai_power_override screen_loc = ui_ai_power_override name = "Toggle Power Override" icon_state = "ai_p_override" - proc_path = TYPE_PROC_REF(/mob/living/silicon/ai, ai_power_override) + proc_path = /mob/living/silicon/ai/proc/ai_power_override /decl/ai_hud/ai_shutdown screen_loc = ui_ai_shutdown name = "Shutdown" icon_state = "ai_shutdown" - proc_path = TYPE_PROC_REF(/mob/living/silicon/ai, ai_shutdown) + proc_path = /mob/living/silicon/ai/proc/ai_shutdown /decl/ai_hud/ai_move_hologram screen_loc = ui_ai_holo_mov name = "Toggle Hologram Movement" icon_state = "ai_holo_mov" - proc_path = TYPE_PROC_REF(/mob/living/silicon/ai, toggle_hologram_movement) + proc_path = /mob/living/silicon/ai/proc/toggle_hologram_movement /decl/ai_hud/ai_core_icon screen_loc = ui_ai_core_icon name = "Pick Icon" icon_state = "ai_core_pick" - proc_path = TYPE_PROC_REF(/mob/living/silicon/ai, pick_icon) + proc_path = /mob/living/silicon/ai/proc/pick_icon /decl/ai_hud/ai_status screen_loc = ui_ai_status name = "Pick Status" icon_state = "ai_status" - proc_path = TYPE_PROC_REF(/mob/living/silicon/ai, ai_statuschange) + proc_path = /mob/living/silicon/ai/proc/ai_statuschange /decl/ai_hud/ai_inbuilt_comp screen_loc = ui_ai_crew_rec name = "Inbuilt Computer" icon_state = "ai_crew_rec" - proc_path = TYPE_PROC_REF(/mob/living/silicon, access_computer) + proc_path = /mob/living/silicon/proc/access_computer diff --git a/code/_onclick/hud/screen/screen_ai_button.dm b/code/_onclick/hud/screen/screen_ai_button.dm index a7f2861002f3..e2060d6b8a1e 100644 --- a/code/_onclick/hud/screen/screen_ai_button.dm +++ b/code/_onclick/hud/screen/screen_ai_button.dm @@ -1,16 +1,18 @@ /obj/screen/ai_button icon = 'icons/mob/screen/ai.dmi' requires_ui_style = FALSE - var/mob/living/silicon/ai/ai_verb + var/ai_verb var/list/input_procs var/list/input_args var/list/template_icon = list(null, "template") var/image/template_undelay /obj/screen/ai_button/handle_click(mob/user, params) - if(!isAI(user)) - return TRUE + var/mob/living/silicon/ai/A = user + if(!istype(A)) + return TRUE + if(!(ai_verb in A.verbs)) return TRUE @@ -30,7 +32,6 @@ if(!(ai_verb in A.verbs) || A.incapacitated()) return - input_arguments += input_arg if(length(input_args)) diff --git a/code/controllers/subsystems/garbage.dm b/code/controllers/subsystems/garbage.dm index 47c1be9e2452..18f925703957 100644 --- a/code/controllers/subsystems/garbage.dm +++ b/code/controllers/subsystems/garbage.dm @@ -37,7 +37,7 @@ SUBSYSTEM_DEF(garbage) //Queue var/list/queues - #ifdef TESTING + #ifdef REFTRACKING_ENABLED var/list/reference_find_on_fail = list() #endif @@ -130,30 +130,33 @@ SUBSYSTEM_DEF(garbage) lastlevel = level +// 1 from the hard reference in the queue, and 1 from `D` in the code below +#define REFS_WE_EXPECT 2 + //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/refidx in 1 to length(queue)) - var/refID = queue[refidx] - if (isnull(refID)) + for (var/i in 1 to length(queue)) + var/list/L = queue[i] + if (length(L) < GC_QUEUE_ITEM_INDEX_COUNT) count++ if (MC_TICK_CHECK) return continue - var/GCd_at_time = queue[refID] - if(GCd_at_time > cut_off_time) + var/queued_at_time = L[GC_QUEUE_ITEM_QUEUE_TIME] + if(queued_at_time > cut_off_time) break // Everything else is newer, skip them count++ - var/datum/D - D = locate(refID) + var/datum/D = L[GC_QUEUE_ITEM_REF] - if (isnull(D) || D.gc_destroyed != GCd_at_time) // So if something else coincidently gets the same ref, it's not deleted by mistake + // If that's all we've got, send er off + if (refcount(D) == REFS_WE_EXPECT) ++gcedlasttick ++totalgcs pass_counts[level]++ - #ifdef TESTING - reference_find_on_fail -= refID //It's deleted we don't care anymore. + #ifdef REFTRACKING_ENABLED + reference_find_on_fail -= ref(D) //It's deleted we don't care anymore. #endif if (MC_TICK_CHECK) return @@ -161,14 +164,19 @@ SUBSYSTEM_DEF(garbage) // Something's still referring to the qdel'd object. fail_counts[level]++ + switch (level) if (GC_QUEUE_CHECK) - #ifdef TESTING + #ifdef REFTRACKING_ENABLED + // Decides how many refs to look for (potentially) + // Based off the remaining and the ones we can account for + var/remaining_refs = refcount(D) - REFS_WE_EXPECT + var/refID = ref(D) if(reference_find_on_fail[refID]) - D.find_references() + D.find_references(remaining_refs) #ifdef GC_FAILURE_HARD_LOOKUP else - D.find_references() + D.find_references(remaining_refs) #endif reference_find_on_fail -= refID #endif @@ -201,12 +209,14 @@ SUBSYSTEM_DEF(garbage) if (level > GC_QUEUE_COUNT) HardDelete(D) return - var/gctime = world.time - var/refid = "\ref[D]" + var/queue_time = world.time - D.gc_destroyed = gctime + if(D.gc_destroyed <= 0) // hasn't been queued yet, or is queued for harddel/actively being qdeleted + D.gc_destroyed = queue_time var/list/queue = queues[level] - queue[refid] = gctime + // not += for byond reasons + // we include D.gc_destroyed to skip things under the cutoff + queue[++queue.len] = list(queue_time, D, D.gc_destroyed) //this is mainly to separate things profile wise. /datum/controller/subsystem/garbage/proc/HardDelete(datum/D) @@ -240,11 +250,6 @@ SUBSYSTEM_DEF(garbage) message_admins("Error: [type]([refID]) took longer than 1 second to delete (took [time/10] seconds to delete).") postpone(time) -/datum/controller/subsystem/garbage/proc/HardQueue(datum/D) - if (D.gc_destroyed == GC_CURRENTLY_BEING_QDELETED) - queues[GC_QUEUE_FILTER] += D - D.gc_destroyed = GC_QUEUED_FOR_HARD_DEL - /datum/controller/subsystem/garbage/Recover() if (istype(SSgarbage.queues)) for (var/i in 1 to SSgarbage.queues.len) @@ -270,7 +275,7 @@ SUBSYSTEM_DEF(garbage) /datum/qdel_item/New(mytype) name = "[mytype]" -#ifdef TESTING +#ifdef REFTRACKING_ENABLED /proc/qdel_and_find_ref_if_fail(datum/D, force = FALSE) SSgarbage.reference_find_on_fail["\ref[D]"] = TRUE qdel(D, force) @@ -328,7 +333,7 @@ SUBSYSTEM_DEF(garbage) return // Returning LETMELIVE after being told to force destroy // indicates the objects Destroy() does not respect force - #ifdef TESTING + #ifdef REFTRACKING_ENABLED if(!I.no_respect_force) PRINT_STACK_TRACE("WARNING: [D.type] has been force deleted, but is \ returning an immortal QDEL_HINT, indicating it does \ @@ -341,21 +346,22 @@ SUBSYSTEM_DEF(garbage) SSgarbage.Queue(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() GC_CHECK_AM_NULLSPACE(D, "QDEL_HINT_HARDDEL") - SSgarbage.HardQueue(D) + 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. + if (QDEL_HINT_FINDREFERENCE)//qdel will, if REFTRACKING_ENABLED is enabled, display all references to this object, then queue the object for deletion. SSgarbage.Queue(D) - #ifdef TESTING - D.find_references() + #ifdef REFTRACKING_ENABLED + var/remaining_refs = refcount(D) - REFS_WE_EXPECT + D.find_references(remaining_refs) #endif if (QDEL_HINT_IFFAIL_FINDREFERENCE) SSgarbage.Queue(D) - #ifdef TESTING + #ifdef REFTRACKING_ENABLED SSgarbage.reference_find_on_fail["\ref[D]"] = TRUE #endif else - #ifdef TESTING + #ifdef REFTRACKING_ENABLED if(!I.no_hint) PRINT_STACK_TRACE("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 @@ -364,7 +370,7 @@ SUBSYSTEM_DEF(garbage) 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 +#ifdef REFTRACKING_ENABLED /datum/verb/find_refs() set category = "Debug" @@ -378,8 +384,9 @@ SUBSYSTEM_DEF(garbage) return find_references() -/datum/proc/find_references() +/datum/proc/find_references(references_to_clear = INFINITY) running_find_references = type + src.references_to_clear = references_to_clear if(usr && usr.client) if(usr.client.running_find_references) testing("CANCELLED search for references to a [usr.client.running_find_references].") @@ -406,13 +413,19 @@ SUBSYSTEM_DEF(garbage) normal_globals[global_var] = global.vars[global_var] DoSearchVar(normal_globals, "(global) -> ") //globals testing("Finished searching globals") + if(src.references_to_clear == 0) // Found all expected references! + return for(var/atom/atom_thing) //atoms DoSearchVar(atom_thing, "World -> [atom_thing]") + if(src.references_to_clear == 0) // Found all expected references! + return testing("Finished searching atoms") for (var/datum/datum_thing) //datums DoSearchVar(datum_thing, "World -> [datum_thing]") + if(src.references_to_clear == 0) // Found all expected references! + return testing("Finished searching datums") #ifndef FIND_REF_SKIP_CLIENTS @@ -420,6 +433,8 @@ SUBSYSTEM_DEF(garbage) // IT WILL CRASH!!! for (var/client/client_thing) //clients DoSearchVar(client_thing, "World -> [client_thing]") + if(src.references_to_clear == 0) // Found all expected references! + return testing("Finished searching clients") #endif @@ -455,37 +470,47 @@ SUBSYSTEM_DEF(garbage) #define GET_TYPEID(ref) ( ( (length(ref) <= 10) ? "TYPEID_NULL" : copytext(ref, 4, length(ref)-6) ) ) #define IS_NORMAL_LIST(L) (GET_TYPEID("\ref[L]") == TYPEID_NORMAL_LIST) -/datum/proc/DoSearchVar(X, Xname, recursive_limit = 128) +/datum/proc/DoSearchVar(X, container_name, recursive_limit = 128) if(usr && usr.client && !usr.client.running_find_references) return if (!recursive_limit) return + if(references_to_clear == 0) + return #ifndef FIND_REF_NO_CHECK_TICK CHECK_TICK #endif if(istype(X, /datum)) - var/datum/D = X - if(D.last_find_references == last_find_references) + var/datum/datum_container = X + if(datum_container.last_find_references == last_find_references) return - D.last_find_references = last_find_references - var/list/L = D.vars + datum_container.last_find_references = last_find_references + var/list/vars_list = datum_container.vars - for(var/varname in L) + var/is_atom = FALSE + var/is_area = FALSE + if(isatom(datum_container)) + is_atom = TRUE + if(isarea(datum_container)) + is_area = TRUE + for(var/varname in vars_list) #ifndef FIND_REF_NO_CHECK_TICK CHECK_TICK #endif - if (varname == "vars") + //Fun fact, vis_locs don't count for references + if(varname == "vars" || (is_atom && (varname == "vis_locs" || varname == "overlays" || varname == "underlays" || varname == "filters" || varname == "verbs" || (is_area && varname == "contents")))) continue - var/variable = L[varname] + var/variable = vars_list[varname] if(variable == src) - testing("Found [src.type] \ref[src] in [D.type]'s [varname] var. [Xname]") + testing("Found [src.type] \ref[src] in [datum_container.type]'s [varname] var. [container_name]") + references_to_clear -= 1 else if(islist(variable)) - DoSearchVar(variable, "[Xname] -> [varname] (list)", recursive_limit-1) + DoSearchVar(variable, "[container_name] -> [varname] (list)", recursive_limit-1) else if(islist(X)) var/normal = IS_NORMAL_LIST(X) @@ -494,16 +519,33 @@ SUBSYSTEM_DEF(garbage) CHECK_TICK #endif if (I == src) - testing("Found [src.type] \ref[src] in list [Xname].") + testing("Found [src.type] \ref[src] in list [container_name].") + + // This is dumb as hell I'm sorry + // I don't want the garbage subsystem to count as a ref for the purposes of this number + // If we find all other refs before it I want to early exit, and if we don't I want to keep searching past it + var/ignore_ref = FALSE + var/list/queues = SSgarbage.queues + for(var/list/queue in queues) + if(X in queue) + ignore_ref = TRUE + break + if(ignore_ref) + testing("[container_name] does not count as a ref for our count") + else + references_to_clear -= 1 + if(references_to_clear == 0) + testing("All references to [type] \ref[src] found, exiting.") + return else if (I && !isnum(I) && normal) if(X[I] == src) - testing("Found [src.type] \ref[src] in list [Xname]\[[I]\]") + testing("Found [src.type] \ref[src] in list [container_name]\[[I]\]") else if(islist(X[I])) - DoSearchVar(X[I], "[Xname]\[[I]\]", recursive_limit-1) + DoSearchVar(X[I], "[container_name]\[[I]\]", recursive_limit-1) else if (islist(I)) var/list/Xlist = X - DoSearchVar(I, "[Xname]\[[Xlist.Find(I)]\] -> list", recursive_limit-1) + DoSearchVar(I, "[container_name]\[[Xlist.Find(I)]\] -> list", recursive_limit-1) #endif diff --git a/code/controllers/subsystems/overlays.dm b/code/controllers/subsystems/overlays.dm index 42e919ae980f..886508113926 100644 --- a/code/controllers/subsystems/overlays.dm +++ b/code/controllers/subsystems/overlays.dm @@ -209,7 +209,7 @@ SUBSYSTEM_DEF(overlays) if(cut_old) our_overlays = cached_other.Copy() else - our_overlays |= cached_other + LAZYDISTINCTADD(our_overlays, cached_other) if(NOT_QUEUED_ALREADY) QUEUE_FOR_COMPILE else if(cut_old) diff --git a/code/controllers/subsystems/processing/nano.dm b/code/controllers/subsystems/processing/nano.dm index dfaae9e7999e..dd358012c92a 100644 --- a/code/controllers/subsystems/processing/nano.dm +++ b/code/controllers/subsystems/processing/nano.dm @@ -37,11 +37,10 @@ PROCESSING_SUBSYSTEM_DEF(nano) * @return /nanoui Returns the found ui, or null if none exists */ /datum/controller/subsystem/processing/nano/proc/get_open_ui(mob/user, src_object, ui_key) - var/src_object_key = "\ref[src_object]" - if (!open_uis[src_object_key] || !open_uis[src_object_key][ui_key]) + if (!open_uis[src_object] || !open_uis[src_object][ui_key]) return - for (var/datum/nanoui/ui in open_uis[src_object_key][ui_key]) + for (var/datum/nanoui/ui in open_uis[src_object][ui_key]) if (ui.user == user) return ui @@ -54,12 +53,11 @@ PROCESSING_SUBSYSTEM_DEF(nano) */ /datum/controller/subsystem/processing/nano/proc/update_uis(src_object) . = 0 - var/src_object_key = "\ref[src_object]" - if (!open_uis[src_object_key]) + if (!open_uis[src_object]) return - for (var/ui_key in open_uis[src_object_key]) - for (var/datum/nanoui/ui in open_uis[src_object_key][ui_key]) + for (var/ui_key in open_uis[src_object]) + for (var/datum/nanoui/ui in open_uis[src_object][ui_key]) if(ui.src_object && ui.user && ui.src_object.nano_host()) ui.try_update(1) .++ @@ -78,12 +76,11 @@ PROCESSING_SUBSYSTEM_DEF(nano) if (!length(open_uis)) return - var/src_object_key = "\ref[src_object]" - if (!open_uis[src_object_key]) + if (!open_uis[src_object]) return - for (var/ui_key in open_uis[src_object_key]) - for (var/datum/nanoui/ui in open_uis[src_object_key][ui_key]) + for (var/ui_key in open_uis[src_object]) + for (var/datum/nanoui/ui in open_uis[src_object][ui_key]) ui.close() // If it's missing src_object or user, we want to close it even more. .++ @@ -134,9 +131,9 @@ PROCESSING_SUBSYSTEM_DEF(nano) * @return nothing */ /datum/controller/subsystem/processing/nano/proc/ui_opened(datum/nanoui/ui) - var/src_object_key = "\ref[ui.src_object]" - LAZYINITLIST(open_uis[src_object_key]) - LAZYDISTINCTADD(open_uis[src_object_key][ui.ui_key], ui) + var/src_object = ui.src_object + LAZYINITLIST(open_uis[src_object]) + LAZYDISTINCTADD(open_uis[src_object][ui.ui_key], ui) LAZYDISTINCTADD(ui.user.open_uis, ui) START_PROCESSING(SSnano, ui) @@ -149,18 +146,18 @@ PROCESSING_SUBSYSTEM_DEF(nano) * @return int 0 if no ui was removed, 1 if removed successfully */ /datum/controller/subsystem/processing/nano/proc/ui_closed(var/datum/nanoui/ui) - var/src_object_key = "\ref[ui.src_object]" - if (!open_uis[src_object_key] || !open_uis[src_object_key][ui.ui_key]) + var/src_object = ui.src_object + if (!open_uis[src_object] || !open_uis[src_object][ui.ui_key]) return 0 // wasn't open STOP_PROCESSING(SSnano, ui) if(ui.user) // Sanity check in case a user has been deleted (say a blown up borg watching the alarm interface) LAZYREMOVE(ui.user.open_uis, ui) - open_uis[src_object_key][ui.ui_key] -= ui - if(!length(open_uis[src_object_key][ui.ui_key])) - open_uis[src_object_key] -= ui.ui_key - if(!length(open_uis[src_object_key])) - open_uis -= src_object_key + open_uis[src_object][ui.ui_key] -= ui + if(!length(open_uis[src_object][ui.ui_key])) + open_uis[src_object] -= ui.ui_key + if(!length(open_uis[src_object])) + open_uis -= src_object return 1 /** diff --git a/code/controllers/subsystems/ticker.dm b/code/controllers/subsystems/ticker.dm index 6bccadcb70a8..89122cad2f97 100644 --- a/code/controllers/subsystems/ticker.dm +++ b/code/controllers/subsystems/ticker.dm @@ -121,7 +121,7 @@ SUBSYSTEM_DEF(ticker) Master.SetRunLevel(RUNLEVEL_POSTGAME) end_game_state = END_GAME_READY_TO_END INVOKE_ASYNC(src, PROC_REF(declare_completion)) - if(get_config_value(/decl/config/toggle/allow_map_switching) && get_config_value(/decl/config/toggle/auto_map_vote) && global.all_maps.len > 1) + if(get_config_value(/decl/config/toggle/allow_map_switching) && get_config_value(/decl/config/toggle/auto_map_vote) && length(global.votable_maps) > 1) SSvote.initiate_vote(/datum/vote/map/end_game, automatic = 1) else if(mode_finished && (end_game_state <= END_GAME_NOT_OVER)) diff --git a/code/datums/ai/aggressive.dm b/code/datums/ai/aggressive.dm index 505017dbc0c9..6c48ef305e55 100644 --- a/code/datums/ai/aggressive.dm +++ b/code/datums/ai/aggressive.dm @@ -178,7 +178,7 @@ if(!obstacle.can_open(1)) return body.face_atom(obstacle) - body.pry_door(obstacle, (obstacle.pry_mod * body.get_door_pry_time())) + body.pry_door((obstacle.pry_mod * body.get_door_pry_time()), obstacle) return /datum/mob_controller/aggressive/retaliate(atom/source) diff --git a/code/datums/datum.dm b/code/datums/datum.dm index a0aeed07808d..aa855c0b1e3b 100644 --- a/code/datums/datum.dm +++ b/code/datums/datum.dm @@ -10,9 +10,14 @@ /// Used to avoid unnecessary refstring creation in Destroy(). var/tmp/has_state_machine = FALSE -#ifdef TESTING +#ifdef REFTRACKING_ENABLED var/tmp/running_find_references + /// When was this datum last touched by a reftracker? + /// If this value doesn't match with the start of the search + /// We know this datum has never been seen before, and we should check it var/tmp/last_find_references = 0 + /// How many references we're trying to find when searching + var/tmp/references_to_clear = 0 #endif // Default implementation of clean-up code. @@ -54,11 +59,11 @@ cleanup_events(src) if(has_state_machine) - var/list/machines = global.state_machines["\ref[src]"] + var/list/machines = global.state_machines[src] if(length(machines)) for(var/base_type in machines) qdel(machines[base_type]) - global.state_machines -= "\ref[src]" + global.state_machines -= src return QDEL_HINT_QUEUE diff --git a/code/datums/extensions/state_machine.dm b/code/datums/extensions/state_machine.dm index 63e58f0dd671..edba84c7965d 100644 --- a/code/datums/extensions/state_machine.dm +++ b/code/datums/extensions/state_machine.dm @@ -3,16 +3,15 @@ var/global/list/state_machines = list() /proc/get_state_machine(var/datum/holder, var/base_type) if(istype(holder) && base_type && holder.has_state_machine) - var/list/machines = global.state_machines["\ref[holder]"] + var/list/machines = global.state_machines[holder] return islist(machines) && machines[base_type] /proc/add_state_machine(var/datum/holder, var/datum/state_machine/fsm_type) if(istype(holder) && fsm_type) - var/holder_ref = "\ref[holder]" - var/list/machines = global.state_machines[holder_ref] + var/list/machines = global.state_machines[holder] if(!islist(machines)) machines = list() - global.state_machines[holder_ref] = machines + global.state_machines[holder] = machines var/base_type = fsm_type::base_type if(!machines[base_type]) var/datum/state_machine/machine = new fsm_type(holder) @@ -22,12 +21,11 @@ var/global/list/state_machines = list() /proc/remove_state_machine(var/datum/holder, var/base_type) if(istype(holder) && base_type && holder.has_state_machine) - var/holder_ref = "\ref[holder]" - var/list/machines = global.state_machines[holder_ref] + var/list/machines = global.state_machines[holder] if(length(machines)) machines -= base_type if(!length(machines)) - global.state_machines -= holder_ref + global.state_machines -= holder holder.has_state_machine = FALSE return TRUE return FALSE diff --git a/code/datums/extensions/storage/_storage.dm b/code/datums/extensions/storage/_storage.dm index c79e91a7357b..4ff70967e1c6 100644 --- a/code/datums/extensions/storage/_storage.dm +++ b/code/datums/extensions/storage/_storage.dm @@ -252,6 +252,8 @@ var/global/list/_test_storage_items = list() storage_ui?.on_insertion() /datum/storage/proc/update_ui_after_item_removal(obj/item/removed) + if(QDELETED(holder)) + return prepare_ui() storage_ui?.on_post_remove() diff --git a/code/datums/extensions/storage/_storage_ui.dm b/code/datums/extensions/storage/_storage_ui.dm index 6f4db924f328..a3e1f23bfcb5 100644 --- a/code/datums/extensions/storage/_storage_ui.dm +++ b/code/datums/extensions/storage/_storage_ui.dm @@ -216,7 +216,8 @@ closer.screen_loc = "LEFT+[SCREEN_LOC_MOD_FIRST + cols + 1]:[SCREEN_LOC_MOD_DIVIDED],BOTTOM+[SCREEN_LOC_MOD_SECOND]:[SCREEN_LOC_MOD_DIVIDED]" /datum/storage_ui/default/proc/space_orient_objs() - + if(QDELETED(_storage?.holder)) // don't bother if we've been deleted + return var/baseline_max_storage_space = DEFAULT_BOX_STORAGE //storage size corresponding to 224 pixels var/storage_cap_width = 2 //length of sprite for start and end of the box representing total storage space var/stored_cap_width = 4 //length of sprite for start and end of the box representing the stored item diff --git a/code/datums/graph/graph.dm b/code/datums/graph/graph.dm index 249f513e419e..1610a6b4397a 100644 --- a/code/datums/graph/graph.dm +++ b/code/datums/graph/graph.dm @@ -35,6 +35,8 @@ /datum/graph/proc/Connect(var/datum/node/node, var/list/neighbours, var/queue = TRUE) SHOULD_NOT_SLEEP(TRUE) SHOULD_NOT_OVERRIDE(TRUE) + if(QDELETED(src)) + CRASH("Attempted to connect node [node] to a qdeleted graph!") if(!istype(neighbours)) neighbours = list(neighbours) if(!length(neighbours)) @@ -90,8 +92,7 @@ if(neighbours_to_disconnect) neighbours |= neighbours_to_disconnect - if(neighbours.len) - LAZYSET(pending_disconnections, node, neighbours) + LAZYSET(pending_disconnections, node, neighbours) if(queue) SSgraphs_update.Queue(src) @@ -193,8 +194,6 @@ neighbour_edges |= N LAZYCLEARLIST(pending_connections) - if(!LAZYLEN(pending_disconnections)) - return for(var/pending_node_disconnect in pending_disconnections) var/pending_edge_disconnects = pending_disconnections[pending_node_disconnect] @@ -211,7 +210,8 @@ if(!length(other_pending_edge_disconnects)) pending_disconnections -= connected_node - edges[pending_node_disconnect] -= pending_edge_disconnects + if(edges[pending_node_disconnect]) + edges[pending_node_disconnect] -= pending_edge_disconnects if(!length(edges[pending_node_disconnect])) edges -= pending_node_disconnect @@ -225,12 +225,15 @@ var/checked_nodes = list() var/list/nodes_to_traverse = list(root_node) while(length(nodes_to_traverse)) - var/node_to_check = nodes_to_traverse[nodes_to_traverse.len] + var/datum/node/node_to_check = nodes_to_traverse[nodes_to_traverse.len] nodes_to_traverse.len-- + if(QDELETED(node_to_check)) + continue checked_nodes += node_to_check nodes_to_traverse |= ((edges[node_to_check] || list()) - checked_nodes) - all_nodes -= checked_nodes - subgraphs[++subgraphs.len] = checked_nodes + if(length(checked_nodes)) + all_nodes -= checked_nodes + subgraphs[++subgraphs.len] = checked_nodes if(length(subgraphs) == 1) if(!length(nodes)) diff --git a/code/datums/vote/map.dm b/code/datums/vote/map.dm index 9e64ac63bcb2..aabfdde28459 100644 --- a/code/datums/vote/map.dm +++ b/code/datums/vote/map.dm @@ -9,14 +9,14 @@ return ..() /datum/vote/map/setup_vote() - for(var/name in global.all_maps) + for(var/name in global.votable_maps) choices += name ..() /datum/vote/map/report_result() if(..()) return 1 - var/datum/map/M = global.all_maps[result[1]] + var/datum/map/M = global.votable_maps[result[1]] fdel("use_map") text2file(M.path, "use_map") diff --git a/code/game/alpha_masks.dm b/code/game/alpha_masks.dm index 05b23f45eaa2..9c8a341a4823 100644 --- a/code/game/alpha_masks.dm +++ b/code/game/alpha_masks.dm @@ -57,7 +57,7 @@ var/global/list/_alpha_masks = list() // Proc called by /turf/Entered() to update a mob's mask overlay. /atom/movable/proc/update_turf_alpha_mask() set waitfor = FALSE - if(!simulated || updating_turf_alpha_mask) + if(!simulated || QDELETED(src) || updating_turf_alpha_mask) return updating_turf_alpha_mask = TRUE sleep(0) diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 5dd985c6064c..d13760956e3b 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -145,10 +145,12 @@ /atom/proc/try_on_reagent_change() SHOULD_NOT_OVERRIDE(TRUE) set waitfor = FALSE - if(_reagent_update_started >= world.time) + if(QDELETED(src) ||_reagent_update_started >= world.time) return FALSE _reagent_update_started = world.time sleep(0) // Defer to end of tick so we don't drop subsequent reagent updates. + if(QDELETED(src)) + return return on_reagent_change() /atom/proc/on_reagent_change() diff --git a/code/game/atoms_init.dm b/code/game/atoms_init.dm index 3a2f3b619c1d..d1cd533c7967 100644 --- a/code/game/atoms_init.dm +++ b/code/game/atoms_init.dm @@ -80,7 +80,7 @@ /atom/Destroy() // must be done before deletion // TODO: ADD PRE_DELETION OBSERVATION - if(isatom(loc) && loc.storage) + if(isatom(loc) && loc.storage && !QDELETED(loc.storage)) loc.storage.on_item_pre_deletion(src) UNQUEUE_TEMPERATURE_ATOM(src) QDEL_NULL(reagents) @@ -94,7 +94,7 @@ QDEL_NULL(atom_codex_ref) var/atom/oldloc = loc . = ..() - if(isatom(oldloc) && oldloc.storage) + if(isatom(oldloc) && oldloc.storage && !QDELETED(loc.storage)) oldloc.storage.on_item_post_deletion(src) // must be done after deletion // This might need to be moved onto a Del() override at some point. QDEL_NULL(storage) diff --git a/code/game/machinery/vending/_vending.dm b/code/game/machinery/vending/_vending.dm index 866062f2a54b..b3036e173176 100644 --- a/code/game/machinery/vending/_vending.dm +++ b/code/game/machinery/vending/_vending.dm @@ -309,10 +309,8 @@ if (href_list["vend"] && !currently_vending) var/key = text2num(href_list["vend"]) - if(!is_valid_index(key, product_records)) - return TOPIC_REFRESH - var/datum/stored_items/vending_products/R = product_records[key] - if(!istype(R)) + var/datum/stored_items/vending_products/R = LAZYACCESS(product_records, key) + if(!R) return TOPIC_REFRESH // This should not happen unless the request from NanoUI was bad diff --git a/code/game/objects/auras/aura.dm b/code/game/objects/auras/aura.dm index 8d1d35a0680c..5de18f3adf62 100644 --- a/code/game/objects/auras/aura.dm +++ b/code/game/objects/auras/aura.dm @@ -15,6 +15,7 @@ They should also be used for when you want to effect the ENTIRE mob, like having /obj/aura/Destroy() if(user) user.remove_aura(src) + removed() return ..() /obj/aura/proc/added_to(var/mob/living/target) diff --git a/code/game/objects/effects/dirty_floor.dm b/code/game/objects/effects/dirty_floor.dm index ae0c3c0cd9b5..e67ac44b0590 100644 --- a/code/game/objects/effects/dirty_floor.dm +++ b/code/game/objects/effects/dirty_floor.dm @@ -9,6 +9,12 @@ alpha = 0 var/dirt_amount = 0 +// 'the dirt falls through the x' is pretty silly, dirt is generated by people walking. +/obj/effect/decal/cleanable/dirt/begin_falling(lastloc, below) + SHOULD_CALL_PARENT(FALSE) + qdel(src) + return TRUE + /obj/effect/decal/cleanable/dirt/visible dirt_amount = 60 persistent = FALSE // This is a subtype for mapping. diff --git a/code/game/objects/item_mob_overlay.dm b/code/game/objects/item_mob_overlay.dm index d6881129c230..fe253ea6f0bb 100644 --- a/code/game/objects/item_mob_overlay.dm +++ b/code/game/objects/item_mob_overlay.dm @@ -8,13 +8,14 @@ var/global/list/bodypart_to_slot_lookup_table = list( ) /obj/item/proc/reconsider_single_icon(var/update_icon) - use_single_icon = check_state_in_icon(ICON_STATE_INV, icon) || check_state_in_icon(ICON_STATE_WORLD, icon) + var/list/icon_states = get_states_in_icon_cached(icon) // pre-cache to make our up-to-three checks faster + // except now we only do two because i rewrote it + has_inventory_icon = use_single_icon = icon_states[ICON_STATE_INV] + if(!has_inventory_icon) + use_single_icon = icon_states[ICON_STATE_WORLD] if(use_single_icon) - has_inventory_icon = check_state_in_icon(ICON_STATE_INV, icon) icon_state = get_world_inventory_state() . = TRUE - else - has_inventory_icon = FALSE if(. || update_icon) update_icon() @@ -27,6 +28,23 @@ var/global/list/icon_state_cache = list() // isicon() is apparently quite expensive so short-circuit out early if we can. if(!istext(checkstate) || isnull(checkicon) || !(isfile(checkicon) || isicon(checkicon))) return FALSE + var/list/check = _fetch_icon_state_cache_entry(checkicon) // should never return null once we reach this point + return check[checkstate] + +/// A proc for getting an associative list of icon states in an icon. +/// Uses the same cache as check_state_in_icon. +/// Does not copy, MUST NOT BE MUTATED. +/proc/get_states_in_icon_cached(checkicon) /* as OD_MAP(text, OD_BOOL) */ + return _fetch_icon_state_cache_entry(checkicon) || list() + +/// get_states_in_icon_cached but it does a copy, so the return value can be mutated. +/proc/get_states_in_icon(checkicon) /* as OD_MAP(text, OD_BOOL) */ + var/list/out = get_states_in_icon_cached(checkicon) + return out.Copy() + +/proc/_fetch_icon_state_cache_entry(checkicon) + if(isnull(checkicon) || !(isfile(checkicon) || isicon(checkicon))) + return null var/checkkey = "\ref[checkicon]" var/list/check = global.icon_state_cache[checkkey] if(!check) @@ -34,7 +52,7 @@ var/global/list/icon_state_cache = list() for(var/istate in icon_states(checkicon)) check[istate] = TRUE global.icon_state_cache[checkkey] = check - . = check[checkstate] + return check /obj/item/proc/update_world_inventory_state() if(use_single_icon && has_inventory_icon) diff --git a/code/game/objects/items/_item_reagents.dm b/code/game/objects/items/_item_reagents.dm index cf7a09c7a708..abe58d48fbf6 100644 --- a/code/game/objects/items/_item_reagents.dm +++ b/code/game/objects/items/_item_reagents.dm @@ -64,12 +64,12 @@ return TRUE var/had_liquids = length(reagents.liquid_volumes) - var/trans = reagents.trans_to(target, amount) + var/transferred_amount = reagents.trans_to(target, amount) if(had_liquids) playsound(src, 'sound/effects/pour.ogg', 25, 1) else // Sounds more like pouring small pellets or dust. playsound(src, 'sound/effects/refill.ogg', 25, 1) - to_chat(user, SPAN_NOTICE("You transfer [trans] unit\s of the solution to \the [target]. \The [src] now contains [src.reagents.total_volume] units.")) + to_chat(user, SPAN_NOTICE("You transfer [transferred_amount] unit\s of the solution to \the [target]. \The [src] now contains [reagents.total_volume] unit\s.")) return TRUE diff --git a/code/game/objects/items/blueprints.dm b/code/game/objects/items/blueprints.dm index c9c67e53fc72..5a082bb9180c 100644 --- a/code/game/objects/items/blueprints.dm +++ b/code/game/objects/items/blueprints.dm @@ -59,7 +59,7 @@ return FALSE name += " - [S.name]" - desc = "Blueprints of \the [S.name]. There is a \"Classified\" stamp and several coffee stains on it." + desc = "Blueprints of \the [S]. There is a \"Classified\" stamp and several coffee stains on it." valid_z_levels += S.map_z area_prefix = S.name return TRUE diff --git a/code/game/objects/items/devices/lightreplacer.dm b/code/game/objects/items/devices/lightreplacer.dm index 3565259a08e1..34115f49b63e 100644 --- a/code/game/objects/items/devices/lightreplacer.dm +++ b/code/game/objects/items/devices/lightreplacer.dm @@ -110,7 +110,7 @@ if(!user.try_unequip(L)) return TRUE AddUses(1) - to_chat(user, "You insert \the [L.name] into \the [src]. You have [uses] light\s remaining.") + to_chat(user, "You insert \the [L] into \the [src]. You have [uses] light\s remaining.") qdel(L) return TRUE else diff --git a/code/game/objects/items/devices/paint_sprayer.dm b/code/game/objects/items/devices/paint_sprayer.dm index a9dbd8a0878b..88c1e1195d73 100644 --- a/code/game/objects/items/devices/paint_sprayer.dm +++ b/code/game/objects/items/devices/paint_sprayer.dm @@ -223,7 +223,7 @@ return FALSE if(!flooring.can_paint || F.is_floor_damaged()) - to_chat(user, SPAN_WARNING("\The [src] cannot paint \the [F.name].")) + to_chat(user, SPAN_WARNING("\The [src] cannot paint \the [F].")) return FALSE var/list/decal_data = decals[decal] diff --git a/code/game/objects/items/devices/transfer_valve.dm b/code/game/objects/items/devices/transfer_valve.dm index b83f35bca481..2f2506b6aa05 100644 --- a/code/game/objects/items/devices/transfer_valve.dm +++ b/code/game/objects/items/devices/transfer_valve.dm @@ -7,11 +7,27 @@ var/obj/item/tank/tank_one var/obj/item/tank/tank_two var/obj/item/assembly/attached_device - var/mob/attacher = null + var/weakref/attacher_ref = null var/valve_open = 0 var/toggle = 1 movable_flags = MOVABLE_FLAG_PROXMOVE +/obj/item/transfer_valve/Destroy() + if(!QDELETED(tank_one)) + QDEL_NULL(tank_one) + else + tank_one = null + if(!QDELETED(tank_two)) + QDEL_NULL(tank_two) + else + tank_two = null + if(!QDELETED(attached_device)) + QDEL_NULL(attached_device) + else + attached_device = null + attacher_ref = null + return ..() + /obj/item/transfer_valve/attackby(obj/item/item, mob/user) var/turf/location = get_turf(src) // For admin logs if(istype(item, /obj/item/tank)) @@ -56,7 +72,7 @@ global.bombers += "[key_name(user)] attached a [item] to a transfer valve." message_admins("[key_name_admin(user)] attached a [item] to a transfer valve. (JMP)") log_game("[key_name_admin(user)] attached a [item] to a transfer valve.") - attacher = user + attacher_ref = weakref(user) . = TRUE if(.) update_icon() @@ -189,6 +205,7 @@ var/area/A = get_area(bombturf) var/attacher_name = "" + var/mob/attacher = attacher_ref.resolve() if(!attacher) attacher_name = "Unknown" else diff --git a/code/game/objects/items/weapons/ecigs.dm b/code/game/objects/items/weapons/ecigs.dm index dc05320c0908..0a55a70a9509 100644 --- a/code/game/objects/items/weapons/ecigs.dm +++ b/code/game/objects/items/weapons/ecigs.dm @@ -22,6 +22,10 @@ ec_cartridge = new cartridge_type(src) . = ..() +/obj/item/clothing/mask/smokable/ecig/Destroy() + QDEL_NULL(ec_cartridge) + return ..() + /obj/item/clothing/mask/smokable/ecig/setup_power_supply(loaded_cell_type, accepted_cell_type, power_supply_extension_type, charge_value) loaded_cell_type = loaded_cell_type || /obj/item/cell/device/standard accepted_cell_type = accepted_cell_type || /obj/item/cell/device diff --git a/code/game/objects/items/weapons/soap.dm b/code/game/objects/items/weapons/soap.dm index c14047c71f62..7667753288a4 100644 --- a/code/game/objects/items/weapons/soap.dm +++ b/code/game/objects/items/weapons/soap.dm @@ -68,13 +68,13 @@ //So this is a workaround. This also makes more sense from an IC standpoint. ~Carn var/cleaned = FALSE if(user.client && (target in user.client.screen)) - to_chat(user, SPAN_NOTICE("You need to take that [target.name] off before cleaning it.")) + to_chat(user, SPAN_NOTICE("You need to take \the [target] off before cleaning it.")) else if(istype(target,/obj/effect/decal/cleanable/blood)) - to_chat(user, SPAN_NOTICE("You scrub \the [target.name] out.")) + to_chat(user, SPAN_NOTICE("You scrub \the [target] out.")) target.clean() //Blood is a cleanable decal, therefore needs to be accounted for before all cleanable decals. cleaned = TRUE else if(istype(target,/obj/effect/decal/cleanable)) - to_chat(user, SPAN_NOTICE("You scrub \the [target.name] out.")) + to_chat(user, SPAN_NOTICE("You scrub \the [target] out.")) qdel(target) cleaned = TRUE else if(isturf(target) || istype(target, /obj/structure/catwalk)) @@ -90,7 +90,7 @@ to_chat(user, SPAN_NOTICE("You wet \the [src] in the sink.")) wet() else - to_chat(user, SPAN_NOTICE("You clean \the [target.name].")) + to_chat(user, SPAN_NOTICE("You clean \the [target].")) target.clean() //Clean bloodied atoms. Blood decals themselves need to be handled above. cleaned = TRUE diff --git a/code/game/objects/items/weapons/storage/belt.dm b/code/game/objects/items/weapons/storage/belt.dm index 3a5cde951c74..641a512ce20d 100644 --- a/code/game/objects/items/weapons/storage/belt.dm +++ b/code/game/objects/items/weapons/storage/belt.dm @@ -57,12 +57,18 @@ . = ..() set_extension(src, /datum/extension/holster, storage, sound_in, sound_out, can_holster) -/obj/item/belt/holster/attackby(obj/item/W, mob/user) - var/datum/extension/holster/H = get_extension(src, /datum/extension/holster) - if(H.holster(W, user)) +/obj/item/belt/holster/get_stored_inventory() + . = ..() + if(length(.)) + var/datum/extension/holster/holster = get_extension(src, /datum/extension/holster) + if(holster.holstered) + . -= holster.holstered + +/obj/item/belt/holster/attackby(obj/item/used_item, mob/user) + var/datum/extension/holster/holster = get_extension(src, /datum/extension/holster) + if(holster?.holster(used_item, user)) return TRUE - else - . = ..(W, user) + return ..(used_item, user) /obj/item/belt/holster/attack_hand(mob/user) if(!user.check_dexterity(DEXTERITY_HOLD_ITEM, TRUE)) diff --git a/code/game/objects/structures/bookcase.dm b/code/game/objects/structures/bookcase.dm index 06d58e357aab..944bcd3fe0fb 100644 --- a/code/game/objects/structures/bookcase.dm +++ b/code/game/objects/structures/bookcase.dm @@ -168,7 +168,7 @@ var/global/list/station_bookcases = list() add_overlay(book_overlay) var/page_state = "[book_overlay.icon_state]-pages" - if(check_state_in_icon(book_overlay.icon, page_state)) + if(check_state_in_icon(page_state, book_overlay.icon)) var/image/page_overlay = overlay_image(book_overlay.icon, page_state, COLOR_WHITE, RESET_COLOR) page_overlay.pixel_x = book_overlay.pixel_x page_overlay.pixel_y = book_overlay.pixel_y diff --git a/code/game/objects/structures/stool_bed_chair_nest_sofa/wheelchair.dm b/code/game/objects/structures/stool_bed_chair_nest_sofa/wheelchair.dm index caa3e14df932..67640a583fdf 100644 --- a/code/game/objects/structures/stool_bed_chair_nest_sofa/wheelchair.dm +++ b/code/game/objects/structures/stool_bed_chair_nest_sofa/wheelchair.dm @@ -141,7 +141,7 @@ user.visible_message("[user] starts to lay out \the [src].") if(do_after(user, 4 SECONDS, src)) var/obj/structure/bed/chair/wheelchair/W = new structure_form_type(get_turf(user)) - user.visible_message(SPAN_NOTICE("[user] lays out \the [W.name].")) + user.visible_message(SPAN_NOTICE("[user] lays out \the [W].")) W.add_fingerprint(user) qdel(src) diff --git a/code/game/objects/structures/well.dm b/code/game/objects/structures/well.dm index 639cc2b263a1..eb6f59bc68f3 100644 --- a/code/game/objects/structures/well.dm +++ b/code/game/objects/structures/well.dm @@ -61,13 +61,6 @@ if(!is_processing && auto_refill) START_PROCESSING(SSobj, src) -/obj/structure/reagent_dispensers/well/attackby(obj/item/W, mob/user) - . = ..() - if(!. && user.check_intent(I_FLAG_HELP) && reagents?.total_volume > FLUID_PUDDLE) - user.visible_message(SPAN_NOTICE("\The [user] dips \the [W] into \the [reagents.get_primary_reagent_name()].")) - W.fluid_act(reagents) - return TRUE - /obj/structure/reagent_dispensers/well/Process() if(!reagents || !auto_refill) // if we're full, we only stop at the end of the proc; we need to check for contaminants first return PROCESS_KILL diff --git a/code/game/turfs/floors/floor_attackby.dm b/code/game/turfs/floors/floor_attackby.dm index c87c7b79ce95..d023e3d2ee58 100644 --- a/code/game/turfs/floors/floor_attackby.dm +++ b/code/game/turfs/floors/floor_attackby.dm @@ -73,8 +73,8 @@ if((istype(flooring) && flooring.constructed) || !istype(used_item) || !istype(user)) return FALSE - flooring = get_base_flooring() - if(istype(flooring) && flooring.constructed) + var/decl/flooring/base_flooring = get_base_flooring() + if(istype(base_flooring) && base_flooring.constructed) return FALSE if(!istype(used_item, /obj/item/stack/material/ore) && !istype(used_item, /obj/item/stack/material/lump)) @@ -84,11 +84,11 @@ to_chat(user, SPAN_WARNING("\The [src] is flush with ground level and cannot be backfilled.")) return TRUE - if(!used_item.material?.can_backfill_turf_type) + if(!used_item.material?.can_backfill_floor_type) to_chat(user, SPAN_WARNING("You cannot use \the [used_item] to backfill \the [src].")) return TRUE - var/can_backfill = islist(used_item.material.can_backfill_turf_type) ? is_type_in_list(src, used_item.material.can_backfill_turf_type) : istype(src, used_item.material.can_backfill_turf_type) + var/can_backfill = islist(used_item.material.can_backfill_floor_type) ? is_type_in_list(flooring, used_item.material.can_backfill_floor_type) : istype(flooring, used_item.material.can_backfill_floor_type) if(!can_backfill) to_chat(user, SPAN_WARNING("You cannot use \the [used_item] to backfill \the [src].")) return TRUE diff --git a/code/game/turfs/floors/floor_icon.dm b/code/game/turfs/floors/floor_icon.dm index 5e060f358487..c4c7cbb83153 100644 --- a/code/game/turfs/floors/floor_icon.dm +++ b/code/game/turfs/floors/floor_icon.dm @@ -45,11 +45,14 @@ layer = initial(layer) if(istype(flooring) && !flooring.render_trenches) // TODO: Update pool tiles/edges to behave properly with this new system. + default_pixel_z = initial(default_pixel_z) + pixel_z = default_pixel_z return FALSE var/my_height = get_physical_height() - if(my_height < 0) - + if(my_height >= 0) + default_pixel_z = initial(default_pixel_z) + else var/height_ratio = clamp(abs(my_height) / FLUID_DEEP, 0, 1) default_pixel_z = -(min(HEIGHT_OFFSET_RANGE, round(HEIGHT_OFFSET_RANGE * height_ratio))) pixel_z = default_pixel_z @@ -76,7 +79,7 @@ var/trench_icon = (istype(neighbor) && neighbor.get_trench_icon()) || get_trench_icon() if(trench_icon) // cache the trench image, keyed by icon and color - var/trench_color = isatom(neighbor) ? neighbor.color : color + var/trench_color = isatom(neighbor) ? neighbor.get_color() : get_color() var/trench_icon_key = "[ref(trench_icon)][trench_color]" I = _trench_image_cache[trench_icon_key] if(!I) @@ -97,6 +100,7 @@ I.appearance_flags |= RESET_COLOR | RESET_ALPHA _height_north_shadow_cache[shadow_alpha_key] = I add_overlay(I) + pixel_z = default_pixel_z /turf/floor/on_update_icon(var/update_neighbors) . = ..() diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index cf4d6f67920d..4bea938640e6 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -284,19 +284,6 @@ if(IS_COIL(W) && try_build_cable(W, user)) return TRUE - if(reagents?.total_volume >= FLUID_PUDDLE) - if(ATOM_IS_OPEN_CONTAINER(W) && W.reagents) - var/taking = min(reagents.total_volume, REAGENTS_FREE_SPACE(W.reagents)) - if(taking > 0) - to_chat(user, SPAN_NOTICE("You fill \the [W] with [reagents.get_primary_reagent_name()] from \the [src].")) - reagents.trans_to(W, taking) - return TRUE - - if(user.check_intent(I_FLAG_HELP)) - user.visible_message(SPAN_NOTICE("\The [user] dips \the [W] into \the [reagents.get_primary_reagent_name()].")) - W.fluid_act(reagents) - return TRUE - return ..() /turf/Enter(atom/movable/mover, atom/forget) @@ -802,6 +789,9 @@ /turf/get_affecting_weather() return weather +/turf/can_be_poured_into(atom/source) + return !density + /turf/get_alt_interactions(mob/user) . = ..() LAZYADD(., /decl/interaction_handler/show_turf_contents) diff --git a/code/game/turfs/turf_fluids.dm b/code/game/turfs/turf_fluids.dm index 0ea31b04c6f3..7d0cec0a34f0 100644 --- a/code/game/turfs/turf_fluids.dm +++ b/code/game/turfs/turf_fluids.dm @@ -179,6 +179,8 @@ var/decl/material/primary_reagent = reagents.get_primary_reagent_decl() if(primary_reagent && (REAGENT_VOLUME(reagents, primary_reagent.type) >= primary_reagent.slippery_amount)) last_slipperiness = primary_reagent.slipperiness + else + last_slipperiness = 0 if(!fluid_overlay) fluid_overlay = new(src, TRUE) fluid_overlay.update_icon() @@ -190,6 +192,7 @@ SSfluids.pending_flows -= src if(last_slipperiness > 0) wet_floor(last_slipperiness) + last_slipperiness = 0 for(var/checkdir in global.cardinal) var/turf/neighbor = get_step_resolving_mimic(src, checkdir) diff --git a/code/game/world.dm b/code/game/world.dm index 079e43ae5439..eb1715e424fb 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -146,11 +146,14 @@ var/global/world_topic_last = world.timeofday return command.try_use(T, addr, master, key) +var/global/_reboot_announced = FALSE /world/Reboot(var/reason) if(get_config_value(/decl/config/toggle/wait_for_sigusr1_reboot) && reason != 3) text2file("foo", "reboot_called") - to_world("World reboot waiting for external scripts. Please be patient.") + if(!global._reboot_announced) + to_world("World reboot waiting for external scripts. Please be patient.") + global._reboot_announced = TRUE global.Master.restart_timeout = 5 MINUTES return diff --git a/code/modules/admin/verbs/modifyvariables.dm b/code/modules/admin/verbs/modifyvariables.dm index 8605ad977a82..06c04257babc 100644 --- a/code/modules/admin/verbs/modifyvariables.dm +++ b/code/modules/admin/verbs/modifyvariables.dm @@ -318,6 +318,7 @@ variable = param_var_name + // TODO: check for list-typed O? Proc does not exist on non-datum types. var_value = O.get_variable_value(variable) if(autodetect_class) diff --git a/code/modules/assembly/assembly.dm b/code/modules/assembly/assembly.dm index a4cf742c410f..47dcc913a450 100644 --- a/code/modules/assembly/assembly.dm +++ b/code/modules/assembly/assembly.dm @@ -12,7 +12,7 @@ var/secured = 1 var/list/attached_overlays = null - var/obj/item/assembly_holder/holder = null + var/obj/item/assembly_holder/holder = null // currently can be a TTV or assemblyholder, todo make ttv use assemblyholder var/cooldown = 0//To prevent spam var/wires = WIRE_RECEIVE | WIRE_PULSE @@ -24,10 +24,7 @@ /obj/item/assembly/Destroy() if(!QDELETED(holder)) - if(holder.a_left == src) - holder.a_left = null - if(holder.a_right == src) - holder.a_right = null + // the holder has the responsibility to clear its associated vars on destroy QDEL_NULL(holder) else holder = null diff --git a/code/modules/client/ui_styles/_ui_style.dm b/code/modules/client/ui_styles/_ui_style.dm index 5e0a5ba34c41..1a5885cfac67 100644 --- a/code/modules/client/ui_styles/_ui_style.dm +++ b/code/modules/client/ui_styles/_ui_style.dm @@ -46,7 +46,7 @@ var/check_icon = icons[ui_key] var/list/missing_states = list() var/list/checking_states = states_to_check[ui_key] - var/list/remaining_states = icon_states(check_icon) + var/list/remaining_states = get_states_in_icon(check_icon) for(var/check_state in checking_states) remaining_states -= check_state if(!check_state_in_icon(check_state, check_icon)) diff --git a/code/modules/clothing/masks/voice.dm b/code/modules/clothing/masks/voice.dm index edee4acd8eb8..8ecc47d727ce 100644 --- a/code/modules/clothing/masks/voice.dm +++ b/code/modules/clothing/masks/voice.dm @@ -30,3 +30,7 @@ /obj/item/clothing/mask/chameleon/voice/Initialize() . = ..() changer = new(src) + +/obj/item/clothing/mask/chameleon/voice/Destroy() + QDEL_NULL(changer) + return ..() diff --git a/code/modules/clothing/spacesuits/rig/modules/combat.dm b/code/modules/clothing/spacesuits/rig/modules/combat.dm index 0386948c6020..f6a35661567b 100644 --- a/code/modules/clothing/spacesuits/rig/modules/combat.dm +++ b/code/modules/clothing/spacesuits/rig/modules/combat.dm @@ -48,7 +48,7 @@ /obj/item/rig_module/device/flash/installed() . = ..() - if(!holder.gloves)//gives select option for gloveless suits, why even use rig at this point + if(!holder?.gloves)//gives select option for gloveless suits, why even use rig at this point selectable = 1 activates_on_touch = 0 toggleable = 0 diff --git a/code/modules/clothing/spacesuits/rig/modules/utility.dm b/code/modules/clothing/spacesuits/rig/modules/utility.dm index 7626ac00c32e..8fa00d9ada6a 100644 --- a/code/modules/clothing/spacesuits/rig/modules/utility.dm +++ b/code/modules/clothing/spacesuits/rig/modules/utility.dm @@ -313,14 +313,16 @@ voice_holder.active = 0 /obj/item/rig_module/voice/installed() - ..() - holder.speech = src - holder.verbs |= /obj/item/rig/proc/alter_voice + . = ..() + if(holder) + holder.speech = src + holder.verbs |= /obj/item/rig/proc/alter_voice /obj/item/rig_module/voice/removed() - ..() - holder.speech = null - holder.verbs -= /obj/item/rig/proc/alter_voice + if(holder) + holder.speech = null + holder.verbs -= /obj/item/rig/proc/alter_voice + . = ..() /obj/item/rig_module/voice/engage() diff --git a/code/modules/clothing/spacesuits/rig/modules/vision.dm b/code/modules/clothing/spacesuits/rig/modules/vision.dm index 3b19bf496531..9ba365b34fa0 100644 --- a/code/modules/clothing/spacesuits/rig/modules/vision.dm +++ b/code/modules/clothing/spacesuits/rig/modules/vision.dm @@ -160,8 +160,14 @@ // There should only ever be one vision module installed in a suit. /obj/item/rig_module/vision/installed() - ..() - holder.visor = src + . = ..() + if(holder) + holder.visor = src + +/obj/item/rig_module/vision/removed() + if(holder) + holder.visor = null + . = ..() /obj/item/rig_module/vision/engage() diff --git a/code/modules/crafting/pottery/pottery_structures.dm b/code/modules/crafting/pottery/pottery_structures.dm index 2c9a369296b4..c952e9b81711 100644 --- a/code/modules/crafting/pottery/pottery_structures.dm +++ b/code/modules/crafting/pottery/pottery_structures.dm @@ -6,7 +6,7 @@ density = TRUE cap_last_fuel_burn = null - var/list/pottery = list() + var/list/pottery var/maximum_items = 3 var/firebox_open = TRUE @@ -17,12 +17,12 @@ /obj/structure/fire_source/kiln/high_temperature material = /decl/material/solid/stone/pottery -/obj/structure/fire_source/kiln/remove_atom(atom/movable/thing) +/obj/structure/fire_source/kiln/Exited(atom/movable/thing) . = ..() - pottery -= thing + LAZYREMOVE(pottery, thing) /obj/structure/fire_source/kiln/get_removable_atoms() - . = pottery?.Copy() + . = pottery?.Copy() || list() if(firebox_open) . |= ..() @@ -38,14 +38,14 @@ if(length(other)) LAZYDISTINCTADD(., other) -/obj/structure/fire_source/kiln/attackby(obj/item/W, mob/user) +/obj/structure/fire_source/kiln/attackby(obj/item/used_item, mob/user) if(firebox_open) return ..() if(length(pottery) >= maximum_items) to_chat(user, SPAN_WARNING("\The [src] is full, take something out first.")) - else if(user.try_unequip(W, src)) - user.visible_message("\The [user] slides \the [W] into \the [src].") - pottery += W + else if(user.try_unequip(used_item, src)) + user.visible_message("\The [user] slides \the [used_item] into \the [src].") + LAZYADD(pottery, used_item) return TRUE /obj/structure/fire_source/kiln/get_alt_interactions(var/mob/user) diff --git a/code/modules/crafting/slapcrafting/_crafting_stage.dm b/code/modules/crafting/slapcrafting/_crafting_stage.dm index b72c4d4cb8e2..50ede2c90bd8 100644 --- a/code/modules/crafting/slapcrafting/_crafting_stage.dm +++ b/code/modules/crafting/slapcrafting/_crafting_stage.dm @@ -76,17 +76,18 @@ . = !consume_completion_trigger || user.try_unequip(thing, target) if(. && stack_consume_amount > 0) var/obj/item/stack/stack = thing - if(!istype(stack) || stack.amount < stack_consume_amount) + if(!istype(stack) || stack.get_amount() < stack_consume_amount) on_insufficient_material(user, stack) return FALSE var/obj/item/stack/used_stack - if(stack.amount == stack_consume_amount) + if(stack.amount > stack_consume_amount) + used_stack = stack.split(stack_consume_amount) + else if(!user.try_unequip(thing, target)) return FALSE used_stack = stack - else - used_stack = stack.split(stack_consume_amount) - used_stack.forceMove(target) + if(!QDELETED(used_stack)) + used_stack.forceMove(target) target?.update_icon() /decl/crafting_stage/proc/on_insufficient_material(var/mob/user, var/obj/item/stack/thing) diff --git a/code/modules/economy/worth_currency.dm b/code/modules/economy/worth_currency.dm index ed12b938e92c..9e5de940b128 100644 --- a/code/modules/economy/worth_currency.dm +++ b/code/modules/economy/worth_currency.dm @@ -70,13 +70,13 @@ if(!name_singular) . += "No singular name set." - var/list/coinage_states = icon_states(icon) + var/list/coinage_states = get_states_in_icon_cached(icon) // cache this to avoid excessive ref() usage for(var/datum/denomination/denomination in denominations) if(!istext(denomination.name)) . += "Non-text name found for '[denomination.type]'." else if(!(denomination.state in coinage_states)) . += "State '[denomination.state]' not found in icon file for '[denomination.type]'." - else if(denomination.mark && !(denomination.mark in coinage_states)) + else if(denomination.mark && !coinage_states[denomination.mark]) . += "Mark state '[denomination.mark]' not found in icon file for '[denomination.type]'." else if(!isnum(denomination.marked_value)) . += "Non-numerical denomination marked value found for '[denomination]'." diff --git a/code/modules/events/dust.dm b/code/modules/events/dust.dm index 736b18d1c976..b1c6d7c59819 100644 --- a/code/modules/events/dust.dm +++ b/code/modules/events/dust.dm @@ -14,13 +14,17 @@ The "dust" will damage the hull of the station causin minor hull breaches. command_announcement.Announce("The [location_name()] is now passing through a belt of space dust.", "[location_name()] Sensor Array", zlevels = affecting_z) /datum/event/dust/tick() - if(world.time > last_wave + min_delay && prob(10)) + if(world.time > last_wave + min_delay && prob(10) && length(affecting_z)) dust_swarm(severity, affecting_z) /datum/event/dust/end() command_announcement.Announce("The [location_name()] has now passed through the belt of space dust.", "[location_name()] Sensor Array", zlevels = affecting_z) /proc/dust_swarm(var/strength = EVENT_LEVEL_MUNDANE, var/list/zlevels) + + if(!length(zlevels)) + return // Not sure how this happened, but saw it in a runtime on Pyrelight. + var/numbers = rand(strength * 10, strength * 15) var/start_dir = pick(global.cardinal) diff --git a/code/modules/events/rogue_drones.dm b/code/modules/events/rogue_drones.dm index a6decd690ff0..c5d0df69341e 100644 --- a/code/modules/events/rogue_drones.dm +++ b/code/modules/events/rogue_drones.dm @@ -38,9 +38,7 @@ var/num_recovered = 0 for(var/mob/living/simple_animal/hostile/malf_drone/D in drones_list) spark_at(D.loc) - D.z = SSmapping.admin_levels[1] D.has_loot = 0 - qdel(D) num_recovered++ diff --git a/code/modules/hydroponics/seed_storage.dm b/code/modules/hydroponics/seed_storage.dm index 2d609cf629e3..840d453ce501 100644 --- a/code/modules/hydroponics/seed_storage.dm +++ b/code/modules/hydroponics/seed_storage.dm @@ -3,14 +3,12 @@ var/amount var/datum/seed/seed_type // Keeps track of what our seed is var/list/obj/item/seeds/seeds = list() // Tracks actual objects contained in the pile - var/ID -/datum/seed_pile/New(var/obj/item/seeds/O, var/ID) +/datum/seed_pile/New(var/obj/item/seeds/O) name = O.name amount = 1 seed_type = O.seed seeds += O - src.ID = ID /datum/seed_pile/proc/matches(var/obj/item/seeds/O) if (O.seed == seed_type) @@ -195,7 +193,8 @@ if ("soil" in scanner) dat += "