mirror of
https://github.com/libretro/scummvm.git
synced 2025-02-24 05:01:43 +00:00

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.
1387 lines
37 KiB
C++
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
|