/* virtual terminals - WA */

/* updated Zilanthius 1994 */

#include <std.h>
#include <string.h>
#include <memory.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <dos.h>
#include <pc.h>
#include <go32.h>

#include "../../config.h"
#include "../../patchlevel.h"
#include "pcdef.h"
#include "c_msdos.h"
#include "version.h"
#include "color.h"


typedef unsigned short CHAR;

#define WIDTH          80
#define LINES          ScreenRows()
#define CONSOLES       10
#define STATUS_LINE    (LINES-2)
#define PAGE           (WIDTH*LINES)
#define PAGE0          (WIDTH*(LINES-1))
#define PAGE1          (WIDTH*(LINES-2))
#define PAGE2          (WIDTH*(LINES-3))
#define SHIFT          (4*PAGE0)
#define BUFFER         25000
#define ANSI_BUFFER    20
#define LINE_BUFFER    WIDTH
#define HISTORY_BUFFER 2000
#define HISTORY_SHIFT  500
#define TAB_WIDTH      7
#define KBD_QUEUE      256


/* Keyboard Codes */

#define F1              315
#define F10             324
#define PgUp            329
#define PgDn            337
#define CrsrUp          328
#define CrsrDn          336
#define CrsrLt          331
#define CrsrRt          333
#define Home            327
#define End             335
#define AltH            291
#define AltS            287
#define AltE            274
#define AltF            289
#define AltM            306
#define AltX            301
#define AltZ            300
#define DEL             339
#define INS             338
#define SHIFT_TAB       271
#define CTRL_BS         127
#define SPACE           32
#define ESC             27
#define CTRL_M          13    /* cr */
#define CTRL_J          10    /* lf */
#define TAB             9
#define BS              8
#define CTRL_G          7

#define IS_PRINT(X)     (X >= 0x20 && X <= 0x7e)
#define IS_INT(X)       (X >= 0x30 && X <= 0x39)
#define ZERO            0x30

#define ANSI_COLON      0x3b
#define ANSI_M          0x6d
#define ANSI_BRACKET    0x5b


#define error(c) { c_errno = (c); return -1; }

struct v_con {
    int echo,connected,ready,x;
    CHAR buffer[BUFFER+1], *top, *end, *screen;
    CHAR ansi_buffer[ANSI_BUFFER+1], ansi_ptr;
    CHAR line_buffer[LINE_BUFFER+1], *line_ptr;
    CHAR color;
} console[CONSOLES];

static unsigned video_address = 0xb8000UL;

static int current, hangup;
static CHAR bar_attrib;
static CHAR scroll;
static CHAR kbd_queue[KBD_QUEUE+1];
static CHAR *queue_end = kbd_queue;
static CHAR special = 0;
static CHAR history_buffer[HISTORY_BUFFER+1];
static CHAR *current_ptr = history_buffer;
static CHAR *history_ptr = history_buffer;
static CHAR *history_end = history_buffer;


/* prototypes */

static void spawn();
static void sys_editor();
static void sys_filemanager();
static void show_help();
static void beep();
static void page();
static void reset_page();
static void scan_kbd();
static CHAR wait_key();
static CHAR poll_key();
static CHAR get_special();
static CHAR any_key();
static CHAR *my_atoi();
static CHAR select_color();
static void init_display();
static void clear_display();
static int scrolling();
static void dsp_crlf();
static void dsp_bs();
static void dsp_tab();
static void status();
static void activate_console();
static void check_inactive();
static int access_console();
void vc_listen();
static int vc_read();
static int vc_write(int,char);
int vc_accept();
int vc_select();
static int vc_close();
static void vc_echo();
void vc_init();
void vc_shutdown();
static void init_buffers();
static void close_buffers();
static void shift_buffer();
static void append_buffer();
static void buffer_display();
static void display_screen();
static void clear_line_buffer();
static void append_line_buffer();
static void clear_ansi_buffer();
static void move_ansi_buffer();
static CHAR ansi_type();
static void set_ansi();
static void ansi_color();
static void toggle_video_mode();

/************************************************************************/
/* Bell */

static void beep()
{
  sound(2500);
  usleep(400);
  sound(0);
}


