/* 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 .
*
*/
#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.
is the terminating character; if this is 0, then
the calling routine will worry about the terminating character.
of 0 indicates no upper bound
*/
static int extract_number(const char **pstr, int maxval,
char term_char) {
const char *s;
long n; /* n holds the value to be returned; i holds
the number of characters parsed. */
n = 0;
s = *pstr;
while (*s == ' ' || *s == '\t') s++;
for (; *s != 0; s++) {
if (*s < '0' || *s > '9') break;
n = 10 * n + (*s - '0');
if (maxval && n > maxval) return -1;
}
if (term_char) {
if (*s == term_char) s++;
else return -1;
}
*pstr = s;
return n;
}
#define BAD_PROP (-1000)
/* This is used by #PROP[obj].[prop]# and $ATTR[obj].[attr]$, etc. */
/* isprop: Are we looking for a property (as opposed to an attribute)? */
static void extract_prop_val(const char **pstr,
int *id, int *val,
rbool isprop, const char term_char) {
const char *s;
int v; /* object number / final value */
int i; /* Attribute id */
rbool builtin; /* Expect builtin property or attribute? */
*id = i = BAD_PROP;
*val = 0; /* Failure case by default */
builtin = 0;
s = *pstr;
if (match_str(&s, "NOUN")) v = dobj;
else if (match_str(&s, "OBJECT")) v = iobj;
else v = extract_number(&s, maxcreat, 0); /* Must be object number */
while (*s == '.') {
s++;
if (*s == '-') {
builtin = 1;
s++;
} else
builtin = 0;
i = extract_number(&s, 0, 0);
if (!troom(v) && !tnoun(v) && !tcreat(v)) {
i = -1;
continue;
}
if (isprop || *s == '.') /* Treat as property */
v = builtin ? getprop(v, i) : op_objprop(2, v, i, 0);
else /* Treat as attribute */
v = builtin ? getattr(v, i) : op_objflag(2, v, i);
}
if (*s != term_char) return;
*pstr = s + 1;
if (i < 0) return;
*id = builtin ? -1 : i;
*val = v;
}
static word it_pronoun(int item, rbool ind_form)
/* Return the correct pronoun to go with item;
ind_form is 1 if we want the object form, 0 if we want the
subject form */
{
if (it_plur(item))
return (ind_form ? ext_code[wthem] : ext_code[wthey]);
if (tcreat(item))
switch (creature[item - first_creat].gender) {
case 0:
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
indicates who called us; this determines
what substitutions are valid. See interp.h for possible
values. Move *p*pvarname after whatever is matched.
contains the parse word when context is MSG_PARSE. */
/* $ forms:
$verb$, $noun$, $adjective$, $prep$, $object$, $name$,
$n_pro$, $o_pro$, $n_indir$, $o_indir$,
$name_pro$, $name_indir$
$n_is$, $o_is$, $name_is$
$c_name$
$n_was$, $o_was$, $name_was$
$the_n$, $the_o$, $the_name$
*/
/* Also $STRn$, $FLAGn$, $ONn$, $OPENn$, $LOCKEDn$ */
/* Support for FLAG, ON, OPEN, and LOCKED added by Mitch Mlinar */
/* Return 0 if no match, 1 if there is */
{
int hold_val, hold_id;
fill_buff[0] = 0; /* By default, return "\0" string */
if (match_str(pvarname, "STR")) { /* String variable */
hold_id = extract_number(pvarname, MAX_USTR, '$');
if (hold_id < 1) return 0;
rstrncpy(fill_buff, userstr[hold_id - 1], FILL_SIZE);
return 1;
} else if (match_str(pvarname, "VAR")) {
hold_id = extract_number(pvarname, VAR_NUM, '$');
if (hold_id < 0) return 0;
hold_val = agt_var[hold_id];
rstrncpy(fill_buff,
get_objattr_str(AGT_VAR, hold_id, hold_val), FILL_SIZE);
return 1;
} else if (match_str(pvarname, "FLAG")) {
hold_id = extract_number(pvarname, FLAG_NUM, '$');
if (hold_id < 0) return 0;
rstrncpy(fill_buff,
get_objattr_str(AGT_FLAG, hold_id, flag[hold_id]), FILL_SIZE);
return 1;
} else if (match_str(pvarname, "ATTR")) {
extract_prop_val(pvarname, &hold_id, &hold_val, 0, '$');
if (hold_id == BAD_PROP) return 1;
rstrncpy(fill_buff,
get_objattr_str(AGT_OBJFLAG, hold_id, hold_val), FILL_SIZE);
return 1;
} else if (match_str(pvarname, "PROP")) {
extract_prop_val(pvarname, &hold_id, &hold_val, 1, '$');
if (hold_id == BAD_PROP) return 1;
rstrncpy(fill_buff,
get_objattr_str(AGT_OBJPROP, hold_id, hold_val), FILL_SIZE);
return 1;
} else if (match_str(pvarname, "OPEN")) {
hold_val = extract_number(pvarname, maxnoun, '$');
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 or default msg ;
A of 0 means there is no standard message counterpart.
determines what $$ substitutions are meaningful
gives the $pword$ substitution for MSG_PARSE messages
msgid 3 should probably *not* be redirected to avoid giving hints to
the player as to what nouns exist in the game.
*/
{
/* Use gamefile's redefined version of message? */
rbool use_game_msg;
rbool nl; /* Should it be followed by a newline? */
nl = 1; /* By default, follow message with newline */
/* The following msgids shouldn't be followed by newlines: */
if (msgid == 1 || msgid == 145 || (msgid >= 218 && msgid <= 223)
|| msgid == 225)
nl = 0;
if (DEBUG_SMSG) rprintf("\nSTD %d", msgid);
use_game_msg = ((PURE_SYSMSG || s == 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