aboutsummaryrefslogtreecommitdiff
path: root/engines/glk/agt/os_glk.cpp
diff options
context:
space:
mode:
authorPaul Gilbert2019-11-24 18:15:56 -0800
committerPaul Gilbert2019-11-27 21:10:29 -0800
commit28c3584148c49c1c6f118c5cbf13d0bf53d56726 (patch)
tree03102c6aeccebf3aec189deaf19cfd641673245e /engines/glk/agt/os_glk.cpp
parentf9921a7a177dd506640211dd05751e046100a57b (diff)
downloadscummvm-rg350-28c3584148c49c1c6f118c5cbf13d0bf53d56726.tar.gz
scummvm-rg350-28c3584148c49c1c6f118c5cbf13d0bf53d56726.tar.bz2
scummvm-rg350-28c3584148c49c1c6f118c5cbf13d0bf53d56726.zip
GLK: AGT: Added subengine files
Diffstat (limited to 'engines/glk/agt/os_glk.cpp')
-rw-r--r--engines/glk/agt/os_glk.cpp6099
1 files changed, 6099 insertions, 0 deletions
diff --git a/engines/glk/agt/os_glk.cpp b/engines/glk/agt/os_glk.cpp
new file mode 100644
index 0000000000..1d9abd6838
--- /dev/null
+++ b/engines/glk/agt/os_glk.cpp
@@ -0,0 +1,6099 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "glk/agt/agility.h"
+#include "glk/agt/interp.h"
+#include "glk/agt/agt.h"
+
+namespace Glk {
+namespace AGT {
+
+/*
+ * Glk interface for AGiliTy 1.1.1.1
+ * -------------------------------
+ *
+ * This module contains the the Glk porting layer for AGiliTy. It
+ * defines the Glk arguments list structure, the entry points for the
+ * Glk library framework to use, and all platform-abstracted I/O to
+ * link to Glk's I/O.
+ *
+ * The following items are omitted from this Glk port:
+ *
+ * o Calls to g_vm->glk_tick(). The Glk documentation states that the
+ * interpreter should call g_vm->glk_tick() every opcode or so. This is
+ * intrusive to code (it goes outside of this module), and since
+ * most Glk libraries do precisely nothing in g_vm->glk_tick(), there is
+ * little motivation to add it.
+ *
+ * o Glk tries to assert control over _all_ file I/O. It's just too
+ * disruptive to add it to existing code, so for now, the AGiliTy
+ * interpreter is still dependent on stdio and the like.
+ */
+
+/*
+ * True and false definitions -- usually defined in glkstart.h, but we need
+ * them early, so we'll define them here too. We also need NULL, but that's
+ * normally from stdio.h or one of it's cousins.
+ */
+#ifndef FALSE
+# define FALSE 0
+#endif
+#ifndef TRUE
+# define TRUE (!FALSE)
+#endif
+
+
+/*---------------------------------------------------------------------*/
+/* Module variables, miscellaneous externals not in header files */
+/*---------------------------------------------------------------------*/
+
+/* Glk AGiliTy port version number. */
+static const glui32 GAGT_PORT_VERSION = 0x00010701;
+
+/*
+ * We use two Glk windows; one is two lines at the top of the display area
+ * for status, and the other is the remainder of the display area, used for,
+ * well, everything else. Where a particular Glk implementation won't do
+ * more than one window, the status window remains NULL.
+ */
+static winid_t gagt_main_window = NULL,
+ gagt_status_window = NULL;
+
+/*
+ * Transcript stream and input log. These are NULL if there is no current
+ * collection of these strings.
+ */
+static strid_t gagt_transcript_stream = NULL,
+ gagt_inputlog_stream = NULL;
+
+/* Input read log stream, for reading back an input log. */
+static strid_t gagt_readlog_stream = NULL;
+
+/* Options that may be turned off or set by command line flags. */
+enum FontMode {
+ FONT_AUTOMATIC, FONT_FIXED_WIDTH, FONT_PROPORTIONAL, FONT_DEBUG
+};
+static FontMode gagt_font_mode = FONT_AUTOMATIC;
+
+enum DelayMode {
+ DELAY_FULL, DELAY_SHORT, DELAY_OFF
+};
+static DelayMode gagt_delay_mode = DELAY_SHORT;
+static int gagt_replacement_enabled = TRUE,
+ gagt_extended_status_enabled = TRUE,
+ gagt_abbreviations_enabled = TRUE,
+ gagt_commands_enabled = TRUE;
+
+/* Forward declaration of event wait functions. */
+static void gagt_event_wait(glui32 wait_type, event_t *event);
+static void gagt_event_wait_2(glui32 wait_type_1,
+ glui32 wait_type_2,
+ event_t *event);
+
+/*
+ * Forward declaration of the g_vm->glk_exit() wrapper. Normal functions in this
+ * module should not to call g_vm->glk_exit() directly; they should always call it
+ * through the wrapper instead.
+ */
+static void gagt_exit();
+
+
+/*---------------------------------------------------------------------*/
+/* Glk port utility functions */
+/*---------------------------------------------------------------------*/
+
+/*
+ * gagt_fatal()
+ *
+ * Fatal error handler. The function returns, expecting the caller to
+ * abort() or otherwise handle the error.
+ */
+static void gagt_fatal(const char *string) {
+ /*
+ * If the failure happens too early for us to have a window, print
+ * the message to stderr.
+ */
+ if (!gagt_main_window)
+ error("INTERNAL ERROR: %s", string);
+
+ /* Cancel all possible pending window input events. */
+ g_vm->glk_cancel_line_event(gagt_main_window, NULL);
+ g_vm->glk_cancel_char_event(gagt_main_window);
+
+ /* Print a message indicating the error. */
+ g_vm->glk_set_window(gagt_main_window);
+ g_vm->glk_set_style(style_Normal);
+ g_vm->glk_put_string("\n\nINTERNAL ERROR: ");
+ g_vm->glk_put_string(string);
+
+ g_vm->glk_put_string("\n\nPlease record the details of this error, try to"
+ " note down everything you did to cause it, and email"
+ " this information to simon_baldwin@yahoo.com.\n\n");
+}
+
+
+/*
+ * gagt_malloc()
+ * gagt_realloc()
+ *
+ * Non-failing malloc and realloc; call gagt_fatal() and exit if memory
+ * allocation fails.
+ */
+static void *gagt_malloc(size_t size) {
+ void *pointer;
+
+ pointer = malloc(size);
+ if (!pointer) {
+ gagt_fatal("GLK: Out of system memory");
+ gagt_exit();
+ }
+
+ return pointer;
+}
+
+static void *gagt_realloc(void *ptr, size_t size) {
+ void *pointer;
+
+ pointer = realloc(ptr, size);
+ if (!pointer) {
+ gagt_fatal("GLK: Out of system memory");
+ gagt_exit();
+ }
+
+ return pointer;
+}
+
+
+/*
+ * gagt_strncasecmp()
+ * gagt_strcasecmp()
+ *
+ * Strncasecmp and strcasecmp are not ANSI functions, so here are local
+ * definitions to do the same jobs.
+ */
+static int gagt_strncasecmp(const char *s1, const char *s2, size_t n) {
+ size_t index;
+
+ for (index = 0; index < n; index++) {
+ int diff;
+
+ diff = g_vm->glk_char_to_lower(s1[index]) - g_vm->glk_char_to_lower(s2[index]);
+ if (diff < 0 || diff > 0)
+ return diff < 0 ? -1 : 1;
+ }
+
+ return 0;
+}
+
+static int gagt_strcasecmp(const char *s1, const char *s2) {
+ size_t s1len, s2len;
+ int result;
+
+ s1len = strlen(s1);
+ s2len = strlen(s2);
+
+ result = gagt_strncasecmp(s1, s2, s1len < s2len ? s1len : s2len);
+ if (result < 0 || result > 0)
+ return result;
+ else
+ return s1len < s2len ? -1 : s1len > s2len ? 1 : 0;
+}
+
+
+/*
+ * gagt_debug()
+ *
+ * Handler for module debug output. If no debug, it ignores the call,
+ * otherwise it prints a debug message, prefixed by the function name.
+ */
+static void gagt_debug(const char *function, const char *format, ...) {
+ if (DEBUG_OUT) {
+ Common::WriteStream *ws = dynamic_cast<Common::WriteStream *>(debugfile);
+ assert(ws);
+
+ ws->writeString(Common::String::format("%s (", function));
+ if (format && strlen(format) > 0) {
+ va_list va;
+
+ va_start(va, format);
+ Common::String data = Common::String::vformat(format, va);
+ ws->writeString(data);
+ va_end(va);
+ }
+
+ ws->writeString(")\n");
+ }
+}
+
+
+/*---------------------------------------------------------------------*/
+/* Functions not ported - functionally unchanged from os_none.c */
+/*---------------------------------------------------------------------*/
+
+/*
+ * agt_tone()
+ *
+ * Produce a hz-Hertz sound for ms milliseconds.
+ */
+void agt_tone(int hz, int ms) {
+ gagt_debug("agt_tone", "hz=%d, ms=%d", hz, ms);
+}
+
+
+/*
+ * agt_rand()
+ *
+ * Return random number from a to b inclusive. The random number generator
+ * is seeded on the first call, to a reproducible sequence if stable_random,
+ * otherwise using time().
+ */
+int agt_rand(int a, int b) {
+ int result;
+
+ result = a + g_vm->getRandomNumber(0x7fffffff) % (b - a + 1);
+ gagt_debug("agt_rand", "a=%d, b=%d -> %d", a, b, result);
+ return result;
+}
+
+
+/*---------------------------------------------------------------------*/
+/* Workrounds for bugs in core AGiliTy. */
+/*---------------------------------------------------------------------*/
+
+/*
+ * gagt_workround_menus()
+ *
+ * Somewhere in AGiliTy's menu handling stuff is a condition that sets up
+ * an eventual NULL dereference in rstrncpy(), called from num_name_func().
+ * For some reason, perhaps memory overruns, perhaps something else, it
+ * happens after a few turns have been made through agt_menu(). Replacing
+ * agt_menu() won't avoid it.
+ *
+ * However, the menu stuff isn't too useful, or attractive, in a game, so one
+ * solution is to simply disable it. While not possible to do this directly,
+ * there is a sneaky way, using our carnal knowledge of core AGiliTy. In
+ * runverb.c, there is code to prevent menu mode from being turned on where
+ * verbmenu is NULL. Verbmenu is set up in agil.c on loading the game, but,
+ * crucially, is set up before agil.c calls start_interface(). So... here
+ * we can free it, set it to NULL, set menu_mode to 0 (it probably is already)
+ * and AGiliTy behaves as if the game prevents menu mode.
+ */
+static void gagt_workround_menus() {
+ free(verbmenu);
+ verbmenu = NULL;
+
+ menu_mode = 0;
+}
+
+
+/*
+ * gagt_workround_fileexist()
+ *
+ * This function verifies that the game file can be opened, in effect second-
+ * guessing run_game().
+ *
+ * AGiliTy's fileexist() has in it either a bug, or a misfeature. It always
+ * passes a nofix value of 1 into try_open_file(), which defeats the code to
+ * retry with both upper and lower cased filenames. So here we have to go
+ * round the houses, with readopen()/readclose().
+ */
+static int gagt_workround_fileexist(fc_type fc, filetype ft) {
+ genfile file;
+ const char *errstr;
+
+ errstr = NULL;
+ file = readopen(fc, ft, &errstr);
+
+ if (file) {
+ readclose(file);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+/*---------------------------------------------------------------------*/
+/* I/O interface start and stop functions. */
+/*---------------------------------------------------------------------*/
+
+/* AGiliTy font_status values that indicate what font may be used. */
+enum {
+ GAGT_FIXED_REQUIRED = 1, GAGT_PROPORTIONAL_OKAY = 2
+};
+
+
+/*
+ * start_interface()
+ * close_interface()
+ *
+ * Startup and shutdown callout points. The start function for Glk looks
+ * at the value of font_status that the game sets, to see if it has a strong
+ * view of the font to use. If it does, then we'll reflect that in the
+ * module's font contol, perhaps overriding any command line options that the
+ * user has passed in.
+ */
+void start_interface(fc_type fc) {
+ switch (font_status) {
+ case GAGT_FIXED_REQUIRED:
+ gagt_font_mode = FONT_FIXED_WIDTH;
+ break;
+
+ case GAGT_PROPORTIONAL_OKAY:
+ gagt_font_mode = FONT_PROPORTIONAL;
+ break;
+
+ default:
+ break;
+ }
+
+ gagt_workround_menus();
+
+ gagt_debug("start_interface", "fc=%p", fc);
+}
+
+void close_interface() {
+ if (filevalid(scriptfile, fSCR))
+ close_pfile(scriptfile, 0);
+
+ gagt_debug("close_interface", "");
+}
+
+
+/*---------------------------------------------------------------------*/
+/* Code page 437 to ISO 8859 Latin-1 translations */
+/*---------------------------------------------------------------------*/
+
+/*
+ * AGiliTy uses IBM code page 437 characters, and Glk works in ISO 8859
+ * Latin-1. There's some good news, in that a number of the characters,
+ * especially international ones, in these two sets are the same. The bad
+ * news is that, for codes above 127 (that is, beyond 7-bit ASCII), or for
+ * codes below 32, they live in different places. So, here is a table of
+ * conversions for codes not equivalent to 7-bit ASCII, and a pair of
+ * conversion routines.
+ *
+ * Note that some code page 437 characters don't have ISO 8859 Latin-1
+ * equivalents. Predominantly, these are the box-drawing characters, which
+ * is a pity, because these are the ones that are used the most. Anyway,
+ * in these cases, the table substitutes an approximated base ASCII char-
+ * acter in its place.
+ *
+ * The first entry of table comments below is the character's UNICODE value,
+ * just in case it's useful at some future date.
+ */
+typedef const struct {
+ const unsigned char cp437; /* Code page 437 character. */
+ const unsigned char iso8859_1; /* ISO 8859 Latin-1 character. */
+} gagt_char_t;
+typedef gagt_char_t *gagt_charref_t;
+
+static gagt_char_t GAGT_CHAR_TABLE[] = {
+ /*
+ * Low characters -- those below 0x20. These are the really odd code
+ * page 437 characters, rarely used by AGT games. Low characters are
+ * omitted from the reverse lookup, and participate only in the forwards
+ * lookup from code page 437 to ISO 8859 Latin-1.
+ */
+ {0x01, '@'}, /* 263a White smiling face */
+ {0x02, '@'}, /* 263b Black smiling face */
+ {0x03, '?'}, /* 2665 Black heart suit */
+ {0x04, '?'}, /* 2666 Black diamond suit */
+ {0x05, '?'}, /* 2663 Black club suit */
+ {0x06, '?'}, /* 2660 Black spade suit */
+ {0x07, 0xb7}, /* 2022 Bullet */
+ {0x08, 0xb7}, /* 25d8 Inverse bullet */
+ {0x09, 0xb7}, /* 25e6 White bullet */
+ {0x0a, 0xb7}, /* 25d9 Inverse white circle */
+ {0x0b, '?'}, /* 2642 Male sign */
+ {0x0c, '?'}, /* 2640 Female sign */
+ {0x0d, '?'}, /* 266a Eighth note */
+ {0x0e, '?'}, /* 266b Beamed eighth notes */
+ {0x0f, 0xa4}, /* 263c White sun with rays */
+ {0x10, '>'}, /* 25b6 Black right-pointing triangle */
+ {0x11, '<'}, /* 25c0 Black left-pointing triangle */
+ {0x12, 0xa6}, /* 2195 Up down arrow */
+ {0x13, '!'}, /* 203c Double exclamation mark */
+ {0x14, 0xb6}, /* 00b6 Pilcrow sign */
+ {0x15, 0xa7}, /* 00a7 Section sign */
+ {0x16, '#'}, /* 25ac Black rectangle */
+ {0x17, 0xa6}, /* 21a8 Up down arrow with base */
+ {0x18, '^'}, /* 2191 Upwards arrow */
+ {0x19, 'v'}, /* 2193 Downwards arrow */
+ {0x1a, '>'}, /* 2192 Rightwards arrow */
+ {0x1b, '<'}, /* 2190 Leftwards arrow */
+ {0x1c, '?'}, /* 2310 Reversed not sign */
+ {0x1d, '-'}, /* 2194 Left right arrow */
+ {0x1e, '^'}, /* 25b2 Black up-pointing triangle */
+ {0x1f, 'v'}, /* 25bc Black down-pointing triangle */
+
+ /*
+ * High characters -- those above 0x7f. These are more often used by AGT
+ * games, particularly for box drawing.
+ */
+ {0x80, 0xc7}, /* 00c7 Latin capital letter c with cedilla */
+ {0x81, 0xfc}, /* 00fc Latin small letter u with diaeresis */
+ {0x82, 0xe9}, /* 00e9 Latin small letter e with acute */
+ {0x83, 0xe2}, /* 00e2 Latin small letter a with circumflex */
+ {0x84, 0xe4}, /* 00e4 Latin small letter a with diaeresis */
+ {0x85, 0xe0}, /* 00e0 Latin small letter a with grave */
+ {0x86, 0xe5}, /* 00e5 Latin small letter a with ring above */
+ {0x87, 0xe7}, /* 00e7 Latin small letter c with cedilla */
+ {0x88, 0xea}, /* 00ea Latin small letter e with circumflex */
+ {0x89, 0xeb}, /* 00eb Latin small letter e with diaeresis */
+ {0x8a, 0xe8}, /* 00e8 Latin small letter e with grave */
+ {0x8b, 0xef}, /* 00ef Latin small letter i with diaeresis */
+ {0x8c, 0xee}, /* 00ee Latin small letter i with circumflex */
+ {0x8d, 0xec}, /* 00ec Latin small letter i with grave */
+ {0x8e, 0xc4}, /* 00c4 Latin capital letter a with diaeresis */
+ {0x8f, 0xc5}, /* 00c5 Latin capital letter a with ring above */
+ {0x90, 0xc9}, /* 00c9 Latin capital letter e with acute */
+ {0x91, 0xe6}, /* 00e6 Latin small ligature ae */
+ {0x92, 0xc6}, /* 00c6 Latin capital ligature ae */
+ {0x93, 0xf4}, /* 00f4 Latin small letter o with circumflex */
+ {0x94, 0xf6}, /* 00f6 Latin small letter o with diaeresis */
+ {0x95, 0xf2}, /* 00f2 Latin small letter o with grave */
+ {0x96, 0xfb}, /* 00fb Latin small letter u with circumflex */
+ {0x97, 0xf9}, /* 00f9 Latin small letter u with grave */
+ {0x98, 0xff}, /* 00ff Latin small letter y with diaeresis */
+ {0x99, 0xd6}, /* 00d6 Latin capital letter o with diaeresis */
+ {0x9a, 0xdc}, /* 00dc Latin capital letter u with diaeresis */
+ {0x9b, 0xa2}, /* 00a2 Cent sign */
+ {0x9c, 0xa3}, /* 00a3 Pound sign */
+ {0x9d, 0xa5}, /* 00a5 Yen sign */
+ {0x9e, 'p'}, /* 20a7 Peseta sign */
+ {0x9f, 'f'}, /* 0192 Latin small letter f with hook */
+ {0xa0, 0xe1}, /* 00e1 Latin small letter a with acute */
+ {0xa1, 0xed}, /* 00ed Latin small letter i with acute */
+ {0xa2, 0xf3}, /* 00f3 Latin small letter o with acute */
+ {0xa3, 0xfa}, /* 00fa Latin small letter u with acute */
+ {0xa4, 0xf1}, /* 00f1 Latin small letter n with tilde */
+ {0xa5, 0xd1}, /* 00d1 Latin capital letter n with tilde */
+ {0xa6, 0xaa}, /* 00aa Feminine ordinal indicator */
+ {0xa7, 0xba}, /* 00ba Masculine ordinal indicator */
+ {0xa8, 0xbf}, /* 00bf Inverted question mark */
+ {0xa9, '.'}, /* 2310 Reversed not sign */
+ {0xaa, 0xac}, /* 00ac Not sign */
+ {0xab, 0xbd}, /* 00bd Vulgar fraction one half */
+ {0xac, 0xbc}, /* 00bc Vulgar fraction one quarter */
+ {0xad, 0xa1}, /* 00a1 Inverted exclamation mark */
+ {0xae, 0xab}, /* 00ab Left-pointing double angle quotation mark */
+ {0xaf, 0xbb}, /* 00bb Right-pointing double angle quotation mark */
+ {0xb0, '#'}, /* 2591 Light shade */
+ {0xb1, '#'}, /* 2592 Medium shade */
+ {0xb2, '#'}, /* 2593 Dark shade */
+ {0xb3, '|'}, /* 2502 Box light vertical */
+ {0xb4, '+'}, /* 2524 Box light vertical and left */
+ {0xb5, '+'}, /* 2561 Box vertical single and left double */
+ {0xb6, '|'}, /* 2562 Box vertical double and left single */
+ {0xb7, '+'}, /* 2556 Box down double and left single */
+ {0xb8, '+'}, /* 2555 Box down single and left double */
+ {0xb9, '+'}, /* 2563 Box double vertical and left */
+ {0xba, '|'}, /* 2551 Box double vertical */
+ {0xbb, '\\'}, /* 2557 Box double down and left */
+ {0xbc, '/'}, /* 255d Box double up and left */
+ {0xbd, '+'}, /* 255c Box up double and left single */
+ {0xbe, '+'}, /* 255b Box up single and left double */
+ {0xbf, '\\'}, /* 2510 Box light down and left */
+ {0xc0, '\\'}, /* 2514 Box light up and right */
+ {0xc1, '+'}, /* 2534 Box light up and horizontal */
+ {0xc2, '+'}, /* 252c Box light down and horizontal */
+ {0xc3, '+'}, /* 251c Box light vertical and right */
+ {0xc4, '-'}, /* 2500 Box light horizontal */
+ {0xc5, '+'}, /* 253c Box light vertical and horizontal */
+ {0xc6, '|'}, /* 255e Box vertical single and right double */
+ {0xc7, '|'}, /* 255f Box vertical double and right single */
+ {0xc8, '\\'}, /* 255a Box double up and right */
+ {0xc9, '/'}, /* 2554 Box double down and right */
+ {0xca, '+'}, /* 2569 Box double up and horizontal */
+ {0xcb, '+'}, /* 2566 Box double down and horizontal */
+ {0xcc, '+'}, /* 2560 Box double vertical and right */
+ {0xcd, '='}, /* 2550 Box double horizontal */
+ {0xce, '+'}, /* 256c Box double vertical and horizontal */
+ {0xcf, '='}, /* 2567 Box up single and horizontal double */
+ {0xd0, '+'}, /* 2568 Box up double and horizontal single */
+ {0xd1, '='}, /* 2564 Box down single and horizontal double */
+ {0xd2, '+'}, /* 2565 Box down double and horizontal single */
+ {0xd3, '+'}, /* 2559 Box up double and right single */
+ {0xd4, '+'}, /* 2558 Box up single and right double */
+ {0xd5, '+'}, /* 2552 Box down single and right double */
+ {0xd6, '+'}, /* 2553 Box down double and right single */
+ {0xd7, '+'}, /* 256b Box vertical double and horizontal single */
+ {0xd8, '+'}, /* 256a Box vertical single and horizontal double */
+ {0xd9, '/'}, /* 2518 Box light up and left */
+ {0xda, '/'}, /* 250c Box light down and right */
+ {0xdb, '@'}, /* 2588 Full block */
+ {0xdc, '@'}, /* 2584 Lower half block */
+ {0xdd, '@'}, /* 258c Left half block */
+ {0xde, '@'}, /* 2590 Right half block */
+ {0xdf, '@'}, /* 2580 Upper half block */
+ {0xe0, 'a'}, /* 03b1 Greek small letter alpha */
+ {0xe1, 0xdf}, /* 00df Latin small letter sharp s */
+ {0xe2, 'G'}, /* 0393 Greek capital letter gamma */
+ {0xe3, 'p'}, /* 03c0 Greek small letter pi */
+ {0xe4, 'S'}, /* 03a3 Greek capital letter sigma */
+ {0xe5, 's'}, /* 03c3 Greek small letter sigma */
+ {0xe6, 0xb5}, /* 00b5 Micro sign */
+ {0xe7, 't'}, /* 03c4 Greek small letter tau */
+ {0xe8, 'F'}, /* 03a6 Greek capital letter phi */
+ {0xe9, 'T'}, /* 0398 Greek capital letter theta */
+ {0xea, 'O'}, /* 03a9 Greek capital letter omega */
+ {0xeb, 'd'}, /* 03b4 Greek small letter delta */
+ {0xec, '.'}, /* 221e Infinity */
+ {0xed, 'f'}, /* 03c6 Greek small letter phi */
+ {0xee, 'e'}, /* 03b5 Greek small letter epsilon */
+ {0xef, '^'}, /* 2229 Intersection */
+ {0xf0, '='}, /* 2261 Identical to */
+ {0xf1, 0xb1}, /* 00b1 Plus-minus sign */
+ {0xf2, '>'}, /* 2265 Greater-than or equal to */
+ {0xf3, '<'}, /* 2264 Less-than or equal to */
+ {0xf4, 'f'}, /* 2320 Top half integral */
+ {0xf5, 'j'}, /* 2321 Bottom half integral */
+ {0xf6, 0xf7}, /* 00f7 Division sign */
+ {0xf7, '='}, /* 2248 Almost equal to */
+ {0xf8, 0xb0}, /* 00b0 Degree sign */
+ {0xf9, 0xb7}, /* 2219 Bullet operator */
+ {0xfa, 0xb7}, /* 00b7 Middle dot */
+ {0xfb, '/'}, /* 221a Square root */
+ {0xfc, 'n'}, /* 207f Superscript latin small letter n */
+ {0xfd, 0xb2}, /* 00b2 Superscript two */
+ {0xfe, '#'}, /* 25a0 Black square */
+ {0xff, 0xa0}, /* 00a0 No-break space */
+ {0, 0} /* 0000 [END OF TABLE] */
+};
+
+
+/*
+ * gagt_cp_to_iso()
+ *
+ * Convert a string from code page 437 into ISO 8859 Latin-1. The input and
+ * output buffers may be one and the same.
+ */
+static void gagt_cp_to_iso(const unsigned char *from_string, unsigned char *to_string) {
+ static int is_initialized = FALSE;
+ static unsigned char table[BYTE_MAX_VAL + 1];
+
+ int index;
+ unsigned char cp437, iso8859_1;
+ assert(from_string && to_string);
+
+ if (!is_initialized) {
+ gagt_charref_t entry;
+
+ /*
+ * Create a lookup entry for each code in the main table. Fill in gaps
+ * for 7-bit characters with their ASCII equivalent values. Any
+ * remaining codes not represented in the main table will map to zeroes
+ * in the lookup table, as static variables are initialized to zero.
+ */
+ for (entry = GAGT_CHAR_TABLE; entry->cp437; entry++) {
+ cp437 = entry->cp437;
+ iso8859_1 = entry->iso8859_1;
+
+// assert(cp437 < 0x20 || (cp437 > INT8_MAX_VAL && cp437 <= BYTE_MAX_VAL));
+ table[cp437] = iso8859_1;
+ }
+ for (index = 0; index <= INT8_MAX_VAL; index++) {
+ if (table[index] == 0)
+ table[index] = index;
+ }
+
+ is_initialized = TRUE;
+ }
+
+ for (index = 0; from_string[index] != '\0'; index++) {
+ cp437 = from_string[index];
+ iso8859_1 = table[cp437];
+
+ to_string[index] = iso8859_1 ? iso8859_1 : cp437;
+ }
+
+ to_string[index] = '\0';
+}
+
+
+/*
+ * gagt_iso_to_cp()
+ *
+ * Convert a string from ISO 8859 Latin-1 to code page 437. The input and
+ * output buffers may be one and the same.
+ */
+static void gagt_iso_to_cp(const unsigned char *from_string, unsigned char *to_string) {
+ static int is_initialized = FALSE;
+ static unsigned char table[BYTE_MAX_VAL + 1];
+
+ int index;
+ unsigned char iso8859_1, cp437;
+ assert(from_string && to_string);
+
+ if (!is_initialized) {
+ gagt_charref_t entry;
+
+ /*
+ * Create a reverse lookup entry for each code in the main table,
+ * overriding all of the low table entries (that is, anything under
+ * 128) with their ASCII no matter what the table contained.
+ *
+ * Any codes not represented in the main table will map to zeroes in
+ * the reverse lookup table, since static variables are initialized to
+ * zero. The first 128 characters are equivalent to ASCII. Moreover,
+ * some ISO 8859 Latin-1 entries are faked as base ASCII; where an
+ * entry is already occupied, the main table entry is skipped, so the
+ * match, which is n:1 in the reverse direction, works in first-found
+ * mode.
+ */
+ for (entry = GAGT_CHAR_TABLE; entry->iso8859_1; entry++) {
+ cp437 = entry->cp437;
+ iso8859_1 = entry->iso8859_1;
+
+ if (table[iso8859_1] == 0)
+ table[iso8859_1] = cp437;
+ }
+ for (index = 0; index <= INT8_MAX_VAL; index++)
+ table[index] = index;
+
+ is_initialized = TRUE;
+ }
+
+ for (index = 0; from_string[index] != '\0'; index++) {
+ iso8859_1 = from_string[index];
+ cp437 = table[iso8859_1];
+
+ to_string[index] = cp437 ? cp437 : iso8859_1;
+ }
+
+ to_string[index] = '\0';
+}
+
+
+/*---------------------------------------------------------------------*/
+/* Glk port status line functions */
+/*---------------------------------------------------------------------*/
+
+/*
+ * Buffered copy of the latest status line passed in by the interpreter.
+ * Buffering it means it's readily available to print for Glk libraries
+ * that don't support separate windows. We also need a copy of the last
+ * status buffer printed for non-windowing Glk libraries, for comparison.
+ */
+static char *gagt_status_buffer = NULL,
+ *gagt_status_buffer_printed = NULL;
+
+/*
+ * Indication that we are in mid-delay. The delay is silent, and can look
+ * kind of confusing, so to try to make it less so, we'll have the status
+ * window show something about it.
+ */
+static int gagt_inside_delay = FALSE;
+
+
+/*
+ * agt_statline()
+ *
+ * This function is called from our call to print_statline(). Here we'll
+ * convert the string and buffer in an allocated area for later use.
+ */
+void agt_statline(const char *cp_string) {
+ assert(cp_string);
+
+ free(gagt_status_buffer);
+ gagt_status_buffer = (char *)gagt_malloc(strlen(cp_string) + 1);
+ gagt_cp_to_iso((const unsigned char *)cp_string, (unsigned char *)gagt_status_buffer);
+
+ gagt_debug("agt_statline", "string='%s'", cp_string);
+}
+
+
+/*
+ * gagt_status_update_extended()
+ *
+ * Helper for gagt_status_update() and gagt_status_in_delay(). This function
+ * displays the second line of any extended status display, giving a list of
+ * exits from the compass rose, and if in an AGT delay, a waiting indicator.
+ */
+static void gagt_status_update_extended() {
+ uint width, height;
+ assert(gagt_status_window);
+
+ g_vm->glk_window_get_size(gagt_status_window, &width, &height);
+ if (height > 1) {
+ uint32 index;
+ int exit;
+
+ /* Clear the second status line only. */
+ g_vm->glk_window_move_cursor(gagt_status_window, 0, 1);
+ g_vm->glk_set_window(gagt_status_window);
+ g_vm->glk_set_style(style_User1);
+ for (index = 0; index < width; index++)
+ g_vm->glk_put_char(' ');
+
+ /*
+ * Check bits in the compass rose, and print out exit names from
+ * the exitname[] array.
+ */
+ g_vm->glk_window_move_cursor(gagt_status_window, 0, 1);
+ g_vm->glk_put_string(" Exits: ");
+ for (exit = 0; exit < (int)sizeof(exitname) / (int)sizeof(exitname[0]); exit++) {
+ if (compass_rose & (1 << exit)) {
+ g_vm->glk_put_string(exitname[exit]);
+ g_vm->glk_put_char(' ');
+ }
+ }
+
+ /* If the delay flag is set, print a waiting indicator at the right. */
+ if (gagt_inside_delay) {
+ g_vm->glk_window_move_cursor(gagt_status_window,
+ width - strlen("Waiting... "), 1);
+ g_vm->glk_put_string("Waiting... ");
+ }
+
+ g_vm->glk_set_window(gagt_main_window);
+ }
+}
+
+
+/*
+ * gagt_status_update()
+ *
+ *
+ * This function calls print_statline() to prompt the interpreter into calling
+ * our agt_statline(), then if we have a status window, displays the status
+ * string, and calls gagt_status_update_extended() if necessary to handle the
+ * second status line. If we don't see a call to our agt_statline, we output
+ * a default status string.
+ */
+static void gagt_status_update() {
+ uint width, height;
+ uint32 index;
+ assert(gagt_status_window);
+
+ g_vm->glk_window_get_size(gagt_status_window, &width, &height);
+ if (height > 0) {
+ g_vm->glk_window_clear(gagt_status_window);
+ g_vm->glk_window_move_cursor(gagt_status_window, 0, 0);
+ g_vm->glk_set_window(gagt_status_window);
+
+ g_vm->glk_set_style(style_User1);
+ for (index = 0; index < width; index++)
+ g_vm->glk_put_char(' ');
+ g_vm->glk_window_move_cursor(gagt_status_window, 0, 0);
+
+ /* Call print_statline() to refresh status line buffer contents. */
+ print_statline();
+
+ /* See if we have a buffered status line available. */
+ if (gagt_status_buffer) {
+ glui32 print_width;
+
+ /*
+ * Print the basic buffered status string, truncating to the
+ * current status window width if necessary, then try adding a
+ * second line if extended status enabled.
+ */
+ print_width = width < strlen(gagt_status_buffer)
+ ? width : strlen(gagt_status_buffer);
+ g_vm->glk_put_buffer(gagt_status_buffer, print_width);
+
+ if (gagt_extended_status_enabled)
+ gagt_status_update_extended();
+ } else {
+ /*
+ * We don't (yet) have a status line. Perhaps we're at the
+ * very start of a game. Print a standard message.
+ */
+ g_vm->glk_put_string("Glk AGiliTy version 1.1.1.1");
+ }
+
+ g_vm->glk_set_window(gagt_main_window);
+ }
+}
+
+
+/*
+ * gagt_status_print()
+ *
+ * Print the current contents of the completed status line buffer out in the
+ * main window, if it has changed since the last call. This is for non-
+ * windowing Glk libraries.
+ *
+ * Like gagt_status_update(), this function calls print_statline() to prompt
+ * the interpreter into calling our agt_statline(), then if we have a new
+ * status line, it prints it.
+ */
+static void gagt_status_print() {
+ /* Call print_statline() to refresh status line buffer contents. */
+ print_statline();
+
+ /*
+ * Do no more if there is no status line to print, or if the status
+ * line hasn't changed since last printed.
+ */
+ if (!gagt_status_buffer
+ || (gagt_status_buffer_printed
+ && strcmp(gagt_status_buffer, gagt_status_buffer_printed) == 0))
+ return;
+
+ /* Set fixed width font to try to preserve status line formatting. */
+ g_vm->glk_set_style(style_Preformatted);
+
+ /*
+ * Bracket, and output the status line buffer. We don't need to put any
+ * spacing after the opening bracket or before the closing one, because
+ * AGiliTy puts leading/trailing spaces on its status lines.
+ */
+ g_vm->glk_put_string("[");
+ g_vm->glk_put_string(gagt_status_buffer);
+ g_vm->glk_put_string("]\n");
+
+ /* Save the details of the printed status buffer. */
+ free(gagt_status_buffer_printed);
+ gagt_status_buffer_printed = (char *)gagt_malloc(strlen(gagt_status_buffer) + 1);
+ strcpy(gagt_status_buffer_printed, gagt_status_buffer);
+}
+
+
+/*
+ * gagt_status_notify()
+ *
+ * Front end function for updating status. Either updates the status window
+ * or prints the status line to the main window.
+ *
+ * Functions interested in updating the status line should call either this
+ * function, or gagt_status_redraw(), and not print_statline().
+ */
+static void gagt_status_notify() {
+ if (!BATCH_MODE) {
+ if (gagt_status_window)
+ gagt_status_update();
+ else
+ gagt_status_print();
+ }
+}
+
+
+/*
+ * gagt_status_redraw()
+ *
+ * Redraw the contents of any status window with the buffered status string.
+ * This function handles window sizing, and updates the interpreter with
+ * status_width, so may, and should, be called on resize and arrange events.
+ *
+ * Functions interested in updating the status line should call either this
+ * function, or gagt_status_notify(), and not print_statline().
+ */
+static void gagt_status_redraw() {
+ if (!BATCH_MODE) {
+ if (gagt_status_window) {
+ uint width, height;
+ winid_t parent;
+
+ /*
+ * Measure the status window, and update the interpreter's
+ * status_width variable.
+ */
+ g_vm->glk_window_get_size(gagt_status_window, &width, &height);
+ status_width = width;
+
+ /*
+ * Rearrange the status window, without changing its actual
+ * arrangement in any way. This is a hack to work round
+ * incorrect window repainting in Xglk; it forces a complete
+ * repaint of affected windows on Glk window resize and
+ * arrange events, and works in part because Xglk doesn't
+ * check for actual arrangement changes in any way before
+ * invalidating its windows. The hack should be harmless to
+ * Glk libraries other than Xglk, moreover, we're careful to
+ * activate it only on resize and arrange events.
+ */
+ parent = g_vm->glk_window_get_parent(gagt_status_window);
+ g_vm->glk_window_set_arrangement(parent,
+ winmethod_Above | winmethod_Fixed,
+ height, NULL);
+
+ gagt_status_update();
+ }
+ }
+}
+
+
+/*
+ * gagt_status_in_delay()
+ *
+ * Tells status line functions whether the game is delaying, or not. This
+ * function updates the extended status line, if present, automatically.
+ */
+static void gagt_status_in_delay(int inside_delay) {
+ if (!BATCH_MODE) {
+ /* Save the new delay status flag. */
+ gagt_inside_delay = inside_delay;
+
+ /*
+ * Update just the second line of the status window display, if
+ * extended status is being displayed.
+ */
+ if (gagt_status_window && gagt_extended_status_enabled)
+ gagt_status_update_extended();
+ }
+}
+
+
+/*
+ * gagt_status_cleanup()
+ *
+ * Free memory resources allocated by status line functions. Called on game
+ * end.
+ */
+static void gagt_status_cleanup() {
+ free(gagt_status_buffer);
+ gagt_status_buffer = NULL;
+
+ free(gagt_status_buffer_printed);
+ gagt_status_buffer_printed = NULL;
+}
+
+
+/*---------------------------------------------------------------------*/
+/* Glk port color and text attribute handling */
+/*---------------------------------------------------------------------*/
+
+/*
+ * AGT color and character attribute definitions. This is the range of
+ * values passed in to agt_textcolor().
+ */
+enum {
+ AGT_BLACK = 0,
+ AGT_BLUE = 1,
+ AGT_GREEN = 2,
+ AGT_CYAN = 3,
+ AGT_RED = 4,
+ AGT_MAGENTA = 5,
+ AGT_BROWN = 6,
+ AGT_NORMAL = 7,
+ AGT_BLINKING = 8,
+ AGT_WHITE = 9,
+ AGT_FIXED_FONT = 10,
+ AGT_VARIABLE_FONT = 11,
+ AGT_EMPHASIS = -1,
+ AGT_DE_EMPHASIS = -2
+};
+
+/*
+ * AGiliTy colors and text attributes seem a bit confused. Let's see if we
+ * can sort them out. Sadly, once we have, it's often not possible to
+ * render the full range in all Glk's anyway. Nevertheless...
+ */
+struct gagt_attrset_t {
+ int color; /* Text color. */
+ int blink; /* Text blinking flag. */
+ int fixed; /* Text fixed font flag. */
+ int emphasis; /* Text emphasized flag. */
+};
+
+/*
+ * Attributes as currently set by AGiliTy. The default values set up here
+ * correspond to AGT_NORMAL.
+ */
+static gagt_attrset_t gagt_current_attribute_set = { AGT_WHITE, FALSE,
+ FALSE, FALSE
+ };
+
+/*
+ * An extra flag to indicate if we have coerced fixed font override. On
+ * some occasions, we need to ensure that we get fixed font no matter what
+ * the game says.
+ */
+static int gagt_coerced_fixed = FALSE;
+
+/*
+ * Bit masks for packing colors and attributes. Normally, I don't like
+ * bit-twiddling all that much, but for packing all of the above into a
+ * single byte, that's what we need. Stuff color into the low four bits,
+ * convenient since color is from 0 to 9, then use three bits for the other
+ * attributes.
+ */
+static const unsigned char GAGT_COLOR_MASK = 0x0f,
+ GAGT_BLINK_MASK = 1 << 4,
+ GAGT_FIXED_MASK = 1 << 5,
+ GAGT_EMPHASIS_MASK = 1 << 6;
+
+/* Forward declaration of message function. */
+static void gagt_standout_string(const char *message);
+
+
+/*
+ * agt_textcolor()
+ *
+ * The AGiliTy porting guide defines the use of this function as:
+ *
+ * Set text color to color #c, where the colors are as follows:
+ * 0=Black, 1=Blue, 2=Green, 3=Cyan,
+ * 4=Red, 5=Magenta, 6=Brown,
+ * 7=Normal("White"-- which may actually be some other color)
+ * This should turn off blinking, bold, color, etc. and restore
+ * the text mode to its default appearance.
+ * 8=Turn on blinking.
+ * 9= *Just* White (not neccessarily "normal" and no need to turn off
+ * blinking)
+ * 10=Turn on fixed pitch font.
+ * 11=Turn off fixed pitch font
+ * Also used to set other text attributes:
+ * -1=emphasized text, used (e.g.) for room titles
+ * -2=end emphasized text
+ *
+ * Here we try to make sense of all this. Given an argument, we'll try to
+ * update our separated color and text attributes flags to reflect the
+ * expected text rendering.
+ */
+void agt_textcolor(int color) {
+ switch (color) {
+ case AGT_BLACK:
+ case AGT_BLUE:
+ case AGT_GREEN:
+ case AGT_CYAN:
+ case AGT_RED:
+ case AGT_MAGENTA:
+ case AGT_BROWN:
+ case AGT_WHITE:
+ gagt_current_attribute_set.color = color;
+ break;
+
+ case AGT_NORMAL:
+ gagt_current_attribute_set.color = AGT_WHITE;
+ gagt_current_attribute_set.blink = FALSE;
+ gagt_current_attribute_set.fixed = FALSE;
+ gagt_current_attribute_set.emphasis = FALSE;
+ break;
+
+ case AGT_BLINKING:
+ gagt_current_attribute_set.blink = TRUE;
+ break;
+
+ case AGT_FIXED_FONT:
+ gagt_current_attribute_set.fixed = TRUE;
+ break;
+
+ case AGT_VARIABLE_FONT:
+ gagt_current_attribute_set.fixed = FALSE;
+ break;
+
+ case AGT_EMPHASIS:
+ gagt_current_attribute_set.emphasis = TRUE;
+ break;
+
+ case AGT_DE_EMPHASIS:
+ gagt_current_attribute_set.emphasis = FALSE;
+ break;
+
+ default:
+ gagt_fatal("GLK: Unknown color encountered");
+ gagt_exit();
+ }
+
+ gagt_debug("agt_textcolor", "color=% d -> %d%s%s%s",
+ color,
+ gagt_current_attribute_set.color,
+ gagt_current_attribute_set.blink ? " blink" : "",
+ gagt_current_attribute_set.fixed ? " fixed" : "",
+ gagt_current_attribute_set.emphasis ? " bold" : "");
+}
+
+
+/*
+ * gagt_coerce_fixed_font()
+ *
+ * This coerces, or relaxes, a fixed font setting. Used by box drawing, to
+ * ensure that we get a temporary fixed font setting for known differenti-
+ * ated parts of game output text. Pass in TRUE to coerce fixed font, and
+ * FALSE to relax it.
+ */
+static void gagt_coerce_fixed_font(int coerce) {
+ gagt_coerced_fixed = coerce;
+}
+
+
+/*
+ * gagt_pack_attributes()
+ *
+ * Pack a set of color and text rendering attributes into a single byte,
+ * and return it. This function is used so that a set of text attributes
+ * can be encoded into a byte array that parallels the output strings that
+ * we buffer from the interpreter.
+ */
+static unsigned char gagt_pack_attributes(const gagt_attrset_t *attribute_set, int coerced) {
+ unsigned char packed;
+ assert(attribute_set);
+
+ /* Set the initial result to be color; these are the low bits. */
+ assert((attribute_set->color & ~GAGT_COLOR_MASK) == 0);
+ packed = attribute_set->color;
+
+ /*
+ * Now OR in the text attributes settings, taking either the value for
+ * fixed or the coerced fixed font.
+ */
+ packed |= attribute_set->blink ? GAGT_BLINK_MASK : 0;
+ packed |= attribute_set->fixed || coerced ? GAGT_FIXED_MASK : 0;
+ packed |= attribute_set->emphasis ? GAGT_EMPHASIS_MASK : 0;
+
+ return packed;
+}
+
+
+/*
+ * gagt_unpack_attributes()
+ *
+ * Unpack a set of packed current color and text rendering attributes from a
+ * single byte, and return the result of unpacking. This reconstitutes the
+ * text attributes that were current at the time of packing.
+ */
+static void gagt_unpack_attributes(unsigned char packed, gagt_attrset_t *attribute_set) {
+ assert(attribute_set);
+
+ attribute_set->color = packed & GAGT_COLOR_MASK;
+ attribute_set->blink = (packed & GAGT_BLINK_MASK) != 0;
+ attribute_set->fixed = (packed & GAGT_FIXED_MASK) != 0;
+ attribute_set->emphasis = (packed & GAGT_EMPHASIS_MASK) != 0;
+}
+
+
+/*
+ * gagt_pack_current_attributes()
+ *
+ * Pack the current color and text rendering attributes into a single byte,
+ * and return it.
+ */
+static unsigned char gagt_pack_current_attributes() {
+ return gagt_pack_attributes(&gagt_current_attribute_set, gagt_coerced_fixed);
+}
+
+
+/*
+ * gagt_init_user_styles()
+ *
+ * Attempt to set up two defined styles, User1 and User2, to represent
+ * fixed font with AGT emphasis (rendered as Glk subheader), and fixed font
+ * with AGT blink (rendered as Glk emphasis), respectively.
+ *
+ * The Glk stylehints here may not actually be honored by the Glk library.
+ * We'll try to detect this later on.
+ */
+static void gagt_init_user_styles() {
+ /*
+ * Set User1 to be fixed width, bold, and not italic. Here we're sort of
+ * assuming that the style starts life equal to Normal.
+ */
+ g_vm->glk_stylehint_set(wintype_TextBuffer, style_User1,
+ stylehint_Proportional, 0);
+ g_vm->glk_stylehint_set(wintype_TextBuffer, style_User1, stylehint_Weight, 1);
+ g_vm->glk_stylehint_set(wintype_TextBuffer, style_User1, stylehint_Oblique, 0);
+
+ /*
+ * Set User2 to be fixed width, normal, and italic, with the same
+ * assumptions.
+ */
+ g_vm->glk_stylehint_set(wintype_TextBuffer, style_User2,
+ stylehint_Proportional, 0);
+ g_vm->glk_stylehint_set(wintype_TextBuffer, style_User2, stylehint_Weight, 0);
+ g_vm->glk_stylehint_set(wintype_TextBuffer, style_User2, stylehint_Oblique, 1);
+}
+
+
+/*
+ * gagt_confirm_appearance()
+ *
+ * Attempt to find out if a Glk style's on screen appearance matches a given
+ * expectation. There's a chance (often 100% with current Xglk) that we
+ * can't tell, in which case we'll play safe, and say that it doesn't (our
+ * caller is hoping it does).
+ *
+ * That is, when we return FALSE, we mean either it's not as expected, or we
+ * don't know.
+ */
+static int gagt_confirm_appearance(glui32 style, glui32 stylehint, glui32 expected) {
+ uint result;
+
+ if (g_vm->glk_style_measure(gagt_main_window, style, stylehint, &result)) {
+ /*
+ * Measurement succeeded, so return TRUE if the result matches the
+ * caller's expectation.
+ */
+ if (result == expected)
+ return TRUE;
+ }
+
+ /* No straight answer, or the style's stylehint failed to match. */
+ return FALSE;
+}
+
+
+/*
+ * gagt_is_style_fixed()
+ * gagt_is_style_bold()
+ * gagt_is_style_oblique()
+ *
+ * Convenience functions for gagt_select_style(). A return of TRUE indicates
+ * that the style has this attribute; FALSE indicates either that it hasn't,
+ * or that it's not determinable.
+ */
+static int gagt_is_style_fixed(glui32 style) {
+ return gagt_confirm_appearance(style, stylehint_Proportional, 0);
+}
+
+static int gagt_is_style_bold(glui32 style) {
+ return gagt_confirm_appearance(style, stylehint_Weight, 1);
+}
+
+static int gagt_is_style_oblique(glui32 style) {
+ return gagt_confirm_appearance(style, stylehint_Oblique, 1);
+}
+
+
+/*
+ * gagt_select_style()
+ *
+ * Given a set of AGT text attributes, this function returns a Glk style that
+ * is suitable (or more accurately, the best we can come up with) for render-
+ * ing this set of attributes.
+ *
+ * For now, we ignore color totally, and just concentrate on the other attr-
+ * ibutes. This is because few, if any, games use color (no Photopia here),
+ * few Glk libraries, at least on Linux, allow fine grained control over text
+ * color, and even if you can get it, the scarcity of user-defined styles in
+ * Glk makes it too painful to contemplate.
+ */
+static glui32 gagt_select_style(gagt_attrset_t *attribute_set) {
+ glui32 style;
+ assert(attribute_set);
+
+ /*
+ * Glk styles are mutually exclusive, so here we'll work here by making a
+ * precedence selection: AGT emphasis take precedence over AGT blinking,
+ * which itself takes precedence over normal text. Fortunately, few, if
+ * any, AGT games set both emphasis and blinking (not likely to be a
+ * pleasant combination).
+ *
+ * We'll try to map AGT emphasis to Glk Subheader, AGT blink to Glk
+ * Emphasized, and normal text to Glk Normal, with modifications to this
+ * for fixed width requests.
+ *
+ * First, then, see if emphasized text is requested in the attributes.
+ */
+ if (attribute_set->emphasis) {
+ /*
+ * Consider whether something requested a fixed width font or
+ * disallowed a proportional one.
+ *
+ * Glk Preformatted is boring, flat, and lifeless. It often offers no
+ * fine grained control over emphasis, and so on. So here we try to
+ * find something better. However, not all Glk libraries implement
+ * stylehints, so we need to try to be careful to ensure that we get a
+ * fixed width font, no matter what else we may miss out on.
+ */
+ if (attribute_set->fixed) {
+ /*
+ * To start off, we'll see if User1, the font we set up for fixed
+ * width bold, really is fixed width and bold. If it is, we'll
+ * use it.
+ *
+ * If it isn't, we'll check Subheader. Our Glk library probably
+ * isn't implementing stylehints, but if Subheader is fixed width,
+ * it may provide a better look than Preformatted -- certainly
+ * it's worth a go.
+ *
+ * If Subheader isn't fixed width, we'll take another look at User1.
+ * It could be that the check for bold wasn't definitive, but it
+ * is nevertheless bold. So check for fixed width -- if set, it's
+ * probably good enough to use this font, certainly no worse than
+ * Preformatted.
+ *
+ * If Subheader isn't guaranteed fixed width, nor is User1, we're
+ * cornered into Preformatted.
+ */
+ if (gagt_is_style_fixed(style_User1)
+ && gagt_is_style_bold(style_User1))
+ style = style_User1;
+
+ else if (gagt_is_style_fixed(style_Subheader))
+ style = style_Subheader;
+
+ else if (gagt_is_style_fixed(style_User1))
+ style = style_User1;
+
+ else
+ style = style_Preformatted;
+ } else
+ /* This is the easy case, use Subheader. */
+ style = style_Subheader;
+ } else if (attribute_set->blink) {
+ /*
+ * Again, consider whether something requested a fixed width
+ * font or disallowed a proportional one.
+ */
+ if (attribute_set->fixed) {
+ /*
+ * As above, try to find something better than Preformatted, first
+ * trying User2, then Emphasized, then User2 again, and finally
+ * settling for Preformatted if neither of these two looks any
+ * better.
+ */
+ if (gagt_is_style_fixed(style_User2)
+ && gagt_is_style_oblique(style_User2))
+ style = style_User2;
+
+ else if (gagt_is_style_fixed(style_Emphasized))
+ style = style_Emphasized;
+
+ else if (gagt_is_style_fixed(style_User2))
+ style = style_User2;
+
+ else
+ style = style_Preformatted;
+ } else
+ /* This is the easy case, use Emphasized. */
+ style = style_Emphasized;
+ } else {
+ /*
+ * There's no emphasis or blinking in the attributes. In this case,
+ * use Preformatted for fixed width, and Normal for text that can be
+ * rendered proportionally.
+ */
+ if (attribute_set->fixed)
+ style = style_Preformatted;
+ else
+ style = style_Normal;
+ }
+
+ return style;
+}
+
+
+/*---------------------------------------------------------------------*/
+/* Glk port output buffering functions */
+/*---------------------------------------------------------------------*/
+
+/*
+ * Buffering game output happens at two levels. The first level is a single
+ * line buffer, used to catch text sent to us with agt_puts(). In parallel
+ * with the text strings, we keep and buffer the game text attributes, as
+ * handed to agt_textcolor(), that are in effect at the time the string is
+ * handed to us, packed for brevity.
+ *
+ * As each line is completed, by a call to agt_newline(), this single line
+ * buffer is transferred to a main text page buffer. The main page buffer
+ * has places in it where we can assign paragraph, font hints, and perhaps
+ * other marker information to a line. Initially unset, they're filled in
+ * at the point where we need to display the buffer.
+ */
+
+/*
+ * Definition of font hints values. Font hints may be:
+ * o none, for lines not in a definite paragraph;
+ * o proportional, for lines that can probably be safely rendered in a
+ * proportional font (if the AGT game text attributes allow it) and
+ * where the newline may be replaced by a space;
+ * o proportional_newline, for lines that may be rendered using a
+ * proportional font, but where the newline looks like it matters;
+ * o proportional_newline_standout, for proportional_newline lines that
+ * are also standout (for spacing in display functions);
+ * o fixed_width, for tables and other text that looks like it is a
+ * candidate for fixed font output.
+ */
+typedef enum {
+ HINT_NONE,
+ HINT_PROPORTIONAL,
+ HINT_PROPORTIONAL_NEWLINE,
+ HINT_PROPORTIONAL_NEWLINE_STANDOUT,
+ HINT_FIXED_WIDTH
+} gagt_font_hint_t;
+
+/* Magic number used to ensure a pointer points to a page buffer line. */
+static const unsigned int GAGT_LINE_MAGIC = 0x5bc14482;
+
+/*
+ * Definition of a single line buffer. This is a growable string and a
+ * parallel growable attributes array. The string is buffered without any
+ * null terminator -- not needed since we retain length.
+ */
+typedef struct {
+ unsigned char *data; /* Buffered character data. */
+ unsigned char *attributes; /* Parallel character attributes, packed. */
+ int allocation; /* Bytes allocated to each of the above. */
+ int length; /* Amount of data actually buffered. */
+} gagt_string_t;
+typedef gagt_string_t *gagt_stringref_t;
+
+/*
+ * Definition of a page buffer entry. This is a structure that holds the
+ * the result of a single line buffer above, plus additional areas that
+ * describe line text positioning, a blank line flag, a paragraph pointer
+ * (NULL if not in a paragraph), and a font hint.
+ */
+typedef struct gagt_line_s *gagt_lineref_t;
+typedef struct gagt_paragraph_s *gagt_paragraphref_t;
+
+struct gagt_line_s {
+ unsigned int magic; /* Assertion check dog-tag. */
+
+ gagt_string_t buffer; /* Buffered line string data. */
+
+ int indent; /* Line indentation. */
+ int outdent; /* Trailing line whitespace. */
+ int real_length; /* Real line length. */
+ int is_blank; /* Line blank flag. */
+ int is_hyphenated; /* Line hyphenated flag. */
+
+ gagt_paragraphref_t paragraph; /* Paragraph containing the line. */
+ gagt_font_hint_t font_hint; /* Line's font hint. */
+
+ gagt_lineref_t next; /* List next element. */
+ gagt_lineref_t prior; /* List prior element. */
+};
+
+/*
+ * Definition of the actual page buffer. This is a doubly-linked list of
+ * lines, with a tail pointer to facilitate adding entries at the end.
+ */
+static gagt_lineref_t gagt_page_head = NULL,
+ gagt_page_tail = NULL;
+
+/*
+ * Definition of the current output line; this one is appended to on
+ * agt_puts(), and transferred into the page buffer on agt_newline().
+ */
+static gagt_string_t gagt_current_buffer = { NULL, NULL, 0, 0 };
+
+/*
+ * gagt_string_append()
+ * gagt_string_transfer()
+ * gagt_string_free()
+ *
+ * String append, move, and allocation free for string_t buffers.
+ */
+static void gagt_string_append(gagt_stringref_t buffer, const char *string,
+ unsigned char packed_attributes) {
+ int length, bytes;
+
+ /*
+ * Find the size we'll need from the line buffer to add this string,
+ * and grow buffer if necessary.
+ */
+ length = strlen(string);
+ for (bytes = buffer->allocation; bytes < buffer->length + length;)
+ bytes = bytes == 0 ? 1 : bytes << 1;
+
+ if (bytes > buffer->allocation) {
+ buffer->data = (uchar *)gagt_realloc(buffer->data, bytes);
+ buffer->attributes = (uchar *)gagt_realloc(buffer->attributes, bytes);
+
+ buffer->allocation = bytes;
+ }
+
+ /* Add string to the line buffer, and store packed text attributes. */
+ memcpy(buffer->data + buffer->length, string, length);
+ memset(buffer->attributes + buffer->length, packed_attributes, length);
+
+ buffer->length += length;
+}
+
+static void gagt_string_transfer(gagt_stringref_t from, gagt_stringref_t to) {
+ *to = *from;
+ from->data = from->attributes = NULL;
+ from->allocation = from->length = 0;
+}
+
+static void gagt_string_free(gagt_stringref_t buffer) {
+ free(buffer->data);
+ free(buffer->attributes);
+ buffer->data = buffer->attributes = NULL;
+ buffer->allocation = buffer->length = 0;
+}
+
+
+/*
+ * gagt_get_string_indent()
+ * gagt_get_string_outdent()
+ * gagt_get_string_real_length()
+ * gagt_is_string_blank()
+ * gagt_is_string_hyphenated()
+ *
+ * Metrics functions for string_t buffers.
+ */
+static int gagt_get_string_indent(const gagt_stringref_t buffer) {
+ int indent, index;
+
+ indent = 0;
+ for (index = 0;
+ index < buffer->length && isspace(buffer->data[index]);
+ index++)
+ indent++;
+
+ return indent;
+}
+
+static int gagt_get_string_outdent(const gagt_stringref_t buffer) {
+ int outdent, index;
+
+ outdent = 0;
+ for (index = buffer->length - 1;
+ index >= 0 && isspace(buffer->data[index]); index--)
+ outdent++;
+
+ return outdent;
+}
+
+
+static int gagt_get_string_real_length(const gagt_stringref_t buffer) {
+ int indent, outdent;
+
+ indent = gagt_get_string_indent(buffer);
+ outdent = gagt_get_string_outdent(buffer);
+
+ return indent == buffer->length ? 0 : buffer->length - indent - outdent;
+}
+
+static int gagt_is_string_blank(const gagt_stringref_t buffer) {
+ return gagt_get_string_indent(buffer) == buffer->length;
+}
+
+static int gagt_is_string_hyphenated(const gagt_stringref_t buffer) {
+ int is_hyphenated;
+
+ is_hyphenated = FALSE;
+
+ if (!gagt_is_string_blank(buffer)
+ && gagt_get_string_real_length(buffer) > 1) {
+ int last;
+
+ last = buffer->length - gagt_get_string_outdent(buffer) - 1;
+
+ if (buffer->data[last] == '-') {
+ if (isalpha(buffer->data[last - 1]))
+ is_hyphenated = TRUE;
+ }
+ }
+
+ return is_hyphenated;
+}
+
+
+/*
+ * gagt_output_delete()
+ *
+ * Delete all buffered page and line text. Free all malloc'ed buffer memory,
+ * and return the buffer variables to their initial values.
+ */
+static void gagt_output_delete() {
+ gagt_lineref_t line, next_line;
+
+ for (line = gagt_page_head; line; line = next_line) {
+ assert(line->magic == GAGT_LINE_MAGIC);
+ next_line = line->next;
+
+ gagt_string_free(&line->buffer);
+
+ memset(line, 0, sizeof(*line));
+ free(line);
+ }
+
+ gagt_page_head = gagt_page_tail = NULL;
+
+ gagt_string_free(&gagt_current_buffer);
+}
+
+
+/*
+ * agt_puts()
+ *
+ * Buffer the string passed in into our current single line buffer. The
+ * function converts to ISO 8859 Latin-1 encoding before buffering.
+ */
+void agt_puts(const char *cp_string) {
+ assert(cp_string);
+
+ if (!BATCH_MODE) {
+ char *iso_string;
+ unsigned char packed;
+ int length;
+
+ /* Update the apparent (virtual) window x position. */
+ length = strlen(cp_string);
+ curr_x += length;
+
+ /*
+ * Convert the buffer from IBM cp 437 to Glk's ISO 8859 Latin-1, and
+ * add string and packed text attributes to the current line buffer.
+ */
+ iso_string = (char *)gagt_malloc(length + 1);
+ gagt_cp_to_iso((const uchar *)cp_string, (uchar *)iso_string);
+ packed = gagt_pack_current_attributes();
+ gagt_string_append(&gagt_current_buffer, iso_string, packed);
+
+ /* Add the string to any script file. */
+ if (script_on)
+ textputs(scriptfile, iso_string);
+
+ free(iso_string);
+ gagt_debug("agt_puts", "string='%s'", cp_string);
+ }
+}
+
+
+/*
+ * agt_newline()
+ *
+ * Accept a newline to the main window. Our job here is to append the
+ * current line buffer to the page buffer, and clear the line buffer to
+ * begin accepting new text.
+ */
+void agt_newline() {
+ if (!BATCH_MODE) {
+ gagt_lineref_t line;
+
+ /* Update the apparent (virtual) window x position. */
+ curr_x = 0;
+
+ /* Create a new line entry for the page buffer. */
+ line = (gagt_lineref_t)gagt_malloc(sizeof(*line));
+ line->magic = GAGT_LINE_MAGIC;
+
+ /* Move the line from the line buffer into the page buffer. */
+ gagt_string_transfer(&gagt_current_buffer, &line->buffer);
+
+ /* Fill in the line buffer metrics. */
+ line->indent = gagt_get_string_indent(&line->buffer);
+ line->outdent = gagt_get_string_outdent(&line->buffer);
+ line->real_length = gagt_get_string_real_length(&line->buffer);
+ line->is_blank = gagt_is_string_blank(&line->buffer);
+ line->is_hyphenated = gagt_is_string_hyphenated(&line->buffer);
+
+ /* For now, default the remaining page buffer fields for the line. */
+ line->paragraph = NULL;
+ line->font_hint = HINT_NONE;
+
+ /* Add to the list, creating a new list if necessary. */
+ line->next = NULL;
+ line->prior = gagt_page_tail;
+ if (gagt_page_head)
+ gagt_page_tail->next = line;
+ else
+ gagt_page_head = line;
+ gagt_page_tail = line;
+
+ /* Add a newline to any script file. */
+ if (script_on)
+ textputs(scriptfile, "\n");
+
+ gagt_debug("agt_newline", "");
+ }
+}
+
+
+/*
+ * gagt_get_first_page_line()
+ * gagt_get_next_page_line()
+ * gagt_get_prior_page_line()
+ *
+ * Iterator functions for the page buffer. These functions return the first
+ * line from the page buffer, the next line, or the previous line, given a
+ * line, respectively. They return NULL if no lines, or no more lines, are
+ * available.
+ */
+static gagt_lineref_t gagt_get_first_page_line() {
+ gagt_lineref_t line;
+
+ line = gagt_page_head;
+ assert(!line || line->magic == GAGT_LINE_MAGIC);
+ return line;
+}
+
+static gagt_lineref_t gagt_get_next_page_line(const gagt_lineref_t line) {
+ gagt_lineref_t next_line;
+ assert(line && line->magic == GAGT_LINE_MAGIC);
+
+ next_line = line->next;
+ assert(!next_line || next_line->magic == GAGT_LINE_MAGIC);
+ return next_line;
+}
+
+static gagt_lineref_t gagt_get_prior_page_line(const gagt_lineref_t line) {
+ gagt_lineref_t prior_line;
+ assert(line && line->magic == GAGT_LINE_MAGIC);
+
+ prior_line = line->prior;
+ assert(!prior_line || prior_line->magic == GAGT_LINE_MAGIC);
+ return prior_line;
+}
+
+
+/*---------------------------------------------------------------------*/
+/* Glk port paragraphing functions and data */
+/*---------------------------------------------------------------------*/
+
+/* Magic number used to ensure a pointer points to a paragraph. */
+static const unsigned int GAGT_PARAGRAPH_MAGIC = 0xb9a2297b;
+
+/* Forward definition of special paragraph reference. */
+typedef const struct gagt_special_s *gagt_specialref_t;
+
+/*
+ * Definition of a paragraph entry. This is a structure that holds a
+ * pointer to the first line buffer in the paragraph.
+ */
+struct gagt_paragraph_s {
+ unsigned int magic; /* Assertion check dog-tag. */
+
+ gagt_lineref_t first_line; /* First line in the paragraph. */
+ gagt_specialref_t special; /* Special paragraph entry. */
+
+ int line_count; /* Number of lines in the paragraph. */
+ int id; /* Paragraph id, sequence, for debug only. */
+
+ gagt_paragraphref_t next; /* List next element. */
+ gagt_paragraphref_t prior; /* List prior element. */
+};
+
+/*
+ * A doubly-linked list of paragraphs, with a tail pointer to facilitate
+ * adding entries at the end.
+ */
+static gagt_paragraphref_t gagt_paragraphs_head = NULL,
+ gagt_paragraphs_tail = NULL;
+
+/*
+ * gagt_paragraphs_delete()
+ *
+ * Delete paragraphs held in the list. This function doesn't delete the
+ * page buffer lines, just the paragraphs describing the page.
+ */
+static void gagt_paragraphs_delete() {
+ gagt_paragraphref_t paragraph, next_paragraph;
+
+ for (paragraph = gagt_paragraphs_head; paragraph; paragraph = next_paragraph) {
+ assert(paragraph->magic == GAGT_PARAGRAPH_MAGIC);
+ next_paragraph = paragraph->next;
+
+ memset(paragraph, 0, sizeof(*paragraph));
+ free(paragraph);
+ }
+
+ gagt_paragraphs_head = gagt_paragraphs_tail = NULL;
+}
+
+
+/*
+ * gagt_find_paragraph_start()
+ *
+ * Find and return the next non-blank line in the page buffer, given a start
+ * point. Returns NULL if there are no more blank lines.
+ */
+static gagt_lineref_t gagt_find_paragraph_start(const gagt_lineref_t begin) {
+ gagt_lineref_t line, match;
+
+ /*
+ * Advance line to the beginning of the next paragraph, stopping on the
+ * first non-blank line, or at the end of the page buffer.
+ */
+ match = NULL;
+ for (line = begin; line; line = gagt_get_next_page_line(line)) {
+ if (!line->is_blank) {
+ match = line;
+ break;
+ }
+ }
+
+ return match;
+}
+
+
+/*
+ * gagt_find_block_end()
+ * gagt_find_blank_line_block_end()
+ *
+ * Find and return the apparent end of a paragraph from the page buffer,
+ * given a start point, and an indentation reference. The end is either
+ * the point where indentation returns to the reference indentation, or
+ * the next blank line.
+ *
+ * Indentation reference can be -1, indicating that only the next blank
+ * line will end the paragraph. Indentation references less than 1 are
+ * also ignored.
+ */
+static gagt_lineref_t gagt_find_block_end(const gagt_lineref_t begin, int indent) {
+ gagt_lineref_t line, match;
+
+ /*
+ * Initialize the match to be the start of the block, then advance line
+ * until we hit a blank line or the end of the page buffer. At this point,
+ * match contains the last line checked.
+ */
+ match = begin;
+ for (line = begin; line; line = gagt_get_next_page_line(line)) {
+ /*
+ * Found if we reach a blank line, or when given an indentation to
+ * check for, we find it.
+ */
+ if (line->is_blank || (indent > 0 && line->indent == indent))
+ break;
+
+ match = line;
+ }
+
+ return match;
+}
+
+static gagt_lineref_t gagt_find_blank_line_block_end(const gagt_lineref_t begin) {
+ return gagt_find_block_end(begin, -1);
+}
+
+
+/*
+ * gagt_find_paragraph_end()
+ *
+ * Find and return the apparent end of a paragraph from the page buffer,
+ * given a start point. The function attempts to recognize paragraphs by
+ * the "shape" of indentation.
+ */
+static gagt_lineref_t gagt_find_paragraph_end(const gagt_lineref_t first_line) {
+ gagt_lineref_t second_line;
+
+ /*
+ * If the start line is the last line in the buffer, or if the next line
+ * is a blank line, return the start line as also being the end of the
+ * paragraph.
+ */
+ second_line = gagt_get_next_page_line(first_line);
+ if (!second_line || second_line->is_blank) {
+ return first_line;
+ }
+
+ /*
+ * Time to look at line indentations...
+ *
+ * If either line is grossly indented, forget about trying to infer
+ * anything from this, and just break the paragraph on the next blank line.
+ */
+ if (first_line->indent > screen_width / 4
+ || second_line->indent > screen_width / 4) {
+ return gagt_find_blank_line_block_end(second_line);
+ }
+
+ /*
+ * If the first line is indented more than the second, end the paragraph
+ * on a blank line, or on a return in indentation to the level of the
+ * first line. Here we're looking for paragraphs with the shape
+ *
+ * aksjdj jfkasjd fjkasjd ajksdj fkaj djf akjsd fkjas dfs
+ * kasjdlkfjkj fj aksd jfjkasj dlkfja skjdk flaks dlf jalksdf
+ * ksjdf kjs kdf lasjd fkjalks jdfkjasjd flkjasl djfkasjfdkl
+ */
+ else if (first_line->indent > second_line->indent) {
+ return gagt_find_block_end(second_line, first_line->indent);
+ }
+
+ /*
+ * If the second line is more indented than the first, this may indicate
+ * a title line, followed by normal indented paragraphing. In this case,
+ * use the second line indentation as the reference, and begin searching
+ * at the next line. This finds
+ *
+ * ksjdkfjask ksadf
+ * kajskd fksjkfj jfkj jfkslaj fksjlfj jkjskjlfa j fjksal
+ * sjkkdjf sj fkjkajkdlfj lsjak dfjk djkfjskl dklf alks dfll
+ * fjksja jkj dksja kjdk kaj dskfj aksjdf aksjd kfjaks fjks
+ *
+ * and
+ *
+ * asdfj kjsdf kjs
+ * akjsdkj fkjs kdjfa lskjdl fjalsj dlfjksj kdj fjkd jlsjd
+ * jalksj jfk slj lkfjsa lkjd lfjlaks dlfkjals djkj alsjd
+ * kj jfksj fjksjl alkjs dlkjf lakjsd fkjas ldkj flkja fsd
+ */
+ else if (second_line->indent > first_line->indent) {
+ gagt_lineref_t third_line;
+
+ /*
+ * See if we have a third buffer line to look at. If we don't, or if
+ * we do but it's blank, the paragraph ends here.
+ */
+ third_line = gagt_get_next_page_line(second_line);
+ if (!third_line || third_line->is_blank) {
+ return second_line;
+ }
+
+ /* As above, give up on gross indentation. */
+ if (second_line->indent > screen_width / 4
+ || third_line->indent > screen_width / 4) {
+ return gagt_find_blank_line_block_end(third_line);
+ }
+
+ /*
+ * If the second line indentation exceeds the third, this is probably
+ * a paragraph with a title line. In this case, end the paragraph on
+ * a return to the indentation of the second line. If not, just find
+ * the next blank line.
+ */
+ else if (second_line->indent > third_line->indent) {
+ return gagt_find_block_end(third_line, second_line->indent);
+ } else {
+ return gagt_find_blank_line_block_end(third_line);
+ }
+ }
+
+ /*
+ * Otherwise, the first and second line indentations are the same, so
+ * break only on the next empty line. This finds the simple
+ *
+ * ksd kjal jdljf lakjsd lkj lakjsdl jfla jsldj lfaksdj fksj
+ * lskjd fja kjsdlk fjlakjs ldkjfksj lkjdf kjalskjd fkjklal
+ * skjd fkaj djfkjs dkfjal sjdlkfj alksjdf lkajs ldkjf alljjf
+ */
+ else {
+ assert(second_line->indent == first_line->indent);
+ return gagt_find_blank_line_block_end(second_line);
+ }
+}
+
+
+/*
+ * gagt_paragraph_page()
+ *
+ * This function breaks the page buffer into what appear to be paragraphs,
+ * based on observations of indentation and blank separator lines.
+ */
+static void gagt_paragraph_page() {
+ gagt_lineref_t start;
+
+ assert(!gagt_paragraphs_head && !gagt_paragraphs_tail);
+
+ /* Find the start of the first paragraph. */
+ start = gagt_find_paragraph_start(gagt_get_first_page_line());
+ while (start) {
+ gagt_paragraphref_t paragraph;
+ gagt_lineref_t end, line;
+
+ /* Create a new paragraph entry. */
+ paragraph = (gagt_paragraphref_t)gagt_malloc(sizeof(*paragraph));
+ paragraph->magic = GAGT_PARAGRAPH_MAGIC;
+ paragraph->first_line = start;
+ paragraph->special = NULL;
+ paragraph->line_count = 1;
+ paragraph->id = gagt_paragraphs_tail ? gagt_paragraphs_tail->id + 1 : 0;
+
+ /* Add to the list, creating a new list if necessary. */
+ paragraph->next = NULL;
+ paragraph->prior = gagt_paragraphs_tail;
+ if (gagt_paragraphs_head)
+ gagt_paragraphs_tail->next = paragraph;
+ else
+ gagt_paragraphs_head = paragraph;
+ gagt_paragraphs_tail = paragraph;
+
+ /* From the start, identify the paragraph end. */
+ end = gagt_find_paragraph_end(start);
+
+ /*
+ * Set paragraph in each line identified as part of this paragraph,
+ * and increment the paragraph's line count.
+ */
+ for (line = start;
+ line != end; line = gagt_get_next_page_line(line)) {
+ line->paragraph = paragraph;
+ paragraph->line_count++;
+ }
+ end->paragraph = paragraph;
+
+ /*
+ * If there's another line, look for the next paragraph there,
+ * otherwise we're done.
+ */
+ line = gagt_get_next_page_line(end);
+ if (line)
+ start = gagt_find_paragraph_start(line);
+ else
+ start = NULL;
+ }
+}
+
+
+/*
+ * gagt_get_first_paragraph()
+ * gagt_get_next_paragraph()
+ *
+ * Iterator functions for the paragraphs list.
+ */
+static gagt_paragraphref_t gagt_get_first_paragraph() {
+ gagt_paragraphref_t paragraph;
+
+ paragraph = gagt_paragraphs_head;
+ assert(!paragraph || paragraph->magic == GAGT_PARAGRAPH_MAGIC);
+ return paragraph;
+}
+
+static gagt_paragraphref_t gagt_get_next_paragraph(const gagt_paragraphref_t paragraph) {
+ gagt_paragraphref_t next_paragraph;
+ assert(paragraph && paragraph->magic == GAGT_PARAGRAPH_MAGIC);
+
+ next_paragraph = paragraph->next;
+ assert(!next_paragraph || next_paragraph->magic == GAGT_PARAGRAPH_MAGIC);
+ return next_paragraph;
+}
+
+
+/*
+ * gagt_get_first_paragraph_line()
+ * gagt_get_next_paragraph_line()
+ * gagt_get_prior_paragraph_line()
+ *
+ * Iterator functions for the page buffer. These functions implement a
+ * paragraph-based view of the page buffer.
+ *
+ * The functions find the first line of a given paragraph; given a line,
+ * the next line in the same paragraph, or NULL if line is the last para-
+ * graph line (or the last line in the page buffer); and given a line,
+ * the previous line in the same paragraph, or NULL if line is the first
+ * paragraph line (or the first line in the page buffer).
+ */
+static gagt_lineref_t gagt_get_first_paragraph_line(const gagt_paragraphref_t paragraph) {
+ assert(paragraph && paragraph->magic == GAGT_PARAGRAPH_MAGIC);
+
+ /* Return the first line for the requested paragraph. */
+ return paragraph->first_line;
+}
+
+static gagt_lineref_t gagt_get_next_paragraph_line(const gagt_lineref_t line) {
+ gagt_lineref_t next_line;
+
+ /* Get the next line; return it if the paragraph matches, else NULL. */
+ next_line = gagt_get_next_page_line(line);
+ if (next_line && next_line->paragraph == line->paragraph)
+ return next_line;
+ else
+ return NULL;
+}
+
+static gagt_lineref_t gagt_get_prior_paragraph_line(const gagt_lineref_t line) {
+ gagt_lineref_t prior_line;
+
+ /* Get the previous line; return it if the paragraph matches, else NULL. */
+ prior_line = gagt_get_prior_page_line(line);
+ if (prior_line && prior_line->paragraph == line->paragraph)
+ return prior_line;
+ else
+ return NULL;
+}
+
+
+/*
+ * gagt_get_paragraph_line_count()
+ *
+ * Return the count of lines contained in the paragraph.
+ */
+static int gagt_get_paragraph_line_count(const gagt_paragraphref_t paragraph) {
+ assert(paragraph && paragraph->magic == GAGT_PARAGRAPH_MAGIC);
+
+ return paragraph->line_count;
+}
+
+
+/*---------------------------------------------------------------------*/
+/* Glk port page buffer analysis functions */
+/*---------------------------------------------------------------------*/
+
+/*
+ * Threshold for consecutive punctuation/spaces before we decide that a line
+ * is in fact part of a table, and a small selection of characters to apply
+ * a somewhat larger threshold to when looking for punctuation (typically,
+ * characters that appear together multiple times in non-table text).
+ */
+static const int GAGT_THRESHOLD = 4,
+ GAGT_COMMON_THRESHOLD = 8;
+static const char *const GAGT_COMMON_PUNCTUATION = ".!?";
+
+
+/*
+ * gagt_line_is_standout()
+ *
+ * Return TRUE if a page buffer line appears to contain "standout" text.
+ * This is one of:
+ * - a line where all characters have some form of AGT text attribute
+ * set (blinking, fixed width font, or emphasis),
+ * - a line where each alphabetical character is uppercase.
+ * Typically, this describes room and other miscellaneous header lines.
+ */
+static int gagt_line_is_standout(const gagt_lineref_t line) {
+ int index, all_formatted, upper_count, lower_count;
+
+ /*
+ * Look at the line, for cases where all characters in it have AGT font
+ * attributes, and counting the upper and lower case characters. Iterate
+ * over only the significant characters in the string.
+ */
+ all_formatted = TRUE;
+ upper_count = lower_count = 0;
+ for (index = line->indent;
+ index < line->buffer.length - line->outdent; index++) {
+ gagt_attrset_t attribute_set;
+ unsigned char character;
+
+ gagt_unpack_attributes(line->buffer.attributes[index], &attribute_set);
+ character = line->buffer.data[index];
+
+ /*
+ * If no AGT attribute is set for this character, then not all of the
+ * line is standout text. In this case, reset the all_formatted flag.
+ */
+ if (!(attribute_set.blink
+ || attribute_set.fixed || attribute_set.emphasis))
+ all_formatted = FALSE;
+
+ /* Count upper and lower case characters. */
+ if (islower(character))
+ lower_count++;
+ else if (isupper(character))
+ upper_count++;
+ }
+
+ /*
+ * Consider standout if every character was formatted, or if the string
+ * is all uppercase.
+ */
+ return all_formatted || (upper_count > 0 && lower_count == 0);
+}
+
+
+/*
+ * gagt_set_font_hint_proportional()
+ * gagt_set_font_hint_proportional_newline()
+ * gagt_set_font_hint_fixed_width()
+ *
+ * Helpers for assigning font hints. Font hints have strengths, and these
+ * functions ensure that gagt_assign_paragraph_font_hints() only increases
+ * strengths, and doesn't need to worry about checking before setting. In
+ * the case of newline, the function also adds standout to the font hint if
+ * appropriate.
+ */
+static void gagt_set_font_hint_proportional(gagt_lineref_t line) {
+ /* The only weaker hint than proportional is none. */
+ if (line->font_hint == HINT_NONE)
+ line->font_hint = HINT_PROPORTIONAL;
+}
+
+static void gagt_set_font_hint_proportional_newline(gagt_lineref_t line) {
+ /*
+ * Proportional and none are weaker than newline. Because of the way we
+ * set font hints, this function can't be called with a current line hint
+ * of proportional newline.
+ */
+ if (line->font_hint == HINT_NONE || line->font_hint == HINT_PROPORTIONAL) {
+ if (gagt_line_is_standout(line))
+ line->font_hint = HINT_PROPORTIONAL_NEWLINE_STANDOUT;
+ else
+ line->font_hint = HINT_PROPORTIONAL_NEWLINE;
+ }
+}
+
+static void gagt_set_font_hint_fixed_width(gagt_lineref_t line) {
+ /* Fixed width font is the strongest hint. */
+ if (line->font_hint == HINT_NONE
+ || line->font_hint == HINT_PROPORTIONAL
+ || line->font_hint == HINT_PROPORTIONAL_NEWLINE
+ || line->font_hint == HINT_PROPORTIONAL_NEWLINE_STANDOUT)
+ line->font_hint = HINT_FIXED_WIDTH;
+}
+
+
+/*
+ * gagt_assign_paragraph_font_hints()
+ *
+ * For a given paragraph in the page buffer, this function looks at the text
+ * style used, and assigns a font hint value to each line. Font hints
+ * indicate whether the line probably requires fixed width font, or may be
+ * okay in variable width, and for lines that look like they might be okay
+ * in variable width, whether the newline should probably be rendered at the
+ * end of the line, or if it might be omitted.
+ */
+static void gagt_assign_paragraph_font_hints(const gagt_paragraphref_t paragraph) {
+ static int is_initialized = FALSE;
+ static int threshold[BYTE_MAX_VAL + 1];
+
+ gagt_lineref_t line, first_line;
+ int is_table, in_list;
+ assert(paragraph);
+
+ /* On first call, set up the table on punctuation run thresholds. */
+ if (!is_initialized) {
+ int character;
+
+ for (character = 0; character <= BYTE_MAX_VAL; character++) {
+ /*
+ * Set the threshold, either a normal value, or a larger one for
+ * punctuation characters that tend to have consecutive runs in
+ * non-table text.
+ */
+ if (ispunct(character)) {
+ threshold[character] = strchr(GAGT_COMMON_PUNCTUATION, character)
+ ? GAGT_COMMON_THRESHOLD : GAGT_THRESHOLD;
+ }
+ }
+
+ is_initialized = TRUE;
+ }
+
+ /*
+ * Note the first paragraph line. This value is commonly used, and under
+ * certain circumstances, it's also modified later on.
+ */
+ first_line = gagt_get_first_paragraph_line(paragraph);
+ assert(first_line);
+
+ /*
+ * Phase 1 -- look for pages that consist of just one paragraph,
+ * itself consisting of only one line.
+ *
+ * There is no point in attempting alignment of text in a one paragraph,
+ * one line page. This would be, for example, an error message from the
+ * interpreter parser. In this case, set the line for proportional with
+ * newline, and return immediately.
+ */
+ if (gagt_get_first_paragraph() == paragraph
+ && !gagt_get_next_paragraph(paragraph)
+ && !gagt_get_next_paragraph_line(first_line)) {
+ /*
+ * Set the first paragraph line for proportional with a newline, and
+ * return.
+ */
+ gagt_set_font_hint_proportional_newline(first_line);
+ return;
+ }
+
+ /*
+ * Phase 2 -- try to identify paragraphs that are tables, based on
+ * looking for runs of punctuation.
+ *
+ * Search for any string that has a run of apparent line drawing or other
+ * formatting characters in it. If we find one, we'll consider the
+ * paragraph to be a "table", that is, it has some quality that we might
+ * destroy if we used a proportional font.
+ */
+ is_table = FALSE;
+ for (line = first_line;
+ line && !is_table; line = gagt_get_next_paragraph_line(line)) {
+ int index, counts[BYTE_MAX_VAL + 1], total_counts;
+
+ /*
+ * Clear the initial counts. Using memset() here is an order of
+ * magnitude or two faster than a for-loop. Also there's a total count
+ * to detect when counts needs to be recleared, or is already clear.
+ */
+ memset(counts, 0, sizeof(counts));
+ total_counts = 0;
+
+ /*
+ * Count consecutive punctuation in the line, excluding the indentation
+ * and outdent.
+ */
+ for (index = line->indent;
+ index < line->buffer.length - line->outdent && !is_table; index++) {
+ int character;
+ character = line->buffer.data[index];
+
+ /* Test this character for punctuation. */
+ if (ispunct(character)) {
+ /*
+ * Increment the count for this character, and note that
+ * counts are no longer empty, then compare against threshold.
+ */
+ counts[character]++;
+ total_counts++;
+
+ is_table = (counts[character] >= threshold[character]);
+ } else {
+ /*
+ * Re-clear all counts, again with memset() for speed, but only
+ * if they need clearing. As they often won't, this optimization
+ * saves quite a bit of work.
+ */
+ if (total_counts > 0) {
+ memset(counts, 0, sizeof(counts));
+ total_counts = 0;
+ }
+ }
+ }
+ }
+
+ /*
+ * Phase 3 -- try again to identify paragraphs that are tables, based
+ * this time on looking for runs of whitespace.
+ *
+ * If no evidence found so far, look again, this time searching for any
+ * run of four or more spaces on the line (excluding any lead-in or
+ * trailing spaces).
+ */
+ if (!is_table) {
+ for (line = first_line;
+ line && !is_table; line = gagt_get_next_paragraph_line(line)) {
+ int index, count;
+
+ /*
+ * Count consecutive spaces in the line, excluding the indentation
+ * and outdent.
+ */
+ count = 0;
+ for (index = line->indent;
+ index < line->buffer.length - line->outdent && !is_table;
+ index++) {
+ int character;
+ character = line->buffer.data[index];
+
+ if (isspace(character)) {
+ count++;
+ is_table = (count >= GAGT_THRESHOLD);
+ } else
+ count = 0;
+ }
+ }
+ }
+
+ /*
+ * If the paragraph appears to be a table, and if it consists of more than
+ * just a single line, mark all lines as fixed font output and return.
+ */
+ if (is_table && gagt_get_next_paragraph_line(first_line)) {
+ for (line = first_line;
+ line; line = gagt_get_next_paragraph_line(line)) {
+ gagt_set_font_hint_fixed_width(line);
+ }
+
+ /* Nothing more to do. */
+ return;
+ }
+
+ /*
+ * Phase 4 -- consider separating the first line from the rest of
+ * the paragraph.
+ *
+ * Not a table, so the choice is between proportional rendering with a
+ * newline, and proportional rendering without...
+ *
+ * If the first paragraph line is standout or short, render it pro-
+ * portionally with a newline, and don't consider it as a further part of
+ * the paragraph.
+ */
+ if (gagt_line_is_standout(first_line)
+ || first_line->real_length < screen_width / 2) {
+ /* Set the first paragraph line for a newline. */
+ gagt_set_font_hint_proportional_newline(first_line);
+
+ /*
+ * Disassociate this line from the rest of the paragraph by moving on
+ * the value of the first_line variable. If it turns out that there
+ * is no next paragraph line, then we have a one-line paragraph, and
+ * there's no more to do.
+ */
+ first_line = gagt_get_next_paragraph_line(first_line);
+ if (!first_line)
+ return;
+ }
+
+ /*
+ * Phase 5 -- try to identify lists by a simple initial look at line
+ * indentations.
+ *
+ * Look through the paragraph for apparent lists, and decide for each
+ * line whether it's appropriate to output a newline, and render
+ * proportionally, or just render proportionally.
+ *
+ * After this loop, each line will have some form of font hint assigned
+ * to it.
+ */
+ in_list = FALSE;
+ for (line = first_line;
+ line; line = gagt_get_next_paragraph_line(line)) {
+ gagt_lineref_t next_line;
+
+ next_line = gagt_get_next_paragraph_line(line);
+
+ /*
+ * Special last-iteration processing. The newline is always output at
+ * the end of a paragraph, so if there isn't a next line, then this
+ * line is the last paragraph line. Set its font hint appropriately,
+ * and do no more for the line.
+ */
+ if (!next_line) {
+ gagt_set_font_hint_proportional_newline(line);
+ continue;
+ }
+
+ /*
+ * If the next line's indentation is deeper that that of the first
+ * line, this paragraph looks like it is trying to be some form of a
+ * list. In this case, make newline significant for the current line,
+ * and set the in_list flag so we can delay the return to proportional
+ * by one line. On return to first line indentation, make newline
+ * significant for the return line.
+ */
+ if (next_line->indent > first_line->indent) {
+ gagt_set_font_hint_proportional_newline(line);
+ in_list = TRUE;
+ } else {
+ if (in_list)
+ gagt_set_font_hint_proportional_newline(line);
+ else
+ gagt_set_font_hint_proportional(line);
+ in_list = FALSE;
+ }
+ }
+
+ /*
+ * Phase 6 -- look again for lines that look like they are supposed
+ * to stand out from their neighbors.
+ *
+ * Now rescan the paragraph, looking this time for lines that stand out
+ * from their neighbours. Make newline significant for each such line,
+ * and the line above, if there is one.
+ *
+ * Here we split the loop on lines so that we avoid looking at the prior
+ * line of the current first line -- because of "adjustments", it may not
+ * be the real paragraph first line.
+ *
+ * So, deal with the current first line...
+ */
+ if (gagt_line_is_standout(first_line)) {
+ /* Make newline significant for this line. */
+ gagt_set_font_hint_proportional_newline(first_line);
+ }
+
+ /* ... then deal with the rest of the lines, looking for standouts. */
+ for (line = gagt_get_next_paragraph_line(first_line);
+ line; line = gagt_get_next_paragraph_line(line)) {
+ if (gagt_line_is_standout(line)) {
+ gagt_lineref_t prior_line;
+
+ /* Make newline significant for this line. */
+ gagt_set_font_hint_proportional_newline(line);
+
+ /*
+ * Make newline significant for the line above. There will always
+ * be one because we start the loop past the first line.
+ */
+ prior_line = gagt_get_prior_paragraph_line(line);
+ gagt_set_font_hint_proportional_newline(prior_line);
+ }
+ }
+
+ /*
+ * Phase 7 -- special case short lines at the paragraph start.
+ *
+ * Make a special case of lines that begin a paragraph, and are short and
+ * followed by a much longer line. This should catch games which output
+ * room titles above descriptions without using AGT fonts/bold/whatever.
+ * Without this trap, room titles and their descriptions are run together.
+ * This is more programmatic guesswork than heuristics.
+ */
+ if (gagt_get_next_paragraph_line(first_line)) {
+ gagt_lineref_t next_line;
+
+ next_line = gagt_get_next_paragraph_line(first_line);
+
+ /*
+ * See if the first line is less than half width, and the second line
+ * is more than three quarters width. If it is, set newline as
+ * significant for the first paragraph line.
+ */
+ if (first_line->real_length < screen_width / 2
+ && next_line->real_length > screen_width * 3 / 4) {
+ gagt_set_font_hint_proportional_newline(first_line);
+ }
+ }
+
+ /*
+ * Phase 8 -- special case paragraphs of only short lines.
+ *
+ * Make a special case out of paragraphs where all lines are short. This
+ * catches elements like indented addresses.
+ */
+ if (gagt_get_next_paragraph_line(first_line)) {
+ int all_short;
+
+ all_short = TRUE;
+ for (line = first_line;
+ line; line = gagt_get_next_paragraph_line(line)) {
+ /* Clear flag if this line isn't 'short'. */
+ if (line->real_length >= screen_width / 2) {
+ all_short = FALSE;
+ break;
+ }
+ }
+
+ /*
+ * If all lines were short, mark the complete paragraph as having
+ * significant newlines.
+ */
+ if (all_short) {
+ for (line = first_line;
+ line; line = gagt_get_next_paragraph_line(line)) {
+ gagt_set_font_hint_proportional_newline(line);
+ }
+ }
+ }
+}
+
+
+/*
+ * gagt_assign_font_hints()
+ *
+ *
+ * Sets a font hint for each line of each page buffer paragraph that is not
+ * a special paragraph.
+ */
+static void gagt_assign_font_hints() {
+ gagt_paragraphref_t paragraph;
+
+ for (paragraph = gagt_get_first_paragraph();
+ paragraph; paragraph = gagt_get_next_paragraph(paragraph)) {
+ if (!paragraph->special)
+ gagt_assign_paragraph_font_hints(paragraph);
+ }
+}
+
+
+/*---------------------------------------------------------------------*/
+/* Glk port special paragraph functions and data */
+/*---------------------------------------------------------------------*/
+
+/*
+ * It's helpful to handle some AGiliTy interpreter output specially, to im-
+ * prove the look of the text where Glk fonts and styles are available. We
+ * build a table of paragraphs the interpreter can come out with, and the
+ * replacement text we'll use when we see this paragraph. Note that matches
+ * are made after factoring out indentation, and replacement lines do not
+ * automatically print with a newline. All clear, then? Here's the table
+ * entry definition.
+ */
+enum { GAGT_SPECIAL_MATCH_MAX = 5 };
+
+typedef const struct gagt_special_s {
+ const int line_count;
+ const char *const compare[GAGT_SPECIAL_MATCH_MAX + 1];
+ const char *const replace;
+} gagt_special_t;
+
+/*
+ * Table of special AGiliTy interpreter strings and paragraphs -- where one
+ * appears in game output, we'll print out its replacement instead. Be
+ * warned; these strings are VERY specific to AGiliTy 1.1.1.1, and are extre-
+ * mely likely to change with any future interpreter releases. They also
+ * omit initializers with abandon, expecting the compiler to default these
+ * to NULL/zero. Replacement strings embed style encoding as |x, where x is
+ * E(mphasized), S(ubheader), or N(ormal) for convenience.
+ */
+static gagt_special_t GAGT_SPECIALS[] = {
+
+ /* Initial screen AGT game type line. */
+ {
+ 1,
+ {"[Created with Malmberg and Welch's Adventure Game Toolkit]"},
+ "|ECreated with Malmberg and Welch's Adventure Game Toolkit|N\n"
+ },
+
+ /* Normal version of initial interpreter information block. */
+ {
+ 4,
+ {
+ "This game is being executed by",
+ "AGiliTy: The (Mostly) Universal AGT Interpreter version 1.1.1.1",
+ "Copyright (C) 1996-99,2001 by Robert Masenten",
+ "Glk version"
+ },
+ "This game is being executed by:\n\n"
+ " |SAGiliTy, The (Mostly) Universal AGT Interpreter, Version 1.1.1.1|N\n"
+ " |ECopyright (C) 1996-1999,2001 by Robert Masenten|N\n"
+ " |EGlk version|N\n"
+ },
+
+ /* AGiliTy "information" screen header block. */
+ {
+ 5,
+ {
+ "AGiliTy",
+ "The (Mostly) Universal AGT Interpreter, version 1.1.1.1",
+ "Copyright (C) 1996-1999,2001 by Robert Masenten",
+ "[Glk version]",
+ "-----------------------------------------------------------"
+ },
+ "|SAGiliTy, The (Mostly) Universal AGT Interpreter, Version 1.1.1.1|N\n"
+ "|ECopyright (C) 1996-1999,2001 by Robert Masenten|N\n"
+ "|EGlk version|N\n"
+ },
+
+ /* "HIT ANY KEY" message, usually displayed after a game's introduction. */
+ {
+ 1,
+ {"--- HIT ANY KEY ---"},
+ "|E[Press any key...]|N"
+ },
+
+ /* Alternative, shrunken version of initial interpreter information block. */
+ {
+ 2,
+ {
+ "Being run by AGiliTy version 1.1.1.1, Copyright (C) 1996-99,2001"
+ " Robert Masenten",
+ "Glk version"
+ },
+ "This game is being executed by:\n\n"
+ " |SAGiliTy, The (Mostly) Universal AGT Interpreter, Version 1.1.1.1|N\n"
+ " |ECopyright (C) 1996-1999,2001 by Robert Masenten|N\n"
+ " |EGlk version|N\n"
+ },
+
+ /* Alternative, minimal version of initial interpreter information block. */
+ {
+ 1,
+ {
+ "Being run by AGiliTy version 1.1.1.1, Copyright (C) 1996-99,2001"
+ " Robert Masenten"
+ },
+ "This game is being executed by:\n\n"
+ " |SAGiliTy, The (Mostly) Universal AGT Interpreter, Version 1.1.1.1|N\n"
+ " |ECopyright (C) 1996-1999,2001 by Robert Masenten|N\n"
+ " |EGlk version|N\n"
+ },
+
+ /* Lengthy version of the "Created with..." message. */
+ {
+ 2,
+ {
+ "This game was created with Malmberg and Welch's Adventure Game Toolkit;"
+ " it is",
+ "being executed by"
+ },
+ "|ECreated with Malmberg and Welch's Adventure Game Toolkit|N\n"
+ },
+
+ /* Three-line version of initial interpreter information block. */
+ {
+ 3,
+ {
+ "AGiliTy: The (Mostly) Universal AGT Interpreter version 1.1.1.1",
+ "Copyright (C) 1996-99,2001 by Robert Masenten",
+ "Glk version"
+ },
+ "This game is being executed by:\n\n"
+ " |SAGiliTy, The (Mostly) Universal AGT Interpreter, Version 1.1.1.1|N\n"
+ " |ECopyright (C) 1996-1999,2001 by Robert Masenten|N\n"
+ " |EGlk version|N\n"
+ },
+
+ /*
+ * Assorted special verb output messages, with the extra icky quality that
+ * we have to spot messages that wrap because we forced screen_width to 80.
+ */
+ {
+ 2,
+ {
+ "[Now in BRIEF mode (room descriptions will only be printed"
+ " when they are entered",
+ "the first time)]"
+ },
+ "|E[Now in BRIEF mode: Room descriptions will only be printed"
+ " when rooms are entered for the first time.]|N\n"
+ },
+
+ {
+ 2,
+ {
+ "[Now in VERBOSE mode (room descriptions will be printed"
+ " every time you enter a",
+ "room)]"
+ },
+ "|E[Now in VERBOSE mode: Room descriptions will be printed"
+ " every time you enter a room.]|N\n"
+ },
+
+ {
+ 1,
+ {"[LISTEXIT mode on: room exits will be listed.]"},
+ "|E[LISTEXIT mode on: Room exits will be listed.]|N\n"
+ },
+
+ {
+ 1,
+ {"[LISTEXIT mode off: room exits will not be listed.]"},
+ "|E[LISTEXIT mode off: Room exits will not be listed.]|N\n"
+ },
+
+ /* End of table sentinel entry. Do not delete. */
+ {0, {NULL}, NULL}
+};
+
+
+/*
+ * gagt_compare_special_line()
+ * gagt_compare_special_paragraph()
+ *
+ * Helpers for gagt_find_equivalent_special(). Compare line data case-
+ * insensitively, taking care to use lengths rather than relying on line
+ * buffer data being NUL terminated (which it's not); and iterate a complete
+ * special paragraph comparison.
+ */
+static int gagt_compare_special_line(const char *compare, const gagt_lineref_t line) {
+ /*
+ * Return true if the lengths match, and the real line data (excluding
+ * indent and outdent) also matches, ignoring case.
+ */
+ return (int)strlen(compare) == line->real_length
+ && gagt_strncasecmp(compare,
+ (const char *)line->buffer.data + line->indent,
+ line->real_length) == 0;
+}
+
+static int gagt_compare_special_paragraph(const gagt_specialref_t special,
+ const gagt_paragraphref_t paragraph) {
+ /* If the line counts match, compare line by line. */
+ if (special->line_count == gagt_get_paragraph_line_count(paragraph)) {
+ gagt_lineref_t line;
+ int index, is_match;
+
+ is_match = TRUE;
+ for (index = 0, line = gagt_get_first_paragraph_line(paragraph);
+ index < special->line_count && line;
+ index++, line = gagt_get_next_paragraph_line(line)) {
+ if (!gagt_compare_special_line(special->compare[index], line)) {
+ is_match = FALSE;
+ break;
+ }
+ }
+
+ return is_match;
+ }
+
+ /* Line count mismatch; return FALSE. */
+ return FALSE;
+}
+
+
+/*
+ * gagt_find_equivalent_special()
+ *
+ * Given a paragraph, see if it matches any of the special ones set up in
+ * our array. Returns the special, or NULL if no match.
+ */
+static gagt_specialref_t gagt_find_equivalent_special(gagt_paragraphref_t paragraph) {
+ gagt_specialref_t special, match;
+
+ /* Check each special paragraph entry for a match against this paragraph. */
+ match = NULL;
+ for (special = GAGT_SPECIALS; special->replace; special++) {
+ if (gagt_compare_special_paragraph(special, paragraph)) {
+ match = special;
+ break;
+ }
+ }
+
+ return match;
+}
+
+
+/*
+ * gagt_mark_specials()
+ *
+ * Search for and mark any lines that match special paragraphs.
+ */
+static void gagt_mark_specials() {
+ static int is_verified = FALSE;
+
+ /*
+ * Verify special paragraphs table contents. This checks that each entry
+ * ends with a NULL comparison, has a replacement, and that the line count
+ * matches.
+ */
+ if (!is_verified) {
+ gagt_specialref_t special;
+
+ for (special = GAGT_SPECIALS; special->replace; special++) {
+ int line_count, index;
+
+ line_count = 0;
+ for (index = 0; special->compare[index]; index++)
+ line_count++;
+
+ assert(special->line_count == line_count);
+ assert(special->replace);
+ assert(!special->compare[GAGT_SPECIAL_MATCH_MAX]);
+ }
+
+ is_verified = TRUE;
+ }
+
+ /*
+ * Search all paragraphs for special matches, if enabled. When a special
+ * match is found, mark the paragraph with a pointer to the matching entry.
+ */
+ if (gagt_replacement_enabled) {
+ gagt_paragraphref_t paragraph;
+
+ for (paragraph = gagt_get_first_paragraph();
+ paragraph; paragraph = gagt_get_next_paragraph(paragraph)) {
+ paragraph->special = gagt_find_equivalent_special(paragraph);
+ }
+ }
+}
+
+
+/*
+ * gagt_display_special()
+ *
+ * Display the replacement text for the specified special table entry. The
+ * current Glk style in force is passed in; we return the Glk style in force
+ * after we've done.
+ */
+static glui32 gagt_display_special(const gagt_specialref_t special, glui32 current_style) {
+ glui32 set_style;
+ int index, marker, length;
+ const char *string;
+ assert(special);
+
+ /* Extract replacement string and length. */
+ string = special->replace;
+ assert(string);
+ length = strlen(string);
+
+ set_style = current_style;
+
+ /*
+ * Iterate each character in replacement string, looking for style escapes,
+ * and flushing delayed output when one is found.
+ */
+ marker = 0;
+ for (index = 0; index < length; index++) {
+ if (string[index] == '|') {
+ glui32 style;
+
+ /* Flush delayed output accumulated so far, excluding escape. */
+ g_vm->glk_put_buffer(string + marker, index - marker);
+ marker = index + 2;
+
+ /* Determine any new text style. */
+ style = set_style;
+ switch (string[++index]) {
+ case 'E':
+ style = style_Emphasized;
+ break;
+
+ case 'S':
+ style = style_Subheader;
+ break;
+
+ case 'N':
+ style = style_Normal;
+ break;
+
+ default:
+ gagt_fatal("GLK: Invalid replacement style escape");
+ gagt_exit();
+ }
+
+ /* If style changed, update Glk's style setting. */
+ if (style != set_style) {
+ g_vm->glk_set_style(style);
+ set_style = style;
+ }
+ }
+ }
+
+ /* Output any remaining delayed characters. */
+ if (marker < length)
+ g_vm->glk_put_buffer(string + marker, length - marker);
+
+ return set_style;
+}
+
+
+/*---------------------------------------------------------------------*/
+/* Glk port output functions */
+/*---------------------------------------------------------------------*/
+
+/*
+ * Flag for if the user entered "help" as their last input, or if hints have
+ * been silenced as a result of already using a Glk command.
+ */
+static int gagt_help_requested = FALSE,
+ gagt_help_hints_silenced = FALSE;
+
+/*
+ * gagt_display_register_help_request()
+ * gagt_display_silence_help_hints()
+ * gagt_display_provide_help_hint()
+ *
+ * Register a request for help, and print a note of how to get Glk command
+ * help from the interpreter unless silenced.
+ */
+static void gagt_display_register_help_request() {
+ gagt_help_requested = TRUE;
+}
+
+static void gagt_display_silence_help_hints() {
+ gagt_help_hints_silenced = TRUE;
+}
+
+static glui32 gagt_display_provide_help_hint(glui32 current_style) {
+ if (gagt_help_requested && !gagt_help_hints_silenced) {
+ g_vm->glk_set_style(style_Emphasized);
+ g_vm->glk_put_string("[Try 'glk help' for help on special interpreter"
+ " commands]\n");
+
+ gagt_help_requested = FALSE;
+ return style_Emphasized;
+ }
+
+ return current_style;
+}
+
+
+/*
+ * gagt_display_text_element()
+ *
+ * Display an element of a buffer string using matching packed attributes.
+ * The currently set Glk style is supplied, and the function returns the
+ * new currently set Glk style.
+ *
+ * The function handles a flag to coerce fixed width font.
+ */
+static glui32 gagt_display_text_element(const char *string, const unsigned char *attributes,
+ int length, glui32 current_style, int fixed_width) {
+ int marker, index;
+ glui32 set_style;
+ assert(g_vm->glk_stream_get_current());
+
+ set_style = current_style;
+
+ /*
+ * Iterate each character in the line range. We actually delay output
+ * until we see a change in style; that way, we can send a buffer of
+ * characters to Glk, rather than sending them just one at a time.
+ */
+ marker = 0;
+ for (index = 0; index < length; index++) {
+ gagt_attrset_t attribute_set;
+ glui32 style;
+ assert(attributes && string);
+
+ /*
+ * Unpack the AGT font attributes for this character, and add fixed
+ * width font coercion.
+ */
+ gagt_unpack_attributes(attributes[index], &attribute_set);
+ attribute_set.fixed |= fixed_width;
+
+ /*
+ * Decide on any applicable new Glk text styling. If it's different
+ * to the current style, output the delayed characters, and update
+ * Glk's style setting.
+ */
+ style = gagt_select_style(&attribute_set);
+ if (style != set_style) {
+ g_vm->glk_put_buffer(string + marker, index - marker);
+ marker = index;
+
+ g_vm->glk_set_style(style);
+ set_style = style;
+ }
+ }
+
+ /* Output any remaining delayed characters. */
+ if (marker < length)
+ g_vm->glk_put_buffer(string + marker, length - marker);
+
+ return set_style;
+}
+
+
+/*
+ * gagt_display_line()
+ *
+ * Display a page buffer line, starting in the current Glk style, and
+ * returning the new current Glk style.
+ *
+ * The function takes additional flags to force fixed width font, skip over
+ * indentation and trailing line whitespace, and trim hyphens (if skipping
+ * trailing whitespace).
+ */
+static glui32 gagt_display_line(const gagt_lineref_t line, glui32 current_style,
+ int fixed_width, int skip_indent, int skip_outdent,
+ int trim_hyphen) {
+ int start, length;
+ glui32 set_style;
+
+ /*
+ * Check the skip indent flag to find the first character to display, and
+ * the count of characters to display.
+ */
+ start = 0;
+ length = line->buffer.length;
+ if (skip_indent) {
+ start += line->indent;
+ length -= line->indent;
+ }
+
+ /* Adjust length for skipping outdent and trimming hyphens. */
+ if (skip_outdent) {
+ length -= line->outdent;
+ if (trim_hyphen && line->is_hyphenated)
+ length--;
+ }
+
+ /* Display this line segment. */
+ set_style = gagt_display_text_element((const char *)line->buffer.data + start,
+ line->buffer.attributes + start,
+ length, current_style, fixed_width);
+
+ return set_style;
+}
+
+
+/*
+ * gagt_display_hinted_line()
+ *
+ * Display a page buffer line, starting in the current Glk style, and
+ * returning the new current Glk style. The function uses the font hints
+ * from the line, and receives the font hint of the prior line.
+ */
+static glui32 gagt_display_hinted_line(const gagt_lineref_t line, glui32 current_style,
+ gagt_font_hint_t prior_hint) {
+ glui32 style;
+
+ style = current_style;
+ switch (line->font_hint) {
+ case HINT_FIXED_WIDTH:
+ /* Force fixed width font on the line. */
+ style = gagt_display_line(line, style, TRUE, FALSE, FALSE, FALSE);
+
+ g_vm->glk_put_char('\n');
+ break;
+
+ case HINT_PROPORTIONAL:
+ /*
+ * Permit proportional font, and suppress outdent. Suppress indent
+ * too if this line follows a line that suppressed newline, or is the
+ * first line in the paragraph. For all cases, trim the hyphen from
+ * hyphenated lines.
+ */
+ if (prior_hint == HINT_PROPORTIONAL || prior_hint == HINT_NONE)
+ style = gagt_display_line(line, style, FALSE, TRUE, TRUE, TRUE);
+ else
+ style = gagt_display_line(line, style, FALSE, FALSE, TRUE, TRUE);
+
+ /*
+ * Where the line is not hyphenated, output a space in place of newline.
+ * This lets paragraph text to flow to the full display width.
+ */
+ if (!line->is_hyphenated)
+ g_vm->glk_put_char(' ');
+ break;
+
+ case HINT_PROPORTIONAL_NEWLINE:
+ case HINT_PROPORTIONAL_NEWLINE_STANDOUT:
+ /*
+ * As above, permit proportional font, suppress outdent, and suppress
+ * indent too under certain conditions; in this case, only when the
+ * prior line suppressed newline.
+ */
+ if (prior_hint == HINT_PROPORTIONAL)
+ style = gagt_display_line(line, style, FALSE, TRUE, TRUE, FALSE);
+ else
+ style = gagt_display_line(line, style, FALSE, FALSE, TRUE, FALSE);
+
+ g_vm->glk_put_char('\n');
+ break;
+
+ case HINT_NONE:
+ gagt_fatal("GLK: Page buffer line with no font hint");
+ gagt_exit();
+
+ default:
+ gagt_fatal("GLK: Invalid font hint encountered");
+ gagt_exit();
+ }
+
+ return style;
+}
+
+
+/*
+ * gagt_display_auto()
+ *
+ * Display buffered output text to the Glk main window using a bunch of
+ * occasionally rather dodgy heuristics to try to automatically set a suitable
+ * font for the way the text is structured, while replacing special paragraphs
+ * with altered text.
+ */
+static void gagt_display_auto() {
+ gagt_paragraphref_t paragraph;
+ glui32 style;
+
+ style = style_Normal;
+ g_vm->glk_set_style(style);
+
+ /* Handle each paragraph. */
+ for (paragraph = gagt_get_first_paragraph();
+ paragraph; paragraph = gagt_get_next_paragraph(paragraph)) {
+ /* If a special paragraph, output replacement text instead. */
+ if (paragraph->special) {
+ style = gagt_display_special(paragraph->special, style);
+ g_vm->glk_put_char('\n');
+ } else {
+ gagt_lineref_t line;
+ gagt_font_hint_t prior_hint;
+
+ /* Get the first line of the paragraph. */
+ line = gagt_get_first_paragraph_line(paragraph);
+
+ /*
+ * Output a blank line where the first line of the first paragraph
+ * is standout; this sets it apart from the prompt.
+ */
+ if (paragraph == gagt_get_first_paragraph()
+ && line == gagt_get_first_paragraph_line(paragraph)) {
+ if (line->font_hint == HINT_PROPORTIONAL_NEWLINE_STANDOUT)
+ g_vm->glk_put_char('\n');
+ }
+
+ /* Handle each line of the paragraph. */
+ prior_hint = HINT_NONE;
+ for (; line; line = gagt_get_next_paragraph_line(line)) {
+ /*
+ * Print this line according to its font hint, noting any change
+ * of style and the line's font hint for use next iteration as
+ * the prior hint.
+ */
+ style = gagt_display_hinted_line(line, style, prior_hint);
+ prior_hint = line->font_hint;
+ }
+
+ /* Output the newline for the end of the paragraph. */
+ g_vm->glk_put_char('\n');
+ }
+ }
+
+ /* If no paragraphs at all, but a current buffer, output a newline. */
+ if (!gagt_get_first_paragraph() && gagt_current_buffer.length > 0)
+ g_vm->glk_put_char('\n');
+
+ /* Output any help hint and unterminated line from the line buffer. */
+ style = gagt_display_provide_help_hint(style);
+ style = gagt_display_text_element((const char *)gagt_current_buffer.data,
+ gagt_current_buffer.attributes,
+ gagt_current_buffer.length, style, FALSE);
+}
+
+
+/*
+ * gagt_display_manual()
+ *
+ * Display buffered output text in the Glk main window, with either a fixed
+ * width or a proportional font.
+ */
+static void gagt_display_manual(int fixed_width) {
+ gagt_lineref_t line;
+ glui32 style;
+
+ style = style_Normal;
+ g_vm->glk_set_style(style);
+
+ for (line = gagt_get_first_page_line();
+ line; line = gagt_get_next_page_line(line)) {
+ gagt_paragraphref_t paragraph;
+
+ paragraph = line->paragraph;
+
+ /*
+ * If this is a special paragraph, display the replacement text on
+ * its first line and ignore remaining special lines. Otherwise,
+ * display the page buffer line using either fixed or proportional
+ * font, as requested.
+ */
+ if (paragraph && paragraph->special) {
+ if (gagt_get_first_paragraph_line(paragraph) == line)
+ style = gagt_display_special(paragraph->special, style);
+ } else {
+ style = gagt_display_line(line, style, fixed_width,
+ FALSE, FALSE, FALSE);
+ g_vm->glk_put_char('\n');
+ }
+ }
+
+ /* Output any help hint and unterminated line from the line buffer. */
+ style = gagt_display_provide_help_hint(style);
+ style = gagt_display_text_element((const char *)gagt_current_buffer.data,
+ gagt_current_buffer.attributes,
+ gagt_current_buffer.length,
+ style, fixed_width);
+}
+
+
+/*
+ * gagt_display_debug()
+ *
+ * Display the analyzed page buffer in a form that shows all of its gory
+ * detail.
+ */
+static void gagt_display_debug() {
+ gagt_lineref_t line;
+ char buffer[256];
+
+ g_vm->glk_set_style(style_Preformatted);
+ for (line = gagt_get_first_page_line();
+ line; line = gagt_get_next_page_line(line)) {
+ gagt_paragraphref_t paragraph;
+
+ paragraph = line->paragraph;
+ sprintf(buffer,
+ "%2d:%2d->%2ld A=%-3d L=%-2d I=%-2d O=%-2d R=%-2d %c%c| ",
+ paragraph ? paragraph->id + 1 : 0,
+ paragraph ? paragraph->line_count : 0,
+ paragraph && paragraph->special
+ ? paragraph->special - GAGT_SPECIALS + 1 : 0,
+ line->buffer.allocation, line->buffer.length,
+ line->indent, line->outdent,
+ line->real_length,
+ line->is_hyphenated ? 'h' : '_',
+ line->is_blank ? 'b' :
+ line->font_hint == HINT_PROPORTIONAL ? 'P' :
+ line->font_hint == HINT_PROPORTIONAL_NEWLINE ? 'N' :
+ line->font_hint == HINT_PROPORTIONAL_NEWLINE_STANDOUT ? 'S' :
+ line->font_hint == HINT_FIXED_WIDTH ? 'F' : '_');
+ g_vm->glk_put_string(buffer);
+
+ g_vm->glk_put_buffer((const char *)line->buffer.data, line->buffer.length);
+ g_vm->glk_put_char('\n');
+ }
+
+ if (gagt_current_buffer.length > 0) {
+ sprintf(buffer,
+ "__,__->__ A=%-3d L=%-2d I=__ O=__ R=__ %s| ",
+ gagt_current_buffer.allocation, gagt_current_buffer.length,
+ gagt_help_requested ? "HR" : "__");
+ g_vm->glk_put_string(buffer);
+
+ g_vm->glk_put_buffer((const char *)gagt_current_buffer.data, gagt_current_buffer.length);
+ }
+
+ gagt_help_requested = FALSE;
+}
+
+
+/*
+ * gagt_output_flush()
+ *
+ * Flush any buffered output text to the Glk main window, and clear the
+ * buffer ready for new output text. The function concerns itself with
+ * both the page buffer and any unterminated line in the line buffer.
+ */
+static void gagt_output_flush() {
+ /*
+ * Run the analysis of page buffer contents. This will fill in the
+ * paragraph and font hints fields, any any applicable special pointer,
+ * for every line held in the buffer.
+ */
+ gagt_paragraph_page();
+ gagt_mark_specials();
+ gagt_assign_font_hints();
+
+ /*
+ * Select the appropriate display routine to use, and call it. The display
+ * routines present somewhat different output, and are responsible for
+ * displaying both the page buffer _and_ any buffered current line text.
+ */
+ switch (gagt_font_mode) {
+ case FONT_AUTOMATIC:
+ gagt_display_auto();
+ break;
+
+ case FONT_FIXED_WIDTH:
+ gagt_display_manual(TRUE);
+ break;
+
+ case FONT_PROPORTIONAL:
+ gagt_display_manual(FALSE);
+ break;
+
+ case FONT_DEBUG:
+ gagt_display_debug();
+ break;
+
+ default:
+ gagt_fatal("GLK: Invalid font mode encountered");
+ gagt_exit();
+ }
+
+ /* Empty the buffer, ready for new game strings. */
+ gagt_paragraphs_delete();
+ gagt_output_delete();
+}
+
+
+/*
+ * agt_clrscr()
+ *
+ * Clear the main playing area window. Although there may be little point
+ * in flushing (rather than emptying) the buffers, nevertheless that is
+ * what we do.
+ */
+void agt_clrscr() {
+ if (!BATCH_MODE) {
+ /* Update the apparent (virtual) window x position. */
+ curr_x = 0;
+
+ /* Flush any pending buffered output, and clear the main window. */
+ gagt_output_flush();
+ g_vm->glk_window_clear(gagt_main_window);
+
+ /* Add a series of newlines to any script file. */
+ if (script_on)
+ textputs(scriptfile, "\n\n\n\n");
+
+ gagt_debug("agt_clrscr", "");
+ }
+}
+
+
+/*
+ * gagt_styled_string()
+ * gagt_styled_char()
+ * gagt_standout_string()
+ * gagt_standout_char()
+ * gagt_normal_string()
+ * gagt_normal_char()
+ * gagt_header_string()
+ *
+ * Convenience functions to print strings in assorted styles. A standout
+ * string is one that hints that it's from the interpreter, not the game.
+ */
+static void gagt_styled_string(glui32 style, const char *message) {
+ assert(message);
+
+ g_vm->glk_set_style(style);
+ g_vm->glk_put_string(message);
+ g_vm->glk_set_style(style_Normal);
+}
+
+static void gagt_styled_char(glui32 style, char c) {
+ char buffer[2];
+
+ buffer[0] = c;
+ buffer[1] = '\0';
+ gagt_styled_string(style, buffer);
+}
+
+static void gagt_standout_string(const char *message) {
+ gagt_styled_string(style_Emphasized, message);
+}
+
+static void gagt_standout_char(char c) {
+ gagt_styled_char(style_Emphasized, c);
+}
+
+static void gagt_normal_string(const char *message) {
+ gagt_styled_string(style_Normal, message);
+}
+
+static void gagt_normal_char(char c) {
+ gagt_styled_char(style_Normal, c);
+}
+
+static void gagt_header_string(const char *message) {
+ gagt_styled_string(style_Header, message);
+}
+
+
+/*---------------------------------------------------------------------*/
+/* Glk port delay functions */
+/*---------------------------------------------------------------------*/
+
+/* Number of milliseconds in a second (traditionally, 1000). */
+static const int GAGT_MS_PER_SEC = 1000;
+
+/*
+ * Number of milliseconds to timeout. Because of jitter in the way Glk
+ * generates timeouts, it's worthwhile implementing a delay using a number
+ * of shorter timeouts. This minimizes inaccuracies in the actual delay.
+ */
+static const glui32 GAGT_DELAY_TIMEOUT = 50;
+
+/* The character key that can be pressed to cancel, and suspend, delays. */
+static const char GAGT_DELAY_SUSPEND = ' ';
+
+/*
+ * Flag to temporarily turn off all delays. This is set when the user
+ * cancels a delay with a keypress, and remains set until the next time
+ * that AGiliTy requests user input. This way, games that call agt_delay()
+ * sequentially don't require multiple keypresses to jump out of delay
+ * sections.
+ */
+static int gagt_delays_suspended = FALSE;
+
+
+/*
+ * agt_delay()
+ *
+ * Delay for the specified number of seconds. The delay can be canceled
+ * by a user keypress.
+ */
+void agt_delay(int seconds) {
+ glui32 milliseconds, delayed;
+ int delay_completed;
+
+ /* Suppress delay if in fast replay or batch mode. */
+ if (fast_replay || BATCH_MODE)
+ return;
+
+ /*
+ * Do nothing if Glk doesn't have timers, if the delay state is set to
+ * ignore delays, if a zero or negative delay was specified, or if delays
+ * are currently temporarily suspended.
+ */
+ if (!g_vm->glk_gestalt(gestalt_Timer, 0)
+ || gagt_delay_mode == DELAY_OFF
+ || seconds <= 0 || gagt_delays_suspended)
+ return;
+
+ /* Flush any pending buffered output, and refresh status to show waiting. */
+ gagt_output_flush();
+ gagt_status_in_delay(TRUE);
+
+ /* Calculate the number of milliseconds to delay. */
+ milliseconds = (seconds * GAGT_MS_PER_SEC)
+ / (gagt_delay_mode == DELAY_SHORT ? 2 : 1);
+
+ /* Request timer events, and let a keypress cancel the delay. */
+ g_vm->glk_request_char_event(gagt_main_window);
+ g_vm->glk_request_timer_events(GAGT_DELAY_TIMEOUT);
+
+ /*
+ * Implement the delay using a sequence of shorter Glk timeouts, with an
+ * option to cancel the delay with a keypress.
+ */
+ delay_completed = TRUE;
+ for (delayed = 0; delayed < milliseconds; delayed += GAGT_DELAY_TIMEOUT) {
+ event_t event;
+
+ /* Wait for the next timeout, or a character. */
+ gagt_event_wait_2(evtype_CharInput, evtype_Timer, &event);
+ if (event.type == evtype_CharInput) {
+ /*
+ * If suspend requested, stop the delay, and set the delay
+ * suspension flag, and a note that the delay loop didn't complete.
+ * Otherwise, reissue the character input request.
+ */
+ if (event.val1 == GAGT_DELAY_SUSPEND) {
+ gagt_delays_suspended = TRUE;
+ delay_completed = FALSE;
+ break;
+ } else
+ g_vm->glk_request_char_event(gagt_main_window);
+ }
+ }
+
+ /* Cancel any pending character input, and timer events. */
+ if (delay_completed)
+ g_vm->glk_cancel_char_event(gagt_main_window);
+ g_vm->glk_request_timer_events(0);
+
+ /* Clear the waiting indicator. */
+ gagt_status_in_delay(FALSE);
+
+ gagt_debug("agt_delay", "seconds=%d [%lu mS] -> %s", seconds, milliseconds,
+ delay_completed ? "completed" : "canceled");
+}
+
+
+/*
+ * gagt_delay_resume()
+ *
+ * Unsuspend delays. This function should be called by agt_input() and
+ * agt_getkey(), to re-enable delays when the interpreter next requests
+ * user input.
+ */
+static void gagt_delay_resume() {
+ gagt_delays_suspended = FALSE;
+}
+
+
+/*---------------------------------------------------------------------*/
+/* Glk port box drawing functions */
+/*---------------------------------------------------------------------*/
+
+/* Saved details of any current box dimensions and flags. */
+static unsigned long gagt_box_flags = 0;
+static int gagt_box_busy = FALSE,
+ gagt_box_width = 0,
+ gagt_box_height = 0,
+ gagt_box_startx = 0;
+
+
+/*
+ * gagt_box_rule()
+ * gagt_box_position()
+ *
+ * Draw a line at the top or bottom of a box, and position the cursor
+ * with a box indent.
+ */
+static void gagt_box_rule(int width) {
+ char *ruler;
+
+ /* Write a +--...--+ ruler to delimit a box. */
+ ruler = (char *)gagt_malloc(width + 2 + 1);
+ memset(ruler + 1, '-', width);
+ ruler[0] = ruler[width + 1] = '+';
+ ruler[width + 2] = '\0';
+ agt_puts(ruler);
+ free(ruler);
+}
+
+static void gagt_box_position(int indent) {
+ char *spaces;
+
+ /* Write a newline before the indent. */
+ agt_newline();
+
+ /* Write the indent to the start of box text. */
+ spaces = (char *)gagt_malloc(indent + 1);
+ memset(spaces, ' ', indent);
+ spaces[indent] = '\0';
+ agt_puts(spaces);
+ free(spaces);
+}
+
+
+/*
+ * agt_makebox()
+ * agt_qnewline()
+ * agt_endbox()
+ *
+ * Start a box of given width, height, and with given flags. Write a new
+ * line in the box. And end the box.
+ */
+void agt_makebox(int width, int height, unsigned long flags) {
+ assert(!gagt_box_busy);
+
+ gagt_box_busy = TRUE;
+ gagt_box_flags = flags;
+ gagt_box_width = width;
+ gagt_box_height = height;
+
+ /* If no centering requested, set the indent to zero. */
+ if (gagt_box_flags & TB_NOCENT)
+ gagt_box_startx = 0;
+ else {
+ int centering_width;
+
+ /*
+ * Calculate the indent for centering, adding 4 characters for borders.
+ * Here, since screen_width is artificial, we'll center off status_width
+ * if it is less than screen width, otherwise we'll center by using
+ * screen_width. The reason for shrinking to screen_width is that if
+ * we don't, we could drive curr_x to beyond screen_width with our box
+ * indentations, and that confuses AGiliTy.
+ */
+ if (status_width < screen_width)
+ centering_width = status_width;
+ else
+ centering_width = screen_width;
+ if (gagt_box_flags & TB_BORDER)
+ gagt_box_startx = (centering_width - gagt_box_width - 4) / 2;
+ else
+ gagt_box_startx = (centering_width - gagt_box_width) / 2;
+
+ /* If the box turns out wider than the window, abandon centering. */
+ if (gagt_box_startx < 0)
+ gagt_box_startx = 0;
+ }
+
+ /*
+ * When in a box, we'll coerce fixed width font by setting it in the AGT
+ * font attributes. This ensures that the box displays as accurately as
+ * we're able to achieve.
+ */
+ gagt_coerce_fixed_font(TRUE);
+
+ /* Position the cursor for the box, and if bordered, write the rule. */
+ gagt_box_position(gagt_box_startx);
+ if (gagt_box_flags & TB_BORDER) {
+ gagt_box_rule(gagt_box_width + 2);
+ gagt_box_position(gagt_box_startx);
+ agt_puts("| ");
+ }
+
+ gagt_debug("agt_makebox", "width=%d, height=%d, flags=0x%lx",
+ width, height, flags);
+}
+
+void agt_qnewline() {
+ assert(gagt_box_busy);
+
+ /* Write box characters for the current and next line. */
+ if (gagt_box_flags & TB_BORDER) {
+ agt_puts(" |");
+ gagt_box_position(gagt_box_startx);
+ agt_puts("| ");
+ } else
+ gagt_box_position(gagt_box_startx);
+
+ gagt_debug("agt_qnewline", "");
+}
+
+void agt_endbox() {
+ assert(gagt_box_busy);
+
+ /* Finish off the current box. */
+ if (gagt_box_flags & TB_BORDER) {
+ agt_puts(" |");
+ gagt_box_position(gagt_box_startx);
+ gagt_box_rule(gagt_box_width + 2);
+ }
+ agt_newline();
+
+ /* An extra newline here improves the appearance. */
+ agt_newline();
+
+ /* Back to allowing proportional font output again. */
+ gagt_coerce_fixed_font(FALSE);
+
+ gagt_box_busy = FALSE;
+ gagt_box_flags = gagt_box_width = gagt_box_startx = 0;
+
+ gagt_debug("agt_endbox", "");
+}
+
+
+/*---------------------------------------------------------------------*/
+/* Glk command escape functions */
+/*---------------------------------------------------------------------*/
+
+/*
+ * gagt_command_script()
+ *
+ * Turn game output scripting (logging) on and off.
+ */
+static void gagt_command_script(const char *argument) {
+ assert(argument);
+
+ if (gagt_strcasecmp(argument, "on") == 0) {
+ frefid_t fileref;
+
+ if (gagt_transcript_stream) {
+ gagt_normal_string("Glk transcript is already on.\n");
+ return;
+ }
+
+ fileref = g_vm->glk_fileref_create_by_prompt(fileusage_Transcript
+ | fileusage_TextMode,
+ filemode_WriteAppend, 0);
+ if (!fileref) {
+ gagt_standout_string("Glk transcript failed.\n");
+ return;
+ }
+
+ gagt_transcript_stream = g_vm->glk_stream_open_file(fileref,
+ filemode_WriteAppend, 0);
+ g_vm->glk_fileref_destroy(fileref);
+ if (!gagt_transcript_stream) {
+ gagt_standout_string("Glk transcript failed.\n");
+ return;
+ }
+
+ g_vm->glk_window_set_echo_stream(gagt_main_window, gagt_transcript_stream);
+
+ gagt_normal_string("Glk transcript is now on.\n");
+ }
+
+ else if (gagt_strcasecmp(argument, "off") == 0) {
+ if (!gagt_transcript_stream) {
+ gagt_normal_string("Glk transcript is already off.\n");
+ return;
+ }
+
+ g_vm->glk_stream_close(gagt_transcript_stream, NULL);
+ gagt_transcript_stream = NULL;
+
+ g_vm->glk_window_set_echo_stream(gagt_main_window, NULL);
+
+ gagt_normal_string("Glk transcript is now off.\n");
+ }
+
+ else if (strlen(argument) == 0) {
+ gagt_normal_string("Glk transcript is ");
+ gagt_normal_string(gagt_transcript_stream ? "on" : "off");
+ gagt_normal_string(".\n");
+ }
+
+ else {
+ gagt_normal_string("Glk transcript can be ");
+ gagt_standout_string("on");
+ gagt_normal_string(", or ");
+ gagt_standout_string("off");
+ gagt_normal_string(".\n");
+ }
+}
+
+
+/*
+ * gagt_command_inputlog()
+ *
+ * Turn game input logging on and off.
+ */
+static void gagt_command_inputlog(const char *argument) {
+ assert(argument);
+
+ if (gagt_strcasecmp(argument, "on") == 0) {
+ frefid_t fileref;
+
+ if (gagt_inputlog_stream) {
+ gagt_normal_string("Glk input logging is already on.\n");
+ return;
+ }
+
+ fileref = g_vm->glk_fileref_create_by_prompt(fileusage_InputRecord
+ | fileusage_BinaryMode,
+ filemode_WriteAppend, 0);
+ if (!fileref) {
+ gagt_standout_string("Glk input logging failed.\n");
+ return;
+ }
+
+ gagt_inputlog_stream = g_vm->glk_stream_open_file(fileref,
+ filemode_WriteAppend, 0);
+ g_vm->glk_fileref_destroy(fileref);
+ if (!gagt_inputlog_stream) {
+ gagt_standout_string("Glk input logging failed.\n");
+ return;
+ }
+
+ gagt_normal_string("Glk input logging is now on.\n");
+ }
+
+ else if (gagt_strcasecmp(argument, "off") == 0) {
+ if (!gagt_inputlog_stream) {
+ gagt_normal_string("Glk input logging is already off.\n");
+ return;
+ }
+
+ g_vm->glk_stream_close(gagt_inputlog_stream, NULL);
+ gagt_inputlog_stream = NULL;
+
+ gagt_normal_string("Glk input log is now off.\n");
+ }
+
+ else if (strlen(argument) == 0) {
+ gagt_normal_string("Glk input logging is ");
+ gagt_normal_string(gagt_inputlog_stream ? "on" : "off");
+ gagt_normal_string(".\n");
+ }
+
+ else {
+ gagt_normal_string("Glk input logging can be ");
+ gagt_standout_string("on");
+ gagt_normal_string(", or ");
+ gagt_standout_string("off");
+ gagt_normal_string(".\n");
+ }
+}
+
+
+/*
+ * gagt_command_readlog()
+ *
+ * Set the game input log, to read input from a file.
+ */
+static void gagt_command_readlog(const char *argument) {
+ assert(argument);
+
+ if (gagt_strcasecmp(argument, "on") == 0) {
+ frefid_t fileref;
+
+ if (gagt_readlog_stream) {
+ gagt_normal_string("Glk read log is already on.\n");
+ return;
+ }
+
+ fileref = g_vm->glk_fileref_create_by_prompt(fileusage_InputRecord
+ | fileusage_BinaryMode,
+ filemode_Read, 0);
+ if (!fileref) {
+ gagt_standout_string("Glk read log failed.\n");
+ return;
+ }
+
+ if (!g_vm->glk_fileref_does_file_exist(fileref)) {
+ g_vm->glk_fileref_destroy(fileref);
+ gagt_standout_string("Glk read log failed.\n");
+ return;
+ }
+
+ gagt_readlog_stream = g_vm->glk_stream_open_file(fileref, filemode_Read, 0);
+ g_vm->glk_fileref_destroy(fileref);
+ if (!gagt_readlog_stream) {
+ gagt_standout_string("Glk read log failed.\n");
+ return;
+ }
+
+ gagt_normal_string("Glk read log is now on.\n");
+ }
+
+ else if (gagt_strcasecmp(argument, "off") == 0) {
+ if (!gagt_readlog_stream) {
+ gagt_normal_string("Glk read log is already off.\n");
+ return;
+ }
+
+ g_vm->glk_stream_close(gagt_readlog_stream, NULL);
+ gagt_readlog_stream = NULL;
+
+ gagt_normal_string("Glk read log is now off.\n");
+ }
+
+ else if (strlen(argument) == 0) {
+ gagt_normal_string("Glk read log is ");
+ gagt_normal_string(gagt_readlog_stream ? "on" : "off");
+ gagt_normal_string(".\n");
+ }
+
+ else {
+ gagt_normal_string("Glk read log can be ");
+ gagt_standout_string("on");
+ gagt_normal_string(", or ");
+ gagt_standout_string("off");
+ gagt_normal_string(".\n");
+ }
+}
+
+
+/*
+ * gagt_command_abbreviations()
+ *
+ * Turn abbreviation expansions on and off.
+ */
+static void gagt_command_abbreviations(const char *argument) {
+ assert(argument);
+
+ if (gagt_strcasecmp(argument, "on") == 0) {
+ if (gagt_abbreviations_enabled) {
+ gagt_normal_string("Glk abbreviation expansions are already on.\n");
+ return;
+ }
+
+ gagt_abbreviations_enabled = TRUE;
+ gagt_normal_string("Glk abbreviation expansions are now on.\n");
+ }
+
+ else if (gagt_strcasecmp(argument, "off") == 0) {
+ if (!gagt_abbreviations_enabled) {
+ gagt_normal_string("Glk abbreviation expansions are already off.\n");
+ return;
+ }
+
+ gagt_abbreviations_enabled = FALSE;
+ gagt_normal_string("Glk abbreviation expansions are now off.\n");
+ }
+
+ else if (strlen(argument) == 0) {
+ gagt_normal_string("Glk abbreviation expansions are ");
+ gagt_normal_string(gagt_abbreviations_enabled ? "on" : "off");
+ gagt_normal_string(".\n");
+ }
+
+ else {
+ gagt_normal_string("Glk abbreviation expansions can be ");
+ gagt_standout_string("on");
+ gagt_normal_string(", or ");
+ gagt_standout_string("off");
+ gagt_normal_string(".\n");
+ }
+}
+
+
+/*
+ * gagt_command_fonts()
+ *
+ * Set the value for gagt_font_mode depending on the argument from the
+ * user's command escape.
+ *
+ * Despite our best efforts, font control may still be wrong in some games.
+ * This command gives us a chance to correct that.
+ */
+static void gagt_command_fonts(const char *argument) {
+ assert(argument);
+
+ if (gagt_strcasecmp(argument, "fixed") == 0) {
+ if (gagt_font_mode == FONT_FIXED_WIDTH) {
+ gagt_normal_string("Glk font control is already 'fixed'.\n");
+ return;
+ }
+
+ gagt_font_mode = FONT_FIXED_WIDTH;
+ gagt_normal_string("Glk font control is now 'fixed'.\n");
+ }
+
+ else if (gagt_strcasecmp(argument, "variable") == 0
+ || gagt_strcasecmp(argument, "proportional") == 0) {
+ if (gagt_font_mode == FONT_PROPORTIONAL) {
+ gagt_normal_string("Glk font control is already 'proportional'.\n");
+ return;
+ }
+
+ gagt_font_mode = FONT_PROPORTIONAL;
+ gagt_normal_string("Glk font control is now 'proportional'.\n");
+ }
+
+ else if (gagt_strcasecmp(argument, "auto") == 0
+ || gagt_strcasecmp(argument, "automatic") == 0) {
+ if (gagt_font_mode == FONT_AUTOMATIC) {
+ gagt_normal_string("Glk font control is already 'automatic'.\n");
+ return;
+ }
+
+ gagt_font_mode = FONT_AUTOMATIC;
+ gagt_normal_string("Glk font control is now 'automatic'.\n");
+ }
+
+ else if (gagt_strcasecmp(argument, "debug") == 0) {
+ if (gagt_font_mode == FONT_DEBUG) {
+ gagt_normal_string("Glk font control is already 'debug'.\n");
+ return;
+ }
+
+ gagt_font_mode = FONT_DEBUG;
+ gagt_normal_string("Glk font control is now 'debug'.\n");
+ }
+
+ else if (strlen(argument) == 0) {
+ gagt_normal_string("Glk font control is set to '");
+ switch (gagt_font_mode) {
+ case FONT_AUTOMATIC:
+ gagt_normal_string("automatic");
+ break;
+
+ case FONT_FIXED_WIDTH:
+ gagt_normal_string("fixed");
+ break;
+
+ case FONT_PROPORTIONAL:
+ gagt_normal_string("proportional");
+ break;
+
+ case FONT_DEBUG:
+ gagt_normal_string("debug");
+ break;
+
+ default:
+ gagt_fatal("GLK: Invalid font mode encountered");
+ gagt_exit();
+ }
+ gagt_normal_string("'.\n");
+ }
+
+ else {
+ /* Avoid mentioning the debug setting. */
+ gagt_normal_string("Glk font control can be ");
+ gagt_standout_string("fixed");
+ gagt_normal_string(", ");
+ gagt_standout_string("proportional");
+ gagt_normal_string(", or ");
+ gagt_standout_string("automatic");
+ gagt_normal_string(".\n");
+ }
+}
+
+
+/*
+ * gagt_command_delays()
+ *
+ * Set a value for gagt_delay_mode depending on the argument from
+ * the user's command escape.
+ */
+static void gagt_command_delays(const char *argument) {
+ assert(argument);
+
+ if (!g_vm->glk_gestalt(gestalt_Timer, 0)) {
+ gagt_normal_string("Glk delays are not available.\n");
+ return;
+ }
+
+ if (gagt_strcasecmp(argument, "full") == 0
+ || gagt_strcasecmp(argument, "on") == 0) {
+ if (gagt_delay_mode == DELAY_FULL) {
+ gagt_normal_string("Glk delay mode is already 'full'.\n");
+ return;
+ }
+
+ gagt_delay_mode = DELAY_FULL;
+ gagt_normal_string("Glk delay mode is now 'full'.\n");
+ }
+
+ else if (gagt_strcasecmp(argument, "short") == 0
+ || gagt_strcasecmp(argument, "half") == 0) {
+ if (gagt_delay_mode == DELAY_SHORT) {
+ gagt_normal_string("Glk delay mode is already 'short'.\n");
+ return;
+ }
+
+ gagt_delay_mode = DELAY_SHORT;
+ gagt_normal_string("Glk delay mode is now 'short'.\n");
+ }
+
+ else if (gagt_strcasecmp(argument, "none") == 0
+ || gagt_strcasecmp(argument, "off") == 0) {
+ if (gagt_delay_mode == DELAY_OFF) {
+ gagt_normal_string("Glk delay mode is already 'none'.\n");
+ return;
+ }
+
+ gagt_delay_mode = DELAY_OFF;
+ gagt_normal_string("Glk delay mode is now 'none'.\n");
+ }
+
+ else if (strlen(argument) == 0) {
+ gagt_normal_string("Glk delay mode is set to '");
+ switch (gagt_delay_mode) {
+ case DELAY_FULL:
+ gagt_normal_string("full");
+ break;
+
+ case DELAY_SHORT:
+ gagt_normal_string("short");
+ break;
+
+ case DELAY_OFF:
+ gagt_normal_string("none");
+ break;
+
+ default:
+ gagt_fatal("GLK: Invalid delay mode encountered");
+ gagt_exit();
+ }
+ gagt_normal_string("'.\n");
+ }
+
+ else {
+ gagt_normal_string("Glk delay mode can be ");
+ gagt_standout_string("full");
+ gagt_normal_string(", ");
+ gagt_standout_string("short");
+ gagt_normal_string(", or ");
+ gagt_standout_string("none");
+ gagt_normal_string(".\n");
+ }
+}
+
+
+/*
+ * gagt_command_width()
+ *
+ * Print out the (approximate) display width, from status_width. It's
+ * approximate because the main window might include a scrollbar that
+ * the status window doesn't have, may use a different size font, and so
+ * on. But the main window won't tell us a width at all - it always
+ * returns zero. If we don't happen to have a status window available
+ * to us, there's not much we can say.
+ *
+ * Note that this function uses the interpreter variable status_width,
+ * so it's important to keep this updated with the current window size at
+ * all times.
+ */
+static void gagt_command_width(const char *argument) {
+ char buffer[16];
+ assert(argument);
+
+ if (!gagt_status_window) {
+ gagt_normal_string("Glk's current display width is unknown.\n");
+ return;
+ }
+
+ gagt_normal_string("Glk's current display width is approximately ");
+ sprintf(buffer, "%d", status_width);
+ gagt_normal_string(buffer);
+ gagt_normal_string(status_width == 1 ? " character" : " characters");
+ gagt_normal_string(".\n");
+}
+
+
+/*
+ * gagt_command_replacements()
+ *
+ * Turn Glk special paragraph replacement on and off.
+ */
+static void gagt_command_replacements(const char *argument) {
+ assert(argument);
+
+ if (gagt_strcasecmp(argument, "on") == 0) {
+ if (gagt_replacement_enabled) {
+ gagt_normal_string("Glk replacements are already on.\n");
+ return;
+ }
+
+ gagt_replacement_enabled = TRUE;
+ gagt_normal_string("Glk replacements are now on.\n");
+ }
+
+ else if (gagt_strcasecmp(argument, "off") == 0) {
+ if (!gagt_replacement_enabled) {
+ gagt_normal_string("Glk replacements are already off.\n");
+ return;
+ }
+
+ gagt_replacement_enabled = FALSE;
+ gagt_normal_string("Glk replacements are now off.\n");
+ }
+
+ else if (strlen(argument) == 0) {
+ gagt_normal_string("Glk replacements are ");
+ gagt_normal_string(gagt_replacement_enabled ? "on" : "off");
+ gagt_normal_string(".\n");
+ }
+
+ else {
+ gagt_normal_string("Glk replacements can be ");
+ gagt_standout_string("on");
+ gagt_normal_string(", or ");
+ gagt_standout_string("off");
+ gagt_normal_string(".\n");
+ }
+}
+
+
+/*
+ * gagt_command_statusline()
+ *
+ * Turn the extended status line on and off.
+ */
+static void gagt_command_statusline(const char *argument) {
+ assert(argument);
+
+ if (!gagt_status_window) {
+ gagt_normal_string("Glk status window is not available.\n");
+ return;
+ }
+
+ if (gagt_strcasecmp(argument, "extended") == 0
+ || gagt_strcasecmp(argument, "full") == 0) {
+ if (gagt_extended_status_enabled) {
+ gagt_normal_string("Glk status line mode is already 'extended'.\n");
+ return;
+ }
+
+ /* Expand the status window down to a second line. */
+ g_vm->glk_window_set_arrangement(g_vm->glk_window_get_parent(gagt_status_window),
+ winmethod_Above | winmethod_Fixed, 2, NULL);
+ gagt_extended_status_enabled = TRUE;
+
+ gagt_normal_string("Glk status line mode is now 'extended'.\n");
+ }
+
+ else if (gagt_strcasecmp(argument, "short") == 0
+ || gagt_strcasecmp(argument, "normal") == 0) {
+ if (!gagt_extended_status_enabled) {
+ gagt_normal_string("Glk status line mode is already 'short'.\n");
+ return;
+ }
+
+ /* Shrink the status window down to one line. */
+ g_vm->glk_window_set_arrangement(g_vm->glk_window_get_parent(gagt_status_window),
+ winmethod_Above | winmethod_Fixed, 1, NULL);
+ gagt_extended_status_enabled = FALSE;
+
+ gagt_normal_string("Glk status line mode is now 'short'.\n");
+ }
+
+ else if (strlen(argument) == 0) {
+ gagt_normal_string("Glk status line mode is set to '");
+ gagt_normal_string(gagt_extended_status_enabled ? "extended" : "short");
+ gagt_normal_string("'.\n");
+ }
+
+ else {
+ gagt_normal_string("Glk status line can be ");
+ gagt_standout_string("extended");
+ gagt_normal_string(", or ");
+ gagt_standout_string("short");
+ gagt_normal_string(".\n");
+ }
+}
+
+
+/*
+ * gagt_command_print_version_number()
+ * gagt_command_version()
+ *
+ * Print out the Glk library version number.
+ */
+static void gagt_command_print_version_number(glui32 version) {
+ char buffer[64];
+
+ sprintf(buffer, "%u.%u.%u",
+ version >> 16, (version >> 8) & 0xff, version & 0xff);
+ gagt_normal_string(buffer);
+}
+
+static void gagt_command_version(const char *argument) {
+ glui32 version;
+ assert(argument);
+
+ gagt_normal_string("This is version ");
+ gagt_command_print_version_number(GAGT_PORT_VERSION);
+ gagt_normal_string(" of the Glk AGiliTy port.\n");
+
+ version = g_vm->glk_gestalt(gestalt_Version, 0);
+ gagt_normal_string("The Glk library version is ");
+ gagt_command_print_version_number(version);
+ gagt_normal_string(".\n");
+}
+
+
+/*
+ * gagt_command_commands()
+ *
+ * Turn command escapes off. Once off, there's no way to turn them back on.
+ * Commands must be on already to enter this function.
+ */
+static void gagt_command_commands(const char *argument) {
+ assert(argument);
+
+ if (gagt_strcasecmp(argument, "on") == 0) {
+ gagt_normal_string("Glk commands are already on.\n");
+ }
+
+ else if (gagt_strcasecmp(argument, "off") == 0) {
+ gagt_commands_enabled = FALSE;
+ gagt_normal_string("Glk commands are now off.\n");
+ }
+
+ else if (strlen(argument) == 0) {
+ gagt_normal_string("Glk commands are ");
+ gagt_normal_string(gagt_commands_enabled ? "on" : "off");
+ gagt_normal_string(".\n");
+ }
+
+ else {
+ gagt_normal_string("Glk commands can be ");
+ gagt_standout_string("on");
+ gagt_normal_string(", or ");
+ gagt_standout_string("off");
+ gagt_normal_string(".\n");
+ }
+}
+
+/* Glk subcommands and handler functions. */
+struct gagt_command_t {
+ const char *const command; /* Glk subcommand. */
+ void (* const handler)(const char *argument); /* Subcommand handler. */
+ const int takes_argument; /* Argument flag. */
+} ;
+typedef const gagt_command_t *gagt_commandref_t;
+
+static void gagt_command_summary(const char *argument);
+static void gagt_command_help(const char *argument);
+
+static gagt_command_t GAGT_COMMAND_TABLE[] = {
+ {"summary", gagt_command_summary, FALSE},
+ {"script", gagt_command_script, TRUE},
+ {"inputlog", gagt_command_inputlog, TRUE},
+ {"readlog", gagt_command_readlog, TRUE},
+ {"abbreviations", gagt_command_abbreviations, TRUE},
+ {"fonts", gagt_command_fonts, TRUE},
+ {"delays", gagt_command_delays, TRUE},
+ {"width", gagt_command_width, FALSE},
+ {"replacements", gagt_command_replacements, TRUE},
+ {"statusline", gagt_command_statusline, TRUE},
+ {"version", gagt_command_version, FALSE},
+ {"commands", gagt_command_commands, TRUE},
+ {"help", gagt_command_help, TRUE},
+ {NULL, NULL, FALSE}
+};
+
+
+/*
+ * gagt_command_summary()
+ *
+ * Report all current Glk settings.
+ */
+static void gagt_command_summary(const char *argument) {
+ gagt_commandref_t entry;
+ assert(argument);
+
+ /*
+ * Call handlers that have status to report with an empty argument,
+ * prompting each to print its current setting.
+ */
+ for (entry = GAGT_COMMAND_TABLE; entry->command; entry++) {
+ if (entry->handler == gagt_command_summary
+ || entry->handler == gagt_command_help)
+ continue;
+
+ entry->handler("");
+ }
+}
+
+
+/*
+ * gagt_command_help()
+ *
+ * Document the available Glk cmds.
+ */
+static void gagt_command_help(const char *cmd) {
+ gagt_commandref_t entry, matched;
+ assert(cmd);
+
+ if (strlen(cmd) == 0) {
+ gagt_normal_string("Glk cmds are");
+ for (entry = GAGT_COMMAND_TABLE; entry->command; entry++) {
+ gagt_commandref_t next;
+
+ next = entry + 1;
+ gagt_normal_string(next->command ? " " : " and ");
+ gagt_standout_string(entry->command);
+ gagt_normal_string(next->command ? "," : ".\n\n");
+ }
+
+ gagt_normal_string("Glk cmds may be abbreviated, as long as"
+ " the abbreviation is unambiguous. Use ");
+ gagt_standout_string("glk help");
+ gagt_normal_string(" followed by a Glk cmd name for help on that"
+ " cmd.\n");
+ return;
+ }
+
+ matched = NULL;
+ for (entry = GAGT_COMMAND_TABLE; entry->command; entry++) {
+ if (gagt_strncasecmp(cmd, entry->command, strlen(cmd)) == 0) {
+ if (matched) {
+ gagt_normal_string("The Glk cmd ");
+ gagt_standout_string(cmd);
+ gagt_normal_string(" is ambiguous. Try ");
+ gagt_standout_string("glk help");
+ gagt_normal_string(" for more information.\n");
+ return;
+ }
+ matched = entry;
+ }
+ }
+ if (!matched) {
+ gagt_normal_string("The Glk cmd ");
+ gagt_standout_string(cmd);
+ gagt_normal_string(" is not valid. Try ");
+ gagt_standout_string("glk help");
+ gagt_normal_string(" for more information.\n");
+ return;
+ }
+
+ if (matched->handler == gagt_command_summary) {
+ gagt_normal_string("Prints a summary of all the current Glk AGiliTy"
+ " settings.\n");
+ }
+
+ else if (matched->handler == gagt_command_script) {
+ gagt_normal_string("Logs the game's output to a file.\n\nUse ");
+ gagt_standout_string("glk script on");
+ gagt_normal_string(" to begin logging game output, and ");
+ gagt_standout_string("glk script off");
+ gagt_normal_string(" to end it. Glk AGiliTy will ask you for a file"
+ " when you turn scripts on.\n");
+ }
+
+ else if (matched->handler == gagt_command_inputlog) {
+ gagt_normal_string("Records the cmds you type into a game.\n\nUse ");
+ gagt_standout_string("glk inputlog on");
+ gagt_normal_string(", to begin recording your cmds, and ");
+ gagt_standout_string("glk inputlog off");
+ gagt_normal_string(" to turn off input logs. You can play back"
+ " recorded cmds into a game with the ");
+ gagt_standout_string("glk readlog");
+ gagt_normal_string(" cmd.\n");
+ }
+
+ else if (matched->handler == gagt_command_readlog) {
+ gagt_normal_string("Plays back cmds recorded with ");
+ gagt_standout_string("glk inputlog on");
+ gagt_normal_string(".\n\nUse ");
+ gagt_standout_string("glk readlog on");
+ gagt_normal_string(". cmd play back stops at the end of the"
+ " file. You can also play back cmds from a"
+ " text file created using any standard editor.\n");
+ }
+
+ else if (matched->handler == gagt_command_abbreviations) {
+ gagt_normal_string("Controls abbreviation expansion.\n\nGlk AGiliTy"
+ " automatically expands several standard single"
+ " letter abbreviations for you; for example, \"x\""
+ " becomes \"examine\". Use ");
+ gagt_standout_string("glk abbreviations on");
+ gagt_normal_string(" to turn this feature on, and ");
+ gagt_standout_string("glk abbreviations off");
+ gagt_normal_string(" to turn it off. While the feature is on, you"
+ " can bypass abbreviation expansion for an"
+ " individual game cmd by prefixing it with a"
+ " single quote.\n");
+ }
+
+ else if (matched->handler == gagt_command_fonts) {
+ gagt_normal_string("Controls the way Glk AGiliTy uses fonts.\n\n"
+ "AGT games normally assume 80x25 monospaced font"
+ " displays. Glk can often use proportional fonts."
+ " To try to improve text display, Glk AGiliTy will"
+ " attempt to automatically detect when game text"
+ " can be displayed safely in a proportional font,"
+ " and when fixed width fonts are required. For"
+ " some games, however, you may need to override"
+ " it. Use ");
+ gagt_standout_string("glk fonts automatic");
+ gagt_normal_string(", ");
+ gagt_standout_string("glk fonts proportional");
+ gagt_normal_string(", and ");
+ gagt_standout_string("glk fonts fixed");
+ gagt_normal_string(" to switch between Glk AGiliTy font modes.\n");
+ }
+
+ else if (matched->handler == gagt_command_delays) {
+ gagt_normal_string("Shortens, or eliminates, AGT game delays.\n\nUse ");
+ gagt_standout_string("glk delays full");
+ gagt_normal_string(", ");
+ gagt_standout_string("glk delays short");
+ gagt_normal_string(", or ");
+ gagt_standout_string("glk delays none");
+ gagt_normal_string(". In Glk AGiliTy, you can also end an AGT game's"
+ " delay early, by pressing Space while the game is"
+ " delaying.\n");
+ }
+
+ else if (matched->handler == gagt_command_width) {
+ gagt_normal_string("Prints the screen width available for fixed font"
+ " display.\n\nEven though Glk AGiliTy tries to handle"
+ " issues surrounding proportional font displays for"
+ " you automatically, some game elements may still"
+ " need to display in fixed width fonts. These"
+ " elements will be happiest if the available screen"
+ " width is at least 80 columns.\n");
+ }
+
+ else if (matched->handler == gagt_command_replacements) {
+ gagt_normal_string("Controls game text scanning and replacement.\n\n"
+ "Glk AGiliTy can monitor the game's output, and"
+ " replace a few selected standard messages with"
+ " equivalents, printed using a style that stands"
+ " out better in Glk displays. Use ");
+ gagt_standout_string("glk replacements on");
+ gagt_normal_string(" to turn this feature on, and ");
+ gagt_standout_string("glk replacements off");
+ gagt_normal_string(" to turn it off.\n");
+ }
+
+ else if (matched->handler == gagt_command_statusline) {
+ gagt_normal_string("Controls the Glk AGiliTy status line display.\n\n"
+ "Use ");
+ gagt_standout_string("glk statusline extended");
+ gagt_normal_string(" to display a full, two line status display, and ");
+ gagt_standout_string("glk statusline short");
+ gagt_normal_string(" for a single line status display.\n");
+ }
+
+ else if (matched->handler == gagt_command_version) {
+ gagt_normal_string("Prints the version numbers of the Glk library"
+ " and the Glk AGiliTy port.\n");
+ }
+
+ else if (matched->handler == gagt_command_commands) {
+ gagt_normal_string("Turn off Glk cmds.\n\nUse ");
+ gagt_standout_string("glk cmds off");
+ gagt_normal_string(" to disable all Glk cmds, including this one."
+ " Once turned off, there is no way to turn Glk"
+ " cmds back on while inside the game.\n");
+ }
+
+ else if (matched->handler == gagt_command_help)
+ gagt_command_help("");
+
+ else
+ gagt_normal_string("There is no help available on that Glk cmd."
+ " Sorry.\n");
+}
+
+
+/*
+ * gagt_command_escape()
+ *
+ * This function is handed each input line. If the line contains a specific
+ * Glk port command, handle it and return TRUE, otherwise return FALSE.
+ */
+static int gagt_command_escape(const char *string) {
+ int posn;
+ char *string_copy, *cmd, *argument;
+ assert(string);
+
+ /*
+ * Return FALSE if the string doesn't begin with the Glk command escape
+ * introducer.
+ */
+ posn = strspn(string, "\t ");
+ if (gagt_strncasecmp(string + posn, "glk", strlen("glk")) != 0)
+ return FALSE;
+
+ /* Take a copy of the string, without any leading space or introducer. */
+ string_copy = (char *)gagt_malloc(strlen(string + posn) + 1 - strlen("glk"));
+ strcpy(string_copy, string + posn + strlen("glk"));
+
+ /*
+ * Find the subcommand; the first word in the string copy. Find its end,
+ * and ensure it terminates with a NUL.
+ */
+ posn = strspn(string_copy, "\t ");
+ cmd = string_copy + posn;
+ posn += strcspn(string_copy + posn, "\t ");
+ if (string_copy[posn] != '\0')
+ string_copy[posn++] = '\0';
+
+ /*
+ * Now find any argument data for the command, ensuring it too terminates
+ * with a NUL.
+ */
+ posn += strspn(string_copy + posn, "\t ");
+ argument = string_copy + posn;
+ posn += strcspn(string_copy + posn, "\t ");
+ string_copy[posn] = '\0';
+
+ /*
+ * Try to handle the command and argument as a Glk subcommand. If it
+ * doesn't run unambiguously, print command usage. Treat an empty command
+ * as "help".
+ */
+ if (strlen(cmd) > 0) {
+ gagt_commandref_t entry, matched;
+ int matches;
+
+ /*
+ * Search for the first unambiguous table cmd string matching
+ * the cmd passed in.
+ */
+ matches = 0;
+ matched = NULL;
+ for (entry = GAGT_COMMAND_TABLE; entry->command; entry++) {
+ if (gagt_strncasecmp(cmd, entry->command, strlen(cmd)) == 0) {
+ matches++;
+ matched = entry;
+ }
+ }
+
+ /* If the match was unambiguous, call the command handler. */
+ if (matches == 1) {
+ gagt_normal_char('\n');
+ matched->handler(argument);
+
+ if (!matched->takes_argument && strlen(argument) > 0) {
+ gagt_normal_string("[The ");
+ gagt_standout_string(matched->command);
+ gagt_normal_string(" cmd ignores arguments.]\n");
+ }
+ }
+
+ /* No match, or the cmd was ambiguous. */
+ else {
+ gagt_normal_string("\nThe Glk cmd ");
+ gagt_standout_string(cmd);
+ gagt_normal_string(" is ");
+ gagt_normal_string(matches == 0 ? "not valid" : "ambiguous");
+ gagt_normal_string(". Try ");
+ gagt_standout_string("glk help");
+ gagt_normal_string(" for more information.\n");
+ }
+ } else {
+ gagt_normal_char('\n');
+ gagt_command_help("");
+ }
+
+ /* The string contained a Glk cmd; return TRUE. */
+ free(string_copy);
+ return TRUE;
+}
+
+
+/*---------------------------------------------------------------------*/
+/* Glk port input functions */
+/*---------------------------------------------------------------------*/
+
+/* Longest line we're going to buffer for input. */
+enum { GAGT_INPUTBUFFER_LENGTH = 256 };
+
+/* Table of single-character command abbreviations. */
+typedef const struct {
+ const char abbreviation; /* Abbreviation character. */
+ const char *const expansion; /* Expansion string. */
+} gagt_abbreviation_t;
+typedef gagt_abbreviation_t *gagt_abbreviationref_t;
+
+static gagt_abbreviation_t GAGT_ABBREVIATIONS[] = {
+ {'c', "close"}, {'g', "again"}, {'i', "inventory"},
+ {'k', "attack"}, {'l', "look"}, {'p', "open"},
+ {'q', "quit"}, {'r', "drop"}, {'t', "take"},
+ {'x', "examine"}, {'y', "yes"}, {'z', "wait"},
+ {'\0', NULL}
+};
+
+
+/*
+ * gagt_expand_abbreviations()
+ *
+ * Expand a few common one-character abbreviations commonly found in other
+ * game systems, but not always normal in AGT games.
+ */
+static void gagt_expand_abbreviations(char *buffer, int size) {
+ char *command_, abbreviation;
+ const char *expansion;
+ gagt_abbreviationref_t entry;
+ assert(buffer);
+
+ /* Ignore anything that isn't a single letter command_. */
+ command_ = buffer + strspn(buffer, "\t ");
+ if (!(strlen(command_) == 1
+ || (strlen(command_) > 1 && isspace(command_[1]))))
+ return;
+
+ /* Scan the abbreviations table for a match. */
+ abbreviation = g_vm->glk_char_to_lower((unsigned char) command_[0]);
+ expansion = NULL;
+ for (entry = GAGT_ABBREVIATIONS; entry->expansion; entry++) {
+ if (entry->abbreviation == abbreviation) {
+ expansion = entry->expansion;
+ break;
+ }
+ }
+
+ /*
+ * If a match found, check for a fit, then replace the character with the
+ * expansion string.
+ */
+ if (expansion) {
+ if ((int)strlen(buffer) + (int)strlen(expansion) - 1 >= size)
+ return;
+
+ memmove(command_ + strlen(expansion) - 1, command_, strlen(command_) + 1);
+ memcpy(command_, expansion, strlen(expansion));
+
+ gagt_standout_string("[");
+ gagt_standout_char(abbreviation);
+ gagt_standout_string(" -> ");
+ gagt_standout_string(expansion);
+ gagt_standout_string("]\n");
+ }
+}
+
+
+/*
+ * agt_input()
+ *
+ * Read a line from the keyboard, allocating space for it using malloc.
+ * AGiliTy defines the following for the in_type argument:
+ *
+ * in_type: 0=command, 1=number, 2=question, 3=userstr, 4=filename,
+ * 5=RESTART,RESTORE,UNDO,QUIT
+ * Negative values are for internal use by the interface (i.e. this module)
+ * and so are free to be defined by the porter.
+ *
+ * Since it's unclear what use we can make of this information in Glk,
+ * for the moment the argument is ignored. It seems that no-one else
+ * uses it, either.
+ */
+char *agt_input(int in_type) {
+ event_t event;
+ int length;
+ char *buffer;
+
+ /*
+ * Update the current status line display, and flush any pending buffered
+ * output. Release any suspension of delays.
+ */
+ gagt_status_notify();
+ gagt_output_flush();
+ gagt_delay_resume();
+
+ /* Reset current x, as line input implies a newline. */
+ curr_x = 0;
+
+ /* Allocate a line input buffer, allowing 256 characters and a NUL. */
+ length = GAGT_INPUTBUFFER_LENGTH + 1;
+ buffer = (char *)gagt_malloc(length);
+
+ /*
+ * If we have an input log to read from, use that until it is exhausted.
+ * On end of file, close the stream and resume input from line requests.
+ */
+ if (gagt_readlog_stream) {
+ glui32 chars;
+
+ /* Get the next line from the log stream. */
+ chars = g_vm->glk_get_line_stream(gagt_readlog_stream, buffer, length);
+ if (chars > 0) {
+ /* Echo the line just read in input style. */
+ g_vm->glk_set_style(style_Input);
+ g_vm->glk_put_buffer(buffer, chars);
+ g_vm->glk_set_style(style_Normal);
+
+ /*
+ * Convert the string from Glk's ISO 8859 Latin-1 to IBM cp 437,
+ * add to any script, and return it.
+ */
+ gagt_iso_to_cp((const uchar *)buffer, (uchar *)buffer);
+ if (script_on)
+ textputs(scriptfile, buffer);
+ return buffer;
+ }
+
+ /*
+ * We're at the end of the log stream. Close it, and then continue
+ * on to request a line from Glk.
+ */
+ g_vm->glk_stream_close(gagt_readlog_stream, NULL);
+ gagt_readlog_stream = NULL;
+ }
+
+ /* Set this up as a read buffer for the main window, and wait. */
+ g_vm->glk_request_line_event(gagt_main_window, buffer, length - 1, 0);
+ gagt_event_wait(evtype_LineInput, &event);
+
+ /* Terminate the input line with a NUL. */
+ assert((int)event.val1 < length);
+ buffer[event.val1] = '\0';
+
+ /*
+ * If neither abbreviations nor local commands are enabled, use the data
+ * read above without further massaging.
+ */
+ if (gagt_abbreviations_enabled || gagt_commands_enabled) {
+ char *cmd;
+
+ /*
+ * If the first non-space input character is a quote, bypass all
+ * abbreviation expansion and local command recognition, and use the
+ * unadulterated input, less introductory quote.
+ */
+ cmd = buffer + strspn(buffer, "\t ");
+ if (cmd[0] == '\'') {
+ /* Delete the quote with memmove(). */
+ memmove(cmd, cmd + 1, strlen(cmd));
+ } else {
+ /* Check for, and expand, any abbreviated commands. */
+ if (gagt_abbreviations_enabled)
+ gagt_expand_abbreviations(buffer, length);
+
+ /*
+ * Check for standalone "help", then for Glk port special commands;
+ * suppress the interpreter's use of this input for Glk commands.
+ */
+ if (gagt_commands_enabled) {
+ int posn;
+
+ posn = strspn(buffer, "\t ");
+ if (gagt_strncasecmp(buffer + posn, "help", strlen("help")) == 0) {
+ if (strspn(buffer + posn + strlen("help"), "\t ")
+ == strlen(buffer + posn + strlen("help"))) {
+ gagt_display_register_help_request();
+ }
+ }
+
+ if (gagt_command_escape(buffer)) {
+ gagt_display_silence_help_hints();
+ buffer[0] = '\0';
+ return buffer;
+ }
+ }
+ }
+ }
+
+ /*
+ * If there is an input log active, log this input string to it. Note that
+ * by logging here we get any abbreviation expansions but we won't log glk
+ * special commands, nor any input read from a current open input log.
+ */
+ if (gagt_inputlog_stream) {
+ g_vm->glk_put_string_stream(gagt_inputlog_stream, buffer);
+ g_vm->glk_put_char_stream(gagt_inputlog_stream, '\n');
+ }
+
+ /*
+ * Convert from Glk's ISO 8859 Latin-1 to IBM cp 437, and add to any script.
+ */
+ gagt_iso_to_cp((const uchar *)buffer, (uchar *)buffer);
+ if (script_on)
+ textputs(scriptfile, buffer);
+
+ gagt_debug("agt_input", "in_type=%d -> '%s'", in_type, buffer);
+ return buffer;
+}
+
+
+/*
+ * agt_getkey()
+ *
+ * Read a single character and return it. AGiliTy defines the echo_char
+ * argument as:
+ *
+ * If echo_char=1, echo character. If 0, then the character is not
+ * required to be echoed (and ideally shouldn't be).
+ *
+ * However, I've found that not all other ports really do this, and in
+ * practice it doesn't always look right. So for Glk, the character is
+ * always echoed to the main window.
+ */
+char agt_getkey(rbool echo_char) {
+ event_t event;
+ char buffer[3];
+ assert(g_vm->glk_stream_get_current());
+
+ /*
+ * Update the current status line display, and flush any pending buffered
+ * output. Release any suspension of delays.
+ */
+ gagt_status_notify();
+ gagt_output_flush();
+ gagt_delay_resume();
+
+ /* Reset current x, as echoed character input implies a newline. */
+ curr_x = 0;
+
+ /*
+ * If we have an input log to read from, use that as above until it is
+ * exhausted. We take just the first character of a given line.
+ */
+ if (gagt_readlog_stream) {
+ glui32 chars;
+ char logbuffer[GAGT_INPUTBUFFER_LENGTH + 1];
+
+ /* Get the next line from the log stream. */
+ chars = g_vm->glk_get_line_stream(gagt_readlog_stream,
+ logbuffer, sizeof(logbuffer));
+ if (chars > 0) {
+ /* Take just the first character, adding a newline if necessary. */
+ buffer[0] = logbuffer[0];
+ buffer[1] = buffer[0] == '\n' ? '\0' : '\n';
+ buffer[2] = '\0';
+
+ /* Echo the character just read in input style. */
+ g_vm->glk_set_style(style_Input);
+ g_vm->glk_put_string(buffer);
+ g_vm->glk_set_style(style_Normal);
+
+ /*
+ * Convert from Glk's ISO 8859 Latin-1 to IBM cp 437, add to any
+ * script, and return the character.
+ */
+ gagt_iso_to_cp((const uchar *)buffer, (uchar *)buffer);
+ if (script_on)
+ textputs(scriptfile, buffer);
+ return buffer[0];
+ }
+
+ /*
+ * We're at the end of the log stream. Close it, and then continue
+ * on to request a character from Glk.
+ */
+ g_vm->glk_stream_close(gagt_readlog_stream, NULL);
+ gagt_readlog_stream = NULL;
+ }
+
+ /*
+ * Request a single character from main window, and wait. Ignore non-
+ * ASCII codes that Glk returns for special function keys; we just want
+ * one ASCII return value. (Glk does treat Return as a special key,
+ * though, and we want to pass that back as ASCII return.)
+ */
+ do {
+ g_vm->glk_request_char_event(gagt_main_window);
+ gagt_event_wait(evtype_CharInput, &event);
+ } while (event.val1 > BYTE_MAX_VAL && event.val1 != keycode_Return);
+
+ /*
+ * Save the character into a short string buffer, converting Return
+ * to newline, and adding a newline if not Return.
+ */
+ buffer[0] = event.val1 == keycode_Return ? '\n' : event.val1;
+ buffer[1] = buffer[0] == '\n' ? '\0' : '\n';
+ buffer[2] = '\0';
+
+ /* If there is an input log active, log this input string to it. */
+ if (gagt_inputlog_stream)
+ g_vm->glk_put_string_stream(gagt_inputlog_stream, buffer);
+
+ /*
+ * No matter what echo_char says, as it happens, the output doesn't look
+ * great if we don't write out the character, and also a newline (c.f.
+ * the "Yes/No" confirmation of the QUIT command)...
+ */
+ g_vm->glk_set_style(style_Input);
+ g_vm->glk_put_string(buffer);
+ g_vm->glk_set_style(style_Normal);
+
+ /*
+ * Convert from Glk's ISO 8859 Latin-1 to IBM cp 437, and add to any
+ * script.
+ */
+ gagt_iso_to_cp((const uchar *)buffer, (uchar *)buffer);
+ if (script_on)
+ textputs(scriptfile, buffer);
+
+ gagt_debug("agt_getkey", "echo_char=%d -> '%c'",
+ echo_char, buffer[0] == '\n' ? '$' : buffer[0]);
+ return buffer[0];
+}
+
+
+/*---------------------------------------------------------------------*/
+/* Glk port event functions */
+/*---------------------------------------------------------------------*/
+
+/*
+ * We have some clever atexit() finalizer handling for exit() calls that
+ * come from the core interpreter. However, an exit() call could also come
+ * from Glk; Xkill for example. To tell the difference, we'll have the
+ * event wait functions set a flag to indicate when g_vm->glk_select() is active.
+ */
+static int gagt_in_glk_select = FALSE;
+
+/*
+ * gagt_event_wait_2()
+ * gagt_event_wait()
+ *
+ * Process Glk events until one of the expected type, or types, arrives.
+ * Return the event of that type.
+ */
+static void gagt_event_wait_2(glui32 wait_type_1, glui32 wait_type_2, event_t *event) {
+ assert(event);
+
+ do {
+ gagt_in_glk_select = TRUE;
+ g_vm->glk_select(event);
+ gagt_in_glk_select = FALSE;
+
+ switch (event->type) {
+ case evtype_Arrange:
+ case evtype_Redraw:
+ gagt_status_redraw();
+ break;
+ default:
+ break;
+ }
+ } while (!(event->type == (EvType)wait_type_1 || event->type == (EvType)wait_type_2));
+}
+
+static void gagt_event_wait(glui32 wait_type, event_t *event) {
+ assert(event);
+ gagt_event_wait_2(wait_type, evtype_None, event);
+}
+
+
+/*
+ * gagt_event_in_glk_select()
+ *
+ * Return TRUE if we're currently awaiting an event in g_vm->glk_select(). Used
+ * by the finalizer to distinguish interpreter and glk exit() calls.
+ */
+static int gagt_event_in_glk_select() {
+ return gagt_in_glk_select;
+}
+
+
+/*---------------------------------------------------------------------*/
+/* Miscellaneous Glk port startup and options functions */
+/*---------------------------------------------------------------------*/
+
+/*
+ * Default screen height and width, and also a default status width for
+ * use with Glk libraries that don't support separate windows.
+ */
+static const int GAGT_DEFAULT_SCREEN_WIDTH = 80,
+ GAGT_DEFAULT_SCREEN_HEIGHT = 25,
+ GAGT_DEFAULT_STATUS_WIDTH = 76;
+
+
+/*
+ * agt_option()
+ *
+ * Platform-specific setup and options handling. AGiliTy defines the
+ * arguments and options as:
+ *
+ * If setflag is 0, then the option was prefixed with NO_. Return 1 if
+ * the option is recognized.
+ *
+ * The Glk port has no options file handling, so none of this is
+ * implemented here.
+ */
+rbool agt_option(int optnum, char *optstr[], rbool setflag) {
+ gagt_debug("agt_option", "optnum=%d, optstr=%s, setflag=%d",
+ optnum, optstr[0], setflag);
+ return 0;
+}
+
+
+/*
+ * agt_globalfile()
+ *
+ * Global options file handle handling. For now, this is a stub, since
+ * there is no .agilrc for this port.
+ */
+genfile agt_globalfile(int fid) {
+ gagt_debug("agt_globalfile", "fid=%d", fid);
+ return badfile(fCFG);
+}
+
+
+/*
+ * init_interface()
+ *
+ * General initialization for the module; sets some variables, and creates
+ * the Glk windows to work in. Called from the AGiliTy main().
+ */
+void init_interface(int argc, char *argv[]) {
+ glui32 status_height;
+
+ /*
+ * Begin with some default values for global variables that this module
+ * is somehow responsible for.
+ */
+ script_on = center_on = par_fill_on = FALSE;
+ scriptfile = badfile(fSCR);
+ debugfile = nullptr; // stderr;
+
+ /*
+ * Set up AGT-specific Glk styles. This needs to be done before any Glk
+ * window is opened.
+ */
+ gagt_init_user_styles();
+
+ /*
+ * Create the main game window. The main game window creation must succeed.
+ * If it fails, we'll return, and the caller can detect this by looking
+ * for a NULL main window.
+ */
+ gagt_main_window = g_vm->glk_window_open(0, 0, 0, wintype_TextBuffer, 0);
+ if (!gagt_main_window)
+ return;
+
+ /*
+ * Set the main window to be the default window, for convenience. We do
+ * this again in glk_main() -- this call is here just in case this version
+ * of init_interface() is ever called by AGiliTy's main.
+ */
+ g_vm->glk_set_window(gagt_main_window);
+
+ /*
+ * Screen height is something we don't use. Linux Xglk returns dimensions
+ * of 0x0 for text buffer windows, so we can't measure the main window
+ * height anyway. But... the height does come into play in AGiliTy's
+ * agil.c, when the interpreter is deciding how to output game titles, and
+ * how much of its own subsequent verbiage to output. This gives us a
+ * problem, since this "verbiage" is stuff we look for and replace with
+ * our own special text. So... sigh, set 25, and try to cope in the
+ * special text we've set up with all the variations that ensue.
+ *
+ * Screen width does get used, but so, so many games, and for that matter
+ * the interpreter itself, assume 80 chars, so it's simplest just to set,
+ * and keep, this, and put up with the minor odd effects (making it match
+ * status_width, or making it something like MAX_INT to defeat the game's
+ * own wrapping, gives a lot of odder effects, trust me on this one...).
+ */
+ screen_width = GAGT_DEFAULT_SCREEN_WIDTH;
+ screen_height = GAGT_DEFAULT_SCREEN_HEIGHT;
+
+ /*
+ * Create a status window, with one or two lines as selected by user
+ * options or flags. We can live without a status window if we have to.
+ */
+ status_height = gagt_extended_status_enabled ? 2 : 1;
+ g_vm->glk_stylehint_set(wintype_TextGrid, style_User1, stylehint_ReverseColor, 1);
+ gagt_status_window = g_vm->glk_window_open(gagt_main_window,
+ winmethod_Above | winmethod_Fixed,
+ status_height, wintype_TextGrid, 0);
+ if (gagt_status_window) {
+ /*
+ * Call gagt_status_redraw() to set the interpreter's status_width
+ * variable initial value.
+ */
+ gagt_status_redraw();
+ } else {
+ /*
+ * No status window, so set a suitable default status width. In this
+ * case, we're using a value four characters less than the set screen
+ * width. AGiliTy's status line code will fill to this width with
+ * justified text, and we add two characters of bracketing when
+ * displaying status lines for Glks that don't support separate windows,
+ * making a total of 78 characters, which should be fairly standard.
+ */
+ status_width = GAGT_DEFAULT_STATUS_WIDTH;
+ }
+
+ agt_clrscr();
+ gagt_debug("init_interface", "argc=%d, argv=%p", argc, argv);
+}
+
+
+/*---------------------------------------------------------------------*/
+/* Replacement interface.c functions */
+/*---------------------------------------------------------------------*/
+
+/* Get_user_file() type codes. */
+enum {
+ AGT_SCRIPT = 0,
+ AGT_SAVE = 1,
+ AGT_RESTORE = 2,
+ AGT_LOG_READ = 3,
+ AGT_LOG_WRITE = 4
+};
+
+/* Longest acceptable filename. */
+enum { GAGT_MAX_PATH = 1024 };
+
+
+#ifdef GLK_ANSI_ONLY
+/*
+ * gagt_confirm()
+ *
+ * Print a confirmation prompt, and read a single input character, taking
+ * only [YyNn] input. If the character is 'Y' or 'y', return TRUE.
+ *
+ * This function is only required for the ANSI version of get_user_file().
+ */
+static int
+gagt_confirm(const char *prompt) {
+ event_t event;
+ unsigned char response;
+ assert(prompt);
+
+ /*
+ * Print the confirmation prompt, in a style that hints that it's from the
+ * interpreter, not the game.
+ */
+ gagt_standout_string(prompt);
+
+ /* Wait for a single 'Y' or 'N' character response. */
+ response = ' ';
+ do {
+ g_vm->glk_request_char_event(gagt_main_window);
+ gagt_event_wait(evtype_CharInput, &event);
+
+ if (event.val1 <= BYTE_MAX_VAL)
+ response = g_vm->glk_char_to_upper(event.val1);
+ } while (!(response == 'Y' || response == 'N'));
+
+ /* Echo the confirmation response, and a blank line. */
+ g_vm->glk_set_style(style_Input);
+ g_vm->glk_put_string(response == 'Y' ? "Yes" : "No");
+ g_vm->glk_set_style(style_Normal);
+ g_vm->glk_put_string("\n");
+
+ return response == 'Y';
+}
+#endif
+
+
+/*
+ * gagt_get_user_file()
+ *
+ * Alternative versions of functions to get a file name from the user, and
+ * return a file stream structure. These functions are front-ended by the
+ * main get_user_file() function, which first converts the AGT file type
+ * into Glk usage and filemode, and also a mode for fopen()/fdopen().
+ *
+ * The ANSI version of the function prompts for the file using the simple
+ * method of querying the user through input in the main window. It then
+ * constructs a file stream around the path entered, and returns it.
+ *
+ * The non-ANSI, Glk version is more sneaky. It prompts for a file using
+ * Glk's functions to get filenames by prompt, file selection dialog, or
+ * whatever. Then it attempts to uncover which file descriptor Glk opened
+ * its file on, dup's it, closes the Glk stream, and returns a file stream
+ * built on this file descriptor. This is all highly non-ANSI, requiring
+ * dup() and fdopen(), and making some assumptions about the way that dup,
+ * open, and friends work. It works on Linux, and on Mac (CodeWarrior).
+ * It may also work for you, but if it doesn't, or if your system lacks
+ * things like dup or fdopen, define g_vm->glk_ANSI_ONLY and use the safe version.
+ *
+ * If GARGLK is used, non-ansi version calls garglk_fileref_get_name()
+ * instead, and opens a file the highly portable way, but still with a
+ * Glkily nice prompt dialog.
+ */
+#ifdef GLK_ANSI_ONLY
+static genfile
+gagt_get_user_file(glui32 usage, glui32 fmode, const char *fdtype) {
+ char filepath[GAGT_MAX_PATH];
+ event_t event;
+ int index, all_spaces;
+ genfile retfile;
+ assert(fdtype);
+
+ /* Prompt in a similar way to Glk. */
+ switch (usage) {
+ case fileusage_SavedGame:
+ gagt_normal_string("Enter saved game");
+ break;
+
+ case fileusage_Transcript:
+ gagt_normal_string("Enter transcript file");
+ break;
+
+ case fileusage_InputRecord:
+ gagt_normal_string("Enter command record file");
+ break;
+ }
+ switch (fmode) {
+ case filemode_Read:
+ gagt_normal_string(" to load: ");
+ break;
+
+ case filemode_Write:
+ gagt_normal_string(" to store: ");
+ break;
+ }
+
+ /* Get the path to the file from the user. */
+ g_vm->glk_request_line_event(gagt_main_window, filepath, sizeof(filepath) - 1, 0);
+ gagt_event_wait(evtype_LineInput, &event);
+
+ /* Terminate the file path with a NUL. */
+ assert(event.val1 < sizeof(filepath));
+ filepath[event.val1] = '\0';
+
+ /* Reject file paths that only contain any whitespace characters. */
+ all_spaces = TRUE;
+ for (index = 0; index < strlen(filepath); index++) {
+ if (!isspace(filepath[index])) {
+ all_spaces = FALSE;
+ break;
+ }
+ }
+ if (all_spaces)
+ return badfile(fSAV);
+
+ /* Confirm overwrite of any existing file. */
+ if (fmode == filemode_Write) {
+ genfile file;
+
+ file = fopen(filepath, "r");
+ if (file) {
+ fclose(file);
+
+ if (!gagt_confirm("Overwrite existing file? [y/n] "))
+ return badfile(fSAV);
+ }
+ }
+
+ /* Open and return a FILE* stream, or badfile if this fails. */
+ retfile = fopen(filepath, fdtype);
+ return retfile ? retfile : badfile(fSAV);
+}
+#endif
+
+#ifndef GLK_ANSI_ONLY
+static genfile gagt_get_user_file(glui32 usage, glui32 fmode, const char *fdtype) {
+ frefid_t fileref;
+ genfile retfile;
+ assert(fdtype);
+
+ /* Try to get a Glk file reference with these attributes. */
+ fileref = g_vm->glk_fileref_create_by_prompt(usage, (FileMode)fmode, 0);
+ if (!fileref)
+ return badfile(fSAV);
+
+ /*
+ * Reject the file reference if we're expecting to read from it,
+ * and the referenced file doesn't exist.
+ */
+ if (fmode == filemode_Read && !g_vm->glk_fileref_does_file_exist(fileref)) {
+ g_vm->glk_fileref_destroy(fileref);
+ return badfile(fSAV);
+ }
+
+ /*
+ * Now, it gets ugly. Glk assumes that the interpreter will do all of
+ * its reading and writing using the Glk streams read/write functions.
+ * It won't; at least, not without major surgery. So here we're going
+ * to do some dangerous stuff...
+ *
+ * Since a Glk stream is opaque, it's hard to tell what the underlying
+ * file descriptor is for it. We can get it if we want to play around
+ * in the internals of the strid_t structure, but it's unpleasant.
+ * The alternative is, arguably, no more pleasant, but it makes for
+ * (perhaps) more portable code. What we'll do is to dup a file, then
+ * immediately close it, and call g_vm->glk_stream_open_file(). The open()
+ * in g_vm->glk_stream_open_file() will return the same file descriptor number
+ * that we just close()d (in theory...). This makes the following two
+ * major assumptions:
+ *
+ * 1) g_vm->glk_stream_open_file() opens precisely one file with open()
+ * 2) open() always uses the lowest available file descriptor number,
+ * like dup()
+ *
+ * Believe it or not, this is better than the alternatives. There is
+ * no Glk function to return the filename from a frefid_t, and it
+ * moves about in different Glk libraries so we can't just take it
+ * from a given offset. And there is no Glk function to return the
+ * underlying file descriptor or FILE* from a Glk stream either. :-(
+ */
+
+#ifdef GARGLK
+ retfile = fopen(g_vm->garglk_fileref_get_name(fileref), fdtype);
+#else
+ strid_t stream;
+ int tryfd, glkfd, dupfd, retfd;
+
+ /* So, start by dup()'ing the first file descriptor we can, ... */
+ glkfd = -1;
+ for (tryfd = 0; tryfd < FD_SETSIZE; tryfd++) {
+ glkfd = fcntl(tryfd, F_DUPFD, 0);
+ if (glkfd != -1)
+ break;
+ }
+ if (tryfd >= FD_SETSIZE) {
+ g_vm->glk_fileref_destroy(fileref);
+ return badfile(fSAV);
+ }
+
+ /* ...then closing it, ... */
+ close(glkfd);
+
+ /* ...now open the Glk stream, assuming it opens on file 'glkfd', ... */
+ stream = g_vm->glk_stream_open_file(fileref, fmode, 0);
+ if (!stream) {
+ g_vm->glk_fileref_destroy(fileref);
+ return badfile(fSAV);
+ }
+
+ /* ...dup() the Glk file onto another file descriptor, ... */
+ dupfd = fcntl(glkfd, F_DUPFD, 0);
+ assert(dupfd != -1);
+
+ /* ...close and destroy the Glk edifice for this file, ... */
+ g_vm->glk_stream_close(stream, NULL);
+ g_vm->glk_fileref_destroy(fileref);
+
+ /* ...for neatness, dup() back to the old Glk file descriptor, ... */
+ retfd = fcntl(dupfd, F_DUPFD, 0);
+ assert(retfd != -1 && retfd == glkfd);
+ close(dupfd);
+
+ /* ...and finally, open a FILE* stream onto the return descriptor. */
+ retfile = fdopen(retfd, fdtype);
+ if (!retfile)
+ return badfile(fSAV);
+#endif /* GARGLK */
+
+ /*
+ * The result of all of this should now be that retfile is a FILE* wrapper
+ * round a file descriptor open on a file indicated by the user through Glk.
+ * Return it.
+ */
+ return retfile;
+}
+#endif
+
+
+/*
+ * get_user_file()
+ *
+ * Get a file name from the user, and return the file stream structure.
+ * This is a front-end to ANSI and non-ANSI variants of the function.
+ */
+genfile get_user_file(int type) {
+ glui32 usage = 0, fmode = 0;
+ const char *fdtype;
+ genfile retfile;
+
+ gagt_output_flush();
+
+ /* Map AGiliTy type to Glk usage and filemode. */
+ switch (type) {
+ case AGT_SCRIPT:
+ usage = fileusage_Transcript;
+ fmode = filemode_Write;
+ break;
+
+ case AGT_SAVE:
+ usage = fileusage_SavedGame;
+ fmode = filemode_Write;
+ break;
+
+ case AGT_RESTORE:
+ usage = fileusage_SavedGame;
+ fmode = filemode_Read;
+ break;
+
+ case AGT_LOG_READ:
+ usage = fileusage_InputRecord;
+ fmode = filemode_Read;
+ break;
+
+ case AGT_LOG_WRITE:
+ usage = fileusage_InputRecord;
+ fmode = filemode_Write;
+ break;
+
+ default:
+ gagt_fatal("GLK: Unknown file type encountered");
+ gagt_exit();
+ }
+
+ /* From these, determine a mode type for the f[d]open() call. */
+ if (fmode == filemode_Write)
+ fdtype = usage == fileusage_SavedGame ? "wb" : "w";
+ else
+ fdtype = usage == fileusage_SavedGame ? "rb" : "r";
+
+ /* Get a file stream from these using the appropriate function. */
+ retfile = gagt_get_user_file(usage, fmode, fdtype);
+
+ gagt_debug("get_user_file", "type=%d -> %p", type, retfile);
+ return retfile;
+}
+
+
+/*
+ * set_default_filenames()
+ *
+ * Set defaults for last save, log, and script filenames.
+ */
+void set_default_filenames(fc_type fc) {
+ /*
+ * There is nothing to do in this function, since Glk has its own ideas on
+ * default names for files obtained with a prompt.
+ */
+ gagt_debug("set_default_filenames", "fc=%p", fc);
+}
+
+
+/*---------------------------------------------------------------------*/
+/* Functions intercepted by link-time wrappers */
+/*---------------------------------------------------------------------*/
+
+/*
+ * __wrap_toupper()
+ * __wrap_tolower()
+ *
+ * Wrapper functions around toupper(), tolower(), and fatal(). The Linux
+ * linker's --wrap option will convert calls to mumble() to __wrap_mumble()
+ * if we give it the right options. We'll use this feature to translate
+ * all toupper() and tolower() calls in the interpreter code into calls to
+ * Glk's versions of these functions.
+ *
+ * It's not critical that we do this. If a linker, say a non-Linux one,
+ * won't do --wrap, then just do without it. It's unlikely that there
+ * will be much noticeable difference.
+ */
+int __wrap_toupper(int ch) {
+ unsigned char uch;
+
+ uch = g_vm->glk_char_to_upper((unsigned char) ch);
+ return (int) uch;
+}
+
+int __wrap_tolower(int ch) {
+ unsigned char lch;
+
+ lch = g_vm->glk_char_to_lower((unsigned char) ch);
+ return (int) lch;
+}
+
+
+/*---------------------------------------------------------------------*/
+/* Replacements for AGiliTy main() and options parsing */
+/*---------------------------------------------------------------------*/
+
+/* External declaration of interface.c's set default options function. */
+extern void set_default_options();
+
+/*
+ * The following values need to be passed between the startup_code and main
+ * functions.
+ */
+static int gagt_saved_argc = 0; /* Recorded argc. */
+static char **gagt_saved_argv = NULL, /* Recorded argv. */
+ *gagt_gamefile = NULL; /* Name of game file. */
+static const char *gagt_game_message = NULL; /* Error message. */
+
+/*
+ * Flag to set if we want to test for a clean exit. Without this it's a
+ * touch tricky sometimes to corner AGiliTy into calling exit() for us; it
+ * tends to require a broken game file.
+ */
+static int gagt_clean_exit_test = FALSE;
+
+
+/*
+ * gagt_parse_option()
+ *
+ * Glk-ified version of AGiliTy's parse_options() function. In practice,
+ * because Glk has got to them first, most options that come in here are
+ * probably going to be single-character ones, since this is what we told
+ * Glk in the arguments structure above. The Glk font control and other
+ * special tweaky flags will probably be the only multiple-character ones.
+ */
+static int gagt_parse_option(const char *option) {
+ unsigned int index;
+ assert(option);
+
+ assert(option[0] == '-');
+ for (index = 1; option[index]; index++) {
+ switch (option[index]) {
+ case 'g':
+ switch (option[++index]) {
+ case 'f':
+ gagt_font_mode = FONT_FIXED_WIDTH;
+ break;
+ case 'p':
+ gagt_font_mode = FONT_PROPORTIONAL;
+ break;
+ case 'a':
+ gagt_font_mode = FONT_AUTOMATIC;
+ break;
+ case 'd':
+ gagt_delay_mode = DELAY_FULL;
+ break;
+ case 'h':
+ gagt_delay_mode = DELAY_SHORT;
+ break;
+ case 'n':
+ gagt_delay_mode = DELAY_OFF;
+ break;
+ case 'r':
+ gagt_replacement_enabled = FALSE;
+ break;
+ case 'x':
+ gagt_abbreviations_enabled = FALSE;
+ break;
+ case 's':
+ gagt_extended_status_enabled = TRUE;
+ break;
+ case 'l':
+ gagt_extended_status_enabled = FALSE;
+ break;
+ case 'c':
+ gagt_commands_enabled = FALSE;
+ break;
+ case 'D':
+ DEBUG_OUT = TRUE;
+ break;
+ case '#':
+ gagt_clean_exit_test = TRUE;
+ break;
+ default:
+ return FALSE;
+ }
+ break;
+
+ case 'p':
+ debug_parse = TRUE;
+ break;
+ case 'a':
+ DEBUG_DISAMBIG = TRUE;
+ break;
+ case 'd':
+ DEBUG_AGT_CMD = TRUE;
+ break;
+ case 'x':
+ DEBUG_EXEC_VERB = TRUE;
+ break;
+ case 's':
+ DEBUG_SMSG = TRUE;
+ break;
+#ifdef MEM_INFO
+ case 'M':
+ DEBUG_MEM = TRUE;
+ break;
+#endif
+ case 'm':
+ descr_maxmem = 0;
+ break;
+ case 't':
+ BATCH_MODE = TRUE;
+ break;
+ case 'c':
+ make_test = TRUE;
+ break;
+ case '1':
+ irun_mode = TRUE;
+ break;
+#ifdef OPEN_FILE_AS_TEXT
+ case 'b':
+ open_as_binary = TRUE;
+ break;
+#endif
+
+ case '?':
+ default:
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+
+/*
+ * gagt_startup_code()
+ * gagt_main()
+ *
+ * Together, these functions take the place of the original AGiliTy main().
+ * The first one is called from glkunix_startup_code(), to parse and
+ * generally handle options. The second is called from glk_main(), and
+ * does the real work of running the game.
+ */
+int gagt_startup_code(int argc, char *argv[]) {
+ int argv_index;
+
+ /*
+ * Before doing anything else, stash argc and argv away for use by
+ * gagt_main() below.
+ */
+ gagt_saved_argc = argc;
+ gagt_saved_argv = argv;
+
+ /* Make the mandatory call for initialization. */
+ set_default_options();
+
+ /* Handle command line arguments. */
+ for (argv_index = 1;
+ argv_index < argc && argv[argv_index][0] == '-'; argv_index++) {
+ /*
+ * Handle an option string coming after "-". If the options parse
+ * fails, return FALSE.
+ */
+ if (!gagt_parse_option(argv[argv_index]))
+ return FALSE;
+ }
+
+ /*
+ * Get the name of the game file. Since we need this in our call from
+ * glk_main, we need to keep it in a module static variable. If the game
+ * file name is omitted, then here we'll set the pointer to NULL, and
+ * complain about it later in main. Passing the message string around
+ * like this is a nuisance...
+ */
+ if (argv_index == argc - 1) {
+ gagt_gamefile = argv[argv_index];
+ gagt_game_message = NULL;
+#ifdef GARGLK
+ char *s;
+ s = strrchr(gagt_gamefile, '\\');
+ if (s) g_vm->garglk_set_story_name(s + 1);
+ s = strrchr(gagt_gamefile, '/');
+ if (s) g_vm->garglk_set_story_name(s + 1);
+#endif /* GARGLK */
+ } else {
+ gagt_gamefile = NULL;
+ if (argv_index < argc - 1)
+ gagt_game_message = "More than one game file was given"
+ " on the command line.";
+ else
+ gagt_game_message = "No game file was given on the command line.";
+ }
+
+ /* All startup options were handled successfully. */
+ return TRUE;
+}
+
+static void gagt_main() {
+ fc_type fc;
+ assert(gagt_saved_argc != 0 && gagt_saved_argv);
+
+ /* Ensure AGiliTy internal types have the right sizes. */
+ if (sizeof(integer) < 2 || sizeof(int32) < 4 || sizeof(uint32) < 4) {
+ gagt_fatal("GLK: Types sized incorrectly, recompilation is needed");
+ gagt_exit();
+ }
+
+ /*
+ * Initialize the interface. As it happens, init_interface() is in our
+ * module here (above), and ignores argc and argv, but since the main() in
+ * AGiliTy passes them, we'll do so here, just in case we ever want to go
+ * back to using AGiliTy's main() function.
+ *
+ * init_interface() can fail if there is a problem creating the main
+ * window. As it doesn't return status, we have to detect this by checking
+ * that gagt_main_window is not NULL.
+ */
+ init_interface(gagt_saved_argc, gagt_saved_argv);
+ if (!gagt_main_window) {
+ gagt_fatal("GLK: Can't open main window");
+ gagt_exit();
+ }
+ g_vm->glk_window_clear(gagt_main_window);
+ g_vm->glk_set_window(gagt_main_window);
+ g_vm->glk_set_style(style_Normal);
+
+ /* If there's a problem with the game file, complain now. */
+ if (!gagt_gamefile) {
+ assert(gagt_game_message);
+ if (gagt_status_window)
+ g_vm->glk_window_close(gagt_status_window, NULL);
+ gagt_header_string("Glk AGiliTy Error\n\n");
+ gagt_normal_string(gagt_game_message);
+ gagt_normal_char('\n');
+ gagt_exit();
+ }
+
+ /*
+ * Create a game file context, and try to ensure it will open successfully
+ * in run_game().
+ */
+ fc = init_file_context(gagt_gamefile, fDA1);
+ if (!(gagt_workround_fileexist(fc, fAGX)
+ || gagt_workround_fileexist(fc, fDA1))) {
+ if (gagt_status_window)
+ g_vm->glk_window_close(gagt_status_window, NULL);
+ gagt_header_string("Glk AGiliTy Error\n\n");
+ gagt_normal_string("Can't find or open game '");
+ gagt_normal_string(gagt_gamefile);
+ gagt_normal_char('\'');
+ gagt_normal_char('\n');
+ gagt_exit();
+ }
+
+ /*
+ * Run the game interpreter in AGiliTy. run_game() releases the file
+ * context, so we don't have to, don't want to, and shouldn't.
+ */
+ run_game(fc);
+
+ /*
+ * Handle any updated status, and flush all remaining buffered output;
+ * this also frees all malloc'ed memory in the buffers.
+ */
+ gagt_status_notify();
+ gagt_output_flush();
+
+ /*
+ * Free any temporary memory that may have been used by status line
+ * functions.
+ */
+ gagt_status_cleanup();
+
+ /* Close any open transcript, input log, and/or read log. */
+ if (gagt_transcript_stream) {
+ g_vm->glk_stream_close(gagt_transcript_stream, NULL);
+ gagt_transcript_stream = NULL;
+ }
+ if (gagt_inputlog_stream) {
+ g_vm->glk_stream_close(gagt_inputlog_stream, NULL);
+ gagt_inputlog_stream = NULL;
+ }
+ if (gagt_readlog_stream) {
+ g_vm->glk_stream_close(gagt_readlog_stream, NULL);
+ gagt_readlog_stream = NULL;
+ }
+}
+
+
+/*---------------------------------------------------------------------*/
+/* Linkage between Glk entry/exit calls and the AGiliTy interpreter */
+/*---------------------------------------------------------------------*/
+
+/*
+ * Safety flags, to ensure we always get startup before main, and that
+ * we only get a call to main once.
+ */
+static int gagt_startup_called = FALSE,
+ gagt_main_called = FALSE;
+
+/*
+ * We try to catch calls to exit() from the interpreter, and redirect them
+ * to g_vm->glk_exit(). To help tell these calls from a call to exit() from
+ * g_vm->glk_exit() itself, we need to monitor when interpreter code is running,
+ * and when not.
+ */
+static int gagt_agility_running = FALSE;
+
+
+/*
+ * gagt_finalizer()
+ *
+ * ANSI atexit() handler. This is the first part of trying to catch and re-
+ * direct the calls the core AGiliTy interpreter makes to exit() -- we really
+ * want it to call g_vm->glk_exit(), but it's hard to achieve. There are three
+ * basic approaches possible, and all have drawbacks:
+ *
+ * o #define exit to gagt_something, and provide the gagt_something()
+ * function. This type of macro definition is portable for the most
+ * part, but tramples the code badly, and messes up the build of the
+ * non-interpreter "support" binaries.
+ * o Use ld's --wrap to wrapper exit. This only works with Linux's linker
+ * and so isn't at all portable.
+ * o Register an exit handler with atexit(), and try to cope in it after
+ * exit() has been called.
+ *
+ * Here we try the last of these. The one sticky part of it is that in our
+ * exit handler we'll want to call g_vm->glk_exit(), which will in all likelihood
+ * call exit(). And multiple calls to exit() from a program are "undefined".
+ *
+ * In practice, C runtimes tend to do one of three things: they treat the
+ * exit() call from the exit handler as if it was a return; they recurse
+ * indefinitely through the hander; or they do something ugly (abort, for
+ * example). The first of these is fine, ideal in fact, and seems to be the
+ * Linux and SVR4 behavior. The second we can avoid with a flag. The last
+ * is the problem case, seen only with SVR3 (and even then, it occurs only
+ * on program exit, after everything's cleaned up, and for that matter only
+ * on abnormal exit).
+ *
+ * Note that here we're not expecting to get a call to this routine, and if
+ * we do, and interpreter code is still running, it's a sign that we need
+ * to take actions we'd hoped not to have to take.
+ */
+static void gagt_finalizer() {
+ /*
+ * If interpreter code is still active, and we're not in a g_vm->glk_select(),
+ * the core interpreter code called exit(). Handle cleanup.
+ */
+ if (gagt_agility_running && !gagt_event_in_glk_select()) {
+ event_t event;
+
+ /*
+ * If we have a main window, try to update status (which may go to the
+ * status window, or to the main window) and flush any pending buffered
+ * output.
+ */
+ if (gagt_main_window) {
+ gagt_status_notify();
+ gagt_output_flush();
+ }
+
+ /*
+ * Clear the flag to avoid recursion, and call g_vm->glk_exit() to clean up
+ * Glk and terminate. This is the call that probably re-calls exit(),
+ * and thus prods "undefined" bits of the C runtime, so we'll make it
+ * configurable and overrideable for problem cases.
+ */
+ gagt_agility_running = FALSE;
+
+ /*
+ * We've decided not to take the dangerous route.
+ *
+ * In that case, providing we have a main window, fake a Glk-like-ish
+ * hit-any-key-and-wait message using a simple string in the main
+ * window. Not great, but usable where we're forced into bypassing
+ * g_vm->glk_exit(). If we have no main window, there's no point in doing
+ * anything more.
+ */
+ if (gagt_main_window) {
+ g_vm->glk_cancel_char_event(gagt_main_window);
+ g_vm->glk_cancel_line_event(gagt_main_window, NULL);
+
+ g_vm->glk_set_style(style_Alert);
+ g_vm->glk_put_string("\n\nHit any key to exit.\n");
+ g_vm->glk_request_char_event(gagt_main_window);
+ gagt_event_wait(evtype_CharInput, &event);
+ }
+ }
+}
+
+
+/*
+ * gagt_exit()
+ *
+ * g_vm->glk_exit() local wrapper. This is the second part of trying to catch
+ * and redirect calls to exit(). g_vm->glk_finalizer() above needs to know that
+ * we called g_vm->glk_exit() already from here, so it doesn't try to do it again.
+ */
+static void gagt_exit() {
+ assert(gagt_agility_running);
+
+ /*
+ * Clear the running flag to neutralize gagt_finalizer(), throw out any
+ * buffered output data, and then call the real g_vm->glk_exit().
+ */
+ gagt_agility_running = FALSE;
+ gagt_output_delete();
+ g_vm->glk_exit();
+}
+
+
+/*
+ * __wrap_exit()
+ *
+ * Exit() wrapper where a linker does --wrap. This is the third part of
+ * trying to catch and redirect calls to exit().
+ *
+ * This function is for use only with IFP, and avoids a nasty attempt at
+ * reusing a longjmp buffer. IFP will redirect calls to exit() into
+ * g_vm->glk_exit() as a matter of course. It also handles atexit(), and we've
+ * registered a function with atexit() that calls g_vm->glk_exit(), and
+ * IFP redirects g_vm->glk_exit() to be an effective return from glk_main(). At
+ * that point it calls finalizers. So without doing something special for
+ * IFP, we'll find ourselves calling g_vm->glk_exit() twice -- once as the IFP
+ * redirected exit(), and once from our finalizer. Two returns from the
+ * function glk_main() is a recipe for unpleasantness.
+ *
+ * As IFP is Linux-only, at present, --wrap will always be available to IFP
+ * plugin builds. So here, we'll wrap exit() before IFP can get to it, and
+ * handle it safely. For non-IFP/non-wrap links, this is just an unused
+ * function definition, and can be safely ignored...
+ */
+void __wrap_exit(int status) {
+ assert(gagt_agility_running);
+
+ /*
+ * In an IFP plugin, only the core interpreter code could have called exit()
+ * here -- we don't, and IFP redirects g_vm->glk_exit(), the only other potential
+ * caller of exit(). (It also redirects exit() if we don't get to it here
+ * first.)
+ *
+ * So, if we have a main window, flush it. This is the same cleanup as
+ * done by the finalizer.
+ */
+ if (gagt_main_window) {
+ gagt_status_notify();
+ gagt_output_flush();
+ }
+
+ /* Clear the running flag, and transform exit() into a g_vm->glk_exit(). */
+ gagt_agility_running = FALSE;
+ g_vm->glk_exit();
+}
+
+
+/*
+ * glk_main()
+ *
+ * Main entry point for Glk. Here, all startup is done, and we call our
+ * function to run the game.
+ */
+void glk_main() {
+ assert(gagt_startup_called && !gagt_main_called);
+ gagt_main_called = TRUE;
+
+ /*
+ * Register gagt_finalizer() with atexit() to cleanup on exit. Note that
+ * this module doesn't expect the atexit() handler to be called on all
+ * forms of exit -- see comments in gagt_finalizer() for more.
+ */
+ if (atexit(gagt_finalizer) != 0) {
+ gagt_fatal("GLK: Failed to register finalizer");
+ gagt_exit();
+ }
+
+ /*
+ * If we're testing for a clean exit, deliberately call exit() to see what
+ * happens. We're hoping for a clean process termination, but our exit
+ * code explores "undefined" ANSI. If we get something ugly, like a core
+ * dump, we'll want to set GLK[AGIL]_CLEAN_EXIT.
+ */
+ if (gagt_clean_exit_test) {
+ gagt_agility_running = TRUE;
+ return;
+ }
+
+ /*
+ * The final part of trapping exit(). Set the running flag, and call the
+ * interpreter main function. Clear the flag when the main function returns.
+ */
+ gagt_agility_running = TRUE;
+ gagt_main();
+ gagt_agility_running = FALSE;
+}
+
+
+/*---------------------------------------------------------------------*/
+/* Glk linkage relevant only to the UNIX platform */
+/*---------------------------------------------------------------------*/
+
+/*
+ * Glk arguments for UNIX versions of the Glk interpreter.
+ */
+/*
+glkunix_argumentlist_t glkunix_arguments[] = {
+ {(char *) "-gf", glkunix_arg_NoValue,
+ (char *) "-gf Force Glk to use only a fixed width font"},
+ {(char *) "-gp", glkunix_arg_NoValue,
+ (char *) "-gp Allow Glk to use only a proportional font"},
+ {(char *) "-ga", glkunix_arg_NoValue,
+ (char *) "-ga Try to use a suitable Glk font automatically"},
+ {(char *) "-gd", glkunix_arg_NoValue,
+ (char *) "-gd Delay for the full period in Glk"},
+ {(char *) "-gh", glkunix_arg_NoValue,
+ (char *) "-gh Delay for approximately half the period in Glk"},
+ {(char *) "-gn", glkunix_arg_NoValue,
+ (char *) "-gn Turn off all game delays in Glk"},
+ {(char *) "-gr", glkunix_arg_NoValue,
+ (char *) "-gr Turn off Glk text replacement"},
+ {(char *) "-gx", glkunix_arg_NoValue,
+ (char *) "-gx Turn off Glk abbreviation expansions"},
+ {(char *) "-gs", glkunix_arg_NoValue,
+ (char *) "-gs Display a short status window in Glk"},
+ {(char *) "-gl", glkunix_arg_NoValue,
+ (char *) "-gl Display an extended status window in Glk"},
+ {(char *) "-gc", glkunix_arg_NoValue,
+ (char *) "-gc Turn off Glk command escapes in games"},
+ {(char *) "-gD", glkunix_arg_NoValue,
+ (char *) "-gD Turn on Glk port module debug tracing"},
+ {(char *) "-g#", glkunix_arg_NoValue,
+ (char *) "-g# Test for clean exit (Glk module debugging only)"},
+ {(char *) "-1", glkunix_arg_NoValue,
+ (char *) "-1 IRUN Mode: Print messages in first person"},
+ {(char *) "-d", glkunix_arg_NoValue,
+ (char *) "-d Debug metacommand execution"},
+ {(char *) "-t", glkunix_arg_NoValue,
+ (char *) "-t Test mode"},
+ {(char *) "-c", glkunix_arg_NoValue,
+ (char *) "-c Create test file"},
+ {(char *) "-m", glkunix_arg_NoValue,
+ (char *) "-m Force descriptions to be loaded from disk"},
+#ifdef OPEN_AS_TEXT
+ {(char *) "-b", glkunix_arg_NoValue,
+ (char *) "-b Open data files as binary files"},
+#endif
+ {(char *) "-p", glkunix_arg_NoValue,
+ (char *) "-p Debug parser"},
+ {(char *) "-x", glkunix_arg_NoValue,
+ (char *) "-x Debug verb execution loop"},
+ {(char *) "-a", glkunix_arg_NoValue,
+ (char *) "-a Debug disambiguation system"},
+ {(char *) "-s", glkunix_arg_NoValue,
+ (char *) "-s Debug STANDARD message handler"},
+#ifdef MEM_INFO
+ {(char *) "-M", glkunix_arg_NoValue,
+ (char *) "-M Debug memory allocation"},
+#endif
+ {(char *) "", glkunix_arg_ValueCanFollow,
+ (char *) "filename game to run"},
+ {NULL, glkunix_arg_End, NULL}
+};
+
+*/
+/*
+ * glkunix_startup_code()
+ *
+ * Startup entry point for UNIX versions of Glk AGiliTy. Glk will call
+ * glkunix_startup_code() to pass in arguments. On startup, we call our
+ * function to parse arguments and generally set stuff up.
+ */
+
+int glk_startup_code(int argc, char *argv[]) {
+ assert(!gagt_startup_called);
+ gagt_startup_called = TRUE;
+
+ return gagt_startup_code(argc, argv);
+}
+
+} // End of namespace AGT
+} // End of namespace Glk