mirror of
https://github.com/libretro/scummvm.git
synced 2025-01-10 20:01:25 +00:00
3530eae6f1
The german and french version of "Just Grandma and Me" (Mohawk engine) have the special characters "ç" and "ö" in their config files. This causes ScummVM to crash when trying to start the game in german or french. Allowing non english chars in config files for Living Books games fixes this issue.
3987 lines
99 KiB
C++
3987 lines
99 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
*/
|
|
|
|
#include "mohawk/livingbooks.h"
|
|
#include "mohawk/resource.h"
|
|
#include "mohawk/cursors.h"
|
|
#include "mohawk/video.h"
|
|
|
|
#include "common/config-manager.h"
|
|
#include "common/error.h"
|
|
#include "common/events.h"
|
|
#include "common/fs.h"
|
|
#include "common/archive.h"
|
|
#include "common/textconsole.h"
|
|
#include "common/system.h"
|
|
#include "common/memstream.h"
|
|
|
|
#include "graphics/palette.h"
|
|
|
|
#include "engines/util.h"
|
|
|
|
#include "gui/message.h"
|
|
|
|
#include "graphics/cursorman.h"
|
|
|
|
namespace Mohawk {
|
|
|
|
// read a null-terminated string from a stream
|
|
Common::String MohawkEngine_LivingBooks::readString(Common::ReadStream *stream) {
|
|
Common::String ret;
|
|
while (!stream->eos()) {
|
|
byte in = stream->readByte();
|
|
if (!in)
|
|
break;
|
|
ret += in;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
// read a rect from a stream
|
|
Common::Rect MohawkEngine_LivingBooks::readRect(Common::ReadStreamEndian *stream) {
|
|
Common::Rect rect;
|
|
|
|
// the V1 mac games have their rects in QuickDraw order
|
|
if (isPreMohawk() && getPlatform() == Common::kPlatformMacintosh) {
|
|
rect.top = stream->readSint16();
|
|
rect.left = stream->readSint16();
|
|
rect.bottom = stream->readSint16();
|
|
rect.right = stream->readSint16();
|
|
} else {
|
|
rect.left = stream->readSint16();
|
|
rect.top = stream->readSint16();
|
|
rect.right = stream->readSint16();
|
|
rect.bottom = stream->readSint16();
|
|
}
|
|
|
|
return rect;
|
|
}
|
|
|
|
LBPage::LBPage(MohawkEngine_LivingBooks *vm) : _vm(vm) {
|
|
_code = NULL;
|
|
_mhk = NULL;
|
|
|
|
_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) {
|
|
_needsUpdate = false;
|
|
_needsRedraw = false;
|
|
_screenWidth = _screenHeight = 0;
|
|
|
|
_curLanguage = 1;
|
|
_curSelectedPage = 1;
|
|
|
|
_alreadyShowedIntro = false;
|
|
|
|
_rnd = new Common::RandomSource("livingbooks");
|
|
|
|
_sound = NULL;
|
|
_video = NULL;
|
|
_page = NULL;
|
|
|
|
const Common::FSNode gameDataDir(ConfMan.get("path"));
|
|
// Rugrats
|
|
SearchMan.addSubDirectoryMatching(gameDataDir, "program", 0, 2);
|
|
SearchMan.addSubDirectoryMatching(gameDataDir, "Rugrats Adventure Game", 0, 2);
|
|
// CarmenTQ
|
|
SearchMan.addSubDirectoryMatching(gameDataDir, "95instal", 0, 4);
|
|
}
|
|
|
|
MohawkEngine_LivingBooks::~MohawkEngine_LivingBooks() {
|
|
destroyPage();
|
|
|
|
delete _sound;
|
|
delete _video;
|
|
delete _gfx;
|
|
delete _rnd;
|
|
_bookInfoFile.clear();
|
|
}
|
|
|
|
Common::Error MohawkEngine_LivingBooks::run() {
|
|
MohawkEngine::run();
|
|
|
|
if (!_mixer->isReady()) {
|
|
return Common::kAudioDeviceInitFailed;
|
|
}
|
|
|
|
setDebugger(new LivingBooksConsole(this));
|
|
// Load the book info from the detected file
|
|
loadBookInfo(getBookInfoFileName());
|
|
|
|
if (!_title.empty()) // Some games don't have the title stored
|
|
debug("Starting Living Books Title \'%s\'", _title.c_str());
|
|
if (!_copyright.empty())
|
|
debug("Copyright: %s", _copyright.c_str());
|
|
debug("This book has %d page(s) in %d language(s).", _numPages, _numLanguages);
|
|
if (_poetryMode)
|
|
debug("Running in poetry mode.");
|
|
|
|
if (!_screenWidth || !_screenHeight)
|
|
error("Could not find xRes/yRes variables");
|
|
|
|
_gfx = new LBGraphics(this, _screenWidth, _screenHeight);
|
|
_video = new VideoManager(this);
|
|
_sound = new Sound(this);
|
|
|
|
if (getGameType() != GType_LIVINGBOOKSV1)
|
|
_cursor = new LivingBooksCursorManager_v2();
|
|
else if (getPlatform() == Common::kPlatformMacintosh)
|
|
_cursor = new MacCursorManager(getAppName());
|
|
else
|
|
_cursor = new NECursorManager(getAppName());
|
|
|
|
_cursor->setDefaultCursor();
|
|
_cursor->showCursor();
|
|
|
|
if (!tryLoadPageStart(kLBIntroMode, 1))
|
|
error("Could not load intro page");
|
|
|
|
Common::Event event;
|
|
while (!shouldQuit()) {
|
|
while (_eventMan->pollEvent(event)) {
|
|
LBItem *found = NULL;
|
|
|
|
switch (event.type) {
|
|
case Common::EVENT_MOUSEMOVE:
|
|
_needsUpdate = true;
|
|
break;
|
|
|
|
case Common::EVENT_LBUTTONUP:
|
|
if (_focus)
|
|
_focus->handleMouseUp(event.mouse);
|
|
break;
|
|
|
|
case Common::EVENT_LBUTTONDOWN:
|
|
for (Common::List<LBItem *>::const_iterator i = _orderedItems.begin(); i != _orderedItems.end(); ++i) {
|
|
if ((*i)->contains(event.mouse)) {
|
|
found = *i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (found && CursorMan.isVisible())
|
|
found->handleMouseDown(event.mouse);
|
|
break;
|
|
|
|
case Common::EVENT_KEYDOWN:
|
|
switch (event.kbd.keycode) {
|
|
case Common::KEYCODE_SPACE:
|
|
pauseGame();
|
|
break;
|
|
|
|
case Common::KEYCODE_ESCAPE:
|
|
if (_curMode == kLBIntroMode)
|
|
tryLoadPageStart(kLBControlMode, 1);
|
|
else
|
|
_video->stopVideos();
|
|
break;
|
|
|
|
case Common::KEYCODE_LEFT:
|
|
prevPage();
|
|
break;
|
|
|
|
case Common::KEYCODE_RIGHT:
|
|
nextPage();
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
updatePage();
|
|
|
|
if (_video->updateMovies())
|
|
_needsUpdate = true;
|
|
|
|
if (_needsUpdate) {
|
|
_system->updateScreen();
|
|
_needsUpdate = false;
|
|
}
|
|
|
|
// Cut down on CPU usage
|
|
_system->delayMillis(10);
|
|
|
|
// handle pending notifications
|
|
while (_notifyEvents.size()) {
|
|
NotifyEvent notifyEvent = _notifyEvents.pop();
|
|
handleNotify(notifyEvent);
|
|
}
|
|
}
|
|
|
|
return Common::kNoError;
|
|
}
|
|
|
|
void MohawkEngine_LivingBooks::pauseEngineIntern(bool pause) {
|
|
MohawkEngine::pauseEngineIntern(pause);
|
|
|
|
if (pause) {
|
|
_video->pauseVideos();
|
|
} else {
|
|
_video->resumeVideos();
|
|
_system->updateScreen();
|
|
}
|
|
}
|
|
|
|
void MohawkEngine_LivingBooks::loadBookInfo(const Common::String &filename) {
|
|
_bookInfoFile.allowNonEnglishCharacters();
|
|
if (!_bookInfoFile.loadFromFile(filename))
|
|
error("Could not open %s as a config file", filename.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(NULL, 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;
|
|
_lastSoundId = 0;
|
|
_soundLockOwner = 0;
|
|
|
|
_gfx->clearCache();
|
|
_video->stopVideos();
|
|
|
|
_eventQueue.clear();
|
|
|
|
delete _page;
|
|
assert(_items.empty());
|
|
assert(_orderedItems.empty());
|
|
_page = NULL;
|
|
|
|
_notifyEvents.clear();
|
|
|
|
_focus = NULL;
|
|
}
|
|
|
|
// Replace any colons (originally a slash) with another character
|
|
static Common::String replaceColons(const Common::String &in, char replace) {
|
|
Common::String out;
|
|
|
|
for (uint32 i = 0; i < in.size(); i++) {
|
|
if (in[i] == ':')
|
|
out += replace;
|
|
else
|
|
out += in[i];
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
// Helper function to assist in opening pages
|
|
static bool tryOpenPage(Archive *archive, const Common::String &fileName) {
|
|
// Try the plain file name first
|
|
if (archive->openFile(fileName))
|
|
return true;
|
|
|
|
// No colons, then bail out
|
|
if (!fileName.contains(':'))
|
|
return false;
|
|
|
|
// Try replacing colons with underscores (in case the original was
|
|
// a Mac version and had slashes not as a separator).
|
|
if (archive->openFile(replaceColons(fileName, '_')))
|
|
return true;
|
|
|
|
// Try replacing colons with slashes (in case the original was a Mac
|
|
// version and had slashes as a separator).
|
|
if (archive->openFile(replaceColons(fileName, '/')))
|
|
return true;
|
|
|
|
// Failed to open the archive
|
|
return false;
|
|
}
|
|
|
|
bool MohawkEngine_LivingBooks::loadPage(LBMode mode, uint page, uint subpage) {
|
|
destroyPage();
|
|
|
|
Common::String name = stringForMode(mode);
|
|
|
|
Common::String base;
|
|
if (subpage)
|
|
base = Common::String::format("Page%d.%d", page, subpage);
|
|
else
|
|
base = Common::String::format("Page%d", page);
|
|
|
|
Common::String filename, leftover;
|
|
|
|
filename = getFileNameFromConfig(name, base, leftover);
|
|
_readOnly = false;
|
|
|
|
if (filename.empty()) {
|
|
leftover.clear();
|
|
filename = getFileNameFromConfig(name, base + ".r", leftover);
|
|
_readOnly = true;
|
|
}
|
|
|
|
// TODO: fading between pages
|
|
#if 0
|
|
bool fade = false;
|
|
if (leftover.contains("fade")) {
|
|
fade = true;
|
|
}
|
|
#endif
|
|
|
|
if (leftover.contains("read")) {
|
|
_readOnly = true;
|
|
}
|
|
if (leftover.contains("load")) {
|
|
// FIXME: don't ignore this
|
|
warning("ignoring 'load' for filename '%s'", filename.c_str());
|
|
}
|
|
if (leftover.contains("cut")) {
|
|
// FIXME: don't ignore this
|
|
warning("ignoring 'cut' for filename '%s'", filename.c_str());
|
|
}
|
|
if (leftover.contains("killgag")) {
|
|
// FIXME: don't ignore this
|
|
warning("ignoring 'killgag' for filename '%s'", filename.c_str());
|
|
}
|
|
|
|
Archive *pageArchive = createArchive();
|
|
if (!filename.empty() && tryOpenPage(pageArchive, filename)) {
|
|
_page = new LBPage(this);
|
|
_page->open(pageArchive, 1000);
|
|
} else {
|
|
delete pageArchive;
|
|
debug(2, "Could not find page %d.%d for '%s'", page, subpage, name.c_str());
|
|
return false;
|
|
}
|
|
|
|
if (getFeatures() & GF_LB_10) {
|
|
if (_readOnly) {
|
|
error("found .r entry in Living Books 1.0 game");
|
|
} else {
|
|
// some very early versions of the LB engine don't have
|
|
// .r entries in their book info; instead, it is just hardcoded
|
|
// like this (which would unfortunately break later games)
|
|
_readOnly = (mode != kLBControlMode && mode != kLBPlayMode);
|
|
}
|
|
}
|
|
|
|
debug(1, "Page Version: %d", _page->getResourceVersion());
|
|
|
|
_curMode = mode;
|
|
_curPage = page;
|
|
_curSubPage = subpage;
|
|
|
|
_cursor->showCursor();
|
|
|
|
_gfx->setPalette(1000);
|
|
|
|
_phase = 0;
|
|
_introDone = false;
|
|
|
|
_needsRedraw = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
void MohawkEngine_LivingBooks::updatePage() {
|
|
switch (_phase) {
|
|
case kLBPhaseInit:
|
|
for (uint32 i = 0; i < _items.size(); i++)
|
|
_items[i]->startPhase(kLBPhaseCreate);
|
|
|
|
for (uint32 i = 0; i < _items.size(); i++)
|
|
_items[i]->startPhase(_phase);
|
|
|
|
if (_curMode == kLBControlMode) {
|
|
// hard-coded control page startup
|
|
LBItem *item;
|
|
|
|
uint16 page = _curPage;
|
|
if (getFeatures() & GF_LB_10) {
|
|
// Living Books 1.0 had the meanings of these pages reversed
|
|
if (page == 2)
|
|
page = 3;
|
|
else if (page == 3)
|
|
page = 2;
|
|
}
|
|
|
|
switch (page) {
|
|
case 1:
|
|
debug(2, "updatePage() for control page 1 (menu)");
|
|
|
|
if (_poetryMode) {
|
|
for (uint16 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);
|
|
}
|
|
}
|
|
|
|
for (uint16 i = 0; i < _numLanguages; i++) {
|
|
item = getItemById(100 + i);
|
|
if (item)
|
|
item->seek((i + 1 == _curLanguage) ? 0xFFFF : 1);
|
|
item = getItemById(200 + i);
|
|
if (item)
|
|
item->setVisible(false);
|
|
}
|
|
|
|
item = getItemById(12);
|
|
if (item)
|
|
item->setVisible(false);
|
|
|
|
if (_alreadyShowedIntro) {
|
|
item = getItemById(10);
|
|
if (item) {
|
|
item->setVisible(false);
|
|
item->seek(0xFFFF);
|
|
}
|
|
} else {
|
|
_alreadyShowedIntro = true;
|
|
item = getItemById(11);
|
|
if (item)
|
|
item->setVisible(false);
|
|
}
|
|
break;
|
|
|
|
case 2:
|
|
debug(2, "updatePage() for control page 2 (quit)");
|
|
|
|
item = getItemById(12);
|
|
if (item)
|
|
item->setVisible(false);
|
|
item = getItemById(13);
|
|
if (item)
|
|
item->setVisible(false);
|
|
break;
|
|
|
|
case 3:
|
|
debug(2, "updatePage() for control page 3 (options)");
|
|
|
|
for (uint i = 0; i < _numLanguages; i++) {
|
|
item = getItemById(100 + i);
|
|
if (item)
|
|
item->setVisible(_curLanguage == i + 1);
|
|
}
|
|
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);
|
|
}
|
|
item = getItemById(202);
|
|
if (item)
|
|
item->setVisible(false);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
_phase++;
|
|
break;
|
|
|
|
case kLBPhaseIntro:
|
|
for (uint32 i = 0; i < _items.size(); i++)
|
|
_items[i]->startPhase(_phase);
|
|
|
|
if (_curMode == kLBControlMode) {
|
|
LBItem *item = getItemById(10);
|
|
if (item)
|
|
item->togglePlaying(false);
|
|
}
|
|
|
|
_phase++;
|
|
break;
|
|
|
|
case kLBPhaseMain:
|
|
if (!_introDone)
|
|
break;
|
|
|
|
for (uint32 i = 0; i < _items.size(); i++)
|
|
_items[i]->startPhase(_phase);
|
|
|
|
_phase++;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
while (_eventQueue.size()) {
|
|
DelayedEvent delayedEvent = _eventQueue.pop();
|
|
for (uint32 i = 0; i < _items.size(); i++) {
|
|
if (_items[i] != delayedEvent.item)
|
|
continue;
|
|
|
|
switch (delayedEvent.type) {
|
|
case kLBDelayedEventDestroy:
|
|
_items.remove_at(i);
|
|
i--;
|
|
_orderedItems.remove(delayedEvent.item);
|
|
_page->itemDestroyed(delayedEvent.item);
|
|
delete delayedEvent.item;
|
|
if (_focus == delayedEvent.item)
|
|
_focus = NULL;
|
|
break;
|
|
case kLBDelayedEventSetNotVisible:
|
|
_items[i]->setVisible(false);
|
|
break;
|
|
case kLBDelayedEventDone:
|
|
_items[i]->done(true);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (uint16 i = 0; i < _items.size(); i++)
|
|
_items[i]->update();
|
|
|
|
if (_needsRedraw) {
|
|
for (Common::List<LBItem *>::const_iterator i = _orderedItems.reverse_begin(); i != _orderedItems.end(); --i)
|
|
(*i)->draw();
|
|
|
|
_needsRedraw = false;
|
|
_needsUpdate = true;
|
|
}
|
|
}
|
|
|
|
void MohawkEngine_LivingBooks::addArchive(Archive *archive) {
|
|
_mhk.push_back(archive);
|
|
}
|
|
|
|
void MohawkEngine_LivingBooks::removeArchive(Archive *archive) {
|
|
for (uint i = 0; i < _mhk.size(); i++) {
|
|
if (archive != _mhk[i])
|
|
continue;
|
|
_mhk.remove_at(i);
|
|
return;
|
|
}
|
|
|
|
error("removeArchive didn't find archive");
|
|
}
|
|
|
|
void MohawkEngine_LivingBooks::addItem(LBItem *item) {
|
|
_items.push_back(item);
|
|
_orderedItems.push_front(item);
|
|
item->_iterator = _orderedItems.begin();
|
|
}
|
|
|
|
void MohawkEngine_LivingBooks::removeItems(const Common::Array<LBItem *> &items) {
|
|
for (uint i = 0; i < items.size(); i++) {
|
|
bool found = false;
|
|
for (uint16 j = 0; j < _items.size(); j++) {
|
|
if (items[i] != _items[j])
|
|
continue;
|
|
found = true;
|
|
_items.remove_at(j);
|
|
break;
|
|
}
|
|
assert(found);
|
|
_orderedItems.erase(items[i]->_iterator);
|
|
}
|
|
}
|
|
|
|
LBItem *MohawkEngine_LivingBooks::getItemById(uint16 id) {
|
|
for (uint16 i = 0; i < _items.size(); i++)
|
|
if (_items[i]->getId() == id)
|
|
return _items[i];
|
|
|
|
return NULL;
|
|
}
|
|
|
|
LBItem *MohawkEngine_LivingBooks::getItemByName(Common::String name) {
|
|
for (uint16 i = 0; i < _items.size(); i++)
|
|
if (_items[i]->getName() == name)
|
|
return _items[i];
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void MohawkEngine_LivingBooks::setFocus(LBItem *focus) {
|
|
_focus = focus;
|
|
}
|
|
|
|
void MohawkEngine_LivingBooks::setEnableForAll(bool enable, LBItem *except) {
|
|
for (uint16 i = 0; i < _items.size(); i++)
|
|
if (except != _items[i])
|
|
_items[i]->setEnabled(enable);
|
|
}
|
|
|
|
void MohawkEngine_LivingBooks::notifyAll(uint16 data, uint16 from) {
|
|
for (uint16 i = 0; i < _items.size(); i++)
|
|
_items[i]->notify(data, from);
|
|
}
|
|
|
|
void MohawkEngine_LivingBooks::queueDelayedEvent(DelayedEvent event) {
|
|
_eventQueue.push(event);
|
|
}
|
|
|
|
bool MohawkEngine_LivingBooks::playSound(LBItem *source, uint16 resourceId) {
|
|
if (_lastSoundId && !_sound->isPlaying(_lastSoundId))
|
|
_lastSoundId = 0;
|
|
|
|
if (!source->isAmbient() || !_sound->isPlaying()) {
|
|
if (!_soundLockOwner) {
|
|
if (_lastSoundId && _lastSoundOwner != source->getId())
|
|
if (source->getSoundPriority() >= _lastSoundPriority)
|
|
return false;
|
|
} else {
|
|
if (_soundLockOwner != source->getId() && source->getSoundPriority() >= _maxSoundPriority)
|
|
return false;
|
|
}
|
|
|
|
if (_lastSoundId)
|
|
_sound->stopSound(_lastSoundId);
|
|
|
|
_lastSoundOwner = source->getId();
|
|
_lastSoundPriority = source->getSoundPriority();
|
|
}
|
|
|
|
_lastSoundId = resourceId;
|
|
_sound->playSound(resourceId);
|
|
|
|
return true;
|
|
}
|
|
|
|
void MohawkEngine_LivingBooks::lockSound(LBItem *owner, bool lock) {
|
|
if (!lock) {
|
|
_soundLockOwner = 0;
|
|
return;
|
|
}
|
|
|
|
if (_soundLockOwner || (owner->isAmbient() && _sound->isPlaying()))
|
|
return;
|
|
|
|
if (_lastSoundId && !_sound->isPlaying(_lastSoundId))
|
|
_lastSoundId = 0;
|
|
|
|
_soundLockOwner = owner->getId();
|
|
_maxSoundPriority = owner->getSoundPriority();
|
|
if (_lastSoundId && _maxSoundPriority <= _lastSoundPriority) {
|
|
_sound->stopSound(_lastSoundId);
|
|
_lastSoundId = 0;
|
|
}
|
|
}
|
|
|
|
// 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::SeekableSubReadStreamEndian *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::SeekableSubReadStreamEndian *MohawkEngine_LivingBooks::wrapStreamEndian(uint32 tag, uint16 id) {
|
|
Common::SeekableReadStream *dataStream = getResource(tag, id);
|
|
return new Common::SeekableSubReadStreamEndian(dataStream, 0, dataStream->size(), 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 Mac OS X 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();
|
|
|
|
return new MohawkArchive();
|
|
}
|
|
|
|
bool MohawkEngine_LivingBooks::isPreMohawk() const {
|
|
return getGameType() == GType_LIVINGBOOKSV1
|
|
|| (getGameType() == GType_LIVINGBOOKSV2 && getPlatform() == Common::kPlatformMacintosh);
|
|
}
|
|
|
|
void MohawkEngine_LivingBooks::addNotifyEvent(NotifyEvent event) {
|
|
_notifyEvents.push(event);
|
|
}
|
|
|
|
bool MohawkEngine_LivingBooks::tryLoadPageStart(LBMode mode, uint page) {
|
|
// try first subpage of the page
|
|
if (loadPage(mode, page, 1))
|
|
return true;
|
|
|
|
// then just the plain page
|
|
if (loadPage(mode, page, 0))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool MohawkEngine_LivingBooks::tryDefaultPage() {
|
|
if (_curMode == kLBCreditsMode || _curMode == kLBPreviewMode) {
|
|
// go to options page
|
|
if (getFeatures() & GF_LB_10) {
|
|
if (tryLoadPageStart(kLBControlMode, 2))
|
|
return true;
|
|
} else {
|
|
if (tryLoadPageStart(kLBControlMode, 3))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// go to menu page
|
|
if (tryLoadPageStart(kLBControlMode, 1))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void MohawkEngine_LivingBooks::prevPage() {
|
|
if (_curPage > 1 && (tryLoadPageStart(_curMode, _curPage - 1)))
|
|
return;
|
|
|
|
if (tryDefaultPage())
|
|
return;
|
|
|
|
error("Could not find page before %d.%d for mode %d", _curPage, _curSubPage, (int)_curMode);
|
|
}
|
|
|
|
void MohawkEngine_LivingBooks::nextPage() {
|
|
// we try the next subpage first
|
|
if (loadPage(_curMode, _curPage, _curSubPage + 1))
|
|
return;
|
|
|
|
if (tryLoadPageStart(_curMode, _curPage + 1))
|
|
return;
|
|
|
|
if (tryDefaultPage())
|
|
return;
|
|
|
|
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)
|
|
|
|
switch (event.type) {
|
|
case kLBNotifyGUIAction:
|
|
debug(2, "kLBNotifyGUIAction: %d", event.param);
|
|
|
|
if (_curMode != kLBControlMode)
|
|
break;
|
|
|
|
// The scripting passes us the control ID as param, so we work
|
|
// out which control was clicked, then run the relevant code.
|
|
|
|
uint16 page;
|
|
page = _curPage;
|
|
if (getFeatures() & GF_LB_10) {
|
|
// Living Books 1.0 had the meanings of these pages reversed
|
|
if (page == 2)
|
|
page = 3;
|
|
else if (page == 3)
|
|
page = 2;
|
|
}
|
|
|
|
switch (page) {
|
|
case 1:
|
|
// main menu
|
|
if (_poetryMode)
|
|
handleUIPoetryMenuClick(event.param);
|
|
else
|
|
handleUIMenuClick(event.param);
|
|
break;
|
|
|
|
case 2:
|
|
// quit screen
|
|
handleUIQuitClick(event.param);
|
|
break;
|
|
|
|
case 3:
|
|
// options screen
|
|
handleUIOptionsClick(event.param);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case kLBNotifyGoToControls:
|
|
debug(2, "kLBNotifyGoToControls: %d", event.param);
|
|
|
|
if (!tryLoadPageStart(kLBControlMode, 1))
|
|
error("couldn't load controls page");
|
|
break;
|
|
|
|
case kLBNotifyChangePage:
|
|
switch (event.param) {
|
|
case 0xfffe:
|
|
debug(2, "kLBNotifyChangePage: next page");
|
|
nextPage();
|
|
return;
|
|
|
|
case 0xffff:
|
|
debug(2, "kLBNotifyChangePage: previous page");
|
|
prevPage();
|
|
break;
|
|
|
|
default:
|
|
debug(2, "kLBNotifyChangePage: trying %d", event.param);
|
|
if (!tryLoadPageStart(_curMode, event.param)) {
|
|
if (!tryDefaultPage()) {
|
|
error("failed to load default page after change to page %d (mode %d) failed", event.param, _curMode);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case kLBNotifyGotoQuit:
|
|
debug(2, "kLBNotifyGotoQuit: %d", event.param);
|
|
|
|
if (!tryLoadPageStart(kLBControlMode, 2))
|
|
error("couldn't load quit page");
|
|
break;
|
|
|
|
case kLBNotifyIntroDone:
|
|
debug(2, "kLBNotifyIntroDone: %d", event.param);
|
|
|
|
if (event.param != 1)
|
|
break;
|
|
|
|
_introDone = true;
|
|
|
|
// TODO: if !_readOnly, go to next page (-2 case above)
|
|
// if in older one (not in e.g. 1.4 w/tortoise),
|
|
// if mode is 6 (kLBPlayMode?), go to next page (-2 case) if curr page > nPages (i.e. the end)
|
|
// else, nothing
|
|
|
|
if (!_readOnly)
|
|
break;
|
|
|
|
nextPage();
|
|
break;
|
|
|
|
case kLBNotifyChangeMode:
|
|
if (getGameType() == GType_LIVINGBOOKSV1) {
|
|
debug(2, "kLBNotifyChangeMode: %d", event.param);
|
|
quitGame();
|
|
break;
|
|
}
|
|
|
|
debug(2, "kLBNotifyChangeMode: v2 type %d", event.param);
|
|
switch (event.param) {
|
|
case 1:
|
|
debug(2, "kLBNotifyChangeMode:, mode %d, page %d.%d",
|
|
event.newMode, event.newPage, event.newSubpage);
|
|
// TODO: what is entry.newUnknown?
|
|
if (!event.newMode)
|
|
event.newMode = _curMode;
|
|
if (!loadPage((LBMode)event.newMode, event.newPage, event.newSubpage)) {
|
|
if (event.newPage != 0 || !loadPage((LBMode)event.newMode, _curPage, event.newSubpage))
|
|
if (event.newSubpage != 0 || !loadPage((LBMode)event.newMode, event.newPage, 1))
|
|
if (event.newSubpage != 1 || !loadPage((LBMode)event.newMode, event.newPage, 0))
|
|
error("kLBNotifyChangeMode failed to move to mode %d, page %d.%d",
|
|
event.newMode, event.newPage, event.newSubpage);
|
|
}
|
|
break;
|
|
case 3:
|
|
debug(2, "kLBNotifyChangeMode: new cursor '%s'", event.newCursor.c_str());
|
|
_cursor->setCursor(event.newCursor);
|
|
break;
|
|
default:
|
|
error("unknown v2 kLBNotifyChangeMode type %d", event.param);
|
|
}
|
|
break;
|
|
|
|
case kLBNotifyCursorChange:
|
|
debug(2, "kLBNotifyCursorChange: %d", event.param);
|
|
|
|
// TODO: show/hide cursor according to parameter?
|
|
break;
|
|
|
|
case kLBNotifyPrintPage:
|
|
debug(2, "kLBNotifyPrintPage: %d", event.param);
|
|
|
|
warning("kLBNotifyPrintPage unimplemented");
|
|
break;
|
|
|
|
case kLBNotifyQuit:
|
|
debug(2, "kLBNotifyQuit: %d", event.param);
|
|
|
|
quitGame();
|
|
break;
|
|
|
|
default:
|
|
error("Unknown notification %d (param 0x%04x)", event.type, event.param);
|
|
}
|
|
}
|
|
|
|
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::SeekableSubReadStreamEndian *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 = NULL;
|
|
} 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::SeekableSubReadStreamEndian *aniStream = _vm->wrapStreamEndian(ID_ANI, resourceId);
|
|
|
|
if (aniStream->size() != 30)
|
|
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->pos() != aniStream->size())
|
|
error("Still %d bytes at the end of anim stream", aniStream->size() - aniStream->pos());
|
|
|
|
delete aniStream;
|
|
|
|
if (sprResourceOffset)
|
|
error("Cannot handle non-zero ANI offset yet");
|
|
|
|
Common::SeekableSubReadStreamEndian *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", 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::SeekableSubReadStreamEndian *shapeStream = _vm->wrapStreamEndian(ID_SHP, resourceId);
|
|
|
|
if (_vm->isPreMohawk()) {
|
|
if (shapeStream->size() < 6)
|
|
error("V1 SHP Record size too short (%d)", 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)", 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)", 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 = NULL;
|
|
argvParam = NULL;
|
|
argvTarget = NULL;
|
|
}
|
|
|
|
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::SeekableSubReadStreamEndian *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, 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)", 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(NULL);
|
|
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();
|
|
}
|
|
|
|
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();
|
|
_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 = NULL;
|
|
}
|
|
|
|
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 = NULL;
|
|
_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 = NULL;
|
|
}
|
|
|
|
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 = NULL;
|
|
|
|
LBItem::unload();
|
|
}
|
|
|
|
LBItem *LBProxyItem::createClone() {
|
|
return new LBProxyItem(_vm, _page, _rect);
|
|
}
|
|
|
|
} // End of namespace Mohawk
|