/***********************************************************************/
/* DOS shell escape */
extern char *mud_lib;
static void spawn()
{
    int out,x;

    if (getenv("COMSPEC") == NULL) return;
    ScreenClear();
    ScreenSetCursor(0,0);
    out = dup(1); /* 'cause stdout is usually redirected to a log file */
    dup2(2,1);
    system(getenv("COMSPEC"));
    dup2(out,1);
    close(out);
    chdir(mud_lib);
    activate_console(current);
}


/**********************************************************************/
/* system editor */

static void sys_editor()
{
    if (getenv("MUD_EDITOR") == NULL) return;
    system(getenv("MUD_EDITOR"));
    chdir(mud_lib);
    activate_console(current);
}


/**************************************************************************/
/* system file manager */

static void sys_filemanager()
{
    if (getenv("MUD_FILEMANAGER") == NULL) return;
    system(getenv("MUD_FILEMANAGER"));
    chdir(mud_lib);
    activate_console(current);
}



/*************************************************************************/
/* command history */

static int prev_history();

static void walk_history()
{
  CHAR *ptr;

  ptr = history_buffer + HISTORY_SHIFT;
  while(*ptr++);
  current_ptr -= ptr - history_buffer;
  for(history_ptr = history_buffer; ptr < history_buffer + HISTORY_BUFFER;) {
    *history_ptr++ = *ptr++;
  }
  while(history_ptr < history_buffer + HISTORY_BUFFER) *history_ptr++ = 0;
  history_ptr = current_ptr;
  history_end = history_buffer;
  prev_history();
}

static int next_history()
{
  CHAR *ptr;

  for(ptr = history_ptr;ptr < history_buffer + HISTORY_BUFFER && *ptr++;);
  if(ptr != history_buffer + HISTORY_BUFFER && *ptr) {
    history_ptr = ptr;
    return 1;
  }
  return 0;
}

static int prev_history()
{
  CHAR *ptr;
  int flag = 1;

  if((ptr = history_ptr) != history_end) {
    do {
      while(ptr > history_end && *--ptr);
    }
    while(flag--);
    if(ptr != history_end) ptr++;
    if(*ptr) {
      history_ptr = ptr;
      return 1;
    }
  }
  return 0;
}

static void show_history()
{
  int i;
  CHAR buf[WIDTH];
  CHAR *ptr;

  for(i = 0, ptr = history_ptr; i < WIDTH && *ptr; i++) {
    if((*ptr & 0xff) == TAB) {
      do {
        buf[i++] = SPACE;
      }
      while(i < WIDTH && (i & TAB_WIDTH));
    }
    else {
      buf[i] = *ptr++;
    }
  }
  while(i < WIDTH) buf[i++] = SPACE;
  buffer_display(buf,WIDTH,PAGE0);
}

static void clean_history()
{
  CHAR *ptr = history_buffer;
  CHAR *cmd = history_ptr;
  CHAR *ptr2;

  while(ptr < cmd) {
    while(*ptr++ == *cmd++) {
      if(!*ptr && !*cmd) {
        ptr2 = ptr;
        while(ptr > history_buffer && *--ptr);
        while(ptr > history_buffer) *--ptr2 = *--ptr;
        history_end = ptr2;
        while(ptr2 > history_buffer) *--ptr2 = 0;
        goto end_clean;
      }
    }
    cmd = history_ptr;
    while(*ptr++);
  }
  end_clean:
}


static void insert_history()
{
  CHAR *ptr, ch;

  ptr = history_ptr;
  reset_page();
  if(!*ptr) {
    beep();
  }
  else {
    for(;(ch = *ptr & 0xff) && ch != CTRL_J; ptr++) {
      *queue_end++ = ch;
      if(queue_end - kbd_queue == KBD_QUEUE) {
        beep(); beep();
        break;
      }
    }
  }
}

static void append_history(key)
int key;
{
  if(current_ptr == history_buffer + HISTORY_BUFFER - 1) walk_history();

  if(IS_PRINT(key) || key == TAB)
    key |= FG_G | FG_R | FG_B | FG_I;
  else if(current_ptr == history_buffer || *(current_ptr-1) == 0)
    return;
  else
    key = 0;
  if(!(*current_ptr++ = key)) {
    history_ptr = current_ptr;
    prev_history();
    clean_history();
    show_history();
  }
}


