diff options
author | Paul Gilbert | 2018-11-13 19:47:07 -0800 |
---|---|---|
committer | Paul Gilbert | 2018-12-08 19:05:59 -0800 |
commit | 1fb931fbd950324754536ee0b33ed0b91f68c9a2 (patch) | |
tree | a00c388b39ece9d327f9ddff4109f8795f8aa3a8 /engines/glk/scott | |
parent | 7d670ff157fbc3df45f70f9e7a5b537b3d13152b (diff) | |
download | scummvm-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.cpp | 109 | ||||
-rw-r--r-- | engines/glk/scott/detection.h | 43 | ||||
-rw-r--r-- | engines/glk/scott/scott.cpp | 1263 | ||||
-rw-r--r-- | engines/glk/scott/scott.h | 191 |
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, <, &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 |