aboutsummaryrefslogtreecommitdiff
path: root/engines/mohawk
diff options
context:
space:
mode:
authorMatthew Hoops2010-11-28 22:55:15 +0000
committerMatthew Hoops2010-11-28 22:55:15 +0000
commit2d924afa9d307c6eaa8126c8e0367b9e1216da5e (patch)
treebbf2b2aa747e5c338f6ea77d8daaa02e3198ae4e /engines/mohawk
parentf403c0f8a862573d46957f8adaf8dc32b7548e2e (diff)
downloadscummvm-rg350-2d924afa9d307c6eaa8126c8e0367b9e1216da5e.tar.gz
scummvm-rg350-2d924afa9d307c6eaa8126c8e0367b9e1216da5e.tar.bz2
scummvm-rg350-2d924afa9d307c6eaa8126c8e0367b9e1216da5e.zip
MOHAWK: Add basic Living Books support (all credit goes to fuzzie!)
v1 and v3 (both Windows and Mac) are working, v1 support is in better shape. svn-id: r54558
Diffstat (limited to 'engines/mohawk')
-rw-r--r--engines/mohawk/console.cpp1
-rw-r--r--engines/mohawk/cursors.cpp155
-rw-r--r--engines/mohawk/cursors.h59
-rw-r--r--engines/mohawk/detection_tables.h23
-rw-r--r--engines/mohawk/graphics.cpp85
-rw-r--r--engines/mohawk/graphics.h6
-rw-r--r--engines/mohawk/livingbooks.cpp2103
-rw-r--r--engines/mohawk/livingbooks.h386
-rw-r--r--engines/mohawk/mohawk.h3
9 files changed, 2681 insertions, 140 deletions
diff --git a/engines/mohawk/console.cpp b/engines/mohawk/console.cpp
index e25ff030d0..e4fe9e0f8b 100644
--- a/engines/mohawk/console.cpp
+++ b/engines/mohawk/console.cpp
@@ -690,6 +690,7 @@ bool LivingBooksConsole::Cmd_DrawImage(int argc, const char **argv) {
}
_vm->_gfx->copyImageToScreen((uint16)atoi(argv[1]));
+ _vm->_system->updateScreen();
return false;
}
diff --git a/engines/mohawk/cursors.cpp b/engines/mohawk/cursors.cpp
index 3b937385f9..c7288ab0c3 100644
--- a/engines/mohawk/cursors.cpp
+++ b/engines/mohawk/cursors.cpp
@@ -30,11 +30,18 @@
#include "mohawk/myst.h"
#include "mohawk/riven_cursors.h"
+#include "common/macresman.h"
+#include "common/ne_exe.h"
#include "common/system.h"
#include "graphics/cursorman.h"
namespace Mohawk {
+static const byte s_bwPalette[] = {
+ 0x00, 0x00, 0x00, 0x00, // Black
+ 0xFF, 0xFF, 0xFF, 0x00 // White
+};
+
void CursorManager::showCursor() {
CursorMan.showMouse(true);
}
@@ -43,6 +50,76 @@ void CursorManager::hideCursor() {
CursorMan.showMouse(false);
}
+void CursorManager::setDefaultCursor() {
+ static const byte defaultCursor[] = {
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0,
+ 1, 2, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0,
+ 1, 2, 2, 2, 2, 2, 1, 0, 0, 0, 0, 0,
+ 1, 2, 2, 2, 2, 2, 2, 1, 0, 0, 0, 0,
+ 1, 2, 2, 2, 2, 2, 2, 2, 1, 0, 0, 0,
+ 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 0, 0,
+ 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 0,
+ 1, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1,
+ 1, 2, 2, 2, 1, 2, 2, 1, 0, 0, 0, 0,
+ 1, 2, 2, 1, 1, 2, 2, 1, 0, 0, 0, 0,
+ 1, 2, 1, 0, 1, 1, 2, 2, 1, 0, 0, 0,
+ 1, 1, 0, 0, 0, 1, 2, 2, 1, 0, 0, 0,
+ 1, 0, 0, 0, 0, 0, 1, 2, 2, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 2, 2, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0
+ };
+
+ CursorMan.replaceCursor(defaultCursor, 12, 20, 0, 0, 0);
+ CursorMan.replaceCursorPalette(s_bwPalette, 1, 2);
+}
+
+void CursorManager::setCursor(uint16 id) {
+ // For the base class, just use the default cursor always
+ setDefaultCursor();
+}
+
+void CursorManager::decodeMacXorCursor(Common::SeekableReadStream *stream, byte *cursor) {
+ assert(stream);
+ assert(cursor);
+
+ // Get black and white data
+ for (int i = 0; i < 32; i++) {
+ byte imageByte = stream->readByte();
+ for (int b = 0; b < 8; b++)
+ cursor[i * 8 + b] = (imageByte & (0x80 >> b)) ? 1 : 2;
+ }
+
+ // Apply mask data
+ for (int i = 0; i < 32; i++) {
+ byte imageByte = stream->readByte();
+ for (int b = 0; b < 8; b++)
+ if ((imageByte & (0x80 >> b)) == 0)
+ cursor[i * 8 + b] = 0;
+ }
+}
+
+void DefaultCursorManager::setCursor(uint16 id) {
+ // The Broderbund devs decided to rip off the Mac format, it seems.
+ // However, they reversed the x/y hotspot. That makes it totally different!!!!
+
+ Common::SeekableReadStream *stream = _vm->getResource(ID_TCUR, id);
+
+ byte cursorBitmap[16 * 16];
+ decodeMacXorCursor(stream, cursorBitmap);
+ uint16 hotspotY = stream->readUint16BE();
+ uint16 hotspotX = stream->readUint16BE();
+
+ CursorMan.replaceCursor(cursorBitmap, 16, 16, hotspotX, hotspotY, 0);
+ CursorMan.replaceCursorPalette(s_bwPalette, 1, 2);
+
+ delete stream;
+}
+
MystCursorManager::MystCursorManager(MohawkEngine_Myst *vm) : _vm(vm) {
_bmpDecoder = new MystBitmap();
}
@@ -83,6 +160,10 @@ void MystCursorManager::setCursor(uint16 id) {
delete mhkSurface;
}
+void MystCursorManager::setDefaultCursor() {
+ setCursor(kDefaultMystCursor);
+}
+
void RivenCursorManager::setCursor(uint16 id) {
// All of Riven's cursors are hardcoded. See riven_cursors.h for these definitions.
@@ -192,4 +273,78 @@ void RivenCursorManager::setCursor(uint16 id) {
g_system->updateScreen();
}
+void RivenCursorManager::setDefaultCursor() {
+ setCursor(kRivenMainCursor);
+}
+
+NECursorManager::NECursorManager(const Common::String &appName) {
+ _exe = new Common::NEResources();
+
+ if (!_exe->loadFromEXE(appName)) {
+ // Not all have cursors anyway, so this is not a problem
+ delete _exe;
+ _exe = 0;
+ }
+}
+
+NECursorManager::~NECursorManager() {
+ delete _exe;
+}
+
+void NECursorManager::setCursor(uint16 id) {
+ if (!_exe) {
+ Common::Array<Common::NECursorGroup> cursors = _exe->getCursors();
+
+ for (uint32 i = 0; i < cursors.size(); i++) {
+ if (cursors[i].id == id) {
+ Common::NECursor *cursor = cursors[i].cursors[0];
+ CursorMan.replaceCursor(cursor->getSurface(), cursor->getWidth(), cursor->getHeight(), cursor->getHotspotX(), cursor->getHotspotY(), 0);
+ CursorMan.replaceCursorPalette(cursor->getPalette(), 0, 256);
+ return;
+ }
+ }
+ }
+
+ // Last resort (not all have cursors)
+ setDefaultCursor();
+}
+
+MacCursorManager::MacCursorManager(const Common::String &appName) {
+ _resFork = new Common::MacResManager();
+
+ if (!_resFork->open(appName)) {
+ // Not all have cursors anyway, so this is not a problem
+ delete _resFork;
+ _resFork = 0;
+ }
+}
+
+MacCursorManager::~MacCursorManager() {
+ delete _resFork;
+}
+
+void MacCursorManager::setCursor(uint16 id) {
+ if (!_resFork) {
+ setDefaultCursor();
+ return;
+ }
+
+ Common::SeekableReadStream *stream = _resFork->getResource(MKID_BE('CURS'), id);
+
+ if (!stream) {
+ setDefaultCursor();
+ return;
+ }
+
+ byte cursorBitmap[16 * 16];
+ decodeMacXorCursor(stream, cursorBitmap);
+ uint16 hotspotX = stream->readUint16BE();
+ uint16 hotspotY = stream->readUint16BE();
+
+ CursorMan.replaceCursor(cursorBitmap, 16, 16, hotspotX, hotspotY, 0);
+ CursorMan.replaceCursorPalette(s_bwPalette, 1, 2);
+
+ delete stream;
+}
+
} // End of namespace Mohawk
diff --git a/engines/mohawk/cursors.h b/engines/mohawk/cursors.h
index 90858a2421..7ff99a342f 100644
--- a/engines/mohawk/cursors.h
+++ b/engines/mohawk/cursors.h
@@ -28,6 +28,13 @@
#include "common/scummsys.h"
+namespace Common {
+ class MacResManager;
+ class NEResources;
+ class SeekableReadStream;
+ class String;
+}
+
namespace Mohawk {
// 803-805 are animated, one large bmp which is in chunks - these are NEVER USED
@@ -57,6 +64,7 @@ enum {
kRivenHideCursor = 9000
};
+class MohawkEngine;
class MohawkEngine_Myst;
class MystBitmap;
@@ -67,9 +75,29 @@ public:
virtual void showCursor();
virtual void hideCursor();
- virtual void setCursor(uint16 id) = 0;
+ virtual void setCursor(uint16 id);
+ virtual void setDefaultCursor();
+
+protected:
+ // Handles the Mac version of the xor/and map cursor
+ void decodeMacXorCursor(Common::SeekableReadStream *stream, byte *cursor);
+};
+
+// The default Mohawk cursor manager
+// Uses standard tCUR resources
+class DefaultCursorManager : public CursorManager {
+public:
+ DefaultCursorManager(MohawkEngine *vm) : _vm(vm) {}
+ ~DefaultCursorManager() {}
+
+ void setCursor(uint16 id);
+
+private:
+ MohawkEngine *_vm;
};
+// The cursor manager for Myst
+// Uses WDIB + CLRC resources
class MystCursorManager : public CursorManager {
public:
MystCursorManager(MohawkEngine_Myst *vm);
@@ -78,18 +106,47 @@ public:
void showCursor();
void hideCursor();
void setCursor(uint16 id);
+ void setDefaultCursor();
private:
MohawkEngine_Myst *_vm;
MystBitmap *_bmpDecoder;
};
+
+// The cursor manager for Riven
+// Uses hardcoded cursors
class RivenCursorManager : public CursorManager {
public:
RivenCursorManager() {}
~RivenCursorManager() {}
void setCursor(uint16 id);
+ void setDefaultCursor();
+};
+
+// The cursor manager for NE exe's
+class NECursorManager : public CursorManager {
+public:
+ NECursorManager(const Common::String &appName);
+ ~NECursorManager();
+
+ void setCursor(uint16 id);
+
+private:
+ Common::NEResources *_exe;
+};
+
+// The cursor manager for Mac applications
+class MacCursorManager : public CursorManager {
+public:
+ MacCursorManager(const Common::String &appName);
+ ~MacCursorManager();
+
+ void setCursor(uint16 id);
+
+private:
+ Common::MacResManager *_resFork;
};
} // End of namespace Mohawk
diff --git a/engines/mohawk/detection_tables.h b/engines/mohawk/detection_tables.h
index fb086d5d93..48d565055c 100644
--- a/engines/mohawk/detection_tables.h
+++ b/engines/mohawk/detection_tables.h
@@ -605,6 +605,21 @@ static const MohawkGameDescription gameDescriptions[] = {
{
{
"tortoise",
+ "",
+ AD_ENTRY1("TORTOISE.512", "dfcf7bff3d0f187832c9897497efde0e"),
+ Common::EN_ANY,
+ Common::kPlatformWindows,
+ ADGF_NO_FLAGS,
+ Common::GUIO_NONE
+ },
+ GType_LIVINGBOOKSV1,
+ 0,
+ "TORTOISE.EXE"
+ },
+
+ {
+ {
+ "tortoise",
"Demo v1.0",
AD_ENTRY1("TORTOISE.512", "75d9a2f8339e423604a0c6e8177600a6"),
Common::EN_ANY,
@@ -658,7 +673,7 @@ static const MohawkGameDescription gameDescriptions[] = {
Common::GUIO_NONE
},
GType_LIVINGBOOKSV1,
- 0,
+ GF_NO_READONLY,
"ARTHUR.EXE" // FIXME: Check this (ST?)
},
@@ -673,7 +688,7 @@ static const MohawkGameDescription gameDescriptions[] = {
Common::GUIO_NONE
},
GType_LIVINGBOOKSV1,
- GF_DEMO,
+ GF_DEMO | GF_NO_READONLY,
"ARTHUR.EXE"
},
@@ -733,7 +748,7 @@ static const MohawkGameDescription gameDescriptions[] = {
Common::GUIO_NONE
},
GType_LIVINGBOOKSV1,
- GF_DEMO,
+ GF_DEMO | GF_NO_READONLY,
"GRANDMA.EXE"
},
@@ -763,7 +778,7 @@ static const MohawkGameDescription gameDescriptions[] = {
Common::GUIO_NONE
},
GType_LIVINGBOOKSV1,
- GF_DEMO,
+ GF_DEMO | GF_NO_READONLY,
"Just Grandma and Me"
},
diff --git a/engines/mohawk/graphics.cpp b/engines/mohawk/graphics.cpp
index 65eebf7134..8da0cd07cb 100644
--- a/engines/mohawk/graphics.cpp
+++ b/engines/mohawk/graphics.cpp
@@ -654,8 +654,10 @@ void RivenGraphics::drawExtrasImage(uint16 id, Common::Rect dstRect) {
_dirtyScreen = true;
}
-LBGraphics::LBGraphics(MohawkEngine_LivingBooks *vm) : GraphicsManager(), _vm(vm) {
+LBGraphics::LBGraphics(MohawkEngine_LivingBooks *vm, uint16 width, uint16 height) : GraphicsManager(), _vm(vm) {
_bmpDecoder = (_vm->getGameType() == GType_LIVINGBOOKSV1) ? new OldMohawkBitmap() : new MohawkBitmap();
+
+ initGraphics(width, height, true);
}
LBGraphics::~LBGraphics() {
@@ -669,15 +671,82 @@ MohawkSurface *LBGraphics::decodeImage(uint16 id) {
return _bmpDecoder->decodeImage(_vm->getResource(ID_TBMP, id));
}
-void LBGraphics::copyImageToScreen(uint16 image, uint16 left, uint16 top) {
- Graphics::Surface *surface = findImage(image)->getSurface();
+void LBGraphics::preloadImage(uint16 image) {
+ findImage(image);
+}
- uint16 width = MIN<int>(surface->w, _vm->_system->getWidth());
- uint16 height = MIN<int>(surface->h, _vm->_system->getHeight());
- _vm->_system->copyRectToScreen((byte *)surface->pixels, surface->pitch, left, top, width, height);
+void LBGraphics::copyImageToScreen(uint16 image, bool useOffsets, int left, int top) {
+ MohawkSurface *mhkSurface = findImage(image);
- // FIXME: Remove this and update only when necessary
- _vm->_system->updateScreen();
+ if (useOffsets) {
+ left -= mhkSurface->getOffsetX();
+ top -= mhkSurface->getOffsetY();
+ }
+
+ uint16 startX = 0;
+ uint16 startY = 0;
+
+ // TODO: clip rect
+ if (left < 0) {
+ startX -= left;
+ left = 0;
+ }
+
+ if (top < 0) {
+ startY -= top;
+ top = 0;
+ }
+
+ if (left >= _vm->_system->getWidth())
+ return;
+ if (top >= _vm->_system->getHeight())
+ return;
+
+ Graphics::Surface *surface = mhkSurface->getSurface();
+ if (startX >= surface->w)
+ return;
+ if (startY >= surface->h)
+ return;
+
+ uint16 width = MIN<int>(surface->w - startX, _vm->_system->getWidth() - left);
+ uint16 height = MIN<int>(surface->h - startY, _vm->_system->getHeight() - top);
+
+ byte *surf = (byte *)surface->getBasePtr(0, startY);
+ Graphics::Surface *screen = _vm->_system->lockScreen();
+
+ // image and screen are always 8bpp for LB
+ for (uint16 y = 0; y < height; y++) {
+ byte *dest = (byte *)screen->getBasePtr(left, top + y);
+ byte *src = surf + startX;
+ // blit, with 0 being transparent
+ for (uint16 x = 0; x < width; x++) {
+ if (*src)
+ *dest = *src;
+ src++;
+ dest++;
+ }
+ surf += surface->pitch;
+ }
+
+ _vm->_system->unlockScreen();
+}
+
+bool LBGraphics::imageIsTransparentAt(uint16 image, bool useOffsets, int x, int y) {
+ MohawkSurface *mhkSurface = findImage(image);
+
+ if (useOffsets) {
+ x += mhkSurface->getOffsetX();
+ y += mhkSurface->getOffsetY();
+ }
+
+ if (x < 0 || y < 0)
+ return true;
+
+ Graphics::Surface *surface = mhkSurface->getSurface();
+ if (x >= surface->w || y >= surface->h)
+ return true;
+
+ return *(byte *)surface->getBasePtr(x, y) == 0;
}
void LBGraphics::setPalette(uint16 id) {
diff --git a/engines/mohawk/graphics.h b/engines/mohawk/graphics.h
index 38d174b481..71bdf2f4a7 100644
--- a/engines/mohawk/graphics.h
+++ b/engines/mohawk/graphics.h
@@ -196,11 +196,13 @@ private:
class LBGraphics : public GraphicsManager {
public:
- LBGraphics(MohawkEngine_LivingBooks *vm);
+ LBGraphics(MohawkEngine_LivingBooks *vm, uint16 width, uint16 height);
~LBGraphics();
- void copyImageToScreen(uint16 image, uint16 left = 0, uint16 top = 0);
+ void preloadImage(uint16 image);
+ void copyImageToScreen(uint16 image, bool useOffsets = false, int left = 0, int top = 0);
void setPalette(uint16 id);
+ bool imageIsTransparentAt(uint16 image, bool useOffsets, int x, int y);
protected:
MohawkSurface *decodeImage(uint16 id);
diff --git a/engines/mohawk/livingbooks.cpp b/engines/mohawk/livingbooks.cpp
index 0ce8135508..fa3db1d316 100644
--- a/engines/mohawk/livingbooks.cpp
+++ b/engines/mohawk/livingbooks.cpp
@@ -25,21 +25,66 @@
#include "mohawk/livingbooks.h"
#include "mohawk/resource.h"
+#include "mohawk/cursors.h"
#include "common/events.h"
+#include "common/EventRecorder.h"
#include "engines/util.h"
namespace Mohawk {
+// read a null-terminated string from a stream
+static Common::String readString(Common::SeekableSubReadStreamEndian *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::SeekableSubReadStreamEndian *stream) {
+ Common::Rect rect;
+
+ // the V1 mac games have their rects in QuickDraw order
+ if (getGameType() == GType_LIVINGBOOKSV1 && 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;
+}
+
MohawkEngine_LivingBooks::MohawkEngine_LivingBooks(OSystem *syst, const MohawkGameDescription *gamedesc) : MohawkEngine(syst, gamedesc) {
_needsUpdate = false;
+ _needsRedraw = false;
_screenWidth = _screenHeight = 0;
+
+ _curLanguage = 1;
+
+ _alreadyShowedIntro = false;
+
+ _rnd = new Common::RandomSource();
+ g_eventRec.registerRandomSource(*_rnd, "livingbooks");
}
MohawkEngine_LivingBooks::~MohawkEngine_LivingBooks() {
+ destroyPage();
+
delete _console;
delete _gfx;
+ delete _rnd;
_bookInfoFile.clear();
}
@@ -47,8 +92,6 @@ Common::Error MohawkEngine_LivingBooks::run() {
MohawkEngine::run();
_console = new LivingBooksConsole(this);
- _gfx = new LBGraphics(this);
-
// Load the book info from the detected file
loadBookInfo(getBookInfoFileName());
@@ -60,33 +103,43 @@ Common::Error MohawkEngine_LivingBooks::run() {
if (!_screenWidth || !_screenHeight)
error("Could not find xRes/yRes variables");
- debug("Setting screen size to %dx%d", _screenWidth, _screenHeight);
+ _gfx = new LBGraphics(this, _screenWidth, _screenHeight);
- // TODO: Eventually move this to a LivingBooksGraphics class or similar
- initGraphics(_screenWidth, _screenHeight, true);
+ if (getPlatform() == Common::kPlatformMacintosh)
+ _cursor = new MacCursorManager(getAppName());
+ else
+ _cursor = new NECursorManager(getAppName());
- loadIntro();
+ _cursor->setDefaultCursor();
+ _cursor->showCursor();
- debug(1, "Stack Version: %d", getResourceVersion());
-
- _gfx->setPalette(1000);
- loadSHP(1000);
- loadANI(1000);
-
- // Code to Load Sounds For Debugging...
- //for (byte i = 0; i < 30; i++)
- // _sound->playSound(1000+i);
+ if (!loadPage(kLBIntroMode, 1, 0))
+ 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 (uint16 i = 0; i < _items.size(); i++)
+ if (_items[i]->contains(event.mouse))
+ found = _items[i];
+
+ if (found)
+ found->handleMouseDown(event.mouse);
break;
+
case Common::EVENT_KEYDOWN:
switch (event.kbd.keycode) {
case Common::KEYCODE_d:
@@ -95,18 +148,32 @@ Common::Error MohawkEngine_LivingBooks::run() {
_console->onFrame();
}
break;
+
case Common::KEYCODE_SPACE:
pauseGame();
break;
+
+ case Common::KEYCODE_ESCAPE:
+ if (_curMode == kLBIntroMode)
+ loadPage(kLBControlMode, 1, 0);
+ break;
+
+ case Common::KEYCODE_RIGHT:
+ nextPage();
+ break;
+
default:
break;
}
break;
+
default:
break;
}
}
+ updatePage();
+
if (_needsUpdate) {
_system->updateScreen();
_needsUpdate = false;
@@ -114,6 +181,12 @@ Common::Error MohawkEngine_LivingBooks::run() {
// Cut down on CPU usage
_system->delayMillis(10);
+
+ // handle pending notifications
+ while (_notifyEvents.size()) {
+ NotifyEvent notifyEvent = _notifyEvents.pop();
+ handleNotify(notifyEvent);
+ }
}
return Common::kNoError;
@@ -132,6 +205,9 @@ void MohawkEngine_LivingBooks::loadBookInfo(const Common::String &filename) {
_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
+ _poetryMode = (getIntFromConfig("BookInfo", "poetry") == 1);
+
// The later Living Books games add some more options:
// - fNeedPalette (always true?)
// - fUse254ColorPalette (always true?)
@@ -139,132 +215,314 @@ void MohawkEngine_LivingBooks::loadBookInfo(const Common::String &filename) {
// - fDebugWindow (always 0?)
}
-void MohawkEngine_LivingBooks::loadIntro() {
- Common::String filename;
+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);
+ }
+}
- // We get to try for a few different names! Yay!
- filename = getFileNameFromConfig("Intro", "Page1");
+void MohawkEngine_LivingBooks::destroyPage() {
+ _sound->stopSound();
+ _gfx->clearCache();
- // Some store with .r, not sure why.
- if (filename.empty())
- filename = getFileNameFromConfig("Intro", "Page1.r");
+ _eventQueue.clear();
- if (!filename.empty()) {
- MohawkArchive *introArchive = createMohawkArchive();
- if (introArchive->open(filename))
- _mhk.push_back(introArchive);
- else
- delete introArchive;
+ for (uint32 i = 0; i < _items.size(); i++)
+ delete _items[i];
+ _items.clear();
+
+ for (uint32 i = 0; i < _mhk.size(); i++)
+ delete _mhk[i];
+ _mhk.clear();
+
+ _notifyEvents.clear();
+
+ _focus = NULL;
+}
+
+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;
+
+ filename = getFileNameFromConfig(name, base);
+ _readOnly = false;
+
+ if (filename.empty()) {
+ filename = getFileNameFromConfig(name, base + ".r");
+ _readOnly = true;
}
- filename = getFileNameFromConfig("Intro", "Page2");
+ if (getFeatures() & GF_NO_READONLY) {
+ if (_readOnly) {
+ // TODO: make this a warning, after some testing?
+ error("game detection table is bad (remove GF_NO_READONLY)");
+ } 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);
+ }
+ }
- if (filename.empty())
- filename = getFileNameFromConfig("Intro", "Page2.r");
+ // TODO: fading between pages
+ bool fade = false;
+ if (filename.hasSuffix(" fade")) {
+ fade = true;
+ filename = Common::String(filename.c_str(), filename.size() - 5);
+ }
- if (!filename.empty()) {
- MohawkArchive *coverArchive = createMohawkArchive();
- if (coverArchive->open(filename))
- _mhk.push_back(coverArchive);
- else
- delete coverArchive;
+ MohawkArchive *pageArchive = createMohawkArchive();
+ if (!filename.empty() && pageArchive->open(filename)) {
+ _mhk.push_back(pageArchive);
+ } else {
+ delete pageArchive;
+ debug(2, "Could not find page %d.%d for '%s'", page, subpage, name.c_str());
+ return false;
}
-}
-// Only 1 VSRN resource per stack, Id 1000
-uint16 MohawkEngine_LivingBooks::getResourceVersion() {
- Common::SeekableReadStream *versionStream = getResource(ID_VRSN, 1000);
+ debug(1, "Stack Version: %d", getResourceVersion());
- if (versionStream->size() != 2)
- warning("Version Record size mismatch");
+ _curMode = mode;
+ _curPage = page;
+ _curSubPage = subpage;
- uint16 version = versionStream->readUint16BE();
+ _cursor->showCursor();
- delete versionStream;
- return version;
+ _gfx->setPalette(1000);
+ loadBITL(1000);
+
+ for (uint32 i = 0; i < _items.size(); i++)
+ _items[i]->init();
+
+ _phase = 0;
+ _introDone = false;
+
+ _needsRedraw = true;
+
+ return true;
}
-// Multiple SHP# resource per stack.. Optional per Card?
-// This record appears to be a list structure of BMAP resource Ids..
-void MohawkEngine_LivingBooks::loadSHP(uint16 resourceId) {
- Common::SeekableSubReadStreamEndian *shpStream = wrapStreamEndian(ID_SHP, resourceId);
+void MohawkEngine_LivingBooks::updatePage() {
+ switch (_phase) {
+ case 0:
+ for (uint32 i = 0; i < _items.size(); i++)
+ _items[i]->startPhase(_phase);
+
+ if (_curMode == kLBControlMode) {
+ // hard-coded control page startup
+ LBItem *item;
+
+ item = getItemById(10);
+ if (item)
+ item->togglePlaying(false);
+
+ switch (_curPage) {
+ case 1:
+ debug(2, "updatePage() for control page 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);
+ }
- if (shpStream->size() < 6)
- warning("SHP Record size too short");
+ item = getItemById(12);
+ if (item)
+ item->setVisible(false);
- if (shpStream->readUint16() != 3)
- warning("SHP Record u0 not 3");
+ 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;
- if (shpStream->readUint16() != 0)
- warning("SHP Record u1 not 0");
+ case 2:
+ debug(2, "updatePage() for control page 2");
- uint16 idCount = shpStream->readUint16();
- debug(1, "SHP: idCount: %d", idCount);
+ item = getItemById(12);
+ if (item)
+ item->setVisible(false);
+ item = getItemById(13);
+ if (item)
+ item->setVisible(false);
+ break;
+
+ case 3:
+ // TODO: hard-coded handling
+ break;
+ }
+ }
+ _phase++;
+ break;
- if (shpStream->size() != (idCount * 2) + 6)
- warning("SHP Record size mismatch");
+ case 1:
+ for (uint32 i = 0; i < _items.size(); i++)
+ _items[i]->startPhase(_phase);
- uint16 *idValues = new uint16[idCount];
- for (uint16 i = 0; i < idCount; i++) {
- idValues[i] = shpStream->readUint16();
- debug(1, "SHP: BMAP Resource Id %d: %d", i, idValues[i]);
+ _phase++;
+ break;
+
+ case 2:
+ if (!_introDone)
+ break;
+
+ for (uint32 i = 0; i < _items.size(); i++)
+ _items[i]->startPhase(_phase);
+
+ _phase++;
+ break;
}
- delete[] idValues;
- delete shpStream;
+ 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 kLBDestroy:
+ _items.remove_at(i);
+ delete delayedEvent.item;
+ if (_focus == delayedEvent.item)
+ _focus = NULL;
+ break;
+ case kLBSetNotVisible:
+ _items[i]->setVisible(false);
+ break;
+ case kLBDone:
+ _items[i]->done(true);
+ break;
+ }
+
+ break;
+ }
+ }
+
+ for (uint16 i = 0; i < _items.size(); i++)
+ _items[i]->update();
+
+ if (_needsRedraw) {
+ for (uint16 i = 0; i < _items.size(); i++)
+ _items[i]->draw();
+
+ _needsUpdate = true;
+ }
}
-// Multiple ANI resource per stack.. Optional per Card?
-void MohawkEngine_LivingBooks::loadANI(uint16 resourceId) {
- Common::SeekableSubReadStreamEndian *aniStream = wrapStreamEndian(ID_ANI, resourceId);
+LBItem *MohawkEngine_LivingBooks::getItemById(uint16 id) {
+ for (uint16 i = 0; i < _items.size(); i++)
+ if (_items[i]->getId() == id)
+ return _items[i];
- if (aniStream->size() != 30)
- warning("ANI Record size mismatch");
+ return NULL;
+}
- if (aniStream->readUint16() != 1)
- warning("ANI Record u0 not 0"); // Version?
-
- uint16 u1 = aniStream->readUint16();
- debug(1, "ANI u1: %d", u1);
-
- uint16 u2 = aniStream->readUint16();
- debug(1, "ANI u2: %d", u2);
-
- Common::Rect u3;
- u3.right = aniStream->readUint16();
- u3.bottom = aniStream->readUint16();
- u3.left = aniStream->readUint16();
- u3.top = aniStream->readUint16();
- debug(1, "ANI u3: (%d, %d), (%d, %d)", u3.left, u3.top, u3.right, u3.bottom);
-
- Common::Rect u4;
- u4.right = aniStream->readUint16();
- u4.bottom = aniStream->readUint16();
- u4.left = aniStream->readUint16();
- u4.top = aniStream->readUint16();
- debug(1, "ANI u4: (%d, %d), (%d, %d)", u4.left, u4.top, u4.right, u4.bottom);
-
- // BMAP Id?
- uint16 u4ResourceId = aniStream->readUint16();
- debug(1, "ANI u4ResourceId: %d", u4ResourceId);
-
- // Following 3 unknowns also resourceIds in Unused?
- uint16 u5 = aniStream->readUint16();
- debug(1, "ANI u5: %d", u5);
- if (u5 != 0)
- warning("ANI u5 non-zero");
-
- uint16 u6 = aniStream->readUint16();
- debug(1, "ANI u6: %d", u6);
- if (u6 != 0)
- warning("ANI u6 non-zero");
-
- uint16 u7 = aniStream->readUint16();
- debug(1, "ANI u7: %d", u7);
- if (u7 != 0)
- warning("ANI u7 non-zero");
+void MohawkEngine_LivingBooks::setFocus(LBItem *focus) {
+ _focus = focus;
+}
- delete aniStream;
+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);
+}
+
+// Only 1 VSRN resource per stack, Id 1000
+uint16 MohawkEngine_LivingBooks::getResourceVersion() {
+ Common::SeekableReadStream *versionStream = getResource(ID_VRSN, 1000);
+
+ if (versionStream->size() != 2)
+ warning("Version Record size mismatch");
+
+ uint16 version = versionStream->readUint16BE();
+
+ delete versionStream;
+ return version;
+}
+
+void MohawkEngine_LivingBooks::loadBITL(uint16 resourceId) {
+ Common::SeekableSubReadStreamEndian *bitlStream = wrapStreamEndian(ID_BITL, resourceId);
+
+ while (true) {
+ Common::Rect rect = readRect(bitlStream);
+ uint16 type = bitlStream->readUint16();
+
+ LBItem *res;
+ switch (type) {
+ case kLBPictureItem:
+ res = new LBPictureItem(this, rect);
+ break;
+ case kLBAnimationItem:
+ res = new LBAnimationItem(this, rect);
+ break;
+ case kLBPaletteItem:
+ res = new LBPaletteItem(this, rect);
+ break;
+ case kLBGroupItem:
+ res = new LBGroupItem(this, rect);
+ break;
+ case kLBSoundItem:
+ res = new LBSoundItem(this, rect);
+ break;
+ case kLBLiveTextItem:
+ res = new LBLiveTextItem(this, rect);
+ break;
+ default:
+ warning("Unknown item type %04x", type);
+ res = new LBItem(this, rect);
+ break;
+ }
+
+ res->readFrom(bitlStream);
+ _items.push_back(res);
+
+ if (bitlStream->size() == bitlStream->pos())
+ break;
+ }
}
Common::SeekableSubReadStreamEndian *MohawkEngine_LivingBooks::wrapStreamEndian(uint32 tag, uint16 id) {
@@ -288,9 +546,8 @@ Common::String MohawkEngine_LivingBooks::getFileNameFromConfig(const Common::Str
}
Common::String MohawkEngine_LivingBooks::removeQuotesFromString(const Common::String &string) {
- // The last char isn't necessarily a quote, the line could have "fade" in it,
- // most likely representing to fade to that page. Hopefully it really is that
- // obvious :P
+ // The last char isn't necessarily a quote, the line could have "fade" in it
+ // (which is then handled in loadPage).
// Some versions wrap in quotations, some don't...
Common::String tmp = string;
@@ -307,7 +564,9 @@ Common::String MohawkEngine_LivingBooks::removeQuotesFromString(const Common::St
Common::String MohawkEngine_LivingBooks::convertMacFileName(const Common::String &string) {
Common::String filename;
- for (uint32 i = 1; i < string.size(); i++) { // First character should be ignored (another colon)
+ for (uint32 i = 0; i < string.size(); i++) {
+ if (i == 0 && string[i] == ':') // First character should be ignored (another colon)
+ continue;
if (string[i] == ':')
filename += '/';
else
@@ -334,4 +593,1612 @@ MohawkArchive *MohawkEngine_LivingBooks::createMohawkArchive() const {
return (getGameType() == GType_LIVINGBOOKSV1) ? new LivingBooksArchive_v1() : new MohawkArchive();
}
+void MohawkEngine_LivingBooks::addNotifyEvent(NotifyEvent event) {
+ _notifyEvents.push(event);
+}
+
+void MohawkEngine_LivingBooks::nextPage() {
+ // we try the next subpage first
+ if (loadPage(_curMode, _curPage, _curSubPage + 1))
+ return;
+
+ // then the first subpage of the next page
+ if (loadPage(_curMode, _curPage + 1, 1))
+ return;
+
+ // then just the next page
+ if (loadPage(_curMode, _curPage + 1, 0))
+ return;
+
+ // then we go back to the control page..
+ if (loadPage(kLBControlMode, 1, 0))
+ return;
+
+ error("Could not find page after %d.%d for mode %d", _curPage, _curSubPage, (int)_curMode);
+}
+
+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.
+
+ LBItem *item;
+ switch (_curPage) {
+ case 1:
+ // main menu
+ // TODO: poetry mode
+
+ switch (event.param) {
+ case 1:
+ // TODO: page 2 in some versions?
+ loadPage(kLBControlMode, 3, 0);
+ 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(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(true);
+ }
+ break;
+
+ case 4:
+ // TODO: page 3 in some versions?
+ loadPage(kLBControlMode, 2, 0);
+ break;
+
+ case 10:
+ item = getItemById(10);
+ if (item)
+ item->destroySelf();
+ item = getItemById(11);
+ if (item)
+ item->setVisible(true);
+ if (item)
+ item->togglePlaying(false);
+ break;
+
+ case 11:
+ item = getItemById(11);
+ if (item)
+ item->togglePlaying(true);
+ break;
+
+ case 12:
+ // start game, in play mode
+ loadPage(kLBPlayMode, 1, 0);
+ break;
+
+ default:
+ if (event.param >= 100 && event.param < 100 + (uint)_numLanguages) {
+ // TODO: language selection?
+ warning("no language selection yet");
+ } else if (event.param >= 200 && event.param < 200 + (uint)_numLanguages) {
+ // start game, in read mode
+ loadPage(kLBReadMode, 1, 0);
+ }
+ break;
+ }
+ break;
+
+ case 2:
+ // quit screen
+
+ switch (event.param) {
+ case 1:
+ case 2:
+ // button clicked, run animation
+ item = getItemById(10);
+ if (item)
+ item->destroySelf();
+ item = getItemById(11);
+ if (item)
+ item->destroySelf();
+ item = getItemById((event.param == 1) ? 12 : 13);
+ if (item) {
+ item->setVisible(true);
+ item->togglePlaying(false);
+ }
+ break;
+
+ case 10:
+ case 11:
+ item = getItemById(11);
+ if (item)
+ item->togglePlaying(true);
+ break;
+
+ case 12:
+ // 'yes', I want to quit
+ quitGame();
+ break;
+
+ case 13:
+ // 'no', go back to menu
+ loadPage(kLBControlMode, 1, 0);
+ break;
+ }
+ break;
+
+ case 3:
+ // options screen
+
+ switch (event.param) {
+ case 1:
+ item = getItemById(10);
+ if (item)
+ item->destroySelf();
+ item = getItemById(202);
+ if (item) {
+ item->setVisible(true);
+ item->togglePlaying(true);
+ }
+ break;
+
+ case 2:
+ item = getItemById(2);
+ if (item)
+ item->seek(1);
+ // TODO: book seeking
+ break;
+
+ case 3:
+ item = getItemById(3);
+ if (item)
+ item->seek(1);
+ // TODO: book seeking
+ break;
+
+ case 4:
+ loadPage(kLBCreditsMode, 1, 0);
+ break;
+
+ case 5:
+ loadPage(kLBPreviewMode, 1, 0);
+ break;
+
+ case 202:
+ // TODO: loadPage(kLBPlayMode, book);
+ break;
+ }
+ break;
+ }
+ break;
+
+ case kLBNotifyGoToControls:
+ debug(2, "kLBNotifyGoToControls: %d", event.param);
+
+ if (!loadPage(kLBControlMode, 1, 0))
+ error("couldn't load controls page");
+ break;
+
+ case kLBNotifyChangePage:
+ {
+ debug("kLBNotifyChangePage: %d", event.param);
+
+ switch (event.param) {
+ case 0xfffe:
+ nextPage();
+ return;
+
+ case 0xffff:
+ warning("ChangePage unimplemented");
+ // TODO: move backwards one page
+ break;
+
+ default:
+ warning("ChangePage unimplemented");
+ // TODO: set page as specified
+ break;
+ }
+
+ // TODO: on bad page:
+ // if mode < 3 (intro/controls) or mode > 4, move to 2/2 (controls/options?)
+ // else, move to 2/1 (kLBControlsMode, page 1)
+ }
+ 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 kLBNotifyQuit:
+ debug(2, "kLBNotifyQuit: %d", event.param);
+
+ quitGame();
+ break;
+
+ case kLBNotifyCursorChange:
+ debug(2, "kLBNotifyCursorChange: %d", event.param);
+
+ // TODO: show/hide cursor according to parameter?
+ 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) {
+ _currentCel = 0;
+
+ 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->getGameType() != GType_LIVINGBOOKSV1) {
+ Common::Point offset = _parent->getOffset(_currentCel - 1);
+ xOffset -= offset.x;
+ yOffset -= offset.y;
+ }
+
+ _vm->_gfx->copyImageToScreen(resourceId, true, xOffset, yOffset);
+}
+
+void LBAnimationNode::reset() {
+ // TODO: this causes stupid flickering
+ //if (_currentCel)
+ // _vm->_needsRedraw = true;
+
+ _currentCel = 0;
+ _currentEntry = 0;
+
+ _xPos = 0;
+ _yPos = 0;
+}
+
+NodeState LBAnimationNode::update(bool seeking) {
+ if (_currentEntry == _scriptEntries.size())
+ return kLBNodeDone;
+
+ 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;
+ }
+
+ assert(entry.size == 4);
+ uint16 strLen = READ_BE_UINT16(entry.data + 2);
+
+ if (strLen)
+ error("String length for unnamed wave file");
+
+ switch (entry.opcode) {
+ case kLBAnimOpPlaySound:
+ if (seeking)
+ break;
+ debug(4, "a: PlaySound(%0d)", soundResourceId);
+ _vm->_sound->playSound(soundResourceId);
+ break;
+ case kLBAnimOpWaitForSound:
+ if (seeking)
+ break;
+ debug(4, "b: WaitForSound(%0d)", soundResourceId);
+ if (!_vm->_sound->isPlaying(soundResourceId))
+ 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;
+ }
+ }
+ break;
+
+ case kLBAnimOpSetTempo:
+ case kLBAnimOpUnknownE: // TODO: complete guesswork, not in 1.x
+ {
+ assert(entry.size == 2);
+ uint16 tempo = (int16)READ_BE_UINT16(entry.data);
+
+ debug(4, "3: SetTempo(%d)", tempo);
+ if (entry.opcode == kLBAnimOpUnknownE) {
+ debug(4, "(beware, stupid OpUnknownE guesswork)");
+ }
+
+ _parent->setTempo(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;
+
+ 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->getGameType() != GType_LIVINGBOOKSV1) {
+ 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]));
+
+ _running = false;
+ _done = false;
+ _tempo = 1;
+}
+
+LBAnimation::~LBAnimation() {
+ for (uint32 i = 0; i < _nodes.size(); i++)
+ delete _nodes[i];
+}
+
+void LBAnimation::loadShape(uint16 resourceId) {
+ if (resourceId == 0)
+ return;
+
+ Common::SeekableSubReadStreamEndian *shapeStream = _vm->wrapStreamEndian(ID_SHP, resourceId);
+
+ if (_vm->getGameType() == GType_LIVINGBOOKSV1) {
+ 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);
+}
+
+void LBAnimation::update() {
+ if (!_running)
+ return;
+
+ if (_vm->_system->getMillis() / 16 <= _lastTime + (uint32)_tempo)
+ return;
+
+ // the second check is to try 'catching up' with lagged animations, might be crazy
+ if (_lastTime == 0 || (_vm->_system->getMillis() / 16) > _lastTime + (uint32)(_tempo * 2))
+ _lastTime = _vm->_system->getMillis() / 16;
+ else
+ _lastTime += _tempo;
+
+ 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) {
+ _running = false;
+ _done = true;
+ }
+}
+
+void LBAnimation::start() {
+ _lastTime = 0;
+ _currentFrame = 0;
+ _running = true;
+ _done = false;
+
+ for (uint32 i = 0; i < _nodes.size(); i++)
+ _nodes[i]->reset();
+}
+
+void LBAnimation::seek(uint16 pos) {
+ start();
+
+ 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::stop() {
+ _running = false;
+ _done = false;
+}
+
+bool LBAnimation::wasDone() {
+ if (!_done)
+ return false;
+
+ _done = 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() {
+ argvParam = NULL;
+ argvTarget = NULL;
+}
+
+LBScriptEntry::~LBScriptEntry() {
+ delete[] argvParam;
+ delete[] argvTarget;
+}
+
+LBItem::LBItem(MohawkEngine_LivingBooks *vm, Common::Rect rect) : _vm(vm), _rect(rect) {
+ _phase = 0;
+ _timingMode = 0;
+ _delayMin = 0;
+ _delayMax = 0;
+ _loopMode = 0;
+ _loopCount = 0;
+ _periodMin = 0;
+ _periodMax = 0;
+ _controlMode = 0;
+
+ _neverEnabled = true;
+ _enabled = false;
+ _visible = true;
+ _playing = false;
+ _nextTime = 0;
+ _startTime = 0;
+ _loops = 0;
+}
+
+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 = 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) {
+ uint16 dataType = stream->readUint16();
+ uint16 dataSize = stream->readUint16();
+
+ debug(4, "Data type %04x, size %d", dataType, dataSize);
+ readData(dataType, dataSize, stream);
+
+ if (stream->pos() == endPos)
+ break;
+
+ if (stream->pos() > endPos)
+ error("Read off the end (at %d) of data (ends at %d)", stream->pos(), endPos);
+
+ assert(!stream->eos());
+ }
+}
+
+void LBItem::readData(uint16 type, uint16 size, Common::SeekableSubReadStreamEndian *stream) {
+ switch (type) {
+ case kLBMsgListScript:
+ case kLBNotifyScript:
+ {
+ if (size < 6)
+ error("Script entry of type 0x%04x was too small (%d)", type, size);
+
+ LBScriptEntry *entry = new LBScriptEntry;
+ entry->type = type;
+ entry->action = stream->readUint16();
+ entry->opcode = stream->readUint16();
+ entry->param = stream->readUint16();
+ debug(4, "Script entry: type 0x%04x, action 0x%04x, opcode 0x%04x, param 0x%04x",
+ entry->type, entry->action, entry->opcode, entry->param);
+
+ if (type == kLBMsgListScript) {
+ if (size < 8)
+ error("Script entry of type 0x%04x was too small (%d)", type, size);
+
+ entry->argc = stream->readUint16();
+ entry->argvParam = new uint16[entry->argc];
+ entry->argvTarget = new uint16[entry->argc];
+ debug(4, "With %d targets:", entry->argc);
+
+ if (size < (8 + 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]);
+ }
+
+ if (size > (8 + entry->argc * 4)) {
+ // TODO
+ warning("Skipping %d probably-important bytes", size - (8 + entry->argc * 4));
+ stream->skip(size - (8 + entry->argc * 4));
+ }
+ } else {
+ if (size > 6) {
+ // TODO
+ warning("Skipping %d probably-important bytes", size - 6);
+ stream->skip(size - 6);
+ }
+ }
+
+ _scriptEntries.push_back(entry);
+ }
+ 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();
+ _periodMin = stream->readUint16();
+ _periodMax = stream->readUint16();
+ _relocPoint.x = stream->readSint16();
+ _relocPoint.y = stream->readSint16();
+ _controlMode = stream->readUint16();
+ uint16 unknown10 = stream->readUint16();
+ // TODO: unknowns
+
+ debug(2, "kLBSetPlayInfo: loop mode %d (%d to %d), timing mode %d (%d to %d), reloc (%d, %d), unknowns %04x, %04x",
+ _loopMode, _delayMin, _delayMax,
+ _timingMode, _periodMin, _periodMax,
+ _relocPoint.x, _relocPoint.y,
+ _controlMode, unknown10);
+ }
+ break;
+
+ case kLBSetPlayPhase:
+ if (size != 2)
+ error("SetPlayPhase had wrong size (%d)", size);
+ _phase = stream->readUint16();
+ break;
+
+ case 0x70:
+ debug(2, "LBItem: 0x70");
+ // TODO
+ break;
+
+ case 0x7b:
+ assert(size == 0);
+ debug(2, "LBItem: 0x7b");
+ // TODO
+ break;
+
+ case 0x69:
+ // TODO: ??
+ case 0x6a:
+ // TODO: ??
+ case 0x6d:
+ // TODO: one-shot?
+ default:
+ for (uint i = 0; i < size; i++)
+ debugN("%02x ", stream->readByte());
+ warning("Unknown message %04x (size 0x%04x)", type, size);
+ break;
+ }
+}
+
+void LBItem::destroySelf() {
+ if (!this->_itemId)
+ error("destroySelf() on an item which was already dead");
+
+ _vm->queueDelayedEvent(DelayedEvent(this, kLBDestroy));
+
+ _itemId = 0;
+}
+
+void LBItem::setEnabled(bool enabled) {
+ if (enabled && _neverEnabled && !_playing) {
+ if (_timingMode == 2) {
+ setNextTime(_periodMin, _periodMax);
+ debug(2, "Enable time startup");
+ }
+ }
+
+ _neverEnabled = false;
+ _enabled = enabled;
+}
+
+bool LBItem::contains(Common::Point point) {
+ if (_playing && _loopMode == 0xFFFF)
+ stop();
+
+ if (!_playing && _timingMode == 2)
+ setNextTime(_periodMin, _periodMax);
+
+ return _visible && _rect.contains(point);
+}
+
+void LBItem::update() {
+ if (_neverEnabled || !_enabled)
+ return;
+
+ if (_nextTime == 0 || _nextTime > (uint32)(_vm->_system->getMillis() / 16))
+ return;
+
+ if (togglePlaying(_playing)) {
+ _nextTime = 0;
+ } else if (_loops == 0 && _timingMode == 2) {
+ debug(9, "Looping in update()");
+ setNextTime(_periodMin, _periodMax);
+ }
+}
+
+void LBItem::handleMouseDown(Common::Point pos) {
+ if (_neverEnabled || !_enabled)
+ return;
+
+ _vm->setFocus(this);
+ runScript(kLBActionMouseDown);
+}
+
+void LBItem::handleMouseMove(Common::Point pos) {
+ // TODO: handle drag
+}
+
+void LBItem::handleMouseUp(Common::Point pos) {
+ _vm->setFocus(NULL);
+ runScript(kLBActionMouseUp);
+}
+
+bool LBItem::togglePlaying(bool playing) {
+ if (playing) {
+ _vm->queueDelayedEvent(DelayedEvent(this, kLBDone));
+ return true;
+ }
+ if (!_neverEnabled && _enabled && !_playing) {
+ _playing = togglePlaying(true);
+ if (_playing) {
+ seek(1); // TODO: this is not good in many situations
+ _nextTime = 0;
+ _startTime = _vm->_system->getMillis() / 16;
+
+ if (_loopMode == 0xFFFF || _loopMode == 0xFFFE)
+ _loops = 0xFFFF;
+ else
+ _loops = _loopMode;
+
+ if (_controlMode >= 1) {
+ debug(2, "Hiding cursor");
+ _vm->_cursor->hideCursor();
+ // TODO: lock sound?
+
+ if (_controlMode >= 2) {
+ debug(2, "Disabling all");
+ _vm->setEnableForAll(false, this);
+ }
+ }
+
+ runScript(kLBActionStarted);
+ 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 >= 1) {
+ debug(2, "Showing cursor");
+ _vm->_cursor->showCursor();
+ // TODO: unlock sound?
+
+ if (_controlMode >= 2) {
+ debug(2, "Enabling all");
+ _vm->setEnableForAll(true, this);
+ }
+ }
+
+ if (_timingMode == 2) {
+ debug(9, "Looping in done() - %d to %d", _periodMin, _periodMax);
+ setNextTime(_periodMin, _periodMax);
+ }
+
+ runScript(kLBActionDone);
+ notify(0xFFFF, _itemId);
+}
+
+void LBItem::setVisible(bool visible) {
+ if (visible == _visible)
+ return;
+
+ _visible = visible;
+ _vm->_needsRedraw = true;
+}
+
+void LBItem::startPhase(uint phase) {
+ if (_phase == phase)
+ setEnabled(true);
+
+ switch (phase) {
+ case 0:
+ runScript(kLBActionPhase0);
+ break;
+ case 1:
+ runScript(kLBActionPhase1);
+ if (_timingMode == 1 || _timingMode == 2) {
+ debug(2, "Phase 1 time startup");
+ setNextTime(_periodMin, _periodMax);
+ }
+ break;
+ case 2:
+ runScript(kLBActionPhase2);
+ if (_timingMode == 2 || _timingMode == 3) {
+ debug(2, "Phase 2 time startup");
+ setNextTime(_periodMin, _periodMax);
+ }
+ break;
+ }
+}
+
+void LBItem::stop() {
+ if (!_playing)
+ return;
+
+ _loops = 0;
+ seek(0xFFFF);
+ done(true);
+}
+
+void LBItem::notify(uint16 data, uint16 from) {
+ if (_timingMode != 4)
+ return;
+
+ // TODO: is this correct?
+ if (_periodMin != from)
+ return;
+ if (_periodMax != data)
+ return;
+
+ debug(2, "Handling notify 0x%04x (from %d)", data, from);
+ setNextTime(0, 0);
+}
+
+void LBItem::runScript(uint id) {
+ for (uint i = 0; i < _scriptEntries.size(); i++) {
+ LBScriptEntry *entry = _scriptEntries[i];
+ if (entry->action != id)
+ continue;
+
+ if (entry->type == kLBNotifyScript) {
+ if (entry->opcode == kLBNotifyGUIAction)
+ _vm->addNotifyEvent(NotifyEvent(entry->opcode, _itemId));
+ else
+ _vm->addNotifyEvent(NotifyEvent(entry->opcode, entry->param));
+ } else {
+ if (entry->param != 0xffff) {
+ // TODO: if param is 1/2/3..
+ warning("Ignoring script entry (type 0x%04x, action 0x%04x, opcode 0x%04x, param 0x%04x)",
+ entry->type, entry->action, entry->opcode, entry->param);
+ continue;
+ }
+
+ for (uint n = 0; n < entry->argc; n++) {
+ uint16 targetId = entry->argvTarget[n];
+ // TODO: is this type, perhaps?
+ uint16 param = entry->argvParam[n];
+ LBItem *target = _vm->getItemById(targetId);
+
+ debug(2, "Script run: type 0x%04x, action 0x%04x, opcode 0x%04x, param 0x%04x, target id %d",
+ entry->type, entry->action, entry->opcode, entry->param, targetId);
+
+ if (!target)
+ continue;
+
+ switch (entry->opcode) {
+ case 1:
+ // TODO: should be setVisible(true) - not a delayed event -
+ // when we're doing the param 1/2/3 stuff above?
+ _vm->queueDelayedEvent(DelayedEvent(this, kLBSetNotVisible));
+ break;
+
+ case 2:
+ target->togglePlaying(false);
+ break;
+
+ case 3:
+ target->setVisible(false);
+ break;
+
+ case 4:
+ target->setVisible(true);
+ break;
+
+ case 5:
+ target->destroySelf();
+ break;
+
+ case 6:
+ target->seek(1);
+ break;
+
+ case 7:
+ target->stop();
+ break;
+
+ case 8:
+ target->setEnabled(false);
+ break;
+
+ case 9:
+ target->setEnabled(true);
+ break;
+
+ case 0xf: // apply palette? seen in greeneggs
+ default:
+ // TODO
+ warning("Ignoring script entry (type 0x%04x, action 0x%04x, opcode 0x%04x, param 0x%04x) for %d (param %04x)",
+ entry->type, entry->action, entry->opcode, entry->param, targetId, param);
+ }
+ }
+ }
+ }
+}
+
+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));
+}
+
+LBSoundItem::LBSoundItem(MohawkEngine_LivingBooks *vm, Common::Rect rect) : LBItem(vm, rect) {
+ debug(3, "new LBSoundItem");
+}
+
+LBSoundItem::~LBSoundItem() {
+ _vm->_sound->stopSound(_resourceId);
+}
+
+bool LBSoundItem::togglePlaying(bool playing) {
+ if (!playing)
+ return LBItem::togglePlaying(playing);
+
+ _vm->_sound->stopSound(_resourceId);
+
+ if (_neverEnabled || !_enabled)
+ return false;
+
+ _vm->_sound->playSound(_resourceId, Audio::Mixer::kMaxChannelVolume, false);
+ return true;
+}
+
+void LBSoundItem::stop() {
+ _vm->_sound->stopSound(_resourceId);
+
+ LBItem::stop();
+}
+
+LBGroupItem::LBGroupItem(MohawkEngine_LivingBooks *vm, Common::Rect rect) : LBItem(vm, rect) {
+ debug(3, "new LBGroupItem");
+ _starting = false;
+}
+
+void LBGroupItem::readData(uint16 type, uint16 size, Common::SeekableSubReadStreamEndian *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::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);
+ }
+ }
+}
+
+bool LBGroupItem::contains(Common::Point point) {
+ return false;
+}
+
+bool LBGroupItem::togglePlaying(bool playing) {
+ for (uint i = 0; i < _groupEntries.size(); i++) {
+ LBItem *item = _vm->getItemById(_groupEntries[i].entryId);
+ if (item)
+ item->togglePlaying(playing);
+ }
+
+ 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::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();
+ }
+}
+
+LBPaletteItem::LBPaletteItem(MohawkEngine_LivingBooks *vm, Common::Rect rect) : LBItem(vm, rect) {
+ debug(3, "new LBPaletteItem");
+}
+
+void LBPaletteItem::readData(uint16 type, uint16 size, Common::SeekableSubReadStreamEndian *stream) {
+ switch (type) {
+ case 0x72:
+ {
+ assert(size == 4 + 256 * 4);
+ // TODO
+ _start = stream->readUint16();
+ _count = stream->readUint16();
+ stream->read(_palette, 256 * 4);
+ }
+ break;
+
+ case 0x75:
+ assert(size == 0);
+ debug(2, "LBPaletteItem: 0x75");
+ // TODO
+ break;
+
+ default:
+ LBItem::readData(type, size, stream);
+ }
+}
+
+void LBPaletteItem::startPhase(uint phase) {
+ //if (_phase != phase)
+ // return;
+
+ /*printf("palette: start %d, count %d\n", _start, _count);
+ byte *localpal = _palette;
+ for (unsigned int i = 0; i < 256 * 4; i++) {
+ printf("%02x ", *localpal++);
+ }
+ printf("\n");*/
+
+ // TODO: huh?
+ if (_start != 1)
+ return;
+
+ // TODO
+ //_vm->_system->setPalette(_start - 1, _count - (_start - 1), _palette + (_start * 4));
+ _vm->_system->setPalette(_palette + _start * 4, 0, 256 - _start);
+}
+
+LBLiveTextItem::LBLiveTextItem(MohawkEngine_LivingBooks *vm, Common::Rect rect) : LBItem(vm, rect) {
+ _running = false;
+ debug(3, "new LBLiveTextItem");
+}
+
+void LBLiveTextItem::readData(uint16 type, uint16 size, Common::SeekableSubReadStreamEndian *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();
+ // TODO: unknowns
+ uint16 unknown1 = stream->readUint16();
+ uint16 unknown2 = stream->readUint16();
+ debug(4, "Word: (%d, %d) to (%d, %d), sound %d, unknowns %04x, %04x",
+ word.bounds.left, word.bounds.top, word.bounds.right, word.bounds.bottom, word.soundId, unknown1, unknown2);
+ _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);
+ }
+}
+
+void LBLiveTextItem::notify(uint16 data, uint16 from) {
+ if (!_paletteIndex) {
+ // TODO
+ warning("Zero palette-index for LiveText; V3 game?");
+ return;
+ }
+
+ for (uint i = 0; i < _phrases.size(); i++) {
+ // TODO
+ if (_phrases[i].highlightStart == data && _phrases[i].startId == from) {
+ debug(2, "Enabling phrase %d", i);
+ for (uint j = 0; j < _phrases[i].wordCount; j++) {
+ uint n = _phrases[i].wordStart + j;
+ _vm->_system->setPalette(_highlightColor, _paletteIndex + n, 1);
+ }
+ } 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++) {
+ uint n = _phrases[i].wordStart + j;
+ _vm->_system->setPalette(_foregroundColor, _paletteIndex + n, 1);
+ }
+ }
+ }
+
+ LBItem::notify(data, from);
+}
+
+LBPictureItem::LBPictureItem(MohawkEngine_LivingBooks *vm, Common::Rect rect) : LBItem(vm, rect) {
+ debug(3, "new LBPictureItem");
+}
+
+void LBPictureItem::readData(uint16 type, uint16 size, Common::SeekableSubReadStreamEndian *stream) {
+ switch (type) {
+ case 0x6b:
+ {
+ 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: 0x6b: %04x", val);
+ }
+ break;
+
+ default:
+ LBItem::readData(type, size, stream);
+ }
+}
+
+bool LBPictureItem::contains(Common::Point point) {
+ if (!LBItem::contains(point))
+ return false;
+
+ // 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);
+}
+
+void LBPictureItem::draw() {
+ if (!_visible)
+ return;
+
+ _vm->_gfx->copyImageToScreen(_resourceId, false, _rect.left, _rect.top);
+}
+
+LBAnimationItem::LBAnimationItem(MohawkEngine_LivingBooks *vm, Common::Rect rect) : LBItem(vm, rect) {
+ _anim = NULL;
+ _running = false;
+ debug(3, "new LBAnimationItem");
+}
+
+LBAnimationItem::~LBAnimationItem() {
+ // TODO: handle this properly
+ if (_running)
+ _vm->_sound->stopSound();
+
+ delete _anim;
+}
+
+void LBAnimationItem::setEnabled(bool enabled) {
+ if (_running) {
+ if (enabled && _neverEnabled)
+ _anim->start();
+ else if (!_neverEnabled && !enabled && _enabled)
+ if (_running) {
+ _anim->stop();
+
+ // TODO: handle this properly
+ _vm->_sound->stopSound();
+ }
+ }
+
+ return LBItem::setEnabled(enabled);
+}
+
+bool LBAnimationItem::contains(Common::Point point) {
+ return LBItem::contains(point) && !_anim->transparentAt(point.x, point.y);
+}
+
+void LBAnimationItem::update() {
+ if (!_neverEnabled && _enabled && _running) {
+ _anim->update();
+ }
+
+ LBItem::update();
+
+ // TODO: where exactly does this go?
+ // TODO: what checks should we have around this?
+ if (!_neverEnabled && _enabled && _running && _anim->wasDone()) {
+ done(true);
+ }
+}
+
+bool LBAnimationItem::togglePlaying(bool playing) {
+ if (playing) {
+ if (!_neverEnabled && _enabled) {
+ _running = true;
+ _anim->start();
+ }
+
+ return _running;
+ }
+
+ return LBItem::togglePlaying(playing);
+}
+
+void LBAnimationItem::done(bool onlyNotify) {
+ if (!onlyNotify) {
+ _anim->stop();
+ }
+
+ LBItem::done(onlyNotify);
+}
+
+void LBAnimationItem::init() {
+ _anim = new LBAnimation(_vm, this, _resourceId);
+}
+
+void LBAnimationItem::stop() {
+ if (_running) {
+ _anim->stop();
+ seek(0xFFFF);
+ }
+
+ // TODO: handle this properly
+ _vm->_sound->stopSound();
+
+ _running = false;
+
+ LBItem::stop();
+}
+
+void LBAnimationItem::seek(uint16 pos) {
+ _anim->seek(pos);
+}
+
+void LBAnimationItem::startPhase(uint phase) {
+ if (phase == _phase)
+ seek(1);
+
+ LBItem::startPhase(phase);
+}
+
+void LBAnimationItem::draw() {
+ if (!_visible)
+ return;
+
+ _anim->draw();
+}
+
} // End of namespace Mohawk
diff --git a/engines/mohawk/livingbooks.h b/engines/mohawk/livingbooks.h
index 0305c92c6a..0ad99e00df 100644
--- a/engines/mohawk/livingbooks.h
+++ b/engines/mohawk/livingbooks.h
@@ -32,14 +32,355 @@
#include "common/config-file.h"
#include "common/substream.h"
+#include "common/rect.h"
+#include "common/queue.h"
+#include "common/random.h"
+
+#include "sound/mixer.h"
namespace Mohawk {
+enum NodeState {
+ kLBNodeDone = 0,
+ kLBNodeRunning = 1,
+ kLBNodeWaiting = 2
+};
+
+enum LBMode {
+ kLBIntroMode = 1,
+ kLBControlMode = 2,
+ kLBCreditsMode = 3,
+ kLBPreviewMode = 4,
+ kLBReadMode = 5,
+ kLBPlayMode = 6
+};
+
+enum {
+ kLBStaticTextItem = 0x1,
+ kLBPictureItem = 0x2,
+ kLBEditTextItem = 0x14,
+ kLBLiveTextItem = 0x15,
+ kLBAnimationItem = 0x40,
+ kLBSoundItem = 0x41,
+ kLBGroupItem = 0x42,
+ kLBPaletteItem = 0x45 // v3
+};
+
enum {
- kIntroPage = 0
+ // no 0x1?
+ kLBAnimOpNotify = 0x2,
+ kLBAnimOpSetTempo = 0x3,
+ // no 0x4?
+ kLBAnimOpMoveTo = 0x5,
+ kLBAnimOpWait = 0x6,
+ kLBAnimOpSetCel = 0x7,
+ kLBAnimOpSleepUntil = 0x8,
+ kLBAnimOpDrawMode = 0x9,
+ kLBAnimOpPlaySound = 0xa,
+ kLBAnimOpWaitForSound = 0xb,
+ kLBAnimOpReleaseSound = 0xc,
+ kLBAnimOpResetSound = 0xd,
+ kLBAnimOpUnknownE = 0xe
};
+enum {
+ kLBActionPhase0 = 0,
+ kLBActionPhase1 = 1,
+ kLBActionMouseDown = 2,
+ kLBActionStarted = 3,
+ kLBActionDone = 4,
+ kLBActionMouseUp = 5,
+ kLBActionPhase2 = 6
+};
+
+enum {
+ kLBGroupData = 0x64,
+ kLBLiveTextData = 0x65,
+ kLBMsgListScript = 0x66,
+ kLBNotifyScript = 0x67,
+ kLBSetPlayInfo = 0x68,
+ kLBSetPlayPhase = 0x6e
+};
+
+enum {
+ kLBNotifyGUIAction = 1,
+ kLBNotifyGoToControls = 2,
+ kLBNotifyChangePage = 3,
+ kLBNotifyIntroDone = 5,
+ kLBNotifyQuit = 6,
+ kLBNotifyCursorChange = 7
+};
+
+class MohawkEngine_LivingBooks;
class LBGraphics;
+class LBAnimation;
+
+struct LBScriptEntry {
+ LBScriptEntry();
+ ~LBScriptEntry();
+
+ uint16 type;
+ uint16 action;
+ uint16 opcode;
+ uint16 param;
+ uint16 argc;
+ uint16 *argvParam;
+ uint16 *argvTarget;
+};
+
+struct LBAnimScriptEntry {
+ byte opcode;
+ byte size;
+ byte *data;
+};
+
+class LBAnimationNode {
+public:
+ LBAnimationNode(MohawkEngine_LivingBooks *vm, LBAnimation *parent, uint16 scriptResourceId);
+ ~LBAnimationNode();
+
+ void draw(const Common::Rect &_bounds);
+ void reset();
+ NodeState update(bool seeking = false);
+ bool transparentAt(int x, int y);
+
+protected:
+ MohawkEngine_LivingBooks *_vm;
+ LBAnimation *_parent;
+
+ void loadScript(uint16 resourceId);
+ uint _currentEntry;
+ Common::Array<LBAnimScriptEntry> _scriptEntries;
+
+ uint _currentCel;
+ int16 _xPos, _yPos;
+};
+
+class LBAnimationItem;
+
+class LBAnimation {
+public:
+ LBAnimation(MohawkEngine_LivingBooks *vm, LBAnimationItem *parent, uint16 resourceId);
+ ~LBAnimation();
+
+ void draw();
+ void update();
+
+ void start();
+ void seek(uint16 pos);
+ void stop();
+
+ bool wasDone();
+ bool transparentAt(int x, int y);
+
+ void setTempo(uint16 tempo);
+
+ uint getNumResources() { return _shapeResources.size(); }
+ uint16 getResource(uint num) { return _shapeResources[num]; }
+ Common::Point getOffset(uint num) { return _shapeOffsets[num]; }
+
+ uint32 getCurrentFrame() { return _currentFrame; }
+
+ uint16 getParentId();
+
+protected:
+ MohawkEngine_LivingBooks *_vm;
+ LBAnimationItem *_parent;
+
+ Common::Rect _bounds, _clip;
+ Common::Array<LBAnimationNode *> _nodes;
+
+ uint16 _tempo;
+ uint32 _lastTime, _currentFrame;
+ bool _running, _done;
+
+ void loadShape(uint16 resourceId);
+ Common::Array<uint16> _shapeResources;
+ Common::Array<Common::Point> _shapeOffsets;
+};
+
+class LBItem {
+public:
+ LBItem(MohawkEngine_LivingBooks *vm, Common::Rect rect);
+ virtual ~LBItem();
+
+ void readFrom(Common::SeekableSubReadStreamEndian *stream);
+ virtual void readData(uint16 type, uint16 size, Common::SeekableSubReadStreamEndian *stream);
+
+ virtual void destroySelf(); // 0x2
+ virtual void setEnabled(bool enabled); // 0x3
+ virtual bool contains(Common::Point point); // 0x7
+ virtual void update(); // 0x8
+ virtual void draw() { } // 0x9
+ virtual void handleKeyChar(Common::Point pos) { } // 0xA
+ virtual void handleMouseDown(Common::Point pos); // 0xB
+ virtual void handleMouseMove(Common::Point pos); // 0xC
+ virtual void handleMouseUp(Common::Point pos); // 0xD
+ virtual bool togglePlaying(bool playing); // 0xF
+ virtual void done(bool onlyNotify); // 0x10
+ virtual void init() { } // 0x11
+ virtual void seek(uint16 pos) { } // 0x13
+ virtual void setFocused(bool focused) { } // 0x14
+ virtual void setVisible(bool visible); // 0x17
+ virtual void startPhase(uint phase); // 0x18
+ virtual void stop(); // 0x19
+ virtual void notify(uint16 data, uint16 from); // 0x1A
+
+ uint16 getId() { return _itemId; }
+
+protected:
+ MohawkEngine_LivingBooks *_vm;
+
+ void setNextTime(uint16 min, uint16 max);
+ void setNextTime(uint16 min, uint16 max, uint32 start);
+
+ Common::Rect _rect;
+ Common::String _desc;
+ uint16 _resourceId;
+ uint16 _itemId;
+
+ bool _visible, _playing, _enabled, _neverEnabled;
+
+ uint32 _nextTime, _startTime;
+ uint16 _loops;
+
+ uint16 _phase, _timingMode, _delayMin, _delayMax;
+ uint16 _loopMode, _loopCount, _periodMin, _periodMax;
+ uint16 _controlMode;
+ Common::Point _relocPoint;
+
+ Common::Array<LBScriptEntry *> _scriptEntries;
+ void runScript(uint id);
+};
+
+class LBSoundItem : public LBItem {
+public:
+ LBSoundItem(MohawkEngine_LivingBooks *_vm, Common::Rect rect);
+ ~LBSoundItem();
+
+ bool togglePlaying(bool playing);
+ void stop();
+};
+
+struct GroupEntry {
+ uint entryId;
+ uint entryType;
+};
+
+class LBGroupItem : public LBItem {
+public:
+ LBGroupItem(MohawkEngine_LivingBooks *_vm, Common::Rect rect);
+
+ void readData(uint16 type, uint16 size, Common::SeekableSubReadStreamEndian *stream);
+
+ void setEnabled(bool enabled);
+ bool contains(Common::Point point);
+ bool togglePlaying(bool playing);
+ // 0x12
+ void seek(uint16 pos);
+ void setVisible(bool visible);
+ void startPhase(uint phase);
+ void stop();
+
+protected:
+ bool _starting;
+
+ Common::Array<GroupEntry> _groupEntries;
+};
+
+class LBPaletteItem : public LBItem {
+public:
+ LBPaletteItem(MohawkEngine_LivingBooks *_vm, Common::Rect rect);
+
+ void readData(uint16 type, uint16 size, Common::SeekableSubReadStreamEndian *stream);
+
+ void startPhase(uint phase);
+
+protected:
+ uint16 _start, _count;
+ byte _palette[256 * 4];
+};
+
+struct LiveTextWord {
+ Common::Rect bounds;
+ uint16 soundId;
+};
+
+struct LiveTextPhrase {
+ uint16 wordStart, wordCount;
+ uint16 highlightStart, highlightEnd;
+ uint16 startId, endId;
+};
+
+class LBLiveTextItem : public LBItem {
+public:
+ LBLiveTextItem(MohawkEngine_LivingBooks *_vm, Common::Rect rect);
+
+ void readData(uint16 type, uint16 size, Common::SeekableSubReadStreamEndian *stream);
+
+ void notify(uint16 data, uint16 from);
+
+protected:
+ bool _running;
+
+ byte _backgroundColor[4];
+ byte _foregroundColor[4];
+ byte _highlightColor[4];
+ uint16 _paletteIndex;
+
+ Common::Array<LiveTextWord> _words;
+ Common::Array<LiveTextPhrase> _phrases;
+};
+
+class LBPictureItem : public LBItem {
+public:
+ LBPictureItem(MohawkEngine_LivingBooks *_vm, Common::Rect rect);
+
+ void readData(uint16 type, uint16 size, Common::SeekableSubReadStreamEndian *stream);
+
+ bool contains(Common::Point point);
+ void draw();
+ void init();
+};
+
+class LBAnimationItem : public LBItem {
+public:
+ LBAnimationItem(MohawkEngine_LivingBooks *_vm, Common::Rect rect);
+ ~LBAnimationItem();
+
+ void setEnabled(bool enabled);
+ bool contains(Common::Point point);
+ void update();
+ void draw();
+ bool togglePlaying(bool playing);
+ void done(bool onlyNotify);
+ void init();
+ void seek(uint16 pos);
+ void startPhase(uint phase);
+ void stop();
+
+protected:
+ LBAnimation *_anim;
+ bool _running;
+};
+
+struct NotifyEvent {
+ NotifyEvent(uint t, uint p) : type(t), param(p) { }
+ uint type;
+ uint param;
+};
+
+enum DelayedEventType {
+ kLBDestroy = 0,
+ kLBSetNotVisible = 1,
+ kLBDone = 2
+};
+
+struct DelayedEvent {
+ DelayedEvent(LBItem *i, DelayedEventType t) : item(i), type(t) { }
+ LBItem *item;
+ DelayedEventType type;
+};
class MohawkEngine_LivingBooks : public MohawkEngine {
protected:
@@ -49,24 +390,54 @@ public:
MohawkEngine_LivingBooks(OSystem *syst, const MohawkGameDescription *gamedesc);
virtual ~MohawkEngine_LivingBooks();
+ Common::RandomSource *_rnd;
+
LBGraphics *_gfx;
- bool _needsUpdate;
+ bool _needsRedraw, _needsUpdate;
+
+ void addNotifyEvent(NotifyEvent event);
Common::SeekableSubReadStreamEndian *wrapStreamEndian(uint32 tag, uint16 id);
+ Common::Rect readRect(Common::SeekableSubReadStreamEndian *stream);
GUI::Debugger *getDebugger() { return _console; }
+ LBItem *getItemById(uint16 id);
+
+ void setFocus(LBItem *focus);
+ void setEnableForAll(bool enable, LBItem *except = 0);
+ void notifyAll(uint16 data, uint16 from);
+ void queueDelayedEvent(DelayedEvent event);
+
+ bool isBigEndian() const { return getGameType() == GType_LIVINGBOOKSV3 || getPlatform() == Common::kPlatformMacintosh; }
+
private:
LivingBooksConsole *_console;
Common::ConfigFile _bookInfoFile;
- uint16 _curPage;
Common::String getBookInfoFileName() const;
void loadBookInfo(const Common::String &filename);
- void loadIntro();
+
+ Common::String stringForMode(LBMode mode);
+
+ bool _readOnly, _introDone;
+ LBMode _curMode;
+ uint16 _curPage, _curSubPage;
+ uint16 _phase;
+ Common::Array<LBItem *> _items;
+ Common::Queue<DelayedEvent> _eventQueue;
+ LBItem *_focus;
+ void destroyPage();
+ bool loadPage(LBMode mode, uint page, uint subpage);
+ void updatePage();
uint16 getResourceVersion();
+ void loadBITL(uint16 resourceId);
void loadSHP(uint16 resourceId);
- void loadANI(uint16 resourceId);
+
+ void nextPage();
+
+ Common::Queue<NotifyEvent> _notifyEvents;
+ void handleNotify(NotifyEvent &event);
uint16 _screenWidth;
uint16 _screenHeight;
@@ -74,6 +445,10 @@ private:
uint16 _numPages;
Common::String _title;
Common::String _copyright;
+ bool _poetryMode;
+
+ uint16 _curLanguage;
+ bool _alreadyShowedIntro;
// String Manipulation Functions
Common::String removeQuotesFromString(const Common::String &string);
@@ -86,7 +461,6 @@ private:
Common::String getFileNameFromConfig(const Common::String &section, const Common::String &key);
// Platform/Version functions
- bool isBigEndian() const { return getGameType() == GType_LIVINGBOOKSV3 || getPlatform() == Common::kPlatformMacintosh; }
MohawkArchive *createMohawkArchive() const;
};
diff --git a/engines/mohawk/mohawk.h b/engines/mohawk/mohawk.h
index ed59f727f3..a1027ce066 100644
--- a/engines/mohawk/mohawk.h
+++ b/engines/mohawk/mohawk.h
@@ -68,7 +68,8 @@ enum MohawkGameFeatures {
GF_ME = (1 << 0), // Myst Masterpiece Edition
GF_DVD = (1 << 1),
GF_DEMO = (1 << 2),
- GF_HASMIDI = (1 << 3)
+ GF_HASMIDI = (1 << 3),
+ GF_NO_READONLY = (1 << 4) // very early Living Books games
};
struct MohawkGameDescription;