static void bs_history()
{
  if(current_ptr == history_buffer) return;
  if(*(current_ptr-1)) *--current_ptr = 0;
}


/**************************************************************************/
/* keyboard input queue handler */


static void scan_kbd()
{
    CHAR key;
    CHAR *walk;

    while (kbhit()) {
        if ((key = getkey()) == DEL || key == CTRL_BS)
            key = BS;
        if (key == SHIFT_TAB)
            key = TAB;
        if (key == CTRL_M)
            key = CTRL_J;

        if (key >= 256) {
            switch (key) {
                case AltH:
                    hangup = 1;
                    break;
                case AltS:
                    spawn();
                    break;
                case AltE:
                    sys_editor();
                    break;
                case AltF:
                    sys_filemanager();
                    break;
                case AltM:
                    toggle_video_mode();
                    break;
                case AltX:
                    shutdowngame();
                    break;
                case AltZ:
                    show_help();
                    break;
                case PgUp: case PgDn: case CrsrUp: case CrsrDn:
                case Home: case End:
                    page(key);
                    break;
                case CrsrRt:
                    if (!next_history())
                        beep();
                    show_history();
                    break;
                case CrsrLt:
                    if (!prev_history())
                        beep();
                    show_history();
                    break;
                case INS:
                    insert_history();
                    return;
                default:
                    reset_page();
                    if (special) {
                        beep();
                    }
                    else {
                        special = key;
                    }
            } /* end switch */
        } /* end if key >= 256 */
        else {
            /* now insert key in buffer */
            reset_page();
            if ((queue_end - kbd_queue) == KBD_QUEUE) {
                beep();         /* buffer full */
            }
            else {
                *queue_end++ = key;
            }
        }
    } /* end while kbhit() */
}


static CHAR wait_key()
{
    CHAR *walk, key;

    do {
        scan_kbd();
    } while (queue_end == kbd_queue);

    /* take first char out of queue */
    key = *kbd_queue;
    for (walk = kbd_queue; walk < queue_end; walk++)
        *walk = *(walk + 1);

    queue_end--;
    return key;
}

static CHAR poll_key()
{
    scan_kbd();
    return (queue_end == kbd_queue) ? 0 : *kbd_queue;
}

static CHAR get_special()
{
    CHAR key;

    key = special;
    special = 0;
    return key;
}


static CHAR any_key()
{
    return special ? special : poll_key();
}


/**************************************************************************/
/* console handling */

static void activate_console(number)
int number;
{
    init_display(number);
    clear_display(console[number].color);
    current = number;
    if (console[current].buffer == NULL) {
        status(current, "UNAVAILABLE", 0);
        return;
    }
    display_screen(current);
    if (console[current].connected) {
        status(current, "CONNECTED", 0);
    }
    else {
        status(current, "AVAILABLE", 0);
    }
}


static void check_inactive()
{
    CHAR key;

    if (console[current].ready || console[current].connected || !any_key())
        return;

    while (key = get_special())
        if (key >= F1 && key <= F10)
            activate_console(key - F1);

    if (poll_key()) {
        wait_key();                     /* removes one key from queue */
        console[current].ready = 1;
    }
}


static int access_console(int vcon)
{
    if (vcon < 0 || vcon >= CONSOLES) {
        fprintf(stderr, "\nERROR: invalid console #%d\n", vcon);
        error(ECOMUSG);
    }
    if (console[vcon].connected)
        return 0;

    /* warning? and then raise an error? */
    fprintf(stderr, "\nWARNING: accessing inactive console #%d\n", vcon);
    error(ECOMUSG);
}


void vc_listen()
{
    init_buffers();
    activate_console(0);
    hangup = 0;
}


static int vc_write2(int vcon, char *msg, int len)
{
    if (access_console(vcon) < 0)
        error(ECOMUSG);

    while (len--)
        vc_write(vcon, *msg++);
    return 1;
}


static int vc_write(int vcon, char ch)
{
    switch (ch) {
        case '\n':
            dsp_crlf(vcon);
            break;
        case '\t':
            dsp_tab(vcon);
            break;
        case '\b':
            dsp_bs(vcon);
            break;
        case '\a':
            beep();
            break;
        default :
            if (IS_PRINT(ch) || ch == ESC) {
                append_line_buffer(vcon, (CHAR) ch);
            }
    }
    return 1;
}


