mirror of
https://github.com/scummvm/scummvm.git
synced 2026-06-20 05:45:29 +00:00
1219 lines
28 KiB
C++
1219 lines
28 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 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/scott/scott.h"
|
|
#include "glk/quetzal.h"
|
|
#include "common/config-manager.h"
|
|
#include "common/translation.h"
|
|
|
|
namespace Glk {
|
|
namespace Scott {
|
|
|
|
Scott::Scott(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc),
|
|
_currentCounter(0), _savedRoom(0), _options(0), _width(0), _topHeight(0), _splitScreen(true),
|
|
_bottomWindow(0), _topWindow(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() {
|
|
int vb, no;
|
|
initialize();
|
|
|
|
_bottomWindow = glk_window_open(0, 0, 0, wintype_TextBuffer, 1);
|
|
if (_bottomWindow == nullptr) {
|
|
glk_exit();
|
|
return;
|
|
}
|
|
glk_set_window(_bottomWindow);
|
|
|
|
if (_options & TRS80_STYLE) {
|
|
_width = 64;
|
|
_topHeight = 11;
|
|
} else {
|
|
_width = 80;
|
|
_topHeight = 10;
|
|
}
|
|
|
|
if (_splitScreen) {
|
|
_topWindow = glk_window_open(_bottomWindow, winmethod_Above | winmethod_Fixed, _topHeight, wintype_TextGrid, 0);
|
|
if (_topWindow == nullptr) {
|
|
_splitScreen = 0;
|
|
_topWindow = _bottomWindow;
|
|
}
|
|
} else {
|
|
_topWindow = _bottomWindow;
|
|
}
|
|
|
|
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
|
|
loadDatabase(&_gameFile, (_options & DEBUGGING) ? 1 : 0);
|
|
|
|
// Main game loop
|
|
while (!shouldQuit()) {
|
|
glk_tick();
|
|
|
|
performActions(0, 0);
|
|
if (shouldQuit())
|
|
break;
|
|
|
|
if (_saveSlot >= 0) {
|
|
// Load any savegame during startup
|
|
loadGameState(_saveSlot);
|
|
_saveSlot = -1;
|
|
}
|
|
|
|
look();
|
|
|
|
if (getInput(&vb, &no) == -1)
|
|
continue;
|
|
if (g_vm->shouldQuit())
|
|
break;
|
|
|
|
switch (performActions(vb, no)) {
|
|
case -1:
|
|
output(_("I don't understand your command. "));
|
|
break;
|
|
case -2:
|
|
output(_("I can't do that yet. "));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (shouldQuit())
|
|
return;
|
|
|
|
// Brian Howarth games seem to use -1 for forever
|
|
if (_items[LIGHT_SOURCE]._location != DESTROYED && _gameHeader._lightTime != -1) {
|
|
_gameHeader._lightTime--;
|
|
if (_gameHeader._lightTime < 1) {
|
|
_bitFlags |= (1 << LIGHTOUTBIT);
|
|
if (_items[LIGHT_SOURCE]._location == CARRIED ||
|
|
_items[LIGHT_SOURCE]._location == MY_LOC) {
|
|
if (_options & SCOTTLIGHT)
|
|
output(_("Light has run out! "));
|
|
else
|
|
output(_("Your light has run out. "));
|
|
}
|
|
if (_options & PREHISTORIC_LAMP)
|
|
_items[LIGHT_SOURCE]._location = DESTROYED;
|
|
} else if (_gameHeader._lightTime < 25) {
|
|
if (_items[LIGHT_SOURCE]._location == CARRIED ||
|
|
_items[LIGHT_SOURCE]._location == MY_LOC) {
|
|
|
|
if (_options & SCOTTLIGHT) {
|
|
output(_("Light runs out in "));
|
|
outputNumber(_gameHeader._lightTime);
|
|
output(_(" turns. "));
|
|
} else {
|
|
if (_gameHeader._lightTime % 5 == 0)
|
|
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;
|
|
}
|
|
|
|
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());
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
void Scott::fatal(const char *x) {
|
|
error("%s", x);
|
|
}
|
|
|
|
void Scott::clearScreen(void) {
|
|
glk_window_clear(_bottomWindow);
|
|
}
|
|
|
|
bool Scott::randomPercent(uint n) {
|
|
return _random.getRandomNumber(99) < n;
|
|
}
|
|
|
|
int Scott::countCarried(void) {
|
|
int ct = 0;
|
|
int n = 0;
|
|
while (ct <= _gameHeader._numItems) {
|
|
if (_items[ct]._location == CARRIED)
|
|
n++;
|
|
ct++;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
const char *Scott::mapSynonym(const char *word) {
|
|
int n = 1;
|
|
const char *tp;
|
|
static char lastword[16]; // Last non synonym
|
|
while (n <= _gameHeader._numWords) {
|
|
tp = _nouns[n].c_str();
|
|
if (*tp == '*')
|
|
tp++;
|
|
else
|
|
strcpy(lastword, tp);
|
|
if (xstrncasecmp(word, tp, _gameHeader._wordLength) == 0)
|
|
return lastword;
|
|
n++;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
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.empty() && _items[ct]._location == loc &&
|
|
xstrncasecmp(_items[ct]._autoGet.c_str(), word, _gameHeader._wordLength) == 0)
|
|
return ct;
|
|
ct++;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
Common::String Scott::readString(Common::SeekableReadStream *f) {
|
|
char tmp[1024];
|
|
int c, nc;
|
|
int ct = 0;
|
|
do {
|
|
c = f->readByte();
|
|
} while (f->pos() < f->size() && Common::isSpace(c));
|
|
if (c != '"') {
|
|
fatal("Initial quote expected");
|
|
}
|
|
|
|
for (;;) {
|
|
if (f->pos() >= f->size())
|
|
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++] = '?';
|
|
}
|
|
|
|
tmp[ct] = 0;
|
|
return Common::String(tmp);
|
|
}
|
|
|
|
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;
|
|
|
|
// Load the header
|
|
readInts(f, 12, &unused, &ni, &na, &nw, &nr, &mc, &pr, &tr, &wl, <, &mn, &trm);
|
|
|
|
_gameHeader._numItems = ni;
|
|
_items.resize(ni + 1);
|
|
_gameHeader._numActions = na;
|
|
_actions.resize(na + 1);
|
|
_gameHeader._numWords = nw;
|
|
_gameHeader._wordLength = wl;
|
|
_verbs.resize(nw + 1);
|
|
_nouns.resize(nw + 1);
|
|
_gameHeader._numRooms = nr;
|
|
_rooms.resize(nr + 1);
|
|
_gameHeader._maxCarry = mc;
|
|
_gameHeader._playerRoom = pr;
|
|
_gameHeader._treasures = tr;
|
|
_gameHeader._lightTime = lt;
|
|
_lightRefill = lt;
|
|
_gameHeader._numMessages = mn;
|
|
_messages.resize(mn + 1);
|
|
_gameHeader._treasureRoom = trm;
|
|
|
|
// Load the actions
|
|
if (loud)
|
|
debug("Reading %d actions.", na);
|
|
|
|
for (int idx = 0; idx < na + 1; ++idx) {
|
|
Action &a = _actions[idx];
|
|
readInts(f, 8,
|
|
&a._vocab, &a._condition[0], &a._condition[1], &a._condition[2],
|
|
&a._condition[3], &a._condition[4], &a._action[0], &a._action[1]);
|
|
}
|
|
|
|
if (loud)
|
|
debug("Reading %d word pairs.", nw);
|
|
for (int idx = 0; idx < nw + 1; ++idx) {
|
|
_verbs[idx] = readString(f);
|
|
_nouns[idx] = readString(f);
|
|
}
|
|
|
|
if (loud)
|
|
debug("Reading %d rooms.", nr);
|
|
for (int idx = 0; idx < nr + 1; ++idx) {
|
|
Room &r = _rooms[idx];
|
|
readInts(f, 6, &r._exits[0], &r._exits[1], &r._exits[2],
|
|
&r._exits[3], &r._exits[4], &r._exits[5]);
|
|
r._text = readString(f);
|
|
}
|
|
|
|
if (loud)
|
|
debug("Reading %d messages.", mn);
|
|
for (int idx = 0; idx < mn + 1; ++idx)
|
|
_messages[idx] = readString(f);
|
|
|
|
if (loud)
|
|
debug("Reading %d items.", ni);
|
|
for (int idx = 0; idx < ni + 1; ++idx) {
|
|
Item &i = _items[idx];
|
|
i._text = readString(f);
|
|
|
|
const char *p = strchr(i._text.c_str(), '/');
|
|
if (p) {
|
|
i._autoGet = Common::String(p);
|
|
|
|
// Some games use // to mean no auto get/drop word!
|
|
if (!i._autoGet.hasPrefix("//") && !i._autoGet.hasPrefix("/*")) {
|
|
i._text = Common::String(i._text.c_str(), p);
|
|
i._autoGet.deleteChar(0);
|
|
|
|
const char *t = strchr(i._autoGet.c_str(), '/');
|
|
if (t)
|
|
i._autoGet = Common::String(i._autoGet.c_str(), t);
|
|
}
|
|
}
|
|
|
|
readInts(f, 1, &lo);
|
|
i._location = (unsigned char)lo;
|
|
i._initialLoc = i._location;
|
|
}
|
|
|
|
// Skip Comment Strings
|
|
for (int idx = 0; idx < na + 1; ++idx)
|
|
readString(f);
|
|
|
|
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);
|
|
}
|
|
|
|
void Scott::output(const Common::String &a) {
|
|
if (_saveSlot == -1)
|
|
display(_bottomWindow, "%s", a.c_str());
|
|
}
|
|
|
|
void Scott::outputNumber(int a) {
|
|
display(_bottomWindow, "%d", a);
|
|
}
|
|
|
|
void Scott::look(void) {
|
|
const char *const ExitNames[6] = {
|
|
_("North"), _("South"), _("East"), _("West"), _("Up"), _("Down")
|
|
};
|
|
Room *r;
|
|
int ct, f;
|
|
int pos;
|
|
|
|
if (_splitScreen)
|
|
glk_window_clear(_topWindow);
|
|
|
|
if ((_bitFlags & (1 << DARKBIT)) && _items[LIGHT_SOURCE]._location != CARRIED
|
|
&& _items[LIGHT_SOURCE]._location != MY_LOC) {
|
|
if (_options & YOUARE)
|
|
display(_topWindow, _("You can't see. It is too dark!\n"));
|
|
else
|
|
display(_topWindow, _("I can't see. It is too dark!\n"));
|
|
if (_options & TRS80_STYLE)
|
|
display(_topWindow, TRS80_LINE);
|
|
return;
|
|
}
|
|
r = &_rooms[MY_LOC];
|
|
if (r->_text.hasPrefix("*"))
|
|
display(_topWindow, "%s\n", r->_text.c_str() + 1);
|
|
else {
|
|
if (_options & YOUARE)
|
|
display(_topWindow, _("You are in a %s\n"), r->_text.c_str());
|
|
else
|
|
display(_topWindow, _("I'm in a %s\n"), r->_text.c_str());
|
|
}
|
|
|
|
ct = 0;
|
|
f = 0;
|
|
display(_topWindow, _("\nObvious exits: "));
|
|
while (ct < 6) {
|
|
if (r->_exits[ct] != 0) {
|
|
if (f == 0)
|
|
f = 1;
|
|
else
|
|
display(_topWindow, ", ");
|
|
display(_topWindow, "%s", ExitNames[ct]);
|
|
}
|
|
ct++;
|
|
}
|
|
|
|
if (f == 0)
|
|
display(_topWindow, _("none"));
|
|
display(_topWindow, ".\n");
|
|
ct = 0;
|
|
f = 0;
|
|
pos = 0;
|
|
while (ct <= _gameHeader._numItems) {
|
|
if (_items[ct]._location == MY_LOC) {
|
|
if (f == 0) {
|
|
if (_options & YOUARE) {
|
|
display(_topWindow, _("\nYou can also see: "));
|
|
pos = 18;
|
|
} else {
|
|
display(_topWindow, _("\nI can also see: "));
|
|
pos = 16;
|
|
}
|
|
f++;
|
|
} else if (!(_options & TRS80_STYLE)) {
|
|
display(_topWindow, " - ");
|
|
pos += 3;
|
|
}
|
|
if (pos + (int)_items[ct]._text.size() > (_width - 10)) {
|
|
pos = 0;
|
|
display(_topWindow, "\n");
|
|
}
|
|
display(_topWindow, "%s", _items[ct]._text.c_str());
|
|
pos += _items[ct]._text.size();
|
|
if (_options & TRS80_STYLE) {
|
|
display(_topWindow, ". ");
|
|
pos += 2;
|
|
}
|
|
}
|
|
ct++;
|
|
}
|
|
|
|
display(_topWindow, "\n");
|
|
if (_options & TRS80_STYLE)
|
|
display(_topWindow, TRS80_LINE);
|
|
}
|
|
|
|
int Scott::whichWord(const char *word, const Common::StringArray &list) {
|
|
int n = 1;
|
|
int ne = 1;
|
|
const char *tp;
|
|
while (ne <= _gameHeader._numWords) {
|
|
tp = list[ne].c_str();
|
|
if (*tp == '*')
|
|
tp++;
|
|
else
|
|
n = ne;
|
|
if (xstrncasecmp(word, tp, _gameHeader._wordLength) == 0)
|
|
return n;
|
|
ne++;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void Scott::lineInput(char *buf, size_t n) {
|
|
event_t ev;
|
|
|
|
glk_request_line_event(_bottomWindow, 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 && _splitScreen)
|
|
look();
|
|
} while (ev.type != evtype_Quit);
|
|
|
|
buf[ev.val1] = 0;
|
|
}
|
|
|
|
Common::Error Scott::writeGameData(Common::WriteStream *ws) {
|
|
Common::String msg;
|
|
|
|
for (int ct = 0; ct < 16; ct++) {
|
|
msg = Common::String::format("%d %d\n", _counters[ct], _roomSaved[ct]);
|
|
ws->write(msg.c_str(), msg.size());
|
|
ws->writeByte(0);
|
|
}
|
|
|
|
msg = Common::String::format("%u %d %d %d %d %d\n",
|
|
_bitFlags, (_bitFlags & (1 << DARKBIT)) ? 1 : 0,
|
|
MY_LOC, _currentCounter, _savedRoom, _gameHeader._lightTime);
|
|
ws->write(msg.c_str(), msg.size());
|
|
ws->writeByte(0);
|
|
|
|
for (int ct = 0; ct <= _gameHeader._numItems; ct++) {
|
|
msg = Common::String::format("%hd\n", (short)_items[ct]._location);
|
|
ws->write(msg.c_str(), msg.size());
|
|
ws->writeByte(0);
|
|
}
|
|
|
|
output(_("Saved.\n"));
|
|
return Common::kNoError;
|
|
}
|
|
|
|
Common::Error Scott::readSaveData(Common::SeekableReadStream *rs) {
|
|
Common::String line;
|
|
int ct = 0;
|
|
short lo;
|
|
short darkFlag;
|
|
|
|
for (ct = 0; ct < 16; ct++) {
|
|
line = QuetzalReader::readString(rs);
|
|
sscanf(line.c_str(), "%d %d", &_counters[ct], &_roomSaved[ct]);
|
|
}
|
|
|
|
line = QuetzalReader::readString(rs);
|
|
sscanf(line.c_str(), "%u %hd %d %d %d %d\n",
|
|
&_bitFlags, &darkFlag, &MY_LOC, &_currentCounter, &_savedRoom,
|
|
&_gameHeader._lightTime);
|
|
|
|
// Backward compatibility
|
|
if (darkFlag)
|
|
_bitFlags |= (1 << 15);
|
|
for (ct = 0; ct <= _gameHeader._numItems; ct++) {
|
|
line = QuetzalReader::readString(rs);
|
|
sscanf(line.c_str(), "%hd\n", &lo);
|
|
_items[ct]._location = (unsigned char)lo;
|
|
}
|
|
|
|
return Common::kNoError;
|
|
}
|
|
|
|
int Scott::getInput(int *vb, int *no) {
|
|
char buf[256];
|
|
char verb[10], noun[10];
|
|
int vc, nc;
|
|
int num;
|
|
|
|
do {
|
|
do {
|
|
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');
|
|
|
|
if (xstrcasecmp(verb, "restore") == 0) {
|
|
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) {
|
|
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
|
|
case 'i':
|
|
strcpy(verb, "INVENTORY");
|
|
break;
|
|
}
|
|
}
|
|
nc = whichWord(verb, _nouns);
|
|
// The Scott Adams system has a hack to avoid typing 'go'
|
|
if (nc >= 1 && nc <= 6) {
|
|
vc = 1;
|
|
} else {
|
|
vc = whichWord(verb, _verbs);
|
|
nc = whichWord(noun, _nouns);
|
|
}
|
|
*vb = vc;
|
|
*no = nc;
|
|
if (vc == -1) {
|
|
output(_("You use word(s) I don't know! "));
|
|
}
|
|
} while (vc == -1);
|
|
|
|
strcpy(_nounText, noun); // Needed by GET/DROP hack
|
|
return 0;
|
|
}
|
|
|
|
int Scott::performLine(int ct) {
|
|
int continuation = 0;
|
|
int param[5], pptr = 0;
|
|
int act[4];
|
|
int cc = 0;
|
|
|
|
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 != MY_LOC)
|
|
return 0;
|
|
break;
|
|
case 3:
|
|
if (_items[dv]._location != CARRIED &&
|
|
_items[dv]._location != MY_LOC)
|
|
return 0;
|
|
break;
|
|
case 4:
|
|
if (MY_LOC != dv)
|
|
return 0;
|
|
break;
|
|
case 5:
|
|
if (_items[dv]._location == MY_LOC)
|
|
return 0;
|
|
break;
|
|
case 6:
|
|
if (_items[dv]._location == CARRIED)
|
|
return 0;
|
|
break;
|
|
case 7:
|
|
if (MY_LOC == dv)
|
|
return 0;
|
|
break;
|
|
case 8:
|
|
if ((_bitFlags & (1 << dv)) == 0)
|
|
return 0;
|
|
break;
|
|
case 9:
|
|
if (_bitFlags & (1 << dv))
|
|
return 0;
|
|
break;
|
|
case 10:
|
|
if (countCarried() == 0)
|
|
return 0;
|
|
break;
|
|
case 11:
|
|
if (countCarried())
|
|
return 0;
|
|
break;
|
|
case 12:
|
|
if (_items[dv]._location == CARRIED || _items[dv]._location == MY_LOC)
|
|
return 0;
|
|
break;
|
|
case 13:
|
|
if (_items[dv]._location == 0)
|
|
return 0;
|
|
break;
|
|
case 14:
|
|
if (_items[dv]._location)
|
|
return 0;
|
|
break;
|
|
case 15:
|
|
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;
|
|
while (cc < 4) {
|
|
if (act[cc] >= 1 && act[cc] < 52) {
|
|
output(_messages[act[cc]]);
|
|
output("\n");
|
|
} else if (act[cc] > 101) {
|
|
output(_messages[act[cc] - 50]);
|
|
output("\n");
|
|
} else {
|
|
switch (act[cc]) {
|
|
case 0:// NOP
|
|
break;
|
|
case 52:
|
|
if (countCarried() == _gameHeader._maxCarry) {
|
|
if (_options & YOUARE)
|
|
output(_("You are carrying too much. "));
|
|
else
|
|
output(_("I've too much to carry! "));
|
|
break;
|
|
}
|
|
_items[param[pptr++]]._location = CARRIED;
|
|
break;
|
|
case 53:
|
|
_items[param[pptr++]]._location = MY_LOC;
|
|
break;
|
|
case 54:
|
|
MY_LOC = 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:
|
|
if (_options & YOUARE)
|
|
output(_("You are dead.\n"));
|
|
else
|
|
output(_("I am dead.\n"));
|
|
_bitFlags &= ~(1 << DARKBIT);
|
|
MY_LOC = _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();
|
|
return 0;
|
|
case 64:
|
|
break;
|
|
case 65: {
|
|
int i = 0;
|
|
int n = 0;
|
|
while (i <= _gameHeader._numItems) {
|
|
if (_items[i]._location == _gameHeader._treasureRoom &&
|
|
_items[i]._text.hasPrefix("*"))
|
|
n++;
|
|
i++;
|
|
}
|
|
if (_options & YOUARE)
|
|
output(_("You have stored "));
|
|
else
|
|
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) {
|
|
output(_("Well done.\n"));
|
|
goto doneit;
|
|
}
|
|
break;
|
|
}
|
|
case 66: {
|
|
int i = 0;
|
|
int f = 0;
|
|
if (_options & YOUARE)
|
|
output(_("You are carrying:\n"));
|
|
else
|
|
output(_("I'm carrying:\n"));
|
|
while (i <= _gameHeader._numItems) {
|
|
if (_items[i]._location == CARRIED) {
|
|
if (f == 1) {
|
|
if (_options & TRS80_STYLE)
|
|
output(". ");
|
|
else
|
|
output(" - ");
|
|
}
|
|
f = 1;
|
|
output(_items[i]._text);
|
|
}
|
|
i++;
|
|
}
|
|
if (f == 0)
|
|
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:
|
|
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:
|
|
outputNumber(_currentCounter);
|
|
break;
|
|
case 79:
|
|
_currentCounter = param[pptr++];
|
|
break;
|
|
case 80: {
|
|
int t = MY_LOC;
|
|
MY_LOC = _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:
|
|
output(_nounText);
|
|
break;
|
|
case 85:
|
|
output(_nounText);
|
|
output("\n");
|
|
break;
|
|
case 86:
|
|
output("\n");
|
|
break;
|
|
case 87: {
|
|
// Changed this to swap location<->roomflag[x] not roomflag 0 and x
|
|
int p = param[pptr++];
|
|
int sr = MY_LOC;
|
|
MY_LOC = _roomSaved[p];
|
|
_roomSaved[p] = sr;
|
|
break;
|
|
}
|
|
case 88:
|
|
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",
|
|
act[cc], param[pptr], param[pptr + 1]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
cc++;
|
|
}
|
|
|
|
return 1 + continuation;
|
|
}
|
|
|
|
int Scott::performActions(int vb, int no) {
|
|
static bool disableSysFunc = false; // Recursion lock
|
|
int d = _bitFlags & (1 << DARKBIT);
|
|
|
|
int ct = 0;
|
|
int fl;
|
|
int doagain = 0;
|
|
if (vb == 1 && no == -1) {
|
|
output(_("Give me a direction too."));
|
|
return 0;
|
|
}
|
|
if (vb == 1 && no >= 1 && no <= 6) {
|
|
int nl;
|
|
if (_items[LIGHT_SOURCE]._location == MY_LOC ||
|
|
_items[LIGHT_SOURCE]._location == CARRIED)
|
|
d = 0;
|
|
if (d)
|
|
output(_("Dangerous to move in the dark! "));
|
|
nl = _rooms[MY_LOC]._exits[no - 1];
|
|
if (nl != 0) {
|
|
MY_LOC = nl;
|
|
return 0;
|
|
}
|
|
if (d) {
|
|
if (_options & YOUARE)
|
|
output(_("You fell down and broke your neck. "));
|
|
else
|
|
output(_("I fell down and broke my neck. "));
|
|
glk_exit();
|
|
return 0;
|
|
}
|
|
if (_options & YOUARE)
|
|
output(_("You can't go in that direction. "));
|
|
else
|
|
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
|
|
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;
|
|
if ((vv == vb) || (doagain && _actions[ct]._vocab == 0)) {
|
|
if ((vv == 0 && randomPercent(nv)) || doagain ||
|
|
(vv != 0 && (nv == no || nv == 0))) {
|
|
int f2;
|
|
if (fl == -1)
|
|
fl = -2;
|
|
if ((f2 = performLine(ct)) > 0) {
|
|
// ahah finally figured it out !
|
|
fl = 0;
|
|
if (f2 == 2)
|
|
doagain = 1;
|
|
if (vb != 0 && doagain == 0)
|
|
return 0;
|
|
}
|
|
|
|
if (shouldQuit())
|
|
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 && disableSysFunc == 0) {
|
|
int item;
|
|
if (_items[LIGHT_SOURCE]._location == MY_LOC ||
|
|
_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) {
|
|
output(_("It is dark.\n"));
|
|
return 0;
|
|
}
|
|
while (i <= _gameHeader._numItems) {
|
|
if (_items[i]._location == MY_LOC && _items[i]._autoGet != nullptr && _items[i]._autoGet[0] != '*') {
|
|
no = whichWord(_items[i]._autoGet.c_str(), _nouns);
|
|
disableSysFunc = true; // Don't recurse into auto get !
|
|
performActions(vb, no); // Recursively check each items table code
|
|
disableSysFunc = false;
|
|
if (shouldQuit())
|
|
return 0;
|
|
|
|
if (countCarried() == _gameHeader._maxCarry) {
|
|
if (_options & YOUARE)
|
|
output(_("You are carrying too much. "));
|
|
else
|
|
output(_("I've too much to carry. "));
|
|
return 0;
|
|
}
|
|
_items[i]._location = CARRIED;
|
|
output(_items[i]._text);
|
|
output(_(": O.K.\n"));
|
|
f = 1;
|
|
}
|
|
i++;
|
|
}
|
|
if (f == 0)
|
|
output(_("Nothing taken."));
|
|
return 0;
|
|
}
|
|
if (no == -1) {
|
|
output(_("What ? "));
|
|
return 0;
|
|
}
|
|
if (countCarried() == _gameHeader._maxCarry) {
|
|
if (_options & YOUARE)
|
|
output(_("You are carrying too much. "));
|
|
else
|
|
output(_("I've too much to carry. "));
|
|
return 0;
|
|
}
|
|
item = matchUpItem(_nounText, MY_LOC);
|
|
if (item == -1) {
|
|
if (_options & YOUARE)
|
|
output(_("It is beyond your power to do that. "));
|
|
else
|
|
output(_("It's beyond my power to do that. "));
|
|
return 0;
|
|
}
|
|
_items[item]._location = CARRIED;
|
|
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.empty()
|
|
&& !_items[i]._autoGet.hasPrefix("*")) {
|
|
no = whichWord(_items[i]._autoGet.c_str(), _nouns);
|
|
disableSysFunc = true;
|
|
performActions(vb, no);
|
|
disableSysFunc = false;
|
|
if (shouldQuit())
|
|
return 0;
|
|
|
|
_items[i]._location = MY_LOC;
|
|
output(_items[i]._text);
|
|
output(_(": O.K.\n"));
|
|
f = 1;
|
|
}
|
|
i++;
|
|
}
|
|
if (f == 0)
|
|
output(_("Nothing dropped.\n"));
|
|
return 0;
|
|
}
|
|
if (no == -1) {
|
|
output(_("What ? "));
|
|
return 0;
|
|
}
|
|
item = matchUpItem(_nounText, CARRIED);
|
|
if (item == -1) {
|
|
if (_options & YOUARE)
|
|
output(_("It's beyond your power to do that.\n"));
|
|
else
|
|
output(_("It's beyond my power to do that.\n"));
|
|
return 0;
|
|
}
|
|
_items[item]._location = MY_LOC;
|
|
output("O.K. ");
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
return fl;
|
|
}
|
|
|
|
int Scott::xstrcasecmp(const char *s1, const char *s2) {
|
|
const unsigned char
|
|
*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
|
|
*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);
|
|
unsigned char c = f->readByte();
|
|
|
|
for (size_t idx = 0; idx < count; ++idx) {
|
|
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 Glk
|