665 lines
17 KiB
C++
665 lines
17 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/alan3/exe.h"
|
|
#include "glk/alan3/actor.h"
|
|
#include "glk/alan3/alan3.h"
|
|
#include "glk/alan3/current.h"
|
|
#include "glk/alan3/decode.h"
|
|
#include "glk/alan3/event.h"
|
|
#include "glk/alan3/glkio.h"
|
|
#include "glk/alan3/lists.h"
|
|
#include "glk/alan3/instance.h"
|
|
#include "glk/alan3/inter.h"
|
|
#include "glk/alan3/memory.h"
|
|
#include "glk/alan3/msg.h"
|
|
#include "glk/alan3/output.h"
|
|
#include "glk/alan3/options.h"
|
|
#include "glk/alan3/save.h"
|
|
#include "glk/alan3/score.h"
|
|
#include "glk/alan3/state.h"
|
|
#include "glk/alan3/syserr.h"
|
|
#include "glk/alan3/sysdep.h"
|
|
#include "glk/alan3/types.h"
|
|
#include "glk/alan3/utils.h"
|
|
#include "glk/alan3/word.h"
|
|
#include "common/stream.h"
|
|
|
|
namespace Glk {
|
|
namespace Alan3 {
|
|
|
|
// PUBLIC DATA
|
|
Common::SeekableReadStream *textFile;
|
|
|
|
// PUBLIC DATA - formerly method statics
|
|
bool printFlag; // Printing already?
|
|
|
|
/* PRIVATE CONSTANTS */
|
|
|
|
#define WIDTH 80
|
|
|
|
/*======================================================================*/
|
|
void print(Aword fpos, Aword len) {
|
|
char str[2 * WIDTH]; /* String buffer */
|
|
uint outlen = 0; /* Current output length */
|
|
int ch = 0;
|
|
int i;
|
|
long savfp = 0; /* Temporary saved text file position */
|
|
bool savedPrintFlag = printFlag;
|
|
void *info = nullptr; /* Saved decoding info */
|
|
|
|
|
|
if (len == 0) return;
|
|
|
|
if (isHere(HERO, /*TRUE*/ DIRECT)) { /* Check if the player will see it */
|
|
if (printFlag) { /* Already printing? */
|
|
/* Save current text file position and/or decoding info */
|
|
if (header->pack)
|
|
info = pushDecode();
|
|
else
|
|
savfp = textFile->pos();
|
|
}
|
|
printFlag = TRUE; /* We're printing now! */
|
|
|
|
/* Position to start of text */
|
|
textFile->seek(fpos + header->stringOffset);
|
|
|
|
if (header->pack)
|
|
startDecoding();
|
|
for (outlen = 0; outlen != len; outlen = outlen + strlen(str)) {
|
|
/* Fill the buffer from the beginning */
|
|
for (i = 0; i <= WIDTH || (i > WIDTH && ch != ' '); i++) {
|
|
if (outlen + i == len) /* No more characters? */
|
|
break;
|
|
if (header->pack)
|
|
ch = decodeChar();
|
|
else
|
|
ch = textFile->readByte();
|
|
|
|
if (ch == EOFChar)
|
|
break; // Or end of text?
|
|
|
|
str[i] = ch;
|
|
}
|
|
str[i] = '\0';
|
|
|
|
output(str);
|
|
}
|
|
|
|
/* And restore */
|
|
printFlag = savedPrintFlag;
|
|
if (printFlag) {
|
|
if (header->pack)
|
|
popDecode(info);
|
|
else
|
|
textFile->seek(savfp);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*======================================================================*/
|
|
void sys(Aword fpos, Aword len) {
|
|
syserr("sys calls are unsupported");
|
|
}
|
|
|
|
|
|
/*======================================================================*/
|
|
char *getStringFromFile(Aword fpos, Aword len) {
|
|
char *buf = (char *)allocate(len + 1);
|
|
char *bufp = buf;
|
|
|
|
/* Position to start of text */
|
|
textFile->seek(fpos + header->stringOffset);
|
|
|
|
if (header->pack)
|
|
startDecoding();
|
|
while (len--)
|
|
if (header->pack)
|
|
*(bufp++) = decodeChar();
|
|
else
|
|
*(bufp++) = textFile->readByte();
|
|
|
|
/* Terminate string with zero */
|
|
*bufp = '\0';
|
|
|
|
return buf;
|
|
}
|
|
|
|
|
|
|
|
/*======================================================================*/
|
|
void score(Aword sc) {
|
|
if (sc == 0) {
|
|
ParameterArray messageParameters = newParameterArray();
|
|
addParameterForInteger(messageParameters, current.score);
|
|
addParameterForInteger(messageParameters, header->maximumScore);
|
|
addParameterForInteger(messageParameters, current.tick);
|
|
printMessageWithParameters(M_SCORE, messageParameters);
|
|
freeParameterArray(messageParameters);
|
|
} else {
|
|
current.score += scores[sc - 1];
|
|
scores[sc - 1] = 0;
|
|
gameStateChanged = TRUE;
|
|
}
|
|
}
|
|
|
|
|
|
/*======================================================================*/
|
|
void visits(Aword v) {
|
|
current.visits = v;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void sayUndoneCommand(char *words) {
|
|
static Parameter *messageParameters = nullptr;
|
|
messageParameters = (Parameter *)ensureParameterArrayAllocated(messageParameters);
|
|
|
|
current.location = where(HERO, DIRECT);
|
|
clearParameterArray(messageParameters);
|
|
addParameterForString(&messageParameters[0], words);
|
|
setEndOfArray(&messageParameters[1]);
|
|
printMessageWithParameters(M_UNDONE, messageParameters);
|
|
}
|
|
|
|
|
|
/*======================================================================*/
|
|
void undo(CONTEXT) {
|
|
forgetGameState();
|
|
if (anySavedState()) {
|
|
recallGameState();
|
|
sayUndoneCommand(recreatePlayerCommand());
|
|
} else {
|
|
printMessage(M_NO_UNDO);
|
|
}
|
|
|
|
LONG_JUMP_LABEL("returnUndo")
|
|
}
|
|
|
|
|
|
/*======================================================================*/
|
|
void quitGame(CONTEXT) {
|
|
char buf[80];
|
|
bool flag;
|
|
|
|
current.location = where(HERO, DIRECT);
|
|
para();
|
|
while (!g_vm->shouldQuit()) {
|
|
col = 1;
|
|
CALL0(g_io->statusLine)
|
|
printMessage(M_QUITACTION);
|
|
|
|
FUNC2(g_io->readLine, flag, buf, 80)
|
|
if (!flag)
|
|
CALL1(terminate, 0)
|
|
|
|
if (scumm_stricmp(buf, "restart") == 0) {
|
|
LONG_JUMP_LABEL("restart")
|
|
|
|
} else if (scumm_stricmp(buf, "restore") == 0) {
|
|
g_vm->loadGame();
|
|
return;
|
|
|
|
} else if (scumm_stricmp(buf, "quit") == 0) {
|
|
CALL1(terminate, 0)
|
|
|
|
} else if (scumm_stricmp(buf, "undo") == 0) {
|
|
if (gameStateChanged) {
|
|
rememberCommands();
|
|
rememberGameState();
|
|
CALL0(undo)
|
|
} else {
|
|
if (anySavedState()) {
|
|
recallGameState();
|
|
sayUndoneCommand(playerWordsAsCommandString());
|
|
} else {
|
|
printMessage(M_NO_UNDO);
|
|
}
|
|
|
|
LONG_JUMP_LABEL("returnUndo")
|
|
}
|
|
}
|
|
}
|
|
|
|
syserr("Fallthrough in QUIT");
|
|
}
|
|
|
|
|
|
|
|
/*======================================================================*/
|
|
void restartGame(CONTEXT) {
|
|
Aint previousLocation = current.location;
|
|
current.location = where(HERO, DIRECT);
|
|
para();
|
|
|
|
bool flag;
|
|
FUNC1(confirm, flag, M_REALLY)
|
|
if (flag) {
|
|
LONG_JUMP_LABEL("restart")
|
|
}
|
|
|
|
current.location = previousLocation;
|
|
}
|
|
|
|
|
|
|
|
/*======================================================================*/
|
|
void cancelEvent(Aword theEvent) {
|
|
int i;
|
|
|
|
for (i = eventQueueTop - 1; i >= 0; i--)
|
|
if (eventQueue[i].event == (int)theEvent) {
|
|
while (i < eventQueueTop - 1) {
|
|
eventQueue[i].event = eventQueue[i + 1].event;
|
|
eventQueue[i].after = eventQueue[i + 1].after;
|
|
eventQueue[i].where = eventQueue[i + 1].where;
|
|
i++;
|
|
}
|
|
eventQueueTop--;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void increaseEventQueue(void) {
|
|
eventQueue = (EventQueueEntry *)realloc(eventQueue, (eventQueueTop + 2) * sizeof(EventQueueEntry));
|
|
if (eventQueue == nullptr) syserr("Out of memory in increaseEventQueue()");
|
|
|
|
eventQueueSize = eventQueueTop + 2;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void moveEvent(int to, int from) {
|
|
eventQueue[to].event = eventQueue[from].event;
|
|
eventQueue[to].after = eventQueue[from].after;
|
|
eventQueue[to].where = eventQueue[from].where;
|
|
}
|
|
|
|
|
|
/*======================================================================*/
|
|
void schedule(Aword event, Aword where, Aword after) {
|
|
uint i;
|
|
|
|
if (event == 0) syserr("NULL event");
|
|
|
|
cancelEvent(event);
|
|
/* Check for overflow */
|
|
if (eventQueue == nullptr || eventQueueTop == eventQueueSize) {
|
|
increaseEventQueue();
|
|
assert(eventQueue);
|
|
}
|
|
|
|
/* Bubble this event down */
|
|
for (i = eventQueueTop; i >= 1 && eventQueue[i - 1].after <= (int)after; i--) {
|
|
moveEvent(i, i - 1);
|
|
}
|
|
|
|
eventQueue[i].after = after;
|
|
eventQueue[i].where = where;
|
|
eventQueue[i].event = event;
|
|
eventQueueTop++;
|
|
}
|
|
|
|
|
|
// TODO Move to string.c?
|
|
/*======================================================================*/
|
|
Aptr concat(Aptr as1, Aptr as2) {
|
|
char *s1 = (char *)fromAptr(as1);
|
|
char *s2 = (char *)fromAptr(as2);
|
|
size_t ln = strlen(s1) + strlen(s2) + 1;
|
|
char *result = (char *)allocate(ln);
|
|
Common::strcpy_s(result, ln, s1);
|
|
Common::strcat_s(result, ln, s2);
|
|
return toAptr(result);
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static char *stripCharsFromStringForwards(int count, char *initialString, char **theRest) {
|
|
int stripPosition;
|
|
char *strippedString;
|
|
char *rest;
|
|
|
|
if (count > (int)strlen(initialString))
|
|
stripPosition = strlen(initialString);
|
|
else
|
|
stripPosition = count;
|
|
rest = scumm_strdup(&initialString[stripPosition]);
|
|
strippedString = scumm_strdup(initialString);
|
|
strippedString[stripPosition] = '\0';
|
|
*theRest = rest;
|
|
return strippedString;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static char *stripCharsFromStringBackwards(Aint count, char *initialString, char **theRest) {
|
|
int stripPosition;
|
|
char *strippedString;
|
|
char *rest;
|
|
|
|
if (count > (int)strlen(initialString))
|
|
stripPosition = 0;
|
|
else
|
|
stripPosition = strlen(initialString) - count;
|
|
strippedString = scumm_strdup(&initialString[stripPosition]);
|
|
rest = scumm_strdup(initialString);
|
|
rest[stripPosition] = '\0';
|
|
*theRest = rest;
|
|
return strippedString;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static int countLeadingBlanks(char *string, int position) {
|
|
static char blanks[] = " ";
|
|
return strspn(&string[position], blanks);
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static int skipWordForwards(char *string, int position) {
|
|
char separators[] = " .,?";
|
|
|
|
uint i;
|
|
|
|
for (i = position; i <= strlen(string) && strchr(separators, string[i]) == nullptr; i++)
|
|
;
|
|
return i;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static char *stripWordsFromStringForwards(Aint count, char *initialString, char **theRest) {
|
|
int skippedChars;
|
|
int position = 0;
|
|
char *stripped;
|
|
int i;
|
|
|
|
for (i = count; i > 0; i--) {
|
|
/* Ignore any initial blanks */
|
|
skippedChars = countLeadingBlanks(initialString, position);
|
|
position += skippedChars;
|
|
position = skipWordForwards(initialString, position);
|
|
}
|
|
|
|
stripped = (char *)allocate(position + 1);
|
|
strncpy(stripped, initialString, position);
|
|
stripped[position] = '\0';
|
|
|
|
skippedChars = countLeadingBlanks(initialString, position);
|
|
*theRest = scumm_strdup(&initialString[position + skippedChars]);
|
|
|
|
return (stripped);
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static int skipWordBackwards(char *string, int position) {
|
|
char separators[] = " .,?";
|
|
int i;
|
|
|
|
for (i = position; i > 0 && strchr(separators, string[i - 1]) == nullptr; i--)
|
|
;
|
|
return i;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static int countTrailingBlanks(char *string, int position) {
|
|
int skippedChars, i;
|
|
skippedChars = 0;
|
|
|
|
if (position > (int)strlen(string) - 1)
|
|
syserr("position > length in countTrailingBlanks");
|
|
for (i = position; i >= 0 && string[i] == ' '; i--)
|
|
skippedChars++;
|
|
return (skippedChars);
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static char *stripWordsFromStringBackwards(Aint count, char *initialString, char **theRest) {
|
|
int skippedChars;
|
|
char *stripped;
|
|
int strippedLength;
|
|
int position = strlen(initialString);
|
|
int i;
|
|
|
|
for (i = count; i > 0 && position > 0; i--) {
|
|
position -= 1;
|
|
/* Ignore trailing blanks */
|
|
skippedChars = countTrailingBlanks(initialString, position);
|
|
if (position - skippedChars < 0) break; /* No more words to strip */
|
|
position -= skippedChars;
|
|
position = skipWordBackwards(initialString, position);
|
|
}
|
|
|
|
skippedChars = countLeadingBlanks(initialString, 0);
|
|
strippedLength = strlen(initialString) - position - skippedChars;
|
|
stripped = (char *)allocate(strippedLength + 1);
|
|
strncpy(stripped, &initialString[position + skippedChars], strippedLength);
|
|
stripped[strippedLength] = '\0';
|
|
|
|
if (position > 0) {
|
|
skippedChars = countTrailingBlanks(initialString, position - 1);
|
|
position -= skippedChars;
|
|
}
|
|
*theRest = scumm_strdup(initialString);
|
|
(*theRest)[position] = '\0';
|
|
return (stripped);
|
|
}
|
|
|
|
|
|
|
|
/*======================================================================*/
|
|
Aptr strip(bool stripFromBeginningNotEnd, int count, bool stripWordsNotChars, int id, int atr) {
|
|
char *initialString = (char *)fromAptr(getInstanceAttribute(id, atr));
|
|
char *theStripped;
|
|
char *theRest;
|
|
|
|
if (stripFromBeginningNotEnd) {
|
|
if (stripWordsNotChars)
|
|
theStripped = stripWordsFromStringForwards(count, initialString, &theRest);
|
|
else
|
|
theStripped = stripCharsFromStringForwards(count, initialString, &theRest);
|
|
} else {
|
|
if (stripWordsNotChars)
|
|
theStripped = stripWordsFromStringBackwards(count, initialString, &theRest);
|
|
else
|
|
theStripped = stripCharsFromStringBackwards(count, initialString, &theRest);
|
|
}
|
|
setInstanceStringAttribute(id, atr, theRest);
|
|
return toAptr(theStripped);
|
|
}
|
|
|
|
|
|
/*======================================================================*/
|
|
int getContainerMember(int container, int index, bool directly) {
|
|
uint i;
|
|
Aint count = 0;
|
|
|
|
for (i = 1; i <= header->instanceMax; i++) {
|
|
if (isIn(i, container, DIRECT)) {
|
|
count++;
|
|
if (count == index)
|
|
return i;
|
|
}
|
|
}
|
|
apperr("Index not in container in 'containerMember()'");
|
|
return 0;
|
|
}
|
|
|
|
|
|
/***********************************************************************\
|
|
|
|
Description Handling
|
|
|
|
\***********************************************************************/
|
|
|
|
|
|
/*======================================================================*/
|
|
void empty(CONTEXT, int cnt, int whr) {
|
|
uint i;
|
|
|
|
for (i = 1; i <= header->instanceMax; i++)
|
|
if (isIn(i, cnt, DIRECT))
|
|
CALL2(locate, i, whr)
|
|
}
|
|
|
|
|
|
|
|
/*======================================================================*/
|
|
void use(CONTEXT, int actor, int script) {
|
|
char str[80];
|
|
StepEntry *step;
|
|
|
|
if (!isAActor(actor)) {
|
|
Common::sprintf_s(str, "Instance is not an Actor (%d).", actor);
|
|
syserr(str);
|
|
}
|
|
|
|
admin[actor].script = script;
|
|
admin[actor].step = 0;
|
|
step = stepOf(actor);
|
|
if (step != nullptr && step->after != 0) {
|
|
FUNC1(evaluate, admin[actor].waitCount, step->after)
|
|
}
|
|
|
|
gameStateChanged = TRUE;
|
|
}
|
|
|
|
/*======================================================================*/
|
|
void stop(int act) {
|
|
char str[80];
|
|
|
|
if (!isAActor(act)) {
|
|
Common::sprintf_s(str, "Instance is not an Actor (%d).", act);
|
|
syserr(str);
|
|
}
|
|
|
|
admin[act].script = 0;
|
|
admin[act].step = 0;
|
|
|
|
gameStateChanged = TRUE;
|
|
}
|
|
|
|
|
|
|
|
static int randomValue = 0;
|
|
/*----------------------------------------------------------------------*/
|
|
int randomInteger(int from, int to) {
|
|
if (regressionTestOption) {
|
|
int ret = from + randomValue;
|
|
/* Generate them in sequence */
|
|
if (ret > to) {
|
|
ret = from;
|
|
randomValue = 1;
|
|
} else if (ret == to)
|
|
randomValue = 0;
|
|
else
|
|
randomValue++;
|
|
return ret;
|
|
} else {
|
|
if (to == from)
|
|
return to;
|
|
else if (to > from)
|
|
return (g_vm->getRandomNumber(0x7fffffff) / 10) % (to - from + 1) + from;
|
|
else
|
|
return (g_vm->getRandomNumber(0x7fffffff) / 10) % (from - to + 1) + to;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
bool between(int val, int low, int high) {
|
|
if (high > low)
|
|
return low <= val && val <= high;
|
|
else
|
|
return high <= val && val <= low;
|
|
}
|
|
|
|
|
|
|
|
/*======================================================================*/
|
|
bool contains(Aptr string, Aptr substring) {
|
|
bool found;
|
|
|
|
strlow((char *)fromAptr(string));
|
|
strlow((char *)fromAptr(substring));
|
|
|
|
found = (strstr((char *)fromAptr(string), (char *)fromAptr(substring)) != nullptr);
|
|
|
|
return found;
|
|
}
|
|
|
|
|
|
/*======================================================================*/
|
|
bool streq(char a[], char b[]) {
|
|
bool eq;
|
|
|
|
strlow(a);
|
|
strlow(b);
|
|
|
|
eq = (strcmp(a, b) == 0);
|
|
|
|
return eq;
|
|
}
|
|
|
|
|
|
|
|
/*======================================================================*/
|
|
void startTranscript(void) {
|
|
if (logFile == nullptr) {
|
|
Common::String filename = g_vm->getTargetName() + ".log";
|
|
|
|
uint fileUsage = transcriptOption ? fileusage_Transcript : fileusage_InputRecord;
|
|
frefid_t logFileRef = g_vm->glk_fileref_create_by_name(fileUsage, filename.c_str(), 0);
|
|
logFile = g_vm->glk_stream_open_file(logFileRef, filemode_Write, 0);
|
|
|
|
if (logFile == nullptr) {
|
|
transcriptOption = FALSE;
|
|
logOption = FALSE;
|
|
} else {
|
|
transcriptOption = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*======================================================================*/
|
|
void stopTranscript(void) {
|
|
if (logFile != nullptr) {
|
|
if (transcriptOption || logOption)
|
|
delete logFile;
|
|
|
|
logFile = nullptr;
|
|
transcriptOption = FALSE;
|
|
logOption = FALSE;
|
|
}
|
|
}
|
|
|
|
} // End of namespace Alan3
|
|
} // End of namespace Glk
|