aboutsummaryrefslogtreecommitdiff
path: root/engines/glk/scott
diff options
context:
space:
mode:
authorPaul Gilbert2018-11-13 19:47:07 -0800
committerPaul Gilbert2018-12-08 19:05:59 -0800
commit1fb931fbd950324754536ee0b33ed0b91f68c9a2 (patch)
treea00c388b39ece9d327f9ddff4109f8795f8aa3a8 /engines/glk/scott
parent7d670ff157fbc3df45f70f9e7a5b537b3d13152b (diff)
downloadscummvm-rg350-1fb931fbd950324754536ee0b33ed0b91f68c9a2.tar.gz
scummvm-rg350-1fb931fbd950324754536ee0b33ed0b91f68c9a2.tar.bz2
scummvm-rg350-1fb931fbd950324754536ee0b33ed0b91f68c9a2.zip
GLK: Changing gargoyle folder to glk
Diffstat (limited to 'engines/glk/scott')
-rw-r--r--engines/glk/scott/detection.cpp109
-rw-r--r--engines/glk/scott/detection.h43
-rw-r--r--engines/glk/scott/scott.cpp1263
-rw-r--r--engines/glk/scott/scott.h191
4 files changed, 1606 insertions, 0 deletions
diff --git a/engines/glk/scott/detection.cpp b/engines/glk/scott/detection.cpp
new file mode 100644
index 0000000000..900f6507fc
--- /dev/null
+++ b/engines/glk/scott/detection.cpp
@@ -0,0 +1,109 @@
+/* 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/detection.h"
+#include "common/file.h"
+#include "common/md5.h"
+
+namespace Gargoyle {
+namespace Scott {
+
+struct ScottGame {
+ const char *_md5;
+ const char *_gameId;
+ int32 _filesize;
+ const char *_desc;
+};
+
+const ScottGame SCOTT_GAMES[] = {
+ // PC game versions
+ { "7c6f495d757a54e73d259efc718d8024", "adventureland", 15896, "Adventureland" },
+ { "ea535fa7684508410151b4561de1f323", "pirateadventure", 16325, "Pirate Adventure" },
+ { "379c77a9a483886366b3b5c425e56410", "missionimpossible", 15275, "Mission Impossible" },
+ { "a530a6857d1092eaa177eee575c94c71", "voodoocastle", 15852, "Voodoo Castle" },
+ { "5ebb4ade985670bb2eac54f8fa202214", "thecount", 17476, "The Count" },
+ { "c57bb6df04dc77a2b232bc5bcab6e417", "strangeodyssey", 17489, "Strange Odyssey" },
+ { "ce2931ac3d5cbc270a5cb7be9e614f6e", "mysteryfunhouse", 17165, "Mystery Fun House" },
+ { "4e6127fad6b5d75eccd3f3b101f8c9c8", "pyramidofdoom", 17673, "Pyramid Of Doom" },
+ { "2c08327ab06d5490bd9e367ddaeca627", "ghosttown", 17831, "Ghost Town" },
+ { "8feb77f11d32e9567ce2fc7d435eaf44", "savageisland1", 19533, "Savage Island, Part 1" },
+ { "20c40a349f7a214ac515fb1d63c30a87", "savageisland2", 18367, "Savage Island, Part 2" },
+ { "e2a8f956ab215012d1495550c4c11ee8", "goldenvoyage", 18513, "The Golden Voyage" },
+ { "f986d7e1ee074f65b6c1d00461c9b3c3", "adventure13", 19232, "Adventure 13" },
+ { "6d98f422cc986d959a3c74351785aea3", "adventure14", 19013, "Adventure 14" },
+ { "aadcc04e6b37eb9d30a58b5bc775842e", "marveladventure", 18876, "Marvel Adventure #1" },
+ { "d569a769f304dc02b3062d97458ddd01", "scottsampler", 13854, "Adventure International's Mini-Adventure Sampler" },
+
+ // PDA game versions
+ { "ae541fc1085da2f7d561b72ed20a6bc1", "adventureland", 18003, "Adventureland" },
+ { "cbd47ab4fcfe00231ffd71d52378d410", "pirateadventure", 18482, "Pirate Adventure" },
+ { "9251ab2c64e63559d8a6e9e6246760a5", "missionimpossible", 17227, "Mission Impossible" },
+ { "be849c5747c7fc3b201984afb4403b8e", "voodoocastle", 18140, "Voodoo Castle" },
+ { "85b75b6079b5ee572b5259b29a0e5d21", "thecount", 19999, "The Count" },
+ { "c423cae841ac1927b5b2e503607b21bc", "strangeodyssey", 20115, "Strange Odyssey" },
+ { "326b98b991d401605074e64d474ce566", "mysteryfunhouse", 19700, "Mystery Fun House" },
+ { "8ef9010399f055da9adb15ce7745a11c", "pyramidofdoom", 20320, "Pyramid Of Doom" },
+ { "fcdcca8b2acf76ba2d0006cefa3630a1", "ghosttown", 20687, "Ghost Town" },
+ { "c8aaa80f07c40fa8e4b17432644919dc", "savageisland1", 22669, "Savage Island, Part 1" },
+ { "2add0f28d9b236c866890cdf8d86ee60", "savageisland2", 21169, "Savage Island, Part 2" },
+ { "675126bd0477e8ed9230ad3db5afc45f", "goldenvoyage", 21401, "The Golden Voyage" },
+ { "0ef0def798d895ed766041fa99dd28a0", "adventure13", 22346, "Adventure 13" },
+ { "0bf1bcc649422798332a38c88588fdff", "adventure14", 22087, "Adventure 14" },
+ { "a0a5423967287dae9cbeb9abe8324479", "buckaroobonzai", 21038, "Buckaroo Banzai" },
+ { nullptr, nullptr, 0, nullptr }
+};
+
+bool ScottMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &gameList) {
+ Common::File gameFile;
+ Common::String md5;
+
+ // Loop through the files of the folder
+ for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
+ if (file->isDirectory() || !(file->getName().hasSuffixIgnoreCase(".saga")
+ || file->getName().hasSuffixIgnoreCase(".dat")))
+ continue;
+
+ if (gameFile.open(*file)) {
+ md5 = Common::computeStreamMD5AsString(gameFile, 5000);
+
+ // Scan through the Scott game list for a match
+ const ScottGame *p = SCOTT_GAMES;
+ while (p->_md5 && p->_filesize != gameFile.size() && md5 != p->_md5)
+ ++p;
+
+ if (p->_filesize) {
+ // Found a match
+ DetectedGame gd(p->_gameId, p->_desc, Common::EN_ANY, Common::kPlatformUnknown);
+ gd.addExtraEntry("filename", file->getName());
+
+ gameList.push_back(gd);
+ }
+
+ gameFile.close();
+ }
+ }
+
+ return !gameList.empty();
+}
+
+} // End of namespace Scott
+} // End of namespace Gargoyle
diff --git a/engines/glk/scott/detection.h b/engines/glk/scott/detection.h
new file mode 100644
index 0000000000..cd487bdfdd
--- /dev/null
+++ b/engines/glk/scott/detection.h
@@ -0,0 +1,43 @@
+/* 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.
+ *
+ */
+
+#ifndef GLK_SCOTT_DETECTION
+#define GLK_SCOTT_DETECTION
+
+#include "common/fs.h"
+#include "engines/game.h"
+
+namespace Gargoyle {
+namespace Scott {
+
+class ScottMetaEngine {
+public:
+ /**
+ * Detect supported games
+ */
+ static bool detectGames(const Common::FSList &fslist, DetectedGames &gameList);
+};
+
+} // End of namespace Scott
+} // End of namespace Gargoyle
+
+#endif
diff --git a/engines/glk/scott/scott.cpp b/engines/glk/scott/scott.cpp
new file mode 100644
index 0000000000..a5e866796a
--- /dev/null
+++ b/engines/glk/scott/scott.cpp
@@ -0,0 +1,1263 @@
+/* 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 "common/config-manager.h"
+
+namespace Gargoyle {
+namespace Scott {
+
+Scott::Scott(OSystem *syst, const GargoyleGameDescription *gameDesc) : Glk(syst, gameDesc),
+ 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
+ loadDatabase(gameFile, (Options & DEBUGGING) ? 1 : 0);
+
+ // Main game loop
+ while (!shouldQuit()) {
+ glk_tick();
+
+ performActions(0, 0);
+
+ 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;
+ }
+
+ // 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 ||
+ Items[LIGHT_SOURCE].Location == MyLoc) {
+ 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 == MyLoc) {
+
+ 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(Bottom);
+}
+
+void *Scott::memAlloc(int size) {
+ void *t = (void *)malloc(size);
+ if (t == nullptr)
+ fatal("Out of memory");
+ return t;
+}
+
+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];
+ 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 && Items[ct].Location == loc &&
+ xstrncasecmp(Items[ct].AutoGet, word, GameHeader.WordLength) == 0)
+ return ct;
+ ct++;
+ }
+
+ return -1;
+}
+
+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 != '"') {
+ 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;
+ 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;
+
+ // Load the header
+ readInts(f, 12, &unused, &ni, &na, &nw, &nr, &mc, &pr, &tr, &wl, &lt, &mn, &trm);
+
+ GameHeader.NumItems = ni;
+ Items = (Item *)memAlloc(sizeof(Item) * (ni + 1));
+ GameHeader.NumActions = na;
+ Actions = (Action *)memAlloc(sizeof(Action) * (na + 1));
+ GameHeader.NumWords = nw;
+ GameHeader.WordLength = wl;
+ Verbs = (const char **)memAlloc(sizeof(char *) * (nw + 1));
+ Nouns = (const char **)memAlloc(sizeof(char *) * (nw + 1));
+ GameHeader.NumRooms = nr;
+ Rooms = (Room *)memAlloc(sizeof(Room) * (nr + 1));
+ GameHeader.MaxCarry = mc;
+ GameHeader.PlayerRoom = pr;
+ GameHeader.Treasures = tr;
+ GameHeader.LightTime = lt;
+ LightRefill = lt;
+ GameHeader.NumMessages = mn;
+ 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,
+ &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);
+ while (ct < nw + 1) {
+ Verbs[ct] = readString(f);
+ Nouns[ct] = readString(f);
+ ct++;
+ }
+ ct = 0;
+ rp = Rooms;
+ if (loud)
+ debug("Reading %d rooms.", nr);
+ while (ct < nr + 1) {
+ readInts(f, 6,
+ &rp->Exits[0], &rp->Exits[1], &rp->Exits[2],
+ &rp->Exits[3], &rp->Exits[4], &rp->Exits[5]);
+
+ rp->Text = readString(f);
+ ct++;
+ rp++;
+ }
+
+ ct = 0;
+ if (loud)
+ debug("Reading %d messages.", mn);
+ while (ct < mn + 1) {
+ Messages[ct] = readString(f);
+ ct++;
+ }
+
+ ct = 0;
+ if (loud)
+ debug("Reading %d items.", ni);
+ ip = Items;
+ while (ct < ni + 1) {
+ 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
+ while (ct < na + 1) {
+ 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);
+}
+
+void Scott::output(const char *a) {
+ if (_saveSlot == -1)
+ display(Bottom, "%s", a);
+}
+
+void Scott::outputNumber(int a) {
+ display(Bottom, "%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 (split_screen)
+ glk_window_clear(Top);
+
+ if ((BitFlags & (1 << DARKBIT)) && Items[LIGHT_SOURCE].Location != CARRIED
+ && Items[LIGHT_SOURCE].Location != MyLoc) {
+ if (Options & YOUARE)
+ display(Top, "You can't see. It is too dark!\n");
+ else
+ display(Top, "I can't see. It is too dark!\n");
+ if (Options & TRS80_STYLE)
+ display(Top, TRS80_LINE);
+ return;
+ }
+ r = &Rooms[MyLoc];
+ if (*r->Text == '*')
+ display(Top, "%s\n", r->Text + 1);
+ else {
+ if (Options & YOUARE)
+ display(Top, "You are in a %s\n", r->Text);
+ else
+ display(Top, "I'm in a %s\n", r->Text);
+ }
+
+ ct = 0;
+ f = 0;
+ display(Top, "\nObvious exits: ");
+ while (ct < 6) {
+ if (r->Exits[ct] != 0) {
+ if (f == 0)
+ f = 1;
+ else
+ display(Top, ", ");
+ display(Top, "%s", ExitNames[ct]);
+ }
+ ct++;
+ }
+
+ if (f == 0)
+ 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) {
+ display(Top, "\nYou can also see: ");
+ pos = 18;
+ } else {
+ display(Top, "\nI can also see: ");
+ pos = 16;
+ }
+ f++;
+ } else if (!(Options & TRS80_STYLE)) {
+ display(Top, " - ");
+ pos += 3;
+ }
+ if (pos + (int)strlen(Items[ct].Text) > (Width - 10)) {
+ pos = 0;
+ display(Top, "\n");
+ }
+ display(Top, "%s", Items[ct].Text);
+ pos += strlen(Items[ct].Text);
+ if (Options & TRS80_STYLE) {
+ display(Top, ". ");
+ pos += 2;
+ }
+ }
+ ct++;
+ }
+
+ display(Top, "\n");
+ if (Options & TRS80_STYLE)
+ display(Top, TRS80_LINE);
+}
+
+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;
+}
+
+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)
+ look();
+ } while (ev.type != evtype_Quit);
+
+ buf[ev.val1] = 0;
+}
+
+void Scott::saveGame(void) {
+ frefid_t ref = glk_fileref_create_by_prompt(fileusage_TextMode | fileusage_SavedGame,
+ 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());
+ }
+
+ msg = Common::String::format("%u %d %hd %d %d %hd\n",
+ 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);
+ output("Saved.\n");
+
+ return Common::kNoError;
+}
+
+void Scott::loadGame(void) {
+ frefid_t ref = glk_fileref_create_by_prompt(fileusage_TextMode | fileusage_SavedGame,
+ 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;
+
+ 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);
+ sscanf(buf, "%u %hd %d %d %d %d\n",
+ &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;
+}
+
+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 != MyLoc)
+ return 0;
+ break;
+ case 3:
+ 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:
+ 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 == 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:
+ 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 = 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:
+ if (Options & YOUARE)
+ output("You are dead.\n");
+ else
+ 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 &&
+ *Items[i].Text == '*')
+ 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 = 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:
+ 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 = MyLoc;
+ MyLoc = 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 int disable_sysfunc = 0; // 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 == MyLoc ||
+ Items[LIGHT_SOURCE].Location == CARRIED)
+ d = 0;
+ if (d)
+ output("Dangerous to move in the dark! ");
+ nl = Rooms[MyLoc].Exits[no - 1];
+ if (nl != 0) {
+ MyLoc = 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();
+ }
+ 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;
+ }
+ }
+ }
+ 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 ||
+ 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 == MyLoc && Items[i].AutoGet != nullptr && Items[i].AutoGet[0] != '*') {
+ no = whichWord(Items[i].AutoGet, Nouns);
+ disable_sysfunc = 1; // Don't recurse into auto get !
+ performActions(vb, no); // Recursively check each items table code
+ disable_sysfunc = 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, MyLoc);
+ 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 && Items[i].AutoGet[0] != '*') {
+ no = whichWord(Items[i].AutoGet, Nouns);
+ disable_sysfunc = 1;
+ performActions(vb, no);
+ disable_sysfunc = 0;
+ Items[i].Location = MyLoc;
+ 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 = MyLoc;
+ 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 Gargoyle
diff --git a/engines/glk/scott/scott.h b/engines/glk/scott/scott.h
new file mode 100644
index 0000000000..ec6a8601ba
--- /dev/null
+++ b/engines/glk/scott/scott.h
@@ -0,0 +1,191 @@
+/* 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.
+ *
+ */
+
+#ifndef GLK_SCOTT
+#define GLK_SCOTT
+
+/*
+ * Controlling block
+ */
+
+#include "common/scummsys.h"
+#include "glk/glk.h"
+
+namespace Gargoyle {
+namespace Scott {
+
+#define LIGHT_SOURCE 9 // Always 9 how odd
+#define CARRIED 255 // Carried
+#define DESTROYED 0 // Destroyed
+#define DARKBIT 15
+#define LIGHTOUTBIT 16 // Light gone out
+
+enum GameOption {
+ YOUARE = 1, ///< You are not I am
+ SCOTTLIGHT = 2, ///< Authentic Scott Adams light messages
+ DEBUGGING = 4, ///< Info from database load
+ TRS80_STYLE = 8, ///< Display in style used on TRS-80
+ PREHISTORIC_LAMP = 16 ///< Destroy the lamp (very old databases)
+};
+
+#define TRS80_LINE "\n<------------------------------------------------------------>\n"
+#define MyLoc (GameHeader.PlayerRoom)
+
+struct Header {
+ int Unknown;
+ int NumItems;
+ int NumActions;
+ int NumWords; ///< Smaller of verb/noun is padded to same size
+ int NumRooms;
+ int MaxCarry;
+ int PlayerRoom;
+ int Treasures;
+ int WordLength;
+ int LightTime;
+ int NumMessages;
+ int TreasureRoom;
+
+ Header() : Unknown(0), NumItems(0), NumActions(0), NumWords(0), NumRooms(0),
+ MaxCarry(0), PlayerRoom(0), Treasures(0), WordLength(0), LightTime(0),
+ NumMessages(0), TreasureRoom(0) {}
+};
+
+struct Action {
+ uint Vocab;
+ uint Condition[5];
+ uint action[2];
+
+ Action() : Vocab(0) {
+ Common::fill(&Condition[0], &Condition[5], 0);
+ Common::fill(&action[0], &action[2], 0);
+ }
+};
+
+struct Room {
+ char *Text;
+ short Exits[6];
+
+ Room() : Text(0) {
+ Common::fill(&Exits[0], &Exits[6], 0);
+ }
+};
+
+struct Item {
+ char *Text; // PORTABILITY WARNING: THESE TWO MUST BE 8 BIT VALUES.
+ byte Location;
+ byte InitialLoc;
+ char *AutoGet;
+
+ Item() : Text(nullptr), Location(0), InitialLoc(0), AutoGet(nullptr) {}
+};
+
+struct Tail {
+ int Version;
+ int AdventureNumber;
+ int Unknown;
+
+ Tail() : Version(0), AdventureNumber(0), Unknown(0) {}
+};
+
+/**
+ * Scott Adams game interpreter
+ */
+class Scott : public Glk {
+private:
+ Header GameHeader;
+ Item *Items;
+ Room *Rooms;
+ const char **Verbs;
+ const char **Nouns;
+ const char **Messages;
+ Action *Actions;
+ int LightRefill;
+ char NounText[16];
+ int Counters[16]; ///< Range unknown
+ int CurrentCounter;
+ int SavedRoom;
+ int RoomSaved[16]; ///< Range unknown
+ int Options; ///< Option flags set
+ int Width; ///< Terminal width
+ int TopHeight; ///< Height of top window
+
+ bool split_screen;
+ winid_t Bottom, Top;
+ uint32 BitFlags; ///< Might be >32 flags - I haven't seen >32 yet
+ int _saveSlot; ///< Save slot when loading savegame from launcher
+private:
+ /**
+ * Initialization code
+ */
+ void initialize();
+
+ void display(winid_t w, const char *fmt, ...);
+ void delay(int seconds);
+ void fatal(const char *x);
+ void clearScreen(void);
+ void *memAlloc(int size);
+ bool randomPercent(uint n);
+ int countCarried(void);
+ const char *mapSynonym(const char *word);
+ int matchUpItem(const char *text, int loc);
+ char *readString(Common::SeekableReadStream *f);
+ void loadDatabase(Common::SeekableReadStream *f, bool loud);
+ void output(const char *a);
+ void outputNumber(int a);
+ void look(void);
+ int whichWord(const char *word, const char **list);
+ void lineInput(char *buf, size_t n);
+ void saveGame(void);
+ void loadGame(void);
+ int getInput(int *vb, int *no);
+ int performLine(int ct);
+ int performActions(int vb, int no);
+
+ int xstrcasecmp(const char *, const char *);
+ int xstrncasecmp(const char *, const char *, size_t);
+ void readInts(Common::SeekableReadStream *f, size_t count, ...);
+public:
+ /**
+ * Constructor
+ */
+ Scott(OSystem *syst, const GargoyleGameDescription *gameDesc);
+
+ /**
+ * Execute the game
+ */
+ virtual void runGame(Common::SeekableReadStream *gameFile) override;
+
+ /**
+ * Load a savegame
+ */
+ virtual Common::Error loadGameState(int slot) override;
+
+ /**
+ * Save the game
+ */
+ virtual Common::Error saveGameState(int slot, const Common::String &desc) override;
+};
+
+} // End of namespace Scott
+} // End of namespace Gargoyle
+
+#endif