")
-/// Max length of chat message in characters
-#define CHAT_MESSAGE_MAX_LENGTH 110
-
//debug printing macros (for development and testing)
/// Used for debug messages to the world
#define debug_world(msg) if (GLOB.Debug2) to_chat(world, \
diff --git a/code/__DEFINES/fonts.dm b/code/__DEFINES/fonts.dm
new file mode 100644
index 0000000000000..ba799a62c9c74
--- /dev/null
+++ b/code/__DEFINES/fonts.dm
@@ -0,0 +1,7 @@
+// Font metrics bitfield
+/// Include leading A width and trailing C width in GetWidth() or in DrawText()
+#define INCLUDE_AC (1<<0)
+
+DEFINE_BITFIELD(font_flags, list(
+ "INCLUDE_AC" = INCLUDE_AC,
+))
diff --git a/code/__DEFINES/html_assistant.dm b/code/__DEFINES/html_assistant.dm
index 03f95ad20158b..ed16afb481c1f 100644
--- a/code/__DEFINES/html_assistant.dm
+++ b/code/__DEFINES/html_assistant.dm
@@ -32,3 +32,10 @@
"[(GLOB.tooltips[config_key] ? "
[hover_me][GLOB.tooltips[config_key]]
" : "[hover_me]")]"
#define OPEN_WIKI(wiki_url, text) (CONFIG_GET(string/wikiurl) ? "[text]" : "[text]")
+
+
+#define HTML_SKELETON_INTERNAL(head, body) \
+"[head][body]"
+
+#define HTML_SKELETON_TITLE(title, body) HTML_SKELETON_INTERNAL("[title]", body)
+#define HTML_SKELETON(body) HTML_SKELETON_INTERNAL("", body)
diff --git a/code/__DEFINES/say.dm b/code/__DEFINES/say.dm
index b5c2691b92277..efd51ec77d58c 100644
--- a/code/__DEFINES/say.dm
+++ b/code/__DEFINES/say.dm
@@ -90,9 +90,9 @@
#define EAVESDROP_EXTRA_RANGE 1 //how much past the specified message_range does the message get starred, whispering only
// A link given to ghost alice to follow bob
-#define FOLLOW_LINK(alice, bob) "(F)"
-#define TURF_LINK(alice, turfy) "(T)"
-#define FOLLOW_OR_TURF_LINK(alice, bob, turfy) "(F)"
+#define FOLLOW_LINK(alice, bob) "(F)"
+#define TURF_LINK(alice, turfy) "(T)"
+#define FOLLOW_OR_TURF_LINK(alice, bob, turfy) "(F)"
#define LINGHIVE_NONE 0
#define LINGHIVE_OUTSIDER 1
diff --git a/code/__DEFINES/text.dm b/code/__DEFINES/text.dm
index dc17aca279b53..459557ee3e0b5 100644
--- a/code/__DEFINES/text.dm
+++ b/code/__DEFINES/text.dm
@@ -5,6 +5,21 @@
/// Prepares a text to be used for maptext. Use this so it doesn't look hideous.
#define MAPTEXT(text) {"[##text]"}
+/// Removes characters incompatible with file names.
+#define SANITIZE_FILENAME(text) (GLOB.filename_forbidden_chars.Replace(text, ""))
+
+/// Simply removes the < and > characters, and limits the length of the message.
+#define STRIP_HTML_SIMPLE(text, limit) (GLOB.angular_brackets.Replace(copytext(text, 1, limit), ""))
+
+/// Removes everything enclose in < and > inclusive of the bracket, and limits the length of the message.
+#define STRIP_HTML_FULL(text, limit) (GLOB.html_tags.Replace(copytext(text, 1, limit), ""))
+
+/**
+ * stuff like `copytext(input, length(input))` will trim the last character of the input,
+ * because DM does it so it copies until the char BEFORE the `end` arg, so we need to bump `end` by 1 in these cases.
+*/
+#define PREVENT_CHARACTER_TRIM_LOSS(integer) (integer + 1)
+
/**
* Pixel-perfect scaled fonts for use in the MAP element as defined in skin.dmf
*
@@ -49,15 +64,6 @@
return_var = text2num(copytext(_measurement, findtextEx(_measurement, "x") + 1)); \
} while(FALSE);
-/// Removes characters incompatible with file names.
-#define SANITIZE_FILENAME(text) (GLOB.filename_forbidden_chars.Replace(text, ""))
-
-/// Simply removes the < and > characters, and limits the length of the message.
-#define STRIP_HTML_SIMPLE(text, limit) (GLOB.angular_brackets.Replace(copytext(text, 1, limit), ""))
-
-/// Removes everything enclose in < and > inclusive of the bracket, and limits the length of the message.
-#define STRIP_HTML_FULL(text, limit) (GLOB.html_tags.Replace(copytext(text, 1, limit), ""))
-
/*
* Uses MAPTEXT to format antag points into a more appealing format
*/
diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm
index d6dde0b6981a5..dd181e86afa4b 100644
--- a/code/__DEFINES/traits/declarations.dm
+++ b/code/__DEFINES/traits/declarations.dm
@@ -1012,6 +1012,9 @@
/// this object has been frozen
#define TRAIT_FROZEN "frozen"
+/// Is runechat for this atom/movable currently disabled, regardless of prefs or anything?
+#define TRAIT_RUNECHAT_HIDDEN "runechat_hidden"
+
/// Currently fishing
#define TRAIT_GONE_FISHING "fishing"
@@ -1024,9 +1027,6 @@
/// Makes a species be better/worse at defending against tackling depending on their tail's status
#define TRAIT_TACKLING_TAILED_DEFENDER "tackling_tailed_defender"
-/// Is runechat for this atom/movable currently disabled, regardless of prefs or anything?
-#define TRAIT_RUNECHAT_HIDDEN "runechat_hidden"
-
/// the object has a label applied
#define TRAIT_HAS_LABEL "labeled"
diff --git a/code/__DEFINES/vv.dm b/code/__DEFINES/vv.dm
index 10d0204ed7ef0..343102e3f591c 100644
--- a/code/__DEFINES/vv.dm
+++ b/code/__DEFINES/vv.dm
@@ -26,8 +26,8 @@
//#define IS_VALID_ASSOC_KEY(V) (istext(V) || ispath(V) || isdatum(V) || islist(V))
#define IS_VALID_ASSOC_KEY(V) (!isnum(V)) //hhmmm..
//General helpers
-#define VV_HREF_TARGET_INTERNAL(target, href_key) "?_src_=vars;[HrefToken()];[href_key]=TRUE;[VV_HK_TARGET]=[REF(target)]"
-#define VV_HREF_TARGETREF_INTERNAL(targetref, href_key) "?_src_=vars;[HrefToken()];[href_key]=TRUE;[VV_HK_TARGET]=[targetref]"
+#define VV_HREF_TARGET_INTERNAL(target, href_key) "byond://?_src_=vars;[HrefToken()];[href_key]=TRUE;[VV_HK_TARGET]=[REF(target)]"
+#define VV_HREF_TARGETREF_INTERNAL(targetref, href_key) "byond://?_src_=vars;[HrefToken()];[href_key]=TRUE;[VV_HK_TARGET]=[targetref]"
#define VV_HREF_TARGET(target, href_key, text) "[text]"
#define VV_HREF_TARGETREF(targetref, href_key, text) "[text]"
#define VV_HREF_TARGET_1V(target, href_key, text, varname) "[text]" //for stuff like basic varedits, one variable
@@ -35,7 +35,7 @@
#define GET_VV_TARGET locate(href_list[VV_HK_TARGET])
#define GET_VV_VAR_TARGET href_list[VV_HK_VARNAME]
//Helper for getting something to vv_do_topic in general
-#define VV_TOPIC_LINK(datum, href_key, text) "text"
+#define VV_TOPIC_LINK(datum, href_key, text) "text"
//Helpers for vv_get_dropdown()
#define VV_DROPDOWN_OPTION(href_key, name) . += ""
#define VV_DROPDOWN_SEPERATOR VV_DROPDOWN_OPTION("", "-----")
diff --git a/code/__HELPERS/AStar.dm b/code/__HELPERS/AStar.dm
index 822d5ebb6239f..3208e7160b0e5 100644
--- a/code/__HELPERS/AStar.dm
+++ b/code/__HELPERS/AStar.dm
@@ -74,33 +74,33 @@ Actual Adjacent procs :
return b.f - a.f
//wrapper that returns an empty list if A* failed to find a path
-/proc/get_path_to(caller, end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableTurftest, id=null, turf/exclude=null, simulated_only = TRUE, get_best_attempt = FALSE)
- var/l = SSpathfinder.mobs.getfree(caller)
+/proc/get_path_to(caller_but_not_a_byond_built_in_proc, end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableTurftest, id=null, turf/exclude=null, simulated_only = TRUE, get_best_attempt = FALSE)
+ var/l = SSpathfinder.mobs.getfree(caller_but_not_a_byond_built_in_proc)
while(!l)
stoplag(3)
- l = SSpathfinder.mobs.getfree(caller)
- var/list/path = AStar(caller, end, dist, maxnodes, maxnodedepth, mintargetdist, adjacent,id, exclude, simulated_only, get_best_attempt)
+ l = SSpathfinder.mobs.getfree(caller_but_not_a_byond_built_in_proc)
+ var/list/path = AStar(caller_but_not_a_byond_built_in_proc, end, dist, maxnodes, maxnodedepth, mintargetdist, adjacent,id, exclude, simulated_only, get_best_attempt)
SSpathfinder.mobs.found(l)
if(!path)
path = list()
return path
-/proc/cir_get_path_to(caller, end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableTurftest, id=null, turf/exclude=null, simulated_only = TRUE)
- var/l = SSpathfinder.circuits.getfree(caller)
+/proc/cir_get_path_to(caller_but_not_a_byond_built_in_proc, end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableTurftest, id=null, turf/exclude=null, simulated_only = TRUE)
+ var/l = SSpathfinder.circuits.getfree(caller_but_not_a_byond_built_in_proc)
while(!l)
stoplag(3)
- l = SSpathfinder.circuits.getfree(caller)
- var/list/path = AStar(caller, end, dist, maxnodes, maxnodedepth, mintargetdist, adjacent,id, exclude, simulated_only)
+ l = SSpathfinder.circuits.getfree(caller_but_not_a_byond_built_in_proc)
+ var/list/path = AStar(caller_but_not_a_byond_built_in_proc, end, dist, maxnodes, maxnodedepth, mintargetdist, adjacent,id, exclude, simulated_only)
SSpathfinder.circuits.found(l)
if(!path)
path = list()
return path
/// Pathfinding for bots
-/proc/AStar(caller, _end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableTurftest, id=null, turf/exclude=null, simulated_only = TRUE, get_best_attempt = FALSE)
+/proc/AStar(caller_but_not_a_byond_built_in_proc, _end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableTurftest, id=null, turf/exclude=null, simulated_only = TRUE, get_best_attempt = FALSE)
//sanitation
var/turf/end = get_turf(_end)
- var/turf/start = get_turf(caller)
+ var/turf/start = get_turf(caller_but_not_a_byond_built_in_proc)
if(!start || !end)
stack_trace("Invalid A* start or destination")
return FALSE
@@ -150,12 +150,12 @@ Actual Adjacent procs :
//is already in open list, check if it's a better way from the current turf
CN.bf &= 15^r //we have no closed, so just cut off exceed dir.00001111 ^ reverse_dir.We don't need to expand to checked turf.
if((newg < CN.g) )
- if(call(cur.source,adjacent)(caller, T, id, simulated_only))
+ if(call(cur.source,adjacent)(caller_but_not_a_byond_built_in_proc, T, id, simulated_only))
CN.setp(cur,newg,CN.h,cur.nt+1)
open.ReSort(CN)//reorder the changed element in the list
else
//is not already in open list, so add it
- if(call(cur.source,adjacent)(caller, T, id, simulated_only))
+ if(call(cur.source,adjacent)(caller_but_not_a_byond_built_in_proc, T, id, simulated_only))
CN = new(T,cur,newg,call(T,dist)(end),cur.nt+1,15^r)
open.Insert(CN)
openc[T] = CN
@@ -179,8 +179,8 @@ Actual Adjacent procs :
//Returns adjacent turfs in cardinal directions that are reachable
//simulated_only controls whether only simulated turfs are considered or not
-/// Returns a list the src/caller can cross into
-/turf/proc/reachableAdjacentTurfs(caller, ID, simulated_only)
+/// Returns a list the src/caller_but_not_a_byond_built_in_proc can cross into
+/turf/proc/reachableAdjacentTurfs(caller_but_not_a_byond_built_in_proc, ID, simulated_only)
var/list/L = new()
var/turf/T
var/static/space_type_cache = typecacheof(/turf/open/space)
@@ -189,12 +189,12 @@ Actual Adjacent procs :
T = get_step(src,GLOB.cardinals[k])
if(!T || (simulated_only && space_type_cache[T.type]))
continue
- if(!T.density && !LinkBlockedWithAccess(T,caller, ID))
+ if(!T.density && !LinkBlockedWithAccess(T,caller_but_not_a_byond_built_in_proc, ID))
L.Add(T)
return L
-/turf/proc/reachableTurftest(caller, turf/T, ID, simulated_only)
- if(T && !T.density && !(simulated_only && SSpathfinder.space_type_cache[T.type]) && !LinkBlockedWithAccess(T,caller, ID))
+/turf/proc/reachableTurftest(caller_but_not_a_byond_built_in_proc, turf/T, ID, simulated_only)
+ if(T && !T.density && !(simulated_only && SSpathfinder.space_type_cache[T.type]) && !LinkBlockedWithAccess(T,caller_but_not_a_byond_built_in_proc, ID))
return TRUE
/// Returns adjacent turfs in cardinal directions that are reachable via atmos
@@ -202,7 +202,7 @@ Actual Adjacent procs :
return atmos_adjacent_turfs
/// Check if there is a door that needs access in its way
-/turf/proc/LinkBlockedWithAccess(turf/T, caller, ID)
+/turf/proc/LinkBlockedWithAccess(turf/T, caller_but_not_a_byond_built_in_proc, ID)
var/adir = get_dir(src, T)
var/rdir = ((adir & MASK_ODD)<<1)|((adir & MASK_EVEN)>>1)
for(var/obj/structure/window/W in src)
@@ -212,29 +212,29 @@ Actual Adjacent procs :
if(!W.CanAStarPass(ID, adir))
return TRUE
for(var/obj/machinery/M in src)
- if(!M.CanAStarPass(ID, adir, caller))
+ if(!M.CanAStarPass(ID, adir, caller_but_not_a_byond_built_in_proc))
return TRUE
for(var/obj/machinery/door/firedoor/border_only/W in src)
- if(!W.CanAStarPass(ID, adir, caller))
+ if(!W.CanAStarPass(ID, adir, caller_but_not_a_byond_built_in_proc))
return TRUE
for(var/obj/O in T)
- if(!O.CanAStarPass(ID, rdir, caller))
+ if(!O.CanAStarPass(ID, rdir, caller_but_not_a_byond_built_in_proc))
return TRUE
return FALSE
//yog procs
-/turf/proc/reachableTurftestPlayer(caller, turf/T, ID, simulated_only)
- if(T && !T.density && !LinkBlockedWithAccess(T, caller, ID) && !(simulated_only && SSpathfinder.space_type_cache[T.type]))
+/turf/proc/reachableTurftestPlayer(caller_but_not_a_byond_built_in_proc, turf/T, ID, simulated_only)
+ if(T && !T.density && !LinkBlockedWithAccess(T, caller_but_not_a_byond_built_in_proc, ID) && !(simulated_only && SSpathfinder.space_type_cache[T.type]))
return TRUE
-/turf/proc/reachableTurftestdensity(caller, turf/T, ID, simulated_only) //used for the sake of pathfinding while excluding turfs with dense objects
- if(T && !T.density && !(simulated_only && SSpathfinder.space_type_cache[T.type]) && !LinkBlockedWithAccess(T,caller, ID))
+/turf/proc/reachableTurftestdensity(caller_but_not_a_byond_built_in_proc, turf/T, ID, simulated_only) //used for the sake of pathfinding while excluding turfs with dense objects
+ if(T && !T.density && !(simulated_only && SSpathfinder.space_type_cache[T.type]) && !LinkBlockedWithAccess(T,caller_but_not_a_byond_built_in_proc, ID))
for(var/obj/D in T)
if(!istype(D, /obj/structure/window) && D.density) //had to do it silly like this so rwindows didn't stop it outright
return FALSE
return TRUE
-/turf/proc/wiringTurfTest(caller, turf/T, ID, simulated_only)
+/turf/proc/wiringTurfTest(caller_but_not_a_byond_built_in_proc, turf/T, ID, simulated_only)
if(T && !T.density && !istype(T.loc, /area/space))
return TRUE
diff --git a/code/__HELPERS/logging/_logging.dm b/code/__HELPERS/logging/_logging.dm
index 8924262be636a..1bc4de8e488b1 100644
--- a/code/__HELPERS/logging/_logging.dm
+++ b/code/__HELPERS/logging/_logging.dm
@@ -301,11 +301,11 @@
if(key)
if(C && C.holder && C.holder.fakekey && !include_name)
if(include_link)
- . += ""
+ . += ""
. += "Administrator"
else
if(include_link)
- . += ""
+ . += ""
. += key
if(!C)
. += "\[DC\]"
diff --git a/code/__HELPERS/roundend.dm b/code/__HELPERS/roundend.dm
index b968d7e33ff05..2891b17e7199f 100644
--- a/code/__HELPERS/roundend.dm
+++ b/code/__HELPERS/roundend.dm
@@ -354,7 +354,7 @@
if(GLOB.round_id)
var/statspage = CONFIG_GET(string/roundstatsurl)
- var/info = statspage ? "[GLOB.round_id]" : GLOB.round_id
+ var/info = statspage ? "[GLOB.round_id]" : GLOB.round_id
parts += "[GLOB.TAB]Round ID: [info]"
parts += "[GLOB.TAB]Shift Duration: [DisplayTimeText(world.time - SSticker.round_start_time)]"
parts += "[GLOB.TAB]Station Integrity: [SSgamemode.station_was_nuked ? span_redtext("Destroyed") : "[popcount["station_integrity"]]%"]"
@@ -610,7 +610,7 @@
var/datum/action/report/R = new
C.player_details.player_actions += R
R.Grant(C.mob)
- to_chat(C,"Show roundend report again")
+ to_chat(C,"Show roundend report again")
/datum/action/report
name = "Show roundend report"
diff --git a/code/__HELPERS/stat_tracking.dm b/code/__HELPERS/stat_tracking.dm
index 097715b940474..2f55ea0455dfd 100644
--- a/code/__HELPERS/stat_tracking.dm
+++ b/code/__HELPERS/stat_tracking.dm
@@ -8,6 +8,6 @@
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)]")]")
+ user << browse(HTML_SKELETON("
[lines.Join("
")]
"), "window=[url_encode("stats:[REF(stats)]")]")
. = lines.Join("\n")
diff --git a/code/__HELPERS/verbs.dm b/code/__HELPERS/verbs.dm
index 3606c7d918af9..7c8382fec834b 100644
--- a/code/__HELPERS/verbs.dm
+++ b/code/__HELPERS/verbs.dm
@@ -43,9 +43,8 @@
for(var/thing in verbs_list)
var/procpath/verb_to_add = thing
output_list[++output_list.len] = list(verb_to_add.category, verb_to_add.name)
- output_list = url_encode(json_encode(output_list))
- target << output("[output_list];", "statbrowser:add_verb_list")
+ target.stat_panel.send_message("add_verb_list", output_list)
/**
* handles removing verb and sending it to browser to update, use this for removing verbs
@@ -91,6 +90,5 @@
for(var/thing in verbs_list)
var/procpath/verb_to_remove = thing
output_list[++output_list.len] = list(verb_to_remove.category, verb_to_remove.name)
- output_list = url_encode(json_encode(output_list))
- target << output("[output_list];", "statbrowser:remove_verb_list")
+ target.stat_panel.send_message("remove_verb_list", output_list)
diff --git a/code/__byond_version_compat.dm b/code/__byond_version_compat.dm
index 7e69aa34da46b..b9b36d93869fa 100644
--- a/code/__byond_version_compat.dm
+++ b/code/__byond_version_compat.dm
@@ -10,11 +10,11 @@
#endif
//If you update these values, update the message in the #error
-#define MAX_BYOND_MAJOR 515
-#define MAX_BYOND_MINOR 1647
+#define MAX_BYOND_MAJOR 516
+#define MAX_BYOND_MINOR 1659
#if ((DM_VERSION > MAX_BYOND_MAJOR) || (DM_BUILD > MAX_BYOND_MINOR)) && !defined(SPACEMAN_DMM)
#error Your version of BYOND is too new to compile this project.
-#error Download version 515.1647 at www.byond.com/download/build/515/515.1642_byond.exe
+#error Download version 515.1659 at www.byond.com/download/build/515/515.1659_byond.exe
#endif
// 515 split call for external libraries into call_ext
diff --git a/code/_globalvars/misc.dm b/code/_globalvars/misc.dm
index b407cf78d19c5..ffd51d879015f 100644
--- a/code/_globalvars/misc.dm
+++ b/code/_globalvars/misc.dm
@@ -17,7 +17,7 @@ GLOBAL_LIST_EMPTY(powernets)
GLOBAL_VAR_INIT(bsa_unlock, FALSE) //BSA unlocked by head ID swipes
-GLOBAL_LIST_EMPTY(player_details) // ckey -> /datum/player_details
+GLOBAL_LIST_EMPTY_TYPED(player_details, /datum/player_details) // ckey -> /datum/player_details
GLOBAL_LIST_INIT(preview_backgrounds, list(
"floor" = "Default Tile",
diff --git a/code/_onclick/ai.dm b/code/_onclick/ai.dm
index 35ea5b3742829..c93db6a54011a 100644
--- a/code/_onclick/ai.dm
+++ b/code/_onclick/ai.dm
@@ -222,9 +222,9 @@
to_chat(user, span_warning("Unable to track 'Unknown' persons! Their name must be visible."))
return
if(src == user.cameraMemoryTarget)
- to_chat(user, span_warning("Stop tracking this individual? \[UNTRACK\]"))
+ to_chat(user, span_warning("Stop tracking this individual? \[UNTRACK\]"))
else
- to_chat(user, span_warning("Track this individual? \[TRACK\]"))
+ to_chat(user, span_warning("Track this individual? \[TRACK\]"))
return
//
diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm
index b86586ecfbccb..a3714ea68c04f 100644
--- a/code/_onclick/click.dm
+++ b/code/_onclick/click.dm
@@ -364,14 +364,14 @@
var/turf/T = get_turf(src)
if(T && user.TurfAdjacent(T))
user.listed_turf = T
- user.client << output("[url_encode(json_encode(T.name))];", "statbrowser:create_listedturf")
+ user.client.stat_panel.send_message("create_listedturf", T.name)
// Use this instead of /mob/proc/AltClickOn(atom/A) where you only want turf content listing without additional atom alt-click interaction
/atom/proc/AltClickNoInteract(mob/user, atom/A)
var/turf/T = get_turf(A)
if(T && user.TurfAdjacent(T))
user.listed_turf = T
- user.client << output("[url_encode(json_encode(T.name))];", "statbrowser:create_listedturf")
+ user.client.stat_panel.send_message("create_listedturf", T.name)
/mob/proc/TurfAdjacent(turf/T)
return T.Adjacent(src)
diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm
index 85099e5bdcd7e..ce9d3464aae19 100644
--- a/code/_onclick/hud/hud.dm
+++ b/code/_onclick/hud/hud.dm
@@ -107,6 +107,10 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
// and avoid needing to make changes to all idk 300 consumers if we want to change the appearance
var/list/asset_refs_for_reuse = list()
+ /// The BYOND version of the client that was last logged into this mob.
+ /// Currently used to rebuild all plane master groups when going between 515<->516.
+ var/last_byond_version
+
/datum/hud/New(mob/owner)
mymob = owner
@@ -153,8 +157,20 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
/datum/hud/proc/client_refresh(datum/source)
SIGNAL_HANDLER
- RegisterSignal(mymob.canon_client, COMSIG_CLIENT_SET_EYE, PROC_REF(on_eye_change))
- on_eye_change(null, null, mymob.canon_client.eye)
+ var/client/client = mymob.canon_client
+ var/new_byond_version = client.byond_version
+#if MIN_COMPILER_VERSION > 515
+ #warn Fully change default relay_loc to "1,1", rather than changing it based on client version
+#endif
+ if(!isnull(last_byond_version) && new_byond_version != last_byond_version)
+ var/new_relay_loc = (new_byond_version > 515) ? "1,1" : "CENTER"
+ for(var/group_key as anything in master_groups)
+ var/datum/plane_master_group/group = master_groups[group_key]
+ group.relay_loc = new_relay_loc
+ group.rebuild_hud()
+ last_byond_version = new_byond_version
+ RegisterSignal(client, COMSIG_CLIENT_SET_EYE, PROC_REF(on_eye_change))
+ on_eye_change(null, null, client.eye)
/datum/hud/proc/clear_client(datum/source)
SIGNAL_HANDLER
diff --git a/code/_onclick/hud/parallax.dm b/code/_onclick/hud/parallax.dm
index c689726a2c454..ef95d619efb54 100755
--- a/code/_onclick/hud/parallax.dm
+++ b/code/_onclick/hud/parallax.dm
@@ -111,7 +111,7 @@
create_parallax(screen_mob)
update_parallax(screen_mob)
-// This sets which way the current shuttle is moving (returns true if the shuttle has stopped moving so the caller can append their animation)
+// This sets which way the current shuttle is moving (returns true if the shuttle has stopped moving so the caller_but_not_a_byond_built_in_proc can append their animation)
/datum/hud/proc/set_parallax_movedir(new_parallax_movedir = 0, skip_windups, mob/viewmob)
. = FALSE
var/mob/screenmob = viewmob || mymob
diff --git a/code/_onclick/hud/rendering/plane_master_group.dm b/code/_onclick/hud/rendering/plane_master_group.dm
index 1aa9cd28ace7c..247db0515ad1d 100644
--- a/code/_onclick/hud/rendering/plane_master_group.dm
+++ b/code/_onclick/hud/rendering/plane_master_group.dm
@@ -47,11 +47,19 @@
stack_trace("Hey brother, our key [key] is already in use by a plane master group on the passed in hud, belonging to [viewing_hud.mymob]. Ya fucked up, why are there dupes")
return
+#if MIN_COMPILER_VERSION > 516
+ #warn Fully change default relay_loc to "1,1", rather than changing it based on client version
+#endif
+
set_hud(viewing_hud)
our_hud.master_groups[key] = src
show_hud()
transform_lower_turfs(our_hud, active_offset)
+ if(viewing_hud.mymob?.client?.byond_version > 515)
+ relay_loc = "1,1"
+ rebuild_plane_masters()
+
/// Well, refresh our group, mostly useful for plane specific updates
/datum/plane_master_group/proc/refresh_hud()
hide_hud()
@@ -62,6 +70,7 @@
hide_hud()
rebuild_plane_masters()
show_hud()
+ our_hud.update_parallax_pref()
transform_lower_turfs(our_hud, active_offset)
/// Regenerate our plane masters, this is useful if we don't have a mob but still want to rebuild. Such in the case of changing the screen_loc of relays
@@ -183,19 +192,16 @@
/// If you wanna try someday feel free, but I can't manage it
/datum/plane_master_group/popup
-/// This is janky as hell but since something changed with CENTER positioning after build 1614 we have to switch to the bandaid LEFT,TOP positioning
-/// using LEFT,TOP *at* or *before* 1614 will result in another broken offset for cameras
-#define MAX_CLIENT_BUILD_WITH_WORKING_SECONDARY_MAPS 1614
-
+/// Note do not use return ..() because it will cause client crush when screen gets deleted
+/// TOOD: Remove this entirely when 516 is stable
/datum/plane_master_group/popup/attach_to(datum/hud/viewing_hud)
- // If we're about to display this group to a mob who's client is more recent than the last known version with working CENTER, then we need to remake the relays
- // with the correct screen_loc using the relay override
- if(viewing_hud.mymob?.client?.byond_build > MAX_CLIENT_BUILD_WITH_WORKING_SECONDARY_MAPS)
- relay_loc = "LEFT,TOP"
- rebuild_plane_masters()
- return ..()
-
-#undef MAX_CLIENT_BUILD_WITH_WORKING_SECONDARY_MAPS
+ if(viewing_hud.master_groups[key])
+ stack_trace("[key] is already in use by a plane master group on the passed in hud, belonging to [viewing_hud.mymob]!")
+ return
+ relay_loc = "1,1"
+ rebuild_plane_masters()
+ set_hud(viewing_hud)
+ show_hud()
/datum/plane_master_group/popup/transform_lower_turfs(datum/hud/source, new_offset, use_scale = TRUE)
return ..(source, new_offset, FALSE)
diff --git a/code/_onclick/hud/rendering/render_plate.dm b/code/_onclick/hud/rendering/render_plate.dm
index 8cafa5d2599f0..c7cc41040c7dd 100644
--- a/code/_onclick/hud/rendering/render_plate.dm
+++ b/code/_onclick/hud/rendering/render_plate.dm
@@ -382,7 +382,7 @@
render_relay_planes = list(RENDER_PLANE_MASTER)
/**
- * Plane master proc called in Initialize() that creates relay objects, and sets them uo as needed
+ * Plane master proc called in Initialize() that creates relay objects, and sets them up as needed
* Sets:
* * layer from plane to avoid z-fighting
* * planes to relay the render to
@@ -392,6 +392,9 @@
* Other vars such as alpha will automatically be applied with the render source
*/
/atom/movable/screen/plane_master/proc/generate_render_relays()
+#if MIN_COMPILER_VERSION > 516
+ #warn Fully change default relay_loc to "1,1"
+#endif
var/relay_loc = home?.relay_loc || "CENTER"
// If we're using a submap (say for a popup window) make sure we draw onto it
if(home?.map)
@@ -426,7 +429,7 @@
if(!length(relays) && !initial(render_target))
render_target = OFFSET_RENDER_TARGET(get_plane_master_render_base(name), offset)
if(!relay_loc)
- relay_loc = "CENTER"
+ relay_loc = (show_to?.byond_version > 515) ? "1,1" : "CENTER"
// If we're using a submap (say for a popup window) make sure we draw onto it
if(home?.map)
relay_loc = "[home.map]:[relay_loc]"
diff --git a/code/controllers/subsystem/blackbox.dm b/code/controllers/subsystem/blackbox.dm
index 68fade5867eaa..aabbc6ac7e9de 100644
--- a/code/controllers/subsystem/blackbox.dm
+++ b/code/controllers/subsystem/blackbox.dm
@@ -93,7 +93,7 @@ SUBSYSTEM_DEF(blackbox)
for(var/player_key in GLOB.player_details)
var/datum/player_details/PD = GLOB.player_details[player_key]
- record_feedback("tally", "client_byond_version", 1, PD.byond_version)
+ record_feedback("tally", "client_byond_version", 1, PD.full_byond_version())
/datum/controller/subsystem/blackbox/Shutdown()
sealed = FALSE
diff --git a/code/controllers/subsystem/dbcore.dm b/code/controllers/subsystem/dbcore.dm
index 34152e98ee59a..f1b53498c000b 100644
--- a/code/controllers/subsystem/dbcore.dm
+++ b/code/controllers/subsystem/dbcore.dm
@@ -427,7 +427,7 @@ Delayed insert mode was removed in mysql 7 and only works with MyISAM type table
return FALSE
/datum/DBQuery/proc/slow_query_check()
- message_admins("HEY! A database query timed out. Did the server just hang? \[YES\]|\[NO\]")
+ message_admins("HEY! A database query timed out. Did the server just hang? \[YES\]|\[NO\]")
/datum/DBQuery/proc/NextRow(async = TRUE)
Activity("NextRow")
diff --git a/code/controllers/subsystem/lag_switch.dm b/code/controllers/subsystem/lag_switch.dm
index 69b740b8dabf6..e4ab27efbf0eb 100644
--- a/code/controllers/subsystem/lag_switch.dm
+++ b/code/controllers/subsystem/lag_switch.dm
@@ -34,7 +34,7 @@ SUBSYSTEM_DEF(lag_switch)
auto_switch = FALSE
UnregisterSignal(SSdcs, COMSIG_GLOB_CLIENT_CONNECT)
veto_timer_id = addtimer(CALLBACK(src, PROC_REF(set_all_measures), TRUE, TRUE), 20 SECONDS, TIMER_STOPPABLE)
- message_admins("Lag Switch population threshold reached. Automatic activation of lag mitigation measures occuring in 20 seconds. (CANCEL)")
+ message_admins("Lag Switch population threshold reached. Automatic activation of lag mitigation measures occuring in 20 seconds. (CANCEL)")
log_admin("Lag Switch population threshold reached. Automatic activation of lag mitigation measures occuring in 20 seconds.")
/// (En/Dis)able automatic triggering of switches based on client count
diff --git a/code/controllers/subsystem/runechat.dm b/code/controllers/subsystem/runechat.dm
index a5193a508ad63..663bb8cf347d7 100644
--- a/code/controllers/subsystem/runechat.dm
+++ b/code/controllers/subsystem/runechat.dm
@@ -1,243 +1,14 @@
-/// Controls how many buckets should be kept, each representing a tick. (30 seconds worth)
-#define BUCKET_LEN (world.fps * 1 * 30)
-/// Helper for getting the correct bucket for a given chatmessage
-#define BUCKET_POS(scheduled_destruction) (((round((scheduled_destruction - SSrunechat.head_offset) / world.tick_lag) + 1) % BUCKET_LEN) || BUCKET_LEN)
-/// Gets the maximum time at which messages will be handled in buckets, used for deferring to secondary queue
-#define BUCKET_LIMIT (world.time + TICKS2DS(min(BUCKET_LEN - (SSrunechat.practical_offset - DS2TICKS(world.time - SSrunechat.head_offset)) - 1, BUCKET_LEN - 1)))
-
-/**
- * # Runechat Subsystem
- *
- * Maintains a timer-like system to handle destruction of runechat messages. Much of this code is modeled
- * after or adapted from the timer subsystem.
- *
- * Note that this has the same structure for storing and queueing messages as the timer subsystem does
- * for handling timers: the bucket_list is a list of chatmessage datums, each of which are the head
- * of a circularly linked list. Any given index in bucket_list could be null, representing an empty bucket.
- */
-SUBSYSTEM_DEF(runechat)
+TIMER_SUBSYSTEM_DEF(runechat)
name = "Runechat"
- flags = SS_TICKER | SS_NO_INIT
- wait = 1
priority = FIRE_PRIORITY_RUNECHAT
- /// world.time of the first entry in the bucket list, effectively the 'start time' of the current buckets
- var/head_offset = 0
- /// Index of the first non-empty bucket
- var/practical_offset = 1
- /// world.tick_lag the bucket was designed for
- var/bucket_resolution = 0
- /// How many messages are in the buckets
- var/bucket_count = 0
- /// List of buckets, each bucket holds every message that has to be killed that byond tick
- var/list/bucket_list = list()
- /// Queue used for storing messages that are scheduled for deletion too far in the future for the buckets
- var/list/datum/chatmessage/second_queue = list()
-
-/datum/controller/subsystem/runechat/PreInit()
- bucket_list.len = BUCKET_LEN
- head_offset = world.time
- bucket_resolution = world.tick_lag
-
-/datum/controller/subsystem/runechat/stat_entry(msg)
- msg += "ActMsgs:[bucket_count] SecQueue:[length(second_queue)]"
- return msg
-
-/datum/controller/subsystem/runechat/get_metrics()
- . = ..()
- .["buckets"] = bucket_count
- .["second_queue"] = length(second_queue)
-
-/datum/controller/subsystem/runechat/fire(resumed = FALSE)
- // Store local references to datum vars as it is faster to access them this way
- var/list/bucket_list = src.bucket_list
-
- if (MC_TICK_CHECK)
- return
-
- // Check for when we need to loop the buckets, this occurs when
- // the head_offset is approaching BUCKET_LEN ticks in the past
- if (practical_offset > BUCKET_LEN)
- head_offset += TICKS2DS(BUCKET_LEN)
- practical_offset = 1
- resumed = FALSE
-
- // Check for when we have to reset buckets, typically from auto-reset
- if ((length(bucket_list) != BUCKET_LEN) || (world.tick_lag != bucket_resolution))
- reset_buckets()
- bucket_list = src.bucket_list
- resumed = FALSE
-
- // Store a reference to the 'working' chatmessage so that we can resume if the MC
- // has us stop mid-way through processing
- var/static/datum/chatmessage/cm
- if (!resumed)
- cm = null
-
- // Iterate through each bucket starting from the practical offset
- while (practical_offset <= BUCKET_LEN && head_offset + ((practical_offset - 1) * world.tick_lag) <= world.time)
- var/datum/chatmessage/bucket_head = bucket_list[practical_offset]
- if (!cm || !bucket_head || cm == bucket_head)
- bucket_head = bucket_list[practical_offset]
- cm = bucket_head
-
- while (cm)
- // If the chatmessage hasn't yet had its life ended then do that now
- var/datum/chatmessage/next = cm.next
- if (!cm.eol_complete)
- cm.end_of_life()
- else if (!QDELETED(cm)) // otherwise if we haven't deleted it yet, do so (this is after EOL completion)
- qdel(cm)
-
- if (MC_TICK_CHECK)
- return
-
- // Break once we've processed the entire bucket
- cm = next
- if (cm == bucket_head)
- break
-
- // Empty the bucket, check if anything in the secondary queue should be shifted to this bucket
- bucket_list[practical_offset++] = null
- var/i = 0
- for (i in 1 to length(second_queue))
- cm = second_queue[i]
- if (cm.scheduled_destruction >= BUCKET_LIMIT)
- i--
- break
-
- // Transfer the message into the bucket, performing necessary circular doubly-linked list operations
- bucket_count++
- var/bucket_pos = max(1, BUCKET_POS(cm.scheduled_destruction))
- var/datum/timedevent/head = bucket_list[bucket_pos]
- if (!head)
- bucket_list[bucket_pos] = cm
- cm.next = null
- cm.prev = null
- continue
-
- if (!head.prev)
- head.prev = head
- cm.next = head
- cm.prev = head.prev
- cm.next.prev = cm
- cm.prev.next = cm
- if (i)
- second_queue.Cut(1, i + 1)
- cm = null
-
-/datum/controller/subsystem/runechat/Recover()
- bucket_list |= SSrunechat.bucket_list
- second_queue |= SSrunechat.second_queue
-
-/datum/controller/subsystem/runechat/proc/reset_buckets()
- bucket_list.len = BUCKET_LEN
- head_offset = world.time
- bucket_resolution = world.tick_lag
-
-/**
- * Enters the runechat subsystem with this chatmessage, inserting it into the end-of-life queue
- *
- * This will also account for a chatmessage already being registered, and in which case
- * the position will be updated to remove it from the previous location if necessary
- *
- * Arguments:
- * * new_sched_destruction Optional, when provided is used to update an existing message with the new specified time
- */
-/datum/chatmessage/proc/enter_subsystem(new_sched_destruction = 0)
- // Get local references from subsystem as they are faster to access than the datum references
- var/list/bucket_list = SSrunechat.bucket_list
- var/list/second_queue = SSrunechat.second_queue
-
- // When necessary, de-list the chatmessage from its previous position
- if (new_sched_destruction)
- if (scheduled_destruction >= BUCKET_LIMIT)
- second_queue -= src
- else
- SSrunechat.bucket_count--
- var/bucket_pos = BUCKET_POS(scheduled_destruction)
- if (bucket_pos > 0)
- var/datum/chatmessage/bucket_head = bucket_list[bucket_pos]
- if (bucket_head == src)
- bucket_list[bucket_pos] = next
- if (prev != next)
- prev.next = next
- next.prev = prev
- else
- prev?.next = null
- next?.prev = null
- prev = next = null
- scheduled_destruction = new_sched_destruction
-
- // Ensure the scheduled destruction time is properly bound to avoid missing a scheduled event
- scheduled_destruction = max(CEILING(scheduled_destruction, world.tick_lag), world.time + world.tick_lag)
-
- // Handle insertion into the secondary queue if the required time is outside our tracked amounts
- if (scheduled_destruction >= BUCKET_LIMIT)
- BINARY_INSERT(src, SSrunechat.second_queue, /datum/chatmessage, src, scheduled_destruction, COMPARE_KEY)
- return
-
- // Get bucket position and a local reference to the datum var, it's faster to access this way
- var/bucket_pos = BUCKET_POS(scheduled_destruction)
-
- // Get the bucket head for that bucket, increment the bucket count
- var/datum/chatmessage/bucket_head = bucket_list[bucket_pos]
- SSrunechat.bucket_count++
-
- // If there is no existing head of this bucket, we can set this message to be that head
- if (!bucket_head)
- bucket_list[bucket_pos] = src
- return
-
- // Otherwise it's a simple insertion into the circularly doubly-linked list
- if (!bucket_head.prev)
- bucket_head.prev = bucket_head
- next = bucket_head
- prev = bucket_head.prev
- next.prev = src
- prev.next = src
-
-
-/**
- * Removes this chatmessage datum from the runechat subsystem
- */
-/datum/chatmessage/proc/leave_subsystem()
- // Attempt to find the bucket that contains this chat message
- var/bucket_pos = BUCKET_POS(scheduled_destruction)
-
- // Get local references to the subsystem's vars, faster than accessing on the datum
- var/list/bucket_list = SSrunechat.bucket_list
- var/list/second_queue = SSrunechat.second_queue
-
- // Attempt to get the head of the bucket
- var/datum/chatmessage/bucket_head
- if (bucket_pos > 0)
- bucket_head = bucket_list[bucket_pos]
-
- // Decrement the number of messages in buckets if the message is
- // the head of the bucket, or has a SD less than BUCKET_LIMIT implying it fits
- // into an existing bucket, or is otherwise not present in the secondary queue
- if(bucket_head == src)
- bucket_list[bucket_pos] = next
- SSrunechat.bucket_count--
- else if(scheduled_destruction < BUCKET_LIMIT)
- SSrunechat.bucket_count--
- else
- var/l = length(second_queue)
- second_queue -= src
- if(l == length(second_queue))
- SSrunechat.bucket_count--
-
- // Remove the message from the bucket, ensuring to maintain
- // the integrity of the bucket's list if relevant
- if(prev != next)
- prev.next = next
- next.prev = prev
- else
- prev?.next = null
- next?.prev = null
- prev = next = null
+ var/list/datum/callback/message_queue = list()
-#undef BUCKET_LEN
-#undef BUCKET_POS
-#undef BUCKET_LIMIT
+/datum/controller/subsystem/timer/runechat/fire(resumed)
+ . = ..() //poggers
+ while(message_queue.len)
+ var/datum/callback/queued_message = message_queue[message_queue.len]
+ queued_message.Invoke()
+ message_queue.len--
+ if(MC_TICK_CHECK)
+ return
diff --git a/code/controllers/subsystem/shuttle.dm b/code/controllers/subsystem/shuttle.dm
index 8a9c1fbbb0d08..7f3e7c427032b 100644
--- a/code/controllers/subsystem/shuttle.dm
+++ b/code/controllers/subsystem/shuttle.dm
@@ -373,7 +373,7 @@ SUBSYSTEM_DEF(shuttle)
if(call_reason)
SSblackbox.record_feedback("text", "shuttle_reason", 1, "[call_reason]")
log_game("Shuttle call reason: [call_reason]")
- message_admins("[ADMIN_LOOKUPFLW(user)] has called the shuttle. (TRIGGER CENTCOM RECALL)")
+ message_admins("[ADMIN_LOOKUPFLW(user)] has called the shuttle. (TRIGGER CENTCOM RECALL)")
/datum/controller/subsystem/shuttle/proc/centcom_recall(old_timer, admiral_message)
if(emergency.mode != SHUTTLE_CALL || emergency.timer != old_timer)
diff --git a/code/controllers/subsystem/statpanel.dm b/code/controllers/subsystem/statpanel.dm
index de3b751f1f15e..3ffe7d8558f1c 100644
--- a/code/controllers/subsystem/statpanel.dm
+++ b/code/controllers/subsystem/statpanel.dm
@@ -6,16 +6,18 @@ SUBSYSTEM_DEF(statpanels)
runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY
flags = SS_NO_INIT
var/list/currentrun = list()
- var/encoded_global_data
- var/mc_data_encoded
- var/list/cached_images = list()
+ var/list/global_data
+ var/list/mc_data
+ var/list/cached_images = list()
///how many subsystem fires between most tab updates
var/default_wait = 10
///how many subsystem fires between updates of the status tab
var/status_wait = 2
///how many subsystem fires between updates of the MC tab
var/mc_wait = 5
+ /// how many subsystem fires between updates of the turf examine tab
+ var/turf_wait = 2
///how many full runs this subsystem has completed. used for variable rate refreshes.
var/num_fires = 0
@@ -23,7 +25,6 @@ SUBSYSTEM_DEF(statpanels)
if (!resumed)
num_fires++
var/datum/map_config/cached = SSmapping.next_map_config
- var/round_time = world.time - SSticker.round_start_time
var/storyteller = "Not Set"
if(SSgamemode.storyteller)
@@ -35,13 +36,13 @@ SUBSYSTEM_DEF(statpanels)
if(SSgamemode.secret_storyteller)
storyteller = "Secret"
- var/list/global_data = list(
+ global_data = list(
"Map: [SSmapping.config?.map_name || "Loading..."]",
cached ? "Next Map: [cached.map_name]" : null,
"Storyteller: [storyteller]",
"Round ID: [GLOB.round_id ? GLOB.round_id : "NULL"]",
"Server Time: [time2text(world.timeofday, "YYYY-MM-DD hh:mm:ss")]",
- "Round Time: [round_time > MIDNIGHT_ROLLOVER ? "[round(round_time/MIDNIGHT_ROLLOVER)]:[worldtime2text()]" : worldtime2text()]",
+ "Round Time: [ROUND_TIME()]",
"Station Time: [station_time_timestamp()]",
"Time Dilation: [round(SStime_track.time_dilation_current,1)]% AVG:([round(SStime_track.time_dilation_avg_fast,1)]%, [round(SStime_track.time_dilation_avg,1)]%, [round(SStime_track.time_dilation_avg_slow,1)]%)"
)
@@ -50,38 +51,43 @@ SUBSYSTEM_DEF(statpanels)
var/ETA = SSshuttle.emergency.getModeStr()
if(ETA)
global_data += "[ETA] [SSshuttle.emergency.getTimerStr()]"
- encoded_global_data = url_encode(json_encode(global_data))
+
src.currentrun = GLOB.clients.Copy()
- mc_data_encoded = null
+ mc_data = null
+
var/list/currentrun = src.currentrun
while(length(currentrun))
var/client/target = currentrun[length(currentrun)]
currentrun.len--
- if(!target)
- continue
- if(!target.statbrowser_ready)
+
+ if(!target.stat_panel.is_ready())
continue
+
if(target.stat_tab == "Status" && num_fires % status_wait == 0)
set_status_tab(target)
+
if(!target.holder)
- target << output("", "statbrowser:remove_admin_tabs")
+ target.stat_panel.send_message("remove_admin_tabs")
else
- target << output("[!!(target.prefs.extra_toggles & SPLIT_ADMIN_TABS)]", "statbrowser:update_split_admin_tabs")
+ target.stat_panel.send_message("update_split_admin_tabs", !!(target.prefs.extra_toggles & SPLIT_ADMIN_TABS))
+
if(!("MC" in target.panel_tabs))
- target << output("[url_encode(target.holder.href_token)]", "statbrowser:add_admin_tabs")
-
- //if(target.stat_tab == "MC" && (num_fires % mc_wait == 0))
- if(target.stat_tab == "MC" && ((num_fires % mc_wait == 0) || (target?.prefs.extra_toggles & FAST_MC_REFRESH)))
+ target.stat_panel.send_message("add_admin_tabs", target.holder.href_token)
+
+ if(target.stat_tab == "MC" && ((num_fires % mc_wait == 0)))
set_MC_tab(target)
if(!length(GLOB.sdql2_queries) && ("SDQL2" in target.panel_tabs))
- target << output("", "statbrowser:remove_sdql2")
+ target.stat_panel.send_message("remove_sdql2")
+
else if(length(GLOB.sdql2_queries) && (target.stat_tab == "SDQL2" || !("SDQL2" in target.panel_tabs)) && num_fires % default_wait == 0)
set_SDQL2_tab(target)
+
if(target.mob)
var/mob/target_mob = target.mob
// Handle the action panels of the stat panel
+
var/update_actions = FALSE
// We're on a spell tab, update the tab so we can see cooldowns progressing and such
if(target.stat_tab in target.spell_tabs)
@@ -95,30 +101,35 @@ SUBSYSTEM_DEF(statpanels)
set_action_tabs(target, target_mob)
// Handle the examined turf of the stat panel
-
- if(target_mob?.listed_turf && num_fires % default_wait == 0)
- if(!target_mob.TurfAdjacent(target_mob.listed_turf))
- target << output("", "statbrowser:remove_listedturf")
+ if(target_mob?.listed_turf && num_fires % turf_wait == 0)
+ if(!target_mob.TurfAdjacent(target_mob.listed_turf) || isnull(target_mob.listed_turf))
+ target.stat_panel.send_message("remove_listedturf")
target_mob.listed_turf = null
else if(target.stat_tab == target_mob?.listed_turf.name || !(target_mob?.listed_turf.name in target.panel_tabs))
set_turf_examine_tab(target, target_mob)
+
if(MC_TICK_CHECK)
return
/datum/controller/subsystem/statpanels/proc/set_status_tab(client/target)
- if(!encoded_global_data)//statbrowser hasnt fired yet and we were called from immediate_send_stat_data()
+#if MIN_COMPILER_VERSION > 515
+ #warn 516 is most certainly out of beta, remove this beta notice if you haven't already
+#endif
+ var/static/list/beta_notice = list("", "You are on the BYOND 516 beta, various UIs and such may be broken!", "Please report issues, and switch back to BYOND 515 if things are causing too many issues for you.")
+ if(!global_data)//statbrowser hasnt fired yet and we were called from immediate_send_stat_data()
return
-
- var/ping_str = url_encode("Ping: [round(target.lastping, 1)]ms (Average: [round(target.avgping, 1)]ms)")
- var/other_str = url_encode(json_encode(target.mob?.get_status_tab_items()))
- target << output("[encoded_global_data];[ping_str];[other_str]", "statbrowser:update")
+ target.stat_panel.send_message("update_stat", list(
+ "global_data" = (target.byond_version < 516) ? global_data : (global_data + beta_notice),
+ "ping_str" = "Ping: [round(target.lastping, 1)]ms (Average: [round(target.avgping, 1)]ms)",
+ "other_str" = target.mob?.get_status_tab_items(),
+ ))
/datum/controller/subsystem/statpanels/proc/set_MC_tab(client/target)
var/turf/eye_turf = get_turf(target.eye)
- var/coord_entry = url_encode(COORD(eye_turf))
- if(!mc_data_encoded)
+ var/coord_entry = COORD(eye_turf)
+ if(!mc_data)
generate_mc_data()
- target << output("[mc_data_encoded];[coord_entry]", "statbrowser:update_mc")
+ target.stat_panel.send_message("update_mc", list("mc_data" = mc_data, "coord_entry" = coord_entry))
/datum/controller/subsystem/statpanels/proc/set_SDQL2_tab(client/target)
var/list/sdql2A = list()
@@ -128,7 +139,7 @@ SUBSYSTEM_DEF(statpanels)
sdql2B = query.generate_stat()
sdql2A += sdql2B
- target << output(url_encode(json_encode(sdql2A)), "statbrowser:update_sdql2")
+ target.stat_panel.send_message("update_sdql2", sdql2A)
/// Set up the various action tabs.
/datum/controller/subsystem/statpanels/proc/set_action_tabs(client/target, mob/target_mob)
@@ -138,7 +149,7 @@ SUBSYSTEM_DEF(statpanels)
for(var/action_data in actions)
target.spell_tabs |= action_data[1]
- target << output("[url_encode(json_encode(target.spell_tabs))];[url_encode(json_encode(actions))]", "statbrowser:update_spells")
+ target.stat_panel.send_message("update_spells", list(spell_tabs = target.spell_tabs, actions = actions))
/datum/controller/subsystem/statpanels/proc/set_turf_examine_tab(client/target, mob/target_mob)
var/list/overrides = list()
@@ -175,29 +186,27 @@ SUBSYSTEM_DEF(statpanels)
else
turfitems[++turfitems.len] = list("[turf_content.name]", REF(turf_content))
- turfitems = url_encode(json_encode(turfitems))
- target << output("[turfitems];", "statbrowser:update_listedturf")
+ target.stat_panel.send_message("update_listedturf", turfitems)
/datum/controller/subsystem/statpanels/proc/generate_mc_data()
- var/list/mc_data = list(
+ mc_data = list(
list("CPU:", world.cpu),
list("Instances:", "[num2text(world.contents.len, 10)]"),
list("World Time:", "[world.time]"),
- list("Globals:", GLOB.stat_entry(), "\ref[GLOB]"),
- list("[config]:", config.stat_entry(), "\ref[config]"),
+ list("Globals:", GLOB.stat_entry(), text_ref(GLOB)),
+ list("[config]:", config.stat_entry(), text_ref(config)),
list("Byond:", "(FPS:[world.fps]) (TickCount:[world.time/world.tick_lag]) (TickDrift:[round(Master.tickdrift,1)]([round((Master.tickdrift/(world.time/world.tick_lag))*100,0.1)]%)) (Internal Tick Usage: [round(MAPTICK_LAST_INTERNAL_TICK_USAGE,0.1)]%)"),
- list("Master Controller:", Master.stat_entry(), "\ref[Master]"),
- list("Failsafe Controller:", Failsafe.stat_entry(), "\ref[Failsafe]"),
+ list("Master Controller:", Master.stat_entry(), text_ref(Master)),
+ list("Failsafe Controller:", Failsafe.stat_entry(), text_ref(Failsafe)),
list("","")
)
for(var/datum/controller/subsystem/sub_system as anything in Master.subsystems)
- mc_data[++mc_data.len] = list("\[[sub_system.state_letter()]][sub_system.name]", sub_system.stat_entry(), "\ref[sub_system]")
- mc_data[++mc_data.len] = list("Camera Net", "Cameras: [GLOB.cameranet.cameras.len] | Chunks: [GLOB.cameranet.chunks.len]", "\ref[GLOB.cameranet]")
- mc_data_encoded = url_encode(json_encode(mc_data))
+ mc_data[++mc_data.len] = list("\[[sub_system.state_letter()]][sub_system.name]", sub_system.stat_entry(), text_ref(sub_system))
+ mc_data[++mc_data.len] = list("Camera Net", "Cameras: [GLOB.cameranet.cameras.len] | Chunks: [GLOB.cameranet.chunks.len]", text_ref(GLOB.cameranet))
///immediately update the active statpanel tab of the target client
/datum/controller/subsystem/statpanels/proc/immediate_send_stat_data(client/target)
- if(!target.statbrowser_ready)
+ if(!target.stat_panel.is_ready())
return FALSE
if(target.stat_tab == "Status")
@@ -205,7 +214,7 @@ SUBSYSTEM_DEF(statpanels)
return TRUE
var/mob/target_mob = target.mob
-
+
// Handle actions
var/update_actions = FALSE
@@ -220,10 +229,9 @@ SUBSYSTEM_DEF(statpanels)
return TRUE
// Handle turfs
-
if(target_mob?.listed_turf)
if(!target_mob.TurfAdjacent(target_mob.listed_turf))
- target << output("", "statbrowser:remove_listedturf")
+ target.stat_panel.send_message("removed_listedturf")
target_mob.listed_turf = null
else if(target.stat_tab == target_mob?.listed_turf.name || !(target_mob?.listed_turf.name in target.panel_tabs))
@@ -238,7 +246,7 @@ SUBSYSTEM_DEF(statpanels)
return TRUE
if(!length(GLOB.sdql2_queries) && ("SDQL2" in target.panel_tabs))
- target << output("", "statbrowser:remove_sdql2")
+ target.stat_panel.send_message("remove_sdql2")
else if(length(GLOB.sdql2_queries) && target.stat_tab == "SDQL2")
set_SDQL2_tab(target)
@@ -248,42 +256,3 @@ SUBSYSTEM_DEF(statpanels)
/atom/proc/remove_from_cache()
SSstatpanels.cached_images -= REF(src)
-
-/// verbs that send information from the browser UI
-/client/verb/set_tab(tab as text|null)
- set name = "Set Tab"
- set hidden = TRUE
-
- stat_tab = tab
- SSstatpanels.immediate_send_stat_data(src)
-
-/client/verb/send_tabs(tabs as text|null)
- set name = "Send Tabs"
- set hidden = TRUE
-
- panel_tabs |= tabs
-
-/client/verb/remove_tabs(tabs as text|null)
- set name = "Remove Tabs"
- set hidden = TRUE
-
- panel_tabs -= tabs
-
-/client/verb/reset_tabs()
- set name = "Reset Tabs"
- set hidden = TRUE
-
- panel_tabs = list()
-
-/client/verb/panel_ready()
- set name = "Panel Ready"
- set hidden = TRUE
-
- statbrowser_ready = TRUE
- init_verbs()
-
-/client/verb/update_verbs()
- set name = "Update Verbs"
- set hidden = TRUE
-
- init_verbs()
diff --git a/code/controllers/subsystem/tgui.dm b/code/controllers/subsystem/tgui.dm
index 7550bab63e83a..9b0cc241c7bd7 100644
--- a/code/controllers/subsystem/tgui.dm
+++ b/code/controllers/subsystem/tgui.dm
@@ -29,7 +29,7 @@ SUBSYSTEM_DEF(tgui)
/datum/controller/subsystem/tgui/PreInit()
basehtml = file2text('tgui/public/tgui.html')
// Inject inline polyfills
- var/polyfill = file2text('tgui/public/tgui-polyfill.bundle.js')
+ var/polyfill = file2text('tgui/public/tgui-polyfill.min.js')
polyfill = ""
basehtml = replacetextEx(basehtml, "", polyfill)
diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm
index 8deb18e3b167e..df20e00ca7a97 100755
--- a/code/controllers/subsystem/ticker.dm
+++ b/code/controllers/subsystem/ticker.dm
@@ -478,7 +478,7 @@ SUBSYSTEM_DEF(ticker)
if(!hard_popcap)
listclearnulls(queued_players)
for (var/mob/dead/new_player/new_player in queued_players)
- to_chat(new_player, span_userdanger("The alive players limit has been released! [html_encode(">>Join Game<<")]"))
+ to_chat(new_player, span_userdanger("The alive players limit has been released! [html_encode(">>Join Game<<")]"))
SEND_SOUND(new_player, sound('sound/misc/notice1.ogg'))
GLOB.latejoin_menu.ui_interact(new_player)
queued_players.len = 0
@@ -493,7 +493,7 @@ SUBSYSTEM_DEF(ticker)
listclearnulls(queued_players)
if(living_player_count() < hard_popcap)
if(next_in_line && next_in_line.client)
- to_chat(next_in_line, span_userdanger("A slot has opened! You have approximately 20 seconds to join. \>\>Join Game\<\<"))
+ to_chat(next_in_line, span_userdanger("A slot has opened! You have approximately 20 seconds to join. \>\>Join Game\<\<"))
SEND_SOUND(next_in_line, sound('sound/misc/notice1.ogg'))
next_in_line.ui_interact(next_in_line)
return
diff --git a/code/datums/actions/cooldown_action.dm b/code/datums/actions/cooldown_action.dm
index 8de28855963f9..3b08ad2a67200 100644
--- a/code/datums/actions/cooldown_action.dm
+++ b/code/datums/actions/cooldown_action.dm
@@ -224,7 +224,7 @@
return PreActivate(user)
/// Intercepts client owner clicks to activate the ability
-/datum/action/cooldown/proc/InterceptClickOn(mob/living/caller, params, atom/target)
+/datum/action/cooldown/proc/InterceptClickOn(mob/living/caller_but_not_a_byond_built_in_proc, params, atom/target)
if(!IsAvailable(feedback = TRUE))
return FALSE
if(!target)
@@ -235,8 +235,8 @@
// And if we reach here, the action was complete successfully
if(unset_after_click)
- unset_click_ability(caller, refund_cooldown = FALSE)
- caller.next_click = world.time + click_cd_override
+ unset_click_ability(caller_but_not_a_byond_built_in_proc, refund_cooldown = FALSE)
+ caller_but_not_a_byond_built_in_proc.next_click = world.time + click_cd_override
return TRUE
diff --git a/code/datums/actions/innate_action.dm b/code/datums/actions/innate_action.dm
index 3f4dde5cd4e47..2f6f952fce010 100644
--- a/code/datums/actions/innate_action.dm
+++ b/code/datums/actions/innate_action.dm
@@ -76,17 +76,17 @@
on_who.click_intercept = null
/// Handles whenever a mob clicks on something
-/datum/action/innate/proc/InterceptClickOn(mob/living/caller, params, atom/clicked_on)
+/datum/action/innate/proc/InterceptClickOn(mob/living/caller_but_not_a_byond_built_in_proc, params, atom/clicked_on)
if(!IsAvailable(feedback = TRUE))
- unset_ranged_ability(caller)
+ unset_ranged_ability(caller_but_not_a_byond_built_in_proc)
return FALSE
if(!clicked_on)
return FALSE
- return do_ability(caller, params, clicked_on)
+ return do_ability(caller_but_not_a_byond_built_in_proc, params, clicked_on)
/// Actually goes through and does the click ability
-/datum/action/innate/proc/do_ability(mob/living/caller, params, atom/clicked_on)
+/datum/action/innate/proc/do_ability(mob/living/caller_but_not_a_byond_built_in_proc, params, atom/clicked_on)
return FALSE
/datum/action/innate/Remove(mob/removed_from)
diff --git a/code/datums/browser.dm b/code/datums/browser.dm
index 815e0dac693bb..f6c2a24f535e3 100644
--- a/code/datums/browser.dm
+++ b/code/datums/browser.dm
@@ -128,13 +128,13 @@
var/output = {"
"
diff --git a/code/datums/chatmessage.dm b/code/datums/chatmessage.dm
index 5392b5bf5f04b..ae3167c41a0fd 100644
--- a/code/datums/chatmessage.dm
+++ b/code/datums/chatmessage.dm
@@ -1,38 +1,38 @@
/// How long the chat message's spawn-in animation will occur for
-#define CHAT_MESSAGE_SPAWN_TIME (0.2 SECONDS)
+#define CHAT_MESSAGE_SPAWN_TIME (0.2 SECONDS)
/// How long the chat message will exist prior to any exponential decay
-#define CHAT_MESSAGE_LIFESPAN (5 SECONDS)
+#define CHAT_MESSAGE_LIFESPAN (5 SECONDS)
/// How long the chat message's end of life fading animation will occur for
-#define CHAT_MESSAGE_EOL_FADE (0.7 SECONDS)
+#define CHAT_MESSAGE_EOL_FADE (0.7 SECONDS)
/// Grace period for fade before we actually delete the chat message
-#define CHAT_MESSAGE_GRACE_PERIOD (0.2 SECONDS)
+#define CHAT_MESSAGE_GRACE_PERIOD (0.2 SECONDS)
/// Factor of how much the message index (number of messages) will account to exponential decay
-#define CHAT_MESSAGE_EXP_DECAY 0.7
+#define CHAT_MESSAGE_EXP_DECAY 0.7
/// Factor of how much height will account to exponential decay
-#define CHAT_MESSAGE_HEIGHT_DECAY 0.9
+#define CHAT_MESSAGE_HEIGHT_DECAY 0.9
/// Approximate height in pixels of an 'average' line, used for height decay
-#define CHAT_MESSAGE_APPROX_LHEIGHT 11
+#define CHAT_MESSAGE_APPROX_LHEIGHT 11
/// Max width of chat message in pixels
-#define CHAT_MESSAGE_WIDTH 96
+#define CHAT_MESSAGE_WIDTH 112
/// The dimensions of the chat message icons
-#define CHAT_MESSAGE_ICON_SIZE 9
+#define CHAT_MESSAGE_ICON_SIZE 9
///Base layer of chat elements
-#define CHAT_LAYER 1
+#define CHAT_LAYER 12.0001
///Highest possible layer of chat elements
-#define CHAT_LAYER_MAX 2
+#define CHAT_LAYER_MAX 12.9999
/// Maximum precision of float before rounding errors occur (in this context)
#define CHAT_LAYER_Z_STEP 0.0001
/// The number of z-layer 'slices' usable by the chat message layering
#define CHAT_LAYER_MAX_Z (CHAT_LAYER_MAX - CHAT_LAYER) / CHAT_LAYER_Z_STEP
/**
- * # Chat Message Overlay
- *
- * Datum for generating a message overlay on the map
- */
+ * # Chat Message Overlay
+ *
+ * Datum for generating a message overlay on the map
+ */
/datum/chatmessage
- /// The visual element of the chat messsage
+ /// The visual element of the chat message
var/image/message
/// The location in which the message is appearing
var/atom/message_loc
@@ -50,18 +50,22 @@
var/datum/chatmessage/prev
/// The current index used for adjusting the layer of each sequential chat message such that recent messages will overlay older ones
var/static/current_z_idx = 0
+ /// When we started animating the message
+ var/animate_start = 0
+ /// Our animation lifespan, how long this message will last
+ var/animate_lifespan = 0
/**
- * Constructs a chat message overlay
- *
- * Arguments:
- * * text - The text content of the overlay
- * * target - The target atom to display the overlay at
- * * owner - The mob that owns this overlay, only this mob will be able to view it
- * * language - The language this message was spoken in
- * * extra_classes - Extra classes to apply to the span that holds the text
- * * lifespan - The lifespan of the message in deciseconds
- */
+ * Constructs a chat message overlay
+ *
+ * Arguments:
+ * * text - The text content of the overlay
+ * * target - The target atom to display the overlay at
+ * * owner - The mob that owns this overlay, only this mob will be able to view it
+ * * language - The language this message was spoken in
+ * * extra_classes - Extra classes to apply to the span that holds the text
+ * * lifespan - The lifespan of the message in deciseconds
+ */
/datum/chatmessage/New(text, atom/target, mob/owner, datum/language/language, list/extra_classes = list(), lifespan = CHAT_MESSAGE_LIFESPAN)
. = ..()
if (!istype(target))
@@ -70,45 +74,47 @@
stack_trace("/datum/chatmessage created with [isnull(owner) ? "null" : "invalid"] mob owner")
qdel(src)
return
- if(!SSlag_switch.measures[DISABLE_RUNECHAT] && !HAS_TRAIT(owner, TRAIT_BYPASS_MEASURES))
- INVOKE_ASYNC(src, PROC_REF(generate_image), text, target, owner, language, extra_classes, lifespan)
+ INVOKE_ASYNC(src, PROC_REF(generate_image), text, target, owner, language, extra_classes, lifespan)
/datum/chatmessage/Destroy()
- if (owned_by)
+ if (!QDELING(owned_by))
+ if(REALTIMEOFDAY < animate_start + animate_lifespan)
+ stack_trace("Del'd before we finished fading, with [(animate_start + animate_lifespan) - REALTIMEOFDAY] time left")
+
if (owned_by.seen_messages)
LAZYREMOVEASSOC(owned_by.seen_messages, message_loc, src)
owned_by.images.Remove(message)
+
owned_by = null
message_loc = null
message = null
- leave_subsystem()
return ..()
/**
- * Calls qdel on the chatmessage when its parent is deleted, used to register qdel signal
- */
+ * Calls qdel on the chatmessage when its parent is deleted, used to register qdel signal
+ */
/datum/chatmessage/proc/on_parent_qdel()
SIGNAL_HANDLER
qdel(src)
/**
- * Generates a chat message image representation
- *
- * Arguments:
- * * text - The text content of the overlay
- * * target - The target atom to display the overlay at
- * * owner - The mob that owns this overlay, only this mob will be able to view it
- * * language - The language this message was spoken in
- * * extra_classes - Extra classes to apply to the span that holds the text
- * * lifespan - The lifespan of the message in deciseconds
- */
+ * Generates a chat message image representation
+ *
+ * Arguments:
+ * * text - The text content of the overlay
+ * * target - The target atom to display the overlay at
+ * * owner - The mob that owns this overlay, only this mob will be able to view it
+ * * language - The language this message was spoken in
+ * * extra_classes - Extra classes to apply to the span that holds the text
+ * * lifespan - The lifespan of the message in deciseconds
+ */
/datum/chatmessage/proc/generate_image(text, atom/target, mob/owner, datum/language/language, list/extra_classes, lifespan)
/// Cached icons to show what language the user is speaking
var/static/list/language_icons
// Register client who owns this message
owned_by = owner.client
- RegisterSignal(owned_by, COMSIG_QDELETING, PROC_REF(on_parent_qdel), src)
+ RegisterSignal(owned_by, COMSIG_QDELETING, PROC_REF(on_parent_qdel))
// Remove spans in the message from things like the recorder
var/static/regex/span_check = new(@"<\/?span[^>]*>", "gi")
@@ -139,6 +145,10 @@
if (!ismob(target))
extra_classes |= "small"
+ // Why are you yelling?
+ if(copytext_char(text, -2) == "!!")
+ extra_classes |= SPAN_YELL
+
var/list/prefixes
// Append radio icon if from a virtual speaker
@@ -165,27 +175,66 @@
var/tgt_color = extra_classes.Find("italics") ? target.chat_color_darkened : target.chat_color
// Approximate text height
- var/complete_text = "[text]"
+ var/complete_text = "[owner.say_emphasis(text)]"
+
var/mheight
WXH_TO_HEIGHT(owned_by.MeasureText(complete_text, null, CHAT_MESSAGE_WIDTH), mheight)
- approx_lines = max(1, mheight / CHAT_MESSAGE_APPROX_LHEIGHT)
+ if(!VERB_SHOULD_YIELD)
+ return finish_image_generation(mheight, target, owner, complete_text, lifespan)
+
+ var/datum/callback/our_callback = CALLBACK(src, PROC_REF(finish_image_generation), mheight, target, owner, complete_text, lifespan)
+ SSrunechat.message_queue += our_callback
+ return
+
+///finishes the image generation after the MeasureText() call in generate_image().
+///necessary because after that call the proc can resume at the end of the tick and cause overtime.
+/datum/chatmessage/proc/finish_image_generation(mheight, atom/target, mob/owner, complete_text, lifespan)
+ var/rough_time = REALTIMEOFDAY
+ approx_lines = max(1, mheight / CHAT_MESSAGE_APPROX_LHEIGHT)
+ var/starting_height = target.maptext_height
// Translate any existing messages upwards, apply exponential decay factors to timers
- message_loc = get_atom_on_turf(target)
+ message_loc = isturf(target) ? target : get_atom_on_turf(target)
if (owned_by.seen_messages)
var/idx = 1
var/combined_height = approx_lines
- for(var/msg in owned_by.seen_messages[message_loc])
- var/datum/chatmessage/m = msg
- animate(m.message, pixel_y = m.message.pixel_y + mheight, time = CHAT_MESSAGE_SPAWN_TIME)
+ for(var/datum/chatmessage/m as anything in owned_by.seen_messages[message_loc])
combined_height += m.approx_lines
+ var/time_spent = rough_time - m.animate_start
+ var/time_before_fade = m.animate_lifespan - CHAT_MESSAGE_EOL_FADE
+
// When choosing to update the remaining time we have to be careful not to update the
- // scheduled time once the EOL completion time has been set.
- var/sched_remaining = m.scheduled_destruction - world.time
- if (!m.eol_complete)
- var/remaining_time = (sched_remaining) * (CHAT_MESSAGE_EXP_DECAY ** idx++) * (CHAT_MESSAGE_HEIGHT_DECAY ** combined_height)
- m.enter_subsystem(world.time + remaining_time) // push updated time to runechat SS
+ // scheduled time once the EOL has been executed.
+ if (time_spent >= time_before_fade)
+ if(m.message.pixel_y < starting_height)
+ var/max_height = m.message.pixel_y + m.approx_lines * CHAT_MESSAGE_APPROX_LHEIGHT - starting_height
+ if(max_height > 0)
+ animate(m.message, pixel_y = m.message.pixel_y + max_height, time = CHAT_MESSAGE_SPAWN_TIME, flags = ANIMATION_PARALLEL)
+ else if(mheight + starting_height >= m.message.pixel_y)
+ animate(m.message, pixel_y = m.message.pixel_y + mheight, time = CHAT_MESSAGE_SPAWN_TIME, flags = ANIMATION_PARALLEL)
+ continue
+
+ var/remaining_time = time_before_fade * (CHAT_MESSAGE_EXP_DECAY ** idx++) * (CHAT_MESSAGE_HEIGHT_DECAY ** combined_height)
+ // Ensure we don't accidentially spike alpha up or something silly like that
+ m.message.alpha = m.get_current_alpha(time_spent)
+ if (remaining_time > 0)
+ // Stay faded in for a while, then
+ animate(m.message, alpha = 255, remaining_time)
+ // Fade out
+ animate(alpha = 0, time = CHAT_MESSAGE_EOL_FADE)
+ m.animate_lifespan = remaining_time + CHAT_MESSAGE_EOL_FADE
+ else
+ // Your time has come my son
+ animate(alpha = 0, time = CHAT_MESSAGE_EOL_FADE)
+ // We run this after the alpha animate, because we don't want to interrup it, but also don't want to block it by running first
+ // Sooo instead we do this. bit messy but it fuckin works
+ if(m.message.pixel_y < starting_height)
+ var/max_height = m.message.pixel_y + m.approx_lines * CHAT_MESSAGE_APPROX_LHEIGHT - starting_height
+ if(max_height > 0)
+ animate(m.message, pixel_y = m.message.pixel_y + max_height, time = CHAT_MESSAGE_SPAWN_TIME, flags = ANIMATION_PARALLEL)
+ else if(mheight + starting_height >= m.message.pixel_y)
+ animate(m.message, pixel_y = m.message.pixel_y + mheight, time = CHAT_MESSAGE_SPAWN_TIME, flags = ANIMATION_PARALLEL)
// Reset z index if relevant
if (current_z_idx >= CHAT_LAYER_MAX_Z)
@@ -196,49 +245,53 @@
SET_PLANE_EXPLICIT(message, RUNECHAT_PLANE, message_loc)
message.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA | KEEP_APART
message.alpha = 0
- message.pixel_y = owner.bound_height * 0.95
+ message.pixel_y = starting_height
+ message.pixel_x = -target.base_pixel_x
message.maptext_width = CHAT_MESSAGE_WIDTH
- message.maptext_height = mheight
+ message.maptext_height = mheight * 1.25 // We add extra because some characters are superscript, like actions
message.maptext_x = (CHAT_MESSAGE_WIDTH - owner.bound_width) * -0.5
- message.maptext = complete_text
+ message.maptext = MAPTEXT(complete_text)
+
+ animate_start = rough_time
+ animate_lifespan = lifespan
// View the message
LAZYADDASSOCLIST(owned_by.seen_messages, message_loc, src)
owned_by.images |= message
+
+ // Fade in
animate(message, alpha = 255, time = CHAT_MESSAGE_SPAWN_TIME)
+ var/time_before_fade = lifespan - CHAT_MESSAGE_SPAWN_TIME - CHAT_MESSAGE_EOL_FADE
+ // Stay faded in
+ animate(alpha = 255, time = time_before_fade)
+ // Fade out
+ animate(alpha = 0, time = CHAT_MESSAGE_EOL_FADE)
- // Register with the runechat SS to handle EOL and destruction
- scheduled_destruction = world.time + (lifespan - CHAT_MESSAGE_EOL_FADE)
- RegisterSignal(message_loc, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(loc_z_changed))
- enter_subsystem()
+ // Register with the runechat SS to handle destruction
+ addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(qdel), src), lifespan + CHAT_MESSAGE_GRACE_PERIOD, TIMER_DELETE_ME, SSrunechat)
+/datum/chatmessage/proc/get_current_alpha(time_spent)
+ if(time_spent < CHAT_MESSAGE_SPAWN_TIME)
+ return (time_spent / CHAT_MESSAGE_SPAWN_TIME) * 255
-/datum/chatmessage/proc/loc_z_changed(datum/source, turf/old_turf, turf/new_turf, same_z_layer)
- SIGNAL_HANDLER
- SET_PLANE(message, RUNECHAT_PLANE, new_turf)
+ var/time_before_fade = animate_lifespan - CHAT_MESSAGE_EOL_FADE
+ if(time_spent <= time_before_fade)
+ return 255
-/**
- * Applies final animations to overlay CHAT_MESSAGE_EOL_FADE deciseconds prior to message deletion,
- * sets time for scheduling deletion and re-enters the runechat SS for qdeling
- *
- * Arguments:
- * * fadetime - The amount of time to animate the message's fadeout for
- */
-/datum/chatmessage/proc/end_of_life(fadetime = CHAT_MESSAGE_EOL_FADE)
- eol_complete = scheduled_destruction + fadetime
- animate(message, alpha = 0, time = fadetime, flags = ANIMATION_PARALLEL)
- enter_subsystem(eol_complete) // re-enter the runechat SS with the EOL completion time to QDEL self
+ return (1 - ((time_spent - time_before_fade) / CHAT_MESSAGE_EOL_FADE)) * 255
/**
- * Creates a message overlay at a defined location for a given speaker
- *
- * Arguments:
- * * speaker - The atom who is saying this message
- * * message_language - The language that the message is said in
- * * raw_message - The text content of the message
- * * spans - Additional classes to be added to the message
- */
+ * Creates a message overlay at a defined location for a given speaker
+ *
+ * Arguments:
+ * * speaker - The atom who is saying this message
+ * * message_language - The language that the message is said in
+ * * raw_message - The text content of the message
+ * * spans - Additional classes to be added to the message
+ */
/mob/proc/create_chat_message(atom/movable/speaker, datum/language/message_language, raw_message, list/spans, runechat_flags = NONE)
+ if(HAS_TRAIT(speaker, TRAIT_RUNECHAT_HIDDEN))
+ return
// Ensure the list we are using, if present, is a copy so we don't modify the list provided to us
spans = spans ? spans.Copy() : list()
@@ -248,11 +301,11 @@
var/atom/movable/virtualspeaker/v = speaker
speaker = v.source
spans |= "virtual-speaker"
-
+
//NTSL doesn't pass a speaker when you do broadcast() since technically nothing is actually speaking.
if(!speaker)
return
-
+
// Ignore virtual speaker (most often radio messages) from ourself
if (originalSpeaker != src && speaker == src)
return
@@ -261,25 +314,24 @@
if(runechat_flags & EMOTE_MESSAGE)
new /datum/chatmessage(raw_message, speaker, src, message_language, list("emote", "italics"))
else
- new /datum/chatmessage(lang_treat(speaker, message_language, raw_message, spans, null, TRUE), speaker, src, message_language, spans)
-
+ new /datum/chatmessage(translate_language(speaker, message_language, raw_message, spans), speaker, src, message_language, spans)
// Tweak these defines to change the available color ranges
-#define CM_COLOR_SAT_MIN 0.6
-#define CM_COLOR_SAT_MAX 0.7
-#define CM_COLOR_LUM_MIN 0.65
-#define CM_COLOR_LUM_MAX 0.75
+#define CM_COLOR_SAT_MIN 0.6
+#define CM_COLOR_SAT_MAX 0.7
+#define CM_COLOR_LUM_MIN 0.65
+#define CM_COLOR_LUM_MAX 0.75
/**
- * Gets a color for a name, will return the same color for a given string consistently within a round.atom
- *
- * Note that this proc aims to produce pastel-ish colors using the HSL colorspace. These seem to be favorable for displaying on the map.
- *
- * Arguments:
- * * name - The name to generate a color for
- * * sat_shift - A value between 0 and 1 that will be multiplied against the saturation
- * * lum_shift - A value between 0 and 1 that will be multiplied against the luminescence
- */
+ * Gets a color for a name, will return the same color for a given string consistently within a round.atom
+ *
+ * Note that this proc aims to produce pastel-ish colors using the HSL colorspace. These seem to be favorable for displaying on the map.
+ *
+ * Arguments:
+ * * name - The name to generate a color for
+ * * sat_shift - A value between 0 and 1 that will be multiplied against the saturation
+ * * lum_shift - A value between 0 and 1 that will be multiplied against the luminescence
+ */
/datum/chatmessage/proc/colorize_string(name, sat_shift = 1, lum_shift = 1)
// seed to help randomness
var/static/rseed = rand(1,26)
@@ -316,13 +368,19 @@
if(5)
return "#[num2hex(c, 2)][num2hex(m, 2)][num2hex(x, 2)]"
-#undef CHAT_MESSAGE_SPAWN_TIME
-#undef CHAT_MESSAGE_LIFESPAN
+
+#undef CHAT_LAYER_MAX_Z
+#undef CHAT_LAYER_Z_STEP
+#undef CHAT_MESSAGE_APPROX_LHEIGHT
+#undef CHAT_MESSAGE_GRACE_PERIOD
#undef CHAT_MESSAGE_EOL_FADE
#undef CHAT_MESSAGE_EXP_DECAY
#undef CHAT_MESSAGE_HEIGHT_DECAY
-#undef CHAT_MESSAGE_APPROX_LHEIGHT
-#undef CHAT_MESSAGE_WIDTH
-#undef CHAT_LAYER_Z_STEP
-#undef CHAT_LAYER_MAX_Z
#undef CHAT_MESSAGE_ICON_SIZE
+#undef CHAT_MESSAGE_LIFESPAN
+#undef CHAT_MESSAGE_SPAWN_TIME
+#undef CHAT_MESSAGE_WIDTH
+#undef CM_COLOR_LUM_MAX
+#undef CM_COLOR_LUM_MIN
+#undef CM_COLOR_SAT_MAX
+#undef CM_COLOR_SAT_MIN
diff --git a/code/datums/holocall.dm b/code/datums/holocall.dm
index a1f4156126f7b..a4f0a6ba8184b 100644
--- a/code/datums/holocall.dm
+++ b/code/datums/holocall.dm
@@ -37,10 +37,10 @@
var/call_start_time
var/head_call = FALSE //calls from a head of staff autoconnect, if the recieving pad is not secure.
-//creates a holocall made by `caller` from `calling_pad` to `callees`
-/datum/holocall/New(mob/living/caller, obj/machinery/holopad/calling_pad, list/callees, elevated_access = FALSE)
+//creates a holocall made by `caller_but_not_a_byond_built_in_proc` from `calling_pad` to `callees`
+/datum/holocall/New(mob/living/caller_but_not_a_byond_built_in_proc, obj/machinery/holopad/calling_pad, list/callees, elevated_access = FALSE)
call_start_time = world.time
- user = caller
+ user = caller_but_not_a_byond_built_in_proc
calling_pad.outgoing_call = src
calling_holopad = calling_pad
head_call = elevated_access
@@ -302,7 +302,7 @@
var/datum/preset_holoimage/H = new preset_image_type
record.caller_image = H.build_image()
-//These build caller image from outfit and some additional data, for use by mappers for ruin holorecords
+//These build caller_but_not_a_byond_built_in_proc image from outfit and some additional data, for use by mappers for ruin holorecords
/datum/preset_holoimage
var/nonhuman_mobtype //Fill this if you just want something nonhuman
var/outfit_type
diff --git a/code/datums/mapgen/dungeon_generators/maintenance_generator/maintenance_generator.dm b/code/datums/mapgen/dungeon_generators/maintenance_generator/maintenance_generator.dm
index 2b1748b4eaa2a..aa2f03adc317f 100644
--- a/code/datums/mapgen/dungeon_generators/maintenance_generator/maintenance_generator.dm
+++ b/code/datums/mapgen/dungeon_generators/maintenance_generator/maintenance_generator.dm
@@ -210,7 +210,7 @@
//We have to rawdog the Astar pathfinding and skip the wrapper proc because that's made specifically for mobs
var/list/cable_path = AStar(
- caller = our_apc,
+ caller_but_not_a_byond_built_in_proc = our_apc,
_end = closest_apc,
dist = /turf/proc/Distance_cardinal,
maxnodes = 0,
diff --git a/code/datums/profiling.dm b/code/datums/profiling.dm
index dcf966750a627..1313655a7e139 100644
--- a/code/datums/profiling.dm
+++ b/code/datums/profiling.dm
@@ -15,4 +15,6 @@ GLOBAL_REAL_VAR(PROFILE_TIME)
var/list/data = PROFILE_STORE[entry]
lines += "[entry] => [num2text(data[PROFILE_ITEM_TIME], 10)]ms ([data[PROFILE_ITEM_COUNT]]) (avg:[num2text(data[PROFILE_ITEM_TIME]/(data[PROFILE_ITEM_COUNT] || 1), 99)])"
- user << browse("
")
+ browser.open()
diff --git a/code/datums/voice_announcements.dm b/code/datums/voice_announcements.dm
index 30afbd51c3ba5..e8b7a583cf0ea 100644
--- a/code/datums/voice_announcements.dm
+++ b/code/datums/voice_announcements.dm
@@ -112,7 +112,7 @@ GLOBAL_LIST_EMPTY(voice_announce_list)
fdel(base_file)
log_admin("[key_name(client)] has made a voice announcement via [ip], saved to [base_filename]")
- message_admins("[key_name_admin(client)] has made a voice announcement. ((CANCEL))")
+ message_admins("[key_name_admin(client)] has made a voice announcement. ((CANCEL))")
announce(snd)
else
diff --git a/code/game/atom/_atom.dm b/code/game/atom/_atom.dm
index f0df0ec8a0371..9eb481155d2c9 100644
--- a/code/game/atom/_atom.dm
+++ b/code/game/atom/_atom.dm
@@ -1187,7 +1187,7 @@
. = ..()
var/refid = REF(src)
. += "[VV_HREF_TARGETREF(refid, VV_HK_AUTO_RENAME, "[src]")]"
- . += " <<[dir2text(dir) || dir]>>"
+ . += " <<[dir2text(dir) || dir]>>"
///Where atoms should drop if taken from this atom
/atom/proc/drop_location()
diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm
index 5231e4953419b..163c54edc8a55 100644
--- a/code/game/machinery/camera/camera.dm
+++ b/code/game/machinery/camera/camera.dm
@@ -304,9 +304,9 @@
if(AI.control_disabled || (AI.stat == DEAD))
return
if(U.name == "Unknown")
- to_chat(AI, "[U] holds \a [itemname] up to one of your cameras ...")
+ to_chat(AI, "[U] holds \a [itemname] up to one of your cameras ...")
else
- to_chat(AI, "[U] holds \a [itemname] up to one of your cameras ...")
+ to_chat(AI, "[U] holds \a [itemname] up to one of your cameras ...")
if(istype(X))
info = X.render_body(AI)
AI.last_paper_seen = "[itemname][info]"
diff --git a/code/game/machinery/computer/card.dm b/code/game/machinery/computer/card.dm
index 872c46be78cf3..57cf0e1c92ae7 100644
--- a/code/game/machinery/computer/card.dm
+++ b/code/game/machinery/computer/card.dm
@@ -139,14 +139,14 @@ GLOBAL_VAR_INIT(time_last_changed_position, 0)
dat += "Crew Manifest: Please use security record computer to modify entries.
"
for(var/datum/data/record/t in sortRecord(GLOB.data_core.general))
dat += {"[t.fields["name"]] - [t.fields["rank"]] "}
- dat += "Print