diff options
Diffstat (limited to 'engines/mohawk/riven_graphics.cpp')
-rw-r--r-- | engines/mohawk/riven_graphics.cpp | 445 |
1 files changed, 445 insertions, 0 deletions
diff --git a/engines/mohawk/riven_graphics.cpp b/engines/mohawk/riven_graphics.cpp new file mode 100644 index 0000000000..9415e51412 --- /dev/null +++ b/engines/mohawk/riven_graphics.cpp @@ -0,0 +1,445 @@ +/* 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/resource.h" +#include "mohawk/riven.h" +#include "mohawk/riven_graphics.h" + +#include "common/system.h" +#include "engines/util.h" + +namespace Mohawk { + +RivenGraphics::RivenGraphics(MohawkEngine_Riven* vm) : GraphicsManager(), _vm(vm) { + _bitmapDecoder = new MohawkBitmap(); + + // Give me the best you've got! + initGraphics(608, 436, true, NULL); + _pixelFormat = _vm->_system->getScreenFormat(); + + if (_pixelFormat.bytesPerPixel == 1) + error("Riven requires greater than 256 colors to run"); + + // The actual game graphics only take up the first 392 rows. The inventory + // occupies the rest of the screen and we don't use the buffer to hold that. + _mainScreen = new Graphics::Surface(); + _mainScreen->create(608, 392, _pixelFormat); + + _updatesEnabled = true; + _scheduledTransition = -1; // no transition + _dirtyScreen = false; + _inventoryDrawn = false; + + _creditsImage = 302; + _creditsPos = 0; +} + +RivenGraphics::~RivenGraphics() { + _mainScreen->free(); + delete _mainScreen; + delete _bitmapDecoder; +} + +MohawkSurface *RivenGraphics::decodeImage(uint16 id) { + MohawkSurface *surface = _bitmapDecoder->decodeImage(_vm->getResource(ID_TBMP, id)); + surface->convertToTrueColor(); + return surface; +} + +void RivenGraphics::copyImageToScreen(uint16 image, uint32 left, uint32 top, uint32 right, uint32 bottom) { + Graphics::Surface *surface = findImage(image)->getSurface(); + + // Clip the width to fit on the screen. Fixes some images. + if (left + surface->w > 608) + surface->w = 608 - left; + + for (uint16 i = 0; i < surface->h; i++) + memcpy(_mainScreen->getBasePtr(left, i + top), surface->getBasePtr(0, i), surface->w * surface->format.bytesPerPixel); + + _dirtyScreen = true; +} + +void RivenGraphics::drawPLST(uint16 x) { + Common::SeekableReadStream* plst = _vm->getResource(ID_PLST, _vm->getCurCard()); + uint16 recordCount = plst->readUint16BE(); + + for (uint16 i = 0; i < recordCount; i++) { + uint16 index = plst->readUint16BE(); + uint16 id = plst->readUint16BE(); + uint16 left = plst->readUint16BE(); + uint16 top = plst->readUint16BE(); + uint16 right = plst->readUint16BE(); + uint16 bottom = plst->readUint16BE(); + + // We are also checking here to make sure we haven't drawn the image yet on screen. + // This fixes problems with drawing PLST 1 twice and some other images twice. PLST + // 1 is sometimes not called by the scripts, so some cards don't appear if we don't + // draw PLST 1 each time. This "hack" is here to catch any PLST attempting to draw + // twice. There should never be a problem with doing it this way. + if (index == x && !(Common::find(_activatedPLSTs.begin(), _activatedPLSTs.end(), x) != _activatedPLSTs.end())) { + debug(0, "Drawing image %d", id); + copyImageToScreen(id, left, top, right, bottom); + _activatedPLSTs.push_back(x); + break; + } + } + + delete plst; +} + +void RivenGraphics::updateScreen(Common::Rect updateRect) { + if (_updatesEnabled) { + _vm->runUpdateScreenScript(); + + if (_dirtyScreen) { + _activatedPLSTs.clear(); + + // Copy to screen if there's no transition. Otherwise transition. ;) + if (_scheduledTransition < 0) + _vm->_system->copyRectToScreen((byte *)_mainScreen->getBasePtr(updateRect.left, updateRect.top), _mainScreen->pitch, updateRect.left, updateRect.top, updateRect.width(), updateRect.height()); + else + runScheduledTransition(); + + // Finally, update the screen. + _vm->_system->updateScreen(); + _dirtyScreen = false; + } + } +} + +void RivenGraphics::scheduleWaterEffect(uint16 sfxeID) { + Common::SeekableReadStream *sfxeStream = _vm->getResource(ID_SFXE, sfxeID); + + if (sfxeStream->readUint16BE() != 'SL') + error ("Unknown sfxe tag"); + + // Read in header info + SFXERecord sfxeRecord; + sfxeRecord.frameCount = sfxeStream->readUint16BE(); + uint32 offsetTablePosition = sfxeStream->readUint32BE(); + sfxeRecord.rect.left = sfxeStream->readUint16BE(); + sfxeRecord.rect.top = sfxeStream->readUint16BE(); + sfxeRecord.rect.right = sfxeStream->readUint16BE(); + sfxeRecord.rect.bottom = sfxeStream->readUint16BE(); + sfxeRecord.speed = sfxeStream->readUint16BE(); + // Skip the rest of the fields... + + // Read in offsets + sfxeStream->seek(offsetTablePosition); + uint32 *frameOffsets = new uint32[sfxeRecord.frameCount]; + for (uint16 i = 0; i < sfxeRecord.frameCount; i++) + frameOffsets[i] = sfxeStream->readUint32BE(); + sfxeStream->seek(frameOffsets[0]); + + // Read in the scripts + for (uint16 i = 0; i < sfxeRecord.frameCount; i++) + sfxeRecord.frameScripts.push_back(sfxeStream->readStream((i == sfxeRecord.frameCount - 1) ? sfxeStream->size() - frameOffsets[i] : frameOffsets[i + 1] - frameOffsets[i])); + + // Set it to the first frame + sfxeRecord.curFrame = 0; + sfxeRecord.lastFrameTime = 0; + + delete[] frameOffsets; + delete sfxeStream; + _waterEffects.push_back(sfxeRecord); +} + +void RivenGraphics::clearWaterEffects() { + _waterEffects.clear(); +} + +bool RivenGraphics::runScheduledWaterEffects() { + // Don't run the effect if it's disabled + if (_vm->_vars["waterenabled"] == 0) + return false; + + Graphics::Surface *screen = NULL; + + for (uint16 i = 0; i < _waterEffects.size(); i++) { + if (_vm->_system->getMillis() > _waterEffects[i].lastFrameTime + 1000 / _waterEffects[i].speed) { + // Lock the screen! + if (!screen) + screen = _vm->_system->lockScreen(); + + // Make sure the script is at the starting point + Common::SeekableReadStream *script = _waterEffects[i].frameScripts[_waterEffects[i].curFrame]; + if (script->pos() != 0) + script->seek(0); + + // Run script + uint16 curRow = 0; + for (uint16 op = script->readUint16BE(); op != 4; op = script->readUint16BE()) { + if (op == 1) { // Increment Row + curRow++; + } else if (op == 3) { // Copy Pixels + uint16 dstLeft = script->readUint16BE(); + uint16 srcLeft = script->readUint16BE(); + uint16 srcTop = script->readUint16BE(); + uint16 rowWidth = script->readUint16BE(); + memcpy ((byte *)screen->getBasePtr(dstLeft, curRow + _waterEffects[i].rect.top), (byte *)_mainScreen->getBasePtr(srcLeft, srcTop), rowWidth * _pixelFormat.bytesPerPixel); + } else if (op != 4) { // End of Script + error ("Unknown SFXE opcode %d", op); + } + } + + // Increment frame + _waterEffects[i].curFrame++; + if (_waterEffects[i].curFrame == _waterEffects[i].frameCount) + _waterEffects[i].curFrame = 0; + + // Set the new time + _waterEffects[i].lastFrameTime = _vm->_system->getMillis(); + } + } + + // Unlock the screen if it has been locked and return true to update the screen + if (screen) { + _vm->_system->unlockScreen(); + return true; + } + + return false; +} + +void RivenGraphics::scheduleTransition(uint16 id, Common::Rect rect) { + _scheduledTransition = id; + _transitionRect = rect; +} + +void RivenGraphics::runScheduledTransition() { + if (_scheduledTransition < 0) // No transition is scheduled + return; + + // TODO: There's a lot to be done here... + + // Note: Transitions 0-11 are actual transitions, but none are used in-game. + // There's no point in implementing them if they're not used. These extra + // transitions were found by hacking scripts. + + switch (_scheduledTransition) { + case 0: // Swipe Left + case 1: // Swipe Right + case 2: // Swipe Up + case 3: // Swipe Down + case 12: // Pan Left + case 13: // Pan Right + case 14: // Pan Up + case 15: // Pan Down + case 16: // Dissolve + case 17: // Dissolve (tspit CARD 155) + break; + default: + if (_scheduledTransition >= 4 && _scheduledTransition <= 11) + error("Found unused transition %d", _scheduledTransition); + else + error("Found unknown transition %d", _scheduledTransition); + } + + // For now, just copy the image to screen without doing any transition. + _vm->_system->copyRectToScreen((byte *)_mainScreen->pixels, _mainScreen->pitch, 0, 0, _mainScreen->w, _mainScreen->h); + _vm->_system->updateScreen(); + + _scheduledTransition = -1; // Clear scheduled transition +} + +void RivenGraphics::clearMainScreen() { + _mainScreen->fillRect(Common::Rect(0, 0, 608, 392), _pixelFormat.RGBToColor(0, 0, 0)); +} + +void RivenGraphics::fadeToBlack() { + // The transition speed is forced to best here + setTransitionSpeed(kRivenTransitionSpeedBest); + scheduleTransition(16); + clearMainScreen(); + runScheduledTransition(); +} + +void RivenGraphics::showInventory() { + // Don't redraw the inventory + if (_inventoryDrawn) + return; + + // Clear the inventory area + clearInventoryArea(); + + // Draw the demo's exit button + if (_vm->getFeatures() & GF_DEMO) { + // extras.mhk tBMP 101 contains "EXIT" instead of Atrus' journal in the demo! + // The demo's extras.mhk contains all the other inventory/marble/credits image + // but has hacked tBMP 101 with "EXIT". *sigh* + drawInventoryImage(101, g_demoExitRect); + } else { + // We don't want to show the inventory on setup screens or in other journals. + if (_vm->getCurStack() == aspit) + return; + + // There are three books and three vars. We have three different + // combinations. At the start you have just Atrus' journal. Later, + // you get Catherine's journal and the trap book. Near the end, + // you lose the trap book and have just the two journals. + + bool hasCathBook = _vm->_vars["acathbook"] != 0; + bool hasTrapBook = _vm->_vars["atrapbook"] != 0; + + if (!hasCathBook) { + drawInventoryImage(101, g_atrusJournalRect1); + } else if (!hasTrapBook) { + drawInventoryImage(101, g_atrusJournalRect2); + drawInventoryImage(102, g_cathJournalRect2); + } else { + drawInventoryImage(101, g_atrusJournalRect3); + drawInventoryImage(102, g_cathJournalRect3); + drawInventoryImage(100, g_trapBookRect3); + } + } + + _vm->_system->updateScreen(); + _inventoryDrawn = true; +} + +void RivenGraphics::hideInventory() { + // Don't hide the inventory twice + if (!_inventoryDrawn) + return; + + // Clear the area + clearInventoryArea(); + + _inventoryDrawn = false; +} + +void RivenGraphics::clearInventoryArea() { + // Clear the inventory area + static const Common::Rect inventoryRect = Common::Rect(0, 392, 608, 436); + + // Lock the screen + Graphics::Surface *screen = _vm->_system->lockScreen(); + + // Fill the inventory area with black + screen->fillRect(inventoryRect, _pixelFormat.RGBToColor(0, 0, 0)); + + _vm->_system->unlockScreen(); +} + +void RivenGraphics::drawInventoryImage(uint16 id, const Common::Rect *rect) { + MohawkSurface *mhkSurface = _bitmapDecoder->decodeImage(_vm->getExtrasResource(ID_TBMP, id)); + mhkSurface->convertToTrueColor(); + Graphics::Surface *surface = mhkSurface->getSurface(); + + _vm->_system->copyRectToScreen((byte *)surface->pixels, surface->pitch, rect->left, rect->top, surface->w, surface->h); + + delete mhkSurface; +} + +void RivenGraphics::drawRect(Common::Rect rect, bool active) { + // Useful with debugging. Shows where hotspots are on the screen and whether or not they're active. + Graphics::Surface *screen = _vm->_system->lockScreen(); + + if (active) + screen->frameRect(rect, _pixelFormat.RGBToColor(0, 255, 0)); + else + screen->frameRect(rect, _pixelFormat.RGBToColor(255, 0, 0)); + + _vm->_system->unlockScreen(); +} + +void RivenGraphics::drawImageRect(uint16 id, Common::Rect srcRect, Common::Rect dstRect) { + // Draw tBMP id from srcRect to dstRect + Graphics::Surface *surface = findImage(id)->getSurface(); + + assert(srcRect.width() == dstRect.width() && srcRect.height() == dstRect.height()); + + for (uint16 i = 0; i < srcRect.height(); i++) + memcpy(_mainScreen->getBasePtr(dstRect.left, i + dstRect.top), surface->getBasePtr(srcRect.left, i + srcRect.top), srcRect.width() * surface->format.bytesPerPixel); + + _dirtyScreen = true; +} + +void RivenGraphics::drawExtrasImage(uint16 id, Common::Rect dstRect) { + MohawkSurface *mhkSurface = _bitmapDecoder->decodeImage(_vm->getExtrasResource(ID_TBMP, id)); + mhkSurface->convertToTrueColor(); + Graphics::Surface *surface = mhkSurface->getSurface(); + + assert(dstRect.width() == surface->w); + + for (uint16 i = 0; i < surface->h; i++) + memcpy(_mainScreen->getBasePtr(dstRect.left, i + dstRect.top), surface->getBasePtr(0, i), surface->pitch); + + delete mhkSurface; + _dirtyScreen = true; +} + +void RivenGraphics::beginCredits() { + // Clear the old cache + clearCache(); + + // Now cache all the credits images + for (uint16 i = 302; i <= 320; i++) { + MohawkSurface *surface = _bitmapDecoder->decodeImage(_vm->getExtrasResource(ID_TBMP, i)); + surface->convertToTrueColor(); + addImageToCache(i, surface); + } + + // And clear our screen too + clearMainScreen(); +} + +void RivenGraphics::updateCredits() { + if ((_creditsImage == 303 || _creditsImage == 304) && _creditsPos == 0) + fadeToBlack(); + + if (_creditsImage < 304) { + // For the first two credit images, they are faded from black to the image and then out again + scheduleTransition(16); + + Graphics::Surface *frame = findImage(_creditsImage++)->getSurface(); + + for (int y = 0; y < frame->h; y++) + memcpy(_mainScreen->getBasePtr(124, y), frame->getBasePtr(0, y), frame->pitch); + + runScheduledTransition(); + } else { + // Otheriwse, we're scrolling + // Move the screen up one row + memmove(_mainScreen->pixels, _mainScreen->getBasePtr(0, 1), _mainScreen->pitch * (_mainScreen->h - 1)); + + // Only update as long as we're not before the last frame + // Otherwise, we're just moving up a row (which we already did) + if (_creditsImage <= 320) { + // Copy the next row to the bottom of the screen + Graphics::Surface *frame = findImage(_creditsImage)->getSurface(); + memcpy(_mainScreen->getBasePtr(124, _mainScreen->h - 1), frame->getBasePtr(0, _creditsPos), frame->pitch); + _creditsPos++; + + if (_creditsPos == _mainScreen->h) { + _creditsImage++; + _creditsPos = 0; + } + } + + // Now flush the new screen + _vm->_system->copyRectToScreen((byte *)_mainScreen->pixels, _mainScreen->pitch, 0, 0, _mainScreen->w, _mainScreen->h); + _vm->_system->updateScreen(); + } +} + +} // End of namespace Mohawk |