This gist shows how I have implemented ANSI True Color Support in the SilberlandLD mudlib.
It can be controlled by the player through telling the MUD to use ANSI True Colors via the stty ansi
and subsequent
stty truecolor
commands.
The code here only shows the changes to be applied to the files in question. It assumes a similar setup to the Tamedhon or Silberland
/std/player/base.c
//...
#ifndef __ANSI__H_
#define __ANSI__H_
// ...
// True Color support
#define ANSI_TRUE_COLOR(r,g,b) "\e[38;2;"+r+";"+g+";"+b+"m" // Select RGB foreground color
#define ANSI_TRUE_COLOR_BG(r,g,b) "\e[48;"+r+";"+g+";"+b+"m" // Select RGB background color
// TTY constants
#define TTY_DUMB "dumb"
#define TTY_VT100 "vt100"
#define TTY_ANSI "ansi"
#define P_ANSI_TRUE_COLOR "tty_ansi_true_color" // 1 = ansi true color support enabled
#endif
//...
/// This function maps an RGB value (each 0..255) to the nearest ANSI color.
string rgb_to_ansi(int r, int g, int b) {
// Define an array of mappings.
// The RGB values here are chosen as “representatives” of the ANSI colors.
// You can adjust these if you have a different idea of what each ANSI code should represent.
mapping *colors = ({
([ "ansi": ANSI_BLACK, "r": 0, "g": 0, "b": 0 ]),
([ "ansi": ANSI_RED, "r": 128, "g": 0, "b": 0 ]),
([ "ansi": ANSI_GREEN, "r": 0, "g": 128, "b": 0 ]),
([ "ansi": ANSI_BROWN, "r": 128, "g": 128, "b": 0 ]), // brown/dark yellow
([ "ansi": ANSI_BLUE, "r": 0, "g": 0, "b": 128 ]),
([ "ansi": ANSI_PURPLE, "r": 128, "g": 0, "b": 128 ]),
([ "ansi": ANSI_CYAN, "r": 0, "g": 128, "b": 128 ]),
([ "ansi": ANSI_GREY, "r": 192, "g": 192, "b": 192 ]),
([ "ansi": ANSI_WHITE, "r": 255, "g": 255, "b": 255 ])
});
// We'll use the squared Euclidean distance for comparison
int best_dist = 1<<30; // a very high value
string best_ansi = ANSI_WHITE; // default fallback
foreach(mapping col in colors) {
int dr = r - col["r"];
int dg = g - col["g"];
int db = b - col["b"];
int dist = dr * dr + dg * dg + db * db;
if(dist < best_dist) {
best_dist = dist;
best_ansi = col["ansi"];
}
}
return best_ansi;
}
// ...
void create() {
// ...
Set(P_ANSI_TRUE_COLOR, SAVE, F_MODE_AS);
// ...
}
// ...
// The rainbow is divided into 6 segments of 10 steps each.
// For simplicity some boundary colors are repeated.
#define ANSI_RAINBOW \
/* Segment 1: Red -> Yellow */ \
ANSI_TRUE_COLOR(255,0,0) + "█" + \
ANSI_TRUE_COLOR(255,28,0) + "█" + \
ANSI_TRUE_COLOR(255,57,0) + "█" + \
ANSI_TRUE_COLOR(255,85,0) + "█" + \
ANSI_TRUE_COLOR(255,113,0) + "█" + \
ANSI_TRUE_COLOR(255,142,0) + "█" + \
ANSI_TRUE_COLOR(255,170,0) + "█" + \
ANSI_TRUE_COLOR(255,198,0) + "█" + \
ANSI_TRUE_COLOR(255,227,0) + "█" + \
ANSI_TRUE_COLOR(255,255,0) + "█" + \
/* Segment 2: Yellow -> Green */ \
ANSI_TRUE_COLOR(255,255,0) + "█" + \
ANSI_TRUE_COLOR(227,255,0) + "█" + \
ANSI_TRUE_COLOR(198,255,0) + "█" + \
ANSI_TRUE_COLOR(170,255,0) + "█" + \
ANSI_TRUE_COLOR(142,255,0) + "█" + \
ANSI_TRUE_COLOR(113,255,0) + "█" + \
ANSI_TRUE_COLOR(85,255,0) + "█" + \
ANSI_TRUE_COLOR(57,255,0) + "█" + \
ANSI_TRUE_COLOR(28,255,0) + "█" + \
ANSI_TRUE_COLOR(0,255,0) + "█" + \
/* Segment 3: Green -> Cyan */ \
ANSI_TRUE_COLOR(0,255,0) + "█" + \
ANSI_TRUE_COLOR(0,255,28) + "█" + \
ANSI_TRUE_COLOR(0,255,57) + "█" + \
ANSI_TRUE_COLOR(0,255,85) + "█" + \
ANSI_TRUE_COLOR(0,255,113) + "█" + \
ANSI_TRUE_COLOR(0,255,142) + "█" + \
ANSI_TRUE_COLOR(0,255,170) + "█" + \
ANSI_TRUE_COLOR(0,255,198) + "█" + \
ANSI_TRUE_COLOR(0,255,227) + "█" + \
ANSI_TRUE_COLOR(0,255,255) + "█" + \
/* Segment 4: Cyan -> Blue */ \
ANSI_TRUE_COLOR(0,255,255) + "█" + \
ANSI_TRUE_COLOR(0,227,255) + "█" + \
ANSI_TRUE_COLOR(0,198,255) + "█" + \
ANSI_TRUE_COLOR(0,170,255) + "█" + \
ANSI_TRUE_COLOR(0,142,255) + "█" + \
ANSI_TRUE_COLOR(0,113,255) + "█" + \
ANSI_TRUE_COLOR(0,85,255) + "█" + \
ANSI_TRUE_COLOR(0,57,255) + "█" + \
ANSI_TRUE_COLOR(0,28,255) + "█" + \
ANSI_TRUE_COLOR(0,0,255) + "█" + \
/* Segment 5: Blue -> Magenta */ \
ANSI_TRUE_COLOR(0,0,255) + "█" + \
ANSI_TRUE_COLOR(28,0,255) + "█" + \
ANSI_TRUE_COLOR(57,0,255) + "█" + \
ANSI_TRUE_COLOR(85,0,255) + "█" + \
ANSI_TRUE_COLOR(113,0,255) + "█" + \
ANSI_TRUE_COLOR(142,0,255) + "█" + \
ANSI_TRUE_COLOR(170,0,255) + "█" + \
ANSI_TRUE_COLOR(198,0,255) + "█" + \
ANSI_TRUE_COLOR(227,0,255) + "█" + \
ANSI_TRUE_COLOR(255,0,255) + "█" + \
/* Segment 6: Magenta -> Red */ \
ANSI_TRUE_COLOR(255,0,255) + "█" + \
ANSI_TRUE_COLOR(255,0,227) + "█" + \
ANSI_TRUE_COLOR(255,0,198) + "█" + \
ANSI_TRUE_COLOR(255,0,170) + "█" + \
ANSI_TRUE_COLOR(255,0,142) + "█" + \
ANSI_TRUE_COLOR(255,0,113) + "█" + \
ANSI_TRUE_COLOR(255,0,85) + "█" + \
ANSI_TRUE_COLOR(255,0,57) + "█" + \
ANSI_TRUE_COLOR(255,0,28) + "█" + \
ANSI_TRUE_COLOR(255,0,0) + "█" + \
ANSI_PLAIN
static int stty(string str)
{
if (str!=TTY_DUMB&&str!=TTY_VT100&&str!=TTY_ANSI&&str!="reset"&&str!="truecolor")
{
write("Syntax: stty dumb|vt100|ansi oder reset\n");
write("Wenn du 'ansi' verwendest kannst du mit 'stty truecolor' den ANSI True Color Modus ein- oder ausschalten.\n");
}
if(str == "reset") {
printf("\e[30;47m\e[0mDieser Text sollte lesbar sein!\n");
return 1;
}
if(str == "truecolor") {
if(Query(P_TTY) != "ansi") {
write("ANSI True Color benötigt 'stty ansi' aktiviert.");
return 1;
}
SetProp(P_ANSI_TRUE_COLOR, !QueryProp(P_ANSI_TRUE_COLOR));
int tc = QueryProp(P_ANSI_TRUE_COLOR);
write("ANSI True Color: "+(tc?"ein":"aus")+"\n");
if(tc) {
write("\nTrueColor Support aktiviert:\n" + ANSI_RAINBOW + "\n" + "Wenn du die Farben sehen kannst, dann unterstützt dein Terminal TrueColor.\n");
}
return 1;
}
write("TTY steht jetzt auf "+SetProp(P_TTY,str)+".\n");
if(str == "ansi" || str == "vt100") {
printf("Terminal Test:\n");
printf("VT100: \e[1mfett\e[0m \e[4munterstrichen\e[0m "+
"\e[5mblinkend\e[0m \e[7minvers\e[0m\n");
if(str == "ansi") {
int fg, bg;
printf("ANSI Farben und VT100 Attribute:\n");
for(fg = 30; fg <= 37; fg++) {
for(bg = 40; bg <= 47; bg++) {
printf("\e[%d;%dm\e[1m@\e[0m", fg, bg);
printf("\e[%d;%dm\e[4m@\e[0m", fg, bg);
printf("\e[%d;%dm\e[5m@\e[0m", fg, bg);
printf("\e[%d;%dm\e[7m@\e[0m", fg, bg);
}
printf("\n");
}
printf("Sollte dieser Text hier nicht richtig lesbar\n"
"sein, benutze das Kommando stty reset!\n");
} else {
SetProp(P_ANSI_TRUE_COLOR, 0); // immer ausschalten, wenn kein ansi terminal
}
}
return 1;
}
// ...
string _query_color(object ob, string prop)
{
mixed s1;
if (!interactive(ob)) return "";
s1=ob->QueryProp(prop);
// true color support
if(pointerp(s1)) {
if(sizeof(s1) != 3) {
tell_object(this_object(), "Warnung: Ungültige Farbeinstellung für "+prop+" zurückgesetzt auf 'plain'.\n");
return ANSI_PLAIN;
}
int r = s1[0];
int g = s1[1];
int b = s1[2];
if(ob->QueryProp(P_ANSI_TRUE_COLOR)) {
return ANSI_TRUE_COLOR(s1[0], s1[1], s1[2]);
} else {
return rgb_to_ansi(r, g, b);
}
}
// ...
}
// ...
int *hex_to_rgb(string hex) {
int r, g, b;
// Validate input
if (!stringp(hex) || strlen(hex) != 7 || hex[0] != '#') {
return 0; // Invalid format
}
// Convert hex to decimal values
r = to_int("0x" + hex[1..2]);
g = to_int("0x" + hex[3..4]);
b = to_int("0x" + hex[5..6]);
return ({ r, g, b });
}
/* Setzt Farbe fuer Properties P_COLOR_EXIT,
* P_COLOR_COMMUNICATION, P_COLOR_INFORM
*/
static void setcolor(string farbe, string prop, string name) {
if(farbe[0..0] == "#") {
int *hexcol = hex_to_rgb(farbe);
if(!pointerp(hexcol)) {
write(sprintf("Ungültige hex-Farbe. Format: #rrggbb\n", hexcol));
return;
}
// true color support
SetProp(prop, hexcol);
} else {
switch(farbe){
case "schwarz": SetProp(prop, 1);break;
case "rot": SetProp(prop, 2);break;
case "grün": SetProp(prop, 3);break;
// ...
default: write("Farbe "+farbe+" nicht erkannt.\n");
farbliste();
return;
}
}
}