/* Sarien - A Sierra AGI resource interpreter engine * Copyright (C) 1999,2001 Stuart George and Claudio Matsuoka * * $Id: console.c,v 1.40 2001/11/04 16:41:36 cmatsuoka Exp $ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; see docs/COPYING for further details. */ #include "sarien.h" #ifdef USE_CONSOLE /* * The console has been added to sarien in version 0.4.9. The main * interpreter engine has not been designed to have a console, and a few * kludges were needed to make the console work. First of all, the main * interpreter loop works at cycle level, not instruction level. To keep * the illusion that the console is threaded, a console updating function * is called from loops inside instructions such as get.string(). */ #include #include #include #include #include #include "agi.h" #include "graphics.h" #include "sprite.h" #include "text.h" #include "keyboard.h" #include "opcodes.h" #include "console.h" #define CONSOLE_LINES_ONSCREEN 20 #define CONSOLE_PROMPT "$" #define CONSOLE_CURSOR_HOLLOW 1 #define CONSOLE_CURSOR_SOLID 2 #define CONSOLE_CURSOR_EMPTY 3 #define CONSOLE_COLOR 14 #define CONSOLE_SCROLLUP_KEY KEY_PGUP #define CONSOLE_SCROLLDN_KEY KEY_PGDN #define CONSOLE_START_KEY KEY_HOME #define CONSOLE_END_KEY KEY_END #define CONSOLE_INPUT_SIZE 39 struct console_command { struct console_command *next; char *cmd; char *dsc; void (*handler)(void); }; struct sarien_console console; struct sarien_debug debug; static struct console_command *ccmd_head = NULL; static UINT8 has_console; static UINT8 console_input = 0; static char *_p0, *_p1, *_p2, *_p3, *_p4, *_p5; /* FIXME: array */ static char _p[80]; static int _pn; /* * Console command parsing */ static UINT8 console_parse (char *b) { struct console_command *d; UINT8 i; for (; *b && *b == ' '; b++) {} /* eat spaces */ for (; *b && b[strlen(b) - 1] == ' '; b[strlen(b) - 1] = 0) {} if (!*b) return 0; /* get or set flag/var values */ if ((b[0] == 'f' || b[0] == 'v') && isdigit (b[1])) { char *e; int f = (int)strtoul (&b[1], &e, 10); if (*e == 0) { if (f >= 0 && f <= 255) { switch (b[0]) { case 'f': report (getflag (f) ? "TRUE\n" : "FALSE\n"); break; case 'v': report ("%3d\n", getvar (f)); break; } return 0; } return -1; } else if (*e == '=') { int n = (int)strtoul (e + 1, NULL, 0); switch (b[0]) { case 'f': setflag (f, !!n); break; case 'v': setvar (f, n); break; } return 0; } } /* tokenize the input line */ if (strchr (b, ' ')) strcpy (_p, strchr (b, ' ') + 1); _p0 = strtok (b, " "); _p1 = strtok (NULL, " "); _p2 = strtok (NULL, " "); _p3 = strtok (NULL, " "); _p4 = strtok (NULL, " "); _p5 = strtok (NULL, " "); /* set number of parameters. ugh, must rewrite this later */ _pn = 5; if (!_p5) _pn = 4; if (!_p4) _pn = 3; if (!_p3) _pn = 2; if (!_p2) _pn = 1; if (!_p1) _pn = 0; _D (_D_WARN "number of parameters: %d", _pn); for (d = ccmd_head; d; d = d->next) { if (!strcmp (_p0, d->cmd) && d->handler) { d->handler (); return 0; } } for (i = 0; logic_names_cmd[i].name; i++) { if (!strcmp (_p0, logic_names_cmd[i].name)) { UINT8 p[16]; if (_pn != logic_names_cmd[i].num_args) { report ("AGI command wants %d arguments\n", logic_names_cmd[i].num_args); return 0; } p[0] = _p1 ? (char)strtoul (_p1, NULL, 0) : 0; p[1] = _p2 ? (char)strtoul (_p2, NULL, 0) : 0; p[2] = _p3 ? (char)strtoul (_p3, NULL, 0) : 0; p[3] = _p4 ? (char)strtoul (_p4, NULL, 0) : 0; p[4] = _p5 ? (char)strtoul (_p5, NULL, 0) : 0; _D (_D_WARN "ccmd: %s %d %d %d", logic_names_cmd[i].name, p[0], p[1], p[2]); execute_agi_command (i, p); return 0; } } return -1; } /* * Console commands */ static void ccmd_help () { struct console_command *d; if (!_p1) { report ("Command Description\n"); report ("------- --------------------------------\n"); for (d = ccmd_head; d; d = d->next) report ("%-8s%s\n", d->cmd, d->dsc); return; } for (d = ccmd_head; d; d = d->next) { if (!strcmp (d->cmd, _p1) && d->handler) { report ("%s\n", d->dsc); return; } } report ("Unknown command or no help available\n"); return; } static void ccmd_ver () { report (VERSION "\n"); return; } static void ccmd_crc () { report ("0x%05x\n", game.crc); return; } static void ccmd_load () { stop_sound (); if (game.state >= STATE_LOADED) { report ("AGI game already loaded.\n"); return; } report ("Opening \"%s\".\n", _p1); if (agi_detect_game (_p1) == err_OK) { game.state = STATE_RUNNING; report ("AGI game successfully loaded.\n"); agi_init (); console_prompt (); return; } report ("AGI game load failed.\n"); return; } static void ccmd_quit () { deinit_video (); deinit_machine (); exit (0); } #if 0 static void ccmd_exec () { if (game.state < STATE_LOADED) { report ("No game loaded.\n"); return; } if (game.state >= STATE_RUNNING) { report ("Game already running.\n"); return; } report ("Executing AGI game.\n"); game.state = STATE_RUNNING; } #endif #ifdef USE_HIRES static void ccmd_hires () { if (_pn != 1 || (strcmp (_p1, "on") && strcmp (_p1, "off"))) { report ("Usage: hires on|off\n"); return; } opt.hires = !strcmp (_p1, "on"); erase_both (); show_pic (); blit_both (); return; } #endif static void ccmd_agiver () { int ver, maj, min; ver = agi_get_release (); maj = (ver >> 12) & 0xf; min = ver & 0xfff; report (maj == 2 ? "%x.%03x\n" : "%x.002.%03x\n", maj, min); return; } static void ccmd_flags () { int i, j; report (" "); for (j = 0; j < 10; j++) report ("%d ", j); report ("\n"); for (i = 0; i < 255;) { report ("%3d ", i); for (j = 0; j < 10; j++, i++) { report ("%c ", getflag (i) ? 'T' : 'F'); } report ("\n"); } return; } static void ccmd_vars () { int i, j; for (i = 0; i < 255;) { for (j = 0; j < 5; j++, i++) { report ("%03d:%3d ", i, getvar (i)); } report ("\n"); } return; } #if 0 static void ccmd_say () { setflag (F_entered_cli, TRUE); dictionary_words (_p); } static void ccmd_inv () { unsigned int i, j; for (j = i = 0; i < game.num_objects; i++) { if (object_get_location (i) == EGO_OWNED) { report ("%3d]%-16.16s", i, object_name (i)); if (j % 2) report ("\n"); j++; } } if (j == 0) { report ("none\n"); return; } if (j % 2) report ("\n"); } #endif static void ccmd_objs () { unsigned int i; for (i = 0; i < game.num_objects; i++) { report ("%3d]%-24s(%3d)\n", i, object_name (i), object_get_location (i)); } return; } static void ccmd_opcode () { if (_pn != 1 || (strcmp (_p1, "on") && strcmp (_p1, "off"))) { report ("Usage: opcode on|off\n"); return; } debug.opcodes = !strcmp (_p1, "on"); return; } static void ccmd_logic0 () { if (_pn != 1 || (strcmp (_p1, "on") && strcmp (_p1, "off"))) { report ("Usage: logic0 on|off\n"); return; } debug.logic0 = !strcmp (_p1, "on"); return; } static void ccmd_step () { debug.enabled = 1; if (_pn == 0) { debug.steps = 1; return; } debug.steps = strtoul (_p1, NULL, 0); return; } static void ccmd_debug () { debug.enabled = 1; debug.steps = 0; return; } static void ccmd_cont () { debug.enabled = 0; debug.steps = 0; return; } /* * Register console commands */ static void console_cmd (char *cmd, char *dsc, void (*handler)(void)) { struct console_command *c, *d; c = malloc (sizeof (struct console_command)); c->cmd = strdup (cmd); c->dsc = strdup (dsc); c->handler = handler; c->next = NULL; if (ccmd_head == NULL) { ccmd_head = c; return; } for (d = ccmd_head; d->next; d = d->next) {} d->next = c; } /* Console reporting */ /* A slightly modified strtok() for report() */ static char *get_token (char *s, char d) { static char *x; char *n, *m; if (s) x = s; m = x; n = strchr (x, d); if (n) { *n = 0; x = n + 1; } else { x = strchr (x, 0); } return m; } static void build_console_lines (int n) { int i, j, y1; clear_console_screen (GFX_HEIGHT - n * 10); for (j = CONSOLE_LINES_ONSCREEN - n; j < CONSOLE_LINES_ONSCREEN; j++) { i = console.first_line + j; print_text_console (console.line[i], 0, j, strlen (console.line[i]) + 1, CONSOLE_COLOR, 0); } y1 = console.y - n * 10; if (y1 < 0) y1 = 0; /* CM: * This will cause blinking when using menus+console, but this * function is called by report() which can be called from any * point with or without sprites placed. If we protect the * flush_block() call with redraw/release sprites cloning will * happen when activating the console. This could be fixed by * keeping a control flag in redraw/release to not do the * actions twice, but I don't want to do that -- not yet. */ flush_block (0, y1, GFX_WIDTH - 1, console.y); } /* * Public functions */ int console_init () { console_cmd ("agiver", "Show emulated Sierra AGI version", ccmd_agiver); console_cmd ("cont", "Resume interpreter execution", ccmd_cont); console_cmd ("debug", "Stop interpreter execution", ccmd_debug); console_cmd ("crc", "Show AGI game CRC", ccmd_crc); #if 0 console_cmd ("exec", "Execute loaded AGI game", ccmd_exec); #endif console_cmd ("flags", "Dump all AGI flags", ccmd_flags); console_cmd ("help", "List available commands", ccmd_help); #ifdef USE_HIRES console_cmd ("hires", "Turn hi-res mode on/off", ccmd_hires); #endif console_cmd ("logic0", "Turn logic 0 debugging on/off", ccmd_logic0); console_cmd ("load", "Load AGI game", ccmd_load); console_cmd ("objs", "List all objects and locations", ccmd_objs); console_cmd ("opcode", "Turn opcodes on/off in debug", ccmd_opcode); console_cmd ("quit", "Quit the interpreter", ccmd_quit); #if 0 console_cmd ("inv", "List current inventory", ccmd_inv); console_cmd ("say", "Pass argument to the AGI parser", ccmd_say); #endif console_cmd ("step", "Execute the next AGI instruction", ccmd_step); console_cmd ("vars", "Dump all AGI variables", ccmd_vars); console_cmd ("ver", "Show interpreter version", ccmd_ver); console.active = 1; console.input_active = 1; console.index = 0; console.max_y = 150; console.y = console.max_y; console.first_line = CONSOLE_LINES_BUFFER - CONSOLE_LINES_ONSCREEN; debug.enabled = 0; debug.opcodes = 0; debug.logic0 = 1; debug.priority = 0; has_console = 1; return err_OK; } static void build_console_layer () { build_console_lines (console.max_y / 10); } void report (char *message, ...) { char x[512], y[512], z[64], *m, *n; va_list args; int i, s, len; va_start (args, message); #ifdef HAVE_VSNPRINTF vsnprintf (y, 510, (char*)message, args); #else vsprintf (y, (char*)message, args); #endif va_end (args); if (!has_console) { #ifndef NATIVE_MACOSX fprintf (stderr, "%s", y); #endif return; } if (console_input) { strcpy (z, console.line[CONSOLE_LINES_BUFFER - 1]); strcpy (x, ">"); } else { strcpy (x, console.line[CONSOLE_LINES_BUFFER - 1]); } strcat (x, y); len = 40; m = n = word_wrap_string (x, &len); for (s = 1; *n; n++) if (*n == '\n') s++; /* Scroll console */ for (i = 0; i < (s - 1); i++) free (console.line[i]); for (i = 0; i < (CONSOLE_LINES_BUFFER - (s - 1)); i++) console.line[i] = console.line[i + (s - 1)]; console.line[CONSOLE_LINES_BUFFER - s] = strdup (get_token (m, '\n')); for (i = 1; i < s; i++) { n = get_token (NULL, '\n'); console.line[CONSOLE_LINES_BUFFER - s + i] = strdup (n); } console.first_line = CONSOLE_LINES_BUFFER - CONSOLE_LINES_ONSCREEN; if (console_input) { free (console.line[CONSOLE_LINES_BUFFER - 1]); console.line[CONSOLE_LINES_BUFFER - 1] = strdup (z); } /* Build layer */ if (console.y) { if (s > 1) build_console_layer (); else build_console_lines (1); } do_update (); } void console_cycle () { static int old_y = 0; static int blink = 0; static char cursor[] = " "; /* If no game has been loaded, keep the console visible! */ if (game.state < STATE_RUNNING) { console.active = 1; console.count = 10; } /* Initial console auto-hide timer */ if (console.count > 0) console.count--; if (console.count == 0) { console.active = 0; console.count = -1; } if (console.active) { if (console.y < console.max_y) console.y += 15; else console.y = console.max_y; } else { console.count = -1; if (console.y > 0) console.y -= 15; else console.y = 0; } /* console shading animation */ if (old_y != console.y) { int y = console.y; if (old_y > console.y) { /* going up */ y = old_y; } flush_block (0, 0, GFX_WIDTH - 1, y); old_y = console.y; } blink++; blink %= 16; if (console.input_active) { *cursor = blink < 8 ? CONSOLE_CURSOR_SOLID : CONSOLE_CURSOR_EMPTY; } else { *cursor = CONSOLE_CURSOR_HOLLOW; } if (console.y > 0 && console.first_line == CONSOLE_LINES_BUFFER - CONSOLE_LINES_ONSCREEN) { SINT16 y1 = console.y - 10, y2 = console.y - 1; if (y1 < 0) y1 = 0; if (y2 < 0) y2 = 0; print_text_console (cursor, (1 + console.index), 19, 2, CONSOLE_COLOR, 0); flush_block ((1 + console.index) * 8, y1, (1 + console.index) * 8 + 7, y2 - 1); } do_update (); } /* Return TRUE if key was handled */ int console_keyhandler (int k) { static char buffer[CONSOLE_INPUT_SIZE]; SINT16 y1,y2; char m[2]; /* Right button switches console on/off*/ if (k == BUTTON_RIGHT) k = CONSOLE_ACTIVATE_KEY; if (!console.active) { if (k == CONSOLE_ACTIVATE_KEY) { console.active = 1; return TRUE; } return FALSE; } if (!console.input_active) { if (k == CONSOLE_SWITCH_KEY) { console.input_active = 1; return TRUE; } if (k == CONSOLE_ACTIVATE_KEY) { console.active = 0; return TRUE; } return FALSE; } y1 = console.y - 10; y2 = console.y - 1; if (y1 < 0) y1 = 0; if (y2 < 0) y2 = 0; /* Ignore left button in console */ if (k == BUTTON_LEFT) return TRUE; if (k) { if (k != KEY_ENTER && console.first_line != CONSOLE_LINES_BUFFER - CONSOLE_LINES_ONSCREEN) { console.first_line = CONSOLE_LINES_BUFFER - CONSOLE_LINES_ONSCREEN; build_console_layer (); } console.count = -1; } switch (k) { case KEY_ENTER: console_lock (); console.index = 0; report ("\n"); if (console_parse (buffer) != 0) report ("What? Where?\n"); buffer[0] = 0; console_prompt (); break; case KEY_BACKSPACE: if (!console.index) break; console.line[CONSOLE_LINES_BUFFER-1][console.index]=0; *m = CONSOLE_CURSOR_EMPTY; print_text_console (m, (console.index+1), 19, 2, CONSOLE_COLOR, 0); flush_block ((console.index+1)*8, y1, (console.index+1)*8+7, y2); console.index--; buffer[console.index] = 0; break; case CONSOLE_ACTIVATE_KEY: console.active = !console.active; if (console.active) build_console_layer (); break; case CONSOLE_SWITCH_KEY: console.count = -1; if (console.y) console.input_active = !console.input_active; break; case CONSOLE_SCROLLUP_KEY: console.count = -1; if (!console.y) break; if (console.first_line > (CONSOLE_LINES_ONSCREEN / 2)) console.first_line -= CONSOLE_LINES_ONSCREEN / 2; else console.first_line = 0; build_console_layer (); break; case CONSOLE_SCROLLDN_KEY: console.count = -1; if (!console.y) break; if (console.first_line < (CONSOLE_LINES_BUFFER - CONSOLE_LINES_ONSCREEN - CONSOLE_LINES_ONSCREEN / 2)) console.first_line += CONSOLE_LINES_ONSCREEN / 2; else console.first_line = CONSOLE_LINES_BUFFER - CONSOLE_LINES_ONSCREEN; build_console_layer (); break; case CONSOLE_START_KEY: console.count = -1; if (console.y) console.first_line = 0; break; case CONSOLE_END_KEY: console.count = -1; if (console.y) console.first_line = CONSOLE_LINES_BUFFER - CONSOLE_LINES_ONSCREEN; break; default: if (k >= 0x20 && k <= 0x7f && (console.index < CONSOLE_INPUT_SIZE - 2)) { char l[42]; buffer[console.index] = k; *m=k;m[1]=0; console.index++; sprintf (l, "%s%c", console.line[CONSOLE_LINES_BUFFER-1],k); free (console.line[CONSOLE_LINES_BUFFER-1]); console.line[CONSOLE_LINES_BUFFER-1] = strdup(l); buffer[console.index] = 0; print_text_console (m, console.index, 19, 2, CONSOLE_COLOR, 0); flush_block (console.index*8, y1, console.index*8+7, y2); } break; } do_update (); return TRUE; } void console_prompt () { report (CONSOLE_PROMPT); console_input = 1; } void console_lock () { console_input = 0; } #else void *debug; void report (char *message, ...) { /* dummy */ } int console_init () { return 0; } /* Date: Sun, 14 Oct 2001 23:02:02 -0700 * From: Vasyl Tsvirkunov * * This one was rather harmless and affected only builds without console. * In SQ1&2 (and likely some others) name entry screen did not update * properly. The bug caused by implicit assumption in cycle.c that * console_cycle() updates the screen. Well, it does, if console is enabled. * The fix is simple. In the second version of console_cycle() in console.c * (the "dummy" one) add call to do_update(). The thing raises some * questions about overall architecture of main cycle, but otherwise the fix * works just fine. */ void console_cycle () { do_update (); } void console_lock () { /* dummy */ } void console_prompt () { /* dummy */ } int console_keyhandler (int i) { return FALSE; } #endif /* USE_CONSOLE */