diff --git a/code/__DEFINES/computer4_defines.dm b/code/__DEFINES/computer4_defines.dm
index c18764ef04fa..4c0d6a398dd3 100644
--- a/code/__DEFINES/computer4_defines.dm
+++ b/code/__DEFINES/computer4_defines.dm
@@ -31,6 +31,38 @@
// Well-Known Directories
#define THINKDOS_BIN_DIRECTORY "/bin"
// Constants
-#define THINKDOS_MAX_COMMANDS 3 //! The maximum amount of commands
+#define THINKDOS_MAX_COMMANDS 10 //! The maximum amount of commands
// Symbols
#define THINKDOS_SYMBOL_SEPARATOR ";" //! Lets you split stdin into distinct commands
+
+//ANSI color helpers.
+
+/// ANSI CSI seq `ESC [`
+#define ANSI_CSI "\x1b\x5B"
+
+/// Generate an ANSI SGR escape code dynamically.
+#define ANSI_SGR(ID) "[ANSI_CSI][ID]m"
+/// Internal only, Variant of `ANSI_SGR` that can const fold and stringifies literally.
+#define _ANSI_SGR_CONSTFOLD(ID) (ANSI_CSI + #ID + "m")
+
+#define ANSI_FULL_RESET _ANSI_SGR_CONSTFOLD(0)
+
+#define ANSI_BOLD _ANSI_SGR_CONSTFOLD(1)
+#define ANSI_UNBOLD _ANSI_SGR_CONSTFOLD(22)
+
+
+#define ANSI_FG_RED _ANSI_SGR_CONSTFOLD(31)
+#define ANSI_FG_GREEN _ANSI_SGR_CONSTFOLD(32)
+#define ANSI_FG_YELLOW _ANSI_SGR_CONSTFOLD(33)
+#define ANSI_FG_BLUE _ANSI_SGR_CONSTFOLD(34)
+#define ANSI_FG_MAGENTA _ANSI_SGR_CONSTFOLD(35)
+#define ANSI_FG_CYAN _ANSI_SGR_CONSTFOLD(36)
+#define ANSI_FG_WHITE _ANSI_SGR_CONSTFOLD(37)
+
+#define ANSI_FG_RESET _ANSI_SGR_CONSTFOLD(39)
+
+// ANSI wrapper helpers.
+
+// Please understand that these might have unexpected styling side effects, as ANSI isn't inherently closed like HTML.
+
+#define ANSI_WRAP_BOLD(bolded_text) (ANSI_BOLD+bolded_text+ANSI_UNBOLD)
diff --git a/code/modules/asset_cache/assets/webfonts.dm b/code/modules/asset_cache/assets/webfonts.dm
index 26b2557f473c..1e80f1e4aa71 100644
--- a/code/modules/asset_cache/assets/webfonts.dm
+++ b/code/modules/asset_cache/assets/webfonts.dm
@@ -36,3 +36,12 @@
parents = list(
"Yrsa.css" = file("fonts/Yrsa.css"),
)
+
+/datum/asset/simple/namespaced/ibm_vga9x16
+ assets = list(
+ "WebPlus_IBM_VGA_9x16.woff" = file("fonts/oldschool_pc_fonts/WebPlus_IBM_VGA_9x16.woff"),
+ )
+
+ parents = list(
+ "WebPlus_IBM_VGA_9x16.css" = file("fonts/oldschool_pc_fonts/WebPlus_IBM_VGA_9x16.css"),
+ )
diff --git a/code/modules/computer4/computer4.dm b/code/modules/computer4/computer4.dm
index 07e463627058..d22925021729 100644
--- a/code/modules/computer4/computer4.dm
+++ b/code/modules/computer4/computer4.dm
@@ -371,10 +371,10 @@ TYPEINFO_DEF(/obj/machinery/computer4)
/obj/machinery/computer4/proc/post_system()
text_buffer = ""
- text_buffer += "Initializing system...
"
+ text_buffer += "Initializing system...\n"
if(!internal_disk)
- text_buffer = "1701 - NO FIXED DISK
"
+ text_buffer = "1701 - NO FIXED DISK\n"
// Os already known.
if(operating_system)
@@ -387,25 +387,25 @@ TYPEINFO_DEF(/obj/machinery/computer4)
var/datum/c4_file/terminal_program/operating_system/new_os = locate() in inserted_disk?.root.contents
if(new_os)
- text_buffer += "Booting from inserted drive...
"
+ text_buffer += "Booting from inserted drive...\n"
set_operating_system(new_os)
else
- text_buffer += "Non-system disk or disk error.
"
+ text_buffer += "Non-system disk or disk error.\n"
// Okay how about the internal drive?
if(!operating_system && internal_disk)
var/datum/c4_file/terminal_program/operating_system/new_os = locate() in internal_disk?.root.contents
if(new_os)
- text_buffer += "Booting from fixed drive...
"
+ text_buffer += "Booting from fixed drive...\n"
set_operating_system(new_os)
else
- text_buffer += "Unable to boot from fixed drive.
"
+ text_buffer += "Unable to boot from fixed drive.\n"
// Fuck.
if(!operating_system)
- text_buffer += "ERR - BOOT FAILURE
"
+ text_buffer += "ERR - BOOT FAILURE\n"
SStgui.update_uis(src)
@@ -416,7 +416,7 @@ TYPEINFO_DEF(/obj/machinery/computer4)
if(operating_system)
set_operating_system(null)
- text_buffer = "Rebooting system...
"
+ text_buffer = "Rebooting system...\n"
tgui_input_history = list()
tgui_input_index = list()
diff --git a/code/modules/computer4/data/terminal/_terminal_program.dm b/code/modules/computer4/data/terminal/_terminal_program.dm
index e0ba95f0136f..a8d776e4c917 100644
--- a/code/modules/computer4/data/terminal/_terminal_program.dm
+++ b/code/modules/computer4/data/terminal/_terminal_program.dm
@@ -17,11 +17,11 @@
return TRUE
if(!system.current_user)
- system.println("Error: Unable to locate credentials.")
+ system.println("[ANSI_WRAP_BOLD("Error:")] Unable to locate credentials.")
return FALSE
if(length(req_access & system.current_user.access) != length(req_access))
- system.println("Error: User '[html_encode(system.current_user.registered_name)]' does not have the required access credentials.")
+ system.println("[ANSI_WRAP_BOLD("Error:")] User '[html_encode(system.current_user.registered_name)]' does not have the required access credentials.")
return FALSE
return TRUE
diff --git a/code/modules/computer4/data/terminal/directive/directman.dm b/code/modules/computer4/data/terminal/directive/directman.dm
index b6a822d8709e..ceb1caffe0f6 100644
--- a/code/modules/computer4/data/terminal/directive/directman.dm
+++ b/code/modules/computer4/data/terminal/directive/directman.dm
@@ -24,7 +24,7 @@
. = ..()
if(!system.get_computer().get_peripheral(PERIPHERAL_TYPE_WIRELESS_CARD))
- system.println("Error: Unable to locate wireless adapter.")
+ system.println("[ANSI_WRAP_BOLD("Error:")] Unable to locate wireless adapter.")
system.clear_screen(TRUE)
view_home()
@@ -81,7 +81,7 @@
system.clear_screen(TRUE)
if(!system.get_computer().get_peripheral(PERIPHERAL_TYPE_WIRELESS_CARD))
view_home()
- system.println("Error: Unable to locate wireless adapter.")
+ system.println("[ANSI_WRAP_BOLD("Error:")] Unable to locate wireless adapter.")
return
view_new()
return TRUE
@@ -90,7 +90,7 @@
if(lowertext(parsed_cmdline.raw) == "s")
if(!system.get_computer().get_peripheral(PERIPHERAL_TYPE_WIRELESS_CARD))
view_home()
- system.println("Error: Unable to locate wireless adapter.")
+ system.println("[ANSI_WRAP_BOLD("Error:")] Unable to locate wireless adapter.")
return
addtimer(CALLBACK(SSdirectives, TYPE_PROC_REF(/datum/controller/subsystem/directives, enact_directive), viewing_directive), rand(3, 10) SECONDS)
@@ -103,23 +103,23 @@
current_menu = DIRECTMAN_MENU_HOME
var/list/out = list(
- @"
┌┬┐┬┬─┐┌─┐┌─┐┌┬┐╔╦╗╔═╗╔╗╔
",
- @" │││├┬┘├┤ │ │ ║║║╠═╣║║║
",
- @" ─┴┘┴┴└─└─┘└─┘ ┴ ╩ ╩╩ ╩╝╚╝
",
- "Commands:
",
- "\[1\] View Current Directives
",
+ @" ┌┬┐┬┬─┐┌─┐┌─┐┌┬┐╔╦╗╔═╗╔╗╔",
+ @" │││├┬┘├┤ │ │ ║║║╠═╣║║║",
+ @" ─┴┘┴┴└─└─┘└─┘ ┴ ╩ ╩╩ ╩╝╚╝",
+ "Commands:",
+ "\[1\] View Current Directives",
)
if(SSdirectives.get_directives_for_selection())
- out += "\[2\] View New Directives
"
+ out += "\[2\] View New Directives"
- out += "
\[R\] Refresh"
+ out += "\[R\] Refresh"
- get_os().println(jointext(out, null))
+ get_os().println(jointext(out, "\n"))
/datum/c4_file/terminal_program/directman/proc/view_current()
if(!get_os().get_computer().get_peripheral(PERIPHERAL_TYPE_WIRELESS_CARD))
- get_os().println("Error: Unable to locate wireless adapter.")
+ get_os().println("[ANSI_WRAP_BOLD("Error:")] Unable to locate wireless adapter.")
return
current_menu = DIRECTMAN_MENU_CURRENT
@@ -130,13 +130,13 @@
for(var/datum/directive/directive as anything in SSdirectives.get_active_directives())
i++
out += "\[[fit_with_zeros("[i]", 2)]\] [directive.name]"
- out += "
\[B\] Return"
+ out += "\n\[B\] Return"
- get_os().println(jointext(out, "
"))
+ get_os().println(jointext(out, "\n"))
/datum/c4_file/terminal_program/directman/proc/view_directive(index)
if(!get_os().get_computer().get_peripheral(PERIPHERAL_TYPE_WIRELESS_CARD))
- get_os().println("Error: Unable to locate wireless adapter.")
+ get_os().println("[ANSI_WRAP_BOLD("Error:")] Unable to locate wireless adapter.")
return
current_menu = DIRECTMAN_MENU_ACTIVE_DIRECTIVE
@@ -148,18 +148,18 @@
"Description: [directive.desc]",
"Severity: [directive.severity]",
"Time Given: [active_directives[directive]]",
- "
\[B\] Return",
+ "\n\[B\] Return",
)
- get_os().println(jointext(out, "
"))
+ get_os().println(jointext(out, "\n"))
/datum/c4_file/terminal_program/directman/proc/view_new()
if(!get_os().get_computer().get_peripheral(PERIPHERAL_TYPE_WIRELESS_CARD))
- get_os().println("Error: Unable to locate wireless adapter.")
+ get_os().println("[ANSI_WRAP_BOLD("Error:")] Unable to locate wireless adapter.")
return
if(!SSdirectives.get_directives_for_selection())
- get_os().println("Error: No new directives to display.")
+ get_os().println("[ANSI_WRAP_BOLD("Error:")] No new directives to display.")
return
current_menu = DIRECTMAN_MENU_NEW_DIRECTIVES
@@ -170,14 +170,14 @@
for(var/datum/directive/directive as anything in SSdirectives.get_directives_for_selection())
i++
out += "\[[fit_with_zeros("[i]", 2)]\] [directive.name]"
- out += "
\[B\] Return"
+ out += "\n\[B\] Return"
- get_os().println(jointext(out, "
"))
+ get_os().println(jointext(out, "\n"))
/datum/c4_file/terminal_program/directman/proc/view_new_directive(index)
if(!get_os().get_computer().get_peripheral(PERIPHERAL_TYPE_WIRELESS_CARD))
view_home()
- get_os().println("Error: Unable to locate wireless adapter.")
+ get_os().println("[ANSI_WRAP_BOLD("Error:")] Unable to locate wireless adapter.")
return
get_os().clear_screen(TRUE)
@@ -190,7 +190,7 @@
"Description: [viewing_directive.desc]",
"Payout: [viewing_directive.reward] mark\s",
"Severity: [viewing_directive.severity]",
- "
\[B\] Return | \[S\] Select"
+ "\n\[B\] Return | \[S\] Select"
)
- get_os().println(jointext(out, "
"))
+ get_os().println(jointext(out, "\n"))
diff --git a/code/modules/computer4/data/terminal/directive/directman_commands.dm b/code/modules/computer4/data/terminal/directive/directman_commands.dm
index b8a2a212e9b0..012c952c2931 100644
--- a/code/modules/computer4/data/terminal/directive/directman_commands.dm
+++ b/code/modules/computer4/data/terminal/directive/directman_commands.dm
@@ -29,7 +29,7 @@
system.clear_screen(TRUE)
if(!system.get_computer().get_peripheral(PERIPHERAL_TYPE_WIRELESS_CARD))
directman.view_home()
- system.println("Error: Unable to locate wireless adapter.")
+ system.println("[ANSI_WRAP_BOLD("Error:")] Unable to locate wireless adapter.")
return
directman.view_current()
@@ -42,7 +42,7 @@
system.clear_screen(TRUE)
if(!system.get_computer().get_peripheral(PERIPHERAL_TYPE_WIRELESS_CARD))
directman.view_home()
- system.println("Error: Unable to locate wireless adapter.")
+ system.println("[ANSI_WRAP_BOLD("Error:")] Unable to locate wireless adapter.")
return
directman.view_new()
diff --git a/code/modules/computer4/data/terminal/medtrak/medtrak.dm b/code/modules/computer4/data/terminal/medtrak/medtrak.dm
index 14d57b2effc3..92e21adb9129 100644
--- a/code/modules/computer4/data/terminal/medtrak/medtrak.dm
+++ b/code/modules/computer4/data/terminal/medtrak/medtrak.dm
@@ -51,7 +51,7 @@
medical_records = SSdatacore.library[DATACORE_RECORDS_MEDICAL]
var/datum/c4_file/folder/log_dir = system.get_log_folder()
if(!log_dir)
- system.println("Error: Unable to locate logging directory.")
+ system.println("[ANSI_WRAP_BOLD("Error:")] Unable to locate logging directory.")
var/datum/c4_file/text/record_log = log_dir.get_file("medtrak_logs")
if(!istype(record_log))
@@ -59,7 +59,7 @@
log_file.set_name("medtrak_logs")
if(!log_dir.try_add_file(log_file))
qdel(log_file)
- system.println("Error: Unable to write log file.")
+ system.println("[ANSI_WRAP_BOLD("Error:")] Unable to write log file.")
write_log("[system.current_user.registered_name] accessed the records database.")
@@ -123,7 +123,7 @@
return TRUE
if(!(input_num in 1 to length(medical_records.records)))
- system.println("Error: Array index out of bounds.")
+ system.println("[ANSI_WRAP_BOLD("Error:")] Array index out of bounds.")
return TRUE
var/datum/data/record/R = medical_records.records[input_num]
@@ -157,12 +157,15 @@
/// Prints the home menu options
/datum/c4_file/terminal_program/medtrak/proc/home_text()
var/title_text = list(
- @(eol)" __ __ _ _____ _
"eol,
- @(eol)"| \/ | ___ __| | |_ _| _ _ __ _ | |__
"eol,
- @(eol)"| |\/| | / -_) / _` | | | | '_| / _` | | / /
"eol,
- @(eol)"|_|__|_| \___| \__,_| _|_|_ _|_|_ \__,_| |_\_\
"eol,
- @(eol)"_|"""""|_|"""""|_|"""""|_|"""""|_|"""""|_|"""""|_|"""""|
"eol,
- @(eol)" `-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'
"eol,
+ "[ANSI_FG_CYAN]",
+ @(eol) __ __ _ _____ _ eol,"\n",
+ @(eol)| \/ | ___ __| | |_ _| _ _ __ _ | |__ eol,"\n",
+ @(eol)| |\/| | / -_) / _` | | | | '_| / _` | | / / eol,"\n",
+ @(eol)|_|__|_| \___| \__,_| _|_|_ _|_|_ \__,_| |_\_\ eol,"\n",
+ "[ANSI_FG_WHITE]",
+ @(eol)_|"""""|_|"""""|_|"""""|_|"""""|_|"""""|_|"""""|_|"""""|eol,"\n",
+ @(eol) `-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'eol,"\n",
+ "[ANSI_FG_RESET]",
).Join("")
current_menu = MEDTRAK_MENU_HOME
@@ -173,7 +176,7 @@
"(2) Record Search",
"(0) Quit",
)
- get_os().println(jointext(out, "
"))
+ get_os().println(jointext(out, "\n"))
/datum/c4_file/terminal_program/medtrak/proc/await_input(text, datum/callback/on_input)
var/datum/c4_file/terminal_program/operating_system/thinkdos/system = get_os()
@@ -196,7 +199,7 @@
system.clear_screen(TRUE)
if(!length(medical_records.records))
- system.println("Error: No records found in database.
Enter 'back' to return.")
+ system.println("[ANSI_WRAP_BOLD("Error:")] No records found in database.\nEnter 'back' to return.")
return
var/list/out = list()
@@ -206,10 +209,10 @@
for(var/i in 1 to length(medical_records.records))
var/datum/data/record/R = medical_records.records[i]
- out +="\[[fit_with_zeros("[i]", zeros)]\][R.fields[DATACORE_ID]]: [R.fields[DATACORE_NAME]]"
+ out +="[ANSI_WRAP_BOLD("\[[fit_with_zeros("[i]", zeros)]\]")][R.fields[DATACORE_ID]]: [R.fields[DATACORE_NAME]]"
- out += "
(#) View record | (new) New record | (back) Return to home"
- system.println(jointext(out, "
"))
+ out += "\n(#) View record | (new) New record | (back) Return to home"
+ system.println(jointext(out, "\n"))
/datum/c4_file/terminal_program/medtrak/proc/view_record(datum/data/record/R)
if(isnull(R))
@@ -223,7 +226,7 @@
var/list/fields = current_record.fields
var/list/out = list(
- "Record Data:",
+ "[ANSI_WRAP_BOLD("Record Data:")]",
"\[01\] Name: [fields[DATACORE_NAME]] | ID: [fields[DATACORE_ID]]",
"\[02\] Sex: [fields[DATACORE_GENDER]]",
"\[03\] Age: [fields[DATACORE_AGE]]",
@@ -236,11 +239,11 @@
"\[10\] Physical Status: [fields[DATACORE_PHYSICAL_HEALTH]]",
"\[11\] Mental Status: [fields[DATACORE_MENTAL_HEALTH]]",
"\[12\] Notes: [fields[DATACORE_NOTES]]",
- "
Enter field number to edit a field",
+ "\nEnter field number to edit a field",
"(C) Comments | (R) Refresh | (D) Delete | (P) Print | (0) Return to index"
)
- get_os().println(jointext(out, "
"))
+ get_os().println(jointext(out, "\n"))
/datum/c4_file/terminal_program/medtrak/proc/record_input_num(number)
switch(number)
@@ -264,19 +267,19 @@
await_input("Enter new Allergies (Max Length: [MAX_MESSAGE_LEN])", CALLBACK(src, PROC_REF(edit_allergies)))
if(10)
await_input(
- {"Edit Physical Status
- \[1\] [PHYSHEALTH_OK]
- \[2\] [PHYSHEALTH_CARE]
+ {"Edit Physical Status\n
+ \[1\] [PHYSHEALTH_OK]\n
+ \[2\] [PHYSHEALTH_CARE]\n
\[3\] [PHYSHEALTH_DECEASED]
"},
CALLBACK(src, PROC_REF(edit_physical_health))
)
if(11)
await_input(
- {"Edit Mental Status
- \[1\] [MENHEALTH_OK]
- \[2\] [MENHEALTH_WATCH]
- \[3\] [MENHEALTH_UNSTABLE]
+ {"Edit Mental Status\n
+ \[1\] [MENHEALTH_OK]\n
+ \[2\] [MENHEALTH_WATCH]\n
+ \[3\] [MENHEALTH_UNSTABLE]\n
\[4\] [MENHEALTH_INSANE]
"},
CALLBACK(src, PROC_REF(edit_mental_health))
@@ -301,6 +304,6 @@
count++
out += "\[[fit_with_zeros("[count]", 2)]\] [comment]"
- system.println(jointext(out, "
"))
+ system.println(jointext(out, "\n"))
system.println("(N) New comment | (0) Return to record")
diff --git a/code/modules/computer4/data/terminal/medtrak/medtrak_edit_record.dm b/code/modules/computer4/data/terminal/medtrak/medtrak_edit_record.dm
index 0bdc6fb99c3d..d20da4383e6f 100644
--- a/code/modules/computer4/data/terminal/medtrak/medtrak_edit_record.dm
+++ b/code/modules/computer4/data/terminal/medtrak/medtrak_edit_record.dm
@@ -98,9 +98,9 @@
if(!(choice in 1 to length(options)))
await_input(
- {"Edit Physical Status
- \[1\] [PHYSHEALTH_OK]
- \[2\] [PHYSHEALTH_CARE]
+ {"Edit Physical Status\n
+ \[1\] [PHYSHEALTH_OK]\n
+ \[2\] [PHYSHEALTH_CARE]\n
\[3\] [PHYSHEALTH_DECEASED]
"},
CALLBACK(src, PROC_REF(edit_physical_health))
@@ -121,10 +121,10 @@
if(!(choice in 1 to length(options)))
await_input(
- {"Edit Mental Status
- \[1\] [MENHEALTH_OK]
- \[2\] [MENHEALTH_WATCH]
- \[3\] [MENHEALTH_UNSTABLE]
+ {"Edit Mental Status\n
+ \[1\] [MENHEALTH_OK]\n
+ \[2\] [MENHEALTH_WATCH]\n
+ \[3\] [MENHEALTH_UNSTABLE]\n
\[4\] [MENHEALTH_INSANE]
"},
CALLBACK(src, PROC_REF(edit_mental_health))
diff --git a/code/modules/computer4/data/terminal/medtrak/medtrak_menu_commands.dm b/code/modules/computer4/data/terminal/medtrak/medtrak_menu_commands.dm
index 46f9c29ce9ba..d9ea0ef160f0 100644
--- a/code/modules/computer4/data/terminal/medtrak/medtrak_menu_commands.dm
+++ b/code/modules/computer4/data/terminal/medtrak/medtrak_menu_commands.dm
@@ -47,9 +47,9 @@
var/i
for(var/datum/data/record/found_record as anything in results)
i++
- out += "\[[fit_with_zeros("[i]", 3)]\] [found_record.fields[DATACORE_ID]]: [found_record.fields[DATACORE_NAME]]"
+ out += "[ANSI_WRAP_BOLD("\[[fit_with_zeros("[i]", 3)]\]")] [found_record.fields[DATACORE_ID]]: [found_record.fields[DATACORE_NAME]]"
- medtrak.await_input(jointext(out, "
"), CALLBACK(src, PROC_REF(fulfill_search), results))
+ medtrak.await_input(jointext(out, "\n"), CALLBACK(src, PROC_REF(fulfill_search), results))
return
/datum/shell_command/medtrak/home/search/proc/fulfill_search(list/results, datum/c4_file/terminal_program/medtrak/medtrak, datum/parsed_cmdline/stdin)
diff --git a/code/modules/computer4/data/terminal/medtrak/medtrak_record_commands.dm b/code/modules/computer4/data/terminal/medtrak/medtrak_record_commands.dm
index 9c64a13fd9e9..ec60b8104100 100644
--- a/code/modules/computer4/data/terminal/medtrak/medtrak_record_commands.dm
+++ b/code/modules/computer4/data/terminal/medtrak/medtrak_record_commands.dm
@@ -54,11 +54,11 @@
var/datum/c4_file/terminal_program/medtrak/medtrak = program
var/obj/item/peripheral/printer/printer = system.get_computer().get_peripheral(PERIPHERAL_TYPE_PRINTER)
if(!printer)
- system.println("Error: Unable to locate printer.")
+ system.println("[ANSI_WRAP_BOLD("Error:")] Unable to locate printer.")
return
if(printer.busy)
- system.println("Error: Printer is busy.")
+ system.println("[ANSI_WRAP_BOLD("Error:")] Printer is busy.")
return
var/list/fields = medtrak.current_record.fields
diff --git a/code/modules/computer4/data/terminal/netpage/netpage.dm b/code/modules/computer4/data/terminal/netpage/netpage.dm
index 1e662ac54345..ace88ba60797 100644
--- a/code/modules/computer4/data/terminal/netpage/netpage.dm
+++ b/code/modules/computer4/data/terminal/netpage/netpage.dm
@@ -24,10 +24,10 @@
system.clear_screen(TRUE)
var/title_text = list(
- @" ___ ___ __ __ ___
",
- @"|\ | |__ | |__) /\ / _` |__
",
- @"| \| |___ | | /~~\ \__> |___
",
- ).Join("")
+ @" ___ ___ __ __ ___",
+ @"|\ | |__ | |__) /\ / _` |__ ",
+ @"| \| |___ | | /~~\ \__> |___",
+ ).Join("\n")
system.println(title_text)
check_for_errors()
@@ -48,18 +48,18 @@
var/datum/c4_file/terminal_program/operating_system/thinkdos/system = get_os()
if(!get_adapter())
- system.println("Error: Unable to locate network adapter.")
+ system.println("[ANSI_WRAP_BOLD("Error:")] Unable to locate network adapter.")
. = TRUE
if(system.needs_login)
return .
if(!get_reader())
- system.println("Error: Unable to locate card reader.")
+ system.println("[ANSI_WRAP_BOLD("Error:")] Unable to locate card reader.")
return TRUE
if(!get_reader().inserted_card)
- system.println("Error: No card inserted.")
+ system.println("[ANSI_WRAP_BOLD("Error:")] No card inserted.")
return TRUE
return FALSE
diff --git a/code/modules/computer4/data/terminal/netpage/netpage_commands.dm b/code/modules/computer4/data/terminal/netpage/netpage_commands.dm
index ee84fc9373cc..1bf27221595d 100644
--- a/code/modules/computer4/data/terminal/netpage/netpage_commands.dm
+++ b/code/modules/computer4/data/terminal/netpage/netpage_commands.dm
@@ -15,7 +15,7 @@
return
if(!options["network"])
- system.println("Syntax: post --network=\[network ID\] \[message\].
Type 'networks' to view networks you may broadcast on.")
+ system.println("Syntax: post --network=\[network ID\] \[message\].\nType 'networks' to view networks you may broadcast on.")
return
var/list/valid_arg_options = list()
@@ -23,7 +23,7 @@
valid_arg_options[info.arg_name] = info.pager_class
if(!(options["network"] in valid_arg_options))
- system.println("Error: Invalid network ID.")
+ system.println("[ANSI_WRAP_BOLD("Error:")] Invalid network ID.")
return
var/message = "[stationtime2text("hh:mm")] | [jointext(arguments, " ")]"
diff --git a/code/modules/computer4/data/terminal/notepad/notepad.dm b/code/modules/computer4/data/terminal/notepad/notepad.dm
index 6fa24200ab38..bf664c13540c 100644
--- a/code/modules/computer4/data/terminal/notepad/notepad.dm
+++ b/code/modules/computer4/data/terminal/notepad/notepad.dm
@@ -24,29 +24,26 @@
note_list = list()
var/title_text = list(
- @"__________________________________________________
",
- @" _____ _____
",
- @" / ) / ) /
",
- @"---/----/----__----__---/----/----__----__---/-__-
",
- @" / / / ) / ' / / / ) / ' /(
",
- @"_/____/___(___/_(___ _/____/___(___/_(___ _/___\__
",
- ).Join("")
+ @"__________________________________________________",
+ @" _____ _____ ",
+ @" / ) / ) / ",
+ @"---/----/----__----__---/----/----__----__---/-__-",
+ @" / / / ) / ' / / / ) / ' /( ",
+ @"_/____/___(___/_(___ _/____/___(___/_(___ _/___\__",
+ ).Join("\n")
system.clear_screen(TRUE)
system.println(title_text)
system.println("Welcome to DocDock, type !help to get started.")
-/datum/c4_file/terminal_program/notepad/parse_cmdline(text)
- return splittext(text, " ")
-
/datum/c4_file/terminal_program/notepad/std_in(text)
. = ..()
- var/list/arguments = parse_cmdline(text)
+ var/list/arguments = splittext(text, " ")
var/command = arguments[1]
arguments.Cut(1,2)
var/datum/c4_file/terminal_program/operating_system/os = get_os()
- if(command[1] == "!")
+ if(text[1] == "!")
command = copytext(command, 2)
for(var/datum/shell_command/potential_command as anything in edit_commands)
if(potential_command.try_exec(command, os, src, arguments, null))
diff --git a/code/modules/computer4/data/terminal/notepad/notepad_commands.dm b/code/modules/computer4/data/terminal/notepad/notepad_commands.dm
index e0520a1f45c4..c8766411878e 100644
--- a/code/modules/computer4/data/terminal/notepad/notepad_commands.dm
+++ b/code/modules/computer4/data/terminal/notepad/notepad_commands.dm
@@ -40,13 +40,13 @@
sortTim(output, GLOBAL_PROC_REF(cmp_text_asc))
output.Insert(1,
"Typing text without a '!' prefix will write to the current line.",
- "You can change lines by typing '!\[number\]'. Zero will change to highest line number.
",
+ "You can change lines by typing '!\[number\]'. Zero will change to highest line number.\n",
"Use help \[command\] to see specific information about a command.",
"List of available commands:"
)
- system.println(jointext(output, "
"))
+ system.println(jointext(output, "\n"))
/datum/shell_command/notepad/edit_cmd/quit
aliases = list("quit", "q", "exit")
@@ -75,7 +75,7 @@
/datum/shell_command/notepad/edit_cmd/delete/exec(datum/c4_file/terminal_program/operating_system/thinkdos/system, datum/c4_file/terminal_program/program, list/arguments, list/options)
var/datum/c4_file/terminal_program/notepad/notepad = program
if(!length(notepad.note_list))
- system.println("Error: Nothing to delete.")
+ system.println("[ANSI_WRAP_BOLD("Error:")] Nothing to delete.")
return
if(notepad.working_line == 0)
@@ -102,7 +102,7 @@
print += "\[[fit_with_zeros("[i]", 3)]\] [note] [assoc_note ? "=[assoc_note]" : ""]"
system.clear_screen(TRUE)
- system.println(jointext(print, "
"))
+ system.println(jointext(print, "\n"))
/datum/shell_command/notepad/edit_cmd/load_note
aliases = list("load", "l")
@@ -123,7 +123,7 @@
else if(istype(found_file, /datum/c4_file/text))
var/datum/c4_file/text/text = found_file
- notepad.note_list = splittext(text.data, "
")
+ notepad.note_list = splittext(text.data, "\n")
else
system.println("Error: File not found.")
return
@@ -145,12 +145,12 @@
var/datum/c4_file/record/existing_file = system.resolve_filepath(path_info.raw)
if(existing_file && !istype(existing_file, /datum/c4_file/record))
- system.println("Error: Name in use.")
+ system.println("[ANSI_WRAP_BOLD("Error:")] Name in use.")
return
if(existing_file)
if(existing_file.drive.read_only)
- system.println("Error: Cannot open file for write.")
+ system.println("[ANSI_WRAP_BOLD("Error")]: Cannot open file for write.")
return
existing_file.stored_record.fields = notepad.note_list.Copy()
@@ -159,7 +159,7 @@
var/datum/c4_file/folder/dest_folder = system.parse_directory(path_info.directory, system.current_directory)
if(!dest_folder || dest_folder.drive.read_only)
- system.println("Error: Cannot open directory for write.")
+ system.println("[ANSI_WRAP_BOLD("Error")]: Cannot open directory for write.")
return
existing_file = new
@@ -169,7 +169,7 @@
system.println("File saved to [existing_file.path_to_string()].")
else
qdel(existing_file)
- system.println("Error: Unable to save to directory.")
+ system.println("[ANSI_WRAP_BOLD("Error")]: Unable to save to directory.")
/datum/shell_command/notepad/edit_cmd/print
aliases = list("print", "p")
@@ -179,12 +179,12 @@
var/datum/c4_file/terminal_program/notepad/notepad = program
var/obj/item/peripheral/printer/printer = system.get_computer().get_peripheral(PERIPHERAL_TYPE_PRINTER)
if(!printer)
- system.println("Error: Unable to locate printer.")
+ system.println("[ANSI_WRAP_BOLD("Error:")] Unable to locate printer.")
return
if(printer.busy)
- system.println("Error: Printer is busy.")
+ system.println("[ANSI_WRAP_BOLD("Error:")] Printer is busy.")
return
- printer.print(jointext(notepad.note_list, "
"), html_encode(trim(jointext(arguments, ""))) || "printout")
+ printer.print(jointext(notepad.note_list, "\n"), html_encode(trim(jointext(arguments, ""))) || "printout")
system.println("Printing...")
diff --git a/code/modules/computer4/data/terminal/operating_system.dm b/code/modules/computer4/data/terminal/operating_system.dm
index 02c84426ad4c..91601d2aa73b 100644
--- a/code/modules/computer4/data/terminal/operating_system.dm
+++ b/code/modules/computer4/data/terminal/operating_system.dm
@@ -109,7 +109,7 @@
var/obj/machinery/computer4/computer = get_computer()
- computer.text_buffer += "[text]
"
+ computer.text_buffer += "[text]\n"
if(update_ui)
SStgui.update_uis(computer)
return TRUE
diff --git a/code/modules/computer4/data/terminal/probe/probe.dm b/code/modules/computer4/data/terminal/probe/probe.dm
index bf154cf32df5..22df1555777c 100644
--- a/code/modules/computer4/data/terminal/probe/probe.dm
+++ b/code/modules/computer4/data/terminal/probe/probe.dm
@@ -22,7 +22,7 @@
system.println("Welcome to NetProbe, type 'help' to get started.")
if(!get_adapter())
- system.println("Error: No network adapter found.")
+ system.println("[ANSI_WRAP_BOLD("Error:")] No network adapter found.")
/// Getter for a network adapter.
/datum/c4_file/terminal_program/probe/proc/get_adapter()
diff --git a/code/modules/computer4/data/terminal/probe/probe_commands.dm b/code/modules/computer4/data/terminal/probe/probe_commands.dm
index 09b2a4f834f7..b2904e96cfcb 100644
--- a/code/modules/computer4/data/terminal/probe/probe_commands.dm
+++ b/code/modules/computer4/data/terminal/probe/probe_commands.dm
@@ -6,7 +6,7 @@
var/obj/item/peripheral/network_card/wireless/adapter = probe.get_adapter()
if(!adapter)
- system.println("Error: No network adapter found.")
+ system.println("[ANSI_WRAP_BOLD("Error:")] No network adapter found.")
return
if(adapter.ping())
@@ -18,7 +18,7 @@
/datum/shell_command/probe_cmd/view/exec(datum/c4_file/terminal_program/operating_system/thinkdos/system, datum/c4_file/terminal_program/program, list/arguments, list/options)
var/datum/c4_file/terminal_program/probe/probe = program
if(!length(probe.ping_replies))
- system.println("Error: No replies found.")
+ system.println("[ANSI_WRAP_BOLD("Error:")] No replies found.")
return
var/list/out = list("Reply list:")
@@ -29,7 +29,7 @@
out += "\[[reply_netclass]\]-TYPE: [reply_id]"
- system.println(jointext(out, "
"))
+ system.println(jointext(out, "\n"))
/datum/shell_command/probe_cmd/quit
aliases = list("quit", "q")
@@ -45,4 +45,4 @@
if(system.try_background_program(program))
system.println("Moved [program.name] to background processes.")
else
- system.println("Error: RAM is full.")
+ system.println("[ANSI_WRAP_BOLD("Error: RAM is full.")]")
diff --git a/code/modules/computer4/data/terminal/rtos/_rtos.dm b/code/modules/computer4/data/terminal/rtos/_rtos.dm
index 0f16f1984f3e..592edb60c0c1 100644
--- a/code/modules/computer4/data/terminal/rtos/_rtos.dm
+++ b/code/modules/computer4/data/terminal/rtos/_rtos.dm
@@ -118,7 +118,7 @@
*/
/datum/c4_file/terminal_program/operating_system/rtos/proc/redraw_screen(update_ui)
var/obj/machinery/computer4/computer = get_computer()
- computer?.text_buffer = jointext(print_history,"
")
+ computer?.text_buffer = jointext(print_history,"\n")
if(update_ui)
SStgui.update_uis(computer)
return TRUE
diff --git a/code/modules/computer4/data/terminal/test_progs/dbg_ansi.dm b/code/modules/computer4/data/terminal/test_progs/dbg_ansi.dm
new file mode 100644
index 000000000000..f5873a19647f
--- /dev/null
+++ b/code/modules/computer4/data/terminal/test_progs/dbg_ansi.dm
@@ -0,0 +1,28 @@
+/datum/c4_file/terminal_program/ansi_test
+ name = "dbg_ansi"
+ size = 1
+
+/datum/c4_file/terminal_program/ansi_test/execute(datum/c4_file/terminal_program/operating_system/thinkdos/system, datum/parsed_cmdline/cmdline)
+ . = ..()
+ //Iterate and test all SGR parameters in standard form, or special form as needed.
+
+ var/print_all = !!cmdline.options.Find("a")
+
+
+ system.println("╒══════════╤═══════════╤═══════════╕")
+ for(var/param_id in 0 to 107)
+ switch(param_id)
+ //Unsupported/undefined
+ if(5-7,20,25-27,50,52,56-57,60-72,76-89,98-99)
+ if(print_all)
+ system.println("│ SGR [fit_with_zeros("[param_id]", 3)]: │ NOT SUPPORTED │")
+ if(38,48,58)
+ system.println("│ SGR [fit_with_zeros("[param_id]", 3)]: │ Uses Extended Color │")
+ else
+ system.println("│ SGR [fit_with_zeros("[param_id]", 3)]: │ Test Text │ [ANSI_SGR(param_id)]Test Text[ANSI_FULL_RESET] │")
+ switch(param_id) //Print a nice visual divider in a few places.
+ if(29,39,49,89,99)
+ system.println("╞══════════╪═══════════╪═══════════╡")
+ system.println("╘══════════╧═══════════╧═══════════╛")
+ system.unload_program(src)
+ return
diff --git a/code/modules/computer4/data/terminal/thinkdos/thinkdos.dm b/code/modules/computer4/data/terminal/thinkdos/thinkdos.dm
index 1fa7e3ef8682..666da6090100 100644
--- a/code/modules/computer4/data/terminal/thinkdos/thinkdos.dm
+++ b/code/modules/computer4/data/terminal/thinkdos/thinkdos.dm
@@ -32,21 +32,21 @@
/datum/c4_file/terminal_program/operating_system/thinkdos/execute()
if(!initialize_logs())
- println("Log system failure.")
+ println("[ANSI_FG_RED]Log system failure.[ANSI_FG_RESET]")
if(!needs_login)
println("Account system disabled.")
else if(!initialize_accounts())
- println("Unable to start account system.")
+ println("[ANSI_FG_RED]Unable to start account system.[ANSI_FG_RESET]")
change_dir(containing_folder)
var/title_text = list(
- @" ___ _ _ _ ___ ___ ___
",
- @"|_ _|| |_ <_>._ _ | |__| . \| . |/ __>
",
- @" | | | . || || ' || / /| | || | |\__ \
",
- @" |_| |_|_||_||_|_||_\_\|___/`___'<___/
",
+ "[ANSI_FG_RED]", @" ___ _ _ _ ___ ___ ___ ", "\n",
+ "[ANSI_FG_YELLOW]", @"|_ _|| |_ <_>._ _ | |__| . \| . |/ __>", "\n",
+ "[ANSI_FG_BLUE]", @" | | | . || || ' || / /| | || | |\__ \", "\n",
+ "[ANSI_FG_CYAN]", @" |_| |_|_||_||_|_||_\_\|___/`___'<___/", "[ANSI_FG_RESET]"
).Join("")
println(title_text)
@@ -135,7 +135,7 @@
if(!command_log || drive.read_only)
return FALSE
- command_log.data += text
+ command_log.data += "[text]\n"
return TRUE
/// Write to the command log if it's enabled, then print to the screen.
@@ -172,16 +172,16 @@
login_user.access = text2access(account_access)
set_current_user(login_user)
- write_log("LOGIN: [html_encode(account_name)] | [html_encode(account_occupation)]")
- println("Welcome [html_encode(account_name)]!
Current Directory: [current_directory.path_to_string()]")
+ write_log("[ANSI_WRAP_BOLD("LOGIN")]: [account_name] | [account_occupation]")
+ println("Welcome [html_encode(account_name)]!\n[ANSI_WRAP_BOLD("Current Directory: [current_directory.path_to_string()]")]")
return TRUE
/datum/c4_file/terminal_program/operating_system/thinkdos/proc/logout()
if(!current_user)
- print_error("Error: Account system inactive.")
+ print_error("[ANSI_WRAP_BOLD("Error")]: Account system inactive.")
return FALSE
- write_log("LOGOUT: [html_encode(current_user.registered_name)]")
+ write_log("[ANSI_WRAP_BOLD("LOGOUT")]: [current_user.registered_name]")
set_current_user(null)
return TRUE
@@ -189,7 +189,7 @@
var/datum/c4_file/folder/account_dir = parse_directory("users")
if(!istype(account_dir))
if(account_dir && !account_dir.containing_folder.try_delete_file(account_dir))
- print_error("Error: Unable to write account folder.")
+ print_error("[ANSI_WRAP_BOLD("Error")]: Unable to write account folder.")
return FALSE
account_dir = new
@@ -197,7 +197,7 @@
if(!containing_folder.try_add_file(account_dir))
qdel(account_dir)
- print_error("Error: Unable to write account folder.")
+ print_error("[ANSI_WRAP_BOLD("Error")]: Unable to write account folder.")
return FALSE
RegisterSignal(account_dir, list(COMSIG_COMPUTER4_FILE_RENAMED, COMSIG_COMPUTER4_FILE_ADDED, COMSIG_COMPUTER4_FILE_REMOVED), PROC_REF(user_folder_gone))
@@ -205,7 +205,7 @@
var/datum/c4_file/user/user_data = account_dir.get_file("admin", FALSE)
if(!istype(user_data))
if(user_data && !user_data.containing_folder.try_delete_file(user_data))
- print_error("Error: Unable to write account folder.")
+ print_error("[ANSI_WRAP_BOLD("Error")]: Unable to write account folder.")
return FALSE
user_data = new
@@ -213,7 +213,7 @@
if(!account_dir.try_add_file(user_data))
qdel(user_data)
- print_error("Error: Unable to write account file.")
+ print_error("[ANSI_WRAP_BOLD("Error")]: Unable to write account file.")
return FALSE
//set_current_user(user_data)
@@ -255,7 +255,7 @@
command_log = log_file
RegisterSignal(command_log, list(COMSIG_COMPUTER4_FILE_RENAMED, COMSIG_COMPUTER4_FILE_ADDED, COMSIG_PARENT_QDELETING), PROC_REF(log_file_gone))
- log_file.data += "
STARTUP: [stationtime2text()], [stationdate2text()]"
+ log_file.data += "[ANSI_WRAP_BOLD("STARTUP")]: [stationtime2text()], [stationdate2text()]\n"
return TRUE
diff --git a/code/modules/computer4/data/terminal/thinkdos/thinkdos_commands.dm b/code/modules/computer4/data/terminal/thinkdos/thinkdos_commands.dm
index abf075a28dc7..1982d538fa5b 100644
--- a/code/modules/computer4/data/terminal/thinkdos/thinkdos_commands.dm
+++ b/code/modules/computer4/data/terminal/thinkdos/thinkdos_commands.dm
@@ -50,7 +50,7 @@
output.Insert(1, "Use help \[command\] to see specific information about a command.", "List of available commands:")
- system.println(jointext(output, "
"))
+ system.println(jointext(output, "\n"))
/// Clear the screen
/datum/shell_command/thinkdos/home
@@ -63,16 +63,16 @@
/// Print the contents of the current directory.
/datum/shell_command/thinkdos/dir
aliases = list("dir", "catalog", "ls")
- help_text = "Prints the contents of a directory.
Usage: 'dir \[directory?\]'"
+ help_text = "Prints the contents of a directory.\nUsage: 'dir \[directory?\]'"
/datum/shell_command/thinkdos/dir/exec(datum/c4_file/terminal_program/operating_system/thinkdos/system, datum/c4_file/terminal_program/program, list/arguments, list/options)
var/inputted_path = jointext(arguments, " ")
var/datum/c4_file/folder/targeted_dir = system.parse_directory(inputted_path, system.current_directory)
if(!targeted_dir)
- system.print_error("Error: Invalid directory or path.")
+ system.print_error("[ANSI_WRAP_BOLD("Error:")] Invalid directory or path.")
return
- system.println("Contents of [targeted_dir.path_to_string()]:", FALSE)
+ system.println("[ANSI_WRAP_BOLD("Contents of [targeted_dir.path_to_string()]:")]", FALSE)
var/list/directory_text = list()
var/list/cache_spaces = new /list(16)
@@ -95,14 +95,14 @@
var/name_length = length(file.name)
if(name_length < longest_name_length)
if(isnull(cache_spaces[name_length]))
- cache_spaces[name_length] = jointext(new /list(longest_name_length - name_length + 1), " ")
+ cache_spaces[name_length] = jointext(new /list(longest_name_length - name_length + 1), " ")
str = "[cache_spaces[name_length]][str]"
directory_text += str
if(length(directory_text))
- system.println(jointext(directory_text, "
"))
+ system.println(jointext(directory_text, "\n"))
/// Change the current directory to the root of the current folder.
/datum/shell_command/thinkdos/root
@@ -111,48 +111,48 @@
/datum/shell_command/thinkdos/root/exec(datum/c4_file/terminal_program/operating_system/thinkdos/system, datum/c4_file/terminal_program/program, list/arguments, list/options)
system.change_dir(system.current_directory.drive.root)
- system.println("Current Directory is now [system.current_directory.path_to_string()]")
+ system.println("[ANSI_WRAP_BOLD("Current Directory is now [system.current_directory.path_to_string()]")]")
return
/// Change directory.
/datum/shell_command/thinkdos/cd
aliases = list("cd", "chdir")
- help_text = "Changes the current directory.
Usage: 'cd \[directory\]'
'.' refers to the current directory.
'../' refers to the parent directory."
+ help_text = "Changes the current directory.\nUsage: 'cd \[directory\]'\n\n'.' refers to the current directory.\n'../' refers to the parent directory."
/datum/shell_command/thinkdos/cd/exec(datum/c4_file/terminal_program/operating_system/thinkdos/system, datum/c4_file/terminal_program/program, list/arguments, list/options)
if(!length(arguments))
- system.println("Syntax: \"cd \[directory string\]\".")
+ system.println("[ANSI_WRAP_BOLD("Syntax:")] \"cd \[directory string\]\".")
return
var/target_dir = jointext(arguments, " ")
var/datum/c4_file/folder/new_dir = system.parse_directory(target_dir, system.current_directory)
if(!new_dir)
- system.print_error("Error: Invalid directory or path.")
+ system.print_error("[ANSI_WRAP_BOLD("Error:")] Invalid directory or path.")
return
system.change_dir(new_dir)
- system.println("Current Directory is now [system.current_directory.path_to_string()]")
+ system.println("[ANSI_WRAP_BOLD("Current Directory is now [system.current_directory.path_to_string()]")]")
/// Create a folder.
/datum/shell_command/thinkdos/makedir
aliases = list("makedir", "mkdir")
- help_text = "Creates a new folder.
Usage: 'makedir \[directory\]'
See 'cd' for more information."
+ help_text = "Creates a new folder.\nUsage: 'makedir \[directory\]'\n\nSee 'cd' for more information."
/datum/shell_command/thinkdos/makedir/exec(datum/c4_file/terminal_program/operating_system/thinkdos/system, datum/c4_file/terminal_program/program, list/arguments, list/options)
if(!length(arguments))
- system.println("Syntax: \"makedir \[new directory name\]\"")
+ system.println("[ANSI_WRAP_BOLD("Syntax:")] \"makedir \[new directory name\]\"")
return
var/folder_name = trim(jointext(arguments, ""), 16)
if(system.resolve_filepath(folder_name))
- system.print_error("Error: File name in use.")
+ system.print_error("[ANSI_WRAP_BOLD("Error:")] File name in use.")
return
if(!system.validate_file_name(folder_name))
- system.print_error("Error: Invalid character(s).")
+ system.print_error("[ANSI_WRAP_BOLD("Error:")] Invalid character(s).")
return
var/datum/c4_file/folder/new_folder = new
@@ -160,7 +160,7 @@
if(!system.current_directory.try_add_file(new_folder))
qdel(new_folder)
- system.print_error("Error: Unable to create new directory.")
+ system.print_error("[ANSI_WRAP_BOLD("Error:")] Unable to create new directory.")
return
system.println("New directory created.")
@@ -168,11 +168,11 @@
/// Rename a file
/datum/shell_command/thinkdos/rename
aliases = list("move","mv", "rename", "ren")
- help_text = "Moves or renames a file or folder.
Usage: 'move \[options?\] \[path\] \[destination path\]'
See 'cd' for more information.
-f, --force       Overwrite any existing files in the destination location."
+ help_text = "Moves or renames a file or folder.\nUsage: 'move \[options?\] \[path\] \[destination path\]'\n\nSee 'cd' for more information.\n-f, --force Overwrite any existing files in the destination location."
/datum/shell_command/thinkdos/rename/exec(datum/c4_file/terminal_program/operating_system/thinkdos/system, datum/c4_file/terminal_program/program, list/arguments, list/options)
if(length(arguments) != 2)
- system.println("Syntax: \"rename \[name of target] \[new name]\"")
+ system.println("[ANSI_WRAP_BOLD("Syntax:")] \"rename \[name of target] \[new name]\"")
return
var/old_path = arguments[1]
@@ -182,7 +182,7 @@
var/datum/c4_file/file = system.resolve_filepath(old_path)
if(!file)
- system.print_error("Error: Source file not found.")
+ system.print_error("[ANSI_WRAP_BOLD("Error:")] Source file not found.")
return
var/datum/file_path/destination_info = system.text_to_filepath(new_path)
@@ -190,11 +190,11 @@
var/datum/c4_file/folder/destination_folder = system.parse_directory(destination_info.directory, system.current_directory)
if(!destination_folder)
- system.print_error("Error: Target directory not found.")
+ system.print_error("[ANSI_WRAP_BOLD("Error:")] Target directory not found.")
return
if(desired_name && !system.validate_file_name(desired_name))
- system.print_error("Error: Invalid character in name.")
+ system.print_error("[ANSI_WRAP_BOLD("Error:")] Invalid character in name.")
return
var/old_name = file.name
@@ -211,17 +211,17 @@
if(err == "Target in use.")
err += " Use -f to overwrite."
- system.print_error("Error: [err]")
+ system.print_error("[ANSI_WRAP_BOLD("Error:")] [err]")
return
var/datum/c4_file/shares_name = destination_folder.get_file(desired_name)
if(shares_name)
if(!overwrite)
- system.print_error("Error: Target in use. Use -f to overwrite.")
+ system.print_error("[ANSI_WRAP_BOLD("Error:")] Target in use. Use -f to overwrite.")
return
if(!destination_folder.try_delete_file(shares_name))
- system.print_error("Error: Unable to delete target.")
+ system.print_error("[ANSI_WRAP_BOLD("Error:")] Unable to delete target.")
return
file.set_name(desired_name)
@@ -230,11 +230,11 @@
/// Copy a file (opens can of worms and begins eating them).
/datum/shell_command/thinkdos/copy
aliases = list("copy","cp")
- help_text = "Copies a file to another location.
Usage: 'move \[options?\] \[path\] \[destination path\]'
See 'cd' for more information.
-f, --force       Overwrite any existing files in the destination location."
+ help_text = "Copies a file to another location.\nUsage: 'move \[options?\] \[path\] \[destination path\]'\n\nSee 'cd' for more information.\n-f, --force Overwrite any existing files in the destination location."
/datum/shell_command/thinkdos/copy/exec(datum/c4_file/terminal_program/operating_system/thinkdos/system, datum/c4_file/terminal_program/program, list/arguments, list/options)
if(length(arguments) != 2)
- system.println("Syntax: \"copy \[name of target] \[new location]\"")
+ system.println("[ANSI_WRAP_BOLD("Syntax:")] \"copy \[name of target] \[new location]\"")
return
var/old_path = arguments[1]
@@ -244,11 +244,11 @@
var/datum/c4_file/to_copy = system.resolve_filepath(old_path)
if(!to_copy)
- system.print_error("Error: Source file not found.")
+ system.print_error("[ANSI_WRAP_BOLD("Error:")] Source file not found.")
return
if(to_copy.size + system.drive.root.size > system.drive.disk_capacity)
- system.print_error("Error: Copy operation would exceed disk storage.")
+ system.print_error("[ANSI_WRAP_BOLD("Error:")] Copy operation would exceed disk storage.")
return
var/datum/file_path/destination_info = system.text_to_filepath(new_path)
@@ -256,11 +256,11 @@
var/datum/c4_file/folder/destination_folder = system.parse_directory(destination_info.directory, system.current_directory)
if(!destination_folder)
- system.print_error("Error: Target directory not found.")
+ system.print_error("[ANSI_WRAP_BOLD("Error:")] Target directory not found.")
return
if(desired_name && !system.validate_file_name(desired_name))
- system.print_error("Error: Invalid character in name.")
+ system.print_error("[ANSI_WRAP_BOLD("Error:")] Invalid character in name.")
return
// Preserve the existing file name if we didn't specify a new name.
@@ -269,27 +269,27 @@
var/datum/c4_file/shares_name = destination_folder.get_file(desired_name)
if(shares_name)
if(shares_name == to_copy)
- system.print_error("Error: Cannot copy in-place.")
+ system.print_error("[ANSI_WRAP_BOLD("Error:")] Cannot copy in-place.")
return
if(!overwrite)
- system.print_error("Error: Target in use. Use -f to overwrite.")
+ system.print_error("[ANSI_WRAP_BOLD("Error:")] Target in use. Use -f to overwrite.")
return
if(!destination_folder.try_delete_file(shares_name))
- system.print_error("Error: Unable to delete target.")
+ system.print_error("[ANSI_WRAP_BOLD("Error:")] Unable to delete target.")
return
var/datum/c4_file/copy = to_copy.copy()
copy?.set_name(desired_name)
if(isnull(copy))
- system.print_error("Error: Unable to copy file.")
+ system.print_error("[ANSI_WRAP_BOLD("Error:")] Unable to copy file.")
return
if(!destination_folder.try_add_file(copy))
qdel(copy)
- system.print_error("Error: Unable to copy file.")
+ system.print_error("[ANSI_WRAP_BOLD("Error:")] Unable to copy file.")
return
system.println("Copied [to_copy.name] to [copy.path_to_string()].")
@@ -303,15 +303,15 @@
var/list/help_list = list(
"Deletes the specified file from the drive.",
"Usage: 'delete \[options?\] \[path\]'",
- "
See 'cd' for more information.",
+ "\nSee 'cd' for more information.",
)
- help_list += "[fit_with("-f, --force", 20, " ", TRUE)]Overwrite any existing files in the destination location."
- help_list += "[fit_with("-r, -R, --recursive", 20, " ", TRUE)]Allow deletion of folders."
- help_text = jointext(help_list, "
")
+ help_list += "[fit_with("-f, --force", 20, " ", TRUE)]Overwrite any existing files in the destination location."
+ help_list += "[fit_with("-r, -R, --recursive", 20, " ", TRUE)]Allow deletion of folders."
+ help_text = jointext(help_list, "\n")
/datum/shell_command/thinkdos/delete/exec(datum/c4_file/terminal_program/operating_system/thinkdos/system, datum/c4_file/terminal_program/program, list/arguments, list/options)
if(!length(arguments))
- system.println("Syntax: \"del \[-f\] \[file name].\"")
+ system.println("[ANSI_WRAP_BOLD("Syntax:")] \"del \[-f\] \[file name].\"")
return
var/force = !!length(options & list("f", "force"))
@@ -320,21 +320,21 @@
var/datum/c4_file/file = system.resolve_filepath(jointext(arguments, ""))
if(!file)
- system.print_error("Error: File not found.")
+ system.print_error("[ANSI_WRAP_BOLD("Error:")] File not found.")
return
if(istype(file, /datum/c4_file/folder))
if(!recursive)
- system.print_error("Error: Use -r option to delete folders.")
+ system.print_error("[ANSI_WRAP_BOLD("Error:")] Use -r option to delete folders.")
return
var/datum/c4_file/folder/to_delete = file
if(length(to_delete.contents) && !force)
- system.print_error("Error: Folder is not empty. Use -f to delete anyway.")
+ system.print_error("[ANSI_WRAP_BOLD("Error:")] Folder is not empty. Use -f to delete anyway.")
return
if(file == system && !force)
- system.print_error("Error: Access denied.")
+ system.print_error("[ANSI_WRAP_BOLD("Error:")] Access denied.")
return
if(!file.containing_folder) // is root
@@ -349,7 +349,7 @@
if(file.containing_folder.try_delete_file(file))
system.println("File deleted.")
else
- system.print_error("Error: Unable to delete file.")
+ system.print_error("[ANSI_WRAP_BOLD("Error:")] Unable to delete file.")
/datum/shell_command/thinkdos/initlogs
aliases = list("initlogs")
@@ -357,11 +357,11 @@
/datum/shell_command/thinkdos/initlogs/exec(datum/c4_file/terminal_program/operating_system/thinkdos/system, datum/c4_file/terminal_program/program, list/arguments, list/options)
if(system.command_log)
- system.print_error("Error: File already exists.")
+ system.print_error("[ANSI_WRAP_BOLD("Error:")] File already exists.")
return
if(!system.initialize_logs())
- system.print_error("Error: File already exists.")
+ system.print_error("[ANSI_WRAP_BOLD("Error:")] File already exists.")
return
system.println("Logging re-initialized.")
@@ -372,7 +372,7 @@
/datum/shell_command/thinkdos/print/exec(datum/c4_file/terminal_program/operating_system/thinkdos/system, datum/c4_file/terminal_program/program, list/arguments, list/options)
if(!length(arguments))
- system.println("Syntax: \"print \[text to be printed]\"")
+ system.println("[ANSI_WRAP_BOLD("Syntax:")] \"print \[text to be printed]\"")
return
var/text = html_encode(jointext(arguments, " "))
@@ -380,16 +380,16 @@
/datum/shell_command/thinkdos/read
aliases = list("read", "type")
- help_text = "Displays the contents of a file.
Usage: 'read \[directory\]'"
+ help_text = "Displays the contents of a file.\nUsage: 'read \[directory\]'"
/datum/shell_command/thinkdos/read/exec(datum/c4_file/terminal_program/operating_system/thinkdos/system, datum/c4_file/terminal_program/program, list/arguments, list/options)
if(!length(arguments))
- system.println("Syntax: \"read \[file name].\"")
+ system.println("[ANSI_WRAP_BOLD("Syntax:")] \"read \[file name].\"")
return
var/datum/c4_file/file = system.resolve_filepath(jointext(arguments, ""))
if(!file)
- system.println("Error: No file found.")
+ system.println("[ANSI_WRAP_BOLD("Error")]: No file found.")
return
system.println(html_encode(file.to_string()))
@@ -399,7 +399,7 @@
help_text = "Displays the version of the operating system."
/datum/shell_command/thinkdos/version/exec(datum/c4_file/terminal_program/operating_system/thinkdos/system, datum/c4_file/terminal_program/program, list/arguments, list/options)
- system.println("[system.system_version]
Copyright Thinktronic Systems, LTD.")
+ system.println("[system.system_version]\nCopyright Thinktronic Systems, LTD.")
/datum/shell_command/thinkdos/time
aliases = list("time")
@@ -410,16 +410,16 @@
/datum/shell_command/thinkdos/sizeof
aliases = list("sizeof", "du")
- help_text = "Displays the size of a file on disk.
Usage: 'sizeof \[directory\]'"
+ help_text = "Displays the size of a file on disk.\nUsage: 'sizeof \[directory\]'"
/datum/shell_command/thinkdos/sizeof/exec(datum/c4_file/terminal_program/operating_system/thinkdos/system, datum/c4_file/terminal_program/program, list/arguments, list/options)
if(!length(arguments))
- system.println("Syntax: \"sizeof \[file path].\"")
+ system.println("[ANSI_WRAP_BOLD("Syntax:")] \"sizeof \[file path].\"")
return
var/datum/c4_file/file = system.resolve_filepath(jointext(arguments, ""))
if(!file)
- system.print_error("Error: File does not exist.")
+ system.print_error("[ANSI_WRAP_BOLD("Error:")] File does not exist.")
return
system.println(file.size)
@@ -427,47 +427,47 @@
/// Renames the drive title
/datum/shell_command/thinkdos/title
aliases = list("title")
- help_text = "Changes the name of the current .
Usage: 'title \[new name\]'"
+ help_text = "Changes the name of the current .\nUsage: 'title \[new name\]'"
/datum/shell_command/thinkdos/title/exec(datum/c4_file/terminal_program/operating_system/thinkdos/system, datum/c4_file/terminal_program/program, list/arguments, list/options)
if(!length(arguments))
- system.println("Syntax: \"title \[title name]\" Set name of active drive to given title.")
+ system.println("[ANSI_WRAP_BOLD("Syntax:")] \"title \[title name]\" Set name of active drive to given title.")
return
if(system.drive.read_only)
- system.print_error("Error: Unable to set title string.")
+ system.print_error("[ANSI_WRAP_BOLD("Error:")] Unable to set title string.")
return
var/new_title = sanitize(trim(jointext(arguments, ""), 8))
system.drive.title = new_title
- system.println("Drive title set to [new_title].")
+ system.println("Drive title set to [ANSI_WRAP_BOLD("[new_title]")].")
/datum/shell_command/thinkdos/run_prog
aliases = list("run")
- help_text = "Runs an executable file.
Usage: 'run \[file\]'"
+ help_text = "Runs an executable file.\nUsage: 'run \[file\]'"
/datum/shell_command/thinkdos/run_prog/exec(datum/c4_file/terminal_program/operating_system/thinkdos/system, datum/c4_file/terminal_program/program, list/arguments, list/options)
if(!length(arguments))
- system.println("Syntax: \"run \[program filepath].\"")
+ system.println("[ANSI_WRAP_BOLD("Syntax:")] \"run \[program filepath].\"")
return
var/file_path = jointext(arguments, "")
var/datum/c4_file/terminal_program/program_to_run = system.resolve_filepath(file_path, system.current_directory)
if(!istype(program_to_run) || istype(program_to_run, /datum/c4_file/terminal_program/operating_system))
- system.print_error("Error: Cannot find executable.")
+ system.print_error("[ANSI_WRAP_BOLD("Error:")] Cannot find executable.")
return
system.execute_program(program_to_run)
/datum/shell_command/thinkdos/tree
aliases = list("tree")
- help_text = "Displays the file system structure relative to a directory.
Usage: 'tree \[options?\] \[directory?\]'
-f, --file       Display files."
+ help_text = "Displays the file system structure relative to a directory.\nUsage: 'tree \[options?\] \[directory?\]'\n\n-f, --file Display files."
/datum/shell_command/thinkdos/tree/exec(datum/c4_file/terminal_program/operating_system/thinkdos/system, datum/c4_file/terminal_program/program, list/arguments, list/options)
var/datum/c4_file/folder/targeted_dir = system.parse_directory(jointext(arguments, " "), system.current_directory)
if(!targeted_dir)
- system.print_error("Error: Invalid directory or path.")
+ system.print_error("[ANSI_WRAP_BOLD("Error:")] Invalid directory or path.")
return
var/show_files = !!length(options & list("f", "files"))
@@ -476,10 +476,10 @@
search_dir(targeted_dir, output, show_files, 1)
- system.println(jointext(output, "
"))
+ system.println(jointext(output, "\n"))
/datum/shell_command/thinkdos/tree/proc/search_dir(datum/c4_file/folder/folder, list/output, show_files, depth)
- var/spaces = jointext(new /list((depth * 2) + 1), " ")
+ var/spaces = jointext(new /list((depth * 2) + 1), " ")
for(var/datum/c4_file/file as anything in folder.contents)
var/is_folder = istype(file, /datum/c4_file/folder)
@@ -504,14 +504,14 @@
"Manage background processes.",
"Usage: 'backprog \[argument 1\] \[argument 2?\]'",
)
- help_list += "[fit_with("k, kill", 20, " ", TRUE)]Terminate a background process."
- help_list += "[fit_with("s, switch", 20, " ", TRUE)]Focus a background process."
- help_list += "[fit_with("v, view", 20, " ", TRUE)]Display background processes."
- help_text = jointext(help_list, "
")
+ help_list += "[fit_with("k, kill", 20, " ", TRUE)]Terminate a background process."
+ help_list += "[fit_with("s, switch", 20, " ", TRUE)]Focus a background process."
+ help_list += "[fit_with("v, view", 20, " ", TRUE)]Display background processes."
+ help_text = jointext(help_list, "\n")
/datum/shell_command/thinkdos/backprog/exec(datum/c4_file/terminal_program/operating_system/thinkdos/system, datum/c4_file/terminal_program/program, list/arguments, list/options)
if(!length(arguments))
- system.println("Syntax: backprog \[argument\]
Valid arguments: view, kill, switch")
+ system.println("[ANSI_WRAP_BOLD("Syntax:")] backprog \[argument\]\n[ANSI_WRAP_BOLD("Valid arguments:")] view, kill, switch")
return
var/sub_name = arguments[1]
@@ -522,20 +522,20 @@
if(sub_command.try_exec(sub_name, system, program, inner_arguments, null))
return
- system.println("Syntax: backprog \[argument\]
Valid arguments: view, kill, switch")
+ system.println("[ANSI_WRAP_BOLD("Syntax:")] backprog \[argument\]\n[ANSI_WRAP_BOLD("Valid arguments:")] view, kill, switch")
/datum/shell_command/thinkdos_backprog/view
aliases = list("view", "v")
/datum/shell_command/thinkdos_backprog/view/exec(datum/c4_file/terminal_program/operating_system/thinkdos/system, datum/c4_file/terminal_program/program, list/arguments, list/options)
- var/list/out = list("Current programs in memory:")
+ var/list/out = list("[ANSI_WRAP_BOLD("Current programs in memory:")]")
var/count = 0
for(var/datum/c4_file/terminal_program/running_program as anything in system.processing_programs)
count++
- out += "ID: [count] [running_program == system ? "SYSTEM" : running_program.name]"
+ out += "[ANSI_WRAP_BOLD("ID: [count]")] [running_program == system ? "SYSTEM" : running_program.name]"
- system.println(jointext(out, "
"))
+ system.println(jointext(out, "\n"))
/datum/shell_command/thinkdos_backprog/kill
aliases = list("kill", "k")
@@ -543,16 +543,16 @@
/datum/shell_command/thinkdos_backprog/kill/exec(datum/c4_file/terminal_program/operating_system/thinkdos/system, datum/c4_file/terminal_program/program, list/arguments, list/options)
var/id = text2num(jointext(arguments, ""))
if(isnull(id))
- system.println("Syntax: backprog kill \[program id\]")
+ system.println("[ANSI_WRAP_BOLD("Syntax:")] backprog kill \[program id\]")
return
if(!(id in 1 to length(system.processing_programs)))
- system.print_error("Error: Array index out of bounds.")
+ system.print_error("[ANSI_WRAP_BOLD("Error:")] Array index out of bounds.")
return
var/datum/c4_file/terminal_program/to_kill = system.processing_programs[id]
if(to_kill == system)
- system.print_error("Error: Unable to terminate process.")
+ system.print_error("[ANSI_WRAP_BOLD("Error:")] Unable to terminate process.")
return
system.unload_program(to_kill)
@@ -564,16 +564,16 @@
/datum/shell_command/thinkdos_backprog/switch_prog/exec(datum/c4_file/terminal_program/operating_system/thinkdos/system, datum/c4_file/terminal_program/program, list/arguments, list/options)
var/id = text2num(jointext(arguments, ""))
if(isnull(id))
- system.println("Syntax: backprog switch \[program id\]")
+ system.println("[ANSI_WRAP_BOLD("Syntax:")] backprog switch \[program id\]")
return
if(!(id in 1 to length(system.processing_programs)))
- system.print_error("Error: Array index out of bounds.")
+ system.print_error("[ANSI_WRAP_BOLD("Error:")] Array index out of bounds.")
return
var/datum/c4_file/terminal_program/to_run = system.processing_programs[id]
if(to_run == system)
- system.print_error("Error: Process already focused.")
+ system.print_error("[ANSI_WRAP_BOLD("Error:")] Process already focused.")
return
system.execute_program(to_run)
@@ -588,7 +588,7 @@
var/obj/item/peripheral/card_reader/reader = system.get_computer().get_peripheral(PERIPHERAL_TYPE_CARD_READER)
if(!reader)
- system.println("Error: No card reader detected.")
+ system.println("[ANSI_WRAP_BOLD("Error:")] No card reader detected.")
return
var/datum/signal/login_packet = reader.scan_card()
@@ -596,11 +596,11 @@
system.login(login_packet.data["name"], login_packet.data["job"], login_packet.data["access"])
else if(login_packet == "nocard")
- system.print_error("Error: No ID card inserted.")
+ system.print_error("[ANSI_WRAP_BOLD("Error:")] No ID card inserted.")
/datum/shell_command/thinkdos/logout
aliases = list("logout", "logoff")
/datum/shell_command/thinkdos/logout/exec(datum/c4_file/terminal_program/operating_system/thinkdos/system, datum/c4_file/terminal_program/program, list/arguments, list/options)
system.logout()
- system.println("Logout complete. Have a secure day.
Authentication required.
Please insert card and 'login'.")
+ system.println("Logout complete. Have a secure day.\n\nAuthentication required.\nPlease insert card and 'login'.")
diff --git a/code/modules/tgui/tgui.dm b/code/modules/tgui/tgui.dm
index 06d466c5398b..e79331cb6e1f 100644
--- a/code/modules/tgui/tgui.dm
+++ b/code/modules/tgui/tgui.dm
@@ -107,6 +107,7 @@
flush_queue |= window.send_asset(get_asset_datum(/datum/asset/simple/namespaced/libre_baskerville))
flush_queue |= window.send_asset(get_asset_datum(/datum/asset/simple/namespaced/jost))
flush_queue |= window.send_asset(get_asset_datum(/datum/asset/simple/namespaced/yrsa))
+ flush_queue |= window.send_asset(get_asset_datum(/datum/asset/simple/namespaced/ibm_vga9x16))
flush_queue |= window.send_asset(get_asset_datum(/datum/asset/json/icon_ref_map))
for(var/datum/asset/asset in src_object.ui_assets(user))
diff --git a/code/modules/tgui_panel/tgui_panel.dm b/code/modules/tgui_panel/tgui_panel.dm
index a6ffb822c10c..7ee73d293e4f 100644
--- a/code/modules/tgui_panel/tgui_panel.dm
+++ b/code/modules/tgui_panel/tgui_panel.dm
@@ -53,6 +53,7 @@
window.send_asset(get_asset_datum(/datum/asset/simple/namespaced/libre_baskerville))
window.send_asset(get_asset_datum(/datum/asset/simple/namespaced/jost))
window.send_asset(get_asset_datum(/datum/asset/simple/namespaced/yrsa))
+ window.send_asset(get_asset_datum(/datum/asset/simple/namespaced/ibm_vga9x16))
window.send_asset(get_asset_datum(/datum/asset/spritesheet/chat))
window.send_asset(get_asset_datum(/datum/asset/simple/namespaced/chat_icons))
diff --git a/daedalus.dme b/daedalus.dme
index 758d238ffa60..8d764fe9b8f0 100644
--- a/daedalus.dme
+++ b/daedalus.dme
@@ -3033,6 +3033,7 @@
#include "code\modules\computer4\data\terminal\rtos\pincode_door.dm"
#include "code\modules\computer4\data\terminal\rtos\simple_door_control.dm"
#include "code\modules\computer4\data\terminal\rtos\slave.dm"
+#include "code\modules\computer4\data\terminal\test_progs\dbg_ansi.dm"
#include "code\modules\computer4\data\terminal\test_progs\flagtest.dm"
#include "code\modules\computer4\data\terminal\thinkdos\thinkdos.dm"
#include "code\modules\computer4\data\terminal\thinkdos\thinkdos_commands.dm"
diff --git a/fonts/oldschool_pc_fonts/LICENSE.TXT b/fonts/oldschool_pc_fonts/LICENSE.TXT
new file mode 100644
index 000000000000..3bb213246ed7
--- /dev/null
+++ b/fonts/oldschool_pc_fonts/LICENSE.TXT
@@ -0,0 +1,432 @@
+Retrieved from https://int10h.org
+
+© 2015-2020 VileR
+
+Attribution-ShareAlike 4.0 International
+
+=======================================================================
+
+Creative Commons Corporation ("Creative Commons") is not a law firm and
+does not provide legal services or legal advice. Distribution of
+Creative Commons public licenses does not create a lawyer-client or
+other relationship. Creative Commons makes its licenses and related
+information available on an "as-is" basis. Creative Commons gives no
+warranties regarding its licenses, any material licensed under their
+terms and conditions, or any related information. Creative Commons
+disclaims all liability for damages resulting from their use to the
+fullest extent possible.
+
+Using Creative Commons Public Licenses
+
+Creative Commons public licenses provide a standard set of terms and
+conditions that creators and other rights holders may use to share
+original works of authorship and other material subject to copyright
+and certain other rights specified in the public license below. The
+following considerations are for informational purposes only, are not
+exhaustive, and do not form part of our licenses.
+
+ Considerations for licensors: Our public licenses are
+ intended for use by those authorized to give the public
+ permission to use material in ways otherwise restricted by
+ copyright and certain other rights. Our licenses are
+ irrevocable. Licensors should read and understand the terms
+ and conditions of the license they choose before applying it.
+ Licensors should also secure all rights necessary before
+ applying our licenses so that the public can reuse the
+ material as expected. Licensors should clearly mark any
+ material not subject to the license. This includes other CC-
+ licensed material, or material used under an exception or
+ limitation to copyright. More considerations for licensors:
+ wiki.creativecommons.org/Considerations_for_licensors
+
+ Considerations for the public: By using one of our public
+ licenses, a licensor grants the public permission to use the
+ licensed material under specified terms and conditions. If
+ the licensor's permission is not necessary for any reason--for
+ example, because of any applicable exception or limitation to
+ copyright--then that use is not regulated by the license. Our
+ licenses grant only permissions under copyright and certain
+ other rights that a licensor has authority to grant. Use of
+ the licensed material may still be restricted for other
+ reasons, including because others have copyright or other
+ rights in the material. A licensor may make special requests,
+ such as asking that all changes be marked or described.
+ Although not required by our licenses, you are encouraged to
+ respect those requests where reasonable. More_considerations
+ for the public:
+ wiki.creativecommons.org/Considerations_for_licensees
+
+=======================================================================
+
+Creative Commons Attribution-ShareAlike 4.0 International Public
+License
+
+By exercising the Licensed Rights (defined below), You accept and agree
+to be bound by the terms and conditions of this Creative Commons
+Attribution-ShareAlike 4.0 International Public License ("Public
+License"). To the extent this Public License may be interpreted as a
+contract, You are granted the Licensed Rights in consideration of Your
+acceptance of these terms and conditions, and the Licensor grants You
+such rights in consideration of benefits the Licensor receives from
+making the Licensed Material available under these terms and
+conditions.
+
+
+Section 1 -- Definitions.
+
+ a. Adapted Material means material subject to Copyright and Similar
+ Rights that is derived from or based upon the Licensed Material
+ and in which the Licensed Material is translated, altered,
+ arranged, transformed, or otherwise modified in a manner requiring
+ permission under the Copyright and Similar Rights held by the
+ Licensor. For purposes of this Public License, where the Licensed
+ Material is a musical work, performance, or sound recording,
+ Adapted Material is always produced where the Licensed Material is
+ synched in timed relation with a moving image.
+
+ b. Adapter's License means the license You apply to Your Copyright
+ and Similar Rights in Your contributions to Adapted Material in
+ accordance with the terms and conditions of this Public License.
+
+ c. BY-SA Compatible License means a license listed at
+ creativecommons.org/compatiblelicenses, approved by Creative
+ Commons as essentially the equivalent of this Public License.
+
+ d. Copyright and Similar Rights means copyright and/or similar rights
+ closely related to copyright including, without limitation,
+ performance, broadcast, sound recording, and Sui Generis Database
+ Rights, without regard to how the rights are labeled or
+ categorized. For purposes of this Public License, the rights
+ specified in Section 2(b)(1)-(2) are not Copyright and Similar
+ Rights.
+
+ e. Effective Technological Measures means those measures that, in the
+ absence of proper authority, may not be circumvented under laws
+ fulfilling obligations under Article 11 of the WIPO Copyright
+ Treaty adopted on December 20, 1996, and/or similar international
+ agreements.
+
+ f. Exceptions and Limitations means fair use, fair dealing, and/or
+ any other exception or limitation to Copyright and Similar Rights
+ that applies to Your use of the Licensed Material.
+
+ g. License Elements means the license attributes listed in the name
+ of a Creative Commons Public License. The License Elements of this
+ Public License are Attribution and ShareAlike.
+
+ h. Licensed Material means the artistic or literary work, database,
+ or other material to which the Licensor applied this Public
+ License.
+
+ i. Licensed Rights means the rights granted to You subject to the
+ terms and conditions of this Public License, which are limited to
+ all Copyright and Similar Rights that apply to Your use of the
+ Licensed Material and that the Licensor has authority to license.
+
+ j. Licensor means the individual(s) or entity(ies) granting rights
+ under this Public License.
+
+ k. Share means to provide material to the public by any means or
+ process that requires permission under the Licensed Rights, such
+ as reproduction, public display, public performance, distribution,
+ dissemination, communication, or importation, and to make material
+ available to the public including in ways that members of the
+ public may access the material from a place and at a time
+ individually chosen by them.
+
+ l. Sui Generis Database Rights means rights other than copyright
+ resulting from Directive 96/9/EC of the European Parliament and of
+ the Council of 11 March 1996 on the legal protection of databases,
+ as amended and/or succeeded, as well as other essentially
+ equivalent rights anywhere in the world.
+
+ m. You means the individual or entity exercising the Licensed Rights
+ under this Public License. Your has a corresponding meaning.
+
+
+Section 2 -- Scope.
+
+ a. License grant.
+
+ 1. Subject to the terms and conditions of this Public License,
+ the Licensor hereby grants You a worldwide, royalty-free,
+ non-sublicensable, non-exclusive, irrevocable license to
+ exercise the Licensed Rights in the Licensed Material to:
+
+ a. reproduce and Share the Licensed Material, in whole or
+ in part; and
+
+ b. produce, reproduce, and Share Adapted Material.
+
+ 2. Exceptions and Limitations. For the avoidance of doubt, where
+ Exceptions and Limitations apply to Your use, this Public
+ License does not apply, and You do not need to comply with
+ its terms and conditions.
+
+ 3. Term. The term of this Public License is specified in Section
+ 6(a).
+
+ 4. Media and formats; technical modifications allowed. The
+ Licensor authorizes You to exercise the Licensed Rights in
+ all media and formats whether now known or hereafter created,
+ and to make technical modifications necessary to do so. The
+ Licensor waives and/or agrees not to assert any right or
+ authority to forbid You from making technical modifications
+ necessary to exercise the Licensed Rights, including
+ technical modifications necessary to circumvent Effective
+ Technological Measures. For purposes of this Public License,
+ simply making modifications authorized by this Section 2(a)
+ (4) never produces Adapted Material.
+
+ 5. Downstream recipients.
+
+ a. Offer from the Licensor -- Licensed Material. Every
+ recipient of the Licensed Material automatically
+ receives an offer from the Licensor to exercise the
+ Licensed Rights under the terms and conditions of this
+ Public License.
+
+ b. Additional offer from the Licensor -- Adapted Material.
+ Every recipient of Adapted Material from You
+ automatically receives an offer from the Licensor to
+ exercise the Licensed Rights in the Adapted Material
+ under the conditions of the Adapter's License You apply.
+
+ c. No downstream restrictions. You may not offer or impose
+ any additional or different terms or conditions on, or
+ apply any Effective Technological Measures to, the
+ Licensed Material if doing so restricts exercise of the
+ Licensed Rights by any recipient of the Licensed
+ Material.
+
+ 6. No endorsement. Nothing in this Public License constitutes or
+ may be construed as permission to assert or imply that You
+ are, or that Your use of the Licensed Material is, connected
+ with, or sponsored, endorsed, or granted official status by,
+ the Licensor or others designated to receive attribution as
+ provided in Section 3(a)(1)(A)(i).
+
+ b. Other rights.
+
+ 1. Moral rights, such as the right of integrity, are not
+ licensed under this Public License, nor are publicity,
+ privacy, and/or other similar personality rights; however, to
+ the extent possible, the Licensor waives and/or agrees not to
+ assert any such rights held by the Licensor to the limited
+ extent necessary to allow You to exercise the Licensed
+ Rights, but not otherwise.
+
+ 2. Patent and trademark rights are not licensed under this
+ Public License.
+
+ 3. To the extent possible, the Licensor waives any right to
+ collect royalties from You for the exercise of the Licensed
+ Rights, whether directly or through a collecting society
+ under any voluntary or waivable statutory or compulsory
+ licensing scheme. In all other cases the Licensor expressly
+ reserves any right to collect such royalties.
+
+
+Section 3 -- License Conditions.
+
+Your exercise of the Licensed Rights is expressly made subject to the
+following conditions.
+
+ a. Attribution.
+
+ 1. If You Share the Licensed Material (including in modified
+ form), You must:
+
+ a. retain the following if it is supplied by the Licensor
+ with the Licensed Material:
+
+ i. identification of the creator(s) of the Licensed
+ Material and any others designated to receive
+ attribution, in any reasonable manner requested by
+ the Licensor (including by pseudonym if
+ designated);
+
+ ii. a copyright notice;
+
+ iii. a notice that refers to this Public License;
+
+ iv. a notice that refers to the disclaimer of
+ warranties;
+
+ v. a URI or hyperlink to the Licensed Material to the
+ extent reasonably practicable;
+
+ b. indicate if You modified the Licensed Material and
+ retain an indication of any previous modifications; and
+
+ c. indicate the Licensed Material is licensed under this
+ Public License, and include the text of, or the URI or
+ hyperlink to, this Public License.
+
+ 2. You may satisfy the conditions in Section 3(a)(1) in any
+ reasonable manner based on the medium, means, and context in
+ which You Share the Licensed Material. For example, it may be
+ reasonable to satisfy the conditions by providing a URI or
+ hyperlink to a resource that includes the required
+ information.
+
+ 3. If requested by the Licensor, You must remove any of the
+ information required by Section 3(a)(1)(A) to the extent
+ reasonably practicable.
+
+ b. ShareAlike.
+
+ In addition to the conditions in Section 3(a), if You Share
+ Adapted Material You produce, the following conditions also apply.
+
+ 1. The Adapter's License You apply must be a Creative Commons
+ license with the same License Elements, this version or
+ later, or a BY-SA Compatible License.
+
+ 2. You must include the text of, or the URI or hyperlink to, the
+ Adapter's License You apply. You may satisfy this condition
+ in any reasonable manner based on the medium, means, and
+ context in which You Share Adapted Material.
+
+ 3. You may not offer or impose any additional or different terms
+ or conditions on, or apply any Effective Technological
+ Measures to, Adapted Material that restrict exercise of the
+ rights granted under the Adapter's License You apply.
+
+
+Section 4 -- Sui Generis Database Rights.
+
+Where the Licensed Rights include Sui Generis Database Rights that
+apply to Your use of the Licensed Material:
+
+ a. for the avoidance of doubt, Section 2(a)(1) grants You the right
+ to extract, reuse, reproduce, and Share all or a substantial
+ portion of the contents of the database;
+
+ b. if You include all or a substantial portion of the database
+ contents in a database in which You have Sui Generis Database
+ Rights, then the database in which You have Sui Generis Database
+ Rights (but not its individual contents) is Adapted Material,
+
+ including for purposes of Section 3(b); and
+ c. You must comply with the conditions in Section 3(a) if You Share
+ all or a substantial portion of the contents of the database.
+
+For the avoidance of doubt, this Section 4 supplements and does not
+replace Your obligations under this Public License where the Licensed
+Rights include other Copyright and Similar Rights.
+
+
+Section 5 -- Disclaimer of Warranties and Limitation of Liability.
+
+ a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
+ EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
+ AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
+ ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
+ IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
+ WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
+ ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
+ KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
+ ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
+
+ b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
+ TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
+ NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
+ INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
+ COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
+ USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
+ ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
+ DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
+ IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
+
+ c. The disclaimer of warranties and limitation of liability provided
+ above shall be interpreted in a manner that, to the extent
+ possible, most closely approximates an absolute disclaimer and
+ waiver of all liability.
+
+
+Section 6 -- Term and Termination.
+
+ a. This Public License applies for the term of the Copyright and
+ Similar Rights licensed here. However, if You fail to comply with
+ this Public License, then Your rights under this Public License
+ terminate automatically.
+
+ b. Where Your right to use the Licensed Material has terminated under
+ Section 6(a), it reinstates:
+
+ 1. automatically as of the date the violation is cured, provided
+ it is cured within 30 days of Your discovery of the
+ violation; or
+
+ 2. upon express reinstatement by the Licensor.
+
+ For the avoidance of doubt, this Section 6(b) does not affect any
+ right the Licensor may have to seek remedies for Your violations
+ of this Public License.
+
+ c. For the avoidance of doubt, the Licensor may also offer the
+ Licensed Material under separate terms or conditions or stop
+ distributing the Licensed Material at any time; however, doing so
+ will not terminate this Public License.
+
+ d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
+ License.
+
+
+Section 7 -- Other Terms and Conditions.
+
+ a. The Licensor shall not be bound by any additional or different
+ terms or conditions communicated by You unless expressly agreed.
+
+ b. Any arrangements, understandings, or agreements regarding the
+ Licensed Material not stated herein are separate from and
+ independent of the terms and conditions of this Public License.
+
+
+Section 8 -- Interpretation.
+
+ a. For the avoidance of doubt, this Public License does not, and
+ shall not be interpreted to, reduce, limit, restrict, or impose
+ conditions on any use of the Licensed Material that could lawfully
+ be made without permission under this Public License.
+
+ b. To the extent possible, if any provision of this Public License is
+ deemed unenforceable, it shall be automatically reformed to the
+ minimum extent necessary to make it enforceable. If the provision
+ cannot be reformed, it shall be severed from this Public License
+ without affecting the enforceability of the remaining terms and
+ conditions.
+
+ c. No term or condition of this Public License will be waived and no
+ failure to comply consented to unless expressly agreed to by the
+ Licensor.
+
+ d. Nothing in this Public License constitutes or may be interpreted
+ as a limitation upon, or waiver of, any privileges and immunities
+ that apply to the Licensor or You, including from the legal
+ processes of any jurisdiction or authority.
+
+
+=======================================================================
+
+Creative Commons is not a party to its public
+licenses. Notwithstanding, Creative Commons may elect to apply one of
+its public licenses to material it publishes and in those instances
+will be considered the “Licensor.” The text of the Creative Commons
+public licenses is dedicated to the public domain under the CC0 Public
+Domain Dedication. Except for the limited purpose of indicating that
+material is shared under a Creative Commons public license or as
+otherwise permitted by the Creative Commons policies published at
+creativecommons.org/policies, Creative Commons does not authorize the
+use of the trademark "Creative Commons" or any other trademark or logo
+of Creative Commons without its prior written consent including,
+without limitation, in connection with any unauthorized modifications
+to any of its public licenses or any other arrangements,
+understandings, or agreements concerning use of licensed material. For
+the avoidance of doubt, this paragraph does not form part of the
+public licenses.
+
+Creative Commons may be contacted at creativecommons.org.
+
diff --git a/fonts/oldschool_pc_fonts/WebPlus_IBM_VGA_9x16.css b/fonts/oldschool_pc_fonts/WebPlus_IBM_VGA_9x16.css
new file mode 100644
index 000000000000..7bffcdacee4b
--- /dev/null
+++ b/fonts/oldschool_pc_fonts/WebPlus_IBM_VGA_9x16.css
@@ -0,0 +1,7 @@
+@font-face {
+ font-family: "IBM VGA 9x16";
+ font-style: normal;
+ font-weight: 100 900;
+ font-display: swap;
+ src: url("WebPlus_IBM_VGA_9x16.woff") format("woff");
+}
diff --git a/fonts/oldschool_pc_fonts/WebPlus_IBM_VGA_9x16.woff b/fonts/oldschool_pc_fonts/WebPlus_IBM_VGA_9x16.woff
new file mode 100644
index 000000000000..969fb6566f45
Binary files /dev/null and b/fonts/oldschool_pc_fonts/WebPlus_IBM_VGA_9x16.woff differ
diff --git a/tgui/package.json b/tgui/package.json
index 7abbb0584e8a..878ed0972ea7 100644
--- a/tgui/package.json
+++ b/tgui/package.json
@@ -38,6 +38,7 @@
"eslint-plugin-simple-import-sort": "^12.1.0",
"eslint-plugin-typescript-sort-keys": "^3.2.0",
"eslint-plugin-unused-imports": "^3.2.0",
+ "fancy-ansi": "^0.1.3",
"file-loader": "^6.2.0",
"jest": "^29.7.0",
"jest-circus": "^29.7.0",
diff --git a/tgui/packages/tgui/interfaces/Terminal/TerminalOutputSection.tsx b/tgui/packages/tgui/interfaces/Terminal/TerminalOutputSection.tsx
index 551ad1712e92..1b726a77fc05 100644
--- a/tgui/packages/tgui/interfaces/Terminal/TerminalOutputSection.tsx
+++ b/tgui/packages/tgui/interfaces/Terminal/TerminalOutputSection.tsx
@@ -6,6 +6,7 @@
*/
import { BooleanLike } from 'common/react';
+import { FancyAnsi } from 'fancy-ansi';
import { useEffect } from 'react';
import { Box, Section } from '../../components';
@@ -16,6 +17,8 @@ type TerminalOutputSectionProps = Pick<
'bgColor' | 'displayHTML' | 'fontColor'
> & { noscroll?: BooleanLike };
+const fancyAnsi = new FancyAnsi();
+
export const TerminalOutputSection = (props: TerminalOutputSectionProps) => {
const { displayHTML, fontColor, bgColor, noscroll } = props;
@@ -30,6 +33,10 @@ export const TerminalOutputSection = (props: TerminalOutputSectionProps) => {
sectionContentElement.scrollTop = sectionContentElement.scrollHeight;
}, [displayHTML]);
+ /* Whoops, lummox' JSON encoder is shoddy! We're being sent invalid UTF-16
+ and need to go back and fix it before passing it into the ANSI decoder. */
+ let fixed_html = displayHTML.replaceAll('\udc1b', '\u001b'); //Bad surrogate.
+
return (
{
container_id="terminalOutput"
>
);
diff --git a/tgui/packages/tgui/package.json b/tgui/packages/tgui/package.json
index 350943ad9f09..e8ed605d16ba 100644
--- a/tgui/packages/tgui/package.json
+++ b/tgui/packages/tgui/package.json
@@ -8,6 +8,7 @@
"common": "workspace:*",
"dateformat": "^5.0.3",
"dompurify": "^3.2.6",
+ "fancy-ansi": "^0.1.3",
"js-yaml": "^4.1.0",
"marked": "^4.3.0",
"react": "^19.1.0",
diff --git a/tgui/packages/tgui/styles/themes/retro-dark.scss b/tgui/packages/tgui/styles/themes/retro-dark.scss
index 446286d6bced..8c07071b740e 100644
--- a/tgui/packages/tgui/styles/themes/retro-dark.scss
+++ b/tgui/packages/tgui/styles/themes/retro-dark.scss
@@ -66,4 +66,17 @@ $lightorange: #fda751;
'../layouts/TitleBar.scss',
$with: ('background-color': color.scale($darkorange, $whiteness: +10%))
);
+
+ // Fancy-ANSI Control Variables
+
+ // Alt Fonts
+ --ansi-font-1: 'Consolas';
+ // --ansi-font-2
+ // --ansi-font-3
+ // --ansi-font-4
+ // --ansi-font-5
+ // --ansi-font-6
+ // --ansi-font-7
+ // --ansi-font-8
+ // --ansi-font-9
}
diff --git a/tgui/yarn.lock b/tgui/yarn.lock
index 4ba4bd0acd6b..bacfbb1fd5d5 100644
--- a/tgui/yarn.lock
+++ b/tgui/yarn.lock
@@ -3772,6 +3772,13 @@ __metadata:
languageName: node
linkType: hard
+"escape-html@npm:^1.0.3":
+ version: 1.0.3
+ resolution: "escape-html@npm:1.0.3"
+ checksum: 10c0/524c739d776b36c3d29fa08a22e03e8824e3b2fd57500e5e44ecf3cc4707c34c60f9ca0781c0e33d191f2991161504c295e98f68c78fe7baa6e57081ec6ac0a3
+ languageName: node
+ linkType: hard
+
"escape-string-regexp@npm:^2.0.0":
version: 2.0.0
resolution: "escape-string-regexp@npm:2.0.0"
@@ -4106,6 +4113,15 @@ __metadata:
languageName: node
linkType: hard
+"fancy-ansi@npm:^0.1.3":
+ version: 0.1.3
+ resolution: "fancy-ansi@npm:0.1.3"
+ dependencies:
+ escape-html: "npm:^1.0.3"
+ checksum: 10c0/691fdb86bbbe12318c2908fc6b725e08c17874b84f11dd22b5833663e5c77c03c5eb1142c6c779f66ab2f60f47038a09c822ee7fe7c0f043fa3c16f1dbb83216
+ languageName: node
+ linkType: hard
+
"fantasticon@npm:^3.0.0":
version: 3.0.0
resolution: "fantasticon@npm:3.0.0"
@@ -8329,6 +8345,7 @@ __metadata:
eslint-plugin-simple-import-sort: "npm:^12.1.0"
eslint-plugin-typescript-sort-keys: "npm:^3.2.0"
eslint-plugin-unused-imports: "npm:^3.2.0"
+ fancy-ansi: "npm:^0.1.3"
file-loader: "npm:^6.2.0"
jest: "npm:^29.7.0"
jest-circus: "npm:^29.7.0"
@@ -8357,6 +8374,7 @@ __metadata:
common: "workspace:*"
dateformat: "npm:^5.0.3"
dompurify: "npm:^3.2.6"
+ fancy-ansi: "npm:^0.1.3"
js-yaml: "npm:^4.1.0"
marked: "npm:^4.3.0"
react: "npm:^19.1.0"