[Scummvm-git-logs] scummvm master -> 0d527c60b8029770ffccdae3631efa7eff19b5dc
bluegr
noreply at scummvm.org
Sun Jun 14 10:00:42 UTC 2026
This automated email contains information about 3 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
d4a8400a96 MOHAWK: LB - Cleanup ~4000 LoC in main LB (I).
7d5666a9f5 MOHAWK: LB - move tryOpenPage function to header
0d527c60b8 MOHAWK: LB - Cleanup ~4000 LoC in main LB (II).
Commit: d4a8400a9655b25b5d0472fd47cefd845b0e45b8
https://github.com/scummvm/scummvm/commit/d4a8400a9655b25b5d0472fd47cefd845b0e45b8
Author: Alstruit (34786806+Alstruit at users.noreply.github.com)
Date: 2026-06-14T13:00:37+03:00
Commit Message:
MOHAWK: LB - Cleanup ~4000 LoC in main LB (I).
Move bookinfo and UI menu related into their respective files.
Move LBAnimationNode into its own file.
Move LBPage, BITL into livingbooks_page.cpp
Create livingbooks_itemtypes.cpp to handle misc item types.
Changed paths:
A engines/mohawk/livingbooks_animation.cpp
A engines/mohawk/livingbooks_bookinfo.cpp
A engines/mohawk/livingbooks_itemtypes.cpp
A engines/mohawk/livingbooks_menus.cpp
A engines/mohawk/livingbooks_page.cpp
engines/mohawk/livingbooks.cpp
engines/mohawk/module.mk
diff --git a/engines/mohawk/livingbooks.cpp b/engines/mohawk/livingbooks.cpp
index 6bf80dfba0a..fdcd4f1e2bd 100644
--- a/engines/mohawk/livingbooks.cpp
+++ b/engines/mohawk/livingbooks.cpp
@@ -75,62 +75,6 @@ Common::Rect MohawkEngine_LivingBooks::readRect(Common::ReadStreamEndian *stream
return rect;
}
-LBPage::LBPage(MohawkEngine_LivingBooks *vm) : _vm(vm) {
- _code = nullptr;
- _mhk = nullptr;
-
- _baseId = 0;
- _cascade = false;
-}
-
-void LBPage::open(Archive *mhk, uint16 baseId) {
- _mhk = mhk;
- _baseId = baseId;
-
- _vm->addArchive(_mhk);
- if (!_vm->hasResource(ID_BCOD, baseId)) {
- // assume that BCOD is mandatory for v4/v5
- if (_vm->getGameType() == GType_LIVINGBOOKSV4 || _vm->getGameType() == GType_LIVINGBOOKSV5)
- error("missing BCOD resource (id %d)", baseId);
- _code = new LBCode(_vm, 0);
- } else {
- _code = new LBCode(_vm, baseId);
- }
-
- loadBITL(baseId);
- for (uint i = 0; i < _items.size(); i++)
- _vm->addItem(_items[i]);
-
- for (uint32 i = 0; i < _items.size(); i++)
- _items[i]->init();
-
- for (uint32 i = 0; i < _items.size(); i++)
- _items[i]->startPhase(kLBPhaseLoad);
-}
-
-void LBPage::addClonedItem(LBItem *item) {
- _vm->addItem(item);
- _items.push_back(item);
-}
-
-void LBPage::itemDestroyed(LBItem *item) {
- for (uint i = 0; i < _items.size(); i++)
- if (item == _items[i]) {
- _items.remove_at(i);
- return;
- }
- error("itemDestroyed didn't find item");
-}
-
-LBPage::~LBPage() {
- delete _code;
- _vm->removeItems(_items);
- for (uint i = 0; i < _items.size(); i++)
- delete _items[i];
- _vm->removeArchive(_mhk);
- delete _mhk;
-}
-
MohawkEngine_LivingBooks::MohawkEngine_LivingBooks(OSystem *syst, const MohawkGameDescription *gamedesc) : MohawkEngine(syst, gamedesc) {
DebugMan.addDebugChannel(kDebugCode, "Code", "Track Script Execution");
@@ -305,65 +249,6 @@ void MohawkEngine_LivingBooks::pauseEngineIntern(bool pause) {
}
}
-void MohawkEngine_LivingBooks::loadBookInfo(const Common::Path &filename) {
- _bookInfoFile.allowNonEnglishCharacters();
- if (!_bookInfoFile.loadFromFile(filename))
- error("Could not open %s as a config file", filename.toString().c_str());
-
- _title = getStringFromConfig("BookInfo", "title");
- _copyright = getStringFromConfig("BookInfo", "copyright");
-
- _numPages = getIntFromConfig("BookInfo", "nPages");
- _numLanguages = getIntFromConfig("BookInfo", "nLanguages");
- _screenWidth = getIntFromConfig("BookInfo", "xRes");
- _screenHeight = getIntFromConfig("BookInfo", "yRes");
- // nColors is here too, but it's always 256 anyway...
-
- // this is 1 in The New Kid on the Block, changes the hardcoded UI
- // v2 games changed the flag name to fPoetry
- if (getGameType() == GType_LIVINGBOOKSV1)
- _poetryMode = (getIntFromConfig("BookInfo", "poetry") == 1);
- else
- _poetryMode = (getIntFromConfig("BookInfo", "fPoetry") == 1);
-
- // The later Living Books games add some more options:
- // - fNeedPalette (always true?)
- // - fUse254ColorPalette (always true?)
- // - nKBRequired (4096, RAM requirement?)
- // - fDebugWindow (always 0?)
-
- if (_bookInfoFile.hasSection("Globals")) {
- const Common::INIFile::SectionKeyList globals = _bookInfoFile.getKeys("Globals");
- for (Common::INIFile::SectionKeyList::const_iterator i = globals.begin(); i != globals.end(); i++) {
- Common::String command = Common::String::format("%s = %s", i->key.c_str(), i->value.c_str());
- LBCode tempCode(this, 0);
- uint offset = tempCode.parseCode(command);
- tempCode.runCode(nullptr, offset);
- }
- }
-}
-
-Common::String MohawkEngine_LivingBooks::stringForMode(LBMode mode) {
- Common::String language = getStringFromConfig("Languages", Common::String::format("Language%d", _curLanguage));
-
- switch (mode) {
- case kLBIntroMode:
- return "Intro";
- case kLBControlMode:
- return "Control";
- case kLBCreditsMode:
- return "Credits";
- case kLBPreviewMode:
- return "Preview";
- case kLBReadMode:
- return language + ".Read";
- case kLBPlayMode:
- return language + ".Play";
- default:
- error("unknown game mode %d", (int)mode);
- }
-}
-
void MohawkEngine_LivingBooks::destroyPage() {
_sound->stopSound();
_lastSoundOwner = 0;
@@ -798,169 +683,11 @@ void MohawkEngine_LivingBooks::lockSound(LBItem *owner, bool lock) {
}
}
-// Only 1 VSRN resource per page
-uint16 LBPage::getResourceVersion() {
- Common::SeekableReadStream *versionStream = _vm->getResource(ID_VRSN, _baseId);
-
- // FIXME: some V2 games have very strange version entries
- if (versionStream->size() != 2)
- debug(1, "Version Record size mismatch");
-
- uint16 version = versionStream->readUint16BE();
-
- delete versionStream;
- return version;
-}
-
-void LBPage::loadBITL(uint16 resourceId) {
- Common::SeekableReadStreamEndian *bitlStream = _vm->wrapStreamEndian(ID_BITL, resourceId);
-
- while (true) {
- Common::Rect rect = _vm->readRect(bitlStream);
- uint16 type = bitlStream->readUint16();
-
- LBItem *res;
- switch (type) {
- case kLBPictureItem:
- res = new LBPictureItem(_vm, this, rect);
- break;
- case kLBAnimationItem:
- res = new LBAnimationItem(_vm, this, rect);
- break;
- case kLBPaletteItem:
- res = new LBPaletteItem(_vm, this, rect);
- break;
- case kLBGroupItem:
- res = new LBGroupItem(_vm, this, rect);
- break;
- case kLBSoundItem:
- res = new LBSoundItem(_vm, this, rect);
- break;
- case kLBLiveTextItem:
- res = new LBLiveTextItem(_vm, this, rect);
- break;
- case kLBMovieItem:
- res = new LBMovieItem(_vm, this, rect);
- break;
- case kLBMiniGameItem:
- res = new LBMiniGameItem(_vm, this, rect);
- break;
- case kLBProxyItem:
- res = new LBProxyItem(_vm, this, rect);
- break;
- default:
- warning("Unknown item type %04x", type);
- // fall through
- case 3: // often used for buttons
- res = new LBItem(_vm, this, rect);
- break;
- }
-
- res->readFrom(bitlStream);
- _items.push_back(res);
-
- if (bitlStream->size() == bitlStream->pos())
- break;
- }
-
- delete bitlStream;
-}
-
Common::SeekableReadStreamEndian *MohawkEngine_LivingBooks::wrapStreamEndian(uint32 tag, uint16 id) {
Common::SeekableReadStream *dataStream = getResource(tag, id);
return new Common::SeekableReadStreamEndianWrapper(dataStream, isBigEndian(), DisposeAfterUse::YES);
}
-Common::String MohawkEngine_LivingBooks::getStringFromConfig(const Common::String §ion, const Common::String &key) {
- Common::String x, leftover;
- _bookInfoFile.getKey(key, section, x);
- Common::String tmp = removeQuotesFromString(x, leftover);
- if (!leftover.empty())
- warning("while parsing config key '%s' from section '%s', string '%s' was left after '%s'",
- key.c_str(), section.c_str(), leftover.c_str(), tmp.c_str());
- return tmp;
-}
-
-Common::String MohawkEngine_LivingBooks::getStringFromConfig(const Common::String §ion, const Common::String &key, Common::String &leftover) {
- Common::String x;
- _bookInfoFile.getKey(key, section, x);
- return removeQuotesFromString(x, leftover);
-}
-
-int MohawkEngine_LivingBooks::getIntFromConfig(const Common::String §ion, const Common::String &key) {
- return atoi(getStringFromConfig(section, key).c_str());
-}
-
-Common::String MohawkEngine_LivingBooks::getFileNameFromConfig(const Common::String §ion, const Common::String &key, Common::String &leftover) {
- Common::String string = getStringFromConfig(section, key, leftover);
-
- if (string.hasPrefix("//")) {
- // skip "//CD-ROM Title/" prefixes which we don't care about
- uint i = 3;
- while (i < string.size() && string[i - 1] != '/')
- i++;
-
- // Already uses slashes, no need to convert further
- return string.c_str() + i;
- }
-
- return (getPlatform() == Common::kPlatformMacintosh) ? convertMacFileName(string) : convertWinFileName(string);
-}
-
-Common::String MohawkEngine_LivingBooks::removeQuotesFromString(const Common::String &string, Common::String &leftover) {
- if (string.empty())
- return string;
-
- char quoteChar = string[0];
- if (quoteChar != '\"' && quoteChar != '\'')
- return string;
-
- Common::String tmp;
- bool inLeftover = false;
- for (uint32 i = 1; i < string.size(); i++) {
- if (inLeftover)
- leftover += string[i];
- else if (string[i] == quoteChar)
- inLeftover = true;
- else
- tmp += string[i];
- }
-
- return tmp;
-}
-
-Common::String MohawkEngine_LivingBooks::convertMacFileName(const Common::String &string) {
- Common::String filename;
-
- for (uint32 i = 0; i < string.size(); i++) {
- if (i == 0 && string[i] == ':') // First character should be ignored (another colon)
- continue;
- if (string[i] == ':') // Directory separator
- filename += '/';
- else if (string[i] == '/') // Literal slash
- filename += ':'; // Replace by colon, as used by macOS for slash
- else
- filename += string[i];
- }
-
- return filename;
-}
-
-Common::String MohawkEngine_LivingBooks::convertWinFileName(const Common::String &string) {
- Common::String filename;
-
- for (uint32 i = 0; i < string.size(); i++) {
- if (i == 0 && (string[i] == '/' || string[i] == '\\')) // ignore slashes at start
- continue;
- if (string[i] == '\\')
- filename += '/';
- else
- filename += string[i];
- }
-
- return filename;
-}
-
Archive *MohawkEngine_LivingBooks::createArchive() const {
if (isPreMohawk())
return new LivingBooksArchive_v1();
@@ -1032,285 +759,6 @@ void MohawkEngine_LivingBooks::nextPage() {
error("Could not find page after %d.%d for mode %d", _curPage, _curSubPage, (int)_curMode);
}
-void MohawkEngine_LivingBooks::handleUIMenuClick(uint controlId) {
- LBItem *item;
-
- switch (controlId) {
- case 1:
- if (getFeatures() & GF_LB_10) {
- if (!tryLoadPageStart(kLBControlMode, 2))
- error("couldn't load options page");
- } else {
- if (!tryLoadPageStart(kLBControlMode, 3))
- error("couldn't load options page");
- }
- break;
-
- case 2:
- item = getItemById(10);
- if (item)
- item->destroySelf();
- item = getItemById(11);
- if (item)
- item->destroySelf();
- item = getItemById(199 + _curLanguage);
- if (item) {
- item->setVisible(true);
- item->togglePlaying(false, true);
- }
- break;
-
- case 3:
- item = getItemById(10);
- if (item)
- item->destroySelf();
- item = getItemById(11);
- if (item)
- item->destroySelf();
- item = getItemById(12);
- if (item) {
- item->setVisible(true);
- item->togglePlaying(false, true);
- }
- break;
-
- case 4:
- if (getFeatures() & GF_LB_10) {
- if (!tryLoadPageStart(kLBControlMode, 3))
- error("couldn't load quit page");
- } else {
- if (!tryLoadPageStart(kLBControlMode, 2))
- error("couldn't load quit page");
- }
- break;
-
- case 10:
- item = getItemById(10);
- if (item)
- item->destroySelf();
- item = getItemById(11);
- if (item) {
- item->setVisible(true);
- item->togglePlaying(false);
- }
- break;
-
- case 11:
- item = getItemById(11);
- if (item)
- item->togglePlaying(false, true);
- break;
-
- case 12:
- // start game, in play mode
- if (!tryLoadPageStart(kLBPlayMode, 1))
- error("couldn't start play mode");
- break;
-
- default:
- if (controlId >= 100 && controlId < 100 + (uint)_numLanguages) {
- uint newLanguage = controlId - 99;
- if (newLanguage == _curLanguage)
- break;
- item = getItemById(99 + _curLanguage);
- if (item)
- item->seek(1);
- _curLanguage = newLanguage;
- } else if (controlId >= 200 && controlId < 200 + (uint)_numLanguages) {
- // start game, in read mode
- if (!tryLoadPageStart(kLBReadMode, 1))
- error("couldn't start read mode");
- }
- break;
- }
-}
-
-void MohawkEngine_LivingBooks::handleUIPoetryMenuClick(uint controlId) {
- LBItem *item;
-
- // the menu UI in New Kid on the Block is a hybrid of the normal menu
- // and the normal options screen
-
- // TODO: this is mostly untested
-
- switch (controlId) {
- case 2:
- case 3:
- handleUIOptionsClick(controlId);
- break;
-
- case 4:
- handleUIMenuClick(controlId);
- break;
-
- case 6:
- handleUIMenuClick(2);
- break;
-
- case 7:
- item = getItemById(10);
- if (item)
- item->destroySelf();
- item = getItemById(11);
- if (item)
- item->destroySelf();
- item = getItemById(12);
- if (item) {
- item->setVisible(true);
- item->togglePlaying(false, true);
- }
- break;
-
- case 0xA:
- item = getItemById(10);
- if (item)
- item->destroySelf();
- item = getItemById(11);
- if (item) {
- item->setVisible(true);
- item->togglePlaying(false);
- }
- break;
-
- case 0xB:
- item = getItemById(11);
- if (item)
- item->togglePlaying(false, true);
- break;
-
- case 0xC:
- if (!tryLoadPageStart(kLBPlayMode, _curSelectedPage))
- error("failed to load page %d", _curSelectedPage);
- break;
-
- default:
- if (controlId < 100) {
- handleUIMenuClick(controlId);
- } else {
- if (!tryLoadPageStart(kLBReadMode, _curSelectedPage))
- error("failed to load page %d", _curSelectedPage);
- }
- }
-}
-
-void MohawkEngine_LivingBooks::handleUIQuitClick(uint controlId) {
- LBItem *item;
-
- switch (controlId) {
- case 1:
- case 2:
- // button clicked, run animation
- item = getItemById(10);
- if (item)
- item->destroySelf();
- item = getItemById(11);
- if (item)
- item->destroySelf();
- item = getItemById((controlId == 1) ? 12 : 13);
- if (item) {
- item->setVisible(true);
- item->togglePlaying(false);
- }
- break;
-
- case 10:
- case 11:
- item = getItemById(11);
- if (item)
- item->togglePlaying(false, true);
- break;
-
- case 12:
- // 'yes', I want to quit
- quitGame();
- break;
-
- case 13:
- // 'no', go back to menu
- if (!tryLoadPageStart(kLBControlMode, 1))
- error("couldn't return to menu");
- break;
-
- default:
- break;
- }
-}
-
-void MohawkEngine_LivingBooks::handleUIOptionsClick(uint controlId) {
- LBItem *item;
-
- switch (controlId) {
- case 1:
- item = getItemById(10);
- if (item)
- item->destroySelf();
- item = getItemById(202);
- if (item) {
- item->setVisible(true);
- item->togglePlaying(false, true);
- }
- break;
-
- case 2:
- // back
- item = getItemById(2);
- if (item)
- item->seek(1);
- if (_curSelectedPage == 1) {
- _curSelectedPage = _numPages;
- } else {
- _curSelectedPage--;
- }
- for (uint i = 0; i < _numPages; i++) {
- item = getItemById(1000 + i);
- if (item)
- item->setVisible(_curSelectedPage == i + 1);
- item = getItemById(1100 + i);
- if (item)
- item->setVisible(_curSelectedPage == i + 1);
- }
- break;
-
- case 3:
- // forward
- item = getItemById(3);
- if (item)
- item->seek(1);
- if (_curSelectedPage == _numPages) {
- _curSelectedPage = 1;
- } else {
- _curSelectedPage++;
- }
- for (uint i = 0; i < _numPages; i++) {
- item = getItemById(1000 + i);
- if (item)
- item->setVisible(_curSelectedPage == i + 1);
- item = getItemById(1100 + i);
- if (item)
- item->setVisible(_curSelectedPage == i + 1);
- }
- break;
-
- case 4:
- if (!tryLoadPageStart(kLBCreditsMode, 1))
- error("failed to start credits");
- break;
-
- case 5:
- if (!tryLoadPageStart(kLBPreviewMode, 1))
- error("failed to start preview");
- break;
-
- case 202:
- if (!tryLoadPageStart(kLBPlayMode, _curSelectedPage))
- error("failed to load page %d", _curSelectedPage);
- break;
-
- default:
- break;
- }
-}
-
void MohawkEngine_LivingBooks::handleNotify(NotifyEvent &event) {
// hard-coded behavior (GUI/navigation)
@@ -1469,610 +917,49 @@ void MohawkEngine_LivingBooks::handleNotify(NotifyEvent &event) {
}
}
-LBAnimationNode::LBAnimationNode(MohawkEngine_LivingBooks *vm, LBAnimation *parent, uint16 scriptResourceId) : _vm(vm), _parent(parent) {
- loadScript(scriptResourceId);
-}
-
-LBAnimationNode::~LBAnimationNode() {
- for (uint32 i = 0; i < _scriptEntries.size(); i++)
- delete[] _scriptEntries[i].data;
+LBScriptEntry::LBScriptEntry() {
+ state = 0;
+ data = nullptr;
+ argvParam = nullptr;
+ argvTarget = nullptr;
}
-void LBAnimationNode::loadScript(uint16 resourceId) {
- Common::SeekableReadStreamEndian *scriptStream = _vm->wrapStreamEndian(ID_SCRP, resourceId);
-
- reset();
-
- while (byte opcodeId = scriptStream->readByte()) {
- byte size = scriptStream->readByte();
-
- LBAnimScriptEntry entry;
- entry.opcode = opcodeId;
- entry.size = size;
-
- if (!size) {
- entry.data = nullptr;
- } else {
- entry.data = new byte[entry.size];
- scriptStream->read(entry.data, entry.size);
- }
-
- _scriptEntries.push_back(entry);
- }
-
- byte size = scriptStream->readByte();
- if (size != 0 || scriptStream->pos() != scriptStream->size())
- error("Failed to read script correctly");
+LBScriptEntry::~LBScriptEntry() {
+ delete[] argvParam;
+ delete[] argvTarget;
+ delete[] data;
- delete scriptStream;
+ for (uint i = 0; i < subentries.size(); i++)
+ delete subentries[i];
}
-void LBAnimationNode::draw(const Common::Rect &_bounds) {
- if (!_currentCel)
- return;
-
- // this is also checked in SetCel, below
- if (_currentCel > _parent->getNumResources())
- error("Animation cel %d was too high, this shouldn't happen!", _currentCel);
-
- int16 xOffset = _xPos + _bounds.left;
- int16 yOffset = _yPos + _bounds.top;
+LBItem::LBItem(MohawkEngine_LivingBooks *vm, LBPage *page, Common::Rect rect) : _vm(vm), _page(page), _rect(rect) {
+ if (_vm->getGameType() == GType_LIVINGBOOKSV1 || _vm->getGameType() == GType_LIVINGBOOKSV2)
+ _phase = kLBPhaseInit;
+ else
+ _phase = kLBPhaseLoad;
- uint16 resourceId = _parent->getResource(_currentCel - 1);
+ _loopMode = 0;
+ _delayMin = 0;
+ _delayMax = 0;
+ _timingMode = kLBAutoNone;
+ _periodMin = 0;
+ _periodMax = 0;
+ _controlMode = kLBControlNone;
+ _soundMode = 0;
- if (!_vm->isPreMohawk()) {
- Common::Point offset = _parent->getOffset(_currentCel - 1);
- xOffset -= offset.x;
- yOffset -= offset.y;
- }
+ _loaded = false;
+ _enabled = false;
+ _visible = true;
+ _playing = false;
+ _globalEnabled = true;
+ _globalVisible = true;
+ _nextTime = 0;
+ _startTime = 0;
+ _loops = 0;
- _vm->_gfx->copyOffsetAnimImageToScreen(resourceId, xOffset, yOffset);
-}
-
-void LBAnimationNode::reset() {
- // TODO: this causes stupid flickering
- //if (_currentCel)
- // _vm->_needsRedraw = true;
-
- _currentCel = 0;
- _currentEntry = 0;
- _delay = 0;
-
- _xPos = 0;
- _yPos = 0;
-}
-
-NodeState LBAnimationNode::update(bool seeking) {
- if (_currentEntry == _scriptEntries.size())
- return kLBNodeDone;
-
- if (_delay > 0 && --_delay)
- return kLBNodeRunning;
-
- while (_currentEntry < _scriptEntries.size()) {
- LBAnimScriptEntry &entry = _scriptEntries[_currentEntry];
- _currentEntry++;
- debug(5, "Running script entry %d of %d", _currentEntry, _scriptEntries.size());
-
- switch (entry.opcode) {
- case kLBAnimOpPlaySound:
- case kLBAnimOpWaitForSound:
- case kLBAnimOpReleaseSound:
- case kLBAnimOpResetSound:
- {
- uint16 soundResourceId = READ_BE_UINT16(entry.data);
-
- if (!soundResourceId) {
- error("Unhandled named wave file, tell clone2727 where you found this");
- break;
- }
-
- Common::String cue;
- uint pos = 2;
- while (pos < entry.size) {
- char in = entry.data[pos];
- if (!in)
- break;
- pos++;
- cue += in;
- }
- if (pos == entry.size)
- error("Cue in sound kLBAnimOp wasn't null-terminated");
-
- switch (entry.opcode) {
- case kLBAnimOpPlaySound:
- if (seeking)
- break;
- debug(4, "a: PlaySound(%0d)", soundResourceId);
- _parent->playSound(soundResourceId);
- break;
- case kLBAnimOpWaitForSound:
- if (seeking)
- break;
- debug(4, "b: WaitForSound(%0d)", soundResourceId);
- if (!_parent->soundPlaying(soundResourceId, cue))
- break;
- _currentEntry--;
- return kLBNodeWaiting;
- case kLBAnimOpReleaseSound:
- debug(4, "c: ReleaseSound(%0d)", soundResourceId);
- // TODO
- _vm->_sound->stopSound(soundResourceId);
- break;
- case kLBAnimOpResetSound:
- debug(4, "d: ResetSound(%0d)", soundResourceId);
- // TODO
- _vm->_sound->stopSound(soundResourceId);
- break;
- default:
- break;
- }
- }
- break;
-
- case kLBAnimOpSetTempo:
- case kLBAnimOpSetTempoDiv:
- {
- assert(entry.size == 2);
- uint16 tempo = (int16)READ_BE_UINT16(entry.data);
-
- // TODO: LB 3 uses fixed-point here.
- if (entry.opcode == kLBAnimOpSetTempo) {
- debug(4, "3: SetTempo(%d)", tempo);
- // TODO: LB 3 uses (tempo * 1000) / 60, while
- // the original divides the system time by 16.
- _parent->setTempo(tempo * 16);
- } else {
- // LB 3.0+ only.
- debug(4, "E: SetTempoDiv(%d)", tempo);
- _parent->setTempo(1000 / tempo);
- }
-
- }
- break;
-
- case kLBAnimOpWait:
- assert(entry.size == 0);
- debug(5, "6: Wait()");
- return kLBNodeRunning;
-
- case kLBAnimOpMoveTo:
- {
- assert(entry.size == 4);
- int16 x = (int16)READ_BE_UINT16(entry.data);
- int16 y = (int16)READ_BE_UINT16(entry.data + 2);
- debug(4, "5: MoveTo(%d, %d)", x, y);
-
- _xPos = x;
- _yPos = y;
- _vm->_needsRedraw = true;
- }
- break;
-
- case kLBAnimOpDrawMode:
- {
- assert(entry.size == 2);
- uint16 mode = (int16)READ_BE_UINT16(entry.data);
- debug(4, "9: DrawMode(%d)", mode);
-
- // TODO
- }
- break;
-
- case kLBAnimOpSetCel:
- {
- assert(entry.size == 2);
- uint16 cel = (int16)READ_BE_UINT16(entry.data);
- debug(4, "7: SetCel(%d)", cel);
-
- _currentCel = cel;
- if (_currentCel > _parent->getNumResources())
- error("SetCel set current cel to %d, but we only have %d cels", _currentCel, _parent->getNumResources());
- _vm->_needsRedraw = true;
- }
- break;
-
- case kLBAnimOpNotify:
- {
- assert(entry.size == 2);
- uint16 data = (int16)READ_BE_UINT16(entry.data);
-
- if (seeking)
- break;
-
- debug(4, "2: Notify(%d)", data);
- _vm->notifyAll(data, _parent->getParentId());
- }
- break;
-
- case kLBAnimOpSleepUntil:
- {
- assert(entry.size == 4);
- uint32 frame = READ_BE_UINT32(entry.data);
- debug(4, "8: SleepUntil(%d)", frame);
-
- if (frame > _parent->getCurrentFrame()) {
- // *not* kLBNodeWaiting
- _currentEntry--;
- return kLBNodeRunning;
- }
- }
- break;
-
- case kLBAnimOpDelay:
- {
- assert(entry.size == 4);
- uint32 delay = READ_BE_UINT32(entry.data);
- debug(4, "f: Delay(%d)", delay);
- _delay = delay;
- return kLBNodeRunning;
- }
- break;
-
- default:
- error("Unknown opcode id %02x (size %d)", entry.opcode, entry.size);
- break;
- }
- }
-
- return kLBNodeRunning;
-}
-
-bool LBAnimationNode::transparentAt(int x, int y) {
- if (!_currentCel)
- return true;
-
- uint16 resourceId = _parent->getResource(_currentCel - 1);
-
- if (!_vm->isPreMohawk()) {
- Common::Point offset = _parent->getOffset(_currentCel - 1);
- x += offset.x;
- y += offset.y;
- }
-
- // TODO: only check pixels if necessary
- return _vm->_gfx->imageIsTransparentAt(resourceId, true, x - _xPos, y - _yPos);
-}
-
-LBAnimation::LBAnimation(MohawkEngine_LivingBooks *vm, LBAnimationItem *parent, uint16 resourceId) : _vm(vm), _parent(parent) {
- Common::SeekableReadStreamEndian *aniStream = _vm->wrapStreamEndian(ID_ANI, resourceId);
-
- // ANI records in the Wanderful sampler are 32 bytes, extra bytes are just NULs
- if (aniStream->size() != 30 && aniStream->size() != 32)
- warning("ANI Record size mismatch");
-
- uint16 version = aniStream->readUint16();
- if (version != 1)
- warning("ANI version not 1");
-
- _bounds = _vm->readRect(aniStream);
- _clip = _vm->readRect(aniStream);
- // TODO: what is colorId for?
- uint32 colorId = aniStream->readUint32();
- uint32 sprResourceId = aniStream->readUint32();
- uint32 sprResourceOffset = aniStream->readUint32();
-
- debug(5, "ANI bounds: (%d, %d), (%d, %d)", _bounds.left, _bounds.top, _bounds.right, _bounds.bottom);
- debug(5, "ANI clip: (%d, %d), (%d, %d)", _clip.left, _clip.top, _clip.right, _clip.bottom);
- debug(5, "ANI color id: %d", colorId);
- debug(5, "ANI SPRResourceId: %d, offset %d", sprResourceId, sprResourceOffset);
- if (aniStream->size() == 32) {
- debug(5, "ANI extra bytes: (%d)", aniStream->readUint16());
- }
-
- if (aniStream->pos() != aniStream->size())
- error("Still %d bytes at the end of anim stream", (int)(aniStream->size() - aniStream->pos()));
-
- delete aniStream;
-
- if (sprResourceOffset)
- error("Cannot handle non-zero ANI offset yet");
-
- Common::SeekableReadStreamEndian *sprStream = _vm->wrapStreamEndian(ID_SPR, sprResourceId);
-
- uint16 numBackNodes = sprStream->readUint16();
- uint16 numFrontNodes = sprStream->readUint16();
- uint32 shapeResourceID = sprStream->readUint32();
- uint32 shapeResourceOffset = sprStream->readUint32();
- uint32 scriptResourceID = sprStream->readUint32();
- uint32 scriptResourceOffset = sprStream->readUint32();
- uint32 scriptResourceLength = sprStream->readUint32();
- debug(5, "SPR# stream: %d front, %d background", numFrontNodes, numBackNodes);
- debug(5, "Shape ID %d (offset 0x%04x), script ID %d (offset 0x%04x, length %d)", shapeResourceID, shapeResourceOffset,
- scriptResourceID, scriptResourceOffset, scriptResourceLength);
-
- Common::Array<uint16> scriptIDs;
- for (uint16 i = 0; i < numFrontNodes; i++) {
- uint32 unknown1 = sprStream->readUint32();
- uint32 unknown2 = sprStream->readUint32();
- uint32 unknown3 = sprStream->readUint32();
- uint16 scriptID = sprStream->readUint32();
- uint32 unknown4 = sprStream->readUint32();
- uint32 unknown5 = sprStream->readUint32();
- scriptIDs.push_back(scriptID);
- debug(6, "Front node %d: script ID %d", i, scriptID);
- if (unknown1 != 0 || unknown2 != 0 || unknown3 != 0 || unknown4 != 0 || unknown5 != 0)
- error("Anim node %d had non-zero unknowns %08x, %08x, %08x, %08x, %08x",
- i, unknown1, unknown2, unknown3, unknown4, unknown5);
- }
-
- if (numBackNodes)
- error("Ignoring %d back nodes", numBackNodes);
-
- if (sprStream->pos() != sprStream->size())
- error("Still %d bytes at the end of sprite stream", (int)(sprStream->size() - sprStream->pos()));
-
- delete sprStream;
-
- loadShape(shapeResourceID);
-
- _nodes.push_back(new LBAnimationNode(_vm, this, scriptResourceID));
- for (uint16 i = 0; i < scriptIDs.size(); i++)
- _nodes.push_back(new LBAnimationNode(_vm, this, scriptIDs[i]));
-
- _currentFrame = 0;
- _currentSound = 0xffff;
- _running = false;
- _tempo = 1;
-}
-
-LBAnimation::~LBAnimation() {
- for (uint32 i = 0; i < _nodes.size(); i++)
- delete _nodes[i];
- if (_currentSound != 0xffff)
- _vm->_sound->stopSound(_currentSound);
-}
-
-void LBAnimation::loadShape(uint16 resourceId) {
- if (resourceId == 0)
- return;
-
- Common::SeekableReadStreamEndian *shapeStream = _vm->wrapStreamEndian(ID_SHP, resourceId);
-
- if (_vm->isPreMohawk()) {
- if (shapeStream->size() < 6)
- error("V1 SHP Record size too short (%d)", (int)shapeStream->size());
-
- uint16 u0 = shapeStream->readUint16();
- if (u0 != 3)
- error("V1 SHP Record u0 is %04x, not 3", u0);
-
- uint16 u1 = shapeStream->readUint16();
- if (u1 != 0)
- error("V1 SHP Record u1 is %04x, not 0", u1);
-
- uint16 idCount = shapeStream->readUint16();
- debug(8, "V1 SHP: idCount: %d", idCount);
-
- if (shapeStream->size() != (idCount * 2) + 6)
- error("V1 SHP Record size mismatch (%d)", (int)shapeStream->size());
-
- for (uint16 i = 0; i < idCount; i++) {
- _shapeResources.push_back(shapeStream->readUint16());
- debug(8, "V1 SHP: BMAP Resource Id %d: %d", i, _shapeResources[i]);
- }
- } else {
- uint16 idCount = shapeStream->readUint16();
- debug(8, "SHP: idCount: %d", idCount);
-
- if (shapeStream->size() != (idCount * 6) + 2)
- error("SHP Record size mismatch (%d)", (int)shapeStream->size());
-
- for (uint16 i = 0; i < idCount; i++) {
- _shapeResources.push_back(shapeStream->readUint16());
- int16 x = shapeStream->readSint16();
- int16 y = shapeStream->readSint16();
- _shapeOffsets.push_back(Common::Point(x, y));
- debug(8, "SHP: tBMP Resource Id %d: %d, at (%d, %d)", i, _shapeResources[i], x, y);
- }
- }
-
- for (uint16 i = 0; i < _shapeResources.size(); i++)
- _vm->_gfx->preloadImage(_shapeResources[i]);
-
- delete shapeStream;
-}
-
-void LBAnimation::draw() {
- for (uint32 i = 0; i < _nodes.size(); i++)
- _nodes[i]->draw(_bounds);
-}
-
-bool LBAnimation::update() {
- if (!_running)
- return false;
-
- if (_vm->_system->getMillis() <= _lastTime + (uint32)_tempo)
- return false;
-
- // the second check is to try 'catching up' with lagged animations, might be crazy
- if (_lastTime == 0 || (_vm->_system->getMillis()) > _lastTime + (uint32)(_tempo * 2))
- _lastTime = _vm->_system->getMillis();
- else
- _lastTime += _tempo;
-
- if (_currentSound != 0xffff && !_vm->_sound->isPlaying(_currentSound)) {
- _currentSound = 0xffff;
- }
-
- NodeState state = kLBNodeDone;
- for (uint32 i = 0; i < _nodes.size(); i++) {
- NodeState s = _nodes[i]->update();
- if (s == kLBNodeWaiting) {
- state = kLBNodeWaiting;
- if (i != 0)
- warning("non-primary node was waiting");
- break;
- }
- if (s == kLBNodeRunning)
- state = kLBNodeRunning;
- }
-
- if (state == kLBNodeRunning) {
- _currentFrame++;
- } else if (state == kLBNodeDone) {
- if (_currentSound == 0xffff) {
- _running = false;
- return true;
- }
- }
-
- return false;
-}
-
-void LBAnimation::start() {
- _lastTime = 0;
- _running = true;
-}
-
-void LBAnimation::seek(uint16 pos) {
- _lastTime = 0;
- _currentFrame = 0;
-
- if (_currentSound != 0xffff) {
- _vm->_sound->stopSound(_currentSound);
- _currentSound = 0xffff;
- }
-
- for (uint32 i = 0; i < _nodes.size(); i++)
- _nodes[i]->reset();
-
- for (uint16 n = 0; n < pos; n++) {
- bool ranSomething = false;
- // nodes don't wait while seeking
- for (uint32 i = 0; i < _nodes.size(); i++)
- ranSomething |= (_nodes[i]->update(true) != kLBNodeDone);
-
- _currentFrame++;
-
- if (!ranSomething) {
- _running = false;
- break;
- }
- }
-}
-
-void LBAnimation::seekToTime(uint32 time) {
- _lastTime = 0;
- _currentFrame = 0;
-
- if (_currentSound != 0xffff) {
- _vm->_sound->stopSound(_currentSound);
- _currentSound = 0xffff;
- }
-
- for (uint32 i = 0; i < _nodes.size(); i++)
- _nodes[i]->reset();
-
- uint32 elapsed = 0;
- while (elapsed <= time) {
- bool ranSomething = false;
- // nodes don't wait while seeking
- for (uint32 i = 0; i < _nodes.size(); i++)
- ranSomething |= (_nodes[i]->update(true) != kLBNodeDone);
-
- elapsed += _tempo;
- _currentFrame++;
-
- if (!ranSomething) {
- _running = false;
- break;
- }
- }
-}
-
-void LBAnimation::stop() {
- _running = false;
- if (_currentSound != 0xffff) {
- _vm->_sound->stopSound(_currentSound);
- _currentSound = 0xffff;
- }
-}
-
-void LBAnimation::playSound(uint16 resourceId) {
- _currentSound = resourceId;
- _vm->_sound->playSound(_currentSound, Audio::Mixer::kMaxChannelVolume, false, &_cueList);
-}
-
-bool LBAnimation::soundPlaying(uint16 resourceId, const Common::String &cue) {
- if (_currentSound != resourceId)
- return false;
- if (!_vm->_sound->isPlaying(_currentSound))
- return false;
-
- if (cue.empty())
- return true;
-
- uint samples = _vm->_sound->getNumSamplesPlayed(_currentSound);
- for (uint i = 0; i < _cueList.pointCount; i++) {
- if (_cueList.points[i].sampleFrame > samples)
- break;
- if (_cueList.points[i].name == cue)
- return false;
- }
-
- return true;
-}
-
-bool LBAnimation::transparentAt(int x, int y) {
- for (uint32 i = 0; i < _nodes.size(); i++)
- if (!_nodes[i]->transparentAt(x - _bounds.left, y - _bounds.top))
- return false;
-
- return true;
-}
-
-void LBAnimation::setTempo(uint16 tempo) {
- _tempo = tempo;
-}
-
-uint16 LBAnimation::getParentId() {
- return _parent->getId();
-}
-
-LBScriptEntry::LBScriptEntry() {
- state = 0;
- data = nullptr;
- argvParam = nullptr;
- argvTarget = nullptr;
-}
-
-LBScriptEntry::~LBScriptEntry() {
- delete[] argvParam;
- delete[] argvTarget;
- delete[] data;
-
- for (uint i = 0; i < subentries.size(); i++)
- delete subentries[i];
-}
-
-LBItem::LBItem(MohawkEngine_LivingBooks *vm, LBPage *page, Common::Rect rect) : _vm(vm), _page(page), _rect(rect) {
- if (_vm->getGameType() == GType_LIVINGBOOKSV1 || _vm->getGameType() == GType_LIVINGBOOKSV2)
- _phase = kLBPhaseInit;
- else
- _phase = kLBPhaseLoad;
-
- _loopMode = 0;
- _delayMin = 0;
- _delayMax = 0;
- _timingMode = kLBAutoNone;
- _periodMin = 0;
- _periodMax = 0;
- _controlMode = kLBControlNone;
- _soundMode = 0;
-
- _loaded = false;
- _enabled = false;
- _visible = true;
- _playing = false;
- _globalEnabled = true;
- _globalVisible = true;
- _nextTime = 0;
- _startTime = 0;
- _loops = 0;
-
- _isAmbient = false;
- _doHitTest = true;
+ _isAmbient = false;
+ _doHitTest = true;
}
LBItem::~LBItem() {
@@ -3146,857 +2033,4 @@ bool LBItem::checkCondition(const Common::String &condition) {
return result.toInt();
}
-LBSoundItem::LBSoundItem(MohawkEngine_LivingBooks *vm, LBPage *page, Common::Rect rect) : LBItem(vm, page, rect) {
- debug(3, "new LBSoundItem");
- _running = false;
-}
-
-LBSoundItem::~LBSoundItem() {
- if (_running)
- _vm->_sound->stopSound(_resourceId);
-}
-
-void LBSoundItem::update() {
- if (_running && !_vm->_sound->isPlaying(_resourceId)) {
- _running = false;
- done(true);
- }
-
- LBItem::update();
-}
-
-bool LBSoundItem::togglePlaying(bool playing, bool restart) {
- if (!playing)
- return LBItem::togglePlaying(playing, restart);
-
- if (_running) {
- _running = false;
- _vm->_sound->stopSound(_resourceId);
- }
-
- if (!_loaded || !_enabled || !_globalEnabled)
- return false;
-
- _running = true;
- debug(4, "sound %d play for item %d (%s)", _resourceId, _itemId, _desc.c_str());
- _vm->playSound(this, _resourceId);
- return true;
-}
-
-void LBSoundItem::stop() {
- if (_running) {
- _running = false;
- _vm->_sound->stopSound(_resourceId);
- }
-
- LBItem::stop();
-}
-
-LBItem *LBSoundItem::createClone() {
- return new LBSoundItem(_vm, _page, _rect);
-}
-
-LBGroupItem::LBGroupItem(MohawkEngine_LivingBooks *vm, LBPage *page, Common::Rect rect) : LBItem(vm, page, rect) {
- debug(3, "new LBGroupItem");
- _starting = false;
-}
-
-void LBGroupItem::readData(uint16 type, uint16 size, Common::MemoryReadStreamEndian *stream) {
- switch (type) {
- case kLBGroupData:
- {
- _groupEntries.clear();
- uint16 count = stream->readUint16();
- debug(3, "Group data: %d entries", count);
-
- if (size != 2 + count * 4)
- error("kLBGroupData was wrong size (%d, for %d entries)", size, count);
-
- for (uint i = 0; i < count; i++) {
- GroupEntry entry;
- // TODO: is type important for any game? at the moment, we ignore it
- entry.entryType = stream->readUint16();
- entry.entryId = stream->readUint16();
- // HACK: The Living Books v3 sampler includes the ID for the group as
- // one of the entries in the Green Eggs and Ham section, which leads
- // to infinite recursion when the group is loaded.
- if (entry.entryId != getId())
- _groupEntries.push_back(entry);
- debug(3, "group entry: id %d, type %d", entry.entryId, entry.entryType);
- }
- }
- break;
-
- default:
- LBItem::readData(type, size, stream);
- }
-}
-
-void LBGroupItem::destroySelf() {
- LBItem::destroySelf();
-
- for (uint i = 0; i < _groupEntries.size(); i++) {
- LBItem *item = _vm->getItemById(_groupEntries[i].entryId);
- if (item)
- item->destroySelf();
- }
-}
-
-void LBGroupItem::setEnabled(bool enabled) {
- if (_starting) {
- _starting = false;
- LBItem::setEnabled(enabled);
- } else {
- for (uint i = 0; i < _groupEntries.size(); i++) {
- LBItem *item = _vm->getItemById(_groupEntries[i].entryId);
- if (item)
- item->setEnabled(enabled);
- }
- }
-}
-
-void LBGroupItem::setGlobalEnabled(bool enabled) {
- for (uint i = 0; i < _groupEntries.size(); i++) {
- LBItem *item = _vm->getItemById(_groupEntries[i].entryId);
- if (item)
- item->setGlobalEnabled(enabled);
- }
-}
-
-bool LBGroupItem::contains(Common::Point point) {
- return false;
-}
-
-bool LBGroupItem::togglePlaying(bool playing, bool restart) {
- for (uint i = 0; i < _groupEntries.size(); i++) {
- LBItem *item = _vm->getItemById(_groupEntries[i].entryId);
- if (item)
- item->togglePlaying(playing, restart);
- }
-
- return false;
-}
-
-void LBGroupItem::seek(uint16 pos) {
- for (uint i = 0; i < _groupEntries.size(); i++) {
- LBItem *item = _vm->getItemById(_groupEntries[i].entryId);
- if (item)
- item->seek(pos);
- }
-}
-
-void LBGroupItem::setVisible(bool visible) {
- for (uint i = 0; i < _groupEntries.size(); i++) {
- LBItem *item = _vm->getItemById(_groupEntries[i].entryId);
- if (item)
- item->setVisible(visible);
- }
-}
-
-void LBGroupItem::setGlobalVisible(bool visible) {
- for (uint i = 0; i < _groupEntries.size(); i++) {
- LBItem *item = _vm->getItemById(_groupEntries[i].entryId);
- if (item)
- item->setGlobalVisible(visible);
- }
-}
-
-void LBGroupItem::startPhase(uint phase) {
- _starting = true;
- LBItem::startPhase(phase);
- _starting = false;
-}
-
-void LBGroupItem::stop() {
- for (uint i = 0; i < _groupEntries.size(); i++) {
- LBItem *item = _vm->getItemById(_groupEntries[i].entryId);
- if (item)
- item->stop();
- }
-}
-
-void LBGroupItem::load() {
- for (uint i = 0; i < _groupEntries.size(); i++) {
- LBItem *item = _vm->getItemById(_groupEntries[i].entryId);
- if (item)
- item->load();
- }
-}
-
-void LBGroupItem::unload() {
- for (uint i = 0; i < _groupEntries.size(); i++) {
- LBItem *item = _vm->getItemById(_groupEntries[i].entryId);
- if (item)
- item->unload();
- }
-}
-
-void LBGroupItem::moveBy(const Common::Point &pos) {
- for (uint i = 0; i < _groupEntries.size(); i++) {
- LBItem *item = _vm->getItemById(_groupEntries[i].entryId);
- if (item)
- item->moveBy(pos);
- }
-}
-
-void LBGroupItem::moveTo(const Common::Point &pos) {
- for (uint i = 0; i < _groupEntries.size(); i++) {
- LBItem *item = _vm->getItemById(_groupEntries[i].entryId);
- if (item)
- item->moveTo(pos);
- }
-}
-
-LBItem *LBGroupItem::createClone() {
- // TODO: needed?
- error("LBGroupItem::createClone unimplemented");
- return new LBGroupItem(_vm, _page, _rect);
-}
-
-LBPaletteItem::LBPaletteItem(MohawkEngine_LivingBooks *vm, LBPage *page, Common::Rect rect) : LBItem(vm, page, rect) {
- debug(3, "new LBPaletteItem");
-
- _fadeInStart = 0;
- _palette = nullptr;
-}
-
-LBPaletteItem::~LBPaletteItem() {
- delete[] _palette;
-}
-
-void LBPaletteItem::readData(uint16 type, uint16 size, Common::MemoryReadStreamEndian *stream) {
- switch (type) {
- case kLBPaletteXData:
- {
- assert(size >= 8);
- _fadeInPeriod = stream->readUint16();
- _fadeInStep = stream->readUint16();
- _drawStart = stream->readUint16();
- _drawCount = stream->readUint16();
- if (_drawStart + _drawCount > 256)
- error("encountered palette trying to set more than 256 colors");
- assert(size == 8 + _drawCount * 4);
-
- // TODO: _drawCount is really more like _drawEnd, so once we're sure that
- // there's really no use for the palette entries before _drawCount, we
- // might want to just discard them here, at load time.
- _palette = new byte[_drawCount * 3];
- for (uint i = 0; i < _drawCount; i++) {
- _palette[i*3 + 0] = stream->readByte();
- _palette[i*3 + 1] = stream->readByte();
- _palette[i*3 + 2] = stream->readByte();
- stream->readByte();
- }
- }
- break;
-
- default:
- LBItem::readData(type, size, stream);
- }
-}
-
-bool LBPaletteItem::togglePlaying(bool playing, bool restart) {
- // TODO: this likely isn't the right place
-
- if (playing) {
- _fadeInStart = _vm->_system->getMillis();
- _fadeInCurrent = 0;
-
- return true;
- }
-
- return LBItem::togglePlaying(playing, restart);
-}
-
-void LBPaletteItem::update() {
- if (_fadeInStart) {
- if (!_palette)
- error("LBPaletteItem had no palette on startup");
-
- uint32 elapsedTime = _vm->_system->getMillis() - _fadeInStart;
- uint32 divTime = elapsedTime / _fadeInStep;
-
- if (divTime > _fadeInPeriod)
- divTime = _fadeInPeriod;
-
- if (_fadeInCurrent != divTime) {
- _fadeInCurrent = divTime;
-
- // TODO: actual fading-in
- if (_visible && _globalVisible) {
- _vm->_system->getPaletteManager()->setPalette(_palette + _drawStart * 3, _drawStart, _drawCount - _drawStart);
- _vm->_needsRedraw = true;
- }
- }
-
- if (elapsedTime >= (uint32)_fadeInPeriod * (uint32)_fadeInStep) {
- // TODO: correct?
- _fadeInStart = 0;
- }
- }
-
- LBItem::update();
-}
-
-LBItem *LBPaletteItem::createClone() {
- error("can't clone LBPaletteItem");
-}
-
-LBLiveTextItem::LBLiveTextItem(MohawkEngine_LivingBooks *vm, LBPage *page, Common::Rect rect) : LBItem(vm, page, rect) {
- _currentPhrase = 0xFFFF;
- _currentWord = 0xFFFF;
- debug(3, "new LBLiveTextItem");
-}
-
-void LBLiveTextItem::readData(uint16 type, uint16 size, Common::MemoryReadStreamEndian *stream) {
- switch (type) {
- case kLBLiveTextData:
- {
- stream->read(_backgroundColor, 4); // unused?
- stream->read(_foregroundColor, 4);
- stream->read(_highlightColor, 4);
- _paletteIndex = stream->readUint16();
- uint16 phraseCount = stream->readUint16();
- uint16 wordCount = stream->readUint16();
-
- debug(3, "LiveText has %d words in %d phrases, palette index 0x%04x", wordCount, phraseCount, _paletteIndex);
- debug(3, "LiveText colors: background %02x%02x%02x%02x, foreground %02x%02x%02x%02x, highlight %02x%02x%02x%02x",
- _backgroundColor[0], _backgroundColor[1], _backgroundColor[2], _backgroundColor[3],
- _foregroundColor[0], _foregroundColor[1], _foregroundColor[2], _foregroundColor[3],
- _highlightColor[0], _highlightColor[1], _highlightColor[2], _highlightColor[3]);
-
- if (size != 18 + 14 * wordCount + 18 * phraseCount)
- error("Bad Live Text data size (got %d, wanted %d words and %d phrases)", size, wordCount, phraseCount);
-
- _words.clear();
- for (uint i = 0; i < wordCount; i++) {
- LiveTextWord word;
- word.bounds = _vm->readRect(stream);
- word.soundId = stream->readUint16();
- word.itemType = stream->readUint16();
- word.itemId = stream->readUint16();
- debug(4, "Word: (%d, %d) to (%d, %d), sound %d, item %d (type %d)",
- word.bounds.left, word.bounds.top, word.bounds.right, word.bounds.bottom, word.soundId, word.itemId, word.itemType);
- _words.push_back(word);
- }
-
- _phrases.clear();
- for (uint i = 0; i < phraseCount; i++) {
- LiveTextPhrase phrase;
- phrase.wordStart = stream->readUint16();
- phrase.wordCount = stream->readUint16();
- phrase.highlightStart = stream->readUint16();
- phrase.startId = stream->readUint16();
- phrase.highlightEnd = stream->readUint16();
- phrase.endId = stream->readUint16();
-
- // The original stored the values in uint32's so we need to swap here
- if (_vm->isBigEndian()) {
- SWAP(phrase.highlightStart, phrase.startId);
- SWAP(phrase.highlightEnd, phrase.endId);
- }
-
- uint32 unknown1 = stream->readUint16();
- uint16 unknown2 = stream->readUint32();
-
- if (unknown1 != 0 || unknown2 != 0)
- error("Unexpected unknowns %08x/%04x in LiveText word", unknown1, unknown2);
-
- debug(4, "Phrase: start %d, count %d, start at %d (from %d), end at %d (from %d)",
- phrase.wordStart, phrase.wordCount, phrase.highlightStart, phrase.startId, phrase.highlightEnd, phrase.endId);
-
- _phrases.push_back(phrase);
- }
- }
- break;
-
- default:
- LBItem::readData(type, size, stream);
- }
-}
-
-bool LBLiveTextItem::contains(Common::Point point) {
- if (!LBItem::contains(point))
- return false;
-
- point.x -= _rect.left;
- point.y -= _rect.top;
-
- for (uint i = 0; i < _words.size(); i++) {
- if (_words[i].bounds.contains(point))
- return true;
- }
-
- return false;
-}
-
-void LBLiveTextItem::paletteUpdate(uint16 word, bool on) {
- _vm->_needsRedraw = true;
-
- // Sometimes the last phrase goes out-of-bounds, the original engine
- // only checks the words which are valid in the palette updating code.
- if (word >= _words.size())
- return;
-
- if (_resourceId) {
- // with a resource, we draw a bitmap in draw() rather than changing the palette
- return;
- }
-
- if (on) {
- _vm->_system->getPaletteManager()->setPalette(_highlightColor, _paletteIndex + word, 1);
- } else {
- _vm->_system->getPaletteManager()->setPalette(_foregroundColor, _paletteIndex + word, 1);
- }
-}
-
-void LBLiveTextItem::update() {
- if (_currentWord != 0xFFFF) {
- uint16 soundId = _words[_currentWord].soundId;
- if (soundId && !_vm->_sound->isPlaying(soundId)) {
- paletteUpdate(_currentWord, false);
-
- // TODO: check this in RE
- LBItem *item = _vm->getItemById(_words[_currentWord].itemId);
- if (item)
- item->togglePlaying(false, true);
-
- _currentWord = 0xFFFF;
- }
- }
-
- LBItem::update();
-}
-
-void LBLiveTextItem::draw() {
- // this is only necessary when we are drawing using a bitmap
- if (!_resourceId)
- return;
-
- if (_currentWord != 0xFFFF) {
- uint yPos = 0;
- if (_currentWord > 0) {
- for (uint i = 0; i < _currentWord; i++) {
- yPos += (_words[i].bounds.bottom - _words[i].bounds.top);
- }
- }
- drawWord(_currentWord, yPos);
- return;
- }
-
- if (_currentPhrase == 0xFFFF)
- return;
-
- uint wordStart = _phrases[_currentPhrase].wordStart;
- uint wordCount = _phrases[_currentPhrase].wordCount;
- if (wordStart + wordCount > _words.size())
- error("phrase %d was invalid (%d words, from %d, out of only %d total)",
- _currentPhrase, wordCount, wordStart, _words.size());
-
- uint yPos = 0;
- for (uint i = 0; i < wordStart + wordCount; i++) {
- if (i >= wordStart)
- drawWord(i, yPos);
- yPos += (_words[i].bounds.bottom - _words[i].bounds.top);
- }
-}
-
-void LBLiveTextItem::drawWord(uint word, uint yPos) {
- Common::Rect srcRect(0, yPos, _words[word].bounds.right - _words[word].bounds.left,
- yPos + _words[word].bounds.bottom - _words[word].bounds.top);
- Common::Rect dstRect = _words[word].bounds;
- dstRect.translate(_rect.left, _rect.top);
- _vm->_gfx->copyAnimImageSectionToScreen(_resourceId, srcRect, dstRect);
-}
-
-void LBLiveTextItem::handleMouseDown(Common::Point pos) {
- if (!_loaded || !_enabled || !_globalEnabled || _playing)
- return LBItem::handleMouseDown(pos);
-
- pos.x -= _rect.left;
- pos.y -= _rect.top;
-
- for (uint i = 0; i < _words.size(); i++) {
- if (_words[i].bounds.contains(pos)) {
- if (_currentWord != 0xFFFF) {
- paletteUpdate(_currentWord, false);
- _currentWord = 0xFFFF;
- }
- uint16 soundId = _words[i].soundId;
- if (!soundId) {
- // TODO: can we be smarter here, using timing?
- warning("ignoring click due to no soundId");
- return;
- }
- _currentWord = i;
- _vm->playSound(this, soundId);
- paletteUpdate(_currentWord, true);
- return;
- }
- }
-
- return LBItem::handleMouseDown(pos);
-}
-
-bool LBLiveTextItem::togglePlaying(bool playing, bool restart) {
- if (!playing)
- return LBItem::togglePlaying(playing, restart);
- if (!_loaded || !_enabled || !_globalEnabled)
- return _playing;
-
- // TODO: handle this properly
- _vm->_sound->stopSound();
-
- _currentWord = 0xFFFF;
- _currentPhrase = 0xFFFF;
-
- return true;
-}
-
-void LBLiveTextItem::stop() {
- // TODO: stop sound, refresh palette
-
- LBItem::stop();
-}
-
-void LBLiveTextItem::notify(uint16 data, uint16 from) {
- if (!_loaded || !_enabled || !_globalEnabled || !_playing)
- return LBItem::notify(data, from);
-
- if (_currentWord != 0xFFFF) {
- // TODO: handle this properly
- _vm->_sound->stopSound();
- paletteUpdate(_currentWord, false);
- _currentWord = 0xFFFF;
- }
-
- for (uint i = 0; i < _phrases.size(); i++) {
- if (_phrases[i].highlightStart == data && _phrases[i].startId == from) {
- debug(2, "Enabling phrase %d", i);
- for (uint j = 0; j < _phrases[i].wordCount; j++) {
- paletteUpdate(_phrases[i].wordStart + j, true);
- }
- _currentPhrase = i;
- // TODO: not sure this is the correct logic
- if (i == _phrases.size() - 1) {
- _currentPhrase = 0xFFFF;
- done(true);
- }
- } else if (_phrases[i].highlightEnd == data && _phrases[i].endId == from) {
- debug(2, "Disabling phrase %d", i);
- for (uint j = 0; j < _phrases[i].wordCount; j++) {
- paletteUpdate(_phrases[i].wordStart + j, false);
- }
- _currentPhrase = 0xFFFF;
- }
- }
-
- LBItem::notify(data, from);
-}
-
-LBItem *LBLiveTextItem::createClone() {
- error("can't clone LBLiveTextItem");
-}
-
-LBPictureItem::LBPictureItem(MohawkEngine_LivingBooks *vm, LBPage *page, Common::Rect rect) : LBItem(vm, page, rect) {
- debug(3, "new LBPictureItem");
-}
-
-void LBPictureItem::readData(uint16 type, uint16 size, Common::MemoryReadStreamEndian *stream) {
- switch (type) {
- case kLBSetDrawMode:
- {
- assert(size == 2);
- // TODO: this probably sets whether points are always contained (0x10)
- // or whether the bitmap contents are checked (00, or anything else?)
- uint16 val = stream->readUint16();
- debug(2, "LBPictureItem: kLBSetDrawMode: %04x", val);
- }
- break;
-
- default:
- LBItem::readData(type, size, stream);
- }
-}
-
-bool LBPictureItem::contains(Common::Point point) {
- if (!LBItem::contains(point))
- return false;
-
- if (!_doHitTest)
- return true;
-
- // TODO: only check pixels if necessary
- return !_vm->_gfx->imageIsTransparentAt(_resourceId, false, point.x - _rect.left, point.y - _rect.top);
-}
-
-void LBPictureItem::init() {
- _vm->_gfx->preloadImage(_resourceId);
-
- LBItem::init();
-}
-
-void LBPictureItem::draw() {
- if (!_loaded || !_visible || !_globalVisible)
- return;
-
- _vm->_gfx->copyAnimImageToScreen(_resourceId, _rect.left, _rect.top);
-}
-
-LBItem *LBPictureItem::createClone() {
- return new LBPictureItem(_vm, _page, _rect);
-}
-
-LBAnimationItem::LBAnimationItem(MohawkEngine_LivingBooks *vm, LBPage *page, Common::Rect rect) : LBItem(vm, page, rect) {
- _anim = nullptr;
- _running = false;
- debug(3, "new LBAnimationItem");
-}
-
-LBAnimationItem::~LBAnimationItem() {
- delete _anim;
-}
-
-void LBAnimationItem::setEnabled(bool enabled) {
- if (_running) {
- if (enabled && _globalEnabled && !_loaded)
- _anim->start();
- else if (_loaded && !enabled && _enabled && _globalEnabled)
- _anim->stop();
- }
-
- return LBItem::setEnabled(enabled);
-}
-
-bool LBAnimationItem::contains(Common::Point point) {
- if (!LBItem::contains(point))
- return false;
-
- if (!_doHitTest)
- return true;
-
- return !_anim->transparentAt(point.x, point.y);
-}
-
-void LBAnimationItem::update() {
- if (_loaded && _enabled && _globalEnabled && _running) {
- bool wasDone = _anim->update();
- if (wasDone) {
- _running = false;
- done(true);
- }
- }
-
- LBItem::update();
-}
-
-bool LBAnimationItem::togglePlaying(bool playing, bool restart) {
- if (playing) {
- if (_loaded && _enabled && _globalEnabled) {
- if (restart)
- seek(1);
- _running = true;
- _anim->start();
- }
-
- return _running;
- }
-
- return LBItem::togglePlaying(playing, restart);
-}
-
-void LBAnimationItem::done(bool onlyNotify) {
- if (!onlyNotify) {
- _anim->stop();
- }
-
- LBItem::done(onlyNotify);
-}
-
-void LBAnimationItem::init() {
- _anim = new LBAnimation(_vm, this, _resourceId);
-
- LBItem::init();
-}
-
-void LBAnimationItem::stop() {
- if (_running) {
- _anim->stop();
- seek(0xFFFF);
- }
-
- _running = false;
-
- LBItem::stop();
-}
-
-void LBAnimationItem::seek(uint16 pos) {
- _anim->seek(pos);
-}
-
-void LBAnimationItem::seekToTime(uint32 time) {
- _anim->seekToTime(time);
-}
-
-void LBAnimationItem::startPhase(uint phase) {
- if (phase == _phase)
- seek(1);
-
- LBItem::startPhase(phase);
-}
-
-void LBAnimationItem::draw() {
- if (!_visible || !_globalVisible)
- return;
-
- _anim->draw();
-}
-
-LBItem *LBAnimationItem::createClone() {
- LBAnimationItem *item = new LBAnimationItem(_vm, _page, _rect);
- item->_anim = new LBAnimation(_vm, item, _resourceId);
- return item;
-}
-
-LBMovieItem::LBMovieItem(MohawkEngine_LivingBooks *vm, LBPage *page, Common::Rect rect) : LBItem(vm, page, rect) {
- debug(3, "new LBMovieItem");
-}
-
-LBMovieItem::~LBMovieItem() {
-}
-
-void LBMovieItem::update() {
- if (_playing) {
- VideoEntryPtr video = _vm->_video->findVideo(_resourceId);
- if (!video || video->endOfVideo())
- done(true);
- }
-
- LBItem::update();
-}
-
-bool LBMovieItem::togglePlaying(bool playing, bool restart) {
- if (playing) {
- if ((_loaded && _enabled && _globalEnabled) || _phase == kLBPhaseNone) {
- debug("toggled video for phase %d", _phase);
- VideoEntryPtr video = _vm->_video->playMovie(_resourceId);
- if (!video)
- error("Failed to open tMOV %d", _resourceId);
-
- video->moveTo(_rect.left, _rect.top);
- return true;
- }
- }
-
- return LBItem::togglePlaying(playing, restart);
-}
-
-LBItem *LBMovieItem::createClone() {
- return new LBMovieItem(_vm, _page, _rect);
-}
-
-LBMiniGameItem::LBMiniGameItem(MohawkEngine_LivingBooks *vm, LBPage *page, Common::Rect rect) : LBItem(vm, page, rect) {
- debug(3, "new LBMiniGameItem");
-}
-
-LBMiniGameItem::~LBMiniGameItem() {
-}
-
-bool LBMiniGameItem::togglePlaying(bool playing, bool restart) {
- // HACK: Since we don't support any of these hardcoded mini games yet,
- // just skip to the most logical page. For optional minigames, this
- // will return the player to the previous page. For mandatory minigames,
- // this will send the player to the next page.
-
- uint16 destPage = 0;
- bool returnToMenu = false;
-
- // Figure out what minigame we have and bring us back to a page where
- // the player can continue
- if (_desc == "Kitch") // Green Eggs and Ham: Kitchen minigame
- destPage = 4;
- else if (_desc == "Eggs") // Green Eggs and Ham: Eggs minigame
- destPage = 5;
- else if (_desc == "Fall") // Green Eggs and Ham: Fall minigame
- destPage = 13;
- else if (_desc == "MagicWrite3") // Arthur's Reading Race: "Let Me Write" minigame (Page 3)
- destPage = 3;
- else if (_desc == "MagicWrite4") // Arthur's Reading Race: "Let Me Write" minigame (Page 4)
- destPage = 4;
- else if (_desc == "MagicSpy5") // Arthur's Reading Race: "I Spy" minigame (Page 5)
- destPage = 5;
- else if (_desc == "MagicSpy6") // Arthur's Reading Race: "I Spy" minigame (Page 6)
- destPage = 6;
- else if (_desc == "MagicWrite7") // Arthur's Reading Race: "Let Me Write" minigame (Page 7)
- destPage = 7;
- else if (_desc == "MagicSpy8") // Arthur's Reading Race: "I Spy" minigame (Page 8)
- destPage = 8;
- else if (_desc == "MagicRace") // Arthur's Reading Race: Race minigame
- returnToMenu = true;
- else
- error("Unknown minigame '%s'", _desc.c_str());
-
- GUI::MessageDialog dialog(Common::String::format("The '%s' minigame is not supported yet.", _desc.c_str()));
- dialog.runModal();
-
- // Go back to the menu if requested, otherwise go to the requested page
- if (returnToMenu)
- _vm->addNotifyEvent(NotifyEvent(kLBNotifyGoToControls, 1));
- else
- _vm->addNotifyEvent(NotifyEvent(kLBNotifyChangePage, destPage));
-
- return false;
-}
-
-LBItem *LBMiniGameItem::createClone() {
- error("can't clone LBMiniGameItem");
-}
-
-LBProxyItem::LBProxyItem(MohawkEngine_LivingBooks *vm, LBPage *page, Common::Rect rect) : LBItem(vm, page, rect) {
- debug(3, "new LBProxyItem");
-
- _page = nullptr;
-}
-
-LBProxyItem::~LBProxyItem() {
- delete _page;
-}
-
-void LBProxyItem::load() {
- if (_loaded)
- return;
-
- Common::String leftover;
- Common::String filename = _vm->getFileNameFromConfig("Proxies", _desc.c_str(), leftover);
- if (!leftover.empty())
- error("LBProxyItem tried loading proxy '%s' but got leftover '%s'", _desc.c_str(), leftover.c_str());
- uint16 baseId = 0;
- for (uint i = 0; i < filename.size(); i++) {
- if (filename[i] == ';') {
- baseId = atoi(filename.c_str() + i + 1);
- filename = Common::String(filename.c_str(), i);
- }
- }
-
- debug(1, "LBProxyItem loading archive '%s' with id %d", filename.c_str(), baseId);
- Archive *pageArchive = _vm->createArchive();
- if (!tryOpenPage(pageArchive, filename))
- error("failed to open archive '%s' (for proxy '%s')", filename.c_str(), _desc.c_str());
- _page = new LBPage(_vm);
- _page->open(pageArchive, baseId);
-
- LBItem::load();
-}
-
-void LBProxyItem::unload() {
- delete _page;
- _page = nullptr;
-
- LBItem::unload();
-}
-
-LBItem *LBProxyItem::createClone() {
- return new LBProxyItem(_vm, _page, _rect);
-}
-
} // End of namespace Mohawk
diff --git a/engines/mohawk/livingbooks_animation.cpp b/engines/mohawk/livingbooks_animation.cpp
new file mode 100644
index 00000000000..33c904c85fd
--- /dev/null
+++ b/engines/mohawk/livingbooks_animation.cpp
@@ -0,0 +1,704 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "mohawk/livingbooks.h"
+#include "mohawk/resource.h"
+
+#include "common/system.h"
+#include "common/textconsole.h"
+
+namespace Mohawk {
+
+// SCRP - Animation sprites/background
+
+LBAnimationNode::LBAnimationNode(MohawkEngine_LivingBooks *vm, LBAnimation *parent, uint16 scriptResourceId) : _vm(vm), _parent(parent) {
+ loadScript(scriptResourceId);
+}
+
+LBAnimationNode::~LBAnimationNode() {
+ for (uint32 i = 0; i < _scriptEntries.size(); i++)
+ delete[] _scriptEntries[i].data;
+}
+
+void LBAnimationNode::loadScript(uint16 resourceId) {
+ Common::SeekableReadStreamEndian *scriptStream = _vm->wrapStreamEndian(ID_SCRP, resourceId);
+
+ reset();
+
+ while (byte opcodeId = scriptStream->readByte()) {
+ byte size = scriptStream->readByte();
+
+ LBAnimScriptEntry entry;
+ entry.opcode = opcodeId;
+ entry.size = size;
+
+ if (!size) {
+ entry.data = nullptr;
+ } else {
+ entry.data = new byte[entry.size];
+ scriptStream->read(entry.data, entry.size);
+ }
+
+ _scriptEntries.push_back(entry);
+ }
+
+ byte size = scriptStream->readByte();
+ if (size != 0 || scriptStream->pos() != scriptStream->size())
+ error("Failed to read script correctly");
+
+ delete scriptStream;
+}
+
+void LBAnimationNode::draw(const Common::Rect &_bounds) {
+ if (!_currentCel)
+ return;
+
+ // this is also checked in SetCel, below
+ if (_currentCel > _parent->getNumResources())
+ error("Animation cel %d was too high, this shouldn't happen!", _currentCel);
+
+ int16 xOffset = _xPos + _bounds.left;
+ int16 yOffset = _yPos + _bounds.top;
+
+ uint16 resourceId = _parent->getResource(_currentCel - 1);
+
+ if (!_vm->isPreMohawk()) {
+ Common::Point offset = _parent->getOffset(_currentCel - 1);
+ xOffset -= offset.x;
+ yOffset -= offset.y;
+ }
+
+ _vm->_gfx->copyOffsetAnimImageToScreen(resourceId, xOffset, yOffset);
+}
+
+void LBAnimationNode::reset() {
+ // TODO: this causes stupid flickering
+ //if (_currentCel)
+ // _vm->_needsRedraw = true;
+
+ _currentCel = 0;
+ _currentEntry = 0;
+ _delay = 0;
+
+ _xPos = 0;
+ _yPos = 0;
+}
+
+NodeState LBAnimationNode::update(bool seeking) {
+ if (_currentEntry == _scriptEntries.size())
+ return kLBNodeDone;
+
+ if (_delay > 0 && --_delay)
+ return kLBNodeRunning;
+
+ while (_currentEntry < _scriptEntries.size()) {
+ LBAnimScriptEntry &entry = _scriptEntries[_currentEntry];
+ _currentEntry++;
+ debug(5, "Running script entry %d of %d", _currentEntry, _scriptEntries.size());
+
+ switch (entry.opcode) {
+ case kLBAnimOpPlaySound:
+ case kLBAnimOpWaitForSound:
+ case kLBAnimOpReleaseSound:
+ case kLBAnimOpResetSound:
+ {
+ uint16 soundResourceId = READ_BE_UINT16(entry.data);
+
+ if (!soundResourceId) {
+ error("Unhandled named wave file, tell clone2727 where you found this");
+ break;
+ }
+
+ Common::String cue;
+ uint pos = 2;
+ while (pos < entry.size) {
+ char in = entry.data[pos];
+ if (!in)
+ break;
+ pos++;
+ cue += in;
+ }
+ if (pos == entry.size)
+ error("Cue in sound kLBAnimOp wasn't null-terminated");
+
+ switch (entry.opcode) {
+ case kLBAnimOpPlaySound:
+ if (seeking)
+ break;
+ debug(4, "a: PlaySound(%0d)", soundResourceId);
+ _parent->playSound(soundResourceId);
+ break;
+ case kLBAnimOpWaitForSound:
+ if (seeking)
+ break;
+ debug(4, "b: WaitForSound(%0d)", soundResourceId);
+ if (!_parent->soundPlaying(soundResourceId, cue))
+ break;
+ _currentEntry--;
+ return kLBNodeWaiting;
+ case kLBAnimOpReleaseSound:
+ debug(4, "c: ReleaseSound(%0d)", soundResourceId);
+ // TODO
+ _vm->_sound->stopSound(soundResourceId);
+ break;
+ case kLBAnimOpResetSound:
+ debug(4, "d: ResetSound(%0d)", soundResourceId);
+ // TODO
+ _vm->_sound->stopSound(soundResourceId);
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+
+ case kLBAnimOpSetTempo:
+ case kLBAnimOpSetTempoDiv:
+ {
+ assert(entry.size == 2);
+ uint16 tempo = (int16)READ_BE_UINT16(entry.data);
+
+ // TODO: LB 3 uses fixed-point here.
+ if (entry.opcode == kLBAnimOpSetTempo) {
+ debug(4, "3: SetTempo(%d)", tempo);
+ // TODO: LB 3 uses (tempo * 1000) / 60, while
+ // the original divides the system time by 16.
+ _parent->setTempo(tempo * 16);
+ } else {
+ // LB 3.0+ only.
+ debug(4, "E: SetTempoDiv(%d)", tempo);
+ _parent->setTempo(1000 / tempo);
+ }
+
+ }
+ break;
+
+ case kLBAnimOpWait:
+ assert(entry.size == 0);
+ debug(5, "6: Wait()");
+ return kLBNodeRunning;
+
+ case kLBAnimOpMoveTo:
+ {
+ assert(entry.size == 4);
+ int16 x = (int16)READ_BE_UINT16(entry.data);
+ int16 y = (int16)READ_BE_UINT16(entry.data + 2);
+ debug(4, "5: MoveTo(%d, %d)", x, y);
+
+ _xPos = x;
+ _yPos = y;
+ _vm->_needsRedraw = true;
+ }
+ break;
+
+ case kLBAnimOpDrawMode:
+ {
+ assert(entry.size == 2);
+ uint16 mode = (int16)READ_BE_UINT16(entry.data);
+ debug(4, "9: DrawMode(%d)", mode);
+
+ // TODO
+ }
+ break;
+
+ case kLBAnimOpSetCel:
+ {
+ assert(entry.size == 2);
+ uint16 cel = (int16)READ_BE_UINT16(entry.data);
+ debug(4, "7: SetCel(%d)", cel);
+
+ _currentCel = cel;
+ if (_currentCel > _parent->getNumResources())
+ error("SetCel set current cel to %d, but we only have %d cels", _currentCel, _parent->getNumResources());
+ _vm->_needsRedraw = true;
+ }
+ break;
+
+ case kLBAnimOpNotify:
+ {
+ assert(entry.size == 2);
+ uint16 data = (int16)READ_BE_UINT16(entry.data);
+
+ if (seeking)
+ break;
+
+ debug(4, "2: Notify(%d)", data);
+ _vm->notifyAll(data, _parent->getParentId());
+ }
+ break;
+
+ case kLBAnimOpSleepUntil:
+ {
+ assert(entry.size == 4);
+ uint32 frame = READ_BE_UINT32(entry.data);
+ debug(4, "8: SleepUntil(%d)", frame);
+
+ if (frame > _parent->getCurrentFrame()) {
+ // *not* kLBNodeWaiting
+ _currentEntry--;
+ return kLBNodeRunning;
+ }
+ }
+ break;
+
+ case kLBAnimOpDelay:
+ {
+ assert(entry.size == 4);
+ uint32 delay = READ_BE_UINT32(entry.data);
+ debug(4, "f: Delay(%d)", delay);
+ _delay = delay;
+ return kLBNodeRunning;
+ }
+ break;
+
+ default:
+ error("Unknown opcode id %02x (size %d)", entry.opcode, entry.size);
+ break;
+ }
+ }
+
+ return kLBNodeRunning;
+}
+
+bool LBAnimationNode::transparentAt(int x, int y) {
+ if (!_currentCel)
+ return true;
+
+ uint16 resourceId = _parent->getResource(_currentCel - 1);
+
+ if (!_vm->isPreMohawk()) {
+ Common::Point offset = _parent->getOffset(_currentCel - 1);
+ x += offset.x;
+ y += offset.y;
+ }
+
+ // TODO: only check pixels if necessary
+ return _vm->_gfx->imageIsTransparentAt(resourceId, true, x - _xPos, y - _yPos);
+}
+
+LBAnimation::LBAnimation(MohawkEngine_LivingBooks *vm, LBAnimationItem *parent, uint16 resourceId) : _vm(vm), _parent(parent) {
+ Common::SeekableReadStreamEndian *aniStream = _vm->wrapStreamEndian(ID_ANI, resourceId);
+
+ // ANI records in the Wanderful sampler are 32 bytes, extra bytes are just NULs
+ if (aniStream->size() != 30 && aniStream->size() != 32)
+ warning("ANI Record size mismatch");
+
+ uint16 version = aniStream->readUint16();
+ if (version != 1)
+ warning("ANI version not 1");
+
+ _bounds = _vm->readRect(aniStream);
+ _clip = _vm->readRect(aniStream);
+ // TODO: what is colorId for?
+ uint32 colorId = aniStream->readUint32();
+ uint32 sprResourceId = aniStream->readUint32();
+ uint32 sprResourceOffset = aniStream->readUint32();
+
+ debug(5, "ANI bounds: (%d, %d), (%d, %d)", _bounds.left, _bounds.top, _bounds.right, _bounds.bottom);
+ debug(5, "ANI clip: (%d, %d), (%d, %d)", _clip.left, _clip.top, _clip.right, _clip.bottom);
+ debug(5, "ANI color id: %d", colorId);
+ debug(5, "ANI SPRResourceId: %d, offset %d", sprResourceId, sprResourceOffset);
+ if (aniStream->size() == 32) {
+ debug(5, "ANI extra bytes: (%d)", aniStream->readUint16());
+ }
+
+ if (aniStream->pos() != aniStream->size())
+ error("Still %d bytes at the end of anim stream", (int)(aniStream->size() - aniStream->pos()));
+
+ delete aniStream;
+
+ if (sprResourceOffset)
+ error("Cannot handle non-zero ANI offset yet");
+
+ Common::SeekableReadStreamEndian *sprStream = _vm->wrapStreamEndian(ID_SPR, sprResourceId);
+
+ uint16 numBackNodes = sprStream->readUint16();
+ uint16 numFrontNodes = sprStream->readUint16();
+ uint32 shapeResourceID = sprStream->readUint32();
+ uint32 shapeResourceOffset = sprStream->readUint32();
+ uint32 scriptResourceID = sprStream->readUint32();
+ uint32 scriptResourceOffset = sprStream->readUint32();
+ uint32 scriptResourceLength = sprStream->readUint32();
+ debug(5, "SPR# stream: %d front, %d background", numFrontNodes, numBackNodes);
+ debug(5, "Shape ID %d (offset 0x%04x), script ID %d (offset 0x%04x, length %d)", shapeResourceID, shapeResourceOffset,
+ scriptResourceID, scriptResourceOffset, scriptResourceLength);
+
+ Common::Array<uint16> scriptIDs;
+ for (uint16 i = 0; i < numFrontNodes; i++) {
+ uint32 unknown1 = sprStream->readUint32();
+ uint32 unknown2 = sprStream->readUint32();
+ uint32 unknown3 = sprStream->readUint32();
+ uint16 scriptID = sprStream->readUint32();
+ uint32 unknown4 = sprStream->readUint32();
+ uint32 unknown5 = sprStream->readUint32();
+ scriptIDs.push_back(scriptID);
+ debug(6, "Front node %d: script ID %d", i, scriptID);
+ if (unknown1 != 0 || unknown2 != 0 || unknown3 != 0 || unknown4 != 0 || unknown5 != 0)
+ error("Anim node %d had non-zero unknowns %08x, %08x, %08x, %08x, %08x",
+ i, unknown1, unknown2, unknown3, unknown4, unknown5);
+ }
+
+ if (numBackNodes)
+ error("Ignoring %d back nodes", numBackNodes);
+
+ if (sprStream->pos() != sprStream->size())
+ error("Still %d bytes at the end of sprite stream", (int)(sprStream->size() - sprStream->pos()));
+
+ delete sprStream;
+
+ loadShape(shapeResourceID);
+
+ _nodes.push_back(new LBAnimationNode(_vm, this, scriptResourceID));
+ for (uint16 i = 0; i < scriptIDs.size(); i++)
+ _nodes.push_back(new LBAnimationNode(_vm, this, scriptIDs[i]));
+
+ _currentFrame = 0;
+ _currentSound = 0xffff;
+ _running = false;
+ _tempo = 1;
+}
+
+LBAnimation::~LBAnimation() {
+ for (uint32 i = 0; i < _nodes.size(); i++)
+ delete _nodes[i];
+ if (_currentSound != 0xffff)
+ _vm->_sound->stopSound(_currentSound);
+}
+
+void LBAnimation::loadShape(uint16 resourceId) {
+ if (resourceId == 0)
+ return;
+
+ Common::SeekableReadStreamEndian *shapeStream = _vm->wrapStreamEndian(ID_SHP, resourceId);
+
+ if (_vm->isPreMohawk()) {
+ if (shapeStream->size() < 6)
+ error("V1 SHP Record size too short (%d)", (int)shapeStream->size());
+
+ uint16 u0 = shapeStream->readUint16();
+ if (u0 != 3)
+ error("V1 SHP Record u0 is %04x, not 3", u0);
+
+ uint16 u1 = shapeStream->readUint16();
+ if (u1 != 0)
+ error("V1 SHP Record u1 is %04x, not 0", u1);
+
+ uint16 idCount = shapeStream->readUint16();
+ debug(8, "V1 SHP: idCount: %d", idCount);
+
+ if (shapeStream->size() != (idCount * 2) + 6)
+ error("V1 SHP Record size mismatch (%d)", (int)shapeStream->size());
+
+ for (uint16 i = 0; i < idCount; i++) {
+ _shapeResources.push_back(shapeStream->readUint16());
+ debug(8, "V1 SHP: BMAP Resource Id %d: %d", i, _shapeResources[i]);
+ }
+ } else {
+ uint16 idCount = shapeStream->readUint16();
+ debug(8, "SHP: idCount: %d", idCount);
+
+ if (shapeStream->size() != (idCount * 6) + 2)
+ error("SHP Record size mismatch (%d)", (int)shapeStream->size());
+
+ for (uint16 i = 0; i < idCount; i++) {
+ _shapeResources.push_back(shapeStream->readUint16());
+ int16 x = shapeStream->readSint16();
+ int16 y = shapeStream->readSint16();
+ _shapeOffsets.push_back(Common::Point(x, y));
+ debug(8, "SHP: tBMP Resource Id %d: %d, at (%d, %d)", i, _shapeResources[i], x, y);
+ }
+ }
+
+ for (uint16 i = 0; i < _shapeResources.size(); i++)
+ _vm->_gfx->preloadImage(_shapeResources[i]);
+
+ delete shapeStream;
+}
+
+void LBAnimation::draw() {
+ for (uint32 i = 0; i < _nodes.size(); i++)
+ _nodes[i]->draw(_bounds);
+}
+
+bool LBAnimation::update() {
+ if (!_running)
+ return false;
+
+ if (_vm->_system->getMillis() <= _lastTime + (uint32)_tempo)
+ return false;
+
+ // the second check is to try 'catching up' with lagged animations, might be crazy
+ if (_lastTime == 0 || (_vm->_system->getMillis()) > _lastTime + (uint32)(_tempo * 2))
+ _lastTime = _vm->_system->getMillis();
+ else
+ _lastTime += _tempo;
+
+ if (_currentSound != 0xffff && !_vm->_sound->isPlaying(_currentSound)) {
+ _currentSound = 0xffff;
+ }
+
+ NodeState state = kLBNodeDone;
+ for (uint32 i = 0; i < _nodes.size(); i++) {
+ NodeState s = _nodes[i]->update();
+ if (s == kLBNodeWaiting) {
+ state = kLBNodeWaiting;
+ if (i != 0)
+ warning("non-primary node was waiting");
+ break;
+ }
+ if (s == kLBNodeRunning)
+ state = kLBNodeRunning;
+ }
+
+ if (state == kLBNodeRunning) {
+ _currentFrame++;
+ } else if (state == kLBNodeDone) {
+ if (_currentSound == 0xffff) {
+ _running = false;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void LBAnimation::start() {
+ _lastTime = 0;
+ _running = true;
+}
+
+void LBAnimation::seek(uint16 pos) {
+ _lastTime = 0;
+ _currentFrame = 0;
+
+ if (_currentSound != 0xffff) {
+ _vm->_sound->stopSound(_currentSound);
+ _currentSound = 0xffff;
+ }
+
+ for (uint32 i = 0; i < _nodes.size(); i++)
+ _nodes[i]->reset();
+
+ for (uint16 n = 0; n < pos; n++) {
+ bool ranSomething = false;
+ // nodes don't wait while seeking
+ for (uint32 i = 0; i < _nodes.size(); i++)
+ ranSomething |= (_nodes[i]->update(true) != kLBNodeDone);
+
+ _currentFrame++;
+
+ if (!ranSomething) {
+ _running = false;
+ break;
+ }
+ }
+}
+
+void LBAnimation::seekToTime(uint32 time) {
+ _lastTime = 0;
+ _currentFrame = 0;
+
+ if (_currentSound != 0xffff) {
+ _vm->_sound->stopSound(_currentSound);
+ _currentSound = 0xffff;
+ }
+
+ for (uint32 i = 0; i < _nodes.size(); i++)
+ _nodes[i]->reset();
+
+ uint32 elapsed = 0;
+ while (elapsed <= time) {
+ bool ranSomething = false;
+ // nodes don't wait while seeking
+ for (uint32 i = 0; i < _nodes.size(); i++)
+ ranSomething |= (_nodes[i]->update(true) != kLBNodeDone);
+
+ elapsed += _tempo;
+ _currentFrame++;
+
+ if (!ranSomething) {
+ _running = false;
+ break;
+ }
+ }
+}
+
+void LBAnimation::stop() {
+ _running = false;
+ if (_currentSound != 0xffff) {
+ _vm->_sound->stopSound(_currentSound);
+ _currentSound = 0xffff;
+ }
+}
+
+void LBAnimation::playSound(uint16 resourceId) {
+ _currentSound = resourceId;
+ _vm->_sound->playSound(_currentSound, Audio::Mixer::kMaxChannelVolume, false, &_cueList);
+}
+
+bool LBAnimation::soundPlaying(uint16 resourceId, const Common::String &cue) {
+ if (_currentSound != resourceId)
+ return false;
+ if (!_vm->_sound->isPlaying(_currentSound))
+ return false;
+
+ if (cue.empty())
+ return true;
+
+ uint samples = _vm->_sound->getNumSamplesPlayed(_currentSound);
+ for (uint i = 0; i < _cueList.pointCount; i++) {
+ if (_cueList.points[i].sampleFrame > samples)
+ break;
+ if (_cueList.points[i].name == cue)
+ return false;
+ }
+
+ return true;
+}
+
+bool LBAnimation::transparentAt(int x, int y) {
+ for (uint32 i = 0; i < _nodes.size(); i++)
+ if (!_nodes[i]->transparentAt(x - _bounds.left, y - _bounds.top))
+ return false;
+
+ return true;
+}
+
+void LBAnimation::setTempo(uint16 tempo) {
+ _tempo = tempo;
+}
+
+uint16 LBAnimation::getParentId() {
+ return _parent->getId();
+}
+
+LBAnimationItem::LBAnimationItem(MohawkEngine_LivingBooks *vm, LBPage *page, Common::Rect rect) : LBItem(vm, page, rect) {
+ _anim = nullptr;
+ _running = false;
+ debug(3, "new LBAnimationItem");
+}
+
+LBAnimationItem::~LBAnimationItem() {
+ delete _anim;
+}
+
+void LBAnimationItem::setEnabled(bool enabled) {
+ if (_running) {
+ if (enabled && _globalEnabled && !_loaded)
+ _anim->start();
+ else if (_loaded && !enabled && _enabled && _globalEnabled)
+ _anim->stop();
+ }
+
+ return LBItem::setEnabled(enabled);
+}
+
+bool LBAnimationItem::contains(Common::Point point) {
+ if (!LBItem::contains(point))
+ return false;
+
+ if (!_doHitTest)
+ return true;
+
+ return !_anim->transparentAt(point.x, point.y);
+}
+
+void LBAnimationItem::update() {
+ if (_loaded && _enabled && _globalEnabled && _running) {
+ bool wasDone = _anim->update();
+ if (wasDone) {
+ _running = false;
+ done(true);
+ }
+ }
+
+ LBItem::update();
+}
+
+bool LBAnimationItem::togglePlaying(bool playing, bool restart) {
+ if (playing) {
+ if (_loaded && _enabled && _globalEnabled) {
+ if (restart)
+ seek(1);
+ _running = true;
+ _anim->start();
+ }
+
+ return _running;
+ }
+
+ return LBItem::togglePlaying(playing, restart);
+}
+
+void LBAnimationItem::done(bool onlyNotify) {
+ if (!onlyNotify) {
+ _anim->stop();
+ }
+
+ LBItem::done(onlyNotify);
+}
+
+void LBAnimationItem::init() {
+ _anim = new LBAnimation(_vm, this, _resourceId);
+
+ LBItem::init();
+}
+
+void LBAnimationItem::stop() {
+ if (_running) {
+ _anim->stop();
+ seek(0xFFFF);
+ }
+
+ _running = false;
+
+ LBItem::stop();
+}
+
+void LBAnimationItem::seek(uint16 pos) {
+ _anim->seek(pos);
+}
+
+void LBAnimationItem::seekToTime(uint32 time) {
+ _anim->seekToTime(time);
+}
+
+void LBAnimationItem::startPhase(uint phase) {
+ if (phase == _phase)
+ seek(1);
+
+ LBItem::startPhase(phase);
+}
+
+void LBAnimationItem::draw() {
+ if (!_visible || !_globalVisible)
+ return;
+
+ _anim->draw();
+}
+
+LBItem *LBAnimationItem::createClone() {
+ LBAnimationItem *item = new LBAnimationItem(_vm, _page, _rect);
+ item->_anim = new LBAnimation(_vm, item, _resourceId);
+ return item;
+}
+
+} // End of namespace Mohawk
diff --git a/engines/mohawk/livingbooks_bookinfo.cpp b/engines/mohawk/livingbooks_bookinfo.cpp
new file mode 100644
index 00000000000..1e7988016a4
--- /dev/null
+++ b/engines/mohawk/livingbooks_bookinfo.cpp
@@ -0,0 +1,177 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "mohawk/livingbooks.h"
+
+#include "common/textconsole.h"
+
+namespace Mohawk {
+
+void MohawkEngine_LivingBooks::loadBookInfo(const Common::Path &filename) {
+ _bookInfoFile.allowNonEnglishCharacters();
+ if (!_bookInfoFile.loadFromFile(filename))
+ error("Could not open %s as a config file", filename.toString().c_str());
+
+ _title = getStringFromConfig("BookInfo", "title");
+ _copyright = getStringFromConfig("BookInfo", "copyright");
+
+ _numPages = getIntFromConfig("BookInfo", "nPages");
+ _numLanguages = getIntFromConfig("BookInfo", "nLanguages");
+ _screenWidth = getIntFromConfig("BookInfo", "xRes");
+ _screenHeight = getIntFromConfig("BookInfo", "yRes");
+ // nColors is here too, but it's always 256 anyway...
+
+ // this is 1 in The New Kid on the Block, changes the hardcoded UI
+ // v2 games changed the flag name to fPoetry
+ if (getGameType() == GType_LIVINGBOOKSV1)
+ _poetryMode = (getIntFromConfig("BookInfo", "poetry") == 1);
+ else
+ _poetryMode = (getIntFromConfig("BookInfo", "fPoetry") == 1);
+
+ // The later Living Books games add some more options:
+ // - fNeedPalette (always true?)
+ // - fUse254ColorPalette (always true?)
+ // - nKBRequired (4096, RAM requirement?)
+ // - fDebugWindow (always 0?)
+
+ if (_bookInfoFile.hasSection("Globals")) {
+ const Common::INIFile::SectionKeyList globals = _bookInfoFile.getKeys("Globals");
+ for (Common::INIFile::SectionKeyList::const_iterator i = globals.begin(); i != globals.end(); i++) {
+ Common::String command = Common::String::format("%s = %s", i->key.c_str(), i->value.c_str());
+ LBCode tempCode(this, 0);
+ uint offset = tempCode.parseCode(command);
+ tempCode.runCode(nullptr, offset);
+ }
+ }
+}
+
+Common::String MohawkEngine_LivingBooks::stringForMode(LBMode mode) {
+ Common::String language = getStringFromConfig("Languages", Common::String::format("Language%d", _curLanguage));
+
+ switch (mode) {
+ case kLBIntroMode:
+ return "Intro";
+ case kLBControlMode:
+ return "Control";
+ case kLBCreditsMode:
+ return "Credits";
+ case kLBPreviewMode:
+ return "Preview";
+ case kLBReadMode:
+ return language + ".Read";
+ case kLBPlayMode:
+ return language + ".Play";
+ default:
+ error("unknown game mode %d", (int)mode);
+ }
+}
+
+Common::String MohawkEngine_LivingBooks::getStringFromConfig(const Common::String §ion, const Common::String &key) {
+ Common::String x, leftover;
+ _bookInfoFile.getKey(key, section, x);
+ Common::String tmp = removeQuotesFromString(x, leftover);
+ if (!leftover.empty())
+ warning("while parsing config key '%s' from section '%s', string '%s' was left after '%s'",
+ key.c_str(), section.c_str(), leftover.c_str(), tmp.c_str());
+ return tmp;
+}
+
+Common::String MohawkEngine_LivingBooks::getStringFromConfig(const Common::String §ion, const Common::String &key, Common::String &leftover) {
+ Common::String x;
+ _bookInfoFile.getKey(key, section, x);
+ return removeQuotesFromString(x, leftover);
+}
+
+int MohawkEngine_LivingBooks::getIntFromConfig(const Common::String §ion, const Common::String &key) {
+ return atoi(getStringFromConfig(section, key).c_str());
+}
+
+Common::String MohawkEngine_LivingBooks::getFileNameFromConfig(const Common::String §ion, const Common::String &key, Common::String &leftover) {
+ Common::String string = getStringFromConfig(section, key, leftover);
+
+ if (string.hasPrefix("//")) {
+ // skip "//CD-ROM Title/" prefixes which we don't care about
+ uint i = 3;
+ while (i < string.size() && string[i - 1] != '/')
+ i++;
+
+ // Already uses slashes, no need to convert further
+ return string.c_str() + i;
+ }
+
+ return (getPlatform() == Common::kPlatformMacintosh) ? convertMacFileName(string) : convertWinFileName(string);
+}
+
+Common::String MohawkEngine_LivingBooks::removeQuotesFromString(const Common::String &string, Common::String &leftover) {
+ if (string.empty())
+ return string;
+
+ char quoteChar = string[0];
+ if (quoteChar != '\"' && quoteChar != '\'')
+ return string;
+
+ Common::String tmp;
+ bool inLeftover = false;
+ for (uint32 i = 1; i < string.size(); i++) {
+ if (inLeftover)
+ leftover += string[i];
+ else if (string[i] == quoteChar)
+ inLeftover = true;
+ else
+ tmp += string[i];
+ }
+
+ return tmp;
+}
+
+Common::String MohawkEngine_LivingBooks::convertMacFileName(const Common::String &string) {
+ Common::String filename;
+
+ for (uint32 i = 0; i < string.size(); i++) {
+ if (i == 0 && string[i] == ':') // First character should be ignored (another colon)
+ continue;
+ if (string[i] == ':') // Directory separator
+ filename += '/';
+ else if (string[i] == '/') // Literal slash
+ filename += ':'; // Replace by colon, as used by macOS for slash
+ else
+ filename += string[i];
+ }
+
+ return filename;
+}
+
+Common::String MohawkEngine_LivingBooks::convertWinFileName(const Common::String &string) {
+ Common::String filename;
+
+ for (uint32 i = 0; i < string.size(); i++) {
+ if (i == 0 && (string[i] == '/' || string[i] == '\\')) // ignore slashes at start
+ continue;
+ if (string[i] == '\\')
+ filename += '/';
+ else
+ filename += string[i];
+ }
+
+ return filename;
+}
+
+} // End of namespace Mohawk
diff --git a/engines/mohawk/livingbooks_itemtypes.cpp b/engines/mohawk/livingbooks_itemtypes.cpp
new file mode 100644
index 00000000000..b1ee4d10772
--- /dev/null
+++ b/engines/mohawk/livingbooks_itemtypes.cpp
@@ -0,0 +1,778 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "mohawk/livingbooks.h"
+#include "mohawk/resource.h"
+#include "mohawk/video.h"
+
+#include "common/memstream.h"
+#include "common/system.h"
+#include "common/textconsole.h"
+
+#include "graphics/paletteman.h"
+
+#include "gui/message.h"
+
+namespace Mohawk {
+
+LBSoundItem::LBSoundItem(MohawkEngine_LivingBooks *vm, LBPage *page, Common::Rect rect) : LBItem(vm, page, rect) {
+ debug(3, "new LBSoundItem");
+ _running = false;
+}
+
+LBSoundItem::~LBSoundItem() {
+ if (_running)
+ _vm->_sound->stopSound(_resourceId);
+}
+
+void LBSoundItem::update() {
+ if (_running && !_vm->_sound->isPlaying(_resourceId)) {
+ _running = false;
+ done(true);
+ }
+
+ LBItem::update();
+}
+
+bool LBSoundItem::togglePlaying(bool playing, bool restart) {
+ if (!playing)
+ return LBItem::togglePlaying(playing, restart);
+
+ if (_running) {
+ _running = false;
+ _vm->_sound->stopSound(_resourceId);
+ }
+
+ if (!_loaded || !_enabled || !_globalEnabled)
+ return false;
+
+ _running = true;
+ debug(4, "sound %d play for item %d (%s)", _resourceId, _itemId, _desc.c_str());
+ _vm->playSound(this, _resourceId);
+ return true;
+}
+
+void LBSoundItem::stop() {
+ if (_running) {
+ _running = false;
+ _vm->_sound->stopSound(_resourceId);
+ }
+
+ LBItem::stop();
+}
+
+LBItem *LBSoundItem::createClone() {
+ return new LBSoundItem(_vm, _page, _rect);
+}
+
+LBGroupItem::LBGroupItem(MohawkEngine_LivingBooks *vm, LBPage *page, Common::Rect rect) : LBItem(vm, page, rect) {
+ debug(3, "new LBGroupItem");
+ _starting = false;
+}
+
+void LBGroupItem::readData(uint16 type, uint16 size, Common::MemoryReadStreamEndian *stream) {
+ switch (type) {
+ case kLBGroupData:
+ {
+ _groupEntries.clear();
+ uint16 count = stream->readUint16();
+ debug(3, "Group data: %d entries", count);
+
+ if (size != 2 + count * 4)
+ error("kLBGroupData was wrong size (%d, for %d entries)", size, count);
+
+ for (uint i = 0; i < count; i++) {
+ GroupEntry entry;
+ // TODO: is type important for any game? at the moment, we ignore it
+ entry.entryType = stream->readUint16();
+ entry.entryId = stream->readUint16();
+ // HACK: The Living Books v3 sampler includes the ID for the group as
+ // one of the entries in the Green Eggs and Ham section, which leads
+ // to infinite recursion when the group is loaded.
+ if (entry.entryId != getId())
+ _groupEntries.push_back(entry);
+ debug(3, "group entry: id %d, type %d", entry.entryId, entry.entryType);
+ }
+ }
+ break;
+
+ default:
+ LBItem::readData(type, size, stream);
+ }
+}
+
+void LBGroupItem::destroySelf() {
+ LBItem::destroySelf();
+
+ for (uint i = 0; i < _groupEntries.size(); i++) {
+ LBItem *item = _vm->getItemById(_groupEntries[i].entryId);
+ if (item)
+ item->destroySelf();
+ }
+}
+
+void LBGroupItem::setEnabled(bool enabled) {
+ if (_starting) {
+ _starting = false;
+ LBItem::setEnabled(enabled);
+ } else {
+ for (uint i = 0; i < _groupEntries.size(); i++) {
+ LBItem *item = _vm->getItemById(_groupEntries[i].entryId);
+ if (item)
+ item->setEnabled(enabled);
+ }
+ }
+}
+
+void LBGroupItem::setGlobalEnabled(bool enabled) {
+ for (uint i = 0; i < _groupEntries.size(); i++) {
+ LBItem *item = _vm->getItemById(_groupEntries[i].entryId);
+ if (item)
+ item->setGlobalEnabled(enabled);
+ }
+}
+
+bool LBGroupItem::contains(Common::Point point) {
+ return false;
+}
+
+bool LBGroupItem::togglePlaying(bool playing, bool restart) {
+ for (uint i = 0; i < _groupEntries.size(); i++) {
+ LBItem *item = _vm->getItemById(_groupEntries[i].entryId);
+ if (item)
+ item->togglePlaying(playing, restart);
+ }
+
+ return false;
+}
+
+void LBGroupItem::seek(uint16 pos) {
+ for (uint i = 0; i < _groupEntries.size(); i++) {
+ LBItem *item = _vm->getItemById(_groupEntries[i].entryId);
+ if (item)
+ item->seek(pos);
+ }
+}
+
+void LBGroupItem::setVisible(bool visible) {
+ for (uint i = 0; i < _groupEntries.size(); i++) {
+ LBItem *item = _vm->getItemById(_groupEntries[i].entryId);
+ if (item)
+ item->setVisible(visible);
+ }
+}
+
+void LBGroupItem::setGlobalVisible(bool visible) {
+ for (uint i = 0; i < _groupEntries.size(); i++) {
+ LBItem *item = _vm->getItemById(_groupEntries[i].entryId);
+ if (item)
+ item->setGlobalVisible(visible);
+ }
+}
+
+void LBGroupItem::startPhase(uint phase) {
+ _starting = true;
+ LBItem::startPhase(phase);
+ _starting = false;
+}
+
+void LBGroupItem::stop() {
+ for (uint i = 0; i < _groupEntries.size(); i++) {
+ LBItem *item = _vm->getItemById(_groupEntries[i].entryId);
+ if (item)
+ item->stop();
+ }
+}
+
+void LBGroupItem::load() {
+ for (uint i = 0; i < _groupEntries.size(); i++) {
+ LBItem *item = _vm->getItemById(_groupEntries[i].entryId);
+ if (item)
+ item->load();
+ }
+}
+
+void LBGroupItem::unload() {
+ for (uint i = 0; i < _groupEntries.size(); i++) {
+ LBItem *item = _vm->getItemById(_groupEntries[i].entryId);
+ if (item)
+ item->unload();
+ }
+}
+
+void LBGroupItem::moveBy(const Common::Point &pos) {
+ for (uint i = 0; i < _groupEntries.size(); i++) {
+ LBItem *item = _vm->getItemById(_groupEntries[i].entryId);
+ if (item)
+ item->moveBy(pos);
+ }
+}
+
+void LBGroupItem::moveTo(const Common::Point &pos) {
+ for (uint i = 0; i < _groupEntries.size(); i++) {
+ LBItem *item = _vm->getItemById(_groupEntries[i].entryId);
+ if (item)
+ item->moveTo(pos);
+ }
+}
+
+LBItem *LBGroupItem::createClone() {
+ // TODO: needed?
+ error("LBGroupItem::createClone unimplemented");
+ return new LBGroupItem(_vm, _page, _rect);
+}
+
+LBPaletteItem::LBPaletteItem(MohawkEngine_LivingBooks *vm, LBPage *page, Common::Rect rect) : LBItem(vm, page, rect) {
+ debug(3, "new LBPaletteItem");
+
+ _fadeInStart = 0;
+ _palette = nullptr;
+}
+
+LBPaletteItem::~LBPaletteItem() {
+ delete[] _palette;
+}
+
+void LBPaletteItem::readData(uint16 type, uint16 size, Common::MemoryReadStreamEndian *stream) {
+ switch (type) {
+ case kLBPaletteXData:
+ {
+ assert(size >= 8);
+ _fadeInPeriod = stream->readUint16();
+ _fadeInStep = stream->readUint16();
+ _drawStart = stream->readUint16();
+ _drawCount = stream->readUint16();
+ if (_drawStart + _drawCount > 256)
+ error("encountered palette trying to set more than 256 colors");
+ assert(size == 8 + _drawCount * 4);
+
+ // TODO: _drawCount is really more like _drawEnd, so once we're sure that
+ // there's really no use for the palette entries before _drawCount, we
+ // might want to just discard them here, at load time.
+ _palette = new byte[_drawCount * 3];
+ for (uint i = 0; i < _drawCount; i++) {
+ _palette[i*3 + 0] = stream->readByte();
+ _palette[i*3 + 1] = stream->readByte();
+ _palette[i*3 + 2] = stream->readByte();
+ stream->readByte();
+ }
+ }
+ break;
+
+ default:
+ LBItem::readData(type, size, stream);
+ }
+}
+
+bool LBPaletteItem::togglePlaying(bool playing, bool restart) {
+ // TODO: this likely isn't the right place
+
+ if (playing) {
+ _fadeInStart = _vm->_system->getMillis();
+ _fadeInCurrent = 0;
+
+ return true;
+ }
+
+ return LBItem::togglePlaying(playing, restart);
+}
+
+void LBPaletteItem::update() {
+ if (_fadeInStart) {
+ if (!_palette)
+ error("LBPaletteItem had no palette on startup");
+
+ uint32 elapsedTime = _vm->_system->getMillis() - _fadeInStart;
+ uint32 divTime = elapsedTime / _fadeInStep;
+
+ if (divTime > _fadeInPeriod)
+ divTime = _fadeInPeriod;
+
+ if (_fadeInCurrent != divTime) {
+ _fadeInCurrent = divTime;
+
+ // TODO: actual fading-in
+ if (_visible && _globalVisible) {
+ _vm->_system->getPaletteManager()->setPalette(_palette + _drawStart * 3, _drawStart, _drawCount - _drawStart);
+ _vm->_needsRedraw = true;
+ }
+ }
+
+ if (elapsedTime >= (uint32)_fadeInPeriod * (uint32)_fadeInStep) {
+ // TODO: correct?
+ _fadeInStart = 0;
+ }
+ }
+
+ LBItem::update();
+}
+
+LBItem *LBPaletteItem::createClone() {
+ error("can't clone LBPaletteItem");
+}
+
+LBLiveTextItem::LBLiveTextItem(MohawkEngine_LivingBooks *vm, LBPage *page, Common::Rect rect) : LBItem(vm, page, rect) {
+ _currentPhrase = 0xFFFF;
+ _currentWord = 0xFFFF;
+ debug(3, "new LBLiveTextItem");
+}
+
+void LBLiveTextItem::readData(uint16 type, uint16 size, Common::MemoryReadStreamEndian *stream) {
+ switch (type) {
+ case kLBLiveTextData:
+ {
+ stream->read(_backgroundColor, 4); // unused?
+ stream->read(_foregroundColor, 4);
+ stream->read(_highlightColor, 4);
+ _paletteIndex = stream->readUint16();
+ uint16 phraseCount = stream->readUint16();
+ uint16 wordCount = stream->readUint16();
+
+ debug(3, "LiveText has %d words in %d phrases, palette index 0x%04x", wordCount, phraseCount, _paletteIndex);
+ debug(3, "LiveText colors: background %02x%02x%02x%02x, foreground %02x%02x%02x%02x, highlight %02x%02x%02x%02x",
+ _backgroundColor[0], _backgroundColor[1], _backgroundColor[2], _backgroundColor[3],
+ _foregroundColor[0], _foregroundColor[1], _foregroundColor[2], _foregroundColor[3],
+ _highlightColor[0], _highlightColor[1], _highlightColor[2], _highlightColor[3]);
+
+ if (size != 18 + 14 * wordCount + 18 * phraseCount)
+ error("Bad Live Text data size (got %d, wanted %d words and %d phrases)", size, wordCount, phraseCount);
+
+ _words.clear();
+ for (uint i = 0; i < wordCount; i++) {
+ LiveTextWord word;
+ word.bounds = _vm->readRect(stream);
+ word.soundId = stream->readUint16();
+ word.itemType = stream->readUint16();
+ word.itemId = stream->readUint16();
+ debug(4, "Word: (%d, %d) to (%d, %d), sound %d, item %d (type %d)",
+ word.bounds.left, word.bounds.top, word.bounds.right, word.bounds.bottom, word.soundId, word.itemId, word.itemType);
+ _words.push_back(word);
+ }
+
+ _phrases.clear();
+ for (uint i = 0; i < phraseCount; i++) {
+ LiveTextPhrase phrase;
+ phrase.wordStart = stream->readUint16();
+ phrase.wordCount = stream->readUint16();
+ phrase.highlightStart = stream->readUint16();
+ phrase.startId = stream->readUint16();
+ phrase.highlightEnd = stream->readUint16();
+ phrase.endId = stream->readUint16();
+
+ // The original stored the values in uint32's so we need to swap here
+ if (_vm->isBigEndian()) {
+ SWAP(phrase.highlightStart, phrase.startId);
+ SWAP(phrase.highlightEnd, phrase.endId);
+ }
+
+ uint32 unknown1 = stream->readUint16();
+ uint16 unknown2 = stream->readUint32();
+
+ if (unknown1 != 0 || unknown2 != 0)
+ error("Unexpected unknowns %08x/%04x in LiveText word", unknown1, unknown2);
+
+ debug(4, "Phrase: start %d, count %d, start at %d (from %d), end at %d (from %d)",
+ phrase.wordStart, phrase.wordCount, phrase.highlightStart, phrase.startId, phrase.highlightEnd, phrase.endId);
+
+ _phrases.push_back(phrase);
+ }
+ }
+ break;
+
+ default:
+ LBItem::readData(type, size, stream);
+ }
+}
+
+bool LBLiveTextItem::contains(Common::Point point) {
+ if (!LBItem::contains(point))
+ return false;
+
+ point.x -= _rect.left;
+ point.y -= _rect.top;
+
+ for (uint i = 0; i < _words.size(); i++) {
+ if (_words[i].bounds.contains(point))
+ return true;
+ }
+
+ return false;
+}
+
+void LBLiveTextItem::paletteUpdate(uint16 word, bool on) {
+ _vm->_needsRedraw = true;
+
+ // Sometimes the last phrase goes out-of-bounds, the original engine
+ // only checks the words which are valid in the palette updating code.
+ if (word >= _words.size())
+ return;
+
+ if (_resourceId) {
+ // with a resource, we draw a bitmap in draw() rather than changing the palette
+ return;
+ }
+
+ if (on) {
+ _vm->_system->getPaletteManager()->setPalette(_highlightColor, _paletteIndex + word, 1);
+ } else {
+ _vm->_system->getPaletteManager()->setPalette(_foregroundColor, _paletteIndex + word, 1);
+ }
+}
+
+void LBLiveTextItem::update() {
+ if (_currentWord != 0xFFFF) {
+ uint16 soundId = _words[_currentWord].soundId;
+ if (soundId && !_vm->_sound->isPlaying(soundId)) {
+ paletteUpdate(_currentWord, false);
+
+ // TODO: check this in RE
+ LBItem *item = _vm->getItemById(_words[_currentWord].itemId);
+ if (item)
+ item->togglePlaying(false, true);
+
+ _currentWord = 0xFFFF;
+ }
+ }
+
+ LBItem::update();
+}
+
+void LBLiveTextItem::draw() {
+ // this is only necessary when we are drawing using a bitmap
+ if (!_resourceId)
+ return;
+
+ if (_currentWord != 0xFFFF) {
+ uint yPos = 0;
+ if (_currentWord > 0) {
+ for (uint i = 0; i < _currentWord; i++) {
+ yPos += (_words[i].bounds.bottom - _words[i].bounds.top);
+ }
+ }
+ drawWord(_currentWord, yPos);
+ return;
+ }
+
+ if (_currentPhrase == 0xFFFF)
+ return;
+
+ uint wordStart = _phrases[_currentPhrase].wordStart;
+ uint wordCount = _phrases[_currentPhrase].wordCount;
+ if (wordStart + wordCount > _words.size())
+ error("phrase %d was invalid (%d words, from %d, out of only %d total)",
+ _currentPhrase, wordCount, wordStart, _words.size());
+
+ uint yPos = 0;
+ for (uint i = 0; i < wordStart + wordCount; i++) {
+ if (i >= wordStart)
+ drawWord(i, yPos);
+ yPos += (_words[i].bounds.bottom - _words[i].bounds.top);
+ }
+}
+
+void LBLiveTextItem::drawWord(uint word, uint yPos) {
+ Common::Rect srcRect(0, yPos, _words[word].bounds.right - _words[word].bounds.left,
+ yPos + _words[word].bounds.bottom - _words[word].bounds.top);
+ Common::Rect dstRect = _words[word].bounds;
+ dstRect.translate(_rect.left, _rect.top);
+ _vm->_gfx->copyAnimImageSectionToScreen(_resourceId, srcRect, dstRect);
+}
+
+void LBLiveTextItem::handleMouseDown(Common::Point pos) {
+ if (!_loaded || !_enabled || !_globalEnabled || _playing)
+ return LBItem::handleMouseDown(pos);
+
+ pos.x -= _rect.left;
+ pos.y -= _rect.top;
+
+ for (uint i = 0; i < _words.size(); i++) {
+ if (_words[i].bounds.contains(pos)) {
+ if (_currentWord != 0xFFFF) {
+ paletteUpdate(_currentWord, false);
+ _currentWord = 0xFFFF;
+ }
+ uint16 soundId = _words[i].soundId;
+ if (!soundId) {
+ // TODO: can we be smarter here, using timing?
+ warning("ignoring click due to no soundId");
+ return;
+ }
+ _currentWord = i;
+ _vm->playSound(this, soundId);
+ paletteUpdate(_currentWord, true);
+ return;
+ }
+ }
+
+ return LBItem::handleMouseDown(pos);
+}
+
+bool LBLiveTextItem::togglePlaying(bool playing, bool restart) {
+ if (!playing)
+ return LBItem::togglePlaying(playing, restart);
+ if (!_loaded || !_enabled || !_globalEnabled)
+ return _playing;
+
+ // TODO: handle this properly
+ _vm->_sound->stopSound();
+
+ _currentWord = 0xFFFF;
+ _currentPhrase = 0xFFFF;
+
+ return true;
+}
+
+void LBLiveTextItem::stop() {
+ // TODO: stop sound, refresh palette
+
+ LBItem::stop();
+}
+
+void LBLiveTextItem::notify(uint16 data, uint16 from) {
+ if (!_loaded || !_enabled || !_globalEnabled || !_playing)
+ return LBItem::notify(data, from);
+
+ if (_currentWord != 0xFFFF) {
+ // TODO: handle this properly
+ _vm->_sound->stopSound();
+ paletteUpdate(_currentWord, false);
+ _currentWord = 0xFFFF;
+ }
+
+ for (uint i = 0; i < _phrases.size(); i++) {
+ if (_phrases[i].highlightStart == data && _phrases[i].startId == from) {
+ debug(2, "Enabling phrase %d", i);
+ for (uint j = 0; j < _phrases[i].wordCount; j++) {
+ paletteUpdate(_phrases[i].wordStart + j, true);
+ }
+ _currentPhrase = i;
+ // TODO: not sure this is the correct logic
+ if (i == _phrases.size() - 1) {
+ _currentPhrase = 0xFFFF;
+ done(true);
+ }
+ } else if (_phrases[i].highlightEnd == data && _phrases[i].endId == from) {
+ debug(2, "Disabling phrase %d", i);
+ for (uint j = 0; j < _phrases[i].wordCount; j++) {
+ paletteUpdate(_phrases[i].wordStart + j, false);
+ }
+ _currentPhrase = 0xFFFF;
+ }
+ }
+
+ LBItem::notify(data, from);
+}
+
+LBItem *LBLiveTextItem::createClone() {
+ error("can't clone LBLiveTextItem");
+}
+
+LBPictureItem::LBPictureItem(MohawkEngine_LivingBooks *vm, LBPage *page, Common::Rect rect) : LBItem(vm, page, rect) {
+ debug(3, "new LBPictureItem");
+}
+
+void LBPictureItem::readData(uint16 type, uint16 size, Common::MemoryReadStreamEndian *stream) {
+ switch (type) {
+ case kLBSetDrawMode:
+ {
+ assert(size == 2);
+ // TODO: this probably sets whether points are always contained (0x10)
+ // or whether the bitmap contents are checked (00, or anything else?)
+ uint16 val = stream->readUint16();
+ debug(2, "LBPictureItem: kLBSetDrawMode: %04x", val);
+ }
+ break;
+
+ default:
+ LBItem::readData(type, size, stream);
+ }
+}
+
+bool LBPictureItem::contains(Common::Point point) {
+ if (!LBItem::contains(point))
+ return false;
+
+ if (!_doHitTest)
+ return true;
+
+ // TODO: only check pixels if necessary
+ return !_vm->_gfx->imageIsTransparentAt(_resourceId, false, point.x - _rect.left, point.y - _rect.top);
+}
+
+void LBPictureItem::init() {
+ _vm->_gfx->preloadImage(_resourceId);
+
+ LBItem::init();
+}
+
+void LBPictureItem::draw() {
+ if (!_loaded || !_visible || !_globalVisible)
+ return;
+
+ _vm->_gfx->copyAnimImageToScreen(_resourceId, _rect.left, _rect.top);
+}
+
+LBItem *LBPictureItem::createClone() {
+ return new LBPictureItem(_vm, _page, _rect);
+}
+
+LBMovieItem::LBMovieItem(MohawkEngine_LivingBooks *vm, LBPage *page, Common::Rect rect) : LBItem(vm, page, rect) {
+ debug(3, "new LBMovieItem");
+}
+
+LBMovieItem::~LBMovieItem() {
+}
+
+void LBMovieItem::update() {
+ if (_playing) {
+ VideoEntryPtr video = _vm->_video->findVideo(_resourceId);
+ if (!video || video->endOfVideo())
+ done(true);
+ }
+
+ LBItem::update();
+}
+
+bool LBMovieItem::togglePlaying(bool playing, bool restart) {
+ if (playing) {
+ if ((_loaded && _enabled && _globalEnabled) || _phase == kLBPhaseNone) {
+ debug("toggled video for phase %d", _phase);
+ VideoEntryPtr video = _vm->_video->playMovie(_resourceId);
+ if (!video)
+ error("Failed to open tMOV %d", _resourceId);
+
+ video->moveTo(_rect.left, _rect.top);
+ return true;
+ }
+ }
+
+ return LBItem::togglePlaying(playing, restart);
+}
+
+LBItem *LBMovieItem::createClone() {
+ return new LBMovieItem(_vm, _page, _rect);
+}
+
+LBMiniGameItem::LBMiniGameItem(MohawkEngine_LivingBooks *vm, LBPage *page, Common::Rect rect) : LBItem(vm, page, rect) {
+ debug(3, "new LBMiniGameItem");
+}
+
+LBMiniGameItem::~LBMiniGameItem() {
+}
+
+bool LBMiniGameItem::togglePlaying(bool playing, bool restart) {
+ // HACK: Since we don't support any of these hardcoded mini games yet,
+ // just skip to the most logical page. For optional minigames, this
+ // will return the player to the previous page. For mandatory minigames,
+ // this will send the player to the next page.
+
+ uint16 destPage = 0;
+ bool returnToMenu = false;
+
+ // Figure out what minigame we have and bring us back to a page where
+ // the player can continue
+ if (_desc == "Kitch") // Green Eggs and Ham: Kitchen minigame
+ destPage = 4;
+ else if (_desc == "Eggs") // Green Eggs and Ham: Eggs minigame
+ destPage = 5;
+ else if (_desc == "Fall") // Green Eggs and Ham: Fall minigame
+ destPage = 13;
+ else if (_desc == "MagicWrite3") // Arthur's Reading Race: "Let Me Write" minigame (Page 3)
+ destPage = 3;
+ else if (_desc == "MagicWrite4") // Arthur's Reading Race: "Let Me Write" minigame (Page 4)
+ destPage = 4;
+ else if (_desc == "MagicSpy5") // Arthur's Reading Race: "I Spy" minigame (Page 5)
+ destPage = 5;
+ else if (_desc == "MagicSpy6") // Arthur's Reading Race: "I Spy" minigame (Page 6)
+ destPage = 6;
+ else if (_desc == "MagicWrite7") // Arthur's Reading Race: "Let Me Write" minigame (Page 7)
+ destPage = 7;
+ else if (_desc == "MagicSpy8") // Arthur's Reading Race: "I Spy" minigame (Page 8)
+ destPage = 8;
+ else if (_desc == "MagicRace") // Arthur's Reading Race: Race minigame
+ returnToMenu = true;
+ else
+ error("Unknown minigame '%s'", _desc.c_str());
+
+ GUI::MessageDialog dialog(Common::String::format("The '%s' minigame is not supported yet.", _desc.c_str()));
+ dialog.runModal();
+
+ // Go back to the menu if requested, otherwise go to the requested page
+ if (returnToMenu)
+ _vm->addNotifyEvent(NotifyEvent(kLBNotifyGoToControls, 1));
+ else
+ _vm->addNotifyEvent(NotifyEvent(kLBNotifyChangePage, destPage));
+
+ return false;
+}
+
+LBItem *LBMiniGameItem::createClone() {
+ error("can't clone LBMiniGameItem");
+}
+
+LBProxyItem::LBProxyItem(MohawkEngine_LivingBooks *vm, LBPage *page, Common::Rect rect) : LBItem(vm, page, rect) {
+ debug(3, "new LBProxyItem");
+
+ _page = nullptr;
+}
+
+LBProxyItem::~LBProxyItem() {
+ delete _page;
+}
+
+void LBProxyItem::load() {
+ if (_loaded)
+ return;
+
+ Common::String leftover;
+ Common::String filename = _vm->getFileNameFromConfig("Proxies", _desc.c_str(), leftover);
+ if (!leftover.empty())
+ error("LBProxyItem tried loading proxy '%s' but got leftover '%s'", _desc.c_str(), leftover.c_str());
+ uint16 baseId = 0;
+ for (uint i = 0; i < filename.size(); i++) {
+ if (filename[i] == ';') {
+ baseId = atoi(filename.c_str() + i + 1);
+ filename = Common::String(filename.c_str(), i);
+ }
+ }
+
+ debug(1, "LBProxyItem loading archive '%s' with id %d", filename.c_str(), baseId);
+ Archive *pageArchive = _vm->createArchive();
+ if (!tryOpenPage(pageArchive, filename))
+ error("failed to open archive '%s' (for proxy '%s')", filename.c_str(), _desc.c_str());
+ _page = new LBPage(_vm);
+ _page->open(pageArchive, baseId);
+
+ LBItem::load();
+}
+
+void LBProxyItem::unload() {
+ delete _page;
+ _page = nullptr;
+
+ LBItem::unload();
+}
+
+LBItem *LBProxyItem::createClone() {
+ return new LBProxyItem(_vm, _page, _rect);
+}
+
+} // End of namespace Mohawk
diff --git a/engines/mohawk/livingbooks_menus.cpp b/engines/mohawk/livingbooks_menus.cpp
new file mode 100644
index 00000000000..0764364ce1c
--- /dev/null
+++ b/engines/mohawk/livingbooks_menus.cpp
@@ -0,0 +1,307 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "mohawk/livingbooks.h"
+
+#include "common/textconsole.h"
+
+namespace Mohawk {
+
+void MohawkEngine_LivingBooks::handleUIMenuClick(uint controlId) {
+ LBItem *item;
+
+ switch (controlId) {
+ case 1:
+ if (getFeatures() & GF_LB_10) {
+ if (!tryLoadPageStart(kLBControlMode, 2))
+ error("couldn't load options page");
+ } else {
+ if (!tryLoadPageStart(kLBControlMode, 3))
+ error("couldn't load options page");
+ }
+ break;
+
+ case 2:
+ item = getItemById(10);
+ if (item)
+ item->destroySelf();
+ item = getItemById(11);
+ if (item)
+ item->destroySelf();
+ item = getItemById(199 + _curLanguage);
+ if (item) {
+ item->setVisible(true);
+ item->togglePlaying(false, true);
+ }
+ break;
+
+ case 3:
+ item = getItemById(10);
+ if (item)
+ item->destroySelf();
+ item = getItemById(11);
+ if (item)
+ item->destroySelf();
+ item = getItemById(12);
+ if (item) {
+ item->setVisible(true);
+ item->togglePlaying(false, true);
+ }
+ break;
+
+ case 4:
+ if (getFeatures() & GF_LB_10) {
+ if (!tryLoadPageStart(kLBControlMode, 3))
+ error("couldn't load quit page");
+ } else {
+ if (!tryLoadPageStart(kLBControlMode, 2))
+ error("couldn't load quit page");
+ }
+ break;
+
+ case 10:
+ item = getItemById(10);
+ if (item)
+ item->destroySelf();
+ item = getItemById(11);
+ if (item) {
+ item->setVisible(true);
+ item->togglePlaying(false);
+ }
+ break;
+
+ case 11:
+ item = getItemById(11);
+ if (item)
+ item->togglePlaying(false, true);
+ break;
+
+ case 12:
+ // start game, in play mode
+ if (!tryLoadPageStart(kLBPlayMode, 1))
+ error("couldn't start play mode");
+ break;
+
+ default:
+ if (controlId >= 100 && controlId < 100 + (uint)_numLanguages) {
+ uint newLanguage = controlId - 99;
+ if (newLanguage == _curLanguage)
+ break;
+ item = getItemById(99 + _curLanguage);
+ if (item)
+ item->seek(1);
+ _curLanguage = newLanguage;
+ } else if (controlId >= 200 && controlId < 200 + (uint)_numLanguages) {
+ // start game, in read mode
+ if (!tryLoadPageStart(kLBReadMode, 1))
+ error("couldn't start read mode");
+ }
+ break;
+ }
+}
+
+void MohawkEngine_LivingBooks::handleUIPoetryMenuClick(uint controlId) {
+ LBItem *item;
+
+ // the menu UI in New Kid on the Block is a hybrid of the normal menu
+ // and the normal options screen
+
+ // TODO: this is mostly untested
+
+ switch (controlId) {
+ case 2:
+ case 3:
+ handleUIOptionsClick(controlId);
+ break;
+
+ case 4:
+ handleUIMenuClick(controlId);
+ break;
+
+ case 6:
+ handleUIMenuClick(2);
+ break;
+
+ case 7:
+ item = getItemById(10);
+ if (item)
+ item->destroySelf();
+ item = getItemById(11);
+ if (item)
+ item->destroySelf();
+ item = getItemById(12);
+ if (item) {
+ item->setVisible(true);
+ item->togglePlaying(false, true);
+ }
+ break;
+
+ case 0xA:
+ item = getItemById(10);
+ if (item)
+ item->destroySelf();
+ item = getItemById(11);
+ if (item) {
+ item->setVisible(true);
+ item->togglePlaying(false);
+ }
+ break;
+
+ case 0xB:
+ item = getItemById(11);
+ if (item)
+ item->togglePlaying(false, true);
+ break;
+
+ case 0xC:
+ if (!tryLoadPageStart(kLBPlayMode, _curSelectedPage))
+ error("failed to load page %d", _curSelectedPage);
+ break;
+
+ default:
+ if (controlId < 100) {
+ handleUIMenuClick(controlId);
+ } else {
+ if (!tryLoadPageStart(kLBReadMode, _curSelectedPage))
+ error("failed to load page %d", _curSelectedPage);
+ }
+ }
+}
+
+void MohawkEngine_LivingBooks::handleUIQuitClick(uint controlId) {
+ LBItem *item;
+
+ switch (controlId) {
+ case 1:
+ case 2:
+ // button clicked, run animation
+ item = getItemById(10);
+ if (item)
+ item->destroySelf();
+ item = getItemById(11);
+ if (item)
+ item->destroySelf();
+ item = getItemById((controlId == 1) ? 12 : 13);
+ if (item) {
+ item->setVisible(true);
+ item->togglePlaying(false);
+ }
+ break;
+
+ case 10:
+ case 11:
+ item = getItemById(11);
+ if (item)
+ item->togglePlaying(false, true);
+ break;
+
+ case 12:
+ // 'yes', I want to quit
+ quitGame();
+ break;
+
+ case 13:
+ // 'no', go back to menu
+ if (!tryLoadPageStart(kLBControlMode, 1))
+ error("couldn't return to menu");
+ break;
+
+ default:
+ break;
+ }
+}
+
+void MohawkEngine_LivingBooks::handleUIOptionsClick(uint controlId) {
+ LBItem *item;
+
+ switch (controlId) {
+ case 1:
+ item = getItemById(10);
+ if (item)
+ item->destroySelf();
+ item = getItemById(202);
+ if (item) {
+ item->setVisible(true);
+ item->togglePlaying(false, true);
+ }
+ break;
+
+ case 2:
+ // back
+ item = getItemById(2);
+ if (item)
+ item->seek(1);
+ if (_curSelectedPage == 1) {
+ _curSelectedPage = _numPages;
+ } else {
+ _curSelectedPage--;
+ }
+ for (uint i = 0; i < _numPages; i++) {
+ item = getItemById(1000 + i);
+ if (item)
+ item->setVisible(_curSelectedPage == i + 1);
+ item = getItemById(1100 + i);
+ if (item)
+ item->setVisible(_curSelectedPage == i + 1);
+ }
+ break;
+
+ case 3:
+ // forward
+ item = getItemById(3);
+ if (item)
+ item->seek(1);
+ if (_curSelectedPage == _numPages) {
+ _curSelectedPage = 1;
+ } else {
+ _curSelectedPage++;
+ }
+ for (uint i = 0; i < _numPages; i++) {
+ item = getItemById(1000 + i);
+ if (item)
+ item->setVisible(_curSelectedPage == i + 1);
+ item = getItemById(1100 + i);
+ if (item)
+ item->setVisible(_curSelectedPage == i + 1);
+ }
+ break;
+
+ case 4:
+ if (!tryLoadPageStart(kLBCreditsMode, 1))
+ error("failed to start credits");
+ break;
+
+ case 5:
+ if (!tryLoadPageStart(kLBPreviewMode, 1))
+ error("failed to start preview");
+ break;
+
+ case 202:
+ if (!tryLoadPageStart(kLBPlayMode, _curSelectedPage))
+ error("failed to load page %d", _curSelectedPage);
+ break;
+
+ default:
+ break;
+ }
+}
+
+} // End of namespace Mohawk
diff --git a/engines/mohawk/livingbooks_page.cpp b/engines/mohawk/livingbooks_page.cpp
new file mode 100644
index 00000000000..775a82cc088
--- /dev/null
+++ b/engines/mohawk/livingbooks_page.cpp
@@ -0,0 +1,153 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "mohawk/livingbooks.h"
+#include "mohawk/resource.h"
+
+#include "common/textconsole.h"
+
+namespace Mohawk {
+
+LBPage::LBPage(MohawkEngine_LivingBooks *vm) : _vm(vm) {
+ _code = nullptr;
+ _mhk = nullptr;
+
+ _baseId = 0;
+ _cascade = false;
+}
+
+void LBPage::open(Archive *mhk, uint16 baseId) {
+ _mhk = mhk;
+ _baseId = baseId;
+
+ _vm->addArchive(_mhk);
+ if (!_vm->hasResource(ID_BCOD, baseId)) {
+ // assume that BCOD is mandatory for v4/v5
+ if (_vm->getGameType() == GType_LIVINGBOOKSV4 || _vm->getGameType() == GType_LIVINGBOOKSV5)
+ error("missing BCOD resource (id %d)", baseId);
+ _code = new LBCode(_vm, 0);
+ } else {
+ _code = new LBCode(_vm, baseId);
+ }
+
+ loadBITL(baseId);
+ for (uint i = 0; i < _items.size(); i++)
+ _vm->addItem(_items[i]);
+
+ for (uint32 i = 0; i < _items.size(); i++)
+ _items[i]->init();
+
+ for (uint32 i = 0; i < _items.size(); i++)
+ _items[i]->startPhase(kLBPhaseLoad);
+}
+
+void LBPage::addClonedItem(LBItem *item) {
+ _vm->addItem(item);
+ _items.push_back(item);
+}
+
+void LBPage::itemDestroyed(LBItem *item) {
+ for (uint i = 0; i < _items.size(); i++)
+ if (item == _items[i]) {
+ _items.remove_at(i);
+ return;
+ }
+ error("itemDestroyed didn't find item");
+}
+
+LBPage::~LBPage() {
+ delete _code;
+ _vm->removeItems(_items);
+ for (uint i = 0; i < _items.size(); i++)
+ delete _items[i];
+ _vm->removeArchive(_mhk);
+ delete _mhk;
+}
+
+// Only 1 VSRN resource per page
+uint16 LBPage::getResourceVersion() {
+ Common::SeekableReadStream *versionStream = _vm->getResource(ID_VRSN, _baseId);
+
+ // FIXME: some V2 games have very strange version entries
+ if (versionStream->size() != 2)
+ debug(1, "Version Record size mismatch");
+
+ uint16 version = versionStream->readUint16BE();
+
+ delete versionStream;
+ return version;
+}
+
+void LBPage::loadBITL(uint16 resourceId) {
+ Common::SeekableReadStreamEndian *bitlStream = _vm->wrapStreamEndian(ID_BITL, resourceId);
+
+ while (true) {
+ Common::Rect rect = _vm->readRect(bitlStream);
+ uint16 type = bitlStream->readUint16();
+
+ LBItem *res;
+ switch (type) {
+ case kLBPictureItem:
+ res = new LBPictureItem(_vm, this, rect);
+ break;
+ case kLBAnimationItem:
+ res = new LBAnimationItem(_vm, this, rect);
+ break;
+ case kLBPaletteItem:
+ res = new LBPaletteItem(_vm, this, rect);
+ break;
+ case kLBGroupItem:
+ res = new LBGroupItem(_vm, this, rect);
+ break;
+ case kLBSoundItem:
+ res = new LBSoundItem(_vm, this, rect);
+ break;
+ case kLBLiveTextItem:
+ res = new LBLiveTextItem(_vm, this, rect);
+ break;
+ case kLBMovieItem:
+ res = new LBMovieItem(_vm, this, rect);
+ break;
+ case kLBMiniGameItem:
+ res = new LBMiniGameItem(_vm, this, rect);
+ break;
+ case kLBProxyItem:
+ res = new LBProxyItem(_vm, this, rect);
+ break;
+ default:
+ warning("Unknown item type %04x", type);
+ // fall through
+ case 3: // often used for buttons
+ res = new LBItem(_vm, this, rect);
+ break;
+ }
+
+ res->readFrom(bitlStream);
+ _items.push_back(res);
+
+ if (bitlStream->size() == bitlStream->pos())
+ break;
+ }
+
+ delete bitlStream;
+}
+
+} // End of namespace Mohawk
diff --git a/engines/mohawk/module.mk b/engines/mohawk/module.mk
index edb3923172f..b264987cae7 100644
--- a/engines/mohawk/module.mk
+++ b/engines/mohawk/module.mk
@@ -8,9 +8,14 @@ MODULE_OBJS = \
graphics.o \
installer_archive.o \
livingbooks.o \
+ livingbooks_animation.o \
+ livingbooks_bookinfo.o \
livingbooks_code.o \
livingbooks_graphics.o \
+ livingbooks_itemtypes.o \
livingbooks_lbx.o \
+ livingbooks_menus.o \
+ livingbooks_page.o \
metaengine.o \
riven_metaengine.o \
myst_metaengine.o \
Commit: 7d5666a9f5619e70726468ae2f61f6c8556b4648
https://github.com/scummvm/scummvm/commit/7d5666a9f5619e70726468ae2f61f6c8556b4648
Author: Alstruit (34786806+Alstruit at users.noreply.github.com)
Date: 2026-06-14T13:00:37+03:00
Commit Message:
MOHAWK: LB - move tryOpenPage function to header
Duplicate function.
Changed paths:
engines/mohawk/livingbooks.cpp
engines/mohawk/livingbooks.h
diff --git a/engines/mohawk/livingbooks.cpp b/engines/mohawk/livingbooks.cpp
index fdcd4f1e2bd..9c186ec7eca 100644
--- a/engines/mohawk/livingbooks.cpp
+++ b/engines/mohawk/livingbooks.cpp
@@ -285,7 +285,7 @@ static Common::String replaceColons(const Common::String &in, char replace) {
}
// Helper function to assist in opening pages
-static bool tryOpenPage(Archive *archive, const Common::String &fileName) {
+bool tryOpenPage(Archive *archive, const Common::String &fileName) {
// Try the plain file name first
if (archive->openFile(Common::Path(fileName)))
return true;
diff --git a/engines/mohawk/livingbooks.h b/engines/mohawk/livingbooks.h
index 7374c7199fd..a9819b3db25 100644
--- a/engines/mohawk/livingbooks.h
+++ b/engines/mohawk/livingbooks.h
@@ -262,6 +262,8 @@ class LBPage;
class LBGraphics;
class LBAnimation;
+bool tryOpenPage(Archive *archive, const Common::String &fileName);
+
struct LBScriptEntry {
LBScriptEntry();
~LBScriptEntry();
Commit: 0d527c60b8029770ffccdae3631efa7eff19b5dc
https://github.com/scummvm/scummvm/commit/0d527c60b8029770ffccdae3631efa7eff19b5dc
Author: Alstruit (34786806+Alstruit at users.noreply.github.com)
Date: 2026-06-14T13:00:37+03:00
Commit Message:
MOHAWK: LB - Cleanup ~4000 LoC in main LB (II).
Create livingbooks_constants.h to handle lots of kLBs.
Move LBItem respectively. Move LBSCriptEntry to itemscript.
Define header files: item,itemscript,itemtypes,aniation,page.
Changed paths:
A engines/mohawk/livingbooks_animation.h
A engines/mohawk/livingbooks_constants.h
A engines/mohawk/livingbooks_item.cpp
A engines/mohawk/livingbooks_item.h
A engines/mohawk/livingbooks_itemscript.cpp
A engines/mohawk/livingbooks_itemscript.h
A engines/mohawk/livingbooks_itemtypes.h
A engines/mohawk/livingbooks_page.h
engines/mohawk/livingbooks.cpp
engines/mohawk/livingbooks.h
engines/mohawk/module.mk
diff --git a/engines/mohawk/livingbooks.cpp b/engines/mohawk/livingbooks.cpp
index 9c186ec7eca..8aa6688eec1 100644
--- a/engines/mohawk/livingbooks.cpp
+++ b/engines/mohawk/livingbooks.cpp
@@ -917,1120 +917,4 @@ void MohawkEngine_LivingBooks::handleNotify(NotifyEvent &event) {
}
}
-LBScriptEntry::LBScriptEntry() {
- state = 0;
- data = nullptr;
- argvParam = nullptr;
- argvTarget = nullptr;
-}
-
-LBScriptEntry::~LBScriptEntry() {
- delete[] argvParam;
- delete[] argvTarget;
- delete[] data;
-
- for (uint i = 0; i < subentries.size(); i++)
- delete subentries[i];
-}
-
-LBItem::LBItem(MohawkEngine_LivingBooks *vm, LBPage *page, Common::Rect rect) : _vm(vm), _page(page), _rect(rect) {
- if (_vm->getGameType() == GType_LIVINGBOOKSV1 || _vm->getGameType() == GType_LIVINGBOOKSV2)
- _phase = kLBPhaseInit;
- else
- _phase = kLBPhaseLoad;
-
- _loopMode = 0;
- _delayMin = 0;
- _delayMax = 0;
- _timingMode = kLBAutoNone;
- _periodMin = 0;
- _periodMax = 0;
- _controlMode = kLBControlNone;
- _soundMode = 0;
-
- _loaded = false;
- _enabled = false;
- _visible = true;
- _playing = false;
- _globalEnabled = true;
- _globalVisible = true;
- _nextTime = 0;
- _startTime = 0;
- _loops = 0;
-
- _isAmbient = false;
- _doHitTest = true;
-}
-
-LBItem::~LBItem() {
- for (uint i = 0; i < _scriptEntries.size(); i++)
- delete _scriptEntries[i];
-}
-
-void LBItem::readFrom(Common::SeekableReadStreamEndian *stream) {
- _resourceId = stream->readUint16();
- _itemId = stream->readUint16();
- uint16 size = stream->readUint16();
- _desc = _vm->readString(stream);
-
- debug(2, "Item: size %d, resource %d, id %d", size, _resourceId, _itemId);
- debug(2, "Coords: %d, %d, %d, %d", _rect.left, _rect.top, _rect.right, _rect.bottom);
- debug(2, "String: '%s'", _desc.c_str());
-
- if (!_itemId)
- error("Item had invalid item id");
-
- int endPos = stream->pos() + size;
- if (endPos > stream->size())
- error("Item is larger (should end at %d) than stream (size %d)", endPos, (int)stream->size());
-
- while (true) {
- if (stream->pos() == endPos)
- break;
-
- uint oldPos = stream->pos();
-
- uint16 dataType = stream->readUint16();
- uint16 dataSize = stream->readUint16();
-
- debug(4, "Data type %04x, size %d", dataType, dataSize);
- byte *buf = new byte[dataSize];
- stream->read(buf, dataSize);
- readData(dataType, dataSize, buf);
- delete[] buf;
-
- if ((uint)stream->pos() != oldPos + 4 + (uint)dataSize)
- error("Failed to read correct number of bytes (off by %d) for data type %04x (size %d)",
- (int)stream->pos() - (int)(oldPos + 4 + (uint)dataSize), dataType, dataSize);
-
- if (stream->pos() > endPos)
- error("Read off the end (at %d) of data (ends at %d)", (int)stream->pos(), endPos);
-
- assert(!stream->eos());
- }
-}
-
-LBScriptEntry *LBItem::parseScriptEntry(uint16 type, uint16 &size, Common::MemoryReadStreamEndian *stream, bool isSubentry) {
- if (size < 6)
- error("Script entry of type 0x%04x was too small (%d)", type, size);
-
- uint16 expectedEndSize = 0;
-
- LBScriptEntry *entry = new LBScriptEntry;
- entry->type = type;
- if (isSubentry) {
- expectedEndSize = size - (stream->readUint16() + 2);
- entry->event = 0xffff;
- } else
- entry->event = stream->readUint16();
- entry->opcode = stream->readUint16();
- entry->param = stream->readUint16();
- debug(4, "Script entry: type 0x%04x, event 0x%04x, opcode 0x%04x, param 0x%04x",
- entry->type, entry->event, entry->opcode, entry->param);
- size -= 6;
-
- // TODO: read as bytes, if this is correct (but beware endianism)
- byte conditionTag = (entry->event & 0xff00) >> 8;
- entry->event = entry->event & 0xff;
-
- if (type == kLBMsgListScript && entry->opcode == kLBOpRunSubentries) {
- debug(4, "%d script subentries:", entry->param);
- entry->argc = 0;
- for (uint i = 0; i < entry->param; i++) {
- LBScriptEntry *subentry = parseScriptEntry(type, size, stream, true);
- entry->subentries.push_back(subentry);
-
- // subentries are aligned
- if (i + 1 < entry->param && size % 2 == 1) {
- stream->skip(1);
- size--;
- }
- }
- } else if (type == kLBMsgListScript) {
- if (size < 2)
- error("Script entry of type 0x%04x was too small (%d)", type, size);
-
- entry->argc = stream->readUint16();
- size -= 2;
-
- entry->targetingType = 0;
-
- uint16 targetingType = entry->argc;
- if (targetingType == kTargetTypeExpression || targetingType == kTargetTypeCode
- || targetingType == kTargetTypeName) {
- entry->targetingType = targetingType;
-
- // FIXME
- if (targetingType == kTargetTypeCode)
- error("encountered kTargetTypeCode");
-
- if (size < 2)
- error("not enough bytes (%d) reading special targeting", size);
- uint16 count = stream->readUint16();
- size -= 2;
-
- debug(4, "%d targets with targeting type %04x", count, targetingType);
-
- uint oldAlign = size % 2;
- for (uint i = 0; i < count; i++) {
- Common::String target = _vm->readString(stream);
- debug(4, "target '%s'", target.c_str());
- entry->targets.push_back(target);
- if (target.size() + 1 > size)
- error("failed to read target (ran out of stream)");
- size -= target.size() + 1;
- }
- entry->argc = entry->targets.size();
-
- if ((uint)(size % 2) != oldAlign) {
- stream->skip(1);
- size--;
- }
- } else if (entry->argc) {
- entry->argvParam = new uint16[entry->argc];
- entry->argvTarget = new uint16[entry->argc];
- debug(4, "With %d targets:", entry->argc);
-
- if (size < (entry->argc * 4))
- error("Script entry of type 0x%04x was too small (%d)", type, size);
-
- for (uint i = 0; i < entry->argc; i++) {
- entry->argvParam[i] = stream->readUint16();
- entry->argvTarget[i] = stream->readUint16();
- debug(4, "Target %d, param 0x%04x", entry->argvTarget[i], entry->argvParam[i]);
- }
-
- size -= (entry->argc * 4);
- }
- }
-
- if (type == kLBMsgListScript && entry->opcode == kLBOpJumpUnlessExpression) {
- if (size < 6)
- error("not enough bytes (%d) in kLBOpJumpUnlessExpression, event 0x%04x", size, entry->event);
- entry->offset = stream->readUint32();
- entry->target = stream->readUint16();
- debug(4, "kLBOpJumpUnlessExpression: offset %08x, target %d", entry->offset, entry->target);
- size -= 6;
- }
- if (type == kLBMsgListScript && entry->opcode == kLBOpJumpToExpression) {
- if (size < 4)
- error("not enough bytes (%d) in kLBOpJumpToExpression, event 0x%04x", size, entry->event);
- entry->offset = stream->readUint32();
- debug(4, "kLBOpJumpToExpression: offset %08x", entry->offset);
- size -= 4;
- }
-
- if (type == kLBNotifyScript && entry->opcode == kLBNotifyChangeMode && _vm->getGameType() != GType_LIVINGBOOKSV1) {
- switch (entry->param) {
- case 1:
- if (size < 8)
- error("%d unknown bytes in notify entry kLBNotifyChangeMode", size);
- entry->newUnknown = stream->readUint16();
- entry->newMode = stream->readUint16();
- entry->newPage = stream->readUint16();
- entry->newSubpage = stream->readUint16();
- debug(4, "kLBNotifyChangeMode: unknown %04x, mode %d, page %d.%d",
- entry->newUnknown, entry->newMode, entry->newPage, entry->newSubpage);
- size -= 8;
- break;
- case 3:
- {
- Common::String newCursor = _vm->readString(stream);
- entry->newCursor = newCursor;
- if (size < newCursor.size() + 1)
- error("failed to read newCursor in notify entry");
- size -= newCursor.size() + 1;
- debug(4, "kLBNotifyChangeMode: new cursor '%s'", newCursor.c_str());
- }
- break;
- default:
- // the original engine also does something when param==2 (but not a notify)
- error("unknown v2 kLBNotifyChangeMode type %d", entry->param);
- }
- }
- if (entry->opcode == kLBOpSendExpression) {
- if (size < 4)
- error("not enough bytes (%d) in kLBOpSendExpression, event 0x%04x", size, entry->event);
- entry->offset = stream->readUint32();
- debug(4, "kLBOpSendExpression: offset %08x", entry->offset);
- size -= 4;
- }
- if (entry->opcode == kLBOpRunData) {
- if (size < 4)
- error("didn't get enough bytes (%d) to read data header in script entry", size);
- entry->dataType = stream->readUint16();
- entry->dataLen = stream->readUint16();
- size -= 4;
-
- if (size < entry->dataLen)
- error("didn't get enough bytes (%d) to read data in script entry", size);
-
- if (entry->dataType == kLBCommand) {
- Common::String command = _vm->readString(stream);
- uint commandSize = command.size() + 1;
- if (commandSize > entry->dataLen)
- error("failed to read command in script entry: dataLen %d, command '%s' (%d chars)",
- entry->dataLen, command.c_str(), commandSize);
- entry->dataLen = commandSize;
- entry->data = new byte[commandSize];
- memcpy(entry->data, command.c_str(), commandSize);
- size -= commandSize;
- } else {
- if (conditionTag)
- error("kLBOpRunData had unexpected conditionTag");
- entry->data = new byte[entry->dataLen];
- stream->read(entry->data, entry->dataLen);
- size -= entry->dataLen;
- }
- }
- if (entry->event == kLBEventNotified) {
- if (size < 4)
- error("not enough bytes (%d) in kLBEventNotified, opcode 0x%04x", size, entry->opcode);
- entry->matchFrom = stream->readUint16();
- entry->matchNotify = stream->readUint16();
- debug(4, "kLBEventNotified: matches %04x (from %04x)",
- entry->matchNotify, entry->matchFrom);
- size -= 4;
- }
-
- if (isSubentry) {
- // TODO: subentries may be aligned, so this check is a bit too relaxed
- if (size != expectedEndSize && size != expectedEndSize + 1)
- error("expected %d bytes left at end of subentry, but had %d",
- expectedEndSize, size);
- return entry;
- }
-
- if (conditionTag == 1) {
- if (!size)
- error("failed to read condition (empty stream)");
- Common::String condition = _vm->readString(stream);
- if (condition.size() == 0) {
- size--;
- if (!size)
- error("failed to read condition (null byte, then ran out of stream)");
- condition = _vm->readString(stream);
- }
- if (condition.size() + 1 > size)
- error("failed to read condition (ran out of stream)");
- size -= (condition.size() + 1);
-
- entry->conditions.push_back(condition);
- debug(4, "script entry condition '%s'", condition.c_str());
- } else if (conditionTag == 2) {
- if (size < 4)
- error("expected more than %d bytes for conditionTag 2", size);
- // FIXME
- stream->skip(4);
- size -= 4;
- }
-
- if (size == 1) {
- // FIXME: this is alignment, but why?
- stream->skip(1);
- size--;
- } else if (size)
- error("failed to read script entry correctly (%d bytes left): type 0x%04x, event 0x%04x, opcode 0x%04x, param 0x%04x",
- size, entry->type, entry->event, entry->opcode, entry->param);
-
- return entry;
-}
-
-void LBItem::readData(uint16 type, uint16 size, byte *data) {
- Common::MemoryReadStreamEndian stream(data, size, _vm->isBigEndian());
- readData(type, size, &stream);
-}
-
-void LBItem::readData(uint16 type, uint16 size, Common::MemoryReadStreamEndian *stream) {
- switch (type) {
- case kLBMsgListScript:
- case kLBNotifyScript:
- _scriptEntries.push_back(parseScriptEntry(type, size, stream));
- break;
-
- case kLBSetPlayInfo:
- {
- if (size != 20)
- error("kLBSetPlayInfo had wrong size (%d)", size);
-
- _loopMode = stream->readUint16();
- _delayMin = stream->readUint16();
- _delayMax = stream->readUint16();
- _timingMode = stream->readUint16();
- if (_timingMode > 7)
- error("encountered timing mode %04x", _timingMode);
- _periodMin = stream->readUint16();
- _periodMax = stream->readUint16();
- _relocPoint.x = stream->readSint16();
- _relocPoint.y = stream->readSint16();
- _controlMode = stream->readUint16();
- _soundMode = stream->readUint16();
-
- debug(2, "kLBSetPlayInfo: loop mode %d (%d to %d), timing mode %d (%d to %d), reloc (%d, %d), control mode %04x, sound mode %04x",
- _loopMode, _delayMin, _delayMax,
- _timingMode, _periodMin, _periodMax,
- _relocPoint.x, _relocPoint.y,
- _controlMode, _soundMode);
- }
- break;
-
- case kLBSetPlayPhase:
- if (size != 2)
- error("SetPlayPhase had wrong size (%d)", size);
- _phase = stream->readUint16();
- debug(2, "kLBSetPlayPhase: %d", _phase);
- break;
-
- case kLBSetKeyNotify:
- {
- // FIXME: variable-size notifies, targets
- if (size != 18)
- error("0x6f had wrong size (%d)", size);
- uint event = stream->readUint16();
- LBKey key;
- stream->read(&key, 4);
- uint opcode = stream->readUint16();
- uint param = stream->readUint16();
- uint u6 = stream->readUint16();
- uint u7 = stream->readUint16();
- uint u8 = stream->readUint16();
- uint u9 = stream->readUint16();
- warning("ignoring kLBSetKeyNotify: item %s, key code %02x (modifier mask %d, char %d, repeat %d), event %04x, opcode %04x, param %04x, unknowns %04x, %04x, %04x, %04x",
- _desc.c_str(), key.code, key.modifiers, key.char_, key.repeats, event, opcode, param, u6, u7, u8, u9);
- }
- break;
-
- case kLBCommand:
- {
- Common::String command = _vm->readString(stream);
- if (size != command.size() + 1)
- error("failed to read command string");
-
- runCommand(command);
- }
- break;
-
- case kLBSetNotVisible:
- assert(size == 0);
- _visible = false;
- break;
-
- case kLBGlobalDisable:
- assert(size == 0);
- _globalEnabled = false;
- break;
-
- case kLBGlobalSetNotVisible:
- assert(size == 0);
- _globalVisible = false;
- break;
-
- case kLBSetAmbient:
- assert(size == 0);
- _isAmbient = true;
- break;
-
- case kLBSetKeyEvent:
- {
- // FIXME: targets
- if (size != 10)
- error("kLBSetKeyEvent had wrong size (%d)", size);
- uint u3 = stream->readUint16();
- LBKey key;
- stream->read(&key, 4);
- uint target = stream->readUint16();
- uint16 event = stream->readUint16();
- // FIXME: this is scripting stuff: what to run when key is pressed
- warning("ignoring kLBSetKeyEvent: item %s, key code %02x (modifier mask %d, char %d, repeat %d) unknown %04x, target %d, event %04x",
- _desc.c_str(), key.code, key.modifiers, key.char_, key.repeats, u3, target, event);
- }
- break;
-
- case kLBSetHitTest:
- {
- assert(size == 2);
- uint val = stream->readUint16();
- _doHitTest = (bool)val;
- debug(2, "kLBSetHitTest (on %s): value %04x", _desc.c_str(), val);
- }
- break;
-
- case kLBSetRolloverData:
- {
- assert(size == 2);
- uint16 flag = stream->readUint16();
- warning("ignoring kLBSetRolloverData: item %s, flag %d", _desc.c_str(), flag);
- }
- break;
-
- case kLBSetParent:
- {
- assert(size == 2);
- uint16 parent = stream->readUint16();
- warning("ignoring kLBSetParent: item %s, parent id %d", _desc.c_str(), parent);
- }
- break;
-
- case kLBUnknown194:
- {
- assert(size == 4);
- uint offset = stream->readUint32();
- _page->_code->runCode(this, offset);
- }
- break;
-
- default:
- error("Unknown message %04x (size 0x%04x)", type, size);
- //for (uint i = 0; i < size; i++)
- // debugN("%02x ", stream->readByte());
- //debugN("\n");
- break;
- }
-}
-
-void LBItem::destroySelf() {
- if (!this->_itemId)
- error("destroySelf() on an item which was already dead");
-
- _vm->queueDelayedEvent(DelayedEvent(this, kLBDelayedEventDestroy));
-
- _itemId = 0;
-}
-
-void LBItem::setEnabled(bool enabled) {
- if (enabled && !_loaded && !_playing) {
- if (_timingMode == kLBAutoUserIdle) {
- setNextTime(_periodMin, _periodMax);
- debug(2, "Enable time startup");
- }
- }
-
- _enabled = enabled;
-}
-
-void LBItem::setGlobalEnabled(bool enabled) {
- bool wasEnabled = _loaded && _enabled && _globalEnabled;
- _globalEnabled = enabled;
- if (wasEnabled != (_loaded && _enabled && _globalEnabled))
- setEnabled(enabled);
-}
-
-bool LBItem::contains(Common::Point point) {
- if (!_loaded)
- return false;
-
- if (_playing && _loopMode == 0xFFFF)
- stop();
-
- if (!_playing && _timingMode == kLBAutoUserIdle)
- setNextTime(_periodMin, _periodMax);
-
- return _visible && _globalVisible && _rect.contains(point);
-}
-
-void LBItem::update() {
- if (_phase != kLBPhaseNone && (!_loaded || !_enabled || !_globalEnabled))
- return;
-
- if (_nextTime == 0 || _nextTime > (uint32)(_vm->_system->getMillis() / 16))
- return;
-
- if (togglePlaying(_playing, true)) {
- _nextTime = 0;
- } else if (_loops == 0 && _timingMode == kLBAutoUserIdle) {
- debug(9, "Looping in update()");
- setNextTime(_periodMin, _periodMax);
- }
-}
-
-void LBItem::handleMouseDown(Common::Point pos) {
- if (!_loaded || !_enabled || !_globalEnabled)
- return;
-
- _vm->setFocus(this);
- runScript(kLBEventMouseDown);
- runScript(kLBEventMouseTrackIn);
-}
-
-void LBItem::handleMouseMove(Common::Point pos) {
- // TODO: handle drag
-}
-
-void LBItem::handleMouseUp(Common::Point pos) {
- _vm->setFocus(nullptr);
- runScript(kLBEventMouseUp);
- runScript(kLBEventMouseUpIn);
-}
-
-bool LBItem::togglePlaying(bool playing, bool restart) {
- if (playing) {
- _vm->queueDelayedEvent(DelayedEvent(this, kLBDelayedEventDone));
- return true;
- }
- if (((_loaded && _enabled && _globalEnabled) || _phase == kLBPhaseNone) && !_playing) {
- _playing = togglePlaying(true, restart);
- if (_playing) {
- _nextTime = 0;
- _startTime = _vm->_system->getMillis() / 16;
-
- if (_loopMode == 0xFFFF || _loopMode == 0xFFFE)
- _loops = 0xFFFF;
- else
- _loops = _loopMode;
-
- if (_controlMode >= kLBControlHideMouse) {
- debug(2, "Hiding cursor");
- _vm->_cursor->hideCursor();
- _vm->lockSound(this, true);
-
- if (_controlMode >= kLBControlPauseItems) {
- debug(2, "Disabling all");
- _vm->setEnableForAll(false, this);
- }
- }
-
- runScript(kLBEventStarted);
- notify(0, _itemId);
- }
- }
- return _playing;
-}
-
-void LBItem::done(bool onlyNotify) {
- if (onlyNotify) {
- if (_relocPoint.x || _relocPoint.y) {
- _rect.translate(_relocPoint.x, _relocPoint.y);
- // TODO: does drag box need adjusting?
- }
-
- if (_loops && --_loops) {
- debug(9, "Real looping (now 0x%04x left)", _loops);
- setNextTime(_delayMin, _delayMax, _startTime);
- } else
- done(false);
-
- return;
- }
-
- _playing = false;
- _loops = 0;
- _startTime = 0;
-
- if (_controlMode >= kLBControlHideMouse) {
- debug(2, "Showing cursor");
- _vm->_cursor->showCursor();
- _vm->lockSound(this, false);
-
- if (_controlMode >= kLBControlPauseItems) {
- debug(2, "Enabling all");
- _vm->setEnableForAll(true, this);
- }
- }
-
- if (_timingMode == kLBAutoUserIdle) {
- debug(9, "Looping in done() - %d to %d", _periodMin, _periodMax);
- setNextTime(_periodMin, _periodMax);
- }
-
- runScript(kLBEventDone);
- notify(0xFFFF, _itemId);
-}
-
-void LBItem::init() {
- runScript(kLBEventInit);
-}
-
-void LBItem::setVisible(bool visible) {
- if (visible == _visible)
- return;
-
- _visible = visible;
- _vm->_needsRedraw = true;
-}
-
-void LBItem::setGlobalVisible(bool visible) {
- bool wasEnabled = _visible && _globalVisible;
- _globalVisible = visible;
- if (wasEnabled != (_visible && _globalVisible))
- _vm->_needsRedraw = true;
-}
-
-void LBItem::startPhase(uint phase) {
- if (_phase == phase) {
- if (_phase != kLBPhaseNone) {
- setEnabled(true);
- }
-
- load();
- }
-
- switch (phase) {
- case kLBPhaseLoad:
- runScript(kLBEventListLoad);
- break;
- case kLBPhaseCreate:
- runScript(kLBEventPhaseCreate);
- if (_timingMode == kLBAutoCreate) {
- debug(2, "Phase create: time startup");
- setNextTime(_periodMin, _periodMax);
- }
- break;
- case kLBPhaseInit:
- runScript(kLBEventPhaseInit);
- if (_timingMode == kLBAutoInit) {
- debug(2, "Phase init: time startup");
- setNextTime(_periodMin, _periodMax);
- }
- break;
- case kLBPhaseIntro:
- runScript(kLBEventPhaseIntro);
- if (_timingMode == kLBAutoIntro || _timingMode == kLBAutoUserIdle) {
- debug(2, "Phase intro: time startup");
- setNextTime(_periodMin, _periodMax);
- }
- break;
- case kLBPhaseMain:
- runScript(kLBEventPhaseMain);
- if (_timingMode == kLBAutoUserIdle || _timingMode == kLBAutoMain) {
- debug(2, "Phase main: time startup");
- setNextTime(_periodMin, _periodMax);
- }
- break;
- default:
- break;
- }
-}
-
-void LBItem::stop() {
- if (!_playing)
- return;
-
- _loops = 0;
- seek(0xFFFF);
- done(true);
-}
-
-void LBItem::notify(uint16 data, uint16 from) {
- if (_timingMode == kLBAutoSync) {
- // TODO: is this correct?
- if (_periodMin == data && _periodMax == from) {
- debug(2, "Handling notify 0x%04x (from %d)", data, from);
- setNextTime(0, 0);
- }
- }
-
- runScript(kLBEventNotified, data, from);
-}
-
-void LBItem::load() {
- if (_loaded)
- return;
-
- _loaded = true;
-
- // FIXME: events etc
- if (_timingMode == kLBAutoLoad) {
- debug(2, "Load: time startup");
- setNextTime(_periodMin, _periodMax);
- }
-}
-
-void LBItem::unload() {
- if (!_loaded)
- return;
-
- _loaded = false;
-
- // FIXME: stuff
-}
-
-void LBItem::moveBy(const Common::Point &pos) {
- _rect.translate(pos.x, pos.y);
-}
-
-void LBItem::moveTo(const Common::Point &pos) {
- _rect.moveTo(pos);
-}
-
-LBItem *LBItem::clone(uint16 newId, const Common::String &newName) {
- LBItem *item = createClone();
-
- item->_itemId = newId;
- item->_desc = newName;
-
- item->_resourceId = _resourceId;
- // FIXME: the rest
-
- _page->addClonedItem(item);
- // FIXME: zorder?
- return item;
-}
-
-LBItem *LBItem::createClone() {
- return new LBItem(_vm, _page, _rect);
-}
-
-void LBItem::runScript(uint event, uint16 data, uint16 from) {
- for (uint i = 0; i < _scriptEntries.size(); i++) {
- LBScriptEntry *entry = _scriptEntries[i];
-
- if (entry->event != event)
- continue;
-
- if (event == kLBEventNotified) {
- if ((entry->matchFrom && entry->matchFrom != from) || entry->matchNotify != data)
- continue;
- }
-
- bool conditionsMatch = true;
- for (uint n = 0; n < entry->conditions.size(); n++) {
- if (!checkCondition(entry->conditions[n])) {
- conditionsMatch = false;
- break;
- }
- }
- if (!conditionsMatch)
- continue;
-
- if (entry->type == kLBNotifyScript) {
- debug(2, "Notify: event 0x%04x, opcode 0x%04x, param 0x%04x",
- entry->event, entry->opcode, entry->param);
-
- if (entry->opcode == kLBNotifyGUIAction)
- _vm->addNotifyEvent(NotifyEvent(entry->opcode, _itemId));
- else if (entry->opcode == kLBNotifyChangeMode && _vm->getGameType() != GType_LIVINGBOOKSV1) {
- NotifyEvent notifyEvent(entry->opcode, entry->param);
- notifyEvent.newUnknown = entry->newUnknown;
- notifyEvent.newMode = entry->newMode;
- notifyEvent.newPage = entry->newPage;
- notifyEvent.newSubpage = entry->newSubpage;
- notifyEvent.newCursor = entry->newCursor;
- _vm->addNotifyEvent(notifyEvent);
- } else
- _vm->addNotifyEvent(NotifyEvent(entry->opcode, entry->param));
- } else
- runScriptEntry(entry);
- }
-}
-
-int LBItem::runScriptEntry(LBScriptEntry *entry) {
- if (entry->state == 0xffff)
- return 0;
-
- uint start = 0;
- uint count = entry->argc;
- // zero targets = apply to self
- if (!count)
- count = 1;
-
- if (entry->opcode != kLBOpRunSubentries) switch (entry->param) {
- case 0xfffe:
- // Run once (disable self after run).
- entry->state = 0xffff;
- break;
- case 0xffff:
- break;
- case 0:
- case 1:
- case 2:
- start = entry->state;
- entry->state++;
- if (entry->state >= count) {
- switch (entry->param) {
- case 0:
- // Disable..
- entry->state = 0xffff;
- return 0;
- case 1:
- // Stay at the end.
- entry->state = count - 1;
- break;
- case 2:
- // Loop.
- entry->state = 0;
- break;
- default:
- break;
- }
- }
- count = 1;
- break;
- case 3:
- // Pick random target.
- start = _vm->_rnd->getRandomNumberRng(0, count);
- count = 1;
- break;
- default:
- warning("Weird param for script entry (type 0x%04x, event 0x%04x, opcode 0x%04x, param 0x%04x)",
- entry->type, entry->event, entry->opcode, entry->param);
- }
-
- for (uint n = start; n < count; n++) {
- LBItem *target;
-
- debug(2, "Script run: type 0x%04x, event 0x%04x, opcode 0x%04x, param 0x%04x",
- entry->type, entry->event, entry->opcode, entry->param);
-
- if (entry->argc) {
- switch (entry->targetingType) {
- case kTargetTypeExpression:
- {
- // FIXME: this should be EVALUATED
- LBValue &tgt = _vm->_variables[entry->targets[n]];
- switch (tgt.type) {
- case kLBValueItemPtr:
- target = tgt.item;
- break;
- case kLBValueString:
- // FIXME: handle 'self', at least
- // TODO: correct otherwise? or only self?
- target = _vm->getItemByName(tgt.string);
- break;
- case kLBValueInteger:
- target = _vm->getItemById(tgt.integer);
- break;
- default:
- // FIXME: handle list
- warning("Target '%s' (by expression) resulted in unknown type, skipping", entry->targets[n].c_str());
- continue;
- }
- }
- if (!target) {
- debug(2, "Target '%s' (by expression) doesn't exist, skipping", entry->targets[n].c_str());
- continue;
- }
- debug(2, "Target: '%s' (expression '%s')", target->_desc.c_str(), entry->targets[n].c_str());
- break;
- case kTargetTypeCode:
- // FIXME
- error("encountered kTargetTypeCode");
- break;
- case kTargetTypeName:
- // FIXME: handle 'self'
- target = _vm->getItemByName(entry->targets[n]);
- if (!target) {
- debug(2, "Target '%s' (by name) doesn't exist, skipping", entry->targets[n].c_str());
- continue;
- }
- debug(2, "Target: '%s' (by name)", target->_desc.c_str());
- break;
- default:
- uint16 targetId = entry->argvTarget[n];
- // TODO: is this type, perhaps?
- uint16 param = entry->argvParam[n];
- target = _vm->getItemById(targetId);
- if (!target) {
- debug(2, "Target %04x (%04x) doesn't exist, skipping", targetId, param);
- continue;
- }
- debug(2, "Target: %04x (%04x) '%s'", targetId, param, target->_desc.c_str());
- }
- } else {
- target = this;
- debug(2, "Self-target on '%s'", _desc.c_str());
- }
-
- // an opcode in the form 0x1xx means to run the script for event 0xx
- if ((entry->opcode & 0xff00) == 0x0100) {
- // FIXME: pass on param
- target->runScript(entry->opcode & 0xff);
- break;
- }
-
- switch (entry->opcode) {
- case kLBOpNone:
- warning("ignoring kLBOpNone (event 0x%04x, param 0x%04x, target '%s')",
- entry->event, entry->param, target->_desc.c_str());
- break;
-
- case kLBOpXShow:
- // TODO: should be setVisible(true) - not a delayed event -
- // when we're doing the param 1/2/3 stuff above?
- // and in modern LB this is perhaps just a direct target->setVisible(true)..
- if (_vm->getGameType() != GType_LIVINGBOOKSV1)
- warning("kLBOpXShow on '%s' is probably broken", target->_desc.c_str());
- _vm->queueDelayedEvent(DelayedEvent(this, kLBDelayedEventSetNotVisible));
- break;
-
- case kLBOpTogglePlay:
- target->togglePlaying(false, true);
- break;
-
- case kLBOpSetNotVisible:
- target->setVisible(false);
- break;
-
- case kLBOpSetVisible:
- target->setVisible(true);
- break;
-
- case kLBOpDestroy:
- target->destroySelf();
- break;
-
- case kLBOpRewind:
- target->seek(1);
- break;
-
- case kLBOpStop:
- target->stop();
- break;
-
- case kLBOpDisable:
- target->setEnabled(false);
- break;
-
- case kLBOpEnable:
- target->setEnabled(true);
- break;
-
- case kLBOpGlobalSetNotVisible:
- target->setGlobalVisible(false);
- break;
-
- case kLBOpGlobalSetVisible:
- target->setGlobalVisible(true);
- break;
-
- case kLBOpGlobalDisable:
- target->setGlobalEnabled(false);
- break;
-
- case kLBOpGlobalEnable:
- target->setGlobalEnabled(true);
- break;
-
- case kLBOpSeekToEnd:
- target->seek(0xFFFF);
- break;
-
- case kLBOpMute:
- case kLBOpUnmute:
- // FIXME
- warning("ignoring kLBOpMute/Unmute (event 0x%04x, param 0x%04x, target '%s')",
- entry->event, entry->param, target->_desc.c_str());
- break;
-
- case kLBOpLoad:
- target->load();
- break;
-
- case kLBOpPreload:
- // FIXME
- warning("ignoring kLBOpPreload (event 0x%04x, param 0x%04x, target '%s')",
- entry->event, entry->param, target->_desc.c_str());
- break;
-
- case kLBOpUnload:
- target->unload();
- break;
-
- case kLBOpSeekToPrev:
- case kLBOpSeekToNext:
- // FIXME
- warning("ignoring kLBOpSeekToPrev/Next (event 0x%04x, param 0x%04x, target '%s')",
- entry->event, entry->param, target->_desc.c_str());
- break;
-
- case kLBOpDragBegin:
- case kLBOpDragEnd:
- // FIXME
- warning("ignoring kLBOpDragBegin/End (event 0x%04x, param 0x%04x, target '%s')",
- entry->event, entry->param, target->_desc.c_str());
- break;
-
- case kLBOpScriptDisable:
- case kLBOpScriptEnable:
- // FIXME
- warning("ignoring kLBOpScriptDisable/Enable (event 0x%04x, param 0x%04x, target '%s')",
- entry->event, entry->param, target->_desc.c_str());
- break;
-
- case kLBOpUnknown1C:
- // FIXME
- warning("ignoring kLBOpUnknown1C (event 0x%04x, param 0x%04x, target '%s')",
- entry->event, entry->param, target->_desc.c_str());
- break;
-
- case kLBOpSendExpression:
- _page->_code->runCode(this, entry->offset);
- break;
-
- case kLBOpRunSubentries:
- for (uint i = 0; i < entry->subentries.size(); i++) {
- LBScriptEntry *subentry = entry->subentries[i];
-
- int e = runScriptEntry(subentry);
-
- switch (subentry->opcode) {
- case kLBOpJumpUnlessExpression:
- debug(2, "JumpUnless got %d (to %d, on %d, of %d)", e, subentry->target, i, entry->subentries.size());
- if (!e)
- i = subentry->target - 1;
- break;
- case kLBOpBreakExpression:
- debug(2, "BreakExpression");
- i = entry->subentries.size();
- break;
- case kLBOpJumpToExpression:
- debug(2, "JumpToExpression got %d (on %d, of %d)", e, i, entry->subentries.size());
- i = e - 1;
- break;
- default:
- break;
- }
- }
- break;
-
- case kLBOpRunData:
- readData(entry->dataType, entry->dataLen, entry->data);
- break;
-
- case kLBOpJumpUnlessExpression:
- case kLBOpBreakExpression:
- case kLBOpJumpToExpression:
- {
- LBValue r = _page->_code->runCode(this, entry->offset);
- // FIXME
- return r.integer;
- }
-
- default:
- error("Unknown script opcode (type 0x%04x, event 0x%04x, opcode 0x%04x, param 0x%04x, target '%s')",
- entry->type, entry->event, entry->opcode, entry->param, target->_desc.c_str());
- }
- }
-
- return 0;
-}
-
-void LBItem::setNextTime(uint16 min, uint16 max) {
- setNextTime(min, max, _vm->_system->getMillis() / 16);
-}
-
-void LBItem::setNextTime(uint16 min, uint16 max, uint32 start) {
- _nextTime = start + _vm->_rnd->getRandomNumberRng((uint)min, (uint)max);
- debug(9, "nextTime is now %d frames away", _nextTime - (uint)(_vm->_system->getMillis() / 16));
-}
-
-void LBItem::runCommand(const Common::String &command) {
- LBCode tempCode(_vm, 0);
-
- debug(2, "running command '%s'", command.c_str());
-
- uint offset = tempCode.parseCode(command);
- tempCode.runCode(this, offset);
-}
-
-bool LBItem::checkCondition(const Common::String &condition) {
- LBCode tempCode(_vm, 0);
-
- debug(3, "checking condition '%s'", condition.c_str());
-
- uint offset = tempCode.parseCode(condition);
- LBValue result = tempCode.runCode(this, offset);
-
- return result.toInt();
-}
-
} // End of namespace Mohawk
diff --git a/engines/mohawk/livingbooks.h b/engines/mohawk/livingbooks.h
index a9819b3db25..6ba5f90572c 100644
--- a/engines/mohawk/livingbooks.h
+++ b/engines/mohawk/livingbooks.h
@@ -25,6 +25,7 @@
#include "mohawk/mohawk.h"
#include "mohawk/console.h"
#include "mohawk/livingbooks_graphics.h"
+#include "mohawk/livingbooks_constants.h"
#include "mohawk/sound.h"
#include "mohawk/video.h"
@@ -35,6 +36,12 @@
#include "livingbooks_code.h"
+#include "mohawk/livingbooks_itemscript.h"
+#include "mohawk/livingbooks_item.h"
+#include "mohawk/livingbooks_itemtypes.h"
+#include "mohawk/livingbooks_animation.h"
+#include "mohawk/livingbooks_page.h"
+
namespace Common {
class SeekableReadStreamEndian;
class MemoryReadStreamEndian;
@@ -42,221 +49,6 @@ namespace Common {
namespace Mohawk {
-// Engine Debug Flags
-enum {
- kDebugCode = 1,
-};
-
-#define LBKEY_MOD_CTRL 1
-#define LBKEY_MOD_ALT 2
-#define LBKEY_MOD_SHIFT 4
-
-struct LBKey {
- byte code;
- byte modifiers;
- byte char_;
- byte repeats;
-};
-
-enum NodeState {
- kLBNodeDone = 0,
- kLBNodeRunning = 1,
- kLBNodeWaiting = 2
-};
-
-enum LBMode {
- kLBIntroMode = 1,
- kLBControlMode = 2,
- kLBCreditsMode = 3,
- kLBPreviewMode = 4,
- kLBReadMode = 5,
- kLBPlayMode = 6
-};
-
-enum {
- kLBPhaseInit = 0x0,
- kLBPhaseIntro = 0x1,
- kLBPhaseMain = 0x2,
- kLBPhaseNone = 0x7fff,
- kLBPhaseLoad = 0xfffe,
- kLBPhaseCreate = 0xffff
-};
-
-// automatic modes used in _timingMode
-enum {
- kLBAutoNone = 0,
- kLBAutoIntro = 1,
- kLBAutoUserIdle = 2,
- kLBAutoMain = 3,
- kLBAutoSync = 4,
- kLBAutoInit = 5,
- kLBAutoCreate = 6,
- kLBAutoLoad = 7
-};
-
-// control modes used in _controlMode
-enum {
- kLBControlNone = 0,
- kLBControlHideMouse = 1,
- kLBControlPauseItems = 2
-};
-
-enum {
- kLBStaticTextItem = 0x1,
- kLBPictureItem = 0x2,
- kLBEditTextItem = 0x14,
- kLBLiveTextItem = 0x15,
- kLBAnimationItem = 0x40,
- kLBSoundItem = 0x41,
- kLBGroupItem = 0x42,
- kLBMovieItem = 0x43,
- kLBPaletteAItem = 0x44, // unused?
- kLBPaletteItem = 0x45,
- kLBProxyItem = 0x46,
- kLBMiniGameItem = 666, // EVIL!!!!
- kLBXDataFileItem = 0x3e9,
- kLBDiscDectectorItem = 0xfa1
-};
-
-enum {
- // no 0x1?
- kLBAnimOpNotify = 0x2,
- kLBAnimOpSetTempo = 0x3,
- // no 0x4?
- kLBAnimOpMoveTo = 0x5,
- kLBAnimOpWait = 0x6,
- kLBAnimOpSetCel = 0x7,
- kLBAnimOpSleepUntil = 0x8,
- kLBAnimOpDrawMode = 0x9,
- kLBAnimOpPlaySound = 0xa,
- kLBAnimOpWaitForSound = 0xb,
- kLBAnimOpReleaseSound = 0xc,
- kLBAnimOpResetSound = 0xd,
- kLBAnimOpSetTempoDiv = 0xe,
- kLBAnimOpDelay = 0xf
-};
-
-enum {
- kLBEventPhaseInit = 0,
- kLBEventPhaseIntro = 1,
- kLBEventMouseDown = 2,
- kLBEventStarted = 3,
- kLBEventDone = 4,
- kLBEventMouseUp = 5,
- kLBEventPhaseMain = 6,
- kLBEventNotified = 7,
- kLBEventDragStart = 8,
- kLBEventDragMove = 9,
- kLBEventDragEnd = 0xa,
- kLBEventRolloverBegin = 0xb,
- kLBEventRolloverMove = 0xc,
- kLBEventRolloverEnd = 0xd,
- kLBEventMouseUpIn = 0xe,
- kLBEventMouseUpOut = 0xf,
- kLBEventMouseTrackIn = 0x10,
- kLBEventMouseTrackMove = 0x11,
- kLBEventMouseTrackMoveIn = 0x12,
- kLBEventMouseTrackMoveOut = 0x13,
- kLBEventMouseTrackOut = 0x14,
- kLBEventFocusBegin = 0x15,
- kLBEventFocusEnd = 0x16,
- kLBEventInit = 0x17,
- kLBEventLoad = 0x1a,
- kLBEventListLoad = 0x1b,
- kLBEventPhaseCreate = 0xff
-};
-
-enum {
- kLBGroupData = 0x64,
- kLBLiveTextData = 0x65,
- kLBMsgListScript = 0x66,
- kLBNotifyScript = 0x67,
- kLBSetPlayInfo = 0x68,
- kLBSetRandomLoc = 0x69, // unused?
- kLBSetDrag = 0x6a, // unused?
- kLBSetDrawMode = 0x6b,
- kLBSetFont = 0x6c, // unused?
- kLBSetOneShot = 0x6d, // unused?
- kLBSetPlayPhase = 0x6e,
- // from here, 2.x+
- kLBSetKeyNotify = 0x6f,
- kLBCommand = 0x70,
- kLBPaletteAData = 0x71, // unused?
- kLBPaletteXData = 0x72,
- kLBDisable = 0x73, // unused?
- kLBEnable = 0x74, // unused?
- kLBSetNotVisible = 0x75,
- kLBSetVisible = 0x76, // unused?
- kLBGlobalDisable = 0x77,
- kLBGlobalEnable = 0x78, // unused?
- kLBGlobalSetNotVisible = 0x79,
- kLBGlobalSetVisible = 0x7a, // unused?
- kLBSetAmbient = 0x7b,
- kLBSetDragParams = 0x7c,
- kLBSetKeyEvent = 0x7d,
- kLBSetRolloverData = 0x7e,
- kLBSetParent = 0x7f,
- kLBSetHitTest = 0x80,
- // from here, rugrats
- kLBUnknown194 = 0x194
-};
-
-enum {
- kLBOpNone = 0x0,
- kLBOpXShow = 0x1,
- kLBOpTogglePlay = 0x2,
- kLBOpSetNotVisible = 0x3,
- kLBOpSetVisible = 0x4,
- kLBOpDestroy = 0x5,
- kLBOpRewind = 0x6,
- kLBOpStop = 0x7,
- kLBOpDisable = 0x8,
- kLBOpEnable = 0x9,
- // (no 0xa)
- kLBOpGlobalSetNotVisible = 0xb,
- kLBOpGlobalSetVisible = 0xc,
- kLBOpGlobalDisable = 0xd,
- kLBOpGlobalEnable = 0xe,
- kLBOpSeekToEnd = 0xf,
- // (no 0x10)
- kLBOpMute = 0x11,
- kLBOpUnmute = 0x12,
- kLBOpLoad = 0x13,
- kLBOpPreload = 0x14,
- kLBOpUnload = 0x15,
- kLBOpSeekToNext = 0x16,
- kLBOpSeekToPrev = 0x17,
- kLBOpDragBegin = 0x18,
- kLBOpDragEnd = 0x19,
- kLBOpScriptDisable = 0x1a,
- kLBOpScriptEnable = 0x1b,
- kLBOpUnknown1C = 0x1c,
- kLBOpSendExpression = 0x1d,
- kLBOpJumpUnlessExpression = 0xfffb,
- kLBOpBreakExpression = 0xfffc,
- kLBOpJumpToExpression = 0xfffd,
- kLBOpRunSubentries = 0xfffe,
- kLBOpRunData = 0xffff
-};
-
-enum {
- kLBNotifyGUIAction = 1,
- kLBNotifyGoToControls = 2,
- kLBNotifyChangePage = 3,
- kLBNotifyGotoQuit = 4,
- kLBNotifyIntroDone = 5,
- kLBNotifyChangeMode = 6,
- kLBNotifyCursorChange = 7,
- kLBNotifyPrintPage = 0xc,
- kLBNotifyQuit = 0xd
-};
-
-enum {
- kTargetTypeExpression = 0x3f3f,
- kTargetTypeCode = 0xfffe,
- kTargetTypeName = 0xffff
-};
-
class MohawkEngine_LivingBooks;
class LBPage;
class LBGraphics;
@@ -264,403 +56,6 @@ class LBAnimation;
bool tryOpenPage(Archive *archive, const Common::String &fileName);
-struct LBScriptEntry {
- LBScriptEntry();
- ~LBScriptEntry();
-
- uint16 state;
-
- uint16 type;
- uint16 event;
- uint16 opcode;
- uint16 param;
-
- uint16 argc;
- uint16 *argvParam;
- uint16 *argvTarget;
-
- uint16 targetingType;
- Common::Array<Common::String> targets;
-
- // kLBNotifyChangeMode
- uint16 newUnknown;
- uint16 newMode;
- uint16 newPage;
- uint16 newSubpage;
- Common::String newCursor;
-
- // kLBEventNotified
- uint16 matchFrom;
- uint16 matchNotify;
-
- // kLBOpSendExpression
- uint32 offset;
- // kLBOpJumpUnlessExpression
- uint16 target;
-
- uint16 dataType;
- uint16 dataLen;
- byte *data;
-
- Common::Array<Common::String> conditions;
- Common::Array<LBScriptEntry *> subentries;
-};
-
-struct LBAnimScriptEntry {
- byte opcode;
- byte size;
- byte *data;
-};
-
-class LBAnimationNode {
-public:
- LBAnimationNode(MohawkEngine_LivingBooks *vm, LBAnimation *parent, uint16 scriptResourceId);
- ~LBAnimationNode();
-
- void draw(const Common::Rect &_bounds);
- void reset();
- NodeState update(bool seeking = false);
- bool transparentAt(int x, int y);
-
-protected:
- MohawkEngine_LivingBooks *_vm;
- LBAnimation *_parent;
-
- void loadScript(uint16 resourceId);
- uint _currentEntry;
- Common::Array<LBAnimScriptEntry> _scriptEntries;
-
- uint _currentCel;
- int16 _xPos, _yPos;
- uint32 _delay;
-};
-
-class LBAnimationItem;
-
-class LBAnimation {
-public:
- LBAnimation(MohawkEngine_LivingBooks *vm, LBAnimationItem *parent, uint16 resourceId);
- ~LBAnimation();
-
- void draw();
- bool update();
-
- void start();
- void seek(uint16 pos);
- void seekToTime(uint32 time);
- void stop();
-
- void playSound(uint16 resourceId);
- bool soundPlaying(uint16 resourceId, const Common::String &cue);
-
- bool transparentAt(int x, int y);
-
- void setTempo(uint16 tempo);
-
- uint getNumResources() { return _shapeResources.size(); }
- uint16 getResource(uint num) { return _shapeResources[num]; }
- Common::Point getOffset(uint num) { return _shapeOffsets[num]; }
-
- uint32 getCurrentFrame() { return _currentFrame; }
-
- uint16 getParentId();
-
-protected:
- MohawkEngine_LivingBooks *_vm;
- LBAnimationItem *_parent;
-
- Common::Rect _bounds, _clip;
- Common::Array<LBAnimationNode *> _nodes;
-
- uint16 _tempo;
-
- uint16 _currentSound;
- CueList _cueList;
-
- uint32 _lastTime, _currentFrame;
- bool _running;
-
- void loadShape(uint16 resourceId);
- Common::Array<uint16> _shapeResources;
- Common::Array<Common::Point> _shapeOffsets;
-};
-
-class LBItem {
- friend class LBCode;
-
-public:
- LBItem(MohawkEngine_LivingBooks *vm, LBPage *page, Common::Rect rect);
- virtual ~LBItem();
-
- void readFrom(Common::SeekableReadStreamEndian *stream);
- void readData(uint16 type, uint16 size, byte *data);
- virtual void readData(uint16 type, uint16 size, Common::MemoryReadStreamEndian *stream);
-
- virtual void destroySelf(); // 0x2
- virtual void setEnabled(bool enabled); // 0x3
- virtual void setGlobalEnabled(bool enabled);
- virtual bool contains(Common::Point point); // 0x7
- virtual void update(); // 0x8
- virtual void draw() { } // 0x9
- virtual void handleKeyChar(Common::Point pos) { } // 0xA
- virtual void handleMouseDown(Common::Point pos); // 0xB
- virtual void handleMouseMove(Common::Point pos); // 0xC
- virtual void handleMouseUp(Common::Point pos); // 0xD
- virtual bool togglePlaying(bool playing, bool restart = false); // 0xF
- virtual void done(bool onlyNotify); // 0x10
- virtual void init(); // 0x11
- virtual void seek(uint16 pos) { } // 0x13
- virtual void seekToTime(uint32 time) { }
- virtual void setFocused(bool focused) { } // 0x14
- virtual void setVisible(bool visible); // 0x17
- virtual void setGlobalVisible(bool enabled);
- virtual void startPhase(uint phase); // 0x18
- virtual void stop(); // 0x19
- virtual void notify(uint16 data, uint16 from); // 0x1A
- virtual void load();
- virtual void unload();
- virtual void moveBy(const Common::Point &pos);
- virtual void moveTo(const Common::Point &pos);
-
- LBItem *clone(uint16 newId, const Common::String &newName);
-
- uint16 getId() { return _itemId; }
- const Common::String &getName() { return _desc; }
- const Common::Rect &getRect() { return _rect; }
- uint16 getSoundPriority() { return _soundMode; }
- bool isLoaded() { return _loaded; }
- bool isAmbient() { return _isAmbient; }
-
- Common::List<LBItem *>::iterator _iterator;
-
- // TODO: make private
- Common::HashMap<Common::String, LBValue, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _variables;
-
-protected:
- MohawkEngine_LivingBooks *_vm;
- LBPage *_page;
-
- void setNextTime(uint16 min, uint16 max);
- void setNextTime(uint16 min, uint16 max, uint32 start);
-
- Common::Rect _rect;
- Common::String _desc;
- uint16 _resourceId;
- uint16 _itemId;
-
- bool _loaded, _visible, _globalVisible, _playing, _enabled, _globalEnabled;
-
- uint32 _nextTime, _startTime;
- uint16 _loops;
-
- uint16 _phase, _timingMode, _delayMin, _delayMax;
- uint16 _loopMode, _periodMin, _periodMax;
- uint16 _controlMode, _soundMode;
- Common::Point _relocPoint;
-
- bool _isAmbient;
- bool _doHitTest;
-
- virtual LBItem *createClone();
-
- Common::Array<LBScriptEntry *> _scriptEntries;
- void runScript(uint event, uint16 data = 0, uint16 from = 0);
- int runScriptEntry(LBScriptEntry *entry);
-
- void runCommand(const Common::String &command);
- bool checkCondition(const Common::String &condition);
-
- LBScriptEntry *parseScriptEntry(uint16 type, uint16 &size, Common::MemoryReadStreamEndian *stream, bool isSubentry = false);
-};
-
-class LBSoundItem : public LBItem {
-public:
- LBSoundItem(MohawkEngine_LivingBooks *_vm, LBPage *page, Common::Rect rect);
- ~LBSoundItem() override;
-
- void update() override;
- bool togglePlaying(bool playing, bool restart) override;
- void stop() override;
-
-protected:
- LBItem *createClone() override;
-
- bool _running;
-};
-
-struct GroupEntry {
- uint entryId;
- uint entryType;
-};
-
-class LBGroupItem : public LBItem {
-public:
- LBGroupItem(MohawkEngine_LivingBooks *_vm, LBPage *page, Common::Rect rect);
-
- void readData(uint16 type, uint16 size, Common::MemoryReadStreamEndian *stream) override;
-
- void destroySelf() override;
- void setEnabled(bool enabled) override;
- void setGlobalEnabled(bool enabled) override;
- bool contains(Common::Point point) override;
- bool togglePlaying(bool playing, bool restart) override;
- // 0x12
- void seek(uint16 pos) override;
- void setVisible(bool visible) override;
- void setGlobalVisible(bool visible) override;
- void startPhase(uint phase) override;
- void stop() override;
- void load() override;
- void unload() override;
- void moveBy(const Common::Point &pos) override;
- void moveTo(const Common::Point &pos) override;
-
-protected:
- LBItem *createClone() override;
-
- bool _starting;
-
- Common::Array<GroupEntry> _groupEntries;
-};
-
-class LBPaletteItem : public LBItem {
-public:
- LBPaletteItem(MohawkEngine_LivingBooks *_vm, LBPage *page, Common::Rect rect);
- ~LBPaletteItem() override;
-
- void readData(uint16 type, uint16 size, Common::MemoryReadStreamEndian *stream) override;
-
- bool togglePlaying(bool playing, bool restart) override;
- void update() override;
-
-protected:
- LBItem *createClone() override;
-
- uint16 _fadeInPeriod, _fadeInStep, _drawStart, _drawCount;
- uint32 _fadeInStart, _fadeInCurrent;
- byte *_palette;
-};
-
-struct LiveTextWord {
- Common::Rect bounds;
- uint16 soundId;
-
- uint16 itemType;
- uint16 itemId;
-};
-
-struct LiveTextPhrase {
- uint16 wordStart, wordCount;
- uint16 highlightStart, highlightEnd;
- uint16 startId, endId;
-};
-
-class LBLiveTextItem : public LBItem {
-public:
- LBLiveTextItem(MohawkEngine_LivingBooks *_vm, LBPage *page, Common::Rect rect);
-
- void readData(uint16 type, uint16 size, Common::MemoryReadStreamEndian *stream) override;
-
- bool contains(Common::Point point) override;
- void update() override;
- void draw() override;
- void handleMouseDown(Common::Point pos) override;
- bool togglePlaying(bool playing, bool restart) override;
- void stop() override;
- void notify(uint16 data, uint16 from) override;
-
-protected:
- LBItem *createClone() override;
-
- void paletteUpdate(uint16 word, bool on);
- void drawWord(uint word, uint yPos);
-
- uint16 _currentPhrase, _currentWord;
-
- byte _backgroundColor[4];
- byte _foregroundColor[4];
- byte _highlightColor[4];
- uint16 _paletteIndex;
-
- Common::Array<LiveTextWord> _words;
- Common::Array<LiveTextPhrase> _phrases;
-};
-
-class LBPictureItem : public LBItem {
-public:
- LBPictureItem(MohawkEngine_LivingBooks *_vm, LBPage *page, Common::Rect rect);
-
- void readData(uint16 type, uint16 size, Common::MemoryReadStreamEndian *stream) override;
-
- bool contains(Common::Point point) override;
- void draw() override;
- void init() override;
-
-protected:
- LBItem *createClone() override;
-};
-
-class LBAnimationItem : public LBItem {
-public:
- LBAnimationItem(MohawkEngine_LivingBooks *_vm, LBPage *page, Common::Rect rect);
- ~LBAnimationItem() override;
-
- void setEnabled(bool enabled) override;
- bool contains(Common::Point point) override;
- void update() override;
- void draw() override;
- bool togglePlaying(bool playing, bool restart) override;
- void done(bool onlyNotify) override;
- void init() override;
- void seek(uint16 pos) override;
- void seekToTime(uint32 time) override;
- void startPhase(uint phase) override;
- void stop() override;
-
-protected:
- LBItem *createClone() override;
-
- LBAnimation *_anim;
- bool _running;
-};
-
-class LBMovieItem : public LBItem {
-public:
- LBMovieItem(MohawkEngine_LivingBooks *_vm, LBPage *page, Common::Rect rect);
- ~LBMovieItem() override;
-
- void update() override;
- bool togglePlaying(bool playing, bool restart) override;
-
-protected:
- LBItem *createClone() override;
-};
-
-class LBMiniGameItem : public LBItem {
-public:
- LBMiniGameItem(MohawkEngine_LivingBooks *_vm, LBPage *page, Common::Rect rect);
- ~LBMiniGameItem() override;
-
- bool togglePlaying(bool playing, bool restart) override;
-
-protected:
- LBItem *createClone() override;
-};
-
-class LBProxyItem : public LBItem {
-public:
- LBProxyItem(MohawkEngine_LivingBooks *_vm, LBPage *page, Common::Rect rect);
- ~LBProxyItem() override;
-
- void load() override;
- void unload() override;
-
-protected:
- LBItem *createClone() override;
-
- class LBPage *_page;
-};
-
struct NotifyEvent {
NotifyEvent(uint t, uint p) : type(t), param(p), newUnknown(0), newMode(0), newPage(0), newSubpage(0) { }
uint type;
@@ -686,31 +81,6 @@ struct DelayedEvent {
DelayedEventType type;
};
-class LBPage {
-public:
- LBPage(MohawkEngine_LivingBooks *vm);
- ~LBPage();
-
- void open(Archive *mhk, uint16 baseId);
- uint16 getResourceVersion();
-
- void addClonedItem(LBItem *item);
- void itemDestroyed(LBItem *item);
-
- LBCode *_code;
-
-protected:
- MohawkEngine_LivingBooks *_vm;
-
- Archive *_mhk;
- Common::Array<LBItem *> _items;
-
- uint16 _baseId;
- bool _cascade;
-
- void loadBITL(uint16 resourceId);
-};
-
class MohawkEngine_LivingBooks : public MohawkEngine {
protected:
Common::Error run() override;
diff --git a/engines/mohawk/livingbooks_animation.h b/engines/mohawk/livingbooks_animation.h
new file mode 100644
index 00000000000..73aca01952e
--- /dev/null
+++ b/engines/mohawk/livingbooks_animation.h
@@ -0,0 +1,143 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef MOHAWK_LIVINGBOOKS_ANIMATION_H
+#define MOHAWK_LIVINGBOOKS_ANIMATION_H
+
+#include "mohawk/livingbooks_constants.h"
+#include "mohawk/livingbooks_item.h"
+#include "mohawk/sound.h"
+
+#include "common/array.h"
+#include "common/rect.h"
+
+namespace Mohawk {
+
+class MohawkEngine_LivingBooks;
+class LBPage;
+class LBAnimation;
+
+struct LBAnimScriptEntry {
+ byte opcode;
+ byte size;
+ byte *data;
+};
+
+class LBAnimationNode {
+public:
+ LBAnimationNode(MohawkEngine_LivingBooks *vm, LBAnimation *parent, uint16 scriptResourceId);
+ ~LBAnimationNode();
+
+ void draw(const Common::Rect &_bounds);
+ void reset();
+ NodeState update(bool seeking = false);
+ bool transparentAt(int x, int y);
+
+protected:
+ MohawkEngine_LivingBooks *_vm;
+ LBAnimation *_parent;
+
+ void loadScript(uint16 resourceId);
+ uint _currentEntry;
+ Common::Array<LBAnimScriptEntry> _scriptEntries;
+
+ uint _currentCel;
+ int16 _xPos, _yPos;
+ uint32 _delay;
+};
+
+class LBAnimationItem;
+
+class LBAnimation {
+public:
+ LBAnimation(MohawkEngine_LivingBooks *vm, LBAnimationItem *parent, uint16 resourceId);
+ ~LBAnimation();
+
+ void draw();
+ bool update();
+
+ void start();
+ void seek(uint16 pos);
+ void seekToTime(uint32 time);
+ void stop();
+
+ void playSound(uint16 resourceId);
+ bool soundPlaying(uint16 resourceId, const Common::String &cue);
+
+ bool transparentAt(int x, int y);
+
+ void setTempo(uint16 tempo);
+
+ uint getNumResources() { return _shapeResources.size(); }
+ uint16 getResource(uint num) { return _shapeResources[num]; }
+ Common::Point getOffset(uint num) { return _shapeOffsets[num]; }
+
+ uint32 getCurrentFrame() { return _currentFrame; }
+
+ uint16 getParentId();
+
+protected:
+ MohawkEngine_LivingBooks *_vm;
+ LBAnimationItem *_parent;
+
+ Common::Rect _bounds, _clip;
+ Common::Array<LBAnimationNode *> _nodes;
+
+ uint16 _tempo;
+
+ uint16 _currentSound;
+ CueList _cueList;
+
+ uint32 _lastTime, _currentFrame;
+ bool _running;
+
+ void loadShape(uint16 resourceId);
+ Common::Array<uint16> _shapeResources;
+ Common::Array<Common::Point> _shapeOffsets;
+};
+
+class LBAnimationItem : public LBItem {
+public:
+ LBAnimationItem(MohawkEngine_LivingBooks *_vm, LBPage *page, Common::Rect rect);
+ ~LBAnimationItem() override;
+
+ void setEnabled(bool enabled) override;
+ bool contains(Common::Point point) override;
+ void update() override;
+ void draw() override;
+ bool togglePlaying(bool playing, bool restart) override;
+ void done(bool onlyNotify) override;
+ void init() override;
+ void seek(uint16 pos) override;
+ void seekToTime(uint32 time) override;
+ void startPhase(uint phase) override;
+ void stop() override;
+
+protected:
+ LBItem *createClone() override;
+
+ LBAnimation *_anim;
+ bool _running;
+};
+
+} // End of namespace Mohawk
+
+#endif
diff --git a/engines/mohawk/livingbooks_constants.h b/engines/mohawk/livingbooks_constants.h
new file mode 100644
index 00000000000..82a6e25f331
--- /dev/null
+++ b/engines/mohawk/livingbooks_constants.h
@@ -0,0 +1,242 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef MOHAWK_LIVINGBOOKS_CONSTANTS_H
+#define MOHAWK_LIVINGBOOKS_CONSTANTS_H
+
+namespace Mohawk {
+
+/* Some disambiguations:
+ * _soundMode is a priority (see LBItem::getSoundPriority), not a kLBControlMode.
+ * _controlMode (item flags) is unrelated to kLBControlMode (the menu screens).
+ * LBProxyItem::_page shadows LBItem::_page (preexisting, intentional-looking, untouched).
+ */
+
+// Engine Debug Flags
+enum {
+ kDebugCode = 1,
+};
+
+#define LBKEY_MOD_CTRL 1
+#define LBKEY_MOD_ALT 2
+#define LBKEY_MOD_SHIFT 4
+enum NodeState {
+ kLBNodeDone = 0,
+ kLBNodeRunning = 1,
+ kLBNodeWaiting = 2
+};
+
+enum LBMode {
+ kLBIntroMode = 1,
+ kLBControlMode = 2,
+ kLBCreditsMode = 3,
+ kLBPreviewMode = 4,
+ kLBReadMode = 5,
+ kLBPlayMode = 6
+};
+
+enum {
+ kLBPhaseInit = 0x0,
+ kLBPhaseIntro = 0x1,
+ kLBPhaseMain = 0x2,
+ kLBPhaseNone = 0x7fff,
+ kLBPhaseLoad = 0xfffe,
+ kLBPhaseCreate = 0xffff
+};
+
+// automatic modes used in _timingMode
+enum {
+ kLBAutoNone = 0,
+ kLBAutoIntro = 1,
+ kLBAutoUserIdle = 2,
+ kLBAutoMain = 3,
+ kLBAutoSync = 4,
+ kLBAutoInit = 5,
+ kLBAutoCreate = 6,
+ kLBAutoLoad = 7
+};
+
+// control modes used in _controlMode
+enum {
+ kLBControlNone = 0,
+ kLBControlHideMouse = 1,
+ kLBControlPauseItems = 2
+};
+
+enum {
+ kLBStaticTextItem = 0x1,
+ kLBPictureItem = 0x2,
+ kLBEditTextItem = 0x14,
+ kLBLiveTextItem = 0x15,
+ kLBAnimationItem = 0x40,
+ kLBSoundItem = 0x41,
+ kLBGroupItem = 0x42,
+ kLBMovieItem = 0x43,
+ kLBPaletteAItem = 0x44, // unused?
+ kLBPaletteItem = 0x45,
+ kLBProxyItem = 0x46,
+ kLBMiniGameItem = 666, // EVIL!!!!
+ kLBXDataFileItem = 0x3e9,
+ kLBDiscDectectorItem = 0xfa1
+};
+
+enum {
+ // no 0x1?
+ kLBAnimOpNotify = 0x2,
+ kLBAnimOpSetTempo = 0x3,
+ // no 0x4?
+ kLBAnimOpMoveTo = 0x5,
+ kLBAnimOpWait = 0x6,
+ kLBAnimOpSetCel = 0x7,
+ kLBAnimOpSleepUntil = 0x8,
+ kLBAnimOpDrawMode = 0x9,
+ kLBAnimOpPlaySound = 0xa,
+ kLBAnimOpWaitForSound = 0xb,
+ kLBAnimOpReleaseSound = 0xc,
+ kLBAnimOpResetSound = 0xd,
+ kLBAnimOpSetTempoDiv = 0xe,
+ kLBAnimOpDelay = 0xf
+};
+
+enum {
+ kLBEventPhaseInit = 0,
+ kLBEventPhaseIntro = 1,
+ kLBEventMouseDown = 2,
+ kLBEventStarted = 3,
+ kLBEventDone = 4,
+ kLBEventMouseUp = 5,
+ kLBEventPhaseMain = 6,
+ kLBEventNotified = 7,
+ kLBEventDragStart = 8,
+ kLBEventDragMove = 9,
+ kLBEventDragEnd = 0xa,
+ kLBEventRolloverBegin = 0xb,
+ kLBEventRolloverMove = 0xc,
+ kLBEventRolloverEnd = 0xd,
+ kLBEventMouseUpIn = 0xe,
+ kLBEventMouseUpOut = 0xf,
+ kLBEventMouseTrackIn = 0x10,
+ kLBEventMouseTrackMove = 0x11,
+ kLBEventMouseTrackMoveIn = 0x12,
+ kLBEventMouseTrackMoveOut = 0x13,
+ kLBEventMouseTrackOut = 0x14,
+ kLBEventFocusBegin = 0x15,
+ kLBEventFocusEnd = 0x16,
+ kLBEventInit = 0x17,
+ kLBEventLoad = 0x1a,
+ kLBEventListLoad = 0x1b,
+ kLBEventPhaseCreate = 0xff
+};
+
+enum {
+ kLBGroupData = 0x64,
+ kLBLiveTextData = 0x65,
+ kLBMsgListScript = 0x66,
+ kLBNotifyScript = 0x67,
+ kLBSetPlayInfo = 0x68,
+ kLBSetRandomLoc = 0x69, // unused?
+ kLBSetDrag = 0x6a, // unused?
+ kLBSetDrawMode = 0x6b,
+ kLBSetFont = 0x6c, // unused?
+ kLBSetOneShot = 0x6d, // unused?
+ kLBSetPlayPhase = 0x6e,
+ // from here, 2.x+
+ kLBSetKeyNotify = 0x6f,
+ kLBCommand = 0x70,
+ kLBPaletteAData = 0x71, // unused?
+ kLBPaletteXData = 0x72,
+ kLBDisable = 0x73, // unused?
+ kLBEnable = 0x74, // unused?
+ kLBSetNotVisible = 0x75,
+ kLBSetVisible = 0x76, // unused?
+ kLBGlobalDisable = 0x77,
+ kLBGlobalEnable = 0x78, // unused?
+ kLBGlobalSetNotVisible = 0x79,
+ kLBGlobalSetVisible = 0x7a, // unused?
+ kLBSetAmbient = 0x7b,
+ kLBSetDragParams = 0x7c,
+ kLBSetKeyEvent = 0x7d,
+ kLBSetRolloverData = 0x7e,
+ kLBSetParent = 0x7f,
+ kLBSetHitTest = 0x80,
+ // from here, rugrats
+ kLBUnknown194 = 0x194
+};
+
+enum {
+ kLBOpNone = 0x0,
+ kLBOpXShow = 0x1,
+ kLBOpTogglePlay = 0x2,
+ kLBOpSetNotVisible = 0x3,
+ kLBOpSetVisible = 0x4,
+ kLBOpDestroy = 0x5,
+ kLBOpRewind = 0x6,
+ kLBOpStop = 0x7,
+ kLBOpDisable = 0x8,
+ kLBOpEnable = 0x9,
+ // (no 0xa)
+ kLBOpGlobalSetNotVisible = 0xb,
+ kLBOpGlobalSetVisible = 0xc,
+ kLBOpGlobalDisable = 0xd,
+ kLBOpGlobalEnable = 0xe,
+ kLBOpSeekToEnd = 0xf,
+ // (no 0x10)
+ kLBOpMute = 0x11,
+ kLBOpUnmute = 0x12,
+ kLBOpLoad = 0x13,
+ kLBOpPreload = 0x14,
+ kLBOpUnload = 0x15,
+ kLBOpSeekToNext = 0x16,
+ kLBOpSeekToPrev = 0x17,
+ kLBOpDragBegin = 0x18,
+ kLBOpDragEnd = 0x19,
+ kLBOpScriptDisable = 0x1a,
+ kLBOpScriptEnable = 0x1b,
+ kLBOpUnknown1C = 0x1c,
+ kLBOpSendExpression = 0x1d,
+ kLBOpJumpUnlessExpression = 0xfffb,
+ kLBOpBreakExpression = 0xfffc,
+ kLBOpJumpToExpression = 0xfffd,
+ kLBOpRunSubentries = 0xfffe,
+ kLBOpRunData = 0xffff
+};
+
+enum {
+ kLBNotifyGUIAction = 1,
+ kLBNotifyGoToControls = 2,
+ kLBNotifyChangePage = 3,
+ kLBNotifyGotoQuit = 4,
+ kLBNotifyIntroDone = 5,
+ kLBNotifyChangeMode = 6,
+ kLBNotifyCursorChange = 7,
+ kLBNotifyPrintPage = 0xc,
+ kLBNotifyQuit = 0xd
+};
+
+enum {
+ kTargetTypeExpression = 0x3f3f,
+ kTargetTypeCode = 0xfffe,
+ kTargetTypeName = 0xffff
+};
+
+} // End of namespace Mohawk
+
+#endif
diff --git a/engines/mohawk/livingbooks_item.cpp b/engines/mohawk/livingbooks_item.cpp
new file mode 100644
index 00000000000..e6c776d980a
--- /dev/null
+++ b/engines/mohawk/livingbooks_item.cpp
@@ -0,0 +1,343 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "mohawk/livingbooks.h"
+#include "mohawk/cursors.h"
+
+#include "common/system.h"
+#include "common/textconsole.h"
+
+namespace Mohawk {
+
+LBItem::LBItem(MohawkEngine_LivingBooks *vm, LBPage *page, Common::Rect rect) : _vm(vm), _page(page), _rect(rect) {
+ if (_vm->getGameType() == GType_LIVINGBOOKSV1 || _vm->getGameType() == GType_LIVINGBOOKSV2)
+ _phase = kLBPhaseInit;
+ else
+ _phase = kLBPhaseLoad;
+
+ _loopMode = 0;
+ _delayMin = 0;
+ _delayMax = 0;
+ _timingMode = kLBAutoNone;
+ _periodMin = 0;
+ _periodMax = 0;
+ _controlMode = kLBControlNone;
+ _soundMode = 0;
+
+ _loaded = false;
+ _enabled = false;
+ _visible = true;
+ _playing = false;
+ _globalEnabled = true;
+ _globalVisible = true;
+ _nextTime = 0;
+ _startTime = 0;
+ _loops = 0;
+
+ _isAmbient = false;
+ _doHitTest = true;
+}
+
+LBItem::~LBItem() {
+ for (uint i = 0; i < _scriptEntries.size(); i++)
+ delete _scriptEntries[i];
+}
+
+void LBItem::destroySelf() {
+ if (!this->_itemId)
+ error("destroySelf() on an item which was already dead");
+
+ _vm->queueDelayedEvent(DelayedEvent(this, kLBDelayedEventDestroy));
+
+ _itemId = 0;
+}
+
+void LBItem::setEnabled(bool enabled) {
+ if (enabled && !_loaded && !_playing) {
+ if (_timingMode == kLBAutoUserIdle) {
+ setNextTime(_periodMin, _periodMax);
+ debug(2, "Enable time startup");
+ }
+ }
+
+ _enabled = enabled;
+}
+
+void LBItem::setGlobalEnabled(bool enabled) {
+ bool wasEnabled = _loaded && _enabled && _globalEnabled;
+ _globalEnabled = enabled;
+ if (wasEnabled != (_loaded && _enabled && _globalEnabled))
+ setEnabled(enabled);
+}
+
+bool LBItem::contains(Common::Point point) {
+ if (!_loaded)
+ return false;
+
+ if (_playing && _loopMode == 0xFFFF)
+ stop();
+
+ if (!_playing && _timingMode == kLBAutoUserIdle)
+ setNextTime(_periodMin, _periodMax);
+
+ return _visible && _globalVisible && _rect.contains(point);
+}
+
+void LBItem::update() {
+ if (_phase != kLBPhaseNone && (!_loaded || !_enabled || !_globalEnabled))
+ return;
+
+ if (_nextTime == 0 || _nextTime > (uint32)(_vm->_system->getMillis() / 16))
+ return;
+
+ if (togglePlaying(_playing, true)) {
+ _nextTime = 0;
+ } else if (_loops == 0 && _timingMode == kLBAutoUserIdle) {
+ debug(9, "Looping in update()");
+ setNextTime(_periodMin, _periodMax);
+ }
+}
+
+void LBItem::handleMouseDown(Common::Point pos) {
+ if (!_loaded || !_enabled || !_globalEnabled)
+ return;
+
+ _vm->setFocus(this);
+ runScript(kLBEventMouseDown);
+ runScript(kLBEventMouseTrackIn);
+}
+
+void LBItem::handleMouseMove(Common::Point pos) {
+ // TODO: handle drag
+}
+
+void LBItem::handleMouseUp(Common::Point pos) {
+ _vm->setFocus(nullptr);
+ runScript(kLBEventMouseUp);
+ runScript(kLBEventMouseUpIn);
+}
+
+bool LBItem::togglePlaying(bool playing, bool restart) {
+ if (playing) {
+ _vm->queueDelayedEvent(DelayedEvent(this, kLBDelayedEventDone));
+ return true;
+ }
+ if (((_loaded && _enabled && _globalEnabled) || _phase == kLBPhaseNone) && !_playing) {
+ _playing = togglePlaying(true, restart);
+ if (_playing) {
+ _nextTime = 0;
+ _startTime = _vm->_system->getMillis() / 16;
+
+ if (_loopMode == 0xFFFF || _loopMode == 0xFFFE)
+ _loops = 0xFFFF;
+ else
+ _loops = _loopMode;
+
+ if (_controlMode >= kLBControlHideMouse) {
+ debug(2, "Hiding cursor");
+ _vm->_cursor->hideCursor();
+ _vm->lockSound(this, true);
+
+ if (_controlMode >= kLBControlPauseItems) {
+ debug(2, "Disabling all");
+ _vm->setEnableForAll(false, this);
+ }
+ }
+
+ runScript(kLBEventStarted);
+ notify(0, _itemId);
+ }
+ }
+ return _playing;
+}
+
+void LBItem::done(bool onlyNotify) {
+ if (onlyNotify) {
+ if (_relocPoint.x || _relocPoint.y) {
+ _rect.translate(_relocPoint.x, _relocPoint.y);
+ // TODO: does drag box need adjusting?
+ }
+
+ if (_loops && --_loops) {
+ debug(9, "Real looping (now 0x%04x left)", _loops);
+ setNextTime(_delayMin, _delayMax, _startTime);
+ } else
+ done(false);
+
+ return;
+ }
+
+ _playing = false;
+ _loops = 0;
+ _startTime = 0;
+
+ if (_controlMode >= kLBControlHideMouse) {
+ debug(2, "Showing cursor");
+ _vm->_cursor->showCursor();
+ _vm->lockSound(this, false);
+
+ if (_controlMode >= kLBControlPauseItems) {
+ debug(2, "Enabling all");
+ _vm->setEnableForAll(true, this);
+ }
+ }
+
+ if (_timingMode == kLBAutoUserIdle) {
+ debug(9, "Looping in done() - %d to %d", _periodMin, _periodMax);
+ setNextTime(_periodMin, _periodMax);
+ }
+
+ runScript(kLBEventDone);
+ notify(0xFFFF, _itemId);
+}
+
+void LBItem::init() {
+ runScript(kLBEventInit);
+}
+
+void LBItem::setVisible(bool visible) {
+ if (visible == _visible)
+ return;
+
+ _visible = visible;
+ _vm->_needsRedraw = true;
+}
+
+void LBItem::setGlobalVisible(bool visible) {
+ bool wasEnabled = _visible && _globalVisible;
+ _globalVisible = visible;
+ if (wasEnabled != (_visible && _globalVisible))
+ _vm->_needsRedraw = true;
+}
+
+void LBItem::startPhase(uint phase) {
+ if (_phase == phase) {
+ if (_phase != kLBPhaseNone) {
+ setEnabled(true);
+ }
+
+ load();
+ }
+
+ switch (phase) {
+ case kLBPhaseLoad:
+ runScript(kLBEventListLoad);
+ break;
+ case kLBPhaseCreate:
+ runScript(kLBEventPhaseCreate);
+ if (_timingMode == kLBAutoCreate) {
+ debug(2, "Phase create: time startup");
+ setNextTime(_periodMin, _periodMax);
+ }
+ break;
+ case kLBPhaseInit:
+ runScript(kLBEventPhaseInit);
+ if (_timingMode == kLBAutoInit) {
+ debug(2, "Phase init: time startup");
+ setNextTime(_periodMin, _periodMax);
+ }
+ break;
+ case kLBPhaseIntro:
+ runScript(kLBEventPhaseIntro);
+ if (_timingMode == kLBAutoIntro || _timingMode == kLBAutoUserIdle) {
+ debug(2, "Phase intro: time startup");
+ setNextTime(_periodMin, _periodMax);
+ }
+ break;
+ case kLBPhaseMain:
+ runScript(kLBEventPhaseMain);
+ if (_timingMode == kLBAutoUserIdle || _timingMode == kLBAutoMain) {
+ debug(2, "Phase main: time startup");
+ setNextTime(_periodMin, _periodMax);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+void LBItem::stop() {
+ if (!_playing)
+ return;
+
+ _loops = 0;
+ seek(0xFFFF);
+ done(true);
+}
+
+void LBItem::load() {
+ if (_loaded)
+ return;
+
+ _loaded = true;
+
+ // FIXME: events etc
+ if (_timingMode == kLBAutoLoad) {
+ debug(2, "Load: time startup");
+ setNextTime(_periodMin, _periodMax);
+ }
+}
+
+void LBItem::unload() {
+ if (!_loaded)
+ return;
+
+ _loaded = false;
+
+ // FIXME: stuff
+}
+
+void LBItem::moveBy(const Common::Point &pos) {
+ _rect.translate(pos.x, pos.y);
+}
+
+void LBItem::moveTo(const Common::Point &pos) {
+ _rect.moveTo(pos);
+}
+
+LBItem *LBItem::clone(uint16 newId, const Common::String &newName) {
+ LBItem *item = createClone();
+
+ item->_itemId = newId;
+ item->_desc = newName;
+
+ item->_resourceId = _resourceId;
+ // FIXME: the rest
+
+ _page->addClonedItem(item);
+ // FIXME: zorder?
+ return item;
+}
+
+LBItem *LBItem::createClone() {
+ return new LBItem(_vm, _page, _rect);
+}
+
+void LBItem::setNextTime(uint16 min, uint16 max) {
+ setNextTime(min, max, _vm->_system->getMillis() / 16);
+}
+
+void LBItem::setNextTime(uint16 min, uint16 max, uint32 start) {
+ _nextTime = start + _vm->_rnd->getRandomNumberRng((uint)min, (uint)max);
+ debug(9, "nextTime is now %d frames away", _nextTime - (uint)(_vm->_system->getMillis() / 16));
+}
+
+} // End of namespace Mohawk
diff --git a/engines/mohawk/livingbooks_item.h b/engines/mohawk/livingbooks_item.h
new file mode 100644
index 00000000000..448bdf5a942
--- /dev/null
+++ b/engines/mohawk/livingbooks_item.h
@@ -0,0 +1,135 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef MOHAWK_LIVINGBOOKS_ITEM_H
+#define MOHAWK_LIVINGBOOKS_ITEM_H
+
+#include "livingbooks_code.h"
+
+#include "common/array.h"
+#include "common/hash-str.h"
+#include "common/hashmap.h"
+#include "common/list.h"
+#include "common/rect.h"
+#include "common/str.h"
+
+namespace Common {
+ class SeekableReadStreamEndian;
+ class MemoryReadStreamEndian;
+}
+
+namespace Mohawk {
+
+class MohawkEngine_LivingBooks;
+class LBPage;
+struct LBScriptEntry;
+
+class LBItem {
+ friend class LBCode;
+
+public:
+ LBItem(MohawkEngine_LivingBooks *vm, LBPage *page, Common::Rect rect);
+ virtual ~LBItem();
+
+ void readFrom(Common::SeekableReadStreamEndian *stream);
+ void readData(uint16 type, uint16 size, byte *data);
+ virtual void readData(uint16 type, uint16 size, Common::MemoryReadStreamEndian *stream);
+
+ virtual void destroySelf(); // 0x2
+ virtual void setEnabled(bool enabled); // 0x3
+ virtual void setGlobalEnabled(bool enabled);
+ virtual bool contains(Common::Point point); // 0x7
+ virtual void update(); // 0x8
+ virtual void draw() { } // 0x9
+ virtual void handleKeyChar(Common::Point pos) { } // 0xA
+ virtual void handleMouseDown(Common::Point pos); // 0xB
+ virtual void handleMouseMove(Common::Point pos); // 0xC
+ virtual void handleMouseUp(Common::Point pos); // 0xD
+ virtual bool togglePlaying(bool playing, bool restart = false); // 0xF
+ virtual void done(bool onlyNotify); // 0x10
+ virtual void init(); // 0x11
+ virtual void seek(uint16 pos) { } // 0x13
+ virtual void seekToTime(uint32 time) { }
+ virtual void setFocused(bool focused) { } // 0x14
+ virtual void setVisible(bool visible); // 0x17
+ virtual void setGlobalVisible(bool enabled);
+ virtual void startPhase(uint phase); // 0x18
+ virtual void stop(); // 0x19
+ virtual void notify(uint16 data, uint16 from); // 0x1A
+ virtual void load();
+ virtual void unload();
+ virtual void moveBy(const Common::Point &pos);
+ virtual void moveTo(const Common::Point &pos);
+
+ LBItem *clone(uint16 newId, const Common::String &newName);
+
+ uint16 getId() { return _itemId; }
+ const Common::String &getName() { return _desc; }
+ const Common::Rect &getRect() { return _rect; }
+ uint16 getSoundPriority() { return _soundMode; }
+ bool isLoaded() { return _loaded; }
+ bool isAmbient() { return _isAmbient; }
+
+ Common::List<LBItem *>::iterator _iterator;
+
+ // TODO: make private
+ Common::HashMap<Common::String, LBValue, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _variables;
+
+protected:
+ MohawkEngine_LivingBooks *_vm;
+ LBPage *_page;
+
+ void setNextTime(uint16 min, uint16 max);
+ void setNextTime(uint16 min, uint16 max, uint32 start);
+
+ Common::Rect _rect;
+ Common::String _desc;
+ uint16 _resourceId;
+ uint16 _itemId;
+
+ bool _loaded, _visible, _globalVisible, _playing, _enabled, _globalEnabled;
+
+ uint32 _nextTime, _startTime;
+ uint16 _loops;
+
+ uint16 _phase, _timingMode, _delayMin, _delayMax;
+ uint16 _loopMode, _periodMin, _periodMax;
+ uint16 _controlMode, _soundMode;
+ Common::Point _relocPoint;
+
+ bool _isAmbient;
+ bool _doHitTest;
+
+ virtual LBItem *createClone();
+
+ Common::Array<LBScriptEntry *> _scriptEntries;
+ void runScript(uint event, uint16 data = 0, uint16 from = 0);
+ int runScriptEntry(LBScriptEntry *entry);
+
+ void runCommand(const Common::String &command);
+ bool checkCondition(const Common::String &condition);
+
+ LBScriptEntry *parseScriptEntry(uint16 type, uint16 &size, Common::MemoryReadStreamEndian *stream, bool isSubentry = false);
+};
+
+} // End of namespace Mohawk
+
+#endif
diff --git a/engines/mohawk/livingbooks_itemscript.cpp b/engines/mohawk/livingbooks_itemscript.cpp
new file mode 100644
index 00000000000..73f83f50d5e
--- /dev/null
+++ b/engines/mohawk/livingbooks_itemscript.cpp
@@ -0,0 +1,834 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "mohawk/livingbooks.h"
+
+#include "common/memstream.h"
+#include "common/textconsole.h"
+
+namespace Mohawk {
+
+// Not to be confused with SCRP as "scripts". SCRP is for LBAnimation
+
+LBScriptEntry::LBScriptEntry() {
+ state = 0;
+ data = nullptr;
+ argvParam = nullptr;
+ argvTarget = nullptr;
+}
+
+LBScriptEntry::~LBScriptEntry() {
+ delete[] argvParam;
+ delete[] argvTarget;
+ delete[] data;
+
+ for (uint i = 0; i < subentries.size(); i++)
+ delete subentries[i];
+}
+
+void LBItem::readFrom(Common::SeekableReadStreamEndian *stream) {
+ _resourceId = stream->readUint16();
+ _itemId = stream->readUint16();
+ uint16 size = stream->readUint16();
+ _desc = _vm->readString(stream);
+
+ debug(2, "Item: size %d, resource %d, id %d", size, _resourceId, _itemId);
+ debug(2, "Coords: %d, %d, %d, %d", _rect.left, _rect.top, _rect.right, _rect.bottom);
+ debug(2, "String: '%s'", _desc.c_str());
+
+ if (!_itemId)
+ error("Item had invalid item id");
+
+ int endPos = stream->pos() + size;
+ if (endPos > stream->size())
+ error("Item is larger (should end at %d) than stream (size %d)", endPos, (int)stream->size());
+
+ while (true) {
+ if (stream->pos() == endPos)
+ break;
+
+ uint oldPos = stream->pos();
+
+ uint16 dataType = stream->readUint16();
+ uint16 dataSize = stream->readUint16();
+
+ debug(4, "Data type %04x, size %d", dataType, dataSize);
+ byte *buf = new byte[dataSize];
+ stream->read(buf, dataSize);
+ readData(dataType, dataSize, buf);
+ delete[] buf;
+
+ if ((uint)stream->pos() != oldPos + 4 + (uint)dataSize)
+ error("Failed to read correct number of bytes (off by %d) for data type %04x (size %d)",
+ (int)stream->pos() - (int)(oldPos + 4 + (uint)dataSize), dataType, dataSize);
+
+ if (stream->pos() > endPos)
+ error("Read off the end (at %d) of data (ends at %d)", (int)stream->pos(), endPos);
+
+ assert(!stream->eos());
+ }
+}
+
+LBScriptEntry *LBItem::parseScriptEntry(uint16 type, uint16 &size, Common::MemoryReadStreamEndian *stream, bool isSubentry) {
+ if (size < 6)
+ error("Script entry of type 0x%04x was too small (%d)", type, size);
+
+ uint16 expectedEndSize = 0;
+
+ LBScriptEntry *entry = new LBScriptEntry;
+ entry->type = type;
+ if (isSubentry) {
+ expectedEndSize = size - (stream->readUint16() + 2);
+ entry->event = 0xffff;
+ } else
+ entry->event = stream->readUint16();
+ entry->opcode = stream->readUint16();
+ entry->param = stream->readUint16();
+ debug(4, "Script entry: type 0x%04x, event 0x%04x, opcode 0x%04x, param 0x%04x",
+ entry->type, entry->event, entry->opcode, entry->param);
+ size -= 6;
+
+ // TODO: read as bytes, if this is correct (but beware endianism)
+ byte conditionTag = (entry->event & 0xff00) >> 8;
+ entry->event = entry->event & 0xff;
+
+ if (type == kLBMsgListScript && entry->opcode == kLBOpRunSubentries) {
+ debug(4, "%d script subentries:", entry->param);
+ entry->argc = 0;
+ for (uint i = 0; i < entry->param; i++) {
+ LBScriptEntry *subentry = parseScriptEntry(type, size, stream, true);
+ entry->subentries.push_back(subentry);
+
+ // subentries are aligned
+ if (i + 1 < entry->param && size % 2 == 1) {
+ stream->skip(1);
+ size--;
+ }
+ }
+ } else if (type == kLBMsgListScript) {
+ if (size < 2)
+ error("Script entry of type 0x%04x was too small (%d)", type, size);
+
+ entry->argc = stream->readUint16();
+ size -= 2;
+
+ entry->targetingType = 0;
+
+ uint16 targetingType = entry->argc;
+ if (targetingType == kTargetTypeExpression || targetingType == kTargetTypeCode
+ || targetingType == kTargetTypeName) {
+ entry->targetingType = targetingType;
+
+ // FIXME
+ if (targetingType == kTargetTypeCode)
+ error("encountered kTargetTypeCode");
+
+ if (size < 2)
+ error("not enough bytes (%d) reading special targeting", size);
+ uint16 count = stream->readUint16();
+ size -= 2;
+
+ debug(4, "%d targets with targeting type %04x", count, targetingType);
+
+ uint oldAlign = size % 2;
+ for (uint i = 0; i < count; i++) {
+ Common::String target = _vm->readString(stream);
+ debug(4, "target '%s'", target.c_str());
+ entry->targets.push_back(target);
+ if (target.size() + 1 > size)
+ error("failed to read target (ran out of stream)");
+ size -= target.size() + 1;
+ }
+ entry->argc = entry->targets.size();
+
+ if ((uint)(size % 2) != oldAlign) {
+ stream->skip(1);
+ size--;
+ }
+ } else if (entry->argc) {
+ entry->argvParam = new uint16[entry->argc];
+ entry->argvTarget = new uint16[entry->argc];
+ debug(4, "With %d targets:", entry->argc);
+
+ if (size < (entry->argc * 4))
+ error("Script entry of type 0x%04x was too small (%d)", type, size);
+
+ for (uint i = 0; i < entry->argc; i++) {
+ entry->argvParam[i] = stream->readUint16();
+ entry->argvTarget[i] = stream->readUint16();
+ debug(4, "Target %d, param 0x%04x", entry->argvTarget[i], entry->argvParam[i]);
+ }
+
+ size -= (entry->argc * 4);
+ }
+ }
+
+ if (type == kLBMsgListScript && entry->opcode == kLBOpJumpUnlessExpression) {
+ if (size < 6)
+ error("not enough bytes (%d) in kLBOpJumpUnlessExpression, event 0x%04x", size, entry->event);
+ entry->offset = stream->readUint32();
+ entry->target = stream->readUint16();
+ debug(4, "kLBOpJumpUnlessExpression: offset %08x, target %d", entry->offset, entry->target);
+ size -= 6;
+ }
+ if (type == kLBMsgListScript && entry->opcode == kLBOpJumpToExpression) {
+ if (size < 4)
+ error("not enough bytes (%d) in kLBOpJumpToExpression, event 0x%04x", size, entry->event);
+ entry->offset = stream->readUint32();
+ debug(4, "kLBOpJumpToExpression: offset %08x", entry->offset);
+ size -= 4;
+ }
+
+ if (type == kLBNotifyScript && entry->opcode == kLBNotifyChangeMode && _vm->getGameType() != GType_LIVINGBOOKSV1) {
+ switch (entry->param) {
+ case 1:
+ if (size < 8)
+ error("%d unknown bytes in notify entry kLBNotifyChangeMode", size);
+ entry->newUnknown = stream->readUint16();
+ entry->newMode = stream->readUint16();
+ entry->newPage = stream->readUint16();
+ entry->newSubpage = stream->readUint16();
+ debug(4, "kLBNotifyChangeMode: unknown %04x, mode %d, page %d.%d",
+ entry->newUnknown, entry->newMode, entry->newPage, entry->newSubpage);
+ size -= 8;
+ break;
+ case 3:
+ {
+ Common::String newCursor = _vm->readString(stream);
+ entry->newCursor = newCursor;
+ if (size < newCursor.size() + 1)
+ error("failed to read newCursor in notify entry");
+ size -= newCursor.size() + 1;
+ debug(4, "kLBNotifyChangeMode: new cursor '%s'", newCursor.c_str());
+ }
+ break;
+ default:
+ // the original engine also does something when param==2 (but not a notify)
+ error("unknown v2 kLBNotifyChangeMode type %d", entry->param);
+ }
+ }
+ if (entry->opcode == kLBOpSendExpression) {
+ if (size < 4)
+ error("not enough bytes (%d) in kLBOpSendExpression, event 0x%04x", size, entry->event);
+ entry->offset = stream->readUint32();
+ debug(4, "kLBOpSendExpression: offset %08x", entry->offset);
+ size -= 4;
+ }
+ if (entry->opcode == kLBOpRunData) {
+ if (size < 4)
+ error("didn't get enough bytes (%d) to read data header in script entry", size);
+ entry->dataType = stream->readUint16();
+ entry->dataLen = stream->readUint16();
+ size -= 4;
+
+ if (size < entry->dataLen)
+ error("didn't get enough bytes (%d) to read data in script entry", size);
+
+ if (entry->dataType == kLBCommand) {
+ Common::String command = _vm->readString(stream);
+ uint commandSize = command.size() + 1;
+ if (commandSize > entry->dataLen)
+ error("failed to read command in script entry: dataLen %d, command '%s' (%d chars)",
+ entry->dataLen, command.c_str(), commandSize);
+ entry->dataLen = commandSize;
+ entry->data = new byte[commandSize];
+ memcpy(entry->data, command.c_str(), commandSize);
+ size -= commandSize;
+ } else {
+ if (conditionTag)
+ error("kLBOpRunData had unexpected conditionTag");
+ entry->data = new byte[entry->dataLen];
+ stream->read(entry->data, entry->dataLen);
+ size -= entry->dataLen;
+ }
+ }
+ if (entry->event == kLBEventNotified) {
+ if (size < 4)
+ error("not enough bytes (%d) in kLBEventNotified, opcode 0x%04x", size, entry->opcode);
+ entry->matchFrom = stream->readUint16();
+ entry->matchNotify = stream->readUint16();
+ debug(4, "kLBEventNotified: matches %04x (from %04x)",
+ entry->matchNotify, entry->matchFrom);
+ size -= 4;
+ }
+
+ if (isSubentry) {
+ // TODO: subentries may be aligned, so this check is a bit too relaxed
+ if (size != expectedEndSize && size != expectedEndSize + 1)
+ error("expected %d bytes left at end of subentry, but had %d",
+ expectedEndSize, size);
+ return entry;
+ }
+
+ if (conditionTag == 1) {
+ if (!size)
+ error("failed to read condition (empty stream)");
+ Common::String condition = _vm->readString(stream);
+ if (condition.size() == 0) {
+ size--;
+ if (!size)
+ error("failed to read condition (null byte, then ran out of stream)");
+ condition = _vm->readString(stream);
+ }
+ if (condition.size() + 1 > size)
+ error("failed to read condition (ran out of stream)");
+ size -= (condition.size() + 1);
+
+ entry->conditions.push_back(condition);
+ debug(4, "script entry condition '%s'", condition.c_str());
+ } else if (conditionTag == 2) {
+ if (size < 4)
+ error("expected more than %d bytes for conditionTag 2", size);
+ // FIXME
+ stream->skip(4);
+ size -= 4;
+ }
+
+ if (size == 1) {
+ // FIXME: this is alignment, but why?
+ stream->skip(1);
+ size--;
+ } else if (size)
+ error("failed to read script entry correctly (%d bytes left): type 0x%04x, event 0x%04x, opcode 0x%04x, param 0x%04x",
+ size, entry->type, entry->event, entry->opcode, entry->param);
+
+ return entry;
+}
+
+void LBItem::readData(uint16 type, uint16 size, byte *data) {
+ Common::MemoryReadStreamEndian stream(data, size, _vm->isBigEndian());
+ readData(type, size, &stream);
+}
+
+void LBItem::readData(uint16 type, uint16 size, Common::MemoryReadStreamEndian *stream) {
+ switch (type) {
+ case kLBMsgListScript:
+ case kLBNotifyScript:
+ _scriptEntries.push_back(parseScriptEntry(type, size, stream));
+ break;
+
+ case kLBSetPlayInfo:
+ {
+ if (size != 20)
+ error("kLBSetPlayInfo had wrong size (%d)", size);
+
+ _loopMode = stream->readUint16();
+ _delayMin = stream->readUint16();
+ _delayMax = stream->readUint16();
+ _timingMode = stream->readUint16();
+ if (_timingMode > 7)
+ error("encountered timing mode %04x", _timingMode);
+ _periodMin = stream->readUint16();
+ _periodMax = stream->readUint16();
+ _relocPoint.x = stream->readSint16();
+ _relocPoint.y = stream->readSint16();
+ _controlMode = stream->readUint16();
+ _soundMode = stream->readUint16();
+
+ debug(2, "kLBSetPlayInfo: loop mode %d (%d to %d), timing mode %d (%d to %d), reloc (%d, %d), control mode %04x, sound mode %04x",
+ _loopMode, _delayMin, _delayMax,
+ _timingMode, _periodMin, _periodMax,
+ _relocPoint.x, _relocPoint.y,
+ _controlMode, _soundMode);
+ }
+ break;
+
+ case kLBSetPlayPhase:
+ if (size != 2)
+ error("SetPlayPhase had wrong size (%d)", size);
+ _phase = stream->readUint16();
+ debug(2, "kLBSetPlayPhase: %d", _phase);
+ break;
+
+ case kLBSetKeyNotify:
+ {
+ // FIXME: variable-size notifies, targets
+ if (size != 18)
+ error("0x6f had wrong size (%d)", size);
+ uint event = stream->readUint16();
+ LBKey key;
+ stream->read(&key, 4);
+ uint opcode = stream->readUint16();
+ uint param = stream->readUint16();
+ uint u6 = stream->readUint16();
+ uint u7 = stream->readUint16();
+ uint u8 = stream->readUint16();
+ uint u9 = stream->readUint16();
+ warning("ignoring kLBSetKeyNotify: item %s, key code %02x (modifier mask %d, char %d, repeat %d), event %04x, opcode %04x, param %04x, unknowns %04x, %04x, %04x, %04x",
+ _desc.c_str(), key.code, key.modifiers, key.char_, key.repeats, event, opcode, param, u6, u7, u8, u9);
+ }
+ break;
+
+ case kLBCommand:
+ {
+ Common::String command = _vm->readString(stream);
+ if (size != command.size() + 1)
+ error("failed to read command string");
+
+ runCommand(command);
+ }
+ break;
+
+ case kLBSetNotVisible:
+ assert(size == 0);
+ _visible = false;
+ break;
+
+ case kLBGlobalDisable:
+ assert(size == 0);
+ _globalEnabled = false;
+ break;
+
+ case kLBGlobalSetNotVisible:
+ assert(size == 0);
+ _globalVisible = false;
+ break;
+
+ case kLBSetAmbient:
+ assert(size == 0);
+ _isAmbient = true;
+ break;
+
+ case kLBSetKeyEvent:
+ {
+ // FIXME: targets
+ if (size != 10)
+ error("kLBSetKeyEvent had wrong size (%d)", size);
+ uint u3 = stream->readUint16();
+ LBKey key;
+ stream->read(&key, 4);
+ uint target = stream->readUint16();
+ uint16 event = stream->readUint16();
+ // FIXME: this is scripting stuff: what to run when key is pressed
+ warning("ignoring kLBSetKeyEvent: item %s, key code %02x (modifier mask %d, char %d, repeat %d) unknown %04x, target %d, event %04x",
+ _desc.c_str(), key.code, key.modifiers, key.char_, key.repeats, u3, target, event);
+ }
+ break;
+
+ case kLBSetHitTest:
+ {
+ assert(size == 2);
+ uint val = stream->readUint16();
+ _doHitTest = (bool)val;
+ debug(2, "kLBSetHitTest (on %s): value %04x", _desc.c_str(), val);
+ }
+ break;
+
+ case kLBSetRolloverData:
+ {
+ assert(size == 2);
+ uint16 flag = stream->readUint16();
+ warning("ignoring kLBSetRolloverData: item %s, flag %d", _desc.c_str(), flag);
+ }
+ break;
+
+ case kLBSetParent:
+ {
+ assert(size == 2);
+ uint16 parent = stream->readUint16();
+ warning("ignoring kLBSetParent: item %s, parent id %d", _desc.c_str(), parent);
+ }
+ break;
+
+ case kLBUnknown194:
+ {
+ assert(size == 4);
+ uint offset = stream->readUint32();
+ _page->_code->runCode(this, offset);
+ }
+ break;
+
+ default:
+ error("Unknown message %04x (size 0x%04x)", type, size);
+ //for (uint i = 0; i < size; i++)
+ // debugN("%02x ", stream->readByte());
+ //debugN("\n");
+ break;
+ }
+}
+
+void LBItem::notify(uint16 data, uint16 from) {
+ if (_timingMode == kLBAutoSync) {
+ // TODO: is this correct?
+ if (_periodMin == data && _periodMax == from) {
+ debug(2, "Handling notify 0x%04x (from %d)", data, from);
+ setNextTime(0, 0);
+ }
+ }
+
+ runScript(kLBEventNotified, data, from);
+}
+
+void LBItem::runScript(uint event, uint16 data, uint16 from) {
+ for (uint i = 0; i < _scriptEntries.size(); i++) {
+ LBScriptEntry *entry = _scriptEntries[i];
+
+ if (entry->event != event)
+ continue;
+
+ if (event == kLBEventNotified) {
+ if ((entry->matchFrom && entry->matchFrom != from) || entry->matchNotify != data)
+ continue;
+ }
+
+ bool conditionsMatch = true;
+ for (uint n = 0; n < entry->conditions.size(); n++) {
+ if (!checkCondition(entry->conditions[n])) {
+ conditionsMatch = false;
+ break;
+ }
+ }
+ if (!conditionsMatch)
+ continue;
+
+ if (entry->type == kLBNotifyScript) {
+ debug(2, "Notify: event 0x%04x, opcode 0x%04x, param 0x%04x",
+ entry->event, entry->opcode, entry->param);
+
+ if (entry->opcode == kLBNotifyGUIAction)
+ _vm->addNotifyEvent(NotifyEvent(entry->opcode, _itemId));
+ else if (entry->opcode == kLBNotifyChangeMode && _vm->getGameType() != GType_LIVINGBOOKSV1) {
+ NotifyEvent notifyEvent(entry->opcode, entry->param);
+ notifyEvent.newUnknown = entry->newUnknown;
+ notifyEvent.newMode = entry->newMode;
+ notifyEvent.newPage = entry->newPage;
+ notifyEvent.newSubpage = entry->newSubpage;
+ notifyEvent.newCursor = entry->newCursor;
+ _vm->addNotifyEvent(notifyEvent);
+ } else
+ _vm->addNotifyEvent(NotifyEvent(entry->opcode, entry->param));
+ } else
+ runScriptEntry(entry);
+ }
+}
+
+int LBItem::runScriptEntry(LBScriptEntry *entry) {
+ if (entry->state == 0xffff)
+ return 0;
+
+ uint start = 0;
+ uint count = entry->argc;
+ // zero targets = apply to self
+ if (!count)
+ count = 1;
+
+ if (entry->opcode != kLBOpRunSubentries) switch (entry->param) {
+ case 0xfffe:
+ // Run once (disable self after run).
+ entry->state = 0xffff;
+ break;
+ case 0xffff:
+ break;
+ case 0:
+ case 1:
+ case 2:
+ start = entry->state;
+ entry->state++;
+ if (entry->state >= count) {
+ switch (entry->param) {
+ case 0:
+ // Disable..
+ entry->state = 0xffff;
+ return 0;
+ case 1:
+ // Stay at the end.
+ entry->state = count - 1;
+ break;
+ case 2:
+ // Loop.
+ entry->state = 0;
+ break;
+ default:
+ break;
+ }
+ }
+ count = 1;
+ break;
+ case 3:
+ // Pick random target.
+ start = _vm->_rnd->getRandomNumberRng(0, count);
+ count = 1;
+ break;
+ default:
+ warning("Weird param for script entry (type 0x%04x, event 0x%04x, opcode 0x%04x, param 0x%04x)",
+ entry->type, entry->event, entry->opcode, entry->param);
+ }
+
+ for (uint n = start; n < count; n++) {
+ LBItem *target;
+
+ debug(2, "Script run: type 0x%04x, event 0x%04x, opcode 0x%04x, param 0x%04x",
+ entry->type, entry->event, entry->opcode, entry->param);
+
+ if (entry->argc) {
+ switch (entry->targetingType) {
+ case kTargetTypeExpression:
+ {
+ // FIXME: this should be EVALUATED
+ LBValue &tgt = _vm->_variables[entry->targets[n]];
+ switch (tgt.type) {
+ case kLBValueItemPtr:
+ target = tgt.item;
+ break;
+ case kLBValueString:
+ // FIXME: handle 'self', at least
+ // TODO: correct otherwise? or only self?
+ target = _vm->getItemByName(tgt.string);
+ break;
+ case kLBValueInteger:
+ target = _vm->getItemById(tgt.integer);
+ break;
+ default:
+ // FIXME: handle list
+ warning("Target '%s' (by expression) resulted in unknown type, skipping", entry->targets[n].c_str());
+ continue;
+ }
+ }
+ if (!target) {
+ debug(2, "Target '%s' (by expression) doesn't exist, skipping", entry->targets[n].c_str());
+ continue;
+ }
+ debug(2, "Target: '%s' (expression '%s')", target->_desc.c_str(), entry->targets[n].c_str());
+ break;
+ case kTargetTypeCode:
+ // FIXME
+ error("encountered kTargetTypeCode");
+ break;
+ case kTargetTypeName:
+ // FIXME: handle 'self'
+ target = _vm->getItemByName(entry->targets[n]);
+ if (!target) {
+ debug(2, "Target '%s' (by name) doesn't exist, skipping", entry->targets[n].c_str());
+ continue;
+ }
+ debug(2, "Target: '%s' (by name)", target->_desc.c_str());
+ break;
+ default:
+ uint16 targetId = entry->argvTarget[n];
+ // TODO: is this type, perhaps?
+ uint16 param = entry->argvParam[n];
+ target = _vm->getItemById(targetId);
+ if (!target) {
+ debug(2, "Target %04x (%04x) doesn't exist, skipping", targetId, param);
+ continue;
+ }
+ debug(2, "Target: %04x (%04x) '%s'", targetId, param, target->_desc.c_str());
+ }
+ } else {
+ target = this;
+ debug(2, "Self-target on '%s'", _desc.c_str());
+ }
+
+ // an opcode in the form 0x1xx means to run the script for event 0xx
+ if ((entry->opcode & 0xff00) == 0x0100) {
+ // FIXME: pass on param
+ target->runScript(entry->opcode & 0xff);
+ break;
+ }
+
+ switch (entry->opcode) {
+ case kLBOpNone:
+ warning("ignoring kLBOpNone (event 0x%04x, param 0x%04x, target '%s')",
+ entry->event, entry->param, target->_desc.c_str());
+ break;
+
+ case kLBOpXShow:
+ // TODO: should be setVisible(true) - not a delayed event -
+ // when we're doing the param 1/2/3 stuff above?
+ // and in modern LB this is perhaps just a direct target->setVisible(true)..
+ if (_vm->getGameType() != GType_LIVINGBOOKSV1)
+ warning("kLBOpXShow on '%s' is probably broken", target->_desc.c_str());
+ _vm->queueDelayedEvent(DelayedEvent(this, kLBDelayedEventSetNotVisible));
+ break;
+
+ case kLBOpTogglePlay:
+ target->togglePlaying(false, true);
+ break;
+
+ case kLBOpSetNotVisible:
+ target->setVisible(false);
+ break;
+
+ case kLBOpSetVisible:
+ target->setVisible(true);
+ break;
+
+ case kLBOpDestroy:
+ target->destroySelf();
+ break;
+
+ case kLBOpRewind:
+ target->seek(1);
+ break;
+
+ case kLBOpStop:
+ target->stop();
+ break;
+
+ case kLBOpDisable:
+ target->setEnabled(false);
+ break;
+
+ case kLBOpEnable:
+ target->setEnabled(true);
+ break;
+
+ case kLBOpGlobalSetNotVisible:
+ target->setGlobalVisible(false);
+ break;
+
+ case kLBOpGlobalSetVisible:
+ target->setGlobalVisible(true);
+ break;
+
+ case kLBOpGlobalDisable:
+ target->setGlobalEnabled(false);
+ break;
+
+ case kLBOpGlobalEnable:
+ target->setGlobalEnabled(true);
+ break;
+
+ case kLBOpSeekToEnd:
+ target->seek(0xFFFF);
+ break;
+
+ case kLBOpMute:
+ case kLBOpUnmute:
+ // FIXME
+ warning("ignoring kLBOpMute/Unmute (event 0x%04x, param 0x%04x, target '%s')",
+ entry->event, entry->param, target->_desc.c_str());
+ break;
+
+ case kLBOpLoad:
+ target->load();
+ break;
+
+ case kLBOpPreload:
+ // FIXME
+ warning("ignoring kLBOpPreload (event 0x%04x, param 0x%04x, target '%s')",
+ entry->event, entry->param, target->_desc.c_str());
+ break;
+
+ case kLBOpUnload:
+ target->unload();
+ break;
+
+ case kLBOpSeekToPrev:
+ case kLBOpSeekToNext:
+ // FIXME
+ warning("ignoring kLBOpSeekToPrev/Next (event 0x%04x, param 0x%04x, target '%s')",
+ entry->event, entry->param, target->_desc.c_str());
+ break;
+
+ case kLBOpDragBegin:
+ case kLBOpDragEnd:
+ // FIXME
+ warning("ignoring kLBOpDragBegin/End (event 0x%04x, param 0x%04x, target '%s')",
+ entry->event, entry->param, target->_desc.c_str());
+ break;
+
+ case kLBOpScriptDisable:
+ case kLBOpScriptEnable:
+ // FIXME
+ warning("ignoring kLBOpScriptDisable/Enable (event 0x%04x, param 0x%04x, target '%s')",
+ entry->event, entry->param, target->_desc.c_str());
+ break;
+
+ case kLBOpUnknown1C:
+ // FIXME
+ warning("ignoring kLBOpUnknown1C (event 0x%04x, param 0x%04x, target '%s')",
+ entry->event, entry->param, target->_desc.c_str());
+ break;
+
+ case kLBOpSendExpression:
+ _page->_code->runCode(this, entry->offset);
+ break;
+
+ case kLBOpRunSubentries:
+ for (uint i = 0; i < entry->subentries.size(); i++) {
+ LBScriptEntry *subentry = entry->subentries[i];
+
+ int e = runScriptEntry(subentry);
+
+ switch (subentry->opcode) {
+ case kLBOpJumpUnlessExpression:
+ debug(2, "JumpUnless got %d (to %d, on %d, of %d)", e, subentry->target, i, entry->subentries.size());
+ if (!e)
+ i = subentry->target - 1;
+ break;
+ case kLBOpBreakExpression:
+ debug(2, "BreakExpression");
+ i = entry->subentries.size();
+ break;
+ case kLBOpJumpToExpression:
+ debug(2, "JumpToExpression got %d (on %d, of %d)", e, i, entry->subentries.size());
+ i = e - 1;
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+
+ case kLBOpRunData:
+ readData(entry->dataType, entry->dataLen, entry->data);
+ break;
+
+ case kLBOpJumpUnlessExpression:
+ case kLBOpBreakExpression:
+ case kLBOpJumpToExpression:
+ {
+ LBValue r = _page->_code->runCode(this, entry->offset);
+ // FIXME
+ return r.integer;
+ }
+
+ default:
+ error("Unknown script opcode (type 0x%04x, event 0x%04x, opcode 0x%04x, param 0x%04x, target '%s')",
+ entry->type, entry->event, entry->opcode, entry->param, target->_desc.c_str());
+ }
+ }
+
+ return 0;
+}
+
+void LBItem::runCommand(const Common::String &command) {
+ LBCode tempCode(_vm, 0);
+
+ debug(2, "running command '%s'", command.c_str());
+
+ uint offset = tempCode.parseCode(command);
+ tempCode.runCode(this, offset);
+}
+
+bool LBItem::checkCondition(const Common::String &condition) {
+ LBCode tempCode(_vm, 0);
+
+ debug(3, "checking condition '%s'", condition.c_str());
+
+ uint offset = tempCode.parseCode(condition);
+ LBValue result = tempCode.runCode(this, offset);
+
+ return result.toInt();
+}
+
+} // End of namespace Mohawk
diff --git a/engines/mohawk/livingbooks_itemscript.h b/engines/mohawk/livingbooks_itemscript.h
new file mode 100644
index 00000000000..620d241aa45
--- /dev/null
+++ b/engines/mohawk/livingbooks_itemscript.h
@@ -0,0 +1,82 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef MOHAWK_LIVINGBOOKS_ITEMSCRIPT_H
+#define MOHAWK_LIVINGBOOKS_ITEMSCRIPT_H
+
+#include "common/scummsys.h"
+#include "common/array.h"
+#include "common/str.h"
+
+namespace Mohawk {
+
+struct LBKey {
+ byte code;
+ byte modifiers;
+ byte char_;
+ byte repeats;
+};
+
+struct LBScriptEntry {
+ LBScriptEntry();
+ ~LBScriptEntry();
+
+ uint16 state;
+
+ uint16 type;
+ uint16 event;
+ uint16 opcode;
+ uint16 param;
+
+ uint16 argc;
+ uint16 *argvParam;
+ uint16 *argvTarget;
+
+ uint16 targetingType;
+ Common::Array<Common::String> targets;
+
+ // kLBNotifyChangeMode
+ uint16 newUnknown;
+ uint16 newMode;
+ uint16 newPage;
+ uint16 newSubpage;
+ Common::String newCursor;
+
+ // kLBEventNotified
+ uint16 matchFrom;
+ uint16 matchNotify;
+
+ // kLBOpSendExpression
+ uint32 offset;
+ // kLBOpJumpUnlessExpression
+ uint16 target;
+
+ uint16 dataType;
+ uint16 dataLen;
+ byte *data;
+
+ Common::Array<Common::String> conditions;
+ Common::Array<LBScriptEntry *> subentries;
+};
+
+} // End of namespace Mohawk
+
+#endif
diff --git a/engines/mohawk/livingbooks_itemtypes.h b/engines/mohawk/livingbooks_itemtypes.h
new file mode 100644
index 00000000000..2d1cc52aa31
--- /dev/null
+++ b/engines/mohawk/livingbooks_itemtypes.h
@@ -0,0 +1,205 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef MOHAWK_LIVINGBOOKS_ITEMTYPES_H
+#define MOHAWK_LIVINGBOOKS_ITEMTYPES_H
+
+#include "mohawk/livingbooks_item.h"
+
+#include "common/array.h"
+#include "common/rect.h"
+
+namespace Common {
+ class MemoryReadStreamEndian;
+}
+
+namespace Mohawk {
+
+class MohawkEngine_LivingBooks;
+class LBPage;
+
+class LBSoundItem : public LBItem {
+public:
+ LBSoundItem(MohawkEngine_LivingBooks *_vm, LBPage *page, Common::Rect rect);
+ ~LBSoundItem() override;
+
+ void update() override;
+ bool togglePlaying(bool playing, bool restart) override;
+ void stop() override;
+
+protected:
+ LBItem *createClone() override;
+
+ bool _running;
+};
+
+struct GroupEntry {
+ uint entryId;
+ uint entryType;
+};
+
+class LBGroupItem : public LBItem {
+public:
+ LBGroupItem(MohawkEngine_LivingBooks *_vm, LBPage *page, Common::Rect rect);
+
+ void readData(uint16 type, uint16 size, Common::MemoryReadStreamEndian *stream) override;
+
+ void destroySelf() override;
+ void setEnabled(bool enabled) override;
+ void setGlobalEnabled(bool enabled) override;
+ bool contains(Common::Point point) override;
+ bool togglePlaying(bool playing, bool restart) override;
+ // 0x12
+ void seek(uint16 pos) override;
+ void setVisible(bool visible) override;
+ void setGlobalVisible(bool visible) override;
+ void startPhase(uint phase) override;
+ void stop() override;
+ void load() override;
+ void unload() override;
+ void moveBy(const Common::Point &pos) override;
+ void moveTo(const Common::Point &pos) override;
+
+protected:
+ LBItem *createClone() override;
+
+ bool _starting;
+
+ Common::Array<GroupEntry> _groupEntries;
+};
+
+class LBPaletteItem : public LBItem {
+public:
+ LBPaletteItem(MohawkEngine_LivingBooks *_vm, LBPage *page, Common::Rect rect);
+ ~LBPaletteItem() override;
+
+ void readData(uint16 type, uint16 size, Common::MemoryReadStreamEndian *stream) override;
+
+ bool togglePlaying(bool playing, bool restart) override;
+ void update() override;
+
+protected:
+ LBItem *createClone() override;
+
+ uint16 _fadeInPeriod, _fadeInStep, _drawStart, _drawCount;
+ uint32 _fadeInStart, _fadeInCurrent;
+ byte *_palette;
+};
+
+struct LiveTextWord {
+ Common::Rect bounds;
+ uint16 soundId;
+
+ uint16 itemType;
+ uint16 itemId;
+};
+
+struct LiveTextPhrase {
+ uint16 wordStart, wordCount;
+ uint16 highlightStart, highlightEnd;
+ uint16 startId, endId;
+};
+
+class LBLiveTextItem : public LBItem {
+public:
+ LBLiveTextItem(MohawkEngine_LivingBooks *_vm, LBPage *page, Common::Rect rect);
+
+ void readData(uint16 type, uint16 size, Common::MemoryReadStreamEndian *stream) override;
+
+ bool contains(Common::Point point) override;
+ void update() override;
+ void draw() override;
+ void handleMouseDown(Common::Point pos) override;
+ bool togglePlaying(bool playing, bool restart) override;
+ void stop() override;
+ void notify(uint16 data, uint16 from) override;
+
+protected:
+ LBItem *createClone() override;
+
+ void paletteUpdate(uint16 word, bool on);
+ void drawWord(uint word, uint yPos);
+
+ uint16 _currentPhrase, _currentWord;
+
+ byte _backgroundColor[4];
+ byte _foregroundColor[4];
+ byte _highlightColor[4];
+ uint16 _paletteIndex;
+
+ Common::Array<LiveTextWord> _words;
+ Common::Array<LiveTextPhrase> _phrases;
+};
+
+class LBPictureItem : public LBItem {
+public:
+ LBPictureItem(MohawkEngine_LivingBooks *_vm, LBPage *page, Common::Rect rect);
+
+ void readData(uint16 type, uint16 size, Common::MemoryReadStreamEndian *stream) override;
+
+ bool contains(Common::Point point) override;
+ void draw() override;
+ void init() override;
+
+protected:
+ LBItem *createClone() override;
+};
+
+class LBMovieItem : public LBItem {
+public:
+ LBMovieItem(MohawkEngine_LivingBooks *_vm, LBPage *page, Common::Rect rect);
+ ~LBMovieItem() override;
+
+ void update() override;
+ bool togglePlaying(bool playing, bool restart) override;
+
+protected:
+ LBItem *createClone() override;
+};
+
+class LBMiniGameItem : public LBItem {
+public:
+ LBMiniGameItem(MohawkEngine_LivingBooks *_vm, LBPage *page, Common::Rect rect);
+ ~LBMiniGameItem() override;
+
+ bool togglePlaying(bool playing, bool restart) override;
+
+protected:
+ LBItem *createClone() override;
+};
+
+class LBProxyItem : public LBItem {
+public:
+ LBProxyItem(MohawkEngine_LivingBooks *_vm, LBPage *page, Common::Rect rect);
+ ~LBProxyItem() override;
+
+ void load() override;
+ void unload() override;
+
+protected:
+ LBItem *createClone() override;
+
+ class LBPage *_page;
+};
+
+} // End of namespace Mohawk
+
+#endif
diff --git a/engines/mohawk/livingbooks_page.h b/engines/mohawk/livingbooks_page.h
new file mode 100644
index 00000000000..02e095964b7
--- /dev/null
+++ b/engines/mohawk/livingbooks_page.h
@@ -0,0 +1,61 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef MOHAWK_LIVINGBOOKS_PAGE_H
+#define MOHAWK_LIVINGBOOKS_PAGE_H
+
+#include "common/array.h"
+
+namespace Mohawk {
+
+class MohawkEngine_LivingBooks;
+class LBItem;
+class LBCode;
+class Archive;
+
+class LBPage {
+public:
+ LBPage(MohawkEngine_LivingBooks *vm);
+ ~LBPage();
+
+ void open(Archive *mhk, uint16 baseId);
+ uint16 getResourceVersion();
+
+ void addClonedItem(LBItem *item);
+ void itemDestroyed(LBItem *item);
+
+ LBCode *_code;
+
+protected:
+ MohawkEngine_LivingBooks *_vm;
+
+ Archive *_mhk;
+ Common::Array<LBItem *> _items;
+
+ uint16 _baseId;
+ bool _cascade;
+
+ void loadBITL(uint16 resourceId);
+};
+
+} // End of namespace Mohawk
+
+#endif
diff --git a/engines/mohawk/module.mk b/engines/mohawk/module.mk
index b264987cae7..c7119b0f973 100644
--- a/engines/mohawk/module.mk
+++ b/engines/mohawk/module.mk
@@ -12,6 +12,8 @@ MODULE_OBJS = \
livingbooks_bookinfo.o \
livingbooks_code.o \
livingbooks_graphics.o \
+ livingbooks_item.o \
+ livingbooks_itemscript.o \
livingbooks_itemtypes.o \
livingbooks_lbx.o \
livingbooks_menus.o \
More information about the Scummvm-git-logs
mailing list