scummvm/engines/gargoyle/scott/scott.cpp

1264 lines
28 KiB
C++
Raw Normal View History

/* 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 "gargoyle/scott/scott.h"
#include "common/config-manager.h"
namespace Gargoyle {
namespace Scott {
Scott::Scott(OSystem *syst, const GargoyleGameDescription *gameDesc) : Glk(syst, gameDesc),
2018-11-10 05:42:22 +00:00
Items(nullptr), Rooms(nullptr), Verbs(nullptr), Nouns(nullptr), Messages(nullptr),
Actions(nullptr), CurrentCounter(0), SavedRoom(0), Options(0), Width(0), TopHeight(0),
split_screen(true), Bottom(0), Top(0), BitFlags(0), _saveSlot(-1) {
Common::fill(&NounText[0], &NounText[16], '\0');
Common::fill(&Counters[0], &Counters[16], 0);
Common::fill(&RoomSaved[0], &RoomSaved[16], 0);
}
void Scott::runGame(Common::SeekableReadStream *gameFile) {
int vb, no;
initialize();
Bottom = glk_window_open(0, 0, 0, wintype_TextBuffer, 1);
if (Bottom == nullptr)
glk_exit();
glk_set_window(Bottom);
if (Options & TRS80_STYLE) {
Width = 64;
TopHeight = 11;
} else {
Width = 80;
TopHeight = 10;
}
if (split_screen) {
Top = glk_window_open(Bottom, winmethod_Above | winmethod_Fixed, TopHeight, wintype_TextGrid, 0);
if (Top == nullptr) {
split_screen = 0;
Top = Bottom;
}
} else {
Top = Bottom;
}
output("ScummVM support adapted from Scott Free, A Scott Adams game driver in C.\n\n");
// Check for savegame
_saveSlot = ConfMan.hasKey("save_slot") ? ConfMan.getInt("save_slot") : -1;
// Load the game
2018-11-02 20:27:51 -07:00
loadDatabase(gameFile, (Options & DEBUGGING) ? 1 : 0);
// Main game loop
while (!shouldQuit()) {
glk_tick();
2018-11-02 20:27:51 -07:00
performActions(0, 0);
if (_saveSlot >= 0) {
// Load any savegame during startup
loadGameState(_saveSlot);
_saveSlot = -1;
}
2018-11-02 20:27:51 -07:00
look();
2018-11-02 20:27:51 -07:00
if (getInput(&vb, &no) == -1)
continue;
if (g_vm->shouldQuit())
break;
2018-11-02 20:27:51 -07:00
switch (performActions(vb, no)) {
case -1:
2018-11-02 20:27:51 -07:00
output("I don't understand your command. ");
break;
case -2:
2018-11-02 20:27:51 -07:00
output("I can't do that yet. ");
break;
default:
break;
}
// Brian Howarth games seem to use -1 for forever
if (Items[LIGHT_SOURCE].Location/*==-1*/ != DESTROYED && GameHeader.LightTime != -1) {
GameHeader.LightTime--;
if (GameHeader.LightTime < 1) {
BitFlags |= (1 << LIGHTOUTBIT);
if (Items[LIGHT_SOURCE].Location == CARRIED ||
2018-11-10 05:42:22 +00:00
Items[LIGHT_SOURCE].Location == MyLoc) {
if (Options & SCOTTLIGHT)
2018-11-02 20:27:51 -07:00
output("Light has run out! ");
else
2018-11-02 20:27:51 -07:00
output("Your light has run out. ");
}
2018-11-10 05:42:22 +00:00
if (Options & PREHISTORIC_LAMP)
Items[LIGHT_SOURCE].Location = DESTROYED;
} else if (GameHeader.LightTime < 25) {
if (Items[LIGHT_SOURCE].Location == CARRIED ||
2018-11-10 05:42:22 +00:00
Items[LIGHT_SOURCE].Location == MyLoc) {
2018-11-10 05:42:22 +00:00
if (Options & SCOTTLIGHT) {
2018-11-02 20:27:51 -07:00
output("Light runs out in ");
outputNumber(GameHeader.LightTime);
output(" turns. ");
} else {
if (GameHeader.LightTime % 5 == 0)
2018-11-02 20:27:51 -07:00
output("Your light is growing dim. ");
}
}
}
}
}
}
void Scott::initialize() {
if (ConfMan.hasKey("YOUARE")) {
if (ConfMan.getBool("YOUARE"))
Options |= YOUARE;
else
Options &= ~YOUARE;
}
if (gDebugLevel > 0)
Options |= DEBUGGING;
if (ConfMan.hasKey("SCOTTLIGHT") && ConfMan.getBool("SCOTTLIGHT"))
Options |= SCOTTLIGHT;
if (ConfMan.hasKey("TRS80_STYLE") && ConfMan.getBool("TRS80_STYLE"))
Options |= TRS80_STYLE;
if (ConfMan.hasKey("PREHISTORIC_LAMP") && ConfMan.getBool("PREHISTORIC_LAMP"))
Options |= PREHISTORIC_LAMP;
}
2018-11-02 20:27:51 -07:00
void Scott::display(winid_t w, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
Common::String msg = Common::String::vformat(fmt, ap);
va_end(ap);
glk_put_string_stream(glk_window_get_stream(w), msg.c_str());
}
2018-11-02 20:27:51 -07:00
void Scott::delay(int seconds) {
event_t ev;
if (!glk_gestalt(gestalt_Timer, 0))
return;
glk_request_timer_events(1000 * seconds);
do {
glk_select(&ev);
} while (ev.type != evtype_Timer && ev.type != evtype_Quit);
glk_request_timer_events(0);
}
2018-11-02 20:27:51 -07:00
void Scott::fatal(const char *x) {
2018-11-10 18:25:20 +00:00
error("%s", x);
}
2018-11-02 20:27:51 -07:00
void Scott::clearScreen(void) {
glk_window_clear(Bottom);
}
2018-11-02 20:27:51 -07:00
void *Scott::memAlloc(int size) {
void *t = (void *)malloc(size);
if (t == nullptr)
2018-11-02 20:27:51 -07:00
fatal("Out of memory");
return t;
}
2018-11-02 20:27:51 -07:00
bool Scott::randomPercent(uint n) {
return _random.getRandomNumber(99) < n;
}
2018-11-02 20:27:51 -07:00
int Scott::countCarried(void) {
int ct = 0;
int n = 0;
while (ct <= GameHeader.NumItems) {
if (Items[ct].Location == CARRIED)
n++;
ct++;
}
return n;
}
2018-11-02 20:27:51 -07:00
const char *Scott::mapSynonym(const char *word) {
int n = 1;
const char *tp;
2018-11-10 05:42:22 +00:00
static char lastword[16]; // Last non synonym
while (n <= GameHeader.NumWords) {
tp = Nouns[n];
if (*tp == '*')
tp++;
else
strcpy(lastword, tp);
if (xstrncasecmp(word, tp, GameHeader.WordLength) == 0)
return lastword;
n++;
}
return nullptr;
}
2018-11-02 20:27:51 -07:00
int Scott::matchUpItem(const char *text, int loc) {
const char *word = mapSynonym(text);
int ct = 0;
if (word == nullptr)
word = text;
while (ct <= GameHeader.NumItems) {
if (Items[ct].AutoGet && Items[ct].Location == loc &&
2018-11-10 05:42:22 +00:00
xstrncasecmp(Items[ct].AutoGet, word, GameHeader.WordLength) == 0)
return ct;
ct++;
}
return -1;
}
2018-11-02 20:27:51 -07:00
char *Scott::readString(Common::SeekableReadStream *f) {
char tmp[1024];
char *t;
int c, nc;
int ct = 0;
do {
c = f->readByte();
} while (f->pos() < f->size() && Common::isSpace(c));
if (c != '"') {
2018-11-02 20:27:51 -07:00
fatal("Initial quote expected");
}
for (;;) {
if (f->pos() >= f->size())
2018-11-02 20:27:51 -07:00
fatal("EOF in string");
c = f->readByte();
if (c == '"') {
nc = f->readByte();
if (nc != '"') {
f->seek(-1, SEEK_CUR);
break;
}
}
if (c == '`')
c = '"'; // pdd
// Ensure a valid Glk newline is sent.
if (c == '\n')
tmp[ct++] = 10;
// Special case: assume CR is part of CRLF in a DOS-formatted file, and ignore it.
else if (c == 13)
;
// Pass only ASCII to Glk; the other reasonable option would be to pass Latin-1,
// but it's probably safe to assume that Scott Adams games are ASCII only.
else if ((c >= 32 && c <= 126))
tmp[ct++] = c;
else
tmp[ct++] = '?';
2018-10-27 22:09:01 -07:00
}
tmp[ct] = 0;
2018-11-02 20:27:51 -07:00
t = (char *)memAlloc(ct + 1);
memcpy(t, tmp, ct + 1);
return t;
}
void Scott::loadDatabase(Common::SeekableReadStream *f, bool loud) {
int unused, ni, na, nw, nr, mc, pr, tr, wl, lt, mn, trm;
int ct;
int lo;
Action *ap;
Room *rp;
Item *ip;
2018-11-10 05:42:22 +00:00
// Load the header
readInts(f, 12, &unused, &ni, &na, &nw, &nr, &mc, &pr, &tr, &wl, &lt, &mn, &trm);
GameHeader.NumItems = ni;
2018-11-10 05:42:22 +00:00
Items = (Item *)memAlloc(sizeof(Item) * (ni + 1));
GameHeader.NumActions = na;
2018-11-10 05:42:22 +00:00
Actions = (Action *)memAlloc(sizeof(Action) * (na + 1));
GameHeader.NumWords = nw;
GameHeader.WordLength = wl;
2018-11-10 05:42:22 +00:00
Verbs = (const char **)memAlloc(sizeof(char *) * (nw + 1));
Nouns = (const char **)memAlloc(sizeof(char *) * (nw + 1));
GameHeader.NumRooms = nr;
2018-11-10 05:42:22 +00:00
Rooms = (Room *)memAlloc(sizeof(Room) * (nr + 1));
GameHeader.MaxCarry = mc;
GameHeader.PlayerRoom = pr;
GameHeader.Treasures = tr;
GameHeader.LightTime = lt;
LightRefill = lt;
GameHeader.NumMessages = mn;
2018-11-10 05:42:22 +00:00
Messages = (const char **)memAlloc(sizeof(char *) * (mn + 1));
GameHeader.TreasureRoom = trm;
// Load the actions
ct = 0;
ap = Actions;
if (loud)
debug("Reading %d actions.", na);
while (ct < na + 1) {
readInts(f, 8,
2018-11-10 05:42:22 +00:00
&ap->Vocab,
&ap->Condition[0],
&ap->Condition[1],
&ap->Condition[2],
&ap->Condition[3],
&ap->Condition[4],
&ap->action[0],
&ap->action[1]);
ap++;
ct++;
}
ct = 0;
if (loud)
debug("Reading %d word pairs.", nw);
2018-11-10 05:42:22 +00:00
while (ct < nw + 1) {
2018-11-02 20:27:51 -07:00
Verbs[ct] = readString(f);
Nouns[ct] = readString(f);
ct++;
}
ct = 0;
rp = Rooms;
if (loud)
debug("Reading %d rooms.", nr);
2018-11-10 05:42:22 +00:00
while (ct < nr + 1) {
readInts(f, 6,
2018-11-10 05:42:22 +00:00
&rp->Exits[0], &rp->Exits[1], &rp->Exits[2],
&rp->Exits[3], &rp->Exits[4], &rp->Exits[5]);
2018-11-02 20:27:51 -07:00
rp->Text = readString(f);
ct++;
rp++;
}
ct = 0;
if (loud)
debug("Reading %d messages.", mn);
2018-11-10 05:42:22 +00:00
while (ct < mn + 1) {
2018-11-02 20:27:51 -07:00
Messages[ct] = readString(f);
ct++;
}
ct = 0;
if (loud)
debug("Reading %d items.", ni);
ip = Items;
while (ct < ni + 1) {
2018-11-02 20:27:51 -07:00
ip->Text = readString(f);
ip->AutoGet = strchr(ip->Text, '/');
// Some games use // to mean no auto get/drop word!
if (ip->AutoGet && strcmp(ip->AutoGet, "//") && strcmp(ip->AutoGet, "/*")) {
char *t;
*ip->AutoGet++ = 0;
t = strchr(ip->AutoGet, '/');
if (t != nullptr)
*t = 0;
}
readInts(f, 1, &lo);
ip->Location = (unsigned char)lo;
ip->InitialLoc = ip->Location;
ip++;
ct++;
}
ct = 0;
// Discard Comment Strings
2018-11-10 05:42:22 +00:00
while (ct < na + 1) {
2018-11-02 20:27:51 -07:00
free(readString(f));
ct++;
}
readInts(f, 1, &ct);
if (loud)
debug("Version %d.%02d of Adventure ", ct / 100, ct % 100);
readInts(f, 1, &ct);
if (loud)
debug("%d.\nLoad Complete.\n", ct);
}
2018-11-02 20:27:51 -07:00
void Scott::output(const char *a) {
if (_saveSlot == -1)
display(Bottom, "%s", a);
}
2018-11-02 20:27:51 -07:00
void Scott::outputNumber(int a) {
display(Bottom, "%d", a);
}
2018-11-02 20:27:51 -07:00
void Scott::look(void) {
2018-11-10 18:25:20 +00:00
const char *const ExitNames[6] = { "North", "South", "East", "West", "Up", "Down" };
Room *r;
int ct, f;
int pos;
if (split_screen)
glk_window_clear(Top);
2018-11-10 05:42:22 +00:00
if ((BitFlags & (1 << DARKBIT)) && Items[LIGHT_SOURCE].Location != CARRIED
&& Items[LIGHT_SOURCE].Location != MyLoc) {
if (Options & YOUARE)
2018-11-02 20:27:51 -07:00
display(Top, "You can't see. It is too dark!\n");
else
2018-11-02 20:27:51 -07:00
display(Top, "I can't see. It is too dark!\n");
if (Options & TRS80_STYLE)
2018-11-02 20:27:51 -07:00
display(Top, TRS80_LINE);
return;
}
r = &Rooms[MyLoc];
if (*r->Text == '*')
2018-11-02 20:27:51 -07:00
display(Top, "%s\n", r->Text + 1);
else {
2018-11-10 05:42:22 +00:00
if (Options & YOUARE)
2018-11-02 20:27:51 -07:00
display(Top, "You are in a %s\n", r->Text);
else
2018-11-02 20:27:51 -07:00
display(Top, "I'm in a %s\n", r->Text);
}
ct = 0;
f = 0;
2018-11-02 20:27:51 -07:00
display(Top, "\nObvious exits: ");
2018-11-10 05:42:22 +00:00
while (ct < 6) {
if (r->Exits[ct] != 0) {
if (f == 0)
f = 1;
else
2018-11-02 20:27:51 -07:00
display(Top, ", ");
display(Top, "%s", ExitNames[ct]);
}
ct++;
}
if (f == 0)
2018-11-02 20:27:51 -07:00
display(Top, "none");
display(Top, ".\n");
ct = 0;
f = 0;
pos = 0;
while (ct <= GameHeader.NumItems) {
if (Items[ct].Location == MyLoc) {
if (f == 0) {
if (Options & YOUARE) {
2018-11-02 20:27:51 -07:00
display(Top, "\nYou can also see: ");
pos = 18;
} else {
2018-11-02 20:27:51 -07:00
display(Top, "\nI can also see: ");
pos = 16;
}
f++;
} else if (!(Options & TRS80_STYLE)) {
2018-11-02 20:27:51 -07:00
display(Top, " - ");
pos += 3;
}
if (pos + (int)strlen(Items[ct].Text) > (Width - 10)) {
pos = 0;
2018-11-02 20:27:51 -07:00
display(Top, "\n");
}
2018-11-02 20:27:51 -07:00
display(Top, "%s", Items[ct].Text);
pos += strlen(Items[ct].Text);
if (Options & TRS80_STYLE) {
2018-11-02 20:27:51 -07:00
display(Top, ". ");
pos += 2;
}
}
ct++;
}
2018-11-02 20:27:51 -07:00
display(Top, "\n");
if (Options & TRS80_STYLE)
2018-11-02 20:27:51 -07:00
display(Top, TRS80_LINE);
}
2018-11-02 20:27:51 -07:00
int Scott::whichWord(const char *word, const char **list) {
int n = 1;
int ne = 1;
const char *tp;
while (ne <= GameHeader.NumWords) {
tp = list[ne];
if (*tp == '*')
tp++;
else
n = ne;
if (xstrncasecmp(word, tp, GameHeader.WordLength) == 0)
return n;
ne++;
}
return -1;
}
2018-11-02 20:27:51 -07:00
void Scott::lineInput(char *buf, size_t n) {
event_t ev;
glk_request_line_event(Bottom, buf, n - 1, 0);
do {
glk_select(&ev);
if (ev.type == evtype_Quit)
return;
else if (ev.type == evtype_LineInput)
break;
else if (ev.type == evtype_Arrange && split_screen)
2018-11-02 20:27:51 -07:00
look();
} while (ev.type != evtype_Quit);
buf[ev.val1] = 0;
}
2018-11-02 20:27:51 -07:00
void Scott::saveGame(void) {
frefid_t ref = glk_fileref_create_by_prompt(fileusage_TextMode | fileusage_SavedGame,
2018-11-10 05:42:22 +00:00
filemode_Write, 0);
if (ref == nullptr)
return;
int slot = ref->_slotNumber;
Common::String desc = ref->_description;
glk_fileref_destroy(ref);
saveGameState(slot, desc);
}
Common::Error Scott::saveGameState(int slot, const Common::String &desc) {
Common::String msg;
FileReference ref(slot, desc, fileusage_TextMode | fileusage_SavedGame);
strid_t file = glk_stream_open_file(&ref, filemode_Write, 0);
if (file == nullptr)
return Common::kWritingFailed;
for (int ct = 0; ct < 16; ct++) {
msg = Common::String::format("%d %d\n", Counters[ct], RoomSaved[ct]);
glk_put_string_stream(file, msg.c_str());
}
2018-11-10 18:25:20 +00:00
msg = Common::String::format("%u %d %hd %d %d %hd\n",
2018-11-10 05:42:22 +00:00
BitFlags, (BitFlags & (1 << DARKBIT)) ? 1 : 0,
MyLoc, CurrentCounter, SavedRoom, GameHeader.LightTime);
glk_put_string_stream(file, msg.c_str());
for (int ct = 0; ct <= GameHeader.NumItems; ct++) {
msg = Common::String::format("%hd\n", (short)Items[ct].Location);
glk_put_string_stream(file, msg.c_str());
}
glk_stream_close(file, nullptr);
2018-11-02 20:27:51 -07:00
output("Saved.\n");
return Common::kNoError;
}
2018-11-02 20:27:51 -07:00
void Scott::loadGame(void) {
frefid_t ref = glk_fileref_create_by_prompt(fileusage_TextMode | fileusage_SavedGame,
2018-11-10 05:42:22 +00:00
filemode_Read, 0);
if (ref == nullptr)
return;
int slotNumber = ref->_slotNumber;
glk_fileref_destroy(ref);
loadGameState(slotNumber);
}
Common::Error Scott::loadGameState(int slot) {
strid_t file;
char buf[128];
int ct = 0;
short lo;
short darkFlag;
FileReference ref(slot, "", fileusage_SavedGame | fileusage_TextMode);
file = glk_stream_open_file(&ref, filemode_Read, 0);
if (file == nullptr)
return Common::kReadingFailed;
2018-11-10 05:42:22 +00:00
for (ct = 0; ct < 16; ct++) {
glk_get_line_stream(file, buf, sizeof buf);
sscanf(buf, "%d %d", &Counters[ct], &RoomSaved[ct]);
}
glk_get_line_stream(file, buf, sizeof buf);
2018-11-10 18:25:20 +00:00
sscanf(buf, "%u %hd %d %d %d %d\n",
2018-11-10 05:42:22 +00:00
&BitFlags, &darkFlag, &MyLoc, &CurrentCounter, &SavedRoom,
&GameHeader.LightTime);
// Backward compatibility
if (darkFlag)
BitFlags |= (1 << 15);
for (ct = 0; ct <= GameHeader.NumItems; ct++) {
glk_get_line_stream(file, buf, sizeof buf);
sscanf(buf, "%hd\n", &lo);
Items[ct].Location = (unsigned char)lo;
}
return Common::kNoError;
}
2018-11-02 20:27:51 -07:00
int Scott::getInput(int *vb, int *no) {
char buf[256];
char verb[10], noun[10];
int vc, nc;
int num;
do {
do {
2018-11-02 20:27:51 -07:00
output("\nTell me what to do ? ");
lineInput(buf, sizeof buf);
if (g_vm->shouldQuit())
return 0;
num = sscanf(buf, "%9s %9s", verb, noun);
} while (num == 0 || *buf == '\n');
2018-11-10 05:42:22 +00:00
if (xstrcasecmp(verb, "restore") == 0) {
2018-11-02 20:27:51 -07:00
loadGame();
return -1;
}
if (num == 1)
*noun = 0;
if (*noun == 0 && strlen(verb) == 1) {
switch (Common::isUpper((unsigned char)*verb) ? tolower((unsigned char)*verb) : *verb) {
2018-11-10 05:42:22 +00:00
case 'n':
strcpy(verb, "NORTH");
break;
case 'e':
strcpy(verb, "EAST");
break;
case 's':
strcpy(verb, "SOUTH");
break;
case 'w':
strcpy(verb, "WEST");
break;
case 'u':
strcpy(verb, "UP");
break;
case 'd':
strcpy(verb, "DOWN");
break;
// Brian Howarth interpreter also supports this
2018-11-10 05:42:22 +00:00
case 'i':
strcpy(verb, "INVENTORY");
break;
}
}
2018-11-02 20:27:51 -07:00
nc = whichWord(verb, Nouns);
// The Scott Adams system has a hack to avoid typing 'go'
if (nc >= 1 && nc <= 6) {
vc = 1;
} else {
2018-11-02 20:27:51 -07:00
vc = whichWord(verb, Verbs);
nc = whichWord(noun, Nouns);
}
*vb = vc;
*no = nc;
if (vc == -1) {
2018-11-02 20:27:51 -07:00
output("You use word(s) I don't know! ");
}
} while (vc == -1);
2018-11-10 05:42:22 +00:00
strcpy(NounText, noun); // Needed by GET/DROP hack
return 0;
}
2018-11-02 20:27:51 -07:00
int Scott::performLine(int ct) {
int continuation = 0;
int param[5], pptr = 0;
int act[4];
int cc = 0;
2018-11-10 05:42:22 +00:00
while (cc < 5) {
int cv, dv;
cv = Actions[ct].Condition[cc];
dv = cv / 20;
cv %= 20;
switch (cv) {
case 0:
param[pptr++] = dv;
break;
case 1:
if (Items[dv].Location != CARRIED)
return 0;
break;
case 2:
if (Items[dv].Location != MyLoc)
return 0;
break;
case 3:
2018-11-10 05:42:22 +00:00
if (Items[dv].Location != CARRIED &&
Items[dv].Location != MyLoc)
return 0;
break;
case 4:
if (MyLoc != dv)
return 0;
break;
case 5:
if (Items[dv].Location == MyLoc)
return 0;
break;
case 6:
if (Items[dv].Location == CARRIED)
return 0;
break;
case 7:
if (MyLoc == dv)
return 0;
break;
case 8:
2018-11-10 05:42:22 +00:00
if ((BitFlags & (1 << dv)) == 0)
return 0;
break;
case 9:
2018-11-10 05:42:22 +00:00
if (BitFlags & (1 << dv))
return 0;
break;
case 10:
2018-11-02 20:27:51 -07:00
if (countCarried() == 0)
return 0;
break;
case 11:
2018-11-02 20:27:51 -07:00
if (countCarried())
return 0;
break;
case 12:
if (Items[dv].Location == CARRIED || Items[dv].Location == MyLoc)
return 0;
break;
case 13:
if (Items[dv].Location == 0)
return 0;
break;
case 14:
if (Items[dv].Location)
return 0;
break;
case 15:
2018-11-10 05:42:22 +00:00
if (CurrentCounter > dv)
return 0;
break;
case 16:
if (CurrentCounter <= dv)
return 0;
break;
case 17:
if (Items[dv].Location != Items[dv].InitialLoc)
return 0;
break;
case 18:
if (Items[dv].Location == Items[dv].InitialLoc)
return 0;
break;
case 19:
// Only seen in Brian Howarth games so far
if (CurrentCounter != dv)
return 0;
break;
}
cc++;
}
// Actions
act[0] = Actions[ct].action[0];
act[2] = Actions[ct].action[1];
act[1] = act[0] % 150;
act[3] = act[2] % 150;
act[0] /= 150;
act[2] /= 150;
cc = 0;
pptr = 0;
2018-11-10 05:42:22 +00:00
while (cc < 4) {
if (act[cc] >= 1 && act[cc] < 52) {
2018-11-02 20:27:51 -07:00
output(Messages[act[cc]]);
output("\n");
} else if (act[cc] > 101) {
2018-11-02 20:27:51 -07:00
output(Messages[act[cc] - 50]);
output("\n");
} else {
switch (act[cc]) {
case 0:// NOP
break;
case 52:
if (countCarried() == GameHeader.MaxCarry) {
if (Options & YOUARE)
2018-11-02 20:27:51 -07:00
output("You are carrying too much. ");
else
2018-11-02 20:27:51 -07:00
output("I've too much to carry! ");
break;
}
Items[param[pptr++]].Location = CARRIED;
break;
case 53:
Items[param[pptr++]].Location = MyLoc;
break;
case 54:
MyLoc = param[pptr++];
break;
case 55:
Items[param[pptr++]].Location = 0;
break;
case 56:
BitFlags |= 1 << DARKBIT;
break;
case 57:
BitFlags &= ~(1 << DARKBIT);
break;
case 58:
BitFlags |= (1 << param[pptr++]);
break;
case 59:
Items[param[pptr++]].Location = 0;
break;
case 60:
BitFlags &= ~(1 << param[pptr++]);
break;
case 61:
2018-11-10 05:42:22 +00:00
if (Options & YOUARE)
2018-11-02 20:27:51 -07:00
output("You are dead.\n");
else
2018-11-02 20:27:51 -07:00
output("I am dead.\n");
BitFlags &= ~(1 << DARKBIT);
MyLoc = GameHeader.NumRooms;// It seems to be what the code says!
break;
case 62: {
// Bug fix for some systems - before it could get parameters wrong */
int i = param[pptr++];
Items[i].Location = param[pptr++];
break;
}
case 63:
doneit:
output("The game is now over.\n");
glk_exit();
break;
case 64:
break;
case 65: {
int i = 0;
int n = 0;
while (i <= GameHeader.NumItems) {
if (Items[i].Location == GameHeader.TreasureRoom &&
2018-11-10 05:42:22 +00:00
*Items[i].Text == '*')
n++;
i++;
}
2018-11-10 05:42:22 +00:00
if (Options & YOUARE)
2018-11-02 20:27:51 -07:00
output("You have stored ");
else
2018-11-02 20:27:51 -07:00
output("I've stored ");
outputNumber(n);
output(" treasures. On a scale of 0 to 100, that rates ");
outputNumber((n * 100) / GameHeader.Treasures);
output(".\n");
if (n == GameHeader.Treasures) {
2018-11-02 20:27:51 -07:00
output("Well done.\n");
goto doneit;
}
break;
}
case 66: {
int i = 0;
int f = 0;
2018-11-10 05:42:22 +00:00
if (Options & YOUARE)
2018-11-02 20:27:51 -07:00
output("You are carrying:\n");
else
2018-11-02 20:27:51 -07:00
output("I'm carrying:\n");
while (i <= GameHeader.NumItems) {
if (Items[i].Location == CARRIED) {
if (f == 1) {
if (Options & TRS80_STYLE)
2018-11-02 20:27:51 -07:00
output(". ");
else
2018-11-02 20:27:51 -07:00
output(" - ");
}
f = 1;
2018-11-02 20:27:51 -07:00
output(Items[i].Text);
}
i++;
}
if (f == 0)
2018-11-02 20:27:51 -07:00
output("Nothing");
output(".\n");
break;
}
case 67:
BitFlags |= (1 << 0);
break;
case 68:
BitFlags &= ~(1 << 0);
break;
case 69:
GameHeader.LightTime = LightRefill;
Items[LIGHT_SOURCE].Location = CARRIED;
BitFlags &= ~(1 << LIGHTOUTBIT);
break;
case 70:
clearScreen(); // pdd.
break;
case 71:
2018-11-02 20:27:51 -07:00
saveGame();
break;
case 72: {
int i1 = param[pptr++];
int i2 = param[pptr++];
int t = Items[i1].Location;
Items[i1].Location = Items[i2].Location;
Items[i2].Location = t;
break;
}
case 73:
continuation = 1;
break;
case 74:
Items[param[pptr++]].Location = CARRIED;
break;
case 75: {
int i1, i2;
i1 = param[pptr++];
i2 = param[pptr++];
Items[i1].Location = Items[i2].Location;
break;
}
case 76:
// Looking at adventure ..
break;
case 77:
if (CurrentCounter >= 0)
CurrentCounter--;
break;
case 78:
2018-11-02 20:27:51 -07:00
outputNumber(CurrentCounter);
break;
case 79:
CurrentCounter = param[pptr++];
break;
case 80: {
int t = MyLoc;
MyLoc = SavedRoom;
SavedRoom = t;
break;
}
case 81: {
// This is somewhat guessed. Claymorgue always seems to do
// select counter n, thing, select counter n, but uses one value that always
// seems to exist. Trying a few options I found this gave sane results on ageing
int t = param[pptr++];
int c1 = CurrentCounter;
CurrentCounter = Counters[t];
Counters[t] = c1;
break;
}
case 82:
CurrentCounter += param[pptr++];
break;
case 83:
CurrentCounter -= param[pptr++];
if (CurrentCounter < -1)
CurrentCounter = -1;
// Note: This seems to be needed. I don't yet know if there
// is a maximum value to limit too
break;
case 84:
2018-11-02 20:27:51 -07:00
output(NounText);
break;
case 85:
2018-11-02 20:27:51 -07:00
output(NounText);
output("\n");
break;
case 86:
2018-11-02 20:27:51 -07:00
output("\n");
break;
case 87: {
// Changed this to swap location<->roomflag[x] not roomflag 0 and x
int p = param[pptr++];
int sr = MyLoc;
MyLoc = RoomSaved[p];
RoomSaved[p] = sr;
break;
}
case 88:
2018-11-02 20:27:51 -07:00
delay(2);
break;
case 89:
pptr++;
// SAGA draw picture n
// Spectrum Seas of Blood - start combat ?
// Poking this into older spectrum games causes a crash
break;
default:
error("Unknown action %d [Param begins %d %d]\n",
2018-11-10 05:42:22 +00:00
act[cc], param[pptr], param[pptr + 1]);
break;
}
}
cc++;
}
return 1 + continuation;
}
2018-11-02 20:27:51 -07:00
int Scott::performActions(int vb, int no) {
2018-11-10 05:42:22 +00:00
static int disable_sysfunc = 0; // Recursion lock
int d = BitFlags & (1 << DARKBIT);
int ct = 0;
int fl;
int doagain = 0;
if (vb == 1 && no == -1) {
2018-11-02 20:27:51 -07:00
output("Give me a direction too.");
return 0;
}
if (vb == 1 && no >= 1 && no <= 6) {
int nl;
if (Items[LIGHT_SOURCE].Location == MyLoc ||
2018-11-10 05:42:22 +00:00
Items[LIGHT_SOURCE].Location == CARRIED)
d = 0;
if (d)
2018-11-02 20:27:51 -07:00
output("Dangerous to move in the dark! ");
nl = Rooms[MyLoc].Exits[no - 1];
if (nl != 0) {
MyLoc = nl;
return 0;
}
if (d) {
2018-11-10 05:42:22 +00:00
if (Options & YOUARE)
2018-11-02 20:27:51 -07:00
output("You fell down and broke your neck. ");
else
2018-11-02 20:27:51 -07:00
output("I fell down and broke my neck. ");
glk_exit();
}
2018-11-10 05:42:22 +00:00
if (Options & YOUARE)
2018-11-02 20:27:51 -07:00
output("You can't go in that direction. ");
else
2018-11-02 20:27:51 -07:00
output("I can't go in that direction. ");
return 0;
}
fl = -1;
while (ct <= GameHeader.NumActions) {
int vv, nv;
vv = Actions[ct].Vocab;
// Think this is now right. If a line we run has an action73
// run all following lines with vocab of 0,0
2018-11-10 05:42:22 +00:00
if (vb != 0 && (doagain && vv != 0))
break;
// Oops.. added this minor cockup fix 1.11
if (vb != 0 && !doagain && fl == 0)
break;
nv = vv % 150;
vv /= 150;
2018-11-10 05:42:22 +00:00
if ((vv == vb) || (doagain && Actions[ct].Vocab == 0)) {
2018-11-02 20:27:51 -07:00
if ((vv == 0 && randomPercent(nv)) || doagain ||
2018-11-10 05:42:22 +00:00
(vv != 0 && (nv == no || nv == 0))) {
int f2;
if (fl == -1)
fl = -2;
2018-11-02 20:27:51 -07:00
if ((f2 = performLine(ct)) > 0) {
// ahah finally figured it out !
fl = 0;
if (f2 == 2)
doagain = 1;
if (vb != 0 && doagain == 0)
return 0;
}
}
}
ct++;
// Previously this did not check ct against GameHeader.NumActions and would read
// past the end of Actions. I don't know what should happen on the last action,
// but doing nothing is better than reading one past the end.
// --Chris
if (ct <= GameHeader.NumActions && Actions[ct].Vocab != 0)
doagain = 0;
}
if (fl != 0 && disable_sysfunc == 0) {
int item;
if (Items[LIGHT_SOURCE].Location == MyLoc ||
2018-11-10 05:42:22 +00:00
Items[LIGHT_SOURCE].Location == CARRIED)
d = 0;
if (vb == 10 || vb == 18) {
// Yes they really _are_ hardcoded values
if (vb == 10) {
if (xstrcasecmp(NounText, "ALL") == 0) {
int i = 0;
int f = 0;
if (d) {
2018-11-02 20:27:51 -07:00
output("It is dark.\n");
return 0;
}
while (i <= GameHeader.NumItems) {
if (Items[i].Location == MyLoc && Items[i].AutoGet != nullptr && Items[i].AutoGet[0] != '*') {
2018-11-02 20:27:51 -07:00
no = whichWord(Items[i].AutoGet, Nouns);
2018-11-10 05:42:22 +00:00
disable_sysfunc = 1; // Don't recurse into auto get !
performActions(vb, no); // Recursively check each items table code
disable_sysfunc = 0;
2018-11-02 20:27:51 -07:00
if (countCarried() == GameHeader.MaxCarry) {
2018-11-10 05:42:22 +00:00
if (Options & YOUARE)
2018-11-02 20:27:51 -07:00
output("You are carrying too much. ");
else
2018-11-02 20:27:51 -07:00
output("I've too much to carry. ");
return 0;
}
Items[i].Location = CARRIED;
2018-11-02 20:27:51 -07:00
output(Items[i].Text);
output(": O.K.\n");
f = 1;
}
i++;
}
if (f == 0)
2018-11-02 20:27:51 -07:00
output("Nothing taken.");
return 0;
}
if (no == -1) {
2018-11-02 20:27:51 -07:00
output("What ? ");
return 0;
}
if (countCarried() == GameHeader.MaxCarry) {
2018-11-10 05:42:22 +00:00
if (Options & YOUARE)
2018-11-02 20:27:51 -07:00
output("You are carrying too much. ");
else
2018-11-02 20:27:51 -07:00
output("I've too much to carry. ");
return 0;
}
2018-11-02 20:27:51 -07:00
item = matchUpItem(NounText, MyLoc);
if (item == -1) {
2018-11-10 05:42:22 +00:00
if (Options & YOUARE)
2018-11-02 20:27:51 -07:00
output("It is beyond your power to do that. ");
else
2018-11-02 20:27:51 -07:00
output("It's beyond my power to do that. ");
return 0;
}
Items[item].Location = CARRIED;
2018-11-02 20:27:51 -07:00
output("O.K. ");
return 0;
}
if (vb == 18) {
if (xstrcasecmp(NounText, "ALL") == 0) {
int i = 0;
int f = 0;
while (i <= GameHeader.NumItems) {
if (Items[i].Location == CARRIED && Items[i].AutoGet && Items[i].AutoGet[0] != '*') {
2018-11-02 20:27:51 -07:00
no = whichWord(Items[i].AutoGet, Nouns);
disable_sysfunc = 1;
2018-11-02 20:27:51 -07:00
performActions(vb, no);
disable_sysfunc = 0;
Items[i].Location = MyLoc;
2018-11-02 20:27:51 -07:00
output(Items[i].Text);
output(": O.K.\n");
f = 1;
}
i++;
}
if (f == 0)
2018-11-02 20:27:51 -07:00
output("Nothing dropped.\n");
return 0;
}
if (no == -1) {
2018-11-02 20:27:51 -07:00
output("What ? ");
return 0;
}
2018-11-02 20:27:51 -07:00
item = matchUpItem(NounText, CARRIED);
if (item == -1) {
2018-11-10 05:42:22 +00:00
if (Options & YOUARE)
2018-11-02 20:27:51 -07:00
output("It's beyond your power to do that.\n");
else
2018-11-02 20:27:51 -07:00
output("It's beyond my power to do that.\n");
return 0;
}
Items[item].Location = MyLoc;
2018-11-02 20:27:51 -07:00
output("O.K. ");
return 0;
}
}
}
return fl;
}
int Scott::xstrcasecmp(const char *s1, const char *s2) {
const unsigned char
2018-11-10 05:42:22 +00:00
*us1 = (const unsigned char *)s1,
*us2 = (const unsigned char *)s2;
while (tolower(*us1) == tolower(*us2++))
if (*us1++ == '\0')
return (0);
return (tolower(*us1) - tolower(*--us2));
}
int Scott::xstrncasecmp(const char *s1, const char *s2, size_t n) {
if (n != 0) {
const unsigned char
2018-11-10 05:42:22 +00:00
*us1 = (const unsigned char *)s1,
*us2 = (const unsigned char *)s2;
do {
if (tolower(*us1) != tolower(*us2++))
return (tolower(*us1) - tolower(*--us2));
if (*us1++ == '\0')
break;
} while (--n != 0);
}
return 0;
}
void Scott::readInts(Common::SeekableReadStream *f, size_t count, ...) {
va_list va;
va_start(va, count);
2018-10-27 22:09:01 -07:00
unsigned char c = f->readByte();
for (size_t idx = 0; idx < count; ++idx) {
2018-10-27 22:09:01 -07:00
while (f->pos() < f->size() && Common::isSpace(c))
c = f->readByte();
// Get the next value
int *val = va_arg(va, int *);
*val = 0;
int factor = c == '-' ? -1 : 1;
if (factor == -1)
c = f->readByte();
while (Common::isDigit(c)) {
*val = (*val * 10) + (c - '0');
c = f->readByte();
}
*val *= factor; // Handle negatives
}
va_end(va);
}
} // End of namespace Scott
} // End of namespace Gargoyle