diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm
index 1da75a3d214b..c535291e7e15 100644
--- a/code/__DEFINES/misc.dm
+++ b/code/__DEFINES/misc.dm
@@ -438,3 +438,5 @@ GLOBAL_LIST_INIT(ghost_others_options, list(GHOST_OTHERS_SIMPLE, GHOST_OTHERS_DE
#define NO_INIT_PARAMETER "no-init"
#define EGG_LAYING_MESSAGES list("lays an egg.","squats down and croons.","begins making a huge racket.","begins clucking raucously.")
+
+#define LIBVG(function, arguments...) call("./libvg.[world.system_type == "UNIX" ? "so" : "dll"]", function)(arguments)
\ No newline at end of file
diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm
index 0b783c64e98b..6aad7197b2b3 100755
--- a/code/controllers/subsystem/ticker.dm
+++ b/code/controllers/subsystem/ticker.dm
@@ -213,7 +213,7 @@ SUBSYSTEM_DEF(ticker)
if(GLOB.secret_force_mode != "secret")
var/datum/game_mode/smode = config.pick_mode(GLOB.secret_force_mode)
if(!smode.can_start())
- message_admins("\blue Unable to force secret [GLOB.secret_force_mode]. [smode.required_players] players and [smode.required_enemies] eligible antagonists needed.")
+ message_admins("Unable to force secret [GLOB.secret_force_mode]. [smode.required_players] players and [smode.required_enemies] eligible antagonists needed.")
else
mode = smode
diff --git a/code/game/gamemodes/sandbox/h_sandbox.dm b/code/game/gamemodes/sandbox/h_sandbox.dm
index 13b4234622b8..622932d432ad 100644
--- a/code/game/gamemodes/sandbox/h_sandbox.dm
+++ b/code/game/gamemodes/sandbox/h_sandbox.dm
@@ -114,11 +114,11 @@ GLOBAL_VAR_INIT(hsboxspawn, TRUE)
if("hsbtobj")
if(!admin) return
if(GLOB.hsboxspawn)
- to_chat(world, "Sandbox: \black[usr.key] has disabled object spawning!")
+ to_chat(world, "Sandbox: [usr.key] has disabled object spawning!")
GLOB.hsboxspawn = FALSE
return
else
- to_chat(world, "Sandbox: \black[usr.key] has enabled object spawning!")
+ to_chat(world, "Sandbox: [usr.key] has enabled object spawning!")
GLOB.hsboxspawn = TRUE
return
//
@@ -128,9 +128,9 @@ GLOBAL_VAR_INIT(hsboxspawn, TRUE)
if(!admin) return
var/sbac = CONFIG_GET(flag/sandbox_autoclose)
if(sbac)
- to_chat(world, "Sandbox: \black [usr.key] has removed the object spawn limiter.")
+ to_chat(world, "Sandbox: [usr.key] has removed the object spawn limiter.")
else
- to_chat(world, "Sandbox: \black [usr.key] has added a limiter to object spawning. The window will now auto-close after use.")
+ to_chat(world, "Sandbox: [usr.key] has added a limiter to object spawning. The window will now auto-close after use.")
CONFIG_SET(flag/sandbox_autoclose, !sbac)
return
//
diff --git a/code/game/machinery/computer/communications.dm b/code/game/machinery/computer/communications.dm
index d0f06e19c829..4881588daa1e 100755
--- a/code/game/machinery/computer/communications.dm
+++ b/code/game/machinery/computer/communications.dm
@@ -53,7 +53,7 @@
if(..())
return
if(!is_station_level(z) && !is_centcom_level(z)) //Can only use on centcom and SS13
- to_chat(usr, "Unable to establish a connection: \black You're too far away from the station!")
+ to_chat(usr, "Unable to establish a connection: You're too far away from the station!")
return
usr.set_machine(src)
@@ -432,7 +432,7 @@
if(..())
return
if (z > 6)
- to_chat(user, "Unable to establish a connection: \black You're too far away from the station!")
+ to_chat(user, "Unable to establish a connection: You're too far away from the station!")
return
user.set_machine(src)
diff --git a/code/game/machinery/computer/robot.dm b/code/game/machinery/computer/robot.dm
index ef38fafd7b86..c15db0e63d90 100644
--- a/code/game/machinery/computer/robot.dm
+++ b/code/game/machinery/computer/robot.dm
@@ -29,7 +29,7 @@
/obj/machinery/computer/robotics/interact(mob/user)
if (src.z > 6)
- to_chat(user, "Unable to establish a connection: \black You're too far away from the station!")
+ to_chat(user, "Unable to establish a connection: You're too far away from the station!")
return
user.set_machine(src)
var/dat
diff --git a/code/game/machinery/computer/security.dm b/code/game/machinery/computer/security.dm
index 8b08824c4483..d8ff898a7e00 100644
--- a/code/game/machinery/computer/security.dm
+++ b/code/game/machinery/computer/security.dm
@@ -58,7 +58,7 @@
if(..())
return
if(src.z > 6)
- to_chat(user, "Unable to establish a connection: \black You're too far away from the station!")
+ to_chat(user, "Unable to establish a connection: You're too far away from the station!")
return
var/dat
diff --git a/code/game/machinery/hologram.dm b/code/game/machinery/hologram.dm
index bce12597ab17..415959b55521 100644
--- a/code/game/machinery/hologram.dm
+++ b/code/game/machinery/hologram.dm
@@ -390,7 +390,7 @@ Possible to do for anyone motivated enough:
if(is_operational() && (!AI || AI.eyeobj.loc == loc))//If the projector has power and client eye is on it
if (AI && istype(AI.current, /obj/machinery/holopad))
- to_chat(user, "ERROR: \black Image feed in progress.")
+ to_chat(user, "ERROR:Image feed in progress.")
return
var/obj/effect/overlay/holo_pad_hologram/Hologram = new(loc)//Spawn a blank effect at the location.
diff --git a/code/game/objects/items/devices/PDA/PDA.dm b/code/game/objects/items/devices/PDA/PDA.dm
index 044336e65d77..218404db932b 100644
--- a/code/game/objects/items/devices/PDA/PDA.dm
+++ b/code/game/objects/items/devices/PDA/PDA.dm
@@ -827,7 +827,7 @@ GLOBAL_LIST_EMPTY(PDAs)
user.show_message("Analyzing Results for [C]:")
if(C.radiation)
- user.show_message("\green Radiation Level: \black [C.radiation]")
+ user.show_message("Radiation Level: [C.radiation]")
else
user.show_message("No radiation detected.")
diff --git a/code/modules/admin/secrets.dm b/code/modules/admin/secrets.dm
index b8176a4ce784..3486c2dadfac 100644
--- a/code/modules/admin/secrets.dm
+++ b/code/modules/admin/secrets.dm
@@ -310,7 +310,7 @@
if(result)
SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Mass Species Change", "[result]"))
log_admin("[key_name(usr)] turned all humans into [result]", 1)
- message_admins("\blue [key_name_admin(usr)] turned all humans into [result]")
+ message_admins("[key_name_admin(usr)] turned all humans into [result]")
var/newtype = GLOB.species_list[result]
for(var/mob/living/carbon/human/H in GLOB.carbon_list)
H.set_species(newtype)
diff --git a/code/modules/admin/verbs/SDQL2/SDQL_2.dm b/code/modules/admin/verbs/SDQL2/SDQL_2.dm
index a19ab3c10061..8c5c2a6846d5 100644
--- a/code/modules/admin/verbs/SDQL2/SDQL_2.dm
+++ b/code/modules/admin/verbs/SDQL2/SDQL_2.dm
@@ -496,7 +496,7 @@
else if(char == "'")
if(word != "")
- to_chat(usr, "\red SDQL2: You have an error in your SDQL syntax, unexpected ' in query: \"[query_text]\" following \"[word]\". Please check your syntax, and try again.")
+ to_chat(usr, "SDQL2: You have an error in your SDQL syntax, unexpected ' in query: \"[query_text]\" following \"[word]\". Please check your syntax, and try again.")
return null
word = "'"
@@ -516,7 +516,7 @@
word += char
if(i > len)
- to_chat(usr, "\red SDQL2: You have an error in your SDQL syntax, unmatched ' in query: \"[query_text]\". Please check your syntax, and try again.")
+ to_chat(usr, "SDQL2: You have an error in your SDQL syntax, unmatched ' in query: \"[query_text]\". Please check your syntax, and try again.")
return null
query_list += "[word]'"
@@ -524,7 +524,7 @@
else if(char == "\"")
if(word != "")
- to_chat(usr, "\red SDQL2: You have an error in your SDQL syntax, unexpected \" in query: \"[query_text]\" following \"[word]\". Please check your syntax, and try again.")
+ to_chat(usr, "SDQL2: You have an error in your SDQL syntax, unexpected \" in query: \"[query_text]\" following \"[word]\". Please check your syntax, and try again.")
return null
word = "\""
@@ -544,7 +544,7 @@
word += char
if(i > len)
- to_chat(usr, "\red SDQL2: You have an error in your SDQL syntax, unmatched \" in query: \"[query_text]\". Please check your syntax, and try again.")
+ to_chat(usr, "SDQL2: You have an error in your SDQL syntax, unmatched \" in query: \"[query_text]\". Please check your syntax, and try again.")
return null
query_list += "[word]\""
diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm
index 5c87293b731d..47e3d1d71e73 100644
--- a/code/modules/admin/verbs/randomverbs.dm
+++ b/code/modules/admin/verbs/randomverbs.dm
@@ -698,7 +698,7 @@ Traitors and the like can also be revived with the previous role mostly intact.
change_view(CONFIG_GET(string/default_view))
log_admin("[key_name(usr)] changed their view range to [view].")
- //message_admins("\blue [key_name_admin(usr)] changed their view range to [view].") //why? removed by order of XSI
+ //message_admins("[key_name_admin(usr)] changed their view range to [view].") //why? removed by order of XSI
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Change View Range", "[view]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
diff --git a/code/modules/awaymissions/gateway.dm b/code/modules/awaymissions/gateway.dm
index 90b2e3a02ec8..a85f01c0b084 100644
--- a/code/modules/awaymissions/gateway.dm
+++ b/code/modules/awaymissions/gateway.dm
@@ -156,10 +156,10 @@ GLOBAL_DATUM(the_gateway, /obj/machinery/gateway/centerstation)
/obj/machinery/gateway/centeraway/attackby(obj/item/device/W, mob/user, params)
if(istype(W, /obj/item/device/multitool))
if(calibrated)
- to_chat(user, "\black The gate is already calibrated, there is no work for you to do here.")
+ to_chat(user, "The gate is already calibrated, there is no work for you to do here.")
return
else
- to_chat(user, "Recalibration successful!: \black This gate's systems have been fine tuned. Travel to this gate will now be on target.")
+ to_chat(user, "Recalibration successful!: This gate's systems have been fine tuned. Travel to this gate will now be on target.")
calibrated = TRUE
return
@@ -201,7 +201,7 @@ GLOBAL_DATUM(the_gateway, /obj/machinery/gateway/centerstation)
/obj/machinery/gateway/centeraway/proc/check_exile_implant(mob/living/L)
for(var/obj/item/implant/exile/E in L.implants)//Checking that there is an exile implant
- to_chat(L, "\black The station gate has detected your exile implant and is blocking your entry.")
+ to_chat(L, "The station gate has detected your exile implant and is blocking your entry.")
return TRUE
return FALSE
diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm
index 193d23e7bfdd..7be4ff1b950f 100644
--- a/code/modules/client/client_defines.dm
+++ b/code/modules/client/client_defines.dm
@@ -64,6 +64,8 @@
var/list/topiclimiter
var/datum/chatOutput/chatOutput
+ // This gets set by goonchat.
+ var/encoding = "1252"
var/list/credits //lazy list of all credit object bound to this client
diff --git a/code/modules/goonchat/browserOutput.dm b/code/modules/goonchat/browserOutput.dm
index 7ac3724662b1..4cff69762c47 100644
--- a/code/modules/goonchat/browserOutput.dm
+++ b/code/modules/goonchat/browserOutput.dm
@@ -83,6 +83,22 @@ GLOBAL_DATUM_INIT(iconCache, /savefile, new("data/iconCache.sav")) //Cache of ic
if("setMusicVolume")
data = setMusicVolume(arglist(params))
+ if("encoding")
+ var/encoding = href_list["encoding"]
+ var/static/regex/RE = regex("windows-(874|125\[0-8])")
+ if (RE.Find(encoding))
+ owner.encoding = RE.group[1]
+
+ else if (encoding == "gb2312")
+ owner.encoding = "2312"
+
+ // This seems to be the result on Japanese locales, but the client still seems to accept 1252.
+ else if (encoding == "_autodetect")
+ owner.encoding = "1252"
+
+ else
+ stack_trace("Unknown encoding received from client: \"[sanitize(encoding)]\". Please report this as a bug.")
+
if(data)
ehjax_send(data = data)
@@ -211,6 +227,8 @@ GLOBAL_DATUM_INIT(iconCache, /savefile, new("data/iconCache.sav")) //Cache of ic
message = replacetext(message, "\n", "
")
message = replacetext(message, "\t", "[GLOB.TAB][GLOB.TAB]")
+ message = to_utf8(message, target)
+
for(var/I in targets)
//Grab us a client if possible
var/client/C = grab_client(I)
diff --git a/code/modules/goonchat/browserassets/js/browserOutput.js b/code/modules/goonchat/browserassets/js/browserOutput.js
index 77aae1148aac..8d03b8acace8 100644
--- a/code/modules/goonchat/browserassets/js/browserOutput.js
+++ b/code/modules/goonchat/browserassets/js/browserOutput.js
@@ -572,6 +572,12 @@ if (typeof $ === 'undefined') {
}
$(function() {
+ // Detect encoding.
+ if (document.defaultCharset)
+ {
+ runByond("?_src_=chat&proc=encoding&encoding=" + escaper(document.defaultCharset));
+ }
+
$messages = $('#messages');
$subOptions = $('#subOptions');
$subAudio = $('#subAudio');
diff --git a/code/modules/libvg/utf8.dm b/code/modules/libvg/utf8.dm
new file mode 100644
index 000000000000..6d7be59eca30
--- /dev/null
+++ b/code/modules/libvg/utf8.dm
@@ -0,0 +1,69 @@
+// Note about encodings:
+// Encodings are passed by number as it's simplest to do it like this (citation needed)
+// This may cause some confusion with what codes correspond how.
+//
+// 874 and 1250-1258 are Windows CodePage encodings. The number corresponds to the CodePage.
+// 2312 is gb2312 (Chinese)
+/proc/_determine_encoding(var/mob_or_client)
+ . = "1252"
+ if (istype(mob_or_client, /client))
+ var/client/C = mob_or_client
+ . = C.encoding
+
+ else if (ismob(mob_or_client))
+ var/mob/M = mob_or_client
+ if (M.client)
+ . = M.client.encoding
+
+
+/proc/to_utf8(var/message, var/mob_or_client)
+ return LIBVG("to_utf8", _determine_encoding(mob_or_client), message)
+
+// Converts a byte string to a UTF-8 string, sanitizes it and caps the length.
+/proc/utf8_sanitize(var/message, var/mob_or_client, var/length)
+ return LIBVG("utf8_sanitize", _determine_encoding(mob_or_client), message, num2text(length))
+
+// Get the length (Unicode Scalars) of a UTF-8 string.
+/proc/utf8_len(var/message)
+ return text2num(LIBVG("utf8_len", message))
+
+/proc/utf8_byte_len(var/a)
+ return length(a)
+
+/proc/utf8_find(var/haystack, var/needle, var/start=1, var/end=0)
+ return text2num(LIBVG("utf8_find", haystack, needle, "[start]", "[end]"))
+
+/proc/utf8_copy(var/text, var/start=1, var/end=0)
+ return LIBVG("utf8_copy", text, "[start]", "[end]")
+
+/proc/utf8_replace(var/text, var/from, var/to_, var/start=1, var/end=0)
+ return LIBVG("utf8_replace", text, from, to_, "[start]", "[end]")
+
+/proc/utf8_index(var/text, var/index)
+ return LIBVG("utf8_index", text, "[index]")
+
+/proc/utf8_uppercase(var/text)
+ return LIBVG("utf8_uppercase", text)
+
+/proc/utf8_lowercase(var/text)
+ return LIBVG("utf8_lowercase", text)
+
+// Removes non-7-bit ASCII characters.
+// Useful for things which BYOND touches itself like object names.
+/proc/strict_ascii(var/text)
+ return LIBVG("strict_ascii", text)
+
+/proc/utf8_capitalize(var/text)
+ return utf8_uppercase(utf8_index(text, 1)) + utf8_copy(text, 2)
+
+/proc/utf8_reverse(var/text)
+ return LIBVG("utf8_reverse", text)
+
+/proc/utf8_leftpad(var/text, var/count, var/with=" ")
+ return LIBVG("utf8_leftpad", text, "[count]", with)
+
+/proc/utf8_is_whitespace(var/text)
+ return text2num(LIBVG("utf8_is_whitespace", text))
+
+/proc/utf8_trim(var/text)
+ return LIBVG("utf8_trim", text)
\ No newline at end of file
diff --git a/code/modules/ninja/suit/suit_initialisation.dm b/code/modules/ninja/suit/suit_initialisation.dm
index 4b159557bcce..8bfd12d08f9a 100644
--- a/code/modules/ninja/suit/suit_initialisation.dm
+++ b/code/modules/ninja/suit/suit_initialisation.dm
@@ -34,7 +34,7 @@
return
lockIcons(U)//Check for icons.
U.regenerate_icons()
- to_chat(U, "Linking neural-net interface...\nPattern\green GREEN, continuing operation.")
+ to_chat(U, "Linking neural-net interface...\nPatternGREEN, continuing operation.")
addtimer(CALLBACK(src, .proc/ninitialize_five, delay, U), delay)
/obj/item/clothing/suit/space/space_ninja/proc/ninitialize_five(delay, mob/living/carbon/human/U)
@@ -79,11 +79,11 @@
addtimer(CALLBACK(src, .proc/deinitialize_six, delay, U), delay)
/obj/item/clothing/suit/space/space_ninja/proc/deinitialize_six(delay, mob/living/carbon/human/U)
- to_chat(U, "Disconnecting neural-net interface...\greenSuccess.")
+ to_chat(U, "Disconnecting neural-net interface...Success.")
addtimer(CALLBACK(src, .proc/deinitialize_seven, delay, U), delay)
/obj/item/clothing/suit/space/space_ninja/proc/deinitialize_seven(delay, mob/living/carbon/human/U)
- to_chat(U, "Disengaging neural-net interface...\greenSuccess.")
+ to_chat(U, "Disengaging neural-net interface...Success.")
addtimer(CALLBACK(src, .proc/deinitialize_eight, delay, U), delay)
/obj/item/clothing/suit/space/space_ninja/proc/deinitialize_eight(delay, mob/living/carbon/human/U)
diff --git a/libvg.dll b/libvg.dll
new file mode 100644
index 000000000000..606f59bf6161
Binary files /dev/null and b/libvg.dll differ
diff --git a/yogstation.dme b/yogstation.dme
index 362a970d58f2..fe05b975ca85 100644
--- a/yogstation.dme
+++ b/yogstation.dme
@@ -1669,6 +1669,7 @@
#include "code\modules\library\lib_readme.dm"
#include "code\modules\library\random_books.dm"
#include "code\modules\library\soapstone.dm"
+#include "code\modules\libvg\utf8.dm"
#include "code\modules\lighting\lighting_area.dm"
#include "code\modules\lighting\lighting_atom.dm"
#include "code\modules\lighting\lighting_corner.dm"