static int vc_read(int vcon, char *data, int length)
{
    int count;
    CHAR key;
    char tmp[6];
    int ansi;

    if (access_console(vcon) < 0)
        error(ECOMUSG);

    if (current != vcon) {
        if (poll_key() && !console[current].connected) {
            console[current].ready = 1;
        }
        return 0;
    }
    if (hangup) {
        hangup = 0;
        return 0;
    }
    for (count = 0; count < length && poll_key(); count++) {
        key = wait_key();

        if (console[vcon].echo) {
            switch (key) {
                case  CTRL_J:
                    strcpy(tmp, "\n");
                    break;
                case  BS:
                    strcpy(tmp, "\b \b");
                    break;
                default:
                    if (!IS_PRINT(key) && key != ESC && key != CTRL_G) {
                        sprintf(tmp, "<%d>", key);
                    }
                    else {
                        sprintf(tmp, "%c", key);
                    }
            }
            vc_write2(vcon, tmp, strlen(tmp));

            if (IS_PRINT(key) || key == CTRL_J || key == TAB) {
                append_history(key);
            }
            else if (key == BS) {
                bs_history();
            }
        }
        *data++ = (char)key;
    }
    return count;
}


int vc_accept()
{
    int vcon;

    check_inactive();

    for (vcon = 0; vcon < CONSOLES; vcon++)
        if (console[vcon].ready) {
            console[vcon].connected = console[vcon].echo = 1;
            console[vcon].ready = 0;
            status(vcon, "CONNECTED", 0);
            return vcon;
        }

/*
    error(ECOMFLW);
    change to non fatal error code */
    error(EAGAIN);
}


/*ARGSUSED*/
int vc_select(mask, timeout, new_con)
int *mask;
struct timeval *timeout;
int *new_con;
{
    int vcon, copy, key;

    /*
     * get_special can't return more than one special
     * without re-reading the keyboard - hohum
     */
    while (key = get_special())
        if (key >= F1 && key <= F10)
            activate_console(key-F1);

    if (console[current].buffer && poll_key()) {
        /* look for new connections */
        if (*new_con && !console[current].connected) {
            console[current].ready = 1;
            *mask = 0;
            return 1;
        }
        *new_con = 0;

        /* see if there is input from current console (a connection) */
        if (console[current].connected && ((*mask >> current) & 1)) {
            *mask = 1 << current;
            return 1;
        }
    }
    /* to-do: read comm1.c and decide how to return.. */
    error(EINTR);
}


static int vc_close(int vcon)
{
    if (access_console(vcon) < 0)
        error(ECOMUSG);

    console[vcon].connected = 0;
    status(vcon, "AVAILABLE",0);
    return 0;
}


static void vc_echo(int vcon, int mode)
{
    if (access_console(vcon) >= 0)
        console[vcon].echo = mode;
}


void vc_init(CLASS_DEF *cd)
{
    vc_listen();
    cd->max_sess = CONSOLES;
    cd->close = vc_close;
    cd->read = vc_read;
    cd->write = vc_write;
    cd->echo = vc_echo;
}


void vc_shutdown()
{
    printf("\n[DETACHED]\n");
    ScreenSetCursor(0,0);
    ScreenClear();
}


/*************************************************************************/


static CHAR *my_atoi(value,str)
CHAR *value;
CHAR *str;
{
    while (*str && !IS_INT(*str))
        str++;
    for (*value = 0; IS_INT(*str); str++)
        *value = *value*10 + *str-ZERO;
    return str;
}


/*********************************************************************/
/* set color attributes */

static CHAR select_color(color)
char *color;
{
  CHAR attrib = 0;


  do {
    switch(*color) {
      case 'r':
       attrib |= FG_R;
      break;

      case 'g':
       attrib |= FG_G;
      break;

      case 'b':
       attrib |= FG_B;
      break;

      case 'R':
       attrib |= BG_R;
      break;

      case 'G':
       attrib |= BG_G;
      break;

      case 'B':
       attrib |= BG_B;
      break;

      case 'i':
       attrib |= FG_I;
      break;

      case 'f':
       attrib |= BLINK;
      break;
    }
  }
  while(*++color);
  return attrib;
}


/********************************************************************/
/* initialise */

