diff --git a/code/__DEFINES/MC.dm b/code/__DEFINES/MC.dm
index aa1d6214bd26..e8dd84b3a366 100644
--- a/code/__DEFINES/MC.dm
+++ b/code/__DEFINES/MC.dm
@@ -99,6 +99,14 @@
}\
/datum/controller/subsystem/##X
+#define TIMER_SUBSYSTEM_DEF(X) GLOBAL_REAL(SS##X, /datum/controller/subsystem/timer/##X);\
+/datum/controller/subsystem/timer/##X/New(){\
+ NEW_SS_GLOBAL(SS##X);\
+ PreInit();\
+}\
+/datum/controller/subsystem/timer/##X/fire() {..() /*just so it shows up on the profiler*/} \
+/datum/controller/subsystem/timer/##X
+
#define PROCESSING_SUBSYSTEM_DEF(X) GLOBAL_REAL(SS##X, /datum/controller/subsystem/processing/##X);\
/datum/controller/subsystem/processing/##X/New(){\
NEW_SS_GLOBAL(SS##X);\
diff --git a/code/__DEFINES/_tick.dm b/code/__DEFINES/_tick.dm
index abafc4465c38..142d904f31a5 100644
--- a/code/__DEFINES/_tick.dm
+++ b/code/__DEFINES/_tick.dm
@@ -22,6 +22,11 @@
/// runs stoplag if tick_usage is above the limit
#define CHECK_TICK ( TICK_CHECK ? stoplag() : 0 )
+/// Checks if a sleeping proc is running before or after the master controller
+#define RUNNING_BEFORE_MASTER ( Master.last_run != null && Master.last_run != world.time )
+/// Returns true if a verb ought to yield to the MC (IE: queue up to be processed by a subsystem)
+#define VERB_SHOULD_YIELD ( TICK_CHECK || RUNNING_BEFORE_MASTER )
+
/// Returns true if tick usage is above 95, for high priority usage
#define TICK_CHECK_HIGH_PRIORITY ( TICK_USAGE > 95 )
/// runs stoplag if tick_usage is above 95, for high priority usage
diff --git a/code/__DEFINES/chat.dm b/code/__DEFINES/chat.dm
index 4c66ba43ada7..d26ce716c3e7 100644
--- a/code/__DEFINES/chat.dm
+++ b/code/__DEFINES/chat.dm
@@ -20,5 +20,8 @@
#define MESSAGE_TYPE_MENTORPM "mentorpm"
#define MESSAGE_TYPE_DONATOR "donator"
+/// Max length of chat message in characters
+#define CHAT_MESSAGE_MAX_LENGTH 110
+
/// Adds a generic box around whatever message you're sending in chat. Really makes things stand out.
#define examine_block(str) ("
" + str + "
")
diff --git a/code/__DEFINES/fonts.dm b/code/__DEFINES/fonts.dm
new file mode 100644
index 000000000000..ba799a62c9c7
--- /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/layers.dm b/code/__DEFINES/layers.dm
index 3ce945e5eecc..0c9c2a3d769b 100644
--- a/code/__DEFINES/layers.dm
+++ b/code/__DEFINES/layers.dm
@@ -92,9 +92,6 @@
#define EMISSIVE_BLOCKER_LAYER 12
#define EMISSIVE_BLOCKER_RENDER_TARGET "*EMISSIVE_BLOCKER_PLANE"
-#define CHAT_LAYER 12.0001 // Do not insert layers between these two values
-#define CHAT_LAYER_MAX 12.9999
-
#define EMISSIVE_PLANE 13
#define EMISSIVE_LAYER 13
#define EMISSIVE_RENDER_TARGET "*EMISSIVE_PLANE"
diff --git a/code/__DEFINES/text.dm b/code/__DEFINES/text.dm
index 823a88345de6..cf35addca922 100644
--- a/code/__DEFINES/text.dm
+++ b/code/__DEFINES/text.dm
@@ -1,8 +1,25 @@
/// Prepares a text to be used for maptext. Use this so it doesn't look hideous.
#define MAPTEXT(text) {"[##text]"}
-/// Macro from Lummox used to get height from a MeasureText proc
-#define WXH_TO_HEIGHT(x) text2num(copytext(x, findtextEx(x, "x") + 1))
+/// Prepares a text to be used for maptext, using a variable size font.
+/// Variable size font. More flexible but doesn't scale pixel perfect to BYOND icon resolutions. (May be blurry.) Can use any size in pt or px.
+#define MAPTEXT_VCR_OSD_MONO(text) {"[##text]"}
+
+/// Prepares a text to be used for maptext using a pixel font. Cleaner but less size choices.
+/// Standard size (ie: normal runechat) Use only sizing pt, multiples of 6: 6pt 12pt 18pt 24pt etc. - Not for use with px sizing
+#define MAPTEXT_GRAND9K(text) {"[##text]"}
+
+/// Prepares a text to be used for maptext using a pixel font. Cleaner but less size choices.
+/// Small size. (ie: whisper runechat) Use only size pt, multiples of 12: 12pt 24pt 48pt etc. - Not for use with px sizing
+#define MAPTEXT_TINY_UNICODE(text) {"[##text]"}
+
+/// Macro from Lummox used to get height from a MeasureText proc.
+/// resolves the MeasureText() return value once, then resolves the height, then sets return_var to that.
+#define WXH_TO_HEIGHT(measurement, return_var) \
+ do { \
+ var/_measurement = measurement; \
+ return_var = text2num(copytext(_measurement, findtextEx(_measurement, "x") + 1)); \
+ } while(FALSE);
/*
* Uses MAPTEXT to format antag points into a more appealing format
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index 0cdc86824d08..b4400312bf5f 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -134,6 +134,8 @@
#define TRAIT_CRITICAL_CONDITION "critical-condition"
/// Is frozen in place
#define TRAIT_FROZEN "frozen"
+/// Is runechat for this atom/movable currently disabled, regardless of prefs or anything?
+#define TRAIT_RUNECHAT_HIDDEN "runechat_hidden"
/// trait associated to a stat value or range of
#define STAT_TRAIT "stat"
#define TRAIT_INCAPACITATED "incapacitated"
diff --git a/code/_globalvars/traits.dm b/code/_globalvars/traits.dm
index 6b17e39cec8b..ebc3d1b0de4c 100644
--- a/code/_globalvars/traits.dm
+++ b/code/_globalvars/traits.dm
@@ -6,6 +6,7 @@
GLOBAL_LIST_INIT(traits_by_type, list(
/mob = list(/atom/movable = list(
"TRAIT_MOVE_PHASING" = TRAIT_MOVE_PHASING,
+ "TRAIT_RUNECHAT_HIDDEN" = TRAIT_RUNECHAT_HIDDEN,
))))
/// value -> trait name, generated on use from trait_by_type global
GLOBAL_LIST(trait_name_map)
diff --git a/code/controllers/subsystem/runechat.dm b/code/controllers/subsystem/runechat.dm
index a5193a508ad6..663bb8cf347d 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/datums/chatmessage.dm b/code/datums/chatmessage.dm
index 5694bfa0fd08..e985de6c63dc 100644
--- a/code/datums/chatmessage.dm
+++ b/code/datums/chatmessage.dm
@@ -1,33 +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)
/// 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
-/// Max length of chat message in characters
-#define CHAT_MESSAGE_MAX_LENGTH 110
+#define CHAT_MESSAGE_WIDTH 112
+/// The dimensions of the chat message icons
+#define CHAT_MESSAGE_ICON_SIZE 9
+
+///Base layer of chat elements
+#define CHAT_LAYER 12.0001
+///Highest possible layer of chat elements
+#define CHAT_LAYER_MAX 12.9999
/// Maximum precision of float before rounding errors occur (in this context)
-#define CHAT_LAYER_Z_STEP 0.0001
+#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
-/// The dimensions of the chat message icons
-#define CHAT_MESSAGE_ICON_SIZE 9
+#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
@@ -45,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))
@@ -68,41 +77,44 @@
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_PARENT_QDELETING, PROC_REF(on_parent_qdel), src)
+ RegisterSignal(owned_by, COMSIG_PARENT_QDELETING, PROC_REF(on_parent_qdel))
// Remove spans in the message from things like the recorder
var/static/regex/span_check = new(@"<\/?span[^>]*>", "gi")
@@ -133,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
@@ -159,26 +175,67 @@
var/tgt_color = extra_classes.Find("italics") ? target.chat_color_darkened : target.chat_color
// Approximate text height
- var/complete_text = ""
- var/mheight = WXH_TO_HEIGHT(owned_by.MeasureText(complete_text, null, CHAT_MESSAGE_WIDTH))
- approx_lines = max(1, mheight / CHAT_MESSAGE_APPROX_LHEIGHT)
+ var/complete_text = ""
+
+ var/mheight
+ WXH_TO_HEIGHT(owned_by.MeasureText(complete_text, null, CHAT_MESSAGE_WIDTH), mheight)
+
+ 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)
@@ -189,43 +246,53 @@
message.plane = RUNECHAT_PLANE
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)
- 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)
-/**
- * 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
+/datum/chatmessage/proc/get_current_alpha(time_spent)
+ if(time_spent < CHAT_MESSAGE_SPAWN_TIME)
+ return (time_spent / CHAT_MESSAGE_SPAWN_TIME) * 255
+
+ var/time_before_fade = animate_lifespan - CHAT_MESSAGE_EOL_FADE
+ if(time_spent <= time_before_fade)
+ return 255
+
+ 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()
@@ -235,11 +302,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
@@ -248,25 +315,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(raw_message, 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)
@@ -303,13 +369,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/game/say.dm b/code/game/say.dm
index 45410e3f1656..88b5eec84c39 100644
--- a/code/game/say.dm
+++ b/code/game/say.dm
@@ -61,7 +61,7 @@ GLOBAL_LIST_INIT(freqtospan, list(
var/endspanpart = ""// Yogs
//Message
- var/messagepart = " [span_message("[lang_treat(speaker, message_language, raw_message, spans, message_mods)]")]"
+ var/messagepart = " [span_message("[say_emphasis(lang_treat(speaker, message_language, raw_message, spans, message_mods))]")]"
var/languageicon = ""
var/datum/language/D = GLOB.language_datum_instances[message_language]
@@ -100,6 +100,22 @@ GLOBAL_LIST_INIT(freqtospan, list(
var/spanned = attach_spans(input, spans)
return "[say_mod(input, message_mods)], \"[spanned]\""
+/// Transforms the speech emphasis mods from [/atom/movable/proc/say_emphasis] into the appropriate HTML tags. Includes escaping.
+#define ENCODE_HTML_EMPHASIS(input, char, html, varname) \
+ var/static/regex/##varname = regex("(?$1[html]>")
+
+/// Scans the input sentence for speech emphasis modifiers, notably |italics|, +bold+, and _underline_ -mothblocks
+/atom/movable/proc/say_emphasis(input)
+ ENCODE_HTML_EMPHASIS(input, "\\|", "i", italics)
+ ENCODE_HTML_EMPHASIS(input, "\\+", "b", bold)
+ ENCODE_HTML_EMPHASIS(input, "_", "u", underline)
+ var/static/regex/remove_escape_backlashes = regex("\\\\(_|\\+|\\|)", "g") // Removes backslashes used to escape text modification.
+ input = remove_escape_backlashes.Replace_char(input, "$1")
+ return input
+
+#undef ENCODE_HTML_EMPHASIS
+
/atom/movable/proc/lang_treat(atom/movable/speaker, datum/language/language, raw_message, list/spans, list/message_mods = list(), no_quote = FALSE)
if(has_language(language))
var/atom/movable/AM = speaker.GetSource()
diff --git a/code/modules/balloon_alert/balloon_alert.dm b/code/modules/balloon_alert/balloon_alert.dm
index 45c78f0f466d..891bea06d50b 100644
--- a/code/modules/balloon_alert/balloon_alert.dm
+++ b/code/modules/balloon_alert/balloon_alert.dm
@@ -58,7 +58,7 @@
balloon_alert.appearance_flags = RESET_ALPHA|RESET_COLOR|RESET_TRANSFORM
balloon_alert.maptext = MAPTEXT("[text]")
balloon_alert.maptext_x = (BALLOON_TEXT_WIDTH - bound_width) * -0.5
- balloon_alert.maptext_height = WXH_TO_HEIGHT(viewer_client?.MeasureText(text, null, BALLOON_TEXT_WIDTH))
+ WXH_TO_HEIGHT(viewer_client?.MeasureText(text, null, BALLOON_TEXT_WIDTH), balloon_alert.maptext_height)
balloon_alert.maptext_width = BALLOON_TEXT_WIDTH
viewer_client?.images += balloon_alert
diff --git a/code/modules/mob/say.dm b/code/modules/mob/say.dm
index 5baae6ee9b54..6818e83cfb4f 100644
--- a/code/modules/mob/say.dm
+++ b/code/modules/mob/say.dm
@@ -142,7 +142,7 @@
if(key)
K = src.key
- var/spanned = say_quote(message)
+ var/spanned = say_quote(say_emphasis(message))
var/source = "[span_prefix("DEAD:")] [span_name("[(src.client.prefs.chat_toggles & GHOST_CKEY) ? "" : "([K]) "][name]")][alt_name]" // yogs - i have no clue
var/rendered = " [span_message("[emoji_parse(spanned)]")]"
log_talk(message, LOG_SAY, tag="DEAD")
diff --git a/interface/fonts/Grand9K_Pixel.ttf b/interface/fonts/Grand9K_Pixel.ttf
new file mode 100644
index 000000000000..cf6fdf44e2ec
Binary files /dev/null and b/interface/fonts/Grand9K_Pixel.ttf differ
diff --git a/interface/fonts/Pixellari.ttf b/interface/fonts/Pixellari.ttf
new file mode 100644
index 000000000000..5a3a3c2b1104
Binary files /dev/null and b/interface/fonts/Pixellari.ttf differ
diff --git a/interface/fonts/SpessFont.ttf b/interface/fonts/SpessFont.ttf
new file mode 100644
index 000000000000..8f7c7e08d0d8
Binary files /dev/null and b/interface/fonts/SpessFont.ttf differ
diff --git a/interface/fonts/TinyUnicode.ttf b/interface/fonts/TinyUnicode.ttf
new file mode 100644
index 000000000000..74d0d3e386e6
Binary files /dev/null and b/interface/fonts/TinyUnicode.ttf differ
diff --git a/interface/fonts/VCR_OSD_Mono.ttf b/interface/fonts/VCR_OSD_Mono.ttf
new file mode 100644
index 000000000000..dcca687a434d
Binary files /dev/null and b/interface/fonts/VCR_OSD_Mono.ttf differ
diff --git a/interface/fonts/fonts_datum.dm b/interface/fonts/fonts_datum.dm
new file mode 100644
index 000000000000..a346706d7fa0
--- /dev/null
+++ b/interface/fonts/fonts_datum.dm
@@ -0,0 +1,78 @@
+/// A font datum, it exists to define a custom font to use in a span style later.
+/datum/font
+ /// Font name, just so people know what to put in their span style.
+ var/name
+ /// The font file we link to.
+ var/font_family
+
+ /// Font features and metrics
+ /// Generated by Lummox's dmifontsplus (https://www.byond.com/developer/LummoxJR/DmiFontsPlus)
+ /// Note: these variable names have been changed, so you can't straight copy/paste from dmifontsplus.exe
+
+ /// list of font size/spacing metrics
+ var/list/metrics
+ /// total height of a line
+ var/height
+ /// distance above baseline (including whitespace)
+ var/ascent
+ /// distance below baseline
+ var/descent
+ /// average character width
+ var/average_width
+ /// maximum character width
+ var/max_width
+ /// extra width, such as from italics, for a line
+ var/overhang
+ /// internal leading vertical space, for accent marks
+ var/in_leading
+ /// external leading vertical space, just plain blank
+ var/ex_leading
+ /// default character (for undefined chars)
+ var/default_character
+ /// first character in metrics
+ var/start
+ /// last character in metrics
+ var/end
+
+/// Get font metrics
+/// From Lummox's dmifontsplus (https://www.byond.com/developer/LummoxJR/DmiFontsPlus)
+/datum/font/proc/get_metrics(text, flags, first_line)
+ . = 0
+ var/longest = 0
+ if(!length(text))
+ return
+
+ var/i = 1
+ var/idx
+ while(i <= length(text))
+ var/character = text2ascii(text, i++)
+ if(character <= 10)
+ if(character <= 7)
+ . += character // spacers for justification
+
+ if(character <= 9)
+ continue // soft-break chars
+
+ if(. && idx && !(flags & INCLUDE_AC))
+ . -= max(metrics[idx + 3], 0)
+
+ longest = max(longest, . + first_line)
+ . = 0
+ first_line = 0
+ idx = 0
+ continue
+
+ idx = (character - start) * 3
+ if(idx <= 0 || idx >= metrics.len)
+ idx = (default_character - start) * 3
+
+ if(!. && !(flags & INCLUDE_AC))
+ . -= metrics[idx + 1]
+ . += metrics[idx + 1] + metrics[idx + 2] + metrics[idx +3]
+
+ if(. && idx && !(flags & INCLUDE_AC))
+ . -= max(metrics[idx + 3], 0)
+
+ . = max(. + first_line, longest)
+ if(. > 0)
+ . += overhang
diff --git a/interface/fonts/grand_9k.dm b/interface/fonts/grand_9k.dm
new file mode 100644
index 000000000000..7993d307bcbe
--- /dev/null
+++ b/interface/fonts/grand_9k.dm
@@ -0,0 +1,253 @@
+/// For clean results on map, use only sizing pt, multiples of 6: 6pt 12pt 18pt 24pt etc. - Not for use with px sizing
+/// Can be used in TGUI etc, px sizing is pt / 0.75. 6pt = 8px, 12pt = 16px etc.
+
+/// Base font
+/datum/font/grand9k
+ name = "Grand9K Pixel"
+ font_family = 'interface/fonts/Grand9K_Pixel.ttf'
+
+/// For icon overlays
+/// Grand9K 6pt metrics generated using Lummox's dmifontsplus (https://www.byond.com/developer/LummoxJR/DmiFontsPlus)
+/// Note: these variable names have been changed, so you can't straight copy/paste from dmifontsplus.exe
+/datum/font/grand9k/size_6pt
+ name = "Grand9K Pixel 6pt"
+ height = 12
+ ascent = 10
+ descent = 2
+ average_width = 4
+ max_width = 9
+ overhang = 0
+ in_leading = 4
+ ex_leading = 1
+ default_character = 31
+ start = 30
+ end = 255
+ metrics = list(
+ 0, 5, 1, // char 30
+ 0, 5, 1, // char 31
+ 0, 1, 1, // char 32
+ 0, 1, 1, // char 33
+ 0, 3, 1, // char 34
+ 0, 6, 1, // char 35
+ 0, 5, 1, // char 36
+ 0, 7, 1, // char 37
+ 0, 5, 1, // char 38
+ 0, 1, 1, // char 39
+ 0, 3, 1, // char 40
+ 0, 3, 1, // char 41
+ 0, 5, 1, // char 42
+ 0, 5, 1, // char 43
+ 0, 1, 1, // char 44
+ 0, 4, 1, // char 45
+ 0, 1, 1, // char 46
+ 0, 3, 1, // char 47
+ 0, 5, 1, // char 48
+ 0, 2, 1, // char 49
+ 0, 5, 1, // char 50
+ 0, 4, 1, // char 51
+ 0, 5, 1, // char 52
+ 0, 5, 1, // char 53
+ 0, 5, 1, // char 54
+ 0, 5, 1, // char 55
+ 0, 5, 1, // char 56
+ 0, 5, 1, // char 57
+ 0, 1, 1, // char 58
+ 0, 1, 1, // char 59
+ 0, 4, 1, // char 60
+ 0, 4, 1, // char 61
+ 0, 4, 1, // char 62
+ 0, 4, 1, // char 63
+ 0, 7, 1, // char 64
+ 0, 5, 1, // char 65
+ 0, 5, 1, // char 66
+ 0, 4, 1, // char 67
+ 0, 5, 1, // char 68
+ 0, 4, 1, // char 69
+ 0, 4, 1, // char 70
+ 0, 5, 1, // char 71
+ 0, 5, 1, // char 72
+ 0, 1, 1, // char 73
+ 0, 5, 1, // char 74
+ 0, 5, 1, // char 75
+ 0, 5, 1, // char 76
+ 0, 5, 1, // char 77
+ 0, 5, 1, // char 78
+ 0, 5, 1, // char 79
+ 0, 5, 1, // char 80
+ 0, 6, 1, // char 81
+ 0, 5, 1, // char 82
+ 0, 5, 1, // char 83
+ 0, 5, 1, // char 84
+ 0, 5, 1, // char 85
+ 0, 5, 1, // char 86
+ 0, 5, 1, // char 87
+ 0, 5, 1, // char 88
+ 0, 5, 1, // char 89
+ 0, 5, 1, // char 90
+ 0, 3, 1, // char 91
+ 0, 3, 1, // char 92
+ 0, 3, 1, // char 93
+ 0, 5, 1, // char 94
+ 0, 4, 0, // char 95
+ 0, 2, 1, // char 96
+ 0, 4, 1, // char 97
+ 0, 4, 1, // char 98
+ 0, 3, 1, // char 99
+ 0, 4, 1, // char 100
+ 0, 4, 1, // char 101
+ 0, 4, 1, // char 102
+ 0, 4, 1, // char 103
+ 0, 4, 1, // char 104
+ 0, 1, 1, // char 105
+ 0, 3, 1, // char 106
+ 0, 4, 1, // char 107
+ 0, 1, 1, // char 108
+ 0, 5, 1, // char 109
+ 0, 4, 1, // char 110
+ 0, 4, 1, // char 111
+ 0, 4, 1, // char 112
+ 0, 4, 1, // char 113
+ 0, 4, 1, // char 114
+ 0, 4, 1, // char 115
+ 0, 4, 1, // char 116
+ 0, 4, 1, // char 117
+ 0, 5, 1, // char 118
+ 0, 5, 1, // char 119
+ 0, 5, 1, // char 120
+ 0, 4, 1, // char 121
+ 0, 5, 1, // char 122
+ 0, 4, 1, // char 123
+ 0, 1, 1, // char 124
+ 0, 4, 1, // char 125
+ 0, 6, 1, // char 126
+ 0, 5, 1, // char 127
+ 0, 5, 1, // char 128
+ 0, 5, 1, // char 129
+ 0, 1, 1, // char 130
+ 0, 5, 1, // char 131
+ 0, 3, 1, // char 132
+ 0, 5, 1, // char 133
+ 0, 5, 1, // char 134
+ 0, 5, 1, // char 135
+ 0, 5, 1, // char 136
+ 0, 5, 1, // char 137
+ 0, 5, 1, // char 138
+ 0, 3, 1, // char 139
+ 0, 6, 1, // char 140
+ 0, 5, 1, // char 141
+ 0, 5, 1, // char 142
+ 0, 5, 1, // char 143
+ 0, 5, 1, // char 144
+ 0, 1, 1, // char 145
+ 0, 1, 1, // char 146
+ 0, 3, 1, // char 147
+ 0, 3, 1, // char 148
+ 0, 1, 1, // char 149
+ 0, 5, 1, // char 150
+ 0, 5, 1, // char 151
+ 0, 5, 1, // char 152
+ 0, 8, 1, // char 153
+ 0, 4, 1, // char 154
+ 0, 3, 1, // char 155
+ 0, 5, 1, // char 156
+ 0, 5, 1, // char 157
+ 0, 5, 1, // char 158
+ 0, 5, 1, // char 159
+ 0, 1, 1, // char 160
+ 0, 1, 1, // char 161
+ 0, 4, 1, // char 162
+ 0, 5, 1, // char 163
+ 0, 5, 1, // char 164
+ 0, 5, 1, // char 165
+ 0, 1, 1, // char 166
+ 0, 5, 1, // char 167
+ 0, 3, 1, // char 168
+ 0, 8, 1, // char 169
+ 0, 5, 1, // char 170
+ 0, 6, 1, // char 171
+ 0, 4, 1, // char 172
+ 0, 5, 1, // char 173
+ 0, 8, 1, // char 174
+ 0, 5, 1, // char 175
+ 0, 3, 1, // char 176
+ 0, 5, 1, // char 177
+ 0, 5, 1, // char 178
+ 0, 5, 1, // char 179
+ 0, 2, 1, // char 180
+ 0, 4, 1, // char 181
+ 0, 5, 1, // char 182
+ 0, 1, 1, // char 183
+ 0, 2, 1, // char 184
+ 0, 5, 1, // char 185
+ 0, 5, 1, // char 186
+ 0, 6, 1, // char 187
+ 0, 5, 1, // char 188
+ 0, 5, 1, // char 189
+ 0, 5, 1, // char 190
+ 0, 4, 1, // char 191
+ 0, 5, 1, // char 192
+ 0, 5, 1, // char 193
+ 0, 5, 1, // char 194
+ 0, 6, 0, // char 195
+ 0, 5, 1, // char 196
+ 0, 5, 1, // char 197
+ 0, 6, 1, // char 198
+ 0, 4, 1, // char 199
+ 0, 4, 1, // char 200
+ 0, 4, 1, // char 201
+ 0, 4, 1, // char 202
+ 0, 4, 1, // char 203
+ 1, 2, 0, // char 204
+ 0, 2, 1, // char 205
+ 0, 3, 0, // char 206
+ 0, 3, 0, // char 207
+ 0, 6, 1, // char 208
+ 0, 6, 0, // char 209
+ 0, 5, 1, // char 210
+ 0, 5, 1, // char 211
+ 0, 5, 1, // char 212
+ 0, 6, 1, // char 213
+ 0, 5, 1, // char 214
+ 0, 5, 1, // char 215
+ 0, 5, 1, // char 216
+ 0, 5, 1, // char 217
+ 0, 5, 1, // char 218
+ 0, 5, 1, // char 219
+ 0, 5, 1, // char 220
+ 0, 5, 1, // char 221
+ 0, 5, 1, // char 222
+ 0, 5, 1, // char 223
+ 0, 4, 1, // char 224
+ 0, 4, 1, // char 225
+ 0, 4, 1, // char 226
+ 0, 4, 1, // char 227
+ 0, 4, 1, // char 228
+ 0, 4, 1, // char 229
+ 0, 5, 1, // char 230
+ 0, 3, 1, // char 231
+ 0, 4, 1, // char 232
+ 0, 4, 1, // char 233
+ 0, 4, 1, // char 234
+ 0, 4, 1, // char 235
+ 0, 2, 1, // char 236
+ 1, 2, 0, // char 237
+ 0, 3, 0, // char 238
+ 0, 3, 0, // char 239
+ 0, 5, 0, // char 240
+ 0, 4, 1, // char 241
+ 0, 4, 1, // char 242
+ 0, 4, 1, // char 243
+ 0, 4, 1, // char 244
+ 0, 4, 1, // char 245
+ 0, 4, 1, // char 246
+ 0, 5, 1, // char 247
+ 0, 4, 1, // char 248
+ 0, 4, 1, // char 249
+ 0, 4, 1, // char 250
+ 0, 4, 1, // char 251
+ 0, 4, 1, // char 252
+ 0, 4, 1, // char 253
+ 0, 4, 1, // char 254
+ 0, 4, 1, // char 255
+ 226
+ )
diff --git a/interface/fonts/license.txt b/interface/fonts/license.txt
new file mode 100644
index 000000000000..9aa70fbac2a9
--- /dev/null
+++ b/interface/fonts/license.txt
@@ -0,0 +1,13 @@
+Grand9K Pixel created by Jayvee Enaguas. Licensed under Creative Commons Attribution 4.0 International (CC BY 4.0)
+(https://creativecommons.org/licenses/by/4.0/) (https://www.dafont.com/grand9k-pixel.font)
+
+Pixellari created by Zacchary Dempsey-Plante. Website indicates free for commercial use.
+(https://www.dafont.com/pixellari.font?fpp=200)
+
+Spess Font created by MTandi (discord) for /tg/station.
+
+Tiny Unicode created by Jakob Riedle/DuffsDevice. Website indicates free for commercial use.
+(https://fontmeme.com/fonts/tiny-unicode-font/)
+
+VCR OSD Mono created by Riciery Leal/mrmanet. Website indicates 100% free, author confirms it's free for all to use.
+(https://www.dafont.com/font-comment.php?file=vcr_osd_mono)
diff --git a/interface/fonts/pixellari.dm b/interface/fonts/pixellari.dm
new file mode 100644
index 000000000000..24fcd1961fec
--- /dev/null
+++ b/interface/fonts/pixellari.dm
@@ -0,0 +1,252 @@
+/// For clean results on map, use only sizing pt, multiples of 12: 12pt 24pt 48pt etc. - Not for use with px sizing
+/// Can be used in TGUI etc, px sizing is pt / 0.75. 12pt = 16px, 24pt = 32px etc.
+
+/// Base font
+/datum/font/pixellari
+ name = "Pixellari"
+ font_family = 'interface/fonts/Pixellari.ttf'
+
+/// For icon overlays
+/// Pixellari 12pt metrics generated using Lummox's dmifontsplus (https://www.byond.com/developer/LummoxJR/DmiFontsPlus)
+/// Note: these variable names have been changed, so you can't straight copy/paste from dmifontsplus.exe
+/datum/font/pixellari/size_12pt
+ name = "Pixellari 12pt"
+ height = 16
+ ascent = 12
+ descent = 4
+ average_width = 7
+ max_width = 15
+ overhang = 0
+ in_leading = 0
+ ex_leading = 1
+ default_character = 31
+ start = 30
+ end = 255
+ metrics = list(\
+ 1, 5, 0, /* char 30 */ \
+ 1, 5, 0, /* char 31 */ \
+ 0, 1, 4, /* char 32 */ \
+ 1, 2, 1, /* char 33 */ \
+ 1, 5, 1, /* char 34 */ \
+ 0, 8, 1, /* char 35 */ \
+ 2, 6, 1, /* char 36 */ \
+ 0, 13, 1, /* char 37 */ \
+ 1, 8, 1, /* char 38 */ \
+ 1, 2, 1, /* char 39 */ \
+ 1, 3, 1, /* char 40 */ \
+ 2, 3, 1, /* char 41 */ \
+ 0, 6, 1, /* char 42 */ \
+ 1, 6, 1, /* char 43 */ \
+ 1, 2, 1, /* char 44 */ \
+ 1, 6, 1, /* char 45 */ \
+ 1, 2, 1, /* char 46 */ \
+ 0, 6, 1, /* char 47 */ \
+ 1, 7, 1, /* char 48 */ \
+ 2, 6, 1, /* char 49 */ \
+ 1, 6, 1, /* char 50 */ \
+ 1, 6, 1, /* char 51 */ \
+ 1, 7, 1, /* char 52 */ \
+ 1, 6, 1, /* char 53 */ \
+ 1, 6, 1, /* char 54 */ \
+ 1, 7, 1, /* char 55 */ \
+ 1, 6, 1, /* char 56 */ \
+ 1, 6, 1, /* char 57 */ \
+ 1, 2, 1, /* char 58 */ \
+ 1, 2, 1, /* char 59 */ \
+ 0, 10, 1, /* char 60 */ \
+ 1, 6, 1, /* char 61 */ \
+ 0, 10, 1, /* char 62 */ \
+ 1, 6, 1, /* char 63 */ \
+ 1, 12, 1, /* char 64 */ \
+ 1, 8, 1, /* char 65 */ \
+ 1, 8, 1, /* char 66 */ \
+ 2, 7, 1, /* char 67 */ \
+ 2, 8, 1, /* char 68 */ \
+ 2, 6, 1, /* char 69 */ \
+ 2, 6, 1, /* char 70 */ \
+ 2, 7, 1, /* char 71 */ \
+ 1, 8, 1, /* char 72 */ \
+ 1, 4, 1, /* char 73 */ \
+ 0, 7, 1, /* char 74 */ \
+ 1, 8, 1, /* char 75 */ \
+ 1, 6, 1, /* char 76 */ \
+ 1, 10, 1, /* char 77 */ \
+ 1, 9, 1, /* char 78 */ \
+ 2, 8, 1, /* char 79 */ \
+ 1, 7, 1, /* char 80 */ \
+ 2, 9, 1, /* char 81 */ \
+ 1, 8, 1, /* char 82 */ \
+ 1, 8, 1, /* char 83 */ \
+ 1, 8, 1, /* char 84 */ \
+ 2, 8, 1, /* char 85 */ \
+ 2, 8, 1, /* char 86 */ \
+ 1, 10, 1, /* char 87 */ \
+ 1, 8, 1, /* char 88 */ \
+ 1, 8, 1, /* char 89 */ \
+ 0, 10, 1, /* char 90 */ \
+ 1, 3, 1, /* char 91 */ \
+ 0, 6, 1, /* char 92 */ \
+ 2, 3, 1, /* char 93 */ \
+ 0, 7, 1, /* char 94 */ \
+ 0, 8, 1, /* char 95 */ \
+ 1, 3, 1, /* char 96 */ \
+ 1, 6, 1, /* char 97 */ \
+ 1, 7, 1, /* char 98 */ \
+ 1, 6, 1, /* char 99 */ \
+ 1, 7, 1, /* char 100 */ \
+ 1, 6, 1, /* char 101 */ \
+ 1, 4, 1, /* char 102 */ \
+ 1, 7, 1, /* char 103 */ \
+ 1, 7, 1, /* char 104 */ \
+ 1, 2, 1, /* char 105 */ \
+ -1, 4, 1, /* char 106 */ \
+ 0, 7, 1, /* char 107 */ \
+ 1, 2, 1, /* char 108 */ \
+ 1, 10, 1, /* char 109 */ \
+ 1, 6, 1, /* char 110 */ \
+ 1, 6, 1, /* char 111 */ \
+ 1, 7, 1, /* char 112 */ \
+ 1, 7, 1, /* char 113 */ \
+ 1, 6, 1, /* char 114 */ \
+ 1, 6, 1, /* char 115 */ \
+ 0, 4, 1, /* char 116 */ \
+ 1, 6, 1, /* char 117 */ \
+ 1, 6, 1, /* char 118 */ \
+ 1, 10, 1, /* char 119 */ \
+ 1, 6, 1, /* char 120 */ \
+ 1, 6, 1, /* char 121 */ \
+ 1, 6, 1, /* char 122 */ \
+ 0, 5, 1, /* char 123 */ \
+ 1, 2, 1, /* char 124 */ \
+ 0, 5, 1, /* char 125 */ \
+ 1, 8, 1, /* char 126 */ \
+ 1, 5, 0, /* char 127 */ \
+ 1, 8, 1, /* char 128 */ \
+ 1, 5, 0, /* char 129 */ \
+ 1, 5, 0, /* char 130 */ \
+ 1, 5, 0, /* char 131 */ \
+ 1, 5, 0, /* char 132 */ \
+ 1, 5, 0, /* char 133 */ \
+ 1, 5, 0, /* char 134 */ \
+ 1, 5, 0, /* char 135 */ \
+ 1, 5, 0, /* char 136 */ \
+ 1, 5, 0, /* char 137 */ \
+ 1, 8, 1, /* char 138 */ \
+ 1, 5, 0, /* char 139 */ \
+ 0, 14, 1, /* char 140 */ \
+ 1, 5, 0, /* char 141 */ \
+ 0, 10, 1, /* char 142 */ \
+ 1, 5, 0, /* char 143 */ \
+ 1, 5, 0, /* char 144 */ \
+ 1, 5, 0, /* char 145 */ \
+ 1, 5, 0, /* char 146 */ \
+ 1, 5, 0, /* char 147 */ \
+ 1, 5, 0, /* char 148 */ \
+ 1, 5, 0, /* char 149 */ \
+ 1, 5, 0, /* char 150 */ \
+ 1, 5, 0, /* char 151 */ \
+ 1, 5, 0, /* char 152 */ \
+ 1, 5, 0, /* char 153 */ \
+ 1, 6, 1, /* char 154 */ \
+ 1, 5, 0, /* char 155 */ \
+ 1, 11, 1, /* char 156 */ \
+ 1, 5, 0, /* char 157 */ \
+ 1, 6, 1, /* char 158 */ \
+ 1, 8, 1, /* char 159 */ \
+ 0, 1, 4, /* char 160 */ \
+ 1, 2, 1, /* char 161 */ \
+ 1, 6, 1, /* char 162 */ \
+ 0, 8, 1, /* char 163 */ \
+ 0, 9, 1, /* char 164 */ \
+ 1, 8, 1, /* char 165 */ \
+ 1, 2, 1, /* char 166 */ \
+ 1, 7, 1, /* char 167 */ \
+ 0, 5, 1, /* char 168 */ \
+ -1, 12, 1, /* char 169 */ \
+ 0, 6, 1, /* char 170 */ \
+ 0, 8, 1, /* char 171 */ \
+ 1, 8, 1, /* char 172 */ \
+ 1, 5, 0, /* char 173 */ \
+ -1, 12, 1, /* char 174 */ \
+ 2, 4, 1, /* char 175 */ \
+ 0, 6, 1, /* char 176 */ \
+ 1, 6, 1, /* char 177 */ \
+ 0, 5, 1, /* char 178 */ \
+ 0, 5, 1, /* char 179 */ \
+ 1, 3, 1, /* char 180 */ \
+ 1, 6, 1, /* char 181 */ \
+ 1, 7, 1, /* char 182 */ \
+ 1, 2, 1, /* char 183 */ \
+ 1, 3, 1, /* char 184 */ \
+ 1, 4, 1, /* char 185 */ \
+ 0, 6, 1, /* char 186 */ \
+ 0, 8, 1, /* char 187 */ \
+ 1, 13, 1, /* char 188 */ \
+ 1, 12, 1, /* char 189 */ \
+ 0, 13, 1, /* char 190 */ \
+ 1, 6, 1, /* char 191 */ \
+ 1, 8, 1, /* char 192 */ \
+ 1, 8, 1, /* char 193 */ \
+ 1, 8, 1, /* char 194 */ \
+ 1, 8, 1, /* char 195 */ \
+ 1, 8, 1, /* char 196 */ \
+ 1, 8, 1, /* char 197 */ \
+ 0, 13, 1, /* char 198 */ \
+ 2, 7, 1, /* char 199 */ \
+ 2, 6, 1, /* char 200 */ \
+ 2, 6, 1, /* char 201 */ \
+ 2, 6, 1, /* char 202 */ \
+ 2, 6, 1, /* char 203 */ \
+ 1, 4, 1, /* char 204 */ \
+ 1, 4, 1, /* char 205 */ \
+ 1, 4, 1, /* char 206 */ \
+ 1, 4, 1, /* char 207 */ \
+ 0, 10, 1, /* char 208 */ \
+ 1, 9, 1, /* char 209 */ \
+ 2, 8, 1, /* char 210 */ \
+ 2, 8, 1, /* char 211 */ \
+ 2, 8, 1, /* char 212 */ \
+ 2, 8, 1, /* char 213 */ \
+ 2, 8, 1, /* char 214 */ \
+ 1, 6, 1, /* char 215 */ \
+ -2, 14, 1, /* char 216 */ \
+ 2, 8, 1, /* char 217 */ \
+ 2, 8, 1, /* char 218 */ \
+ 2, 8, 1, /* char 219 */ \
+ 2, 8, 1, /* char 220 */ \
+ 1, 8, 1, /* char 221 */ \
+ 1, 8, 1, /* char 222 */ \
+ 1, 8, 1, /* char 223 */ \
+ 1, 6, 1, /* char 224 */ \
+ 1, 6, 1, /* char 225 */ \
+ 1, 6, 1, /* char 226 */ \
+ 1, 6, 1, /* char 227 */ \
+ 1, 6, 1, /* char 228 */ \
+ 1, 6, 1, /* char 229 */ \
+ 1, 11, 1, /* char 230 */ \
+ 1, 6, 1, /* char 231 */ \
+ 1, 6, 1, /* char 232 */ \
+ 1, 6, 1, /* char 233 */ \
+ 1, 6, 1, /* char 234 */ \
+ 1, 6, 1, /* char 235 */ \
+ 1, 2, 1, /* char 236 */ \
+ 1, 2, 1, /* char 237 */ \
+ 0, 4, 1, /* char 238 */ \
+ 0, 4, 1, /* char 239 */ \
+ 1, 7, 1, /* char 240 */ \
+ 1, 6, 1, /* char 241 */ \
+ 1, 6, 1, /* char 242 */ \
+ 1, 6, 1, /* char 243 */ \
+ 1, 6, 1, /* char 244 */ \
+ 1, 6, 1, /* char 245 */ \
+ 1, 6, 1, /* char 246 */ \
+ 1, 6, 1, /* char 247 */ \
+ 0, 10, 1, /* char 248 */ \
+ 1, 6, 1, /* char 249 */ \
+ 1, 6, 1, /* char 250 */ \
+ 1, 6, 1, /* char 251 */ \
+ 1, 6, 1, /* char 252 */ \
+ 1, 6, 1, /* char 253 */ \
+ 1, 8, 1, /* char 254 */ \
+ 1, 6, 1, /* char 255 */ \
+ 226)
diff --git a/interface/fonts/spess_font.dm b/interface/fonts/spess_font.dm
new file mode 100644
index 000000000000..07e8ea5b3ba6
--- /dev/null
+++ b/interface/fonts/spess_font.dm
@@ -0,0 +1,252 @@
+/// For clean results on map, use only sizing pt, multiples of 6: 6t 12pt 18pt etc. - Not for use with px sizing
+/// Can be used in TGUI etc, px sizing is pt / 0.75. 12pt = 16px, 24pt = 32px etc.
+
+/// Base font
+/datum/font/spessfont
+ name = "Spess Font"
+ font_family = 'interface/fonts/SpessFont.ttf'
+
+/// For icon overlays
+/// Spess Font 6pt metrics generated using Lummox's dmifontsplus (https://www.byond.com/developer/LummoxJR/DmiFontsPlus)
+/// Note: these variable names have been changed, so you can't straight copy/paste from dmifontsplus.exe
+/datum/font/spessfont/size_6pt
+ name = "Spess Font 6pt"
+ height = 8
+ ascent = 6
+ descent = 2
+ average_width = 4
+ max_width = 6
+ overhang = 0
+ in_leading = 0
+ ex_leading = 0
+ default_character = 31
+ start = 30
+ end = 255
+ metrics = list(\
+ 0, 1, 0, /* char 30 */ \
+ 0, 1, 0, /* char 31 */ \
+ 0, 1, 1, /* char 32 */ \
+ 0, 1, 1, /* char 33 */ \
+ 0, 3, 1, /* char 34 */ \
+ 0, 5, 1, /* char 35 */ \
+ 0, 3, 1, /* char 36 */ \
+ 0, 5, 1, /* char 37 */ \
+ 0, 5, 1, /* char 38 */ \
+ 0, 1, 1, /* char 39 */ \
+ 0, 2, 1, /* char 40 */ \
+ 0, 2, 1, /* char 41 */ \
+ 0, 3, 1, /* char 42 */ \
+ 0, 3, 1, /* char 43 */ \
+ 0, 1, 1, /* char 44 */ \
+ 0, 3, 1, /* char 45 */ \
+ 0, 1, 1, /* char 46 */ \
+ 0, 3, 1, /* char 47 */ \
+ 0, 4, 1, /* char 48 */ \
+ 0, 2, 1, /* char 49 */ \
+ 0, 4, 1, /* char 50 */ \
+ 0, 4, 1, /* char 51 */ \
+ 0, 4, 1, /* char 52 */ \
+ 0, 4, 1, /* char 53 */ \
+ 0, 4, 1, /* char 54 */ \
+ 0, 4, 1, /* char 55 */ \
+ 0, 4, 1, /* char 56 */ \
+ 0, 4, 1, /* char 57 */ \
+ 0, 1, 1, /* char 58 */ \
+ 0, 1, 1, /* char 59 */ \
+ 0, 3, 1, /* char 60 */ \
+ 0, 3, 1, /* char 61 */ \
+ 0, 3, 1, /* char 62 */ \
+ 0, 3, 1, /* char 63 */ \
+ 0, 4, 1, /* char 64 */ \
+ 0, 4, 1, /* char 65 */ \
+ 0, 4, 1, /* char 66 */ \
+ 0, 4, 1, /* char 67 */ \
+ 0, 4, 1, /* char 68 */ \
+ 0, 4, 1, /* char 69 */ \
+ 0, 4, 1, /* char 70 */ \
+ 0, 4, 1, /* char 71 */ \
+ 0, 4, 1, /* char 72 */ \
+ 0, 3, 1, /* char 73 */ \
+ 0, 4, 1, /* char 74 */ \
+ 0, 4, 1, /* char 75 */ \
+ 0, 4, 1, /* char 76 */ \
+ 0, 5, 1, /* char 77 */ \
+ 0, 4, 1, /* char 78 */ \
+ 0, 4, 1, /* char 79 */ \
+ 0, 4, 1, /* char 80 */ \
+ 0, 4, 1, /* char 81 */ \
+ 0, 4, 1, /* char 82 */ \
+ 0, 4, 1, /* char 83 */ \
+ 0, 5, 1, /* char 84 */ \
+ 0, 4, 1, /* char 85 */ \
+ 0, 4, 1, /* char 86 */ \
+ 0, 5, 1, /* char 87 */ \
+ 0, 5, 1, /* char 88 */ \
+ 0, 4, 1, /* char 89 */ \
+ 0, 4, 1, /* char 90 */ \
+ 0, 2, 1, /* char 91 */ \
+ 0, 3, 1, /* char 92 */ \
+ 0, 2, 1, /* char 93 */ \
+ 0, 3, 1, /* char 94 */ \
+ 0, 4, 1, /* char 95 */ \
+ 0, 2, 1, /* char 96 */ \
+ 0, 3, 1, /* char 97 */ \
+ 0, 4, 1, /* char 98 */ \
+ 0, 3, 1, /* char 99 */ \
+ 0, 4, 1, /* char 100 */ \
+ 0, 3, 1, /* char 101 */ \
+ 0, 2, 1, /* char 102 */ \
+ 0, 4, 1, /* char 103 */ \
+ 0, 3, 1, /* char 104 */ \
+ 0, 1, 1, /* char 105 */ \
+ 0, 1, 1, /* char 106 */ \
+ 0, 3, 1, /* char 107 */ \
+ 0, 1, 1, /* char 108 */ \
+ 0, 5, 1, /* char 109 */ \
+ 0, 3, 1, /* char 110 */ \
+ 0, 4, 1, /* char 111 */ \
+ 0, 4, 1, /* char 112 */ \
+ 0, 4, 1, /* char 113 */ \
+ 0, 2, 1, /* char 114 */ \
+ 0, 3, 1, /* char 115 */ \
+ 0, 2, 1, /* char 116 */ \
+ 0, 3, 1, /* char 117 */ \
+ 0, 3, 1, /* char 118 */ \
+ 0, 5, 1, /* char 119 */ \
+ 0, 3, 1, /* char 120 */ \
+ 0, 3, 1, /* char 121 */ \
+ 0, 3, 1, /* char 122 */ \
+ 0, 3, 1, /* char 123 */ \
+ 0, 1, 1, /* char 124 */ \
+ 0, 3, 1, /* char 125 */ \
+ 0, 4, 1, /* char 126 */ \
+ 0, 1, 0, /* char 127 */ \
+ 0, 1, 0, /* char 128 */ \
+ 0, 1, 0, /* char 129 */ \
+ 0, 1, 0, /* char 130 */ \
+ 0, 1, 0, /* char 131 */ \
+ 0, 1, 0, /* char 132 */ \
+ 0, 1, 0, /* char 133 */ \
+ 0, 1, 0, /* char 134 */ \
+ 0, 1, 0, /* char 135 */ \
+ 0, 1, 0, /* char 136 */ \
+ 0, 1, 0, /* char 137 */ \
+ 0, 1, 0, /* char 138 */ \
+ 0, 1, 0, /* char 139 */ \
+ 0, 1, 0, /* char 140 */ \
+ 0, 1, 0, /* char 141 */ \
+ 0, 1, 0, /* char 142 */ \
+ 0, 1, 0, /* char 143 */ \
+ 0, 1, 0, /* char 144 */ \
+ 0, 1, 0, /* char 145 */ \
+ 0, 1, 0, /* char 146 */ \
+ 0, 1, 0, /* char 147 */ \
+ 0, 1, 0, /* char 148 */ \
+ 0, 1, 0, /* char 149 */ \
+ 0, 1, 0, /* char 150 */ \
+ 0, 1, 0, /* char 151 */ \
+ 0, 1, 0, /* char 152 */ \
+ 0, 1, 0, /* char 153 */ \
+ 0, 1, 0, /* char 154 */ \
+ 0, 1, 0, /* char 155 */ \
+ 0, 1, 0, /* char 156 */ \
+ 0, 1, 0, /* char 157 */ \
+ 0, 1, 0, /* char 158 */ \
+ 0, 1, 0, /* char 159 */ \
+ 0, 1, 0, /* char 160 */ \
+ 0, 1, 0, /* char 161 */ \
+ 0, 1, 0, /* char 162 */ \
+ 0, 1, 0, /* char 163 */ \
+ 0, 1, 0, /* char 164 */ \
+ 0, 1, 0, /* char 165 */ \
+ 0, 1, 0, /* char 166 */ \
+ 0, 1, 0, /* char 167 */ \
+ 0, 1, 0, /* char 168 */ \
+ 0, 1, 0, /* char 169 */ \
+ 0, 1, 0, /* char 170 */ \
+ 0, 1, 0, /* char 171 */ \
+ 0, 1, 0, /* char 172 */ \
+ 0, 1, 0, /* char 173 */ \
+ 0, 1, 0, /* char 174 */ \
+ 0, 1, 0, /* char 175 */ \
+ 0, 1, 0, /* char 176 */ \
+ 0, 1, 0, /* char 177 */ \
+ 0, 1, 0, /* char 178 */ \
+ 0, 1, 0, /* char 179 */ \
+ 0, 1, 0, /* char 180 */ \
+ 0, 1, 0, /* char 181 */ \
+ 0, 1, 0, /* char 182 */ \
+ 0, 1, 0, /* char 183 */ \
+ 0, 1, 0, /* char 184 */ \
+ 0, 1, 0, /* char 185 */ \
+ 0, 1, 0, /* char 186 */ \
+ 0, 1, 0, /* char 187 */ \
+ 0, 1, 0, /* char 188 */ \
+ 0, 1, 0, /* char 189 */ \
+ 0, 1, 0, /* char 190 */ \
+ 0, 1, 0, /* char 191 */ \
+ 0, 1, 0, /* char 192 */ \
+ 0, 1, 0, /* char 193 */ \
+ 0, 1, 0, /* char 194 */ \
+ 0, 1, 0, /* char 195 */ \
+ 0, 1, 0, /* char 196 */ \
+ 0, 1, 0, /* char 197 */ \
+ 0, 1, 0, /* char 198 */ \
+ 0, 1, 0, /* char 199 */ \
+ 0, 1, 0, /* char 200 */ \
+ 0, 1, 0, /* char 201 */ \
+ 0, 1, 0, /* char 202 */ \
+ 0, 1, 0, /* char 203 */ \
+ 0, 1, 0, /* char 204 */ \
+ 0, 1, 0, /* char 205 */ \
+ 0, 1, 0, /* char 206 */ \
+ 0, 1, 0, /* char 207 */ \
+ 0, 1, 0, /* char 208 */ \
+ 0, 1, 0, /* char 209 */ \
+ 0, 1, 0, /* char 210 */ \
+ 0, 1, 0, /* char 211 */ \
+ 0, 1, 0, /* char 212 */ \
+ 0, 1, 0, /* char 213 */ \
+ 0, 1, 0, /* char 214 */ \
+ 0, 1, 0, /* char 215 */ \
+ 0, 1, 0, /* char 216 */ \
+ 0, 1, 0, /* char 217 */ \
+ 0, 1, 0, /* char 218 */ \
+ 0, 1, 0, /* char 219 */ \
+ 0, 1, 0, /* char 220 */ \
+ 0, 1, 0, /* char 221 */ \
+ 0, 1, 0, /* char 222 */ \
+ 0, 1, 0, /* char 223 */ \
+ 0, 1, 0, /* char 224 */ \
+ 0, 1, 0, /* char 225 */ \
+ 0, 1, 0, /* char 226 */ \
+ 0, 1, 0, /* char 227 */ \
+ 0, 1, 0, /* char 228 */ \
+ 0, 1, 0, /* char 229 */ \
+ 0, 1, 0, /* char 230 */ \
+ 0, 1, 0, /* char 231 */ \
+ 0, 1, 0, /* char 232 */ \
+ 0, 1, 0, /* char 233 */ \
+ 0, 1, 0, /* char 234 */ \
+ 0, 1, 0, /* char 235 */ \
+ 0, 1, 0, /* char 236 */ \
+ 0, 1, 0, /* char 237 */ \
+ 0, 1, 0, /* char 238 */ \
+ 0, 1, 0, /* char 239 */ \
+ 0, 1, 0, /* char 240 */ \
+ 0, 1, 0, /* char 241 */ \
+ 0, 1, 0, /* char 242 */ \
+ 0, 1, 0, /* char 243 */ \
+ 0, 1, 0, /* char 244 */ \
+ 0, 1, 0, /* char 245 */ \
+ 0, 1, 0, /* char 246 */ \
+ 0, 1, 0, /* char 247 */ \
+ 0, 1, 0, /* char 248 */ \
+ 0, 1, 0, /* char 249 */ \
+ 0, 1, 0, /* char 250 */ \
+ 0, 1, 0, /* char 251 */ \
+ 0, 1, 0, /* char 252 */ \
+ 0, 1, 0, /* char 253 */ \
+ 0, 1, 0, /* char 254 */ \
+ 0, 1, 0, /* char 255 */ \
+ 226)
diff --git a/interface/fonts/tiny_unicode.dm b/interface/fonts/tiny_unicode.dm
new file mode 100644
index 000000000000..d6af265d5182
--- /dev/null
+++ b/interface/fonts/tiny_unicode.dm
@@ -0,0 +1,253 @@
+/// For clean results on map, use only sizing pt, multiples of 12: 12pt 24pt 48pt etc. - Not for use with px sizing
+/// Can be used in TGUI etc, px sizing is pt / 0.75. 12pt = 16px, 24pt = 32px etc.
+
+/// Base font
+/datum/font/tiny_unicode
+ name = "TinyUnicode"
+ font_family = 'interface/fonts/TinyUnicode.ttf'
+
+/// For icon overlays
+/// TinyUnicode 12pt metrics generated using Lummox's dmifontsplus (https://www.byond.com/developer/LummoxJR/DmiFontsPlus)
+/// Note: these variable names have been changed, so you can't straight copy/paste from dmifontsplus.exe
+/datum/font/tiny_unicode/size_12pt
+ name = "TinyUnicode 12pt"
+ height = 13
+ ascent = 11
+ descent = 2
+ average_width = 5
+ max_width = 11
+ overhang = 0
+ in_leading = -3
+ ex_leading = 1
+ default_character = 31
+ start = 30
+ end = 255
+ metrics = list(
+ 1, 5, 0, // char 30
+ 1, 5, 0, // char 31
+ 0, 1, 4, // char 32
+ 0, 1, 1, // char 33
+ 0, 3, 1, // char 34
+ 0, 5, 1, // char 35
+ 0, 4, 1, // char 36
+ 0, 3, 1, // char 37
+ 0, 5, 1, // char 38
+ 0, 1, 1, // char 39
+ 0, 2, 1, // char 40
+ 0, 2, 1, // char 41
+ 0, 3, 1, // char 42
+ 0, 3, 1, // char 43
+ 0, 2, 1, // char 44
+ 0, 3, 1, // char 45
+ 0, 1, 1, // char 46
+ 0, 3, 1, // char 47
+ 0, 4, 1, // char 48
+ 0, 2, 1, // char 49
+ 0, 4, 1, // char 50
+ 0, 4, 1, // char 51
+ 0, 4, 1, // char 52
+ 0, 4, 1, // char 53
+ 0, 4, 1, // char 54
+ 0, 4, 1, // char 55
+ 0, 4, 1, // char 56
+ 0, 4, 1, // char 57
+ 0, 1, 1, // char 58
+ 0, 2, 1, // char 59
+ 0, 2, 1, // char 60
+ 0, 4, 1, // char 61
+ 0, 2, 1, // char 62
+ 0, 4, 1, // char 63
+ 0, 7, 1, // char 64
+ 0, 4, 1, // char 65
+ 0, 4, 1, // char 66
+ 0, 3, 1, // char 67
+ 0, 4, 1, // char 68
+ 0, 3, 1, // char 69
+ 0, 3, 1, // char 70
+ 0, 4, 1, // char 71
+ 0, 4, 1, // char 72
+ 0, 3, 1, // char 73
+ 0, 4, 1, // char 74
+ 0, 4, 1, // char 75
+ 0, 3, 1, // char 76
+ 0, 5, 1, // char 77
+ 0, 4, 1, // char 78
+ 0, 4, 1, // char 79
+ 0, 4, 1, // char 80
+ 0, 4, 1, // char 81
+ 0, 4, 1, // char 82
+ 0, 4, 1, // char 83
+ 0, 3, 1, // char 84
+ 0, 4, 1, // char 85
+ 0, 4, 1, // char 86
+ 0, 5, 1, // char 87
+ 0, 4, 1, // char 88
+ 0, 4, 1, // char 89
+ 0, 3, 1, // char 90
+ 0, 2, 1, // char 91
+ 0, 3, 1, // char 92
+ 0, 2, 1, // char 93
+ 0, 3, 1, // char 94
+ 0, 5, 1, // char 95
+ 0, 2, 1, // char 96
+ 0, 4, 1, // char 97
+ 0, 4, 1, // char 98
+ 0, 3, 1, // char 99
+ 0, 4, 1, // char 100
+ 0, 4, 1, // char 101
+ 0, 3, 1, // char 102
+ 0, 4, 1, // char 103
+ 0, 4, 1, // char 104
+ 0, 1, 1, // char 105
+ 0, 2, 1, // char 106
+ 0, 4, 1, // char 107
+ 0, 1, 1, // char 108
+ 0, 5, 1, // char 109
+ 0, 4, 1, // char 110
+ 0, 4, 1, // char 111
+ 0, 4, 1, // char 112
+ 0, 4, 1, // char 113
+ 0, 3, 1, // char 114
+ 0, 4, 1, // char 115
+ 0, 3, 1, // char 116
+ 0, 4, 1, // char 117
+ 0, 4, 1, // char 118
+ 0, 5, 1, // char 119
+ 0, 3, 1, // char 120
+ 0, 4, 1, // char 121
+ 0, 4, 1, // char 122
+ 0, 3, 1, // char 123
+ 0, 1, 1, // char 124
+ 0, 3, 1, // char 125
+ 0, 5, 1, // char 126
+ 1, 5, 0, // char 127
+ 0, 4, 1, // char 128
+ 1, 5, 0, // char 129
+ 1, 5, 0, // char 130
+ 1, 5, 0, // char 131
+ 1, 5, 0, // char 132
+ 1, 5, 0, // char 133
+ 1, 5, 0, // char 134
+ 1, 5, 0, // char 135
+ 1, 5, 0, // char 136
+ 0, 5, 1, // char 137
+ 1, 5, 0, // char 138
+ 1, 5, 0, // char 139
+ 0, 6, 1, // char 140
+ 1, 5, 0, // char 141
+ 1, 5, 0, // char 142
+ 1, 5, 0, // char 143
+ 1, 5, 0, // char 144
+ 1, 5, 0, // char 145
+ 1, 5, 0, // char 146
+ 1, 5, 0, // char 147
+ 1, 5, 0, // char 148
+ 0, 2, 1, // char 149
+ 1, 5, 0, // char 150
+ 1, 5, 0, // char 151
+ 1, 5, 0, // char 152
+ 0, 4, 1, // char 153
+ 1, 5, 0, // char 154
+ 1, 5, 0, // char 155
+ 1, 5, 0, // char 156
+ 1, 5, 0, // char 157
+ 1, 5, 0, // char 158
+ 0, 4, 1, // char 159
+ 1, 5, 0, // char 160
+ 0, 1, 1, // char 161
+ 0, 4, 1, // char 162
+ 0, 4, 1, // char 163
+ 0, 5, 1, // char 164
+ 0, 3, 1, // char 165
+ 0, 1, 1, // char 166
+ 0, 4, 1, // char 167
+ 0, 3, 1, // char 168
+ 0, 2, 1, // char 169
+ 0, 8, 1, // char 170
+ 0, 4, 1, // char 171
+ 0, 4, 1, // char 172
+ 1, 5, 0, // char 173
+ 0, 2, 1, // char 174
+ 0, 4, 1, // char 175
+ 0, 3, 1, // char 176
+ 0, 3, 1, // char 177
+ 0, 2, 1, // char 178
+ 0, 2, 1, // char 179
+ 0, 2, 1, // char 180
+ 0, 4, 1, // char 181
+ 0, 5, 1, // char 182
+ 1, 1, 1, // char 183
+ 0, 8, 1, // char 184
+ 0, 2, 1, // char 185
+ 0, 2, 1, // char 186
+ 0, 4, 1, // char 187
+ 0, 7, 1, // char 188
+ 0, 8, 1, // char 189
+ 0, 8, 1, // char 190
+ 0, 4, 1, // char 191
+ 0, 4, 1, // char 192
+ 0, 4, 1, // char 193
+ 0, 4, 1, // char 194
+ 0, 4, 1, // char 195
+ 0, 4, 1, // char 196
+ 0, 4, 1, // char 197
+ 0, 6, 1, // char 198
+ 0, 3, 1, // char 199
+ 0, 3, 1, // char 200
+ 0, 3, 1, // char 201
+ 0, 3, 1, // char 202
+ 0, 3, 1, // char 203
+ 0, 3, 1, // char 204
+ 0, 3, 1, // char 205
+ 0, 3, 1, // char 206
+ 0, 3, 1, // char 207
+ 0, 10, 1, // char 208
+ 0, 4, 1, // char 209
+ 0, 4, 1, // char 210
+ 0, 4, 1, // char 211
+ 0, 4, 1, // char 212
+ 0, 4, 1, // char 213
+ 0, 4, 1, // char 214
+ 0, 3, 1, // char 215
+ 0, 5, 1, // char 216
+ 0, 4, 1, // char 217
+ 0, 4, 1, // char 218
+ 0, 4, 1, // char 219
+ 0, 4, 1, // char 220
+ 0, 4, 1, // char 221
+ 0, 3, 1, // char 222
+ 0, 3, 1, // char 223
+ 0, 4, 1, // char 224
+ 0, 4, 1, // char 225
+ 0, 4, 1, // char 226
+ 0, 4, 1, // char 227
+ 0, 4, 1, // char 228
+ 0, 4, 1, // char 229
+ 0, 7, 1, // char 230
+ 0, 3, 1, // char 231
+ 0, 4, 1, // char 232
+ 0, 4, 1, // char 233
+ 0, 4, 1, // char 234
+ 0, 4, 1, // char 235
+ 0, 2, 1, // char 236
+ 0, 2, 1, // char 237
+ 0, 3, 1, // char 238
+ 0, 3, 1, // char 239
+ 0, 5, 1, // char 240
+ 0, 4, 1, // char 241
+ 0, 4, 1, // char 242
+ 0, 4, 1, // char 243
+ 0, 4, 1, // char 244
+ 0, 4, 1, // char 245
+ 0, 4, 1, // char 246
+ 0, 5, 1, // char 247
+ 0, 4, 1, // char 248
+ 0, 4, 1, // char 249
+ 0, 4, 1, // char 250
+ 0, 4, 1, // char 251
+ 0, 4, 1, // char 252
+ 0, 4, 1, // char 253
+ 0, 10, 1, // char 254
+ 0, 4, 1, // char 255
+ 226
+ )
diff --git a/interface/fonts/vcr_osd_mono.dm b/interface/fonts/vcr_osd_mono.dm
new file mode 100644
index 000000000000..301d90d2f7ea
--- /dev/null
+++ b/interface/fonts/vcr_osd_mono.dm
@@ -0,0 +1,3 @@
+/datum/font/vcr_osd_mono
+ name = "VCR OSD Mono"
+ font_family = 'interface/fonts/VCR_OSD_Mono.ttf'
diff --git a/strings/tips.txt b/strings/tips.txt
index 71c6df861459..453759ef1fa0 100644
--- a/strings/tips.txt
+++ b/strings/tips.txt
@@ -1,3 +1,5 @@
+@You can use the |, + and _ characters to emphasize parts of what you say in-game (e.g. say"my _ass_ |is| +heavy+." will be outputted as "my ass is heavy."). You can also escape these emphasizers by appending backslashes before them (e.g. say"1\+2\+3" will come out as "1+2+3" and not "1\2\3").
+♪ Hey, have you ever tried appending the % character before your messages when speaking in-game? ♫
Where the space map levels connect is randomized every round, but are otherwise kept consistent within rounds. Remember that they are not necessarily bidirectional!
You can catch thrown items by toggling on your throw mode with an empty hand active.
To crack the safe in the vault, use a stethoscope or three plastic explosives on it.
diff --git a/yogstation.dme b/yogstation.dme
index bcdae563b9c2..75729538f34a 100644
--- a/yogstation.dme
+++ b/yogstation.dme
@@ -66,6 +66,7 @@
#include "code\__DEFINES\fantasy_affixes.dm"
#include "code\__DEFINES\fastdmm2.dm"
#include "code\__DEFINES\flags.dm"
+#include "code\__DEFINES\fonts.dm"
#include "code\__DEFINES\food.dm"
#include "code\__DEFINES\footsteps.dm"
#include "code\__DEFINES\forensics.dm"
@@ -3801,6 +3802,12 @@
#include "interface\menu.dm"
#include "interface\stylesheet.dm"
#include "interface\skin.dmf"
+#include "interface\fonts\fonts_datum.dm"
+#include "interface\fonts\grand_9k.dm"
+#include "interface\fonts\pixellari.dm"
+#include "interface\fonts\spess_font.dm"
+#include "interface\fonts\tiny_unicode.dm"
+#include "interface\fonts\vcr_osd_mono.dm"
#include "yogstation\code\__HELPERS\_lists.dm"
#include "yogstation\code\__HELPERS\_logging.dm"
#include "yogstation\code\__HELPERS\game.dm"