/* 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. * */ // Based on Deniz Oezmen's code: http://oezmen.eu/ #include "lastexpress/data/animation.h" #include "lastexpress/data/sequence.h" #include "lastexpress/data/snd.h" #include "lastexpress/debug.h" #include "lastexpress/helpers.h" #include "common/events.h" #include "common/rational.h" #include "common/stream.h" #include "common/system.h" #include "engines/engine.h" namespace LastExpress { Animation::Animation() : _stream(NULL), _currentChunk(NULL), _overlay(NULL), _background1(NULL), _background2(NULL), _backgroundCurrent(0), _audio(NULL), _startTime(0), _changed(false), _flag(0) { } Animation::~Animation() { reset(); } void Animation::reset() { SAFE_DELETE(_overlay); SAFE_DELETE(_background1); SAFE_DELETE(_background2); SAFE_DELETE(_audio); _backgroundCurrent = 0; _chunks.clear(); _currentChunk = NULL; SAFE_DELETE(_stream); } bool Animation::load(Common::SeekableReadStream *stream, int flag) { if (!stream) return false; reset(); // Keep stream for later decoding of animation _stream = stream; // Read header to get the number of chunks uint32 numChunks = _stream->readUint32LE(); debugC(3, kLastExpressDebugGraphics, "Number of chunks in NIS file: %d", numChunks); // Check if there is enough data if (_stream->size() - _stream->pos() < (signed)(numChunks * sizeof(Chunk))) { debugC(2, kLastExpressDebugGraphics, "NIS file seems to be corrupted"); return false; } // Read all the chunks for (uint32 i = 0; i < numChunks; ++i) { Chunk chunk; chunk.type = (ChunkType)_stream->readUint16LE(); chunk.frame = _stream->readUint16LE(); chunk.size = _stream->readUint32LE(); _chunks.push_back(chunk); debugC(9, kLastExpressDebugGraphics, "Chunk Entry: type 0x%.4x, frame=%d, size=%d", chunk.type, chunk.frame, chunk.size); } _currentChunk = _chunks.begin(); _changed = false; _startTime = g_system->getMillis(); return true; } bool Animation::process() { if (!_currentChunk) error("[Animation::process] Current chunk iterator is invalid"); if (_stream == NULL || _chunks.size() == 0) error("[Animation::process] Trying to show an animation before loading data"); // TODO: - subtract the time paused by the GUI // - Re-implement to be closer to the original engine // - Add support for subtitles // - Use engine sound queue instead of our own appendable sound instance int32 currentFrame = (g_system->getMillis() - _startTime) * 3 / 100; // Process all chunks until the current frame while (!_changed && _currentChunk != NULL && currentFrame > _currentChunk->frame && !hasEnded()) { switch(_currentChunk->type) { //TODO: some info chunks are probably subtitle/sync related case kChunkTypeUnknown1: case kChunkTypeUnknown2: case kChunkTypeUnknown5: debugC(9, kLastExpressDebugGraphics | kLastExpressDebugUnknown, " info chunk: type 0x%.4x (size %d)", _currentChunk->type, _currentChunk->size); assert (_currentChunk->frame == 0); //TODO: _currentChunk->size? break; case kChunkTypeAudioInfo: debugC(9, kLastExpressDebugGraphics, " audio info: %d blocks", _currentChunk->size); assert (_currentChunk->frame == 0); //TODO: save the size? _audio = new AppendableSound(); break; case kChunkTypeUnknown4: debugC(9, kLastExpressDebugGraphics | kLastExpressDebugUnknown, " info block 4"); assert (_currentChunk->frame == 0 && _currentChunk->size == 0); //TODO unknown type of chunk break; case kChunkTypeBackground1: debugC(9, kLastExpressDebugGraphics, " background frame 1 (%d bytes, frame %d)", _currentChunk->size, _currentChunk->frame); delete _background1; _background1 = processChunkFrame(_stream, *_currentChunk); break; case kChunkTypeSelectBackground1: debugC(9, kLastExpressDebugGraphics, " select background 1"); assert (_currentChunk->frame == 0 && _currentChunk->size == 0); _backgroundCurrent = 1; break; case kChunkTypeBackground2: debugC(9, kLastExpressDebugGraphics, " background frame 2 (%d bytes, frame %d)", _currentChunk->size, _currentChunk->frame); delete _background2; _background2 = processChunkFrame(_stream, *_currentChunk); break; case kChunkTypeSelectBackground2: debugC(9, kLastExpressDebugGraphics, " select background 2"); assert (_currentChunk->frame == 0 && _currentChunk->size == 0); _backgroundCurrent = 2; break; case kChunkTypeOverlay: debugC(9, kLastExpressDebugGraphics, " overlay frame (%d bytes, frame %d)", _currentChunk->size, _currentChunk->frame); delete _overlay; _overlay = processChunkFrame(_stream, *_currentChunk); break; case kChunkTypeUpdate: case kChunkTypeUpdateTransition: debugC(9, kLastExpressDebugGraphics, " update%s: frame %d", _currentChunk->type == 15 ? "" : " with transition", _currentChunk->frame); assert (_currentChunk->size == 0); _changed = true; break; case kChunkTypeAudioData: debugC(9, kLastExpressDebugGraphics, " audio (%d blocks, %d bytes, frame %d)", _currentChunk->size / _soundBlockSize, _currentChunk->size, _currentChunk->frame); processChunkAudio(_stream, *_currentChunk); // Synchronize the audio by resetting the start time if (_currentChunk->frame == 0) _startTime = g_system->getMillis(); break; case kChunkTypeAudioEnd: debugC(9, kLastExpressDebugGraphics, " audio end: %d blocks", _currentChunk->frame); assert (_currentChunk->size == 0); _audio->finish(); //TODO: we need to start the linked sound (.LNK) after the audio from the animation ends break; default: error("[Animation::process] UNKNOWN chunk type=%x frame=%d size=%d", _currentChunk->type, _currentChunk->frame, _currentChunk->size); break; } _currentChunk++; } return true; } bool Animation::hasEnded() { return _currentChunk == _chunks.end(); } Common::Rect Animation::draw(Graphics::Surface *surface) { if (!_overlay) error("[Animation::draw] Current overlay animation frame is invalid"); // Paint the background if (_backgroundCurrent == 1 && _background1) _background1->draw(surface); else if (_backgroundCurrent == 2 && _background2) _background2->draw(surface); // Paint the overlay _overlay->draw(surface); //TODO return Common::Rect(); } AnimFrame *Animation::processChunkFrame(Common::SeekableReadStream *in, const Chunk &c) const { assert (c.frame == 0); // Create a temporary chunk buffer Common::SeekableReadStream *str = in->readStream(c.size); // Read the frame information FrameInfo i; i.read(str, false); // Decode the frame AnimFrame *f = new AnimFrame(str, i); // Delete the temporary chunk buffer delete str; return f; } void Animation::processChunkAudio(Common::SeekableReadStream *in, const Chunk &c) { if (!_audio) error("[Animation::processChunkAudio] Audio stream is invalid"); // Skip the Snd header, to queue just the audio blocks uint32 size = c.size; if ((c.size % 739) != 0) { // Read Snd header uint32 header1 = in->readUint32LE(); uint16 header2 = in->readUint16LE(); warning("Start ADPCM: %d, %d", header1, header2); size -= 6; } // Append the current chunk to the Snd _audio->queueBuffer(in->readStream(size)); } // TODO: this method will probably go away and be integrated in the main loop void Animation::play() { Common::EventManager *eventMan = g_system->getEventManager(); while (!hasEnded() && !Engine::shouldQuit()) { process(); if (_changed) { // Create a temporary surface to merge the overlay with the background Graphics::Surface *s = new Graphics::Surface; s->create(640, 480, Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0)); draw(s); // XXX: Update the screen g_system->copyRectToScreen(s->pixels, s->pitch, 0, 0, s->w, s->h); // Free the temporary surface s->free(); delete s; _changed = false; } g_system->updateScreen(); //FIXME: implement subtitles g_system->delayMillis(20); // Handle right-click to interrupt animations Common::Event ev = Common::Event(); while (eventMan->pollEvent(ev)) { if (ev.type == Common::EVENT_RBUTTONUP) { // Stop audio if (_audio) _audio->finish(); // TODO start LNK file sound? return; } } } } } // End of namespace LastExpress