static void init_display(int vcon)
{
    char mono[] = "mono";

    if (getenv("MUD_MODE") != NULL && strcmp(getenv("MUD_MODE"), mono) == 0) {
        video_address = 0xb0000UL;
        console[vcon].color = 0x0700;
        bar_attrib = 0x70;
    }
    else {
        video_address = 0xb8000UL;
        if (getenv("MUD_SCREEN") != NULL) {
            console[vcon].color = select_color(getenv("MUD_SCREEN"));
        }
        else {
            console[vcon].color = FG;
        }
        if (getenv("MUD_BAR") != NULL) {
            bar_attrib = select_color(getenv("MUD_BAR")) >> 8;
        }
        else {
            bar_attrib = BG >> 8;
        }
   }
}

static void init_buffers()
{
    int con = CONSOLES;
    int i;

    while (con--) {
        console[con].connected = console[con].ready = console[con].x = 0;
        console[con].top = console[con].screen = console[con].buffer;
        console[con].end = console[con].buffer + PAGE1;
        init_display(con);
        for (i = 0; i < BUFFER; i++) {
            console[con].buffer[i] = (SPACE | console[con].color);
        }
        clear_line_buffer(con);
    }

    memset(history_buffer, 0, sizeof(history_buffer));
    show_history();
}


static void shift_buffer(int vcon)
{
  int i;

  if ((console[vcon].end - console[vcon].buffer) >= BUFFER - PAGE) {
    for (i = 0; i + SHIFT < BUFFER && console[vcon].buffer[i+SHIFT]; i++) {
      console[vcon].buffer[i] = console[vcon].buffer[i+SHIFT];
    }
    while(i < BUFFER)
      console[vcon].buffer[i++] = ((SPACE & 0xff) | console[vcon].color);
    console[vcon].end -= SHIFT;
    console[vcon].top = console[vcon].end - PAGE2;
    console[vcon].buffer[BUFFER] = 0;
  }
}


static void append_buffer(int vcon, CHAR *ptr)
{
  for (;*ptr; ptr++) {
    shift_buffer(vcon);
    *console[vcon].end++ = *ptr;
  }
  while ((console[vcon].end - console[vcon].buffer)%WIDTH) {
    shift_buffer(vcon);
    *console[vcon].end++ = (SPACE | console[vcon].color);
  }
  console[vcon].top = console[vcon].end - PAGE2;
}


static int scrolling() {
  if(scroll) {
    scroll = (scroll != '*') ? '*' : '#';
    ScreenPutChar(scroll, bar_attrib, WIDTH - 2, STATUS_LINE);
    return 1;
  }
  return 0;
}


static void dsp_crlf(int vcon)
{
  if (!console[vcon].line_buffer[0]) {
    console[vcon].line_buffer[0] = (SPACE | console[vcon].color);
  }
  append_buffer(vcon, console[vcon].line_buffer);
  clear_line_buffer(vcon);
  if (current == vcon) {
    if (!scrolling())
      display_screen(vcon);
  }
}


static void dsp_bs(con)
int con;
{
  if (!console[con].x) {
    beep();
    return;
  }
  *--console[con].line_ptr = 0;
  if (con == current) ScreenSetCursor(STATUS_LINE-1,--console[con].x);
}


static void dsp_tab(con)
int con;
{
    int length;

    for(length = 0; console[con].line_buffer[length]; length++);
    if(length >= WIDTH-2-TAB_WIDTH) {
      dsp_crlf(con);
    }
    else {
      do {
        append_line_buffer(con,SPACE);
      } while (++length & TAB_WIDTH);
    }
}

static void display_line_buffer(con)
int con;
{
    int i;
    CHAR buf[LINE_BUFFER];

    for (i = 0; i < LINE_BUFFER && console[con].line_buffer[i]; i++) {
        buf[i] = console[con].line_buffer[i];
    }
    console[con].x = i%WIDTH;
    ScreenSetCursor(STATUS_LINE - 1, console[con].x);
    while (i < LINE_BUFFER)
        buf[i++] = SPACE | console[con].color;
    buffer_display(buf,LINE_BUFFER,PAGE2);
}


static void display_screen(con)
int con;
{
  buffer_display(console[con].top,PAGE2,0);
  console[con].x = 0;
  ScreenSetCursor(STATUS_LINE-1,0);
  display_line_buffer(con);
  status(con,((console[con].connected) ? "CONNECTED" : "AVAILABLE"),0);
}


