"}
if (inputtype == "checkbox" || inputtype == "radio")
for (var/i in values)
var/div_slider = slidecolor
diff --git a/code/datums/browser/preflikepicker.dm b/code/datums/browser/preflikepicker.dm
index a0514b3864e..8f250cc6675 100644
--- a/code/datums/browser/preflikepicker.dm
+++ b/code/datums/browser/preflikepicker.dm
@@ -23,11 +23,11 @@
var/setting = settings["mainsettings"][name]
if (setting["type"] == "datum")
if (setting["subtypesonly"])
- dat += "[setting["desc"]]: [setting["value"]]
"
+ dat += "[setting["desc"]]: [setting["value"]]
"
else
- dat += "[setting["desc"]]: [setting["value"]]
"
+ dat += "[setting["desc"]]: [setting["value"]]
"
else
- dat += "[setting["desc"]]: [setting["value"]]
"
+ dat += "[setting["desc"]]: [setting["value"]]
"
if (preview_icon)
dat += ""
@@ -38,7 +38,7 @@
dat += ""
- dat += " Ok "
+ dat += " Ok "
dat += ""
diff --git a/code/datums/callback.dm b/code/datums/callback.dm
index 3918cf039a4..476b309cb05 100644
--- a/code/datums/callback.dm
+++ b/code/datums/callback.dm
@@ -5,10 +5,10 @@
* ## USAGE
*
* ```
- * var/datum/callback/C = new(object|null, /proc/type/path|"procstring", arg1, arg2, ... argn)
+ * var/datum/callback/C = new(object|null, PROC_REF(procname), arg1, arg2, ... argn)
* var/timerid = addtimer(C, time, timertype)
* you can also use the compiler define shorthand
- * var/timerid = addtimer(CALLBACK(object|null, /proc/type/path|procstring, arg1, arg2, ... argn), time, timertype)
+ * var/timerid = addtimer(CALLBACK(object|null, PROC_REF(procname), arg1, arg2, ... argn), time, timertype)
* ```
*
* Note: proc strings can only be given for datum proc calls, global procs must be proc paths
@@ -26,21 +26,19 @@
* ## PROC TYPEPATH SHORTCUTS
* (these operate on paths, not types, so to these shortcuts, datum is NOT a parent of atom, etc...)
*
- * ### global proc while in another global proc:
- * GLOBAL_PROC_REF(some_proc_here)
- *
- * `CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(some_proc_here))`
- *
- * ### proc defined on current(src) object (when in a /proc/ and not an override) OR overridden at src or any of it's parents:
- * PROC_REF(some_proc_here)
+ * ### proc defined on current(src) object OR overridden at src or any of its parents:
+ * PROC_REF(procname)
*
* `CALLBACK(src, PROC_REF(some_proc_here))`
*
- * proc defined on a parent of a some type
+ * ### global proc
+ * GLOBAL_PROC_REF(procname)
*
- * `TYPE_PROC_REF(/some/type, some_proc_here)`
+ * `CALLBACK(src, GLOBAL_PROC_REF(some_proc_here))`
*
- * Otherwise you must always provide the full typepath of the proc via TYPE_PROC_REF(/type/of/thing, procname)
+ *
+ * ### proc defined on some type
+ * TYPE_PROC_REF(/some/type/, some_proc_here)
*/
/datum/callback
@@ -62,16 +60,28 @@
* * ... an optional list of extra arguments to pass to the proc
*/
/datum/callback/New(thingtocall, proctocall, ...)
- if(thingtocall)
+ if (thingtocall)
object = thingtocall
delegate = proctocall
- if(length(args) > 2)
+ if (length(args) > 2)
arguments = args.Copy(3)
if(usr)
user = WEAKREF(usr)
-/datum/callback/proc/operator""()
- return "callback [object] ([ref(object)])[isdatum(object) ? " ([object.type])" : ""] args: \[[english_list(arguments)]\]"
+/**
+ * Qdel a callback datum
+ * This is not allowed and will stack trace. callback datums are structs, if they are referenced they exist
+ *
+ * Arguments
+ * * force set to true to force the deletion to be allowed.
+ * * ... an optional list of extra arguments to pass to the proc
+ */
+/datum/callback/Destroy(force=FALSE, ...)
+ SHOULD_CALL_PARENT(FALSE)
+ if (force)
+ return ..()
+ stack_trace("Callbacks can not be qdeleted. If they are referenced, they must exist. ([object == GLOBAL_PROC ? GLOBAL_PROC : object.type] [delegate])")
+ return QDEL_HINT_LETMELIVE
/**
* Invoke this callback
@@ -87,26 +97,116 @@
if(W)
var/mob/M = W.resolve()
if(M)
- if(length(args))
+ if (length(args))
return world.push_usr(arglist(list(M, src) + args))
return world.push_usr(M, src)
- if(!object)
+ if (!object)
return
var/list/calling_arguments = arguments
- if(length(args))
- if(length(arguments))
+ if (length(args))
+ if (length(arguments))
calling_arguments = calling_arguments + args //not += so that it creates a new list so the arguments list stays clean
else
calling_arguments = args
if(datum_flags & DF_VAR_EDITED)
- . = WrapAdminProcCall(object, delegate, calling_arguments)
- else if(object == GLOBAL_PROC)
- . = call(delegate)(arglist(calling_arguments))
- else
- . = call(object, delegate)(arglist(calling_arguments))
- pass()
+ return WrapAdminProcCall(object, delegate, calling_arguments)
+ if (object == GLOBAL_PROC)
+ return call(delegate)(arglist(calling_arguments))
+ return call(object, delegate)(arglist(calling_arguments))
+
+/**
+ * Invoke this callback async (waitfor=false)
+ *
+ * Calls the registered proc on the registered object, if the user ref
+ * can be resolved it also inclues that as an arg
+ *
+ * If the datum being called on is varedited, the call is wrapped via WrapAdminProcCall
+ */
+/datum/callback/proc/InvokeAsync(...)
+ set waitfor = FALSE
+
+ if(!usr)
+ var/datum/weakref/W = user
+ if(W)
+ var/mob/M = W.resolve()
+ if(M)
+ if (length(args))
+ return world.push_usr(arglist(list(M, src) + args))
+ return world.push_usr(M, src)
+
+ if (!object)
+ return
+
+ var/list/calling_arguments = arguments
+ if (length(args))
+ if (length(arguments))
+ calling_arguments = calling_arguments + args //not += so that it creates a new list so the arguments list stays clean
+ else
+ calling_arguments = args
+ if(datum_flags & DF_VAR_EDITED)
+ return WrapAdminProcCall(object, delegate, calling_arguments)
+ if (object == GLOBAL_PROC)
+ return call(delegate)(arglist(calling_arguments))
+ return call(object, delegate)(arglist(calling_arguments))
+
+/**
+ Helper datum for the select callbacks proc
+ */
+/datum/callback_select
+ var/list/finished
+ var/pendingcount
+ var/total
+
+/datum/callback_select/New(count, savereturns)
+ total = count
+ if (savereturns)
+ finished = new(count)
+
+
+/datum/callback_select/proc/invoke_callback(index, datum/callback/callback, list/callback_args, savereturn = TRUE)
+ set waitfor = FALSE
+ if (!callback || !istype(callback))
+ //This check only exists because the alternative is callback_select would block forever if given invalid data
+ CRASH("invalid callback passed to invoke_callback")
+ if (!length(callback_args))
+ callback_args = list()
+ pendingcount++
+ var/rtn = callback.Invoke(arglist(callback_args))
+ pendingcount--
+ if (savereturn)
+ finished[index] = rtn
+
+/**
+ * Runs a list of callbacks asyncronously, returning only when all have finished
+ *
+ * Callbacks can be repeated, to call it multiple times
+ *
+ * Arguments:
+ * * list/callbacks the list of callbacks to be called
+ * * list/callback_args the list of lists of arguments to pass into each callback
+ * * savereturns Optionally save and return the list of returned values from each of the callbacks
+ * * resolution The number of byond ticks between each time you check if all callbacks are complete
+ */
+/proc/callback_select(list/callbacks, list/callback_args, savereturns = TRUE, resolution = 1)
+ if (!callbacks)
+ return
+ var/count = length(callbacks)
+ if (!count)
+ return
+ if (!callback_args)
+ callback_args = list()
+
+ callback_args.len = count
+
+ var/datum/callback_select/CS = new(count, savereturns)
+ for (var/i in 1 to count)
+ CS.invoke_callback(i, callbacks[i], callback_args[i], savereturns)
+
+ while(CS.pendingcount)
+ sleep(resolution*world.tick_lag)
+ return CS.finished
/**
* Invoke this callback and crash if it sleeps.
@@ -133,105 +233,3 @@
/datum/callback/proc/invoke_no_sleep_call(...)
set waitfor = FALSE
. = Invoke(arglist(args))
-
-/**
- * Invoke this callback async (waitfor=false)
- *
- * Calls the registered proc on the registered object, if the user ref
- * can be resolved it also inclues that as an arg
- *
- * If the datum being called on is varedited, the call is wrapped via WrapAdminProcCall
- */
-/datum/callback/proc/InvokeAsync(...)
- set waitfor = FALSE
-
- if(!usr)
- var/datum/weakref/W = user
- if(W)
- var/mob/M = W.resolve()
- if(M)
- if(length(args))
- return world.push_usr(arglist(list(M, src) + args))
- return world.push_usr(M, src)
-
- if(!object)
- return
-
- var/list/calling_arguments = arguments
- if(length(args))
- if(length(arguments))
- calling_arguments = calling_arguments + args //not += so that it creates a new list so the arguments list stays clean
- else
- calling_arguments = args
- if(datum_flags & DF_VAR_EDITED)
- return WrapAdminProcCall(object, delegate, calling_arguments)
- if(object == GLOBAL_PROC)
- return call(delegate)(arglist(calling_arguments))
- return call(object, delegate)(arglist(calling_arguments))
-
-/**
- Helper datum for the select callbacks proc
- */
-/datum/callback_select
- var/list/finished
- var/pendingcount
- var/total
-
-/datum/callback_select/New(count, savereturns)
- total = count
- if(savereturns)
- finished = new(count)
-
-
-/datum/callback_select/proc/invoke_callback(index, datum/callback/callback, list/callback_args, savereturn = TRUE)
- set waitfor = FALSE
- if(!callback || !istype(callback))
- //This check only exists because the alternative is callback_select would block forever if given invalid data
- CRASH("invalid callback passed to invoke_callback")
- if(!length(callback_args))
- callback_args = list()
- pendingcount++
- var/rtn = callback.Invoke(arglist(callback_args))
- pendingcount--
- if(savereturn)
- finished[index] = rtn
-
-/**
- * Runs a list of callbacks asyncronously, returning only when all have finished
- *
- * Callbacks can be repeated, to call it multiple times
- *
- * Arguments:
- * * list/callbacks the list of callbacks to be called
- * * list/callback_args the list of lists of arguments to pass into each callback
- * * savereturns Optionally save and return the list of returned values from each of the callbacks
- * * resolution The number of byond ticks between each time you check if all callbacks are complete
- */
-/proc/callback_select(list/callbacks, list/callback_args, savereturns = TRUE, resolution = 1)
- if(!callbacks)
- return
- var/count = length(callbacks)
- if(!count)
- return
- if(!callback_args)
- callback_args = list()
-
- callback_args.len = count
-
- var/datum/callback_select/CS = new(count, savereturns)
- for(var/i in 1 to count)
- CS.invoke_callback(i, callbacks[i], callback_args[i], savereturns)
-
- while(CS.pendingcount)
- sleep(resolution*world.tick_lag)
- return CS.finished
-
-///Makes a call in the context of a different usr. Use sparingly
-/world/proc/push_usr(mob/user_mob, datum/callback/invoked_callback, ...)
- var/temp = usr
- usr = user_mob
- if (length(args) > 2)
- . = invoked_callback.Invoke(arglist(args.Copy(3)))
- else
- . = invoked_callback.Invoke()
- usr = temp
diff --git a/code/datums/components/_component.dm b/code/datums/components/_component.dm
index ec9b3e6a4a3..7112774d1ca 100644
--- a/code/datums/components/_component.dm
+++ b/code/datums/components/_component.dm
@@ -158,7 +158,7 @@
* This sets up a listening relationship such that when the target object emits a signal
* the source datum this proc is called upon, will receive a callback to the given proctype
* Use PROC_REF(procname), TYPE_PROC_REF(type,procname) or GLOBAL_PROC_REF(procname) macros to validate the passed in proc at compile time.
- * PROC_REF for procs defined on current type or it's ancestors, TYPE_PROC_REF for procs defined on unrelated type and GLOBAL_PROC_REF for global procs.
+ * PROC_REF for procs defined on current type or its ancestors, TYPE_PROC_REF for procs defined on unrelated type and GLOBAL_PROC_REF for global procs.
* Return values from procs registered must be a bitfield
*
* Arguments:
@@ -187,21 +187,23 @@
var/list/target_procs = (procs[target] ||= list())
var/list/lookup = (target.comp_lookup ||= list())
- if(!override && target_procs[signal_type])
- var/override_message = "[signal_type] overridden. Use override = TRUE to suppress this warning.\nTarget: [target] ([target.type]) Proc: [proctype]"
- stack_trace(override_message)
-
+ var/exists = target_procs[signal_type]
target_procs[signal_type] = proctype
+
+ if(exists)
+ if(!override)
+ var/override_message = "[signal_type] overridden. Use override = TRUE to suppress this warning.\nTarget: [target] ([target.type]) Existing Proc: [exists] New Proc: [proctype]"
+ stack_trace(override_message)
+ return
+
var/list/looked_up = lookup[signal_type]
if(isnull(looked_up)) // Nothing has registered here yet
lookup[signal_type] = src
- else if(looked_up == src) // We already registered here
- return
- else if(!length(looked_up)) // One other thing registered here
- lookup[signal_type] = list((looked_up) = TRUE, (src) = TRUE)
+ else if(!islist(looked_up)) // One other thing registered here
+ lookup[signal_type] = list(looked_up, src)
else // Many other things have registered here
- looked_up[src] = TRUE
+ looked_up += src
/// Registers multiple signals to the same proc.
/datum/proc/RegisterSignals(datum/target, list/signal_types, proctype, override = FALSE)
@@ -326,7 +328,7 @@
/**
* Internal proc to handle most all of the signaling procedure
*
- * Will runtime if used on datums with an empty component list
+ * Will runtime if used on datums with an empty lookup list
*
* Use the [SEND_SIGNAL] define instead
*/
@@ -340,10 +342,12 @@
// all the objects that are receiving the signal get the signal this final time.
// AKA: No you can't cancel the signal reception of another object by doing an unregister in the same signal.
var/list/queued_calls = list()
- for(var/datum/listening_datum as anything in target)
- queued_calls[listening_datum] = listening_datum.signal_procs[src][sigtype]
- for(var/datum/listening_datum as anything in queued_calls)
- . |= call(listening_datum, queued_calls[listening_datum])(arglist(arguments))
+ // This should be faster than doing `var/datum/listening_datum as anything in target` as it does not implicitly copy the list
+ for(var/i in 1 to length(target))
+ var/datum/listening_datum = target[i]
+ queued_calls.Add(listening_datum, listening_datum.signal_procs[src][sigtype])
+ for(var/i in 1 to length(queued_calls) step 2)
+ . |= call(queued_calls[i], queued_calls[i + 1])(arglist(arguments))
/**
* Return any component assigned to this datum of the given registered component type
diff --git a/code/datums/progressbar.dm b/code/datums/progressbar.dm
index 5db22df8352..3405a5ffb42 100644
--- a/code/datums/progressbar.dm
+++ b/code/datums/progressbar.dm
@@ -1,51 +1,149 @@
+#define PROGRESSBAR_HEIGHT 6
+#define PROGRESSBAR_ANIMATION_TIME 5
+
/proc/create_actor_progress_bar(datum/event_args/actor/e_args, goal_number, atom/target)
// todo: also show initiator the progress bar
return new /datum/progressbar(e_args.performer, goal_number, target)
/datum/progressbar
- var/goal = 1
+ ///The progress bar visual element.
var/image/bar
- var/shown = 0
+ ///The target where this progress bar is applied and where it is shown.
+ var/atom/bar_loc
+ ///The mob whose client sees the progress bar.
var/mob/user
- var/client/client
+ ///The client seeing the progress bar.
+ var/client/user_client
+ ///Effectively the number of steps the progress bar will need to do before reaching completion.
+ var/goal = 1
+ ///Control check to see if the progress was interrupted before reaching its goal.
+ var/last_progress = 0
+ ///Variable to ensure smooth visual stacking on multiple progress bars.
+ var/listindex = 0
+ ///The type of our last value for bar_loc, for debugging
+ var/location_type
+ ///Where to draw the progress bar above the icon
+ var/offset_y
-/datum/progressbar/New(mob/user, goal_number, atom/target)
+/datum/progressbar/New(mob/User, goal_number, atom/target)
. = ..()
+
if(!target)
- target = user
+ target = User
+
if (!istype(target))
- EXCEPTION("Invalid target given")
- if (goal_number)
- goal = goal_number
- bar = image('icons/effects/progessbar.dmi', target, "prog_bar_0")
+ stack_trace("Invalid target [target] passed in")
+ qdel(src)
+ return
+ if(QDELETED(User) || !istype(User))
+ stack_trace("/datum/progressbar created with [isnull(User) ? "null" : "invalid"] user")
+ qdel(src)
+ return
+ if(!isnum(goal_number))
+ stack_trace("/datum/progressbar created with [isnull(goal_number) ? "null" : "invalid"] goal_number")
+ qdel(src)
+ return
+ goal = goal_number
+ bar_loc = target
+ location_type = bar_loc.type
+
+ var/list/icon_offsets = target.get_oversized_icon_offsets()
+ var/offset_x = icon_offsets["x"]
+ offset_y = icon_offsets["y"]
+
+ bar = image('icons/effects/progressbar.dmi', bar_loc, "prog_bar_0", pixel_x = offset_x)
+ bar.plane = ABOVE_HUD_PLANE
bar.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
- bar.pixel_y = 32
- bar.plane = HUD_PLANE
- src.user = user
- if(user)
- client = user.client
+ user = User
+
+ LAZYADDASSOCLIST(user.progressbars, bar_loc, src)
+ var/list/bars = user.progressbars[bar_loc]
+ listindex = bars.len
+
+ if(user.client)
+ user_client = user.client
+ add_prog_bar_image_to_client()
+
+ RegisterSignal(user, COMSIG_PARENT_QDELETING, PROC_REF(on_user_delete))
+ RegisterSignal(user, COMSIG_MOB_CLIENT_LOGOUT, PROC_REF(clean_user_client))
+ RegisterSignal(user, COMSIG_MOB_CLIENT_LOGIN, PROC_REF(on_user_login))
/datum/progressbar/Destroy()
- if (client)
- client.images -= bar
- QDEL_NULL(bar)
- user = null
- client = null
+ if(user)
+ for(var/pb in user.progressbars[bar_loc])
+ var/datum/progressbar/progress_bar = pb
+ if(progress_bar == src || progress_bar.listindex <= listindex)
+ continue
+ progress_bar.listindex--
+
+ progress_bar.bar.pixel_y = 32 + offset_y + (PROGRESSBAR_HEIGHT * (progress_bar.listindex - 1))
+ var/dist_to_travel = 32 + offset_y + (PROGRESSBAR_HEIGHT * (progress_bar.listindex - 1)) - PROGRESSBAR_HEIGHT
+ animate(progress_bar.bar, pixel_y = dist_to_travel, time = PROGRESSBAR_ANIMATION_TIME, easing = SINE_EASING)
+
+ LAZYREMOVEASSOC(user.progressbars, bar_loc, src)
+ user = null
+
+ if(user_client)
+ clean_user_client()
+
+ bar_loc = null
+ bar = null
+
return ..()
-/datum/progressbar/proc/update(progress)
- //to_chat(world, "Update [progress] - [goal] - [(progress / goal)] - [((progress / goal) * 100)] - [round(((progress / goal) * 100), 5)]")
- if (!user || !user.client)
- shown = 0
- return
- if (user.client != client)
- if (client)
- client.images -= bar
- shown = 0
- client = user.client
+///Called right before the user's Destroy()
+/datum/progressbar/proc/on_user_delete(datum/source)
+ SIGNAL_HANDLER
+ user.progressbars = null //We can simply nuke the list and stop worrying about updating other prog bars if the user itself is gone.
+ user = null
+ qdel(src)
+
+///Removes the progress bar image from the user_client and nulls the variable, if it exists.
+/datum/progressbar/proc/clean_user_client(datum/source)
+ SIGNAL_HANDLER
+
+ if(!user_client) //Disconnected, already gone.
+ return
+ user_client.images -= bar
+ user_client = null
+
+///Called by user's Login(), it transfers the progress bar image to the new client.
+/datum/progressbar/proc/on_user_login(datum/source)
+ SIGNAL_HANDLER
+
+ if(user_client)
+ if(user_client == user.client) //If this was not client handling I'd condemn this sanity check. But clients are fickle things.
+ return
+ clean_user_client()
+ if(!user.client) //Clients can vanish at any time, the bastards.
+ return
+ user_client = user.client
+ add_prog_bar_image_to_client()
+
+///Adds a smoothly-appearing progress bar image to the player's screen.
+/datum/progressbar/proc/add_prog_bar_image_to_client()
+ bar.pixel_y = 0
+ bar.alpha = 0
+ user_client.images += bar
+ animate(bar, pixel_y = 32 + offset_y + (PROGRESSBAR_HEIGHT * (listindex - 1)), alpha = 255, time = PROGRESSBAR_ANIMATION_TIME, easing = SINE_EASING)
+
+///Updates the progress bar image visually.
+/datum/progressbar/proc/update(progress)
progress = clamp(progress, 0, goal)
+ if(progress == last_progress)
+ return
+ last_progress = progress
bar.icon_state = "prog_bar_[round(((progress / goal) * 100), 5)]"
- if (!shown)
- user.client.images += bar
- shown = 1
+
+///Called on progress end, be it successful or a failure. Wraps up things to delete the datum and bar.
+/datum/progressbar/proc/end_progress()
+ if(last_progress != goal)
+ bar.icon_state = "[bar.icon_state]_fail"
+
+ animate(bar, alpha = 0, time = PROGRESSBAR_ANIMATION_TIME)
+
+ QDEL_IN(src, PROGRESSBAR_ANIMATION_TIME)
+
+#undef PROGRESSBAR_ANIMATION_TIME
+#undef PROGRESSBAR_HEIGHT
diff --git a/code/datums/weakref.dm b/code/datums/weakref.dm
index 617cc0c3bca..eaeea94b3c3 100644
--- a/code/datums/weakref.dm
+++ b/code/datums/weakref.dm
@@ -1,30 +1,112 @@
-/**
- * * Returns a /datum/weakref if input is a datum, and not undergoing GC
- * * Returns null otherwise.
- */
+/// Creates a weakref to the given input.
+/// See /datum/weakref's documentation for more information.
/proc/WEAKREF(datum/input)
- RETURN_TYPE(/datum/weakref)
if(istype(input) && !QDELETED(input))
- if(istype(input, /datum/weakref))
+ if(isweakref(input))
return input
if(!input.weak_reference)
input.weak_reference = new /datum/weakref(input)
return input.weak_reference
-/datum/proc/create_weakref() //Forced creation for admin proccalls
+/datum/proc/create_weakref() //Forced creation for admin proccalls
return WEAKREF(src)
+/**
+ * A weakref holds a non-owning reference to a datum.
+ * The datum can be referenced again using `resolve()`.
+ *
+ * To figure out why this is important, you must understand how deletion in
+ * BYOND works.
+ *
+ * Imagine a datum as a TV in a living room. When one person enters to watch
+ * TV, they turn it on. Others can come into the room and watch the TV.
+ * When the last person leaves the room, they turn off the TV because it's
+ * no longer being used.
+ *
+ * A datum being deleted tells everyone who's watching the TV to stop.
+ * If everyone leaves properly (AKA cleaning up their references), then the
+ * last person will turn off the TV, and everything is well.
+ * However, if someone is resistant (holds a hard reference after deletion),
+ * then someone has to walk in, drag them away, and turn off the TV forecefully.
+ * This process is very slow, and it's known as hard deletion.
+ *
+ * This is where weak references come in. Weak references don't count as someone
+ * watching the TV. Thus, when what it's referencing is destroyed, it will
+ * hopefully clean up properly, and limit hard deletions.
+ *
+ * A common use case for weak references is holding onto what created itself.
+ * For example, if a machine wanted to know what its last user was, it might
+ * create a `var/mob/living/last_user`. However, this is a strong reference to
+ * the mob, and thus will force a hard deletion when that mob is deleted.
+ * It is often better in this case to instead create a weakref to the user,
+ * meaning this type definition becomes `var/datum/weakref/last_user`.
+ *
+ * A good rule of thumb is that you should hold strong references to things
+ * that you *own*. For example, a dog holding a chew toy would be the owner
+ * of that chew toy, and thus a `var/obj/item/chew_toy` reference is fine
+ * (as long as it is cleaned up properly).
+ * However, a chew toy does not own its dog, so a `var/mob/living/dog/owner`
+ * might be inferior to a weakref.
+ * This is also a good rule of thumb to avoid circular references, such as the
+ * chew toy example. A circular reference that doesn't clean itself up properly
+ * will always hard delete.
+ */
/datum/weakref
var/reference
/datum/weakref/New(datum/thing)
reference = REF(thing)
-/datum/weakref/Destroy()
- . = ..()
- return QDEL_HINT_LETMELIVE //Let BYOND autoGC thiswhen nothing is using it anymore.
+/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.
+ target?.weak_reference = null
+ return ..()
+
+/**
+ * Retrieves the datum that this weakref is referencing.
+ *
+ * This will return `null` if the datum was deleted. This MUST be respected.
+ */
/datum/weakref/proc/resolve()
var/datum/D = locate(reference)
return (!QDELETED(D) && D.weak_reference == src) ? D : null
+
+/**
+ * SERIOUSLY READ THE AUTODOC COMMENT FOR THIS PROC BEFORE EVEN THINKING ABOUT USING IT
+ *
+ * Like resolve, but doesn't care if the datum is being qdeleted but hasn't been deleted yet.
+ *
+ * The return value of this proc leaves hanging references if the datum is being qdeleted but hasn't been deleted yet.
+ *
+ * Do not do anything that would create a lasting reference to the return value, such as giving it a tag, putting it on the map,
+ * adding it to an atom's contents or vis_contents, giving it a key (if it's a mob), attaching it to an atom (if it's an image),
+ * or assigning it to a datum or list referenced somewhere other than a temporary value.
+ *
+ * Unless you're resolving a weakref to a datum in a COMSIG_QDELETING signal handler registered on that very same datum,
+ * just use resolve instead.
+ */
+/datum/weakref/proc/hard_resolve()
+ var/datum/D = locate(reference)
+ return (D?.weak_reference == src) ? D : null
+
+/datum/weakref/vv_get_dropdown()
+ . = ..()
+ VV_DROPDOWN_OPTION(VV_HK_WEAKREF_RESOLVE, "Go to reference")
+
+/datum/weakref/vv_do_topic(list/href_list)
+ . = ..()
+
+ if(!.)
+ return
+
+ if(href_list[VV_HK_WEAKREF_RESOLVE])
+ if(!check_rights(NONE))
+ return
+ var/datum/R = resolve()
+ if(R)
+ usr.client.debug_variables(R)
diff --git a/code/datums/world_topic.dm b/code/datums/world_topic.dm
index 44779622aa4..7df78d28808 100644
--- a/code/datums/world_topic.dm
+++ b/code/datums/world_topic.dm
@@ -26,13 +26,17 @@
var/require_comms_key = FALSE
/datum/world_topic/proc/TryRun(list/input)
- key_valid = config_legacy.comms_key == input["key"] && (config_legacy.comms_key != initial(config_legacy.comms_key)) && config_legacy.comms_key && input["key"] //no fucking defaults allowed.
- //key_valid = config && (CONFIG_GET(string/comms_key) == input["key"])
- if(require_comms_key && !key_valid)
- return "Bad Key"
+ key_valid = (config_legacy.comms_key == input["key"]) && (config_legacy.comms_key != initial(config_legacy.comms_key)) && config_legacy.comms_key && input["key"] //no fucking defaults allowed.
input -= "key"
- . = Run(input)
- if(islist(.))
+ if(require_comms_key && !key_valid)
+ . = "Bad Key"
+ if (input["format"] == "json")
+ . = list("error" = .)
+ else
+ . = Run(input)
+ if (input["format"] == "json")
+ . = json_encode(.)
+ else if(islist(.))
. = list2params(.)
/datum/world_topic/proc/Run(list/input)
@@ -54,7 +58,7 @@
log = FALSE
/datum/world_topic/playing/Run(list/input)
- return length(GLOB.player_list)
+ return GLOB.player_list.len
/datum/world_topic/pr_announce
keyword = "announce"
@@ -71,7 +75,7 @@
if(PRcounts[id] > PR_ANNOUNCEMENTS_PER_ROUND)
return
- var/final_composed = "PR: [input[keyword]]"
+ var/final_composed = SPAN_ANNOUNCE("PR: [input[keyword]]")
for(var/client/C in GLOB.clients)
C.AnnouncePR(final_composed)
@@ -232,31 +236,28 @@
.["enter"] = config_legacy.enter_allowed
.["vote"] = config_legacy.allow_vote_mode
.["ai"] = config_legacy.allow_ai
- .["host"] = host || null
+ .["host"] = world.host ? world.host : null
.["round_id"] = GLOB.round_id
.["players"] = GLOB.clients.len
.["revision"] = GLOB.revdata.commit
.["revision_date"] = GLOB.revdata.date
+ .["hub"] = GLOB.hub_visibility
var/list/adm = get_admin_counts()
var/list/presentmins = adm["present"]
var/list/afkmins = adm["afk"]
.["admins"] = presentmins.len + afkmins.len //equivalent to the info gotten from adminwho
- //.["gamestate"] = SSticker.current_state
+ .["gamestate"] = SSticker.current_state
- //.["map_name"] = SSmapping.config?.map_name || "Loading..."
+ .["map_name"] = (LEGACY_MAP_DATUM)?.name || "Loading..."
- //if(key_valid)
- //.["active_players"] = get_active_player_count()
- /*
- if(SSticker.HasRoundStarted())
- .["real_mode"] = SSticker.mode.name
- // Key-authed callers may know the truth behind the "secret"
- */
+ if(key_valid)
+ .["active_players"] = get_active_player_count()
.["security_level"] = get_security_level()
-// .["round_duration"] = SSticker ? round((world.time-SSticker.SSticker.round_start_time)/10) : 0
-// // Amount of world's ticks in seconds, useful for calculating round duration
+ .["round_duration"] = SSticker ? round((world.time-SSticker.round_start_time)/10) : 0
+ // Amount of world's ticks in seconds, useful for calculating round duration
+
.["stationtime"] = stationtime2text()
.["roundduration"] = roundduration2text()
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index 128ef7012b9..a55c48ebcc8 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -172,7 +172,11 @@ var/list/admin_verbs_server = list(
/client/proc/everyone_random,
/datum/admins/proc/toggleAI,
/client/proc/cmd_admin_delete, // Delete an instance/object/mob/etc,
- /client/proc/cmd_debug_del_all,
+ /client/proc/cmd_del_all,
+ /client/proc/cmd_del_all_force,
+ /client/proc/cmd_del_all_hard,
+ /client/proc/check_timer_sources,
+ /client/proc/allow_browser_inspect,
/client/proc/cmd_admin_clear_mobs,
/datum/admins/proc/adspawn,
/datum/admins/proc/adjump,
@@ -201,7 +205,11 @@ var/list/admin_verbs_debug = list(
/client/proc/debug_antagonist_template,
/client/proc/cmd_debug_mob_lists,
/client/proc/cmd_admin_delete,
- /client/proc/cmd_debug_del_all,
+ /client/proc/cmd_del_all,
+ /client/proc/cmd_del_all_force,
+ /client/proc/cmd_del_all_hard,
+ /client/proc/check_timer_sources,
+ /client/proc/allow_browser_inspect,
/client/proc/cmd_debug_tog_aliens,
/client/proc/cmd_display_del_log,
/client/proc/cmd_display_init_log,
@@ -320,7 +328,11 @@ var/list/admin_verbs_hideable = list(
/client/proc/debug_controller,
/client/proc/startSinglo,
/client/proc/cmd_debug_mob_lists,
- /client/proc/cmd_debug_del_all,
+ /client/proc/cmd_del_all,
+ /client/proc/cmd_del_all_force,
+ /client/proc/cmd_del_all_hard,
+ /client/proc/check_timer_sources,
+ /client/proc/allow_browser_inspect,
/client/proc/cmd_admin_clear_mobs,
/client/proc/cmd_debug_tog_aliens,
/client/proc/cmd_display_del_log,
diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm
index ad220ad3826..f95188837d7 100644
--- a/code/modules/admin/verbs/debug.dm
+++ b/code/modules/admin/verbs/debug.dm
@@ -1,16 +1,14 @@
/client/proc/Debug2()
- set category = "Debug"
+ set category = ADMIN_CATEGORY_DEBUG
set name = "Debug-Game"
+ set desc = "Toggles game debugging."
+
if(!check_rights(R_DEBUG)) return
- if(GLOB.Debug2)
- GLOB.Debug2 = 0
- message_admins("[key_name(src)] toggled debugging off.")
- log_admin("[key_name(src)] toggled debugging off.")
- else
- GLOB.Debug2 = 1
- message_admins("[key_name(src)] toggled debugging on.")
- log_admin("[key_name(src)] toggled debugging on.")
+ GLOB.Debug2 = !GLOB.Debug2
+ var/message = "toggled debugging [(GLOB.Debug2 ? "ON" : "OFF")]"
+ message_admins("[key_name_admin(src)] [message].")
+ log_admin("[key_name(src)] [message].")
feedback_add_details("admin_verb","DG2") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
@@ -105,29 +103,280 @@
else
alert("Invalid mob")
-//TODO: merge the vievars version into this or something maybe mayhaps
-/client/proc/cmd_debug_del_all(object as text)
- set category = "Debug"
- set name = "Del-All"
+/client/proc/poll_type_to_del(search_string)
+ var/list/types = get_fancy_list_of_atom_types()
+ if (!isnull(search_string) && search_string != "")
+ types = filter_fancy_list(types, search_string)
- var/list/matches = get_fancy_list_of_atom_types()
- if (!isnull(object) && object!="")
- matches = filter_fancy_list(matches, object)
-
- if(matches.len==0)
+ if(!length(types))
return
- var/hsbitem = input(usr, "Choose an object to delete. Use clear-mobs instead on LIVE.", "Delete:") as null|anything in matches
- if(hsbitem)
- hsbitem = matches[hsbitem]
- var/counter = 0
+
+ var/key = input(usr, "Choose an object to delete.", "Delete:") as null|anything in sortList(types)
+
+ if(!key)
+ return
+ return types[key]
+
+/client/proc/cmd_del_all(object as text)
+ set category = ADMIN_CATEGORY_DEBUG
+ set name = "Del-All"
+ set desc = "Delete all datums with the specified type."
+
+ if(!check_rights(R_DEBUG|R_SPAWN)) return
+
+ var/type_to_del = src.poll_type_to_del(object)
+ if(!type_to_del)
+ return
+
+ var/counter = 0
+ for(var/atom/O in world)
+ if(istype(O, type_to_del))
+ counter++
+ qdel(O)
+ CHECK_TICK
+ log_admin("[key_name(src)] has deleted all ([counter]) instances of [type_to_del].")
+ message_admins("[key_name_admin(src)] has deleted all ([counter]) instances of [type_to_del].")
+
+/client/proc/cmd_del_all_force(object as text)
+ set category = ADMIN_CATEGORY_DEBUG
+ set name = "Force-Del-All"
+ set desc = "Forcibly delete all datums with the specified type."
+
+ if(!check_rights(R_DEBUG|R_SPAWN)) return
+
+ var/type_to_del = src.poll_type_to_del(object)
+ if(!type_to_del)
+ return
+
+ var/counter = 0
+ for(var/atom/O in world)
+ if(istype(O, type_to_del))
+ counter++
+ qdel(O, force = TRUE)
+ CHECK_TICK
+ log_admin("[key_name(src)] has force-deleted all ([counter]) instances of [type_to_del].")
+ message_admins("[key_name_admin(src)] has force-deleted all ([counter]) instances of [type_to_del].")
+
+/client/proc/cmd_del_all_hard(object as text)
+ set category = ADMIN_CATEGORY_DEBUG
+ set name = "Hard-Del-All"
+ set desc = "Hard delete all datums with the specified type."
+
+ if(!check_rights(R_DEBUG|R_SPAWN)) return
+
+ var/type_to_del = src.poll_type_to_del(object)
+ if(!type_to_del)
+ return
+
+ var/choice = alert(src, "ARE YOU SURE that you want to hard delete this type? It will cause MASSIVE lag.", "Hoooo lad what happen?", "Yes", "No")
+ if(choice != "Yes")
+ return
+
+ choice = alert(src, "Do you want to pre qdelete the atom? This will speed things up significantly, but may break depending on your level of fuckup.", "How do you even get it that bad", "Yes", "No")
+ var/should_pre_qdel = TRUE
+ if(choice == "No")
+ should_pre_qdel = FALSE
+
+ choice = alert(src, "Ok one last thing, do you want to yield to the game? or do it all at once. These are hard deletes remember.", "Jesus christ man", "Yield", "Ignore the server")
+ var/should_check_tick = TRUE
+ if(choice == "Ignore the server")
+ should_check_tick = FALSE
+
+ var/counter = 0
+ if(should_check_tick)
for(var/atom/O in world)
- if(istype(O, hsbitem))
+ if(istype(O, type_to_del))
counter++
- qdel(O)
+ if(should_pre_qdel)
+ qdel(O)
+ del(O)
CHECK_TICK
- log_admin("[key_name(src)] has deleted all ([counter]) instances of [hsbitem].")
- message_admins("[key_name_admin(src)] has deleted all ([counter]) instances of [hsbitem].")
- // SSblackbox.record_feedback("tally", "admin_verb", 1, "Delete All") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+ else
+ for(var/atom/O in world)
+ if(istype(O, type_to_del))
+ counter++
+ if(should_pre_qdel)
+ qdel(O)
+ del(O)
+
+ log_admin("[key_name(src)] has hard deleted all ([counter]) instances of [type_to_del].")
+ message_admins("[key_name_admin(src)] has hard deleted all ([counter]) instances of [type_to_del].")
+
+/client/proc/cmd_debug_make_powernets()
+ set category = ADMIN_CATEGORY_DEBUG
+ set name = "Make Powernets"
+ set desc = "Regenerates all powernets for all cables."
+
+ if(!check_rights(R_DEBUG|R_SERVER)) return
+
+ SSmachines.makepowernets()
+ log_admin("[key_name(src)] has remade the powernet. SSmachines.makepowernets() called.")
+ message_admins("[key_name_admin(src)] has remade the powernets. SSmachines.makepowernets() called.", 0)
+ feedback_add_details("admin_verb","MPWN") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+
+/client/proc/cmd_admin_grantfullaccess(var/mob/M in GLOB.mob_list)
+ set category = ADMIN_CATEGORY_DEBUG
+ set name = "Grant Full Access"
+ set desc = "Grant full access to a mob."
+
+ if(!check_rights(R_DEBUG)) return
+
+ if(!SSticker.HasRoundStarted())
+ tgui_alert(src, "Wait until the game starts")
+ return
+ if (ishuman(M))
+ var/mob/living/carbon/human/H = M
+
+ if (H.wear_id)
+ var/obj/item/card/id/id = H.wear_id
+ if(istype(H.wear_id, /obj/item/pda))
+ var/obj/item/pda/pda = H.wear_id
+ id = pda.id
+ id.icon_state = "gold"
+ id.access = get_all_accesses().Copy()
+ else
+ var/obj/item/card/id/id = new/obj/item/card/id(M);
+ id.icon_state = "gold"
+ id.access = get_all_accesses().Copy()
+ id.registered_name = H.real_name
+ id.assignment = "Facility Director"
+ id.name = "[id.registered_name]'s ID Card ([id.assignment])"
+ H.equip_to_slot_or_del(id, SLOT_ID_WORN_ID)
+ H.update_inv_wear_id()
+ else
+ alert("Invalid mob")
+ feedback_add_details("admin_verb","GFA") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+ log_admin("[key_name(src)] has granted [M.key] full access.")
+ message_admins(SPAN_ADMINNOTICE("[key_name_admin(usr)] has granted [M.key] full access."))
+
+/client/proc/cmd_debug_mob_lists()
+ set category = ADMIN_CATEGORY_DEBUG
+ set name = "Debug Mob Lists"
+ set desc = "For when you just gotta know"
+
+ if(!check_rights(R_DEBUG)) return
+
+ var/chosen_list = input("Which list?") in list("Players","Admins","Mobs","Living Mobs","Dead Mobs","Clients")
+ if(isnull(chosen_list))
+ return
+ switch(chosen_list)
+ if("Players")
+ to_chat(usr, jointext(GLOB.player_list,","), confidential = TRUE)
+ if("Admins")
+ to_chat(usr, jointext(GLOB.admins,","), confidential = TRUE)
+ if("Mobs")
+ to_chat(usr, jointext(GLOB.mob_list,","), confidential = TRUE)
+ if("Living Mobs")
+ to_chat(usr, jointext(living_mob_list,","), confidential = TRUE)
+ if("Dead Mobs")
+ to_chat(usr, jointext(dead_mob_list,","), confidential = TRUE)
+ if("Clients")
+ to_chat(usr, jointext(GLOB.clients,","), confidential = TRUE)
+
+/client/proc/cmd_display_del_log()
+ set category = ADMIN_CATEGORY_DEBUG
+ set name = "Display del() Log"
+ set desc = "Display del's log of everything that's passed through it."
+
+ if(!check_rights(R_DEBUG)) return
+
+ var/list/dellog = list("List of things that have gone through qdel this round
")
+ tim_sort(SSgarbage.items, cmp=GLOBAL_PROC_REF(cmp_qdel_item_time), associative = TRUE)
+ for(var/path in SSgarbage.items)
+ var/datum/qdel_item/I = SSgarbage.items[path]
+ dellog += "- [path]
"
+ if (I.failures)
+ dellog += "- Failures: [I.failures]
"
+ dellog += "- qdel() Count: [I.qdels]
"
+ dellog += "- Destroy() Cost: [I.destroy_time]ms
"
+ if (I.hard_deletes)
+ dellog += "- Total Hard Deletes [I.hard_deletes]
"
+ dellog += "- Time Spent Hard Deleting: [I.hard_delete_time]ms
"
+ if (I.slept_destroy)
+ dellog += "- Sleeps: [I.slept_destroy]
"
+ if (I.no_respect_force)
+ dellog += "- Ignored force: [I.no_respect_force]
"
+ if (I.no_hint)
+ dellog += "- No hint: [I.no_hint]
"
+ dellog += " "
+
+ dellog += " "
+
+ var/datum/browser/browser = new(usr, "dellog", "Del Log", 200, 400)
+ browser.set_content(dellog.Join())
+ browser.open()
+
+/client/proc/cmd_display_overlay_log()
+ set category = ADMIN_CATEGORY_DEBUG
+ set name = "Display overlay Log"
+ set desc = "Display SSoverlays log of everything that's passed through it."
+
+ if(!check_rights(R_DEBUG)) return
+
+ render_stats(SSoverlays.stats, src)
+
+/client/proc/cmd_display_init_log()
+ set category = ADMIN_CATEGORY_DEBUG
+ set name = "Display Initialize() Log"
+ set desc = "Displays a list of things that didn't handle Initialize() properly"
+
+ if(!check_rights(R_DEBUG)) return
+
+ var/datum/browser/browser = new(usr, "initlog", "Initialize Log", 500, 500)
+ browser.set_content(replacetext(SSatoms.InitLog(), "\n", " "))
+ browser.open()
+
+/datum/admins/proc/view_runtimes()
+ set category = ADMIN_CATEGORY_DEBUG
+ set name = "View Runtimes"
+ set desc = "Open the Runtime Viewer"
+
+ if(!check_rights(R_DEBUG)) return
+
+ GLOB.error_cache.show_to(usr)
+
+ // The runtime viewer has the potential to crash the server if there's a LOT of runtimes
+ // this has happened before, multiple times, so we'll just leave an alert on it
+ if(GLOB.total_runtimes >= 50000) // arbitrary number, I don't know when exactly it happens
+ var/warning = "There are a lot of runtimes, clicking any button (especially \"linear\") can have the potential to lag or crash the server"
+ if(GLOB.total_runtimes >= 100000)
+ warning = "There are a TON of runtimes, clicking any button (especially \"linear\") WILL LIKELY crash the server"
+ // Not using TGUI alert, because it's view runtimes, stuff is probably broken
+ alert(src, "[warning]. Proceed with caution. If you really need to see the runtimes, download the runtime log and view it in a text editor.", "HEED THIS WARNING CAREFULLY MORTAL")
+
+/client/proc/check_timer_sources()
+ set category = ADMIN_CATEGORY_DEBUG
+ set name = "Check Timer Sources"
+ set desc = "Checks the sources of running timers."
+
+ if(!check_rights(R_DEBUG)) return
+
+ var/bucket_list_output = generate_timer_source_output(SStimer.bucket_list)
+ var/second_queue = generate_timer_source_output(SStimer.second_queue)
+
+ var/datum/browser/browser = new(src, "check_timer_sources", "Timer Sources", 700, 700)
+ browser.set_content({"
+ bucket_list
+ [bucket_list_output]
+
+ second_queue
+ [second_queue]
+ "})
+ browser.open()
+
+/client/proc/allow_browser_inspect()
+ set category = ADMIN_CATEGORY_DEBUG
+ set name = "Allow Browser Inspect"
+ set desc = "Allow browser debugging via inspect"
+
+ if(!check_rights(R_DEBUG)) return
+
+ if(src.byond_version < 516)
+ to_chat(src, SPAN_WARNING("You can only use this on 516!"))
+ return
+
+ to_chat(src, SPAN_NOTICE("You can now right click to use inspect on browsers."))
+ winset(src, null, list("browser-options" = "+devtools"))
/client/proc/cmd_admin_clear_mobs()
set category = "Admin"
@@ -152,15 +401,6 @@
message_admins("[key_name_admin(src)] has deleted all instances of [hsbitem] in a range of [range] tiles.", 0)
feedback_add_details("admin_verb","CLRM") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-
-/client/proc/cmd_debug_make_powernets()
- set category = "Debug"
- set name = "Make Powernets"
- SSmachines.makepowernets()
- log_admin("[key_name(src)] has remade the powernet. SSmachines.makepowernets() called.")
- message_admins("[key_name_admin(src)] has remade the powernets. SSmachines.makepowernets() called.", 0)
- feedback_add_details("admin_verb","MPWN") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-
/client/proc/cmd_debug_tog_aliens()
set category = "Server"
set name = "Toggle Aliens"
@@ -170,103 +410,6 @@
message_admins("[key_name_admin(src)] has turned aliens [config_legacy.aliens_allowed ? "on" : "off"].", 0)
feedback_add_details("admin_verb","TAL") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-/client/proc/cmd_display_del_log()
- set category = "Debug"
- set name = "Display del() Log"
- set desc = "Display del's log of everything that's passed through it."
-
- if(!check_rights(R_DEBUG)) return
- var/list/dellog = list("List of things that have gone through qdel this round
")
- tim_sort(SSgarbage.items, cmp=GLOBAL_PROC_REF(cmp_qdel_item_time), associative = TRUE)
- for(var/path in SSgarbage.items)
- var/datum/qdel_item/I = SSgarbage.items[path]
- dellog += "- [path]
"
- if (I.failures)
- dellog += "- Failures: [I.failures]
"
- dellog += "- qdel() Count: [I.qdels]
"
- dellog += "- Destroy() Cost: [I.destroy_time]ms
"
- if (I.hard_deletes)
- dellog += "- Total Hard Deletes [I.hard_deletes]
"
- dellog += "- Time Spent Hard Deleting: [I.hard_delete_time]ms
"
- if (I.slept_destroy)
- dellog += "- Sleeps: [I.slept_destroy]
"
- if (I.no_respect_force)
- dellog += "- Ignored force: [I.no_respect_force]
"
- if (I.no_hint)
- dellog += "- No hint: [I.no_hint]
"
- dellog += " "
-
- dellog += " "
-
- usr << browse(dellog.Join(), "window=dellog")
-
-/client/proc/cmd_display_init_log()
- set category = "Debug"
- set name = "Display Initialize() Log"
- set desc = "Displays a list of things that didn't handle Initialize() properly"
-
- if(!check_rights(R_DEBUG))
- return
- var/rendered = replacetext(SSatoms.InitLog(), "\n", " ")
- if(!length(rendered))
- to_chat(usr, SPAN_BOLDNOTICE("There were no bad init calls so far! Yay :)"))
- return
- src << browse(rendered, "window=initlog")
-
-/client/proc/cmd_display_overlay_log()
- set category = "Debug"
- set name = "Display overlay Log"
- set desc = "Display SSoverlays log of everything that's passed through it."
-
- if(!check_rights(R_DEBUG))
- return
- render_stats(SSoverlays.stats, src)
-
-// Render stats list for round-end statistics.
-/proc/render_stats(list/stats, user, sort = GLOBAL_PROC_REF(cmp_generic_stat_item_time))
- tim_sort(stats, sort, TRUE)
-
- var/list/lines = list()
- for (var/entry in stats)
- var/list/data = stats[entry]
- lines += "[entry] => [num2text(data[STAT_ENTRY_TIME], 10)]ms ([data[STAT_ENTRY_COUNT]]) (avg:[num2text(data[STAT_ENTRY_TIME]/(data[STAT_ENTRY_COUNT] || 1), 99)])"
-
- if (user)
- user << browse("- [lines.Join("
- ")]
", "window=[url_encode("stats:\ref[stats]")]")
- else
- . = lines.Join("\n")
-
-/client/proc/cmd_admin_grantfullaccess(var/mob/M in GLOB.mob_list)
- set category = "Admin"
- set name = "Grant Full Access"
-
- if (!SSticker)
- alert("Wait until the game starts")
- return
- if (istype(M, /mob/living/carbon/human))
- var/mob/living/carbon/human/H = M
- if (H.wear_id)
- var/obj/item/card/id/id = H.wear_id
- if(istype(H.wear_id, /obj/item/pda))
- var/obj/item/pda/pda = H.wear_id
- id = pda.id
- id.icon_state = "gold"
- id.access = get_all_accesses().Copy()
- else
- var/obj/item/card/id/id = new/obj/item/card/id(M);
- id.icon_state = "gold"
- id.access = get_all_accesses().Copy()
- id.registered_name = H.real_name
- id.assignment = "Facility Director"
- id.name = "[id.registered_name]'s ID Card ([id.assignment])"
- H.equip_to_slot_or_del(id, SLOT_ID_WORN_ID)
- H.update_inv_wear_id()
- else
- alert("Invalid mob")
- feedback_add_details("admin_verb","GFA") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
- log_admin("[key_name(src)] has granted [M.key] full access.")
- message_admins("[key_name_admin(usr)] has granted [M.key] full access.", 1)
-
/client/proc/cmd_assume_direct_control(var/mob/M in GLOB.mob_list)
set category = "Admin"
set name = "Assume direct control"
@@ -543,25 +686,6 @@
message_admins("[key_name_admin(usr)] setup the supermatter engine [response == "Setup except coolant" ? "without coolant": ""]", 1)
return
-/client/proc/cmd_debug_mob_lists()
- set category = "Debug"
- set name = "Debug Mob Lists"
- set desc = "For when you just gotta know"
-
- switch(input("Which list?") in list("Players","Admins","Mobs","Living Mobs","Dead Mobs", "Clients"))
- if("Players")
- to_chat(usr, jointext(GLOB.player_list,","))
- if("Admins")
- to_chat(usr, jointext(GLOB.admins,","))
- if("Mobs")
- to_chat(usr, jointext(GLOB.mob_list,","))
- if("Living Mobs")
- to_chat(usr, jointext(living_mob_list,","))
- if("Dead Mobs")
- to_chat(usr, jointext(dead_mob_list,","))
- if("Clients")
- to_chat(usr, jointext(GLOB.clients,","))
-
// DNA2 - Admin Hax
/client/proc/cmd_admin_toggle_block(var/mob/M,var/block)
if(istype(M, /mob/living/carbon))
@@ -575,16 +699,6 @@
else
alert("Invalid mob")
-/datum/admins/proc/view_runtimes()
- set category = "Debug"
- set name = "View Runtimes"
- set desc = "Open the Runtime Viewer"
-
- if(!check_rights(R_DEBUG))
- return
-
- GLOB.error_cache.show_to(usr)
-
/datum/admins/proc/change_weather()
set category = "Debug"
set name = "Change Weather"
@@ -676,3 +790,44 @@
log_and_message_admins("[key_name(src)] Quick NIF'd [H.real_name] with a [input_NIF].")
feedback_add_details("admin_verb","QNIF") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+
+/proc/generate_timer_source_output(list/datum/timedevent/events)
+ var/list/per_source = list()
+
+ // Collate all events and figure out what sources are creating the most
+ for (var/_event in events)
+ if (!_event)
+ continue
+ var/datum/timedevent/event = _event
+
+ do
+ if (event.source)
+ if (per_source[event.source] == null)
+ per_source[event.source] = 1
+ else
+ per_source[event.source] += 1
+ event = event.next
+ while (event && event != _event)
+
+ // Now, sort them in order
+ var/list/sorted = list()
+ for (var/source in per_source)
+ sorted += list(list("source" = source, "count" = per_source[source]))
+ tim_sort(sorted, GLOBAL_PROC_REF(cmp_timer_data))
+
+ // Now that everything is sorted, compile them into an HTML output
+ var/output = ""
+
+ for (var/_timer_data in sorted)
+ var/list/timer_data = _timer_data
+ output += {"
+ | [timer_data["source"]] |
+ [timer_data["count"]] |
+ "}
+
+ output += " "
+
+ return output
+
+/proc/cmp_timer_data(list/a, list/b)
+ return b["count"] - a["count"]
diff --git a/code/modules/artwork/structures/sculpting_block.dm b/code/modules/artwork/structures/sculpting_block.dm
index b8598d4cbbd..3eff718dc7f 100644
--- a/code/modules/artwork/structures/sculpting_block.dm
+++ b/code/modules/artwork/structures/sculpting_block.dm
@@ -311,7 +311,7 @@
while(progress < finished_progress)
if(QDELETED(src))
- QDEL_NULL(progressbar)
+ progressbar.end_progress()
return
if(!do_after(user, time_per_line, src, DO_AFTER_NO_PROGRESS))
break
@@ -331,7 +331,8 @@
last = world.time
progressbar.update(sculpting_line - should_be_at)
- QDEL_NULL(progressbar)
+ if(!QDELETED(progressbar))
+ progressbar.end_progress()
lines = min(sculpting_line, progress / time_per_line)
diff --git a/code/modules/error_handler/error_handler.dm b/code/modules/error_handler/error_handler.dm
index a6f2d5af0a5..c61ad8bfafb 100644
--- a/code/modules/error_handler/error_handler.dm
+++ b/code/modules/error_handler/error_handler.dm
@@ -1,34 +1,24 @@
// Why? Because when you screw up too early in init, total runtimes won't be initialized. You can see why this can be a problem, right?
GLOBAL_VAR_INIT(total_runtimes, GLOB.total_runtimes || 0)
GLOBAL_VAR_INIT(total_runtimes_skipped, 0)
-GLOBAL_VAR_INIT(total_runtimes_seen, 0)
+
// to detect when someone fucks up royally and breaks error handling with preinit runtimes
GLOBAL_REAL_VAR(runtime_skip_once) = FALSE
GLOBAL_REAL_VAR(runtime_trap_triggered) = FALSE
#ifdef USE_CUSTOM_ERROR_HANDLER
-
#define ERROR_USEFUL_LEN 2
+
/world/Error(exception/E, datum/e_src)
+// preinit runtimes on unit test
#ifdef UNIT_TESTS
if(runtime_skip_once)
runtime_skip_once = FALSE
runtime_trap_triggered = TRUE
return
#endif
-
- ++GLOB.total_runtimes
-
- var/static/list/error_last_seen = list()
- var/static/list/error_cooldown = list() /* Error_cooldown items will either be positive(cooldown time) or negative(silenced error)
- If negative, starts at -1, and goes down by 1 each time that error gets skipped*/
-
- if(!GLOB || !error_last_seen)
- log_world("early runtime caught;")
- return ..()
-
- ++GLOB.total_runtimes_seen
+ GLOB.total_runtimes++
if(!istype(E)) //Something threw an unusual exception
log_world("uncaught runtime error: [E]")
@@ -45,17 +35,28 @@ GLOBAL_REAL_VAR(runtime_trap_triggered) = FALSE
// this will never happen.
return
- else if(copytext(E.name, 1, 18) == "Out of resources!")
- log_world("BYOND out of memory. Restarting")
- log_game("BYOND out of memory. Restarting")
+ else if(copytext(E.name, 1, 18) == "Out of resources!")//18 == length() of that string + 1
+ log_world("BYOND out of memory. Restarting ([E?.file]:[E?.line])")
TgsEndProcess()
+ . = ..()
Reboot(reason = 1)
+ return
+
+ var/static/regex/stack_workaround
+ if(isnull(stack_workaround))
+ stack_workaround = regex("[WORKAROUND_IDENTIFIER](.+?)[WORKAROUND_IDENTIFIER]")
+ var/static/list/error_last_seen = list()
+ var/static/list/error_cooldown = list() /* Error_cooldown items will either be positive(cooldown time) or negative(silenced error)
+ If negative, starts at -1, and goes down by 1 each time that error gets skipped*/
+
+ if(!error_last_seen) // A runtime is occurring too early in start-up initialization
return ..()
- if (islist(stack_trace_storage))
- for (var/line in splittext(E.desc, "\n"))
- if (text2ascii(line) != 32)
- stack_trace_storage += line
+ if(stack_workaround.Find(E.name))
+ var/list/data = json_decode(stack_workaround.group[1])
+ E.file = data[1]
+ E.line = data[2]
+ E.name = stack_workaround.Replace(E.name, "")
var/erroruid = "[E.file][E.line]"
var/last_seen = error_last_seen[erroruid]
@@ -69,7 +70,6 @@ GLOBAL_REAL_VAR(runtime_trap_triggered) = FALSE
error_cooldown[erroruid]-- //Used to keep track of skip count for this error
GLOB.total_runtimes_skipped++
return //Error is currently silenced, skip handling it
-
//Handle cooldowns and silencing spammy errors
var/silencing = FALSE
@@ -138,7 +138,7 @@ GLOBAL_REAL_VAR(runtime_trap_triggered) = FALSE
desclines += (" " + line) // Pad any unpadded lines, so they look pretty
else
desclines += line
- if(usrinfo) //If thi s info isn't null, it hasn't been added yet
+ if(usrinfo) //If this info isn't null, it hasn't been added yet
desclines.Add(usrinfo)
if(silencing)
desclines += " (This error will now be silenced for [DisplayTimeText(configured_error_silence_time)])"
@@ -156,5 +156,6 @@ GLOBAL_REAL_VAR(runtime_trap_triggered) = FALSE
// This writes the regular format (unwrapping newlines and inserting timestamps as needed).
log_runtime("runtime error: [E.name]\n[E.desc]")
-
#endif
+
+#undef ERROR_USEFUL_LEN
diff --git a/code/modules/error_handler/error_viewer.dm b/code/modules/error_handler/error_viewer.dm
index 87d93fd19e7..350ea2b5527 100644
--- a/code/modules/error_handler/error_viewer.dm
+++ b/code/modules/error_handler/error_viewer.dm
@@ -71,7 +71,7 @@ GLOBAL_DATUM(error_cache, /datum/error_viewer/error_cache)
if (linear)
back_to_param += ";viewruntime_linear=1"
- return "[linktext]"
+ return "[linktext]"
/datum/error_viewer/error_cache
var/list/errors = list()
@@ -116,16 +116,13 @@ GLOBAL_DATUM(error_cache, /datum/error_viewer/error_cache)
// from the same source hasn't been shown too recently
if (error_source.next_message_at <= world.time)
var/const/viewtext = "\[view]" // Nesting these in other brackets went poorly
- //log_debug(SPAN_DEBUGERROR("Runtime in [e.file], line [e.line]: [html_encode(e.name)] [error_entry.make_link(viewtext)]"))
- /*
- var/err_msg_delay
- if(config?.loaded)
- err_msg_delay = CONFIG_GET(number/error_msg_delay)
- else
- var/datum/config_entry/CE = /datum/config_entry/number/error_msg_delay
- err_msg_delay = initial(CE.config_entry_value)
- */
+ //log_debug("Runtime in [e.file], line [e.line]: [html_encode(e.name)] [error_entry.make_link(viewtext)]")
var/err_msg_delay = 50
+ // if(config?.loaded)
+ // err_msg_delay = CONFIG_GET(number/error_msg_delay)
+ // else
+ // var/datum/config_entry/CE = /datum/config_entry/number/error_msg_delay
+ // err_msg_delay = initial(CE.default)
error_source.next_message_at = world.time + err_msg_delay
/datum/error_viewer/error_source
@@ -185,12 +182,12 @@ GLOBAL_DATUM(error_cache, /datum/error_viewer/error_cache)
var/html = build_header(back_to, linear)
html += "[name][desc] "
if (usr_ref)
- html += " usr: VV"
- html += " PP"
- html += " Follow"
+ html += " usr: VV"
+ html += " PP"
+ html += " Follow"
if (istype(usr_loc))
- html += " usr.loc: VV"
- html += " JMP"
+ html += " usr.loc: VV"
+ html += " JMP"
browse_to(user, html)
diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm
index 1557e7f0fb9..3d70e0e44e4 100644
--- a/code/modules/mob/mob_defines.dm
+++ b/code/modules/mob/mob_defines.dm
@@ -448,3 +448,6 @@
//Moved from code\modules\nano\nanoexternal.dm
// Used by the Nano UI Manager (/datum/nanomanager) to track UIs opened by this mob
var/list/open_uis = list()
+
+ ///List of progress bars this mob is currently seeing for actions
+ var/list/progressbars = null //for stacking do_after bars
diff --git a/code/modules/stockmarket/computer.dm b/code/modules/stockmarket/computer.dm
index e30228b5a8c..774eb321412 100644
--- a/code/modules/stockmarket/computer.dm
+++ b/code/modules/stockmarket/computer.dm
@@ -63,7 +63,6 @@
continue
var/datum/browser/popup = new(usr, "stock_logs", "Stock Transaction Logs", 600, 400)
popup.set_content(dat)
- popup.set_title_image(usr.browse_rsc_icon(src.icon, src.icon_state))
popup.open()
if("stocks_archive")
@@ -91,7 +90,6 @@
dat += " |