From 8dd52b6cce3835950f255f48d13f3d09a7dbe0ff Mon Sep 17 00:00:00 2001 From: Borja Lorente Date: Sat, 25 Jun 2016 17:38:15 +0200 Subject: MACVENTURE: Complete text decoding --- engines/macventure/gui.cpp | 54 ++++++++++++++---------- engines/macventure/gui.h | 1 + engines/macventure/image.h | 1 + engines/macventure/macventure.cpp | 54 ++++++++++++++++-------- engines/macventure/macventure.h | 9 +++- engines/macventure/script.cpp | 12 +++--- engines/macventure/text.cpp | 89 +++++++++++++++++++++++++++++++-------- engines/macventure/text.h | 8 +++- engines/macventure/world.cpp | 7 +-- engines/macventure/world.h | 2 +- 10 files changed, 165 insertions(+), 72 deletions(-) (limited to 'engines/macventure') diff --git a/engines/macventure/gui.cpp b/engines/macventure/gui.cpp index d43be9b269..e0fcc5d691 100644 --- a/engines/macventure/gui.cpp +++ b/engines/macventure/gui.cpp @@ -174,6 +174,7 @@ void Gui::setWindowTitle(WindowReference winID, Common::String string) { void Gui::updateWindowInfo(WindowReference ref, ObjID objID, const Common::Array &children) { WindowData &data = findWindowData(ref); data.children.clear(); + data.objRef = objID; uint32 originx = 0x7fff; uint32 originy = 0x7fff; for (uint i = 0; i < children.size(); i++) { @@ -224,6 +225,8 @@ void Gui::initGUI() { initWindows(); + assignObjReferences(); + if (!loadControls()) error("Could not load controls"); @@ -238,43 +241,47 @@ void Gui::initWindows() { _controlsWindow->setDimensions(getWindowData(kCommandsWindow).bounds); _controlsWindow->setActive(false); _controlsWindow->setCallback(commandsWindowCallback, this); - loadBorder(_controlsWindow, "border_command.bmp", false); - loadBorder(_controlsWindow, "border_command.bmp", true); + //loadBorder(_controlsWindow, "border_command.bmp", false); + //loadBorder(_controlsWindow, "border_command.bmp", true); // Main Game Window _mainGameWindow = _wm.addWindow(false, false, false); _mainGameWindow->setDimensions(getWindowData(kMainGameWindow).bounds); _mainGameWindow->setActive(false); _mainGameWindow->setCallback(mainGameWindowCallback, this); - loadBorder(_mainGameWindow, "border_no_scroll_inac.bmp", false); - loadBorder(_mainGameWindow, "border_no_scroll_act.bmp", true); - findWindowData(kMainGameWindow).objRef = 3; + //loadBorder(_mainGameWindow, "border_no_scroll_inac.bmp", false); + //loadBorder(_mainGameWindow, "border_no_scroll_act.bmp", true); // In-game Output Console _outConsoleWindow = _wm.addWindow(false, true, true); _outConsoleWindow->setDimensions(Common::Rect(20, 20, 120, 120)); _outConsoleWindow->setActive(false); _outConsoleWindow->setCallback(outConsoleWindowCallback, this); - loadBorder(_outConsoleWindow, "border_left_scroll_inac.bmp", false); - loadBorder(_outConsoleWindow, "border_left_scroll_inac.bmp", true); + //loadBorder(_outConsoleWindow, "border_left_scroll_inac.bmp", false); + //loadBorder(_outConsoleWindow, "border_left_scroll_inac.bmp", true); // Self Window _selfWindow = _wm.addWindow(false, true, true); _selfWindow->setDimensions(getWindowData(kSelfWindow).bounds); _selfWindow->setActive(false); _selfWindow->setCallback(selfWindowCallback, this); - loadBorder(_selfWindow, "border_no_scroll_inac.bmp", false); - loadBorder(_selfWindow, "border_no_scroll_inac.bmp", true); - findWindowData(kMainGameWindow).objRef = 0; + //loadBorder(_selfWindow, "border_no_scroll_inac.bmp", false); + //loadBorder(_selfWindow, "border_no_scroll_inac.bmp", true); // Exits Window _exitsWindow = _wm.addWindow(false, true, true); _exitsWindow->setDimensions(getWindowData(kExitsWindow).bounds); _exitsWindow->setActive(false); _exitsWindow->setCallback(exitsWindowCallback, this); - loadBorder(_exitsWindow, "border_no_scroll_inac.bmp", false); - loadBorder(_exitsWindow, "border_no_scroll_act.bmp", true); + //loadBorder(_exitsWindow, "border_no_scroll_inac.bmp", false); + //loadBorder(_exitsWindow, "border_no_scroll_act.bmp", true); + + +} +void Gui::assignObjReferences() { + + findWindowData(kSelfWindow).objRef = 0; } @@ -282,7 +289,7 @@ WindowReference Gui::createInventoryWindow(ObjID objRef) { Graphics::MacWindow *newWindow = _wm.addWindow(true, true, true); WindowData newData; GlobalSettings settings = _engine->getGlobalSettings(); - newData.refcon = (WindowReference)ABS(_inventoryWindows.size()); // This is a hack + newData.refcon = (WindowReference)ABS(_inventoryWindows.size()); // This is a HACK if (_windowData->back().refcon < 0x80) { // There is already another inventory window newData.bounds = _windowData->back().bounds; // Inventory windows are always last @@ -303,8 +310,8 @@ WindowReference Gui::createInventoryWindow(ObjID objRef) { newWindow->setDimensions(newData.bounds); newWindow->setCallback(inventoryWindowCallback, this); - loadBorder(newWindow, "border_no_scroll_inac.bmp", false); - loadBorder(newWindow, "border_no_scroll_act.bmp", true); + //loadBorder(newWindow, "border_no_scroll_inac.bmp", false); + //loadBorder(newWindow, "border_no_scroll_act.bmp", true); _inventoryWindows.push_back(newWindow); debug("Create new inventory window. Reference: %d", newData.refcon); @@ -587,7 +594,7 @@ void Gui::drawExitsWindow() { srf->w + border.rightOffset, srf->h + border.bottomOffset), kColorWhite); - drawObjectsInWindow(kExitsWindow, _exitsWindow->getSurface()); + drawObjectsInWindow(kMainGameWindow, _exitsWindow->getSurface()); } void Gui::drawObjectsInWindow(WindowReference target, Graphics::ManagedSurface * surface) { @@ -792,12 +799,6 @@ void Gui::updateWindow(WindowReference winID, bool containerOpen) { it->unselect(); } } - if (winID == kMainGameWindow) { - drawMainGameWindow(); - } else { - Graphics::MacWindow *winRef = findWindow(winID); - winRef->getSurface()->fillRect(data.bounds, kColorGray); - } Common::Array &children = data.children; for (uint i = 0; i < children.size(); i++) { uint flag = 0; @@ -812,8 +813,17 @@ void Gui::updateWindow(WindowReference winID, bool containerOpen) { mode = kBlitOR; } children[i] = DrawableObject(child, mode); + } else { + children[i] = DrawableObject(child, kBlitXOR); } } + if (winID == kMainGameWindow) { + drawMainGameWindow(); + } + else { + Graphics::MacWindow *winRef = findWindow(winID); + winRef->getSurface()->fillRect(data.bounds, kColorGray); + } if (data.type == kZoomDoc && data.updateScroll) { warning("Unimplemented: update scroll"); } diff --git a/engines/macventure/gui.h b/engines/macventure/gui.h index 1da1b103dd..3569df7bbe 100644 --- a/engines/macventure/gui.h +++ b/engines/macventure/gui.h @@ -221,6 +221,7 @@ private: // Methods // Initializers void initGUI(); void initWindows(); + void assignObjReferences(); // Mainly guesswork // Loaders bool loadMenus(); diff --git a/engines/macventure/image.h b/engines/macventure/image.h index 52f0dfa591..9538d0cbb6 100644 --- a/engines/macventure/image.h +++ b/engines/macventure/image.h @@ -37,6 +37,7 @@ enum BlitMode { kBlitBIC = 1, kBlitOR = 2, kBlitXOR = 3 + //kBlitNONE = 4, }; enum GraphicsEncoding { diff --git a/engines/macventure/macventure.cpp b/engines/macventure/macventure.cpp index 21496cd8b2..3187b39709 100644 --- a/engines/macventure/macventure.cpp +++ b/engines/macventure/macventure.cpp @@ -59,8 +59,14 @@ MacVentureEngine::~MacVentureEngine() { if (_filenames) delete _filenames; - if (_decodingArticles) - delete _decodingArticles; + if (_decodingDirectArticles) + delete _decodingDirectArticles; + + if (_decodingNamingArticles) + delete _decodingNamingArticles; + + if (_decodingIndirectArticles) + delete _decodingIndirectArticles; if (_textHuffman) delete _textHuffman; @@ -88,7 +94,9 @@ Common::Error MacVentureEngine::run() { _oldTextEncoding = !loadTextHuffman(); _filenames = new StringTable(this, _resourceManager, kFilenamesStringTableID); - _decodingArticles = new StringTable(this, _resourceManager, kCommonArticlesStringTableID); + _decodingDirectArticles = new StringTable(this, _resourceManager, kCommonArticlesStringTableID); + _decodingNamingArticles = new StringTable(this, _resourceManager, kNamingArticlesStringTableID); + _decodingDirectArticles = new StringTable(this, _resourceManager, kIndirectArticlesStringTableID); // Big class instantiation _gui = new Gui(this, _resourceManager); @@ -251,7 +259,6 @@ void MacVentureEngine::enqueueText(TextQueueID type, ObjID target, ObjID source, } bool MacVentureEngine::printTexts() { - warning("printTexts: unimplemented"); for (uint i = 0; i < _textQueue.size(); i++) { QueuedText text = _textQueue.front(); _textQueue.remove_at(0); @@ -265,7 +272,7 @@ bool MacVentureEngine::printTexts() { gameChanged(); break; case kTextPlain: - debug("Print Plain Text: %s", _world->getText(text.asset).c_str()); + debug("Print Plain Text: %s", _world->getText(text.asset, text.source, text.destination).c_str()); gameChanged(); break; } @@ -401,11 +408,7 @@ bool MacVenture::MacVentureEngine::runScriptEngine() { if (_selectedControl == 1) _gameChanged = false; -<<<<<<< HEAD - else if (_gameState == kGameStateInit || _gameState == kGameStatePlaying){ -======= else if (_gameState == kGameStateInit || _gameState == kGameStatePlaying) { ->>>>>>> 088fc4d... MACVENTURE: Script engine fixes if (_scriptEngine->runControl(kTick, _selectedControl, _destObject, _deltaPoint)) { _haltedAtEnd = true; return true; @@ -493,7 +496,6 @@ void MacVentureEngine::resetVars() { void MacVentureEngine::unselectAll() { while (!_currentSelection.empty()) { unselectObject(_currentSelection.front()); - //_currentSelection.remove_at(0); } } @@ -532,6 +534,22 @@ int MacVentureEngine::findObjectInArray(ObjID objID, const Common::Array return found ? i : -1; } +uint MacVentureEngine::getPrefixNdx(ObjID obj) { + return _world->getObjAttr(obj, kAttrPrefixes); +} + +Common::String MacVentureEngine::getPrefixString(uint flag, ObjID obj) { + uint ndx = _world->getObjAttr(obj, kAttrPrefixes); // HACK should check the type of that one + ndx = ((ndx) >> flag) & 3; + if (ndx) { + return (*_decodingNamingArticles->getStrings())[ndx]; + } +} + +Common::String MacVentureEngine::getNoun(ObjID ndx) { + return (*_decodingIndirectArticles->getStrings())[ndx]; +} + void MacVentureEngine::highlightExit(ObjID objID) { warning("highlightExit: unimplemented"); } @@ -541,7 +559,7 @@ void MacVentureEngine::selectPrimaryObject(ObjID objID) { int idx; if (_destObject > 0 && (idx = findObjectInArray(_destObject, _selectedObjs)) != -1 && - findObjectInArray(_destObject, _currentSelection) == -1) + findObjectInArray(_destObject, _currentSelection) == -1) { _selectedObjs.remove_at(idx); highlightExit(_destObject); @@ -570,13 +588,13 @@ void MacVentureEngine::openObject(ObjID objID) { _gui->updateWindowInfo(kMainGameWindow, objID, _world->getChildren(objID, true)); _gui->updateWindow(kMainGameWindow, _world->getObjAttr(objID, kAttrContainerOpen)); //_gui->drawExits(); - _gui->setWindowTitle(kMainGameWindow, _world->getText(objID)); + _gui->setWindowTitle(kMainGameWindow, _world->getText(objID, objID, objID)); // it ignores source and target in the original } else { // Open inventory window Common::Point p(_world->getObjAttr(objID, kAttrPosX), _world->getObjAttr(objID, kAttrPosY)); //getParentWin(obj).localToGlobal(p); //globalToDesktop(p); WindowReference invID = _gui->createInventoryWindow(objID); - _gui->setWindowTitle(invID, _world->getText(objID)); + _gui->setWindowTitle(invID, _world->getText(objID, objID, objID)); _gui->updateWindowInfo(invID, objID, _world->getChildren(objID, true)); _gui->updateWindow(invID, _world->getObjAttr(objID, kAttrContainerOpen)); } @@ -664,7 +682,7 @@ void MacVentureEngine::reflectSwap(ObjID fromID, ObjID toID) { tmp = from; } if (tmp) { - Common::String newTitle = _world->getText(toID); + Common::String newTitle = _world->getText(toID, 0, 0); // Ignores src and targ in the original _gui->setWindowTitle(tmp, newTitle); _gui->updateWindowInfo(tmp, toID, _world->getChildren(toID, true)); updateWindow(tmp); @@ -677,7 +695,7 @@ void MacVentureEngine::toggleExits() { _selectedObjs.remove_at(0); highlightExit(obj); updateWindow(findParentWindow(obj)); - } + } } void MacVentureEngine::zoomObject(ObjID objID) { @@ -775,13 +793,13 @@ Common::Rect MacVentureEngine::getObjBounds(ObjID objID) { uint MacVentureEngine::getOverlapPercent(ObjID one, ObjID other) { //not the same parent? 0 overlap if (_world->getObjAttr(one, kAttrParentObject) != - _world->getObjAttr(other, kAttrParentObject)) + _world->getObjAttr(other, kAttrParentObject)) return 0; Common::Rect oneBounds = getObjBounds(one); Common::Rect otherBounds = getObjBounds(other); if (otherBounds.intersects(oneBounds) || - oneBounds.intersects(otherBounds)) + oneBounds.intersects(otherBounds)) { uint areaOne = oneBounds.width() * oneBounds.height(); uint areaOther = otherBounds.width() * otherBounds.height(); @@ -805,7 +823,7 @@ WindowReference MacVentureEngine::findObjWindow(ObjID objID) { // This is a bit of a hack, we take advantage of the consecutive nature of references for (uint i = kCommandsWindow; i <= kDiplomaWindow; i++) { const WindowData &data = _gui->getWindowData((WindowReference)i); - if (data.refcon == objID) { return data.refcon; } + if (data.objRef == objID) { return data.refcon; } } return kNoWindow; } diff --git a/engines/macventure/macventure.h b/engines/macventure/macventure.h index 4671c2a881..891fdb5ad6 100644 --- a/engines/macventure/macventure.h +++ b/engines/macventure/macventure.h @@ -194,6 +194,10 @@ public: const HuffmanLists *getDecodingHuffman() const; uint32 randBetween(uint32 min, uint32 max); uint32 getInvolvedObjects(); + int findObjectInArray(ObjID objID, const Common::Array &list); + uint getPrefixNdx(ObjID obj); + Common::String getPrefixString(uint flag, ObjID obj); + Common::String getNoun(ObjID ndx); // Attributes consult Common::Point getObjPosition(ObjID objID); @@ -224,7 +228,6 @@ private: void unselectAll(); void selectObject(ObjID objID); void unselectObject(ObjID objID); - int findObjectInArray(ObjID objID, const Common::Array &list); void highlightExit(ObjID objID); void selectPrimaryObject(ObjID objID); @@ -261,7 +264,9 @@ private: // Attributes // String tables StringTable *_filenames; - StringTable *_decodingArticles; + StringTable *_decodingDirectArticles; + StringTable *_decodingNamingArticles; + StringTable *_decodingIndirectArticles; // Engine state GameState _gameState; diff --git a/engines/macventure/script.cpp b/engines/macventure/script.cpp index 4c00054bb6..2e283e4c15 100644 --- a/engines/macventure/script.cpp +++ b/engines/macventure/script.cpp @@ -794,21 +794,21 @@ void ScriptEngine::opacEQ(EngineState * state, EngineFrame * frame) { } void ScriptEngine::opadEQS(EngineState * state, EngineFrame * frame) { - Common::String b = _world->getText(state->pop()); - Common::String a = _world->getText(state->pop()); + Common::String b = _world->getText(state->pop(), 0, 0); // HACK, these destinations might be wrong + Common::String a = _world->getText(state->pop(), 0, 0); state->push((a == b) ? 1 : 0); } void ScriptEngine::opaeCONT(EngineState * state, EngineFrame * frame) { - Common::String needle = _world->getText(state->pop()); - Common::String haystack = _world->getText(state->pop()); + Common::String needle = _world->getText(state->pop(), 0, 0); + Common::String haystack = _world->getText(state->pop(), 0, 0); haystack.toLowercase(); state->push(haystack.contains(needle) ? 1 : 0); } void ScriptEngine::opafCONTW(EngineState * state, EngineFrame * frame) { - Common::String needle = _world->getText(state->pop()); - Common::String haystack = _world->getText(state->pop()); + Common::String needle = _world->getText(state->pop(), 0, 0); + Common::String haystack = _world->getText(state->pop(), 0, 0); haystack.toLowercase(); state->push(haystack.contains(needle) ? 1 : 0); } diff --git a/engines/macventure/text.cpp b/engines/macventure/text.cpp index 49df7ed9a0..79c3200146 100644 --- a/engines/macventure/text.cpp +++ b/engines/macventure/text.cpp @@ -23,11 +23,14 @@ #include "macventure/text.h" namespace MacVenture { -TextAsset::TextAsset(ObjID objid, Container *container, bool isOld, const HuffmanLists *huffman) { +TextAsset::TextAsset(MacVentureEngine *engine, ObjID objid, ObjID source, ObjID target, Container *container, bool isOld, const HuffmanLists *huffman) { _id = objid; + _sourceObj = source; + _targetObj = target; _container = container; _huffman = huffman; _isOld = isOld; + _engine = engine; if (_isOld) { decodeOld(); @@ -77,8 +80,20 @@ void TextAsset::decodeOld() { lowercase = true; } else if (val == 0x1D) { // Composite - warning("Composite strings not implemented"); - stream.getBits(16); + ObjID subval = stream.getBits(16); + Common::String child; + if (subval & 0x8000) { + // Composite object id + subval ^= 0xFFFF; + child = getNoun(subval); + } else { + // Just another id + // HACK, see below in getNoun() + child = *TextAsset(_engine, subval, _sourceObj, _targetObj, _container, _isOld, _huffman).decode(); + } + if (child.size() > 0) { + c = '?'; // HACK Will fix later, should append + } lowercase = true; } else if (val == 0x1E) { @@ -102,7 +117,7 @@ void TextAsset::decodeOld() { void TextAsset::decodeHuffman() { _decoded = Common::String(""); Common::SeekableReadStream *res = _container->getItem(_id); - Common::BitStream32BEMSB stream(res); + Common::BitStream8MSB stream(res); uint16 strLen = 0; if (stream.getBit()) { strLen = stream.getBits(15); @@ -110,12 +125,13 @@ void TextAsset::decodeHuffman() { else { strLen = stream.getBits(7); } - + // OK up to here uint32 mask = 0; uint32 symbol = 0; char c; for (uint16 i = 0; i < strLen; i++) { - mask = stream.peekBits(16); // The mask is OK, so it means that I don't know how to use the huffman + mask = stream.peekBits(16); // The mask is OK + uint32 entry; // Find the length index for (entry = 0; entry < _huffman->getNumEntries(); entry++) { @@ -128,24 +144,63 @@ void TextAsset::decodeHuffman() { if (symbol == 1) { // 7-bit ascii c = stream.getBits(7); - } - else if (symbol == 2) { // Composite - warning("Composite huffman strings not tested"); + _decoded += c; + } else if (symbol == 2) { // Composite if (stream.getBit()) { // TextID ObjID embedId = stream.getBits(15); - TextAsset embedded(embedId, _container, _isOld, _huffman); - _decoded += *embedded.decode(); + uint pos = stream.pos(); // HACK, part 1 + TextAsset embedded(_engine, embedId, _sourceObj, _targetObj, _container, _isOld, _huffman); + stream.rewind();// HACK, part 2 + stream.skip(pos); + + _decoded.replace(_decoded.end(), _decoded.end(), *embedded.decode()); + + // Another HACK, to get around that EOS char I insert at the end + _decoded.replace(_decoded.end() - 1, _decoded.end(), ""); } else { //Composite obj string - _decoded += Common::String("Unimplemented"); + ObjID embedId = stream.getBits(8); + _decoded.replace(_decoded.end(), _decoded.end(), getNoun(embedId)); + // Another HACK, to get around that EOS char I insert at the end + _decoded.replace(_decoded.end() - 1, _decoded.end(), ""); } - } - else { // Plain ascii + } else { // Plain ascii c = symbol & 0xFF; - } - - _decoded += c; + _decoded.replace(_decoded.end(), _decoded.end(), Common::String(c)); + } } _decoded += '\0'; debug(7, "Decoded %d'th string (new): %s", _id, _decoded.c_str()); } +Common::String TextAsset::getNoun(ObjID subval) { + ObjID obj; + Common::String name; + if (subval & 8) + obj = _targetObj; + else + obj = _sourceObj; + if ((subval & 3) == 1) + { + uint idx = _engine->getPrefixNdx(obj); + idx = ((idx >> 4) & 3) + 1; + name = _engine->getNoun(idx); + } + else + { + // HACK, there should be a pool of assets or something like in the GUI + name = *TextAsset(_engine, obj, _sourceObj, _targetObj, _container, _isOld, _huffman).decode(); + switch (subval & 3) + { + case 2: + name = _engine->getPrefixString(0, obj) + name; + break; + case 3: + name = _engine->getPrefixString(2, obj) + name; + break; + } + } + if (name.size() && (subval & 4)) + name.toUppercase(); // HACK, should only capitalize first char? + return name; +} + } // End of namespace MacVenture \ No newline at end of file diff --git a/engines/macventure/text.h b/engines/macventure/text.h index cfe76d6e11..2389454042 100644 --- a/engines/macventure/text.h +++ b/engines/macventure/text.h @@ -31,7 +31,7 @@ typedef uint32 ObjID; class TextAsset { public: - TextAsset(ObjID objid, Container *container, bool isOld, const HuffmanLists *huffman); + TextAsset(MacVentureEngine *engine, ObjID objid, ObjID source, ObjID target, Container *container, bool isOld, const HuffmanLists *huffman); ~TextAsset() {} const Common::String *decode() { @@ -42,9 +42,15 @@ private: void decodeOld(); void decodeHuffman(); + Common::String getNoun(ObjID id); + private: + MacVentureEngine *_engine; + Container *_container; ObjID _id; + ObjID _targetObj; + ObjID _sourceObj; const HuffmanLists *_huffman; bool _isOld; diff --git a/engines/macventure/world.cpp b/engines/macventure/world.cpp index 2ee7bae92f..02d388ab5f 100644 --- a/engines/macventure/world.cpp +++ b/engines/macventure/world.cpp @@ -25,9 +25,6 @@ World::World(MacVentureEngine *engine, Common::MacResManager *resMan) { warning("Test functions about to happen"); _gameText = new Container("Shadowgate II/Shadow Text"); - - ObjID tid = (ObjID)1; - TextAsset test = TextAsset(tid, _gameText, _engine->isOldText(), _engine->getDecodingHuffman()); delete saveGameRes; saveGameFile.close(); @@ -153,8 +150,8 @@ void World::captureChildren(ObjID objID) { void World::releaseChildren(ObjID objID) { } -Common::String World::getText(ObjID objID) { - TextAsset text = TextAsset(objID, _gameText, _engine->isOldText(), _engine->getDecodingHuffman()); +Common::String World::getText(ObjID objID, ObjID source, ObjID target) { + TextAsset text = TextAsset(_engine, objID, source, target, _gameText, _engine->isOldText(), _engine->getDecodingHuffman()); return *text.decode(); } diff --git a/engines/macventure/world.h b/engines/macventure/world.h index f42dd3a816..c023c70ec6 100644 --- a/engines/macventure/world.h +++ b/engines/macventure/world.h @@ -99,7 +99,7 @@ public: uint32 getObjAttr(ObjID objID, uint32 attrID); Attribute getGlobal(uint32 attrID); - Common::String getText(ObjID objID); + Common::String getText(ObjID objID, ObjID source, ObjID target); bool isObjActive(ObjID objID); -- cgit v1.2.3