From 5658e71f4d7c2245098316434db2f239871e9f05 Mon Sep 17 00:00:00 2001 From: Torbjörn Andersson Date: Sat, 8 Jul 2006 11:42:07 +0000 Subject: Added support for DXA cutscenes, while still retaining support for the old MPEG cutscenes and the "dummy" (subtitles and voice-over) mode. Several tweaks and cleanups were made in this process, and there may very well be regressions, but it should be stable enough to commit. svn-id: r23420 --- engines/sword2/animation.cpp | 958 +++++++++++++++++++++++++++---------------- engines/sword2/animation.h | 143 +++++-- engines/sword2/function.cpp | 14 +- engines/sword2/render.cpp | 31 -- engines/sword2/screen.h | 4 - 5 files changed, 731 insertions(+), 419 deletions(-) (limited to 'engines') diff --git a/engines/sword2/animation.cpp b/engines/sword2/animation.cpp index 50adeef69b..cd0baaa980 100644 --- a/engines/sword2/animation.cpp +++ b/engines/sword2/animation.cpp @@ -20,9 +20,10 @@ */ #include "common/stdafx.h" -#include "common/file.h" #include "common/config-manager.h" +#include "common/file.h" #include "common/system.h" + #include "sound/vorbis.h" #include "sound/mp3.h" @@ -30,6 +31,7 @@ #include "sword2/defs.h" #include "sword2/header.h" #include "sword2/maketext.h" +#include "sword2/mouse.h" #include "sword2/resman.h" #include "sword2/screen.h" #include "sword2/sound.h" @@ -37,94 +39,15 @@ namespace Sword2 { -AnimationState::AnimationState(Sword2Engine *vm) - : BaseAnimationState(vm->_mixer, vm->_system, 640, 480), _vm(vm) { -} - -AnimationState::~AnimationState() { -} - -#ifdef BACKEND_8BIT - -void AnimationState::setPalette(byte *pal) { - _vm->_screen->setPalette(0, 256, pal, RDPAL_INSTANT); -} - -#else - -void AnimationState::drawTextObject(SpriteInfo *s, byte *src) { - int moviePitch = _movieScale * _movieWidth; - int textX = _movieScale * s->x; - int textY = _movieScale * (_frameHeight - s->h - 12); - - OverlayColor *dst = _overlay + textY * moviePitch + textX; - - OverlayColor pen = _sys->RGBToColor(255, 255, 255); - OverlayColor border = _sys->RGBToColor(0, 0, 0); - - // TODO: Use the AdvMame scalers for the text? Pre-scale it? - - for (int y = 0; y < s->h; y++) { - OverlayColor *ptr = dst; - - for (int x = 0; x < s->w; x++) { - switch (src[x]) { - case 1: - *ptr++ = border; - if (_movieScale > 1) { - *ptr++ = border; - if (_movieScale > 2) - *ptr++ = border; - } - break; - case 255: - *ptr++ = pen; - if (_movieScale > 1) { - *ptr++ = pen; - if (_movieScale > 2) - *ptr++ = pen; - } - break; - default: - ptr += _movieScale; - break; - } - } - - if (_movieScale > 1) { - memcpy(dst + moviePitch, dst, _movieScale * s->w * sizeof(OverlayColor)); - if (_movieScale > 2) - memcpy(dst + 2 * moviePitch, dst, _movieScale * s->w * sizeof(OverlayColor)); - } +// TODO: The interaction between the basic cutscene player class and the +// specific plyers is sometimes a bit awkward, since our classes for +// DXA and MPEG decoding are so fundamentally different. The DXA decoder +// is just a decoder, while the MPEG decoder has delusions of being a +// player. This could probably be simplified quite a bit. - dst += _movieScale * moviePitch; - src += s->w; - } -} - -#endif - -void AnimationState::clearScreen() { -#ifdef BACKEND_8BIT - memset(_vm->_screen->getScreen(), 0, _movieWidth * _movieHeight); -#else - OverlayColor black = _sys->RGBToColor(0, 0, 0); - - for (int i = 0; i < _movieScale * _movieWidth * _movieScale * _movieHeight; i++) - _overlay[i] = black; -#endif -} - -void AnimationState::drawYUV(int width, int height, byte *const *dat) { - _frameWidth = width; - _frameHeight = height; - -#ifdef BACKEND_8BIT - _vm->_screen->plotYUV(_lut, width, height, dat); -#else - plotYUV(width, height, dat); -#endif -} +/////////////////////////////////////////////////////////////////////////////// +// Basic movie player +/////////////////////////////////////////////////////////////////////////////// MovieInfo MoviePlayer::_movies[] = { { "carib", 222, false }, @@ -141,208 +64,318 @@ MovieInfo MoviePlayer::_movies[] = { { "river", 656, false }, { "sailing", 138, false }, { "shaman", 788, true }, - { "stone1", 34, false }, + { "stone1", 34, true }, { "stone2", 282, false }, - { "stone3", 65, false }, + { "stone3", 65, true }, { "demo", 60, false }, { "enddemo", 110, false } }; -MoviePlayer::MoviePlayer(Sword2Engine *vm) - : _vm(vm), _snd(_vm->_mixer), _sys(_vm->_system), _textSurface(NULL) { +MoviePlayer::MoviePlayer(Sword2Engine *vm) { + _vm = vm; + _mixer = _vm->_mixer; + _system = _vm->_system; + _textSurface = NULL; + _bgSoundStream = NULL; + _ticks = 0; + _currentFrame = 0; + _frameBuffer = NULL; + _frameWidth = 0; + _frameHeight = 0; + _frameX = 0; + _frameY = 0; + _black = 1; + _white = 255; + _numFrames = 0; + _leadOutFrame = (uint)-1; + _seamless = false; + _framesSkipped = 0; + _forceFrame = false; + _textList = NULL; + _currentText = 0; } -void MoviePlayer::openTextObject(MovieTextObject *obj) { - if (obj->textSprite) - _vm->_screen->createSurface(obj->textSprite, &_textSurface); +MoviePlayer::~MoviePlayer() { } -void MoviePlayer::closeTextObject(MovieTextObject *obj) { - if (_textSurface) { - _vm->_screen->deleteSurface(_textSurface); - _textSurface = NULL; - } -} +void MoviePlayer::updatePalette(byte *pal, bool packed) { + byte palette[4 * 256]; + byte *p = palette; -void MoviePlayer::drawTextObject(AnimationState *anim, MovieTextObject *obj) { - if (obj->textSprite && _textSurface) { -#ifdef BACKEND_8BIT - _vm->_screen->drawSurface(obj->textSprite, _textSurface); -#else - if (anim) - anim->drawTextObject(obj->textSprite, _textSurface); - else - _vm->_screen->drawSurface(obj->textSprite, _textSurface); -#endif + uint32 maxWeight = 0; + uint32 minWeight = 0xFFFFFFFF; + + for (int i = 0; i < 256; i++) { + int r = *pal++; + int g = *pal++; + int b = *pal++; + + if (!packed) + pal++; + + uint32 weight = 3 * r * r + 6 * g * g + 2 * b * b; + + if (weight >= maxWeight) { + _black = i; + maxWeight = weight; + } + + if (weight <= minWeight) { + _white = i; + minWeight = i; + } + + *p++ = r; + *p++ = g; + *p++ = b; + *p++ = 0; } + + _vm->_screen->setPalette(0, 256, palette, RDPAL_INSTANT); + _forceFrame = true; } -/** - * Plays an animated cutscene. - * @param filename the file name of the cutscene file - * @param text the subtitles and voiceovers for the cutscene - * @param leadInRes lead-in music resource id - * @param leadOutRes lead-out music resource id - */ +void MoviePlayer::savePalette() { + memcpy(_originalPalette, _vm->_screen->getPalette(), sizeof(_originalPalette)); +} -int32 MoviePlayer::play(const char *filename, MovieTextObject *text[], int32 leadInRes, int32 leadOutRes) { - Audio::SoundHandle leadInHandle; +void MoviePlayer::restorePalette() { + _vm->_screen->setPalette(0, 256, _originalPalette, RDPAL_INSTANT); +} - // This happens if the user quits during the "eye" smacker - if (_vm->_quit) - return RD_OK; +void MoviePlayer::clearScreen() { + _vm->_screen->clearScene(); + _system->copyRectToScreen(_vm->_screen->getScreen(), _vm->_screen->getScreenWide(), 0, 0, _vm->_screen->getScreenWide(), _vm->_screen->getScreenDeep()); +} - if (leadInRes) { - byte *leadIn = _vm->_resman->openResource(leadInRes); - uint32 leadInLen = _vm->_resman->fetchLen(leadInRes) - ResHeader::size(); +void MoviePlayer::updateScreen() { + _system->updateScreen(); +} - assert(_vm->_resman->fetchType(leadIn) == WAV_FILE); +bool MoviePlayer::checkSkipFrame() { + if (_forceFrame) { + _forceFrame = false; + return false; + } - leadIn += ResHeader::size(); + if (_framesSkipped > 10) { + warning("Forced frame %d to be displayed", _currentFrame); + _framesSkipped = 0; + return false; + } - _vm->_sound->playFx(&leadInHandle, leadIn, leadInLen, Audio::Mixer::kMaxChannelVolume, 0, false, Audio::Mixer::kMusicSoundType); + if (_bgSoundStream) { + if ((_mixer->getSoundElapsedTime(_bgSoundHandle) * 12) / 1000 < _currentFrame + 1) + return false; + } else { + if (_system->getMillis() <= _ticks) + return false; } - byte *leadOut = NULL; - uint32 leadOutLen = 0; + _framesSkipped++; + return true; +} - if (leadOutRes) { - leadOut = _vm->_resman->openResource(leadOutRes); - leadOutLen = _vm->_resman->fetchLen(leadOutRes) - ResHeader::size(); +void MoviePlayer::waitForFrame() { + if (_bgSoundStream) { + while (_mixer->isSoundHandleActive(_bgSoundHandle) && (_mixer->getSoundElapsedTime(_bgSoundHandle) * 12) / 1000 < _currentFrame) { + _system->delayMillis(10); + } - assert(_vm->_resman->fetchType(leadOut) == WAV_FILE); + // In case the background sound ends prematurely, update _ticks + // so that we can still fall back on the no-sound sync case for + // the subsequent frames. - leadOut += ResHeader::size(); + _ticks = _system->getMillis(); + } else { + while (_system->getMillis() < _ticks) { + _system->delayMillis(10); + } } +} - _leadOutFrame = (uint)-1; - - int i; +void MoviePlayer::drawFrame() { + _ticks += 83; - for (i = 0; i < ARRAYSIZE(_movies); i++) { - if (scumm_stricmp(filename, _movies[i].name) == 0) { - _seamless = _movies[i].seamless; - if (_movies[i].frames > 60) - _leadOutFrame = _movies[i].frames - 60; - break; - } + if (checkSkipFrame()) { + warning("Skipped frame %d", _currentFrame); + return; } - if (i == ARRAYSIZE(_movies)) - warning("Unknown movie, '%s'", filename); + waitForFrame(); -#ifdef USE_MPEG2 - playMPEG(filename, text, leadOut, leadOutLen); -#else - // No MPEG2? Use the old 'Narration Only' hack - playDummy(filename, text, leadOut, leadOutLen); -#endif + int screenWidth = _vm->_screen->getScreenWide(); - _snd->stopHandle(leadInHandle); + _system->copyRectToScreen(_frameBuffer + _frameY * screenWidth + _frameX, screenWidth, _frameX, _frameY, _frameWidth, _frameHeight); + _vm->_screen->setNeedFullRedraw(); +} - // Wait for the lead-out to stop, if there is any. Though don't do it - // for seamless movies, since they are meant to blend into the rest of - // the game. +void MoviePlayer::openTextObject(MovieTextObject *t) { + if (t->textSprite) { + _vm->_screen->createSurface(t->textSprite, &_textSurface); + } +} - if (!_seamless) { - while (_vm->_mixer->isSoundHandleActive(_leadOutHandle)) { - _vm->_screen->updateDisplay(); - _vm->_system->delayMillis(30); +void MoviePlayer::closeTextObject(MovieTextObject *t) { + if (_textSurface) { + _vm->_screen->deleteSurface(_textSurface); + _textSurface = NULL; + } +} + +void MoviePlayer::drawTextObject(MovieTextObject *t) { + if (t->textSprite && _textSurface) { + int screenWidth = _vm->_screen->getScreenWide(); + byte *src = t->textSprite->data; + byte *dst = _frameBuffer + (_frameY + _frameHeight - t->textSprite->h - 20) * screenWidth + _frameX + (_frameWidth - t->textSprite->w) / 2; + + for (int y = 0; y < t->textSprite->h; y++) { + for (int x = 0; x < t->textSprite->w; x++) { + if (src[x] == 1) + dst[x] = _white; + else if (src[x] == 255) + dst[x] = _black; + } + src += t->textSprite->w; + dst += screenWidth; } } +} - if (leadInRes) - _vm->_resman->closeResource(leadInRes); +void MoviePlayer::undrawTextObject(MovieTextObject *t) { +} - if (leadOutRes) - _vm->_resman->closeResource(leadOutRes); +bool MoviePlayer::load(const char *name, MovieTextObject *text[]) { + _bgSoundStream = NULL; + _textList = text; + _currentText = 0; + _currentFrame = 0; - return RD_OK; + for (int i = 0; i < ARRAYSIZE(_movies); i++) { + if (scumm_stricmp(name, _movies[i].name) == 0) { + _seamless = _movies[i].seamless; + _numFrames = _movies[i].frames; + if (_numFrames > 60) + _leadOutFrame = _numFrames - 60; + return true; + } + } + + return false; } -void MoviePlayer::playMPEG(const char *filename, MovieTextObject *text[], byte *leadOut, uint32 leadOutLen) { - uint frameCounter = 0, textCounter = 0; - Audio::SoundHandle handle; - bool skipCutscene = false, textVisible = false; - uint32 flags = Audio::Mixer::FLAG_16BITS; +void MoviePlayer::play(int32 leadIn, int32 leadOut) { + bool terminate = false; + bool textVisible = false; bool startNextText = false; + byte *data; + uint32 len; + Audio::SoundHandle leadInHandle, leadOutHandle; + uint32 flags = Audio::Mixer::FLAG_16BITS; + + // This happens if the user quits during the "eye" cutscene. + if (_vm->_quit) + return; - byte oldPal[256 * 4]; - memcpy(oldPal, _vm->_screen->getPalette(), sizeof(oldPal)); + if (leadIn) { + data = _vm->_resman->openResource(leadIn); + len = _vm->_resman->fetchLen(leadIn) - ResHeader::size(); - AnimationState *anim = new AnimationState(_vm); + assert(_vm->_resman->fetchType(data) == WAV_FILE); - if (!anim->init(filename)) { - delete anim; - anim = NULL; - // Missing Files? Use the old 'Narration Only' hack - playDummy(filename, text, leadOut, leadOutLen); - return; + data += ResHeader::size(); + + _vm->_sound->playFx(&leadInHandle, data, len, Audio::Mixer::kMaxChannelVolume, 0, false, Audio::Mixer::kMusicSoundType); + } + + savePalette(); + + // Not all cutscenes cover the entire screen, so clear it. We will + // always clear the game screen, no matter how the cutscene is to be + // displayed. + + _vm->_mouse->closeMenuImmediately(); + + if (!_seamless) { + _vm->_screen->clearScene(); } - // Clear the screen, because whatever is on it will be visible when the - // overlay is removed. And if there isn't an overlay, we don't want it - // to be visible during the cutscene. (Not all cutscenes cover the - // entire screen.) - _vm->_screen->clearScene(); _vm->_screen->updateDisplay(); #ifndef SCUMM_BIG_ENDIAN flags |= Audio::Mixer::FLAG_LITTLE_ENDIAN; #endif - while (!skipCutscene && anim->decodeFrame()) { - // The frame has been drawn. Now draw the subtitles, if any, - // before updating the screen. + _framesSkipped = 0; + + _ticks = _system->getMillis(); + + if (_bgSoundStream) { + _mixer->playInputStream(Audio::Mixer::kSFXSoundType, &_bgSoundHandle, _bgSoundStream); + } - if (text && text[textCounter]) { - if (frameCounter == text[textCounter]->startFrame) { - openTextObject(text[textCounter]); + while (!terminate && _currentFrame < _numFrames && decodeFrame()) { + _currentFrame++; + + // The frame has been decoded. Now draw the subtitles, if any, + // before drawing it to the screen. + + if (_textList && _textList[_currentText]) { + MovieTextObject *t = _textList[_currentText]; + + if (_currentFrame == t->startFrame) { + openTextObject(t); textVisible = true; - if (text[textCounter]->speech) { + if (t->speech) { startNextText = true; } } - if (startNextText && !_snd->isSoundHandleActive(handle)) { - _snd->playRaw(&handle, text[textCounter]->speech, text[textCounter]->speechBufferSize, 22050, flags); + if (startNextText && !_mixer->isSoundHandleActive(_speechHandle)) { + _mixer->playRaw(&_speechHandle, t->speech, t->speechBufferSize, 22050, flags); startNextText = false; } - if (frameCounter == text[textCounter]->endFrame) { - closeTextObject(text[textCounter]); - textCounter++; + if (_currentFrame == t->endFrame) { + undrawTextObject(t); + closeTextObject(t); + _currentText++; textVisible = false; } if (textVisible) - drawTextObject(anim, text[textCounter]); + drawTextObject(t); } -#ifdef BACKEND_8BIT - _sys->copyRectToScreen(_vm->_screen->getScreen(), 640, 0, 0, 640, 480); -#endif + if (leadOut && _currentFrame == _leadOutFrame) { + data = _vm->_resman->openResource(leadOut); + len = _vm->_resman->fetchLen(leadOut) - ResHeader::size(); - anim->updateScreen(); - frameCounter++; + assert(_vm->_resman->fetchType(data) == WAV_FILE); - if (frameCounter == _leadOutFrame && leadOut) - _vm->_sound->playFx(&_leadOutHandle, leadOut, leadOutLen, Audio::Mixer::kMaxChannelVolume, 0, false, Audio::Mixer::kMusicSoundType); + data += ResHeader::size(); + + _vm->_sound->playFx(&leadOutHandle, data, len, Audio::Mixer::kMaxChannelVolume, 0, false, Audio::Mixer::kMusicSoundType); + } + + drawFrame(); + updateScreen(); OSystem::Event event; - while (_sys->pollEvent(event)) { + + while (_system->pollEvent(event)) { switch (event.type) { case OSystem::EVENT_SCREEN_CHANGED: - anim->handleScreenChanged(); - break; - case OSystem::EVENT_KEYDOWN: - if (event.kbd.keycode == 27) - skipCutscene = true; + handleScreenChanged(); break; case OSystem::EVENT_QUIT: _vm->closeGame(); - skipCutscene = true; + terminate = true; + break; + case OSystem::EVENT_KEYDOWN: + if (event.kbd.keycode == 27) + terminate = true; break; default: break; @@ -350,208 +383,433 @@ void MoviePlayer::playMPEG(const char *filename, MovieTextObject *text[], byte * } } - if (!skipCutscene) { - // Sleep for one frame so that the last frame is displayed. - _sys->delayMillis(1000 / 12); - } - if (!_seamless) { - // Most movies fade to black on their own, but not all of them. - // Since we may be hanging around in the cutscene player for a - // while longer, waiting for the lead-out sound to finish, - // paint the overlay black. - - anim->clearScreen(); - } + // Most cutscenes fade to black on their own, but not all of + // them. I think it looks better if they do. - // If the speech is still playing, redraw the subtitles. At least in - // the English version this is most noticeable in the "carib" cutscene. + clearScreen(); - if (textVisible && _snd->isSoundHandleActive(handle)) - drawTextObject(anim, text[textCounter]); + // If the sound is still playing, draw the subtitles one final + // time. This happens in the "carib" cutscene. - if (text) - closeTextObject(text[textCounter]); + if (textVisible && _mixer->isSoundHandleActive(_speechHandle)) { + drawTextObject(_textList[_currentText]); + } - anim->updateScreen(); + updateScreen(); + } - // Wait for the voice to stop playing. This is to make sure that we - // don't cut off the speech in mid-sentence, and - even more - // importantly - that we don't free the sound buffer while it's in use. + if (!terminate) { + // Wait for the voice to stop playing. This is to make sure + // that we don't cut off the speech in mid-sentence, and - even + // more importantly - that we don't free the sound buffer while + // it's still in use. - if (skipCutscene) - _snd->stopHandle(handle); + while (_mixer->isSoundHandleActive(_speechHandle)) { + _system->delayMillis(100); + } - while (_snd->isSoundHandleActive(handle)) { - _vm->_screen->updateDisplay(false); - _sys->delayMillis(100); + while (_mixer->isSoundHandleActive(_bgSoundHandle)) { + _system->delayMillis(100); + } + } else { + _mixer->stopHandle(_speechHandle); + _mixer->stopHandle(_bgSoundHandle); } if (!_seamless) { - // Clear the screen again - anim->clearScreen(); - anim->updateScreen(); + clearScreen(); + updateScreen(); } - _vm->_screen->setPalette(0, 256, oldPal, RDPAL_INSTANT); + // Setting the palette implies a full redraw. + restorePalette(); +} + +#ifdef USE_ZLIB + +/////////////////////////////////////////////////////////////////////////////// +// Movie player for the new DXA movies +/////////////////////////////////////////////////////////////////////////////// - delete anim; - anim = NULL; +MoviePlayerDXA::MoviePlayerDXA(Sword2Engine *vm) : MoviePlayer(vm) { + debug(0, "Creating DXA cutscene player"); } -/** - * This just plays the cutscene with voiceovers / subtitles, in case the files - * are missing. - */ +MoviePlayerDXA::~MoviePlayerDXA() { + closeFile(); +} -void MoviePlayer::playDummy(const char *filename, MovieTextObject *text[], byte *leadOut, uint32 leadOutLen) { - if (!text) - return; +void MoviePlayerDXA::setPalette(byte *pal) { + updatePalette(pal); +} - int frameCounter = 0, textCounter = 0; +bool MoviePlayerDXA::decodeFrame() { + decodeNextFrame(); + copyFrameToBuffer(_frameBuffer, _frameX, _frameY, _vm->_screen->getScreenWide()); + return true; +} - byte oldPal[256 * 4]; - byte tmpPal[256 * 4]; +bool MoviePlayerDXA::load(const char *name, MovieTextObject *text[]) { + if (!MoviePlayer::load(name, text)) + return false; - _vm->_screen->clearScene(); + char filename[20]; - // HACK: Draw instructions - // - // I'm using the the menu area, because that's unlikely to be touched - // by anything else during the cutscene. + snprintf(filename, sizeof(filename), "%s.dxa", name); - memset(_vm->_screen->getScreen(), 0, _vm->_screen->getScreenWide() * MENUDEEP); + if (loadFile(filename)) { + // The Broken Sword games always use external audio tracks. + if (_fd.readUint32BE() != MKID_BE('NULL')) + return false; - byte *data; + _frameBuffer = _vm->_screen->getScreen(); - // Russian version substituted latin characters with cyrillic. That's - // why it renders completely unreadable with default message - if (Common::parseLanguage(ConfMan.get("language")) == Common::RU_RUS) { - byte msg[] = "Po\344uk - to\344\345ko pev\345: hagmute k\344abuwy Ucke\343n, u\344u nocetute ca\343t npoekta u ckava\343te budeo po\344uku"; - data = _vm->_fontRenderer->makeTextSprite(msg, RENDERWIDE, 255, _vm->_speechFontId); - } else { - // TODO: Translate message to other languages? -#ifdef USE_MPEG2 - byte msg[] = "Cutscene - Narration Only: Press ESC to exit, or visit www.scummvm.org to download cutscene videos"; + _frameWidth = getWidth(); + _frameHeight = getHeight(); + + _frameX = (_vm->_screen->getScreenWide() - _frameWidth) / 2; + _frameY = (_vm->_screen->getScreenDeep() - _frameHeight) / 2; + + _bgSoundStream = Audio::AudioStream::openStreamFile(name); + + return true; + } + + return false; +} + +#endif + +/////////////////////////////////////////////////////////////////////////////// +// Movie player for the old MPEG movies +/////////////////////////////////////////////////////////////////////////////// + +MoviePlayerMPEG::MoviePlayerMPEG(Sword2Engine *vm) : MoviePlayer(vm) { +#ifdef BACKEND_8BIT + debug(0, "Creating MPEG cutscene player (8-bit)"); #else - byte msg[] = "Cutscene - Narration Only: Press ESC to exit, or recompile ScummVM with MPEG2 support"; + debug(0, "Creating MPEG cutscene player (16-bit)"); #endif +} + +MoviePlayerMPEG::~MoviePlayerMPEG() { + delete _anim; + _anim = NULL; +} - data = _vm->_fontRenderer->makeTextSprite(msg, RENDERWIDE, 255, _vm->_speechFontId); +bool MoviePlayerMPEG::load(const char *name, MovieTextObject *text[]) { + if (!MoviePlayer::load(name, text)) + return false; + + _anim = new AnimationState(_vm, this); + + if (!_anim->init(name)) { + delete _anim; + _anim = NULL; + return false; } - FrameHeader frame_head; - SpriteInfo msgSprite; - byte *msgSurface; +#ifdef BACKEND_8BIT + _frameBuffer = _vm->_screen->getScreen(); +#endif - frame_head.read(data); + return true; +} - msgSprite.x = _vm->_screen->getScreenWide() / 2 - frame_head.width / 2; - msgSprite.y = MENUDEEP / 2 - frame_head.height / 2; - msgSprite.w = frame_head.width; - msgSprite.h = frame_head.height; - msgSprite.type = RDSPR_NOCOMPRESSION; - msgSprite.data = data + FrameHeader::size(); +bool MoviePlayerMPEG::checkSkipFrame() { + return false; +} - _vm->_screen->createSurface(&msgSprite, &msgSurface); - _vm->_screen->drawSurface(&msgSprite, msgSurface); - _vm->_screen->deleteSurface(msgSurface); +void MoviePlayerMPEG::waitForFrame() { +} - free(data); +bool MoviePlayerMPEG::decodeFrame() { + bool result = _anim->decodeFrame(); - // In case the cutscene has a long lead-in, start just before the first - // line of text. +#ifdef BACKEND_8BIT + _frameWidth = _anim->getFrameWidth(); + _frameHeight = _anim->getFrameHeight(); - frameCounter = text[0]->startFrame - 12; + _frameX = (_vm->_screen->getScreenWide() - _frameWidth) / 2; + _frameY = (_vm->_screen->getScreenDeep() - _frameHeight) / 2; +#endif - // Fake a palette that will hopefully make the text visible. In the - // opening cutscene it seems to use colours 1 (black) and 255 (white). + return result; +} - memcpy(oldPal, _vm->_screen->getPalette(), sizeof(oldPal)); - memset(tmpPal, 0, sizeof(tmpPal)); - tmpPal[255 * 4 + 0] = 255; - tmpPal[255 * 4 + 1] = 255; - tmpPal[255 * 4 + 2] = 255; - _vm->_screen->setPalette(0, 256, tmpPal, RDPAL_INSTANT); +#ifndef BACKEND_8BIT +void MoviePlayerMPEG::handleScreenChanged() { + _anim->handleScreenChanged(); +} - Audio::SoundHandle handle; +void MoviePlayerMPEG::clearScreen() { + _anim->clearScreen(); +} - bool skipCutscene = false; +void MoviePlayerMPEG::drawFrame() { +} - uint32 flags = Audio::Mixer::FLAG_16BITS; +void MoviePlayerMPEG::updateScreen() { + _anim->updateScreen(); +} -#ifndef SCUMM_BIG_ENDIAN - flags |= Audio::Mixer::FLAG_LITTLE_ENDIAN; +void MoviePlayerMPEG::drawTextObject(MovieTextObject *t) { + if (t->textSprite && _textSurface) { + _anim->drawTextObject(t->textSprite, _textSurface); + } +} #endif - while (1) { - if (!text[textCounter]) - break; +AnimationState::AnimationState(Sword2Engine *vm, MoviePlayer *player) + : BaseAnimationState(vm->_mixer, vm->_system, 640, 480) { + _vm = vm; + _player = player; +} + +AnimationState::~AnimationState() { +} + +#ifdef BACKEND_8BIT +void AnimationState::setPalette(byte *pal) { + _player->updatePalette(pal, false); +} +#else + +void AnimationState::drawTextObject(SpriteInfo *s, byte *src) { + int moviePitch = _movieScale * _movieWidth; + int textX = _movieScale * s->x; + int textY = _movieScale * (_frameHeight - s->h - 12); + + OverlayColor *dst = _overlay + textY * moviePitch + textX; + + OverlayColor pen = _sys->RGBToColor(255, 255, 255); + OverlayColor border = _sys->RGBToColor(0, 0, 0); - if (frameCounter == text[textCounter]->startFrame) { - _vm->_screen->clearScene(); - openTextObject(text[textCounter]); - drawTextObject(NULL, text[textCounter]); - if (text[textCounter]->speech) { - _snd->playRaw(&handle, text[textCounter]->speech, text[textCounter]->speechBufferSize, 22050, flags); + // TODO: Use the AdvMame scalers for the text? Pre-scale it? + + for (int y = 0; y < s->h; y++) { + OverlayColor *ptr = dst; + + for (int x = 0; x < s->w; x++) { + switch (src[x]) { + case 1: + *ptr++ = border; + if (_movieScale > 1) { + *ptr++ = border; + if (_movieScale > 2) + *ptr++ = border; + } + break; + case 255: + *ptr++ = pen; + if (_movieScale > 1) { + *ptr++ = pen; + if (_movieScale > 2) + *ptr++ = pen; + } + break; + default: + ptr += _movieScale; + break; } } - if (frameCounter == text[textCounter]->endFrame) { - closeTextObject(text[textCounter]); - _vm->_screen->clearScene(); - _vm->_screen->setNeedFullRedraw(); - textCounter++; + if (_movieScale > 1) { + memcpy(dst + moviePitch, dst, _movieScale * s->w * sizeof(OverlayColor)); + if (_movieScale > 2) + memcpy(dst + 2 * moviePitch, dst, _movieScale * s->w * sizeof(OverlayColor)); } - frameCounter++; - _vm->_screen->updateDisplay(); + dst += _movieScale * moviePitch; + src += s->w; + } +} - KeyboardEvent *ke = _vm->keyboardEvent(); +#endif - if ((ke && ke->keycode == 27) || _vm->_quit) { - _snd->stopHandle(handle); - skipCutscene = true; - break; +void AnimationState::clearScreen() { +#ifdef BACKEND_8BIT + memset(_vm->_screen->getScreen(), 0, _movieWidth * _movieHeight); +#else + OverlayColor black = _sys->RGBToColor(0, 0, 0); + + for (int i = 0; i < _movieScale * _movieWidth * _movieScale * _movieHeight; i++) + _overlay[i] = black; +#endif +} + +void AnimationState::drawYUV(int width, int height, byte *const *dat) { + _frameWidth = width; + _frameHeight = height; + +#ifdef BACKEND_8BIT + byte *buf = _vm->_screen->getScreen() + ((480 - height) / 2) * RENDERWIDE + (640 - width) / 2; + + int x, y; + + int ypos = 0; + int cpos = 0; + int linepos = 0; + + for (y = 0; y < height; y += 2) { + for (x = 0; x < width; x += 2) { + int i = ((((dat[2][cpos] + ROUNDADD) >> SHIFT) * (BITDEPTH + 1)) + ((dat[1][cpos] + ROUNDADD) >> SHIFT)) * (BITDEPTH + 1); + cpos++; + + buf[linepos ] = _lut[i + ((dat[0][ ypos ] + ROUNDADD) >> SHIFT)]; + buf[RENDERWIDE + linepos++] = _lut[i + ((dat[0][width + ypos++] + ROUNDADD) >> SHIFT)]; + buf[linepos ] = _lut[i + ((dat[0][ ypos ] + ROUNDADD) >> SHIFT)]; + buf[RENDERWIDE + linepos++] = _lut[i + ((dat[0][width + ypos++] + ROUNDADD) >> SHIFT)]; } + linepos += (2 * RENDERWIDE - width); + ypos += width; + } +#else + plotYUV(width, height, dat); +#endif +} - // Simulate ~12 frames per second. I don't know what frame rate - // the original movies had, or even if it was constant, but - // this seems to work reasonably. +/////////////////////////////////////////////////////////////////////////////// +// Dummy player for subtitled speech only +/////////////////////////////////////////////////////////////////////////////// + +MoviePlayerDummy::MoviePlayerDummy(Sword2Engine *vm) : MoviePlayer(vm) { + debug(0, "Creating Dummy cutscene player"); +} - _sys->delayMillis(90); +MoviePlayerDummy::~MoviePlayerDummy() { +} + +bool MoviePlayerDummy::load(const char *name, MovieTextObject *text[]) { + if (!MoviePlayer::load(name, text)) + return false; + + _frameBuffer = _vm->_screen->getScreen(); + + _frameWidth = 640; + _frameHeight = 400; + _frameX = 0; + _frameY = 40; + + return true; +} + +bool MoviePlayerDummy::decodeFrame() { + if (_currentFrame == 0 && _textList) { + byte dummyPalette[] = { + 0, 0, 0, 0, + 255, 255, 255, 0, + }; + + // 0 is always black + // 1 is the border colour - black + // 255 is the pen colour - white + + _system->setPalette(dummyPalette, 0, 1); + _system->setPalette(dummyPalette, 1, 1); + _system->setPalette(dummyPalette + 4, 255, 1); + + byte msgNoCutscenesRU[] = "Po\344uk - to\344\345ko pev\345: hagmute k\344abuwy Ucke\343n, u\344u nocetute ca\343t npoekta u ckava\343te budeo po\344uku"; + +#if defined(USE_MPEG2) || defined(USE_ZLIB) + byte msgNoCutscenes[] = "Cutscene - Narration Only: Press ESC to exit, or visit www.scummvm.org to download cutscene videos"; +#else + byte msgNoCutscenes[] = "Cutscene - Narration Only: Press ESC to exit, or recompile ScummVM with MPEG2 or ZLib support"); +#endif + + byte *msg; + + // Russian version substituted latin characters with Cyrillic. + if (Common::parseLanguage(ConfMan.get("language")) == Common::RU_RUS) { + msg = msgNoCutscenesRU; + } else { + msg = msgNoCutscenes; + } + + byte *data = _vm->_fontRenderer->makeTextSprite(msg, RENDERWIDE, 255, _vm->_speechFontId); + + FrameHeader frame_head; + SpriteInfo msgSprite; + byte *msgSurface; + + frame_head.read(data); + + msgSprite.x = _vm->_screen->getScreenWide() / 2 - frame_head.width / 2; + msgSprite.y = (480 - frame_head.height) / 2; + msgSprite.w = frame_head.width; + msgSprite.h = frame_head.height; + msgSprite.type = RDSPR_NOCOMPRESSION; + msgSprite.data = data + FrameHeader::size(); + + _vm->_screen->createSurface(&msgSprite, &msgSurface); + _vm->_screen->drawSurface(&msgSprite, msgSurface); + _vm->_screen->deleteSurface(msgSurface); + + free(data); + updateScreen(); } - // Wait for the voice to stop playing. This is to make sure that we - // don't cut off the speech in mid-sentence, and - even more - // importantly - that we don't free the sound buffer while it's in use. + // If we have played the final voice-over, skip ahead to the lead out - while (_snd->isSoundHandleActive(handle)) { - _vm->_screen->updateDisplay(false); - _sys->delayMillis(100); + if (_textList && !_textList[_currentText] && !_mixer->isSoundHandleActive(_speechHandle) && _leadOutFrame != (uint)-1 && _currentFrame < _leadOutFrame) { + _currentFrame = _leadOutFrame - 1; } - closeTextObject(text[textCounter]); + return true; +} - _vm->_screen->clearScene(); - _vm->_screen->setNeedFullRedraw(); +bool MoviePlayerDummy::checkSkipFrame() { + return false; +} + +void MoviePlayerDummy::waitForFrame() { + if (!_textList || _currentFrame < _textList[0]->startFrame) { + _ticks = _system->getMillis(); + return; + } + + MoviePlayer::waitForFrame(); +} + +void MoviePlayerDummy::drawFrame() { + _ticks += 83; + waitForFrame(); +} - // HACK: Remove the instructions created above - Common::Rect r; +void MoviePlayerDummy::drawTextObject(MovieTextObject *t) { + _vm->_screen->drawSurface(t->textSprite, _textSurface); +} + +void MoviePlayerDummy::undrawTextObject(MovieTextObject *t) { + memset(_textSurface, 1, t->textSprite->w * t->textSprite->h); + drawTextObject(t); +} - memset(_vm->_screen->getScreen(), 0, _vm->_screen->getScreenWide() * MENUDEEP); - r.left = r.top = 0; - r.right = _vm->_screen->getScreenWide(); - r.bottom = MENUDEEP; - _vm->_screen->updateRect(&r); +/////////////////////////////////////////////////////////////////////////////// +// Factory function for creating the appropriate cutscene player +/////////////////////////////////////////////////////////////////////////////// - // FIXME: For now, only play the lead-out music for cutscenes that have - // subtitles. +MoviePlayer *makeMoviePlayer(Sword2Engine *vm, const char *name) { + char filename[20]; - if (!skipCutscene && leadOut) - _vm->_sound->playFx(&_leadOutHandle, leadOut, leadOutLen, Audio::Mixer::kMaxChannelVolume, 0, false, Audio::Mixer::kMusicSoundType); +#ifdef USE_ZLIB + snprintf(filename, sizeof(filename), "%s.dxa", name); + + if (Common::File::exists(filename)) { + return new MoviePlayerDXA(vm); + } +#endif + +#ifdef USE_MPEG2 + snprintf(filename, sizeof(filename), "%s.mp2", name); + + if (Common::File::exists(filename)) { + return new MoviePlayerMPEG(vm); + } +#endif - _vm->_screen->setPalette(0, 256, oldPal, RDPAL_INSTANT); + return new MoviePlayerDummy(vm); } } // End of namespace Sword2 diff --git a/engines/sword2/animation.h b/engines/sword2/animation.h index 32598c013d..09ece3b8e8 100644 --- a/engines/sword2/animation.h +++ b/engines/sword2/animation.h @@ -23,6 +23,7 @@ #define SWORD2_ANIMATION_H #include "graphics/animation.h" +#include "graphics/dxa_player.h" #include "sound/mixer.h" namespace Sword2 { @@ -41,12 +42,97 @@ struct MovieTextObject { uint16 *speech; }; +struct MovieInfo { + const char *name; + const uint frames; + const bool seamless; +}; + +class MoviePlayer { +protected: + Sword2Engine *_vm; + Audio::Mixer *_mixer; + OSystem *_system; + + byte _originalPalette[4 * 256]; + + byte *_textSurface; + + Audio::SoundHandle _speechHandle; + Audio::SoundHandle _bgSoundHandle; + Audio::AudioStream *_bgSoundStream; + + uint32 _ticks; + + uint _currentFrame; + byte *_frameBuffer; + int _frameWidth, _frameHeight; + int _frameX, _frameY; + + byte _black, _white; + + uint _numFrames; + uint _leadOutFrame; + bool _seamless; + + int _framesSkipped; + bool _forceFrame; + + static struct MovieInfo _movies[]; + + MovieTextObject **_textList; + int _currentText; + + void savePalette(); + void restorePalette(); + + void openTextObject(MovieTextObject *t); + void closeTextObject(MovieTextObject *t); + + virtual void handleScreenChanged() {} + + virtual void clearScreen(); + virtual void updateScreen(); + virtual bool decodeFrame() = 0; + virtual bool checkSkipFrame(); + virtual void waitForFrame(); + virtual void drawFrame(); + virtual void drawTextObject(MovieTextObject *t); + virtual void undrawTextObject(MovieTextObject *t); + +public: + MoviePlayer(Sword2Engine *vm); + virtual ~MoviePlayer(); + + void updatePalette(byte *pal, bool packed = true); + virtual bool load(const char *name, MovieTextObject *text[]); + void play(int32 leadIn, int32 leadOut); +}; + +class MoviePlayerDummy : public MoviePlayer { +protected: + virtual bool decodeFrame(); + virtual bool checkSkipFrame(); + virtual void waitForFrame(); + virtual void drawFrame(); + virtual void drawTextObject(MovieTextObject *t); + virtual void undrawTextObject(MovieTextObject *t); + +public: + MoviePlayerDummy(Sword2Engine *vm); + virtual ~MoviePlayerDummy(); + + virtual bool load(const char *name, MovieTextObject *text[]); +}; + +#ifdef USE_MPEG2 class AnimationState : public ::Graphics::BaseAnimationState { private: Sword2Engine *_vm; + MoviePlayer *_player; public: - AnimationState(Sword2Engine *vm); + AnimationState(Sword2Engine *vm, MoviePlayer *player); ~AnimationState(); #ifndef BACKEND_8BIT @@ -63,38 +149,45 @@ private: #endif }; -struct MovieInfo { - char name[9]; - uint frames; - bool seamless; -}; +class MoviePlayerMPEG : public MoviePlayer { +protected: + AnimationState *_anim; -class MoviePlayer { -private: - Sword2Engine *_vm; - Audio::Mixer *_snd; - OSystem *_sys; - - byte *_textSurface; + virtual bool checkSkipFrame(); + virtual void waitForFrame(); + virtual bool decodeFrame(); - Audio::SoundHandle _leadOutHandle; - - uint _leadOutFrame; - bool _seamless; +#ifndef BACKEND_8BIT + virtual void handleScreenChanged(); + virtual void clearScreen(); + virtual void drawFrame(); + virtual void updateScreen(); + virtual void drawTextObject(MovieTextObject *t); +#endif - static struct MovieInfo _movies[]; +public: + MoviePlayerMPEG(Sword2Engine *vm); + virtual ~MoviePlayerMPEG(); - void openTextObject(MovieTextObject *obj); - void closeTextObject(MovieTextObject *obj); - void drawTextObject(AnimationState *anim, MovieTextObject *obj); + virtual bool load(const char *name, MovieTextObject *text[]); +}; +#endif - void playMPEG(const char *filename, MovieTextObject *text[], byte *leadOut, uint32 leadOutLen); - void playDummy(const char *filename, MovieTextObject *text[], byte *leadOut, uint32 leadOutLen); +#ifdef USE_ZLIB +class MoviePlayerDXA : public MoviePlayer, ::Graphics::DXAPlayer { +protected: + virtual void setPalette(byte *pal); + virtual bool decodeFrame(); public: - MoviePlayer(Sword2Engine *vm); - int32 play(const char *filename, MovieTextObject *text[], int32 leadInRes, int32 leadOutRes); + MoviePlayerDXA(Sword2Engine *vm); + virtual ~MoviePlayerDXA(); + + virtual bool load(const char *name, MovieTextObject *text[]); }; +#endif + +MoviePlayer *makeMoviePlayer(Sword2Engine *vm, const char *name); } // End of namespace Sword2 diff --git a/engines/sword2/function.cpp b/engines/sword2/function.cpp index b35325f9f9..a186fb9266 100644 --- a/engines/sword2/function.cpp +++ b/engines/sword2/function.cpp @@ -2131,17 +2131,13 @@ int32 Logic::fnPlaySequence(int32 *params) { // pause sfx during sequence _vm->_sound->pauseFx(); - MoviePlayer player(_vm); - uint32 rv; + MoviePlayer *player = makeMoviePlayer(_vm, filename); - if (_sequenceTextLines && !readVar(DEMO)) - rv = player.play(filename, sequenceSpeechArray, _smackerLeadIn, _smackerLeadOut); - else - rv = player.play(filename, NULL, _smackerLeadIn, _smackerLeadOut); + if (player->load(filename, (_sequenceTextLines && !readVar(DEMO)) ? sequenceSpeechArray : NULL)) { + player->play(_smackerLeadIn, _smackerLeadOut); + } - // check the error return-value - if (rv) - debug(5, "MoviePlayer.play(\"%s\") returned 0x%.8x", filename, rv); + delete player; // unpause sound fx again, in case we're staying in same location _vm->_sound->unpauseFx(); diff --git a/engines/sword2/render.cpp b/engines/sword2/render.cpp index 4231c49025..24130af5c6 100644 --- a/engines/sword2/render.cpp +++ b/engines/sword2/render.cpp @@ -29,10 +29,6 @@ #include "sword2/defs.h" #include "sword2/screen.h" -#ifdef BACKEND_8BIT -#include "sword2/animation.h" -#endif - namespace Sword2 { #define MILLISECSPERCYCLE 83 @@ -555,31 +551,4 @@ void Screen::closeBackgroundLayer() { _layer = 0; } -#ifdef BACKEND_8BIT -void Screen::plotYUV(byte *lut, int width, int height, byte *const *dat) { - byte *buf = _buffer + ((480 - height) / 2) * RENDERWIDE + (640 - width) / 2; - - int x, y; - - int ypos = 0; - int cpos = 0; - int linepos = 0; - - for (y = 0; y < height; y += 2) { - for (x = 0; x < width; x += 2) { - int i = ((((dat[2][cpos] + ROUNDADD) >> SHIFT) * (BITDEPTH + 1)) + ((dat[1][cpos] + ROUNDADD) >> SHIFT)) * (BITDEPTH + 1); - cpos++; - - buf[linepos ] = lut[i + ((dat[0][ ypos ] + ROUNDADD) >> SHIFT)]; - buf[RENDERWIDE + linepos++] = lut[i + ((dat[0][width + ypos++] + ROUNDADD) >> SHIFT)]; - buf[linepos ] = lut[i + ((dat[0][ ypos ] + ROUNDADD) >> SHIFT)]; - buf[RENDERWIDE + linepos++] = lut[i + ((dat[0][width + ypos++] + ROUNDADD) >> SHIFT)]; - } - linepos += (2 * RENDERWIDE - width); - ypos += width; - } -} -#endif - - } // End of namespace Sword2 diff --git a/engines/sword2/screen.h b/engines/sword2/screen.h index 1a393e53a4..d5a01485fa 100644 --- a/engines/sword2/screen.h +++ b/engines/sword2/screen.h @@ -428,10 +428,6 @@ public: void plotPoint(int x, int y, uint8 colour); void drawLine(int x0, int y0, int x1, int y1, uint8 colour); -#ifdef BACKEND_8BIT - void plotYUV(byte *lut, int width, int height, byte *const *dat); -#endif - void rollCredits(); void splashScreen(); }; -- cgit v1.2.3