2019-06-25 21:18:44 -07:00
|
|
|
/* ScummVM - Graphic Adventure Engine
|
|
|
|
*
|
|
|
|
* ScummVM is the legal property of its developers, whose names
|
|
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
|
|
* file distributed with this source distribution.
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public License
|
|
|
|
* as published by the Free Software Foundation; either version 2
|
|
|
|
* of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "glk/alan3/main.h"
|
|
|
|
#include "glk/alan3/alan_version.h"
|
|
|
|
#include "glk/alan3/class.h"
|
|
|
|
#include "glk/alan3/compatibility.h"
|
|
|
|
#include "glk/alan3/container.h"
|
|
|
|
#include "glk/alan3/current.h"
|
|
|
|
#include "glk/alan3/debug.h"
|
|
|
|
#include "glk/alan3/decode.h"
|
|
|
|
#include "glk/alan3/dictionary.h"
|
|
|
|
#include "glk/alan3/event.h"
|
|
|
|
#include "glk/alan3/exe.h"
|
|
|
|
#include "glk/alan3/glkio.h"
|
|
|
|
#include "glk/alan3/instance.h"
|
|
|
|
#include "glk/alan3/inter.h"
|
2019-09-24 20:20:35 -07:00
|
|
|
#include "glk/jumps.h"
|
2019-06-25 21:18:44 -07:00
|
|
|
#include "glk/alan3/lists.h"
|
|
|
|
#include "glk/alan3/literal.h"
|
|
|
|
#include "glk/alan3/location.h"
|
|
|
|
#include "glk/alan3/memory.h"
|
|
|
|
#include "glk/alan3/msg.h"
|
|
|
|
#include "glk/alan3/options.h"
|
|
|
|
#include "glk/alan3/output.h"
|
|
|
|
#include "glk/alan3/parse.h"
|
|
|
|
#include "glk/alan3/reverse.h"
|
|
|
|
#include "glk/alan3/rules.h"
|
|
|
|
#include "glk/alan3/scan.h"
|
|
|
|
#include "glk/alan3/score.h"
|
|
|
|
#include "glk/alan3/state.h"
|
|
|
|
#include "glk/alan3/syserr.h"
|
|
|
|
#include "glk/alan3/syntax.h"
|
|
|
|
#include "glk/alan3/utils.h"
|
|
|
|
|
|
|
|
namespace Glk {
|
|
|
|
namespace Alan3 {
|
|
|
|
|
|
|
|
/* PUBLIC DATA */
|
|
|
|
|
|
|
|
/* Amachine structures - Static */
|
2019-06-27 04:02:48 +01:00
|
|
|
VerbEntry *vrbs; /* Verb table pointer */
|
2019-06-25 21:18:44 -07:00
|
|
|
|
|
|
|
|
|
|
|
/* PRIVATE DATA */
|
|
|
|
#define STACKSIZE 100
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------------*
|
|
|
|
*
|
|
|
|
* Event Handling
|
|
|
|
*
|
|
|
|
*----------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
|
|
static char *eventName(int event) {
|
2019-06-27 04:02:48 +01:00
|
|
|
return stringAt(events[event].id);
|
2019-06-25 21:18:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
2019-07-01 20:56:55 -07:00
|
|
|
static void runPendingEvents(CONTEXT) {
|
2019-06-27 04:02:48 +01:00
|
|
|
int i;
|
|
|
|
|
|
|
|
resetRules();
|
|
|
|
while (eventQueueTop != 0 && eventQueue[eventQueueTop - 1].after == 0) {
|
|
|
|
eventQueueTop--;
|
|
|
|
if (isALocation(eventQueue[eventQueueTop].where))
|
|
|
|
current.location = eventQueue[eventQueueTop].where;
|
|
|
|
else
|
|
|
|
current.location = where(eventQueue[eventQueueTop].where, TRANSITIVE);
|
|
|
|
if (traceSectionOption) {
|
|
|
|
printf("\n<EVENT %s[%d] (at ", eventName(eventQueue[eventQueueTop].event),
|
|
|
|
eventQueue[eventQueueTop].event);
|
2019-07-01 20:56:55 -07:00
|
|
|
CALL1(traceSay, current.location)
|
2019-06-27 04:02:48 +01:00
|
|
|
printf(" [%d]):>\n", current.location);
|
|
|
|
}
|
2019-07-01 20:56:55 -07:00
|
|
|
CALL1(interpret, events[eventQueue[eventQueueTop].event].code)
|
|
|
|
CALL1(evaluateRules, rules)
|
2019-06-27 04:02:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < eventQueueTop; i++)
|
|
|
|
eventQueue[i].after--;
|
2019-06-25 21:18:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------------*\
|
|
|
|
|
|
|
|
Main program and initialisation
|
|
|
|
|
|
|
|
\*----------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
Common::SeekableReadStream *codfil;
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------------
|
|
|
|
Calculate where to start calculating the CRC. Is different for
|
|
|
|
different versions. CRC is calculated from pre-beta2 memory start to
|
|
|
|
be compatible. If header size changes this should return beta2
|
|
|
|
header size for later versions.
|
|
|
|
*/
|
2019-06-28 19:48:56 -07:00
|
|
|
static int crcStart(const byte version[4]) {
|
2019-06-27 04:02:48 +01:00
|
|
|
/* Some earlier versions had a shorter header */
|
|
|
|
if (isPreAlpha5(version))
|
|
|
|
return sizeof(Pre3_0alpha5Header) / sizeof(Aword);
|
|
|
|
else if (isPreBeta2(version))
|
|
|
|
return sizeof(Pre3_0beta2Header) / sizeof(Aword);
|
|
|
|
else
|
|
|
|
return sizeof(ACodeHeader) / sizeof(Aword);
|
2019-06-25 21:18:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
2019-07-02 19:54:32 -07:00
|
|
|
static void readTemporaryHeader(CONTEXT, ACodeHeader *tmphdr) {
|
2019-06-25 21:18:44 -07:00
|
|
|
codfil->seek(0);
|
2019-06-28 20:20:37 -07:00
|
|
|
if (codfil->read(&tmphdr->tag[0], sizeof(ACodeHeader)) != sizeof(ACodeHeader) ||
|
|
|
|
strncmp((char *)tmphdr, "ALAN", 4) != 0)
|
2019-07-02 19:54:32 -07:00
|
|
|
playererr(context, "Not an Alan game file, does not start with \"ALAN\"");
|
2019-06-25 21:18:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
2019-07-06 17:02:26 -07:00
|
|
|
#ifdef SCUMM_LITTLE_ENDIAN
|
2019-06-25 21:18:44 -07:00
|
|
|
static void reverseMemory() {
|
2019-06-28 20:20:37 -07:00
|
|
|
if (debugOption || traceSectionOption || traceInstructionOption)
|
|
|
|
output("<Hmm, this is a little-endian machine, fixing byte ordering....");
|
|
|
|
reverseACD(); /* Reverse content of the ACD file */
|
|
|
|
if (debugOption || traceSectionOption || traceInstructionOption)
|
|
|
|
output("OK.>$n");
|
2019-06-25 21:18:44 -07:00
|
|
|
}
|
2019-07-06 17:02:26 -07:00
|
|
|
#endif
|
2019-06-25 21:18:44 -07:00
|
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
|
|
static void setupHeader(ACodeHeader tmphdr) {
|
2019-06-27 04:02:48 +01:00
|
|
|
if (isPreBeta2(tmphdr.version)) {
|
|
|
|
header = (ACodeHeader *)duplicate(&memory[0], sizeof(ACodeHeader));
|
|
|
|
if (isPreAlpha5(tmphdr.version)) {
|
|
|
|
header->ifids = 0;
|
|
|
|
}
|
|
|
|
header->prompt = 0;
|
|
|
|
} else if (isPreBeta3(tmphdr.version)) {
|
|
|
|
header = (ACodeHeader *)pointerTo(0);
|
|
|
|
} else {
|
|
|
|
header = (ACodeHeader *)pointerTo(0);
|
|
|
|
}
|
2019-06-25 21:18:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
|
|
static void loadAndCheckMemory(ACodeHeader tmphdr, Aword crc, char err[]) {
|
2019-06-25 22:37:19 -07:00
|
|
|
int i;
|
2019-06-27 04:02:48 +01:00
|
|
|
/* No memory allocated yet? */
|
|
|
|
if (memory == NULL) {
|
|
|
|
memory = (Aword *)allocate(tmphdr.size * sizeof(Aword));
|
|
|
|
}
|
2019-06-25 21:18:44 -07:00
|
|
|
|
|
|
|
memTop = tmphdr.size;
|
2019-06-28 20:20:37 -07:00
|
|
|
codfil->seek(0);
|
|
|
|
if (codfil->read(memory, sizeof(Aword) * memTop) != (sizeof(Aword) * memTop))
|
2019-06-25 21:18:44 -07:00
|
|
|
syserr("Could not read all ACD code.");
|
|
|
|
|
2019-06-27 04:02:48 +01:00
|
|
|
/* Calculate checksum */
|
|
|
|
for (i = crcStart(tmphdr.version); 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 Acode (.a3c) file (0x%lx instead of 0x%lx).",
|
|
|
|
(unsigned long) crc, (unsigned long) tmphdr.acdcrc);
|
|
|
|
if (!ignoreErrorOption)
|
|
|
|
syserr(err);
|
|
|
|
else {
|
|
|
|
output("<WARNING! $$");
|
|
|
|
output(err);
|
|
|
|
output("$$ Ignored, proceed at your own risk.>$n");
|
|
|
|
}
|
|
|
|
}
|
2019-06-25 21:18:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
2019-06-26 19:37:00 -07:00
|
|
|
static const char *decodeState(int c) {
|
2019-06-27 04:02:48 +01:00
|
|
|
static char state[3] = "\0\0";
|
|
|
|
switch (c) {
|
|
|
|
case 0:
|
|
|
|
return ".";
|
|
|
|
case 'd':
|
|
|
|
return "dev";
|
|
|
|
case 'a':
|
|
|
|
return "alpha";
|
|
|
|
case 'b':
|
|
|
|
return "beta";
|
|
|
|
default:
|
|
|
|
state[0] = header->version[3];
|
|
|
|
return state;
|
|
|
|
}
|
2019-06-25 21:18:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/*======================================================================*/
|
2019-06-28 19:48:56 -07:00
|
|
|
char *decodedGameVersion(const byte version[]) {
|
2019-06-27 04:02:48 +01:00
|
|
|
static char str[100];
|
|
|
|
sprintf(str, "%d.%d%s%d",
|
|
|
|
(int)version[3],
|
|
|
|
(int)version[2],
|
|
|
|
decodeState(version[0]),
|
|
|
|
(int)version[1]);
|
|
|
|
return str;
|
2019-06-25 21:18:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
2019-06-25 22:37:19 -07:00
|
|
|
static void incompatibleDevelopmentVersion(ACodeHeader *hdr) {
|
2019-07-24 20:32:14 -07:00
|
|
|
Common::String msg = Common::String::format("Incompatible version of ACODE program. "
|
|
|
|
"Development versions always require exact match. Game is %ld.%ld%s%ld, interpreter %ld.%ld%s%ld!",
|
2019-06-27 04:02:48 +01:00
|
|
|
(long)(hdr->version[0]),
|
|
|
|
(long)(hdr->version[1]),
|
|
|
|
decodeState(hdr->version[3]),
|
|
|
|
(long)(hdr->version[2]),
|
|
|
|
(long)alan.version.version,
|
|
|
|
(long)alan.version.revision,
|
|
|
|
alan.version.state,
|
|
|
|
(long)alan.version.correction);
|
2019-07-24 20:32:14 -07:00
|
|
|
apperr(msg.c_str());
|
2019-06-25 21:18:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
2019-06-25 22:37:19 -07:00
|
|
|
static void incompatibleVersion(ACodeHeader *hdr) {
|
2019-07-24 20:32:14 -07:00
|
|
|
Common::String msg = Common::String::format("Incompatible version of ACODE program. Game is %ld.%ld, interpreter %ld.%ld.",
|
2019-06-27 04:02:48 +01:00
|
|
|
(long)(hdr->version[0]),
|
|
|
|
(long)(hdr->version[1]),
|
|
|
|
(long)alan.version.version,
|
|
|
|
(long)alan.version.revision);
|
2019-07-24 20:32:14 -07:00
|
|
|
apperr(msg.c_str());
|
2019-06-25 21:18:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
|
|
static void alphaRunningLaterGame(char gameState) {
|
2019-06-27 04:02:48 +01:00
|
|
|
output("<WARNING! You are running an alpha interpreter, but the game is generated by a");
|
|
|
|
if (gameState == 'b')
|
|
|
|
output("beta");
|
|
|
|
else
|
|
|
|
output("release");
|
|
|
|
output("state compiler which was released later. This might cause the game to not work fully as intended. Look for an upgraded game file.>\n");
|
2019-06-25 21:18:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
2019-06-28 19:48:56 -07:00
|
|
|
static void nonDevelopmentRunningDevelopmentStateGame(const byte version[]) {
|
2019-06-27 04:02:48 +01:00
|
|
|
char errorMessage[200];
|
|
|
|
char versionString[100];
|
|
|
|
|
|
|
|
strcpy(errorMessage, "Games generated by a development state compiler");
|
|
|
|
sprintf(versionString, "(this game is v%d.%d.%d%s)", version[0], version[1],
|
|
|
|
version[2], decodeState(version[3]));
|
|
|
|
strcat(errorMessage, versionString);
|
|
|
|
strcat(errorMessage, "can only be run with a matching interpreter. Look for a game file generated with an alpha, beta or release state compiler.>\n");
|
|
|
|
apperr(errorMessage);
|
2019-06-25 21:18:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*======================================================================*/
|
2019-06-27 04:02:48 +01:00
|
|
|
void checkVersion(ACodeHeader *hdr) {
|
|
|
|
/* Strategy for version matching is:
|
|
|
|
1) Development interpreters/games require exact match
|
|
|
|
2) Alpha, Beta and Release interpreters will not run development games
|
|
|
|
3) Alpha interpreters must warn if they run beta or release games
|
|
|
|
4) Beta interpreters may introduce changes which are not alpha compatible,
|
|
|
|
if the change is a strict addition (i.e. if not used will not affect
|
|
|
|
alpha interpreters, example is introduction of a new opcode if it is
|
|
|
|
done at the end of the list)
|
|
|
|
5) Release interpreters should run alpha and beta games without problems
|
|
|
|
|
|
|
|
NOTE that we are working with a non-reversed version string/word here.
|
|
|
|
*/
|
|
|
|
|
|
|
|
char interpreterVersion[4];
|
|
|
|
bool developmentVersion;
|
|
|
|
bool alphaVersion;
|
|
|
|
int compareLength;
|
|
|
|
char gameState = hdr->version[3];
|
|
|
|
|
|
|
|
/* Construct our own version */
|
|
|
|
interpreterVersion[0] = alan.version.version;
|
|
|
|
interpreterVersion[1] = alan.version.revision;
|
|
|
|
interpreterVersion[2] = alan.version.correction;
|
|
|
|
interpreterVersion[3] = alan.version.state[0];
|
|
|
|
|
|
|
|
/* Check version of .ACD file */
|
|
|
|
if (debugOption && !regressionTestOption) {
|
|
|
|
printf("<Version of '%s' is %d.%d%s%d!>\n",
|
2019-07-06 20:55:47 -07:00
|
|
|
g_vm->getFilename().c_str(),
|
2019-06-27 04:02:48 +01:00
|
|
|
(int)hdr->version[0],
|
|
|
|
(int)hdr->version[1],
|
|
|
|
decodeState(hdr->version[3]),
|
|
|
|
(int)hdr->version[2]);
|
|
|
|
newline();
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Development version require exact match, else only 2 digit match */
|
|
|
|
developmentVersion = (strcmp(alan.version.state, "dev") == 0);
|
|
|
|
alphaVersion = (strcmp(alan.version.state, "alpha") == 0);
|
|
|
|
compareLength = (developmentVersion ? 3 : 2);
|
|
|
|
|
|
|
|
if (gameState == 'd' && !developmentVersion)
|
|
|
|
/* Development state game requires development state interpreter... */
|
|
|
|
nonDevelopmentRunningDevelopmentStateGame(hdr->version);
|
|
|
|
else {
|
|
|
|
/* Compatible if version, revision (and correction if dev state) match... */
|
|
|
|
if (memcmp(hdr->version, interpreterVersion, compareLength) != 0) {
|
|
|
|
/* Mismatch! */
|
|
|
|
if (!ignoreErrorOption) {
|
|
|
|
if (developmentVersion)
|
|
|
|
incompatibleDevelopmentVersion(hdr);
|
|
|
|
else
|
|
|
|
incompatibleVersion(hdr);
|
|
|
|
} else
|
|
|
|
output("<WARNING! Incompatible version of ACODE program.>\n");
|
|
|
|
} else if (developmentVersion && gameState != 'd')
|
|
|
|
/* ... unless interpreter is development and game not */
|
|
|
|
incompatibleDevelopmentVersion(hdr);
|
|
|
|
else if (alphaVersion && gameState != 'a') {
|
|
|
|
/* If interpreter is alpha version and the game is later, warn! */
|
|
|
|
alphaRunningLaterGame(gameState);
|
|
|
|
}
|
|
|
|
}
|
2019-06-25 21:18:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
2019-07-02 19:54:32 -07:00
|
|
|
static void load(CONTEXT) {
|
2019-06-27 04:02:48 +01:00
|
|
|
ACodeHeader tmphdr;
|
|
|
|
Aword crc = 0;
|
|
|
|
char err[100];
|
2019-06-25 21:18:44 -07:00
|
|
|
|
2019-07-02 19:54:32 -07:00
|
|
|
CALL1(readTemporaryHeader, &tmphdr)
|
2019-06-27 04:02:48 +01:00
|
|
|
checkVersion(&tmphdr);
|
2019-06-25 21:18:44 -07:00
|
|
|
|
2019-06-27 04:02:48 +01:00
|
|
|
/* Allocate and load memory */
|
2019-06-28 20:20:37 -07:00
|
|
|
#ifdef SCUMM_LITTLE_ENDIAN
|
|
|
|
reverseHdr(&tmphdr);
|
|
|
|
#endif
|
|
|
|
|
2019-06-27 04:02:48 +01:00
|
|
|
if (tmphdr.size <= sizeof(ACodeHeader) / sizeof(Aword))
|
|
|
|
syserr("Malformed game file. Too small.");
|
2019-06-25 21:18:44 -07:00
|
|
|
|
2019-06-27 04:02:48 +01:00
|
|
|
loadAndCheckMemory(tmphdr, crc, err);
|
2019-06-25 21:18:44 -07:00
|
|
|
|
2019-06-28 20:20:37 -07:00
|
|
|
#ifdef SCUMM_LITTLE_ENDIAN
|
2019-06-27 04:02:48 +01:00
|
|
|
reverseMemory();
|
2019-06-28 20:20:37 -07:00
|
|
|
#endif
|
2019-06-27 04:02:48 +01:00
|
|
|
setupHeader(tmphdr);
|
2019-06-25 21:18:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
2019-07-02 19:54:32 -07:00
|
|
|
static void checkDebug(CONTEXT) {
|
2019-06-27 04:02:48 +01:00
|
|
|
/* Make sure he can't debug if not allowed! */
|
|
|
|
if (!header->debug) {
|
|
|
|
if (debugOption | traceSectionOption | traceInstructionOption) {
|
2019-07-06 20:55:47 -07:00
|
|
|
printf("<Sorry, '%s' is not compiled for debug! Exiting.>\n", g_vm->getFilename().c_str());
|
2019-07-02 19:54:32 -07:00
|
|
|
CALL1(terminate, 0)
|
2019-06-27 04:02:48 +01:00
|
|
|
}
|
|
|
|
para();
|
|
|
|
debugOption = FALSE;
|
|
|
|
traceSectionOption = FALSE;
|
|
|
|
traceInstructionOption = FALSE;
|
|
|
|
tracePushOption = FALSE;
|
|
|
|
}
|
2019-07-06 21:05:22 -07:00
|
|
|
|
|
|
|
// If debugging, use no randomization
|
|
|
|
if (debugOption || regressionTestOption)
|
|
|
|
g_vm->setRandomNumberSeed(1);
|
2019-06-25 21:18:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
2019-06-27 04:02:48 +01:00
|
|
|
static void initStaticData(void) {
|
|
|
|
/* Dictionary */
|
|
|
|
dictionary = (DictionaryEntry *) pointerTo(header->dictionary);
|
|
|
|
/* Find out number of entries in dictionary */
|
|
|
|
for (dictionarySize = 0; !isEndOfArray(&dictionary[dictionarySize]); dictionarySize++);
|
|
|
|
|
|
|
|
/* Scores */
|
|
|
|
|
|
|
|
|
|
|
|
/* All addresses to tables indexed by ids are converted to pointers,
|
|
|
|
then adjusted to point to the (imaginary) element before the
|
|
|
|
actual table so that [0] does not exist. Instead indices goes
|
|
|
|
from 1 and we can use [1]. */
|
|
|
|
|
|
|
|
if (header->instanceTableAddress == 0)
|
|
|
|
syserr("Instance table pointer == 0");
|
|
|
|
instances = (InstanceEntry *) pointerTo(header->instanceTableAddress);
|
|
|
|
instances--; /* Back up one so that first is no. 1 */
|
|
|
|
|
|
|
|
|
|
|
|
if (header->classTableAddress == 0)
|
|
|
|
syserr("Class table pointer == 0");
|
|
|
|
classes = (ClassEntry *) pointerTo(header->classTableAddress);
|
|
|
|
classes--; /* Back up one so that first is no. 1 */
|
|
|
|
|
|
|
|
if (header->containerTableAddress != 0) {
|
|
|
|
containers = (ContainerEntry *) pointerTo(header->containerTableAddress);
|
|
|
|
containers--;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (header->eventTableAddress != 0) {
|
|
|
|
events = (EventEntry *) pointerTo(header->eventTableAddress);
|
|
|
|
events--;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Scores, if already allocated, copy initial data */
|
|
|
|
if (scores == NULL)
|
|
|
|
scores = (Aword *)duplicate((Aword *) pointerTo(header->scores), header->scoreCount * sizeof(Aword));
|
|
|
|
else
|
|
|
|
memcpy(scores, pointerTo(header->scores), header->scoreCount * sizeof(Aword));
|
|
|
|
|
|
|
|
if (literals == NULL)
|
|
|
|
literals = (LiteralEntry *)allocate(sizeof(Aword) * (MAXPARAMS + 1));
|
|
|
|
|
|
|
|
stxs = (SyntaxEntry *) pointerTo(header->syntaxTableAddress);
|
|
|
|
vrbs = (VerbEntry *) pointerTo(header->verbTableAddress);
|
|
|
|
msgs = (MessageEntry *) pointerTo(header->messageTableAddress);
|
|
|
|
initRules(header->ruleTableAddress);
|
|
|
|
|
|
|
|
if (header->pack)
|
|
|
|
freq = (Aword *) pointerTo(header->freq);
|
2019-06-25 21:18:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
2019-06-27 04:02:48 +01:00
|
|
|
static void initStrings(void) {
|
|
|
|
StringInitEntry *init;
|
2019-06-25 21:18:44 -07:00
|
|
|
|
2019-06-27 04:02:48 +01:00
|
|
|
for (init = (StringInitEntry *) pointerTo(header->stringInitTable); !isEndOfArray(init); init++)
|
|
|
|
setInstanceAttribute(init->instanceCode, init->attributeCode, toAptr(getStringFromFile(init->fpos, init->len)));
|
2019-06-25 21:18:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
2019-06-27 04:02:48 +01:00
|
|
|
static Aint sizeOfAttributeData(void) {
|
|
|
|
uint i;
|
|
|
|
int size = 0;
|
|
|
|
|
|
|
|
for (i = 1; i <= header->instanceMax; i++) {
|
|
|
|
AttributeEntry *attribute = (AttributeEntry *)pointerTo(instances[i].initialAttributes);
|
|
|
|
while (!isEndOfArray(attribute)) {
|
|
|
|
size += AwordSizeOf(AttributeEntry);
|
|
|
|
attribute++;
|
|
|
|
}
|
|
|
|
size += 1; /* For EOD */
|
|
|
|
}
|
|
|
|
|
|
|
|
if (size != header->attributesAreaSize
|
|
|
|
&& (sizeof(AttributeHeaderEntry) == sizeof(AttributeEntry)))
|
|
|
|
syserr("Attribute area size calculated wrong.");
|
|
|
|
return size;
|
2019-06-25 21:18:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
2019-06-27 04:02:48 +01:00
|
|
|
static AttributeEntry *initializeAttributes(int awordSize) {
|
|
|
|
Aword *attributeArea = (Aword *)allocate(awordSize * sizeof(Aword));
|
|
|
|
Aword *currentAttributeArea = attributeArea;
|
|
|
|
uint i;
|
|
|
|
|
|
|
|
for (i = 1; i <= header->instanceMax; i++) {
|
|
|
|
AttributeHeaderEntry *originalAttribute = (AttributeHeaderEntry *)pointerTo(instances[i].initialAttributes);
|
|
|
|
admin[i].attributes = (AttributeEntry *)currentAttributeArea;
|
|
|
|
while (!isEndOfArray(originalAttribute)) {
|
|
|
|
((AttributeEntry *)currentAttributeArea)->code = originalAttribute->code;
|
|
|
|
((AttributeEntry *)currentAttributeArea)->value = originalAttribute->value;
|
|
|
|
((AttributeEntry *)currentAttributeArea)->id = originalAttribute->id;
|
|
|
|
currentAttributeArea += AwordSizeOf(AttributeEntry);
|
|
|
|
originalAttribute++;
|
|
|
|
}
|
|
|
|
*((Aword *)currentAttributeArea) = EOD;
|
|
|
|
currentAttributeArea += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (AttributeEntry *)attributeArea;
|
2019-06-25 21:18:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
2019-06-27 04:02:48 +01:00
|
|
|
static void initDynamicData(void) {
|
|
|
|
uint instanceId;
|
2019-06-25 21:18:44 -07:00
|
|
|
|
2019-06-27 04:02:48 +01:00
|
|
|
/* Allocate for administrative table */
|
|
|
|
admin = (AdminEntry *)allocate((header->instanceMax + 1) * sizeof(AdminEntry));
|
2019-06-25 21:18:44 -07:00
|
|
|
|
2019-06-27 04:02:48 +01:00
|
|
|
/* Create game state copy of attributes */
|
|
|
|
attributes = initializeAttributes(sizeOfAttributeData());
|
2019-06-25 21:18:44 -07:00
|
|
|
|
2019-06-27 04:02:48 +01:00
|
|
|
/* Initialise string & set attributes */
|
|
|
|
initStrings();
|
|
|
|
initSets((SetInitEntry *)pointerTo(header->setInitTable));
|
2019-06-25 21:18:44 -07:00
|
|
|
|
2019-06-27 04:02:48 +01:00
|
|
|
/* Set initial locations */
|
|
|
|
for (instanceId = 1; instanceId <= header->instanceMax; instanceId++)
|
|
|
|
admin[instanceId].location = instances[instanceId].initialLocation;
|
2019-06-25 21:18:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
2019-07-01 20:56:55 -07:00
|
|
|
static void runInheritedInitialize(CONTEXT, Aint theClass) {
|
2019-06-27 04:02:48 +01:00
|
|
|
if (theClass == 0) return;
|
2019-07-01 20:56:55 -07:00
|
|
|
CALL1(runInheritedInitialize, classes[theClass].parent)
|
|
|
|
|
2019-06-27 04:02:48 +01:00
|
|
|
if (classes[theClass].initialize)
|
2019-07-01 20:56:55 -07:00
|
|
|
interpret(context, classes[theClass].initialize);
|
2019-06-25 21:18:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
2019-07-01 20:56:55 -07:00
|
|
|
static void runInitialize(CONTEXT, Aint theInstance) {
|
|
|
|
CALL1(runInheritedInitialize, instances[theInstance].parent)
|
|
|
|
|
2019-06-27 04:02:48 +01:00
|
|
|
if (instances[theInstance].initialize != 0)
|
2019-07-01 20:56:55 -07:00
|
|
|
interpret(context, instances[theInstance].initialize);
|
2019-06-25 21:18:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
2019-07-01 20:56:55 -07:00
|
|
|
static void initializeInstances(CONTEXT) {
|
2019-06-27 04:02:48 +01:00
|
|
|
uint instanceId;
|
2019-06-25 21:18:44 -07:00
|
|
|
|
2019-06-27 04:02:48 +01:00
|
|
|
/* Set initial locations */
|
|
|
|
for (instanceId = 1; instanceId <= header->instanceMax; instanceId++) {
|
|
|
|
current.instance = instanceId;
|
2019-07-01 20:56:55 -07:00
|
|
|
CALL1(runInitialize, instanceId)
|
2019-06-27 04:02:48 +01:00
|
|
|
}
|
2019-06-25 21:18:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
2019-07-01 20:56:55 -07:00
|
|
|
static void start(CONTEXT) {
|
2019-06-27 04:02:48 +01:00
|
|
|
int startloc;
|
|
|
|
|
|
|
|
current.tick = 0;
|
|
|
|
current.location = startloc = where(HERO, TRANSITIVE);
|
|
|
|
current.actor = HERO;
|
|
|
|
current.score = 0;
|
|
|
|
|
2019-07-01 20:56:55 -07:00
|
|
|
CALL0(initializeInstances)
|
2019-06-27 04:02:48 +01:00
|
|
|
|
|
|
|
if (traceSectionOption)
|
|
|
|
printf("\n<START:>\n");
|
2019-07-01 20:56:55 -07:00
|
|
|
CALL1(interpret, header->start)
|
2019-06-27 04:02:48 +01:00
|
|
|
para();
|
|
|
|
|
|
|
|
if (where(HERO, TRANSITIVE) == startloc) {
|
|
|
|
if (traceSectionOption)
|
|
|
|
printf("<CURRENT LOCATION:>");
|
2019-07-01 20:56:55 -07:00
|
|
|
CALL0(look)
|
2019-06-27 04:02:48 +01:00
|
|
|
}
|
2019-07-01 20:56:55 -07:00
|
|
|
|
|
|
|
resetAndEvaluateRules(context, rules, header->version);
|
2019-06-25 21:18:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
2019-06-27 04:02:48 +01:00
|
|
|
static void openFiles(void) {
|
|
|
|
/* If logging open log file */
|
|
|
|
if (transcriptOption || logOption) {
|
|
|
|
startTranscript();
|
|
|
|
}
|
2019-06-25 21:18:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
2019-07-01 20:56:55 -07:00
|
|
|
static void init(CONTEXT) {
|
2019-06-27 04:02:48 +01:00
|
|
|
int i;
|
|
|
|
|
|
|
|
/* Initialise some status */
|
|
|
|
eventQueueTop = 0; /* No pending events */
|
|
|
|
initStaticData();
|
|
|
|
initDynamicData();
|
|
|
|
initParsing();
|
2019-07-02 19:54:32 -07:00
|
|
|
CALL0(checkDebug)
|
2019-06-27 04:02:48 +01:00
|
|
|
|
|
|
|
getPageSize();
|
|
|
|
|
|
|
|
/* Find first conjunction and use that for ',' handling */
|
|
|
|
for (i = 0; i < dictionarySize; i++)
|
|
|
|
if (isConjunction(i)) {
|
|
|
|
conjWord = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Start the adventure */
|
2019-07-01 20:56:55 -07:00
|
|
|
if (debugOption) {
|
|
|
|
CALL3(debug, FALSE, 0, 0)
|
|
|
|
} else {
|
2019-06-27 04:02:48 +01:00
|
|
|
clear();
|
2019-07-01 20:56:55 -07:00
|
|
|
}
|
2019-06-27 04:02:48 +01:00
|
|
|
|
2019-07-01 20:56:55 -07:00
|
|
|
start(context);
|
2019-06-25 21:18:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
2019-07-01 20:56:55 -07:00
|
|
|
static bool traceActor(CONTEXT, int theActor) {
|
2019-06-27 04:02:48 +01:00
|
|
|
if (traceSectionOption) {
|
|
|
|
printf("\n<ACTOR ");
|
2019-07-01 20:56:55 -07:00
|
|
|
R0CALL1(traceSay, theActor)
|
2019-06-27 04:02:48 +01:00
|
|
|
printf("[%d]", theActor);
|
|
|
|
if (current.location != 0) {
|
|
|
|
printf(" (at ");
|
2019-07-01 20:56:55 -07:00
|
|
|
R0CALL1(traceSay, current.location)
|
2019-06-27 04:02:48 +01:00
|
|
|
} else
|
|
|
|
printf(" (nowhere");
|
|
|
|
printf("[%d])", current.location);
|
|
|
|
}
|
|
|
|
return traceSectionOption;
|
2019-06-25 21:18:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
2019-06-27 04:02:48 +01:00
|
|
|
static char *scriptName(int theActor, int theScript) {
|
|
|
|
ScriptEntry *scriptEntry = (ScriptEntry *)pointerTo(header->scriptTableAddress);
|
|
|
|
|
|
|
|
while (theScript > 1) {
|
|
|
|
scriptEntry++;
|
|
|
|
theScript--;
|
|
|
|
}
|
|
|
|
return (char *)pointerTo(scriptEntry->id);
|
2019-06-25 21:18:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
2019-06-29 14:42:50 -07:00
|
|
|
static void moveActor(CONTEXT, int theActor) {
|
2019-06-27 04:02:48 +01:00
|
|
|
ScriptEntry *scr;
|
|
|
|
StepEntry *step;
|
|
|
|
Aint previousInstance = current.instance;
|
2019-07-01 20:56:55 -07:00
|
|
|
bool flag;
|
2019-06-27 04:02:48 +01:00
|
|
|
|
2019-06-29 14:56:31 -07:00
|
|
|
if (context._break) {
|
|
|
|
// forfeit setjmp replacement destination
|
|
|
|
assert(context._label == "forfeit");
|
|
|
|
context.clear();
|
|
|
|
current.instance = previousInstance;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-06-27 04:02:48 +01:00
|
|
|
current.actor = theActor;
|
|
|
|
current.instance = theActor;
|
|
|
|
current.location = where(theActor, TRANSITIVE);
|
2019-06-29 14:56:31 -07:00
|
|
|
|
|
|
|
if (theActor == (int)HERO) {
|
|
|
|
// Ask him!
|
|
|
|
CALL0(parse)
|
|
|
|
capitalize = TRUE;
|
|
|
|
fail = FALSE; // fail only aborts one actor
|
2019-06-29 14:42:50 -07:00
|
|
|
|
2019-06-27 04:02:48 +01:00
|
|
|
} else if (admin[theActor].script != 0) {
|
|
|
|
for (scr = (ScriptEntry *) pointerTo(header->scriptTableAddress); !isEndOfArray(scr); scr++) {
|
|
|
|
if (scr->code == admin[theActor].script) {
|
|
|
|
/* Find correct step in the list by indexing */
|
|
|
|
step = (StepEntry *) pointerTo(scr->steps);
|
|
|
|
step = (StepEntry *) &step[admin[theActor].step];
|
|
|
|
/* Now execute it, maybe. First check wait count */
|
|
|
|
if (admin[theActor].waitCount > 0) { /* Wait some more ? */
|
2019-07-01 20:56:55 -07:00
|
|
|
FUNC1(traceActor, flag, theActor)
|
|
|
|
if (flag)
|
2019-06-27 04:02:48 +01:00
|
|
|
printf(", SCRIPT %s[%ld], STEP %ld, Waiting another %ld turns>\n",
|
|
|
|
scriptName(theActor, admin[theActor].script),
|
|
|
|
(long)admin[theActor].script, (long)admin[theActor].step + 1,
|
|
|
|
(long)admin[theActor].waitCount);
|
2019-07-01 20:56:55 -07:00
|
|
|
|
2019-06-27 04:02:48 +01:00
|
|
|
admin[theActor].waitCount--;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
/* Then check possible expression to wait for */
|
|
|
|
if (step->exp != 0) {
|
2019-07-01 20:56:55 -07:00
|
|
|
FUNC1(traceActor, flag, theActor)
|
|
|
|
if (flag)
|
|
|
|
printf(", SCRIPT %s[%ld], STEP %ld, Evaluating:>\n",
|
2019-06-27 04:02:48 +01:00
|
|
|
scriptName(theActor, admin[theActor].script),
|
|
|
|
(long)admin[theActor].script, (long)admin[theActor].step + 1);
|
2019-07-01 20:56:55 -07:00
|
|
|
FUNC1(evaluate, flag, step->exp)
|
|
|
|
if (!flag)
|
2019-06-27 04:02:48 +01:00
|
|
|
break; /* Break loop, don't execute step*/
|
|
|
|
}
|
|
|
|
/* OK, so finally let him do his thing */
|
|
|
|
admin[theActor].step++; /* Increment step number before executing... */
|
|
|
|
if (!isEndOfArray(step + 1) && (step + 1)->after != 0) {
|
2019-07-01 20:56:55 -07:00
|
|
|
FUNC1(evaluate, admin[theActor].waitCount, (step + 1)->after)
|
2019-06-27 04:02:48 +01:00
|
|
|
}
|
2019-07-01 20:56:55 -07:00
|
|
|
|
|
|
|
FUNC1(traceActor, flag, theActor)
|
|
|
|
if (flag)
|
2019-06-27 04:02:48 +01:00
|
|
|
printf(", SCRIPT %s[%ld], STEP %ld, Executing:>\n",
|
|
|
|
scriptName(theActor, admin[theActor].script),
|
|
|
|
(long)admin[theActor].script,
|
|
|
|
(long)admin[theActor].step);
|
2019-07-01 20:56:55 -07:00
|
|
|
CALL1(interpret, step->stms)
|
2019-06-27 04:02:48 +01:00
|
|
|
step++;
|
|
|
|
/* ... so that we can see if he failed or is USEing another script now */
|
|
|
|
if (fail || (admin[theActor].step != 0 && isEndOfArray(step)))
|
|
|
|
/* No more steps in this script, so stop him */
|
|
|
|
admin[theActor].script = 0;
|
|
|
|
fail = FALSE; /* fail only aborts one actor */
|
|
|
|
break; /* We have executed a script so leave loop */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (isEndOfArray(scr))
|
|
|
|
syserr("Unknown actor script.");
|
|
|
|
} else {
|
2019-07-01 20:56:55 -07:00
|
|
|
FUNC1(traceActor, flag, theActor)
|
|
|
|
if (flag) {
|
2019-06-27 04:02:48 +01:00
|
|
|
printf(", Idle>\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
current.instance = previousInstance;
|
2019-06-25 21:18:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/*======================================================================*/
|
2019-06-25 22:37:19 -07:00
|
|
|
void run(void) {
|
2019-06-26 20:34:53 -07:00
|
|
|
Stack theStack = NULL;
|
|
|
|
Context ctx;
|
2019-06-25 21:18:44 -07:00
|
|
|
|
2019-07-05 20:12:52 -07:00
|
|
|
openFiles();
|
|
|
|
load(ctx); // Load program
|
|
|
|
|
2019-06-26 20:34:53 -07:00
|
|
|
do {
|
2019-06-26 22:02:38 -07:00
|
|
|
ctx.clear();
|
2019-07-02 19:54:32 -07:00
|
|
|
if (ctx._break)
|
|
|
|
break;
|
2019-06-27 04:02:48 +01:00
|
|
|
|
2019-06-26 20:34:53 -07:00
|
|
|
if (theStack)
|
|
|
|
deleteStack(theStack);
|
2019-06-27 04:02:48 +01:00
|
|
|
|
2019-06-26 20:34:53 -07:00
|
|
|
theStack = createStack(STACKSIZE);
|
|
|
|
setInterpreterStack(theStack);
|
2019-06-27 04:02:48 +01:00
|
|
|
|
2019-06-26 20:34:53 -07:00
|
|
|
initStateStack();
|
2019-06-27 04:02:48 +01:00
|
|
|
|
2019-06-26 20:34:53 -07:00
|
|
|
// Initialise and start the adventure
|
2019-07-01 20:56:55 -07:00
|
|
|
init(ctx);
|
2019-06-27 04:02:48 +01:00
|
|
|
|
2019-06-26 20:34:53 -07:00
|
|
|
while (!g_vm->shouldQuit()) {
|
2019-06-29 14:42:50 -07:00
|
|
|
if (!(ctx._break && ctx._label == "forfeit")) {
|
|
|
|
if (ctx._break) {
|
2019-07-05 20:12:52 -07:00
|
|
|
assert(ctx._label.hasPrefix("return"));
|
|
|
|
|
|
|
|
if (ctx._label == "returnError") {
|
2019-06-29 14:42:50 -07:00
|
|
|
forgetGameState();
|
|
|
|
forceNewPlayerInput();
|
2019-07-05 20:12:52 -07:00
|
|
|
} else if (ctx._label == "returnUndo") {
|
2019-06-29 14:42:50 -07:00
|
|
|
forceNewPlayerInput();
|
|
|
|
}
|
2019-07-05 20:12:52 -07:00
|
|
|
|
2019-06-29 14:42:50 -07:00
|
|
|
ctx.clear();
|
|
|
|
} else {
|
|
|
|
if (debugOption)
|
2019-07-01 20:56:55 -07:00
|
|
|
debug(ctx, FALSE, 0, 0);
|
2019-06-29 14:42:50 -07:00
|
|
|
|
2019-07-01 20:56:55 -07:00
|
|
|
if (!ctx._break) {
|
|
|
|
if (stackDepth(theStack) != 0)
|
|
|
|
syserr("Stack is not empty in main loop");
|
2019-06-29 14:42:50 -07:00
|
|
|
|
2019-07-01 20:56:55 -07:00
|
|
|
if (!current.meta)
|
|
|
|
runPendingEvents(ctx);
|
|
|
|
}
|
2019-06-26 20:34:53 -07:00
|
|
|
}
|
2019-06-26 22:02:38 -07:00
|
|
|
|
2019-06-29 14:42:50 -07:00
|
|
|
recursionDepth = 0;
|
2019-06-26 22:02:38 -07:00
|
|
|
|
2019-06-29 14:42:50 -07:00
|
|
|
// Move all characters, hero first
|
|
|
|
rememberGameState();
|
|
|
|
current.meta = FALSE;
|
2019-06-26 20:34:53 -07:00
|
|
|
}
|
2019-06-27 04:02:48 +01:00
|
|
|
|
2019-06-29 14:42:50 -07:00
|
|
|
moveActor(ctx, header->theHero);
|
2019-06-27 04:02:48 +01:00
|
|
|
|
2019-06-29 14:42:50 -07:00
|
|
|
if (!ctx._break) {
|
|
|
|
if (gameStateChanged)
|
|
|
|
rememberCommands();
|
|
|
|
else
|
|
|
|
forgetGameState();
|
2019-06-27 04:02:48 +01:00
|
|
|
|
2019-06-29 14:42:50 -07:00
|
|
|
if (!current.meta) {
|
|
|
|
current.tick++;
|
2019-06-27 04:02:48 +01:00
|
|
|
|
2019-06-29 14:42:50 -07:00
|
|
|
// Remove this call? Since Eval is done up there after each event...
|
2019-07-01 20:56:55 -07:00
|
|
|
resetAndEvaluateRules(ctx, rules, header->version);
|
|
|
|
|
|
|
|
if (!ctx._break) {
|
|
|
|
// Then all the other actors...
|
|
|
|
for (uint i = 1; i <= header->instanceMax; i++) {
|
|
|
|
if (i != header->theHero && isAActor(i)) {
|
|
|
|
moveActor(ctx, i);
|
|
|
|
if (ctx._break)
|
|
|
|
break;
|
|
|
|
|
|
|
|
resetAndEvaluateRules(ctx, rules, header->version);
|
|
|
|
if (ctx._break)
|
|
|
|
break;
|
|
|
|
}
|
2019-06-29 14:42:50 -07:00
|
|
|
}
|
2019-06-26 20:34:53 -07:00
|
|
|
}
|
2019-06-29 14:42:50 -07:00
|
|
|
}
|
|
|
|
}
|
2019-07-06 15:56:26 -07:00
|
|
|
|
|
|
|
if (ctx._break && ctx._label == "restart")
|
|
|
|
break;
|
2019-06-27 04:02:48 +01:00
|
|
|
}
|
2019-06-26 20:34:53 -07:00
|
|
|
} while (!g_vm->shouldQuit() && ctx._label == "restart");
|
2019-06-25 21:18:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
} // End of namespace Alan3
|
|
|
|
} // End of namespace Glk
|