static void clear_line_buffer(con)
int con;
{
  int i = LINE_BUFFER;

  console[con].line_ptr = console[con].line_buffer;
  while(i--) console[con].line_buffer[i] = 0;
  console[con].x = 0;
}


static void append_line_buffer(con,ch)
int con;
CHAR ch;
{
  if(!console[con].ansi_ptr) {
    if(ch == ESC) {
      console[con].ansi_buffer[console[con].ansi_ptr++] = ESC;
      return;
    }
    else {
      *console[con].line_ptr++ = ch | console[con].color;
    }
  }
  else {
    if(console[con].ansi_ptr + 1 == ANSI_BUFFER) {
      move_ansi_buffer(con);
      return;
    }
    console[con].ansi_buffer[console[con].ansi_ptr++] = ch;
    switch(console[con].ansi_ptr) {
      case 2:
     if(ch != ANSI_BRACKET) {
       move_ansi_buffer(con);
     }
     break;
      case 3:
     if(!IS_INT(ch)) {
       move_ansi_buffer(con);
     }
     break;
      default:
     if(!IS_INT(ch) && ch != ANSI_COLON && ch != ANSI_M) {
       move_ansi_buffer(con);
     }
     else if(ch == ANSI_M) {
       set_ansi(con,console[con].ansi_buffer);
       clear_ansi_buffer(con);
     }
    }
    return;
  }
  if(console[con].x >= WIDTH-1) {
    append_buffer(con,console[con].line_buffer);
    clear_line_buffer(con);
    if(con == current) {
      ScreenSetCursor(STATUS_LINE-1,0);
      if(!scrolling()) display_screen(con);
    }
  }
  else if(current == con) {
    if(!scroll) {
      ScreenPutChar(ch,console[con].color>>8,console[con].x,STATUS_LINE-1);
    }
    ScreenSetCursor(STATUS_LINE-1,++console[con].x);
  }
}


/* ansi simulation */

static void clear_ansi_buffer(con)
int con;
{
  int i = ANSI_BUFFER;

  console[con].ansi_ptr = 0;
  while(i--) console[con].ansi_buffer[i] = 0;
}


static void move_ansi_buffer(con)
int con;
{
  CHAR *ptr;
  for(ptr = console[con].ansi_buffer;*ptr; ptr++, console[con].x++) {
    if(console[con].x >= WIDTH-1) {
      append_buffer(con,console[con].line_buffer);
      clear_line_buffer(con);
    }
    *console[con].line_ptr++ = *ptr | console[con].color;
  }
  if(current == con) ScreenSetCursor(STATUS_LINE-1,console[con].x);
  clear_ansi_buffer(con);
  if(current == con) display_screen(con);
}


static CHAR ansi_type(str)
CHAR *str;
{
  while(*str == ANSI_COLON || IS_INT(*str)) *str++;
  return *str;
}


static void set_ansi(con,ptr)
int con;
CHAR *ptr;
{
  CHAR attrib;

  if(*ptr == ESC && *(ptr+1) == ANSI_BRACKET && IS_INT(*(ptr+2))) {
    switch(ansi_type(ptr+2)) {
      case ANSI_M:
     *ptr++;
     while(*ptr++ != ANSI_M) {
       ptr = my_atoi(&attrib,ptr);
       ansi_color(con,attrib);
     }
      break;
    }
  }
}


