diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm index b6115e93e9..97c904d4c8 100644 --- a/code/__DEFINES/misc.dm +++ b/code/__DEFINES/misc.dm @@ -499,3 +499,6 @@ GLOBAL_LIST_INIT(pda_reskins, list(PDA_SKIN_CLASSIC = 'icons/obj/pda.dmi', PDA_S #define VOMIT_TOXIC 1 #define VOMIT_PURPLE 2 + +//Misc text define. Does 4 spaces. Used as a makeshift tabulator. +#define FOURSPACES "    " diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index 3394fba90c..d4086bc4d9 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -83,7 +83,9 @@ #define INIT_ORDER_SHUTTLE -21 #define INIT_ORDER_MINOR_MAPPING -40 #define INIT_ORDER_PATH -50 -#define INIT_ORDER_PERSISTENCE -100 +#define INIT_ORDER_PERSISTENCE -95 +#define INIT_ORDER_CHAT -100 //Should be last to ensure chat remains smooth during init. + // Subsystem fire priority, from lowest to highest priority // If the subsystem isn't listed here it's either DEFAULT or PROCESS (if it's a processing subsystem child) @@ -114,6 +116,7 @@ #define FIRE_PRIORITY_MOBS 100 #define FIRE_PRIORITY_TGUI 110 #define FIRE_PRIORITY_TICKER 200 +#define FIRE_PRIORITY_CHAT 400 #define FIRE_PRIORITY_OVERLAYS 500 #define FIRE_PRIORITY_INPUT 1000 // This must always always be the max highest priority. Player input must never be lost. diff --git a/code/__HELPERS/roundend.dm b/code/__HELPERS/roundend.dm index 0e9413520b..8e59106d98 100644 --- a/code/__HELPERS/roundend.dm +++ b/code/__HELPERS/roundend.dm @@ -280,41 +280,41 @@ if(GLOB.round_id) var/statspage = CONFIG_GET(string/roundstatsurl) var/info = statspage ? "[GLOB.round_id]" : GLOB.round_id - parts += "[GLOB.TAB]Round ID: [info]" + parts += "[FOURSPACES]Round ID: [info]" var/list/voting_results = SSvote.stored_gamemode_votes if(length(voting_results)) - parts += "[GLOB.TAB]Voting: " + parts += "[FOURSPACES]Voting: " var/total_score = 0 for(var/choice in voting_results) var/score = voting_results[choice] total_score += score - parts += "[GLOB.TAB][GLOB.TAB][choice]: [score]" + parts += "[FOURSPACES][FOURSPACES][choice]: [score]" - parts += "[GLOB.TAB]Shift Duration: [DisplayTimeText(world.time - SSticker.round_start_time)]" - parts += "[GLOB.TAB]Station Integrity: [mode.station_was_nuked ? "Destroyed" : "[popcount["station_integrity"]]%"]" + parts += "[FOURSPACES]Shift Duration: [DisplayTimeText(world.time - SSticker.round_start_time)]" + parts += "[FOURSPACES]Station Integrity: [mode.station_was_nuked ? "Destroyed" : "[popcount["station_integrity"]]%"]" var/total_players = GLOB.joined_player_list.len if(total_players) - parts+= "[GLOB.TAB]Total Population: [total_players]" + parts+= "[FOURSPACES]Total Population: [total_players]" if(station_evacuated) - parts += "
[GLOB.TAB]Evacuation Rate: [popcount[POPCOUNT_ESCAPEES]] ([PERCENT(popcount[POPCOUNT_ESCAPEES]/total_players)]%)" - parts += "[GLOB.TAB](on emergency shuttle): [popcount[POPCOUNT_SHUTTLE_ESCAPEES]] ([PERCENT(popcount[POPCOUNT_SHUTTLE_ESCAPEES]/total_players)]%)" - parts += "[GLOB.TAB]Survival Rate: [popcount[POPCOUNT_SURVIVORS]] ([PERCENT(popcount[POPCOUNT_SURVIVORS]/total_players)]%)" + parts += "
[FOURSPACES]Evacuation Rate: [popcount[POPCOUNT_ESCAPEES]] ([PERCENT(popcount[POPCOUNT_ESCAPEES]/total_players)]%)" + parts += "[FOURSPACES](on emergency shuttle): [popcount[POPCOUNT_SHUTTLE_ESCAPEES]] ([PERCENT(popcount[POPCOUNT_SHUTTLE_ESCAPEES]/total_players)]%)" + parts += "[FOURSPACES]Survival Rate: [popcount[POPCOUNT_SURVIVORS]] ([PERCENT(popcount[POPCOUNT_SURVIVORS]/total_players)]%)" if(SSblackbox.first_death) var/list/ded = SSblackbox.first_death if(ded.len) - parts += "[GLOB.TAB]First Death: [ded["name"]], [ded["role"]], at [ded["area"]]. Damage taken: [ded["damage"]].[ded["last_words"] ? " Their last words were: \"[ded["last_words"]]\"" : ""]" + parts += "[FOURSPACES]First Death: [ded["name"]], [ded["role"]], at [ded["area"]]. Damage taken: [ded["damage"]].[ded["last_words"] ? " Their last words were: \"[ded["last_words"]]\"" : ""]" //ignore this comment, it fixes the broken sytax parsing caused by the " above else - parts += "[GLOB.TAB]Nobody died this shift!" + parts += "[FOURSPACES]Nobody died this shift!" if(istype(SSticker.mode, /datum/game_mode/dynamic)) var/datum/game_mode/dynamic/mode = SSticker.mode - parts += "[GLOB.TAB]Threat level: [mode.threat_level]" - parts += "[GLOB.TAB]Threat left: [mode.threat]" - parts += "[GLOB.TAB]Executed rules:" + parts += "[FOURSPACES]Threat level: [mode.threat_level]" + parts += "[FOURSPACES]Threat left: [mode.threat]" + parts += "[FOURSPACES]Executed rules:" for(var/datum/dynamic_ruleset/rule in mode.executed_rules) - parts += "[GLOB.TAB][GLOB.TAB][rule.ruletype] - [rule.name]: -[rule.cost] threat" + parts += "[FOURSPACES][FOURSPACES][rule.ruletype] - [rule.name]: -[rule.cost] threat" return parts.Join("
") /client/proc/roundend_report_file() diff --git a/code/_globalvars/misc.dm b/code/_globalvars/misc.dm index e7b2ae6cbe..037e5067d8 100644 --- a/code/_globalvars/misc.dm +++ b/code/_globalvars/misc.dm @@ -6,8 +6,6 @@ GLOBAL_VAR_INIT(timezoneOffset, 0) // The difference betwen midnight (of the hos // However it'd be ok to use for accessing attack logs and such too, which are even laggier. GLOBAL_VAR_INIT(fileaccess_timer, 0) -GLOBAL_VAR_INIT(TAB, "    ") - GLOBAL_DATUM_INIT(data_core, /datum/datacore, new) GLOBAL_VAR_INIT(CELLRATE, 0.002) // conversion ratio between a watt-tick and kilojoule diff --git a/code/controllers/subsystem/chat.dm b/code/controllers/subsystem/chat.dm new file mode 100644 index 0000000000..37e53e8990 --- /dev/null +++ b/code/controllers/subsystem/chat.dm @@ -0,0 +1,67 @@ +SUBSYSTEM_DEF(chat) + name = "Chat" + flags = SS_TICKER|SS_NO_INIT + wait = 1 + priority = FIRE_PRIORITY_CHAT + init_order = INIT_ORDER_CHAT + var/list/payload = list() + + +/datum/controller/subsystem/chat/fire() + for(var/i in payload) + var/client/C = i + C << output(payload[C], "browseroutput:output") + payload -= C + + if(MC_TICK_CHECK) + return + + +/datum/controller/subsystem/chat/proc/queue(target, message, handle_whitespace = TRUE) + if(!target || !message) + return + + if(!istext(message)) + stack_trace("to_chat called with invalid input type") + return + + if(target == world) + target = GLOB.clients + + //Some macros remain in the string even after parsing and fuck up the eventual output + message = replacetext(message, "\improper", "") + message = replacetext(message, "\proper", "") + if(handle_whitespace) + message = replacetext(message, "\n", "
") + message = replacetext(message, "\t", "[FOURSPACES][FOURSPACES]") + message += "
" + + + //url_encode it TWICE, this way any UTF-8 characters are able to be decoded by the Javascript. + //Do the double-encoding here to save nanoseconds + var/twiceEncoded = url_encode(url_encode(message)) + + if(islist(target)) + for(var/I in target) + var/client/C = CLIENT_FROM_VAR(I) //Grab us a client if possible + + if(!C?.chatOutput || C.chatOutput.broken) //A player who hasn't updated his skin file. + continue + + if(!C.chatOutput.loaded) //Client still loading, put their messages in a queue + C.chatOutput.messageQueue += message + continue + + payload[C] += twiceEncoded + + else + var/client/C = CLIENT_FROM_VAR(target) //Grab us a client if possible + + if(!C?.chatOutput || C.chatOutput.broken) //A player who hasn't updated his skin file. + return + + if(!C.chatOutput.loaded) //Client still loading, put their messages in a queue + C.chatOutput.messageQueue += message + return + + payload[C] += twiceEncoded diff --git a/code/modules/admin/verbs/adminhelp.dm b/code/modules/admin/verbs/adminhelp.dm index 91fdc78d20..4e58a9cba5 100644 --- a/code/modules/admin/verbs/adminhelp.dm +++ b/code/modules/admin/verbs/adminhelp.dm @@ -413,9 +413,9 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) dat += "CLOSED" else dat += "UNKNOWN" - dat += "[GLOB.TAB][TicketHref("Refresh", ref_src)][GLOB.TAB][TicketHref("Re-Title", ref_src, "retitle")]" + dat += "[FOURSPACES][TicketHref("Refresh", ref_src)][FOURSPACES][TicketHref("Re-Title", ref_src, "retitle")]" if(state != AHELP_ACTIVE) - dat += "[GLOB.TAB][TicketHref("Reopen", ref_src, "reopen")]" + dat += "[FOURSPACES][TicketHref("Reopen", ref_src, "reopen")]" dat += "

Opened at: [GAMETIMESTAMP("hh:mm:ss", closed_at)] (Approx [DisplayTimeText(world.time - opened_at)] ago)" if(closed_at) dat += "
Closed at: [GAMETIMESTAMP("hh:mm:ss", closed_at)] (Approx [DisplayTimeText(world.time - closed_at)] ago)" @@ -423,7 +423,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) if(initiator) dat += "Actions: [FullMonty(ref_src)]
" else - dat += "DISCONNECTED[GLOB.TAB][ClosureLinks(ref_src)]
" + dat += "DISCONNECTED[FOURSPACES][ClosureLinks(ref_src)]
" dat += "
Log:

" for(var/I in _interactions) dat += "[I]
" diff --git a/code/modules/antagonists/devil/devil.dm b/code/modules/antagonists/devil/devil.dm index 3f2bd003a3..dc649025d2 100644 --- a/code/modules/antagonists/devil/devil.dm +++ b/code/modules/antagonists/devil/devil.dm @@ -539,10 +539,10 @@ GLOBAL_LIST_INIT(devil_suffix, list(" the Red", " the Soulless", " the Master", var/list/parts = list() parts += "The devil's true name is: [truename]" parts += "The devil's bans were:" - parts += "[GLOB.TAB][GLOB.lawlorify[LORE][ban]]" - parts += "[GLOB.TAB][GLOB.lawlorify[LORE][bane]]" - parts += "[GLOB.TAB][GLOB.lawlorify[LORE][obligation]]" - parts += "[GLOB.TAB][GLOB.lawlorify[LORE][banish]]" + parts += "[FOURSPACES][GLOB.lawlorify[LORE][ban]]" + parts += "[FOURSPACES][GLOB.lawlorify[LORE][bane]]" + parts += "[FOURSPACES][GLOB.lawlorify[LORE][obligation]]" + parts += "[FOURSPACES][GLOB.lawlorify[LORE][banish]]" return parts.Join("
") /datum/antagonist/devil/roundend_report() diff --git a/code/modules/goonchat/browserOutput.dm b/code/modules/goonchat/browserOutput.dm index 082f20f524..72e0576f4c 100644 --- a/code/modules/goonchat/browserOutput.dm +++ b/code/modules/goonchat/browserOutput.dm @@ -181,8 +181,8 @@ GLOBAL_DATUM_INIT(iconCache, /savefile, new("tmp/iconCache.sav")) //Cache of ico log_world("\[[time2text(world.realtime, "YYYY-MM-DD hh:mm:ss")]\] Client: [(src.owner.key ? src.owner.key : src.owner)] triggered JS error: [error]") //Global chat procs -/proc/to_chat(target, message, handle_whitespace=TRUE) - if(!target) +/proc/to_chat_immediate(target, message, handle_whitespace=TRUE) + if(!target || !message) return //Ok so I did my best but I accept that some calls to this will be for shit like sound and images @@ -204,7 +204,7 @@ GLOBAL_DATUM_INIT(iconCache, /savefile, new("tmp/iconCache.sav")) //Cache of ico message = replacetext(message, "\proper", "") if(handle_whitespace) message = replacetext(message, "\n", "
") - message = replacetext(message, "\t", "[GLOB.TAB][GLOB.TAB]") + message = replacetext(message, "\t", "[FOURSPACES][FOURSPACES]") if(islist(target)) // Do the double-encoding outside the loop to save nanoseconds @@ -247,6 +247,11 @@ GLOBAL_DATUM_INIT(iconCache, /savefile, new("tmp/iconCache.sav")) //Cache of ico // url_encode it TWICE, this way any UTF-8 characters are able to be decoded by the Javascript. C << output(url_encode(url_encode(message)), "browseroutput:output") +/proc/to_chat(target, message, handle_whitespace = TRUE) + if(Master.current_runlevel == RUNLEVEL_INIT || !SSchat?.initialized) + to_chat_immediate(target, message, handle_whitespace) + return + SSchat.queue(target, message, handle_whitespace) /datum/chatOutput/proc/swaptolightmode() //Dark mode light mode stuff. Yell at KMC if this breaks! (See darkmode.dm for documentation) owner.force_white_theme() diff --git a/tgstation.dme b/tgstation.dme index cceeb1f915..8b5ea81313 100755 --- a/tgstation.dme +++ b/tgstation.dme @@ -231,6 +231,7 @@ #include "code\controllers\subsystem\atoms.dm" #include "code\controllers\subsystem\augury.dm" #include "code\controllers\subsystem\blackbox.dm" +#include "code\controllers\subsystem\chat.dm" #include "code\controllers\subsystem\communications.dm" #include "code\controllers\subsystem\dbcore.dm" #include "code\controllers\subsystem\dcs.dm"