diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index d25839b92fcb..2d912d710405 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -641,6 +641,10 @@ Isn't that confusing?
There is also an undocumented keyword called `static` that has the same behaviour as global but more correctly describes BYOND's behaviour. Therefore, we always use static instead of global where we need it, as it reduces suprise when reading BYOND code.
+### Don't create code that hangs references
+
+This is part of the larger issue of hard deletes, read this file for more info: [Guide to Harddels](.\guides\HARDDELETES.md)
+
## Pull Request Process
There is no strict process when it comes to merging pull requests. Pull requests will sometimes take a while before they are looked at by a maintainer; the bigger the change, the more time it will take before they are accepted into the code. Every team member is a volunteer who is giving up their own time to help maintain and contribute, so please be courteous and respectful. Here are some helpful ways to make it easier for you and for the maintainers when making a pull request.
diff --git a/.github/guides/HARDDELETES.md b/.github/guides/HARDDELETES.md
new file mode 100644
index 000000000000..defc09360af6
--- /dev/null
+++ b/.github/guides/HARDDELETES.md
@@ -0,0 +1,289 @@
+# Hard Deletes
+
+> Garbage collection is pretty gothic when you think about it.
+>
+>An object in code is like a ghost, clinging to its former life, and especially to the people it knew. It can only pass on and truly die when it has dealt with its unfinished business. And only when its been forgotten by everyone who ever knew it. If even one other object remembers it, it has a connection to the living world that lets it keep hanging on
+>
+>There is a kind of sombre tone to fixing GC errors too, its almost shamanistic, making sure all these little objects clear up their final affairs in life before they die, to ensure they don't become ghosts
+>
+> -- Nanako
+
+### Table of contents
+
+1. [What is hard deletion](#What-is-hard-deletion)
+2. [Causes of hard deletes](#causes-of-hard-deletes)
+3. [Detecting hard deletes](#detecting-hard-deletes)
+4. [Techniques for fixing hard deletes](#techniques-for-fixing-hard-deletes)
+5. [Help my code is erroring how fix](#help-my-code-is-erroring-how-fix)
+
+
+## What is Hard Deletion
+
+Hard deletion is a very expensive operation that basically clears all references to some "thing" from memory. Objects that undergo this process are referred to as hard deletes, or simply harddels
+
+What follows is a discussion of the theory behind this, why we would ever do it, and the what we do to avoid doing it as often as possible
+
+I'm gonna be using words like references and garbage collection, but don't worry, it's not complex, just a bit hard to pierce
+
+### Why do we need to Hard Delete?
+
+Ok so let's say you're some guy called Jerry, and you're writing a programming language
+
+You want your coders to be able to pass around objects without doing a full copy. So you'll store the pack of data somewhere in memory
+
+```dm
+/someobject
+ var/id = 42
+ var/name = "some shit"
+```
+
+Then you want them to be able to pass that object into say a proc, without doing a full copy. So you let them pass in the object's location in memory instead
+This is called passing something by reference
+
+```dm
+someshit(someobject) //This isn't making a copy of someobject, it's passing in a reference to it
+```
+
+This of course means they can store that location in memory in another object's vars, or in a list, or whatever
+
+```dm
+/datum
+ var/reference
+
+/proc/someshit(mem_location)
+ var/datum/some_obj = new()
+ some_obj.reference = mem_location
+```
+
+But what happens when you get rid of the object we're passing around references to? If we just cleared it out from memory, everything that holds a reference to it would suddenly be pointing to nowhere, or worse, something totally different!
+
+So then, you've gotta do something to clean up these references when you want to delete an object
+
+We could hold a list of references to everything that references us, but god, that'd get really expensive wouldn't it
+
+Why not keep count of how many times we're referenced then? If an object's ref count is ever 0, nothing whatsoever cares about it, so we can freely get rid of it
+
+But if something's holding onto a reference to us, we're not gonna have any idea where or what it is
+
+So I guess you should scan all of memory for that reference?
+
+```dm
+del(someobject) //We now need to scan memory until we find the thing holding a ref to us, and clear it
+```
+
+This pattern is about how BYOND handles this problem of hanging references, or Garbage Collection
+
+It's not a broken system, but as you can imagine scanning all of memory gets expensive fast
+
+What can we do to help that?
+
+### How we can avoid hard deletes
+
+If hard deletion is so slow, we're gonna need to clean up all our references ourselves
+
+In our codebase we do this with `/datum/proc/Destroy()`, a proc called by `qdel()`, whose purpose I will explain later
+
+This procs only job is cleaning up references to the object it's called on. Nothing more, nothing else. Don't let me catch you giving it side effects
+
+There's a long long list of things this does, since we use it a TON. So I can't really give you a short description. It will always move the object to nullspace though
+
+## Causes Of Hard Deletes
+
+Now that you know the theory, let's go over what can actually cause hard deletes. Some of this is obvious, some of it's much less so.
+
+The BYOND reference has a list [Here](https://secure.byond.com/docs/ref/#/DM/garbage), but it's not a complete one
+
+* Stored in a var
+* An item in a list, or associated with a list item
+* Has a tag
+* Is on the map (always true for turfs)
+* Inside another atom's contents
+* Inside an atom's vis_contents
+* A temporary value in a still-running proc
+* Is a mob with a key
+* Is an image object attached to an atom
+
+Let's briefly go over the more painful ones yeah?
+
+### Sleeping procs
+
+Any proc that calls `sleep()`, `spawn()`, or anything that creates a separate "thread" (not technically a thread, but it's the same in these terms. Not gonna cause any race conditions tho) will hang references to any var inside it. This includes the usr it started from, the src it was called on, and any vars created as a part of processing
+
+### Static vars
+
+`/static` and `/global` vars count for this too, they'll hang references just as well as anything. Be wary of this, these suckers can be a pain to solve
+
+### Range() and View() like procs
+
+Some internal BYOND procs will hold references to objects passed into them for a time after the proc is finished doing work, because they cache the returned info to make some code faster. You should never run into this issue, since we wait for what should be long enough to avoid this issue as a part of garbage collection
+
+This is what `qdel()` does by the by, it literally just means queue deletion. A reference to the object gets put into a queue, and if it still exists after 5 minutes or so, we hard delete it
+
+### Walk() procs
+
+Calling `walk()` on something will put it in an internal queue, which it'll remain in until `walk(thing, 0)` is called on it, which removes it from the queue
+
+This sort is very cheap to harddel, since BYOND prioritizes checking this queue first when it's clearing refs, but it should be avoided since it causes false positives
+
+You can read more about how BYOND prioritizes these things [Here](https://www.patreon.com/posts/diving-for-35855766)
+
+## Detecting Hard Deletes
+
+For very simple hard deletes, simple inspection should be enough to find them. Look at what the object does during `Initialize()`, and see if it's doing anything it doesn't undo later.
+If that fails, search the object's typepath, and look and see if anything is holding a reference to it without regard for the object deleting
+
+BYOND currently doesn't have the capability to give us information about where a hard delete is. Fortunately we can search for most all of then ourselves.
+The procs to perform this search are hidden behind compile time defines, since they'd be way too risky to expose to admin button pressing
+
+If you're having issues solving a harddel and want to perform this check yourself, go to `_compile_options.dm` and uncomment `TESTING`, `REFERENCE_TRACKING`, and `GC_FAILURE_HARD_LOOKUP`
+
+You can read more about what each of these do in that file, but the long and short of it is if something would hard delete our code will search for the reference (This will look like your game crashing, just hold out) and print information about anything it finds to the runtime log, which you can find inside the round folder inside `/data/logs/year/month/day`
+
+It'll tell you what object is holding the ref if it's in an object, or what pattern of list transversal was required to find the ref if it's hiding in a list of some sort
+
+## Techniques For Fixing Hard Deletes
+
+Once you've found the issue, it becomes a matter of making sure the ref is cleared as a part of Destroy(). I'm gonna walk you through a few patterns and discuss how you might go about fixing them
+
+### Our Tools
+
+First and simplest we have `Destroy()`. Use this to clean up after yourself for simple cases
+
+```dm
+/someobject/Initialize(mapload)
+ . = ..()
+ GLOB.somethings += src //We add ourselves to some global list
+
+/someobject/Destroy()
+ GLOB.somethings -= src //So when we Destroy() clean yourself from the list
+ return ..()
+```
+
+Next, and slightly more complex, pairs of objects that reference each other
+
+This is helpful when for cases where both objects "own" each other
+
+```dm
+/someobject
+ var/someotherobject/buddy
+
+/someotherobject
+ var/someobject/friend
+
+/someobject/Initialize(mapload)
+ if(!buddy)
+ buddy = new()
+ buddy.friend = src
+
+/someotherobject/Initialize(mapload)
+ if(!friend)
+ friend = new()
+ friend.buddy = src
+
+/someobject/Destroy()
+ if(buddy)
+ buddy.friend = null //Make sure to clear their ref to you
+ buddy = null //We clear our ref to them to make sure nothing goes wrong
+
+/someotherobject/Destroy()
+ if(friend)
+ friend.buddy = null //Make sure to clear their ref to you
+ friend = null //We clear our ref to them to make sure nothing goes wrong
+```
+
+Something similar can be accomplished with `QDELETED()`, a define that checks to see if something has started being `Destroy()`'d yet, and `QDEL_NULL()`, a define that `qdel()`'s a var and then sets it to null
+
+Now let's discuss something a bit more complex, weakrefs
+
+You'll need a bit of context, so let's do that now
+
+BYOND has an internal bit of behavior that looks like this
+
+`var/string = "\ref[someobject]"`
+
+This essentially gets that object's position in memory directly. Unlike normal references, this doesn't count for hard deletes. You can retrieve the object in question by using `locate()`
+
+`var/someobject/someobj = locate(string)`
+
+This has some flaws however, since the bit of memory we're pointing to might change, which would cause issues. Fortunately we've developed a datum to handle worrying about this for you, `/datum/weakref`
+
+You can create one using the `WEAKREF()` proc, and use weakref.resolve() to retrieve the actual object
+
+This should be used for things that your object doesn't "own", but still cares about
+
+For instance, a paper bin would own the paper inside it, but the paper inside it would just hold a weakref to the bin
+
+There's no need to clean these up, just make sure you account for it being null, since it'll return that if the object doesn't exist or has been queued for deletion
+
+```dm
+/someobject
+ var/datum/weakref/our_coin
+
+/someobject/proc/set_coin(/obj/item/coin/new_coin)
+ our_coin = WEAKREF(new_coin)
+
+/someobject/proc/get_value()
+ if(!our_coin)
+ return 0
+
+ var/obj/item/coin/potential_coin = our_coin.resolve()
+ if(!potential_coin)
+ our_coin = null //Remember to clear the weakref if we get nothing
+ return 0
+ return potential_coin.value
+```
+
+Now, for the worst case scenario
+
+Let's say you've got a var that's used too often to be weakref'd without making the code too expensive
+
+You can't hold a paired reference to it because it's not like it would ever care about you outside of just clearing the ref
+
+So then, we want to temporarily remember to clear a reference when it's deleted
+
+This is where I might lose you, but we're gonna use signals
+
+`qdel()`, the proc that sets off this whole deletion business, sends a signal called `COMSIG_PARENT_QDELETING`
+
+We can listen for that signal, and if we hear it clear whatever reference we may have
+
+Here's an example
+
+```dm
+/somemob
+ var/mob/target
+
+/somemob/proc/set_target(new_target)
+ if(target)
+ UnregisterSignal(target, COMSIG_PARENT_QDELETING) //We need to make sure any old signals are cleared
+ target = new_target
+ if(target)
+ RegisterSignal(target, COMSIG_PARENT_QDELETING, PROC_REF(clear_target)) //Call clear_target if target is ever qdel()'d
+
+/somemob/proc/clear_target(datum/source)
+ SIGNAL_HANDLER
+ set_target(null)
+```
+
+This really should be your last resort, since signals have some limitations. If some subtype of somemob also registered for parent_qdeleting on the same target you'd get a runtime, since signals don't support it
+
+But if you can't do anything else for reasons of conversion ease, or hot code, this will work
+
+## Help My Code Is Erroring How Fix
+
+First, do a quick check.
+
+Are you doing anything to the object in `Initialize()` that you don't undo in `Destroy()`? I don't mean like, setting its name, but are you adding it to any lists, stuff like that
+
+If this fails, you're just gonna have to read over this doc. You can skip the theory if you'd like, but it's all pretty important for having an understanding of this problem
+
+## Misc facts
+
+> i like rust and all, buuut it removes garbage collecctor, and i pretend garbage collector is a cute girl checking my code
+>
+> -- Armhulenn
+
+- The reference tracker, while powerful, is incredibly easy to break
+If it weren't for those unit tests we'd still be missing list["a"] = list(ref)
+- Everyone but me sucks, because everyone but me keeps adding new hard deletes
+- Garbage collection is a spook, best practice is to use a random reference in place of null, it scares the compiler demons
diff --git a/code/__DEFINES/_flags/_flags.dm b/code/__DEFINES/_flags/_flags.dm
index 265f4e14db61..a93bc6509380 100644
--- a/code/__DEFINES/_flags/_flags.dm
+++ b/code/__DEFINES/_flags/_flags.dm
@@ -26,44 +26,44 @@ GLOBAL_LIST_INIT(bitflags, list(
//FLAGS BITMASK
///This flag is what recursive_hear_check() uses to determine wether to add an item to the hearer list or not.
-#define HEAR_1 (1<<3)
+#define HEAR_1 (1<<0)
///Projectiles will use default chance-based ricochet handling on things with this.
-#define DEFAULT_RICOCHET_1 (1<<4)
+#define DEFAULT_RICOCHET_1 (1<<1)
///Conducts electricity (metal etc.).
-#define CONDUCT_1 (1<<5)
+#define CONDUCT_1 (1<<2)
///For machines and structures that should not break into parts, eg, holodeck stuff.
-#define NODECONSTRUCT_1 (1<<7)
+#define NODECONSTRUCT_1 (1<<3)
///Item has priority to check when entering or leaving.
-#define ON_BORDER_1 (1<<9)
+#define ON_BORDER_1 (1<<5)
///Whether or not this atom shows screentips when hovered over
-#define NO_SCREENTIPS_1 (1<<10)
+#define NO_SCREENTIPS_1 (1<<6)
///Prevent clicking things below it on the same turf eg. doors/ fulltile windows.
-#define PREVENT_CLICK_UNDER_1 (1<<11)
-#define HOLOGRAM_1 (1<<12)
+#define PREVENT_CLICK_UNDER_1 (1<<7)
+#define HOLOGRAM_1 (1<<8)
///Prevents mobs from getting chainshocked by teslas and the supermatter.
-#define SHOCKED_1 (1<<13)
+#define SHOCKED_1 (1<<9)
///Whether /atom/Initialize() has already run for the object.
-#define INITIALIZED_1 (1<<14)
+#define INITIALIZED_1 (1<<10)
///was this spawned by an admin? used for stat tracking stuff.
-#define ADMIN_SPAWNED_1 (1<<15)
+#define ADMIN_SPAWNED_1 (1<<11)
/// should not get harmed if this gets caught by an explosion?
-#define PREVENT_CONTENTS_EXPLOSION_1 (1<<16)
+#define PREVENT_CONTENTS_EXPLOSION_1 (1<<12)
/// Early returns mob.face_atom()
-#define BLOCK_FACE_ATOM_1 (1<<17)
+#define BLOCK_FACE_ATOM_1 (1<<13)
//turf-only flags
-#define NOJAUNT_1 (1<<0)
-#define UNUSED_RESERVATION_TURF_1 (1<<1)
+#define NOJAUNT_1 (1<<14)
+#define UNUSED_RESERVATION_TURF_1 (1<<15)
/// If a turf can be made dirty at roundstart. This is also used in areas.
-#define CAN_BE_DIRTY_1 (1<<2)
+#define CAN_BE_DIRTY_1 (1<<16)
/// Blocks lava rivers being generated on the turf
-#define NO_LAVA_GEN_1 (1<<6)
+#define NO_LAVA_GEN_1 (1<<17)
/// Blocks ruins spawning on the turf
-#define NO_RUINS_1 (1<<10)
+#define NO_RUINS_1 (1<<18)
/// Should this tile be cleaned up and reinserted into an excited group?
-#define EXCITED_CLEANUP_1 (1 << 13)
+#define EXCITED_CLEANUP_1 (1 << 19)
/// Whether or not this atom has contextual screentips when hovered OVER
-#define HAS_CONTEXTUAL_SCREENTIPS_1 (1 << 14)
+#define HAS_CONTEXTUAL_SCREENTIPS_1 (1 << 20)
////////////////Area flags\\\\\\\\\\\\\\
/// If it's a valid territory for cult summoning or the CRAB-17 phone to spawn
diff --git a/code/__DEFINES/cooldowns.dm b/code/__DEFINES/cooldowns.dm
index 0dbd4c15a487..dfb18c3a7fa2 100644
--- a/code/__DEFINES/cooldowns.dm
+++ b/code/__DEFINES/cooldowns.dm
@@ -78,7 +78,7 @@
#define COOLDOWN_DECLARE(cd_index) var/##cd_index = 0
-#define COOLDOWN_START(cd_source, cd_index, cd_time) (cd_source.cd_index = world.time + cd_time)
+#define COOLDOWN_START(cd_source, cd_index, cd_time) (cd_source.cd_index = world.time + (cd_time))
//Returns true if the cooldown has run its course, false otherwise
#define COOLDOWN_FINISHED(cd_source, cd_index) (cd_source.cd_index < world.time)
diff --git a/code/__DEFINES/machines.dm b/code/__DEFINES/machines.dm
index b25ef763cf41..419fc1e11521 100644
--- a/code/__DEFINES/machines.dm
+++ b/code/__DEFINES/machines.dm
@@ -128,3 +128,15 @@
#define CLONEPOD_GET_MIND 1
#define CLONEPOD_POLL_MIND 2
#define CLONEPOD_NO_MIND 3
+
+/// Max length of a status line in the status display
+#define MAX_STATUS_LINE_LENGTH 40
+
+/// Blank Status Display
+#define SD_BLANK 0
+/// Shows the emergency shuttle timer
+#define SD_EMERGENCY 1
+/// Shows an arbitrary message, user-set
+#define SD_MESSAGE 2
+/// Shows an alert picture (e.g. red alert, radiation, etc.)
+#define SD_PICTURE 3
diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm
index cd4e609e7a73..216a42f88f04 100644
--- a/code/__DEFINES/mobs.dm
+++ b/code/__DEFINES/mobs.dm
@@ -366,6 +366,8 @@
#define AI_EMOTION_BLUE_GLOW "Blue Glow"
#define AI_EMOTION_RED_GLOW "Red Glow"
+/// Icon state to use for ai displays that just turns them off
+#define AI_DISPLAY_DONT_GLOW "ai_off"
// / Breathing types. Lungs can access either by these or by a string, which will be considered a gas ID.
#define BREATH_OXY /datum/breathing_class/oxygen
#define BREATH_PLASMA /datum/breathing_class/plasma
diff --git a/code/__DEFINES/qdel.dm b/code/__DEFINES/qdel.dm
index 32e0025ab2a2..7a94df025e5c 100644
--- a/code/__DEFINES/qdel.dm
+++ b/code/__DEFINES/qdel.dm
@@ -40,6 +40,6 @@
#define GC_DEL_QUEUE 10 SECONDS
#define QDELING(X) (X.gc_destroyed)
-#define QDELETED(X) (!X || QDELING(X))
+#define QDELETED(X) (isnull(X) || QDELING(X))
#define QDESTROYING(X) (!X || X.gc_destroyed == GC_CURRENTLY_BEING_QDELETED)
diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm
index a66ec3ca6154..dc6d7168340e 100644
--- a/code/__DEFINES/subsystems.dm
+++ b/code/__DEFINES/subsystems.dm
@@ -87,6 +87,9 @@
///Call qdel on the atom after intialization
#define INITIALIZE_HINT_QDEL 2
+//Call qdel with a force of TRUE after initialization
+#define INITIALIZE_HINT_QDEL_FORCE 3
+
///type and all subtypes should always immediately call Initialize in New()
#define INITIALIZE_IMMEDIATE(X) ##X/New(loc, ...){\
..();\
diff --git a/code/__HELPERS/_logging.dm b/code/__HELPERS/_logging.dm
index 58b7516be355..92cc6da23735 100644
--- a/code/__HELPERS/_logging.dm
+++ b/code/__HELPERS/_logging.dm
@@ -42,9 +42,16 @@
SEND_TEXT(world.log, text)
#endif
-#ifdef REFERENCE_TRACKING_LOG
+#if defined(REFERENCE_DOING_IT_LIVE)
+#define log_reftracker(msg) log_harddel("## REF SEARCH [msg]")
+
+/proc/log_harddel(text)
+ WRITE_LOG(GLOB.harddel_log, text)
+
+#elif defined(REFERENCE_TRACKING) // Doing it locally
#define log_reftracker(msg) log_world("## REF SEARCH [msg]")
-#else
+
+#else //Not tracking at all
#define log_reftracker(msg)
#endif
diff --git a/code/__HELPERS/do_after.dm b/code/__HELPERS/do_after.dm
index 9f98b0e10e64..7bf176c38eb8 100644
--- a/code/__HELPERS/do_after.dm
+++ b/code/__HELPERS/do_after.dm
@@ -23,7 +23,8 @@
while (world.time + resume_time < endtime)
stoplag(1)
if (progress)
- progbar.update(world.time - starttime + resume_time)
+ if(!QDELETED(progbar))
+ progbar.update(world.time - starttime + resume_time)
if(drifting && !user.inertia_dir)
drifting = FALSE
@@ -93,7 +94,8 @@
while (world.time + resume_time < endtime)
stoplag(1)
if (progress)
- progbar.update(world.time - starttime + resume_time)
+ if(!QDELETED(progbar))
+ progbar.update(world.time - starttime + resume_time)
if(drifting && !user.inertia_dir)
drifting = FALSE
diff --git a/code/__HELPERS/qdel.dm b/code/__HELPERS/qdel.dm
index ba31b067c4aa..4943cd0b6823 100644
--- a/code/__HELPERS/qdel.dm
+++ b/code/__HELPERS/qdel.dm
@@ -1,4 +1,8 @@
-#define QDEL_IN(item, time) addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(qdel), item), time, TIMER_STOPPABLE)
+// This is a bit hacky, we do it to avoid people relying on a return value for the macro
+// If you need that you should use QDEL_IN_STOPPABLE instead
+#define QDEL_IN(item, time) ; \
+ addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(qdel), (time) > GC_FILTER_QUEUE ? WEAKREF(item) : item), time);
+#define QDEL_IN_STOPPABLE(item, time) addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(qdel), (time) > GC_FILTER_QUEUE ? WEAKREF(item) : item), time, TIMER_STOPPABLE)
#define QDEL_IN_CLIENT_TIME(item, time) addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(qdel), item), time, TIMER_STOPPABLE | TIMER_CLIENT_TIME)
#define QDEL_NULL(item) qdel(item); item = null
#define QDEL_LIST(L) if(L) { for(var/I in L) qdel(I); L.Cut(); }
diff --git a/code/_compile_options.dm b/code/_compile_options.dm
index 1ed2075537e3..a47cdc833f4a 100644
--- a/code/_compile_options.dm
+++ b/code/_compile_options.dm
@@ -43,6 +43,14 @@
// #define TRACK_MAX_SHARE //Allows max share tracking, for use in the atmos debugging ui
#endif //ifdef TESTING
+//#define REFERENCE_DOING_IT_LIVE
+#ifdef REFERENCE_DOING_IT_LIVE
+// compile the backend
+#define REFERENCE_TRACKING
+// actually look for refs
+#define GC_FAILURE_HARD_LOOKUP
+#endif // REFERENCE_DOING_IT_LIVE
+
//#define UNIT_TESTS //If this is uncommented, we do a single run though of the game setup and tear down process with unit tests in between
#ifndef PRELOAD_RSC //set to:
@@ -72,6 +80,7 @@
#define REFERENCE_TRACKING
#define REFERENCE_TRACKING_DEBUG
#define FIND_REF_NO_CHECK_TICK
+// #define GC_FAILURE_HARD_LOOKUP // Uncomment this to have harddel reftracking in unit tests (takes 3-5min to run per single harddel)
#endif
#ifdef TGS
diff --git a/code/_globalvars/logging.dm b/code/_globalvars/logging.dm
index e9c38546a73f..aa70b7094aa6 100644
--- a/code/_globalvars/logging.dm
+++ b/code/_globalvars/logging.dm
@@ -87,6 +87,11 @@ GLOBAL_PROTECT(picture_logging_id)
GLOBAL_VAR(picture_logging_prefix)
GLOBAL_PROTECT(picture_logging_prefix)
/////
+#ifdef REFERENCE_DOING_IT_LIVE
+GLOBAL_LIST_EMPTY(harddel_log)
+GLOBAL_PROTECT(harddel_log)
+#endif
+
//// cit logging
GLOBAL_VAR(subsystem_log)
diff --git a/code/_onclick/hud/credits.dm b/code/_onclick/hud/credits.dm
index 73986095deff..e991ebc47b75 100644
--- a/code/_onclick/hud/credits.dm
+++ b/code/_onclick/hud/credits.dm
@@ -55,14 +55,15 @@
animate(src, alpha = 255, time = CREDIT_EASE_DURATION, flags = ANIMATION_PARALLEL)
addtimer(CALLBACK(src, PROC_REF(FadeOut)), CREDIT_ROLL_SPEED - CREDIT_EASE_DURATION)
QDEL_IN(src, CREDIT_ROLL_SPEED)
- P.screen += src
+ if(parent)
+ parent.screen += src
/atom/movable/screen/credit/Destroy()
- var/client/P = parent
- P.screen -= src
icon = null
- LAZYREMOVE(P.credits, src)
- parent = null
+ if(parent)
+ parent.screen -= src
+ LAZYREMOVE(parent.credits, src)
+ parent = null
return ..()
/atom/movable/screen/credit/proc/FadeOut()
diff --git a/code/_onclick/hud/radial.dm b/code/_onclick/hud/radial.dm
index 7ddcd1c9f024..c35e693ea6f8 100644
--- a/code/_onclick/hud/radial.dm
+++ b/code/_onclick/hud/radial.dm
@@ -9,6 +9,17 @@ GLOBAL_LIST_EMPTY(radial_menus)
plane = ABOVE_HUD_PLANE
var/datum/radial_menu/parent
+/atom/movable/screen/radial/proc/set_parent(new_value)
+ if(parent)
+ UnregisterSignal(parent, COMSIG_PARENT_QDELETING)
+ parent = new_value
+ if(parent)
+ RegisterSignal(parent, COMSIG_PARENT_QDELETING, PROC_REF(handle_parent_del))
+
+/atom/movable/screen/radial/proc/handle_parent_del()
+ SIGNAL_HANDLER
+ set_parent(null)
+
/atom/movable/screen/radial/slice
icon_state = "radial_slice"
var/choice
@@ -124,7 +135,7 @@ GLOBAL_LIST_EMPTY(radial_menus)
for(var/i in 1 to elements_to_add) //Create all elements
var/atom/movable/screen/radial/slice/new_element = new /atom/movable/screen/radial/slice
new_element.tooltips = use_tooltips
- new_element.parent = src
+ new_element.set_parent(src)
elements += new_element
var/page = 1
@@ -210,7 +221,7 @@ GLOBAL_LIST_EMPTY(radial_menus)
/datum/radial_menu/New()
close_button = new
- close_button.parent = src
+ close_button.set_parent(src)
/datum/radial_menu/proc/Reset()
choices.Cut()
diff --git a/code/_onclick/hud/robot.dm b/code/_onclick/hud/robot.dm
index 175d6e2a536b..5b78696ec507 100644
--- a/code/_onclick/hud/robot.dm
+++ b/code/_onclick/hud/robot.dm
@@ -301,6 +301,12 @@
else
icon_state = "lamp_off"
+/atom/movable/screen/robot/lamp/Destroy()
+ if(robot)
+ robot.lampButton = null
+ robot = null
+ return ..()
+
/atom/movable/screen/robot/alerts
name = "Alert Panel"
icon = 'icons/mob/screen_ai.dmi'
@@ -337,6 +343,12 @@
icon_state = "template"
var/mob/living/silicon/robot/robot
+/atom/movable/screen/robot/modPC/Destroy()
+ if(robot)
+ robot.interfaceButton = null
+ robot = null
+ return ..()
+
/atom/movable/screen/robot/modPC/Click()
. = ..()
if(.)
diff --git a/code/controllers/subsystem/atoms.dm b/code/controllers/subsystem/atoms.dm
index ba2da8365bf9..b0c7d67d96e6 100644
--- a/code/controllers/subsystem/atoms.dm
+++ b/code/controllers/subsystem/atoms.dm
@@ -97,6 +97,9 @@ SUBSYSTEM_DEF(atoms)
if(INITIALIZE_HINT_QDEL)
qdel(A)
qdeleted = TRUE
+ if(INITIALIZE_HINT_QDEL_FORCE)
+ qdel(A, force = TRUE)
+ qdeleted = TRUE
else
BadInitializeCalls[the_type] |= BAD_INIT_NO_HINT
diff --git a/code/controllers/subsystem/jukeboxes.dm b/code/controllers/subsystem/jukeboxes.dm
index 1e457703c2ab..b19058e97f59 100644
--- a/code/controllers/subsystem/jukeboxes.dm
+++ b/code/controllers/subsystem/jukeboxes.dm
@@ -74,6 +74,8 @@ SUBSYSTEM_DEF(jukeboxes)
activejukeboxes[IDtoupdate][JUKE_FALLOFF] = jukefalloff
/datum/controller/subsystem/jukeboxes/proc/removejukebox(IDtoremove)
+ if(!IDtoremove)
+ return
if(islist(activejukeboxes[IDtoremove]))
var/jukechannel = activejukeboxes[IDtoremove][JUKE_CHANNEL]
for(var/mob/M in GLOB.player_list)
diff --git a/code/datums/action.dm b/code/datums/action.dm
index f27c1fcabb84..b48b7bda7037 100644
--- a/code/datums/action.dm
+++ b/code/datums/action.dm
@@ -696,6 +696,7 @@
/datum/action/spell_action/Destroy()
var/obj/effect/proc_holder/S = target
S.action = null
+ target = null
return ..()
/datum/action/spell_action/Trigger()
diff --git a/code/datums/brain_damage/imaginary_friend.dm b/code/datums/brain_damage/imaginary_friend.dm
index 38e187845198..5d2b6e63956a 100644
--- a/code/datums/brain_damage/imaginary_friend.dm
+++ b/code/datums/brain_damage/imaginary_friend.dm
@@ -87,6 +87,8 @@
to_chat(src, "You cannot directly influence the world around you, but you can see what [owner] cannot.")
/mob/camera/imaginary_friend/Initialize(mapload, _trauma)
+ if(!_trauma)
+ return INITIALIZE_HINT_QDEL
. = ..()
trauma = _trauma
@@ -129,7 +131,7 @@
client.images |= current_image
/mob/camera/imaginary_friend/Destroy()
- if(owner.client)
+ if(owner?.client)
owner.client.images.Remove(human_image)
if(client)
client.images.Remove(human_image)
diff --git a/code/datums/components/mood.dm b/code/datums/components/mood.dm
index e28e1086771c..aec336a1e525 100644
--- a/code/datums/components/mood.dm
+++ b/code/datums/components/mood.dm
@@ -43,6 +43,7 @@
hud.show_hud(hud.hud_version)
/datum/component/mood/Destroy()
+ QDEL_LIST_ASSOC_VAL(mood_events)
STOP_PROCESSING(SSobj, src)
unmodify_hud()
return ..()
diff --git a/code/datums/components/pellet_cloud.dm b/code/datums/components/pellet_cloud.dm
index 58e838e357d4..afbef51fa57a 100644
--- a/code/datums/components/pellet_cloud.dm
+++ b/code/datums/components/pellet_cloud.dm
@@ -266,7 +266,7 @@
var/w_bonus = wound_info_by_part[hit_part][CLOUD_POSITION_W_BONUS]
var/bw_bonus = wound_info_by_part[hit_part][CLOUD_POSITION_BW_BONUS]
var/wound_type = (initial(P.damage_type) == BRUTE) ? WOUND_BLUNT : WOUND_BURN // sharpness is handled in the wound rolling
- wound_info_by_part[hit_part] = null
+ wound_info_by_part -= hit_part
hit_part.painless_wound_roll(wound_type, damage_dealt, w_bonus, bw_bonus, initial(P.sharpness))
if(num_hits > 1)
diff --git a/code/datums/components/squeak.dm b/code/datums/components/squeak.dm
index 564290354558..289e171d5f2b 100644
--- a/code/datums/components/squeak.dm
+++ b/code/datums/components/squeak.dm
@@ -111,7 +111,7 @@
if(AM.movement_type & (FLYING|FLOATING) || !AM.has_gravity())
return
var/atom/current_parent = parent
- if(isturf(current_parent.loc))
+ if(isturf(current_parent?.loc))
if(do_play_squeak())
SEND_SIGNAL(AM, COMSIG_CROSS_SQUEAKED)
diff --git a/code/datums/components/tackle.dm b/code/datums/components/tackle.dm
index cace48f53e51..ae53c57cbd26 100644
--- a/code/datums/components/tackle.dm
+++ b/code/datums/components/tackle.dm
@@ -49,7 +49,7 @@
var/mob/living/carbon/P = parent
to_chat(P, "You can no longer tackle.")
P.tackling = FALSE
- ..()
+ return ..()
/datum/component/tackler/RegisterWithParent()
RegisterSignal(parent, COMSIG_MOB_CLICKON, PROC_REF(checkTackle))
diff --git a/code/datums/dash_weapon.dm b/code/datums/dash_weapon.dm
index adf7b72c1129..b86854f18a7d 100644
--- a/code/datums/dash_weapon.dm
+++ b/code/datums/dash_weapon.dm
@@ -17,6 +17,10 @@
. = ..()
dashing_item = dasher
+/datum/action/innate/dash/Destroy()
+ dashing_item = null
+ return ..()
+
/datum/action/innate/dash/IsAvailable(silent = FALSE)
if(current_charges > 0)
return TRUE
diff --git a/code/datums/datum.dm b/code/datums/datum.dm
index 2cc72f7ca91e..6c6030625a47 100644
--- a/code/datums/datum.dm
+++ b/code/datums/datum.dm
@@ -110,6 +110,12 @@
continue
qdel(timer)
+ #ifdef REFERENCE_TRACKING
+ #ifdef REFERENCE_TRACKING_DEBUG
+ found_refs = null
+ #endif
+ #endif
+
//BEGIN: ECS SHIT
signal_enabled = FALSE
diff --git a/code/datums/elements/dwarfism.dm b/code/datums/elements/dwarfism.dm
index edeeb85144d0..6093f2a1a34a 100644
--- a/code/datums/elements/dwarfism.dm
+++ b/code/datums/elements/dwarfism.dm
@@ -29,14 +29,15 @@
/datum/element/dwarfism/Detach(mob/living/L)
. = ..()
+ attached_targets -= L
+ UnregisterSignal(L, comsig)
if(QDELETED(L))
return
if(L.lying != 0)
L.transform = L.transform.Scale(TALL, 1)
else
L.transform = L.transform.Scale(1, TALL)
- UnregisterSignal(L, comsig)
- attached_targets -= L
+ L.transform = L.transform.Translate(0, 16*(TALL-1)) //Makes sure you stand on the tile no matter the size - sand
#undef SHORT
#undef TALL
diff --git a/code/datums/elements/spellcasting.dm b/code/datums/elements/spellcasting.dm
index 91b2d5aea6cb..7b41925dcfc3 100644
--- a/code/datums/elements/spellcasting.dm
+++ b/code/datums/elements/spellcasting.dm
@@ -24,6 +24,7 @@
UnregisterSignal(target, list(COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED, COMSIG_MOB_SPELL_CAN_CAST))
if(users_by_item[target])
var/mob/user = users_by_item[target]
+ users_by_item -= target
stacked_spellcasting_by_user[user]--
if(!stacked_spellcasting_by_user[user])
stacked_spellcasting_by_user -= user
diff --git a/code/datums/explosion.dm b/code/datums/explosion.dm
index 339d0ce786ed..99bc12dc62d3 100644
--- a/code/datums/explosion.dm
+++ b/code/datums/explosion.dm
@@ -205,8 +205,8 @@ GLOBAL_LIST_EMPTY(explosions)
//lists are guaranteed to contain at least 1 turf at this point
var/iteration = 0
- var/affTurfLen = affected_turfs.len
- var/expBlockLen = cached_exp_block.len
+ var/affTurfLen = length(affected_turfs)
+ var/expBlockLen = length(cached_exp_block)
for(var/TI in affected_turfs)
var/turf/T = TI
++iteration
@@ -282,8 +282,8 @@ GLOBAL_LIST_EMPTY(explosions)
break
//update the trackers
- affTurfLen = affected_turfs.len
- expBlockLen = cached_exp_block.len
+ affTurfLen = length(affected_turfs)
+ expBlockLen = length(cached_exp_block)
if(break_condition)
if(reactionary)
@@ -299,8 +299,8 @@ GLOBAL_LIST_EMPTY(explosions)
break
//update the trackers
- affTurfLen = affected_turfs.len
- expBlockLen = cached_exp_block.len
+ affTurfLen = length(affected_turfs)
+ expBlockLen = length(cached_exp_block)
var/circumference = (PI * (init_dist + 4) * 2) //+4 to radius to prevent shit gaps
if(exploded_this_tick.len > circumference) //only do this every revolution
@@ -357,7 +357,7 @@ GLOBAL_LIST_EMPTY(explosions)
var/processed = 0
while(running)
var/I
- for(I in (processed + 1) to affected_turfs.len) // we cache the explosion block rating of every turf in the explosion area
+ for(I in (processed + 1) to length(affected_turfs)) // we cache the explosion block rating of every turf in the explosion area
var/turf/T = affected_turfs[I]
var/current_exp_block = T.density ? T.explosion_block : 0
diff --git a/code/datums/mood_events/mood_event.dm b/code/datums/mood_events/mood_event.dm
index c125ba054a00..7afc4d1e32ed 100644
--- a/code/datums/mood_events/mood_event.dm
+++ b/code/datums/mood_events/mood_event.dm
@@ -11,6 +11,7 @@
/datum/mood_event/Destroy()
remove_effects()
+ owner = null
return ..()
/datum/mood_event/proc/add_effects(param)
diff --git a/code/datums/mutations/antenna.dm b/code/datums/mutations/antenna.dm
index 866180ab4b45..1ea4e787c028 100644
--- a/code/datums/mutations/antenna.dm
+++ b/code/datums/mutations/antenna.dm
@@ -15,7 +15,7 @@
icon_state = "walkietalkie"
/obj/item/implant/radio/antenna/Initialize(mapload)
- ..()
+ . = ..()
if (radio)
radio.name = "internal antenna"
diff --git a/code/datums/weakrefs.dm b/code/datums/weakrefs.dm
index 31e0c3501b78..c243f35f3432 100644
--- a/code/datums/weakrefs.dm
+++ b/code/datums/weakrefs.dm
@@ -17,9 +17,10 @@
reference = REF(thing)
/datum/weakref/Destroy(force)
+ var/datum/target = resolve()
+ qdel(target)
if(!force)
return QDEL_HINT_LETMELIVE //Let BYOND autoGC thiswhen nothing is using it anymore.
- var/datum/target = resolve()
target?.weak_reference = null
return ..()
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 0eaba60ba827..514758e35c86 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -240,7 +240,7 @@
AA.remove_from_hud(src)
if(reagents)
- qdel(reagents)
+ QDEL_NULL(reagents)
orbiters = null // The component is attached to us normaly and will be deleted elsewhere
diff --git a/code/game/gamemodes/meteor/meteors.dm b/code/game/gamemodes/meteor/meteors.dm
index 446573e2afc4..8f97e232ae68 100644
--- a/code/game/gamemodes/meteor/meteors.dm
+++ b/code/game/gamemodes/meteor/meteors.dm
@@ -131,7 +131,7 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
GLOB.meteor_list += src
SSaugury.register_doom(src, threat)
SpinAnimation()
- timerid = QDEL_IN(src, lifetime)
+ timerid = QDEL_IN_STOPPABLE(src, lifetime)
chase_target(target)
/obj/effect/meteor/Bump(atom/A)
diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm
index 035f18f2d4bb..a6d5982a812e 100644
--- a/code/game/machinery/_machinery.dm
+++ b/code/game/machinery/_machinery.dm
@@ -178,6 +178,8 @@ Class Procs:
for(var/atom/A in component_parts)
qdel(A)
component_parts.Cut()
+ if(circuit)
+ QDEL_NULL(circuit)
return ..()
/obj/machinery/proc/locate_machinery()
@@ -457,6 +459,7 @@ Class Procs:
for(var/obj/item/I in component_parts)
I.forceMove(loc)
LAZYCLEARLIST(component_parts)
+ circuit = null
qdel(src)
/obj/machinery/proc/spawn_frame(disassembled)
diff --git a/code/game/machinery/computer/communications.dm b/code/game/machinery/computer/communications.dm
index f1ed801af1d8..533adaae91b2 100755
--- a/code/game/machinery/computer/communications.dm
+++ b/code/game/machinery/computer/communications.dm
@@ -1,5 +1,4 @@
#define IMPORTANT_ACTION_COOLDOWN (60 SECONDS)
-#define MAX_STATUS_LINE_LENGTH 40
#define STATE_BUYING_SHUTTLE "buying_shuttle"
#define STATE_CHANGING_STATUS "changing_status"
@@ -296,9 +295,8 @@
if ("setStatusMessage")
if (!authenticated(usr))
return
- var/line_one = reject_bad_text(params["lineOne"] || "", MAX_STATUS_LINE_LENGTH)
- var/line_two = reject_bad_text(params["lineTwo"] || "", MAX_STATUS_LINE_LENGTH)
- post_status("alert", "blank")
+ var/line_one = reject_bad_text(params["upperText"] || "", MAX_STATUS_LINE_LENGTH)
+ var/line_two = reject_bad_text(params["lowerText"] || "", MAX_STATUS_LINE_LENGTH)
post_status("message", line_one, line_two)
last_status_display = list(line_one, line_two)
playsound(src, "terminal_type", 50, FALSE)
@@ -472,8 +470,8 @@
data["budget"] = bank_account.account_balance
data["shuttles"] = shuttles
if (STATE_CHANGING_STATUS)
- data["lineOne"] = last_status_display ? last_status_display[1] : ""
- data["lineTwo"] = last_status_display ? last_status_display[2] : ""
+ data["upperText"] = last_status_display ? last_status_display[1] : ""
+ data["lowerText"] = last_status_display ? last_status_display[2] : ""
return data
@@ -567,8 +565,8 @@
var/datum/signal/status_signal = new(list("command" = command))
switch(command)
if("message")
- status_signal.data["msg1"] = data1
- status_signal.data["msg2"] = data2
+ status_signal.data["top_text"] = data1
+ status_signal.data["bottom_text"] = data2
if("alert")
status_signal.data["picture_state"] = data1
diff --git a/code/game/machinery/launch_pad.dm b/code/game/machinery/launch_pad.dm
index 14a90ff3bf3d..08fcae10137a 100644
--- a/code/game/machinery/launch_pad.dm
+++ b/code/game/machinery/launch_pad.dm
@@ -39,9 +39,10 @@
MA.plane = 0
holder.appearance = MA
update_indicator()
-
+
/obj/machinery/launchpad/Destroy()
- qdel(hud_list[DIAG_LAUNCHPAD_HUD])
+ for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds)
+ diag_hud.remove_from_hud(src)
return ..()
/obj/machinery/launchpad/examine(mob/user)
@@ -229,7 +230,9 @@
src.briefcase = briefcase
/obj/machinery/launchpad/briefcase/Destroy()
- QDEL_NULL(briefcase)
+ if(!QDELETED(briefcase))
+ qdel(briefcase)
+ briefcase = null
return ..()
/obj/machinery/launchpad/briefcase/isAvailable(silent = FALSE)
@@ -271,7 +274,8 @@
/obj/item/storage/briefcase/launchpad/Destroy()
if(!QDELETED(pad))
- QDEL_NULL(pad)
+ qdel(pad)
+ pad = null
return ..()
/obj/item/storage/briefcase/launchpad/PopulateContents()
diff --git a/code/game/machinery/navbeacon.dm b/code/game/machinery/navbeacon.dm
index 2bcb9b0762e7..2d442c745d76 100644
--- a/code/game/machinery/navbeacon.dm
+++ b/code/game/machinery/navbeacon.dm
@@ -26,20 +26,13 @@
set_codes()
+ glob_lists_register(init=TRUE)
+
var/turf/T = loc
hide(T.intact)
- if(codes["patrol"])
- if(!GLOB.navbeacons["[z]"])
- GLOB.navbeacons["[z]"] = list()
- GLOB.navbeacons["[z]"] += src //Register with the patrol list!
- if(codes["delivery"])
- GLOB.deliverybeacons += src
- GLOB.deliverybeacontags += location
/obj/machinery/navbeacon/Destroy()
- if (GLOB.navbeacons["[z]"])
- GLOB.navbeacons["[z]"] -= src //Remove from beacon list, if in one.
- GLOB.deliverybeacons -= src
+ glob_lists_deregister()
return ..()
/obj/machinery/navbeacon/onTransitZ(old_z, new_z)
@@ -67,6 +60,26 @@
else
codes[e] = "1"
+/obj/machinery/navbeacon/proc/glob_lists_deregister()
+ if (GLOB.navbeacons["[z]"])
+ GLOB.navbeacons["[z]"] -= src //Remove from beacon list, if in one.
+ GLOB.deliverybeacons -= src
+ GLOB.deliverybeacontags -= location
+
+///Registers the navbeacon to the global beacon lists
+/obj/machinery/navbeacon/proc/glob_lists_register(init=FALSE)
+ if(!init)
+ glob_lists_deregister()
+ if(!codes)
+ return
+ if(codes["patrol"])
+ if(!GLOB.navbeacons["[z]"])
+ GLOB.navbeacons["[z]"] = list()
+ GLOB.navbeacons["[z]"] += src //Register with the patrol list!
+ if(codes["delivery"])
+ GLOB.deliverybeacons += src
+ GLOB.deliverybeacontags += location
+
// called when turf state changes
// hide the object if turf is intact
diff --git a/code/game/machinery/status_display.dm b/code/game/machinery/status_display.dm
index c550f729468b..527e481b428f 100644
--- a/code/game/machinery/status_display.dm
+++ b/code/game/machinery/status_display.dm
@@ -1,18 +1,14 @@
// Status display
-// (formerly Countdown timer display)
-#define CHARS_PER_LINE 5
-#define FONT_SIZE "5pt"
-#define FONT_COLOR "#09f"
-#define FONT_STYLE "Small Fonts"
+#define MAX_STATIC_WIDTH 22
+#define FONT_STYLE "12pt 'TinyUnicode'"
#define SCROLL_RATE (0.04 SECONDS) // time per pixel
-#define LINE1_Y -8
-#define LINE2_Y -15
-
-#define SD_BLANK 0 // 0 = Blank
-#define SD_EMERGENCY 1 // 1 = Emergency Shuttle timer
-#define SD_MESSAGE 2 // 2 = Arbitrary message(s)
-#define SD_PICTURE 3 // 3 = alert picture
+#define SCROLL_PADDING 2 // how many pixels we chop to make a smooth loop
+#define LINE1_X 1
+#define LINE1_Y -4
+#define LINE2_X 1
+#define LINE2_Y -11
+#define STATUS_DISPLAY_FONT_DATUM /datum/font/tiny_unicode/size_12pt
/// Status display which can show images and scrolling text.
/obj/machinery/status_display
@@ -20,7 +16,6 @@
desc = null
icon = 'icons/obj/status_display.dmi'
icon_state = "frame"
- base_icon_state = "unanchoredstatusdisplay"
verb_say = "beeps"
verb_ask = "beeps"
verb_exclaim = "beeps"
@@ -29,11 +24,19 @@
var/obj/effect/overlay/status_display_text/message1_overlay
var/obj/effect/overlay/status_display_text/message2_overlay
- var/mutable_appearance/ai_vtuber_overlay
var/current_picture = ""
var/current_mode = SD_BLANK
var/message1 = ""
var/message2 = ""
+
+ /// Normal text color
+ var/text_color = "#09F"
+ /// Color for headers, eg. "- ETA -"
+ var/header_text_color = "#2CF"
+
+ /// AI image to display
+ var/mutable_appearance/ai_vtuber_overlay
+ /// AI controlling this display
var/mob/living/silicon/ai/master
/obj/item/wallframe/status_display
@@ -44,6 +47,12 @@
result_path = /obj/machinery/status_display/evac
pixel_shift = -32
+//makes it go on the wall when built
+/obj/machinery/status_display/Initialize(mapload, ndir, building)
+ . = ..()
+ update_appearance()
+ register_context()
+
/obj/machinery/status_display/wrench_act(mob/living/user, obj/item/tool)
. = ..()
balloon_alert(user, "[anchored ? "un" : ""]securing...")
@@ -92,13 +101,17 @@
line1 = uppertext(line1)
line2 = uppertext(line2)
- if(line1 != message1)
+ var/message_changed = FALSE
+ if(line1 != message1 || isnull(message1_overlay))
message1 = line1
+ message_changed = TRUE
- if(line2 != message2)
+ if(line2 != message2 || isnull(message2_overlay))
message2 = line2
+ message_changed = TRUE
- update_appearance()
+ if(message_changed)
+ update_appearance()
/**
* Remove both message objs and null the fields.
@@ -117,10 +130,11 @@
* Arguments:
* * overlay - the current /obj/effect/overlay/status_display_text instance
* * line_y - The Y offset to render the text.
+ * * x_offset - Used to offset the text on the X coordinates, not usually needed.
* * message - the new message text.
* Returns new /obj/effect/overlay/status_display_text or null if unchanged.
*/
-/obj/machinery/status_display/proc/update_message(obj/effect/overlay/status_display_text/overlay, line_y, message)
+/obj/machinery/status_display/proc/update_message(obj/effect/overlay/status_display_text/overlay, line_y, message, x_offset, line_pair)
if(overlay && message == overlay.message)
return null
@@ -131,7 +145,10 @@
if(overlay)
qdel(overlay)
- var/obj/effect/overlay/status_display_text/new_status_display_text = new(src, line_y, message)
+ var/obj/effect/overlay/status_display_text/new_status_display_text = new(src, line_y, message, text_color, header_text_color, x_offset, line_pair)
+ // Draw our object visually "in front" of this display, taking advantage of sidemap
+ new_status_display_text.pixel_y = -32
+ new_status_display_text.pixel_z = 32
vis_contents += new_status_display_text
return new_status_display_text
@@ -144,9 +161,9 @@
)
set_light(0)
return
- set_light(1.4, 0.7, LIGHT_COLOR_BLUE) // blue light
+ set_light(1.5, 0.7, LIGHT_COLOR_BLUE) // blue light
-/obj/machinery/status_display/update_overlays()
+/obj/machinery/status_display/update_overlays(updates)
. = ..()
if(stat & (NOPOWER|BROKEN))
@@ -165,11 +182,21 @@
if(SD_PICTURE)
remove_messages()
. += mutable_appearance(icon, current_picture)
+ if(current_picture == AI_DISPLAY_DONT_GLOW) // If the thing's off, don't display the emissive yeah?
+ return .
else
- var/overlay = update_message(message1_overlay, LINE1_Y, message1)
+ var/line1_metric
+ var/line2_metric
+ var/line_pair
+ var/datum/font/display_font = new STATUS_DISPLAY_FONT_DATUM()
+ line1_metric = display_font.get_metrics(message1)
+ line2_metric = display_font.get_metrics(message2)
+ line_pair = (line1_metric > line2_metric ? line1_metric : line2_metric)
+
+ var/overlay = update_message(message1_overlay, LINE1_Y, message1, LINE1_X, line_pair)
if(overlay)
message1_overlay = overlay
- overlay = update_message(message2_overlay, LINE2_Y, message2)
+ overlay = update_message(message2_overlay, LINE2_Y, message2, LINE2_X, line_pair)
if(overlay)
message2_overlay = overlay
@@ -177,7 +204,7 @@
if(message1 == "" && message2 == "")
return
- . += emissive_appearance(icon, "outline", alpha = src.alpha)
+ . += emissive_appearance(icon, "outline", src, alpha = src.alpha)
// Timed process - performs nothing in the base class
/obj/machinery/status_display/process()
@@ -205,7 +232,7 @@
/obj/machinery/status_display/examine(mob/user)
. = ..()
- if (current_mode == SD_MESSAGE && (message1_overlay?.message || message2_overlay?.message))
+ if (message1_overlay || message2_overlay)
. += "The display says:"
if (message1_overlay.message)
. += "\t[html_encode(message1_overlay.message)]"
@@ -216,31 +243,17 @@
/obj/machinery/status_display/proc/display_shuttle_status(obj/docking_port/mobile/shuttle)
if(!shuttle)
// the shuttle is missing - no processing
- set_messages("shutl?","")
+ set_messages("shutl","not in service")
return PROCESS_KILL
else if(shuttle.timer)
- var/line1 = "-[shuttle.getModeStr()]-"
+ var/line1 = "<<< [shuttle.getModeStr()]"
var/line2 = shuttle.getTimerStr()
- if(length_char(line2) > CHARS_PER_LINE)
- line2 = "error"
set_messages(line1, line2)
else
// don't kill processing, the timer might turn back on
set_messages("", "")
-/obj/machinery/status_display/proc/examine_shuttle(mob/user, obj/docking_port/mobile/shuttle)
- if (shuttle)
- var/modestr = shuttle.getModeStr()
- if (modestr)
- if (shuttle.timer)
- modestr = " \t[modestr]: [shuttle.getTimerStr()]"
- else
- modestr = " \t[modestr]"
- return "The display says: \t[shuttle.name][modestr]"
- else
- return "The display says: \tShuttle missing!"
-
/obj/machinery/status_display/Destroy()
remove_messages()
return ..()
@@ -252,39 +265,56 @@
icon = 'icons/obj/status_display.dmi'
vis_flags = VIS_INHERIT_LAYER | VIS_INHERIT_PLANE | VIS_INHERIT_ID
+ /// The message this overlay is displaying.
var/message
-/obj/effect/overlay/status_display_text/Initialize(mapload, yoffset, line)
+ // If the line is short enough to not marquee, and it matches this, it's a header.
+ var/static/regex/header_regex = regex("^-.*-$")
+
+/obj/effect/overlay/status_display_text/Initialize(mapload, yoffset, line, text_color, header_text_color, xoffset = 0, line_pair)
. = ..()
maptext_y = yoffset
message = line
- var/line_length = length_char(line)
+ var/datum/font/display_font = new STATUS_DISPLAY_FONT_DATUM()
+ var/line_width = display_font.get_metrics(line)
- if(line_length > CHARS_PER_LINE)
+ if(line_width > MAX_STATIC_WIDTH)
// Marquee text
- var/marquee_message = "[line] • [line] • [line]"
- var/marquee_length = line_length * 3 + 6
- maptext = generate_text(marquee_message, center = FALSE)
- maptext_width = 6 * marquee_length
- maptext_x = 32
+ var/marquee_message = "[line] [line] [line]"
+
+ // Width of full content. Must of these is never revealed unless the user inputted a single character.
+ var/full_marquee_width = display_font.get_metrics("[marquee_message] ")
+ // We loop after only this much has passed.
+ var/looping_marquee_width = (display_font.get_metrics("[line] ]") - SCROLL_PADDING)
+
+ maptext = generate_text(marquee_message, center = FALSE, text_color = text_color)
+ maptext_width = full_marquee_width
+ maptext_x = 0
// Mask off to fit in screen.
add_filter("mask", 1, alpha_mask_filter(icon = icon(icon, "outline")))
// Scroll.
- var/width = 4 * marquee_length
- var/time = (width + 32) * SCROLL_RATE
- animate(src, maptext_x = -width, time = time, loop = -1)
- animate(maptext_x = 32, time = 0)
+ var/time = line_pair * SCROLL_RATE
+ animate(src, maptext_x = (-looping_marquee_width) + MAX_STATIC_WIDTH, time = time, loop = -1)
+ animate(maptext_x = MAX_STATIC_WIDTH, time = 0)
else
// Centered text
- maptext = generate_text(line, center = TRUE)
- maptext_x = 0
+ var/color = header_regex.Find(line) ? header_text_color : text_color
+ maptext = generate_text(line, center = TRUE, text_color = color)
+ maptext_x = xoffset //Defaults to 0, this would be centered unless overided
-/obj/effect/overlay/status_display_text/proc/generate_text(text, center)
- return {"
[text]
"}
+/**
+ * Generate the actual maptext.
+ * Arguments:
+ * * text - the text to display
+ * * center - center the text if TRUE, otherwise right-align (the direction the text is coming from)
+ * * text_color - the text color
+ */
+/obj/effect/overlay/status_display_text/proc/generate_text(text, center, text_color)
+ return {"
[text]
"}
/// Evac display which shows shuttle timer or message set by Command.
/obj/machinery/status_display/evac
@@ -295,12 +325,6 @@
// MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/status_display/evac, 32)
-//makes it go on the wall when built
-/obj/machinery/status_display/Initialize(mapload, ndir, building)
- . = ..()
- update_appearance()
- register_context()
-
/obj/machinery/status_display/add_context(atom/source, list/context, obj/item/held_item, mob/living/user)
. = ..()
if(isAI(user))
@@ -347,13 +371,6 @@
set_picture(last_picture)
return PROCESS_KILL
-/obj/machinery/status_display/evac/examine(mob/user)
- . = ..()
- if(current_mode == SD_EMERGENCY)
- . += examine_shuttle(user, SSshuttle.emergency)
- else if(!message1 && !message2)
- . += "The display is blank."
-
/obj/machinery/status_display/evac/receive_signal(datum/signal/signal)
switch(signal.data["command"])
if("blank")
@@ -364,7 +381,7 @@
set_messages("", "")
if("message")
current_mode = SD_MESSAGE
- set_messages(signal.data["msg1"] || "", signal.data["msg2"] || "")
+ set_messages(signal.data["top_text"] || "", signal.data["bottom_text"] || "")
if("alert")
current_mode = SD_PICTURE
last_picture = signal.data["picture_state"]
@@ -378,6 +395,8 @@
/obj/machinery/status_display/supply
name = "supply display"
current_mode = SD_MESSAGE
+ text_color = "#F90"
+ header_text_color = "#FC2"
/obj/machinery/status_display/supply/process()
if(stat & NOPOWER)
@@ -390,8 +409,8 @@
if(!SSshuttle.supply)
// Might be missing in our first update on initialize before shuttles
// have loaded. Cross our fingers that it will soon return.
- line1 = "CARGO"
- line2 = "shutl?"
+ line1 = "shutl"
+ line2 = "not in service"
else if(SSshuttle.supply.mode == SHUTTLE_IDLE)
if(is_station_level(SSshuttle.supply.z))
line1 = "CARGO"
@@ -400,26 +419,10 @@
line1 = ""
line2 = ""
else
- line1 = "CARGO"
+ line1 = "<<< [SSshuttle.supply.getModeStr()]"
line2 = SSshuttle.supply.getTimerStr()
- if(length_char(line2) > CHARS_PER_LINE)
- line2 = "Error"
set_messages(line1, line2)
-/obj/machinery/status_display/supply/examine(mob/user)
- . = ..()
- var/obj/docking_port/mobile/shuttle = SSshuttle.supply
- var/shuttleMsg = null
- if (shuttle.mode == SHUTTLE_IDLE)
- if (is_station_level(shuttle.z))
- shuttleMsg = "Docked"
- else
- shuttleMsg = "[shuttle.getModeStr()]: [shuttle.getTimerStr()]"
- if (shuttleMsg)
- . += "The display says: \t[shuttleMsg]"
- else
- . += "The display is blank."
-
/// General-purpose shuttle status display.
/obj/machinery/status_display/shuttle
@@ -427,6 +430,9 @@
current_mode = SD_MESSAGE
var/shuttle_id
+ text_color = "#0F5"
+ header_text_color = "#2FC"
+
/obj/machinery/status_display/shuttle/process()
if(!shuttle_id || (stat & NOPOWER))
// No power, no processing.
@@ -435,13 +441,6 @@
return display_shuttle_status(SSshuttle.getShuttle(shuttle_id))
-/obj/machinery/status_display/shuttle/examine(mob/user)
- . = ..()
- if(shuttle_id)
- . += examine_shuttle(user, SSshuttle.getShuttle(shuttle_id))
- else
- . += "The display is blank."
-
/obj/machinery/status_display/shuttle/vv_edit_var(var_name, var_value)
. = ..()
if(!.)
@@ -462,29 +461,9 @@
desc = "A small screen which the AI can use to present itself."
current_mode = SD_PICTURE
+ /// Current AI emotion to display
var/emotion = AI_EMOTION_BLANK
- /// A mapping between AI_EMOTION_* string constants, which also double as user readable descriptions, and the name of the iconfile.
- var/static/list/emotion_map = list(
- AI_EMOTION_BLANK = "ai_off",
- AI_EMOTION_VERY_HAPPY = "ai_veryhappy",
- AI_EMOTION_HAPPY = "ai_happy",
- AI_EMOTION_NEUTRAL = "ai_neutral",
- AI_EMOTION_UNSURE = "ai_unsure",
- AI_EMOTION_CONFUSED = "ai_confused",
- AI_EMOTION_SAD = "ai_sad",
- AI_EMOTION_BSOD = "ai_bsod",
- AI_EMOTION_PROBLEMS = "ai_trollface",
- AI_EMOTION_AWESOME = "ai_awesome",
- AI_EMOTION_DORFY = "ai_urist",
- AI_EMOTION_THINKING = "ai_thinking",
- AI_EMOTION_FACEPALM = "ai_facepalm",
- AI_EMOTION_FRIEND_COMPUTER = "ai_friend",
- AI_EMOTION_BLUE_GLOW = "ai_sal",
- AI_EMOTION_RED_GLOW = "ai_hal",
- )
-
-
// MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/status_display/ai, 32)
/obj/machinery/status_display/ai/Initialize(mapload)
@@ -499,8 +478,8 @@
if(!isAI(user))
return
var/list/choices = list()
- for(var/emotion_const in emotion_map)
- var/icon_state = emotion_map[emotion_const]
+ for(var/emotion_const in GLOB.ai_status_display_emotes)
+ var/icon_state = GLOB.ai_status_display_emotes[emotion_const]
choices[emotion_const] = image(icon = 'icons/obj/status_display.dmi', icon_state = icon_state)
var/emotion_result = show_radial_menu(user, src, choices, tooltips = TRUE)
@@ -510,6 +489,14 @@
user.emote(initial(emote.key))
break
+/obj/machinery/status_display/ai/process()
+ if(stat & NOPOWER)
+ update_appearance()
+ return PROCESS_KILL
+
+ set_picture(GLOB.ai_status_display_emotes[emotion])
+ return PROCESS_KILL
+
// ai vtuber moment
/obj/machinery/status_display/AICtrlClick(mob/living/silicon/ai/user)
if(!isAI(user) || master || (user.current && !istype(user.current, /obj/machinery/status_display))) // don't let two AIs control the same one, don't let AI control two things at once
@@ -546,14 +533,6 @@
if(master && !radio_freq && master.controlled_display == src)
master.relay_speech(message, speaker, message_language, raw_message, radio_freq, spans, message_mods)
-/obj/machinery/status_display/ai/process()
- if(stat & NOPOWER)
- update_appearance()
- return PROCESS_KILL
-
- set_picture(emotion_map[emotion])
- return PROCESS_KILL
-
/*
/obj/item/circuit_component/status_display
display_name = "Status Display"
@@ -584,9 +563,13 @@
var/static/list/picture_options = list(
"Default" = "default",
+ "Delta Alert" = "deltaalert",
"Red Alert" = "redalert",
+ "Blue Alert" = "bluealert",
+ "Green Alert" = "greenalert",
"Biohazard" = "biohazard",
"Lockdown" = "lockdown",
+ "Radiation" = "radiation",
"Happy" = "ai_happy",
"Neutral" = "ai_neutral",
"Very Happy" = "ai_veryhappy",
@@ -623,18 +606,21 @@
var/datum/signal/status_signal = new(list("command" = command_value))
switch(command_value)
if("message")
- status_signal.data["msg1"] = message1.value
- status_signal.data["msg2"] = message2.value
+ status_signal.data["top_text"] = message1.value
+ status_signal.data["bottom_text"] = message2.value
if("alert")
status_signal.data["picture_state"] = picture_map[picture.value]
connected_display.receive_signal(status_signal)
*/
-#undef CHARS_PER_LINE
-#undef FONT_SIZE
-#undef FONT_COLOR
+#undef MAX_STATIC_WIDTH
#undef FONT_STYLE
#undef SCROLL_RATE
+#undef LINE1_X
#undef LINE1_Y
+#undef LINE2_X
#undef LINE2_Y
+#undef STATUS_DISPLAY_FONT_DATUM
+
+#undef SCROLL_PADDING
diff --git a/code/game/machinery/suit_storage_unit.dm b/code/game/machinery/suit_storage_unit.dm
index 64b89b498042..acb805f1e8bf 100644
--- a/code/game/machinery/suit_storage_unit.dm
+++ b/code/game/machinery/suit_storage_unit.dm
@@ -205,6 +205,7 @@
QDEL_NULL(mask)
QDEL_NULL(mod)
QDEL_NULL(storage)
+ QDEL_NULL(wires)
return ..()
/obj/machinery/suit_storage_unit/update_overlays()
diff --git a/code/game/machinery/telecomms/telecomunications.dm b/code/game/machinery/telecomms/telecomunications.dm
index a49fb325384e..9593066248c9 100644
--- a/code/game/machinery/telecomms/telecomunications.dm
+++ b/code/game/machinery/telecomms/telecomunications.dm
@@ -97,7 +97,7 @@ GLOBAL_LIST_EMPTY(telecomms_list)
/obj/machinery/telecomms/proc/add_link(obj/machinery/telecomms/T)
var/turf/position = get_turf(src)
var/turf/T_position = get_turf(T)
- if((position.z == T_position.z) || (long_range_link && T.long_range_link))
+ if((position?.z == T_position?.z) || (long_range_link && T.long_range_link))
if(src != T)
for(var/x in autolinkers)
if(x in T.autolinkers)
diff --git a/code/game/objects/effects/anomalies.dm b/code/game/objects/effects/anomalies.dm
index e8f83bd0c9d1..ef622631f8e2 100644
--- a/code/game/objects/effects/anomalies.dm
+++ b/code/game/objects/effects/anomalies.dm
@@ -57,7 +57,7 @@
/obj/effect/anomaly/Destroy()
GLOB.poi_list.Remove(src)
STOP_PROCESSING(SSobj, src)
- qdel(countdown)
+ QDEL_NULL(countdown)
if(aSignal)
QDEL_NULL(aSignal)
return ..()
diff --git a/code/game/objects/effects/decals/crayon.dm b/code/game/objects/effects/decals/crayon.dm
index d84b3f15edbf..2293c79c1d94 100644
--- a/code/game/objects/effects/decals/crayon.dm
+++ b/code/game/objects/effects/decals/crayon.dm
@@ -81,4 +81,4 @@ GLOBAL_LIST(gang_tags)
/obj/effect/decal/cleanable/crayon/gang/Destroy()
LAZYREMOVE(GLOB.gang_tags, src)
- ..()
+ return ..()
diff --git a/code/game/objects/effects/landmarks.dm b/code/game/objects/effects/landmarks.dm
index 5e533888f95d..476e1d602e04 100644
--- a/code/game/objects/effects/landmarks.dm
+++ b/code/game/objects/effects/landmarks.dm
@@ -42,13 +42,13 @@ INITIALIZE_IMMEDIATE(/obj/effect/landmark)
if(delete_after_roundstart)
qdel(src)
-/obj/effect/landmark/start/New()
+/obj/effect/landmark/start/Initialize(mapload)
+ . = ..()
GLOB.start_landmarks_list += src
if(jobspawn_override)
if(!GLOB.jobspawn_overrides[name])
GLOB.jobspawn_overrides[name] = list()
GLOB.jobspawn_overrides[name] += src
- ..()
if(name != "start")
tag = "start*[name]"
diff --git a/code/game/objects/effects/temporary_visuals/temporary_visual.dm b/code/game/objects/effects/temporary_visuals/temporary_visual.dm
index bf4e82f7b7f0..29696f5ad73b 100644
--- a/code/game/objects/effects/temporary_visuals/temporary_visual.dm
+++ b/code/game/objects/effects/temporary_visuals/temporary_visual.dm
@@ -13,7 +13,7 @@
if(randomdir)
setDir(pick(GLOB.cardinals))
- timerid = QDEL_IN(src, duration)
+ timerid = QDEL_IN_STOPPABLE(src, duration)
/obj/effect/temp_visual/Destroy()
. = ..()
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index e437ac5c8bda..8779251f7ad5 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -212,6 +212,7 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb
LAZYADD(used_skills[path], S.skill_traits)
/obj/item/Destroy()
+ master = null
item_flags &= ~DROPDEL //prevent reqdels
if(ismob(loc))
var/mob/m = loc
diff --git a/code/game/objects/items/cards_ids.dm b/code/game/objects/items/cards_ids.dm
index fac44ddf5447..cf11e2852695 100644
--- a/code/game/objects/items/cards_ids.dm
+++ b/code/game/objects/items/cards_ids.dm
@@ -782,6 +782,7 @@
/obj/item/card/id/departmental_budget/Destroy()
SSeconomy.dep_cards -= src
+ registered_account.bank_cards -= src
return ..()
/obj/item/card/id/departmental_budget/update_label()
diff --git a/code/game/objects/items/chrono_eraser.dm b/code/game/objects/items/chrono_eraser.dm
index 6670af9bea98..97c2a25d6965 100644
--- a/code/game/objects/items/chrono_eraser.dm
+++ b/code/game/objects/items/chrono_eraser.dm
@@ -130,6 +130,11 @@
if(istype(C))
gun = C.gun
+/obj/item/projectile/energy/chrono_beam/Destroy()
+ gun = null
+ return ..()
+
+
/obj/item/projectile/energy/chrono_beam/on_hit(atom/target)
if(target && gun && isliving(target))
var/obj/effect/chrono_field/F = new(target.loc, target, gun)
@@ -148,7 +153,9 @@
gun = loc
. = ..()
-
+/obj/item/ammo_casing/energy/chrono_beam/Destroy()
+ gun = null
+ return ..()
diff --git a/code/game/objects/items/crab17.dm b/code/game/objects/items/crab17.dm
index b228e647360a..6a63213adc3e 100644
--- a/code/game/objects/items/crab17.dm
+++ b/code/game/objects/items/crab17.dm
@@ -81,6 +81,14 @@
addtimer(CALLBACK(src, PROC_REF(startUp)), 50)
QDEL_IN(src, 8 MINUTES) //Self destruct after 8 min
+/obj/structure/checkoutmachine/Destroy()
+ bogdanoff = null
+ stop_dumping()
+ STOP_PROCESSING(SSfastprocess, src)
+ priority_announce("The credit deposit machine at [get_area(src)] has been destroyed. Station funds have stopped draining!", sender_override = "CRAB-17 Protocol")
+ explosion(src, 0,0,1, flame_range = 2)
+ return ..()
+
/obj/structure/checkoutmachine/proc/startUp() //very VERY snowflake code that adds a neat animation when the pod lands.
start_dumping() //The machine doesnt move during this time, giving people close by a small window to grab their funds before it starts running around
@@ -145,13 +153,6 @@
canwalk = TRUE
START_PROCESSING(SSfastprocess, src)
-/obj/structure/checkoutmachine/Destroy()
- stop_dumping()
- STOP_PROCESSING(SSfastprocess, src)
- priority_announce("The credit deposit machine at [get_area(src)] has been destroyed. Station funds have stopped draining!", sender_override = "CRAB-17 Protocol")
- explosion(src, 0,0,1, flame_range = 2)
- return ..()
-
/obj/structure/checkoutmachine/proc/start_dumping()
accounts_to_rob = SSeconomy.bank_accounts.Copy()
accounts_to_rob -= bogdanoff.get_bank_account()
@@ -220,7 +221,10 @@
playsound(src, 'sound/weapons/mortar_whistle.ogg', 70, TRUE, 6)
addtimer(CALLBACK(src, PROC_REF(endLaunch)), 5, TIMER_CLIENT_TIME) //Go onto the last step after a very short falling animation
-
+/obj/effect/dumpeetTarget/Destroy()
+ dump = null
+ bogdanoff = null
+ return ..()
/obj/effect/dumpeetTarget/proc/endLaunch()
QDEL_NULL(DF) //Delete the falling machine effect, because at this point its animation is over. We dont use temp_visual because we want to manually delete it as soon as the pod appears
diff --git a/code/game/objects/items/devices/PDA/cart.dm b/code/game/objects/items/devices/PDA/cart.dm
index 0a3d63521894..8fe93d9344e9 100644
--- a/code/game/objects/items/devices/PDA/cart.dm
+++ b/code/game/objects/items/devices/PDA/cart.dm
@@ -212,8 +212,8 @@
var/datum/signal/status_signal = new(list("command" = command))
switch(command)
if("message")
- status_signal.data["msg1"] = data1
- status_signal.data["msg2"] = data2
+ status_signal.data["top_text"] = data1
+ status_signal.data["bottom_text"] = data2
if("alert")
status_signal.data["picture_state"] = data1
diff --git a/code/game/objects/items/devices/chameleonproj.dm b/code/game/objects/items/devices/chameleonproj.dm
index 8656cc21f168..da252a322666 100644
--- a/code/game/objects/items/devices/chameleonproj.dm
+++ b/code/game/objects/items/devices/chameleonproj.dm
@@ -175,5 +175,7 @@
return
/obj/effect/dummy/chameleon/Destroy()
- master.disrupt(0)
+ if(master)
+ master.disrupt(0)
+ master = null
return ..()
diff --git a/code/game/objects/items/devices/forcefieldprojector.dm b/code/game/objects/items/devices/forcefieldprojector.dm
index 47c3bc8d134c..58fafbc298a1 100644
--- a/code/game/objects/items/devices/forcefieldprojector.dm
+++ b/code/game/objects/items/devices/forcefieldprojector.dm
@@ -98,8 +98,9 @@
/obj/structure/projected_forcefield/Destroy()
visible_message("[src] flickers and disappears!")
playsound(src,'sound/weapons/resonator_blast.ogg',25,1)
- generator.current_fields -= src
- generator = null
+ if(generator)
+ generator.current_fields -= src
+ generator = null
return ..()
/obj/structure/projected_forcefield/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0)
@@ -108,4 +109,5 @@
/obj/structure/projected_forcefield/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir)
if(sound_effect)
play_attack_sound(damage_amount, damage_type, damage_flag)
- generator.shield_integrity = max(generator.shield_integrity - damage_amount, 0)
+ if(generator)
+ generator.shield_integrity = max(generator.shield_integrity - damage_amount, 0)
diff --git a/code/game/objects/items/devices/radio/headset.dm b/code/game/objects/items/devices/radio/headset.dm
index 8403ea30e73b..99cc0c58935b 100644
--- a/code/game/objects/items/devices/radio/headset.dm
+++ b/code/game/objects/items/devices/radio/headset.dm
@@ -265,10 +265,6 @@ GLOBAL_LIST_INIT(channel_tokens, list(
name = "\proper mini Integrated Subspace Transceiver "
subspace_transmission = FALSE
-/obj/item/radio/headset/silicon/pai/ComponentInitialize()
- . = ..()
- AddElement(/datum/element/empprotection, EMP_PROTECT_WIRES)
-
/obj/item/radio/headset/silicon/pai/emp_act(severity)
. = ..()
return EMP_PROTECT_SELF
diff --git a/code/game/objects/items/devices/transfer_valve.dm b/code/game/objects/items/devices/transfer_valve.dm
index 4ed50044a072..626fe6569f92 100644
--- a/code/game/objects/items/devices/transfer_valve.dm
+++ b/code/game/objects/items/devices/transfer_valve.dm
@@ -19,6 +19,23 @@
/obj/item/transfer_valve/IsAssemblyHolder()
return TRUE
+/obj/item/transfer_valve/Destroy()
+ attached_device = null
+ QDEL_NULL(tank_one)
+ QDEL_NULL(tank_two)
+ return ..()
+
+/obj/item/transfer_valve/handle_atom_del(atom/deleted_atom)
+ . = ..()
+ if(deleted_atom == tank_one)
+ tank_one = null
+ update_appearance()
+ return
+ if(deleted_atom == tank_two)
+ tank_two = null
+ update_appearance()
+ return
+
/obj/item/transfer_valve/attackby(obj/item/item, mob/user, params)
if(istype(item, /obj/item/tank))
if(tank_one && tank_two)
diff --git a/code/game/objects/items/grenades/clusterbuster.dm b/code/game/objects/items/grenades/clusterbuster.dm
index 838704c9c035..e6823c42336d 100644
--- a/code/game/objects/items/grenades/clusterbuster.dm
+++ b/code/game/objects/items/grenades/clusterbuster.dm
@@ -70,7 +70,8 @@
/////////////////////////////////
/obj/effect/payload_spawner/Initialize(mapload, type, numspawned)
..()
- spawn_payload(type, numspawned)
+ if(type && isnum(numspawned))
+ spawn_payload(type, numspawned)
return INITIALIZE_HINT_QDEL
/obj/effect/payload_spawner/proc/spawn_payload(type, numspawned)
diff --git a/code/game/objects/items/grenades/plastic.dm b/code/game/objects/items/grenades/plastic.dm
index 338975cb3101..6b3ab67ef3de 100644
--- a/code/game/objects/items/grenades/plastic.dm
+++ b/code/game/objects/items/grenades/plastic.dm
@@ -33,7 +33,7 @@
qdel(nadeassembly)
nadeassembly = null
target = null
- ..()
+ return ..()
/obj/item/grenade/plastic/attackby(obj/item/I, mob/user, params)
if(!nadeassembly && istype(I, /obj/item/assembly_holder))
diff --git a/code/game/objects/items/melee/energy.dm b/code/game/objects/items/melee/energy.dm
index b389efe5425c..f80538b01596 100644
--- a/code/game/objects/items/melee/energy.dm
+++ b/code/game/objects/items/melee/energy.dm
@@ -332,6 +332,10 @@
spark_system.set_up(5, 0, src)
spark_system.attach(src)
+/obj/item/melee/transforming/energy/blade/Destroy()
+ QDEL_NULL(spark_system)
+ . = ..()
+
/obj/item/melee/transforming/energy/blade/transform_weapon(mob/living/user, supress_message_text)
return
diff --git a/code/game/objects/items/plushes.dm b/code/game/objects/items/plushes.dm
index ef7d1d385c5e..d5e1dac7088c 100644
--- a/code/game/objects/items/plushes.dm
+++ b/code/game/objects/items/plushes.dm
@@ -469,7 +469,7 @@ GLOBAL_LIST_INIT(valid_plushie_paths, valid_plushie_paths())
can_random_spawn = FALSE
/obj/item/toy/plush/random/Initialize(mapload)
- ..()
+ . = ..()
var/newtype
var/list/snowflake_list = CONFIG_GET(keyed_list/snowflake_plushies)
diff --git a/code/game/objects/items/religion.dm b/code/game/objects/items/religion.dm
index bd03698cd7e4..e03b24640246 100644
--- a/code/game/objects/items/religion.dm
+++ b/code/game/objects/items/religion.dm
@@ -294,19 +294,13 @@
name = "Crusader's Armour Set" //i can't into ck2 references
desc = "This armour is said to be based on the armor of kings on another world thousands of years ago, who tended to assassinate, conspire, and plot against everyone who tried to do the same to them. Some things never change."
-/obj/item/storage/box/itemset/crusader/blue/New()
- ..()
- contents = list()
- sleep(1)
+/obj/item/storage/box/itemset/crusader/blue/PopulateContents()
new /obj/item/clothing/suit/armor/plate/crusader/blue(src)
new /obj/item/clothing/head/helmet/plate/crusader/blue(src)
new /obj/item/clothing/gloves/plate/blue(src)
new /obj/item/clothing/shoes/plate/blue(src)
-/obj/item/storage/box/itemset/crusader/red/New()
- ..()
- contents = list()
- sleep(1)
+/obj/item/storage/box/itemset/crusader/red/PopulateContents()
new /obj/item/clothing/suit/armor/plate/crusader/red(src)
new /obj/item/clothing/head/helmet/plate/crusader/red(src)
new /obj/item/clothing/gloves/plate/red(src)
diff --git a/code/game/objects/items/storage/_storage.dm b/code/game/objects/items/storage/_storage.dm
index cbaa1775eb0d..af61ee1ea99e 100644
--- a/code/game/objects/items/storage/_storage.dm
+++ b/code/game/objects/items/storage/_storage.dm
@@ -16,7 +16,7 @@
AddComponent(component_type)
/obj/item/storage/AllowDrop()
- return TRUE
+ return !QDELETED(src)
/obj/item/storage/contents_explosion(severity, target, origin)
var/in_storage = istype(loc, /obj/item/storage)? (max(0, severity - 1)) : (severity)
diff --git a/code/game/objects/items/storage/boxes.dm b/code/game/objects/items/storage/boxes.dm
index 9aa29a4d7571..c05e20d1dac6 100644
--- a/code/game/objects/items/storage/boxes.dm
+++ b/code/game/objects/items/storage/boxes.dm
@@ -631,7 +631,8 @@
STR.max_items = 8
/obj/item/storage/box/snappops/PopulateContents()
- SEND_SIGNAL(src, COMSIG_TRY_STORAGE_FILL_TYPE, /obj/item/toy/snappop)
+ for(var/i in 1 to 8)
+ new /obj/item/toy/snappop(src)
/obj/item/storage/box/matches
name = "matchbox"
@@ -651,7 +652,8 @@
STR.can_hold = typecacheof(list(/obj/item/match))
/obj/item/storage/box/matches/PopulateContents()
- SEND_SIGNAL(src, COMSIG_TRY_STORAGE_FILL_TYPE, /obj/item/match)
+ for(var/i in 1 to 10)
+ new /obj/item/match(src)
/obj/item/storage/box/matches/attackby(obj/item/match/W as obj, mob/user as mob, params)
if(istype(W, /obj/item/match))
diff --git a/code/game/objects/items/storage/fancy.dm b/code/game/objects/items/storage/fancy.dm
index 111e3c96a052..050dcd0df073 100644
--- a/code/game/objects/items/storage/fancy.dm
+++ b/code/game/objects/items/storage/fancy.dm
@@ -23,6 +23,8 @@
var/fancy_open = FALSE
/obj/item/storage/fancy/PopulateContents()
+ if(!spawn_type)
+ return
var/datum/component/storage/STR = GetComponent(/datum/component/storage)
for(var/i = 1 to STR.max_items)
new spawn_type(src)
diff --git a/code/game/objects/items/summon.dm b/code/game/objects/items/summon.dm
index 6de3d80a82ac..0b7fe9b57d5d 100644
--- a/code/game/objects/items/summon.dm
+++ b/code/game/objects/items/summon.dm
@@ -34,6 +34,10 @@
if(host_type)
host = new host_type(src, summon_count, range)
+/obj/item/summon/Destroy()
+ QDEL_NULL(host)
+ return ..()
+
/obj/item/summon/afterattack(atom/target, mob/user, proximity_flag, click_parameters)
. = ..()
if(!host)
@@ -329,7 +333,9 @@
if(del_no_host)
qdel(src)
return
- HardReset(null)
+ if(animation_timerid)
+ deltimer(animation_timerid)
+ atom.transform = null
atom.moveToNullspace()
return
if(immediate)
diff --git a/code/game/objects/items/tanks/watertank.dm b/code/game/objects/items/tanks/watertank.dm
index db895bbcd755..62fb75518f0c 100644
--- a/code/game/objects/items/tanks/watertank.dm
+++ b/code/game/objects/items/tanks/watertank.dm
@@ -119,11 +119,17 @@
/obj/item/reagent_containers/spray/mister/Initialize(mapload)
. = ..()
+ QDEL_NULL(reagents)
tank = loc
if(!istype(tank))
return INITIALIZE_HINT_QDEL
reagents = tank.reagents //This mister is really just a proxy for the tank's reagents
+/obj/item/reagent_containers/spray/mister/Destroy()
+ tank = null
+ reagents = null
+ return ..()
+
/obj/item/reagent_containers/spray/mister/attack_self()
return
@@ -221,12 +227,18 @@
/obj/item/extinguisher/mini/nozzle/Initialize(mapload)
. = ..()
+ QDEL_NULL(reagents)
tank = loc
if (!istype(tank))
return INITIALIZE_HINT_QDEL
reagents = tank.reagents
max_water = tank.volume
+/obj/item/extinguisher/mini/nozzle/Destroy()
+ reagents = null //This is a borrowed reference from the tank.
+ tank = null
+ return ..()
+
/obj/item/extinguisher/mini/nozzle/doMove(atom/destination)
if(destination && (destination != tank.loc || !ismob(destination)))
diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm
index b05abdc49a96..3ad00bf95623 100644
--- a/code/game/objects/items/toys.dm
+++ b/code/game/objects/items/toys.dm
@@ -546,15 +546,15 @@
/obj/effect/decal/cleanable/ash/snappop_phoenix
var/respawn_time = 300
-/obj/effect/decal/cleanable/ash/snappop_phoenix/New()
+/obj/effect/decal/cleanable/ash/snappop_phoenix/Initialize(mapload)
. = ..()
- addtimer(CALLBACK(src, PROC_REF(respawn)), respawn_time)
+ if(!QDELETED(src))
+ addtimer(CALLBACK(src, PROC_REF(respawn)), respawn_time)
/obj/effect/decal/cleanable/ash/snappop_phoenix/proc/respawn()
new /obj/item/toy/snappop/phoenix(get_turf(src))
qdel(src)
-
/*
* Mech prizes
*/
diff --git a/code/game/objects/items/weaponry.dm b/code/game/objects/items/weaponry.dm
index 2c669e23a9f5..8d85f97ba9dd 100644
--- a/code/game/objects/items/weaponry.dm
+++ b/code/game/objects/items/weaponry.dm
@@ -226,9 +226,9 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
/obj/item/claymore/highlander/robot/Initialize(mapload)
var/obj/item/robot_module/kiltkit = loc
robot = kiltkit.loc
+ . = ..()
if(!istype(robot))
- qdel(src)
- return ..()
+ return INITIALIZE_HINT_QDEL
/obj/item/claymore/highlander/robot/process()
loc.layer = LARGE_MOB_LAYER
diff --git a/code/game/objects/structures/bedsheet_bin.dm b/code/game/objects/structures/bedsheet_bin.dm
index 2c3b0545f81b..f1b580dc765e 100644
--- a/code/game/objects/structures/bedsheet_bin.dm
+++ b/code/game/objects/structures/bedsheet_bin.dm
@@ -293,7 +293,7 @@ GLOBAL_LIST_INIT(double_bedsheets, list(/obj/item/bedsheet/double,
desc = "If you're reading this description ingame, something has gone wrong! Honk!"
/obj/item/bedsheet/random/Initialize(mapload)
- ..()
+ . = ..()
if(bedsheet_type == BEDSHEET_SINGLE)
var/type = pick(typesof(/obj/item/bedsheet) - (list(/obj/item/bedsheet/random, /obj/item/bedsheet/chameleon) + typesof(/obj/item/bedsheet/unlockable) + GLOB.double_bedsheets))
new type(loc)
@@ -454,7 +454,7 @@ GLOBAL_LIST_INIT(double_bedsheets, list(/obj/item/bedsheet/double,
bedsheet_type = BEDSHEET_DOUBLE
/obj/item/bedsheet/random/double/Initialize(mapload)
- ..()
+ . = ..()
if(bedsheet_type == BEDSHEET_DOUBLE)
var/type = pick(GLOB.double_bedsheets)
new type(loc)
diff --git a/code/game/objects/structures/displaycase.dm b/code/game/objects/structures/displaycase.dm
index 4df8d709391f..e887577ecb4e 100644
--- a/code/game/objects/structures/displaycase.dm
+++ b/code/game/objects/structures/displaycase.dm
@@ -376,8 +376,9 @@
I = icon('icons/obj/stationobjs.dmi',"laserbox_broken")
if(showpiece)
var/icon/S = getFlatIcon(showpiece)
- S.Scale(17,17)
- I.Blend(S,ICON_UNDERLAY,8,12)
+ if(S)
+ S.Scale(17,17)
+ I.Blend(S,ICON_UNDERLAY,8,12)
src.icon = I
return
diff --git a/code/game/objects/structures/ghost_role_spawners.dm b/code/game/objects/structures/ghost_role_spawners.dm
index 0b8a7c5a30bb..3744793c0759 100644
--- a/code/game/objects/structures/ghost_role_spawners.dm
+++ b/code/game/objects/structures/ghost_role_spawners.dm
@@ -429,7 +429,7 @@
/obj/effect/mob_spawn/human/hotel_staff/Destroy()
new/obj/structure/fluff/empty_sleeper/syndicate(get_turf(src))
- ..()
+ return ..()
/obj/effect/mob_spawn/human/hotel_staff/special(mob/living/carbon/human/new_spawn)
ADD_TRAIT(new_spawn,TRAIT_EXEMPT_HEALTH_EVENTS,GHOSTROLE_TRAIT)
@@ -453,6 +453,8 @@
/obj/effect/mob_spawn/human/demonic_friend/Initialize(mapload, datum/mind/owner_mind, obj/effect/proc_holder/spell/targeted/summon_friend/summoning_spell)
. = ..()
+ if(!owner_mind)
+ return
owner = owner_mind
flavour_text = "You have been given a reprieve from your eternity of torment, to be [owner.name]'s friend for [owner.p_their()] short mortal coil."
important_info = "Be aware that if you do not live up to [owner.name]'s expectations, they can send you back to hell with a single thought. [owner.name]'s death will also return you to hell."
diff --git a/code/game/objects/structures/guncase.dm b/code/game/objects/structures/guncase.dm
index 78f0da2db22f..33803d8e8595 100644
--- a/code/game/objects/structures/guncase.dm
+++ b/code/game/objects/structures/guncase.dm
@@ -13,7 +13,7 @@
var/capacity = 4
/obj/structure/guncase/Initialize(mapload)
- ..()
+ . = ..()
if(mapload)
for(var/obj/item/I in loc.contents)
if(istype(I, gun_category))
diff --git a/code/game/objects/structures/manned_turret.dm b/code/game/objects/structures/manned_turret.dm
index 7f7574c2f34d..2abad25c1fff 100644
--- a/code/game/objects/structures/manned_turret.dm
+++ b/code/game/objects/structures/manned_turret.dm
@@ -193,7 +193,7 @@
/obj/item/gun_control/Destroy()
turret = null
- ..()
+ return ..()
/obj/item/gun_control/CanItemAutoclick()
return TRUE
diff --git a/code/game/objects/structures/morgue.dm b/code/game/objects/structures/morgue.dm
index 42a5a0d8cb89..e4bde706a8a7 100644
--- a/code/game/objects/structures/morgue.dm
+++ b/code/game/objects/structures/morgue.dm
@@ -121,7 +121,8 @@ GLOBAL_LIST_EMPTY(bodycontainers) //Let them act as spawnpoints for revenants an
playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 1)
playsound(src, 'sound/effects/roll.ogg', 5, 1)
var/turf/T = get_step(src, dir)
- connected.setDir(dir)
+ if(connected)
+ connected.setDir(dir)
for(var/atom/movable/AM in src)
AM.forceMove(T)
update_icon()
diff --git a/code/game/objects/structures/traps.dm b/code/game/objects/structures/traps.dm
index e6a76f36caf9..2c3f01935da0 100644
--- a/code/game/objects/structures/traps.dm
+++ b/code/game/objects/structures/traps.dm
@@ -169,6 +169,12 @@
. = ..()
time_between_triggers = 10
+/obj/structure/trap/stun/hunter/Destroy()
+ if(!QDELETED(stored_item))
+ qdel(stored_item)
+ stored_item = null
+ return ..()
+
/obj/structure/trap/stun/hunter/Crossed(atom/movable/AM)
if(isliving(AM))
var/mob/living/L = AM
@@ -179,6 +185,11 @@
/obj/structure/trap/stun/hunter/flare()
..()
+ var/turf/our_turf = get_turf(src)
+ if(!our_turf)
+ return
+ if(!stored_item)
+ return
stored_item.forceMove(get_turf(src))
forceMove(stored_item)
if(caught)
@@ -208,6 +219,12 @@
stored_trap.name = name
stored_trap.stored_item = src
+/obj/item/bountytrap/Destroy()
+ QDEL_NULL(stored_trap)
+ QDEL_NULL(radio)
+ QDEL_NULL(spark_system)
+ . = ..()
+
/obj/item/bountytrap/proc/announce_fugitive()
spark_system.start()
playsound(src, 'sound/machines/ding.ogg', 50, TRUE)
@@ -220,9 +237,3 @@
to_chat(user, "You set up [src]. Examine while close to disarm it.")
stored_trap.forceMove(T)//moves trap to ground
forceMove(stored_trap)//moves item into trap
-
-/obj/item/bountytrap/Destroy()
- qdel(stored_trap)
- QDEL_NULL(radio)
- QDEL_NULL(spark_system)
- . = ..()
diff --git a/code/game/shuttle_engines.dm b/code/game/shuttle_engines.dm
index b0f06a1495c3..5327ce9419e9 100644
--- a/code/game/shuttle_engines.dm
+++ b/code/game/shuttle_engines.dm
@@ -72,7 +72,7 @@
/obj/structure/shuttle/engine/Destroy()
if(state == ENGINE_WELDED)
alter_engine_power(-engine_power)
- . = ..()
+ return ..()
//Propagates the change to the shuttle.
/obj/structure/shuttle/engine/proc/alter_engine_power(mod)
diff --git a/code/game/turfs/simulated/lava.dm b/code/game/turfs/simulated/lava.dm
index 3cd08ff419f9..d2d4ddd1ee94 100644
--- a/code/game/turfs/simulated/lava.dm
+++ b/code/game/turfs/simulated/lava.dm
@@ -48,6 +48,7 @@
initial_gas_mix = AIRLESS_ATMOS
/turf/open/lava/Entered(atom/movable/AM)
+ . = ..()
if(burn_stuff(AM))
START_PROCESSING(SSobj, src)
@@ -126,7 +127,6 @@
///Proc that sets on fire something or everything on the turf that's not immune to lava. Returns TRUE to make the turf start processing.
/turf/open/lava/proc/burn_stuff(atom/movable/to_burn, delta_time = 1)
-
if(is_safe())
return FALSE
diff --git a/code/game/turfs/simulated/openspace.dm b/code/game/turfs/simulated/openspace.dm
index 935bfdc1a7ab..aed5afe315bc 100644
--- a/code/game/turfs/simulated/openspace.dm
+++ b/code/game/turfs/simulated/openspace.dm
@@ -168,6 +168,8 @@ GLOBAL_DATUM_INIT(openspace_backdrop_one_for_all, /atom/movable/openspace_backdr
/turf/open/openspace/icemoon/Initialize(mapload)
. = ..()
var/turf/T = below()
+ if(!T)
+ return
if(T.flags_1 & NO_RUINS_1 && protect_ruin)
ChangeTurf(replacement_turf, null, CHANGETURF_IGNORE_AIR)
return
diff --git a/code/game/turfs/space/transit.dm b/code/game/turfs/space/transit.dm
index ed3b20fce473..fae12cc51efc 100644
--- a/code/game/turfs/space/transit.dm
+++ b/code/game/turfs/space/transit.dm
@@ -92,9 +92,11 @@
_y = min
var/turf/T = locate(_x, _y, _z)
- AM.forceMove(T)
- var/turf/throwturf = get_ranged_target_turf(T, dir, 1)
- AM.safe_throw_at(throwturf, 1, 4, null, FALSE)
+
+ if(!QDELETED(AM))
+ AM.forceMove(T)
+ var/turf/throwturf = get_ranged_target_turf(T, dir, 1)
+ AM.safe_throw_at(throwturf, 1, 4, null, FALSE)
/turf/open/space/transit/CanBuildHere()
diff --git a/code/game/world.dm b/code/game/world.dm
index c6a5a2ef957a..9a5fed497f47 100644
--- a/code/game/world.dm
+++ b/code/game/world.dm
@@ -23,6 +23,10 @@ GLOBAL_LIST(topic_status_cache)
make_datum_references_lists() //initialises global lists for referencing frequently used datums (so that we only ever do it once)
+ #ifdef REFERENCE_DOING_IT_LIVE
+ GLOB.harddel_log = GLOB.world_game_log
+ #endif
+
GLOB.revdata = new
InitTgs()
@@ -137,6 +141,10 @@ GLOBAL_LIST(topic_status_cache)
#ifdef UNIT_TESTS
GLOB.test_log = "[GLOB.log_directory]/tests.log"
start_log(GLOB.test_log)
+#endif
+#ifdef REFERENCE_DOING_IT_LIVE
+ GLOB.harddel_log = "[GLOB.log_directory]/harddels.log"
+ start_log(GLOB.harddel_log)
#endif
start_log(GLOB.world_game_log)
start_log(GLOB.world_attack_log)
diff --git a/code/modules/admin/view_variables/reference_tracking.dm b/code/modules/admin/view_variables/reference_tracking.dm
index 714b54cd45f0..5953c8cf279f 100644
--- a/code/modules/admin/view_variables/reference_tracking.dm
+++ b/code/modules/admin/view_variables/reference_tracking.dm
@@ -134,6 +134,15 @@ GLOBAL_LIST_EMPTY(deletion_failures)
DoSearchVar(GLOB, "GLOB") //globals
log_reftracker("Finished searching globals")
+ //Yes we do actually need to do this. The searcher refuses to read weird lists
+ //And global.vars is a really weird list
+ var/global_vars = list()
+ for(var/key in global.vars)
+ global_vars[key] = global.vars[key]
+
+ DoSearchVar(global_vars, "Native Global", search_time = starting_time)
+ log_reftracker("Finished searching native 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")
@@ -143,9 +152,11 @@ GLOBAL_LIST_EMPTY(deletion_failures)
log_reftracker("Finished searching datums")
//Warning, attempting to search clients like this will cause crashes if done on live. Watch yourself
+#ifndef REFERENCE_DOING_IT_LIVE
for(var/client/thing) //clients
DoSearchVar(thing, "Clients -> [thing.type]", search_time = starting_time)
log_reftracker("Finished searching clients")
+#endif
log_reftracker("Completed search for references to a [type].")
@@ -159,7 +170,7 @@ GLOBAL_LIST_EMPTY(deletion_failures)
/datum/proc/DoSearchVar(potential_container, container_name, recursive_limit = 64, search_time = world.time)
#ifdef REFERENCE_TRACKING_DEBUG
- if(!found_refs && SSgarbage.should_save_refs)
+ if(SSgarbage.should_save_refs && !found_refs)
found_refs = list()
#endif
diff --git a/code/modules/antagonists/blob/blob/blobstrains/synchronous_mesh.dm b/code/modules/antagonists/blob/blob/blobstrains/synchronous_mesh.dm
index ad6b36cf428b..11030923668b 100644
--- a/code/modules/antagonists/blob/blob/blobstrains/synchronous_mesh.dm
+++ b/code/modules/antagonists/blob/blob/blobstrains/synchronous_mesh.dm
@@ -9,6 +9,7 @@
complementary_color = "#AD6570"
blobbernaut_message = "synchronously strikes"
message = "The blobs strike you"
+ reagent = /datum/reagent/blob/synchronous_mesh
/datum/blobstrain/reagent/synchronous_mesh/damage_reaction(obj/structure/blob/B, damage, damage_type, damage_flag)
if(damage_flag == MELEE || damage_flag == BULLET || damage_flag == LASER) //the cause isn't fire or bombs, so split the damage
diff --git a/code/modules/antagonists/blob/blob/overmind.dm b/code/modules/antagonists/blob/blob/overmind.dm
index 80b7d097d076..928afdfce6ec 100644
--- a/code/modules/antagonists/blob/blob/overmind.dm
+++ b/code/modules/antagonists/blob/blob/overmind.dm
@@ -72,19 +72,23 @@ GLOBAL_LIST_EMPTY(blob_nodes)
forceMove(T)
/mob/camera/blob/proc/set_strain(datum/blobstrain/new_strain)
- if (ispath(new_strain))
- var/hadstrain = FALSE
- if (istype(blobstrain))
- blobstrain.on_lose()
- qdel(blobstrain)
- hadstrain = TRUE
- blobstrain = new new_strain(src)
- blobstrain.on_gain()
- if (hadstrain)
- to_chat(src, "Your strain is now: [blobstrain.name]!")
- to_chat(src, "The [blobstrain.name] strain [blobstrain.description]")
- if(blobstrain.effectdesc)
- to_chat(src, "The [blobstrain.name] strain [blobstrain.effectdesc]")
+ if(!ispath(new_strain))
+ return FALSE
+
+ var/had_strain = FALSE
+ if(istype(blobstrain))
+ blobstrain.on_lose()
+ qdel(blobstrain)
+ had_strain = TRUE
+
+ blobstrain = new new_strain(src)
+ blobstrain.on_gain()
+
+ if(had_strain)
+ to_chat(src, "Your strain is now: [blobstrain.name]!")
+ to_chat(src, "The [blobstrain.name] strain [blobstrain.description]")
+ if(blobstrain.effectdesc)
+ to_chat(src, "The [blobstrain.name] strain [blobstrain.effectdesc]")
/mob/camera/blob/proc/is_valid_turf(turf/T)
var/area/A = get_area(T)
diff --git a/code/modules/antagonists/clockcult/clock_effects/spatial_gateway.dm b/code/modules/antagonists/clockcult/clock_effects/spatial_gateway.dm
index df93111d31a2..922e442e90da 100644
--- a/code/modules/antagonists/clockcult/clock_effects/spatial_gateway.dm
+++ b/code/modules/antagonists/clockcult/clock_effects/spatial_gateway.dm
@@ -35,7 +35,7 @@
clockwork_desc = "A gateway in reality. It can only [sender ? "send" : "receive"] objects."
if(is_stable)
return
- timerid = QDEL_IN(src, lifetime) //We only need this if the gateway is not stable
+ timerid = QDEL_IN_STOPPABLE(src, lifetime) //We only need this if the gateway is not stable
//set up a gateway with another gateway
/obj/effect/clockwork/spatial_gateway/proc/setup_gateway(obj/effect/clockwork/spatial_gateway/gatewayB, set_duration, set_uses, two_way)
@@ -108,12 +108,12 @@
visible_message("[src] is disrupted!")
animate(src, alpha = 0, transform = matrix()*2, time = 10, flags = ANIMATION_END_NOW)
deltimer(timerid)
- timerid = QDEL_IN(src, 10)
+ timerid = QDEL_IN_STOPPABLE(src, 10)
linked_gateway.uses = 0
linked_gateway.visible_message("[linked_gateway] is disrupted!")
animate(linked_gateway, alpha = 0, transform = matrix()*2, time = 10, flags = ANIMATION_END_NOW)
deltimer(linked_gateway.timerid)
- linked_gateway.timerid = QDEL_IN(linked_gateway, 10)
+ linked_gateway.timerid = QDEL_IN_STOPPABLE(linked_gateway, 10)
return TRUE
return FALSE
@@ -279,8 +279,8 @@
/obj/effect/clockwork/spatial_gateway/stable/proc/start_shutdown()
deltimer(timerid)
deltimer(linked_gateway.timerid)
- timerid = QDEL_IN(src, 20)
- linked_gateway.timerid = QDEL_IN(linked_gateway, 20)
+ timerid = QDEL_IN_STOPPABLE(src, 20)
+ linked_gateway.timerid = QDEL_IN_STOPPABLE(linked_gateway, 20)
animate(src, alpha = 0, transform = matrix()*2, time = 20, flags = ANIMATION_END_NOW)
animate(linked_gateway, alpha = 0, transform = matrix()*2, time = 20, flags = ANIMATION_END_NOW)
src.visible_message("[src] begins to destabilise!")
diff --git a/code/modules/antagonists/clockcult/clock_structures/ark_of_the_clockwork_justicar.dm b/code/modules/antagonists/clockcult/clock_structures/ark_of_the_clockwork_justicar.dm
index 288285e5a595..f280ddecf2c4 100644
--- a/code/modules/antagonists/clockcult/clock_structures/ark_of_the_clockwork_justicar.dm
+++ b/code/modules/antagonists/clockcult/clock_structures/ark_of_the_clockwork_justicar.dm
@@ -40,6 +40,24 @@
if(!GLOB.ark_of_the_clockwork_justiciar)
GLOB.ark_of_the_clockwork_justiciar = src
+/obj/structure/destructible/clockwork/massive/celestial_gateway/Destroy()
+ STOP_PROCESSING(SSprocessing, src)
+ if(!purpose_fulfilled)
+ var/area/gate_area = get_area(src)
+ hierophant_message("An Ark of the Clockwork Justicar has fallen at [gate_area.map_name]!")
+ send_to_playing_players(sound(null, 0, channel = CHANNEL_JUSTICAR_ARK))
+ var/was_stranded = SSshuttle.emergency.mode == SHUTTLE_STRANDED
+ SSshuttle.clearHostileEnvironment(src)
+ if(!was_stranded && !purpose_fulfilled)
+ priority_announce("Massive energy anomaly no longer on short-range scanners, bluespace distortions still detected.","Central Command Higher Dimensional Affairs")
+ if(glow)
+ QDEL_NULL(glow)
+ if(countdown)
+ QDEL_NULL(countdown)
+ if(GLOB.ark_of_the_clockwork_justiciar == src)
+ GLOB.ark_of_the_clockwork_justiciar = null
+ . = ..()
+
/obj/structure/destructible/clockwork/massive/celestial_gateway/on_attack_hand(mob/user, act_intent, unarmed_attack_flags)
if(!active && is_servant_of_ratvar(user) && user.canUseTopic(src, !issilicon(user), NO_DEXTERY))
if(alert(user, "Are you sure you want to activate the ark? Once enabled, there will be no turning back.", "Enabling the ark", "Activate!", "Cancel") == "Activate!")
@@ -125,7 +143,7 @@
L.forceMove(pick(open_turfs))
glow = new(get_turf(src))
var/area/gate_area = get_area(src)
- hierophant_message("An Ark of the Clockwork Justicar has been created in [gate_area.map_name]!", FALSE, src)
+ hierophant_message("An Ark of the Clockwork Justicar has been created in [gate_area?.map_name]!", FALSE, src)
/obj/structure/destructible/clockwork/massive/celestial_gateway/proc/initiate_mass_recall()
recalling = TRUE
@@ -149,23 +167,7 @@
transform = matrix() * 2
animate(src, transform = matrix() * 0.5, time = 30, flags = ANIMATION_END_NOW)
-/obj/structure/destructible/clockwork/massive/celestial_gateway/Destroy()
- STOP_PROCESSING(SSprocessing, src)
- if(!purpose_fulfilled)
- var/area/gate_area = get_area(src)
- hierophant_message("An Ark of the Clockwork Justicar has fallen at [gate_area.map_name]!")
- send_to_playing_players(sound(null, 0, channel = CHANNEL_JUSTICAR_ARK))
- var/was_stranded = SSshuttle.emergency.mode == SHUTTLE_STRANDED
- SSshuttle.clearHostileEnvironment(src)
- if(!was_stranded && !purpose_fulfilled)
- priority_announce("Massive energy anomaly no longer on short-range scanners, bluespace distortions still detected.","Central Command Higher Dimensional Affairs")
- if(glow)
- qdel(glow)
- glow = null
- if(countdown)
- qdel(countdown)
- countdown = null
- . = ..()
+
/obj/structure/destructible/clockwork/massive/celestial_gateway/deconstruct(disassembled = TRUE)
if(!(flags_1 & NODECONSTRUCT_1))
diff --git a/code/modules/antagonists/clockcult/clock_structures/taunting_trail.dm b/code/modules/antagonists/clockcult/clock_structures/taunting_trail.dm
index 5305758b255b..853acdbe1999 100644
--- a/code/modules/antagonists/clockcult/clock_structures/taunting_trail.dm
+++ b/code/modules/antagonists/clockcult/clock_structures/taunting_trail.dm
@@ -16,7 +16,7 @@
/obj/structure/destructible/clockwork/taunting_trail/Initialize(mapload)
. = ..()
- timerid = QDEL_IN(src, 15)
+ timerid = QDEL_IN_STOPPABLE(src, 15)
var/obj/structure/destructible/clockwork/taunting_trail/Tt = locate(/obj/structure/destructible/clockwork/taunting_trail) in loc
if(Tt && Tt != src)
if(!step(src, pick(GLOB.alldirs)))
diff --git a/code/modules/antagonists/cult/blood_magic.dm b/code/modules/antagonists/cult/blood_magic.dm
index d4ff89084c5f..23763924727e 100644
--- a/code/modules/antagonists/cult/blood_magic.dm
+++ b/code/modules/antagonists/cult/blood_magic.dm
@@ -358,9 +358,10 @@
/obj/item/melee/blood_magic/Initialize(mapload, spell)
. = ..()
ADD_TRAIT(src, TRAIT_NODROP, CULT_TRAIT)
- source = spell
- uses = source.charges
- health_cost = source.health_cost
+ if(spell)
+ source = spell
+ uses = source.charges
+ health_cost = source.health_cost
/obj/item/melee/blood_magic/Destroy()
@@ -375,7 +376,7 @@
source.desc = source.base_desc
source.desc += " Has [uses] use\s remaining."
source.UpdateButtons()
- ..()
+ return ..()
/obj/item/melee/blood_magic/attack_self(mob/living/user)
afterattack(user, user, TRUE)
diff --git a/code/modules/antagonists/cult/cult_items.dm b/code/modules/antagonists/cult/cult_items.dm
index 878fe4a443f6..71eab3740c4e 100644
--- a/code/modules/antagonists/cult/cult_items.dm
+++ b/code/modules/antagonists/cult/cult_items.dm
@@ -137,6 +137,11 @@
jaunt = new(src)
linked_action = new(src)
+/obj/item/cult_bastard/Destroy()
+ QDEL_NULL(jaunt)
+ QDEL_NULL(linked_action)
+ . = ..()
+
/obj/item/cult_bastard/ComponentInitialize()
. = ..()
AddComponent(/datum/component/butchering, 50, 80)
@@ -740,7 +745,7 @@
/obj/item/cult_spear/Destroy()
if(spear_act)
qdel(spear_act)
- ..()
+ return ..()
/obj/item/cult_spear/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
var/turf/T = get_turf(hit_atom)
diff --git a/code/modules/antagonists/devil/imp/imp.dm b/code/modules/antagonists/devil/imp/imp.dm
index 4b2a41db71b7..bdbeb6f00f5e 100644
--- a/code/modules/antagonists/devil/imp/imp.dm
+++ b/code/modules/antagonists/devil/imp/imp.dm
@@ -45,7 +45,7 @@
of intentionally harming a fellow devil."
/mob/living/simple_animal/imp/Initialize(mapload)
- ..()
+ . = ..()
boost = world.time + 30
/mob/living/simple_animal/imp/BiologicalLife(delta_time, times_fired)
diff --git a/code/modules/antagonists/devil/true_devil/_true_devil.dm b/code/modules/antagonists/devil/true_devil/_true_devil.dm
index f013e5673732..96eb898a7432 100644
--- a/code/modules/antagonists/devil/true_devil/_true_devil.dm
+++ b/code/modules/antagonists/devil/true_devil/_true_devil.dm
@@ -29,7 +29,7 @@
create_bodyparts() //initialize bodyparts
create_internal_organs()
grant_all_languages()
- ..()
+ . = ..()
/mob/living/carbon/true_devil/create_internal_organs()
internal_organs += new /obj/item/organ/brain
diff --git a/code/modules/antagonists/eldritch_cult/eldritch_items.dm b/code/modules/antagonists/eldritch_cult/eldritch_items.dm
index 58e0193de583..de0ca6b71599 100644
--- a/code/modules/antagonists/eldritch_cult/eldritch_items.dm
+++ b/code/modules/antagonists/eldritch_cult/eldritch_items.dm
@@ -314,6 +314,10 @@
. = ..()
linked_action = new(src)
+/obj/item/melee/rune_knife/Destroy()
+ QDEL_NULL(linked_action)
+ . = ..()
+
/obj/item/melee/rune_knife/pickup(mob/user)
. = ..()
linked_action.Grant(user, src)
diff --git a/code/modules/antagonists/revenant/revenant.dm b/code/modules/antagonists/revenant/revenant.dm
index e81d7f236909..3d55ff931528 100644
--- a/code/modules/antagonists/revenant/revenant.dm
+++ b/code/modules/antagonists/revenant/revenant.dm
@@ -453,7 +453,7 @@
/obj/item/ectoplasm/revenant/Destroy()
if(!QDELETED(revenant))
qdel(revenant)
- ..()
+ return ..()
/proc/RevenantThrow(over, mob/user, obj/item/throwable)
var/mob/living/simple_animal/revenant/spooker = user
diff --git a/code/modules/antagonists/slaughter/slaughter.dm b/code/modules/antagonists/slaughter/slaughter.dm
index 1dba6f8b88cb..f4d33949219f 100644
--- a/code/modules/antagonists/slaughter/slaughter.dm
+++ b/code/modules/antagonists/slaughter/slaughter.dm
@@ -71,7 +71,7 @@
var/datum/action/cooldown/slam
/mob/living/simple_animal/slaughter/Initialize(mapload)
- ..()
+ . = ..()
var/obj/effect/proc_holder/spell/bloodcrawl/bloodspell = new
AddSpell(bloodspell)
slam = new /datum/action/cooldown/slam
diff --git a/code/modules/antagonists/swarmer/swarmer.dm b/code/modules/antagonists/swarmer/swarmer.dm
index 0eac4678d703..8af9563d145d 100644
--- a/code/modules/antagonists/swarmer/swarmer.dm
+++ b/code/modules/antagonists/swarmer/swarmer.dm
@@ -194,9 +194,11 @@
return FALSE
/obj/item/IntegrateAmount() //returns the amount of resources gained when eating this item
+ . = ..()
+ if(!custom_materials)
+ return
if(custom_materials[SSmaterials.GetMaterialRef(/datum/material/iron)] || custom_materials[SSmaterials.GetMaterialRef(/datum/material/glass)])
return TRUE
- return ..()
/obj/item/gun/swarmer_act()//Stops you from eating the entire armory
return FALSE
diff --git a/code/modules/assembly/flash.dm b/code/modules/assembly/flash.dm
index c76cb0c3f8e0..f2469b7087dd 100644
--- a/code/modules/assembly/flash.dm
+++ b/code/modules/assembly/flash.dm
@@ -234,6 +234,10 @@
var/obj/item/organ/cyberimp/arm/flash/I = null
var/active_light_strength = 7
+/obj/item/assembly/flash/armimplant/Destroy()
+ I = null
+ return ..()
+
/obj/item/assembly/flash/armimplant/burn_out()
if(I && I.owner)
to_chat(I.owner, "Your photon projector implant overheats and deactivates!")
diff --git a/code/modules/awaymissions/capture_the_flag.dm b/code/modules/awaymissions/capture_the_flag.dm
index 0e3aa60fd7e2..21bf1262fb08 100644
--- a/code/modules/awaymissions/capture_the_flag.dm
+++ b/code/modules/awaymissions/capture_the_flag.dm
@@ -173,7 +173,7 @@
/obj/machinery/capture_the_flag/Destroy()
GLOB.poi_list.Remove(src)
- ..()
+ return ..()
/obj/machinery/capture_the_flag/process(delta_time)
for(var/i in spawned_mobs)
@@ -642,7 +642,7 @@
invisibility = 0
/obj/effect/ctf/ammo/Initialize(mapload)
- ..()
+ . = ..()
QDEL_IN(src, AMMO_DROP_LIFETIME)
/obj/effect/ctf/ammo/Crossed(atom/movable/AM)
@@ -681,6 +681,11 @@
for(var/obj/machinery/capture_the_flag/CTF in GLOB.machines)
CTF.dead_barricades += src
+/obj/effect/ctf/dead_barricade/Destroy(force)
+ for(var/obj/machinery/capture_the_flag/CTF in GLOB.machines)
+ CTF.dead_barricades -= src
+ return ..()
+
/obj/effect/ctf/dead_barricade/proc/respawn()
if(!QDELETED(src))
new /obj/structure/barricade/security/ctf(get_turf(src))
diff --git a/code/modules/awaymissions/mission_code/Cabin.dm b/code/modules/awaymissions/mission_code/Cabin.dm
index 51c2c130c862..4cadb23e53ee 100644
--- a/code/modules/awaymissions/mission_code/Cabin.dm
+++ b/code/modules/awaymissions/mission_code/Cabin.dm
@@ -44,7 +44,7 @@
var/active = 1
/obj/structure/firepit/Initialize(mapload)
- ..()
+ . = ..()
toggleFirepit()
/obj/structure/firepit/interact(mob/living/user)
diff --git a/code/modules/buildmode/effects/line.dm b/code/modules/buildmode/effects/line.dm
index d21c0787fa34..dfcfd86475b8 100644
--- a/code/modules/buildmode/effects/line.dm
+++ b/code/modules/buildmode/effects/line.dm
@@ -3,6 +3,9 @@
var/client/cl
/obj/effect/buildmode_line/New(client/C, atom/atom_a, atom/atom_b, linename)
+ if(!C || !atom_a || !atom_b)
+ stack_trace("Buildmode effect created with odd inputs")
+ return
name = linename
loc = get_turf(atom_a)
I = image('icons/misc/mark.dmi', src, "line", 19.0)
diff --git a/code/modules/cargo/gondolapod.dm b/code/modules/cargo/gondolapod.dm
index c2736c83b832..82fb5f4ecf93 100644
--- a/code/modules/cargo/gondolapod.dm
+++ b/code/modules/cargo/gondolapod.dm
@@ -28,6 +28,9 @@
var/obj/structure/closet/supplypod/centcompod/linked_pod
/mob/living/simple_animal/pet/gondola/gondolapod/Initialize(mapload, pod)
+ if(!pod)
+ stack_trace("Gondola pod created with no pod")
+ return INITIALIZE_HINT_QDEL
linked_pod = pod
name = linked_pod.name
. = ..()
@@ -71,6 +74,6 @@
update_icon()
/mob/living/simple_animal/pet/gondola/gondolapod/death()
- qdel(linked_pod) //Will cause the open() proc for the linked supplypod to be called with the "broken" parameter set to true, meaning that it will dump its contents on death
+ QDEL_NULL(linked_pod) //Will cause the open() proc for the linked supplypod to be called with the "broken" parameter set to true, meaning that it will dump its contents on death
qdel(src)
..()
diff --git a/code/modules/cargo/supplypod.dm b/code/modules/cargo/supplypod.dm
index d495702fb51a..27e8a51c69ec 100644
--- a/code/modules/cargo/supplypod.dm
+++ b/code/modules/cargo/supplypod.dm
@@ -538,6 +538,9 @@
/obj/effect/pod_landingzone_effect/Initialize(mapload, obj/structure/closet/supplypod/pod)
. = ..()
+ if(!pod)
+ stack_trace("Pod landingzone effect created with no pod")
+ return INITIALIZE_HINT_QDEL
transform = matrix() * 1.5
animate(src, transform = matrix()*0.01, time = pod.delays[POD_TRANSIT]+pod.delays[POD_FALLING])
@@ -556,6 +559,9 @@
/obj/effect/pod_landingzone/Initialize(mapload, podParam, single_order = null, clientman)
. = ..()
+ if(!podParam)
+ stack_trace("Pod landingzone created with no pod")
+ return INITIALIZE_HINT_QDEL
if (ispath(podParam)) //We can pass either a path for a pod (as expressconsoles do), or a reference to an instantiated pod (as the centcom_podlauncher does)
podParam = new podParam() //If its just a path, instantiate it
pod = podParam
diff --git a/code/modules/clothing/gloves/mittens.dm b/code/modules/clothing/gloves/mittens.dm
index 2d00da6780c8..b5cd5e79c723 100644
--- a/code/modules/clothing/gloves/mittens.dm
+++ b/code/modules/clothing/gloves/mittens.dm
@@ -13,7 +13,7 @@
/obj/item/clothing/gloves/mittens/random
/obj/item/clothing/gloves/mittens/random/Initialize(mapload)
- ..()
+ . = ..()
var/colours = list("black", "yellow", "lightbrown", "brown", "orange", "red", "purple", "green", "blue", "kitten")
var/picked_c = pick(colours)
if(picked_c == "kitten")
diff --git a/code/modules/clothing/masks/gasmask.dm b/code/modules/clothing/masks/gasmask.dm
index 1c9b15b39889..316ce0e5f6c2 100644
--- a/code/modules/clothing/masks/gasmask.dm
+++ b/code/modules/clothing/masks/gasmask.dm
@@ -56,7 +56,7 @@
/obj/item/clothing/mask/gas/welding/up
/obj/item/clothing/mask/gas/welding/up/Initialize(mapload)
- ..()
+ . = ..()
visor_toggling()
diff --git a/code/modules/clothing/spacesuits/chronosuit.dm b/code/modules/clothing/spacesuits/chronosuit.dm
index f5c2c44f58f0..4ac0e9b89335 100644
--- a/code/modules/clothing/spacesuits/chronosuit.dm
+++ b/code/modules/clothing/spacesuits/chronosuit.dm
@@ -32,11 +32,16 @@
var/teleporting = 0
var/phase_timer_id
-/obj/item/clothing/suit/space/chronos/New()
- ..()
+/obj/item/clothing/suit/space/chronos/Initialize(mapload)
+ . = ..()
teleport_now.chronosuit = src
teleport_now.target = src
+/obj/item/clothing/suit/space/chronos/Destroy()
+ QDEL_NULL(teleport_now)
+ QDEL_NULL(camera)
+ . = ..()
+
/obj/item/clothing/suit/space/chronos/proc/new_camera(mob/user)
if(camera)
qdel(camera)
diff --git a/code/modules/clothing/spacesuits/hardsuit.dm b/code/modules/clothing/spacesuits/hardsuit.dm
index e6ed9c3d5115..4ca3effc1e41 100644
--- a/code/modules/clothing/spacesuits/hardsuit.dm
+++ b/code/modules/clothing/spacesuits/hardsuit.dm
@@ -313,6 +313,10 @@
if(istype(loc, /obj/item/clothing/suit/space/hardsuit/syndi))
linkedsuit = loc
+/obj/item/clothing/head/helmet/space/hardsuit/syndi/Destroy()
+ linkedsuit = null
+ return ..()
+
/obj/item/clothing/head/helmet/space/hardsuit/syndi/attack_self(mob/user) //Toggle Helmet
if(!isturf(user.loc))
to_chat(user, "You cannot toggle your helmet while in this [user.loc]!" )
@@ -526,6 +530,10 @@
. = ..()
bomb_radar = new /obj/machinery/doppler_array/integrated(src)
+/obj/item/clothing/head/helmet/space/hardsuit/rd/Destroy()
+ QDEL_NULL(bomb_radar)
+ return ..()
+
/obj/item/clothing/head/helmet/space/hardsuit/rd/equipped(mob/living/carbon/human/user, slot)
..()
if (slot == ITEM_SLOT_HEAD)
@@ -700,6 +708,10 @@
. = ..()
bomb_radar = new /obj/machinery/doppler_array/integrated(src)
+/obj/item/clothing/head/helmet/space/hardsuit/ancient/mason/Destroy()
+ QDEL_NULL(bomb_radar)
+ return ..()
+
/obj/item/clothing/head/helmet/space/hardsuit/ancient/mason/equipped(mob/living/carbon/human/user, slot)
..()
if (slot == ITEM_SLOT_HEAD)
@@ -965,7 +977,7 @@
var/energy_color = "#35FFF0"
/obj/item/clothing/suit/space/hardsuit/lavaknight/Initialize(mapload)
- ..()
+ . = ..()
light_color = energy_color
set_light(1)
update_icon()
diff --git a/code/modules/clothing/suits/toggles.dm b/code/modules/clothing/suits/toggles.dm
index 9fc51b82871c..1e36db17a742 100644
--- a/code/modules/clothing/suits/toggles.dm
+++ b/code/modules/clothing/suits/toggles.dm
@@ -153,8 +153,8 @@
/obj/item/clothing/suit/space/hardsuit/Destroy()
if(helmet)
helmet.suit = null
- qdel(helmet)
- qdel(jetpack)
+ QDEL_NULL(helmet)
+ QDEL_NULL(jetpack)
return ..()
/obj/item/clothing/head/helmet/space/hardsuit/Destroy()
diff --git a/code/modules/clothing/under/color.dm b/code/modules/clothing/under/color.dm
index 215be905aa6a..4c9dea45b7a1 100644
--- a/code/modules/clothing/under/color.dm
+++ b/code/modules/clothing/under/color.dm
@@ -12,8 +12,8 @@
icon_state = "random_jumpsuit"
/obj/item/clothing/under/color/random/Initialize(mapload)
- ..()
- var/obj/item/clothing/under/color/C = pick(subtypesof(/obj/item/clothing/under/color) - subtypesof(/obj/item/clothing/under/color/jumpskirt) - /obj/item/clothing/under/color/random - /obj/item/clothing/under/color/grey/glorf - /obj/item/clothing/under/color/black/ghost)
+ . = ..()
+ var/obj/item/clothing/under/color/C = pick(typesof(/obj/item/clothing/under/color) - subtypesof(/obj/item/clothing/under/color/jumpskirt) - /obj/item/clothing/under/color/random - /obj/item/clothing/under/color/grey/glorf - /obj/item/clothing/under/color/black/ghost)
if(ishuman(loc))
var/mob/living/carbon/human/H = loc
@@ -26,7 +26,7 @@
icon_state = "random_jumpsuit" //Skirt variant needed
/obj/item/clothing/under/color/jumpskirt/random/Initialize(mapload)
- ..()
+ . = ..()
var/obj/item/clothing/under/color/jumpskirt/C = pick(subtypesof(/obj/item/clothing/under/color/jumpskirt) - /obj/item/clothing/under/color/jumpskirt/random)
if(ishuman(loc))
var/mob/living/carbon/human/H = loc
diff --git a/code/modules/detectivework/detective_work.dm b/code/modules/detectivework/detective_work.dm
index 3b1b00fc3a49..81eaa8b70d42 100644
--- a/code/modules/detectivework/detective_work.dm
+++ b/code/modules/detectivework/detective_work.dm
@@ -67,7 +67,7 @@
//Set ignoregloves to add prints irrespective of the mob having gloves on.
/atom/proc/add_fingerprint(mob/living/M, ignoregloves = FALSE)
- if(!M || !M.key)
+ if(!istype(M))
return
add_hiddenprint(M)
diff --git a/code/modules/error_handler/error_handler.dm b/code/modules/error_handler/error_handler.dm
index 834f5051c262..379568ca9125 100644
--- a/code/modules/error_handler/error_handler.dm
+++ b/code/modules/error_handler/error_handler.dm
@@ -128,7 +128,7 @@ GLOBAL_VAR_INIT(total_runtimes_skipped, 0)
#ifdef UNIT_TESTS
if(GLOB.current_test)
//good day, sir
- GLOB.current_test.Fail("[main_line]\n[desclines.Join("\n")]")
+ GLOB.current_test.Fail("[main_line]\n[desclines.Join("\n")]", file = E.file, line = E.line)
#endif
diff --git a/code/modules/events/travelling_trader.dm b/code/modules/events/travelling_trader.dm
index ac9d103f5f4d..acc5324dc5d6 100644
--- a/code/modules/events/travelling_trader.dm
+++ b/code/modules/events/travelling_trader.dm
@@ -118,7 +118,7 @@
smoke.set_up(1, loc)
smoke.start()
visible_message("[src] disappears in a puff of smoke, leaving something on the ground!")
- ..()
+ return ..()
//travelling trader subtypes (the types that can actually spawn)
//so far there's: cook / botanist / bartender / animal hunter / artifact dealer / surgeon (6 types!)
@@ -144,7 +144,7 @@
requested_item = result
else
requested_item = /obj/item/reagent_containers/food/snacks/copypasta
- ..()
+ . = ..()
//botanist
/mob/living/carbon/human/dummy/travelling_trader/gardener
@@ -164,7 +164,7 @@
requested_item = pick(subtypesof(/obj/item/reagent_containers/food/snacks/grown) - list(/obj/item/reagent_containers/food/snacks/grown/shell,
/obj/item/reagent_containers/food/snacks/grown/shell/gatfruit,
/obj/item/reagent_containers/food/snacks/grown/cherry_bomb))
- ..()
+ . = ..()
//animal hunter
/mob/living/carbon/human/dummy/travelling_trader/animal_hunter
@@ -280,7 +280,7 @@
/mob/living/carbon/human/dummy/travelling_trader/artifact_dealer/Initialize(mapload)
possible_rewards += list(pick(subtypesof(/obj/item/clothing/head/collectable)) = 1) //this is slightly lower because it's absolutely useless
- ..()
+ . = ..()
/datum/outfit/artifact_dealer
name = "Artifact Dealer"
diff --git a/code/modules/events/wizard/greentext.dm b/code/modules/events/wizard/greentext.dm
index 577a24ae04ee..8071abf9db8c 100644
--- a/code/modules/events/wizard/greentext.dm
+++ b/code/modules/events/wizard/greentext.dm
@@ -39,6 +39,26 @@
roundend_callback = CALLBACK(src,PROC_REF(check_winner))
SSticker.OnRoundend(roundend_callback)
+/obj/item/greentext/Destroy(force)
+ if(!(resistance_flags & ON_FIRE) && !force)
+ return QDEL_HINT_LETMELIVE
+
+ SSticker.round_end_events -= roundend_callback
+ GLOB.poi_list.Remove(src)
+ roundend_callback = null
+ for(var/i in GLOB.player_list)
+ var/mob/M = i
+ var/message = "A dark temptation has passed from this world"
+ if(M in color_altered_mobs)
+ message += " and you're finally able to forgive yourself"
+ if(M.color == "#FF0000" || M.color == "#00FF00")
+ M.remove_atom_colour(ADMIN_COLOUR_PRIORITY)
+ message += "..."
+ // can't skip the mob check as it also does the decolouring
+ if(!quiet)
+ to_chat(M, message)
+ . = ..()
+
/obj/item/greentext/equipped(mob/living/user as mob)
to_chat(user, "So long as you leave this place with greentext in hand you know will be happy...")
var/list/other_objectives = user.mind.get_all_objectives()
@@ -80,24 +100,7 @@
last_holder.add_atom_colour("#FF0000", ADMIN_COLOUR_PRIORITY)
last_holder = new_holder //long live the king
-/obj/item/greentext/Destroy(force)
- if(!(resistance_flags & ON_FIRE) && !force)
- return QDEL_HINT_LETMELIVE
- SSticker.round_end_events -= roundend_callback
- GLOB.poi_list.Remove(src)
- for(var/i in GLOB.player_list)
- var/mob/M = i
- var/message = "A dark temptation has passed from this world"
- if(M in color_altered_mobs)
- message += " and you're finally able to forgive yourself"
- if(M.color == "#FF0000" || M.color == "#00FF00")
- M.remove_atom_colour(ADMIN_COLOUR_PRIORITY)
- message += "..."
- // can't skip the mob check as it also does the decolouring
- if(!quiet)
- to_chat(M, message)
- . = ..()
/obj/item/greentext/quiet
quiet = TRUE
diff --git a/code/modules/fields/infinite_void.dm b/code/modules/fields/infinite_void.dm
index 9caa75b24e65..b6f23861d18f 100644
--- a/code/modules/fields/infinite_void.dm
+++ b/code/modules/fields/infinite_void.dm
@@ -34,7 +34,8 @@
INVOKE_ASYNC(src, PROC_REF(domain_expansion))
/obj/effect/domain_expansion/Destroy()
- qdel(chronofield)
+ QDEL_NULL(chronofield)
+ target = null
return ..()
/obj/effect/domain_expansion/proc/domain_expansion()
diff --git a/code/modules/flufftext/Hallucination.dm b/code/modules/flufftext/Hallucination.dm
index d7d53ba69111..8f95baa40c2e 100644
--- a/code/modules/flufftext/Hallucination.dm
+++ b/code/modules/flufftext/Hallucination.dm
@@ -106,6 +106,9 @@ GLOBAL_LIST_INIT(hallucination_list, list(
/obj/effect/hallucination/simple/Initialize(mapload, var/mob/living/carbon/T)
. = ..()
+ if(!T)
+ stack_trace("A hallucination was created with no target")
+ return INITIALIZE_HINT_QDEL
target = T
current_image = GetImage()
if(target.client)
diff --git a/code/modules/food_and_drinks/food/snacks/meat.dm b/code/modules/food_and_drinks/food/snacks/meat.dm
index 20bd5880ff7f..adeef7f91d13 100644
--- a/code/modules/food_and_drinks/food/snacks/meat.dm
+++ b/code/modules/food_and_drinks/food/snacks/meat.dm
@@ -278,7 +278,7 @@
visible_message("[src] finishes cooking!")
new /obj/item/reagent_containers/food/snacks/meat/steak/goliath(loc)
qdel(src)
-
+
/obj/item/reagent_containers/food/snacks/meat/slab/dragon
name = "ash drake meat"
desc = "Meat from an ash drake. It's probably not a good idea to eat this raw."
@@ -408,7 +408,7 @@
trash = null
tastes = list("meat" = 1, "rock" = 1)
foodtype = MEAT
-
+
/obj/item/reagent_containers/food/snacks/meat/steak/dragon
name = "dragon steak"
desc = "Spicy."
diff --git a/code/modules/holiday/halloween/halloween.dm b/code/modules/holiday/halloween/halloween.dm
index 59a7fcce0c03..28709d0a0587 100644
--- a/code/modules/holiday/halloween/halloween.dm
+++ b/code/modules/holiday/halloween/halloween.dm
@@ -43,7 +43,7 @@
var/mob/trapped_mob
/obj/structure/closet/Initialize(mapload)
- ..()
+ . = ..()
if(prob(30))
set_spooky_trap()
diff --git a/code/modules/instruments/songs/_song.dm b/code/modules/instruments/songs/_song.dm
index a0d96658e62f..085b5e06c10e 100644
--- a/code/modules/instruments/songs/_song.dm
+++ b/code/modules/instruments/songs/_song.dm
@@ -146,6 +146,8 @@
stop_playing()
SSinstruments.on_song_del(src)
lines = null
+ if(using_instrument)
+ using_instrument.songs_using -= src
using_instrument = null
allowed_instrument_ids = null
parent = null
diff --git a/code/modules/integrated_electronics/subtypes/input.dm b/code/modules/integrated_electronics/subtypes/input.dm
index ad8ef4ab2157..77ea2ae2f7ef 100644
--- a/code/modules/integrated_electronics/subtypes/input.dm
+++ b/code/modules/integrated_electronics/subtypes/input.dm
@@ -654,18 +654,19 @@
/obj/item/integrated_circuit/input/signaler/Initialize(mapload)
. = ..()
- spawn(40)
- set_frequency(frequency)
- // Set the pins so when someone sees them, they won't show as null
- set_pin_data(IC_INPUT, 1, frequency)
- set_pin_data(IC_INPUT, 2, code)
+ addtimer(CALLBACK(src, PROC_REF(init_frequency)), 4 SECONDS)
/obj/item/integrated_circuit/input/signaler/Destroy()
SSradio.remove_object(src,frequency)
-
frequency = 0
return ..()
+/obj/item/integrated_circuit/input/signaler/proc/init_frequency()
+ set_frequency(frequency)
+ // Set the pins so when someone sees them, they won't show as null
+ set_pin_data(IC_INPUT, 1, frequency)
+ set_pin_data(IC_INPUT, 2, code)
+
/obj/item/integrated_circuit/input/signaler/on_data_written()
var/new_freq = get_pin_data(IC_INPUT, 1)
var/new_code = get_pin_data(IC_INPUT, 2)
diff --git a/code/modules/integrated_electronics/subtypes/smart.dm b/code/modules/integrated_electronics/subtypes/smart.dm
index 0f97ff8e087c..249379c3324a 100644
--- a/code/modules/integrated_electronics/subtypes/smart.dm
+++ b/code/modules/integrated_electronics/subtypes/smart.dm
@@ -177,7 +177,7 @@
/obj/item/integrated_circuit/input/mmi_tank/Destroy()
RemoveBrain()
- ..()
+ return ..()
/obj/item/integrated_circuit/input/mmi_tank/relaymove(var/n,var/dir)
set_pin_data(IC_OUTPUT, 2, dir)
@@ -320,7 +320,7 @@
/obj/item/integrated_circuit/input/pAI_connector/Destroy()
RemovepAI()
- ..()
+ return ..()
/obj/item/integrated_circuit/input/pAI_connector/proc/RemovepAI()
if(installed_pai)
diff --git a/code/modules/library/random_books.dm b/code/modules/library/random_books.dm
index be5e9ea6b516..137d1d7b243d 100644
--- a/code/modules/library/random_books.dm
+++ b/code/modules/library/random_books.dm
@@ -2,7 +2,7 @@
icon_state = "random_book"
/obj/item/book/manual/random/Initialize(mapload)
- ..()
+ . = ..()
var/static/banned_books = list(/obj/item/book/manual/random, /obj/item/book/manual/nuclear, /obj/item/book/manual/wiki)
var/newtype = pick(subtypesof(/obj/item/book/manual) - banned_books)
new newtype(loc)
diff --git a/code/modules/mining/equipment/kinetic_crusher.dm b/code/modules/mining/equipment/kinetic_crusher.dm
index 6da752f17dcb..6ae450a74a36 100644
--- a/code/modules/mining/equipment/kinetic_crusher.dm
+++ b/code/modules/mining/equipment/kinetic_crusher.dm
@@ -270,6 +270,11 @@
active_style = new /datum/gauntlet_style/brawler
active_style.on_apply(src)
+/obj/item/kinetic_crusher/glaive/gauntlets/Destroy()
+ QDEL_NULL(active_style)
+ current_target = null
+ return ..()
+
/obj/item/kinetic_crusher/glaive/gauntlets/examine(mob/living/user)
. = ..()
. += "According to a very small display, the currently loaded style is \"[active_style.name]\"."
diff --git a/code/modules/mining/fulton.dm b/code/modules/mining/fulton.dm
index 13744aa100dd..58949b46d5d3 100644
--- a/code/modules/mining/fulton.dm
+++ b/code/modules/mining/fulton.dm
@@ -168,7 +168,7 @@ GLOBAL_LIST_EMPTY(total_extraction_beacons)
/obj/structure/extraction_point/Destroy()
GLOB.total_extraction_beacons -= src
- ..()
+ return ..()
/obj/effect/extraction_holder
name = "extraction holder"
diff --git a/code/modules/mining/lavaland/ash_tree.dm b/code/modules/mining/lavaland/ash_tree.dm
index 0483aa0c30e2..e6cd52fa9e80 100644
--- a/code/modules/mining/lavaland/ash_tree.dm
+++ b/code/modules/mining/lavaland/ash_tree.dm
@@ -25,7 +25,7 @@
var/sap_amount
/obj/structure/flora/ashtree/Initialize(mapload)
- ..()
+ . = ..()
if(prob(50))
sap = TRUE
icon_state = sap_icon_state
diff --git a/code/modules/mining/lavaland/necropolis_chests.dm b/code/modules/mining/lavaland/necropolis_chests.dm
index efc00159ea50..6a7999b8b92d 100644
--- a/code/modules/mining/lavaland/necropolis_chests.dm
+++ b/code/modules/mining/lavaland/necropolis_chests.dm
@@ -434,6 +434,12 @@
var/obj/item/warp_cube/linked
var/teleporting = FALSE
+/obj/item/warp_cube/Destroy()
+ if(!QDELETED(linked))
+ linked.linked = null
+ QDEL_NULL(linked)
+ return ..()
+
/obj/item/warp_cube/attack_self(mob/user)
if(!linked)
to_chat(user, "[src] fizzles uselessly.")
@@ -480,6 +486,12 @@
linked = blue
blue.linked = src
+/obj/item/warp_cube/red/Destroy()
+ if(!QDELETED(linked))
+ linked.linked = null
+ QDEL_NULL(linked)
+ return ..()
+
/obj/effect/warp_cube
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
anchored = TRUE
diff --git a/code/modules/mob/living/blood.dm b/code/modules/mob/living/blood.dm
index 70307520bca7..c1824ecbc29a 100644
--- a/code/modules/mob/living/blood.dm
+++ b/code/modules/mob/living/blood.dm
@@ -346,6 +346,8 @@
var/obj/effect/decal/cleanable/blood/B = locate() in T
if(!B)
B = new /obj/effect/decal/cleanable/blood/splatter(T, get_static_viruses())
+ if(QDELETED(B)) //Give it up
+ return
if(B.bloodiness < MAX_SHOE_BLOODINESS) //add more blood, up to a limit
B.bloodiness += BLOOD_AMOUNT_PER_DECAL
B.transfer_mob_blood_dna(src) //give blood info to the blood decal.
diff --git a/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm b/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm
index 71b1d09fadc9..a63aa11962a7 100644
--- a/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm
+++ b/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm
@@ -17,8 +17,8 @@
var/drooling = 0 //For Neruotoxic spit overlays
bodyparts = list(/obj/item/bodypart/chest/alien, /obj/item/bodypart/head/alien, /obj/item/bodypart/l_arm/alien,
/obj/item/bodypart/r_arm/alien, /obj/item/bodypart/r_leg/alien, /obj/item/bodypart/l_leg/alien)
-
can_ventcrawl = TRUE
+ var/obj/effect/proc_holder/alien/regurgitate/regurg
GLOBAL_LIST_INIT(strippable_alien_humanoid_items, create_strippable_list(list(
/datum/strippable_item/hand/left,
@@ -29,7 +29,13 @@ GLOBAL_LIST_INIT(strippable_alien_humanoid_items, create_strippable_list(list(
//This is fine right now, if we're adding organ specific damage this needs to be updated
/mob/living/carbon/alien/humanoid/Initialize(mapload)
- AddAbility(new/obj/effect/proc_holder/alien/regurgitate(null))
+ regurg = new(null)
+ AddAbility(regurg)
+ . = ..()
+
+/mob/living/carbon/alien/humanoid/Destroy()
+ RemoveAbility(regurg)
+ QDEL_NULL(regurg)
. = ..()
/mob/living/carbon/alien/humanoid/ComponentInitialize()
diff --git a/code/modules/mob/living/carbon/alien/humanoid/queen.dm b/code/modules/mob/living/carbon/alien/humanoid/queen.dm
index a5ef90f28336..d4e60465dabd 100644
--- a/code/modules/mob/living/carbon/alien/humanoid/queen.dm
+++ b/code/modules/mob/living/carbon/alien/humanoid/queen.dm
@@ -29,6 +29,7 @@
health = 400
icon_state = "alienq"
var/datum/action/small_sprite/smallsprite = new/datum/action/small_sprite/queen()
+ var/obj/effect/proc_holder/alien/royal/queen/promote/promote
/mob/living/carbon/alien/humanoid/royal/queen/Initialize(mapload)
//there should only be one queen
@@ -44,10 +45,16 @@
real_name = src.name
AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/repulse/xeno(src))
- AddAbility(new/obj/effect/proc_holder/alien/royal/queen/promote())
+ promote = new(null)
+ AddAbility(promote)
smallsprite.Grant(src)
return ..()
+/mob/living/carbon/alien/humanoid/royal/queen/Destroy()
+ RemoveAbility(promote)
+ QDEL_NULL(promote)
+ return ..()
+
/mob/living/carbon/alien/humanoid/royal/queen/create_internal_organs()
internal_organs += new /obj/item/organ/alien/plasmavessel/large/queen
internal_organs += new /obj/item/organ/alien/resinspinner
diff --git a/code/modules/mob/living/carbon/alien/organs.dm b/code/modules/mob/living/carbon/alien/organs.dm
index f96c5f952b60..7850ec130110 100644
--- a/code/modules/mob/living/carbon/alien/organs.dm
+++ b/code/modules/mob/living/carbon/alien/organs.dm
@@ -12,6 +12,9 @@
alien_powers += new A(src)
/obj/item/organ/alien/Destroy()
+ if(owner)
+ Remove(TRUE)
+ owner = null
QDEL_LIST(alien_powers)
return ..()
diff --git a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
index 550789a0666f..4bb2d978ca0b 100644
--- a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
@@ -446,6 +446,14 @@
var/datum/action/innate/use_extract/major/extract_major
var/extract_cooldown = 0
+/datum/species/jelly/luminescent/Destroy(force)
+ current_extract = null
+ QDEL_NULL(glow)
+ QDEL_NULL(extract_major)
+ QDEL_NULL(integrate_extract)
+ QDEL_NULL(extract_minor)
+ return ..()
+
/datum/species/jelly/luminescent/on_species_loss(mob/living/carbon/C)
..()
if(current_extract)
@@ -620,6 +628,14 @@
if(link_minds)
link_minds.Remove(C)
+//Species datums don't normally implement destroy, but JELLIES SUCK ASS OUT OF A STEEL STRAW ~LemonInTheDark, Tsurupeta
+/datum/species/jelly/stargazer/Destroy()
+ QDEL_NULL(project_thought)
+ QDEL_NULL(link_minds)
+ QDEL_LIST(linked_actions)
+ slimelink_owner = null
+ return ..()
+
/datum/species/jelly/stargazer/spec_death(gibbed, mob/living/carbon/human/H)
..()
for(var/M in linked_mobs)
@@ -672,6 +688,10 @@
..()
species = _species
+/datum/action/innate/linked_speech/Destroy()
+ species = null
+ return ..()
+
/datum/action/innate/linked_speech/Activate()
var/mob/living/carbon/human/H = owner
if(!species || !(H in species.linked_mobs))
@@ -749,6 +769,10 @@
..()
src.species = species
+/datum/action/innate/linked_speech/Destroy()
+ species = null
+ return ..()
+
/datum/action/innate/link_minds/Activate()
var/mob/living/carbon/human/H = owner
if(!is_species(H, /datum/species/jelly/stargazer))
diff --git a/code/modules/mob/living/carbon/monkey/monkey.dm b/code/modules/mob/living/carbon/monkey/monkey.dm
index 91e62f6ce107..3014e75347ba 100644
--- a/code/modules/mob/living/carbon/monkey/monkey.dm
+++ b/code/modules/mob/living/carbon/monkey/monkey.dm
@@ -183,6 +183,9 @@ GLOBAL_LIST_INIT(strippable_monkey_items, create_strippable_list(list(
/mob/living/carbon/monkey/angry/Initialize(mapload)
. = ..()
if(prob(10))
- var/obj/item/clothing/head/helmet/justice/escape/helmet = new(src)
- equip_to_slot_or_del(helmet,ITEM_SLOT_HEAD)
- INVOKE_ASYNC(helmet, TYPE_PROC_REF(/obj/item, attack_self), src) // todo encapsulate toggle
+ INVOKE_ASYNC(src, PROC_REF(give_ape_escape_helmet))
+
+/mob/living/carbon/monkey/angry/proc/give_ape_escape_helmet()
+ var/obj/item/clothing/head/helmet/justice/escape/helmet = new(src)
+ equip_to_slot_or_del(helmet,ITEM_SLOT_HEAD)
+ helmet.attack_self(src) // todo encapsulate toggle
diff --git a/code/modules/mob/living/silicon/ai/_preferences.dm b/code/modules/mob/living/silicon/ai/_preferences.dm
new file mode 100644
index 000000000000..1e7ada4dbffc
--- /dev/null
+++ b/code/modules/mob/living/silicon/ai/_preferences.dm
@@ -0,0 +1,19 @@
+// A mapping between AI_EMOTION_* string constants, which also double as user readable descriptions, and the name of the iconfile. (used for /obj/machinery/status_display/ai )
+GLOBAL_LIST_INIT(ai_status_display_emotes, list(
+ AI_EMOTION_AWESOME = "ai_awesome",
+ AI_EMOTION_BLANK = AI_DISPLAY_DONT_GLOW,
+ AI_EMOTION_BLUE_GLOW = "ai_sal",
+ AI_EMOTION_BSOD = "ai_bsod",
+ AI_EMOTION_CONFUSED = "ai_confused",
+ AI_EMOTION_DORFY = "ai_urist",
+ AI_EMOTION_FACEPALM = "ai_facepalm",
+ AI_EMOTION_FRIEND_COMPUTER = "ai_friend",
+ AI_EMOTION_HAPPY = "ai_happy",
+ AI_EMOTION_NEUTRAL = "ai_neutral",
+ AI_EMOTION_PROBLEMS = "ai_trollface",
+ AI_EMOTION_RED_GLOW = "ai_hal",
+ AI_EMOTION_SAD = "ai_sad",
+ AI_EMOTION_THINKING = "ai_thinking",
+ AI_EMOTION_UNSURE = "ai_unsure",
+ AI_EMOTION_VERY_HAPPY = "ai_veryhappy",
+))
diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm
index 26431ece5f29..bed6a3663a5c 100644
--- a/code/modules/mob/living/silicon/pai/pai.dm
+++ b/code/modules/mob/living/silicon/pai/pai.dm
@@ -118,10 +118,9 @@
//PDA
pda = new(src)
- spawn(5)
- pda.ownjob = "pAI Messenger"
- pda.owner = text("[]", src)
- pda.name = pda.owner + " (" + pda.ownjob + ")"
+ pda.ownjob = "pAI Messenger"
+ pda.owner = text("[]", src)
+ pda.name = pda.owner + " (" + pda.ownjob + ")"
possible_chassis = typelist(NAMEOF(src, possible_chassis), list("cat" = TRUE, "mouse" = TRUE, "monkey" = TRUE, "corgi" = FALSE,
"fox" = FALSE, "repairbot" = TRUE, "rabbit" = TRUE, "borgi" = FALSE ,
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index 2def55c3eeb1..b62a83495c26 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -121,17 +121,19 @@
if(connected_ai)
set_connected_ai(null)
if(shell) //??? why would you give an ai radio keys?
- GLOB.available_ai_shells -= src
+ revert_shell()
else
if(T && istype(radio) && istype(radio.keyslot))
radio.keyslot.forceMove(T)
radio.keyslot = null
+ QDEL_LIST(upgrades)
QDEL_NULL(wires)
QDEL_NULL(module)
QDEL_NULL(eye_lights)
QDEL_NULL(inv1)
QDEL_NULL(inv2)
QDEL_NULL(inv3)
+ QDEL_NULL(aiPDA)
cell = null
return ..()
@@ -574,7 +576,7 @@
/mob/living/silicon/robot/proc/SetLockdown(state = TRUE)
// They stay locked down if their wire is cut.
- if(wires.is_cut(WIRE_LOCKDOWN))
+ if(wires?.is_cut(WIRE_LOCKDOWN))
state = TRUE
if(state)
throw_alert("locked", /atom/movable/screen/alert/locked)
@@ -648,7 +650,7 @@
// set_light_color(COLOR_RED) //This should only matter for doomsday borgs, as any other time the lamp will be off and the color not seen
// set_light_range(1) //Again, like above, this only takes effect when the light is forced on by doomsday mode.
lamp_enabled = FALSE
- lampButton.update_icon()
+ lampButton?.update_icon()
update_icons()
return
set_light(lamp_intensity, l_color = (lamp_doom? COLOR_RED : lamp_color))
@@ -656,7 +658,7 @@
// set_light_color(lamp_doom? COLOR_RED : lamp_color) //Red for doomsday killborgs, borg's choice otherwise
// set_light_on(TRUE)
lamp_enabled = TRUE
- lampButton.update_icon()
+ lampButton?.update_icon()
update_icons()
/mob/living/silicon/robot/proc/deconstruct()
@@ -1113,6 +1115,7 @@
for(var/obj/item/borg/upgrade/ai/boris in src)
//A player forced reset of a borg would drop the module before this is called, so this is for catching edge cases
qdel(boris)
+ upgrades -= boris
shell = FALSE
GLOB.available_ai_shells -= src
name = "Unformatted Cyborg-[ident]"
diff --git a/code/modules/mob/living/simple_animal/bot/ed209bot.dm b/code/modules/mob/living/simple_animal/bot/ed209bot.dm
index 4074bb471a75..cf89a80c2a68 100644
--- a/code/modules/mob/living/simple_animal/bot/ed209bot.dm
+++ b/code/modules/mob/living/simple_animal/bot/ed209bot.dm
@@ -53,21 +53,21 @@
lasercolor = created_lasercolor
icon_state = "[lasercolor]ed209[on]"
set_weapon() //giving it the right projectile and firing sound.
- spawn(3)
- var/datum/job/detective/J = new/datum/job/detective
- access_card.access += J.get_access()
- prev_access = access_card.access
- if(lasercolor)
- shot_delay = 6//Longer shot delay because JESUS CHRIST
- check_records = 0//Don't actively target people set to arrest
- arrest_type = 1//Don't even try to cuff
- bot_core.req_access = list(ACCESS_MAINT_TUNNELS, ACCESS_THEATRE)
- arrest_type = 1
- if((lasercolor == "b") && (name == "\improper ED-209 Security Robot"))//Picks a name if there isn't already a custome one
- name = pick("BLUE BALLER","SANIC","BLUE KILLDEATH MURDERBOT")
- if((lasercolor == "r") && (name == "\improper ED-209 Security Robot"))
- name = pick("RED RAMPAGE","RED ROVER","RED KILLDEATH MURDERBOT")
+ var/datum/job/detective/J = new /datum/job/detective
+ access_card.access += J.get_access()
+ prev_access = access_card.access
+
+ if(lasercolor)
+ shot_delay = 6//Longer shot delay because JESUS CHRIST
+ check_records = 0//Don't actively target people set to arrest
+ arrest_type = 1//Don't even try to cuff
+ bot_core.req_access = list(ACCESS_MAINT_TUNNELS, ACCESS_THEATRE)
+ arrest_type = 1
+ if((lasercolor == "b") && (name == "\improper ED-209 Security Robot"))//Picks a name if there isn't already a custome one
+ name = pick("BLUE BALLER","SANIC","BLUE KILLDEATH MURDERBOT")
+ if((lasercolor == "r") && (name == "\improper ED-209 Security Robot"))
+ name = pick("RED RAMPAGE","RED ROVER","RED KILLDEATH MURDERBOT")
//SECHUD
var/datum/atom_hud/secsensor = GLOB.huds[DATA_HUD_SECURITY_ADVANCED]
diff --git a/code/modules/mob/living/simple_animal/bot/firebot.dm b/code/modules/mob/living/simple_animal/bot/firebot.dm
index 0b97a553b2fe..b681fa895f87 100644
--- a/code/modules/mob/living/simple_animal/bot/firebot.dm
+++ b/code/modules/mob/living/simple_animal/bot/firebot.dm
@@ -45,9 +45,12 @@
var/datum/job/engineer/J = new/datum/job/engineer
access_card.access += J.get_access()
prev_access = access_card.access
-
create_extinguisher()
+/mob/living/simple_animal/bot/firebot/Destroy()
+ QDEL_NULL(internal_ext)
+ return ..()
+
/mob/living/simple_animal/bot/firebot/bot_reset()
create_extinguisher()
diff --git a/code/modules/mob/living/simple_animal/bot/mulebot.dm b/code/modules/mob/living/simple_animal/bot/mulebot.dm
index 68721fa0d5e0..cde12d97e449 100644
--- a/code/modules/mob/living/simple_animal/bot/mulebot.dm
+++ b/code/modules/mob/living/simple_animal/bot/mulebot.dm
@@ -68,8 +68,8 @@
/mob/living/simple_animal/bot/mulebot/Destroy()
unload(0)
- qdel(wires)
- wires = null
+ QDEL_NULL(wires)
+ QDEL_NULL(cell)
return ..()
/mob/living/simple_animal/bot/mulebot/proc/set_id(new_id)
diff --git a/code/modules/mob/living/simple_animal/friendly/farm_animals.dm b/code/modules/mob/living/simple_animal/friendly/farm_animals.dm
index d53d596505de..949e63763acf 100644
--- a/code/modules/mob/living/simple_animal/friendly/farm_animals.dm
+++ b/code/modules/mob/living/simple_animal/friendly/farm_animals.dm
@@ -202,7 +202,7 @@
/mob/living/simple_animal/cow/random/Initialize(mapload)
milk_reagent = get_random_reagent_id() //this has a blacklist so don't worry about romerol cows, etc
- ..()
+ . = ..()
//Wisdom cow, speaks and bestows great wisdoms
/mob/living/simple_animal/cow/wisdom
diff --git a/code/modules/mob/living/simple_animal/guardian/types/support.dm b/code/modules/mob/living/simple_animal/guardian/types/support.dm
index 78225a458c73..5243905b66ff 100644
--- a/code/modules/mob/living/simple_animal/guardian/types/support.dm
+++ b/code/modules/mob/living/simple_animal/guardian/types/support.dm
@@ -98,7 +98,7 @@
/obj/structure/receiving_pad/New(loc, mob/living/simple_animal/hostile/guardian/healer/G)
. = ..()
- if(G.guardiancolor)
+ if(G?.guardiancolor)
add_atom_colour(G.guardiancolor, FIXED_COLOUR_PRIORITY)
/obj/structure/receiving_pad/proc/disappear()
diff --git a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm
index 37f0ee5106c3..6c25564faa53 100644
--- a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm
+++ b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm
@@ -396,10 +396,6 @@
action_icon_state = "wrap_0"
action_background_icon_state = "bg_alien"
-/obj/effect/proc_holder/wrap/Initialize(mapload)
- . = ..()
- action = new(src)
-
/obj/effect/proc_holder/wrap/update_icon()
action.button_icon_state = "wrap_[active]"
action.UpdateButtons()
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm
index 8b657fc72943..e931e615c063 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm
@@ -62,6 +62,10 @@ Difficulty: Medium
internal = new/obj/item/gps/internal/miner(src)
miner_saw = new(src)
+/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/Destroy()
+ QDEL_NULL(miner_saw)
+ return ..()
+
/datum/action/innate/megafauna_attack/dash
name = "Dash To Target"
icon_icon = 'icons/mob/actions/actions_items.dmi'
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm
index 288da357c3e5..bfa15c032f77 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm
@@ -457,7 +457,7 @@ Difficulty: Hard
deathsound = 'sound/effects/splat.ogg'
/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/Initialize(mapload)
- ..()
+ . = ..()
toggle_ai(AI_OFF)
/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/charge(atom/chargeat = target, delay = 3, chargepast = 2)
@@ -466,7 +466,7 @@ Difficulty: Hard
/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/Destroy()
new /obj/effect/decal/cleanable/blood(get_turf(src))
- . = ..()
+ return ..()
/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/CanAllowThrough(atom/movable/mover, turf/target)
. = ..()
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
index 6c7a24dc0619..f1816485741a 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
@@ -252,6 +252,8 @@ Difficulty: Very Hard
var/list/stored_items = list()
var/list/blacklist = list()
+GLOBAL_VAR(blackbox_smartfridge)
+
/obj/machinery/smartfridge/black_box/ComponentInitialize()
. = ..()
AddElement(/datum/element/update_icon_blocker)
@@ -266,11 +268,10 @@ Difficulty: Very Hard
/obj/machinery/smartfridge/black_box/Initialize(mapload)
. = ..()
- var/static/obj/machinery/smartfridge/black_box/current
- if(current && current != src)
+ if(GLOB.blackbox_smartfridge && GLOB.blackbox_smartfridge != src)
qdel(src, force=TRUE)
return
- current = src
+ GLOB.blackbox_smartfridge = src
ReadMemory()
/obj/machinery/smartfridge/black_box/process()
@@ -317,6 +318,8 @@ Difficulty: Very Hard
if(force)
for(var/thing in src)
qdel(thing)
+ if(GLOB.blackbox_smartfridge == src)
+ GLOB.blackbox_smartfridge = null
return ..()
else
return QDEL_HINT_LETMELIVE
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/curse_blob.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/curse_blob.dm
index 04b003b315be..dd38a80f12bf 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/curse_blob.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/curse_blob.dm
@@ -30,7 +30,7 @@
/mob/living/simple_animal/hostile/asteroid/curseblob/Initialize(mapload)
. = ..()
- timerid = QDEL_IN(src, 600)
+ timerid = QDEL_IN_STOPPABLE(src, 600)
playsound(src, 'sound/effects/curse1.ogg', 100, 1, -1)
/mob/living/simple_animal/hostile/asteroid/curseblob/Destroy()
@@ -57,7 +57,8 @@
/mob/living/simple_animal/hostile/asteroid/curseblob/proc/check_for_target()
if(QDELETED(set_target) || set_target.stat != CONSCIOUS || z != set_target.z)
- qdel(src)
+ if(!QDELETED(src))
+ qdel(src)
return TRUE
/mob/living/simple_animal/hostile/asteroid/curseblob/GiveTarget(new_target)
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm
index a72a002da002..47b1f39ba3c7 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm
@@ -204,7 +204,7 @@
var/mob/living/simple_animal/hostile/asteroid/elite/herald/my_master = null
/mob/living/simple_animal/hostile/asteroid/elite/herald/mirror/Initialize(mapload)
- ..()
+ . = ..()
toggle_ai(AI_OFF)
/mob/living/simple_animal/hostile/asteroid/elite/herald/mirror/Destroy()
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/goliath.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/goliath.dm
index bc339065da7f..d29de0753243 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/goliath.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/goliath.dm
@@ -211,4 +211,4 @@
/obj/effect/temp_visual/goliath_tentacle/proc/retract()
icon_state = "Goliath_tentacle_retract"
deltimer(timerid)
- timerid = QDEL_IN(src, 7)
+ timerid = QDEL_IN_STOPPABLE(src, 7)
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/gutlunch.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/gutlunch.dm
index 5ed68e6f4024..35664018e2a9 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/gutlunch.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/gutlunch.dm
@@ -130,11 +130,6 @@
/obj/item/udder/gutlunch
name = "nutrient sac"
-/obj/item/udder/gutlunch/Initialize(mapload)
- . = ..()
- reagents = new(50)
- reagents.my_atom = src
-
/obj/item/udder/gutlunch/generateMilk()
if(prob(60))
reagents.add_reagent(/datum/reagent/consumable/cream, rand(2, 5))
diff --git a/code/modules/mob/living/simple_animal/hostile/wizard.dm b/code/modules/mob/living/simple_animal/hostile/wizard.dm
index d871a739e5d7..c2b7bad5b6d1 100644
--- a/code/modules/mob/living/simple_animal/hostile/wizard.dm
+++ b/code/modules/mob/living/simple_animal/hostile/wizard.dm
@@ -60,6 +60,12 @@
blink.outer_tele_radius = 3
AddSpell(blink)
+/mob/living/simple_animal/hostile/wizard/Destroy()
+ QDEL_NULL(fireball)
+ QDEL_NULL(mm)
+ QDEL_NULL(blink)
+ return ..()
+
/mob/living/simple_animal/hostile/wizard/handle_automated_action()
. = ..()
INVOKE_ASYNC(src, PROC_REF(AutomatedCast))
diff --git a/code/modules/modular_computers/computers/item/computer.dm b/code/modules/modular_computers/computers/item/computer.dm
index 267691be39a6..d2e20decc31f 100644
--- a/code/modules/modular_computers/computers/item/computer.dm
+++ b/code/modules/modular_computers/computers/item/computer.dm
@@ -71,7 +71,6 @@
for(var/H in all_components)
var/obj/item/computer_hardware/CH = all_components[H]
if(CH.holder == src)
- CH.on_remove(src)
CH.holder = null
all_components.Remove(CH.device_type)
qdel(CH)
diff --git a/code/modules/modular_computers/file_system/programs/signaler.dm b/code/modules/modular_computers/file_system/programs/signaler.dm
index acbbff6c4ff5..52e14d63e0f3 100644
--- a/code/modules/modular_computers/file_system/programs/signaler.dm
+++ b/code/modules/modular_computers/file_system/programs/signaler.dm
@@ -19,6 +19,11 @@
set_frequency(signal_frequency)
return ..()
+/datum/computer_file/program/signaler/Destroy()
+ SSradio.remove_object(src, signal_frequency)
+ radio_connection = null
+ return ..()
+
/datum/computer_file/program/signaler/ui_data(mob/user)
var/list/data = get_header_data()
data["frequency"] = signal_frequency
diff --git a/code/modules/modular_computers/hardware/ai_slot.dm b/code/modules/modular_computers/hardware/ai_slot.dm
index 8740b59b3523..455202a41415 100644
--- a/code/modules/modular_computers/hardware/ai_slot.dm
+++ b/code/modules/modular_computers/hardware/ai_slot.dm
@@ -10,6 +10,10 @@
var/obj/item/aicard/stored_card
var/locked = FALSE
+/obj/item/computer_hardware/ai_slot/Destroy()
+ QDEL_NULL(stored_card)
+ return ..()
+
///What happens when the intellicard is removed (or deleted) from the module, through try_eject() or not.
/obj/item/computer_hardware/ai_slot/Exited(atom/movable/gone, direction)
if(stored_card == gone)
@@ -55,7 +59,7 @@
if(Adjacent(user))
user.put_in_hands(stored_card)
else
- stored_card.forceMove(drop_location())
+ stored_card.forceMove(get_turf(src))
return TRUE
return FALSE
diff --git a/code/modules/modular_computers/hardware/battery_module.dm b/code/modules/modular_computers/hardware/battery_module.dm
index 27d3546ca24f..355e5049e10e 100644
--- a/code/modules/modular_computers/hardware/battery_module.dm
+++ b/code/modules/modular_computers/hardware/battery_module.dm
@@ -16,7 +16,7 @@
battery = new battery_type(src)
/obj/item/computer_hardware/battery/Destroy()
- battery = null
+ QDEL_NULL(battery)
return ..()
///What happens when the battery is removed (or deleted) from the module, through try_eject() or not.
@@ -59,7 +59,7 @@
user.put_in_hands(battery)
to_chat(user, span_notice("You detach \the [battery] from \the [src]."))
else
- battery.forceMove(drop_location())
+ battery.forceMove(get_turf(src))
return TRUE
/obj/item/stock_parts/cell/computer
diff --git a/code/modules/modular_computers/hardware/card_slot.dm b/code/modules/modular_computers/hardware/card_slot.dm
index 13f1b3bbc9b3..0a4c9cf8e1df 100644
--- a/code/modules/modular_computers/hardware/card_slot.dm
+++ b/code/modules/modular_computers/hardware/card_slot.dm
@@ -94,7 +94,7 @@
if(user && !issilicon(user) && in_range(src, user))
user.put_in_hands(stored_card)
else
- stored_card.forceMove(drop_location())
+ stored_card.forceMove(get_turf(src))
to_chat(user, span_notice("You remove the card from \the [src]."))
playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE)
diff --git a/code/modules/ninja/suit/suit.dm b/code/modules/ninja/suit/suit.dm
index 44ff5c709893..4ed9b2fa2526 100644
--- a/code/modules/ninja/suit/suit.dm
+++ b/code/modules/ninja/suit/suit.dm
@@ -84,12 +84,14 @@
cell.icon_state = "bscell"
/obj/item/clothing/suit/space/space_ninja/Initialize(mapload)
- START_PROCESSING(SSobj, src)
- return ..()
+ START_PROCESSING(SSobj, src)
+ return ..()
/obj/item/clothing/suit/space/space_ninja/Destroy()
- STOP_PROCESSING(SSobj, src)
- return ..()
+ QDEL_NULL(spark_system)
+ QDEL_NULL(cell)
+ STOP_PROCESSING(SSobj, src)
+ return ..()
// Power usage
/obj/item/clothing/suit/space/space_ninja/process(delta_time)
diff --git a/code/modules/paperwork/contract.dm b/code/modules/paperwork/contract.dm
index 11c10e1e4a69..c0957466c01d 100644
--- a/code/modules/paperwork/contract.dm
+++ b/code/modules/paperwork/contract.dm
@@ -92,6 +92,8 @@
/obj/item/paper/contract/infernal/New(atom/loc, mob/living/nTarget, datum/mind/nOwner)
..()
+ if(!nOwner || !nTarget)
+ return
owner = nOwner
devil_datum = owner.has_antag_datum(/datum/antagonist/devil)
target = nTarget
diff --git a/code/modules/pool/pool_controller.dm b/code/modules/pool/pool_controller.dm
index f2a3b23e5715..942fb2eeb63a 100644
--- a/code/modules/pool/pool_controller.dm
+++ b/code/modules/pool/pool_controller.dm
@@ -80,6 +80,7 @@
linked_turfs.Cut()
mobs_in_pool.Cut()
mist_off()
+ QDEL_NULL(wires)
return ..()
/obj/machinery/pool/controller/proc/scan_things()
diff --git a/code/modules/pool/pool_drain.dm b/code/modules/pool/pool_drain.dm
index 6e76a625a437..9dcfbf84d6be 100644
--- a/code/modules/pool/pool_drain.dm
+++ b/code/modules/pool/pool_drain.dm
@@ -31,8 +31,9 @@
/obj/machinery/pool/drain/Destroy()
STOP_PROCESSING(SSfastprocess, src)
- controller.linked_drain = null
- controller = null
+ if(controller)
+ controller.linked_drain = null
+ controller = null
whirling_mobs = null
return ..()
@@ -129,8 +130,9 @@
var/obj/machinery/pool/controller/controller
/obj/machinery/pool/filter/Destroy()
- controller.linked_filter = null
- controller = null
+ if(controller)
+ controller.linked_filter = null
+ controller = null
return ..()
/obj/machinery/pool/filter/emag_act(mob/living/user)
diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm
index ae95ad8d4a89..040e4b6888c6 100644
--- a/code/modules/power/apc.dm
+++ b/code/modules/power/apc.dm
@@ -303,17 +303,18 @@
if(malfai && operating)
malfai.malf_picker.processing_time = clamp(malfai.malf_picker.processing_time - 10,0,1000)
- area.power_light = FALSE
- area.power_equip = FALSE
- area.power_environ = FALSE
- area.power_change()
- area.poweralert(FALSE, src)
+ if(area)
+ area.power_light = FALSE
+ area.power_equip = FALSE
+ area.power_environ = FALSE
+ area.power_change()
+ area.poweralert(FALSE, src)
if(occupier)
malfvacate(1)
- qdel(wires)
- wires = null
+ if(wires)
+ QDEL_NULL(wires)
if(cell)
- qdel(cell)
+ QDEL_NULL(cell)
if(terminal)
disconnect_terminal()
. = ..()
diff --git a/code/modules/power/reactor/rbmk.dm b/code/modules/power/reactor/rbmk.dm
index f49148faad8b..498cd274e2d5 100644
--- a/code/modules/power/reactor/rbmk.dm
+++ b/code/modules/power/reactor/rbmk.dm
@@ -552,6 +552,11 @@ The reactor CHEWS through moderator. It does not do this slowly. Be very careful
. = ..()
addtimer(CALLBACK(src, PROC_REF(link_to_reactor)), 10 SECONDS)
+/obj/machinery/computer/reactor/Destroy()
+ reactor = null
+ return ..()
+
+
/obj/machinery/computer/reactor/wrench_act(mob/living/user, obj/item/I)
to_chat(user, "You start [anchored ? "un" : ""]securing [name]...")
if(I.use_tool(src, user, 40, volume=75))
@@ -728,6 +733,11 @@ The reactor CHEWS through moderator. It does not do this slowly. Be very careful
. = ..()
radio_connection = SSradio.add_object(src, FREQ_RBMK_CONTROL,filter=RADIO_ATMOSIA)
+/obj/machinery/computer/reactor/pump/Destroy()
+ SSradio.remove_object(src, FREQ_RBMK_CONTROL)
+ radio_connection = null
+ return ..()
+
/obj/machinery/computer/reactor/pump/proc/signal(power, set_output_pressure=null)
var/datum/signal/signal
if(!set_output_pressure) //Yes this is stupid, but technically if you pass through "set_output_pressure" onto the signal, it'll always try and set its output pressure and yeahhh...
diff --git a/code/modules/power/singularity/containment_field.dm b/code/modules/power/singularity/containment_field.dm
index 6f5e101c6c49..c2e0e5acdde4 100644
--- a/code/modules/power/singularity/containment_field.dm
+++ b/code/modules/power/singularity/containment_field.dm
@@ -13,12 +13,16 @@
interaction_flags_machine = NONE
light_range = 4
layer = ABOVE_OBJ_LAYER
- var/obj/machinery/field/generator/FG1 = null
- var/obj/machinery/field/generator/FG2 = null
+ var/obj/machinery/field/generator/field_gen_1 = null
+ var/obj/machinery/field/generator/field_gen_2 = null
/obj/machinery/field/containment/Destroy()
- FG1.fields -= src
- FG2.fields -= src
+ if(field_gen_1)
+ field_gen_1.fields -= src
+ field_gen_1 = null
+ if(field_gen_2)
+ field_gen_2.fields -= src
+ field_gen_2 = null
return ..()
/obj/machinery/field/containment/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
@@ -46,12 +50,12 @@
return FALSE
/obj/machinery/field/containment/attack_animal(mob/living/simple_animal/M)
- if(!FG1 || !FG2)
+ if(!field_gen_1 || !field_gen_2)
qdel(src)
return
if(ismegafauna(M))
M.visible_message("[M] glows fiercely as the containment field flickers out!")
- FG1.calc_power(INFINITY) //rip that 'containment' field
+ field_gen_1.calc_power(INFINITY) //rip that 'containment' field
M.adjustHealth(-M.obj_damage)
else
..()
@@ -68,12 +72,12 @@
/obj/machinery/field/containment/proc/set_master(master1,master2)
if(!master1 || !master2)
return FALSE
- FG1 = master1
- FG2 = master2
+ field_gen_1 = master1
+ field_gen_2 = master2
return TRUE
/obj/machinery/field/containment/shock(mob/living/user)
- if(!FG1 || !FG2)
+ if(!field_gen_1 || !field_gen_2)
qdel(src)
return FALSE
..()
diff --git a/code/modules/power/singularity/emitter.dm b/code/modules/power/singularity/emitter.dm
index 65dfbf7a85b1..98646a46baf5 100644
--- a/code/modules/power/singularity/emitter.dm
+++ b/code/modules/power/singularity/emitter.dm
@@ -110,6 +110,7 @@
log_game("Emitter deleted at [AREACOORD(T)]")
investigate_log("deleted at [AREACOORD(T)]", INVESTIGATE_SINGULO)
QDEL_NULL(sparks)
+ QDEL_NULL(wires)
return ..()
/obj/machinery/power/emitter/update_icon_state()
diff --git a/code/modules/power/tesla/coil.dm b/code/modules/power/tesla/coil.dm
index 3ef0eea7fee4..dee2a25ff7ae 100644
--- a/code/modules/power/tesla/coil.dm
+++ b/code/modules/power/tesla/coil.dm
@@ -28,6 +28,12 @@
wires = new /datum/wires/tesla_coil(src)
linked_techweb = SSresearch.science_tech
+/obj/machinery/power/tesla_coil/Destroy()
+ QDEL_NULL(wires)
+ linked_techweb = null
+ return ..()
+
+
/obj/machinery/power/tesla_coil/RefreshParts()
var/power_multiplier = 0
zap_cooldown = 100
diff --git a/code/modules/projectiles/ammunition/energy/portal.dm b/code/modules/projectiles/ammunition/energy/portal.dm
index 7bb10da0cc24..749ac8c3a414 100644
--- a/code/modules/projectiles/ammunition/energy/portal.dm
+++ b/code/modules/projectiles/ammunition/energy/portal.dm
@@ -2,8 +2,8 @@
projectile_type = /obj/item/projectile/beam/wormhole
e_cost = 0
fire_sound = 'sound/weapons/pulse3.ogg'
- var/obj/item/gun/energy/wormhole_projector/gun = null
select_name = "blue"
+ var/datum/weakref/gun
/obj/item/ammo_casing/energy/wormhole/orange
projectile_type = /obj/item/projectile/beam/wormhole/orange
@@ -11,7 +11,7 @@
/obj/item/ammo_casing/energy/wormhole/Initialize(mapload, obj/item/gun/energy/wormhole_projector/wh)
. = ..()
- gun = wh
+ gun = WEAKREF(wh)
/obj/item/ammo_casing/energy/wormhole/throw_proj()
. = ..()
diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm
index f8f8182a2883..aba5d8b2b7b9 100644
--- a/code/modules/projectiles/gun.dm
+++ b/code/modules/projectiles/gun.dm
@@ -150,7 +150,7 @@
AddComponent(/datum/component/automatic_fire, fire_delay)
/obj/item/gun/Destroy()
- if(pin)
+ if(isobj(pin))
QDEL_NULL(pin)
if(gun_light)
QDEL_NULL(gun_light)
@@ -162,6 +162,8 @@
QDEL_NULL(azoom)
if(firemode_action)
QDEL_NULL(firemode_action)
+ if(isatom(suppressed))
+ QDEL_NULL(suppressed)
return ..()
/obj/item/gun/examine(mob/user)
diff --git a/code/modules/projectiles/guns/ballistic/pistol.dm b/code/modules/projectiles/guns/ballistic/pistol.dm
index 53ba6d6a9211..8635cc7ff501 100644
--- a/code/modules/projectiles/guns/ballistic/pistol.dm
+++ b/code/modules/projectiles/guns/ballistic/pistol.dm
@@ -49,9 +49,9 @@
/obj/item/gun/ballistic/automatic/pistol/modular/update_overlays()
. = ..()
if(magazine && suppressed)
- . += "[unique_reskin[current_skin]["icon_state"]]-magazine-sup" //Yes, this means the default iconstate can't have a magazine overlay
+ . += "[current_skin ? unique_reskin[current_skin]["icon_state"] : initial(icon_state)]-magazine-sup" //Yes, this means the default iconstate can't have a magazine overlay
else if (magazine)
- . += "[unique_reskin[current_skin]["icon_state"]]-magazine"
+ . += "[current_skin ? unique_reskin[current_skin]["icon_state"] : initial(icon_state)]-magazine"
/obj/item/gun/ballistic/automatic/pistol/m1911
name = "\improper M1911"
diff --git a/code/modules/projectiles/guns/energy.dm b/code/modules/projectiles/guns/energy.dm
index 03dfc46713d6..b6915303330b 100644
--- a/code/modules/projectiles/guns/energy.dm
+++ b/code/modules/projectiles/guns/energy.dm
@@ -69,6 +69,8 @@
AddElement(/datum/element/update_icon_updates_onmob)
/obj/item/gun/energy/Destroy()
+ if(cell)
+ QDEL_NULL(cell)
STOP_PROCESSING(SSobj, src)
return ..()
diff --git a/code/modules/projectiles/guns/energy/laser_gatling.dm b/code/modules/projectiles/guns/energy/laser_gatling.dm
index 65d525b6382a..3eb932529a7c 100644
--- a/code/modules/projectiles/guns/energy/laser_gatling.dm
+++ b/code/modules/projectiles/guns/energy/laser_gatling.dm
@@ -23,6 +23,9 @@
START_PROCESSING(SSfastprocess, src)
/obj/item/minigunpack/Destroy()
+ if(!QDELETED(gun))
+ qdel(gun)
+ gun = null
STOP_PROCESSING(SSfastprocess, src)
return ..()
@@ -120,6 +123,12 @@
return ..()
+/obj/item/gun/energy/minigun/Destroy()
+ if(!QDELETED(ammo_pack))
+ qdel(ammo_pack)
+ ammo_pack = null
+ return ..()
+
/obj/item/gun/energy/minigun/attack_self(mob/living/user)
return
diff --git a/code/modules/projectiles/guns/energy/special.dm b/code/modules/projectiles/guns/energy/special.dm
index 67e7b58bd3e5..35d5d213e26a 100644
--- a/code/modules/projectiles/guns/energy/special.dm
+++ b/code/modules/projectiles/guns/energy/special.dm
@@ -243,10 +243,10 @@
for(var/i in 1 to ammo_type.len)
var/obj/item/ammo_casing/energy/wormhole/W = ammo_type[i]
if(istype(W))
- W.gun = src
+ W.gun = WEAKREF(src)
var/obj/item/projectile/beam/wormhole/WH = W.BB
if(istype(WH))
- WH.gun = src
+ WH.gun = WEAKREF(src)
/obj/item/gun/energy/wormhole_projector/process_chamber()
..()
diff --git a/code/modules/projectiles/guns/magic.dm b/code/modules/projectiles/guns/magic.dm
index eaa0dae83da1..0ed9b9152ef4 100644
--- a/code/modules/projectiles/guns/magic.dm
+++ b/code/modules/projectiles/guns/magic.dm
@@ -52,7 +52,8 @@
/obj/item/gun/magic/Initialize(mapload)
. = ..()
charges = max_charges
- chambered = new ammo_type(src)
+ if(ammo_type)
+ chambered = new ammo_type(src)
if(can_charge)
START_PROCESSING(SSobj, src)
diff --git a/code/modules/projectiles/guns/magic/motivation.dm b/code/modules/projectiles/guns/magic/motivation.dm
index db4c222619e4..e9e5c0ab3ad6 100644
--- a/code/modules/projectiles/guns/magic/motivation.dm
+++ b/code/modules/projectiles/guns/magic/motivation.dm
@@ -27,6 +27,10 @@
. = ..()
judgementcut = new(src)
+/obj/item/gun/magic/staff/motivation/Destroy()
+ QDEL_NULL(judgementcut)
+ . = ..()
+
//lets the user know that their judgement cuts are recharging
/obj/item/gun/magic/staff/motivation/shoot_with_empty_chamber(mob/living/user as mob|obj)
to_chat(user, "Judgement Cut is recharging.")
diff --git a/code/modules/projectiles/guns/misc/beam_rifle.dm b/code/modules/projectiles/guns/misc/beam_rifle.dm
index 4548719a20b3..9527ba0e2d68 100644
--- a/code/modules/projectiles/guns/misc/beam_rifle.dm
+++ b/code/modules/projectiles/guns/misc/beam_rifle.dm
@@ -511,8 +511,8 @@
duration = 0
. = ..()
if(!generation) //first one
- QDEL_LIST(gun.current_tracers)
- gun.current_tracers += .
+ QDEL_LIST(gun?.current_tracers)
+ gun?.current_tracers += .
/obj/item/projectile/beam/beam_rifle/hitscan/aiming_beam
tracer_type = /obj/effect/projectile/tracer/tracer/aiming
diff --git a/code/modules/projectiles/projectile/special/curse.dm b/code/modules/projectiles/projectile/special/curse.dm
index 89b1ecba4c6b..0609ba849696 100644
--- a/code/modules/projectiles/projectile/special/curse.dm
+++ b/code/modules/projectiles/projectile/special/curse.dm
@@ -20,6 +20,10 @@
handedness = prob(50)
icon_state = "cursehand[handedness]"
+/obj/item/projectile/curse_hand/Destroy()
+ QDEL_NULL(arm)
+ . = ..()
+
/obj/item/projectile/curse_hand/update_icon_state()
icon_state = "[initial(icon_state)][handedness]"
@@ -43,9 +47,10 @@
for(var/obj/effect/temp_visual/dir_setting/curse/grasp_portal/G in starting)
qdel(G)
new /obj/effect/temp_visual/dir_setting/curse/grasp_portal/fading(starting, dir)
- var/datum/beam/D = starting.Beam(T, icon_state = "curse[handedness]", time = 32, maxdistance = INFINITY, beam_type=/obj/effect/ebeam/curse_arm, beam_sleep_time = 1)
- for(var/b in D.elements)
- var/obj/effect/ebeam/B = b
- animate(B, alpha = 0, time = 32)
+ var/datum/beam/D = starting?.Beam(T, icon_state = "curse[handedness]", time = 32, maxdistance = INFINITY, beam_type=/obj/effect/ebeam/curse_arm, beam_sleep_time = 1)
+ if(D)
+ for(var/b in D.elements)
+ var/obj/effect/ebeam/B = b
+ animate(B, alpha = 0, time = 32)
return ..()
diff --git a/code/modules/projectiles/projectile/special/gravity.dm b/code/modules/projectiles/projectile/special/gravity.dm
index ba21ecb28c08..f5572f2f1bb1 100644
--- a/code/modules/projectiles/projectile/special/gravity.dm
+++ b/code/modules/projectiles/projectile/special/gravity.dm
@@ -13,7 +13,7 @@
. = ..()
var/obj/item/ammo_casing/energy/gravity/G = loc
if(istype(G))
- power = min(G.gun.power, 15)
+ power = min(G.gun?.power, 15)
/obj/item/projectile/gravity/on_hit()
. = ..()
diff --git a/code/modules/projectiles/projectile/special/hallucination.dm b/code/modules/projectiles/projectile/special/hallucination.dm
index 691178f93587..bd7d939e34ee 100644
--- a/code/modules/projectiles/projectile/special/hallucination.dm
+++ b/code/modules/projectiles/projectile/special/hallucination.dm
@@ -28,7 +28,7 @@
hal_target.client.images += fake_icon
/obj/item/projectile/hallucination/Destroy()
- if(hal_target.client)
+ if(hal_target?.client)
hal_target.client.images -= fake_icon
QDEL_NULL(fake_icon)
return ..()
diff --git a/code/modules/projectiles/projectile/special/wormhole.dm b/code/modules/projectiles/projectile/special/wormhole.dm
index aaf9f542d38e..99410d38e4a2 100644
--- a/code/modules/projectiles/projectile/special/wormhole.dm
+++ b/code/modules/projectiles/projectile/special/wormhole.dm
@@ -5,12 +5,13 @@
damage = 0
nodamage = TRUE
pass_flags = PASSGLASS | PASSTABLE | PASSGRILLE | PASSMOB
- var/obj/item/gun/energy/wormhole_projector/gun
color = "#33CCFF"
tracer_type = /obj/effect/projectile/tracer/wormhole
impact_type = /obj/effect/projectile/impact/wormhole
muzzle_type = /obj/effect/projectile/muzzle/wormhole
hitscan = TRUE
+ //Weakref to the thing that shot us
+ var/datum/weakref/gun
/obj/item/projectile/beam/wormhole/orange
name = "orange bluespace beam"
@@ -23,7 +24,8 @@
/obj/item/projectile/beam/wormhole/on_hit(atom/target)
- if(!gun)
+ var/obj/item/gun/energy/wormhole_projector/projector = gun.resolve()
+ if(!projector)
qdel(src)
return BULLET_ACT_BLOCK
- gun.create_portal(src, get_turf(src))
+ projector.create_portal(src, get_turf(src))
diff --git a/code/modules/reagents/chemistry/holder.dm b/code/modules/reagents/chemistry/holder.dm
index 312f3caace66..02730a26b000 100644
--- a/code/modules/reagents/chemistry/holder.dm
+++ b/code/modules/reagents/chemistry/holder.dm
@@ -112,7 +112,6 @@
value_multiplier = new_value
/datum/reagents/Destroy()
- . = ..()
//We're about to delete all reagents, so lets cleanup
addiction_list.Cut()
var/list/cached_reagents = reagent_list
@@ -124,6 +123,7 @@
if(my_atom && my_atom.reagents == src)
my_atom.reagents = null
my_atom = null
+ return ..()
// Used in attack logs for reagents in pills and such
/datum/reagents/proc/log_list()
diff --git a/code/modules/reagents/reagent_containers/borghypo.dm b/code/modules/reagents/reagent_containers/borghypo.dm
index a581463da2ef..14e79d48d30d 100644
--- a/code/modules/reagents/reagent_containers/borghypo.dm
+++ b/code/modules/reagents/reagent_containers/borghypo.dm
@@ -43,6 +43,7 @@ Borg Hypospray
START_PROCESSING(SSobj, src)
/obj/item/reagent_containers/borghypo/Destroy()
+ QDEL_LIST(reagent_list)
STOP_PROCESSING(SSobj, src)
return ..()
diff --git a/code/modules/reagents/reagent_containers/pill.dm b/code/modules/reagents/reagent_containers/pill.dm
index 56002c2e0cc3..2898ba7dc398 100644
--- a/code/modules/reagents/reagent_containers/pill.dm
+++ b/code/modules/reagents/reagent_containers/pill.dm
@@ -203,7 +203,7 @@
/obj/item/reagent_containers/pill/neurine
name = "neurine pill"
desc = "Used to treat non-severe mental traumas."
- list_reagents = list("neurine" = 10)
+ list_reagents = list(/datum/reagent/medicine/neurine = 10)
icon_state = "pill22"
roundstart = TRUE
diff --git a/code/modules/research/techweb/_techweb.dm b/code/modules/research/techweb/_techweb.dm
index dc07a5a1846e..6d43912d4db0 100644
--- a/code/modules/research/techweb/_techweb.dm
+++ b/code/modules/research/techweb/_techweb.dm
@@ -212,7 +212,6 @@
/datum/techweb/proc/add_design(datum/design/design, custom = FALSE)
if(!istype(design))
return FALSE
- researched_designs[design.id] = design
researched_designs[design.id] = TRUE
if(custom)
custom_designs[design.id] = TRUE
diff --git a/code/modules/ruins/lavalandruin_code/puzzle.dm b/code/modules/ruins/lavalandruin_code/puzzle.dm
index 2bae9fe9cb9b..520f15269718 100644
--- a/code/modules/ruins/lavalandruin_code/puzzle.dm
+++ b/code/modules/ruins/lavalandruin_code/puzzle.dm
@@ -242,7 +242,8 @@
/obj/structure/puzzle_element/Moved()
. = ..()
- source.validate()
+ if(source)
+ source.validate()
//Admin abuse version so you can pick the icon before it sets up
/obj/effect/sliding_puzzle/admin
diff --git a/code/modules/smithing/anvil.dm b/code/modules/smithing/anvil.dm
index 122a859761c4..2b5ae0d0b239 100644
--- a/code/modules/smithing/anvil.dm
+++ b/code/modules/smithing/anvil.dm
@@ -69,7 +69,7 @@
RECIPE_PIKE = /obj/item/smithing/pikehead)
/obj/structure/anvil/Initialize(mapload)
- ..()
+ . = ..()
currentquality = anvilquality
/obj/structure/anvil/attackby(obj/item/I, mob/user)
diff --git a/code/modules/smithing/finished_items.dm b/code/modules/smithing/finished_items.dm
index 97618df8fc2a..3b139bb61d0a 100644
--- a/code/modules/smithing/finished_items.dm
+++ b/code/modules/smithing/finished_items.dm
@@ -8,6 +8,7 @@
material_flags = MATERIAL_COLOR | MATERIAL_AFFECT_STATISTICS
total_mass = TOTAL_MASS_MEDIEVAL_WEAPON //yeah ok
slot_flags = ITEM_SLOT_BELT
+ obj_flags = UNIQUE_RENAME
w_class = WEIGHT_CLASS_NORMAL
force = 6
lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi'
@@ -19,7 +20,7 @@
var/wield_force = 15
/obj/item/melee/smith/Initialize(mapload)
- ..()
+ . = ..()
if(desc == "cringe")
desc = "A handmade [name]."
overlay = mutable_appearance(icon, overlay_state)
@@ -56,7 +57,7 @@
sharpness = SHARP_POINTY//it doesnt have a blade it has a point
/obj/item/mining_scanner/prospector/Initialize(mapload)
- ..()
+ . = ..()
var/mutable_appearance/overlay
desc = "A handmade [name]."
overlay = mutable_appearance(icon, "minihandle")
@@ -74,7 +75,7 @@
sharpness = SHARP_POINTY
/obj/item/pickaxe/smithed/Initialize(mapload)
- ..()
+ . = ..()
desc = "A handmade [name]."
var/mutable_appearance/overlay
overlay = mutable_appearance(icon, "stick")
@@ -95,7 +96,7 @@
sharpness = SHARP_EDGED //it cuts through the earth
/obj/item/shovel/smithed/Initialize(mapload)
- ..()
+ . = ..()
desc = "A handmade [name]."
var/mutable_appearance/overlay
overlay = mutable_appearance(icon, "shovelhandle")
diff --git a/code/modules/smithing/furnace.dm b/code/modules/smithing/furnace.dm
index 70bff3203006..952f185550fe 100644
--- a/code/modules/smithing/furnace.dm
+++ b/code/modules/smithing/furnace.dm
@@ -11,13 +11,13 @@
/obj/structure/furnace/Initialize(mapload)
- ..()
+ . = ..()
create_reagents(250, TRANSPARENT)
START_PROCESSING(SSobj, src)
/obj/structure/furnace/Destroy()
- ..()
STOP_PROCESSING(SSobj, src)
+ return ..()
/obj/structure/furnace/process()
if(debug)
diff --git a/code/modules/spells/spell.dm b/code/modules/spells/spell.dm
index 28cb9ac36c8c..de5526055d8b 100644
--- a/code/modules/spells/spell.dm
+++ b/code/modules/spells/spell.dm
@@ -20,6 +20,12 @@
if(has_action)
action = new base_action(src)
+/obj/effect/proc_holder/Destroy()
+ QDEL_NULL(action)
+ if(ranged_ability_user)
+ remove_ranged_ability()
+ return ..()
+
/obj/effect/proc_holder/proc/on_gain(mob/living/user)
return
@@ -34,12 +40,6 @@
GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for the badmin verb for now
-/obj/effect/proc_holder/Destroy()
- QDEL_NULL(action)
- if(ranged_ability_user)
- remove_ranged_ability()
- return ..()
-
/obj/effect/proc_holder/singularity_act()
return
diff --git a/code/modules/spells/spell_types/lichdom.dm b/code/modules/spells/spell_types/lichdom.dm
index 0d1f03d53262..8e4f36e25f9e 100644
--- a/code/modules/spells/spell_types/lichdom.dm
+++ b/code/modules/spells/spell_types/lichdom.dm
@@ -87,6 +87,9 @@
/obj/item/phylactery/Initialize(mapload, datum/mind/newmind)
. = ..()
+ if(!newmind)
+ stack_trace("A phylactery was created with no target mind")
+ return INITIALIZE_HINT_QDEL
mind = newmind
name = "phylactery of [mind.name]"
diff --git a/code/modules/spells/spell_types/shapeshift.dm b/code/modules/spells/spell_types/shapeshift.dm
index 67c2e3e9411e..d7c6607f9bdc 100644
--- a/code/modules/spells/spell_types/shapeshift.dm
+++ b/code/modules/spells/spell_types/shapeshift.dm
@@ -95,7 +95,8 @@
src.source = source
shape = loc
if(!istype(shape))
- CRASH("shapeshift holder created outside mob/living")
+ stack_trace("shapeshift holder created outside mob/living")
+ return INITIALIZE_HINT_QDEL
stored = caster
if(stored.mind)
stored.mind.transfer_to(shape)
diff --git a/code/modules/spells/spell_types/touch_attacks.dm b/code/modules/spells/spell_types/touch_attacks.dm
index 4f0ac2964f70..4f51daac8fd0 100644
--- a/code/modules/spells/spell_types/touch_attacks.dm
+++ b/code/modules/spells/spell_types/touch_attacks.dm
@@ -7,6 +7,14 @@
include_user = 1
range = -1
+
+/obj/effect/proc_holder/spell/targeted/touch/Destroy()
+ remove_hand()
+ if(action?.owner)
+ var/mob/guy_who_needs_to_know = action.owner
+ to_chat(guy_who_needs_to_know, span_notice("The power of the spell dissipates from your hand."))
+ return ..()
+
/obj/effect/proc_holder/spell/targeted/touch/proc/remove_hand(recharge = FALSE)
QDEL_NULL(attached_hand)
if(recharge)
diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm
index c4ab435ee0cf..5e4fb22904d0 100644
--- a/code/modules/surgery/bodyparts/_bodyparts.dm
+++ b/code/modules/surgery/bodyparts/_bodyparts.dm
@@ -636,6 +636,7 @@
/obj/item/bodypart/proc/update_limb(dropping_limb, mob/living/carbon/source)
body_markings_list = list()
var/mob/living/carbon/C
+ owner.create_weakref()
if(source)
C = source
if(!original_owner)
@@ -646,6 +647,9 @@
C = owner
no_update = FALSE
+ if(!C)
+ return
+
if(HAS_TRAIT(C, TRAIT_HUSK) && is_organic_limb())
species_id = "husk" //overrides species_id
dmg_overlay_type = "" //no damage overlay shown when husked
diff --git a/code/modules/surgery/organs/augments_arms.dm b/code/modules/surgery/organs/augments_arms.dm
index 6ffcfd9dc7c1..f133961248f4 100644
--- a/code/modules/surgery/organs/augments_arms.dm
+++ b/code/modules/surgery/organs/augments_arms.dm
@@ -24,6 +24,11 @@
for(var/obj/item/I in contents)
add_item(I)
+/obj/item/organ/cyberimp/arm/Destroy()
+ QDEL_LIST(items_list)
+ QDEL_NULL(holder)
+ return ..()
+
/obj/item/organ/cyberimp/arm/proc/add_item(obj/item/I)
if(I in items_list)
return
diff --git a/code/modules/surgery/organs/organ_internal.dm b/code/modules/surgery/organs/organ_internal.dm
index 2cc777536dae..997e1767b0f4 100644
--- a/code/modules/surgery/organs/organ_internal.dm
+++ b/code/modules/surgery/organs/organ_internal.dm
@@ -360,7 +360,7 @@
desc = "Something hecked up"
/obj/item/organ/random/Initialize(mapload)
- ..()
+ . = ..()
var/list = list(/obj/item/organ/tongue, /obj/item/organ/brain, /obj/item/organ/heart, /obj/item/organ/liver, /obj/item/organ/ears, /obj/item/organ/eyes, /obj/item/organ/tail, /obj/item/organ/stomach)
var/newtype = pick(list)
new newtype(loc)
diff --git a/code/modules/tcg/cards.dm b/code/modules/tcg/cards.dm
index cd2510a182c7..1ff88ba9a0f0 100644
--- a/code/modules/tcg/cards.dm
+++ b/code/modules/tcg/cards.dm
@@ -114,12 +114,15 @@
. = ..()
if(!special)
datum_type = new_datum
- card_datum = new datum_type
+ if(datum_type)
+ card_datum = new datum_type
+ illegal = illegal_card
+ if(!card_datum)
+ return
icon = card_datum.pack
icon_state = card_datum.icon_state
name = card_datum.name
desc = card_datum.desc
- illegal = illegal_card
switch(card_datum.rarity)
if("Common")
@@ -378,8 +381,8 @@
var/static/radial_pickup = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_pickup")
/obj/item/tcgcard_deck/Initialize(mapload)
- . = ..()
LoadComponent(/datum/component/storage/concrete/tcg)
+ . = ..()
/obj/item/tcgcard_deck/ComponentInitialize()
. = ..()
diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm
index c7f69fcaf1e9..c1551cc77b11 100644
--- a/code/modules/unit_tests/_unit_tests.dm
+++ b/code/modules/unit_tests/_unit_tests.dm
@@ -3,9 +3,18 @@
#if defined(UNIT_TESTS) || defined(SPACEMAN_DMM)
+/// For advanced cases, fail unconditionally but don't return (so a test can return multiple results)
+#define TEST_FAIL(reason) (Fail(reason || "No reason", __FILE__, __LINE__))
+
/// Asserts that a condition is true
/// If the condition is not true, fails the test
-#define TEST_ASSERT(assertion, reason) if (!(assertion)) { return Fail("Assertion failed: [reason || "No reason"]") }
+#define TEST_ASSERT(assertion, reason) if (!(assertion)) { return Fail("Assertion failed: [reason || "No reason"]", __FILE__, __LINE__) }
+
+/// Asserts that a parameter is not null
+#define TEST_ASSERT_NOTNULL(a, reason) if (isnull(a)) { return Fail("Expected non-null value: [reason || "No reason"]", __FILE__, __LINE__) }
+
+/// Asserts that a parameter is null
+#define TEST_ASSERT_NULL(a, reason) if (!isnull(a)) { return Fail("Expected null value but received [a]: [reason || "No reason"]", __FILE__, __LINE__) }
/// Asserts that the two parameters passed are equal, fails otherwise
/// Optionally allows an additional message in the case of a failure
@@ -13,7 +22,7 @@
var/lhs = ##a; \
var/rhs = ##b; \
if (lhs != rhs) { \
- return Fail("Expected [isnull(lhs) ? "null" : lhs] to be equal to [isnull(rhs) ? "null" : rhs].[message ? " [message]" : ""]"); \
+ return Fail("Expected [isnull(lhs) ? "null" : lhs] to be equal to [isnull(rhs) ? "null" : rhs].[message ? " [message]" : ""]", __FILE__, __LINE__); \
} \
} while (FALSE)
@@ -23,7 +32,7 @@
var/lhs = ##a; \
var/rhs = ##b; \
if (lhs == rhs) { \
- return Fail("Expected [isnull(lhs) ? "null" : lhs] to not be equal to [isnull(rhs) ? "null" : rhs].[message ? " [message]" : ""]"); \
+ return Fail("Expected [isnull(lhs) ? "null" : lhs] to not be equal to [isnull(rhs) ? "null" : rhs].[message ? " [message]" : ""]", __FILE__, __LINE__); \
} \
} while (FALSE)
@@ -37,8 +46,25 @@
#define UNIT_TEST_FAILED 1
#define UNIT_TEST_SKIPPED 2
+#define TEST_PRE 0
#define TEST_DEFAULT 1
-#define TEST_DEL_WORLD INFINITY
+/// After most test steps, used for tests that run long so shorter issues can be noticed faster
+#define TEST_LONGER 10
+/// This must be the last test to run due to the inherent nature of the test iterating every single tangible atom in the game and qdeleting all of them (while taking long sleeps to make sure the garbage collector fires properly) taking a large amount of time.
+#define TEST_CREATE_AND_DESTROY INFINITY
+
+/// Change color to red on ANSI terminal output, if enabled with -DANSICOLORS.
+#ifdef ANSICOLORS
+#define TEST_OUTPUT_RED(text) "\x1B\x5B1;31m[text]\x1B\x5B0m"
+#else
+#define TEST_OUTPUT_RED(text) (text)
+#endif
+/// Change color to green on ANSI terminal output, if enabled with -DANSICOLORS.
+#ifdef ANSICOLORS
+#define TEST_OUTPUT_GREEN(text) "\x1B\x5B1;32m[text]\x1B\x5B0m"
+#else
+#define TEST_OUTPUT_GREEN(text) (text)
+#endif
/// A trait source when adding traits through unit tests
#define TRAIT_SOURCE_UNIT_TESTS "unit_tests"
@@ -55,7 +81,7 @@
// #include "connect_loc.dm"
// #include "confusion.dm"
// #include "crayons.dm"
-// #include "create_and_destroy.dm"
+#include "create_and_destroy.dm"
// #include "designs.dm"
#include "dynamic_ruleset_sanity.dm"
// #include "egg_glands.dm"
@@ -102,12 +128,12 @@
/// CIT TESTS
#include "character_saving.dm"
-#ifdef REFERENCE_TRACKING //Don't try and parse this file if ref tracking isn't turned on. IE: don't parse ref tracking please mr linter
+#ifdef REFERENCE_TRACKING_DEBUG //Don't try and parse this file if ref tracking isn't turned on. IE: don't parse ref tracking please mr linter
#include "find_reference_sanity.dm"
#endif
#undef TEST_ASSERT
#undef TEST_ASSERT_EQUAL
#undef TEST_ASSERT_NOTEQUAL
-#undef TEST_FOCUS
+//#undef TEST_FOCUS - This define is used by vscode unit test extension to pick specific unit tests to run and appended later so needs to be used out of scope here
#endif
diff --git a/code/modules/unit_tests/anchored_mobs.dm b/code/modules/unit_tests/anchored_mobs.dm
index 103b97e7a993..88487ea2b8d7 100644
--- a/code/modules/unit_tests/anchored_mobs.dm
+++ b/code/modules/unit_tests/anchored_mobs.dm
@@ -4,6 +4,4 @@
var/mob/M = i
if(initial(M.anchored))
L += "[i]"
- if(!L.len)
- return //passed!
- Fail("The following mobs are defined as anchored. This is incompatible with the new move force/resist system and needs to be revised.: [L.Join(" ")]")
+ TEST_ASSERT(!L.len, "The following mobs are defined as anchored. This is incompatible with the new move force/resist system and needs to be revised.: [L.Join(" ")]")
diff --git a/code/modules/unit_tests/bespoke_id.dm b/code/modules/unit_tests/bespoke_id.dm
index 06676c626c7e..e1356650ded2 100644
--- a/code/modules/unit_tests/bespoke_id.dm
+++ b/code/modules/unit_tests/bespoke_id.dm
@@ -5,4 +5,4 @@
for(var/i in subtypesof(/datum/element))
var/datum/element/faketype = i
if((initial(faketype.element_flags) & ELEMENT_BESPOKE) && initial(faketype.id_arg_index) == base_index)
- Fail("A bespoke element was not configured with a proper id_arg_index: [faketype]")
+ TEST_FAIL("A bespoke element was not configured with a proper id_arg_index: [faketype]")
diff --git a/code/modules/unit_tests/card_mismatch.dm b/code/modules/unit_tests/card_mismatch.dm
index 506e88f19c3f..90d8250ff0db 100644
--- a/code/modules/unit_tests/card_mismatch.dm
+++ b/code/modules/unit_tests/card_mismatch.dm
@@ -1,7 +1,6 @@
/datum/unit_test/card_mismatch
/datum/unit_test/card_mismatch/Run()
- var/message = checkCardpacks(SStrading_card_game.card_packs)
- message += checkCardDatums()
- if(message)
- Fail(message)
+ var/message = SStrading_card_game.check_cardpacks(SStrading_card_game.card_packs)
+ message += SStrading_card_game.check_card_datums()
+ TEST_ASSERT(!message, message)
diff --git a/code/modules/unit_tests/chain_pull_through_space.dm b/code/modules/unit_tests/chain_pull_through_space.dm
index 10363d5aadf6..b59de2a46703 100644
--- a/code/modules/unit_tests/chain_pull_through_space.dm
+++ b/code/modules/unit_tests/chain_pull_through_space.dm
@@ -41,22 +41,15 @@
// Walk normally to the left, make sure we're still a chain
alice.Move(locate(run_loc_floor_bottom_left.x + 1, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z))
- if (bob.x != run_loc_floor_bottom_left.x + 2)
- return Fail("During normal move, Bob was not at the correct x ([bob.x])")
- if (charlie.x != run_loc_floor_bottom_left.x + 3)
- return Fail("During normal move, Charlie was not at the correct x ([charlie.x])")
+ TEST_ASSERT_EQUAL(bob.x, run_loc_floor_bottom_left.x + 2, "During normal move, Bob was not at the correct x ([bob.x])")
+ TEST_ASSERT_EQUAL(charlie.x, run_loc_floor_bottom_left.x + 3, "During normal move, Charlie was not at the correct x ([charlie.x])")
// We're going through the space turf now that should teleport us
alice.Move(run_loc_floor_bottom_left)
- if (alice.z != space_tile.destination_z)
- return Fail("Alice did not teleport to the destination z-level. Current location: ([alice.x], [alice.y], [alice.z])")
-
- if (bob.z != space_tile.destination_z)
- return Fail("Bob did not teleport to the destination z-level. Current location: ([bob.x], [bob.y], [bob.z])")
- if (!bob.Adjacent(alice))
- return Fail("Bob is not adjacent to Alice. Bob is at [bob.x], Alice is at [alice.x]")
-
- if (charlie.z != space_tile.destination_z)
- return Fail("Charlie did not teleport to the destination z-level. Current location: ([charlie.x], [charlie.y], [charlie.z])")
- if (!charlie.Adjacent(bob))
- return Fail("Charlie is not adjacent to Bob. Charlie is at [charlie.x], Bob is at [bob.x]")
+ TEST_ASSERT_EQUAL(alice.z, space_tile.destination_z, "Alice did not teleport to the destination z-level. Current location: ([alice.x], [alice.y], [alice.z])")
+
+ TEST_ASSERT_EQUAL(bob.z, space_tile.destination_z, "Bob did not teleport to the destination z-level. Current location: ([bob.x], [bob.y], [bob.z])")
+ TEST_ASSERT(bob.Adjacent(alice), "Bob is not adjacent to Alice. Bob is at [bob.x], Alice is at [alice.x]")
+
+ TEST_ASSERT_EQUAL(charlie.z, space_tile.destination_z, "Charlie did not teleport to the destination z-level. Current location: ([charlie.x], [charlie.y], [charlie.z])")
+ TEST_ASSERT(charlie.Adjacent(bob), "Charlie is not adjacent to Bob. Charlie is at [charlie.x], Bob is at [bob.x]")
diff --git a/code/modules/unit_tests/character_saving.dm b/code/modules/unit_tests/character_saving.dm
index cca17b81e4fe..f7ca8b738b05 100644
--- a/code/modules/unit_tests/character_saving.dm
+++ b/code/modules/unit_tests/character_saving.dm
@@ -12,17 +12,17 @@
P.save_character()
P.load_character()
if(P.features["flavor_text"] != UNIT_TEST_SAVING_FLAVOR_TEXT)
- Fail("Flavor text is failing to save.")
+ TEST_FAIL("Flavor text is failing to save.")
if(P.features["silicon_flavor_text"] != UNIT_TEST_SAVING_SILICON_FLAVOR_TEXT)
- Fail("Silicon flavor text is failing to save.")
+ TEST_FAIL("Silicon flavor text is failing to save.")
if(P.features["ooc_notes"] != UNIT_TEST_SAVING_OOC_NOTES)
- Fail("OOC text is failing to save.")
+ TEST_FAIL("OOC text is failing to save.")
P.save_character()
P.load_character()
if((P.features["flavor_text"] != UNIT_TEST_SAVING_FLAVOR_TEXT) || (P.features["silicon_flavor_text"] != UNIT_TEST_SAVING_SILICON_FLAVOR_TEXT) || (P.features["ooc_notes"] != UNIT_TEST_SAVING_OOC_NOTES))
- Fail("Repeated saving and loading possibly causing save deletion.")
+ TEST_FAIL("Repeated saving and loading possibly causing save deletion.")
catch(var/exception/e)
- Fail("Failed to save and load character due to exception [e.file]:[e.line], [e.name]")
+ TEST_FAIL("Failed to save and load character due to exception [e.file]:[e.line], [e.name]")
#undef UNIT_TEST_SAVING_FLAVOR_TEXT
#undef UNIT_TEST_SAVING_SILICON_FLAVOR_TEXT
diff --git a/code/modules/unit_tests/component_tests.dm b/code/modules/unit_tests/component_tests.dm
index 0099d7508c5d..f609e73c4b72 100644
--- a/code/modules/unit_tests/component_tests.dm
+++ b/code/modules/unit_tests/component_tests.dm
@@ -8,5 +8,5 @@
var/dupe_type = initial(comp.dupe_type)
if(dupe_type && !ispath(dupe_type))
bad_dts += t
- if(length(bad_dms) || length(bad_dts))
- Fail("Components with invalid dupe modes: ([bad_dms.Join(",")]) ||| Components with invalid dupe types: ([bad_dts.Join(",")])")
+ TEST_ASSERT(!length(bad_dms) && !length(bad_dts),
+ "Components with invalid dupe modes: ([bad_dms.Join(",")]) ||| Components with invalid dupe types: ([bad_dts.Join(",")])")
diff --git a/code/modules/unit_tests/crafting_recipes.dm b/code/modules/unit_tests/crafting_recipes.dm
index 33803bd1113e..9b437c801c04 100644
--- a/code/modules/unit_tests/crafting_recipes.dm
+++ b/code/modules/unit_tests/crafting_recipes.dm
@@ -2,6 +2,6 @@
for(var/i in GLOB.crafting_recipes)
var/datum/crafting_recipe/R = i
if(!R.subcategory)
- Fail("Invalid subcategory on [R] ([R.type]).")
- if(!R.category && (R.cateogry != CAT_NONE))
- Fail("Invalid category on [R] ([R.type])")
+ TEST_FAIL("Invalid subcategory on [R] ([R.type]).")
+ if(!R.category && (R.category != CAT_NONE))
+ TEST_FAIL("Invalid category on [R] ([R.type])")
diff --git a/code/modules/unit_tests/create_and_destroy.dm b/code/modules/unit_tests/create_and_destroy.dm
new file mode 100644
index 000000000000..06fbc678c659
--- /dev/null
+++ b/code/modules/unit_tests/create_and_destroy.dm
@@ -0,0 +1,227 @@
+///Delete one of every type, sleep a while, then check to see if anything has gone fucky
+/datum/unit_test/create_and_destroy
+ //You absolutely must run last
+ priority = TEST_CREATE_AND_DESTROY
+
+GLOBAL_VAR_INIT(running_create_and_destroy, FALSE)
+/datum/unit_test/create_and_destroy/Run()
+ //We'll spawn everything here
+ var/turf/spawn_at = run_loc_floor_bottom_left
+ var/list/ignore = list(
+ //Never meant to be created, errors out the ass for mobcode reasons
+ /mob/living/carbon,
+ //Nother template type, doesn't like being created with no seed
+ // /obj/item/food/grown,
+ //And another
+ /obj/item/slimecross/recurring,
+ //This should be obvious
+ /obj/machinery/doomsday_device,
+ //Yet more templates
+ // /obj/machinery/restaurant_portal,
+ //Template type
+ /obj/effect/mob_spawn,
+ /obj/effect/mob_spawn/alien,
+ /obj/effect/mob_spawn/alien/corpse,
+ /obj/effect/mob_spawn/alien/corpse/humanoid,
+ //Template type
+ // /obj/structure/holosign/robot_seat,
+ //Singleton
+ /mob/dview,
+ //Template type
+ /obj/item/bodypart,
+ //This is meant to fail extremely loud every single time it occurs in any environment in any context, and it falsely alarms when this unit test iterates it. Let's not spawn it in.
+ // /obj/merge_conflict_marker,
+ //briefcase launchpads erroring
+ /obj/machinery/launchpad/briefcase,
+ // Needs mind
+ /obj/item/phylactery,
+ //No ID to pass in
+ /obj/effect/spawner/structure/window/reinforced/tinted/electrochromatic,
+ // Needs proper args
+ /obj/effect/buildmode_line,
+ //Spawns it in the wall and shuttle controller runtimes (actually not caught in unit test)
+ /obj/effect/landmark/latejoin,
+ //Those DAMN SWARMERS ARE EATING EVERYTHING WHILE TEST IS RUNNING
+ /mob/living/simple_animal/hostile/megafauna/swarmer_swarm_beacon,
+ // Randomly causes test to fail because of random movement
+ /obj/item/grenade/clusterbuster/segment,
+ // With 10% Spawns `while() ... sleep()` proc that causes her hat to harddel // TODO rewrite helmet code attack_self() and port modern /tg/ helmet code
+ /mob/living/carbon/monkey/angry,
+ )
+ //Say it with me now, type template
+ ignore += typesof(/obj/effect/mapping_helpers)
+ //This turf existing is an error in and of itself
+ ignore += typesof(/turf/baseturf_skipover)
+ ignore += typesof(/turf/baseturf_bottom)
+ // Messes with test results by teleporting stuff out of location
+ ignore += typesof(/turf/open/space/transit)
+ //This demands a borg, so we'll let if off easy
+ ignore += typesof(/obj/item/modular_computer/tablet/integrated)
+ //This one demands a computer, ditto
+ ignore += typesof(/obj/item/modular_computer/processor)
+ //Very finiky, blacklisting to make things easier
+ ignore += typesof(/obj/item/poster/wanted)
+ //This expects a seed, we can't pass it
+ ignore += typesof(/obj/item/reagent_containers/food/snacks/grown)
+ //Needs clients / mobs to observe it to exist. Also includes hallucinations.
+ // ignore += typesof(/obj/effect/client_image_holder)
+ ignore += typesof(/obj/effect/hallucination)
+ //Same to above. Needs a client / mob / hallucination to observe it to exist.
+ ignore += typesof(/obj/item/projectile/hallucination)
+ // ignore += typesof(/obj/item/hallucinated)
+ //Can't pass in a thing to glow
+ ignore += typesof(/obj/effect/abstract/eye_lighting)
+ //We don't have a pod
+ ignore += typesof(/obj/effect/pod_landingzone_effect)
+ ignore += typesof(/obj/effect/pod_landingzone)
+ //We have a baseturf limit of 10, adding more than 10 baseturf helpers will kill CI, so here's a future edge case to fix.
+ ignore += typesof(/obj/effect/baseturf_helper)
+ //No host to pass in
+ ignore += typesof(/obj/effect/abstract/proximity_checker)
+ //No owner to pass in
+ ignore += typesof(/obj/effect/abstract/parry)
+ //No tauma to pass in
+ ignore += typesof(/mob/camera/imaginary_friend)
+ //There's no shapeshift to hold
+ ignore += typesof(/obj/shapeshift_holder)
+ //No pod to gondola
+ ignore += typesof(/mob/living/simple_animal/pet/gondola/gondolapod)
+ //No heart to give
+ // ignore += typesof(/obj/structure/ethereal_crystal)
+ //No linked console
+ ignore += typesof(/mob/camera/aiEye/remote/base_construction)
+ //See above
+ ignore += typesof(/mob/camera/aiEye/remote/shuttle_docker)
+ //Hangs a ref post invoke async, which we don't support. Could put a qdeleted check but it feels hacky
+ ignore += typesof(/obj/effect/anomaly/grav/high)
+ //See above
+ ignore += typesof(/obj/effect/timestop)
+ // See above
+ ignore += typesof(/obj/effect/domain_expansion)
+ //Invoke async in init, skippppp
+ ignore += typesof(/mob/living/silicon/robot/modules)
+ //This lad also sleeps
+ ignore += typesof(/obj/item/hilbertshotel)
+ //this boi spawns turf changing stuff, and it stacks and causes pain. Let's just not
+ ignore += typesof(/obj/effect/sliding_puzzle)
+ //Stacks baseturfs, can't be tested here
+ ignore += typesof(/obj/effect/temp_visual/lava_warning)
+ //Stacks baseturfs, can't be tested here
+ // ignore += typesof(/obj/effect/landmark/ctf)
+ //Our system doesn't support it without warning spam from unregister calls on things that never registered
+ ignore += typesof(/obj/docking_port)
+ //Asks for a shuttle that may not exist, let's leave it alone
+ ignore += typesof(/obj/item/pinpointer/shuttle)
+ //This spawns beams as a part of init, which can sleep past an async proc. This hangs a ref, and fucks us. It's only a problem here because the beam sleeps with CHECK_TICK
+ // ignore += typesof(/obj/structure/alien/resin/flower_bud)
+ //Expects a mob to holderize, we have nothing to give
+ ignore += typesof(/obj/item/clothing/head/mob_holder)
+ //Needs cards passed into the initilazation args
+ ignore += typesof(/obj/item/toy/cards/cardhand)
+ //Needs cards passed into the initilazation args
+ ignore += typesof(/obj/item/toy/cards/cardhand)
+ //Needs a holodeck area linked to it which is not guarenteed to exist and technically is supposed to have a 1:1 relationship with computer anyway.
+ ignore += typesof(/obj/machinery/computer/holodeck)
+ //runtimes if not paired with a landmark
+ // ignore += typesof(/obj/structure/industrial_lift)
+ // Runtimes if the associated machinery does not exist, but not the base type
+ // ignore += subtypesof(/obj/machinery/airlock_controller)
+ // All of them sleep with CHECK_TICK and hang refs. //TODO: Port modern /tg/ techwebs
+ ignore += typesof(/obj/machinery/rnd/production)
+ // This one sleeps too in it's AI code
+ ignore += typesof(/mob/living/simple_animal/hostile/swarmer)
+
+ var/list/cached_contents = spawn_at.contents.Copy()
+ var/original_turf_type = spawn_at.type
+ var/original_baseturfs = islist(spawn_at.baseturfs) ? spawn_at.baseturfs.Copy() : spawn_at.baseturfs
+ var/original_baseturf_count = length(original_baseturfs)
+
+ GLOB.running_create_and_destroy = TRUE
+ for(var/type_path in typesof(/atom/movable, /turf) - ignore) //No areas please
+ if(ispath(type_path, /turf))
+ spawn_at.ChangeTurf(type_path)
+ //We change it back to prevent baseturfs stacking and hitting the limit
+ spawn_at.ChangeTurf(original_turf_type, original_baseturfs)
+ if(original_baseturf_count != length(spawn_at.baseturfs))
+ TEST_FAIL("[type_path] changed the amount of baseturfs from [original_baseturf_count] to [length(spawn_at.baseturfs)]; [english_list(original_baseturfs)] to [islist(spawn_at.baseturfs) ? english_list(spawn_at.baseturfs) : spawn_at.baseturfs]")
+ //Warn if it changes again
+ original_baseturfs = islist(spawn_at.baseturfs) ? spawn_at.baseturfs.Copy() : spawn_at.baseturfs
+ original_baseturf_count = length(original_baseturfs)
+ else
+ var/atom/creation = new type_path(spawn_at)
+ if(QDELETED(creation))
+ continue
+ //Go all in
+ qdel(creation, force = TRUE)
+ //This will hold a ref to the last thing we process unless we set it to null
+ //Yes byond is fucking sinful
+ creation = null
+
+ //There's a lot of stuff that either spawns stuff in on create, or removes stuff on destroy. Let's cut it all out so things are easier to deal with
+ var/list/to_del = spawn_at.contents - cached_contents
+ if(length(to_del))
+ for(var/atom/to_kill in to_del)
+ qdel(to_kill)
+
+ GLOB.running_create_and_destroy = FALSE
+ //Hell code, we're bound to have ended the round somehow so let's stop if from ending while we work
+ SSticker.delay_end = TRUE
+ //Prevent the garbage subsystem from harddeling anything, if only to save time
+ SSgarbage.collection_timeout[GC_QUEUE_HARDDELETE] = 10000 HOURS
+ //Clear it, just in case
+ cached_contents.Cut()
+
+ //Now that we've qdel'd everything, let's sleep until the gc has processed all the shit we care about
+ var/time_needed = SSgarbage.collection_timeout[GC_QUEUE_CHECK]
+ var/start_time = world.time
+ var/garbage_queue_processed = FALSE
+
+ sleep(time_needed)
+ while(!garbage_queue_processed)
+ var/list/queue_to_check = SSgarbage.queues[GC_QUEUE_CHECK]
+ //How the hell did you manage to empty this? Good job!
+ if(!length(queue_to_check))
+ garbage_queue_processed = TRUE
+ break
+
+ var/list/oldest_packet = queue_to_check[1]
+ //Pull out the time we deld at
+ var/qdeld_at = oldest_packet[1]
+ //If we've found a packet that got del'd later then we finished, then all our shit has been processed
+ if(qdeld_at > start_time)
+ garbage_queue_processed = TRUE
+ break
+
+ if(world.time > start_time + time_needed + 30 MINUTES) //If this gets us gitbanned I'm going to laugh so hard
+ TEST_FAIL("Something has gone horribly wrong, the garbage queue has been processing for well over 30 minutes. What the hell did you do")
+ break
+
+ //Immediately fire the gc right after
+ SSgarbage.next_fire = 1
+ //Unless you've seriously fucked up, queue processing shouldn't take "that" long. Let her run for a bit, see if anything's changed
+ sleep(20 SECONDS)
+
+ //Alright, time to see if anything messed up
+ var/list/cache_for_sonic_speed = SSgarbage.items
+ for(var/path in cache_for_sonic_speed)
+ var/datum/qdel_item/item = cache_for_sonic_speed[path]
+ if(item.failures)
+ TEST_FAIL("[item.name] hard deleted [item.failures] times out of a total del count of [item.qdels]")
+ if(item.no_respect_force)
+ TEST_FAIL("[item.name] failed to respect force deletion [item.no_respect_force] times out of a total del count of [item.qdels]")
+ if(item.no_hint)
+ TEST_FAIL("[item.name] failed to return a qdel hint [item.no_hint] times out of a total del count of [item.qdels]")
+
+ cache_for_sonic_speed = SSatoms.BadInitializeCalls
+ for(var/path in cache_for_sonic_speed)
+ var/fails = cache_for_sonic_speed[path]
+ if(fails & BAD_INIT_NO_HINT)
+ TEST_FAIL("[path] didn't return an Initialize hint")
+ if(fails & BAD_INIT_QDEL_BEFORE)
+ TEST_FAIL("[path] qdel'd in New()")
+ if(fails & BAD_INIT_SLEPT)
+ TEST_FAIL("[path] slept during Initialize()")
+
+ SSticker.delay_end = FALSE
+ //This shouldn't be needed, but let's be polite
+ SSgarbage.collection_timeout[GC_QUEUE_HARDDELETE] = 10 SECONDS
diff --git a/code/modules/unit_tests/dynamic_ruleset_sanity.dm b/code/modules/unit_tests/dynamic_ruleset_sanity.dm
index 837e0b235cca..8ec500e1a82a 100644
--- a/code/modules/unit_tests/dynamic_ruleset_sanity.dm
+++ b/code/modules/unit_tests/dynamic_ruleset_sanity.dm
@@ -9,9 +9,9 @@
var/is_lone = initial(ruleset.flags) & (LONE_RULESET | HIGH_IMPACT_RULESET)
if (has_scaling_cost && is_lone)
- Fail("[ruleset] has a scaling_cost, but is also a lone/highlander ruleset.")
+ TEST_FAIL("[ruleset] has a scaling_cost, but is also a lone/highlander ruleset.")
else if (!has_scaling_cost && !is_lone)
- Fail("[ruleset] has no scaling cost, but is also not a lone/highlander ruleset.")
+ TEST_FAIL("[ruleset] has no scaling cost, but is also not a lone/highlander ruleset.")
/// Verifies that dynamic rulesets have unique antag_flag.
/datum/unit_test/dynamic_unique_antag_flags
@@ -26,11 +26,11 @@
var/antag_flag = initial(ruleset.antag_flag)
if (isnull(antag_flag))
- Fail("[ruleset] has a null antag_flag!")
+ TEST_FAIL("[ruleset] has a null antag_flag!")
continue
if (antag_flag in known_antag_flags)
- Fail("[ruleset] has a non-unique antag_flag [antag_flag] (used by [known_antag_flags[antag_flag]])!")
+ TEST_FAIL("[ruleset] has a non-unique antag_flag [antag_flag] (used by [known_antag_flags[antag_flag]])!")
continue
known_antag_flags[antag_flag] = ruleset
diff --git a/code/modules/unit_tests/find_reference_sanity.dm b/code/modules/unit_tests/find_reference_sanity.dm
index f41714f0659d..1dcabc7bacb2 100644
--- a/code/modules/unit_tests/find_reference_sanity.dm
+++ b/code/modules/unit_tests/find_reference_sanity.dm
@@ -2,12 +2,14 @@
/datum/unit_test/find_reference_sanity
/atom/movable/ref_holder
+ var/static/atom/movable/ref_test/static_test
var/atom/movable/ref_test/test
var/list/test_list = list()
var/list/test_assoc_list = list()
/atom/movable/ref_holder/Destroy()
test = null
+ static_test = null
test_list.Cut()
test_assoc_list.Cut()
return ..()
@@ -25,6 +27,12 @@
SSgarbage.should_save_refs = TRUE
//Sanity check
+ /*
+ #if DM_VERSION >= 515
+ var/refcount = refcount(victim)
+ TEST_ASSERT_EQUAL(refcount, 3, "Should be: test references: 0 + baseline references: 3 (victim var,loc,allocated list)")
+ #endif
+ */
victim.DoSearchVar(testbed, "Sanity Check", search_time = 1) //We increment search time to get around an optimization
TEST_ASSERT(!victim.found_refs.len, "The ref-tracking tool found a ref where none existed")
SSgarbage.should_save_refs = FALSE
@@ -39,6 +47,12 @@
testbed.test_list += victim
testbed.test_assoc_list["baseline"] = victim
+ /*
+ #if DM_VERSION >= 515
+ var/refcount = refcount(victim)
+ TEST_ASSERT_EQUAL(refcount, 6, "Should be: test references: 3 + baseline references: 3 (victim var,loc,allocated list)")
+ #endif
+ */
victim.DoSearchVar(testbed, "First Run", search_time = 2)
TEST_ASSERT(victim.found_refs["test"], "The ref-tracking tool failed to find a regular value")
@@ -56,6 +70,12 @@
testbed.vis_contents += victim
testbed.test_assoc_list[victim] = TRUE
+ /*
+ #if DM_VERSION >= 515
+ var/refcount = refcount(victim)
+ TEST_ASSERT_EQUAL(refcount, 6, "Should be: test references: 3 + baseline references: 3 (victim var,loc,allocated list)")
+ #endif
+ */
victim.DoSearchVar(testbed, "Second Run", search_time = 3)
//This is another sanity check
@@ -76,6 +96,12 @@
var/list/to_find_assoc = list(victim)
testbed.test_assoc_list["Nesting"] = to_find_assoc
+ /*
+ #if DM_VERSION >= 515
+ var/refcount = refcount(victim)
+ TEST_ASSERT_EQUAL(refcount, 6, "Should be: test references: 3 + baseline references: 3 (victim var,loc,allocated list)")
+ #endif
+ */
victim.DoSearchVar(victim, "Third Run Self", search_time = 4)
victim.DoSearchVar(testbed, "Third Run Testbed", search_time = 4)
TEST_ASSERT(victim.found_refs["self_ref"], "The ref-tracking tool failed to find a self reference")
@@ -90,7 +116,12 @@
//Calm before the storm
testbed.test_assoc_list = list(null = victim)
-
+ /*
+ #if DM_VERSION >= 515
+ var/refcount = refcount(victim)
+ TEST_ASSERT_EQUAL(refcount, 4, "Should be: test references: 1 + baseline references: 3 (victim var,loc,allocated list)")
+ #endif
+ */
victim.DoSearchVar(testbed, "Fourth Run", search_time = 5)
TEST_ASSERT(testbed.test_assoc_list, "The ref-tracking tool failed to find a null key'd assoc list entry")
@@ -105,7 +136,39 @@
var/list/to_find_null_assoc_nested = list(victim)
testbed.test_assoc_list[null] = to_find_null_assoc_nested
+ /*
+ #if DM_VERSION >= 515
+ var/refcount = refcount(victim)
+ TEST_ASSERT_EQUAL(refcount, 5, "Should be: test references: 2 + baseline references: 3 (victim var,loc,allocated list)")
+ #endif
+ */
victim.DoSearchVar(testbed, "Fifth Run", search_time = 6)
TEST_ASSERT(victim.found_refs[to_find_in_key], "The ref-tracking tool failed to find a nested assoc list key")
TEST_ASSERT(victim.found_refs[to_find_null_assoc_nested], "The ref-tracking tool failed to find a null key'd nested assoc list entry")
SSgarbage.should_save_refs = FALSE
+
+/datum/unit_test/find_reference_static_inPvestigation/Run()
+ var/atom/movable/ref_test/victim = allocate(/atom/movable/ref_test)
+ var/atom/movable/ref_holder/testbed = allocate(/atom/movable/ref_holder)
+ pass(testbed)
+ SSgarbage.should_save_refs = TRUE
+
+ //Lets check static vars now, since those can be a real headache
+ testbed.static_test = victim
+
+ //Yes we do actually need to do this. The searcher refuses to read weird lists
+ //And global.vars is a really weird list
+ var/global_vars = list()
+ for(var/key in global.vars)
+ global_vars[key] = global.vars[key]
+
+ /*
+ #if DM_VERSION >= 515
+ var/refcount = refcount(victim)
+ TEST_ASSERT_EQUAL(refcount, 5, "Should be: test references: 2 + baseline references: 3 (victim var,loc,allocated list)")
+ #endif
+ */
+ victim.DoSearchVar(global_vars, "Sixth Run", search_time = 7)
+
+ TEST_ASSERT(victim.found_refs[global_vars], "The ref-tracking tool failed to find a natively global variable")
+ SSgarbage.should_save_refs = FALSE
diff --git a/code/modules/unit_tests/heretic_knowledge.dm b/code/modules/unit_tests/heretic_knowledge.dm
index a433bce1ec99..484cc90245c0 100644
--- a/code/modules/unit_tests/heretic_knowledge.dm
+++ b/code/modules/unit_tests/heretic_knowledge.dm
@@ -18,4 +18,4 @@
var/list/unreachables = all_possible_knowledge - list_to_check
for(var/X in unreachables)
var/datum/eldritch_knowledge/eldritch_knowledge = X
- Fail("[initial(eldritch_knowledge.name)] is unreachable by players! Add it to the blacklist in /code/modules/unit_tests/heretic_knowledge.dm if it is purposeful!")
+ TEST_FAIL("[initial(eldritch_knowledge.name)] is unreachable by players! Add it to the blacklist in /code/modules/unit_tests/heretic_knowledge.dm if it is purposeful!")
diff --git a/code/modules/unit_tests/keybinding_init.dm b/code/modules/unit_tests/keybinding_init.dm
index 16141bc55380..c9d17f688afd 100644
--- a/code/modules/unit_tests/keybinding_init.dm
+++ b/code/modules/unit_tests/keybinding_init.dm
@@ -3,4 +3,4 @@
var/datum/keybinding/KB = i
if(initial(KB.keybind_signal) || !initial(KB.name))
continue
- Fail("[initial(KB.name)] does not have a keybind signal defined.")
+ TEST_FAIL("[KB.name] does not have a keybind signal defined.")
diff --git a/code/modules/unit_tests/merge_type.dm b/code/modules/unit_tests/merge_type.dm
index 373ffa2b66f2..e1683a28396b 100644
--- a/code/modules/unit_tests/merge_type.dm
+++ b/code/modules/unit_tests/merge_type.dm
@@ -14,4 +14,4 @@
continue
var/obj/item/stack/stack = new stackpath
if(!stack.merge_type)
- Fail("([stack]) lacks set merge_type variable!")
+ TEST_FAIL("([stack]) lacks set merge_type variable!")
diff --git a/code/modules/unit_tests/outfit_sanity.dm b/code/modules/unit_tests/outfit_sanity.dm
index 57ce22434e11..f6a5d1d79ae4 100644
--- a/code/modules/unit_tests/outfit_sanity.dm
+++ b/code/modules/unit_tests/outfit_sanity.dm
@@ -2,7 +2,7 @@
H.equip_to_slot_or_del(new outfit.##outfit_key(H), ##slot_name, TRUE); \
/* We don't check the result of equip_to_slot_or_del because it returns false for random jumpsuits, as they delete themselves on init */ \
if (!H.get_item_by_slot(##slot_name)) { \
- Fail("[outfit.name]'s [#outfit_key] is invalid!"); \
+ TEST_FAIL("[outfit.name]'s [#outfit_key] is invalid!"); \
} \
}
@@ -45,6 +45,6 @@
var/number = backpack_contents[path] || 1
for (var/_ in 1 to number)
if (!H.equip_to_slot_or_del(new path(H), ITEM_SLOT_BACKPACK, TRUE))
- Fail("[outfit.name]'s backpack_contents are invalid! Couldn't add [path] to backpack.")
+ TEST_FAIL("[outfit.name]'s backpack_contents are invalid! Couldn't add [path] to backpack.")
#undef CHECK_OUTFIT_SLOT
diff --git a/code/modules/unit_tests/plantgrowth_tests.dm b/code/modules/unit_tests/plantgrowth_tests.dm
index 6b40236860ef..b1b213f03489 100644
--- a/code/modules/unit_tests/plantgrowth_tests.dm
+++ b/code/modules/unit_tests/plantgrowth_tests.dm
@@ -17,11 +17,11 @@
for(var/i in 1 to seed.growthstages)
if("[seed.icon_grow][i]" in states)
continue
- Fail("[seed.name] ([seed.type]) lacks the [seed.icon_grow][i] icon!")
+ TEST_FAIL("[seed.name] ([seed.type]) lacks the [seed.icon_grow][i] icon!")
if(!(seed.icon_dead in states))
- Fail("[seed.name] ([seed.type]) lacks the [seed.icon_dead] icon!")
+ TEST_FAIL("[seed.name] ([seed.type]) lacks the [seed.icon_dead] icon!")
if(seed.icon_harvest) // mushrooms have no grown sprites, same for items with no product
if(!(seed.icon_harvest in states))
- Fail("[seed.name] ([seed.type]) lacks the [seed.icon_harvest] icon!")
+ TEST_FAIL("[seed.name] ([seed.type]) lacks the [seed.icon_harvest] icon!")
diff --git a/code/modules/unit_tests/projectiles.dm b/code/modules/unit_tests/projectiles.dm
index f1a2391c0701..ddc7979d3df7 100644
--- a/code/modules/unit_tests/projectiles.dm
+++ b/code/modules/unit_tests/projectiles.dm
@@ -2,4 +2,4 @@
for(var/path in typesof(/obj/item/projectile))
var/obj/item/projectile/projectile = path
if(initial(projectile.movement_type) & PHASING)
- Fail("[path] has default movement type PHASING. Piercing projectiles should be done using the projectile piercing system, not movement_types!")
+ TEST_FAIL("[path] has default movement type PHASING. Piercing projectiles should be done using the projectile piercing system, not movement_types!")
diff --git a/code/modules/unit_tests/reactions.dm b/code/modules/unit_tests/reactions.dm
index c2b62f6fdcd1..596e9eca8d75 100644
--- a/code/modules/unit_tests/reactions.dm
+++ b/code/modules/unit_tests/reactions.dm
@@ -4,4 +4,4 @@
var/test_info = G.test()
if(!test_info["success"])
var/message = test_info["message"]
- Fail("Gas reaction [G.name] is failing its unit test with the following message: [message]")
+ TEST_FAIL("Gas reaction [G.name] is failing its unit test with the following message: [message]")
diff --git a/code/modules/unit_tests/reagent_id_typos.dm b/code/modules/unit_tests/reagent_id_typos.dm
index d6548852fa52..f85834999962 100644
--- a/code/modules/unit_tests/reagent_id_typos.dm
+++ b/code/modules/unit_tests/reagent_id_typos.dm
@@ -11,4 +11,4 @@
var/datum/chemical_reaction/R = V
for(var/id in (R.required_reagents + R.required_catalysts))
if(!GLOB.chemical_reagents_list[id])
- Fail("Unknown chemical id \"[id]\" in recipe [R.type]")
+ TEST_FAIL("Unknown chemical id \"[id]\" in recipe [R.type]")
diff --git a/code/modules/unit_tests/reagent_recipe_collisions.dm b/code/modules/unit_tests/reagent_recipe_collisions.dm
index 20e875422f29..b75a17a7e73c 100644
--- a/code/modules/unit_tests/reagent_recipe_collisions.dm
+++ b/code/modules/unit_tests/reagent_recipe_collisions.dm
@@ -12,4 +12,4 @@
var/datum/chemical_reaction/r1 = reactions[i]
var/datum/chemical_reaction/r2 = reactions[i2]
if(chem_recipes_do_conflict(r1, r2))
- Fail("Chemical recipe conflict between [r1.type] and [r2.type]")
+ TEST_FAIL("Chemical recipe conflict between [r1.type] and [r2.type]")
diff --git a/code/modules/unit_tests/species_whitelists.dm b/code/modules/unit_tests/species_whitelists.dm
index 145f3a259fc2..ec05d0cf9f8f 100644
--- a/code/modules/unit_tests/species_whitelists.dm
+++ b/code/modules/unit_tests/species_whitelists.dm
@@ -2,4 +2,4 @@
for(var/typepath in subtypesof(/datum/species))
var/datum/species/S = typepath
if(initial(S.changesource_flags) == NONE)
- Fail("A species type was detected with no changesource flags: [S]")
+ TEST_FAIL("A species type was detected with no changesource flags: [S]")
diff --git a/code/modules/unit_tests/subsystem_init.dm b/code/modules/unit_tests/subsystem_init.dm
index 7d5473bc1bb7..c377302ba6a1 100644
--- a/code/modules/unit_tests/subsystem_init.dm
+++ b/code/modules/unit_tests/subsystem_init.dm
@@ -4,4 +4,4 @@
if(ss.flags & SS_NO_INIT)
continue
if(!ss.initialized)
- Fail("[ss]([ss.type]) is a subsystem meant to initialize but doesn't get set as initialized.")
+ TEST_FAIL("[ss]([ss.type]) is a subsystem meant to initialize but doesn't get set as initialized.")
diff --git a/code/modules/unit_tests/timer_sanity.dm b/code/modules/unit_tests/timer_sanity.dm
index d92323a5253f..dbdf3f6d8e8d 100644
--- a/code/modules/unit_tests/timer_sanity.dm
+++ b/code/modules/unit_tests/timer_sanity.dm
@@ -1,3 +1,3 @@
/datum/unit_test/timer_sanity/Run()
- if(SStimer.bucket_count < 0)
- Fail("SStimer is going into negative bucket count from something")
+ TEST_ASSERT(SStimer.bucket_count >= 0,
+ "SStimer is going into negative bucket count from something")
diff --git a/code/modules/unit_tests/unit_test.dm b/code/modules/unit_tests/unit_test.dm
index 378c6b868929..86fab0196595 100644
--- a/code/modules/unit_tests/unit_test.dm
+++ b/code/modules/unit_tests/unit_test.dm
@@ -3,7 +3,7 @@
Usage:
Override /Run() to run your test code
-Call Fail() to fail the test (You should specify a reason)
+Call TEST_FAIL() to fail the test (You should specify a reason)
You may use /New() and /Destroy() for setup/teardown respectively
@@ -15,6 +15,18 @@ GLOBAL_DATUM(current_test, /datum/unit_test)
GLOBAL_VAR_INIT(failed_any_test, FALSE)
GLOBAL_VAR(test_log)
+/// A list of every test that is currently focused.
+/// Use the PERFORM_ALL_TESTS macro instead.
+GLOBAL_VAR_INIT(focused_tests, focused_tests())
+
+/proc/focused_tests()
+ var/list/focused_tests = list()
+ for (var/datum/unit_test/unit_test as anything in subtypesof(/datum/unit_test))
+ if (initial(unit_test.focus))
+ focused_tests += unit_test
+
+ return focused_tests.len > 0 ? focused_tests : null
+
/datum/unit_test
//Bit of metadata for the future maybe
var/list/procs_tested
@@ -62,15 +74,15 @@ GLOBAL_VAR(test_log)
return ..()
/datum/unit_test/proc/Run()
- Fail("Run() called parent or not implemented")
+ TEST_FAIL("Run() called parent or not implemented")
-/datum/unit_test/proc/Fail(reason = "No reason")
+/datum/unit_test/proc/Fail(reason = "No reason", file = "OUTDATED_TEST", line = 1)
succeeded = FALSE
if(!istext(reason))
reason = "FORMATTED: [reason != null ? reason : "NULL"]"
- LAZYADD(fail_reasons, reason)
+ LAZYADD(fail_reasons, list(list(reason, file, line)))
/// Allocates an instance of the provided type, and places it somewhere in an available loc
/// Instances allocated through this proc will be destroyed when the test is over
@@ -80,16 +92,71 @@ GLOBAL_VAR(test_log)
arguments = list(run_loc_floor_bottom_left)
else if (arguments[1] == null)
arguments[1] = run_loc_floor_bottom_left
- var/instance = new type(arglist(arguments))
+ var/instance
+ // Byond will throw an index out of bounds if arguments is empty in that arglist call. Sigh
+ if(length(arguments))
+ instance = new type(arglist(arguments))
+ else
+ instance = new type()
allocated += instance
return instance
+/*
+/datum/unit_test/proc/test_screenshot(name, icon/icon)
+ if (!istype(icon))
+ TEST_FAIL("[icon] is not an icon.")
+ return
+
+ var/path_prefix = replacetext(replacetext("[type]", "/datum/unit_test/", ""), "/", "_")
+ name = replacetext(name, "/", "_")
+
+ var/filename = "code/modules/unit_tests/screenshots/[path_prefix]_[name].png"
+
+ if (fexists(filename))
+ var/data_filename = "data/screenshots/[path_prefix]_[name].png"
+ fcopy(icon, data_filename)
+ log_test("\t[path_prefix]_[name] was found, putting in data/screenshots")
+ else if (fexists("code"))
+ // We are probably running in a local build
+ fcopy(icon, filename)
+ TEST_FAIL("Screenshot for [name] did not exist. One has been created.")
+ else
+ // We are probably running in real CI, so just pretend it worked and move on
+ fcopy(icon, "data/screenshots_new/[path_prefix]_[name].png")
+
+ log_test("\t[path_prefix]_[name] was put in data/screenshots_new")
+
+/// Helper for screenshot tests to take an image of an atom from all directions and insert it into one icon
+/datum/unit_test/proc/get_flat_icon_for_all_directions(atom/thing, no_anim = TRUE)
+ var/icon/output = icon('icons/effects/effects.dmi', "nothing")
+
+ for (var/direction in GLOB.cardinals)
+ var/icon/partial = getFlatIcon(thing, defdir = direction, no_anim = no_anim)
+ output.Insert(partial, dir = direction)
+
+ return output
+*/
+/// Logs a test message. Will use GitHub action syntax found at https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions
+/datum/unit_test/proc/log_for_test(text, priority, file, line)
+ var/map_name = SSmapping.config.map_name
+
+ // Need to escape the text to properly support newlines.
+ var/annotation_text = replacetext(text, "%", "%25")
+ annotation_text = replacetext(annotation_text, "\n", "%0A")
+
+ log_world("::[priority] file=[file],line=[line],title=[map_name]: [type]::[annotation_text]")
+
/proc/RunUnitTest(test_path, list/test_results)
+/*
+ if (ispath(test_path, /datum/unit_test/focus_only))
+ return
+*/
var/datum/unit_test/test = new test_path
GLOB.current_test = test
var/duration = REALTIMEOFDAY
+ log_world("::group::[test_path]")
test.Run()
duration = REALTIMEOFDAY - duration
@@ -99,11 +166,28 @@ GLOBAL_VAR(test_log)
var/list/log_entry = list("[test.succeeded ? "PASS" : "FAIL"]: [test_path] [duration / 10]s")
var/list/fail_reasons = test.fail_reasons
- for(var/J in 1 to LAZYLEN(fail_reasons))
- log_entry += "\tREASON #[J]: [fail_reasons[J]]"
+ for(var/reasonID in 1 to LAZYLEN(fail_reasons))
+ var/text = fail_reasons[reasonID][1]
+ var/file = fail_reasons[reasonID][2]
+ var/line = fail_reasons[reasonID][3]
+
+ test.log_for_test(text, "error", file, line)
+
+ // Normal log message
+ log_entry += "\tREASON #[reasonID]: [text] at [file]:[line]"
+
var/message = log_entry.Join("\n")
log_test(message)
+ var/test_output_desc = "[test_path] [duration / 10]s"
+ if (test.succeeded)
+ log_world("[TEST_OUTPUT_GREEN("PASS")] [test_output_desc]")
+
+ log_world("::endgroup::")
+
+ if (!test.succeeded)
+ log_world("::error::[TEST_OUTPUT_RED("FAIL")] [test_output_desc]")
+
test_results[test_path] = list("status" = test.succeeded ? UNIT_TEST_PASSED : UNIT_TEST_FAILED, "message" = message, "name" = test_path)
qdel(test)
@@ -112,11 +196,13 @@ GLOBAL_VAR(test_log)
CHECK_TICK
var/list/tests_to_run = subtypesof(/datum/unit_test)
+ var/list/focused_tests = list()
for (var/_test_to_run in tests_to_run)
var/datum/unit_test/test_to_run = _test_to_run
if (initial(test_to_run.focus))
- tests_to_run = list(test_to_run)
- break
+ focused_tests += test_to_run
+ if(length(focused_tests))
+ tests_to_run = focused_tests
tests_to_run = sortTim(tests_to_run, GLOBAL_PROC_REF(cmp_unit_test_priority))
diff --git a/code/modules/unit_tests/vore_tests.dm b/code/modules/unit_tests/vore_tests.dm
index 65a8ad94db18..465859acc25f 100644
--- a/code/modules/unit_tests/vore_tests.dm
+++ b/code/modules/unit_tests/vore_tests.dm
@@ -11,7 +11,7 @@
break
mobloc = default_mobloc
if(!mobloc)
- Fail("Unable to find a location to create test mob")
+ TEST_FAIL("Unable to find a location to create test mob")
return FALSE
var/mob/living/carbon/human/H = new mobtype(mobloc)
@@ -44,7 +44,7 @@
endOxyloss = H.getOxyLoss()
if(!startOxyloss < endOxyloss)
- Fail("Human mob is not taking oxygen damage in space. (Before: [startOxyloss]; after: [endOxyloss])")
+ TEST_FAIL("Human mob is not taking oxygen damage in space. (Before: [startOxyloss]; after: [endOxyloss])")
qdel(H)
return TRUE
@@ -74,7 +74,7 @@
// Now that pred belly exists, we can eat the prey.
if(!pred.vore_selected)
- Fail("[pred] has no vore_selected.")
+ TEST_FAIL("[pred] has no vore_selected.")
return TRUE
// Attempt to eat the prey
@@ -82,7 +82,7 @@
pred.vore_selected.nom_mob(prey)
if(prey.loc != pred.vore_selected)
- Fail("[pred.vore_selected].nom_mob([prey]) did not put prey inside [pred]")
+ TEST_FAIL("[pred.vore_selected].nom_mob([prey]) did not put prey inside [pred]")
return TRUE
// Okay, we succeeded in eating them, now lets wait a bit
@@ -96,7 +96,7 @@
// Alright lets check it!
endOxyloss = prey.getOxyLoss()
if(startOxyloss < endOxyloss)
- Fail("Prey takes oxygen damage in a pred's belly! (Before: [startOxyloss]; after: [endOxyloss])")
+ TEST_FAIL("Prey takes oxygen damage in a pred's belly! (Before: [startOxyloss]; after: [endOxyloss])")
QDEL_NULL(prey)
QDEL_NULL(pred)
return TRUE
@@ -128,7 +128,7 @@
// Now that pred belly exists, we can eat the prey.
if(!pred.vore_selected)
- Fail("[pred] has no vore_selected.")
+ TEST_FAIL("[pred] has no vore_selected.")
return TRUE
// Attempt to eat the prey
@@ -136,12 +136,12 @@
pred.vore_selected.nom_mob(prey)
if(prey.loc != pred.vore_selected)
- Fail("[pred.vore_selected].nom_mob([prey]) did not put prey inside [pred]")
+ TEST_FAIL("[pred.vore_selected].nom_mob([prey]) did not put prey inside [pred]")
return TRUE
else
var/turf/T = locate(/turf/open/space)
if(!T)
- Fail("could not find a space turf for testing")
+ TEST_FAIL("could not find a space turf for testing")
return TRUE
else
pred.forceMove(T)
@@ -159,7 +159,7 @@
endOxyloss = prey.getOxyLoss()
endBruteloss = prey.getBruteLoss()
if(startBruteloss < endBruteloss)
- Fail("Prey takes brute damage in space! (Before: [startBruteloss]; after: [endBruteloss])")
+ TEST_FAIL("Prey takes brute damage in space! (Before: [startBruteloss]; after: [endBruteloss])")
QDEL_NULL(prey)
QDEL_NULL(pred)
return TRUE
@@ -189,7 +189,7 @@
// Now that pred belly exists, we can eat the prey.
if(!pred.vore_selected)
- Fail("[pred] has no vore_selected.")
+ TEST_FAIL("[pred] has no vore_selected.")
return TRUE
// Attempt to eat the prey
@@ -197,7 +197,7 @@
pred.vore_selected.nom_mob(prey)
if(prey.loc != pred.vore_selected)
- Fail("[pred.vore_selected].nom_mob([prey]) did not put prey inside [pred]")
+ TEST_FAIL("[pred.vore_selected].nom_mob([prey]) did not put prey inside [pred]")
return TRUE
// Okay, we succeeded in eating them, now lets wait a bit
@@ -212,7 +212,7 @@
// Alright lets check it!
endBruteBurn = prey.getBruteLoss() + prey.getFireLoss()
if(startBruteBurn >= endBruteBurn)
- Fail("Prey doesn't take damage in digesting belly! (Before: [startBruteBurn]; after: [endBruteBurn])")
+ TEST_FAIL("Prey doesn't take damage in digesting belly! (Before: [startBruteBurn]; after: [endBruteBurn])")
QDEL_NULL(prey)
QDEL_NULL(pred)
return TRUE
diff --git a/code/modules/vehicles/atv.dm b/code/modules/vehicles/atv.dm
index cb32b1108386..1edb33b9e726 100644
--- a/code/modules/vehicles/atv.dm
+++ b/code/modules/vehicles/atv.dm
@@ -41,25 +41,29 @@
/obj/vehicle/ridden/atv/turret/Moved()
. = ..()
- if(turret)
- turret.forceMove(get_turf(src))
- switch(dir)
- if(NORTH)
- turret.pixel_x = 0
- turret.pixel_y = 4
- turret.layer = ABOVE_MOB_LAYER
- if(EAST)
- turret.pixel_x = -12
- turret.pixel_y = 4
- turret.layer = OBJ_LAYER
- if(SOUTH)
- turret.pixel_x = 0
- turret.pixel_y = 4
- turret.layer = OBJ_LAYER
- if(WEST)
- turret.pixel_x = 12
- turret.pixel_y = 4
- turret.layer = OBJ_LAYER
+ if(!turret)
+ return
+ var/turf/our_turf = get_turf(src)
+ if(!our_turf)
+ return
+ turret.forceMove(our_turf)
+ switch(dir)
+ if(NORTH)
+ turret.pixel_x = 0
+ turret.pixel_y = 4
+ turret.layer = ABOVE_MOB_LAYER
+ if(EAST)
+ turret.pixel_x = -12
+ turret.pixel_y = 4
+ turret.layer = OBJ_LAYER
+ if(SOUTH)
+ turret.pixel_x = 0
+ turret.pixel_y = 4
+ turret.layer = OBJ_LAYER
+ if(WEST)
+ turret.pixel_x = 12
+ turret.pixel_y = 4
+ turret.layer = OBJ_LAYER
/obj/vehicle/ridden/atv/snowmobile
name = "snowmobile"
diff --git a/code/modules/vehicles/mecha/mech_fabricator.dm b/code/modules/vehicles/mecha/mech_fabricator.dm
index 4acc0560fb84..6eda99a72343 100644
--- a/code/modules/vehicles/mecha/mech_fabricator.dm
+++ b/code/modules/vehicles/mecha/mech_fabricator.dm
@@ -72,6 +72,11 @@
RefreshParts() //Recalculating local material sizes if the fab isn't linked
return ..()
+/obj/machinery/mecha_part_fabricator/Destroy()
+ QDEL_NULL(stored_research)
+ rmat = null
+ return ..()
+
/obj/machinery/mecha_part_fabricator/RefreshParts()
var/T = 0
diff --git a/code/modules/vending/cola.dm b/code/modules/vending/cola.dm
index f60c3fc2fa64..c2f2b16fe342 100644
--- a/code/modules/vending/cola.dm
+++ b/code/modules/vending/cola.dm
@@ -37,7 +37,7 @@
desc = "Uh oh!"
/obj/machinery/vending/cola/random/Initialize(mapload)
- ..()
+ . = ..()
var/T = pick(subtypesof(/obj/machinery/vending/cola) - /obj/machinery/vending/cola/random)
new T(loc)
return INITIALIZE_HINT_QDEL
diff --git a/code/modules/vending/snack.dm b/code/modules/vending/snack.dm
index efb667049603..c85ec8a594c2 100644
--- a/code/modules/vending/snack.dm
+++ b/code/modules/vending/snack.dm
@@ -42,7 +42,7 @@
desc = "Uh oh!"
/obj/machinery/vending/snack/random/Initialize(mapload)
- ..()
+ . = ..()
var/T = pick(subtypesof(/obj/machinery/vending/snack) - /obj/machinery/vending/snack/random)
new T(loc)
return INITIALIZE_HINT_QDEL
diff --git a/modular_citadel/code/modules/clothing/neck.dm b/modular_citadel/code/modules/clothing/neck.dm
index 9507e65e0e53..9e1870307d3f 100644
--- a/modular_citadel/code/modules/clothing/neck.dm
+++ b/modular_citadel/code/modules/clothing/neck.dm
@@ -19,5 +19,5 @@
/obj/item/clothing/neck/undertale/Initialize(mapload)
- ..()
+ . = ..()
AddComponent(/datum/component/souldeath/neck)
diff --git a/modular_citadel/code/modules/eventmaps/Spookystation/JTGSZwork.dm b/modular_citadel/code/modules/eventmaps/Spookystation/JTGSZwork.dm
index 244b0be71eb4..868f5fd6b9b3 100644
--- a/modular_citadel/code/modules/eventmaps/Spookystation/JTGSZwork.dm
+++ b/modular_citadel/code/modules/eventmaps/Spookystation/JTGSZwork.dm
@@ -1031,7 +1031,7 @@ GLOBAL_LIST_EMPTY(rain_sounds)
var/open = FALSE
/obj/item/umbrella/Initialize(mapload)
- ..()
+ . = ..()
color = RANDOM_COLOUR
update_icon()
diff --git a/modular_citadel/code/modules/vectorcrafts/vectorcraft.dm b/modular_citadel/code/modules/vectorcrafts/vectorcraft.dm
index 0b393e33e11e..04c8126e0ac3 100644
--- a/modular_citadel/code/modules/vectorcrafts/vectorcraft.dm
+++ b/modular_citadel/code/modules/vectorcrafts/vectorcraft.dm
@@ -36,7 +36,7 @@
var/mob/living/carbon/human/driver
/obj/vehicle/sealed/vectorcraft/Initialize(mapload)
- ..()
+ . = ..()
i_m_acell = max_acceleration
i_m_decell = max_deceleration
i_boost = boost_power
diff --git a/tgstation.dme b/tgstation.dme
index 4d5b5f23def0..7424fde76e17 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -2828,6 +2828,7 @@
#include "code\modules\mob\living\silicon\silicon.dm"
#include "code\modules\mob\living\silicon\silicon_defense.dm"
#include "code\modules\mob\living\silicon\silicon_movement.dm"
+#include "code\modules\mob\living\silicon\ai\_preferences.dm"
#include "code\modules\mob\living\silicon\ai\ai.dm"
#include "code\modules\mob\living\silicon\ai\ai_defense.dm"
#include "code\modules\mob\living\silicon\ai\ai_portrait_picker.dm"
diff --git a/tgui/packages/tgui/interfaces/CommunicationsConsole.js b/tgui/packages/tgui/interfaces/CommunicationsConsole.js
index a1b7bb4ff365..fd8e8739c4fc 100644
--- a/tgui/packages/tgui/interfaces/CommunicationsConsole.js
+++ b/tgui/packages/tgui/interfaces/CommunicationsConsole.js
@@ -200,8 +200,8 @@ const PageChangingStatus = (props, context) => {
const { act, data } = useBackend(context);
const { maxStatusLineLength } = data;
- const [lineOne, setLineOne] = useLocalState(context, "lineOne", data.lineOne);
- const [lineTwo, setLineTwo] = useLocalState(context, "lineTwo", data.lineTwo);
+ const [upperText, setupperText] = useLocalState(context, "upperText", data.upperText);
+ const [lowerText, setlowerText] = useLocalState(context, "lowerText", data.lowerText);
return (
@@ -263,18 +263,18 @@ const PageChangingStatus = (props, context) => {
setLineOne(value)}
+ onChange={(_, value) => setupperText(value)}
/>
setLineTwo(value)}
+ onChange={(_, value) => setlowerText(value)}
/>
@@ -283,8 +283,8 @@ const PageChangingStatus = (props, context) => {
icon="comment-o"
content="Message"
onClick={() => act("setStatusMessage", {
- lineOne,
- lineTwo,
+ upperText,
+ lowerText,
})}
/>