static void ansi_color(con,attrib)
int con;
CHAR attrib;
{
    switch(attrib) {
      /* OFF */
      case 0:
     init_display(con);
      break;

      /* BOLD */
      case 1:
     if(getenv("MUD_BOLD") != NULL) {
       console[con].color = select_color(getenv("MUD_BOLD"));
     }
     else {
       console[con].color |= FG_I;
     }
      break;

      /* FAINT */
      case 2:
     console[con].color &= ~FG_I;
      break;

      /* underline mono display only */
      case 4:
     console[con].color |= FG_B;
      break;

      /* BLINK */
      case 5:
     console[con].color |= BLINK;
      break;

      /* REVERSE BLINK */
      case 6:
     console[con].color = ~console[con].color;
     console[con].color |= BLINK;
      break;

      /* REVERSE */
      case 7:
     console[con].color = ~console[con].color;
     console[con].color &= ~BLINK;
      break;

      /* foreground *
      case 30: /* black */
     console[con].color &= ~FG_R & ~FG_G & ~FG_B;
      break;

      case 31: /* red */
     console[con].color &= ~FG_R & ~FG_G & ~FG_B;
     console[con].color |= FG_R;
      break;

      case 32: /* green */
     console[con].color &= ~FG_R & ~FG_G & ~FG_B;
     console[con].color |= FG_G;
      break;

      case 33: /* yellow */
     console[con].color &= ~FG_R & ~FG_G & ~FG_B;
     console[con].color |= FG_R | FG_G;
      break;

      case 34: /* blue */
     console[con].color &= ~FG_R & ~FG_G & ~FG_B;
     console[con].color |= FG_B;
      break;

      case 35: /* magenta */
     console[con].color &= ~FG_R & ~FG_G & ~FG_B;
     console[con].color |= FG_R | FG_B;
      break;

      case 36: /* cyan */
     console[con].color &= ~FG_R & ~FG_G & ~FG_B;
     console[con].color |= FG_G | FG_B;
      break;

      case 37: /* white */
     console[con].color |= FG_R | FG_G | FG_B;
      break;

      /* Background */
      case 40: /* black */
     console[con].color &= ~BG_R & ~BG_G & ~BG_B;
      break;

      case 41: /* red */
     console[con].color &= ~BG_R & ~BG_G & ~BG_B;
     console[con].color |= BG_R;
      break;

      case 42: /* green */
     console[con].color &= ~BG_R & ~BG_G & ~BG_B;
     console[con].color |= BG_G;
      break;

      case 43: /* yellow */
     console[con].color &= ~BG_R & ~BG_G & ~BG_B;
     console[con].color |= BG_R | BG_G;
      break;

      case 44: /* blue */
     console[con].color &= ~BG_R & ~BG_G & ~BG_B;
     console[con].color |= BG_B;
      break;

      case 45: /* magenta */
     console[con].color &= ~BG_R & ~BG_G & ~BG_B;
     console[con].color |= BG_R | BG_B;
      break;

      case 46: /* cyan */
     console[con].color &= ~BG_R & ~BG_G & ~BG_B;
     console[con].color |= BG_G | BG_B;
      break;

      case 47: /* white */
     console[con].color |= BG_R | BG_G | BG_B;
      break;

    }
}



/* clear screen */

static void clear_display(color)
CHAR color;
{
    int i;
    CHAR buf[PAGE1];

    for (i = 0; i < PAGE1; i++)
        buf[i] = SPACE | color;
    dosmemput(buf, PAGE1*2, video_address);
}


/* display buffer */

static void buffer_display(buf, length, screen_offset)
CHAR *buf;
int length;
int screen_offset;
{
    if (length + screen_offset > PAGE)
        length = PAGE - screen_offset;

    dosmemput(buf, length*2, video_address + 2*screen_offset);
}


/***************************************************************************/
/* status bar */

static void status(number, st_name, scroll)
int number,scroll;
short int *st_name;
{
  char msg[WIDTH];
  int walk;
  char mode[7];

  sprintf(msg,
    " #%d  [%s]%s   MSDOS %s Amylaar%s%s %s Driver (Help: Alt-Z) ",
    number+1,
    st_name,
    scroll ? "SCROLLED" : "",
    MSDOS_VERSION,
    GAME_VERSION,
    PATCH_LEVEL LOCAL_LEVEL,

    /* if X-Comp was written as X-Compat the below would cause
        something to malfunction.
        stack would get overwritten and other nice things */
#ifdef COMPAT_MODE
    strcpy(mode,"Compat"));
#else
#ifdef NATIVE_MODE
    strcpy(mode,"Native"));
#else
    strcpy(mode,"X-Comp"));
#endif /* NATIVE_MODE */
#endif /* COMPAT_MODE */

  for (walk = 0; msg[walk]; walk++) {
      ScreenPutChar(msg[walk],bar_attrib,walk,STATUS_LINE);
  }
  while(walk < WIDTH) {
    ScreenPutChar(SPACE,bar_attrib,walk++,STATUS_LINE);
  }
}


/**************************************************************************/
/* help */

