diff options
author | Paul Gilbert | 2018-10-17 20:07:11 -0700 |
---|---|---|
committer | Paul Gilbert | 2018-12-08 19:05:59 -0800 |
commit | 22648145a91ae42ed0e1ab69b1dc9ad564e9fd1d (patch) | |
tree | 3517e8ec3eca2a36d2297101490c417bedd49b9e | |
parent | b62e30b84c6dfa98942ca094f86ecc0538cebbec (diff) | |
download | scummvm-rg350-22648145a91ae42ed0e1ab69b1dc9ad564e9fd1d.tar.gz scummvm-rg350-22648145a91ae42ed0e1ab69b1dc9ad564e9fd1d.tar.bz2 scummvm-rg350-22648145a91ae42ed0e1ab69b1dc9ad564e9fd1d.zip |
GLK: SCOTT: Initial conversion of ScottFree
-rw-r--r-- | engines/gargoyle/gargoyle.cpp | 9 | ||||
-rw-r--r-- | engines/gargoyle/gargoyle.h | 6 | ||||
-rw-r--r-- | engines/gargoyle/glk.cpp | 6 | ||||
-rw-r--r-- | engines/gargoyle/glk.h | 6 | ||||
-rw-r--r-- | engines/gargoyle/scott/scott.cpp | 1259 | ||||
-rw-r--r-- | engines/gargoyle/scott/scott.h | 76 |
6 files changed, 1346 insertions, 16 deletions
diff --git a/engines/gargoyle/gargoyle.cpp b/engines/gargoyle/gargoyle.cpp index 3457f5fa63..7953c684f8 100644 --- a/engines/gargoyle/gargoyle.cpp +++ b/engines/gargoyle/gargoyle.cpp @@ -32,10 +32,12 @@ namespace Gargoyle { GargoyleEngine::GargoyleEngine(OSystem *syst, const GargoyleGameDescription *gameDesc) : - _gameDescription(gameDesc), Engine(syst) { + _gameDescription(gameDesc), Engine(syst), _random("Gargoyle") { + _screen = nullptr; } GargoyleEngine::~GargoyleEngine() { + delete _screen; } void GargoyleEngine::initialize() { @@ -46,11 +48,14 @@ void GargoyleEngine::initialize() { DebugMan.addDebugChannel(kDebugSound, "sound", "Sound and Music handling"); initGraphics(640, 480, false); + _screen = new Graphics::Screen(); } Common::Error GargoyleEngine::run() { initialize(); - main(); + + // TODO: Pass proper gamefile + runGame(nullptr); return Common::kNoError; } diff --git a/engines/gargoyle/gargoyle.h b/engines/gargoyle/gargoyle.h index 09402487c2..37c1a22196 100644 --- a/engines/gargoyle/gargoyle.h +++ b/engines/gargoyle/gargoyle.h @@ -24,10 +24,12 @@ #define GARGOYLE_GARGOLE_H #include "common/scummsys.h" +#include "common/random.h" #include "common/system.h" #include "common/serializer.h" #include "engines/advancedDetector.h" #include "engines/engine.h" +#include "graphics/screen.h" namespace Gargoyle { @@ -67,6 +69,8 @@ private: void initialize(); protected: const GargoyleGameDescription *_gameDescription; + Graphics::Screen *_screen; + Common::RandomSource _random; int _loadSaveSlot; // Engine APIs @@ -76,7 +80,7 @@ protected: /** * Main game loop for the individual interpreters */ - virtual void main() = 0; + virtual void runGame(Common::SeekableReadStream *gameFile) = 0; public: GargoyleEngine(OSystem *syst, const GargoyleGameDescription *gameDesc); virtual ~GargoyleEngine(); diff --git a/engines/gargoyle/glk.cpp b/engines/gargoyle/glk.cpp index 350b40f4ec..82943bd497 100644 --- a/engines/gargoyle/glk.cpp +++ b/engines/gargoyle/glk.cpp @@ -191,11 +191,11 @@ void Glk::glk_put_char_stream(strid_t str, unsigned char ch) { // TODO } -void Glk::glk_put_string(char *s) { +void Glk::glk_put_string(const char *s) { // TODO } -void Glk::glk_put_string_stream(strid_t str, char *s) { +void Glk::glk_put_string_stream(strid_t str, const char *s) { // TODO } @@ -203,7 +203,7 @@ void Glk::glk_put_buffer(char *buf, glui32 len) { // TODO } -void Glk::glk_put_buffer_stream(strid_t str, char *buf, glui32 len) { +void Glk::glk_put_buffer_stream(strid_t str, const char *buf, glui32 len) { // TODO } diff --git a/engines/gargoyle/glk.h b/engines/gargoyle/glk.h index eab6f0be70..55cd563ba3 100644 --- a/engines/gargoyle/glk.h +++ b/engines/gargoyle/glk.h @@ -94,10 +94,10 @@ public: void glk_put_char(unsigned char ch); void glk_put_char_stream(strid_t str, unsigned char ch); - void glk_put_string(char *s); - void glk_put_string_stream(strid_t str, char *s); + void glk_put_string(const char *s); + void glk_put_string_stream(strid_t str, const char *s); void glk_put_buffer(char *buf, glui32 len); - void glk_put_buffer_stream(strid_t str, char *buf, glui32 len); + void glk_put_buffer_stream(strid_t str, const char *buf, glui32 len); void glk_set_style(glui32 styl); void glk_set_style_stream(strid_t str, glui32 styl); diff --git a/engines/gargoyle/scott/scott.cpp b/engines/gargoyle/scott/scott.cpp index 9a1a6e324e..02096c6f27 100644 --- a/engines/gargoyle/scott/scott.cpp +++ b/engines/gargoyle/scott/scott.cpp @@ -25,16 +25,1265 @@ namespace Gargoyle { namespace Scott { -void Scott::main() { +/* +glkunix_argumentlist_t glkunix_arguments[] = +{ + { "-y", glkunix_arg_NoValue, "-y Generate 'You are', 'You are carrying' type messages for games that use these instead (eg Robin Of Sherwood)" }, + { "-i", glkunix_arg_NoValue, "-i Generate 'I am' type messages (default)" }, + { "-d", glkunix_arg_NoValue, "-d Debugging info on load " }, + { "-s", glkunix_arg_NoValue, "-s Generate authentic Scott Adams driver light messages rather than other driver style ones (Light goes out in n turns..)" }, + { "-t", glkunix_arg_NoValue, "-t Generate TRS80 style display (terminal width is 64 characters; a line <-----------------> is displayed after the top stuff; objects have periods after them instead of hyphens" }, + { "-p", glkunix_arg_NoValue, "-p Use for prehistoric databases which don't use bit 16" }, + { "-w", glkunix_arg_NoValue, "-w Disable upper window" }, + { "", glkunix_arg_ValueFollows, "filename file to load" }, + + { nullptr, glkunix_arg_End, nullptr } +}; +*/ + +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) { + 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("\ +Scott Free, A Scott Adams game driver in C.\n\ +Release 1.14, (c) 1993,1994,1995 Swansea University Computer Society.\n\ +Distributed under the GNU software license\n\n"); + LoadDatabase(gameFile, (Options & DEBUGGING) ? 1 : 0); + + while (!shouldQuit()) { + glk_tick(); + + PerformActions(0, 0); + + Look(); + + if (GetInput(&vb, &no) == -1) + continue; + 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() { + /* + int argc = data->argc; + char **argv = data->argv; + + if (argc < 1) + return 0; + + while (argv[1]) + { + if (*argv[1] != '-') + break; + switch (argv[1][1]) + { + case 'y': + Options |= YOUARE; + break; + case 'i': + Options &= ~YOUARE; + break; + case 'd': + Options |= DEBUGGING; + break; + case 's': + Options |= SCOTTLIGHT; + break; + case 't': + Options |= TRS80_STYLE; + break; + case 'p': + Options |= PREHISTORIC_LAMP; + break; + case 'w': + split_screen = 0; + break; + } + argv++; + argc--; + } + + if (argc == 2) + { + game_file = argv[1]; +#ifdef GARGLK + const char *s; + if ((s = strrchr(game_file, '/')) != nullptr || (s = strrchr(game_file, '\\')) != nullptr) + { + garglk_set_story_name(s + 1); + } + else + { + garglk_set_story_name(game_file); + } +#endif + } + */ +} + +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); + + glk_request_timer_events(0); +} + +void Scott::Fatal(const char *x) { + error(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"); + } + + do { + c = f->readByte(); + if (c == EOF) + Fatal("EOF in string"); + 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++] = '?'; + } while (1); + + tmp[ct] = 0; + t = (char *)MemAlloc(ct + 1); + memcpy(t, tmp, ct + 1); + return t; +} + +void Scott::LoadDatabase(Common::SeekableReadStream *f, int 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) { + Display(Bottom, "%s", a); +} + +void Scott::OutputNumber(int a) { + Display(Bottom, "%d", a); +} + +void Scott::Look(void) { + static char *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); - switch (ev.type) { - default: - /* do nothing */ + + 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) { + strid_t file; + frefid_t ref; + int ct; + Common::String msg; + + ref = glk_fileref_create_by_prompt(fileusage_TextMode | fileusage_SavedGame, filemode_Write, 0); + if (ref == nullptr) return; + + file = glk_stream_open_file(ref, filemode_Write, 0); + glk_fileref_destroy(ref); + if (file == nullptr) return; + + for (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("%lu %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 (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"); +} + +void Scott::LoadGame(void) { + strid_t file; + frefid_t ref; + char buf[128]; + int ct = 0; + short lo; + short DarkFlag; + + ref = glk_fileref_create_by_prompt(fileusage_TextMode | fileusage_SavedGame, filemode_Read, 0); + if (ref == nullptr) return; + + file = glk_stream_open_file(ref, filemode_Read, 0); + glk_fileref_destroy(ref); + if (file == nullptr) return; + + 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, "%ld %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; + } +} + +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); + 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(); + 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 = '\0'; + + for (size_t idx = 0; idx < count; ++idx) { + if (idx > 0) { + while (f->pos() < f->size() && Common::isSpace(c)) + c = f->readByte(); + } else { + c = f->readByte(); + } + + // Get the next value + int *val = (int *)va_arg(va, int); + *val = 0; + while (Common::isDigit(c)) { + *val = (*val * 10) + (c - '0'); + c = f->readByte(); + } + } + + va_end(va); } } // End of namespace Scott diff --git a/engines/gargoyle/scott/scott.h b/engines/gargoyle/scott/scott.h index 0e7d85cc72..52ad560ebe 100644 --- a/engines/gargoyle/scott/scott.h +++ b/engines/gargoyle/scott/scott.h @@ -45,6 +45,9 @@ namespace Scott { #define TRS80_STYLE 8 // Display in style used on TRS-80 #define PREHISTORIC_LAMP 16 // Destroy the lamp (very old databases) +#define TRS80_LINE "\n<------------------------------------------------------------>\n" +#define MyLoc (GameHeader.PlayerRoom) + struct Header { int Unknown; int NumItems; @@ -58,17 +61,30 @@ struct Header { 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 { @@ -76,28 +92,84 @@ struct Item { 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 +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, int 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) : Glk(syst, gameDesc) {} + Scott(OSystem *syst, const GargoyleGameDescription *gameDesc); /** * Execute the game */ - virtual void main(); + virtual void runGame(Common::SeekableReadStream *gameFile) override; }; } // End of namespace Scott |