From 28c3584148c49c1c6f118c5cbf13d0bf53d56726 Mon Sep 17 00:00:00 2001 From: Paul Gilbert Date: Sun, 24 Nov 2019 18:15:56 -0800 Subject: GLK: AGT: Added subengine files --- engines/glk/agt/agil.cpp | 980 +++++++ engines/glk/agt/agility.h | 1223 ++++++++ engines/glk/agt/agt.cpp | 9 +- engines/glk/agt/agt.h | 6 +- engines/glk/agt/agtread.cpp | 1721 +++++++++++ engines/glk/agt/agxfile.cpp | 1483 ++++++++++ engines/glk/agt/auxfile.cpp | 596 ++++ engines/glk/agt/config.h | 289 ++ engines/glk/agt/debugcmd.cpp | 866 ++++++ engines/glk/agt/disassemble.cpp | 317 ++ engines/glk/agt/exec.cpp | 1375 +++++++++ engines/glk/agt/exec.h | 272 ++ engines/glk/agt/filename.cpp | 692 +++++ engines/glk/agt/gamedata.cpp | 1590 ++++++++++ engines/glk/agt/interface.cpp | 1159 ++++++++ engines/glk/agt/interp.h | 539 ++++ engines/glk/agt/metacommand.cpp | 1154 ++++++++ engines/glk/agt/object.cpp | 1064 +++++++ engines/glk/agt/os_glk.cpp | 6099 +++++++++++++++++++++++++++++++++++++++ engines/glk/agt/parser.cpp | 1621 +++++++++++ engines/glk/agt/runverb.cpp | 1687 +++++++++++ engines/glk/agt/savegame.cpp | 429 +++ engines/glk/agt/token.cpp | 1158 ++++++++ engines/glk/agt/util.cpp | 1489 ++++++++++ engines/glk/agt/vars.cpp | 209 ++ engines/glk/module.mk | 21 +- 26 files changed, 28045 insertions(+), 3 deletions(-) create mode 100644 engines/glk/agt/agil.cpp create mode 100644 engines/glk/agt/agility.h create mode 100644 engines/glk/agt/agtread.cpp create mode 100644 engines/glk/agt/agxfile.cpp create mode 100644 engines/glk/agt/auxfile.cpp create mode 100644 engines/glk/agt/config.h create mode 100644 engines/glk/agt/debugcmd.cpp create mode 100644 engines/glk/agt/disassemble.cpp create mode 100644 engines/glk/agt/exec.cpp create mode 100644 engines/glk/agt/exec.h create mode 100644 engines/glk/agt/filename.cpp create mode 100644 engines/glk/agt/gamedata.cpp create mode 100644 engines/glk/agt/interface.cpp create mode 100644 engines/glk/agt/interp.h create mode 100644 engines/glk/agt/metacommand.cpp create mode 100644 engines/glk/agt/object.cpp create mode 100644 engines/glk/agt/os_glk.cpp create mode 100644 engines/glk/agt/parser.cpp create mode 100644 engines/glk/agt/runverb.cpp create mode 100644 engines/glk/agt/savegame.cpp create mode 100644 engines/glk/agt/token.cpp create mode 100644 engines/glk/agt/util.cpp create mode 100644 engines/glk/agt/vars.cpp (limited to 'engines/glk') 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 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, " $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 --> 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 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 Replay your commands from a file, " + "executing one every 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 nstructions, GiliTy Information, " + "or to start the game"); + else + writestr("Choose GiliTy Information or 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 .scr + -- Automatically read/write input from .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 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 */ +#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: */ +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); + } + + /* 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 */ + + 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 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 AND 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 .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 " and "#endif" + --Various defaults + --Filename extensions + + Ideally, a port to a new platform should only need to modify this + file, the makefile, os_.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_.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. + is the terminating character; if this is 0, then + the calling routine will worry about the terminating character. + 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 + indicates who called us; this determines + what substitutions are valid. See interp.h for possible + values. Move *p*pvarname after whatever is matched. + 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 or default msg ; + A of 0 means there is no standard message counterpart. + determines what $$ substitutions are meaningful + 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 */ +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 +#include /* 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 +#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(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 */ + "=+> 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))&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: */ +{ + 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: */ + /* = (&)^ ) */ + 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_.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 +#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. (.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 .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 es or 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(""); + 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 and ANY to be + distingused) */ + +global rbool smart_look; /* If true, then LOOK will be converted + to EXAMINE. This is set automatically in agil.c, + based on whether the game file uses + LOOK 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 or default msg ; + determines what $$ substitutions are meaningful + 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_.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; +} + +/* 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) */ +/* 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 [x_obj]? */ +/* --[obj] must match up with */ +/* --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); /* */ + + 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_ 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(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 --> */ + 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, , 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 ) */ +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 AND 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 TO ... 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 TO ... construction */ + /* If found, convert to , ... 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 ON to TURN ON */ + 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 ==> */ + 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 AT to + SHOOT WITH */ + 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 into EXAMINE */ + 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 */ + 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 */ + 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 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 */ + 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 */ +/* 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(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(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(stream); + assert(ws); + return ws->write(ptr, size * nmemb); +} + +size_t ftell(genfile f) { + Common::SeekableReadStream *rs = dynamic_cast(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(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(f); + assert(rs); + + return rs->readByte(); +} + +void textungetc(genfile f, char c) { + Common::SeekableReadStream *rs = dynamic_cast(f); + assert(rs); + + rs->seek(-1, SEEK_SET); +} + +bool texteof(genfile f) { + Common::ReadStream *rs = dynamic_cast(f); + assert(rs); + + return rs->eos(); +} + +void textputs(genfile f, const char *s) { + Common::WriteStream *ws = dynamic_cast(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 -- cgit v1.2.3