static void show_help()
{
  int attrib, i, x_pos, y_pos;
  char help[] = \
  "                          -=[ Help ]=-\n\n"
  "                       Alt-h........Hang Up\n"\
  "                       Alt-s........Spawn\n"\
  "                       Alt-e........Dos Editor\n"\
  "                       Alt-f........Dos File Manager\n"\
  "                       Alt-m........Toggle Video Mode\n"\
  "                       Alt-x........Shutdown Game\n"\
  "                       Alt-z........Help\n"\
  "                       F1-F10.......Virtual Consoles\n"\
  "                       <home>.......Top of Screen Buffer\n"\
  "                       <end>........End of Screen Buffer\n"\
  "                       < ->, <- >...History Buffer\n"\
  "                       <Insert>.....Insert Current History Line\n"\
  "Press Key to Continue.";

  if (getenv("MUD_SCREEN") != NULL) {
    attrib = select_color(getenv("MUD_SCREEN"));
  }
  else {
    attrib = FG;
  }
  clear_display(attrib);
  for(i = 0, x_pos = 0, y_pos = 0; i < strlen(help); i++) {
    if(help[i] == '\n') {
      y_pos++; x_pos = 0;
    }
    else {
      ScreenPutChar(help[i],attrib >> 8,x_pos++,y_pos);
    }
  }
  ScreenSetCursor(y_pos,x_pos++);
  wait_key();
  activate_console(current);
}


/* page movement */

static void page(key)
CHAR key;
{
    CHAR *ptr;

    if (!scroll) scroll = '#';

    ScreenPutChar(scroll, bar_attrib, WIDTH - 2, STATUS_LINE);

    ptr = console[current].screen;
    switch (key) {
        case PgUp:
            if (ptr - PAGE1 < console[current].buffer)
                ptr = console[current].buffer;
            else
                ptr -= PAGE1;
            break;

        case CrsrUp:
            if (ptr - WIDTH < console[current].buffer)
                ptr = console[current].buffer;
            else
                ptr -= WIDTH;
            break;

        case PgDn:
            if (ptr + PAGE1 > console[current].top)
                ptr = console[current].top;
            else
                ptr += PAGE1;
            break;

        case CrsrDn:
            if (ptr + WIDTH > console[current].top)
                ptr = console[current].top;
            else
                ptr += WIDTH;
            break;

        case End:
            ptr = console[current].top;
            break;

        case Home:
            ptr = console[current].buffer;
            break;
    }

    console[current].screen = ptr;
    buffer_display(ptr, PAGE1, 0);
}


/* reset page */

static void reset_page()
{
    scroll = 0;
    ScreenPutChar(SPACE,bar_attrib,WIDTH-2,STATUS_LINE);

    if (console[current].screen != console[current].top) {
        console[current].screen = console[current].top;
        display_screen(current);
    }
}


/********************************************************************/
/* video mode */

/* from gppconio src */

enum text_modes { LASTMODE=-1, BW40=0, C40, BW80, C80, MONO=7, C4350=64 };

void toggle_video_mode()
{
    union REGS regs;
    static int mode = C4350;
    int mode_to_set;

    regs.h.ah = 0x12;
    regs.h.bl = 0x10;
    regs.h.bh = 0xff;
    int86(0x10, &regs, &regs);
    if(regs.h.bh == 0xff) return; /* mono */

    if(mode == C4350)
      mode = C80;
    else
      mode = C4350;
    mode_to_set = mode;

    if (mode == C4350)
        /*
         * just set mode 3 and load 8x8 font, idea taken
         * (and code translated from Assembler to C)
         * form Csaba Biegels stdvga.asm
         */
      mode_to_set = 0x03;
    regs.h.ah = 0x00; /* set mode */
    regs.h.al = mode_to_set;
    int86(0x10, &regs, &regs);

   /*
    * enable cursor size emulation, see Ralf Browns
    * interrupt list
    */
    regs.h.ah = 0x12;
    regs.h.bl = 0x34;
    regs.h.al = 0x00; /* 0: enable (1: disable) */
    int86(0x10, &regs, &regs);
    if (mode == C4350)
    {
      /* load 8x8 font */
      regs.x.ax = 0x1112;
      regs.x.bx = 0;
      int86(0x10, &regs, &regs);
    }
    display_screen(current);
}
