diff options
Diffstat (limited to 'engines/glk/agt/exec.cpp')
-rw-r--r-- | engines/glk/agt/exec.cpp | 1375 |
1 files changed, 1375 insertions, 0 deletions
diff --git a/engines/glk/agt/exec.cpp b/engines/glk/agt/exec.cpp new file mode 100644 index 0000000000..983002aa44 --- /dev/null +++ b/engines/glk/agt/exec.cpp @@ -0,0 +1,1375 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/agt/agility.h" +#include "glk/agt/interp.h" +#include "glk/agt/exec.h" + +namespace Glk { +namespace AGT { + +/* This file contains the wrapper for running player commands, + routines run at the end of the turn, and various other functions + needed by runverb.c and token.c. */ + +#define global + +static rbool pronoun_mode; + +word realverb = 0; /* Name of current verb. (normally ~= input[vp])*/ + + +/* ------------------------------------------------------------------- */ +/* High level output functions, used for printing messages, error */ +/* messages, and everything else. They call the direct output functions */ +/* in interface.c The reason they're in runverb.c is that they need to */ +/* access item info in order to fill in the blanks */ + + +/* This updates the contents of compass_rose, which can be used by the + OS layer to print out some sort of representation of which way the + player can go. */ + +static void set_compass_rose(void) { + int i, bit; + + compass_rose = 0; + if (!islit()) return; /* No compass in darkness */ + for (i = 0, bit = 1; i < 12; i++, bit <<= 1) + if (troom(room[loc].path[i])) compass_rose |= bit; +} + +static void time_out(char *s) { + int hr, min; + + hr = curr_time / 100; + min = curr_time % 100; + + if (milltime_mode) + sprintf(s, "%02d:%02d", hr, min); + else { + if (hr > 12) hr = hr - 12; + if (hr == 0) hr = 12; + sprintf(s, "%2d:%02d %s", hr, min, (curr_time >= 1200) ? "pm" : "am"); + } +} + + + + + +void set_statline() { + char timestr[20]; + + recompute_score(); + set_compass_rose(); + + rstrncpy(l_stat, room[loc].name, 81); + + time_out(timestr); + + switch (statusmode) { + case 0: + sprintf(r_stat, "Score: %ld Moves: %d", tscore, turncnt); + break; + case 1: + sprintf(r_stat, "Score: %ld %s", tscore, timestr); + break; + case 2: + sprintf(r_stat, "Moves: %d", turncnt); + break; + case 3: + sprintf(r_stat, "%s", timestr); + break; + case 4: + r_stat[0] = '\0'; + break; /* 'Trinity style' status line */ + case 5: + sprintf(r_stat, "Score: %ld", tscore); + break; + } +} + + +/* -------------------------------------------------------------------- */ +/* Message printing / $ substitution Routines */ +/* -------------------------------------------------------------------- */ + +#define FILL_SIZE 100 + + +/* Tries to convert *pstr to a number, which it returns. + If it fails, or if the number is not in the range 0..maxval, + it returns -1. + It advances *pstr to point after the number and after the + terminating character, if relevant. + <term_char> is the terminating character; if this is 0, then + the calling routine will worry about the terminating character. + <maxval> of 0 indicates no upper bound +*/ + +static int extract_number(const char **pstr, int maxval, + char term_char) { + const char *s; + long n; /* n holds the value to be returned; i holds + the number of characters parsed. */ + n = 0; + s = *pstr; + while (*s == ' ' || *s == '\t') s++; + for (; *s != 0; s++) { + if (*s < '0' || *s > '9') break; + n = 10 * n + (*s - '0'); + if (maxval && n > maxval) return -1; + } + if (term_char) { + if (*s == term_char) s++; + else return -1; + } + *pstr = s; + return n; +} + +#define BAD_PROP (-1000) + +/* This is used by #PROP[obj].[prop]# and $ATTR[obj].[attr]$, etc. */ +/* isprop: Are we looking for a property (as opposed to an attribute)? */ +static void extract_prop_val(const char **pstr, + int *id, int *val, + rbool isprop, const char term_char) { + const char *s; + int v; /* object number / final value */ + int i; /* Attribute id */ + rbool builtin; /* Expect builtin property or attribute? */ + + *id = i = BAD_PROP; + *val = 0; /* Failure case by default */ + builtin = 0; + s = *pstr; + if (match_str(&s, "NOUN")) v = dobj; + else if (match_str(&s, "OBJECT")) v = iobj; + else v = extract_number(&s, maxcreat, 0); /* Must be object number */ + while (*s == '.') { + s++; + if (*s == '-') { + builtin = 1; + s++; + } else + builtin = 0; + i = extract_number(&s, 0, 0); + if (!troom(v) && !tnoun(v) && !tcreat(v)) { + i = -1; + continue; + } + if (isprop || *s == '.') /* Treat as property */ + v = builtin ? getprop(v, i) : op_objprop(2, v, i, 0); + else /* Treat as attribute */ + v = builtin ? getattr(v, i) : op_objflag(2, v, i); + } + if (*s != term_char) return; + *pstr = s + 1; + if (i < 0) return; + *id = builtin ? -1 : i; + *val = v; +} + + + + +static word it_pronoun(int item, rbool ind_form) +/* Return the correct pronoun to go with item; + ind_form is 1 if we want the object form, 0 if we want the + subject form */ +{ + if (it_plur(item)) + return (ind_form ? ext_code[wthem] : ext_code[wthey]); + if (tcreat(item)) + switch (creature[item - first_creat].gender) { + case 0: + return ext_code[wit]; + case 1: + return (ind_form ? ext_code[wher] : ext_code[wshe]); + case 2: + return (ind_form ? ext_code[whim] : ext_code[whe]); + } + return ext_code[wit]; +} + +/* This sets the value of "The" for the given noun. */ +/* (In particular, proper nouns shouldn't have a "the") */ +static void theset(char *buff, int item) { + if (it_proper(item)) + strcpy(buff, ""); + else + strcpy(buff, "the "); +} + + +static void num_name_func(parse_rec *obj_rec, char *fill_buff, word prev_adj) +/* This is a subroutine to wordcode_match. */ +/* It gives either a noun name or a number, depending. */ +/* prev_adj is a word if this was preceded by its associated $adjective$; + the goal is to avoid having $adjective$ $noun$ expand to (e.g.) + 'silver silver' when the player types in "get silver" to pick up + a magic charm with synonym 'silver'. */ +{ + word w; + + if (obj_rec == NULL) { + strcpy(fill_buff, ""); + return; + } + + w = 0; + if (obj_rec->noun != 0) w = obj_rec->noun; + if ((w == 0 || w == prev_adj) && obj_rec->obj != 0) + w = it_name(obj_rec->obj); + + if (w == 0) { + if (obj_rec->info == D_NUM) sprintf(fill_buff, "%ld", (long)obj_rec->num); + else strcpy(fill_buff, ""); +#if 0 + strcpy(fill_buff, "that"); /* We can try and hope */ +#endif + return; + } + + if (w == prev_adj) /* ... and prev_adj!=0 but we don't need to explicity + test that since w!=0 */ + fill_buff[0] = 0; /* i.e. an empty string */ + else { + rstrncpy(fill_buff, dict[w], FILL_SIZE); + if (it_proper(obj_rec->obj)) fill_buff[0] = toupper(fill_buff[0]); + } +} + +static word get_adj(parse_rec *obj_rec, char *buff) { + word w; + + if (obj_rec->adj != 0) w = obj_rec->adj; + else w = it_adj(obj_rec->obj); + + if (w == 0) strcpy(buff, ""); + else { + rstrncpy(buff, dict[w], FILL_SIZE); + if (it_proper(obj_rec->obj)) buff[0] = toupper(buff[0]); + } + + return w; +} + + + +#define d2buff(i) {rstrncpy(fill_buff,dict[i],FILL_SIZE);return 1;} +#define num_name(obj_rec,jsa) {num_name_func(obj_rec,fill_buff,jsa);return 1;} +/* jsa= Just seen adj */ +#define youme(mestr,youstr) {strcpy(fill_buff,irun_mode?mestr:youstr);\ + return 1;} + +word just_seen_adj; /* This determines if we just saw $adjective$; if so, + this is set to it, otherwise it is zero. See + num_name_func above. */ + +static int wordcode_match(const char **pvarname, char *fill_buff, + int context, const char *pword) +/* Check <*p*pvarname> for a match; put subs text in fill_buf + <context> indicates who called us; this determines + what substitutions are valid. See interp.h for possible + values. Move *p*pvarname after whatever is matched. + <pword> contains the parse word when context is MSG_PARSE. */ +/* $ forms: + $verb$, $noun$, $adjective$, $prep$, $object$, $name$, + $n_pro$, $o_pro$, $n_indir$, $o_indir$, + $name_pro$, $name_indir$ + $n_is$, $o_is$, $name_is$ + $c_name$ + $n_was$, $o_was$, $name_was$ + $the_n$, $the_o$, $the_name$ + */ +/* Also $STRn$, $FLAGn$, $ONn$, $OPENn$, $LOCKEDn$ */ +/* Support for FLAG, ON, OPEN, and LOCKED added by Mitch Mlinar */ +/* Return 0 if no match, 1 if there is */ +{ + int hold_val, hold_id; + + fill_buff[0] = 0; /* By default, return "\0" string */ + if (match_str(pvarname, "STR")) { /* String variable */ + hold_id = extract_number(pvarname, MAX_USTR, '$'); + if (hold_id < 1) return 0; + rstrncpy(fill_buff, userstr[hold_id - 1], FILL_SIZE); + return 1; + } else if (match_str(pvarname, "VAR")) { + hold_id = extract_number(pvarname, VAR_NUM, '$'); + if (hold_id < 0) return 0; + hold_val = agt_var[hold_id]; + rstrncpy(fill_buff, + get_objattr_str(AGT_VAR, hold_id, hold_val), FILL_SIZE); + return 1; + } else if (match_str(pvarname, "FLAG")) { + hold_id = extract_number(pvarname, FLAG_NUM, '$'); + if (hold_id < 0) return 0; + rstrncpy(fill_buff, + get_objattr_str(AGT_FLAG, hold_id, flag[hold_id]), FILL_SIZE); + return 1; + } else if (match_str(pvarname, "ATTR")) { + extract_prop_val(pvarname, &hold_id, &hold_val, 0, '$'); + if (hold_id == BAD_PROP) return 1; + rstrncpy(fill_buff, + get_objattr_str(AGT_OBJFLAG, hold_id, hold_val), FILL_SIZE); + return 1; + } else if (match_str(pvarname, "PROP")) { + extract_prop_val(pvarname, &hold_id, &hold_val, 1, '$'); + if (hold_id == BAD_PROP) return 1; + rstrncpy(fill_buff, + get_objattr_str(AGT_OBJPROP, hold_id, hold_val), FILL_SIZE); + return 1; + } else if (match_str(pvarname, "OPEN")) { + hold_val = extract_number(pvarname, maxnoun, '$'); + strcpy(fill_buff, it_open(hold_val) ? "open" : "closed"); + return 1; + } else if (match_str(pvarname, "ON")) { + hold_val = extract_number(pvarname, maxnoun, '$'); + strcpy(fill_buff, it_on(hold_val) ? "on" : "off"); + return 1; + } else if (match_str(pvarname, "LOCKED")) { + hold_val = extract_number(pvarname, maxnoun, '$'); + strcpy(fill_buff, it_locked(hold_val, 0) ? "locked" : "unlocked"); + return 1; + } + + if (context == MSG_MAIN) return 0; + + if (context == MSG_PARSE) { + /* The only special subsitution allowed is $word$. */ + if (match_str(pvarname, "WORD$")) { + if (pword == NULL) fill_buff[0] = 0; + else rstrncpy(fill_buff, pword, FILL_SIZE); + return 1; + } else return 0; + } + + /* d2buff is a macro that returns 1 */ + if (match_str(pvarname, "NOUN$")) + num_name(dobj_rec, just_seen_adj); + just_seen_adj = 0; /* It doesn't matter. */ + if (match_str(pvarname, "VERB$")) + d2buff(realverb); /* auxsyn[vb][0] */ + if (match_str(pvarname, "OBJECT$")) + num_name(iobj_rec, 0); + if (match_str(pvarname, "NAME$")) + num_name(actor_rec, 0); + if (match_str(pvarname, "ADJECTIVE$")) { + just_seen_adj = get_adj(dobj_rec, fill_buff); + return 1; + } + if (match_str(pvarname, "PREP$")) + d2buff(prep); + if (match_str(pvarname, "N_PRO$")) + d2buff(it_pronoun(dobj, 0)); + if (match_str(pvarname, "O_PRO$")) + d2buff(it_pronoun(iobj, 0)); + if (match_str(pvarname, "NAME_PRO$")) + d2buff(it_pronoun(actor, 0)); + if (match_str(pvarname, "N_INDIR$")) + d2buff(it_pronoun(dobj, 1)); + if (match_str(pvarname, "O_INDIR$")) + d2buff(it_pronoun(iobj, 1)); + if (match_str(pvarname, "NAME_INDIR$")) + d2buff(it_pronoun(actor, 1)); + if (match_str(pvarname, "N_IS$")) { + if (!it_plur(dobj)) d2buff(ext_code[wis]) + else d2buff(ext_code[ware]); + } + if (match_str(pvarname, "O_IS$")) { + if (!it_plur(iobj)) d2buff(ext_code[wis]) + else d2buff(ext_code[ware]); + } + if (match_str(pvarname, "NAME_IS$")) { + if (!it_plur(actor)) d2buff(ext_code[wis]) + else d2buff(ext_code[ware]); + } + + if (match_str(pvarname, "N_WAS$")) { + if (!it_plur(dobj)) d2buff(ext_code[wwas]) + else d2buff(ext_code[wwere]); + } + if (match_str(pvarname, "O_WAS$")) { + if (!it_plur(iobj)) d2buff(ext_code[wwas]) + else d2buff(ext_code[wwere]); + } + if (match_str(pvarname, "NAME_WAS$")) { + if (!it_plur(actor)) d2buff(ext_code[wwas]) + else d2buff(ext_code[wwere]); + } + if (match_str(pvarname, "THE_N$")) { + theset(fill_buff, dobj); + return 1; + } + if (match_str(pvarname, "THE_O$")) { + theset(fill_buff, iobj); + return 1; + } + if (match_str(pvarname, "THE_NAME$")) { + theset(fill_buff, actor); + return 1; + } + if (match_str(pvarname, "THE_C$")) { + theset(fill_buff, curr_creat_rec->obj); + return 1; + } + if (match_str(pvarname, "C_NAME$")) + num_name(curr_creat_rec, 0); + if (match_str(pvarname, "TIME$")) { + time_out(fill_buff); + return 1; + } + + if (pronoun_mode && match_str(pvarname, "YOU$")) + youme("I", "you"); + if (pronoun_mode && match_str(pvarname, "ARE$")) + youme("am", "are"); + if (pronoun_mode && match_str(pvarname, "YOU_OBJ$")) + youme("me", "you"); + if (pronoun_mode && match_str(pvarname, "YOUR$")) + youme("my", "your"); + if (pronoun_mode && match_str(pvarname, "YOU'RE$")) + youme("i'm", "you're"); + return 0; /* Don't recognize $word$ */ +} + + + +static int capstate(const char *varname) { + if (islower(varname[0])) return 0; /* $word$ */ + if (islower(varname[1])) return 2; /* $Word$ */ + if (!isalpha(varname[1]) && varname[1] != 0 + && islower(varname[2])) return 2; + else return 1; /* $WORD$ */ +} + +static char fill_buff[FILL_SIZE]; /* Buffer to hold returned string */ + +static char *wordvar_match(const char **pvarname, char match_type, + int context, const char *pword) +/* Match_type=='#' for variables, '$' for parsed words */ +/* Possible # forms: #VARn#, #CNTn# */ +/* See above for $ forms */ +/* Moves *pvarname to point after matched object */ +{ + int i, hold_val, hold_prop; + const char *start; + + start = *pvarname; + if (match_type == '$') { + i = wordcode_match(pvarname, fill_buff, context, pword); + if (i == 0) return NULL; + /* Now need to fix capitalization */ + switch (capstate(start)) { + case 0: + break; /* $word$ */ + case 1: /* $WORD$ */ + for (i = 0; fill_buff[i] != '\0'; i++) + fill_buff[i] = toupper(fill_buff[i]); + break; + case 2: /* $Word$ */ + fill_buff[0] = toupper(fill_buff[0]); + break; + } + } else { /* So match type is '#' */ + if (match_str(pvarname, "VAR")) { + hold_val = extract_number(pvarname, VAR_NUM, '#'); + if (hold_val < 0) return NULL; + hold_val = agt_var[hold_val]; + } else if (match_str(pvarname, "CNT") || + match_str(pvarname, "CTR")) { + hold_val = extract_number(pvarname, CNT_NUM, '#'); + if (hold_val < 0) return NULL; + hold_val = cnt_val(agt_counter[hold_val]); + } else if (match_str(pvarname, "PROP")) { + extract_prop_val(pvarname, &hold_prop, &hold_val, 1, '#'); + if (hold_prop == BAD_PROP) hold_val = 0; + } else + return NULL; + + /* Now to convert hold_val into a string */ + sprintf(fill_buff, "%d", hold_val); + + } + return fill_buff; +} + +static char *format_line(const char *s, int context, const char *pword) +/* Do $word$ substituations; return the result */ +{ + char *t; /* The new string after all the substitutions. */ + int t_size; /* How much space has been allocated for it. */ + const char *p, *oldp; /* Pointer to the original string */ + int i; + char *fill_word, *q; /* Word used to fill in the blanks, and a pointer + used to iterate through it*/ + char fill_type; /* '#'=#variable#, '$'=$word$ */ + + /* Need to do subsitutions and also correct for tabs */ + t_size = 200; + t = (char *)rmalloc(t_size + FILL_SIZE + 10); + just_seen_adj = 0; + + /* Note that I leave some margin here: t is 310 characters, but i will never + be allowed above around 200. This is to avoid having to put special + checking code throughout the following to make sure t isn't overrun */ + for (p = s, i = 0; *p != '\0'; p++) { + if (i >= t_size) { + t_size = i + 100; + t = (char *)rrealloc(t, t_size + FILL_SIZE + 10); + } + if (!rspace(*p) && *p != '$') + just_seen_adj = 0; + if (*p == '$' || *p == '#') { + /* Read in $word$ or #var# and do substitution */ + fill_type = *p; + oldp = p++; /* Save old value in case we are wrong and then + increment p */ + fill_word = wordvar_match(&p, fill_type, context, pword); + if (fill_word == NULL) { + /*i.e. no match-- so just copy it verbatim */ + t[i++] = fill_type; + just_seen_adj = 0; + p = oldp; /* Go back and try again... */ + } else { /* Fill in word */ + p--; + if (fill_word[0] == '\0') { /* Empty string */ + /* We need to eliminate a 'double space' in this case */ + if ((oldp == s || rspace(*(oldp - 1))) && rspace(*(p + 1))) + p++; + } else /* Normal case */ + for (q = fill_word; *q != '\0';) + t[i++] = *q++; + } + } /* End $/# matcher */ + else + t[i++] = *p; + } /* End scanning loop */ + + if (aver < AGX00 && i > 0 && t[i - 1] == ' ') { + /* For pre-Magx, delete trailing spaces */ + do + i--; + while (i > 0 && t[i] == ' '); + i++; + } + t[i] = 0; + t = (char *)rrealloc(t, i + 1); + return t; +} + +void raw_lineout(const char *s, rbool do_repl, int context, const char *pword) { + char *outstr; + + if (do_repl) { + outstr = format_line(s, context, pword); + writestr(outstr); + rfree(outstr); + } else + writestr(s); +} + + +static void lineout(const char *s, rbool nl, int context, const char *pword) { + raw_lineout(s, 1, context, pword); + if (nl) writeln(""); + else writestr(" "); +} + +static void gen_print_descr(descr_ptr dp_, rbool nl, + int context, const char *pword) { + int j; + descr_line *txt; + + agt_textcolor(7); + textbold = 0; + agt_par(1); + txt = read_descr(dp_.start, dp_.size); + if (txt != NULL) + for (j = 0; txt[j] != NULL; j++) + lineout(txt[j], nl || (txt[j + 1] != NULL), context, pword); + free_descr(txt); + agt_par(0); + agt_textcolor(7); + textbold = 0; +} + +void print_descr(descr_ptr dp_, rbool nl) { + gen_print_descr(dp_, nl, MSG_DESC, NULL); +} + +void quote(int msgnum) { + char **qptr; + descr_line *txt; + int i; + int len; + + txt = read_descr(msg_ptr[msgnum - 1].start, msg_ptr[msgnum - 1].size); + if (txt != NULL) { + for (len = 0; txt[len] != NULL; len++); + qptr = (char **)rmalloc(len * sizeof(char *)); + for (i = 0; i < len; i++) + qptr[i] = format_line(txt[i], MSG_DESC, NULL); + free_descr(txt); + textbox(qptr, len, TB_BORDER | TB_CENTER); + rfree(qptr); + } +} + + +void msgout(int msgnum, rbool add_nl) { + print_descr(msg_ptr[msgnum - 1], add_nl); +} + + +#define MAX_NUM_ERR 240 /* Highest numbered STANDARD message */ +#define OLD_MAX_STD_MSG 185 + +/* Fallback messages should always have msgid less than the original */ +int stdmsg_fallback[MAX_NUM_ERR - OLD_MAX_STD_MSG] = { + 0, 0, 0, 12, 0, /* 186 - 190 */ + 0, 0, 0, 0, 0, /* 191 - 195 */ + 0, 13, 13, 5, 10, /* 196 - 200 */ + 10, 61, 10, 16, 59, /* 201 - 205 */ + 90, 107, 116, 135, 140, /* 206 - 210 */ + 184, 3, 47, 185, 61, /* 211 - 215 */ + 0, 0, 0, 0, 0, /* 216 - 220 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 221 - 230 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 /* 231 - 240 */ +}; + +void gen_sysmsg(int msgid, const char *s, int context, const char *pword) +/* Prints either STANDARD message number <msgid> or default msg <s>; + A <msgid> of 0 means there is no standard message counterpart. + <context> determines what $$ substitutions are meaningful + <parseword> gives the $pword$ substitution for MSG_PARSE messages + msgid 3 should probably *not* be redirected to avoid giving hints to + the player as to what nouns exist in the game. +*/ +{ + /* Use gamefile's redefined version of message? */ + rbool use_game_msg; + rbool nl; /* Should it be followed by a newline? */ + + nl = 1; /* By default, follow message with newline */ + + /* The following msgids shouldn't be followed by newlines: */ + if (msgid == 1 || msgid == 145 || (msgid >= 218 && msgid <= 223) + || msgid == 225) + nl = 0; + + if (DEBUG_SMSG) rprintf("\nSTD %d", msgid); + + use_game_msg = ((PURE_SYSMSG || s == NULL) + && msgid != 0 && msgid <= NUM_ERR + && err_ptr != NULL); + + if (use_game_msg) { + /* Check for fall-back messages */ + if (err_ptr[msgid - 1].size <= 0 + && msgid > OLD_MAX_STD_MSG && msgid <= MAX_NUM_ERR) { + msgid = stdmsg_fallback[msgid - OLD_MAX_STD_MSG - 1]; + if (DEBUG_SMSG) rprintf("==> %3d"); + } + if (msgid != 0 && err_ptr[msgid - 1].size > 0) { + if (DEBUG_SMSG) rprintf(" : From gamefile\n"); + gen_print_descr(err_ptr[msgid - 1], nl, context, pword); + } else use_game_msg = 0; + } + + if (DEBUG_SMSG && !use_game_msg) rprintf(" : Default\n"); + + if (!use_game_msg) { + /* Either the game doesn't redefine the message, or we're ignoring + redefinitions */ + if (s == NULL) return; + pronoun_mode = 1; + lineout(s, nl, context, pword); + pronoun_mode = !PURE_PROSUB; + } +} + + +void sysmsg(int msgid, const char *s) { + gen_sysmsg(msgid, s, MSG_RUN, NULL); +} + + +void alt_sysmsg(int msgid, const char *s, parse_rec *new_dobjrec, parse_rec *new_iobjrec) { + parse_rec *save_dobjrec, *save_iobjrec; + integer save_dobj, save_iobj; + + save_dobj = dobj; + save_dobjrec = dobj_rec; + dobj = p_obj(new_dobjrec); + dobj_rec = new_dobjrec; + + save_iobj = iobj; + save_iobjrec = iobj_rec; + iobj = p_obj(new_iobjrec); + iobj_rec = new_iobjrec; + + gen_sysmsg(msgid, s, MSG_RUN, NULL); + + dobj = save_dobj; + dobj_rec = save_dobjrec; + iobj = save_iobj; + iobj_rec = save_iobjrec; +} + + +void sysmsgd(int msgid, const char *s, parse_rec *new_dobjrec) +/* Front end for sysmsg w/alternative direct object */ +{ + alt_sysmsg(msgid, s, new_dobjrec, NULL); +} + + +/* -------------------------------------------------------------------- */ +/* QUESTION and ANSWER processing */ +/* -------------------------------------------------------------------- */ + + +static char *match_string(char *ans, char *corr_ans, int n) +/* Searches for s (w/ surrounding whitespace removed) inside ans */ +/* looking at only n characters of s */ +{ + char *s; + char *corr; + int i; + + s = rstrdup(corr_ans); + for (i = n - 1; i > 0 && isspace(s[i]); i--); /* Kill trailing whitespace */ + s[i + 1] = 0; + for (i = 0; s[i] != 0; i++) s[i] = tolower(s[i]); + for (i = 0; isspace(s[i]); i++); /* Kill leading whitespace */ + corr = strstr(ans, s + i); + rfree(s); + return corr; +} + + +static rbool check_answer(char *ans, long start, long size) +/* qnum has already been fixed to start from 0 */ +/* Master's edition answer checker. Master's edition answers can */ +/* be seperate by AND and OR characters. If there is one OR in the */ +/* answer, all ANDs will also be treated as ORs */ +/* Furthermore, AND-delimited strings must appear in the correct order */ +/* unless PURE_ANSWER is false */ +{ + char *corr, *corr2; /* Pointer into answer to match correct answers */ + int match_mode; /* 0=AND mode, 1=OR mode */ + descr_line *astr; /* Holds the answer string */ + int i; /* Index to line of astr we're on. */ + char *p, *q, *r; /* Used to break astr up into pieces and + loop over them */ + + astr = read_descr(start, size); + if (astr == NULL) { + if (!PURE_ERROR) + writeln("GAME ERROR: Empty answer field."); + return 1; + } + + match_mode = 0; + for (i = 0; astr[i] != NULL; i++) + if (strstr(astr[i], "OR") != NULL) { + match_mode = 1; + break; + } + + corr = ans; + for (i = 0; astr[i] != NULL; i++) { /* loop over all lines of the answer */ + p = astr[i]; + do { + q = strstr(p, "OR"); + r = strstr(p, "AND"); + if (q == NULL || (r != NULL && r < q)) q = r; + if (q == NULL) q = p + strlen(p); /* i.e. points at the concluding null */ + corr2 = match_string(corr, p, q - p); + if (corr2 == NULL && match_mode == 0) { + free_descr(astr); + return 0; + } + if (corr2 != NULL && match_mode == 1) { + free_descr(astr); + return 1; + } + if (PURE_ANSWER && match_mode == 0) corr = corr2; + if (*q == 'O') p = q + 2; + else if (*q == 'A') p = q + 3; + else assert(*q == 0); + } while (*q != 0); + } + free_descr(astr); + if (match_mode == 0) return 1; /* AND: Matched them all */ + else return 0; /* OR: Didn't find a single match */ +} + + +/* Does the answer in string ans match answer anum? */ +/* Warning: this changes and then rfrees ans */ +rbool match_answer(char *ans, int anum) { + char *corr; + rbool ans_corr; + + for (corr = ans; *corr != 0; corr++) + *corr = tolower(*corr); + if (answer != NULL) { + /* corr=strstr(ans,answer[anum]); */ + corr = match_string(ans, answer[anum], strlen(answer[anum])); + rfree(ans); + if (corr == NULL) return 0; + } else if (ans_ptr != NULL) { + ans_corr = check_answer(ans, ans_ptr[anum].start, ans_ptr[anum].size); + rfree(ans); + return ans_corr; + } else writeln("INT ERR: Invalid answer pointer."); + return 1; + +} + + +rbool ask_question(int qnum) +/* 1=got it right, 0=got it wrong */ +{ + char *ans; + + qnum--; + + /* Now actually ask the question and compare the answers */ + if (question != NULL) + writeln(question[qnum]); + else if (quest_ptr != NULL) + print_descr(quest_ptr[qnum], 1); + else { + writeln("INT ERR: Invalid question pointer"); + return 1; + } + ans = agt_readline(2); + return match_answer(ans, qnum); +} + + +/* -------------------------------------------------------------------- */ +/* Miscellaneous support routines */ +/* -------------------------------------------------------------------- */ + +long read_number(void) { + char *s, *err; + long n; + + n = 1; + do { + if (n != 1) gen_sysmsg(218, "Please enter a *number*. ", MSG_MAIN, NULL); + s = agt_readline(1); + n = strtol(s, &err, 10); + if (err == s) err = NULL; + rfree(s); + } while (err == NULL); + return n; +} + + + + +void runptr(int i, descr_ptr dp_[], const char *msg, int msgid, + parse_rec *nounrec, parse_rec *objrec) +/* Prints out description unless it doesn't exist, in which + case it prints out either system message #msgid or the message + contained in msg. */ +{ + if (dp_[i].size > 0) print_descr(dp_[i], 1); + else alt_sysmsg(msgid, msg, nounrec, objrec); +} + + + +/* Score modes: + S:Score, R:Room +=list '(out of..'), -=don't list at all. + 0-- S+ R+ + 1-- S+ R + 2-- S R+ + 3-- S R + 4-- S+ R- + 5-- S R- + 6-- S- R+ + 7-- S- R + 8-- S- R- and disable SCORE. + */ + + +void print_score(void) { + char s[80]; + int i, rmcnt, totroom; + + if (score_mode < 5) { + if (score_mode == 0 || score_mode == 1 || score_mode == 4) + sprintf(s, "Your score is %ld (out of %ld possible).", tscore, max_score); + else sprintf(s, "Your score is %ld.", tscore); + writeln(s); + } + + if (score_mode < 4 || score_mode == 6 || score_mode == 7) { + rmcnt = 0; + totroom = 0; + for (i = 0; i <= maxroom - first_room; i++) + if (!room[i].unused) { + if (room[i].seen) rmcnt++; + /* Should really compute this once at the beginning, but */ + /* I don't want to add yet another global variable, particulary */ + /* since this is only used here. */ + totroom++; + } + if (score_mode % 2 == 0) + sprintf(s, "You have visited %d locations (out of %d in the game)", rmcnt, + totroom); + else sprintf(s, "You have visited %d locations.", rmcnt); + writeln(s); + } +} + + +int normalize_time(int tnum) { /* Convert hhmm so mm<60 */ + int min, hr; + + min = tnum % 100; /* The minutes */ + hr = tnum / 100; /* The hours */ + hr += min / 60; + min = min % 60; + while (hr < 0) hr += 24; + hr = hr % 24; + return hr * 100 + min; +} + + +void add_time(int dt) { + int min, hr; + + min = curr_time % 100; /* The minutes */ + hr = curr_time / 100; /* The hours */ + if (aver == AGT183) min += dt; /* AGT 1.83 version */ + else { /* Normal version */ + min += dt % 100; + hr += dt / 100; + } + while (min < 0) { + min = min + 60; + hr++; + } + hr += min / 60; + min = min % 60; + while (hr < 0) hr += 24; + hr = hr % 24; + curr_time = hr * 100 + min; +} + + +void look_room(void) { + compute_seen(); + writeln(""); + if (islit()) { + if (room[loc].name != NULL && room[loc].name[0] != 0 && + (!PURE_ROOMTITLE)) { + agt_textcolor(-1); /* Emphasized text on */ + writestr(room[loc].name); + agt_textcolor(-2); + writeln(""); + } /* Emphasized text off */ + if (room_firstdesc && room[loc].initdesc != 0) + msgout(room[loc].initdesc, 1); + else if (room_ptr[loc].size > 0) + print_descr(room_ptr[loc], 1); + print_contents(loc + first_room, 1); + if (listexit_flag) + v_listexit(); + } else + sysmsg(room[loc].light == 1 ? 6 : 7, + "It is dark. $You$ can't see anything."); + room_firstdesc = 0; + do_look = 0; +} + + +static void run_autoverb(void) { + int v0; /* Will hold the verb number of the autoverb */ + int savevb; + integer saveactor, savedobj, saveiobj; + parse_rec *save_actor_rec, *save_dobj_rec, *save_iobj_rec; + word saveprep; + + + beforecmd = 1; + + /* This is the penalty for vb, actor, etc being global variables. */ + savevb = vb; + saveactor = actor; + savedobj = dobj; + saveprep = prep; + saveiobj = iobj; + save_actor_rec = copy_parserec(actor_rec); + save_dobj_rec = copy_parserec(dobj_rec); + save_iobj_rec = copy_parserec(iobj_rec); + + if (room[loc].autoverb != 0) { + v0 = verb_code(room[loc].autoverb); + (void)scan_metacommand(0, v0, 0, 0, 0, NULL); + } + free_all_parserec(); + vb = savevb; + actor = saveactor; + dobj = savedobj; + iobj = saveiobj; + actor_rec = save_actor_rec; + dobj_rec = save_dobj_rec; + iobj_rec = save_iobj_rec; + prep = saveprep; +} + + + +/* ------------------------------------------------------------------- */ +/* MAIN COMMAND EXECUTION ROUTINES-- */ +/* These routines handle the execution of player commands */ +/* Then they change the status line, update counters, etc. */ +/* ------------------------------------------------------------------- */ + +static void creat_initdesc(void) { + int i; + + creatloop(i) + if (creature[i].location == loc + first_room && + creature[i].initdesc != 0) { + msgout(creature[i].initdesc, 1); + creature[i].initdesc = 0; + } +} + +/* Print out picture names, remember to put intro before first one. */ +/* This should be called with s==NULL before and after: + before to reset it, after to put the trailing newline on. */ +void listpictname(const char *s) { + static rbool first_pict = 1; /* True until we output first picture */ + + if (s == NULL) { + if (!first_pict) writeln(""); /* Trailing newline */ + first_pict = 1; + return; + } + if (first_pict) { + writeln(""); /* Skip a line */ + sysmsg(219, " Illustrations:"); + first_pict = 0; + } + writestr(" "); + writestr(s); +} + + +void listpict(int obj) { + char *s; + + if (it_pict(obj) != 0) { + s = objname(obj); + listpictname(s); + rfree(s); + } +} + + +void list_viewable(void) +/* List pictures that can be viewed, if any */ +{ + int i; + + listpictname(NULL); + + if (room[loc].pict != 0) + listpictname("scene"); + contloop(i, 1) + listpict(i); + contloop(i, 1000) + listpict(i); + contloop(i, loc + first_room) + listpict(i); + + for (i = 0; i < maxpix; i++) + if (room[loc].PIX_bits & (1L << i)) + listpictname(dict[pix_name[i]]); + listpictname(NULL); +} + + + +void newroom(void) { + rbool save_do_look; + integer prevloc; + + do { + save_do_look = do_look; + if (do_look == 1) look_room(); + creat_initdesc(); + if (save_do_look == 1 && aver >= AGTME10) + list_viewable(); /* Print out picts that can be viewed here. */ + do_look = 0; + + prevloc = loc; + if (do_autoverb) { + do_autoverb = 0; + run_autoverb(); + } + + if (!room[loc].seen) { /* This only runs on the first turn */ + room[loc].seen = 1; + tscore += room[loc].points; + } + } while (prevloc != loc); /* Autoverb could move player */ +} + + +static int min_delta(void) { + return (aver == AGT183) ? 1 : 0 ; +} + + +void increment_turn(void) { + int i; + + compute_seen(); + + newlife_flag = 0; + + if (quitflag) return; + + newroom(); + + if (winflag || deadflag || endflag) return; + + if (was_metaverb) return; /* No time should pass during a metaverb. */ + + turncnt++; + /* Now increment the time counter */ + if (delta_time > 0) { + if (PURE_TIME) + add_time(agt_rand(min_delta(), delta_time)); + else /* if !PURE_TIME */ + add_time(delta_time); + } + + for (i = 0; i <= CNT_NUM; i++) + if (agt_counter[i] >= 0) ++agt_counter[i]; + creatloop(i) + if (creature[i].location == loc + first_room && creature[i].hostile && + creature[i].timethresh > 0) { + parse_rec tmpcreat; /* Used for creature messages */ + make_parserec(i + first_creat, &tmpcreat); + curr_creat_rec = &tmpcreat; + + if (++creature[i].timecounter >= creature[i].timethresh) { + /* Creature attacks */ + sysmsg(16, "$The_c$$c_name$ suddenly attacks $you_obj$!"); + sysmsg(creature[i].gender == 0 ? 17 : 18, + " $You$ try to defend $your$self, but $the_c$$c_name$ " + "kills $you_obj$ anyhow."); + deadflag = 1; + } else /* 'Angrier' messages */ + if (creature[i].timethresh > 0 && + creature[i].timecounter > creature[i].timethresh - 3) + sysmsg(15, "$The_c$$c_name$ seems to be getting angrier."); + } +} + + +/* Wrapper for increment_turn used by exec routines below. + This just checks to make sure we're not one of the 1.8x versions + (which call increment turn from elsewhere) */ +static void exec_increment_turn(void) { + if (PURE_AFTER) increment_turn(); +} + +static void end_turn(void) { + if (textbold) agt_textcolor(-2); + textbold = 0; + set_statline(); + + if (quitflag) return; + + if (notify_flag && !was_metaverb) { + if (old_score < tscore) + sysmsg(227, " [Your score just went up]"); + else if (old_score > tscore) + sysmsg(228, " [Your score just went down]"); + } + old_score = tscore; + +} + + + +static void set_pronoun(int item) { + if (item == 0) return; + switch (it_gender(item)) { + case 0: + if (it_plur(item)) + last_they = item; + last_it = item; /* Change: last_it will be set even if the + noun is plural */ + break; + case 1: + last_she = item; + break; + case 2: + last_he = item; + break; + } +} + + +/* True if the current noun is the last one in the list. */ +static rbool lastnoun(parse_rec *list) { + if (list->info == D_END) return 1; + list++; + while (list->info == D_AND) list++; + return (list->info == D_END); +} + + + + +static void runverbs(parse_rec *actor0, int vnum, + parse_rec *lnoun, word prep0, parse_rec *iobj0) +/* The zeros are postpended simply to avoid a name conflict */ +{ + parse_rec *currnoun; + + textbold = 0; + do_look = 0; + do_autoverb = 0; + was_metaverb = 0; + actor = actor0->obj; + actor_rec = copy_parserec(actor0); + vb = vnum; + dobj = lnoun[0].obj; + dobj_rec = copy_parserec(lnoun); + prep = prep0; + iobj = iobj0->obj; + iobj_rec = copy_parserec(iobj0); + set_pronoun(actor); /* Basically the last one that isn't 0 will stick */ + set_pronoun(iobj0->obj); + was_metaverb = 0; /* Most verbs are not metaverbs; assume this by default */ + start_of_turn = 1; + end_of_turn = 0; + + if (lnoun[0].info == D_END || lnoun[0].info == D_ALL) { + end_of_turn = 1; + exec_verb(); + if (doing_restore) { + free_all_parserec(); + return; + } + if (PURE_AND) exec_increment_turn(); + } else for (currnoun = lnoun; currnoun->info != D_END; currnoun++) + if (currnoun->info != D_AND) { + free_all_parserec(); + end_of_turn = lastnoun(currnoun); + actor = actor0->obj; + actor_rec = copy_parserec(actor0); + vb = vnum; + dobj = currnoun->obj; + dobj_rec = copy_parserec(currnoun); + iobj = iobj0->obj; + iobj_rec = copy_parserec(iobj0); + set_pronoun(dobj); + exec_verb(); + if (doing_restore) return; + if (PURE_AND) + exec_increment_turn(); + else + start_of_turn = 0; + if (quitflag || winflag || deadflag || endflag) + break; + } + assert(end_of_turn); + if (!PURE_AND) exec_increment_turn(); + end_turn(); + free_all_parserec(); +} + + +/* The following store values for use by AGAIN */ +/* (so AGAIN can be implemented just by executing runverbs w/ the saved + values) */ +static int save_vnum; +static word save_prep; +static parse_rec save_actor; +static parse_rec save_obj; +parse_rec *save_lnoun = NULL; + + + +void exec(parse_rec *actor_, int vnum, + parse_rec *lnoun, word prep_, parse_rec *iobj_) { + + cmd_saveable = 0; + pronoun_mode = !PURE_PROSUB; + + if (vnum == verb_code(ext_code[wagain]) && lnoun[0].info == D_END + && iobj_->info == D_END && + (actor_->info == D_END || actor_->obj == save_actor.obj)) + if (save_lnoun == NULL) { + rfree(lnoun); + sysmsg(186, + "You can't use AGAIN until you've entered at least one command."); + return; + } else { + memcpy(actor_, &save_actor, sizeof(parse_rec)); + vnum = save_vnum; + prep_ = save_prep; + memcpy(iobj_, &save_obj, sizeof(parse_rec)); + rfree(lnoun); + lnoun = save_lnoun; + save_lnoun = NULL; + } + else + realverb = input[vp]; + + + runverbs(actor_, vnum, lnoun, prep_, iobj_); + + if (cmd_saveable) { + if (save_lnoun != NULL) rfree(save_lnoun); + + memcpy(&save_actor, actor_, sizeof(parse_rec)); + save_vnum = vnum; + save_lnoun = lnoun; + lnoun = NULL; + save_prep = prep_; + memcpy(&save_obj, iobj_, sizeof(parse_rec)); + } else + rfree(lnoun); +} + +} // End of namespace AGT +} // End of namespace Glk |