From cb151844eab0218875e3cdbce5b862ce2c759c11 Mon Sep 17 00:00:00 2001
From: Francinum <5572280+francinum@users.noreply.github.com>
Date: Wed, 14 Jan 2026 04:36:45 -0500
Subject: [PATCH 1/3] HTML -> ANSI-based styling A lot of computer4 programs
need to be altered for this. More helpers need to be made. It's 5am. I'm
going to sleep and kapu has tired of my bullshit.
---
code/__DEFINES/computer4_defines.dm | 31 +++++++++++++-
code/modules/computer4/computer4.dm | 16 +++----
.../data/terminal/directive/directman.dm | 42 +++++++++----------
.../data/terminal/operating_system.dm | 2 +-
.../data/terminal/test_progs/dbg_ansi.dm | 26 ++++++++++++
.../data/terminal/thinkdos/thinkdos.dm | 30 ++++++-------
daedalus.dme | 1 +
tgui/package.json | 1 +
.../Terminal/TerminalOutputSection.tsx | 9 +++-
tgui/packages/tgui/package.json | 1 +
tgui/yarn.lock | 18 ++++++++
11 files changed, 130 insertions(+), 47 deletions(-)
create mode 100644 code/modules/computer4/data/terminal/test_progs/dbg_ansi.dm
diff --git a/code/__DEFINES/computer4_defines.dm b/code/__DEFINES/computer4_defines.dm
index c18764ef04fa..0ed882adf3aa 100644
--- a/code/__DEFINES/computer4_defines.dm
+++ b/code/__DEFINES/computer4_defines.dm
@@ -31,6 +31,35 @@
// 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_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/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/directive/directman.dm b/code/modules/computer4/data/terminal/directive/directman.dm
index b6a822d8709e..7d7f5cb59f81 100644
--- a/code/modules/computer4/data/terminal/directive/directman.dm
+++ b/code/modules/computer4/data/terminal/directive/directman.dm
@@ -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/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/test_progs/dbg_ansi.dm b/code/modules/computer4/data/terminal/test_progs/dbg_ansi.dm
new file mode 100644
index 000000000000..10eccb6b714b
--- /dev/null
+++ b/code/modules/computer4/data/terminal/test_progs/dbg_ansi.dm
@@ -0,0 +1,26 @@
+/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 |")
+ continue
+ 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] |")
+ 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..da6cb978b4ed 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_GREEN]", @"|_ _|| |_ <_>._ _ | |__| . \| . |/ __>", "\n",
+ "[ANSI_FG_YELLOW]",@" | | | . || || ' || / /| | || | |\__ \", "\n",
+ "[ANSI_FG_BLUE]", @" |_| |_|_||_||_|_||_\_\|___/`___'<___/", "[ANSI_FG_RESET]"
).Join("")
println(title_text)
@@ -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 += "\n[ANSI_WRAP_BOLD("STARTUP")]: [stationtime2text()], [stationdate2text()]"
return TRUE
diff --git a/daedalus.dme b/daedalus.dme
index 686efc0d1264..325b240c7dd6 100644
--- a/daedalus.dme
+++ b/daedalus.dme
@@ -3027,6 +3027,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/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..9fde7561b5b6 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 (
{
color={fontColor}
fontSize="1.2em"
preserveWhitespace
- dangerouslySetInnerHTML={{ __html: displayHTML }}
+ dangerouslySetInnerHTML={{ __html: fancyAnsi.toHtml(fixed_html) }}
/>
);
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/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"
From 560f29af34f7f2321478dccb4a145629b78f33ab Mon Sep 17 00:00:00 2001
From: Francinum <5572280+francinum@users.noreply.github.com>
Date: Thu, 15 Jan 2026 01:44:02 -0500
Subject: [PATCH 2/3] Purges most (probably missed something) remaining HTML
styling
---
code/__DEFINES/computer4_defines.dm | 3 +
.../data/terminal/_terminal_program.dm | 4 +-
.../data/terminal/directive/directman.dm | 6 +-
.../terminal/directive/directman_commands.dm | 4 +-
.../data/terminal/medtrak/medtrak.dm | 55 ++++---
.../terminal/medtrak/medtrak_edit_record.dm | 14 +-
.../terminal/medtrak/medtrak_menu_commands.dm | 4 +-
.../medtrak/medtrak_record_commands.dm | 4 +-
.../data/terminal/netpage/netpage.dm | 14 +-
.../data/terminal/netpage/netpage_commands.dm | 4 +-
.../data/terminal/notepad/notepad.dm | 14 +-
.../data/terminal/notepad/notepad_commands.dm | 24 +--
.../computer4/data/terminal/probe/probe.dm | 2 +-
.../data/terminal/probe/probe_commands.dm | 8 +-
.../computer4/data/terminal/rtos/_rtos.dm | 2 +-
.../data/terminal/thinkdos/thinkdos.dm | 8 +-
.../terminal/thinkdos/thinkdos_commands.dm | 154 +++++++++---------
17 files changed, 165 insertions(+), 159 deletions(-)
diff --git a/code/__DEFINES/computer4_defines.dm b/code/__DEFINES/computer4_defines.dm
index 0ed882adf3aa..4c0d6a398dd3 100644
--- a/code/__DEFINES/computer4_defines.dm
+++ b/code/__DEFINES/computer4_defines.dm
@@ -55,6 +55,9 @@
#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)
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 7d7f5cb59f81..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)
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..5f99f1a46339 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,13 +157,16 @@
/// 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,
- ).Join("")
+ "[ANSI_FG_CYAN]",
+ @(eol)" __ __ _ _____ _ "eol,
+ @(eol)"| \/ | ___ __| | |_ _| _ _ __ _ | |__ "eol,
+ @(eol)"| |\/| | / -_) / _` | | | | '_| / _` | | / / "eol,
+ @(eol)"|_|__|_| \___| \__,_| _|_|_ _|_|_ \__,_| |_\_\ "eol,
+ "[ANSI_FG_WHITE]",
+ @(eol)"_|"""""|_|"""""|_|"""""|_|"""""|_|"""""|_|"""""|_|"""""|"eol,
+ @(eol)" `-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"eol,
+ "[ANSI_FG_RESET]",
+ ).Join("\n")
current_menu = MEDTRAK_MENU_HOME
var/list/out = list(
@@ -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..6d37ca528dd9 100644
--- a/code/modules/computer4/data/terminal/notepad/notepad.dm
+++ b/code/modules/computer4/data/terminal/notepad/notepad.dm
@@ -24,13 +24,13 @@
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.")
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/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/thinkdos/thinkdos.dm b/code/modules/computer4/data/terminal/thinkdos/thinkdos.dm
index da6cb978b4ed..928d4bb6d18b 100644
--- a/code/modules/computer4/data/terminal/thinkdos/thinkdos.dm
+++ b/code/modules/computer4/data/terminal/thinkdos/thinkdos.dm
@@ -43,10 +43,10 @@
change_dir(containing_folder)
var/title_text = list(
- "[ANSI_FG_RED]", @" ___ _ _ _ ___ ___ ___ ", "\n",
- "[ANSI_FG_GREEN]", @"|_ _|| |_ <_>._ _ | |__| . \| . |/ __>", "\n",
- "[ANSI_FG_YELLOW]",@" | | | . || || ' || / /| | || | |\__ \", "\n",
- "[ANSI_FG_BLUE]", @" |_| |_|_||_||_|_||_\_\|___/`___'<___/", "[ANSI_FG_RESET]"
+ "[ANSI_FG_RED]", @" ___ _ _ _ ___ ___ ___ ", "\n",
+ "[ANSI_FG_YELLOW]", @"|_ _|| |_ <_>._ _ | |__| . \| . |/ __>", "\n",
+ "[ANSI_FG_BLUE]", @" | | | . || || ' || / /| | || | |\__ \", "\n",
+ "[ANSI_FG_CYAN]", @" |_| |_|_||_||_|_||_\_\|___/`___'<___/", "[ANSI_FG_RESET]"
).Join("")
println(title_text)
diff --git a/code/modules/computer4/data/terminal/thinkdos/thinkdos_commands.dm b/code/modules/computer4/data/terminal/thinkdos/thinkdos_commands.dm
index abf075a28dc7..97895c05ff7f 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)
@@ -102,7 +102,7 @@
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_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,7 +476,7 @@
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), " ")
@@ -507,11 +507,11 @@
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_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'.")
From f50554e52f2fef03abb48866f93d5ca832da907c Mon Sep 17 00:00:00 2001
From: Francinum <5572280+francinum@users.noreply.github.com>
Date: Thu, 15 Jan 2026 03:22:31 -0500
Subject: [PATCH 3/3] Various program patches New font
---
code/modules/asset_cache/assets/webfonts.dm | 9 +
.../data/terminal/medtrak/medtrak.dm | 14 +-
.../data/terminal/notepad/notepad.dm | 7 +-
.../data/terminal/test_progs/dbg_ansi.dm | 16 +-
.../data/terminal/thinkdos/thinkdos.dm | 4 +-
.../terminal/thinkdos/thinkdos_commands.dm | 20 +-
code/modules/tgui/tgui.dm | 1 +
code/modules/tgui_panel/tgui_panel.dm | 1 +
fonts/oldschool_pc_fonts/LICENSE.TXT | 432 ++++++++++++++++++
.../WebPlus_IBM_VGA_9x16.css | 7 +
.../WebPlus_IBM_VGA_9x16.woff | Bin 0 -> 22712 bytes
.../Terminal/TerminalOutputSection.tsx | 5 +-
.../tgui/styles/themes/retro-dark.scss | 13 +
13 files changed, 496 insertions(+), 33 deletions(-)
create mode 100644 fonts/oldschool_pc_fonts/LICENSE.TXT
create mode 100644 fonts/oldschool_pc_fonts/WebPlus_IBM_VGA_9x16.css
create mode 100644 fonts/oldschool_pc_fonts/WebPlus_IBM_VGA_9x16.woff
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/data/terminal/medtrak/medtrak.dm b/code/modules/computer4/data/terminal/medtrak/medtrak.dm
index 5f99f1a46339..92e21adb9129 100644
--- a/code/modules/computer4/data/terminal/medtrak/medtrak.dm
+++ b/code/modules/computer4/data/terminal/medtrak/medtrak.dm
@@ -158,15 +158,15 @@
/datum/c4_file/terminal_program/medtrak/proc/home_text()
var/title_text = list(
"[ANSI_FG_CYAN]",
- @(eol)" __ __ _ _____ _ "eol,
- @(eol)"| \/ | ___ __| | |_ _| _ _ __ _ | |__ "eol,
- @(eol)"| |\/| | / -_) / _` | | | | '_| / _` | | / / "eol,
- @(eol)"|_|__|_| \___| \__,_| _|_|_ _|_|_ \__,_| |_\_\ "eol,
+ @(eol) __ __ _ _____ _ eol,"\n",
+ @(eol)| \/ | ___ __| | |_ _| _ _ __ _ | |__ eol,"\n",
+ @(eol)| |\/| | / -_) / _` | | | | '_| / _` | | / / eol,"\n",
+ @(eol)|_|__|_| \___| \__,_| _|_|_ _|_|_ \__,_| |_\_\ eol,"\n",
"[ANSI_FG_WHITE]",
- @(eol)"_|"""""|_|"""""|_|"""""|_|"""""|_|"""""|_|"""""|_|"""""|"eol,
- @(eol)" `-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"eol,
+ @(eol)_|"""""|_|"""""|_|"""""|_|"""""|_|"""""|_|"""""|_|"""""|eol,"\n",
+ @(eol) `-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'eol,"\n",
"[ANSI_FG_RESET]",
- ).Join("\n")
+ ).Join("")
current_menu = MEDTRAK_MENU_HOME
var/list/out = list(
diff --git a/code/modules/computer4/data/terminal/notepad/notepad.dm b/code/modules/computer4/data/terminal/notepad/notepad.dm
index 6d37ca528dd9..bf664c13540c 100644
--- a/code/modules/computer4/data/terminal/notepad/notepad.dm
+++ b/code/modules/computer4/data/terminal/notepad/notepad.dm
@@ -35,18 +35,15 @@
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/test_progs/dbg_ansi.dm b/code/modules/computer4/data/terminal/test_progs/dbg_ansi.dm
index 10eccb6b714b..f5873a19647f 100644
--- a/code/modules/computer4/data/terminal/test_progs/dbg_ansi.dm
+++ b/code/modules/computer4/data/terminal/test_progs/dbg_ansi.dm
@@ -9,18 +9,20 @@
var/print_all = !!cmdline.options.Find("a")
- system.println("#----------#-----------#-----------#")
+ 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(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 |")
- continue
+ 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 |")
+ 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] |")
- system.println("#----------#-----------#-----------#")
+ 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 928d4bb6d18b..666da6090100 100644
--- a/code/modules/computer4/data/terminal/thinkdos/thinkdos.dm
+++ b/code/modules/computer4/data/terminal/thinkdos/thinkdos.dm
@@ -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.
@@ -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 += "\n[ANSI_WRAP_BOLD("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 97895c05ff7f..1982d538fa5b 100644
--- a/code/modules/computer4/data/terminal/thinkdos/thinkdos_commands.dm
+++ b/code/modules/computer4/data/terminal/thinkdos/thinkdos_commands.dm
@@ -95,7 +95,7 @@
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]"
@@ -168,7 +168,7 @@
/// Rename a file
/datum/shell_command/thinkdos/rename
aliases = list("move","mv", "rename", "ren")
- 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."
+ 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)
@@ -230,7 +230,7 @@
/// 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.\nUsage: 'move \[options?\] \[path\] \[destination path\]'\n\nSee 'cd' for more information.\n-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)
@@ -305,8 +305,8 @@
"Usage: 'delete \[options?\] \[path\]'",
"\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_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)
@@ -462,7 +462,7 @@
/datum/shell_command/thinkdos/tree
aliases = list("tree")
- help_text = "Displays the file system structure relative to a directory.\nUsage: 'tree \[options?\] \[directory?\]'\n\n-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)
@@ -479,7 +479,7 @@
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,9 +504,9 @@
"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_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)
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/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 0000000000000000000000000000000000000000..969fb6566f456c7b420fc86b1c043bbe0b2ee75a
GIT binary patch
literal 22712
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 {
container_id="terminalOutput"
>
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
}