1418 lines
32 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/>.
*
*/
#define V27COMPATIBLE
#include "glk/alan2/alan2.h"
#include "glk/alan2/alan_version.h"
#include "glk/alan2/args.h"
#include "glk/alan2/debug.h"
#include "glk/alan2/exe.h"
#include "glk/alan2/glkio.h"
#include "glk/alan2/inter.h"
#include "glk/alan2/main.h"
#include "glk/alan2/parse.h"
#include "glk/alan2/reverse.h"
#include "glk/alan2/rules.h"
#include "glk/alan2/stack.h"
#include "glk/alan2/sysdep.h"
#include "glk/alan2/types.h"
#include "common/file.h"
namespace Glk {
namespace Alan2 {
/* PUBLIC DATA */
/* The Amachine memory */
Aword *memory;
//static AcdHdr dummyHeader; // Dummy to use until memory allocated
AcdHdr *header;
Aaddr memTop; // Top of load memory
int conjWord; // First conjunction in dictonary, for ','
/* Amachine variables */
CurVars cur;
/* Amachine structures */
WrdElem *dict; /* Dictionary pointer */
ActElem *acts; /* Actor table pointer */
LocElem *locs; /* Location table pointer */
VrbElem *vrbs; /* Verb table pointer */
StxElem *stxs; /* Syntax table pointer */
ObjElem *objs; /* Object table pointer */
CntElem *cnts; /* Container table pointer */
RulElem *ruls; /* Rule table pointer */
EvtElem *evts; /* Event table pointer */
MsgElem *msgs; /* Message table pointer */
Aword *scores; /* Score table pointer */
Aword *freq; /* Cumulative character frequencies */
int dictsize;
Boolean verbose = FALSE;
Boolean errflg = TRUE;
Boolean trcflg = FALSE;
Boolean dbgflg = FALSE;
Boolean stpflg = FALSE;
Boolean logflg = FALSE;
Boolean statusflg = TRUE;
Boolean fail = FALSE;
Boolean anyOutput = FALSE;
/* The files and filenames */
const char *advnam;
Common::File *txtfil;
Common::WriteStream *logfil;
/* Screen formatting info */
int col, lin;
Boolean needsp = FALSE;
Boolean skipsp = FALSE;
/*======================================================================
terminate()
Terminate the execution of the adventure, e.g. close windows,
return buffers...
*/
void terminate(CONTEXT, int code) {
newline();
g_vm->glk_exit();
LONG_JUMP
}
/*======================================================================
usage()
*/
void usage() {
printf("Usage:\n\n");
printf(" %s [<switches>] <adventure>\n\n", PROGNAME);
printf("where the possible optional switches are:\n");
g_vm->glk_set_style(style_Preformatted);
printf(" -v verbose mode\n");
printf(" -l log player commands and game output to a file\n");
printf(" -i ignore version and checksum errors\n");
printf(" -n no Status Line\n");
printf(" -d enter debug mode\n");
printf(" -t trace game execution\n");
printf(" -s single instruction trace\n");
g_vm->glk_set_style(style_Normal);
}
/*======================================================================
syserr()
Print a little text blaming the user for the system error.
*/
void syserr(const char *str) {
::error("%s", str);
}
/*======================================================================
error()
Print an error message, force new player input and abort.
*/
/* IN - The error message number */
void error(CONTEXT, MsgKind msgno) {
if (msgno != MSGMAX)
prmsg(msgno);
wrds[wrdidx] = EOD; /* Force new player input */
dscrstkp = 0; /* Reset describe stack */
LONG_JUMP
}
/*======================================================================
statusline()
Print the the status line on the top of the screen.
*/
void statusline() {
uint glkWidth;
char line[100];
int pcol = col;
uint i;
if (nullptr == glkStatusWin)
return;
g_vm->glk_set_window(glkStatusWin);
g_vm->glk_window_clear(glkStatusWin);
g_vm->glk_window_get_size(glkStatusWin, &glkWidth, nullptr);
g_vm->glk_set_style(style_User1);
for (i = 0; i < glkWidth; i++)
g_vm->glk_put_char(' ');
col = 1;
g_vm->glk_window_move_cursor(glkStatusWin, 1, 0);
needsp = FALSE;
say(where(HERO));
if (header->maxscore > 0)
sprintf(line, "Score %d(%d)/%d moves", cur.score, (int)header->maxscore, cur.tick);
else
sprintf(line, "%d moves", cur.tick);
g_vm->glk_window_move_cursor(glkStatusWin, glkWidth - col - strlen(line), 0);
printf(line);
needsp = FALSE;
col = pcol;
g_vm->glk_set_window(glkMainWin);
}
/*======================================================================
logprint()
Print some text and log it if logging is on.
*/
static void logprint(const char str[]) {
printf(str);
if (logflg)
fprintf(logfil, "%s", str);
}
/*======================================================================
newline()
Make a newline, but check for screen full.
*/
void newline() {
g_vm->glk_put_char('\n');
}
/*======================================================================
para()
Make a new paragraph, i.e one empty line (one or two newlines).
*/
void para() {
if (col != 1)
newline();
newline();
}
/*======================================================================
clear()
Clear the screen.
*/
void clear() {
g_vm->glk_window_clear(glkMainWin);
}
/*======================================================================
allocate()
Safely allocate new memory.
*/
void *allocate(unsigned long len /* IN - Length to allocate */) {
void *p = (void *)malloc((size_t)len);
if (p == nullptr)
syserr("Out of memory.");
return p;
}
/*----------------------------------------------------------------------
just()
Justify a string so that it wraps at end of screen.
*/
static void just(const char str[]) {
logprint(str);
}
/*----------------------------------------------------------------------
space()
Output a space if needed.
*/
static void space() {
if (skipsp)
skipsp = FALSE;
else {
if (needsp) {
logprint(" ");
col++;
}
}
needsp = FALSE;
}
/*----------------------------------------------------------------------
sayparam()
A parameter needs to be said, check for words the player used and use
them if possible.
*/
static void sayparam(int p) {
for (int i = 0; i <= p; i++) {
if (params[i].code == EOD)
syserr("Nonexistent parameter referenced.");
}
// Any words he used?
if (params[p].firstWord == EOD) {
say(params[p].code);
} else {
// Yes, so use them...
for (uint i = params[p].firstWord; i <= params[p].lastWord; i++) {
just((char *)addrTo(dict[wrds[i]].wrd));
if (i < params[p].lastWord)
just(" ");
}
}
}
/*----------------------------------------------------------------------
prsym()
Print an expanded symbolic reference.
N = newline
I = indent on a new line
P = new paragraph
L = current location name
O = current object -> first parameter!
V = current verb
A = current actor
T = tabulation
$ = no space needed after this
*/
static void prsym(
char *str /* IN - The string starting with '$' */
) {
switch (toLower(str[1])) {
case 'n':
newline();
needsp = FALSE;
break;
case 'i':
newline();
logprint(" ");
col = 5;
needsp = FALSE;
break;
case 'o':
sayparam(0);
needsp = TRUE; /* We did print something non-white */
break;
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
sayparam(str[1] - '1');
needsp = TRUE; /* We did print something non-white */
break;
case 'l':
say(cur.loc);
needsp = TRUE; /* We did print something non-white */
break;
case 'a':
say(cur.act);
needsp = TRUE; /* We did print something non-white */
break;
case 'v':
just((char *)addrTo(dict[vrbwrd].wrd));
needsp = TRUE; /* We did print something non-white */
break;
case 'p':
para();
needsp = FALSE;
break;
case 't': {
int i;
int spaces = 4 - (col - 1) % 4;
for (i = 0; i < spaces; i++) logprint(" ");
col = col + spaces;
needsp = FALSE;
break;
}
case '$':
skipsp = TRUE;
break;
default:
logprint("$");
break;
}
}
/*======================================================================
output()
Output a string to suit the screen. Any symbolic inserts ('$') are
recogniced and performed.
*/
void output(const char original[]) {
char ch;
char *str, *copy;
char *symptr;
copy = scumm_strdup(original);
str = copy;
if (str[0] != '$' || str[1] != '$')
space(); /* Output space if needed (& not inhibited) */
while ((symptr = strchr(str, '$')) != (char *) nullptr) {
ch = *symptr; /* Terminate before symbol */
*symptr = '\0';
if (strlen(str) > 0) {
just(str); /* Output part before '$' */
if (str[strlen(str) - 1] == ' ')
needsp = FALSE;
}
*symptr = ch; /* restore '$' */
prsym(symptr); /* Print the symbolic reference */
str = &symptr[2]; /* Advance to after symbol and continue */
}
if (str[0] != 0) {
just(str); /* Output trailing part */
skipsp = FALSE;
if (str[strlen(str) - 1] != ' ')
needsp = TRUE;
}
anyOutput = TRUE;
free(copy);
}
/*======================================================================
prmsg()
Print a message from the message table.
*/
void prmsg(MsgKind msg /* IN - message number */) {
interpret(msgs[msg].stms);
}
/*----------------------------------------------------------------------*\
Various check functions
endOfTable()
isObj, isLoc, isAct, IsCnt & isNum
\*----------------------------------------------------------------------*/
/* How to know we are at end of a table */
Boolean eot(const void *adr) {
const byte *v = (const byte *)adr;
return v[0] == 0xff && v[1] == 0xff &&
v[2] == 0xff && v[3] == 0xff;
}
Boolean isObj(Aword x) {
return x >= OBJMIN && x <= OBJMAX;
}
Boolean isCnt(Aword x) {
return (x >= CNTMIN && x <= CNTMAX) ||
(isObj(x) && objs[x - OBJMIN].cont != 0) ||
(isAct(x) && acts[x - ACTMIN].cont != 0);
}
Boolean isAct(Aword x) {
return x >= ACTMIN && x <= ACTMAX;
}
Boolean isLoc(Aword x) {
return x >= LOCMIN && x <= LOCMAX;
}
Boolean isNum(Aword x) {
return x >= LITMIN && x <= LITMAX && litValues[x - LITMIN].type == TYPNUM;
}
Boolean isStr(Aword x) {
return x >= LITMIN && x <= LITMAX && litValues[x - LITMIN].type == TYPSTR;
}
Boolean isLit(Aword x) {
return x >= LITMIN && x <= LITMAX;
}
/*======================================================================
exitto()
Is there an exit from one location to another?
*/
Boolean exitto(int to, int from) {
ExtElem *ext;
if (locs[from - LOCMIN].exts == 0)
return (FALSE); /* No exits */
for (ext = (ExtElem *) addrTo(locs[from - LOCMIN].exts); !endOfTable(ext); ext++)
if ((int)ext->next == to)
return (TRUE);
return (FALSE);
}
/*----------------------------------------------------------------------
count()
Count the number of items in a container.
*/
static int count(int cnt /* IN - the container to count */) {
int j = 0;
for (uint i = OBJMIN; i <= OBJMAX; i++)
if (in(i, cnt))
/* Then it's in this container also */
j++;
return j;
}
/*----------------------------------------------------------------------
sumatr()
Sum the values of one attribute in a container. Recursively.
*/
static int sumatr(
Aword atr, /* IN - the attribute to sum over */
Aword cnt /* IN - the container to sum */
) {
uint i;
int sum = 0;
for (i = OBJMIN; i <= OBJMAX; i++)
if (objs[i - OBJMIN].loc == cnt) { /* Then it's in this container */
if (objs[i - OBJMIN].cont != 0) /* This is also a container! */
sum = sum + sumatr(atr, i);
sum = sum + attribute(i, atr);
}
return (sum);
}
/*======================================================================
checklim()
Checks if a limit for a container is exceeded.
*/
Boolean checklim(
Aword cnt, /* IN - Container code */
Aword obj /* IN - The object to add */
) {
LimElem *lim;
Aword props;
fail = TRUE;
if (!isCnt(cnt))
syserr("Checking limits for a non-container.");
/* Find the container properties */
if (isObj(cnt))
props = objs[cnt - OBJMIN].cont;
else if (isAct(cnt))
props = acts[cnt - ACTMIN].cont;
else
props = cnt;
if (cnts[props - CNTMIN].lims != 0) { /* Any limits at all? */
for (lim = (LimElem *) addrTo(cnts[props - CNTMIN].lims); !endOfTable(lim); lim++)
if (lim->atr == 0) {
if (count(cnt) >= (int)lim->val) {
interpret(lim->stms);
return (TRUE); /* Limit check failed */
}
} else {
if (sumatr(lim->atr, cnt) + attribute(obj, lim->atr) > lim->val) {
interpret(lim->stms);
return (TRUE);
}
}
}
fail = FALSE;
return (FALSE);
}
/*----------------------------------------------------------------------*\
Action routines
\*----------------------------------------------------------------------*/
/*----------------------------------------------------------------------
trycheck()
Tries a check, returns TRUE if it passed, FALSE else.
*/
static Boolean trycheck(
Aaddr adr, /* IN - ACODE address to check table */
Boolean act /* IN - Act if it fails ? */
) {
ChkElem *chk;
chk = (ChkElem *) addrTo(adr);
if (chk->exp == 0) {
interpret(chk->stms);
return (FALSE);
} else {
while (!endOfTable(chk)) {
interpret(chk->exp);
if (!(Abool)pop()) {
if (act)
interpret(chk->stms);
return (FALSE);
}
chk++;
}
return (TRUE);
}
}
/*======================================================================
go()
Move hero in a direction.
*/
void go(CONTEXT, int dir) {
ExtElem *ext;
Boolean ok;
Aword oldloc;
ext = (ExtElem *) addrTo(locs[cur.loc - LOCMIN].exts);
if (locs[cur.loc - LOCMIN].exts != 0) {
while (!endOfTable(ext)) {
if ((int)ext->code == dir) {
ok = TRUE;
if (ext->checks != 0) {
if (trcflg) {
printf("\n<EXIT %d (%s) from %d (", dir,
(char *)addrTo(dict[wrds[wrdidx - 1]].wrd), cur.loc);
debugsay(cur.loc);
printf("), Checking:>\n");
}
ok = trycheck(ext->checks, TRUE);
}
if (ok) {
oldloc = cur.loc;
if (ext->action != 0) {
if (trcflg) {
printf("\n<EXIT %d (%s) from %d (", dir,
(char *)addrTo(dict[wrds[wrdidx - 1]].wrd), cur.loc);
debugsay(cur.loc);
printf("), Executing:>\n");
}
interpret(ext->action);
}
/* Still at the same place? */
if (where(HERO) == oldloc) {
if (trcflg) {
printf("\n<EXIT %d (%s) from %d (", dir,
(char *)addrTo(dict[wrds[wrdidx - 1]].wrd), cur.loc);
debugsay(cur.loc);
printf("), Moving:>\n");
}
locate(HERO, ext->next);
}
}
return;
}
ext++;
}
}
CALL1(error, M_NO_WAY)
}
/*----------------------------------------------------------------------
findalt()
Find the verb alternative wanted in a verb list and return
the address to it.
*/
static AltElem *findalt(
Aword vrbsadr, /* IN - Address to start of list */
Aword param /* IN - Which parameter to match */
) {
VrbElem *vrb;
AltElem *alt;
if (vrbsadr == 0)
return (nullptr);
for (vrb = (VrbElem *) addrTo(vrbsadr); !endOfTable(vrb); vrb++)
if ((int)vrb->code == cur.vrb) {
for (alt = (AltElem *) addrTo(vrb->alts); !endOfTable(alt); alt++)
if (alt->param == param || alt->param == 0)
return alt;
return nullptr;
}
return nullptr;
}
/*======================================================================
possible()
Check if current action is possible according to the CHECKs.
*/
Boolean possible() {
AltElem *alt[MAXPARAMS + 2]; /* List of alt-pointers, one for each param */
int i; /* Parameter index */
fail = FALSE;
alt[0] = findalt(header->vrbs, 0);
/* Perform global checks */
if (alt[0] != nullptr && alt[0]->checks != 0) {
if (!trycheck(alt[0]->checks, FALSE)) return FALSE;
if (fail) return FALSE;
}
/* Now CHECKs in this location */
alt[1] = findalt(locs[cur.loc - LOCMIN].vrbs, 0);
if (alt[1] != nullptr && alt[1]->checks != 0)
if (!trycheck(alt[1]->checks, FALSE))
return FALSE;
for (i = 0; params[i].code != EOD; i++) {
alt[i + 2] = findalt(objs[params[i].code - OBJMIN].vrbs, i + 1);
/* CHECKs in a possible parameter */
if (alt[i + 2] != nullptr && alt[i + 2]->checks != 0)
if (!trycheck(alt[i + 2]->checks, FALSE))
return FALSE;
}
for (i = 0; i < 2 || params[i - 2].code != EOD; i++)
if (alt[i] != nullptr && alt[i]->action != 0)
break;
if (i >= 2 && params[i - 2].code == EOD)
/* Didn't find any code for this verb/object combination */
return FALSE;
else
return TRUE;
}
/*----------------------------------------------------------------------
do_it()
Execute the action commanded by hero.
*/
static void do_it(CONTEXT) {
AltElem *alt[MAXPARAMS + 2]; /* List of alt-pointers, one for each param */
Boolean done[MAXPARAMS + 2]; /* Is it done */
int i; /* Parameter index */
char trace[80]; /* Trace string buffer */
fail = FALSE;
alt[0] = findalt(header->vrbs, 0);
/* Perform global checks */
if (alt[0] != nullptr && alt[0]->checks != 0) {
if (trcflg)
printf("\n<VERB %d, CHECK, GLOBAL:>\n", cur.vrb);
if (!trycheck(alt[0]->checks, TRUE)) return;
if (fail) return;
}
/* Now CHECKs in this location */
alt[1] = findalt(locs[cur.loc - LOCMIN].vrbs, 0);
if (alt[1] != nullptr && alt[1]->checks != 0) {
if (trcflg)
printf("\n<VERB %d, CHECK, in LOCATION:>\n", cur.vrb);
if (!trycheck(alt[1]->checks, TRUE)) return;
if (fail) return;
}
for (i = 0; params[i].code != EOD; i++) {
if (isLit(params[i].code))
alt[i + 2] = nullptr;
else {
if (isObj(params[i].code))
alt[i + 2] = findalt(objs[params[i].code - OBJMIN].vrbs, i + 1);
else if (isAct(params[i].code))
alt[i + 2] = findalt(acts[params[i].code - ACTMIN].vrbs, i + 1);
else
syserr("Illegal parameter type.");
/* CHECKs in the parameters */
if (alt[i + 2] != nullptr && alt[i + 2]->checks != 0) {
if (trcflg)
printf("\n<VERB %d, CHECK, in Parameter #%d:>\n", cur.vrb, i);
if (!trycheck(alt[i + 2]->checks, TRUE)) return;
if (fail) return;
}
}
}
/* Check for anything to execute... */
for (i = 0; i < 2 || params[i - 2].code != EOD; i++)
if (alt[i] != nullptr && alt[i]->action != 0)
break;
if (i >= 2 && params[i - 2].code == EOD) {
// Didn't find any code for this verb/object combination
CALL1(error, M_CANT0)
}
/* Perform actions! */
/* First try any BEFORE or ONLY from outside in */
done[0] = FALSE;
done[1] = FALSE;
for (i = 2; params[i - 2].code != EOD; i++)
done[i] = FALSE;
i--;
while (i >= 0) {
if (alt[i] != nullptr)
if (alt[i]->qual == (Aword)Q_BEFORE || alt[i]->qual == (Aword)Q_ONLY) {
if (alt[i]->action != 0) {
if (trcflg) {
if (i == 0)
strcpy(trace, "GLOBAL");
else if (i == 1)
strcpy(trace, "in LOCATION");
else
sprintf(trace, "in PARAMETER %d", i - 1);
if (alt[i]->qual == (Aword)Q_BEFORE)
printf("\n<VERB %d, %s (BEFORE), Body:>\n", cur.vrb, trace);
else
printf("\n<VERB %d, %s (ONLY), Body:>\n", cur.vrb, trace);
}
CALL1(interpret, alt[i]->action)
if (fail) return;
if (alt[i]->qual == (Aword)Q_ONLY) return;
}
done[i] = TRUE;
}
i--;
}
/* Then execute any not declared as AFTER, i.e. the default */
for (i = 0; i < 2 || params[i - 2].code != EOD; i++) {
if (alt[i] != nullptr)
if (alt[i]->qual != (Aword)Q_AFTER) {
if (!done[i] && alt[i]->action != 0) {
if (trcflg) {
if (i == 0)
strcpy(trace, "GLOBAL");
else if (i == 1)
strcpy(trace, "in LOCATION");
else
sprintf(trace, "in PARAMETER %d", i - 1);
printf("\n<VERB %d, %s, Body:>\n", cur.vrb, trace);
}
CALL1(interpret, alt[i]->action)
if (fail) return;
}
done[i] = TRUE;
}
}
/* Finally, the ones declared as after */
i--;
while (i >= 0) {
if (alt[i] != nullptr)
if (!done[i] && alt[i]->action != 0) {
if (trcflg) {
if (i == 0)
strcpy(trace, "GLOBAL");
else if (i == 1)
strcpy(trace, "in LOCATION");
else
sprintf(trace, "in PARAMETER %d", i - 1);
printf("\n<VERB %d, %s (AFTER), Body:>\n", cur.vrb, trace);
}
CALL1(interpret, alt[i]->action)
if (fail) return;
}
i--;
}
}
/*======================================================================
action()
Execute all activities commanded. Handles possible multiple actions
such as THEM or lists of objects.
*/
void action(CONTEXT, ParamElem plst[] /* IN - Plural parameter list */) {
int i, mpos;
char marker[10];
if (plural) {
/*
The code == 0 means this is a multiple position. We must loop
over this position (and replace it by each present in the plst)
*/
for (mpos = 0; params[mpos].code != 0; mpos++); /* Find multiple position */
sprintf(marker, "($%d)", mpos + 1); /* Prepare a printout with $1/2/3 */
for (i = 0; plst[i].code != EOD; i++) {
params[mpos] = plst[i];
output(marker);
CALL0(do_it)
if (plst[i + 1].code != EOD)
para();
}
params[mpos].code = 0;
} else {
CALL0(do_it)
}
}
/*----------------------------------------------------------------------*\
Event Handling
eventchk()
\*----------------------------------------------------------------------*/
/*----------------------------------------------------------------------
eventchk()
Check if any events are pending. If so execute them.
*/
static void eventchk() {
while (etop != 0 && eventq[etop - 1].time == cur.tick) {
etop--;
if (isLoc(eventq[etop].where))
cur.loc = eventq[etop].where;
else
cur.loc = where(eventq[etop].where);
if (trcflg) {
printf("\n<EVENT %d (at ", eventq[etop].event);
debugsay(cur.loc);
printf("):>\n");
}
interpret(evts[eventq[etop].event - EVTMIN].code);
}
}
/*----------------------------------------------------------------------*\
Main program and initialisation
codfil
filenames
checkvers()
load()
checkdebug()
initheader()
initstrings()
start()
init()
main()
\*----------------------------------------------------------------------*/
Common::SeekableReadStream *codfil;
/*----------------------------------------------------------------------
checkvers()
*/
static void checkvers(AcdHdr *hdr) {
char vers[4];
char state[2];
/* Construct our own version */
vers[0] = alan.version.version;
vers[1] = alan.version.revision;
/* Check version of .ACD file */
if (dbgflg) {
state[0] = hdr->vers[3];
state[1] = '\0';
printf("<Version of '%s' is %d.%d(%d)%s>",
advnam,
(int)(hdr->vers[0]),
(int)(hdr->vers[1]),
(int)(hdr->vers[2]),
(hdr->vers[3]) == 0 ? "" : state);
newline();
}
/* Compatible if version and revision match... */
if (strncmp(hdr->vers, vers, 2) != 0) {
#ifdef V25COMPATIBLE
if (hdr->vers[0] == 2 && hdr->vers[1] == 5) /* Check for 2.5 version */
/* This we can convert later if needed... */;
else
#endif
#ifdef V27COMPATIBLE
if (hdr->vers[0] == 2 && hdr->vers[1] == 7) /* Check for 2.7 version */
/* This we can convert later if needed... */;
else
#endif
if (errflg) {
char str[80];
sprintf(str, "Incompatible version of ACODE program. Game is %ld.%ld, interpreter %ld.%ld.",
(long)(hdr->vers[0]),
(long)(hdr->vers[1]),
(long) alan.version.version,
(long) alan.version.revision);
syserr(str);
} else
output("<WARNING! Incompatible version of ACODE program.>\n");
}
}
/*----------------------------------------------------------------------
load()
*/
static void load() {
AcdHdr tmphdr;
Aword crc = 0;
uint i;
char err[100];
Aword *ptr = (Aword *)&tmphdr + 1;
codfil->seek(0);
codfil->read(&tmphdr.vers[0], 4);
for (i = 1; i < sizeof(tmphdr) / sizeof(Aword); ++i, ++ptr)
*ptr = codfil->readUint32BE();
checkvers(&tmphdr);
/* Allocate and load memory */
/* No memory allocated yet? */
if (memory == nullptr) {
#ifdef V25COMPATIBLE
if (tmphdr.vers[0] == 2 && tmphdr.vers[1] == 5)
/* We need some more memory to expand 2.5 format*/
memory = allocate((tmphdr.size + tmphdr.objmax - tmphdr.objmin + 1 + 2) * sizeof(Aword));
else
#endif
memory = (Aword *)allocate(tmphdr.size * sizeof(Aword));
}
memTop = tmphdr.size;
header = (AcdHdr *) addrTo(0);
if ((int)(tmphdr.size * sizeof(Aword)) > codfil->size())
::error("Header size is greater than filesize");
codfil->seek(0);
codfil->read(&header->vers[0], sizeof(Aword) * tmphdr.size);
/* Calculate checksum */
for (i = sizeof(tmphdr) / sizeof(Aword); i < memTop; i++) {
crc += memory[i] & 0xff;
crc += (memory[i] >> 8) & 0xff;
crc += (memory[i] >> 16) & 0xff;
crc += (memory[i] >> 24) & 0xff;
}
if (crc != tmphdr.acdcrc) {
sprintf(err, "Checksum error in .ACD file (0x%lx instead of 0x%lx).",
(unsigned long) crc, (unsigned long) tmphdr.acdcrc);
if (errflg)
syserr(err);
else {
output("<WARNING! $$");
output(err);
output("$$ Ignored, proceed at your own risk.>$n");
}
}
#if defined(SCUMM_LITTLE_ENDIAN)
if (dbgflg || trcflg || stpflg)
output("<Hmm, this is a little-endian machine, fixing byte ordering....");
reverseACD(tmphdr.vers[0] == 2 && tmphdr.vers[1] == 5); /* Reverse all words in the ACD file */
if (dbgflg || trcflg || stpflg)
output("OK.>$n");
#endif
#ifdef V25COMPATIBLE
/* Check for 2.5 version */
if (tmphdr.vers[0] == 2 && tmphdr.vers[1] == 5) {
if (dbgflg || trcflg || stpflg)
output("<Hmm, this is a v2.5 game, please wait while I convert it...");
c25to26ACD();
if (dbgflg || trcflg || stpflg)
output("OK.>$n");
}
#endif
}
/*----------------------------------------------------------------------
checkdebug()
*/
static void checkdebug() {
/* Make sure he can't debug if not allowed! */
if (!header->debug) {
if (dbgflg | trcflg | stpflg)
printf("<Sorry, '%s' is not compiled for debug!>\n", advnam);
para();
dbgflg = FALSE;
trcflg = FALSE;
stpflg = FALSE;
}
}
/*----------------------------------------------------------------------
initheader()
*/
static void initheader() {
dict = (WrdElem *) addrTo(header->dict);
/* Find out number of entries in dictionary */
for (dictsize = 0; !endOfTable(&dict[dictsize]); dictsize++);
vrbs = (VrbElem *) addrTo(header->vrbs);
stxs = (StxElem *) addrTo(header->stxs);
locs = (LocElem *) addrTo(header->locs);
acts = (ActElem *) addrTo(header->acts);
objs = (ObjElem *) addrTo(header->objs);
evts = (EvtElem *) addrTo(header->evts);
cnts = (CntElem *) addrTo(header->cnts);
ruls = (RulElem *) addrTo(header->ruls);
msgs = (MsgElem *) addrTo(header->msgs);
scores = (Aword *) addrTo(header->scores);
if (header->pack)
freq = (Aword *) addrTo(header->freq);
}
/*----------------------------------------------------------------------
initstrings()
*/
static void initstrings() {
IniElem *init;
for (init = (IniElem *) addrTo(header->init); !endOfTable(init); init++) {
getstr(init->fpos, init->len);
memory[init->adr] = pop();
}
}
/*----------------------------------------------------------------------
start()
*/
static void start() {
int startloc;
cur.tick = -1;
cur.loc = startloc = where(HERO);
cur.act = HERO;
cur.score = 0;
if (trcflg)
printf("\n<START:>\n");
interpret(header->start);
para();
acts[HERO - ACTMIN].loc = 0;
locate(HERO, startloc);
}
/*----------------------------------------------------------------------
init()
Initialization, program load etc.
*/
static void init() {
int i;
/* Initialise some status */
etop = 0; /* No pending events */
looking = FALSE; /* Not looking now */
dscrstkp = 0; /* No describe in progress */
load();
initheader();
checkdebug();
/* Initialise string attributes */
initstrings();
/* Find first conjunction and use that for ',' handling */
for (i = 0; i < dictsize; i++)
if (isConj(i)) {
conjWord = i;
break;
}
/* Start the adventure */
clear();
start();
}
/*----------------------------------------------------------------------
movactor()
Let the current actor move. If player, ask him.
*/
static void movactor(CONTEXT) {
ScrElem *scr;
StepElem *step;
ActElem *act = (ActElem *) &acts[cur.act - ACTMIN];
cur.loc = where(cur.act);
if (cur.act == (int)HERO) {
CALL0(parse)
if (g_vm->shouldQuit())
return;
fail = FALSE; /* fail only aborts one actor */
rules();
} else if (act->script != 0) {
for (scr = (ScrElem *) addrTo(act->scradr); !endOfTable(scr); scr++)
if (scr->code == act->script) {
/* Find correct step in the list by indexing */
step = (StepElem *) addrTo(scr->steps);
step = (StepElem *) &step[act->step];
/* Now execute it, maybe. First check wait count */
if (step->after > act->count) {
/* Wait some more */
if (trcflg) {
printf("\n<ACTOR %d, ", cur.act);
debugsay(cur.act);
printf(" (at ");
debugsay(cur.loc);
printf("), SCRIPT %u, STEP %u, Waiting %d more>\n",
act->script, act->step + 1, step->after - act->count);
}
act->count++;
rules();
return;
} else
act->count = 0;
/* Then check possible expression */
if (step->exp != 0) {
if (trcflg) {
printf("\n<ACTOR %d, ", cur.act);
debugsay(cur.act);
printf(" (at ");
debugsay(cur.loc);
printf("), SCRIPT %u, STEP %u, Evaluating:>\n",
act->script, act->step + 1);
}
interpret(step->exp);
if (!(Abool)pop()) {
rules();
return; /* Hadn't happened yet */
}
}
/* OK, so finally let him do his thing */
act->step++; /* Increment step number before executing... */
if (trcflg) {
printf("\n<ACTOR %d, ", cur.act);
debugsay(cur.act);
printf(" (at ");
debugsay(cur.loc);
printf("), SCRIPT %u, STEP %u, Executing:>\n",
act->script, act->step);
}
interpret(step->stm);
step++;
/* ... so that we can see if he is USEing another script now */
if (act->step != 0 && endOfTable(step))
/* No more steps in this script, so stop him */
act->script = 0;
fail = FALSE; /* fail only aborts one actor */
rules();
return;
}
syserr("Unknown actor script.");
} else if (trcflg) {
printf("\n<ACTOR %d, ", cur.act);
debugsay(cur.act);
printf(" (at ");
debugsay(cur.loc);
printf("), Idle>\n");
rules();
return;
}
}
/*----------------------------------------------------------------------
openFiles()
Open the necessary files.
*/
static void openFiles() {
// If logging open log file
if (logflg) {
Common::String filename = Common::String::format("%s.log", advnam);
logfil = g_system->getSavefileManager()->openForSaving(filename);
logflg = logfil != nullptr;
}
}
/*======================================================================
run()
Run the adventure
*/
void run() {
openFiles();
// Set default line and column
col = lin = 1;
while (!g_vm->shouldQuit()) {
// Load, initialise and start the adventure
g_vm->setRestart(false);
init();
if (g_vm->_saveSlot != -1) {
if (g_vm->loadGameState(g_vm->_saveSlot).getCode() != Common::kNoError)
return;
g_vm->_saveSlot = -1;
g_vm->_pendingLook = true;
}
Context ctx;
while (!g_vm->shouldQuit() && !g_vm->shouldRestart()) {
if (!ctx._break) {
if (dbgflg)
debug();
eventchk();
cur.tick++;
}
// Execution ends up here after calls to the error method
// Move all characters
ctx._break = false;
for (cur.act = ACTMIN; cur.act <= (int)ACTMAX && !ctx._break; cur.act++) {
movactor(ctx);
if (g_vm->shouldQuit())
return;
}
}
}
}
} // End of namespace Alan2
} // End of namespace Glk