mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-15 22:28:10 +00:00
295 lines
8.3 KiB
C++
295 lines
8.3 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
*/
|
|
|
|
#include "audio/mixer.h"
|
|
#include "audio/decoders/raw.h"
|
|
|
|
#include "toltecs/toltecs.h"
|
|
#include "toltecs/movie.h"
|
|
#include "toltecs/palette.h"
|
|
#include "toltecs/resource.h"
|
|
#include "toltecs/screen.h"
|
|
#include "toltecs/script.h"
|
|
|
|
namespace Toltecs {
|
|
|
|
enum ChunkTypes {
|
|
kChunkFirstImage = 0,
|
|
kChunkSubsequentImages = 1,
|
|
kChunkPalette = 2,
|
|
kChunkUnused = 3,
|
|
kChunkAudio = 4,
|
|
kChunkShowSubtitle = 5,
|
|
kChunkShakeScreen = 6,
|
|
kChunkSetupSubtitles = 7,
|
|
kChunkStopSubtitles = 8
|
|
};
|
|
|
|
MoviePlayer::MoviePlayer(ToltecsEngine *vm) : _vm(vm), _isPlaying(false), _lastPrefetchOfs(0), _framesPerSoundChunk(0), _endPos(0) {
|
|
}
|
|
|
|
MoviePlayer::~MoviePlayer() {
|
|
}
|
|
|
|
void MoviePlayer::playMovie(uint resIndex) {
|
|
const uint32 subtitleSlot = kMaxScriptSlots - 1;
|
|
int16 savedSceneWidth = _vm->_sceneWidth;
|
|
int16 savedSceneHeight = _vm->_sceneHeight;
|
|
int16 savedCameraHeight = _vm->_cameraHeight;
|
|
int16 savedCameraX = _vm->_cameraX;
|
|
int16 savedCameraY = _vm->_cameraY;
|
|
int16 savedGuiHeight = _vm->_guiHeight;
|
|
byte moviePalette[768];
|
|
|
|
_isPlaying = true;
|
|
_vm->_isSaveAllowed = false;
|
|
|
|
memset(moviePalette, 0, sizeof(moviePalette));
|
|
|
|
_vm->_screen->finishTalkTextItems();
|
|
|
|
_vm->_arc->openResource(resIndex);
|
|
_endPos = _vm->_arc->pos() + _vm->_arc->getResourceSize(resIndex);
|
|
|
|
/*_frameCount = */_vm->_arc->readUint32LE();
|
|
uint32 chunkCount = _vm->_arc->readUint32LE();
|
|
|
|
// TODO: Figure out rest of the header
|
|
_vm->_arc->readUint32LE();
|
|
_vm->_arc->readUint32LE();
|
|
_framesPerSoundChunk = _vm->_arc->readUint32LE();
|
|
int rate = _vm->_arc->readUint32LE();
|
|
|
|
_vm->_sceneWidth = 640;
|
|
_vm->_sceneHeight = 400;
|
|
_vm->_cameraHeight = 400;
|
|
_vm->_cameraX = 0;
|
|
_vm->_cameraY = 0;
|
|
_vm->_guiHeight = 0;
|
|
|
|
_audioStream = Audio::makeQueuingAudioStream(rate, false);
|
|
|
|
_vm->_mixer->playStream(Audio::Mixer::kPlainSoundType, &_audioStreamHandle, _audioStream);
|
|
|
|
_lastPrefetchOfs = 0;
|
|
|
|
fetchAudioChunks();
|
|
|
|
byte *chunkBuffer = NULL;
|
|
uint32 chunkBufferSize = 0;
|
|
uint32 frame = 0;
|
|
bool abortMovie = false;
|
|
uint32 soundChunkFramesLeft = 0;
|
|
|
|
while (chunkCount-- && !abortMovie) {
|
|
byte chunkType = _vm->_arc->readByte();
|
|
uint32 chunkSize = _vm->_arc->readUint32LE();
|
|
|
|
debug(0, "chunkType = %d; chunkSize = %d", chunkType, chunkSize);
|
|
|
|
// Skip audio chunks - we've already queued them in
|
|
// fetchAudioChunks()
|
|
if (chunkType == kChunkAudio) {
|
|
_vm->_arc->skip(chunkSize);
|
|
soundChunkFramesLeft += _framesPerSoundChunk;
|
|
} else {
|
|
// Only reallocate the chunk buffer if the new chunk is bigger
|
|
if (chunkSize > chunkBufferSize) {
|
|
delete[] chunkBuffer;
|
|
chunkBuffer = new byte[chunkSize];
|
|
chunkBufferSize = chunkSize;
|
|
}
|
|
|
|
_vm->_arc->read(chunkBuffer, chunkSize);
|
|
}
|
|
|
|
switch (chunkType) {
|
|
case kChunkFirstImage:
|
|
case kChunkSubsequentImages:
|
|
unpackRle(chunkBuffer, _vm->_screen->_backScreen);
|
|
_vm->_screen->_fullRefresh = true;
|
|
|
|
if (--soundChunkFramesLeft <= _framesPerSoundChunk) {
|
|
fetchAudioChunks();
|
|
}
|
|
|
|
while (_vm->_mixer->getSoundElapsedTime(_audioStreamHandle) < (1000 * frame) / 9) {
|
|
if (_vm->_screen->_shakeActive && _vm->_screen->updateShakeScreen()) {
|
|
_vm->_screen->_fullRefresh = true;
|
|
}
|
|
if (!handleInput())
|
|
abortMovie = true;
|
|
_vm->drawScreen();
|
|
// Note: drawScreen() calls delayMillis()
|
|
}
|
|
|
|
frame++;
|
|
break;
|
|
case kChunkPalette:
|
|
unpackPalette(chunkBuffer, moviePalette, 256, 3);
|
|
_vm->_palette->setFullPalette(moviePalette);
|
|
break;
|
|
case kChunkUnused:
|
|
error("Chunk considered to be unused has been encountered");
|
|
case kChunkAudio:
|
|
// Already processed
|
|
break;
|
|
case kChunkShowSubtitle:
|
|
memcpy(_vm->_script->getSlotData(subtitleSlot), chunkBuffer, chunkSize);
|
|
// The last character of the subtitle determines if it should
|
|
// always be displayed or not. If it's 0xFF, it should always be
|
|
// displayed, otherwise, if it's 0xFE, it can be toggled.
|
|
_vm->_screen->updateTalkText(subtitleSlot, 0, (chunkBuffer[chunkSize - 1] == 0xFF));
|
|
break;
|
|
case kChunkShakeScreen: // start/stop shakescreen effect
|
|
if (chunkBuffer[0] == 0xFF)
|
|
_vm->_screen->stopShakeScreen();
|
|
else
|
|
_vm->_screen->startShakeScreen(chunkBuffer[0]);
|
|
break;
|
|
case kChunkSetupSubtitles: // setup subtitle parameters
|
|
_vm->_screen->_talkTextY = READ_LE_UINT16(chunkBuffer + 0);
|
|
_vm->_screen->_talkTextX = READ_LE_UINT16(chunkBuffer + 2);
|
|
_vm->_screen->_talkTextFontColor = ((chunkBuffer[4] << 4) & 0xF0) | ((chunkBuffer[4] >> 4) & 0x0F);
|
|
debug(0, "_talkTextX = %d; _talkTextY = %d; _talkTextFontColor = %d",
|
|
_vm->_screen->_talkTextX, _vm->_screen->_talkTextY, _vm->_screen->_talkTextFontColor);
|
|
break;
|
|
case kChunkStopSubtitles:
|
|
_vm->_script->getSlotData(subtitleSlot)[0] = 0xFF;
|
|
_vm->_screen->finishTalkTextItems();
|
|
break;
|
|
default:
|
|
error("MoviePlayer::playMovie(%04X) Unknown chunk type %d at %08X", resIndex, chunkType, _vm->_arc->pos() - 5 - chunkSize);
|
|
}
|
|
|
|
if (!handleInput())
|
|
abortMovie = true;
|
|
}
|
|
|
|
delete[] chunkBuffer;
|
|
|
|
_audioStream->finish();
|
|
_vm->_mixer->stopHandle(_audioStreamHandle);
|
|
|
|
_vm->_arc->closeResource();
|
|
|
|
debug(0, "playMovie() done");
|
|
|
|
_vm->_sceneWidth = savedSceneWidth;
|
|
_vm->_sceneHeight = savedSceneHeight;
|
|
_vm->_cameraHeight = savedCameraHeight;
|
|
_vm->_cameraX = savedCameraX;
|
|
_vm->_cameraY = savedCameraY;
|
|
_vm->_guiHeight = savedGuiHeight;
|
|
|
|
_vm->_isSaveAllowed = true;
|
|
_isPlaying = false;
|
|
}
|
|
|
|
void MoviePlayer::fetchAudioChunks() {
|
|
uint32 startOfs = _vm->_arc->pos();
|
|
uint prefetchChunkCount = 0;
|
|
|
|
if (_lastPrefetchOfs != 0)
|
|
_vm->_arc->seek(_lastPrefetchOfs, SEEK_SET);
|
|
|
|
while (prefetchChunkCount < _framesPerSoundChunk / 2 && _vm->_arc->pos() < _endPos) {
|
|
byte chunkType = _vm->_arc->readByte();
|
|
uint32 chunkSize = _vm->_arc->readUint32LE();
|
|
if (chunkType == kChunkAudio) {
|
|
byte *chunkBuffer = (byte *)malloc(chunkSize);
|
|
_vm->_arc->read(chunkBuffer, chunkSize);
|
|
_audioStream->queueBuffer(chunkBuffer, chunkSize, DisposeAfterUse::YES, Audio::FLAG_UNSIGNED);
|
|
chunkBuffer = NULL;
|
|
prefetchChunkCount++;
|
|
} else {
|
|
_vm->_arc->seek(chunkSize, SEEK_CUR);
|
|
}
|
|
}
|
|
|
|
_lastPrefetchOfs = _vm->_arc->pos();
|
|
|
|
_vm->_arc->seek(startOfs, SEEK_SET);
|
|
}
|
|
|
|
void MoviePlayer::unpackPalette(byte *source, byte *dest, int elemCount, int elemSize) {
|
|
int ofs = 0, size = elemCount * elemSize;
|
|
while (ofs < size) {
|
|
byte len;
|
|
len = *source++;
|
|
if (len == 0) {
|
|
len = *source++;
|
|
} else {
|
|
byte value = *source++;
|
|
memset(dest, value, len);
|
|
}
|
|
ofs += len;
|
|
dest += len;
|
|
}
|
|
}
|
|
|
|
void MoviePlayer::unpackRle(byte *source, byte *dest) {
|
|
int size = 256000; // 640x400
|
|
//int packedSize = 0;
|
|
while (size > 0) {
|
|
byte a = *source++;
|
|
byte b = *source++;
|
|
//packedSize += 2;
|
|
if (a == 0) {
|
|
dest += b;
|
|
size -= b;
|
|
} else {
|
|
memset(dest, b, a);
|
|
dest += a;
|
|
size -= a;
|
|
}
|
|
}
|
|
//debug("Packed RLE size: %d", packedSize);
|
|
}
|
|
|
|
bool MoviePlayer::handleInput() {
|
|
Common::Event event;
|
|
Common::EventManager *eventMan = g_system->getEventManager();
|
|
while (eventMan->pollEvent(event)) {
|
|
switch (event.type) {
|
|
case Common::EVENT_KEYDOWN:
|
|
if (event.kbd.keycode == Common::KEYCODE_ESCAPE)
|
|
return false;
|
|
if (event.kbd.keycode == Common::KEYCODE_F10) {
|
|
// TODO: The original would bring up a stripped down
|
|
// main menu dialog, without the save/restore options.
|
|
}
|
|
break;
|
|
case Common::EVENT_LBUTTONDOWN:
|
|
case Common::EVENT_RBUTTONDOWN:
|
|
return false;
|
|
case Common::EVENT_QUIT:
|
|
return false;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return !_vm->shouldQuit();
|
|
}
|
|
|
|
} // End of namespace Toltecs
|