From d342d5faea30d5a63289d6421a8123a9e3c09cc3 Mon Sep 17 00:00:00 2001 From: Stefan Jokisch Date: Wed, 7 Dec 2011 15:46:49 +0000 Subject: [PATCH] Released Frotz232Src.zip --- bcfrotz.h | 85 +++ bcinit.c | 755 +++++++++++++++++++++++ bcinput.c | 989 ++++++++++++++++++++++++++++++ bcmouse.c | 75 +++ bcpic.c | 677 +++++++++++++++++++++ bcsample.c | 414 +++++++++++++ bcscreen.c | 298 +++++++++ bctext.c | 842 +++++++++++++++++++++++++ buffer.c | 112 ++++ fastmem.c | 910 +++++++++++++++++++++++++++ files.c | 555 +++++++++++++++++ font.dat | Bin 0 -> 26976 bytes frotz.h | 597 ++++++++++++++++++ getopt.c | 69 +++ hotkey.c | 242 ++++++++ input.c | 303 +++++++++ main.c | 181 ++++++ math.c | 248 ++++++++ object.c | 904 +++++++++++++++++++++++++++ process.c | 746 +++++++++++++++++++++++ random.c | 69 +++ readme.txt | 103 ++++ redirect.c | 159 +++++ screen.c | 1725 ++++++++++++++++++++++++++++++++++++++++++++++++++++ sound.c | 161 +++++ stream.c | 352 +++++++++++ table.c | 180 ++++++ text.c | 1080 ++++++++++++++++++++++++++++++++ variable.c | 291 +++++++++ 29 files changed, 13122 insertions(+) create mode 100644 bcfrotz.h create mode 100644 bcinit.c create mode 100644 bcinput.c create mode 100644 bcmouse.c create mode 100644 bcpic.c create mode 100644 bcsample.c create mode 100644 bcscreen.c create mode 100644 bctext.c create mode 100644 buffer.c create mode 100644 fastmem.c create mode 100644 files.c create mode 100644 font.dat create mode 100644 frotz.h create mode 100644 getopt.c create mode 100644 hotkey.c create mode 100644 input.c create mode 100644 main.c create mode 100644 math.c create mode 100644 object.c create mode 100644 process.c create mode 100644 random.c create mode 100644 readme.txt create mode 100644 redirect.c create mode 100644 screen.c create mode 100644 sound.c create mode 100644 stream.c create mode 100644 table.c create mode 100644 text.c create mode 100644 variable.c diff --git a/bcfrotz.h b/bcfrotz.h new file mode 100644 index 0000000..70c99a1 --- /dev/null +++ b/bcfrotz.h @@ -0,0 +1,85 @@ +/* + * "BCfrotz.h" + * + * Borland C interface, declarations + * + */ + +#define byte0(v) ((byte *)&v)[0] +#define byte1(v) ((byte *)&v)[1] +#define byte2(v) ((byte *)&v)[2] +#define byte3(v) ((byte *)&v)[3] +#define word0(v) ((word *)&v)[0] +#define word1(v) ((word *)&v)[1] + +#ifndef HISTORY_MIN_ENTRY +#define HISTORY_MIN_ENTRY 1 +#endif + +#define SPECIAL_KEY_MIN 256 +#define SPECIAL_KEY_HOME 256 +#define SPECIAL_KEY_END 257 +#define SPECIAL_KEY_WORD_LEFT 258 +#define SPECIAL_KEY_WORD_RIGHT 259 +#define SPECIAL_KEY_DELETE 260 +#define SPECIAL_KEY_INSERT 261 +#define SPECIAL_KEY_PAGE_UP 262 +#define SPECIAL_KEY_PAGE_DOWN 263 +#define SPECIAL_KEY_TAB 264 +#define SPECIAL_KEY_MAX 264 + +#define _MONO_ 0 +#define _TEXT_ 1 +#define _CGA_ 2 +#define _MCGA_ 3 +#define _EGA_ 4 +#define _AMIGA_ 5 + +typedef unsigned char byte; +typedef unsigned short word; + +extern display; + +extern cursor_x; +extern cursor_y; + +extern char latin1_to_ibm[]; +extern char latin1_to_ascii[]; + +extern byte text_bg; +extern byte text_fg; + +extern byte scrn_attr; + +extern user_background; +extern user_foreground; +extern user_emphasis; +extern user_reverse_bg; +extern user_reverse_fg; +extern user_screen_height; +extern user_screen_width; +extern user_tandy_bit; +extern user_bold_typing; +extern user_random_seed; +extern user_font; + +extern char stripped_story_name[]; +extern char *prog_name; + +extern current_bg; +extern current_fg; +extern current_style; +extern current_font; + +extern scaler; + +/* BCinit */ int dectoi (const char *); +/* BCinit */ int hextoi (const char *); +/* BCmouse */ bool detect_mouse (void); +/* BCmouse */ int read_mouse (void); +/* BCpic */ bool init_pictures (void); +/* BCpic */ void reset_pictures (void); +/* BCsmpl */ bool init_sound (void); +/* BCsmpl */ void reset_sound (void); +/* BCtext */ void switch_scrn_attr (bool); +/* BCtext */ void load_fonts (const char *); diff --git a/bcinit.c b/bcinit.c new file mode 100644 index 0000000..3fba0d9 --- /dev/null +++ b/bcinit.c @@ -0,0 +1,755 @@ +/* + * file "BCinit.c" + * + * Borland C front end, initialisation + * + */ + +#include +#include +#include +#include +#include +#include "frotz.h" +#include "BCfrotz.h" + +#define INFORMATION "\ +\n\ +FROTZ V2.32 - interpreter for all Infocom games. Complies with standard\n\ +1.0 of Graham Nelson's specification. Written by Stefan Jokisch in 1995-7\n\ +\n\ +Syntax: frotz [options] story-file\n\ +\n\ + -a watch attribute setting \t -l # left margin\n\ + -A watch attribute testing \t -o watch object movement\n\ + -b # background colour \t -O watch object locating\n\ + -B # reverse background colour\t -p alter piracy opcode\n\ + -c # context lines \t -r # right margin\n\ + -d # display mode (see below) \t -s # random number seed value\n\ + -e # emphasis colour [mode 1] \t -S # transscript width\n\ + -f # foreground colour \t -t set Tandy bit\n\ + -F # reverse foreground colour\t -T bold typing [modes 2+4+5]\n\ + -g # font [mode 5] (see below)\t -u # slots for multiple undo\n\ + -h # screen height \t -w # screen width\n\ + -i ignore runtime errors \t -x expand abbreviations g/x/z\n\ +\n\ +Fonts are 0 (fixed), 1 (sans serif), 2 (comic), 3 (times), 4 (serif).\n\ +\n\ +Display modes are 0 (mono), 1 (text), 2 (CGA), 3 (MCGA), 4 (EGA), 5 (Amiga)." + +extern unsigned cdecl _heaplen = 0x800 + 4 * BUFSIZ; +extern unsigned cdecl _stklen = 0x800; + +extern const char *optarg; +extern int optind; + +int cdecl getopt (int, char *[], const char *); + +static const char *progname = NULL; + +extern char script_name[]; +extern char command_name[]; +extern char save_name[]; +extern char auxilary_name[]; + +char stripped_story_name[10]; + +int display = -1; + +int user_background = -1; +int user_foreground = -1; +int user_emphasis = -1; +int user_bold_typing = -1; +int user_reverse_bg = -1; +int user_reverse_fg = -1; +int user_screen_height = -1; +int user_screen_width = -1; +int user_tandy_bit = -1; +int user_random_seed = -1; +int user_font = 1; + +static byte old_video_mode = 0; + +static void interrupt (*oldvect) () = NULL; + +/* + * dectoi + * + * Convert a string containing a decimal number to integer. The string may + * be NULL, but it must not be empty. + * + */ + +int dectoi (const char *s) +{ + int n = 0; + + if (s != NULL) + + do { + + n = 10 * n + (*s & 15); + + } while (*++s > ' '); + + return n; + +}/* dectoi */ + +/* + * hextoi + * + * Convert a string containing a hex number to integer. The string may be + * NULL, but it must not be empty. + * + */ + +int hextoi (const char *s) +{ + int n = 0; + + if (s != NULL) + + do { + + n = 16 * n + (*s & 15); + + if (*s > '9') + n += 9; + + } while (*++s > ' '); + + return n; + +}/* hextoi */ + +/* + * cleanup + * + * Shut down the IO interface: free memory, close files, restore + * interrupt pointers and return to the previous video mode. + * + */ + +static void cleanup (void) +{ + + reset_sound (); + reset_pictures (); + + asm mov ah,0 + asm mov al,old_video_mode + asm int 0x10 + + setvect (0x1b, oldvect); + +}/* cleanup */ + +/* + * fast_exit + * + * Handler routine to be called when the crtl-break key is pressed. + * + */ + +static void interrupt fast_exit () +{ + + cleanup (); exit (EXIT_FAILURE); + +}/* fast_exit */ + +/* + * os_fatal + * + * Display error message and exit program. + * + */ + +void os_fatal (const char *s) +{ + + if (h_interpreter_number) + os_reset_screen (); + + /* Display error message */ + + fputs ("\nFatal error: ", stderr); + fputs (s, stderr); + fputs ("\n", stderr); + + /* Abort program */ + + exit (EXIT_FAILURE); + +}/* os_fatal */ + +/* + * parse_options + * + * Parse program options and set global flags accordingly. + * + */ + +static void parse_options (int argc, char **argv) +{ + int c; + + do { + + int num = 0; + + c = getopt (argc, argv, "aAb:B:c:d:e:f:F:g:h:il:oOpr:s:S:tTu:w:x"); + + if (optarg != NULL) + num = dectoi (optarg); + + if (c == 'a') + option_attribute_assignment = 1; + if (c == 'A') + option_attribute_testing = 1; + if (c == 'b') + user_background = num; + if (c == 'B') + user_reverse_bg = num; + if (c == 'c') + option_context_lines = num; + if (c == 'd') + display = optarg[0] | 32; + if (c == 'e') + user_emphasis = num; + if (c == 'T') + user_bold_typing = 1; + if (c == 'f') + user_foreground = num; + if (c == 'F') + user_reverse_fg = num; + if (c == 'g') + user_font = num; + if (c == 'h') + user_screen_height = num; + if (c == 'i') + option_ignore_errors = 1; + if (c == 'l') + option_left_margin = num; + if (c == 'o') + option_object_movement = 1; + if (c == 'O') + option_object_locating = 1; + if (c == 'p') + option_piracy = 1; + if (c == 'r') + option_right_margin = num; + if (c == 's') + user_random_seed = num; + if (c == 'S') + option_script_cols = num; + if (c == 't') + user_tandy_bit = 1; + if (c == 'u') + option_undo_slots = num; + if (c == 'w') + user_screen_width = num; + if (c == 'x') + option_expand_abbreviations = 1; + + } while (c != EOF); + +}/* parse_options */ + +/* + * os_process_arguments + * + * Handle command line switches. Some variables may be set to activate + * special features of Frotz: + * + * option_attribute_assignment + * option_attribute_testing + * option_context_lines + * option_object_locating + * option_object_movement + * option_left_margin + * option_right_margin + * option_ignore_errors + * option_piracy + * option_undo_slots + * option_expand_abbreviations + * option_script_cols + * + * The global pointer "story_name" is set to the story file name. + * + */ + +void os_process_arguments (int argc, char *argv[]) +{ + const char *p; + int i; + + /* Parse command line options */ + + parse_options (argc, argv); + + if (optind != argc - 1) { + puts (INFORMATION); + exit (EXIT_FAILURE); + } + + /* Set the story file name */ + + story_name = argv[optind]; + + /* Strip path and extension off the story file name */ + + p = story_name; + + for (i = 0; story_name[i] != 0; i++) + if (story_name[i] == '\\' || story_name[i] == ':') + p = story_name + i + 1; + + for (i = 0; p[i] != 0 && p[i] != '.' && i < 8; i++) + stripped_story_name[i] = p[i]; + + stripped_story_name[i] = 0; + + /* Create nice default file names */ + + strcpy (script_name, stripped_story_name); + strcpy (command_name, stripped_story_name); + strcpy (save_name, stripped_story_name); + strcpy (auxilary_name, stripped_story_name); + + strcat (script_name, ".scr"); + strcat (command_name, ".rec"); + strcat (save_name, ".sav"); + strcat (auxilary_name, ".aux"); + + /* Save the executable file name */ + + progname = argv[0]; + +}/* os_process_arguments */ + +/* + * standard_palette + * + * Set palette registers to EGA default values and call VGA BIOS to + * use DAC registers #0 to #63. + * + */ + +static void standard_palette (void) +{ + + static byte palette[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x14, 0x07, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x00 /* last one is the overscan register */ + }; + + if (display == _AMIGA_) { + asm mov ax,0x1002 + asm lea dx,palette + asm push ds + asm pop es + asm int 0x10 + asm mov ax,0x1013 + asm mov bx,0x0001 + asm int 0x10 + } + +}/* standard_palette */ + +/* + * special_palette + * + * Set palette register #i to value i and call VGA BIOS to use DAC + * registers #64 to #127. + * + */ + +static void special_palette (void) +{ + + static byte palette[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x00 /* last one is the overscan register */ + }; + + if (display == _AMIGA_) { + asm mov ax,0x1002 + asm mov dx,offset palette + asm push ds + asm pop es + asm int 0x10 + asm mov ax,0x1013 + asm mov bx,0x0101 + asm int 0x10 + } + +}/* special_palette */ + +/* + * os_init_screen + * + * Initialise the IO interface. Prepare the screen and other devices + * (mouse, sound board). Set various OS depending story file header + * entries: + * + * h_config (aka flags 1) + * h_flags (aka flags 2) + * h_screen_cols (aka screen width in characters) + * h_screen_rows (aka screen height in lines) + * h_screen_width + * h_screen_height + * h_font_height (defaults to 1) + * h_font_width (defaults to 1) + * h_default_foreground + * h_default_background + * h_interpreter_number + * h_interpreter_version + * h_user_name (optional; not used by any game) + * + * Finally, set reserve_mem to the amount of memory (in bytes) that + * should not be used for multiple undo and reserved for later use. + * + */ + +void os_init_screen (void) +{ + static byte zcolour[] = { + BLACK_COLOUR, + BLUE_COLOUR, + GREEN_COLOUR, + CYAN_COLOUR, + RED_COLOUR, + MAGENTA_COLOUR, + BROWN + 16, + LIGHTGRAY + 16, + GREY_COLOUR, + LIGHTBLUE + 16, + LIGHTGREEN + 16, + LIGHTCYAN + 16, + LIGHTRED + 16, + LIGHTMAGENTA + 16, + YELLOW_COLOUR, + WHITE_COLOUR + }; + + static struct { /* information on modes 0 to 5 */ + byte vmode; + word width; + word height; + byte font_width; + byte font_height; + byte fg; + byte bg; + } info[] = { + { 0x07, 80, 25, 1, 1, LIGHTGRAY + 16, BLACK_COLOUR }, /* MONO */ + { 0x03, 80, 25, 1, 1, LIGHTGRAY + 16, BLUE_COLOUR }, /* TEXT */ + { 0x06, 640, 200, 8, 8, WHITE_COLOUR, BLACK_COLOUR }, /* CGA */ + { 0x13, 320, 200, 5, 8, WHITE_COLOUR, GREY_COLOUR }, /* MCGA */ + { 0x0e, 640, 200, 8, 8, WHITE_COLOUR, BLUE_COLOUR }, /* EGA */ + { 0x12, 640, 400, 8, 16, WHITE_COLOUR, BLACK_COLOUR } /* AMIGA */ + }; + + static struct { /* information on modes A to E */ + word vesamode; + word width; + word height; + } subinfo[] = { + { 0x001, 40, 25 }, + { 0x109, 132, 25 }, + { 0x10b, 132, 50 }, + { 0x108, 80, 60 }, + { 0x10c, 132, 60 } + }; + + int subdisplay; + + /* Get the current video mode. This video mode will be selected + when the program terminates. It's also useful to auto-detect + monochrome boards. */ + + asm mov ah,15 + asm int 0x10 + asm mov old_video_mode,al + + /* If the display mode has not already been set by the user then see + if this is a monochrome board. If so, set the display mode to 0. + Otherwise check the graphics flag of the story. Select a graphic + mode if it is set or if this is a V6 game. Select text mode if it + is not. */ + + if (display == -1) + + if (old_video_mode == 7) + display = '0'; + else if (h_version == V6 || (h_flags & GRAPHICS_FLAG)) + display = '5'; + else + display = '1'; + + /* Activate the desired display mode. All VESA text modes are very + similar to the standard text mode; in fact, only here we need to + know which VESA mode is used. */ + + if (display >= '0' && display <= '5') { + subdisplay = -1; + display -= '0'; + _AL = info[display].vmode; + _AH = 0; + } else if (display == 'a') { + subdisplay = 0; + display = 1; + _AL = 0x01; + _AH = 0; + } else if (display >= 'b' && display <= 'e') { + subdisplay = display - 'a'; + display = 1; + _BX = subinfo[subdisplay].vesamode; + _AX = 0x4f02; + } + + geninterrupt (0x10); + + /* Make various preparations */ + + if (display <= _TEXT_) { + + /* Enable bright background colours */ + + asm mov ax,0x1003 + asm mov bl,0 + asm int 0x10 + + /* Turn off hardware cursor */ + + asm mov ah,1 + asm mov cx,0xffff + asm int 0x10 + + } else { + + load_fonts (progname); + + if (display == _AMIGA_) { + + scaler = 2; + + /* Use resolution 640 x 400 instead of 640 x 480. BIOS doesn't + help us here since this is not a standard resolution. */ + + outportb (0x03c2, 0x63); + + outport (0x03d4, 0x0e11); + outport (0x03d4, 0xbf06); + outport (0x03d4, 0x1f07); + outport (0x03d4, 0x9c10); + outport (0x03d4, 0x8f12); + outport (0x03d4, 0x9615); + outport (0x03d4, 0xb916); + + } + + } + +#if !defined(__SMALL__) && !defined (__TINY__) && !defined (__MEDIUM__) + + /* Set the amount of memory to reserve for later use. It takes + some memory to open command, script and game files. If Frotz + is compiled in a small memory model then memory for opening + files is allocated on the "near heap" while other allocations + are made on the "far heap", i.e. we need not reserve memory + in this case. */ + + reserve_mem = 4 * BUFSIZ; + +#endif + + /* Amiga emulation under V6 needs special preparation. */ + + if (display == _AMIGA_ && h_version == V6) { + + user_reverse_fg = -1; + user_reverse_bg = -1; + zcolour[LIGHTGRAY] = LIGHTGREY_COLOUR; + zcolour[DARKGRAY] = DARKGREY_COLOUR; + + special_palette (); + + } + + /* Set various bits in the configuration byte. These bits tell + the game which features are supported by the interpreter. */ + + if (h_version == V3 && user_tandy_bit != -1) + h_config |= CONFIG_TANDY; + if (h_version == V3) + h_config |= CONFIG_SPLITSCREEN; + if (h_version == V3 && (display == _MCGA_ || (display == _AMIGA_ && user_font != 0))) + h_config |= CONFIG_PROPORTIONAL; + if (h_version >= V4 && display != _MCGA_ && (user_bold_typing != -1 || display <= _TEXT_)) + h_config |= CONFIG_BOLDFACE; + if (h_version >= V4) + h_config |= CONFIG_EMPHASIS | CONFIG_FIXED | CONFIG_TIMEDINPUT; + if (h_version >= V5 && display != _MONO_ && display != _CGA_) + h_config |= CONFIG_COLOUR; + if (h_version >= V5 && display >= _CGA_ && init_pictures ()) + h_config |= CONFIG_PICTURES; + + /* Handle various game flags. These flags are set if the game wants + to use certain features. The flags must be cleared if the feature + is not available. */ + + if (h_flags & GRAPHICS_FLAG) + if (display <= _TEXT_) + h_flags &= ~GRAPHICS_FLAG; + if (h_version == V3 && (h_flags & OLD_SOUND_FLAG)) + if (!init_sound ()) + h_flags &= ~OLD_SOUND_FLAG; + if (h_flags & SOUND_FLAG) + if (!init_sound ()) + h_flags &= ~SOUND_FLAG; + if (h_version >= V5 && (h_flags & UNDO_FLAG)) + if (!option_undo_slots) + h_flags &= ~UNDO_FLAG; + if (h_flags & MOUSE_FLAG) + if (subdisplay != -1 || !detect_mouse ()) + h_flags &= ~MOUSE_FLAG; + if (h_flags & COLOUR_FLAG) + if (display == _MONO_ || display == _CGA_) + h_flags &= ~COLOUR_FLAG; + h_flags &= ~MENU_FLAG; + + /* Set the screen dimensions, font size and default colour */ + + h_screen_width = info[display].width; + h_screen_height = info[display].height; + h_font_height = info[display].font_height; + h_font_width = info[display].font_width; + h_default_foreground = info[display].fg; + h_default_background = info[display].bg; + + if (subdisplay != -1) { + h_screen_width = subinfo[subdisplay].width; + h_screen_height = subinfo[subdisplay].height; + } + + if (user_screen_width != -1) + h_screen_width = user_screen_width; + if (user_screen_height != -1) + h_screen_height = user_screen_height; + + h_screen_cols = h_screen_width / h_font_width; + h_screen_rows = h_screen_height / h_font_height; + + if (user_foreground != -1) + h_default_foreground = zcolour[user_foreground]; + if (user_background != -1) + h_default_background = zcolour[user_background]; + + /* Set the interpreter number (a constant telling the game which + operating system it runs on) and the interpreter version. The + interpreter number has effect on V6 games and "Beyond Zork". */ + + h_interpreter_number = INTERP_MSDOS; + h_interpreter_version = 'F'; + + if (display == _AMIGA_) + h_interpreter_number = INTERP_AMIGA; + + /* Install the fast_exit routine to handle the ctrl-break key */ + + oldvect = getvect (0x1b); setvect (0x1b, fast_exit); + +}/* os_init_screen */ + +/* + * os_reset_screen + * + * Reset the screen before the program stops. + * + */ + +void os_reset_screen (void) +{ + + os_set_font (TEXT_FONT); + os_set_text_style (0); + os_display_string ((zchar *) "[Hit any key to exit.]"); + os_read_key (0, TRUE); + + cleanup (); + +}/* os_reset_screen */ + +/* + * os_restart_game + * + * This routine allows the interface to interfere with the process of + * restarting a game at various stages: + * + * RESTART_BEGIN - restart has just begun + * RESTART_WPROP_SET - window properties have been initialised + * RESTART_END - restart is complete + * + */ + +void os_restart_game (int stage) +{ + int x, y; + + if (story_id == BEYOND_ZORK) + + if (stage == RESTART_BEGIN) + + if ((display == _MCGA_ || display == _AMIGA_) && os_picture_data (1, &x, &y)) { + + special_palette (); + + asm mov ax,0x1010 + asm mov bx,64 + asm mov dh,0 + asm mov ch,0 + asm mov cl,0 + asm int 0x10 + asm mov ax,0x1010 + asm mov bx,79 + asm mov dh,0xff + asm mov ch,0xff + asm mov cl,0xff + asm int 0x10 + + os_draw_picture (1, 1, 1); + os_read_key (0, FALSE); + + standard_palette (); + + } + +}/* os_restart_game */ + +/* + * os_random_seed + * + * Return an appropriate random seed value in the range from 0 to + * 32767, possibly by using the current system time. + * + */ + +int os_random_seed (void) +{ + + if (user_random_seed == -1) { + + /* Use the time of day as seed value */ + + asm mov ah,0 + asm int 0x1a + + return _DX & 0x7fff; + + } else return user_random_seed; + +}/* os_random_seed */ diff --git a/bcinput.c b/bcinput.c new file mode 100644 index 0000000..04c6e0f --- /dev/null +++ b/bcinput.c @@ -0,0 +1,989 @@ +/* + * file "BCinput.c" + * + * Borland C front end, input functions + * + */ + +#include +#include +#include +#include "frotz.h" +#include "BCfrotz.h" + +#ifndef HISTORY_BUFSIZE +#define HISTORY_BUFSIZE 500 +#endif + +extern bool is_terminator (zchar); + +extern bool read_yes_or_no (const char *); +extern void read_string (int, zchar *); + +extern int completion (const zchar *, zchar *); + +static long limit = 0; + +static struct { + zchar buffer[HISTORY_BUFSIZE]; + int latest; + int current; + int prefix_len; +} history; + +static struct { + zchar *buffer; + int pos; + int length; + int max_length; + int width; + int max_width; +} input; + +static bool overwrite = FALSE; + +/* + * swap_colours + * + * This is a small helper function for switch_cursor. It swaps the + * current background and foreground colours. + * + */ + +static void swap_colours (void) +{ + + _AL = text_fg; + _AH = text_bg; + text_fg = _AH; + text_bg = _AL; + +}/* swap_colours */ + +/* + * switch_cursor + * + * Turn cursor on/off. If there is mouse support then turn the mouse + * pointer on/off as well. The cursor should not be moved and the + * contents of the screen should not be changed while the cursor is + * visible (because of the primitive cursor emulation we use here). + * + */ + +static void switch_cursor (bool cursor) +{ + + if (display <= _TEXT_) { + + /* Use hardware cursor in text mode */ + + if (display == _MONO_) + _CX = overwrite ? 0x080f : 0x0a0b; + else + _CX = overwrite ? 0x0408 : 0x0506; + + if (!cursor) + _CX = 0xffff; + + asm mov ah,2 + asm mov bh,0 + asm mov dh,byte ptr cursor_y + asm mov dl,byte ptr cursor_x + asm int 0x10 + asm mov ah,1 + asm int 0x10 + + } else { + + int saved_x = cursor_x; + + if (cursor) + swap_colours (); + + if (input.pos < input.length) + os_display_char (input.buffer[input.pos]); + else + os_display_char (' '); + + if (cursor) + swap_colours (); + + cursor_x = saved_x; + + } + +}/* switch_cursor */ + +/* + * get_current_time + * + * Return the current system time in 1/10 seconds. + * + */ + +static long get_current_time (void) +{ + long time; + + /* Get the current time of day measured in + 65536 / 1,193,180 = 0.054925493 + seconds. Multiply this value with + 959 / 1746 = 0.54925544 + to get the current time in 0.1 seconds. */ + + asm mov ah,0 + asm int 0x1a + asm mov word ptr time,dx + asm mov word ptr time + 2,cx + + return time * 959 / 1746; + +}/* get_current_time */ + +/* + * set_timer + * + * Set a time limit of timeout/10 seconds if timeout is not zero; + * otherwise clear the time limit. + * + */ + +static void set_timer (int timeout) +{ + + limit = (timeout != 0) ? get_current_time () + timeout : 0; + +}/* set_timer */ + +/* + * time_limit_hit + * + * Return true if a previously set time limit has been exceeded. + * + */ + +static bool out_of_time (void) +{ + + if (limit != 0) { + + long now = get_current_time (); + + if (now < 1L * 3600 * 10 && limit > 23L * 3600 * 10) + now += 24L * 3600 * 10; + + return now >= limit; + + } else return FALSE; + +}/* out_of_time */ + +/* + * get_key + * + * Read a keypress or a mouse click. Returns... + * + * ZC_TIME_OUT = time limit exceeded, + * ZC_BACKSPACE = the backspace key, + * ZC_RETURN = the return key, + * ZC_HKEY_MIN...ZC_HKEY_MAX = a hot key, + * ZC_ESCAPE = the escape key, + * ZC_ASCII_MIN...ZC_ASCII_MAX = ASCII character, + * ZC_ARROW_MIN...ZC_ARROW_MAX = an arrow key, + * ZC_FKEY_MIN...ZC_FKEY_MAX = a function key, + * ZC_NUMPAD_MIN...ZC_NUMPAD_MAX = a number pad key, + * ZC_SINGLE_CLICK = single mouse click, + * ZC_DOUBLE_CLICK = double mouse click, + * ZC_LATIN1_MIN+1...ZC_LATIN1_MAX = ISO Latin-1 character, + * SPECIAL_KEY_MIN...SPECIAL_KEY_MAX = a special editing key. + * + */ + +static int get_key (bool cursor) +{ + static byte arrow_key_map[] = { + 0x48, 0x50, 0x4b, 0x4d + }; + static byte special_key_map[] = { + 0x47, 0x4f, 0x73, 0x74, 0x53, 0x52, 0x49, 0x51, 0x0f + }; + static byte hot_key_map[] = { + 0x13, 0x19, 0x1f, 0x16, 0x31, 0x2d, 0x20, 0x23 + }; + + int key; + + /* Loop until a key was pressed */ + + if (cursor) + switch_cursor (TRUE); + + if (h_flags & MOUSE_FLAG) { + asm mov ax,1 + asm int 0x33 + } + + do { + + if (_bios_keybrd (_KEYBRD_READY)) { + + word code = _bios_keybrd (_KEYBRD_READ); + + if (byte0 (code) != 0 && byte0 (code) != 9) { + + for (key = ZC_NUMPAD_MIN; key <= ZC_NUMPAD_MAX; key++) + if (byte0 (code) == key - ZC_NUMPAD_MIN + '0' && byte1 (code) >= 0x10) + goto exit_loop; + + for (key = ZC_LATIN1_MIN + 1; key <= ZC_LATIN1_MAX; key++) + if (byte0 (code) == latin1_to_ibm[key - ZC_LATIN1_MIN]) + goto exit_loop; + + key = byte0 (code); + + if (key == ZC_BACKSPACE) + goto exit_loop; + if (key == ZC_RETURN) + goto exit_loop; + if (key == ZC_ESCAPE) + goto exit_loop; + if (key >= ZC_ASCII_MIN && key <= ZC_ASCII_MAX) + goto exit_loop; + + } else { + + for (key = ZC_ARROW_MIN; key <= ZC_ARROW_MAX; key++) + if (byte1 (code) == arrow_key_map[key - ZC_ARROW_MIN]) + goto exit_loop; + + for (key = ZC_FKEY_MIN; key <= ZC_FKEY_MAX - 2; key++) + if (byte1 (code) == key - ZC_FKEY_MIN + 0x3b) + goto exit_loop; + + for (key = ZC_HKEY_MIN; key <= ZC_HKEY_MAX; key++) + if (byte1 (code) == hot_key_map[key - ZC_HKEY_MIN]) + goto exit_loop; + + for (key = SPECIAL_KEY_MIN; key <= SPECIAL_KEY_MAX; key++) + if (byte1 (code) == special_key_map[key - SPECIAL_KEY_MIN]) + goto exit_loop; + + } + + } else { + + int clicks = read_mouse (); + + if (clicks == 1) + { key = ZC_SINGLE_CLICK; goto exit_loop; } + if (clicks == 2) + { key = ZC_DOUBLE_CLICK; goto exit_loop; } + + } + + key = ZC_TIME_OUT; + + } while (!out_of_time ()); + +exit_loop: + + if (h_flags & MOUSE_FLAG) { + asm mov ax,2 + asm int 0x33 + } + + if (cursor) + switch_cursor (FALSE); + + return key; + +}/* get_key */ + +/* + * cursor_left + * + * Move the cursor one character to the left. + * + */ + +static void cursor_left (void) +{ + + if (input.pos > 0) + cursor_x -= os_char_width (input.buffer[--input.pos]); + +}/* cursor_left */ + +/* + * cursor_right + * + * Move the cursor one character to the right. + * + */ + +static void cursor_right (void) +{ + + if (input.pos < input.length) + cursor_x += os_char_width (input.buffer[input.pos++]); + +}/* cursor_right */ + +/* + * first_char + * + * Move the cursor to the beginning of the input line. + * + */ + +static void first_char (void) +{ + + while (input.pos > 0) + cursor_left (); + +}/* first_char */ + +/* + * last_char + * + * Move the cursor to the end of the input line. + * + */ + +static void last_char (void) +{ + + while (input.pos < input.length) + cursor_right (); + +}/* last_char */ + +/* + * prev_word + * + * Move the cursor to the start of the previous word. + * + */ + +static void prev_word (void) +{ + + do { + + cursor_left (); + + if (input.pos == 0) + return; + + } while (input.buffer[input.pos] == ' ' || input.buffer[input.pos - 1] != ' '); + +}/* prev_word */ + +/* + * next_word + * + * Move the cursor to the start of the next word. + * + */ + +static void next_word (void) +{ + + do { + + cursor_right (); + + if (input.pos == input.length) + return; + + } while (input.buffer[input.pos] == ' ' || input.buffer[input.pos - 1] != ' '); + +}/* next_word */ + +/* + * input_move + * + * Helper function to move parts of the input buffer: + * + * newc != 0, oldc == 0: INSERT + * newc != 0, oldc != 0: OVERWRITE + * newc == 0, oldc != 0: DELETE + * newc == 0, oldc == 0: NO OPERATION + * + */ + +#define H(x) (x ? 1 : 0) + +static void input_move (zchar newc, zchar oldc) +{ + int newwidth = (newc != 0) ? os_char_width (newc) : 0; + int oldwidth = (oldc != 0) ? os_char_width (oldc) : 0; + + zchar *p = input.buffer + input.pos; + + int saved_x = cursor_x; + + int updated_width = input.width + newwidth - oldwidth; + int updated_length = input.length + H (newc) - H (oldc); + + if (updated_width > input.max_width) + return; + if (updated_length > input.max_length) + return; + + input.width = updated_width; + input.length = updated_length; + + if (oldc != 0 && newc == 0) + memmove (p, p + 1, updated_length - input.pos + 1); + if (newc != 0 && oldc == 0) + memmove (p + 1, p, updated_length - input.pos); + + if (newc != 0) + *p = newc; + + os_display_string (p); + + switch_scrn_attr (TRUE); + + if (oldwidth > newwidth) + + os_erase_area ( + cursor_y + 1, + cursor_x + 1, + cursor_y + h_font_height, + cursor_x + oldwidth - newwidth); + + switch_scrn_attr (FALSE); + + cursor_x = saved_x; + + if (newc != 0) + cursor_right (); + +}/* input_move */ + +#undef H(x) + +/* + * delete_char + * + * Delete the character below the cursor. + * + */ + +static void delete_char (void) +{ + + input_move (0, input.buffer[input.pos]); + +}/* delete_char */ + +/* + * delete_left + * + * Delete the character to the left of the cursor. + * + */ + +static void delete_left (void) +{ + + if (input.pos > 0) { + cursor_left (); + delete_char (); + } + +}/* delete_left */ + +/* + * truncate_line + * + * Truncate the input line to n characters. + * + */ + +static void truncate_line (int n) +{ + + last_char (); + + while (input.length > n) + delete_left (); + +}/* truncate_line */ + +/* + * insert_char + * + * Insert a character into the input buffer. + * + */ + +static void insert_char (zchar newc) +{ + zchar oldc = 0; + + if (overwrite) + oldc = input.buffer[input.pos]; + + input_move (newc, oldc); + +}/* insert_char */ + +/* + * insert_string + * + * Add a string of characters to the input line. + * + */ + +static void insert_string (const zchar *s) +{ + + while (*s != 0) { + + if (input.length + 1 > input.max_length) + break; + if (input.width + os_char_width (*s) > input.max_width) + break; + + insert_char (*s++); + + } + +}/* insert_string */ + +/* + * tabulator_key + * + * Complete the word at the end of the input line, if possible. + * + */ + +static void tabulator_key (void) +{ + int status; + + if (input.pos == input.length) { + + zchar extension[10]; + + status = completion (input.buffer, extension); + insert_string (extension); + + } else status = 2; + + /* Beep if the completion was impossible or ambiguous */ + + if (status != 0) + os_beep (status); + +}/* tabulator_key */ + +/* + * store_input + * + * Copy the current input line to the history buffer. + * + */ + +static void store_input (void) +{ + + if (input.length >= HISTORY_MIN_ENTRY) { + + const zchar *ptr = input.buffer; + + do { + + if (history.latest++ == HISTORY_BUFSIZE - 1) + history.latest = 0; + + history.buffer[history.latest] = *ptr; + + } while (*ptr++ != 0); + + } + +}/* store_input */ + +/* + * fetch_entry + * + * Copy the current history entry to the input buffer and check if it + * matches the prefix in the input buffer. + * + */ + +static bool fetch_entry (zchar *buf, int entry) +{ + int i = 0; + + zchar c; + + do { + + if (entry++ == HISTORY_BUFSIZE - 1) + entry = 0; + + c = history.buffer[entry]; + + if (i < history.prefix_len && input.buffer[i] != c) + return FALSE; + + buf[i++] = c; + + } while (c != 0); + + return (i > history.prefix_len) && (i > 1); + +}/* fetch_entry */ + +/* + * get_prev_entry + * + * Copy the previous history entry to the input buffer. + * + */ + +static void get_prev_entry (void) +{ + zchar buf[INPUT_BUFFER_SIZE]; + + int i = history.current; + + do { + + do { + + if (i-- == 0) + i = HISTORY_BUFSIZE - 1; + + if (i == history.latest) + return; + + } while (history.buffer[i] != 0); + + } while (!fetch_entry (buf, i)); + + truncate_line (history.prefix_len); + + insert_string (buf + history.prefix_len); + + history.current = i; + +}/* get_prev_entry */ + +/* + * get_next_entry + * + * Copy the next history entry to the input buffer. + * + */ + +static void get_next_entry (void) +{ + zchar buf[INPUT_BUFFER_SIZE]; + + int i = history.current; + + truncate_line (history.prefix_len); + + do { + + do { + + if (i == history.latest) + return; + + if (i++ == HISTORY_BUFSIZE - 1) + i = 0; + + } while (history.buffer[i] != 0); + + if (i == history.latest) + goto no_further; + + } while (!fetch_entry (buf, i)); + + insert_string (buf + history.prefix_len); + +no_further: + + history.current = i; + +}/* get_next_entry */ + +/* + * os_read_line + * + * Read a line of input from the keyboard into a buffer. The buffer + * may already be primed with some text. In this case, the "initial" + * text is already displayed on the screen. After the input action + * is complete, the function returns with the terminating key value. + * The length of the input should not exceed "max" characters plus + * an extra 0 terminator. + * + * Terminating keys are the return key (13) and all function keys + * (see the Specification of the Z-machine) which are accepted by + * the is_terminator function. Mouse clicks behave like function + * keys except that the mouse position is stored in global variables + * "mouse_x" and "mouse_y" (top left coordinates are (1,1)). + * + * Furthermore, Frotz introduces some special terminating keys: + * + * ZC_HKEY_PLAYBACK (Alt-P) + * ZC_HKEY_RECORD (Alt-R) + * ZC_HKEY_SEED (Alt-S) + * ZC_HKEY_UNDO (Alt-U) + * ZC_HKEY_RESTART (Alt-N, "new game") + * ZC_HKEY_QUIT (Alt-X, "exit game") + * ZC_HKEY_DEBUGGING (Alt-D) + * ZC_HKEY_HELP (Alt-H) + * + * If the timeout argument is not zero, the input gets interrupted + * after timeout/10 seconds (and the return value is 0). + * + * The complete input line including the cursor must fit in "width" + * screen units. + * + * The function may be called once again to continue after timeouts, + * misplaced mouse clicks or hot keys. In this case the "continued" + * flag will be set. This information can be useful if the interface + * implements input line history. + * + * The screen is not scrolled after the return key was pressed. The + * cursor is at the end of the input line when the function returns. + * + * Since Inform 2.2 the helper function "completion" can be called + * to implement word completion (similar to tcsh under Unix). + * + */ + +#define new_history_search() \ + { history.prefix_len = input.pos; history.current = history.latest; } + +zchar os_read_line (int max, zchar *buf, int timeout, int width, int continued) +{ + int key = continued ? 9999 : 0; + + /* Initialise input variables */ + + input.buffer = buf; + input.pos = strlen ((char *) buf); + input.length = strlen ((char *) buf); + input.max_length = max; + input.width = os_string_width (buf); + input.max_width = width - os_char_width (' '); + + /* Calculate time limit */ + + set_timer (timeout); + + /* Loop until a terminator is found */ + + do { + + if (key != 9999) + new_history_search (); + + /* Get next key from mouse or keyboard */ + + key = get_key (TRUE); + + if (key < ZC_ASCII_MIN || key > ZC_ASCII_MAX && key < ZC_LATIN1_MIN || key > ZC_LATIN1_MAX) { + + /* Ignore time-outs if the cursor is not at end of the line */ + + if (key == ZC_TIME_OUT && input.pos < input.length) + key = 9999; + + /* Backspace, return and escape keys */ + + if (key == ZC_BACKSPACE) + delete_left (); + if (key == ZC_RETURN) + store_input (); + if (key == ZC_ESCAPE) + truncate_line (0); + + /* Editing keys */ + + if (cwin == 0) { + + if (key == ZC_ARROW_UP) + get_prev_entry (); + if (key == ZC_ARROW_DOWN) + get_next_entry (); + if (key == ZC_ARROW_LEFT) + cursor_left (); + if (key == ZC_ARROW_RIGHT) + cursor_right (); + + if (key >= ZC_ARROW_MIN && key <= ZC_ARROW_MAX) + key = 9999; + + if (key == SPECIAL_KEY_HOME) + first_char (); + if (key == SPECIAL_KEY_END) + last_char (); + if (key == SPECIAL_KEY_WORD_LEFT) + prev_word (); + if (key == SPECIAL_KEY_WORD_RIGHT) + next_word (); + if (key == SPECIAL_KEY_DELETE) + delete_char (); + if (key == SPECIAL_KEY_INSERT) + overwrite = !overwrite; + if (key == SPECIAL_KEY_TAB) + tabulator_key (); + + } + + if (key == SPECIAL_KEY_PAGE_UP) + key = ZC_ARROW_UP; + if (key == SPECIAL_KEY_PAGE_DOWN) + key = ZC_ARROW_DOWN; + + } else insert_char (key); + + } while (key > 0xff || !is_terminator (key)); + + last_char (); + + overwrite = FALSE; + + /* Return terminating key */ + + return key; + +}/* os_read_line */ + +#undef new_history_search() + +/* + * os_read_key + * + * Read a single character from the keyboard (or a mouse click) and + * return it. Input aborts after timeout/10 seconds. + * + */ + +zchar os_read_key (int timeout, bool cursor) +{ + int key; + + set_timer (timeout); + + do { + + key = get_key (cursor); + + } while (key > 0xff); + + return key; + +}/* os_read_key */ + +/* + * os_read_file_name + * + * Return the name of a file. Flag can be one of: + * + * FILE_SAVE - Save game file + * FILE_RESTORE - Restore game file + * FILE_SCRIPT - Transscript file + * FILE_RECORD - Command file for recording + * FILE_PLAYBACK - Command file for playback + * FILE_SAVE_AUX - Save auxilary ("preferred settings") file + * FILE_LOAD_AUX - Load auxilary ("preferred settings") file + * + * The length of the file name is limited by MAX_FILE_NAME. Ideally + * an interpreter should open a file requester to ask for the file + * name. If it is unable to do that then this function should call + * print_string and read_string to ask for a file name. + * + */ + +int os_read_file_name (char *file_name, const char *default_name, int flag) +{ + char *extension; + FILE *fp; + bool terminal; + bool result; + + bool saved_replay = istream_replay; + bool saved_record = ostream_record; + + /* Turn off playback and recording temporarily */ + + istream_replay = FALSE; + ostream_record = FALSE; + + /* Select appropriate extension */ + + extension = ".aux"; + + if (flag == FILE_SAVE || flag == FILE_RESTORE) + extension = ".sav"; + if (flag == FILE_SCRIPT) + extension = ".scr"; + if (flag == FILE_RECORD || flag == FILE_PLAYBACK) + extension = ".rec"; + + /* Input file name (reserve four bytes for a file name extension) */ + + print_string ("Enter file name (\""); + print_string (extension); + print_string ("\" will be added).\nDefault is \""); + print_string (default_name); + print_string ("\": "); + + read_string (MAX_FILE_NAME - 4, (zchar *) file_name); + + /* Use the default name if nothing was typed */ + + if (file_name[0] == 0) + strcpy (file_name, default_name); + if (strchr (file_name, '.') == NULL) + strcat (file_name, extension); + + /* Make sure it is safe to use this file name */ + + result = TRUE; + + /* OK if the file is opened for reading */ + + if (flag != FILE_SAVE && flag != FILE_SAVE_AUX && flag != FILE_RECORD) + goto finished; + + /* OK if the file does not exist */ + + if ((fp = fopen (file_name, "rb")) == NULL) + goto finished; + + /* OK if this is a pseudo-file (like PRN, CON, NUL) */ + + terminal = fp->flags & _F_TERM; + + fclose (fp); + + if (terminal) + goto finished; + + /* OK if user wants to overwrite */ + + result = read_yes_or_no ("Overwrite existing file"); + +finished: + + /* Restore state of playback and recording */ + + istream_replay = saved_replay; + ostream_record = saved_record; + + return result; + +}/* os_read_file_name */ diff --git a/bcmouse.c b/bcmouse.c new file mode 100644 index 0000000..ea8444b --- /dev/null +++ b/bcmouse.c @@ -0,0 +1,75 @@ +/* + * file "BCmouse.c" + * + * Borland C front end, mouse support + * + */ + +#include +#include "frotz.h" +#include "BCfrotz.h" + +/* + * detect_mouse + * + * Return true if a mouse driver is present. + * + */ + +bool detect_mouse (void) +{ + + asm xor ax,ax + asm int 0x33 + + return _AX; + +}/* detect_mouse */ + +/* + * read_mouse + * + * Report any mouse clicks. Return 2 for a double click, 1 for a single + * click or 0 if there was no mouse activity at all. + * + */ + +int read_mouse (void) +{ + int click; + + /* Read the current mouse status */ + + for (click = 0; click < 2; click++) { + + if (click == 1) + delay (222); + + asm mov ax,6 + asm xor bx,bx + asm int 0x33 + + if (_BX == 0) + break; + + mouse_x = _CX; + mouse_y = _DX; + + if (display <= _TEXT_) { + mouse_x /= 8; + mouse_y /= 8; + } + + if (display == _MCGA_) + mouse_x /= 2; + + mouse_x++; + mouse_y++; + + } + + /* Return single or double click */ + + return click; + +}/* read_mouse */ diff --git a/bcpic.c b/bcpic.c new file mode 100644 index 0000000..59115a9 --- /dev/null +++ b/bcpic.c @@ -0,0 +1,677 @@ +/* + * "BCpic.c" + * + * Borland C front end, picture functions + * + */ + +#include +#include +#include +#include +#include "frotz.h" +#include "BCfrotz.h" + +#define PIC_NUMBER 0 +#define PIC_WIDTH 2 +#define PIC_HEIGHT 4 +#define PIC_FLAGS 6 +#define PIC_DATA 8 +#define PIC_COLOUR 11 + +#define READ_BYTE(v,p,o) v = *(byte far *)(p+o) +#define READ_WORD(v,p,o) v = *(word far *)(p+o) + +extern byte far *get_scrnptr (int); + +static struct { + byte fileno; + byte flags; + word unused1; + word images; + word link; + byte entry_size; + byte padding; + word checksum; + word unused2; + word version; +} gheader; + +int scaler = 1; + +static word pic_width = 0; +static word pic_height = 0; +static word pic_flags = 0; +static long pic_data = 0; +static long pic_colour = 0; + +static byte far *table_val = NULL; +static word far *table_ref = NULL; + +static FILE *file = NULL; +static byte far *info = NULL; + +/* + * open_graphics_file + * + * Open a graphics file. EGA pictures may be stored in two separate + * graphics files. + * + */ + +static bool open_graphics_file (int number) +{ + char fname[MAX_FILE_NAME + 1]; + char extension[4 + 1]; + + /* Build graphics file name */ + + extension[0] = '.'; + extension[1] = "cmem"[display - 2]; + extension[2] = 'g'; + extension[3] = '0' + number; + extension[4] = 0; + + strcpy (fname, stripped_story_name); + strcat (fname, extension); + + /* Open file, load header, allocate memory, load picture directory */ + + if ((file = fopen (fname, "rb")) == NULL) + goto failure1; + if (fread (&gheader, sizeof (gheader), 1, file) != 1) + goto failure2; + if ((info = farmalloc (gheader.images * gheader.entry_size)) == NULL) + goto failure2; + if (fread (info, gheader.entry_size, gheader.images, file) != gheader.images) + goto failure3; + return TRUE; + +failure3: + farfree (info); info = NULL; +failure2: + fclose (file); file = NULL; +failure1: + return FALSE; + +}/* open_graphics_file */ + +/* + * close_graphics_file + * + * Free resources allocated for pictures. + * + */ + +static void close_graphics_file (void) +{ + + if (file != NULL) + { fclose (file); file = NULL; } + if (info != NULL) + { farfree (info); info = NULL; } + +}/* close_graphics_file */ + +/* + * init_pictures + * + * Prepare to draw pictures. Return true if pictures are available. + * + */ + +bool init_pictures (void) +{ + + /* Allocate memory for decompression */ + + table_val = (byte far *) farmalloc (3 * 3840); + table_ref = (word far *) (table_val + 3840); + + if (table_val == NULL) + return FALSE; + + /* Open the [first of two] graphics file[s] */ + + return open_graphics_file (1); + +}/* init_pictures */ + +/* + * reset_pictures + * + * Free resources allocated for decompression of pictures. + * + */ + +void reset_pictures (void) +{ + + if (table_val != NULL) + { farfree (table_val); table_val = NULL; } + if (file != NULL) + { fclose (file); file = NULL; } + if (info != NULL) + { farfree (info); info = NULL; } + +}/* reset_pictures */ + +/* + * load_picture_info + * + * Helper function for os_picture_data. Load all information about + * the given picture from the graphics file and store it in global + * variables. + * + */ + +static bool load_picture_info (int picture) +{ + byte far *ptr; + byte fileno; + + fileno = gheader.fileno; + + do { + + int i; + + /* Abort if there is a problem with the graphics file */ + + if (file == NULL) + return FALSE; + + /* Scan the directory of the current graphics file */ + + ptr = info; + + for (i = 0; i < gheader.images; i++) { + + if (picture == * (int far *) ptr) { + + READ_WORD (pic_width, ptr, PIC_WIDTH); + READ_WORD (pic_height, ptr, PIC_HEIGHT); + READ_WORD (pic_flags, ptr, PIC_FLAGS); + + pic_height *= scaler; + pic_width *= scaler; + + READ_BYTE (byte0 (pic_data), ptr, PIC_DATA + 2); + READ_BYTE (byte1 (pic_data), ptr, PIC_DATA + 1); + READ_BYTE (byte2 (pic_data), ptr, PIC_DATA); + + if (gheader.entry_size > PIC_COLOUR + 2) { + + READ_BYTE (byte0 (pic_colour), ptr, PIC_COLOUR + 2); + READ_BYTE (byte1 (pic_colour), ptr, PIC_COLOUR + 1); + READ_BYTE (byte2 (pic_colour), ptr, PIC_COLOUR); + + } else pic_colour = 0; + + return TRUE; + + } + + ptr += gheader.entry_size; + + } + + /* Close current graphics file */ + + close_graphics_file (); + + /* Open next graphics file */ + + open_graphics_file ((gheader.link != 0) ? gheader.fileno + 1 : 1); + + } while (fileno != gheader.fileno); + + return FALSE; + +}/* load_picture_info */ + +/* + * load_colour_map + * + * Helper function for os_draw_picture. Load a colour map from the + * graphics file then copy it to the palette registers. + * + */ + +static void load_colour_map (int first_colour) +{ + byte rgb[42]; + int n, i; + + fseek (file, pic_colour, SEEK_SET); + + /* Some pictures from Arthur mistakenly claim to have 16 colours */ + + if ((n = fgetc (file)) == 16) + n = 14; + + /* Each colour is stored in three bytes R-G-B */ + + fread (rgb, 3, n, file); + + /* MCGA boards can only handle R-G-B values from 0 to 63 */ + + for (i = 0; i < 42; i++) + rgb[i] = (rgb[i] * 63 + 128) / 255; + + /* Synchronise with vertical retrace */ + + while ((inportb (0x03da) & 8) == 0); + while ((inportb (0x03da) & 8) == 8); + + /* Copy colours to palette registers */ + + asm mov ax,0x1012 + asm mov bx,first_colour + asm mov cx,n + asm lea dx,rgb + asm push ss + asm pop es + asm int 0x10 + +}/* load_colour_map */ + +/* + * draw_picture + * + * Helper function for os_draw_picture. The real work of drawing a + * picture happens here. + * + */ + +#pragma warn -def + +static void pascal draw_picture (int y, int x) +{ + static int raise_bits[4] = { + 0x0100, 0x0300, 0x0700, 0x0000 + }; + + byte buf[512]; + byte far *screen; + byte transparent; + byte colour_shift; + int first_colour; + int code, prev_code; + int next_entry; + int bits_per_code; + int bits_shift; + int bits; + int current_y; + int current_x; + int bufpos; + int pixels; + int i; + + bufpos = 0; + + /* When the given picture provides a colour map then activate it. + This is only used for MCGA pictures; the colour map affects + every picture on the screen. The first colour to be defined is + colour 2. Every map defines up to 14 colours (colour 2 to 15). + These colours are not related to the standard Z-machine colour + scheme which remains unchanged. (This is based on the Amiga + interpreter which had to work with 16 colours. Colours 0 and 1 + were used for text; changing the text colours actually changed + palette entries 0 and 1. This interface uses the same trick in + Amiga mode.) */ + + if (display == _CGA_) + colour_shift = -2; + if (display == _EGA_) + colour_shift = 0; + if (display == _MCGA_) + { colour_shift = 32; first_colour = 34; } + if (display == _AMIGA_) + { colour_shift = -1; first_colour = 65; } + + if (pic_colour != 0) + load_colour_map (first_colour); + + fseek (file, pic_data, SEEK_SET); + + /* Bit 0 of "flags" indicates that the picture uses a transparent + colour, the top four bits tell us which colour it is. For CGA + and MCGA pictures this is always 0; for EGA pictures it can be + any colour between 0 and 15. */ + + transparent = 0xff; + + if (pic_flags & 1) + transparent = pic_flags >> 12; + + /* Prepare EGA hardware for setting pixels */ + + if (display >= _EGA_) { + outport (0x03ce, 0x0205); + outport (0x03ce, 0xff08); + } + + /* The uncompressed picture is a long sequence of bytes. Every + byte holds the colour of a pixel, starting at the top left, + stopping at the bottom right. We keep track of our position + in the current line. (There is a special case: CGA pictures + with no transparent colour are stored as bit patterns, i.e. + every byte holds the pattern for eight pixels. A pixel must + be white if the corresponding bit is set, otherwise it must + be black.) */ + + current_x = x + pic_width; + current_y = y - 1; + + /* The compressed picture is a stream of bits. We read the file + byte-wise, storing the current byte in the variable "bits". + Several bits make one code; the variable "bits_shift" helps + us to build the next code. */ + + bits_shift = 0; + bits = 0; + +reset_table: + + /* Clear the table. We use a table of 3840 entries. Each entry + consists of both a value and a reference to another table + entry. Following these references we get a sequence of + values. At the start of decompression all table entries are + undefined. Later we see how entries are set and used. */ + + next_entry = 1; + + /* At the start of decompression 9 bits make one code; during + the process this can rise to 12 bits per code. 9 bits are + sufficient to address both 256 literal values and 256 table + entries; 12 bits are sufficient to address both 256 literal + values and all 3840 table entries. The number of bits per + code rises with the number of table entries. When the table + is cleared, the number of bits per code drops back to 9. */ + + bits_per_code = 9; + +next_code: + + /* Read the next code from the graphics file. This requires + some confusing bit operations. Note that low bits always + come first. Usually there are a few bits left over from + the previous code; these bits must be used before further + bits are read from the graphics file. */ + + code = bits >> (8 - bits_shift); + + do { + + bits = fgetc (file); + + code |= bits << bits_shift; + + bits_shift += 8; + + } while (bits_shift < bits_per_code); + + bits_shift -= bits_per_code; + + code &= 0xfff >> (12 - bits_per_code); + + /* There are two codes with a special meaning. The first one + is 256 which clears the table and sets the number of bits + per code to 9. (This is necessary when the table is full.) + The second one is 257 which marks the end of the picture. + For the sake of efficiency, we drecement the code by 256. */ + + byte1 (code) --; + + if (code == 0) + goto reset_table; + if (code == 1) + return; + + /* Codes from 0 to 255 are literals, i.e. they represent a + plain byte value. Codes from 258 onwards are references + to table entries, i.e. they represent a sequence of byte + values (see the remarks on the table above). This means + that for each code one or several byte values are added + to the decompressed picture. But there is yet more work + to do: Every time we read a code one table entry is set. + As we said above, a table entry consist of both a value + and a reference to another table entry. If the current + code is a literal, then the value has to be set to this + literal; but if the code refers to a sequence of byte + values, then the value has to be set to the last byte of + this sequence. In any case, the reference is set to the + previous code. Finally, one should be aware that a code + may legally refer to the table entry which is currently + being set. This requires some extra care. */ + + table_ref[next_entry] = prev_code; + + prev_code = code; + + while (code >= 0) { + buf[bufpos++] = table_val[code]; + code = (short) table_ref[code]; + } + + if (next_entry == prev_code) + buf[0] = code; + + table_val[next_entry] = code; + + /* The number of bits per code is incremented when the current + number of bits no longer suffices to address all defined + table entries; but in any case the number of bits may never + be greater than 12. */ + + next_entry++; + + if (next_entry == raise_bits[bits_per_code - 9]) + bits_per_code++; + +reverse_buffer: + + /* Append the sequence of byte values (pixels) to the picture. + The order of the sequence must be reversed. (This is why we + have stored the sequence in a buffer; experiments show that + a buffer of 512 bytes suffices.) The sequence of values may + spread over several lines of the picture, so we must take + care to start a new line when we reach the right border of + the picture. */ + + if (current_x == x + pic_width) { + + screen = get_scrnptr (current_y); + + current_x -= pic_width; + current_y += scaler; + + } + + /* Either add a single pixel or a pattern of eight bits (b/w + CGA pictures without a transparent colour) to the current + line. Increment our position by 1 or 8 respectively. The + pixel may have to be painted several times if the scaling + factor is greater than one. */ + + if (display == _CGA_ && transparent == 0xff) { + + pixels = x + pic_width - current_x; + + if (pixels > 8) + pixels = 8; + + asm les bx,screen + asm mov dx,current_x + asm dec dx + asm push dx + asm mov cl,3 + asm shr dx,cl + asm add bx,dx + asm mov ax,es:[bx] + asm mov dx,0xffff + asm mov cl,byte ptr pixels + asm shr dl,cl + asm pop cx + asm and cl,7 + asm ror dx,cl + asm and ax,dx + asm mov dx,code + asm inc dh + asm ror dx,cl + asm or ax,dx + asm mov es:[bx],ax + + current_x += pixels; + + } else for (i = 0; i < scaler; i++) { + + _AH = code; + + if (_AH != transparent) { + + asm add ah,colour_shift + asm les bx,screen + asm mov dx,current_x + asm dec dx + + if (display != _MCGA_) { + + asm push dx + asm mov cl,3 + asm shr dx,cl + asm pop cx + asm and cl,7 + asm add bx,dx + asm mov al,es:[bx] + + if (display == _CGA_) { + asm mov dl,0x7f + asm ror dl,cl + asm and al,dl + asm xor ah,1 + asm ror ah,1 + asm shr ah,cl + asm or ah,al + } else { + asm mov al,0x80 + asm shr al,cl + asm mov dx,0x03cf + asm out dx,al + } + + } else asm add bx,dx + + asm mov es:[bx],ah + + if (display == _AMIGA_) { + asm add bx,80 + asm mov al,es:[bx] + asm mov es:[bx],ah + } + + } + + current_x++; + + } + + /* If there are no more values in the buffer then read the + next code from the file. Otherwise fetch the next byte + value from the buffer and continue painting the picture. */ + + if (bufpos == 0) + goto next_code; + + byte0 (code) = buf[--bufpos]; + + goto reverse_buffer; + +}/* draw_picture */ + +#pragma warn +def + +/* + * os_draw_picture + * + * Display a picture at the given coordinates. Top left is (1,1). + * + */ + +void os_draw_picture (int picture, int y, int x) +{ + + if (load_picture_info (picture)) + draw_picture (y, x); + +}/* os_draw_picture */ + +/* + * os_peek_colour + * + * Return the colour of the pixel below the cursor. This is used + * by V6 games to print text on top of pictures. The coulor need + * not be in the standard set of Z-machine colours. To handle + * this situation, Frotz extends the colour scheme: Values above + * 15 (and below 256) may be used by the interface to refer to + * non-standard colours. Of course, os_set_colour must be able to + * deal with these colours. Interfaces which refer to characters + * instead of pixels might return the current background colour + * instead. + * + */ + +int os_peek_colour (void) +{ + + if (display >= _CGA_) { + + asm mov ah,13 + asm mov bh,0 + asm mov cx,cursor_x + asm mov dx,cursor_y + asm int 0x10 + asm mov ah,0 + + return _AX + 16; + + } else return current_bg; + +}/* os_peek_colour */ + +/* + * os_picture_data + * + * Return true if the given picture is available. If so, write the + * width and height of the picture into the appropriate variables. + * Only when picture 0 is asked for, write the number of available + * pictures and the release number instead. + * + */ + +bool os_picture_data (int picture, int *height, int *width) +{ + bool avail; + + if (picture == 0) { + + avail = FALSE; + + /* This is the special case mentioned above. In practice, only + the release number is used; and even this is only used by + the DOS version of "Zork Zero". Infocom's Amiga interpreter + could not handle this feature, and the Amiga version of the + story file does not use it. */ + + pic_height = gheader.images; + pic_width = gheader.version; + + } else avail = load_picture_info (picture); + + *height = pic_height; + *width = pic_width; + + return avail; + +}/* os_picture_data */ diff --git a/bcsample.c b/bcsample.c new file mode 100644 index 0000000..bb50b23 --- /dev/null +++ b/bcsample.c @@ -0,0 +1,414 @@ +/* + * file "BCsample.c" + * + * Borland C front end, sound support + * + */ + +#include +#include +#include +#include +#include +#include "frotz.h" +#include "BCfrotz.h" + +#define SWAP_BYTES(v) {_AX=v;asm xchg al,ah;v=_AX;} + +#define READ_DSP(v) {while(!inportb(sound_adr+14)&0x80);v=inportb(sound_adr+10);} +#define WRITE_DSP(v) {while(inportb(sound_adr+12)&0x80);outportb(sound_adr+12,v);} + +extern void end_of_sound (void); + +static struct { + word prefix; + byte repeats; + byte base_note; + word frequency; + word unused; + word length; +} sheader; + +static current_sample = 0; + +static void interrupt (*vect) (void) = NULL; + +static play_part = 0; +static play_count = 0; + +static word sound_adr = 0; +static word sound_irq = 0; +static word sound_dma = 0; +static word sound_int = 0; +static word sound_ver = 0; + +static byte far *sample_data = NULL; + +static long sample_adr1 = 0; +static long sample_adr2 = 0; +static word sample_len1 = 0; +static word sample_len2 = 0; + +/* + * start_of_dma + * + * Start the DMA transfer to the sound board. + * + */ + +static void start_of_dma (long address, unsigned length) +{ + static unsigned dma_page_port[] = { + 0x87, 0x83, 0x81, 0x82 + }; + + length--; + + /* Set up DMA chip */ + + outportb (0x0a, 0x04 | sound_dma); + outportb (0x0c, 0x00); + outportb (0x0b, 0x48 | sound_dma); + outportb (2 * sound_dma, byte0 (address)); + outportb (2 * sound_dma, byte1 (address)); + outportb (dma_page_port[sound_dma], byte2 (address)); + outportb (2 * sound_dma + 1, byte0 (length)); + outportb (2 * sound_dma + 1, byte1 (length)); + outportb (0x0a, sound_dma); + + /* Play 8-bit mono sample */ + + WRITE_DSP (0x14) + WRITE_DSP (byte0 (length)) + WRITE_DSP (byte1 (length)) + +}/* start_of_dma */ + +/* + * end_of_dma + * + * This function is called when a hardware interrupt signals the + * end of the current sound. We may have to play the second half + * of the sound effect, or we may have to repeat it, or call the + * end_of_sound function when we are finished. + * + */ + +static void interrupt end_of_dma (void) +{ + + /* Play the second half, play another cycle or finish */ + + if (play_part == 1 && sample_len2 != 0) { + play_part = 2; + start_of_dma (sample_adr2, sample_len2); + } else if (play_count == 255 || --play_count != 0) { + play_part = 1; + start_of_dma (sample_adr1, sample_len1); + } else { + play_part = 0; + end_of_sound (); + } + + /* Tell interrupt controller(s) + sound board we are done */ + + outportb (0x20, 0x20); + + if (sound_irq >= 8) + outportb (0xa0, 0x20); + + inportb (sound_adr + 14); + +}/* end_of_dma */ + +/* + * init_sound + * + * Initialise the sound board and various sound related variables. + * + */ + +bool init_sound (void) +{ + const char *settings = getenv ("BLASTER"); + word irc_mask_port; + + /* Read the IRQ, port address, DMA channel and SB version */ + + if ((settings = getenv ("BLASTER")) == NULL) + return FALSE; + + sound_irq = dectoi (strchr (settings, 'I') + 1); + sound_adr = hextoi (strchr (settings, 'A') + 1); + sound_dma = dectoi (strchr (settings, 'D') + 1); + sound_ver = dectoi (strchr (settings, 'T') + 1); + + /* Reset mixer chip and DSP */ + + outportb (sound_adr + 4, 0); + outportb (sound_adr + 5, 0); + + outportb (sound_adr + 6, 1); + inportb (sound_adr + 6); + inportb (sound_adr + 6); + inportb (sound_adr + 6); + outportb (sound_adr + 6, 0); + + /* Turn on speakers */ + + WRITE_DSP (0xd1) + + /* Install the end_of_dma interrupt */ + + if (sound_irq < 8) { + irc_mask_port = 0x21; + sound_int = 0x08 + sound_irq; + } else { + irc_mask_port = 0xa1; + sound_int = 0x68 + sound_irq; + } + + vect = getvect (sound_int); setvect (sound_int, end_of_dma); + + /* Allocate 64KB RAM for sample data */ + + if ((sample_data = (byte far *) farmalloc (0x10000L)) == NULL) + return FALSE; + + word0 (sample_adr1) = FP_OFF (sample_data) | (FP_SEG (sample_data) << 4); + word1 (sample_adr1) = FP_SEG (sample_data) >> 12; + word0 (sample_adr2) = 0; + word1 (sample_adr2) = word1 (sample_adr1) + 1; + + /* Enable the end_of_dma interrupt */ + + outportb (0x20, 0x20); + + if (sound_irq >= 8) + outportb (0xa0, 0x20); + + outportb (irc_mask_port, inportb (irc_mask_port) & ~(1 << (sound_irq & 7))); + + /* Indicate success */ + + return TRUE; + +}/* init_sound */ + +/* + * reset_sound + * + * Free resources allocated for playing samples. + * + */ + +void reset_sound (void) +{ + + os_stop_sample (); + + if (sample_data != NULL) + { farfree (sample_data); sample_data = NULL; } + if (sound_adr != 0) + { setvect (sound_int, vect); sound_adr = 0; } + +}/* reset_sound */ + +/* + * os_beep + * + * Play a beep sound. Ideally, the sound should be high- (number == 1) + * or low-pitched (number == 2). + * + */ + +void os_beep (int number) +{ + word T = 888 * number; + + outportb (0x43, 0xb6); + outportb (0x42, lo (T)); + outportb (0x42, hi (T)); + outportb (0x61, inportb (0x61) | 3); + + delay (75); + + outportb (0x61, inportb (0x61) & ~3); + +}/* os_beep */ + +/* + * os_prepare_sample + * + * Load the sample from the disk. + * + */ + +void os_prepare_sample (int number) +{ + + os_stop_sample (); + + /* Exit if the sound board isn't set up properly */ + + if (sample_data == NULL) + return; + if (sound_adr == 0) + return; + + /* Continue only if the desired sample is not already present */ + + if (current_sample != number) { + + char sample_name[MAX_FILE_NAME + 1]; + char numstr[2]; + FILE *fp; + + /* Build sample file name */ + + strcpy (sample_name, "sound\\"); + + numstr[0] = '0' + number / 10; + numstr[1] = '0' + number % 10; + + strncat (sample_name, stripped_story_name, 6); + strncat (sample_name, numstr, 2); + strncat (sample_name, ".snd", 4); + + /* Open sample file */ + + if ((fp = fopen (sample_name, "rb")) == NULL) + return; + + /* Load header and sample data */ + + fread (&sheader, sizeof (sheader), 1, fp); + + SWAP_BYTES (sheader.frequency) + SWAP_BYTES (sheader.length) + + fread (sample_data, 1, sheader.length, fp); + + sample_len1 = -word0 (sample_adr1); + + if (sample_len1 > sheader.length || sample_len1 == 0) + sample_len1 = sheader.length; + + sample_len2 = sheader.length - sample_len1; + + WRITE_DSP (0x40) + WRITE_DSP (256 - 1000000L / sheader.frequency) + + current_sample = number; + + /* Close sample file */ + + fclose (fp); + + } + +}/* os_prepare_sample */ + +/* + * os_start_sample + * + * Play the given sample at the given volume (ranging from 1 to 8 and + * 255 meaning a default volume). The sound is played once or several + * times in the background (255 meaning forever). The end_of_sound + * function is called as soon as the sound finishes. + * + */ + +void os_start_sample (int number, int volume, int repeats) +{ + + os_stop_sample (); + + /* Exit if the sound board isn't set up properly */ + + if (sample_data == NULL) + return; + if (sound_adr == 0) + return; + + /* Load new sample */ + + os_prepare_sample (number); + + /* Continue only if the sample's in memory now */ + + if (current_sample == number) { + + play_count = repeats; + + if (sound_ver < 6) { /* Set up SB pro mixer chip */ + + volume = (volume != 255) ? 7 + volume : 15; + + outportb (sound_adr + 4, 0x04); + outportb (sound_adr + 5, (volume << 4) | volume); + outportb (sound_adr + 4, 0x22); + outportb (sound_adr + 5, 0xff); + + } else { /* Set up SB16 mixer chip */ + + /* Many thanks to Linards Ticmanis for writing this part! */ + + volume = (volume != 255) ? 127 + 16 * volume : 255; + + outportb (sound_adr + 4, 0x32); + outportb (sound_adr + 5, volume); + outportb (sound_adr + 4, 0x33); + outportb (sound_adr + 5, volume); + outportb (sound_adr + 4, 0x30); + outportb (sound_adr + 5, 0xff); + outportb (sound_adr + 4, 0x31); + outportb (sound_adr + 5, 0xff); + + } + + play_part = 1; + start_of_dma (sample_adr1, sample_len1); + + } + +}/* os_start_sample */ + +/* + * os_stop_sample + * + * Turn off the current sample. + * + */ + +void os_stop_sample (void) +{ + + play_part = 0; + + /* Exit if the sound board isn't set up properly */ + + if (sample_data == NULL) + return; + if (sound_adr == 0) + return; + + /* Tell DSP to stop the current sample */ + + WRITE_DSP (0xd0) + +}/* os_stop_sample */ + +/* + * os_finish_with_sample + * + * Remove the current sample from memory (if any). + * + */ + +void os_finish_with_sample (void) +{ + + os_stop_sample (); /* we keep 64KB allocated all the time */ + +}/* os_finish_with_sample */ diff --git a/bcscreen.c b/bcscreen.c new file mode 100644 index 0000000..ed42e2e --- /dev/null +++ b/bcscreen.c @@ -0,0 +1,298 @@ +/* + * file "BCscreen.c" + * + * Borland C front end, screen manipulation + * + */ + +#include +#include +#include "frotz.h" +#include "BCfrotz.h" + +/* + * get_scrnptr + * + * Return a pointer to the given line in video RAM. + * + */ + +byte far *get_scrnptr (int y) +{ + + if (display == _CGA_) + return MK_FP ((y & 1) ? 0xba00 : 0xb800, 40 * (y & ~1)); + else if (display == _MCGA_) + return MK_FP (0xa000, 320 * y); + else + return MK_FP (0xa000, 80 * y); + +}/* get_scrnptr */ + +/* + * clear_byte + * + * Helper function for clear_line. + * + */ + +static void clear_byte (byte far *scrn, word mask) +{ + + if (display == _CGA_) + + if (scrn_attr == 0) + *scrn &= ~mask; + else + *scrn |= mask; + + else { + + outport (0x03ce, 0x0205); + + outportb (0x03ce, 0x08); + outportb (0x03cf, mask); + + asm les bx,scrn + asm mov al,es:[bx] + asm mov al,scrn_attr + asm mov es:[bx],al + + } + +}/* clear_byte */ + +/* + * clear_line + * + * Helper function for os_erase_area. + * + */ + +static void clear_line (int y, int left, int right) +{ + byte far *scrn = get_scrnptr (y); + + if (display == _MCGA_) + + _fmemset (scrn + left, scrn_attr, right - left + 1); + + else { + + word mask1 = 0x00ff >> (left & 7); + word mask2 = 0xff80 >> (right & 7); + + int x = right / 8 - left / 8; + + scrn += left / 8; + + if (x == 0) { + mask1 &= mask2; + mask2 = 0; + } + + /* Clear first byte */ + + clear_byte (scrn++, mask1); + + /* Clear middle bytes */ + + if (display >= _EGA_) + outport (0x03ce, 0xff08); + + while (--x > 0) + *scrn++ = scrn_attr; + + /* Clear last byte */ + + clear_byte (scrn, mask2); + + } + +}/* clear_line */ + +/* + * os_erase_area + * + * Fill a rectangular area of the screen with the current background + * colour. Top left coordinates are (1,1). The cursor does not move. + * + */ + +void os_erase_area (int top, int left, int bottom, int right) +{ + int y; + + top--; + left--; + bottom--; + right--; + + if (display <= _TEXT_) { + + asm mov ax,0x0600 + asm mov ch,byte ptr top + asm mov cl,byte ptr left + asm mov dh,byte ptr bottom + asm mov dl,byte ptr right + asm mov bh,scrn_attr + asm int 0x10 + + } else + + for (y = top; y <= bottom; y++) + clear_line (y, left, right); + +}/* os_erase_area */ + +/* + * copy_byte + * + * Helper function for copy_line. + * + */ + +static void copy_byte (byte far *scrn1, byte far *scrn2, byte mask) +{ + int i; + + if (display == _CGA_) + + *scrn1 = (*scrn1 & ~mask) | (*scrn2 & mask); + + else { + + outport (0x03ce, 0x0005); + + outportb (0x03ce, 0x08); + outportb (0x03cf, mask); + + outportb (0x03ce, 0x04); + outportb (0x03c4, 0x02); + + for (i = 0; i < 4; i++) { + + outportb (0x03cf, i); + outportb (0x03c5, 1 << i); + + asm les bx,scrn2 + asm mov ah,es:[bx] + asm les bx,scrn1 + asm mov al,es:[bx] + asm mov es:[bx],ah + + } + + outportb (0x03c5, 0x0f); + + } + +}/* copy_byte */ + +/* + * copy_line + * + * Helper function for os_scroll_area. + * + */ + +static void copy_line (int y1, int y2, int left, int right) +{ + byte far *scrn1 = get_scrnptr (y1); + byte far *scrn2 = get_scrnptr (y2); + + if (display == _MCGA_) + + _fmemcpy (scrn1 + left, scrn2 + left, right - left + 1); + + else { + + word mask1 = 0x00ff >> (left & 7); + word mask2 = 0xff80 >> (right & 7); + + int x = right / 8 - left / 8; + + scrn1 += left / 8; + scrn2 += left / 8; + + if (x == 0) { + mask1 &= mask2; + mask2 = 0; + } + + /* Copy first byte */ + + copy_byte (scrn1++, scrn2++, mask1); + + /* Copy middle bytes */ + + if (display >= _EGA_) + outport (0x03ce, 0x0105); + + while (--x > 0) + *scrn1++ = *scrn2++; + + /* Copy last byte */ + + copy_byte (scrn1, scrn2, mask2); + + } + +}/* copy_line */ + +/* + * os_scroll_area + * + * Scroll a rectangular area of the screen up (units > 0) or down + * (units < 0) and fill the empty space with the current background + * colour. Top left coordinates are (1,1). The cursor stays put. + * + */ + +void os_scroll_area (int top, int left, int bottom, int right, int units) +{ + int y; + + top--; + left--; + bottom--; + right--; + + if (display <= _TEXT_) { + + asm mov ah,6 + asm mov bx,units + asm cmp bx,0 + asm jg scroll + asm mov ah,7 + asm neg bx + scroll: + asm mov al,bl + asm mov ch,byte ptr top + asm mov cl,byte ptr left + asm mov dh,byte ptr bottom + asm mov dl,byte ptr right + asm mov bh,scrn_attr + asm int 0x10 + + } else + + if (units > 0) + + for (y = top; y <= bottom; y++) + + if (y <= bottom - units) + copy_line (y, y + units, left, right); + else + clear_line (y, left, right); + + else + + for (y = bottom; y >= top; y--) + + if (y >= top - units) + copy_line (y, y + units, left, right); + else + clear_line (y, left, right); + +}/* os_scroll_area */ diff --git a/bctext.c b/bctext.c new file mode 100644 index 0000000..4ab9100 --- /dev/null +++ b/bctext.c @@ -0,0 +1,842 @@ +/* + * file "BCtext.c" + * + * Borland C front end, text functions + * + */ + +#include +#include +#include +#include +#include +#include "frotz.h" +#include "BCfrotz.h" + +extern byte far *get_scrnptr (int); + +int current_bg = 0; +int current_fg = 0; +int current_style = 0; +int current_font = 0; + +byte text_bg = 0; +byte text_fg = 0; +byte bg = 0; +byte fg = 0; +byte scrn_attr = 0; + +int cursor_x = 0; +int cursor_y = 0; + +char latin1_to_ascii[] = + " ! c L >o> 1/41/23/4? " + "A A A A Ae A AE C E E E E I I I I " + "Th N O O O O Oe * O U U U Ue Y Th ss " + "a a a a ae a ae c e e e e i i i i " + "th n o o o o oe : o u u u ue y th y "; + +char latin1_to_ibm[] = { + 0x20, 0xad, 0xbd, 0x9c, 0xcf, 0xbe, 0xdd, 0xf5, + 0xf9, 0xb8, 0xa6, 0xae, 0xaa, 0xf0, 0xa9, 0xee, + 0xf8, 0xf1, 0xfd, 0xfc, 0xef, 0xe6, 0xf4, 0xfa, + 0xf7, 0xfb, 0xa7, 0xaf, 0xac, 0xab, 0xf3, 0xa8, + 0xb7, 0xb5, 0xb6, 0xc7, 0x8e, 0x8f, 0x92, 0x80, + 0xd4, 0x90, 0xd2, 0xd3, 0xde, 0xd6, 0xd7, 0xd8, + 0xd1, 0xa5, 0xe3, 0xe0, 0xe2, 0xe5, 0x99, 0x9e, + 0x9d, 0xeb, 0xe9, 0xea, 0x9a, 0xed, 0xe8, 0xe1, + 0x85, 0xa0, 0x83, 0xc6, 0x84, 0x86, 0x91, 0x87, + 0x8a, 0x82, 0x88, 0x89, 0x8d, 0xa1, 0x8c, 0x8b, + 0xd0, 0xa4, 0x95, 0xa2, 0x93, 0xe4, 0x94, 0xf6, + 0x9b, 0x97, 0xa3, 0x96, 0x81, 0xec, 0xe7, 0x98 +}; + +static byte far *graphics_font = NULL; +static byte far *mcga_font = NULL; +static byte far *mcga_width = NULL; +static word far *serif_font = NULL; +static byte far *serif_width = NULL; + +/* + * get_font + * + * Helper function for load_fonts. + * + */ + +static void far *get_chunk (FILE *fp, int num) +{ + static chunk_size[] = { + 0x0300, + 0x0360, + 0x18c0, + 0x18c0, + 0x18c0, + 0x18c0 }; + + void far *ptr; + long offs = 0; + int i; + + ptr = farmalloc (chunk_size[num]); + + for (i = 0; i <= num; i++) + offs -= chunk_size[i]; + + fseek (fp, offs, SEEK_END); + + if (ptr == NULL || !fread (ptr, chunk_size[num], 1, fp)) + os_fatal ("Cannot load fonts"); + + return ptr; + +}/* get_chunk */ + +/* + * load_fonts + * + * Load the proportional and graphics fonts. In the release version all + * font data is appended to the end of the executable. + * + */ + +void load_fonts (const char *progname) +{ + FILE *fp; + + if ((fp = fopen (progname, "rb")) == NULL) + os_fatal ("Cannot load fonts"); + + if (display == _MCGA_) { + + mcga_font = get_chunk (fp, 1); + mcga_width = (byte far *) mcga_font + 0x300; + + } else graphics_font = get_chunk (fp, 0); + + if (display == _AMIGA_ && user_font != 0) { + + serif_font = get_chunk (fp, 1 + user_font); + serif_width = (byte far *) serif_font + 0x1800; + + } + + fclose (fp); + +}/* load_fonts */ + +/* + * os_font_data + * + * Return true if the given font is available. The font can be + * + * TEXT_FONT + * PICTURE_FONT + * GRAPHICS_FONT + * FIXED_WIDTH_FONT + * + * The font size should be stored in "height" and "width". If the given + * font is unavailable then these values must _not_ be changed. + * + */ + +int os_font_data (int font, int *height, int *width) +{ + + /* All fonts of this interface have the same size */ + + *height = h_font_height; + *width = h_font_width; + + /* Not every font is available in every mode */ + + if (font == TEXT_FONT) + return TRUE; + if (font == GRAPHICS_FONT && (display == _CGA_ || display >= _EGA_)) + return TRUE; + if (font == FIXED_WIDTH_FONT) + return TRUE; + + /* Unavailable font */ + + return FALSE; + +}/* os_font_data */ + +/* + * switch_scrn_attr + * + * Parts of the screen are usually erased to background colour. However, + * for deleting text in the input routine it can be useful to erase to + * the current text background colour. The two colours can be different, + * for example when the reverse text style is used. This helper function + * toggles between the two possible behaviours. + * + */ + +void switch_scrn_attr (bool flag) +{ + byte scrn_bg; + byte scrn_fg; + + if (flag) { + scrn_bg = text_bg; + scrn_fg = text_fg; + } else { + scrn_bg = bg; + scrn_fg = fg; + } + + if (display <= _TEXT_) + scrn_attr = (scrn_bg << 4) | scrn_fg; + else if (display == _CGA_) + scrn_attr = (scrn_bg != BLACK) ? 0xff : 0x00; + else + scrn_attr = scrn_bg; + +}/* switch_scrn_attr */ + +/* + * adjust_style + * + * Set the current colours. This combines the current colour selection + * and the current text style. + * + */ + +static void adjust_style (void) +{ + static byte amiga_palette[][3] = { + { 0x00, 0x00, 0x00 }, + { 0x2a, 0x00, 0x00 }, + { 0x00, 0x2a, 0x00 }, + { 0x3f, 0x3f, 0x15 }, + { 0x00, 0x00, 0x2a }, + { 0x2a, 0x00, 0x2a }, + { 0x00, 0x2a, 0x2a }, + { 0x3f, 0x3f, 0x3f }, + { 0x30, 0x30, 0x30 }, + { 0x20, 0x20, 0x20 }, + { 0x10, 0x10, 0x10 }, + }; + + static byte pc_colour[] = { + BLACK, + RED, + GREEN, + YELLOW, + BLUE, + MAGENTA, + CYAN, + WHITE, + DARKGRAY + }; + + static byte palette_bg = 0xff; + static byte palette_fg = 0xff; + + fg = current_fg; + bg = current_bg; + + /* V6 game, Amiga mode: Alter the palette registers if the colours + of window 0 have changed. DAC register #79 holds the foreground, + DAC register #64 the background colour. */ + + if (display == _AMIGA_ && h_version == V6 && cwin == 0) { + + if (fg < 16 && fg != palette_fg) { + + byte R = amiga_palette[fg - 2][0]; + byte G = amiga_palette[fg - 2][1]; + byte B = amiga_palette[fg - 2][2]; + + asm mov ax,0x1010 + asm mov bx,79 + asm mov dh,R + asm mov ch,G + asm mov cl,B + asm int 0x10 + + palette_fg = fg; + + } + + if (bg < 16 && bg != palette_bg) { + + byte R = amiga_palette[bg - 2][0]; + byte G = amiga_palette[bg - 2][1]; + byte B = amiga_palette[bg - 2][2]; + + asm mov ax,0x1010 + asm mov bx,64 + asm mov dh,R + asm mov ch,G + asm mov cl,B + asm int 0x10 + + palette_bg = bg; + + } + + } + + /* Handle colours */ + + if (fg < 16) + + if (display == _MONO_) + fg = (fg == WHITE_COLOUR) ? LIGHTGRAY : BLACK; + else if (h_version == V6 && display == _AMIGA_) + fg = (palette_fg == fg) ? 15 : 0; + else + fg = pc_colour[fg - 2]; + + else fg -= 16; + + if (bg < 16) + + if (display == _MONO_) + bg = (bg == WHITE_COLOUR) ? LIGHTGRAY : BLACK; + else if (h_version == V6 && display == _AMIGA_) + bg = (palette_bg == bg) ? 0 : 15; + else + bg = pc_colour[bg - 2]; + + else bg -= 16; + + /* Handle reverse text style */ + + if (current_style & REVERSE_STYLE) { + text_fg = (user_reverse_fg != -1) ? user_reverse_fg : bg; + text_bg = (user_reverse_bg != -1) ? user_reverse_bg : fg; + } else { + text_fg = fg; + text_bg = bg; + } + + /* Handle emphasis style */ + + if (current_style & EMPHASIS_STYLE) { + + if (display == _MONO_ && text_bg == BLACK) + text_fg = BLUE; /* blue in monochrome mode is underlined */ + if (display == _TEXT_) + text_fg = (user_emphasis != -1) ? user_emphasis : YELLOW; + + } + + /* Handle boldface style */ + + if (current_style & BOLDFACE_STYLE) { + + if (display == _MONO_) + text_fg = WHITE; + if (display == _TEXT_) + text_fg ^= 8; + + } + + /* Set the screen attribute for scrolling and erasing */ + + switch_scrn_attr (FALSE); + +}/* adjust_style */ + +/* + * os_set_colour + * + * Set the foreground and background colours which can be: + * + * DEFAULT_COLOUR + * BLACK_COLOUR + * RED_COLOUR + * GREEN_COLOUR + * YELLOW_COLOUR + * BLUE_COLOUR + * MAGENTA_COLOUR + * CYAN_COLOUR + * WHITE_COLOUR + * + * MS-DOS 320 columns MCGA mode only: + * + * GREY_COLOUR + * + * Amiga only: + * + * LIGHTGREY_COLOUR + * MEDIUMGREY_COLOUR + * DARKGREY_COLOUR + * + * There may be more colours in the range from 16 to 255; see the remarks + * on os_peek_colour. + * + */ + +void os_set_colour (int new_foreground, int new_background) +{ + + current_fg = new_foreground; + current_bg = new_background; + + /* Apply changes */ + + adjust_style (); + +}/* os_set_colour */ + +/* + * os_set_text_style + * + * Set the current text style. Following flags can be set: + * + * REVERSE_STYLE + * BOLDFACE_STYLE + * EMPHASIS_STYLE (aka underline aka italics) + * FIXED_WIDTH_STYLE + * + */ + +void os_set_text_style (int new_style) +{ + + current_style = new_style; + + /* Apply changes */ + + adjust_style (); + +}/* os_set_text_style */ + +/* + * os_set_font + * + * Set the font for text output. The interpreter takes care not to + * choose fonts which aren't supported by the interface. + * + */ + +void os_set_font (int new_font) +{ + + current_font = new_font; + +}/* os_set_font */ + +/* + * write_pattern + * + * Helper function for drawing characters in EGA and Amiga mode. + * + */ + +void write_pattern (byte far *screen, byte val, byte mask) +{ + + if (mask != 0) { + + if (display == _CGA_) { + + if (text_bg == BLACK) + *screen &= ~mask; + if (text_bg == WHITE) + *screen |= mask; + if (text_fg != text_bg) + *screen ^= val; + + } else if (display == _MCGA_) { + + byte i; + + for (i = 0x80; (mask & i) != 0; i >>= 1) + *screen++ = (val & i) ? text_fg : text_bg; + + } else { + + asm mov dx,0x03cf + asm mov al,mask + asm out dx,al + asm les bx,screen + asm mov ch,text_bg + asm mov al,es:[bx] + asm mov es:[bx],ch + asm mov al,val + asm out dx,al + asm mov ch,text_fg + asm mov al,es:[bx] + asm mov es:[bx],ch + + } + + } + +}/* write_pattern */ + +/* + * os_display_char + * + * Display a character of the current font using the current colours and + * text style. The cursor moves to the next position. Printable codes are + * all ASCII values from 32 to 126, ISO Latin-1 characters from 160 to + * 255, ZC_GAP (gap between two sentences) and ZC_INDENT (paragraph + * indentation). The screen should not be scrolled after printing to the + * bottom right corner. + * + */ + +void os_display_char (zchar c) +{ + int width = os_char_width (c); + + /* Handle accented characters */ + + if (c >= ZC_LATIN1_MIN && (story_id != BEYOND_ZORK || (h_flags & GRAPHICS_FLAG))) + + if (display == _CGA_ || display == _MCGA_) { + + char *ptr = latin1_to_ascii + 3 * (c - ZC_LATIN1_MIN); + + char c1 = *ptr++; + char c2 = *ptr++; + char c3 = *ptr++; + + os_display_char (c1); + + if (c2 != ' ') + os_display_char (c2); + if (c3 != ' ') + os_display_char (c3); + + return; + + } else if (display == _AMIGA_ && current_font == TEXT_FONT && !(current_style & FIXED_WIDTH_STYLE) && user_font != 0) { + + if (c >= ZC_LATIN1_MIN) + c -= 32; + + } else c = latin1_to_ibm[c - ZC_LATIN1_MIN]; + + /* Handle special indentations */ + + if (c == ZC_INDENT) + { os_display_char (' '); os_display_char (' '); os_display_char (' '); return; } + if (c == ZC_GAP) + { os_display_char (' '); os_display_char (' '); return; } + + /* Display character */ + + if (display <= _TEXT_) { + + asm mov ah,2 + asm mov bh,0 + asm mov dh,byte ptr cursor_y + asm mov dl,byte ptr cursor_x + asm int 0x10 + asm mov ah,9 + asm mov bh,0 + asm mov bl,byte ptr text_bg + asm mov cl,4 + asm shl bl,cl + asm or bl,byte ptr text_fg + asm mov cx,1 + asm mov al,byte ptr c + asm int 0x10 + + } else { + + void far *table; + word mask; + word val; + byte mask0; + byte mask1; + int align; + int underline; + int boldface; + int type; + + int shift = (display != _MCGA_) ? cursor_x % 8 : 0; + int offset = (display != _MCGA_) ? cursor_x / 8 : cursor_x; + + int i; + + if (current_font == GRAPHICS_FONT) { + table = graphics_font + 8 * (c - 32); + mask = 0xff; + underline = -1; + boldface = -1; + align = 0; + type = 1; + } else if (display == _AMIGA_ && current_font == TEXT_FONT && !(current_style & FIXED_WIDTH_STYLE) && user_font != 0) { + table = serif_font + 16 * (c - 32); + mask = 0xffff << (16 - width); + underline = 14; + boldface = 1; + align = 0; + type = 2; + } else if (display == _CGA_) { + table = (byte far *) MK_FP (0xf000, 0xfa6e) + 8 * c; + mask = 0xff; + underline = 7; + boldface = (user_bold_typing != -1) ? 1 : -1; + align = 0; + type = 3; + } else if (display >= _EGA_) { + table = (byte far *) getvect (0x43) + h_font_height * c; + mask = 0xff; + underline = h_font_height - 1; + boldface = (user_bold_typing != -1) ? 1 : -1; + align = 0; + type = 3; + } else { + table = mcga_font + 8 * (c - 32); + mask = 0xff & (0xff << (8 - width)); + underline = 7; + boldface = -1; + align = (width + 1 - mcga_width[c - 32]) / 2; + type = 3; + } + + mask0 = mask >> shift; + mask1 = mask << (8 - shift); + + if (!(current_style & BOLDFACE_STYLE)) + boldface = -1; + if (!(current_style & EMPHASIS_STYLE)) + underline = -1; + + if (display >= _EGA_) { + outport (0x03ce, 0x0205); + outport (0x03ce, 0xff08); + } + + for (i = 0; i < h_font_height; i++) { + + byte far *screen = get_scrnptr (cursor_y + i) + offset; + + if (type == 1) + val = *((byte far *) table + 8 * i / h_font_height); + if (type == 2) + val = *((word far *) table + i); + if (type == 3) + val = *((byte far *) table + i); + + if (align != 0) + val >>= align; + + if (boldface == 1) + val |= val >> 1; + if (underline == i) + val ^= mask; + + if (type == 2) + write_pattern (screen++, val >> (8 + shift), mask >> (8 + shift)); + + write_pattern (screen + 0, val >> shift, mask0); + write_pattern (screen + 1, val << (8 - shift), mask1); + + } + + } + + /* Move cursor to next position */ + + cursor_x += width; + +}/* os_display_char */ + +/* + * os_display_string + * + * Pass a string of characters to os_display_char. + * + */ + +void os_display_string (const zchar *s) +{ + + zchar c; + + while ((c = *s++) != 0) + + if (c == ZC_NEW_FONT || c == ZC_NEW_STYLE) { + + int arg = *s++; + + if (c == ZC_NEW_FONT) + os_set_font (arg); + if (c == ZC_NEW_STYLE) + os_set_text_style (arg); + + } else os_display_char (c); + +}/* os_display_string */ + +/* + * os_char_width + * + * Return the width of the character in screen units. + * + */ + +int os_char_width (zchar c) +{ + + /* Handle accented characters */ + + if (c >= ZC_LATIN1_MIN && (story_id != BEYOND_ZORK || (h_flags & GRAPHICS_FLAG))) + + if (display == _CGA_ || display == _MCGA_) { + + const char *ptr = latin1_to_ascii + 3 * (c - ZC_LATIN1_MIN); + + int width = 0; + + char c1 = *ptr++; + char c2 = *ptr++; + char c3 = *ptr++; + + width = os_char_width (c1); + + if (c2 != ' ') + width += os_char_width (c2); + if (c3 != ' ') + width += os_char_width (c3); + + return width; + + } else if (display == _AMIGA_ && current_font == TEXT_FONT && !(current_style & FIXED_WIDTH_STYLE) && user_font != 0) + + if (c >= ZC_LATIN1_MIN) + c -= 32; + + /* Handle special indentations */ + + if (c == ZC_INDENT) + return 3 * os_char_width (' '); + if (c == ZC_GAP) + return 2 * os_char_width (' '); + + /* Calculate width */ + + if (display <= _TEXT_) + return 1; + if (display == _CGA_) + return 8; + if (display == _EGA_) + return 8; + + if (current_font == GRAPHICS_FONT) + return 8; + if (current_font == FIXED_WIDTH_FONT || (current_style & FIXED_WIDTH_STYLE) || (display == _AMIGA_ && user_font == 0)) + return (display == _AMIGA_) ? 8 : 5; + + if (display == _MCGA_) + return mcga_width[c - 32]; + if (display == _AMIGA_) + return serif_width[c - 32] + ((current_style & BOLDFACE_STYLE) ? 1 : 0); + + return 0; + +}/* os_char_width */ + +/* + * os_string_width + * + * Calculate the length of a word in screen units. Apart from letters, + * the word may contain special codes: + * + * ZC_NEW_STYLE - next character is a new text style + * ZC_NEW_FONT - next character is a new font + * + */ + +int os_string_width (const zchar *s) +{ + int width = 0; + + int saved_font = current_font; + int saved_style = current_style; + + zchar c; + + while ((c = *s++) != 0) + + if (c == ZC_NEW_STYLE || c == ZC_NEW_FONT) { + + int arg = *s++; + + if (c == ZC_NEW_FONT) + current_font = arg; + if (c == ZC_NEW_STYLE) + current_style = arg; + + } else width += os_char_width (c); + + current_font = saved_font; + current_style = saved_style; + + return width; + +}/* os_string_width */ + +/* + * os_set_cursor + * + * Place the text cursor at the given coordinates. Top left is (1,1). + * + */ + +void os_set_cursor (int y, int x) +{ + + cursor_y = y - 1; + cursor_x = x - 1; + +}/* os_set_cursor */ + +/* + * os_more_prompt + * + * Display a MORE prompt, wait for a keypress and remove the MORE + * prompt from the screen. + * + */ + +void os_more_prompt (void) +{ + int saved_x; + + /* Save text font and style */ + + int saved_font = current_font; + int saved_style = current_style; + + /* Choose plain text style */ + + current_font = TEXT_FONT; + current_style = 0; + + adjust_style (); + + /* Wait until the user presses a key */ + + saved_x = cursor_x; + + os_display_string ((zchar *) "[MORE]"); + os_read_key (0, TRUE); + + os_erase_area (cursor_y + 1, + saved_x + 1, + cursor_y + h_font_height, + cursor_x + 1); + + cursor_x = saved_x; + + /* Restore text font and style */ + + current_font = saved_font; + current_style = saved_style; + + adjust_style (); + +}/* os_more_prompt */ diff --git a/buffer.c b/buffer.c new file mode 100644 index 0000000..6aa48a1 --- /dev/null +++ b/buffer.c @@ -0,0 +1,112 @@ +/* + * buffer.c + * + * Text buffering and word wrapping + * + */ + +#include "frotz.h" + +extern void stream_char (zchar); +extern void stream_word (const zchar *); +extern void stream_new_line (void); + +static zchar buffer[TEXT_BUFFER_SIZE]; +static bufpos = 0; + +static zchar prev_c = 0; + +/* + * flush_buffer + * + * Copy the contents of the text buffer to the output streams. + * + */ + +void flush_buffer (void) +{ + static bool locked = FALSE; + + /* Make sure we stop when flush_buffer is called from flush_buffer. + Note that this is difficult to avoid as we might print a newline + during flush_buffer, which might cause a newline interrupt, that + might execute any arbitrary opcode, which might flush the buffer. */ + + if (locked || bufpos == 0) + return; + + /* Send the buffer to the output streams */ + + buffer[bufpos] = 0; + + locked = TRUE; stream_word (buffer); locked = FALSE; + + /* Reset the buffer */ + + bufpos = 0; + prev_c = 0; + +}/* flush_buffer */ + +/* + * print_char + * + * High level output function. + * + */ + +void print_char (zchar c) +{ + static bool flag = FALSE; + + if (message || ostream_memory || enable_buffering) { + + if (!flag) { + + /* Characters 0 and ZC_RETURN are special cases */ + + if (c == ZC_RETURN) + { new_line (); return; } + if (c == 0) + return; + + /* Flush the buffer before a whitespace or after a hyphen */ + + if (c == ' ' || c == ZC_INDENT || c == ZC_GAP || prev_c == '-' && c != '-') + flush_buffer (); + + /* Set the flag if this is part one of a style or font change */ + + if (c == ZC_NEW_FONT || c == ZC_NEW_STYLE) + flag = TRUE; + + /* Remember the current character code */ + + prev_c = c; + + } else flag = FALSE; + + /* Insert the character into the buffer */ + + buffer[bufpos++] = c; + + if (bufpos == TEXT_BUFFER_SIZE) + runtime_error ("Text buffer overflow"); + + } else stream_char (c); + +}/* print_char */ + +/* + * new_line + * + * High level newline function. + * + */ + +void new_line (void) +{ + + flush_buffer (); stream_new_line (); + +}/* new_line */ diff --git a/fastmem.c b/fastmem.c new file mode 100644 index 0000000..d121f1b --- /dev/null +++ b/fastmem.c @@ -0,0 +1,910 @@ +/* + * fastmem.c + * + * Memory related functions (fast version without virtual memory) + * + */ + +#include +#include +#include "frotz.h" + +#ifdef __MSDOS__ + +#include + +#define malloc(size) farmalloc (size) +#define realloc(size,p) farrealloc (size,p) +#define free(size) farfree (size) +#define memcpy(d,s,n) _fmemcpy (d,s,n) + +#else + +#include + +#ifndef SEEK_SET +#define SEEK_SET 0 +#define SEEK_CUR 1 +#define SEEK_END 2 +#endif + +#define far + +#endif + +extern void seed_random (int); +extern void restart_screen (void); +extern void refresh_text_style (void); +extern void call (zword, int, zword *, int); +extern void split_window (zword); +extern void script_open (void); +extern void script_close (void); + +extern void (*op0_opcodes[]) (void); +extern void (*op1_opcodes[]) (void); +extern void (*op2_opcodes[]) (void); +extern void (*var_opcodes[]) (void); + +char save_name[MAX_FILE_NAME + 1] = DEFAULT_SAVE_NAME; +char auxilary_name[MAX_FILE_NAME + 1] = DEFAULT_AUXILARY_NAME; + +zbyte far *zmp = NULL; +zbyte far *pcp = NULL; + +static FILE *story_fp = NULL; + +static zbyte far *undo[MAX_UNDO_SLOTS]; + +static undo_slots = 0; +static undo_count = 0; +static undo_valid = 0; + +/* + * get_header_extension + * + * Read a value from the header extension (former mouse table). + * + */ + +zword get_header_extension (int entry) +{ + zword addr; + zword val; + + if (h_extension_table == 0 || entry > hx_table_size) + return 0; + + addr = h_extension_table + 2 * entry; + LOW_WORD (addr, val) + + return val; + +}/* get_header_extension */ + +/* + * set_header_extension + * + * Set an entry in the header extension (former mouse table). + * + */ + +void set_header_extension (int entry, zword val) +{ + zword addr; + + if (h_extension_table == 0 || entry > hx_table_size) + return; + + addr = h_extension_table + 2 * entry; + SET_WORD (addr, val) + +}/* set_header_extension */ + +/* + * restart_header + * + * Set all header fields which hold information about the interpreter. + * + */ + +void restart_header (void) +{ + zword screen_x_size; + zword screen_y_size; + zbyte font_x_size; + zbyte font_y_size; + + int i; + + SET_BYTE (H_CONFIG, h_config) + SET_WORD (H_FLAGS, h_flags) + + if (h_version >= V4) { + SET_BYTE (H_INTERPRETER_NUMBER, h_interpreter_number) + SET_BYTE (H_INTERPRETER_VERSION, h_interpreter_version) + SET_BYTE (H_SCREEN_ROWS, h_screen_rows) + SET_BYTE (H_SCREEN_COLS, h_screen_cols) + } + + /* It's less trouble to use font size 1x1 for V5 games, especially + because of a bug in the unreleased German version of "Zork 1" */ + + if (h_version != V6) { + screen_x_size = (zword) h_screen_cols; + screen_y_size = (zword) h_screen_rows; + font_x_size = 1; + font_y_size = 1; + } else { + screen_x_size = h_screen_width; + screen_y_size = h_screen_height; + font_x_size = h_font_width; + font_y_size = h_font_height; + } + + if (h_version >= V5) { + SET_WORD (H_SCREEN_WIDTH, screen_x_size) + SET_WORD (H_SCREEN_HEIGHT, screen_y_size) + SET_BYTE (H_FONT_HEIGHT, font_y_size) + SET_BYTE (H_FONT_WIDTH, font_x_size) + SET_BYTE (H_DEFAULT_BACKGROUND, h_default_background) + SET_BYTE (H_DEFAULT_FOREGROUND, h_default_foreground) + } + + if (h_version == V6) + for (i = 0; i < 8; i++) + storeb ((zword) (H_USER_NAME + i), h_user_name[i]); + + SET_BYTE (H_STANDARD_HIGH, h_standard_high) + SET_BYTE (H_STANDARD_LOW, h_standard_low) + +}/* restart_header */ + +/* + * init_memory + * + * Allocate memory and load the story file. + * + */ + +void init_memory (void) +{ + long size; + zword addr; + unsigned n; + int i, j; + + static struct { + enum story story_id; + zword release; + zbyte serial[6]; + } records[] = { + { SHERLOCK, 21, "871214" }, + { SHERLOCK, 26, "880127" }, + { BEYOND_ZORK, 47, "870915" }, + { BEYOND_ZORK, 49, "870917" }, + { BEYOND_ZORK, 51, "870923" }, + { BEYOND_ZORK, 57, "871221" }, + { ZORK_ZERO, 296, "881019" }, + { ZORK_ZERO, 366, "890323" }, + { ZORK_ZERO, 383, "890602" }, + { ZORK_ZERO, 393, "890714" }, + { SHOGUN, 292, "890314" }, + { SHOGUN, 295, "890321" }, + { SHOGUN, 311, "890510" }, + { SHOGUN, 322, "890706" }, + { ARTHUR, 54, "890606" }, + { ARTHUR, 63, "890622" }, + { ARTHUR, 74, "890714" }, + { JOURNEY, 26, "890316" }, + { JOURNEY, 30, "890322" }, + { JOURNEY, 77, "890616" }, + { JOURNEY, 83, "890706" }, + { LURKING_HORROR, 203, "870506" }, + { LURKING_HORROR, 219, "870912" }, + { LURKING_HORROR, 221, "870918" }, + { UNKNOWN, 0, "------" } + }; + + /* Open story file */ + + if ((story_fp = fopen (story_name, "rb")) == NULL) + os_fatal ("Cannot open story file"); + + /* Allocate memory for story header */ + + if ((zmp = (zbyte far *) malloc (64)) == NULL) + os_fatal ("Out of memory"); + + /* Load header into memory */ + + if (fread (zmp, 1, 64, story_fp) != 64) + os_fatal ("Story file read error"); + + /* Copy header fields to global variables */ + + LOW_BYTE (H_VERSION, h_version) + + if (h_version < V1 || h_version > V8) + os_fatal ("Unknown Z-code version"); + + LOW_BYTE (H_CONFIG, h_config) + + if (h_version == V3 && (h_config & CONFIG_BYTE_SWAPPED)) + os_fatal ("Byte swapped story file"); + + LOW_WORD (H_RELEASE, h_release) + LOW_WORD (H_RESIDENT_SIZE, h_resident_size) + LOW_WORD (H_START_PC, h_start_pc) + LOW_WORD (H_DICTIONARY, h_dictionary) + LOW_WORD (H_OBJECTS, h_objects) + LOW_WORD (H_GLOBALS, h_globals) + LOW_WORD (H_DYNAMIC_SIZE, h_dynamic_size) + LOW_WORD (H_FLAGS, h_flags) + + for (i = 0, addr = H_SERIAL; i < 6; i++, addr++) + LOW_BYTE (addr, h_serial[i]) + + /* Auto-detect buggy story files that need special fixes */ + + for (i = 0; records[i].story_id != UNKNOWN; i++) { + + if (h_release == records[i].release) { + + for (j = 0; j < 6; j++) + if (h_serial[j] != records[i].serial[j]) + goto no_match; + + story_id = records[i].story_id; + + } + + no_match: + + } + + LOW_WORD (H_ABBREVIATIONS, h_abbreviations) + LOW_WORD (H_FILE_SIZE, h_file_size) + + /* Calculate story file size in bytes */ + + if (h_file_size != 0) { + + story_size = (long) 2 * h_file_size; + + if (h_version >= V4) + story_size *= 2; + if (h_version >= V6) + story_size *= 2; + + } else { /* some old games lack the file size entry */ + + fseek (story_fp, 0, SEEK_END); + story_size = ftell (story_fp); + fseek (story_fp, 64, SEEK_SET); + + } + + LOW_WORD (H_CHECKSUM, h_checksum) + LOW_WORD (H_ALPHABET, h_alphabet) + LOW_WORD (H_FUNCTIONS_OFFSET, h_functions_offset) + LOW_WORD (H_STRINGS_OFFSET, h_strings_offset) + LOW_WORD (H_TERMINATING_KEYS, h_terminating_keys) + LOW_WORD (H_EXTENSION_TABLE, h_extension_table) + + /* Zork Zero Macintosh doesn't have the graphics flag set */ + + if (story_id == ZORK_ZERO && h_release == 296) + h_flags |= GRAPHICS_FLAG; + + /* Adjust opcode tables */ + + if (h_version <= V4) { + op0_opcodes[0x09] = z_pop; + op1_opcodes[0x0f] = z_not; + } else { + op0_opcodes[0x09] = z_catch; + op1_opcodes[0x0f] = z_call_n; + } + + /* Allocate memory for story data */ + + if ((zmp = (zbyte far *) realloc (zmp, story_size)) == NULL) + os_fatal ("Out of memory"); + + /* Load story file in chunks of 32KB */ + + n = 0x8000; + + for (size = 64; size < story_size; size += n) { + + if (story_size - size < 0x8000) + n = (unsigned) (story_size - size); + + SET_PC (size) + + if (fread (pcp, 1, n, story_fp) != n) + os_fatal ("Story file read error"); + + } + + /* Read header extension table */ + + hx_table_size = get_header_extension (HX_TABLE_SIZE); + hx_unicode_table = get_header_extension (HX_UNICODE_TABLE); + +}/* init_memory */ + +/* + * init_undo + * + * Allocate memory for multiple undo. It is important not to occupy + * all the memory available, since the IO interface may need memory + * during the game, e.g. for loading sounds or pictures. + * + */ + +void init_undo (void) +{ + void far *reserved; + + if (reserve_mem != 0) + if ((reserved = malloc (reserve_mem)) == NULL) + return; + + while (undo_slots < option_undo_slots && undo_slots < MAX_UNDO_SLOTS) { + + void far *mem = malloc ((long) sizeof (stack) + h_dynamic_size); + + if (mem == NULL) + break; + + undo[undo_slots++] = mem; + + } + + if (reserve_mem != 0) + free (reserved); + +}/* init_undo */ + +/* + * reset_memory + * + * Close the story file and deallocate memory. + * + */ + +void reset_memory (void) +{ + + fclose (story_fp); + + while (undo_slots--) + free (undo[undo_slots]); + + free (zmp); + +}/* reset_memory */ + +/* + * storeb + * + * Write a byte value to the dynamic Z-machine memory. + * + */ + +void storeb (zword addr, zbyte value) +{ + + if (addr >= h_dynamic_size) + runtime_error ("Store out of dynamic memory"); + + if (addr == H_FLAGS + 1) { /* flags register is modified */ + + h_flags &= ~(SCRIPTING_FLAG | FIXED_FONT_FLAG); + h_flags |= value & (SCRIPTING_FLAG | FIXED_FONT_FLAG); + + if (value & SCRIPTING_FLAG) { + if (!ostream_script) + script_open (); + } else { + if (ostream_script) + script_close (); + } + + refresh_text_style (); + + } + + SET_BYTE (addr, value) + +}/* storeb */ + +/* + * storew + * + * Write a word value to the dynamic Z-machine memory. + * + */ + +void storew (zword addr, zword value) +{ + + storeb ((zword) (addr + 0), hi (value)); + storeb ((zword) (addr + 1), lo (value)); + +}/* storew */ + +/* + * z_restart, re-load dynamic area, clear the stack and set the PC. + * + * no zargs used + * + */ + +void z_restart (void) +{ + static bool first_restart = TRUE; + + flush_buffer (); + + os_restart_game (RESTART_BEGIN); + + seed_random (0); + + if (!first_restart) { + + fseek (story_fp, 0, SEEK_SET); + + if (fread (zmp, 1, h_dynamic_size, story_fp) != h_dynamic_size) + os_fatal ("Story file read error"); + + } else first_restart = FALSE; + + restart_header (); + restart_screen (); + + sp = fp = stack + STACK_SIZE; + + if (h_version != V6) { + + long pc = (long) h_start_pc; + SET_PC (pc) + + } else call (h_start_pc, 0, NULL, 0); + + os_restart_game (RESTART_END); + +}/* z_restart */ + +/* + * get_default_name + * + * Read a default file name from the memory of the Z-machine and + * copy it to a string. + * + */ + +static void get_default_name (char *default_name, zword addr) +{ + + if (addr != 0) { + + zbyte len; + int i; + + LOW_BYTE (addr, len) + addr++; + + for (i = 0; i < len; i++) { + + zbyte c; + + LOW_BYTE (addr, c) + addr++; + + if (c >= 'A' && c <= 'Z') + c += 'a' - 'A'; + + default_name[i] = c; + + } + + default_name[i] = 0; + + if (strchr (default_name, '.') == NULL) + strcpy (default_name + i, ".AUX"); + + } else strcpy (default_name, auxilary_name); + +}/* get_default_name */ + +/* + * z_restore, restore [a part of] a Z-machine state from disk + * + * zargs[0] = address of area to restore (optional) + * zargs[1] = number of bytes to restore + * zargs[2] = address of suggested file name + * + */ + +void z_restore (void) +{ + char new_name[MAX_FILE_NAME + 1]; + char default_name[MAX_FILE_NAME + 1]; + FILE *gfp; + + zword success = 0; + + if (zargc != 0) { + + /* Get the file name */ + + get_default_name (default_name, (zargc >= 3) ? zargs[2] : 0); + + if (os_read_file_name (new_name, default_name, FILE_LOAD_AUX) == 0) + goto finished; + + strcpy (auxilary_name, default_name); + + /* Open auxilary file */ + + if ((gfp = fopen (new_name, "rb")) == NULL) + goto finished; + + /* Load auxilary file */ + + success = fread (zmp + zargs[0], 1, zargs[1], gfp); + + /* Close auxilary file */ + + fclose (gfp); + + } else { + + long pc; + zword release; + zword addr; + int i; + + /* Get the file name */ + + if (os_read_file_name (new_name, save_name, FILE_RESTORE) == 0) + goto finished; + + strcpy (save_name, new_name); + + /* Open game file */ + + if ((gfp = fopen (new_name, "rb")) == NULL) + goto finished; + + /* Load game file */ + + release = (unsigned) fgetc (gfp) << 8; + release |= fgetc (gfp); + + (void) fgetc (gfp); + (void) fgetc (gfp); + + /* Check the release number */ + + if (release == h_release) { + + pc = (long) fgetc (gfp) << 16; + pc |= (unsigned) fgetc (gfp) << 8; + pc |= fgetc (gfp); + + SET_PC (pc) + + sp = stack + (fgetc (gfp) << 8); + sp += fgetc (gfp); + fp = stack + (fgetc (gfp) << 8); + fp += fgetc (gfp); + + for (i = (int) (sp - stack); i < STACK_SIZE; i++) { + stack[i] = (unsigned) fgetc (gfp) << 8; + stack[i] |= fgetc (gfp); + } + + fseek (story_fp, 0, SEEK_SET); + + for (addr = 0; addr < h_dynamic_size; addr++) { + int skip = fgetc (gfp); + for (i = 0; i < skip; i++) + zmp[addr++] = fgetc (story_fp); + zmp[addr] = fgetc (gfp); + (void) fgetc (story_fp); + } + + /* Check for errors */ + + if (ferror (gfp) || ferror (story_fp) || addr != h_dynamic_size) + os_fatal ("Error reading save file"); + + /* Reset upper window (V3 only) */ + + if (h_version == V3) + split_window (0); + + /* Initialise story header */ + + restart_header (); + + /* Success */ + + success = 2; + + } else print_string ("Invalid save file\n"); + + /* Close game file */ + + fclose (gfp); + + } + +finished: + + if (h_version <= V3) + branch (success); + else + store (success); + +}/* z_restore */ + +/* + * restore_undo + * + * This function does the dirty work for z_restore_undo. + * + */ + +int restore_undo (void) +{ + + if (undo_slots == 0) /* undo feature unavailable */ + + return -1; + + else if (undo_valid == 0) /* no saved game state */ + + return 0; + + else { /* undo possible */ + + long pc; + + if (undo_count == 0) + undo_count = undo_slots; + + memcpy (stack, undo[undo_count - 1], sizeof (stack)); + memcpy (zmp, undo[undo_count - 1] + sizeof (stack), h_dynamic_size); + + pc = ((long) stack[0] << 16) | stack[1]; + sp = stack + stack[2]; + fp = stack + stack[3]; + + SET_PC (pc) + + restart_header (); + + undo_count--; + undo_valid--; + + return 2; + + } + +}/* restore_undo */ + +/* + * z_restore_undo, restore a Z-machine state from memory. + * + * no zargs used + * + */ + +void z_restore_undo (void) +{ + + store ((zword) restore_undo ()); + +}/* restore_undo */ + +/* + * z_save, save [a part of] the Z-machine state to disk. + * + * zargs[0] = address of memory area to save (optional) + * zargs[1] = number of bytes to save + * zargs[2] = address of suggested file name + * + */ + +void z_save (void) +{ + char new_name[MAX_FILE_NAME + 1]; + char default_name[MAX_FILE_NAME + 1]; + FILE *gfp; + + zword success = 0; + + if (zargc != 0) { + + /* Get the file name */ + + get_default_name (default_name, (zargc >= 3) ? zargs[2] : 0); + + if (os_read_file_name (new_name, default_name, FILE_SAVE_AUX) == 0) + goto finished; + + strcpy (auxilary_name, default_name); + + /* Open auxilary file */ + + if ((gfp = fopen (new_name, "wb")) == NULL) + goto finished; + + /* Write auxilary file */ + + success = fwrite (zmp + zargs[0], zargs[1], 1, gfp); + + /* Close auxilary file */ + + fclose (gfp); + + } else { + + long pc; + zword addr; + zword nsp, nfp; + int skip; + int i; + + /* Get the file name */ + + if (os_read_file_name (new_name, save_name, FILE_SAVE) == 0) + goto finished; + + strcpy (save_name, new_name); + + /* Open game file */ + + if ((gfp = fopen (new_name, "wb")) == NULL) + goto finished; + + /* Write game file */ + + fputc ((int) hi (h_release), gfp); + fputc ((int) lo (h_release), gfp); + fputc ((int) hi (h_checksum), gfp); + fputc ((int) lo (h_checksum), gfp); + + GET_PC (pc) + + fputc ((int) (pc >> 16) & 0xff, gfp); + fputc ((int) (pc >> 8) & 0xff, gfp); + fputc ((int) (pc) & 0xff, gfp); + + nsp = (int) (sp - stack); + nfp = (int) (fp - stack); + + fputc ((int) hi (nsp), gfp); + fputc ((int) lo (nsp), gfp); + fputc ((int) hi (nfp), gfp); + fputc ((int) lo (nfp), gfp); + + for (i = nsp; i < STACK_SIZE; i++) { + fputc ((int) hi (stack[i]), gfp); + fputc ((int) lo (stack[i]), gfp); + } + + fseek (story_fp, 0, SEEK_SET); + + for (addr = 0, skip = 0; addr < h_dynamic_size; addr++) + if (zmp[addr] != fgetc (story_fp) || skip == 255 || addr + 1 == h_dynamic_size) { + fputc (skip, gfp); + fputc (zmp[addr], gfp); + skip = 0; + } else skip++; + + /* Close game file and check for errors */ + + if (fclose (gfp) == EOF || ferror (story_fp)) { + print_string ("Error writing save file\n"); + goto finished; + } + + /* Success */ + + success = 1; + + } + +finished: + + if (h_version <= V3) + branch (success); + else + store (success); + +}/* z_save */ + +/* + * save_undo + * + * This function does the dirty work for z_save_undo. + * + */ + +int save_undo (void) +{ + long pc; + + if (undo_slots == 0) /* undo feature unavailable */ + + return -1; + + else { /* save undo possible */ + + if (undo_count == undo_slots) + undo_count = 0; + + GET_PC (pc) + + stack[0] = (zword) (pc >> 16); + stack[1] = (zword) (pc & 0xffff); + stack[2] = (zword) (sp - stack); + stack[3] = (zword) (fp - stack); + + memcpy (undo[undo_count], stack, sizeof (stack)); + memcpy (undo[undo_count] + sizeof (stack), zmp, h_dynamic_size); + + if (++undo_count == undo_slots) + undo_count = 0; + if (++undo_valid > undo_slots) + undo_valid = undo_slots; + + return 1; + + } + +}/* save_undo */ + +/* + * z_save_undo, save the current Z-machine state for a future undo. + * + * no zargs used + * + */ + +void z_save_undo (void) +{ + + store ((zword) save_undo ()); + +}/* z_save_undo */ + +/* + * z_verify, check the story file integrity. + * + * no zargs used + * + */ + +void z_verify (void) +{ + zword checksum = 0; + long i; + + /* Sum all bytes in story file except header bytes */ + + fseek (story_fp, 64, SEEK_SET); + + for (i = 64; i < story_size; i++) + checksum += fgetc (story_fp); + + /* Branch if the checksums are equal */ + + branch (checksum == h_checksum); + +}/* z_verify */ diff --git a/files.c b/files.c new file mode 100644 index 0000000..784f04d --- /dev/null +++ b/files.c @@ -0,0 +1,555 @@ +/* + * files.c + * + * Transscription, recording and playback + * + */ + +#include +#include +#include "frotz.h" + +#ifndef SEEK_SET +#define SEEK_SET 0 +#define SEEK_CUR 1 +#define SEEK_END 2 +#endif + +extern void set_more_prompts (bool); + +extern bool is_terminator (zchar); + +extern bool read_yes_or_no (const char *); + +char script_name[MAX_FILE_NAME + 1] = DEFAULT_SCRIPT_NAME; +char command_name[MAX_FILE_NAME + 1] = DEFAULT_COMMAND_NAME; + +#ifdef __MSDOS__ +extern char latin1_to_ibm[]; +#endif + +static script_width = 0; + +static FILE *sfp = NULL; +static FILE *rfp = NULL; +static FILE *pfp = NULL; + +/* + * script_open + * + * Open the transscript file. 'AMFV' makes this more complicated as it + * turns transscription on/off several times to exclude some text from + * the transscription file. This wasn't a problem for the original V4 + * interpreters which always sent transscription to the printer, but it + * means a problem to modern interpreters that offer to open a new file + * every time transscription is turned on. Our solution is to append to + * the old transscription file in V1 to V4, and to ask for a new file + * name in V5+. + * + */ + +void script_open (void) +{ + static bool script_valid = FALSE; + + char new_name[MAX_FILE_NAME + 1]; + + h_flags &= ~SCRIPTING_FLAG; + + if (h_version >= V5 || !script_valid) { + + if (!os_read_file_name (new_name, script_name, FILE_SCRIPT)) + goto done; + + strcpy (script_name, new_name); + + } + + /* Opening in "at" mode doesn't work for script_erase_input... */ + + if ((sfp = fopen (script_name, "r+t")) != NULL || (sfp = fopen (script_name, "w+t")) != NULL) { + + fseek (sfp, 0, SEEK_END); + + h_flags |= SCRIPTING_FLAG; + + script_valid = TRUE; + ostream_script = TRUE; + + script_width = 0; + + } else print_string ("Cannot open file\n"); + +done: + + SET_WORD (H_FLAGS, h_flags) + +}/* script_open */ + +/* + * script_close + * + * Stop transscription. + * + */ + +void script_close (void) +{ + + h_flags &= ~SCRIPTING_FLAG; + SET_WORD (H_FLAGS, h_flags) + + fclose (sfp); ostream_script = FALSE; + +}/* script_close */ + +/* + * script_new_line + * + * Write a newline to the transscript file. + * + */ + +void script_new_line (void) +{ + + if (fputc ('\n', sfp) == EOF) + script_close (); + + script_width = 0; + +}/* script_new_line */ + +/* + * script_char + * + * Write a single character to the transscript file. + * + */ + +void script_char (zchar c) +{ + + if (c == ZC_INDENT && script_width != 0) + c = ' '; + + if (c == ZC_INDENT) + { script_char (' '); script_char (' '); script_char (' '); return; } + if (c == ZC_GAP) + { script_char (' '); script_char (' '); return; } + +#ifdef __MSDOS__ + if (c >= ZC_LATIN1_MIN) + c = latin1_to_ibm[c - ZC_LATIN1_MIN]; +#endif + + fputc (c, sfp); script_width++; + +}/* script_char */ + +/* + * script_word + * + * Write a string to the transscript file. + * + */ + +void script_word (const zchar *s) +{ + int width; + int i; + + if (*s == ZC_INDENT && script_width != 0) + script_char (*s++); + + for (i = 0, width = 0; s[i] != 0; i++) + + if (s[i] == ZC_NEW_STYLE || s[i] == ZC_NEW_FONT) + i++; + else if (s[i] == ZC_GAP) + width += 3; + else if (s[i] == ZC_INDENT) + width += 2; + else + width += 1; + + if (option_script_cols != 0 && script_width + width > option_script_cols) { + + if (*s == ' ' || *s == ZC_INDENT || *s == ZC_GAP) + s++; + + script_new_line (); + + } + + for (i = 0; s[i] != 0; i++) + + if (s[i] == ZC_NEW_FONT || s[i] == ZC_NEW_STYLE) + i++; + else + script_char (s[i]); + +}/* script_word */ + +/* + * script_write_input + * + * Send an input line to the transscript file. + * + */ + +void script_write_input (const zchar *buf, zchar key) +{ + int width; + int i; + + for (i = 0, width = 0; buf[i] != 0; i++) + width++; + + if (option_script_cols != 0 && script_width + width > option_script_cols) + script_new_line (); + + for (i = 0; buf[i] != 0; i++) + script_char (buf[i]); + + if (key == ZC_RETURN) + script_new_line (); + +}/* script_write_input */ + +/* + * script_erase_input + * + * Remove an input line from the transscript file. + * + */ + +void script_erase_input (const zchar *buf) +{ + int width; + int i; + + for (i = 0, width = 0; buf[i] != 0; i++) + width++; + + fseek (sfp, -width, SEEK_CUR); script_width -= width; + +}/* script_erase_input */ + +/* + * script_mssg_on + * + * Start sending a "debugging" message to the transscript file. + * + */ + +void script_mssg_on (void) +{ + + if (script_width != 0) + script_new_line (); + + script_char (ZC_INDENT); + +}/* script_mssg_on */ + +/* + * script_mssg_off + * + * Stop writing a "debugging" message. + * + */ + +void script_mssg_off (void) +{ + + script_new_line (); + +}/* script_mssg_off */ + +/* + * record_open + * + * Open a file to record the player's input. + * + */ + +void record_open (void) +{ + char new_name[MAX_FILE_NAME + 1]; + + if (os_read_file_name (new_name, command_name, FILE_RECORD)) { + + strcpy (command_name, new_name); + + if ((rfp = fopen (new_name, "wt")) != NULL) + ostream_record = TRUE; + else + print_string ("Cannot open file\n"); + + } + +}/* record_open */ + +/* + * record_close + * + * Stop recording the player's input. + * + */ + +void record_close (void) +{ + + fclose (rfp); ostream_record = FALSE; + +}/* record_close */ + +/* + * record_code + * + * Helper function for record_char. + * + */ + +static void record_code (int c, bool force_encoding) +{ + + if (force_encoding || c == '[' || c < 0x20 || c > 0x7e) { + + int i; + + fputc ('[', rfp); + + for (i = 10000; i != 0; i /= 10) + if (c >= i || i == 1) + fputc ('0' + (c / i) % 10, rfp); + + fputc (']', rfp); + + } else fputc (c, rfp); + +}/* record_code */ + +/* + * record_char + * + * Write a character to the command file. + * + */ + +static void record_char (zchar c) +{ + + if (c != ZC_RETURN) + + if (c < ZC_HKEY_MIN || c > ZC_HKEY_MAX) { + + record_code (translate_to_zscii (c), FALSE); + + if (c == ZC_SINGLE_CLICK || c == ZC_DOUBLE_CLICK) { + record_code (mouse_x, TRUE); + record_code (mouse_y, TRUE); + } + + } else record_code (1000 + c - ZC_HKEY_MIN, TRUE); + +}/* record_char */ + +/* + * record_write_key + * + * Copy a keystroke to the command file. + * + */ + +void record_write_key (zchar key) +{ + + record_char (key); + + if (fputc ('\n', rfp) == EOF) + record_close (); + +}/* record_write_key */ + +/* + * record_write_input + * + * Copy a line of input to a command file. + * + */ + +void record_write_input (const zchar *buf, zchar key) +{ + zchar c; + + while ((c = *buf++) != 0) + record_char (c); + + record_char (key); + + if (fputc ('\n', rfp) == EOF) + record_close (); + +}/* record_write_input */ + +/* + * replay_open + * + * Open a file of commands for playback. + * + */ + +void replay_open (void) +{ + char new_name[MAX_FILE_NAME + 1]; + + if (os_read_file_name (new_name, command_name, FILE_PLAYBACK)) { + + strcpy (command_name, new_name); + + if ((pfp = fopen (new_name, "rt")) != NULL) { + + set_more_prompts (read_yes_or_no ("Do you want MORE prompts")); + + istream_replay = TRUE; + + } else print_string ("Cannot open file\n"); + + } + +}/* replay_open */ + +/* + * replay_close + * + * Stop playback of commands. + * + */ + +void replay_close (void) +{ + + set_more_prompts (TRUE); + + fclose (pfp); istream_replay = FALSE; + +}/* replay_close */ + +/* + * replay_code + * + * Helper function for replay_key and replay_line. + * + */ + +static int replay_code (void) +{ + int c; + + if ((c = fgetc (pfp)) == '[') { + + int c2; + + c = 0; + + while ((c2 = fgetc (pfp)) != EOF && c2 >= '0' && c2 <= '9') + c = 10 * c + c2 - '0'; + + return (c2 == ']') ? c : EOF; + + } else return c; + +}/* replay_code */ + +/* + * replay_char + * + * Read a character from the command file. + * + */ + +static zchar replay_char (void) +{ + int c; + + if ((c = replay_code ()) != EOF) { + + if (c != '\n') + + if (c < 1000) { + + c = translate_from_zscii (c); + + if (c == ZC_SINGLE_CLICK || c == ZC_DOUBLE_CLICK) { + mouse_x = replay_code (); + mouse_y = replay_code (); + } + + return c; + + } else return ZC_HKEY_MIN + c - 1000; + + ungetc ('\n', pfp); + + return ZC_RETURN; + + } else return ZC_BAD; + +}/* replay_char */ + +/* + * replay_read_key + * + * Read a keystroke from a command file. + * + */ + +zchar replay_read_key (void) +{ + zchar key; + + key = replay_char (); + + if (fgetc (pfp) != '\n') { + + replay_close (); + return ZC_BAD; + + } else return key; + +}/* replay_read_key */ + +/* + * replay_read_input + * + * Read a line of input from a command file. + * + */ + +zchar replay_read_input (zchar *buf) +{ + zchar c; + + for (;;) { + + c = replay_char (); + + if (c == ZC_BAD || is_terminator (c)) + break; + + *buf++ = c; + + } + + *buf = 0; + + if (fgetc (pfp) != '\n') { + + replay_close (); + return ZC_BAD; + + } else return c; + +}/* replay_read_input */ diff --git a/font.dat b/font.dat new file mode 100644 index 0000000000000000000000000000000000000000..fe9799ca6d3dd437be5ea902fca948f7cf4edbea GIT binary patch literal 26976 zcmcIsZ;V~XRUhwr`~K|O?z3ZH0pq=EZ>d`$v?)>KMq6jR*|_V-N;jcJA&SaN*0>Q( z%S&6xmRjp^u*7~qXp4%J3R*T26!Xm=`k|F7^q>+7Qa@CDifEB;MMVIKYAcXfMC9f7 zn{&^-Gxyzl*OA&YyLbMaIcLtCnLGE)oO_=!_XXC3fh0DfTQ>~#aLO=jnezy5`?_K6 zff<-3vxZ<1_Yl?)CkPoNOLonznFr03X^(r!iDY4H73brD=X~BhVBTvUH!J3GcOk-y z^iffiVOvrsycQC$l({&tRA68}VlLo+5OFPM*v}jIZ>}X&%1Ce^GwHOE56QkUUS@id0^NM2L^aw++4= zA&dV3((RBeOW*LZT+8T!PWgm2D8Gykdry$7Pa5OqZIXN_xsrtsjT3V^LrFwh-oa-P zE}Bc`lKYnkzhxGDxR}KYxYq-4XWWA0`1pX-f;khQ4ep9_X2B&AfIWwj5Vp{6ViwIZ z{@H`GF@NM@e1e$}K+*pAlCubjnGev$eI}FpQI#JhA&h*hY*|CZANRiA7ZUH7G$c2# zK)yxH?j`&$BE4`3{{?e1q#qC6gFd{^F)X^p@$ZCqw}G|sL9Bi6HSb0^hwyTUhqj^r zB2?&~Bg?Ry^)T*V6@ACF@-}W{)TEdG#qB?2u9{VhKJWiFQrFBgIV}?Iz-{~a$$2Ir znt%ED$SmVEnm?G0n3G-xdD4(a0AK&l!Q+F+le5WL`%?_A=OX<0`+iQIG)ECmQdi^e z2js@{r*=+N%V!SEtLT^B!wF{n10FgO*8InV>n5Ul}n_%0Q@qngq6INN%^lw^w#I37P*)CPvWJP^K`}t zsm+sUZ4O9ZWI^>E(%=MwR06 z;e0NmHsCc^xw|LZxyS_!xi`+dOWQNwlD$ipWQar znU5myUY-Qfla_bW4=2$mqwbV{Vs05~p1z!=A*k)e`k)I@3&peiIvb{F$%B$YGzlN01NG;kQq)uSTQToMr{Wo9pYs%LqS!N~QGuvh# zocD9h#m}cgG9f7ErTqjPxOQ=jdx)3v_SW-Mbz?vHIwoe7}$qHXd6ckc3NszBf@}4SoFY)}g zxEB&=!IkVD`X{prAG>YZtGd_w&hSH7{UZF^F|87DP1@rKrXhrPfQV zc?-E^PNiBD#T@(l8-E#l&Vc}&HGhkz(W+Z>|L#t@v+gnH0<=-Qx#IrA4PgDOxZjWP zW0sjkci#O&6+K3dz!Bsq*+#aR zdOYXZ05$>djWCQd(PhNy@w{`l8|V$NrV-DEbpJJS*n>ek#s2GO%`Y0$u)h%fRqYR? z130mPhU@X(-fqDA_8ag9_PS}riyXWbPxqqCr!4oUm)wfu9k}j>=6Um#tcven!~Vnf zuK_Rn3Ef{-?Oz~&1HGSrhweW(KbCPPN#Gt%=Z|zlEx$GBKtj0xk~wJn?ySnb7n`OY zultWBjC2*f!ES-=)#D9Tq&hsg^NC%Dmmc!^u<4se!D$UPa+Q4;?>{)QvM$Pri-GL2 zyD0B3wEuN@?SCDfvT=V_k9Sy?O#Y1F3Oz)=faDN7pTAA?<`DckX3RSDQCRr#{z}F# z+#g&ogaa*gDgD#=71D?LtHOu+tHSI2q5d?4{3(j+w+_!b9BtYyGU_aeVHwuzM|`*j zTcQB%U)}yg`&Wn8{?*~x66LSMEC2fa#~Aa&;vTz}>!-YbBmWBeR%^Q3IXcs7x4Yd| zyVdUG&}wzNK=u3mnO=8hX1dew&Cbm9X8PU!O#j$Szt;v$r`_pxx?K{tx~)Day3^Aj zWNNxK-7~Eg8MZppv#8Ua?X*#`-RrQaE?9T)4>;QI0j2>n0%#D>?{!6-KH6XtUM41U zpz7`esGTqvO!GuA2Rn2- z<_icf4BI=sox#pvnB+(%x>&jTW+pvqA9AbavB3-J2P3@79u?*pnIB2iL)sE@SK{#k zjO1wnN?_gsLlEypv;RV7`&ThH!nLb*kQrZTPon&2l{~;Ix;1gi@jIm7dQ>eGkL?HCk=U!Z018gJgT)B3+mkb6;Z`{ooQ0v$s zm=g%tE4w89k+Syj7HnRIi#Yog3;3P+FF@}Sn*~P;I-0j0#H$3)Hv&Kl^C3O7t(NsgBP+$>^lj4`D-)%^XxKaW zXG;2e#9YSyTIcABdBHrVq>K;qU*zK>qm*D4MklA~kI-Y5GH{;qJUoDJ{!edVurN5c zb8`0-gYDU9eHs6srxv;ydN9WS6_A_sb8Z06c*20Mgs6SYKb$@Q<5xfMiv1TO^t?KV zmYsjXu!iWKPoDh#gdAyJu6T{pOiG^5t(n^h?+kP;v1s9NkX2l4+>1eVJ>98af2A_% zMGx`%+V0lO_L1K~L{Rd4It@6HE8}Ra)2!!f#f28n(+)G_v`jw?W%+{ zq}Mvnm>*>Nt3H1(o?@~7C+X>5EccST2pylRNb^33?}BHb6_GyHkMfssTEbnjR(gIL zPgjiJdgb%Au3XNCZQ@yB!3!QsY6%2(R0(IE!J;@By>@o|b+IT8QuoXh zLNkmi(3J0+_K)qK*)QVyjYvLS<15JWB3&5@edMStA#+amFZW95k=gO1UDy823}(jA46PLm*e~$W=LJxt8~vQL-e0P z>t|%8NJpOc$WDlJi5q$g@zWOX=h=RG!X_~uC#tloKAZNEz8%2+UWo81*0UF2xBS5N zKz|}#i0QMs1H>|p)Hn4=e-GCnuI1JCJ@4x#d@Q^g)bB53 z{?y}T{?y}T{?y}T{xsp)KLVdDd`pr~nG)D}`Y-GX?1MFgCAVaLBdg;3*RcQa{cFJc z{x#se{0(@||Iqyh=MVPYS>PT{=MTFf@9M0;dgCrk!uKK-$X!rp-}8KiYezkvT5ZD1 z`FB0N!IQa;p1e77b$B2$c+{=Ad2r&p&aY@;5VL(gGi3k3m2fG;(l!wORr`yu|JC@S z|JC@Sf7N&gnGF8NUOGycB40pq2%gU$gHa#iU&5#$_NiT5>-bZ<=dr@le$)A}9MQ}8 z)$cC~{pmA0wj_%AQ;je5SB)>`Pc^>KZylabK4WONFs_CVp#PW66R=;;#^)2zDXHrB zAJV^iysXmoc#*#zFZ}EFA3Cnj!3NOrlX;fr9p(L1LEnaxp?mCTYkIodYfaOOp#V2S zuh%^epT%syH#0lkne88&o$b%g^!l^2$5Dvdot_X()A`YD&60K+K8h|YN2SZ@kDqN@nBw;ph(+(URnAcmu` z6Henkd>`iDW$=F_M(6i+sW{8hN=Tv}T3@1b-Fcj@4H8?Fxtv{scf(|s=0z0=Qp!7I z709S4%Q^(`z~343=J&82Bt@ zG+xpwY>25IU+(h8piipFF?R@Zovh+OJM{W@6UaT^drCg^I2+YCb2@DYPWknV7F_kz%eE3k)9m!O~e@H4d=B4&ZDyY zJm2Q2-4J0Nsmn3B*A1oCQ06b%{Wbsg@JIc7#1R8$-O#PWep_>^ZUyfW*$8cL(s;rp zsD*$YmcCy=-4SGxpr`FIgER0jd1_o&-8p!%j+i4s71FcctGEZ*ztsT8juppBkx7b#yf@R#D!&yzd#vpIRtjr;OcV*M+xaQs9T#YkL?zvwTVqpWe) z_pORj=sCVaXsK5*KkjGzRQm1A^CVo0EAg}=$*T&eyRRX91oNN@Zliq3+i|z6&{(&) zMzRK5;9VR8wZgT*mgJ+SLBrVJ>5^N@$jPlLDED!I)z(>mtQE5O8a!C9mCAk`SqmmA zzs4b7%Cc!*#O&TRcMwL%hj1p-*xOk@%I_yg-M`0EmHMS`u?({Qp7+mTd0QZ?QRvQO zEhHft>C>=J&BW(w)kCA^7GluOuU>}Yv7Sjsi?vC$AjU$TV*zrA)g-5U#ob+9g59jXv!Vvu#W|g@+;WKJ- z)<^}}9dLiz3>r7dn6H@WzM-qaObY=4TrC zQ+9fg>gicS;Wb!aL)acWTA(pJT+d_QbQ?RXFPQI}$ILe~jLwg`{fEwvI=s%0dc4e! zI=qhmVfznS99R9yconJgI;VCo?N8dCw9lRP9kI^B!oG{`dzGRNud}co|8M5&Xr~@8 z&);f1rI)WItMPe9+`L;fkDJT(ntL=a3=`zU^^f)=zj{*8{?k6L+FyuGC-!p{Ui)81 zFZWn+8A_U-Kz}FSC z@D#v{(Cfojz>gw2vt9TjdiY?XKi!$`=m!#=&eU{gs>iP-rg>pGT@>(NbU+Fp2|l6d zO!wj4pzEOr*9MsO;Lh-HoN4cp==%>ZW;))X;rWr>EH54SFM{twd{L+9USle}Z)vSg4o z<2;TfMPO~0;S;}SdovblBFEE>*e^3tB`+v==S4oT7iVj8Hp0lcipVa#jAwUoI?ufU z11;eKxYoyKGpZS>BlN}jokc%GAf9Kt`u#z6wdIRy^s^;CK@<5Q+5fA*_* z12~pnc+$pZFGLR^f04iNUR3d?R`k8oGG2JufMuB0;;_u-b4Rg9p`9hHbNmMBs1}xD zYBknuQ9&t%OHt00^iRfz8sLo2!F7jogJh7r@z~nx+UoY|=$uKG=Ek9KQ+FraKjMVn z0djICw!F=IDUX>7!JmHn5yB`rdH*TIgWjQB1GvTXH#1t@9oYQ+AmK8Jw;_A^zL-4l zWjXgc5puQT{i^?WVf|0sQ5ld&ef^uO303yMCiDV#v*`#+Z_-alYH z@tkrEDAOmNZ<=wpKF%5qqL@EhT+XmUQ9M0(2ZvyNMr!HxFg)wItdh{I;@;QqFIGbG zNbV~BzB0bQxXwFYLPRf_diez}`jhd~o`-DW@e?^h{G|S-LNxj|40;lwd`6zqe1l=~ z`N`JOfI68Z2bBgshbMtPOxDof8z|Mte?6-1_oHat(>uUj?(GsWvoU|#5#y*9&&1S{ z+>9_h+to16Qwg5*0;21xtKefA{4`*5dV zRhiv_q1u6@boG);Xi zJB)%w?DwMQk;ZOj!ExUtzaO=Z{l&+!D&D*2qTPoJ`{(CJ1D^dm z*?{-*H{gXoHXwd?qxV8-f8%G5jv>d6vnd2V9~hp?Vx~fnQDm)Z`<`}<*x%K7@!{3e z%kNv(;}^X!b$D{8rBsK{dXj!{urj!~_n283tdxF#MQV_qnvwkjM^^2xqW>${xlQgb zivCyQi~d*Ri~d#P9ojTrGLeth5RyaiRjWWqzw0vv?=pYRt5eX2j&9k8(C;s<8F~8h z8bYYQs{KW%zbbsFzbbq&e*{~WFVt@py~xTr%yo_NBurs`@#_nIfAIp+HmE{dzyy6S z??0q}g>K4t?$Dd)MVs~X!oPn1AtPJAe-r){^!)v(?$JIx1JnNZqttuhe-H|p49M#4 zNe)+sDMWsVhdY2F>Wrxg(V zcZ@Loe7j%=@c?^bTq9>4_-qfnH9Fk6d)JLT{oW4bPItgRWjg?rBj@vI-%&0flisHPyr2(Hz@L$9Ktp==ng2 zzW*Td^}%F)JpsS%?K^LR-}ZWvtTSc;<EOq!&- zDe~#=E@P8|9x+O@{BFwp(57HSJj)Z5A>IWYII$npI$-Q}p#YY9K>htY0K_S(qg?1U z1|`c=^dKbx%N@9bUrKh!1MTHv%skoc`*Q2~v78hs`kz8>i*d)mlbQcV21OvScc2b) zkU@J2F1=~|9vi%kt?4Oz+{CY#2vaRM5Id5_mF3LgUtEm1J@K<{AQV9Lsg7paJ_Xij zDTAfa!DtETqsI@9!#iSFq>x<$^EQakLqICJXC~f;qj28z(Wqzi;snmPwr>;1Xw^kPC<`!3OQK!`Z-162O03KAp*7BUsBR;t0;lM0k@9?ALjo*E4 zxOshOHb1p-W8+hsu;_n&Ztl%Zv$65Yl~*=4%*HF9_{0X{D^Fj0aWotnv+?lh(+{Kk zS3kV*x!>F{O9E(S>q}_S42PR9eH`^KEI&+)dF2z}f;@OGgSC12x31xDh}T0_~2V#`ro$>P@cj61CZtfY5)KL literal 0 HcmV?d00001 diff --git a/frotz.h b/frotz.h new file mode 100644 index 0000000..93a2c96 --- /dev/null +++ b/frotz.h @@ -0,0 +1,597 @@ +/* + * frotz.h + * + * Global declarations and definitions + * + */ + +typedef int bool; + +#define TRUE 1 +#define FALSE 0 + +typedef unsigned char zbyte; +typedef unsigned short zword; + +enum story { + BEYOND_ZORK, + SHERLOCK, + ZORK_ZERO, + SHOGUN, + ARTHUR, + JOURNEY, + LURKING_HORROR, + UNKNOWN +}; + +typedef unsigned char zchar; + +/*** Constants that may be set at compile time ***/ + +#ifndef MAX_UNDO_SLOTS +#define MAX_UNDO_SLOTS 25 +#endif +#ifndef MAX_FILE_NAME +#define MAX_FILE_NAME 80 +#endif +#ifndef TEXT_BUFFER_SIZE +#define TEXT_BUFFER_SIZE 200 +#endif +#ifndef INPUT_BUFFER_SIZE +#define INPUT_BUFFER_SIZE 200 +#endif +#ifndef STACK_SIZE +#define STACK_SIZE 1024 +#endif + +#ifndef DEFAULT_SAVE_NAME +#define DEFAULT_SAVE_NAME "story.sav" +#endif +#ifndef DEFAULT_SCRIPT_NAME +#define DEFAULT_SCRIPT_NAME "story.scr" +#endif +#ifndef DEFAULT_COMMAND_NAME +#define DEFAULT_COMMAND_NAME "story.rec" +#endif +#ifndef DEFAULT_AUXILARY_NAME +#define DEFAULT_AUXILARY_NAME "story.aux" +#endif + +/*** Story file header format ***/ + +#define H_VERSION 0 +#define H_CONFIG 1 +#define H_RELEASE 2 +#define H_RESIDENT_SIZE 4 +#define H_START_PC 6 +#define H_DICTIONARY 8 +#define H_OBJECTS 10 +#define H_GLOBALS 12 +#define H_DYNAMIC_SIZE 14 +#define H_FLAGS 16 +#define H_SERIAL 18 +#define H_ABBREVIATIONS 24 +#define H_FILE_SIZE 26 +#define H_CHECKSUM 28 +#define H_INTERPRETER_NUMBER 30 +#define H_INTERPRETER_VERSION 31 +#define H_SCREEN_ROWS 32 +#define H_SCREEN_COLS 33 +#define H_SCREEN_WIDTH 34 +#define H_SCREEN_HEIGHT 36 +#define H_FONT_HEIGHT 38 /* this is the font width in V5 */ +#define H_FONT_WIDTH 39 /* this is the font height in V5 */ +#define H_FUNCTIONS_OFFSET 40 +#define H_STRINGS_OFFSET 42 +#define H_DEFAULT_BACKGROUND 44 +#define H_DEFAULT_FOREGROUND 45 +#define H_TERMINATING_KEYS 46 +#define H_LINE_WIDTH 48 +#define H_STANDARD_HIGH 50 +#define H_STANDARD_LOW 51 +#define H_ALPHABET 52 +#define H_EXTENSION_TABLE 54 +#define H_USER_NAME 56 + +#define HX_TABLE_SIZE 0 +#define HX_MOUSE_X 1 +#define HX_MOUSE_Y 2 +#define HX_UNICODE_TABLE 3 + +/*** Various Z-machine constants ***/ + +#define V1 1 +#define V2 2 +#define V3 3 +#define V4 4 +#define V5 5 +#define V6 6 +#define V7 7 +#define V8 8 + +#define CONFIG_BYTE_SWAPPED 0x01 /* Story file is byte swapped - V3 */ +#define CONFIG_TIME 0x02 /* Status line displays time - V3 */ +#define CONFIG_TWODISKS 0x04 /* Story file occupied two disks - V3 */ +#define CONFIG_TANDY 0x08 /* Tandy licensed game - V3 */ +#define CONFIG_NOSTATUSLINE 0x10 /* Interpr can't support status lines - V3 */ +#define CONFIG_SPLITSCREEN 0x20 /* Interpr supports split screen mode - V3 */ +#define CONFIG_PROPORTIONAL 0x40 /* Interpr uses proportional font - V3 */ + +#define CONFIG_COLOUR 0x01 /* Interpr supports colour - V5+ */ +#define CONFIG_PICTURES 0x02 /* Interpr supports pictures - V6 */ +#define CONFIG_BOLDFACE 0x04 /* Interpr supports boldface style - V4+ */ +#define CONFIG_EMPHASIS 0x08 /* Interpr supports emphasis style - V4+ */ +#define CONFIG_FIXED 0x10 /* Interpr supports fixed width style - V4+ */ +#define CONFIG_TIMEDINPUT 0x80 /* Interpr supports timed input - V4+ */ + +#define SCRIPTING_FLAG 0x0001 /* Outputting to transscription file - V1+ */ +#define FIXED_FONT_FLAG 0x0002 /* Use fixed width font - V3+ */ +#define REFRESH_FLAG 0x0004 /* Refresh the screen - V6 */ +#define GRAPHICS_FLAG 0x0008 /* Game wants to use graphics - V5+ */ +#define OLD_SOUND_FLAG 0x0010 /* Game wants to use sound effects - V3 */ +#define UNDO_FLAG 0x0010 /* Game wants to use UNDO feature - V5+ */ +#define MOUSE_FLAG 0x0020 /* Game wants to use a mouse - V5+ */ +#define COLOUR_FLAG 0x0040 /* Game wants to use colours - V5+ */ +#define SOUND_FLAG 0x0080 /* Game wants to use sound effects - V5+ */ +#define MENU_FLAG 0x0100 /* Game wants to use menus - V6 */ + +#define INTERP_DEC_20 1 +#define INTERP_APPLE_IIE 2 +#define INTERP_MACINTOSH 3 +#define INTERP_AMIGA 4 +#define INTERP_ATARI_ST 5 +#define INTERP_MSDOS 6 +#define INTERP_CBM_128 7 +#define INTERP_CBM_64 8 +#define INTERP_APPLE_IIC 9 +#define INTERP_APPLE_IIGS 10 +#define INTERP_TANDY 11 + +#define BLACK_COLOUR 2 +#define RED_COLOUR 3 +#define GREEN_COLOUR 4 +#define YELLOW_COLOUR 5 +#define BLUE_COLOUR 6 +#define MAGENTA_COLOUR 7 +#define CYAN_COLOUR 8 +#define WHITE_COLOUR 9 +#define GREY_COLOUR 10 /* INTERP_MSDOS only */ +#define LIGHTGREY_COLOUR 10 /* INTERP_AMIGA only */ +#define MEDIUMGREY_COLOUR 11 /* INTERP_AMIGA only */ +#define DARKGREY_COLOUR 12 /* INTERP_AMIGA only */ + +#define REVERSE_STYLE 1 +#define BOLDFACE_STYLE 2 +#define EMPHASIS_STYLE 4 +#define FIXED_WIDTH_STYLE 8 + +#define TEXT_FONT 1 +#define PICTURE_FONT 2 +#define GRAPHICS_FONT 3 +#define FIXED_WIDTH_FONT 4 + +/*** Constants for os_restart_game */ + +#define RESTART_BEGIN 0 +#define RESTART_WPROP_SET 1 +#define RESTART_END 2 + +/*** Character codes ***/ + +#define ZC_TIME_OUT 0x00 +#define ZC_NEW_STYLE 0x01 +#define ZC_NEW_FONT 0x02 +#define ZC_BACKSPACE 0x08 +#define ZC_INDENT 0x09 +#define ZC_GAP 0x0b +#define ZC_RETURN 0x0d +#define ZC_HKEY_MIN 0x0e +#define ZC_HKEY_RECORD 0x0e +#define ZC_HKEY_PLAYBACK 0x0f +#define ZC_HKEY_SEED 0x10 +#define ZC_HKEY_UNDO 0x11 +#define ZC_HKEY_RESTART 0x12 +#define ZC_HKEY_QUIT 0x13 +#define ZC_HKEY_DEBUG 0x14 +#define ZC_HKEY_HELP 0x15 +#define ZC_HKEY_MAX 0x15 +#define ZC_ESCAPE 0x1b +#define ZC_ASCII_MIN 0x20 +#define ZC_ASCII_MAX 0x7e +#define ZC_BAD 0x7f +#define ZC_ARROW_MIN 0x81 +#define ZC_ARROW_UP 0x81 +#define ZC_ARROW_DOWN 0x82 +#define ZC_ARROW_LEFT 0x83 +#define ZC_ARROW_RIGHT 0x84 +#define ZC_ARROW_MAX 0x84 +#define ZC_FKEY_MIN 0x85 +#define ZC_FKEY_MAX 0x90 +#define ZC_NUMPAD_MIN 0x91 +#define ZC_NUMPAD_MAX 0x9a +#define ZC_SINGLE_CLICK 0x9b +#define ZC_DOUBLE_CLICK 0x9c +#define ZC_MENU_CLICK 0x9d +#define ZC_LATIN1_MIN 0xa0 +#define ZC_LATIN1_MAX 0xff + +/*** File types ***/ + +#define FILE_RESTORE 0 +#define FILE_SAVE 1 +#define FILE_SCRIPT 2 +#define FILE_PLAYBACK 3 +#define FILE_RECORD 4 +#define FILE_LOAD_AUX 5 +#define FILE_SAVE_AUX 6 + +/*** Data access macros ***/ + +#define SET_BYTE(addr,v) { zmp[addr] = v; } +#define LOW_BYTE(addr,v) { v = zmp[addr]; } +#define CODE_BYTE(v) { v = *pcp++; } + +#if defined (AMIGA) + +extern zbyte *pcp; +extern zbyte *zmp; + +#define lo(v) ((zbyte *)&v)[1] +#define hi(v) ((zbyte *)&v)[0] + +#define SET_WORD(addr,v) { zmp[addr] = hi(v); zmp[addr+1] = lo(v); } +#define LOW_WORD(addr,v) { hi(v) = zmp[addr]; lo(v) = zmp[addr+1]; } +#define HIGH_WORD(addr,v) { hi(v) = zmp[addr]; lo(v) = zmp[addr+1]; } +#define CODE_WORD(v) { hi(v) = *pcp++; lo(v) = *pcp++; } +#define GET_PC(v) { v = pcp - zmp; } +#define SET_PC(v) { pcp = zmp + v; } + +#endif + +#if defined (__MSDOS__) + +extern zbyte far *pcp; +extern zbyte far *zmp; + +#define lo(v) ((zbyte *)&v)[0] +#define hi(v) ((zbyte *)&v)[1] + +#define SET_WORD(addr,v) asm {\ + les bx,zmp;\ + add bx,addr;\ + mov ax,v;\ + xchg al,ah;\ + mov es:[bx],ax } + +#define LOW_WORD(addr,v) asm {\ + les bx,zmp;\ + add bx,addr;\ + mov ax,es:[bx];\ + xchg al,ah;\ + mov v,ax } + +#define HIGH_WORD(addr,v) asm {\ + mov bx,word ptr zmp;\ + add bx,word ptr addr;\ + mov al,bh;\ + mov bh,0;\ + mov ah,0;\ + adc ah,byte ptr addr+2;\ + mov cl,4;\ + shl ax,cl;\ + add ax,word ptr zmp+2;\ + mov es,ax;\ + mov ax,es:[bx];\ + xchg al,ah;\ + mov v,ax } + +#define CODE_WORD(v) asm {\ + les bx,pcp;\ + mov ax,es:[bx];\ + xchg al,ah;\ + mov v,ax;\ + add word ptr pcp,2 } + +#define GET_PC(v) asm {\ + mov bx,word ptr pcp+2;\ + sub bx,word ptr zmp+2;\ + mov ax,bx;\ + mov cl,4;\ + shl bx,cl;\ + mov cl,12;\ + shr ax,cl;\ + add bx,word ptr pcp;\ + adc al,0;\ + sub bx,word ptr zmp;\ + sbb al,0;\ + mov word ptr v,bx;\ + mov word ptr v+2,ax } + +#define SET_PC(v) asm {\ + mov bx,word ptr zmp;\ + add bx,word ptr v;\ + mov al,bh;\ + mov bh,0;\ + mov ah,0;\ + adc ah,byte ptr v+2;\ + mov cl,4;\ + shl ax,cl;\ + add ax,word ptr zmp+2;\ + mov word ptr pcp,bx;\ + mov word ptr pcp+2,ax } + +#endif + +#if !defined (AMIGA) && !defined (__MSDOS__) + +extern zbyte *pcp; +extern zbyte *zmp; + +#define lo(v) (v & 0xff) +#define hi(v) (v >> 8) + +#define SET_WORD(addr,v) { zmp[addr] = hi(v); zmp[addr+1] = lo(v); } +#define LOW_WORD(addr,v) { v = ((zword) zmp[addr] << 8) | zmp[addr+1]; } +#define HIGH_WORD(addr,v) { v = ((zword) zmp[addr] << 8) | zmp[addr+1]; } +#define CODE_WORD(v) { v = ((zword) pcp[0] << 8) | pcp[1]; pcp += 2; } +#define GET_PC(v) { v = pcp - zmp; } +#define SET_PC(v) { pcp = zmp + v; } + +#endif + +/*** Story file header data ***/ + +extern zbyte h_version; +extern zbyte h_config; +extern zword h_release; +extern zword h_resident_size; +extern zword h_start_pc; +extern zword h_dictionary; +extern zword h_objects; +extern zword h_globals; +extern zword h_dynamic_size; +extern zword h_flags; +extern zbyte h_serial[6]; +extern zword h_abbreviations; +extern zword h_file_size; +extern zword h_checksum; +extern zbyte h_interpreter_number; +extern zbyte h_interpreter_version; +extern zbyte h_screen_rows; +extern zbyte h_screen_cols; +extern zword h_screen_width; +extern zword h_screen_height; +extern zbyte h_font_height; +extern zbyte h_font_width; +extern zword h_functions_offset; +extern zword h_strings_offset; +extern zbyte h_default_background; +extern zbyte h_default_foreground; +extern zword h_terminating_keys; +extern zword h_line_width; +extern zbyte h_standard_high; +extern zbyte h_standard_low; +extern zword h_alphabet; +extern zword h_extension_table; +extern zbyte h_user_name[8]; + +extern zword hx_table_size; +extern zword hx_mouse_x; +extern zword hx_mouse_y; +extern zword hx_unicode_table; + +/*** Various data ***/ + +extern const char *story_name; + +extern enum story story_id; +extern long story_size; + +extern zword stack[STACK_SIZE]; +extern zword *sp; +extern zword *fp; + +extern zword zargs[8]; +extern zargc; + +extern bool ostream_screen; +extern bool ostream_script; +extern bool ostream_memory; +extern bool ostream_record; +extern bool istream_replay; +extern bool message; + +extern cwin; +extern mwin; + +extern mouse_x; +extern mouse_y; + +extern bool enable_wrapping; +extern bool enable_scripting; +extern bool enable_scrolling; +extern bool enable_buffering; + +extern option_attribute_assignment; +extern option_attribute_testing; +extern option_object_locating; +extern option_object_movement; +extern option_context_lines; +extern option_left_margin; +extern option_right_margin; +extern option_ignore_errors; +extern option_piracy; +extern option_undo_slots; +extern option_expand_abbreviations; +extern option_script_cols; + +extern long reserve_mem; + +/*** Z-machine opcodes ***/ + +void z_add (void); +void z_and (void); +void z_art_shift (void); +void z_buffer_mode (void); +void z_call_n (void); +void z_call_s (void); +void z_catch (void); +void z_check_arg_count (void); +void z_check_unicode (void); +void z_clear_attr (void); +void z_copy_table (void); +void z_dec (void); +void z_dec_chk (void); +void z_div (void); +void z_draw_picture (void); +void z_encode_text (void); +void z_erase_line (void); +void z_erase_picture (void); +void z_erase_window (void); +void z_get_child (void); +void z_get_cursor (void); +void z_get_next_prop (void); +void z_get_parent (void); +void z_get_prop (void); +void z_get_prop_addr (void); +void z_get_prop_len (void); +void z_get_sibling (void); +void z_get_wind_prop (void); +void z_inc (void); +void z_inc_chk (void); +void z_input_stream (void); +void z_insert_obj (void); +void z_je (void); +void z_jg (void); +void z_jin (void); +void z_jl (void); +void z_jump (void); +void z_jz (void); +void z_load (void); +void z_loadb (void); +void z_loadw (void); +void z_log_shift (void); +void z_make_menu (void); +void z_mod (void); +void z_mouse_window (void); +void z_move_window (void); +void z_mul (void); +void z_new_line (void); +void z_nop (void); +void z_not (void); +void z_or (void); +void z_output_stream (void); +void z_picture_data (void); +void z_picture_table (void); +void z_piracy (void); +void z_pop (void); +void z_pop_stack (void); +void z_print (void); +void z_print_addr (void); +void z_print_char (void); +void z_print_form (void); +void z_print_num (void); +void z_print_obj (void); +void z_print_paddr (void); +void z_print_ret (void); +void z_print_table (void); +void z_print_unicode (void); +void z_pull (void); +void z_push (void); +void z_push_stack (void); +void z_put_prop (void); +void z_put_wind_prop (void); +void z_quit (void); +void z_random (void); +void z_read (void); +void z_read_char (void); +void z_read_mouse (void); +void z_remove_obj (void); +void z_restart (void); +void z_restore (void); +void z_restore_undo (void); +void z_ret (void); +void z_ret_popped (void); +void z_rfalse (void); +void z_rtrue (void); +void z_save (void); +void z_save_undo (void); +void z_scan_table (void); +void z_scroll_window (void); +void z_set_attr (void); +void z_set_font (void); +void z_set_colour (void); +void z_set_cursor (void); +void z_set_margins (void); +void z_set_window (void); +void z_set_text_style (void); +void z_show_status (void); +void z_sound_effect (void); +void z_split_window (void); +void z_store (void); +void z_storeb (void); +void z_storew (void); +void z_sub (void); +void z_test (void); +void z_test_attr (void); +void z_throw (void); +void z_tokenise (void); +void z_verify (void); +void z_window_size (void); +void z_window_style (void); + +/*** Various global functions ***/ + +zchar translate_from_zscii (zbyte); +zbyte translate_to_zscii (zchar); + +void flush_buffer (void); +void new_line (void); +void print_char (zchar); +void print_num (zword); +void print_object (zword); +void print_string (const char *); + +void stream_mssg_on (void); +void stream_mssg_off (void); + +void runtime_error (const char *); + +void ret (zword); +void store (zword); +void branch (bool); + +void storeb (zword, zbyte); +void storew (zword, zword); + +/*** Interface functions ***/ + +void os_beep (int); +int os_char_width (zchar); +void os_display_char (zchar); +void os_display_string (const zchar *); +void os_draw_picture (int, int, int); +void os_erase_area (int, int, int, int); +void os_fatal (const char *); +void os_finish_with_sample (void); +int os_font_data (int, int *, int *); +void os_init_screen (void); +void os_more_prompt (void); +int os_peek_colour (void); +int os_picture_data (int, int *, int *); +void os_prepare_sample (int); +void os_process_arguments (int, char *[]); +int os_random_seed (void); +int os_read_file_name (char *, const char *, int); +zchar os_read_key (int, int); +zchar os_read_line (int, zchar *, int, int, int); +void os_reset_screen (void); +void os_restart_game (int); +void os_scroll_area (int, int, int, int, int); +void os_set_colour (int, int); +void os_set_cursor (int, int); +void os_set_font (int); +void os_set_text_style (int); +void os_start_sample (int, int, int); +void os_stop_sample (void); +int os_string_width (const zchar *); diff --git a/getopt.c b/getopt.c new file mode 100644 index 0000000..c1e40e1 --- /dev/null +++ b/getopt.c @@ -0,0 +1,69 @@ +/* + * getopt.c + * + * Replacement for a Unix style getopt function + * + */ + +#include +#include + +#ifndef __MSDOS__ +#define cdecl +#endif + +int optind = 1; +int optopt = 0; + +const char *optarg = NULL; + +int cdecl getopt (int argc, char *argv[], const char *options) +{ + static pos = 1; + + const char *p; + + if (optind >= argc || argv[optind][0] != '-' || argv[optind][1] == 0) + return EOF; + + optopt = argv[optind][pos++]; + optarg = NULL; + + if (argv[optind][pos] == 0) + { pos = 1; optind++; } + + p = strchr (options, optopt); + + if (optopt == ':' || p == NULL) { + + fputs ("illegal option -- ", stderr); + goto error; + + } else if (p[1] == ':') + + if (optind >= argc) { + + fputs ("option requires an argument -- ", stderr); + goto error; + + } else { + + optarg = argv[optind]; + + if (pos != 1) + optarg += pos; + + pos = 1; optind++; + + } + + return optopt; + +error: + + fputc (optopt, stderr); + fputc ('\n', stderr); + + return '?'; + +}/* getopt */ diff --git a/hotkey.c b/hotkey.c new file mode 100644 index 0000000..56bc413 --- /dev/null +++ b/hotkey.c @@ -0,0 +1,242 @@ +/* + * hotkey.c + * + * Hot key functions + * + */ + +#include "frotz.h" + +extern int restore_undo (void); + +extern int read_number (void); + +extern bool read_yes_or_no (const char *); + +extern void replay_open (void); +extern void replay_close (void); +extern void record_open (void); +extern void record_close (void); + +extern void seed_random (int); + +/* + * hot_key_debugging + * + * ...allows user to toggle cheating options on/off. + * + */ + +static bool hot_key_debugging (void) +{ + + print_string ("Debugging options\n"); + + option_attribute_assignment = read_yes_or_no ("Watch attribute assignment"); + option_attribute_testing = read_yes_or_no ("Watch attribute testing"); + option_object_movement = read_yes_or_no ("Watch object movement"); + option_object_locating = read_yes_or_no ("Watch object locating"); + + return FALSE; + +}/* hot_key_debugging */ + +/* + * hot_key_help + * + * ...displays a list of all hot keys. + * + */ + +static bool hot_key_help (void) { + + print_string ("Help\n"); + + print_string ( + "\n" + "Alt-D debugging options\n" + "Alt-H help\n" + "Alt-N new game\n" + "Alt-P playback on\n" + "Alt-R recording on/off\n" + "Alt-S seed random numbers\n" + "Alt-U undo one turn\n" + "Alt-X exit game\n"); + + return FALSE; + +}/* hot_key_help */ + +/* + * hot_key_playback + * + * ...allows user to turn playback on. + * + */ + +static bool hot_key_playback (void) +{ + + print_string ("Playback on\n"); + + if (!istream_replay) + replay_open (); + + return FALSE; + +}/* hot_key_playback */ + +/* + * hot_key_recording + * + * ...allows user to turn recording on/off. + * + */ + +static bool hot_key_recording (void) +{ + + if (istream_replay) { + print_string ("Playback off\n"); + replay_close (); + } else if (ostream_record) { + print_string ("Recording off\n"); + record_close (); + } else { + print_string ("Recording on\n"); + record_open (); + } + + return FALSE; + +}/* hot_key_recording */ + +/* + * hot_key_seed + * + * ...allows user to seed the random number seed. + * + */ + +static bool hot_key_seed (void) +{ + + print_string ("Seed random numbers\n"); + + print_string ("Enter seed value (or return to randomize): "); + seed_random (read_number ()); + + return FALSE; + +}/* hot_key_seed */ + +/* + * hot_key_undo + * + * ...allows user to undo the previous turn. + * + */ + +static bool hot_key_undo (void) +{ + + print_string ("Undo one turn\n"); + + if (restore_undo ()) { + + if (h_version >= V5) { /* for V5+ games we must */ + store (2); /* store 2 (for success) */ + return TRUE; /* and abort the input */ + } + + if (h_version <= V3) { /* for V3- games we must */ + z_show_status (); /* draw the status line */ + return FALSE; /* and continue input */ + } + + } else print_string ("No more undo information available.\n"); + + return FALSE; + +}/* hot_key_undo */ + +/* + * hot_key_restart + * + * ...allows user to start a new game. + * + */ + +static bool hot_key_restart (void) +{ + + print_string ("New game\n"); + + if (read_yes_or_no ("Do you wish to restart")) { + + z_restart (); + return TRUE; + + } else return FALSE; + +}/* hot_key_restart */ + +/* + * hot_key_quit + * + * ...allows user to exit the game. + * + */ + +static bool hot_key_quit (void) +{ + + print_string ("Exit game\n"); + + if (read_yes_or_no ("Do you wish to quit")) { + + z_quit (); + return TRUE; + + } else return FALSE; + +}/* hot_key_quit */ + +/* + * handle_hot_key + * + * Perform the action associated with a so-called hot key. Return + * true to abort the current input action. + * + */ + +bool handle_hot_key (zchar key) +{ + + if (cwin == 0) { + + bool aborting; + + print_string ("\nHot key -- "); + + switch (key) { + case ZC_HKEY_RECORD: aborting = hot_key_recording (); break; + case ZC_HKEY_PLAYBACK: aborting = hot_key_playback (); break; + case ZC_HKEY_SEED: aborting = hot_key_seed (); break; + case ZC_HKEY_UNDO: aborting = hot_key_undo (); break; + case ZC_HKEY_RESTART: aborting = hot_key_restart (); break; + case ZC_HKEY_QUIT: aborting = hot_key_quit (); break; + case ZC_HKEY_DEBUG: aborting = hot_key_debugging (); break; + case ZC_HKEY_HELP: aborting = hot_key_help (); break; + } + + if (aborting) + return TRUE; + + print_string ("\nContinue input...\n"); + + } + + return FALSE; + +}/* handle_hot_key */ diff --git a/input.c b/input.c new file mode 100644 index 0000000..0642aef --- /dev/null +++ b/input.c @@ -0,0 +1,303 @@ +/* + * input.c + * + * High level input functions + * + */ + +#include "frotz.h" + +extern int save_undo (void); + +extern zchar stream_read_key (zword, zword, bool); +extern zchar stream_read_input (int, zchar *, zword, zword, bool, bool); + +extern void tokenise_line (zword, zword, zword, bool); + +/* + * is_terminator + * + * Check if the given key is an input terminator. + * + */ + +bool is_terminator (zchar key) +{ + + if (key == ZC_TIME_OUT) + return TRUE; + if (key == ZC_RETURN) + return TRUE; + if (key >= ZC_HKEY_MIN && key <= ZC_HKEY_MAX) + return TRUE; + + if (h_terminating_keys != 0) + + if (key >= ZC_ARROW_MIN && key <= ZC_MENU_CLICK) { + + zword addr = h_terminating_keys; + zbyte c; + + do { + LOW_BYTE (addr, c) + if (c == 255 || key == translate_from_zscii (c)) + return TRUE; + addr++; + } while (c != 0); + + } + + return FALSE; + +}/* is_terminator */ + +/* + * z_make_menu, add or remove a menu and branch if successful. + * + * zargs[0] = number of menu + * zargs[1] = table of menu entries or 0 to remove menu + * + */ + +void z_make_menu (void) +{ + + /* This opcode was only used for the Macintosh version of Journey. + It controls menus with numbers greater than 2 (menus 0, 1 and 2 + are system menus). Frotz doesn't implement menus yet. */ + + branch (FALSE); + +}/* z_make_menu */ + +/* + * read_yes_or_no + * + * Ask the user a question; return true if the answer is yes. + * + */ + +bool read_yes_or_no (const char *s) +{ + zchar key; + + print_string (s); + print_string ("? (y/n) >"); + + key = stream_read_key (0, 0, FALSE); + + if (key == 'y' || key == 'Y') { + print_string ("y\n"); + return TRUE; + } else { + print_string ("n\n"); + return FALSE; + } + +}/* read_yes_or_no */ + +/* + * read_string + * + * Read a string from the current input stream. + * + */ + +void read_string (int max, zchar *buffer) +{ + zchar key; + + buffer[0] = 0; + + do { + + key = stream_read_input (max, buffer, 0, 0, FALSE, FALSE); + + } while (key != ZC_RETURN); + +}/* read_string */ + +/* + * read_number + * + * Ask the user to type in a number and return it. + * + */ + +int read_number (void) +{ + zchar buffer[6]; + int value = 0; + int i; + + read_string (5, buffer); + + for (i = 0; buffer[i] != 0; i++) + if (buffer[i] >= '0' && buffer[i] <= '9') + value = 10 * value + buffer[i] - '0'; + + return value; + +}/* read_number */ + +/* + * z_read, read a line of input and (in V5+) store the terminating key. + * + * zargs[0] = address of text buffer + * zargs[1] = address of token buffer + * zargs[2] = timeout in tenths of a second (optional) + * zargs[3] = packed address of routine to be called on timeout + * + */ + +void z_read (void) +{ + zchar buffer[INPUT_BUFFER_SIZE]; + zword addr; + zchar key; + zbyte max, size; + zbyte c; + int i; + + /* Supply default arguments */ + + if (zargc < 3) + zargs[2] = 0; + + /* Get maximum input size */ + + addr = zargs[0]; + + LOW_BYTE (addr, max) + + if (h_version <= V4) + max--; + + if (max >= INPUT_BUFFER_SIZE) + max = INPUT_BUFFER_SIZE - 1; + + /* Get initial input size */ + + if (h_version >= V5) { + addr++; + LOW_BYTE (addr, size) + } else size = 0; + + /* Copy initial input to local buffer */ + + for (i = 0; i < size; i++) { + addr++; + LOW_BYTE (addr, c) + buffer[i] = translate_from_zscii (c); + } + + buffer[i] = 0; + + /* Draw status line for V1 to V3 games */ + + if (h_version <= V3) + z_show_status (); + + /* Read input from current input stream */ + + key = stream_read_input ( + max, buffer, /* buffer and size */ + zargs[2], /* timeout value */ + zargs[3], /* timeout routine */ + TRUE, /* enable hot keys */ + h_version == V6); /* no script in V6 */ + + if (key == ZC_BAD) + return; + + /* Perform save_undo for V1 to V4 games */ + + if (h_version <= V4) + save_undo (); + + /* Copy local buffer back to dynamic memory */ + + for (i = 0; buffer[i] != 0; i++) { + + if (key == ZC_RETURN) { + + if (buffer[i] >= 'A' && buffer[i] <= 'Z') + buffer[i] += 'a' - 'A'; + if (buffer[i] >= 0xc0 && buffer[i] <= 0xde && buffer[i] != 0xd7) + buffer[i] += 0x20; + + } + + storeb ((zword) (zargs[0] + ((h_version <= V4) ? 1 : 2) + i), translate_to_zscii (buffer[i])); + + } + + /* Add null character (V1-V4) or write input length into 2nd byte */ + + if (h_version <= V4) + storeb ((zword) (zargs[0] + 1 + i), 0); + else + storeb ((zword) (zargs[0] + 1), i); + + /* Tokenise line if a token buffer is present */ + + if (key == ZC_RETURN && zargs[1] != 0) + tokenise_line (zargs[0], zargs[1], 0, FALSE); + + /* Store key */ + + if (h_version >= V5) + store (translate_to_zscii (key)); + +}/* z_read */ + +/* + * z_read_char, read and store a key. + * + * zargs[0] = input device (must be 1) + * zargs[1] = timeout in tenths of a second (optional) + * zargs[2] = packed address of routine to be called on timeout + * + */ + +void z_read_char (void) +{ + zchar key; + + /* Supply default arguments */ + + if (zargc < 2) + zargs[1] = 0; + + /* Read input from the current input stream */ + + key = stream_read_key ( + zargs[1], /* timeout value */ + zargs[2], /* timeout routine */ + TRUE); /* enable hot keys */ + + if (key == ZC_BAD) + return; + + /* Store key */ + + store (translate_to_zscii (key)); + +}/* z_read_char */ + +/* + * z_read_mouse, write the current mouse status into a table. + * + * zargs[0] = address of table + * + */ + +void z_read_mouse (void) +{ + + storew ((zword) (zargs[0] + 0), hx_mouse_y); + storew ((zword) (zargs[0] + 2), hx_mouse_x); + storew ((zword) (zargs[0] + 4), 1); /* mouse button bits */ + storew ((zword) (zargs[0] + 6), 0); /* menu selection */ + +}/* z_read_mouse */ diff --git a/main.c b/main.c new file mode 100644 index 0000000..9a8e9ba --- /dev/null +++ b/main.c @@ -0,0 +1,181 @@ +/* + * main.c + * + * Frotz V2.32 main function + * + * This is an interpreter for Infocom V1 to V6 games. It also supports + * the recently defined V7 and V8 games. Please report bugs to + * + * s.jokisch@avu.de + * + * Frotz is freeware. It may be used and distributed freely provided + * no commercial profit is involved. (c) 1995-1997 Stefan Jokisch + * + */ + +#include "frotz.h" + +#ifndef __MSDOS__ +#define cdecl +#endif + +extern void interpret (void); +extern void init_memory (void); +extern void init_undo (void); +extern void reset_memory (void); + +/* Story file name, id number and size */ + +const char *story_name = 0; + +enum story story_id = UNKNOWN; +long story_size = 0; + +/* Story file header data */ + +zbyte h_version = 0; +zbyte h_config = 0; +zword h_release = 0; +zword h_resident_size = 0; +zword h_start_pc = 0; +zword h_dictionary = 0; +zword h_objects = 0; +zword h_globals = 0; +zword h_dynamic_size = 0; +zword h_flags = 0; +zbyte h_serial[6] = { 0, 0, 0, 0, 0, 0 }; +zword h_abbreviations = 0; +zword h_file_size = 0; +zword h_checksum = 0; +zbyte h_interpreter_number = 0; +zbyte h_interpreter_version = 0; +zbyte h_screen_rows = 0; +zbyte h_screen_cols = 0; +zword h_screen_width = 0; +zword h_screen_height = 0; +zbyte h_font_height = 1; +zbyte h_font_width = 1; +zword h_functions_offset = 0; +zword h_strings_offset = 0; +zbyte h_default_background = 0; +zbyte h_default_foreground = 0; +zword h_terminating_keys = 0; +zword h_line_width = 0; +zbyte h_standard_high = 1; +zbyte h_standard_low = 0; +zword h_alphabet = 0; +zword h_extension_table = 0; +zbyte h_user_name[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + +zword hx_table_size = 0; +zword hx_mouse_x = 0; +zword hx_mouse_y = 0; +zword hx_unicode_table = 0; + +/* Stack data */ + +zword stack[STACK_SIZE]; +zword *sp = 0; +zword *fp = 0; + +/* IO streams */ + +bool ostream_screen = TRUE; +bool ostream_script = FALSE; +bool ostream_memory = FALSE; +bool ostream_record = FALSE; +bool istream_replay = FALSE; +bool message = FALSE; + +/* Current window and mouse data */ + +int cwin = 0; +int mwin = 0; + +int mouse_y = 0; +int mouse_x = 0; + +/* Window attributes */ + +bool enable_wrapping = FALSE; +bool enable_scripting = FALSE; +bool enable_scrolling = FALSE; +bool enable_buffering = FALSE; + +/* User options */ + +int option_attribute_assignment = 0; +int option_attribute_testing = 0; +int option_context_lines = 0; +int option_object_locating = 0; +int option_object_movement = 0; +int option_left_margin = 0; +int option_right_margin = 0; +int option_ignore_errors = 0; +int option_piracy = 0; +int option_undo_slots = MAX_UNDO_SLOTS; +int option_expand_abbreviations = 0; +int option_script_cols = 80; + +/* Size of memory to reserve (in bytes) */ + +long reserve_mem = 0; + +/* + * runtime_error + * + * An error has occured. Ignore it or pass it to os_fatal. + * + */ + +void runtime_error (const char *s) +{ + + if (!option_ignore_errors) + { flush_buffer (); os_fatal (s); } + +}/* runtime_error */ + +/* + * z_piracy, branch if the story file is a legal copy. + * + * no zargs used + * + */ + +void z_piracy (void) +{ + + branch (!option_piracy); + +}/* z_piracy */ + +/* + * main + * + * Prepare and run the game. + * + */ + +int cdecl main (int argc, char *argv[]) +{ + + os_process_arguments (argc, argv); + + init_memory (); + + os_init_screen (); + + init_undo (); + + z_restart (); + + interpret (); + + reset_memory (); + + os_reset_screen (); + + return 0; + +}/* main */ diff --git a/math.c b/math.c new file mode 100644 index 0000000..20ae247 --- /dev/null +++ b/math.c @@ -0,0 +1,248 @@ +/* + * math.c + * + * Arithmetic, compare and logical opcodes + * + */ + +#include "frotz.h" + +/* + * z_add, 16bit addition. + * + * zargs[0] = first value + * zargs[1] = second value + * + */ + +void z_add (void) +{ + + store ((zword) ((short) zargs[0] + (short) zargs[1])); + +}/* z_add */ + +/* + * z_and, bitwise AND operation. + * + * zargs[0] = first value + * zargs[1] = second value + * + */ + +void z_and (void) +{ + + store ((zword) (zargs[0] & zargs[1])); + +}/* z_and */ + +/* + * z_art_shift, arithmetic SHIFT operation. + * + * zargs[0] = value + * zargs[1] = #positions to shift left (positive) or right + * + */ + +void z_art_shift (void) +{ + + if ((short) zargs[1] > 0) + store ((zword) ((short) zargs[0] << (short) zargs[1])); + else + store ((zword) ((short) zargs[0] >> - (short) zargs[1])); + +}/* z_art_shift */ + +/* + * z_div, signed 16bit division. + * + * zargs[0] = first value + * zargs[1] = second value + * + */ + +void z_div (void) +{ + + if (zargs[1] == 0) + runtime_error ("Division by zero"); + + store ((zword) ((short) zargs[0] / (short) zargs[1])); + +}/* z_div */ + +/* + * z_je, branch if the first value equals any of the following. + * + * zargs[0] = first value + * zargs[1] = second value (optional) + * ... + * zargs[3] = fourth value (optional) + * + */ + +void z_je (void) +{ + + branch ( + zargc > 1 && (zargs[0] == zargs[1] || ( + zargc > 2 && (zargs[0] == zargs[2] || ( + zargc > 3 && (zargs[0] == zargs[3])))))); + +}/* z_je */ + +/* + * z_jg, branch if the first value is greater than the second. + * + * zargs[0] = first value + * zargs[1] = second value + * + */ + +void z_jg (void) +{ + + branch ((short) zargs[0] > (short) zargs[1]); + +}/* z_jg */ + +/* + * z_jl, branch if the first value is less than the second. + * + * zargs[0] = first value + * zargs[1] = second value + * + */ + +void z_jl (void) +{ + + branch ((short) zargs[0] < (short) zargs[1]); + +}/* z_jl */ + +/* + * z_jz, branch if value is zero. + * + * zargs[0] = value + * + */ + +void z_jz (void) +{ + + branch ((short) zargs[0] == 0); + +}/* z_jz */ + +/* + * z_log_shift, logical SHIFT operation. + * + * zargs[0] = value + * zargs[1] = #positions to shift left (positive) or right (negative) + * + */ + +void z_log_shift (void) +{ + + if ((short) zargs[1] > 0) + store ((zword) (zargs[0] << (short) zargs[1])); + else + store ((zword) (zargs[0] >> - (short) zargs[1])); + +}/* z_log_shift */ + +/* + * z_mod, remainder after signed 16bit division. + * + * zargs[0] = first value + * zargs[1] = second value + * + */ + +void z_mod (void) +{ + + if (zargs[1] == 0) + runtime_error ("Division by zero"); + + store ((zword) ((short) zargs[0] % (short) zargs[1])); + +}/* z_mod */ + +/* + * z_mul, 16bit multiplication. + * + * zargs[0] = first value + * zargs[1] = second value + * + */ + +void z_mul (void) +{ + + store ((zword) ((short) zargs[0] * (short) zargs[1])); + +}/* z_mul */ + +/* + * z_not, bitwise NOT operation. + * + * zargs[0] = value + * + */ + +void z_not (void) +{ + + store ((zword) ~zargs[0]); + +}/* z_not */ + +/* + * z_or, bitwise OR operation. + * + * zargs[0] = first value + * zargs[1] = second value + * + */ + +void z_or (void) +{ + + store ((zword) (zargs[0] | zargs[1])); + +}/* z_or */ + +/* + * z_sub, 16bit substraction. + * + * zargs[0] = first value + * zargs[1] = second value + * + */ + +void z_sub (void) +{ + + store ((zword) ((short) zargs[0] - (short) zargs[1])); + +}/* z_sub */ + +/* + * z_test, branch if all the flags of a bit mask are set in a value. + * + * zargs[0] = value to be examined + * zargs[1] = bit mask + * + */ + +void z_test (void) +{ + + branch ((zargs[0] & zargs[1]) == zargs[1]); + +}/* z_test */ diff --git a/object.c b/object.c new file mode 100644 index 0000000..c6c79e5 --- /dev/null +++ b/object.c @@ -0,0 +1,904 @@ +/* + * object.c + * + * Object manipulation opcodes + * + */ + +#include "frotz.h" + +#define MAX_OBJECT 2000 + +#define O1_PARENT 4 +#define O1_SIBLING 5 +#define O1_CHILD 6 +#define O1_PROPERTY_OFFSET 7 +#define O1_SIZE 9 + +#define O4_PARENT 6 +#define O4_SIBLING 8 +#define O4_CHILD 10 +#define O4_PROPERTY_OFFSET 12 +#define O4_SIZE 14 + +/* + * object_address + * + * Calculate the address of an object. + * + */ + +static zword object_address (zword obj) +{ + + /* Check object number */ + + if (obj > ((h_version <= V3) ? 255 : MAX_OBJECT)) + runtime_error ("Illegal object"); + + /* Return object address */ + + if (h_version <= V3) + return h_objects + ((obj - 1) * O1_SIZE + 62); + else + return h_objects + ((obj - 1) * O4_SIZE + 126); + +}/* object_address */ + +/* + * object_name + * + * Return the address of the given object's name. + * + */ + +zword object_name (zword object) +{ + zword obj_addr; + zword name_addr; + + obj_addr = object_address (object); + + /* The object name address is found at the start of the properties */ + + if (h_version <= V3) + obj_addr += O1_PROPERTY_OFFSET; + else + obj_addr += O4_PROPERTY_OFFSET; + + LOW_WORD (obj_addr, name_addr) + + return name_addr; + +}/* object_name */ + +/* + * first_property + * + * Calculate the start address of the property list associated with + * an object. + * + */ + +static zword first_property (zword obj) +{ + zword prop_addr; + zbyte size; + + /* Fetch address of object name */ + + prop_addr = object_name (obj); + + /* Get length of object name */ + + LOW_BYTE (prop_addr, size) + + /* Add name length to pointer */ + + return prop_addr + 1 + 2 * size; + +}/* first_property */ + +/* + * next_property + * + * Calculate the address of the next property in a property list. + * + */ + +static zword next_property (zword prop_addr) +{ + zbyte value; + + /* Load the current property id */ + + LOW_BYTE (prop_addr, value) + prop_addr++; + + /* Calculate the length of this property */ + + if (h_version <= V3) + value >>= 5; + else if (!(value & 0x80)) + value >>= 6; + else { + + LOW_BYTE (prop_addr, value) + value &= 0x3f; + + if (value == 0) value = 64; /* demanded by Spec 1.0 */ + + } + + /* Add property length to current property pointer */ + + return prop_addr + value + 1; + +}/* next_property */ + +/* + * unlink_object + * + * Unlink an object from its parent and siblings. + * + */ + +static void unlink_object (zword object) +{ + zword obj_addr; + zword parent_addr; + zword sibling_addr; + + obj_addr = object_address (object); + + if (h_version <= V3) { + + zbyte parent; + zbyte younger_sibling; + zbyte older_sibling; + zbyte zero = 0; + + /* Get parent of object, and return if no parent */ + + obj_addr += O1_PARENT; + LOW_BYTE (obj_addr, parent) + if (!parent) + return; + + /* Get (older) sibling of object and set both parent and sibling + pointers to 0 */ + + SET_BYTE (obj_addr, zero) + obj_addr += O1_SIBLING - O1_PARENT; + LOW_BYTE (obj_addr, older_sibling) + SET_BYTE (obj_addr, zero) + + /* Get first child of parent (the youngest sibling of the object) */ + + parent_addr = object_address (parent) + O1_CHILD; + LOW_BYTE (parent_addr, younger_sibling) + + /* Remove object from the list of siblings */ + + if (younger_sibling == object) + SET_BYTE (parent_addr, older_sibling) + else { + do { + sibling_addr = object_address (younger_sibling) + O1_SIBLING; + LOW_BYTE (sibling_addr, younger_sibling) + } while (younger_sibling != object); + SET_BYTE (sibling_addr, older_sibling) + } + + } else { + + zword parent; + zword younger_sibling; + zword older_sibling; + zword zero = 0; + + /* Get parent of object, and return if no parent */ + + obj_addr += O4_PARENT; + LOW_WORD (obj_addr, parent) + if (!parent) + return; + + /* Get (older) sibling of object and set both parent and sibling + pointers to 0 */ + + SET_WORD (obj_addr, zero) + obj_addr += O4_SIBLING - O4_PARENT; + LOW_WORD (obj_addr, older_sibling) + SET_WORD (obj_addr, zero) + + /* Get first child of parent (the youngest sibling of the object) */ + + parent_addr = object_address (parent) + O4_CHILD; + LOW_WORD (parent_addr, younger_sibling) + + /* Remove object from the list of siblings */ + + if (younger_sibling == object) + SET_WORD (parent_addr, older_sibling) + else { + do { + sibling_addr = object_address (younger_sibling) + O4_SIBLING; + LOW_WORD (sibling_addr, younger_sibling) + } while (younger_sibling != object); + SET_WORD (sibling_addr, older_sibling) + } + + } + +}/* unlink_object */ + +/* + * z_clear_attr, clear an object attribute. + * + * zargs[0] = object + * zargs[1] = number of attribute to be cleared + * + */ + +void z_clear_attr (void) +{ + zword obj_addr; + zbyte value; + + if (story_id == SHERLOCK) + if (zargs[1] == 48) + return; + + if (zargs[1] > ((h_version <= V3) ? 31 : 47)) + runtime_error ("Illegal attribute"); + + /* If we are monitoring attribute assignment display a short note */ + + if (option_attribute_assignment) { + stream_mssg_on (); + print_string ("@clear_attr "); + print_object (zargs[0]); + print_string (" "); + print_num (zargs[1]); + stream_mssg_off (); + } + + /* Get attribute address */ + + obj_addr = object_address (zargs[0]) + zargs[1] / 8; + + /* Clear attribute bit */ + + LOW_BYTE (obj_addr, value) + value &= ~(0x80 >> (zargs[1] & 7)); + SET_BYTE (obj_addr, value) + +}/* z_clear_attr */ + +/* + * z_jin, branch if the first object is inside the second. + * + * zargs[0] = first object + * zargs[1] = second object + * + */ + +void z_jin (void) +{ + zword obj_addr; + + /* If we are monitoring object locating display a short note */ + + if (option_object_locating) { + stream_mssg_on (); + print_string ("@jin "); + print_object (zargs[0]); + print_string (" "); + print_object (zargs[1]); + stream_mssg_off (); + } + + obj_addr = object_address (zargs[0]); + + if (h_version <= V3) { + + zbyte parent; + + /* Get parent id from object */ + + obj_addr += O1_PARENT; + LOW_BYTE (obj_addr, parent) + + /* Branch if the parent is obj2 */ + + branch (parent == zargs[1]); + + } else { + + zword parent; + + /* Get parent id from object */ + + obj_addr += O4_PARENT; + LOW_WORD (obj_addr, parent) + + /* Branch if the parent is obj2 */ + + branch (parent == zargs[1]); + + } + +}/* z_jin */ + +/* + * z_get_child, store the child of an object. + * + * zargs[0] = object + * + */ + +void z_get_child (void) +{ + zword obj_addr; + + /* If we are monitoring object locating display a short note */ + + if (option_object_locating) { + stream_mssg_on (); + print_string ("@get_child "); + print_object (zargs[0]); + stream_mssg_off (); + } + + obj_addr = object_address (zargs[0]); + + if (h_version <= V3) { + + zbyte child; + + /* Get child id from object */ + + obj_addr += O1_CHILD; + LOW_BYTE (obj_addr, child) + + /* Store child id and branch */ + + store (child); + branch (child); + + } else { + + zword child; + + /* Get child id from object */ + + obj_addr += O4_CHILD; + LOW_WORD (obj_addr, child) + + /* Store child id and branch */ + + store (child); + branch (child); + + } + +}/* z_get_child */ + +/* + * z_get_next_prop, store the number of the first or next property. + * + * zargs[0] = object + * zargs[1] = address of current property (0 gets the first property) + * + */ + +void z_get_next_prop (void) +{ + zword prop_addr; + zbyte value; + zbyte mask; + + /* Property id is in bottom five (six) bits */ + + mask = (h_version <= V3) ? 0x1f : 0x3f; + + /* Load address of first property */ + + prop_addr = first_property (zargs[0]); + + if (zargs[1] != 0) { + + /* Scan down the property list */ + + do { + LOW_BYTE (prop_addr, value) + prop_addr = next_property (prop_addr); + } while ((value & mask) > zargs[1]); + + /* Exit if the property does not exist */ + + if ((value & mask) != zargs[1]) + runtime_error ("No such property"); + + } + + /* Return the property id */ + + LOW_BYTE (prop_addr, value) + store ((zword) (value & mask)); + +}/* z_get_next_prop */ + +/* + * z_get_parent, store the parent of an object. + * + * zargs[0] = object + * + */ + +void z_get_parent (void) +{ + zword obj_addr; + + /* If we are monitoring object locating display a short note */ + + if (option_object_locating) { + stream_mssg_on (); + print_string ("@get_parent "); + print_object (zargs[0]); + stream_mssg_off (); + } + + obj_addr = object_address (zargs[0]); + + if (h_version <= V3) { + + zbyte parent; + + /* Get parent id from object */ + + obj_addr += O1_PARENT; + LOW_BYTE (obj_addr, parent) + + /* Store parent */ + + store (parent); + + } else { + + zword parent; + + /* Get parent id from object */ + + obj_addr += O4_PARENT; + LOW_WORD (obj_addr, parent) + + /* Store parent */ + + store (parent); + + } + +}/* z_get_parent */ + +/* + * z_get_prop, store the value of an object property. + * + * zargs[0] = object + * zargs[1] = number of property to be examined + * + */ + +void z_get_prop (void) +{ + zword prop_addr; + zword wprop_val; + zbyte bprop_val; + zbyte value; + zbyte mask; + + /* Property id is in bottom five (six) bits */ + + mask = (h_version <= V3) ? 0x1f : 0x3f; + + /* Load address of first property */ + + prop_addr = first_property (zargs[0]); + + /* Scan down the property list */ + + for (;;) { + LOW_BYTE (prop_addr, value) + if ((value & mask) <= zargs[1]) + break; + prop_addr = next_property (prop_addr); + } + + if ((value & mask) == zargs[1]) { /* property found */ + + /* Load property (byte or word sized) */ + + prop_addr++; + + if (h_version <= V3 && !(value & 0xe0) || h_version >= V4 && !(value & 0xc0)) { + + LOW_BYTE (prop_addr, bprop_val) + wprop_val = bprop_val; + + } else LOW_WORD (prop_addr, wprop_val) + + } else { /* property not found */ + + /* Load default value */ + + prop_addr = h_objects + 2 * (zargs[1] - 1); + LOW_WORD (prop_addr, wprop_val) + + } + + /* Store the property value */ + + store (wprop_val); + +}/* z_get_prop */ + +/* + * z_get_prop_addr, store the address of an object property. + * + * zargs[0] = object + * zargs[1] = number of property to be examined + * + */ + +void z_get_prop_addr (void) +{ + zword prop_addr; + zbyte value; + zbyte mask; + + if (story_id == BEYOND_ZORK) + if (zargs[0] > MAX_OBJECT) + { store (0); return; } + + /* Property id is in bottom five (six) bits */ + + mask = (h_version <= V3) ? 0x1f : 0x3f; + + /* Load address of first property */ + + prop_addr = first_property (zargs[0]); + + /* Scan down the property list */ + + for (;;) { + LOW_BYTE (prop_addr, value) + if ((value & mask) <= zargs[1]) + break; + prop_addr = next_property (prop_addr); + } + + /* Calculate the property address or return zero */ + + if ((value & mask) == zargs[1]) { + + if (h_version >= V4 && (value & 0x80)) + prop_addr++; + store ((zword) (prop_addr + 1)); + + } else store (0); + +}/* z_get_prop_addr */ + +/* + * z_get_prop_len, store the length of an object property. + * + * zargs[0] = address of property to be examined + * + */ + +void z_get_prop_len (void) +{ + zword addr; + zbyte value; + + /* Back up the property pointer to the property id */ + + addr = zargs[0] - 1; + LOW_BYTE (addr, value) + + /* Calculate length of property */ + + if (h_version <= V3) + value = (value >> 5) + 1; + else if (!(value & 0x80)) + value = (value >> 6) + 1; + else { + + value &= 0x3f; + + if (value == 0) value = 64; /* demanded by Spec 1.0 */ + + } + + /* Store length of property */ + + store (value); + +}/* z_get_prop_len */ + +/* + * z_get_sibling, store the sibling of an object. + * + * zargs[0] = object + * + */ + +void z_get_sibling (void) +{ + zword obj_addr; + + obj_addr = object_address (zargs[0]); + + if (h_version <= V3) { + + zbyte sibling; + + /* Get sibling id from object */ + + obj_addr += O1_SIBLING; + LOW_BYTE (obj_addr, sibling) + + /* Store sibling and branch */ + + store (sibling); + branch (sibling); + + } else { + + zword sibling; + + /* Get sibling id from object */ + + obj_addr += O4_SIBLING; + LOW_WORD (obj_addr, sibling) + + /* Store sibling and branch */ + + store (sibling); + branch (sibling); + + } + +}/* z_get_sibling */ + +/* + * z_insert_obj, make an object the first child of another object. + * + * zargs[0] = object to be moved + * zargs[1] = destination object + * + */ + +void z_insert_obj (void) +{ + zword obj1 = zargs[0]; + zword obj2 = zargs[1]; + zword obj1_addr; + zword obj2_addr; + + /* If we are monitoring object movements display a short note */ + + if (option_object_movement) { + stream_mssg_on (); + print_string ("@move_obj "); + print_object (obj1); + print_string (" "); + print_object (obj2); + stream_mssg_off (); + } + + /* Get addresses of both objects */ + + obj1_addr = object_address (obj1); + obj2_addr = object_address (obj2); + + /* Remove object 1 from current parent */ + + unlink_object (obj1); + + /* Make object 1 first child of object 2 */ + + if (h_version <= V3) { + + zbyte child; + + obj1_addr += O1_PARENT; + SET_BYTE (obj1_addr, obj2) + obj2_addr += O1_CHILD; + LOW_BYTE (obj2_addr, child) + SET_BYTE (obj2_addr, obj1) + obj1_addr += O1_SIBLING - O1_PARENT; + SET_BYTE (obj1_addr, child) + + } else { + + zword child; + + obj1_addr += O4_PARENT; + SET_WORD (obj1_addr, obj2) + obj2_addr += O4_CHILD; + LOW_WORD (obj2_addr, child) + SET_WORD (obj2_addr, obj1) + obj1_addr += O4_SIBLING - O4_PARENT; + SET_WORD (obj1_addr, child) + + } + +}/* z_insert_obj */ + +/* + * z_put_prop, set the value of an object property. + * + * zargs[0] = object + * zargs[1] = number of property to set + * zargs[2] = value to set property to + * + */ + +void z_put_prop (void) +{ + zword prop_addr; + zword value; + zbyte mask; + + /* Property id is in bottom five or six bits */ + + mask = (h_version <= V3) ? 0x1f : 0x3f; + + /* Load address of first property */ + + prop_addr = first_property (zargs[0]); + + /* Scan down the property list */ + + for (;;) { + LOW_BYTE (prop_addr, value) + if ((value & mask) <= zargs[1]) + break; + prop_addr = next_property (prop_addr); + } + + /* Exit if the property does not exist */ + + if ((value & mask) != zargs[1]) + runtime_error ("No such property"); + + /* Store the new property value (byte or word sized) */ + + prop_addr++; + + if (h_version <= V3 && !(value & 0xe0) || h_version >= V4 && !(value & 0xc0)) { + zbyte v = zargs[2]; + SET_BYTE (prop_addr, v) + } else { + zword v = zargs[2]; + SET_WORD (prop_addr, v) + } + +}/* z_put_prop */ + +/* + * z_remove_obj, unlink an object from its parent and siblings. + * + * zargs[0] = object + * + */ + +void z_remove_obj (void) +{ + + /* If we are monitoring object movements display a short note */ + + if (option_object_movement) { + stream_mssg_on (); + print_string ("@remove_obj "); + print_object (zargs[0]); + stream_mssg_off (); + } + + /* Call unlink_object to do the job */ + + unlink_object (zargs[0]); + +}/* z_remove_obj */ + +/* + * z_set_attr, set an object attribute. + * + * zargs[0] = object + * zargs[1] = number of attribute to set + * + */ + +void z_set_attr (void) +{ + zword obj_addr; + zbyte value; + + if (story_id == SHERLOCK) + if (zargs[1] == 48) + return; + + if (zargs[1] > ((h_version <= V3) ? 31 : 47)) + runtime_error ("Illegal attribute"); + + /* If we are monitoring attribute assignment display a short note */ + + if (option_attribute_assignment) { + stream_mssg_on (); + print_string ("@set_attr "); + print_object (zargs[0]); + print_string (" "); + print_num (zargs[1]); + stream_mssg_off (); + } + + /* Get attribute address */ + + obj_addr = object_address (zargs[0]) + zargs[1] / 8; + + /* Load attribute byte */ + + LOW_BYTE (obj_addr, value) + + /* Set attribute bit */ + + value |= 0x80 >> (zargs[1] & 7); + + /* Store attribute byte */ + + SET_BYTE (obj_addr, value) + +}/* z_set_attr */ + +/* + * z_test_attr, branch if an object attribute is set. + * + * zargs[0] = object + * zargs[1] = number of attribute to test + * + */ + +void z_test_attr (void) +{ + zword obj_addr; + zbyte value; + + if (zargs[1] > ((h_version <= V3) ? 31 : 47)) + runtime_error ("Illegal attribute"); + + /* If we are monitoring attribute testing display a short note */ + + if (option_attribute_testing) { + stream_mssg_on (); + print_string ("@test_attr "); + print_object (zargs[0]); + print_string (" "); + print_num (zargs[1]); + stream_mssg_off (); + } + + /* Get attribute address */ + + obj_addr = object_address (zargs[0]) + zargs[1] / 8; + + /* Load attribute byte */ + + LOW_BYTE (obj_addr, value) + + /* Test attribute */ + + branch (value & (0x80 >> (zargs[1] & 7))); + +}/* z_test_attr */ diff --git a/process.c b/process.c new file mode 100644 index 0000000..dd42baa --- /dev/null +++ b/process.c @@ -0,0 +1,746 @@ +/* + * process.c + * + * Interpreter loop and program control + * + */ + +#include "frotz.h" + +zword zargs[8]; +int zargc; + +static finished = 0; + +static void __extended__ (void); +static void __illegal__ (void); + +void (*op0_opcodes[0x10]) (void) = { + z_rtrue, + z_rfalse, + z_print, + z_print_ret, + z_nop, + z_save, + z_restore, + z_restart, + z_ret_popped, + z_catch, + z_quit, + z_new_line, + z_show_status, + z_verify, + __extended__, + z_piracy +}; + +void (*op1_opcodes[0x10]) (void) = { + z_jz, + z_get_sibling, + z_get_child, + z_get_parent, + z_get_prop_len, + z_inc, + z_dec, + z_print_addr, + z_call_s, + z_remove_obj, + z_print_obj, + z_ret, + z_jump, + z_print_paddr, + z_load, + z_call_n +}; + +void (*var_opcodes[0x40]) (void) = { + __illegal__, + z_je, + z_jl, + z_jg, + z_dec_chk, + z_inc_chk, + z_jin, + z_test, + z_or, + z_and, + z_test_attr, + z_set_attr, + z_clear_attr, + z_store, + z_insert_obj, + z_loadw, + z_loadb, + z_get_prop, + z_get_prop_addr, + z_get_next_prop, + z_add, + z_sub, + z_mul, + z_div, + z_mod, + z_call_s, + z_call_n, + z_set_colour, + z_throw, + __illegal__, + __illegal__, + __illegal__, + z_call_s, + z_storew, + z_storeb, + z_put_prop, + z_read, + z_print_char, + z_print_num, + z_random, + z_push, + z_pull, + z_split_window, + z_set_window, + z_call_s, + z_erase_window, + z_erase_line, + z_set_cursor, + z_get_cursor, + z_set_text_style, + z_buffer_mode, + z_output_stream, + z_input_stream, + z_sound_effect, + z_read_char, + z_scan_table, + z_not, + z_call_n, + z_call_n, + z_tokenise, + z_encode_text, + z_copy_table, + z_print_table, + z_check_arg_count +}; + +void (*ext_opcodes[0x1d]) (void) = { + z_save, + z_restore, + z_log_shift, + z_art_shift, + z_set_font, + z_draw_picture, + z_picture_data, + z_erase_picture, + z_set_margins, + z_save_undo, + z_restore_undo, + z_print_unicode, + z_check_unicode, + __illegal__, + __illegal__, + __illegal__, + z_move_window, + z_window_size, + z_window_style, + z_get_wind_prop, + z_scroll_window, + z_pop_stack, + z_read_mouse, + z_mouse_window, + z_push_stack, + z_put_wind_prop, + z_print_form, + z_make_menu, + z_picture_table +}; + +/* + * load_operand + * + * Load an operand, either a variable or a constant. + * + */ + +static void load_operand (zbyte type) +{ + zword value; + + if (type & 2) { /* variable */ + + zbyte variable; + + CODE_BYTE (variable) + + if (variable == 0) + value = *sp++; + else if (variable < 16) + value = *(fp - variable); + else { + zword addr = h_globals + 2 * (variable - 16); + LOW_WORD (addr, value) + } + + } else if (type & 1) { /* small constant */ + + zbyte bvalue; + + CODE_BYTE (bvalue) + value = bvalue; + + } else CODE_WORD (value) /* large constant */ + + zargs[zargc++] = value; + +}/* load_operand */ + +/* + * load_all_operands + * + * Given the operand specifier byte, load all (up to four) operands + * for a VAR or EXT opcode. + * + */ + +static void load_all_operands (zbyte specifier) +{ + int i; + + for (i = 6; i >= 0; i -= 2) { + + zbyte type = (specifier >> i) & 0x03; + + if (type == 3) + break; + + load_operand (type); + + } + +}/* load_all_operands */ + +/* + * interpret + * + * Z-code interpreter main loop + * + */ + +void interpret (void) +{ + + do { + + zbyte opcode; + + CODE_BYTE (opcode) + + zargc = 0; + + if (opcode < 0x80) { /* 2OP opcodes */ + + load_operand ((zbyte) (opcode & 0x40) ? 2 : 1); + load_operand ((zbyte) (opcode & 0x20) ? 2 : 1); + + var_opcodes[opcode & 0x1f] (); + + } else if (opcode < 0xb0) { /* 1OP opcodes */ + + load_operand ((zbyte) (opcode >> 4)); + + op1_opcodes[opcode & 0x0f] (); + + } else if (opcode < 0xc0) { /* 0OP opcodes */ + + op0_opcodes[opcode - 0xb0] (); + + } else { /* VAR opcodes */ + + zbyte specifier1; + zbyte specifier2; + + if (opcode == 0xec || opcode == 0xfa) { /* opcodes 0xec */ + CODE_BYTE (specifier1) /* and 0xfa are */ + CODE_BYTE (specifier2) /* call opcodes */ + load_all_operands (specifier1); /* with up to 8 */ + load_all_operands (specifier2); /* arguments */ + } else { + CODE_BYTE (specifier1) + load_all_operands (specifier1); + } + + var_opcodes[opcode - 0xc0] (); + + } + + } while (finished == 0); + + finished--; + +}/* interpret */ + +/* + * call + * + * Call a subroutine. Save PC and FP then load new PC and initialise + * new stack frame. Note that the caller may legally provide less or + * more arguments than the function actually has. The call type "ct" + * can be 0 (z_call_s), 1 (z_call_n) or 2 (direct call). + * + */ + +void call (zword routine, int argc, zword *args, int ct) +{ + long pc; + zword value; + zbyte count; + int i; + + if (sp - stack < 4) + runtime_error ("Stack overflow"); + + GET_PC (pc) + + *--sp = (zword) (pc >> 9); /* for historical reasons */ + *--sp = (zword) (pc & 0x1ff); /* Frotz keeps its stack */ + *--sp = (zword) (fp - stack - 1); /* format compatible with */ + *--sp = (zword) (argc | (ct << 8)); /* Mark Howell's Zip */ + + fp = sp; + + /* Calculate byte address of routine */ + + if (h_version <= V3) + pc = (long) routine << 1; + else if (h_version <= V5) + pc = (long) routine << 2; + else if (h_version <= V7) + pc = ((long) routine << 2) + ((long) h_functions_offset << 3); + else /* h_version == V8 */ + pc = (long) routine << 3; + + if (pc >= story_size) + runtime_error ("Call to illegal address"); + + SET_PC (pc) + + /* Initialise local variables */ + + CODE_BYTE (count) + + if (count > 15) + runtime_error ("Call to non-routine"); + if (sp - stack < count) + runtime_error ("Stack overflow"); + + value = 0; + + for (i = 0; i < count; i++) { + + if (h_version <= V4) /* V1 to V4 games provide default */ + CODE_WORD (value) /* values for all local variables */ + + *--sp = (zword) ((argc-- > 0) ? args[i] : value); + + } + + /* Start main loop for direct calls */ + + if (ct == 2) + interpret (); + +}/* call */ + +/* + * ret + * + * Return from the current subroutine and restore the previous stack + * frame. The result may be stored (0), thrown away (1) or pushed on + * the stack (2). In the latter case a direct call has been finished + * and we must exit the interpreter loop. + * + */ + +void ret (zword value) +{ + long pc; + int ct; + + if (sp > fp) + runtime_error ("Stack underflow"); + + sp = fp; + + ct = *sp++ >> 8; + fp = stack + 1 + *sp++; + pc = *sp++; + pc = ((long) *sp++ << 9) | pc; + + SET_PC (pc) + + /* Handle resulting value */ + + if (ct == 0) + store (value); + if (ct == 2) + *--sp = value; + + /* Stop main loop for direct calls */ + + if (ct == 2) + finished++; + +}/* ret */ + +/* + * branch + * + * Take a jump after an instruction based on the flag, either true or + * false. The branch can be short or long; it is encoded in one or two + * bytes respectively. When bit 7 of the first byte is set, the jump + * takes place if the flag is true; otherwise it is taken if the flag + * is false. When bit 6 of the first byte is set, the branch is short; + * otherwise it is long. The offset occupies the bottom 6 bits of the + * first byte plus all the bits in the second byte for long branches. + * Uniquely, an offset of 0 means return false, and an offset of 1 is + * return true. + * + */ + +void branch (bool flag) +{ + long pc; + zword offset; + zbyte specifier; + zbyte off1; + zbyte off2; + + CODE_BYTE (specifier) + + off1 = specifier & 0x3f; + + if (!flag) + specifier ^= 0x80; + + if (!(specifier & 0x40)) { /* it's a long branch */ + + if (off1 & 0x20) /* propagate sign bit */ + off1 |= 0xc0; + + CODE_BYTE (off2) + + offset = (off1 << 8) | off2; + + } else offset = off1; /* it's a short branch */ + + if (specifier & 0x80) + + if (offset > 1) { /* normal branch */ + + GET_PC (pc) + pc += (short) offset - 2; + SET_PC (pc) + + } else ret (offset); /* special case, return 0 or 1 */ + +}/* branch */ + +/* + * store + * + * Store an operand, either as a variable or pushed on the stack. + * + */ + +void store (zword value) +{ + zbyte variable; + + CODE_BYTE (variable) + + if (variable == 0) + *--sp = value; + else if (variable < 16) + *(fp - variable) = value; + else { + zword addr = h_globals + 2 * (variable - 16); + SET_WORD (addr, value) + } + +}/* store */ + +/* + * direct_call + * + * Call the interpreter loop directly. This is necessary when + * + * - a sound effect has been finished + * - a read instruction has timed out + * - a newline countdown has hit zero + * + * The interpreter returns the result value on the stack. + * + */ + +int direct_call (zword addr) +{ + zword saved_zargs[8]; + int saved_zargc; + int i; + + /* Calls to address 0 return false */ + + if (addr == 0) + return 0; + + /* Save operands and operand count */ + + for (i = 0; i < 8; i++) + saved_zargs[i] = zargs[i]; + + saved_zargc = zargc; + + /* Call routine directly */ + + call (addr, 0, 0, 2); + + /* Restore operands and operand count */ + + for (i = 0; i < 8; i++) + zargs[i] = saved_zargs[i]; + + zargc = saved_zargc; + + /* Resulting value lies on top of the stack */ + + return (short) *sp++; + +}/* direct_call */ + +/* + * __extended__ + * + * Load and execute an extended opcode. + * + */ + +static void __extended__ (void) +{ + zbyte opcode; + zbyte specifier; + + CODE_BYTE (opcode) + CODE_BYTE (specifier) + + load_all_operands (specifier); + + if (opcode < 0x1d) /* extended opcodes from 0x1d on */ + ext_opcodes[opcode] (); /* are reserved for future spec' */ + +}/* __extended__ */ + +/* + * __illegal__ + * + * Exit game because an unknown opcode has been hit. + * + */ + +static void __illegal__ (void) +{ + + runtime_error ("Illegal opcode"); + +}/* __illegal__ */ + +/* + * z_catch, store the current stack frame for later use with z_throw. + * + * no zargs used + * + */ + +void z_catch (void) +{ + + store ((zword) (fp - stack)); + +}/* z_catch */ + +/* + * z_throw, go back to the given stack frame and return the given value. + * + * zargs[0] = value to return + * zargs[1] = stack frame + * + */ + +void z_throw (void) +{ + + if (zargs[1] > STACK_SIZE) + runtime_error ("Bad stack frame"); + + fp = stack + zargs[1]; + + ret (zargs[0]); + +}/* z_throw */ + +/* + * z_call_n, call a subroutine and discard its result. + * + * zargs[0] = packed address of subroutine + * zargs[1] = first argument (optional) + * ... + * zargs[7] = seventh argument (optional) + * + */ + +void z_call_n (void) +{ + + if (zargs[0] != 0) + call (zargs[0], zargc - 1, zargs + 1, 1); + +}/* z_call_n */ + +/* + * z_call_s, call a subroutine and store its result. + * + * zargs[0] = packed address of subroutine + * zargs[1] = first argument (optional) + * ... + * zargs[7] = seventh argument (optional) + * + */ + +void z_call_s (void) +{ + + if (zargs[0] != 0) + call (zargs[0], zargc - 1, zargs + 1, 0); + else + store (0); + +}/* z_call_s */ + +/* + * z_check_arg_count, branch if subroutine was called with >= n arg's. + * + * zargs[0] = number of arguments + * + */ + +void z_check_arg_count (void) +{ + + if (fp == stack + STACK_SIZE) + branch (zargs[0] == 0); + else + branch (zargs[0] <= (*fp & 0xff)); + +}/* z_check_arg_count */ + +/* + * z_jump, jump unconditionally to the given address. + * + * zargs[0] = PC relative address + * + */ + +void z_jump (void) +{ + long pc; + + GET_PC (pc) + + pc += (short) zargs[0] - 2; + + if (pc >= story_size) + runtime_error ("Jump to illegal address"); + + SET_PC (pc) + +}/* z_jump */ + +/* + * z_nop, no operation. + * + * no zargs used + * + */ + +void z_nop (void) +{ + + /* Do nothing */ + +}/* z_nop */ + +/* + * z_quit, stop game and exit interpreter. + * + * no zargs used + * + */ + +void z_quit (void) +{ + + finished = 9999; + +}/* z_quit */ + +/* + * z_ret, return from a subroutine with the given value. + * + * zargs[0] = value to return + * + */ + +void z_ret (void) +{ + + ret (zargs[0]); + +}/* z_ret */ + +/* + * z_ret_popped, return from a subroutine with a value popped off the stack. + * + * no zargs used + * + */ + +void z_ret_popped (void) +{ + + ret (*sp++); + +}/* z_ret_popped */ + +/* + * z_rfalse, return from a subroutine with false (0). + * + * no zargs used + * + */ + +void z_rfalse (void) +{ + + ret (0); + +}/* z_rfalse */ + +/* + * z_rtrue, return from a subroutine with true (1). + * + * no zargs used + * + */ + +void z_rtrue (void) +{ + + ret (1); + +}/* z_rtrue */ diff --git a/random.c b/random.c new file mode 100644 index 0000000..c2df1d1 --- /dev/null +++ b/random.c @@ -0,0 +1,69 @@ +/* + * random.c + * + * Z-machine random number generator + * + */ + +#include "frotz.h" + +static long A = 1; + +static interval = 0; +static counter = 0; + +/* + * seed_random + * + * Set the seed value for the random number generator. + * + */ + +void seed_random (int value) +{ + + if (value == 0) { /* ask interface for seed value */ + A = os_random_seed (); + interval = 0; + } else if (value < 1000) { /* special seed value */ + counter = 0; + interval = value; + } else { /* standard seed value */ + A = value; + interval = 0; + } + +}/* seed_random */ + +/* + * z_random, store a random number or set the random number seed. + * + * zargs[0] = range (positive) or seed value (negative) + * + */ + +void z_random () +{ + + if ((short) zargs[0] <= 0) { /* set random seed */ + + seed_random (- (short) zargs[0]); + store (0); + + } else { /* generate random number */ + + zword result; + + if (interval != 0) { /* ...in special mode */ + result = counter++; + if (counter == interval) counter = 0; + } else { /* ...in standard mode */ + A = 0x015a4e35L * A + 1; + result = (A >> 16) & 0x7fff; + } + + store ((zword) (result % zargs[0] + 1)); + + } + +}/* z_random */ diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..bba204f --- /dev/null +++ b/readme.txt @@ -0,0 +1,103 @@ + + + +This is the source distribution of DOS Frotz 2.32. Sorry, +documentation is not complete yet, and this small readme +file is all you get at the moment... + + + +The latest changes in the 2.32 interface are: + +-> Text and keys (except for file names) are now presented + using the new "zchar" type. This is a 1-byte value that + contains an ISO Latin-1 character or a special Infocom + character like code $09 (paragraph indentation). See + FROTZ.H for possible character codes. Some Infocom codes + have changed, too, since they collided with ISO Latin-1 + codes. I strongly recommend using the constants defined + in FROTZ.H. + + If you want to support different character sets, look at + + * zchar definition (FROTZ.H), + * z_print_unicode (TEXT.C), + * z_check_unicode (TEXT.C), + * translate_from_zscii (TEXT.C), + * translate_to_zscii (TEXT.C), + * conversion to lower case in z_read (INPUT.C), + * script_char (FILES.C). + + If you can't read accented characters from the keyboard, + make sure z_check_unicode stores 1 for codes from $0a to + $ff. If you can't write accented characters, prints some + suitable ASCII representation instead. + +-> There are some differences between Amiga and DOS picture + files, and an interpreter must take some extra care to + combine Amiga story files with DOS picture files. So far + the Amiga and DOS front-ends contained the necessary + code to make this work. Since Frotz 2.32 this problem is + dealt with in SCREEN.C, and interfaces no longer have to + worry about this. + +-> os_cursor_on and os_cursor_off are no more. Instead, an + additional argument tells os_read_key to make the cursor + visible or not. + + (I believe a game would not want to turn off the cursor + for reading a string.) + +-> os_wait_sample is no longer needed! + + SOUND.C uses the end_of_sound call to solve all problems + with 'The Lurking Horror' more effectively than before. + +-> os_start_sample should now ignore the play-once-or-loop- + forever information in the sound files and rely on its + arguments instead. The necessary information for 'The + Lurking Horror' has been wired into SOUND.C. This should + make it easier to use a more common sound format in the + future. + +-> 'Journey' asked for font #4 (fixed font) on most systems + since it simply expected the interpreter to be unable to + print a proportional font. This unpleasant behaviour is + now overwritten by Frotz. + +-> Many extern declarations in FROTZ.H have been removed. In + particular, this includes + + * end_of_sound, + * restart_header, + * resize_screen, + * completion, + * is_terminator, + * read_yes_or_no, + * read_string. + + It's fine to call these functions from your front-end, + though. Just add their prototypes to your code. + +-> For different reasons, several front-ends felt the need + to interfere with the process of restarting a game. + + There is now an interface function os_restart_game that + gives the front-end a chance to do its business while + the game restarts. The function is called several times + during the process, its sole argument indicating the + current "stage" of the restart: + + * RESTART_BEGIN -- restart has just begun + * RESTART_WPROP_SET -- window properties have been set + * RESTART_END -- restart completed + + os_restart_game is also useful to display the 'Beyond + Zork' title picture that is now available in MG1 format. + + + +Final note: The FONT.DAT file in this distribution must be +appended to the DOS executable -- sorry about this! + + diff --git a/redirect.c b/redirect.c new file mode 100644 index 0000000..fc92b41 --- /dev/null +++ b/redirect.c @@ -0,0 +1,159 @@ +/* + * redirect.c + * + * Output redirection to Z-machine memory + * + */ + +#include "frotz.h" + +#define MAX_NESTING 16 + +extern zword get_max_width (zword); + +static depth = -1; + +static struct { + zword xsize; + zword table; + zword width; + zword total; +} redirect[MAX_NESTING]; + +/* + * memory_open + * + * Begin output redirection to the memory of the Z-machine. + * + */ + +void memory_open (zword table, zword xsize, bool buffering) +{ + + if (++depth < MAX_NESTING) { + + if (!buffering) + xsize = 0xffff; + if (buffering && (short) xsize <= 0) + xsize = get_max_width ((zword) (- (short) xsize)); + + storew (table, 0); + + redirect[depth].table = table; + redirect[depth].width = 0; + redirect[depth].total = 0; + redirect[depth].xsize = xsize; + + ostream_memory = TRUE; + + } else runtime_error ("Nesting stream #3 too deep"); + +}/* memory_open */ + +/* + * memory_new_line + * + * Redirect a newline to the memory of the Z-machine. + * + */ + +void memory_new_line (void) +{ + zword size; + zword addr; + + redirect[depth].total += redirect[depth].width; + redirect[depth].width = 0; + + addr = redirect[depth].table; + + LOW_WORD (addr, size) + addr += 2; + + if (redirect[depth].xsize != 0xffff) { + + redirect[depth].table = addr + size; + size = 0; + + } else storeb ((zword) (addr + (size++)), 13); + + storew (redirect[depth].table, size); + +}/* memory_new_line */ + +/* + * memory_word + * + * Redirect a string of characters to the memory of the Z-machine. + * + */ + +void memory_word (const zchar *s) +{ + zword size; + zword addr; + zchar c; + + if (h_version == V6) { + + int width = os_string_width (s); + + if (redirect[depth].xsize != 0xffff) + + if (redirect[depth].width + width > redirect[depth].xsize) { + + if (*s == ' ' || *s == ZC_INDENT || *s == ZC_GAP) + width = os_string_width (++s); + + memory_new_line (); + + } + + redirect[depth].width += width; + + } + + addr = redirect[depth].table; + + LOW_WORD (addr, size) + addr += 2; + + while ((c = *s++) != 0) + storeb ((zword) (addr + (size++)), translate_to_zscii (c)); + + storew (redirect[depth].table, size); + +}/* memory_word */ + +/* + * memory_close + * + * End of output redirection. + * + */ + +void memory_close (void) +{ + + if (depth >= 0) { + + if (redirect[depth].xsize != 0xffff) + memory_new_line (); + + if (h_version == V6) { + + h_line_width = (redirect[depth].xsize != 0xffff) ? + redirect[depth].total : redirect[depth].width; + + SET_WORD (H_LINE_WIDTH, h_line_width) + + } + + if (depth == 0) + ostream_memory = FALSE; + + depth--; + + } + +}/* memory_close */ diff --git a/screen.c b/screen.c new file mode 100644 index 0000000..851451e --- /dev/null +++ b/screen.c @@ -0,0 +1,1725 @@ +/* + * screen.c + * + * Generic screen manipulation + * + */ + +#include "frotz.h" + +extern void set_header_extension (int, zword); + +extern int direct_call (zword); + +static struct { + enum story story_id; + int pic; + int pic1; + int pic2; +} mapper[] = { + { ZORK_ZERO, 5, 497, 498 }, + { ZORK_ZERO, 6, 501, 502 }, + { ZORK_ZERO, 7, 499, 500 }, + { ZORK_ZERO, 8, 503, 504 }, + { ARTHUR, 54, 170, 171 }, + { SHOGUN, 50, 61, 62 }, + { UNKNOWN, 0, 0, 0 } +}; + +static font_height = 1; +static font_width = 1; + +static bool input_redraw = FALSE; +static bool more_prompts = TRUE; +static bool discarding = FALSE; +static bool cursor = TRUE; + +static input_window = 0; + +static struct { + zword y_pos; + zword x_pos; + zword y_size; + zword x_size; + zword y_cursor; + zword x_cursor; + zword left; + zword right; + zword nl_routine; + zword nl_countdown; + zword style; + zword colour; + zword font; + zword font_size; + zword attribute; + zword line_count; +} wp[8], *cwp; + +/* + * winarg0 + * + * Return the window number in zargs[0]. In V6 only, -3 refers to the + * current window. + * + */ + +static zword winarg0 (void) +{ + + if (h_version == V6 && (short) zargs[0] == -3) + return cwin; + + if (zargs[0] >= ((h_version == V6) ? 8 : 2)) + runtime_error ("Illegal window"); + + return zargs[0]; + +}/* winarg0 */ + +/* + * winarg2 + * + * Return the (optional) window number in zargs[2]. -3 refers to the + * current window. This optional window number was only used by some + * V6 opcodes: set_cursor, set_margins, set_colour. + * + */ + +static zword winarg2 (void) +{ + + if (zargc < 3 || (short) zargs[2] == -3) + return cwin; + + if (zargs[2] >= 8) + runtime_error ("Illegal window"); + + return zargs[2]; + +}/* winarg2 */ + +/* + * update_cursor + * + * Move the hardware cursor to make it match the window properties. + * + */ + +static void update_cursor (void) +{ + + os_set_cursor ( + cwp->y_pos + cwp->y_cursor - 1, + cwp->x_pos + cwp->x_cursor - 1); + +}/* update_cursor */ + +/* + * reset_cursor + * + * Reset the cursor of a given window to its initial position. + * + */ + +static void reset_cursor (zword win) +{ + int lines = 0; + + if (h_version <= V4 && win == 0) + lines = wp[0].y_size / hi (wp[0].font_size) - 1; + + wp[win].y_cursor = hi (wp[0].font_size) * lines + 1; + wp[win].x_cursor = wp[win].left + 1; + + if (win == cwin) + update_cursor (); + +}/* reset_cursor */ + +/* + * set_more_prompts + * + * Turn more prompts on/off. + * + */ + +void set_more_prompts (bool flag) +{ + + if (flag && !more_prompts) + cwp->line_count = 0; + + more_prompts = flag; + +}/* set_more_prompts */ + +/* + * units_left + * + * Return the #screen units from the cursor to the end of the line. + * + */ + +static int units_left (void) +{ + + return cwp->x_size - cwp->right - cwp->x_cursor + 1; + +}/* units_left */ + +/* + * get_max_width + * + * Return maximum width of a line in the given window. This is used in + * connection with the extended output stream #3 call in V6. + * + */ + +zword get_max_width (zword win) +{ + + if (h_version == V6) { + + if (win >= 8) + runtime_error ("Illegal window"); + + return wp[win].x_size - wp[win].left - wp[win].right; + + } else return 0xffff; + +}/* get_max_width */ + +/* + * countdown + * + * Decrement the newline counter. Call the newline interrupt when the + * counter hits zero. This is a helper function for screen_new_line. + * + */ + +static void countdown (void) +{ + + if (cwp->nl_countdown != 0) + if (--cwp->nl_countdown == 0) + direct_call (cwp->nl_routine); + +}/* countdown */ + +/* + * screen_new_line + * + * Print a newline to the screen. + * + */ + +void screen_new_line (void) +{ + + if (discarding) return; + + /* Handle newline interrupts at the start (for most cases) */ + + if (h_interpreter_number != INTERP_MSDOS || story_id != ZORK_ZERO || h_release != 393) + countdown (); + + /* Check whether the last input line gets destroyed */ + + if (input_window == cwin) + input_redraw = TRUE; + + /* If the cursor has not reached the bottom line, then move it to + the next line; otherwise scroll the window or reset the cursor + to the top left. */ + + cwp->x_cursor = cwp->left + 1; + + if (cwp->y_cursor + 2 * font_height - 1 > cwp->y_size) + + if (enable_scrolling) { + + zword y = cwp->y_pos; + zword x = cwp->x_pos; + + os_scroll_area (y, + x, + y + cwp->y_size - 1, + x + cwp->x_size - 1, + font_height); + + } else cwp->y_cursor = 1; + + else cwp->y_cursor += font_height; + + update_cursor (); + + /* See if we need to print a more prompt (unless the game has set + the line counter to -999 in order to suppress more prompts). */ + + if (enable_scrolling && (short) cwp->line_count != -999) { + + zword above = (cwp->y_cursor - 1) / font_height; + zword below = (cwp->y_size - cwp->y_cursor + 1) / font_height; + + cwp->line_count++; + + if ((short) cwp->line_count >= (short) above + below - 1) { + + if (more_prompts) + os_more_prompt (); + + cwp->line_count = option_context_lines; + + } + + } + + /* Handle newline interrupts at the end for Zork Zero under DOS */ + + if (h_interpreter_number == INTERP_MSDOS && story_id == ZORK_ZERO && h_release == 393) + countdown (); + +}/* screen_new_line */ + +/* + * screen_char + * + * Display a single character on the screen. + * + */ + +void screen_char (zchar c) +{ + int width; + + if (discarding) return; + + if (c == ZC_INDENT && cwp->x_cursor != cwp->left + 1) + c = ' '; + + if (units_left () < (width = os_char_width (c))) { + + if (!enable_wrapping) + { cwp->x_cursor = cwp->x_size - cwp->right; return; } + + screen_new_line (); + + } + + os_display_char (c); cwp->x_cursor += width; + +}/* screen_char */ + +/* + * screen_word + * + * Display a string of characters on the screen. If the word doesn't fit + * then use wrapping or clipping depending on the current setting of the + * enable_wrapping flag. + * + */ + +void screen_word (const zchar *s) +{ + int width; + + if (discarding) return; + + if (*s == ZC_INDENT && cwp->x_cursor != cwp->left + 1) + screen_char (*s++); + + if (units_left () < (width = os_string_width (s))) { + + if (!enable_wrapping) { + + zchar c; + + while ((c = *s++) != 0) + + if (c == ZC_NEW_FONT || c == ZC_NEW_STYLE) { + + int arg = (int) *s++; + + if (c == ZC_NEW_FONT) + os_set_font (arg); + if (c == ZC_NEW_STYLE) + os_set_text_style (arg); + + } else screen_char (c); + + return; + + } + + if (*s == ' ' || *s == ZC_INDENT || *s == ZC_GAP) + width = os_string_width (++s); + +#ifdef AMIGA + if (cwin == 0) Justifiable (); +#endif + + screen_new_line (); + + } + + os_display_string (s); cwp->x_cursor += width; + +}/* screen_word */ + +/* + * screen_write_input + * + * Display an input line on the screen. This is required during playback. + * + */ + +void screen_write_input (const zchar *buf, zchar key) +{ + int width; + + if (units_left () < (width = os_string_width (buf))) + screen_new_line (); + + os_display_string (buf); cwp->x_cursor += width; + + if (key == ZC_RETURN) + screen_new_line (); + +}/* screen_write_input */ + +/* + * screen_erase_input + * + * Remove an input line that has already been printed from the screen + * as if it was deleted by the player. This could be necessary during + * playback. + * + */ + +void screen_erase_input (const zchar *buf) +{ + + if (buf[0] != 0) { + + int width = os_string_width (buf); + + zword y; + zword x; + + cwp->x_cursor -= width; + + y = cwp->y_pos + cwp->y_cursor - 1; + x = cwp->x_pos + cwp->x_cursor - 1; + + os_erase_area (y, x, y + font_height - 1, x + width - 1); + os_set_cursor (y, x); + + } + +}/* screen_erase_input */ + +/* + * console_read_input + * + * Read an input line from the keyboard and return the terminating key. + * + */ + +zchar console_read_input (int max, zchar *buf, zword timeout, bool continued) +{ + zchar key; + int i; + + /* Make sure there is some space for input */ + + if (cwin == 0 && units_left () + os_string_width (buf) < 10 * font_width) + screen_new_line (); + + /* Make sure the input line is visible */ + + if (continued && input_redraw) + screen_write_input (buf, -1); + + input_window = cwin; + input_redraw = FALSE; + + /* Get input line from IO interface */ + + cwp->x_cursor -= os_string_width (buf); + key = os_read_line (max, buf, timeout, units_left (), continued); + cwp->x_cursor += os_string_width (buf); + + if (key != ZC_TIME_OUT) + for (i = 0; i < 8; i++) + wp[i].line_count = 0; + + /* Add a newline if the input was terminated normally */ + + if (key == ZC_RETURN) + screen_new_line (); + + return key; + +}/* console_read_input */ + +/* + * console_read_key + * + * Read a single keystroke and return it. + * + */ + +zchar console_read_key (zword timeout) +{ + zchar key; + int i; + + key = os_read_key (timeout, cursor); + + if (key != ZC_TIME_OUT) + for (i = 0; i < 8; i++) + wp[i].line_count = 0; + + return key; + +}/* console_read_key */ + +/* + * update_attributes + * + * Set the three enable_*** variables to make them match the attributes + * of the current window. + * + */ + +static void update_attributes (void) +{ + zword attr = cwp->attribute; + + enable_wrapping = attr & 1; + enable_scrolling = attr & 2; + enable_scripting = attr & 4; + enable_buffering = attr & 8; + + /* Some story files forget to select wrapping for printing hints */ + + if (story_id == ZORK_ZERO && h_release == 366) + if (cwin == 0) + enable_wrapping = TRUE; + if (story_id == SHOGUN && h_release <= 295) + if (cwin == 0) + enable_wrapping = TRUE; + +}/* update_attributes */ + +/* + * refresh_text_style + * + * Set the right text style. This can be necessary when the fixed font + * flag is changed, or when a new window is selected, or when the game + * uses the set_text_style opcode. + * + */ + +void refresh_text_style (void) +{ + zword style; + + if (h_version != V6) { + + style = wp[0].style; + + if (cwin != 0 || h_flags & FIXED_FONT_FLAG) + style |= FIXED_WIDTH_STYLE; + + } else style = cwp->style; + + if (!ostream_memory && ostream_screen && enable_buffering) { + + print_char (ZC_NEW_STYLE); + print_char (style); + + } else os_set_text_style (style); + +}/* refresh_text_style */ + +/* + * set_window + * + * Set the current window. In V6 every window has its own set of window + * properties such as colours, text style, cursor position and size. + * + */ + +static void set_window (zword win) +{ + + flush_buffer (); + + cwin = win; cwp = wp + win; + + update_attributes (); + + if (h_version == V6) { + + os_set_colour (lo (cwp->colour), hi (cwp->colour)); + + if (os_font_data (cwp->font, &font_height, &font_width)) + os_set_font (cwp->font); + + os_set_text_style (cwp->style); + + } else refresh_text_style (); + + if (h_version != V6 && win != 0) { + wp[win].y_cursor = 1; + wp[win].x_cursor = 1; + } + + update_cursor (); + +}/* set_window */ + +/* + * erase_window + * + * Erase a window to background colour. + * + */ + +static void erase_window (zword win) +{ + zword y = wp[win].y_pos; + zword x = wp[win].x_pos; + + if (h_version == V6 && win != cwin && h_interpreter_number != INTERP_AMIGA) + os_set_colour (lo (wp[win].colour), hi (wp[win].colour)); + + os_erase_area (y, + x, + y + wp[win].y_size - 1, + x + wp[win].x_size - 1); + + if (h_version == V6 && win != cwin && h_interpreter_number != INTERP_AMIGA) + os_set_colour (lo (cwp->colour), hi (cwp->colour)); + + reset_cursor (win); + + wp[win].line_count = 0; + +}/* erase_window */ + +/* + * split_window + * + * Divide the screen into upper (1) and lower (0) windows. In V3 the upper + * window appears below the status line. + * + */ + +void split_window (zword height) +{ + zword stat_height = 0; + + flush_buffer (); + + /* Calculate height of status line and upper window */ + + if (h_version != V6) + height *= hi (wp[1].font_size); + + if (h_version <= V3) + stat_height = hi (wp[7].font_size); + + /* Cursor of upper window mustn't be swallowed by the lower window */ + + wp[1].y_cursor += wp[1].y_pos - 1 - stat_height; + + wp[1].y_pos = 1 + stat_height; + wp[1].y_size = height; + + if ((short) wp[1].y_cursor > (short) wp[1].y_size) + reset_cursor (1); + + /* Cursor of lower window mustn't be swallowed by the upper window */ + + wp[0].y_cursor += wp[0].y_pos - 1 - stat_height - height; + + wp[0].y_pos = 1 + stat_height + height; + wp[0].y_size = h_screen_height - stat_height - height; + + if ((short) wp[0].y_cursor < 1) + reset_cursor (0); + + /* Erase the upper window in V3 only */ + + if (h_version == V3 && height != 0) + erase_window (1); + +}/* split_window */ + +/* + * erase_screen + * + * Erase the entire screen to background colour. + * + */ + +static void erase_screen (zword win) +{ + int i; + + os_erase_area (1, 1, h_screen_height, h_screen_width); + + if ((short) win == -1) { + split_window (0); + set_window (0); + reset_cursor (0); + } + + for (i = 0; i < 8; i++) + wp[i].line_count = 0; + +}/* erase_screen */ + +#ifdef AMIGA + +/* + * resize_screen + * + * Try to adapt the window properties to a new screen size. + * + */ + +void resize_screen (void) +{ + + if (h_version != V6) { + + wp[0].x_size = h_screen_width; + wp[1].x_size = h_screen_width; + wp[7].x_size = h_screen_width; + + wp[0].y_size = h_screen_height - wp[1].y_size - wp[7].y_size; + + } + +}/* resize_screen */ + +#endif + +/* + * restart_screen + * + * Prepare the screen for a new game. + * + */ + +void restart_screen (void) +{ + + /* Use default settings */ + + os_set_colour (h_default_foreground, h_default_background); + + if (os_font_data (TEXT_FONT, &font_height, &font_width)) + os_set_font (TEXT_FONT); + + os_set_text_style (0); + + cursor = TRUE; + + /* Initialise window properties */ + + mwin = 1; + + for (cwp = wp; cwp < wp + 8; cwp++) { + cwp->y_pos = 1; + cwp->x_pos = 1; + cwp->y_size = 0; + cwp->x_size = 0; + cwp->y_cursor = 1; + cwp->x_cursor = 1; + cwp->left = 0; + cwp->right = 0; + cwp->nl_routine = 0; + cwp->nl_countdown = 0; + cwp->style = 0; + cwp->colour = (h_default_background << 8) | h_default_foreground; + cwp->font = TEXT_FONT; + cwp->font_size = (font_height << 8) | font_width; + cwp->attribute = 8; + } + + /* Prepare lower/upper windows and status line */ + + wp[0].attribute = 15; + + wp[0].left = option_left_margin; + wp[0].right = option_right_margin; + + wp[0].x_size = h_screen_width; + wp[1].x_size = h_screen_width; + + if (h_version <= V3) + wp[7].x_size = h_screen_width; + + os_restart_game (RESTART_WPROP_SET); + + /* Clear the screen, unsplit it and select window 0 */ + + erase_screen ((zword) (-1)); + +}/* restart_screen */ + +/* + * validate_click + * + * Return false if the last mouse click occured outside the current + * mouse window; otherwise write the mouse arrow coordinates to the + * memory of the header extension table and return true. + * + */ + +bool validate_click (void) +{ + + if (mwin >= 0) { + + if (mouse_y < wp[mwin].y_pos || mouse_y >= wp[mwin].y_pos + wp[mwin].y_size) + return FALSE; + if (mouse_x < wp[mwin].x_pos || mouse_x >= wp[mwin].x_pos + wp[mwin].x_size) + return FALSE; + + hx_mouse_y = mouse_y - wp[mwin].y_pos + 1; + hx_mouse_x = mouse_x - wp[mwin].x_pos + 1; + + } else { + + if (mouse_y < 1 || mouse_y > h_screen_height) + return FALSE; + if (mouse_x < 1 || mouse_x > h_screen_width) + return FALSE; + + hx_mouse_y = mouse_y; + hx_mouse_x = mouse_x; + + } + + if (h_version != V6) { + hx_mouse_y = (hx_mouse_y - 1) / h_font_height + 1; + hx_mouse_x = (hx_mouse_x - 1) / h_font_width + 1; + } + + set_header_extension (HX_MOUSE_Y, hx_mouse_y); + set_header_extension (HX_MOUSE_X, hx_mouse_x); + + return TRUE; + +}/* validate_click */ + +/* + * screen_mssg_on + * + * Start printing a so-called debugging message. The contents of the + * message are passed to the message stream, a Frotz specific output + * stream with maximum priority. + * + */ + +void screen_mssg_on (void) +{ + + if (cwin == 0) { /* messages in window 0 only */ + + os_set_text_style (0); + + if (cwp->x_cursor != cwp->left + 1) + screen_new_line (); + + screen_char (ZC_INDENT); + + } else discarding = TRUE; /* discard messages in other windows */ + +}/* screen_mssg_on */ + +/* + * screen_mssg_off + * + * Stop printing a "debugging" message. + * + */ + +void screen_mssg_off (void) +{ + + if (cwin == 0) { /* messages in window 0 only */ + + screen_new_line (); + + refresh_text_style (); + + } else discarding = FALSE; /* message has been discarded */ + +}/* screen_mssg_off */ + +/* + * z_buffer_mode, turn text buffering on/off. + * + * zargs[0] = new text buffering flag (0 or 1) + * + */ + +void z_buffer_mode (void) +{ + + /* Infocom's V6 games rarely use the buffer_mode opcode. If they do + then only to print text immediately, without any delay. This was + used to give the player some sign of life while the game was + spending much time on parsing a complicated input line. (To turn + off word wrapping, V6 games use the window_style opcode instead.) + Today we can afford to ignore buffer_mode in V6. */ + + if (h_version != V6) { + + flush_buffer (); + + wp[0].attribute &= ~8; + + if (zargs[0] != 0) + wp[0].attribute |= 8; + + update_attributes (); + + } + +}/* z_buffer_mode */ + +/* + * z_draw_picture, draw a picture. + * + * zargs[0] = number of picture to draw + * zargs[1] = y-coordinate of top left corner + * zargs[2] = x-coordinate of top left corner + * + */ + +void z_draw_picture (void) +{ + zword pic = zargs[0]; + + zword y = zargs[1]; + zword x = zargs[2]; + + int i; + + flush_buffer (); + + if (y == 0) /* use cursor line if y-coordinate is 0 */ + y = cwp->y_cursor; + if (x == 0) /* use cursor column if x-coordinate is 0 */ + x = cwp->x_cursor; + + y += cwp->y_pos - 1; + x += cwp->x_pos - 1; + + /* The following is necessary to make Amiga and Macintosh story + files work with MCGA graphics files. Some screen-filling + pictures of the original Amiga release like the borders of + Zork Zero were split into several MCGA pictures (left, right + and top borders). We pretend this has not happened. */ + + for (i = 0; mapper[i].story_id != UNKNOWN; i++) + + if (story_id == mapper[i].story_id && pic == mapper[i].pic) { + + int height1, width1; + int height2, width2; + + int delta = 0; + + os_picture_data (pic, &height1, &width1); + os_picture_data (mapper[i].pic2, &height2, &width2); + + if (story_id == ARTHUR && pic == 54) + delta = h_screen_width / 160; + + os_draw_picture (mapper[i].pic1, y + height1, x + delta); + os_draw_picture (mapper[i].pic2, y + height1, x + width1 - width2 - delta); + + } + + os_draw_picture (pic, y, x); + + if (story_id == SHOGUN) + + if (pic == 3) { + + int height, width; + + os_picture_data (59, &height, &width); + os_draw_picture (59, y, h_screen_width - width + 1); + + } + +}/* z_draw_picture */ + +/* + * z_erase_line, erase the line starting at the cursor position. + * + * zargs[0] = 1 + #units to erase (1 clears to the end of the line) + * + */ + +void z_erase_line (void) +{ + zword pixels = zargs[0]; + zword y, x; + + flush_buffer (); + + /* Clipping at the right margin of the current window */ + + if (--pixels == 0 || pixels > units_left ()) + pixels = units_left (); + + /* Erase from cursor position */ + + y = cwp->y_pos + cwp->y_cursor - 1; + x = cwp->x_pos + cwp->x_cursor - 1; + + os_erase_area (y, x, y + font_height - 1, x + pixels - 1); + +}/* z_erase_line */ + +/* + * z_erase_picture, erase a picture with background colour. + * + * zargs[0] = number of picture to erase + * zargs[1] = y-coordinate of top left corner (optional) + * zargs[2] = x-coordinate of top left corner (optional) + * + */ + +void z_erase_picture (void) +{ + int height, width; + + zword y = zargs[1]; + zword x = zargs[2]; + + flush_buffer (); + + if (y == 0) /* use cursor line if y-coordinate is 0 */ + y = cwp->y_cursor; + if (x == 0) /* use cursor column if x-coordinate is 0 */ + x = cwp->x_cursor; + + os_picture_data (zargs[0], &height, &width); + + y += cwp->y_pos - 1; + x += cwp->x_pos - 1; + + os_erase_area (y, x, y + height - 1, x + width - 1); + +}/* z_erase_picture */ + +/* + * z_erase_window, erase a window or the screen to background colour. + * + * zargs[0] = window (-3 current, -2 screen, -1 screen & unsplit) + * + */ + +void z_erase_window (void) +{ + + flush_buffer (); + + if ((short) zargs[0] == -1 || (short) zargs[0] == -2) + erase_screen (zargs[0]); + else + erase_window (winarg0 ()); + +}/* z_erase_window */ + +/* + * z_get_cursor, write the cursor coordinates into a table. + * + * zargs[0] = address to write information to + * + */ + +void z_get_cursor (void) +{ + zword y, x; + + flush_buffer (); + + y = cwp->y_cursor; + x = cwp->x_cursor; + + if (h_version != V6) { /* convert to grid positions */ + y = (y - 1) / h_font_height + 1; + x = (x - 1) / h_font_width + 1; + } + + storew ((zword) (zargs[0] + 0), y); + storew ((zword) (zargs[0] + 2), x); + +}/* z_get_cursor */ + +/* + * z_get_wind_prop, store the value of a window property. + * + * zargs[0] = window (-3 is the current one) + * zargs[1] = number of window property to be stored + * + */ + +void z_get_wind_prop (void) +{ + + flush_buffer (); + + if (zargs[1] >= 16) + runtime_error ("Illegal window property"); + + store (((zword *) (wp + winarg0 ())) [zargs[1]]); + +}/* z_get_wind_prop */ + +/* + * z_mouse_window, select a window as mouse window. + * + * zargs[0] = window number (-3 is the current) or -1 for the screen + * + */ + +void z_mouse_window (void) +{ + + mwin = ((short) zargs[0] == -1) ? -1 : winarg0 (); + +}/* z_mouse_window */ + +/* + * z_move_window, place a window on the screen. + * + * zargs[0] = window (-3 is the current one) + * zargs[1] = y-coordinate + * zargs[2] = x-coordinate + * + */ + +void z_move_window (void) +{ + zword win = winarg0 (); + + flush_buffer (); + + wp[win].y_pos = zargs[1]; + wp[win].x_pos = zargs[2]; + + if (win == cwin) + update_cursor (); + +}/* z_move_window */ + +/* + * z_picture_data, get information on a picture or the graphics file. + * + * zargs[0] = number of picture or 0 for the graphics file + * zargs[1] = address to write information to + * + */ + +void z_picture_data (void) +{ + zword pic = zargs[0]; + zword table = zargs[1]; + + int height, width; + int i; + + bool avail = os_picture_data (pic, &height, &width); + + for (i = 0; mapper[i].story_id != UNKNOWN; i++) + + if (story_id == mapper[i].story_id) + + if (pic == mapper[i].pic) { + + int height2, width2; + + avail &= os_picture_data (mapper[i].pic1, &height2, &width2); + avail &= os_picture_data (mapper[i].pic2, &height2, &width2); + + height += height2; + + } else if (pic == mapper[i].pic1 || pic == mapper[i].pic2) + + avail = FALSE; + + storew ((zword) (table + 0), (zword) (height)); + storew ((zword) (table + 2), (zword) (width)); + + branch (avail); + +}/* z_picture_data */ + +/* + * z_picture_table, prepare a group of pictures for faster display. + * + * zargs[0] = address of table holding the picture numbers + * + */ + +void z_picture_table (void) +{ + + /* This opcode is used by Shogun and Zork Zero when the player + encounters built-in games such as Peggleboz. Nowadays it is + not very helpful to hold the picture data in memory because + even a small disk cache avoids re-loading of data. */ + +}/* z_picture_table */ + +/* + * z_print_table, print ASCII text in a rectangular area. + * + * zargs[0] = address of text to be printed + * zargs[1] = width of rectangular area + * zargs[2] = height of rectangular area (optional) + * zargs[3] = number of char's to skip between lines (optional) + * + */ + +void z_print_table (void) +{ + zword addr = zargs[0]; + zword x; + int i, j; + + flush_buffer (); + + /* Supply default arguments */ + + if (zargc < 3) + zargs[2] = 1; + if (zargc < 4) + zargs[3] = 0; + + /* Write text in width x height rectangle */ + + x = cwp->x_cursor; + + for (i = 0; i < zargs[2]; i++) { + + if (i != 0) { + + flush_buffer (); + + cwp->y_cursor += font_height; + cwp->x_cursor = x; + + update_cursor (); + + } + + for (j = 0; j < zargs[1]; j++) { + + zbyte c; + + LOW_BYTE (addr, c) + addr++; + + print_char (c); + + } + + addr += zargs[3]; + + } + +}/* z_print_table */ + +/* + * z_put_wind_prop, set the value of a window property. + * + * zargs[0] = window (-3 is the current one) + * zargs[1] = number of window property to set + * zargs[2] = value to set window property to + * + */ + +void z_put_wind_prop (void) +{ + + flush_buffer (); + + if (zargs[1] >= 16) + runtime_error ("Illegal window property"); + + ((zword *) (wp + winarg0 ())) [zargs[1]] = zargs[2]; + +}/* z_put_wind_prop */ + +/* + * z_scroll_window, scroll a window up or down. + * + * zargs[0] = window (-3 is the current one) + * zargs[1] = #screen units to scroll up (positive) or down (negative) + * + */ + +void z_scroll_window (void) +{ + zword win = winarg0 (); + zword y, x; + + flush_buffer (); + + /* Use the correct set of colours when scrolling the window */ + + if (win != cwin && h_interpreter_number != INTERP_AMIGA) + os_set_colour (lo (wp[win].colour), hi (wp[win].colour)); + + y = wp[win].y_pos; + x = wp[win].x_pos; + + os_scroll_area (y, + x, + y + wp[win].y_size - 1, + x + wp[win].x_size - 1, + (short) zargs[1]); + + if (win != cwin && h_interpreter_number != INTERP_AMIGA) + os_set_colour (lo (cwp->colour), hi (cwp->colour)); + +}/* z_scroll_window */ + +/* + * z_set_colour, set the foreground and background colours. + * + * zargs[0] = foreground colour + * zargs[1] = background colour + * zargs[2] = window (-3 is the current one, optional) + * + */ + +void z_set_colour (void) +{ + zword win = (h_version == V6) ? winarg2 () : 0; + + zword fg = zargs[0]; + zword bg = zargs[1]; + + flush_buffer (); + + if ((short) fg == -1) /* colour -1 is the colour at the cursor */ + fg = os_peek_colour (); + if ((short) bg == -1) + bg = os_peek_colour (); + + if (fg == 0) /* colour 0 means keep current colour */ + fg = lo (wp[win].colour); + if (bg == 0) + bg = hi (wp[win].colour); + + if (fg == 1) /* colour 1 is the system default colour */ + fg = h_default_foreground; + if (bg == 1) + bg = h_default_background; + + if (h_version == V6 && h_interpreter_number == INTERP_AMIGA) + + /* Changing colours of window 0 affects the entire screen */ + + if (win == 0) { + + int i; + + for (i = 1; i < 8; i++) { + + zword bg2 = hi (wp[i].colour); + zword fg2 = lo (wp[i].colour); + + if (bg2 < 16) + bg2 = (bg2 == lo (wp[0].colour)) ? fg : bg; + if (fg2 < 16) + fg2 = (fg2 == lo (wp[0].colour)) ? fg : bg; + + wp[i].colour = (bg2 << 8) | fg2; + + } + + } + + wp[win].colour = (bg << 8) | fg; + + if (win == cwin || h_version != V6) + os_set_colour (fg, bg); + +}/* z_set_colour */ + +/* + * z_set_font, set the font for text output and store the previous font. + * + * zargs[0] = number of font or 0 to keep current font + * + */ + +void z_set_font (void) +{ + zword win = (h_version == V6) ? cwin : 0; + zword font = zargs[0]; + + if (font != 0) { + + if (story_id == JOURNEY && font == 4) /* Journey uses fixed fonts */ + font = 1; /* for most interpreter #'s */ + + if (os_font_data (font, &font_height, &font_width)) { + + store (wp[win].font); + + wp[win].font = font; + wp[win].font_size = (font_height << 8) | font_width; + + if (!ostream_memory && ostream_screen && enable_buffering) { + + print_char (ZC_NEW_FONT); + print_char (font); + + } else os_set_font (font); + + } else store (0); + + } else store (wp[win].font); + +}/* z_set_font */ + +/* + * z_set_cursor, set the cursor position or turn the cursor on/off. + * + * zargs[0] = y-coordinate or -2/-1 for cursor on/off + * zargs[1] = x-coordinate + * zargs[2] = window (-3 is the current one, optional) + * + */ + +void z_set_cursor (void) +{ + zword win = (h_version == V6) ? winarg2 () : 1; + + zword y = zargs[0]; + zword x = zargs[1]; + + flush_buffer (); + + /* Supply default arguments */ + + if (zargc < 3) + zargs[2] = -3; + + /* Handle cursor on/off */ + + if ((short) y < 0) { + + if ((short) y == -2) + cursor = TRUE; + if ((short) y == -1) + cursor = FALSE; + + return; + + } + + /* Convert grid positions to screen units if this is not V6 */ + + if (h_version != V6) { + + if (cwin == 0) + return; + + y = (y - 1) * h_font_height + 1; + x = (x - 1) * h_font_width + 1; + + } + + /* Protect the margins */ + + if (x <= wp[win].left || x > wp[win].x_size - wp[win].right) + x = wp[win].left + 1; + + /* Move the cursor */ + + wp[win].y_cursor = y; + wp[win].x_cursor = x; + + if (win == cwin) + update_cursor (); + +}/* z_set_cursor */ + +/* + * z_set_margins, set the left and right margins of a window. + * + * zargs[0] = left margin in pixels + * zargs[1] = right margin in pixels + * zargs[2] = window (-3 is the current one, optional) + * + */ + +void z_set_margins (void) +{ + zword win = winarg2 (); + + flush_buffer (); + + wp[win].left = zargs[0]; + wp[win].right = zargs[1]; + + /* Protect the margins */ + + if (wp[win].x_cursor <= zargs[0] || wp[win].x_cursor > wp[win].x_size - zargs[1]) { + + wp[win].x_cursor = zargs[0] + 1; + + if (win == cwin) + update_cursor (); + + } + +}/* z_set_margins */ + +/* + * z_set_text_style, set the style for text output. + * + * zargs[0] = style flags to set or 0 to reset text style + * + */ + +void z_set_text_style (void) +{ + zword win = (h_version == V6) ? cwin : 0; + zword style = zargs[0]; + + wp[win].style |= style; + + if (style == 0) + wp[win].style = 0; + + refresh_text_style (); + +}/* z_set_text_style */ + +/* + * z_set_window, select the current window. + * + * zargs[0] = window to be selected (-3 is the current one) + * + */ + +void z_set_window (void) +{ + + set_window (winarg0 ()); + +}/* z_set_window */ + +/* + * pad_status_line + * + * Pad the status line with spaces up to the given position. + * + */ + +static void pad_status_line (int column) +{ + int spaces; + + flush_buffer (); + + spaces = units_left () / os_char_width (' ') - column; + + while (spaces--) + screen_char (' '); + +}/* pad_status_line */ + +/* + * z_show_status, display the status line for V1 to V3 games. + * + * no zargs used + * + */ + +void z_show_status (void) +{ + zword global0; + zword global1; + zword global2; + zword addr; + + bool brief = FALSE; + + /* One V5 game (Wishbringer Solid Gold) contains this opcode by + accident, so just return if the version number does not fit */ + + if (h_version >= V4) + return; + + /* Read all relevant global variables from the memory of the + Z-machine into local variables */ + + addr = h_globals; + LOW_WORD (addr, global0) + addr += 2; + LOW_WORD (addr, global1) + addr += 2; + LOW_WORD (addr, global2) + + /* Frotz uses window 7 for the status line. Don't forget to select + reverse and fixed width text style */ + + set_window (7); + + print_char (ZC_NEW_STYLE); + print_char (REVERSE_STYLE | FIXED_WIDTH_STYLE); + + /* If the screen width is below 55 characters then we have to use + the brief status line format */ + + if (h_screen_cols < 55) + brief = TRUE; + + /* Print the object description for the global variable 0 */ + + print_char (' '); + print_object (global0); + + /* A header flag tells us whether we have to display the current + time or the score/moves information */ + + if (h_config & CONFIG_TIME) { /* print hours and minutes */ + + zword hours = (global1 + 11) % 12 + 1; + + pad_status_line (brief ? 15 : 20); + + print_string ("Time: "); + + if (hours < 10) + print_char (' '); + print_num (hours); + + print_char (':'); + + if (global2 < 10) + print_char ('0'); + print_num (global2); + + print_char (' '); + + print_char ((global1 >= 12) ? 'p' : 'a'); + print_char ('m'); + + } else { /* print score and moves */ + + pad_status_line (brief ? 15 : 30); + + print_string (brief ? "S: " : "Score: "); + print_num (global1); + + pad_status_line (brief ? 8 : 14); + + print_string (brief ? "M: " : "Moves: "); + print_num (global2); + + } + + /* Pad the end of the status line with spaces */ + + pad_status_line (0); + + /* Return to the lower window */ + + set_window (0); + +}/* z_show_status */ + +/* + * z_split_window, split the screen into an upper (1) and lower (0) window. + * + * zargs[0] = height of upper window in screen units (V6) or #lines + * + */ + +void z_split_window (void) +{ + + split_window (zargs[0]); + +}/* z_split_window */ + +/* + * z_window_size, change the width and height of a window. + * + * zargs[0] = window (-3 is the current one) + * zargs[1] = new height in screen units + * zargs[2] = new width in screen units + * + */ + +void z_window_size (void) +{ + zword win = winarg0 (); + + flush_buffer (); + + wp[win].y_size = zargs[1]; + wp[win].x_size = zargs[2]; + + /* Keep the cursor within the window */ + + if (wp[win].y_cursor > zargs[1] || wp[win].x_cursor > zargs[2]) + reset_cursor (win); + +}/* z_window_size */ + +/* + * z_window_style, set / clear / toggle window attributes. + * + * zargs[0] = window (-3 is the current one) + * zargs[1] = window attribute flags + * zargs[2] = operation to perform (optional, defaults to 0) + * + */ + +void z_window_style (void) +{ + zword win = winarg0 (); + zword flags = zargs[1]; + + flush_buffer (); + + /* Supply default arguments */ + + if (zargc < 3) + zargs[2] = 0; + + /* Set window style */ + + switch (zargs[2]) { + case 0: wp[win].attribute = flags; break; + case 1: wp[win].attribute |= flags; break; + case 2: wp[win].attribute &= ~flags; break; + case 3: wp[win].attribute ^= flags; break; + } + + if (cwin == win) + update_attributes (); + +}/* z_window_style */ diff --git a/sound.c b/sound.c new file mode 100644 index 0000000..bfaf156 --- /dev/null +++ b/sound.c @@ -0,0 +1,161 @@ +/* + * sound.c + * + * Sound effect function + * + */ + +#include "frotz.h" + +#define EFFECT_PREPARE 1 +#define EFFECT_PLAY 2 +#define EFFECT_STOP 3 +#define EFFECT_FINISH_WITH 4 + +extern int direct_call (zword); + +static zword routine = 0; + +static next_sample = 0; +static next_volume = 0; + +static bool locked = FALSE; +static bool playing = FALSE; + +/* + * start_sample + * + * Call the IO interface to play a sample. + * + */ + +static void start_sample (int number, int volume, int repeats, zword eos) +{ + + static zbyte lh_repeats[] = { + 0x00, 0x00, 0x00, 0x01, 0xff, + 0x00, 0x01, 0x01, 0x01, 0x01, + 0xff, 0x01, 0x01, 0xff, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff + }; + + if (story_id == LURKING_HORROR) + repeats = lh_repeats[number]; + + os_start_sample (number, volume, repeats); + + routine = eos; + playing = TRUE; + +}/* start_sample */ + +/* + * start_next_sample + * + * Play a sample that has been delayed until the previous sound effect has + * finished. This is necessary for two samples in The Lurking Horror that + * immediately follow other samples. + * + */ + +static void start_next_sample (void) +{ + + if (next_sample != 0) + start_sample (next_sample, next_volume, 0, 0); + + next_sample = 0; + next_volume = 0; + +}/* start_next_sample */ + +/* + * end_of_sound + * + * Call the Z-code routine which was given as the last parameter of + * a sound_effect call. This function may be called from a hardware + * interrupt (which requires extremely careful programming). + * + */ + +void end_of_sound (void) +{ + + playing = FALSE; + + if (!locked) { + + if (story_id == LURKING_HORROR) + start_next_sample (); + + direct_call (routine); + + } + +}/* end_of_sound */ + +/* + * z_sound_effect, load / play / stop / discard a sound effect. + * + * zargs[0] = number of bleep (1 or 2) or sample + * zargs[1] = operation to perform (samples only) + * zargs[2] = repeats and volume (play sample only) + * zargs[3] = end-of-sound routine (play sample only, optional) + * + * Note: Volumes range from 1 to 8, volume 255 is the default volume. + * Repeats are stored in the high byte, 255 is infinite loop. + * + */ + +void z_sound_effect (void) +{ + zword number = zargs[0]; + zword effect = zargs[1]; + zword volume = zargs[2]; + + if (number >= 3) { + + locked = TRUE; + + if (story_id == LURKING_HORROR && (number == 9 || number == 16)) { + + if (effect == EFFECT_PLAY) { + + next_sample = number; + next_volume = volume; + + locked = FALSE; + + if (!playing) + start_next_sample (); + + } else locked = FALSE; + + return; + + } + + playing = FALSE; + + switch (effect) { + + case EFFECT_PREPARE: + os_prepare_sample (number); + break; + case EFFECT_PLAY: + start_sample (number, lo (volume), hi (volume), (zargc == 4) ? zargs[3] : 0); + break; + case EFFECT_STOP: + os_stop_sample (); + break; + case EFFECT_FINISH_WITH: + os_finish_with_sample (); + break; + + } + + locked = FALSE; + + } else os_beep (number); + +}/* z_sound_effect */ diff --git a/stream.c b/stream.c new file mode 100644 index 0000000..c0c6e3b --- /dev/null +++ b/stream.c @@ -0,0 +1,352 @@ +/* + * stream.c + * + * IO stream implementation + * + */ + +#include "frotz.h" + +extern bool handle_hot_key (zchar); + +extern bool validate_click (void); + +extern void replay_open (void); +extern void replay_close (void); +extern void memory_open (zword, zword, bool); +extern void memory_close (void); +extern void record_open (void); +extern void record_close (void); +extern void script_open (void); +extern void script_close (void); + +extern void memory_word (const zchar *); +extern void memory_new_line (void); +extern void record_write_key (zchar); +extern void record_write_input (const zchar *, zchar); +extern void script_char (zchar); +extern void script_word (const zchar *); +extern void script_new_line (void); +extern void script_write_input (const zchar *, zchar); +extern void script_erase_input (const zchar *); +extern void script_mssg_on (void); +extern void script_mssg_off (void); +extern void screen_char (zchar); +extern void screen_word (const zchar *); +extern void screen_new_line (void); +extern void screen_write_input (const zchar *, zchar); +extern void screen_erase_input (const zchar *); +extern void screen_mssg_on (void); +extern void screen_mssg_off (void); + +extern zchar replay_read_key (void); +extern zchar replay_read_input (zchar *); +extern zchar console_read_key (zword); +extern zchar console_read_input (int, zchar *, zword, bool); + +extern int direct_call (zword); + +/* + * stream_mssg_on + * + * Start printing a "debugging" message. + * + */ + +void stream_mssg_on (void) +{ + + flush_buffer (); + + if (ostream_screen) + screen_mssg_on (); + if (ostream_script && enable_scripting) + script_mssg_on (); + + message = TRUE; + +}/* stream_mssg_on */ + +/* + * stream_mssg_off + * + * Stop printing a "debugging" message. + * + */ + +void stream_mssg_off (void) +{ + + flush_buffer (); + + if (ostream_screen) + screen_mssg_off (); + if (ostream_script && enable_scripting) + script_mssg_off (); + + message = FALSE; + +}/* stream_mssg_off */ + +/* + * z_output_stream, open or close an output stream. + * + * zargs[0] = stream to open (positive) or close (negative) + * zargs[1] = address to redirect output to (stream 3 only) + * zargs[2] = width of redirected output (stream 3 only, optional) + * + */ + +void z_output_stream (void) +{ + + flush_buffer (); + + switch ((short) zargs[0]) { + + case 1: ostream_screen = TRUE; + break; + case -1: ostream_screen = FALSE; + break; + case 2: if (!ostream_script) script_open (); + break; + case -2: if (ostream_script) script_close (); + break; + case 3: memory_open (zargs[1], zargs[2], zargc >= 3); + break; + case -3: memory_close (); + break; + case 4: if (!ostream_record) record_open (); + break; + case -4: if (ostream_record) record_close (); + break; + + } + +}/* z_output_stream */ + +/* + * stream_char + * + * Send a single character to the output stream. + * + */ + +void stream_char (zchar c) +{ + + if (ostream_screen) + screen_char (c); + if (ostream_script && enable_scripting) + script_char (c); + +}/* stream_char */ + +/* + * stream_word + * + * Send a string of characters to the output streams. + * + */ + +void stream_word (const zchar *s) +{ + + if (ostream_memory && !message) + + memory_word (s); + + else { + + if (ostream_screen) + screen_word (s); + if (ostream_script && enable_scripting) + script_word (s); + + } + +}/* stream_word */ + +/* + * stream_new_line + * + * Send a newline to the output streams. + * + */ + +void stream_new_line (void) +{ + + if (ostream_memory && !message) + + memory_new_line (); + + else { + + if (ostream_screen) + screen_new_line (); + if (ostream_script && enable_scripting) + script_new_line (); + + } + +}/* stream_new_line */ + +/* + * z_input_stream, select an input stream. + * + * zargs[0] = input stream to be selected + * + */ + +void z_input_stream (void) +{ + + flush_buffer (); + + if (zargs[0] == 0 && istream_replay) + replay_close (); + if (zargs[0] == 1 && !istream_replay) + replay_open (); + +}/* z_input_stream */ + +/* + * stream_read_key + * + * Read a single keystroke from the current input stream. + * + */ + +zchar stream_read_key ( zword timeout, zword routine, + bool hot_keys ) +{ + zchar key = ZC_BAD; + + flush_buffer (); + + /* Read key from current input stream */ + +continue_input: + + do { + + if (istream_replay) + key = replay_read_key (); + else + key = console_read_key (timeout); + + } while (key == ZC_BAD); + + /* Verify mouse clicks */ + + if (key == ZC_SINGLE_CLICK || key == ZC_DOUBLE_CLICK) + if (!validate_click ()) + goto continue_input; + + /* Copy key to the command file */ + + if (ostream_record && !istream_replay) + record_write_key (key); + + /* Handle timeouts */ + + if (key == ZC_TIME_OUT) + if (direct_call (routine) == 0) + goto continue_input; + + /* Handle hot keys */ + + if (hot_keys && key >= ZC_HKEY_MIN && key <= ZC_HKEY_MAX) { + + if (h_version == V4 && key == ZC_HKEY_UNDO) + goto continue_input; + if (!handle_hot_key (key)) + goto continue_input; + + return ZC_BAD; + + } + + /* Return key */ + + return key; + +}/* stream_read_key */ + +/* + * stream_read_input + * + * Read a line of input from the current input stream. + * + */ + +zchar stream_read_input ( int max, zchar *buf, + zword timeout, zword routine, + bool hot_keys, + bool no_scripting ) +{ + zchar key = ZC_BAD; + + flush_buffer (); + + /* Remove initial input from the transscript file or from the screen */ + + if (ostream_script && enable_scripting && !no_scripting) + script_erase_input (buf); + if (istream_replay) + screen_erase_input (buf); + + /* Read input line from current input stream */ + +continue_input: + + do { + + if (istream_replay) + key = replay_read_input (buf); + else + key = console_read_input (max, buf, timeout, key != ZC_BAD); + + } while (key == ZC_BAD); + + /* Verify mouse clicks */ + + if (key == ZC_SINGLE_CLICK || key == ZC_DOUBLE_CLICK) + if (!validate_click ()) + goto continue_input; + + /* Copy input line to the command file */ + + if (ostream_record && !istream_replay) + record_write_input (buf, key); + + /* Handle timeouts */ + + if (key == ZC_TIME_OUT) + if (direct_call (routine) == 0) + goto continue_input; + + /* Handle hot keys */ + + if (hot_keys && key >= ZC_HKEY_MIN && key <= ZC_HKEY_MAX) { + + if (!handle_hot_key (key)) + goto continue_input; + + return ZC_BAD; + + } + + /* Copy input line to transscript file or to the screen */ + + if (ostream_script && enable_scripting && !no_scripting) + script_write_input (buf, key); + if (istream_replay) + screen_write_input (buf, key); + + /* Return terminating key */ + + return key; + +}/* stream_read_input */ diff --git a/table.c b/table.c new file mode 100644 index 0000000..9999b67 --- /dev/null +++ b/table.c @@ -0,0 +1,180 @@ +/* + * table.c + * + * Table handling opcodes + * + */ + +#include "frotz.h" + +/* + * z_copy_table, copy a table or fill it with zeroes. + * + * zargs[0] = address of table + * zargs[1] = destination address or 0 for fill + * zargs[2] = size of table + * + * Note: Copying is safe even when source and destination overlap; but + * if zargs[1] is negative the table _must_ be copied forwards. + * + */ + +void z_copy_table (void) +{ + zword addr; + zword size = zargs[2]; + zbyte value; + int i; + + if (zargs[1] == 0) /* zero table */ + + for (i = 0; i < size; i++) + storeb ((zword) (zargs[0] + i), 0); + + else if ((short) size < 0 || zargs[0] > zargs[1]) /* copy forwards */ + + for (i = 0; i < (((short) size < 0) ? - (short) size : size); i++) { + addr = zargs[0] + i; + LOW_BYTE (addr, value) + storeb ((zword) (zargs[1] + i), value); + } + + else /* copy backwards */ + + for (i = size - 1; i >= 0; i--) { + addr = zargs[0] + i; + LOW_BYTE (addr, value) + storeb ((zword) (zargs[1] + i), value); + } + +}/* z_copy_table */ + +/* + * z_loadb, store a value from a table of bytes. + * + * zargs[0] = address of table + * zargs[1] = index of table entry to store + * + */ + +void z_loadb (void) +{ + zword addr = zargs[0] + zargs[1]; + zbyte value; + + LOW_BYTE (addr, value) + + store (value); + +}/* z_loadb */ + +/* + * z_loadw, store a value from a table of words. + * + * zargs[0] = address of table + * zargs[1] = index of table entry to store + * + */ + +void z_loadw (void) +{ + zword addr = zargs[0] + 2 * zargs[1]; + zword value; + + LOW_WORD (addr, value) + + store (value); + +}/* z_loadw */ + +/* + * z_scan_table, find and store the address of a target within a table. + * + * zargs[0] = target value to be searched for + * zargs[1] = address of table + * zargs[2] = number of table entries to check value against + * zargs[3] = type of table (optional, defaults to 0x82) + * + * Note: The table is a word array if bit 7 of zargs[3] is set; otherwise + * it's a byte array. The lower bits hold the address step. + * + */ + +void z_scan_table (void) +{ + zword addr = zargs[1]; + int i; + + /* Supply default arguments */ + + if (zargc < 4) + zargs[3] = 0x82; + + /* Scan byte or word array */ + + for (i = 0; i < zargs[2]; i++) { + + if (zargs[3] & 0x80) { /* scan word array */ + + zword wvalue; + + LOW_WORD (addr, wvalue) + + if (wvalue == zargs[0]) + goto finished; + + } else { /* scan byte array */ + + zbyte bvalue; + + LOW_BYTE (addr, bvalue) + + if (bvalue == zargs[0]) + goto finished; + + } + + addr += zargs[3] & 0x7f; + + } + + addr = 0; + +finished: + + store (addr); + branch (addr); + +}/* z_scan_table */ + +/* + * z_storeb, write a byte into a table of bytes. + * + * zargs[0] = address of table + * zargs[1] = index of table entry + * zargs[2] = value to be written + * + */ + +void z_storeb (void) +{ + + storeb ((zword) (zargs[0] + zargs[1]), zargs[2]); + +}/* z_storeb */ + +/* + * z_storew, write a word into a table of words. + * + * zargs[0] = address of table + * zargs[1] = index of table entry + * zargs[2] = value to be written + * + */ + +void z_storew (void) +{ + + storew ((zword) (zargs[0] + 2 * zargs[1]), zargs[2]); + +}/* z_storew */ diff --git a/text.c b/text.c new file mode 100644 index 0000000..c8c3dbe --- /dev/null +++ b/text.c @@ -0,0 +1,1080 @@ +/* + * text.c + * + * Text manipulation functions + * + */ + +#include "frotz.h" + +enum string_type { + LOW_STRING, ABBREVIATION, HIGH_STRING, EMBEDDED_STRING, VOCABULARY +}; + +extern zword object_name (zword); + +static zchar decoded[10]; +static zword encoded[3]; + +static zchar zscii_to_latin1[] = { + 0xe4, 0xf6, 0xfc, 0xc4, 0xd6, 0xdc, 0xdf, 0xab, + 0xbb, 0xeb, 0xef, 0xff, 0xcb, 0xcf, 0xe1, 0xe9, + 0xed, 0xf3, 0xfa, 0xfd, 0xc1, 0xc9, 0xcd, 0xd3, + 0xda, 0xdd, 0xe0, 0xe8, 0xec, 0xf2, 0xf9, 0xc0, + 0xc8, 0xcc, 0xd2, 0xd9, 0xe2, 0xea, 0xee, 0xf4, + 0xfb, 0xc2, 0xca, 0xce, 0xd4, 0xdb, 0xe5, 0xc5, + 0xf8, 0xd8, 0xe3, 0xf1, 0xf5, 0xc3, 0xd1, 0xd5, + 0xe6, 0xc6, 0xe7, 0xc7, 0xfe, 0xf0, 0xde, 0xd0, + 0xa3, 0x00, 0x00, 0xa1, 0xbf +}; + +/* + * translate_from_zscii + * + * Map a ZSCII character onto the ISO Latin-1 alphabet. + * + */ + +zchar translate_from_zscii (zbyte c) +{ + + if (c == 0xfc) + return ZC_MENU_CLICK; + if (c == 0xfd) + return ZC_DOUBLE_CLICK; + if (c == 0xfe) + return ZC_SINGLE_CLICK; + + if (c >= 0x9b && story_id != BEYOND_ZORK) + + if (hx_unicode_table != 0) { /* game has its own Unicode table */ + + zbyte N; + + LOW_BYTE (hx_unicode_table, N) + + if (c - 0x9b < N) { + + zword addr = hx_unicode_table + 1 + 2 * (c - 0x9b); + zword unicode; + + LOW_WORD (addr, unicode) + + return (unicode < 0x100) ? (zchar) unicode : '?'; + + } else return '?'; + + } else /* game uses standard set */ + + if (c <= 0xdf) { + + if (c == 0xdc || c == 0xdd) /* Oe and oe ligatures */ + return '?'; /* are not ISO-Latin 1 */ + + return zscii_to_latin1[c - 0x9b]; + + } else return '?'; + + return c; + +}/* translate_from_zscii */ + +/* + * translate_to_zscii + * + * Map an ISO Latin-1 character onto the ZSCII alphabet. + * + */ + +zbyte translate_to_zscii (zchar c) +{ + int i; + + if (c == ZC_SINGLE_CLICK) + return 0xfe; + if (c == ZC_DOUBLE_CLICK) + return 0xfd; + if (c == ZC_MENU_CLICK) + return 0xfc; + + if (c >= ZC_LATIN1_MIN) + + if (hx_unicode_table != 0) { /* game has its own Unicode table */ + + zbyte N; + int i; + + LOW_BYTE (hx_unicode_table, N) + + for (i = 0x9b; i < 0x9b + N; i++) { + + zword addr = hx_unicode_table + 1 + 2 * (i - 0x9b); + zword unicode; + + LOW_WORD (addr, unicode) + + if (c == unicode) + return (zbyte) i; + + } + + return '?'; + + } else { /* game uses standard set */ + + for (i = 0x9b; i <= 0xdf; i++) + if (c == zscii_to_latin1[i - 0x9b]) + return (zbyte) i; + + return '?'; + + } + + return c; + +}/* translate_to_zscii */ + +/* + * alphabet + * + * Return a character from one of the three character sets. + * + */ + +static zchar alphabet (int set, int index) +{ + + if (h_alphabet != 0) { /* game uses its own alphabet */ + + zbyte c; + + zword addr = h_alphabet + 26 * set + index; + LOW_BYTE (addr, c) + + return translate_from_zscii (c); + + } else /* game uses default alphabet */ + + if (set == 0) + return 'a' + index; + else if (set == 1) + return 'A' + index; + else if (h_version == V1) + return " 0123456789.,!?_#'\"/\\<-:()"[index]; + else + return " ^0123456789.,!?_#'\"/\\-:()"[index]; + +}/* alphabet */ + +/* + * load_string + * + * Copy a ZSCII string from the memory to the global "decoded" string. + * + */ + +static void load_string (zword addr, zword length) +{ + int resolution = (h_version <= V3) ? 2 : 3; + int i = 0; + + while (i < 3 * resolution) + + if (i < length) { + + zbyte c; + + LOW_BYTE (addr, c) + addr++; + + decoded[i++] = translate_from_zscii (c); + + } else decoded[i++] = 0; + +}/* load_string */ + +/* + * encode_text + * + * Encode the Unicode text in the global "decoded" string then write + * the result to the global "encoded" array. (This is used to look up + * words in the dictionary.) Up to V3 the vocabulary resolution is + * two, since V4 it is three words. Because each word contains three + * Z-characters, that makes six or nine Z-characters respectively. + * Longer words are chopped to the proper size, shorter words are are + * padded out with 5's. For word completion we pad with 0s and 31s, + * the minimum and maximum Z-characters. + * + */ + +static void encode_text (int padding) +{ + static zchar again[] = { 'a', 'g', 'a', 'i', 'n', 0 }; + static zchar examine[] = { 'e', 'x', 'a', 'm', 'i', 'n', 'e', 0 }; + static zchar wait[] = { 'w', 'a', 'i', 't', 0 }; + + zbyte zchars[12]; + const zchar *ptr = decoded; + zchar c; + int resolution = (h_version <= V3) ? 2 : 3; + int i = 0; + + /* Expand abbreviations that some old Infocom games lack */ + + if (option_expand_abbreviations) + + if (padding == 0x05 && decoded[1] == 0) + + switch (decoded[0]) { + case 'g': ptr = again; break; + case 'x': ptr = examine; break; + case 'z': ptr = wait; break; + } + + /* Translate string to a sequence of Z-characters */ + + while (i < 3 * resolution) + + if ((c = *ptr++) != 0) { + + int index, set; + zbyte c2; + + /* Search character in the alphabet */ + + for (set = 0; set < 3; set++) + for (index = 0; index < 26; index++) + if (c == alphabet (set, index)) + goto letter_found; + + /* Character not found, store its ZSCII value */ + + c2 = translate_to_zscii (c); + + zchars[i++] = 5; + zchars[i++] = 6; + zchars[i++] = c2 >> 5; + zchars[i++] = c2 & 0x1f; + + continue; + + letter_found: + + /* Character found, store its index */ + + if (set != 0) + zchars[i++] = ((h_version <= V2) ? 1 : 3) + set; + + zchars[i++] = index + 6; + + } else zchars[i++] = padding; + + /* Three Z-characters make a 16bit word */ + + for (i = 0; i < resolution; i++) + + encoded[i] = + (zchars[3 * i + 0] << 10) | + (zchars[3 * i + 1] << 5) | + (zchars[3 * i + 2]); + + encoded[resolution - 1] |= 0x8000; + +}/* encode_text */ + +/* + * z_check_unicode, test if a unicode character can be read and printed. + * + * zargs[0] = Unicode + * + */ + +void z_check_unicode (void) +{ + zword c = zargs[0]; + + if (c >= 0x20 && c <= 0x7e) + store (3); + else if (c == 0xa0) + store (1); + else if (c >= 0xa1 && c <= 0xff) + store (3); + else + store (0); + +}/* z_check_unicode */ + +/* + * z_encode_text, encode a ZSCII string for use in a dictionary. + * + * zargs[0] = address of text buffer + * zargs[1] = length of ASCII string + * zargs[2] = offset of ASCII string within the text buffer + * zargs[3] = address to store encoded text in + * + * This is a V5+ opcode and therefore the dictionary resolution must be + * three 16bit words. + * + */ + +void z_encode_text (void) +{ + int i; + + load_string ((zword) (zargs[0] + zargs[2]), zargs[1]); + + encode_text (0x05); + + for (i = 0; i < 3; i++) + storew ((zword) (zargs[3] + 2 * i), encoded[i]); + +}/* z_encode_text */ + +/* + * decode_text + * + * Convert encoded text to Unicode. The encoded text consists of 16bit + * words. Every word holds 3 Z-characters (5 bits each) plus a spare + * bit to mark the last word. The Z-characters translate to ZSCII by + * looking at the current current character set. Some select another + * character set, others refer to abbreviations. + * + * There are several different string types: + * + * LOW_STRING - from the lower 64KB (byte address) + * ABBREVIATION - from the abbreviations table (word address) + * HIGH_STRING - from the end of the memory map (packed address) + * EMBEDDED_STRING - from the instruction stream (at PC) + * VOCABULARY - from the dictionary (byte address) + * + * The last type is only used for word completion. + * + */ + +#define outchar(c) if (st==VOCABULARY) *ptr++=c; else print_char(c) + +static void decode_text (enum string_type st, zword addr) +{ + zchar *ptr; + long byte_addr; + zchar c2; + zword code; + zbyte c, prev_c = 0; + int shift_state = 0; + int shift_lock = 0; + int status = 0; + + /* Calculate the byte address if necessary */ + + if (st == ABBREVIATION) + + byte_addr = (long) addr << 1; + + else if (st == HIGH_STRING) { + + if (h_version <= V3) + byte_addr = (long) addr << 1; + else if (h_version <= V5) + byte_addr = (long) addr << 2; + else if (h_version <= V7) + byte_addr = ((long) addr << 2) + ((long) h_strings_offset << 3); + else /* h_version == V8 */ + byte_addr = (long) addr << 3; + + if (byte_addr >= story_size) + runtime_error ("Print at illegal address"); + + } + + /* Loop until a 16bit word has the highest bit set */ + + if (st == VOCABULARY) + ptr = decoded; + + do { + + int i; + + /* Fetch the next 16bit word */ + + if (st == LOW_STRING || st == VOCABULARY) { + LOW_WORD (addr, code) + addr += 2; + } else if (st == HIGH_STRING || st == ABBREVIATION) { + HIGH_WORD (byte_addr, code) + byte_addr += 2; + } else + CODE_WORD (code) + + /* Read its three Z-characters */ + + for (i = 10; i >= 0; i -= 5) { + + zword abbr_addr; + zword ptr_addr; + + c = (code >> i) & 0x1f; + + switch (status) { + + case 0: /* normal operation */ + + if (shift_state == 2 && c == 6) + status = 2; + + else if (h_version == V1 && c == 1) + new_line (); + + else if (h_version >= V2 && shift_state == 2 && c == 7) + new_line (); + + else if (c >= 6) + outchar (alphabet (shift_state, c - 6)); + + else if (c == 0) + outchar (' '); + + else if (h_version >= V2 && c == 1) + status = 1; + + else if (h_version >= V3 && c <= 3) + status = 1; + + else { + + shift_state = (shift_lock + (c & 1) + 1) % 3; + + if (h_version <= V2 && c >= 4) + shift_lock = shift_state; + + break; + + } + + shift_state = shift_lock; + + break; + + case 1: /* abbreviation */ + + ptr_addr = h_abbreviations + 64 * (prev_c - 1) + 2 * c; + + LOW_WORD (ptr_addr, abbr_addr) + decode_text (ABBREVIATION, abbr_addr); + + status = 0; + break; + + case 2: /* ZSCII character - first part */ + + status = 3; + break; + + case 3: /* ZSCII character - second part */ + + c2 = translate_from_zscii ((prev_c << 5) | c); + outchar (c2); + + status = 0; + break; + + } + + prev_c = c; + + } + + } while (!(code & 0x8000)); + + if (st == VOCABULARY) + *ptr = 0; + +}/* decode_text */ + +#undef outchar + +/* + * z_new_line, print a new line. + * + * no zargs used + * + */ + +void z_new_line (void) +{ + + new_line (); + +}/* z_new_line */ + +/* + * z_print, print a string embedded in the instruction stream. + * + * no zargs used + * + */ + +void z_print (void) +{ + + decode_text (EMBEDDED_STRING, 0); + +}/* z_print */ + +/* + * z_print_addr, print a string from the lower 64KB. + * + * zargs[0] = address of string to print + * + */ + +void z_print_addr (void) +{ + + decode_text (LOW_STRING, zargs[0]); + +}/* z_print_addr */ + +/* + * z_print_char print a single ZSCII character. + * + * zargs[0] = ZSCII character to be printed + * + */ + +void z_print_char (void) +{ + + print_char (translate_from_zscii (zargs[0])); + +}/* z_print_char */ + +/* + * z_print_form, print a formatted table. + * + * zargs[0] = address of formatted table to be printed + * + */ + +void z_print_form (void) +{ + zword count; + zword addr = zargs[0]; + + bool first = TRUE; + + for (;;) { + + LOW_WORD (addr, count) + addr += 2; + + if (count == 0) + break; + + if (!first) + new_line (); + + while (count--) { + + zbyte c; + + LOW_BYTE (addr, c) + addr++; + + print_char (translate_from_zscii (c)); + + } + + first = FALSE; + + } + +}/* z_print_form */ + +/* + * print_num + * + * Print a signed 16bit number. + * + */ + +void print_num (zword value) +{ + int i; + + /* Print sign */ + + if ((short) value < 0) { + print_char ('-'); + value = - (short) value; + } + + /* Print absolute value */ + + for (i = 10000; i != 0; i /= 10) + if (value >= i || i == 1) + print_char ('0' + (value / i) % 10); + +}/* print_num */ + +/* + * z_print_num, print a signed number. + * + * zargs[0] = number to print + * + */ + +void z_print_num (void) +{ + + print_num (zargs[0]); + +}/* z_print_num */ + +/* + * print_object + * + * Print an object description. + * + */ + +void print_object (zword object) +{ + zword addr = object_name (object); + zword code = 0x94a5; + zbyte length; + + LOW_BYTE (addr, length) + addr++; + + if (length != 0) + LOW_WORD (addr, code) + + if (code == 0x94a5) { /* encoded text 0x94a5 == empty string */ + + print_string ("object#"); /* supply a generic name */ + print_num (object); /* for anonymous objects */ + + } else decode_text (LOW_STRING, addr); + +}/* print_object */ + +/* + * z_print_obj, print an object description. + * + * zargs[0] = number of object to be printed + * + */ + +void z_print_obj (void) +{ + + print_object (zargs[0]); + +}/* z_print_obj */ + +/* + * z_print_paddr, print the string at the given packed address. + * + * zargs[0] = packed address of string to be printed + * + */ + +void z_print_paddr (void) +{ + + decode_text (HIGH_STRING, zargs[0]); + +}/* z_print_paddr */ + +/* + * z_print_ret, print the string at PC, print newline then return true. + * + * no zargs used + * + */ + +void z_print_ret (void) +{ + + decode_text (EMBEDDED_STRING, 0); + new_line (); + ret (1); + +}/* z_print_ret */ + +/* + * print_string + * + * Print a string of ASCII characters. + * + */ + +void print_string (const char *s) +{ + char c; + + while ((c = *s++) != 0) + + if (c == '\n') + new_line (); + else + print_char (c); + +}/* print_string */ + +/* + * z_print_unicode + * + * zargs[0] = Unicode + * + */ + +void z_print_unicode (void) +{ + + print_char ((zargs[0] <= 0xff) ? zargs[0] : '?'); + +}/* z_print_unicode */ + +/* + * lookup_text + * + * Scan a dictionary searching for the given word. The first argument + * can be + * + * 0x00 - find the first word which is >= the given one + * 0x05 - find the word which exactly matches the given one + * 0x1f - find the last word which is <= the given one + * + * The return value is 0 if the search fails. + * + */ + +static zword lookup_text (int padding, zword dct) +{ + zword entry_addr; + zword entry_count; + zword entry; + zword addr; + zbyte entry_len; + zbyte sep_count; + int resolution = (h_version <= V3) ? 2 : 3; + int entry_number; + int lower, upper; + int i; + bool sorted; + + encode_text (padding); + + LOW_BYTE (dct, sep_count) /* skip word separators */ + dct += 1 + sep_count; + LOW_BYTE (dct, entry_len) /* get length of entries */ + dct += 1; + LOW_WORD (dct, entry_count) /* get number of entries */ + dct += 2; + + if ((short) entry_count < 0) { /* bad luck, entries aren't sorted */ + + entry_count = - (short) entry_count; + sorted = FALSE; + + } else sorted = TRUE; /* entries are sorted */ + + lower = 0; + upper = entry_count - 1; + + while (lower <= upper) { + + if (sorted) /* binary search */ + entry_number = (lower + upper) / 2; + else /* linear search */ + entry_number = lower; + + entry_addr = dct + entry_number * entry_len; + + /* Compare word to dictionary entry */ + + addr = entry_addr; + + for (i = 0; i < resolution; i++) { + LOW_WORD (addr, entry) + if (encoded[i] != entry) + goto continuing; + addr += 2; + } + + return entry_addr; /* exact match found, return now */ + + continuing: + + if (sorted) /* binary search */ + + if (encoded[i] > entry) + lower = entry_number + 1; + else + upper = entry_number - 1; + + else lower++; /* linear search */ + + } + + /* No exact match has been found */ + + if (padding == 0x05) + return 0; + + entry_number = (padding == 0x00) ? lower : upper; + + if (entry_number == -1 || entry_number == entry_count) + return 0; + + return dct + entry_number * entry_len; + +}/* lookup_text */ + +/* + * tokenise_text + * + * Translate a single word to a token and append it to the token + * buffer. Every token consists of the address of the dictionary + * entry, the length of the word and the offset of the word from + * the start of the text buffer. Unknown words cause empty slots + * if the flag is set (such that the text can be scanned several + * times with different dictionaries); otherwise they are zero. + * + */ + +static void tokenise_text (zword text, zword length, zword from, zword parse, zword dct, bool flag) +{ + zword addr; + zbyte token_max, token_count; + + LOW_BYTE (parse, token_max) + parse++; + LOW_BYTE (parse, token_count) + + if (token_count < token_max) { /* sufficient space left for token? */ + + storeb (parse++, token_count + 1); + + load_string ((zword) (text + from), length); + + addr = lookup_text (0x05, dct); + + if (addr != 0 || !flag) { + + parse += 4 * token_count; + + storew ((zword) (parse + 0), addr); + storeb ((zword) (parse + 2), length); + storeb ((zword) (parse + 3), from); + + } + + } + +}/* tokenise_text */ + +/* + * tokenise_line + * + * Split an input line into words and translate the words to tokens. + * + */ + +void tokenise_line (zword text, zword token, zword dct, bool flag) +{ + zword addr1; + zword addr2; + zbyte length; + zbyte c; + + /* Use standard dictionary if the given dictionary is zero */ + + if (dct == 0) + dct = h_dictionary; + + /* Remove all tokens before inserting new ones */ + + storeb ((zword) (token + 1), 0); + + /* Move the first pointer across the text buffer searching for the + beginning of a word. If this succeeds, store the position in a + second pointer. Move the first pointer searching for the end of + the word. When it is found, "tokenise" the word. Continue until + the end of the buffer is reached. */ + + addr1 = text; + addr2 = 0; + + if (h_version >= V5) { + addr1++; + LOW_BYTE (addr1, length) + } + + do { + + zword sep_addr; + zbyte sep_count; + zbyte separator; + + /* Fetch next ZSCII character */ + + addr1++; + + if (h_version >= V5 && addr1 == text + 2 + length) + c = 0; + else + LOW_BYTE (addr1, c) + + /* Check for separator */ + + sep_addr = dct; + + LOW_BYTE (sep_addr, sep_count) + sep_addr++; + + do { + + LOW_BYTE (sep_addr, separator) + sep_addr++; + + } while (c != separator && --sep_count != 0); + + /* This could be the start or the end of a word */ + + if (sep_count == 0 && c != ' ' && c != 0) { + + if (addr2 == 0) + addr2 = addr1; + + } else if (addr2 != 0) { + + tokenise_text ( + text, + (zword) (addr1 - addr2), + (zword) (addr2 - text), + token, dct, flag ); + + addr2 = 0; + + } + + /* Translate separator (which is a word in its own right) */ + + if (sep_count != 0) + + tokenise_text ( + text, + (zword) (1), + (zword) (addr1 - text), + token, dct, flag ); + + } while (c != 0); + +}/* tokenise_line */ + +/* + * z_tokenise, make a lexical analysis of a ZSCII string. + * + * zargs[0] = address of string to analyze + * zargs[1] = address of token buffer + * zargs[2] = address of dictionary (optional) + * zargs[3] = set when unknown words cause empty slots (optional) + * + */ + +void z_tokenise (void) +{ + + /* Supply default arguments */ + + if (zargc < 3) + zargs[2] = 0; + if (zargc < 4) + zargs[3] = 0; + + /* Call tokenise_line to do the real work */ + + tokenise_line (zargs[0], zargs[1], zargs[2], zargs[3] != 0); + +}/* z_tokenise */ + +/* + * completion + * + * Scan the vocabulary to complete the last word on the input line + * (similar to "tcsh" under Unix). The return value is + * + * 2 ==> completion is impossible + * 1 ==> completion is ambiguous + * 0 ==> completion is successful + * + * The function also returns a string in its second argument. In case + * of 2, the string is empty; in case of 1, the string is the longest + * extension of the last word on the input line that is common to all + * possible completions (for instance, if the last word on the input + * is "fo" and its only possible completions are "follow" and "folly" + * then the string is "ll"); in case of 0, the string is an extension + * to the last word that results in the only possible completion. + * + */ + +int completion (const zchar *buffer, zchar *result) +{ + zword minaddr; + zword maxaddr; + zchar *ptr; + zchar c; + int len; + int i; + + *result = 0; + + /* Copy last word to "decoded" string */ + + len = 0; + + while ((c = *buffer++) != 0) + + if (c != ' ') { + + if (len < 9) + decoded[len++] = c; + + } else len = 0; + + decoded[len] = 0; + + /* Search the dictionary for first and last possible extensions */ + + minaddr = lookup_text (0x00, h_dictionary); + maxaddr = lookup_text (0x1f, h_dictionary); + + if (minaddr == 0 || maxaddr == 0 || minaddr > maxaddr) + return 2; + + /* Copy first extension to "result" string */ + + decode_text (VOCABULARY, minaddr); + + ptr = result; + + for (i = len; (c = decoded[i]) != 0; i++) + *ptr++ = c; + *ptr = 0; + + /* Merge second extension with "result" string */ + + decode_text (VOCABULARY, maxaddr); + + for (i = len, ptr = result; (c = decoded[i]) != 0; i++, ptr++) + if (*ptr != c) break; + *ptr = 0; + + /* Search was ambiguous or successful */ + + return (minaddr == maxaddr) ? 0 : 1; + +}/* completion */ diff --git a/variable.c b/variable.c new file mode 100644 index 0000000..86a663b --- /dev/null +++ b/variable.c @@ -0,0 +1,291 @@ +/* + * variable.c + * + * Variable and stack related opcodes + * + */ + +#include "frotz.h" + +/* + * z_dec, decrement a variable. + * + * zargs[0] = variable to decrement + * + */ + +void z_dec (void) +{ + zword value; + + if (zargs[0] == 0) + (*sp)--; + else if (zargs[0] < 16) + (*(fp - zargs[0]))--; + else { + zword addr = h_globals + 2 * (zargs[0] - 16); + LOW_WORD (addr, value) + value--; + SET_WORD (addr, value) + } + +}/* z_dec */ + +/* + * z_dec_chk, decrement a variable and branch if now less than value. + * + * zargs[0] = variable to decrement + * zargs[1] = value to check variable against + * + */ + +void z_dec_chk (void) +{ + zword value; + + if (zargs[0] == 0) + value = --(*sp); + else if (zargs[0] < 16) + value = --(*(fp - zargs[0])); + else { + zword addr = h_globals + 2 * (zargs[0] - 16); + LOW_WORD (addr, value) + value--; + SET_WORD (addr, value) + } + + branch ((short) value < (short) zargs[1]); + +}/* z_dec_chk */ + +/* + * z_inc, increment a variable. + * + * zargs[0] = variable to increment + * + */ + +void z_inc (void) +{ + zword value; + + if (zargs[0] == 0) + (*sp)++; + else if (zargs[0] < 16) + (*(fp - zargs[0]))++; + else { + zword addr = h_globals + 2 * (zargs[0] - 16); + LOW_WORD (addr, value) + value++; + SET_WORD (addr, value) + } + +}/* z_inc */ + +/* + * z_inc_chk, increment a variable and branch if now greater than value. + * + * zargs[0] = variable to increment + * zargs[1] = value to check variable against + * + */ + +void z_inc_chk (void) +{ + zword value; + + if (zargs[0] == 0) + value = ++(*sp); + else if (zargs[0] < 16) + value = ++(*(fp - zargs[0])); + else { + zword addr = h_globals + 2 * (zargs[0] - 16); + LOW_WORD (addr, value) + value++; + SET_WORD (addr, value) + } + + branch ((short) value > (short) zargs[1]); + +}/* z_inc_chk */ + +/* + * z_load, store the value of a variable. + * + * zargs[0] = variable to store + * + */ + +void z_load (void) +{ + zword value; + + if (zargs[0] == 0) + value = *sp; + else if (zargs[0] < 16) + value = *(fp - zargs[0]); + else { + zword addr = h_globals + 2 * (zargs[0] - 16); + LOW_WORD (addr, value) + } + + store (value); + +}/* z_load */ + +/* + * z_pop, pop a value off the game stack and discard it. + * + * no zargs used + * + */ + +void z_pop (void) +{ + + sp++; + +}/* z_pop */ + +/* + * z_pop_stack, pop n values off the game or user stack and discard them. + * + * zargs[0] = number of values to discard + * zargs[1] = address of user stack (optional) + * + */ + +void z_pop_stack (void) +{ + + if (zargc == 2) { /* it's a user stack */ + + zword size; + zword addr = zargs[1]; + + LOW_WORD (addr, size) + + size += zargs[0]; + storew (addr, size); + + } else sp += zargs[0]; /* it's the game stack */ + +}/* z_pop_stack */ + +/* + * z_pull, pop a value off... + * + * a) ...the game or a user stack and store it (V6) + * + * zargs[0] = address of user stack (optional) + * + * b) ...the game stack and write it to a variable (other than V6) + * + * zargs[0] = variable to write value to + * + */ + +void z_pull (void) +{ + zword value; + + if (h_version != V6) { /* not a V6 game, pop stack and write */ + + value = *sp++; + + if (zargs[0] == 0) + *sp = value; + else if (zargs[0] < 16) + *(fp - zargs[0]) = value; + else { + zword addr = h_globals + 2 * (zargs[0] - 16); + SET_WORD (addr, value) + } + + } else { /* it's V6, but is there a user stack? */ + + if (zargc == 1) { /* it's a user stack */ + + zword size; + zword addr = zargs[0]; + + LOW_WORD (addr, size) + + size++; + storew (addr, size); + + addr += 2 * size; + LOW_WORD (addr, value) + + } else value = *sp++; /* it's the game stack */ + + store (value); + + } + +}/* z_pull */ + +/* + * z_push, push a value onto the game stack. + * + * zargs[0] = value to push onto the stack + * + */ + +void z_push (void) +{ + + *--sp = zargs[0]; + +}/* z_push */ + +/* + * z_push_stack, push a value onto a user stack then branch if successful. + * + * zargs[0] = value to push onto the stack + * zargs[1] = address of user stack + * + */ + +void z_push_stack (void) +{ + zword size; + zword addr = zargs[1]; + + LOW_WORD (addr, size) + + if (size != 0) { + + storew ((zword) (addr + 2 * size), zargs[0]); + + size--; + storew (addr, size); + + } + + branch (size); + +}/* z_push_stack */ + +/* + * z_store, write a value to a variable. + * + * zargs[0] = variable to be written to + * zargs[1] = value to write + * + */ + +void z_store (void) +{ + zword value = zargs[1]; + + if (zargs[0] == 0) + *sp = value; + else if (zargs[0] < 16) + *(fp - zargs[0]) = value; + else { + zword addr = h_globals + 2 * (zargs[0] - 16); + SET_WORD (addr, value) + } + +}/* z_store */ -- 2.34.1