aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Gilbert2019-11-24 18:15:56 -0800
committerPaul Gilbert2019-11-27 21:10:29 -0800
commit28c3584148c49c1c6f118c5cbf13d0bf53d56726 (patch)
tree03102c6aeccebf3aec189deaf19cfd641673245e
parentf9921a7a177dd506640211dd05751e046100a57b (diff)
downloadscummvm-rg350-28c3584148c49c1c6f118c5cbf13d0bf53d56726.tar.gz
scummvm-rg350-28c3584148c49c1c6f118c5cbf13d0bf53d56726.tar.bz2
scummvm-rg350-28c3584148c49c1c6f118c5cbf13d0bf53d56726.zip
GLK: AGT: Added subengine files
-rw-r--r--engines/glk/agt/agil.cpp980
-rw-r--r--engines/glk/agt/agility.h1223
-rw-r--r--engines/glk/agt/agt.cpp9
-rw-r--r--engines/glk/agt/agt.h6
-rw-r--r--engines/glk/agt/agtread.cpp1721
-rw-r--r--engines/glk/agt/agxfile.cpp1483
-rw-r--r--engines/glk/agt/auxfile.cpp596
-rw-r--r--engines/glk/agt/config.h289
-rw-r--r--engines/glk/agt/debugcmd.cpp866
-rw-r--r--engines/glk/agt/disassemble.cpp317
-rw-r--r--engines/glk/agt/exec.cpp1375
-rw-r--r--engines/glk/agt/exec.h272
-rw-r--r--engines/glk/agt/filename.cpp692
-rw-r--r--engines/glk/agt/gamedata.cpp1590
-rw-r--r--engines/glk/agt/interface.cpp1159
-rw-r--r--engines/glk/agt/interp.h539
-rw-r--r--engines/glk/agt/metacommand.cpp1154
-rw-r--r--engines/glk/agt/object.cpp1064
-rw-r--r--engines/glk/agt/os_glk.cpp6099
-rw-r--r--engines/glk/agt/parser.cpp1621
-rw-r--r--engines/glk/agt/runverb.cpp1687
-rw-r--r--engines/glk/agt/savegame.cpp429
-rw-r--r--engines/glk/agt/token.cpp1158
-rw-r--r--engines/glk/agt/util.cpp1489
-rw-r--r--engines/glk/agt/vars.cpp209
-rw-r--r--engines/glk/module.mk21
26 files changed, 28045 insertions, 3 deletions
diff --git a/engines/glk/agt/agil.cpp b/engines/glk/agt/agil.cpp
new file mode 100644
index 0000000000..5bf19f74ae
--- /dev/null
+++ b/engines/glk/agt/agil.cpp
@@ -0,0 +1,980 @@
+/* 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 "common/str.h"
+
+namespace Glk {
+namespace AGT {
+
+/* ------------------------------------------------------------------- */
+/* Description Pointers */
+
+
+descr_ptr intro_ptr;
+descr_ptr title_ptr, ins_ptr; /* Only defined if agx_file is true */
+descr_ptr *err_ptr; /* [NUM_ERR];*/
+
+descr_ptr *msg_ptr; /* [MAX_MSG];*/
+descr_ptr *help_ptr, *room_ptr, *special_ptr; /*[ROOM] */
+descr_ptr *noun_ptr, *text_ptr, *turn_ptr, /* [NOUN] */
+ *push_ptr, *pull_ptr, *play_ptr;
+descr_ptr *talk_ptr, *ask_ptr, *creat_ptr; /* [CREAT] */
+
+descr_ptr *quest_ptr, *ans_ptr; /* [MAX_QUEST] */
+tline *question, *answer; /* [MAX_QUEST] */
+
+
+
+/* ------------------------------------------------------------------------ */
+/* Dynamically allocated data blocks (which are pointed to from elsewhere) */
+
+char **dict; /* dict[n] points to the nth dictionary word */
+long dp; /* Dictionary pointer: number of words in dict */
+
+#define DICT_INIT 12*1024 /* Starting size of dictstr */
+#define DICT_GRAN 1024 /* Granularity of dictstr size requests
+ must be at least 4. */
+char *dictstr; /* Pointer to memory block containing dict words */
+long dictstrptr, dictstrsize;
+/* dictstrptr points to the first unused byte in dictstr.
+ dictstrsize points to the end of the space currently allocated for
+ dictstr.
+*/
+
+char *static_str; /*Static string space */
+long ss_end; /* Pointer to end of used space in above */
+long ss_size; /* Current size of static string space */
+
+word *syntbl; /* Synonym list space */
+slist synptr; /* Points to end of used space */
+long syntbl_size; /* Long so we can catch overflows */
+
+long descr_maxmem;
+char *mem_descr; /* Copy of descriptor in memory */
+
+
+
+/* ------------------------------------------------------------------------ */
+/* Data structures used internally by agtread.c */
+
+/*The following are all set to NULL after agtread finishes. */
+long *cmd_ptr; /* ME only;Points to cmd start locs in gamefile.*/
+long *room_name, *noun_sdesc, *noun_pos, *creat_sdesc;
+long *t_pictlist, *t_pixlist, *t_songlist, *t_fontlist;
+
+/* These are only used by agtout (to allow the AGT reading routines to
+ pass back the count of nouns inside the given object) */
+integer *room_inside, *noun_inside, *creat_inside;
+
+/* This is used to translate ASCII codes */
+uchar fixchar[256];
+
+rbool text_file; /* Set if we are currently opening a binary file. */
+#ifdef OPEN_AS_TEXT
+rbool open_as_binary; /* Open text files as binary, anyhow. */
+#endif
+
+/* The following are AGT 'purity' flags; they turn off features of */
+/* my interpreter that are not fully consistent with the original AGT */
+/* and so could break some games. Some of these are trivial improvements; */
+/* some are more radical and should be used with caution. Several are */
+/* only useful if a game was designed with them in mind. */
+/* In all cases, setting the flag to 1 more closely follows the */
+/* behavior of the original interpreters */
+/* WARNING: Most of these haven't been tested in the non-default state. */
+/* Most of these will eventually become variables settable by the user */
+/* or from a (possibly game-specific) configuration file */
+
+rbool PURE_INPUT = 1; /* Is the input line bold? */
+
+rbool PURE_TONE = 0; /* Is low level sound enabled? */
+
+
+/*-------------------------------------------------------------------*/
+/* Misc. things to support the tokenizer and the dictionry. */
+/*-------------------------------------------------------------------*/
+
+/* The following should not be changed without also changing the
+ wtype enum statement in interp.h to match */
+static const char *ext_voc[] = {
+ "the", "my", "a", "an", /* These 4 are ignored in canonical AGT */
+ "then", ".", ";", "and", "," , "its", "all", "undo", "look", "g",
+ "pick", "go", "exits", "talk", "take", "door", "again", "but", "except",
+ "scene", "everything", "listexit", "listexits", "close",
+ "verb", "noun", "adjective", "prep", "object", "name", "step",
+ " any", "either", "both", "everyone", "everybody",
+ "he", "she", "it", "they", "him", "her", "them", "is", "are", "oops",
+ "was", "were",
+ /* Everything between 'in' and 'about' should be a preposition */
+ "in", "out", "into", "at", "to", "across", "inside", "with", "near", "for",
+ "of", "behind", "beside", "on", "off", "under", "from", "through",
+ "toward", "towards", "between", "around", "upon", "thru",
+ "by", "over", "up", "down",
+ "about"
+};
+/* 'about' must be the last element of this list */
+
+
+/*-------------------------------------------------------------------*/
+/* Routines to read in and use various auxilary files. */
+/* (.TTL, .INS, .VOC, .CFG) */
+/*-------------------------------------------------------------------*/
+
+static rbool emptyline(unsigned char *s)
+/* Check if s consists only of white space and control characters */
+{
+ unsigned char *p;
+
+ for (p = s; *p != 0; p++)
+ if (!rspace(*p) && *p > 26) return 0;
+ return 1;
+}
+
+static void print_title(fc_type fc) {
+ int height;
+ signed char center_mode; /* Center title? */
+ descr_line *buff;
+ char *s;
+ rbool skip_line; /* Skip first line: it has COLORS */
+
+ if (agx_file)
+ buff = read_descr(title_ptr.start, title_ptr.size);
+ else
+ buff = read_ttl(fc);
+
+ if (buff == NULL) {
+ writeln("");
+ writeln("");
+ s = formal_name(fc, fNONE);
+ if (s != NULL) {
+ s[0] = toupper(s[0]);
+ agt_center(1);
+ agt_textcolor(-1);
+ writeln(s);
+ agt_textcolor(-2);
+ agt_center(0);
+ rfree(s);
+ }
+ writeln("");
+ writeln("");
+
+ if (aver < AGX00)
+ writeln("This game was created with Malmberg and Welch's Adventure "
+ "Game Toolkit; it is being executed by");
+ else writeln("This game is being executed by ");
+ writeln("");
+ height = 0;
+ } else {
+ if (buff[0] != NULL && strncasecmp(buff[0], "COLORS", 6) == 0) {
+ /* Do screen colors */
+ skip_line = 1;
+ } else skip_line = 0;
+ /* Compute height and count the number of non-empty lines
+ starting with spaces. We use height as a loop variable
+ and center_mode to store the count temporarily. */
+ center_mode = 0;
+ for (height = skip_line; buff[height] != NULL; height++)
+ if (!emptyline((uchar *)buff[height])) {
+ if (rspace(buff[height][0])) center_mode++;
+ else center_mode--;
+ }
+
+ if (box_title || aver == AGTCOS) center_mode = TB_CENTER;
+ else /* includes aver==AGT135 */
+ if (center_mode <= 0) center_mode = TB_CENTER;
+ else center_mode = TB_NOCENT;
+
+ if (!bold_mode) agt_textcolor(-1);
+ agt_clrscr();
+ textbox(buff + skip_line, height - skip_line, center_mode |
+ (bold_mode ? 0 : TB_BOLD) | TB_TTL |
+ (box_title ? TB_BORDER : 0));
+ if (!bold_mode) agt_textcolor(-2); /* Bold off */
+ } /* End printing of title proper */
+
+ if (agx_file)
+ free_descr(buff);
+ else
+ free_ttl(buff);
+
+ agt_textcolor(7);
+ agt_center(1);
+ if (buff != NULL) {
+ if (aver < AGX00 && height <= screen_height - 6)
+ writeln("[Created with Malmberg and Welch's Adventure Game Toolkit]");
+ if (height <= screen_height - 9) writeln("");
+ if (height <= screen_height - 5) writeln("This game is being executed by");
+ }
+ agt_textcolor(-1);
+ s = (char *)rmalloc(80);
+ if (height <= screen_height - 5)
+ sprintf(s, "AGiliTy: "
+ "The (Mostly) Universal AGT Interpreter %s", version_str);
+ else
+ sprintf(s, "Being run by AGiliTy %s, "
+ "Copyright (C) 1996-99,2001 Robert Masenten",
+ version_str);
+ writeln(s);
+ rfree(s);
+ agt_textcolor(-2);
+ if (height <= screen_height - 5)
+ writeln("Copyright (C) 1996-99,2001 by Robert Masenten");
+ if (height <= screen_height - 3) writeln(portstr);
+ if (height <= screen_height - 10) writeln("");
+ agt_center(0);
+}
+
+
+
+/* .INS reading routines -------------------------------------- */
+
+void print_instructions(fc_type fc) {
+ char *buffer;
+ uchar *s;
+
+ writeln("INSTRUCTIONS:");
+ if (open_ins_file(fc, 1)) { /* Instruction file exists */
+ while (NULL != (buffer = read_ins_line())) {
+ for (s = (uchar *)buffer; *s != 0; s++) *s = trans_ascii[*s];
+ writeln(buffer);
+ }
+ }
+ writeln("");
+}
+
+/* Routines to build the verb menu from the .VOC information */
+
+static void build_verbmenu(void) {
+ int i, n;
+ char *p, *d;
+
+ verbmenu = (menuentry *)rmalloc(vm_size * sizeof(menuentry));
+ vm_width = 0;
+ for (i = 0; i < vm_size; i++) {
+ p = verbmenu[i];
+ d = dict[verbinfo[i].verb];
+ n = 0;
+ for (; n < MENU_WIDTH && *d != 0; p++, d++, n++) *p = *d;
+ if (verbinfo[i].prep != 0 && n + 1 < MENU_WIDTH) {
+ *p++ = ' ';
+ d = dict[verbinfo[i].prep];
+ *p++ = toupper(*d++);
+ for (; n < MENU_WIDTH && *d != 0; p++, d++, n++) *p = *d;
+ }
+ verbmenu[i][0] = toupper(verbmenu[i][0]);
+ *p = 0;
+ if (n > vm_width) vm_width = n;
+ }
+}
+
+/* .CFG reading routines -------------------------------------------- */
+
+#define opt(s) (strcasecmp(optstr[0],s)==0)
+
+/* These are the interpreter specific options; this is called
+ from cfg_option in agtdata.c. */
+void agil_option(int optnum, char *optstr[], rbool setflag, rbool lastpass) {
+ if (opt("ibm_char")) fix_ascii_flag = !setflag;
+ else if (!lastpass) return; /* On the first pass through the game specific
+ file, we ignore all but the above options */
+ else if (opt("tone")) PURE_TONE = setflag;
+ else if (opt("input_bold")) PURE_INPUT = setflag;
+ else if (opt("force_load")) FORCE_VERSION = setflag;
+ else if (!agt_option(optnum, optstr, setflag)) /* Platform specific options */
+ rprintf("Invalid option %s\n", optstr[0]);
+}
+
+/*-------------------------------------------------------------------*/
+/* Tokeniser: Split input into words and look them up in dictionary */
+/*-------------------------------------------------------------------*/
+
+static rbool noise_word(word w) {
+ if (w == ext_code[wthe] || w == ext_code[wa] || w == ext_code[wan]) return 1;
+ if (w == ext_code[wmy]) return 1;
+ if (aver >= AGT18 && aver <= AGT18MAX && w == ext_code[wis]) return 1;
+ return 0;
+}
+
+
+static rbool check_dot(char *prevtext, int prevcnt, char *lookahead)
+/* This routine is devoted to trying to figure out whether
+ we should treat '.' as punctuation or as a letter. */
+/* It returns true if '.' should be treated as punctuation. */
+/* prevtext=the current word, as far as it has been parsed.
+ prevcnt=the number of letters in prevtext
+ [which is *not* 0 terminated] */
+/* lookahead=the rest of the current input line *after* the period. */
+{
+ int i, endword, restcnt;
+
+ if (!PURE_DOT) return 1; /* No words with periods in them, so it must
+ be punctuation. */
+ /* We just start scanning the dictionary to see if any of them
+ are possible matches, looking ahead as neccessary. */
+
+ /* Find the next unambiguous word end. This ignores possible
+ word ends caused by periods. */
+ for (endword = 0; lookahead[endword] != 0; endword++)
+ if (isspace(lookahead[endword]) ||
+ lookahead[endword] == ',' || lookahead[endword] == ';')
+ break;
+
+ for (i = 0; i < dp; i++) {
+ if (i == ext_code[wp]) continue; /* Ignore matches with the word ".". */
+
+ /* If it doesn't contain a '.' at the right location, there is no
+ point in continuing. */
+ restcnt = strlen(dict[i]);
+ if (restcnt <= prevcnt || dict[i][prevcnt] != '.') continue;
+
+ /* Now make sure the previous characters are correct */
+ if (strncasecmp(prevtext, dict[i], prevcnt) != 0) continue;
+
+ /* Finally, compare the trailing text. This is complicated by
+ the fact that the trailing text could itself contain ambiguous '.'s */
+ restcnt -= prevcnt + 1; /* Number of characters in dict entry after '.' */
+ if (restcnt > endword) continue; /* Dictionary entry is longer than
+ following text */
+
+ /* Check to see if the dictionary entry can be found in the lookahead
+ buffer */
+ if (strncasecmp(lookahead, dict[i] + prevcnt + 1, restcnt) != 0) continue;
+
+ if (restcnt == endword) return 0; /* We have a match */
+ /* At this point, we know that restcnt<endword and the dictionary
+ entry matches as far as restcnt. */
+ /* endword ignores '.', though, so it could be we have a match
+ but are missing it because it is period-terminated. Check this. */
+ if (lookahead[restcnt] == '.') return 0;
+
+ /* Otherwise, no match... try again with the next word... */
+ }
+ return 1; /* No matches: treat it as punctuation. */
+}
+
+
+
+
+
+
+static void tokenise(char *buff)
+/* Convert input string into vocabulary codes */
+/* 0 here denotes an unrecognized word and -1 marks the end. */
+{
+ int ip_, j, k;
+ rbool punctuation;
+
+ j = 0;
+ ip_ = 0;
+ k = 0; /* k is the character pointer */
+ for (k = 0;; k++) {
+ /* If PURE_DOT is set, then there are periods in some of the dictionary
+ words, so '.' could be a letter or punctuation-- we have to examine
+ context to figure out which. */
+ if (buff[k] == '.' && PURE_DOT)
+ /* Note: check_dot is in agtdata.c, since it needs to access
+ internal implementation details of the dictionary */
+ punctuation = check_dot(in_text[ip_], j, buff + k + 1);
+ else
+ punctuation = (buff[k] == ',' || buff[k] == ';' || buff[k] == '.');
+ if (buff[k] != 0 && !isspace(buff[k]) && !punctuation) {
+ if (j < WORD_LENG - 1)
+ in_text[ip_][j++] = buff[k];
+ } else if (j > 0) { /* End of word: add it to input */
+ in_text[ip_][j] = 0;
+ input[ip_] = search_dict(in_text[ip_]);
+ if (input[ip_] == -1) input[ip_] = 0;
+ else if (input[ip_] == 0) input[ip_] = ext_code[w_any]; /* _Real_ 'ANY' */
+ if (!noise_word(input[ip_])) ip_ += 1;
+ /* i.e. if not one of the four ignored words, advance */
+ j = 0;
+ } /* If j=0 and not punct, then no new word; just skip the whitespace */
+ if (punctuation) {
+ in_text[ip_][0] = buff[k];
+ in_text[ip_][1] = 0;
+ input[ip_] = search_dict(in_text[ip_]);
+ if (input[ip_] == -1) input[ip_] = 0;
+ j = 0;
+ ip_++;
+ }
+ if (ip_ >= MAXINPUT - 1) {
+ writeln("Too many words in input; ignoring rest of line.");
+ break;
+ }
+ if (buff[k] == 0) break;
+ }
+ input[ip_] = -1;
+ in_text[ip_][0] = 0;
+}
+
+
+
+/*-------------------------------------------------------------------*/
+/* Main game loop: Get player input and call the parser. */
+/*-------------------------------------------------------------------*/
+
+static void game_end(void) {
+ rbool done_flag;
+ char *s;
+
+ if (winflag || deadflag) {
+ writeln("");
+ writeln("");
+ agt_center(1);
+ if (winflag)
+ gen_sysmsg(148, "***** $You$ have won! *****", MSG_MAIN, NULL);
+ if (deadflag)
+ gen_sysmsg(147, "***** $You$ have died! *****", MSG_MAIN, NULL);
+ writeln("");
+ writeln("");
+ agt_center(0);
+ }
+ if (deadflag && !endflag) {
+ if (curr_lives > 1) { /* Resurrection code */
+ if (curr_lives == max_lives)
+ gen_sysmsg(151, "Hmmm.... so $you$'ve gotten $your$self killed. "
+ "Would you like me to try a resurrection?", MSG_MAIN, NULL);
+ else gen_sysmsg(152, "<Sigh> $You$'ve died *again*. "
+ "Would you like me to try another resurrection?",
+ MSG_MAIN, NULL);
+ if (yesno("? ")) { /* Now do resurrection */
+ curr_lives--;
+ quitflag = deadflag = 0;
+ gen_sysmsg(154,
+ "$You$ emerge coughing from a cloud of dark green smoke.",
+ MSG_MAIN, NULL);
+ writeln("");
+ loc = resurrect_room - first_room;
+ newlife_flag = 1;
+ set_statline();
+ do_look = do_autoverb = 1;
+ newroom();
+ return;
+ } else writeln("As you wish...");
+ } else if (max_lives > 1)
+ gen_sysmsg(153, "$You$'ve used up all of $your$ lives.", MSG_MAIN, NULL);
+ }
+ writeln("");
+ print_score();
+ writeln("");
+ done_flag = quitflag; /* If player has QUIT, don't ask again */
+ while (!done_flag && !quitflag) {
+ writestr("Would you like to ");
+ if (restart_state != NULL) writestr("restart, ");
+ writestr("restore");
+ if (undo_state != NULL && can_undo)
+ writestr(", undo,");
+ else if (restart_state != NULL) writestr(",");
+ writestr(" or quit? ");
+ s = agt_readline(5);
+ if (strncasecmp(s, "RESTART", 7) == 0)
+ if (restart_state != NULL) {
+ restart_game();
+ done_flag = 1;
+ } else writeln("Sorry, I'm unable to do that because of limited memory.");
+ else if (strncasecmp(s, "RESTORE", 7) == 0)
+ if (loadgame()) {
+ done_flag = 1;
+ } else writeln("(RESTORE failed)");
+ else if (strncasecmp(s, "UNDO", 4) == 0)
+ if (can_undo && undo_state != NULL) {
+ putstate(undo_state);
+ done_flag = 1;
+ } else writeln("Insufficiant memory to support UNDO");
+ else if (toupper(s[0]) == 'Q') {
+ quitflag = 1;
+ done_flag = 1;
+ }
+ }
+ set_statline();
+}
+
+
+static void parse_loop(void)
+/* This exists to deal with THEN lists; parse() handles the indiviudual
+ commands */
+{
+ for (ip = 0; ip >= 0 && ip < MAXINPUT && input[ip] != -1;) {
+ if (!parse() || quitflag || winflag || deadflag || endflag) break;
+ if (doing_restore) break;
+ if (ip >= 0 && ip < MAXINPUT && input[ip] != -1)
+ writeln(""); /* Insert blank lines between commands when dealing
+ with THEN lists */
+ }
+}
+
+
+static long rm_start_size;
+static char memstr[100];
+
+static void mainloop(void) {
+ char *s;
+
+ doing_restore = 0;
+ while (!quitflag) {
+ if (DEBUG_MEM) {
+ sprintf(memstr,
+ "A:%ld F:%ld Delta:%ld Size:%ld+%ld=%ld (%ld left)\n",
+ ralloc_cnt, rfree_cnt, ralloc_cnt - rfree_cnt,
+ rm_start_size, rm_size - rm_start_size, rm_size,
+ rm_freesize);
+ writeln(memstr);
+ }
+ rm_size = 0; /* Reset it to zero */
+ rm_freesize = get_rm_freesize();
+ if (!menu_mode) {
+ prompt_out(1);
+ s = agt_readline(0);
+ agt_newline();
+ if (!doing_restore) tokenise(s); /* Tokenizes into input */
+ rfree(s);
+ if (!doing_restore) parse_loop();
+ } else
+ menu_cmd();
+ if (doing_restore) {
+ if (doing_restore == 1) loadgame();
+ else if (doing_restore == 2) restart_game();
+ else if (doing_restore == 3 || doing_restore == 4)
+ return; /* Quit or New game requested */
+ doing_restore = 0;
+ }
+ if (winflag || deadflag || endflag || quitflag)
+ game_end();
+ }
+}
+
+
+/*-------------------------------------------------------------------*/
+/* Start up and shut down: Routines to initialise the game state and */
+/* clean up after the game ends. */
+/*-------------------------------------------------------------------*/
+
+static int init(void) {
+ int i, can_save;
+ uchar *tmp1, *tmp2;
+
+ init_vals();
+ init_creat_fix();
+ if (!agx_file) dict[0][0] = 0; /* Turn "ANY" into "" */
+ l_stat[0] = r_stat[0] = 0; /* Clear the status line */
+ /* lactor=lobj=lnoun=NULL;*/
+ tscore = old_score = objscore = 0;
+ turncnt = 0;
+ curr_time = startup_time;
+ loc = start_room - first_room;
+ cmd_saveable = 0;
+ first_visit_flag = newlife_flag = room_firstdesc = 1;
+ curr_lives = max_lives;
+
+ /* Note: flag[0] is the debugging flag and is set elsewhere */
+ if (FLAG_NUM < 0) FLAG_NUM = 0;
+ dbgflagptr = flag = (rbool *)rrealloc(flag, sizeof(rbool) * (FLAG_NUM + 1));
+ for (i = 1; i <= FLAG_NUM; i++)
+ flag[i] = 0;
+ dbgcntptr = agt_counter = (short *)rmalloc(sizeof(short) * (CNT_NUM + 1));
+ for (i = 0; i <= CNT_NUM; i++) {
+ agt_counter[i] = -1;
+ }
+ dbgvarptr = agt_var = (long *)rmalloc(sizeof(*agt_var) * (VAR_NUM + 1));
+ for (i = 0; i <= VAR_NUM; i++)
+ agt_var[i] = 0;
+
+ for (i = 0; i <= maxnoun - first_noun; i++) {
+ if (noun[i].position == NULL || noun[i].position[0] == '\0')
+ noun[i].pos_prep = 0;
+ else noun[i].pos_prep = -1;
+ noun[i].pos_name = 0;
+ noun[i].initpos = noun[i].position;
+ }
+
+ nomatch_aware = 0; /* By default, not aware. */
+ smart_look = 1; /* By default, LOOK <x> --> EXAMINE */
+ for (i = 0; i < last_cmd; i++) {
+ if (command[i].nouncmd == -1 || command[i].objcmd == -1
+ || command[i].noun_adj == -1 || command[i].obj_adj == -1
+ || command[i].prep == -1)
+ nomatch_aware = 1;
+ if (command[i].verbcmd == ext_code[wlook] &&
+ (command[i].nouncmd > 0 || command[i].noun_adj > 0
+ || command[i].objcmd > 0 || command[i].obj_adj > 0
+ || command[i].prep > 0))
+ smart_look = 0;
+ }
+
+ pictable = (integer *)rmalloc(sizeof(int) * maxpict);
+ for (i = 0; i < maxpict; i++) pictable[i] = i;
+ init_state_sys(); /* Initialize the system for saving and restoring
+ game states */
+ tmp1 = (uchar *)rmalloc(MEM_MARGIN); /* Preserve some work space */
+
+ tmp2 = getstate(NULL); /* Make sure we have space to save */
+ if (tmp2 == NULL) can_save = 0;
+ else can_save = 1;
+
+ if (tmp2 != NULL)
+ undo_state = getstate(NULL);
+ else undo_state = NULL;
+
+ if (undo_state != NULL)
+ restart_state = getstate(NULL);
+ else restart_state = NULL;
+
+ rfree(tmp1);
+ rfree(tmp2);
+ rm_start_size = get_rm_size();
+ rm_freesize = get_rm_freesize();
+ return can_save;
+}
+
+
+static void ext_dict(void)
+/* Enter the vocabulary extensions into the dictionary */
+{
+ wtype i;
+ for (i = wthe; i <= wabout; i = (wtype)((int)i + 1))
+ ext_code[i] = add_dict(ext_voc[i]);
+}
+
+
+static void fix_dummy(void) {
+ int i;
+
+ /* At this point, all occurances in the game file of the dictionary
+ words have been converted to dictionary indices, and so as long as
+ we don't change the dictionary index values, we can change the contents
+ without interfering with the metacommand scanner (since it compares
+ dictionary indices, not actual strings) */
+
+ if (!PURE_DUMMY) {
+ for (i = 0; i < DUMB_VERB; i++)
+ dict[ syntbl[auxsyn[i + BASE_VERB]] ][5] = ' ';
+ /* Convert underscores into spaces:
+ i.e. 'dummy_verb5' -> 'dummy verb5' */
+ dict[ syntbl[auxsyn[21]] ][6] = ' '; /* change_locations */
+ dict[ syntbl[auxsyn[55]] ][5] = ' '; /* magic_word */
+ }
+
+ if (!PURE_SUBNAME) /* Replace the 'e' by a space */
+ for (i = 0; i < MAX_SUB; i++)
+ sprintf(dict[sub_name[i]], "subroutin %d", i + 1);
+ /* This must be no longer than 25 characters with the terminating null */
+
+ /* Now set PURE_DOT based on whether any dictionary word
+ contains a period. */
+ if (aver >= AGT18 && aver <= AGT18MAX) PURE_DOT = 0;
+ else {
+ PURE_DOT = FORCE_PURE_DOT;
+ for (i = 0; i < dp && !PURE_DOT; i++)
+ if (strchr(dict[i], '.') != NULL && /* i.e. dict[i] contains period */
+ i != ext_code[wp]) /* The period itself _is_ a dictionary word:
+ avoid this false match */
+ PURE_DOT = 1;
+ }
+}
+
+
+/* char *v */
+
+static void print_license(void) {
+ writeln("AGiliTy");
+ writestr("The (Mostly) Universal AGT Interpreter, ");
+ writeln(version_str);
+ writeln(" Copyright (C) 1996-1999,2001 by Robert Masenten");
+ writestr("[");
+ writestr(portstr);
+ writeln("]");
+ writeln("-----------------------------------------------------------");
+ writeln("");
+ writeln(" This is an interpreter for game files created with Malmberg and "
+ "Welch's _Adventure Game Toolkit_. AGiliTy is universal in the "
+ "sense that it understands and interprets most of the many versions "
+ "of the AGT game file format.");
+ writeln(" It is *not* a port of the original interpreters but rather a "
+ "completely new interpreter built around the game file format; "
+ "while it follows the original interpreters on most things, there "
+ "are some differences which are described in the file "
+ "'readme.agility' which should have come with this program.");
+ writeln("");
+ writeln(" This software is copyright 1996-1999,2001 by Robert Masenten ");
+ writeln(" This program is free software; you can redistribute it and/or "
+ "modify it under the terms of version 2 of the GNU General "
+ "Public License as published by the Free Software Foundation.");
+ writeln(" 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.");
+ writeln(" 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");
+ writeln("");
+ writeln(" Send comments and bug reports to Robert Masenten at:");
+ writeln(" rcm-math@pacbell.net");
+ writeln("");
+ writeln("ACKNOWLEDGMENTS");
+ writeln("Thanks to Jay Goemmer, who has sent me pages and pages of "
+ "comments and bug reports; "
+ "David Kinder, responsible for the Amiga and Windows ports and a "
+ "source of much valuble feedback; "
+ "David Youd, who has uncovered many subtle and complex bugs in "
+ "both AGiliTy and Magx; "
+ "Mitch Mlinar, who has contributed several patches and other "
+ "suggestions; "
+ "all of those who have sent me suggestions and bug reports, "
+ "including "
+ "Audrey DeLisle (responsible for the red smoke), David Doherty,"
+ "Al Golden, "
+ "John Hartnup, Walter Isaac, Sami Kivela, Alexander Lehmann, "
+ "Grant E. Metcalf, "
+ "Paul Mikell, Adam Myrow, Olav Nielsen, "
+ "D.J. Picton, Kevin Soucy, Ben Straub, \"Grand Moff Tarkin\", "
+ "Adam Thornton, "
+ "Mark Tilford, David Turpin, and Gil Williamson; "
+ "Volker Blasius,"
+ " original maintainer of the Interactive Fiction Archive; "
+ "Robert Pelak, who suggested the name \"AGiliTy\"; "
+ "and to everyone on Rec.arts.int-fiction who suggested names for "
+ "my interpreter.");
+ writeln("");
+ writeln("SPECIAL VERBS RECOGNIZED");
+ writeln("These are all of the special verbs recognized by the interpreter:");
+ writeln(" SCORE Print out your score.");
+ writeln(" NOTIFY Turn score notification on and off.");
+ writeln(" INSTRUCTIONS or INS Display the instructions for the game.");
+ writeln(" INTRODUCTION or INTRO Repeat the introduction of the game.");
+ writeln(" VIEW <picture> Views an illustration. (Not supported on all "
+ "platforms.)");
+ writeln(" BRIEF Don't print room descriptions for rooms you've seen.");
+ writeln(" VERBOSE Print room descriptions even for rooms you've already "
+ "seen.");
+ writeln(" LIST EXITS List the exits from a room.");
+ writeln(" LISTEXIT ON/OFF Turn on/off automatic listing of exits.");
+ writeln(" SCRIPT Start sending a transcript to a file.");
+ writeln(" UNSCRIPT Stop creating a transcript.");
+ writeln(" SOUND ON, OFF Turn sound on/off.");
+ writeln(" LOG Start sending all of your commands to a file.");
+ writeln(" REPLAY <number> Replay your commands from a file, "
+ "executing one every <number> seconds.");
+ writeln(" REPLAY FAST Replay your commands from a file without waiting "
+ "for you to read the scrolling text.");
+ writeln(" REPLAY STEP Replay your commands from a file, "
+ "one for every keypress.");
+ writeln(" AGILDEBUG Access debugging commands.");
+ writeln(" MENU Toggle menu mode on or off.");
+ writeln(" OOPS Correct a word you just mistyped; must be the first "
+ "command on a line.");
+ writeln(" UNDO Undo your last move; must be the first command on a line.");
+ writeln(" SAVE Save the game.");
+ writeln(" RESTORE Restore the game.");
+ writeln(" RESTART Restart the game.");
+ writeln(" QUIT Quit.");
+ writeln("");
+}
+
+
+/* This is a hack to get rid of the "What Now?" prompt. */
+static void fix_prompt(void) {
+ descr_line *d;
+
+ if (err_ptr == NULL) return;
+ d = read_descr(err_ptr[0].start, err_ptr[0].size);
+ if (d == NULL) return;
+ if (strncasecmp(d[0], "What Now?", 9) == 0)
+ err_ptr[0].size = err_ptr[0].start = 0;
+ free_descr(d);
+}
+
+
+
+void close_game(void); /* Called by setup_game, and so needs
+ to be defined here. */
+
+static fc_type setup_game(fc_type fc)
+/* game_name is the common filename of the AGT game files */
+{
+ int can_save;
+ char choice;
+ rbool have_ins;
+
+ bold_mode = 0;
+ rm_acct = 1;
+ rm_trap = 1;
+ rm_size = ralloc_cnt = rfree_cnt = 0;
+ mars_fix = 0;
+ no_auxsyn = 0;
+ debug_disambig = 0;
+ debug_any = 1;
+ dbg_nomsg = 1; /* Supress output of MSG arguments to metacommands */
+ textbold = 0;
+ debug_mode = 0;
+ aver = 0;
+ verboseflag = 1;
+ notify_flag = 0;
+ logflag = 0;
+ menu_mode = 0;
+ fast_replay = 0;
+ stable_random = BATCH_MODE || make_test;
+ if (make_test) BATCH_MODE = 0;
+ hold_fc = fc;
+ set_default_filenames(fc);
+
+ init_stack();
+ read_config(agt_globalfile(0), 1); /* Global configuration file */
+
+ /* Now that we *have* PATH information, go looking for the games */
+ /* At the very least, it creates an rmalloc'd copy of game_name */
+ read_config(openfile(fc, fCFG, NULL, 0), 0);
+ text_file = 0;
+ /* First pass through game specific config file */
+ build_trans_ascii();
+#ifdef PROFILE
+ resetwatch();
+#endif
+ writeln("Loading game...");
+ if (!read_agx(fc, 0) && !readagt(fc, 0))
+ fatal("Unable to load game.");
+#ifdef PROFILE
+ writeln(stopwatch());
+ agt_waitkey();
+#endif
+ if (have_opt)
+ menu_mode = opt_data[5]; /* See agtread.c for discussion of OPT file
+ format */
+ text_file = 1;
+ read_config(openfile(fc, fCFG, NULL, 0), 1); /*Game specific config file*/
+ text_file = 0;
+ if (min_ver > AGIL_VERID) {
+ if (FORCE_VERSION)
+ agtwarn("This game requires a later version of AGiliTy.", 0);
+ else
+ fatal("This game requires a later version of AGiliTy.");
+ }
+ sort_cmd();
+ ext_dict();
+ build_verbmenu();
+ fix_dummy(); /* Prevent player from calling dummy verbs or subroutines by
+ typing 'Subroutine n' on the command line */
+ can_save = init();
+ if (!agx_file) open_descr(fc);
+ fix_prompt(); /* Kill off 'What Now?' prompt. */
+ if (BATCH_MODE || make_test)
+ set_test_mode(fc);
+ start_interface(fc);
+ fontcmd(2, 0); /* Set initial font */
+ if (intro_first && intro_ptr.size > 0) {
+ agt_clrscr();
+ print_descr(intro_ptr, 1);
+ wait_return();
+ }
+ if (aver >= AGTME10)
+ pictcmd(3, 0); /* Show title image, if there is one */
+ print_title(fc);
+ have_ins = open_ins_file(fc, 0);
+ do {
+ if (have_ins)
+ writestr("Choose <I>nstructions, <A>GiliTy Information, "
+ "or <other> to start the game");
+ else
+ writestr("Choose <A>GiliTy Information or <other> to start the game");
+ choice = tolower(agt_getchar()); /* Wait for keypress */
+ agt_clrscr();
+ if (have_ins && choice == 'i') print_instructions(fc);
+ else if (choice == 'a') print_license();
+ } while ((choice == 'i' && have_ins) || choice == 'a');
+ close_ins_file();
+ if (!intro_first && intro_ptr.size > 0) {
+ print_descr(intro_ptr, 1);
+ wait_return();
+ agt_clrscr();
+ }
+ if (maxroom < first_room) {
+ close_game();
+ error("Invalid first room");
+ }
+ set_statline();
+ if (can_save == 0) {
+ writeln("[Insufficiant memory to support SAVE, RESTORE, or UNDO]");
+ } else if (undo_state == NULL)
+ writeln("[Insufficiant memory to support UNDO]");
+ do_look = do_autoverb = 1;
+ newroom();
+ rm_acct = 1; /* Turn on memory allocation accounting */
+ return fc;
+}
+
+
+/* We need to import save_lnoun from exec.c so that we can free it. */
+extern parse_rec *save_lnoun;
+
+void close_game(void) {
+ if (agx_file)
+ agx_close_descr();
+ else
+ close_descr();
+ fontcmd(1, -1); /* Restore original font */
+ musiccmd(7, -1); /* Clean up */
+ close_interface();
+
+ /* Now free everything in sight; this _shouldn't_ be necessary,
+ but why take chances? */
+ free_all_agtread();
+ rfree(restart_state);
+ rfree(undo_state);
+ rfree(pictable);
+ rfree(save_lnoun);
+ rfree(verbptr);
+ rfree(verbend);
+ rfree(agt_counter);
+ rfree(agt_var);
+ free_creat_fix();
+ flag = (rbool *)rrealloc(flag, sizeof(rbool)); /* Preserve the debugging flag */
+
+ if (DEBUG_MEM)
+ debug("\n\nAlloc:%ld Freed:%ld Difference:%ld\n", ralloc_cnt,
+ rfree_cnt, ralloc_cnt - rfree_cnt);
+}
+
+
+void run_game(fc_type fc) {
+ doing_restore = 0;
+ rm_acct = 1;
+ rm_trap = 1;
+ rm_size = ralloc_cnt = rfree_cnt = 0;
+ read_config(agt_globalfile(0), 1); /* Global configuration file:
+ get PATH information*/
+ fix_file_context(fc, fDA1);
+ do {
+ if (doing_restore == 3) {
+ release_file_context(&fc);
+ fc = setup_game(new_game());
+ } else setup_game(fc);
+ doing_restore = 0;
+ mainloop();
+ close_game();
+ } while (doing_restore == 3);
+ release_file_context(&fc);
+}
+
+} // End of namespace AGT
+} // End of namespace Glk
diff --git a/engines/glk/agt/agility.h b/engines/glk/agt/agility.h
new file mode 100644
index 0000000000..34c125a6a9
--- /dev/null
+++ b/engines/glk/agt/agility.h
@@ -0,0 +1,1223 @@
+/* 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.
+ *
+ */
+
+#ifndef GLK_AGT_AGILITY
+#define GLK_AGT_AGILITY
+
+#include "glk/agt/config.h"
+#include "common/str.h"
+
+namespace Glk {
+namespace AGT {
+
+/* This indicates the AGiliTy version code for the current version. */
+/* 0=0.8.7 and the numbers are assigned sequentially from there.
+ 1=0.8.8
+ 2=1.0
+ (pre-0.8.7 versions of AGiliTy don't have version codes) */
+/* Don't touch this unless you know exactly what you're doing. */
+#define AGIL_VERID 3
+
+/*
+ This is the master header file for all of the AGT stuff.
+ It includes the global variables, the data types, etc.
+ (everything that is read in from the game file).
+ Variables not read in from the game file but used internally
+ by the AGiliTy interpreter are declared in interp.h.
+ Magx specific things are in comp.h.
+*/
+
+/* ------------------------------------------------------------------- */
+/* This is a preprocessor trick to ensure that space is only allocated
+ for global variables once. 'global' should only be defined in one
+ source file; for all of the other modules, it will be converted to
+ extern by the following lines */
+/* ------------------------------------------------------------------- */
+#ifndef global /* Don't touch this */
+#define global extern
+#define global_defined_agtread
+#endif
+
+
+/* ------------------------------------------------------------------- */
+/* DEFINITIONS OF SPECIAL DATA TYPES */
+/* These should by platform-independent. */
+/* ------------------------------------------------------------------- */
+
+#ifdef force16 /* This is for debugging purposes */
+#define int short int
+#endif
+
+/* General data types */
+
+typedef byte uchar;
+typedef int8 schar;
+typedef int16 integer;
+typedef uchar rbool;
+
+enum { INT8_MAX_VAL = 127, BYTE_MAX_VAL = 255 };
+
+#define WORD_LENG 25
+
+/* Game-specific data type */
+typedef char tline[81]; /* Both of these must include terminating null */
+typedef char words[WORD_LENG]; /* ...23 in classic, 16 in master's */
+typedef short word; /* A pointer into the dictionary */
+typedef short slist; /* Index into synlist marking beginning of this
+ synonym list. [SYNCNT]
+ list of word pointers -- eg synonyms */
+typedef char *descr_line; /* This is the type used to return descriptions.
+ They are actually returned as an array of
+ pointers to char, one for each line.
+ It is NULL terminated. */
+typedef char *filename; /* Datatype used for picture, sound, etc. names*/
+
+
+typedef enum {fNONE,
+ fDA1, fDA2, fDA3, fDA4, fDA5, fDA6, fDSS,
+ fHNT, fOPT, fTTL,
+ fSAV, fSCR, fLOG,
+ fAGX, fINS, fVOC, fCFG,
+ fAGT, fDAT, fMSG, fCMD, fSTD, fAGT_STD
+ } filetype;
+
+
+/* ------------------------------------------------------------------- */
+/* GLOBAL FLAGS */
+/* Many of the following should be made into command line options */
+/* ------------------------------------------------------------------- */
+
+/* #define AGT_16BIT */ /* Force interpreter to use 16-bit quantities */
+/* #define DUMP_VLIST */ /* Dump out the verb list info */
+/* #define USE_CMD_INDEX */ /* Read in metacommand index data for objects */
+
+
+#define SS_GRAIN 1024 /* Granularity of size request for static string
+ array (to avoid calling rrealloc too often) */
+#define SYN_GRAIN 32 /* Granularity of requests for synonym array; this
+ is in units of sizeof(word) */
+#define MAX_BADTOK 10 /* How many bad tokens to report before giving
+ up */
+
+
+
+/* The following are defaults that can be overridden from the command line */
+/* The real variable has the name without the "def_" */
+
+#define def_DIAG 0 /* Print out diagnostic data? */
+#define def_interp_arg 1 /* Decipher arguments to opcodes? */
+#define def_debug_da1 0 /* used to analyse .DA1 files */
+#define def_RAW_CMD_OUT 0 /*Print CMDs out raw, undecoded; sometimes useful
+ when trying to understand new gamefile version */
+#define def_ERR_LEVEL 2 /* Level of error reporting. */
+/* 0== almost none */
+/* 1== Report possibly serious conditions */
+/* 2== Report any fall from perfection */
+
+
+
+
+
+
+
+/* ------------------------------------------------------------------- */
+/* DEFINES OF GLOBAL PARAMETERS */
+/* ------------------------------------------------------------------- */
+
+#define FORMAT_CODE 0xFF /* Character used for special formatting codes:
+ --in 1.8x games, it designates bold
+ --in AGX+ games, it will prefix format codes
+ --otherwise it designates black (replacing
+ 0, which has obvious problems) */
+#define LAST_TEXTCODE 8 /* Last ascii code to be used for text attributes */
+
+
+
+#define MAX_PIX 31
+#define MAX_FLAG_NOUN 31
+
+
+
+#define OLD_VERB 59 /* Number of built in verbs in original interpreters--
+ this number includes ANY, which is verb 0. */
+#define DIR_ADDR_CODE (OLD_VERB+17) /* Verb ID used for direct address */
+#define AUX_VERB 18 /* Additional verbs supported by the interpreter */
+#define BASE_VERB (OLD_VERB+AUX_VERB) /* Total number of built-in verbs */
+#define DUMB_VERB (DVERB+MAX_SUB) /* Number of dummy verbs and subroutines */
+#define TOTAL_VERB (BASE_VERB+DUMB_VERB) /* Total count of verbs */
+
+
+/* The following numbers refer to the ideal code we are translating into,
+ not the parameters for the actual data file we're reading. */
+#define MAX_COND 143 /* Last condition token id */
+#define START_ACT 1000 /* First action code */
+#define PREWIN_ACT 1161 /* Last action code before WinGame */
+#define WIN_ACT 2000 /* Value of WinGame opcode */
+#define END_ACT (WIN_ACT+2) /* Lowest command-terminating action code */
+#define MAX_ACT (WIN_ACT+4) /* Highest action code */
+
+/* Note: the following values are used by Magx internally:
+ 3000-- Used to mark 'no entry' in the opcode hash table.
+ 8000-- Used for NEXT
+ 8001-- Used for PREV
+ */
+
+#define MAX_TOKEN_ID 250 /* Upper limit on legal (raw) token values
+ read from AGT files. Doesn't need to be exact. */
+
+/* Number of built in properties and attributes. */
+#define NUM_PROP 14
+#define NUM_ATTR 26
+
+
+/*
+ None of these are used any more, but I leave them here for reference
+ #define MAX_ROOM 300
+ #define MAX_NOUN 300
+ #define MAX_CREAT 200
+ #define MAX_CMD 1500
+ #define MAX_QUEST 100
+ #define MAX_MSG 1000
+ #define MAX_PICT 250
+ #define ABS_MAX_REC_CMD 34
+ #define MAX_OBJ (MAX_ROOM+MAX_NOUN+MAX_CREAT)
+ #define SYNCNT 15
+*/
+
+
+
+/* --------------------------------------------------------------------- */
+/* DATA STRUCTURE DEFINITIONS */
+/* */
+/* All of the internal data structures used to store the contents of the */
+/* game file */
+/* --------------------------------------------------------------------- */
+
+/* First, pointers to game descriptions */
+/* start and size may be measured in units of characters or in units */
+/* of tline */
+typedef struct {
+ long start;
+ long size;
+} descr_ptr;
+
+
+/* Entries in the opcode tables: the name of the opcode, the number of
+ arguments, and the types of those arguments */
+typedef struct {
+ const char *opcode;
+ integer argnum;
+ integer arg1, arg2;
+} opdef; /* Opcode table entry */
+
+
+
+/* This is the data type for opcode correction entries */
+/* These are used to translate the opcodes from the various versions */
+/* to a uniform internal format. */
+/* The actual translation tables built with this are in agtdata.c */
+typedef struct {
+ integer told, tnew; /* Old and new values. */
+} cmd_fix_rec;
+
+typedef const cmd_fix_rec *fix_array;
+
+
+/* ROOMS */
+typedef struct {
+ const char *name; /* [31] */
+ int32 flag_noun_bits, PIX_bits; /* Master's Ed. only */
+ slist replacing_word; /* List of words to be replaced */
+ word replace_word; /* Word that does replacing */
+ word autoverb; /* Verb automatically executed on entry to room */
+ integer path[13];
+ integer key;
+ integer contents; /* Used internally by the interpreter; not read in
+ from the game file */
+ integer points;
+ integer light; /* Object that lights room; 0=none needed, 1=any */
+ integer pict, initdesc;
+ integer oclass;
+ rbool seen, locked_door;
+ rbool end, win, killplayer;
+ rbool unused; /* True if this room entry is unused */
+} room_rec;
+
+
+/* NOUNS */
+typedef struct {
+ const char *shortdesc; /* tline */
+ const char *position;
+ /* 23 characters in position for classic ed, 31 for ME */
+ const char *initpos; /*Starting value of position;used for RESTART/RESTORE*/
+ int scratch; /* Scratch space for use by various parser routines. */
+ slist syns; /* List of synonyms */
+ word name, adj;
+ word related_name; /* Word that should appear on menu when this noun
+ is in scope */
+ word pos_prep, pos_name; /* Used internally by the interpreter */
+ /* pos_prep==-1 means use noun.position */
+ integer nearby_noun; /* Noun this noun is behind */
+ integer num_shots;
+ integer points;
+ integer weight, size;
+ integer key;
+ integer initdesc, pict;
+ integer location; /* 1=carried, 1000=worn */
+ integer contents, next; /* Used internally by the interpreter; not read in
+ from the game file */
+ integer oclass;
+ integer flagnum; /* Number of associated flag + 1. 0=no flag. */
+ rbool scope; /* Used internally by the interpreter */
+ rbool isglobal; /* True if global object */
+ uchar plural;
+ rbool something_pos_near_noun; /* Anybody behind us? */
+ rbool has_syns;
+ rbool pushable, pullable, turnable, playable, readable;
+ rbool on, closable, open, lockable, locked, edible, wearable;
+ rbool drinkable, poisonous, movable, light;
+ rbool shootable;
+ rbool win;
+ rbool unused; /* True if this noun entry is unused */
+ rbool seen; /* True if this noun has been seen by the player */
+ rbool proper; /* True if noun's name is to be treated as proper noun. */
+} noun_rec;
+
+
+/* CREATURES */
+typedef struct {
+ const char *shortdesc; /* tline */
+ int scratch; /* Scratch space for use by various parser routines. */
+ slist syns;
+ word name;
+ word adj;
+ integer location;
+ integer contents, next; /* Used internally by the interpreter; not read in
+ from the game file */
+ integer weapon; /* Killed by this */
+ integer points;
+ integer counter; /* How many times has player been nasty to it? */
+ integer threshold, timethresh, timecounter;
+ integer pict, initdesc;
+ integer oclass;
+ integer flagnum; /* Number of associated flag + 1. 0=no flag. */
+ rbool scope; /* Used internally by the interpreter */
+ rbool isglobal; /* True if global object */
+ rbool has_syns;
+ rbool groupmemb;
+ rbool hostile;
+ uchar gender;
+ rbool unused; /* True if this creature entry is unused */
+ rbool seen; /* True if this creature has been seen by the player */
+ rbool proper; /* True if this creature's name is to be treated as a proper
+ noun (i.e. not prepended with "the") */
+} creat_rec;
+
+/* These records are used to hold reference information for
+ user-defined flags and properties */
+/* These give the base index offset in objflag/objprop of each flag/prop
+ for rooms, nouns, and creatures. The flag record also contains the
+ bit offset in the given byte. */
+/* This approach allows for a single property to be located in different
+ places for each of the three types of objects. */
+/* A value of -1 for any of the three fields indicates that this
+ property doesn't exist for the given type of object. */
+
+typedef struct {
+ long r, n, c;
+ long str_cnt; /* values from 0..(str_cnt-1) have assoc strings */
+ long str_list; /* Index into propstr[] array */
+} propdef_rec;
+
+typedef struct {
+ long r, n, c;
+ char rbit, nbit, cbit;
+ const char *ystr, *nstr; /* Yes and no strings */
+} attrdef_rec;
+
+typedef struct {
+ long str_cnt;
+ long str_list;
+} vardef_rec;
+
+
+typedef struct {
+ const char *ystr, *nstr;
+} flagdef_rec;
+
+/* Metacommand headers and a pointer to the actual sequence of tokens
+ to execute if the metacommand is run. */
+typedef struct {
+ integer actor; /* Contains the actor object number;
+ 1==self(no explicit actor) 2=anybody
+ It is multiplied by negative one for redirected
+ commands */
+ /* 0 in any of the following word variables denotes ANY;
+ -1 denotes <*none*> and will only occur in Magx-generated files.
+ (verbcmd cannot be equal to -1). Support for -1 is still experimental */
+ word verbcmd, nouncmd, objcmd, prep; /* prep only in ME games */
+ word noun_adj, obj_adj; /* Adjectives for noun and obj; not
+ supported in original AGT games */
+ integer noun_obj, obj_obj; /* Actual object numbers,
+ or 0 if none specified */
+ integer *data; /* MaxSizeCommand */
+ integer cmdsize; /* Number of commands */
+ /* integer ptr; */ /* In ME games-- see below for replacement */
+} cmd_rec;
+
+
+/* FRS=file record size; these are the sizes to be allocated to the buffer
+ used to read in the various records from the files; they should be at
+ least as big as the worst case scenario. */
+#define FRS_ROOM 220
+#define FRS_NOUN 310
+#define FRS_CREAT 240
+#define FRS_CMD 150
+
+
+/* This is the record used to hold menu information for verbs */
+typedef struct { /*verb menu entry */
+ word verb; /* Verb word */
+ word prep; /* Associated preposition */
+ short objnum; /* Number of objects */
+} verbentry_rec;
+
+
+/* This is the datatype that will be used (tenatively) for hint info */
+/* This isn't implemented yet. */
+typedef struct {
+ integer dtype; /* The hint element type */
+ integer child; /* The hint element value */
+ const char *name; /* The hint element name */
+} hint_rec;
+
+
+/* This is the data type used to hold information about
+ built-in attributes and properties */
+struct prop_struct {
+ const char *name; /* Property name. */
+ int room, noun, creature; /* Offsets for the various object types. */
+};
+
+
+/* The following data structure is used to store info on fields of a struct
+ that may need to be read from/written to a file. */
+/* They are used by both the AGX and the Savefile routines */
+/* They should be organized in ftype==0 terminated arrays,
+ in the order they occur in the file (the file is assumed to have
+ no padding, so we don't need file offsets: they can be computed) */
+/* The following is used for both global variables and fields in
+ structures. For global variables, ptr is set to point at the variable
+ and offset is 0. For fields, offset is set to the offset of the field
+ in the structure and ptr is set internally */
+typedef struct {
+ int ftype; /* Type in file */
+ int dtype; /* Data type of field in memory; often ignored */
+ void *ptr; /* Pointer to variable */
+ size_t offset; /* Offset of field in structure */
+} file_info;
+
+
+
+/* This contains all of the information needed to find files. */
+#ifndef REPLACE_FC
+typedef struct {
+ char *gamename; /* Name as entered by user */
+ char *path; /* The path */
+ char *shortname; /* The filename w/o directory information */
+ char *ext; /* The preexisting extension/prefix */
+ filetype ft; /* The filetype corresponding to ext */
+ int special; /* Used to mark special files, such as UNIX pipes */
+} file_context_rec;
+
+typedef file_context_rec *fc_type;
+
+#endif
+
+
+/* ------------------------------------------------------------------- */
+/* GLOBAL VARIABLES */
+/* ------------------------------------------------------------------- */
+
+/* ------------------------------------------------------------------- */
+/* Flags used internally by the interpreter and reading routines */
+
+global uchar DIAG, interp_arg, debug_da1, RAW_CMD_OUT;
+global int ERR_LEVEL;
+
+global rbool agx_file; /* Are we reading an AGX file? */
+global rbool have_opt; /* Do we have an OPT file? */
+global rbool skip_descr; /* Causes read_filerec() to skip over description
+ pointers without actually reading them in.
+ Used to support RESTORE for multi-part games
+ such as Klaustrophobia */
+global rbool no_auxsyn; /* Prevents building of auxsyn and preplist
+ synonym lists; used by agt2agx. */
+
+
+global rbool BATCH_MODE, make_test;
+/* These indicates we are in testing mode:
+ -- The random number generator should be set to a standard state.
+ -- Automatically send output to <gamename>.scr
+ -- Automatically read/write input from <gamename>.log
+ (depending on whether we are in BATCH_MODE or make_test mode,
+ respectivly).
+*/
+
+
+/* ------------------------------------------------------------------- */
+/* Flags reflecting game version and configuration */
+
+global rbool have_meta; /* Do we have any metacommands? */
+global rbool debug_mode, freeze_mode, milltime_mode, bold_mode;
+global uchar score_mode, statusmode;
+global rbool intro_first;
+global rbool box_title;
+global rbool mars_fix;
+global rbool fix_ascii_flag; /* Translate IBM characters?
+ Defaults to fix_ascii #define'd above */
+global rbool dbg_nomsg; /* Causes printing of <msg> arguments by
+ debug disassembler to be supressed */
+
+global rbool irun_mode; /* If true, all messages will be in 1st person */
+global rbool verboseflag;
+
+global int font_status; /* 0=unknown; 1=force fixed font,
+ 2=allow proportional font. */
+
+
+/* The following are AGT 'purity' flags; they turn off features of */
+/* my interpreter that are not fully consistent with the original AGT. */
+/* They are defined in auxfile.c (see also interp.h and agil.c for */
+/* interpreter-specific flags) */
+/* Anything added here should also be correctly initialized in agt2agx */
+
+extern rbool PURE_ANSWER, PURE_TIME, PURE_ROOMTITLE;
+extern rbool PURE_AND, PURE_METAVERB, PURE_ERROR;
+extern rbool PURE_SYN, PURE_NOUN, PURE_ADJ, PURE_SIZE;
+extern rbool PURE_DUMMY, PURE_SUBNAME, PURE_PROSUB;
+extern rbool PURE_HOSTILE, PURE_GETHOSTILE;
+extern rbool PURE_DISAMBIG, PURE_ALL, PURE_OBJ_DESC;
+extern rbool PURE_GRAMMAR, PURE_SYSMSG, PURE_AFTER;
+extern rbool PURE_PROPER;
+
+extern rbool TWO_CYCLE, FORCE_VERSION;
+extern rbool MASTERS_OR;
+
+/* ------------------------------------------------------------------- */
+/* Variables containing limits and counts of objects */
+
+global integer FLAG_NUM, CNT_NUM, VAR_NUM; /* (255, 50, 50) */
+global integer MAX_USTR; /* Maximum number of user strings (25) */
+global integer MAX_SUB; /* Number of subroutines (15) */
+global integer DVERB; /* Number of real dummy_verbs (50) */
+global integer NUM_ERR; /* For ME is 185 */
+
+/* Number of objflags and objprops for each type of object */
+/* The flag counts are for groups of 8 flags. */
+global integer num_rflags, num_nflags, num_cflags;
+global integer num_rprops, num_nprops, num_cprops;
+global integer oprop_cnt, oflag_cnt; /* Size of flag and property tables */
+
+global integer maxroom, maxnoun, maxcreat;
+global long MaxQuestion;
+global integer first_room, first_noun, first_creat, last_obj;
+global long last_message, last_cmd;
+global long numglobal; /* Number of global nouns */
+global long maxpict, maxpix, maxfont, maxsong;
+global long num_prep; /* Number of user-defined prepositions */
+global int num_auxcomb;
+global int num_comb;
+
+global integer exitmsg_base; /* Number added to messages used as
+ "illegal direction" messages */
+
+
+/* ------------------------------------------------------------------- */
+/* Miscellaneous other variables read in from the game file */
+
+global integer start_room, treas_room, resurrect_room, max_lives;
+global long max_score;
+global integer startup_time, delta_time;
+
+/* ver contains the size of the game, aver indicates its version */
+/* See the #define's below for details */
+global int ver, aver; /* ver: 0=unknown, 1=small, 2=big, 4=masters1.5 */
+global long game_sig; /* 2-byte quantity used to identify game files */
+/* (It's declared long to avoid overflow problems when
+computing it) */
+global int vm_size; /* Size of verb menu */
+
+global int min_ver; /* Lowest version of AGiliTy this will run on. */
+
+/* ------------------------------------------------------------------- */
+/* Miscellaneous Game Data Structures */
+
+/* All of the following are allocated dynamically */
+global room_rec *room; /* [MAX_ROOM]; */
+global creat_rec *creature; /* [MAX_CREAT]; */
+global noun_rec *noun; /* [MAX_NOUN]; */
+global cmd_rec *command;
+
+global unsigned char *objflag;
+global long *objprop;
+
+/* Next we have tables linking abstract flag/property values to
+ the indices with the objflag/objprop arrays. */
+global attrdef_rec *attrtable;
+global propdef_rec *proptable;
+global vardef_rec *vartable; /* For itemized variables */
+global flagdef_rec *flagtable;
+
+/* Output strings associated with various property values. */
+/* See propdef_rec */
+global const char **propstr;
+global long propstr_size;
+
+global tline *userstr; /*[MAX_USTR];*/
+global word *sub_name; /* [MAX_SUB] Dictionary id's of all subroutines */
+
+/* Verb information */
+global verbentry_rec *verbinfo; /* Verb information */
+global short *verbptr, *verbend; /* [TOTAL_VERB] */
+global slist *synlist; /* [MAX_VERBS+1];*/
+global slist *comblist; /* Global combination list */
+global word *old_agt_verb; /* List of non-canonical verb synonyms in the
+ original AGT; these are not allowed to be
+ expanded as dummy verbs. */
+
+
+global slist *userprep; /* Array of user-defined prepostions */
+
+global word flag_noun[MAX_FLAG_NOUN], *globalnoun;
+global word pix_name[MAX_PIX];
+global filename *pictlist, *pixlist, *fontlist, *songlist;
+
+global uchar opt_data[14]; /* Contents of OPT file. For the format of this
+ block, see the comments to read_opt() in
+ agtread.c */
+
+/* These are built by reinit_dict */
+
+global slist *auxsyn; /* [TOTAL_VERB] Built-in synonym list */
+global slist *preplist; /* [TOTAL_VERB] */
+global uchar *verbflag; /* [TOTAL_VERB] Verb flags; see below */
+global slist *auxcomb; /* Built-in combination lists (for multi-word
+ verbs) */
+
+#ifdef PATH_SEP
+global char **gamepath;
+#endif
+
+/* ------------------------------------------------------------------- */
+/* Description Pointers */
+
+
+global descr_ptr intro_ptr;
+global descr_ptr title_ptr, ins_ptr; /* Only defined if agx_file is true */
+global descr_ptr *err_ptr; /* [NUM_ERR];*/
+
+global descr_ptr *msg_ptr; /* [MAX_MSG];*/
+global descr_ptr *help_ptr, *room_ptr, *special_ptr; /*[ROOM] */
+global descr_ptr *noun_ptr, *text_ptr, *turn_ptr, /* [NOUN] */
+ *push_ptr, *pull_ptr, *play_ptr;
+global descr_ptr *talk_ptr, *ask_ptr, *creat_ptr; /* [CREAT] */
+
+global descr_ptr *quest_ptr, *ans_ptr; /* [MAX_QUEST] */
+global tline *question, *answer; /* [MAX_QUEST] */
+
+
+
+/* ------------------------------------------------------------------------ */
+/* Dynamically allocated data blocks (which are pointed to from elsewhere) */
+
+global char **dict; /* dict[n] points to the nth dictionary word */
+global long dp; /* Dictionary pointer: number of words in dict */
+
+#define DICT_INIT 12*1024 /* Starting size of dictstr */
+#define DICT_GRAN 1024 /* Granularity of dictstr size requests
+ must be at least 4. */
+global char *dictstr; /* Pointer to memory block containing dict words */
+global long dictstrptr, dictstrsize;
+/* dictstrptr points to the first unused byte in dictstr.
+ dictstrsize points to the end of the space currently allocated for
+ dictstr.
+*/
+
+global char *static_str; /*Static string space */
+global long ss_end; /* Pointer to end of used space in above */
+global long ss_size; /* Current size of static string space */
+
+global word *syntbl; /* Synonym list space */
+global slist synptr; /* Points to end of used space */
+global long syntbl_size; /* Long so we can catch overflows */
+
+global long descr_maxmem;
+global char *mem_descr; /* Copy of descriptor in memory */
+
+
+
+/* ------------------------------------------------------------------------ */
+/* Data structures used internally by agtread.c */
+
+/*The following are all set to NULL after agtread finishes. */
+global long *cmd_ptr; /* ME only;Points to cmd start locs in gamefile.*/
+global long *room_name, *noun_sdesc, *noun_pos, *creat_sdesc;
+global long *t_pictlist, *t_pixlist, *t_songlist, *t_fontlist;
+
+/* These are only used by agtout (to allow the AGT reading routines to
+ pass back the count of nouns inside the given object) */
+global integer *room_inside, *noun_inside, *creat_inside;
+
+/* This is used to translate ASCII codes */
+global uchar fixchar[256];
+
+global rbool text_file; /* Set if we are currently opening a binary file. */
+#ifdef OPEN_AS_TEXT
+global rbool open_as_binary; /* Open text files as binary, anyhow. */
+#endif
+
+
+/* ------------------------------------------------------------------ */
+/* SYMBOLIC CONSTANTS: VERSION CODES */
+/* These are the values stored in the variable 'aver'. */
+/* ------------------------------------------------------------------ */
+
+#define AGT10 1 /* SPA */
+#define AGT118 2 /* TAMORET, PORK II */
+#define AGT12 3 /* SOS,... */
+#define AGTCOS 4 /* COSMOS and SOGGY: enhanced versions of 1.3x */
+#define AGT135 5 /* By far the most common version; includes practically
+ every version of Classic AGT from 1.19 to 1.7 */
+#define AGT182 6
+#define AGT183 7
+#define AGT15 8 /* HOTEL */
+#define AGT15F 9 /* MDTHIEF */
+#define AGT16 10 /* PORK */
+#define AGTME10 11 /* CLIFF2, ELF20 */
+#define AGTME10A 12 /* HURRY */
+#define AGTME15 13 /* WOK */
+#define AGTME155 14 /* TJA */
+#define AGTME16 15 /* also includes v1.56 and 1.7 */
+#define AGX00 16 /* Tenative */
+
+#define AGTMAST AGTME16
+#define AGTCLASS AGT16 /* Dividing line between master's ed and classic */
+#define AGT18 AGT182 /* Defines lowest 1.8x version */
+#define AGT18MAX AGT183 /* Defines the highest 1.8x version */
+#define AGTSTD AGT135 /* "Default" version of AGT */
+
+
+
+/* ------------------------------------------------------------------ */
+/* SYMBOLIC CONSTANTS: ARGUMENT TYPES */
+/* These are used to encode the argument types of metacommands for */
+/* opcode tables. */
+/* ------------------------------------------------------------------ */
+
+#define AGT_UNK 0 /* Unknown argument type */
+
+/* The following can all mix and match in various ways and so are
+ put together as powers of two. */
+#define AGT_NONE 1 /* 0 is allowed */
+#define AGT_SELF 2 /* 1 is allowed */
+#define AGT_WORN 4 /* 1000 is allowed */
+#define AGT_ROOM 8 /* A room # is allowed */
+#define AGT_ITEM 16 /* An item # is allowed */
+#define AGT_CREAT 32 /* A creature # is allowed */
+
+/* AGT_VAR is special, since it is always combined with another type--
+ the type that the variable is expected to be */
+#define AGT_VAR 64
+
+/* The rest of the values are always distinct; they never mix and so
+ they can be given simple consecutive indices */
+#define AGT_NUM 128
+#define AGT_FLAG 129
+#define AGT_QUEST 130 /* Question */
+#define AGT_MSG 131 /* Message */
+#define AGT_STR 132 /* String */
+#define AGT_CNT 133 /* Counter */
+#define AGT_DIR 134 /* Direction */
+#define AGT_SUB 135 /* Subroutine */
+#define AGT_PIC 136 /* Picture */
+#define AGT_PIX 137 /* Room picture */
+#define AGT_FONT 138
+#define AGT_SONG 139
+#define AGT_ROOMFLAG 140
+#define AGT_TIME 141
+#define AGT_ERR 142
+#define AGT_OBJFLAG 143 /* User defined object flags */
+#define AGT_OBJPROP 144 /* User defined object properties */
+#define AGT_ATTR 145 /* Built-in attribute */
+#define AGT_PROP 146 /* Built-in property */
+
+
+/* These next three may not occur as operand types */
+#define AGT_EXIT 147 /* Valid values for an exit: room, msg+msgbase, 0 */
+#define AGT_GENFLAG 148 /* PIX or Room Flag; used internally by compiler */
+#define AGT_GENPROP 149 /* ObjProp/Property */
+
+/* certain restrictions. Used internally
+ by the compiler in the parsing
+ of "[obj].[prop].[prop].[flag]"
+ constructions. */
+
+#define AGT_LVAL 150 /* Used by the compiler in certain psuedo-ops */
+
+
+#define AGT_DEFINE 256
+
+
+
+/* ------------------------------------------------------------------ */
+/* Verb flags for verbflag[]; these should be powers of 2 */
+
+#define VERB_TAKEOBJ 1
+#define VERB_META 2
+#define VERB_MULTI 4 /* Can the verb take mulitple objects? */
+#define VERB_GLOBAL 8 /* Does the verb have global scope? */
+
+/* ------------------------------------------------------------------ */
+/* SYMBOLIC CONSTANTS: FILE DATA TYPES */
+/* Data type specifiers for file i/o routines */
+/* The FT_* constants specify file data types */
+/* The DT_* constants specify internal data types */
+/* ------------------------------------------------------------------ */
+
+#define FT_COUNT 17 /* Number of file data types */
+
+#define FT_END 0 /* End of list of fields/variables in file */
+#define FT_INT16 1 /* DT_SHORT */
+#define FT_UINT16 2 /* DT_LONG */
+#define FT_INT32 3 /* DT_LONG */
+#define FT_UINT32 4
+#define FT_BYTE 5 /* aka uchar; DT_UCHAR */
+#define FT_VERSION 6 /* Game version */
+#define FT_BOOL 7 /* DT_BOOL. Adjacent rbooleans are packed */
+#define FT_DESCPTR 8 /* DT_DESCPTR */
+#define FT_STR 9 /* Integer pointer into static string array */
+#define FT_SLIST 10 /* Synonym list index */
+#define FT_WORD FT_INT16 /* Index into dictionary */
+#define FT_PATHARRAY 11 /* 13 integers in an array of directions */
+#define FT_CMDPTR 12 /* Pointer into command block */
+#define FT_DICTPTR 13 /* Pointer into dictionary text */
+#define FT_TLINE 14 /* TextLine */
+#define FT_CHAR 15 /* Characters. */
+#define FT_CFG 16 /* Configuration byte; 0=false, 1=true,
+ 2=none (don't change) */
+
+#define DT_DEFAULT 0 /* Default internal type for <ftype> */
+#define DT_LONG 1
+#define DT_DESCPTR 2 /* Description pointer, which are treated specially */
+#define DT_CMDPTR 3 /* Command block pointer, also treated specially */
+
+/* This is the end marker for the file definitions used by the file I/O
+ routines */
+#define endrec {FT_END,0,NULL,0}
+
+
+/* ------------------------------------------------------------------- */
+/* Date type macros */
+/* ------------------------------------------------------------------- */
+#define troom(x) ((x)>=first_room && (x)<=maxroom)
+#define tnoun(x) ((x)>=first_noun && (x)<=maxnoun)
+#define tcreat(x) ((x)>=first_creat && (x)<=maxcreat)
+
+
+/* ------------------------------------------------------------------- */
+/* FUNCTION PROTOTYPES AND INITIALIZED TABLES */
+/* ------------------------------------------------------------------- */
+
+/* This is intended for removing whitespace in AGT data files. */
+#define rspace(c) ((c)==' ' || (c)=='\t')
+
+/* ------------------------------------------------------------------- */
+/* In GAMEDATA.C */
+/* This module contains the major initialized data structures and */
+/* routines to manipulate game data structures, in particular the */
+/* game's dictionary */
+/* ------------------------------------------------------------------- */
+
+void init_dict(void); /* 1=set of verblist, 0=don't */
+void build_verblist(void); /* Creates verblist */
+void reinit_dict(void);
+void free_dict(void);
+word search_dict(const char *);
+word add_dict(const char *);
+const char *gdict(word w); /* Almost equivalent to dict[w], but with
+ some error checking and handling for
+ the w==-1 case. */
+
+int verb_code(word);
+int verb_builtin(word);
+int verb_authorsyn(word);
+void addsyn(word);
+slist add_multi_word(word);
+
+/* Commands to manipulate object flags */
+long objextsize(char op);
+long op_objprop(int op, int obj, int propid, long val);
+rbool op_objflag(int op, integer obj, int flagid);
+long lookup_objflag(int id, int t, char *ofs);
+long lookup_objprop(int id, int t);
+rbool have_objattr(rbool prop, integer obj, int id);
+int num_oattrs(int t, rbool isflag);
+rbool op_simpflag(uchar *flag, char ofs, int op);
+/* op: 0=clear, 1=set, 2=nop, 3=toggle two bits: <ab> */
+const char *get_objattr_str(int dtype, int id, long val);
+
+
+const opdef *get_opdef(integer op);
+char *objname(int);
+void sort_cmd(void);
+
+void agtwarn(const char *, int elev);
+void agtnwarn(const char *, int, int elev);
+
+void init_flags(void);
+
+#ifdef ZIP
+#define fatal agil_fatal
+#endif
+void fatal(const char *);
+
+long new_str(char *buff, int max_leng, rbool pasc);
+
+descr_line *read_descr(long start, long size);
+void free_descr(descr_line *p);
+
+extern const char trans_ibm[];
+extern const char nonestr[];
+
+/* Tables of opcodes */
+extern const opdef cond_def[], act_def[], end_def[], illegal_def;
+
+/* Table of built in properties and attributes */
+extern const prop_struct proplist[NUM_PROP];
+extern const prop_struct attrlist[NUM_ATTR];
+
+global words *verblist; /* List of prexisting words, intialized by init_dict */
+extern const fix_array FIX_LIST[];
+extern const char *exitname[13];
+extern const char *verstr[], *averstr[];
+extern const char *version_str, *portstr;
+
+
+/* ------------------------------------------------------------------- */
+/* In AGTREAD.C */
+/* Routines to read in AGT data files */
+/* ------------------------------------------------------------------- */
+
+void open_descr(fc_type fc);
+void close_descr(void);
+descr_line *agt_read_descr(long start, long len);
+rbool readagt(fc_type fc, rbool diag);
+void free_all_agtread(void); /* Cleans up everything allocated in agtread
+ should only be called at the very end of
+ the program */
+void free_ttl(descr_line *title);
+
+
+/* ------------------------------------------------------------------- */
+/* In AGXFILE.C */
+/* Routines to read and write AGX files */
+/* ------------------------------------------------------------------- */
+
+int read_agx(fc_type fc, rbool diag);
+descr_line *agx_read_descr(long start, long size);
+void agx_close_descr(void);
+
+/* The following are in the order they should be called */
+void agx_create(fc_type fc);
+void write_descr(descr_ptr *dp, descr_line *txt);
+void agx_write(void);
+void agx_wclose(void);
+void agx_wabort(void);
+
+/* ------------------------------------------------------------------- */
+/* In AUXFILE.C */
+/* Routines to read VOC, OPT, CFG, TTL, INS, etc. files */
+/* ------------------------------------------------------------------- */
+void read_opt(fc_type fc);
+void read_config(genfile cfgfile, rbool lastpass);
+rbool parse_config_line(char *s, rbool lastpass);
+
+descr_line *read_ttl(fc_type fc); /* This returns the title. The return string
+ must be freed with free_ttl() and not
+ with free_descr */
+void free_ttl(descr_line *title);
+
+void read_voc(fc_type fc);
+void init_verbrec(void);
+void add_verbrec(const char *verbline, rbool addnew); /* addnew should be 0 */
+void finish_verbrec(void);
+
+descr_line *read_ins(fc_type fc);
+void free_ins(descr_line *instr);
+rbool open_ins_file(fc_type fc, rbool report_error);
+char *read_ins_line(void); /* Reuses buffer, so return value should be copied
+ if it needs to be used past the next call to
+ read_ins_line or close_ins_file */
+void close_ins_file(void);
+
+void build_fixchar(void);
+
+/* ------------------------------------------------------------------- */
+/* In or used by DISASSEMBLE.C */
+/* Routines to disassemble metacommands (used by the interpreter for */
+/* tracing and by agtout to produce the metacommand output). */
+/* ------------------------------------------------------------------- */
+
+global rbool *dbgflagptr;
+global long *dbgvarptr;
+global short *dbgcntptr;
+
+void dbgprintf(const char *fmt, ...);
+void debugout(const char *s);
+int argout(int dtype, int dval, int optype);
+
+/* ------------------------------------------------------------------- */
+/* In INTERFACE.C, AGIL.C and/or AGILSTUB.C */
+/* agilstub.c provides minimal versions of these routines for use by */
+/* programs other than the interpreter */
+/* ------------------------------------------------------------------- */
+
+void writeln(const char *s);
+void writestr(const char *s);
+void agil_option(int optnum, char *optstr[], rbool setflag, rbool lastpass);
+void close_interface(void);
+
+void print_tos(void); /* Called by the disassembler; in either TOKEN.C
+ or AGTOUT.C */
+
+
+/* ------------------------------------------------------------------- */
+/* In UTIL.C */
+/* Low-level utilites, including memory allocation, string manip., */
+/* and buffered file I/O. */
+/* ------------------------------------------------------------------- */
+
+global uchar trans_ascii[256]; /* Table to translate ascii values read
+ in from file */
+
+void build_trans_ascii(void); /* Set up the above table. */
+
+void rprintf(const char *fmt, ...); /* General output routine, mainly used
+ for diagnostics. There can be a newline
+ at the end, but there shouldn't be
+ one in the middle of the string */
+
+
+
+/* Memory management variables and routines */
+
+extern rbool rm_trap; /* Trap memory allocation failures? */
+global rbool rm_acct; /* Turn on rmem accounting, to locate memory leaks */
+global long rfree_cnt, ralloc_cnt; /* # of allocs/frees since acct turned on */
+global long rm_size, rm_freesize; /* These hold worst case values */
+
+long get_rm_size(void); /* These get the current values */
+long get_rm_freesize(void);
+void *rmalloc(long size);
+void r_free(void *p);
+#define rfree(p) (r_free(p),p=NULL) /* Traps errors & catch memory leaks */
+void *rrealloc(void *p, long size);
+char *rstrdup(const char *s);
+
+
+/* String utilities */
+
+char *concdup(const char *s1, const char *s2); /* Concacate and duplicate */
+char *rstrncpy(char *dest, const char *src, int max);
+/* Copy at most max-1 characters */
+rbool match_str(const char **pstr, const char *match);
+
+#ifdef NEED_STR_CMP
+#undef strcasecmp
+//define strcasecmp Common::scumm_strcasecmp
+extern int strcasecmp(const char *s1, const char *s2);
+#endif
+#ifdef NEED_STRN_CMP
+#undef strncasecmp
+//define strncasecmp Common::scumm_strnicmp
+extern int strncasecmp(const char *s1, const char *s2, size_t len);
+#endif
+
+#undef isspace
+#define isspace Common::isSpace
+#undef isprint
+#define isprint Common::isPrint
+#undef isalpha
+#define isalpha Common::isAlpha
+#undef isalnum
+#define isalnum Common::isAlnum
+#undef islower
+#define islower Common::isLower
+#undef isupper
+#define isupper Common::isUpper
+#undef ispunct
+#define ispunct Common::isPunct
+
+/* The fixsign.. routines put together unsigned bytes to form signed ints */
+
+#ifndef FAST_FIXSIGN
+short fixsign16(uchar n1, uchar n2);
+long fixsign32(uchar n1, uchar n2, uchar n3, uchar n4);
+#else
+#define fixsign16(u1,u2) ( (u1) | ((u2)<<8) )
+#define fixsign32(u1,u2,u3,u4) ( ((long)u1) | (((long)u2)<<8) | \
+ (((long)u3)<<16) | (((long)u4)<<24) )
+#endif
+
+
+/* Miscellaneous */
+long rangefix(long n);
+
+/* File routines */
+genfile openfile(fc_type fc, filetype ext, const char *err, rbool ferr);
+genfile openbin(fc_type fc, filetype ext, const char *err, rbool ferr);
+
+#undef fopen
+genfile fopen(const char *name, const char *how);
+#undef fseek
+int fseek(genfile stream, long int offset, int whence);
+#undef fread
+size_t fread(void *ptr, size_t size, size_t nmemb, genfile stream);
+#undef fwrite
+size_t fwrite(const void *ptr, size_t size, size_t nmemb, genfile stream);
+#undef fclose
+#define fclose(f) delete f
+#undef ftell
+size_t ftell(genfile f);
+
+char *readln(genfile f, char *buff, int n);
+/* Read a line from a 'text' file */
+
+/* (None of the following routines are at all reentrant) */
+long buffopen(fc_type fc, filetype ext,
+ long minbuffsize, const char *rectype, long recnum);
+/* Open file for our buffered routines and make it our current file;
+returns the record size. Prints out error message on failure
+rectype="noun","room",etc.; recnum=expected # of objects in file */
+uchar *buffread(long index);
+/* seek to index*recsize, read buff_rsize bytes, return pointer to a
+ buffer with them. */
+void buffclose(void); /* Close the current file */
+
+void bw_open(fc_type fc, filetype ext); /* Open buffered file for writing */
+void bw_close(void); /* Close buffered file */
+void bw_abort(void); /* Close and delete buffered file */
+
+/* "Universal" file routines */
+extern const size_t ft_leng[FT_COUNT]; /* File lengths of the data types */
+long compute_recsize(file_info *recinfo);
+void *read_recblock(void *base, int ftype, long numrec, long offset,
+ long blocksize);
+/* Only works for FT_BYTE, FT_SLIST, FT_WORD, FT_DICTTEXT, FT_INT16 */
+void *read_recarray(void *base, long eltsize, long numelts,
+ file_info *field_info, const char *rectype,
+ long file_offset, long file_blocksize);
+void read_globalrec(file_info *global_info, const char *rectype,
+ long file_offset, long file_blocksize);
+
+long write_recarray(void *base, long eltsize, long numelts,
+ file_info *field_info, long file_offset);
+long write_globalrec(file_info *global_info, long file_offset);
+long write_recblock(void *base, int ftype, long numrec, long offset);
+
+char *textgets(genfile f, char *buf, size_t n);
+char textgetc(genfile f);
+void textungetc(genfile f, char c);
+bool texteof(genfile f);
+void textputs(genfile f, const char *s);
+
+void set_internal_buffer(void *p);
+/* Causes all of the above routines to write to the block of memory pointed
+ at by p instead of to a file */
+
+#ifdef PROFILE_SUPPORT
+/* These are functions to do quick-and-dirty timing of routines;
+ I added them to check the performance of the AGX code.
+ They aren't likely to work on anything other than a *nix box */
+void resetwatch(void);
+void startwatch(void);
+char *stopwatch(void);
+char *timestring(void);
+#define runwatch(cmd) do{resetwatch();cmd;printf("%s\n",stopwatch());}while(0)
+#else
+#define runwatch(cmd) cmd
+#endif
+
+
+
+/* ------------------------------------------------------------------- */
+/* In FILENAME.C */
+/* The low-level file routines */
+/* ------------------------------------------------------------------- */
+
+fc_type init_file_context(const char *game_name, filetype ft);
+/* This sets up the filename system, based around a game with name
+ "gamename". Must be called before any of the following are called
+ with relative filetypes (all filetypes that depend on the game's
+ name and location-- everything but the global config file.) */
+/* ft indicates the file type class. At the moment, it can be
+ fAGX, fSAV, fSCR, fLOG
+ */
+void fix_file_context(fc_type fc, filetype ft);
+fc_type convert_file_context(fc_type fc, filetype ft, const char *name);
+void release_file_context(fc_type *pfc);
+
+
+char *formal_name(fc_type fc, filetype ft); /* Used for messages */
+genfile badfile(filetype ft); /* Bad file of type ft */
+rbool fileexist(fc_type fc, filetype ft);
+
+genfile readopen(fc_type fc, filetype ft, const char **errstr);
+genfile writeopen(fc_type fc, filetype ft,
+ file_id_type *pfileid, const char **errstr);
+rbool filevalid(genfile f, filetype ft);
+void readclose(genfile f);
+void writeclose(genfile f, file_id_type fileid);
+
+void binremove(genfile f, file_id_type fileid);
+void binseek(genfile f, long offset);
+rbool binread(genfile f, void *buff, long recsize, long recnum, const char **errstr);
+long varread(genfile f, void *buff, long recsize, long recnum, const char **errstr);
+rbool binwrite(genfile f, void *buff, long recsize, long recnum, rbool ferr);
+long binsize(genfile f); /* Size of an open binary file */
+
+rbool textrewind(genfile f);
+
+char *assemble_filename(const char *path, const char *root,
+ const char *ext);
+
+
+#ifdef global_defined_agtread
+#undef global
+#undef global_defined_agtread
+#endif
+
+} // End of namespace AGT
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/agt/agt.cpp b/engines/glk/agt/agt.cpp
index 29e7222a4e..ea28cf8f4f 100644
--- a/engines/glk/agt/agt.cpp
+++ b/engines/glk/agt/agt.cpp
@@ -28,11 +28,18 @@
namespace Glk {
namespace AGT {
+AGT *g_vm;
+
+extern void glk_main();
+extern int glk_startup_code(int argc, char *argv[]);
+
AGT::AGT(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc) {
+ g_vm = this;
}
void AGT::runGame() {
- // TODO
+ glk_startup_code(0, nullptr);
+ glk_main();
}
Common::Error AGT::readSaveData(Common::SeekableReadStream *rs) {
diff --git a/engines/glk/agt/agt.h b/engines/glk/agt/agt.h
index 70bb98ae87..1d4ed00154 100644
--- a/engines/glk/agt/agt.h
+++ b/engines/glk/agt/agt.h
@@ -45,7 +45,9 @@ public:
/**
* Returns the running interpreter type
*/
- virtual InterpreterType getInterpreterType() const override { return INTERPRETER_AGT; }
+ virtual InterpreterType getInterpreterType() const override {
+ return INTERPRETER_AGT;
+ }
/**
* Execute the game
@@ -64,6 +66,8 @@ public:
virtual Common::Error writeGameData(Common::WriteStream *ws) override;
};
+extern AGT *g_vm;
+
} // End of namespace AGT
} // End of namespace Glk
diff --git a/engines/glk/agt/agtread.cpp b/engines/glk/agt/agtread.cpp
new file mode 100644
index 0000000000..7fce3294d7
--- /dev/null
+++ b/engines/glk/agt/agtread.cpp
@@ -0,0 +1,1721 @@
+/* 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"
+
+namespace Glk {
+namespace AGT {
+
+static void read_da6(fc_type fc);
+static void check_cmd_version(void);
+static void build_cmd_table(void);
+static void fixcmd(integer *, int);
+
+/* This parses the block of integers of a command to standardize
+ the command numbers */
+static short *cmd_table; /* Holds the command translation table used
+ by fixcmd */
+int topcmd; /* The highest legal opcode in the current AGT version. */
+
+
+
+static genfile fd_desc; /* File pointer for description file. */
+static long desc_size; /* Size of description file. */
+
+static int top_quest; /* Highest question actually referenced */
+/* This is computed by fixcmd */
+
+static int SL_NAME, SL_TEXT, SL_ROOM, SL_WORD;
+static integer MAX_CMD_SIZE;
+
+static rbool encrypt_desc = 1; /* Are descriptions encrypted? */
+
+
+/* This translates v1.8 status mode codes into ME statue mode codes */
+const uchar agt18_statmode[] = {0, 4, 3, 1};
+
+
+/*-------------------------------------------------------------------------*/
+/* Utilities to convert strings, do ASCII translation, etc. and to add */
+/* words to the dictionary (the actual dictionary routines are in */
+/* agtdata.c; these routines here are just wrappers that prepare words */
+/* to be added). */
+/*-------------------------------------------------------------------------*/
+
+
+
+static void fatals(const char *msg, const char *fname) {
+ Common::String str = Common::String::format(msg, fname);
+ error("%s", str.c_str());
+}
+
+
+static word add_dic1(uchar *buff, int n) {
+ char nbuff[100];
+
+ int i;
+ if (n > 100) n = 100;
+ for (i = 0; i < buff[0] && i < n; i++)
+ nbuff[i] = buff[i + 1];
+ nbuff[i] = 0;
+ return add_dict(nbuff);
+}
+
+
+static slist add_slist(uchar *buff) {
+ int j, k;
+ slist start_ptr;
+ char nbuff[100];
+
+ k = 0;
+ start_ptr = synptr;
+ if (buff[0] > 80) fatal("Invalid game file format");
+ for (j = 1; j <= buff[0]; j++)
+ if (rspace(buff[j]) && k > 0) {
+ nbuff[k] = 0;
+ addsyn(add_dict(nbuff));
+ k = 0;
+ } else nbuff[k++] = buff[j];
+ if (k > 0) {
+ nbuff[k] = 0;
+ addsyn(add_dict(nbuff));
+ }
+ addsyn(-1); /* End of list marker */
+ return start_ptr;
+}
+
+
+
+
+/*-------------------------------------------------------------------------*/
+/* Description file manipulation routines: routines to open and close the */
+/* description file and read in individual descriptions. */
+/*-------------------------------------------------------------------------*/
+
+/* The memory-based stuff is not done yet */
+
+
+void convert_agt_descr(uchar *s)
+/* Convert encrypted pascal string into plaintext C string */
+{
+ int j, n;
+
+ n = s[0];
+ if (n > 80) {
+ s[0] = 0;
+ return;
+ }
+
+ if (encrypt_desc)
+ for (j = 0; j < n; j++)
+ if (s[j + 1] != ' ')
+ s[j] = fixchar[(s[j + 1] - (j + 1) + 0x100) & 0xFF];
+ else s[j] = ' ';
+ else
+ for (j = 0; j < n; j++)
+ s[j] = fixchar[s[j + 1]];
+
+ s[n] = 0;
+}
+
+
+void open_descr(fc_type fc) {
+ const char *errstr;
+ long i;
+ int alpha, cnt;
+ tline buff;
+
+ fd_desc = readopen(fc, fDSS, &errstr);
+ if (errstr != NULL) fatal(errstr);
+ desc_size = binsize(fd_desc);
+ if (DIAG) {
+ char *s;
+ s = formal_name(fc, fDSS);
+ rprintf("Opened file %s (size:%ld)\n", s, desc_size);
+ rfree(s);
+ }
+
+ /* <Sigh> Now need to figure out if the input is encoded. Do this by
+ reading a few random lines and seeing if they "look" encoded */
+ alpha = cnt = 0;
+ if (aver > AGT135 || aver == AGTCOS)
+ encrypt_desc = 1;
+ else {
+ binread(fd_desc, buff, 81, 1, &errstr); /* Throw away first line */
+ while (cnt < 300) {
+ if (!binread(fd_desc, buff, 81, 1, &errstr)) { /* EOF */
+ writeln("");
+ agtwarn("EOF while analyzing descriptions", 0);
+ rprintf("......assuming type ");
+ break;
+ }
+ if (buff[0] > 0 && buff[1] != ' ' && buff[1] > 0) /* To avoid "banner"
+ lines */
+ {
+ for (i = 1; i <= buff[0]; i++) {
+ if (buff[i] >= 'A' && buff[i] <= 'z') alpha++;
+ if (buff[i] != ' ') cnt++;
+ }
+ }
+ }
+ if (3 * cnt < 4 * alpha) {
+ encrypt_desc = 0;
+ if (aver == AGT135) aver = AGT12;
+ } else encrypt_desc = 1;
+ }
+ if (DIAG) {
+ if (encrypt_desc) rprintf(" [encrypted]\n");
+ else rprintf(" [plaintext: %d/%d]\n", alpha, cnt);
+ }
+
+ mem_descr = NULL;
+ if (desc_size <= descr_maxmem) {
+ /* This is where we need to read the data in and convert it:
+ encrypted Pascal strings --> plaintext C strings */
+ binseek(fd_desc, 0);
+ mem_descr = (char *)rmalloc(desc_size);
+ /* Read in the whole file */
+ binread(fd_desc, mem_descr, desc_size, 1, &errstr);
+ if (errstr != NULL) fatal(errstr);
+ for (i = 0; i < desc_size; i += sizeof(tline))
+ convert_agt_descr((uchar *)(mem_descr + i));
+ /* Decode and convert to C string */
+ }
+}
+
+
+void close_descr(void) {
+ if (mem_descr != NULL)
+ rfree(mem_descr);
+ else {
+ readclose(fd_desc);
+ fd_desc = NULL;
+ }
+}
+
+
+descr_line *agt_read_descr(long start, long len) {
+ tline *d;
+ descr_line *lines;
+ long i;
+ const char *errstr;
+
+ if (len == -1 || start == -1) return NULL;
+ lines = (descr_line *)rmalloc(sizeof(descr_line) * (len + 1));
+
+ if (mem_descr != NULL) {
+ d = ((tline *)mem_descr) + start;
+ for (i = 0; i < len; i++)
+ lines[i] = (char *)(d + i);
+ } else {
+ d = (tline *)rmalloc(sizeof(tline) * len);
+ binseek(fd_desc, start * sizeof(tline));
+ binread(fd_desc, d, sizeof(tline), len, &errstr);
+ if (errstr != NULL) fatal(errstr);
+ for (i = 0; i < len; i++) {
+ lines[i] = (char *)(d + i);
+ convert_agt_descr((uchar *)(d + i));
+ }
+ }
+ lines[len] = NULL; /* Mark end of array */
+ return lines;
+}
+
+
+
+/*-------------------------------------------------------------------------*/
+/* Read DA2: The Room File. */
+/*-------------------------------------------------------------------------*/
+
+
+#define seti(a) (room[i].a=buff[bp] | (buff[bp+1]<<8),bp+=2)
+#define set32(a) (room[i].a=buff[bp] | (buff[bp+1]<<8) | (buff[bp+2]<<16)|\
+ (buff[bp+3]<<24), bp+=4)
+#define setb(a) (room[i].a=buff[bp],bp++)
+
+#define setstr(leng) (bp+=(leng),new_str((char*)buff+bp-(leng),(leng),1))
+#define setd(leng) (bp+=(leng),add_dic1(buff+bp-(leng),(leng)))
+#define setsl() (bp+=sizeof(tline),add_slist(buff+bp-sizeof(tline)))
+#define nonecheck(leng) (memcmp(buff+bp,nonestr,5)==0)
+
+
+static void read_da2(fc_type fc) {
+ int i, j, numroom;
+ uchar *buff; /* [FRS_ROOM];*/
+ long bp;
+
+ numroom = maxroom - first_room + 1;
+ if (numroom < 0) return;
+ room_name = (long *)rmalloc(numroom * sizeof(long));
+
+ buffopen(fc, fDA2, FRS_ROOM, "room", numroom);
+
+ bp = 0;
+ for (i = 0; i < numroom; i++) {
+ buff = buffread(i);
+ bp = 0;
+ if (nonecheck(SL_ROOM))
+ room[i].unused = 1;
+ else room[i].unused = 0;
+ room_name[i] = setstr(SL_ROOM);
+ room[i].replace_word = setd(SL_WORD);
+ room[i].replacing_word = setsl();
+ for (j = 0; j < 12; j++) seti(path[j]);
+
+ if (aver >= AGT15) set32(flag_noun_bits); /* Menu flags */
+ else room[i].flag_noun_bits = 0;
+
+ if (aver >= AGTME10) set32(PIX_bits); /* PIX bits */
+ else room[i].PIX_bits = 0;
+
+ seti(path[12]); /* Special */
+
+ /* There's a small possibility that the 1.5/Hotel flag_noun_bits
+ goes here, rather than above; 1.5/F is known to go above */
+
+ setb(seen);
+ seti(key);
+ setb(locked_door);
+ if (room_inside != NULL)
+ room_inside[i] = fixsign16(buff[bp], buff[bp + 1]);
+ bp += 2; /* Skip # of nouns in this room */
+
+ seti(points);
+ seti(light);
+ setb(end);
+ setb(win);
+ if (aver != AGT10) setb(killplayer); /* I'm guessing here */
+ else room[i].killplayer = room[i].end;
+
+ if (aver >= AGTME10) {
+ seti(initdesc);
+ seti(pict);
+ } else {
+ room[i].initdesc = 0;
+ room[i].pict = 0;
+ }
+ if (aver >= AGTME15) room[i].autoverb = setd(SL_WORD);
+ else room[i].autoverb = 0;
+ room[i].oclass = 0;
+ room[i].seen = 0;
+ }
+ if (DIAG)
+ rprintf(" Internal:%ld\n", bp);
+ buffclose();
+}
+
+
+
+/*-------------------------------------------------------------------------*/
+/* Read DA3: The Noun File. */
+/*-------------------------------------------------------------------------*/
+
+#undef seti
+#undef setb
+#define seti(a) (noun[i].a=buff[bp] | (buff[bp+1]<<8),bp+=2)
+#define setb(a) (noun[i].a=buff[bp],bp++)
+
+static void read_da3(fc_type fc) {
+ int i, numnoun;
+ long recsize;
+ uchar *buff; /* [FRS_NOUN];*/
+ long bp;
+
+ numnoun = maxnoun - first_noun + 1;
+ if (numnoun < 0) return;
+ noun_sdesc = (long *)rmalloc(numnoun * sizeof(long));
+ noun_pos = (long *)rmalloc(numnoun * sizeof(long));
+
+ recsize = buffopen(fc, fDA3, FRS_NOUN, "noun", numnoun);
+ if (aver == AGT15 && recsize > 263) aver = AGT15F;
+
+ bp = 0;
+ for (i = 0; i < numnoun; i++) {
+ buff = buffread(i);
+ bp = 0;
+ if (nonecheck(SL_NAME)) {
+ bp += SL_NAME;
+ noun[i].name = 0;
+ noun[i].unused = 1;
+ } else {
+ noun[i].name = setd(SL_NAME);
+ noun[i].unused = 0;
+ }
+ noun_sdesc[i] = setstr(SL_TEXT);
+ noun[i].adj = setd(SL_NAME);
+
+ if (aver >= AGT15F) seti(initdesc);
+ else noun[i].initdesc = 0;
+
+ setb(plural);
+ /* The following is a guess for ME games */
+ noun_pos[i] = setstr((ver == 3) ? SL_ROOM : SL_NAME);
+ setb(something_pos_near_noun); /* These may not be valid */
+ seti(nearby_noun); /* in masters ed. */
+
+ setb(has_syns);
+ noun[i].syns = setsl(); /*,SL_TEXT);*/
+ if (aver >= AGT15)
+ noun[i].related_name = setd(SL_NAME);
+ else
+ noun[i].related_name = 0;
+ seti(location);
+ seti(weight);
+ seti(size);
+ seti(key);
+ /* All of following flags known to be valid except
+ pullable, on, and win */
+ setb(pushable);
+ setb(pullable);
+ setb(turnable);
+ setb(playable);
+ setb(readable);
+ setb(on);
+ setb(closable);
+ setb(open);
+ setb(lockable);
+ setb(locked);
+ setb(edible);
+ setb(wearable);
+ setb(drinkable);
+ setb(poisonous);
+ setb(movable);
+ setb(light);
+ setb(shootable);
+ seti(num_shots);
+ seti(points);
+ if (noun_inside != NULL)
+ noun_inside[i] = fixsign16(buff[bp], buff[bp + 1]);
+ bp += 2; /* Skip # of nouns contained in this one */
+ setb(win);
+ if (ver == 3) seti(pict);
+ else noun[i].pict = 0;
+ noun[i].oclass = 0; /* AGT games don't support classes */
+ noun[i].isglobal = 0;
+ noun[i].flagnum = 0;
+ noun[i].seen = 0;
+ noun[i].proper = 0;
+ }
+ if (DIAG)
+ rprintf(" Internal:%ld\n", bp);
+ buffclose();
+}
+
+
+#undef seti
+#undef setb
+#define seti(a) (creature[i].a=buff[bp] | (buff[bp+1]<<8),bp+=2)
+#define setb(a) (creature[i].a=buff[bp],bp++)
+
+
+
+/*-------------------------------------------------------------------------*/
+/* Read DA4: The Creature File. */
+/*-------------------------------------------------------------------------*/
+
+static void read_da4(fc_type fc) {
+ int i, numcreat;
+ uchar *buff; /* [FRS_CREAT];*/
+ long bp;
+
+ numcreat = maxcreat - first_creat + 1;
+ if (numcreat <= 0) return;
+ creat_sdesc = (long *)rmalloc(numcreat * sizeof(long));
+
+ buffopen(fc, fDA4, FRS_CREAT, "creature", numcreat);
+
+ bp = 0;
+ for (i = 0; i < numcreat; i++) {
+ buff = buffread(i);
+ bp = 0;
+ if (nonecheck(SL_NAME)) {
+ bp += SL_NAME;
+ creature[i].name = 0;
+ creature[i].unused = 1;
+ } else {
+ creature[i].name = setd(SL_NAME);
+ creature[i].unused = 0;
+ }
+ creat_sdesc[i] = setstr(SL_TEXT);
+ creature[i].adj = setd(SL_NAME);
+ if (ver == 3) seti(initdesc);
+ else creature[i].initdesc = 0;
+ setb(has_syns);
+ creature[i].syns = setsl();
+ setb(groupmemb);
+ seti(location);
+ seti(weapon);
+ setb(hostile);
+ seti(points);
+ if (creat_inside != NULL)
+ creat_inside[i] = fixsign16(buff[bp], buff[bp + 1]);
+ bp += 2; /* Skip # of nouns the creature is carrying */
+ seti(counter);
+ seti(threshold);
+ seti(timethresh);
+ seti(timecounter);
+ setb(gender);
+ if (ver == 3) seti(pict);
+ else creature[i].pict = 0;
+ creature[i].oclass = 0; /* AGT games don't support classes */
+ creature[i].isglobal = 0;
+ creature[i].flagnum = 0;
+ creature[i].seen = 0;
+ creature[i].proper = 0;
+ }
+ if (DIAG)
+ rprintf(" Internal:%ld\n", bp);
+ buffclose();
+}
+
+#undef seti
+#undef setb
+
+
+
+
+/*-------------------------------------------------------------------------*/
+/* Read Commands (DA5 and DA6) and convert them to a uniform internal */
+/* format. */
+/*-------------------------------------------------------------------------*/
+
+
+static int translate_vnum(int vnum)
+/* actor is a numerical index occuring at the beginning of each command.
+ In general, it contains the verb number of the verb associated with
+ this command; because of AGiliTy's dictionary organization, we don't
+ really need this (the verb itself will be converted to a number anyhow),
+ but the field contains other useful information as well:
+ i)If this command header is really the object of a redirection command,
+ then the actor will have 1000 or 2000 added to it, depending on
+ AGT version.
+ ii)If this command is directed at an actor, then the creature number
+ will be in this field instead of the verb number.
+ Commands directed to ANYBODY have one plus the maximum verb number
+ in this field (a hassle since the maximum verb number depends on
+ AGT version: Classic:106, Master's:123); EVERYONE is the next
+ code after ANYBODY.
+ What this routine does is rationalize the differences between AGT versions.
+ --Verb values (ie. not referring to creatures) are converted to 1.
+ --Redirections are marked by multiplying by negative one and setting
+ cmdsize to 0.
+ --ANYBODY is set to 2
+ --EVERYBODY is set to 3
+*/
+{
+ rbool redir; /* Is this command redirected? */
+ integer anycode;
+ int redir_val;
+
+ /* Earlier games use 1000 as redirect value, later games use 2000: */
+ /* We strip it off, but remember whether it was there or not so we
+ can restore this information later. */
+ redir_val = (aver <= AGT18MAX ? 1000 : 2000);
+ if (vnum >= redir_val) {
+ vnum = vnum % redir_val;
+ redir = 1;
+ } else redir = 0;
+
+ anycode = (aver <= AGT18MAX) ? 106 : 123;
+
+ /* Now to correct ANYBODY to something consistent and set verb
+ references to 1 since we don't need them and they just confuse things */
+ if (vnum < anycode) vnum = 1; /* "player" */
+ else if (vnum == anycode) vnum = 2; /* ANYBODY */
+ else if (vnum == anycode + 1) vnum = 3; /* EVERYBODY */
+
+ /* Finally restore redirection info. We now use the sign of vnum
+ to indicate this.*/
+ if (redir) vnum = -vnum;
+
+ return vnum;
+}
+
+
+#define CREC_SIZE (FRS_CMD)
+
+static long badtokcnt;
+
+static void read_da5(fc_type fc) {
+ long i, j;
+ uchar *buff; /* [CREC_SIZE];*/
+ long bp;
+
+ if (!have_meta) return;
+ if (last_cmd <= 0)
+ fatal("Bogus last_cmd");
+
+ buffopen(fc, fDA5, CREC_SIZE, "command", last_cmd);
+
+ if (aver >= AGT15F) cmd_ptr = (long *)rmalloc(sizeof(long) * last_cmd);
+ else cmd_ptr = NULL;
+
+ bp = 0;
+ for (i = 0; i < last_cmd; i++) {
+ buff = buffread(i);
+ command[i].actor = translate_vnum(buff[0] + (buff[1] << 8));
+ bp = 2;
+ command[i].verbcmd = setd(SL_WORD);
+ command[i].nouncmd = setd(SL_WORD);
+ if (aver >= AGTME10)
+ command[i].prep = setd(SL_WORD);
+ else command[i].prep = 0;
+ command[i].objcmd = setd(SL_WORD);
+ command[i].noun_adj = command[i].obj_adj = 0;
+ command[i].noun_obj = command[i].obj_obj = 0;
+ if (aver < AGT15F) {
+ command[i].data = (integer *)rmalloc(MAX_CMD_SIZE * sizeof(integer));
+ for (j = 0; j < MAX_CMD_SIZE; j++)
+ command[i].data[j] = fixsign16(buff[bp + 2 * j], buff[bp + 2 * j + 1]);
+ bp += 2 * MAX_CMD_SIZE;
+ command[i].cmdsize = MAX_CMD_SIZE;
+ } else {
+ cmd_ptr[i] = (long)buff[bp] + (((long)buff[bp + 1]) << 8);
+ bp += 2;
+ }
+ }
+ if (DIAG)
+ rprintf(" Internal:%ld\n", bp);
+ buffclose();
+
+ /* Now to read in DA6 for versions that have it */
+ if (aver >= AGT15F) read_da6(fc);
+ check_cmd_version(); /* This uses the opcodes to check gamefile
+ version information and change it if neccesary. */
+ build_cmd_table(); /* Create the command translation table for
+ this version of AGT. */
+
+ badtokcnt = 0;
+ if (!RAW_CMD_OUT)
+ for (i = 0; i < last_cmd; i++)
+ fixcmd(command[i].data, command[i].cmdsize);
+ rfree(cmd_table);
+ if (badtokcnt > MAX_BADTOK)
+ agtnwarn("Total number of bad opcodes:", badtokcnt, 1);
+}
+
+
+
+static void read_da6(fc_type fc)
+/* This will only be called for versions with a DA6 file--
+ i.e. Master's Edition and proto-ME games. */
+{
+ genfile fda6;
+ char *fullname;
+ const char *errstr;
+ long fsize; /* Size of the file */
+ long frame; /* The first element of the file that is in the buffer. */
+ uchar *cbuf; /* Buffer */
+ long cfile_size, cbuf_size; /* Number of tokens in file and in buffer */
+ long i, j;
+ long cmdstart, cmdend; /* Marks the start and end of the current command */
+ long ip; /* Points to instruction in cmd.data[] that we are writing to */
+ long bp; /* Pointer into buffer */
+ long endp; /* Used to indicate end of current read loop
+ (with an infinite buffer, this would always be an adjusted
+ cmdend) */
+ long adj_cbuf_size; /* Stores number of bytes actually read in to cbuf */
+
+ fda6 = openbin(fc, fDA6, "Could not open code file '%s'.", 1);
+ fsize = binsize(fda6);
+ fullname = formal_name(fc, fDA6);
+ if (DIAG) rprintf("Reading code file %s (size:%ld)\n", fullname, fsize);
+
+ if (aver == AGT15F && fsize == 20000) aver = AGT16;
+ if (aver >= AGTME10) cfile_size = 20000;
+ else if (aver == AGT16) cfile_size = 10000;
+ else cfile_size = 5000;
+
+ if (fsize != 2 * cfile_size)
+ fatals("Code file %s is the wrong size.", fullname);
+
+ cbuf_size = (cfile_size < CBUF_SIZE) ? cfile_size : CBUF_SIZE;
+ cbuf = (uchar *)rmalloc(2 * cbuf_size);
+ frame = cfile_size + 1; /* Guarentee frame will be wrong */
+
+ for (i = 0; i < last_cmd; i++)
+ if (cmd_ptr[i] >= 2) {
+ for (j = i + 1; j < last_cmd && cmd_ptr[j] <= cmd_ptr[i]; j++);
+ if (j < last_cmd) cmdend = cmd_ptr[j];
+ else cmdend = cfile_size;
+ if (cmdend > cfile_size) fatals("Code file overrun(%s)", fullname);
+ --cmdend;
+ cmdstart = cmd_ptr[i] - 1;
+ command[i].cmdsize = cmdend - cmdstart;
+ command[i].data = (integer *)rmalloc(command[i].cmdsize * sizeof(integer));
+
+ ip = 0;
+ bp = cmdstart - frame;
+ adj_cbuf_size = cbuf_size;
+
+ while (ip < command[i].cmdsize) {
+ if (bp < 0 || bp >= adj_cbuf_size) { /* Read in new block */
+ frame = frame + bp;
+ binseek(fda6, frame * 2);
+ if (frame + cbuf_size <= cfile_size)
+ adj_cbuf_size = cbuf_size;
+ else
+ adj_cbuf_size = cfile_size - frame;
+ if (adj_cbuf_size <= 0) fatal("Unexpected end of file.");
+ if (!binread(fda6, cbuf, 2, adj_cbuf_size, &errstr))
+ fatal(errstr);
+ bp = 0;
+ }
+ endp = cmdend - frame;
+ if (endp > cbuf_size) endp = cbuf_size;
+ for (; bp < endp; ip++, bp++)
+ command[i].data[ip] = fixsign16(cbuf[bp * 2L], cbuf[bp * 2L + 1]);
+ }
+ } else {
+ command[i].data = NULL;
+ command[i].cmdsize = 0;
+ }
+ rfree(cbuf);
+ readclose(fda6);
+ rfree(fullname);
+}
+
+
+
+
+
+static int check_endcmd(void)
+/* What is the most common last byte for metacommands? Except
+ under very abnormal situations, this should be the EndCmd opcode */
+{
+ int count[MAX_TOKEN_ID + 1];
+ int i, tok, maxcnt, maxtok;
+ /* int nextcnt; */
+
+ for (i = 0; i <= MAX_TOKEN_ID; i++) count[i] = 0;
+ for (i = 0; i < last_cmd; i++)
+ if (command[i].cmdsize > 0) {
+ tok = command[i].data[command[i].cmdsize - 1];
+ if (tok >= 0 && tok <= MAX_TOKEN_ID) count[tok]++;
+ }
+ maxcnt = maxtok = 0; /* nextcnt=0;*/
+ for (i = 0; i <= MAX_TOKEN_ID; i++)
+ if (count[i] >= maxcnt) {
+ /* nextcnt=maxcnt; */
+ maxcnt = count[i];
+ maxtok = i;
+ }
+ return maxtok;
+}
+
+static int compute_endcode(int ver_)
+/* Computes the correct endcode for a given gamefile version */
+{
+ int i;
+
+ for (i = 0; FIX_LIST[ver_][i].tnew != -1; i++);
+ return (FIX_LIST[ver_][i].told - 3); /* -3 to get to EndCmd */
+}
+
+
+static void check_cmd_version(void)
+/* Run through the commands looking at the last legal byte. This is
+ normally the EndCmd token code, which can give us info on which
+ token encoding scheme is being used. */
+{
+ int endcode;
+
+ endcode = check_endcmd();
+ if (DIAG) rprintf(" (EndCmd=%d)\n", endcode);
+ if (endcode < 150) return; /* No metacommands, or something else is wrong. */
+ if (endcode == compute_endcode(aver)) return; /* We're okay */
+
+ /* Check for the special cases we know about */
+ if (aver == AGT135) {
+ if (endcode == compute_endcode(AGT182)) {
+ aver = AGT182;
+ return;
+ } else if (endcode == compute_endcode(AGT118)) {
+ aver = AGT118;
+ return;
+ }
+ }
+ if (aver == AGTME10)
+ if (endcode == compute_endcode(AGTME10A)) {
+ aver = AGTME10A;
+ return;
+ }
+ if (aver == AGTMAST)
+ if (endcode == compute_endcode(AGTME155)) {
+ aver = AGTME155;
+ return;
+ }
+
+ /* If we still haven't fixed the problem, print out a warning and
+ pray. */
+ agtnwarn("Game has invalid EndCmd: ", endcode, 1);
+}
+
+
+
+static void build_cmd_table(void) {
+ int told, tnew, fp;
+ const cmd_fix_rec *fixtbl;
+
+ topcmd = compute_endcode(aver) + 3;
+ cmd_table = (short *)rmalloc(sizeof(short) * topcmd);
+
+ fixtbl = FIX_LIST[aver];
+ fp = 0; /* Pointer into fix table */
+ tnew = 0; /* This shouldn't be neccessary */
+ for (told = 0; told < topcmd;) {
+ if (told == fixtbl[fp].told) tnew = fixtbl[fp++].tnew;
+ cmd_table[told++] = tnew++;
+ }
+}
+
+
+
+static void badtokerr(const char *s, int tok) {
+ if (++badtokcnt <= MAX_BADTOK) agtnwarn(s, tok, 1);
+}
+
+static void fixcmd(integer *clist, int cnt)
+/* Okay, we need to go through the elements of clist (which is an array,
+ actually), figure out which ones are commands (as opposed to arguments)
+ and tweak them to hide version differences. */
+{
+ long ip;
+
+ /* Now need to go through and adjust opcodes. */
+ for (ip = 0; ip < cnt; ip++)
+ if (clist[ip] >= topcmd || clist[ip] < 0)
+ badtokerr("Invalid token found: ", clist[ip]);
+ else {
+
+ clist[ip] = cmd_table[clist[ip]]; /* Translate */
+
+ /* Now increment ip by the length of the instruction */
+ /* Remember that we are already incrementing by one automatically */
+
+ if (clist[ip] >= END_ACT) break; /* CMD end marker */
+ if (clist[ip] <= MAX_COND)
+ ip += cond_def[clist[ip]].argnum;
+ else if (clist[ip] < WIN_ACT) {
+ if (clist[ip] == 1087 && ip + 1 < cnt) /* AskQuestion: Adjust top_quest */
+ if (top_quest < clist[ip + 1]) top_quest = clist[ip + 1];
+ ip += act_def[clist[ip] - START_ACT].argnum;
+ }
+ /* else do nothing */
+ }
+}
+
+
+
+
+
+/*-------------------------------------------------------------------------*/
+/* DA1 Reading Utilites: routines to read the various lines of the DA1 file */
+/*-------------------------------------------------------------------------*/
+
+static void chop_newline(char *s)
+/* Remove trailing \r,\n, etc. characters */
+{
+ char *t;
+
+ for (t = s; *t != 0; t++); /* Find the end of the string */
+ for (; t >= s && (*t == 0 || *t == '\r' || *t == '\n'); t--);
+ *(t + 1) = 0;
+}
+
+static void fix_answer(char *s)
+/* Put answer s into standard format: all lower case and with trailing/
+ following whitespace removed */
+{
+ char *t, *p;
+
+ for (t = s; *t != 0; t++)
+ *t = tolower(*t);
+ /* Eliminate trailing space and newlines */
+ for (; t >= s && (*t == 0 || rspace(*t)); t--);
+ *(t + 1) = 0;
+ /* Eliminate leading space and newlines */
+ for (t = s; rspace(*t); t++);
+ if (t != s) {
+ for (p = s; *t != 0;)
+ *(p++) = *(t++);
+ *p = 0;
+ }
+}
+
+
+static char linebuffer[81];
+static int bhold;
+static int linenum;
+static rbool unexpected_eof;
+
+static void read_line(genfile fd, const char *typestr)
+/* Read a line into buffer, unless bhold=1 in which case we want
+ to use the last line read */
+{
+ if (bhold == 0) {
+ readln(fd, linebuffer, 80);
+ if (linebuffer[0] == 0 && texteof(fd)) {
+ unexpected_eof = 1;
+ strcpy(linebuffer, ">End Of File<");
+ } else chop_newline(linebuffer);
+ linenum++;
+ }
+ if (debug_da1 && typestr != NULL) {
+ rprintf("%s %4d:%s", typestr, linenum, linebuffer);
+ if (bhold) rprintf(" *");
+ writeln("");
+ }
+ bhold = 0;
+}
+
+
+static void report(const char *s, genfile fd) {
+ if (DIAG) rprintf("REPORT:%s at %d\n", s, linenum);
+}
+
+static int isbool(genfile fd) {
+ read_line(fd, NULL);
+ bhold = 1;
+ return (strncasecmp(linebuffer, "TRUE", 4) == 0 ||
+ strncasecmp(linebuffer, "FALSE", 5) == 0);
+}
+
+static int isnum(genfile fd) {
+ char *errstr;
+
+ read_line(fd, NULL);
+ bhold = 1;
+ (void)strtol(linebuffer, &errstr, 10);
+ while (*errstr == '\n' || *errstr == '\r') errstr++;
+ if (debug_da1)
+ rprintf("NUMCHK: %s==>%c\n", linebuffer, *errstr);
+ return (*errstr == '\0');
+}
+
+static rbool readrbool(genfile fd) {
+ read_line(fd, "BOOL");
+ return (strncasecmp(linebuffer, "TRUE", 4) == 0);
+}
+
+
+static long readnum(genfile fd) {
+ read_line(fd, "NUM ");
+ return strtol(linebuffer, NULL, 10);
+}
+
+
+static void readptr(genfile fd, descr_ptr *desc) {
+ read_line(fd, "PTR ");
+ desc->start = strtol(linebuffer, NULL, 10);
+ read_line(fd, "LEN");
+ desc->size = strtol(linebuffer, NULL, 10);
+}
+
+
+static void readjunk(genfile fd) {
+ read_line(fd, "JUNK");
+}
+
+static void readtext(genfile fd, tline s) {
+ read_line(fd, "TEXT");
+ strncpy((char *)s, linebuffer, 80);
+ s[80] = 0;
+}
+
+static long readfname(genfile fd) {
+ read_line(fd, "FILE");
+ return new_str(linebuffer, 0, 0);
+ /* Copy filename string to static string space and return index */
+}
+
+static word readdict(genfile fd) {
+ read_line(fd, "DICT");
+ return add_dict(linebuffer);
+}
+
+
+static slist readslist(genfile fd) { /* Read in synonym list line */
+ slist start_ptr;
+ char nbuff[50];
+ int j, k;
+
+ start_ptr = synptr;
+ read_line(fd, "SYN ");
+ /* Need to see if it is none or * terminated. */
+ for (j = 0; linebuffer[j] != 0 && linebuffer[j] != '*'; j++);
+ linebuffer[j] = 0;
+ k = 0;
+ for (j = 0; linebuffer[j] != 0; j++)
+ if (rspace(linebuffer[j]) && k > 0) {
+ nbuff[k] = 0;
+ addsyn(add_dict(nbuff));
+ k = 0;
+ } else if (!rspace(linebuffer[j]))
+ nbuff[k++] = linebuffer[j];
+ if (k > 0) {
+ nbuff[k] = 0;
+ addsyn(add_dict(nbuff));
+ }
+ addsyn(-1);
+ return start_ptr;
+}
+
+
+
+/*-------------------------------------------------------------------------*/
+/* Version analysis: Utilities to analyse the file format version and */
+/* deduce sizes. */
+/*-------------------------------------------------------------------------*/
+
+static int soggy_test(fc_type fc) {
+ genfile fda3;
+ long fsize;
+
+ if (DIAG) {
+ char *s;
+ s = formal_name(fc, fDA3);
+ rprintf("Testing %s for abnormal noun organization....", s);
+ rfree(s);
+ }
+ fda3 = openbin(fc, fDA3, "Could not find room file '%s'.", 1);
+ fsize = binsize(fda3);
+ readclose(fda3);
+
+ if (fsize % (maxnoun - 300 + 1) != 0) {
+ if (DIAG) rprintf("FOUND!\n");
+ return 1;
+ }
+ if (fsize / (maxnoun - 300 + 1) > 300) {
+ if (DIAG) rprintf("FOUND!\n");
+ return 1;
+ }
+ if (DIAG) rprintf("nope.\n");
+ return 0;
+}
+
+
+static void deduce_sizes(fc_type fc, rbool diag)
+/* If diag is true, we will also allocate space for
+noun inside information; this is used by agtout */
+{
+ if (ver == 0) {
+ ver = 1;
+ if (maxroom >= 200) ver = 2;
+ else if (maxnoun != 0)
+ if (maxnoun < 300)
+ if (maxcreat != 0)
+ if (maxcreat >= 500) ver = 4; /* SOGGY */
+ else ver = 1; /* Small */
+ else if (aver == AGTCOS) ver = 4; /* SOGGY */
+ else ver = 1; /* Small */
+ else if (aver != AGTCOS) ver = 2; /* Large */
+ else if (soggy_test(fc)) ver = 4;
+ else ver = 2;
+ else if (maxcreat != 0)
+ if (maxcreat >= 500)
+ if (aver != AGTCOS) ver = 2; /* Large */
+ else if (soggy_test(fc)) ver = 4; /* Either large or SOGGY */
+ else ver = 2;
+ else ver = 1; /* Small */
+ else
+ agtwarn("No nouns or creatures: unable to determine version."
+ "\nAssuming AGT Small", 0);
+ }
+
+ if (aver < AGTME15)
+ MaxQuestion = 25;
+ else
+ MaxQuestion = 100; /* This is a guess. */
+ if (aver == AGTCOS)
+ MaxQuestion = 10;
+ if (aver == AGT15 || aver == AGT15F)
+ MaxQuestion = 57;
+ first_room = 2;
+ if (ver == 1) {
+ first_noun = 200;
+ first_creat = 300;
+ last_obj = 399;
+ last_message = 250;
+ } else { /* ver 2 or 3 or 4 */
+ if (ver != 4)
+ first_noun = 300;
+ else first_noun = 200;
+ first_creat = 500;
+ last_obj = 699;
+ if (aver <= AGT12) last_message = 500;
+ else if (aver < AGTME155) last_message = 600;
+ else last_message = 800;
+ }
+ if (aver == AGTCOS) {
+ if (ver == 4) last_obj = 610;
+ else last_obj = 599;
+ if (ver == 4) last_message = 810; /* Soggy case */
+ else last_message = 700;
+ }
+
+ if (aver >= AGT18 && aver <= AGT18MAX) {
+ bold_mode = 1;
+ build_fixchar();
+ fixchar['\\'] = FORMAT_CODE;
+ }
+
+ if (aver < AGTME10) {
+ SL_TEXT = 81;
+ SL_NAME = SL_WORD = 23;
+ SL_ROOM = 31;
+ } else {
+ SL_TEXT = 81;
+ SL_NAME = SL_WORD = 16;
+ SL_ROOM = 31;
+ }
+ if (aver == AGT15 || aver == AGT15F) SL_NAME = SL_WORD = 16;
+
+ if (aver >= AGTME10) {
+ MAX_USTR = 25;
+ MAX_SUB = 15;
+ } else MAX_SUB = MAX_USTR = 0;
+
+ if (aver >= AGT15)
+ NUM_ERR = 185; /* Number of standard error messages */
+ else
+ NUM_ERR = 0;
+
+ DVERB = 50;
+ FLAG_NUM = 255;
+ CNT_NUM = VAR_NUM = 50;
+ exitmsg_base = 1000;
+
+ num_rflags = num_nflags = num_cflags = 0;
+ num_rprops = num_nprops = num_cprops = 0;
+ objflag = NULL;
+ objprop = NULL;
+ attrtable = NULL;
+ proptable = NULL;
+ oflag_cnt = 0;
+ oprop_cnt = 0;
+ propstr = NULL;
+ propstr_size = 0;
+ vartable = NULL;
+ flagtable = NULL;
+
+
+
+ /* Now to allocate space for all of the 'immortal' data structures */
+ /* We do this all at once to avoid fragmentation; all of the following
+ will be around for the life of the program (unless we restart) and so
+ should be allocated first */
+
+ synlist = (slist *)rmalloc(sizeof(slist) * TOTAL_VERB);
+ comblist = NULL; /* The original AGT didn't support multi-word verbs */
+ num_comb = 0;
+ userprep = NULL; /* ... nor did it allow user-defined prepostions */
+ num_prep = 0;
+
+ if (numglobal > 0)
+ globalnoun = (word *)rmalloc(numglobal * sizeof(word));
+
+ if (aver < AGTME15 && aver != AGT10) {
+ question = (tline *)rmalloc(MaxQuestion * sizeof(tline));
+ answer = (tline *)rmalloc(MaxQuestion * sizeof(tline));
+ } else if (aver >= AGTME15) {
+ quest_ptr = (descr_ptr *)rmalloc(MaxQuestion * sizeof(descr_ptr));
+ ans_ptr = (descr_ptr *)rmalloc(MaxQuestion * sizeof(descr_ptr));
+ }
+ msg_ptr = (descr_ptr *)rmalloc((last_message) * sizeof(descr_ptr));
+
+ if (maxroom >= first_room) {
+ room = (room_rec *)rmalloc((maxroom - first_room + 1) * sizeof(room_rec));
+ room_ptr = (descr_ptr *)rmalloc((maxroom - first_room + 1) * sizeof(descr_ptr));
+ help_ptr = (descr_ptr *)rmalloc((maxroom - first_room + 1) * sizeof(descr_ptr));
+ special_ptr = (descr_ptr *)rmalloc((maxroom - first_room + 1) * sizeof(descr_ptr));
+ if (diag) room_inside = (integer *)rmalloc((maxroom - first_room + 1) * sizeof(integer));
+ }
+
+ if (maxnoun >= first_noun) {
+ noun = (noun_rec *)rmalloc((maxnoun - first_noun + 1) * sizeof(noun_rec));
+ noun_ptr = (descr_ptr *)rmalloc((maxnoun - first_noun + 1) * sizeof(descr_ptr));
+ push_ptr = (descr_ptr *)rmalloc((maxnoun - first_noun + 1) * sizeof(descr_ptr));
+ pull_ptr = (descr_ptr *)rmalloc((maxnoun - first_noun + 1) * sizeof(descr_ptr));
+ text_ptr = (descr_ptr *)rmalloc((maxnoun - first_noun + 1) * sizeof(descr_ptr));
+ turn_ptr = (descr_ptr *)rmalloc((maxnoun - first_noun + 1) * sizeof(descr_ptr));
+ play_ptr = (descr_ptr *)rmalloc((maxnoun - first_noun + 1) * sizeof(descr_ptr));
+ if (diag) noun_inside = (integer *)rmalloc((maxnoun - first_noun + 1) * sizeof(integer));
+ }
+
+ if (maxcreat >= first_creat) {
+ creature = (creat_rec *)rmalloc((maxcreat - first_creat + 1) * sizeof(creat_rec));
+ creat_ptr = (descr_ptr *)rmalloc((maxcreat - first_creat + 1) * sizeof(descr_ptr));
+ ask_ptr = (descr_ptr *)rmalloc((maxcreat - first_creat + 1) * sizeof(descr_ptr));
+ talk_ptr = (descr_ptr *)rmalloc((maxcreat - first_creat + 1) * sizeof(descr_ptr));
+ if (diag) creat_inside = (integer *)rmalloc((maxcreat - first_creat + 1) * sizeof(integer));
+ }
+
+ if (aver >= AGTME10) {
+ userstr = (tline *)rmalloc(MAX_USTR * sizeof(tline));
+ sub_name = (word *)rmalloc(MAX_SUB * sizeof(word));
+ }
+ command = (cmd_rec *)rmalloc(sizeof(cmd_rec) * last_cmd);
+
+ if (aver >= AGT15)
+ err_ptr = (descr_ptr *)rmalloc(NUM_ERR * sizeof(descr_ptr));
+
+
+ reinit_dict(); /* The dictionary grows dynamically so we want to
+ allocate it AFTER we have allocated all the permenent
+ things */
+}
+
+
+
+
+/*-------------------------------------------------------------------------*/
+/* Read DA1: The Info file; this is a text file containing a miscellany of */
+/* game information that wouldn't fit elsewhere */
+/*-------------------------------------------------------------------------*/
+
+static int try_read_da1(fc_type fc, genfile fda1, rbool diag)
+/* Returns new aver value to try, or 0 on success. */
+/* diag determines if noun inside info will be read */
+/* VER values: 1=Small
+ 2=Large
+ 3=Master's Edition
+ 4="Soggy Large", with a larger last_room
+*/
+/* AVER values: see agility.h for the current values */
+/* NOTE: This routine is allowed to set *ver*, but is not allowed to
+ change *aver*; should it be neccessary to change *aver*, then the routine
+ should return the new *aver* value.
+ (The only exception to this is in the very beginning-- and that may get
+ changed)
+ [This is done to allow the user to force a version number]
+*/
+{
+ int i;
+
+ MAX_CMD_SIZE = 30;
+ maxpict = maxpix = maxfont = maxsong = 0;
+ linenum = 0;
+ bhold = 0;
+ game_sig = 0;
+ unexpected_eof = 0;
+
+ if (aver == 0 && isbool(fda1)) aver = AGTMAST;
+ /* From this point on can assume ME detected */
+
+ freeze_mode = 0; /* The default values */
+ if (aver >= AGTME10) { /* 2 rbool */
+ debug_mode = readrbool(fda1); /* DEBUG */
+ if (aver >= AGTME15) {
+ if (!isbool(fda1)) aver = AGTME10;
+ else freeze_mode = readrbool(fda1);
+ } /* FREEZE */
+ ver = 3;
+ }
+
+ start_room = readnum(fda1);
+ treas_room = readnum(fda1);
+ if (aver != AGT10) resurrect_room = readnum(fda1);
+ else resurrect_room = start_room;
+ if (aver >= AGTME10) { /* 4 int */
+ score_mode = readnum(fda1); /* Score option */
+ statusmode = readnum(fda1); /* Status option */
+ startup_time = readnum(fda1); /* Starting time */
+ delta_time = readnum(fda1); /* Delta_time */
+ } else {
+ score_mode = statusmode = 0;
+ startup_time = delta_time = 0;
+ }
+ max_lives = readnum(fda1);
+ if (aver != AGT10) max_score = readnum(fda1);
+ else max_score = 0;
+ maxroom = readnum(fda1);
+ maxnoun = readnum(fda1);
+ maxcreat = readnum(fda1);
+ if (aver >= AGTME10) numglobal = readnum(fda1); /* # of global nouns? */
+ else numglobal = 0;
+ last_cmd = readnum(fda1);
+ readjunk(fda1); /* Number of items being carried */
+ readjunk(fda1); /* Number of items being worn */
+ if (isbool(fda1)) return AGT10; /* AGT v1.0 */
+ /* From this point on, can assume AGT v1.0 is detected. */
+ readptr(fda1, &intro_ptr);
+
+ deduce_sizes(fc, diag);
+
+ if (aver >= AGTME10) {
+
+ (void)readdict(fda1); /* ?!?! Not sure what this is */
+
+ report("Reading global and flag nouns", fda1);
+
+ for (i = 0; i < MAX_FLAG_NOUN; i++)
+ flag_noun[i] = readdict(fda1); /* Read in flag nouns; may be NONE */
+ for (; i < 32; i++)
+ readjunk(fda1);
+
+ for (i = 0; i < numglobal; i++)
+ globalnoun[i] = readdict(fda1); /* Global nouns */
+ } else
+ for (i = 0; i < MAX_FLAG_NOUN; i++)
+ flag_noun[i] = 0;
+
+ report("Reading questions and junk", fda1);
+
+ if (aver < AGTME15 && aver != AGT10) {
+ for (i = 0; i < MaxQuestion; i++) {
+ readtext(fda1, question[i]); /* Question[i]== question #(i+1) */
+ chop_newline(question[i]);
+ readtext(fda1, answer[i]);
+ fix_answer(answer[i]);
+ }
+ } else if (aver >= AGTME15) {
+ /* There are 400 lines of description pointers, meaning
+ 200 descriptions. I'm guessing they're all questions and
+ answers, which means that there are 100 questions here. */
+ for (i = 0; i < MaxQuestion; i++) {
+ readptr(fda1, &quest_ptr[i]);
+ readptr(fda1, &ans_ptr[i]);
+ }
+ }
+
+ if (!isbool(fda1)) { /* Something is wrong... */
+ if (aver == AGTMAST)
+ return AGTME10;
+ else if (aver != AGTCOS && aver != AGT15 && aver != AGT15F) return AGTCOS;
+ else return AGT15;
+ }
+ report("Reading have_meta", fda1);
+ have_meta = readrbool(fda1);
+
+ if (have_meta) {
+ for (i = 0; i <= last_obj; i++) { /* i.e. iterate over all objects */
+ readjunk(fda1);
+ readjunk(fda1);
+ }
+ }
+
+ /* The Master's Edition apparently _always_ sets have_meta,
+ even if there are no metacommands. The only way to determine
+ if there are really metacommands is to check last_cmd */
+ if (aver >= AGTME10 && last_cmd == 0) have_meta = 0;
+
+ report("Reading synonyms", fda1);
+
+ for (i = 0; i < TOTAL_VERB; i++)
+ synlist[i] = synptr; /* Is this correct? */
+ addsyn(-1); /* Put an end-of-list marker in place */
+
+ for (i = 0; i < 56; i++)
+ synlist[i] = readslist(fda1); /* May read <none> */
+
+ if (aver >= AGTME10) { /* Unknown verbs */
+ synlist[56] = readslist(fda1); /* VIEW */
+ synlist[57] = synlist[14]; /* AFTER */
+ synlist[14] = readslist(fda1); /* THROW */
+ }
+
+ if (aver == AGT183) {
+ /* Eliminate LIST_EXITS and add INSTRUCTIONS */
+ synlist[58] = synlist[52];
+ /* Move 'REMOVE'-- the last thing before INS in 1.83 verblist --
+ up to INSTRUCTIONS where it belongs */
+ for (i = 52; i > 42; i--) /* i:=Remove to Brief (above List Exits) */
+ synlist[i] = synlist[i - 1];
+ synlist[41] = synptr; /* LIST_EXITS, which doesn't exist in 1.83 and so
+ can't have synonyms */
+ addsyn(-1);
+ }
+
+ report("Starting dummy verbs", fda1);
+ for (i = 0; i < 25; i++) {
+ if (i != 0 || aver < AGTME10)
+ synlist[i + BASE_VERB] = readslist(fda1);
+ synlist[i + BASE_VERB + 25] = readslist(fda1);
+ }
+ if (aver >= AGTME10) {
+ synlist[BASE_VERB] = readslist(fda1);
+ for (i = 0; i < 15; i++) /* Subroutines */
+ synlist[i + BASE_VERB + 50] = readslist(fda1);
+ }
+ report("Reading DESC ptrs", fda1);
+ if (aver >= AGT15)
+ for (i = 0; i < NUM_ERR; i++)
+ readptr(fda1, &err_ptr[i]); /* Read in "standard" error messages. */
+ else /* Otherwise need to initialize them to nothing */
+ for (i = 0; i < NUM_ERR; i++) {
+ err_ptr[i].start = 0;
+ err_ptr[i].size = -1;
+ }
+
+ report("Reading messages", fda1);
+ if (DIAG) rprintf(" MSGS:1..%ld [%ld]\n", last_message, last_message);
+ for (i = 0; i < last_message; i++)
+ readptr(fda1, &msg_ptr[i]);
+
+ report("Reading room descs", fda1);
+ for (i = 0; i <= maxroom - first_room; i++) {
+ readptr(fda1, &room_ptr[i]);
+ readptr(fda1, &help_ptr[i]);
+ readptr(fda1, &special_ptr[i]);
+ }
+
+ report("Reading noun descs", fda1);
+ for (i = 0; i <= maxnoun - first_noun; i++) {
+ readptr(fda1, &noun_ptr[i]);
+ readptr(fda1, &push_ptr[i]);
+ readptr(fda1, &pull_ptr[i]);
+ readptr(fda1, &text_ptr[i]);
+ readptr(fda1, &turn_ptr[i]);
+ readptr(fda1, &play_ptr[i]);
+ }
+
+ report("Reading creatures", fda1);
+ if (maxcreat >= first_creat) {
+ for (i = 0; i <= maxcreat - first_creat; i++) {
+ readptr(fda1, &creat_ptr[i]);
+ if (aver != 0 && aver <= AGTCLASS) {
+ ask_ptr[i].start = talk_ptr[i].start = 0;
+ ask_ptr[i].size = talk_ptr[i].size = -1;
+ } else {
+ readptr(fda1, &talk_ptr[i]);
+ readptr(fda1, &ask_ptr[i]);
+ if (aver == 0 && (talk_ptr[i].size == 0 || ask_ptr[i].size == 0 ||
+ unexpected_eof)) return AGT135;
+ }
+ }
+ }
+ if (aver == AGT135 && unexpected_eof) return AGT12;
+
+ if (aver >= AGTME10) {
+ if (aver >= AGTME155 && !isnum(fda1)) return AGTME15;
+
+ maxpict = rangefix(readnum(fda1)); /* Number of pictures */
+ maxpix = rangefix(readnum(fda1)); /* Numper of PIXs */
+ maxsong = rangefix(readnum(fda1)); /* Number of sounds */
+ maxfont = rangefix(readnum(fda1)); /* Number of fonts. */
+
+ if (maxpix > MAX_PIX) {
+ rprintf("Invalid MAXPIX value?!?\n");
+ maxpix = MAX_PIX;
+ }
+
+ t_pictlist = (long *)rmalloc(sizeof(long) * maxpict);
+ t_pixlist = (long *)rmalloc(sizeof(long) * maxpix);
+ t_songlist = (long *)rmalloc(sizeof(long) * maxsong);
+ t_fontlist = (long *)rmalloc(sizeof(long) * maxfont);
+
+ for (i = 0; i < maxpict; i++)
+ t_pictlist[i] = readfname(fda1); /* picture file names */
+ for (i = 0; i < maxpix; i++)
+ pix_name[i] = readdict(fda1); /* PIX names */
+ for (i = 0; i < maxpix; i++)
+ t_pixlist[i] = readfname(fda1); /* PIX filenames */
+ for (i = 0; i < maxsong; i++)
+ t_songlist[i] = readfname(fda1); /* Sound filenames */
+ for (i = 0; i < maxfont; i++)
+ t_fontlist[i] = readfname(fda1); /* Font filenames */
+
+ for (i = 0; i < MAX_USTR; i++)
+ readtext(fda1, userstr[i]); /* This is just a guess-- should be
+ tested. */
+ } else {
+ for (i = 0; i < maxpix; i++) pix_name[i] = 0;
+ maxpict = maxpix = maxsong = maxfont = 0;
+ }
+ if ((aver == AGT135 || aver == 0) && isnum(fda1)) return AGT183;
+ if (aver == AGT183) {
+ long tval;
+ tval = readnum(fda1); /* Needs to be translated */
+ if (tval >= 1 && tval <= 4)
+ statusmode = agt18_statmode[tval - 1];
+ else statusmode = 0;
+ tval = readnum(fda1); /* Hours */
+ startup_time = readnum(fda1); /* Minutes */
+ tval += startup_time / 60;
+ startup_time = (startup_time % 60) + 100 * tval;
+ if (readrbool(fda1) && startup_time < 1200)
+ startup_time += 1200;
+ milltime_mode = readrbool(fda1); /* Military time */
+ delta_time = readnum(fda1);
+ }
+ if (DIAG) rprintf("Read in %d lines\n", linenum);
+ return 0;
+}
+
+
+
+static void set_da1_null(void)
+/* Set pointers that are malloc'd by try_read_da1 to NULL, to clear
+ the way for free_da1_stuff to recover them */
+{
+ static_str = NULL;
+ ss_end = ss_size = 0;
+ command = NULL;
+ cmd_ptr = NULL;
+ synlist = NULL;
+ userstr = NULL;
+ sub_name = NULL;
+ globalnoun = NULL;
+ err_ptr = NULL;
+ quest_ptr = ans_ptr = NULL;
+ question = answer = NULL;
+ msg_ptr = room_ptr = help_ptr = special_ptr = NULL;
+ noun_ptr = push_ptr = pull_ptr = text_ptr = turn_ptr = play_ptr = NULL;
+ room_inside = noun_inside = creat_inside = NULL;
+ creat_ptr = ask_ptr = talk_ptr = NULL;
+ pictlist = pixlist = fontlist = songlist = NULL;
+ room = NULL;
+ noun = NULL;
+ creature = NULL;
+ command = NULL;
+ t_pictlist = t_pixlist = t_fontlist = t_songlist = NULL;
+}
+
+
+
+static void free_da1_stuff(void)
+/* Free all data structures malloc'd by try_read_da1 */
+/* (This is neccessary since try_read_da1 may have to restart) */
+/* Note that if a pointer is NULL, rfree does nothing */
+/* Recall that rfree() is a macro that sets its argument to NULL */
+/* after freeing it */
+{
+ rfree(static_str);
+ ss_end = ss_size = 0;
+ rfree(userstr);
+ rfree(sub_name);
+ rfree(globalnoun);
+ rfree(err_ptr);
+ rfree(synlist);
+ rfree(quest_ptr);
+ rfree(ans_ptr);
+ rfree(question);
+ rfree(answer);
+ rfree(msg_ptr);
+ rfree(room_ptr);
+ rfree(help_ptr);
+ rfree(special_ptr);
+ rfree(noun_ptr);
+ rfree(push_ptr);
+ rfree(pull_ptr);
+ rfree(text_ptr);
+ rfree(room_inside);
+ rfree(noun_inside);
+ rfree(creat_inside);
+ rfree(turn_ptr);
+ rfree(play_ptr);
+ rfree(creat_ptr);
+ rfree(ask_ptr);
+ rfree(talk_ptr);
+ rfree(t_pictlist);
+ rfree(t_pixlist);
+ rfree(t_songlist);
+ rfree(t_fontlist);
+ rfree(room);
+ rfree(noun);
+ rfree(creature);
+ rfree(command);
+ free_dict();
+}
+
+static rbool read_da1(fc_type fc, rbool diag)
+/* diag is set by agtout to save extra diagnostic information */
+/* It has nothing to do with DIAG */
+{
+ genfile fda1;
+ int i;
+
+ ver = 0;
+ aver = 0;
+ top_quest = 0; /* Highest question actually referenced; set by fixcmd */
+ fda1 = openfile(fc, fDA1, NULL, 0);
+ if (!filevalid(fda1, fDA1)) return 0;
+
+ if (DIAG) {
+ char *s;
+ s = formal_name(fc, fDA1);
+ rprintf("Reading info file %s\n", s);
+ rfree(s);
+ }
+ set_da1_null();
+ while ((i = try_read_da1(fc, fda1, diag)) != 0) {
+ if (aver == i) {
+ rprintf("[Recoginiton loop: AVER=%d]\n", aver);
+ fatal("AGT version not recognized\n");
+ }
+ aver = i;
+ /* fseek(fda1,0,SEEK_SET); Go back to beginning... */
+ textrewind(fda1);
+ if (DIAG)
+ rprintf("...Found incompatibility; restarting, w/ AVER=%d\n", aver);
+ free_da1_stuff();
+ /* set_da1_null();*/
+ ver = 0;
+ }
+ if (aver == 0) aver = AGTSTD; /* i.e. if we didn't notice any differences from
+ standard format, it must be a standard file. */
+ readclose(fda1);
+ return 1; /* Success */
+}
+
+
+
+/*-------------------------------------------------------------------------*/
+/* Miscellaneous routines to tie up loose ends and clean up afterwards. */
+/*-------------------------------------------------------------------------*/
+
+static void finish_read(rbool cleanup)
+/* cleanup=0 means it will leave cmd_ptr, 1=it cleans up cmd_ptr */
+/* The only reason to set cleanup==0 is if we are writing a diagnostic
+ program of some sort */
+{
+ int i;
+
+ if (aver >= AGT18 && aver <= AGT18MAX) {
+ intro_first = 1;
+ max_lives = 1;
+ TWO_CYCLE = 1;
+ PURE_AFTER = 0;
+ } else {
+ intro_first = 0;
+ TWO_CYCLE = 0;
+ PURE_AFTER = 1;
+ }
+
+ min_ver = 0; /* All original AGT games will run with any version of
+ AGiliTy. */
+
+ if (aver >= AGTME10)
+ PURE_ROOMTITLE = 0;
+
+ if (aver >= AGT15)
+ box_title = 1;
+ else box_title = 0;
+
+ /* Compute max_score if it isn't already computed */
+ if (max_score == 0) {
+ for (i = 0; i < maxroom - first_room + 1; i++)
+ if (!room[i].unused) max_score += room[i].points;
+ for (i = 0; i < maxnoun - first_noun + 1; i++)
+ if (!noun[i].unused) max_score += noun[i].points;
+ for (i = 0; i < maxcreat - first_creat + 1; i++)
+ if (!creature[i].unused) max_score += creature[i].points;
+ }
+
+ if (cleanup) rfree(cmd_ptr);
+ if (ss_end > 0)
+ static_str = (char *)rrealloc(static_str, sizeof(char) * ss_end);
+
+ /* Now convert string handles into honest pointers */
+ for (i = 0; i <= maxroom - first_room; i++)
+ room[i].name = static_str + room_name[i];
+ for (i = 0; i <= maxnoun - first_noun; i++) {
+ noun[i].shortdesc = static_str + noun_sdesc[i];
+ noun[i].position = static_str + noun_pos[i];
+ }
+ for (i = 0; i <= maxcreat - first_creat; i++)
+ creature[i].shortdesc = static_str + creat_sdesc[i];
+
+ if (aver >= AGTME10) {
+ pictlist = (filename *)rmalloc(sizeof(filename) * maxpict);
+ pixlist = (filename *)rmalloc(sizeof(filename) * maxpix);
+ songlist = (filename *)rmalloc(sizeof(filename) * maxsong);
+ fontlist = (filename *)rmalloc(sizeof(filename) * maxfont);
+
+ for (i = 0; i < maxpict; i++)
+ pictlist[i] = static_str + t_pictlist[i];
+ for (i = 0; i < maxpix; i++)
+ pixlist[i] = static_str + t_pixlist[i];
+ for (i = 0; i < maxsong; i++)
+ songlist[i] = static_str + t_songlist[i];
+ for (i = 0; i < maxfont; i++)
+ fontlist[i] = static_str + t_fontlist[i];
+ }
+
+ /* Free the various temporary arrays */
+ rfree(room_name);
+ rfree(noun_sdesc);
+ rfree(noun_pos);
+ rfree(creat_sdesc);
+ rfree(t_pictlist);
+ rfree(t_pixlist);
+ rfree(t_songlist);
+ rfree(t_fontlist);
+
+ /* Reallocate questions and asnwers to only use the space that they need */
+ if (!RAW_CMD_OUT && top_quest < MaxQuestion) {
+ MaxQuestion = top_quest; /* top_quest is computed by fixcmd */
+ if (top_quest == 0) {
+ rfree(question);
+ rfree(answer);
+ rfree(quest_ptr);
+ rfree(ans_ptr);
+ } else {
+ if (question != NULL)
+ question = (tline *)rrealloc(question, top_quest * sizeof(tline));
+ if (answer != NULL)
+ answer = (tline *)rrealloc(answer, top_quest * sizeof(tline));
+ if (quest_ptr != NULL)
+ quest_ptr = (descr_ptr *)rrealloc(quest_ptr, top_quest * sizeof(descr_ptr));
+ if (ans_ptr != NULL)
+ ans_ptr = (descr_ptr *)rrealloc(ans_ptr, top_quest * sizeof(descr_ptr));
+ }
+ }
+}
+
+void free_all_agtread() {
+ int i;
+
+ if (!agx_file)
+ for (i = 0; i < last_cmd; i++)
+ rfree(command[i].data);
+ free_da1_stuff();
+ /* userstr, globalnoun, quest_ptr, ans_ptr, question, answer, msg_ptr,
+ room_ptr, help_ptr, special_ptr, noun_ptr, push_ptr, pull_ptr,
+ text_ptr, turn_ptr, play_ptr, creat_ptr, ask_ptr, talk_ptr,
+ room_inside, noun_inside, creat_inside
+ pictlist, pixlist, songlist, fontlist,
+ room, noun, creature, command,
+ dictionary data structures */
+}
+
+rbool readagt(fc_type fc, rbool diag)
+/* If diag==1, then extra diagnostic information is preserved */
+{
+ agx_file = 0;
+ mem_descr = NULL;
+ build_fixchar();
+ init_dict();
+ if (!read_da1(fc, diag)) return 0; /* Couldn't open DA1 file */
+ read_da2(fc);
+ read_da3(fc);
+ read_da4(fc);
+ read_da5(fc);
+ read_voc(fc);
+ read_opt(fc);
+ finish_read(!diag);
+ return 1;
+}
+
+} // End of namespace AGT
+} // End of namespace Glk
diff --git a/engines/glk/agt/agxfile.cpp b/engines/glk/agt/agxfile.cpp
new file mode 100644
index 0000000000..04729f5959
--- /dev/null
+++ b/engines/glk/agt/agxfile.cpp
@@ -0,0 +1,1483 @@
+/* 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"
+
+namespace Glk {
+namespace AGT {
+
+/* NOTES ON CHANGING THE AGX FILE FORMAT
+
+ First of all, don't.
+
+ One of the benefits of adventure creation systems like this is
+ that the same game files can be played on a variety of different
+ platforms without any extra effort on the part of the game
+ author. If you change the file format, this is no longer true: games
+ created under the new format won't run on the old interpreters.
+
+ Even if you distribute a new interpreter with your game, there are two
+ problems:
+
+ i) People on other platforms won't be able to play your game unless
+ and until your modified interpreter is ported to their machine. Since
+ I-F players as a group tend to use a wider range of different computers
+ and operating systems than the population at large, this is bad.
+
+ ii) Even for machines that you port your modified interpreter to,
+ people will now need to maintain two interpreters: the original one
+ (for most of the games) and your modified one (for your new game).
+ This is not only a nuisance but it wastes disk space.
+
+
+ If you *do* decide to change the file format anyhow, please adhere to
+ the following guidelines, to minimize confusion.
+
+GUIDLINES FOR NEW FILE FORMAT VERSIONS
+
+ File format version are labled by a series of four bytes near the
+beginning of the file. (They are actually the fifth, sixth, seventh,
+and eight bytes-- the first four bytes are the file format signature
+that indicate the file is an AGX file and not, say, a PCX file)
+
+ In order, they are the version owner id and number, and the
+extension owner id and number. In "vanilla" AGX, both owner id's are
+'R', the version number is 2, and the extension number is 1 (the
+extension associated with AGiliTy 1.0). (There are AGX files with
+version numbers 0 and 1 created by earlier releases of agt2agx. In
+fact, for downward compatibility, agt2agx will sometimes create a file
+of version 1 and extnum 7 if later features aren't being used.)
+
+ I will discuss the difference between "versions" and "extensions"
+further below, but briefly: extensions are minor changes whereas versions
+represent vast reoganizations of the file format. The routines below
+will still try to read in a file with an unrecognized extension, but
+they will give up on an unrecognized version.
+
+ If you create a new extension, then you should first change the
+extension owner id to something else; the owner id is intended to
+indicate who is responsible for defining this extension; 'R' indicates
+Robert Masenten (the author of this file) and so shouldn't be used by anyone
+else. The id isn't required to be a printable character.
+
+ You can then define the extension number however you want. The
+extension number is intended to differentiate between different
+extensions defined by the same source (e.g. two extensions both
+defined by me would have the same owner id but different extension
+numbers). The extensions that I define are numbered sequentially
+starting at 0, but you don't have to follow this convention if you
+don't want to.
+
+ Finally, send me an e-mail note telling me what you've done so I can
+keep track of the different extensions and prevent conflicts between
+owner-ids.
+
+ Creating a new version works the same way: change the version owner-id to
+something no one is using and set the version number however you want.
+If you're defining a new version, you can do whatever you want with
+the two extension bytes.
+
+
+EXTENSIONS AND VERSIONS
+
+ For purposes of the file format, an 'extension' is a change to the
+format that follows certain restrictions given below; a 'version' is one that
+violates one or more of these restrictions.
+
+ If at all possible you should try to fit your changes to the format
+within the limitations of an 'extension': it is more likely that other
+programs will work with your new file format and it is also more
+likely that your modified interpreter will still be able to understand
+the original file format.
+
+ An extension shouldn't change any of the existing data fields; nor
+should it insert new data fields into the middle of records. An
+extension *may* add new fields onto the end of one or more of the
+records and it can define new blocks.
+
+ Examples of things that would be extensions (create a new extension
+id and number, but keep the version the same):
+
+--Adding a new field onto the end of the creature record, containing
+ the code for a sound file that should be played whenever
+ the creature is in the room.
+
+--Adding a new block to the file containing debugging information for
+ the new AGT compiler you've just written, numbered 35.
+
+
+ Things that would *not* be extensions (create a new version id and
+number; do what you want with the extension id and number)
+
+ --Going to 32-bit object numbers for everything. (There *are*
+sneaky ways you could make this an extension; but not if you just do this
+by converting all the int16 fields in the file to int32)
+
+ --Changing metacommands to accept an arbitrary string of tokens
+instead of just ACTOR,VERB NOUN PREP OBJECT.
+
+
+
+A FEW NOTES ON BACKWARD COMPATIBILITY
+
+ (These notes only apply if you are creating an extension; if you are
+creating a new version, then anything goes)
+
+ If you add a new field onto an existing record (like the creature
+soundtrack example above) and read in an old-format file, then the new
+data fields will be automatically initialized to zero so long as none
+of them are individually longer than 81 bytes (if any *are* longer than
+81 bytes then the file routines may break). (There is nothing magic
+about 81 bytes; it just happens to be the length of the largest data
+structure that shows up in 'vanilla' AGX files).
+
+ If you add a new block, then you should check the extension number
+of the file and if the block doesn't exists be prepared to either
+initialize the data to something sensible or to exit cleanly with an
+error message.
+
+ */
+
+/* AGX File format versions and corresponding versions of AGiliTy
+ and Magx: (versions given as 'Version-Extension')
+
+ AGX AGiliTy Magx
+ 0-0 0.5
+ 0-1 0.7 0.1
+ 1-0 0.7.2 0.2
+ 1-1 0.8
+ 1-2 0.8.1 0.3
+ 1-3 0.8.3 0.4
+ 1-4 0.8.5
+ 1-5 0.8.6 0.5
+ 1-6 0.8.7 0.5.2
+ 1-7 0.8.8 0.6
+ 2-0 0.8.9 0.6.1
+ 2-1 1.0 0.6.3
+ 2-2 1.1 0.6.4
+ */
+
+/* Changes is AGX File format versions:
+ 0-0: Original format.
+ 0-1: Add
+ PURE_TIME, PURE_OBJ_DESC, exitmsg_base
+ noun.related_name
+ command.noun_adj, command.obj_adj
+ 1-0: Change index file format, fixing a bug
+ 1-1: Add
+ Multi-word verb block(28)
+ Preposition block(29)
+ (room|noun|creature).oclass
+ 1-2: Add (room|noun|creature).unused
+ 1-3: Add PURE_GRAMMAR
+ 1-4: Add (noun|creature).isglobal and (noun|creature).flagnum
+ Add TWO_CYCLE
+ 1-5: Add min_ver
+ Add PURE_AFTER
+ 1-6: Add (noun|creature).seen
+ 1-7: Add objflag and objprop blocks (with corrosponding
+ support data in the gameinfo block).
+ 2-0: No change in file format; version upped to protect against
+ a bug in early versions of the AGX file code.
+ 2-1: Added (noun|creature).proper
+ 2-2: Added noun_obj and obj_obj to cmd header
+ Added objflag.ystr, objflag.nstr, objprop.str_cnt, objprop.str_list
+ Added propstr block.
+ Added fallback_ext to file header.
+*/
+
+#define AGX_NUMBLOCK 37
+#define AGT_FILE_SIG 0x51C1C758L
+
+/* AGX File format:
+ (This tends to lag a little behind the code below;
+ you should double check against the actual file_info definitions
+ below)
+All integer values stored little-endian.
+desc_ptrs: int32 ofs, int32 leng (both in lines)
+dictionary word: int16
+slist ptr: int16
+tline: char[81]
+filename: char[10]
+rbool values are packed into bytes, 1 bit per value, from lsb to msb.
+cfgopt: 0=false, 1=true, 2=leave alone
+
+Mandatory blocks are marked with astericks.
+
+*File header: 16 bytes
+ uint 32 File ID [AGT_FILE_SIG, 4 bytes]
+ byte Version owner: 'R'
+ byte Version 0
+ byte Extension owner 'R'
+ byte Extension 0
+ char[2]: '\n\r' -- to catch download errors
+ byte Extension fallback. For non-'R' extensions, this gives the
+ 'R' extension to fall back to.
+ char[5] Reserved for future use (should be 0 right now)
+*0-File index:
+ For each block (including itself): [16 bytes]
+ uint32 starting offset
+ uint32 block size
+ uint32 number of records
+ uint32 size of a record (recsize*numrec == blocksize)
+11-Description strings (block of tline)
+12-Command text (block of int16)
+*1-Game header
+ uint16 AGT_version_code; +1 for "big/soggy" games
+ uint32 game_sig (game signature, used to check save files and debug info)
+ rbool debug_mode, freeze_mode, milltime_mode, bold_mode,
+ have_meta, mars_fix, intro_first, TWO_CYCLE;
+ uchar score_mode, statusmode;
+ uint16 max_lives
+ uint32 max_score;
+ uint16 startup_time, delta_time;
+ descr_ptr intro_ptr, title_ptr, ins_ptr;
+ int16 start_room, treas_room, resurrect_room
+ int16 first_room, first_noun, first_creat
+ int16 FLAG_NUM, CNT_NUM, VAR_NUM
+ int16 BASE_VERB
+ cfgopt PURE_ANSWER, PURE_TIME, PURE_ROOMTITLE;
+ cfgopt PURE_AND, PURE_METAVERB;
+ cfgopt PURE_SYN, PURE_NOUN, PURE_ADJ;
+ cfgopt PURE_DUMMY, PURE_SUBNAME, PURE_PROSUB;
+ cfgopt PURE_HOSTILE, PURE_GETHOSTILE;
+ cfgopt PURE_DISAMBIG, PURE_ALL;
+ cfgopt irun_mode, verboseflag;
+ cfgopt PURE_GRAMMAR (Extension R1-R3)
+ rbool TWO_CYCLE (R1-R4)
+ PURE_AFTER (R1-R5)
+ int16 min_ver
+ uchar font_status;
+ int16 num_rflags, num_nflags, num_cflags;
+ int16 num_rprops, num_nprops, num_cprops;
+2-Room data (room_rec format, pointers->int ref into static string)
+ include help, desc, special ptrs
+3-Noun data (noun_rec format)
+ include noun, text, turn, push, pull, play ptrs
+4-Creature data (creat_rec format)
+ include creature, talk, ask ptrs
+5-Command headers (cmd_rec format), pointers into command text
+ must be in increasing order.
+6-Standard error message ptrs (array of descptr
+7-Message ptrs (array of descptr)
+8-Question pointers (array of descptr)
+9-Answer pointers (array of descptr)
+10-User strings (array of tline)
+*13-Static string block (block of chars)
+14-Subroutine dictionary ids (array of word:int16)
+*15-Synlist (for verbs) (array of slist:int16)
+16-Pix names (array of word:int16 -- pointers into dictionary)
+17-Global nouns (array of word:int16 -- ptrs into dictionary)
+18-Flag nouns (array of word:int16)
+*19-Syntbl (block of word:int16)
+*20-Dictionary text (block of char)
+*21-Dictionary 'index' (array of uint32)
+22-OPT block (14 bytes)
+23-Picture filename ptrs
+24-Pix filename ptrs
+25-Font filename ptrs
+26-Sound filename ptrs
+27-VOC block, an array of verbinfo_rec
+28-Multi-word verbs (Extension R1-R1)
+29-Prep table (Extension R1-R1)
+30- ObjFlag Data (Extension R1-R7)
+31- ObjProp Data (Extension R1-R7)
+32- ObjFlag Lookup (Extension R1-R7)
+33- ObjProp Lookup (Extension R1-R7)
+34- ObjProp string pointers (array of FT_STR) (Extension R2-R2)
+35- Variable itemization array (Extension R2-R2)
+36- Flag itemization array (Extension R2-R2)
+
+*/
+
+/* AGT Version IDs; +1 for LARGE/SOGGY
+ 00000=v1.0
+ 01800=v1.18
+ 01900=v1.19
+ 02000=v1.20 ("Early Classic")
+ 03200=v1.32/COS
+ 03500=v1.35 ("Classic")
+ 05000=v1.5/H
+ 05050=v1.5/F (MDT)
+ 05070=v1.6 (PORK)
+ 08200=v1.82
+ 08300=v1.83
+ 10000=ME/1.0
+ 15000=ME/1.5
+ 15500=ME/1.55
+ 16000=ME/1.6
+ 20000=Magx/0.0
+ etc.
+*/
+
+
+
+
+/* ------------------------------------------------------------- */
+/* AGX Block Descriptions */
+/* ------------------------------------------------------------- */
+
+
+static integer old_base_verb;
+
+/* AGX file info blocks */
+
+#define g(ft,dt,var) {ft,dt,&var,0}
+#define r(ft,dt,str,f) {ft,dt,NULL,offsetof(str,f)}
+#define dptype {FT_DESCPTR,DT_DESCPTR,NULL,0}
+#define xx DT_DEFAULT
+#define u16 FT_UINT16
+#define u32 FT_UINT32
+#define bb FT_BOOL
+#define i16 FT_INT16
+
+static file_info fi_gameinfo[] = {
+ /* 0 */
+ g(FT_VERSION, xx, aver), /* FT_VERSION converter also sets ver */
+ g(u32, DT_LONG, game_sig),
+ /* 6 */
+ g(bb, xx, debug_mode), g(bb, xx, freeze_mode), g(bb, xx, milltime_mode),
+ g(bb, xx, bold_mode), g(bb, xx, have_meta), g(bb, xx, mars_fix),
+ g(bb, xx, intro_first), g(bb, xx, box_title),
+ /* 7 */
+ g(FT_BYTE, xx, score_mode), g(FT_BYTE, xx, statusmode),
+ g(i16, xx, max_lives), g(u32, DT_LONG, max_score),
+ /* 15 */
+ g(i16, xx, startup_time), g(i16, xx, delta_time),
+ /* 19 */
+ g(FT_DESCPTR, xx, intro_ptr), g(FT_DESCPTR, xx, title_ptr),
+ g(FT_DESCPTR, xx, ins_ptr),
+ /* 43 */
+ g(i16, xx, treas_room),
+ g(i16, xx, start_room), g(i16, xx, resurrect_room),
+ g(i16, xx, first_room), g(i16, xx, first_noun),
+ g(i16, xx, first_creat), g(i16, xx, FLAG_NUM),
+ g(i16, xx, CNT_NUM), g(i16, xx, VAR_NUM),
+ g(i16, xx, old_base_verb),
+ /* 63 */
+ g(FT_CFG, xx, PURE_ANSWER), g(FT_CFG, xx, PURE_ROOMTITLE),
+ g(FT_CFG, xx, PURE_AND), g(FT_CFG, xx, PURE_METAVERB),
+ g(FT_CFG, xx, PURE_SYN), g(FT_CFG, xx, PURE_NOUN), g(FT_CFG, xx, PURE_ADJ),
+ g(FT_CFG, xx, PURE_DUMMY), g(FT_CFG, xx, PURE_SUBNAME),
+ g(FT_CFG, xx, PURE_PROSUB), g(FT_CFG, xx, PURE_HOSTILE),
+ g(FT_CFG, xx, PURE_GETHOSTILE), g(FT_CFG, xx, PURE_DISAMBIG),
+ g(FT_CFG, xx, PURE_ALL),
+ g(FT_CFG, xx, irun_mode), g(FT_CFG, xx, verboseflag),
+ g(FT_CFG, xx, PURE_TIME), /* Ext R0-1 */
+ g(FT_CFG, xx, PURE_OBJ_DESC), /* Ext R0-1 */
+ /* 81 */
+ g(i16, xx, exitmsg_base), /* Ext R0-1 */
+ /* 83 */
+ g(FT_CFG, xx, PURE_GRAMMAR), /* Ext R1-3 */
+ g(bb, xx, TWO_CYCLE), /* Ext R1-4 */
+ g(bb, xx, PURE_AFTER), /* Ext R1-5 */
+ g(i16, xx, min_ver), /* Ext R1-5 */
+ g(FT_BYTE, xx, font_status), /* Ext R1-5 */
+ g(i16, xx, num_rflags), g(i16, xx, num_nflags), g(i16, xx, num_cflags), /* Ext R1-7 */
+ g(i16, xx, num_rprops), g(i16, xx, num_nprops), g(i16, xx, num_cprops), /* Ext R1-7 */
+ endrec
+};
+
+static file_info fi_room[] = {
+ dptype, /* help */
+ dptype, /* desc */
+ dptype, /* special */
+ r(FT_STR, xx, room_rec, name),
+ r(FT_INT32, xx, room_rec, flag_noun_bits),
+ r(FT_INT32, xx, room_rec, PIX_bits),
+ r(FT_SLIST, xx, room_rec, replacing_word),
+ r(FT_WORD, xx, room_rec, replace_word),
+ r(FT_WORD, xx, room_rec, autoverb),
+ r(FT_PATHARRAY, xx, room_rec, path),
+ r(FT_INT16, xx, room_rec, key),
+ r(FT_INT16, xx, room_rec, points),
+ r(FT_INT16, xx, room_rec, light),
+ r(FT_INT16, xx, room_rec, pict),
+ r(FT_INT16, xx, room_rec, initdesc),
+ r(bb, xx, room_rec, seen), r(bb, xx, room_rec, locked_door),
+ r(bb, xx, room_rec, end), r(bb, xx, room_rec, win), r(bb, xx, room_rec, killplayer),
+ r(bb, xx, room_rec, unused), /* Ext R1-2: Can add here since rbool */
+ r(FT_INT16, xx, room_rec, oclass), /* Ext R1-1 */
+ endrec
+};
+
+static file_info fi_noun[] = {
+ dptype, /* Noun */
+ dptype, /* Text */
+ dptype, dptype, dptype, dptype, /* Turn, push, pull, play */
+ r(FT_STR, xx, noun_rec, shortdesc),
+ r(FT_STR, xx, noun_rec, position),
+ r(FT_SLIST, xx, noun_rec, syns),
+ r(FT_WORD, xx, noun_rec, name), r(FT_WORD, xx, noun_rec, adj),
+ /* r(FT_WORD,xx,noun_rec,pos_prep), r(FT_WORD,xx,noun_rec,pos_name),*/
+ r(FT_INT16, xx, noun_rec, nearby_noun),
+ r(FT_INT16, xx, noun_rec, num_shots), r(FT_INT16, xx, noun_rec, points),
+ r(FT_INT16, xx, noun_rec, weight), r(FT_INT16, xx, noun_rec, size),
+ r(FT_INT16, xx, noun_rec, key),
+ r(FT_INT16, xx, noun_rec, initdesc), r(FT_INT16, xx, noun_rec, pict),
+ r(FT_INT16, xx, noun_rec, location),
+ r(bb, xx, noun_rec, plural),
+ r(bb, xx, noun_rec, something_pos_near_noun),
+ r(bb, xx, noun_rec, has_syns),
+ r(bb, xx, noun_rec, pushable), r(bb, xx, noun_rec, pullable),
+ r(bb, xx, noun_rec, turnable), r(bb, xx, noun_rec, playable),
+ r(bb, xx, noun_rec, readable), r(bb, xx, noun_rec, on),
+ r(bb, xx, noun_rec, closable), r(bb, xx, noun_rec, open),
+ r(bb, xx, noun_rec, lockable), r(bb, xx, noun_rec, locked),
+ r(bb, xx, noun_rec, edible), r(bb, xx, noun_rec, wearable),
+ r(bb, xx, noun_rec, drinkable), r(bb, xx, noun_rec, poisonous),
+ r(bb, xx, noun_rec, movable), r(bb, xx, noun_rec, light),
+ r(bb, xx, noun_rec, shootable), r(bb, xx, noun_rec, win),
+ r(bb, xx, noun_rec, unused), /* Ext R1-2: Can add here since packed rbool*/
+ r(bb, xx, noun_rec, isglobal), /* Ext R1-4: ditto (&room for 1 more). */
+ r(FT_WORD, xx, noun_rec, related_name), /* Ext R0-1 */
+ r(FT_INT16, xx, noun_rec, oclass), /* Ext R1-1 */
+ r(FT_INT16, xx, noun_rec, flagnum), /* Ext R1-4 */
+ r(bb, xx, noun_rec, seen), /* Ext R1-6 */
+ r(bb, xx, noun_rec, proper), /* Ext R2-1 */
+ endrec
+};
+
+static file_info fi_creat[] = {
+ dptype, /* Creature */
+ dptype, dptype, /* Talk, ask */
+ r(FT_STR, xx, creat_rec, shortdesc),
+ r(FT_SLIST, xx, creat_rec, syns),
+ r(FT_WORD, xx, creat_rec, name), r(FT_WORD, xx, creat_rec, adj),
+ r(FT_INT16, xx, creat_rec, location),
+ r(FT_INT16, xx, creat_rec, weapon), r(FT_INT16, xx, creat_rec, points),
+ r(FT_INT16, xx, creat_rec, counter), r(FT_INT16, xx, creat_rec, threshold),
+ r(FT_INT16, xx, creat_rec, timethresh), r(FT_INT16, xx, creat_rec, timecounter),
+ r(FT_INT16, xx, creat_rec, pict), r(FT_INT16, xx, creat_rec, initdesc),
+ r(bb, xx, creat_rec, has_syns), r(bb, xx, creat_rec, groupmemb),
+ r(bb, xx, creat_rec, hostile),
+ r(bb, xx, creat_rec, unused), /* Ext R1-2: Can add since packed rbool */
+ r(bb, xx, creat_rec, isglobal), /* Ext R1-4: ditto (& space for 3 more) */
+ r(FT_BYTE, xx, creat_rec, gender),
+ r(FT_INT16, xx, creat_rec, oclass), /* Ext R1-1 */
+ r(FT_INT16, xx, creat_rec, flagnum), /* Ext R1-4 */
+ r(bb, xx, creat_rec, seen), /* Ext R1-6 */
+ r(bb, xx, creat_rec, proper), /* Ext R2-1 */
+ endrec
+};
+
+static file_info fi_cmdhead[] = {
+ {FT_CMDPTR, DT_CMDPTR, NULL, 0},
+ r(FT_INT16, xx, cmd_rec, actor),
+ r(FT_WORD, xx, cmd_rec, verbcmd), r(FT_WORD, xx, cmd_rec, nouncmd),
+ r(FT_WORD, xx, cmd_rec, objcmd), r(FT_WORD, xx, cmd_rec, prep),
+ r(FT_INT16, xx, cmd_rec, cmdsize),
+ r(FT_WORD, xx, cmd_rec, noun_adj), r(FT_WORD, xx, cmd_rec, obj_adj), /* Ext R0-1*/
+ r(FT_INT16, xx, cmd_rec, noun_obj), /* Ext R2-2 */
+ r(FT_INT16, xx, cmd_rec, obj_obj), /* Ext R2-2 */
+ endrec
+};
+
+static file_info fi_verbentry[] = {
+ r(FT_WORD, xx, verbentry_rec, verb),
+ r(FT_WORD, xx, verbentry_rec, prep),
+ r(FT_INT16, xx, verbentry_rec, objnum),
+ endrec
+};
+
+
+static file_info fi_descptr[] = {
+ r(FT_INT32, xx, descr_ptr, start),
+ r(FT_INT32, xx, descr_ptr, size),
+ endrec
+};
+
+static file_info fi_tline[] = {
+ {FT_TLINE, xx, NULL, 0},
+ endrec
+};
+
+static file_info fi_attrrec[] = { /* Ext R1-R7 */
+ r(FT_INT32, xx, attrdef_rec, r),
+ r(FT_INT32, xx, attrdef_rec, n),
+ r(FT_INT32, xx, attrdef_rec, c),
+ r(FT_BYTE, xx, attrdef_rec, rbit),
+ r(FT_BYTE, xx, attrdef_rec, nbit),
+ r(FT_BYTE, xx, attrdef_rec, cbit),
+ r(FT_STR, xx, attrdef_rec, ystr), /* Ext R2-R2 */
+ r(FT_STR, xx, attrdef_rec, nstr), /* Ext R2-R2 */
+ endrec
+};
+
+static file_info fi_proprec[] = { /* Ext R1-R7 */
+ r(FT_INT32, xx, propdef_rec, r),
+ r(FT_INT32, xx, propdef_rec, n),
+ r(FT_INT32, xx, propdef_rec, c),
+ r(FT_INT32, xx, propdef_rec, str_cnt), /* Ext R2-R2 */
+ r(FT_INT32, xx, propdef_rec, str_list), /* Ext R2-R2 */
+ endrec
+};
+
+static file_info fi_varrec[] = { /* Ext R2-R2 */
+ r(FT_INT32, xx, vardef_rec, str_cnt),
+ r(FT_INT32, xx, vardef_rec, str_list),
+ endrec
+};
+
+static file_info fi_flagrec[] = { /* Ext R2-R2 */
+ r(FT_STR, xx, flagdef_rec, ystr),
+ r(FT_STR, xx, flagdef_rec, nstr),
+ endrec
+};
+
+#undef g
+#undef r
+#undef xx
+#undef u16
+#undef u32
+#undef bb
+#undef i16
+#undef dptype
+
+static void set_endrec(file_info *fi, int index) {
+ fi[index].ftype = FT_END;
+ fi[index].dtype = 0;
+ fi[index].ptr = NULL;
+ fi[index].offset = 0;
+}
+
+/* ------------------------------------------------------------- */
+
+/* If <to_intern> is true, convert "0" string ptrs to "yes/no" ptrs.
+ If it is false, convert the other way. */
+/* This is done for the sake of downward compatibility.
+ It *does* mean that the first string in static_str cannot
+ be an attribute's yes/no string. */
+
+/* "0" pointers in this case will actually be equal to static_str
+ (since that is the base point for all pointers to static strings.) */
+
+const char base_yesstr[] = "yes";
+const char base_nostr[] = "no";
+
+static void conv_fstr(const char **s, rbool yes, rbool to_intern) {
+ if (to_intern) { /* Convert to internal form */
+ assert(*s != NULL);
+ if (*s == static_str) *s = yes ? base_yesstr : base_nostr;
+ } else { /* convert to external form */
+ if (*s == NULL || *s == base_yesstr || *s == base_nostr)
+ *s = static_str;
+ }
+}
+
+static void fix_objflag_str(rbool to_intern) {
+ int i;
+ for (i = 0; i < oflag_cnt; i++) {
+ conv_fstr(&attrtable[i].ystr, 1, to_intern);
+ conv_fstr(&attrtable[i].nstr, 0, to_intern);
+ }
+ if (flagtable)
+ for (i = 0; i <= FLAG_NUM; i++) {
+ conv_fstr(&flagtable[i].ystr, 1, to_intern);
+ conv_fstr(&flagtable[i].nstr, 0, to_intern);
+ }
+}
+
+/* ------------------------------------------------------------- */
+/* AGX Reading Code */
+/* ------------------------------------------------------------- */
+
+
+static long descr_ofs;
+
+void agx_close_descr(void) {
+ if (mem_descr != NULL)
+ rfree(mem_descr);
+ else if (descr_ofs != -1)
+ buffclose(); /* This closes the whole AGX file */
+}
+
+descr_line *agx_read_descr(long start, long size) {
+ long i, line, len;
+ descr_line *txt;
+ char *buff;
+
+ if (size <= 0) return NULL;
+
+ if (mem_descr == NULL && descr_ofs != -1)
+ buff = (char *)read_recblock(NULL, FT_CHAR, size,
+ descr_ofs + start, size * ft_leng[FT_CHAR]);
+ else
+ buff = mem_descr + start;
+
+ len = 0;
+ for (i = 0; i < size; i++) /* Count the number of lines */
+ if (buff[i] == 0) len++;
+ txt = (descr_line *)rmalloc(sizeof(descr_line) * (len + 1));
+ txt[0] = buff;
+ i = 0;
+ for (line = 1; line < len;) /* Determine where each of the lines is */
+ if (buff[i++] == 0)
+ txt[line++] = buff + i;
+ txt[len] = NULL; /* Mark the end of the array */
+ return txt;
+}
+
+
+/* We need to read in command text and use cmd_rec[] values to
+ rebuild command[].data. We are guaranteed that cmd_rec[] is in
+ increasing order */
+
+static void read_command(long cmdcnt, long cmdofs, rbool diag) {
+ long i;
+
+ for (i = 0; i < last_cmd; i++) {
+ command[i].data = (integer *)rmalloc(sizeof(integer) * command[i].cmdsize);
+ read_recblock(command[i].data, FT_INT16, command[i].cmdsize,
+ cmdofs + 2 * cmd_ptr[i], 2 * command[i].cmdsize);
+ }
+ if (!diag) rfree(cmd_ptr);
+}
+
+
+/* Correct for differences between old_base_verb and BASE_VERB.
+ This means that the interpreter's set of built-inv verbs has changed
+ since the file was created. */
+static void correct_synlist(void) {
+ int i;
+ if (BASE_VERB == old_base_verb) return; /* Nothing needs to be done */
+
+ /* Need to move everything >= old_base_verb to BASE_VERB */
+ memmove(synlist + BASE_VERB, synlist + old_base_verb,
+ sizeof(slist) * (DVERB + MAX_SUB));
+
+ if (BASE_VERB < old_base_verb) /* We've _lost_ verbs */
+ agtwarn("Missing built-in verbs.", 0);
+
+ /* Now we need to give the "new" verbs empty synonym lists */
+ for (i = old_base_verb; i < BASE_VERB; i++)
+ synlist[i] = synptr;
+ addsyn(-1);
+}
+
+
+
+static void set_roomdesc(file_info fi[]) {
+ fi[0].ptr = help_ptr = (descr_ptr *)rmalloc(sizeof(descr_ptr) * (maxroom - first_room + 1));
+ fi[1].ptr = room_ptr = (descr_ptr *)rmalloc(sizeof(descr_ptr) * (maxroom - first_room + 1));
+ fi[2].ptr = special_ptr = (descr_ptr *)rmalloc(sizeof(descr_ptr) * (maxroom - first_room + 1));
+}
+
+static void wset_roomdesc(file_info fi[]) {
+ fi[0].ptr = help_ptr;
+ fi[1].ptr = room_ptr;
+ fi[2].ptr = special_ptr;
+}
+
+static void set_noundesc(file_info *fi) {
+ fi[0].ptr = noun_ptr = (descr_ptr *)rmalloc(sizeof(descr_ptr) * (maxnoun - first_noun + 1));
+ fi[1].ptr = text_ptr = (descr_ptr *)rmalloc(sizeof(descr_ptr) * (maxnoun - first_noun + 1));
+ fi[2].ptr = turn_ptr = (descr_ptr *)rmalloc(sizeof(descr_ptr) * (maxnoun - first_noun + 1));
+ fi[3].ptr = push_ptr = (descr_ptr *)rmalloc(sizeof(descr_ptr) * (maxnoun - first_noun + 1));
+ fi[4].ptr = pull_ptr = (descr_ptr *)rmalloc(sizeof(descr_ptr) * (maxnoun - first_noun + 1));
+ fi[5].ptr = play_ptr = (descr_ptr *)rmalloc(sizeof(descr_ptr) * (maxnoun - first_noun + 1));
+}
+
+static void wset_noundesc(file_info *fi) {
+ fi[0].ptr = noun_ptr;
+ fi[1].ptr = text_ptr;
+ fi[2].ptr = turn_ptr;
+ fi[3].ptr = push_ptr;
+ fi[4].ptr = pull_ptr;
+ fi[5].ptr = play_ptr;
+}
+
+static void set_creatdesc(file_info *fi) {
+ fi[0].ptr = creat_ptr = (descr_ptr *)rmalloc(sizeof(descr_ptr) * (maxcreat - first_creat + 1));
+ fi[1].ptr = talk_ptr = (descr_ptr *)rmalloc(sizeof(descr_ptr) * (maxcreat - first_creat + 1));
+ fi[2].ptr = ask_ptr = (descr_ptr *)rmalloc(sizeof(descr_ptr) * (maxcreat - first_creat + 1));
+}
+
+static void wset_creatdesc(file_info *fi) {
+ fi[0].ptr = creat_ptr;
+ fi[1].ptr = talk_ptr;
+ fi[2].ptr = ask_ptr;
+}
+
+static void set_cmdptr(file_info *fi) {
+ fi[0].ptr = cmd_ptr = (long *)rmalloc(sizeof(long) * last_cmd);
+}
+
+static void wset_cmdptr(file_info *fi) {
+ fi[0].ptr = cmd_ptr;
+}
+
+
+typedef struct { /* Entries in the index header of the AGX file */
+ uint32 file_offset;
+ uint32 blocksize;
+ uint32 numrec;
+ uint32 recsize;
+} index_rec;
+
+static file_info fi_index[] = {
+ {FT_UINT32, DT_DEFAULT, NULL, offsetof(index_rec, file_offset)},
+ {FT_UINT32, DT_DEFAULT, NULL, offsetof(index_rec, blocksize)},
+ {FT_UINT32, DT_DEFAULT, NULL, offsetof(index_rec, numrec)},
+ {FT_UINT32, DT_DEFAULT, NULL, offsetof(index_rec, recsize)},
+ endrec
+};
+
+
+/*
+ uint32 File ID ['....' 4 bytes]
+ byte Version owner: 'R'
+ byte Version 0
+ byte Extension owner 'R'
+ byte Extension 0
+ */
+
+typedef struct {
+ unsigned long fileid;
+ unsigned long res1; /* Reserved for future use */
+ uchar res2;
+ uchar eol_chk1; /* Catch non-binary upload errors */
+ uchar eol_chk2;
+ uchar ver_own;
+ uchar version;
+ uchar ext_own;
+ uchar extnum;
+ uchar fallback_ext; /* For non-'R' extensions, this is the 'R' extension
+ to fall back to. */
+} file_head_rec;
+
+static file_info fi_header[] = {
+ {FT_UINT32, DT_LONG, NULL, offsetof(file_head_rec, fileid)}, /* File ID */
+ {FT_BYTE, DT_DEFAULT, NULL, offsetof(file_head_rec, ver_own)}, /* Owner */
+ {FT_BYTE, DT_DEFAULT, NULL, offsetof(file_head_rec, version)}, /* Version */
+ {FT_BYTE, DT_DEFAULT, NULL, offsetof(file_head_rec, ext_own)}, /*Ext owner*/
+ {FT_BYTE, DT_DEFAULT, NULL, offsetof(file_head_rec, extnum)}, /* Ext vers */
+ {FT_BYTE, DT_DEFAULT, NULL, offsetof(file_head_rec, eol_chk1)},
+ {FT_BYTE, DT_DEFAULT, NULL, offsetof(file_head_rec, eol_chk2)},
+ {FT_BYTE, DT_DEFAULT, NULL, offsetof(file_head_rec, fallback_ext)},
+ {FT_BYTE, DT_DEFAULT, NULL, offsetof(file_head_rec, res2)},
+ {FT_UINT32, DT_DEFAULT, NULL, offsetof(file_head_rec, res1)},
+ endrec
+};
+
+static const char *block_name[AGX_NUMBLOCK] = {
+ "Index", "Game Info", "Room(DA2)", "Noun(DA3)", "Creature(DA4)",
+ "Command Header(DA5)", "Error Message(STD)", "Message",
+ "Question", "Answer", "User String", "Description Text(D$$)",
+ "Command Tokens(DA6)", "Static String", "Subroutine ID",
+ "Verb Synonym", "RoomPIX", "Global Noun", "Flag Noun", "Word Lists(Syntbl)",
+ "Dictionary Text", "Dictionary Index", "OPT",
+ "Picture Filename", "RoomPIX Filename", "Font Filename", "Sound Filename",
+ "Menu(VOC)", "Multi-word Verb", "Preposition", "ObjFlag", "ObjProp",
+ "Attrtable", "PropTable", "PropStr", "Itemized Variables",
+ "Itemized Flags"
+};
+
+
+/* Return 0 on failure, 1 on success */
+int read_agx(fc_type fc, rbool diag) {
+ file_head_rec filehead;
+ unsigned long fsize;
+ index_rec *index;
+ long i;
+ int index_recsize;
+ int index_start;
+
+ agx_file = 1;
+ fsize = buffopen(fc, fAGX, 16, NULL, 1);
+ if (fsize == 0) {
+ agx_file = 0;
+ return 0;
+ }
+
+ /* Read header */
+ read_recarray(&filehead, sizeof(file_head_rec), 1, fi_header,
+ "File Header", 0, compute_recsize(fi_header));
+ if (filehead.fileid != AGT_FILE_SIG) {
+ buffclose();
+ return 0;
+ }
+ if (DIAG) {
+ rprintf("AGX file format");
+ if (isprint(filehead.ver_own) && isprint(filehead.ext_own))
+ rprintf(" Version:%c%d\tExtension:%c%d\n",
+ filehead.ver_own, filehead.version,
+ filehead.ext_own, filehead.extnum);
+ else
+ rprintf(" Version:%d:%d\tExtension:%d:%d\n",
+ filehead.ver_own, filehead.version,
+ filehead.ext_own, filehead.extnum);
+ }
+ if (filehead.ver_own != 'R' || filehead.version > 2) {
+ rprintf("Unsupported AGX file version.\n");
+ rprintf(" Either the file is corrupted or or you need a more recent "
+ "version of AGiliTy.\n");
+ rprintf("\n");
+ fatal("Can't read AGX file.");
+ }
+
+ index_recsize = compute_recsize(fi_index);
+ if (filehead.version == 0) {
+ if (debug_da1)
+ rprintf("[AGX version 0: obsolete.]\n");
+ index_recsize += 8; /* Extra junk block in version 0. */
+ index_start = 8;
+ } else {
+ index_start = 16;
+ if (filehead.eol_chk1 != '\n' || filehead.eol_chk2 != '\r')
+ fatal("File apparently downloaded as non-binary file.");
+ }
+ if (filehead.ext_own != 'R'
+ || (filehead.version == 0 && filehead.extnum > 1)
+ || (filehead.version == 1 && filehead.extnum > 7)
+ || (filehead.version == 2 && filehead.extnum > 2))
+ agtwarn("Unrecognized extension to AGX file format.", 0);
+ if (filehead.ext_own != 'R') { /* Assume lowest common denomenator */
+ if (filehead.version < 2)
+ fatal("Extensions of AGX beta versions not supported.");
+ if (filehead.fallback_ext < 1) filehead.fallback_ext = 1;
+ }
+
+ /* Now read master index */
+ /* This assumes that the file is long enough to absorb any
+ 'extra' blocks we read in in early versions with fewer blocks. */
+ /* (Right now, this must be true: the next block alone is big enough) */
+ index = (index_rec *)read_recarray(NULL, sizeof(index_rec), AGX_NUMBLOCK,
+ fi_index, "File Index", index_start,
+ index_recsize * AGX_NUMBLOCK);
+
+ /* Zero index entries for any blocks that are beyond the bounds of the
+ file's index */
+ if (AGX_NUMBLOCK > index[0].numrec)
+ memset(index + index[0].numrec, 0,
+ (AGX_NUMBLOCK - index[0].numrec)*sizeof(index_rec));
+
+ if (DIAG) {
+ rprintf("\n");
+ rprintf("File Index:\n");
+ rprintf(" Offset Size NumRec RecSz\n");
+ rprintf(" ------ ------ ------ ------\n");
+ for (i = 0; i < AGX_NUMBLOCK; i++)
+ rprintf("%2d: %6d %6d %6d %6d %s\n", i,
+ index[i].file_offset, index[i].blocksize,
+ index[i].numrec, index[i].recsize, block_name[i]);
+ }
+ if ((int)index[0].file_offset != index_start)
+ fatal("File header corrupted.");
+
+ for (i = 0; i < AGX_NUMBLOCK; i++) { /* Error checking */
+#ifdef DEBUG_AGX
+ rprintf(" Verifying block %d...\n", i);
+#endif
+ if (index[i].recsize * index[i].numrec != index[i].blocksize)
+ fatal("File header corrupted.");
+ if (index[i].file_offset + index[i].blocksize > fsize)
+ fatal("File index points past end of file.");
+ }
+
+ /* Check for mandatory fields */
+ if (!index[0].numrec /* File index */
+ || !index[1].numrec /* Game header */
+ || !index[13].numrec /* Static string block */
+ || !index[15].numrec /* Synonym list */
+ || !index[19].numrec /* Syntbl */
+ || !index[20].numrec /* Dictionary text */
+ || !index[21].numrec /* Dictionary index */
+ )
+ fatal("AGX file missing mandatory block.");
+
+
+ read_globalrec(fi_gameinfo, "Game Info", index[1].file_offset,
+ index[1].blocksize);
+ if (filehead.version == 0 && filehead.extnum == 0) {
+ exitmsg_base = 1000;
+ if (aver >= AGT15)
+ box_title = 1;
+ }
+ if (index[1].blocksize == 83 && filehead.version == 1 && filehead.extnum >= 5) {
+ /* Detect 0.8-compatibility hack */
+ filehead.extnum = 2;
+ }
+ if (filehead.version == 0 || (filehead.version == 1 && filehead.extnum < 5)) {
+ if (aver >= AGT182 && aver <= AGT18MAX) {
+ if (filehead.extnum < 4) TWO_CYCLE = 1;
+ } else
+ PURE_AFTER = 1;
+ }
+
+ /* Need to read in ss_array before rooms/nouns/creatures */
+ ss_size = ss_end = index[13].numrec;
+ static_str = (char *)read_recblock(NULL, FT_CHAR,
+ index[13].numrec, index[13].file_offset,
+ index[13].blocksize);
+
+ synptr = syntbl_size = index[19].numrec;
+ syntbl = (word *)read_recblock(NULL, FT_WORD, index[19].numrec, index[19].file_offset,
+ index[19].blocksize);
+
+ maxroom = first_room + index[2].numrec - 1;
+ set_roomdesc(fi_room);
+ room = (room_rec *)read_recarray(NULL, sizeof(room_rec), index[2].numrec,
+ fi_room, "Room", index[2].file_offset, index[2].blocksize);
+
+ maxnoun = first_noun + index[3].numrec - 1;
+ set_noundesc(fi_noun);
+ noun = (noun_rec *)read_recarray(NULL, sizeof(noun_rec), index[3].numrec,
+ fi_noun, "Noun", index[3].file_offset, index[3].blocksize);
+
+ last_obj = maxcreat = first_creat + index[4].numrec - 1;
+ set_creatdesc(fi_creat);
+ creature = (creat_rec *)read_recarray(NULL, sizeof(creat_rec), index[4].numrec,
+ fi_creat, "Creature", index[4].file_offset,
+ index[4].blocksize);
+
+ last_cmd = index[5].numrec;
+ set_cmdptr(fi_cmdhead);
+ command = (cmd_rec *)read_recarray(NULL, sizeof(cmd_rec), index[5].numrec,
+ fi_cmdhead, "Metacommand", index[5].file_offset,
+ index[5].blocksize);
+ if (filehead.ext_own != 'R' && filehead.fallback_ext <= 1) {
+ for (i = 0; i < last_cmd; i++)
+ command[i].noun_obj = command[i].obj_obj = 0;
+ }
+
+ NUM_ERR = index[6].numrec;
+ err_ptr = (descr_ptr *)read_recarray(NULL, sizeof(descr_ptr), index[6].numrec,
+ fi_descptr, "Error Message", index[6].file_offset,
+ index[6].blocksize);
+
+ last_message = index[7].numrec;
+ msg_ptr = (descr_ptr *)read_recarray(NULL, sizeof(descr_ptr), index[7].numrec,
+ fi_descptr, "Message", index[7].file_offset,
+ index[7].blocksize);
+
+ MaxQuestion = index[8].numrec;
+ question = answer = NULL;
+ quest_ptr = (descr_ptr *)read_recarray(NULL, sizeof(descr_ptr), index[8].numrec,
+ fi_descptr, "Question", index[8].file_offset,
+ index[8].blocksize);
+ if (index[9].numrec != index[8].numrec)
+ fatal("File corrputed: questions and answers don't match.");
+ ans_ptr = (descr_ptr *)read_recarray(NULL, sizeof(descr_ptr), index[9].numrec,
+ fi_descptr, "Answer", index[9].file_offset,
+ index[9].blocksize);
+
+ MAX_USTR = index[10].numrec;
+ userstr = (tline *)read_recarray(NULL, sizeof(tline), index[10].numrec,
+ fi_tline, "User String", index[10].file_offset,
+ index[10].blocksize);
+
+ MAX_SUB = index[14].numrec;
+ sub_name = (word *)read_recblock(NULL, FT_WORD, index[14].numrec, index[14].file_offset,
+ index[14].blocksize);
+
+ if (index[16].numrec > MAX_PIX) {
+ index[16].numrec = MAX_PIX;
+ index[16].blocksize = index[16].recsize * index[16].numrec;
+ }
+ maxpix = index[16].numrec;
+ for (i = 0; i < MAX_PIX; i++) pix_name[i] = 0; /* In case there are less than
+ MAX_PIX names */
+ read_recblock(pix_name, FT_WORD, index[16].numrec, index[16].file_offset,
+ index[16].blocksize);
+
+ numglobal = index[17].numrec;
+ globalnoun = (word *)read_recblock(NULL, FT_WORD,
+ index[17].numrec, index[17].file_offset,
+ index[17].blocksize);
+
+ if (index[18].numrec > MAX_FLAG_NOUN) {
+ index[18].numrec = MAX_FLAG_NOUN;
+ index[18].blocksize = index[18].recsize * index[18].numrec;
+ }
+
+ for (i = 0; i < MAX_FLAG_NOUN; i++) flag_noun[i] = 0;
+ read_recblock(flag_noun, FT_WORD, index[18].numrec, index[18].file_offset,
+ index[18].blocksize);
+
+
+
+ DVERB = index[15].numrec - old_base_verb - MAX_SUB;
+ synlist = (slist *)read_recblock(NULL, FT_SLIST, index[15].numrec, index[15].file_offset,
+ index[15].blocksize);
+ correct_synlist();
+
+ num_comb = index[28].numrec;
+ comblist = (slist *)read_recblock(NULL, FT_SLIST, index[28].numrec, index[28].file_offset,
+ index[28].blocksize);
+
+ num_prep = index[29].numrec;
+ userprep = (slist *)read_recblock(NULL, FT_SLIST, index[29].numrec, index[29].file_offset,
+ index[29].blocksize);
+
+ /* dicstr must be read in before dict */
+ dictstrsize = dictstrptr = index[20].numrec;
+ dictstr = (char *)read_recblock(NULL, FT_CHAR, index[20].numrec, index[20].file_offset,
+ index[20].blocksize);
+
+ dp = index[21].numrec;
+ dict = (char **)read_recblock(NULL, FT_DICTPTR,
+ index[21].numrec, index[21].file_offset,
+ index[21].blocksize);
+
+ have_opt = (index[22].numrec != 0);
+ for (i = 0; i < 14; i++) opt_data[i] = 0;
+ if (have_opt) {
+ if (index[22].numrec > 14) index[22].numrec = 14;
+ read_recblock(opt_data, FT_BYTE, index[22].numrec, index[22].file_offset,
+ index[22].blocksize);
+ }
+
+ maxpict = index[23].numrec;
+ pictlist = (filename *)read_recblock(NULL, FT_STR, index[23].numrec, index[23].file_offset,
+ index[23].blocksize);
+ maxpix = index[24].numrec;
+ pixlist = (filename *)read_recblock(NULL, FT_STR, index[24].numrec, index[24].file_offset,
+ index[24].blocksize);
+ maxfont = index[25].numrec;
+ fontlist = (filename *)read_recblock(NULL, FT_STR, index[25].numrec, index[25].file_offset,
+ index[25].blocksize);
+ maxsong = index[26].numrec;
+ songlist = (filename *)read_recblock(NULL, FT_STR, index[26].numrec, index[26].file_offset,
+ index[26].blocksize);
+
+ vm_size = index[27].numrec;
+ verbinfo = (verbentry_rec *)read_recarray(NULL, sizeof(verbentry_rec), index[27].numrec,
+ fi_verbentry, "Menu Vocabulary", index[27].file_offset,
+ index[27].blocksize);
+
+ /* Check that objflag and objprop fields are of correct size */
+ if (index[30].numrec != (uint32)objextsize(0))
+ fatal("Object flag block not of the correct size.");
+
+ if (index[31].numrec != (uint32)objextsize(1))
+ fatal("Object property block not of the correct size.");
+
+ objflag = (uchar *)read_recblock(NULL, FT_BYTE, index[30].numrec, index[30].file_offset,
+ index[30].blocksize);
+ objprop = (long *)read_recblock(NULL, FT_INT32, index[31].numrec, index[31].file_offset,
+ index[31].blocksize);
+
+ oflag_cnt = index[32].numrec;
+ attrtable = (attrdef_rec *)read_recarray(NULL, sizeof(attrdef_rec), index[32].numrec,
+ fi_attrrec, "Object Flag Table",
+ index[32].file_offset,
+ index[32].blocksize);
+ /* Objflags are converted to internal form later, after
+ block 36 has been read in. */
+
+ oprop_cnt = index[33].numrec;
+ proptable = (propdef_rec *)read_recarray(NULL, sizeof(propdef_rec), index[33].numrec,
+ fi_proprec, "Object Property Table",
+ index[33].file_offset,
+ index[33].blocksize);
+
+ if (filehead.ext_own != 'R' && filehead.fallback_ext <= 1) {
+ /* Non-standard extension */
+// int i;
+ for (i = 0; i < oflag_cnt; i++) /* These are converted later */
+ attrtable[i].ystr = NULL;
+ attrtable[i].nstr = NULL;
+ for (i = 0; i < oprop_cnt; i++)
+ proptable[i].str_cnt = 0;
+ propstr_size = 0;
+ propstr = NULL;
+ vartable = NULL;
+ flagtable = NULL;
+ } else { /* Normal case */
+ propstr_size = index[34].numrec;
+ propstr = (const char **)read_recblock(NULL, FT_STR, index[34].numrec,
+ index[34].file_offset, index[34].blocksize);
+
+ if (index[35].numrec && index[35].numrec != (uint32)VAR_NUM + 1)
+ fatal("AGX file corrupted: variable itemization table size mismatch.");
+ vartable = (vardef_rec *)read_recarray(NULL, sizeof(vardef_rec), index[35].numrec,
+ fi_varrec, "Variable Itemization Table",
+ index[35].file_offset,
+ index[35].blocksize);
+
+ if (index[36].numrec && index[36].numrec != (uint32)FLAG_NUM + 1)
+ fatal("AGX file corrupted: flag itemization table size mismatch.");
+ flagtable = (flagdef_rec *)read_recarray(NULL, sizeof(flagdef_rec), index[36].numrec,
+ fi_flagrec, "Flag Itemization Table",
+ index[36].file_offset,
+ index[36].blocksize);
+ }
+
+ fix_objflag_str(1); /* Convert flags and objflags to internal form */
+
+
+ /* Block 12: Command text */
+ read_command(index[12].numrec, index[12].file_offset, diag);
+
+ /* Block 11 is description block; it doesn't get read in by
+ agxread() but during play */
+ if ((long)index[11].blocksize <= descr_maxmem) {
+ /* ... if we decided to load descriptions into memory */
+ mem_descr = (char *)read_recblock(NULL, FT_CHAR, index[11].numrec,
+ index[11].file_offset,
+ index[11].blocksize);
+ buffclose(); /* Don't need to keep it open */
+ descr_ofs = -1;
+ } else {
+ descr_ofs = index[11].file_offset;
+ mem_descr = NULL;
+ }
+ reinit_dict();
+ return 1;
+}
+
+
+
+
+/* ------------------------------------------------------------- */
+/* AGX Writing Code */
+/* ------------------------------------------------------------- */
+
+static index_rec *gindex;
+
+
+/* This patches the block descriptions to create AGiliTy-0.8
+ compatible files. This is just a quick hack to solve a short-term
+ problem. */
+void patch_08(void) {
+ set_endrec(fi_gameinfo, 48); /* Should give size of 83 */
+ set_endrec(fi_noun, 45);
+ set_endrec(fi_creat, 23);
+}
+
+
+/* This writes the file header; it needs to be called near the
+ end */
+void write_header(void) {
+ int i;
+ rbool simple;
+ file_head_rec filehead;
+
+ filehead.fileid = AGT_FILE_SIG;
+ filehead.ver_own = filehead.ext_own = 'R';
+ /* The following will be converted to 1-7 if advanced features aren't
+ being used. */
+ filehead.version = 2;
+ filehead.extnum = 2;
+ filehead.fallback_ext = 2; /* 'R' extension to fall back to;
+ only meaningful if ext_own is *not* 'R' */
+ filehead.eol_chk1 = '\n';
+ filehead.eol_chk2 = '\r';
+ filehead.res1 = 0;
+ filehead.res2 = 0;
+
+ /* This automatically patches the block descriptions to create
+ pre-AGiliTy-0.8.8 compatible files. If it can't (because the
+ file uses 0.8.8+ features) then it leaves the version at 2;
+ otherwise the version is reduced to 1. */
+ /* The files thus created are actually hybrid files-- they
+ have some 0.8.8+ features, just not the ones that might
+ break pre-0.8.8 interpreters. */
+ simple = 1;
+ for (i = 30; i < AGX_NUMBLOCK; i++)
+ if (gindex[i].numrec != 0) simple = 0;
+ if (simple) {
+ gindex[0].numrec = 30; /* 0.8.7 compatibility */
+ gindex[0].blocksize = gindex[0].recsize * gindex[0].numrec;
+ filehead.version = 1;
+ filehead.extnum = 7;
+ }
+ write_recarray(&filehead, sizeof(file_head_rec), 1, fi_header, 0);
+}
+
+
+static void agx_compute_index(void)
+/* This computes the blocksize and offset values for all blocks */
+{
+ int i;
+
+ for (i = 0; i < AGX_NUMBLOCK; i++)
+ gindex[i].blocksize = gindex[i].recsize * gindex[i].numrec;
+ gindex[0].file_offset = 16;
+ gindex[11].file_offset = gindex[0].file_offset + gindex[0].blocksize;
+ gindex[12].file_offset = gindex[11].file_offset + gindex[11].blocksize;
+ gindex[1].file_offset = gindex[12].file_offset + gindex[12].blocksize;
+ for (i = 2; i <= AGX_NUMBLOCK - 1; i++)
+ if (i == 13)
+ gindex[13].file_offset = gindex[10].file_offset + gindex[10].blocksize;
+ else if (i != 11 && i != 12)
+ gindex[i].file_offset = gindex[i - 1].file_offset + gindex[i - 1].blocksize;
+}
+
+
+/* Create the preliminary gindex for the new file and set it up so we can
+ write descriptions to the new file */
+void agx_create(fc_type fc) {
+ int i;
+
+ bw_open(fc, fAGX);
+ gindex = (index_rec *)rmalloc(sizeof(index_rec) * AGX_NUMBLOCK);
+
+ gindex[0].numrec = AGX_NUMBLOCK;
+ for (i = 1; i < AGX_NUMBLOCK; i++) /* Initialize the rest to 0 */
+ gindex[i].numrec = 0;
+
+ /* This writes random data to the file; their only purpose
+ is to prevent problems with seeking beyond the end of file */
+ write_recarray(NULL, sizeof(file_head_rec), 1, fi_header, 0);
+ write_recarray(NULL, sizeof(index_rec), AGX_NUMBLOCK, fi_index, 16);
+
+ old_base_verb = BASE_VERB; /* This will be constant for any given version
+ of the interpreter, but may change across
+ versions of the interpreter */
+ /* Set record sizes */
+ gindex[0].recsize = compute_recsize(fi_index);
+ gindex[1].recsize = compute_recsize(fi_gameinfo);
+ gindex[2].recsize = compute_recsize(fi_room);
+ gindex[3].recsize = compute_recsize(fi_noun);
+ gindex[4].recsize = compute_recsize(fi_creat);
+ gindex[5].recsize = compute_recsize(fi_cmdhead);
+ gindex[6].recsize = gindex[7].recsize = gindex[8].recsize =
+ gindex[9].recsize = compute_recsize(fi_descptr);
+ gindex[10].recsize = ft_leng[FT_TLINE];
+ gindex[11].recsize = ft_leng[FT_CHAR];
+ gindex[12].recsize = ft_leng[FT_INT16];
+ gindex[13].recsize = gindex[20].recsize = ft_leng[FT_CHAR];
+ gindex[14].recsize = gindex[16].recsize = gindex[17].recsize =
+ gindex[18].recsize = ft_leng[FT_WORD];
+ gindex[15].recsize = ft_leng[FT_SLIST];
+ gindex[19].recsize = ft_leng[FT_WORD];
+ gindex[21].recsize = ft_leng[FT_DICTPTR];
+ gindex[22].recsize = ft_leng[FT_BYTE];
+ gindex[23].recsize = gindex[24].recsize = gindex[25].recsize =
+ gindex[26].recsize = ft_leng[FT_STR];
+ gindex[27].recsize = compute_recsize(fi_verbentry);
+ gindex[28].recsize = ft_leng[FT_SLIST];
+ gindex[29].recsize = ft_leng[FT_SLIST];
+ gindex[30].recsize = ft_leng[FT_BYTE];
+ gindex[31].recsize = ft_leng[FT_INT32];
+ gindex[32].recsize = compute_recsize(fi_attrrec);
+ gindex[33].recsize = compute_recsize(fi_proprec);
+ gindex[34].recsize = ft_leng[FT_STR];
+ gindex[35].recsize = compute_recsize(fi_varrec);
+ gindex[36].recsize = compute_recsize(fi_flagrec);
+
+ agx_compute_index(); /* Only the first 10 blocks will be correct */
+ /* The important thing is to get the offset of block 11, the desciption
+ text block, so we can write to it. */
+ /* Block 11 is the description block; it doesn't get written by agxwrite()
+ but by its own routines. */
+}
+
+
+static void agx_finish_index(void) {
+ /* Still have 11, 27-29 */
+ /* Block 12 is taken care of elsewhere (in write_command) */
+
+ gindex[1].numrec = 1;
+ gindex[2].numrec = rangefix(maxroom - first_room + 1);
+ gindex[3].numrec = rangefix(maxnoun - first_noun + 1);
+ gindex[4].numrec = rangefix(maxcreat - first_creat + 1);
+ gindex[5].numrec = last_cmd;
+ gindex[6].numrec = NUM_ERR;
+ gindex[7].numrec = last_message;
+ gindex[8].numrec = gindex[9].numrec = MaxQuestion;
+ if (userstr != NULL)
+ gindex[10].numrec = MAX_USTR;
+ else gindex[10].numrec = 0;
+ gindex[13].numrec = ss_end;
+ gindex[14].numrec = MAX_SUB;
+ gindex[15].numrec = TOTAL_VERB;
+ gindex[16].numrec = maxpix;
+ gindex[17].numrec = numglobal;
+ gindex[19].numrec = synptr;
+ gindex[20].numrec = dictstrptr;
+ gindex[21].numrec = dp;
+ gindex[23].numrec = maxpict;
+ gindex[24].numrec = maxpix;
+ gindex[25].numrec = maxfont;
+ gindex[26].numrec = maxsong;
+ gindex[27].numrec = vm_size;
+ gindex[28].numrec = num_comb;
+ gindex[29].numrec = num_prep;
+ gindex[30].numrec = objextsize(0);
+ gindex[31].numrec = objextsize(1);
+ gindex[32].numrec = oflag_cnt;
+ gindex[33].numrec = oprop_cnt;
+ gindex[34].numrec = propstr_size;
+ gindex[35].numrec = (vartable ? VAR_NUM + 1 : 0);
+ gindex[36].numrec = (flagtable ? FLAG_NUM + 1 : 0);
+
+ /* These may also be zero (?) */
+ gindex[22].numrec = have_opt ? 14 : 0;
+ gindex[18].numrec = MAX_FLAG_NOUN;
+
+ agx_compute_index(); /* This time it will be complete except for
+ the VOC-TTL-INS blocks at the end */
+}
+
+
+
+/* The following routine writes a description to disk,
+ and stores the size and length in dp */
+void write_descr(descr_ptr *dp_, descr_line *txt) {
+ long i;
+ long size;
+ char *buff, *buffptr, *src;
+
+ size = 0;
+ if (txt == NULL) {
+ dp_->start = 0;
+ dp_->size = 0;
+ return;
+ }
+
+ for (i = 0; txt[i] != NULL; i++) /* Compute size */
+ size += strlen(txt[i]) + 1; /* Remember trailing \0 */
+ buff = (char *)rmalloc(sizeof(char) * size);
+
+ buffptr = buff;
+ for (i = 0; txt[i] != NULL; i++) {
+ for (src = txt[i]; *src != 0; src++, buffptr++)
+ *buffptr = *src;
+ *buffptr++ = 0;
+ }
+ dp_->start = gindex[11].numrec;
+ dp_->size = size;
+ gindex[11].numrec +=
+ write_recblock(buff, FT_CHAR, size,
+ gindex[11].file_offset + gindex[11].numrec);
+ rfree(buff);
+}
+
+/* Write command text to file and return number of bytes written. */
+static long write_command(long cmdofs) {
+ long i, cnt;
+
+ cmd_ptr = (long *)rmalloc(sizeof(long) * last_cmd);
+ cnt = 0;
+ for (i = 0; i < last_cmd; i++) {
+ cmd_ptr[i] = cnt;
+ write_recblock(command[i].data, FT_INT16, command[i].cmdsize,
+ cmdofs + 2 * cnt);
+ cnt += command[i].cmdsize;
+ }
+ return cnt;
+}
+
+
+
+
+/* Write the bulk of the AGX file. This requires that the descriptions,
+ etc. have already been written */
+void agx_write(void) {
+ gindex[11].blocksize = gindex[11].numrec * gindex[11].recsize;
+ gindex[12].file_offset = gindex[11].file_offset + gindex[11].blocksize;
+
+ gindex[12].numrec = write_command(gindex[12].file_offset);
+
+ agx_finish_index();
+
+ /* Need to write these blocks in order */
+
+ write_globalrec(fi_gameinfo, gindex[1].file_offset);
+
+ wset_roomdesc(fi_room);
+ write_recarray(room, sizeof(room_rec), gindex[2].numrec,
+ fi_room, gindex[2].file_offset);
+
+ wset_noundesc(fi_noun);
+ write_recarray(noun, sizeof(noun_rec), gindex[3].numrec,
+ fi_noun, gindex[3].file_offset);
+
+ wset_creatdesc(fi_creat);
+ write_recarray(creature, sizeof(creat_rec), gindex[4].numrec,
+ fi_creat, gindex[4].file_offset);
+
+ wset_cmdptr(fi_cmdhead);
+ write_recarray(command, sizeof(cmd_rec), gindex[5].numrec,
+ fi_cmdhead, gindex[5].file_offset);
+
+ write_recarray(err_ptr, sizeof(descr_ptr), gindex[6].numrec,
+ fi_descptr, gindex[6].file_offset);
+ write_recarray(msg_ptr, sizeof(descr_ptr), gindex[7].numrec,
+ fi_descptr, gindex[7].file_offset);
+ write_recarray(quest_ptr, sizeof(descr_ptr), gindex[8].numrec,
+ fi_descptr, gindex[8].file_offset);
+ write_recarray(ans_ptr, sizeof(descr_ptr), gindex[9].numrec,
+ fi_descptr, gindex[9].file_offset);
+
+ if (userstr != NULL)
+ write_recarray(userstr, sizeof(tline), gindex[10].numrec,
+ fi_tline, gindex[10].file_offset);
+
+ write_recblock(static_str, FT_CHAR,
+ gindex[13].numrec, gindex[13].file_offset);
+
+ write_recblock(sub_name, FT_WORD, gindex[14].numrec, gindex[14].file_offset);
+ write_recblock(synlist, FT_SLIST, gindex[15].numrec, gindex[15].file_offset);
+ write_recblock(pix_name, FT_WORD, gindex[16].numrec, gindex[16].file_offset);
+ write_recblock(globalnoun, FT_WORD, gindex[17].numrec, gindex[17].file_offset);
+ write_recblock(flag_noun, FT_WORD, gindex[18].numrec, gindex[18].file_offset);
+ write_recblock(syntbl, FT_WORD, gindex[19].numrec, gindex[19].file_offset);
+ write_recblock(dictstr, FT_CHAR, gindex[20].numrec, gindex[20].file_offset);
+ write_recblock(dict, FT_DICTPTR, gindex[21].numrec, gindex[21].file_offset);
+ if (have_opt)
+ write_recblock(opt_data, FT_BYTE, gindex[22].numrec, gindex[22].file_offset);
+
+ write_recblock(pictlist, FT_STR, gindex[23].numrec, gindex[23].file_offset);
+ write_recblock(pixlist, FT_STR, gindex[24].numrec, gindex[24].file_offset);
+ write_recblock(fontlist, FT_STR, gindex[25].numrec, gindex[25].file_offset);
+ write_recblock(songlist, FT_STR, gindex[26].numrec, gindex[26].file_offset);
+
+ write_recarray(verbinfo, sizeof(verbentry_rec), gindex[27].numrec,
+ fi_verbentry, gindex[27].file_offset);
+ write_recblock(comblist, FT_SLIST, gindex[28].numrec, gindex[28].file_offset);
+ write_recblock(userprep, FT_SLIST, gindex[29].numrec, gindex[29].file_offset);
+ write_recblock(objflag, FT_BYTE, gindex[30].numrec, gindex[30].file_offset);
+ write_recblock(objprop, FT_INT32, gindex[31].numrec, gindex[31].file_offset);
+ fix_objflag_str(0); /* Convert to external form */
+ write_recarray(attrtable, sizeof(attrdef_rec),
+ gindex[32].numrec, fi_attrrec, gindex[32].file_offset);
+ write_recarray(proptable, sizeof(propdef_rec),
+ gindex[33].numrec, fi_proprec, gindex[33].file_offset);
+ write_recblock(propstr, FT_STR, gindex[34].numrec, gindex[34].file_offset);
+ write_recarray(vartable, sizeof(vardef_rec),
+ gindex[35].numrec, fi_varrec, gindex[35].file_offset);
+ write_recarray(flagtable, sizeof(flagdef_rec),
+ gindex[36].numrec, fi_flagrec, gindex[36].file_offset);
+ fix_objflag_str(1); /* Restore to internal form */
+}
+
+
+/* Write header and master gindex and then close AGX file */
+void agx_wclose(void) {
+ write_header();
+ write_recarray(gindex, sizeof(index_rec), AGX_NUMBLOCK, fi_index, 16);
+ bw_close();
+ rfree(gindex);
+}
+
+
+void agx_wabort(void) {
+ bw_abort();
+ rfree(gindex);
+}
+
+} // End of namespace AGT
+} // End of namespace Glk
diff --git a/engines/glk/agt/auxfile.cpp b/engines/glk/agt/auxfile.cpp
new file mode 100644
index 0000000000..bfc97282e3
--- /dev/null
+++ b/engines/glk/agt/auxfile.cpp
@@ -0,0 +1,596 @@
+/* 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 "common/str.h"
+
+namespace Glk {
+namespace AGT {
+
+/* ------------------------------------------------------------------- */
+/* Purity flag initialization */
+/* Logically, these belong in agtdata.c, but I wanted to keep them */
+/* near the CFG reading routines. */
+/* ------------------------------------------------------------------- */
+/* The following are AGT 'purity' flags; they turn off features of */
+/* my interpreter that are not fully consistent with the original AGT */
+/* and so could break some games. Some of these are trivial improvements; */
+/* some are more radical and should be used with caution. Several are */
+/* only useful if a game was designed with them in mind. */
+/* In all cases, setting the flag to 1 more closely follows the */
+/* behavior of the original interpreters */
+/* WARNING: Many of these haven't been tested extenstivly in the non-default
+ state. */
+
+
+rbool PURE_ANSWER = 0; /* For ME questions, requires that AND-separated
+ answers be in the same order in the player's
+ answer as they are in the game file. According
+ to the AGT documentation, AND should ignore
+ the order, but the original AGT interpreters
+ (at least the one I've tested) don't conform
+ to this. */
+
+rbool PURE_TIME = 1; /* Set to 0 causes time to always be increased
+ by delta_time rather than by a random amount
+ between 0 and delta_time. Only really of any use
+ to a game author who wanted to write a game
+ explicitly for AGiliTy. */
+
+/* rbool PURE_BOLD=1; Set to 0 causes the backslash to toggle bold on and
+ off for all versions of AGT, not just 1.8x.
+ I can think of no reason to do this unless
+ you are an AGT author who wants to use the 1.8x
+ bold feature with the Master's Edition compiler. */
+
+rbool PURE_AND = 1; /* increment the turn counter for each noun in a
+ chain of <noun> AND <noun> AND ... If 0, the turn
+ counter will only be incremented by one in such a case.
+ (need to do something about metacommands, as well...) */
+
+rbool PURE_METAVERB = 1; /* If set, ANY and AFTER commands are run even
+ if you type in a metaverb (SAVE, RESTORE,...
+ that is, any verb that doesn't cause time to
+ pass). Verb specific metacommands are _always_
+ run. */
+
+rbool PURE_ROOMTITLE = 1; /* If 0, the interpreter will print out room
+ names before room descriptions even for
+ pre-ME games */
+
+rbool PURE_SYN = 0; /* Treats synonyms as nouns when parsing: that is, they
+ must show up only as the last word and they have the
+ same priority as noun matches during disambiguation.
+ If this is 0, then synonyms can appear anywhere in
+ the name the player types in but are still
+ disambiguated as nouns. */
+
+rbool PURE_NOUN = 0; /* _Requires_ a noun to end a word. This is only
+ imperfectly supported: if there are no other
+ possible matches the parser will take the adjective-
+ only one anyhow. Frankly, I can't think of any reason
+ to set this to 1, but it's included for completeness
+ sake (and for any AGT Purists out there :-) ) */
+
+rbool PURE_ADJ = 1; /* Picks noun/syn-matches over pure adj matches
+ when disambiguating. This is redundant if PURE_NOUN=1
+ since in that case pure adjective matches will
+ be rejected anyhow. */
+
+rbool PURE_DUMMY = 0; /* If set, the player can running dummy verbs
+ in the game by typing 'dummy_verb3'; otherwise,
+ this will produce an error message */
+
+rbool PURE_SUBNAME = 0; /* If set, the player can run subroutines from
+ the parse line by typing (e.g.) 'subroutine4'
+ (yes, the original AGT interpreters actually
+ allow this). If cleared, this cheat isn't
+ available */
+rbool PURE_PROSUB = 0; /* If clear, then $you$ substitutions are done
+ everywhere $$ substitutions are, even in
+ messages written by the game author.
+ If set, these substitutions are only made
+ in internal game messages */
+
+rbool PURE_HOSTILE = 1; /* =0 Will allow you to leave a room with a hostile
+ creature if you go back the way you came */
+rbool PURE_ALL = 1; /* =0 will cause the parser to expand ALL */
+rbool PURE_DISAMBIG = 1; /* =0 will cause intelligent disambiguation */
+rbool PURE_GETHOSTILE = 1; /* =0 will prevent the player from picking things
+ up in a room with a hostile creature */
+
+rbool PURE_OBJ_DESC = 1; /* =0 prevents [providing light] messages
+ from being shown */
+
+rbool PURE_ERROR = 0; /* =1 means no GAME ERROR messages will be printed
+ out */
+
+rbool PURE_SIZE = 1; /* =0 eliminates size/weight limits on how many
+ things the player can wear or carry. (But it's
+ still impossible to pick things up that are
+ in themselves larger than the player's capacity) */
+
+rbool PURE_GRAMMAR = 1; /* =0 prints error messages if the player uses a
+ built in verb with an extra object.
+ (e.g. YELL CHAIR). Otherwise, the extra object
+ will just be ignored. */
+
+rbool PURE_SYSMSG = 1; /* =0 causes AGiliTy to always use the default
+ messages even if the game file has its own
+ standard error messages. */
+
+rbool PURE_AFTER = 1; /* =0 causes LOOK and other end-of-turn events
+ to happen *before* AFTER commands run. */
+
+rbool PURE_PROPER = 1; /* Don't automatically treat creatures as proper nouns */
+
+rbool TWO_CYCLE = 0; /* AGT 1.83-style two-cycle metacommand execution. */
+rbool FORCE_VERSION = 0; /* Load even if the version is wrong. */
+
+
+/*-------------------------------------------------------------------------*/
+/* .CFG reading routines */
+/*-------------------------------------------------------------------------*/
+
+/* The main interpreter handles configuration in this order:
+ 1) Global configuration file
+ 2) First pass through game specific CFG to get the settings for
+ SLASH_BOLD and IBM_CHAR which we need to know _before_ reading
+ in the game.
+ 3) Read in the game.
+ 4) Main pass through game specific CFG. Doing it here ensures that
+ its settings will override those in the gamefile.
+ Secondary programs (such as agt2agx) usually only call this once, for
+ the game specific configuration file.
+ */
+
+#define opt(s) (strcasecmp(optstr[0],s)==0)
+
+static void cfg_option(int optnum, char *optstr[], rbool lastpass)
+/* This is passed each of the options; it is responsible for parsing
+ them or passing them on to the platform-specific option handler
+ agt_option() */
+/* lastpass is set if it is the last pass through this configuration
+ file; it is false only on the first pass through the game specific
+ configuration file during the run of the main interpreter */
+{
+ rbool setflag;
+
+ if (optnum == 0 || optstr[0] == NULL) return;
+
+ if (strncasecmp(optstr[0], "no_", 3) == 0) {
+ optstr[0] += 3;
+ setflag = 0;
+ } else setflag = 1;
+
+ if (opt("slash_bold")) bold_mode = setflag;
+ else if (!lastpass) {
+ /* On the first pass, we ignore all but a few options */
+ agil_option(optnum, optstr, setflag, lastpass);
+ return;
+ } else if (opt("irun")) irun_mode = setflag;
+ else if (opt("block_hostile")) PURE_HOSTILE = setflag;
+ else if (opt("get_hostile")) PURE_GETHOSTILE = setflag;
+ else if (opt("debug")) {
+ if (!agx_file && aver <= AGTME10) debug_mode = setflag;
+ if (setflag == 0) debug_mode = 0; /* Can always turn debugging support off */
+ } else if (opt("pure_answer")) PURE_ANSWER = setflag;
+ else if (opt("const_time")) PURE_TIME = !setflag;
+ else if (opt("fix_multinoun")) PURE_AND = !setflag;
+ else if (opt("fix_metaverb")) PURE_METAVERB = !setflag;
+ else if (opt("roomtitle")) PURE_ROOMTITLE = !setflag;
+ else if (opt("pure_synonym")) PURE_SYN = setflag;
+ else if (opt("adj_noun")) PURE_ADJ = !setflag;
+ else if (opt("pure_dummy")) PURE_DUMMY = setflag;
+ else if (opt("pure_subroutine")) PURE_SUBNAME = setflag;
+ else if (opt("pronoun_subs")) PURE_PROSUB = !setflag;
+ else if (opt("verbose")) verboseflag = setflag;
+ else if (opt("fixed_font")) font_status = 1 + !setflag;
+ else if (opt("alt_any")) mars_fix = setflag;
+ else if (opt("smart_disambig")) PURE_DISAMBIG = !setflag;
+ else if (opt("expand_all")) PURE_ALL = !setflag;
+ else if (opt("object_notes")) PURE_OBJ_DESC = setflag;
+ else if (opt("error")) PURE_ERROR = !setflag;
+ else if (opt("ignore_size")) PURE_SIZE = !setflag;
+ else if (opt("check_grammar")) PURE_GRAMMAR = !setflag;
+ else if (opt("default_errors")) PURE_SYSMSG = !setflag;
+ else if (opt("pure_after")) PURE_AFTER = !setflag;
+ else if (opt("proper_creature")) PURE_PROPER = !setflag;
+ else agil_option(optnum, optstr, setflag, lastpass);
+}
+
+#undef opt
+
+/* Returns false if it there are too many tokens on the line */
+rbool parse_config_line(char *buff, rbool lastpass) {
+ char *opt[50], *p;
+ int optc;
+
+ optc = 0;
+ opt[0] = NULL;
+ for (p = buff; *p; p++) {
+ if (isspace(*p)) { /* Whitespace */
+ if (opt[optc] != NULL) { /*... which means this is the first whitespace */
+ if (optc == 50) return 0; /* Too many */
+ opt[++optc] = NULL;
+ }
+ *p = 0;
+ } else /* No whitespace */
+ if (opt[optc] == NULL) /* ...this is the first non-whitespace */
+ opt[optc] = p;
+ }
+ if (opt[optc] != NULL) opt[++optc] = NULL;
+ cfg_option(optc, opt, lastpass);
+ return 1;
+}
+
+
+/* For the meaning of lastpass, see comments to cfg_option() above */
+void read_config(genfile cfgfile, rbool lastpass) {
+ char buff[100];
+
+ if (!filevalid(cfgfile, fCFG)) return;
+
+ while (readln(cfgfile, buff, 99)) {
+ if (buff[0] == '#') continue; /* Comments */
+ /* Now we parse the line into words, with opt[] pointing at the words
+ and optc counting how many there are. */
+ if (!parse_config_line(buff, lastpass))
+ rprintf("Too many tokens on configuration line.\n");
+ }
+ readclose(cfgfile);
+}
+
+
+
+/*-------------------------------------------------------------------------*/
+/* Read OPT file */
+/* (most of these routines used to be in agil.c) */
+/*-------------------------------------------------------------------------*/
+
+/* .OPT reading routines */
+/* I've put the comments on the format here because they don't really
+ belong anywhere else. (Maybe in agility.h, but I don't want to further
+ clutter that already quite cluttered file with something as peripheral
+ as this) */
+/* OPT file format: the .OPT file consists of 14 bytes. They are:
+ 0 Screen size(0=43/50 rows, 1=25 rows)
+ 1 Status line(1=top, 0=none, -1=bottom)
+ 2 Unknown, always seems to be 0
+ 3 Put box around status line?
+ 4 Sound on?
+ 5 Menus on?
+ 6 Fixed input line?
+ 7 Print transcript?
+ 8 Height of menus (3, 4, 5, 6, 7, or 8)
+ 9 Unknown, always seems to be 0
+ 10-13 Color scheme: output/status/input/menu, specified in DOS attribute
+ format (Bbbbffff, B=blink, b=backround, f=foreground,
+ MSB of foreground specifies intensity ("bold") ). */
+/* The interpreter ignores almost all of this. */
+
+void read_opt(fc_type fc) {
+ const char *errstr;
+ genfile optfile;
+
+ have_opt = 0;
+ optfile = openbin(fc, fOPT, NULL, 0);
+ if (filevalid(optfile, fOPT)) {
+ if (!binread(optfile, opt_data, 14, 1, &errstr))
+ fatal("Invalid OPT file.");
+ have_opt = 1;
+ readclose(optfile);
+ }
+}
+
+
+/*-------------------------------------------------------------------------*/
+/* Read and process TTL */
+/* (most of these routines used to be in agil.c) */
+/*-------------------------------------------------------------------------*/
+
+/* Shades of Gray uses a custom interpreter that prints out the names
+ of the authors as the program loads. */
+/* Normally I wouldn't bother with this, but Shades of Gray is probably
+ the best known of all AGT games */
+
+#define SOGCREDIT 7
+static const char *sogauthor[SOGCREDIT] = {
+ "Mark \"Sam\" Baker",
+ "Steve \"Aaargh\" Bauman",
+ "Belisana \"The\" Magnificent",
+ "Mike \"of Locksley\" Laskey",
+ "Judith \"Teela Brown\" Pintar",
+ "Hercules \"The Loyal\" SysOp",
+ "Cindy \"Nearly Amelia\" Yans"
+};
+
+static rbool check_dollar(char *s)
+/* Determines if s consists of an empty string with a single dollar sign
+ and possibly whitespace */
+{
+ rbool dfound;
+ dfound = 0;
+ for (; *s != 0; s++)
+ if (*s == '$' && !dfound) dfound = 1;
+ else if (!rspace(*s)) return 0;
+ return dfound;
+}
+
+descr_line *read_ttl(fc_type fc) {
+ genfile ttlfile;
+ int i, j, height;
+ descr_line *buff;
+
+ ttlfile = openfile(fc, fTTL, NULL, 0);
+ /* "Warning: Could not open title file '%s'." */
+ if (!filevalid(ttlfile, fTTL)) return NULL;
+ build_fixchar();
+
+ buff = (descr_line *)rmalloc(sizeof(descr_line));
+ i = 0;
+ while (NULL != (buff[i] = readln(ttlfile, NULL, 0))) {
+ if (strncmp(buff[i], "END OF FILE", 11) == 0) break;
+ else if (aver >= AGT18 && aver <= AGT18MAX && check_dollar(buff[i]))
+ statusmode = 4;
+ else {
+ for (j = 0; buff[i][j] != 0; j++)
+ buff[i][j] = fixchar[(uchar)buff[i][j]];
+ /* Advance i and set the next pointer to NULL */
+ buff = (descr_line *)rrealloc(buff, sizeof(descr_line) * (++i + 1));
+ buff[i] = NULL;
+ }
+ rfree(buff[i]);
+ }
+ readclose(ttlfile);
+
+ rfree(buff[i]);
+ while (buff[i] == NULL || strlen(buff[i]) <= 1) { /* Discard 'empty' lines */
+ if (i == 0) break;
+ rfree(buff[i]);
+ i--;
+ }
+ height = i;
+
+ if (aver == AGTCOS && ver == 4 && height >= 17) /* SOGGY */
+ for (i = 0; i < SOGCREDIT; i++)
+ if (strlen(sogauthor[i]) + 9 + i < strlen(buff[i + 7]))
+ memcpy(buff[i + 7] + 9 + i, sogauthor[i], strlen(sogauthor[i]));
+
+ return buff;
+}
+
+void free_ttl(descr_line *title) {
+ int i;
+ if (title == NULL) return;
+ for (i = 0; title[i] != NULL; i++)
+ rfree(title[i]);
+ rfree(title);
+}
+
+
+/*-------------------------------------------------------------------------*/
+/* Read and convert VOC */
+/* (most of these routines used to be in agil.c) */
+/*-------------------------------------------------------------------------*/
+
+
+static const char *newvoc[] = { "1 Menu", "1 Restart", "1 Undo" };
+static int newindex = 0; /* Points into newvoc */
+
+void add_verbrec(const char *verb_line, rbool addnew) {
+ char s[3];
+ Common::String verbStr(verb_line);
+
+ while (!verbStr.empty() && rspace(verbStr.firstChar()))
+ verbStr.deleteChar(0);
+
+ if (verbStr.empty() || verbStr.hasPrefix("!"))
+ return; /* Comment or empty line */
+
+ /* The following guarentees automatic initialization of the verbrec structures */
+ if (!addnew)
+ while (newindex < 3 && strcasecmp(verbStr.c_str() + 2, newvoc[newindex] + 2) > 0)
+ add_verbrec(newvoc[newindex++], 1);
+
+ verbinfo = (verbentry_rec *)rrealloc(verbinfo, (vm_size + 1) * sizeof(verbentry_rec));
+
+ s[0] = verbStr.firstChar();
+ s[1] = 0;
+ verbinfo[vm_size].objnum = strtol(s, NULL, 10) - 1;
+
+ verbStr.deleteChar(0);
+ verbStr.deleteChar(0);
+
+ verbinfo[vm_size].verb = verbinfo[vm_size].prep = 0;
+
+ uint idx = 0;
+ while (idx < verbStr.size()) {
+ while (idx < verbStr.size() && !rspace(verbStr[idx]))
+ ++idx;
+ if (idx < verbStr.size()) {
+ verbStr.setChar('\0', idx);
+ ++idx;
+ }
+
+ verbinfo[vm_size].verb = search_dict(verbStr.c_str());
+ if (verbinfo[vm_size].verb == -1) {
+ verbinfo[vm_size].verb = 0;
+ return;
+ }
+ if (idx < verbStr.size()) {
+ verbinfo[vm_size].prep = search_dict(verbStr.c_str() + idx);
+ if (verbinfo[vm_size].prep == -1)
+ verbinfo[vm_size].prep = 0;
+ }
+ }
+
+ vm_size++;
+}
+
+void init_verbrec(void)
+/* Need to insert special verbs into verbinfo */
+/* Fill in vnum field */
+/* UNDO, RESTART, MENU */
+{
+ verbinfo = NULL;
+ vm_size = 0;
+ newindex = 0;
+ if (freeze_mode) newindex = 1; /* Don't include MENU option if we can't
+ use it. */
+}
+
+void finish_verbrec(void) {
+ for (; newindex < 3; newindex++) add_verbrec(newvoc[newindex], 1);
+}
+
+
+void read_voc(fc_type fc) {
+ char linbuf[80];
+ genfile vocfile;
+
+ init_verbrec();
+ vocfile = openfile(fc, fVOC, NULL, 0);
+ if (filevalid(vocfile, fVOC)) { /* Vocabulary file exists */
+ while (readln(vocfile, linbuf, 79))
+ add_verbrec(linbuf, 0);
+ readclose(vocfile);
+ finish_verbrec();
+ }
+}
+
+
+
+
+/*-------------------------------------------------------------------------*/
+/* Read INS file */
+/* (most of these routines used to be in agil.c) */
+/*-------------------------------------------------------------------------*/
+
+
+static genfile insfile = BAD_TEXTFILE;
+static char *ins_buff;
+
+static descr_line *ins_descr = NULL;
+static int ins_line; /* Current instruction line */
+
+
+/* Return 1 on success, 0 on failure */
+rbool open_ins_file(fc_type fc, rbool report_error) {
+ ins_buff = NULL;
+ ins_line = 0;
+
+ if (ins_descr != NULL) return 1;
+
+ if (filevalid(insfile, fINS)) {
+ textrewind(insfile);
+ return 1;
+ }
+
+ if (agx_file) {
+ ins_descr = read_descr(ins_ptr.start, ins_ptr.size);
+ if (ins_descr != NULL) return 1;
+
+ /* Note that if the AGX file doesn't contain an INS block, we
+ don't immediatly give up but try opening <fname>.INS */
+ }
+
+ insfile = openfile(fc, fINS,
+ report_error
+ ? "Sorry, Instructions aren't available for this game"
+ : NULL,
+ 0);
+ return (filevalid(insfile, fINS));
+}
+
+char *read_ins_line(void) {
+ if (ins_descr) {
+ if (ins_descr[ins_line] != NULL)
+ return ins_descr[ins_line++];
+ else return NULL;
+ } else {
+ rfree(ins_buff);
+ ins_buff = readln(insfile, NULL, 0);
+ return ins_buff;
+ }
+}
+
+void close_ins_file(void) {
+ if (ins_descr) {
+ free_descr(ins_descr);
+ ins_descr = NULL;
+ } else if (filevalid(insfile, fINS)) {
+ rfree(ins_buff);
+ readclose(insfile);
+ insfile = BAD_TEXTFILE;
+ }
+}
+
+
+
+descr_line *read_ins(fc_type fc) {
+ descr_line *txt;
+ char *buff;
+ int i;
+
+ i = 0;
+ txt = NULL;
+ if (open_ins_file(fc, 0)) { /* Instruction file exists */
+ while (NULL != (buff = read_ins_line())) {
+ /* Enlarge txt; we use (i+2) here to leave space for the trailing \0 */
+ txt = (descr_line *)rrealloc(txt, sizeof(descr_ptr) * (i + 2));
+ txt[i++] = rstrdup(buff);
+ }
+ if (txt != NULL)
+ txt[i] = 0; /* There is space for this since we used (i+2) above */
+ close_ins_file();
+ }
+ return txt;
+}
+
+
+void free_ins(descr_line *instr) {
+ int i;
+ if (instr == NULL) return;
+ for (i = 0; instr[i] != NULL; i++)
+ rfree(instr[i]);
+ rfree(instr);
+}
+
+
+
+/* Character translation routines, used by agtread.c and read_ttl() */
+void build_fixchar(void) {
+ int i;
+ for (i = 0; i < 256; i++) {
+ if (i == '\r' || i == '\n') fixchar[i] = ' ';
+ else if (i == '\\' && bold_mode) fixchar[i] = FORMAT_CODE;
+ else if (i >= 0x80 && fix_ascii_flag)
+ fixchar[i] = trans_ibm[i & 0x7f];
+ else if (i == 0) /* Fix color and blink codes */
+ fixchar[i] = FORMAT_CODE;
+ else fixchar[i] = i;
+ }
+}
+
+} // End of namespace AGT
+} // End of namespace Glk
diff --git a/engines/glk/agt/config.h b/engines/glk/agt/config.h
new file mode 100644
index 0000000000..0780e1a9a6
--- /dev/null
+++ b/engines/glk/agt/config.h
@@ -0,0 +1,289 @@
+/* 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.
+ *
+ */
+
+#ifndef GLK_AGT_CONFIG
+#define GLK_AGT_CONFIG
+
+#include "glk/glk_api.h"
+#include "common/stream.h"
+
+namespace Glk {
+namespace AGT {
+
+/*
+ This file contains most of the configuration information
+ including the platform-dependent #define statements
+ It's in three major sections:
+ --Platform specific defines for various platforms, each
+ surrounded by "#ifdef <platform>" and "#endif"
+ --Various defaults
+ --Filename extensions
+
+ Ideally, a port to a new platform should only need to modify this
+ file, the makefile, os_<whatever>.c, and possibly filename.c. (In
+ practice, you may also need to tweak the the high-level I/O code
+ in interface.c or the memory-allocation code in util.c. If you
+ find yourself needing to do more than that, get in touch with me.) */
+
+#undef _WIN32 /* GARGLK */
+
+
+/* Default to PLAIN platform */
+/* At the moment, you can replace this with LINUX, HPUX, AMIGA, */
+/* MSDOS, SUN, or NEXT; some of these may require the correct os_... */
+/* file to work */
+/* (In particular, AMIGA requires David Kinder's os_amiga.c file) */
+/* The actual platform specific defines don't start until a few */
+/* lines down, past the #includes and the definition of global */
+#ifndef PLAIN
+#define PLAIN
+#endif
+
+/* ------------------------------------------------------------------- */
+/* PLATFORM SPECIFIC DEFINITIONS, ETC. */
+/* See agility.doc or porting.txt for more information. */
+/* Things you can currently define: */
+/* fix_ascii: 1=translate IBM character set, 0=don't */
+/* NEED_STR_CMP: define if strcasecmp() not defined on your system */
+/* NEED_STRN_CMP: define if strncasecmp() not defined on your system */
+/* HAVE_STRDUP: define if strdup() exists on your system */
+/* REPLACE_GETFILE: define if you replace the default get_user_file(). */
+/* REPLACE_MENU if you replace agt_menu(). */
+/* REPLACE_MAIN: define if you replace the default main(). */
+/* (replacements should be defined in the relevant os_<platform>.c file) */
+/* DA1,DA2,...DA6,DSS,pTTL: file name extensions for the various AGT
+ files */
+/* HAVE_SLEEP if your platform has the sleep() function */
+/* BUFF_SIZE is the maximum size of the buffer to use when reading
+ in files. Regardless, it will be made no bigger than the file
+ being read in and no smaller than the record size; thus setting
+ it to 0 will cause the smallest buffer to always be used and
+ setting this to 1MB will in practice always use a buffer the
+ sizs of the file. It defaults to 32K */
+/* CBUF_SIZE is the maximum size of the buffer used for reading in
+ the Master's Edition DA6 files; the size of the buffer in bytes
+ is twice this value (since an individual token is two bytes long). */
+/* DESCR_BUFFSIZE is the maximum size of the description text block before
+ the interpreter will read it from disk rather than storing it in
+ memory during play. At the moment this only affects AGX games;
+ original AGT games always use the disk. */
+/* DOHASH to use a hash table for dictionary searches; the only
+ reason not to have this would be memory */
+/* HASHBITS determines the size of the hash table: (2^HASHBITS)*sizeof(word);
+ the hash table must be at least as large as the dictionary.
+ In practice this means HASHBITS should be at least 12;
+ this is the current default. */
+/* MAXSTRUC The maximum size (in chars) which a single data structure can
+ be on this platform. This defaults to 1MB (i.e. no limit for
+ practical purposes). In practice I know of no game files that
+ require any structures bigger than about 30K. */
+/* LOWMEM Define this if you are low on memory. At the moment this
+ only saves a few K.*/
+/* PORTSTR Is the string describing this particular port.
+ e.g. #define PORTSTR "OrfDOS Port by R.J. Wright" */
+/* UNIX_IO if you have Unix-like low level file I/O functions.
+ (MS-DOS, for example, does). This speeds up the reading
+ of the large game data files on some platforms. If this is
+ defined, READFLAG, WRITEFLAG, and FILE_PERM also need to
+ be defined. (Giving the flags needed for opening a file for
+ reading or writing, and the file permissions to be given to newly
+ created files. */
+/* OPEN_AS_TEXT Define to cause text files to be opened as text files. */
+/* PREFIX_EXT Add filename extensions at the beginning of the name,
+ rather than at the end. */
+/* PATH_SEP, if defined, is a string containing all characters which
+ can be used to separate the path from the filename. */
+/* pathtest(s) is a macro that should check whether the given string
+ is an absolute path. If this is left undefined, then _all_
+ paths will be treated as absolute. You don't need to define
+ this if you are replacing filename.c. */
+/* ------------------------------------------------------------------- */
+
+/* force16 is used purely for debugging purposes, to make sure that
+ everything works okay even with 16-bit ints */
+/* #define force16 */
+
+#define DOHASH
+
+/*
+ * The Glk port is very similar to plain ASCII, to give it the best
+ * chance at success on multiple Glk platforms. The only basic change
+ * is to turn off IBM character translations; Glk works in ISO 8859
+ * Latin-1, which can offer slightly closer translation of the IBM
+ * code page 437 characters that the simpler mappings in the core
+ * AGiliTy code. The os_glk.c module handles the translations.
+ */
+#ifdef GARGLK
+#define NEED_STR_CMP /* Inherited from PLAIN. */
+#define NEED_STRN_CMP /* Inherited from PLAIN. */
+#define BUFF_SIZE 0 /* Inherited from PLAIN. */
+#define CBUF_SIZE (5000L) /* Inherited from PLAIN. */
+#define INBUFF_SIZE (1024) /* Inherited from PLAIN. */
+#define fix_ascii 0 /* os_glk.c does translations. */
+#define MAXSTRUC (1024L*1024L) /* 32Kb from PLAIN is too small for
+ several games (including Soggy). */
+#define PORTSTR "Glk version" /* Identify ourselves discreetly. */
+#define REPLACE_GETFILE /* Override get_user_file. */
+#define REPLACE_MAIN /* Override main. */
+#define fnamecmp strcasecmp /* Case insensitive filename compare. */
+#undef PLAIN
+
+#endif
+
+/* PLAIN should always come last, giving everyone else a chance
+ to #undef it. */
+#ifdef PLAIN /* This should work if nothing else does */
+#define NEED_STR_CMP
+#define NEED_STRN_CMP
+#define BUFF_SIZE 0
+#define CBUF_SIZE (5000L)
+#define INBUFF_SIZE (1024) /* Used by Magx */
+#define MAXSTRUC (32L*1024L) /* IIRC, 32K is the minimum required by
+ the ANSI standard */
+#define PORTSTR "Pure ANSI C version"
+#endif
+
+
+/* __GNUC__ */
+
+
+/* ------------------------------------------------------------------- */
+/* DEFAULTS FOR "PLATFORM SPECIFIC" DEFINES */
+/* ------------------------------------------------------------------- */
+
+#ifdef __STRICT_ANSI__
+#define NEED_STR_CMP
+#define NEED_STRN_CMP
+#undef HAVE_STRDUP
+#endif
+
+#ifndef fix_ascii
+#define fix_ascii 1 /* Translate IBM character set by default */
+#endif
+
+#ifndef BUFF_SIZE
+#ifdef LOWMEM
+#define BUFF_SIZE 0 /* i.e. unbuffered */
+#else
+#define BUFF_SIZE (32L*1024L) /* 32K */
+#endif
+#endif /* BUFF_SIZE */
+
+#ifndef MAXSTRUC
+#define MAXSTRUC (1024L*1024L)
+#endif
+
+#ifndef DESCR_BUFFSIZE
+#define DESCR_BUFFSIZE 0 /* Always load descriptions from disk */
+#endif
+
+#ifndef HASHBITS
+#ifdef LOWMEM
+#define HASHBITS 12 /* 4K entries */
+#else
+#define HASHBITS 13 /* 8K entries in hash table */
+#endif
+#endif /* HASHBITS */
+
+#ifndef fnamecmp /* Used to compare filenames */
+#define fnamecmp strcmp
+#endif
+
+#ifndef fnamencmp /* Also used to compare filenames */
+#define fnamencmp strncmp
+#endif
+
+/* If DOSFARDATA hasn't been defined, define it as the empty string. */
+#ifndef DOSFARDATA
+#define DOSFARDATA
+#endif
+
+/* ---------------------------------------------------------------------- */
+/* FILENAME EXTENSIONS */
+/* These are the various filename extensions for the different data files.*/
+/* ---------------------------------------------------------------------- */
+
+/* The following are only used by the interpreter, agtout, and agt2agx */
+#ifndef DA1
+#define DA1 ".da1" /* General info (text file) */
+#define DA2 ".da2" /* Rooms */
+#define DA3 ".da3" /* Items */
+#define DA4 ".da4" /* Creatures */
+#define DA5 ".da5" /* Commands, headers */
+#define DA6 ".da6" /* Commands, code (Master's Ed only) */
+#define DSS ".d$$" /* Description strings */
+#define pHNT ".hnt" /* Popup hint file; not used yet. */
+#define pOPT ".opt" /* Interface specification file */
+#endif
+
+/* The following are only used by the Magx compiler */
+#ifndef pAGT
+#define pAGT ".agt"
+#define pDAT ".dat"
+#define pMSG ".msg"
+#define pCMD ".cmd"
+#define pSTD ".std"
+#define AGTpSTD "agt.std" /* Default error message file */
+#endif
+
+/* The following are used by both the interpreter and the compiler */
+#ifndef pAGX
+#define pAGX ".agx" /* Extension for new Adventure Game eXecutable format */
+#define pTTL ".ttl" /* Title file */
+#define pINS ".ins" /* Instruction file */
+#define pVOC ".voc" /* Menu vocabulary file */
+#define pCFG ".cfg" /* Game configuration file */
+#define pEXT "." /* Separator between extension and base of filename */
+#endif
+
+
+#ifndef pSAV
+#define pSAV ".sav" /* Extension for save files */
+#endif
+
+#ifndef pSCR
+#define pSCR ".scr" /* Script file */
+#endif
+
+#ifndef pLOG
+#define pLOG ".log" /* LOG/REPLAY file */
+#endif
+
+
+
+
+
+/* Finally, two potentially platform dependent type defintions,
+ for binary and text files respectively. Don't change these
+ unless you are also changing filename.c */
+
+typedef Common::Stream *genfile;
+typedef char *file_id_type; /* i.e. the filename */
+
+#define NO_FILE_ID NULL
+#define BAD_TEXTFILE NULL
+#define BAD_BINFILE NULL
+
+} // End of namespace AGT
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/agt/debugcmd.cpp b/engines/glk/agt/debugcmd.cpp
new file mode 100644
index 0000000000..83be57ac0c
--- /dev/null
+++ b/engines/glk/agt/debugcmd.cpp
@@ -0,0 +1,866 @@
+/* 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/exec.h"
+
+namespace Glk {
+namespace AGT {
+
+static void d_moveobj(int obj, int dest)
+/* 1=the player, -1=unknown: ask */
+{
+ if (obj == -1) {
+ writestr("Which object? ");
+ obj = read_number();
+ if (obj != 1 && !tnoun(obj) && !tcreat(obj)) {
+ writeln("Invalid object");
+ return;
+ }
+ }
+ if (dest == -1) {
+ writestr("To where? ");
+ dest = read_number();
+ if (dest != 1 && dest != 0 && !tnoun(dest) && !tcreat(dest) && !troom(dest)) {
+ writeln("Invalid object");
+ return;
+ }
+ }
+ if (obj != 1)
+ it_move(obj, dest);
+ else {
+ if (!troom(dest)) {
+ writeln("Player can only be moved to a room");
+ return;
+ }
+ goto_room(dest - first_room);
+ }
+}
+
+static int print_objid(int obj) {
+ char buff[10];
+ char *s;
+ int n;
+
+ sprintf(buff, "%4d: ", obj);
+ writestr(buff);
+ s = objname(obj);
+ for (n = 0; s[n] != 0; n++)
+ if (s[n] <= 8 || (uchar)s[n] == 0xFF) s[n] = ' '; /* Strip out format codes */
+ writestr(s);
+ n = strlen(s);
+ rfree(s);
+ return n;
+}
+
+static void d_listroom() {
+ int i;
+
+ writeln(" ROOM");
+ writeln(" ------");
+ writeln("");
+ for (i = 0; i <= maxroom - first_room; i++) {
+ print_objid(i + first_room);
+ writeln("");
+ }
+}
+
+#define SEPLENG 27 /* Width between beginning of object column and
+ location column */
+
+static void d_listnoun() {
+ int i;
+ int len;
+
+ writestr(" NOUN ");
+ padout(SEPLENG - 6);
+ writeln(" LOCATION ");
+ writestr(" ------");
+ padout(SEPLENG - 6);
+ writeln(" ----------");
+ writeln("");
+ len = SEPLENG - print_objid(1);
+ padout(len);
+ writestr("[");
+ print_objid(loc);
+ writeln("]");
+
+ nounloop(i) {
+ len = print_objid(i + first_noun);
+ len = SEPLENG - len;
+ if (len > 0) padout(len);
+ writestr("[");
+ print_objid(noun[i].location);
+ writeln("]");
+ }
+}
+
+static void d_listcreat() {
+ int i;
+ int len;
+
+ writestr(" CREATURE ");
+ padout(SEPLENG - 11);
+ writeln(" LOCATION ");
+ writestr(" ----------");
+ padout(SEPLENG - 11);
+ writeln(" ----------");
+ writeln("");
+
+ creatloop(i) {
+ len = print_objid(i + first_creat);
+ len = SEPLENG - len;
+ if (len > 0) padout(len);
+ writestr(" [");
+ print_objid(creature[i].location);
+ writeln("]");
+ }
+}
+
+static void writetbl(const char *s, int width)
+/* This writes out s and then prints out any additional spaces needed
+ to make the output string *width* wide. */
+{
+ writestr(s);
+ width = width - strlen(s);
+ if (width > 0) padout(width);
+}
+
+static void var_edit(int vtype)
+/* vtype=0 for variable, 1 for counter, 2 for flag */
+{
+ long n;
+ int i;
+ int imax;
+ char sbuff[30];
+
+ switch (vtype) {
+ case 0:
+ imax = VAR_NUM;
+ break;
+ case 1:
+ imax = CNT_NUM;
+ break;
+ case 2:
+ imax = FLAG_NUM;
+ break;
+ default:
+ writeln("INTERNAL ERROR: Invalid vtype.");
+ return;
+ }
+
+ for (;;) {
+ agt_clrscr();
+ writeln("");
+ switch (vtype) {
+ case 0:
+ writeln("Variables");
+ break;
+ case 1:
+ writeln("Counters (-1 means the counter is off)");
+ break;
+ case 2:
+ writeln("Flags ( f=false [OFF] and t=true [ON] )");
+ break;
+ }
+ writeln("");
+ for (i = 0; i <= imax; i++) {
+ switch (vtype) {
+ case 0:
+ sprintf(sbuff, "[Var%3d]=%4ld", i, (long)agt_var[i]);
+ break;
+ case 1:
+ sprintf(sbuff, "[Cnt%3d]=%4ld", i, (long)agt_counter[i]);
+ break;
+ case 2:
+ sprintf(sbuff, "%3d%c", i, flag[i] ? 't' : 'f');
+ break;
+ }
+ writetbl(sbuff, (vtype == 2) ? 5 : 20);
+ }
+ writeln("");
+ writeln("");
+ for (;;) {
+ switch (vtype) {
+ case 0:
+ writestr("Variable to change");
+ break;
+ case 1:
+ writestr("Counter to change");
+ break;
+ case 2:
+ writestr("Flag to toggle");
+ break;
+ }
+ writestr(" (-1 to quit): ");
+ i = read_number();
+ if (i < 0) return;
+ if (i <= imax) {
+ if (vtype != 2) {
+ if (vtype == 0)
+ sprintf(sbuff, "[Var%d]=%ld", i, (long)agt_var[i]);
+ else sprintf(sbuff, "[Cnt%d]=%ld (-1 means it's off)",
+ i, (long)agt_counter[i]);
+ writestr(sbuff);
+ writestr("; new value = ");
+ n = read_number();
+ if (vtype == 0)
+ agt_var[i] = n;
+ else if (n < -1 || n > (((long)1) << 15) - 1)
+ writeln("Invalid value for a counter.");
+ else agt_counter[i] = n;
+ } else flag[i] = !flag[i];
+ break;
+ } else
+ writeln("Invalid index.");
+ }
+ }
+}
+
+/* Routines to edit user strings */
+static void edit_str() {
+ int i, j;
+ char buff[10];
+ char *tmpstr;
+
+ if (MAX_USTR == 0 || userstr == NULL) {
+ writeln("This game doesn't contain any user strings");
+ return;
+ }
+ for (;;) {
+ agt_clrscr();
+ writeln("User Definable Strings");
+ writeln("");
+ for (i = 0; i < MAX_USTR; i++) {
+ sprintf(buff, "%2d:", i + 1);
+ writestr(buff);
+ writeln(userstr[i]);
+ }
+ writestr(" (0 to quit): ");
+ i = read_number();
+ if (i == 0) return;
+ if (i > 0 && i <= MAX_USTR) {
+ writeln("Enter new string:");
+ tmpstr = agt_readline(3);
+ j = strlen(tmpstr) - 1;
+ if (j > 0 && tmpstr[j] == '\n') tmpstr[j] = 0;
+ strncpy(userstr[i - 1], tmpstr, 80);
+ } else writeln("Invalid string number");
+ }
+}
+
+static uchar attrcol; /* Determines which column the attribute is put in */
+static uchar attrwidth; /* Number of attribute columns */
+
+static void next_col() {
+ if (++attrcol == attrwidth) {
+ writeln("");
+ attrcol = 0;
+ } else
+ padout(10);
+}
+
+static void writeattr(const char *attrname, rbool attrval) {
+ writestr(attrname);
+ padout(15 - strlen(attrname));
+ if (attrval) writestr("yes");
+ else writestr("no ");
+ next_col();
+}
+
+static void writegender(const char *gendername, uchar genderval) {
+ writestr(gendername);
+ padout(15 - strlen(gendername) - 3);
+ switch (genderval) {
+ case 2:
+ writestr("Male ");
+ break;
+ case 1:
+ writestr("Female");
+ break;
+ case 0:
+ writestr("Thing");
+ break;
+ }
+ next_col();
+}
+
+static void writeprop(const char *propname, int obj) {
+ writestr(propname);
+ writestr(" [");
+ print_objid(obj);
+ writeln("]");
+}
+
+static int writedir(int index, int dir, int obj) {
+ char sbuff[40];
+
+ sprintf(sbuff, "%2d.%-2s %d", index, exitname[dir], obj);
+ writestr(sbuff);
+ return strlen(sbuff);
+}
+
+void writenum(const char *propname, int n) {
+ char sbuff[20];
+
+ writestr(propname);
+ sprintf(sbuff, "%4d", n);
+ writeln(sbuff);
+}
+
+static void writeflags(const char *flagname, int32 flags) {
+ int i;
+ char sbuff[5];
+
+ writestr(flagname);
+ for (i = 0; i < 32; i++) {
+ if (flags & 1) {
+ sprintf(sbuff, "%2d ", i);
+ writestr(sbuff);
+ } else
+ writestr(" ");
+ flags >>= 1;
+ if (i % 12 == 11) {
+ writeln("");
+ padout(strlen(flagname));
+ }
+ }
+ writeln("");
+}
+
+static void readflags(int32 *flags) {
+ long n;
+
+ writestr("Room flag to toggle (0-31)? ");
+ n = read_number();
+ if (n <= 31 && n >= 0)
+ *flags ^= (((long)1) << n);
+}
+
+static long readval(const char *prompt, int type) {
+ long val;
+
+ for (;;) {
+ writestr(prompt);
+ writestr(" ");
+ val = read_number();
+ if (argvalid(type, val)) return val;
+ writeln("Invalid value.");
+ }
+}
+
+static uchar readgender() {
+ char c;
+
+ writestr("Gender (M/F/N): ");
+ for (;;) {
+ c = tolower(agt_getchar());
+ switch (c) {
+ case 'm':
+ return 2;
+ case 'w':
+ case 'f':
+ return 1;
+ case 'n':
+ case 't':
+ return 0;
+ default: ;/* Do nothing */
+ }
+ }
+}
+
+static void edit_objattr(int obj) {
+ int i, k, kprop, n;
+ long v;
+
+ for (;;) {
+ k = 1;
+ agt_clrscr();
+ print_objid(obj);
+ writeln("");
+ if (oflag_cnt > 0) {
+ writeln("ObjFlags:");
+ for (i = 0; i < oflag_cnt; i++)
+ if (have_objattr(0, obj, i)) {
+ v = op_objflag(2, obj, i);
+ rprintf("%2d. ObjProp%2d:%c %-40s\n", k++, i, (v ? '+' : '-'),
+ get_objattr_str(AGT_OBJFLAG, i, v));
+ }
+ writeln("");
+ }
+ kprop = k;
+ if (oprop_cnt > 0) {
+ writeln("ObjProps:");
+ for (i = 0; i < oprop_cnt; i++)
+ if (have_objattr(1, obj, i)) {
+ v = op_objprop(2, obj, i, 0);
+ rprintf("%2d. ObjFlag%2d: [%3ld] %-40s\n", k++, i, v,
+ get_objattr_str(AGT_OBJPROP, i, v));
+ }
+ writeln("");
+ }
+ writestr("Field to change (0 to return to main view)? ");
+ n = read_number();
+ if (n == 0) return;
+ if (n < 1 || n >= k) continue;
+ k = 0;
+ if (n < kprop) { /* Attribute */
+ for (i = 0; i < oflag_cnt; i++)
+ if (have_objattr(0, obj, i))
+ if (n == ++k) break;
+ if (n == k && have_objattr(0, obj, i))
+ op_objflag(3, obj, i); /* Toggle it */
+ } else { /* Property */
+ for (i = 0; i < oprop_cnt; i++)
+ if (have_objattr(1, obj, i))
+ if (n == ++k) break;
+ if (n == k && have_objattr(1, obj, i))
+ op_objprop(1, obj, i, readval("New value:", AGT_NUM));
+ }
+ }
+}
+
+static void room_edit(int i) {
+ int n, j;
+
+ for (;;) {
+ agt_clrscr();
+ writestr("ROOM ");
+ print_objid(i + first_room);
+ writeln("");
+ writeln("");
+ attrcol = 0;
+ attrwidth = 2;
+ writeattr("1.*WinGame:", room[i].win);
+ writeattr("4. Seen:", room[i].seen);
+ writeattr("2.*EndGame:", room[i].end);
+ writeattr("5. LockedDoor:", room[i].locked_door);
+ writeattr("3.*Die:", room[i].killplayer);
+ writeln("");
+ writeln("");
+ writeprop("6.*Key =", room[i].key);
+ writeprop("7. Light =", room[i].light);
+ writenum("8. Points =", room[i].points);
+ writeprop("9. Class = ", room[i].oclass);
+ writeln("");
+ writeln("EXITS:");
+ for (j = 0; j < 12; j++) {
+ n = writedir(j + 10, j, room[i].path[j]);
+ if (j % 4 == 3) writeln("");
+ else padout(15 - n);
+ }
+ writeprop("22. SPECIAL:", room[i].path[12]);
+ writeflags("23. Room Flags:", room[i].flag_noun_bits);
+ writeln("24. Object properties and attributes.");
+ writeln("");
+ writeln("(Fields marked with an * are not saved or restored.)");
+ /* writeln(""); */
+ writestr("Field to change (0 to exit)? ");
+ n = read_number();
+ if (n == 0) return;
+ switch (n) {
+ case 1:
+ room[i].win = !room[i].win;
+ break;
+ case 2:
+ room[i].end = !room[i].end;
+ break;
+ case 3:
+ room[i].killplayer = !room[i].killplayer;
+ break;
+ case 4:
+ room[i].seen = !room[i].seen;
+ break;
+ case 5:
+ room[i].locked_door = !room[i].locked_door;
+ break;
+ case 6:
+ room[i].key = readval("Key = ", AGT_ITEM | AGT_NONE);
+ break;
+ case 7:
+ room[i].light = readval("Light = ", AGT_ITEM | AGT_NONE | AGT_SELF);
+ break;
+ case 8:
+ room[i].points = readval("Points = ", AGT_NUM);
+ break;
+ case 9:
+ room[i].oclass = readval("Class = ", AGT_ROOM | AGT_NONE);
+ break;
+ case 22:
+ room[i].path[12] = readval("SPECIAL: ", AGT_NUM);
+ break;
+ case 23:
+ readflags(&room[i].flag_noun_bits);
+ break;
+ case 24:
+ edit_objattr(i + first_room);
+ break;
+ default:
+ if (n >= 10 && n < 22) { /* Direction */
+ room[i].path[n - 10] = readval(exitname[n - 10], AGT_NUM);
+ } else writeln("Invalid field");
+ }
+ }
+}
+
+#define tog(x) {x=!x;break;}
+
+static void noun_edit(int i) {
+ int n;
+
+ for (;;) {
+ agt_clrscr();
+ /* writeln("");*/
+ writestr("NOUN ");
+ print_objid(i + first_noun);
+ /* writeln("");*/
+ /* writeln("");*/
+ writeprop(" Location=", noun[i].location);
+ writeln("");
+ attrcol = 0;
+ attrwidth = 3;
+ writeattr(" 1.*Pushable:", noun[i].pushable);
+ writeattr(" 8.*Lockable:", noun[i].lockable);
+ writeattr("15.*Drinkable:", noun[i].drinkable);
+ writeattr(" 2.*Pullable:", noun[i].pullable);
+ writeattr(" 9.*Light:", noun[i].light);
+ writeattr("16.*Poisonous:", noun[i].poisonous);
+ writeattr(" 3.*Turnable:", noun[i].turnable);
+ writeattr("10.*Plural:", noun[i].plural);
+ writeattr("17. Open:", noun[i].open);
+ writeattr(" 4.*Playable:", noun[i].playable);
+ writeattr("11. Movable:", noun[i].movable);
+ writeattr("18. Locked:", noun[i].locked);
+ writeattr(" 5.*Readable:", noun[i].readable);
+ writeattr("12.*Shootable:", noun[i].shootable);
+ writeattr("19.*Win Game:", noun[i].win);
+ writeattr(" 6.*Wearable:", noun[i].wearable);
+ writeattr("13. On:", noun[i].on);
+ writeattr("20.*Global:", noun[i].isglobal);
+ writeattr(" 7.*Closable:", noun[i].closable);
+ writeattr("14.*Edible:", noun[i].edible);
+ writeattr("21.*Proper:", noun[i].proper);
+
+ writeln("");
+ writenum("22. Shots =", noun[i].num_shots);
+ writenum("23. Points =", noun[i].points);
+ writenum("24. Weight =", noun[i].weight);
+ writenum("25. Size =", noun[i].size);
+ writeprop("26.*Key =", noun[i].key);
+ writeprop("27. Class =", noun[i].oclass);
+ writenum("28. Flag =", noun[i].flagnum);
+ writeln("");
+ /* writeln(""); */
+ writeln("29. Object properties and attributes.");
+ writeln("");
+ writeln("(Fields marked with an * are not saved or restored.)");
+ writestr("Field to change (0 to exit)? ");
+ n = read_number();
+ if (n == 0) return;
+ switch (n) {
+ case 1:
+ tog(noun[i].pushable); /* tog() macro includes break */
+ case 2:
+ tog(noun[i].pullable);
+ case 3:
+ tog(noun[i].turnable);
+ case 4:
+ tog(noun[i].playable);
+ case 5:
+ tog(noun[i].readable);
+ case 6:
+ tog(noun[i].wearable);
+ case 7:
+ tog(noun[i].closable);
+ case 8:
+ tog(noun[i].lockable);
+ case 9:
+ tog(noun[i].light);
+ case 10:
+ tog(noun[i].plural);
+ case 11:
+ tog(noun[i].movable);
+ case 12:
+ tog(noun[i].shootable);
+ case 13:
+ tog(noun[i].on);
+ case 14:
+ tog(noun[i].edible);
+ case 15:
+ tog(noun[i].drinkable);
+ case 16:
+ tog(noun[i].poisonous);
+ case 17:
+ tog(noun[i].open);
+ case 18:
+ tog(noun[i].locked);
+ case 19:
+ tog(noun[i].win);
+ case 20:
+ tog(noun[i].isglobal);
+ case 21:
+ tog(noun[i].proper);
+
+ case 22:
+ noun[i].num_shots = readval("Shots =", AGT_NUM);
+ break;
+ case 23:
+ noun[i].points = readval("Points =", AGT_NUM);
+ break;
+ case 24:
+ noun[i].weight = readval("Weight =", AGT_NUM);
+ break;
+ case 25:
+ noun[i].size = readval("Size =", AGT_NUM);
+ break;
+ case 26:
+ noun[i].key = readval("Key =", AGT_ITEM | AGT_NONE);
+ break;
+ case 27:
+ noun[i].oclass = readval("Class =", AGT_ITEM | AGT_NONE);
+ break;
+ case 28:
+ noun[i].flagnum = readval("Flag Number=", AGT_ROOMFLAG);
+ break;
+ case 29:
+ edit_objattr(i + first_noun);
+ break;
+ default:
+ writeln("Invalid field");
+ }
+ }
+}
+
+static void creat_edit(int i) {
+ int n;
+
+ for (;;) {
+ agt_clrscr();
+ writestr("CREATURE ");
+ print_objid(i + first_creat);
+ writeln("");
+ writeln("");
+ writeprop("Location =", creature[i].location);
+ writeln("");
+ attrcol = 0;
+ attrwidth = 2;
+ writeattr(" 1. Hostile:", creature[i].hostile);
+ writeattr(" 4. Global:", creature[i].isglobal);
+ writeattr(" 2. Grp member:", creature[i].groupmemb);
+ writeattr(" 5.*Proper:", creature[i].proper);
+ writegender(" 3.*Gender:", creature[i].gender);
+ writeln("");
+ writeln("");
+ writeprop(" 6.*Weapon = ", creature[i].weapon);
+ writenum(" 7. Points = ", creature[i].points);
+ writenum(" 8.*Attack Threshold = ", creature[i].threshold);
+ writenum(" 9. Attack counter = ", creature[i].counter);
+ writenum("10.*Attack Time Limit = ", creature[i].timethresh);
+ writenum("11. Attack timer = ", creature[i].timecounter);
+ writeprop("12. Class = ", creature[i].oclass);
+ writenum("13. Flag = ", creature[i].flagnum);
+ writeln("");
+ writeln("14. Object properties and attributes.");
+ writeln("");
+ writeln("(Fields marked with an * are not saved or restored.)");
+ writeln("");
+ writestr("Field to change (0 to exit)? ");
+ n = read_number();
+ if (n == 0) return;
+ switch (n) {
+ case 1:
+ tog(creature[i].hostile);
+ case 2:
+ tog(creature[i].groupmemb);
+ case 3:
+ tog(creature[i].isglobal);
+ case 4:
+ tog(creature[i].proper);
+
+ case 5:
+ creature[i].gender = readgender();
+ break;
+ case 6:
+ creature[i].weapon = readval("Weapon =", AGT_ITEM | AGT_NONE);
+ break;
+ case 7:
+ creature[i].points = readval("Points =", AGT_NUM);
+ break;
+ case 8:
+ creature[i].threshold = readval("Threshold =", AGT_NUM);
+ break;
+ case 9:
+ creature[i].counter = readval("Attack counter =", AGT_NUM);
+ break;
+ case 10:
+ creature[i].timethresh = readval("Time limit =", AGT_NUM);
+ break;
+ case 11:
+ creature[i].timecounter = readval("Timer =", AGT_NUM);
+ break;
+ case 12:
+ creature[i].oclass = readval("Class =", AGT_ITEM | AGT_NONE);
+ break;
+ case 13:
+ noun[i].flagnum = readval("Flag Number=", AGT_ROOMFLAG);
+ break;
+ case 14:
+ edit_objattr(i + first_creat);
+ break;
+ default:
+ writeln("Invalid field");
+ }
+ }
+}
+
+#undef tog
+
+
+static void obj_edit() {
+ int n;
+
+ for (;;) {
+ writeln("");
+ do {
+ writestr("Enter object number (0 to exit)? ");
+ n = read_number();
+ if (n <= 0) return;
+ } while (!troom(n) && !tnoun(n) && !tcreat(n));
+
+ if (troom(n)) room_edit(n - first_room);
+ else if (tnoun(n)) noun_edit(n - first_noun);
+ else if (tcreat(n)) creat_edit(n - first_creat);
+ else writeln("[Not yet implemented]");
+
+ }
+}
+
+static const char *yesnostr[] = { "No", "Yes" };
+
+static void set_debug_options() {
+ char buff[80];
+ int n;
+
+ for (;;) {
+ agt_clrscr();
+ writeln("DEBUGGING OPTIONS:");
+ writeln("");
+ sprintf(buff, " 1. Trace metacommands: %s", yesnostr[DEBUG_AGT_CMD]);
+ writeln(buff);
+ sprintf(buff, " 2. Trace ANY metacommands: %s", yesnostr[debug_any]);
+ writeln(buff);
+ sprintf(buff, " 3. Trace during disambiguation: %s",
+ yesnostr[debug_disambig]);
+ writeln(buff);
+ writeln("");
+ writeln("(<2> and <3> are ignored if <1> is not set; option <1> can"
+ " also be changed from the main debugging menu)");
+ writeln("");
+ writestr("Option to toggle (0 to exit): ");
+ n = read_number();
+ switch (n) {
+ case 0:
+ return;
+ case 1:
+ DEBUG_AGT_CMD = !DEBUG_AGT_CMD;
+ break;
+ case 2:
+ debug_any = !debug_any;
+ break;
+ case 3:
+ debug_disambig = !debug_disambig;
+ break;
+ default:
+ writeln("Not a valid option");
+ }
+ }
+}
+
+void get_debugcmd() {
+ int n;
+
+ for (;;) {
+ writeln("DEBUGGING COMMANDS");
+ writeln("");
+ writeln("1. Move player 8. List Rooms");
+ writeln("2. Get Noun 9. List Nouns");
+ writeln("3. Move object 10. List Creatures");
+ writeln("4. View/Edit object 11. List/Set Flags");
+ writeln("5. Toggle Trace 12. List/Set Variables");
+ writeln("6. Set Debug Options 13. List/Set Counters");
+ writeln("7. Edit User Strings");
+ writeln("");
+ writestr("Enter choice (0 to exit): ");
+ n = read_number();
+ switch (n) {
+ case -1:
+ case 0:
+ return;
+ case 1:
+ d_moveobj(1, -1);
+ break;
+ case 2:
+ d_moveobj(-1, 1);
+ break;
+ case 3:
+ d_moveobj(-1, -1);
+ break;
+ case 4:
+ obj_edit();
+ break;
+ case 5:
+ DEBUG_AGT_CMD = !DEBUG_AGT_CMD;
+ break;
+ case 6:
+ set_debug_options();
+ break;
+ case 7:
+ edit_str();
+ break;
+ case 8:
+ d_listroom();
+ break;
+ case 9:
+ d_listnoun();
+ break;
+ case 10:
+ d_listcreat();
+ break;
+ case 11:
+ var_edit(2);
+ break;
+ case 12:
+ var_edit(0);
+ break;
+ case 13:
+ var_edit(1);
+ break;
+ default:
+ writeln("Not a valid option");
+ }
+ writeln("");
+ };
+}
+
+} // End of namespace AGT
+} // End of namespace Glk
diff --git a/engines/glk/agt/disassemble.cpp b/engines/glk/agt/disassemble.cpp
new file mode 100644
index 0000000000..cd24c48f37
--- /dev/null
+++ b/engines/glk/agt/disassemble.cpp
@@ -0,0 +1,317 @@
+/* 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"
+
+namespace Glk {
+namespace AGT {
+
+void dbgprintf(const char *fmt, ...) {
+ va_list vp;
+ char buff[300];
+
+ va_start(vp, fmt);
+ vsprintf(buff, fmt, vp);
+ va_end(vp);
+
+ debugout(buff);
+}
+
+
+static void print_msg(descr_ptr dptr) {
+ int j;
+ descr_line *txt;
+
+ txt = read_descr(dptr.start, dptr.size);
+ if (txt != NULL) {
+ for (j = 0; txt[j] != NULL; j++) {
+ dbgprintf("\n");
+ debugout(txt[j]);
+ }
+ }
+ free_descr(txt);
+}
+
+
+static char *getname(int inum)
+/* Name should be 20 chars or less */
+{
+ if (inum == 0) return rstrdup("* 0 *");
+ return objname(inum);
+}
+
+
+extern integer dobj, iobj, actor;
+
+void print_special_obj(int i)
+/* This is called by the disassembler in agtdbg.c */
+/* i=0 NOUN, 1 OBJECT, 2 NAME */
+{
+ int dval;
+ char *s;
+ switch (i) {
+ case 0:
+ dval = dobj;
+ dbgprintf("NOUN");
+ break;
+ case 1:
+ dval = iobj;
+ dbgprintf("OBJECT");
+ break;
+ case 2:
+ dval = actor;
+ dbgprintf("NAME");
+ break;
+ default:
+ dval = 0; /* Silence compiler warnings. */
+ fatal("INTERNAL ERROR: Invalid *dval* in print_special_obj.");
+ }
+ if (dbgflagptr == NULL)
+ /* This determines whether we are linked with agtout or agil */
+ return;
+ s = getname(dval);
+ dbgprintf("(%d:%s)", dval, s);
+ rfree(s);
+}
+
+#define printval(str,index,ptr) {dbgprintf("[%s%d",str,index);\
+ if (ptr==NULL) dbgprintf("]");\
+ else dbgprintf("=%ld]",(long)ptr[index]);}
+
+int argout(int dtype, int dval, int optype) {
+ char *s;
+
+ if (dtype & AGT_VAR) dtype = AGT_VAR;
+
+ if ((optype & 3) == 1) /* variable */
+ dtype = AGT_VAR;
+ if (optype & 2) { /* NOUN or OBJECT */
+ if (dtype >= 64 && dtype != AGT_NUM)
+ dbgprintf("ILL:");
+ if (optype == 2)
+ print_special_obj(0); /* NOUN */
+ else
+ print_special_obj(1); /* OBJECT */
+ return 0;
+ }
+
+ if (!interp_arg)
+ dbgprintf("%d", dval);
+ else {
+ if (dtype < 64) {
+ if (dval == -1)
+ print_special_obj(2); /* NAME */
+ else {
+ s = getname(dval);
+ if (dtype & (AGT_ITEM | AGT_CREAT | AGT_SELF | AGT_WORN))
+ dbgprintf("<%d:%s>", dval, s);
+ else
+ dbgprintf("{%d:%s}", dval, s);
+ rfree(s);
+ }
+ } else if ((dtype & AGT_VAR) != 0) {
+ if (dval == -1)
+ print_tos();
+ else
+ printval("Var", dval, dbgvarptr);
+ } else switch (dtype) {
+ case AGT_TIME:
+ dbgprintf("%2d:%2d", dval / 100, dval % 100);
+ break;
+ case AGT_NUM: /* Numeric */
+ dbgprintf("%d", dval);
+ break;
+ case AGT_FLAG: /* Flag */
+ printval("Flg", dval, dbgflagptr);
+ break;
+ case AGT_ROOMFLAG: /* Roomflag */
+ dbgprintf("RoomFlag%d", dval);
+ break;
+ case AGT_QUEST: /* Question */
+ if (dval <= MaxQuestion && dval >= 1 && question != NULL) {
+ dbgprintf("\nQ%d:%s\n", dval, question[dval - 1]);
+ dbgprintf("[A:%s]", answer[dval - 1]);
+ } else if (quest_ptr != NULL) {
+ dbgprintf("\nQ%d: ", dval);
+ print_msg(quest_ptr[dval - 1]);
+ dbgprintf("[A:");
+ print_msg(ans_ptr[dval - 1]);
+ }
+ break;
+ case AGT_MSG: /* Message */
+ if (dval > last_message || dval < 1 || msg_ptr == NULL)
+ dbgprintf("ILLEGAL MESSAGE");
+ else {
+ dbgprintf("(Msg%d)", dval);
+ if (!dbg_nomsg)
+ print_msg(msg_ptr[dval - 1]);
+ }
+ break;
+ case AGT_ERR: /* Message */
+ if (dval > NUM_ERR || dval < 1 || err_ptr == NULL)
+ dbgprintf("ILLEGAL MESSAGE");
+ else {
+ dbgprintf("(Std%d)", dval);
+ if (!dbg_nomsg)
+ print_msg(err_ptr[dval - 1]);
+ }
+ break;
+ case AGT_STR: /* String */
+ if (dval - 1 >= MAX_USTR || userstr == NULL)
+ dbgprintf("ILLEGAL STRING");
+ else
+ dbgprintf("\nStr%d:%s", dval, userstr[dval]);
+ break;
+ case AGT_CNT: /* Counter */
+ printval("Cnt", dval, dbgcntptr);
+ break;
+ case AGT_DIR: /* Direction */
+ if (dval >= 1 && dval <= 13)
+ dbgprintf("%s", exitname[dval - 1]);
+ else dbgprintf("ILL_DIR(%d)", dval);
+ break;
+ case AGT_SUB: /* Subroutine */
+ dbgprintf("Subroutine %d", dval);
+ break;
+ case AGT_PIC: /* Picture */
+ case AGT_PIX:
+ dbgprintf("Picture #%d", dval);
+ break;
+ case AGT_FONT: /* Font */
+ dbgprintf("Font #%d", dval);
+ break;
+ case AGT_SONG: /* Song */
+ dbgprintf("Song #%d", dval);
+ break;
+ case AGT_OBJFLAG:
+ dbgprintf("ObjFlag%d", dval);
+ break;
+ case AGT_OBJPROP:
+ dbgprintf("ObjProp%d", dval);
+ break;
+ case AGT_ATTR:
+ if (dval < 0 || dval >= NUM_ATTR)
+ dbgprintf("UnkownAttr%d", dval);
+ else
+ dbgprintf("%s", attrlist[dval].name);
+ break;
+ case AGT_PROP:
+ if (dval < 0 || dval >= NUM_PROP)
+ dbgprintf("UnknownProp%d", dval);
+ else
+ dbgprintf("%s", proplist[dval].name);
+ break;
+ case AGT_EXIT:
+ if (dval >= exitmsg_base)
+ argout(AGT_MSG, dval - exitmsg_base, 0);
+ else
+ argout(AGT_ROOM, dval, 0);
+ break;
+ default:
+ dbgprintf("?+%d", dval);
+ }
+ }
+ return 1;
+}
+
+
+void debug_newline(integer op, rbool first_nl) {
+ rbool early_nl;
+
+ if (!dbg_nomsg) return;
+ early_nl = (op == 1008 || op == 1027 || op == 1083 || op == 1105
+ || (op >= 1126 && op <= 1131));
+ if (early_nl == first_nl)
+ debugout("\n");
+}
+
+
+void debug_cmd_out(int ip, integer op, int arg1, int arg2, int optype) {
+ int j;
+ const opdef *opdata;
+ rbool save_dbg_nomsg;
+
+ dbgprintf(" %2d:", ip);
+ save_dbg_nomsg = 0; /* Just to silence compiler warnings. */
+
+ opdata = get_opdef(op);
+ if (opdata == &illegal_def)
+ dbgprintf("ILLEGAL %d\n", op);
+ else {
+ if (op >= END_ACT) dbgprintf("!"); /* "Terminal" Actions */
+ else if (op <= MAX_COND) dbgprintf("?"); /* Condition */
+ if (op == 1063) { /* RandomMessage needs special handling */
+ save_dbg_nomsg = dbg_nomsg;
+ dbg_nomsg = 1;
+ }
+ dbgprintf("%s", opdata->opcode);
+ for (j = 0; j < opdata->argnum; j++) {
+ dbgprintf("\t");
+ argout(j == 0 ? opdata->arg1 : opdata->arg2 , j == 0 ? arg1 : arg2,
+ optype >> 2);
+ optype <<= 2;
+ }
+ if (op == 1063)
+ dbg_nomsg = save_dbg_nomsg;
+ }
+ debug_newline(op, 1);
+}
+
+
+void debug_head(int i) {
+ int v, w, a;
+
+ v = verb_code(command[i].verbcmd);
+ if (v >= BASE_VERB && v < BASE_VERB + DUMB_VERB && syntbl[synlist[v]] != 0)
+ w = syntbl[synlist[v]];
+ else w = command[i].verbcmd;
+ if (command[i].actor > 0) {
+ dbgprintf("CMD %d: ", i);
+ a = command[i].actor;
+ } else {
+ dbgprintf("REDIR: ");
+ a = -command[i].actor;
+ }
+
+ if (a == 2)
+ dbgprintf("anybody, ");
+ else if (a > 2) {
+ char *name;
+ name = objname(a);
+ name[0] = toupper(name[0]);
+ dbgprintf("%s, ", name);
+ rfree(name);
+ }
+
+ dbgprintf("%s ", w == 0 ? "any" : dict[w]);
+ if (command[i].noun_adj != 0)
+ dbgprintf("%s ", gdict(command[i].noun_adj));
+ dbgprintf("%s %s ", gdict(command[i].nouncmd),
+ (ver == 3) ? gdict(command[i].prep) : "->");
+ if (command[i].obj_adj != 0)
+ dbgprintf("%s ", gdict(command[i].obj_adj));
+ dbgprintf("%s\n", gdict(command[i].objcmd));
+
+}
+
+} // End of namespace AGT
+} // End of namespace Glk
diff --git a/engines/glk/agt/exec.cpp b/engines/glk/agt/exec.cpp
new file mode 100644
index 0000000000..983002aa44
--- /dev/null
+++ b/engines/glk/agt/exec.cpp
@@ -0,0 +1,1375 @@
+/* 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/exec.h"
+
+namespace Glk {
+namespace AGT {
+
+/* This file contains the wrapper for running player commands,
+ routines run at the end of the turn, and various other functions
+ needed by runverb.c and token.c. */
+
+#define global
+
+static rbool pronoun_mode;
+
+word realverb = 0; /* Name of current verb. (normally ~= input[vp])*/
+
+
+/* ------------------------------------------------------------------- */
+/* High level output functions, used for printing messages, error */
+/* messages, and everything else. They call the direct output functions */
+/* in interface.c The reason they're in runverb.c is that they need to */
+/* access item info in order to fill in the blanks */
+
+
+/* This updates the contents of compass_rose, which can be used by the
+ OS layer to print out some sort of representation of which way the
+ player can go. */
+
+static void set_compass_rose(void) {
+ int i, bit;
+
+ compass_rose = 0;
+ if (!islit()) return; /* No compass in darkness */
+ for (i = 0, bit = 1; i < 12; i++, bit <<= 1)
+ if (troom(room[loc].path[i])) compass_rose |= bit;
+}
+
+static void time_out(char *s) {
+ int hr, min;
+
+ hr = curr_time / 100;
+ min = curr_time % 100;
+
+ if (milltime_mode)
+ sprintf(s, "%02d:%02d", hr, min);
+ else {
+ if (hr > 12) hr = hr - 12;
+ if (hr == 0) hr = 12;
+ sprintf(s, "%2d:%02d %s", hr, min, (curr_time >= 1200) ? "pm" : "am");
+ }
+}
+
+
+
+
+
+void set_statline() {
+ char timestr[20];
+
+ recompute_score();
+ set_compass_rose();
+
+ rstrncpy(l_stat, room[loc].name, 81);
+
+ time_out(timestr);
+
+ switch (statusmode) {
+ case 0:
+ sprintf(r_stat, "Score: %ld Moves: %d", tscore, turncnt);
+ break;
+ case 1:
+ sprintf(r_stat, "Score: %ld %s", tscore, timestr);
+ break;
+ case 2:
+ sprintf(r_stat, "Moves: %d", turncnt);
+ break;
+ case 3:
+ sprintf(r_stat, "%s", timestr);
+ break;
+ case 4:
+ r_stat[0] = '\0';
+ break; /* 'Trinity style' status line */
+ case 5:
+ sprintf(r_stat, "Score: %ld", tscore);
+ break;
+ }
+}
+
+
+/* -------------------------------------------------------------------- */
+/* Message printing / $ substitution Routines */
+/* -------------------------------------------------------------------- */
+
+#define FILL_SIZE 100
+
+
+/* Tries to convert *pstr to a number, which it returns.
+ If it fails, or if the number is not in the range 0..maxval,
+ it returns -1.
+ It advances *pstr to point after the number and after the
+ terminating character, if relevant.
+ <term_char> is the terminating character; if this is 0, then
+ the calling routine will worry about the terminating character.
+ <maxval> of 0 indicates no upper bound
+*/
+
+static int extract_number(const char **pstr, int maxval,
+ char term_char) {
+ const char *s;
+ long n; /* n holds the value to be returned; i holds
+ the number of characters parsed. */
+ n = 0;
+ s = *pstr;
+ while (*s == ' ' || *s == '\t') s++;
+ for (; *s != 0; s++) {
+ if (*s < '0' || *s > '9') break;
+ n = 10 * n + (*s - '0');
+ if (maxval && n > maxval) return -1;
+ }
+ if (term_char) {
+ if (*s == term_char) s++;
+ else return -1;
+ }
+ *pstr = s;
+ return n;
+}
+
+#define BAD_PROP (-1000)
+
+/* This is used by #PROP[obj].[prop]# and $ATTR[obj].[attr]$, etc. */
+/* isprop: Are we looking for a property (as opposed to an attribute)? */
+static void extract_prop_val(const char **pstr,
+ int *id, int *val,
+ rbool isprop, const char term_char) {
+ const char *s;
+ int v; /* object number / final value */
+ int i; /* Attribute id */
+ rbool builtin; /* Expect builtin property or attribute? */
+
+ *id = i = BAD_PROP;
+ *val = 0; /* Failure case by default */
+ builtin = 0;
+ s = *pstr;
+ if (match_str(&s, "NOUN")) v = dobj;
+ else if (match_str(&s, "OBJECT")) v = iobj;
+ else v = extract_number(&s, maxcreat, 0); /* Must be object number */
+ while (*s == '.') {
+ s++;
+ if (*s == '-') {
+ builtin = 1;
+ s++;
+ } else
+ builtin = 0;
+ i = extract_number(&s, 0, 0);
+ if (!troom(v) && !tnoun(v) && !tcreat(v)) {
+ i = -1;
+ continue;
+ }
+ if (isprop || *s == '.') /* Treat as property */
+ v = builtin ? getprop(v, i) : op_objprop(2, v, i, 0);
+ else /* Treat as attribute */
+ v = builtin ? getattr(v, i) : op_objflag(2, v, i);
+ }
+ if (*s != term_char) return;
+ *pstr = s + 1;
+ if (i < 0) return;
+ *id = builtin ? -1 : i;
+ *val = v;
+}
+
+
+
+
+static word it_pronoun(int item, rbool ind_form)
+/* Return the correct pronoun to go with item;
+ ind_form is 1 if we want the object form, 0 if we want the
+ subject form */
+{
+ if (it_plur(item))
+ return (ind_form ? ext_code[wthem] : ext_code[wthey]);
+ if (tcreat(item))
+ switch (creature[item - first_creat].gender) {
+ case 0:
+ return ext_code[wit];
+ case 1:
+ return (ind_form ? ext_code[wher] : ext_code[wshe]);
+ case 2:
+ return (ind_form ? ext_code[whim] : ext_code[whe]);
+ }
+ return ext_code[wit];
+}
+
+/* This sets the value of "The" for the given noun. */
+/* (In particular, proper nouns shouldn't have a "the") */
+static void theset(char *buff, int item) {
+ if (it_proper(item))
+ strcpy(buff, "");
+ else
+ strcpy(buff, "the ");
+}
+
+
+static void num_name_func(parse_rec *obj_rec, char *fill_buff, word prev_adj)
+/* This is a subroutine to wordcode_match. */
+/* It gives either a noun name or a number, depending. */
+/* prev_adj is a word if this was preceded by its associated $adjective$;
+ the goal is to avoid having $adjective$ $noun$ expand to (e.g.)
+ 'silver silver' when the player types in "get silver" to pick up
+ a magic charm with synonym 'silver'. */
+{
+ word w;
+
+ if (obj_rec == NULL) {
+ strcpy(fill_buff, "");
+ return;
+ }
+
+ w = 0;
+ if (obj_rec->noun != 0) w = obj_rec->noun;
+ if ((w == 0 || w == prev_adj) && obj_rec->obj != 0)
+ w = it_name(obj_rec->obj);
+
+ if (w == 0) {
+ if (obj_rec->info == D_NUM) sprintf(fill_buff, "%ld", (long)obj_rec->num);
+ else strcpy(fill_buff, "");
+#if 0
+ strcpy(fill_buff, "that"); /* We can try and hope */
+#endif
+ return;
+ }
+
+ if (w == prev_adj) /* ... and prev_adj!=0 but we don't need to explicity
+ test that since w!=0 */
+ fill_buff[0] = 0; /* i.e. an empty string */
+ else {
+ rstrncpy(fill_buff, dict[w], FILL_SIZE);
+ if (it_proper(obj_rec->obj)) fill_buff[0] = toupper(fill_buff[0]);
+ }
+}
+
+static word get_adj(parse_rec *obj_rec, char *buff) {
+ word w;
+
+ if (obj_rec->adj != 0) w = obj_rec->adj;
+ else w = it_adj(obj_rec->obj);
+
+ if (w == 0) strcpy(buff, "");
+ else {
+ rstrncpy(buff, dict[w], FILL_SIZE);
+ if (it_proper(obj_rec->obj)) buff[0] = toupper(buff[0]);
+ }
+
+ return w;
+}
+
+
+
+#define d2buff(i) {rstrncpy(fill_buff,dict[i],FILL_SIZE);return 1;}
+#define num_name(obj_rec,jsa) {num_name_func(obj_rec,fill_buff,jsa);return 1;}
+/* jsa= Just seen adj */
+#define youme(mestr,youstr) {strcpy(fill_buff,irun_mode?mestr:youstr);\
+ return 1;}
+
+word just_seen_adj; /* This determines if we just saw $adjective$; if so,
+ this is set to it, otherwise it is zero. See
+ num_name_func above. */
+
+static int wordcode_match(const char **pvarname, char *fill_buff,
+ int context, const char *pword)
+/* Check <*p*pvarname> for a match; put subs text in fill_buf
+ <context> indicates who called us; this determines
+ what substitutions are valid. See interp.h for possible
+ values. Move *p*pvarname after whatever is matched.
+ <pword> contains the parse word when context is MSG_PARSE. */
+/* $ forms:
+ $verb$, $noun$, $adjective$, $prep$, $object$, $name$,
+ $n_pro$, $o_pro$, $n_indir$, $o_indir$,
+ $name_pro$, $name_indir$
+ $n_is$, $o_is$, $name_is$
+ $c_name$
+ $n_was$, $o_was$, $name_was$
+ $the_n$, $the_o$, $the_name$
+ */
+/* Also $STRn$, $FLAGn$, $ONn$, $OPENn$, $LOCKEDn$ */
+/* Support for FLAG, ON, OPEN, and LOCKED added by Mitch Mlinar */
+/* Return 0 if no match, 1 if there is */
+{
+ int hold_val, hold_id;
+
+ fill_buff[0] = 0; /* By default, return "\0" string */
+ if (match_str(pvarname, "STR")) { /* String variable */
+ hold_id = extract_number(pvarname, MAX_USTR, '$');
+ if (hold_id < 1) return 0;
+ rstrncpy(fill_buff, userstr[hold_id - 1], FILL_SIZE);
+ return 1;
+ } else if (match_str(pvarname, "VAR")) {
+ hold_id = extract_number(pvarname, VAR_NUM, '$');
+ if (hold_id < 0) return 0;
+ hold_val = agt_var[hold_id];
+ rstrncpy(fill_buff,
+ get_objattr_str(AGT_VAR, hold_id, hold_val), FILL_SIZE);
+ return 1;
+ } else if (match_str(pvarname, "FLAG")) {
+ hold_id = extract_number(pvarname, FLAG_NUM, '$');
+ if (hold_id < 0) return 0;
+ rstrncpy(fill_buff,
+ get_objattr_str(AGT_FLAG, hold_id, flag[hold_id]), FILL_SIZE);
+ return 1;
+ } else if (match_str(pvarname, "ATTR")) {
+ extract_prop_val(pvarname, &hold_id, &hold_val, 0, '$');
+ if (hold_id == BAD_PROP) return 1;
+ rstrncpy(fill_buff,
+ get_objattr_str(AGT_OBJFLAG, hold_id, hold_val), FILL_SIZE);
+ return 1;
+ } else if (match_str(pvarname, "PROP")) {
+ extract_prop_val(pvarname, &hold_id, &hold_val, 1, '$');
+ if (hold_id == BAD_PROP) return 1;
+ rstrncpy(fill_buff,
+ get_objattr_str(AGT_OBJPROP, hold_id, hold_val), FILL_SIZE);
+ return 1;
+ } else if (match_str(pvarname, "OPEN")) {
+ hold_val = extract_number(pvarname, maxnoun, '$');
+ strcpy(fill_buff, it_open(hold_val) ? "open" : "closed");
+ return 1;
+ } else if (match_str(pvarname, "ON")) {
+ hold_val = extract_number(pvarname, maxnoun, '$');
+ strcpy(fill_buff, it_on(hold_val) ? "on" : "off");
+ return 1;
+ } else if (match_str(pvarname, "LOCKED")) {
+ hold_val = extract_number(pvarname, maxnoun, '$');
+ strcpy(fill_buff, it_locked(hold_val, 0) ? "locked" : "unlocked");
+ return 1;
+ }
+
+ if (context == MSG_MAIN) return 0;
+
+ if (context == MSG_PARSE) {
+ /* The only special subsitution allowed is $word$. */
+ if (match_str(pvarname, "WORD$")) {
+ if (pword == NULL) fill_buff[0] = 0;
+ else rstrncpy(fill_buff, pword, FILL_SIZE);
+ return 1;
+ } else return 0;
+ }
+
+ /* d2buff is a macro that returns 1 */
+ if (match_str(pvarname, "NOUN$"))
+ num_name(dobj_rec, just_seen_adj);
+ just_seen_adj = 0; /* It doesn't matter. */
+ if (match_str(pvarname, "VERB$"))
+ d2buff(realverb); /* auxsyn[vb][0] */
+ if (match_str(pvarname, "OBJECT$"))
+ num_name(iobj_rec, 0);
+ if (match_str(pvarname, "NAME$"))
+ num_name(actor_rec, 0);
+ if (match_str(pvarname, "ADJECTIVE$")) {
+ just_seen_adj = get_adj(dobj_rec, fill_buff);
+ return 1;
+ }
+ if (match_str(pvarname, "PREP$"))
+ d2buff(prep);
+ if (match_str(pvarname, "N_PRO$"))
+ d2buff(it_pronoun(dobj, 0));
+ if (match_str(pvarname, "O_PRO$"))
+ d2buff(it_pronoun(iobj, 0));
+ if (match_str(pvarname, "NAME_PRO$"))
+ d2buff(it_pronoun(actor, 0));
+ if (match_str(pvarname, "N_INDIR$"))
+ d2buff(it_pronoun(dobj, 1));
+ if (match_str(pvarname, "O_INDIR$"))
+ d2buff(it_pronoun(iobj, 1));
+ if (match_str(pvarname, "NAME_INDIR$"))
+ d2buff(it_pronoun(actor, 1));
+ if (match_str(pvarname, "N_IS$")) {
+ if (!it_plur(dobj)) d2buff(ext_code[wis])
+ else d2buff(ext_code[ware]);
+ }
+ if (match_str(pvarname, "O_IS$")) {
+ if (!it_plur(iobj)) d2buff(ext_code[wis])
+ else d2buff(ext_code[ware]);
+ }
+ if (match_str(pvarname, "NAME_IS$")) {
+ if (!it_plur(actor)) d2buff(ext_code[wis])
+ else d2buff(ext_code[ware]);
+ }
+
+ if (match_str(pvarname, "N_WAS$")) {
+ if (!it_plur(dobj)) d2buff(ext_code[wwas])
+ else d2buff(ext_code[wwere]);
+ }
+ if (match_str(pvarname, "O_WAS$")) {
+ if (!it_plur(iobj)) d2buff(ext_code[wwas])
+ else d2buff(ext_code[wwere]);
+ }
+ if (match_str(pvarname, "NAME_WAS$")) {
+ if (!it_plur(actor)) d2buff(ext_code[wwas])
+ else d2buff(ext_code[wwere]);
+ }
+ if (match_str(pvarname, "THE_N$")) {
+ theset(fill_buff, dobj);
+ return 1;
+ }
+ if (match_str(pvarname, "THE_O$")) {
+ theset(fill_buff, iobj);
+ return 1;
+ }
+ if (match_str(pvarname, "THE_NAME$")) {
+ theset(fill_buff, actor);
+ return 1;
+ }
+ if (match_str(pvarname, "THE_C$")) {
+ theset(fill_buff, curr_creat_rec->obj);
+ return 1;
+ }
+ if (match_str(pvarname, "C_NAME$"))
+ num_name(curr_creat_rec, 0);
+ if (match_str(pvarname, "TIME$")) {
+ time_out(fill_buff);
+ return 1;
+ }
+
+ if (pronoun_mode && match_str(pvarname, "YOU$"))
+ youme("I", "you");
+ if (pronoun_mode && match_str(pvarname, "ARE$"))
+ youme("am", "are");
+ if (pronoun_mode && match_str(pvarname, "YOU_OBJ$"))
+ youme("me", "you");
+ if (pronoun_mode && match_str(pvarname, "YOUR$"))
+ youme("my", "your");
+ if (pronoun_mode && match_str(pvarname, "YOU'RE$"))
+ youme("i'm", "you're");
+ return 0; /* Don't recognize $word$ */
+}
+
+
+
+static int capstate(const char *varname) {
+ if (islower(varname[0])) return 0; /* $word$ */
+ if (islower(varname[1])) return 2; /* $Word$ */
+ if (!isalpha(varname[1]) && varname[1] != 0
+ && islower(varname[2])) return 2;
+ else return 1; /* $WORD$ */
+}
+
+static char fill_buff[FILL_SIZE]; /* Buffer to hold returned string */
+
+static char *wordvar_match(const char **pvarname, char match_type,
+ int context, const char *pword)
+/* Match_type=='#' for variables, '$' for parsed words */
+/* Possible # forms: #VARn#, #CNTn# */
+/* See above for $ forms */
+/* Moves *pvarname to point after matched object */
+{
+ int i, hold_val, hold_prop;
+ const char *start;
+
+ start = *pvarname;
+ if (match_type == '$') {
+ i = wordcode_match(pvarname, fill_buff, context, pword);
+ if (i == 0) return NULL;
+ /* Now need to fix capitalization */
+ switch (capstate(start)) {
+ case 0:
+ break; /* $word$ */
+ case 1: /* $WORD$ */
+ for (i = 0; fill_buff[i] != '\0'; i++)
+ fill_buff[i] = toupper(fill_buff[i]);
+ break;
+ case 2: /* $Word$ */
+ fill_buff[0] = toupper(fill_buff[0]);
+ break;
+ }
+ } else { /* So match type is '#' */
+ if (match_str(pvarname, "VAR")) {
+ hold_val = extract_number(pvarname, VAR_NUM, '#');
+ if (hold_val < 0) return NULL;
+ hold_val = agt_var[hold_val];
+ } else if (match_str(pvarname, "CNT") ||
+ match_str(pvarname, "CTR")) {
+ hold_val = extract_number(pvarname, CNT_NUM, '#');
+ if (hold_val < 0) return NULL;
+ hold_val = cnt_val(agt_counter[hold_val]);
+ } else if (match_str(pvarname, "PROP")) {
+ extract_prop_val(pvarname, &hold_prop, &hold_val, 1, '#');
+ if (hold_prop == BAD_PROP) hold_val = 0;
+ } else
+ return NULL;
+
+ /* Now to convert hold_val into a string */
+ sprintf(fill_buff, "%d", hold_val);
+
+ }
+ return fill_buff;
+}
+
+static char *format_line(const char *s, int context, const char *pword)
+/* Do $word$ substituations; return the result */
+{
+ char *t; /* The new string after all the substitutions. */
+ int t_size; /* How much space has been allocated for it. */
+ const char *p, *oldp; /* Pointer to the original string */
+ int i;
+ char *fill_word, *q; /* Word used to fill in the blanks, and a pointer
+ used to iterate through it*/
+ char fill_type; /* '#'=#variable#, '$'=$word$ */
+
+ /* Need to do subsitutions and also correct for tabs */
+ t_size = 200;
+ t = (char *)rmalloc(t_size + FILL_SIZE + 10);
+ just_seen_adj = 0;
+
+ /* Note that I leave some margin here: t is 310 characters, but i will never
+ be allowed above around 200. This is to avoid having to put special
+ checking code throughout the following to make sure t isn't overrun */
+ for (p = s, i = 0; *p != '\0'; p++) {
+ if (i >= t_size) {
+ t_size = i + 100;
+ t = (char *)rrealloc(t, t_size + FILL_SIZE + 10);
+ }
+ if (!rspace(*p) && *p != '$')
+ just_seen_adj = 0;
+ if (*p == '$' || *p == '#') {
+ /* Read in $word$ or #var# and do substitution */
+ fill_type = *p;
+ oldp = p++; /* Save old value in case we are wrong and then
+ increment p */
+ fill_word = wordvar_match(&p, fill_type, context, pword);
+ if (fill_word == NULL) {
+ /*i.e. no match-- so just copy it verbatim */
+ t[i++] = fill_type;
+ just_seen_adj = 0;
+ p = oldp; /* Go back and try again... */
+ } else { /* Fill in word */
+ p--;
+ if (fill_word[0] == '\0') { /* Empty string */
+ /* We need to eliminate a 'double space' in this case */
+ if ((oldp == s || rspace(*(oldp - 1))) && rspace(*(p + 1)))
+ p++;
+ } else /* Normal case */
+ for (q = fill_word; *q != '\0';)
+ t[i++] = *q++;
+ }
+ } /* End $/# matcher */
+ else
+ t[i++] = *p;
+ } /* End scanning loop */
+
+ if (aver < AGX00 && i > 0 && t[i - 1] == ' ') {
+ /* For pre-Magx, delete trailing spaces */
+ do
+ i--;
+ while (i > 0 && t[i] == ' ');
+ i++;
+ }
+ t[i] = 0;
+ t = (char *)rrealloc(t, i + 1);
+ return t;
+}
+
+void raw_lineout(const char *s, rbool do_repl, int context, const char *pword) {
+ char *outstr;
+
+ if (do_repl) {
+ outstr = format_line(s, context, pword);
+ writestr(outstr);
+ rfree(outstr);
+ } else
+ writestr(s);
+}
+
+
+static void lineout(const char *s, rbool nl, int context, const char *pword) {
+ raw_lineout(s, 1, context, pword);
+ if (nl) writeln("");
+ else writestr(" ");
+}
+
+static void gen_print_descr(descr_ptr dp_, rbool nl,
+ int context, const char *pword) {
+ int j;
+ descr_line *txt;
+
+ agt_textcolor(7);
+ textbold = 0;
+ agt_par(1);
+ txt = read_descr(dp_.start, dp_.size);
+ if (txt != NULL)
+ for (j = 0; txt[j] != NULL; j++)
+ lineout(txt[j], nl || (txt[j + 1] != NULL), context, pword);
+ free_descr(txt);
+ agt_par(0);
+ agt_textcolor(7);
+ textbold = 0;
+}
+
+void print_descr(descr_ptr dp_, rbool nl) {
+ gen_print_descr(dp_, nl, MSG_DESC, NULL);
+}
+
+void quote(int msgnum) {
+ char **qptr;
+ descr_line *txt;
+ int i;
+ int len;
+
+ txt = read_descr(msg_ptr[msgnum - 1].start, msg_ptr[msgnum - 1].size);
+ if (txt != NULL) {
+ for (len = 0; txt[len] != NULL; len++);
+ qptr = (char **)rmalloc(len * sizeof(char *));
+ for (i = 0; i < len; i++)
+ qptr[i] = format_line(txt[i], MSG_DESC, NULL);
+ free_descr(txt);
+ textbox(qptr, len, TB_BORDER | TB_CENTER);
+ rfree(qptr);
+ }
+}
+
+
+void msgout(int msgnum, rbool add_nl) {
+ print_descr(msg_ptr[msgnum - 1], add_nl);
+}
+
+
+#define MAX_NUM_ERR 240 /* Highest numbered STANDARD message */
+#define OLD_MAX_STD_MSG 185
+
+/* Fallback messages should always have msgid less than the original */
+int stdmsg_fallback[MAX_NUM_ERR - OLD_MAX_STD_MSG] = {
+ 0, 0, 0, 12, 0, /* 186 - 190 */
+ 0, 0, 0, 0, 0, /* 191 - 195 */
+ 0, 13, 13, 5, 10, /* 196 - 200 */
+ 10, 61, 10, 16, 59, /* 201 - 205 */
+ 90, 107, 116, 135, 140, /* 206 - 210 */
+ 184, 3, 47, 185, 61, /* 211 - 215 */
+ 0, 0, 0, 0, 0, /* 216 - 220 */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 221 - 230 */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 /* 231 - 240 */
+};
+
+void gen_sysmsg(int msgid, const char *s, int context, const char *pword)
+/* Prints either STANDARD message number <msgid> or default msg <s>;
+ A <msgid> of 0 means there is no standard message counterpart.
+ <context> determines what $$ substitutions are meaningful
+ <parseword> gives the $pword$ substitution for MSG_PARSE messages
+ msgid 3 should probably *not* be redirected to avoid giving hints to
+ the player as to what nouns exist in the game.
+*/
+{
+ /* Use gamefile's redefined version of message? */
+ rbool use_game_msg;
+ rbool nl; /* Should it be followed by a newline? */
+
+ nl = 1; /* By default, follow message with newline */
+
+ /* The following msgids shouldn't be followed by newlines: */
+ if (msgid == 1 || msgid == 145 || (msgid >= 218 && msgid <= 223)
+ || msgid == 225)
+ nl = 0;
+
+ if (DEBUG_SMSG) rprintf("\nSTD %d", msgid);
+
+ use_game_msg = ((PURE_SYSMSG || s == NULL)
+ && msgid != 0 && msgid <= NUM_ERR
+ && err_ptr != NULL);
+
+ if (use_game_msg) {
+ /* Check for fall-back messages */
+ if (err_ptr[msgid - 1].size <= 0
+ && msgid > OLD_MAX_STD_MSG && msgid <= MAX_NUM_ERR) {
+ msgid = stdmsg_fallback[msgid - OLD_MAX_STD_MSG - 1];
+ if (DEBUG_SMSG) rprintf("==> %3d");
+ }
+ if (msgid != 0 && err_ptr[msgid - 1].size > 0) {
+ if (DEBUG_SMSG) rprintf(" : From gamefile\n");
+ gen_print_descr(err_ptr[msgid - 1], nl, context, pword);
+ } else use_game_msg = 0;
+ }
+
+ if (DEBUG_SMSG && !use_game_msg) rprintf(" : Default\n");
+
+ if (!use_game_msg) {
+ /* Either the game doesn't redefine the message, or we're ignoring
+ redefinitions */
+ if (s == NULL) return;
+ pronoun_mode = 1;
+ lineout(s, nl, context, pword);
+ pronoun_mode = !PURE_PROSUB;
+ }
+}
+
+
+void sysmsg(int msgid, const char *s) {
+ gen_sysmsg(msgid, s, MSG_RUN, NULL);
+}
+
+
+void alt_sysmsg(int msgid, const char *s, parse_rec *new_dobjrec, parse_rec *new_iobjrec) {
+ parse_rec *save_dobjrec, *save_iobjrec;
+ integer save_dobj, save_iobj;
+
+ save_dobj = dobj;
+ save_dobjrec = dobj_rec;
+ dobj = p_obj(new_dobjrec);
+ dobj_rec = new_dobjrec;
+
+ save_iobj = iobj;
+ save_iobjrec = iobj_rec;
+ iobj = p_obj(new_iobjrec);
+ iobj_rec = new_iobjrec;
+
+ gen_sysmsg(msgid, s, MSG_RUN, NULL);
+
+ dobj = save_dobj;
+ dobj_rec = save_dobjrec;
+ iobj = save_iobj;
+ iobj_rec = save_iobjrec;
+}
+
+
+void sysmsgd(int msgid, const char *s, parse_rec *new_dobjrec)
+/* Front end for sysmsg w/alternative direct object */
+{
+ alt_sysmsg(msgid, s, new_dobjrec, NULL);
+}
+
+
+/* -------------------------------------------------------------------- */
+/* QUESTION and ANSWER processing */
+/* -------------------------------------------------------------------- */
+
+
+static char *match_string(char *ans, char *corr_ans, int n)
+/* Searches for s (w/ surrounding whitespace removed) inside ans */
+/* looking at only n characters of s */
+{
+ char *s;
+ char *corr;
+ int i;
+
+ s = rstrdup(corr_ans);
+ for (i = n - 1; i > 0 && isspace(s[i]); i--); /* Kill trailing whitespace */
+ s[i + 1] = 0;
+ for (i = 0; s[i] != 0; i++) s[i] = tolower(s[i]);
+ for (i = 0; isspace(s[i]); i++); /* Kill leading whitespace */
+ corr = strstr(ans, s + i);
+ rfree(s);
+ return corr;
+}
+
+
+static rbool check_answer(char *ans, long start, long size)
+/* qnum has already been fixed to start from 0 */
+/* Master's edition answer checker. Master's edition answers can */
+/* be seperate by AND and OR characters. If there is one OR in the */
+/* answer, all ANDs will also be treated as ORs */
+/* Furthermore, AND-delimited strings must appear in the correct order */
+/* unless PURE_ANSWER is false */
+{
+ char *corr, *corr2; /* Pointer into answer to match correct answers */
+ int match_mode; /* 0=AND mode, 1=OR mode */
+ descr_line *astr; /* Holds the answer string */
+ int i; /* Index to line of astr we're on. */
+ char *p, *q, *r; /* Used to break astr up into pieces and
+ loop over them */
+
+ astr = read_descr(start, size);
+ if (astr == NULL) {
+ if (!PURE_ERROR)
+ writeln("GAME ERROR: Empty answer field.");
+ return 1;
+ }
+
+ match_mode = 0;
+ for (i = 0; astr[i] != NULL; i++)
+ if (strstr(astr[i], "OR") != NULL) {
+ match_mode = 1;
+ break;
+ }
+
+ corr = ans;
+ for (i = 0; astr[i] != NULL; i++) { /* loop over all lines of the answer */
+ p = astr[i];
+ do {
+ q = strstr(p, "OR");
+ r = strstr(p, "AND");
+ if (q == NULL || (r != NULL && r < q)) q = r;
+ if (q == NULL) q = p + strlen(p); /* i.e. points at the concluding null */
+ corr2 = match_string(corr, p, q - p);
+ if (corr2 == NULL && match_mode == 0) {
+ free_descr(astr);
+ return 0;
+ }
+ if (corr2 != NULL && match_mode == 1) {
+ free_descr(astr);
+ return 1;
+ }
+ if (PURE_ANSWER && match_mode == 0) corr = corr2;
+ if (*q == 'O') p = q + 2;
+ else if (*q == 'A') p = q + 3;
+ else assert(*q == 0);
+ } while (*q != 0);
+ }
+ free_descr(astr);
+ if (match_mode == 0) return 1; /* AND: Matched them all */
+ else return 0; /* OR: Didn't find a single match */
+}
+
+
+/* Does the answer in string ans match answer anum? */
+/* Warning: this changes and then rfrees ans */
+rbool match_answer(char *ans, int anum) {
+ char *corr;
+ rbool ans_corr;
+
+ for (corr = ans; *corr != 0; corr++)
+ *corr = tolower(*corr);
+ if (answer != NULL) {
+ /* corr=strstr(ans,answer[anum]); */
+ corr = match_string(ans, answer[anum], strlen(answer[anum]));
+ rfree(ans);
+ if (corr == NULL) return 0;
+ } else if (ans_ptr != NULL) {
+ ans_corr = check_answer(ans, ans_ptr[anum].start, ans_ptr[anum].size);
+ rfree(ans);
+ return ans_corr;
+ } else writeln("INT ERR: Invalid answer pointer.");
+ return 1;
+
+}
+
+
+rbool ask_question(int qnum)
+/* 1=got it right, 0=got it wrong */
+{
+ char *ans;
+
+ qnum--;
+
+ /* Now actually ask the question and compare the answers */
+ if (question != NULL)
+ writeln(question[qnum]);
+ else if (quest_ptr != NULL)
+ print_descr(quest_ptr[qnum], 1);
+ else {
+ writeln("INT ERR: Invalid question pointer");
+ return 1;
+ }
+ ans = agt_readline(2);
+ return match_answer(ans, qnum);
+}
+
+
+/* -------------------------------------------------------------------- */
+/* Miscellaneous support routines */
+/* -------------------------------------------------------------------- */
+
+long read_number(void) {
+ char *s, *err;
+ long n;
+
+ n = 1;
+ do {
+ if (n != 1) gen_sysmsg(218, "Please enter a *number*. ", MSG_MAIN, NULL);
+ s = agt_readline(1);
+ n = strtol(s, &err, 10);
+ if (err == s) err = NULL;
+ rfree(s);
+ } while (err == NULL);
+ return n;
+}
+
+
+
+
+void runptr(int i, descr_ptr dp_[], const char *msg, int msgid,
+ parse_rec *nounrec, parse_rec *objrec)
+/* Prints out description unless it doesn't exist, in which
+ case it prints out either system message #msgid or the message
+ contained in msg. */
+{
+ if (dp_[i].size > 0) print_descr(dp_[i], 1);
+ else alt_sysmsg(msgid, msg, nounrec, objrec);
+}
+
+
+
+/* Score modes:
+ S:Score, R:Room +=list '(out of..'), -=don't list at all.
+ 0-- S+ R+
+ 1-- S+ R
+ 2-- S R+
+ 3-- S R
+ 4-- S+ R-
+ 5-- S R-
+ 6-- S- R+
+ 7-- S- R
+ 8-- S- R- and disable SCORE.
+ */
+
+
+void print_score(void) {
+ char s[80];
+ int i, rmcnt, totroom;
+
+ if (score_mode < 5) {
+ if (score_mode == 0 || score_mode == 1 || score_mode == 4)
+ sprintf(s, "Your score is %ld (out of %ld possible).", tscore, max_score);
+ else sprintf(s, "Your score is %ld.", tscore);
+ writeln(s);
+ }
+
+ if (score_mode < 4 || score_mode == 6 || score_mode == 7) {
+ rmcnt = 0;
+ totroom = 0;
+ for (i = 0; i <= maxroom - first_room; i++)
+ if (!room[i].unused) {
+ if (room[i].seen) rmcnt++;
+ /* Should really compute this once at the beginning, but */
+ /* I don't want to add yet another global variable, particulary */
+ /* since this is only used here. */
+ totroom++;
+ }
+ if (score_mode % 2 == 0)
+ sprintf(s, "You have visited %d locations (out of %d in the game)", rmcnt,
+ totroom);
+ else sprintf(s, "You have visited %d locations.", rmcnt);
+ writeln(s);
+ }
+}
+
+
+int normalize_time(int tnum) { /* Convert hhmm so mm<60 */
+ int min, hr;
+
+ min = tnum % 100; /* The minutes */
+ hr = tnum / 100; /* The hours */
+ hr += min / 60;
+ min = min % 60;
+ while (hr < 0) hr += 24;
+ hr = hr % 24;
+ return hr * 100 + min;
+}
+
+
+void add_time(int dt) {
+ int min, hr;
+
+ min = curr_time % 100; /* The minutes */
+ hr = curr_time / 100; /* The hours */
+ if (aver == AGT183) min += dt; /* AGT 1.83 version */
+ else { /* Normal version */
+ min += dt % 100;
+ hr += dt / 100;
+ }
+ while (min < 0) {
+ min = min + 60;
+ hr++;
+ }
+ hr += min / 60;
+ min = min % 60;
+ while (hr < 0) hr += 24;
+ hr = hr % 24;
+ curr_time = hr * 100 + min;
+}
+
+
+void look_room(void) {
+ compute_seen();
+ writeln("");
+ if (islit()) {
+ if (room[loc].name != NULL && room[loc].name[0] != 0 &&
+ (!PURE_ROOMTITLE)) {
+ agt_textcolor(-1); /* Emphasized text on */
+ writestr(room[loc].name);
+ agt_textcolor(-2);
+ writeln("");
+ } /* Emphasized text off */
+ if (room_firstdesc && room[loc].initdesc != 0)
+ msgout(room[loc].initdesc, 1);
+ else if (room_ptr[loc].size > 0)
+ print_descr(room_ptr[loc], 1);
+ print_contents(loc + first_room, 1);
+ if (listexit_flag)
+ v_listexit();
+ } else
+ sysmsg(room[loc].light == 1 ? 6 : 7,
+ "It is dark. $You$ can't see anything.");
+ room_firstdesc = 0;
+ do_look = 0;
+}
+
+
+static void run_autoverb(void) {
+ int v0; /* Will hold the verb number of the autoverb */
+ int savevb;
+ integer saveactor, savedobj, saveiobj;
+ parse_rec *save_actor_rec, *save_dobj_rec, *save_iobj_rec;
+ word saveprep;
+
+
+ beforecmd = 1;
+
+ /* This is the penalty for vb, actor, etc being global variables. */
+ savevb = vb;
+ saveactor = actor;
+ savedobj = dobj;
+ saveprep = prep;
+ saveiobj = iobj;
+ save_actor_rec = copy_parserec(actor_rec);
+ save_dobj_rec = copy_parserec(dobj_rec);
+ save_iobj_rec = copy_parserec(iobj_rec);
+
+ if (room[loc].autoverb != 0) {
+ v0 = verb_code(room[loc].autoverb);
+ (void)scan_metacommand(0, v0, 0, 0, 0, NULL);
+ }
+ free_all_parserec();
+ vb = savevb;
+ actor = saveactor;
+ dobj = savedobj;
+ iobj = saveiobj;
+ actor_rec = save_actor_rec;
+ dobj_rec = save_dobj_rec;
+ iobj_rec = save_iobj_rec;
+ prep = saveprep;
+}
+
+
+
+/* ------------------------------------------------------------------- */
+/* MAIN COMMAND EXECUTION ROUTINES-- */
+/* These routines handle the execution of player commands */
+/* Then they change the status line, update counters, etc. */
+/* ------------------------------------------------------------------- */
+
+static void creat_initdesc(void) {
+ int i;
+
+ creatloop(i)
+ if (creature[i].location == loc + first_room &&
+ creature[i].initdesc != 0) {
+ msgout(creature[i].initdesc, 1);
+ creature[i].initdesc = 0;
+ }
+}
+
+/* Print out picture names, remember to put intro before first one. */
+/* This should be called with s==NULL before and after:
+ before to reset it, after to put the trailing newline on. */
+void listpictname(const char *s) {
+ static rbool first_pict = 1; /* True until we output first picture */
+
+ if (s == NULL) {
+ if (!first_pict) writeln(""); /* Trailing newline */
+ first_pict = 1;
+ return;
+ }
+ if (first_pict) {
+ writeln(""); /* Skip a line */
+ sysmsg(219, " Illustrations:");
+ first_pict = 0;
+ }
+ writestr(" ");
+ writestr(s);
+}
+
+
+void listpict(int obj) {
+ char *s;
+
+ if (it_pict(obj) != 0) {
+ s = objname(obj);
+ listpictname(s);
+ rfree(s);
+ }
+}
+
+
+void list_viewable(void)
+/* List pictures that can be viewed, if any */
+{
+ int i;
+
+ listpictname(NULL);
+
+ if (room[loc].pict != 0)
+ listpictname("scene");
+ contloop(i, 1)
+ listpict(i);
+ contloop(i, 1000)
+ listpict(i);
+ contloop(i, loc + first_room)
+ listpict(i);
+
+ for (i = 0; i < maxpix; i++)
+ if (room[loc].PIX_bits & (1L << i))
+ listpictname(dict[pix_name[i]]);
+ listpictname(NULL);
+}
+
+
+
+void newroom(void) {
+ rbool save_do_look;
+ integer prevloc;
+
+ do {
+ save_do_look = do_look;
+ if (do_look == 1) look_room();
+ creat_initdesc();
+ if (save_do_look == 1 && aver >= AGTME10)
+ list_viewable(); /* Print out picts that can be viewed here. */
+ do_look = 0;
+
+ prevloc = loc;
+ if (do_autoverb) {
+ do_autoverb = 0;
+ run_autoverb();
+ }
+
+ if (!room[loc].seen) { /* This only runs on the first turn */
+ room[loc].seen = 1;
+ tscore += room[loc].points;
+ }
+ } while (prevloc != loc); /* Autoverb could move player */
+}
+
+
+static int min_delta(void) {
+ return (aver == AGT183) ? 1 : 0 ;
+}
+
+
+void increment_turn(void) {
+ int i;
+
+ compute_seen();
+
+ newlife_flag = 0;
+
+ if (quitflag) return;
+
+ newroom();
+
+ if (winflag || deadflag || endflag) return;
+
+ if (was_metaverb) return; /* No time should pass during a metaverb. */
+
+ turncnt++;
+ /* Now increment the time counter */
+ if (delta_time > 0) {
+ if (PURE_TIME)
+ add_time(agt_rand(min_delta(), delta_time));
+ else /* if !PURE_TIME */
+ add_time(delta_time);
+ }
+
+ for (i = 0; i <= CNT_NUM; i++)
+ if (agt_counter[i] >= 0) ++agt_counter[i];
+ creatloop(i)
+ if (creature[i].location == loc + first_room && creature[i].hostile &&
+ creature[i].timethresh > 0) {
+ parse_rec tmpcreat; /* Used for creature messages */
+ make_parserec(i + first_creat, &tmpcreat);
+ curr_creat_rec = &tmpcreat;
+
+ if (++creature[i].timecounter >= creature[i].timethresh) {
+ /* Creature attacks */
+ sysmsg(16, "$The_c$$c_name$ suddenly attacks $you_obj$!");
+ sysmsg(creature[i].gender == 0 ? 17 : 18,
+ " $You$ try to defend $your$self, but $the_c$$c_name$ "
+ "kills $you_obj$ anyhow.");
+ deadflag = 1;
+ } else /* 'Angrier' messages */
+ if (creature[i].timethresh > 0 &&
+ creature[i].timecounter > creature[i].timethresh - 3)
+ sysmsg(15, "$The_c$$c_name$ seems to be getting angrier.");
+ }
+}
+
+
+/* Wrapper for increment_turn used by exec routines below.
+ This just checks to make sure we're not one of the 1.8x versions
+ (which call increment turn from elsewhere) */
+static void exec_increment_turn(void) {
+ if (PURE_AFTER) increment_turn();
+}
+
+static void end_turn(void) {
+ if (textbold) agt_textcolor(-2);
+ textbold = 0;
+ set_statline();
+
+ if (quitflag) return;
+
+ if (notify_flag && !was_metaverb) {
+ if (old_score < tscore)
+ sysmsg(227, " [Your score just went up]");
+ else if (old_score > tscore)
+ sysmsg(228, " [Your score just went down]");
+ }
+ old_score = tscore;
+
+}
+
+
+
+static void set_pronoun(int item) {
+ if (item == 0) return;
+ switch (it_gender(item)) {
+ case 0:
+ if (it_plur(item))
+ last_they = item;
+ last_it = item; /* Change: last_it will be set even if the
+ noun is plural */
+ break;
+ case 1:
+ last_she = item;
+ break;
+ case 2:
+ last_he = item;
+ break;
+ }
+}
+
+
+/* True if the current noun is the last one in the list. */
+static rbool lastnoun(parse_rec *list) {
+ if (list->info == D_END) return 1;
+ list++;
+ while (list->info == D_AND) list++;
+ return (list->info == D_END);
+}
+
+
+
+
+static void runverbs(parse_rec *actor0, int vnum,
+ parse_rec *lnoun, word prep0, parse_rec *iobj0)
+/* The zeros are postpended simply to avoid a name conflict */
+{
+ parse_rec *currnoun;
+
+ textbold = 0;
+ do_look = 0;
+ do_autoverb = 0;
+ was_metaverb = 0;
+ actor = actor0->obj;
+ actor_rec = copy_parserec(actor0);
+ vb = vnum;
+ dobj = lnoun[0].obj;
+ dobj_rec = copy_parserec(lnoun);
+ prep = prep0;
+ iobj = iobj0->obj;
+ iobj_rec = copy_parserec(iobj0);
+ set_pronoun(actor); /* Basically the last one that isn't 0 will stick */
+ set_pronoun(iobj0->obj);
+ was_metaverb = 0; /* Most verbs are not metaverbs; assume this by default */
+ start_of_turn = 1;
+ end_of_turn = 0;
+
+ if (lnoun[0].info == D_END || lnoun[0].info == D_ALL) {
+ end_of_turn = 1;
+ exec_verb();
+ if (doing_restore) {
+ free_all_parserec();
+ return;
+ }
+ if (PURE_AND) exec_increment_turn();
+ } else for (currnoun = lnoun; currnoun->info != D_END; currnoun++)
+ if (currnoun->info != D_AND) {
+ free_all_parserec();
+ end_of_turn = lastnoun(currnoun);
+ actor = actor0->obj;
+ actor_rec = copy_parserec(actor0);
+ vb = vnum;
+ dobj = currnoun->obj;
+ dobj_rec = copy_parserec(currnoun);
+ iobj = iobj0->obj;
+ iobj_rec = copy_parserec(iobj0);
+ set_pronoun(dobj);
+ exec_verb();
+ if (doing_restore) return;
+ if (PURE_AND)
+ exec_increment_turn();
+ else
+ start_of_turn = 0;
+ if (quitflag || winflag || deadflag || endflag)
+ break;
+ }
+ assert(end_of_turn);
+ if (!PURE_AND) exec_increment_turn();
+ end_turn();
+ free_all_parserec();
+}
+
+
+/* The following store values for use by AGAIN */
+/* (so AGAIN can be implemented just by executing runverbs w/ the saved
+ values) */
+static int save_vnum;
+static word save_prep;
+static parse_rec save_actor;
+static parse_rec save_obj;
+parse_rec *save_lnoun = NULL;
+
+
+
+void exec(parse_rec *actor_, int vnum,
+ parse_rec *lnoun, word prep_, parse_rec *iobj_) {
+
+ cmd_saveable = 0;
+ pronoun_mode = !PURE_PROSUB;
+
+ if (vnum == verb_code(ext_code[wagain]) && lnoun[0].info == D_END
+ && iobj_->info == D_END &&
+ (actor_->info == D_END || actor_->obj == save_actor.obj))
+ if (save_lnoun == NULL) {
+ rfree(lnoun);
+ sysmsg(186,
+ "You can't use AGAIN until you've entered at least one command.");
+ return;
+ } else {
+ memcpy(actor_, &save_actor, sizeof(parse_rec));
+ vnum = save_vnum;
+ prep_ = save_prep;
+ memcpy(iobj_, &save_obj, sizeof(parse_rec));
+ rfree(lnoun);
+ lnoun = save_lnoun;
+ save_lnoun = NULL;
+ }
+ else
+ realverb = input[vp];
+
+
+ runverbs(actor_, vnum, lnoun, prep_, iobj_);
+
+ if (cmd_saveable) {
+ if (save_lnoun != NULL) rfree(save_lnoun);
+
+ memcpy(&save_actor, actor_, sizeof(parse_rec));
+ save_vnum = vnum;
+ save_lnoun = lnoun;
+ lnoun = NULL;
+ save_prep = prep_;
+ memcpy(&save_obj, iobj_, sizeof(parse_rec));
+ } else
+ rfree(lnoun);
+}
+
+} // End of namespace AGT
+} // End of namespace Glk
diff --git a/engines/glk/agt/exec.h b/engines/glk/agt/exec.h
new file mode 100644
index 0000000000..d82ccf0504
--- /dev/null
+++ b/engines/glk/agt/exec.h
@@ -0,0 +1,272 @@
+/* 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.
+ *
+ */
+
+
+namespace Glk {
+namespace AGT {
+
+#ifndef global /* Don't touch this */
+#define global extern
+#define global_defined_exec
+#endif
+
+
+
+/* This contains the decoding of the current instruction */
+struct op_rec {
+ integer op;
+ int arg1;
+ int arg2;
+ int optype;
+ int argcnt; /* Actual number of argument words present */
+ const opdef *opdata;
+ const char *errmsg;
+ rbool disambig; /* Trigger disambiguation? */
+ rbool negate; /* NOT? (cond token only) */
+ rbool failmsg; /* Run only on failure? */
+ rbool endor; /* End any OR blocks? (action tokens, mainly) */
+} ;
+
+
+
+/* The following determines if we are doing disambiguation
+ or actually executing a verb */
+global uchar do_disambig; /* 0= execution
+ 1= disambiguating noun
+ 2= disambiguating object */
+
+
+/* Flags used during turn execution */
+global rbool beforecmd; /* Only used by 1.8x games */
+global rbool supress_debug; /* Causes debugging info to _not_ be printed
+ even if debugging is on; used by disambiguator
+ and to supress ANY commands */
+global rbool was_metaverb; /* Was the verb that just executed a metaverb? */
+/* Metaverbs are commands that should not take game time
+to execute: SAVE, RESTORE, RESTART, QUIT, SCRIPT, UNSCRIPT,
+NOTIFY, SCORE, etc. */
+global integer oldloc; /* Save old location for NO_BLOCK_HOSTILE purposes */
+
+/* This is a hack to pass the subroutine number from exec_token
+ back to scan_metacommand when a DoSubroutine is done */
+global integer subcall_arg;
+
+/* This fixes a bug in the original AGT spec, causing "actor, verb ..."
+ commands to misfire if there is more than one creature of the same
+ name. */
+global integer *creat_fix;
+
+
+/* -------------------------------------------------------------------- */
+/* Defined in EXEC.C */
+/* -------------------------------------------------------------------- */
+extern void raw_lineout(const char *s, rbool do_repl,
+ int context, const char *pword);
+extern void msgout(int msgnum, rbool add_nl);
+extern void sysmsg(int msgid, const char *s);
+extern void alt_sysmsg(int msgid, const char *s, parse_rec *new_dobjrec,
+ parse_rec *new_iobjrec);
+extern void sysmsgd(int msgid, const char *s, parse_rec *new_dobj_rec);
+
+rbool ask_question(int qnum);
+extern void increment_turn(void);
+
+/* Warning: the following function rfrees <ans> */
+extern rbool match_answer(char *ans, int anum);
+
+extern void look_room(void);
+extern void runptr(int i, descr_ptr dp[], const char *msg, int msgid,
+ parse_rec *nounrec, parse_rec *objrec);
+
+extern int normalize_time(int tnum); /* Convert hhmm so mm<60 */
+extern void add_time(int dt);
+
+
+/* -------------------------------------------------------------------- */
+/* Defined in OBJECT.C */
+/* -------------------------------------------------------------------- */
+extern parse_rec *make_parserec(int obj, parse_rec *rec);
+extern parse_rec *copy_parserec(parse_rec *rec);
+extern void free_all_parserec(void); /* Freeds doj_rec, iobj_rec, and actor_rec */
+
+extern rbool in_scope(int item);
+extern rbool islit(void);
+extern rbool it_possess(int item);
+extern rbool it_proper(int item);
+extern rbool it_isweapon(int item);
+extern rbool it_door(int obj, word noun); /* Is obj a door? */
+extern rbool is_within(integer obj1, integer obj2, rbool stop_if_closed);
+
+extern integer it_room(int item); /* Returns the room that the item is in */
+
+extern int lightcheck(int parent, int roomlight, rbool active);
+/* If active is false, we don't care if the light is actually working. */
+
+#define it_move(a,b) it_reposition(a,b,0)
+#define it_destroy(item) it_move(item,0)
+#define get_obj(dobj) it_move(dobj,1)
+#define drop_obj(dobj) it_move(dobj,loc+first_room)
+
+extern void it_reposition(int item, int newloc, rbool save_pos);
+extern void goto_room(int newroom);
+
+extern void it_describe(int dobj);
+extern int print_contents(int obj, int ind_lev);
+
+extern void recompute_score(void);
+
+extern int check_fit(int obj1, int obj2);
+
+/* And its possible return values: */
+
+#define FIT_OK 0 /* Fits */
+#define FIT_WEIGHT 1 /* Too heavy [*] */
+#define FIT_NETWEIGHT 2 /* With other stuff is too heavy [*] */
+#define FIT_SIZE 3 /* Too big */
+#define FIT_NETSIZE 4 /* With other stuff is too big */
+/* [*]-- These can only occur if obj2==1 or for ME/1.5-1.7 */
+
+
+extern long getprop(int obj, int prop);
+extern void setprop(int obj, int prop, long val);
+extern rbool getattr(int obj, int prop);
+extern void setattr(int obj, int prop, rbool val);
+
+extern rbool matchclass(int obj, int oclass);
+
+/* ---------------------------------------------------------------------- */
+/* Define in RUNVERB.C */
+/* ---------------------------------------------------------------------- */
+
+/* Verbs actually used elsewhere in th interpreter */
+extern void v_inventory(void);
+extern void v_look(void);
+extern void v_listexit(void);
+
+/* The routine that actually runs the current player command */
+extern void exec_verb(void);
+
+
+/* ---------------------------------------------------------------------- */
+/* In METACOMMAND.C */
+/* ---------------------------------------------------------------------- */
+/* The main routine to search the metacommand list and run the appropriate
+ meta-commands */
+extern int scan_metacommand(integer m_actor, int vcode,
+ integer m_dobj, word m_prep, integer m_iobj,
+ int *redir_flag);
+
+/* The type checking routine */
+rbool argvalid(int argtype, int arg);
+
+/* ---------------------------------------------------------------------- */
+/* In TOKEN.C */
+/* ---------------------------------------------------------------------- */
+extern int exec_instr(op_rec *oprec); /* Execute instruction */
+extern long pop_expr_stack(void); /* Wrapper around routine to access TOS */
+
+/* ---------------------------------------------------------------------- */
+/* Defined in DEBUGCMD.C */
+/* ---------------------------------------------------------------------- */
+extern void get_debugcmd(void); /* Get and execute debugging commands */
+
+
+/* ------------------------------------------------------------------- */
+/* Macros for getting information about items */
+/* (mainly used to blackbox the difference between nouns and creatures) */
+/* -------------------------------------------------------------------- */
+
+/* A note on object codes:
+ <0 obj is a 'virtual' object, existing only as the word
+ dict[-obj], e.g. DOOR, flag nouns, global nouns
+ 0 No object (or any object)
+ 1 Self(i.e. the player)
+ first_room..last_room Rooms
+ first_noun..last_noun Nouns
+ first_creat..last_creat Creatures
+ 1000 Being worn by the player */
+
+
+/* The following macro loops over the contents of an object */
+#define contloop(i,obj) for(i=it_contents(obj);i!=0;i=it_next(i))
+#define safecontloop(i,j,obj) for(i=it_contents(obj),j=it_next(i); \
+ i!=0;i=j,j=it_next(i))
+
+#define cnt_val(c) ((c)==-1 ? 0 : (c))
+
+
+/* -------------------------------------------------------------------- */
+/* These are the macros that should usually be used to determine */
+/* information about the objects in the game, unless the object type */
+/* is definitely known */
+/* ------------------------------------------------------------------- */
+
+#define it_on(item) nounattr(item,on)
+#define it_group(item) creatattr(item,groupmemb)
+#define it_adj(item) objattr(item,adj)
+#define it_pushable(item) nounattr(item,pushable)
+#define it_pullable(item) nounattr(item,pullable)
+#define it_turnable(item) nounattr(item,turnable)
+#define it_playable(item) nounattr(item,playable)
+#define it_plur(item) nounattr(item,plural)
+#define it_gender(item) creatattr(item,gender)
+
+#define it_pict(item) objattr(item,pict)
+#define it_class(item) anyattr(item,oclass)
+#define it_next(item) objattr(item,next)
+#define it_isglobal(item) objattr(item,isglobal)
+#define it_flagnum(item) objattr(item,flagnum)
+#define it_seen(item) anyattr(item,seen)
+
+
+#define it_name(item) objattr2(item,name,(item<0) ? -item : 0)
+#define it_open(item) nounattr2(item,open, tcreat(item) || \
+ (tdoor(item) && !room[loc].locked_door))
+
+/* This checks to make sure the object isn't unmovable. */
+/* (As such, all non-nouns automatically pass) */
+#define it_canmove(item) (!tnoun(item) || noun[(item)-first_noun].movable)
+
+
+#ifdef IT_MACRO
+#define it_contents(item) objattr2(item,contents,\
+ roomattr2(item,contents,\
+ (item==1) ? player_contents : \
+ (item==1000) ? player_worn : 0))
+#define it_lockable(item) nounattr2(item,lockable, (tdoor(item) ? 1 : 0) )
+#define it_locked(item,name) nounattr2(item,locked,\
+ (tdoor(item) && room[loc].locked_door ? \
+ 1 : 0))
+#else
+extern int it_contents(integer obj);
+extern rbool it_lockable(integer obj, word noun);
+extern rbool it_locked(integer obj, word noun);
+#endif
+
+
+#ifdef global_defined_exec
+#undef global
+#undef global_defined_exec
+#endif
+
+} // End of namespace AGT
+} // End of namespace Glk
diff --git a/engines/glk/agt/filename.cpp b/engines/glk/agt/filename.cpp
new file mode 100644
index 0000000000..75f18f8bea
--- /dev/null
+++ b/engines/glk/agt/filename.cpp
@@ -0,0 +1,692 @@
+/* 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"
+
+namespace Glk {
+namespace AGT {
+
+#ifdef force16
+#undef int
+#endif
+
+#ifdef UNIX_IO
+#include <fcntl.h>
+#include <sys/stat.h> /* Needed only for file permission bits */
+
+#ifdef __STRICT_ANSI__
+int fileno(FILE *f);
+FILE *popen(char *s, char *how);
+int pclose(FILE *p);
+#endif
+
+#ifdef MSDOS16
+#include <io.h>
+#endif
+#endif /* UNIX_IO */
+
+#ifdef force16
+#define int short
+#endif
+
+
+/*----------------------------------------------------------------------*/
+/* Filetype Data */
+/*----------------------------------------------------------------------*/
+const char *extname[] = {
+ "",
+ DA1, DA2, DA3, DA4, DA5, DA6, DSS,
+ pHNT, pOPT, pTTL,
+ pSAV, pSCR, pLOG,
+ pAGX, pINS, pVOC, pCFG,
+ pAGT, pDAT, pMSG, pCMD, pSTD, AGTpSTD
+};
+
+
+#ifdef PATH_SEP
+static const char *path_sep = PATH_SEP;
+#else
+static const char *path_sep = NULL;
+#endif
+
+/* This returns the options to use when opening the given file type */
+/* rw is true if we are writing, false if we are reading. */
+const char *filetype_info(filetype ft, rbool rw) {
+ if (ft < fTTL) return "rb";
+ if (ft == fAGX) return rw ? "wb" : "rb";
+ if (ft == fSAV) return (rw ? "wb" : "rb");
+ if (ft == fTTL || ft == fINS || ft == fVOC) return "rb";
+#ifdef OPEN_AS_TEXT
+ if (ft >= fCFG) return (open_as_binary ? "rb" : "r");
+#else
+ if (ft >= fCFG) return "rb";
+#endif
+ if (ft == fSCR) {
+ if (rw)
+ return (BATCH_MODE || make_test) ? "w" : "a";
+ else return "r";
+ }
+ if (ft == fLOG) return rw ? "w" : "r";
+ fatal("INTERNAL ERROR: Invalid filetype.");
+ return NULL;
+}
+
+
+/* Returns true if ft is a possible extension in general context ft_base */
+static rbool compat_ext(filetype ft, filetype ft_base) {
+ if (ft_base == fNONE || ft_base == fDA1 || ft_base == fAGX) { /* Game file */
+ return (ft >= fDA1 && ft <= fDSS)
+ || ft == fOPT || ft == fTTL
+ || (ft >= fAGX && ft <= fCFG);
+ }
+
+ if (ft_base == fSAV || ft_base == fSCR || ft_base == fLOG)
+ return (ft == ft_base);
+
+ if (ft_base == fAGT) { /* Source code */
+ return (ft >= fAGT && ft <= fCMD)
+ || ft == fTTL || ft == fCFG;
+ }
+
+ fatal("INTERNAL ERROR: Invalid file class.");
+ return 0;
+}
+
+
+
+/*----------------------------------------------------------------------*/
+/* Misc. utilities */
+/*----------------------------------------------------------------------*/
+
+char *assemble_filename(const char *path, const char *root,
+ const char *ext) {
+ int len1, len2, len3;
+ char *name;
+
+ len1 = len2 = len3 = 0;
+ if (path != NULL) len1 = strlen(path);
+ if (root != NULL) len2 = strlen(root);
+ if (ext != NULL) len3 = strlen(ext);
+ name = (char *)rmalloc(len1 + len2 + len3 + 1);
+ if (path != NULL) memcpy(name, path, len1);
+#ifdef PREFIX_EXT
+ if (ext != NULL) memcpy(name + len1, ext, len3);
+ if (root != NULL) memcpy(name + len1 + len3, root, len2);
+#else
+ if (root != NULL) memcpy(name + len1, root, len2);
+ if (ext != NULL) memcpy(name + len1 + len2, ext, len3);
+#endif
+ name[len1 + len2 + len3] = 0;
+ return name;
+}
+
+#ifdef PATH_SEP
+/* This works for binary files; we don't care about non-binary
+ files since this only used to search for game files. */
+static rbool file_exist(const char *fname) {
+ return Common::File::exists(fname);
+}
+#endif
+
+/* This checks to see if c matches any of the characters in matchset */
+static rbool smatch(char c, const char *matchset) {
+ for (; *matchset != 0; matchset++)
+ if (*matchset == c) return 1;
+ return 0;
+}
+
+
+
+/*----------------------------------------------------------------------*/
+/* Taking Apart the Filename */
+/*----------------------------------------------------------------------*/
+
+static int find_path_sep(const char *name) {
+ int i;
+
+ if (path_sep == NULL)
+ return -1;
+ for (i = strlen(name) - 1; i >= 0; i--)
+ if (smatch(name[i], path_sep)) break;
+ return i;
+}
+
+
+/* Checks to see if the filename (which must be path-free)
+ has an extension. Returns the length of the extensions
+ and writes the extension type in pft */
+static int search_for_ext(const char *name, filetype base_ft,
+ filetype *pft) {
+ filetype t;
+ int xlen, len;
+
+ *pft = fNONE;
+ len = strlen(name);
+ if (len == 0) return 0;
+ for (t = (filetype)(fNONE + 1); t <= fSTD; t = (filetype)((int)t + 1))
+ if (compat_ext(t, base_ft)) {
+ xlen = strlen(extname[t]);
+ if (xlen == 0 || xlen > len) continue;
+#ifdef PREFIX_EXT
+ if (strncasecmp(name, extname[t], xlen) == 0)
+#else
+ if (fnamecmp(name + len - xlen, extname[t]) == 0)
+#endif
+ {
+ *pft = t;
+ return xlen;
+ }
+ }
+#ifdef UNIX_IO
+ /* This is code to make the Unix/etc ports easier to use under
+ tab-completing shells (which often complete "gamename._" or
+ "gamename.ag_" since there are other files in the directory
+ with the same root.) */
+ assert(*pft == fNONE);
+ if (name[len - 1] == '.') return 1;
+ if (fnamecmp(name + len - 3, ".ag") == 0) {
+ if (base_ft == fDA1 || base_ft == fAGX) *pft = fAGX;
+ if (base_ft == fAGT) *pft = fAGT;
+ }
+ if (fnamecmp(name + len - 3, ".da") == 0) {
+ if (base_ft == fDA1 || base_ft == fAGX) *pft = fDA1;
+ if (base_ft == fAGT) *pft = fAGT;
+ }
+ if (*pft != fNONE) return 3;
+#endif
+ return 0;
+}
+
+
+/* Extract root filename or extension from
+ pathless name, given that the extension is of length extlen. */
+/* If isext is true, extract the extension. If isext is false,
+ then extrac the root. */
+static char *extract_piece(const char *name, int extlen, rbool isext) {
+ char *root;
+ int len, xlen;
+ rbool first; /* If true, extract from beginning; if false, extract
+ from end */
+
+ len = strlen(name) - extlen;
+ xlen = extlen;
+ if (isext) {
+ int tmp;
+ tmp = len;
+ len = xlen;
+ xlen = tmp;
+ }
+ if (len == 0) return NULL;
+ root = (char *)rmalloc((len + 1) * sizeof(char));
+#ifdef PREFIX_EXT
+ first = isext ? 1 : 0;
+#else
+ first = isext ? 0 : 1;
+#endif
+ if (first) {
+ memcpy(root, name, len);
+ root[len] = 0;
+ } else {
+ memcpy(root, name + xlen, len);
+ root[len] = 0;
+ }
+ return root;
+}
+
+
+/* This returns true if "path" is absolute, false otherwise.
+ This is _very_ platform dependent. */
+static rbool absolute_path(char *path) {
+#ifdef pathtest
+ return pathtest(path);
+#else
+ return 1;
+#endif
+}
+
+/*----------------------------------------------------------------------*/
+/* Basic routines for dealing with file contexts */
+/*----------------------------------------------------------------------*/
+
+#define FC(x) ((file_context_rec*)(x))
+
+/* formal_name is used to get filenames for diagnostic messages, etc. */
+char *formal_name(fc_type fc, filetype ft) {
+ if (FC(fc)->special) return FC(fc)->gamename;
+ if (ft == fNONE)
+ return rstrdup(FC(fc)->shortname);
+ if (ft == fAGT_STD)
+ return rstrdup(AGTpSTD);
+ return assemble_filename("", FC(fc)->shortname, extname[ft]);
+}
+
+#ifdef PATH_SEP
+static rbool test_file(const char *path, const char *root, const char *ext) {
+ char *name;
+ rbool tmp;
+
+ name = assemble_filename(path, root, ext);
+ tmp = file_exist(name);
+ rfree(name);
+ return tmp;
+}
+
+/* This does a path search for the game files. */
+static void fix_path(file_context_rec *fc) {
+ char **ppath;
+
+
+ if (gamepath == NULL) return;
+ for (ppath = gamepath; *ppath != NULL; ppath++)
+ if (test_file(*ppath, fc->shortname, fc->ext)
+ || test_file(*ppath, fc->shortname, pAGX)
+ || test_file(*ppath, fc->shortname, DA1)) {
+ fc->path = rstrdup(*ppath);
+ return;
+ }
+}
+#endif
+
+
+/* This creates a new file context based on gamename. */
+/* ft indicates the rough use it will be put towards:
+ ft=fNONE indicates it's the first pass read, before PATH has been
+ read in, and so the fc shouldn't be filled out until
+ fix_file_context() is called.
+ ft=pDA1 indicates that name refers to the game files.
+ ft=pAGX indicates the name of the AGX file to be written to.
+ ft=pSAV,pLOG,pSCR all indicate that name corresponds to the
+ related type of file. */
+fc_type init_file_context(const char *name, filetype ft) {
+ file_context_rec *fc;
+ int p, x; /* Path and extension markers */
+
+ fc = (file_context_rec *)rmalloc(sizeof(file_context_rec));
+ fc->special = 0;
+
+#ifdef UNIX
+ if (name[0] == '|') { /* Output pipe */
+ name++;
+ fc->special = 1;
+ }
+#endif
+
+ fc->gamename = rstrdup(name);
+
+#ifdef UNIX
+ x = strlen(fc->gamename);
+ if (fc->gamename[x - 1] == '|') { /* Input pipe */
+ fc->gamename[x - 1] = 0;
+ fc->special |= 2;
+ }
+ if (fc->special) {
+ fc->path = fc->shortname = fc->ext = NULL;
+ return fc;
+ }
+#endif
+
+ p = find_path_sep(fc->gamename);
+ if (p < 0)
+ fc->path = NULL;
+ else {
+ fc->path = (char *)rmalloc((p + 2) * sizeof(char));
+ memcpy(fc->path, fc->gamename, p + 1);
+ fc->path[p + 1] = '\0';
+ }
+ x = search_for_ext(fc->gamename + p + 1, ft, &fc->ft);
+ fc->shortname = extract_piece(fc->gamename + p + 1, x, 0);
+ fc->ext = extract_piece(fc->gamename + p + 1, x, 1);
+
+#ifdef PATH_SEP
+ if (fc->path == NULL && ft == fDA1)
+ fix_path(fc);
+#endif
+ return fc;
+}
+
+
+void fix_file_context(fc_type fc, filetype ft) {
+#ifdef PATH_SEP
+ if (FC(fc)->path == NULL && ft == fDA1)
+ fix_path(FC(fc));
+#endif
+}
+
+
+/* This creates new file contexts from old. */
+/* This is used to create save/log/script filenames from the game name,
+ and to create include files in the same directory as the source file. */
+fc_type convert_file_context(fc_type fc, filetype ft, const char *name) {
+ file_context_rec *nfc;
+ rbool local_ftype; /* Indicates file should be in working directory,
+ not game directory. */
+
+ local_ftype = (ft == fSAV || ft == fSCR || ft == fLOG);
+ if (BATCH_MODE || make_test) local_ftype = 0;
+
+ if (name == NULL) {
+ nfc = (file_context_rec *)rmalloc(sizeof(file_context_rec));
+ nfc->gamename = NULL;
+ nfc->path = NULL;
+ nfc->shortname = rstrdup(fc->shortname);
+ nfc->ext = NULL;
+ nfc->ft = fNONE;
+ nfc->special = 0;
+ } else {
+ nfc = init_file_context(name, ft);
+ }
+
+ /* If path already defined, then combine paths. */
+ if (!local_ftype && nfc->path != NULL && !absolute_path(nfc->path)) {
+ char *newpath;
+ newpath = nfc->path;
+ newpath = assemble_filename(fc->path, nfc->path, "");
+ rfree(nfc->path);
+ nfc->path = newpath;
+ }
+
+ /* scripts, save-games and logs should go in the working directory,
+ not the game directory, so leave nfc->path equal to NULL for them. */
+ if (!local_ftype && nfc->path == NULL)
+ nfc->path = rstrdup(fc->path); /* Put files in game directory */
+ return nfc;
+}
+
+void release_file_context(fc_type *pfc) {
+ file_context_rec *fc;
+ fc = FC(*pfc);
+ rfree(fc->gamename);
+ rfree(fc->path);
+ rfree(fc->shortname);
+ rfree(fc->ext);
+ rfree(fc);
+}
+
+
+/*----------------------------------------------------------------------*/
+/* Routines for Finding Files */
+/*----------------------------------------------------------------------*/
+
+#ifdef UNIX
+/* This requires that no two sav/scr/log files be open at the same time. */
+static int pipecnt = 0;
+static FILE *pipelist[6];
+
+static genfile try_open_pipe(fc_type fc, filetype ft, rbool rw) {
+ FILE *f;
+
+ errno = 0;
+ if (ft != fSAV && ft != fSCR && ft != fLOG) return NULL;
+ if (rw && fc->special != 1) return NULL;
+ if (!rw && fc->special != 2) return NULL;
+ if (pipecnt >= 6) return NULL;
+
+ f = popen(fc->gamename, rw ? "w" : "r"); /* Need to indicate this is a pipe */
+ pipelist[pipecnt++] = f;
+ return f;
+}
+#endif
+
+
+static genfile try_open_file(const char *path, const char *root,
+ const char *ext, const char *how,
+ rbool nofix) {
+ char *name = assemble_filename(path, root, ext);
+ genfile f = fopen(name, how);
+ rfree(name);
+
+ return f;
+}
+
+
+static genfile findread(file_context_rec *fc, filetype ft) {
+ genfile f;
+
+ f = NULL;
+
+#ifdef UNIX
+ if (fc->special) { /* It's a pipe */
+ f = try_open_pipe(fc, ft, 0);
+ return f;
+ }
+#endif
+ if (ft == fAGT_STD) {
+ f = try_open_file(fc->path, AGTpSTD, "", filetype_info(ft, 0), 0);
+ return f;
+ }
+ if (ft == fAGX || ft == fNONE) /* Try opening w/o added extension */
+ f = try_open_file(fc->path, fc->shortname, fc->ext, filetype_info(ft, 0), 0);
+ if (f == NULL)
+ f = try_open_file(fc->path, fc->shortname, extname[ft], filetype_info(ft, 0), 0);
+ return f;
+}
+
+
+/*----------------------------------------------------------------------*/
+/* File IO Routines */
+/*----------------------------------------------------------------------*/
+
+genfile readopen(fc_type fc, filetype ft, const char **errstr) {
+ genfile f;
+
+ *errstr = NULL;
+ f = findread(fc, ft);
+ if (f == NULL) {
+ *errstr = "Cannot open file";
+ }
+ return f;
+}
+
+rbool fileexist(fc_type fc, filetype ft) {
+ genfile f;
+
+ if (fc->special) return 0;
+ f = try_open_file(fc->path, fc->shortname, extname[ft], filetype_info(ft, 0), 1);
+ if (f != NULL) { /* File already exists */
+ readclose(f);
+ return 1;
+ }
+ return 0;
+}
+
+
+genfile writeopen(fc_type fc, filetype ft,
+ file_id_type *pfileid, const char **errstr) {
+ char *name;
+ genfile f;
+
+ *errstr = NULL;
+ name = NULL;
+
+#ifdef UNIX
+ if (fc->special) { /* It's a pipe */
+ f = try_open_pipe(fc, ft, 1);
+ if (f == NULL && errno == 0) {
+ *errstr = rstrdup("Invalid pipe request.");
+ return f;
+ }
+ if (f == NULL) /* For error messages */
+ name = rstrdup(fc->gamename);
+ } else
+#endif
+ {
+ name = assemble_filename(FC(fc)->path, FC(fc)->shortname, extname[ft]);
+ f = fopen(name, filetype_info(ft, 1));
+ }
+ if (f == NULL) {
+ *errstr = "Cannot open file";
+ }
+ if (pfileid == NULL)
+ rfree(name);
+ else
+ *pfileid = name;
+ return f;
+}
+
+
+rbool filevalid(genfile f, filetype ft) {
+ return (f != NULL);
+}
+
+
+
+void binseek(genfile f, long offset) {
+ assert(f != NULL);
+ assert(offset >= 0);
+#ifdef UNIX_IO
+ if (lseek(fileno(f), offset, SEEK_SET) == -1)
+#else
+ if (fseek(f, offset, SEEK_SET) != 0)
+#endif
+ fatal("binseek");
+}
+
+
+/* This returns the number of bytes read, or 0 if there was an error. */
+long varread(genfile f, void *buff, long recsize, long recnum, const char **errstr) {
+ long num;
+
+ *errstr = NULL;
+ assert(f != NULL);
+#ifdef UNIX_IO
+#ifdef MSDOS16
+ num = (unsigned int)read(fileno(f), buff, recsize * recnum);
+ if (num == (unsigned int) - 1)
+#else
+ num = read(fileno(f), buff, recsize * recnum);
+ if (num == -1)
+#endif
+ {
+ *errstr = rstrdup(strerror(errno));
+ return 0;
+ }
+#else
+ num = fread(buff, recsize, recnum, f);
+ if (num != recnum)
+ *errstr = "varread";
+ num = num * recsize;
+#endif
+ return num;
+}
+
+rbool binread(genfile f, void *buff, long recsize, long recnum, const char **errstr) {
+ long num;
+
+ num = varread(f, buff, recsize, recnum, errstr);
+ if (num < recsize * recnum && *errstr == NULL)
+ *errstr = rstrdup("Unexpected end of file.");
+ return (*errstr == NULL);
+}
+
+
+rbool binwrite(genfile f, void *buff, long recsize, long recnum, rbool ferr) {
+ assert(f != NULL);
+#ifdef UNIX_IO
+ if (write(fileno(f), buff, recsize * recnum) == -1)
+#else
+ if (fwrite(buff, recsize, recnum, f) != (size_t)recnum)
+#endif
+ {
+ if (ferr) fatal("binwrite");
+ return 0;
+ }
+ return 1;
+}
+
+#ifdef UNIX
+static rbool closepipe(genfile f) {
+ int i;
+ for (i = 0; i < pipecnt; i++)
+ if (pipelist[i] == f) {
+ pclose(f);
+ for (; i < pipecnt - 1; i++)
+ pipelist[i] = pipelist[i + 1];
+ pipecnt--;
+ return 1;
+ }
+ return 0;
+}
+#endif
+
+void readclose(genfile f) {
+ assert(f != NULL);
+#ifdef UNIX
+ if (closepipe(f)) return;
+#endif
+ fclose(f);
+}
+
+void writeclose(genfile f, file_id_type fileid) {
+ assert(f != NULL);
+ rfree(fileid);
+#ifdef UNIX
+ if (closepipe(f)) return;
+#endif
+ fclose(f);
+}
+
+void binremove(genfile f, file_id_type fileid) {
+ assert(f != NULL);
+ assert(fileid != NULL);
+ fclose(f);
+ remove((char *)fileid);
+ rfree(fileid);
+}
+
+long binsize(genfile f)
+/* Returns the size of a binary file */
+{
+ long pos, leng;
+
+ assert(f != NULL);
+#ifdef UNIX_IO
+ {
+ long fd;
+
+ fd = fileno(f);
+ pos = lseek(fd, 0, SEEK_CUR);
+ leng = lseek(fd, 0, SEEK_END);
+ lseek(fd, pos, SEEK_SET);
+ }
+#else
+ pos = ftell(f);
+ fseek(f, 0, SEEK_END);
+ leng = ftell(f);
+ fseek(f, pos, SEEK_SET);
+#endif
+ return leng;
+}
+
+rbool textrewind(genfile f) {
+ Common::SeekableReadStream *rs = dynamic_cast<Common::SeekableReadStream *>(f);
+ assert(rs);
+ rs->seek(0);
+ return 0;
+}
+
+
+genfile badfile(filetype ft) {
+ return NULL;
+}
+
+} // End of namespace AGT
+} // End of namespace Glk
diff --git a/engines/glk/agt/gamedata.cpp b/engines/glk/agt/gamedata.cpp
new file mode 100644
index 0000000000..e33b594018
--- /dev/null
+++ b/engines/glk/agt/gamedata.cpp
@@ -0,0 +1,1590 @@
+/* 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"
+
+namespace Glk {
+namespace AGT {
+
+/*
+ This is a mishmash of utilities and preinitialized arrays,
+ including the verblist, the metacommand token list,
+ and the dictionary routines.
+*/
+
+/* ------------------------------------------------------------------- */
+/* Preinitialized data structures */
+/* Most of the preinitialized data structures used by all of the */
+/* AGT-related programs go here . */
+/* ------------------------------------------------------------------- */
+
+
+/* ------------------------------------------------------------ */
+/* The PC --> ASCII conversion table. This converts the 8th-bit */
+/* PC characters to their nearest ASCII equivalent. */
+/* ------------------------------------------------------------ */
+
+const char trans_ibm[] =
+ "CueaaaaceeeiiiAA" /* 80 */
+ "E@@ooouuyOUc$$pf" /* 90 */
+ "aiounNao?....!<>" /* A0 */
+ "###|++|+++|\\/++\\" /* B0 */
+ "\\+++-+||\\/+++=+=" /* C0 */
+ "+=+++++++//@@@@@" /* D0 */
+ "abGpSsmtFTOd.fe^" /* E0 */
+ "=+><fj/=***/n2# "; /* F0 */
+
+
+/* ------------------------------------------------------------- */
+/* Tables of built in properties and attributes */
+/* ------------------------------------------------------------- */
+
+#define rnc(p) {#p,offsetof(room_rec,p),offsetof(noun_rec,p), \
+ offsetof(creat_rec,p)}
+#define rn(p) {#p,offsetof(room_rec,p),offsetof(noun_rec,p),-1}
+#define nc(p) {#p,-1,offsetof(noun_rec,p),offsetof(creat_rec,p)}
+#define rc(p) {#p,offsetof(room_rec,p),-1,offsetof(creat_rec,p)}
+#define r(p) {#p, offsetof(room_rec,p), -1, -1}
+#define n(p) {#p, -1, offsetof(noun_rec,p),-1}
+#define c(p) {#p, -1, -1, offsetof(creat_rec,p)}
+
+const prop_struct proplist[NUM_PROP] = {
+ /* The following are writable */
+ rnc(oclass), rnc(points), r(light),
+ n(num_shots), n(weight), n(size),
+ c(counter), c(timecounter),
+ /* The remaining properties are read-only */
+ nc(location), rn(key), c(weapon), c(threshold), c(timethresh),
+ nc(flagnum)
+};
+
+const prop_struct attrlist[NUM_ATTR] = {
+ /* The following are writable */
+ n(on), n(open), n(locked), n(movable),
+ c(groupmemb), c(hostile),
+ /* The remaining attributes are read-only */
+ r(end), rn(win), r(killplayer), n(plural),
+ n(pushable), n(pullable), n(turnable), n(playable), n(readable), n(closable),
+ n(lockable), n(edible), n(wearable), n(drinkable), n(poisonous), n(light),
+ n(shootable), nc(isglobal),
+ /* This is writable again */
+ rnc(seen),
+ nc(proper) /* This is not writable */
+};
+
+#undef rnc
+#undef rn
+#undef rc
+#undef cn
+#undef r
+#undef c
+#undef n
+
+
+
+/* ------------------------------------------------------------- */
+/* Tables of Opcodes */
+/* These gives the names and argument types of all of the AGT */
+/* opcodes. */
+/* ------------------------------------------------------------- */
+
+
+/* All of the following are undefined again just after the table */
+
+#define n AGT_NUM
+#define v AGT_VAR
+#define r AGT_ROOM
+#define i AGT_ITEM
+
+#define o (AGT_ITEM|AGT_CREAT) /* "object" */
+#define l (r|o|AGT_NONE|AGT_SELF|AGT_WORN) /* "location" */
+
+
+
+/* opcode, argnum, arg1, arg2 */
+#ifdef LOWMEM
+#define a(s) {"",0,0,0}
+#define b(s,a1) {"",1,(a1),0}
+#define c(s,a1,a2) {"",2,(a1),(a2)}
+#else
+#define a(s) {#s,0,0,0}
+#define b(s,a1) {#s,1,(a1),0}
+#define c(s,a1,a2) {#s,2,(a1),(a2)}
+#endif
+
+const opdef cond_def[] = {
+ b(AtLocation, r), b(AtLocationGT, n), b(AtLocationLT, n),
+ a(SongPlaying), a(SoundIsOn), a(DirectionOK), b(DirectionIs, AGT_DIR),
+ c(BetweenRooms, n, n), b(HasVisitedRoom, r),
+ a(EnteredObject), b(TimeGT, n), b(TimeLT, n),
+ a(FirstVisitToRoom),
+ a(NewLife),
+ a(IsCarryingSomething), a(IsCarryingNothing),
+ a(IsWearingSomething),
+ b(IsCarryingTreasure, n),
+ a(IsWearingNothing),
+ b(LoadWeightEquals, n), b(LoadWeightGT, n), b(LoadWeightLT, n),
+ b(Present, o), b(IsWearing, o), b(IsCarrying, o),
+ b(IsNowhere, o), b(IsSomewhere, o),
+ b(InRoom, o), c(IsLocated, o, l), c(Together, o, o),
+ b(IsON, o), b(IsOFF, o),
+ b(IsGroupMember, AGT_CREAT),
+ b(IsOpen, o), b(IsClosed, o), b(IsLocked, o), b(IsUnLocked, o),
+ b(IsEdible, o), b(IsDrinkable, o), b(IsPoisonous, o),
+ b(IsMovable, o),
+ a(NOUNPresent), a(NOUNIsWearing), a(NOUNIsCarrying),
+ a(NOUNIsNowhere), a(NOUNIsSomewhere),
+ a(NOUNInRoom), b(NOUNIsLocated, l),
+ a(NOUNIsOn), a(NOUNIsOff),
+ a(NOUNIsOpen), a(NOUNIsClosed), a(NOUNIsLocked), a(NOUNIsUnLocked),
+ a(NOUNIsEdible), a(NOUNIsDrinkable), a(NOUNIsPoisonous),
+ a(NOUNIsMovable),
+ b(NOUNpointsEquals, n), b(NOUNpointsGT, n), b(NOUNpointsLT, n),
+ b(NOUNweightEquals, n), b(NOUNweightGT, n), b(NOUNweightLT, n),
+ a(LightPresent), a(RoomNeedsLight),
+ b(FlagON, AGT_FLAG), b(FlagOFF, AGT_FLAG),
+ b(RoomFlagOn, AGT_ROOMFLAG), b(Room_PixHere, AGT_PIX),
+ b(RoomFlagOff, AGT_ROOMFLAG),
+ b(ScoreEquals, n), b(ScoreGT, n), b(ScoreLT, n),
+ b(NumberEquals, n), b(NumberGT, n), b(NumberLT, n),
+ a(AnswerIsCorrect), a(AnswerIsWrong),
+ b(TurnsEquals, n), b(TurnsGT, n), b(TurnsLT, n),
+ c(CounterEquals, AGT_CNT, n), c(CounterGT, AGT_CNT, n), c(CounterLT, AGT_CNT, n),
+ c(VariableEquals, v | n, n), c(VariableGT, v | n, n), c(VariableLT, v | n, n),
+ c(CompareVariables, v, v), c(VariableChance, v | n, n),
+ a(NamePresent), b(NameIsNumber, o | AGT_NONE), /* QQ:Not sure about these */
+ b(NOUNIsNumber, o | AGT_NONE), b(ObjectIsNumber, o | AGT_NONE),
+ b(SomethingInside, r | o | AGT_SELF),
+ b(Chance, n),
+ a(PromptForYES), a(PromptForNO),
+ a(VerbIsDirection),
+ a(NOUNIsCreature),
+ a(NOUNIsMan), a(NOUNIsWoman), a(NOUNIsThing),
+ a(OBJECTIsMan), a(OBJECTIsWoman), a(OBJECTIsThing),
+ a(ObjectIsCreature),
+ a(ObjectPresent),
+ a(NOT), a(OR),
+ a(BeforeCommand), a(AfterCommand), /* 110,111 */
+ b(HourEquals, n), b(HourGT, n), b(HourLT, n),
+ b(MinuteEq, n), b(MinuteGT, n), b(MinuteLT, n),
+ a(IsAM),
+
+ a(OnDisambig),
+ b(IsHostile, o), a(HostilePresent),
+ a(NameWasPresent), a(OncePerTurn),
+ c(IsClass, r | o, AGT_NONE | r | o),
+ c(AttrOn, r | o, AGT_ATTR),
+ a(NumericNOUN), a(NumericOBJECT),
+ c(Equal, n, n), c(GT, n, n), c(LT, n, n), c(GE, n, n), c(LE, n, n),
+ c(CaseCompareStrings, AGT_STR, AGT_STR), c(CaseStringBefore, AGT_STR, AGT_STR),
+ c(CaseStringAfter, AGT_STR, AGT_STR),
+ c(CompareStrings, AGT_STR, AGT_STR), c(StringBefore, AGT_STR, AGT_STR),
+ c(StringAfter, AGT_STR, AGT_STR),
+ c(StringIsAnswer, AGT_STR, AGT_QUEST),
+ b(HasSeen, r | o),
+ c(ObjFlagON, r | o, AGT_OBJFLAG),
+ c(ObjFlagOFF, r | o, AGT_OBJFLAG),
+ c(CanGo, r | o | AGT_SELF, AGT_DIR)
+};
+
+
+const opdef act_def[] = {
+ b(GoToRoom, r), c(GoToRandomRoom, r, r),
+ b(MakeVarRoomNum, v), b(MakeVarNounNum, v), b(MakeVarObjectNum, v),
+ b(GoToVariableRoom, v | r), c(SendToVariableRoom, o, v | l),
+ b(GetVariableIt, v | o), b(PrintVariableMessage, v | AGT_MSG),
+ b(GetIt, o), b(WearIt, o), b(DropIt, o), b(RemoveIt, o),
+ b(LoadFont, AGT_FONT), b(ShowPicture, AGT_PIC), c(ChangePicture, AGT_PIC, AGT_PIC),
+ b(IfYShowPicture, AGT_PIC),
+ b(ShowRoom_Pix, AGT_PIX), b(IfYShowRoom_Pix, AGT_PIX),
+ b(PlaySong, AGT_SONG), c(PlayRandom, n, n), b(RepeatSong, AGT_SONG),
+ a(EndRepeatSong), a(StopSong), a(SuspendSong), a(ResumeSong),
+ b(ToggleMovable, i), c(ChangeDescr, r | o, AGT_MSG), c(ChangePoints, r | o, n),
+ a(DestroyOBJECT), b(GetString, AGT_STR),
+ b(GetVariable, v), b(SetVariableToTime, v), b(SetTimeToVariable, v | n),
+ b(SetTime, n), b(AddToTime, n), b(SetDeltaTime, n),
+ b(DoSubroutine, AGT_SUB), a(Return),
+ a(GetNOUN), a(WearNOUN), a(DropNOUN), a(RemoveNOUN),
+ a(DropEverything), a(RemoveEverything), a(KillPlayer),
+ b(PutInCurrentRoom, o), c(SendToRoom, o, l),
+ c(RePosition, o, l),
+ a(PutNOUNInCurrentRoom), b(SendNOUNToRoom, l),
+ b(SendAllToRoom, l), c(SendTreasuresToRoom, l, n),
+ c(RelocateAll, l, l),
+ b(Destroy, o), a(DestroyNOUN),
+ c(SwapLocations, o, o), c(SendToItem, o, o), b(SendNOUNtoItem, o),
+ b(AddToGroup, AGT_CREAT), b(RemoveFromGroup, AGT_CREAT), b(MoveTheGroup, l),
+ a(RedirectTo),
+ c(RandomMessage, AGT_MSG, AGT_MSG), b(ShowContents, r | o | AGT_SELF | AGT_WORN),
+ b(OpenIt, i), b(CloseIt, i), b(LockIt, i), b(UnlockIt, i),
+ a(OpenNOUN), a(CloseNOUN), a(LockNOUN), a(UnlockNOUN),
+ a(ShowScore), b(PlusScore, n), b(MinusScore, n),
+ a(ShowInventory), a(WaitForReturn), a(TimePasses),
+ b(Delay, n),
+ a(ClearScreen),
+ b(DescribeThing, r | o), a(LookAtRoom),
+ b(PrintMessage, AGT_MSG), a(BlankLine), c(Tone, n, n),
+ c(GetNumberInput, n, n), b(AskQuestion, AGT_QUEST),
+ c(ChangePassageway, AGT_DIR, AGT_EXIT),
+ b(TurnFlagOn, AGT_FLAG), b(TurnFlagOff, AGT_FLAG), b(ToggleFlag, AGT_FLAG),
+ b(TurnRoomFlagOn, AGT_ROOMFLAG), b(TurnRoomFlagOff, AGT_ROOMFLAG),
+ b(ToggleRoomFlag, AGT_ROOMFLAG),
+ b(TurnCounterOn, AGT_CNT), b(TurnCounterOff, AGT_CNT),
+ c(SetVariableTo, v, n), c(AddToVariable, v | n, n), c(SubtractFromVariable, v | n, n),
+ c(AddVariables, v, v), c(SubtractVariables, v, v),
+ c(RandomVariable, v, n),
+ b(NounToVariable, v), b(ObjectToVariable, v),
+ b(Quote, AGT_MSG),
+ b(TimePlus, n), b(TimeMinus, n), b(SetHour, n), b(SetMinute, n),
+ b(TimePlusVariable, v | n), b(TimeMinusVariable, v | n),
+ b(SetHourToVariable, v | n), b(SetMinutesToVariable, v | n),
+
+ b(SubtractFromTime, n), b(SetDisambigPriority, n),
+ b(SetVariableToDeltaTime, v), b(ChangeStatus, n),
+ c(MultiplyVariable, v | n, n), c(DivideVariable, v | n, n),
+ c(ComputeRemainder, v | n, n),
+ a(WaitForKey),
+ b(SetHE, o), b(SetSHE, o), b(SetIT, o), b(SetTHEY, o),
+ b(PrintMessageNoNL, AGT_MSG),
+ b(StandardMessage, AGT_ERR),
+ b(FailMessage, AGT_MSG), b(FailStdMessage, AGT_ERR),
+ c(ErrMessage, n, AGT_MSG), c(ErrStdMessage, n, AGT_ERR),
+ a(AND),
+ c(SetClass, r | o, AGT_NONE | r | o),
+ c(SetVariableToClass, v, r | o),
+ b(PushStack, n), b(PopStack, v),
+ a(AddStack), a(SubStack), a(MultStack), a(DivStack), a(ModStack),
+ a(DupStack), a(DiscardStack),
+ b(SetVariableToInput, v),
+ c(TurnAttrOn, r | o, AGT_ATTR), c(TurnAttrOff, r | o, AGT_ATTR),
+ c(PushProp, r | o, AGT_PROP), c(PopProp, r | o, AGT_PROP),
+ b(Goto, n), b(OnFailGoto, n),
+ b(EndDisambig, n),
+ b(XRedirect, n),
+ c(CopyString, AGT_STR, AGT_STR),
+ b(UpcaseString, AGT_STR), b(DowncaseString, AGT_STR),
+ c(TurnObjFlagON, r | o, AGT_OBJFLAG), c(TurnObjFlagOFF, r | o, AGT_OBJFLAG),
+ c(ToggleObjFlag, r | o, AGT_OBJFLAG),
+ c(PushObjProp, r | o, AGT_OBJPROP),
+ c(PopObjProp, r | o, AGT_OBJPROP),
+ c(MoveInDirection, o | AGT_SELF, AGT_DIR)
+};
+
+const opdef end_def[] = {
+ a(WinGame), a(EndGame),
+ a(QuitThisCMD), a(QuitAllCMDs), a(DoneWithTurn)
+};
+
+const opdef illegal_def = a(ILLEGAL);
+
+#undef a
+#undef b
+#undef c
+
+#undef n
+#undef v
+#undef r
+#undef i
+#undef o
+#undef l
+
+
+
+
+/* ------------------------------------------------------------- */
+/* Opcode Translation Tables */
+/* These convert opcode numbers from the various AGT versions */
+/* to a uniform coding. */
+/* ------------------------------------------------------------- */
+
+/*NOTE this is being changed so that rather than the second term
+ is an absolute offset of the first term. Still applies to ranges
+ up until next one. Also incorporates the +1000 correction
+ into the correction set itself. (to avoid further problems
+ when including more opcodes, e.g. AGT 1.83).
+ The last table entry is now marked by a new value of -1.*/
+
+/* Versions of the command set:
+ v1.21 apparantly has a compatible command set w/ 1.7 (!)
+ [except that their maxcmd is apparantly 22, not 30]
+ 1.0 doesn't; it seems to have an EOC code of 154, as opposed to
+ 165 or so.
+ 1.18 seems to be slightly different from 1.7, but seemingly only
+ by one opcode.
+ [And of course both ME and 1.8 have their own extended command sets]
+*/
+
+static const cmd_fix_rec FIX_ME[] = /* No longer using this as baseline */
+{ {0, 0},
+ {110, 1000}, /* i.e. commands moved to start at opcode 1000 */
+ {215, WIN_ACT},
+ {220, -1}
+};
+
+static const cmd_fix_rec FIX_ME0[] =
+ /* 169 */
+{ {0, 0},
+ {110, 1000},
+ {136, 1028}, /* Skip ToggleMoveable and ChangeDescr */
+ {156, 1049}, /* Skip RePosition */
+ {212, WIN_ACT},
+ {217, -1}
+};
+
+static const cmd_fix_rec FIX_ME0A[] = /* Pre-ME/1.0: */
+ /* 169 */
+{ {0, 0},
+ {110, 1000},
+ {130, 1021}, /* Skip PlayRandom */
+ {135, 1028}, /* Skip ToggleMoveable and ChangeDescr */
+ {155, 1049}, /* Skip RePosition */
+ {211, WIN_ACT},
+ {216, -1}
+};
+
+static const cmd_fix_rec FIX_ME15[] = {
+ {0, 0},
+ {110, 1000}, /* i.e. commands moved to start at opcode 1000 */
+ {158, 1049}, /* Skip the one opcode added in 1.56: RePosition */
+ {214, WIN_ACT},
+ {219, -1}
+};
+
+static const cmd_fix_rec FIX_135[] = {
+ {0, 0},
+ {3, 12},
+ {59, 71},
+ {88, 106},
+ {92, 1000},
+ {105, 1039}, /* 149 */
+ {114, 1049}, /* 159 */
+ {157, 1095}, /* 205 */
+ {167, WIN_ACT},
+ {172, -1}
+};
+
+static const cmd_fix_rec FIX_118[] = {
+ {0, 0},
+ {3, 12},
+ {59, 71},
+ {88, 106},
+ {92, 1000},
+ {105, 1039}, /* 149 */
+ {114, 1049}, /* 159 */
+ {118, 1054}, /* Skip SendTreasuresToRoom */
+ {156, 1095}, /* 205 */
+ {166, WIN_ACT},
+ {171, -1}
+};
+
+
+static const cmd_fix_rec FIX_182[] = {
+ {0, 0},
+ {3, 12},
+ {53, 110}, /* Shift BeforeCmd and AfterCmd */
+ {55, 62},
+ {61, 71},
+ {90, 106},
+ {94, 1000},
+ {107, 1039}, /* 149 */
+ {116, 1049}, /* 159 */
+ {143, 1105}, /* QUOTE-- need to move somewhere else */
+ {144, 1076},
+ {160, 1095}, /* 205 */
+ {170, WIN_ACT},
+ {175, -1}
+};
+
+
+static const cmd_fix_rec FIX_183[] = {
+ {0, 0},
+ {3, 12},
+ {55, 110}, /* Shift BeforeCmd and AfterCmd */
+ {57, 64},
+ {61, 71},
+ {90, 106},
+ {94, 112}, /* Time condition tokens */
+ {101, 1000},
+ {114, 1039},
+ {123, 1049},
+ {158, 1105}, /* QUOTE-- need to move somewhere else */
+ {159, 1084},
+ {167, 1095},
+ {169, 1106}, /* Time Action Tokens */
+ {177, 1097},
+ {185, WIN_ACT},
+ {190, -1}
+};
+
+static const cmd_fix_rec FIX_10[] = /* This *seems* to work */
+{ {0, 0},
+ {3, 12},
+ {59, 71},
+ {80, 95},
+ {84, 108},
+ {86, 1000},
+ {88, 1009},
+ {92, 1039},
+ {101, 1049},
+ {105, 1054},
+ {115, 1065},
+ {142, 1095},
+ {152, WIN_ACT},
+ {157, -1}
+};
+
+static const cmd_fix_rec FIX_15[] = /* This works */
+{ {0, 0},
+ {3, 12}, /* Skip 3-11 */
+ {60, 70}, /* Skip 69 */
+ /* {61,72}, */ /* Skip 71 -- WRONG! */
+ {90, 106}, /* Skip 101-105 */
+ {94, 1000},
+ {107, 1039}, /* skip 1013-1038 */
+ {116, 1049}, /* Skip 1048 */
+ {172, WIN_ACT},
+ {177, -1}
+};
+
+const fix_array FIX_LIST[] = /* An array of arrays, indexed by aver */
+{
+ FIX_135, /* Aver=0: unknown format, might as well assume Classic */
+ FIX_10, FIX_118, FIX_135, FIX_135, FIX_135, FIX_182, FIX_183,
+ FIX_15, FIX_15, FIX_15, FIX_ME0, FIX_ME0A, FIX_ME15, FIX_ME15, FIX_ME
+};
+
+
+/* ------------------------------------------------------------- */
+/* Miscellaneous collections of strings */
+/* ------------------------------------------------------------- */
+
+const char *verstr[] = {"????", "SMALL", "BIG", "MASTER", "SOGGY"};
+const char *averstr[] = {"????", "1.0", "1.18",
+ "1.2", "1.32/COS", "Classic",
+ "1.82", "1.83",
+ "1.5/H", "1.5/F", "1.6",
+ "ME/1.0b", "ME/1.0a",
+ "ME/1.5", "ME/1.55", "ME/1.6",
+ "Magx"
+ };
+
+const char *portstr = PORTSTR;
+const char *version_str = "version 1.1.1";
+
+const char nonestr[5] = {4, 'n', 'o', 'n', 'e'};
+static const char NONEstr[5] = {4, 'N', 'O', 'N', 'E'};
+
+
+/* Names of exits */
+const char *exitname[13] =
+{"N", "S", "E", "W", "NE", "NW", "SE", "SW", "U", "D", "IN", "OUT", "SPC"};
+
+
+
+
+/* ------------------------------------------------------------- */
+/* Verblist is the array of canonical forms of all the verbs */
+/* ------------------------------------------------------------- */
+/* The following long string defines all the built in AGT verbs, in the
+ following format:
+ verb syn syn syn , prep prep ; next_verb ....
+ except that if a verb takes no objects at all, it should be period
+ terminated and if it is a metaverb it should be terminated by '!'. */
+static const char verbdef[] =
+ "north n. south s. east e. west w."
+ "northeast ne. northwest nw. southeast se. southwest sw."
+ "up u. down d."
+ "enter in inside go&in go&into go&in&to get&in get&into get&in&to."
+ "exit leave out go&out get&out get&out&of. special."
+ "throw cast dump, at to in into across inside;"
+ "open , with; close shut; lock, with; unlock, with;"
+ "look l. examine x ex check inspect look&at look&in;"
+ "change_locations change_location;"
+ "read; eat; drink; score! attack kill fight hit, with;"
+ "wait z. yell shout scream."
+ "put place, in with inside into near behind over under on;"
+ "quit q! tell talk talk&to talk&with, to about;"
+ "inventory inv i. get take pick pick&up; ask, about for;"
+ "turn, on off; push touch press, with; pull; play;"
+ "list. show, to; drop;"
+ "listexit listexits list_exits list&exits show&exits."
+ "brief! verbose! save! restore!"
+ "light; extinguish ext put&out; fire shoot, at with;"
+ "help h. wear put&on; remove take&off;"
+ "script script&on! unscript script&off! magic_word. view; after."
+ "instructions ins!" /* INSTRUCTIONS is "1.83 only" */
+ /* The following are not defined in the original AGT */
+ "again g. restart! oops; undo. notify!"
+ "listexit_on listexit&on listexits&on!"
+ "listexit_off listexit&off listexits&off!"
+ "agildebug agtdebug! log! logoff log&off log&close! replay!"
+ "replay_step replay&step! menu! replay_fast replay&fast."
+ "sound sound_on sound&on! sound_off sound&off! introduction intro!"
+ "dir_addr.";
+
+/* 1.83: Removes listexit; adds instructions after remove. */
+
+/* Then come the dummy verbs */
+/* Dummy verb n ==> n-55 105,122
+ Dummy_verb1...Dummy_Verb50 */
+
+/* Possible extension to verb definitons (not implemented):
+ If it _requires_ a prep, use : ?
+ If it takes a prep and no dobj, use | ?
+*/
+
+/* These are alternative (that is, non-canonical) forms of verbs that
+ were present in the oringal AGT interpreters. They have the property
+ that they have no effect if used in a dummy_verb declaration. */
+/* Their dictionary indices are stored in old_agt_verb, which is
+ initialized by reinit_dict. */
+/* PICK, GO */
+const char *const old_agt_verb_str[] = {
+ "n", "s", "e", "w", "ne", "nw", "se", "sw", "u", "d", "in", "inside", "leave",
+ "cast", "dump", "shut", "l", "ex", "inspect", "check", "kill", "fight", "hit",
+ "shout", "scream", "place", "q", "talk", "i", "take", "touch", "ext",
+ "shoot", "h", "ins", nullptr
+};
+
+
+
+/* ------------------------------------------------------------------- */
+/* Dictionary primitives: the basic functions for manipulating the */
+/* dictionary data structures. */
+/* ------------------------------------------------------------------- */
+#define HASHSIZE (1<<HASHBITS)
+#define HASHMASK (HASHSIZE-1)
+
+#ifdef DOHASH
+static word DOSFARDATA hash[HASHSIZE];
+#endif
+
+static int hashfunc(const char *s) {
+ unsigned long n, i;
+
+ n = 0;
+ for (; *s != 0; s++) {
+ n += (n << 2) + (uchar) * s;
+ i = n & ~HASHMASK;
+ if (i)
+ n = (n ^ (i >> HASHBITS))&HASHMASK;
+ }
+ return (n & HASHMASK);
+}
+
+static word search0_dict(const char *s) {
+ int i;
+
+#ifdef DOHASH
+ for (i = hashfunc(s);
+ hash[i] != -1 && strcmp(s, dict[hash[i]]) != 0;
+ i = (i + 1)&HASHMASK);
+ return hash[i];
+#else
+ for (i = 0; strcmp(s, dict[i]) != 0 && i < dp; i++);
+ if (i < dp) return i;
+ return -1;
+#endif
+}
+
+word search_dict(const char *s)
+/* This does a case-insensitive search */
+{
+ word w;
+ char *t, *p;
+
+ t = rstrdup(s);
+ for (p = t; *p; p++) *p = tolower(*p);
+ w = search0_dict(t);
+ rfree(t);
+ return w;
+}
+
+/* The basic routine to add s to the dictionary; this does no preprocessing
+ of s; use add_dict for that */
+static word add0_dict(const char *s) {
+ int i;
+ long newptr;
+ char *newstr;
+
+ i = search0_dict(s);
+ if (i != -1) return i;
+ /* Okay, it's not in the dictionary; need to add it. */
+ /* rprintf("Adding %s\n",s);*/
+
+ dict = (char **)rrealloc(dict, sizeof(char *) * (dp + 1));
+ newptr = dictstrptr + strlen(s) + 1;
+ if (newptr > dictstrsize) { /* Enlarge dictstr */
+ if (dictstrsize == 0) dictstrsize = DICT_INIT;
+ while (newptr > dictstrsize)
+ dictstrsize += DICT_GRAN;
+ newstr = (char *)rrealloc(dictstr, dictstrsize);
+ /* Now need to update all of our pointers */
+ for (i = 0; i < dp; i++)
+ dict[i] = (dict[i] - dictstr) + newstr;
+ dictstr = newstr;
+ }
+ strcpy(dictstr + dictstrptr, s); /* Copy word into memory */
+ dict[dp] = dictstr + dictstrptr;
+ dictstrptr = newptr;
+
+#ifdef DOHASH /* Need to update the hash table */
+ if (dp > HASHSIZE) fatal("Hash table overflow");
+ for (i = hashfunc(s); hash[i] != -1; i = (i + 1)&HASHMASK);
+ hash[i] = dp;
+#endif
+ return dp++;
+}
+
+#ifdef DOHASH
+
+static void init_hash(void) {
+ int i;
+
+ for (i = 0; i < HASHSIZE; i++) hash[i] = -1;
+}
+
+
+/* This routine rebuilds the hash table from the dictionary. */
+/* It's used by the AGX reading routines, since they save */
+/* the dictionary but not the hash table */
+static void rebuild_hash(void) {
+ int i, j;
+
+ if (dp > HASHSIZE) fatal("Hash table overflow");
+ init_hash();
+
+ for (i = 0; i < dp; i++) {
+ for (j = hashfunc(dict[i]); hash[j] != -1; j = (j + 1)&HASHMASK);
+ hash[j] = i;
+ }
+}
+#endif
+
+
+static void init0_dict(void)
+/* This sets up the basic data structures associated with the dictionary */
+/* (It's called by init_dict, which also adds the basic verbs) */
+{
+#ifdef DOHASH
+ init_hash();
+ hash[hashfunc("any")] = 0;
+#endif
+
+ dict = (char **)rmalloc(sizeof(char *));
+ dictstr = (char *)rmalloc(DICT_GRAN);
+ strcpy(dictstr, "any");
+ dict[0] = dictstr;
+
+ dictstrptr = 4; /* Point just after 'any' */
+ dictstrsize = DICT_GRAN;
+ dp = 1;
+ syntbl = NULL;
+ synptr = 0;
+ syntbl_size = 0; /* Clear synonym table */
+}
+
+
+
+
+/* ------------------------------------------------------------------- */
+/* Higher level dictionary routines: Things that load initial vocab, */
+/* and massage strings into the correct form for the dictionary */
+/* ------------------------------------------------------------------- */
+
+static rbool no_syn;
+
+/* This splits dict[w] into space-separated pieces and adds them to
+ the dictionary and to a growing synonym list, which it marks the end of.
+ It returns a pointer to the beginning of this list.
+ If there are no spaces, it doesn't do anything and returns 0. */
+slist add_multi_word(word w) {
+ slist start_list;
+ rbool end_found;
+ char *curr;
+ char *s, *t;
+
+ for (s = dict[w]; *s != 0 && *s != ' '; s++);
+ if (*s != ' ') return 0;
+
+ start_list = synptr;
+ curr = t = rstrdup(dict[w]);
+ s = t + (s - dict[w]);
+
+ addsyn(w); /* First entry is the 'word' to condense to */
+ while (1) {
+ end_found = (*s == 0);
+ *s = 0;
+ addsyn(add0_dict(curr)); /* Add to comb list */
+ if (end_found) break;
+ curr = ++s;
+ while (*s != 0 && *s != ' ') s++;
+ }
+ addsyn(-1); /* Mark the end of the list */
+ rfree(t);
+ return start_list;
+}
+
+
+/* Check verb vp for multiwords and enter any found in the auxilary
+ combination list */
+static void verb_multiword(int vp) {
+ int i;
+ slist ptr;
+
+ if (no_syn) return;
+ for (i = auxsyn[vp]; syntbl[i] != 0; i++) {
+ ptr = add_multi_word(syntbl[i]);
+ if (ptr != 0) {
+ num_auxcomb += 1;
+ auxcomb = (slist *)rrealloc(auxcomb, num_auxcomb * sizeof(slist));
+ auxcomb[num_auxcomb - 1] = ptr;
+ }
+ }
+}
+
+
+static void enter_verbs(int vp, const char *s)
+/* Read definition string s, starting to make entries at verb # vp */
+/* WARNING: This doesn't do any sort of checking; it assumes the input
+ string is correctly formed. */
+{
+ const char *p; /* Points along string. */
+ words curr; /* word currently being read. */
+ int n; /* length of curr */
+ rbool have_multiword;
+
+ n = 0;
+ have_multiword = 0;
+ auxsyn[vp] = synptr;
+ for (p = s; *p != 0; p++)
+ if (*p == ';' || *p == ',' || *p == '.' || *p == '!' || isspace(*p)) {
+ if (n > 0) { /* word just ended: need to add it to dictionary etc */
+ curr[n] = 0;
+ n = 0;
+ addsyn(add0_dict(curr)); /* Add to syn list or prep list, depending */
+ }
+ if (!isspace(*p))
+ addsyn(-1); /* Mark the end of the list */
+ if (*p == ';' || *p == '.' || *p == '!') {
+ if (*p == ';') verbflag[vp] |= VERB_TAKEOBJ;
+ if (*p == '!') verbflag[vp] |= VERB_META;
+ if (have_multiword)
+ verb_multiword(vp);
+ have_multiword = 0;
+ vp++;
+ if (vp >= TOTAL_VERB) break;
+ auxsyn[vp] = synptr; /* The following words will be the syn list */
+ } else if (*p == ',')
+ preplist[vp] = synptr; /* The following words will be the prep list */
+ } else if (*p == '&') {
+ curr[n++] = ' ';
+ have_multiword = 1;
+ } else curr[n++] = *p;
+}
+
+
+
+
+void init_dict(void) {
+ dict = NULL;
+ verblist = NULL;
+ syntbl = NULL;
+ no_syn = 0;
+ auxsyn = NULL;
+ preplist = NULL;
+ verbflag = NULL;
+ auxcomb = NULL;
+ old_agt_verb = NULL;
+ num_auxcomb = 0;
+}
+
+/* This is called by agttest.c */
+void build_verblist(void) {
+ int i;
+
+ verblist = (words *)rmalloc(sizeof(words) * TOTAL_VERB);
+ for (i = 0; i < TOTAL_VERB; i++)
+ strncpy(verblist[i], dict[syntbl[auxsyn[i]]], sizeof(words));
+#ifdef DUMP_VLIST
+ {
+ int j;
+ rprintf("VERB LIST:\n");
+ for (i = 0; i < TOTAL_VERB; i++) {
+ rprintf("%2d %s:", i, verblist[i]);
+ for (j = auxsyn[i]; syntbl[j] != 0; j++)
+ rprintf(" %s", dict[syntbl[auxsyn[i]]]);
+ rprintf(" ==> ");
+ for (j = preplist[i]; syntbl[j] != 0; j++)
+ rprintf(" %s", dict[ syntbl[preplist[i]]]);
+ writeln("");
+ }
+ }
+#endif
+}
+
+
+
+void set_verbflag(void) {
+ verbflag[14] |= VERB_MULTI; /* throw */
+ verbflag[29] |= VERB_MULTI; /* put */
+ verbflag[33] |= VERB_MULTI; /* get */
+ verbflag[41] |= VERB_MULTI; /* drop */
+ verbflag[51] |= VERB_MULTI; /* wear */
+ verbflag[52] |= VERB_MULTI; /* remove */
+}
+
+
+void reinit_dict(void)
+/* reinit_dict initializes verblist and sets up aux_syn as well
+ as loading the initial vocabulary into the dictionary. */
+{
+ char buff[16]; /* Needs to be big enough to hold dummy_verbNNN\0
+ or subroutineNNN\0 */
+ int i;
+
+ no_syn = no_auxsyn;
+
+ auxsyn = (slist *)rmalloc(sizeof(slist) * TOTAL_VERB);
+ auxcomb = NULL;
+ num_auxcomb = 0;
+ preplist = (slist *)rmalloc(sizeof(slist) * TOTAL_VERB);
+ verbflag = (uchar *)rmalloc(sizeof(uchar) * TOTAL_VERB);
+
+ if (!agx_file)
+ init0_dict();
+#ifdef DOHASH
+ else
+ rebuild_hash();
+#endif
+
+ for (i = 0; i < TOTAL_VERB; i++)
+ verbflag[i] = 0;
+
+ auxsyn[0] = synptr;
+ addsyn(-1);
+
+ enter_verbs(1, verbdef);
+ set_verbflag(); /* Do additional verbflag initialization */
+
+ for (i = 0; i < DVERB; i++) {
+ sprintf(buff, "dummy_verb%d", i + 1);
+ auxsyn[i + BASE_VERB] = synptr;
+ addsyn(add0_dict(buff));
+ addsyn(-1);
+ }
+ for (i = 0; i < MAX_SUB; i++) {
+ sprintf(buff, "subroutine%d", i + 1);
+ auxsyn[i + BASE_VERB + DVERB] = synptr;
+ addsyn(sub_name[i] = add0_dict(buff));
+ addsyn(-1);
+ }
+ no_syn = 0; /* Return to usual state */
+ verblist = NULL;
+
+ /* Now initialize old_agt_verb array */
+ for (i = 0; old_agt_verb_str[i] != NULL; i++);
+ rfree(old_agt_verb);
+ old_agt_verb = (word *)rmalloc(sizeof(word) * (i + 1));
+ for (i = 0; old_agt_verb_str[i] != NULL; i++) {
+ old_agt_verb[i] = search_dict(old_agt_verb_str[i]);
+ assert(old_agt_verb[i] != -1);
+ }
+ old_agt_verb[i] = -1; /* Mark end of list */
+}
+
+
+
+
+void free_dict(void) {
+ rfree(dict);
+ rfree(verblist);
+ rfree(syntbl);
+ rfree(auxsyn);
+ rfree(preplist);
+ rfree(verbflag);
+}
+
+word add_dict(const char *str) {
+ int i, j;
+ char s[50];
+
+ strncpy(s, str, 48);
+ for (i = 0; s[i] != 0 && rspace(s[i]); i++);
+ if (s[i] == 0) return 0; /* If it's all whitespace, ignore. */
+ /* i now points at first non-whitespace character */
+ /* Eliminate leading whitespace and lowercase the string. */
+ for (j = 0; s[j + i] != 0; j++) s[j] = tolower(s[j + i]);
+ s[j] = 0;
+ /* Now eliminate trailing whitespace (j points to end of string) */
+ for (j--; rspace(s[j]) && j > 0; j--);
+ s[j + 1] = 0;
+ /* Okay, now make sure it isn't 'none' */
+ if (strcmp(s, "none") == 0) return 0;
+ /* Finally, add it to the dictionary if it isn't already there */
+ return add0_dict(s);
+}
+
+/* Adds w to dynamically grown synonym list */
+/* If no_syn is set, then *don't* add a synonym: return immediatly */
+/* (This is done by agt2agx to avoid creating the auxsyn lists, */
+/* since those should be created when the interpreter loads the */
+/* game file and not before) */
+void addsyn(word w) {
+ if (no_syn) return;
+ if (w == 0) return;
+ if (w == -1) w = 0;
+ if (synptr >= syntbl_size) {
+ syntbl_size += SYN_GRAIN;
+ if (syntbl_size > 0x7FFF)
+ fatal("Too many synonyms.");
+ syntbl = (word *)rrealloc(syntbl, ((long)syntbl_size) * sizeof(word));
+ }
+ syntbl[synptr++] = w;
+}
+
+
+/* Returns the given dictionary word with some checking for -1 */
+const char *gdict(word w) {
+ assert(w >= -1 && w < dp);
+ if (w == -1) return "___"; /* NONE */
+ return dict[w];
+}
+
+
+
+
+
+/* ------------------------------------------------------------------- */
+/* General utilities linking objects to their names */
+/* ------------------------------------------------------------------- */
+
+/* Search auxsyn for verb: that is, check built in synonyms */
+int verb_builtin(word w) {
+ int i, j;
+
+ for (i = 1; i < TOTAL_VERB; i++)
+ for (j = auxsyn[i]; syntbl[j] != 0; j++)
+ if (syntbl[j] == w) return i;
+
+ /* Failed to find a match */
+ return 0;
+}
+
+int verb_authorsyn(word w) {
+ int i, j;
+
+ /* Check game-specific synonyms first */
+ /* Scan in reverse so later synonyms will override earlier ones */
+ for (i = TOTAL_VERB - 1; i > 0; i--)
+ for (j = synlist[i]; syntbl[j] != 0; j++)
+ if (w == syntbl[j]) return i;
+ return 0;
+}
+
+
+int verb_code(word w)
+/* Given a word w, searches auxsyn and returns the verb id */
+{
+ int canon, tmp;
+
+ /* Expand author-defined synonyms */
+ tmp = verb_authorsyn(w);
+ if (tmp != 0) return tmp;
+
+ /* Expand built-in synonyms */
+ canon = verb_builtin(w);
+ if (canon != 0) {
+ /* Allow built-in verbs to be overridden */
+ tmp = verb_authorsyn(syntbl[auxsyn[canon]]);
+ if (tmp != 0) return tmp;
+ }
+
+ return canon; /* No new synonyms; return canonical match if it exists */
+}
+
+
+/* This is a faster version of the above for use in the special case of
+ command headers where the verb word is much more restricted; it should
+ be the first auxsyn entry and it should never by a synlist entry. */
+static int cmdverb_code(word w) {
+ int i, j;
+
+ for (i = 0; i < TOTAL_VERB; i++)
+ if (syntbl[auxsyn[i]] == w) return i;
+ /* Hmm... that failed. Search the rest of the auxsyns in case the
+ order of auxsyns has changed or something */
+ agtwarn("Header verb not in canonical form.", 1);
+ for (i = 1; i < TOTAL_VERB; i++)
+ for (j = auxsyn[i]; syntbl[j] != 0; j++)
+ if (syntbl[j] == w) return i;
+ agtwarn("Header verb not in internal list.", 1);
+ return verb_code(w);
+}
+
+char *objname(int i) { /* returns malloc'd name string of object i */
+ char *s;
+
+ if (i < 0)
+ return rstrdup(dict[-i]);
+ if (i == 0)
+ return rstrdup("....");
+ if (i == 1) return rstrdup("*Self*");
+ if (i == 1000) return rstrdup("*Worn*");
+ if (i >= first_room && i <= maxroom)
+ return rstrdup(room[i - first_room].name);
+ if ((i >= first_noun && i <= maxnoun) || (i >= first_creat && i <= maxcreat)) {
+ word adjw, nounw;
+ if (i >= first_noun && i <= maxnoun) {
+ adjw = noun[i - first_noun].adj;
+ nounw = noun[i - first_noun].name;
+ } else {
+ adjw = creature[i - first_creat].adj;
+ nounw = creature[i - first_creat].name;
+ }
+ if (adjw == 0 || !strcmp(dict[adjw], "no_adjective"))
+ return rstrdup(dict[nounw]);
+ return concdup(dict[adjw], dict[nounw]);
+ }
+ /* At this point we can't get a name: return ILLn. */
+ s = (char *)rmalloc(3 + 1 + (5 * sizeof(int)) / 2 + 1);
+ /* Make sure we have enough space in case i is big */
+ sprintf(s, "ILL%d", i);
+ return s;
+}
+
+
+
+/* ------------------------------------------------------------------- */
+/* Routines to sort the command array and construct verbptr */
+/* ------------------------------------------------------------------- */
+
+#define SORT_META
+
+#ifdef SORT_META
+
+#define ch1 ((const cmd_rec*)cmd1)
+#define ch2 ((const cmd_rec*)cmd2)
+
+/* See notes below before trying to decipher this routine;
+ during the sort, many of the fields are being used for nonstandard
+ purposes */
+
+
+
+#define s_verb(cmd) ( (cmd)->actor<0 ? (cmd)->data[0] : (cmd)->verbcmd)
+
+static int cmp_cmd(const void *cmd1, const void *cmd2) {
+ word v1, v2;
+
+ /* We are sorting on command[].verbcmd, but if one of the headers
+ is really the object of a redirect command then we need to use
+ its parent's verbcmd */
+ /* For commands with actors, we need to avoid sorting them at all. */
+ v1 = s_verb(ch1);
+ v2 = s_verb(ch2);
+
+ if (v1 < v2) return -1;
+ if (v1 > v2) return +1;
+
+ /* v1==v2, so leave them in the same order as before */
+ /* We have to take absolute values here because we are using negatives
+ to indicate redirection objects */
+ if (ABS(ch1->actor) < ABS(ch2->actor))
+ return -1;
+ else if (ABS(ch1->actor) == ABS(ch2->actor))
+ return 0;
+ else return 1;
+ /* Equality should be impossible */
+}
+
+#undef ch1
+#undef ch2
+
+/* This sets things up for qsort */
+/* We need a sort that is
+ i) Stable and
+ ii) Keeps "redirection headers" attached to the correct command */
+/* We steal the field actor for this purpose */
+/* actor will equal the index of the header in the original list. */
+/* (or negative the header if the command is a redirection) */
+/* For redirected commands, we steal the data pointer since it shouldn't
+ be being used anyhow. */
+/* In a field pointed to by data we store the verb word */
+/* NOTE: this routine requires that the data type of *data (namely
+ integer) is big enough to hold a value of type word. */
+
+static void rsort(void) {
+ long i;
+ integer *save_actor;
+ word *save_verb;
+
+ save_actor = (integer *)rmalloc(last_cmd * sizeof(integer));
+ save_verb = (word *)rmalloc(last_cmd * sizeof(word));
+
+ /* The following loop does three things:
+ i) Copies command[].actor to save_actor[]
+ ii) Sets command[].actor to the commands index in the array
+ iii) For actor commands, sets the verb to .... after saving it
+ in save_verb.
+ iv) For redirection commands, stores the verb of the owning
+ header in a block pointed to by data */
+
+ for (i = 0; i < last_cmd; i++) { /* Copy actor to save_actor */
+ save_verb[i] = command[i].verbcmd;
+ if (command[i].actor > 1) /* i.e. there _is_ an actor */
+ command[i].verbcmd = syntbl[auxsyn[DIR_ADDR_CODE]];
+ save_actor[i] = command[i].actor;
+ command[i].actor = i;
+ if (save_actor[i] < 0) { /* Redirected command */
+ int j;
+
+ command[i].actor = -i;
+ rfree(command[i].data); /* data should be NULL, anyhow */
+ command[i].data = (integer *)rmalloc(sizeof(integer));
+ for (j = i; j > 0 && save_actor[j] < 0; j--);
+ if (save_actor[j] > 0)
+ command[i].data[0] = command[j].verbcmd;
+ else {
+ command[i].data[0] = 0;
+ agtwarn("First command header is REDIRECT object!", 0);
+ }
+ }
+ }
+
+ /* Now do the sort... */
+ qsort(command, last_cmd, sizeof(cmd_rec), cmp_cmd);
+
+#if 0 /* This is code to test the integrity of the sort */
+ for (i = 0; i < last_command; i++)
+ if (command[i].actor < 0)
+ assert(i == 0 || command[i].data[0] == command[i - 1].verbcmd);
+#endif
+
+ /* Finally, restore everything to normal */
+ for (i = 0; i < last_cmd; i++) { /* Restore actor */
+ command[i].verbcmd = save_verb[ABS(command[i].actor)];
+ command[i].actor = save_actor[ABS(command[i].actor)];
+ if (command[i].actor < 0) {
+ rfree(command[i].data); /* Sets it to NULL automatically */
+ command[i].cmdsize = 0;
+ }
+ }
+ rfree(save_actor);
+ rfree(save_verb);
+}
+
+#endif
+
+void sort_cmd(void) {
+ int i;
+ word curr_vb;
+ word all_word, global_word;
+
+ verbptr = (short *)rmalloc(sizeof(short) * TOTAL_VERB);
+ verbend = (short *)rmalloc(sizeof(short) * TOTAL_VERB);
+
+ if (mars_fix) { /* Don't bother if mars scanning is active */
+ for (i = 0; i < TOTAL_VERB; i++) {
+ verbptr[i] = 0; /* That is, scan the whole space for all verbs */
+ verbend[i] = last_cmd;
+ }
+ return;
+ }
+
+#ifdef SORT_META
+ if (!agx_file && aver >= AGX00) rsort();
+#endif
+
+
+ if (no_auxsyn) return; /* Used by agt2agx */
+
+ for (i = 0; i < TOTAL_VERB; i++) {
+ verbptr[i] = last_cmd;
+ verbend[i] = 0;
+ }
+
+ all_word = search_dict("all");
+ if (all_word == 0) all_word = -1; /* This means none of the metacommands
+ used ALL, so prevent ANY matches */
+ global_word = search_dict("global_scope");
+ if (global_word == 0) global_word = -1; /* Ditto */
+
+
+ for (i = 0; i < last_cmd; i++) {
+ if (command[i].actor < 0) continue; /* Redirection */
+ if (command[i].nouncmd == all_word)
+ /* Detect multinoun accepting verbs by ALL */
+ verbflag[cmdverb_code(command[i].verbcmd)] |= VERB_MULTI;
+ if (command[i].actor > 1)
+ curr_vb = DIR_ADDR_CODE;
+ else
+ curr_vb = cmdverb_code(command[i].verbcmd);
+ if (i < verbptr[curr_vb]) verbptr[curr_vb] = i;
+ if (i > verbend[curr_vb]) verbend[curr_vb] = i;
+ }
+
+ for (i = 0; i < TOTAL_VERB; i++)
+ if (verbptr[i] == last_cmd) /* No occurences of this verb */
+ verbend[i] = last_cmd;
+ else verbend[i]++; /* Point *after* last occurance */
+
+ for (i = 0; i < TOTAL_VERB; i++) {
+ int j;
+
+ j = synlist[i];
+ if (syntbl[j] == 0) continue;
+ while (syntbl[j] != 0) j++;
+ j--;
+ if (syntbl[j] == global_word) { /* Ends with global_scope */
+ verbflag[i] |= VERB_GLOBAL;
+ syntbl[j] = 0;
+ }
+ }
+}
+
+
+
+
+/* ------------------------------------------------------------------- */
+/* Functions for getting opcode information */
+/* ------------------------------------------------------------------- */
+
+
+/* Returns the opdef structure associated with an opcode */
+const opdef *get_opdef(integer op) {
+ op = op % 2048; /* Strip operand information */
+ if (op < 0 || (op > MAX_COND && op < START_ACT) || (op > PREWIN_ACT && op < WIN_ACT)
+ || (op > MAX_ACT)) {
+ return &illegal_def;
+ }
+ if (op >= 2000)
+ return &end_def[op - 2000];
+ if (op >= 1000)
+ return &act_def[op - 1000];
+ return &cond_def[op];
+}
+
+
+
+/* ------------------------------------------------------------------- */
+/* Functions for processing strings */
+/* ------------------------------------------------------------------- */
+
+long new_str(char *buff, int max_leng, rbool pasc)
+/* Stores the (up to leng) characters of a string
+ into our master string space (enlarging it if neccessary)
+ and returns the offset into the array.
+ pasc=1 ==> pascal-style string
+ pasc=0 ==> C-style string; ignore max_leng and NONE strings
+ */
+{
+ int leng, i;
+ long p;
+
+ if (pasc) {
+ leng = buff[0];
+ if (leng > max_leng) leng = max_leng;
+ } else
+ leng = strlen(buff);
+
+ if (ss_size < ss_end + leng + 1) {
+ while (ss_size < ss_end + leng + 1) ss_size += SS_GRAIN;
+ static_str = (char *)rrealloc(static_str, sizeof(char) * ss_size);
+ }
+
+ if (pasc)
+ if (memcmp(buff, nonestr, 5) == 0 || memcmp(buff, NONEstr, 5) == 0) {
+ /* "none" --> empty string */
+ if (ss_end != 0) return (ss_end - 1); /* Points to last \0 */
+ else { /* Very first string */
+ static_str[0] = 0;
+ ss_end = 1;
+ return 0;
+ }
+ }
+
+ p = ss_end; /* Remember begining of string */
+ for (i = 0; i < leng;)
+ static_str[ss_end++] = fixchar[(uchar)buff[pasc + (i++)]];
+ static_str[ss_end++] = 0;
+
+ return p;
+}
+
+
+
+/* ------------------------------------------------------------------- */
+/* Functions for reading in descriptions */
+/* ------------------------------------------------------------------- */
+
+
+descr_line *read_descr(long start, long size) {
+ if (agx_file)
+ return agx_read_descr(start, size);
+ else
+ return agt_read_descr(start, size);
+}
+
+void free_descr(descr_line *txt) {
+ if (txt == NULL) return;
+ if (mem_descr == NULL)
+ rfree(txt[0]); /* First free the string block containing the text...*/
+ rfree(txt); /* ... then the array of pointers to it */
+}
+
+
+
+/* ------------------------------------------------------------------- */
+/* ObjFlag and ObjProp routines */
+/* ------------------------------------------------------------------- */
+
+long objextsize(char op) {
+
+ /* op=0 for flags, =1 for props */
+ if (op == 0)
+ return num_rflags * rangefix(maxroom - first_room + 1)
+ + num_nflags * rangefix(maxnoun - first_noun + 1)
+ + num_cflags * rangefix(maxcreat - first_creat + 1);
+ else
+ return num_rprops * rangefix(maxroom - first_room + 1)
+ + num_nprops * rangefix(maxnoun - first_noun + 1)
+ + num_cprops * rangefix(maxcreat - first_creat + 1);
+}
+
+long lookup_objflag(int id, int t, char *ofs) {
+ if (id < 0 || id >= oflag_cnt) return -1;
+ switch (t) {
+ case 0:
+ *ofs = attrtable[id].rbit;
+ return attrtable[id].r;
+ case 1:
+ *ofs = attrtable[id].nbit;
+ return attrtable[id].n;
+ case 2:
+ *ofs = attrtable[id].cbit;
+ return attrtable[id].c;
+ default:
+ rprintf("INT ERROR: Invalid object type.\n");
+ return -1;
+ }
+}
+
+long lookup_objprop(int id, int t) {
+ if (id < 0 || id >= oprop_cnt) return -1;
+ switch (t) {
+ case 0:
+ return proptable[id].r;
+ case 1:
+ return proptable[id].n;
+ case 2:
+ return proptable[id].c;
+ default:
+ rprintf("INT ERROR: Invalid object type.\n");
+ return -1;
+ }
+}
+
+int num_oattrs(int t, rbool isflag) {
+ switch (t) {
+ case 0:
+ return isflag ? num_rflags : num_rprops;
+ case 1:
+ return isflag ? num_nflags : num_nprops;
+ case 2:
+ return isflag ? num_cflags : num_cprops;
+ default:
+ rprintf("INT ERROR: Invalid object type.\n");
+ return 0;
+ }
+}
+
+rbool op_simpflag(uchar *pf, char ofs, int op)
+/* op: 0=clear, 1=set, 2=nop, 3=toggle two bits: <ab> */
+{
+ unsigned char mask, amask, bmask;
+
+ mask = 1 << ofs;
+ amask = ~mask | ((op >> 1) << ofs);
+ bmask = (op & 1) << ofs;
+
+ *pf = (*pf & amask)^bmask;
+
+ return (*pf & mask) != 0;
+}
+
+static long calcindex(integer obj, integer objbase, int ocnt, int base) {
+ int rval;
+
+ if (base == -1) rval = -1;
+ else rval = (obj - objbase) * ocnt + base;
+ /* rprintf("INDEX %d + %d::%d ==> %d\n",base,obj,ocnt,rval); */
+ return rval;
+}
+
+
+rbool have_objattr(rbool prop, integer obj, int id) {
+ int t;
+ char ofs;
+
+ if (troom(obj)) t = 0;
+ else if (tnoun(obj)) t = 1;
+ else if (tcreat(obj)) t = 2;
+ else return 0;
+ if (prop)
+ return (lookup_objprop(id, t) >= 0);
+ else
+ return (lookup_objflag(id, t, &ofs) >= 0);
+}
+
+
+
+rbool op_objflag(int op, integer obj, int id) {
+ /* op: 0=clear, 1=set, 2=nop, 3=toggle two bits: <ab> */
+ /* <flagbit>= (<flagbit>&<a>)^<b> ) */
+ int index;
+ int t, firstobj;
+ char ofs;
+
+ if (troom(obj)) {
+ t = 0;
+ firstobj = first_room;
+ } else if (tnoun(obj)) {
+ t = 1;
+ firstobj = first_noun;
+ } else if (tcreat(obj)) {
+ t = 2;
+ firstobj = first_creat;
+ } else return 0;
+
+ index = calcindex(obj, firstobj, num_oattrs(t, 1), lookup_objflag(id, t, &ofs));
+ if (index == -1) return 0;
+
+ return op_simpflag(&objflag[index], ofs, op);
+}
+
+long op_objprop(int op, int obj, int id, long val) {
+ /* op: 2=get, 1=set */
+ int index, t, firstobj;
+
+ if (troom(obj)) {
+ t = 0;
+ firstobj = first_room;
+ } else if (tnoun(obj)) {
+ t = 1;
+ firstobj = first_noun;
+ } else if (tcreat(obj)) {
+ t = 2;
+ firstobj = first_creat;
+ } else return 0;
+
+ index = calcindex(obj, firstobj, num_oattrs(t, 0), lookup_objprop(id, t));
+ if (index == -1) return 0;
+
+ if (op == 2) return objprop[index];
+ else objprop[index] = val;
+ return val;
+}
+
+const char *get_objattr_str(int dtype, int id, long val) {
+ int max_val;
+
+ if (dtype == AGT_OBJPROP) {
+ if (!proptable || !propstr || id < 0 || id >= oprop_cnt) return "";
+ max_val = proptable[id].str_cnt;
+ if (val < 0) val = 0;
+ if (val >= max_val) val = max_val - 1;
+ if (max_val > 0)
+ return propstr[ proptable[id].str_list + val ];
+ return "";
+ } else if (dtype == AGT_VAR) {
+ if (!vartable || !propstr || id < 0 || id > VAR_NUM) return "";
+ max_val = vartable[id].str_cnt;
+ if (val < 0) val = 0;
+ if (val >= max_val) val = max_val - 1;
+ if (max_val > 0)
+ return propstr[ vartable[id].str_list + val ];
+ return "";
+ } else if (dtype == AGT_OBJFLAG) {
+ if (attrtable && id >= 0 && id < oflag_cnt)
+ return (val ? attrtable[id].ystr : attrtable[id].nstr);
+ else
+ return (val ? "yes" : "no");
+ } else if (dtype == AGT_FLAG) {
+ /* This uses yes/no as defaults, not on/off */
+ if (flagtable && id >= 0 && id <= FLAG_NUM)
+ return val ? flagtable[id].ystr : flagtable[id].nstr;
+ else
+ return val ? "on" : "off";
+ } else
+ rprintf("INTERNAL ERROR: Invalid data type for get_objattr_str().");
+ return "";
+}
+
+/* ------------------------------------------------------------------- */
+/* Warning and error functions */
+/* ------------------------------------------------------------------- */
+
+void agtwarn(const char *s, int elev) {
+ if (ERR_LEVEL >= elev)
+ rprintf("Warning: %s\n", s);
+}
+
+void agtnwarn(const char *s, int n, int elev) {
+ if (ERR_LEVEL >= elev)
+ rprintf("Warning: %s%d.\n", s, n);
+}
+
+void fatal(const char *s) {
+ error("Fatal error: %s", s);
+}
+
+void init_flags(void) {
+ rm_trap = 1;
+ DIAG = def_DIAG;
+ interp_arg = def_interp_arg;
+ debug_da1 = def_debug_da1;
+ RAW_CMD_OUT = def_RAW_CMD_OUT;
+ ERR_LEVEL = def_ERR_LEVEL;
+ irun_mode = 0;
+ fix_ascii_flag = fix_ascii;
+ descr_maxmem = DESCR_BUFFSIZE;
+ bold_mode = 0;
+ dbg_nomsg = 0; /* Print out MSG arguments to metacommands */
+ debug_mode = 0;
+ dbgflagptr = NULL;
+ dbgvarptr = NULL;
+ dbgcntptr = NULL;
+ no_auxsyn = 0;
+ text_file = 0;
+#ifdef PATH_SEP
+ gamepath = NULL;
+#endif
+ BATCH_MODE = make_test = 0;
+ font_status = 0;
+#ifdef OPEN_AS_TEXT
+ open_as_binary = 0;
+#endif
+}
+
+} // End of namespace AGT
+} // End of namespace Glk
diff --git a/engines/glk/agt/interface.cpp b/engines/glk/agt/interface.cpp
new file mode 100644
index 0000000000..e7875696fa
--- /dev/null
+++ b/engines/glk/agt/interface.cpp
@@ -0,0 +1,1159 @@
+/* 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"
+
+namespace Glk {
+namespace AGT {
+
+/* This module contains a miscellany of things that are somewhat */
+/* system dependent but not enough so to justify being put in */
+/* OS_<whatever>.c */
+/* --writestr() and writeln().*/
+/* --Hooks for sound, pictures, and fonts. */
+/* --yesno() and wait_return() */
+/* --Some lower level file stuff */
+/* --main() and command line parseing stuff */
+
+#ifdef _DOS
+#ifdef UNIX_IO
+#undef UNIX_IO
+#endif
+#endif
+
+#ifndef REPLACE_GETFILE
+#ifdef UNIX_IO
+#include <dirent.h>
+#endif
+#endif
+
+
+#ifdef UNIX
+/* Needed because we are compiling with ANSI set */
+FILE *popen(const char *, const char *);
+int pclose(FILE *);
+#endif
+
+#ifndef REPLACE_BNW
+
+/* #define DEBUG_BELLS_AND_WHISTLES */
+
+/* Warning for fontcmd, pictcmd, musiccmd:
+ These all extract filenames from fontlist, pictlist, pixlist, songlist.
+ Any of these are allowed to be NULL and this should be checked
+ before accessing them. */
+
+#ifdef DEBUG_BELLS_AND_WHISTLES
+void bnw_report(char *cmdstr, filename *list, int index) {
+ writeln("");
+ writestr(">** ");
+ writestr(cmdstr);
+ writestr(" ");
+ if (list != NULL) {
+ writestr(list[index]);
+ writestr(" ");
+ }
+ writeln("**<");
+}
+#endif /* DEBUG_BELLS_AND_WHISTLES */
+
+void fontcmd(int cmd, int font)
+/* 0=Load font, name is fontlist[font]
+ 1=Restore original (pre-startup) font
+ 2=Set startup font. (<gamename>.FNT)
+*/
+{
+#ifdef DEBUG_BELLS_AND_WHISTLES
+ if (cmd == 0) bnw_report("Loading Font", fontlist, font);
+ else if (cmd == 1) bnw_report("Restoring original font", NULL, 0);
+#endif
+ return;
+}
+
+void pictcmd(int cmd, int pict)
+/* 1=show global picture, name is pictlist[pict]
+ 2=show room picture, name is pixlist[pict]
+ 3=show startup picture <gamename>.P..
+ */
+{
+#ifdef DEBUG_BELLS_AND_WHISTLES
+ if (cmd == 1) bnw_report("Showing picture", pictlist, pict);
+ else if (cmd == 2) bnw_report("Showing pix", pixlist, pict);
+ agt_waitkey();
+#endif
+ return;
+}
+
+
+
+int musiccmd(int cmd, int song)
+/* For cmd=1 or 2, the name of the song is songlist[song]
+ The other commands don't take an additional argument.
+ 1=play song
+ 2=repeat song
+ 3=end repeat
+ 4=end song
+ 5=suspend song
+ 6=resume song
+ 7=clean-up
+ 8=turn sound on
+ 9=turn sound off
+ -1=Is a song playing? (0=false, -1=true)
+ -2=Is the sound on? (0=false, -1=true)
+*/
+{
+ if (cmd == 8) sound_on = 1;
+ else if (cmd == 9) sound_on = 0;
+#ifdef DEBUG_BELLS_AND_WHISTLES
+ switch (cmd) {
+ case 1:
+ bnw_report("Play song", songlist, song);
+ break;
+ case 2:
+ bnw_report("Repeat song", songlist, song);
+ break;
+ case 3:
+ bnw_report("End repeat", NULL, 0);
+ break;
+ case 4:
+ bnw_report("End song", NULL, 0);
+ break;
+ case 5:
+ bnw_report("Suspend song", NULL, 0);
+ break;
+ case 6:
+ bnw_report("Resume song", NULL, 0);
+ break;
+ case 7:
+ bnw_report("Clean up", NULL, 0);
+ break;
+ case 8:
+ bnw_report("Sound On", NULL, 0);
+ break;
+ case 9:
+ bnw_report("Sound Off", NULL, 0);
+ break;
+ case -1:
+ return yesno("Is song playing?");
+ case -2:
+ return 1;
+ }
+#endif
+ return 0;
+}
+
+#endif /* REPLACE_BNW */
+
+static char linebuff[100];
+static int lp; /* Line pointer */
+static rbool savenl = 0;
+static rbool needfill; /* Used for paragraph filling */
+static rbool quotemode = 0;
+
+#ifdef UNIX
+static rbool ispipe[3] = {0, 0, 0}; /* script, log_in, log_out */
+#endif
+
+
+
+void debugout(const char *s) {
+ int i;
+
+ if (DEBUG_OUT) {
+ debugfile->write(s, strlen(s));
+ } else {
+ lp = 0;
+ for (; *s != 0; s++) {
+ if (curr_x + lp >= screen_width || lp > 80) {
+ if (lp + curr_x >= screen_width)
+ lp = screen_width - curr_x - 1;
+ linebuff[lp] = 0;
+ agt_puts(linebuff);
+ agt_newline();
+ lp = 0;
+ }
+ if (*s == '\n') {
+ linebuff[lp] = 0;
+ agt_puts(linebuff);
+ agt_newline();
+ lp = 0;
+ } else if (*s == '\t') {
+ for (i = 0; i < 3; i++) linebuff[lp++] = ' ';
+ } else if (*s >= 0 && *s <= 9) linebuff[lp++] = ' ';
+ else linebuff[lp++] = *s;
+ }
+ linebuff[lp] = 0;
+ agt_puts(linebuff);
+ }
+}
+
+
+int close_pfile(genfile f, int ft)
+/* ft=0 for script, 4 for log_in, 5 for log_out */
+{
+ delete f;
+ return 0;
+}
+
+
+
+static char *get_log(void)
+/* Read string from logfile_in */
+{
+ char *s;
+ static int dead_log;
+
+ if (!filevalid(log_in, fLOG)) { /* We are finishing up */
+ if (++dead_log > 100) fatal("Internal error: LOG.");
+ assert(BATCH_MODE);
+ s = (char *)rmalloc(2);
+ s[0] = ' ';
+ s[1] = 0;
+ return s;
+ }
+
+ s = (char *)rmalloc(1000);
+ s[0] = ' ';
+ s[1] = 0;
+ (void)textgets(log_in, s, 1000);
+ if (texteof(log_in)) { /* Reached end of logfile */
+ close_pfile(log_in, 1);
+ log_in = BAD_TEXTFILE;
+ if (BATCH_MODE) {
+ writeln("");
+ writeln("ERROR: Unexpected end of log file.");
+ agt_quit(); /* This doesn't actually quit; it just sets things
+ up so we *will* quit. */
+ dead_log = 0;
+ } else {
+ logflag &= ~2;
+ fast_replay = 0;
+ }
+ } else { /* Need to delay or wait for keypress */
+ if (logdelay == -1) agt_waitkey();
+ else agt_delay(logdelay);
+ if (s[0] != 0) writeln(s);
+ }
+ return s;
+}
+
+
+static void put_log(const char *s)
+/* Write s to logfile_out */
+{
+ textputs(log_out, s);
+ if (s[strlen(s) - 1] != '\n')
+ textputs(log_out, "\n");
+}
+
+
+char *agt_readline(int in_type) {
+ char *s;
+
+ if (PURE_INPUT) agt_textcolor(-1);
+ if (logflag & 2)
+ s = get_log();
+ else
+ s = agt_input(in_type);
+ if (PURE_INPUT) agt_textcolor(-2);
+
+ if (logflag & 1)
+ put_log(s);
+
+ return s;
+}
+
+char agt_getchar(void) {
+ char c, *s, buff[2];
+
+ if (PURE_INPUT) agt_textcolor(-1);
+ if (logflag & 2) {
+ s = get_log();
+ c = s[0];
+ rfree(s);
+ } else
+ c = agt_getkey(1);
+ if (PURE_INPUT) agt_textcolor(-2);
+ if (logflag & 1) {
+ buff[0] = c;
+ buff[1] = 0;
+ put_log(buff);
+ }
+ return c;
+}
+
+void agt_center(rbool b)
+/* 1=turn on text centering, 0=turn off */
+/* At the moment, this is only used for game end messages */
+/* When it is on, text output with writeln() should be 'centered'; */
+/* it is up to the interface to decide what that means. */
+/* writestr() should not be called while centering is on. */
+{
+ center_on = b;
+}
+
+void agt_par(rbool b)
+/* This has been added for the sake of anyone trying to get this to */
+/* work on less-than-80 column screens. My personal opinion is that this */
+/* is probably hopeless; many AGT games assume 80-column format for */
+/* creating tables and ascii graphics. Nevertheless... */
+/* Text between an agt_par(1) and an agt_par(0) is logically connected */
+/* (all part of one description) and so it *might* be possible to reformat */
+/* it, treating multiple lines as being one paragraph. */
+/* At the very least, you should look for blank lines and indentation */
+/* since a single section of text could contain multiple paragraphs. */
+/* Sections of text _not_ between an agt_par(1) and an agt_par(0) should */
+/* be treated as though each line were a new paragraph */
+{
+ par_fill_on = b;
+ if (b == 0 && savenl) agt_newline();
+ savenl = 0;
+ needfill = 0;
+}
+
+/* This handles the various format code. They all show up after
+ '\r'; unrecogonized codes are just ignored */
+static uchar xlat_format_code(uchar c) {
+ if (c == 0xFF) {
+ if (fix_ascii) return trans_ibm[0xFF - 0x80];
+ else return 0xFF;
+ }
+ return 0;
+}
+
+#define FMT_CODE_CNT 15
+
+static void run_format_code(uchar c) {
+ if (c < FMT_CODE_CNT)
+ agt_textcolor(c - 3);
+}
+
+#define format_code(c) ((c>0 && c<=LAST_TEXTCODE)||((uchar)c==FORMAT_CODE))
+
+void writestr(const char *s) {
+ int i, j;
+ char c;
+ int endmark, old_x;
+
+ if (savenl) {
+ assert(par_fill_on);
+ if (!isalnum(s[0])) agt_newline();
+ else agt_puts(" ");
+ /* If combining two lines, insert a space between them. */
+ }
+ savenl = 0;
+ i = 0;
+ lp = 0;
+
+ while (s[i] != 0) {
+ for (; s[i] != 0 && lp < 90 && curr_x + lp < screen_width; i++)
+ if (s[i] == '\t')
+ for (j = 0; j < TAB_SIZE && curr_x + lp < screen_width; j++) linebuff[lp++] = ' ';
+ else if (format_code(s[i])) {
+ linebuff[lp++] = ' '; /* Color code */
+ break;
+ } else if (s[i] == '\r') { /* New format code */
+ if (s[i + 1] == 0) continue; /* Bogus format code */
+ if (((uchar)s[i + 1]) < FMT_CODE_CNT) break;
+ c = (char)xlat_format_code((uchar)s[++i]);
+ if (c != 0) linebuff[lp++] = c;
+ } else if (s[i] == '\n') {
+ break;
+ } else linebuff[lp++] = s[i];
+
+ linebuff[lp] = 0;
+
+ /* Backtrack to last space; in case of formatting codes, we should
+ already have one */
+ endmark = lp;
+
+ if (!isspace(s[i]) && !format_code(s[i]) && s[i] != 0) {
+ /* If we aren't conveniently at a break...*/
+ do { /* Find last space */
+ endmark--;
+ } while (endmark > 0 && !isspace(linebuff[endmark]));
+ }
+
+ if (endmark == 0 && !isspace(linebuff[endmark])) { /* Can't find a break */
+ if (curr_x + lp < screen_width) /* Not a line break */
+ endmark = lp; /* Break at end; it doesn't matter that much */
+ else /* We _need_ a line break but are having trouble finding one */
+ if (curr_x > 0) /* already stuff on this line printed previously */
+ endmark = 0; /* i.e. print out nothing; move it to next line */
+ else /* We have a single word that is longer than our line */
+ endmark = screen_width; /* Give up */
+ }
+
+ c = linebuff[endmark];
+ linebuff[endmark] = 0;
+ old_x = curr_x;
+
+ agt_puts(linebuff);
+
+ linebuff[endmark] = c;
+
+ if (old_x + lp >= screen_width)
+ /* Need to insert line break and skip any spaces */
+ {
+ if (!quotemode) agt_newline();
+ else return; /* In quote mode, just truncate */
+
+ /* Now set up beginning of next line: skip over whitespace */
+ while (endmark < lp && isspace(linebuff[endmark]))
+ endmark++; /* Eliminate EOL whitespace */
+ if (endmark == lp) {
+ /* Nothing left; eliminate whitespace at beginning
+ of next line */
+ while (isspace(s[i]) && s[i] != '\r') i++;
+ lp = endmark = 0;
+ }
+ needfill = 1;
+ if (endmark == lp && s[i] == 0) {
+ needfill = 2;
+ return; /* If only spaces left, don't print them */
+ }
+ }
+
+ /* Now copy remaining text */
+ for (j = 0; endmark < lp; j++, endmark++) linebuff[j] = linebuff[endmark];
+ lp = j;
+
+ /* Now to deal with format codes */
+ if ((unsigned char)s[i] == FORMAT_CODE) {
+ i++;
+ if (bold_mode) { /* Translate as BOLD toggle */
+ if (textbold)
+ agt_textcolor(-2); /* Turn bold off */
+ else agt_textcolor(-1); /* Turn bold on */
+ textbold = !textbold;
+ } else /* translate as BLACK */
+ agt_textcolor(0);
+ } else if (s[i] > 0 && s[i] <= LAST_TEXTCODE)
+ agt_textcolor(s[i++]);
+ else if (s[i] == '\r') {
+ run_format_code((uchar)s[i + 1]);
+ i += 2;
+ } else if (s[i] == '\n') {
+ i += 1;
+ agt_newline();
+ }
+ }
+}
+
+
+
+void writeln(const char *s) {
+ int i, pad;
+ char *padstr;
+
+ if (center_on && (int)strlen(s) + curr_x < screen_width) {
+ pad = (screen_width - strlen(s)) / 2;
+ padstr = (char *)rmalloc((pad + 1) * sizeof(char));
+ for (i = 0; i < pad; i++) padstr[i] = ' ';
+ padstr[i] = 0;
+ agt_puts(padstr);
+ rfree(padstr);
+ }
+ writestr(s);
+ /* needfill=2 if writestr ended with a line that wrapped only
+ because of excess spaces (which have been discarded); needfill==1
+ if writestr wrapped a line for any reason */
+ /* If needfill==2, we've already issued a new-line, so don't issue a
+ second one. */
+ /* If needfill==1, set savenl rather than wrapping (writestr will
+ then decide to wrap or not depending on whether the next line starts
+ with text or nontext), *unless* we are version magx, in which case
+ the game author presumably knew what they were doing, so honor their
+ wishes. */
+ if (par_fill_on && needfill == 1)
+ if (aver >= AGX00) agt_newline();
+ else savenl = 1;
+ else if (needfill != 2)
+ agt_newline();
+ needfill = 0;
+}
+
+
+static char fixstatchar(uchar c)
+/* Eliminate formating characters in the status line */
+{
+ if (c == '\t' || c <= LAST_TEXTCODE ||
+ (c == FORMAT_CODE) || c == '\r' || c == '\n')
+ return ' ';
+ return c;
+}
+
+void print_statline(void)
+/* Use strings in l_stat and r_stat */
+{
+ int i, j;
+ char *s, *t;
+ static rbool lastline = 0; /* Was a non-empty status line printed last time? */
+
+ s = (char *)rmalloc((status_width + 1) * sizeof(char));
+
+ /* If both strings empty, don't print the status line */
+ if (l_stat[0] == 0 && r_stat[0] == 0 && !lastline) return;
+ lastline = (l_stat[0] || r_stat[0]);
+
+ i = status_width - strlen(l_stat) - strlen(r_stat);
+
+ j = 0;
+ if (r_stat[0] == 0) { /* Center the status line */
+ while (j < i / 2) s[j++] = ' ';
+ i -= j;
+ } else if (i > 6) {
+ s[j++] = ' ';
+ i -= 2;
+ } /* If statline is wide enough, put a
+ space on each side */
+
+ if ((int)strlen(l_stat) < status_width) /* Copy left side of status line into s*/
+ for (t = l_stat; *t != 0; t++) s[j++] = fixstatchar(*t);
+
+ for (; i > 0; i--) s[j++] = ' '; /* Insert space between left and right sides */
+
+ if (j + (int)strlen(r_stat) <= status_width) /*Copy right side into s */
+ for (t = r_stat; *t != 0; t++) s[j++] = fixstatchar(*t);
+
+ while (j < status_width) s[j++] = ' '; /* Pad any extra width with spaces */
+ s[j] = 0; /* Put end of string marker */
+ agt_statline(s); /* Output it */
+ rfree(s);
+}
+
+
+void padout(int padleng) {
+ int i;
+ char *pstr;
+
+ if (padleng <= 0) return;
+ pstr = (char *)rmalloc(padleng + 1);
+ for (i = 0; i < padleng; i++) pstr[i] = ' ';
+ pstr[padleng] = 0;
+ writestr(pstr);
+ free(pstr);
+}
+
+static int textwidth(char *s) {
+ int n;
+
+ n = 0;
+ for (; *s != 0; s++) n += (*s == '\t') ? TAB_SIZE : 1;
+ return n;
+}
+
+void textbox(char *(txt[]), int len, unsigned long flags)
+/* TB_TTL, TB_BOLD, TB_BORDER, TB_CENTER */
+{
+ int i, width, padwidth;
+ int *linewidth;
+
+ agt_textcolor(7);
+ if (flags & TB_BOLD) agt_textcolor(-1);
+ else agt_textcolor(-2);
+
+ linewidth = (int *)rmalloc(len * sizeof(int));
+
+ width = 0; /* This contains the maximum width of any line */
+ for (i = 0; i < len; i++) {
+ linewidth[i] = textwidth(txt[i]);
+ if (linewidth[i] > width) width = linewidth[i];
+ }
+
+ agt_makebox(width, len, flags & ~(TB_BOLD | TB_CENTER));
+ quotemode = 1; /* So newlines will cause truncation rather than a
+ real newline */
+ for (i = 0; i < len; i++) {
+ padwidth = width - linewidth[i]; /* Amount of padding we need */
+ if (flags & TB_CENTER) {
+ padout(padwidth / 2);
+ padwidth -= padwidth / 2;
+ }
+ writestr(txt[i]);
+ padout(padwidth);
+ if (i != len - 1) agt_qnewline();
+ }
+ agt_endbox();
+ quotemode = 0; /* Back to normal */
+
+ agt_textcolor(7);
+ textbold = 0;
+}
+
+
+#ifndef REPLACE_MENU
+
+int agt_menu(const char *header, int size, int width, menuentry *menu)
+/* This is _very_ minimal as it stands */
+{
+ int i, j;
+ char sbuff[10];
+ int numcol, colheight;
+
+ if (size == 0) return 0;
+
+ width = width + 5;
+ numcol = screen_width / width;
+ colheight = size / numcol;
+ if (size % numcol != 0) colheight++;
+
+ writeln(header);
+ for (i = 0; i < colheight; i++) {
+ for (j = 0; j < numcol; j++) {
+ if (j * colheight + i >= size) break;
+ sprintf(sbuff, "%2d.", j * colheight + i + 1);
+ writestr(sbuff);
+ writestr(menu[j * colheight + i]);
+ if (j < numcol - 1) padout(width - 3 - strlen(menu[j * colheight + i]));
+ }
+ writeln("");
+ }
+ do {
+ writestr("Choice:");
+ i = read_number() - 1;
+ if (i < 0 || i >= size)
+ writeln("Please choose an option from the menu.");
+ } while (i < 0 || i >= size);
+ return i;
+}
+
+#endif /* REPLACE_MENU */
+
+
+
+void prompt_out(int n)
+/* n=1 standard prompt
+ n=2 question prompt */
+{
+ agt_textcolor(7);
+ if (PURE_INPUT && n == 1) agt_textcolor(-1);
+ if (n == 1) {
+ agt_newline();
+ gen_sysmsg(1, ">", MSG_MAIN, NULL);
+ }
+ if (n == 2) agt_puts("? ");
+ agt_textcolor(7);
+}
+
+void agt_waitkey(void) {
+ if (BATCH_MODE || fast_replay)
+ return;
+ agt_getkey(0);
+}
+
+
+void wait_return(void) {
+ writeln(" --- HIT ANY KEY ---");
+ agt_waitkey();
+}
+
+
+rbool yesno(const char *s)
+/* True for yes, false for no. */
+{
+ char c;
+
+ writestr(s);
+ writestr(" ");
+ c = 'y';
+ do {
+ if (c != 'y')
+ writestr("Please answer <y>es or <n>o. ");
+ c = tolower(agt_getchar());
+ } while (c != 'y' && c != 'n' && !quitflag);
+ return (c == 'y');
+}
+
+
+void set_test_mode(fc_type fc) {
+ const char *errstr;
+
+ log_in = readopen(fc, fLOG, &errstr);
+
+ if (make_test) {
+ if (errstr == NULL)
+ fatal("Log file already exists.");
+ log_out = writeopen(fc, fLOG, NULL, &errstr);
+ if (errstr != NULL)
+ fatal("Couldn't create log file.");
+ logflag = 1;
+ return;
+ }
+
+ logdelay = 0;
+ if (errstr != NULL)
+ fatal("Couldn't open log file.");
+ logflag = 2;
+
+ script_on = 1;
+ scriptfile = writeopen(fc, fSCR, NULL, &errstr);
+ if (errstr != NULL)
+ fatal("Couldn't open script file.");
+}
+
+
+#ifndef REPLACE_GETFILE
+
+
+#ifdef UNIX_IO
+
+extern const char *extname[]; /* From filename.c */
+
+static rbool check_fname(char *name, filetype ext) {
+ return 0 == strcmp(name + strlen(name) - strlen(extname[ext]), extname[ext]);
+}
+
+
+static void list_files(char *type, filetype ext) {
+ DIR *currdir;
+ struct dirent *entry;
+ char **filelist;
+ int filecnt, listsize;
+ int maxleng; /* Longest filename; used for formatting */
+ int i, j;
+ int numcols, height;
+
+ filelist = NULL;
+ filecnt = listsize = 0;
+ maxleng = 0;
+ currdir = opendir(".");
+ if (currdir == NULL) return; /* Nothing we can do except give up */
+ do {
+ entry = readdir(currdir);
+ if (entry != NULL && check_fname(entry->d_name, ext)) {
+ /* It has the right extension; add it to our list of files */
+ if (filecnt >= listsize) {
+ listsize += 5;
+ filelist = rrealloc(filelist, listsize * sizeof(char *));
+ }
+ filelist[filecnt] = rstrdup(entry->d_name);
+ i = strlen(entry->d_name);
+ if (i > screen_width - 1) {
+ filelist[filecnt][screen_width - 1] = 0;
+ i = screen_width - 1;
+ }
+ if (i > maxleng) maxleng = i;
+ filecnt++;
+ }
+ } while (entry != NULL);
+ closedir(currdir);
+ if (filecnt == 0) return; /* No files */
+
+ numcols = (screen_width - 1) / (maxleng + 2); /* Two spaces between columns */
+ if (numcols < 1)
+ numcols = 1;
+ height = (filecnt + numcols - 1) / numcols; /* Height, rounded up. */
+
+ writeln("");
+ writestr("Existing ");
+ writestr(type);
+ writestr("files:");
+ for (i = 0; i < height; i++) {
+ writeln("");
+ for (j = 0; j < numcols; j++)
+ if (i + j * height < filecnt) {
+ if (maxleng + 2 <= screen_width - 1) writestr(" ");
+ writestr(filelist[i + j * height]);
+ padout(maxleng - strlen(filelist[i + j * height]));
+ rfree(filelist[i + j * height]);
+ }
+ }
+ writeln("");
+ rfree(filelist);
+}
+#endif /* UNIX_IO */
+
+
+
+
+/* This opens the file refered to by fname and returns it */
+static genfile uf_open(fc_type fc, filetype ext, rbool rw) {
+ char *errstr;
+ genfile f;
+
+ if (rw) { /* Check to see if we are overwriting... */
+#ifdef UNIX
+ if (fc->special)
+ f = writeopen(fc, ext, NULL, &errstr);
+ else
+#endif
+ {
+ if (fileexist(fc, ext) && ext != fSCR) {
+ if (!yesno("This file already exists; overwrite?"))
+ /* That is, DON'T overwrite */
+ return badfile(ext);
+ }
+ f = writeopen(fc, ext, NULL, &errstr);
+ }
+ } else
+ f = readopen(fc, ext, &errstr);
+ if (errstr != NULL) writeln(errstr);
+ return f;
+}
+
+static fc_type last_save = NULL;
+static fc_type last_log = NULL;
+static fc_type last_script = NULL;
+
+
+genfile get_user_file(int ft)
+/* ft= 0:script, 1:save 2:restore, 3:log(read) 4:log(write) */
+/* Should return file in open state, ready to be read or written to,
+ as the case may be */
+{
+ /* int extlen;*/
+ rbool rw; /* True if writing, false if reading */
+ filetype ext;
+ genfile fd;
+ fc_type def_fc, fc;
+ char *fname;
+ char *ftype;
+ char *p, *q;
+
+ switch (ft) {
+ case 0:
+ ftype = "script ";
+ def_fc = last_script;
+ rw = 1;
+ ext = fSCR;
+ break;
+ case 1:
+ ftype = "save ";
+ def_fc = last_save;
+ rw = 1;
+ ext = fSAV;
+ break;
+ case 2:
+ ftype = "restore ";
+ def_fc = last_save;
+ rw = 0;
+ ext = fSAV;
+ break;
+ case 3:
+ ftype = "log ";
+ def_fc = last_log;
+ rw = 0;
+ ext = fLOG;
+ break;
+ case 4:
+ ftype = "log ";
+ def_fc = last_log;
+ rw = 1;
+ ext = fLOG;
+ break;
+ default:
+ writeln("<INTERNAL ERROR: invalid file type>");
+ return badfile(fSAV);
+ }
+#ifdef UNIX_IO
+ if (!rw) { /* List available files. */
+ list_files(ftype, ext);
+ ftype = NULL;
+ } else
+#else
+ writestr(" ");
+#endif
+ writestr("Enter ");
+ if (ftype != NULL) writestr(ftype);
+ writestr("file name");
+ if (def_fc != NULL) {
+ char *s;
+ s = formal_name(def_fc, ext);
+ writestr(" (");
+ writestr(s);
+ writestr(")");
+ rfree(s);
+ }
+ writestr(": ");
+
+ if (PURE_INPUT) agt_textcolor(-1);
+ fname = agt_input(4);
+ if (PURE_INPUT) agt_textcolor(-2);
+
+ /* Delete whitespace before and after the file name. */
+ for (p = fname; isspace(*p); p++);
+ if (*p == 0) { /* Line is all whitespace; use default if there is one */
+ if (def_fc == NULL) {
+ writeln("Never mind.");
+ rfree(fname);
+ return badfile(ext);
+ } else {
+ rfree(fname);
+ fc = def_fc;
+ }
+ } else { /* Line is _not_ all whitespace: we have a file name */
+ for (q = fname; *p != 0; p++, q++)
+ *q = *p;
+ q--;
+ while (isspace(*q)) q--;
+ q++;
+ *q = 0;
+ fc = init_file_context(fname, ext);
+ }
+
+ fd = uf_open(fc, ext, rw);
+
+ if (!filevalid(fd, ext)) {
+ if (fc != def_fc) release_file_context(&fc);
+ return fd;
+ }
+
+ switch (ft) {
+ case 0:
+ last_script = fc;
+ break;
+ case 1:
+ last_save = fc;
+ break;
+ case 2:
+ last_save = fc;
+ break;
+ case 3:
+ last_log = fc;
+ break;
+ case 4:
+ last_log = fc;
+ break;
+ }
+ if (fc != def_fc) release_file_context(&def_fc);
+ return fd;
+}
+
+
+void set_default_filenames(fc_type fc) {
+ last_save = convert_file_context(fc, fSAV, NULL);
+ last_log = convert_file_context(fc, fLOG, NULL);
+ last_script = convert_file_context(fc, fSCR, NULL);
+}
+
+
+
+#endif /* REPLACE_GETFILE */
+
+
+
+
+void script(uchar onp) {
+ if (onp == script_on)
+ if (onp == 0) writeln("Scripting wasn't on.");
+ else writeln("Scripting is already on.");
+ else if (onp == 1) {
+ scriptfile = get_user_file(0);
+ if (filevalid(scriptfile, fSCR)) script_on = 1;
+ } else if (filevalid(scriptfile, fSCR)) {
+ close_pfile(scriptfile, 0);
+ scriptfile = BAD_TEXTFILE;
+ script_on = 0;
+ }
+}
+
+
+void logon(void) {
+ if (logflag & 1) {
+ writeln("Already logging");
+ return;
+ }
+ log_out = get_user_file(4);
+ if (filevalid(log_out, fLOG))
+ logflag |= 1;
+}
+
+void replay(int delay) {
+ if (logflag & 2) return; /* Nested replays are meaningless */
+ log_in = get_user_file(3);
+ if (filevalid(log_in, fLOG)) {
+ logflag |= 2;
+ logdelay = delay;
+ }
+}
+
+
+/* These two are intended to be called by the platform-dependent
+ interface (e.g. if the user had chosen these from some general purpose
+ menu) */
+/* They're never called from the rest of the code */
+
+void agt_save(void) {
+ savegame();
+}
+
+void agt_restore(void) {
+ doing_restore = 1;
+}
+
+void agt_restart(void) {
+ doing_restore = 2;
+}
+
+void agt_quit(void) {
+ doing_restore = 4;
+}
+
+
+/* This should be rmalloc'd */
+static fc_type newgame_fc;
+
+fc_type new_game(void) {
+ return newgame_fc;
+}
+
+void agt_newgame(fc_type fc) {
+ newgame_fc = fc;
+ doing_restore = 3;
+}
+
+#if 0
+static rbool end_cmd_options;
+#endif
+
+void set_default_options(void) {
+ init_flags();
+ flag = (rbool *)rmalloc(sizeof(rbool));
+ debug_parse = 0;
+ DEBUG_AGT_CMD = 0;
+ DEBUG_EXEC_VERB = 0;
+ DEBUG_DISAMBIG = 0;
+ DEBUG_SMSG = 0;
+}
+
+void helpmsg(void) {
+ /*
+ printf(" -i Try to use IBM character set.\n");
+ printf(" -1 IRUN Mode: Print messages in first person\n");
+ printf(" -h Print out this message\n");
+ printf(" -d Debug metacommand execution\n");
+ printf(" -t Test mode; see accompanying documentation. Implies -r.\n");
+ printf(" -c Create test file.\n");
+ printf(" -m Force descriptions to be loaded from disk.\n");
+ #ifdef OPEN_AS_TEXT
+ printf(" -b Open data files as binary files.\n");
+ #endif
+ printf("\nTechnical options (intended for debugging AGiliTy itself).\n");
+ printf(" -p Debug parser\n");
+ printf(" -x Debug verb execution loop\n");
+ printf(" -a Debug disambiguation system\n");
+ printf(" -s Debug STANDARD message handler\n");
+ */
+}
+
+#if 0
+static rbool setarg(char **optptr) {
+ if ((*optptr)[1] == '+') {
+ (*optptr)++;
+ return 1;
+ }
+ if ((*optptr)[1] == '-') {
+ (*optptr)++;
+ return 0;
+ }
+ return 1;
+}
+#endif
+
+#ifdef UNIX
+#define fixcase(c) (c)
+#else
+#define fixcase(c) tolower(c)
+#endif
+
+#if 0
+void parse_options(char *opt, char *next) {
+ /*
+ if (opt[0]=='-' && opt[1]==0)
+ {end_cmd_options=1;return;}
+ for(;*opt!=0;opt++)
+ switch(fixcase(*opt))
+ {
+ case 'p': debug_parse=setarg(&opt);break;
+ case 'a': DEBUG_DISAMBIG=setarg(&opt);break;
+ case 'd': DEBUG_AGT_CMD=setarg(&opt);break;
+ case 'x':DEBUG_EXEC_VERB=setarg(&opt);break;
+ case 's':DEBUG_SMSG=setarg(&opt);break;
+ #ifdef MEM_INFO
+ case 'M': DEBUG_MEM=setarg(&opt);break;
+ #endif
+ case 'm': descr_maxmem=0; break;
+ case 'i': fix_ascii_flag=!setarg(&opt);break;
+ case 't': BATCH_MODE=setarg(&opt); break;
+ case 'c': make_test=setarg(&opt); break;
+ case '1': irun_mode=setarg(&opt);break;
+ #ifdef OPEN_FILE_AS_TEXT
+ case 'b': open_as_binary=setarg(&opt);break;
+ #endif
+ default:printf("Do not recognize option %c\n",*opt);
+ helpmsg();
+ exit(EXIT_FAILURE);
+ }
+ */
+}
+#endif
+
+#ifndef REPLACE_MAIN
+
+#ifdef MSDOS
+extern rbool use_bios;
+#endif
+
+
+int main(int argc, char *argv[]) {
+ int i;
+ char *gamefile;
+#ifdef MSDOS
+ rbool biosvar = 0;
+#endif
+
+ set_default_options();
+ end_cmd_options = 0;
+ gamefile = NULL;
+ for (i = 1; i < argc; i++)
+ if (argv[i][0] == '-' && !end_cmd_options)
+ parse_options(argv[i] + 1, argv[i + 1]);
+#ifdef MSDOS /* For backward compatibility w/ original AGT interpreters */
+ else if (argv[i][0] == '/' && tolower(argv[i][1]) == 'b'
+ && argv[i][2] == 0)
+ biosvar = 1;
+#endif
+ else if (gamefile == NULL)
+ gamefile = argv[i];
+ else fatal("Please specify only one game\n");
+ if (gamefile == NULL) {
+ helpmsg();
+ exit(EXIT_FAILURE);
+ }
+
+ init_interface(argc, argv);
+ /* From this point on, MUST use writestr/writeln or may
+ cause problems w/ the interfaces on some platforms
+ that have to keep track of cursor position */
+#ifdef MSDOS
+ use_bios = biosvar;
+#endif
+ run_game(init_file_context(gamefile, fDA1));
+ return EXIT_SUCCESS;
+}
+
+#endif /* REPLACE_MAIN */
+
+} // End of namespace AGT
+} // End of namespace Glk
diff --git a/engines/glk/agt/interp.h b/engines/glk/agt/interp.h
new file mode 100644
index 0000000000..872e8873e3
--- /dev/null
+++ b/engines/glk/agt/interp.h
@@ -0,0 +1,539 @@
+/* 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 "common/file.h"
+
+#ifndef GLK_AGT_INTERP
+#define GLK_AGT_INTERP
+
+namespace Glk {
+namespace AGT {
+
+/* This file contains variables and data structures used
+ by the interpreter but not read in from the gamefile.
+ For the rest of the data structures, see agility.h and
+ agtdata.c */
+
+#ifndef global
+#define uagt_defined_global
+#define global extern
+#endif
+
+/* -------------------------------------------------------------------- */
+/* The following are debugging and diagnostic flags. */
+/* They are mainly intended for debugging the interpreter, but */
+/* they could concievable be used for debugging games under the */
+/* interpreter */
+/* -------------------------------------------------------------------- */
+
+global rbool stable_random;
+/* --Force random numbers to be repeatable. */
+
+
+global rbool DEBUG_MEM; /* prints out information on memory allocation */
+
+global rbool debug_parse; /* Prints out long parse diagnostic information
+ after the sentence has been parse but before
+ disambiguation */
+global rbool DEBUG_EXEC_VERB; /* This prints out a line indicating each command
+ that exec_verb() is asked to run */
+global rbool DEBUG_DISAMBIG; /* Print out dismabiguation debugging info */
+global rbool DEBUG_SMSG; /* Print out STANDARD message info */
+
+#define DEBUG_AGT_CMD flag[0] /* This sends metacommand execution information
+ to either the screen or debugfile, depending on
+ whether DEBUG_OUT is true or false. */
+
+global rbool debug_disambig, debug_any;
+/* These determine if metacommands are traced during disambiguation
+ or during the scanning of ANY commands */
+
+global rbool DEBUG_OUT; /* True if debugging output is redirected somewhere
+ other than the screen */
+global Common::DumpFile *debugfile; /* Where debugging output should be sent */
+
+#define def_DEBUG_MEM 1 /* parser.c */
+
+
+/* -------------------------------------------------------------------- */
+/* The following are AGT 'purity' flags; they turn off features of */
+/* my interpreter that are not fully consistent with the original AGT.*/
+/* More are defined in agility.h, and you should look there for general */
+/* notes */
+/* -------------------------------------------------------------------- */
+
+/* The following are defined (and described) in agil.c */
+extern rbool PURE_INPUT, PURE_TONE;
+
+#define PURE_WEAR 1 /* If this is cleared, then things taken off
+ still stay in the player's inventory.
+ The support for this isn't quite complete yet
+ (there are one or two checks that need to be done
+ but aren't) and so right now this should be 1. */
+
+global rbool PURE_DOT; /* Treats period as a letter-character and not
+ as punctuation. This should be set automatically
+ during initialization based on whether any words
+ in the dictionary have dots. */
+
+#define FORCE_PURE_DOT 0 /* This forces the period to be treated as a letter
+ even if there are no words in the dictionary
+ containing periods. The only reason to set
+ this was if you were concerned that knowledge
+ about the presence or absence of periods in the
+ dictionary would give puzzles away. */
+
+
+
+
+#define MEM_MARGIN (16*1024) /* 16K should be enough (SOGGY, the largest AGT
+ game, uses around 12K) */
+#define PICT_SUPPORT 0 /* Graphics not supported */
+#define TAB_SIZE 3 /* Number of spaces in a tab */
+
+
+
+
+/* -------------------------------------------------------------------- */
+/* Variables and Flags related to Metaverbs */
+/* -------------------------------------------------------------------- */
+
+global rbool notify_flag, listexit_flag, menu_mode;
+
+global rbool cmd_saveable; /* set indicates that this command can be repeated
+ with AGAIN. */
+global rbool can_undo; /* Can we UNDO the last turn? */
+
+global uchar *restart_state, *undo_state; /* Store old game states for
+ RESTART and UNDO */
+global char doing_restore; /* Have we been asked to RESTORE? */
+
+global rbool do_look; /* True if we should print a room description */
+global rbool do_autoverb; /* True if we should run the autoexec verb
+ for the current room */
+
+/* The following are used for scripting and logging */
+global rbool script_on;
+global genfile scriptfile;
+global signed char logflag; /* 1=logging, 2=replaying, 0=neither, 3=both */
+global int logdelay; /* -1=wait for keypress, >=0 is numerical delay */
+global genfile log_in, log_out;
+
+global rbool fast_replay; /* If true, don't print MORE prompts. */
+
+global rbool sound_on; /* Manipulated by music_cmd; can be used by interface
+ to determine if a tone should be made */
+
+global integer *pictable; /* Used to decode picture numbers */
+global fc_type hold_fc; /* Needed to print instructions on demand */
+
+global unsigned short compass_rose; /* Used to pass compass info to
+ the os layer */
+
+
+/* -------------------------------------------------------------------- */
+/* Game State */
+/* -------------------------------------------------------------------- */
+
+global rbool quitflag, deadflag, winflag, endflag;
+global rbool first_visit_flag, newlife_flag, room_firstdesc;
+
+global rbool start_of_turn; /* True if running the command on the first
+ noun in the list */
+global rbool end_of_turn; /* True if running command on last noun in
+ the list. */
+
+global rbool actor_in_scope; /* Used to determine if the actor was in
+ scope when the command was originally
+ given */
+
+global integer loc; /* Player's location */
+global integer player_contents, player_worn; /* Worn and carried objects*/
+
+global long totwt, totsize; /* Size and wt player is carrying around */
+
+global integer curr_lives; /* Number of lives left. */
+
+global long tscore, old_score; /* Total score */
+global long objscore; /* Portion of tscore that comes from the POINTS
+ field of nouns and creatures. */
+
+global integer turncnt; /* Number of turns that have passed */
+global integer curr_time; /* The time in the game; in the format
+ 1243==12:43 */
+
+global rbool *flag; /* AGT Flags */
+global short *agt_counter; /* AGT counters */
+#ifdef AGT_16BIT
+global short *agt_var; /*AGT variables */
+#else
+global long *agt_var;
+#endif
+
+global long agt_number; /* The number entered by the player */
+global rbool agt_answer; /* Did the player get the answer to the last question
+ right? */
+
+global tline l_stat, r_stat; /* Left and right parts of status line */
+/* If r_stat is the empty string, l_stat should be
+centered to create a Trinity-like status line */
+
+global rbool nomatch_aware; /* Does the game use the nomatch extension
+ to the metacommand format?
+ (which allow <none> and ANY to be
+ distingused) */
+
+global rbool smart_look; /* If true, then LOOK <object> will be converted
+ to EXAMINE. This is set automatically in agil.c,
+ based on whether the game file uses
+ LOOK <object> in any of the metacommands; if it
+ does, then smart_look is set to 0. */
+
+/* -------------------------------------------------------------------- */
+/* Menu data structures */
+/* -------------------------------------------------------------------- */
+
+#define MENU_WIDTH 50
+typedef char menuentry[MENU_WIDTH];
+
+global int vm_width; /* Width of widest element */
+global menuentry *verbmenu;
+
+
+
+/* -------------------------------------------------------------------- */
+/* Parser Data Structures */
+/* This also includes "parser-related" variables like dobj and iobj */
+/* -------------------------------------------------------------------- */
+
+/* This extracts the object number from a parse rec */
+#define p_obj(objrec) ((objrec) ? (objrec)->obj : 0)
+
+/* The following data structures are used for disambiguation of nouns */
+struct parse_rec {
+ long num; /* Numeric value of object; 0 if object doesn't have one */
+ int obj; /* Object number; negative values point into the dictionary */
+ int info; /* Disambiguation info */
+ /* -1=last record; ignore obj field. */
+ word noun, adj; /* Used for printing out error messages */
+ short score; /* Disambiguation score */
+}; /* Stores objects that have been found during parse */
+
+
+/* In an ideal world, the following would all be local variables. */
+/* Unfortunately, they're used in too many different places for this
+ to be practical */
+
+global int vb;
+global integer actor, dobj, iobj;
+global parse_rec *actor_rec, *dobj_rec, *iobj_rec;
+global word prep;
+global parse_rec *curr_creat_rec;
+/* Creature currently behaving in a hostile way:
+used to fill in $c_name$ messages */
+
+global int disambig_score; /* Used to rank objects during disambiguation */
+
+#define DISAMBIG_SUCC 1000 /* Score given to an object that triggers a
+ built-in verb or an action token */
+
+
+#define MAXINPUT 200 /* Max number of words input */
+
+global word input[MAXINPUT]; /* 200 words of input should be enough */
+global words in_text[MAXINPUT];
+/* The corrospoinding strings, for error reporting purposes */
+
+global short ip, ep; /* input pointer and error pointer */
+global short ap, vp, np, pp, op; /* Points to first word in actor, verb, noun,
+ and object resp. */
+
+
+
+/* The following needs to be kept consistant with ext_voc[] in
+ agil.c */
+typedef enum {wthe, wmy, wa, wan, wthen, wp, wsc, wand, wc, wits, wall, wundo, wlook, wg,
+ wpick, wgo, wexits, wtalk, wtake, wdoor, wagain, wbut, wexcept,
+ wscene, weverything, wlistexit, wlistexits, wclose,
+ wdverb, wdnoun, wdadjective, wdprep, wdobject, wdname,
+ wstep, w_any, weither, wboth, weveryone, weverybody,
+ whe, wshe, wit, wthey, whim, wher, wthem, wis, ware, woops,
+ wwas, wwere,
+ win, wout, winto, wat, wto, wacross, winside, wwith, wnear, wfor,
+ wof, wbehind, wbeside, won, woff, wunder, wfrom, wthrough,
+ wtoward, wtowards, wbetween, waround, wupon, wthru,
+ wby, wover, wup, wdown,
+ wabout
+ } wtype;
+global word ext_code[wabout + 1]; /* Codes for the above */
+global short last_he, last_she, last_it, last_they;
+/* Used for pronoun support */
+
+
+
+/* -------------------------------------------------------------------- */
+/* Noun List Data structures and constants */
+/* -------------------------------------------------------------------- */
+
+
+/* The following are used in noun lists */
+#define AND_MARK (-ext_code[wand])
+#define ALL_MARK (-ext_code[wall])
+
+#define D_END 50 /* Marks end of disambiguation list */
+#define D_AND 51 /* Used to seperate multiple objects during disambig */
+#define D_NOUN 0 /* Noun match */
+#define D_SYN 1 /* Adjective/synonym only match */
+#define D_ADJ 2 /* Adj only match */
+#define D_FLAG 3 /* Flag noun */
+#define D_GLOBAL 4 /* Global noun */
+#define D_PIX 5 /* PIX name */
+#define D_PRO 6 /* Pronoun */
+#define D_ALL 7 /* ALL, or a header to an ALL EXCEPT _ AND _ ... list */
+#define D_INTERN 8 /* Internal nouns: DOOR, SCENE */
+#define D_NUM 9 /* A number, value is in obj */
+#define D_EITHER 10 /* EITHER or ANY, used only to resolve disambiguation */
+
+#define D_MARK 0x80 /* Used as a temporary marker, usually to indicate
+ this noun is being considered for elimination */
+
+
+
+/* -------------------------------------------------------------------- */
+/* These are used for text boxes (quotes and the title) */
+/* -------------------------------------------------------------------- */
+#define TB_TTL 1 /* We're printing the title */
+#define TB_BOLD 2 /* Print it bold */
+#define TB_BORDER 4 /* Give it a border */
+#define TB_CENTER 8 /* Center the text inside */
+#define TB_NOCENT 16 /* Don't center the whole box */
+
+
+/* -------------------------------------------------------------------- */
+/* In AGIL.C */
+/* -------------------------------------------------------------------- */
+extern void print_instructions(fc_type fc);
+extern void run_game(fc_type fc);
+
+/* -------------------------------------------------------------------- */
+/* In PARSER.C */
+/* -------------------------------------------------------------------- */
+extern rbool parse(void); /* Returns true unless there is ambiguity */
+extern void menu_cmd(void);
+
+
+/* -------------------------------------------------------------------- */
+/* In EXEC.C */
+/* -------------------------------------------------------------------- */
+
+/* Legal values for gen_sysmsg context; they indicate who is calling it */
+#define MSG_PARSE 0 /* The parser */
+#define MSG_MAIN 1 /* The main execution loop */
+#define MSG_RUN 2 /* The routines that execute the player's commands */
+#define MSG_DESC 3 /* Printing out description. */
+
+extern void gen_sysmsg(int msgid, const char *s, int context, const char *pword);
+/* Prints either STANDARD message <msgid> or default msg <s>;
+ <context> determines what $$ substitutions are meaningful
+ <parseword> gives the $pword$ substitution for MSG_PARSE messages */
+
+extern void exec(parse_rec *actor, int vnum, parse_rec *lnoun,
+ word prep, parse_rec *iobj);
+extern void set_statline(void);
+extern void newroom(void);
+extern void print_descr(descr_ptr dp, rbool nl);
+extern void quote(int msgnum);
+extern void print_score(void);
+extern long read_number(void);
+
+
+/* -------------------------------------------------------------------- */
+/* In TOKEN.C */
+/* -------------------------------------------------------------------- */
+extern void init_stack(void); /* Set up expression stack */
+extern void clear_stack(void); /* Set stack back to empty state */
+
+/* -------------------------------------------------------------------- */
+/* In OBJECT.C */
+/* -------------------------------------------------------------------- */
+extern rbool player_has(int item);
+extern rbool visible(int item);
+extern rbool genvisible(parse_rec *dobj);
+extern int *get_nouns(void); /* Returns list of in scope nouns */
+extern void add_object(int loc, int item); /* Adds item to loc's contents list */
+extern void tmpobj(parse_rec *objrec);
+extern void compute_scope(void); /* Sets scope flags for all of the objects */
+extern void compute_seen(void); /* Determine HAS_SEEN flag for nouns and creatures */
+
+extern void init_creat_fix(void);
+extern void free_creat_fix(void);
+
+/* ------------------------------------------------------------------- */
+/* The following are intended as building blocks to construct macros */
+/* to extract information about general objects, regardless of whether */
+/* they are nouns, creatures, or virtual nouns with no associated */
+/* data structure. */
+/* ------------------------------------------------------------------- */
+/* nounattr(item,attr) -- returns 0 if not noun.
+ creatattr(item,attr) -- returns 0 if not creature
+ objattr(item,attr) -- Returns attribute for noun or creature, 0 otherwise
+ anyattr(item,attr) -- Returns attribute for noun, creature, or room,
+ 0 otherwise.
+*/
+
+#define creatattr2(item,attr,op3) (tcreat(item)? \
+ creature[(item)-first_creat].attr:\
+ (op3))
+#define creatattr(item,attr) creatattr2(item,attr,0)
+#define nounattr2(item,attr,alt) (tnoun(item)? \
+ noun[(item)-first_noun].attr:(alt))
+#define nounattr(item,attr) nounattr2(item,attr,0)
+#define objattr(item,attr) nounattr2(item,attr,creatattr(item,attr))
+#define objattr2(item,attr,op3) nounattr2(item,attr,creatattr2(item,attr,op3))
+#define roomattr2(item,attr,op3) (troom(item)?\
+ room[(item)-first_room].attr:(op3))
+#define anyattr(item,attr) roomattr2(item,attr,objattr(item,attr))
+
+#define it_scratch(item) objattr(item,scratch)
+#define it_loc(item) objattr2(item,location,\
+ (tdoor(item)) ? loc+first_room : 0)
+
+
+/* -------------------------------------------------------------------- */
+/* In RUNVERB.C */
+/* -------------------------------------------------------------------- */
+extern int check_obj(parse_rec *act, int verbid,
+ parse_rec *donum, word prep, parse_rec *ionum);
+
+
+/* -------------------------------------------------------------------- */
+/* In AGTDBG.C */
+/* -------------------------------------------------------------------- */
+extern void debug_cmd_out(int ip, integer op, int arg1, int arg2, int optype);
+extern void debug_head(int);
+extern void debug_newline(integer op, rbool first_nl);
+
+/* -------------------------------------------------------------------- */
+/* In SAVEGAME.C */
+/* -------------------------------------------------------------------- */
+extern void savegame(void);
+extern rbool loadgame(void);
+extern void init_state_sys(void); /* Must be called before either of the following */
+extern uchar *getstate(uchar *gs);
+/* Returns malloc'd block containing game state. */
+extern void putstate(uchar *gs); /* Restores games state. */
+extern void init_vals(void); /* Compute dependent variables
+ such as totwt, totsize, etc. */
+extern void restart_game(void);
+
+
+/* -------------------------------------------------------------------- */
+/* In OS_<whatever>.C */
+/* -------------------------------------------------------------------- */
+global volatile int screen_width, status_width;
+global int screen_height;
+global volatile int curr_x;
+
+extern void init_interface(int argc, char *argv[]);
+extern void start_interface(fc_type fc);
+extern void close_interface(void);
+extern char *agt_input(int in_type); /* read line, return malloc'd string */
+extern char agt_getkey(rbool echo_char);
+extern void agt_clrscr(void);
+extern void agt_textcolor(int c);
+extern void agt_delay(int n); /* n in seconds */
+extern int agt_rand(int a, int b); /* Return random number from a to b, inclusive */
+extern void agt_newline(void);
+extern void agt_puts(const char *s); /* Output string */
+extern void agt_statline(const char *s); /* Prints s out on status line */
+extern void agt_tone(int hz, int ms);
+extern void agt_makebox(int width, int height, unsigned long flags);
+extern void agt_qnewline(void);
+extern void agt_endbox(void);
+extern genfile agt_globalfile(int fid); /* When fid=0, return global config file */
+extern rbool agt_option(int optnum, char *optstr[], rbool setflag);
+
+/* These have stubs defined in interface.c that would ened to be
+ commented out if you actually wanted to support these */
+extern void fontcmd(int cmd, int font); /* fontlist[font] */
+extern void pictcmd(int cmd, int pict); /* pictlist[pict] or pixlist[pict] */
+extern int musiccmd(int cmd, int song); /* songlist[song] */
+
+
+/* -------------------------------------------------------------------- */
+/* In INTERFACE.C */
+/* -------------------------------------------------------------------- */
+/* init_interface() (in os_?????.c) is responsible for initializing these */
+global rbool par_fill_on, center_on;
+global rbool textbold; /* Is the text currently bold? */
+
+extern void wait_return(void);
+extern void agt_waitkey(void);
+
+extern void agt_center(rbool b); /* 1=turn on text centering, 0=turn off */
+extern void agt_par(rbool b); /* 1=turn on "paragraph" mode, 0=turn off */
+extern char *agt_readline(int in_type); /* Front end for agt_input */
+extern char agt_getchar(void); /* Front end for some uses of agt_getkey */
+extern void prompt_out(int); /* 1=standard prompt, 2=question prompt */
+extern genfile get_user_file(int ft); /* 0=script, 1=save, 2=restore */
+extern void set_default_filenames(fc_type fc);
+extern void script(uchar); /* 0=turn off, 1=turn on */
+extern void logon(void); /* Turn on logging */
+extern int close_pfile(genfile f, int ft); /* ft is the same as for get_user_file */
+extern void replay(int delay); /* REPLAY */
+extern rbool yesno(const char *);
+extern void textbox(char *(txt[]), int len, unsigned long flags);
+extern void padout(int padleng); /* Outputs padleng spaces */
+extern int agt_menu(const char *header, int size, int width, menuentry *menu);
+extern fc_type new_game(void);
+
+extern void set_test_mode(fc_type fc);
+/* This sets up scripting and replaying for testing mode */
+
+/* These are intended to be called by the os layer */
+extern void print_statline(void);
+
+extern void agt_save(void);
+extern void agt_restore(void);
+extern void agt_restart(void);
+extern void agt_quit(void);
+extern void agt_newgame(fc_type fc);
+
+/* -------------------------------------------------------------------- */
+/* Object manipulation macros */
+/* -------------------------------------------------------------------- */
+#define objloop(i) for(i=first_noun; i<=maxnoun || i<=maxcreat; \
+ (i<=maxnoun || i>=first_creat) ? (i++) : (i=first_creat) )
+#define nounloop(i) for(i=0;i<=maxnoun-first_noun;i++)
+#define creatloop(i) for(i=0;i<=maxcreat-first_creat;i++)
+
+#define tdoor(x) ((x)==-ext_code[wdoor])
+
+#ifdef uagt_defined_global
+#undef global
+#undef uagt_define_global
+#endif
+
+} // End of namespace AGT
+} // End of namespace Glk
+
+#endif
diff --git a/engines/glk/agt/metacommand.cpp b/engines/glk/agt/metacommand.cpp
new file mode 100644
index 0000000000..9324a0b054
--- /dev/null
+++ b/engines/glk/agt/metacommand.cpp
@@ -0,0 +1,1154 @@
+/* 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/exec.h"
+
+namespace Glk {
+namespace AGT {
+
+/* This contains the code for scanning and running metacommands.
+ Note that while the code here deals with all of the flow-of-execution
+ details, the code for executing individual tokens is actually
+ in token.c (With a few exceptions for commands that impact
+ the order of execution). */
+
+#define DEBUG_SCAN 1
+
+#define MAX_REDIR 50000L /* Maximum number of redirects, to catch
+ infinite loops. If this is 0, allow infinitely
+ many */
+
+#define MAX_SUBCALL 2047 /* Maximum number of subroutine calls.
+ If this is 0, no limit (except for the
+ program's stack size). */
+
+
+/*
+
+scan_metacommand
+ -2=end of cycle, something happened (disambiguation only)
+ 0=end of this cycle (disambig: end of cycle, nothing happened)
+ 1=end of all commands (disambig: end of cycle, nothing happened)
+ 2=end of turn (disambig: nothing happened)
+
+run_metacommand
+ 0 to go on to next metacommand,
+ 1 to stop running metacommands, and
+ 2 to end the turn.
+ 3 indicates that redirection has just occured
+ 4 indicates a subcall has just occured.
+ 5 to go on to next metacommand after a return has occured.
+ -2 means we're doing disambiguation and just hit an action token.
+
+*/
+
+
+/* ====================================================================*/
+/* RUN METACOMMAND: The following are the routines used to execute */
+/* a single metacommand block. run_metacommand is invoked by */
+/* scan_metacommand, which is further down in this file. */
+/* ====================================================================*/
+
+/* ------------------------------------------------------------------- */
+/* TYPE CHECKING ROUTINES */
+/* Routines used to do type checking for metacommands. */
+/* ------------------------------------------------------------------- */
+
+
+rbool argvalid(int argtype, int arg) {
+ if (argtype & AGT_VAR) { /* We have a variable */
+ /* First, verify that arg actually indexes a variable */
+ if (arg < 0 || arg > VAR_NUM) return 0; /* Nope */
+
+ if (argtype == AGT_VAR) return 1; /* Pure variable; contents don't matter */
+
+ /* Next, verify its contents, using the rest of this routine */
+ arg = agt_var[arg];
+ argtype &= ~AGT_VAR; /* Mask off AGT_VAR */
+ }
+
+ if (argtype < 128) {
+ if (tnoun(arg)) return (argtype & AGT_ITEM) != 0;
+ if (troom(arg)) return (argtype & AGT_ROOM) != 0;
+ if (arg == 0) return (argtype & AGT_NONE) != 0;
+ if (arg == 1) return (argtype & AGT_SELF) != 0;
+ if (tcreat(arg)) return (argtype & AGT_CREAT) != 0;
+ if (arg == 1000) return (argtype & AGT_WORN) != 0;
+ return 0;
+ } else switch (argtype) {
+ case AGT_NUM:
+ return 1;
+ case AGT_DIR:
+ return (arg >= 1 && arg <= 12);
+ case AGT_FLAG:
+ return (arg >= 0 && arg <= FLAG_NUM);
+ case AGT_CNT:
+ return (arg >= 0 && arg <= CNT_NUM);
+ case AGT_QUEST:
+ return (arg >= 1 && arg <= MaxQuestion);
+ case AGT_MSG:
+ return (arg >= 1 && arg <= last_message);
+ case AGT_ERR:
+ return (arg >= 1 && arg <= NUM_ERR);
+ case AGT_STR:
+ return (arg >= 1 && arg <= MAX_USTR);
+ case AGT_SUB:
+ return (arg >= 1 && arg <= MAX_SUB);
+ case AGT_PIC:
+ return (arg >= 1 && arg <= maxpict);
+ case AGT_PIX:
+ return (arg >= 1 && arg <= maxpix);
+ case AGT_FONT:
+ return (arg >= 1 && arg <= maxfont);
+ case AGT_SONG:
+ return (arg >= 1 && arg <= maxsong);
+ case AGT_ROOMFLAG:
+ return (arg >= 1 && arg <= 32);
+ case AGT_EXIT:
+ return (argvalid(AGT_ROOM | AGT_NONE, arg)
+ || argvalid(AGT_MSG, arg - exitmsg_base)
+ || (arg < 0 && aver >= AGX00)); /* Treat as verb */
+ case AGT_OBJFLAG:
+ return (arg >= 0 && arg < oflag_cnt);
+ case AGT_OBJPROP:
+ return (arg >= 0 && arg < oprop_cnt);
+ case AGT_ATTR: /* ATTR and PROP are type-checked elsewhere */
+ case AGT_PROP:
+ return 1;
+ default:
+ writeln("INTERNAL ERROR:Unrecognized type specifier.");
+ }
+ return 0;
+}
+
+/* <special> is set true for NOUN, OBJECT, NAME variables that are 0 */
+/* (In this case, some error handling is suppressed) */
+
+static rbool argfix(int argtype, int *arg, int optype, rbool *special) {
+ *special = 0;
+ switch (optype) {
+ case 0:
+ break; /* Direct: The easy case */
+ case 1: /* Variable */
+ if (*arg == -1) { /* Top-of-stack */
+ *arg = pop_expr_stack();
+ break;
+ }
+ if (!argvalid(AGT_VAR, *arg)) return 0;
+ *arg = (int)agt_var[*arg];
+ break;
+ case 2:
+ *arg = dobj;
+ *special = (dobj == 0);
+ break; /* NOUN */
+ case 3:
+ *arg = iobj;
+ *special = (iobj == 0);
+ break; /* OBJECT */
+ default:
+ rprintf("Internal error: Invalid optype.");
+ return 0;
+ }
+ if (!(optype & 2)) {
+ /* i.e. we have direct or variable type */
+ /* The noun and object types below are useless for direct use,
+ but may be useful when used as values of variables. */
+ if (argtype < 64) {
+ if (*arg == -1) { /* NAME */
+ *arg = actor;
+ *special = (actor == 0);
+ } else if (*arg == -2) { /* NOUN */
+ *arg = dobj;
+ *special = (dobj == 0);
+ } else if (*arg == -3) { /* OBJECT */
+ *arg = iobj;
+ *special = (iobj == 0);
+ }
+ }
+ }
+ return argvalid(argtype, *arg);
+}
+
+
+/* These are handled in the order ARG2 then ARG1 so that
+ top-of-stack references will pop the stack in that order
+ (so that the push-order will corrospond to the argument order) */
+/* <grammer_arg> is true if "bad" argument is NOUN/OBJECT/etc. and
+ is 0. */
+static int argok(const opdef *opdata, int *arg1, int *arg2, int optype,
+ rbool *grammer_arg) {
+ if ((opdata->argnum) > 1 && !argfix(opdata->arg2, arg2, optype % 4, grammer_arg))
+ return 0;
+ if ((opdata->argnum) > 0 && !argfix(opdata->arg1, arg1, optype / 4, grammer_arg))
+ return 0;
+ return 1;
+}
+
+/* ------------------------------------------------------------------- */
+/* INSTRUCTION DECODING ROUTINES */
+/* Routines for decoding opcodes and their arguments */
+/* ------------------------------------------------------------------- */
+
+static int decode_instr(op_rec *oprec, const integer *data, int maxleng) {
+ integer op_;
+ int optype;
+ int leng;
+ rbool special_arg1; /* Is the first argument a special 0-length argument? */
+
+ oprec->negate = oprec->failmsg = oprec->disambig = 0;
+ oprec->errmsg = NULL;
+ oprec->op = -1;
+ oprec->opdata = &illegal_def;
+ oprec->argcnt = 0;
+ oprec->endor = 1;
+
+ special_arg1 = 0;
+
+ if (maxleng <= 0) {
+ oprec->errmsg = "GAME ERROR: Unexpected end of token sequence.";
+ return 1;
+ }
+ op_ = data[0];
+ if (op_ < 0) {
+ oprec->errmsg = "GAME ERROR: Negative token found.";
+ return 1;
+ }
+ oprec->optype = optype = op_ / 2048; /* Split op_ into operand proper and optype */
+ oprec->op = op_ = op_ % 2048;
+ oprec->opdata = get_opdef(op_);
+
+ if (oprec->opdata == &illegal_def) {
+ if (op_ < START_ACT)
+ oprec->errmsg = "GAME ERROR: Illegal condition token encountered.";
+ else
+ oprec->errmsg = "GAME ERROR: Illegal action token encountered.";
+ return 1;
+ }
+
+ if (op_ < 1000) oprec->endor = 0; /* Conditional tokens don't end OR block */
+
+ /* Recall that oprec->disambig is initialized to 0 */
+ switch (op_) {
+ case 89:
+ case 95:
+ case 96:
+ case 97:
+ oprec->disambig = 1;
+ break; /* YesNo and Chance */
+ case WIN_ACT:
+ case WIN_ACT+1:
+ oprec->disambig = 1;
+ break; /* WinGame, EndGame */
+
+ case 1037:
+ case 1038: /* DoSubroutine, Return */
+ case 1062:
+ case 1115: /* RedirectTo, SetDisambigPriority */
+ case 1132: /* AND */
+ case 1149:
+ case 1150: /* Goto and OnFailGoto */
+ case 1151: /* EndDisambig */
+ case 1152: /* XRedirect */
+ break; /* Accept default of 0: these tokens don' trigger disambig */
+
+ case 1135:
+ case 1137:
+ case 1138:
+ case 1139:
+ case 1140:
+ case 1141:
+ case 1142:
+ case 1143:
+ case 1147:
+ case 1159:
+ oprec->endor = 0;
+ break; /* Operations that only affect the stack don't
+ stop disambiguation, either. They also
+ don't mark the end of an OR block */
+
+ default:
+ /* Aside from the above exceptions, all actions will stop
+ disambiguation (with success) and all conditions will let it
+ continue. */
+ oprec->disambig = (op_ >= START_ACT && op_ < WIN_ACT);
+ }
+
+ if (op_ >= 1128 && op_ <= 1131) /* FailMessage group */
+ oprec->failmsg = 1;
+
+ leng = oprec->opdata->argnum + 1;
+ if (optype != 0) { /* Correct leng for NOUN and OBJECT args */
+ special_arg1 = ((optype & 8) == 8);
+ leng -= special_arg1 + ((optype & 2) == 2);
+ if (leng < 1) {
+ oprec->errmsg = "GAME ERROR: Token list corrupted.";
+ return 1;
+ }
+ }
+ if (leng > maxleng) {
+ oprec->errmsg = "GAME ERROR: Unexpected end of token sequence";
+ return 1;
+ }
+
+ if (op_ == 108) { /* NOT */
+ leng = 1 + decode_instr(oprec, data + 1, maxleng - 1);
+ oprec->negate = !oprec->negate;
+ return leng;
+ }
+ oprec->argcnt = leng - 1;
+ oprec->arg1 = oprec->arg2 = 0;
+ if (leng >= 2) {
+ if (special_arg1) {
+ assert(leng == 2);
+ oprec->arg2 = data[1];
+ oprec->arg1 = 0;
+ } else oprec->arg1 = data[1];
+ }
+ if (leng >= 3) oprec->arg2 = data[2];
+ if (leng >= 4) writeln("INTERNAL ERROR: Too many token arguments.");
+ return leng;
+}
+
+
+/* decode_args checks and decodes the arguments to metacommand tokens */
+/* Returns false on an error */
+static rbool decode_args(int ip_, op_rec *oprec) {
+ rbool grammer_arg; /* Have NOUN/OBJECT that is 0 and so failed argok tests */
+
+ if (oprec->errmsg != NULL) {
+ if (!PURE_ERROR)
+ writeln(oprec->errmsg);
+ return 0;
+ }
+ if (DEBUG_AGT_CMD && !supress_debug) {
+ if (oprec->negate) { /* Output NOT */
+ debug_cmd_out(ip_, 108, 0, 0, 0);
+ ip_++;
+ }
+ }
+
+ if (DEBUG_AGT_CMD && !supress_debug)
+ debug_cmd_out(ip_, oprec->op, oprec->arg1, oprec->arg2, oprec->optype);
+
+ /* This checks and translates the arguments */
+ if (!argok(oprec->opdata, &(oprec->arg1), &(oprec->arg2),
+ oprec->optype, &grammer_arg)) {
+ /* Don't report errors for null NOUN/OBJECT/ACTOR arguments
+ used in conditional tokens */
+ if (grammer_arg && oprec->op <= MAX_COND)
+ return 0;
+ if (!PURE_ERROR) {
+ if (DEBUG_AGT_CMD && !supress_debug) debugout("\n");
+ writeln("GAME ERROR: Invalid argument to metacommand token.");
+ }
+ return 0;
+ }
+ return 1;
+}
+
+
+
+
+/* ------------------------------------------------------------------- */
+/* Subroutine Call Stack routines */
+/* ------------------------------------------------------------------- */
+/* Note: run_metacommand() passes subroutine calls up to it's parent,
+ but it processes Returns on its own (and is the routine responsible
+ for maintaining the subcall stack-- scan_metacommand treats
+ a subroutine call just like RedirecTo) */
+/* The progression for subroutine calls goes like this:
+ run_metacommand hits a DoSubroutine token;
+ the subroutine id is saved in subcall_arg by exec_token.
+ run_metacommand does push_subcall, saving cnum and ip,
+ and then returns 4 to scan_metacommand.
+ scan_metacommand saves grammar state to the new stack entry
+ with push_subcall and then starts scanning SUBROUTINEnn
+
+ Many tokens are executed.
+
+ run_metacommand hits Return. It sets restart_state and
+ returns 5 to its parent.
+ scan_metacommand then runs pop_subcall_grammar and restores
+ the original scanning grammer. It subtracts one from cnum
+ so the original cnum will be rerun.
+ run_metacommand sees that restart_state is set and pops the
+ rest of the information (cnum and ip) off of the stack.
+ Things continue as usual.
+ */
+
+
+
+typedef struct {
+ /* run_metacommand state */
+ short cnum, ip, failaddr;
+ /* scan_metacommand state */
+ integer mactor, mdobj, miobj;
+ word mprep;
+ short vcode;
+ /* Global state (is this really saved?) */
+ short vb;
+ word prep;
+} subcall_rec;
+
+
+static subcall_rec *substack = NULL;
+static short subcnt = 0;
+static short subsize = 0;
+
+
+static rbool push_subcall(int cnum, int ip_, int failaddr) {
+ subcall_rec *savestack; /* In case something goes wrong. */
+
+ if (MAX_SUBCALL != 0 && ++subcnt > MAX_SUBCALL)
+ return 0;
+ if (subcnt > subsize) {
+ subsize += 5;
+ savestack = substack;
+ rm_trap = 0;
+ substack = (subcall_rec *)rrealloc(substack, subsize * sizeof(subcall_rec));
+ rm_trap = 1;
+ if (substack == NULL) { /* out of memory */
+ substack = savestack;
+ return 0;
+ }
+ }
+ substack[subcnt - 1].cnum = cnum;
+ substack[subcnt - 1].ip = ip_;
+ substack[subcnt - 1].failaddr = failaddr;
+ return 1;
+}
+
+
+/* pop_subcall_grammar is called before this */
+static void pop_subcall(int *rcnum, int *rip, int *rfailaddr) {
+ assert(*rcnum == substack[subcnt - 1].cnum);
+ /* *rcnum=substack[subcnt-1].cnum; */
+ *rip = substack[subcnt - 1].ip;
+ *rfailaddr = substack[subcnt - 1].failaddr;
+ subcnt--;
+}
+
+/* This is called after push_subcall */
+static void push_subcall_grammar(int m_actor, int vcode, int m_dobj, word m_prep,
+ int m_iobj, int cnum) {
+ /* run_metacommand should already have pushed cnum on the stack */
+ substack[subcnt - 1].vb = vb;
+ substack[subcnt - 1].prep = prep;
+ substack[subcnt - 1].mactor = m_actor;
+ substack[subcnt - 1].vcode = vcode;
+ substack[subcnt - 1].mdobj = m_dobj;
+ substack[subcnt - 1].mprep = m_prep;
+ substack[subcnt - 1].miobj = m_iobj;
+}
+
+/* Return false if something goes wrong-- such as stack underflow. */
+/* This is called *before* pop_subcall */
+static rbool pop_subcall_grammar(integer *m_actor, int *vcode,
+ integer *m_dobj, word *m_prep, integer *m_iobj,
+ int *cnum) {
+ if (subcnt == 0) return 0;
+ vb = substack[subcnt - 1].vb;
+ prep = substack[subcnt - 1].prep;
+ *cnum = substack[subcnt - 1].cnum;
+ *m_actor = substack[subcnt - 1].mactor;
+ *vcode = substack[subcnt - 1].vcode;
+ *m_dobj = substack[subcnt - 1].mdobj;
+ *m_prep = substack[subcnt - 1].mprep;
+ *m_iobj = substack[subcnt - 1].miobj;
+ return 1;
+}
+
+
+
+
+/* ------------------------------------------------------------------- */
+/* Run Metacommand */
+/* ------------------------------------------------------------------- */
+
+static int run_metacommand(int cnum, int *redir_offset)
+/* cnum=command number to run. */
+/* *redir_offset=offset of redirect header, if we exit with redirection. */
+/* Return
+ 0 to go on to next metacommand,
+ 1 to stop running metacommands, and
+ 2 to end the turn.
+ 3 indicates that redirection has just occured
+ 4 indicates a subcall has just occured.
+ 5 Is used to go on to the next metacommand after a Return.
+ -2 means we're doing disambiguation and just hit an action token. */
+{
+ int ip_, oip; /* ip_=Instruction pointer, oip=Old instruction pointer */
+ int r; /* Used to hold return value from token execution */
+ int fail_addr; /* What address to jump to on failure */
+ rbool fail; /* Last token was a conditional token that failed */
+ rbool ortrue, blocktrue, orflag; /* OR stuff
+ orflag: Are we in an OR group?
+ ortrue: Is current OR group true?
+ blocktrue: Is current block w/in OR true?
+ */
+ static rbool restart = 0; /* Restarting after subroutine? */
+ op_rec currop; /* Information on the current token and its args */
+
+ fail_addr = 32000; /* Fall off the end when we fail */
+ fail = 0;
+ ip_ = 0;
+ orflag = blocktrue = ortrue = 0;
+ *redir_offset = 1; /* Default: This is what RedirectTo does.
+ Only XRedirect can send a different value */
+
+
+ if (restart) /* finish up Return from subroutine */
+ pop_subcall(&cnum, &ip_, &fail_addr);
+
+ if (DEBUG_AGT_CMD && !supress_debug) {
+ debug_head(cnum);
+ if (restart) debugout(" (Resuming after subroutine)\n");
+ }
+
+ restart = 0;
+
+
+ /* ========== Main Loop ================= */
+ while (ip_ < command[cnum].cmdsize) {
+
+ oip = ip_;
+ ip_ += decode_instr(&currop, command[cnum].data + ip_, command[cnum].cmdsize - ip_);
+
+ /* ------- OR Logic --------------- */
+ if (currop.op == 109) { /* OR */
+ if (!orflag) { /* First OR; set things up */
+ orflag = 1;
+ ortrue = 0;
+ blocktrue = 1;
+ }
+ blocktrue = blocktrue && !fail; /* Was the previous token true? */
+ fail = 0;
+ ortrue = ortrue || blocktrue; /* OR in last block */
+ blocktrue = 1; /* New block starts out true. */
+ } else if (orflag) { /* we're in the middle of a block */
+ blocktrue = blocktrue && !fail; /* Add in previous token */
+ fail = 0;
+ if (currop.endor) { /* i.e. not a conditional token */
+ orflag = 0; /* End of OR block */
+ ortrue = ortrue || blocktrue; /* OR in last block */
+ fail = !ortrue; /* Success of whole group */
+ }
+ }
+
+ /* ------------ FAILMESSAGE handling ------------- */
+ if (currop.failmsg) { /* Is the current token a Fail... token? */
+ if (!fail) continue; /* Skip it; look at next instruction */
+ /* ErrMessage and ErrStdMessage: set disambiguation score */
+ if (do_disambig) {
+ if (currop.op == 1130 || currop.op == 1131) {
+ if (!decode_args(oip, &currop)) return 2;
+ disambig_score = currop.arg1;
+ return 2;
+ } else return -2; /* FailMessage counts as an action token */
+ }
+ /* Then run the failmessage, skipping the following step... */
+ }
+ /* -------- Failure routines -------------------- */
+ else if (fail) { /* ... and not failmessage */
+ /* consequences of failure */
+ fail = 0; /* In case fail_addr doesn't point off the edge of the world */
+ ip_ = fail_addr;
+ fail_addr = 32000; /* Reset fail_addr */
+ continue; /* Usually fail_addr will fall off the end, causing this to
+ return 0 */
+ }
+
+ /* - Finish decoding arguments and print out debugging message - */
+ if (!decode_args(oip, &currop)) {
+ if (currop.op < 1000) fail = currop.negate ? 0 : 1;
+ continue;
+ /* return 2;*/
+ }
+
+ /* -------- Commands that need to be handled specially -------------- */
+ if (currop.op == 109) { /* OR */
+ if (DEBUG_AGT_CMD && !supress_debug) debug_newline(op, 0);
+ continue; /* OR: skip further processing */
+ }
+
+ if (currop.op == 1037) { /* DoSubroutine */
+ if (!push_subcall(cnum, ip_, fail_addr)) {
+ writeln("GAME ERROR: Subroutine stack overflow.");
+ return 2;
+ }
+ subcall_arg = currop.arg1;
+ if (DEBUG_AGT_CMD && !supress_debug) debugout("--> Call\n");
+ return 4;
+ }
+
+ if (currop.op == 1038) { /* Return */
+ restart = 1;
+ if (DEBUG_AGT_CMD && !supress_debug) debugout("--> Return\n");
+ return 5;
+ }
+
+ if (currop.op == 1149) { /* Goto */
+ ip_ = currop.arg1;
+ if (DEBUG_AGT_CMD && !supress_debug) debugout("\n");
+ continue;
+ }
+
+ if (currop.op == 1150) { /* OnFailGoto */
+ fail_addr = currop.arg1;
+ if (DEBUG_AGT_CMD && !supress_debug) debugout("\n");
+ continue;
+ }
+
+ if (currop.op == 1152) /* XRedirect */
+ *redir_offset = currop.arg1;
+
+ /* ---------- Disambiguation Success -------------- */
+ if (do_disambig && currop.disambig) {
+ if (DEBUG_AGT_CMD && !supress_debug) debugout("==> ACTION\n");
+ return -2;
+ }
+
+ /* ---------- Run normal metacommands -------------- */
+ switch (r = exec_instr(&currop)) {
+ case 0: /* Normal action token or successful conditional token */
+ if (DEBUG_AGT_CMD && !supress_debug) debug_newline(op, 0);
+ continue;
+ case 1: /* Conditional token: fail */
+ if (DEBUG_AGT_CMD && !supress_debug) {
+ if (orflag) debugout(" (-->FAIL)\n");
+ else debugout("--->FAIL\n");
+ }
+ fail = 1;
+ continue;
+ default: /* Return explicit value */
+ if (DEBUG_AGT_CMD && !supress_debug) {
+ if (r == 103) debugout("-->Redirect\n");
+ else debugout("==> END\n");
+ }
+ return r - 100;
+ }
+ }
+ return 0;
+}
+
+
+
+/* ====================================================================*/
+/* SCAN METACOMMAND: These are the routines that scan through the */
+/* metacommand headers and find the appropriate ones to execute */
+/* Redirection is also handled at this level */
+/* ====================================================================*/
+
+
+/* ------------------------------------------------------------------- */
+/* Support routines for extracting object information */
+/* ------------------------------------------------------------------- */
+
+/* For $ strings. Returns object number if there is one, or negative
+ the dictionary index.
+ This is used by the metacommand redirection routines */
+
+static integer expand_redirect(word w) {
+ assert(w != -1); /* <*NONE*> object shouldn't make it this far */
+ if (w == 0 || aver < AGTME10) return -w;
+ if (w == ext_code[wdverb]) return -syntbl[auxsyn[vb]];
+ if (w == ext_code[wdnoun]) return dobj;
+ if (w == ext_code[wdobject]) return iobj;
+ if (w == ext_code[wdname]) return actor;
+ if (w == ext_code[wdadjective]) return -it_adj(dobj);
+ if (w == ext_code[wdprep]) return -prep;
+ return -w;
+}
+
+
+static int extract_actor(int actnum) {
+ if (actnum < 0) actnum = -actnum; /* Erase redirection stuff */
+ if (tcreat(actnum)) return actnum;
+ else return 0;
+}
+
+/* Basically, we need to find an object with a matching noun
+ and adj to our choice. */
+static int extract_obj(word name, word adj) {
+ int i, obj;
+
+ /* We just take the first one. We split this into separate noun and
+ creature loops for performance reaons */
+
+ if (name == -1) /* <*NONE*> */
+ return 0;
+
+ obj = expand_redirect(name);
+ adj = it_name(expand_redirect(adj));
+
+ if (obj > 0) { /* $noun$, $object$, or $name$ */
+ if (adj == 0 || adj == it_adj(obj))
+ return obj; /* We're done */
+ name = it_name(obj);
+ } else
+ name = -obj;
+
+ if (adj == 0) return -name; /* Adjectives required for CLASS redirect */
+ nounloop(i)
+ if (noun[i].name == name && noun[i].adj == adj) return i + first_noun;
+ creatloop(i)
+ if (creature[i].name == name && creature[i].adj == adj)
+ return i + first_creat;
+ /* Hmm... just hope it's an internal noun. */
+ writeln("GAME ERROR: Redirect statement with bad object name.");
+ return -name;
+}
+
+
+/* ------------------------------------------------------------------- */
+/* Redirection Routines */
+/* ------------------------------------------------------------------- */
+
+
+#define wordcode_fix(w) it_name(expand_redirect(w));
+
+/* 'real_obj' below is the dobj_obj/iobj_obj field; it takes
+ precedence over anything else if it is nonzero.
+ It represents an *explicitly* declared object in
+ the header */
+
+static void fix_objnum(integer *objnum, word match,
+ int real_obj,
+ int actor_, int dobj_, int iobj_) {
+ if (real_obj) *objnum = real_obj;
+ else if (match == ext_code[wdobject]) *objnum = iobj_;
+ else if (match == ext_code[wdnoun]) *objnum = dobj_;
+ else if (match == ext_code[wdname]) *objnum = actor_;
+}
+
+/* Returns TRUE if we changed *objrec, FALSE otherwise */
+/* (This is needed for memory allocation purposes) */
+static rbool fix_objrec(parse_rec **objrec, word match,
+ int real_obj,
+ parse_rec *actrec, parse_rec *dobjrec,
+ parse_rec *iobjrec) {
+ if (real_obj) *objrec = make_parserec(real_obj, NULL);
+ else if (match == ext_code[wdobject]) *objrec = copy_parserec(iobjrec);
+ else if (match == ext_code[wdnoun]) *objrec = copy_parserec(dobjrec);
+ else if (match == ext_code[wdname]) *objrec = copy_parserec(actrec);
+ else return 0; /* *objrec unchanged */
+
+ return 1; /* *objrec changed */
+}
+
+static void objcode_fix(cmd_rec *cmd)
+/* For $ strings. Fixes object redirection if neccessary */
+{
+ int actorword;
+ word nounword, objword;
+ int dobj_obj, iobj_obj;
+ int savedobj, saveactor;
+ parse_rec *savedrec, *saveactrec, *saveirec;
+ rbool achange, dchange, ichange; /* Did the given _rec ptr change? */
+
+ /* dobj_obj/iobj_obj take precedence over anything else */
+ actorword = cmd->actor;
+ nounword = cmd->nouncmd;
+ objword = cmd->objcmd;
+ dobj_obj = cmd->noun_obj;
+ iobj_obj = cmd->obj_obj;
+
+ /* Make temporary copies of things for when more than one thing is
+ being shuffled around; we don't need to save iobj since
+ it's processed last */
+ saveactor = actor;
+ saveactrec = actor_rec;
+ savedobj = dobj;
+ savedrec = dobj_rec;
+ saveirec = iobj_rec; /* Saved only so it can be freed */
+
+ /* Fix object numbers... */
+ fix_objnum(&actor, actorword, 0, saveactor, savedobj, iobj);
+ fix_objnum(&dobj, nounword, dobj_obj, saveactor, savedobj, iobj);
+ fix_objnum(&iobj, objword, iobj_obj, saveactor, savedobj, iobj);
+
+ /* ... and records */
+ achange = fix_objrec(&actor_rec, actorword, 0, saveactrec, savedrec, iobj_rec);
+ dchange = fix_objrec(&dobj_rec, nounword, dobj_obj, saveactrec, savedrec, iobj_rec);
+ ichange = fix_objrec(&iobj_rec, objword, iobj_obj, saveactrec, savedrec, iobj_rec);
+
+ /* Free up whatever needs freeing */
+ if (achange) rfree(saveactrec);
+ if (dchange) rfree(savedrec);
+ if (ichange) rfree(saveirec);
+}
+
+
+/* Redirection is very superficial-- normally all it does is */
+/* change the matching pattern, not the underlying objects */
+/* The one exception is when we use the special redirection tokens */
+/* NOUN or OBJECT */
+
+void redirect_exec(cmd_rec *cmd, word *m_actor, int *vcode,
+ word *m_dobj, word *m_prep, word *m_iobj) {
+ *m_actor = extract_actor(cmd->actor);
+ vb = *vcode = verb_code(it_name(expand_redirect(cmd->verbcmd)));
+ *m_dobj = extract_obj(cmd->nouncmd, cmd->noun_adj);
+ if (cmd->prep == -1)
+ *m_prep = 0;
+ else
+ *m_prep = it_name(expand_redirect(cmd->prep));
+ *m_iobj = extract_obj(cmd->objcmd, cmd->obj_adj);
+
+ /* This shuffles the _real_ objects if $noun$ forms are being
+ used */
+ objcode_fix(cmd);
+}
+
+
+
+
+/* ------------------------------------------------------------------- */
+/* Scan Metacommand and the matching function it uses */
+/* ------------------------------------------------------------------- */
+
+/* This is used to match the elements of metacommand trigger patterns */
+/* Sees if w2 matches COMMMAND pattern word w1; w1==0 corresponds to ANY */
+#define cmatch(w1,w2) ((w1)==0 || (w1)==(w2) || ((w1)==-1 && (w2)==0))
+
+static int cm_actor(int actnum, int actor_)
+/* cmd: actnum, player entry: actor_ */
+{
+ if (aver < AGX00) return 1; /* Bit of AGT brain-deadness. */
+ if (actnum == 1) return actor_ == 0; /* No actor_: just the player */
+ if (tcreat(actnum))
+ return (creat_fix[actor_ - first_creat] == creat_fix[actnum - first_creat]);
+ if (actnum == 2) return (actor_ != 0); /* ANYBODY? */
+ return (actor_ == 0);
+}
+
+
+/* Check that the explicit object matches */
+static rbool cm_x_obj(int x_obj, int real_obj) {
+ if (x_obj == 0) return 1; /* No explicit object; automatically match. */
+ /* Explicit object case */
+ /* In this case, we match against the _real_ object */
+ /* However, we also require a "normal" match */
+ do {
+ if (x_obj == real_obj) return 1;
+ real_obj = it_class(real_obj);
+ } while (real_obj != 0);
+ return 0;
+}
+
+/* Does [obj] match <adj> <noun> [x_obj]? */
+/* --[obj] must match up with <adj> <noun> */
+/* --If x_obj(the explicit object) is defined, it must match with
+ the "real" object-- that is, the global dobj or iobj value. */
+static rbool cm_obj(word name, word adj, int x_obj, int obj, int real_obj) {
+ if (name == -1) return (obj == 0); /* <NONE> */
+
+ if (x_obj && !cm_x_obj(x_obj, real_obj)) return 0;
+
+ /* (Note that ANY does not match ALL) */
+ if (obj == -ext_code[wall])
+ return (name == ext_code[wall] && adj == 0);
+
+ do { /* Work our way up the class hierarchy */
+ if (cmatch(name, it_name(obj)) && cmatch(adj, it_adj(obj)))
+ return 1;
+ obj = it_class(obj);
+ } while (obj != 0);
+
+ return 0;
+}
+
+
+
+static void scan_dbg(int vcode) {
+ char buff[220];
+ word w;
+
+ if (vcode >= BASE_VERB && vcode < BASE_VERB + DUMB_VERB
+ && syntbl[synlist[vcode]] != 0)
+ w = syntbl[synlist[vcode]];
+ else w = syntbl[auxsyn[vcode]];
+
+ if (strlen(dict[w]) > 200) return; /* Just in case... */
+ sprintf(buff, "+++++Scanning %s\n", dict[w]);
+ debugout(buff);
+}
+
+#define not_any(n,a) (n!=0 || a!=0)
+
+/* This returns true if we redirect from VERB OBJ {PREP OBJ}
+ to something that has fewer objects or no (explicit) preposition.
+ This is less perfect than I would like since there is currently
+ no way of distinguishing between ANY and an empty slot unless
+ the new "NOMATCH" extension is used. */
+
+static rbool redir_narrows_grammar(cmd_rec *cmd1, cmd_rec *cmd2) {
+ /* Check inward from obj to prep to noun; if in any of these
+ fields cmd2 has ANY and cmd1 doesn't, return 1.
+ Stop as soon as we find a non-ANY field in either one. */
+
+ /* If we *are* using the new extension, we can just use that info */
+ if (cmd2->objcmd == -1) {
+ if (cmd1->objcmd != -1) return 1;
+ if (cmd1->prep == -1) {
+ if (cmd1->prep != -1) return 1;
+ if (cmd2->nouncmd == -1 && cmd1->objcmd != -1) return 1;
+ }
+ }
+ if (nomatch_aware) return 0; /* If we are using nomatch, don't need
+ to go through the rest of this nonsense. */
+
+ if (not_any(cmd2->objcmd, cmd2->obj_adj)) return 0;
+ if (not_any(cmd1->objcmd, cmd1->obj_adj)) return 1;
+
+ if (cmd2->prep != 0) return 0;
+ if (cmd1->prep != 0) return 1;
+
+ if (not_any(cmd2->nouncmd, cmd2->noun_adj)) return 0;
+ if (not_any(cmd1->nouncmd, cmd1->noun_adj)) return 1;
+
+ return 0; /* They are both all ANY. */
+}
+
+
+
+static rbool cm_command(cmd_rec *cmd,
+ integer m_actor, int m_verb,
+ integer m_dobj, word m_prep, integer m_iobj) {
+ if (cmd->verbcmd == 0) { /* ANY */
+ if (cmd->actor == 0 && aver >= AGX00)
+ return (m_verb == 0); /* ANY command: rest of line ignored */
+ /* Else ANY matchs; go on to test other things. */
+ } else if (cmd->verbcmd != m_verb) return 0;
+
+ return
+ cm_actor(cmd->actor, m_actor)
+ && cm_obj(cmd->nouncmd, cmd->noun_adj, cmd->noun_obj, m_dobj, dobj)
+ && cmatch(cmd->prep, m_prep)
+ && cm_obj(cmd->objcmd, cmd->obj_adj, cmd->obj_obj, m_iobj, iobj);
+}
+
+
+
+static void scan_for_actor(integer m_actor, int *start, int *end) {
+ int i;
+
+ assert(m_actor != 0);
+
+ if (aver >= AGX00) {
+ if (start != NULL) *start = verbptr[DIR_ADDR_CODE];
+ *end = verbend[DIR_ADDR_CODE];
+ return;
+ }
+ for (i = verbend[DIR_ADDR_CODE]; i > verbptr[DIR_ADDR_CODE]; i--)
+ if (creat_fix[command[i].actor - first_creat]
+ == creat_fix[m_actor - first_creat]) {
+ i++;
+ break;
+ }
+ *end = i;
+
+ if (start == NULL) return;
+
+ for (i = verbptr[DIR_ADDR_CODE]; i <= *end; i++)
+ if (creat_fix[command[i].actor - first_creat]
+ == creat_fix[m_actor - first_creat])
+ break;
+ *start = i;
+}
+
+
+/* m_<word> are the matching criterion; they have no *neccessary*
+ connection to dobj, iobj, etc. */
+
+int scan_metacommand(integer m_actor, int vcode,
+ integer m_dobj, word m_prep, integer m_iobj,
+ int *redir_flag)
+/* Return codes: 0=end of this cycle, 1=end of all commands
+ 2=end of turn */
+/* If doing disambiguation, then -2=end of cycle, something happened;
+ 0 or 1=end of cycle; nothing happened; 2=end of turn, nothing happened. */
+/* If redir_flag is non-NULL, it is set when redirection occurs:
+ 1+=Redirection occured
+ 2=Grammar-changing redirection occured. */
+{
+ int i, oldi;
+ word m_verb;
+ int scanend;
+ int redir_offset; /* Used for multiple redirects in the same
+ metacommand (which can occur in AGATE-style
+ commands)-- this is used to hold the offset
+ of the given redirect. */
+ long redirect_count; /* This is a safety measure: this keeps track of how
+ many redirections have occured on a single turn, and
+ if there are "too many" it will issue an error message
+ and stop. This is to prevent the system from getting
+ into a redirection loop. The number should be set
+ high enough not to prevent deliberate loops,
+ however. */
+
+ rfree(substack);
+ subcnt = 0;
+ subsize = 0;
+ redirect_count = 0;
+
+ if (mars_fix)
+ if (vcode == 0 || m_actor == 2) return 0;
+ /* Don't explicity scan ANY metacommands if MARS fix is active. */
+ if (m_actor == -ext_code[weverybody]) m_actor = 2;
+
+
+ if (DEBUG_AGT_CMD && DEBUG_SCAN && !supress_debug) scan_dbg(vcode);
+
+ m_verb = syntbl[auxsyn[vcode]];
+ if (m_actor == 0) {
+ i = verbptr[vcode];
+ scanend = verbend[vcode];
+ } else
+ scan_for_actor(m_actor, &i, &scanend);
+ for (; i < scanend; i++)
+ if (command[i].actor < 0) {
+ /* REDIRECT data; skip over it */;
+ } else if (cm_command(&command[i], m_actor, m_verb, m_dobj, m_prep, m_iobj))
+ switch (run_metacommand(i, &redir_offset)) {
+ case -2:
+ rfree(substack);
+ return -2;
+ /* We are doing disambiguation and reached
+ an action token */
+ case 0:
+ break; /* Go onto next metacommand */
+ case 1:
+ rfree(substack);
+ return 1; /* Done with metacommands */
+ case 2:
+ rfree(substack);
+ return 2; /* Done with turn */
+
+
+ /* -------- REDIRECTION ------------ */
+ /* This handles RedirectTo tokens */
+ case 3:
+ oldi = i;
+ i += redir_offset;
+ if (i == last_cmd || command[i].actor > 0) {
+ if (!PURE_ERROR) writeln("GAME ERROR: Invalid REDIRECT token.");
+ rfree(substack);
+ return 2;
+ }
+ if (MAX_REDIR != 0 && ++redirect_count > MAX_REDIR) {
+ if (!PURE_ERROR) writeln("GAME ERROR: Infinite REDIRECT loop.");
+ rfree(substack);
+ return 2;
+ }
+ if (DEBUG_AGT_CMD && !supress_debug) {
+ debugout(" ==>");
+ debug_head(i);
+ }
+
+ /* REDIRECT :If we do a redirect from a broader grammar to a
+ narrower grammer, it will be noted so that certain types
+ of grammer checking can be disabled. */
+ if (redir_flag != NULL) {
+ if (*redir_flag < 2
+ && redir_narrows_grammar(&command[oldi], &command[i]))
+ *redir_flag = 2;
+
+ /* Set *redir_flag to at least 1 if we do *any* redirection. */
+ if (!*redir_flag) *redir_flag = 1;
+ }
+
+ /* REDIRECT: Do the actual redirection, building the new command
+ header and shuffling around nouns and verbs as
+ neccessary */
+ redirect_exec(&command[i], &m_actor, &vcode,
+ &m_dobj, &m_prep, &m_iobj);
+
+ /* REDIRECT: Start scanning again from the beginning */
+ if (!mars_fix) {/* In MARS, we *don't* go back to the top */
+ if (m_actor != 0)
+ scan_for_actor(m_actor, &i, &scanend);
+ else {
+ i = verbptr[vcode];
+ scanend = verbend[vcode];
+ }
+ i--; /* Back up one so that the following i++ we'll
+ be at the right location */
+ }
+
+ /* So when i is incremented, we start back at the correct start: i.e.
+ we start scanning again from the beginning. It's even possible
+ to use REDIRECT to run verb commands from an AFTER command,
+ although it precludes other AFTER commands from running. */
+ m_verb = syntbl[auxsyn[vcode]];
+ break;
+
+
+
+ /* -------- SUBROUTINE CALL ------------ */
+ case 4: /* Subroutine Call -- same idea as RedirectTo,
+ but less complicated */
+ push_subcall_grammar(m_actor, vcode, m_dobj, m_prep, m_iobj, i);
+ vcode = verb_code(sub_name[subcall_arg - 1]);
+ m_actor = m_dobj = m_iobj = 0;
+ m_prep = 0;
+
+ if (!mars_fix) /* In MARS, we *don't* go back to the top */
+ i = verbptr[vcode] - 1;
+ scanend = verbend[vcode];
+ m_verb = syntbl[auxsyn[vcode]];
+ break;
+
+
+ /* -------- RETURN ------------ */
+ case 5: /* Return: pop grammar state, then ... ? */
+ if (!pop_subcall_grammar(&m_actor, &vcode,
+ &m_dobj, &m_prep, &m_iobj, &i)) {
+ writeln("GAME ERROR: Return without DoSubroutine.");
+ rfree(substack);
+ return 2;
+ }
+
+ if (m_actor == 0)
+ scanend = verbend[vcode];
+ else
+ scan_for_actor(m_actor, NULL, &scanend);
+ m_verb = syntbl[auxsyn[vcode]];
+
+ i--; /* Cause the last command to restart,
+ at which point run_command will pop the rest of the
+ stack. */
+
+ break;
+ }
+ rfree(substack);
+ return 0; /* Done with this cycle of metacommands */
+}
+
+/* ====================================================================*/
+
+#undef cm
+
+} // End of namespace AGT
+} // End of namespace Glk
diff --git a/engines/glk/agt/object.cpp b/engines/glk/agt/object.cpp
new file mode 100644
index 0000000000..7095e1a54c
--- /dev/null
+++ b/engines/glk/agt/object.cpp
@@ -0,0 +1,1064 @@
+/* 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/exec.h"
+
+namespace Glk {
+namespace AGT {
+
+/* */
+/* This module contains most of the routines that implement the */
+/* "physics" of the AGT world, including the scope routines. */
+
+
+/* ------------------------------------------------------------------- */
+/* Functions for manipulating parse_recs */
+/* ------------------------------------------------------------------- */
+
+/* Make artificial parse record for an object */
+parse_rec *make_parserec(int obj, parse_rec *rec) {
+ if (rec == NULL) rec = (parse_rec *)rmalloc(sizeof(parse_rec));
+ rec->obj = obj;
+ rec->info = D_NOUN;
+ rec->noun = it_name(obj);
+ rec->adj = it_adj(obj);
+ rec->num = 0;
+ return rec;
+}
+
+/* This is used by the parser to initialize a blank parse_rec */
+void tmpobj(parse_rec *objrec) {
+ objrec->info = D_NOUN;
+ objrec->num = 0;
+ objrec->noun = objrec->adj = 0;
+ objrec->obj = 0;
+}
+
+parse_rec *copy_parserec(parse_rec *rec) {
+ parse_rec *newrec;
+ if (rec == NULL) return NULL;
+ newrec = (parse_rec *)rmalloc(sizeof(parse_rec));
+ memcpy(newrec, rec, sizeof(parse_rec));
+ return newrec;
+}
+
+void free_all_parserec(void) {
+ rfree(actor_rec);
+ rfree(dobj_rec);
+ rfree(iobj_rec);
+}
+
+
+/* ------------------------------------------------------------------- */
+/* Functions for doing basic manipulation of items and extracting */
+/* nformation about these items */
+/* (often used to blackbox the difference between nouns and creatures) */
+
+
+
+rbool matchclass(int obj, int oclass) {
+ int i;
+ if (oclass == 0) return 0;
+ for (i = obj; i != oclass && i != 0; i = it_class(i));
+ return i == oclass;
+}
+
+
+/* Functions for getting bascic information about items */
+
+static const char *it_sdesc(int item) {
+ if (tnoun(item)) return noun[item - first_noun].shortdesc;
+ if (tcreat(item)) return creature[item - first_creat].shortdesc;
+ if (item < 0) return dict[-item];
+ return NULL;
+}
+
+rbool it_possess(int item) {
+ int l;
+
+ l = it_loc(item);
+ return (l == 1 || l == 1000);
+}
+
+rbool it_proper(int item) {
+ if (tcreat(item))
+ return (!PURE_PROPER) || creature[item - first_creat].proper;
+ if (tnoun(item))
+ return noun[item - first_noun].proper;
+ return 0;
+}
+
+
+static rbool invischeck(const char *s) {
+ while (rspace(*s)) s++;
+ return strncasecmp(s, "INVISIBLE", 9) == 0;
+}
+
+
+static rbool it_invisible(int item, rbool sdesc_flag) {
+ if (sdesc_flag)
+ return invischeck(it_sdesc(item));
+ else {
+ char *s;
+ rbool tmp;
+
+ if (it_name(item) == 0 && it_adj(item) == 0) return 1;
+ s = objname(item); /* Must remember to rfree s before exiting */
+ tmp = invischeck(s);
+ rfree(s);
+ return tmp;
+ }
+}
+
+
+static rbool it_appears_empty(int item) {
+ int i;
+ int sdesc_flag;
+
+ if (item < 0) return 1;
+ sdesc_flag = !player_has(item);
+
+ contloop(i, item)
+ if (!it_invisible(i, sdesc_flag)) return 0;
+ return 1;
+}
+
+/* We classify something as a weapon if it kills something. */
+rbool it_isweapon(int objnum) {
+ int i;
+
+ creatloop(i)
+ if (matchclass(objnum, creature[i].weapon)) return 1;
+ return 0;
+}
+
+/* This used to be a macro like troom, tnoun, and tcreat, but it
+ got too complicated and isn't used in time-critical areas,
+ anyhow */
+
+rbool it_door(int obj, word nword) {
+ if (aver >= AGX00) return 0; /* No doors under Magx */
+ if (tdoor(obj)) return 1; /* The basic door */
+ if (it_loc(obj) == loc + first_room) return 0;
+ return (nword == ext_code[wdoor]);
+}
+
+
+/* ------------------------------------------------------------------- */
+/* Routines that manipulate the linked lists representing containment */
+/* information */
+
+static void set_contents(int p, int newval) {
+ if (troom(p)) room[p - first_room].contents = newval;
+ else if (p == 1) player_contents = newval;
+ else if (p == 1000) player_worn = newval;
+ else if (tnoun(p)) noun[p - first_noun].contents = newval;
+ else if (tcreat(p)) creature[p - first_creat].contents = newval;
+ else {
+ writeln("INT ERR: Invalid object heading chain.");
+ return;
+ }
+}
+
+static void set_next(int p, int newval) {
+ if (tnoun(p)) noun[p - first_noun].next = newval;
+ else if (tcreat(p)) creature[p - first_creat].next = newval;
+ else {
+ writeln("INT ERR: Invalid object in chain.");
+ return;
+ }
+}
+
+void add_object(int loc_, int item) {
+ int p, q;
+
+ set_next(item, 0);
+
+ if (loc_ == 0) return;
+ p = it_contents(loc_);
+
+ if (p == 0 || p > item) {
+ set_contents(loc_, item);
+ set_next(item, p);
+ } else { /* Figure out where to put the item */
+ do {
+ q = p;
+ p = it_next(p);
+ } while (p != 0 && p < item);
+
+ set_next(q, item);
+ set_next(item, p);
+ }
+}
+
+
+static void set_location(int item, int newloc)
+/* This routine assumes item is either a noun or a creature */
+{
+ int p, q;
+
+ p = it_loc(item);
+ if (p != 0) { /* Fix .next values */
+ q = it_contents(p);
+ if (q == item) set_contents(p, it_next(item));
+ else {
+ while (q != item && q != 0) {
+ p = q;
+ q = it_next(p);
+ }
+ assert(q != 0); /* This would mean the list structure was corrupted */
+ set_next(p, it_next(item));
+ }
+ }
+ /* We've unlinked it from the list at this point. */
+
+ if (tnoun(item))
+ noun[item - first_noun].location = newloc;
+ else if (tcreat(item))
+ creature[item - first_creat].location = newloc;
+
+ add_object(newloc, item);
+}
+
+
+void it_reposition(int item, int newloc, rbool save_pos) {
+ integer i;
+
+ if (tnoun(item)) {
+ if (player_has(item)) totwt -= noun[item - first_noun].weight;
+ if (it_loc(item) == 1) totsize -= noun[item - first_noun].size;
+
+ /* Set position to NULL */
+ if (!save_pos) {
+ noun[item - first_noun].pos_prep = 0;
+ noun[item - first_noun].pos_name = 0;
+ noun[item - first_noun].nearby_noun = 0;
+ noun[item - first_noun].position = NULL;
+#if 0 /* I think this was wrong, so I'm commenting it out. */
+ noun[item - first_noun].initdesc = 0;
+#endif
+ }
+
+ set_location(item, newloc);
+
+ if (player_has(item)) {
+ totwt += noun[item - first_noun].weight;
+ if (noun[item - first_noun].win)
+ winflag = 1;
+ }
+ if (it_loc(item) == 1) /* only things you are carrying directly count vs.
+ size limit. */
+ totsize += noun[item - first_noun].size;
+ } else if (tcreat(item)) {
+ if (newloc == 0) {
+ creature[item - first_creat].timecounter = 0; /* Reset attack counter */
+ creature[item - first_creat].counter = 0;
+ }
+ set_location(item, newloc);
+ }
+
+ nounloop(i)
+ if (noun[i].nearby_noun == item) {
+ noun[i].nearby_noun = 0;
+ noun[i].pos_prep = 0;
+ noun[i].pos_name = 0;
+ noun[i].position = NULL;
+ }
+}
+
+
+
+/* ------------------------------------------------------------------- */
+/* Routines to deal with size and weight */
+
+static long contsize(integer obj) {
+ int i;
+ long net;
+
+ net = 0;
+ contloop(i, obj) {
+ if (tnoun(i))
+ net += noun[i - first_noun].size;
+ if (aver < AGX00) /* Under Magx, size isn't recursive */
+ net += contsize(i);
+ }
+ return net;
+}
+
+static long contweight(integer obj) {
+ int i;
+ long net;
+
+ net = 0;
+ contloop(i, obj) {
+ if (tnoun(i))
+ net += noun[i - first_noun].weight;
+ net += contweight(i);
+ }
+ return net;
+}
+
+rbool is_within(integer obj1, integer obj2, rbool stop_if_closed)
+/* True if obj1 is contained in obj2 */
+{
+ int i;
+ long cnt;
+
+ for (i = obj1, cnt = 0;
+ i != obj2 && i >= maxroom && i != 1000 && cnt < 40000L;
+ cnt++) {
+ i = it_loc(i);
+ if (stop_if_closed && !it_open(i)) break;
+ }
+ if (cnt >= 40000L) {
+ /* writeln("GAME ERROR: Loop in object tree.");*/
+ return 0;
+ }
+ if (i == obj2) return 1;
+ return 0;
+}
+
+
+
+int check_fit(int obj1, int obj2)
+/* Does obj1 fit inside obj2? Return one of the FIT_... values
+defined in exec.h */
+{
+ int size, weight;
+ long net;
+
+ assert(tnoun(obj1)); /* This should have been checked earlier */
+ if (obj2 == 1000) obj2 = 1;
+
+ if (obj2 == 1) size = weight = 100;
+ else {
+ assert(tnoun(obj2)); /* check_fit shouldn't be called otherwise */
+ size = noun[obj2 - first_noun].size;
+ weight = noun[obj2 - first_noun].weight;
+ }
+
+ /* Weight */
+ if (obj2 == 1 || (aver > AGTME15 && aver < AGX00)) {
+ /* Pre-1.56 interpreters forgot to check this;
+ Magx deliberatly *doesn't* check this */
+
+ net = noun[obj1 - first_noun].weight;
+ if (aver >= AGX00) net += contweight(obj1);
+ if (net > weight) return FIT_WEIGHT;
+
+ if (obj2 == 1) {
+ if (is_within(obj1, 1, 0) || is_within(obj1, 1000, 0)) net = 0;
+ net += contweight(1);
+ if (aver >= AGX00)
+ net += contweight(1000);
+ if (!PURE_SIZE) net = 0;
+ } else {
+ if (is_within(obj1, obj2, 0)) net = 0; /* Avoid double-counting */
+ net += contweight(obj2); /* Net size of contents of obj2 */
+ }
+ if (net > weight) return FIT_NETWEIGHT;
+ }
+
+ net = noun[obj1 - first_noun].size;
+ if (net > size) return FIT_SIZE;
+
+ if (obj2 == 1 && !PURE_SIZE) return FIT_OK;
+
+ if (obj2 == 1 || aver > AGTME15) {
+ /* Pre-ME/1.56 interpreters didn't check this except for the player's
+ inventory */
+ if (it_loc(obj1) == obj2
+ || (aver < AGX00 && is_within(obj1, obj2, 0)))
+ net = 0; /* Avoid double-counting */
+ net += contsize(obj2); /* Net size of contents of obj2 */
+ if (net > size) return FIT_NETSIZE;
+ }
+
+ return FIT_OK;
+}
+
+
+
+/* ------------------------------------------------------------------- */
+/* Scope and visibility routines */
+
+integer it_room(int item) {
+ int tmploc;
+ long cnt;
+
+ cnt = 0;
+ while (!troom(item)) {
+ tmploc = item;
+ if (item == 0) return 0;
+ if (item == 1 || item == 1000) item = loc;
+ else item = it_loc(item);
+ if (item == tmploc || ++cnt >= 40000L) {
+ /* writeln("GAME ERROR: Loop in object tree."); */
+ return 0;
+ }
+ }
+ return item;
+}
+
+rbool player_has(int item) {
+ return is_within(item, 1, 0) || is_within(item, 1000, 0);
+}
+
+
+rbool in_scope(int item)
+/* strictly speaking, visible actually checks scope; this routine
+ determines if an object would be in scope if there were no light
+ problems. */
+{
+ int curloc;
+ int tmp;
+ long cnt;
+
+ if (it_isglobal(item)) return 1; /* Global objects always in scope. */
+
+ /* Flag objects in scope if their associated flag is set. */
+ tmp = it_flagnum(item);
+ if (tmp &&
+ (room[loc].flag_noun_bits & (1L << (tmp - 1)))) return 1;
+
+ curloc = it_loc(item); /* Should work for nouns or creatures */
+ cnt = 0;
+ while (curloc > maxroom && curloc != 1000 && it_open(curloc)) {
+ int tmploc;
+ tmploc = it_loc(curloc);
+ if (tmploc == curloc || ++cnt >= 40000L) {
+ /* writeln("GAME ERROR: Loop in the object tree."); */
+ return 0;
+ } else curloc = tmploc;
+ }
+ if (curloc == 1 || curloc == 1000 || curloc == loc + first_room) return 1;
+ else return 0;
+}
+
+
+static int good_light(int obj, int roomlight, rbool active)
+/* obj is a noun number */
+/* If active is false, we don't care if the light is actually turned
+on is the valid light */
+{
+ if (roomlight == 1 && !noun[obj].light)
+ return 0; /* obj is not a light source */
+ if (roomlight > 1) {
+ if (!matchclass(first_noun + obj, roomlight))
+ return 0; /* Not the correct light */
+ else return 1; /* The correct light _always_ illuminates the room */
+ }
+ if (!active) return 1;
+ /* Now need to determine if obj is actually providing light */
+ if (!noun[obj].on)
+ return 0; /* Light source is off or extinguished */
+ return 1;
+}
+
+int lightcheck(int parent, int roomlight, rbool active)
+/* This checks to see if anything contained in parent is a valid
+room light */
+/* active=1 means that we only want active lights;
+active=0 indicates that extinguished light sources are okay. */
+{
+ int i;
+
+ contloop(i, parent) {
+ if (tnoun(i) && good_light(i - first_noun, roomlight, active)) return 1;
+ if (it_open(i) && lightcheck(i, roomlight, active))
+ return 1; /* Check children */
+ }
+ return 0;
+ /*
+ nounloop(i)
+ if (good_light(i,room[loc].light) && in_scope(i+first_noun))
+ return 1;
+ return 0;*/
+}
+
+
+rbool islit(void) {
+ if (room[loc].light == 0) return 1;
+ if (lightcheck(loc + first_room, room[loc].light, 1)) return 1;
+ if (lightcheck(1, room[loc].light, 1)) return 1; /* Player carried light */
+ if (lightcheck(1000, room[loc].light, 1)) return 1; /* Worn light */
+ return 0;
+}
+
+/* Is item visible to player? */
+/* visible only works for "normal" items; if the object could
+ be a virtual object (i.e. with negative item number), then use
+ gen_visible() below */
+rbool visible(int item) {
+ assert(item >= 0);
+ if (islit())
+ return in_scope(item);
+ else
+ return player_has(item);
+}
+
+rbool genvisible(parse_rec *dobj_) {
+ int i;
+
+ if (dobj_->obj > 0) return visible(dobj_->obj);
+
+ if (dobj_->info == D_INTERN) {
+ if (dobj_->obj != -ext_code[wdoor]) return 1;
+ return islit(); /* If item is a is a door */
+ }
+ if (dobj_->info == D_GLOBAL || dobj_->info == D_NUM) return 1;
+ if (dobj_->info == D_FLAG) {
+ for (i = 0; i < MAX_FLAG_NOUN; i++) /* Flag nouns */
+ if (flag_noun[i] != 0 && dobj_->obj == -flag_noun[i]
+ && (room[loc].flag_noun_bits & (1L << i)) != 0)
+ return 1;
+ return 0;
+ }
+ if (dobj_->info == D_PIX) {
+ for (i = 0; i < MAX_PIX; i++) /* PIX names */
+ if (pix_name[i] != 0 && dobj_->obj == -pix_name[i] &&
+ (room[loc].PIX_bits & (1L << i)) != 0)
+ return 1;
+ return 0;
+ }
+ fatal("INTERNAL ERROR: Invalid gen_visible type.");
+ return 0;
+}
+
+
+
+
+/* Need to find a noun related to w */
+/* If there is an object with name w in scope, it returns 0
+ (since the object will have already been added to the menu).
+ if there are none, it returns the first object with name w. */
+static integer find_related(word w) {
+ int i;
+ int item;
+
+ if (w == 0) return 0;
+ item = 0;
+ nounloop(i)
+ if (noun[i].name == w) {
+ if (visible(i + first_noun)) return i + first_noun;
+ else if (item == 0) item = i + first_noun;
+ }
+ creatloop(i)
+ if (creature[i].name == w) {
+ if (visible(i + first_creat)) return i + first_creat;
+ else if (item == 0) item = i + first_creat;
+ }
+ return item;
+}
+
+
+static void add_to_scope(integer item) {
+ integer i;
+
+ if (tnoun(item)) {
+ noun[item - first_noun].scope = 1;
+ i = find_related(noun[item - first_noun].related_name);
+ if (i != 0) {
+ if (tnoun(i)) noun[i - first_noun].scope = 1;
+ else if (tcreat(i)) creature[i - first_creat].scope = 1;
+ }
+ } else if (tcreat(item)) creature[item - first_creat].scope = 1;
+ if (item == 1 || item == 1000 || troom(item) || it_open(item))
+ contloop(i, item)
+ add_to_scope(i);
+}
+
+
+void compute_scope(void) {
+ int i;
+ uint32 rflag;
+
+ nounloop(i) noun[i].scope = 0;
+ creatloop(i) creature[i].scope = 0;
+ add_to_scope(1);
+ add_to_scope(1000);
+ add_to_scope(loc + first_room);
+ rflag = room[loc].flag_noun_bits;
+ nounloop(i)
+ if (noun[i].isglobal ||
+ (noun[i].flagnum && (rflag & (1L << (noun[i].flagnum - 1)))))
+ add_to_scope(i + first_noun);
+ creatloop(i)
+ if (creature[i].isglobal ||
+ (creature[i].flagnum && (rflag & (1L << (creature[i].flagnum - 1)))))
+ add_to_scope(i + first_creat);
+}
+
+void compute_seen(void) {
+ int i;
+
+ compute_scope();
+ nounloop(i)
+ noun[i].seen = noun[i].seen || noun[i].scope;
+ creatloop(i)
+ creature[i].seen = creature[i].seen || creature[i].scope;
+}
+
+
+/*---------------------------------------------------------------------*/
+/* Routines to compute the score */
+
+void recompute_score(void) {
+ int obj;
+
+ tscore -= objscore;
+ objscore = 0;
+ nounloop(obj)
+ if (noun[obj].points && !noun[obj].unused &&
+ (visible(obj + first_noun)
+ || is_within(obj + first_noun, treas_room, 0)))
+ objscore += noun[obj].points;
+ creatloop(obj)
+ if (!creature[obj].unused && creature[obj].points
+ && visible(obj + first_creat))
+ objscore += creature[obj].points;
+ tscore += objscore;
+}
+
+
+
+/*---------------------------------------------------------------------*/
+/* Menu Noun section: routines to get a list of 'relevant' nouns for */
+/* the menuing system. They're here because they belong next to the */
+/* scope routines, above */
+
+static int *nlist, nleng; /* These are really local variables */
+
+static void add_mnoun(int n) {
+ nlist = (int *)rrealloc(nlist, (nleng + 2) * sizeof(int));
+ nlist[nleng] = n;
+ nlist[++nleng] = 0;
+}
+
+
+/* This adds mitem and everything it contains */
+static void add_mitem(int item) {
+ integer i;
+
+ if (tnoun(item) || tcreat(item)) add_mnoun(item);
+ if (item == 1 || item == 1000 || troom(item) || it_open(item))
+ contloop(i, item)
+ add_mitem(i);
+ /* Need to check related nouns */
+ if (tnoun(item)) {
+ i = find_related(noun[item - first_noun].related_name);
+ if (i != 0) add_mnoun(i);
+ }
+}
+
+
+static word getword(int item, int n)
+/* Gets nth word associated with item */
+{
+ if (n == 1) {
+ if (item < 0) return -item;
+ else if (tnoun(item)) return noun[item - first_noun].adj;
+ else if (tcreat(item)) return creature[item - first_creat].adj;
+ }
+ if (n == 2)
+ if (tnoun(item) || tcreat(item)) return it_name(item);
+ return 0;
+}
+
+static int cmp_nouns(const void *a, const void *b)
+/* *a, *b are object numbers; need alphabetic sort.*/
+{
+ word wa, wb;
+ int cmp;
+
+ wa = getword(*((const int *)a), 1);
+ wb = getword(*((const int *)b), 1);
+ cmp = strcmp(dict[wa], dict[wb]);
+ if (cmp != 0) return cmp;
+ wa = getword(*(const int *)a, 2);
+ wb = getword(*(const int *)b, 2);
+ return strcmp(dict[wa], dict[wb]);
+}
+
+int *get_nouns(void)
+/* This returns the list of all objects that should show up on the menu */
+/* The list should be 0 terminated and needs to be sorted */
+{
+ int i;
+ uint32 rflag;
+
+ nlist = (int *)rmalloc(sizeof(int));
+ nlist[0] = 0;
+ nleng = 0;
+
+ for (i = 0; i < numglobal; i++)
+ add_mnoun(-globalnoun[i]);
+ for (i = 0; i < MAX_FLAG_NOUN; i++)
+ if (room[loc].flag_noun_bits & (1L << i))
+ add_mnoun(-flag_noun[i]);
+ add_mitem(1);
+ add_mitem(1000);
+ add_mitem(loc + first_room);
+ rflag = room[loc].flag_noun_bits;
+ nounloop(i)
+ if (noun[i].isglobal ||
+ (noun[i].flagnum && (rflag & (1L << (noun[i].flagnum - 1)))))
+ add_mitem(i + first_noun);
+ creatloop(i)
+ if (creature[i].isglobal ||
+ (creature[i].flagnum && (rflag & (1L << (creature[i].flagnum - 1)))))
+ add_mitem(i + first_creat);
+ qsort(nlist, nleng, sizeof(int), cmp_nouns);
+ return nlist;
+}
+
+
+
+/*---------------------------------------------------------------------*/
+/* goto_room, the basic primitive used to move the player around */
+
+void goto_room(int newroom) {
+ int i, j;
+
+ /* Move group members in old room to new room */
+ safecontloop(i, j, loc + first_room)
+ if (it_group(i))
+ it_move(i, newroom + first_room);
+
+#if 0 /* -- this has been moved to v_go*/
+ if (loc != newroom)
+ oldloc = loc; /* Save old location for NO_BLOCK_HOSTILE purposes */
+#endif
+ loc = newroom;
+ if (loc != newroom) oldloc = loc; /* No backtracking unless v_go allows it */
+ if (!room[loc].seen) {
+ room[loc].seen = 1;
+ tscore += room[loc].points;
+ first_visit_flag = 1;
+ room_firstdesc = 1;
+ v_look();
+ } else {
+ first_visit_flag = 0;
+ if (verboseflag)
+ v_look(); /* But see v_go() for a special case involving SPECIAL */
+ room_firstdesc = 0;
+ }
+ if (room[loc].end) endflag = 1;
+ if (room[loc].win) winflag = 1;
+ if (room[loc].killplayer) deadflag = 1;
+ do_autoverb = 1;
+ set_statline();
+}
+
+
+static void rundesc(int i, descr_ptr dp_[], const char *shortdesc, int msgid) {
+ if (dp_[i].size > 0)
+ print_descr(dp_[i], 1);
+ else if (!invischeck(shortdesc))
+ raw_lineout(shortdesc, 1, MSG_DESC, NULL);
+ else sysmsg(msgid, "$You$ see nothing unexpected.");
+}
+
+void it_describe(int dobj_) {
+ if (troom(dobj_))
+ print_descr(room_ptr[dobj_ - first_room], 1);
+ else if (tnoun(dobj_))
+ rundesc(dobj_ - first_noun, noun_ptr, noun[dobj_ - first_noun].shortdesc, 194);
+ else if (tcreat(dobj_))
+ rundesc(dobj_ - first_creat, creat_ptr,
+ creature[dobj_ - first_creat].shortdesc, 195);
+ else if (dobj_ == -ext_code[wdoor]) { /* i.e. DOOR */
+ if (room[loc].locked_door)
+ sysmsg(21, "$You$ see a locked door.");
+ else sysmsg(22, "$You$ see a perfectly normal doorway.");
+ } else sysmsg(194, "$You$ see nothing unexpected.");
+ if (tnoun(dobj_) &&
+ (noun[dobj_ - first_noun].open || !noun[dobj_ - first_noun].closable) &&
+ !it_appears_empty(dobj_)) {
+ sysmsg(228, "Which contains:");
+ print_contents(dobj_, 1);
+ }
+}
+
+
+
+static char *build_position(word prep_, word name)
+/* Return the malloc'd string '$prep_$ the $name$' */
+{
+ int leng;
+ char *s;
+
+ leng = strlen(dict[prep_]) + strlen(dict[name]) + 6; /* includes final '\0' */
+ s = (char *)rmalloc(leng * sizeof(char));
+
+ strcpy(s, dict[prep_]);
+ strcat(s, " the ");
+ strcat(s, dict[name]);
+ assert((int)strlen(s) + 1 == leng);
+ return s;
+}
+
+
+
+
+
+
+static int print_obj(int obj, int ind_lev)
+/* Prints out s on a line of its own if obj isn't INVISIBLE */
+/* parent_descr is true if the parent has been described, false
+ otherwise (say if the parent is invisible). */
+/* ind_lev=indentation level */
+/* Return 1 if we actually printed something, 0 if obj is invisible */
+{
+ int sdesc_flag; /* True if should print out as sdesc rather than
+ as adjective-noun */
+ int i, retval, parent;
+ const char *s;
+ char *t, *s0, *posstr;
+
+ if (tcreat(obj) && creature[obj - first_creat].initdesc != 0)
+ return 0; /* Don't print normal description if printing initdesc */
+
+ s0 = NULL;
+ sdesc_flag = !player_has(obj); /* This should be tested. */
+ sdesc_flag = sdesc_flag || (ind_lev > 1); /* It seems that AGT uses the
+ sdesc for describing items
+ contained in other items */
+ /* Some code below relies on this, as well */
+
+ if (sdesc_flag)
+ s = it_sdesc(obj);
+ else if (it_name(obj) == 0 && it_adj(obj) == 0) /* Invisible */
+ return 0;
+ else {
+ s0 = objname(obj); /* Must remember to rfree s before exiting */
+ if (aver >= AGTME10) {
+ for (t = s0; isspace(*t); t++); /* Skip over initial whitespace... */
+ *t = toupper(*t); /* ...and upcase the first non-space character */
+ }
+ s = s0;
+ }
+
+ retval = 0;
+ if (sdesc_flag && tnoun(obj) && noun[obj - first_noun].initdesc != 0) {
+ retval = 1;
+ msgout(noun[obj - first_noun].initdesc, 1);
+ noun[obj - first_noun].initdesc = 0; /* Only show it once */
+ } else if (!invischeck(s)) {
+ retval = 1; /* We're actually going to print something */
+ for (i = 0; i < ind_lev; i++) writestr(" ");
+ raw_lineout(s, sdesc_flag, MSG_DESC, NULL);
+ /* Do $word$ formatting if sdesc */
+ /* Need to output container */
+ parent = it_loc(obj);
+ if (tnoun(obj) && noun[obj - first_noun].pos_prep != 0) {
+ writestr(" (");
+ if (noun[obj - first_noun].pos_prep == -1)
+ writestr(noun[obj - first_noun].position);
+ else {
+ posstr = build_position(noun[obj - first_noun].pos_prep,
+ noun[obj - first_noun].pos_name);
+ writestr(posstr);
+ rfree(posstr);
+ }
+ writestr(")");
+ } else if (parent >= first_noun && it_invisible(parent, sdesc_flag)
+ && (it_name(parent) != 0 || it_adj(parent) != 0)) {
+ /* If the parent object *isn't* invisible, we will already have
+ printed it out */
+ /* This also relies on sdesc_flag being the same for parent
+ and child objects */
+
+ if (parent >= first_creat && parent <= maxcreat)
+ sysmsg(221, "(Carried by");
+ else
+ sysmsg(222, " (Inside");
+ t = objname(parent);
+ writestr(t);
+ rfree(t);
+ sysmsg(223, ")");
+ }
+ if (tnoun(obj) && noun[obj - first_noun].light && noun[obj - first_noun].on
+ && PURE_OBJ_DESC)
+ sysmsg(220, " (Providing light)");
+ writeln("");
+ }
+ if (!sdesc_flag)
+ rfree(s0);
+ return retval;
+}
+
+
+int print_contents(int obj, int ind_lev)
+/* obj=object to list contents of; ind_lev=indentation level */
+/* Returns number of objects contained in obj that were listed */
+{
+ int i, cnt;
+
+ cnt = 0;
+
+ contloop(i, obj) {
+ if (print_obj(i, ind_lev)) cnt++;
+ if (it_open(i)) print_contents(i, ind_lev + 1);
+ }
+ return cnt;
+}
+
+
+/* ------------------------------------------------------------------- */
+/* Routines for directly getting and setting object properties and */
+/* attributes. */
+
+
+#define NUM_WPROP 6
+#define NUM_WATTR 6
+
+
+static void *compute_addr(int obj, int prop, const prop_struct *ptable) {
+ int ofs;
+ void *base;
+
+ if (DIAG)
+ rprintf("(Accessing %s->%s)\n", dict[it_name(obj)], ptable[prop].name);
+ if (troom(obj)) {
+ base = (void *)(&room[obj - first_room]);
+ ofs = ptable[prop].room;
+ } else if (tnoun(obj)) {
+ base = (void *)(&noun[obj - first_noun]);
+ ofs = ptable[prop].noun;
+ } else if (tcreat(obj)) {
+ base = (void *)(&creature[obj - first_creat]);
+ ofs = ptable[prop].creature;
+ } else return NULL;
+
+ if (ofs == -1) /* Field doesn't exist in this type of object */
+ return NULL;
+
+ return (void *)(((char *)base) + ofs);
+}
+
+
+long getprop(int obj, int prop) {
+ integer *paddr;
+
+ if (prop >= NUM_PROP) return 0;
+ paddr = (integer *)compute_addr(obj, prop, proplist);
+ if (paddr == NULL) return 0;
+ return *paddr;
+}
+
+void setprop(int obj, int prop, long val) {
+ integer *paddr;
+
+ if (prop >= NUM_WPROP) {
+ writeln("GAME ERROR: Read-only or non-existant property.");
+ return;
+ }
+
+ paddr = (integer *)compute_addr(obj, prop, proplist);
+ if (paddr == NULL) {
+ writeln("GAME ERROR: Property-object mismatch.");
+ return;
+ }
+ *paddr = val;
+}
+
+rbool getattr(int obj, int prop) {
+ rbool *paddr;
+
+ if (prop >= NUM_ATTR) return 0;
+ paddr = (rbool *)compute_addr(obj, prop, attrlist);
+ if (paddr == NULL) return 0;
+ return *paddr;
+}
+
+void setattr(int obj, int prop, rbool val) {
+ rbool *paddr;
+
+ if (prop >= NUM_WATTR && prop != 24) {
+ writeln("GAME ERROR: Read-only or non-existant attribute.");
+ return;
+ }
+
+ paddr = (rbool *)compute_addr(obj, prop, attrlist);
+ if (paddr == NULL) {
+ writeln("GAME ERROR: Property-object mismatch.");
+ return;
+ }
+ *paddr = val;
+}
+
+
+
+/* ------------------------------------------------------------------- */
+/* This sets up the creat_fix[] array, which is used to determine the */
+/* scan ranges for addressed creatures in cases where there is more */
+/* than one creature of the same name */
+
+void init_creat_fix(void) {
+ int i, j;
+
+ creat_fix = (integer *)rmalloc(rangefix(maxcreat - first_creat + 1) * sizeof(integer));
+ for (i = 0; i < maxcreat - first_creat + 1; i++)
+ creat_fix[i] = i + first_creat;
+ for (i = 0; i < maxcreat - first_creat + 1; i++)
+ if (creat_fix[i] == i + first_creat) /* That is, it hasn't changed. */
+ for (j = i + 1; j < maxcreat - first_creat + 1; j++)
+ if (creature[i].name == creature[j].name &&
+ creature[i].adj == creature[j].adj)
+ creat_fix[j] = i + first_creat; /* That is, j --> i */
+}
+
+void free_creat_fix(void) {
+ rfree(creat_fix);
+}
+
+/* ------------------------------------------------------------------- */
+
+#ifndef IT_MACRO
+int it_contents(integer obj) {
+ if (tnoun(obj)) return noun[obj - first_noun].contents;
+ else if (troom(obj)) return room[obj - first_room].contents;
+ else if (tcreat(obj)) return creature[obj - first_creat].contents;
+ else if (obj == 1) return player_contents;
+ else if (obj == 1000) return player_worn;
+ else return 0;
+}
+
+rbool it_lockable(integer obj, word nword) {
+ if (tnoun(obj)) return noun[obj - first_noun].lockable;
+ else if (it_door(obj, nword)) return 1;
+ else return 0;
+}
+
+rbool it_locked(integer obj, word nword) {
+ if (tnoun(obj)) return noun[obj - first_noun].locked;
+ else if (it_door(obj, nword) && room[loc].locked_door) return 1;
+ else return 0;
+}
+
+#endif
+
+} // End of namespace AGT
+} // End of namespace Glk
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
diff --git a/engines/glk/agt/parser.cpp b/engines/glk/agt/parser.cpp
new file mode 100644
index 0000000000..72bb9eaa7b
--- /dev/null
+++ b/engines/glk/agt/parser.cpp
@@ -0,0 +1,1621 @@
+/* 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"
+
+namespace Glk {
+namespace AGT {
+
+/*
+
+ This is probably the ugliest and least readable of all of the
+ source files. The parser isn't really that complex in principle,
+ but it has to deal with a lot of of special cases and also be
+ downward-compatible with the original parsers (which sometimes did
+ strange things) which adds a lot of additional code. The noun parsing
+ code is particularly convoluted.
+
+ Also, there are a fair number of global variables in this module
+ that are really local variables at heart but ended up global
+ because they were needed in too many different places and I didn't
+ want to complicate things even more by having to explicitly pass
+ them all up and down through the parsing routines. (One of those
+ times it would be convenient to have the ability to nest procedures
+ and functions like Pascal.)
+
+*/
+
+/* These store the previous turn's values for use in disambiguation and
+ implementing OOPS */
+/* parse_ip saves the ip after parsing, ip_back saves it before. */
+word input_back[MAXINPUT];
+words in_text_back[MAXINPUT];
+int ip_back, parse_ip;
+
+/* The following are global only for rfree() purposes and
+ in order to maintain state for disambiguation */
+static int vnum; /* Verb number from synonym scan */
+
+/* Pointers to negative-terminated arrays of possible nouns */
+static parse_rec *lactor = NULL, *lobj = NULL, *lnoun = NULL;
+
+static int ambig_flag = 0;
+/* Was last input ambiguous? (so player could be entering
+disambiguation info?) 1=ambig actor, 2=ambig noun,
+3=ambig obj */
+
+
+/* Empty ALL error messages, for those verbs that have their own */
+int all_err_msg[] = {73, 83, 113, 103, /* open, close, lock, unlock: 15 - 18 */
+ 239, 239, 239, 239, /* 19 - 22 */
+ 123, 125
+ }; /* eat, drink: 23 - 24 */
+
+
+
+/*-------------------------------------------------------------------*/
+/* DEBUGGING OUTPUT FUNCTIONS & MISC UTILITIES */
+/*-------------------------------------------------------------------*/
+
+/* This routine frees the space used by the parsing data structures */
+static void freeall(void) {
+ rfree(lnoun);
+ rfree(lobj);
+ lnoun = lobj = NULL;
+}
+
+
+/* Print out parse error message and abort the parse */
+static int parseerr(int msgid, const char *s, int n) {
+ if (n >= 0)
+ gen_sysmsg(msgid, s, MSG_PARSE, in_text[n]);
+ else
+ gen_sysmsg(msgid, s, MSG_PARSE, "");
+ freeall();
+ ep = n;
+ ip = -1;
+ return -1;
+}
+
+
+/* Print out noun list; used for debugging the parser */
+static void print_nlist(parse_rec *n) {
+ char *s;
+ int c;
+ char buff[100];
+
+ if (n->info == D_END)
+ writestr("----");
+ if (n->info == D_ALL) {
+ writestr("ALL ");
+ n++;
+ }
+ for (c = 0; n->info != D_END && c < 20; n++, c++)
+ if (n->info == D_AND) writestr(" AND ");
+ else if (n->info == D_NUM) { /* Number entered */
+ sprintf(buff, "#%ld(%d); ", n->num, n->obj);
+ writestr(buff);
+ } else if (n->obj < 0) {
+ writestr(dict[-(n->obj)]);
+ sprintf(buff, "(%d); ", n->obj);
+ writestr(buff);
+ } else {
+ s = objname(n->obj);
+ writestr(s);
+ rfree(s);
+ sprintf(buff, "(%d) ['%s %s']; ", n->obj, dict[n->adj], dict[n->noun]);
+ writestr(buff);
+ }
+ if (n->info != D_END) writestr("///");
+ writeln("");
+}
+
+/* Output the parser's analysis of the current input line; used
+ for debugging */
+static int parse_out(parse_rec *lactor_, int vb_, parse_rec *lnoun_, int prep_,
+ parse_rec *lobj_) {
+ writeln("ANALYSIS:");
+ writestr("Actor: ");
+ print_nlist(lactor_);
+ writestr("Verb:");
+ writeln(dict[ syntbl[auxsyn[vb_]] ]);
+ writestr("DObj: ");
+ print_nlist(lnoun_);
+ writestr("Prep: ");
+ if (prep_ != 0) writeln(dict[prep_]);
+ else writeln("---");
+ writestr("IObj: ");
+ print_nlist(lobj_);
+ return 0;
+}
+
+
+static void save_input(void) {
+ int i;
+
+ for (i = 0; input[i] != -1 && i < MAXINPUT; i++) {
+ input_back[i] = input[i];
+ strncpy(in_text_back[i], in_text[i], 24);
+ }
+ input_back[i] = -1;
+ ip_back = ip;
+}
+
+static void restore_input(void) {
+ int i;
+
+ for (i = 0; input_back[i] != -1 && i < MAXINPUT; i++) {
+ input[i] = input_back[i];
+ strncpy(in_text[i], in_text_back[i], 24);
+ }
+ input[i] = -1;
+ ip = ip_back;
+}
+
+
+
+/*-------------------------------------------------------------------*/
+/* Misc. Parsing Routines (includes parsing of verbs and preps) */
+/*-------------------------------------------------------------------*/
+
+#define w_and(w) (w==ext_code[wand] || w==ext_code[wc])
+#define w_but(w) (w==ext_code[wbut] || w==ext_code[wexcept])
+#define w_isterm(w) (w==ext_code[wp] || w==ext_code[wthen] || \
+ w==ext_code[wsc] || w_and(w) || w==-1)
+
+
+
+static word check_comb(int combptr) {
+ int k;
+ word w;
+
+ if (combptr == 0) return 0;
+ w = syntbl[combptr];
+ for (combptr += 1, k = ip; syntbl[combptr] != 0; combptr++, k++)
+ if (syntbl[combptr] != input[k]) break;
+ if (syntbl[combptr] == 0) {
+ ip = k - 1;
+ return w;
+ }
+ return 0;
+}
+
+
+static int comb_verb(void)
+/* This eliminates two-word verbs */
+{
+ int i;
+ word w;
+
+ for (i = 0; i < num_comb; i++) {
+ w = check_comb(comblist[i]);
+ if (w != 0) return w;
+ }
+
+ if (input[ip] == ext_code[wgo] && verb_authorsyn(ext_code[wgo]) == 0) {
+ /* GO <dir> --> <dir> */
+ w = input[ip + 1];
+ if (w != 0) i = verb_builtin(w);
+ else i = 0;
+ if (i != 0) {
+ ip++;
+ return w;
+ }
+ }
+
+ for (i = 0; i < num_auxcomb; i++) {
+ w = check_comb(auxcomb[i]);
+ if (w != 0) return w;
+ }
+
+ return input[ip];
+}
+
+
+
+
+
+/* This return true if the word in question is in the list
+ of original AGT verbs, but is not the canonical verb.
+ This is needed because verbs on this list
+ are not overridden by dummy_verbs in pre-Magx games. */
+static rbool orig_agt_verb(word w) {
+ int i;
+ if (aver <= AGT10 && w == ext_code[wg]) return 0; /* AGT 1.0 didn't have AGAIN */
+ for (i = 0; old_agt_verb[i] != -1 && old_agt_verb[i] != w; i++);
+ return (old_agt_verb[i] == w);
+}
+
+/* A few comments on id_verb:
+ The sequence is as follows:
+ i) Convert built-in synonyms to the base form (TAKE-->GET, for example)
+ _unless_ there is an author-defined synonym. (The original AGT
+ didn't have this 'unless'.)
+ ii) Check room-synonyms
+ iii) ID the verb based first on game-specific synonyms and then
+ falling back to the built-in ones.
+ (AGT gave the built-in ones higher priority than the game-specific
+ ones, but this causes problems and is generally a bad idea.)
+ */
+
+static int id_verb(void)
+/* Identify verb at ip=i ; return verb id if found, 0 otherwise */
+{
+ word w;
+ int j, canon_word, tmp;
+
+ w = comb_verb(); /* Combine 2-word verbs */
+ if (w == 0) return 0;
+
+ /* Pre-Canonization of w: see if w has any built-in synonyms */
+ canon_word = verb_builtin(w);
+ if (canon_word != 0) {
+ if (aver < AGX00 && orig_agt_verb(w))
+ /* In orig AGT, author-defined verbs don't override builtin syns */
+ tmp = 0;
+ else
+ tmp = verb_authorsyn(w); /* Author-defined verbs override built-in ones */
+ if (tmp == 0 || tmp == canon_word)
+ w = syntbl[auxsyn[canon_word]];
+ }
+
+ /* Now check room-specific synonyms (being the most localized,
+ they have the highest priority) */
+ for (j = room[loc].replacing_word; syntbl[j] != 0; j++)
+ if (w == syntbl[j])
+ w = room[loc].replace_word;
+
+ /* Next check to see if we already have the canonical form of the verb. */
+ /* and go through the built-in list of synonyms */
+ canon_word = verb_code(w);
+ if (!PURE_DUMMY && canon_word == 57) canon_word = 0; /* AFTER */
+ return canon_word;
+}
+
+
+#define compr_prep(w1,w2,r) {if (w1==input[ip] && w2==input[ip+1]) \
+ {ip+=2;return r;}}
+#define cprep(c1,c2,r) compr_prep(ext_code[c1],ext_code[c2],ext_code[r])
+
+/* Eventually should add support for the handful of two word preps */
+/* (eg IN TO, OUT OF,...); otherwise, this is pretty trivial. */
+static int parse_prep(void) {
+ int i;
+ int j, k;
+
+ for (j = 0; j < num_prep; j++) { /* Scan user-defined prepositions */
+ /* This table is formatted like the multi-verb table:
+ end-prep prep-word1 prepword2 etc. */
+ for (k = 0; syntbl[userprep[j] + k + 1] != 0; k++)
+ if (syntbl[userprep[j] + k + 1] != input[ip + k]) break;
+ if (syntbl[userprep[j] + k + 1] == 0) {
+ ip += k;
+ return syntbl[userprep[j]];
+ }
+ }
+ cprep(win, wto, winto);
+ cprep(wout, wof, wfrom);
+ for (i = win; i <= wabout; ++i)
+ if (ext_code[i] == input[ip]) return input[ip++];
+ return 0;
+}
+
+
+
+static int noun_syn(word w, int obj)
+/* Is the word w a synonym for the object obj? */
+/* obj is encoded as ususal. */
+/* 0=no match, 1=adjective match, 2=synonym match, 3=noun match */
+/* 2 will only occur if PURE_SYN is false */
+{
+ int i;
+
+ if (w <= 0) return 0;
+
+ if (obj >= first_noun && obj <= maxnoun) {
+ obj = obj - first_noun;
+ if (w == noun[obj].name) return 3;
+ if (noun[obj].has_syns)
+ for (i = noun[obj].syns; syntbl[i] != 0; i++)
+ if (w == syntbl[i]) return (PURE_SYN ? 3 : 2);
+ if (w == noun[obj].adj) return 1;
+ return 0;
+ }
+ if (obj >= first_creat && obj <= maxcreat) {
+ obj = obj - first_creat;
+ if (w == creature[obj].name) return 3;
+ if (creature[obj].has_syns)
+ for (i = creature[obj].syns; syntbl[i] != 0; i++)
+ if (w == syntbl[i]) return (PURE_SYN ? 3 : 2);
+ if (w == creature[obj].adj) return 1;
+ return 0;
+ }
+ return 0; /* If the object doesn't exist, can't have synonyms */
+}
+
+
+
+/*-------------------------------------------------------------------*/
+/* Noun-list manipulation functions. */
+/*-------------------------------------------------------------------*/
+
+static parse_rec *new_list(void) {
+ parse_rec *list;
+
+ list = (parse_rec *)rmalloc(sizeof(parse_rec));
+ list[0].obj = 0;
+ list[0].num = 0;
+ list[0].adj = list[0].noun = 0;
+ list[0].info = D_END;
+ return list;
+}
+
+static parse_rec *add_w_rec(parse_rec *pold, int obj0, long num0, int info0,
+ word adj0, word noun0) {
+ parse_rec *pnew;
+ int n;
+
+ for (n = 0; pold[n].info != D_END; n++);
+ pnew = (parse_rec *)rrealloc(pold, (n + 2) * sizeof(parse_rec));
+ pnew[n].obj = obj0;
+ pnew[n].num = num0;
+ pnew[n].info = info0;
+ pnew[n].adj = adj0;
+ pnew[n].noun = noun0;
+ pnew[n + 1].obj = 0;
+ pnew[n + 1].info = D_END;
+ return pnew;
+}
+
+static parse_rec *add_rec(parse_rec *old, int obj0, long num0, int info0) {
+ word w;
+
+ if (obj0 < 0) w = -obj0; /* The NOUN field of literal words will just be
+ that word */
+ else w = 0;
+ return add_w_rec(old, obj0, num0, info0, 0, w);
+}
+
+
+static parse_rec *kill_rec(parse_rec *old, int index)
+/* Remove record old[index] */
+{
+ parse_rec *pnew;
+ int i;
+
+ for (i = index; old[i].info != D_END; i++) {
+ old[i].obj = old[i + 1].obj;
+ old[i].num = old[i + 1].num;
+ old[i].noun = old[i + 1].noun;
+ old[i].adj = old[i + 1].adj;
+ old[i].info = old[i + 1].info;
+ old[i].score = old[i + 1].score;
+ }
+ pnew = (parse_rec *)rrealloc(old, i * sizeof(parse_rec)); /* Shrink it by one */
+ return pnew;
+}
+
+static parse_rec *concat_list(parse_rec *dest, parse_rec *src) {
+ int i, j;
+
+ for (i = 0; dest[i].info != D_END; i++); /* Put i at end of first list */
+ for (j = 0; src[j].info != D_END; j++); /* j marks end of the second list */
+ dest = (parse_rec *)rrealloc(dest, sizeof(parse_rec) * (i + j + 1));
+ memcpy(dest + i, src, (j + 1)*sizeof(parse_rec));
+ return dest;
+}
+
+static parse_rec *purge_list(parse_rec *list)
+/* It should be possible to make this more efficiant */
+{
+ int i;
+
+ for (i = 0; list[i].info != D_END;)
+ if ((list[i].info & D_MARK) != 0)
+ list = kill_rec(list, i);
+ else i++; /* (Note: we only increment i if we _don't_ kill) */
+ return list;
+}
+
+static void clean_list(parse_rec *list) {
+ for (; list->info != D_END; list++)
+ list->info &= ~D_MARK; /* Clear mark */
+}
+
+
+static parse_rec *copy_list(parse_rec *list) {
+ parse_rec *cpy;
+ int i;
+
+ cpy = new_list();
+ for (i = 0; list[i].info != D_END; i++);
+ cpy = (parse_rec *)rmalloc(sizeof(parse_rec) * (i + 1));
+ memcpy(cpy, list, (i + 1)*sizeof(parse_rec));
+ return cpy;
+}
+
+
+
+/* Among other things, this is used to add disambiguation information */
+static int scan_rec(int item, int num, parse_rec *list) {
+ int i;
+
+ for (i = 0; list[i].info != D_END && list[i].info != D_AND; i++)
+ if (list[i].obj == item && list[i].num == num)
+ return i;
+ return -1;
+}
+
+static rbool scan_andrec(int item, parse_rec *list) {
+ for (; list->info != D_END; list++)
+ if (list->obj == item && list->info != D_AND
+ && list->info != D_ALL) return 1;
+ return 0;
+}
+
+static rbool multinoun(parse_rec *list)
+/* Determines if LIST refers to a multiple object */
+{
+ if (list->info == D_ALL) return 1;
+ for (; list->info != D_END; list++)
+ if (list->info == D_AND) return 1;
+ return 0;
+}
+
+/* This updates the adj and noun fields of a record and also
+ updates the disambiguation priority */
+/* We are already either D_ADJ or D_SYN; this just handles
+ possible promotion */
+static void add_words_to_rec(parse_rec *nrec, word w, int tmp) {
+ word w2;
+
+ if (tmp == 2 || tmp == 3) {
+ w2 = nrec->noun;
+ nrec->noun = w;
+ w = w2;
+ }
+ if (nrec->adj == 0)
+ nrec->adj = w;
+ if (tmp == 2) nrec->info = D_SYN;
+ else if (tmp == 3) nrec->info = D_NOUN;
+}
+
+/* This updates nrec with the information in newobj; this routine
+ is called while adding disambiguation information; newobj is from
+ what the player just typed in to clarify the ambiguity */
+void update_rec_words(parse_rec *nrec, parse_rec *newobj) {
+ int tmp;
+
+ if (nrec->adj == 0) nrec->adj = newobj->adj;
+ if (newobj->info == D_ADJ) tmp = 1;
+ else if (newobj->info == D_SYN) tmp = 2;
+ else if (newobj->info == D_NOUN) tmp = 3;
+ else return;
+ add_words_to_rec(nrec, newobj->noun, tmp);
+}
+
+
+static rbool ident_objrec(parse_rec *p1, parse_rec *p2) {
+ word noun1, adj1, noun2, adj2;
+
+ if (p1->obj == p2->obj) return 1;
+ if (p1->obj <= 0 || p2->obj <= 0) return 0;
+ if (tnoun(p1->obj)) {
+ noun1 = noun[p1->obj - first_noun].name;
+ adj1 = noun[p1->obj - first_noun].adj;
+ } else if (tcreat(p1->obj)) {
+ noun1 = creature[p1->obj - first_creat].name;
+ adj1 = creature[p1->obj - first_creat].adj;
+ } else return 0;
+ if (tnoun(p2->obj)) {
+ noun2 = noun[p2->obj - first_noun].name;
+ adj2 = noun[p2->obj - first_noun].adj;
+ } else if (tcreat(p2->obj)) {
+ noun2 = creature[p2->obj - first_creat].name;
+ adj2 = creature[p2->obj - first_creat].adj;
+ } else return 0;
+ return (noun1 == noun2 && adj1 == adj2);
+}
+
+/*-------------------------------------------------------------------*/
+/* Disambiguation and ALL expansion routines */
+/*-------------------------------------------------------------------*/
+
+/* Eliminate non-creatures and non-present creatures from list. */
+/* Very similar to disambig below, but simpler. */
+static parse_rec *fix_actor(parse_rec *alist) {
+ int i, cnt;
+
+ assert(alist != NULL);
+ if (alist[0].info == D_ALL) { /* ALL?! */
+ rfree(alist);
+ return new_list();
+ }
+
+ /* Start by eliminating non-creatures */
+ cnt = 0;
+ for (i = 0; alist[i].info != D_END; i++)
+ if ((alist[i].obj < first_creat || alist[i].obj > maxcreat)
+ && alist[i].obj != -ext_code[weverybody]) {
+ if (alist[i].info != D_AND)
+ alist[i].info |= D_MARK;
+ } else cnt++;
+ alist = purge_list(alist);
+ if (cnt <= 1) return alist;
+
+ /* Now eliminate those not present */
+ cnt = 0;
+ for (i = 0; alist[i].info != D_END; i++)
+ if (!genvisible(&alist[i])) {
+ if (alist[i].info != D_AND)
+ alist[i].info |= D_MARK;
+ } else cnt++;
+
+ if (cnt == 0) alist[0].info &= ~D_MARK;
+
+ return purge_list(alist);
+}
+
+
+/* Convert disambig list to all list, moving things up. */
+static parse_rec *convert_to_all(parse_rec *list, int *ofsref) {
+ int i;
+ int cnt;
+
+ for (i = *ofsref; list[i].info != D_AND && list[i].info != D_END; i++);
+ cnt = i - *ofsref; /* Number of objects. We will add cnt-1 ANDs */
+
+ while (list[i].info != D_END) i++;
+ list = (parse_rec *)rrealloc(list, (i + cnt) * sizeof(parse_rec));
+ memmove(list + *ofsref + 2 * cnt - 1, list + *ofsref + cnt,
+ (i + 1 - cnt - *ofsref)*sizeof(parse_rec));
+ for (i = cnt - 1; i >= 0; i--) {
+ int k;
+ list[*ofsref + 2 * i] = list[*ofsref + i];
+ if (i == 0) break;
+ k = *ofsref + 2 * i - 1;
+ list[k].obj = 0;
+ list[k].num = 0;
+ list[k].adj = list[k].noun = 0;
+ list[k].info = D_AND;
+ }
+ /* *ofsref+=2*cnt-1; */
+ return list;
+}
+
+
+
+static parse_rec *add_disambig_info(parse_rec *ilist, int ofs,
+ parse_rec *truenoun)
+/* Basically, try to find interesection of tmp and truenoun,
+or failing that, between ilist and truenoun */
+/* truenoun is what the player just typed in to resolve
+the ambiguity */
+{
+ int i, n;
+
+ for (i = ofs; ilist[i].info != D_AND && ilist[i].info != D_END; i++) {
+ if (truenoun[0].info == D_EITHER) {
+ /* Mark all but the first for deletion */
+ if (i > ofs) ilist[i].info |= D_MARK;
+ } else {
+ n = scan_rec(ilist[i].obj, ilist[i].num, truenoun);
+ if (n == -1)
+ ilist[i].info |= D_MARK;
+ else /* Add any new information to the words */
+ update_rec_words(&ilist[i], &truenoun[n]);
+ }
+ }
+ ilist = purge_list(ilist);
+ truenoun[0].obj = 0;
+ truenoun[0].num = 0;
+ truenoun[0].info = D_END; /* Truenoun no longer useful */
+ return ilist;
+}
+
+static int max_disambig_score;
+
+static int score_disambig(parse_rec *rec, int ambig_type)
+/* This is just a wrapper for check_obj (defined in runverb.c) */
+/* ambig_type=1 for actor, 2 for noun, 3 for object */
+/* We can assume that the earlier bits have already been disambiguated */
+/* Parse of current command in lactor, vnum, lnoun, prep, and lobj */
+{
+ if (ambig_type == 1) /* ACTOR */
+ return DISAMBIG_SUCC;
+ else if (ambig_type == 2) /* NOUN */
+ return check_obj(lactor, vnum, rec, prep, NULL);
+ else if (ambig_type == 3) /* IOBJ */
+ return check_obj(lactor, vnum, lnoun, prep, rec);
+ else fatal("Invalid ambig_type!");
+ return 0;
+}
+
+
+/* This routine does all expansion: it returns a list of ALL objects in the
+ current context (lactor, vnum, <ALL>, prep, lobj) */
+/* lnoun is assumed to be a list of exceptions, which needs to be freed
+ at the end. (i.e. we want to expand to ALL EXCEPT <lnoun>) */
+static parse_rec *expand_all(parse_rec *lnoun_) {
+ parse_rec *list;
+ int i, j;
+ rbool prev_obj; /* Is there a previous object on the list? */
+ rbool kill_obj; /* Don't put current object on ALL list after all */
+ parse_rec temp_obj; /* Used to pass object info to disambiguation routine */
+
+ if (debug_parse) {
+ writestr("ALL BUT:");
+ print_nlist(lnoun_);
+ }
+ tmpobj(&temp_obj);
+ nounloop(i)
+ noun[i].scratch = 0;
+ creatloop(i)
+ creature[i].scratch = 0;
+ objloop(i)
+ if (((verbflag[vnum]&VERB_GLOBAL) != 0 || visible(i))
+ && (lnoun_ == NULL || !scan_andrec(i, lnoun_))) {
+ temp_obj.obj = i;
+ if (score_disambig(&temp_obj, 2) >= 500) {
+ if (tnoun(i)) noun[i - first_noun].scratch = 1;
+ else if (tcreat(i)) creature[i - first_creat].scratch = 1;
+ else writeln("INTERNAL ERROR: Invalid object type in expand_all().");
+ }
+ }
+
+ /* The following ensures that if an object and it's container
+ are both selected, only the container will actually make it
+ onto the list.*/
+ list = new_list();
+ prev_obj = 0;
+ objloop(i)
+ if (it_scratch(i)) {
+ kill_obj = 0;
+ for (j = it_loc(i); tnoun(j) || tcreat(j); j = it_loc(j))
+ if (it_scratch(j)) {
+ kill_obj = 1;
+ break;
+ }
+ if (kill_obj) continue;
+ /* Now actually add object to list. */
+ if (prev_obj) list = add_rec(list, 0, 0, D_AND);
+ list = add_rec(list, i, 0, D_SYN);
+ prev_obj = 1;
+ }
+
+ if (debug_parse) {
+ writestr("ALL==>");
+ print_nlist(list);
+ }
+ rfree(lnoun_);
+ return list;
+}
+
+
+
+/* disambig check checks for the various things that can eliminate a noun
+ from the list. The higher dlev is, the more things that are bad */
+/* Returns 1 if we should keep it, 0 if we should kill it */
+/* The elimination order is based on AGT:
+ 0-Eliminate adjective matches if PURE_NOUN is set and
+ out-of-scope adjectives that are not at the head of the list.
+ 1-Eliminate adj matches if the adjective is out-of-scope.
+ 2-eliminate SCENE and DOOR (D_INTERN)
+ 3-eliminate out-of-scope nouns that are not at the head of the list
+ 4-eliminate out-of-scope nouns
+ 5-eliminate numbers that don't have associated dictionary words
+ 6-eliminate adj only matches (i.e. noun-free) [only if PURE_ADJ]
+ 7-eliminate pronouns (D_PRO)
+ -eliminate ALL (D_ALL)
+ -eliminate numbers
+ (Never eliminated: FLAG,GLOBAL,PIX,SYN,NOUN)
+*/
+
+#define MAX_DSCHEME 3
+#define MAX_DLEV 2
+/* ambig_type=1 for actor, 2 for noun, 3 for object */
+/* We can assume that the earlier bits have already been disambiguated */
+/* Parse of current command in lactor, vnum, lnoun, prep, and lobj */
+/* pick_one is used to select the first noun in the case that all of
+ them get eliminated during visibility testing (if none of the nouns
+ are visible, we don't want to ask disambiguation questions) */
+
+static rbool disambig_check(parse_rec *rec, int dsch, int dlev,
+ int ambig_type, rbool pick_one) {
+ switch (dsch) {
+ case 0:
+ switch (dlev) { /* Syntactic checks: pre-scope */
+ case 0:
+ return (!PURE_ADJ || rec->info != D_ADJ);
+ case 1:
+ return (rec->info != D_INTERN); /* Elim SCENE and DOOR */
+ case 2:
+ return (rec->info != D_NUM || rec->obj != 0); /* Eliminate numbers w/o
+ corrosponding word matches */
+ default:
+ return 0;
+ }
+ case 1:
+ switch (dlev) { /* Scope checks */
+ case 0:
+ /* Just compute the scores, but don't eliminate anything yet */
+ /* if PURE_DISAMBIG, no intel dismbig */
+ if (PURE_DISAMBIG || rec->info == D_NUM)
+ rec->score = DISAMBIG_SUCC;
+ else rec->score = score_disambig(rec, ambig_type);
+ if (rec->score >= max_disambig_score)
+ max_disambig_score = rec->score;
+ return 1;
+ case 1:
+ return (rec->score == max_disambig_score);
+ case 2:
+ return (rec->info == D_NUM
+ || ((verbflag[vnum] & VERB_GLOBAL) != 0 && rec->score >= 500)
+ || (tnoun(rec->obj) && noun[rec->obj - first_noun].scope)
+ || (tcreat(rec->obj) && creature[rec->obj - first_creat].scope));
+ default:
+ return 1;
+ }
+ case 2:
+ switch (dlev) { /* Syntax, take 2 */
+ case 0: /* Reserved for alternative adjective checking */
+ return 1;
+ /* Kill internal matches */
+ case 1:
+ return (rec->info != D_PRO && rec->info != D_ALL &&
+ rec->info != D_INTERN && rec->info != D_NUM);
+ default:
+ return 0;
+ }
+ case 3:
+ return pick_one;
+ default:
+ return 0;
+ }
+}
+
+
+/* disambig_a_noun does disambiguation for a single AND-terminated block */
+/* *list* contains the list of possible objects, */
+/* *ofs* is our starting offset within the list (since with ANDs we */
+/* may not be starting at list[0]) */
+
+static parse_rec *disambig_a_noun(parse_rec *list, int ofs, int ambig_type)
+/* ambig_type=1 for actor, 2 for noun, 3 for object */
+/* We can assume that the earlier bits have already been disambiguated */
+/* Parse of current command in lactor, vnum, lnoun, prep, and lobj */
+{
+ int i, cnt; /* cnt keeps track of how many nouns we've let through */
+ int dsch; /* Dismabiguation scheme; we run through these in turn,
+ pushing dlev as high as possible for each scheme */
+ int dlev; /* Disambiguation level: how picky do we want to be?
+ We keep raising this until we get a unique choice
+ or until we reach MAX_DLEV and have to give up */
+ rbool one_in_scope; /* True if at least one noun is visible; if no nouns
+ are visible then we never ask for disambiguation
+ from the player (to avoid giving things away) but
+ just take the first one. */
+
+ cnt = 2; /* Arbitrary number > 1 */
+ one_in_scope = 0;
+ max_disambig_score = -1000; /* Nothing built in returns anything lower than 0,
+ but some game author might come up with a
+ clever application of negative scores */
+ for (dsch = 0; dsch <= MAX_DSCHEME; dsch++)
+ for (dlev = 0; dlev <= MAX_DLEV; dlev++) {
+ if (DEBUG_DISAMBIG)
+ rprintf("\nDISAMBIG%c%d:%d: ", (dsch == 1 ? '*' : ' '), dsch, dlev);
+ cnt = 0;
+ for (i = ofs; list[i].info != D_END && list[i].info != D_AND; i++)
+ if (disambig_check(&list[i], dsch, dlev, ambig_type,
+ one_in_scope || (i == ofs))
+ ) {
+ cnt++;
+ if (DEBUG_DISAMBIG)
+ rprintf("+%d ", list[i].obj);
+ } else {
+ if (DEBUG_DISAMBIG)
+ rprintf("-%d ", list[i].obj);
+ list[i].info |= D_MARK; /* Mark it for deletion */
+ }
+ if (cnt != 0) {
+ list = purge_list(list); /* Delete marked items */
+ if (cnt == 1) return list;
+ if (dsch == 1 && dlev == MAX_DLEV)
+ one_in_scope = 1;
+ } else {
+ clean_list(list); /* Remove marks; we're not purging */
+ break;
+ }
+ }
+ /* Check to make sure we don't have a list of multiple identical items */
+ for (i = ofs; list[i].info != D_END && list[i].info != D_AND; i++) {
+ if (!ident_objrec(&list[i], &list[ofs])) break;
+ list[i].info |= D_MARK;
+ }
+ if (list[i].info == D_END || list[i].info == D_AND) {
+ /* If all of the items are identical, just pick the first */
+ if (one_in_scope) writeln("(Picking one at random)");
+ list[0].info &= ~D_MARK;
+ list = purge_list(list);
+ } else clean_list(list);
+ if (DEBUG_DISAMBIG) rprintf("\n");
+ return list;
+}
+
+
+
+/* Note that this routine must be _restartable_, when new input comes in. */
+/* Truenoun is 0 or else the "correct" noun for where disambig first
+ got stuck. */
+/* Returns the offset at which it got stuck, or -1 if everything
+ went ok. Return -2 if we eliminate everything */
+/* *tn_ofs* contains the offset where truenoun is supposed to be used */
+
+#define list (*ilist) /* in disambig_phrase only */
+
+
+static int disambig_phrase(parse_rec **ilist, parse_rec *truenoun, int tn_ofs,
+ int ambig_type)
+/* Note that ilist is double dereferenced: this is so we can realloc it */
+/* ambig_type=1 for actor, 2 for noun, 3 for object */
+/* We can assume that the earlier bits have already been disambiguated */
+{
+ int ofs, i;
+ char *s;
+
+ ofs = 0;
+ if (list[0].info == D_END) return -1; /* No nouns, so no ambiguity */
+ if (list[0].info == D_ALL) ofs = 1; /* might have ALL EXCEPT construction */
+#ifdef OMEGA
+ return -1; /* No ambiguity over ALL, either */
+ /* (at least if it appears as the first element of the list) */
+#endif
+
+ while (list[ofs].info != D_END) { /* Go through each AND block */
+ if (ofs == tn_ofs) {
+ if (truenoun[0].info == D_ALL) /* Convert to ALL list */
+ list = convert_to_all(list, &ofs);
+ else {
+ list = add_disambig_info(list, ofs, truenoun);
+ if (list[ofs].info == D_END) { /* We have eliminated all matches */
+ gen_sysmsg(240, "In that case, I don't know what you mean.",
+ MSG_PARSE, "");
+ return -2;
+ }
+ }
+ }
+ list = disambig_a_noun(list, ofs, ambig_type);
+ assert(list[ofs].info != D_END && list[ofs].info != D_AND);
+ if (list[ofs + 1].info != D_END && list[ofs + 1].info != D_AND)
+ /* Disambiguation failed */
+ {
+ writestr("Do you mean");
+ for (i = ofs; list[i].info != D_END && list[i].info != D_AND; i++) {
+ if (list[i + 1].info == D_END || list[i + 1].info == D_AND)
+ if (i > ofs + 1) writestr(", or");
+ else writestr(" or");
+ else if (i > ofs) writestr(",");
+ writestr(" the ");
+ if (list[i].info != D_NUM || list[i].obj != 0)
+ s = (char *)objname(list[i].obj);
+ else {
+ s = (char *)rmalloc(30 * sizeof(char));
+ sprintf(s, "%ld", list[i].num);
+ }
+ writestr(s);
+ rfree(s);
+ }
+ writeln("?");
+ return ofs;
+ }
+ /* Skip forward to next AND */
+ while ((*ilist)[ofs].info != D_END && (*ilist)[ofs].info != D_AND)
+ ofs++;
+ if ((*ilist)[ofs].info == D_AND) ofs++;
+ }
+ return -1;
+}
+
+#undef list
+
+static int disambig_ofs; /* Offset where disambig failed */
+
+/* ambig_flag stores what we were disambiguating the last time this
+ routine was called: it tells us where we failed so that if the
+ player enters new disambiguation information, we can figure out where
+ it should go */
+
+static parse_rec *disambig(int ambig_set, parse_rec *list, parse_rec *truenoun)
+/* ambig_set = 1 for actor, 2 for noun, 3 for object */
+{
+ if (ambig_flag == ambig_set || ambig_flag == 0) { /* restart where we left off...*/
+ if (truenoun == NULL || truenoun[0].info == D_END) disambig_ofs = -1;
+ disambig_ofs = disambig_phrase(&list, truenoun, disambig_ofs, ambig_set);
+ if (disambig_ofs == -1) ambig_flag = 0; /* Success */
+ else if (disambig_ofs == -2) ambig_flag = -1; /* Error: elim all choices */
+ else ambig_flag = ambig_set;
+ }
+ return list;
+}
+
+
+
+
+/*-------------------------------------------------------------------*/
+/* Noun parsing routines */
+/*-------------------------------------------------------------------*/
+
+
+/* PARSE_A_NOUN(), parses a single noun, leaves ip pointing after it. */
+/* Just be greedy: grab as many of the input words as possible */
+/* Leave ip pointing _after_ last word we get. */
+/* Return list of all possible objects */
+/* Go to some difficullty to make sure "all the kings men" will
+ not be accidentally parsed as "all" + "the kings men" */
+/* (Yes, yes, I know -- you can't have an AGT object with a name as
+ complex as 'all the king's men'-- but you could try to simulate it using
+ synonyms) */
+/* May also want to use more intellegence along the adj--noun distinction */
+/* all_ok indicates whether ALL is acceptable */
+
+static parse_rec *parse_a_noun(void)
+/* Returns a list of parse_rec's containing the various possible
+ nouns. */
+{
+ parse_rec *nlist;
+ char *errptr;
+ int i, tmp, numval, num, oip;
+
+ nlist = new_list();
+ oip = ip; /* Save starting input pointer value */
+
+ if (input[ip] == -1) /* End of input */
+ return nlist;
+ if (input[ip] == 0) { /* i.e. tokeniser threw up its hands */
+ numval = strtol(in_text[ip], &errptr, 10); /* Is it a number? */
+ if (errptr == in_text[ip]) /* Nope. */
+ return nlist;
+ if (*errptr != 0) return nlist; /* May want to be less picky */
+ nlist = add_rec(nlist, 0, numval, D_NUM);
+ ip++;
+ return nlist;
+ }
+
+ /* Basic strategy: try to match nouns. If all matches are of length<=1,
+ then go looking for flag nouns, global nouns, ALL, DOOR, etc. */
+ num = 0;
+ objloop(i)
+ if ((tmp = noun_syn(input[ip], i)) != 0) {
+ numval = strtol(in_text[ip], &errptr, 10); /* Is it a number, too? */
+ if (*errptr != 0) numval = 0; /* Only accept perfectly formed numbers */
+ nlist = add_w_rec(nlist, i, numval,
+ (tmp == 1) ? D_ADJ : (tmp == 2 ? D_SYN : D_NOUN),
+ (tmp == 1) ? input[ip] : 0, /* Adjective */
+ (tmp == 1) ? 0 : input[ip]); /* Noun */
+ num++;
+ }
+
+ /* Now we need to try to match more words and reduce our list. */
+ /* Basically, we keep advancing ip until we get no matches at all. */
+ /* Note that nouns may only come at the end, so if we find one we know */
+ /* we are done. Synonyms and adjectives can come anywhere */
+ /* (If PURE_SYN is set, then we won't see any synonyms-- they */
+ /* get converted into nouns by noun_syn() */
+ /* *num* is used to keep track of how many matches we have so we know */
+ /* when to stop. */
+
+ /* compare against the next word in the queue */
+ while (num > 0 && input[ip] != -1 && ip < MAXINPUT) {
+ ip++;
+ if (input[ip] == -1) break;
+ num = 0;
+ for (i = 0; nlist[i].info != D_END; i++) /* Which nouns match the new word? */
+ if (nlist[i].info == D_NOUN) /* Nothing can come after a noun */
+ nlist[i].info |= D_MARK;
+ else if ((tmp = noun_syn(input[ip], nlist[i].obj)) == 0)
+ nlist[i].info |= D_MARK; /* Mark this noun to be eliminated */
+ else { /* Noun _does_ match */
+ num++; /* Count them up */
+ add_words_to_rec(&nlist[i], input[ip], tmp);
+ }
+ /* If we had any matches, kill the nouns that didn't match */
+ if (num != 0) {
+ nlist = purge_list(nlist);
+ for (i = 0; nlist[i].info != D_END; i++)
+ nlist[i].num = 0; /* Multi-word nouns can't be numbers */
+ } else /* num==0; we need to clear all of the marks */
+ clean_list(nlist);
+ }
+
+ /* So at this point num is zero-- that is we have reached the limit
+ of our matches. */
+ /* If ip==oip is 0 (meaning no matches so far) or ip==oip+1
+ (meaning we have a one-word match) then we need to check for
+ flag nouns, global nouns, ALL, DOOR, pronouns, etc.
+ and add them to the list if neccessary */
+
+ if (ip == oip || ip == oip + 1) {
+
+ /* First match the built in things... */
+ if ((input[oip] == ext_code[wdoor] && aver <= AGX00)
+ || input[oip] == ext_code[wscene])
+ nlist = add_rec(nlist, -input[oip], 0, D_INTERN);
+ else if (input[oip] == ext_code[wall] ||
+ input[oip] == ext_code[weverything])
+ nlist = add_rec(nlist, ALL_MARK, 0, D_ALL);
+ else if (input[oip] == ext_code[whe] || input[oip] == ext_code[whim])
+ nlist = add_rec(nlist, last_he, 0, D_PRO);
+ else if (input[oip] == ext_code[wshe] || input[oip] == ext_code[wher])
+ nlist = add_rec(nlist, last_she, 0, D_PRO);
+ else if (input[oip] == ext_code[wit])
+ nlist = add_rec(nlist, last_it, 0, D_PRO);
+ else if (input[oip] == ext_code[wthey] || input[oip] == ext_code[wthem])
+ nlist = add_rec(nlist, last_they, 0, D_PRO);
+ else for (i = 0; i < 10; i++) /* Match direction names */
+ if (input[oip] == syntbl[auxsyn[i]])
+ nlist = add_rec(nlist, -syntbl[auxsyn[i]], 0, D_INTERN);
+ else if (input[oip] == ext_code[weveryone] ||
+ input[oip] == ext_code[weverybody])
+ nlist = add_rec(nlist, -ext_code[weverybody], 0, D_INTERN);
+
+ /* Next look for number word matches */
+ numval = strtol(in_text[oip], &errptr, 10); /* Is it a number? */
+ if (*errptr == 0) /* Yes */
+ nlist = add_rec(nlist, -input[oip], numval, D_NUM);
+
+ /* Next handle the flag nouns and global nouns */
+ if (globalnoun != NULL)
+ for (i = 0; i < numglobal; i++)
+ if (input[oip] == globalnoun[i])
+ nlist = add_rec(nlist, -input[oip], 0, D_GLOBAL);
+ for (i = 0; i < MAX_FLAG_NOUN; i++)
+ if (flag_noun[i] != 0 && input[oip] == flag_noun[i]
+#if 0
+ && (room[loc].flag_noun_bits & (1L << i)) != 0
+#endif
+ )
+ nlist = add_rec(nlist, -input[oip], 0, D_FLAG);
+
+ /* Finally the PIX names */
+ for (i = 0; i < MAX_PIX; i++)
+ if (pix_name[i] != 0 && input[oip] == pix_name[i] &&
+ (room[loc].PIX_bits & (1L << i)) != 0)
+ nlist = add_rec(nlist, -input[oip], 0, D_PIX);
+
+ if (nlist[0].info != D_END) ip = oip + 1;
+ }
+ return nlist;
+}
+
+static parse_rec *parse_noun(int and_ok, int is_actor)
+/* handles multiple nouns */
+/* and_ok indicates wheter multiple objects are acceptable */
+/* Either_ok indicates whether EITHER is okay (only true when
+resolving disambiguation) */
+{
+ parse_rec *next, *lnoun_;
+ int saveinfo;
+ rbool all_except;
+
+ all_except = 0;
+ next = lnoun_ = parse_a_noun();
+ saveinfo = next[0].info;
+
+#if 0 /* Let the main parser sort this out */
+ if (!and_ok) return lnoun_; /* If no ANDs allowed, stop here. */
+#endif
+ /* We need to explicitly handle the actor case here because
+ the comma after an actor can be confused with the comma
+ used to separate multiple objects */
+ if (is_actor) return lnoun_; /* If no ANDs allowed, stop here. */
+
+ if (lnoun_[0].info == D_ALL && w_but(input[ip])) /* ALL EXCEPT ... */
+ all_except = 1; /* This will cause us to skip over EXCEPT and
+ start creating an AND list */
+
+ /* Now, itereate over <noun> AND <noun> AND ... */
+ while ((all_except || w_and(input[ip])) &&
+ saveinfo != D_END) { /* && saveinfo!=D_ALL */
+ ip++; /* Skip over AND or EXCEPT */
+ next = parse_a_noun();
+ saveinfo = next[0].info;
+ if (next[0].info != D_END) { /* We found a word */
+ if (!all_except) lnoun_ = add_rec(lnoun_, AND_MARK, 0, D_AND);
+ lnoun_ = concat_list(lnoun_, next);
+ } else ip--; /* We hit trouble: back up to the AND */
+ all_except = 0; /* Only skip EXCEPT once */
+ rfree(next);
+ }
+ return lnoun_;
+}
+
+
+parse_rec *parse_disambig_answer(void) {
+ parse_rec *temp;
+
+ if (input[ip + 1] == -1) {
+ if (input[ip] == ext_code[wall] || input[ip] == ext_code[weverything]
+ || input[ip] == ext_code[wboth]) {
+ temp = new_list();
+ ip++;
+ return add_rec(temp, ALL_MARK, 0, D_ALL);
+ }
+ if (input[ip] == ext_code[weither] || input[ip] == ext_code[w_any]) {
+ temp = new_list();
+ ip++;
+ return add_rec(temp, 0, 0, D_EITHER);
+ }
+ }
+ return parse_noun(0, 0);
+}
+
+
+
+/*-------------------------------------------------------------------*/
+/* Main parsing routines */
+/*-------------------------------------------------------------------*/
+
+
+static int parse_cmd(void)
+/* Parse entered text and execute it, one statement at a time */
+/* Needs to leave ip pointing at beginning of next statement */
+{
+ rbool new_actor; /* This is used for some error checking; it is set
+ if we just found an actor on this command
+ (as opposed to inheriting one from a previous
+ command in a multiple statement) */
+ parse_rec *tmp;
+ int tp;
+
+ /* First go looking for an actor. */
+ ap = ip;
+ new_actor = 0;
+ if (lactor == NULL) {
+ new_actor = 1;
+ lactor = parse_noun(0, 1);
+ /* Check that actor is a creature. */
+ if (lactor[0].info != D_END) {
+ lactor = fix_actor(lactor); /* eliminate non-creatures */
+ if (lactor[0].info == D_END) { /* Not a creature. */
+ /* print intelligent error message */
+ if (input[ip] == ext_code[wc]) /* ie there is a comma */
+ return parseerr(229, "Who is this '$word$' you are addressing?", ap);
+ else ip = ap; /* Otherwise, assume we shouldn't have parsed it as
+ an actor-- it may be a verb. */
+ }
+ }
+ if (lactor[0].info != D_END && input[ip] == ext_code[wc])
+ ip++; /* this skips over a comma after an actor. */
+ }
+ /* Now onwards... */
+ vp = ip;
+ vnum = id_verb(); /* May increment ip (ip will point at last word in verb) */
+ if (vnum == 0 && new_actor && lactor[0].info != D_END) {
+ /* maybe actor is messing us up. */
+ ip = ap; /* restart from beginning */
+ vnum = id_verb();
+ if (vnum == 0) /* if it's still bad, probably we really have an actor */
+ ip = vp;
+ else { /* no actor; really a verb */
+ lactor[0].obj = 0;
+ lactor[0].info = D_END;
+ vp = ap;
+ }
+ }
+
+TELLHack: /* This is used to restart the noun/prep/object scan
+ if we find a TELL <actor> TO <verb> ... command */
+
+ if (vnum == 0)
+ return parseerr(230, "I don't understand '$word$' as a verb.", ip);
+
+ /* Now we need to find noun, obj, and prep (if they all exist) */
+ /* standard grammer: verb noun prep obj */
+ prep = 0;
+ np = ++ip; /* ip now points just _after_ verb */
+ lnoun = parse_noun((verbflag[vnum] & VERB_MULTI) != 0, 0);
+ /* leaves ip pointing just after it.;
+ lnoun[0].info==D_END means no noun. */
+ if (prep == 0) { /* prep==0 unless we've met the special TURN ON|OFF case */
+ pp = ip;
+ prep = parse_prep(); /* Should be trivial */
+ op = ip;
+ lobj = parse_noun(prep == 0, 0); /* and_ok if no prep */
+ }
+
+ /* Check for TELL <actor> TO <verb> ... construction */
+ /* If found, convert to <actor>, <verb> ... construction */
+ if (lactor[0].info == D_END && lnoun[0].info != D_END &&
+ vnum == 31 && prep == ext_code[wto] && !multinoun(lnoun)) {
+ ip = op; /* Back up */
+ rfree(lactor);
+ rfree(lobj);
+ lactor = lnoun;
+ lnoun = NULL;
+ vp = ip; /* Replace TELL with new verb */
+ vnum = id_verb(); /* May increment ip (ip points att last word in verb) */
+ goto TELLHack; /* Go back up and reparse the sentence from
+ the new start point. */
+ }
+
+ /* Convert TURN <noun> ON to TURN ON <noun> */
+ if (vnum == 35 && (prep == ext_code[won] || prep == ext_code[woff])
+ && lobj[0].info == D_END) {
+ tmp = lobj;
+ lobj = lnoun;
+ lnoun = tmp;
+ tp = op;
+ np = op;
+ op = tp;
+ }
+
+
+ /* For pre-Magx versions of AGT,
+ convert <verb> <prep> <noun> ==> <verb> <noun> <prep> <noun> */
+ if (aver < AGX00 && lnoun[0].info == D_END && lobj[0].info != D_END) {
+ rfree(lnoun);
+ lnoun = copy_list(lobj);
+ np = op;
+ }
+
+ /* Next we check to convert SHOOT <noun> AT <object> to
+ SHOOT <object> WITH <noun> */
+ if (vnum == 49 && prep == ext_code[wat] && !multinoun(lnoun)) {
+ tmp = lobj;
+ lobj = lnoun;
+ lnoun = tmp;
+ tp = np;
+ np = op;
+ op = tp;
+ prep = ext_code[wwith];
+ }
+
+ /* Now to convert SHOW <*nothing*> to SHOW EXITS */
+ if (vnum == 40 && prep == 0 && lnoun[0].info == D_END && lobj[0].info == D_END)
+ vnum = 42; /* LISTEXITS */
+
+ /* Convert LOOK <something> into EXAMINE <something> */
+ if (smart_look && vnum == 19 && lnoun[0].info != D_END) vnum = 20;
+
+ /* need better error msgs */
+ if ((verbflag[vnum]&VERB_MULTI) == 0 && multinoun(lnoun)) {
+ /* Multiple objects when they are not allowed */
+ int msgnum;
+ if (vnum == 31) msgnum = 155; /* TALK */
+ else if (vnum == 34) msgnum = 160; /* ASK */
+ else msgnum = 231;
+ return parseerr(msgnum,
+ "The verb '$word$' doesn't take multiple objects.", vp);
+ } else if (multinoun(lobj))
+ return parseerr(232, "You can't use multiple indirect objects.", op);
+ else if (lnoun[0].info == D_END && !w_isterm(input[np]) && np != pp)
+ /* i.e. invalid noun */
+ return parseerr(233, "I don't understand the word '$word$' as a noun.", np);
+ else if (lnoun[0].obj == 0 && lnoun[0].info == D_PRO)
+ return parseerr(234, "I don't know to what '$word$' refers.", np);
+ else if (lobj[0].info == D_END && !w_isterm(input[op]))
+ /* i.e. invalid object */
+ return parseerr(235, "I don't understand the word '$word$' as a noun.", op);
+ else if (lobj[0].obj == 0 && lobj[0].info == D_PRO)
+ return parseerr(236, "I don't know to what '$word$' refers.", op);
+ else if (!w_isterm(input[ip]))
+ return parseerr(238, "Extra input found: \"$word$...\"", ip);
+
+ return 0;
+}
+
+
+static void v_undo(void) {
+ if (undo_state == NULL) {
+ writeln("There is insufficiant memory to support UNDO");
+ ip = -1;
+ return;
+ }
+ if (can_undo == 0) {
+ if (newlife_flag)
+ writeln("You can't UNDO on the first turn.");
+ else writeln("You can only UNDO one turn.");
+ ip = -1;
+ return;
+ }
+ writeln("");
+ writeln("UNDOing a turn...");
+ can_undo = 0;
+ putstate(undo_state);
+ ip = 1;
+ set_statline();
+ return;
+}
+
+rbool parse(void)
+/* Wrapper around parse_cmd to handle disambiguation, etc */
+/* It returns 1 if everything is okay; 0 if there is ambiguity */
+{
+ parse_rec *currnoun;
+ int fixword;
+ int start_ip;
+
+ currnoun = NULL;
+ start_ip = ip;
+ /* First, we need to see if someone has issued an OOPS command.
+ OOPS commands are always assumed to stand alone. (i.e. no
+ THEN nonsense). OOPS commands are always of the form
+ OOPS <word> */
+ if (ip == 0 && input[0] == ext_code[woops] && input[1] > 0 &&
+ input[2] == -1 && ep > -1) {
+ fixword = input[ip + 1];
+ restore_input();
+ input[ep] = fixword;
+ ambig_flag = 0;
+ }
+ ep = -1;
+
+
+ /* Next, we need to determine if someone is trying to do
+ disambiguation. This is only the case if
+ i)ambig_flag is set
+ ii)ip=0 (no multiple command nonsense)
+ iii)there is only one noun on the line. */
+ if (ip != 0) ambig_flag = 0;
+ if (ambig_flag) {
+ currnoun = parse_disambig_answer();
+ if (input[ip] == -1 && currnoun[0].info != D_END) {
+ restore_input(); /* Yep, we're disambiguaing. */
+ ip = parse_ip;
+ } else { /* nope; it's a new command */
+ ip = 0;
+ ambig_flag = 0;
+ rfree(currnoun);
+ freeall();
+ currnoun = NULL;
+ }
+ }
+
+ /* Next we go looking for UNDO; again, this must be the first
+ thing on an empty line. */
+ if (ip == 0 && input[0] == ext_code[wundo] && input[1] == -1) {
+ v_undo();
+ return 1;
+ }
+
+ save_input();
+
+ /* If starting a new line, clear out old the old actor */
+ if (ip == 0) {
+ actor_in_scope = 0; /* Clear this */
+ rfree(lactor); /* This resets lactor to NULL */
+ }
+
+ if (!ambig_flag)
+ if (parse_cmd() == -1)
+ return 1; /* error condition */
+
+ parse_ip = ip;
+
+ if (debug_parse)
+ parse_out(lactor, vnum, lnoun, prep, lobj);
+
+ /*Disambiguation routines; do it here instead of earlier to get
+ error messages in the right place (it's silly and annoying to ask the
+ player for disambiguation and then die on a parse error after they've
+ provided it) */
+ compute_scope(); /* The disambig routines use this information */
+ lactor = disambig(1, lactor, currnoun);
+ lnoun = disambig(2, lnoun, currnoun);
+ lobj = disambig(3, lobj, currnoun);
+ if (ambig_flag > 0) return 0; /* We need to get disambig info */
+ if (ambig_flag == -1) {
+ ambig_flag = 0;
+ return 1;
+ }
+ /* We got rid of too much */
+ rfree(currnoun);
+
+ /* Next, expand ALL if neccessary */
+ if (!PURE_ALL && lnoun[0].info == D_ALL) {
+ lnoun = expand_all(lnoun);
+ if (lnoun[0].info == D_END) { /* ALL expands to nothing */
+ int msgnum;
+ if (vnum >= 15 && vnum <= 24)
+ msgnum = all_err_msg[vnum - 15];
+ else
+ msgnum = 239;
+ parseerr(msgnum, "I don't know what you are referring to.", np);
+ return 1;
+ }
+ }
+
+ /* Now that we know that we have an executable command,
+ we save the undo state before executing if this is the first command
+ in a sequence. (That is, UNDO undoes whole lines of commands,
+ not just individual commands) */
+ if (start_ip == 0 && undo_state != NULL) {
+ undo_state = getstate(undo_state);
+ can_undo = 1;
+ }
+
+ /* Now to actually execute the command that has been parsed. */
+ /* Remember: disambiguation has been done by this time. */
+
+ exec(&lactor[0], vnum, lnoun, prep, &lobj[0]);
+ rfree(lobj);
+
+ /* exec is responsible for freeing or whatever lnoun (this is for AGAIN) */
+
+ /* Now we clear lnoun and lobj; lactor is handled elsewhere since
+ we might have FRED, GET ROCK THEN GO NORTH */
+ lnoun = lobj = NULL;
+
+ /* Finally check for THENs */
+
+ if (ip != -1 && w_and(input[ip]) && input[ip + 1] == ext_code[wthen])
+ ip++; /* AND THEN construction */
+ if (ip != -1 && input[ip] != -1) ip++;
+ return 1;
+}
+
+
+
+
+void menu_cmd(void) {
+ int i, j;
+ int choice;
+ char *curr_cmd, *tmp1, *tmp2; /* String of current command */
+ int objcnt; /* Number of objects taken by the current verb */
+ int verbword; /* Verb word */
+ parse_rec actrec;
+
+ parse_rec mobj;
+ int vnum_; /* Verb number */
+ word prep_;
+
+ menuentry *nounmenu;
+ int *nounval; /* Object id's for the menu entries */
+ int nm_size, nm_width; /* Size and width of noun menu */
+
+
+ nounval = NULL;
+ nounmenu = NULL;
+ /* Get verb+prep */
+ choice = agt_menu("", vm_size, vm_width, verbmenu);
+ if (choice == -1 || doing_restore) return;
+
+ verbword = verbinfo[choice].verb;
+ prep_ = verbinfo[choice].prep;
+ objcnt = verbinfo[choice].objnum;
+
+ /* Now identify the verb */
+ ip = 0;
+ input[0] = verbword;
+ input[1] = input[2] = -1;
+ if (objcnt <= 1 && prep_ != 0) input[1] = prep_;
+ vnum_ = id_verb();
+
+ lnoun = (parse_rec *)rmalloc(sizeof(parse_rec) * 2);
+ lnoun[0].obj = 0;
+ lnoun[0].num = 0;
+ lnoun[0].info = D_END;
+
+ nm_size = nm_width = 0;
+
+ if (objcnt >= 1) {
+ /* Construct noun list */
+ nounval = get_nouns();
+ for (nm_size = 0; nounval[nm_size] != 0; nm_size++);
+ nounmenu = (menuentry *)rmalloc(nm_size * sizeof(menuentry));
+ nm_width = 0;
+ for (i = 0; i < nm_size; i++) {
+ tmp1 = objname(nounval[i]);
+ strncpy(nounmenu[i], tmp1, MENU_WIDTH);
+ j = strlen(tmp1);
+ if (j > nm_width) nm_width = j;
+ }
+ if (nm_width > MENU_WIDTH) nm_width = MENU_WIDTH;
+
+ if (objcnt >= 2 || prep_ == 0)
+ curr_cmd = rstrdup(dict[verbword]);
+ else
+ curr_cmd = concdup(dict[verbword], dict[prep_]);
+
+ choice = agt_menu(curr_cmd, nm_size, nm_width, nounmenu);
+ rfree(curr_cmd);
+ if (choice == -1 || doing_restore) {
+ rfree(nounmenu);
+ rfree(nounval);
+ rfree(lnoun);
+ return;
+ }
+
+ if (objcnt == 1 && prep_ != 0) { /* VERB PREP OBJ construction */
+ mobj.obj = nounval[choice];
+ mobj.num = 0;
+ mobj.info = D_NOUN;
+ } else { /* Normal VERB OBJ construction */
+ lnoun[0].obj = nounval[choice];
+ lnoun[0].num = 0;
+ lnoun[0].info = D_NOUN;
+ lnoun[1].obj = 0;
+ lnoun[1].num = 0;
+ lnoun[1].info = D_END;
+ }
+ }
+
+ if (objcnt >= 2) {
+ tmp1 = objname(lnoun[0].obj); /* Build up current command line */
+ tmp2 = concdup(dict[verbword], tmp1); /* VERB NOUN */
+ rfree(tmp1);
+ curr_cmd = concdup(tmp2, dict[prep_]); /* VERB NOUN PREP */
+ rfree(tmp2);
+
+ choice = agt_menu(curr_cmd, nm_size, nm_width, nounmenu);
+ rfree(curr_cmd);
+ if (choice == -1 || doing_restore) {
+ rfree(lnoun);
+ rfree(nounmenu);
+ rfree(nounval);
+ return;
+ }
+
+ mobj.obj = nounval[choice];
+ mobj.num = 0;
+ mobj.info = D_NOUN;
+ }
+
+ rfree(nounmenu);
+ rfree(nounval);
+
+ if (vnum_ == OLD_VERB + 3) { /* UNDO */
+ v_undo();
+ return;
+ }
+
+ if (undo_state != NULL) {
+ undo_state = getstate(undo_state);
+ can_undo = 1;
+ }
+
+ /* Now to actually execute the command that has been selected. */
+ tmpobj(&actrec);
+ actrec.obj = 0;
+ exec(&actrec, vnum_, lnoun, prep_, &mobj);
+ lnoun = NULL; /* exec() is responsible for freeing lnoun */
+}
+
+
+/* Grammer structures:
+ sverb, dverb (n,s,e,w,...,q,l,....)
+ overb noun (close,examine,read,eat,drink,pull,light,ext)
+ averb noun|ALL (drop,get,wear,remove)
+ TURN noun ON|OFF
+ TURN ON|OFF noun
+ a-verb noun ABOUT obj (tell, ask)
+ pverb noun [prep object] (put, throw)
+ w-verb noun [WITH object] (attack, open, lock, unlock, push, shoot)
+ SHOOT noun [AT object]
+ dummy noun [prep obj]
+( pverb obj noun ==> pverb noun TO obj e.g. give dog the bone)
+*/
+
+} // End of namespace AGT
+} // End of namespace Glk
diff --git a/engines/glk/agt/runverb.cpp b/engines/glk/agt/runverb.cpp
new file mode 100644
index 0000000000..9f2144deb4
--- /dev/null
+++ b/engines/glk/agt/runverb.cpp
@@ -0,0 +1,1687 @@
+/* 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/exec.h"
+
+namespace Glk {
+namespace AGT {
+
+/*
+
+ This file contains several things:
+ i) The code for each of the built-in verbs, all prefixed with 'v_'
+ (so, for example, the code for DROP is in v_drop()).
+ ii) The main routine for checking and running player commands.
+ iii) The main routine for doing intelligent disambiguation.
+
+ */
+
+
+/* ------------------------------------------------------------------- */
+/* VERBS: Functions that implement the predefined verbs. */
+/* ------------------------------------------------------------------- */
+
+void v_look() {
+ do_look = 1;
+}
+
+
+static void v_go(int dir)
+/* 1=N, etc. */
+{
+ int newloc, tmploc;
+ int i;
+ /* rbool has_seen;*/
+
+ dir--;
+
+ tmploc = loc;
+ newloc = room[loc].path[dir];
+ if (newloc > exitmsg_base) { /* Customized error messages */
+ msgout(newloc - exitmsg_base, 1);
+ return;
+ }
+ if (newloc < 0) { /* Run autoverb */
+ int v0;
+
+ v0 = verb_code(-newloc);
+ if (v0 == 0) {
+ if (!PURE_ERROR)
+ writeln("GAME ERROR: Invalid verb.");
+ return;
+ }
+ clear_stack();
+ (void)scan_metacommand(0, v0, 0, 0, 0, NULL);
+ return;
+ }
+ if (newloc < first_room) {
+ if (dir == 12) /* Special */
+ sysmsg(182, "Nothing happens.");
+ else if (dir == 10) /* ENTER */
+ sysmsg(197, "$You$ can't enter anything here.");
+ else if (dir == 11) /* EXIT */
+ sysmsg(198, "$You're$ not inside anything that $you$ can exit.");
+ else
+ sysmsg(13, "$You$ can't go that way.");
+ return;
+ }
+ if (newloc > maxroom) {
+ if (!PURE_ERROR)
+ writeln("GAME ERROR: Invalid room number.");
+ return;
+ }
+
+ /* Then need to check for hostile creatures */
+ /* (If we are going back to the room we came from and not PURE_HOSTILE
+ is set, then we don't need to check this) */
+
+ if (dir != 12 && (PURE_HOSTILE || newloc != oldloc + first_room))
+ creatloop(i)
+ if (creature[i].location == loc + first_room &&
+ creature[i].hostile) {
+ parse_rec tmpcreat;
+ curr_creat_rec = &tmpcreat;
+ make_parserec(i + first_creat, &tmpcreat);
+ sysmsg(14, "$The_c$$c_name$ blocks $your$ way.");
+ curr_creat_rec = NULL;
+ return;
+ }
+
+ /* has_seen=room[newloc-first_room].has_seen;*/
+ goto_room(newloc - first_room);
+ if (dir != 12 && newloc != tmploc + first_room) /* SPECIAL */
+ oldloc = tmploc; /* Can backtrack as long as not from special */
+ if (dir == 12 && special_ptr[loc].size > 0)
+ /* need to print special of NEW room */
+ runptr(loc, special_ptr, "INTERNAL ERROR: Invalid special ptr", 0, NULL, NULL);
+
+ if (tmploc == loc && dir == 12) /* SPECIAL that sends us nowhere */
+ do_look = 0;
+}
+
+
+
+
+static void v_noun(int vc, parse_rec *nounrec)
+/* PUSH, PULL, TURN, PLAY, CHANGE_LOCATIONS */
+{
+ int dobj_;
+
+ dobj_ = p_obj(nounrec);
+
+ if (vc == 0 && !it_pushable(dobj_)) {
+ int msgnum;
+ if (!tcreat(dobj_)) msgnum = 172;
+ else if (creature[dobj_ - first_creat].gender == 0)
+ if (creature[dobj_ - first_creat].hostile) msgnum = 167;
+ else msgnum = 168;
+ else if (creature[dobj_ - first_creat].hostile) msgnum = 169;
+ else msgnum = 170;
+ sysmsgd(msgnum, "$You$ can't $verb$ $the_n$$noun$.", nounrec); /* Push */
+ return;
+ }
+ if (vc == 1 && !it_pullable(dobj_)) { /* Pull */
+ sysmsgd(tcreat(dobj_) ? 173 : 175, "$You$ can't $verb$ $the_n$$noun$.",
+ nounrec);
+ return;
+ }
+ if (vc == 2 && !it_turnable(dobj_)) { /* Turn */
+ sysmsgd(tcreat(dobj_) ? 164 : 166, "$You$ can't $verb$ $the_n$$noun$.",
+ nounrec);
+ return;
+ }
+ if (vc == 3 && !it_playable(dobj_)) { /* Play */
+ sysmsgd(tcreat(dobj_) ? 176 : 178, "$You$ can't $verb$ $the_n$$noun$.",
+ nounrec);
+ return;
+ }
+ if (matchclass(dobj_, room[loc].key)) { /* SPECIAL triggered */
+ v_go(13);
+ return;
+ }
+ if (vc == 4) {
+ sysmsgd(tcreat(dobj_) ? 180 : 181, "Nothing happens.", nounrec);
+ return;
+ }
+ /* otherwise, print out relevent description. */
+ if (vc == 0) /* Push */
+ runptr(dobj_ - first_noun, push_ptr,
+ "$You$ $verb$ $the_n$$noun$ for a while, but nothing happens.",
+ 171, nounrec, NULL);
+ if (vc == 1) /* Pull */
+ runptr(dobj_ - first_noun, pull_ptr,
+ "$You$ $verb$ $the_n$$noun$ a bit, but nothing happens.", 174,
+ nounrec, NULL);
+ if (vc == 2) /* Turn */
+ runptr(dobj_ - first_noun, turn_ptr,
+ "$You$ $verb$ $the_n$$noun$, but nothing happens.", 165,
+ nounrec, NULL);
+ if (vc == 3) /* Play */
+ runptr(dobj_ - first_noun, play_ptr,
+ "$You$ $verb$ $the_n$$noun$ for a bit, but nothing happens.", 177,
+ nounrec, NULL);
+}
+
+static void v_talk(int vc, parse_rec *nounrec, parse_rec *objrec)
+/* vc==1 if ASK, 0 if TALK TO */
+{
+ int dobj_, iobj_;
+
+ dobj_ = p_obj(nounrec);
+ iobj_ = p_obj(objrec);
+
+ if (nounrec->info == D_END || nounrec->info == D_AND) {
+ alt_sysmsg(211, "Who $are$ $you$ addressing?", nounrec, objrec);
+ return;
+ }
+ if (!genvisible(nounrec)) {
+ alt_sysmsg(212, "Who $are$ $you$ addressing?", nounrec, objrec);
+ return;
+ }
+ if (!tcreat(dobj_)) {
+ alt_sysmsg(vc ? 161 : 156, "That isn't animate.", nounrec, objrec);
+ return;
+ }
+ if (vc == 0)
+ runptr(dobj_ - first_creat,
+ talk_ptr, "$Your$ conversational gambit is ignored.",
+ creature[dobj_ - first_creat].gender == 0 ? 157 : (iobj_ == 0 ? 159 : 158),
+ nounrec, objrec);
+ if (vc == 1)
+ runptr(dobj_ - first_creat, ask_ptr, "$You$ get no answer.",
+ iobj_ == 0 ? 162 : 163, nounrec, objrec);
+}
+
+static void v_examine(parse_rec *nounrec) {
+ if (!islit()) {
+ sysmsgd(room[loc].light == 1 ? 19 : 20, "It's too dark to see anything.",
+ nounrec);
+ }
+ it_describe(nounrec->obj);
+}
+
+static void v_view(parse_rec *nounrec) { /* VIEW a picture */
+ int i;
+ int dobj_;
+ dobj_ = p_obj(nounrec);
+
+ if (tnoun(dobj_) && noun[dobj_ - first_noun].pict != 0)
+ pictcmd(1, pictable[noun[dobj_ - first_noun].pict - 1]);
+ else if (tcreat(dobj_) && creature[dobj_ - first_creat].pict != 0)
+ pictcmd(1, pictable[creature[dobj_ - first_creat].pict - 1]);
+ else if (dobj_ == -ext_code[wscene] && room[loc].pict != 0)
+ /* View the room picture */
+ pictcmd(1, pictable[room[loc].pict - 1]);
+ else { /* room.PIX_bits */
+ if (dobj_ < 0)
+ for (i = 0; i < maxpix; i++) /* Check them all */
+ if (dobj_ == -pix_name[i] &&
+ (room[loc].PIX_bits & (1L << i))) {
+ pictcmd(2, i);
+ return;
+ }
+ sysmsgd(217, "That can't be VIEWed here.", nounrec);
+ }
+}
+
+
+static void v_read(parse_rec *nounrec) {
+ int dobj_;
+ dobj_ = p_obj(nounrec);
+
+ if (!tnoun(dobj_) || !noun[dobj_ - first_noun].readable) {
+ sysmsg(134,
+ "$You$ can't read $the_n$$noun$, "
+ "so instead $you$ just examine $n_indir$.");
+ it_describe(dobj_);
+ return;
+ }
+ if (text_ptr[dobj_ - first_noun].size > 0)
+ runptr(dobj_ - first_noun, text_ptr,
+ "INTERNAL ERROR: Invalid read pointer", 0, NULL, NULL);
+ else
+ runptr(dobj_ - first_noun, noun_ptr, "$You$ learn nothing new.",
+ 193, nounrec, NULL);
+}
+
+
+static void v_eat(int vc, parse_rec *nounrec) {
+ int dobj_;
+ dobj_ = p_obj(nounrec);
+
+ if (!tnoun(dobj_)) {
+ sysmsgd(124, "That can't be consumed.", nounrec);
+ return;
+ }
+ if (vc == 0 && !noun[dobj_ - first_noun].edible) {
+ sysmsgd(124, "$You$ can't eat that.", nounrec);
+ return;
+ }
+ if (vc == 1 && !noun[dobj_ - first_noun].drinkable) {
+ sysmsgd(127, "$You$ can't drink that.", nounrec);
+ return;
+ }
+
+ sysmsgd(128, "$You$ $verb$ $the_n$$adjective$ $noun$.", nounrec);
+
+ if (noun[dobj_ - first_noun].movable) it_destroy(dobj_);
+ if (noun[dobj_ - first_noun].poisonous) {
+ sysmsgd(129, "Unfortunatly, $n_pro$ $n_was$ poisonous.", nounrec);
+ deadflag = 1;
+ }
+}
+
+
+static int can_wear(parse_rec *objrec)
+/* assumes objrec is in the noun range */
+{
+ static const char *errs[] = {
+ "$The_n$$noun$ $n_is$ far too heavy to wear.",
+ "$You're$ already loaded down with too much weight as it is."
+ "$The_n$$noun$ $n_is$ too big and bulky to wear.",
+ "$You're$ wearing too much to also wear $the_n$$noun$."
+ };
+ int n;
+
+ if (!it_canmove(objrec->obj)) {
+ sysmsgd(202, "$You$ can't move $the_n$$noun$.", objrec);
+ }
+ n = check_fit(objrec->obj, 1000);
+ if (n == FIT_OK /* || n>=FIT_SIZE */) return 1;
+ sysmsgd(37 + n, errs[n - 1], objrec);
+ return 0;
+}
+
+
+static int can_carry(parse_rec *objrec)
+/* assumes objrec is in the noun range */
+{
+ static const char *errs[] = {
+ "$The_n$$noun$ $n_is$ far too heavy to carry.",
+ "$You're$ already carrying too much weight as it is.",
+ "$The_n$$noun$ $n_is$ too big and bulky to pick up.",
+ "$You're$ carrying too much to also carry $the_n$$noun$."
+ };
+ int n;
+
+ n = check_fit(objrec->obj, 1);
+ if (n == FIT_OK) return 1;
+ sysmsgd(30 + n - 1, errs[n - 1], objrec);
+ return 0;
+}
+
+static int v_get(parse_rec *objrec) {
+ int cnt, i;
+ int obj;
+
+ obj = objrec->obj;
+
+ /* If there is a hostile creature in the room and PURE_GETHOSTILE isn't
+ set, then don't let the player pick up anything */
+ if (!PURE_GETHOSTILE)
+ creatloop(i)
+ if (creature[i].location == loc + first_room &&
+ creature[i].hostile) {
+ parse_rec tmpcreat;
+ make_parserec(i + first_creat, &tmpcreat);
+ curr_creat_rec = &tmpcreat;
+ sysmsgd(14, "$The_c$$c_name$ blocks $your$ way.", objrec);
+ return 0;
+ }
+
+ if (objrec->info == D_ALL) {
+ cnt = 0;
+ nounloop(i)
+ if (noun[i].location == loc + first_room && noun[i].movable) {
+ /* Need to add weight/size check */
+ parse_rec tmpnoun;
+ make_parserec(i + first_noun, &tmpnoun);
+ if (can_carry(&tmpnoun)) {
+ get_obj(i + first_noun);
+ sysmsgd(8, "$You$ pick up $the_n$$adjective$ $noun$.", &tmpnoun);
+ }
+ cnt++;
+ }
+ if (cnt == 0) {
+ sysmsgd(24, "There doesn't seem to be anything here to take.", objrec);
+ return 0;
+ } else return 1;
+ }
+ if (it_door(obj, objrec->noun)) {
+ if (room[loc].locked_door)
+ sysmsgd(25, "You can't pick up the door.", objrec);
+ else
+ sysmsgd(26, "You can't pick up the doorway.", objrec);
+ return 0;
+ }
+ if (!tnoun(obj) || !noun[obj - first_noun].movable) {
+ sysmsgd(tcreat(obj) ? (creature[obj - first_creat].hostile ? 34 : 35) : 29,
+ "$You$ can't pick $the_n$$noun$ up.", objrec);
+ return 0;
+ }
+ if (it_loc(obj) == 1) {
+ sysmsgd(27, "$You$ already have $the_n$$noun$.", objrec);
+ return 1;
+ }
+ if (!can_carry(objrec)) return 0;
+ get_obj(obj);
+ sysmsgd(8, "$You$ pick up $the_n$$adjective$ $noun$.", objrec);
+ return 1;
+}
+
+static int v_remove(parse_rec *objrec) {
+ int i, j;
+ integer obj;
+
+ obj = objrec->obj;
+ if (objrec->info == D_ALL) {
+ if (player_worn == 0) {
+ sysmsgd(46, "$You're$ not wearing anything.", objrec);
+ return 0;
+ }
+ safecontloop(i, j, 1000)
+ if (it_canmove(i)) {
+ parse_rec tmp;
+ if (PURE_WEAR) drop_obj(i);
+ else it_move(i, 1); /* Really need to check to make sure
+ we haven't exceeded weight requirement
+ here */
+ make_parserec(i, &tmp);
+ sysmsgd(9, "$You$ take off $the_n$$noun$.", &tmp);
+ }
+ return 1;
+ }
+ if (it_loc(obj) != 1000) {
+ sysmsgd(213, "$You're$ not wearing that.", objrec);
+ return 0;
+ }
+ if (!it_canmove(obj)) {
+ sysmsgd(201, "$You're$ not able to remove $the_n$$noun$.", objrec);
+ return 0;
+ }
+ sysmsgd(9, "$You$ take off $the_n$$noun$.", objrec);
+ if (PURE_WEAR) drop_obj(obj); /* Required to be consistent w/ AGT */
+ else v_get(objrec); /* (trap can_carry problems) */
+ return 1;
+}
+
+static void v_drop(parse_rec *objrec) {
+ int i, j;
+ int obj;
+ obj = objrec->obj;
+
+ if (obj == ALL_MARK) {
+ if (player_contents == 0)
+ sysmsgd(45, "$You$ don't have anything to drop.", objrec);
+ else safecontloop(i, j, 1) {
+ parse_rec tmp;
+ make_parserec(i, &tmp);
+ drop_obj(i);
+ sysmsgd(9, "$You$ $verb$ $the_n$$noun$.", &tmp);
+ }
+ return;
+ }
+ if (!it_possess(obj)) {
+ sysmsgd(47, "$You$ don't have that.", objrec);
+ return;
+ }
+ if (!it_canmove(obj)) {
+ sysmsgd(200, "$You're$ not able to $verb$ $the_n$$noun$.", objrec);
+ return;
+ }
+ if (it_loc(obj) == 1000) {
+ sysmsgd(216, "(Taking it off first)", objrec);
+ }
+ sysmsgd(9, "$You$ $verb$ $the_n$$noun$.", objrec);
+ drop_obj(obj);
+}
+
+static void v_wear(parse_rec *objrec) {
+ int i, cnt;
+ int obj;
+
+ obj = objrec->obj;
+ if (objrec->info == D_ALL) {
+ cnt = 0;
+ nounloop(i)
+ if (noun[i].location != 1000 && visible(i + first_noun) &&
+ noun[i].wearable) {
+ parse_rec tmp;
+ make_parserec(i + first_noun, &tmp);
+ if (can_wear(&tmp)) {
+ it_move(i + first_noun, 1000);
+ sysmsgd(42, "$You$ put on $the_n$$adjective$ $noun$.", &tmp);
+ }
+ cnt++;
+ }
+ if (cnt == 0)
+ sysmsgd(36, "There doesn't seem to be anything $you$ can wear here.",
+ objrec);
+ return;
+ }
+ if (!tnoun(obj) || !noun[obj - first_noun].wearable) {
+ sysmsgd(tcreat(obj) ? (creature[obj - first_creat].hostile ? 43 : 44) : 203,
+ "$You$ can't wear that.", objrec);
+ return;
+ }
+ if (it_loc(obj) == 1000) {
+ sysmsgd(37, "$You$ $are$ already wearing $the_n$$noun$.", objrec);
+ return;
+ }
+ if (!can_wear(objrec)) return;
+ sysmsgd(42, "$You$ put on $the_n$$noun$.", objrec);
+ it_move(obj, 1000);
+}
+
+static int do_lock(uchar l_or_u, parse_rec *nounrec, parse_rec *objrec)
+/* l_or_u: 0=lock, 1=unlock */
+{
+ int dnoun;
+ int dobj_, iobj_;
+ word dobj_word;
+
+ dobj_ = p_obj(nounrec);
+ iobj_ = p_obj(objrec);
+ dobj_word = nounrec->noun;
+
+ if (it_door(dobj_, dobj_word) && l_or_u != room[loc].locked_door) {
+ /* That is, trying to unlock an unlocked door, or lock a locked one. */
+ if (l_or_u == 0)
+ alt_sysmsg(114, "The door is already locked.", nounrec, objrec);
+ else
+ alt_sysmsg(105,
+ "There doesn't seem to be any door here that need unlocking.",
+ nounrec, objrec);
+ return 0;
+ }
+ if (!it_lockable(dobj_, dobj_word)) {
+ alt_sysmsg((l_or_u ? 108 : 118), "$The_n$$noun$ can't be $verb$ed.",
+ nounrec, objrec);
+ return 0;
+ }
+ if (tnoun(dobj_) && noun[dobj_ - first_noun].closable && it_open(dobj_)) {
+ if (l_or_u == 0) {
+ alt_sysmsg(120, "$You$ will need to close $the_n$$noun$ first.",
+ nounrec, objrec);
+ return 0;
+ } else { /* l_or_u==1 */
+ alt_sysmsg(110, "$The_n$$noun$ $n_is$ already open!",
+ nounrec, objrec);
+ return 0;
+ }
+ }
+ if (it_locked(dobj_, dobj_word) != l_or_u) {
+ alt_sysmsg((l_or_u ? 109 : 119), "$The_n$$noun$ $n_is$ already $verb$ed",
+ nounrec, objrec);
+ return 0;
+ }
+ if (it_door(dobj_, dobj_word) || dobj_ < 0) { /* i.e. a door */
+ alt_sysmsg((l_or_u ? 104 : 115),
+ "$You$ try to $verb$ $the_n$$noun$, but fail.",
+ nounrec, objrec);
+ return 0;
+ }
+ dnoun = dobj_ - first_noun;
+ if (iobj_ == 0) {
+ alt_sysmsg((l_or_u ? 106 : 208),
+ "$You$ will need to use something to do that.",
+ nounrec, objrec);
+ return 0;
+ }
+ if (!player_has(iobj_)) {
+ alt_sysmsg((l_or_u ? 107 : 117), "$You$ don't have $the_o$$object$.",
+ nounrec, objrec);
+ return 0;
+ }
+ if (!matchclass(iobj_, noun[dnoun].key)) {
+ alt_sysmsg(l_or_u ? (vb == 15 ? 80 : 111) : 121, /* vb 15 is OPEN */
+ "$The_o$$object$ doesn't fit.", nounrec, objrec);
+ return 0;
+ }
+ noun[dnoun].locked = !l_or_u;
+ return 1;
+}
+
+static void v_lock(uchar l_or_u, parse_rec *nounrec, parse_rec *objrec)
+/* First argument indicates lock or unlock-- 0=lock, 1=unlock */
+{
+ if (!do_lock(l_or_u, nounrec, objrec)) return;
+ /* Need to fix these messages: */
+ alt_sysmsg((l_or_u ? 112 : 122),
+ "$You$ $verb$ $the_n$$noun$ with $the_o$$object$.",
+ nounrec, objrec);
+}
+
+static void v_open(parse_rec *nounrec, parse_rec *objrec)
+/* OPEN ... WITH ... */
+{
+ int dnoun;
+ int dobj_, iobj_;
+
+ dobj_ = p_obj(nounrec);
+ iobj_ = p_obj(objrec);
+
+ dnoun = dobj_ - first_noun;
+ if (it_door(dobj_, nounrec->noun)) {
+ if (room[loc].locked_door)
+ alt_sysmsg(71, "$The_n$$noun$ $n_is$ locked.",
+ nounrec, objrec);
+ else
+ alt_sysmsg(72, "$The_n$$noun$ $n_is$ already open.",
+ nounrec, objrec);
+ return;
+ }
+ if (it_open(dobj_)) {
+ alt_sysmsg(78, "$The_n$$noun$ $n_is$ already open.", nounrec, objrec);
+ return;
+ }
+ if (!tnoun(dobj_) || !noun[dnoun].closable) {
+ alt_sysmsg(77, "$You$ can't open $the_n$$noun$.", nounrec, objrec);
+ return;
+ }
+ if (iobj_ != 0) { /* Need to do unlock action */
+ if (!do_lock(1, nounrec, objrec)) return;
+ /* If something goes wrong, return */
+ }
+ if (noun[dnoun].lockable && noun[dnoun].locked) {
+ alt_sysmsg(79, "It is locked.", nounrec, objrec);
+ return;
+ }
+ noun[dnoun].open = 1;
+ if (iobj_ != 0) /* Obviously these messages need improvement */
+ alt_sysmsg(81, "$You$ have opened $the_n$$noun$ with $the_o$$object$.",
+ nounrec, objrec);
+ else alt_sysmsg(82, "$You$ have opened $the_n$$noun$.", nounrec, objrec);
+ if (noun[dnoun].contents != 0)
+ alt_sysmsg(187, "Inside, $you$ see the following:", nounrec, objrec);
+ print_contents(dobj_, 1);
+}
+
+static void v_close(parse_rec *nounrec) {
+ int dobj_;
+ dobj_ = nounrec->obj;
+
+ if (it_door(dobj_, nounrec->noun)) {
+ if (room[loc].locked_door)
+ sysmsgd(84, "The door is already closed.", nounrec);
+ else
+ sysmsgd(85, "That apparently can't be closed.", nounrec);
+ return;
+ }
+ if (!it_open(dobj_)) {
+ sysmsgd(88, "$The_n$$noun$ $n_is$ already closed.", nounrec);
+ return;
+ }
+ if (!tnoun(dobj_) || !noun[dobj_ - first_noun].closable) {
+ sysmsgd(87, "$You$ can't close $the_n$$noun$.", nounrec);
+ return;
+ }
+ noun[dobj_ - first_noun].open = 0;
+ sysmsgd(89, "$You$ have closed $the_n$$noun$.", nounrec);
+}
+
+
+static void v_light(int newstate, parse_rec *nounrec) {
+ int dobj_;
+ dobj_ = p_obj(nounrec);
+
+ if (!tnoun(dobj_) || !noun[dobj_ - first_noun].light) {
+ sysmsgd(newstate ? 135 : 140, "$You$ can't $verb$ $the_n$$noun$.", nounrec);
+ return;
+ }
+ dobj_ -= first_noun;
+ if (noun[dobj_].on == newstate) {
+ if (newstate)
+ sysmsgd(136, "$The_n$$noun$ $n_is$ already lit.", nounrec);
+ else sysmsgd(141,
+ "$The_n$$noun$ $n_is$n't lit, so $you$ can't extinguish $n_indir$",
+ nounrec);
+ return;
+ }
+ noun[dobj_].on = newstate;
+ if (newstate) sysmsgd(138, "$The_n$$noun$ $n_is$ now lit.", nounrec);
+ else sysmsgd(143, "$The_n$$noun$ $n_is$ no longer lit.", nounrec);
+}
+
+static void v_turn(word prep_, parse_rec *nounrec) {
+ int newstate; /* 1=on, 0=off */
+ int dobj_;
+ dobj_ = p_obj(nounrec);
+
+ newstate = (prep_ == ext_code[won]); /* ON or OFF ? */
+ if (!it_turnable(dobj_) && !nounattr(dobj_, light)) {
+ sysmsgd(newstate ? 209 : 210,
+ "$You$ can't turn $the_n$$noun$ $prep_$.", nounrec);
+ return;
+ }
+ if (matchclass(dobj_, room[loc].key)) { /* SPECIAL triggered */
+ v_go(13);
+ return;
+ }
+ if (!tnoun(dobj_)) { /* This should be redundant */
+ writeln("INTERNAL ERROR: Non-noun turn on/off not supported");
+ return;
+ }
+ dobj_ -= first_noun;
+ if (noun[dobj_].on == newstate) {
+ sysmsgd(newstate ? 137 : 142, "$The_n$$noun$ $n_is$ already $prep_$.",
+ nounrec);
+ return;
+ }
+ noun[dobj_].on = newstate;
+ sysmsgd(newstate ? 139 : 144, "$The_n$$noun$ $n_is$ now $prep_$.", nounrec);
+}
+
+
+
+static void v_attack(uchar missile, parse_rec *targrec, parse_rec *weprec)
+/* Missile=1 if actually firing a weapon. */
+{
+ int targ, wep;
+ targ = targrec->obj;
+ wep = weprec->obj;
+
+ /* The following fix really belongs in the parser, but it might
+ break some games to do this translation before running metacommands */
+ if (missile && targ == 0) /* SHOOT <target> */
+ if (!tnoun(wep) || !noun[wep - first_noun].shootable) {
+ targ = wep;
+ targrec = weprec;
+ wep = 0;
+ }
+
+ curr_creat_rec = targrec; /* So error messages will print properly */
+ if (wep > 0 && !player_has(wep)) {
+ alt_sysmsg(98, "(Getting $the_o$$object$ first)", targrec, weprec);
+ if (!v_get(weprec)) return;
+ }
+ if ((targ > 0 && !tcreat(targ)) || targ < 0) {
+ alt_sysmsg(missile ? 90 : 93,
+ "It only makes sense to attack living things.",
+ targrec, weprec);
+ return;
+ }
+ if (missile) {
+ if (wep == 0) {
+ sysmsgd(94, "It's not clear what $you$ want to $verb$ with.", targrec);
+ return;
+ } else if (!tnoun(wep) || !noun[wep - first_noun].shootable) {
+ alt_sysmsg(it_isweapon(wep) ? 96 : 95,
+ "$The_o$$object$ doesn't seem to be able to fire.",
+ targrec, weprec);
+ return;
+ } else if (noun[wep - first_noun].num_shots <= 0) {
+ alt_sysmsg(97, "$The_o$$object$ $o_is$ out of ammunition.",
+ targrec, weprec);
+ return;
+ } else noun[wep - first_noun].num_shots--;
+ }
+
+ if (targ == 0) {
+ if (!missile) {
+ alt_sysmsg(206, "Attack what???", NULL, weprec);
+ return;
+ } else {
+ alt_sysmsg(188, "$You$ fire a shot into the air.", NULL, weprec);
+ return;
+ }
+ }
+
+ if (wep == 0) { /* and !missile, but that's taken care of above */
+ sysmsgd(creature[targ - first_creat].hostile ? 91 : 92,
+ "$You$ attack $the_n$$noun$ with $your$ bare hands, but $n_pro$ "
+ "evades $your$ attack.", targrec);
+ return;
+ }
+
+ if (matchclass(wep, creature[targ - first_creat].weapon)) {
+ if (missile)
+ alt_sysmsg(creature[targ - first_creat].hostile ? 99 : 101,
+ "$You$ shoot $the_n$$noun$; "
+ "$n_pro$ vanishes in a cloud of red smoke."
+ , targrec, weprec);
+ else
+ alt_sysmsg(creature[targ - first_creat].hostile ? 49 : 53,
+ "$You$ kill $the_o$$object$; "
+ "$o_pro$ vanishes in a cloud of red smoke.",
+ weprec, targrec);
+ it_destroy(targ);
+ if (!missile) drop_obj(wep);
+ return;
+ } else {
+ if (!missile) {
+ int msgnum;
+ if (creature[targ - first_creat].hostile) {
+ alt_sysmsg(50, NULL, weprec, targrec); /* Preliminary message */
+ msgnum = 51;
+ } else msgnum = 54;
+ if (noun[wep - first_noun].drinkable) { /* i.e. a liquid */
+ alt_sysmsg(msgnum + 1, "$You$ splash $the_o$$object$ with "
+ "$the_n$$noun$, but the liquid quickly evaporates "
+ "without noticable effect.", weprec, targrec);
+ it_destroy(wep);
+ } else {
+ alt_sysmsg(msgnum,
+ "$You$ strike at $the_o$$object$ with $the_n$$noun$, "
+ "but $your$ weapon bounces off of $o_indir$ harmlessly",
+ weprec, targrec);
+ drop_obj(wep);
+ }
+ } else
+ alt_sysmsg(creature[targ - first_creat].hostile ? 100 : 102 ,
+ "$You$ fire at $the_n$$noun$ with $the_o$$object$, but $your$ "
+ "shots don't seem to have any effect.", targrec, weprec);
+
+ if (creature[targ - first_creat].hostile &&
+ ++creature[targ - first_creat].counter >=
+ creature[targ - first_creat].threshold) {
+ alt_sysmsg(204, "$The_n$$noun$ counterattacks! $N_pro$ fights "
+ "viciously and $you$ $are$ unable to defend $your$self "
+ "against $n_indir$.", targrec, weprec);
+ deadflag = 1;
+ }
+ }
+}
+
+/* child_proc is true if v_put is being called by v_put, and so
+ shouldn't print success messages */
+static rbool v_put(parse_rec *nounrec, word prep_,
+ parse_rec *objrec, rbool child_proc) {
+ rbool in_prep;
+ int dobj_, iobj_;
+
+ dobj_ = p_obj(nounrec);
+ iobj_ = p_obj(objrec);
+
+ in_prep = (prep_ == ext_code[win] || prep_ == ext_code[winto]
+ || prep_ == ext_code[winside]);
+
+ if (prep_ == 0 || iobj_ == 0) {
+ v_drop(nounrec);
+ return 1;
+ }
+ if (!tnoun(dobj_)) {
+ alt_sysmsg(tcreat(dobj_) ? 11 : 10,
+ "$You$ can't do that with $the_n$$noun$.",
+ nounrec, objrec);
+ return 0;
+ }
+ if (!noun[dobj_ - first_noun].movable) {
+ alt_sysmsg(61, "$You$ can't move $the_n$$adjective$ $noun$.",
+ nounrec, objrec);
+ return 0;
+ }
+ if (tcreat(iobj_)) {
+ alt_sysmsg(189, "$The_o$$object$ doesn't want $n_indir$.",
+ nounrec, objrec);
+ return 0;
+ }
+ if (!tnoun(iobj_)) {
+ alt_sysmsg(tcreat(iobj_) ? 12 : 64,
+ "$You$ can't put something $prep_$ $the_o$$object$.",
+ nounrec, objrec);
+ return 0;
+ }
+ if (dobj_ == iobj_) {
+ alt_sysmsg(62, "$You$ can't put something $prep_$ $n_indir$self.",
+ nounrec, objrec);
+ return 0;
+ }
+ if (!it_open(iobj_) && in_prep) {
+ alt_sysmsg(65, "$The_o$$object$ $o_is$n't open.", nounrec, objrec);
+ return 0;
+ }
+ if (player_has(iobj_) && !in_prep) {
+ alt_sysmsg(is_within(iobj_, 1, 0) ? 68 : 69,
+ "$You$ can't put $the_n$$noun$ $prep_$ something that $you$ "
+ "$are$ carrying.", nounrec, objrec);
+ return 0;
+ }
+
+ if (in_prep) { /* PUT IN */
+ if (check_fit(dobj_, iobj_) != FIT_OK) {
+ alt_sysmsg(66, "$You$ can't fit $the_n$$noun$ into $the_o$$object$.",
+ nounrec, objrec);
+ return 0;
+ }
+ if (it_loc(dobj_) == 1000)
+ alt_sysmsg(216, "(Taking $n_indir$ off first)", nounrec, objrec);
+ it_move(dobj_, iobj_);
+ } else { /* PUT <prep_> with a preposition other than IN */
+ int parent;
+
+ parent = it_loc(iobj_);
+ if (!troom(parent)) {
+ parse_rec parent_rec;
+ make_parserec(parent, &parent_rec);
+ if (!v_put(nounrec, ext_code[win], &parent_rec, 1)) return 0;
+ } else {
+ if (it_loc(dobj_) == 1000)
+ alt_sysmsg(216, "(Taking $n_indir$ off first)", nounrec, objrec);
+ drop_obj(dobj_);
+ }
+ dobj_ -= first_noun;
+ assert(noun[dobj_].pos_prep == 0); /* v_put should have ensured this */
+ noun[dobj_].pos_prep = prep_;
+ noun[dobj_].pos_name = it_name(iobj_);
+ if (iobj_ > 0) noun[dobj_].nearby_noun = iobj_;
+ }
+ if (!child_proc)
+ alt_sysmsg(67, "$You$ place $the_n$$noun$ $prep_$ $the_o$$object$.",
+ nounrec, objrec);
+ return 1;
+}
+
+
+static void v_throw(parse_rec *nounrec, word prep_, parse_rec *objrec)
+/* at, to, in, into, across, inside */
+{
+ int dobj_, iobj_;
+ dobj_ = p_obj(nounrec);
+ iobj_ = p_obj(objrec);
+
+ /* Need to check to see what the preposition is-- if it is AT
+ then we should send it to attack routine. */
+ if (!player_has(nounrec->obj)) {
+ alt_sysmsg(47, "$You$ don't have $the_n$$noun$.", nounrec, objrec);
+ return;
+ }
+ if (prep_ == 0) {
+ v_drop(nounrec);
+ return;
+ }
+ if (prep_ != ext_code[wat])
+ v_put(nounrec, prep_, objrec, 0);
+ else /* prep_ is AT */
+ if (!noun[dobj_ - first_noun].movable) {
+ alt_sysmsg(215, "$You$ can't move $the_n$$adjective$ $noun$.",
+ nounrec, objrec);
+ return;
+ }
+ if (tcreat(iobj_)) /* If a creature, treat as an attack */
+ v_attack(0, objrec, nounrec);
+ else { /* THROW AT somethin inanimate */
+ if (dobj_ == iobj_) {
+ alt_sysmsg(56, "$You$ can't $verb$ $the_n$$noun$ $prep_$ $n_indir$self.",
+ nounrec, objrec);
+ return;
+ }
+ if (it_loc(dobj_) == 1000)
+ alt_sysmsg(216, "(Taking it off first)", nounrec, objrec);
+
+ if (tnoun(dobj_) && noun[dobj_ - first_noun].drinkable) {
+ /* A liquid */
+ if (tnoun(iobj_) && noun[iobj_ - first_noun].open)
+ alt_sysmsg(58, "$You$ throw $the_n$$noun$ into $the_o$$object$, "
+ "but $n_pro$ quickly evaporates.",
+ nounrec, objrec);
+ else
+ alt_sysmsg(57, "$The_n$$noun$ splashes on $the_o$$object$ but "
+ "quickly evaporates.", nounrec, objrec);
+ it_destroy(dobj_);
+ } else { /* _Not_ a liquid: */
+ if (tnoun(iobj_) && noun[iobj_ - first_noun].open)
+ if (check_fit(dobj_, iobj_)) {
+ alt_sysmsg(60, "$The_n$$noun$ lands inside $the_o$$object$.",
+ nounrec, objrec);
+ it_move(dobj_, iobj_);
+ return;
+ } else {
+ alt_sysmsg(205, "You $verb$ $the_n$$noun$ into $the_o$$object$, "
+ "but there isn't enough room and $n_pro$ falls out.",
+ nounrec, objrec);
+ }
+ else
+ alt_sysmsg(59, "$The_n$$noun$ bounces off $the_o$$object$.",
+ nounrec, objrec);
+ /* At this point, either the object is closed or doesn't have enough
+ room */
+ it_move(dobj_, first_room + loc);
+ }
+ }
+}
+
+
+
+void v_inventory(void) {
+ if (player_contents != 0) {
+ sysmsg(130, "$You're$ carrying:");
+ print_contents(1, 1); /* obj=1=self, ind_lev=1 */
+ } else sysmsg(131, "$You$ $are$ empty-handed.");
+ if (player_worn != 0) {
+ sysmsg(132, "$You're$ wearing:");
+ print_contents(1000, 1);
+ }
+}
+
+
+
+
+
+static void v_quit(void) {
+ sysmsg(145, "Are you sure you want to quit?");
+ if (yesno("")) {
+ sysmsg(146, NULL);
+ quitflag = 1;
+ }
+}
+
+const char dirname[12][10] = {"north", "south", "east", "west",
+ "northeast", "northwest", "southeast", "southwest",
+ "up", "down", "in", "out"
+ };
+
+void v_listexit(void) {
+ int i, j, k;
+
+ if (!islit()) {
+ sysmsg(23, "It is too dark to see anything.");
+ return;
+ }
+ j = k = 0;
+ for (i = 0; i < 12; i++)
+ if (room[loc].path[i] != 0) k++;
+ if (k == 0)
+ sysmsg(224, "There are no immediately visible exits.");
+ else {
+ sysmsg(225, "There are exits to");
+ for (i = 0; i < 12; i++)
+ if (room[loc].path[i] != 0) {
+ j++;
+ if (j > 1) writestr(", ");
+ if (j > 1 && j == k) writestr("or ");
+ if (i < 8) writestr("the ");
+ writestr(dirname[i]);
+ }
+ writeln(".");
+ }
+}
+
+
+static void v_yell(void) {
+ sysmsg(150, "YAAAAEEEEEEEEOOOOOOUUUUUAAAAHHHHHH!!!!!");
+}
+
+
+
+/* ------------------------------------------------------------------- */
+/* VERB EXECUTION AND GRAMMER CHECKING */
+
+
+static int checkgram(int vb_, int dobj_, word prep_, int iobj_, rbool redir_flag) {
+ int i;
+ int msgnum;
+
+ /* We turn off certain sorts of grammar checking if either PURE_GRAMMAR
+ is set or there has been signicant redirection. */
+ if (redir_flag < 2) redir_flag = 0;
+ if (PURE_GRAMMAR) redir_flag = 1;
+
+ /* First of all, no constraints on dummy_verb grammer */
+ if (vb_ >= BASE_VERB && vb_ < TOTAL_VERB) return 0;
+
+ if (!(verbflag[vb_]&VERB_TAKEOBJ)
+ && (dobj_ != 0 || iobj_ != 0 || prep_ > 0)
+ && vb_ != OLD_VERB + 11) {
+ if (redir_flag) return 0; /* Original AGT doesn't check this. */
+ sysmsg(190, "$Verb$ doesn't take an object.");
+ return -1;
+ }
+
+ /* Now verify prepositons. If PURE_GRAMMAR is set, we don't
+ check prepositions unless the verb actually accepts at least one.
+ (this reflects the behavior of the original AGT interpreters). */
+ if (prep_ > 0 && !(redir_flag && syntbl[preplist[vb_]] == 0)) {
+ for (i = preplist[vb_]; syntbl[i] != 0 && syntbl[i] != prep_; i++);
+ if (syntbl[i] != prep_) {
+ msgnum = 191;
+ if (vb_ == 15) msgnum = 74; /* Open */
+ if (vb_ == 17) msgnum = 116; /* Lock */
+ if (vb_ == 14) msgnum = 48; /* Throw */
+ sysmsg(msgnum, "$Verb$ doesn't take $prep_$ as a preposition.");
+ return -1;
+ }
+ }
+ if (iobj_ == ALL_MARK) {
+ sysmsg(199, "You can't use ALL as an indirect object");
+ return -1;
+ }
+ if (dobj_ == ALL_MARK && vb_ != 33 && vb_ != 41 && vb_ != 51 && vb_ != 52) {
+ /* i.e. verb is not GET,DROP,WEAR,REMOVE */
+ msgnum = 5;
+ if (vb_ == 31) msgnum = 155; /* Talk */
+ if (vb_ == 34) msgnum = 160; /* Ask */
+ sysmsg(5, "You can't use ALL with '$verb$'.");
+ return -1;
+ }
+ return 0;
+}
+
+
+static rbool verify_scope(int vb_, parse_rec *nounrec,
+ word prep_, parse_rec *objrec)
+/* This checks to make sure that all of the objects are present */
+{
+ int msgnum;
+ int dobj_, iobj_;
+ dobj_ = nounrec->obj;
+ iobj_ = objrec->obj;
+
+ if (!(verbflag[vb_]&VERB_TAKEOBJ)) return 1;
+ /* No objects (and we've already checked the grammar in
+ a previous routine) */
+
+ if (vb_ == 31 || vb_ == 34) /* TELL, ASK */
+ return 1; /* These verbs handle this themselves */
+
+ if (dobj_ == 0) {
+ sysmsg(184, "What do $you$ want to $verb$?");
+ return 0;
+ }
+ if (dobj_ != ALL_MARK && !genvisible(nounrec)
+ && !(it_door(dobj_, nounrec->noun) && /* DOOR object handling */
+ (vb_ == 33 || vb_ == 15 || vb_ == 16 || vb_ == 17 || vb_ == 18
+ || vb_ == 29 || vb_ == 24 || vb_ == 22 || vb_ == 21))) {
+ msgnum = 3;
+ if (vb_ == 33) msgnum = 28; /* Get */
+ if (vb_ == 29) msgnum = 63; /* Put */
+ if (vb_ == 15) msgnum = 75; /* Open */
+ if (vb_ == 16) msgnum = 86; /* Close */
+ if (vb_ == 24) msgnum = 126; /* Drink */
+ if (vb_ == 22) msgnum = 133; /* Read */
+ if (vb_ == 21) msgnum = 179; /* Change_Locations */
+ sysmsg(msgnum, "$You$ don't see any $noun$ here.");
+ return 0;
+ }
+
+ if (prep_ != 0 && vb_ != 35) { /* verb 35 is TURN e.g. ON|OFF */
+ if (iobj_ == 0) {
+ msgnum = 214;
+ if (vb_ == 29) msgnum = 70; /* Put */
+ sysmsg(msgnum, "What do $you$ want to $verb$ $the_n$$noun$ $prep_$?");
+ return 0;
+ }
+ if (iobj_ == -ext_code[wdoor]) {
+ sysmsg(183, "You can't $verb$ $prep_$ $the_o$$object$.");
+ return 0;
+ }
+ if (iobj_ != ALL_MARK && !genvisible(objrec)) {
+ msgnum = 4;
+ if (vb_ == 15) msgnum = 76; /* Open */
+ if (vb_ == 18) msgnum = 207; /* Unlock */
+ sysmsg(msgnum, "$You$ don't see any $object$ here.");
+ return 0;
+ }
+ }
+ return 1;
+}
+
+
+static void exec_verb_info(void) {
+ char *a, *b, *c;
+ char buff[200];
+
+ a = objname(dobj);
+ b = objname(iobj);
+ c = objname(actor);
+ sprintf(buff, "\t\t]]%s, %s %s(%ld) %s %s(%ld)", c, dict[ syntbl[auxsyn[vb]] ],
+ a, dobj_rec->num, prep == 0 ? "->" : dict[prep], b, iobj_rec->num);
+ writeln(buff);
+ rfree(a);
+ rfree(b);
+ rfree(c);
+}
+
+
+/* Returns true if the turn is done. */
+rbool metacommand_cycle(int save_vb, int *p_redir_flag) {
+ if (!have_meta) return 0;
+
+
+ /* Now check metacommands */
+ if (DEBUG_AGT_CMD)
+ debugout("*** Scanning: ANY metacommands ****\n");
+ /* ANY metacommands: */
+ supress_debug = !debug_any;
+ clear_stack();
+ if ((PURE_METAVERB || !was_metaverb)
+ && 2 == scan_metacommand(0, 0, 0, 0, 0, NULL))
+ return 1;
+
+ supress_debug = 0;
+
+ vb = save_vb;
+ actor_in_scope |= visible(actor); /* Set up for ActorWasPresent */
+
+ clear_stack();
+ if (actor != 0 && aver < AGX00) {
+ if (DEBUG_AGT_CMD)
+ debugout("*** Scanning: ANYBODY metacommands ****\n");
+ if (2 == scan_metacommand(2, vb, dobj, prep, iobj, NULL))
+ return 1;
+ }
+
+ clear_stack();
+ if (DEBUG_AGT_CMD)
+ debugout("*** Scanning: VERB metacommands ****\n");
+ /* Normal treatment */
+ if (2 == scan_metacommand(actor, vb, dobj, prep, iobj, p_redir_flag))
+ return 1;
+ /* Note that scan_metacommand will change the -global- copy of vb if a
+ RedirectTo occurs. */
+
+ return 0;
+}
+
+
+
+void exec_verb(void)
+/* Execute both meta-commands and more normal commands */
+/* May need tweaking for AGAIN and UNDO */
+{
+ int objswap; /* 1=if iobj has been moved to dobj */
+ /* (Done for metacommands when there is an iobj but no dobj) */
+ rbool turndone;
+ int save_vb;
+ int redir_flag;
+
+ if (DEBUG_EXEC_VERB) exec_verb_info();
+
+ do_disambig = 0; /* We're doing this for real */
+
+ save_vb = vb;
+ cmd_saveable = 1;
+ redir_flag = 0;
+
+ was_metaverb = (verbflag[vb] & VERB_META)
+ && actor == 0 && dobj == 0 && prep == 0 && iobj == 0;
+
+ /* The following is purely for metacommands */
+ if (dobj == 0 && dobj_rec->info != D_NUM && iobj != 0) {
+ dobj = iobj;
+ rfree(dobj_rec);
+ dobj_rec = copy_parserec(iobj_rec);
+ objswap = 1;
+ } else objswap = 0;
+
+ beforecmd = 1; /* This is for 1.8x support */
+
+ turndone = metacommand_cycle(save_vb, &redir_flag) || deadflag;
+
+ if (!turndone && DEBUG_AGT_CMD)
+ debugout("*** Executing Built-in Verbs ****\n");
+
+ if (actor > 0 && !turndone) {
+ if (!actor_in_scope)
+ sysmsg(196, "I don't see whom $you$ $are$ trying to address here.");
+ else
+ sysmsg(192, "$The_name$$name$ doesn't want to.");
+ } else if (vb == 19 && dobj == 0 && prep == 0 && iobj == 0)
+ /* LOOK: Doesn't matter if turn is done. */
+ v_look();
+ else if (!turndone) {
+ /* Execute normal verbs: check grammer and then call */
+ if (!objswap) {
+ if (checkgram(vb, dobj, prep, iobj, redir_flag) == -1) return;
+ } else if (checkgram(vb, 0, prep, iobj, redir_flag) == -1) return;
+
+ if (!verify_scope(vb, dobj_rec, prep, iobj_rec)) return;
+
+ if (vb < 13 && vb > 0) v_go(vb);
+ else switch (vb) {
+
+ case 14:
+ v_throw(dobj_rec, prep, iobj_rec);
+ break;
+ case 29:
+ v_put(dobj_rec, prep, iobj_rec, 0);
+ break;
+
+ /* _with_ verbs */
+ case 15:
+ v_open(dobj_rec, iobj_rec);
+ break;
+ case 16:
+ v_close(dobj_rec);
+ break;
+ case 17:
+ v_lock(0, dobj_rec, iobj_rec);
+ break; /* LOCK */
+ case 18:
+ v_lock(1, dobj_rec, iobj_rec);
+ break; /* UNLOCK */
+ case 36:
+ v_noun(0, dobj_rec);
+ break; /* PUSH (WITH);Ignore indir object*/
+
+ case 26:
+ v_attack(0, dobj_rec, iobj_rec);
+ break;
+ case 49:
+ if (prep == ext_code[wwith])
+ v_attack(1, dobj_rec, iobj_rec); /* SHOOT WITH */
+ else
+ v_attack(1, iobj_rec, dobj_rec); /* SHOOT AT */
+ break;
+
+ /* _about_ verbs */
+ case 31:
+ v_talk(0, dobj_rec, iobj_rec);
+ break; /* TELL */
+ case 34:
+ v_talk(1, dobj_rec, iobj_rec);
+ break; /* ASK */
+
+ case 28:
+ v_yell();
+ break;
+ case 27:
+ sysmsg(149, "Time passes...");
+ break; /* wait */
+ case 55:
+ v_go(13);
+ break; /* magic_word */
+
+ /* case 19: v_look();break; -- this is moved up above */
+
+ case 50:
+ runptr(loc, help_ptr, "Sorry, you're on your own here.",
+ 2, NULL, NULL);
+ break; /* HELP */
+ case 32:
+ v_inventory();
+ break;
+ case 56:
+ v_view(dobj_rec);
+ break; /* VIEW */
+ case 35:
+ if (prep > 0)
+ v_turn(prep, dobj_rec); /* TURN ON|OFF */
+ else
+ v_noun(2, dobj_rec); /* TURN */
+ break;
+ case 20:
+ v_examine(dobj_rec);
+ break;
+ case 22:
+ v_read(dobj_rec);
+ break;
+ case 23:
+ v_eat(0, dobj_rec);
+ break; /* EAT */
+ case 24:
+ v_eat(1, dobj_rec);
+ break; /* DRINK */
+ case 37:
+ v_noun(1, dobj_rec);
+ break; /* PULL */
+ case 38:
+ v_noun(3, dobj_rec);
+ break; /* PLAY */
+ case 47:
+ v_light(1, dobj_rec);
+ break; /* LIGHT */
+ case 48:
+ v_light(0, dobj_rec);
+ break; /* EXTINGUISH */
+ case 21:
+ v_noun(4, dobj_rec);
+ break; /* Change Location */
+
+ case 51:
+ v_wear(dobj_rec);
+ break;
+ case 33:
+ v_get(dobj_rec);
+ break; /* ? */
+ case 52:
+ v_remove(dobj_rec);
+ break;
+ case 41:
+ v_drop(dobj_rec);
+ break;
+
+ case 19:
+ v_look();
+ break;
+ case 25:
+ print_score();
+ break;
+ case 30:
+ cmd_saveable = 0;
+ v_quit();
+ break;
+ /* case 40: SHOW --> default message */
+ case 39:
+ case 42:
+ v_listexit();
+ break;
+ case 43:
+ cmd_saveable = 0;
+ verboseflag = 0; /* BRIEF */
+ writeln(
+ "[Now in BRIEF mode (room descriptions will only be printed"
+ " when they are entered the first time)]");
+ break;
+ case 44:
+ cmd_saveable = 0;
+ verboseflag = 1;
+ v_look(); /* VERBOSE */
+ writeln("[Now in VERBOSE mode (room descriptions will be"
+ " printed every time you enter a room)]");
+ break;
+ case 45:
+ cmd_saveable = 0;
+ savegame();
+ break;
+ case 46:
+ cmd_saveable = 0;
+ doing_restore = 1;
+ return;
+ break;
+ case 53:
+ cmd_saveable = 0;
+ script(1);
+ break;
+ case 54:
+ cmd_saveable = 0;
+ script(0);
+ break;
+ case 58: /* INSTRUCTIONS */
+ agt_clrscr();
+ print_instructions(hold_fc);
+ close_ins_file();
+ break;
+ case (OLD_VERB+1):
+ cmd_saveable = 0; /* RESTART */
+ if (restart_state == NULL)
+ writeln("Sorry, too little memory to support RESTART.");
+ else {
+ doing_restore = 2;
+ return;
+ }
+ case (OLD_VERB+4):
+ cmd_saveable = 0; /* NOTIFY */
+ notify_flag = !notify_flag;
+ if (notify_flag) writeln("Score notification is now on.");
+ else writeln("Score notification is now off.");
+ break;
+ case (OLD_VERB+5):
+ listexit_flag = 1;
+ writeln("[LISTEXIT mode on: room exits will be listed.]");
+ break; /* LISTEXIT ON */
+ case (OLD_VERB+6):
+ listexit_flag = 0;
+ writeln("[LISTEXIT mode off: room exits will not be listed.]");
+ break;
+ case (OLD_VERB+7): /* AGILDEBUG */
+ if (debug_mode) get_debugcmd();
+ else writeln("Nice try.");
+ break;
+ case (OLD_VERB+8): /* LOG, LOG ON */
+ logon();
+ break;
+ case (OLD_VERB+9): /* LOG OFF */
+ if (logflag & 2) break; /* We're replaying; ignore. */
+ if (logflag & 1) close_pfile(log_out, 5);
+ logflag = 0;
+ break;
+ case (OLD_VERB+10): /* REPLAY n */
+ fast_replay = 0;
+ replay(dobj_rec->num);
+ break;
+ case (OLD_VERB+11): /* REPLAY STEP */
+ fast_replay = 0;
+ replay(-1);
+ break;
+ case (OLD_VERB+13): /* REPLAY FAST */
+ fast_replay = 1;
+ replay(0);
+ break;
+ case (OLD_VERB+12): /* MENU */
+ if (verbmenu == NULL) {
+ writeln("Sorry, but menus are not supported by this game.");
+ menu_mode = 0;
+ break;
+ }
+ if (freeze_mode) {
+ writeln("Sorry, but that is not allowed.");
+ break;
+ }
+ menu_mode = !menu_mode;
+ break;
+ case 57: /* AFTER ?!? */
+ writeln("INTERNAL ERROR: Invalid execution of AFTER");
+ break;
+ case (OLD_VERB+14): /* SOUND ON */
+ musiccmd(8, 0);
+ break;
+ case (OLD_VERB+15): /* SOUND OFF */
+ musiccmd(9, 0);
+ break;
+ case (OLD_VERB+16): /* INTRO */
+ agt_clrscr();
+ print_descr(intro_ptr, 1);
+ break;
+ default:
+ sysmsg(185, "Don't know how to $verb$ here...");
+ return;
+ }
+ }
+
+ compute_seen();
+
+ if (!PURE_AFTER && !doing_restore && end_of_turn)
+ increment_turn();
+
+ beforecmd = 0;
+
+ /* In AGT 1.8x, run aftercommand verb metacommands. */
+ /* (This is the most serious flaw in 1.82/1.83; it drastically changes the
+ semantics of metacommand execution from the earlier formats) */
+ if (TWO_CYCLE && !quitflag && !turndone && !deadflag) {
+ if (DEBUG_AGT_CMD)
+ debugout("*** Scanning (after) metacommands ****\n");
+ /* Normal treatment */
+ turndone = turndone || metacommand_cycle(save_vb, &redir_flag);
+ }
+
+ if (aver >= AGT15 && !quitflag && !endflag && !deadflag) {
+ if (DEBUG_AGT_CMD)
+ debugout("*** Scanning: AFTER metacommands ****\n");
+ /* AFTER metacommands: */
+ supress_debug = !debug_any;
+ clear_stack();
+ if ((PURE_METAVERB || !was_metaverb) &&
+ 2 == scan_metacommand(0, 57, 0, 0, 0, NULL))
+ turndone = 1;
+ supress_debug = 0;
+ }
+
+ /* If the player really typed 'q' and we generated an "EndGame"
+ metacommand, then really quit. (usually it just gives the
+ "restart, restore, undo, quit..." message */
+ if (save_vb == 30 && endflag) quitflag = 1;
+}
+
+
+
+
+
+
+
+/* We need to be able to handle both NOUN and OBJECT searches */
+/* If obj==0, then we are doing a noun search, otherwise we are doing
+ an object search */
+/* Return the disambiguation score;
+ 0 if the object doesn't trigger anything
+ 1000 if it runs an action token or built in verb.
+ Other values may be returned if an ErrMessage token is encountered.
+ 500 is the cutoff for ALL expansion.
+ */
+
+
+int objcheck_cycle(rbool *success, parse_rec *act, int verbid,
+ parse_rec *dorec, word prep_, parse_rec *iorec) {
+ int result;
+
+ actor = act->obj;
+ actor_rec = copy_parserec(act);
+ /* The xobj_rec don't really matter */
+ dobj = dorec->obj;
+ dobj_rec = copy_parserec(dorec);
+ if (iorec == NULL) {
+ iobj_rec = make_parserec(0, NULL);
+ iobj = 0;
+ } else {
+ iobj = iorec->obj;
+ iobj_rec = copy_parserec(iorec);
+ }
+
+ clear_stack();
+ *success = 1;
+ supress_debug = !debug_disambig;
+ if (actor != 0 && aver < AGX00) {
+ result = scan_metacommand(2, verbid, dobj, prep_, iobj, NULL);
+ if (result == 2) {
+ free_all_parserec();
+ return disambig_score;
+ }
+ if (result == -2) {
+ free_all_parserec();
+ return DISAMBIG_SUCC;
+ }
+ }
+ clear_stack();
+ result = scan_metacommand(actor, verbid, dobj, prep_, iobj, NULL);
+ supress_debug = 0;
+ switch (result) {
+ case -2:
+ free_all_parserec();
+ return DISAMBIG_SUCC; /* We matched with something */
+ case 0:
+ case 1:
+ break; /* Nothing matched, but we still need to check
+ built-in verbs */
+ case 2:
+ free_all_parserec();
+ return disambig_score; /* End of turn, no match */
+ default:
+ writeln("INTERNAL ERROR: Invalid scan_metacommand return value.");
+ }
+ *success = 0;
+ free_all_parserec();
+ return 0;
+}
+
+
+
+int check_obj(parse_rec *act, int verbid,
+ parse_rec *dorec, word prep_, parse_rec *iorec) {
+ int result;
+ rbool success;
+
+ if (iorec == NULL)
+ do_disambig = 1; /* Disambiguating dobj */
+ else
+ do_disambig = 2; /* Disambiguating iobj */
+
+ disambig_score = 0;
+ if (have_meta) {
+ beforecmd = 1;
+ result = objcheck_cycle(&success, act, verbid, dorec, prep_, iorec);
+ if (success) return result;
+ }
+
+ /* Check built-in verbs here */
+ if (verbid < BASE_VERB)
+ switch (verbid) {
+ case 14: /* THROW dobj prep_ iobj */
+ case 29: /* PUT dobj prep_ iobj */
+ if (do_disambig == 2 && genvisible(iorec)) return DISAMBIG_SUCC;
+ /* ... fall through to next case ... */
+ case 41: /* DROP */
+ if (do_disambig == 1 && it_possess(dobj)) return DISAMBIG_SUCC;
+ break;
+
+ case 49: /* SHOOT ... AT or WITH ... */
+ if (prep_ == ext_code[wwith]) {
+ if (do_disambig == 1 && tcreat(dobj)) return DISAMBIG_SUCC;
+ else if (do_disambig == 2 && it_possess(iobj) && tnoun(iobj)
+ && noun[iobj - first_noun].shootable)
+ return DISAMBIG_SUCC;
+ } else { /* prep_!=wwith */
+ if (do_disambig == 2 && tcreat(iobj)) return DISAMBIG_SUCC;
+ else if (do_disambig == 1 && it_possess(dobj) && tnoun(dobj)
+ && noun[dobj - first_noun].shootable)
+ return DISAMBIG_SUCC;
+ }
+ break;
+
+ case 26: /* ATTACK ... WITH ... */
+ if (do_disambig == 2 && it_possess(iobj)) return DISAMBIG_SUCC;
+ if (do_disambig == 1 && tcreat(dobj) && visible(dobj))
+ return DISAMBIG_SUCC;
+ break;
+
+ case 51: /* WEAR */
+ if (do_disambig == 1)
+ if (tnoun(dobj) && visible(dobj) && noun[dobj - first_noun].wearable
+ && it_loc(dobj) != 1000)
+ return DISAMBIG_SUCC;
+ break;
+ case 33: /* GET */
+ if (do_disambig == 1 && tnoun(dobj)
+ && visible(dobj)
+ && noun[dobj - first_noun].location != 1
+ && noun[dobj - first_noun].movable)
+ return (player_has(dobj)) ? 499 : DISAMBIG_SUCC;
+ break;
+ case 52: /* REMOVE */
+ if (do_disambig == 1 && it_loc(dobj) == 1000) return DISAMBIG_SUCC;
+ break;
+
+ /* The following could be better, but I don't want to give
+ away puzzles by accident */
+ case 15: /* OPEN */
+ case 17: /* LOCK */
+ case 18: /* UNLOCK */
+ if (do_disambig == 2 && it_possess(iobj)) return DISAMBIG_SUCC;
+ /* ... fall through ... */
+ default: /* All other verbs just use visibility check */
+ if (do_disambig == 1 && genvisible(dorec)) return DISAMBIG_SUCC;
+ if (do_disambig == 2 && genvisible(iorec)) return DISAMBIG_SUCC;
+ }
+
+ if (have_meta && TWO_CYCLE) {
+ beforecmd = 0;
+ result = objcheck_cycle(&success, act, verbid, dorec, prep_, iorec);
+ if (success) return result;
+ }
+
+ return disambig_score; /* Failed to find a match */
+}
+
+} // End of namespace AGT
+} // End of namespace Glk
diff --git a/engines/glk/agt/savegame.cpp b/engines/glk/agt/savegame.cpp
new file mode 100644
index 0000000000..68285d2575
--- /dev/null
+++ b/engines/glk/agt/savegame.cpp
@@ -0,0 +1,429 @@
+/* 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 "agility.h"
+#include "interp.h"
+#include "exec.h"
+
+namespace Glk {
+namespace AGT {
+
+#define SAVE_UNDO
+#define DEBUG_SAVE_SIZE 0
+
+long state_size;
+
+
+/*-------------------------------------------------------------------*/
+/* INITIALISATION ROUTINES */
+/* These initialize all of the values that can be derived from */
+/* other data in the game file or that are reset when a game */
+/* is restored */
+/* See parser.c for the interpreter's main initialisation routines */
+
+void init_vals(void)
+/* Compute quantities that can be deduced from existing data */
+{
+ int i;
+
+ quitflag = winflag = deadflag = endflag = 0;
+ cmd_saveable = 0;
+ last_he = last_she = last_it = 0;
+ totwt = totsize = 0;
+ for (i = 0; i <= maxroom - first_room; i++)
+ room[i].contents = 0;
+ player_contents = player_worn = 0;
+ for (i = 0; i <= maxnoun - first_noun; i++) {
+ if (player_has(i + first_noun)) totwt += noun[i].weight;
+ if (noun[i].location == 1) totsize += noun[i].size;
+ noun[i].something_pos_near_noun = 0;
+ noun[i].contents = noun[i].next = 0;
+ }
+ for (i = 0; i <= maxcreat - first_creat; i++)
+ creature[i].contents = creature[i].next = 0;
+ for (i = 0; i <= maxnoun - first_noun; i++) {
+ add_object(noun[i].location, i + first_noun);
+ if (noun[i].nearby_noun >= first_noun &&
+ noun[i].nearby_noun <= maxnoun)
+ noun[noun[i].nearby_noun - first_noun].something_pos_near_noun = 1;
+ }
+ for (i = 0; i <= maxcreat - first_creat; i++)
+ add_object(creature[i].location, i + first_creat);
+ objscore = 0; /* Will need to recompute this ... */
+}
+
+
+
+
+/*-------------------------------------------------------------------*/
+/* ROUTINES TO SAVE/RESTORE THE GAME STATE */
+/* These are used by RESTART and UNDO as well as SAVE and RESTORE */
+
+/* Game State format: */
+/* The first two bytes indicate the length of the block (unsigned).*/
+/* The next two bytes indicate the game file somehow (so we don't try to */
+/* restore to a different game). */
+/* After this comes the game information itself. */
+/* All values are still little-endian (that is, LSB first) */
+
+/* These are the macros for writing game information to the state block */
+/* There is no difference between signed and unsigned when storing them;
+ there will be problems when recovering them again. */
+
+#define g(ft,var) {ft,DT_DEFAULT,&var,0}
+#define r(ft,str,f) {ft,DT_DEFAULT,NULL,offsetof(str,f)}
+#define dptype {FT_DESCPTR,DT_DESCPTR,NULL,0}
+
+static file_info fi_savehead[] = {
+ g(FT_INT16, loc), g(FT_INT32, tscore), g(FT_INT16, turncnt),
+ g(FT_BYTE, statusmode),
+ g(FT_BOOL, first_visit_flag), g(FT_BOOL, newlife_flag),
+ g(FT_BOOL, room_firstdesc), g(FT_BOOL, verboseflag),
+ g(FT_BOOL, notify_flag), g(FT_BOOL, listexit_flag),
+ g(FT_BOOL, menu_mode), g(FT_BOOL, sound_on),
+ g(FT_BOOL, agt_answer), g(FT_INT32, agt_number),
+ g(FT_INT16, curr_time), g(FT_INT16, curr_lives),
+ g(FT_INT16, delta_time),
+ endrec
+};
+
+static file_info fi_saveroom[] = {
+ dptype,
+ r(FT_BOOL, room_rec, seen),
+ r(FT_BOOL, room_rec, locked_door),
+ r(FT_INT16, room_rec, oclass),
+ r(FT_INT16, room_rec, points),
+ r(FT_INT16, room_rec, light),
+ r(FT_PATHARRAY, room_rec, path),
+ r(FT_UINT32, room_rec, flag_noun_bits),
+ endrec
+};
+
+static file_info fi_savenoun[] = {
+ dptype,
+ r(FT_INT16, noun_rec, location),
+ r(FT_INT16, noun_rec, nearby_noun),
+ r(FT_INT16, noun_rec, num_shots),
+ r(FT_INT16, noun_rec, initdesc),
+ r(FT_INT16, noun_rec, oclass),
+ r(FT_INT16, noun_rec, points),
+ r(FT_INT16, noun_rec, weight),
+ r(FT_INT16, noun_rec, size),
+ r(FT_BOOL, noun_rec, on),
+ r(FT_BOOL, noun_rec, open),
+ r(FT_BOOL, noun_rec, locked),
+ r(FT_BOOL, noun_rec, movable),
+ r(FT_BOOL, noun_rec, seen),
+ r(FT_WORD, noun_rec, pos_prep),
+ r(FT_WORD, noun_rec, pos_name),
+ endrec
+};
+
+static file_info fi_savecreat[] = {
+ dptype,
+ r(FT_INT16, creat_rec, location),
+ r(FT_INT16, creat_rec, counter),
+ r(FT_INT16, creat_rec, timecounter),
+ r(FT_INT16, creat_rec, initdesc),
+ r(FT_INT16, creat_rec, oclass),
+ r(FT_INT16, creat_rec, points),
+ r(FT_BOOL, creat_rec, groupmemb),
+ r(FT_BOOL, creat_rec, hostile),
+ r(FT_BOOL, creat_rec, seen),
+ endrec
+};
+
+static file_info fi_saveustr[] = {
+ {FT_TLINE, DT_DEFAULT, NULL, 0},
+ endrec
+};
+
+
+
+uchar *getstate(uchar *gs)
+/* Returns block containing game state.
+ If gs!=NULL, uses that space as a buffer;
+ if gs==NULL, we malloc a new block and return it */
+{
+ rbool new_block; /* True if we allocate a new block */
+ long bp;
+
+ if (gs == NULL) {
+ rm_trap = 0; /* Don't exit on out-of-memory condition */
+ gs = (uchar *)rmalloc(state_size); /* This should be enough. */
+ rm_trap = 1;
+ if (gs == NULL) /* This is why we set rm_trap to 0 before calling rmalloc */
+ return NULL;
+ new_block = 1;
+ } else new_block = 0;
+
+ /* First two bytes reserved for block size, which we don't know yet.*/
+ gs[4] = game_sig & 0xFF;
+ gs[5] = (game_sig >> 8) & 0xFF;
+
+ tscore -= objscore; /* Only include "permanent" part of score;
+ objscore we can recompute on RESTORE */
+
+ /* Need to setup here */
+ set_internal_buffer(gs);
+ fi_saveroom[0].ptr = room_ptr;
+ fi_savenoun[0].ptr = noun_ptr;
+ fi_savecreat[0].ptr = creat_ptr;
+
+ bp = 6;
+ bp += write_globalrec(fi_savehead, bp);
+ bp += write_recblock(flag, FT_BYTE, FLAG_NUM + 1, bp);
+ bp += write_recblock(agt_counter, FT_INT16, CNT_NUM + 1, bp);
+ bp += write_recblock(agt_var, FT_INT32, VAR_NUM + 1, bp);
+ bp += write_recarray(room, sizeof(room_rec), rangefix(maxroom - first_room + 1),
+ fi_saveroom, bp);
+ bp += write_recarray(noun, sizeof(noun_rec), rangefix(maxnoun - first_noun + 1),
+ fi_savenoun, bp);
+ bp += write_recarray(creature, sizeof(creat_rec),
+ rangefix(maxcreat - first_creat + 1),
+ fi_savecreat, bp);
+ if (userstr != NULL)
+ bp += write_recarray(userstr, sizeof(tline), MAX_USTR, fi_saveustr, bp);
+ if (objflag != NULL)
+ bp += write_recblock(objflag, FT_BYTE, objextsize(0), bp);
+ if (objprop != NULL)
+ bp += write_recblock(objprop, FT_INT32, objextsize(1), bp);
+ set_internal_buffer(NULL);
+ gs[0] = bp & 0xFF;
+ gs[1] = (bp >> 8) & 0xFF;
+ gs[2] = (bp >> 16) & 0xFF;
+ gs[3] = (bp >> 24) & 0x7F; /* Don't trust top bit */
+ if (new_block)
+ gs = (uchar *)rrealloc(gs, bp);
+ tscore += objscore;
+ return gs;
+}
+
+
+
+void putstate(uchar *gs) { /* Restores games state. */
+ long size, bp, numrec, i;
+
+
+ size = gs[0] + (((long)gs[1]) << 8) + (((long)gs[2]) << 16) + (((long)gs[3]) << 24);
+ if (size != state_size) {
+ writeln("Size difference in save files!");
+ agt_delay(3);
+ return;
+ }
+ if (gs[4] + (((long)gs[5]) << 8) != game_sig) {
+ writestr("This appears to be a save file for a different game. Is this"
+ " from an earlier chapter in a multi-part game such as"
+ " Klaustrophobia");
+ if (yesno("?"))
+ skip_descr = 1; /* We don't want to overwrite the descriptions
+ with the pointers from the save file. */
+ else {
+ writestr("Do you want to try using it anyhow (WARNING: This could"
+ " crash the interpreter)");
+ if (!(yesno("?"))) {
+ writeln("Command cancelled!");
+ agt_delay(3);
+ return;
+ }
+ }
+ }
+
+
+ /* setup... */
+ set_internal_buffer(gs);
+ fi_saveroom[0].ptr = room_ptr;
+ fi_savenoun[0].ptr = noun_ptr;
+ fi_savecreat[0].ptr = creat_ptr;
+ bp = 6;
+
+ read_globalrec(fi_savehead, 0, bp, 0);
+ bp += compute_recsize(fi_savehead);
+ read_recblock(flag, FT_BYTE, FLAG_NUM + 1, bp, 0);
+ bp += ft_leng[FT_BYTE] * (FLAG_NUM + 1);
+ read_recblock(agt_counter, FT_INT16, CNT_NUM + 1, bp, 0);
+ bp += ft_leng[FT_INT16] * (CNT_NUM + 1);
+ read_recblock(agt_var, FT_INT32, VAR_NUM + 1, bp, 0);
+ bp += ft_leng[FT_INT32] * (VAR_NUM + 1);
+
+ numrec = rangefix(maxroom - first_room + 1);
+ read_recarray(room, sizeof(room_rec), numrec, fi_saveroom, 0, bp, 0);
+ bp += compute_recsize(fi_saveroom) * numrec;
+ numrec = rangefix(maxnoun - first_noun + 1);
+ read_recarray(noun, sizeof(noun_rec), numrec, fi_savenoun, 0, bp, 0);
+ bp += compute_recsize(fi_savenoun) * numrec;
+ numrec = rangefix(maxcreat - first_creat + 1);
+ read_recarray(creature, sizeof(creat_rec), numrec, fi_savecreat, 0, bp, 0);
+ bp += compute_recsize(fi_savecreat) * numrec;
+ if (userstr != NULL) {
+ read_recarray(userstr, sizeof(tline), MAX_USTR, fi_saveustr, 0, bp, 0);
+ bp += ft_leng[FT_TLINE] * MAX_USTR;
+ }
+ if (objflag != NULL) {
+ i = objextsize(0);
+ read_recblock(objflag, FT_BYTE, i, bp, 0);
+ bp += ft_leng[FT_BYTE] * i;
+ }
+ if (objprop != NULL) {
+ i = objextsize(1);
+ read_recblock(objprop, FT_INT32, i, bp, 0);
+ bp += ft_leng[FT_INT32] * i;
+ }
+ set_internal_buffer(NULL);
+
+ if (skip_descr) /* Need to "fix" position information. This is a hack. */
+ /* Basically, this sets the position of each object to its default */
+ /* The problem here is that the usual position info is invalid-- we've
+ changed games, and hence dictionaries */
+ for (i = 0; i < maxnoun - first_noun; i++) {
+ if (noun[i].position != NULL && noun[i].position[0] != 0)
+ noun[i].pos_prep = -1;
+ else noun[i].pos_prep = 0;
+ }
+ else /* Rebuild position information */
+ for (i = 0; i < maxnoun - first_noun; i++)
+ if (noun[i].pos_prep == -1)
+ noun[i].position = noun[i].initpos;
+ else
+ noun[i].position = NULL;
+
+ init_vals();
+ skip_descr = 0; /* If we set this to 1, restore it to its original state */
+ /* Now do some simple consistancy checking on major variables */
+ if (loc > maxroom || loc < 0 || turncnt < 0 ||
+ curr_lives < 0 || curr_lives > max_lives) {
+ error("Error: Save file inconsistent.");
+ }
+}
+
+void init_state_sys(void)
+/* Initializes the state saving mechanisms */
+/* Mainly it just computes the size of a state block */
+{
+ state_size = compute_recsize(fi_savehead)
+ + compute_recsize(fi_saveroom) * rangefix(maxroom - first_room + 1)
+ + compute_recsize(fi_savenoun) * rangefix(maxnoun - first_noun + 1)
+ + compute_recsize(fi_savecreat) * rangefix(maxcreat - first_creat + 1)
+ + ft_leng[FT_BYTE] * (FLAG_NUM + 1)
+ + ft_leng[FT_INT16] * (CNT_NUM + 1)
+ + ft_leng[FT_INT32] * (VAR_NUM + 1)
+ + ft_leng[FT_BYTE] * objextsize(0)
+ + ft_leng[FT_INT32] * objextsize(1)
+ + 6; /* Six bytes in header */
+ if (userstr != NULL) state_size += ft_leng[FT_TLINE] * MAX_USTR;
+}
+
+
+/*-------------------------------------------------------------------*/
+/* SAVE FILE ROUTINES */
+
+void savegame(void) {
+ genfile savefile;
+ uchar *gs;
+ long size;
+
+#ifndef UNDO_SAVE
+ gs = getstate(NULL);
+#else
+ gs = undo_state;
+#endif
+ if (gs == NULL) {
+ writeln("Insufficiant memory to support SAVE.");
+ return;
+ }
+ savefile = get_user_file(1);
+ if (!filevalid(savefile, fSAV)) {
+ writeln("That is not a valid save file.");
+ return;
+ }
+ size = gs[0] + (((long)gs[1]) << 8) + (((long)gs[2]) << 16) + (((long)gs[3]) << 24);
+ if (!binwrite(savefile, gs, size, 1, 0))
+ writeln("Error writing save file.");
+#ifndef UNDO_SAVE
+ rfree(gs);
+#endif
+ writeclose(savefile, NO_FILE_ID);
+}
+
+
+rbool loadgame(void)
+/* 1=success, 0=failure */
+{
+ genfile loadfile;
+ long size;
+ uchar *gs;
+ const char *errstr;
+
+ loadfile = get_user_file(2);
+ if (!filevalid(loadfile, fSAV)) {
+ writeln("Unable to open file.");
+ return 0;
+ }
+ size = binsize(loadfile);
+ if (size == -1) {
+ writeln("Could not access file.");
+ readclose(loadfile);
+ return 0;
+ }
+ gs = (uchar *)rmalloc(size);
+ if (!binread(loadfile, gs, size, 1, &errstr)) {
+ writeln("Error reading file.");
+ rfree(gs);
+ readclose(loadfile);
+ return 0;
+ }
+ readclose(loadfile);
+ if (size != gs[0] + (((long)gs[1]) << 8) + (((long)gs[2]) << 16) + (((long)gs[3]) << 24)) {
+ if (size == gs[0] + (((long)gs[1]) << 8)) {
+ /* Old save file format; patch to look like new format */
+ gs = (uchar *)rrealloc(gs, size + 2);
+ memmove(gs + 4, gs + 2, size - 2);
+ gs[2] = gs[3] = 0;
+ } else {
+ writeln("Save file corrupted or invalid.");
+ rfree(gs);
+ return 0;
+ }
+ }
+ putstate(gs);
+ rfree(gs);
+ set_statline();
+ look_room();
+ return 1;
+}
+
+void restart_game(void) {
+ putstate(restart_state);
+ agt_clrscr();
+ set_statline();
+ do_look = do_autoverb = 1;
+ if (intro_ptr.size > 0) {
+ print_descr(intro_ptr, 1);
+ wait_return();
+ agt_clrscr();
+ }
+ newroom();
+}
+
+} // End of namespace AGT
+} // End of namespace Glk
diff --git a/engines/glk/agt/token.cpp b/engines/glk/agt/token.cpp
new file mode 100644
index 0000000000..1305056c8e
--- /dev/null
+++ b/engines/glk/agt/token.cpp
@@ -0,0 +1,1158 @@
+/* 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/exec.h"
+
+namespace Glk {
+namespace AGT {
+
+/*
+
+ This contains the code for actually executing each of the
+ individual metacommand tokens as well as the routines for
+ doing range checking to prevent a bad metacommand from crashing
+ the interpreter.
+
+*/
+
+static void it_newdesc(integer item, descr_ptr *newdesc) {
+ descr_ptr *desc;
+
+ if (tnoun(item)) desc = noun_ptr + (item - first_noun);
+ else if (tcreat(item)) desc = creat_ptr + (item - first_creat);
+ else if (item >= first_room && item <= maxroom)
+ desc = room_ptr + (item - first_room);
+ else {
+ writeln("INTERNAL ERROR: it_newdesc called with invalid object");
+ return;
+ }
+ desc->start = newdesc->start;
+ desc->size = newdesc->size;
+}
+
+static void changepict(int old_pict, int new_pict)
+/* Replace old_pict by new_pict */
+{
+ pictable[old_pict - 1] = new_pict - 1;
+}
+
+
+static const int antidir[12] = {1, 0, 3, 2, 7, 6, 5, 4, 9, 8, 11, 10};
+
+static void change_passage(int start, int dir, int newend)
+/* dir is 1..12 */
+/* START is a room number: i.e. starting at 0. */
+/* NEWEND is an object number, so starts at first_room */
+{
+ int oldend, i;
+
+ dir--; /* Convert direction to 0..11 scale */
+ oldend = room[start].path[dir];
+ room[start].path[dir] = newend;
+ if (newend == 0) {
+ if (room[oldend - first_room].path[antidir[dir]] == start + first_room)
+ room[oldend - first_room].path[antidir[dir]] = 0;
+ else for (i = 0; i < 12; i++)
+ if (room[oldend - first_room].path[i] == start + first_room) {
+ room[oldend - first_room].path[i] = 0;
+ break;
+ }
+ } else
+ room[newend - first_room].path[antidir[dir]] = start + first_room;
+}
+
+
+static long ask_for_number(int n1, int n2) {
+ char s[50];
+ int n;
+
+ if (n1 != n2)
+ sprintf(s, "Enter a number from %d to %d: ", n1, n2);
+ else
+ sprintf(s, "Enter a number: ");
+ for (;;) {
+ writestr(s);
+ n = read_number();
+ if (n1 == n2 || (n >= n1 && n <= n2)) return n;
+ writeln("");
+ }
+}
+
+/* Check if n1*n2 will fit in 32 bits and print an error message if not */
+/* This errs on the side of caution */
+static rbool mult_rangecheck(long n1, long n2) {
+ int cnt;
+
+ if (n1 == 0 || n2 == 0) return 1;
+ if (n1 < 0) n1 = -n1;
+ if (n2 < 0) n2 = -n2;
+
+ for (cnt = 0; n1 != 0; n1 >>= 1, cnt++);
+ for (; n2 != 0; n2 >>= 1, cnt++);
+ cnt--;
+
+ if (cnt <= 31) return 1; /* We're okay */
+
+ if (!PURE_ERROR)
+ writeln("GAME ERROR: Multiplication out of range.");
+ return 0;
+}
+
+static rbool is_numeric(parse_rec *objrec) {
+ char *s;
+
+ if (objrec->num != 0 || objrec->info == D_NUM) return 1;
+ if (objrec->adj != 0) return 0;
+ if (objrec->noun <= 0) return 0;
+ (void)strtol(dict[objrec->noun], &s, 10);
+ return (*s == 0); /* *s==0 means no error-- it parsed as a number. */
+}
+
+static void setcase(char *s, rbool up) {
+ for (; *s != 0; s++)
+ if (up) *s = toupper(*s);
+ else *s = tolower(*s);
+}
+
+
+void move_in_dir(int obj, int dir) {
+ int r;
+
+ r = it_room(obj);
+ if (!troom(r)) {
+ writeln("GAME ERROR: Object not in a room.");
+ return;
+ }
+ r = room[r - first_room].path[dir - 1];
+ if (!troom(r)) return; /* Can't go that way; Fail silently */
+ if (obj == 1)
+ goto_room(r);
+ else
+ it_move(obj, r);
+}
+
+
+/* ------------------------------------------------------------------- */
+/* Stack routines: Manipulating the expression stack */
+/* ------------------------------------------------------------------- */
+
+static long *stack = NULL;
+static int sp = 0; /* Stack pointer */
+static int stacksize = 0; /* Actual space allocated to the stack */
+
+void init_stack(void) {
+ rfree(stack);
+ sp = 0;
+ stacksize = 0;
+}
+
+/* This resets the stack to an empty state. */
+void clear_stack(void) {
+ sp = 0;
+}
+
+static void push_stack(long val) {
+ sp++;
+ if (sp > stacksize) {
+ stacksize += 10;
+ stack = (long *)rrealloc(stack, stacksize * sizeof(long));
+ }
+ stack[sp - 1] = val;
+}
+
+static long pop_stack(void) {
+ long n;
+ if (sp == 0) {
+ writeln("GAME ERROR: Stack underflow.");
+ return 0;
+ }
+ n = stack[--sp];
+ if (sp + 100 < stacksize) {
+ stacksize -= 50;
+ stack = (long *)rrealloc(stack, stacksize * sizeof(long));
+ }
+ return n;
+}
+
+long pop_expr_stack(void) {
+ return pop_stack();
+}
+
+
+/* opnum: 0=+, 1=-, 2=*, 3=/ 4=% */
+static void op_stack(int opnum) {
+ long n1, n2;
+ n1 = pop_stack();
+ n2 = pop_stack();
+ switch (opnum) {
+ case 0:
+ n1 = n1 + n2;
+ break;
+ case 1:
+ n1 = n1 - n2;
+ break;
+ case 2:
+ if (mult_rangecheck(n1, n2)) n1 = n1 * n2;
+ break;
+ case 3:
+ if (n2 != 0) n1 = n1 / n2;
+ else writeln("GAME ERROR: Division by zero.");
+ break;
+ case 4:
+ if (n2 != 0) n1 = n1 % n2;
+ else writeln("GAME ERROR: Division by zero.");
+ break;
+ default:
+ writeln("INTERNAL ERROR: Invalid stack operation.");
+ }
+ push_stack(n1);
+}
+
+/* This is called from the disassembler */
+void print_tos(void) {
+ if (sp > 0)
+ dbgprintf("TOS(%d)", stack[sp - 1]);
+ else
+ debugout("TOS(xxx)");
+}
+
+
+
+/* ------------------------------------------------------------------- */
+/* METACOMMAND ROUTINES */
+/* Functions for scanning and decoding of metacommands */
+/* ------------------------------------------------------------------- */
+
+/* #define cret(b) return ((b) ? -1 : 0)*/
+#define cret(b) return (b)
+#define cretn(i,f) cret(tnoun(i) && noun[i-first_noun].f)
+#define cretc(i,f) cret(tcreat(i) && creature[i-first_creat].f)
+#define icretc(f) cret(do_disambig==1 || \
+ (tcreat(iobj) && creature[iobj-first_creat].f))
+
+static int obj_cond(int op_, int obj, int arg) {
+ switch (op_) {
+ case 0:
+ cret(in_scope(obj)); /* Present--
+ Do we want to use visible here?? */
+ case 1:
+ cret(is_within(obj, 1000, 1)); /* IsWearing */
+ case 2:
+ cret(is_within(obj, 1, 1));
+ /* if (PURE_WEAR) return (it_loc(obj)==1); else */
+ case 3:
+ cret(it_loc(obj) == 0); /* Nowhere */
+ case 4:
+ cret(it_loc(obj) != 0);
+ case 5:
+ cret(!player_has(obj) && in_scope(obj));
+ case 6:
+ cret(it_loc(obj) == arg);
+ case 7:
+ cret(it_on(obj));
+ case 8:
+ cret(!it_on(obj));
+ case 9:
+ cret(it_open(obj));
+ case 10:
+ cret(!it_open(obj));
+ case 11:
+ cretn(obj, locked);
+ case 12:
+ cret(!tnoun(obj) || !noun[obj - first_noun].locked);
+ case 13:
+ cretn(obj, edible);
+ case 14:
+ cretn(obj, drinkable);
+ case 15:
+ cretn(obj, poisonous);
+ case 16:
+ cretn(obj, movable);
+ default:
+ writeln("INTERNAL ERROR: Bad obj_cond value.");
+ return 2;
+ }
+}
+
+
+static int exec_cond(int op_, int arg1, int arg2) {
+ int i;
+
+ switch (op_) {
+ /* First the conditions */
+ case 0:
+ cret(loc + first_room == arg1); /* AtLoc(Room) */
+ case 1:
+ cret(loc + first_room > arg1);
+ case 2:
+ cret(loc + first_room < arg1);
+ case 3:
+ return musiccmd(-1, -1); /* SongPlaying */
+ case 4:
+ return musiccmd(-2, -1); /* SoundIsOn */
+ case 5:
+ cret(vb <= 13 && vb > 0 &&
+ room[loc].path[vb - 1] >= first_room); /*DirOK*/
+ case 6:
+ cret(vb == arg1); /* DirectionIs */
+ case 7:
+ cret(loc + first_room >= arg1 &&
+ loc + first_room <= arg2); /* BetweenRooms ?? */
+ case 8:
+ cret(room[arg1 - first_room].seen);
+ case 9: /* Entered Object? i.e. is iobj valid */
+ cret(do_disambig == 1 || iobj > 0);
+ case 10:
+ cret(curr_time > arg1); /* TimeGT */
+ case 11:
+ cret(curr_time < arg1); /* TimeLT */
+ case 12:
+ cret(first_visit_flag);
+ case 13:
+ cret(newlife_flag);
+ case 14:
+ cret(player_contents != 0); /* CarrySome */
+ case 15:
+ cret(player_contents == 0); /* CarryNo */
+ case 16:
+ cret(player_worn != 0); /* WearSome */
+ case 18:
+ cret(player_worn == 0); /* WearNo */
+ case 17: /* CarryTreas */
+ contloop(i, 1)
+ if (tnoun(i) && noun[i - first_noun].points >= arg1) return 1;
+ contloop(i, 1000)
+ if (tnoun(i) && noun[i - first_noun].points >= arg1) return 1;
+ return 0;
+ case 19:
+ cret(totwt == arg1);
+ case 20:
+ cret(totwt > arg1);
+ case 21:
+ cret(totwt < arg1);
+ case 22:
+ case 23:
+ case 24:
+ case 25:
+ case 26:
+ case 27:
+ case 28:
+ return obj_cond(op_ - 22, arg1, arg2);
+ case 29:
+ cret(it_loc(arg1) == it_loc(arg2));
+ case 30:
+ case 31:
+ return obj_cond(op_ - 23, arg1, arg2);
+ case 32:
+ cret(it_group(arg1));
+ case 33:
+ case 34:
+ case 35:
+ case 36:
+ case 37:
+ case 38:
+ case 39:
+ case 40:
+ return obj_cond(op_ - 24, arg1, arg2);
+ case 41:
+ case 42:
+ case 43:
+ case 44:
+ case 45:
+ case 46:
+ case 47:
+ case 48:
+ case 49:
+ case 50:
+ case 51:
+ case 52:
+ case 53:
+ case 54:
+ case 55:
+ case 56:
+ case 57:
+ return obj_cond(op_ - 41, dobj, arg1);
+ case 58:
+ cretn(dobj, points == arg1);
+ case 59:
+ cretn(dobj, points > arg1);
+ case 60:
+ cretn(dobj, points < arg1);
+ case 61:
+ cretn(dobj, weight == arg1);
+ case 62:
+ cretn(dobj, weight > arg1);
+ case 63:
+ cretn(dobj, weight < arg1);
+ case 64:
+ cret(islit());
+ case 65:
+ cret(room[loc].light != 0);
+ case 66:
+ cret(flag[arg1]);
+ case 67:
+ cret(!flag[arg1]);
+ case 68:
+ cret(room[loc].flag_noun_bits & (1 << (arg1 - 1)));
+ case 70:
+ cret(!(room[loc].flag_noun_bits & (1 << (arg1 - 1))));
+ case 69:
+ cret(room[loc].PIX_bits & (1 << (arg1 - 1))); /* Room Pix here? */
+ case 71:
+ cret(tscore == arg1);
+ case 72:
+ cret(tscore > arg1);
+ case 73:
+ cret(tscore < arg1);
+ case 74:
+ cret(agt_number == arg1);
+ case 75:
+ cret(agt_number > arg1);
+ case 76:
+ cret(agt_number < arg1);
+ case 77:
+ cret(agt_answer);
+ case 78:
+ cret(!agt_answer);
+ case 79:
+ cret(turncnt == arg1);
+ case 80:
+ cret(turncnt > arg1);
+ case 81:
+ cret(turncnt < arg1);
+ case 82:
+ cret(cnt_val(agt_counter[arg1]) == arg2);
+ case 83:
+ cret(cnt_val(agt_counter[arg1]) > arg2);
+ case 84:
+ cret(cnt_val(agt_counter[arg1]) < arg2);
+
+ case 85:
+ cret(agt_var[arg1] == arg2);
+ case 86:
+ cret(agt_var[arg1] > arg2);
+ case 87:
+ cret(agt_var[arg1] < arg2);
+ case 88:
+ cret(agt_var[arg1] < agt_var[arg2]);
+ case 89:
+ cret(agt_var[arg1] < agt_rand(1, arg2));
+ case 90:
+ cret((actor != 0) && (it_loc(actor) == loc + first_room));
+ case 91:
+ cret(actor == arg1);
+ case 92:
+ cret(dobj == arg1);
+ case 93:
+ cret(do_disambig == 1 || iobj == arg1);
+ case 94:
+ cret(it_contents(arg1) != 0);
+ case 95:
+ cret(agt_rand(1, 100) <= arg1);
+ case 96:
+ cret(yesno("Yes or no? "));
+ case 97:
+ cret(!yesno("Yes or no? "));
+ case 98:
+ cret(vb > 0 && vb <= 13);
+ case 99:
+ cret(tcreat(dobj));
+ case 100:
+ cretc(dobj, gender == 2); /* man */
+ case 101:
+ cretc(dobj, gender == 1); /* woman */
+ case 102:
+ cretc(dobj, gender == 0); /* thing */
+ case 103:
+ cretc(iobj, gender == 2);
+ case 104:
+ cretc(iobj, gender == 1); /* woman */
+ case 105:
+ cretc(iobj, gender == 0); /* thing */
+ case 106:
+ cret(do_disambig == 1 || tcreat(iobj));
+ case 107:
+ return (do_disambig == 1 || obj_cond(0, iobj, 0));
+ /* OR and NOT are handled higher up. */
+ /* The following are all v1.8x metacommands */
+ case 110:
+ cret(beforecmd);
+ case 111:
+ cret(!beforecmd);
+ case 112:
+ cret(curr_time / 100 == arg1); /* HoursEqual */
+ case 113:
+ cret(curr_time / 100 > arg1);
+ case 114:
+ cret(curr_time / 100 < arg1);
+ case 115:
+ cret(curr_time % 100 == arg1); /* MinutesEqual */
+ case 116:
+ cret(curr_time % 100 > arg1);
+ case 117:
+ cret(curr_time % 100 < arg1);
+ case 118:
+ cret(curr_time < 1200); /* IsAM */
+
+ case 119:
+ cret(do_disambig); /* OnDisambig */
+ case 120:
+ cretc(arg1, hostile); /* IsHostile */
+ case 121: /* HostilePresent */
+ creatloop(i)
+ if (creature[i].location == loc + first_room &&
+ creature[i].hostile) return 1;
+ return 0;
+ /* Otherwise, we're in trouble. */
+ case 122:
+ cret(actor_in_scope); /* NameWasPresent */
+ case 123: /* OncePerTurn */
+ if (beforecmd)
+ cret(start_of_turn);
+ else
+ cret(end_of_turn);
+ case 124: /* IsClass */
+ cret(arg2 == 0 || matchclass(arg1, arg2));
+ case 125:
+ cret(getattr(arg1, arg2)); /* IsSet */
+ case 126:
+ cret(is_numeric(dobj_rec));
+ case 127:
+ cret(is_numeric(iobj_rec));
+ case 128:
+ cret(arg1 == arg2);
+ case 129:
+ cret(arg1 > arg2);
+ case 130:
+ cret(arg1 < arg2);
+ case 131:
+ cret(arg1 >= arg2);
+ case 132:
+ cret(arg1 <= arg2);
+ case 133:
+ cret(strcmp(userstr[arg1 - 1], userstr[arg2 - 1]) == 0);
+ case 134:
+ cret(strcmp(userstr[arg1 - 1], userstr[arg2 - 1]) < 0);
+ case 135:
+ cret(strcmp(userstr[arg1 - 1], userstr[arg2 - 1]) > 0);
+ case 136:
+ cret(strcasecmp(userstr[arg1 - 1], userstr[arg2 - 1]) == 0);
+ case 137:
+ cret(strcasecmp(userstr[arg1 - 1], userstr[arg2 - 1]) < 0);
+ case 138:
+ cret(strcasecmp(userstr[arg1 - 1], userstr[arg2 - 1]) > 0);
+ case 139:
+ cret(match_answer(rstrdup(userstr[arg1 - 1]), arg2 - 1));
+ /* Note that match_answer rfrees it's first argument */
+ case 140:
+ cret(it_seen(arg1));
+ case 141:
+ cret(op_objflag(2, arg1, arg2));
+ case 142:
+ cret(!op_objflag(2, arg1, arg2));
+ case 143:
+ i = it_room(arg1);
+ cret(troom(i) && troom(room[i - first_room].path[arg2 - 1]));
+ default:
+ writeln("INTERNAL ERROR: Condition token not supported.");
+ rprintf("Condition #%d", op_);
+ writeln("");
+ return 0;
+ }
+}
+
+#undef cret
+#undef cretn
+#undef cretc
+
+
+static void obj_act(int op_, int obj) {
+ switch (op_) {
+ case 0:
+ case 1: /* open and close */
+ if (tnoun(obj))
+ noun[obj - first_noun].open = (op_ == 0);
+ break;
+ case 2:
+ case 3: /* lock and unlock */
+ if (tnoun(obj))
+ noun[obj - first_noun].locked = (op_ == 2);
+ }
+}
+
+
+static void exec_action(int op_, int arg1, int arg2) {
+ int i, j;
+ char *tmpstr;
+
+ switch (op_) {
+ case 1000:
+ goto_room(arg1 - first_room);
+ break;
+ case 1001:
+ goto_room(agt_rand(arg1, arg2) - first_room);
+ break;
+ case 1002:
+ agt_var[arg1] = loc + first_room;
+ break;
+ case 1003:
+ agt_var[arg1] = dobj;
+ break;
+ case 1004:
+ agt_var[arg1] = iobj;
+ break;
+ case 1005:
+ goto_room(agt_var[arg1] - first_room);
+ break;
+ case 1006:
+ it_move(arg1, agt_var[arg2]);
+ break;
+ case 1007:
+ get_obj(agt_var[arg1]);
+ break;
+ case 1008:
+ msgout(agt_var[arg1], 1);
+ break;
+ case 1009:
+ get_obj(arg1);
+ break;
+ case 1010:
+ get_obj(arg1);
+ it_move(arg1, 1000);
+ break;
+ case 1011:
+ drop_obj(arg1);
+ break;
+ case 1012:
+ if (it_loc(arg1) == 1000) {
+ if (PURE_WEAR) drop_obj(arg1);
+ else it_move(arg1, 1);
+ }
+ break;
+ case 1013:
+ fontcmd(0, arg1 - 1);
+ break; /* Load font */
+ case 1014:
+ pictcmd(1, pictable[arg1 - 1]);
+ break; /* Show picture */
+ case 1015:
+ changepict(arg1, arg2);
+ break; /* ChangePicture */
+ case 1016:
+ if (PICT_SUPPORT &&
+ yesno("Would you like to see the picture?"))
+ pictcmd(1, pictable[arg1 - 1]);
+ break;
+ case 1017:
+ pictcmd(2, arg1);
+ break; /* Show room pix */
+ case 1018:
+ if (PICT_SUPPORT &&
+ yesno("Would you like to see the picture?"))
+ pictcmd(2, arg1 - 1);
+ break;
+ case 1019:
+ musiccmd(1, arg1 - 1);
+ break;
+ case 1020:
+ musiccmd(1, agt_rand(arg1, arg2) - 1);
+ break;
+ case 1021:
+ musiccmd(2, arg1 - 1);
+ break;
+ case 1022:
+ musiccmd(3, -1);
+ break; /* Stop Repeat */
+ case 1023:
+ musiccmd(4, -1);
+ break; /* Stop song */
+ case 1024:
+ musiccmd(5, -1);
+ break; /* Suspend song */
+ case 1025:
+ musiccmd(6, -1);
+ break; /* Resume song */
+ case 1026:
+ if (tnoun(dobj))
+ noun[dobj - first_noun].movable = !noun[dobj - first_noun].movable;
+ break;
+ case 1027:
+ it_newdesc(arg1, &msg_ptr[arg2 - 1]);
+ break;
+ case 1028:
+ if (tnoun(arg1)) noun[arg1 - first_noun].points = arg2;
+ else if (tcreat(arg1)) creature[arg1 - first_creat].points = arg2;
+ else if (troom(arg1)) room[arg1 - first_room].points = arg2;
+ break;
+ case 1029:
+ it_destroy(iobj);
+ break;
+ case 1030:
+ tmpstr = agt_readline(3);
+ i = strlen(tmpstr) - 1;
+ if (i > 0 && tmpstr[i] == '\n') tmpstr[i] = 0;
+ strncpy(userstr[arg1 - 1], tmpstr, 80);
+ rfree(tmpstr);
+ break;
+ case 1031:
+ agt_var[arg1] = read_number();
+ break;
+ case 1032:
+ agt_var[arg1] = curr_time;
+ break;
+ case 1033:
+ curr_time = normalize_time(agt_var[arg1]);
+ break;
+ case 1034:
+ curr_time = normalize_time(arg1);
+ break;
+ case 1035:
+ add_time(arg1);
+ break;
+ case 1036:
+ delta_time = arg1;
+ break;
+ /* 1037 and 1038 are subroutine commands */
+ case 1039:
+ get_obj(dobj);
+ break;
+ case 1040:
+ it_move(dobj, 1000);
+ break;
+ case 1041:
+ drop_obj(dobj);
+ break;
+ case 1042:
+ if (it_loc(dobj) == 1000) {
+ if (PURE_WEAR) it_move(dobj, 1);
+ else drop_obj(dobj);
+ }
+ break;
+ case 1043: /* drop all */
+ safecontloop(i, j, 1) drop_obj(i);
+ break;
+ case 1044: /* remove all */
+ safecontloop(i, j, 1000) drop_obj(i);
+ break;
+ case 1045:
+ deadflag = 1;
+ break;
+ case 1046:
+ it_move(arg1, loc + first_room);
+ break;
+ case 1047:
+ it_move(arg1, arg2);
+ break;
+ case 1048:
+ it_reposition(arg1, arg2, 1);
+ break; /* RePosition */
+ case 1049:
+ it_move(dobj, loc + first_room);
+ break;
+ case 1050:
+ it_move(dobj, arg1);
+ break;
+ case 1051:
+ safecontloop(i, j, 1) it_move(i, arg1);
+ safecontloop(i, j, 1000) it_move(i, arg1);
+ break;
+ case 1052:
+ nounloop(i)
+ if (player_has(i + first_noun) && noun[i].points > arg2)
+ it_move(i + first_noun, arg1);
+ break;
+ case 1053:
+ safecontloop(i, j, arg1)
+ if (tnoun(i)) it_move(i, arg2);
+ break;
+ case 1054:
+ it_destroy(arg1);
+ break;
+ case 1055:
+ it_destroy(dobj);
+ break;
+ case 1056:
+ i = it_loc(arg1);
+ it_move(arg1, it_loc(arg2));
+ it_move(arg2, i);
+ break;
+ case 1057:
+ it_move(arg1, it_loc(arg2));
+ break;
+ case 1058:
+ it_move(dobj, it_loc(arg2));
+ break;
+ case 1059:
+ case 1060: /* Add to/remove from group */
+ if (tcreat(arg1))
+ creature[arg1 - first_creat].groupmemb = (op_ == 1059);
+ break;
+ case 1061: /* Move group */
+ safecontloop(i, j, loc + first_room)
+ if (it_group(i)) it_move(i, arg1);
+ break;
+ /* 1062 is RedirectTo */
+ case 1063:
+ msgout(agt_rand(arg1, arg2), 1);
+ break;
+ case 1064:
+ print_contents(arg1, 1);
+ break;
+ case 1065:
+ case 1066:
+ case 1067:
+ case 1068:
+ obj_act(op_ - 1065, arg1);
+ break;
+ case 1069:
+ case 1070:
+ case 1071:
+ case 1072:
+ obj_act(op_ - 1069, dobj);
+ break;
+ case 1073:
+ print_score();
+ break;
+ case 1074:
+ tscore += arg1;
+ break;
+ case 1075:
+ tscore -= arg1;
+ break;
+ case 1076:
+ v_inventory();
+ break;
+ case 1077:
+ wait_return();
+ break;
+ case 1078:
+ writeln("Time passes...");
+ break;
+ case 1079:
+ agt_delay(arg1);
+ break;
+ case 1080:
+ agt_clrscr();
+ break;
+ case 1081:
+ it_describe(arg1);
+ break;
+ case 1082:
+ look_room();
+ break; /* LOOK */
+ case 1083:
+ msgout(arg1, 1);
+ break;
+ case 1084:
+ writeln("");
+ break;
+ case 1085:
+ if (PURE_TONE && sound_on)
+ agt_tone(arg1, arg2);
+ break; /* Tone */
+ case 1086:
+ agt_number = ask_for_number(arg1, arg2);
+ break;
+ case 1087:
+ agt_answer = ask_question(arg1);
+ break;
+ case 1088:
+ change_passage(loc, arg1, arg2);
+ break;
+ case 1089:
+ flag[arg1] = 1;
+ break;
+ case 1090:
+ flag[arg1] = 0;
+ break;
+ case 1091:
+ flag[arg1] = !flag[arg1];
+ break;
+ case 1092:
+ room[loc].flag_noun_bits |= (1 << (arg1 - 1));
+ break; /* Roomflag on */
+ case 1093:
+ room[loc].flag_noun_bits &= ~(1 << (arg1 - 1));
+ break; /* Off */
+ case 1094:
+ room[loc].flag_noun_bits ^= (1 << (arg1 - 1));
+ break; /* Toggle */
+ case 1095: /* if (agt_counter[arg1]==-1)*/
+ agt_counter[arg1] = 1;
+ break;
+ case 1096:
+ agt_counter[arg1] = -1;
+ break;
+ case 1097:
+ agt_var[arg1] = arg2;
+ break;
+ case 1098:
+ agt_var[arg1] += arg2;
+ break;
+ case 1099:
+ agt_var[arg1] -= arg2;
+ break;
+ case 1100:
+ agt_var[arg1] += agt_var[arg2];
+ break;
+ case 1101:
+ agt_var[arg1] -= agt_var[arg2];
+ break;
+ case 1102:
+ agt_var[arg1] = agt_rand(0, arg2);
+ break;
+ case 1103:
+ agt_var[arg1] = dobj_rec->num;
+ break;
+ case 1104:
+ agt_var[arg1] = iobj_rec->num;
+ break;
+
+ /* The following are v1.8x specific */
+ case 1105:
+ quote(arg1);
+ break;
+ case 1106:
+ add_time(arg1);
+ break;
+ case 1107:
+ add_time(-arg1);
+ break;
+ case 1108:
+ curr_time = (curr_time % 100) + 100 * arg1;
+ break;
+ case 1109:
+ curr_time = (curr_time / 100) * 100 + arg1;
+ break;
+ case 1110:
+ add_time(agt_var[arg1]);
+ break;
+ case 1111:
+ add_time(-agt_var[arg1]);
+ break;
+ case 1112:
+ curr_time = (curr_time % 100) + 100 * agt_var[arg1];
+ break;
+ case 1113:
+ curr_time = (curr_time / 100) * 100 + agt_var[arg1];
+ break;
+
+ /* Now for the AGX additions */
+ case 1114:
+ add_time(-arg1);
+ break; /* ME-style SubtractFromTime */
+ case 1115:
+ disambig_score = arg1;
+ break; /* SetDisambigPriority */
+ case 1116:
+ agt_var[arg1] = delta_time;
+ break;
+ case 1117: /* ChangeStatus */
+ statusmode = arg1;
+ break;
+ case 1118:
+ if (!mult_rangecheck(agt_var[arg1], arg2)) break;
+ agt_var[arg1] *= arg2;
+ break;
+ case 1119:
+ if (arg2 == 0) {
+ if (!PURE_ERROR)
+ writeln("GAME ERROR: Division by zero.");
+ } else agt_var[arg1] /= arg2;
+ break;
+ case 1120:
+ if (arg2 == 0) {
+ if (!PURE_ERROR)
+ writeln("GAME ERROR: Attempt to divide by zero.");
+ } else agt_var[arg1] %= arg2;
+ break;
+ case 1121:
+ agt_waitkey();
+ break;
+ case 1122:
+ last_he = arg1;
+ break; /* SetHE */
+ case 1123:
+ last_she = arg1;
+ break;
+ case 1124:
+ last_it = arg1;
+ break;
+ case 1125:
+ last_they = arg1;
+ break;
+ case 1126:
+ msgout(arg1, 0);
+ break;
+ case 1127:
+ if (!PURE_ERROR)
+ sysmsg(arg1, "GAME ERROR: Standard message not defined.");
+ break;
+ case 1128:
+ msgout(arg1, 1);
+ break; /* FailMessage */
+ case 1129: /* StdMessage */
+ sysmsg(arg1, "GAME ERROR: Standard message not defined.");
+ break;
+ case 1130:
+ msgout(arg2, 1);
+ break; /* ErrMessage */
+ case 1131: /* StdErrMessage */
+ sysmsg(arg1, "GAME ERROR: Standard message not defined.");
+ break;
+ case 1132: /* AND */
+ break; /* These don't do anything under normal circumstances */
+ case 1133: /* SetClass */
+ if (troom(arg1)) room[arg1 - first_room].oclass = arg2;
+ else if (tnoun(arg1)) noun[arg1 - first_noun].oclass = arg2;
+ else if (tcreat(arg1)) noun[arg1 - first_creat].oclass = arg2;
+ break;
+ case 1134:
+ agt_var[arg1] = it_class(arg2);
+ break; /* SetVariableToClass */
+
+ /* Stack commands */
+ case 1135:
+ push_stack(arg1);
+ break;
+ case 1136:
+ agt_var[arg1] = pop_stack();
+ break;
+ case 1137:
+ case 1138:
+ case 1139:
+ case 1140:
+ case 1141:
+ op_stack(op_ - 1137); /* +,-,*, /,% * */
+ break;
+ case 1142: { /* DupStack */
+ long n;
+ n = pop_stack();
+ push_stack(n);
+ push_stack(n);
+ break;
+ }
+ case 1143:
+ pop_stack();
+ break; /* Discard TOS */
+ case 1144:
+ agt_var[arg1] = agt_number;
+ break; /* SetVariableToInput */
+ case 1145:
+ setattr(arg1, arg2, 1);
+ break; /* Set */
+ case 1146:
+ setattr(arg1, arg2, 0);
+ break; /* Clear */
+ case 1147:
+ push_stack(getprop(arg1, arg2));
+ break; /* PushProp */
+ case 1148:
+ setprop(arg1, arg2, pop_stack());
+ break; /* PopProp */
+ /* 1149, 1150 handled by run_metacommand */
+ /* 1151 is EndDisambig */
+ /* 1152 is XRedirect */
+ case 1153:
+ rstrncpy(userstr[arg1 - 1], userstr[arg2 - 1], 81);
+ break;
+ case 1154:
+ setcase(userstr[arg1 - 1], 1);
+ break;
+ case 1155:
+ setcase(userstr[arg1 - 1], 0);
+ break;
+ case 1156:
+ op_objflag(1, arg1, arg2);
+ break;
+ case 1157:
+ op_objflag(0, arg1, arg2);
+ break;
+ case 1158:
+ op_objflag(3, arg1, arg2);
+ break;
+ case 1159:
+ push_stack(op_objprop(2, arg1, arg2, 0));
+ break;
+ case 1160:
+ op_objprop(1, arg1, arg2, pop_stack());
+ break;
+ case 1161:
+ move_in_dir(arg1, arg2);
+ break;
+ default:
+ writeln("INTERNAL ERROR: Action token not supported.");
+ rprintf("Action #%d", op_);
+ writeln("");
+ }
+}
+
+int exec_instr(op_rec *oprec)
+/* This routine is responsible for executing all conditions and action
+ tokens. Remember action tokens are numbered from 1000. */
+/* Return codes:
+ 0 to continue running commands
+ 1 for a failed conditional token
+ 100 Next metacommand
+ 101 Stop running metacommands
+ 102 End turn
+ 103 Redirection */
+{
+ rbool r;
+
+ if (oprec->op < 1000) {
+ r = exec_cond(oprec->op, oprec->arg1, oprec->arg2);
+ return (oprec->negate ? r : !r); /* Is it prefixed by NOT? */
+ }
+ switch (oprec->op) {
+ case 1151: /* EndDisambig <num> */
+ if (do_disambig) {
+ disambig_score = oprec->arg1;
+ return 102; /* "End turn" if disambiguating */
+ }
+ return 0; /* ... otherwise do nothing */
+ case 1062:
+ case 1152:
+ return 103; /* Redirect */
+ case WIN_ACT:
+ winflag = 1;
+ return 0; /* win game */
+ case (WIN_ACT+1):
+ endflag = 1;
+ return 102; /* end game */
+ case (WIN_ACT+2):
+ return 100; /* end this command */
+ case (WIN_ACT+3):
+ return 101; /* end all commands */
+ case (WIN_ACT+4):
+ return 102; /* end turn */
+ default:
+ exec_action(oprec->op, oprec->arg1, oprec->arg2);
+ if (oprec->failmsg) return 102;
+ else return 0;
+ }
+}
+
+} // End of namespace AGT
+} // End of namespace Glk
diff --git a/engines/glk/agt/util.cpp b/engines/glk/agt/util.cpp
new file mode 100644
index 0000000000..0bf96351f1
--- /dev/null
+++ b/engines/glk/agt/util.cpp
@@ -0,0 +1,1489 @@
+/* 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/quetzal.h"
+#include "common/textconsole.h"
+
+namespace Glk {
+namespace AGT {
+
+/* This includes wrappers for malloc, realloc, strdup, and free
+ that exit gracefully if we run out of memory. */
+/* There are also various utilities:
+ concdup: Creates a new string that is concatation of two others.
+ strcasecmp: case insensitive comparison of strings
+ strncasecmp: case insensitive compare of first n characters of strings
+ fixsign16, fixsign32: routines to assemble signed ints out of
+ individual bytes in an endian-free way. */
+/* Also buffered file i/o routines and some misc. file utilites. */
+
+#ifdef force16
+#undef int
+#endif
+
+#ifdef force16
+#define int short
+#endif
+
+long rangefix(long n) {
+ if (n > 0) return n;
+ return 0;
+}
+
+/*-------------------------------------------------------------------*/
+/* Sign fixing routines, to build a signed 16- and 32-bit quantities */
+/* out of their component bytes. */
+/*-------------------------------------------------------------------*/
+
+#ifndef FAST_FIXSIGN
+short fixsign16(uchar n1, uchar n2) {
+ rbool sflag;
+ short n;
+
+ if (n2 > 0x80) {
+ n2 &= 0x7F;
+ sflag = 1;
+ } else sflag = 0;
+
+ n = n1 + (n2 << 8);
+ if (sflag) n = n - 0x7fff - 1;
+ return n;
+}
+
+long fixsign32(uchar n1, uchar n2, uchar n3, uchar n4) {
+ rbool sflag;
+ long n;
+
+ if (n4 > 0x80) {
+ n4 &= 0x7F;
+ sflag = 1;
+ } else sflag = 0;
+
+ n = n1 + (((long)n2) << 8) + (((long)n3) << 16) + (((long)n4) << 24);
+ if (sflag) n = n - 0x7fffffffL - 1L;
+ return n;
+}
+#endif
+
+
+
+/*----------------------------------------------------------------------*/
+/* rprintf(), uses writestr for output */
+/* This function is used mainly for diagnostic information */
+/* There should be no newlines in the format string or in any of the */
+/* arguments as those could confuse writestr, except for the last */
+/* character in the string which can be a newline. */
+/*----------------------------------------------------------------------*/
+
+void rprintf(const char *fmt, ...) {
+ int i;
+ char s[100];
+ va_list args;
+
+ va_start(args, fmt);
+ vsprintf(s, fmt, args);
+ va_end(args);
+ i = strlen(s) - 1;
+ if (i >= 0 && s[i] == '\n') {
+ s[i] = 0;
+ writeln(s);
+ } else writestr(s);
+}
+
+
+/*----------------------------------------------------------------------*/
+/* Memory allocation wrappers: All memory allocation should run through */
+/* these routines, which trap error conditions and do accounting to */
+/* help track down memory leaks. */
+/*----------------------------------------------------------------------*/
+
+rbool rm_trap = 1;
+
+long get_rm_size(void)
+/* Return the amount of space being used by dynamically allocated things */
+{
+#ifdef MEM_INFO
+ struct mstats memdata;
+
+ memdata = mstats();
+ return memdata.bytes_used;
+#endif
+ return 0;
+}
+
+long get_rm_freesize(void)
+/* Return estimate of amount of space left */
+{
+#ifdef MEM_INFO
+ struct mstats memdata;
+
+ memdata = mstats();
+ return memdata.bytes_free;
+#endif
+ return 0;
+}
+
+
+void *rmalloc(long size) {
+ void *p;
+
+ if (size > MAXSTRUC) {
+ error("Memory allocation error: Over-sized structure requested.");
+ }
+ assert(size >= 0);
+ if (size == 0) return NULL;
+ p = malloc((size_t)size);
+ if (p == NULL && rm_trap && size > 0) {
+ error("Memory allocation error: Out of memory.");
+ }
+ if (rm_acct) ralloc_cnt++;
+ return p;
+}
+
+void *rrealloc(void *old, long size) {
+ void *p;
+
+ if (size > MAXSTRUC) {
+ error("Memory reallocation error: Oversized structure requested.");
+ }
+ assert(size >= 0);
+ if (size == 0) {
+ r_free(old);
+ return NULL;
+ }
+ if (rm_acct && old == NULL) ralloc_cnt++;
+ p = realloc(old, (size_t)size);
+ if (p == NULL && rm_trap && size > 0) {
+ error("Memory reallocation error: Out of memory.");
+ }
+ return p;
+}
+
+char *rstrdup(const char *s) {
+ char *t;
+#ifndef HAVE_STRDUP
+ int i;
+#endif
+
+ if (s == NULL) return NULL;
+#ifdef HAVE_STRDUP
+ t = strdup(s);
+#else
+ t = (char *)malloc((strlen(s) + 1) * sizeof(char));
+#endif
+ if (t == NULL && rm_trap) {
+ error("Memory duplication error: Out of memory.");
+ }
+ if (rm_acct) ralloc_cnt++;
+#ifndef HAVE_STRDUP
+ for (i = 0; s[i] != 0; i++)
+ t[i] = s[i];
+ t[i] = 0;
+#endif
+ return t;
+}
+
+void r_free(void *p) {
+ int tmp;
+
+ if (p == NULL) return;
+
+ tmp = get_rm_size(); /* Take worst case in all cases */
+ if (tmp > rm_size) rm_size = tmp;
+ tmp = get_rm_freesize();
+ if (tmp < rm_freesize) rm_freesize = tmp;
+
+ if (rm_acct) rfree_cnt++;
+ free(p);
+}
+
+
+
+/*----------------------------------------------------------------------*/
+/* String utilities: These are utilities to manipulate strings. */
+/*----------------------------------------------------------------------*/
+
+/* rnstrncpy copies src to dest, copying at most (max-1) characters.
+ Unlike ANSI strncpy, it doesn't fill extra space will nulls and
+ it always puts a terminating null. */
+char *rstrncpy(char *dest, const char *src, int max) {
+ int i;
+ for (i = 0; i < max - 1 && src[i]; i++)
+ dest[i] = src[i];
+ dest[i] = 0;
+ return dest;
+}
+
+/* This does a case-insensitive match of the beginning of *pstr to match */
+/* <match> must be all upper case */
+/* *pstr is updated to point after the match, if it is succesful.
+ Otherwise *pstr is left alone. */
+rbool match_str(const char **pstr, const char *match) {
+ int i;
+ const char *s;
+
+ s = *pstr;
+ for (i = 0; match[i] != 0 && s[i] != 0; i++)
+ if (toupper(s[i]) != match[i]) return 0;
+ if (match[i] != 0) return 0;
+ *pstr += i;
+ return 1;
+}
+
+
+
+
+/* Utility to concacate two strings with a space inserted */
+
+char *concdup(const char *s1, const char *s2) {
+ int len1, len2;
+ char *s;
+
+ len1 = len2 = 0;
+ if (s1 != NULL) len1 = strlen(s1);
+ if (s2 != NULL) len2 = strlen(s2);
+
+ s = (char *)rmalloc(sizeof(char) * (len1 + len2 + 2));
+ if (s1 != NULL)
+ memcpy(s, s1, len1);
+ memcpy(s + len1, " ", 1);
+ if (s2 != NULL)
+ memcpy(s + len1 + 1, s2, len2);
+ s[len1 + len2 + 1] = 0;
+ return s;
+}
+
+
+/* Misc. C utility functions that may be supported locally.
+ If they are, use the local functions since they'll probably be faster
+ and more efficiant. */
+
+#ifdef NEED_STR_CMP
+int strcasecmp(const char *s1, const char *s2)
+/* Compare strings s1 and s2, case insensitive; */
+/* If equal, return 0. Otherwise return nonzero. */
+{
+ int i;
+
+ for (i = 0; tolower(s1[i]) == tolower(s2[i]) && s1[i] != 0; i++);
+ if (tolower(s1[i]) == tolower(s2[i])) return 0;
+ if (s1[i] == 0) return -1;
+ if (s2[i] == 0) return 1;
+ if (tolower(s1[i]) < tolower(s2[i])) return -1;
+ return 1;
+}
+#endif /* NEED_STR_CMP */
+
+#ifdef NEED_STRN_CMP
+int strncasecmp(const char *s1, const char *s2, size_t n)
+/* Compare first n letters of strings s1 and s2, case insensitive; */
+/* If equal, return 0. Otherwise return nonzero. */
+{
+ size_t i;
+
+ for (i = 0; i < n && tolower(s1[i]) == tolower(s2[i]) && s1[i] != 0; i++);
+ if (i == n || tolower(s1[i]) == tolower(s2[i])) return 0;
+ if (s1[i] == 0) return -1;
+ if (s2[i] == 0) return 1;
+ if (tolower(s1[i]) < tolower(s2[i])) return -1;
+ return 1;
+}
+#endif /* NEED_STRN_CMP */
+
+/*----------------------------------------------------------------------*/
+/* Character utilities: Do character translation */
+/*----------------------------------------------------------------------*/
+
+void build_trans_ascii(void) {
+ int i;
+
+ for (i = 0; i < 256; i++)
+ trans_ascii[i] = (!fix_ascii_flag || i < 0x80) ? i : trans_ibm[i & 0x7f];
+ trans_ascii[0xFF] = 0xFF; /* Preserve format character */
+}
+
+
+/*----------------------------------------------------------------------*/
+/* File utilities: Utilities to manipulate files. */
+/*----------------------------------------------------------------------*/
+
+void print_error(const char *fname, filetype ext, const char *err, rbool ferr) {
+ char *estring; /* Hold error string */
+ estring = (char *)rmalloc(strlen(err) + strlen(fname) + 2);
+ sprintf(estring, err, fname);
+ if (ferr) fatal(estring);
+ else writeln(estring);
+ rfree(estring);
+}
+
+/* Routine to open files with extensions and handle basic error conditions */
+
+genfile fopen(const char *name, const char *how) {
+ if (!strcmp(how, "r") || !strcmp(how, "rb")) {
+ Common::File *f = new Common::File();
+ if (!f->open(name)) {
+ delete f;
+ f = nullptr;
+ }
+
+ return f;
+ } else if (!strcmp(how, "w") || !strcmp(how, "wb")) {
+ Common::DumpFile *f = new Common::DumpFile();
+ if (!f->open(name)) {
+ delete f;
+ f = nullptr;
+ }
+
+ return f;
+ } else {
+ error("Unknown file open how");
+ }
+}
+
+int fseek(genfile stream, long int offset, int whence) {
+ Common::SeekableReadStream *rs = dynamic_cast<Common::SeekableReadStream *>(stream);
+ assert(rs);
+ return rs->seek(offset, whence);
+}
+
+size_t fread(void *ptr, size_t size, size_t nmemb, genfile stream) {
+ Common::SeekableReadStream *rs = dynamic_cast<Common::SeekableReadStream *>(stream);
+ assert(rs);
+ return rs->read(ptr, size * nmemb);
+}
+
+size_t fwrite(const void *ptr, size_t size, size_t nmemb, genfile stream) {
+ Common::WriteStream *ws = dynamic_cast<Common::WriteStream *>(stream);
+ assert(ws);
+ return ws->write(ptr, size * nmemb);
+}
+
+size_t ftell(genfile f) {
+ Common::SeekableReadStream *rs = dynamic_cast<Common::SeekableReadStream *>(f);
+ assert(rs);
+ return rs->pos();
+}
+
+genfile openfile(fc_type fc, filetype ext, const char *err, rbool ferr)
+/* Opens the file fname+ext, printing out err if something goes wrong.
+ (unless err==NULL, in which case nothing will be printed) */
+/* err can have one %s paramater in it, which will have the file name
+ plugged in to it. */
+/* If ferr is true, then on failure the routine will abort with a fatal
+ error. */
+{
+ genfile tfile; /* Actually, this may not be a text file anymore */
+ const char *errstr;
+
+ tfile = readopen(fc, ext, &errstr);
+ if (errstr != NULL && err != NULL)
+ print_error("", ext, err, ferr);
+
+ return tfile;
+}
+
+
+genfile openbin(fc_type fc, filetype ext, const char *err, rbool ferr)
+/* Opens the file fname+ext, printing out err if something goes wrong.
+ (unless err==NULL, in which case nothing will be printed) */
+/* err can have one %s paramater in it, which will have the file name
+ plugged in to it. */
+/* If ferr is true, then on failure the routine will abort with a fatal
+ error. */
+{
+ genfile f; /* Actually, this may not be a text file anymore */
+ const char *errstr;
+ char *fname;
+
+ f = readopen(fc, ext, &errstr);
+ if (errstr != NULL && err != NULL) {
+ fname = formal_name(fc, ext);
+ print_error(fname, ext, err, ferr);
+ rfree(fname);
+ }
+
+ return f;
+}
+
+
+
+/* This routine reads in a line from a 'text' file; it's designed to work
+ regardless of the EOL conventions of the platform, at least up to a point.
+ It should work with files that have \n, \r, or \r\n termined lines. */
+
+#define READLN_GRAIN 64 /* Granularity of readln() rrealloc requests
+ this needs to be at least the size of a tab
+ character */
+#define DOS_EOF 26 /* Ctrl-Z is the DOS end-of-file marker */
+
+char *readln(genfile f, char *buff, int n)
+/* Reads first n characters of line, eliminates any newline,
+and truncates the rest of the line. 'n' does *not* include terminating
+null. */
+/* If we pass it BUFF=NULL, then it will reallocate buff as needed and
+pass it back as its return value. n is ignored in this case */
+/* If it reaches EOF, it will return NULL */
+/* This routine recognizes lines terminated by \n, \r, or \r\n */
+{
+ int c;
+ int i, j, csize;
+ int buffsize; /* Current size of buff, if we are allocating it dynamically */
+
+ if (buff == NULL) {
+ buff = (char *)rrealloc(buff, READLN_GRAIN * sizeof(char));
+ buffsize = READLN_GRAIN;
+ n = buffsize - 1;
+ } else buffsize = -1; /* So we know that we are using a fixed-size buffer */
+
+ i = 0;
+ for (;;) {
+ c = textgetc(f);
+
+ if (c == '\n' || c == '\r' || c == EOF || c == DOS_EOF) break;
+
+ csize = (c == '\t') ? 5 : 1; /* Tabs are translated into five spaces */
+
+ if (i + csize >= n && buffsize >= 0) {
+ buffsize += READLN_GRAIN;
+ n = buffsize - 1;
+ buff = (char *)rrealloc(buff, buffsize * sizeof(char));
+ }
+
+ if (c == 0) c = FORMAT_CODE;
+ else if (c != '\t') {
+ if (i < n) buff[i++] = c;
+ } else for (j = 0; j < 5 && i < n; j++) buff[i++] = ' ';
+
+ /* We can't exit the loop if i>n since we still need to discard
+ the rest of the line */
+ }
+
+ buff[i] = 0;
+
+ if (c == '\r') { /* Check for \r\n DOS-style newline */
+ char newc;
+ newc = textgetc(f);
+ if (newc != '\n') textungetc(f, newc);
+ /* Replace the character we just read. */
+ } else if (c == DOS_EOF) /* Ctrl-Z is the DOS EOF marker */
+ textungetc(f, c); /* So it will be the first character we see next time */
+
+ if (i == 0 && (c == EOF || c == DOS_EOF)) { /* We've hit the end of the file */
+ if (buffsize >= 0) rfree(buff);
+ return NULL;
+ }
+
+ if (buffsize >= 0) { /* Shrink buffer to appropriate size */
+ buffsize = i + 1;
+ buff = (char *)rrealloc(buff, buffsize);
+ }
+
+ return buff;
+}
+
+
+/*-------------------------------------------------------------------------*/
+/* Buffered file Input: Routines to do buffered file I/O for files organized */
+/* into records. These routines are highly non-reentrant: they use a */
+/* global buffer and a global file id, so only they can only access one */
+/* file at a time. */
+/* buffopen() should not be called on a new file until buffclose has been */
+/* called on the old one. */
+/*-------------------------------------------------------------------------*/
+
+genfile bfile;
+
+static uchar *buffer = NULL;
+static long buffsize; /* How big the buffer is */
+static long record_size; /* Size of a record in the file */
+static long buff_frame; /* The file index corrosponding to buffer[0] */
+static long buff_fcnt; /* Number of records that can be held in the buffer */
+static long real_buff_fcnt; /* Number of records actually held in buffer */
+static long buff_rsize; /* Minimum amount that must be read. */
+
+static long block_size; /* Size of the current block
+ (for non-AGX files, this is just the filesize) */
+static long block_offset; /* Offset of current block in file (this should
+ be zero for non-AGX files) */
+
+
+static void buff_setrecsize(long recsize) {
+ const char *errstr;
+
+ record_size = recsize;
+ real_buff_fcnt = buff_fcnt = buffsize / record_size;
+ buff_frame = 0;
+
+ /* Note that real_buff_cnt==buff_fcnt in this case because
+ the buffer will have already been resized to be <=
+ the block size-- so we don't need to worry about the
+ buffer being larger than the data we're reading in. */
+
+ binseek(bfile, block_offset);
+ if (!binread(bfile, buffer, record_size, real_buff_fcnt, &errstr))
+ fatal(errstr);
+}
+
+
+
+long buffopen(fc_type fc, filetype ext, long minbuff, const char *rectype, long recnum)
+/* Returns record size; print out error and halt on failure */
+/* (if agx_file, it returns the filesize instead) */
+/* rectype="noun","room", etc. recnum=number of records expected */
+/* If rectype==NULL, buffopen() will return 0 on failure instead of
+halting */
+/* For AGX files, recsize should be set to minbuff... but
+buffreopen will be called before any major file activity
+(in particular, recnum should be 1) */
+{
+ long filesize;
+ long recsize;
+ char ebuff[200];
+ const char *errstr;
+
+ assert(buffer == NULL); /* If not, it means these routines have been
+ called by someone else who isn't done yet */
+
+ bfile = readopen(fc, ext, &errstr);
+ if (errstr != NULL) {
+ if (rectype == NULL) {
+ return 0;
+ } else
+ fatal(errstr);
+ }
+
+ filesize = binsize(bfile);
+
+ block_size = filesize;
+ block_offset = 0;
+ if (agx_file) block_size = minbuff; /* Just for the beginning */
+
+ if (block_size % recnum != 0) {
+ sprintf(ebuff, "Fractional record count in %s file.", rectype);
+ agtwarn(ebuff, 0);
+ }
+ buff_rsize = recsize = block_size / recnum;
+ if (buff_rsize > minbuff) buff_rsize = minbuff;
+
+ /* No point in having a buffer bigger than the block size */
+ buffsize = BUFF_SIZE;
+ if (block_size < buffsize) buffsize = block_size;
+
+ /* ... but it needs to be big enough: */
+ if (buffsize < minbuff) buffsize = minbuff;
+ if (buffsize < recsize) buffsize = recsize;
+
+ buffer = (uchar *)rmalloc(buffsize); /* Might want to make this adaptive eventually */
+
+ buff_setrecsize(recsize);
+ if (!agx_file && DIAG) {
+ char *s;
+ s = formal_name(fc, ext);
+ rprintf("Reading %s file %s (size:%ld)\n", rectype, s, filesize);
+ rfree(s);
+ rprintf(" Record size= Formal:%d File:%ld", minbuff, recsize);
+ }
+ if (agx_file) return (long) filesize;
+ else return (long) recsize;
+}
+
+
+/* Compute the game signature: a checksum of relevant parts of the file */
+
+static void compute_sig(uchar *buff) {
+ long bp;
+ for (bp = 0; bp < buff_rsize; bp++)
+ game_sig = (game_sig + buff[bp]) & 0xFFFF;
+}
+
+uchar *buffread(long index) {
+ uchar *bptr;
+ const char *errstr;
+
+ assert(buff_rsize <= record_size);
+ if (index >= buff_frame && index < buff_frame + real_buff_fcnt)
+ bptr = buffer + (index - buff_frame) * record_size;
+ else {
+ binseek(bfile, block_offset + index * record_size);
+ real_buff_fcnt = block_size / record_size - index; /* How many records
+ could we read in? */
+ if (real_buff_fcnt > buff_fcnt)
+ real_buff_fcnt = buff_fcnt; /* Don't overflow buffer */
+ if (!binread(bfile, buffer, record_size, real_buff_fcnt, &errstr))
+ fatal(errstr);
+ buff_frame = index;
+ bptr = buffer;
+ }
+ if (!agx_file) compute_sig(bptr);
+ return bptr;
+}
+
+
+void buffclose(void) {
+ readclose(bfile);
+ rfree(buffer);
+}
+
+
+/* This changes the record size and offset settings of the buffered
+ file so we can read files that consist of multiple sections with
+ different structures */
+static void buffreopen(long f_ofs, long file_recsize, long recnum,
+ long bl_size, const char *rectype) {
+ char ebuff[200];
+ long recsize;
+
+ /* Compute basic statistics */
+ block_offset = f_ofs; /* Offset of this block */
+ block_size = bl_size; /* Size of the entire block (all records) */
+ if (block_size % recnum != 0) {
+ /* Check that the number of records divides the block size evenly */
+ sprintf(ebuff, "Fractional record count in %s block.", rectype);
+ agtwarn(ebuff, 0);
+ }
+ buff_rsize = recsize = block_size / recnum;
+ if (buff_rsize > file_recsize) buff_rsize = file_recsize;
+ /* recsize is the size of each record in the file.
+ buff_rsize is the internal size of each record (the part
+ we actually look at, which may be smaller than recsize) */
+
+ /* No point in having a buffer bigger than the block size */
+ buffsize = BUFF_SIZE;
+ if (block_size < buffsize) buffsize = block_size;
+
+ /* The buffer needs to be at least as big as one block, so
+ we have space to both read it in and so we can look at the
+ block without having to worry about how big it really is */
+ if (buffsize < file_recsize) buffsize = file_recsize;
+ if (buffsize < recsize) buffsize = recsize;
+
+ rfree(buffer);
+ buffer = (uchar *)rmalloc(buffsize); /* Resize the buffer */
+
+ buff_setrecsize(recsize); /* Set up remaining stats */
+}
+
+
+/*-------------------------------------------------------------------------*/
+/* Buffered file output: Routines to buffer output for files organized */
+/* into records. These routines are highly non-reentrant: they use a */
+/* global buffer and a global file id, so only they can only access one */
+/* file at a time. */
+/* This routine uses the same buffer and data structures as the reading */
+/* routines above, so both sets of routines should not be used */
+/* concurrently */
+/*-------------------------------------------------------------------------*/
+
+/* #define DEBUG_SEEK*/ /* Debug seek beyond EOF problem */
+
+static long bw_first, bw_last; /* First and last record in buffer written to.
+ This is relative to the beginning of the
+ buffer bw_last points just beyond the last
+ one written to */
+#ifdef DEBUG_SEEK
+static long bw_fileleng; /* Current file length */
+#endif /* DEBUG_SEEK */
+file_id_type bw_fileid;
+
+/* Unlike is reading counterpart, this doesn't actually allocate
+ a buffer; that's done by bw_setblock() which should be called before
+ any I/O */
+void bw_open(fc_type fc, filetype ext) {
+ const char *errstr;
+
+ assert(buffer == NULL);
+
+ bfile = writeopen(fc, ext, &bw_fileid, &errstr);
+ if (errstr != NULL) fatal(errstr);
+ bw_last = 0;
+ buffsize = 0;
+ buffer = NULL;
+#ifdef DEBUG_SEEK
+ bw_fileleng = 0;
+#endif
+}
+
+static void bw_seek(long offset) {
+#ifdef DEBUG_SEEK
+ assert(offset <= bw_fileleng);
+#endif
+ binseek(bfile, offset);
+}
+
+
+static void bw_flush(void) {
+ if (bw_first == bw_last) return; /* Nothing to do */
+ bw_first += buff_frame;
+ bw_last += buff_frame;
+ bw_seek(block_offset + bw_first * record_size);
+ binwrite(bfile, buffer, record_size, bw_last - bw_first, 1);
+#ifdef DEBUG_SEEK
+ if (block_offset + bw_last * record_size > bw_fileleng)
+ bw_fileleng = block_offset + bw_last * record_size;
+#endif
+ bw_first = bw_last = 0;
+}
+
+
+
+static void bw_setblock(long fofs, long recnum, long rsize)
+/* Set parameters for current block */
+{
+ /* First, flush old block if neccessary */
+ if (buffer != NULL) {
+ bw_flush();
+ rfree(buffer);
+ }
+ block_size = rsize * recnum;
+ block_offset = fofs;
+ record_size = rsize;
+ buff_frame = 0;
+ bw_first = bw_last = 0;
+ buffsize = BUFF_SIZE;
+ if (buffsize > block_size) buffsize = block_size;
+ if (buffsize < rsize) buffsize = rsize;
+ buff_fcnt = buffsize / rsize;
+ buffsize = buff_fcnt * rsize;
+ buffer = (uchar *)rmalloc(buffsize);
+}
+
+/* This routine returns a buffer of the current recsize and with
+ the specified index into the file */
+/* The buffer will be written to disk after the next call to
+ bw_getbuff() or bw_closebuff() */
+static uchar *bw_getbuff(long index) {
+ index -= buff_frame;
+ if (index < bw_first || index > bw_last || index >= buff_fcnt) {
+ bw_flush();
+ bw_first = bw_last = 0;
+ buff_frame = buff_frame + index;
+ index = 0;
+ }
+ if (index == bw_last) bw_last++;
+ return buffer + record_size * index;
+}
+
+
+/* This flushes all buffers to disk and closes all files */
+void bw_close(void) {
+ bw_flush();
+ rfree(buffer);
+ writeclose(bfile, bw_fileid);
+}
+
+void bw_abort(void) {
+ binremove(bfile, bw_fileid);
+}
+
+
+/*-------------------------------------------------------------------------*/
+/* Block reading and writing code and support for internal buffers */
+/*-------------------------------------------------------------------------*/
+
+
+/* If the internal buffer is not NULL, it is used instead of a file */
+/* (This is used by RESTART, etc. to save state to memory rather than
+ to a file) */
+static uchar *int_buff = NULL;
+static long ibuff_ofs, ibuff_rsize;
+
+void set_internal_buffer(void *buff) {
+ int_buff = (uchar *)buff;
+}
+
+static void set_ibuff(long offset, long rsize) {
+ ibuff_ofs = offset;
+ record_size = ibuff_rsize = rsize;
+}
+
+static uchar *get_ibuff(long index) {
+ return int_buff + ibuff_ofs + index * ibuff_rsize;
+}
+
+/* This does a block write to the currently buffered file.
+ At the moment this itself does no buffering at all; it's intended
+ for high speed reading of blocks of chars for which we've already
+ allocated the space. */
+static void buff_blockread(void *buff, long size, long offset) {
+ const char *errstr;
+
+ if (int_buff != NULL)
+ memcpy((char *)buff, int_buff + offset, size);
+ else {
+ binseek(bfile, offset);
+ if (!binread(bfile, buff, size, 1, &errstr)) fatal(errstr);
+ }
+}
+
+
+/* This writes buff to disk. */
+static void bw_blockwrite(void *buff, long size, long offset) {
+ if (int_buff != NULL)
+ memcpy(int_buff + offset, (char *)buff, size);
+ else {
+ bw_flush();
+ bw_seek(offset);
+ binwrite(bfile, buff, size, 1, 1);
+#ifdef DEBUG_SEEK
+ if (offset + size > bw_fileleng) bw_fileleng = offset + size;
+#endif
+ }
+}
+
+
+/*-------------------------------------------------------------------------*/
+/* Platform-independent record-based file I/O: Routines to read and write */
+/* files according to the file_info data structures. */
+/* These routines use the buffered I/O routines above */
+/*-------------------------------------------------------------------------*/
+
+/* Length of file datatypes */
+const size_t ft_leng[FT_COUNT] = {0, 2, 2, /* END, int16, and uint16 */
+ 4, 4, /* int32 and uint32 */
+ 1, 2, 0, /* byte, version, rbool */
+ 8, 4, /* descptr, ss_ptr */
+ 2, 26, /* slist, path[13] */
+ 4, 4, /* cmdptr, dictptr */
+ 81, /* tline */
+ 1, 1
+ }; /* char, cfg */
+
+
+long compute_recsize(file_info *recinfo) {
+ long cnt, bcnt;
+
+ cnt = 0;
+ for (; recinfo->ftype != FT_END; recinfo++)
+ if (recinfo->ftype == FT_BOOL) {
+ for (bcnt = 0; recinfo->ftype == FT_BOOL; recinfo++, bcnt++);
+ recinfo--;
+ cnt += (bcnt + 7) / 8; /* +7 is to round up */
+ } else
+ cnt += ft_leng[recinfo->ftype];
+ return cnt;
+}
+
+static const int agx_version[] = {0, 0000, 1800, 2000, 3200, 3500, 8200, 8300,
+ 5000, 5050, 5070, 10000, 10050, 15000, 15500, 16000, 20000
+ };
+
+static int agx_decode_version(int vercode) {
+ if (vercode & 1) /* Large/Soggy */
+ if (vercode == 3201) ver = 4;
+ else ver = 2;
+ else if (vercode < 10000) ver = 1;
+ else ver = 3;
+ switch (vercode & (~1)) {
+ case 0000:
+ return AGT10;
+ case 1800:
+ return AGT118;
+ case 1900:
+ return AGT12;
+ case 2000:
+ return AGT12;
+ case 3200:
+ return AGTCOS;
+ case 3500:
+ return AGT135;
+ case 5000:
+ return AGT15;
+ case 5050:
+ return AGT15F;
+ case 5070:
+ return AGT16;
+ case 8200:
+ return AGT182;
+ case 8300:
+ return AGT183;
+ case 8350:
+ return AGT183;
+ case 10000:
+ return AGTME10;
+ case 10050:
+ return AGTME10A;
+ case 15000:
+ return AGTME15;
+ case 15500:
+ return AGTME155;
+ case 16000:
+ return AGTME16;
+ case 20000:
+ return AGX00;
+ default:
+ agtwarn("Unrecognize AGT version", 0);
+ return 0;
+ }
+}
+
+/* The following reads a section of a file into variables, doing
+ the neccessary conversions. It is the foundation of all the generic
+ file reading code */
+
+#define p(t) ((t*)(rec_desc->ptr))
+#define fixu16(n1,n2) ( ((long)(n1))|( ((long)(n2))<<8 ))
+
+/* This is as large as the largest data structure we could run into */
+static const uchar zero_block[81] = {0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0
+ };
+
+static void read_filerec(const file_info *rec_desc, const uchar *filedata) {
+ uchar mask;
+ rbool past_eob; /* Are we past the end of block? */
+ const uchar *filebase;
+
+ mask = 1;
+ past_eob = 0;
+ filebase = filedata;
+ for (; rec_desc->ftype != FT_END; rec_desc++) {
+ if (mask != 1 && rec_desc->ftype != FT_BOOL) { /* Just finished rboolean */
+ mask = 1;
+ filedata += 1;
+ }
+ if (filebase == NULL || (filedata - filebase) >= record_size) {
+ /* We're past the end of the block; read in zeros for the rest
+ of entries. */
+ past_eob = 1;
+ filedata = zero_block;
+ filebase = NULL;
+ }
+ switch (rec_desc->ftype) {
+ case FT_INT16:
+ if (rec_desc->dtype == DT_LONG)
+ *p(long) = fixsign16(filedata[0], filedata[1]);
+ else
+ *p(integer) = fixsign16(filedata[0], filedata[1]);
+ break;
+ case FT_UINT16:
+ *p(long) = fixu16(filedata[0], filedata[1]);
+ break;
+ case FT_CMDPTR: /* cmd ptr */
+ case FT_INT32:
+ *p(long) = fixsign32(filedata[0], filedata[1],
+ filedata[2], filedata[3]);
+ break;
+ case FT_UINT32:
+ if (filedata[3] & 0x80)
+ agtwarn("File value out of range", 0);
+ *p(long) = fixsign32(filedata[0], filedata[1],
+ filedata[2], filedata[3] & 0x7F);
+ break;
+ case FT_BYTE:
+ *p(uchar) = filedata[0];
+ break;
+ case FT_CHAR:
+ *p(uchar) = trans_ascii[filedata[0]^'r'];
+ break;
+ case FT_VERSION:
+ *p(int) = agx_decode_version(fixu16(filedata[0], filedata[1]));
+ break;
+ case FT_CFG:
+ if (filedata[0] != 2 && !past_eob)
+ *p(rbool) = filedata[0];
+ break;
+ case FT_BOOL:
+ *p(rbool) = ((filedata[0] & mask) != 0);
+ if (mask == 0x80) {
+ filedata++;
+ mask = 1;
+ } else
+ mask <<= 1;
+ break;
+ case FT_DESCPTR:
+ if (skip_descr) break;
+ p(descr_ptr)->start = fixsign32(filedata[0], filedata[1],
+ filedata[2], filedata[3]);
+ p(descr_ptr)->size = fixsign32(filedata[4], filedata[5],
+ filedata[6], filedata[7]);
+ break;
+ case FT_STR: /* ss_string ptr */
+ *p(char *) = static_str + fixsign32(filedata[0], filedata[1],
+ filedata[2], filedata[3]);
+ break;
+ case FT_SLIST:
+ *p(slist) = fixsign16(filedata[0], filedata[1]);
+ break;
+ case FT_PATHARRAY: { /* integer array[13] */
+ int i;
+ for (i = 0; i < 13; i++)
+ p(integer)[i] = fixsign16(filedata[2 * i], filedata[2 * i + 1]);
+ break;
+ }
+ case FT_TLINE: { /* string of length at most 80 characters +null */
+ uchar *s;
+ int i;
+ s = (uchar *)*p(tline);
+ for (i = 0; i < 80; i++)
+ s[i] = trans_ascii[filedata[i]^'r'];
+ s[80] = 0;
+ break;
+ }
+ case FT_DICTPTR: /* ptr into dictstr */
+ *p(char *) = dictstr + fixsign32(filedata[0], filedata[1],
+ filedata[2], filedata[3]);
+ break;
+ default:
+ fatal("Unreconized field type");
+ }
+ filedata += ft_leng[rec_desc->ftype];
+ }
+}
+
+
+#define v(t) (*(t*)(rec_desc->ptr))
+/* Here is the corresponding routien for _writing_ to files */
+/* This copies the contents of a record into a buffer */
+
+static void write_filerec(const file_info *rec_desc, uchar *filedata) {
+ uchar mask;
+
+ mask = 1;
+ for (; rec_desc->ftype != FT_END; rec_desc++) {
+ if (mask != 1 && rec_desc->ftype != FT_BOOL) { /* Just finished rboolean */
+ mask = 1;
+ filedata += 1;
+ }
+ switch (rec_desc->ftype) {
+ case FT_INT16:
+ if (rec_desc->dtype == DT_LONG) {
+ filedata[0] = v(long) & 0xFF;
+ filedata[1] = (v(long) >> 8) & 0xFF;
+ } else {
+ filedata[0] = v(integer) & 0xFF;
+ filedata[1] = (v(integer) >> 8) & 0xFF;
+ }
+ break;
+ case FT_UINT16:
+ filedata[0] = v(long) & 0xFF;
+ filedata[1] = (v(long) >> 8) & 0xFF;
+ break;
+ case FT_CMDPTR: /* cmd ptr */
+ case FT_INT32:
+ case FT_UINT32:
+ filedata[0] = v(long) & 0xFF;
+ filedata[1] = (v(long) >> 8) & 0xFF;
+ filedata[2] = (v(long) >> 16) & 0xFF;
+ filedata[3] = (v(long) >> 24) & 0xFF;
+ break;
+ case FT_BYTE:
+ filedata[0] = v(uchar);
+ break;
+ case FT_CFG:
+ filedata[0] = v(uchar);
+ break;
+ case FT_CHAR:
+ filedata[0] = v(uchar)^'r';
+ break;
+ case FT_VERSION: {
+ int tver;
+ tver = agx_version[v(int)];
+ if (ver == 2 || ver == 4) tver += 1;
+ filedata[0] = tver & 0xFF;
+ filedata[1] = (tver >> 8) & 0xFF;
+ break;
+ }
+ case FT_BOOL:
+ if (mask == 1) filedata[0] = 0;
+ filedata[0] |= v(rbool) ? mask : 0;
+ if (mask == 0x80) {
+ filedata++;
+ mask = 1;
+ } else
+ mask <<= 1;
+ break;
+ case FT_DESCPTR: {
+ long i, n1, n2;
+ n1 = p(descr_ptr)->start;
+ n2 = p(descr_ptr)->size;
+ for (i = 0; i < 4; i++) {
+ filedata[i] = n1 & 0xFF;
+ filedata[i + 4] = n2 & 0xFF;
+ n1 >>= 8;
+ n2 >>= 8;
+ }
+ }
+ break;
+ case FT_STR: { /* ss_string ptr */
+ long delta;
+ delta = v(char *) - static_str;
+ filedata[0] = delta & 0xFF;
+ filedata[1] = (delta >> 8) & 0xFF;
+ filedata[2] = (delta >> 16) & 0xFF;
+ filedata[3] = (delta >> 24) & 0xFF;
+ break;
+ }
+ case FT_SLIST:
+ filedata[0] = v(slist) & 0xFF;
+ filedata[1] = (v(slist) >> 8) & 0xFF;
+ break;
+ case FT_PATHARRAY: { /* integer array[13] */
+ int i;
+ for (i = 0; i < 13; i++) {
+ filedata[2 * i] = *(p(integer) + i) & 0xFF;
+ filedata[2 * i + 1] = (*(p(integer) + i) >> 8) & 0xFF;
+ }
+ break;
+ }
+ case FT_TLINE: { /* string of length at most 80 characters +null */
+ uchar *s;
+ int i;
+ s = (uchar *)v(tline);
+ for (i = 0; i < 80; i++)
+ filedata[i] = s[i]^'r';
+ filedata[80] = 0;
+ break;
+ }
+ case FT_DICTPTR: { /* ptr into dictstr */
+ long delta;
+ delta = v(char *) - dictstr;
+ filedata[0] = delta & 0xFF;
+ filedata[1] = (delta >> 8) & 0xFF;
+ filedata[2] = (delta >> 16) & 0xFF;
+ filedata[3] = (delta >> 24) & 0xFF;
+ break;
+ }
+ default:
+ fatal("Unreconized field type");
+ }
+ filedata += ft_leng[rec_desc->ftype];
+ }
+}
+
+#undef v
+#undef p
+
+
+
+
+/* This reads in a structure array */
+/* base=the beginning of the array. If NULL, this is malloc'd and returned
+ eltsize = the size of each structure
+ numelts = the number of elements in the array
+ field_info = the arrangement of fields within the strucutre
+ rectype = string to print out for error messages
+ file_offset = the offset of the beginning of the array into the file
+ */
+void *read_recarray(void *base, long eltsize, long numelts,
+ file_info *field_info, const char *rectype,
+ long file_offset, long file_blocksize) {
+ long i;
+ file_info *curr;
+ uchar *file_data;
+
+ if (numelts == 0) return NULL;
+
+ if (int_buff)
+ set_ibuff(file_offset, compute_recsize(field_info));
+ else
+ buffreopen(file_offset, compute_recsize(field_info), numelts,
+ file_blocksize, rectype);
+
+ if (base == NULL)
+ base = rmalloc(eltsize * numelts);
+
+ for (curr = field_info; curr->ftype != FT_END; curr++)
+ if (curr->dtype != DT_DESCPTR && curr->dtype != DT_CMDPTR)
+ curr->ptr = ((char *)base + curr->offset);
+
+ for (i = 0; i < numelts; i++) {
+ if (!int_buff)
+ file_data = buffread(i);
+ else
+ file_data = get_ibuff(i);
+ read_filerec(field_info, file_data);
+ for (curr = field_info; curr->ftype != FT_END; curr++)
+ if (curr->dtype == DT_DESCPTR)
+ curr->ptr = (char *)(curr->ptr) + sizeof(descr_ptr);
+ else if (curr->dtype == DT_CMDPTR)
+ curr->ptr = (char *)(curr->ptr) + sizeof(long);
+ else
+ curr->ptr = (char *)(curr->ptr) + eltsize;
+ }
+
+ return base;
+}
+
+
+/* A NULL value means to write junk; we're just producing
+ a placeholder for systems that can't seek beyond the end-of-file */
+
+long write_recarray(void *base, long eltsize, long numelts,
+ file_info *field_info, long file_offset) {
+ long i;
+ file_info *curr;
+ uchar *file_data;
+
+ if (numelts == 0) return 0;
+
+ if (int_buff)
+ set_ibuff(file_offset, compute_recsize(field_info));
+ else
+ bw_setblock(file_offset, numelts, compute_recsize(field_info));
+
+ if (base != NULL)
+ for (curr = field_info; curr->ftype != FT_END; curr++)
+ if (curr->dtype != DT_DESCPTR && curr->dtype != DT_CMDPTR)
+ curr->ptr = ((char *)base + curr->offset);
+
+ for (i = 0; i < numelts; i++) {
+ if (int_buff)
+ file_data = get_ibuff(i);
+ else
+ file_data = bw_getbuff(i);
+ if (base != NULL) {
+ write_filerec(field_info, file_data);
+ for (curr = field_info; curr->ftype != FT_END; curr++)
+ if (curr->dtype == DT_DESCPTR)
+ curr->ptr = (char *)(curr->ptr) + sizeof(descr_ptr);
+ else if (curr->dtype == DT_CMDPTR)
+ curr->ptr = (char *)(curr->ptr) + sizeof(long);
+ else
+ curr->ptr = (char *)(curr->ptr) + eltsize;
+ }
+ }
+ return compute_recsize(field_info) * numelts;
+}
+
+
+void read_globalrec(file_info *global_info, const char *rectype,
+ long file_offset, long file_blocksize) {
+ uchar *file_data;
+
+ if (int_buff) {
+ set_ibuff(file_offset, compute_recsize(global_info));
+ file_data = get_ibuff(0);
+ } else {
+ buffreopen(file_offset, compute_recsize(global_info), 1, file_blocksize,
+ rectype);
+ file_data = buffread(0);
+ }
+ read_filerec(global_info, file_data);
+}
+
+
+long write_globalrec(file_info *global_info, long file_offset) {
+ uchar *file_data;
+
+ if (int_buff) {
+ set_ibuff(file_offset, compute_recsize(global_info));
+ file_data = get_ibuff(0);
+ } else {
+ bw_setblock(file_offset, 1, compute_recsize(global_info));
+ file_data = bw_getbuff(0);
+ }
+ write_filerec(global_info, file_data);
+ return compute_recsize(global_info);
+}
+
+
+
+static file_info fi_temp[] = {
+ {0, DT_DEFAULT, NULL, 0},
+ endrec
+};
+
+/* This routine reads in an array of simple data */
+
+void *read_recblock(void *base, int ftype, long numrec,
+ long offset, long bl_size) {
+ int dsize;
+
+ switch (ftype) {
+ case FT_CHAR:
+ case FT_BYTE:
+ if (base == NULL) base = rmalloc(numrec * sizeof(char));
+ buff_blockread(base, numrec, offset);
+ if (ftype == FT_CHAR) {
+ long i;
+ for (i = 0; i < numrec; i++)
+ ((uchar *)base)[i] = trans_ascii[((uchar *)base)[i]^'r' ];
+ }
+ return base;
+ case FT_SLIST:
+ dsize = sizeof(slist);
+ break;
+ case FT_INT16:
+ dsize = sizeof(integer);
+ break;
+ case FT_UINT16:
+ case FT_INT32:
+ dsize = sizeof(long);
+ break;
+ case FT_STR:
+ case FT_DICTPTR:
+ dsize = sizeof(char *);
+ break;
+ default:
+ fatal("Invalid argument to read_recblock.");
+ dsize = 0; /* Silence compiler warnings; this will never actually
+ be reached. */
+ }
+
+ fi_temp[0].ftype = ftype;
+ return read_recarray(base, dsize, numrec, fi_temp, "", offset, bl_size);
+}
+
+
+long write_recblock(void *base, int ftype, long numrec, long offset) {
+ int dsize;
+
+ if (numrec == 0) return 0;
+ switch (ftype) {
+ case FT_CHAR: {
+ int i;
+ for (i = 0; i < numrec; i++)
+ ((uchar *)base)[i] = ((uchar *)base)[i]^'r';
+ }
+ /* Fall through.... */
+ case FT_BYTE:
+ bw_blockwrite(base, numrec, offset);
+ return numrec;
+ case FT_SLIST:
+ dsize = sizeof(slist);
+ break;
+ case FT_INT16:
+ dsize = sizeof(integer);
+ break;
+ case FT_INT32:
+ dsize = sizeof(long);
+ break;
+ case FT_STR:
+ case FT_DICTPTR:
+ dsize = sizeof(char *);
+ break;
+ default:
+ fatal("Invalid argument to write_recblock.");
+ dsize = 0; /* Silence compiler warnings; this will never actually
+ be reached. */
+ }
+
+ fi_temp[0].ftype = ftype;
+ return write_recarray(base, dsize, numrec, fi_temp, offset);
+}
+
+char *textgets(genfile f, char *buf, size_t n) {
+ Common::ReadStream *rs = dynamic_cast<Common::ReadStream *>(f);
+ assert(rs);
+
+ size_t count = 0;
+ char c;
+
+ while (!rs->eos() && (count < (n - 1)) && (c = rs->readByte()) != '\n') {
+ buf[count] = c;
+ ++count;
+ }
+
+ buf[count] = '\0';
+ return count ? buf : nullptr;
+}
+
+char textgetc(genfile f) {
+ Common::ReadStream *rs = dynamic_cast<Common::ReadStream *>(f);
+ assert(rs);
+
+ return rs->readByte();
+}
+
+void textungetc(genfile f, char c) {
+ Common::SeekableReadStream *rs = dynamic_cast<Common::SeekableReadStream *>(f);
+ assert(rs);
+
+ rs->seek(-1, SEEK_SET);
+}
+
+bool texteof(genfile f) {
+ Common::ReadStream *rs = dynamic_cast<Common::ReadStream *>(f);
+ assert(rs);
+
+ return rs->eos();
+}
+
+void textputs(genfile f, const char *s) {
+ Common::WriteStream *ws = dynamic_cast<Common::WriteStream *>(f);
+ assert(ws);
+
+ ws->write(s, strlen(s));
+}
+
+/* ------------------------------------------------------------------- */
+/* "Profiling" functions */
+/* Routines for timing code execution */
+/* These will only work on POSIX systems */
+
+#ifdef PROFILE_SUPPORT
+
+static struct tms start;
+clock_t start_realtime;
+static struct tms delta;
+clock_t delta_realtime;
+
+void resetwatch(void) {
+ delta.tms_utime = delta.tms_stime = delta.tms_cutime = delta.tms_cstime = 0;
+ delta_realtime = 0;
+ start_realtime = times(&start);
+}
+
+void startwatch(void) {
+ start_realtime = times(&start);
+}
+
+static char watchbuff[81];
+char *timestring(void) {
+ sprintf(watchbuff, "User:%ld.%02ld Sys:%ld.%02ld Total:%ld.%02ld"
+ " Real:%ld.%02ld",
+ delta.tms_utime / 100, delta.tms_utime % 100,
+ delta.tms_stime / 100, delta.tms_stime % 100,
+ (delta.tms_utime + delta.tms_stime) / 100,
+ (delta.tms_utime + delta.tms_stime) % 100,
+ delta_realtime / 100, delta_realtime % 100
+ );
+ return watchbuff;
+}
+
+char *stopwatch(void) {
+ struct tms curr;
+
+ delta_realtime += times(&curr) - start_realtime;
+ delta.tms_utime += (curr.tms_utime - start.tms_utime);
+ delta.tms_stime += (curr.tms_stime - start.tms_stime);
+ delta.tms_cutime += (curr.tms_cutime - start.tms_cutime);
+ delta.tms_cstime += (curr.tms_cstime - start.tms_cstime);
+ return timestring();
+}
+
+/* 5+7+9+8+4*3+4*?? = 41+?? */
+
+#endif /* PROFILE_SUPPORT */
+
+} // End of namespace AGT
+} // End of namespace Glk
diff --git a/engines/glk/agt/vars.cpp b/engines/glk/agt/vars.cpp
new file mode 100644
index 0000000000..fd174182bd
--- /dev/null
+++ b/engines/glk/agt/vars.cpp
@@ -0,0 +1,209 @@
+/* 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"
+
+namespace Glk {
+namespace AGT {
+
+uchar DIAG, interp_arg, debug_da1, RAW_CMD_OUT;
+int ERR_LEVEL;
+
+rbool agx_file;
+rbool have_opt;
+rbool skip_descr;
+rbool no_auxsyn;
+rbool BATCH_MODE, make_test;
+rbool have_meta;
+rbool debug_mode, freeze_mode, milltime_mode, bold_mode;
+uchar score_mode, statusmode;
+rbool intro_first;
+rbool box_title;
+rbool mars_fix;
+rbool fix_ascii_flag;
+rbool dbg_nomsg;
+rbool irun_mode;
+rbool verboseflag;
+int font_status;
+
+rbool MASTERS_OR;
+integer FLAG_NUM, CNT_NUM, VAR_NUM;
+integer MAX_USTR;
+integer MAX_SUB;
+integer DVERB;
+integer NUM_ERR;
+integer num_rflags, num_nflags, num_cflags;
+integer num_rprops, num_nprops, num_cprops;
+integer oprop_cnt, oflag_cnt;
+integer maxroom, maxnoun, maxcreat;
+long MaxQuestion;
+integer first_room, first_noun, first_creat, last_obj;
+long last_message, last_cmd;
+long numglobal;
+long maxpict, maxpix, maxfont, maxsong;
+long num_prep;
+int num_auxcomb;
+int num_comb;
+integer exitmsg_base;
+integer start_room, treas_room, resurrect_room, max_lives;
+long max_score;
+integer startup_time, delta_time;
+int ver, aver;
+long game_sig;
+int vm_size;
+int min_ver;
+room_rec *room;
+creat_rec *creature;
+noun_rec *noun;
+cmd_rec *command;
+unsigned char *objflag;
+long *objprop;
+attrdef_rec *attrtable;
+propdef_rec *proptable;
+vardef_rec *vartable;
+flagdef_rec *flagtable;
+const char **propstr;
+long propstr_size;
+tline *userstr;
+word *sub_name;
+verbentry_rec *verbinfo;
+short *verbptr, *verbend;
+slist *synlist;
+slist *comblist;
+word *old_agt_verb;
+slist *userprep;
+word flag_noun[MAX_FLAG_NOUN], *globalnoun;
+word pix_name[MAX_PIX];
+filename *pictlist, *pixlist, *fontlist, *songlist;
+uchar opt_data[14];
+slist *auxsyn;
+slist *preplist;
+uchar *verbflag;
+slist *auxcomb;
+
+#ifdef PATH_SEP
+char **gamepath;
+#endif
+
+rbool stable_random;
+rbool DEBUG_MEM;
+rbool debug_parse;
+rbool DEBUG_EXEC_VERB;
+rbool DEBUG_DISAMBIG;
+rbool DEBUG_SMSG;
+rbool debug_disambig, debug_any;
+rbool DEBUG_OUT;
+Common::DumpFile *debugfile;
+
+rbool notify_flag, listexit_flag, menu_mode;
+rbool cmd_saveable;
+rbool can_undo;
+uchar *restart_state, *undo_state;
+char doing_restore;
+rbool do_look;
+rbool do_autoverb;
+rbool script_on;
+genfile scriptfile;
+signed char logflag;
+int logdelay;
+genfile log_in, log_out;
+rbool fast_replay;
+rbool sound_on;
+integer *pictable;
+fc_type hold_fc;
+unsigned short compass_rose;
+
+rbool quitflag, deadflag, winflag, endflag;
+rbool first_visit_flag, newlife_flag, room_firstdesc;
+rbool start_of_turn;
+rbool end_of_turn;
+rbool actor_in_scope;
+integer loc;
+integer player_contents, player_worn;
+long totwt, totsize;
+integer curr_lives;
+long tscore, old_score;
+long objscore;
+integer turncnt;
+integer curr_time;
+rbool *flag;
+short *agt_counter;
+
+#ifdef AGT_16BIT
+short *agt_var;
+#else
+long *agt_var;
+#endif
+
+long agt_number;
+rbool agt_answer;
+tline l_stat, r_stat;
+rbool nomatch_aware;
+rbool smart_look;
+int vm_width;
+menuentry *verbmenu;
+
+int vb;
+integer actor, dobj, iobj;
+parse_rec *actor_rec, *dobj_rec, *iobj_rec;
+word prep;
+parse_rec *curr_creat_rec;
+int disambig_score;
+word input[MAXINPUT];
+words in_text[MAXINPUT];
+short ip, ep;
+short ap, vp, np, pp, op;
+word ext_code[wabout + 1];
+short last_he, last_she, last_it, last_they;
+
+volatile int screen_width, status_width;
+int screen_height;
+volatile int curr_x;
+rbool par_fill_on, center_on;
+rbool textbold;
+
+uchar trans_ascii[256];
+rbool rm_acct;
+long rfree_cnt, ralloc_cnt;
+long rm_size, rm_freesize;
+words *verblist;
+
+/*-----------------------------------------------------------------*/
+
+uchar do_disambig;
+rbool beforecmd;
+rbool supress_debug;
+rbool was_metaverb;
+integer oldloc;
+integer subcall_arg;
+integer *creat_fix;
+
+/*-----------------------------------------------------------------*/
+
+rbool *dbgflagptr;
+long *dbgvarptr;
+short *dbgcntptr;
+rbool PURE_DOT;
+
+} // End of namespace AGT
+} // End of namespace Glk
diff --git a/engines/glk/module.mk b/engines/glk/module.mk
index a37bd2bcde..c7ecb6d661 100644
--- a/engines/glk/module.mk
+++ b/engines/glk/module.mk
@@ -71,8 +71,27 @@ endif
ifdef ENABLE_GLK_AGT
MODULE_OBJS += \
+ agt/agil.o \
agt/agt.o \
- agt/detection.o
+ agt/agtread.o \
+ agt/agxfile.o \
+ agt/auxfile.o \
+ agt/debugcmd.o \
+ agt/detection.o \
+ agt/disassemble.o \
+ agt/exec.o \
+ agt/filename.o \
+ agt/gamedata.o \
+ agt/interface.o \
+ agt/metacommand.o \
+ agt/object.o \
+ agt/os_glk.o \
+ agt/parser.o \
+ agt/runverb.o \
+ agt/savegame.o \
+ agt/token.o \
+ agt/util.o \
+ agt/vars.o
endif
ifdef ENABLE_GLK_ALAN2