mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-14 13:50:13 +00:00
303 lines
9.0 KiB
C++
303 lines
9.0 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.
|
|
*
|
|
*/
|
|
|
|
// 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) {
|
|
}
|
|
|
|
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, true);
|
|
|
|
// 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();
|
|
debugC(4, kLastExpressDebugSound, "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->getPixels(), 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
|