Initial commit
This commit is contained in:
895
engines/glk/agt/agil.cpp
Normal file
895
engines/glk/agt/agil.cpp
Normal file
@@ -0,0 +1,895 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#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", "scream",
|
||||
/* 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 auxiliary 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 == nullptr) {
|
||||
writeln("");
|
||||
writeln("");
|
||||
s = formal_name(fc, fNONE);
|
||||
if (s != nullptr) {
|
||||
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] != nullptr && 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] != nullptr; 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 != nullptr) {
|
||||
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)
|
||||
Common::sprintf_s(s, 80, "AGiliTy: "
|
||||
"The (Mostly) Universal AGT Interpreter %s", version_str);
|
||||
else
|
||||
Common::sprintf_s(s, 80, "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 (nullptr != (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 (opt("stable_random")) stable_random = 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 necessary. */
|
||||
|
||||
/* Find the next unambiguous word end. This ignores possible
|
||||
word ends caused by periods. */
|
||||
for (endword = 0; lookahead[endword] != 0; endword++)
|
||||
if (isspace(lookahead[endword]) ||
|
||||
lookahead[endword] == ',' || lookahead[endword] == ';')
|
||||
break;
|
||||
|
||||
for (i = 0; i < dp; i++) {
|
||||
if (i == ext_code[wp]) continue; /* Ignore matches with the word ".". */
|
||||
|
||||
/* If it doesn't contain a '.' at the right location, there is no
|
||||
point in continuing. */
|
||||
restcnt = strlen(dict[i]);
|
||||
if (restcnt <= prevcnt || dict[i][prevcnt] != '.') continue;
|
||||
|
||||
/* Now make sure the previous characters are correct */
|
||||
if (strncasecmp(prevtext, dict[i], prevcnt) != 0) continue;
|
||||
|
||||
/* Finally, compare the trailing text. This is complicated by
|
||||
the fact that the trailing text could itself contain ambiguous '.'s */
|
||||
restcnt -= prevcnt + 1; /* Number of characters in dict entry after '.' */
|
||||
if (restcnt > endword) continue; /* Dictionary entry is longer than
|
||||
following text */
|
||||
|
||||
/* Check to see if the dictionary entry can be found in the lookahead
|
||||
buffer */
|
||||
if (strncasecmp(lookahead, dict[i] + prevcnt + 1, restcnt) != 0) continue;
|
||||
|
||||
if (restcnt == endword) return 0; /* We have a match */
|
||||
/* At this point, we know that restcnt<endword and the dictionary
|
||||
entry matches as far as restcnt. */
|
||||
/* endword ignores '.', though, so it could be we have a match
|
||||
but are missing it because it is period-terminated. Check this. */
|
||||
if (lookahead[restcnt] == '.') return 0;
|
||||
|
||||
/* Otherwise, no match... try again with the next word... */
|
||||
}
|
||||
return 1; /* No matches: treat it as punctuation. */
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
static void tokenise(char *buff)
|
||||
/* Convert input string into vocabulary codes */
|
||||
/* 0 here denotes an unrecognized word and -1 marks the end. */
|
||||
{
|
||||
int ip_, j, k;
|
||||
rbool punctuation;
|
||||
|
||||
j = 0;
|
||||
ip_ = 0;
|
||||
k = 0; /* k is the character pointer */
|
||||
for (k = 0;; k++) {
|
||||
/* If PURE_DOT is set, then there are periods in some of the dictionary
|
||||
words, so '.' could be a letter or punctuation-- we have to examine
|
||||
context to figure out which. */
|
||||
if (buff[k] == '.' && PURE_DOT)
|
||||
/* Note: check_dot is in agtdata.c, since it needs to access
|
||||
internal implementation details of the dictionary */
|
||||
punctuation = check_dot(in_text[ip_], j, buff + k + 1);
|
||||
else
|
||||
punctuation = (buff[k] == ',' || buff[k] == ';' || buff[k] == '.');
|
||||
if (buff[k] != 0 && !isspace(buff[k]) && !punctuation) {
|
||||
if (j < WORD_LENG - 1)
|
||||
in_text[ip_][j++] = buff[k];
|
||||
} else if (j > 0) { /* End of word: add it to input */
|
||||
in_text[ip_][j] = 0;
|
||||
input[ip_] = search_dict(in_text[ip_]);
|
||||
if (input[ip_] == -1) input[ip_] = 0;
|
||||
else if (input[ip_] == 0) input[ip_] = ext_code[w_any]; /* _Real_ 'ANY' */
|
||||
if (!noise_word(input[ip_])) ip_ += 1;
|
||||
/* i.e. if not one of the four ignored words, advance */
|
||||
j = 0;
|
||||
} /* If j=0 and not punct, then no new word; just skip the whitespace */
|
||||
if (punctuation) {
|
||||
in_text[ip_][0] = buff[k];
|
||||
in_text[ip_][1] = 0;
|
||||
input[ip_] = search_dict(in_text[ip_]);
|
||||
if (input[ip_] == -1) input[ip_] = 0;
|
||||
j = 0;
|
||||
ip_++;
|
||||
}
|
||||
if (ip_ >= MAXINPUT - 1) {
|
||||
writeln("Too many words in input; ignoring rest of line.");
|
||||
break;
|
||||
}
|
||||
if (buff[k] == 0) break;
|
||||
}
|
||||
input[ip_] = -1;
|
||||
in_text[ip_][0] = 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*-------------------------------------------------------------------*/
|
||||
/* Main game loop: Get player input and call the parser. */
|
||||
/*-------------------------------------------------------------------*/
|
||||
|
||||
static void game_end(void) {
|
||||
rbool done_flag;
|
||||
char *s;
|
||||
|
||||
if (winflag || deadflag) {
|
||||
writeln("");
|
||||
writeln("");
|
||||
agt_center(1);
|
||||
if (winflag)
|
||||
gen_sysmsg(148, "***** $You$ have won! *****", MSG_MAIN, nullptr);
|
||||
if (deadflag)
|
||||
gen_sysmsg(147, "***** $You$ have died! *****", MSG_MAIN, nullptr);
|
||||
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, nullptr);
|
||||
else gen_sysmsg(152, "<Sigh> $You$'ve died *again*. "
|
||||
"Would you like me to try another resurrection?",
|
||||
MSG_MAIN, nullptr);
|
||||
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, nullptr);
|
||||
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, nullptr);
|
||||
}
|
||||
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 != nullptr) writestr("restart, ");
|
||||
writestr("restore");
|
||||
if (undo_state != nullptr && can_undo)
|
||||
writestr(", undo,");
|
||||
else if (restart_state != nullptr) writestr(",");
|
||||
writestr(" or quit? ");
|
||||
s = agt_readline(5);
|
||||
if (strncasecmp(s, "RESTART", 7) == 0)
|
||||
if (restart_state != nullptr) {
|
||||
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 (g_vm->loadGame().getCode() == Common::kNoError) {
|
||||
done_flag = 1;
|
||||
} else writeln("(RESTORE failed)");
|
||||
else if (strncasecmp(s, "UNDO", 4) == 0)
|
||||
if (can_undo && undo_state != nullptr) {
|
||||
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) {
|
||||
Common::sprintf_s(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);
|
||||
if (g_vm->shouldQuit())
|
||||
return;
|
||||
|
||||
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)
|
||||
g_vm->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 == nullptr || noun[i].position[0] == '\0')
|
||||
noun[i].pos_prep = 0;
|
||||
else noun[i].pos_prep = -1;
|
||||
noun[i].pos_name = 0;
|
||||
noun[i].initpos = noun[i].position;
|
||||
}
|
||||
|
||||
nomatch_aware = 0; /* By default, not aware. */
|
||||
smart_look = 1; /* By default, LOOK <x> --> EXAMINE */
|
||||
for (i = 0; i < last_cmd; i++) {
|
||||
if (command[i].nouncmd == -1 || command[i].objcmd == -1
|
||||
|| command[i].noun_adj == -1 || command[i].obj_adj == -1
|
||||
|| command[i].prep == -1)
|
||||
nomatch_aware = 1;
|
||||
if (command[i].verbcmd == ext_code[wlook] &&
|
||||
(command[i].nouncmd > 0 || command[i].noun_adj > 0
|
||||
|| command[i].objcmd > 0 || command[i].obj_adj > 0
|
||||
|| command[i].prep > 0))
|
||||
smart_look = 0;
|
||||
}
|
||||
|
||||
pictable = (integer *)rmalloc(sizeof(int) * maxpict);
|
||||
for (i = 0; i < maxpict; i++) pictable[i] = i;
|
||||
init_state_sys(); /* Initialize the system for saving and restoring
|
||||
game states */
|
||||
tmp1 = (uchar *)rmalloc(MEM_MARGIN); /* Preserve some work space */
|
||||
|
||||
tmp2 = getstate(nullptr); /* Make sure we have space to save */
|
||||
if (tmp2 == nullptr) can_save = 0;
|
||||
else can_save = 1;
|
||||
|
||||
if (tmp2 != nullptr)
|
||||
undo_state = getstate(nullptr);
|
||||
else undo_state = nullptr;
|
||||
|
||||
if (undo_state != nullptr)
|
||||
restart_state = getstate(nullptr);
|
||||
else restart_state = nullptr;
|
||||
|
||||
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 occurrences 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++)
|
||||
Common::sprintf_s(dict[sub_name[i]], strlen(dict[sub_name[i]]) + 1, "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], '.') != nullptr && /* i.e. dict[i] contains period */
|
||||
i != ext_code[wp]) /* The period itself _is_ a dictionary word:
|
||||
avoid this false match */
|
||||
PURE_DOT = 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* This is a hack to get rid of the "What Now?" prompt. */
|
||||
static void fix_prompt(void) {
|
||||
descr_line *d;
|
||||
|
||||
if (err_ptr == nullptr) return;
|
||||
d = read_descr(err_ptr[0].start, err_ptr[0].size);
|
||||
if (d == nullptr) 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; /* Suppress 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, nullptr, 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, nullptr, 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);
|
||||
|
||||
if (have_ins) {
|
||||
do {
|
||||
writestr("Choose <I>nstructions, or <other> to start the game");
|
||||
|
||||
choice = tolower(agt_getchar()); /* Wait for keypress */
|
||||
if (g_vm->shouldQuit())
|
||||
return nullptr;
|
||||
|
||||
agt_clrscr();
|
||||
if (have_ins && choice == 'i')
|
||||
print_instructions(fc);
|
||||
} while (choice == 'i');
|
||||
}
|
||||
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 == nullptr)
|
||||
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;
|
||||
|
||||
if (!g_vm->shouldQuit())
|
||||
mainloop();
|
||||
close_game();
|
||||
|
||||
if (g_vm->shouldQuit())
|
||||
break;
|
||||
} while (doing_restore == 3);
|
||||
release_file_context(&fc);
|
||||
}
|
||||
|
||||
} // End of namespace AGT
|
||||
} // End of namespace Glk
|
||||
Reference in New Issue
Block a user