From 483cc8cdd5e202b518dc2ffde25083e5d9aea165 Mon Sep 17 00:00:00 2001 From: JohnFulpWillard <53777086+JohnFulpWillard@users.noreply.github.com> Date: Sat, 2 Dec 2023 08:36:40 -0500 Subject: [PATCH 1/3] ports swag ass fonts --- code/__DEFINES/MC.dm | 8 + code/__DEFINES/_tick.dm | 5 + code/__DEFINES/chat.dm | 3 + code/__DEFINES/fonts.dm | 7 + code/__DEFINES/text.dm | 21 +- code/__DEFINES/traits.dm | 2 + code/_globalvars/traits.dm | 1 + code/controllers/subsystem/runechat.dm | 249 +---------------- code/datums/chatmessage.dm | 280 ++++++++++++-------- code/game/say.dm | 18 +- code/modules/balloon_alert/balloon_alert.dm | 2 +- code/modules/mob/say.dm | 2 +- interface/fonts/Grand9K_Pixel.ttf | Bin 0 -> 22108 bytes interface/fonts/Pixellari.ttf | Bin 0 -> 39908 bytes interface/fonts/SpessFont.ttf | Bin 0 -> 7692 bytes interface/fonts/TinyUnicode.ttf | Bin 0 -> 23280 bytes interface/fonts/VCR_OSD_Mono.ttf | Bin 0 -> 75864 bytes interface/fonts/fonts_datum.dm | 78 ++++++ interface/fonts/grand_9k.dm | 253 ++++++++++++++++++ interface/fonts/license.txt | 13 + interface/fonts/pixellari.dm | 252 ++++++++++++++++++ interface/fonts/spess_font.dm | 252 ++++++++++++++++++ interface/fonts/tiny_unicode.dm | 253 ++++++++++++++++++ interface/fonts/vcr_osd_mono.dm | 3 + strings/tips.txt | 2 + yogstation.dme | 7 + 26 files changed, 1361 insertions(+), 350 deletions(-) create mode 100644 code/__DEFINES/fonts.dm create mode 100644 interface/fonts/Grand9K_Pixel.ttf create mode 100644 interface/fonts/Pixellari.ttf create mode 100644 interface/fonts/SpessFont.ttf create mode 100644 interface/fonts/TinyUnicode.ttf create mode 100644 interface/fonts/VCR_OSD_Mono.ttf create mode 100644 interface/fonts/fonts_datum.dm create mode 100644 interface/fonts/grand_9k.dm create mode 100644 interface/fonts/license.txt create mode 100644 interface/fonts/pixellari.dm create mode 100644 interface/fonts/spess_font.dm create mode 100644 interface/fonts/tiny_unicode.dm create mode 100644 interface/fonts/vcr_osd_mono.dm 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/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..90de8a8d5101 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 1 +///Highest possible layer of chat elements +#define CHAT_LAYER_MAX 2 /// 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 = "[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 = "[owner.say_emphasis(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,7 @@ 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 +311,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 +365,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") + +/// 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 0000000000000000000000000000000000000000..cf6fdf44e2ec74a0ebe28b4bbb21f89b66bf8420 GIT binary patch literal 22108 zcmdU1dz4*eb>HXSx${ginY`b*6KxcbnMtCQM5V~P2tq?d6p9QpH!weXmH{ z)7UQ<8=l@m|7M;GW?d_R4<<%PUZ+Ix)PbcH;vp zz9e$lRp9@ny_3^3r(OM~TSRtLMM_(QvSWY4H9tN6+TNG^RC+pbLF8+nyyai*{GOd6 zC!`Y!;?u76Swfp`Tu-En-*7*@qu+6OLa#glS$h3a;heO+7+O@MvvOx;3ka;XbNL&x zv$P1(C6&ZJH;dBf_VyQDQmx9_QtjW+A$17b4U1GI={s91wBtw%ac{%kft}Z=ncw4e z?)>i8Dkys!UW1eL9)-)XR|mz$kk?q>JmYp#py!!u=fe*Es` z`0Tp!n#&U3Z&r@^@;<+Nd!8wCY!k2VyoeUoVO^G8W|x=sS(o3vp3^9v{XS{Mdl|rE zd*4~FS=-{ZR^MaVahdg-+1B`GGQ@3)Wzr~KYxdpk6zd%OEY^|joi5LQ&#&`Zyt}{L zN95)FoJV}kB0e?m8jJkog}8#OP(DN2H20h%XG67;9F#wo56Xw+BXU?iE)U6Pq@MI9 ztCLO1MadP(tCQiRmP{r$BnOka{d4t4>YuGYT>nh{ zuj`+xKUlx7es}$y^_lwX>tprd`fKa2s$W)rdHu{|cOUuGkq3`FaO4w5K7Qozk-LtZ z_RpU*9U;lH`Lvg4&P0wq8zM@f1Y5Vky6tdoCjzM(>3N>amj%d^3#CsM$zoX|OJ$iX zmld*7R>^8vBWq=ytcNZ8WrI9No-4m18|8WOd^tr9GkG68{(d0m1M(L@&wcWjK+s|NsC*3me_UQK*UJbf{ew)(d*yX9CjTgVWGAlN zD7WIc8@tG;Oz1g2+zA{`%dm{&;@|6yT{0!#laQ7-XVu@{q1s_yhq+GpAPly^ae!6 zLKM%|$dF$oGM;^0fWotiieFMp2r#+~_&W=z_;fOWNS;XEm3%OHGWl7ly|koMEp01J zmEKaiuk?87Sb1Ifg7Rqjp7K}AN6P=xHq>@O+rhT`+McSkSB5H=RCZNvuG~?1sPb6l zpDI6Y-`svq`|kET+CSa?-Hy(VfsU&>c6S`=c(CKi&W)WHb?)yx)cL{AhdRI4`ID~B zu7R#sbiJgK0-8XgL+x>9&ksj$;*K=mi^*wLvxvS^PJwKVZ zZQix>-ahaCc@NKfYJNAib@MNoKQ;gG{4dP^_JYj|-nQV51xI_Uy=V9C=-u0UTkl=H z_b-%%r!IWW!b1xmS$M4P)V`g4xAi^LcVtoLqB9p=x9FxtcP@Hl(RUY@7GJ#h;Nnj# z{?U@5C8J9YFL`R|qNQgoomzVL(#MwmWZA}L*DSkz*;kh@Tz=m2L(4zE{HYadR$RW~ z))k*$@#Ko5E7z^uwsLgkohu(*Ra$k{s+(6mxa#p$CsuDyE6iuD^Kw%=-J+KLw0jTfM9Lh3aF~ zC#yfK9_?SyzoGw({>%HX?Z2V_-u_27ls3FjJ7^>y^ebf^lD_^v?iG0k$&HtOj{QXa znFKu!Gajyb-A$oxf^l1dt&R$8$9febr!-e4{iq4LT8rJt3Ef$by_eEkpYgWZjmI{$ zt1B<#R|;Ioxvn(Ja$Lu;l-wk;d&kpuxPecq?{hM)iOSf>l~>w8jrK^C3%R=l95vf_ zN<$0qcnrsQqkP$BW9SaIV-LPBMs8n?eEuBd@Y9gDw;)%)ScDelmh|;_5&o~(e(m#fK{*4?6M{o$36e2CbcUHkieXS7W0h6AdOcS@j<_?7L_U1IZ0hdSz}d+QYuLycs<4yzZqNVMzrGt-Kj0c zf6y(a>n7saR7_i;jd7utW-oKE(c@4AP0(I6FSVSV>%EH_MOKQplOlLtAtQ+D1>HGf?vSd=(2tZ zmP?S|sBn;{g7MdUjJZPOfw%je(P8YKL5bpIPBIlqGU(P8V_>mT3v`?uB{XubgIkJZ z;xcHejZ<8KUL=>PLlfC%F+}AU&1R-LOi{vS5<}qznwZr`7$;539+eD(=zCo!ORmy7Vc)mNz99R6_x=fcqK0>i5AA^ayv$-8n zx21fB%^;IIQO_X2&E?~HHi_c9En+obBW9M8^q6&-ot$G4VS!{^haymge(S@$Z6)Sv zj9Bl*$smKG-hg=sJvRpXX*%sFjMYz;y)cdfE~06 zoejH@GTQRU_9rw6T0A$)GdUzva8D~tgCt^jCLt5@7}T~Dp=m;CMwoM~xkMgv)Gh9V zb!-@NUg+IuDwn{yTO(SWNe*ggu2+6&#i;QulQt(`Rv$|tS$(hVb&nIvnSD5#fQ6lQ zl3AG0@UawuP5olMlwa^s#4mPFhuZY#YFpFC$2Xbz>^3)o+2yL8EL?5%B>)%u^w=d6RQSAQ>i-tHdX3%Jl9{cK9(xkd&TD+L+Qv}dl+<0bPhqUK-2SA$L zW8a~d({_P|cf5|jvZCNPY(;~HaUZP6@DDl0e40`rHEujJ!#-1xh%R%tHAt4X8}nBV zCq}t316cGk7vv;`oDF@lBbDM|C+tX%wxZpfHYqx&vNFhJSc3W{53#{fgr_nEevGMj z>~FS-Y?6hGhD=kL$%g{RXG&+?jRkozENEj3e9VaRcKwAgZdNz}Z!^D{=PHIrUA!&C zw$CHe+!D(jmsJ**Vp>P3W2pWbO`#uUwaXIO;nQ9DgFH^aI9UV?t##EU&l;_+a^dG8& z*5M(WGphgH=d_e*osC6X(PusC$=rl20{u7|%ExZHLZT8G*33e#AUS$$WIphc@vfr= zKG1;lI8dkh_#ofVJb`DHDR7^3ZG1>GqPFTE#%nqg0xh$*+Fb2!s{1^c%}fyUgozpW zQd{NQkmnqEPK=ghKh9ADOZWs|z+`z}P#!uUp1+$TOkvHh8Q;7H!Euz;Sel_r7E+D+ zyV^Xb<0!g?V8~Vi%4!1&Z=lZWpY|0>#p#&U%mB2SS@KxgZ$f|iH0Y09Our^{LBL5r z2Iyu4VwgBHpW8@`zBMtCfnu8kR0mmfkZ%f|05|rgR09t-v`wq^KIS&gpA9oqJH<5R zR3AZ1OJA&Jd(@1IrTDOnfmo+hK}V;-OSCE#GMiagI89!4J}gs(V>qWV-(>t0=54{Y ziuAxW*p}Z&lpCkd#==2EJ8^KH4!(nZl^*gkMYhdw&McF)QML6D_^0?FS8hDV5RZYD zjk;lb@J%t&BE~Gk(Rf4)dr6Bhjj=*|M_7;oQk8GPf?2Q!LAgM)}1s2yd?Xt@8< zoM8cEeGN&p0Sx@0A`f{z$k93bnHaK2%z9O`jU7qUl9$TSl5R{}%V@`qwB1U1io_dt z`&map*8k&iLdLmRDt`BTk>)jc%u(kyi?f=yw~??}vTRP!{z3I3{tQRzbw9_%z8s>gOUao5gwBqcto8 znUCk-r1KMdjI7C6a|$i1X3(SMloBvO9kQO^%|hw%(RezEydTFMGPirtIA%wplpKtU zm-*Pc__QXUml$7&X;(ezy4BC>_~R z2kO#n_j_DA@`VcGfx2-gGKh_zV`t9hN=xRwe90`$9K&pCS>Ag@_YUO7Z@s7omd0!x zmzO*B2BmfBnw=>XcS;?fO=_3keUkB?yQCoss`~5$k!g~chm4oz=H~YA$8RrA!S5|L zSvzZ+w>`DVwzx*@1x7jTYme!?*QO8V_66<10gvT;N27f+s)kvOZ;XA2ya-2)$N@de zwRX1NJ0Hnet_bs)u;%FcQDZ)lVeHU~rk|n9kb?mlupiG&fPGDWXpozhuwEGF=vX6- z5%4hDW`TBKk=onBsQI;Sc_fUEGC$aakzO}@QAbkw87!mgJ;>d}HOz&ch8%6=<9bss zeAJMU-0nu2eNODhdQdXAUBg)=Dy=kxy0UxR7zDfUA_5_lr*i=d{@MIA}8 z-Ho&PfCi&i5%#t=mCoQvHzTR8^Ta{88`K)trj4wzFmX?UzQHI@o>_>a=Xl|}Ks=1U zBH8hLBu2SrT%iD+NRBpb%R2Pd>N!`ojBJRh z%4|Mr#*f@*<3rbH{_$Tn6m97C^RtH*A zTr0Fb-R46yC5dv(z@7K-0gjnbHBXwAB#B0r0m|BvqXdd67rDGC?i%!` zr!vY0aE?VY@3{U7R=s6Wd}yuRPFVkM@w9}UC)bPFd(r8(EJY+*iFkFi z6X1*+wISzG>D*8?36Vp;aO1W3%=~GvvavGdwSFl}OE!AQp3^Ye_)NuJ;ubO2Kooqm z|1i7Qc+owZa;GG^gBEc@svL=W%$V~n`uy2TF590xAMZ?xY^W@SpM?ojQTsONgMKBp zqif^Ws87t!Z0x9N&D3oPzWm1p_p8rm!wkr|_?g_jtmguEi^I&Lv#IS8s5gU+Q;g5z z%T|2p*V*|&O)#Jvn$9tF?3U9mASZV%;D@}-%ES>j_X5k*E!NMkHQs?tp&tz6>|g%x zoU(p5i<_nxi?KyqnWof>&%n5MM0&a&X^y22i*$<-;&5$ARH3Ox%F&HhfOFoV$8wl7 z-?Wz8y_ESOhry!!hW>~Hb6AGFqFuD_$?Tm)m(ndrPTTlW0_{SwW*?Sp+H4CLzD#Pc zg6Nj(v-wpLA(vd3Sxr(KfcmNm`%4n$kwHN)1{|j2J+xP^1(HtMCTLuIb>RE-J|6J% z>Hbl4E;fv!76I>VOgm-$6&-*3!q_7h+nk4<)3)SAcX_~3Xmc-v&pMA>;{HW=88BT; zvT`;QdQL`ayMj(@TWCcc8#Qxwex8kK23qYi8u<3l!C589+12CFxf`-ttL+k)M^m3F zcKDQ}wNIP#rr+k~nf~@mW7PSq#ecW7#!qu=Q-0Ug&$C&adfILphq)_4yX?Q+$|0Cs zowJ_P*3^>I3Pz1@F=vyER}9f9KdxRXzwq1BlwaaA42B1lG3=6%tzB5OTM}s%6S1)Q zcn4eS)|rjs2qXi0l*l2Xkw2;UYsSmsFV8A-%$$r`#9uQ`sb5Gm*38-2c{c5M&-|jf zJBHD8r{@Ia61*T(gA9M1?UxbBJl{*)F1lxsT@CxUHGo zQQg>3F<#Dip2YkmCXyN{%wN)eC1)Q>>4wj@hPnz?l^Zkl6z^2sH}7kVnd!nl((Fm% zW`;U?A%0?umhr5 zpjpq|B+O!+X6NIbT+GJRF^jci0^7xXO~z-=`AJ?YXJ_ZuOx>2?tMil4$HjctH;gV* zuch`a*tD^mn_B|)X0UOJ;W_6gd9B^x89;3y$M2ppb*zY3$<0r=l9VEZHTihbweYq^ z!E!`+UfP3RDHmt`0neZoY+}6G(d1K5d_|vUXdVdmZ_@XecuIs$HODeIXj(oz^6*eY z!k=@{bCCMrmqc0;3;9=bgvWE1DN-2Fh$^9*uC;X?F)usx zkb?tykkL}#DlzNG2@TI{ZUjojK9=`Pc0SzBTQ)rVPtTQ8J1g#3S)#=YB8Tv_eY|8FC8z~SWfr#lsJf3cAP1nT>r&2 zE02msJ|40(joH}wGzM$TM===IZPHm&Z2h=wGcjAa%rj!|m;FNB*SsP|+K-0yJpCn7 zu18cRz_>j|vwn|q(;jp^f4P?a%bUj_e2_{UJ-x`GGFxShNj@&Ze2A!_ zpt?1BAU__X6By80*NhD$hPl*7zINFVBQBrIXc^aFU_)ICNEge7 z=&e^QQ;RgsQ^5|wCeAY(agcAusW{q5X&2UcbEq&D7WCQU3^NLCX-`knC0@6a+K+v0 z_`ejF)MX50gC`Aa_74qKrra*M%W3(hIAJiiIp2^I(*K7cAM(uj#@~E`Di{@RzS{%&K<1^os8QUm*czs$DQ z$sI-C8FN8D66eE)wMM7RWvcjBebmS3SW>jU&laeBj9UTlLERpFDd7wEi8*z-(n*^2*upiFf|369p-wwu{y->q;eQ%$ z4drgU+33TeJP*Il`#~r#kb$HZVkK=zk#cA9(ojxhRq~xsF3E!AzeBk!r>582qH;wB zN(w{JX_t+q!BD?T&MLh!l)L3%>Aj&mPcAO^h4KQqrhN5>s+)&~w!Q$zO+$Lzq{q#A z+)}-8YItI_dhV{_$?59lQ_~VymQu1ybKf8g}#;lV8fLxXsER246^8p5^}FS@ewCVb1vo1*gO zsJsQWFT^XNhVe3~QIyY>T_{iDB~n$q>S_w5QM>|b1h0=8M|lF*2XN;Kl=nytrR}&r zhBhmuQjZI9`QR!z--VwuZL*P##8$E4AHp;8VqqM+cxm z6+a`b;#FOP_`U^4YROV2GwEhj^E59847lhj9rgbJ!q80;qOO4{e1lxD7yp8nhh8^$E10HD^*fg9S>N z_odUX&<<^Uw+>&5sw9(rexxl89{SW9^x-gDOM^8_yb z|9zj^buyfk@TRu6;eBp*$oKK)vs(~u_u}1dZ^gUW-i`OV9g@G1C-DZi_v8I-e}{Le z9oDzE-43UG1#bB=-oy5Feb?HfcysAaywmM5yj$&KF!7J&ck#xxZ_2mep#PNr!rR$i z2a~@ZuHOx#-vdXy0q&oI!S}(P`vJWlAargFZPP|R-<4IT2E#H%WOM385!THGoyj}1q`FFf^?g9Cvd`iBE_YMA) zdqWdqxWbmS=O~BjmEM(wlvactmoKAlI0hEVQgcYm&elRVf11w z8Jjp{Lx@8N0UC%Q;CMNNQlN$y$8B6fSf^=Lp@~bK;v9Oaz4tRLizlgUbAuY!|#8~KZrz!QNDfuv8j{SthwVRkp}LM5AMJJ)GApMzgwh# z4A<+2rtUj==8OQt#~w#{&Y`0Z9{ldt{%60)z!yb+^P$7jQwIia_|a!^?R)rLb{G|n zH`ez8{#umR9zJ&Jfj@cVx))GRMf&C(J+Xgk%X=3;5BOVv@8M%p51fqNBY%SH*WmtD z$ES`>f9>%{UM2FuZK!+xXFt_n3K%KwEB^#|k~8h~cs|34 zd%T>&w3bxYD^GYvWtM=(4)T<+TS_`Cri6=UTwPl# zg=+*JaB6|C(C&mPQlgKK0Ts~(@Xa4@oU!3{ZfA^uk+3g_UvL20i zE>i%rkZrB40#SPS)D_zj`(zEY^hraSprv2t$^fXEkN$U|ERw~tLJ zvO>-S^)2+k7l6*y@bts7Rz~DPStl3CdbwCeeQ1thRr_Q+njQufJJa6kQ?PDxmjK!Gy*U4-{65Yz z_=y}6^xD~RRDK2de@=S^Y722>E;D9r7-DBpQsCW>=Vb2ER1g4y7e5y5j+t z{<{1&dh7SfZ^`@Q{qh0%9r=*_9UKN96~4-${2jVj_2Fd;`hbzw6{M_A)=mGXswfd~x7~fuGJ>FmKzuah@a^bFp_bzk1cw3(RUV~vv?c+?pXZr;=fwb zSaQvhdzXA+$@7C(4jvtReDI4)H!Mw--n;ZeOVgz<4#h*43>_FcHT1rrCx-s^oCW6` zJ?B3z+q3M{vZt5*aQTkq?_BW?pIgTS6sg0ffb)v@!X1+&)aa`@$dGYr~ zFB&~M`ti}fyJYz#w_fsrOP;@U`K3oMeR4y*A=&WW4bN^|zVWV&AKCczu?1tv*vYZS z#{OdL2b)%Gx@ObNrpGotz3IoBw{1SU`LWF}j&B)1IR1h0C&$0BrLkq(mIGTpu;r;O zFHFptxOw886JNb7zU=19-hElRwZ3)p){|R5x%E5S)^0nz?c>{iuzlC|hqixe`}cRO z+Hr8lBRjsibNS8#J3qPe`@1gMb!yksyIz=FGP!5+{>e{HK6m-j%WuB?-IqUe`H!#I zaK))Bp1R`4$tB6bTEEYwz3kerfNQV2?3lzi4y( z8F;#;Z1{X68+Sd`tjj-c{(PhF8@ryW#mLI%Ypm{L^;6A8^s`-0MXVoRyn6Av@x{Yy zqD3!0`)vG~mu_DZA0*5KkW={RCcKY~4^KurqgJ$Tc(RGKX_EX%^Q_IWiNy5%k-_r= z@M01TPuB2UZM91y-g!oFic*{k2cA^`aN=9xu>{Y=qfraL$rxmePXZXuG2r26)A3d` z+=}!Z*Kh~!k0Mh5YYS;bT~(D5n9$R+Hx=+Pim?X}g9=DuVt4{njZXj-#lqicG&DXm z-in8ZK#6_43UE~&*sYW2S^_+;g6F*x{tbL97BU8yLqrUO6QeouiJcZFWx`7&gSQS3 zF`tn$v<}ju1V<)@vwUo-D56-#e*WxUk0??2w-L8i=#g@GqlW+%%($A1|-Q~WU90^Y}*+BredJk`2aF8K1A7& zsaS6@_C}nE3j<(y0>m~Fjn;8jU#V$YOpY%29Kdbn9>>Gd>pW2o!+JfY*`pQmtjO6? z&;WUE6GTQ*kO4Srn*#j|t98OQYp~5ZXh3z?Z5^<)>_fd7<@oTE~m(XB5%6lN>FJ-VQ=TL(Z{yA6<#f z1bmdU?i%p1U0eHFuafJtQd2`~P$M*hdZA&LffdVuStCs4VFXsT)pJpYduIRV9k&X~ zvoNS8QQ?dnsYN7N$z9F$6lb_cF`_A2UaV#ayHEDO>h zV}bvHxAHMh&APUNq1giNhJ5G?@eF<8P{|wmM>A|D%Q>_h4<@mR6L=pP0aI!gIJ1et z-1Q%fkxeXCYhg2vGcdhnEN@1A{zCRdBi<9&e5Sx&2-UC<_~?M%t)sCe6N!?JQVo8= z{P&t|i;so_%-aaRMTj;5rBRv(}2LkHZ4hSVG%+Svrs@yQz6aV@Su(vG&G=4C#} z-zz+Fhv9evV+s0%OEK!F+-mS;ppadfWrE$B#$K>8#;QA`SW39lQn|Y5O@?^YPbl7X z@O8V{TKgvF?rhkuK>GuA4>iW@>L6;wT+!K?YGkEacUR^t5T`r7wT}w>aP_ottFbdi zV?t~&RDrYvy{!maA$$Y5j;LHS?ieAAPc|UrMui|9nQicFMd>Q#X!&4_pnTLJL*E@i zV89aZMEXiN2w|f|!Ml|~*HcSY4`)Xw?F=(3#h59VH{%0zquPn~4!evVxDuyZ_CLc2 znQASDO>?2n+D=3VNaw~o+wk75(gscS zLqV;7KBi3Q6NVvIL?Lzv+iF-HDzWJ4)IU*^vjYxrRA#;04DjjKf$1 z_9{GW?|rC?yu6Jh&NfG>DL@{AM~St-=#pwvheoU%5pqC~hRnR8e9*2r7KDk=?J%Bz z#^&T*dRO3-o9!=X=Ng-nhkn~aCpntzAMP|~(iO5^3}AEg;yjPe|1}W%putX}%floF z#}(yq*zDl#9hL{`p0>76@n~$v7ON~^zHzb+JoqVYG@_q_sUlsW4LJWg32ie-fRo^~ z0-X{m(Vh%bYP3Dpf*{T^kQVDhegV!l@8|5u7m;Q+#I3#_?ZituqOdiFp)VetW4U8P zRyGmRL_7N}PvFaD7=6S=p}mq4?LC-FgK!5B7eInNPNU&@9DeQx?S=ZlSiqjC4Ek~S zIz9I!7i~MBiR~XFBK8VUxK7T+t%2D)X+AGeg1*skh8**9_=7np9PP}qZrHn3C%H0t z%9_ZTB*yA2*8V_C!#aaDPm>aay&re$+@#K5(oo>Kz`ovrYq$@G#Oxz<+;^UhN@}>( zk7gQ(Ty^kLATZ^FrNj6#amQJ|!&o1dgc)hF?cx!gH|f)^4!~(h<7MxVf%9NXX?C4< z=G+qD%93(*wWD(xPErAHo?Ng zbLv49)0(hMosnVE!`UtMABgO^&c&7lL*$>;pEe>Wv7?lW7>XrDLmO1u&vlU^B5hN= zFDO}iLZ2*d76E$Dz}ctGopWHkjf&VWfHF%qqf7l0+qddl@iaBqYV^!6Va4Pz^mTFI z>oSJ~Qw`}dVZr6II)&-lgZetG0zo%J&I|#QZCOL@%++NND==G1HD7tki6Q+VE;M1x z*=EiHPKm}amwk2C=!$@yPbyXxum;$?&!CQ5t~D^gb#ArK6do)Win?RGf~j+im?xB8 z&J5Q936p`GM8}$Fj<^h|q72}+ZHVog$I&(>>*~>T)mOb)4d=At;jk*?QTq?}ehgYM z;L|~0^K1+(?~62M*SL@)M@FWMP+<;WR6+te<}>+aZH%*z1==zif8Iux)2(fcJ&X%d zXh-U{hO!>$tUZm&_zdG9_8HzQs&GO{HvpLONlPLrmrIvf%w(1M3HD0b4A5Q1FgpaP zfXx%5%CplcVlIsVAsdYWZB%L36$uTFk4uWMVyL*6nIOl3m5Xux1=o+~0l++i%lvgX zEUy7gzL()T8%WbW2UOWu*RFP&4zSv)0UWk~s1e%2zA~)Gat+EE16pck1Z@o~v})_8 zz!w(I*^h#=%6>F0HHS`U3{4R47n)qZ%E3vnc$^P?Ag=1eR9ANTETU329=dXe)fI|eFBa~!dVy>V>>O5$ zeaZsY44q=t&fm#JPpfy_1HCWfOv<2Y%Im~Vk6ELJWT7KGQ#gu^)9@N~&r=GWz>@r4 z89dJBWt$kg3W8%=1W|yV7X-jyp9$z}VHdUaDQIYL5hT_}s&Iv{+x9rl;L+kiYY-d& z=-{3Q{z2~`jg(Y|pfPd04sf^PmlolmR0eId82~nL#5h6zI?tnog5lvI+<_w&ooLSFQGqkcCDMN*oA6Wqt4n&AoYMeyi(cNvbGoJ*K|H; z1oW_ds2OG*^pHkFLF>U4kXwS;xv1D^n~;LO$+ip;(QO=KYcN2&S0S+LA0A@ArB#~P zJR2720$P6>8e~5lN^l4W^5Z4NnwQTuS5SeYox0nLHx=IC#+bInq7_-Xtk}p+73o9Z z(TzY|yB_JzK{drDnPo+*^cbzm1a)XUuqUaJ7wX}4c#~rP&#Mc}ICS+gbk5oFQdr1p z&pK1KKjZyWocW*C+^^H8(*SmU5O!oYMQ6hcKjT)0rZP+0iHfz#+CxkkUW#1@@JO&) zzq_Sr&3VGlxi@|Ahaq9K7;XXt7LT-8>{zlk2~uar4;p|?+iVd(>SHNkNK54o#SDFj zeOGSFYTE{3ks z{=+^u)YcG1;m0sRcXnX)!@1Y-f_tyTnpM7>?HZ>63@#xmt1V=1jR%5!=T!&db8q=6LkwouDP7$WEyQk_TKx2Y@A*-tD z0@q40gG)6@0Kf$KFl*Hkz#Z1TtQozr^VgVPRC=vGBm~hdZ{_c0LHT1oEth{T6_}aD z0IJbMOaPQ5CP}qtv$CeZ4ch2>_vVg$V*#VEn(pq*_cUp^&e_t6j)q7KPhAgFyh{9a z>N+-PtL*XiaW2TOl+jh_K0q3Ii@lBg)7hqel-b2VRR~2uQ4ust>MTqzgQ4(QD=-iR z7pt~U+D2SFqT7ZLia2k=UPjl!xlsypUzKr)387cy3)jI$SG?4|2E0YQsEwrry8yRz z4aLG`N-xB_&cz>|-`vZTS?&;c6rclm8JVPy~D}tSTG3ul+zH2x&}QO+*A9R+9?71JXHXatG5Npl94tnKWza-W;Yg3#z%v-wFhB)XD( zj#8Jr5G%O9owX4*x$JM0^Ot?G5JiW>6za82<~7BOd3}~E-H>J%Gy^&1ZM25oiE#!@ z!D@)>_RXC&zo|@nixNCQNioTS+TKRC(BRqO1Tlz-%2?C0LrI)@yBJqKIXX}~tl+W_ zShs3l6HC%|Kn13)vO@(W!=otYrkC^TPO`3U2i@%7;>pio8M+1HIDyU9Fe1d-BXx^y z7vhD(aWz{dnxuxv2-F=?tQnAReIGAoZ&MXYnj)f#Qt1wm zVEbUq#io$+1@Lv)YiwIl4fy<$M7Ea<(`3b{E1Pem>ujHg$PmU9Yn`Veu0d!E^G=nx za49XAYYH4_vG{ez1T2`7$#FF?Z@ZDB0di*Twi=_2(KLEUiOL-*Qp3FV53?W#?nWDK zwp+yx`E}!${TK`+#b16fZ%9Bd=o7GAclZr-YI&cHbNH!J-$kUe;>&qOJLMikw$urF zh}9i*ng?#|!>qi)S$3o@XD9*V3WX%AjK$W|LxAa;#2oWY+DMlBEv)sjvy9nv*=&8! zi7oDSbL5Eb$%`|f7G&wHgI_xUFI{2L__~y`hxYI}v{Y%4aNO(L5}8IIoh|Qie%t#5 z56Pva4zqMM*K-Qy*Tcf$uV~?mb$-s{EAnkupemZN18!_e&9}EPJ6R7~f!@811-NZn zh7Zg9MkQ`-)(%}EFnI^IHv8LUoloP2aHXubDJ&Ng&D z3cW1mb6wbLhq}698htw=Zs?|Xm}0S_l-)sJ0rHD>%Jv!d0%#`i1KgJ6_CqpqftcDm zfUeFzq(l8!v26x@deJWgdnVi6f#alF=^2}`Bn3a?4%r&&1xJiHEr}EKS);@ZtDKK1gZe8O=cOG`X|*?3;)JazzgDI}%!1yrE6-NI zB{WXPQmU>fI^1qN%{`0l>x;UPi&I+R+Ug)0Irjv`Wh7|@mQGbRaCTi{)?WBt)jE#r zyNnccJCX_DrpSlo;8I>63pK25CQjgf?0Rwq$F~0;;t;#YwR717R5;SCl;^UlV#YDe z$}kSd$4K4yhEsPWSTGKB>QWsWxqOxwpNmCv)H$+l4Q@O3Sco5uaOl(i*@yxAnH8OF z9Og)=a_z_{@Pwnqo(}jDJuEHSpN!~ijz(Za77Q2kts^@wgT;r3L7_9sTuS8GI6HUi zfAm(mfxu0k>-a0i912$sFyT4Gu&a_+5|HxL;fPZx7i~K8&DzIU{~yL8kz0 zTjbloU)%LIo#XD~`|fC_9hSXdR9#km(wv53&V)rZI^b75>wD_`@K4-fGO6!`z&{`o z;X0$8)`+opPrpfmnIlH4j8kh0Bn`oOZxxHJec7e9R6HAB1Q}@GJQ>6#znnE$D+gEv zdS{KvxFQ>{4@X0Vps8V-b0~vB+}768oX<`@hi$3xH4!q(y4pX~ftXXRzr#)gjz2U$ zNIis#)Kt{@Dlkff$}fDHf!&RQ$h~*~%5JN8>EJ;^E>fG~()6Sx!G&6<7CIN<=rblmQU`e|t8r-?qCqb^W8s zop@Do89ELO_+ox=hC6*b1lub6F5bjmcjq{z8rZ(|J>wJ_X~O2N#<$;tu{++a+}X<; z|8{E!>=>#~3wZK^tv9UD4lsF1n6EzAB+FXlsDy?+^jBV>3jPm{5*+sCO#J>*L)*#xDo2ytoD*y?#Xv6?iY~U93i!UNo2T>lFopf9TwuTbSns?Rb61Rv z7#48{N{_Nh0m%f*F`@N4CIetk1@pP-ARy$_^u1$9t6dZdvR;rBl){UALcl4<*^-%$P!{P>53$!3O22 z@Ba-Fga59Iz6A%@r|dw7kS9&w6hKrJyg~CCqy#Y`Q64TR?w{5D#BT4b54eR-^>Dxd z3Ea}2Ko5g_b1$cz?X#~nORVL4HD}M+o@BfMlgl~RW~+;s(@r#7A=g1Gj7I4^*S^2# zX|>BNzJDbu=~NDCvg?d;j~s}(@$zC(o933Xwf zN~K&@yEN+pRMz0$zSG7`#gUflavq=y3L}N@=19qnhJlNGV;6p~s~unabWx&|;dL)V zJK&YyTgvtf=)01GQ1)v4+-ivD*&<09iH3)zxl_Lg%hGo1=D+#}21H zF_Haiq5tt8ID^IEPi+oslKj&-ReDludL|?y$Oq)5L3Nib{5RT?8<)+Fb8mFp>m`tgpj--WNZX>j^pY$lc#_v2UWJnq6PE_pvn+v1zl@E&~k zn;{8y>H?fHkGXggzzG-$YT=sgO|JhC!ldWa{hB1MpYtUlpu*EAI2QqXr>{)W?rZ8T zy#}1n^=r5Y64X&=Fgj6{-Qa9b{UmJdZ9 zRflb(R6XTPzXCb;Q_k+~)>gbYB)?3x7eyeQ0-6l20o-%B?+E>-rPMh_gZufbJ}f?o zG{zxqw0#hyU*dru(6>#ri)V0yKL*cdZ}=8|0{E7~B6tXv!1HgXRMW~@@T)eU^W-%< z8l!&f+v~by&X<9#?G9y*%Y6fxcY@=z4L-!%dyt(!txZVj$=VNWl6H>n2}G3lHl+nk zqzSE~YW6I^U_8(8cY*nkuJ;~7uqwba>mROi_Yh4;{GkX@LzvlAHS;d?XVzBLAXDGA^k|IY59?s2&Brs_Xm zGiuj<=UEcMsSIZc-XEE_hs(^>p+Y|Cj77^DmfhHy#X9^?<{BJu@Dn& z)i^$bHAeqFms@#3>`-{Ci%q)wI%DU?lMz5d)TDI2QTa`E|BKx(@1pDuJi~1}8rnD~ z+{(sdc!ftSJSQRVzwCGGK#cl}K793No$i;$Fsr3gczCZSTWIA%rIqM~(f*K75^nJ^TcgH2L4uxsTrSV>j7K8&=%(O?hlabpP{5&4cPC?cSp) z1XF}#`*eMOf!`F^M+(sfxW4Hd0$0If2&A}3^$UE7A!tRZHWuyI;{<-zegh)>9*29O zo&@m~rB*HfbU%0J#Zu%CrL?W4vckG*nz@yU+DQ#jlZ&5qZ+kYki{6#3vuGC9D5{iT z{Y{AZ1D!P-5b>({{J>PgM~JjGhtk|*FN2HO0#t159a0< zMj#C1*CCX%yTjcklX^--h9`e?Md@OPB-7(wSXVe7%w13z+gGTvqvs3NnR+TZrMXHI zON;8XrQ<(8pBQR%aUoPNMX;5=4jQ+&p`9ma?BtbiliPMqtzy1+GSV&$E1hnU84RmP z5zhN}?KF}|ix@kWy4@G!B2-J=I$u{qE5e&06sdk?2@}xj=-R)fOCq|#rt<0b+AZAL zcbv&!FIOeh*R{!j9g509$MNXibe93uz}4W{79@c^I82bQ%t9KFMLm!Ozu~gow!76m zNW{{UZ~|y{WJ*G%aDXst`;d%wIEdr`U7LQ>~+%L$E6a z&9;IxB`3@ z(zY5`VRZwC3IQ1;oVNq~am-6r5g!pn)|GaI8eoehE2s|Wzab~f1E*!-zA+s`UhMy$ zqnq+Dur7Y+Jp-d8~Y6c{b~gxipuX0 z@WNKF2Gd3?K165mryW|4OP&r0=tv;I6ujB@HA-(MF_bL6CE}?NXg>`ou>@#EyBiJe zv%N0*T+hFLN7D8;K zAUBFQ?SY6`^wv-gzuY#Q$BP^r^G)Ky$hb`w=#5f}J3AN{+M|2Zh3@VC83TWTb*DV; zfT43?0?QtL`OQW>#skjM+4`mI#w-)WI{cmoqAq^bLgTN{z_ExNWzT4)H&iah)NgRp z2Nrj}5fOVo_FP=i0!H1(jlJCJ7%}M+gucL-7HQCu;n~`3Z|+&2;xj4RTU~J0y(y$m z<8yWhtMQ9E{|^z2zjT8jh$;!jg16$ay6>)cPYzSC0g@a|3HmQa4x9Lf^6DPGvcgw} z^Swh=WPvIZ;%m=aLBbP3E^gi1?pX}J?>O6=ap6A2b`0QBD?6ID@z@}cuQKX3|uS`ywikTGwAPl(s&GbK?6_RvTet$rV<ST(RE`X+G*WAZmD~egsCONYRcl_SF)G*x zd2kSCCjuBLh^ju+TAg1|8}R)xBZICMUy5%*08U3 z`B)nG-m|@@CM)Dom#^cVzjpaPStS3<|U5U&lRv>hgVf_CL9NLzYByC?J_~S!IP<7;i>7z%dW{|xBR}P@=G{DFW(+7^6Mmigo z9aTWfn0EwNy3=qwXqW*DN92SY2g_sl+awz_9i#YepPsqz$cf{t#>O^n7#kZyO?QAX zB#z^ZVSLqoM0-$&p*kd6M@-s4A20`+oQrsD9_D8k$U^9HG4wDfOYvQVbMW1j<=AeIZ6E7olgp7$g5n(7$fLJRlj^4D4Gl<8vAM+ii%w zcVH%Z7p(nq%>O5d$o3#QxDs=iSHaG%!93n|*u8W;6mp~71pZzvx8U2qx8jTMx8wgB zxkK)RR$eEs$5+(f2wS{crlMH>NZuvCA%8B9$amynd7u2Y{DFL0J|XXsugkB?2cue4 zm*0u{T3zaqaX56Ls~hxq?e-i~qCJLG%vqI@B0MsuS6Xl^tR&6Ahpz=)!1S zbWyZEx){#llIYTCL$pzTD*q)Ki#A1@qw#1 zv*>tqB03qpMZPWnJ(`K`Yo0!SWNd8rm`(R=cImiFCtSL{x%=4E{+ScUn^QJz?7nAa z`u^$0lqSvHCk~xBKK%cwrOLZg*dIrz|}b+1Bdeb z+^aJHa}Q{h@GXJK-r;i_+nmK*!=+x;`^G}4O z#x0hH8B4=00or*pL7{PrrD4VtFHs`NI;)oI7oZI-Lkn)KZ^bL9A;zSBIMf&V5^!t?xTMbApX{99U5r%a8Gg_F&%y%3GV(7o3?QJQ%P@+7ii#_u2)c-ZD9r8-EN1p6y9?dcra5Vv zHfd<%Mbk9Bur+DCXqsO1qT0lGVcQ$EjTcS-VyLxAf4pdWQ8Y2K+vj=T?>pzrEZEzg z*)!kIdB6APd4Im|%t|04y%I`Vjt&eQJA8NMYyT8k|8LB$d+5M}1F{)CS^s-{tvWJz z+s+pTzflnhp2qmh@K|$l7hCB&b)}p zx5k>YlQJ$_@N756mGS0S>u=kKKPR#p`lkLcIWax+@s*G9Y~4GcNeOMkedWN7|2VyL z-xXPu!Gy>chhJU7_WifAKNgvnR0x%!ucX-E!kRSZ`OPa8Dktr`uOXcokxf$68NV{7 zNd>lo-#;v@t-t&pt&sY=jX{f{?-}WmwB8Qi za$T~oI*~8d>AdfAj9cf?Do>8X`1X8!OWKHzwC?tNxYk`>&2=h7Sb} z00v*${4z2`;N9=f&(Htmk8e79Oa(d95M9QuiQ~Xu;Fr#1bNNECXVK!`B}`Dk>NRWEt-oV{rBDf z05bZagAX5iA9Xa~=6NAT|JpRe=T`pJ{qY{~zsgWcpFLD6hz6`7lTY ztAahjW5LN_G?)o41wRVj2>zN{oZ6l`m>N%gJ@tC(gVcZ0x%AfbzVxZ|7t$}KUr)cE z{v@*^b4zA_=1gWb^Ucf;GVf&G%gklhWcOsB$j)ZJn|(9;o9u_VLT-C*B=?ovE4kNl z@8&+peUi`RSLJu*kLI7xe>ML~{`LIj{6~dCVNGFo;XvU`;bP(2g`X6DUASEMw79&u zwRnH=`QoMG4~uUWFBd=TDfDdV+0}ChAkvG-QUpg@@*<^rsaCF+Yay<*%t;vHDQJQ) z2oXyWO#r$uM2cf}9s$CDFId1-FN-?XD-3^tw89NS+6N`Q9gAeC^nu%2SY0hu zgL?Vq#9iBrxy_J3+fv3u8Np~y&0%1sd-fr!v+{RX#{7vz)%2~SCD$tOBZk`o8-uH9 z&Ncu`LeJK~WiK+sk!mWU4wyaaIa3K&>itvR~c!!%76lUVA&<3?4=M-iw;CNELhv&dsG`;UHN-4rQ70@;brQ=I zYsHR813CH#pGZC+f34j}MP@l!Q=IWZoXsO1XMpKLCyA*TvKGs_+AtjnrFVQknt%amHni&>} za8IT-s1dq1QdYDFQ=Y&4u0v4Z+z_&3yTBP-oHv2jRef%_K>_S9JjZYk26xJ$#6~@f z5&3HcP_E?89YePtGD*e(t8^C!t~M>>l^A#Dk5+geb=R+k>NdkFw#~(odX3c`dLmum z22g8-!?)HYzMH~UpX;!OMPr1Hxrm;_?Rv(t{AmA2ZIAo?sm58j4Q5X zT!9&R=-Aq=ZbOouGhPInWW{8;CiEhf)_ zXg;#MiOw!o*DOL2{pDJo67?c|EK^63T=(7;djTu$oW-RYx!Cpy1m7zGi>pI@dNyr9 zPo64iB@lo`=4&qw#a{)hrHZM>lI`Q}l&**>odeVAjq`y(2S{gxLNnGoub1xg;0f5T zOLLWWx)?xXdmUp~tt3jGqoM*n$XR=V*6kPO@G|}y`_vI!Ej~hr2df}Zv%Rx~Qv-G6 zw?-7JCvS3c7@%=`wBG|MpLet(%5l*8y41akL=nY6H0-h7If1egKI_^!{8^1&qfmGV z#JE+*din?oeBh|e=h1YzCwSS7W+2dtnF*G2YVOj{@b zx$a`eP7mC(@R*k!F+-3Ut;yjzh*_i=uYAE@Pjxt3fY`-Gq?4y+od;*?LEg}cMp{hR z*~$1@MCo}(rRcs>HHmILdHB7-GcsllH~(%C<6`Hd1^Z2;4^(o<5+;m@EmTbEl(a;v zYjU+ifQyrOW%&z-y>$X#Ry*a-(k5g+`0 z1k3S9uP~QB4HxoL4QlSam+v=NZk_<6_|=!Xz;6Bs9QlvhAgeofXI&h-0BYfK@#@ZL zv>Lmmoj1W(V<C--t=z&nrE(k@wTiJC(u!Pp>mB#OC)(@mdHoATaaeKGxr!oJG z>t|%STylM)|ElZffb8#FzbHBRm+SY)O~HWc_sYuPCD-qhT<{AZ8Dw~x$>FwsAS-0C z>!)Oi-0J#i%s=S*8QCOfT|X-~$oE`72i<<|`bBX0v+MWBWAc&f_sWi7-1Ylpaqv_8 zHa{%eM79p&YfvVoCDZs+WI!hHpQag1PPV3}D+3eb=s$tS=j4L4r72Sww$5E>H_`c@ z3$H~`>1G#8szjWRL*f(^ICVDs4I+mSfO%Tt>h{ z4j*fdkDxt@!7Ny}Vd+_CX+_3BID%Q5n?gH?Pe1-&Re=h`rUkS{F&Y90L-@2YGYxj< zXIt&cSzuHdYmT(go2m>B_SY-Zldab9Xl3Za&``St!V{2n0qaL0>7q)SL7x)IeF_?! z2S0DFoS3*!8Esx{Rc1z8mC4rB*!gLiEpialZG0xAib1R0ouaWM#|Jt@cKDR~;0okaefl4s>PIW1?T8Ki?u4*y!6 z9PKZjJ>PD(MusM4a|d_sy!}qII>3LJ?0=t&0m3qe3u=$UCm=L``t95OY?J>B{V%!Z BI|={* literal 0 HcmV?d00001 diff --git a/interface/fonts/TinyUnicode.ttf b/interface/fonts/TinyUnicode.ttf new file mode 100644 index 0000000000000000000000000000000000000000..74d0d3e386e61f47db5c6e7670870d28ca4c7a89 GIT binary patch literal 23280 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 literal 0 HcmV?d00001 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" From 46b142399ce0415d89e26affb8237ae73802ba8e Mon Sep 17 00:00:00 2001 From: JohnFulpWillard <53777086+JohnFulpWillard@users.noreply.github.com> Date: Sat, 2 Dec 2023 08:51:46 -0500 Subject: [PATCH 2/3] a --- code/__DEFINES/layers.dm | 3 --- code/datums/chatmessage.dm | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) 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/datums/chatmessage.dm b/code/datums/chatmessage.dm index 90de8a8d5101..6c95976d163c 100644 --- a/code/datums/chatmessage.dm +++ b/code/datums/chatmessage.dm @@ -18,9 +18,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 From 9cbb071861b96812142b9369465fc9c5d94bcb4c Mon Sep 17 00:00:00 2001 From: JohnFulpWillard <53777086+JohnFulpWillard@users.noreply.github.com> Date: Sat, 2 Dec 2023 09:12:41 -0500 Subject: [PATCH 3/3] Update chatmessage.dm --- code/datums/chatmessage.dm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/code/datums/chatmessage.dm b/code/datums/chatmessage.dm index 6c95976d163c..e985de6c63dc 100644 --- a/code/datums/chatmessage.dm +++ b/code/datums/chatmessage.dm @@ -303,6 +303,10 @@ 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