diff options
author | Paul Gilbert | 2019-07-20 14:58:39 -0700 |
---|---|---|
committer | Paul Gilbert | 2019-09-29 15:08:52 -0700 |
commit | 255f2b4c82867b3c113743d02cf91b91a15176bd (patch) | |
tree | 6157bd018d16aa066d22cdc53926fe4c17da0491 /engines/glk | |
parent | 6d6dd2ed62b08a89b15982160ef79faef340d524 (diff) | |
download | scummvm-rg350-255f2b4c82867b3c113743d02cf91b91a15176bd.tar.gz scummvm-rg350-255f2b4c82867b3c113743d02cf91b91a15176bd.tar.bz2 scummvm-rg350-255f2b4c82867b3c113743d02cf91b91a15176bd.zip |
GLK: QUEST: Initial subengine commit
Diffstat (limited to 'engines/glk')
29 files changed, 8735 insertions, 0 deletions
diff --git a/engines/glk/detection.cpp b/engines/glk/detection.cpp index 60b2faa01c..f6531723d0 100644 --- a/engines/glk/detection.cpp +++ b/engines/glk/detection.cpp @@ -39,6 +39,8 @@ #include "glk/hugo/hugo.h" #include "glk/magnetic/detection.h" #include "glk/magnetic/magnetic.h" +#include "glk/quest/detection.h" +#include "glk/quest/quest.h" #include "glk/scott/detection.h" #include "glk/scott/scott.h" #include "glk/tads/detection.h" @@ -163,6 +165,7 @@ Common::Error GlkMetaEngine::createInstance(OSystem *syst, Engine **engine) cons else if ((*engine = create<Glk::Frotz::FrotzMetaEngine, Glk::Frotz::Frotz>(syst, gameDesc)) != nullptr) {} else if ((*engine = create<Glk::Glulxe::GlulxeMetaEngine, Glk::Glulxe::Glulxe>(syst, gameDesc)) != nullptr) {} else if ((*engine = create<Glk::Hugo::HugoMetaEngine, Glk::Hugo::Hugo>(syst, gameDesc)) != nullptr) {} + else if ((*engine = create<Glk::Quest::QuestMetaEngine, Glk::Quest::Quest>(syst, gameDesc)) != nullptr) {} else if ((*engine = create<Glk::Scott::ScottMetaEngine, Glk::Scott::Scott>(syst, gameDesc)) != nullptr) {} #ifndef RELEASE_BUILD else if ((*engine = create<Glk::Magnetic::MagneticMetaEngine, Glk::Magnetic::Magnetic>(syst, gameDesc)) != nullptr) {} @@ -211,6 +214,7 @@ PlainGameList GlkMetaEngine::getSupportedGames() const { Glk::Frotz::FrotzMetaEngine::getSupportedGames(list); Glk::Glulxe::GlulxeMetaEngine::getSupportedGames(list); Glk::Hugo::HugoMetaEngine::getSupportedGames(list); + Glk::Quest::QuestMetaEngine::getSupportedGames(list); Glk::Scott::ScottMetaEngine::getSupportedGames(list); #ifndef RELEASE_BUILD Glk::Magnetic::MagneticMetaEngine::getSupportedGames(list); @@ -242,6 +246,9 @@ PlainGameDescriptor GlkMetaEngine::findGame(const char *gameId) const { gd = Glk::Hugo::HugoMetaEngine::findGame(gameId); if (gd._description) return gd; + gd = Glk::Quest::QuestMetaEngine::findGame(gameId); + if (gd._description) return gd; + gd = Glk::Scott::ScottMetaEngine::findGame(gameId); if (gd._description) return gd; @@ -267,6 +274,7 @@ DetectedGames GlkMetaEngine::detectGames(const Common::FSList &fslist) const { Glk::Frotz::FrotzMetaEngine::detectGames(fslist, detectedGames); Glk::Glulxe::GlulxeMetaEngine::detectGames(fslist, detectedGames); Glk::Hugo::HugoMetaEngine::detectGames(fslist, detectedGames); + Glk::Quest::QuestMetaEngine::detectGames(fslist, detectedGames); Glk::Scott::ScottMetaEngine::detectGames(fslist, detectedGames); #ifndef RELEASE_BUILD @@ -286,6 +294,7 @@ void GlkMetaEngine::detectClashes() const { Glk::Frotz::FrotzMetaEngine::detectClashes(map); Glk::Glulxe::GlulxeMetaEngine::detectClashes(map); Glk::Hugo::HugoMetaEngine::detectClashes(map); + Glk::Quest::QuestMetaEngine::detectClashes(map); Glk::Scott::ScottMetaEngine::detectClashes(map); #ifndef RELEASE_BUILD diff --git a/engines/glk/glk_types.h b/engines/glk/glk_types.h index ca20fe8a06..f99d2b1972 100644 --- a/engines/glk/glk_types.h +++ b/engines/glk/glk_types.h @@ -48,6 +48,7 @@ enum InterpreterType { INTERPRETER_JACL, INTERPRETER_LEVEL9, INTERPRETER_MAGNETIC, + INTERPRETER_QUEST, INTERPRETER_SCARE, INTERPRETER_SCOTT, INTERPRETER_TADS2, diff --git a/engines/glk/module.mk b/engines/glk/module.mk index dd63c77952..4f4608af83 100644 --- a/engines/glk/module.mk +++ b/engines/glk/module.mk @@ -182,6 +182,17 @@ MODULE_OBJS := \ magnetic/graphics.o \ magnetic/magnetic.o \ magnetic/sound.o \ + quest/detection.o \ + quest/geas_file.o \ + quest/geas_glk.o \ + quest/geas_runner.o \ + quest/geas_state.o \ + quest/geas_util.o \ + quest/quest.o \ + quest/tstring.o \ + quest/read_file.o \ + quest/string.o \ + quest/streams.o \ scott/detection.o \ scott/scott.o \ tads/detection.o \ diff --git a/engines/glk/quest/detection.cpp b/engines/glk/quest/detection.cpp new file mode 100644 index 0000000000..a0aa6b8634 --- /dev/null +++ b/engines/glk/quest/detection.cpp @@ -0,0 +1,93 @@ +/* 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/quest/detection.h" +#include "glk/quest/detection_tables.h" +#include "common/debug.h" +#include "common/file.h" +#include "common/md5.h" +#include "engines/game.h" + +namespace Glk { +namespace Quest { + +void QuestMetaEngine::getSupportedGames(PlainGameList &games) { + for (const PlainGameDescriptor *pd = QUEST_GAME_LIST; pd->gameId; ++pd) + games.push_back(*pd); +} + +GameDescriptor QuestMetaEngine::findGame(const char *gameId) { + for (const PlainGameDescriptor *pd = QUEST_GAME_LIST; pd->gameId; ++pd) { + if (!strcmp(gameId, pd->gameId)) + return *pd; + } + + return GameDescriptor::empty(); +} + +bool QuestMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &gameList) { + // Loop through the files of the folder + for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) { + // Check for a recognised filename + if (file->isDirectory()) + continue; + + Common::String filename = file->getName(); + if (!filename.hasSuffixIgnoreCase(".quest")) + continue; + + Common::File gameFile; + if (!gameFile.open(*file)) + continue; + + gameFile.seek(0); + Common::String md5 = Common::computeStreamMD5AsString(gameFile, 5000); + uint32 filesize = gameFile.size(); + + // Scan through the Quest game list for a match + const GlkDetectionEntry *p = QUEST_GAMES; + while (p->_md5 && p->_filesize != filesize && md5 != p->_md5) + ++p; + + if (!p->_gameId) { + const PlainGameDescriptor &desc = QUEST_GAME_LIST[0]; + gameList.push_back(GlkDetectedGame(desc.gameId, desc.description, filename, md5, filesize)); + } else { + // Found a match + PlainGameDescriptor gameDesc = findGame(p->_gameId); + gameList.push_back(GlkDetectedGame(p->_gameId, gameDesc.description, filename)); + } + } + + return !gameList.empty(); +} + +void QuestMetaEngine::detectClashes(Common::StringMap &map) { + for (const PlainGameDescriptor *pd = QUEST_GAME_LIST; pd->gameId; ++pd) { + if (map.contains(pd->gameId)) + error("Duplicate game Id found - %s", pd->gameId); + map[pd->gameId] = ""; + } +} + +} // End of namespace Quest +} // End of namespace Glk diff --git a/engines/glk/quest/detection.h b/engines/glk/quest/detection.h new file mode 100644 index 0000000000..d94e40da95 --- /dev/null +++ b/engines/glk/quest/detection.h @@ -0,0 +1,60 @@ +/* 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_QUEST_DETECTION +#define GLK_QUEST_DETECTION + +#include "common/fs.h" +#include "common/hash-str.h" +#include "engines/game.h" +#include "glk/detection.h" + +namespace Glk { +namespace Quest { + +class QuestMetaEngine { +public: + /** + * Get a list of supported games + */ + static void getSupportedGames(PlainGameList &games); + + /** + * Returns a game description for the given game Id, if it's supported + */ + static GameDescriptor findGame(const char *gameId); + + /** + * Detect supported games + */ + static bool detectGames(const Common::FSList &fslist, DetectedGames &gameList); + + /** + * Check for game Id clashes with other sub-engines + */ + static void detectClashes(Common::StringMap &map); +}; + +} // End of namespace Quest +} // End of namespace Glk + +#endif diff --git a/engines/glk/quest/detection_tables.h b/engines/glk/quest/detection_tables.h new file mode 100644 index 0000000000..8e1fe11ba6 --- /dev/null +++ b/engines/glk/quest/detection_tables.h @@ -0,0 +1,81 @@ +/* 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 "engines/game.h" +#include "common/gui_options.h" +#include "common/language.h" + +namespace Glk { +namespace Quest { + +const PlainGameDescriptor QUEST_GAME_LIST[] = { + { "quest", "Quest Game" }, + + { "attackonfrightside", "Attack On Frightside" }, + { "balaclava", "Balaclava" }, + { "bearsepicquest", "Bear's Epic Quest" }, + { "caught", "Caught!" }, + { "cuttings", "Cuttings" }, + { "draculacrl", "Dracula: CRL remake" }, + { "elections4", "It's election time in Pakistan: Go rich boy, go!" }, + { "everyman", "Everyman" }, + { "firstTimes", "First Times" }, + { "giftofthemagi", "Gift of the Magi" }, + { "medievalistsquest", "Medievalist's Quest" }, + { "parishotel", "Welcome to the Paris Hotel" }, + { "questforloot", "Quest for loot and something else" }, + { "sleepingassassin", "El asesino durmiente (The Sleeping Assassin)" }, + { "spondre", "Spondre" }, + { "thelasthero", "The Last Hero" }, + { "tokindlealight", "To Kindle a Light" }, + { "xanadu", "Xanadu - The World's Only Hope" }, + + { nullptr, nullptr } +}; + +const GlkDetectionEntry QUEST_GAMES[] = { + DT_ENTRY0("attackonfrightside", "84542fc6460833bbf2594ed83f8b1fc7", 46019), + DT_ENTRY0("balaclava", "8b30af05d9986f9f962c677181ecc766", 57719), + DT_ENTRY0("bearsepicquest", "e6896a65527f456b4362aaebcf39e354", 62075), + DT_ENTRY0("caught", "4502d89d8e304fe4165d46eb22f21f10", 5168593), + DT_ENTRY0("cuttings", "e0ded5a6b78e8c9482e746d55f61972c", 6583866), + DT_ENTRY0("draculacrl", "1af3ec877584b290f7ab1a1be8f944a5", 4548737), + DT_ENTRY0("elections4", "d0bc0cd54182d6099808767068592b94", 591994), + DT_ENTRY0("everyman", "410c7211d3f0c700f34e97ed258e33f1", 56218), + DT_ENTRY0("firstTimes", "31d878c82d99856d473762612f154eb6", 10253826), + DT_ENTRY0("giftofthemagi", "b33132ce71c8a2eed0f6c1c1af284765", 78647), + DT_ENTRY0("medievalistsquest", "e0a15bc2a74a0bd6bb5c24661ea35829", 127977271), + DT_ENTRY0("parishotel", "c9a42bc3f306aba5e318b0a74115e0d4", 474983), + DT_ENTRY0("questforloot", "f7e32aec0f961a59a69bead3fadff4f0", 1357373), + DT_ENTRY0("sleepingassassin", "9c2aa213bb73d8083506ee6f64436d9d", 287227), + DT_ENTRY0("spondre", "c639077eb487eb6d1b63cda2c9ba5a9b", 1169469), + DT_ENTRY0("thelasthero", "31e10b8a7f11a6289955b89437f8178c", 62512), + DT_ENTRY0("tokindlealight", "5d3b57830b003046a621620ba0869d7c", 811845), + + + DT_ENTRY0("xanadu", "fef25e3473755ec572d4236d56f918e2", 396973), + + DT_END_MARKER +}; + +} // End of namespace Quest +} // End of namespace Glk diff --git a/engines/glk/quest/geas_file.cpp b/engines/glk/quest/geas_file.cpp new file mode 100644 index 0000000000..05c78e3e08 --- /dev/null +++ b/engines/glk/quest/geas_file.cpp @@ -0,0 +1,659 @@ +/* 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/quest/geas_file.h" +#include "glk/quest/reserved_words.h" +#include "glk/quest/read_file.h" +#include "glk/quest/geas_util.h" +#include "glk/quest/geas_impl.h" +#include "glk/quest/streams.h" +#include "glk/quest/string.h" + +namespace Glk { +namespace Quest { + +void report_error(const String &s); + +reserved_words obj_tag_property("look", "examine", "speak", "take", "alias", "prefix", "suffix", "detail", "displaytype", "gender", "article", "hidden", "invisible", (char *) NULL); + +reserved_words room_tag_property("look", "alias", "prefix", "indescription", "description", "north", "south", "east", "west", "northwest", "northeast", "southeast", "southwest", "up", "down", "out", (char *) NULL); + +void GeasFile::debug_print(String s) const { + if (gi == NULL) + cerr << s << endl; + else + gi->debug_print(s); +} + +const GeasBlock *GeasFile::find_by_name(String type, String name) const { + //name = lcase (name); + for (uint i = 0; i < size(type); i ++) { + //cerr << "find_by_name (" << type << ", " << name << "), vs. '" + // << block(type, i).name << "'\n"; + //if (block(type, i).lname == name) + if (ci_equal(block(type, i).name, name)) + return &block(type, i); + } + return NULL; +} + +const GeasBlock &GeasFile::block(String type, uint index) const { + StringArrayIntMap::const_iterator iter; + iter = type_indecies.find(type); + if (!(iter != type_indecies.end() && index < (*iter)._value.size())) + cerr << "Unable to find type " << type << "\n"; + + assert(iter != type_indecies.end() && index < (*iter)._value.size()); + //assert (index >= 0 && index < size(type)); + return blocks[(*iter)._value[index]]; +} + +uint GeasFile::size(String type) const { + //cerr << "GeasFile::size (" << type << ")" << endl; + + // SENSITIVE? + //std::map<String, Common::Array<int>, CI_LESS>::const_iterator iter; + StringArrayIntMap::const_iterator iter; + //cerr << type_indecies << endl; + iter = type_indecies.find(type); + if (iter == type_indecies.end()) { + //cerr << " returning 0" << endl; + return 0; + } + //cerr << " returning " << (*iter)._value.size() << endl; + return (*iter)._value.size(); +} + + +bool GeasFile::obj_has_property(String objname, String propname) const { + String tmp; + return get_obj_property(objname, propname, tmp); +} + +Common::WriteStream &operator<< (Common::WriteStream &, const Set<String> &); + +/** + * Currently only works for actual objects, not rooms or the game + */ +//Set<String, CI_LESS> GeasFile::get_obj_keys (String obj) const +Set<String> GeasFile::get_obj_keys(String obj) const { + //Set<String, CI_LESS> rv; + Set<String> rv; + get_obj_keys(obj, rv); + return rv; +} + +void GeasFile::get_obj_keys(String obj, Set<String> &rv) const { + cerr << "get_obj_keys (gf, <" << obj << ">)\n"; + //Set<String> rv; + + uint c1, c2; + String tok, line; + reserved_words *rw = NULL; + + const GeasBlock *gb = find_by_name("object", obj); + rw = &obj_tag_property; + + if (gb == NULL) { + cerr << "No such object found, aborting\n"; + //return rv; + return; + } + + for (uint i = 0; i < gb->data.size(); i ++) { + line = gb->data[i]; + cerr << " handling line <" << line << ">\n"; + tok = first_token(line, c1, c2); + // SENSITIVE? + if (tok == "properties") { + tok = next_token(line, c1, c2); + if (is_param(tok)) { + vstring params = split_param(param_contents(tok)); + for (uint j = 0; j < params.size(); j ++) { + cerr << " handling parameter <" << params[j] << ">\n"; + uint k = params[j].find('='); + // SENSITIVE? + if (starts_with(params[j], "not ")) { + rv.insert(trim(params[j].substr(4))); + cerr << " adding <" << trim(params[j].substr(4)) + << ">\n"; + } else if (k == -1) { + rv.insert(params[j]); + cerr << " adding <" << params[j] << ">\n"; + } else { + rv.insert(trim(params[j].substr(0, k))); + cerr << " adding <" << trim(params[j].substr(0, k)) + << ">\n"; + } + } + } + } + // SENSITIVE? + else if (tok == "type") { + tok = next_token(line, c1, c2); + if (is_param(tok)) + get_type_keys(param_contents(tok), rv); + } + //else if (has (tag_property, tok) && tag_property[tok]) + else if (rw != NULL && rw->has(tok)) { + String tok1 = next_token(line, c1, c2); + if (is_param(tok1)) + rv.insert(tok); + } + } + + cerr << "Returning (" << rv << ")\n"; +} + +void GeasFile::get_type_keys(String typen, Set<String> &rv) const { + cerr << "get_type_keys (" << typen << ", " << rv << ")\n"; + const GeasBlock *gb = find_by_name("type", typen); + if (gb == NULL) { + cerr << " g_t_k: Nonexistent type\n"; + return; + } + String line, tok; + uint c1, c2; + for (uint i = 0; i < gb->data.size(); i ++) { + line = gb->data[i]; + //cerr << " g_t_k: Handling line '" << line << "'\n"; + tok = first_token(line, c1, c2); + // SENSISTIVE? + if (tok == "type") { + tok = next_token(line, c1, c2); + if (is_param(tok)) { + get_type_keys(param_contents(tok), rv); + cerr << " g_t_k: Adding <" << tok << "> to rv: " << rv << "\n"; + } + } + // SENSITIVE? + else if (tok == "action") { + cerr << " action, skipping\n"; + } else { + uint ch = line.find('='); + if (ch != -1) { + rv.insert(trim(line.substr(0, ch))); + cerr << " adding <" << trim(line.substr(0, ch)) << ">\n"; + } + } + } + cerr << "Returning (" << rv << ")\n"; +} + +bool GeasFile::get_obj_property(String objname, String propname, String &string_rv) const { + cerr << "g_o_p: Getting prop <" << propname << "> of obj <" << objname << ">\n"; + string_rv = "!"; + bool bool_rv = false; + + //cerr << "obj_types == " << obj_types << endl; + /* + cerr << "obj_types == \n"; + for (map<String, String>::const_iterator iter = obj_types.begin(); + iter != obj_types.end(); iter ++) + cerr << " " << (*iter)._key << " -> " << (*iter)._value << "\n"; + cerr << ".\n"; + */ + + /* + String objtype; + + if (objname == "game") + objtype = "game"; + else if (!has (obj_types, objname)) + { + debug_print ("Checking property of nonexistent object " + objname); + return false; + } + else + objtype = (*obj_types.find(objname))._value; + */ + + if (!has(obj_types, objname)) { + debug_print("Checking nonexistent object <" + objname + "> for property <" + propname + ">"); + return false; + } + String objtype = (*obj_types.find(objname))._value; + + const GeasBlock *block = find_by_name(objtype, objname); + + String not_prop = "not " + propname; + uint c1, c2; + assert(block != NULL); + //assert (block->data != NULL); + for (uint i = 0; i < block->data.size(); i ++) { + String line = block->data[i]; + //cerr << " g_o_p: Handling line <" << line << ">\n"; + String tok = first_token(line, c1, c2); + // SENSITIVE? + if (tok == "type") { + tok = next_token(line, c1, c2); + if (is_param(tok)) + get_type_property(param_contents(tok), propname, bool_rv, string_rv); + else { + debug_print("Expected parameter for type in " + line); + } + } + // SENSITIVE? + else if (tok == "properties") { + tok = next_token(line, c1, c2); + if (!is_param(tok)) { + debug_print("Expected param on line " + line); + continue; + } + Common::Array<String> props = split_param(param_contents(tok)); + for (uint j = 0; j < props.size(); j ++) { + //cerr << " g_o_p: Comparing against <" << props[j] << ">\n"; + uint index; + if (props[j] == propname) { + //cerr << " g_o_p: Present but empty, blanking\n"; + string_rv = ""; + bool_rv = true; + } else if (props[j] == not_prop) { + //cerr << " g_o_p: Negation, removing\n"; + string_rv = "!"; + bool_rv = false; + } else if ((index = props[j].find('=')) != -1 && + (trim(props[j].substr(0, index)) == propname)) { + string_rv = props[j].substr(index + 1); + bool_rv = true; + //cerr << " g_o_p: Normal prop, now to <" << string_rv << ">\n"; + } + } + } + } + cerr << "g_o_p: Ultimately returning " << (bool_rv ? "true" : "false") + << ", with String <" << string_rv << ">\n\n"; + return bool_rv; +} + +void GeasFile::get_type_property(String typenamex, String propname, bool &bool_rv, String &string_rv) const { + //cerr << " Checking type <" << typenamex << "> for prop <" << propname << ">\n"; + const GeasBlock *block = find_by_name("type", typenamex); + if (block == NULL) { + debug_print("Object of nonexistent type " + typenamex); + return; + } + for (uint i = 0; i < block->data.size(); i ++) { + String line = block->data[i]; + //cerr << " Comparing vs. line <" << line << ">\n"; + uint c1, c2; + String tok = first_token(line, c1, c2); + + // SENSITIVE? + if (tok == "type") { + tok = next_token(line, c1, c2); + if (is_param(tok)) + get_type_property(param_contents(tok), propname, bool_rv, string_rv); + } else if (line == propname) { + bool_rv = true; + string_rv = ""; + } else { + c1 = line.find('='); + if (c1 != -1) { + tok = trim(line.substr(0, c1)); + if (tok == propname) { + string_rv = trim(line.substr(c1 + 1)); + bool_rv = true; + } + } + } + /* + if (tok == propname) + { + cerr << " match..."; + tok = next_token (line, c1, c2); + if (tok == "") + { + bool_rv = true; + string_rv = ""; + //cerr << " present but empty\n"; + } + else if (tok == "=") + { + bool_rv = true; + string_rv = trim (line.substr (c2)); + //cerr << " now <" << string_rv << ">\n"; + } + else + { + cerr << "Bad line while checking " << typenamex << " for prop " + << propname << ": " << line << endl; + } + } + else if (tok == "type") + { + tok = next_token (line, c1, c2); + if (is_param (tok)) + get_type_property (param_contents(tok), propname, bool_rv, string_rv); + } + */ + } +} + + + +bool GeasFile::obj_of_type(String objname, String typenamex) const { + if (!has(obj_types, objname)) { + debug_print("Checking nonexistent obj <" + objname + "> for type <" + + typenamex + ">"); + return false; + } + String objtype = (*obj_types.find(objname))._value; + + const GeasBlock *block = find_by_name(objtype, objname); + + uint c1, c2; + assert(block != NULL); + for (uint i = 0; i < block->data.size(); i ++) { + String line = block->data[i]; + String tok = first_token(line, c1, c2); + // SENSITIVE? + if (tok == "type") { + tok = next_token(line, c1, c2); + if (is_param(tok)) { + if (type_of_type(param_contents(tok), typenamex)) + return true; + } else { + debug_print("Eg_o_p: xpected parameter for type in " + line); + } + } + } + return false; +} + + +bool GeasFile::type_of_type(String subtype, String supertype) const { + if (ci_equal(subtype, supertype)) + return true; + //cerr << " Checking type <" << subtype << "> for type <" << supertype << ">\n"; + const GeasBlock *block = find_by_name("type", subtype); + if (block == NULL) { + debug_print("t_o_t: Nonexistent type " + subtype); + return false; + } + for (uint i = 0; i < block->data.size(); i ++) { + String line = block->data[i]; + //cerr << " Comparing vs. line <" << line << ">\n"; + uint c1, c2; + String tok = first_token(line, c1, c2); + // SENSITIVE? + if (tok == "type") { + tok = next_token(line, c1, c2); + if (is_param(tok) && type_of_type(param_contents(tok), supertype)) + return true; + } + } + return false; +} + + + +bool GeasFile::get_obj_action(String objname, String propname, String &string_rv) const { + cerr << "g_o_a: Getting action <" << propname << "> of object <" << objname << ">\n"; + string_rv = "!"; + bool bool_rv = false; + + //cerr << "obj_types == " << obj_types << endl; + /* + cerr << "obj_types == \n"; + for (map<String, String>::const_iterator iter = obj_types.begin(); + iter != obj_types.end(); iter ++) + cerr << " " << (*iter)._key << " -> " << (*iter)._value << "\n"; + cerr << ".\n"; + */ + if (!has(obj_types, objname)) { + debug_print("Checking nonexistent object <" + objname + "> for action <" + propname + ">."); + return false; + } + String objtype = (*obj_types.find(objname))._value; + + //reserved_words *rw; + + const GeasBlock *block = find_by_name(objtype, objname); + String not_prop = "not " + propname; + uint c1, c2; + for (uint i = 0; i < block->data.size(); i ++) { + String line = block->data[i]; + //cerr << " g_o_a: Handling line <" << line << ">\n"; + String tok = first_token(line, c1, c2); + // SENSITIVE? + if (tok == "type") { + tok = next_token(line, c1, c2); + if (is_param(tok)) + get_type_action(param_contents(tok), propname, bool_rv, string_rv); + else { + gi->debug_print("Expected parameter for type in " + line); + } + } + /* + else if (rw != NULL && tok == propname && rw->has(propname)) + { + tok = next_token (line, c1, c2); + if (is_param(tok)) + { + cerr << " Parameter, skipping\n"; + } + else + { + //cerr << " Action, skipping\n"; + cerr << " Action, string_rv is now <" << string_rv << ">\n"; + string_rv = line.substr (c1); + bool_rv = true; + } + } + */ + // SENSITIVE? + else if (tok == "action") { + tok = next_token(line, c1, c2); + if (is_param(tok) && param_contents(tok) == propname) { + if (c2 + 1 < line.length()) + string_rv = line.substr(c2 + 1); + else + string_rv = ""; + bool_rv = true; + cerr << " Action line, string_rv now <" << string_rv << ">\n"; + } + } + } + + cerr << "g_o_a: Ultimately returning value " << (bool_rv ? "true" : "false") << ", with String <" << string_rv << ">\n\n"; + + return bool_rv; +} + +void GeasFile::get_type_action(String typenamex, String actname, bool &bool_rv, String &string_rv) const { + //cerr << " Checking type <" << typenamex << "> for action <" << actname << ">\n"; + const GeasBlock *block = find_by_name("type", typenamex); + if (block == NULL) { + debug_print("Object of nonexistent type " + typenamex); + return; + } + for (uint i = 0; i < block->data.size(); i ++) { + String line = block->data[i]; + //cerr << " g_t_a: Comparing vs. line <" << line << ">\n"; + uint c1, c2; + String tok = first_token(line, c1, c2); + // SENSITIVE? + if (tok == "action") { + //cerr << " match...\n"; + tok = next_token(line, c1, c2); + if (is_param(tok) && param_contents(tok) == actname) { + bool_rv = true; + string_rv = line.substr(c2); + //cerr << " present: {" + string_rv + "}\n"; + } + } + // SENSITIVE? + else if (tok == "type") { + tok = next_token(line, c1, c2); + if (is_param(tok)) + get_type_action(param_contents(tok), actname, bool_rv, string_rv); + } + } +} + +void GeasFile::register_block(String blockname, String blocktype) { + cerr << "registering block " << blockname << " / " << blocktype << endl; + if (has(obj_types, blockname)) { + String errdesc = "Trying to register block of named <" + blockname + + "> of type <" + blocktype + "> when there is already one, of type <" + + obj_types[blockname] + ">"; + debug_print(errdesc); + throw errdesc; + } + obj_types[blockname] = blocktype; +} + +String GeasFile::static_svar_lookup(String varname) const { + cerr << "static_svar_lookup(" << varname << ")" << endl; + //varname = lcase (varname); + for (uint i = 0; i < size("variable"); i ++) + //if (blocks[i].lname == varname) + if (ci_equal(blocks[i].name, varname)) { + String rv; + String tok; + uint c1, c2; + bool found_typeline = false; + for (uint j = 0; j < blocks[i].data.size(); j ++) { + String line = blocks[i].data[j]; + tok = first_token(line, c1, c2); + // SENSITIVE? + if (tok == "type") { + tok = next_token(line, c1, c2); + // SENSITIVE? + if (tok == "numeric") + throw String("Trying to evaluate int var '" + varname + + "' as String"); + // SENSITIVE? + if (tok != "String") + throw String("Bad variable type " + tok); + found_typeline = true; + } + // SENSITIVE? + else if (tok == "value") { + tok = next_token(line, c1, c2); + if (!is_param(tok)) + throw String("Expected param after value in " + line); + rv = param_contents(tok); + } + } + if (!found_typeline) + throw String(varname + " is a numeric variable"); + cerr << "static_svar_lookup(" << varname << ") -> \"" << rv << "\"" << endl; + return rv; + } + debug_print("Variable <" + varname + "> not found."); + return ""; +} + +String GeasFile::static_ivar_lookup(String varname) const { + //varname = lcase (varname); + for (uint i = 0; i < size("variable"); i ++) + //if (blocks[i].lname == varname) + if (ci_equal(blocks[i].name, varname)) { + String rv; + String tok; + uint c1, c2; + for (uint j = 0; j < blocks[i].data.size(); j ++) { + String line = blocks[i].data[j]; + tok = first_token(line, c1, c2); + // SENSITIVE? + if (tok == "type") { + tok = next_token(line, c1, c2); + // SENSITIVE? + if (tok == "String") + throw String("Trying to evaluate String var '" + varname + + "' as numeric"); + // SENSITIVE? + if (tok != "numeric") + throw String("Bad variable type " + tok); + } + // SENSITIVE? + else if (tok == "value") { + tok = next_token(line, c1, c2); + if (!is_param(tok)) + throw String("Expected param after value in " + line); + rv = param_contents(tok); + } + } + return rv; + } + debug_print("Variable <" + varname + "> not found"); + return "-32768"; +} + +String GeasFile::static_eval(String input) const { + //cerr << "static_eval (" << input << ")" << endl; + String rv = ""; + for (uint i = 0; i < input.length(); i ++) { + if (input[i] == '#') { + uint j; + for (j = i + 1; j < input.length() && input[j] != '#'; j ++) + ; + if (j == input.length()) + throw String("Error processing '" + input + "', odd hashes"); + uint k; + for (k = i + 1; k < j && input[k] != ':'; k ++) + ; + if (k == ':') { + String objname; + if (input[i + 1] == '(' && input[k - 1] == ')') + objname = static_svar_lookup(input.substr(i + 2, k - i - 4)); + else + objname = input.substr(i + 1, k - i - 2); + cerr << " objname == '" << objname << endl; + //rv += get_obj_property (objname, input.substr (k+1, j-k-2)); + String tmp; + bool had_var; + + String objprop = input.substr(k + 1, j - k - 2); + cerr << " objprop == " << objprop << endl; + had_var = get_obj_property(objname, objprop, tmp); + rv += tmp; + if (!had_var) + debug_print("Requesting nonexistent property <" + objprop + + "> of object <" + objname + ">"); + } else { + cerr << "i == " << i << ", j == " << j << ", length is " << input.length() << endl; + cerr << "Looking up static var " << input.substr(i + 1, j - i - 1) << endl; + rv += static_svar_lookup(input.substr(i + 1, j - i - 1)); + } + i = j; + } else if (input[i] == '%') { + uint j; + for (j = i; j < input.length() && input[j] != '%'; j ++) + ; + if (j == input.length()) + throw String("Error processing '" + input + "', unmatched %"); + rv += static_ivar_lookup(input.substr(i + 1, j - i - 2)); + i = j; + } else + rv += input[i]; + } + if (rv != input) + cerr << "*** CHANGED ***\n"; + //cerr << "static_eval (" << input << ") --> \"" << rv << "\"" << endl; + return rv; +} + +} // End of namespace Quest +} // End of namespace Glk diff --git a/engines/glk/quest/geas_file.h b/engines/glk/quest/geas_file.h new file mode 100644 index 0000000000..e9558cadd8 --- /dev/null +++ b/engines/glk/quest/geas_file.h @@ -0,0 +1,123 @@ +/* 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_QUEST_GEAS_FILE +#define GLK_QUEST_GEAS_FILE + +#include "glk/quest/string.h" +#include "common/algorithm.h" +#include "common/hashmap.h" +#include "common/hash-str.h" +#include "common/stream.h" + +namespace Glk { +namespace Quest { + +class GeasInterface; + +class reserved_words; + +/** + * Ordered array of items + */ +template<class T> +class Set : public Common::Array<T> { +public: + /** + * Insert a new item + */ + void insert(T val) { + this->push_back(val); + Common::sort(this->begin(), this->end()); + } +}; + +struct GeasBlock { + ////// lname == lowercase name + ////// nname == normal name + ////// parent == initial parent object (lowercased) + // name == name + // parent == initial parent object + String blocktype, name, parent; + Common::Array<String> data; + //GeasBlock (const Common::Array<String> &, String, uint, bool); + GeasBlock() {} +}; + +struct GeasFile { + GeasInterface *gi; + void debug_print(String s) const; + + //vector<GeasBlock> rooms, objects, textblocks, functions, procedures, types; + //GeasBlock synonyms, game; + Common::Array <GeasBlock> blocks; + + //Common::Array<GeasBlock> rooms, objects, textblocks, functions, procedures, + // types, synonyms, game, variables, timers, choices; + StringMap obj_types; + StringArrayIntMap type_indecies; + + void register_block(String blockname, String blocktype); + + const GeasBlock &block(String type, uint index) const; + uint size(String type) const; + + void read_into(const Common::Array<String> &, String, uint, bool, const reserved_words &, const reserved_words &); + + + + GeasFile() : gi(nullptr) {} + explicit GeasFile(const Common::Array<String> &in_data, + GeasInterface *gi); + + bool obj_has_property(String objname, String propname) const; + bool get_obj_property(String objname, String propname, + String &rv) const; + + void get_type_property(String typenamex, String propname, + bool &, String &) const; + bool obj_of_type(String object, String type) const; + bool type_of_type(String subtype, String supertype) const; + + Set<String> get_obj_keys(String obj) const; + void get_obj_keys(String, Set<String> &) const; + void get_type_keys(String, Set<String> &) const; + + bool obj_has_action(String objname, String propname) const; + bool get_obj_action(String objname, String propname, + String &rv) const; + void get_type_action(String typenamex, String propname, + bool &, String &) const; + String static_eval(String) const; + String static_ivar_lookup(String varname) const; + String static_svar_lookup(String varname) const; + + const GeasBlock *find_by_name(String type, String name) const; +}; + +Common::WriteStream &operator<<(Common::WriteStream &, const GeasBlock &); +Common::WriteStream &operator<<(Common::WriteStream &, const GeasFile &); + +} // End of namespace Quest +} // End of namespace Glk + +#endif diff --git a/engines/glk/quest/geas_glk.cpp b/engines/glk/quest/geas_glk.cpp new file mode 100644 index 0000000000..8f48799d9a --- /dev/null +++ b/engines/glk/quest/geas_glk.cpp @@ -0,0 +1,240 @@ +/* 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/quest/geas_glk.h" +#include "glk/quest/geas_runner.h" +#include "glk/quest/quest.h" +#include "glk/quest/streams.h" +#include "glk/windows.h" + +namespace Glk { +namespace Quest { + +void glk_put_cstring(const char *); + + +winid_t mainglkwin; +winid_t inputwin; +winid_t bannerwin; +strid_t inputwinstream; + +const bool use_inputwindow = false; + +int ignore_lines; // count of lines to ignore in game output + +void draw_banner() { + uint32 width; + uint32 index; + if (bannerwin) { + g_vm->glk_window_clear(bannerwin); + g_vm->glk_window_move_cursor(bannerwin, 0, 0); + strid_t stream = g_vm->glk_window_get_stream(bannerwin); + + g_vm->glk_set_style_stream(stream, style_User1); + g_vm->glk_window_get_size(bannerwin, &width, NULL); + for (index = 0; index < width; index++) + g_vm->glk_put_char_stream(stream, ' '); + g_vm->glk_window_move_cursor(bannerwin, 1, 0); + + if (g_vm->banner.empty()) + g_vm->glk_put_string_stream(stream, (char *)"Geas 0.4"); + else + g_vm->glk_put_string_stream(stream, (char *)g_vm->banner.c_str()); + } +} + +void glk_put_cstring(const char *s) { + /* The cast to remove const is necessary because g_vm->glk_put_string + * receives a "char *" despite the fact that it could equally well use + * "const char *". */ + g_vm->glk_put_string((char *)s); +} + +GeasResult GeasGlkInterface::print_normal(const String &s) { + if (!ignore_lines) + glk_put_cstring(s.c_str()); + return r_success; +} + +GeasResult GeasGlkInterface::print_newline() { + if (!ignore_lines) + glk_put_cstring("\n"); + else + ignore_lines--; + return r_success; +} + +GeasResult GeasGlkInterface::set_style(const GeasFontStyle &style) { + // Glk styles are defined before the window opens, so at this point we can only + // pick the most suitable style, not define a new one. + uint match; + if (style.is_italic && style.is_bold) + match = style_Alert; + else if (style.is_italic) + match = style_Emphasized; + else if (style.is_bold) + match = style_Subheader; + else if (style.is_underlined) + match = style_User2; + else + match = style_Normal; + + g_vm->glk_set_style_stream(g_vm->glk_window_get_stream(mainglkwin), match); + return r_success; +} + +void GeasGlkInterface::set_foreground(String s) { + if (s != "") { + } +} + +void GeasGlkInterface::set_background(String s) { + if (s != "") { + } +} + +/* Code lifted from GeasWindow. Should be common. Maybe in + * GeasInterface? + */ +String GeasGlkInterface::get_file(const String &fname) const { + Common::File ifs; + if (ifs.open(fname)) { + glk_put_cstring("Couldn't open "); + glk_put_cstring(fname.c_str()); + g_vm->glk_put_char(0x0a); + return ""; + } + + // Read entirety of the file + char *buf = new char[ifs.size()]; + ifs.read(buf, ifs.size()); + + String result(buf, buf + ifs.size()); + delete[] buf; + + return result; +} + +String GeasGlkInterface::get_string() { + char buf[200]; + g_vm->glk_request_line_event(inputwin, buf, (sizeof buf) - 1, 0); + while (1) { + event_t ev; + + g_vm->glk_select(&ev); + + if (ev.type == evtype_LineInput && ev.window == inputwin) { + return String(buf, ev.val1); + } + /* All other events, including timer, are deliberately + * ignored. + */ + } +} + +uint GeasGlkInterface::make_choice(String label, Common::Array<String> v) { + size_t n; + + g_vm->glk_window_clear(inputwin); + + glk_put_cstring(label.c_str()); + g_vm->glk_put_char(0x0a); + n = v.size(); + for (size_t i = 0; i < n; ++i) { + StringStream t; + String s; + t << i + 1; + t >> s; + glk_put_cstring(s.c_str()); + glk_put_cstring(": "); + glk_put_cstring(v[i].c_str()); + glk_put_cstring("\n"); + } + + StringStream t; + String s; + String s1; + t << n; + t >> s; + s1 = "Choose [1-" + s + "]> "; + g_vm->glk_put_string_stream(inputwinstream, (char *)(s1.c_str())); + + int choice = atoi(get_string().c_str()); + if (choice < 1) { + choice = 1; + } + if ((size_t)choice > n) { + choice = n; + } + + StringStream u; + u << choice; + u >> s; + s1 = "Chosen: " + s + "\n"; + glk_put_cstring(s1.c_str()); + + return choice - 1; +} + +String GeasGlkInterface::absolute_name(String rel_name, String parent) const { + cerr << "absolute_name ('" << rel_name << "', '" << parent << "')\n"; + if (parent[0] != '/') + return rel_name; + + if (rel_name[0] == '/') { + cerr << " --> " << rel_name << "\n"; + return rel_name; + } + Common::Array<String> path; + uint dir_start = 1, dir_end; + while (dir_start < parent.length()) { + dir_end = dir_start; + while (dir_end < parent.length() && parent[dir_end] != '/') + dir_end ++; + path.push_back(parent.substr(dir_start, dir_end - dir_start)); + dir_start = dir_end + 1; + } + path.pop_back(); + dir_start = 0; + String tmp; + while (dir_start < rel_name.length()) { + dir_end = dir_start; + while (dir_end < rel_name.length() && rel_name[dir_end] != '/') + dir_end ++; + tmp = rel_name.substr(dir_start, dir_end - dir_start); + dir_start = dir_end + 1; + if (tmp == ".") + continue; + else if (tmp == "..") + path.pop_back(); + else + path.push_back(tmp); + } + String rv; + for (uint i = 0; i < path.size(); i ++) + rv = rv + "/" + path[i]; + cerr << " ---> " << rv << "\n"; + return rv; +} + +} // End of namespace Quest +} // End of namespace Glk diff --git a/engines/glk/quest/geas_glk.h b/engines/glk/quest/geas_glk.h new file mode 100644 index 0000000000..2647bf13b5 --- /dev/null +++ b/engines/glk/quest/geas_glk.h @@ -0,0 +1,92 @@ +/* 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_QUEST_GEAS_GLK +#define GLK_QUEST_GEAS_GLK + +#include "glk/quest/geas_runner.h" +#include "glk/windows.h" + +namespace Glk { +namespace Quest { + + +/* User interface bridge from Geas Core to Glk. + + Glk Window arrangment. + + +---------+ + | B | + +---------+ + | M | + | | + +---------+ + | I | + +---------+ + + B is a one line "banner window", showing the game name and author. Kept + in the global variable, it's optional, null if unavailable. + optional. + M is the main window where the text of the game appears. Kept in the + global variable mainglkwin. + I is a one line "input window" where the user inputs their commands. + Kept in the global variable inputwin, it's optional, and if not separate + is set to mainglkwin. + + Maybe in future revisions there will be a status window (including a + compass rose). +*/ + +class GeasGlkInterface : public GeasInterface { +protected: + virtual String get_file(const String &fname) const; + virtual GeasResult print_normal(const String &s); + virtual GeasResult print_newline(); + + virtual void set_foreground(String); + virtual void set_background(String); + virtual GeasResult set_style(const GeasFontStyle &); + + virtual String get_string(); + virtual uint make_choice(String, Common::Array<String>); + + virtual String absolute_name(String, String) const; +public: + GeasGlkInterface() { + ; + } +}; + +extern winid_t mainglkwin; +extern winid_t inputwin; +extern winid_t bannerwin; +extern strid_t inputwinstream; +extern int ignore_lines; +extern const bool use_inputwindow; + +extern void glk_put_cstring(const char *); +extern void draw_banner(); + +} // End of namespace Quest +} // End of namespace Glk + +#endif diff --git a/engines/glk/quest/geas_impl.h b/engines/glk/quest/geas_impl.h new file mode 100644 index 0000000000..1bdbbfdd9a --- /dev/null +++ b/engines/glk/quest/geas_impl.h @@ -0,0 +1,214 @@ +/* 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_QUEST_GEAS_IMPL +#define GLK_QUEST_GEAS_IMPL + +#include "glk/quest/geas_runner.h" +#include "glk/quest/geas_state.h" +#include "glk/quest/limit_stack.h" + +namespace Glk { +namespace Quest { + +struct match_binding { + String var_name; + String var_text; + uint start, end; + //operator String(); + String tostring(); + match_binding(String vn, uint i) : var_name(vn), start(i) {} + void set(String vt, uint i) { + var_text = vt; + end = i; + } +}; + +Common::WriteStream &operator<< (Common::WriteStream &, const match_binding &); + + +struct match_rv { + bool success; + Common::Array<match_binding> bindings; + //match_rv (bool b, const Common::Array<String> &v) : success(b), bindings(v) {} + match_rv() : success(false) {} + match_rv(bool b, const match_rv &rv) : success(b), bindings(rv.bindings) {} + operator bool () { + return success; + } +}; + +Common::WriteStream &operator<< (Common::WriteStream &o, const match_rv &rv); +/* + inline ostream &operator<< (ostream &o, const match_rv &rv) +{ + //o << "match_rv {" << (rv.success ? "TRUE" : "FALSE") << ": " << rv.bindings << "}"; + o << "match_rv {" << (rv.success ? "TRUE" : "FALSE") << ": ["; + //o << rv.bindings.size(); + //o << rv.bindings; + for (uint i = 0; i < rv.bindings.size(); i ++) + o << rv.bindings[i] << ", "; + o << "]}"; + return o; +} +*/ + +class geas_implementation : public GeasRunner { + //GeasInterface *gi; + GeasFile gf; + //bool running; + bool dont_process, outputting; + LimitStack <GeasState> undo_buffer; + Common::Array <String> function_args; + String this_object; + v2string current_places; + bool is_running_; + Logger logger; + +public: + geas_implementation(GeasInterface *in_gi) + : GeasRunner(in_gi), undo_buffer(20), is_running_(true) {} + void set_game(const String &fname); + + bool is_running() const; + String get_banner(); + void run_command(String); + bool try_match(String s, bool, bool); + match_rv match_command(String input, String action) const; + match_rv match_command(String input, uint ichar, + String action, uint achar, match_rv rv) const; + bool dereference_vars(Common::Array<match_binding> &bindings, bool is_internal) const; + bool dereference_vars(Common::Array<match_binding> &, const Common::Array<String> &, bool is_internal) const; + bool match_object(String text, String name, bool is_internal = false) const; + void set_vars(const Common::Array<match_binding> &v); + bool run_commands(String, const GeasBlock *, bool is_internal = false); + + void display_error(String errorname, String object = ""); + + String substitute_synonyms(String) const; + + void set_svar(String, String); + void set_svar(String, uint, String); + void set_ivar(String, int); + void set_ivar(String, uint, int); + + String get_svar(String) const; + String get_svar(String, uint) const; + int get_ivar(String) const; + int get_ivar(String, uint) const; + + bool find_ivar(String, uint &) const; + bool find_svar(String, uint &) const; + + void regen_var_look(); + void regen_var_dirs(); + void regen_var_objects(); + void regen_var_room(); + + void look(); + + String displayed_name(String object) const; + //String get_obj_name (const Common::Array<String> &args) const; + String get_obj_name(String name, const Common::Array<String> &where, bool is_internal) const; + + bool has_obj_property(String objname, String propname) const; + bool get_obj_property(String objname, String propname, + String &rv) const; + bool has_obj_action(String obj, String prop) const; + bool get_obj_action(String objname, String actname, + String &rv) const; + String exit_dest(String room, String dir, bool *is_act = NULL) const; + Common::Array<Common::Array<String> > get_places(String room); + + void set_obj_property(String obj, String prop); + void set_obj_action(String obj, String act); + void move(String obj, String dest); + void goto_room(String room); + String get_obj_parent(String obj); + + void print_eval(String); + void print_eval_p(String); + String eval_string(String s); + String eval_param(String s) { + assert(is_param(s)); + return eval_string(param_contents(s)); + } + + + void run_script_as(String, String); + void run_script(String); + void run_script(String, String &); + void run_procedure(String); + void run_procedure(String, Common::Array<String> args); + String run_function(String); + String run_function(String, Common::Array<String> args); + String bad_arg_count(String); + + bool eval_conds(String); + bool eval_cond(String); + GeasState state; + + virtual void tick_timers(); + virtual v2string get_inventory(); + virtual v2string get_room_contents(); + v2string get_room_contents(String); + virtual vstring get_status_vars(); + virtual Common::Array<bool> get_valid_exits(); + + + inline void print_formatted(String s) const { + if (outputting) gi->print_formatted(s); + } + inline void print_normal(String s) const { + if (outputting) gi->print_normal(s); + } + inline void print_newline() const { + if (outputting) gi->print_newline(); + } + + /* + inline void print_formatted (String s) const { + if (outputting) + gi->print_formatted(s); + else + gi->print_formatted ("{{" + s + "}}"); + } + inline void print_normal (String s) const + { + if (outputting) + gi->print_normal (s); + else + gi->print_normal("{{" + s + "}}"); + } + inline void print_newline() const { + if (outputting) + gi->print_newline(); + else + gi->print_normal ("{{|n}}"); + } + */ +}; + +} // End of namespace Quest +} // End of namespace Glk + +#endif diff --git a/engines/glk/quest/geas_runner.cpp b/engines/glk/quest/geas_runner.cpp new file mode 100644 index 0000000000..a029c07824 --- /dev/null +++ b/engines/glk/quest/geas_runner.cpp @@ -0,0 +1,3675 @@ +/* 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/quest/geas_runner.h" +#include "glk/quest/read_file.h" +#include "glk/quest/geas_state.h" +#include "glk/quest/geas_util.h" +#include "glk/quest/reserved_words.h" +#include "glk/quest/geas_impl.h" +#include "glk/quest/quest.h" +#include "glk/quest/streams.h" +#include "glk/quest/String.h" + +namespace Glk { +namespace Quest { + +class GeasInterface; + +static const char *dir_names[] = {"north", "south", "east", "west", "northeast", "northwest", "southeast", "southwest", "up", "down", "out"}; +static const char *short_dir_names[] = {"n", "s", "e", "w", "ne", "nw", "se", "sw", "u", "d", "out"}; + +const ObjectRecord *get_obj_record(const Common::Array<ObjectRecord> &v, const String &name) { + for (uint i = 0; i < v.size(); i ++) + if (ci_equal(v[i].name, name)) + return &v[i]; + return nullptr; +} + + + +GeasRunner *GeasRunner::get_runner(GeasInterface *gi) { + return new geas_implementation(gi); +} + +bool geas_implementation::find_ivar(String name, uint &rv) const { + for (uint n = 0; n < state.ivars.size(); n ++) + if (ci_equal(state.ivars[n].name, name)) { + rv = n; + return true; + } + return false; +} + +bool geas_implementation::find_svar(String name, uint &rv) const { + //name = lcase (name); + for (uint n = 0; n < state.svars.size(); n ++) + if (ci_equal(state.svars[n].name, name)) { + rv = n; + return true; + } + return false; +} + +void geas_implementation::set_svar(String varname, String varval) { + cerr << "set_svar (" << varname << ", " << varval << ")\n"; + int i1 = varname.find('['); + if (i1 == -1) + return set_svar(varname, 0, varval); + if (varname[varname.length() - 1] != ']') { + gi->debug_print("set_svar: Badly formatted name " + varname); + return; + } + String arrayname = varname.substr(0, i1); + String indextext = varname.substr(i1 + 1, varname.length() - i1 - 2); + cerr << "set_svar(" << varname << ") --> set_svar (" << arrayname << ", " << indextext << ")\n"; + for (uint c3 = 0; c3 < indextext.size(); c3 ++) + if (indextext[c3] < '0' || indextext[c3] > '9') { + set_svar(arrayname, get_ivar(indextext), varval); + return; + } + set_svar(arrayname, parse_int(indextext), varval); + return; +} + +void geas_implementation::set_svar(String varname, uint index, String varval) { + uint n, m; + if (!find_svar(varname, n)) { + if (find_ivar(varname, m)) { + gi->debug_print("Defining " + varname + " as String variable when there is already a numeric variable of that name."); + return; + } + SVarRecord svr; + svr.name = varname; + n = state.svars.size(); + state.svars.push_back(svr); + } + state.svars[n].set(index, varval); + if (index == 0) { + for (uint varn = 0; varn < gf.size("variable"); varn ++) { + const GeasBlock &go(gf.block("variable", varn)); + if (ci_equal(go.name, varname)) { + String script = ""; + uint c1, c2; + for (uint j = 0; j < go.data.size(); j ++) + // SENSITIVE ? + if (first_token(go.data[j], c1, c2) == "onchange") + script = trim(go.data[j].substr(c2 + 1)); + if (script != "") + run_script(script); + } + } + } +} + +String geas_implementation::get_svar(String varname) const { + int i1 = varname.find('['); + if (i1 == -1) + return get_svar(varname, 0); + if (varname[varname.length() - 1] != ']') { + gi->debug_print("get_svar: badly formatted name " + varname); + return ""; + } + String arrayname = varname.substr(0, i1); + String indextext = varname.substr(i1 + 1, varname.length() - i1 - 2); + cerr << "get_svar(" << varname << ") --> get_svar (" << arrayname << ", " << indextext << ")\n"; + for (uint c3 = 0; c3 < indextext.size(); c3 ++) + if (indextext[c3] < '0' || indextext[c3] > '9') + return get_svar(arrayname, get_ivar(indextext)); + return get_svar(arrayname, parse_int(indextext)); +} +String geas_implementation::get_svar(String varname, uint index) const { + for (uint i = 0; i < state.svars.size(); i ++) { + if (ci_equal(state.svars[i].name, varname)) + return state.svars[i].get(index); + } + + gi->debug_print("get_svar (" + varname + ", " + string_int(index) + "): No such variable defined."); + return ""; +} + +int geas_implementation::get_ivar(String varname) const { + int i1 = varname.find('['); + if (i1 == -1) + return get_ivar(varname, 0); + if (varname[varname.length() - 1] != ']') { + gi->debug_print("get_ivar: Badly formatted name " + varname); + return -32767; + } + String arrayname = varname.substr(0, i1); + String indextext = varname.substr(i1 + 1, varname.length() - i1 - 2); + cerr << "get_ivar(" << varname << ") --> get_ivar (" << arrayname << ", " << indextext << ")\n"; + for (uint c3 = 0; c3 < indextext.size(); c3 ++) + if (indextext[c3] < '0' || indextext[c3] > '9') + return get_ivar(arrayname, get_ivar(indextext)); + return get_ivar(arrayname, parse_int(indextext)); +} +int geas_implementation::get_ivar(String varname, uint index) const { + for (uint i = 0; i < state.ivars.size(); i ++) + if (ci_equal(state.ivars[i].name, varname)) + return state.ivars[i].get(index); + gi->debug_print("get_ivar: Tried to read undefined int '" + varname + + "' [" + string_int(index) + "]"); + return -32767; +} +void geas_implementation::set_ivar(String varname, int varval) { + int i1 = varname.find('['); + if (i1 == -1) + return set_ivar(varname, 0, varval); + if (varname[varname.length() - 1] != ']') { + gi->debug_print("set_ivar: Badly formatted name " + varname); + return; + } + String arrayname = varname.substr(0, i1); + String indextext = varname.substr(i1 + 1, varname.length() - i1 - 2); + cerr << "set_svar(" << varname << ") --> set_svar (" << arrayname << ", " << indextext << ")\n"; + for (uint c3 = 0; c3 < indextext.size(); c3 ++) + if (indextext[c3] < '0' || indextext[c3] > '9') { + set_ivar(arrayname, get_ivar(indextext), varval); + return; + } + set_ivar(arrayname, parse_int(indextext), varval); +} + +void geas_implementation::set_ivar(String varname, uint index, int varval) { + uint n, m; + if (!find_ivar(varname, n)) { + if (find_svar(varname, m)) { + gi->debug_print("Defining " + varname + " as numeric variable when there is already a String variable of that name."); + return; + } + IVarRecord ivr; + ivr.name = varname; + n = state.ivars.size(); + state.ivars.push_back(ivr); + } + state.ivars[n].set(index, varval); + if (index == 0) { + for (uint varn = 0; varn < gf.size("variable"); varn ++) { + const GeasBlock &go(gf.block("variable", varn)); + //if (go.lname == varname) + if (ci_equal(go.name, varname)) { + String script = ""; + uint c1, c2; + for (uint j = 0; j < go.data.size(); j ++) + // SENSITIVE? + if (first_token(go.data[j], c1, c2) == "onchange") + script = trim(go.data[j].substr(c2 + 1)); + if (script != "") + run_script(script); + } + } + } +} + + + +Common::WriteStream &operator<<(Common::WriteStream &o, const match_binding &mb) { + o << "MB['" << mb.var_name << "' == '" << mb.var_text << "' @ " + << mb.start << " to " << mb.end << "]"; + return o; +} + +String match_binding::tostring() { + ostringstream oss; + oss << *this; + return oss.str(); +} + +Common::WriteStream &operator<<(Common::WriteStream &o, const Set<String> &s) { + o << "{ "; + for (Set<String>::const_iterator i = s.begin(); i != s.end(); i ++) { + if (i != s.begin()) + o << ", "; + o << (*i); + } + o << " }"; + return o; +} + +bool geas_implementation::has_obj_action(String obj, String prop) const { + String tmp; + return get_obj_action(obj, prop, tmp); +} + + +bool geas_implementation::get_obj_action(String objname, String actname, + String &rv) const { + //String backup_object = this_object; + //this_object = objname; + + cerr << "get_obj_action (" << objname << ", " << actname << ")\n"; + String tok; + uint c1, c2; + for (uint i = state.props.size() - 1; i + 1 > 0; i --) + if (state.props[i].name == objname) { + String line = state.props[i].data; + // SENSITIVE? + if (first_token(line, c1, c2) != "action") + continue; + tok = next_token(line, c1, c2); + if (!is_param(tok) || ci_equal(param_contents(tok), actname)) + continue; + rv = trim(line.substr(c2)); + cerr << " g_o_a: returning true, \"" << rv << "\"."; + return true; + } + return gf.get_obj_action(objname, actname, rv); + //bool bool_rv = gf.get_obj_action (objname, actname, rv); + //this_object = backup_object; + //return bool_rv; +} + +bool geas_implementation::has_obj_property(String obj, String prop) const { + String tmp; + return get_obj_property(obj, prop, tmp); +} + +bool geas_implementation::get_obj_property(String obj, String prop, + String &string_rv) const { + String is_prop = "properties " + prop; + String not_prop = "properties not " + prop; + for (uint i = state.props.size() - 1; i + 1 > 0; i --) + if (ci_equal(state.props[i].name, obj)) { + String dat = state.props[i].data; + //cerr << "In looking for " << obj << ":" << prop << ", got line " + // << dat << endl; + if (ci_equal(dat, not_prop)) { + //cerr << " not_prop, returning false\n"; + string_rv = "!"; + return false; + } + if (ci_equal(dat, is_prop)) { + //cerr << " is_prop, returning true\n"; + string_rv = ""; + return true; + } + int index = dat.find('='); + if (index != -1 && ci_equal(dat.substr(0, index), is_prop)) { + string_rv = dat.substr(index + 1); + return true; + } + } + return gf.get_obj_property(obj, prop, string_rv); +} + +void geas_implementation::set_obj_property(String obj, String prop) { + state.props.push_back(PropertyRecord(obj, "properties " + prop)); + if (ci_equal(prop, "hidden") || ci_equal(prop, "not hidden") || + ci_equal(prop, "invisible") || ci_equal(prop, "not invisible")) { + gi->update_sidebars(); + regen_var_objects(); + } +} + +void geas_implementation::set_obj_action(String obj, String act) { + state.props.push_back(PropertyRecord(obj, "action " + act)); +} + +void geas_implementation::move(String obj, String dest) { + for (uint i = 0; i < state.objs.size(); i ++) + if (ci_equal(state.objs[i].name, obj)) { + state.objs[i].parent = dest; + gi->update_sidebars(); + regen_var_objects(); + return; + } + gi->debug_print("Tried to move nonexistent object '" + obj + + "' to '" + dest + "'."); +} + +String geas_implementation::get_obj_parent(String obj) { + //obj = lcase (obj); + for (uint i = 0; i < state.objs.size(); i ++) + if (ci_equal(state.objs[i].name, obj)) + return state.objs[i].parent; + gi->debug_print("Tried to find parent of nonexistent object " + obj); + return ""; +} + +void geas_implementation::goto_room(String room) { + state.location = room; + regen_var_room(); + regen_var_dirs(); + regen_var_look(); + regen_var_objects(); + String scr; + if (get_obj_action(room, "script", scr)) + run_script_as(room, scr); + //run_script (scr); + look(); +} + +void geas_implementation::display_error(String errorname, String obj) { + cerr << "display_error (" << errorname << ", " << obj << ")\n"; + if (obj != "") { + String tmp; + if (!get_obj_property(obj, "gender", tmp)) + tmp = "it"; + set_svar("quest.error.gender", tmp); + + if (!get_obj_property(obj, "article", tmp)) + tmp = "it"; + set_svar("quest.error.article", tmp); + + cerr << "In erroring " << errorname << " / " << obj << ", qeg == " + << get_svar("quest.error.gender") << ", qea == " + << get_svar("quest.error.article") << endl; + // TODO quest.error.charactername + } + + const GeasBlock *game = gf.find_by_name("game", "game"); + assert(game != NULL); + String tok; + uint c1, c2; + for (uint i = 0; i < game->data.size(); i ++) { + String line = game->data[i]; + tok = first_token(line, c1, c2); + // SENSITIVE? + if (tok == "error") { + tok = next_token(line, c1, c2); + if (is_param(tok)) { + String text = param_contents(tok); + int index = text.find(';'); + String errortype = trim(text.substr(0, index)); + // SENSITIVE? + if (errortype == errorname) { + print_eval_p(trim(text.substr(index + 1))); + return; + } + } else + gi->debug_print("Bad error line: " + line); + } + } + //print_formatted ("Default error " + errorname); + + // ARE THESE SENSITIVE? + if (errorname == "badcommand") + print_eval("I don't understand your command. Type HELP for a list of valid commands."); + else if (errorname == "badgo") + print_eval("I don't understand your use of 'GO' - you must either GO in some direction, or GO TO a place."); + else if (errorname == "badgive") + print_eval("You didn't say who you wanted to give that to."); + else if (errorname == "badcharacter") + print_eval("I can't see anybody of that name here."); + else if (errorname == "noitem") + print_eval("You don't have that."); + else if (errorname == "itemunwanted") + print_eval_p("#quest.error.gender# doesn't want #quest.error.article#."); + else if (errorname == "badlook") + print_eval("You didn't say what you wanted to look at."); + else if (errorname == "badthing") + print_eval("I can't see that here."); + else if (errorname == "defaultlook") + print_eval("Nothing out of the ordinary."); + else if (errorname == "defaultspeak") + print_eval_p("#quest.error.gender# says nothing."); + else if (errorname == "baditem") + print_eval("I can't see that anywhere."); + else if (errorname == "defaulttake") + print_eval("You pick #quest.error.article# up."); + else if (errorname == "baduse") + print_eval("You didn't say what you wanted to use that on."); + else if (errorname == "defaultuse") + print_eval("You can't use that here."); + else if (errorname == "defaultout") + print_eval("There's nowhere you can go out to around here."); + else if (errorname == "badplace") + print_eval("You can't go there."); + else if (errorname == "defaultexamine") + print_eval("Nothing out of the ordinary."); + else if (errorname == "badtake") + print_eval("You can't take #quest.error.article#."); + else if (errorname == "cantdrop") + print_eval("You can't drop that here."); + else if (errorname == "defaultdrop") + print_eval("You drop #quest.error.article#."); + else if (errorname == "baddrop") + print_eval("You are not carrying such a thing."); + else if (errorname == "badpronoun") + print_eval("I don't know what '#quest.error.pronoun#' you are referring to."); + else if (errorname == "badexamine") + print_eval("You didn't say what you wanted to examine."); + else + gi->debug_print("Bad error name " + errorname); +} + +String geas_implementation::displayed_name(String obj) const { + String rv = obj, tmp; + + if (get_obj_property(obj, "alias", tmp)) + rv = tmp; + else { + for (uint i = 0; i < gf.blocks.size(); i ++) + if (ci_equal(gf.blocks[i].name, obj)) { + rv = gf.blocks[i].name; + break; + } + } + return rv; +} + +/* For each destination, give: + * - printed name + * - accepted name, with prefix + * - accepted name, without prefix + * - destination, internal format + * - script (optional) + */ +Common::Array<Common::Array<String> > geas_implementation::get_places(String room) { + Common::Array<Common::Array<String> > rv; + + const GeasBlock *gb = gf.find_by_name("room", room); + if (gb == NULL) + return rv; + + String line, tok; + uint c1, c2; + for (uint i = 0; i < gb->data.size(); i ++) { + line = gb->data[i]; + tok = first_token(line, c1, c2); + if (tok == "place") { + tok = next_token(line, c1, c2); + if (!is_param(tok)) { + gi->debug_print("Expected parameter after 'place' in " + line); + continue; + } + String dest_param = eval_param(tok); + if (dest_param == "") { + gi->debug_print("Parameter empty in " + line); + continue; + } + + int j = dest_param.find(';'); + String dest, prefix = ""; + if (j == -1) + dest = trim(dest_param); + else { + dest = trim(dest_param.substr(j + 1)); + prefix = trim(dest_param.substr(0, j)); + } + String displayed = displayed_name(dest); + String printed_dest = (prefix != "" ? prefix + " " : "") + + "|b" + displayed + "|xb"; + + Common::Array<String> tmp; + tmp.push_back(printed_dest); + tmp.push_back(prefix + " " + displayed); + tmp.push_back(displayed); + tmp.push_back(dest); + String rest = trim(line.substr(c2)); + if (rest != "") + tmp.push_back(rest); + rv.push_back(tmp); + } + } + + for (uint i = 0; i < state.exits.size(); i ++) { + if (state.exits[i].src != room) + continue; + line = state.exits[i].dest; + tok = first_token(line, c1, c2); + if (tok == "exit") { + tok = next_token(line, c1, c2); + if (!is_param(tok)) + continue; + tok = next_token(line, c1, c2); + assert(is_param(tok)); + tok = param_contents(tok); + Common::Array<String> args = split_param(tok); + if (args.size() != 2) { + gi->debug_print("Expected two arguments in " + tok); + continue; + } + assert(args[0] == room); + Common::Array<String> tmp; + String displayed = displayed_name(args[1]); + tmp.push_back("|b" + displayed + "|xb"); + tmp.push_back(displayed); + tmp.push_back(displayed); + tmp.push_back(args[1]); + rv.push_back(tmp); + } else if (tok == "destroy") { + tok = next_token(line, c1, c2); + assert(tok == "exit"); + tok = next_token(line, c1, c2); + + for (v2string::iterator j = rv.begin(); j != rv.end(); j ++) + if ((*j)[3] == tok) { + rv.erase(j); + break; + } + } + + + } + + cerr << "get_places (" << room << ") -> " << rv << "\n"; + return rv; +} + +String geas_implementation::exit_dest(String room, String dir, bool *is_script) const { + uint c1, c2; + String tok; + if (is_script != NULL) + *is_script = false; + for (uint i = state.exits.size() - 1; i + 1 > 0; i --) + if (state.exits[i].src == room) { + String line = state.exits[i].dest; + cerr << "Processing exit line '" << state.exits[i].dest << "'\n"; + tok = first_token(line, c1, c2); + cerr << " first tok is " << tok << " (vs. exit)\n"; + // SENSITIVE? + if (tok != "exit") + continue; + tok = next_token(line, c1, c2); + cerr << " second tok is " << tok << " (vs. " << dir << ")\n"; + if (tok != dir) + continue; + tok = next_token(line, c1, c2); + cerr << " third tok is " << tok << " (expecting parameter)\n"; + assert(is_param(tok)); + Common::Array<String> p = split_param(param_contents(tok)); + assert(p.size() == 2); + assert(ci_equal(p[0], room)); + return p[1]; + } + /* + if (gf.get_obj_action (room, dir, tok)) + { + if (is_script != NULL) + *is_script = true; + return tok; + } + if (gf.get_obj_property (room, dir, tok)) + return tok; + else + return ""; + */ + + const GeasBlock *gb = gf.find_by_name("room", room); + if (gb == NULL) { + gi->debug_print(String("Trying to find exit <") + dir + + "> of nonexistent room <" + room + ">."); + return ""; + } + // TODO: what's the priority on this? + for (uint i = 0; i < gb->data.size(); i ++) { + String line = gb->data[i]; + tok = first_token(line, c1, c2); + if (tok == dir) { + uint line_start = c2; + tok = next_token(line, c1, c2); + if (is_param(tok)) + return param_contents(tok); + if (tok != "") { + if (is_script != NULL) + *is_script = true; + return trim(line.substr(line_start + 1)); + } + return ""; + } + } + return ""; +} + +void geas_implementation::look() { + String tmp; + if (get_obj_action(state.location, "description", tmp)) + run_script_as(state.location, tmp); + //run_script(tmp); + else if (get_obj_property(state.location, "description", tmp)) + print_formatted(tmp); + else if (get_obj_action("game", "description", tmp)) + run_script_as("game", tmp); + //run_script (tmp); + else if (get_obj_property("game", "description", tmp)) + print_formatted(tmp); + else { + String in_desc; + if (get_obj_property(state.location, "indescription", tmp)) + in_desc = tmp; + else + in_desc = "You are in"; + print_formatted(in_desc + " " + get_svar("quest.formatroom")); + + if ((tmp = get_svar("quest.formatobjects")) != "") + //print_formatted ("There is " + tmp + " here."); + print_eval("There is #quest.formatobjects# here."); + if ((tmp = get_svar("quest.doorways.out")) != "") + print_formatted("You can go out to " + tmp + "."); + if ((tmp = get_svar("quest.doorways.dirs")) != "") + //print_formatted ("You can go " + tmp + "."); + print_eval("You can go #quest.doorways.dirs#."); + if ((tmp = get_svar("quest.doorways.places")) != "") + print_formatted("You can go to " + tmp + "."); + if ((tmp = get_svar("quest.lookdesc")) != "") + print_formatted(tmp); + } +} + +void geas_implementation::set_game(const String &fname) { + cerr << "set_game (...)\n"; + + gf = read_geas_file(gi, fname); + if (gf.blocks.size() == 0) { + is_running_ = false; + return; + } + //print_formatted ("Ready...|n|cbblack|crred|clblue|cggreen|cyyellow|n|uunderlined: |cbblack|crred|clblue|cggreen|cyyellow|xu|n"); + //cerr << "Read game " << gf << endl; + uint tok_start, tok_end; + outputting = true; + + state = GeasState(*gi, gf); + + state.running = true; + + for (uint gline = 0; gline < gf.block("game", 0).data.size(); gline ++) { + String s = gf.block("game", 0).data[gline]; + String tok = first_token(s, tok_start, tok_end); + // SENSITIVE? + if (tok == "asl-version") { + String ver = next_token(s, tok_start, tok_end); + if (!is_param(ver)) { + gi->debug_print("Version " + s + " has invalid version " + + ver); + continue; + } + int vernum = parse_int(param_contents(ver)); + if (vernum < 311 || vernum > 353) + gi->debug_print("Warning: Geas only supports ASL " + " versions 3.11 to 3.53"); + } + // SENSITIVE? + else if (tok == "background") { + tok = next_token(s, tok_start, tok_end); + if (!is_param(tok)) + gi->debug_print(nonparam("background color", s)); + else + gi->set_background(param_contents(tok)); + } + // SENSITIVE? + else if (tok == "default") { + tok = next_token(s, tok_start, tok_end); + // SENSITIVE? + if (tok == "fontname") { + tok = next_token(s, tok_start, tok_end); + if (!is_param(tok)) + gi->debug_print(nonparam("font name", s)); + else + gi->set_default_font(param_contents(tok)); + } + // SENSITIVE? + else if (tok == "fontsize") { + tok = next_token(s, tok_start, tok_end); + if (!is_param(tok)) + gi->debug_print(nonparam("font size", s)); + else + gi->set_default_font_size(param_contents(tok)); + } + } + // SENSITIVE? + else if (tok == "foreground") { + tok = next_token(s, tok_start, tok_end); + if (!is_param(tok)) + gi->debug_print(nonparam("foreground color", s)); + else + gi->set_foreground(param_contents(tok)); + } + // SENSITIVE? + else if (tok == "gametype") { + tok = next_token(s, tok_start, tok_end); + // SENSITIVE? + if (tok == "singleplayer") + continue; + // SENSITIVE? + if (tok == "multiplayer") + throw String("Error: geas is single player only."); + gi->debug_print("Unexpected game type " + s); + } + // SENSITIVE? + else if (tok == "nodebug") { + } + // SENSITIVE? + else if (tok == "start") { + tok = next_token(s, tok_start, tok_end); + if (!is_param(tok)) + gi->debug_print(nonparam("start room", s)); + else { + state.location = param_contents(tok); + } + } + } + + const GeasBlock &game = gf.block("game", 0); + cerr << gf << endl; + //print_formatted ("Done loading " + game.name); + uint c1, c2; + String tok; + + /* TODO do I run the startscript or print the opening text first? */ + run_script("displaytext <intro>"); + + for (uint i = 0; i < game.data.size(); i ++) + // SENSITIVE? + if (first_token(game.data[i], c1, c2) == "startscript") { + run_script_as("game", game.data[i].substr(c2 + 1)); + //run_script (game.data[i].substr (c2 + 1)); + break; + } + + regen_var_room(); + regen_var_objects(); + regen_var_dirs(); + regen_var_look(); + look(); + + cerr << "s_g: done with set_game (...)\n\n"; +} + +void geas_implementation::regen_var_objects() { + String tmp; + Common::Array <String> objs; + for (uint i = 0; i < state.objs.size(); i ++) { + //cerr << "r_v_o: Checking '" << state.objs[i].name << "' (" << state.objs[i].parent << "): " << ((state.objs[i].parent == state.location) ? "YES" : "NO") << endl; + if (ci_equal(state.objs[i].parent, state.location) && + !get_obj_property(state.objs[i].name, "hidden", tmp) && + !get_obj_property(state.objs[i].name, "invisible", tmp)) + //!state.objs[i].hidden && + //!state.objs[i].invisible) + objs.push_back(state.objs[i].name); + } + String qobjs = "", qfobjs = ""; + String objname, prefix, main, suffix, propval, print1, print2; + for (uint i = 0; i < objs.size(); i ++) { + objname = objs[i]; + if (!get_obj_property(objname, "alias", main)) + main = objname; + print1 = main; + print2 = "|b" + main + "|xb"; + if (get_obj_property(objname, "prefix", prefix)) { + print1 = prefix + " " + print1; + print2 = prefix + " " + print2; + } + if (get_obj_property(objname, "suffix", suffix)) { + print1 = print1 + " " + suffix; + print2 = print2 + " " + suffix; + } + qobjs = qobjs + print1; + qfobjs = qfobjs + print2; + if (i + 2 < objs.size()) { + qobjs = qobjs + ", "; + qfobjs = qfobjs + ", "; + } else if (i + 2 == objs.size()) { + qobjs = qobjs + " and "; + qfobjs = qfobjs + " and "; + } + } + set_svar("quest.objects", qobjs); + set_svar("quest.formatobjects", qfobjs); +} + +void geas_implementation::regen_var_room() { + set_svar("quest.currentroom", state.location); + + String tmp, formatroom; + if (!get_obj_property(state.location, "alias", formatroom)) + formatroom = state.location; + formatroom = "|cr" + formatroom + "|cb"; + if (get_obj_property(state.location, "prefix", tmp)) + formatroom = tmp + " " + formatroom; + if (get_obj_property(state.location, "suffix", tmp)) + formatroom = formatroom + " " + tmp; + //set_svar ("quest.formatroom", displayed_name (state.location)); + set_svar("quest.formatroom", formatroom); + + // regen_var_objects(); + /* + String out_dest = exit_dest (state.location, "out"); + if (out_dest == "") + { + set_svar ("quest.doorways.out", ""); + set_svar ("quest.doorways.out.display", ""); + } + else + { + cerr << "Updating quest.doorways.out; out_dest == {" << out_dest << "}"; + uint i = out_dest.find (';'); + cerr << ", i == " << i; + String prefix = ""; + if (i != -1) + { + prefix = trim (out_dest.substr (0, i-1)); + out_dest = trim (out_dest.substr (i + 1)); + cerr << "; prefix == {" << prefix << "}, out_dest == {" << out_dest << "}"; + } + cerr << " quest.doorways.out == {" << out_dest << "}"; + set_svar ("quest.doorways.out", out_dest); + cerr << endl; + + String tmp = displayed_name (out_dest); + + cerr << ", tmp == {" << tmp << "}"; + + if (tmp != "") + tmp = "|b" + tmp + "|xb"; + else if (prefix != "") + tmp = prefix + " |b" + out_dest + "|xb"; + else + tmp = "|b" + out_dest + "|xb"; + + cerr << ", final value {" << tmp << "}" << endl; + + set_svar ("quest.doorways.out.display", tmp); + } + */ +} + + +void geas_implementation::regen_var_look() { + String look_tag; + if (!get_obj_property(state.location, "look", look_tag)) + look_tag = ""; + set_svar("quest.lookdesc", look_tag); +} + + +void geas_implementation::regen_var_dirs() { + Common::Array <String> dirs; + // the -1 is so that it skips 'out' + for (uint i = 0; i < ARRAYSIZE(dir_names) - 1; i ++) + if (exit_dest(state.location, dir_names[i]) != "") + dirs.push_back(dir_names[i]); + String exits = ""; + if (dirs.size() == 1) + exits = "|b" + dirs[0] + "|xb"; + else if (dirs.size() > 1) { + for (uint i = 0; i < dirs.size(); i ++) { + exits = exits + "|b" + dirs[i] + "|xb"; + if (i < dirs.size() - 2) + exits = exits + ", "; + else if (i == dirs.size() - 2) + exits = exits + " or "; + } + } + set_svar("quest.doorways.dirs", exits); + + /* + String tmp; + if ((tmp = exit_dest (state.location, "out")) != "") + set_svar ("quest.doorways.out", displayed_name (tmp)); + else + set_svar ("quest.doorways.out", ""); + */ + + String out_dest = exit_dest(state.location, "out"); + if (out_dest == "") { + set_svar("quest.doorways.out", ""); + set_svar("quest.doorways.out.display", ""); + } else { + cerr << "Updating quest.doorways.out; out_dest == {" << out_dest << "}"; + int i = out_dest.find(';'); + cerr << ", i == " << i; + String prefix = ""; + if (i != -1) { + prefix = trim(out_dest.substr(0, i - 1)); + out_dest = trim(out_dest.substr(i + 1)); + cerr << "; prefix == {" << prefix << "}, out_dest == {" << out_dest << "}"; + } + cerr << " quest.doorways.out == {" << out_dest << "}"; + set_svar("quest.doorways.out", out_dest); + cerr << endl; + + String tmp = displayed_name(out_dest); + + cerr << ", tmp == {" << tmp << "}"; + + if (tmp != "") + tmp = "|b" + tmp + "|xb"; + else if (prefix != "") + tmp = prefix + " |b" + out_dest + "|xb"; + else + tmp = "|b" + out_dest + "|xb"; + + cerr << ", final value {" << tmp << "}" << endl; + + set_svar("quest.doorways.out.display", tmp); + } + + /* TODO handle this */ + //set_svar ("quest.doorways.places", ""); + current_places = get_places(state.location); + String printed_places = ""; + for (uint i = 0; i < current_places.size(); i ++) { + if (i == 0) + printed_places = current_places[i][0]; + else if (i < current_places.size() - 1) + printed_places = printed_places + ", " + current_places[i][0]; + else if (current_places.size() == 2) + printed_places = printed_places + " or " + current_places[i][0]; + else + printed_places = printed_places + ", or " + current_places[i][0]; + } + set_svar("quest.doorways.places", printed_places); +} + + + +// TODO: SENSITIVE??? +String geas_implementation::substitute_synonyms(String s) const { + String orig = s; + cerr << "substitute_synonyms (" << s << ")\n"; + const GeasBlock *gb = gf.find_by_name("synonyms", ""); + if (gb != NULL) { + /* TODO: exactly in what order does it try synonyms? + * Does it have to be flanked by whitespace? + */ + for (uint i = 0; i < gb->data.size(); i ++) { + String line = gb->data[i]; + int index = line.find('='); + if (index == -1) + continue; + Common::Array<String> words = split_param(line.substr(0, index)); + String rhs = trim(line.substr(index + 1)); + if (rhs == "") + continue; + for (uint j = 0; j < words.size(); j ++) { + String lhs = words[j]; + if (lhs == "") + continue; + int k = 0; + while ((k = s.find(lhs, k)) != -1) { + uint end_index = k + lhs.length(); + if ((k == 0 || s[k - 1] == ' ') && + (end_index == s.length() || s[end_index] == ' ')) { + s = s.substr(0, k) + rhs + s.substr(k + lhs.length()); + k = k + rhs.length(); + } else + k ++; + } + } + } + } + cerr << "substitute_synonyms (" << orig << ") -> '" << s << "'\n"; + return s; +} + +bool geas_implementation::is_running() const { + return is_running_; +} + +String geas_implementation::get_banner() { + String banner; + const GeasBlock *gb = gf.find_by_name("game", "game"); + if (gb) { + String line = gb->data[0]; + uint c1, c2; + String tok = first_token(line, c1, c2); + tok = next_token(line, c1, c2); + tok = next_token(line, c1, c2); + if (is_param(tok)) { + banner = eval_param(tok); + + for (uint i = 0; i < gb->data.size(); i ++) { + line = gb->data[i]; + if (first_token(line, c1, c2) == "game" && + next_token(line, c1, c2) == "version" && + is_param(tok = next_token(line, c1, c2))) { + banner += ", v"; + banner += eval_param(tok); + } + } + + for (uint i = 0; i < gb->data.size(); i ++) { + line = gb->data[i]; + if (first_token(line, c1, c2) == "game" && + next_token(line, c1, c2) == "author" && + is_param(tok = next_token(line, c1, c2))) { + banner += " | "; + banner += eval_param(tok); + } + } + } + } + return banner; +} + +void geas_implementation::run_command(String s) { + /* if s == "restore" or "restart" or "quit" or "undo" */ + + if (!is_running_) + return; + + print_newline(); + print_normal("> " + s); + print_newline(); + + if (s == "dump status") { + //cerr << state << endl; + ostringstream oss; + oss << state; + print_normal(oss.str()); + return; + } + + if (s == "undo") { + if (undo_buffer.size() < 2) { + print_formatted("(No more undo information available!)"); + return; + } + undo_buffer.pop(); + state = undo_buffer.peek(); + print_formatted("Undone."); + return; + } + + if (!state.running) + return; + // TODO: does this get the original command, or the lowercased version? + set_svar("quest.originalcommand", s); + s = substitute_synonyms(lcase(s)); + set_svar("quest.command", s); + + bool overridden = false; + dont_process = false; + + const GeasBlock *gb = gf.find_by_name("room", state.location); + if (gb != NULL) { + String line, tok; + uint c1, c2; + for (uint i = 0; i < gb->data.size(); i ++) { + line = gb->data[i]; + tok = first_token(line, c1, c2); + // SENSITIVE? + if (tok == "beforeturn") { + uint scr_starts = c2; + tok = next_token(line, c1, c2); + // SENSITIVE? + if (tok == "override") { + overridden = true; + scr_starts = c2; + } + String scr = line.substr(scr_starts); + run_script(state.location, scr); + //run_script (scr); + } + } + } else + gi->debug_print("Unable to find block " + state.location + ".\n"); + + if (!overridden) { + gb = gf.find_by_name("game", "game"); + if (gb != NULL) { + String line, tok; + uint c1, c2; + for (uint i = 0; i < gb->data.size(); i ++) { + line = gb->data[i]; + tok = first_token(line, c1, c2); + // SENSITIVE? + if (tok == "beforeturn") { + uint scr_starts = c2; + tok = next_token(line, c1, c2); + // SENSITIVE? + if (tok == "override") { + overridden = true; + scr_starts = c2; + } + String scr = line.substr(scr_starts); + run_script_as("game", scr); + //run_script (scr); + } + } + } else + gi->debug_print("Unable to find block game.\n"); + } + + if (!dont_process) { + if (try_match(s, false, false)) { + /* TODO TODO */ + // run after turn events ??? + } else + display_error("badcommand"); + } + + overridden = false; + + gb = gf.find_by_name("room", state.location); + if (gb != NULL) { + String line, tok; + uint c1, c2; + for (uint i = 0; i < gb->data.size(); i ++) { + line = gb->data[i]; + tok = first_token(line, c1, c2); + // SENSITIVE? + if (tok == "afterturn") { + uint scr_starts = c2; + tok = next_token(line, c1, c2); + // SENSITIVE? + if (tok == "override") { + overridden = true; + scr_starts = c2; + } + String scr = line.substr(scr_starts); + run_script_as(state.location, scr); + //run_script (scr); + } + } + } + if (!overridden) { + gb = gf.find_by_name("game", "game"); + if (gb != NULL) { + String line, tok; + uint c1, c2; + for (uint i = 0; i < gb->data.size(); i ++) { + line = gb->data[i]; + tok = first_token(line, c1, c2); + // SENSITIVE? + if (tok == "afterturn") { + uint scr_starts = c2; + tok = next_token(line, c1, c2); + // SENSITIVE? + if (tok == "override") { + overridden = true; + scr_starts = c2; + } + String scr = line.substr(scr_starts); + run_script_as("game", scr); + //run_script (scr); + } + } + } + } + + if (state.running) + undo_buffer.push(state); +} + +Common::WriteStream &operator<< (Common::WriteStream &o, const match_rv &rv) { + //o << "match_rv {" << (rv.success ? "TRUE" : "FALSE") << ": " << rv.bindings << "}"; + o << "match_rv {" << (rv.success ? "TRUE" : "FALSE") << ": ["; + //o << rv.bindings.size(); + o << rv.bindings; + //for (uint i = 0; i < rv.bindings.size(); i ++) + // o << rv.bindings[i] << ", "; + o << "]}"; + return o; +} + +match_rv geas_implementation::match_command(String input, String action) const { + //cerr << "match_command (\"" << input << "\", \"" << action << "\")" << endl; + match_rv rv = match_command(input, 0, action, 0, match_rv()); + cerr << "match_command (\"" << input << "\", \"" << action << "\") -> " << rv << endl; + return rv; + //return match_command (input, 0, action, 0, match_rv ()); +} + +match_rv geas_implementation::match_command(String input, uint ichar, String action, uint achar, match_rv rv) const { + //cerr << "match_command (\"" << input << "\", " << ichar << ", \"" << action << "\", " << achar << ", " << rv << ")" << endl; + for (;;) { + if (achar == action.length()) { + //cerr << "End of action, returning " << (ichar == input.length()) << "\n"; + return match_rv(ichar == input.length(), rv); + } + if (action[achar] == '#') { + + achar ++; + String varname; + while (achar != action.length() && action[achar] != '#') { + varname += action[achar]; + achar ++; + } + if (achar == action.length()) + throw String("Unpaired hashes in command String " + action); + //rv.bindings.push_back (varname); + int index = rv.bindings.size(); + rv.bindings.push_back(match_binding(varname, ichar)); + achar ++; + varname = ""; + //rv.bindings.push_back (varname); + rv.bindings[index].set(varname, ichar); + while (ichar < input.length()) { + match_rv tmp = match_command(input, ichar, action, achar, rv); + if (tmp.success) + return tmp; + varname += input[ichar]; + ichar ++; + //rv.bindings[index] = varname; + rv.bindings[index].set(varname, ichar); + } + return match_rv(achar == action.length(), rv); + } + // SENSITIVE? + if (ichar == input.length() || !c_equal_i(input[ichar], action[achar])) + return match_rv(); + //cerr << "Matched " << input[ichar] << " to " << action[achar] << endl; + ++ achar; + ++ ichar; + } +} + +bool match_object_alts(String text, const Common::Array<String> &alts, bool is_internal) { + for (uint i = 0; i < alts.size(); i ++) { + cerr << "m_o_a: Checking '" << text << "' v. alt '" << alts[i] << "'.\n"; + if (starts_with(text, alts[i])) { + uint len = alts[i].length(); + if (text.length() == len) + return true; + if (text.length() > len && text[len] == ' ' && + match_object_alts(text.substr(len + 1), alts, is_internal)) + return true; + } + } + return false; +} + + +bool geas_implementation::match_object(String text, String name, bool is_internal) const { + cerr << "* * * match_object (" << text << ", " << name << ", " + << (is_internal ? "true" : "false") << ")\n"; + + String alias, alt_list, prefix, suffix; + + if (is_internal && ci_equal(text, name)) return true; + + if (get_obj_property(name, "prefix", prefix) && + starts_with(text, prefix + " ") && + match_object(text.substr(prefix.length() + 1), name, false)) + return true; + + if (get_obj_property(name, "suffix", suffix) && + ends_with(text, " " + suffix) && + match_object(text.substr(0, text.length() - suffix.length() - 1), name, false)) + return true; + + if (!get_obj_property(name, "alias", alias)) + alias = name; + if (ci_equal(text, alias)) + return true; + + const GeasBlock *gb = gf.find_by_name("object", name); + if (gb != NULL) { + String tok, line; + uint c1, c2; + for (uint ln = 0; ln < gb->data.size(); ln ++) { + line = gb->data[ln]; + tok = first_token(line, c1, c2); + // SENSITIVE? + if (tok == "alt") { + tok = next_token(line, c1, c2); + if (!is_param(tok)) + gi->debug_print("Expected param after alt in " + line); + else { + Common::Array<String> alts = split_param(param_contents(tok)); + cerr << " m_o: alt == " << alts << "\n"; + return match_object_alts(text, alts, is_internal); + } + } + } + } + + return false; +} + + +bool geas_implementation::dereference_vars(Common::Array<match_binding> &bindings, bool is_internal) const { + /* TODO */ + Common::Array<String> where; + where.push_back("inventory"); + where.push_back(state.location); + return dereference_vars(bindings, where, is_internal); +} + +bool geas_implementation::dereference_vars(Common::Array<match_binding> &bindings, const Common::Array<String> &where, bool is_internal) const { + bool rv = true; + for (uint i = 0; i < bindings.size(); i ++) + if (bindings[i].var_name[0] == '@') { + String obj_name = get_obj_name(bindings[i].var_text, where, is_internal); + if (obj_name == "!") { + print_formatted("You don't see any " + bindings[i].var_text + "."); + rv = false; + } else { + bindings[i].var_text = obj_name; + bindings[i].var_name = bindings[i].var_name.substr(1); + } + } + return rv; +} + +String geas_implementation::get_obj_name(String name, const Common::Array<String> &where, bool is_internal) const { + Common::Array<String> objs, printed_objs; + for (uint objnum = 0; objnum < state.objs.size(); objnum ++) { + bool is_used = false; + for (uint j = 0; j < where.size(); j ++) { + cerr << "Object #" << objnum << ": " << state.objs[objnum].name + << "@" << state.objs[objnum].parent << " vs. " + << where[j] << endl; + // SENSITIVE? + if (where[j] == "game" || state.objs[objnum].parent == where[j]) + is_used = true; + } + if (is_used && !has_obj_property(state.objs[objnum].name, "hidden") && + match_object(name, state.objs[objnum].name, is_internal)) { + String printed_name, tmp, oname = state.objs[objnum].name; + objs.push_back(oname); + if (!get_obj_property(oname, "alias", printed_name)) + printed_name = oname; + if (get_obj_property(oname, "detail", tmp)) + printed_name = tmp; + printed_objs.push_back(printed_name); + } + } + cerr << "objs == " << objs << ", printed_objs == " << printed_objs << "\n"; + if (objs.size() > 1) { + //bindings[i].var_name = bindings[i].var_name.substr(1); + uint num = 0; + //if (objs.size() > 1) + num = gi->make_choice("Which " + name + " do you mean?", printed_objs); + + //bindings[i].var_text = objs[num]; + return objs[num]; + } + if (objs.size() == 1) + return objs[0]; + return "!"; +} + + +void geas_implementation::set_vars(const Common::Array<match_binding> &v) { + for (uint i = 0; i < v.size(); i ++) + set_svar(v[i].var_name, v[i].var_text); +} + + +bool geas_implementation::run_commands(String cmd, const GeasBlock *room, bool is_internal) { + uint c1, c2; + String line, tok; + match_rv match; + + if (room != NULL) { + for (uint i = 0; i < room->data.size(); i++) { + line = room->data[i]; + tok = first_token(line, c1, c2); + // SENSITIVE? + if (tok == "command") { + tok = next_token(line, c1, c2); + if (is_param(tok)) { + Common::Array<String> tmp = split_param(param_contents(tok)); + + for (uint j = 0; j < tmp.size(); j++) + if (match = match_command(cmd, tmp[j])) { + if (!dereference_vars(match.bindings, is_internal)) + return false; + set_vars(match.bindings); + run_script_as(state.location, line.substr(c2 + 1)); + //run_script (line.substr (c2+1)); + return true; + } + /* + if (match = match_command (cmd, param_contents(tok))) + { + if (!dereference_vars (match.bindings)) + return false; + set_vars (match.bindings); + run_script (line.substr (c2+1)); + return true; + } + */ + } else { + gi->debug_print("Bad command line: " + line); + } + } + } + } else + gi->debug_print("room is null\n"); + + return false; +} + +bool geas_implementation::try_match(String cmd, bool is_internal, bool is_normal) { + //print_formatted ("geas_impl registers " + cmd); + + String line, tok; + match_rv match; + + if (!is_normal) { + if (run_commands(cmd, gf.find_by_name("room", state.location)) || + run_commands(cmd, gf.find_by_name("game", "game"))) + return true; + } + + if ((match = match_command(cmd, "look at #@object#")) || + (match = match_command(cmd, "look #@object#"))) { + if (!dereference_vars(match.bindings, is_internal)) + return true; + + String object = match.bindings[0].var_text; + + if (get_obj_action(object, "look", tok)) + run_script_as(object, tok); + //run_script (tok); + else if (get_obj_property(object, "look", tok)) + print_formatted(tok); + else + display_error("defaultlook", object); + + return true; + } + + if ((match = match_command(cmd, "examine #@object#")) || + (match = match_command(cmd, "x #@object#"))) { + if (!dereference_vars(match.bindings, is_internal)) + return true; + + String object = match.bindings[0].var_text; + if (get_obj_action(object, "examine", tok)) + run_script_as(object, tok); + //run_script (tok); + else if (get_obj_property(object, "examine", tok)) + print_formatted(tok); + else if (get_obj_action(object, "look", tok)) + run_script_as(object, tok); + //run_script (tok); + else if (get_obj_property(object, "look", tok)) + print_formatted(tok); + else + display_error("defaultexamine", object); + return true; + } + + if (match = match_command(cmd, "look")) { + look(); + return true; + } + + if (match = match_command(cmd, "give #@first# to #@second#")) { + if (!dereference_vars(match.bindings, is_internal)) + return true; + String script, first = match.bindings[0].var_text, second = match.bindings[1].var_text; + if (! ci_equal(get_obj_parent(first), "inventory")) + display_error("noitem", first); + else if (get_obj_action(second, "give " + first, script)) + run_script(second, script); + //run_script (script); + else if (get_obj_action(first, "give to " + second, script)) + run_script_as(first, script); + //run_script (script); + else if (get_obj_action(second, "give anything", script)) { + set_svar("quest.give.object.name", first); + run_script_as(second, script); + //run_script (script); + } else if (get_obj_action(first, "give to anything", script)) { + set_svar("quest.give.object.name", second); + run_script_as(first, script); + //run_script (script); + } else { + String tmp; + if (!get_obj_property(second, "gender", tmp)) + tmp = "it"; + set_svar("quest.error.gender", tmp); + if (!get_obj_property(first, "article", tmp)) + tmp = "it"; + set_svar("quest.error.article", tmp); + display_error("itemunwanted"); + } + return true; + } + + if ((match = match_command(cmd, "use #@first# on #@second#")) || + (match = match_command(cmd, "use #@first# with #@second#"))) { + if (!dereference_vars(match.bindings, is_internal)) + return true; + String script, first = match.bindings[0].var_text, second = match.bindings[1].var_text; + if (! ci_equal(get_obj_parent(first), "inventory")) + display_error("noitem", first); + else if (get_obj_action(second, "use " + first, script)) { + //set_svar ("quest.use.object", first); + run_script_as(second, script); + //run_script (script); + } else if (get_obj_action(first, "use on " + second, script)) { + //set_svar ("quest.use.object", second); + run_script_as(first, script); + //run_script (script); + } else if (get_obj_action(second, "use anything", script)) { + set_svar("quest.use.object", first); + run_script(second, script); + //run_script (script); + } else if (get_obj_action(first, "use on anything", script)) { + set_svar("quest.use.object", second); + run_script_as(first, script); + //run_script (script); + } else + display_error("defaultuse"); + + return true; + } + + if (match = match_command(cmd, "use #@first#")) { + if (!dereference_vars(match.bindings, is_internal)) + return true; + String tmp, obj = match.bindings[0].var_text; + if (!ci_equal(get_obj_parent(obj), "inventory")) + display_error("noitem", obj); + else if (get_obj_action(obj, "use", tmp)) + run_script_as(obj, tmp); + //run_script (tmp); + else if (get_obj_property(obj, "use", tmp)) + print_formatted(tmp); + else + display_error("defaultuse", obj); + return true; + } + + + if ((match = match_command(cmd, "take #@object#")) || + (match = match_command(cmd, "get #@object#"))) { + if (!dereference_vars(match.bindings, is_internal)) + return true; + + String object = match.bindings[0].var_text; + if (get_obj_action(object, "take", tok)) { + cerr << "Running script '" << tok << "' for take " << object << endl; + run_script_as(object, tok); + //run_script (tok); + } else if (get_obj_property(object, "take", tok)) { + cerr << "Found property '" << tok << "' for take " << object << endl; + if (tok != "") + print_formatted(tok); + else + display_error("defaulttake", object); + String tmp; + move(object, "inventory"); + if (get_obj_action(object, "gain", tmp)) + run_script(object, tmp); + //run_script (tmp); + else if (get_obj_property(object, "gain", tmp)) + print_formatted(tmp); + } else { + cerr << "No match found for take " << object << endl; + // TODO set variable with object name + display_error("badtake", object); + } + return true; + } + + + if (match = match_command(cmd, "drop #@object#")) { + if (!dereference_vars(match.bindings, is_internal)) + return true; + String scr, obj = match.bindings[0].var_text; + if (get_obj_action(obj, "drop", scr)) { + run_script_as(obj, scr); + //run_script (scr); + return true; + } + + const GeasBlock *gb = gf.find_by_name("object", obj); + if (gb != NULL) { + uint c1, c2, script_begins; + for (uint i = 0; i < gb->data.size(); i ++) { + line = gb->data[i]; + tok = first_token(line, c1, c2); + // SENSITIVE? + if (tok == "drop") { + script_begins = c2; + tok = next_token(line, c1, c2); + // SENSITIVE? + if (tok == "everywhere") { + tok = next_token(line, c1, c2); + move(obj, state.location); + if (is_param(tok)) + print_eval(tok); + else + gi->debug_print("Expected param after drop everywhere in " + line); + return true; + } + // SENSITIVE? + if (tok == "nowhere") { + if (is_param(tok)) + print_eval(tok); + else + gi->debug_print("Expected param after drop nowhere in " + line); + return true; + } + run_script_as(obj, line.substr(script_begins)); + //run_script (line.substr (script_begins)); + return true; + } + } + } + move(obj, state.location); + display_error("defaultdrop", obj); + return true; + } + + if ((match = match_command(cmd, "speak to #@object#")) || + (match = match_command(cmd, "speak #@object#")) || + (match = match_command(cmd, "talk to #@object#")) || + (match = match_command(cmd, "talk #@object#"))) { + //print_formatted ("Talk to <" + String (match.bindings[0]) + ">"); + if (!dereference_vars(match.bindings, is_internal)) + return true; + String obj = match.bindings[0].var_text; + String script; + if (get_obj_action(obj, "speak", script)) + run_script_as(obj, script); + //run_script (script); + else + display_error("defaultspeak", obj); + //print_formatted ("Talk to <" + String (match.bindings[0]) + ">"); + return true; + } + + if (cmd == "exit" || cmd == "out" || cmd == "go out") { + const GeasBlock *gb = gf.find_by_name("room", state.location); + if (gb == NULL) { + gi->debug_print("Bad room"); + return true; + } + + line = ""; + int c1 = -1, c2 = -1; + uint uc1, uc2; + // TODO: Use the first matching line or the last? + for (uint i = 0; i < gb->data.size(); i ++) { + if (first_token(gb->data[i], uc1, uc2) == "out") + line = gb->data[i]; + c1 = uc1; + c2 = uc2; + } + + //gi->debug_print ("COMMAND " + cmd + ": line == " + line); + + if (line == "") + display_error("defaultout"); + else { + c1 = line.find('<'); + if (c1 != -1) + c2 = line.find('>', c1); + + if (c1 == -1 || c2 == -1) + gi->debug_print("Bad out line: " + line); + else { + String tmp = trim(line.substr(c2 + 1)); + //gi->debug_print ("tmp1 == {" + tmp + "}"); + if (tmp != "") + run_script_as(state.location, tmp); + //run_script (tmp); + else { + tmp = line.substr(c1, c2 - c1 + 1); + //gi->debug_print ("tmp2 == {" + tmp + "}"); + assert(is_param(tmp)); + tmp = param_contents(tmp); + c1 = tmp.find(';'); + if (c1 == -1) + goto_room(trim(tmp)); + else + goto_room(trim(tmp.substr(c1 + 1))); + } + } + } + return true; + } + + for (uint i = 0; i < ARRAYSIZE(dir_names); i ++) + if (cmd == dir_names[i] || cmd == (String("go ") + dir_names[i]) || + cmd == short_dir_names[i] || cmd == (String("go ") + short_dir_names[i])) { + bool is_script = false; + //print_formatted ("Trying to go " + dir_names[i]); + if ((tok = exit_dest(state.location, dir_names[i], &is_script)) == "") { + // TODO Which display_error do I use? + print_formatted("You can't go that way."); + return true; + } + if (is_script) + run_script_as(state.location, tok); + //run_script (tok); + else { + int index = tok.find(';'); + if (index == -1) + goto_room(trim(tok)); + else + goto_room(trim(tok.substr(index + 1))); + } + return true; + } + + if ((match = match_command(cmd, "go to #@room#")) || + (match = match_command(cmd, "go #@room#"))) { + assert(match.bindings.size() == 1); + String destination = match.bindings[0].var_text; + for (uint i = 0; i < current_places.size(); i ++) { + if (ci_equal(destination, current_places[i][1]) || + ci_equal(destination, current_places[i][2])) { + if (current_places[i].size() == 5) + run_script_as(state.location, current_places[i][4]); + //run_script (current_places[i][4]); + else + goto_room(current_places[i][3]); + return true; + } + } + display_error("badplace", destination); + return true; + } + + if (ci_equal(cmd, "inventory") || ci_equal(cmd, "i")) { + Common::Array<Common::Array<String> > inv = get_inventory(); + if (inv.size() == 0) + print_formatted("You are carrying nothing."); + else + print_formatted("You are carrying:"); + for (uint i = 0; i < inv.size(); i ++) { + print_normal(inv[i][0]); + print_newline(); + } + return true; + } + + if (ci_equal(cmd, "help")) { + print_formatted("|b|cl|s14Quest Quick Help|xb|cb|s00|n|n|cl|bMoving|xb|cb Press the direction buttons in the 'Compass' pane, or type |bGO NORTH|xb, |bSOUTH|xb, |bE|xb, etc. |xnTo go into a place, type |bGO TO ...|xb . To leave a place, type |bOUT, EXIT|xb or |bLEAVE|xb, or press the '|crOUT|cb' button.|n|cl|bObjects and Characters|xb|cb Use |bTAKE ...|xb, |bGIVE ... TO ...|xb, |bTALK|xb/|bSPEAK TO ...|xb, |bUSE ... ON|xb/|bWITH ...|xb, |bLOOK AT ...|xb, etc.|n|cl|bExit Quest|xb|cb Type |bQUIT|xb to leave Quest.|n|cl|bMisc|xb|cb Type |bABOUT|xb to get information on the current game."); + return true; + } + + if (ci_equal(cmd, "about")) { + const GeasBlock *gb = gf.find_by_name("game", "game"); + if (gb == NULL) + return true; + cerr << *gb << endl; + + uint c1, c2; + //print_formatted ("Game name: "); + line = gb->data[0]; + tok = first_token(line, c1, c2); // game + tok = next_token(line, c1, c2); // name + tok = next_token(line, c1, c2); // <whatever> + if (is_param(tok)) + print_formatted("Game name: " + eval_param(tok)); + + + for (uint i = 0; i < gb->data.size(); i ++) { + line = gb->data[i]; + // SENSITIVE? + if (first_token(line, c1, c2) == "game" && + next_token(line, c1, c2) == "version" && + is_param(tok = next_token(line, c1, c2))) + print_formatted("Version " + eval_param(tok)); + } + + for (uint i = 0; i < gb->data.size(); i ++) { + line = gb->data[i]; + // SENSITIVE? + if (first_token(line, c1, c2) == "game" && + next_token(line, c1, c2) == "author" && + is_param(tok = next_token(line, c1, c2))) + print_formatted("Author: " + eval_param(tok)); + } + + for (uint i = 0; i < gb->data.size(); i ++) { + line = gb->data[i]; + // SENSITIVE? + if (first_token(line, c1, c2) == "game" && + next_token(line, c1, c2) == "copyright" && + is_param(tok = next_token(line, c1, c2))) + print_formatted("Copyright: " + eval_param(tok)); + } + + for (uint i = 0; i < gb->data.size(); i ++) { + line = gb->data[i]; + // SENSITIVE? + if (first_token(line, c1, c2) == "game" && + next_token(line, c1, c2) == "info" && + is_param(tok = next_token(line, c1, c2))) + print_formatted(eval_param(tok)); + } + + return true; + } + + if (ci_equal(cmd, "quit")) { + is_running_ = false; + return true; + } + + return false; +} + +void geas_implementation::run_script_as(String obj, String scr) { + String backup_object, garbage; + backup_object = this_object; + this_object = obj; + run_script(scr, garbage); + this_object = backup_object; +} + +void geas_implementation::run_script(String s) { + String garbage; + run_script(s, garbage); +} + +void geas_implementation::run_script(String s, String &rv) { + //print_formatted (" Running script " + s + "."); + cerr << "Script line '" << s << "'\n"; + String tok; + uint c1, c2; + + tok = first_token(s, c1, c2); + + if (tok == "") return; + + if (tok[0] == '{') { + uint brace1 = c1 + 1, brace2; + for (brace2 = s.length() - 1; brace2 >= brace1 && s[brace2] != '}'; brace2 --) + ; + if (brace2 >= brace1) + run_script(s.substr(brace1, brace2 - brace1)); + else + gi->debug_print("Unterminated brace block in " + s); + return; + } + + // SENSITIVE? + if (tok == "action") { + tok = next_token(s, c1, c2); + if (!is_param(tok)) { + gi->debug_print("Expected parameter after action in " + s); + return; + } + tok = eval_param(tok); + int index = tok.find(';'); + if (index == -1) { + gi->debug_print("Error: no semicolon in " + s); + return; + } + set_obj_action(trim(tok.substr(0, index)), + "<" + trim(tok.substr(index + 1)) + "> " + s.substr(c2 + 1)); + return; + } + // SENSITIVE? + else if (tok == "animate") { + } + // SENSITIVE? + else if (tok == "background") { + tok = next_token(s, c1, c2); + if (is_param(tok)) + gi->set_background(eval_param(tok)); + else + gi->debug_print("Expected parameter after foreground in " + s); + return; + } + // SENSITIVE? + else if (tok == "choose") { + tok = next_token(s, c1, c2); + if (!is_param(tok)) { + gi->debug_print("Expected parameter after choose in " + s); + return; + } + tok = eval_param(tok); + const GeasBlock *gb = gf.find_by_name("selection", tok); + if (gb == NULL) { + gi->debug_print("No selection called " + tok + " found"); + return; + } + String question, line; + Common::Array<String> choices, actions; + for (uint ln = 0; ln < gb->data.size(); ln ++) { + line = gb->data[ln]; + tok = first_token(line, c1, c2); + // SENSITIVE? + if (tok == "info") { + tok = next_token(line, c1, c2); + if (is_param(tok)) + question = eval_param(tok); + else + gi->debug_print("Expected parameter after info in " + line); + } + // SENSITIVE? + else if (tok == "choice") { + tok = next_token(line, c1, c2); + if (is_param(tok)) { + choices.push_back(eval_param(tok)); + actions.push_back(line.substr(c2)); + } else + gi->debug_print("Expected parameter after choice in " + line); + } else + gi->debug_print("Bad line " + line + " in selection"); + } + if (choices.size() == 0) + //gi->debug_print ("No choices in selection " + gb->lname); + gi->debug_print("No choices in selection " + gb->name); + else + run_script(actions[gi->make_choice(question, choices)]); + return; + } + // SENSITIVE? + else if (tok == "clear") { + gi->clear_screen(); + return; + } + // SENSITIVE? + else if (tok == "clone") { + /* TODO */ + } + // SENSITIVE? + else if (tok == "create") { + tok = next_token(s, c1, c2); + // SENSITIVE? + if (tok == "exit") { // create exit + String dir = ""; + + tok = next_token(s, c1, c2); + if (!is_param(tok)) { + dir = tok; + tok = next_token(s, c1, c2); + } + + if (!is_param(tok)) { + gi->debug_print("Expected param after create exit in " + s); + return; + } + tok = eval_param(tok); + Common::Array<String> args = split_param(tok); + if (args.size() != 2) { + gi->debug_print("Expected 2 elements in param in " + s); + return; + } + if (dir != "") + state.exits.push_back(ExitRecord(args[0], + "exit " + dir + " <" + tok + ">")); + else + state.exits.push_back(ExitRecord(args[0], "exit <" + tok + ">")); + //gi->debug_print ("Not yet able to create place type exits"); + regen_var_dirs(); + return; + } + // SENSITIVE? + else if (tok == "object") { // create object + /* TODO */ + } + // SENSITIVE? + else if (tok == "room") { // create room + /* TODO */ + } else + gi->debug_print("Bad create line " + s); + return; + } + // SENSITIVE? + else if (tok == "debug") { + tok = next_token(s, c1, c2); + if (is_param(tok)) + gi->debug_print(eval_param(tok)); + else + gi->debug_print("Expected param after debug in " + s); + return; + } + // SENSITIVE? + else if (tok == "destroy") { + tok = next_token(s, c1, c2); + if (tok != "exit") { + gi->debug_print("expected 'exit' after 'destroy' in " + s); + return; + } + tok = next_token(s, c1, c2); + if (!is_param(tok)) { + gi->debug_print("Expected param after 'destroy exit' in " + s); + return; + } + tok = eval_param(tok); + Common::Array<String> args = split_param(tok); + if (args.size() != 2) { + gi->debug_print("Expected two arguments in " + s); + return; + } + //state.exits.push_back (ExitRecord (args[0], "destroy exit <" + tok + ">")); + state.exits.push_back(ExitRecord(args[0], "destroy exit " + args[1])); + regen_var_dirs(); + return; + } + // SENSITIVE? + else if (tok == "disconnect") { + /* QNSO */ + } + // SENSITIVE? + else if (tok == "displaytext") { + tok = next_token(s, c1, c2); + if (!is_param(tok)) { + gi->debug_print("Expected parameter after displaytext in " + s); + return; + } + const GeasBlock *gb = gf.find_by_name("text", param_contents(tok)); + if (gb != NULL) { + for (uint i = 0; i < gb->data.size(); i ++) { + print_formatted(gb->data[i]); + print_newline(); + } + } else + gi->debug_print("No such text block " + tok); + return; + } + // SENSITIVE? + else if (tok == "do") { + tok = next_token(s, c1, c2); + if (!is_param(tok)) { + gi->debug_print("Expected parameter after do in " + s); + return; + } + String fname = eval_param(tok); + int index = fname.find('('); + if (index != -1) { + int index2 = fname.find(')'); + run_procedure(trim(fname.substr(0, index)), + split_f_args(fname.substr(index + 1, index2 - index - 1))); + } else + run_procedure(fname); + + //run_procedure (fname); + return; + } + // SENSITIVE? + else if (tok == "doaction") { + tok = next_token(s, c1, c2); + if (!is_param(tok)) { + gi->debug_print("Expected parameter after doaction in " + s); + return; + } + String line = eval_param(tok); + int index = line.find(';'); + String obj = trim(line.substr(0, index)); + String act = trim(line.substr(index + 1)); + String old_object = this_object; + this_object = obj; + if (get_obj_action(obj, act, tok)) + run_script_as(obj, tok); + //run_script (tok); + else + gi->debug_print("No action defined for " + obj + " // " + act); + this_object = old_object; + return; + } + // SENSITIVE? + else if (tok == "dontprocess") { + dont_process = true; + return; + } + // SENSITIVE? + else if (tok == "enter") { + tok = next_token(s, c1, c2); + if (!is_param(tok)) { + gi->debug_print("Expected parameter after enter in " + s); + return; + } + tok = eval_param(tok); + set_svar(tok, gi->get_string()); + return; + } + // SENSITIVE? + else if (tok == "exec") { + tok = next_token(s, c1, c2); + if (!is_param(tok)) { + gi->debug_print("Expected parameter after exec in " + s); + return; + } + tok = eval_param(tok); + int index = tok.find(';'); + if (index != -1) { + String tmp = trim(tok.substr(index + 1)); + // SENSITIVE? + if (tmp == "normal") { + //run_command (trim (tok.substr (0, index)), true, true); + try_match(trim(tok.substr(0, index)), true, true); + } else { + gi->debug_print("Bad " + tmp + " in exec in " + s); + //run_command (trim (tok.substr (0, index)), true, false); + try_match(trim(tok.substr(0, index)), true, false); + } + } else { + //run_command (trim (tok.substr (0, index)), true, false); + try_match(trim(tok.substr(0, index)), true, false); + } + return; + } + // SENSITIVE? + else if (tok == "flag") { + tok = next_token(s, c1, c2); + bool is_on; + // SENSITIVE? + if (tok == "on") + is_on = true; + // SENSITIVE? + else if (tok == "off") + is_on = false; + else { + gi->debug_print("Expected 'on' or 'off' after flag in " + s); + return; + } + String onoff = tok; + + tok = next_token(s, c1, c2); + if (is_param(tok)) + set_obj_property("game", (is_on ? "" : "not ") + eval_param(tok)); + else + gi->debug_print("Expected param after flag " + onoff + " in " + s); + return; + } + // SENSITIVE? + else if (tok == "font") { + /* TODO */ + } + // SENSITIVE? + else if (tok == "for") { + tok = next_token(s, c1, c2); + // SENSITIVE? + if (tok == "each") { + // SENSITIVE? + if (next_token(s, c1, c2) == "object" && + next_token(s, c1, c2) == "in") { + tok = next_token(s, c1, c2); + // SENSITIVE? + if (tok == "game") { + /* TODO: This will run over the game, rooms, and objects */ + /* It should just do the objects. */ + String script = s.substr(c2); + // Start at 1 to skip game + for (uint i = 1; i < state.objs.size(); i ++) { + cerr << " quest.thing -> " + state.objs[i].name + "\n"; + set_svar("quest.thing", state.objs[i].name); + run_script(script); + } + return; + } else if (is_param(tok)) { + tok = trim(eval_param(tok)); + String script = s.substr(c2); + for (uint i = 0; i < state.objs.size(); i ++) + if (state.objs[i].parent == tok) { + set_svar("quest.thing", state.objs[i].name); + run_script(script); + } + return; + } + } + } else if (is_param(tok)) { + Common::Array<String> args = split_param(eval_param(tok)); + String varname = args[0]; + String script = s.substr(c2); + int startindex = parse_int(args[1]); + int endindex = parse_int(args[2]); + int step = 1; + if (args.size() > 3) + step = parse_int(args[3]); + for (set_ivar(varname, startindex); get_ivar(varname) < endindex; + set_ivar(varname, get_ivar(varname) + step)) + run_script(script); + return; + } + + } + // SENSITIVE? + else if (tok == "foreground") { + tok = next_token(s, c1, c2); + if (is_param(tok)) + gi->set_foreground(eval_param(tok)); + else + gi->debug_print("Expected parameter after foreground in " + s); + return; + } + // SENSITIVE? + else if (tok == "give") { + tok = next_token(s, c1, c2); + if (!is_param(tok)) { + gi->debug_print("Expected parameter after give in " + s); + return; + } + tok = eval_param(tok); + move(tok, "inventory"); + String tmp; + if (get_obj_action(tok, "gain", tmp)) + run_script_as(tok, tmp); + //run_script (tmp); + else if (get_obj_property(tok, "gain", tmp)) + print_formatted(tmp); + return; + } + // SENSITIVE? + else if (tok == "goto") { + tok = next_token(s, c1, c2); + if (is_param(tok)) + goto_room(trim(eval_param(tok))); + else + gi->debug_print("Expected parameter after goto in " + s); + return; + } + // SENSITIVE? + else if (tok == "helpclear") { + } + // SENSITIVE? + else if (tok == "helpclose") { + } + // SENSITIVE? + else if (tok == "helpdisplaytext") { + } + // SENSITIVE? + else if (tok == "helpmsg") { + } + // SENSITIVE? + else if (tok == "hide") { + tok = next_token(s, c1, c2); + if (is_param(tok)) + set_obj_property(eval_param(tok), "hidden"); + else + gi->debug_print("Expected param after conceal in " + s); + return; + } + // SENSITIVE? + else if (tok == "show") { + tok = next_token(s, c1, c2); + if (is_param(tok)) + set_obj_property(eval_param(tok), "not hidden"); + else + gi->debug_print("Expected param after conceal in " + s); + return; + } + // SENSITIVE? + else if (tok == "if") { + /* TODO TODO */ + uint begin_cond = c2 + 1, end_cond, begin_then, end_then; + + do { + tok = next_token(s, c1, c2); + // SENSITIVE? + } while (tok != "then" && tok != ""); + + if (tok == "") { + gi->debug_print("Expected then in if: " + s); + return; + } + end_cond = c1; + String cond_str = s.substr(begin_cond, end_cond - begin_cond); + + begin_then = c2 + 1; + int brace_count = 0; + do { + tok = next_token(s, c1, c2); + for (uint i = 0; i < tok.length(); i ++) + if (tok[i] == '{') + brace_count ++; + else if (tok[i] == '}') + brace_count --; + // SENSITIVE? + } while (tok != "" && !(brace_count == 0 && tok == "else")); + end_then = c1; + + + if (eval_conds(cond_str)) + run_script(s.substr(begin_then, end_then - begin_then), rv); + else if (c2 < s.length()) + run_script(s.substr(c2), rv); + return; + } + // SENSITIVE? + else if (tok == "inc" || tok == "dec") { + // SENSITIVE? + bool is_dec = (tok == "dec"); + tok = next_token(s, c1, c2); + if (!is_param(tok)) { + gi->debug_print("Expected parameter after inc in " + s); + return; + } + tok = eval_param(tok); + int diff; + int index = tok.find(';'); + String varname; + if (index == -1) { + varname = trim(tok); + diff = 1; + } else { + varname = trim(tok.substr(0, index)); + diff = eval_int(tok.substr(index + 1)); + } + if (is_dec) + set_ivar(varname, get_ivar(varname) - diff); + else + set_ivar(varname, get_ivar(varname) + diff); + return; + } + // SENSITIVE? + else if (tok == "lose") { + /* TODO TODO */ + tok = next_token(s, c1, c2); + if (!is_param(tok)) { + gi->debug_print("Expected parameter after lose in " + s); + return; + } + tok = eval_param(tok); + + /* TODO: is the object always moved to location, or only + * when it had been in the inventory ? + */ + bool was_lost = (ci_equal(get_obj_parent(tok), "inventory")); + if (was_lost) { + move(tok, state.location); + String tmp; + if (get_obj_action(tok, "lose", tmp)) + run_script_as(tok, tmp); + //run_script (tmp); + else if (get_obj_property(tok, "lose", tmp)) + print_formatted(tmp); + } + return; + } + // SENSITIVE? + else if (tok == "mailto") { + } + // SENSITIVE? + else if (tok == "modvolume") { + } + // SENSITIVE? + else if (tok == "move") { + tok = next_token(s, c1, c2); + if (!is_param(tok)) { + gi->debug_print("Expected parameter after move in " + s); + return; + } + tok = eval_param(tok); + int index = tok.find(';'); + if (index == -1) { + gi->debug_print("No semi in " + tok + " in " + s); + return; + } + move(trim(tok.substr(0, index)), trim(tok.substr(index + 1))); + return; + } + // SENSITIVE? + else if (tok == "msg") { + tok = next_token(s, c1, c2); + if (is_param(tok)) + print_eval(param_contents(tok)); + else + gi->debug_print("Expected parameter after msg in " + s); + return; + } + // SENSITIVE? + else if (tok == "msgto") { + /* QNSO */ + } + // SENSITIVE? + else if (tok == "outputoff") { + //print_formatted ("<<"); + outputting = false; + return; + } + // SENSITIVE? + else if (tok == "outputon") { + outputting = true; + //print_formatted (">>"); + return; + } + // SENSITIVE? + else if (tok == "panes") { + /* TODO */ + } + // SENSITIVE? + else if (tok == "pause") { + tok = next_token(s, c1, c2); + if (!is_param(tok)) { + gi->debug_print("Expected parameter after pause in " + s);; + return; + } + int i = eval_int(param_contents(tok)); + gi->pause(i); + return; + } + // SENSITIVE? + else if (tok == "picture") { + } + // SENSITIVE? + else if (tok == "playerlose") { + run_script("displaytext <lose>"); + state.running = false; + return; + } + // SENSITIVE? + else if (tok == "playerwin") { + run_script("displaytext <win>"); + state.running = false; + return; + } + // SENSITIVE? + else if (tok == "playmidi") { + } + // SENSITIVE? + else if (tok == "playmod") { + } + // SENSITIVE? + else if (tok == "playwav") { + } + // SENSITIVE? + else if (tok == "property") { + tok = next_token(s, c1, c2); + if (!is_param(tok)) { + gi->debug_print("Expected parameter in '" + s + "'"); + return; + } + Common::Array<String> args = split_param(eval_param(tok)); + for (uint i = 1; i < args.size(); i ++) { + String val = args[i]; + /* + if (val[0] == '[' && val[val.length() - 1] == ']') + val = val.substr (1, val.length() - 2); + //state.props.push_back (PropertyRecord (args[0], val)); + */ + val = trim_braces(val); + set_obj_property(args[0], val); + } + return; + } + // SENSITIVE? + else if (tok == "repeat") { + /* TODO TODO: assumes script is a "do ..." */ + tok = next_token(s, c1, c2); + // SENSITIVE? + if (tok != "while" && tok != "until") { + gi->debug_print("Expected while or until after repeat in " + s); + return; + } + bool is_while = (tok == "while"); + uint start_cond = c2, end_cond = (uint) -1; + while ((tok = next_token(s, c1, c2)) != "") { + // SENSITIVE? + if (tok == "do") { + end_cond = c1; + break; // TODO: Do I break here? + } + } + if (end_cond == -1) { + gi->debug_print("No script found after condition in " + s); + return; + } + String cond = trim(s.substr(start_cond, end_cond - start_cond)); + String script = trim(s.substr(end_cond)); + cerr << "Interpreting '" << s << "' as (" + << (is_while ? "WHILE" : "UNTIL") << ") (" + << cond << ") {" << script << "}\n"; + while (eval_conds(cond) == is_while) + run_script(script); + return; + } + // SENSITIVE? + else if (tok == "return") { + tok = next_token(s, c1, c2); + if (is_param(tok)) + rv = eval_param(tok); + else + gi->debug_print("Expected parameter after return in " + s); + return; + } + // SENSITIVE? + else if (tok == "reveal") { + tok = next_token(s, c1, c2); + if (is_param(tok)) + set_obj_property(eval_param(tok), "not invisible"); + else + gi->debug_print("Expected param after reveal in " + s); + return; + } + // SENSITIVE? + else if (tok == "conceal") { + tok = next_token(s, c1, c2); + if (is_param(tok)) + set_obj_property(eval_param(tok), "invisible"); + else + gi->debug_print("Expected param after conceal in " + s); + return; + } + // SENSITIVE? + else if (tok == "say") { + tok = next_token(s, c1, c2); + if (is_param(tok)) { + tok = eval_param(tok); + print_formatted("\"" + tok + "\""); + } else + gi->debug_print("Expected param after say in " + s); + return; + } + // SENSITIVE? + else if (tok == "set") { + String vartype = ""; + tok = next_token(s, c1, c2); + // SENSITIVE? + if (tok == "interval") { + tok = next_token(s, c1, c2); + if (!is_param(tok)) { + gi->debug_print("Expected param after set interval in " + s); + return; + } + tok = eval_param(tok); + int index = tok.find(';'); + if (index == -1) { + gi->debug_print("No semicolon in param in " + s); + return; + } + //String timer_name = lcase (trim (tok.substr (0, index))); + String timer_name = trim(tok.substr(0, index)); + uint time_val = parse_int(trim(tok.substr(index + 1))); + + for (uint i = 0; i < state.timers.size(); i ++) + if (state.timers[i].name == timer_name) { + state.timers[i].interval = time_val; + return; + } + gi->debug_print("no interval named " + timer_name + " found!"); + return; + } + // SENSITIVE? + if (tok == "String" || tok == "numeric") { + vartype = tok; + tok = next_token(s, c1, c2); + } + if (!is_param(tok)) { + gi->debug_print("Expected parameter in " + s); + return; + } + if (tok.find(';') == -1) { + gi->debug_print("Only one expression in set in " + s); + return; + } + tok = eval_param(tok); + int index = tok.find(';'); + //String varname = lcase (trim (tok.substr (0, index))); + String varname = trim(tok.substr(0, index)); + if (vartype == "") { + for (uint varn = 0; varn < state.ivars.size(); varn ++) + if (state.ivars[varn].name == varname) { + vartype = "numeric"; + break; + } + if (vartype == "") + for (uint varn = 0; varn < state.svars.size(); varn ++) + if (state.svars[varn].name == varname) { + vartype = "String"; + break; + } + } + if (vartype == "") { + gi->debug_print("Undefined variable " + varname + " in " + s); + return; + } + if (vartype == "String") + set_svar(varname, trim_braces(trim(tok.substr(index + 1)))); + else + set_ivar(varname, eval_int(tok.substr(index + 1))); + return; + } + // SENSITIVE? + else if (tok == "setstring") { + tok = next_token(s, c1, c2); + if (!is_param(tok)) { + gi->debug_print("Expected parameter in " + s); + return; + } + if (tok.find(';') == -1) { + gi->debug_print("Only one expression in set in " + s); + return; + } + tok = eval_param(tok); + int index = tok.find(';'); + String varname = trim(tok.substr(0, index)); + set_svar(varname, trim_braces(trim(tok.substr(index + 1)))); + return; + } + // SENSITIVE? + else if (tok == "setvar") { + tok = next_token(s, c1, c2); + if (!is_param(tok)) { + gi->debug_print("Expected parameter in " + s); + return; + } + if (tok.find(';') == -1) { + gi->debug_print("Only one expression in set in " + s); + return; + } + tok = eval_param(tok); + int index = tok.find(';'); + String varname = trim(tok.substr(0, index)); + set_ivar(varname, eval_int(tok.substr(index + 1))); + return; + } + // SENSITIVE? + else if (tok == "shell") { + + } + // SENSITIVE? + else if (tok == "shellexe") { + } + // SENSITIVE? + else if (tok == "speak") { + tok = next_token(s, c1, c2); + if (is_param(tok)) + gi->speak(eval_param(tok)); + else + gi->debug_print("Expected param after speak in " + s); + return; + } + // SENSITIVE? + else if (tok == "stop") { + state.running = false; + return; + } + // SENSITIVE? + else if (tok == "timeron" || tok == "timeroff") { + // SENSITIVE? + bool running = (tok == "timeron"); + //tok = lcase (next_token (s, c1, c2)); + tok = next_token(s, c1, c2); + if (is_param(tok)) { + tok = eval_param(tok); + for (uint i = 0; i < state.timers.size(); i ++) + if (state.timers[i].name == tok) { + if (running) + state.timers[i].timeleft = state.timers[i].interval; + state.timers[i].is_running = running; + return; + } + gi->debug_print("No timer " + tok + " found"); + return; + } + gi->debug_print(String("Expected parameter after timer") + + (running ? "on" : "off") + " in " + s); + return; + } + // SENSITIVE? + else if (tok == "type") { + /* TODO */ + } + // SENSITIVE? + else if (tok == "wait") { + tok = next_token(s, c1, c2); + if (tok != "") { + if (!is_param(tok)) { + gi->debug_print("Expected parameter after wait in " + s); + return; + } + tok = eval_param(tok); + } + gi->wait_keypress(tok); + return; + } + // SENSITIVE? + else if (tok == "with") { + // QNSO + } + gi->debug_print("Unrecognized script " + s); +} + +bool geas_implementation::eval_conds(String s) { + cerr << "if (" + s + ")" << endl; + + uint c1, c2; + String tok = first_token(s, c1, c2); + + if (tok == "") return true; + + bool rv = eval_cond(s); + + while (tok != "" && tok != "and") + tok = next_token(s, c1, c2); + + if (tok == "and") + rv = rv && eval_conds(s.substr(c2)); + else { + tok = first_token(s, c1, c2); + while (tok != "" && tok != "or") + tok = next_token(s, c1, c2); + if (tok == "or") + rv = rv || eval_conds(s.substr(c2)); + } + + cerr << "if (" << s << ") --> " << (rv ? "true" : "false") << endl; + return rv; +} + +bool geas_implementation::eval_cond(String s) { + uint c1, c2; + String tok = first_token(s, c1, c2); + // SENSITIVE? + if (tok == "not") + return !eval_cond(s.substr(c2)); + // SENSITIVE? + else if (tok == "action") { + tok = next_token(s, c1, c2); + if (!is_param(tok)) { + gi->debug_print("expected parameter after property in " + s); + return false; + } + tok = eval_param(tok); + int index = tok.find(';'); + if (index == -1) { + gi->debug_print("Only one argument to property in " + s); + return false; + } + String obj = trim(tok.substr(0, index)); + String act = trim(tok.substr(index + 1)); + return has_obj_action(obj, act); + } + // SENSITIVE? + else if (tok == "ask") { + tok = next_token(s, c1, c2); + if (!is_param(tok)) { + gi->debug_print("expected parameter after ask in " + s); + return false; + } + tok = eval_param(tok); + return gi->choose_yes_no(tok); + } + // SENSITIVE? + else if (tok == "exists") { + tok = next_token(s, c1, c2); + if (!is_param(tok)) { + gi->debug_print("expected parameter after exists in " + s); + return false; + } + Common::Array<String> args = split_param(eval_param(tok)); + bool do_report = false; + for (uint i = 1; i < args.size(); i ++) + // SENSITIVE? + if (args[i] == "report") + do_report = true; + else + gi->debug_print("Got modifier " + args[i] + " after exists"); + //args[0] = lcase (args[0]); + for (uint i = 0; i < state.objs.size(); i ++) + if (ci_equal(state.objs[i].name, args[0])) + return state.objs[i].parent != ""; + if (do_report) + gi->debug_print("exists " + args[0] + " failed due to nonexistence"); + return false; + } + // SENSITIVE? + else if (tok == "flag") { + tok = next_token(s, c1, c2); + if (!is_param(tok)) { + gi->debug_print("expected parameter after flag in " + s); + return false; + } + tok = trim(eval_param(tok)); + return has_obj_property("game", tok); + } + // SENSITIVE? + else if (tok == "got") { + tok = next_token(s, c1, c2); + if (!is_param(tok)) { + gi->debug_print("expected parameter after got in " + s); + return false; + } + //tok = lcase (trim (eval_param (tok))); + tok = trim(eval_param(tok)); + for (uint i = 0; i < state.objs.size(); i ++) + if (ci_equal(state.objs[i].name, tok)) + return ci_equal(state.objs[i].parent, "inventory"); + gi->debug_print("No object " + tok + " found while evaling " + s); + return false; + } + // SENSITIVE? + else if (tok == "here") { + tok = next_token(s, c1, c2); + if (!is_param(tok)) { + gi->debug_print("expected parameter after here in " + s); + return false; + } + //tok = lcase (trim (eval_param (tok))); + tok = trim(eval_param(tok)); + for (uint i = 0; i < state.objs.size(); i ++) + if (ci_equal(state.objs[i].name, tok)) { + //return (ci_equal (state.objs[i].parent, state.location) && + // !has_obj_property (tok, "invisible")); + return (ci_equal(state.objs[i].parent, state.location)); + } + /* TODO: is it invisible or hidden? */ + gi->debug_print("No object " + tok + " found while evaling " + s); + return false; + } + // SENSITIVE? + else if (tok == "is") { + tok = next_token(s, c1, c2); + if (!is_param(tok)) { + gi->debug_print("expected parameter after is in " + s); + return false; + } + tok = eval_param(tok); + int index; + // SENSITIVE? + if ((index = tok.find("!=;")) != -1) { + uint index1 = index; + do { + -- index1; + } while (index1 > 0 && tok[index1] != ';'); + + cerr << "Comparing <" << trim_braces(trim(tok.substr(0, index1))) + << "> != <" << trim_braces(trim(tok.substr(index + 3))) + << ">\n"; + return ci_notequal(trim_braces(trim(tok.substr(0, index - 1))), + trim_braces(trim(tok.substr(index + 3)))); + } + if ((index = tok.find("lt=;")) != -1) { + cerr << "Comparing <" << trim_braces(trim(tok.substr(0, index))) + << "> < <" << trim_braces(trim(tok.substr(index + 4))) + << ">\n"; + return eval_int(tok.substr(0, index - 1)) + <= eval_int(tok.substr(index + 4)); + } + if ((index = tok.find("gt=;")) != -1) + return eval_int(tok.substr(0, index)) + >= eval_int(tok.substr(index + 4)); + if ((index = tok.find("lt;")) != -1) + return eval_int(tok.substr(0, index)) + < eval_int(tok.substr(index + 3)); + if ((index = tok.find("gt;")) != -1) + return eval_int(tok.substr(0, index)) + > eval_int(tok.substr(index + 3)); + if ((index = tok.find(";")) != -1) { + cerr << "Comparing <" << trim_braces(trim(tok.substr(0, index))) + << "> == <" << trim_braces(trim(tok.substr(index + 1))) + << ">\n"; + return ci_equal(trim_braces(trim(tok.substr(0, index))), + trim_braces(trim(tok.substr(index + 1)))); + } + gi->debug_print("Bad is condition " + tok + " in " + s); + return false; + } + // SENSITIVE? + else if (tok == "property") { + tok = next_token(s, c1, c2); + if (!is_param(tok)) { + gi->debug_print("expected parameter after property in " + s); + return false; + } + tok = eval_param(tok); + int index = tok.find(';'); + if (index == -1) { + gi->debug_print("Only one argument to property in " + s); + return false; + } + String obj = trim(tok.substr(0, index)); + String prop = trim(tok.substr(index + 1)); + return has_obj_property(obj, prop); + } + // SENSITIVE? + else if (tok == "real") { + tok = next_token(s, c1, c2); + if (!is_param(tok)) { + gi->debug_print("expected parameter after real in " + s); + return false; + } + Common::Array<String> args = split_param(eval_param(tok)); + bool do_report = false; + for (uint i = 1; i < args.size(); i ++) + // SENSITIVE? + if (args[i] == "report") + do_report = true; + else + gi->debug_print("Got modifier " + args[i] + " after exists"); + //args[0] = lcase (args[0]); + for (uint i = 0; i < state.objs.size(); i ++) + if (ci_equal(state.objs[i].name, args[0])) + return true; + if (do_report) + gi->debug_print("real " + args[0] + " failed due to nonexistence"); + return false; + } + // SENSITIVE? + else if (tok == "type") { + tok = next_token(s, c1, c2); + if (!is_param(tok)) { + gi->debug_print("Expected parameter after type in " + s); + return false; + } + Common::Array<String> args = split_param(eval_param(tok)); + if (args.size() != 2) { + gi->debug_print("Expected two parameters to type in " + s); + return false; + } + return gf.obj_of_type(args[0], args[1]); + } + + gi->debug_print("Bad condition " + s); + return false; +} + +void geas_implementation::run_procedure(String pname, Common::Array<String> args) { + cerr << "run_procedure " << pname << " (" << args << ")\n"; + Common::Array<String> backup = function_args; + function_args = args; + run_procedure(pname); + function_args = backup; +} + +void geas_implementation::run_procedure(String pname) { + for (uint i = 0; i < gf.size("procedure"); i ++) + if (ci_equal(gf.block("procedure", i).name, pname)) { + const GeasBlock &proc = gf.block("procedure", i); + //cerr << "Running procedure " << proc << endl; + for (uint j = 0; j < proc.data.size(); j ++) { + //cerr << " Running line #" << j << ": " << proc.data[j] << endl; + run_script(proc.data[j]); + } + return; + } + gi->debug_print("No procedure " + pname + " found."); +} + +String geas_implementation::run_function(String pname, Common::Array<String> args) { + cerr << "run_function (w/ args) " << pname << " (" << args << ")\n"; + /* Parameter is handled specially because it can't change the stack */ + // SENSITIVE? + if (pname == "parameter") { + if (args.size() != 1) { + gi->debug_print("parameter called with " + string_int(args.size()) + + " args"); + return ""; + } + uint num = parse_int(args[0]); + if (0 < num && num <= function_args.size()) { + cerr << " --> " << function_args[num - 1] << "\n"; + return function_args[num - 1]; + } + cerr << " --> too many arguments\n"; + return ""; + } + Common::Array<String> backup = function_args; + function_args = args; + for (uint i = 0; i < args.size(); i ++) + set_svar("quest.function.parameter." + string_int(i + 1), args[i]); + String rv = run_function(pname); + function_args = backup; + return rv; +} + +String geas_implementation::bad_arg_count(String fname) { + gi->debug_print("Called " + fname + " with " + + string_int(function_args.size()) + " arguments."); + return ""; +} + +String geas_implementation::run_function(String pname) { + cerr << "geas_implementation::run_function (" << pname << ", " << function_args << ")\n"; + //pname = lcase (pname); + // SENSITIVE? + if (pname == "getobjectname") { + if (function_args.size() == 0) + return bad_arg_count(pname); + //return get_obj_name (function_args); + Common::Array<String> where; + for (uint i = 1; i < function_args.size(); i ++) + where.push_back(function_args[i]); + if (where.size() == 0) { + where.push_back(state.location); + where.push_back("inventory"); + } + bool is_internal = false; + return get_obj_name(function_args[0], where, is_internal); + } + // SENSITIVE? + else if (pname == "loadmethod") { + /* TODO TODO */ + return "normal"; + // "loaded" on restore from qsg + } + // SENSITIVE? + else if (pname == "locationof") { + if (function_args.size() != 1) + return bad_arg_count(pname); + + return get_obj_parent(function_args[0]); + } + // SENSITIVE? + else if (pname == "objectproperty") { + if (function_args.size() != 2) + return bad_arg_count(pname); + + String rv; + get_obj_property(function_args[0], function_args[1], rv); + return rv; + } + // SENSITIVE? + else if (pname == "timerstate") { + if (function_args.size() != 1) + return bad_arg_count(pname); + + //String timername = lcase (function_args[0]); + String timername = function_args[0]; + for (uint i = 0; i < state.timers.size(); i ++) + if (state.timers[i].name == timername) + return state.timers[i].is_running ? "1" : "0"; + return "!"; + } + // SENSITIVE? + else if (pname == "displayname") { + if (function_args.size() != 1) + return bad_arg_count(pname); + + return displayed_name(function_args[0]); + } + // SENSITIVE? + else if (pname == "capfirst") { + if (function_args.size() != 1) + return bad_arg_count(pname); + + return pcase(function_args[0]); + } + // SENSITIVE? + else if (pname == "instr") { + /* TODO What if it's not present? */ + if (function_args.size() != 2 && function_args.size() != 3) + return bad_arg_count(pname); + + int rv; + if (function_args.size() == 2) + rv = function_args[0].find(function_args[1]); + else + rv = function_args[1].find(function_args[2], + parse_int(function_args[0])); + + if (rv == -1) + return string_int(rv); // TODO What goes here? + else + return string_int(rv); + } + // SENSITIVE? + else if (pname == "lcase") { + if (function_args.size() != 1) + return bad_arg_count(pname); + + String rv = function_args[0]; + for (uint i = 0; i < rv.size(); i ++) + rv[i] = tolower(rv[i]); + return rv; + } + // SENSITIVE? + else if (pname == "left") { + if (function_args.size() != 2) + return bad_arg_count(pname); + + uint i = parse_int(function_args[1]); + if (i > function_args[0].length()) + return function_args[0]; + else + return function_args[0].substr(0, i); + } + // SENSITIVE? + else if (pname == "lengthof") { + if (function_args.size() != 1) + return bad_arg_count(pname); + + return string_int(function_args[0].length()); + } + // SENSITIVE? + else if (pname == "mid") { + if (function_args.size() != 3) + return bad_arg_count(pname); + + uint start = parse_int(function_args[1]), + len = parse_int(function_args[2]); + if (start > function_args[0].length()) + return ""; + if (start + len > function_args[0].length()) + return function_args[0].substr(start); + return function_args[0].substr(start, len); + } + // SENSITIVE? + else if (pname == "right") { + if (function_args.size() != 2) + return bad_arg_count(pname); + + uint size = parse_int(function_args[1]); + if (size > function_args[0].length()) + return function_args[0]; + return function_args[0].substr(function_args[0].length() - size); + } else if (pname == "ubound") { + if (function_args.size() != 1) + return bad_arg_count(pname); + + /* TODO TODO */ + return ""; + } + // SENSITIVE? + else if (pname == "ucase") { + if (function_args.size() != 1) + return bad_arg_count(pname); + + String rv = function_args[0]; + for (uint i = 0; i < rv.length(); i ++) + rv[i] = toupper(rv[i]); + return rv; + } + // SENSITIVE? + else if (pname == "rand") { + if (function_args.size() != 2) + return bad_arg_count(pname); + uint lower = parse_int(function_args[0]), + upper = parse_int(function_args[1]); + + // TODO: change this to use the high order bits of the random # instead + return string_int(lower + (g_vm->getRandomNumber(0x7fffff) % (upper + 1 - lower))); + } + // SENSITIVE? + else if (pname == "speechenabled") { + if (function_args.size() != 0) + return bad_arg_count(pname); + + return "0"; + /* TODO: return 1 if speech is enabled */ + } + // SENSITIVE? + else if (pname == "symbol") { + if (function_args.size() != 1) + return bad_arg_count(pname); + + // SENSITIVE? + if (function_args[0] == "gt") + return ">"; + // SENSITIVE? + else if (function_args[0] == "lt") + return "<"; + gi->debug_print("Bad symbol argument: " + function_args[0]); + return ""; + } + // SENSITIVE? + else if (pname == "numberparameters") + return string_int(function_args.size()); + // SENSITIVE? + else if (pname == "thisobject") + return this_object; + + /* disconnectedby, id, name, removeformatting */ + + String rv = ""; + + for (uint i = 0; i < gf.size("function"); i ++) + if (ci_equal(gf.block("function", i).name, pname)) { + const GeasBlock &proc = gf.block("function", i); + cerr << "Running function " << proc << endl; + for (uint j = 0; j < proc.data.size(); j ++) { + cerr << " Running line #" << j << ": " << proc.data[j] << endl; + run_script(proc.data[j], rv); + } + return rv; + } + gi->debug_print("No function " + pname + " found."); + return ""; +} + + + +v2string geas_implementation::get_inventory() { + return get_room_contents("inventory"); +} + +v2string geas_implementation::get_room_contents() { + return get_room_contents(state.location); +} + +v2string geas_implementation::get_room_contents(String room) { + v2string rv; + String objname; + for (uint i = 0; i < state.objs.size(); i ++) + if (state.objs[i].parent == room) { + objname = state.objs[i].name; + if (!has_obj_property(objname, "invisible") && + !has_obj_property(objname, "hidden")) { + vstring tmp; + + String print_name, temp_str; + if (!get_obj_property(objname, "alias", print_name)) + print_name = objname; + /* + if (get_obj_property (objname, "prefix", temp_str)) + print_name = temp_str + " " + print_name; + if (get_obj_property (objname, "suffix", temp_str)) + print_name = print_name + " " + temp_str; + */ + tmp.push_back(print_name); + + String otype; + if (!get_obj_property(objname, "displaytype", otype)) + otype = "object"; + tmp.push_back(otype); + rv.push_back(tmp); + } + } + return rv; +} + +vstring geas_implementation::get_status_vars() { + vstring rv; + + String tok, line; + uint c1, c2; + + for (uint i = 0; i < gf.size("variable"); i ++) { + const GeasBlock &gb = gf.block("variable", i); + + bool nozero = false; + String disp; + bool is_numeric = true; + + cerr << "g_s_v: " << gb << endl; + + for (uint j = 0; j < gb.data.size(); j ++) { + line = gb.data[j]; + cerr << " g_s_v: " << line << endl; + tok = first_token(line, c1, c2); + // SENSITIVE? + if (tok == "display") { + tok = next_token(line, c1, c2); + + // SENSITIVE? + if (tok == "nozero") { + nozero = true; + tok = next_token(line, c1, c2); + } + if (!is_param(tok)) + gi->debug_print("Expected param after display: " + line); + else + disp = tok; + } + // SENSITIVE? + else if (tok == "type") { + tok = next_token(line, c1, c2); + // SENSITIVE? + if (tok == "String") + is_numeric = false; + } + } + + cerr << " g_s_v, block 2, tok == '" << tok << "'" << endl; + if (!(is_numeric && nozero && get_ivar(gb.name) == 0) && disp != "") { + disp = param_contents(disp); + String outval = ""; + for (uint j = 0; j < disp.length(); j ++) + if (disp[j] == '!') { + if (is_numeric) + outval = outval + string_int(get_ivar(gb.name)); + //outval = outval + string_int (get_ivar (gb.lname)); + else + outval = outval + get_svar(gb.name); + //outval = outval + get_svar (gb.lname); + } else if (disp[j] == '*') { + uint k; + for (k = j + 1; k < disp.length() && disp[k] != '*'; k ++) + ; + //if (!is_numeric || get_ivar (gb.lname) != 1) + if (!is_numeric || get_ivar(gb.name) != 1) + outval = outval + disp.substr(j + 1, k - j - 1); + j = k; + } else + outval = outval + disp[j]; + rv.push_back(eval_string(outval)); + } + } + return rv; +} + +Common::Array<bool> geas_implementation::get_valid_exits() { + Common::Array<bool> rv; + cerr << "Getting valid exits\n"; + rv.push_back(exit_dest(state.location, "northwest") != ""); + rv.push_back(exit_dest(state.location, "north") != ""); + rv.push_back(exit_dest(state.location, "northeast") != ""); + rv.push_back(exit_dest(state.location, "west") != ""); + rv.push_back(exit_dest(state.location, "out") != ""); + rv.push_back(exit_dest(state.location, "east") != ""); + rv.push_back(exit_dest(state.location, "southwest") != ""); + rv.push_back(exit_dest(state.location, "south") != ""); + rv.push_back(exit_dest(state.location, "southeast") != ""); + rv.push_back(exit_dest(state.location, "up") != ""); + rv.push_back(exit_dest(state.location, "down") != ""); + cerr << "Done getting valid exits\n"; + + return rv; +} + +void geas_implementation::print_eval_p(String s) { + print_formatted(pcase(eval_string(s))); +} + +void geas_implementation::print_eval(String s) { + print_formatted(eval_string(s)); +} + +String geas_implementation::eval_string(String s) { + String rv; + uint i, j; + bool do_print = (s.find('$') != -1); + if (do_print) cerr << "eval_string (" << s << ")\n"; + for (i = 0; i < s.length(); i ++) { + //if (do_print) cerr << "e_s: i == " << i << ", s[i] == '" << s[i] << "'\n"; + if (i + 1 < s.length() && s[i] == '#' && s[i + 1] == '@') { + for (j = i + 1; j < s.length() && s[j] != '#'; j ++) + ; + if (j == s.length()) { + gi->debug_print("eval_string: Unmatched hash in " + s); + break; + } + //cerr << "dereferencing " + s.substr (i+2, j-i-2) << endl; + rv = rv + displayed_name(get_svar(s.substr(i + 2, j - i - 2))); + i = j; + } else if (s[i] == '#') { + for (j = i + 1; j < s.length() && s[j] != '#'; j ++) + ; + if (j == s.length()) { + gi->debug_print("eval_string: Unmatched hash in " + s); + break; + } + uint k; + for (k = i + 1; k < j && s[k] != ':'; k ++) + ; + if (k == j && j == i + 1) + rv += "#"; + else if (k == j) + rv += get_svar(s.substr(i + 1, j - i - 1)); + else { + String propname = s.substr(k + 1, j - k - 1); + if (s[i + 1] == '(') { + if (s[k - 1] != ')') { + gi->debug_print("e_s: Missing paren in '" + + s.substr(i, j - i) + "' of '" + s + "'"); + break; + } + String objvar = s.substr(i + 2, k - i - 3); + String objname = get_svar(objvar); + /* + cerr << "e_s: Getting prop [(" << objvar << ")] --> [" + << objname + << "]:[" << propname << "] for " << s << endl; + */ + String tmp; + if (get_obj_property(objname, propname, tmp)) + rv += tmp; + else + gi->debug_print("e_s: Evaluating nonexistent object prop " + "{" + objname + "}:{" + propname + "}"); + } else { + String objname = s.substr(i + 1, k - i - 1); + /* + cerr << "e_s: Getting prop [" << objname << "]:[" + << propname << "] for " << s << endl; + */ + String tmp; + if (get_obj_property(objname, propname, tmp)) + rv += tmp; + else + gi->debug_print("e_s: Evaluating nonexistent var " + objname); + } + } + i = j; + } else if (s[i] == '%') { + for (j = i + 1; j < s.length() && s[j] != '%'; j ++) + ; + if (j == s.length()) { + gi->debug_print("e_s: Unmatched %s in " + s); + break; + } + //cerr << "e_s: Getting ivar [" << s.substr (i+1, j-i-1) << "] for " << s << endl; + if (j == i + 1) + rv += "%"; + else + rv += string_int(get_ivar(s.substr(i + 1, j - i - 1))); + i = j; + } else if (s[i] == '$') { + j = s.find('$', i + 1); + /* + for (j = i + 1; j < s.length() && s[j] != '$'; j ++) + { + cerr << " In searching for partner, j == " << j + << ", s[j] == '" << s[j] << "', (s[j] == '$') == " + << ((s[j] == '$') ? "true" : "false") << "\n"; + } + */ + //if (j == rv.size()) + if (j == -1) { + gi->debug_print("Unmatched $s in " + s); + return rv + s.substr(i); + } + String tmp1 = s.substr(i + 1, j - i - 1); + cerr << "e_s: first substr was '" << tmp1 << "'\n"; + String tmp = eval_string(tmp1); + //String tmp = eval_string (s.substr (i + 1, j - i - 2)); + //cerr << "Taking substring of '" + s + "': '" + tmp + "'\n"; + cerr << "e_s: eval substr " + s + "': '" + tmp + "'\n"; + + String func_eval; + + int paren_open, paren_close; + if ((paren_open = tmp.find('(')) == -1) + func_eval = run_function(tmp); + else { + paren_close = tmp.find(')', paren_open); + if (paren_close == -1) + gi->debug_print("No matching right paren in " + tmp); + else { + String f_name = tmp.substr(0, paren_open); + String f_args = tmp.substr(paren_open + 1, + paren_close - paren_open - 1); + func_eval = run_function(f_name, split_f_args(f_args)); + } + } + rv = rv + func_eval; + i = j; + } else + rv += s[i]; + } + + + //cerr << "eval_string (" << s << ") -> <" << rv << ">\n"; + return rv; +} + +void geas_implementation::tick_timers() { + if (!state.running) + return; + //cerr << "tick_timers()\n"; + for (uint i = 0; i < state.timers.size(); i ++) { + TimerRecord &tr = state.timers[i]; + //cerr << " Examining " << tr << "\n"; + if (tr.is_running) { + //cerr << " Advancing " << tr.name << ": " << tr.timeleft + // << " / " << tr.interval << "\n"; + if (tr.timeleft != 0) + tr.timeleft --; + else { + tr.is_running = false; + tr.timeleft = tr.interval; + const GeasBlock *gb = gf.find_by_name("timer", tr.name); + if (gb != NULL) { + //cout << "Running it!\n"; + String tok, line; + uint c1, c2; + for (uint j = 0; j < gb->data.size(); j ++) { + line = gb->data[j]; + tok = first_token(line, c1, c2); + // SENSITIVE? + if (tok == "action") { + run_script(line.substr(c2)); + break; + } + } + } + } + } + } +} + +/*********************************** + * * + * * + * * + * GeasInterface related functions * + * * + * * + * * + ***********************************/ + + +GeasResult GeasInterface::print_formatted(String s, bool with_newline) { + unsigned int i, j; + + //cerr << "print_formatted (" << s << ", " << with_newline << ")" << endl; + + for (i = 0; i < s.length(); i ++) { + //std::cerr << "i == " << i << std::endl; + if (s[i] == '|') { + // changed indicated whether cur_style has been changed + // and update_style should be called at the end. + // it is true unless cleared (by |n or |w). + bool changed = true; + j = i; + i ++; + if (i == s.length()) + continue; + + // Are the | codes case-sensitive? + switch (s[i]) { + case 'u': + cur_style.is_underlined = true; + break; + case 'i': + cur_style.is_italic = true; + break; + case 'b': + cur_style.is_bold = true; + break; + case 'c': + i ++; + + if (i == s.length()) { + clear_screen(); + break; + } + + switch (s[i]) { + case 'y': + cur_style.color = "#ffff00"; + break; + case 'g': + cur_style.color = "#00ff00"; + break; + case 'l': + cur_style.color = "#0000ff"; + break; + case 'r': + cur_style.color = "#ff0000"; + break; + case 'b': + cur_style.color = ""; + break; + + default: + clear_screen(); + --i; + } + break; + + case 's': { + i ++; + if (i == s.length() || !(s[i] >= '0' && s[i] <= '9')) + continue; + i ++; + if (i == s.length() || !(s[i] >= '0' && s[i] <= '9')) + continue; + + int newsize = parse_int(s.substr(j, i - j)); + if (newsize > 0) + cur_style.size = newsize; + else + cur_style.size = default_size; + } + break; + + case 'j': + i ++; + + if (i == s.length() || + !(s[i] == 'l' || s[i] == 'c' || s[i] == 'r')) + continue; + if (s[i] == 'l') cur_style.justify = JUSTIFY_LEFT; + else if (s[i] == 'r') cur_style.justify = JUSTIFY_RIGHT; + else if (s[i] == 'c') cur_style.justify = JUSTIFY_CENTER; + break; + + case 'n': + print_newline(); + changed = false; + break; + + case 'w': + wait_keypress(""); + changed = false; + break; + + case 'x': + i ++; + + if (s[i] == 'b') + cur_style.is_bold = false; + else if (s[i] == 'u') + cur_style.is_underlined = false; + else if (s[i] == 'i') + cur_style.is_italic = false; + else if (s[i] == 'n' && i + 1 == s.length()) + changed = with_newline = false; + break; + + default: + cerr << "p_f: Fallthrough " << s[i] << endl; + changed = false; + } + if (changed) + update_style(); + } else { + for (j = i; i != s.length() && s[i] != '|'; i ++) + ; + print_normal(s.substr(j, i - j)); + if (s[i] == '|') + -- i; + } + } + if (with_newline) + print_newline(); + return r_success; +} + +void GeasInterface::set_default_font_size(String size) { + default_size = parse_int(size); +} + +void GeasInterface::set_default_font(String font) { + default_font = font; +} + +bool GeasInterface::choose_yes_no(String question) { + Common::Array<String> v; + v.push_back("Yes"); + v.push_back("No"); + return (make_choice(question, v) == 0); +} + +} // End of namespace Quest +} // End of namespace Glk diff --git a/engines/glk/quest/geas_runner.h b/engines/glk/quest/geas_runner.h new file mode 100644 index 0000000000..bc6d858d5b --- /dev/null +++ b/engines/glk/quest/geas_runner.h @@ -0,0 +1,220 @@ +/* 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_QUEST_GEAS_RUNNER +#define GLK_QUEST_GEAS_RUNNER + +#include "glk/quest/string.h" +#include "common/array.h" +#include "common/stream.h" + +namespace Glk { +namespace Quest { + +typedef Common::Array<String> vstring; +typedef Common::Array<vstring> v2string; + +enum geas_justification { JUSTIFY_LEFT, JUSTIFY_RIGHT, JUSTIFY_CENTER }; + +struct GeasFontStyle { + bool is_underlined, is_italic, is_bold; + String color, font; + int size; + geas_justification justify; + + GeasFontStyle() : is_underlined(false), is_italic(false), is_bold(false), + color(""), font(""), size(10), justify(JUSTIFY_LEFT) {} +}; + +class GeasFontStyleCompare { +public: + int operator()(const GeasFontStyle &a, const GeasFontStyle &b) { + if (a.size != b.size) return a.size < b.size; + if (a.is_underlined != b.is_underlined) return a.is_underlined; + if (a.is_bold != b.is_bold) return a.is_bold; + if (a.is_italic != b.is_italic) return a.is_italic; + if (a.color != b.color) return a.color < b.color; + if (a.justify != b.justify) return a.justify < b.justify; + return 0; + } +}; + +Common::WriteStream &operator<< (Common::WriteStream &o, const GeasFontStyle &gfs); + +enum GeasResult { + r_success, + r_failure, + r_not_supported +}; + +/* Callback object used to pass information from GeasCore + * to the interface objects + */ +class GeasInterface { +private: + GeasFontStyle cur_style; + String default_font; + int default_size; + //string fgcolor, bgcolor; + +public: + /* Takes 1 argument, a string with Quest markup + * Will output it to the user interface + * If the with_newline flag is set, it will print a newline afterwords + * unless the string ends in "|xn" + */ + GeasResult print_formatted(String s, bool with_newline = true); + + /* Takes one argument; that string is printed without interpretation + * Must be implemented + * Called by print_formatted and by Geas directly. + */ + virtual GeasResult print_normal(const String &s) = 0; + + virtual GeasResult print_newline() = 0; + +protected: + + void update_style() { + set_style(cur_style); + } + + /* Changes style of output text. + * Need not be implemented + * Only called by update_style() + */ + virtual GeasResult set_style(const GeasFontStyle &) { + return r_not_supported; + } + +public: + virtual String absolute_name(String rel_name, String parent) const = 0; + virtual String get_file(const String &filename) const = 0; + virtual void debug_print(const String &s) { + warning("%s", s.c_str()); + } + virtual GeasResult wait_keypress(String) { + return r_not_supported; + } + virtual GeasResult pause(int msec) { + return r_not_supported; + } + virtual GeasResult clear_screen() { + return r_not_supported; + } + + //virtual GeasResult set_foreground (string) { return r_not_supported; } + //virtual GeasResult set_background (string) { return r_not_supported; } + virtual void set_foreground(String) = 0; + virtual void set_background(String) = 0; + void set_default_font_size(String s); + void set_default_font(String s); + + /* Unsure what arguments this will take. + * May also add animated, persistent, close image + */ + virtual GeasResult show_image(String filename, String resolution, + String caption, ...) { + return r_not_supported; + } + + /* Again, unsure what arguments to give + * May add sound type + * If sync is true, do not return until file ends + * If filename is "", stop playing sounds. + */ + virtual GeasResult play_sound(String filename, bool looped, bool sync) { + return r_not_supported; + } + + /* Asks the user to type a free format string + */ + virtual String get_string() = 0; + + /* Presents a list with header 'info', and prompts the user to + * choose one item from 'choices'. + * returns the index chosen. + */ + virtual uint make_choice(String info, Common::Array<String> choices) = 0; + + /* Asks the user a yes/no question + * (If not overridden, this has an implementation that uses make_choice() + */ + virtual bool choose_yes_no(String question); + + /* args holds arguments sent to program. + * if active is true, geas should retain focus + * returns - 0 if disallowed + * - 1 if succeeded + * - 2 if file not found + * - 3 if it couldn't find a program to run it + * - 4 if it ran out of memory + */ + virtual int shell(Common::Array<String> args, bool active) { + return 0; + } + + /* say the argument using text-to-speech + */ + virtual GeasResult speak(String) { + return r_not_supported; + } + + virtual ~GeasInterface() {} + + /* This is a notification that some object has changed, and + * the interpreter may want to update the inventory or room object + * listings. + */ + virtual void update_sidebars() { } +}; + + +/* Callback for passing information from the UI to the execution core + */ +class GeasRunner { +protected: + GeasInterface *gi; + +public: + GeasRunner(GeasInterface *_gi) : gi(_gi) {} + + virtual bool is_running() const = 0; + virtual String get_banner() = 0; + virtual void run_command(String) = 0; + + virtual v2string get_inventory() = 0; + virtual v2string get_room_contents() = 0; + virtual vstring get_status_vars() = 0; + virtual Common::Array<bool> get_valid_exits() = 0; + + virtual void tick_timers() = 0; + + virtual ~GeasRunner() { } + virtual void set_game(const String &fname) = 0; + static GeasRunner *get_runner(GeasInterface *gi); +}; + +} // End of namespace Quest +} // End of namespace Glk + +#endif diff --git a/engines/glk/quest/geas_state.cpp b/engines/glk/quest/geas_state.cpp new file mode 100644 index 0000000000..985b4e935e --- /dev/null +++ b/engines/glk/quest/geas_state.cpp @@ -0,0 +1,356 @@ +/* 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/quest/geas_state.h" +#include "glk/quest/geas_runner.h" +#include "glk/quest/geas_util.h" +#include "glk/quest/read_file.h" +#include "glk/quest/streams.h" + +namespace Glk { +namespace Quest { + +Common::WriteStream &operator<< (Common::WriteStream &o, const StringMap &m) { + for (StringMap::iterator i = m.begin(); i != m.end(); ++i) + o << (*i)._key << " -> " << (*i)._value << "\n"; + + return o; +} + +class GeasOutputStream { + Common::WriteStream *_ws; +public: + GeasOutputStream &put(const String &s) { + _ws->writeString(s); + _ws->writeByte(0); + return *this; + } + GeasOutputStream &put(char ch) { + _ws->writeByte(ch); + return *this; + } + GeasOutputStream &put(int i) { + Common::String s = Common::String::format("%d", i); + _ws->writeString(s); + _ws->writeByte(0); + return *this; + } + GeasOutputStream &put(uint i) { + Common::String s = Common::String::format("%u", i); + _ws->writeString(s); + _ws->writeByte(0); + return *this; + } + GeasOutputStream &put(unsigned long i) { + Common::String s = Common::String::format("%lu", i); + _ws->writeString(s); + _ws->writeByte(0); + return *this; + } + + void write_out(const String &gameName, const String &saveName) { +#ifdef TODO + ofstream ofs; + ofs.open(savename.c_str()); + if (!ofs.is_open()) + throw String("Unable to open \"" + savename + "\""); + ofs << "QUEST300" << char(0) << gamename << char(0); + String tmp = o.str(); + for (uint i = 0; i < tmp.size(); i ++) + ofs << char (255 - tmp[i]); + cerr << "Done writing save game\n"; +#else + error("TODO"); +#endif + } +}; + +template <class T> void write_to(GeasOutputStream &gos, const Common::Array<T> &v) { + gos.put(v.size()); + for (uint i = 0; i < v.size(); i ++) + write_to(gos, v[i]); +} + +void write_to(GeasOutputStream &gos, const PropertyRecord &pr) { + gos.put(pr.name).put(pr.data); +} + +void write_to(GeasOutputStream &gos, const ObjectRecord &pr) { + gos.put(pr.name).put(char(pr.hidden ? 0 : 1)) + .put(char(pr.invisible ? 0 : 1)).put(pr.parent); +} + +void write_to(GeasOutputStream &gos, const ExitRecord &er) { + gos.put(er.src).put(er.dest); +} + +void write_to(GeasOutputStream &gos, const TimerRecord &tr) { + gos.put(tr.name).put(tr.is_running ? 0 : 1).put(tr.interval) + .put(tr.timeleft); +} + + +void write_to(GeasOutputStream &gos, const SVarRecord &svr) { + gos.put(svr.name); + gos.put(svr.max()); + for (uint i = 0; i < svr.size(); i ++) + gos.put(svr.get(i)); +} + +void write_to(GeasOutputStream &gos, const IVarRecord &ivr) { + gos.put(ivr.name); + gos.put(ivr.max()); + for (uint i = 0; i < ivr.size(); i ++) + gos.put(ivr.get(i)); +} + +void write_to(GeasOutputStream &gos, const GeasState &gs) { + gos.put(gs.location); + write_to(gos, gs.props); + write_to(gos, gs.objs); + write_to(gos, gs.exits); + write_to(gos, gs.timers); + write_to(gos, gs.svars); + write_to(gos, gs.ivars); +} + +void save_game_to(String gamename, String savename, const GeasState &gs) { + GeasOutputStream gos; + write_to(gos, gs); + gos.write_out(gamename, savename); +} + +GeasState::GeasState(GeasInterface &gi, const GeasFile &gf) { + running = false; + + cerr << "GeasState::GeasState()" << endl; + for (uint i = 0; i < gf.size("game"); i ++) { + //const GeasBlock &go = gf.game[i]; + //register_block ("game", "game"); + ObjectRecord data; + data.name = "game"; + data.parent = ""; + data.hidden = false; + data.invisible = true; + objs.push_back(data); + } + + cerr << "GeasState::GeasState() done setting game" << endl; + for (uint i = 0; i < gf.size("room"); i ++) { + const GeasBlock &go = gf.block("room", i); + ObjectRecord data; + //data.name = go.lname; + data.name = go.name; + data.parent = ""; + data.hidden = data.invisible = true; + //register_block (data.name, "room"); + objs.push_back(data); + } + + cerr << "GeasState::GeasState() done setting rooms" << endl; + for (uint i = 0; i < gf.size("object"); i++) { + const GeasBlock &go = gf.block("object", i); + ObjectRecord data; + //data.name = go.lname; + data.name = go.name; + if (go.parent == "") + data.parent = ""; + else + //data.parent = lcase (param_contents (go.parent)); + data.parent = param_contents(go.parent); + //register_block (data.name, "object"); + data.hidden = data.invisible = false; + objs.push_back(data); + } + + cerr << "GeasState::GeasState() done setting objects" << endl; + for (uint i = 0; i < gf.size("timer"); i ++) { + const GeasBlock &go = gf.block("timer", i); + //cerr << "GS::GS: Handling timer " << go << "\n"; + TimerRecord tr; + String interval = "", status = ""; + for (uint j = 0; j < go.data.size(); j ++) { + String line = go.data[j]; + uint c1, c2; + String tok = first_token(line, c1, c2); + if (tok == "interval") { + tok = next_token(line, c1, c2); + if (!is_param(tok)) + gi.debug_print(nonparam("interval", line)); + else + interval = param_contents(tok); + } else if (tok == "enabled" || tok == "disabled") { + if (status != "") + gi.debug_print("Repeated status for timer"); + else + status = tok; + } else if (tok == "action") { + } else { + gi.debug_print("Bad timer line " + line); + } + } + //tr.name = go.lname; + tr.name = go.name; + tr.is_running = (status == "enabled"); + tr.interval = tr.timeleft = parse_int(interval); + //register_block (tr.name, "timer"); + timers.push_back(tr); + } + + cerr << "GeasState::GeasState() done with timers" << endl; + for (uint i = 0; i < gf.size("variable"); i ++) { + const GeasBlock &go(gf.block("variable", i)); + cerr << "GS::GS: Handling variable #" << i << ": " << go << endl; + String vartype; + String value; + for (uint j = 0; j < go.data.size(); j ++) { + String line = go.data[j]; + cerr << " Line #" << j << " of var: \"" << line << "\"" << endl; + uint c1, c2; + String tok = first_token(line, c1, c2); + if (tok == "type") { + tok = next_token(line, c1, c2); + if (tok == "") + gi.debug_print(String("Missing variable type in ") + + string_geas_block(go)); + else if (vartype != "") + gi.debug_print(String("Redefining var. type in ") + + string_geas_block(go)); + else if (tok == "numeric" || tok == "String") + vartype = tok; + else + gi.debug_print(String("Bad var. type ") + line); + } else if (tok == "value") { + tok = next_token(line, c1, c2); + if (!is_param(tok)) + gi.debug_print(String("Expected parameter in " + line)); + else + value = param_contents(tok); + } else if (tok == "display" || tok == "onchange") { + } else { + gi.debug_print(String("Bad var. line: ") + line); + } + } + if (vartype == "" || vartype == "numeric") { + IVarRecord ivr; + //ivr.name = go.lname; + ivr.name = go.name; + ivr.set(0, parse_int(value)); + ivars.push_back(ivr); + //register_block (ivr.name, "numeric"); + } else { + SVarRecord svr; + //svr.name = go.lname; + svr.name = go.name; + svr.set(0, value); + svars.push_back(svr); + //register_block (svr.name, "String"); + } + } + //cerr << obj_types << endl; + cerr << "GeasState::GeasState() done with variables" << endl; +} + +Common::WriteStream &operator<< (Common::WriteStream &o, const PropertyRecord &pr) { + o << pr.name << ", data == " << pr.data; + return o; +} + +Common::WriteStream &operator<< (Common::WriteStream &o, const ObjectRecord &objr) { + o << objr.name << ", parent == " << objr.parent; + if (objr.hidden) + o << ", hidden"; + if (objr.invisible) + o << ", invisible"; + return o; +} + +Common::WriteStream &operator<< (Common::WriteStream &o, const ExitRecord er) { + return o << er.src << ": " << er.dest; +} + +Common::WriteStream &operator<< (Common::WriteStream &o, const TimerRecord &tr) { + return o << tr.name << ": " << (tr.is_running ? "" : "not ") << "running (" + << tr.timeleft << " // " << tr.interval << ")"; +} + +Common::WriteStream &operator<< (Common::WriteStream &o, const SVarRecord &sr) { + o << sr.name << ": "; + if (sr.size() == 0) + o << "(empty)"; + else if (sr.size() <= 1) + o << "<" << sr.get(0) << ">"; + else + for (uint i = 0; i < sr.size(); i ++) { + o << i << ": <" << sr.get(i) << ">"; + if (i + 1 < sr.size()) + o << ", "; + } + return o; +} + +Common::WriteStream &operator<< (Common::WriteStream &o, const IVarRecord &ir) { + o << ir.name << ": "; + if (ir.size() == 0) + o << "(empty)"; + else if (ir.size() <= 1) + o << ir.get(0); + else + for (uint i = 0; i < ir.size(); i ++) { + o << i << ": " << ir.get(i); + if (i + 1 < ir.size()) + o << ", "; + } + return o; +} + +Common::WriteStream &operator<< (Common::WriteStream &o, const GeasState &gs) { + o << "location == " << gs.location << "\nprops: \n"; + + for (uint i = 0; i < gs.props.size(); i ++) + o << " " << i << ": " << gs.props[i] << "\n"; + + o << "objs:\n"; + for (uint i = 0; i < gs.objs.size(); i ++) + o << " " << i << ": " << gs.objs[i] << "\n"; + + o << "exits:\n"; + for (uint i = 0; i < gs.exits.size(); i ++) + o << " " << i << ": " << gs.exits[i] << "\n"; + + o << "timers:\n"; + for (uint i = 0; i < gs.timers.size(); i ++) + o << " " << i << ": " << gs.timers[i] << "\n"; + + o << "String variables:\n"; + for (uint i = 0; i < gs.svars.size(); i ++) + o << " " << i << ": " << gs.svars[i] << "\n"; + + o << "integer variables:\n"; + for (uint i = 0; i < gs.svars.size(); i ++) + o << " " << i << ": " << gs.svars[i] << "\n"; + + return o; +} + +} // End of namespace Quest +} // End of namespace Glk diff --git a/engines/glk/quest/geas_state.h b/engines/glk/quest/geas_state.h new file mode 100644 index 0000000000..70365f1fd8 --- /dev/null +++ b/engines/glk/quest/geas_state.h @@ -0,0 +1,171 @@ +/* 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_QUEST_GEAS_STATE +#define GLK_QUEST_GEAS_STATE + +#include "glk/quest/string.h" +#include "common/array.h" +#include "common/stream.h" + +namespace Glk { +namespace Quest { + +struct PropertyRecord { + String name, data; + PropertyRecord(String in_name, String in_data) : name(in_name), data(in_data) {} +}; + +struct ObjectRecord { + String name, parent; + bool hidden, invisible; + + //ObjectRecord (String in_name, String in_parent) : name (in_name), parent (in_parent), hidden (false), concealed (false) {} + +}; + +struct ExitRecord { + String src, dest; + ExitRecord(String in_src, String in_dest) : src(in_src), dest(in_dest) {} +}; + +struct TimerRecord { + String name; + bool is_running; + uint interval, timeleft; +}; + +struct SVarRecord { +private: + Common::Array<String> data; +public: + String name; + + SVarRecord() {} + SVarRecord(String in_name) : name(in_name) { + set(0, ""); + } + uint size() const { + return data.size(); + } + uint max() const { + return size() - 1; + } + void set(uint i, String val) { + if (i >= size()) data.resize(i + 1); + data[i] = val; + } + String get(uint i) const { + if (i < size()) return data[i]; + return "!"; + } + void set(String val) { + data[0] = val; + } + String get() const { + return data[0]; + } +}; + +struct IVarRecord { +private: + Common::Array<int> data; +public: + String name; + + IVarRecord() {} + IVarRecord(String in_name) : name(in_name) { + set(0, 0); + } + uint size() const { + return data.size(); + } + uint max() const { + return size() - 1; + } + void set(uint i, int val) { + if (i >= size()) data.resize(i + 1); + data[i] = val; + } + int get(uint i) const { + if (i < size()) return data[i]; + else return -32767; + } + void set(int val) { + data[0] = val; + } + int get() const { + return data[0]; + } +}; + +struct GeasFile; +class GeasInterface; + +struct GeasState { + //private: + //std::auto_ptr<GeasFile> gf; + +public: + bool running; + String location; + Common::Array<PropertyRecord> props; + Common::Array<ObjectRecord> objs; + Common::Array<ExitRecord> exits; + Common::Array<TimerRecord> timers; + Common::Array<SVarRecord> svars; + Common::Array<IVarRecord> ivars; + //std::map <String, String> obj_types; + + //void register_block (String blockname, String blocktype); + + GeasState() {} + //GeasState (GeasRunner &, const GeasFile &); + GeasState(GeasInterface &, const GeasFile &); + /* + bool has_svar (string s) { for (uint i = 0; i < svars.size(); i ++) if (svars[i].name == s) return true; } + uint find_svar (string s) { for (uint i = 0; i < svars.size(); i ++) if (svars[i].name == s) return i; svars.push_back (SVarRecord (s)); return svars.size() - 1;} + string get_svar (string s, uint index) { if (!has_svar(s)) return "!"; return svars[find_svar(s)].get(index); } + string get_svar (string s) { return get_svar (s, 0); } + + bool has_ivar (string s) { for (uint i = 0; i < ivars.size(); i ++) if (ivars[i].name == s) return true; } + uint find_ivar (string s) { for (uint i = 0; i < ivars.size(); i ++) if (ivars[i].name == s) return i; ivars.push_back (IVarRecord (s)); return ivars.size() - 1;} + int get_ivar (string s, uint index) { if (!has_ivar(s)) return -32767; return ivars[find_ivar(s)].get(index); } + int get_ivar (string s) { return get_ivar (s, 0); } + */ +}; + +void save_game_to(String gamename, String savename, const GeasState &gs); + +Common::WriteStream &operator<< (Common::WriteStream &o, const StringMap &m); +Common::WriteStream &operator<< (Common::WriteStream &o, const PropertyRecord &pr); +Common::WriteStream &operator<< (Common::WriteStream &o, const ObjectRecord &objr); +Common::WriteStream &operator<< (Common::WriteStream &o, const ExitRecord er); +Common::WriteStream &operator<< (Common::WriteStream &o, const TimerRecord &tr); +Common::WriteStream &operator<< (Common::WriteStream &o, const SVarRecord &sr); +Common::WriteStream &operator<< (Common::WriteStream &o, const IVarRecord &ir); +Common::WriteStream &operator<< (Common::WriteStream &o, const GeasState &gs); + +} // End of namespace Quest +} // End of namespace Glk + +#endif diff --git a/engines/glk/quest/geas_util.cpp b/engines/glk/quest/geas_util.cpp new file mode 100644 index 0000000000..bc5eaac177 --- /dev/null +++ b/engines/glk/quest/geas_util.cpp @@ -0,0 +1,245 @@ +/* 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/quest/geas_util.h" +#include "glk/quest/geas_file.h" +#include "glk/quest/streams.h" +#include "glk/quest/string.h" + +namespace Glk { +namespace Quest { + +int eval_int(String s) { + cerr << "eval_int (" << s << ")" << endl; + + uint index = 0, index2; + String tmp; + while (index < s.length() && Common::isSpace(s[index])) { + cerr << " index == " << index << endl; + index ++; + } + if (index == s.length() || !Common::isDigit(s[index])) { + cerr << "Failed to match, returning 0" << endl; + return 0; + } + for (index2 = index; index2 < s.length() && Common::isDigit(s[index2]); index2 ++) { + cerr << " index2 == " << index2 << endl; + } + //; + tmp = s.substr(index, index2 - index); + cerr << "tmp == < " << tmp << ">" << endl; + + //cerr << "index == " << index << ", index2 == " << index2 + // << ", tmp == " << tmp << endl; + + int arg1 = atoi(tmp.c_str()); + cerr << "arg1 == " << arg1 << endl; + index = index2; + while (index < s.length() && Common::isSpace(s[index])) + ++ index; + if (index == s.length()) + return arg1; + + //cerr << "index == " << index << ", s.length() == " << s.length() << endl; + + char symbol = s[index]; + + //cerr << "symbol == " << symbol << "; find --> " + // << String("+-*/").find (symbol) << endl; + + if (String("+-*/").find(symbol) == String::npos) + return arg1; + + ++ index; + while (index < s.length() && Common::isSpace(s[index])) + ++ index; + if (index == s.length() || ! Common::isDigit(s[index])) { + if (symbol == '*') + return 0; + return arg1; + } + index2 = index + 1; + while (index2 < s.length() && Common::isDigit(s[index2])) + ++ index2; + tmp = s.substr(index, index2 - index); + int arg2 = atoi(tmp.c_str()); + + switch (symbol) { + case '+': + return arg1 + arg2; + case '-': + return arg1 - arg2; + case '*': + return arg1 * arg2; + case '/': + return arg1 / arg2; + // TODO: division should use accountant's round + } + return 0; +} + +String trim_braces(String s) { + if (s.length() > 1 && s[0] == '[' && s[s.length() - 1] == ']') + return s.substr(1, s.length() - 2); + else + return s; +} + +bool is_param(String s) { + return s.length() > 1 && s[0] == '<' && s[s.length() - 1] == '>'; +} + +String param_contents(String s) { + //cerr << "param_contents (" << s << ")" << endl; + assert(is_param(s)); + return s.substr(1, s.length() - 2); +} + +String nonparam(String type, String var) { + return "Non-parameter for " + type + " in \"" + var + "\""; +} + +//ostream &operator << (ostream &o, const GeasBlock &gb) { return o; } +//String trim (String s, trim_modes) { return s; } + +String string_geas_block(const GeasBlock &gb) { + ostringstream oss; + oss << gb; // temporary removed TODO + return oss.str(); +} + + +bool starts_with(String a, String b) { + return (a.length() >= b.length()) && (a.substr(0, b.length()) == b); +} +bool ends_with(String a, String b) { + return (a.length() >= b.length()) && + (a.substr(a.length() - b.length(), b.length()) == b); +} + +bool starts_with_i(String a, String b) { + return (a.length() >= b.length()) && ci_equal(a.substr(0, b.length()), b); + // return starts_with (lcase(a), lcase(b)); +} +bool ends_with_i(String a, String b) { + return (a.length() >= b.length()) && + ci_equal(a.substr(a.length() - b.length(), b.length()), b); + //return ends_with (lcase(a), lcase(b)); +} + +String pcase(String s) { + if (s.length() == 0) + return s; + if (Common::isLower(s[0])) + s[0] = toupper(s[0]); + return s; +} + +String ucase(String s) { + for (uint i = 0; i < s.length(); i ++) + s[i] = toupper(s[i]); + return s; +} + +// There's a good chance s is already all-lowercase, in which case +// the test will avoid making a copy +String lcase(String s) { + for (uint i = 0; i < s.length(); i ++) + if (Common::isUpper(s[i])) + s[i] = tolower(s[i]); + return s; +} + +Common::Array<String> split_param(String s) { + Common::Array<String> rv; + uint c1 = 0, c2; + + for (;;) { + c2 = s.find(';', c1); + if (c2 == -1) { + rv.push_back(s.substr(c1).trim()); + return rv; + } + rv.push_back(s.substr(c1, c2 - c1).trim()); + c1 = c2 + 1; + } +} + +Common::Array<String> split_f_args(String s) { + Common::Array<String> rv = split_param(s); + for (uint i = 0; i < rv.size(); i ++) { + String tmp = rv[i]; + if (tmp[0] == '_') + rv[i][0] = ' '; + if (tmp[tmp.length() - 1] == '_') + rv[i][tmp.length() - 1] = ' '; + } + return rv; +} + +void show_split(String s) { + Common::Array<String> tmp = split_param(s); + cerr << "Splitting <" << s << ">: "; + for (uint i = 0; i < tmp.size(); i ++) + cerr << "<" << tmp[i] << ">, "; + cerr << "\n"; +} + +Logger::Nullstreambuf Logger::cnull; + +Logger::Logger() : logfilestr_(NULL) { //, cerrbuf_(NULL) { +/* + cerr.flush(); + + const char *const logfile = getenv("GEAS_LOGFILE"); + if (logfile) { + ofstream *filestr = new ofstream(logfile); + if (filestr->fail()) + delete filestr; + else { + logfilestr_ = filestr; + cerrbuf_ = cerr.rdbuf(filestr->rdbuf()); + } + } + + if (!cerrbuf_) + cerrbuf_ = cerr.rdbuf(&cnull); + */ +} + +Logger::~Logger() { + /* + cerr.flush(); + + cerr.rdbuf(cerrbuf_); + cerrbuf_ = NULL; + + if (logfilestr_) { + logfilestr_->close(); + delete logfilestr_; + logfilestr_ = NULL; + } + */ +} + +} // End of namespace Quest +} // End of namespace Glk diff --git a/engines/glk/quest/geas_util.h b/engines/glk/quest/geas_util.h new file mode 100644 index 0000000000..2cedadde02 --- /dev/null +++ b/engines/glk/quest/geas_util.h @@ -0,0 +1,97 @@ +/* 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_QUEST_GEAS_UTIL +#define GLK_QUEST_GEAS_UTIL + +#include "glk/quest/read_file.h" +#include "common/stream.h" + +namespace Glk { +namespace Quest { + +typedef Common::Array<String> vstring; + +inline int parse_int(const String &s) { + return atoi(s.c_str()); +} + +vstring split_param(String s); +vstring split_f_args(String s); + +bool is_param(String s); +String param_contents(String s); + +String nonparam(String, String); + +String string_geas_block(const GeasBlock &); + +bool starts_with(String, String); +bool ends_with(String, String); + +String string_int(int i); + +String trim_braces(String s); + +int eval_int(String s); + +String pcase(String s); +String ucase(String s); +String lcase(String s); + + +template<class T> Common::WriteStream &operator<<(Common::WriteStream &o, Common::Array<T> v) { + o << "{ '"; + for (uint i = 0; i < v.size(); i ++) { + o << v[i]; + if (i + 1 < v.size()) + o << "', '"; + } + o << "' }"; + return o; +} + +template <class KEYTYPE, class VALTYPE> +bool has(Common::HashMap<KEYTYPE, VALTYPE, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> m, KEYTYPE key) { + return m.contains(key); +}; + +class Logger { +public: + Logger(); + ~Logger(); + +private: + class Nullstreambuf : public Common::WriteStream { + virtual uint32 write(const void *dataPtr, uint32 dataSize) { return dataSize; } + virtual int32 pos() const { return 0; } + }; + + Common::WriteStream *logfilestr_; +// std::streambuf *cerrbuf_; + static Nullstreambuf cnull; +}; + +} // End of namespace Quest +} // End of namespace Glk + +#endif diff --git a/engines/glk/quest/limit_stack.h b/engines/glk/quest/limit_stack.h new file mode 100644 index 0000000000..75cad4401d --- /dev/null +++ b/engines/glk/quest/limit_stack.h @@ -0,0 +1,103 @@ +/* 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_QUEST_LIMIT_STACK +#define GLK_QUEST_LIMIT_STACK + +namespace Glk { +namespace Quest { + +template <class T> class LimitStack { + uint stack_size, cur_ptr, end_ptr; + Common::Array<T> data; + //bool last_push; + + + uint dofwd(uint i) const { + i ++; + return i == stack_size ? 0 : i; + } + uint dobwd(uint i) const { + return (i == 0 ? stack_size : i) - 1; + } + void fwd(uint &i) const { + i = dofwd(i); + } + void bwd(uint &i) const { + i = dobwd(i); + } + + /* + void fwd (uint &i) { i ++; if (i == stack_size) i = 0; } + void bwd (uint &i) { i = (i == 0 ? stack_size : i) - 1; } + uint dofwd (uint i) { uint rv = i; fwd(rv); return rv; } + uint dobwd (uint i) { uint rv = i; bwd(rv); return rv; } + */ + +public: + LimitStack(uint size) : stack_size(size), cur_ptr(0), end_ptr(size - 1), data(Common::Array<T> (size)) { } + + void push(T &item) { + if (cur_ptr == end_ptr) + fwd(end_ptr); + data[cur_ptr] = item; + fwd(cur_ptr); + } + + T &pop() { + assert(!is_empty()); + bwd(cur_ptr); + return data[cur_ptr]; + } + + bool is_empty() { + return dobwd(cur_ptr) == end_ptr; + } + + uint size() { + if (cur_ptr > end_ptr) + return cur_ptr - end_ptr - 1; + else + return (cur_ptr + stack_size) - end_ptr - 1; + } + + void dump(Common::WriteStream &o) { + o << size() << ": < "; + for (uint i = dobwd(cur_ptr); i != end_ptr; bwd(i)) + o << data[i] << " "; + o << ">"; + } + + T &peek() { + return data[dobwd(cur_ptr)]; + } +}; + +template<class T> Common::WriteStream &operator<< (Common::WriteStream &o, LimitStack<T> st) { + st.dump(o); + return o; +} + +} // End of namespace Quest +} // End of namespace Glk + +#endif diff --git a/engines/glk/quest/quest.cpp b/engines/glk/quest/quest.cpp new file mode 100644 index 0000000000..93de43f6d7 --- /dev/null +++ b/engines/glk/quest/quest.cpp @@ -0,0 +1,149 @@ +/* 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/quest/quest.h" +#include "glk/quest/geas_glk.h" +#include "glk/quest/geas_glk.h" +#include "glk/quest/streams.h" +#include "common/config-manager.h" +#include "common/translation.h" + +namespace Glk { +namespace Quest { + +Quest *g_vm; + +void Quest::runGame() { + // Check for savegame + _saveSlot = ConfMan.hasKey("save_slot") ? ConfMan.getInt("save_slot") : -1; + + if (!initialize()) { + GUIErrorMessage(_("Could not start Quest game")); + return; + } + + playGame(); + + deinitialize(); +} + +void Quest::playGame() { + char cur_buf[1024]; + char buf[200]; + + GeasRunner *gr = GeasRunner::get_runner(new GeasGlkInterface()); + gr->set_game(String(getFilename().c_str())); + banner = gr->get_banner(); + draw_banner(); + + while (gr->is_running()) { + if (inputwin != mainglkwin) + glk_window_clear(inputwin); + else + glk_put_cstring("\n"); + + sprintf(cur_buf, "> "); + glk_put_string_stream(inputwinstream, cur_buf); + + glk_request_line_event(inputwin, buf, (sizeof buf) - 1, 0); + + event_t ev; + ev.type = evtype_None; + + while (ev.type != evtype_LineInput) { + glk_select(&ev); + + switch (ev.type) { + case evtype_LineInput: + if (ev.window == inputwin) { + String cmd = String(buf, ev.val1); + if (inputwin == mainglkwin) + ignore_lines = 2; + gr->run_command(cmd); + } + break; + + case evtype_Timer: + gr->tick_timers(); + break; + + case evtype_Arrange: + case evtype_Redraw: + draw_banner(); + break; + } + } + } +} + +bool Quest::initialize() { + glk_stylehint_set(wintype_TextBuffer, style_User2, stylehint_ReverseColor, 1); + + // Open the main window + mainglkwin = glk_window_open(0, 0, 0, wintype_TextBuffer, 1); + if (!mainglkwin) + return false; + glk_set_window(mainglkwin); + + glk_stylehint_set(wintype_TextGrid, style_User1, stylehint_ReverseColor, 1); + bannerwin = glk_window_open(mainglkwin, + winmethod_Above | winmethod_Fixed, + 1, wintype_TextGrid, 0); + + if (use_inputwindow) + inputwin = glk_window_open(mainglkwin, + winmethod_Below | winmethod_Fixed, + 1, wintype_TextBuffer, 0); + else + inputwin = NULL; + + if (!inputwin) + inputwin = mainglkwin; + + inputwinstream = glk_window_get_stream(inputwin); + + if (!glk_gestalt(gestalt_Timer, 0)) { + const char *err = "\nNote -- The underlying Glk library does not support" + " timers. If this game tries to use timers, then some" + " functionality may not work correctly.\n\n"; + glk_put_string(err); + } + + glk_request_timer_events(1000); + ignore_lines = 0; + + return true; +} + +void Quest::deinitialize() { +} + +Common::Error Quest::readSaveData(Common::SeekableReadStream *rs) { + return Common::kNoError; +} + +Common::Error Quest::writeGameData(Common::WriteStream *ws) { + return Common::kNoError; +} + +} // End of namespace Quest +} // End of namespace Glk diff --git a/engines/glk/quest/quest.h b/engines/glk/quest/quest.h new file mode 100644 index 0000000000..69aa2db094 --- /dev/null +++ b/engines/glk/quest/quest.h @@ -0,0 +1,92 @@ +/* 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_QUEST_QUEST +#define GLK_QUEST_QUEST + +#include "glk/glk_api.h" +#include "glk/quest/string.h" + +namespace Glk { +namespace Quest { + +/** + * Quest game interpreter + */ +class Quest : public GlkAPI { +private: + int _saveSlot; +public: + String banner; +private: + /** + * Engine initialization + */ + bool initialize(); + + /** + * Engine cleanup + */ + void deinitialize(); + + /** + * Inner gameplay method + */ + void playGame(); +public: + /** + * Constructor + */ + Quest(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc), _saveSlot(-1) { + g_vm = this; + } + + /** + * Run the game + */ + virtual void runGame() override; + + /** + * Returns the running interpreter type + */ + virtual InterpreterType getInterpreterType() const override { + return INTERPRETER_QUEST; + } + + /** + * Load a savegame from the passed Quetzal file chunk stream + */ + virtual Common::Error readSaveData(Common::SeekableReadStream *rs) override; + + /** + * Save the game. The passed write stream represents access to the UMem chunk + * in the Quetzal save file that will be created + */ + virtual Common::Error writeGameData(Common::WriteStream *ws) override; +}; + +extern Quest *g_vm; + +} // End of namespace Quest +} // End of namespace Glk + +#endif diff --git a/engines/glk/quest/read_file.cpp b/engines/glk/quest/read_file.cpp new file mode 100644 index 0000000000..c749bb2718 --- /dev/null +++ b/engines/glk/quest/read_file.cpp @@ -0,0 +1,1043 @@ +/* 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/quest/read_file.h" +#include "glk/quest/geas_util.h" +#include "glk/quest/reserved_words.h" +#include "glk/quest/geas_runner.h" +#include "glk/quest/streams.h" +#include "common/util.h" + +namespace Glk { +namespace Quest { + +String next_token(String full, uint &tok_start, uint &tok_end, bool cvt_paren) { + tok_start = tok_end; + while (tok_start < full.size() && Common::isSpace(full[tok_start])) + ++ tok_start; + if (tok_start >= full.size()) { + tok_start = tok_end = full.size(); + //tok_start = tok_end = String::npos; + return ""; + } + tok_end = tok_start + 1; + if (full[tok_start] == '{' || full[tok_start] == '}') + /* brace is a token by itself */; + else if (full[tok_start] == '<') { + while (tok_end < full.size() && full [tok_end] != '>') + ++ tok_end; + if (full[tok_end] == '>') + ++ tok_end; + } else if (cvt_paren && full[tok_start] == '(') { + uint depth = 1; + /* + while (tok_end < full.size() && full [tok_end] != ')') + ++ tok_end; + if (full[tok_end] == ')') + ++ tok_end; + */ + do { + if (full[tok_end] == '(') + ++ depth; + else if (full[tok_end] == ')') + -- depth; + ++ tok_end; + } while (tok_end < full.size() && depth > 0); + } else + while (tok_end < full.size() && !Common::isSpace(full[tok_end])) + ++ tok_end; + return full.substr(tok_start, tok_end - tok_start); +} + +String first_token(String s, uint &t_start, uint &t_end) { + t_end = 0; + return next_token(s, t_start, t_end); +} + +String nth_token(String s, int n) { + uint x1, x2 = 0; + String rv; + do + rv = next_token(s, x1, x2); + while (-- n > 0); + return rv; +} + +String get_token(String s, bool cvt_paren) { + uint x1, x2 = 0; + return next_token(s, x1, x2, cvt_paren); +} + +bool find_token(String s, String tok, int &tok_start, int &tok_end, bool cvt_paren) { + uint copy_start, copy_end; + copy_end = tok_end; + + do { + String tmp = next_token(s, copy_start, copy_end, cvt_paren); + if (tmp == tok) { + tok_start = copy_start; + tok_end = copy_end; + return true; + } + } while (copy_end < s.size()); + + return false; +} + +bool is_define(String s) { + return get_token(s) == "define"; +} + +bool is_define(String s, String t) { + uint t1, t2 = 0; + return next_token(s, t1, t2) == "define" && + next_token(s, t1, t2) == t; +} + +bool is_start_textmode(String s) { + uint start_char, end_char = 0; + if (next_token(s, start_char, end_char) != "define") return false; + String tmp = next_token(s, start_char, end_char); + // SENSITIVE? + return tmp == "text" || tmp == "synonyms"; +} + +bool is_end_define(String s) { + uint start_char, end_char = 0; + // SENSITIVE? + return (next_token(s, start_char, end_char) == "end" && + next_token(s, start_char, end_char) == "define"); +} + + +extern Common::Array<String> split_lines(String data); + +reserved_words dir_tag_property("north", "south", "east", "west", "northwest", "northeast", "southeast", "southwest", "up", "down", "out", (char *) NULL); + +void GeasFile::read_into(const Common::Array<String> &in_data, + String in_parent, uint cur_line, bool recurse, + const reserved_words &props, + const reserved_words &actions) { + //cerr << "r_i: Reading in from" << cur_line << ": " << in_data[cur_line] << endl; + //output.push_back (GeasBlock()); + //GeasBlock &out_block = output[output.size() - 1]; + int blocknum = blocks.size(); + blocks.push_back(GeasBlock()); + GeasBlock &out_block = blocks[blocknum]; + + Common::Array<String> &out_data = out_block.data; + out_block.parent = in_parent; + uint t1, t2; + String line = in_data[cur_line]; + // SENSITIVE? + assert(first_token(line, t1, t2) == "define"); + String blocktype = out_block.blocktype = next_token(line, t1, t2); // "object", or the like + //cerr << "r_i: Pushing back block of type " << blocktype << "\n"; + type_indecies[blocktype].push_back(blocknum); + String name = next_token(line, t1, t2); // "<itemname>", or the like + + // SENSITIVE? + if (blocktype == "game") { + out_block.name = "game"; + out_data.push_back("game name " + name); + } else if (is_param(name)) + out_block.name = param_contents(name); + else if (name != "") + error("Expected parameter; %s found instead.", name.c_str()); + //out_block.lname = lcase (out_block.nname); + + // apparently not all block types are unique ... TODO which? + // SENSITIVE? + if (blocktype == "room" || blocktype == "object" || blocktype == "game") + register_block(out_block.name, blocktype); + //register_block (out_block.lname, blocktype); + + // SENSITIVE? + if (blocktype == "room" && find_by_name("type", "defaultroom")) + out_data.push_back("type <defaultroom>"); + // SENSITIVE? + if (blocktype == "object" && find_by_name("type", "default")) + out_data.push_back("type <default>"); + + cur_line ++; + uint depth = 1; + while (cur_line < in_data.size() && depth > 0) { + line = in_data[cur_line]; + if (recurse && is_define(line)) + ++ depth; + else if (is_end_define(in_data[cur_line])) + -- depth; + else if (depth == 1) { + //cerr << "r_i: Processing line #" << cur_line << ": " << line << endl; + //String dup_data = ""; + String tok = first_token(line, t1, t2); + String rest = next_token(line, t1, t2); + + //cerr << "r_i: tok == '" << tok << "', props[tok] == " << props[tok] + // << ", actions[tok] == " << actions[tok] << "\n"; + + if (props[tok] && dir_tag_property[tok]) + out_data.push_back(line); + + if (props[tok] && rest == "") { + //cerr << "r_i: Handling as props <tok>\n"; + line = "properties <" + tok + ">"; + } else if (props[tok] && is_param(rest)) { + //cerr << "r_i: Handling as props <tok = ...>\n"; + line = "properties <" + tok + "=" + param_contents(rest) + ">"; + } else if (actions[tok] && + (tok == "use" || tok == "give" || !is_param(rest))) { + //cerr << "r_i: Handling as action '" << tok << "'\n"; + // SENSITIVE? + if (tok == "use") { + //cerr << "r_i: ********** Use line: <" + line + "> ---> "; + String lhs = "action <use "; + // SENSITIVE? + if (rest == "on") { + rest = next_token(line, t1, t2); + String rhs = line.substr(t2); + // SENSITIVE? + if (rest == "anything") + line = lhs + "on anything> " + rhs; + // SENSITIVE? + else if (is_param(rest)) + line = lhs + "on " + param_contents(rest) + "> " + rhs; + else { + //cerr << "r_i: Error handling '" << line << "'" << endl; + line = "ERROR: " + line; + } + } + // SENSITIVE? + else if (rest == "anything") + line = lhs + "anything> " + line.substr(t2); + else if (is_param(rest)) + line = lhs + param_contents(rest) + "> " + line.substr(t2); + else + line = "action <use> " + line.substr(t1); + //cerr << "r_i: <" << line << ">\n"; + } + // SENSITIVE? + else if (tok == "give") { + String lhs = "action <give "; + // SENSITIVE? + if (rest == "to") { + rest = next_token(line, t1, t2); + String rhs = line.substr(t2); + // SENSITIVE? + if (rest == "anything") + line = lhs + "to anything> " + rhs; + else if (is_param(rest)) + line = lhs + "to " + param_contents(rest) + "> " + rhs; + else { + cerr << "Error handling '" << line << "'" << endl; + line = "ERROR: " + line; + } + } + // SENSITIVE? + else if (rest == "anything") + line = lhs + "anything> " + line.substr(t2); + else if (is_param(rest)) + line = lhs + param_contents(rest) + "> " + line.substr(t2); + else + line = "action <give> " + line.substr(t1); + } else + line = "action <" + tok + "> " + line.substr(t1); + } + //else + // cerr << "Handling as ordinary line\n"; + + // recalculating tok because it might have changed + /* TODO: Make sure this only happens on object-type blocks */ + tok = first_token(line, t1, t2); + // SENSITIVE? + if (tok == "properties") { + rest = next_token(line, t1, t2); + if (is_param(rest)) { + vstring items = split_param(param_contents(rest)); + for (uint i = 0; i < items.size(); i ++) + out_data.push_back("properties <" + + static_eval(items[i]) + ">"); + } else + out_data.push_back("ERROR " + line); + } else + out_data.push_back(line); + + //if (dup_data != "") + // out_data.push_back (dup_data); + } + cur_line ++; + } +} + +GeasFile::GeasFile(const Common::Array<String> &v, GeasInterface *_gi) : gi(_gi) { + uint depth = 0; + + String parentname, parenttype; + + static String pass_names[] = { + "game", "type", "room", "variable", "object", "procedure", + "function", "selection", "synonyms", "text", "timer" + }; + + reserved_words recursive_passes("game", "room", (char *) NULL), + object_passes("game", "room", "objects", (char *) NULL); + + + //Common::Array <GeasBlock> outv; + for (uint pass = 0; pass < sizeof(pass_names) / sizeof(*pass_names); + pass ++) { + String this_pass = pass_names[pass]; + bool recursive = recursive_passes[this_pass]; + //bool is_object = object_passes[this_pass]; + + reserved_words actions((char *) NULL), props((char *) NULL); + // SENSITIVE? + if (this_pass == "room") { + props = reserved_words("look", "alias", "prefix", "indescription", "description", "north", "south", "east", "west", "northeast", "northwest", "southeast", "southwest", "out", "up", "down", (char *) NULL); + actions = reserved_words("description", "script", "north", "south", "east", "west", "northeast", "northwest", "southeast", "southwest", "out", "up", "down", (char *) NULL); + } + // SENSITIVE? + else if (this_pass == "object") { + props = reserved_words("look", "examine", "speak", "take", "alias", "prefix", "suffix", "detail", "displaytype", "gender", "article", "hidden", "invisible", (char *) NULL); + actions = reserved_words("look", "examine", "speak", "take", "gain", "lose", "use", "give", (char *) NULL); + } + + depth = 0; + for (uint i = 0; i < v.size(); i ++) + if (is_define(v[i])) { + ++ depth; + String blocktype = nth_token(v[i], 2); + if (depth == 1) { + parenttype = blocktype; + parentname = nth_token(v[i], 3); + + // SENSITIVE? + if (blocktype == this_pass) + read_into(v, "", i, recursive, props, actions); + } else if (depth == 2 && blocktype == this_pass) { + // SENSITIVE? + if (this_pass == "object" && parenttype == "room") + read_into(v, parentname, i, false, props, actions); + // SENSITIVE? + else if (this_pass == "variable" && parenttype == "game") + read_into(v, "", i, false, props, actions); + } + } else if (is_end_define(v[i])) + -- depth; + } + +} + +bool decompile(String data, Common::Array<String> &rv); + +bool preprocess(Common::Array<String> v, String fname, Common::Array<String> &rv, GeasInterface *gi); + +GeasFile read_geas_file(GeasInterface *gi, const String &filename) { + //return GeasFile (split_lines(gi->get_file(s)), gi); + String file_contents = gi->get_file(filename); + + if (file_contents == "") + return GeasFile(); + + Common::Array<String> data; + bool success; + + cerr << "Header is '" << file_contents.substr(0, 7) << "'.\n"; + if (file_contents.size() > 8 && file_contents.substr(0, 7) == "QCGF002") { + cerr << "Decompiling\n"; + success = decompile(file_contents, data); + } else { + cerr << "Preprocessing\n"; + success = preprocess(split_lines(file_contents), filename, data, gi); + } + + cerr << "File load was " << (success ? "success" : "failure") << "\n"; + + if (success) + return GeasFile(data, gi); + + gi->debug_print("Unable to read file " + filename); + return GeasFile(); +} + +Common::WriteStream &operator<<(Common::WriteStream &o, const GeasBlock &gb) { + //o << "Block " << gb.blocktype << " '" << gb.nname; + o << "Block " << gb.blocktype << " '" << gb.name; + if (gb.parent != "") + o << "' and parent '" << gb.parent; + o << "'\n"; + for (uint i = 0; i < gb.data.size(); i ++) + o << " " << gb.data[i] << "\n"; + o << "\n"; + return o; +} + +void print_vblock(Common::WriteStream &o, String blockname, const Common::Array<GeasBlock> &blocks) { + o << blockname << ":\n"; + for (uint i = 0; i < blocks.size(); i ++) + o << " " << blocks[i] << "\n"; + o << "\n"; +} + +Common::WriteStream &operator<<(Common::WriteStream &o, const GeasFile &gf) { + /* + o << "Geas File\nThing-type blocks:\n"; + for (uint i = 0; i < gf.things.size(); i ++) + o << gf.things[i]; + o << "\nOther-type blocks:\n"; + for (uint i = 0; i < gf.others.size(); i ++) + o << gf.others[i]; + */ + o << "Geas File\n"; + for (StringArrayIntMap::const_iterator i = gf.type_indecies.begin(); i != gf.type_indecies.end(); i ++) { + o << "Blocks of type " << (*i)._key << "\n"; + for (uint j = 0; j < (*i)._value.size(); j ++) + o << gf.blocks[(*i)._value[j]]; + o << "\n"; + } + + /* + o << "Geas File\n"; + print_vblock (o, "game", gf.game); + print_vblock (o, "rooms", gf.rooms); + print_vblock (o, "objects", gf.objects); + print_vblock (o, "text blocks", gf.textblocks); + print_vblock (o, "functions", gf.functions); + print_vblock (o, "procedures", gf.procedures); + print_vblock (o, "types", gf.types); + print_vblock (o, "synonyms", gf.synonyms); + print_vblock (o, "timers", gf.timers); + print_vblock (o, "variables", gf.variables); + print_vblock (o, "choices", gf.choices); + */ + o << endl; + return o; +} + + + + + +String compilation_tokens[256] = { + "", "game", "procedure", "room", "object", "character", "text", "selection", + "define", "end", "", "asl-version", "game", "version", "author", "copyright", + "info", "start", "possitems", "startitems", "prefix", "look", "out", "gender", + "speak", "take", "alias", "place", "east", "north", "west", "south", "give", + "hideobject", "hidechar", "showobject", "showchar", "collectable", + "collecatbles", "command", "use", "hidden", "script", "font", "default", + "fontname", "fontsize", "startscript", "nointro", "indescription", + "description", "function", "setvar", "for", "error", "synonyms", "beforeturn", + "afterturn", "invisible", "nodebug", "suffix", "startin", "northeast", + "northwest", "southeast", "southwest", "items", "examine", "detail", "drop", + "everywhere", "nowhere", "on", "anything", "article", "gain", "properties", + "type", "action", "displaytype", "override", "enabled", "disabled", + "variable", "value", "display", "nozero", "onchange", "timer", "alt", "lib", + "up", "down", "gametype", "singleplayer", "multiplayer", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "do", "if", "got", "then", "else", + "has", "say", "playwav", "lose", "msg", "not", "playerlose", "playerwin", + "ask", "goto", "set", "show", "choice", "choose", "is", "setstring", + "displaytext", "exec", "pause", "clear", "debug", "enter", "movechar", + "moveobject", "revealchar", "revealobject", "concealchar", "concealobject", + "mailto", "and", "or", "outputoff", "outputon", "here", "playmidi", "drop", + "helpmsg", "helpdisplaytext", "helpclear", "helpclose", "hide", "show", + "move", "conceal", "reveal", "numeric", "String", "collectable", "property", + "create", "exit", "doaction", "close", "each", "in", "repeat", "while", + "until", "timeron", "timeroff", "stop", "panes", "on", "off", "return", + "playmod", "modvolume", "clone", "shellexe", "background", "foreground", + "wait", "picture", "nospeak", "animate", "persist", "inc", "dec", "flag", + "dontprocess", "destroy", "beforesave", "onload", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "" +}; + + +bool decompile(String s, Common::Array<String> &rv) { + String cur_line, tok; + uint expect_text = 0, obfus = 0; + unsigned char ch; + + for (uint i = 8; i < s.size(); i ++) { + ch = s[i]; + if (obfus == 1 && ch == 0) { + cur_line += "> "; + obfus = 0; + } else if (obfus == 1) + cur_line += char (255 - ch); + else if (obfus == 2 && ch == 254) { + obfus = 0; + cur_line += " "; + } else if (obfus == 2) + cur_line += ch; + else if (expect_text == 2) { + if (ch == 253) { + expect_text = 0; + rv.push_back(cur_line); + cur_line = ""; + } else if (ch == 0) { + rv.push_back(cur_line); + cur_line = ""; + } else + cur_line += char (255 - ch); + } else if (obfus == 0 && ch == 10) { + cur_line += "<"; + obfus = 1; + } else if (obfus == 0 && ch == 254) + obfus = 2; + else if (ch == 255) { + if (expect_text == 1) + expect_text = 2; + rv.push_back(cur_line); + cur_line = ""; + } else { + tok = compilation_tokens[ch]; + if ((tok == "text" || tok == "synonyms" || tok == "type") && + cur_line == "define ") + expect_text = 1; + cur_line += tok + " "; + } + } + rv.push_back(cur_line); + + for (uint i = 0; i < rv.size(); i ++) + cerr << "rv[" << i << "]: " << rv[i] << "\n"; + + return true; +} + + + + + + + + +Common::Array<String> tokenize(String s) { + uint tok_start, tok_end; + String tok; + Common::Array<String> rv; + tok_end = 0; + while (tok_end + 1 <= s.size()) + rv.push_back(next_token(s, tok_start, tok_end)); + return rv; +} + +String string_int(int i) { + ostringstream o; + o << i; + return o.str(); +} + +void report_error(String s) { + //cerr << s << endl; + cerr << s << endl; + throw s; +} + +Common::Array<String> split_lines(String data) { + Common::Array <String> rv; + String tmp; + uint i = 0; + while (i < data.size()) { + //cerr << "data[" << i << "] == " << int(data[i]) << '\n'; + + if (data[i] == '\n' || data[i] == '\r') { + /* + if (data[i] == '\n' && i < data.size() && data[i+1] == '\r') + ++ i; + */ + if (tmp.size() > 0 && tmp[tmp.size() - 1] == '_') { + //cerr << "Line with trailing underscores: " << tmp << '\n'; + tmp.erase(tmp.size() - 1); + if (tmp[tmp.size() - 1] == '_') + tmp.erase(tmp.size() - 1); + if (i < data.size() && data[i] == '\r' && data[i + 1] == '\n') + ++ i; + ++ i; + //cerr << " WSK: data[" << i<< "] == " << int(data[i]) << '\n'; + while (i < data.size() && data[i] != '\r' && + data[i] != '\n' && Common::isSpace(data[i])) { + //cerr << " WS: data[" << i<< "] = " << int(data[i]) << '\n'; + ++ i; + } + //cerr << " WS: data[" << i<< "] == " << int(data[i]) << '\n'; + -- i; + } else { + //cerr << "Pushing back {<{" << tmp << "}>}\n"; + rv.push_back(tmp); + tmp = ""; + if (i < data.size() && data[i] == '\r' && data[i + 1] == '\n') + ++ i; + } + } else + tmp += data[i]; + ++ i; + } + if (tmp != "") + rv.push_back(tmp); + return rv; +} + +void show_tokenize(String s) { + //cerr << "s_t: Tokenizing '" << s << "' --> " << tokenize(s) << endl; +} + +void say_push(const Common::Array<String> &v) { + //cerr << "s_p: Pushing '" << v[v.size() - 1] << "'" << endl; +} + + + + + +//enum trim_modes { TRIM_SPACES, TRIM_UNDERSCORE, TRIM_BRACE }; + +//String trim (String s, trim_modes trim_mode = TRIM_SPACES) +String trim(String s, trim_modes trim_mode) { + uint i, j; + /* + cerr << "Trimming (" << s << "): ["; + for (i = 0; i < s.size(); i ++) + cerr << int (s[i]) << "(" << s[i] << "), "; + cerr << "]\n"; + */ + for (i = 0; i < s.size() && Common::isSpace(s[i]); i ++) + ; + if (i == s.size()) return ""; + if ((trim_mode == TRIM_UNDERSCORE && s[i] == '_') || + (trim_mode == TRIM_BRACE && s[i] == '[')) + ++ i; + if (i == s.size()) return ""; + for (j = s.size() - 1; Common::isSpace(s[j]); j --) + ; + if ((trim_mode == TRIM_UNDERSCORE && i < s.size() && s[j] == '_') || + (trim_mode == TRIM_BRACE && i < s.size() && s[j] == ']')) + -- j; + return s.substr(i, j - i + 1); +} + +/* bool is_balanced (String) + * + * Decides whether the String has balanced braces (and needn't be deinlined) + * - Track the nesting depth, starting at first lbrace to end of line + * - If it is ever at depth 0 before the end, it's balanced + * - Otherwise, it's unbalanced + */ +bool is_balanced(String str) { + uint index = str.find('{'); + if (index == -1) + return true; + int depth; + for (depth = 1, index ++; depth > 0 && index < str.size(); index ++) + if (str[index] == '{') + ++ depth; + else if (str[index] == '}') + -- depth; + return depth == 0; +} + +int count_depth(String str, int count) { + //cerr << "count_depth (" << str << ", " << count << ")" << endl; + uint index = 0; + if (count == 0) + index = str.find('{'); + while (index < str.size()) { + if (str[index] == '{') + ++ count; + else if (str[index] == '}') + -- count; + //cerr << " After char #" << index << ", count is " << count << endl; + ++ index; + } + //cerr << "returning " << count << endl; + return count; +} + +void handle_includes(const Common::Array<String> &in_data, String filename, Common::Array<String> &out_data, GeasInterface *gi) { + String line, tok; + uint tok_start, tok_end; + for (uint ln = 0; ln < in_data.size(); ln ++) { + line = in_data[ln]; + tok = first_token(line, tok_start, tok_end); + if (tok == "!include") { + tok = next_token(line, tok_start, tok_end); + if (!is_param(tok)) { + gi->debug_print("Expected parameter after !include"); + continue; + } + //handle_includes (split_lines (gi->get_file (param_contents (tok))), out_data, gi); + String newname = gi->absolute_name(param_contents(tok), filename); + handle_includes(split_lines(gi->get_file(newname)), newname, out_data, gi); + } else if (tok == "!QDK") { + while (ln < in_data.size() && + first_token(in_data[ln], tok_start, tok_end) != "!end") + ++ ln; + } else + out_data.push_back(line); + } +} + +bool preprocess(Common::Array<String> v, String fname, Common::Array<String> &rv, + GeasInterface *gi) { + //cerr << "Before preprocessing:\n" << v << "\n\n" << endl; + /* + cerr << "Before preprocessing:\n"; + for (uint i = 0; i < v.size(); i ++) + cerr << i << ": " << v[i] << "\n"; + cerr << "\n\n"; + */ + + // TODO: Is it "!=" or "<>" or both, and if both, which has priority? + static String comps[][2] = { + { "<>", "!=;" }, { "!=", "!=;" }, { "<=", "lt=;" }, { ">=", "gt=;" }, + { "<", "lt;" }, { ">", "gt;" }, { "=", "" } + }; + + uint tok_start, tok_end; + String tok; + + // preprocessing step 0: + // Loop through the lines. Replace !include with appropriate text + + Common::Array<String> v2; + handle_includes(v, fname, v2, gi); + v.clear(); + + StringArrayStringMap addtos; + for (uint line = 0; line < v2.size(); line ++) { + //cerr << "Line #" << line << ", looking for addtos: " << v2[line] << endl; + tok = first_token(v2[line], tok_start, tok_end); + if (tok == "!addto") { + tok = next_token(v2[line], tok_start, tok_end); + if (!(tok == "game" || tok == "synonyms" || tok == "type")) { + gi->debug_print("Error: Had addto for '" + tok + "'."); + continue; + } + if (tok == "type") { + String tmp = next_token(v2[line], tok_start, tok_end); + if (!(tmp == "<default>" || tmp == "<defaultroom>")) { + gi->debug_print("Error: Bad addto '" + v2[line] + "'\n"); + continue; + } + tok = tok + " " + tmp; + } + ++ line; // skip !addto + while (line < v2.size() && + first_token(v2[line], tok_start, tok_end) != "!end") + addtos[tok].push_back(v2[line ++]); + } + } + for (uint line = 0; line < v2.size(); line ++) { + tok = first_token(v2[line], tok_start, tok_end); + if (tok == "!addto") { + while (line < v2.size() && + first_token(v2[line], tok_start, tok_end) != "!end") + line ++; + } else { + v.push_back(v2[line]); + if (tok == "define") { + // TODO: What if there's a !addto for a block other than + // game, synonyms, default, defaultroom? + // + // Also, do the !addto'ed lines go at the front or end? + tok = next_token(v2[line], tok_start, tok_end); + if (tok == "type") + tok = tok + " " + next_token(v2[line], tok_start, tok_end); + + if (addtos.find(tok) != addtos.end()) { + Common::Array<String> &lines = addtos[tok]; + for (uint line2 = 0; line2 < lines.size(); line2 ++) + v.push_back(lines[line2]); + addtos.erase(tok); + } + } + } + } + //cerr << "Done looking for addtos" << endl; + v2.clear(); + + for (StringArrayStringMap::iterator i = addtos.begin(); i != addtos.end(); i ++) { + v.push_back("define " + i->_key); + for (uint j = 0; j < i->_value.size(); j ++) + v.push_back(i->_value[j]); + v.push_back("end define"); + } + /* + if (addtos.find("<default>") != addtos.end()) + { + Common::Array<String> &lines = addtos ["<default>"]; + v.push_back ("define type <default>"); + for (uint i = 0; i < lines.size(); i ++) + v.push_back (lines[i]); + v.push_back ("end define"); + } + + if (addtos.find("<defaultroom>") != addtos.end()) + { + Common::Array<String> &lines = addtos ["<defaultroom>"]; + v.push_back ("define type <defaultroom>"); + for (uint i = 0; i < lines.size(); i ++) + v.push_back (lines[i]); + v.push_back ("end define"); + } + */ + /* + cerr << "Done special-pushing <default> and <defaultroom>\n"; + cerr << "After includes & addtos:\n"; + for (uint i = 0; i < v.size(); i ++) + cerr << i << ": " << v[i] << "\n"; + cerr << "\n\n"; + */ + + // Preprocessing step 1: + // Loop through the lines. Look for "if/and/or (.. <.. )" or the like + // If there is such a pair, convert the second to "if/and/or is <..;lt;..>" + + for (uint line = 0; line < v.size(); line ++) { + tok_end = 0; + while (tok_end < v[line].size()) { + tok = next_token(v[line], tok_start, tok_end, false); + if (tok == "if" || tok == "repeat" || tok == "until" || + tok == "and" || tok == "or") { + tok = next_token(v[line], tok_start, tok_end, true); + //cerr << "Checking for comparison {" << tok << "}\n"; + if (tok.size() > 2 && tok[0] == '(' && + tok [tok.size() - 1] == ')') { + //cerr << " IT IS!\n"; + tok = tok.substr(1, tok.size() - 2); + String str = v[line]; + int cmp_start; + for (uint cmp = 0; cmp < ARRAYSIZE(comps); cmp ++) + if ((cmp_start = tok.find(comps[cmp][0])) != -1) { + uint cmp_end = cmp_start + comps[cmp][0].size(); + //cerr << "Changed str from {" << str << "} to {"; + str = str.substr(0, tok_start) + + "is <" + trim(tok.substr(0, cmp_start)) + ";" + + comps[cmp][1] + + trim(tok.substr(cmp_end)) + ">" + + str.substr(tok_end); + //cerr << str << "}\n"; + cmp = ARRAYSIZE(comps); + v[line] = str; + tok_end = tok_start; // old value of tok_end invalid + } + } + } + } + } + //cerr << "Done with pass 1!" << endl; + + // Pass 2: Extract comments from non-text blocks + bool in_text_block = false; + for (uint line = 0; line < v.size(); line ++) { + //cerr << "Checking line '" << v[line] << "' for comments\n"; + if (!in_text_block && is_start_textmode(v[line])) + in_text_block = true; + else if (in_text_block && is_end_define(v[line])) + in_text_block = false; + else if (!in_text_block) { + //cerr << " checking...\n"; + uint start_ch = 0, end_ch = 0; + while (start_ch < v[line].size()) + if (next_token(v[line], start_ch, end_ch)[0] == '\'') { + v[line] = v[line].substr(0, start_ch); + //cerr << " abbreviating to '" << v[line] << "'\n"; + break; + } + } + } + //cerr << "Done with pass 2!" << endl; + + /* There should be a pass 2.5: check that lbraces count equals + * rbrace count, but I'm skipping that + */ + + // Pass 3: Deinline braces + in_text_block = false; + int int_proc_count = 0; + for (uint line = 0; line < v.size(); line ++) { + //cerr << "Pass 3, line #" << line << endl; + String str = v[line]; + if (!in_text_block && is_start_textmode(str)) + in_text_block = true; + else if (in_text_block && is_end_define(str)) + in_text_block = false; + else if (!is_balanced(str)) { + //cerr << "...Special line!" << endl; + uint init_size = v.size(); + v.push_back("define procedure <!intproc" + + string_int(++int_proc_count) + ">"); + //cerr << "Pushing back on v: '" << v[v.size()-1] << "'" << endl; + + + uint tmp_index = str.find('{'); + v2.push_back(trim(str.substr(0, tmp_index)) + + " do <!intproc" + string_int(int_proc_count) + "> "); + //cerr << "Done with '" << v2[v2.size()-1] << "'" << endl; + + { + /* + String tmp_str = trim (str.substr (tmp_index + 1)); + if (tmp_str != "") + { + v.push_back (tmp_str); + cerr << "Pushing back on v: '" << v[v.size()-1] << "'" << endl; + } + */ + v.push_back(str.substr(tmp_index + 1)); + //cerr << "Pushing back on v: '" << v[v.size()-1] << "'" << endl; + } + + int count = count_depth(str, 0); + while (++ line < init_size && count != 0) { + str = v[line]; + count = count_depth(str, count); + if (count != 0) { + /* + str = trim(str); + if (str != "") + { + v.push_back (str); + cerr << "Pushing back on v: '" << str << "'" << endl; + } + */ + v.push_back(str); + //cerr << "Pushing back on v: '" << str << "'" << endl; + } + } + if (count != 0) { + report_error("Braces Unbalanced"); + return false; + } + tmp_index = str.rfind('}'); + { + /* + String tmp2 = trim (str.substr (0, tmp_index)); + if (tmp2 != "") + v.push_back (tmp2); + */ + v.push_back(str.substr(0, tmp_index)); + //cerr << "Pushing back on v: '" << v[v.size()-1] << "'" << endl; + } + + v.push_back("end define"); + //cerr << "Pushing back on v: '" << v[v.size()-1] << "'" << endl; + //cerr << "Done with special line" << endl; + line --; + continue; + // The continue is to avoid the v2.push_back(...); + // this block pushes stuff onto v2 on its own. + } + //cerr << "Done with '" << str << "'" << endl; + v2.push_back(str); + } + //cerr << "Done with pass 3!" << endl; + + /* Pass 4: trim lines, drop blank lines, combine elses */ + + in_text_block = false; + int_proc_count = 0; + for (uint line = 0; line < v2.size(); line ++) { + String str = v2[line]; + //cerr << "Pass 4, line #" << line << ", " << in_text_block << ": '" << str << "'\n"; + if (!in_text_block && is_start_textmode(str)) + in_text_block = true; + else if (in_text_block && is_end_define(str)) + in_text_block = false; + else if (rv.size() > 0 && !in_text_block && get_token(str) == "else") { + rv[rv.size() - 1] = rv[rv.size() - 1] + " " + trim(str); + //cerr << " Replacing else: " << rv[rv.size() - 1] << "\n"; + continue; + } + if (!in_text_block) + str = trim(str); + if (in_text_block || str != "") + rv.push_back(str); + //if (rv.size() > 0) + // cerr << " Result: " << rv[rv.size() - 1] << "\n"; + } + + /* + cerr << "At end of procedure, v == " << v << "\n"; + cerr << "and v2 == " << v2 << "\n"; + cerr << "and rv == " << rv << "\n"; + */ + //rv = v2; + + /* + cerr << "After all preprocessing\n"; + for (uint i = 0; i < rv.size(); i ++) + cerr << i << ": " << rv[i] << "\n"; + cerr << "\n\n"; + */ + + return true; + //return v2; +} + + +void show_find(String s, char ch) { + cerr << "Finding '" << ch << "' in '" << s << "': " << s.find(ch) + 1 << endl; +} + +void show_trim(String s) { + cerr << "Trimming '" << s << "': spaces (" << trim(s) + << "), underscores (" << trim(s, TRIM_UNDERSCORE) + << "), braces (" << trim(s, TRIM_BRACE) << ").\n"; + + //cerr << "Trimming '" << s << "': '" << trim (s) << "'\n"; +} + +template<class T> Common::Array<T> &operator<< (Common::Array<T> &v, T val) { + v.push_back(val); + return v; +} + +template<class T> class makevector { + Common::Array<T> dat; +public: + makevector<T> &operator<<(T it) { + dat.push_back(it); + return *this; + } + operator Common::Array<T>() { + return dat; + } +}; + +Common::Array <String> split(String s, char ch) { + uint i = 0, j; + Common::Array<String> rv; + do { + //cerr << "split (" << s << "): i == " << i << ", j == " << j << endl; + j = s.find(ch, i); + if (i != j) + rv.push_back(s.substr(i, j - i)); + i = j + 1; + } while (j < s.size()); + + //cerr << rv << endl; + return rv; +} + +} // End of namespace Quest +} // End of namespace Glk diff --git a/engines/glk/quest/read_file.h b/engines/glk/quest/read_file.h new file mode 100644 index 0000000000..062234d586 --- /dev/null +++ b/engines/glk/quest/read_file.h @@ -0,0 +1,49 @@ +/* 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_QUEST_READ_FILE +#define GLK_QUEST_READ_FILE + +#include "glk/quest/geas_file.h" +#include "glk/quest/string.h" +#include "common/array.h" + +namespace Glk { +namespace Quest { + +extern Common::Array<String> tokenize(String s); +extern String next_token(String full, uint &tok_start, uint &tok_end, bool cvt_paren = false); +extern String first_token(String s, uint &t_start, uint &t_end); +extern String nth_token(String s, int n); +extern String get_token(String s, bool cvt_paren = false); +extern bool find_token(String s, String tok, int &tok_start, int &tok_end, bool cvt_paren = false); +extern GeasFile read_geas_file(GeasInterface *gi, const String &filename); + +enum trim_modes { TRIM_SPACES, TRIM_UNDERSCORE, TRIM_BRACE }; +extern String trim(String, trim_modes mode = TRIM_SPACES); + +//Common::WriteStream &operator<< (Common::WriteStream &o, const Common::Array<String> &v); + +} // End of namespace Quest +} // End of namespace Glk + +#endif diff --git a/engines/glk/quest/reserved_words.h b/engines/glk/quest/reserved_words.h new file mode 100644 index 0000000000..5d736078b7 --- /dev/null +++ b/engines/glk/quest/reserved_words.h @@ -0,0 +1,84 @@ +/* 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_QUEST_RESERVED_WORDS +#define GLK_QUEST_RESERVED_WORDS + +#include "glk/quest/string.h" +#include "common/hashmap.h" +#include "common/stream.h" + +namespace Glk { +namespace Quest { + +class reserved_words { +private: + StringBoolMap _data; + +public: + /** + * Constructor + */ + reserved_words(const char *c, ...) { + va_list ap; + va_start(ap, c); + + while (c != nullptr) { + _data[String(c)] = true; + c = va_arg(ap, const char *); + } + } + + /** + * Returns true if the passed string is a reserved word + */ + bool operator[](const String &s) const { + return has(s); + } + + /** + * Returns true if the passed string is a reserved word + */ + bool has(const String &s) const { + return _data.contains(s) && (*this)[s]; + } + + /** + * Dumps the list of reserved words to the passed output stream + */ + void dump(Common::WriteStream &o) const { + o.writeString("RW {"); + + for (StringBoolMap::iterator i = _data.begin(); i != _data.end(); ++i) { + if (i != _data.begin()) + o.writeString(", "); + o.writeString((*i)._key); + } + + o.writeString("}"); + } +}; + +} // End of namespace Quest +} // End of namespace Glk + +#endif diff --git a/engines/glk/quest/streams.cpp b/engines/glk/quest/streams.cpp new file mode 100644 index 0000000000..37ea04298a --- /dev/null +++ b/engines/glk/quest/streams.cpp @@ -0,0 +1,78 @@ +/* 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/quest/streams.h" +#include "common/debug.h" +#include "common/str.h" + +namespace Glk { +namespace Quest { + +ConsoleStream *g_cerr; +const char endl = '\n'; + +void Streams::initialize() { + g_cerr = new ConsoleStream(); +} + +void Streams::deinitialize() { + delete g_cerr; +} + +uint32 ConsoleStream::write(const void *dataPtr, uint32 dataSize) { + Common::String s((const char *)dataPtr, (const char *)dataPtr + dataSize); + debug("%s", s.c_str()); + return dataSize; +} + +/*--------------------------------------------------------------------------*/ + +String ostringstream::str() { + return String((const char *)getData(), (const char *)getData() + size()); +} + +/*--------------------------------------------------------------------------*/ + +Common::WriteStream &operator<<(Common::WriteStream &ws, const String &s) { + ws.writeString(s); + return ws; +} + +Common::WriteStream &operator<<(Common::WriteStream &ws, char c) { + ws.writeByte(c); + return ws; +} + +Common::WriteStream &operator<<(Common::WriteStream &ws, int i) { + Common::String s = Common::String::format("%d", i); + ws.writeString(s); + return ws; +} + +Common::WriteStream &operator<<(Common::WriteStream &ws, uint i) { + Common::String s = Common::String::format("%u", i); + ws.writeString(s); + return ws; +} + +} // End of namespace Quest +} // End of namespace Glk diff --git a/engines/glk/quest/streams.h b/engines/glk/quest/streams.h new file mode 100644 index 0000000000..3a8d2a4a60 --- /dev/null +++ b/engines/glk/quest/streams.h @@ -0,0 +1,85 @@ +/* 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_QUEST_STREAMS +#define GLK_QUEST_STREAMS + +#include "glk/quest/string.h" +#include "common/memstream.h" +#include "common/stream.h" + +namespace Glk { +namespace Quest { + +/** + * Write stream wrapper around ScummVM debug calls. Can only handle text being written + */ +class ConsoleStream : public Common::WriteStream { +public: + virtual uint32 write(const void *dataPtr, uint32 dataSize) override; + virtual int32 pos() const override { return 0; } +}; + +class ostringstream : public Common::MemoryWriteStreamDynamic { +public: + ostringstream() : Common::MemoryWriteStreamDynamic(DisposeAfterUse::YES) {} + + String str(); +}; +class StringStream : public ostringstream { +public: + StringStream &operator>>(String &rhs) { + rhs = str(); + return *this; + } +}; + +/** + * Simple wrapper for managing streams initialization + */ +class Streams { +public: + /** + * Initialization + */ + static void initialize(); + + /** + * Deinitialization + */ + static void deinitialize(); +}; + +extern ConsoleStream *g_cerr; +extern const char endl; + +#define cerr (*g_cerr) + +Common::WriteStream &operator<<(Common::WriteStream &, const String &); +Common::WriteStream &operator<<(Common::WriteStream &, char); +Common::WriteStream &operator<<(Common::WriteStream &, int); +Common::WriteStream &operator<<(Common::WriteStream &, uint); + +} // End of namespace Quest +} // End of namespace Glk + +#endif diff --git a/engines/glk/quest/string.cpp b/engines/glk/quest/string.cpp new file mode 100644 index 0000000000..d6a17847c6 --- /dev/null +++ b/engines/glk/quest/string.cpp @@ -0,0 +1,99 @@ +/* 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/quest/string.h" + +namespace Glk { +namespace Quest { + +CI_EQUAL ci_equal_obj; +CI_LESS ci_less_obj; +CI_LESS_EQ ci_less_eq_obj; + +String operator+(const String &x, const String &y) { + String temp(x); + temp += y; + return temp; +} + +String operator+(const char *x, const String &y) { + String temp(x); + temp += y; + return temp; +} + +String operator+(const String &x, const char *y) { + String temp(x); + temp += y; + return temp; +} + +String operator+(char x, const String &y) { + String temp(x); + temp += y; + return temp; +} + +String operator+(const String &x, char y) { + String temp(x); + temp += y; + return temp; +} + +/*--------------------------------------------------------------------------*/ + +// Code for testing case insensitively by John Harrison + +bool c_equal_i(char ch1, char ch2) { + return tolower((unsigned char)ch1) == tolower((unsigned char)ch2); +} + +size_t ci_find(const String &str1, const String &str2) { + const char *pos = strstr(str1.c_str(), str2.c_str()); + return !pos ? String::npos : pos - str1.c_str(); +} + +static int my_stricmp(const String &s1, const String &s2) { + return s1.compareToIgnoreCase(s2); +} + +bool ci_equal(const String &str1, const String &str2) { + return my_stricmp(str1, str2) == 0; +} +bool ci_less_eq(const String &str1, const String &str2) { + return my_stricmp(str1, str2) <= 0; +} +bool ci_less(const String &str1, const String &str2) { + return my_stricmp(str1, str2) < 0; +} +bool ci_notequal(const String &str1, const String &str2) { + return !ci_equal(str1, str2); +} +bool ci_gt_eq(const String &str1, const String &str2) { + return my_stricmp(str1, str2) >= 0; +} +bool ci_gt(const String &str1, const String &str2) { + return my_stricmp(str1, str2) > 0; +} + +} // End of namespace Quest +} // End of namespace Glk diff --git a/engines/glk/quest/string.h b/engines/glk/quest/string.h new file mode 100644 index 0000000000..43f6ab602b --- /dev/null +++ b/engines/glk/quest/string.h @@ -0,0 +1,134 @@ +/* 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_QUEST_STRING +#define GLK_QUEST_STRING + +#include "common/hashmap.h" +#include "common/hash-str.h" +#include "common/str.h" + +namespace Glk { +namespace Quest { + +class String; + +typedef Common::HashMap<String, String, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> StringMap; +typedef Common::HashMap<String, bool, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> StringBoolMap; +typedef Common::HashMap<String, Common::Array<int>, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> StringArrayIntMap; +typedef Common::HashMap<String, Common::Array<String>, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> StringArrayStringMap; + +class String : public Common::String { +public: + String() : Common::String() {} + String(const char *str) : Common::String(str) {} + String(const char *str, uint32 len) : Common::String(str, len) {} + String(const char *beginP, const char *endP) : Common::String(beginP, endP) {} + String(const String &str) : Common::String(str) {} + explicit String(char c) : Common::String(c) {} + + char &operator[](int idx) { + assert(_str && idx >= 0 && idx < (int)_size); + return _str[idx]; + } + + inline uint length() const { + return size(); + } + + String substr(size_t pos, size_t len) const { + return String(c_str() + pos, c_str() + pos + len); + } + String substr(size_t pos) const { + return String(c_str() + pos); + } + + int find(char c, int pos = 0) const { + const char *p = strchr(c_str() + pos, c); + return p ? p - c_str() : -1; + } + + int find(const Common::String &s, int pos = 0) const { + const char *p = strstr(c_str() + pos, s.c_str()); + return p ? p - c_str() : -1; + } + + int rfind(char c) const { + const char *p = strrchr(c_str(), c); + return p ? p - c_str() : -1; + } + + String trim() const { + String result = *this; + static_cast<Common::String>(result).trim(); + return result; + } +}; + +// Append two strings to form a new (temp) string +String operator+(const String &x, const String &y); + +String operator+(const char *x, const String &y); +String operator+(const String &x, const char *y); + +String operator+(const String &x, char y); +String operator+(char x, const String &y); + + +bool c_equal_i(char ch1, char ch2); +size_t ci_find(const String &str1, const String &str2); +bool ci_equal(const String &str1, const String &str2); +bool ci_less_eq(const String &str1, const String &str2); +bool ci_less(const String &str1, const String &str2); +bool ci_notequal(const String &str1, const String &str2); +bool ci_gt_eq(const String &str1, const String &str2); +bool ci_gt(const String &str1, const String &str2); + +class CI_EQUAL { +public: + bool operator()(const String &str1, const String &str2) { + return ci_equal(str1, str2); + } +}; + +class CI_LESS_EQ { +public: + bool operator()(const String &str1, const String &str2) { + return ci_less_eq(str1, str2); + } +}; + +class CI_LESS { +public: + bool operator()(const String &str1, const String &str2) { + return ci_less(str1, str2); + } +}; + +extern CI_EQUAL ci_equal_obj; +extern CI_LESS ci_less_obj; +extern CI_LESS_EQ ci_less_eq_obj; + +} // End of namespace Quest +} // End of namespace Glk + +#endif diff --git a/engines/glk/quest/uncas.pl b/engines/glk/quest/uncas.pl new file mode 100644 index 0000000000..4e5dc8ee92 --- /dev/null +++ b/engines/glk/quest/uncas.pl @@ -0,0 +1,471 @@ +#!/usr/bin/perl + +############################################################################### +# # +# Copyright (C) 2006 by Mark J. Tilford # +# # +# This file is part of Geas. # +# # +# Geas 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. # +# # +# Geas 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 Geas; if not, write to the Free Software # +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # +# # +############################################################################### + +use strict; + +sub mtext { + my $str = shift; + my $rv = chr(254); + foreach (split //, $str) { + $rv . = chr(255 - ord $_); +} +return $rv . chr(0); +} + + +sub obfus { + my $str = shift; + my $rv = chr(10); + foreach (split //, $str) { + $rv . = chr(255 - ord $_); +} +return $rv . chr(0); +} + +my $is_raw = 0; + +my @hash_data = + ([1, 'game'], [2, 'procedure'], [3, 'room'], [4, 'object'], + [5, 'character'], [6, 'text'], [7, 'selection'], [8, 'define'], + [9, 'end'], [11, 'asl-version'], [12, 'game'], [13, 'version'], + [14, 'author'], [15, 'copyright'], [16, 'info'], [17, 'start'], + [18, 'possitems'], [19, 'startitems'], [20, 'prefix'], [21, 'look'], + [22, 'out'], [23, 'gender'], [24, 'speak'], [25, 'take'], [26, 'alias'], + [27, 'place'], [28, 'east'], [29, 'north'], [30, 'west'], [31, 'south'], + [32, 'give'], [33, 'hideobject'], [34, 'hidechar'], [35, 'showobject'], + [36, 'showchar'], [37, 'collectable'], [38, 'collecatbles'], + [39, 'command'], [40, 'use'], [41, 'hidden'], [42, 'script'], + [43, 'font'], [44, 'default'], [45, 'fontname'], [46, 'fontsize'], + [47, 'startscript'], [48, 'nointro'], [49, 'indescription'], + [50, 'description'], [51, 'function'], [52, 'setvar'], [53, 'for'], + [54, 'error'], [55, 'synonyms'], [56, 'beforeturn'], [57, 'afterturn'], + [58, 'invisible'], [59, 'nodebug'], [60, 'suffix'], [61, 'startin'], + [62, 'northeast'], [63, 'northwest'], [64, 'southeast'], + [65, 'southwest'], [66, 'items'], [67, 'examine'], [68, 'detail'], + [69, 'drop'], [70, 'everywhere'], [71, 'nowhere'], [72, 'on'], + [73, 'anything'], [74, 'article'], [75, 'gain'], [76, 'properties'], + [77, 'type'], [78, 'action'], [79, 'displaytype'], [80, 'override'], + [81, 'enabled'], [82, 'disabled'], [83, 'variable'], [84, 'value'], + [85, 'display'], [86, 'nozero'], [87, 'onchange'], [88, 'timer'], + [89, 'alt'], [90, 'lib'], [91, 'up'], [92, 'down'], [93, 'gametype'], + [94, 'singleplayer'], [95, 'multiplayer'], [150, 'do'], [151, 'if'], + [152, 'got'], [153, 'then'], [154, 'else'], [155, 'has'], [156, 'say'], + [157, 'playwav'], [158, 'lose'], [159, 'msg'], [160, 'not'], + [161, 'playerlose'], [162, 'playerwin'], [163, 'ask'], [164, 'goto'], + [165, 'set'], [166, 'show'], [167, 'choice'], [168, 'choose'], + [169, 'is'], [170, 'setstring'], [171, 'displaytext'], [172, 'exec'], + [173, 'pause'], [174, 'clear'], [175, 'debug'], [176, 'enter'], + [177, 'movechar'], [178, 'moveobject'], [179, 'revealchar'], + [180, 'revealobject'], [181, 'concealchar'], [182, 'concealobject'], + [183, 'mailto'], [184, 'and'], [185, 'or'], [186, 'outputoff'], + [187, 'outputon'], [188, 'here'], [189, 'playmidi'], [190, 'drop'], + [191, 'helpmsg'], [192, 'helpdisplaytext'], [193, 'helpclear'], + [194, 'helpclose'], [195, 'hide'], [196, 'show'], [197, 'move'], + [198, 'conceal'], [199, 'reveal'], [200, 'numeric'], [201, 'string'], + [202, 'collectable'], [203, 'property'], [204, 'create'], [205, 'exit'], + [206, 'doaction'], [207, 'close'], [208, 'each'], [209, 'in'], + [210, 'repeat'], [211, 'while'], [212, 'until'], [213, 'timeron'], + [214, 'timeroff'], [215, 'stop'], [216, 'panes'], [217, 'on'], + [218, 'off'], [219, 'return'], [220, 'playmod'], [221, 'modvolume'], + [222, 'clone'], [223, 'shellexe'], [224, 'background'], + [225, 'foreground'], [226, 'wait'], [227, 'picture'], [228, 'nospeak'], + [229, 'animate'], [230, 'persist'], [231, 'inc'], [232, 'dec'], + [233, 'flag'], [234, 'dontprocess'], [235, 'destroy'], + [236, 'beforesave'], [237, 'onload']); + +my % tokens = (); +my % rtokens = (); +foreach (@hash_data) { + if ($_->[0] >= 0 && $_->[0] < 256) { + if ($_->[1] eq '') { + $_->[1] = "[?" . $_->[0] . "?]"; + } + $rtokens{chr($_->[0])} = $_->[1]; + $tokens{$_->[1]} = chr($_->[0]); + } +} + +#print "{"; +#for (my $i = 0; $i < 256; $i ++) { +# print "\"", $rtokens{chr($i)}, "\", "; +#} +#print "}\n"; +#die; + + +my % text_block_starters = map { $_ => 1 } qw / text synonyms type /; + +sub uncompile_fil { + my $IFH; + open($IFH, "<", $_[0]); + binmode $IFH; + $ / = undef; + my $dat = < $IFH >; +#print "uncompile_fil : "; +#print "\$IFH == '$IFH',"; +#print "\$dat == '$dat'\n"; + my @dat = split //, $dat; + + + my $OFH; + if (@_ == 1) { + push @_, "&STDOUT"; + } + open $OFH, ">$_[1]" or die "Can't open '$_[1]' for output: $!"; + + my @output = (); + my $curline = ""; + + + my $obfus = 0; + my $expect_text == 0; + my($ch, $chn, $tok); + for (my $n = 8; $n < @dat; $n ++) { + $ch = $dat[$n]; + $chn = ord $ch; + $tok = $rtokens{$ch}; + if ($obfus == 1 && $chn == 0) { +#print $OFH "> "; + $curline . = "> "; + $obfus = 0; + } + elsif($obfus == 1) { +#print $OFH chr (255 - $chn); + $curline . = chr(255 - $chn); + } + elsif($obfus == 2 && $chn == 254) { + $obfus = 0; +#print $OFH " "; + $curline . = " "; + } + elsif($obfus == 2) { +#print $OFH chr ($chn); + $curline . = chr($chn); + } + elsif($expect_text == 2) { + if ($chn == 253) { + $expect_text = 0; +##print $OFH "\n"; + push @output, $curline; + $curline = ""; + } + elsif($chn == 0) { +#print $OFH "\n"; + push @output, $curline; + $curline = ""; + } + else { +#print $OFH chr (255 - $chn); + $curline . = chr(255 - $chn); + } + } + elsif($obfus == 0 && $chn == 10) { +#print $OFH "<"; + $curline . = "<"; + $obfus = 1; + } + elsif($obfus == 0 && $chn == 254) { + $obfus = 2; + } + elsif($chn == 255) { + if ($expect_text == 1) { + $expect_text = 2; + } +#print $OFH "\n"; + push @output, $curline; + $curline = ""; + } + else { + if (($tok eq 'text' || $tok eq 'synonyms' || $tok eq 'type') && + $dat[$n - 1] eq chr(8)) { + $expect_text = 1; + } +#print $OFH "$tok "; + $curline . = "$tok "; + } + } + push @output, $curline; + $curline = ""; + + if (!$is_raw) { + @output = pretty_print(reinline(@output)); + } + + foreach (@output) { + print $OFH $_, "\r\n"; + } +} + +sub list_grab_file { + my $IFH; + open($IFH, "<:crlf", $_[0]); + my @rv = < $IFH >; + chomp @rv; + return @rv; +} + + +sub compile_fil { + my @dat = list_grab_file($ARGV[0]); + my $OFH; + open $OFH, ">$ARGV[1]"; + + print $OFH "QCGF200".chr(0); + +# Mode 0 == normal, mode 1 == block text + my $mode = 0; + for (my $n = 0; $n < @dat; $n ++) { + my $l = $dat[$n]; + while (substr($l, length($l) - 1, 1) eq '_' && $n < @dat) { + $n ++; + $l = substr($l, 0, length($l) - 1) . $dat[$n]; + } + if ($l = ~ / ^ !include *<([\S] *)> /) { + @dat = (@dat[0..$n], list_grab_file($1), @dat[$n + 1..$#dat]); + } + elsif($l = ~ / ^ !addto.* /) { +# TODO + } + else { + my $i = 0; + my $max = length $l; + my @l = split //, $l; + + if ($mode == 1) { + if ($l = ~ / ^\s * end\s * define\s*$ /) { + print $OFH chr(253); + $mode = 0; +# FALL THROUGH + } else { +#print $OFH chr(0); + foreach (split //, $l) { + print $OFH chr(255 - ord $_); + } + next; + } + } + if ($l = ~ / ^\s*$ /) { + next; + } + if ($l = ~ / ^\s * define\s * (text | synonyms | type) /) { +#[\s$] + $mode = 1; + } + while ($i < $max) { + while ($i <= $max && $l[$i] = ~ / \s /) { + ++ $i; + } + if ($i == $max) { + next; + } + + my $j = $i + 1; + if ($l[$i] eq '<') { + while ($j < $max && $l[$j] ne '>') { + ++ $j; + } + if ($l[$j] eq '>') { + print $OFH obfus(substr($l, $i + 1, $j - $i - 1)); + $i = $j + 1; + next; + } + $j = $i + 1; + while ($j < $max && $l[$j] ne ' ') { + ++ $j; + } + print $OFH chr(254). substr($l, $i + 1, $j - $i - 1). chr(0); + $i = $j + 1; + next; + } + while ($j < $max && $l[$j] ne ' ') { + ++ $j; + } + my $str = substr($l, $i, $j - $i); + if (defined $tokens{$str}) { + print $OFH $tokens{$str}; + } + else { + print $OFH chr(254). $str. chr(254); + } + $i = $j + 1; + } + } + print $OFH chr(255); +} +} + +sub is_define_t { + my($line, $type) = (@_); + return ($line = ~ / ^ *define[\s] + $type + /); +} +sub is_define { + my($line) = (@_); + return ($line = ~ / ^ *define[\s] + [^\s] /); +} +sub is_end_define { return (shift = ~ / ^ *end + define *$ /); } + +sub trim { + my $tmp = trim1($_[0]); +#print "trimming ($_[0]) -> ($tmp)\n"; + return $tmp; +} + +sub trim1 { + if ($_[0] = ~ / ^[\s] * (.* ?)[\s]*$ /) { + return $1; + } + print "* * * Huh on trimming '$_[0]' * * *\n"; +} + +sub reinline { + my % reinlined = (); + my @head_prog = (); + my @rest_prog = (); + while (@_) { + push @rest_prog, (pop @_); + } + + while (@rest_prog) { + my $line = pop @rest_prog; +#print "processing $line\n"; + if ($line = ~ / ^ (.* |)do ( < !intproc[0 - 9] * >) * (.*)$ /) { +#print " reinlining...\n"; + my($prefix, $func_name, $suffix) = ($1, $2, $3); + $prefix = trim($prefix); + $suffix = trim($suffix); + $reinlined{$func_name} = 1; + for (my $line_num = 0; $line_num < @rest_prog; $line_num ++) { + if ($rest_prog[$line_num] = ~ / ^ *define + procedure + $func_name *$ /) { + my $end_line = $line_num; + while (!is_end_define($rest_prog[$end_line])) { +#print " checking $rest_prog[$end_line]\n"; + -- $end_line; + } + ++ $end_line; +#print " backpushing } ".$suffix."\n"; +#push @rest_prog, trim ("} " . $suffix); + if ($suffix ne '') { + push @rest_prog, $suffix; + } + push @rest_prog, "}"; + while ($end_line < $line_num) { + push @rest_prog, $rest_prog[$end_line]; +#print " backpushing $rest_prog[$end_line]\n"; + $end_line ++; + } +#print " backpushing $prefix {\n"; + push @rest_prog, trim($prefix." {"); + $line_num = scalar @rest_prog; + } + } + } + else { + push @head_prog, $line; + } + } + my @rv = (); + for (my $n = 0; $n < @head_prog; $n ++) { + if ($head_prog[$n] = ~ / ^define procedure(<.*>) *$ / && + $reinlined{$1}) { + while (!is_end_define($head_prog[$n])) { + ++ $n; + } + } + else { + push @rv, $head_prog[$n]; + } + } +#for (my $n = 0; $n < @rv; $n ++) { +# print "$n: $rv[$n]\n"; +#} + return @rv; +} + +sub pretty_print { + my $indent = 0; + my $not_in_text_mode = 1; + + my @rv = (); + + for (my $n = 0; $n < @_; $n ++) { + my $line = $_[$n]; + if (is_end_define($line)) { + -- $indent; + $not_in_text_mode = 1; + } + / { /; if ($line = ~ / ^} /) { + -- $indent; + } +###if (is_define ($line) && ($n == 0 || !is_define ($_[$n-1]))) { print "\n"; } + if (is_define($line) && ($n == 0 || !is_define($_[$n - 1]))) { + push @rv, ""; + } +###if ($in_text_mode == 0) { print " "x$indent; } + push @rv, (" "x($indent*$not_in_text_mode)).trim($line); +###print $line, " line $n, indent $indent, text $in_text_mode\n"; +###print $line, "\n"; + if (is_end_define($line) && $n < @_ && !is_end_define($_[$n + 1]) + && !is_define($_[$n + 1])) { +###print "\n"; + push @rv, ""; + } + if (is_define($line)) { + ++ $indent; + } + if ($line = ~ / {$ /) { + ++ $indent; + } / + } /; + if ($line = ~ / ^ *define + text /) { + $not_in_text_mode = 0; + } + } + return @rv; +} + + +sub error_msg { + die "Usage: 'perl uncas.pl file.asl file2.cas' to compile to file\n". + " 'perl uncas.pl file.cas' to decompile to console\n". + " 'perl uncas.pl file.cas file2.asl' to decompile to file\n"; +} + +if ($ARGV[0] eq '-raw') { + $is_raw = 1; + shift @ARGV; +} + +if ($ARGV[0] = ~ / \.asl$ /) { + if (@ARGV != 2) { + error_msg(); + } + compile_fil(@ARGV); +} +elsif($ARGV[0] = ~ / \.cas$ /) { +#print "compile_fil (", join (", ", @ARGV), ")\n"; + if (@ARGV != 1 && @ARGV != 2) { + error_msg(); + } + uncompile_fil(@ARGV); +} diff --git a/engines/glk/quetzal.cpp b/engines/glk/quetzal.cpp index e60460209a..0a2bc448ac 100644 --- a/engines/glk/quetzal.cpp +++ b/engines/glk/quetzal.cpp @@ -44,6 +44,7 @@ const uint32 INTERPRETER_IDS[INTERPRETER_TADS3 + 1] = { MKTAG('J', 'A', 'C', 'L'), MKTAG('L', 'V', 'L', '9'), MKTAG('M', 'A', 'G', 'N'), + MKTAG('Q', 'E', 'S', 'T'), MKTAG('S', 'C', 'A', 'R'), MKTAG('S', 'C', 'O', 'T'), MKTAG('T', 'A', 'D', '2'), |