scummvm/engines/glk/scott/scott.cpp
2022-07-02 15:17:53 +02:00

2179 lines
55 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/>.
*
*/
/*
* Based on ScottFree interpreter version 1.14 developed by Swansea
* University Computer Society without disassembly of any other game
* drivers, only of game databases as permitted by EEC law (for purposes
* of compatibility).
*
* Licensed under GPLv2
*
* https://github.com/angstsmurf/spatterlight/tree/master/terps/scott
*/
#include "common/config-manager.h"
#include "common/translation.h"
#include "common/ustr.h"
#include "glk/quetzal.h"
#include "glk/scott/scott.h"
#include "glk/scott/command_parser.h"
#include "glk/scott/definitions.h"
#include "glk/scott/load_game.h"
#include "glk/scott/game_info.h"
#include "glk/scott/globals.h"
#include "glk/scott/hulk.h"
#include "glk/scott/layout_text.h"
#include "glk/scott/line_drawing.h"
#include "glk/scott/saga_draw.h"
#include "glk/scott/restore_state.h"
#include "glk/scott/robin_of_sherwood.h"
#include "glk/scott/gremlins.h"
#include "glk/scott/seas_of_blood.h"
#include "glk/scott/game_specific.h"
#include "glk/scott/ti99_4a_terp.h"
namespace Glk {
namespace Scott {
Scott *g_scott;
Scott::Scott(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc) {
g_scott = this;
Common::fill(&_nounText[0], &_nounText[16], '\0');
}
void Scott::runGame() {
int vb, no;
initialize();
glk_stylehint_set(wintype_TextBuffer, style_User1, stylehint_Proportional, 0);
glk_stylehint_set(wintype_TextBuffer, style_User1, stylehint_Indentation, 20);
glk_stylehint_set(wintype_TextBuffer, style_User1, stylehint_ParaIndentation, 20);
glk_stylehint_set(wintype_TextBuffer, style_Preformatted, stylehint_Justification, stylehint_just_Centered);
_G(_bottomWindow) = glk_window_open(0, 0, 0, wintype_TextBuffer, GLK_BUFFER_ROCK);
if (_G(_bottomWindow) == nullptr)
glk_exit();
glk_set_window(_G(_bottomWindow));
for (int i = 0; i < MAX_SYSMESS; i++) {
_G(_sys)[i] = g_sysDict[i];
}
const char **dictpointer;
if (_G(_options) & YOUARE)
dictpointer = g_sysDict;
else
dictpointer = g_sysDictIAm;
for (int i = 0; i < MAX_SYSMESS && dictpointer[i] != nullptr; i++) {
_G(_sys)[i] = dictpointer[i];
}
loadGameFile(&_gameFile);
if (CURRENT_GAME == SCOTTFREE)
loadDatabase(&_gameFile, (_G(_options) & DEBUGGING) ? 1 : 0);
if (!CURRENT_GAME)
fatal("Unsupported game!");
if (CURRENT_GAME != SCOTTFREE && CURRENT_GAME != TI994A) {
_G(_options) |= SPECTRUM_STYLE;
_splitScreen = 1;
} else {
if (CURRENT_GAME != TI994A)
_G(_options) |= TRS80_STYLE;
_splitScreen = 1;
}
if (!_G(_titleScreen).empty()) {
if (_splitScreen)
printTitleScreenGrid();
else
printTitleScreenBuffer();
}
if (_G(_options) & TRS80_STYLE) {
_topWidth = 64;
_topHeight = 11;
} else {
_topWidth = 80;
_topHeight = 10;
}
if (CURRENT_GAME == TI994A) {
display(_G(_bottomWindow), "In this adventure, you may abbreviate any word \
by typing its first %d letters, and directions by typing \
one letter.\n\nDo you want to restore previously saved game?\n",
_G(_gameHeader)->_wordLength);
if (yesOrNo())
loadGame();
clearScreen();
}
openTopWindow();
if (CURRENT_GAME == SCOTTFREE)
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;
_G(_initialState) = saveCurrentState();
while (!shouldQuit()) {
glk_tick();
if (_G(_shouldRestart))
restartGame();
if (!_G(_stopTime))
performActions(0, 0);
if (shouldQuit())
break;
if (!(_G(_currentCommand) && _G(_currentCommand)->_allFlag && !(_G(_currentCommand)->_allFlag & LASTALL))) {
_printLookToTranscript = _G(_shouldLookInTranscript);
look();
_printLookToTranscript = _G(_shouldLookInTranscript) = 0;
if (!_G(_stopTime) && !_G(_shouldRestart))
saveUndo();
}
if (_G(_shouldRestart))
continue;
if (getInput(&vb, &no) == 1)
continue;
if (g_vm->shouldQuit())
break;
switch (performActions(vb, no)) {
case ER_RAN_ALL_LINES_NO_MATCH:
if (!recheckForExtraCommand()) {
output(_G(_sys)[I_DONT_UNDERSTAND]);
freeCommands();
}
break;
case ER_RAN_ALL_LINES:
output(_G(_sys)[YOU_CANT_DO_THAT_YET]);
freeCommands();
break;
default:
_G(_justStarted) = 0;
}
/* Brian Howarth games seem to use -1 for forever */
if (_G(_items)[LIGHT_SOURCE]._location != DESTROYED && _G(_gameHeader)->_lightTime != -1 && !_G(_stopTime)) {
_G(_gameHeader)->_lightTime--;
if (_G(_gameHeader)->_lightTime < 1) {
_G(_bitFlags) |= (1 << LIGHTOUTBIT);
if (_G(_items)[LIGHT_SOURCE]._location == CARRIED || _G(_items)[LIGHT_SOURCE]._location == MY_LOC) {
output(_G(_sys)[LIGHT_HAS_RUN_OUT]);
}
if ((_G(_options) & PREHISTORIC_LAMP) || (_G(_game)->_subType & MYSTERIOUS) || CURRENT_GAME == TI994A)
_G(_items)[LIGHT_SOURCE]._location = DESTROYED;
} else if (_G(_gameHeader)->_lightTime < 25) {
if (_G(_items)[LIGHT_SOURCE]._location == CARRIED || _G(_items)[LIGHT_SOURCE]._location == MY_LOC) {
if ((_G(_options) & SCOTTLIGHT) || (_G(_game)->_subType & MYSTERIOUS)) {
display(_G(_bottomWindow), "%s %d %s\n", _G(_sys)[LIGHT_RUNS_OUT_IN].c_str(), _G(_gameHeader)->_lightTime, _G(_sys)[TURNS].c_str());
} else {
if (_G(_gameHeader)->_lightTime % 5 == 0)
output(_G(_sys)[LIGHT_GROWING_DIM].c_str());
}
}
}
}
if (_G(_stopTime))
_G(_stopTime)--;
}
}
void Scott::initialize() {
if (ConfMan.hasKey("YOUARE")) {
if (ConfMan.getBool("YOUARE"))
_G(_options) |= YOUARE;
else
_G(_options) &= ~YOUARE;
}
if (gDebugLevel > 0)
_G(_options) |= DEBUGGING;
if (ConfMan.hasKey("SCOTTLIGHT") && ConfMan.getBool("SCOTTLIGHT"))
_G(_options) |= SCOTTLIGHT;
if (ConfMan.hasKey("TRS80_STYLE") && ConfMan.getBool("TRS80_STYLE"))
_G(_options) |= TRS80_STYLE;
if (ConfMan.hasKey("PREHISTORIC_LAMP") && ConfMan.getBool("PREHISTORIC_LAMP"))
_G(_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());
if (_G(_transcript))
glk_put_string_stream(_G(_transcript), msg.c_str());
}
void Scott::display(winid_t w, const Common::U32String fmt, ...) {
Common::U32String msg;
va_list ap;
va_start(ap, fmt);
Common::U32String::vformat(fmt.begin(), fmt.end(), msg, ap);
va_end(ap);
glk_put_string_stream_uni(glk_window_get_stream(w), msg.u32_str());
if (_G(_transcript))
glk_put_string_stream_uni(_G(_transcript), msg.u32_str());
}
void Scott::updateSettings() {
if (drawingVector())
glk_request_timer_events(20);
PaletteType previousPal = _G(_palChosen);
if (_G(_options) & FORCE_PALETTE_ZX)
_G(_palChosen) = ZXOPT;
else if (_G(_options) & FORCE_PALETTE_C64) {
if (_G(_game)->_pictureFormatVersion == 99)
_G(_palChosen) = C64A;
else
_G(_palChosen) = C64B;
} else
_G(_palChosen) = _G(_game)->_palette;
if (_G(_palChosen) != previousPal) {
definePalette();
if (_G(_vectorState) != NO_VECTOR_IMAGE)
drawSomeVectorPixels(1);
}
}
uint Scott::getRandomNumber(uint max) {
return _random.getRandomNumber(max);
}
void Scott::updates(event_t ev) {
if (ev.type == evtype_Arrange) {
updateSettings();
_G(_vectorState) = NO_VECTOR_IMAGE;
closeGraphicsWindow();
openGraphicsWindow();
if (_splitScreen) {
look();
}
} else if (ev.type == evtype_Timer) {
switch (_G(_game->_type)) {
case SHERWOOD_VARIANT:
updateRobinOfSherwoodAnimations();
break;
case GREMLINS_VARIANT:
updateGremlinsAnimations();
break;
case SECRET_MISSION_VARIANT:
// TODO
// UpdateSecretAnimations();
break;
default:
if (_G(_game)->_pictureFormatVersion == 99 && drawingVector())
drawSomeVectorPixels((_G(_vectorState) == NO_VECTOR_IMAGE));
break;
}
}
}
void Scott::delay(double seconds) {
if (_G(_options) & NO_DELAYS)
return;
event_t ev;
if (!glk_gestalt(gestalt_Timer, 0))
return;
glk_request_char_event(_G(_bottomWindow));
glk_cancel_char_event(_G(_bottomWindow));
if (drawingVector()) {
do {
glk_select(&ev);
updates(ev);
} while (drawingVector());
if (_G(_gliSlowDraw))
seconds = 0.5;
}
glk_request_timer_events(1000 * seconds);
do {
glk_select(&ev);
updates(ev);
} while (ev.type != evtype_Timer);
glk_request_timer_events(0);
}
void Scott::fatal(const char *x) {
display(_G(_bottomWindow), "%s\n", x);
cleanupAndExit();
}
void Scott::clearScreen(void) {
glk_window_clear(_G(_bottomWindow));
}
bool Scott::randomPercent(uint n) {
return _random.getRandomNumber(99) < n;
}
int Scott::countCarried(void) {
int ct = 0;
int n = 0;
while (ct <= _G(_gameHeader)->_numItems) {
if (_G(_items)[ct]._location == CARRIED)
n++;
ct++;
}
return n;
}
const char *Scott::mapSynonym(int noun) {
int n = 1;
const char *tp;
static char lastword[16]; // Last non synonym
while (n <= _G(_gameHeader)->_numWords) {
tp = _G(_nouns)[n].c_str();
if (*tp == '*')
tp++;
else
strcpy(lastword, tp);
if (n == noun)
return lastword;
n++;
}
return nullptr;
}
int Scott::matchUpItem(int noun, int loc) {
const char *word = mapSynonym(noun);
int ct = 0;
if (word == nullptr)
word = _G(_nouns)[noun].c_str();
while (ct <= _G(_gameHeader)->_numItems) {
if (!_G(_items)[ct]._autoGet.empty() && _G(_items)[ct]._location == loc &&
scumm_strnicmp(_G(_items)[ct]._autoGet.c_str(), word, _G(_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, &lt, &mn, &trm);
_G(_gameHeader)->_numItems = ni;
_G(_items).resize(ni + 1);
_G(_gameHeader)->_numActions = na;
_G(_actions).resize(na + 1);
_G(_gameHeader)->_numWords = nw;
_G(_gameHeader)->_wordLength = wl;
_G(_verbs).resize(nw + 1);
_G(_nouns).resize(nw + 1);
_G(_gameHeader)->_numRooms = nr;
_G(_rooms).resize(nr + 1);
_G(_gameHeader)->_maxCarry = mc;
_G(_gameHeader)->_playerRoom = pr;
_G(_gameHeader)->_treasures = tr;
_G(_gameHeader)->_lightTime = lt;
_G(_lightRefill) = lt;
_G(_gameHeader)->_numMessages = mn;
_G(_messages).resize(mn + 1);
_G(_gameHeader)->_treasureRoom = trm;
// Load the actions
if (loud)
debug("Reading %d actions.", na);
for (int idx = 0; idx < na + 1; ++idx) {
Action &a = _G(_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) {
_G(_verbs)[idx] = readString(f);
_G(_nouns)[idx] = readString(f);
}
if (loud)
debug("Reading %d rooms.", nr);
for (int idx = 0; idx < nr + 1; ++idx) {
Room &r = _G(_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)
_G(_messages)[idx] = readString(f);
if (loud)
debug("Reading %d items.", ni);
for (int idx = 0; idx < ni + 1; ++idx) {
Item &i = _G(_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(_G(_bottomWindow), "%s", a.c_str());
}
void Scott::output(const Common::U32String &a) {
if (_saveSlot == -1)
display(_G(_bottomWindow), Common::U32String("%S"), a.c_str());
}
void Scott::outputNumber(int a) {
display(_G(_bottomWindow), "%d", a);
}
void Scott::look(void) {
drawRoomImage();
if (_splitScreen && _G(_topWindow) == nullptr)
return;
char *buf = new char[1000];
buf = static_cast<char *>(memset(buf, 0, 1000));
_roomDescriptionStream = glk_stream_open_memory(buf, 1000, filemode_Write, 0);
Room *r;
int ct, f;
if (!_splitScreen) {
writeToRoomDescriptionStream("\n");
} else if (_G(_transcript) && _printLookToTranscript) {
glk_put_char_stream_uni(_G(_transcript), 10);
}
if ((_G(_bitFlags) & (1 << DARKBIT)) && _G(_items)[LIGHT_SOURCE]._location != CARRIED && _G(_items)[LIGHT_SOURCE]._location != MY_LOC) {
writeToRoomDescriptionStream("%s", _G(_sys)[TOO_DARK_TO_SEE].c_str());
flushRoomDescription(buf);
return;
}
r = &_G(_rooms)[MY_LOC];
if (r->_text.empty())
return;
if (r->_text.hasPrefix("*"))
writeToRoomDescriptionStream("%s", r->_text.substr(1).c_str());
else {
writeToRoomDescriptionStream("%s%s", _G(_sys)[YOU_ARE].c_str(), r->_text.c_str());
}
if (!(_G(_options) & SPECTRUM_STYLE)) {
listExits();
writeToRoomDescriptionStream(".\n");
}
ct = 0;
f = 0;
while (ct <= _G(_gameHeader)->_numItems) {
if (_G(_items)[ct]._location == MY_LOC) {
if (_G(_items)[ct]._text[0] == 0) {
error("Scott::look(): Invisible item in room: %d", ct);
ct++;
continue;
}
if (f == 0) {
writeToRoomDescriptionStream("%s", _G(_sys)[YOU_SEE].c_str());
f++;
if (_G(_options) & SPECTRUM_STYLE)
writeToRoomDescriptionStream("\n");
} else if (!(_G(_options) & (TRS80_STYLE | SPECTRUM_STYLE))) {
writeToRoomDescriptionStream("%s", _G(_sys)[ITEM_DELIMITER].c_str());
}
writeToRoomDescriptionStream("%s", _G(_items)[ct]._text.c_str());
if (_G(_options) & (TRS80_STYLE | SPECTRUM_STYLE)) {
writeToRoomDescriptionStream("%s", _G(_sys)[ITEM_DELIMITER].c_str());
}
}
ct++;
}
if ((_G(_options) & TI994A_STYLE) && f) {
writeToRoomDescriptionStream("%s", ".");
}
if (_G(_options) & SPECTRUM_STYLE) {
listExitsSpectrumStyle();
} else if (f) {
writeToRoomDescriptionStream("\n");
}
if ((_G(_autoInventory) || (_G(_options) & FORCE_INVENTORY)) && !(_G(_options) & FORCE_INVENTORY_OFF))
listInventoryInUpperWindow();
flushRoomDescription(buf);
}
int Scott::whichWord(const char *word, const Common::StringArray &list) {
int n = 1;
int ne = 1;
const char *tp;
while (ne <= _G(_gameHeader)->_numWords) {
tp = list[ne].c_str();
if (*tp == '*')
tp++;
else
n = ne;
if (scumm_strnicmp(word, tp, _G(_gameHeader)->_wordLength) == 0)
return n;
ne++;
}
return -1;
}
Common::Error Scott::writeGameData(Common::WriteStream *ws) {
Common::String msg;
for (int ct = 0; ct < 16; ct++) {
msg = Common::String::format("%d %d\n", _G(_counters)[ct], _G(_roomSaved)[ct]);
ws->write(msg.c_str(), msg.size());
ws->writeByte(0);
}
msg = Common::String::format("%u %d %d %d %d %d %d\n",
_G(_bitFlags), (_G(_bitFlags) & (1 << DARKBIT)) ? 1 : 0,
MY_LOC, _G(_currentCounter), _G(_savedRoom), _G(_gameHeader)->_lightTime, _G(_autoInventory));
ws->write(msg.c_str(), msg.size());
ws->writeByte(0);
for (int ct = 0; ct <= _G(_gameHeader)->_numItems; ct++) {
msg = Common::String::format("%hd\n", (short)_G(_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;
int previousAutoInventory = _G(_autoInventory);
SavedState *state = saveCurrentState();
int result = 0;
for (ct = 0; ct < 16; ct++) {
line = QuetzalReader::readString(rs);
result = sscanf(line.c_str(), "%d %d", &_G(_counters)[ct], &_G(_roomSaved)[ct]);
if (result != 2 || _G(_roomSaved)[ct] > _G(_gameHeader)->_numRooms) {
recoverFromBadRestore(state);
return Common::kNoError;
}
}
line = QuetzalReader::readString(rs);
result = sscanf(line.c_str(), "%u %hd %d %d %d %d %d\n",
&_G(_bitFlags), &darkFlag, &MY_LOC, &_G(_currentCounter), &_G(_savedRoom),
&_G(_gameHeader)->_lightTime, &_G(_autoInventory));
if (result == 6)
_G(_autoInventory) = previousAutoInventory;
if ((result != 7 && result != 6) || MY_LOC > _G(_gameHeader)->_numRooms || MY_LOC < 1 || _G(_savedRoom) > _G(_gameHeader)->_numRooms) {
recoverFromBadRestore(state);
return Common::kNoError;
}
// Backward compatibility
if (darkFlag)
_G(_bitFlags) |= (1 << 15);
for (ct = 0; ct <= _G(_gameHeader)->_numItems; ct++) {
line = QuetzalReader::readString(rs);
result = sscanf(line.c_str(), "%hd\n", &lo);
_G(_items)[ct]._location = (unsigned char)lo;
if (result != 1 || (_G(_items)[ct]._location > _G(_gameHeader)->_numRooms && _G(_items)[ct]._location != CARRIED)) {
recoverFromBadRestore(state);
return Common::kNoError;
}
}
saveUndo();
_G(_justStarted) = 0;
_G(_stopTime) = 1;
return Common::kNoError;
}
ActionResultType Scott::performLine(int ct) {
#ifdef DEBUG_ACTIONS
debug("Performing line %d: ", ct);
#endif
int continuation = 0, dead = 0;
int param[5], pptr = 0;
int p;
int act[4];
int cc = 0;
while (cc < 5) {
int cv, dv;
cv = _G(_actions)[ct]._condition[cc];
dv = cv / 20;
cv %= 20;
#ifdef DEBUG_ACTIONS
debug("Testing condition %d: ", cv);
#endif
switch (cv) {
case 0:
param[pptr++] = dv;
break;
case 1:
#ifdef DEBUG_ACTIONS
debug("Does the player carry %s?\n", _G(_items)[dv].Text);
#endif
if (_G(_items)[dv]._location != CARRIED)
return ACT_FAILURE;
break;
case 2:
#ifdef DEBUG_ACTIONS
debug("Is %s in location?\n", _G(_items)[dv].Text);
#endif
if (_G(_items)[dv]._location != MY_LOC)
return ACT_FAILURE;
break;
case 3:
#ifdef DEBUG_ACTIONS
debug("Is %s held or in location?\n", _G(_items)[dv].Text);
#endif
if (_G(_items)[dv]._location != CARRIED && _G(_items)[dv]._location != MY_LOC)
return ACT_FAILURE;
break;
case 4:
#ifdef DEBUG_ACTIONS
debug("Is location %s?\n", _G(_rooms)[dv].Text);
#endif
if (MY_LOC != dv)
return ACT_FAILURE;
break;
case 5:
#ifdef DEBUG_ACTIONS
debug("Is %s NOT in location?\n", _G(_items)[dv].Text);
#endif
if (_G(_items)[dv]._location == MY_LOC)
return ACT_FAILURE;
break;
case 6:
#ifdef DEBUG_ACTIONS
debug("Does the player NOT carry %s?\n", _G(_items)[dv].Text);
#endif
if (_G(_items)[dv]._location == CARRIED)
return ACT_FAILURE;
break;
case 7:
#ifdef DEBUG_ACTIONS
debug("Is location NOT %s?\n", _G(_rooms)[dv].Text);
#endif
if (MY_LOC == dv)
return ACT_FAILURE;
break;
case 8:
#ifdef DEBUG_ACTIONS
debug("Is bitflag %d set?\n", dv);
#endif
if ((_G(_bitFlags) & (1 << dv)) == 0)
return ACT_FAILURE;
break;
case 9:
#ifdef DEBUG_ACTIONS
debug("Is bitflag %d NOT set?\n", dv);
#endif
if (_G(_bitFlags) & (1 << dv))
return ACT_FAILURE;
break;
case 10:
#ifdef DEBUG_ACTIONS
debug("Does the player carry anything?\n");
#endif
if (countCarried() == 0)
return ACT_FAILURE;
break;
case 11:
#ifdef DEBUG_ACTIONS
debug("Does the player carry nothing?\n");
#endif
if (countCarried())
return ACT_FAILURE;
break;
case 12:
#ifdef DEBUG_ACTIONS
debug("Is %s neither carried nor in room?\n", _G(_items)[dv].Text);
#endif
if (_G(_items)[dv]._location == CARRIED || _G(_items)[dv]._location == MY_LOC)
return ACT_FAILURE;
break;
case 13:
#ifdef DEBUG_ACTIONS
debug("Is %s (%d) in play?\n", _G(_items)[dv].Text, dv);
#endif
if (_G(_items)[dv]._location == 0)
return ACT_FAILURE;
break;
case 14:
#ifdef DEBUG_ACTIONS
debug("Is %s NOT in play?\n", _G(_items)[dv].Text);
#endif
if (_G(_items)[dv]._location)
return ACT_FAILURE;
break;
case 15:
#ifdef DEBUG_ACTIONS
debug("Is _G(_currentCounter) <= %d?\n", dv);
#endif
if (_G(_currentCounter) > dv)
return ACT_FAILURE;
break;
case 16:
#ifdef DEBUG_ACTIONS
debug("Is _G(_currentCounter) > %d?\n", dv);
#endif
if (_G(_currentCounter) <= dv)
return ACT_FAILURE;
break;
case 17:
#ifdef DEBUG_ACTIONS
debug("Is %s still in initial room?\n", _G(_items)[dv].Text);
#endif
if (_G(_items)[dv]._location != _G(_items)[dv]._initialLoc)
return ACT_FAILURE;
break;
case 18:
#ifdef DEBUG_ACTIONS
debug("Has %s been moved?\n", _G(_items)[dv].Text);
#endif
if (_G(_items)[dv]._location == _G(_items)[dv]._initialLoc)
return ACT_FAILURE;
break;
case 19: /* Only seen in Brian Howarth games so far */
#ifdef DEBUG_ACTIONS
debug("Is current counter == %d?\n", dv);
if (_G(_currentCounter) != dv)
debug("Nope, current counter is %d\n", _G(_currentCounter));
#endif
if (_G(_currentCounter) != dv)
return ACT_FAILURE;
break;
}
#ifdef DEBUG_ACTIONS
debug("YES\n");
#endif
cc++;
}
#if defined(__clang__)
#pragma mark Subcommands
#endif
/* Actions */
act[0] = _G(_actions)[ct]._action[0];
act[2] = _G(_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) {
#ifdef DEBUG_ACTIONS
debug("Performing action %d: ", act[cc]);
#endif
if (act[cc] >= 1 && act[cc] < 52) {
printMessage(act[cc]);
} else if (act[cc] > 101) {
printMessage(act[cc] - 50);
} else
switch (act[cc]) {
case 0: /* NOP */
break;
case 52:
if (countCarried() >= _G(_gameHeader)->_maxCarry) {
output(_G(_sys)[YOURE_CARRYING_TOO_MUCH]);
return ACT_SUCCESS;
}
_G(_items)[param[pptr++]]._location = CARRIED;
break;
case 53:
#ifdef DEBUG_ACTIONS
debug("item %d (\"%s\") is now in location.\n", param[pptr],
_G(_items)[param[pptr]].Text);
#endif
_G(_items)[param[pptr++]]._location = MY_LOC;
_G(_shouldLookInTranscript) = 1;
break;
case 54:
#ifdef DEBUG_ACTIONS
debug("player location is now room %d (%s).\n", param[pptr],
_G(_rooms)[param[pptr]].Text);
#endif
MY_LOC = param[pptr++];
_G(_shouldLookInTranscript) = 1;
look();
break;
case 55:
#ifdef DEBUG_ACTIONS
fprintf(stderr,
"Item %d (%s) is removed from the game (put in room 0).\n",
param[pptr], _G(_items)[param[pptr]].Text);
#endif
_G(_items)[param[pptr++]]._location = 0;
break;
case 56:
_G(_bitFlags) |= 1 << DARKBIT;
break;
case 57:
_G(_bitFlags) &= ~(1 << DARKBIT);
break;
case 58:
#ifdef DEBUG_ACTIONS
debug("Bitflag %d is set\n", param[pptr]);
#endif
_G(_bitFlags) |= (1 << param[pptr++]);
break;
case 59:
#ifdef DEBUG_ACTIONS
debug("Item %d (%s) is removed from play.\n", param[pptr],
_G(_items)[param[pptr]].Text);
#endif
_G(_items)[param[pptr++]]._location = 0;
break;
case 60:
#ifdef DEBUG_ACTIONS
debug("BitFlag %d is cleared\n", param[pptr]);
#endif
_G(_bitFlags) &= ~(1 << param[pptr++]);
break;
case 61:
playerIsDead();
break;
case 62:
p = param[pptr++];
putItemAInRoomB(p, param[pptr++]);
break;
case 63:
#ifdef DEBUG_ACTIONS
debug("Game over.\n");
#endif
doneIt();
dead = 1;
break;
case 64:
break;
case 65:
dead = printScore();
_G(_stopTime) = 2;
break;
case 66:
if (_G(_game)->_type == SEAS_OF_BLOOD_VARIANT)
adventureSheet();
else
listInventory();
_G(_stopTime) = 2;
break;
case 67:
_G(_bitFlags) |= (1 << 0);
break;
case 68:
_G(_bitFlags) &= ~(1 << 0);
break;
case 69:
_G(_gameHeader)->_lightTime = _G(_lightRefill);
_G(_items)[LIGHT_SOURCE]._location = CARRIED;
_G(_bitFlags) &= ~(1 << LIGHTOUTBIT);
break;
case 70:
clearScreen(); /* pdd. */
break;
case 71:
saveGame();
_G(_stopTime) = 2;
break;
case 72:
p = param[pptr++];
swapItemLocations(p, param[pptr++]);
break;
case 73:
#ifdef DEBUG_ACTIONS
debug("Continue with next line\n");
#endif
continuation = 1;
break;
case 74:
_G(_items)[param[pptr++]]._location = CARRIED;
break;
case 75:
p = param[pptr++];
moveItemAToLocOfItemB(p, param[pptr++]);
break;
case 76: /* Looking at adventure .. */
#ifdef DEBUG_ACTIONS
debug("LOOK\n");
#endif
if (_splitScreen)
look();
_G(_shouldLookInTranscript) = 1;
break;
case 77:
if (_G(_currentCounter) >= 1)
_G(_currentCounter)--;
#ifdef DEBUG_ACTIONS
fprintf(stderr,
"decrementing current counter. Current counter is now %d.\n",
_G(_currentCounter));
#endif
break;
case 78:
outputNumber(_G(_currentCounter));
output(" ");
break;
case 79:
#ifdef DEBUG_ACTIONS
debug("_G(_currentCounter) is set to %d.\n", param[pptr]);
#endif
_G(_currentCounter) = param[pptr++];
break;
case 80:
goToStoredLoc();
break;
case 81:
swapCounters(param[pptr++]);
break;
case 82:
_G(_currentCounter) += param[pptr++];
break;
case 83:
_G(_currentCounter) -= param[pptr++];
if (_G(_currentCounter) < -1)
_G(_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:
if (_G(_currentCommand))
glk_put_string_stream_uni(
glk_window_get_stream(_G(_bottomWindow)),
_G(_unicodeWords)[_G(_currentCommand)->_nounWordIndex]);
break;
case 85:
if (_G(_currentCommand))
glk_put_string_stream_uni(
glk_window_get_stream(_G(_bottomWindow)),
_G(_unicodeWords)[_G(_currentCommand)->_nounWordIndex]);
output("\n");
break;
case 86:
if (!(_G(_options) & SPECTRUM_STYLE))
output("\n");
break;
case 87:
swapLocAndRoomFlag(param[pptr++]);
break;
case 88:
#ifdef DEBUG_ACTIONS
debug("Delay\n");
#endif
delay(1);
break;
case 89:
#ifdef DEBUG_ACTIONS
debug("Action 89, parameter %d\n", param[pptr]);
#endif
p = param[pptr++];
switch (CURRENT_GAME) {
case SPIDERMAN:
case SPIDERMAN_C64:
drawBlack();
break;
case SECRET_MISSION:
case SECRET_MISSION_C64:
// TODO
// SecretAction(p);
break;
case ADVENTURELAND:
case ADVENTURELAND_C64:
adventurelandAction(p);
break;
case SEAS_OF_BLOOD:
case SEAS_OF_BLOOD_C64:
bloodAction(p);
break;
case ROBIN_OF_SHERWOOD:
case ROBIN_OF_SHERWOOD_C64:
sherwoodAction(p);
break;
case GREMLINS:
case GREMLINS_SPANISH:
case GREMLINS_GERMAN:
case GREMLINS_GERMAN_C64:
gremlinsAction(p);
break;
default:
break;
}
break;
case 90:
#ifdef DEBUG_ACTIONS
debug("Draw Hulk image, parameter %d\n", param[pptr]);
#endif
if (CURRENT_GAME != HULK && CURRENT_GAME != HULK_C64) {
pptr++;
} else if (!(_G(_bitFlags) & (1 << DARKBIT)))
drawHulkImage(param[pptr++]);
break;
default:
debug("Unknown action %d [Param begins %d %d]\n", act[cc],
param[pptr], param[pptr + 1]);
break;
}
cc++;
}
if (dead) {
return ACT_GAMEOVER;
} else if (continuation) {
return ACT_CONTINUE;
} else {
return ACT_SUCCESS;
}
}
ExplicitResultType Scott::performActions(int vb, int no) {
int dark = _G(_bitFlags) & (1 << DARKBIT);
int ct = 0;
ExplicitResultType flag = ER_NO_RESULT;
int doAgain = 0;
int foundMatch = 0;
if (vb == GO && no == -1) {
output(_G(_sys)[DIRECTION]);
return ER_SUCCESS;
}
if (vb == 1 && no >= 1 && no <= 6) {
int nl;
if (_G(_items)[LIGHT_SOURCE]._location == MY_LOC || _G(_items)[LIGHT_SOURCE]._location == CARRIED)
dark = 0;
if (dark)
output(_G(_sys)[DANGEROUS_TO_MOVE_IN_DARK]);
nl = _G(_rooms)[MY_LOC]._exits[no - 1];
if (nl != 0) {
/* Seas of Blood needs this to be able to flee back to the last room */
if (_G(_game)->_type == SEAS_OF_BLOOD_VARIANT)
_G(_savedRoom) = MY_LOC;
if (_G(_options) & (SPECTRUM_STYLE | TI994A_STYLE))
output(_G(_sys)[OK]);
MY_LOC = nl;
_G(_shouldLookInTranscript) = 1;
if (_G(_currentCommand) && _G(_currentCommand)->_next) {
lookWithPause();
}
return ER_SUCCESS;
}
if (dark) {
_G(_bitFlags) &= ~(1 << DARKBIT);
MY_LOC = _G(_gameHeader)->_numRooms; /* It seems to be what the code says! */
output(_G(_sys)[YOU_FELL_AND_BROKE_YOUR_NECK]);
_G(_bitFlags) &= ~(1 << DARKBIT);
MY_LOC = _G(_gameHeader)->_numRooms; /* It seems to be what the code says! */
return ER_SUCCESS;
}
output(_G(_sys)[YOU_CANT_GO_THAT_WAY]);
return ER_SUCCESS;
}
if ((CURRENT_GAME == HULK || CURRENT_GAME == HULK_C64) && vb == 39 && !dark) {
hulkShowImageOnExamine(no);
}
if (_G(_currentCommand) && _G(_currentCommand)->_allFlag && vb == _G(_currentCommand)->_verb && !(dark && vb == TAKE)) {
output(_G(_items)[_G(_currentCommand)->_item]._text);
output("....");
}
flag = ER_RAN_ALL_LINES_NO_MATCH;
if (CURRENT_GAME != TI994A) {
while (ct <= _G(_gameHeader)->_numActions) {
int verbvalue, nounvalue;
verbvalue = _G(_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 && verbvalue != 0))
break;
/* Oops.. added this minor cockup fix 1.11 */
if (vb != 0 && !doAgain && flag == 0)
break;
nounvalue = verbvalue % 150;
verbvalue /= 150;
if ((verbvalue == vb) || (doAgain && _G(_actions)[ct]._vocab == 0)) {
if ((verbvalue == 0 && randomPercent(nounvalue)) || doAgain || (verbvalue != 0 && (nounvalue == no || nounvalue == 0))) {
if (verbvalue == vb && vb != 0 && nounvalue == no)
foundMatch = 1;
ActionResultType flag2;
if (flag == ER_RAN_ALL_LINES_NO_MATCH)
flag = ER_RAN_ALL_LINES;
if ((flag2 = performLine(ct)) != ACT_FAILURE) {
/* ahah finally figured it out ! */
flag = ER_SUCCESS;
if (flag2 == ACT_CONTINUE)
doAgain = 1;
else if (flag2 == ACT_GAMEOVER)
return ER_SUCCESS;
if (vb != 0 && doAgain == 0)
return ER_SUCCESS;
}
}
}
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 <= _G(_gameHeader)->_numActions && _G(_actions)[ct]._vocab != 0)
doAgain = 0;
}
} else {
if (vb == 0) {
runImplicitTI99Actions();
return ER_NO_RESULT;
} else {
flag = runExplicitTI99Actions(vb, no);
}
}
if (foundMatch)
return flag;
if (flag != ER_SUCCESS) {
int item = 0;
if (_G(_items)[LIGHT_SOURCE]._location == MY_LOC || _G(_items)[LIGHT_SOURCE]._location == CARRIED)
dark = 0;
if (vb == TAKE || vb == DROP) {
if (_G(_currentCommand)->_allFlag) {
if (vb == TAKE && dark) {
output(_G(_sys)[TOO_DARK_TO_SEE]);
while (!(_G(_currentCommand)->_allFlag & LASTALL)) {
_G(_currentCommand) = _G(_currentCommand)->_next;
}
return ER_SUCCESS;
}
item = _G(_currentCommand)->_item;
int location = CARRIED;
if (vb == TAKE)
location = MY_LOC;
while (_G(_items)[item]._location != location && !(_G(_currentCommand)->_allFlag & LASTALL)) {
_G(_currentCommand) = _G(_currentCommand)->_next;
}
if (_G(_items)[item]._location != location)
return ER_SUCCESS;
}
/* Yes they really _are_ hardcoded values */
if (vb == TAKE) {
if (no == -1) {
output(_G(_sys)[WHAT]);
return ER_SUCCESS;
}
if (countCarried() >= _G(_gameHeader)->_maxCarry) {
output(_G(_sys)[YOURE_CARRYING_TOO_MUCH]);
return ER_SUCCESS;
}
if (!item)
item = matchUpItem(no, MY_LOC);
if (item == -1) {
item = matchUpItem(no, CARRIED);
if (item == -1) {
item = matchUpItem(no, 0);
if (item == -1) {
output(_G(_sys)[THATS_BEYOND_MY_POWER]);
} else {
output(_G(_sys)[YOU_DONT_SEE_IT]);
}
} else {
output(_G(_sys)[YOU_HAVE_IT]);
}
return ER_SUCCESS;
}
_G(_items)[item]._location = CARRIED;
printTakenOrDropped(TAKEN);
return ER_SUCCESS;
}
if (vb == DROP) {
if (no == -1) {
output(_G(_sys)[WHAT]);
return ER_SUCCESS;
}
if (!item)
item = matchUpItem(no, CARRIED);
if (item == -1) {
item = matchUpItem(no, 0);
if (item == -1) {
output(_G(_sys)[THATS_BEYOND_MY_POWER]);
} else {
output(_G(_sys)[YOU_HAVENT_GOT_IT]);
}
return ER_SUCCESS;
}
_G(_items)[item]._location = MY_LOC;
printTakenOrDropped(DROPPED);
return ER_SUCCESS;
}
}
}
return flag;
}
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);
}
void Scott::writeToRoomDescriptionStream(const char *fmt, ...) {
if (_roomDescriptionStream == nullptr)
return;
va_list ap;
va_start(ap, fmt);
Common::String msg = Common::String::vformat(fmt, ap);
va_end(ap);
glk_put_string_stream(_roomDescriptionStream, msg.c_str());
}
void Scott::flushRoomDescription(char *buf) {
glk_stream_close(_roomDescriptionStream, 0);
strid_t storedTranscript = _G(_transcript);
if (!_printLookToTranscript)
_G(_transcript) = nullptr;
int printDelimiter = (_G(_options) & (TRS80_STYLE | SPECTRUM_STYLE | TI994A_STYLE));
if (_splitScreen) {
glk_window_clear(_G(_topWindow));
glk_window_get_size(_G(_topWindow), (uint *)&_topWidth, (uint *)&_topHeight);
int rows, length;
char *textWithBreaks = lineBreakText(buf, _topWidth, &rows, &length);
uint bottomheight;
glk_window_get_size(_G(_bottomWindow), nullptr, &bottomheight);
winid_t o2 = glk_window_get_parent(_G(_topWindow));
if (!(bottomheight < 3 && _topHeight < rows)) {
glk_window_get_size(_G(_topWindow), (uint *)&_topWidth, (uint *)&_topHeight);
glk_window_set_arrangement(o2, winmethod_Above | winmethod_Fixed, rows, _G(_topWindow));
} else {
printDelimiter = 0;
}
int line = 0;
int index = 0;
int i;
char *string = new char[_topWidth + 1];
for (line = 0; line < rows && index < length; line++) {
for (i = 0; i < _topWidth; i++) {
string[i] = textWithBreaks[index++];
if (string[i] == 10 || string[i] == 13 || index >= length)
break;
}
if (i < _topWidth + 1) {
string[i++] = '\n';
}
string[i] = 0;
if (strlen(string) == 0)
break;
glk_window_move_cursor(_G(_topWindow), 0, line);
display(_G(_topWindow), "%s", string);
}
if (line < rows - 1) {
glk_window_get_size(_G(_topWindow), (uint *)&_topWidth, (uint *)&_topHeight);
glk_window_set_arrangement(o2, winmethod_Above | winmethod_Fixed, MIN(rows - 1, _topHeight - 1), _G(_topWindow));
}
delete[] textWithBreaks;
} else {
display(_G(_bottomWindow), "%s", buf);
}
if (printDelimiter) {
printWindowDelimiter();
}
if (_pauseNextRoomDescription) {
delay(0.8);
_pauseNextRoomDescription = 0;
}
_G(_transcript) = storedTranscript;
if (buf != nullptr) {
delete[] buf;
buf = nullptr;
}
}
void Scott::printWindowDelimiter() {
glk_window_get_size(_G(_topWindow), (uint *)&_topWidth, (uint *)&_topHeight);
glk_window_move_cursor(_G(_topWindow), 0, _topHeight - 1);
glk_stream_set_current(glk_window_get_stream(_G(_topWindow)));
if (_G(_options) & SPECTRUM_STYLE)
for (int i = 0; i < _topWidth; i++)
glk_put_char('*');
else {
glk_put_char('<');
for (int i = 0; i < _topWidth - 2; i++)
glk_put_char('-');
glk_put_char('>');
}
}
void Scott::listExits() {
int ct = 0;
int f = 0;
writeToRoomDescriptionStream("\n\n%s", _G(_sys)[EXITS].c_str());
while (ct < 6) {
if ((&_G(_rooms)[MY_LOC])->_exits[ct] != 0) {
if (f) {
writeToRoomDescriptionStream("%s", _G(_sys)[EXITS_DELIMITER].c_str());
}
/* _G(_sys)[] begins with the exit names */
writeToRoomDescriptionStream("%s", _G(_sys)[ct].c_str());
f = 1;
}
ct++;
}
if (f == 0)
writeToRoomDescriptionStream("%s", _G(_sys)[NONE].c_str());
return;
}
void Scott::listExitsSpectrumStyle() {
int ct = 0;
int f = 0;
while (ct < 6) {
if ((&_G(_rooms)[MY_LOC])->_exits[ct] != 0) {
if (f == 0) {
writeToRoomDescriptionStream("\n\n%s", _G(_sys)[EXITS].c_str());
} else {
writeToRoomDescriptionStream("%s", _G(_sys)[EXITS_DELIMITER].c_str());
}
/* sys[] begins with the exit names */
writeToRoomDescriptionStream("%s", _G(_sys)[ct].c_str());
f = 1;
}
ct++;
}
writeToRoomDescriptionStream("\n");
return;
}
void Scott::listInventoryInUpperWindow() {
int i = 0;
int lastitem = -1;
writeToRoomDescriptionStream("\n%s", _G(_sys)[INVENTORY].c_str());
while (i <= _G(_gameHeader)->_numItems) {
if (_G(_items)[i]._location == CARRIED) {
if (_G(_items)[i]._text[0] == 0) {
error("Scott::listInventoryInUpperWindow(): Invisible item in inventory: %d", i);
i++;
continue;
}
if (lastitem > -1 && (_G(_options) & (TRS80_STYLE | SPECTRUM_STYLE)) == 0) {
writeToRoomDescriptionStream("%s", _G(_sys)[ITEM_DELIMITER].c_str());
}
lastitem = i;
writeToRoomDescriptionStream("%s", _G(_items)[i]._text.c_str());
if (_G(_options) & (TRS80_STYLE | SPECTRUM_STYLE)) {
writeToRoomDescriptionStream("%s", _G(_sys)[ITEM_DELIMITER].c_str());
}
}
i++;
}
if (lastitem == -1) {
writeToRoomDescriptionStream("%s\n", _G(_sys)[NOTHING].c_str());
} else {
if (_G(_options) & TI994A_STYLE && !itemEndsWithPeriod(lastitem))
writeToRoomDescriptionStream(".");
writeToRoomDescriptionStream("\n");
}
}
int Scott::itemEndsWithPeriod(int item) {
if (item < 0 || item > _G(_gameHeader)->_numItems)
return 0;
Common::String desc = _G(_items)[item]._text;
if (!desc.empty() && desc[0] != 0) {
const char lastchar = desc[desc.size() - 1];
if (lastchar == '.' || lastchar == '!') {
return 1;
}
}
return 0;
}
void Scott::closeGraphicsWindow() {
if (_G(_graphics) == nullptr)
_G(_graphics) = findGlkWindowWithRock(GLK_GRAPHICS_ROCK);
if (_G(_graphics)) {
glk_window_close(_G(_graphics), nullptr);
_G(_graphics) = nullptr;
glk_window_get_size(_G(_topWindow), (uint *)&_topWidth, (uint *)&_topHeight);
}
}
winid_t Scott::findGlkWindowWithRock(glui32 rock) {
uint rockptr;
winid_t win = glk_window_iterate(nullptr, &rockptr);
while (win) {
if (rockptr == rock)
return win;
win = glk_window_iterate(win, &rockptr);
}
return 0;
}
void Scott::openGraphicsWindow() {
if (!glk_gestalt(gestalt_Graphics, 0))
return;
uint graphwidth, graphheight, optimalWidth, optimalHeight;
if (_G(_topWindow) == nullptr)
_G(_topWindow) = findGlkWindowWithRock(GLK_STATUS_ROCK);
if (_G(_graphics) == nullptr)
_G(_graphics) = findGlkWindowWithRock(GLK_GRAPHICS_ROCK);
if (_G(_graphics) == nullptr && _G(_topWindow) != nullptr) {
glk_window_get_size(_G(_topWindow), (uint *)&_topWidth, (uint *)&_topHeight);
glk_window_close(_G(_topWindow), nullptr);
_G(_graphics) = glk_window_open(_G(_bottomWindow), winmethod_Above | winmethod_Proportional, 60, wintype_Graphics, GLK_GRAPHICS_ROCK);
glk_window_get_size(_G(_graphics), &graphwidth, &graphheight);
_G(_pixelSize) = optimalPictureSize(&optimalWidth, &optimalHeight);
_G(_xOffset) = ((int)graphwidth - (int)optimalWidth) / 2;
if (graphheight > optimalHeight) {
winid_t parent = glk_window_get_parent(_G(_graphics));
glk_window_set_arrangement(parent, winmethod_Above | winmethod_Fixed, optimalHeight, nullptr);
}
// Set the graphics window background to match the main window background, best as we can, and clear the window.
uint backgroundColor;
if (glk_style_measure(_G(_bottomWindow), style_Normal, stylehint_BackColor, &backgroundColor)) {
glk_window_set_background_color(_G(_graphics), backgroundColor);
glk_window_clear(_G(_graphics));
}
_G(_topWindow) = glk_window_open(_G(_bottomWindow), winmethod_Above | winmethod_Fixed, _topHeight, wintype_TextGrid, GLK_STATUS_ROCK);
glk_window_get_size(_G(_topWindow), (uint *)&_topWidth, (uint *)&_topHeight);
} else {
if (!_G(_graphics))
_G(_graphics) = glk_window_open(_G(_bottomWindow), winmethod_Above | winmethod_Proportional, 60, wintype_Graphics, GLK_GRAPHICS_ROCK);
glk_window_get_size(_G(_graphics), &graphwidth, &graphheight);
_G(_pixelSize) = optimalPictureSize(&optimalWidth, &optimalHeight);
_G(_xOffset) = (graphwidth - optimalWidth) / 2;
winid_t parent = glk_window_get_parent(_G(_graphics));
glk_window_set_arrangement(parent, winmethod_Above | winmethod_Fixed, optimalHeight, nullptr);
}
}
glui32 Scott::optimalPictureSize(uint *width, uint *height) {
*width = 255;
*height = 96;
int multiplier = 1;
uint graphwidth, graphheight;
glk_window_get_size(_G(_graphics), &graphwidth, &graphheight);
multiplier = graphheight / 96;
if (static_cast<glui32>(255 * multiplier) > graphwidth)
multiplier = graphwidth / 255;
if (multiplier == 0)
multiplier = 1;
*width = 255 * multiplier;
*height = 96 * multiplier;
return multiplier;
}
void Scott::openTopWindow() {
_G(_topWindow) = findGlkWindowWithRock(GLK_STATUS_ROCK);
if (_G(_topWindow) == nullptr) {
if (_splitScreen) {
_G(_topWindow) = glk_window_open(_G(_bottomWindow), winmethod_Above | winmethod_Fixed, _topHeight, wintype_TextGrid, GLK_STATUS_ROCK);
if (_G(_topWindow) == nullptr) {
_splitScreen = 0;
_G(_topWindow) = _G(_bottomWindow);
} else {
glk_window_get_size(_G(_topWindow), (uint *)&_topWidth, nullptr);
}
} else {
_G(_topWindow) = _G(_bottomWindow);
}
}
}
void Scott::cleanupAndExit() {
if (_G(_transcript))
glk_stream_close(_G(_transcript), nullptr);
if (drawingVector()) {
_G(_gliSlowDraw) = 0;
drawSomeVectorPixels(0);
}
glk_exit();
}
void Scott::drawBlack() {
glk_window_fill_rect(_G(_graphics), 0, _G(_xOffset), 0, 32 * 8 * _G(_pixelSize), 12 * 8 * _G(_pixelSize));
}
void Scott::drawImage(int image) {
if (!glk_gestalt(gestalt_Graphics, 0))
return;
openGraphicsWindow();
if (_G(_graphics) == nullptr) {
error("drawImage: Graphic window nullptr?");
return;
}
if (_G(_game)->_pictureFormatVersion == 99)
drawVectorPicture(image);
else
drawSagaPictureNumber(image);
}
void Scott::drawRoomImage() {
if (CURRENT_GAME == ADVENTURELAND || CURRENT_GAME == ADVENTURELAND_C64) {
adventurelandDarkness();
}
int dark = ((_G(_bitFlags) & (1 << DARKBIT)) && _G(_items)[LIGHT_SOURCE]._location != CARRIED && _G(_items)[LIGHT_SOURCE]._location != MY_LOC);
if (dark && _G(_graphics) != nullptr && !(_G(_rooms)[MY_LOC]._image == 255)) {
_G(_vectorImageShown) = -1;
_G(_vectorState) = NO_VECTOR_IMAGE;
glk_request_timer_events(0);
drawBlack();
return;
}
switch (CURRENT_GAME) {
case SEAS_OF_BLOOD:
case SEAS_OF_BLOOD_C64:
seasOfBloodRoomImage();
return;
case ROBIN_OF_SHERWOOD:
case ROBIN_OF_SHERWOOD_C64:
robinOfSherwoodLook();
return;
case HULK:
case HULK_C64:
hulkLook();
return;
default:
break;
}
if (_G(_rooms)[MY_LOC]._image == 255) {
closeGraphicsWindow();
return;
}
if (dark)
return;
if (_G(_game)->_pictureFormatVersion == 99) {
drawImage(MY_LOC - 1);
return;
}
if (_G(_game)->_type == GREMLINS_VARIANT) {
gremlinsLook();
} else {
drawImage(_G(_rooms)[MY_LOC]._image & 127);
}
for (int ct = 0; ct <= _G(_gameHeader)->_numItems; ct++)
if (_G(_items)[ct]._image && _G(_items)[ct]._location == MY_LOC) {
if ((_G(_items)[ct]._flag & 127) == MY_LOC) {
drawImage(_G(_items)[ct]._image);
/* Draw the correct image of the bear on the beach */
} else if (_G(_game)->_type == SAVAGE_ISLAND_VARIANT && ct == 20 && MY_LOC == 8) {
drawImage(9);
}
}
}
void Scott::restartGame() {
if (_G(_currentCommand))
freeCommands();
restoreState(_G(_initialState));
_G(_justStarted) = 0;
_G(_stopTime) = 0;
glk_window_clear(_G(_bottomWindow));
openTopWindow();
_G(_shouldRestart) = 0;
}
void Scott::transcriptOn() {
frefid_t ref;
if (_G(_transcript)) {
output(_G(_sys)[TRANSCRIPT_ALREADY]);
return;
}
ref = glk_fileref_create_by_prompt(fileusage_TextMode | fileusage_Transcript, filemode_Write, 0);
if (ref == nullptr)
return;
_G(_transcript) = glk_stream_open_file_uni(ref, filemode_Write, 0);
glk_fileref_destroy(ref);
if (_G(_transcript) == nullptr) {
output(_G(_sys)[FAILED_TRANSCRIPT]);
return;
}
glui32 *startOfTranscript = toUnicode(_G(_sys)[TRANSCRIPT_START].c_str());
glk_put_string_stream_uni(_G(_transcript), startOfTranscript);
delete[] startOfTranscript;
glk_put_string_stream(glk_window_get_stream(_G(_bottomWindow)), _G(_sys)[TRANSCRIPT_ON].c_str());
}
void Scott::transcriptOff() {
if (_G(_transcript) == nullptr) {
output(_G(_sys)[NO_TRANSCRIPT]);
return;
}
glui32 *endOfTranscript = toUnicode(_G(_sys)[TRANSCRIPT_END].c_str());
glk_put_string_stream_uni(_G(_transcript), endOfTranscript);
delete[] endOfTranscript;
glk_stream_close(_G(_transcript), nullptr);
_G(_transcript) = nullptr;
output(_G(_sys)[TRANSCRIPT_OFF]);
}
int Scott::performExtraCommand(int extraStopTime) {
Command command = *_G(_currentCommand);
int verb = command._verb;
if (verb > _G(_gameHeader)->_numWords)
verb -= _G(_gameHeader)->_numWords;
int noun = command._noun;
if (noun > _G(_gameHeader)->_numWords)
noun -= _G(_gameHeader)->_numWords;
else if (noun) {
const char *nounWord = _G(_charWords)[_G(_currentCommand)->_nounWordIndex];
int newnoun = whichWord(nounWord, _G(_extraNouns));
newnoun = _G(_extraNounsKey)[newnoun];
if (newnoun)
noun = newnoun;
}
_G(_stopTime) = 1 + extraStopTime;
switch (verb) {
case RESTORE:
if (noun == 0 || noun == GAME) {
loadGame();
return 1;
}
break;
case RESTART:
if (noun == 0 || noun == GAME) {
output(_G(_sys)[ARE_YOU_SURE]);
if (yesOrNo()) {
_G(_shouldRestart) = 1;
}
return 1;
}
break;
case SAVE:
if (noun == 0 || noun == GAME) {
saveGame();
return 1;
}
break;
case UNDO:
if (noun == 0 || noun == COMMAND) {
restoreUndo();
return 1;
}
break;
case RAM:
if (noun == RAMLOAD) {
ramRestore();
return 1;
} else if (noun == RAMSAVE) {
ramSave();
return 1;
}
break;
case RAMSAVE:
if (noun == 0) {
ramSave();
return 1;
}
break;
case RAMLOAD:
if (noun == 0) {
ramRestore();
return 1;
}
break;
case SCRIPT:
if (noun == ON || noun == 0) {
transcriptOn();
return 1;
} else if (noun == OFF) {
transcriptOff();
return 1;
}
break;
case EXCEPT:
freeCommands();
}
_G(_stopTime) = 0;
return 0;
}
int Scott::yesOrNo() {
glk_request_char_event(_G(_bottomWindow));
event_t ev;
int result = 0;
const char y = tolower((unsigned char)_G(_sys)[YES][0]);
const char n = tolower((unsigned char)_G(_sys)[NO][0]);
do {
glk_select(&ev);
if (ev.type == evtype_CharInput) {
const char reply = tolower(ev.val1);
if (reply == y) {
result = 1;
} else if (reply == n) {
result = 2;
} else {
output(_G(_sys)[ANSWER_YES_OR_NO]);
glk_request_char_event(_G(_bottomWindow));
}
} else
updates(ev);
} while (result == 0);
return (result == 1);
}
void Scott::hitEnter() {
glk_request_char_event(_G(_bottomWindow));
event_t ev;
int result = 0;
do {
glk_select(&ev);
if (ev.type == evtype_CharInput) {
if (ev.val1 == keycode_Return) {
result = 1;
} else {
glk_request_char_event(_G(_bottomWindow));
}
} else
updates(ev);
} while (result == 0);
return;
}
void Scott::listInventory() {
int i = 0;
int lastitem = -1;
output(_G(_sys)[INVENTORY]);
while (i <= _G(_gameHeader)->_numItems) {
if (_G(_items)[i]._location == CARRIED) {
if (_G(_items)[i]._text[0] == 0) {
warning("Invisible item in inventory: %d\n", i);
i++;
continue;
}
if (lastitem > -1 && (_G(_options) & (TRS80_STYLE | SPECTRUM_STYLE)) == 0) {
output(_G(_sys)[ITEM_DELIMITER]);
}
lastitem = i;
output(_G(_items)[i]._text);
if (_G(_options) & (TRS80_STYLE | SPECTRUM_STYLE)) {
output(_G(_sys)[ITEM_DELIMITER]);
}
}
i++;
}
if (lastitem == -1)
output(_G(_sys)[NOTHING]);
else if (_G(_options) & TI994A_STYLE) {
if (!itemEndsWithPeriod(lastitem))
output(".");
output(" ");
}
if (_G(_transcript)) {
glk_put_char_stream_uni(_G(_transcript), 10);
}
}
void Scott::lookWithPause() {
char fc = _G(_rooms)[MY_LOC]._text[0];
if (_G(_rooms)[MY_LOC]._text.empty() || MY_LOC == 0 || fc == 0 || fc == '.' || fc == ' ')
return;
_G(_shouldLookInTranscript) = 1;
_pauseNextRoomDescription = 1;
look();
}
void Scott::doneIt() {
if (_splitScreen && _G(_topWindow))
look();
output("\n\n");
output(_G(_sys)[PLAY_AGAIN]);
output("\n");
if (yesOrNo()) {
_G(_shouldRestart) = 1;
} else {
cleanupAndExit();
}
}
int Scott::printScore() {
int i = 0;
int n = 0;
while (i <= _G(_gameHeader)->_numItems) {
if (_G(_items)[i]._location == _G(_gameHeader)->_treasureRoom && _G(_items)[i]._text[0] == '*')
n++;
i++;
}
display(_G(_bottomWindow), "%s %d %s%s %d.\n", _G(_sys)[IVE_STORED].c_str(), n, _G(_sys)[TREASURES].c_str(),
_G(_sys)[ON_A_SCALE_THAT_RATES].c_str(), (n * 100) / _G(_gameHeader)->_treasures);
if (n == _G(_gameHeader)->_treasures) {
output(_G(_sys)[YOUVE_SOLVED_IT].c_str());
doneIt();
return 1;
}
return 0;
}
void Scott::printNoun() {
if (_G(_currentCommand))
glk_put_string_stream_uni(glk_window_get_stream(_G(_bottomWindow)), _G(_unicodeWords)[_G(_currentCommand)->_nounWordIndex]);
}
void Scott::moveItemAToLocOfItemB(int itemA, int itemB) {
_G(_items)
[itemA]._location = _G(_items)[itemB]._location;
if (_G(_items)[itemB]._location == MY_LOC)
_G(_shouldLookInTranscript) = 1;
}
void Scott::goToStoredLoc() {
#ifdef DEBUG_ACTIONS
debug("switch location to stored location (%d) (%s).\n",
SavedRoom, _G(_rooms)[SavedRoom].Text);
#endif
int t = MY_LOC;
MY_LOC = _G(_savedRoom);
_G(_savedRoom) = t;
_G(_shouldLookInTranscript) = 1;
}
void Scott::swapLocAndRoomFlag(int index) {
#ifdef DEBUG_ACTIONS
debug("swap location<->roomflag[%d]\n", index);
#endif
int temp = MY_LOC;
MY_LOC = _G(_roomSaved)[index];
_G(_roomSaved)[index] = temp;
_G(_shouldLookInTranscript) = 1;
look();
}
void Scott::swapItemLocations(int itemA, int itemB) {
int temp = _G(_items)[itemA]._location;
_G(_items)[itemA]._location = _G(_items)[itemB]._location;
_G(_items)[itemB]._location = temp;
if (_G(_items)[itemA]._location == MY_LOC || _G(_items)[itemB]._location == MY_LOC)
_G(_shouldLookInTranscript) = 1;
}
void Scott::putItemAInRoomB(int itemA, int roomB) {
#ifdef DEBUG_ACTIONS
debug("Item %d (%s) is put in room %d (%s). MY_LOC: %d (%s)\n",
itemA, _G(_items)[arg1].Text, roomB, _G(_rooms)[roomB].Text, MY_LOC,
_G(_rooms)[MY_LOC].Text);
#endif
if (_G(_items)[itemA]._location == MY_LOC)
lookWithPause();
_G(_items)[itemA]._location = roomB;
}
void Scott::swapCounters(int index) {
#ifdef DEBUG_ACTIONS
debug("Select a counter.Current counter is swapped with backup "
"counter %d\n",
index);
#endif
if (index > 15) {
error("Scott::swapCounters(int index): ERROR! parameter out of range. Max 15, got %d", index);
index = 15;
}
int temp = _G(_currentCounter);
_G(_currentCounter) = _G(_counters)[index];
_G(_counters)[index] = temp;
#ifdef DEBUG_ACTIONS
debug("Value of new selected counter is %d\n", _G(_currentCounter));
#endif
}
void Scott::printMessage(int index) {
#ifdef DEBUG_ACTIONS
debug("Print message %d: \"%s\"\n", index, Messages[index]);
#endif
Common::String message = _G(_messages)[index];
if (!message.empty() && message[0] != 0) {
output(message);
const char lastchar = message[message.size() - 1];
if (lastchar != 13 && lastchar != 10)
output(_G(_sys)[MESSAGE_DELIMITER]);
}
}
void Scott::playerIsDead() {
#ifdef DEBUG_ACTIONS
debug("Player is dead\n");
#endif
output(_G(_sys)[IM_DEAD]);
_G(_bitFlags) &= ~(1 << DARKBIT);
MY_LOC = _G(_gameHeader)->_numRooms; /* It seems to be what the code says! */
}
void Scott::printTakenOrDropped(int index) {
output(_G(_sys[index]));
int length = _G(_sys)[index].size();
char last = _G(_sys)[index][length - 1];
if (last == 10 || last == 13)
return;
output(" ");
if ((!(_G(_currentCommand)->_allFlag & LASTALL)) || _splitScreen == 0) {
output("\n");
}
}
void Scott::printTitleScreenBuffer() {
glk_stream_set_current(glk_window_get_stream(_G(_bottomWindow)));
glk_set_style(style_User1);
clearScreen();
output(_G(_titleScreen));
glk_set_style(style_Normal);
hitEnter();
clearScreen();
}
void Scott::printTitleScreenGrid() {
int titleLength = _G(_titleScreen).size();
int rows = 0;
for (int i = 0; i < titleLength; i++)
if (_G(_titleScreen)[i] == '\n')
rows++;
winid_t titlewin = glk_window_open(_G(_bottomWindow), winmethod_Above | winmethod_Fixed, rows + 2,
wintype_TextGrid, 0);
uint width, height;
glk_window_get_size(titlewin, &width, &height);
if (width < 40 || height < static_cast<glui32>(rows + 2)) {
glk_window_close(titlewin, nullptr);
printTitleScreenBuffer();
return;
}
int offset = (width - 40) / 2;
int pos = 0;
for (int i = 1; i <= rows; i++) {
glk_window_move_cursor(titlewin, offset, i);
while (_G(_titleScreen)[pos] != '\n' && pos < titleLength)
display(titlewin, "%c", _G(_titleScreen)[pos++]);
pos++;
}
hitEnter();
glk_window_close(titlewin, nullptr);
}
void writeToRoomDescriptionStream(const char *fmt, ...) {
if (_G(_roomDescriptionStream == nullptr))
return;
va_list ap;
va_start(ap, fmt);
Common::String msg = Common::String::vformat(fmt, ap);
va_end(ap);
g_scott->glk_put_string_stream(_G(_roomDescriptionStream), msg.c_str());
}
} // End of namespace Scott
} // End of namespace Glk