aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--engines/glk/detection.cpp9
-rw-r--r--engines/glk/glk_types.h1
-rw-r--r--engines/glk/module.mk11
-rw-r--r--engines/glk/quest/detection.cpp93
-rw-r--r--engines/glk/quest/detection.h60
-rw-r--r--engines/glk/quest/detection_tables.h81
-rw-r--r--engines/glk/quest/geas_file.cpp659
-rw-r--r--engines/glk/quest/geas_file.h123
-rw-r--r--engines/glk/quest/geas_glk.cpp240
-rw-r--r--engines/glk/quest/geas_glk.h92
-rw-r--r--engines/glk/quest/geas_impl.h214
-rw-r--r--engines/glk/quest/geas_runner.cpp3675
-rw-r--r--engines/glk/quest/geas_runner.h220
-rw-r--r--engines/glk/quest/geas_state.cpp356
-rw-r--r--engines/glk/quest/geas_state.h171
-rw-r--r--engines/glk/quest/geas_util.cpp245
-rw-r--r--engines/glk/quest/geas_util.h97
-rw-r--r--engines/glk/quest/limit_stack.h103
-rw-r--r--engines/glk/quest/quest.cpp149
-rw-r--r--engines/glk/quest/quest.h92
-rw-r--r--engines/glk/quest/read_file.cpp1043
-rw-r--r--engines/glk/quest/read_file.h49
-rw-r--r--engines/glk/quest/reserved_words.h84
-rw-r--r--engines/glk/quest/streams.cpp78
-rw-r--r--engines/glk/quest/streams.h85
-rw-r--r--engines/glk/quest/string.cpp99
-rw-r--r--engines/glk/quest/string.h134
-rw-r--r--engines/glk/quest/uncas.pl471
-rw-r--r--engines/glk/quetzal.cpp1
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'),