scummvm/engines/glk/agt/exec.cpp
D G Turner 93af8e33e9 GLK: AGT: Correction To Previous Fix For Temporary Variable Pointer Usage
The previous change corrected the compiler warning, but the resulting
allocated buffer was not passed out of the function. This will likely
result in a memory leak if this is not deallocated elsewhere, but this
is a better situation than a likely segfault from usage of a local
variable pointer out of scope.
2023-12-05 22:53:51 +00:00

1387 lines
37 KiB
C++

/* 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 "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;
// s is minimum 20 bytes large
if (milltime_mode)
Common::sprintf_s(s, 20, "%02d:%02d", hr, min);
else {
if (hr > 12) hr = hr - 12;
if (hr == 0) hr = 12;
Common::sprintf_s(s, 20, "%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:
Common::sprintf_s(r_stat, "Score: %ld Moves: %d", tscore, turncnt);
break;
case 1:
Common::sprintf_s(r_stat, "Score: %ld %s", tscore, timestr);
break;
case 2:
Common::sprintf_s(r_stat, "Moves: %d", turncnt);
break;
case 3:
Common::sprintf_s(r_stat, "%s", timestr);
break;
case 4:
r_stat[0] = '\0';
break; /* 'Trinity style' status line */
case 5:
Common::sprintf_s(r_stat, "Score: %ld", tscore);
break;
default:
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:
default:
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))
buff[0] = '\0';
else
Common::strcpy_s(buff, FILL_SIZE, "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 == nullptr) {
fill_buff[0] = '\0';
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) Common::sprintf_s(fill_buff, FILL_SIZE, "%ld", (long)obj_rec->num);
else fill_buff[0] = '\0';
#if 0
Common::strcpy_s(fill_buff, FILL_SIZE, "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) buff[0] = '\0';
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) {Common::strcpy_s(fill_buff,FILL_SIZE,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, '$');
Common::strcpy_s(fill_buff, FILL_SIZE, it_open(hold_val) ? "open" : "closed");
return 1;
} else if (match_str(pvarname, "ON")) {
hold_val = extract_number(pvarname, maxnoun, '$');
Common::strcpy_s(fill_buff, FILL_SIZE, it_on(hold_val) ? "on" : "off");
return 1;
} else if (match_str(pvarname, "LOCKED")) {
hold_val = extract_number(pvarname, maxnoun, '$');
Common::strcpy_s(fill_buff, FILL_SIZE, it_locked(hold_val, 0) ? "locked" : "unlocked");
return 1;
}
// WORKAROUND: $You$ substitution on win/lose message
if ((winflag || deadflag) && pronoun_mode && match_str(pvarname, "YOU$")) {
youme("I", "you");
return 1;
}
if (context == MSG_PARSE) {
/* The only special subsitution allowed is $word$. */
if (match_str(pvarname, "WORD$")) {
if (pword == nullptr) fill_buff[0] = 0;
else rstrncpy(fill_buff, pword, FILL_SIZE);
return 1;
} else return 0;
}
if (context!=MSG_MAIN) {
/* 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$") || 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 nullptr;
/* Now need to fix capitalization */
switch (capstate(start)) {
case 0:
default:
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 nullptr;
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 nullptr;
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 nullptr;
/* Now to convert hold_val into a string */
Common::sprintf_s(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 == nullptr) {
/*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 != nullptr)
for (j = 0; txt[j] != nullptr; j++)
lineout(txt[j], nl || (txt[j + 1] != nullptr), 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, nullptr);
}
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 != nullptr) {
for (len = 0; txt[len] != nullptr; len++);
qptr = (char **)rmalloc(len * sizeof(char *));
for (i = 0; i < len; i++)
qptr[i] = format_line(txt[i], MSG_DESC, nullptr);
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 == nullptr)
&& msgid != 0 && msgid <= NUM_ERR
&& err_ptr != nullptr);
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", msgid);
}
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 == nullptr) 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, nullptr);
}
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, nullptr);
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, nullptr);
}
/* -------------------------------------------------------------------- */
/* 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 separate 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 == nullptr) {
if (!PURE_ERROR)
writeln("GAME ERROR: Empty answer field.");
return 1;
}
match_mode = 0;
for (i = 0; astr[i] != nullptr; i++)
if (strstr(astr[i], "OR") != nullptr) {
match_mode = 1;
break;
}
corr = ans;
for (i = 0; astr[i] != nullptr; i++) { /* loop over all lines of the answer */
p = astr[i];
do {
q = strstr(p, "OR");
r = strstr(p, "AND");
if (q == nullptr || (r != nullptr && r < q)) q = r;
if (q == nullptr) q = p + strlen(p); /* i.e. points at the concluding null */
corr2 = match_string(corr, p, q - p);
if (corr2 == nullptr && match_mode == 0) {
free_descr(astr);
return 0;
}
if (corr2 != nullptr && 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 != nullptr) {
/* corr=strstr(ans,answer[anum]); */
corr = match_string(ans, answer[anum], strlen(answer[anum]));
rfree(ans);
if (corr == nullptr) return 0;
} else if (ans_ptr != nullptr) {
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 != nullptr)
writeln(question[qnum]);
else if (quest_ptr != nullptr)
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, nullptr);
s = agt_readline(1);
n = strtol(s, &err, 10);
if (err == s) err = nullptr;
rfree(s);
} while (!quitflag && (err == nullptr));
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)
Common::sprintf_s(s, "Your score is %ld (out of %ld possible).", tscore, max_score);
else Common::sprintf_s(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)
Common::sprintf_s(s, "You have visited %d locations (out of %d in the game)", rmcnt,
totroom);
else Common::sprintf_s(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 != nullptr && 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, nullptr);
}
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 == nullptr) {
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(nullptr);
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(nullptr);
}
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(get_random(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) {
// FIXME: Need to deallocate curr_creat_rec before assignment to new object, to avoid memory leakage?
curr_creat_rec = make_parserec(i + first_creat, nullptr); /* Used for creature messages */
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;
default:
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 = nullptr;
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 == nullptr) {
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 = nullptr;
}
else
realverb = input[vp];
runverbs(actor_, vnum, lnoun, prep_, iobj_);
if (cmd_saveable) {
if (save_lnoun != nullptr) rfree(save_lnoun);
memcpy(&save_actor, actor_, sizeof(parse_rec));
save_vnum = vnum;
save_lnoun = lnoun;
lnoun = nullptr;
save_prep = prep_;
memcpy(&save_obj, iobj_, sizeof(parse_rec));
} else
rfree(lnoun);
}
} // End of namespace AGT
} // End of namespace Glk