From 86d650926f9b991b6398e4ad4b0613ac264dfbaa Mon Sep 17 00:00:00 2001 From: Eugene Sandulenko Date: Mon, 18 Oct 2010 19:17:38 +0000 Subject: LASTEXPRESS: Merge in the engine. svn-id: r53579 --- engines/lastexpress/data/animation.cpp | 300 ++ engines/lastexpress/data/animation.h | 114 + engines/lastexpress/data/archive.cpp | 115 + engines/lastexpress/data/archive.h | 75 + engines/lastexpress/data/background.cpp | 141 + engines/lastexpress/data/background.h | 81 + engines/lastexpress/data/cursor.cpp | 144 + engines/lastexpress/data/cursor.h | 93 + engines/lastexpress/data/font.cpp | 205 ++ engines/lastexpress/data/font.h | 82 + engines/lastexpress/data/scene.cpp | 292 ++ engines/lastexpress/data/scene.h | 245 ++ engines/lastexpress/data/sequence.cpp | 475 +++ engines/lastexpress/data/sequence.h | 205 ++ engines/lastexpress/data/snd.cpp | 141 + engines/lastexpress/data/snd.h | 97 + engines/lastexpress/data/subtitle.cpp | 243 ++ engines/lastexpress/data/subtitle.h | 79 + engines/lastexpress/debug.cpp | 1091 +++++++ engines/lastexpress/debug.h | 104 + engines/lastexpress/detection.cpp | 171 ++ engines/lastexpress/drawable.h | 42 + engines/lastexpress/entities/abbot.cpp | 1910 ++++++++++++ engines/lastexpress/entities/abbot.h | 225 ++ engines/lastexpress/entities/alexei.cpp | 1897 ++++++++++++ engines/lastexpress/entities/alexei.h | 213 ++ engines/lastexpress/entities/alouan.cpp | 504 ++++ engines/lastexpress/entities/alouan.h | 139 + engines/lastexpress/entities/anna.cpp | 3400 +++++++++++++++++++++ engines/lastexpress/entities/anna.h | 252 ++ engines/lastexpress/entities/august.cpp | 2804 ++++++++++++++++++ engines/lastexpress/entities/august.h | 275 ++ engines/lastexpress/entities/boutarel.cpp | 1260 ++++++++ engines/lastexpress/entities/boutarel.h | 188 ++ engines/lastexpress/entities/chapters.cpp | 1810 ++++++++++++ engines/lastexpress/entities/chapters.h | 166 ++ engines/lastexpress/entities/cooks.cpp | 571 ++++ engines/lastexpress/entities/cooks.h | 109 + engines/lastexpress/entities/coudert.cpp | 3611 ++++++++++++++++++++++ engines/lastexpress/entities/coudert.h | 229 ++ engines/lastexpress/entities/entity.cpp | 434 +++ engines/lastexpress/entities/entity.h | 681 +++++ engines/lastexpress/entities/entity39.cpp | 103 + engines/lastexpress/entities/entity39.h | 78 + engines/lastexpress/entities/entity_intern.h | 577 ++++ engines/lastexpress/entities/francois.cpp | 1043 +++++++ engines/lastexpress/entities/francois.h | 170 ++ engines/lastexpress/entities/gendarmes.cpp | 491 +++ engines/lastexpress/entities/gendarmes.h | 99 + engines/lastexpress/entities/hadija.cpp | 529 ++++ engines/lastexpress/entities/hadija.h | 144 + engines/lastexpress/entities/ivo.cpp | 829 ++++++ engines/lastexpress/entities/ivo.h | 177 ++ engines/lastexpress/entities/kahina.cpp | 944 ++++++ engines/lastexpress/entities/kahina.h | 166 ++ engines/lastexpress/entities/kronos.cpp | 666 +++++ engines/lastexpress/entities/kronos.h | 138 + engines/lastexpress/entities/mahmud.cpp | 839 ++++++ engines/lastexpress/entities/mahmud.h | 153 + engines/lastexpress/entities/max.cpp | 628 ++++ engines/lastexpress/entities/max.h | 129 + engines/lastexpress/entities/mertens.cpp | 4113 ++++++++++++++++++++++++++ engines/lastexpress/entities/mertens.h | 220 ++ engines/lastexpress/entities/milos.cpp | 1077 +++++++ engines/lastexpress/entities/milos.h | 175 ++ engines/lastexpress/entities/mmeboutarel.cpp | 939 ++++++ engines/lastexpress/entities/mmeboutarel.h | 164 + engines/lastexpress/entities/pascale.cpp | 1232 ++++++++ engines/lastexpress/entities/pascale.h | 166 ++ engines/lastexpress/entities/rebecca.cpp | 1732 +++++++++++ engines/lastexpress/entities/rebecca.h | 230 ++ engines/lastexpress/entities/salko.cpp | 642 ++++ engines/lastexpress/entities/salko.h | 149 + engines/lastexpress/entities/servers0.cpp | 1039 +++++++ engines/lastexpress/entities/servers0.h | 173 ++ engines/lastexpress/entities/servers1.cpp | 787 +++++ engines/lastexpress/entities/servers1.h | 167 ++ engines/lastexpress/entities/sophie.cpp | 279 ++ engines/lastexpress/entities/sophie.h | 98 + engines/lastexpress/entities/tables.cpp | 221 ++ engines/lastexpress/entities/tables.h | 77 + engines/lastexpress/entities/tatiana.cpp | 1711 +++++++++++ engines/lastexpress/entities/tatiana.h | 235 ++ engines/lastexpress/entities/train.cpp | 575 ++++ engines/lastexpress/entities/train.h | 95 + engines/lastexpress/entities/vassili.cpp | 589 ++++ engines/lastexpress/entities/vassili.h | 110 + engines/lastexpress/entities/verges.cpp | 1898 ++++++++++++ engines/lastexpress/entities/verges.h | 182 ++ engines/lastexpress/entities/vesna.cpp | 1161 ++++++++ engines/lastexpress/entities/vesna.h | 176 ++ engines/lastexpress/entities/yasmin.cpp | 490 +++ engines/lastexpress/entities/yasmin.h | 142 + engines/lastexpress/eventhandler.h | 53 + engines/lastexpress/game/action.cpp | 1968 ++++++++++++ engines/lastexpress/game/action.h | 135 + engines/lastexpress/game/beetle.cpp | 517 ++++ engines/lastexpress/game/beetle.h | 118 + engines/lastexpress/game/entities.cpp | 2731 +++++++++++++++++ engines/lastexpress/game/entities.h | 378 +++ engines/lastexpress/game/fight.cpp | 1587 ++++++++++ engines/lastexpress/game/fight.h | 269 ++ engines/lastexpress/game/inventory.cpp | 594 ++++ engines/lastexpress/game/inventory.h | 169 ++ engines/lastexpress/game/logic.cpp | 577 ++++ engines/lastexpress/game/logic.h | 91 + engines/lastexpress/game/menu.cpp | 1564 ++++++++++ engines/lastexpress/game/menu.h | 211 ++ engines/lastexpress/game/object.cpp | 108 + engines/lastexpress/game/object.h | 83 + engines/lastexpress/game/savegame.cpp | 310 ++ engines/lastexpress/game/savegame.h | 173 ++ engines/lastexpress/game/savepoint.cpp | 296 ++ engines/lastexpress/game/savepoint.h | 149 + engines/lastexpress/game/scenes.cpp | 1195 ++++++++ engines/lastexpress/game/scenes.h | 120 + engines/lastexpress/game/sound.cpp | 1676 +++++++++++ engines/lastexpress/game/sound.h | 333 +++ engines/lastexpress/game/state.cpp | 74 + engines/lastexpress/game/state.h | 593 ++++ engines/lastexpress/graphics.cpp | 166 ++ engines/lastexpress/graphics.h | 76 + engines/lastexpress/helpers.h | 102 + engines/lastexpress/lastexpress.cpp | 310 ++ engines/lastexpress/lastexpress.h | 155 + engines/lastexpress/module.mk | 73 + engines/lastexpress/resource.cpp | 248 ++ engines/lastexpress/resource.h | 70 + engines/lastexpress/shared.h | 1670 +++++++++++ 129 files changed, 73102 insertions(+) create mode 100644 engines/lastexpress/data/animation.cpp create mode 100644 engines/lastexpress/data/animation.h create mode 100644 engines/lastexpress/data/archive.cpp create mode 100644 engines/lastexpress/data/archive.h create mode 100644 engines/lastexpress/data/background.cpp create mode 100644 engines/lastexpress/data/background.h create mode 100644 engines/lastexpress/data/cursor.cpp create mode 100644 engines/lastexpress/data/cursor.h create mode 100644 engines/lastexpress/data/font.cpp create mode 100644 engines/lastexpress/data/font.h create mode 100644 engines/lastexpress/data/scene.cpp create mode 100644 engines/lastexpress/data/scene.h create mode 100644 engines/lastexpress/data/sequence.cpp create mode 100644 engines/lastexpress/data/sequence.h create mode 100644 engines/lastexpress/data/snd.cpp create mode 100644 engines/lastexpress/data/snd.h create mode 100644 engines/lastexpress/data/subtitle.cpp create mode 100644 engines/lastexpress/data/subtitle.h create mode 100644 engines/lastexpress/debug.cpp create mode 100644 engines/lastexpress/debug.h create mode 100644 engines/lastexpress/detection.cpp create mode 100644 engines/lastexpress/drawable.h create mode 100644 engines/lastexpress/entities/abbot.cpp create mode 100644 engines/lastexpress/entities/abbot.h create mode 100644 engines/lastexpress/entities/alexei.cpp create mode 100644 engines/lastexpress/entities/alexei.h create mode 100644 engines/lastexpress/entities/alouan.cpp create mode 100644 engines/lastexpress/entities/alouan.h create mode 100644 engines/lastexpress/entities/anna.cpp create mode 100644 engines/lastexpress/entities/anna.h create mode 100644 engines/lastexpress/entities/august.cpp create mode 100644 engines/lastexpress/entities/august.h create mode 100644 engines/lastexpress/entities/boutarel.cpp create mode 100644 engines/lastexpress/entities/boutarel.h create mode 100644 engines/lastexpress/entities/chapters.cpp create mode 100644 engines/lastexpress/entities/chapters.h create mode 100644 engines/lastexpress/entities/cooks.cpp create mode 100644 engines/lastexpress/entities/cooks.h create mode 100644 engines/lastexpress/entities/coudert.cpp create mode 100644 engines/lastexpress/entities/coudert.h create mode 100644 engines/lastexpress/entities/entity.cpp create mode 100644 engines/lastexpress/entities/entity.h create mode 100644 engines/lastexpress/entities/entity39.cpp create mode 100644 engines/lastexpress/entities/entity39.h create mode 100644 engines/lastexpress/entities/entity_intern.h create mode 100644 engines/lastexpress/entities/francois.cpp create mode 100644 engines/lastexpress/entities/francois.h create mode 100644 engines/lastexpress/entities/gendarmes.cpp create mode 100644 engines/lastexpress/entities/gendarmes.h create mode 100644 engines/lastexpress/entities/hadija.cpp create mode 100644 engines/lastexpress/entities/hadija.h create mode 100644 engines/lastexpress/entities/ivo.cpp create mode 100644 engines/lastexpress/entities/ivo.h create mode 100644 engines/lastexpress/entities/kahina.cpp create mode 100644 engines/lastexpress/entities/kahina.h create mode 100644 engines/lastexpress/entities/kronos.cpp create mode 100644 engines/lastexpress/entities/kronos.h create mode 100644 engines/lastexpress/entities/mahmud.cpp create mode 100644 engines/lastexpress/entities/mahmud.h create mode 100644 engines/lastexpress/entities/max.cpp create mode 100644 engines/lastexpress/entities/max.h create mode 100644 engines/lastexpress/entities/mertens.cpp create mode 100644 engines/lastexpress/entities/mertens.h create mode 100644 engines/lastexpress/entities/milos.cpp create mode 100644 engines/lastexpress/entities/milos.h create mode 100644 engines/lastexpress/entities/mmeboutarel.cpp create mode 100644 engines/lastexpress/entities/mmeboutarel.h create mode 100644 engines/lastexpress/entities/pascale.cpp create mode 100644 engines/lastexpress/entities/pascale.h create mode 100644 engines/lastexpress/entities/rebecca.cpp create mode 100644 engines/lastexpress/entities/rebecca.h create mode 100644 engines/lastexpress/entities/salko.cpp create mode 100644 engines/lastexpress/entities/salko.h create mode 100644 engines/lastexpress/entities/servers0.cpp create mode 100644 engines/lastexpress/entities/servers0.h create mode 100644 engines/lastexpress/entities/servers1.cpp create mode 100644 engines/lastexpress/entities/servers1.h create mode 100644 engines/lastexpress/entities/sophie.cpp create mode 100644 engines/lastexpress/entities/sophie.h create mode 100644 engines/lastexpress/entities/tables.cpp create mode 100644 engines/lastexpress/entities/tables.h create mode 100644 engines/lastexpress/entities/tatiana.cpp create mode 100644 engines/lastexpress/entities/tatiana.h create mode 100644 engines/lastexpress/entities/train.cpp create mode 100644 engines/lastexpress/entities/train.h create mode 100644 engines/lastexpress/entities/vassili.cpp create mode 100644 engines/lastexpress/entities/vassili.h create mode 100644 engines/lastexpress/entities/verges.cpp create mode 100644 engines/lastexpress/entities/verges.h create mode 100644 engines/lastexpress/entities/vesna.cpp create mode 100644 engines/lastexpress/entities/vesna.h create mode 100644 engines/lastexpress/entities/yasmin.cpp create mode 100644 engines/lastexpress/entities/yasmin.h create mode 100644 engines/lastexpress/eventhandler.h create mode 100644 engines/lastexpress/game/action.cpp create mode 100644 engines/lastexpress/game/action.h create mode 100644 engines/lastexpress/game/beetle.cpp create mode 100644 engines/lastexpress/game/beetle.h create mode 100644 engines/lastexpress/game/entities.cpp create mode 100644 engines/lastexpress/game/entities.h create mode 100644 engines/lastexpress/game/fight.cpp create mode 100644 engines/lastexpress/game/fight.h create mode 100644 engines/lastexpress/game/inventory.cpp create mode 100644 engines/lastexpress/game/inventory.h create mode 100644 engines/lastexpress/game/logic.cpp create mode 100644 engines/lastexpress/game/logic.h create mode 100644 engines/lastexpress/game/menu.cpp create mode 100644 engines/lastexpress/game/menu.h create mode 100644 engines/lastexpress/game/object.cpp create mode 100644 engines/lastexpress/game/object.h create mode 100644 engines/lastexpress/game/savegame.cpp create mode 100644 engines/lastexpress/game/savegame.h create mode 100644 engines/lastexpress/game/savepoint.cpp create mode 100644 engines/lastexpress/game/savepoint.h create mode 100644 engines/lastexpress/game/scenes.cpp create mode 100644 engines/lastexpress/game/scenes.h create mode 100644 engines/lastexpress/game/sound.cpp create mode 100644 engines/lastexpress/game/sound.h create mode 100644 engines/lastexpress/game/state.cpp create mode 100644 engines/lastexpress/game/state.h create mode 100644 engines/lastexpress/graphics.cpp create mode 100644 engines/lastexpress/graphics.h create mode 100644 engines/lastexpress/helpers.h create mode 100644 engines/lastexpress/lastexpress.cpp create mode 100644 engines/lastexpress/lastexpress.h create mode 100644 engines/lastexpress/module.mk create mode 100644 engines/lastexpress/resource.cpp create mode 100644 engines/lastexpress/resource.h create mode 100644 engines/lastexpress/shared.h (limited to 'engines') diff --git a/engines/lastexpress/data/animation.cpp b/engines/lastexpress/data/animation.cpp new file mode 100644 index 0000000000..88973c4b0b --- /dev/null +++ b/engines/lastexpress/data/animation.cpp @@ -0,0 +1,300 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +// 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 "common/events.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() { + delete _overlay; + _overlay = NULL; + delete _background1; + _background1 = NULL; + delete _background2; + _background2 = NULL; + delete _audio; + _audio = NULL; + + _backgroundCurrent = 0; + _chunks.clear(); + + _currentChunk = NULL; + + 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_engine->_system->getMillis(); + + return true; +} + +bool Animation::process() { + if (!_currentChunk) + error("Animation::process - internal error: the current chunk iterator is invalid!"); + + if (_stream == NULL || _chunks.size() == 0) + error("Trying to show an animation before loading data"); + + // TODO: substract the time paused by the GUI + uint32 currentFrame = (uint32)(((float)(g_engine->_system->getMillis() - _startTime)) / 33.33f); + + // Process all chunks until the current frame + while (!_changed && 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_engine->_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(" 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 - internal error: the 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::MemoryReadStream *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 - internal error: the 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() { + while (!hasEnded() && !g_engine->getEventManager()->shouldQuit() && !g_engine->getEventManager()->shouldRTL()) { + process(); + + if (_changed) { + // Create a temporary surface to merge the overlay with the background + Graphics::Surface *s = new Graphics::Surface; + s->create(640, 480, 2); + + draw(s); + + // XXX: Update the screen + g_system->copyRectToScreen((byte *)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_engine->_system->delayMillis(20); + + // Handle right-click to interrupt animations + Common::Event ev; + while (g_engine->getEventManager()->pollEvent(ev)) { + if (ev.type == Common::EVENT_RBUTTONUP) { + // Stop audio + if (_audio) + _audio->finish(); + + // TODO start LNK file sound? + return; + } + } + } +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/data/animation.h b/engines/lastexpress/data/animation.h new file mode 100644 index 0000000000..ca1f7c6fa0 --- /dev/null +++ b/engines/lastexpress/data/animation.h @@ -0,0 +1,114 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_ANIMATION_H +#define LASTEXPRESS_ANIMATION_H + +/* + Animation format (.NIS) + + uint32 {4} - Number of chunks + + // for each chunk + uint16 {2} - Type + uint16 {2} - Tag + uint32 {4} - Size of chunk + byte {x} - Data (for "data" chunks: backgrounds, overlay & audio data) +*/ + +#include "lastexpress/drawable.h" + +#include "common/array.h" +#include "common/stream.h" + +namespace LastExpress { + +class AnimFrame; +class AppendableSound; + +class Animation : public Drawable { +public: + enum FlagType { + kFlagDefault = 16384, + kFlagProcess = 49152 + }; + + Animation(); + ~Animation(); + + bool load(Common::SeekableReadStream *stream, int flag = kFlagDefault); + bool process(); + bool hasEnded(); + Common::Rect draw(Graphics::Surface *surface); + void play(); + +private: + static const uint32 _soundBlockSize = 739; + + // despite their size field, info chunks don't have a payload + enum ChunkType { + kChunkTypeUnknown1 = 1, + kChunkTypeUnknown2 = 2, + kChunkTypeAudioInfo = 3, + kChunkTypeUnknown4 = 4, + kChunkTypeUnknown5 = 5, + kChunkTypeBackground1 = 10, + kChunkTypeSelectBackground1 = 11, + kChunkTypeBackground2 = 12, + kChunkTypeSelectBackground2 = 13, + kChunkTypeOverlay = 20, + kChunkTypeUpdate = 21, + kChunkTypeUpdateTransition = 22, + kChunkTypeSound1 = 30, + kChunkTypeSound2 = 31, + kChunkTypeAudioData = 32, + kChunkTypeAudioEnd = 99 + }; + + struct Chunk { + ChunkType type; + uint16 frame; + uint32 size; + }; + + void reset(); + AnimFrame *processChunkFrame(Common::SeekableReadStream *in, const Chunk &c) const; + void processChunkAudio(Common::SeekableReadStream *in, const Chunk &c); + + Common::SeekableReadStream *_stream; + Common::Array _chunks; + Common::Array::iterator _currentChunk; + AnimFrame *_overlay, *_background1, *_background2; + byte _backgroundCurrent; + AppendableSound *_audio; + + uint32 _startTime; + bool _changed; + int _flag; +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_ANIMATION_H diff --git a/engines/lastexpress/data/archive.cpp b/engines/lastexpress/data/archive.cpp new file mode 100644 index 0000000000..1a5b6905a3 --- /dev/null +++ b/engines/lastexpress/data/archive.cpp @@ -0,0 +1,115 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +// Based on the Xentax Wiki documentation: +// http://wiki.xentax.com/index.php/The_Last_Express_SND + +#include "lastexpress/data/archive.h" + +#include "lastexpress/debug.h" + +#include "common/debug.h" +#include "common/file.h" + +namespace LastExpress { + +HPFArchive::HPFArchive(const Common::String &path) { + _filename = path; + + // Open a stream to the archive + Common::SeekableReadStream *archive = SearchMan.createReadStreamForMember(_filename); + if (!archive) { + debugC(2, kLastExpressDebugResource, "Error opening file: %s", path.c_str()); + return; + } + + debugC(2, kLastExpressDebugResource, "Opened archive: %s", path.c_str()); + + // Read header to get the number of files + uint32 numFiles = archive->readUint32LE(); + debugC(3, kLastExpressDebugResource, "Number of files in archive: %d", numFiles); + + // Read the list of files + for (unsigned int i = 0; i < numFiles; ++i) { + char name[13]; + HPFEntry entry; + + archive->read(&name, sizeof(char) * _archiveNameSize); + entry.offset = archive->readUint32LE(); + entry.size = archive->readUint32LE(); + entry.isOnHD = archive->readUint16LE(); + + // Terminate string + name[12] = '\0'; + + Common::String filename(name); + filename.toLowercase(); + + _files[filename] = entry; + + //debugC(9, kLastExpressDebugResource, "File entry: %s (offset:%d - Size: %d - HD: %u)", &name, entry.offset, entry.size, entry.isOnHD); + } + + // Close stream + delete archive; +} + +bool HPFArchive::hasFile(const Common::String &name) { + return (_files.find(name) != _files.end()); +} + +int HPFArchive::listMembers(Common::ArchiveMemberList &list) { + int numMembers = 0; + + for (FileMap::const_iterator i = _files.begin(); i != _files.end(); ++i) { + list.push_back(Common::ArchiveMemberList::value_type(new Common::GenericArchiveMember(i->_key, this))); + numMembers++; + } + + return numMembers; +} + +Common::ArchiveMemberPtr HPFArchive::getMember(const Common::String &name) { + if (!hasFile(name)) + return Common::ArchiveMemberPtr(); + + return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(name, this)); +} + +Common::SeekableReadStream *HPFArchive::createReadStreamForMember(const Common::String &name) const { + FileMap::const_iterator fDesc = _files.find(name); + if (fDesc == _files.end()) + return NULL; + + Common::File *archive = new Common::File(); + if (!archive->open(_filename)) { + delete archive; + return NULL; + } + + return new Common::SeekableSubReadStream(archive, fDesc->_value.offset * _archiveSectorSize, fDesc->_value.offset * _archiveSectorSize + fDesc->_value.size * _archiveSectorSize, DisposeAfterUse::YES); +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/data/archive.h b/engines/lastexpress/data/archive.h new file mode 100644 index 0000000000..3860245bc5 --- /dev/null +++ b/engines/lastexpress/data/archive.h @@ -0,0 +1,75 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_HPFARCHIVE_H +#define LASTEXPRESS_HPFARCHIVE_H + +/* + Archive file format (.HPF) + + uint32 {4} - number of files + + // for each file + char {12} - name (zero-terminated) + uint32 {4} - offset (expressed in sectors of 2048 bytes) + uint32 {4} - size (expressed in sectors of 2048 bytes) + byte {2} - file status: 1 = on disk (ie. in HD.HPF), 0 = on CD +*/ + +#include "common/archive.h" + +namespace LastExpress { + +class HPFArchive : public Common::Archive { +public: + HPFArchive(const Common::String &path); + + bool hasFile(const Common::String &name); + int listMembers(Common::ArchiveMemberList &list); + Common::ArchiveMemberPtr getMember(const Common::String &name); + Common::SeekableReadStream *createReadStreamForMember(const Common::String &name) const; + + int count() { return _files.size(); } + +private: + static const unsigned int _archiveNameSize = 12; + static const unsigned int _archiveSectorSize = 2048; + + // File entry + struct HPFEntry { + uint32 offset; ///< Offset (in sectors of 2048 bytes) + uint32 size; ///< Size (in sectors of 2048 bytes) + uint16 isOnHD; ///< File location (1: on HD; 0: on CD) + }; + + typedef Common::HashMap FileMap; + + FileMap _files; ///< List of files + Common::String _filename; ///< Filename of the archive +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_HPFARCHIVE_H diff --git a/engines/lastexpress/data/background.cpp b/engines/lastexpress/data/background.cpp new file mode 100644 index 0000000000..94d7fb16c3 --- /dev/null +++ b/engines/lastexpress/data/background.cpp @@ -0,0 +1,141 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +// Based on Deniz Oezmen's code and Xentax Wiki documentation +// http://oezmen.eu/ +// http://wiki.xentax.com/index.php/The_Last_Express_BG + +#include "lastexpress/data/background.h" + +#include "lastexpress/debug.h" + +namespace LastExpress { + +Background::Background() : _data(NULL) { + memset(&_header, 0, sizeof(BackgroundHeader)); +} + +Background::~Background() { + delete[] _data; +} + +bool Background::load(Common::SeekableReadStream *stream) { + if (!stream) + return false; + + // Reset data + delete[] _data; + + // Load Background header + _header.posX = stream->readUint32LE(); + _header.posY = stream->readUint32LE(); + _header.width = stream->readUint32LE(); + _header.height = stream->readUint32LE(); + _header.redSize = stream->readUint32LE(); + _header.blueSize = stream->readUint32LE(); + _header.greenSize = stream->readUint32LE(); + + debugC(3, kLastExpressDebugGraphics, "Background Info: (%d, %d) - (%d x %d) - (%d, %d, %d)", + _header.posX, _header.posY, _header.width, _header.height, + _header.redSize, _header.blueSize, _header.greenSize); + + // Load and decompress Background channel data + uint32 numPix = _header.width * _header.height; + byte *dataR = decodeComponent(stream, _header.redSize, numPix); + byte *dataB = decodeComponent(stream, _header.blueSize, numPix); + byte *dataG = decodeComponent(stream, _header.greenSize, numPix); + + // Save to pixel buffer + // FIXME handle big-endian case + _data = new uint16[_header.width * _header.height]; + for (uint i = 0; i < _header.width * _header.height; i++) + _data[i] = (uint16)((dataR[i] << 10) + (dataG[i] << 5) + dataB[i]); + + // Cleanup buffers + delete[] dataR; + delete[] dataG; + delete[] dataB; + + delete stream; + + return true; +} + +Common::Rect Background::draw(Graphics::Surface *surface) { + if (!_data) { + debugC(2, kLastExpressDebugGraphics, "Trying to show a background before loading data!"); + return Common::Rect(); + } + + int i = 0; + for (uint16 y = 0; y < _header.height; y++) { + for (uint16 x = 0; x < _header.width; x++) { + surface->fillRect(Common::Rect((int16)(_header.posX + x), (int16)(_header.posY + y), (int16)(_header.posX + x + 1), (int16)(_header.posY + y + 1)), _data[i]); + i ++; + } + } + + return Common::Rect((int16)_header.posX, (int16)_header.posY, (int16)(_header.posX + _header.width), (int16)(_header.posY + _header.height)); +} + +byte *Background::decodeComponent(Common::SeekableReadStream *in, uint32 inSize, uint32 outSize) const { + // Create the destination array + byte *out = new byte[outSize]; + if (!out) + return NULL; + + // Initialize the decoding + uint32 inPos = 0; + uint32 outPos = 0; + + // Decode + while (inPos < inSize) { + byte inByte = in->readByte(); + inPos++; + + if (inByte < 0x80) { + // Direct decompression (RLE) + byte len = (inByte >> 5) + 1; + byte data = inByte & 0x1f; + for (int i = 0; i < len && outPos < outSize; i++) + out[outPos++] = data; + } else { + // Buffer back reference, 4096 byte window + // Take inByte and the following value as a big endian + // OfsLen while zeroing the first bit + uint16 ofsLen = ((inByte & 0x7F) << 8) | in->readByte(); + inPos++; + + int32 len = (ofsLen >> 12) + 3; + int32 hisPos = (int32)(outPos + (ofsLen & 0x0FFF) - 4096); + for (int i = 0; i < len && outPos < outSize; i++) + out[outPos++] = out[hisPos++]; + } + } + + return out; +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/data/background.h b/engines/lastexpress/data/background.h new file mode 100644 index 0000000000..fc1cc26fa4 --- /dev/null +++ b/engines/lastexpress/data/background.h @@ -0,0 +1,81 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_BACKGROUND_H +#define LASTEXPRESS_BACKGROUND_H + +/* + Background file format (.BG) + + header: + uint32 {4} - position X on screen + uint32 {4} - position Y on screen + uint32 {4} - image width + uint32 {4} - image height + uint32 {4} - red colour channel data size + uint32 {4} - blue colour channel data size + uint32 {4} - green colour channel data size + + data: + byte {x} - red colour channel data + byte {x} - blue colour channel data + byte {x} - green colour channel data +*/ + +#include "lastexpress/drawable.h" + +#include "common/stream.h" + +namespace LastExpress { + +class Background : public Drawable { +public: + Background(); + ~Background(); + + bool load(Common::SeekableReadStream *stream); + + Common::Rect draw(Graphics::Surface *surface); + +private: + struct BackgroundHeader { + uint32 posX; ///< position X on screen + uint32 posY; ///< position Y on screen + uint32 width; ///< image width + uint32 height; ///< image height + uint32 redSize; ///< red color channel data size + uint32 blueSize; ///< blue color channel data size + uint32 greenSize; ///< green color channel data size + }; + + BackgroundHeader _header; + uint16 *_data; ///< decoded background data + + byte *decodeComponent(Common::SeekableReadStream *in, uint32 inSize, uint32 outSize) const; +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_BACKGROUND_H diff --git a/engines/lastexpress/data/cursor.cpp b/engines/lastexpress/data/cursor.cpp new file mode 100644 index 0000000000..4e7003578a --- /dev/null +++ b/engines/lastexpress/data/cursor.cpp @@ -0,0 +1,144 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/data/cursor.h" + +#include "lastexpress/lastexpress.h" + +#include "common/system.h" +#include "graphics/cursorman.h" + +namespace LastExpress { + +Cursor::Cursor() : _current(kCursorMAX) { + memset(&_cursors, 0, sizeof(_cursors)); +} + +bool Cursor::load(Common::SeekableReadStream *stream) { + if (!stream) + return false; + + // Load the whole file to memory + Common::MemoryReadStream *data = stream->readStream((uint32) stream->size()); + delete stream; + if (!data) + return false; + + // Read the hotspot data + for (int i = 0; i < kCursorMAX; i++) { + _cursors[i].hotspotX = data->readUint16LE(); + _cursors[i].hotspotY = data->readUint16LE(); + debugC(15, kLastExpressDebugCursor | kLastExpressDebugAll, + "Cursor %d hotspot x: %d, hotspot y: %d", + i, _cursors[i].hotspotX, _cursors[i].hotspotY); + } + + // Read the pixel data + for (int i = 0; i < kCursorMAX; i++) + for (int pix = 0; pix < 32 * 32; pix++) + _cursors[i].image[pix] = data->readUint16LE(); + + delete data; + return true; +} + +void Cursor::show(bool visible) const { + CursorMan.showMouse(visible); +} + +bool Cursor::checkStyle(CursorStyle style) const { + if (style >= kCursorMAX) { + debugC(2, kLastExpressDebugGraphics, "Trying to use an invalid cursor style: was %d, max %d", (int)style, kCursorMAX); + return false; + } + + return true; +} + +void Cursor::setStyle(CursorStyle style) { + if (!checkStyle(style)) + return; + + if (style == _current) + return; + + debugC(10, kLastExpressDebugCursor | kLastExpressDebugAll, "Cursor: setting style: %d", style); + + // Save the new cursor + _current = style; + + // Reuse the screen pixel format + Graphics::PixelFormat pf = g_system->getScreenFormat(); + CursorMan.replaceCursor((const byte *)getCursorImage(style), + 32, 32, _cursors[style].hotspotX, _cursors[style].hotspotY, + 0, 1, &pf); +} + +const uint16 *Cursor::getCursorImage(CursorStyle style) const { + if (!checkStyle(style)) + return NULL; + + return _cursors[style].image; +} + + +Icon::Icon(CursorStyle style) : _style(style), _x(0), _y(0), _brightness(100) {} + +void Icon::setPosition(int16 x, int16 y) { + _x = x; + _y = y; +} + +void Icon::setBrightness(uint brightness) { + assert(brightness <= 100); + + _brightness = (uint8)brightness; +} + +Common::Rect Icon::draw(Graphics::Surface *surface) { + const uint16 *image = ((LastExpressEngine *)g_engine)->getCursor()->getCursorImage((CursorStyle)_style); + if (!image) + return Common::Rect(); + + // TODO adjust brightness. The original game seems to be using a table for that (at least in the highlighting case) + for (int j = 0; j < 32; j++) { + uint16 *s = (uint16 *)surface->getBasePtr(_x, _y + j); + for (int i = 0; i < 32; i++) { + if (_brightness == 100) + *s = *image; + else + // HACK change color to show highlight + *s = (*image & 0x739C) >> 1; + + // Update the image and surface pointers + image++; + s++; + } + } + + return Common::Rect(_x, _y, _x + 32, _y + 32); +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/data/cursor.h b/engines/lastexpress/data/cursor.h new file mode 100644 index 0000000000..992266569f --- /dev/null +++ b/engines/lastexpress/data/cursor.h @@ -0,0 +1,93 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_CURSOR_H +#define LASTEXPRESS_CURSOR_H + +/* + Cursor format (CURSORS.TBM) + + style table: + (for each cursor) + uint16 {2} - hotspot X + uint16 {2} - hotspot Y + + data: + (for each cursor) + uint16 {32*32} - cursor data +*/ + +#include "lastexpress/drawable.h" + +#include "lastexpress/shared.h" + +#include "common/stream.h" + +namespace LastExpress { + +class Icon : public Drawable { +public: + Icon(CursorStyle style); + + void setPosition(int16 x, int16 y); + void setBrightness(uint brightness); + Common::Rect draw(Graphics::Surface *surface); + +private: + CursorStyle _style; + int16 _x, _y; + uint8 _brightness; +}; + +class Cursor { +public: + Cursor(); + + bool load(Common::SeekableReadStream *stream); + void show(bool visible) const; + + void setStyle(CursorStyle style); + CursorStyle getStyle() const { return _current; } + +private: + // Style + CursorStyle _current; + + // Cursors data + struct { + uint16 image[32 * 32]; + uint16 hotspotX, hotspotY; + } _cursors[kCursorMAX]; + + bool checkStyle(CursorStyle style) const; + const uint16 *getCursorImage(CursorStyle style) const; + + // Only allow full access for drawing (needed for getCursorImage) + friend Common::Rect Icon::draw(Graphics::Surface *surface); +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_CURSOR_H diff --git a/engines/lastexpress/data/font.cpp b/engines/lastexpress/data/font.cpp new file mode 100644 index 0000000000..5f4b3b40b8 --- /dev/null +++ b/engines/lastexpress/data/font.cpp @@ -0,0 +1,205 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/data/font.h" + +#include "common/system.h" + +namespace LastExpress { + +Font::Font() : _numGlyphs(0), _glyphs(NULL), _glyphWidths(0) { + memset(&_palette, 0, sizeof(_palette)); + memset(&_charMap, 0, sizeof(_charMap)); +} + +Font::~Font() { + reset(); +} + +void Font::reset() { + delete[] _glyphs; + delete[] _glyphWidths; +} + +bool Font::load(Common::SeekableReadStream *stream) { + if (!stream) + return false; + + // Reset data + reset(); + + // Read the palette + for (uint i = 0; i < _paletteSize; i++) { + _palette[i] = stream->readUint16LE(); + } + + // Read the character map + stream->read(_charMap, _charMapSize); + + // Read the glyphs + _numGlyphs = stream->readUint16LE(); + _glyphs = new byte[_numGlyphs * 18 * 8]; + stream->read(_glyphs, _numGlyphs * 18 * 8); + + // TODO: Read something else? + //uint16 unknown = fontFile->readByte(); + //warning("unknown = %d", unknown); + //warning("pos = %d", fontFile->pos()); + //warning("left = %d", fontFile->size() - fontFile->pos()); + + //while (!fontFile->eos()) { + //unknown = fontFile->readByte(); + //warning("val = %d", unknown); + //} + + // Precalculate glyph widths + _glyphWidths = new byte[_numGlyphs]; + for (uint16 i = 0; i < _numGlyphs; i++) { + _glyphWidths[i] = getGlyphWidth(i); + } + + delete stream; + + return true; +} + + +uint16 Font::getCharGlyph(uint16 c) const { + //warning("%c", c); + if (c >= 0x200) + error("Express::Font: Invalid character %d", c); + + return _charMap[c]; +} + +byte *Font::getGlyphImg(uint16 g) { + if (!_glyphs) + error("Express::getGlyphImg: Invalid glyphs!"); + + if (g >= _numGlyphs) + error("Express::getGlyphImg: Invalid glyph %d (%d available)", g, _numGlyphs); + + return _glyphs + g * 18 * 8; +} + +uint8 Font::getGlyphWidth(uint16 g) { + byte *p = getGlyphImg(g); + + uint8 maxLineWidth = 0; + for (int j = 0; j < 18; j++) { + uint8 currentLineWidth = 0; + for (uint8 i = 0; i < 16; i++) { + byte index; + if (i % 2) + index = *p & 0xf; + else + index = *p >> 4; + uint16 color = _palette[index]; + if (color != 0x1f) + currentLineWidth = i; + if (i % 2) + p++; + } + if (currentLineWidth > maxLineWidth) + maxLineWidth = currentLineWidth; + } + + return maxLineWidth; +} + +byte *Font::getCharImg(uint16 c) { + return getGlyphImg(getCharGlyph(c)); +} + +uint8 Font::getCharWidth(uint16 c) const{ + if (c == 0x20) { + // Space is a special case + // TODO: this is an arbitrary value + return 10; + } else { + if (!_glyphWidths) + error("Express::getCharWidth: Invalid glyphs widths!"); + + return _glyphWidths[getCharGlyph(c)]; + } +} + +uint16 Font::getStringWidth(Common::String str) const { + uint16 width = 0; + for (uint i = 0; i < str.size(); i++) + width += getCharWidth((unsigned) (int)str[i]); + + return width; +} + +uint16 Font::getStringWidth(const uint16 *str, uint16 length) const { + uint16 width = 0; + for (uint i = 0; i < length; i++) + width += getCharWidth(str[i]); + + return width; +} + +void Font::drawChar(Graphics::Surface *surface, int16 x, int16 y, uint16 c) { + byte *p = getCharImg(c); + + for (int16 j = 0; j < 18; j++) { + for (int16 i = 0; i < 16; i++) { + byte index; + if (i % 2) + index = *p & 0xf; + else + index = *p >> 4; + uint16 color = _palette[index]; + if (color != 0x1f) { + surface->fillRect(Common::Rect(x+i, y+j, x+i+1, y+j+1), color); + } + if (i % 2) + p++; + } + } +} + +Common::Rect Font::drawString(Graphics::Surface *surface, int16 x, int16 y, Common::String str) { + int16 currentX = x; + for (uint i = 0; i < str.size(); i++) { + drawChar(surface, currentX, y, (unsigned) (int)str[i]); + currentX += getCharWidth((unsigned) (int)str[i]); + } + + return Common::Rect(x, y, x + currentX, y + (int16)_charHeight); +} + +Common::Rect Font::drawString(Graphics::Surface *surface, int16 x, int16 y, const uint16 *str, uint16 length) { + int16 currentX = x; + for (uint i = 0; i < length; i++) { + drawChar(surface, currentX, y, str[i]); + currentX += getCharWidth(str[i]); + } + + return Common::Rect(x, y, x + currentX, y + (int16)_charHeight); +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/data/font.h b/engines/lastexpress/data/font.h new file mode 100644 index 0000000000..457c0c3432 --- /dev/null +++ b/engines/lastexpress/data/font.h @@ -0,0 +1,82 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_FONT_H +#define LASTEXPRESS_FONT_H + +/* + Font format (FONT.DAT) + + uint16 {40} - Palette data + byte {200} - Character map + uint16 {2} - Number of glyphs + + // For each glyph + byte {18*8} - Glyph data + + byte {x} - Unknown data (probably just garbage) +*/ + +#include "common/stream.h" +#include "graphics/surface.h" + +namespace LastExpress { + +class Font { +public: + Font(); + ~Font(); + + bool load(Common::SeekableReadStream *stream); + Common::Rect drawString(Graphics::Surface *surface, int16 x, int16 y, Common::String str); + Common::Rect drawString(Graphics::Surface *surface, int16 x, int16 y, const uint16 *str, uint16 length); + +private: + static const uint32 _paletteSize = 0x10; + static const uint32 _charMapSize = 0x200; + static const uint32 _charHeight = 16; + + void reset(); + + uint16 getCharGlyph(uint16 c) const; + byte *getGlyphImg(uint16 g); + uint8 getGlyphWidth(uint16 g); + byte *getCharImg(uint16 c); + uint8 getCharWidth(uint16 c) const; + uint16 getStringWidth(Common::String str) const; + uint16 getStringWidth(const uint16 *str, uint16 length) const; + void drawChar(Graphics::Surface *surface, int16 x, int16 y, uint16 c); + + // Font data + uint16 _palette[_paletteSize]; + uint8 _charMap[_charMapSize]; + uint16 _numGlyphs; + byte *_glyphs; + uint8 *_glyphWidths; +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_FONT_H diff --git a/engines/lastexpress/data/scene.cpp b/engines/lastexpress/data/scene.cpp new file mode 100644 index 0000000000..e1be515bbc --- /dev/null +++ b/engines/lastexpress/data/scene.cpp @@ -0,0 +1,292 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/data/scene.h" + +#include "lastexpress/data/background.h" + +#include "lastexpress/lastexpress.h" +#include "lastexpress/resource.h" + +namespace LastExpress { + +SceneHotspot *SceneHotspot::load(Common::SeekableReadStream *stream) { + SceneHotspot *hs = new SceneHotspot(); + + // Rect + hs->rect.left = (int16)stream->readUint16LE(); + hs->rect.right = (int16)stream->readUint16LE(); + hs->rect.top = (int16)stream->readUint16LE(); + hs->rect.bottom = (int16)stream->readUint16LE(); + + hs->coordsOffset = stream->readUint32LE(); + hs->scene = (SceneIndex)stream->readUint16LE(); + hs->location = stream->readByte(); + hs->action = (Action)stream->readByte(); + hs->param1 = stream->readByte(); + hs->param2 = stream->readByte(); + hs->param3 = stream->readByte(); + hs->cursor = stream->readByte(); + hs->next = stream->readUint32LE(); + + debugC(10, kLastExpressDebugScenes, "\thotspot: scene=%d location=%02d action=%d param1=%02d param2=%02d param3=%02d cursor=%02d rect=(%d, %d)x(%d,%d)", + hs->scene, hs->location, hs->action, hs->param1, hs->param2, hs->param3, hs->cursor, hs->rect.left, hs->rect.top, hs->rect.right, hs->rect.bottom); + debugC(10, kLastExpressDebugScenes, "\t coords=%d next=%d ", hs->coordsOffset, hs->next); + + // Read all coords data + uint32 offset = hs->coordsOffset; + while (offset != 0) { + + SceneCoord *sceneCoord = new SceneCoord; + + stream->seek(offset, SEEK_SET); + + sceneCoord->field_0 = stream->readSint32LE(); + sceneCoord->field_4 = stream->readSint32LE(); + sceneCoord->field_8 = stream->readByte(); + sceneCoord->next = stream->readUint32LE(); + + hs->_coords.push_back(sceneCoord); + + offset = sceneCoord->next; + } + + return hs; +} + +Common::String SceneHotspot::toString() const { + Common::String output = ""; + + output += Common::String::printf(" hotspot: scene=%d location=%02d action=%d param1=%02d param2=%02d param3=%02d cursor=%02d rect=(%d, %d)x(%d,%d)", + scene, location, action, param1, param2, param3, cursor, rect.left, rect.top, rect.right, rect.bottom); + + return output; +} + +bool SceneHotspot::isInside(const Common::Point &point) { + + bool contains = rect.contains(point); + + if (_coords.empty() || !contains) + return contains; + + // Checks extended coordinates + for (uint i = 0; i < _coords.size(); i++) { + + SceneCoord *sCoord = _coords[i]; + + bool cont; + if (sCoord->field_8) + cont = (sCoord->field_4 + point.x * sCoord->field_0 + 1000 * point.y) >= 0; + else + cont = (sCoord->field_4 + point.x * sCoord->field_0 + 1000 * point.y) <= 0; + + if (!cont) + return false; + } + + return true; +} + +////////////////////////////////////////////////////////////////////////// +// Scene +Scene::~Scene() { + // Free the hotspots + for (int i = 0; i < (int)_hotspots.size(); i++) + delete _hotspots[i]; +} + +Scene *Scene::load(Common::SeekableReadStream *stream) { + Scene *scene = new Scene(); + + stream->read(&scene->_name, sizeof(scene->_name)); + scene->_sig = stream->readByte(); + scene->entityPosition = (EntityPosition)stream->readUint16LE();; + scene->location = (Location)stream->readUint16LE(); + scene->car = (CarIndex)stream->readUint16LE(); + scene->position = stream->readByte(); + scene->type = (Type)stream->readByte(); + scene->param1 = stream->readByte(); + scene->param2 = stream->readByte(); + scene->param3 = stream->readByte(); + scene->_hotspot = stream->readUint32LE(); + + return scene; +} + +void Scene::loadHotspots(Common::SeekableReadStream *stream) { + if (!_hotspots.empty()) + return; + + debugC(10, kLastExpressDebugScenes, "Scene: name=%s, sig=%02d, entityPosition=%d, location=%d", _name, _sig, entityPosition, location); + debugC(10, kLastExpressDebugScenes, "\tcar=%02d, position=%02d, type=%02d, param1=%02d", car, position, type, param1); + debugC(10, kLastExpressDebugScenes, "\tparam2=%02d, param3=%02d, hotspot=%d\n", param2, param3, _hotspot); + + // Read all hotspots + if (_hotspot != 0) { + stream->seek((int32)_hotspot, SEEK_SET); + SceneHotspot *hotspot = SceneHotspot::load(stream); + while (hotspot) { + _hotspots.push_back(hotspot); + + if (hotspot->next == 0) + break; + + stream->seek((int32)hotspot->next, SEEK_SET); + hotspot = SceneHotspot::load(stream); + } + } +} + +bool Scene::checkHotSpot(const Common::Point &coord, SceneHotspot **hotspot) { + bool found = false; + int _location = 0; + + for (int i = 0; i < (int)_hotspots.size(); i++) { + if (_hotspots[i]->isInside(coord)) { + if (_location <= _hotspots[i]->location) { + _location = _hotspots[i]->location; + *hotspot = _hotspots[i]; + found = true; + } + } + } + + return found; +} + +SceneHotspot *Scene::getHotspot(uint index) { + if (_hotspots.empty()) + error("Scene::getHotspot: scene does not have any hotspots!"); + + if (index >= _hotspots.size()) + error("Scene::getHotspot: invalid index (was: %d, max: %d)", index, _hotspots.size() - 1); + + return _hotspots[index]; +} + +Common::Rect Scene::draw(Graphics::Surface *surface) { + // Safety checks + Common::Rect rect; + + Common::String sceneName(_name); + sceneName.trim(); + if (sceneName.empty()) + error("Scene::draw: This scene is not a valid drawing scene!"); + + // Load background + Background *background = ((LastExpressEngine *)g_engine)->getResourceManager()->loadBackground(sceneName); + if (background) { + rect = background->draw(surface); + delete background; + } + + return rect; +} + +Common::String Scene::toString() { + Common::String output = ""; + + output += Common::String::printf("Scene: name=%s, sig=%02d, entityPosition=%d, location=%d\n", _name, _sig, entityPosition, location); + output += Common::String::printf(" car=%02d, position=%02d, type=%02d, param1=%02d\n", car, position, type, param1); + output += Common::String::printf(" param2=%02d, param3=%02d, hotspot=%d\n", param2, param3, _hotspot); + + // Hotspots + if (_hotspots.size() != 0) { + output += "\nHotspots:\n"; + for (int i = 0; i < (int)_hotspots.size(); i++) + output += _hotspots[i]->toString() + "\n"; + } + + return output; +} + +////////////////////////////////////////////////////////////////////////// +// SceneLoader +SceneLoader::SceneLoader() : _stream(NULL) {} + +SceneLoader::~SceneLoader() { + clear(); +} + +void SceneLoader::clear() { + // Remove all scenes + for (int i = 0; i < (int)_scenes.size(); i++) + delete _scenes[i]; + + _scenes.clear(); + + delete _stream; +} + +bool SceneLoader::load(Common::SeekableReadStream *stream) { + if (!stream) + return false; + + clear(); + + _stream = stream; + + // Read the default scene to get the total number of scenes + Scene *header = Scene::load(_stream); + if (!header) + error("SceneLoader::load: Invalid data file!"); + + debugC(2, kLastExpressDebugScenes, " found %d entries", header->entityPosition); /* Header entityPosition is the scene count */ + + if (header->entityPosition > 2500) { + delete header; + + return false; + } + + _scenes.push_back(header); + + // Read all the chunks + for (uint i = 0; i < (uint)header->entityPosition; ++i) { + Scene *scene = Scene::load(_stream); + if (!scene) + break; + + _scenes.push_back(scene); + } + + return true; +} + +Scene *SceneLoader::get(SceneIndex index) { + if (_scenes.empty()) + return NULL; + + if (index > _scenes.size()) + return NULL; + + // Load the hotspots if needed + _scenes[(int)index]->loadHotspots(_stream); + + return _scenes[(int)index]; +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/data/scene.h b/engines/lastexpress/data/scene.h new file mode 100644 index 0000000000..ae2f3d2d28 --- /dev/null +++ b/engines/lastexpress/data/scene.h @@ -0,0 +1,245 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_SCENE_H +#define LASTEXPRESS_SCENE_H + +/* + Scene format (CDTRAIN.DAT) + + (text:00484750) + header (24 bytes) + char {8} - entry name (null terminated) + byte {1} - 0xCD + uint16 {2} - number of scenes (for first entry - always 0 after?) + uint16 {2} - 11 ?? + uint16 {2} - car + byte {1} - camera position (used to get the proper sequences to show) + byte {1} - type + byte {1} - param1 + byte {1} - param2 + byte {1} - param3 + uint32 {4} - Offset to hotspot info struct + + probably contains cursor type too / scene index : 0 - 2500 (max) + + hotspot info (24 bytes) + uint16 {2} - left + uint16 {2} - right + uint16 {2} - top + uint16 {2} - bottom + uint32 {4} - scene coords data + uint16 {2} - scene + byte {1} - location; + byte {1} - action; + byte {1} - param1; + byte {1} - param2; + byte {1} - param3 + byte {1} - cursor + uint32{4} - offset to next hotpost + + coords data (9 bytes) + uint32 {4} - ?? + uint32 {4} - ?? + byte {1} - ?? + uint32 {4} - offset to next coords data structure + +*/ + +#include "lastexpress/drawable.h" +#include "lastexpress/shared.h" + +#include "common/array.h" +#include "common/stream.h" + +namespace LastExpress { + +class Scene; + +class SceneHotspot { +public: + enum Action { + kActionInventory = 1, + kActionSavePoint = 2, + kActionPlaySound = 3, + kActionPlayMusic = 4, + kActionKnockOnDoor = 5, + kActionCompartment = 6, + kActionPlaySounds = 7, + kActionPlayAnimation = 8, + kActionOpenCloseObject = 9, + kActionObjectUpdateLocation2 = 10, + kActionSetItemLocation = 11, + kAction12 = 12, + kActionPickItem = 13, + kActionDropItem = 14, + kAction15 = 15, + kActionEnterCompartment = 16, + kActionGetOutsideTrain = 18, + kActionSlip = 19, + kActionGetInsideTrain = 20, + kActionClimbUpTrain = 21, + kActionClimbDownTrain = 22, + kActionJumpUpDownTrain = 23, + kActionUnbound = 24, + kAction25 = 25, + kAction26 = 26, + kAction27 = 27, + kActionConcertSitCough = 28, + kAction29 = 29, + kActionCatchBeetle = 30, + kActionExitCompartment = 31, + kAction32 = 32, + KActionUseWhistle = 33, + kActionOpenMatchBox = 34, + kActionOpenBed = 35, + kActionDialog = 37, + kActionEggBox = 38, + kAction39 = 39, + kActionBed = 40, + kAction41 = 41, + kAction42 = 42, + kActionSwitchChapter = 43, + kAction44 = 44 + }; + + struct SceneCoord { + int32 field_0; + int32 field_4; + byte field_8; + uint32 next; + + SceneCoord() { + field_0 = 0; + field_4 = 0; + field_8 = 0; + next = 0; + } + }; + + Common::Rect rect; + uint32 coordsOffset; + SceneIndex scene; + byte location; + Action action; + byte param1; + byte param2; + byte param3; + byte cursor; + uint32 next; + + SceneHotspot() {} + static SceneHotspot *load(Common::SeekableReadStream *stream); + + bool isInside(const Common::Point &point); + + Common::String toString() const; + +private: + Common::Array _coords; +}; + +class SceneLoader { +public: + SceneLoader(); + ~SceneLoader(); + + bool load(Common::SeekableReadStream *stream); + Scene *get(SceneIndex index); + + uint32 count() const { return _scenes.size() - 1; }; + +private: + Common::SeekableReadStream *_stream; + Common::Array _scenes; + + void clear(); +}; + +class Scene : public Drawable { +public: + // Enumerations + enum Type { + // PreProcess + kTypeObject = 1, + kTypeItem = 2, + kTypeItem2 = 3, + kTypeObjectItem = 4, + kTypeItem3 = 5, + kTypeObjectLocation2 = 6, + kTypeCompartments = 7, + kTypeCompartmentsItem = 8, + + // PostProcess + kTypeList = 128, + kTypeSavePointChapter = 129, + kTypeLoadBeetleSequences = 130, + kTypeGameOver = 131, + kTypeReadText = 132, + kType133 = 133 + }; + + // Data + EntityPosition entityPosition; + Location location; + CarIndex car; + Position position; + Type type; + byte param1; + byte param2; + byte param3; + + ~Scene(); + + Common::Rect draw(Graphics::Surface *surface); + + // Hotspots + Common::Array *getHotspots() { return &_hotspots; } + bool checkHotSpot(const Common::Point &coord, SceneHotspot **hotspot); + SceneHotspot *getHotspot(uint index = 0); + + Common::String toString(); + +private: + char _name[8]; + byte _sig; + uint32 _hotspot; + + Scene() {} + Common::Array _hotspots; + + static Scene *load(Common::SeekableReadStream *stream); + void loadHotspots(Common::SeekableReadStream *stream); + + void clear(); + + // Only allow full access for loading the scene and the hotspots + friend bool SceneLoader::load(Common::SeekableReadStream *stream); + friend Scene *SceneLoader::get(SceneIndex index); +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_SCENE_H diff --git a/engines/lastexpress/data/sequence.cpp b/engines/lastexpress/data/sequence.cpp new file mode 100644 index 0000000000..be8b14fc27 --- /dev/null +++ b/engines/lastexpress/data/sequence.cpp @@ -0,0 +1,475 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +// Based on Deniz Oezmen's code: http://oezmen.eu/ + +#include "lastexpress/data/sequence.h" + +#include "lastexpress/debug.h" + +namespace LastExpress { + +void FrameInfo::read(Common::SeekableReadStream *in, bool isSequence) { + // Save the current position + int32 basePos = in->pos(); + + dataOffset = in->readUint32LE(); + unknown = in->readUint32LE(); + paletteOffset = in->readUint32LE(); + xPos1 = in->readUint32LE(); + yPos1 = in->readUint32LE(); + xPos2 = in->readUint32LE(); + yPos2 = in->readUint32LE(); + initialSkip = in->readUint32LE(); + decompressedEndOffset = in->readUint32LE(); + + // Read the compression type for NIS files + if (!isSequence) { + in->seek(basePos + 0x124); + } else { + hotspot.left = (int16)in->readUint16LE(); + hotspot.right = (int16)in->readUint16LE(); + hotspot.top = (int16)in->readUint16LE(); + hotspot.bottom = (int16)in->readUint16LE(); + } + + compressionType = in->readByte(); + subType = (FrameSubType)in->readByte(); + + // Sequence information + field_2E = in->readByte(); + keepPreviousFrame = in->readByte(); + field_30 = in->readByte(); + field_31 = in->readByte(); + soundAction = in->readByte(); + field_33 = in->readByte(); + position = in->readByte(); + field_35 = in->readByte(); + field_36 = in->readUint16LE(); + field_38 = in->readUint32LE(); + entityPosition = (EntityPosition)in->readUint16LE(); + location = in->readUint16LE(); + next = in->readUint32LE(); +} + + +// AnimFrame + +AnimFrame::AnimFrame(Common::SeekableReadStream *in, const FrameInfo &f) : _palette(NULL) { + _palSize = 1; + // TODO: use just the needed rectangle + _image.create(640, 480, 1); + + //debugC(6, kLastExpressDebugGraphics, " Offsets: data=%d, unknown=%d, palette=%d", f.dataOffset, f.unknown, f.paletteOffset); + //debugC(6, kLastExpressDebugGraphics, " Position: (%d, %d) - (%d, %d)", f.xPos1, f.yPos1, f.xPos2, f.yPos2); + //debugC(6, kLastExpressDebugGraphics, " Initial Skip: %d", f.initialSkip); + //debugC(6, kLastExpressDebugGraphics, " Decompressed end offset: %d", f.decompressedEndOffset); + //debugC(6, kLastExpressDebugGraphics, " Hotspot: (%d, %d) x (%d, %d)\n", f.hotspot.left, f.hotspot.top, f.hotspot.right, f.hotspot.bottom); + //debugC(6, kLastExpressDebugGraphics, " Compression type: %u / %u", f.compressionType, f.subType); + //debugC(6, kLastExpressDebugGraphics, " Unknown: %u - %u - %u - %u - %u - %u - %u - %d", f.field_2E, f.field_2F, f.field_30, f.field_31, f.field_33, f.field_35, f.field_36, f.field_38); + //debugC(6, kLastExpressDebugGraphics, " Sound action: %u", f.soundAction); + //debugC(6, kLastExpressDebugGraphics, " Position: %d", f.position); + //debugC(6, kLastExpressDebugGraphics, " Entity Position: %d", f.entityPosition); + //debugC(6, kLastExpressDebugGraphics, " Location: %d", f.location); + //debugC(6, kLastExpressDebugGraphics, " next: %d", f.next); + + switch (f.compressionType) { + case 0: + // Empty frame + break; + case 3: + decomp3(in, f); + break; + case 4: + decomp4(in, f); + break; + case 5: + decomp5(in, f); + break; + case 7: + decomp7(in, f); + break; + case 255: + decompFF(in, f); + break; + default: + error("Unknown frame compression: %d", f.compressionType); + } + + readPalette(in, f); + _rect = Common::Rect((int16)f.xPos1, (int16)f.yPos1, (int16)f.xPos2, (int16)f.yPos2); + //_rect.debugPrint(0, "Frame rect:"); +} + +AnimFrame::~AnimFrame() { + _image.free(); + delete[] _palette; +} + +Common::Rect AnimFrame::draw(Graphics::Surface *s) { + byte *inp = (byte *)_image.pixels; + uint16 *outp = (uint16 *)s->pixels; + for (int i = 0; i < 640 * 480; i++, inp++, outp++) { + if (*inp) + *outp = _palette[*inp]; + } + return _rect; +} + +void AnimFrame::readPalette(Common::SeekableReadStream *in, const FrameInfo &f) { + // Read the palette + in->seek((int)f.paletteOffset); + _palette = new uint16[_palSize]; + for (uint32 i = 0; i < _palSize; i++) { + _palette[i] = in->readUint16LE(); + } +} + +void AnimFrame::decomp3(Common::SeekableReadStream *in, const FrameInfo &f) { + decomp34(in, f, 0x7, 3); +} + +void AnimFrame::decomp4(Common::SeekableReadStream *in, const FrameInfo &f) { + decomp34(in, f, 0xf, 4); +} + +void AnimFrame::decomp34(Common::SeekableReadStream *in, const FrameInfo &f, byte mask, byte shift) { + byte *p = (byte *)_image.getBasePtr(0, 0); + + uint32 skip = f.initialSkip / 2; + uint32 size = f.decompressedEndOffset / 2; + //warning("skip: %d, %d", skip % 640, skip / 640); + //warning("size: %d, %d", size % 640, size / 640); + //assert (f.yPos1 == skip / 640); + //assert (f.yPos2 == size / 640); + + uint32 numBlanks = 640 - (f.xPos2 - f.xPos1); + + in->seek((int)f.dataOffset); + for (uint32 out = skip; out < size; ) { + uint16 opcode = in->readByte(); + + if (opcode & 0x80) { + if (opcode & 0x40) { + opcode &= 0x3f; + out += numBlanks + opcode + 1; + } else { + opcode &= 0x3f; + if (opcode & 0x20) { + opcode = ((opcode & 0x1f) << 8) + in->readByte(); + if (opcode & 0x1000) { + out += opcode & 0xfff; + continue; + } + } + out += opcode + 2; + } + } else { + byte value = opcode & mask; + opcode >>= shift; + if (_palSize <= value) + _palSize = value + 1; + if (!opcode) + opcode = in->readByte(); + for (int i = 0; i < opcode; i++, out++) { + p[out] = value; + } + } + } +} + +void AnimFrame::decomp5(Common::SeekableReadStream *in, const FrameInfo &f) { + byte *p = (byte *)_image.getBasePtr(0, 0); + + uint32 skip = f.initialSkip / 2; + uint32 size = f.decompressedEndOffset / 2; + //warning("skip: %d, %d", skip % 640, skip / 640); + //warning("size: %d, %d", size % 640, size / 640); + //assert (f.yPos1 == skip / 640); + //assert (f.yPos2 == size / 640); + + in->seek((int)f.dataOffset); + for (uint32 out = skip; out < size; ) { + uint16 opcode = in->readByte(); + if (!(opcode & 0x1f)) { + opcode = (uint16)((opcode << 3) + in->readByte()); + if (opcode & 0x400) { + // skip these 10 bits + out += (opcode & 0x3ff); + } else { + out += opcode + 2; + } + } else { + byte value = opcode & 0x1f; + opcode >>= 5; + if (_palSize <= value) + _palSize = value + 1; + if (!opcode) + opcode = in->readByte(); + for (int i = 0; i < opcode; i++, out++) { + p[out] = value; + } + } + } +} + +void AnimFrame::decomp7(Common::SeekableReadStream *in, const FrameInfo &f) { + byte *p = (byte *)_image.getBasePtr(0, 0); + + uint32 skip = f.initialSkip / 2; + uint32 size = f.decompressedEndOffset / 2; + //warning("skip: %d, %d", skip % 640, skip / 640); + //warning("size: %d, %d", size % 640, size / 640); + //assert (f.yPos1 == skip / 640); + //assert (f.yPos2 == size / 640); + + uint32 numBlanks = 640 - (f.xPos2 - f.xPos1); + + in->seek((int)f.dataOffset); + for (uint32 out = skip; out < size; ) { + uint16 opcode = in->readByte(); + if (opcode & 0x80) { + if (opcode & 0x40) { + if (opcode & 0x20) { + opcode &= 0x1f; + out += numBlanks + opcode + 1; + } else { + opcode &= 0x1f; + if (opcode & 0x10) { + opcode = ((opcode & 0xf) << 8) + in->readByte(); + if (opcode & 0x800) { + // skip these 11 bits + out += (opcode & 0x7ff); + continue; + } + } + + // skip these 4 bits + out += opcode + 2; + } + } else { + opcode &= 0x3f; + byte value = in->readByte(); + if (_palSize <= value) + _palSize = value + 1; + for (int i = 0; i < opcode; i++, out++) { + p[out] = value; + } + } + } else { + if (_palSize <= opcode) + _palSize = opcode + 1; + // set the given value + p[out] = (byte)opcode; + out++; + } + } +} + +void AnimFrame::decompFF(Common::SeekableReadStream *in, const FrameInfo &f) { + byte *p = (byte *)_image.getBasePtr(0, 0); + + uint32 skip = f.initialSkip / 2; + uint32 size = f.decompressedEndOffset / 2; + + in->seek((int)f.dataOffset); + for (uint32 out = skip; out < size; ) { + uint16 opcode = in->readByte(); + + if (opcode < 0x80) { + if (_palSize <= opcode) + _palSize = opcode + 1; + // set the given value + p[out] = (byte)opcode; + out++; + } else { + if (opcode < 0xf0) { + if (opcode < 0xe0) { + // copy old part + uint32 old = out + ((opcode & 0x7) << 8) + in->readByte() - 2048; + opcode = ((opcode >> 3) & 0xf) + 3; + for (int i = 0; i < opcode; i++, out++, old++) { + p[out] = p[old]; + } + } else { + opcode = (opcode & 0xf) + 1; + byte value = in->readByte(); + if (_palSize <= value) + _palSize = value + 1; + for (int i = 0; i < opcode; i++, out++) { + p[out] = value; + } + } + } else { + out += ((opcode & 0xf) << 8) + in->readByte(); + } + } + } +} + + +////////////////////////////////////////////////////////////////////////// +// SEQUENCE +////////////////////////////////////////////////////////////////////////// + +Sequence::~Sequence() { + reset(); +} + +void Sequence::reset() { + _frames.clear(); + delete _stream; + _stream = NULL; +} + +Sequence *Sequence::load(Common::String name, Common::SeekableReadStream *stream, byte field30) { + Sequence *sequence = new Sequence(name); + + if (!sequence->load(stream, field30)) { + delete sequence; + return NULL; + } + + return sequence; +} + +bool Sequence::load(Common::SeekableReadStream *stream, byte field30) { + if (!stream) + return false; + + // Reset data + reset(); + + _field30 = field30; + + // Keep stream for later decoding of sequence + _stream = stream; + + // Read header to get the number of frames + _stream->seek(0); + uint32 numframes = _stream->readUint32LE(); + uint32 unknown = _stream->readUint32LE(); + debugC(3, kLastExpressDebugGraphics, "Number of frames in sequence: %d / unknown=0x%x", numframes, unknown); + + // Store frames information + for (uint i = 0; i < numframes; i++) { + + // Move stream to start of frame + _stream->seek((int32)(_sequenceHeaderSize + i * _sequenceFrameSize), SEEK_SET); + if (_stream->eos()) + error("Couldn't seek to the current frame data"); + + // Check if there is enough data + if ((unsigned)(_stream->size() - _stream->pos()) < _sequenceFrameSize) + error("The sequence frame does not have a valid header"); + + FrameInfo info; + info.read(_stream, true); + _frames.push_back(info); + } + + _isLoaded = true; + + return true; +} + +FrameInfo *Sequence::getFrameInfo(uint16 index) { + if (_frames.size() == 0) + error("Trying to decode a sequence before loading its data"); + + if (index > _frames.size() - 1) + error("Invalid sequence frame requested: %d, max %d", index, _frames.size() - 1); + + return &_frames[index]; +} + +AnimFrame *Sequence::getFrame(uint16 index) { + + FrameInfo *frame = getFrameInfo(index); + + if (!frame) + return NULL; + + // Skip "invalid" frames + if (frame->compressionType == 0) + return NULL; + + debugC(9, kLastExpressDebugGraphics, "Decoding sequence %s: frame %d / %d", _name.c_str(), index, _frames.size() - 1); + + return new AnimFrame(_stream, *frame); +} + +////////////////////////////////////////////////////////////////////////// +// SequenceFrame +SequenceFrame::~SequenceFrame() { + if (_dispose && _sequence) { + delete _sequence; + } + + _sequence = NULL; +} + +Common::Rect SequenceFrame::draw(Graphics::Surface *surface) { + if (!_sequence || _frame >= _sequence->count()) + return Common::Rect(); + + AnimFrame *f = _sequence->getFrame(_frame); + if (!f) + return Common::Rect(); + + Common::Rect rect = f->draw(surface); + + delete f; + + return rect; +} + +bool SequenceFrame::setFrame(uint16 frame) { + if (!_sequence) + return false; + + if (frame < _sequence->count()) { + _frame = frame; + return true; + } else + return false; +} + +bool SequenceFrame::nextFrame() { + return setFrame(_frame + 1); +} + +FrameInfo *SequenceFrame::getInfo() { + if (!_sequence) + error("SequenceFrame::getFrameInfo: Invalid sequence!"); + + return _sequence->getFrameInfo(_frame); +} + +bool SequenceFrame::equal(const SequenceFrame *other) const { + return _sequence->getName() == other->_sequence->getName() && _frame == other->_frame; +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/data/sequence.h b/engines/lastexpress/data/sequence.h new file mode 100644 index 0000000000..eadc2962f8 --- /dev/null +++ b/engines/lastexpress/data/sequence.h @@ -0,0 +1,205 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_SEQUENCE_H +#define LASTEXPRESS_SEQUENCE_H + +/* + Sequence format (.SEQ / .NIS (frame header & data only)) + + uint32 {4} - Number of frames in sequence + uint32 {4} - Unknown + + frames headers (68 bytes): + // for each frame + uint32 {4} - Data offset (from beginning of file) + uint32 {4} - Unknown + uint32 {4} - Palette offset (from beginning of file) + uint32 {4} - Top-left X coordinate + uint32 {4} - Top-left Y coordinate + uint32 {4} - Bottom-right X coordinate + uint32 {4} - Bottom-right Y coordinate + uint32 {4} - Initial offset of decompressed data (doubled, since each pixel occupies one color word) + uint32 {4} - End of data after decompression + + (for SEQ files only) + uint16 {2} - Hotspot left + uint16 {2} - Hotspot right + uint16 {2} - Hotspot top + uint16 {2} - Hotspot bottom + byte {1} - Compression type + byte {1} - Subtype (determines which set of decompression functions will be called) => 0, 1, 2, 3 + byte {1} - Unknown + byte {1} - Unknown + uint16 {2} - Unknown + byte {1} - Sound action + byte {1} - Unknown + uint32 {4} - positionId + uint32 {4} - Unknown + uint16 {2} - Entity Position + uint16 {2} - Location (~z-order) + uint32 {4} - Next sequence in the linked list + + (for NIS files: found at 0x124) + byte {1} - Compression type + + palette data: + uint16 {x} - palette data (max size: 256) + + data + byte {x} - compressed image data +*/ + +#include "lastexpress/drawable.h" + +#include "lastexpress/shared.h" + +#include "common/array.h" +#include "common/stream.h" + +namespace LastExpress { + +enum FrameSubType { + kFrameTypeNone = 0, + kFrameType1 = 1, + kFrameType2 = 2, + kFrameType3 = 3 +}; + +struct FrameInfo { + void read(Common::SeekableReadStream *in, bool isSequence); + + uint32 dataOffset; ///< Data offset (from beginning of file) + uint32 unknown; ///< FIXME: unknown data + uint32 paletteOffset; ///< Palette offset (from beginning of file) + uint32 xPos1; ///< Top-left X coordinate + uint32 yPos1; ///< Top-left Y coordinate + uint32 xPos2; ///< Bottom-right X coordinate + uint32 yPos2; ///< Bottom-right Y coordinate + uint32 initialSkip; ///< Initial on-screen offset of decompressed data (doubled, since each pixel occupies one color word) + uint32 decompressedEndOffset; ///< End of data after decompression + + // NIS frame headers end here. SEQ frame headers have additional 32 bytes of + // data, notably the compression type at the position outlined above in + // CompPos_SEQ + + Common::Rect hotspot; + + byte compressionType; ///< Type of frame compression (0x03, 0x04, 0x05, 0x07, 0xFF) + FrameSubType subType; ///< Subtype (byte) + + byte field_2E; + byte keepPreviousFrame; + byte field_30; + byte field_31; + byte soundAction; + byte field_33; + Position position; + byte field_35; + int16 field_36; + uint32 field_38; + EntityPosition entityPosition; + uint16 location; + uint32 next; +}; + +class AnimFrame : public Drawable { +public: + AnimFrame(Common::SeekableReadStream *in, const FrameInfo &f); + ~AnimFrame(); + Common::Rect draw(Graphics::Surface *s); + +private: + void decomp3(Common::SeekableReadStream *in, const FrameInfo &f); + void decomp4(Common::SeekableReadStream *in, const FrameInfo &f); + void decomp34(Common::SeekableReadStream *in, const FrameInfo &f, byte mask, byte shift); + void decomp5(Common::SeekableReadStream *in, const FrameInfo &f); + void decomp7(Common::SeekableReadStream *in, const FrameInfo &f); + void decompFF(Common::SeekableReadStream *in, const FrameInfo &f); + void readPalette(Common::SeekableReadStream *in, const FrameInfo &f); + + Graphics::Surface _image; + uint16 _palSize; + uint16 *_palette; + Common::Rect _rect; +}; + +class Sequence { +public: + Sequence(Common::String name) : _stream(NULL), _isLoaded(false), _name(name), _field30(15) {} + ~Sequence(); + + static Sequence *load(Common::String name, Common::SeekableReadStream *stream = NULL, byte field30 = 15); + + bool load(Common::SeekableReadStream *stream, byte field30 = 15); + + uint16 count() const { return (uint16)_frames.size(); }; + AnimFrame *getFrame(uint16 index = 0); + FrameInfo *getFrameInfo(uint16 index = 0); + + Common::String getName() { return _name; } + byte getField30() { return _field30; } + + bool isLoaded() { return _isLoaded; } + +private: + static const uint32 _sequenceHeaderSize = 8; + static const uint32 _sequenceFrameSize = 68; + + void reset(); + + Common::Array _frames; + Common::SeekableReadStream *_stream; + bool _isLoaded; + + Common::String _name; + byte _field30; // used when copying sequences +}; + +class SequenceFrame : public Drawable { +public: + SequenceFrame(Sequence *sequence, uint16 frame = 0, bool dispose = false) : _sequence(sequence), _frame(frame), _dispose(dispose) {} + ~SequenceFrame(); + + Common::Rect draw(Graphics::Surface *surface); + + bool setFrame(uint16 frame); + uint32 getFrame() { return _frame; } + bool nextFrame(); + + Common::String getName() { return _sequence->getName(); } + FrameInfo *getInfo(); + + bool equal(const SequenceFrame *other) const; + +private: + Sequence *_sequence; + uint16 _frame; + bool _dispose; +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_SEQUENCE_H diff --git a/engines/lastexpress/data/snd.cpp b/engines/lastexpress/data/snd.cpp new file mode 100644 index 0000000000..496bd58772 --- /dev/null +++ b/engines/lastexpress/data/snd.cpp @@ -0,0 +1,141 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +// Based on the Xentax Wiki documentation: +// http://wiki.xentax.com/index.php/The_Last_Express_SND + +#include "lastexpress/data/snd.h" + +#include "lastexpress/debug.h" + +#include "sound/decoders/adpcm.h" +#include "sound/audiostream.h" + +namespace LastExpress { + +////////////////////////////////////////////////////////////////////////// +// Sound +////////////////////////////////////////////////////////////////////////// +SimpleSound::SimpleSound() : _size(0), _blocks(0), _blockSize(0) {} + +SimpleSound::~SimpleSound() { + stop(); +} + +// Stop the sound +void SimpleSound::stop() const { + g_system->getMixer()->stopHandle(_handle); +} + +void SimpleSound::loadHeader(Common::SeekableReadStream *in) { + _size = in->readUint32LE(); + _blocks = in->readUint16LE(); + debugC(5, kLastExpressDebugSound, " sound header data: size=\"%d\", %d blocks", _size, _blocks); + + assert (_size % _blocks == 0); + _blockSize = _size / _blocks; +} + +Audio::AudioStream *SimpleSound::makeDecoder(Common::SeekableReadStream *in, uint32 size) const { + return Audio::makeADPCMStream(in, DisposeAfterUse::YES, size, Audio::kADPCMMSImaLastExpress, 44100, 1, _blockSize); +} + +void SimpleSound::play(Audio::AudioStream *as) { + g_system->getMixer()->playStream(Audio::Mixer::kPlainSoundType, &_handle, as); +} + +////////////////////////////////////////////////////////////////////////// +// StreamedSound +////////////////////////////////////////////////////////////////////////// +StreamedSound::StreamedSound() {} +StreamedSound::~StreamedSound() {} + +bool StreamedSound::load(Common::SeekableReadStream *stream) { + if (!stream) + return false; + + g_system->getMixer()->stopHandle(_handle); + + loadHeader(stream); + + // Start decoding the input stream + Audio::AudioStream *as = makeDecoder(stream, _size); + + // Start playing the decoded audio stream + play(as); + + return true; +} + +////////////////////////////////////////////////////////////////////////// +// StreamedSound +////////////////////////////////////////////////////////////////////////// +AppendableSound::AppendableSound() : SimpleSound() { + // Create an audio stream where the decoded chunks will be appended + _as = Audio::makeQueuingAudioStream(44100, false); + _finished = false; + + // Start playing the decoded audio stream + play(_as); + + // Initialize the block size + // TODO: get it as an argument? + _blockSize = 739; +} + +AppendableSound::~AppendableSound() { + finish(); + + _as = NULL; +} + +void AppendableSound::queueBuffer(const byte *data, uint32 size) { + Common::MemoryReadStream *buffer = new Common::MemoryReadStream(data, size); + queueBuffer(buffer); +} + +void AppendableSound::queueBuffer(Common::SeekableReadStream *bufferIn) { + if (!_as) + error("AppendableSound::queueBuffer - internal error: the audio stream is invalid!"); + + // Setup the ADPCM decoder + uint32 sizeIn = (uint32)bufferIn->size(); + Audio::AudioStream *adpcm = makeDecoder(bufferIn, sizeIn); + + // Queue the stream + _as->queueAudioStream(adpcm); +} + +void AppendableSound::finish() { + if (!_as) + error("AppendableSound::queueBuffer - internal error: the audio stream is invalid!"); + + if (!_finished) + _as->finish(); + + _finished = true; +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/data/snd.h b/engines/lastexpress/data/snd.h new file mode 100644 index 0000000000..2e0bc8c1b0 --- /dev/null +++ b/engines/lastexpress/data/snd.h @@ -0,0 +1,97 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_SND_H +#define LASTEXPRESS_SND_H + +/* + Sound format (.SND / .LNK) + + uint32 {4} - data size + uint16 {2} - number of blocks + + // for each block + int16 {2} - initial sample + byte {1} - initial index + byte {1} - unused (00) + byte {x} - IMA ADPCM sample codes +*/ + +#include "common/stream.h" +#include "sound/mixer.h" + +namespace Audio { + class AudioStream; + class QueuingAudioStream; +} + +namespace LastExpress { + +class SimpleSound { +public: + SimpleSound(); + virtual ~SimpleSound(); + + void stop() const; + +protected: + void loadHeader(Common::SeekableReadStream *in); + Audio::AudioStream *makeDecoder(Common::SeekableReadStream *in, uint32 size) const; + void play(Audio::AudioStream *as); + + uint32 _size; ///< data size + ///< - NIS: size of all blocks, including those located in the matching LNK file + ///< - LNK: size of the LNK file itself, including the header + ///< - SND: size of all blocks + uint16 _blocks; ///< number of blocks + uint32 _blockSize; + Audio::SoundHandle _handle; +}; + +class StreamedSound : public SimpleSound { +public: + StreamedSound(); + ~StreamedSound(); + + bool load(Common::SeekableReadStream *stream); +}; + +class AppendableSound : public SimpleSound { +public: + AppendableSound(); + ~AppendableSound(); + + void queueBuffer(const byte *data, uint32 size); + void queueBuffer(Common::SeekableReadStream *bufferIn); + void finish(); + +private: + Audio::QueuingAudioStream *_as; + bool _finished; +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_SND_H diff --git a/engines/lastexpress/data/subtitle.cpp b/engines/lastexpress/data/subtitle.cpp new file mode 100644 index 0000000000..fb7d4ec6fa --- /dev/null +++ b/engines/lastexpress/data/subtitle.cpp @@ -0,0 +1,243 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +// Based on the Xentax Wiki documentation: +// http://wiki.xentax.com/index.php/The_Last_Express_SBE + +#include "lastexpress/data/subtitle.h" + +#include "lastexpress/data/font.h" + +#include "lastexpress/debug.h" + +#include "common/debug.h" + +namespace LastExpress { + +////////////////////////////////////////////////////////////////////////// +// Subtitle +////////////////////////////////////////////////////////////////////////// +class Subtitle { +public: + Subtitle() : _timeStart(0), _timeStop(0), _topLength(0), _topText(NULL), + _bottomLength(0), _bottomText(NULL) {} + ~Subtitle() { reset(); } + + bool load(Common::SeekableReadStream *in); + Common::Rect draw(Graphics::Surface *surface, Font *font); + + uint16 getTimeStart() const { return _timeStart; } + uint16 getTimeStop() const { return _timeStop; } + +private: + uint16 _timeStart; ///< display start time + uint16 _timeStop; ///< display stop time + + uint16 _topLength; ///< top line length + uint16 *_topText; ///< bottom line length + + uint16 _bottomLength; ///< top line (UTF-16 string) + uint16 *_bottomText; ///< bottom line (UTF-16 string) + + void reset(); +}; + +void Subtitle::reset() { + delete[] _topText; + delete[] _bottomText; + _topText = NULL; + _bottomText = NULL; +} + +template +T *newArray(size_t n) +{ + if (n <= (size_t)-1 / sizeof(T)) + return new T[n]; + + // n is too large + return NULL; +} + +bool Subtitle::load(Common::SeekableReadStream *in) { + reset(); + + if (!in) + return false; + + // Read the display times + _timeStart = in->readUint16LE(); + _timeStop = in->readUint16LE(); + + // Read the text lengths + _topLength = in->readUint16LE(); + _bottomLength = in->readUint16LE(); + + // Create the buffers + if (_topLength) { + _topText = newArray(_topLength); + if (!_topText) + return false; + } + if (_bottomLength) { + _bottomText = newArray(_bottomLength); + if (!_bottomText) + return false; + } + + // Read the texts + for (int i = 0; i < _topLength; i++) + _topText[i] = in->readUint16LE(); + for (int i = 0; i < _bottomLength; i++) + _bottomText[i] = in->readUint16LE(); + + debugC(9, kLastExpressDebugSubtitle, " %d -> %d:", _timeStart, _timeStop); + if (_topLength) + debugC(9, kLastExpressDebugSubtitle, "\t%ls", (wchar_t *)_topText); + if (_bottomLength) + debugC(9, kLastExpressDebugSubtitle, "\t%ls", (wchar_t *)_bottomText); + + return true; +} + +Common::Rect Subtitle::draw(Graphics::Surface *surface, Font *font) { + Common::Rect rectTop, rectBottom; + + //FIXME find out proper subtitles coordinates (and hope it's hardcoded and not stored in the sequence or animation) + rectTop = font->drawString(surface, 100, 100, _topText, _topLength); + rectBottom = font->drawString(surface, 100, 300, _bottomText, _bottomLength); + + rectTop.extend(rectBottom); + + return rectTop; +} + + +////////////////////////////////////////////////////////////////////////// +// SubtitleManager +////////////////////////////////////////////////////////////////////////// +SubtitleManager::SubtitleManager(Font *font) : _font(font), _maxTime(0), _currentIndex(-1), _lastIndex(-1) {} + +SubtitleManager::~SubtitleManager() { + reset(); + + _font = NULL; +} + +void SubtitleManager::reset() { + for (int i = 0; i < (int)_subtitles.size(); i++) + delete _subtitles[i]; + + _subtitles.clear(); + _currentIndex = -1; + _lastIndex = -1; + + // Zero passed pointers + _font = NULL; +} + +bool SubtitleManager::load(Common::SeekableReadStream *stream) { + if (!stream) + return false; + + reset(); + + // Read header to get the number of subtitles + uint32 numSubtitles = stream->readUint16LE(); + if (stream->eos()) + error("Cannot read from subtitle file"); + + debugC(3, kLastExpressDebugSubtitle, "Number of subtitles in file: %d", numSubtitles); + + // TODO: Check that stream contain enough data + //if (stream->size() < (signed)(numSubtitles * sizeof(SubtitleData))) { + //debugC(2, kLastExpressDebugSubtitle, "Subtitle file does not contain valid data!"); + //return false; + //} + + // Read the list of subtitles + _maxTime = 0; + for (uint i = 0; i < numSubtitles; i++) { + Subtitle *subtitle = new Subtitle(); + if (!subtitle->load(stream)) { + // Failed to read this line + reset(); + + delete subtitle; + + return false; + } + + // Update the max time + if (subtitle->getTimeStop() > _maxTime) + _maxTime = subtitle->getTimeStop(); + + _subtitles.push_back(subtitle); + } + + delete stream; + + return true; +} + +uint16 SubtitleManager::getMaxTime() const { + return _maxTime; +} + +void SubtitleManager::setTime(uint16 time) { + _currentIndex = -1; + + // Find the appropriate line to show + for (int16 i = 0; i < (int16)_subtitles.size(); i++) { + if ((time >= _subtitles[i]->getTimeStart()) && (time <= _subtitles[i]->getTimeStop())) { + // Keep the index of the line to show + _currentIndex = i; + return; + } + } +} + +bool SubtitleManager::hasChanged() const { + // TODO: mark the old line rect as dirty + if (_currentIndex != _lastIndex) + return true; + else + return false; +} + +Common::Rect SubtitleManager::draw(Graphics::Surface *surface) { + // Update the last drawn index + _lastIndex = _currentIndex; + + // Return if we don't have to draw any line + if (_currentIndex == -1) + return Common::Rect(); + + // Draw the current line + assert(_currentIndex >= 0 && _currentIndex < (int16)_subtitles.size()); + return _subtitles[_currentIndex]->draw(surface, _font); +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/data/subtitle.h b/engines/lastexpress/data/subtitle.h new file mode 100644 index 0000000000..9acb7068f1 --- /dev/null +++ b/engines/lastexpress/data/subtitle.h @@ -0,0 +1,79 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_SUBTITLE_H +#define LASTEXPRESS_SUBTITLE_H + +/* + Subtitle format (.SBE) + + uint16 {2} - number of subtitles + + // for each subtitle + uint16 {2} - display start time + uint16 {2} - display stop time + uint16 {2} - top line length + uint16 {2} - bottom line length + byte {x} - top line (UTF-16 string) + byte {x} - bottom line (UTF-16 string) + + Subtitles seem to be drawn on screen at (80, 420) x (560, 458) +*/ + +#include "lastexpress/drawable.h" + +#include "common/array.h" +#include "common/stream.h" + +namespace LastExpress { + +class Font; +class Subtitle; + +class SubtitleManager : public Drawable { +public: + SubtitleManager(Font *font); + ~SubtitleManager(); + + bool load(Common::SeekableReadStream *stream); + uint16 getMaxTime() const; + void setTime(uint16 time); + bool hasChanged() const; + Common::Rect draw(Graphics::Surface *surface); + +private: + Common::Array _subtitles; + Font *_font; + uint16 _maxTime; + + int16 _currentIndex; + int16 _lastIndex; + + void reset(); +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_SUBTITLE_H diff --git a/engines/lastexpress/debug.cpp b/engines/lastexpress/debug.cpp new file mode 100644 index 0000000000..cf81e162ae --- /dev/null +++ b/engines/lastexpress/debug.cpp @@ -0,0 +1,1091 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/debug.h" + +// Data +#include "lastexpress/data/animation.h" +#include "lastexpress/data/background.h" +#include "lastexpress/data/cursor.h" +#include "lastexpress/data/scene.h" +#include "lastexpress/data/sequence.h" +#include "lastexpress/data/snd.h" +#include "lastexpress/data/subtitle.h" + +#include "lastexpress/game/action.h" +#include "lastexpress/game/beetle.h" +#include "lastexpress/game/fight.h" +#include "lastexpress/game/inventory.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savegame.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/graphics.h" +#include "lastexpress/helpers.h" +#include "lastexpress/lastexpress.h" +#include "lastexpress/resource.h" + +#include "common/debug-channels.h" +#include "common/events.h" + +namespace LastExpress { + +Debugger::Debugger(LastExpressEngine *engine) : _engine(engine), _command(NULL), _numParams(0), _commandParams(NULL) { + + ////////////////////////////////////////////////////////////////////////// + // Register the debugger commands + + // General + DCmd_Register("help", WRAP_METHOD(Debugger, cmdHelp)); + + // Data + DCmd_Register("ls", WRAP_METHOD(Debugger, cmdListFiles)); + + DCmd_Register("showframe", WRAP_METHOD(Debugger, cmdShowFrame)); + DCmd_Register("showbg", WRAP_METHOD(Debugger, cmdShowBg)); + DCmd_Register("playseq", WRAP_METHOD(Debugger, cmdPlaySeq)); + DCmd_Register("playsnd", WRAP_METHOD(Debugger, cmdPlaySnd)); + DCmd_Register("playsbe", WRAP_METHOD(Debugger, cmdPlaySbe)); + DCmd_Register("playnis", WRAP_METHOD(Debugger, cmdPlayNis)); + + // Scene & interaction + DCmd_Register("loadscene", WRAP_METHOD(Debugger, cmdLoadScene)); + DCmd_Register("fight", WRAP_METHOD(Debugger, cmdFight)); + DCmd_Register("beetle", WRAP_METHOD(Debugger, cmdBeetle)); + + // Game + DCmd_Register("delta", WRAP_METHOD(Debugger, cmdTimeDelta)); + DCmd_Register("dump", WRAP_METHOD(Debugger, cmdDump)); + DCmd_Register("entity", WRAP_METHOD(Debugger, cmdEntity)); + + // Misc + DCmd_Register("loadgame", WRAP_METHOD(Debugger, cmdLoadGame)); + DCmd_Register("chapter", WRAP_METHOD(Debugger, cmdSwitchChapter)); + DCmd_Register("clear", WRAP_METHOD(Debugger, cmdClear)); + + resetCommand(); + + _soundStream = new StreamedSound(); +} + +Debugger::~Debugger() { + DebugMan.clearAllDebugChannels(); + + delete _soundStream; + + // Zero passed pointers + _engine = NULL; + _command = NULL; + _commandParams = NULL; +} + +////////////////////////////////////////////////////////////////////////// +// Helper functions +////////////////////////////////////////////////////////////////////////// +bool Debugger::hasCommand() const { + return (_numParams != 0); +} + +void Debugger::resetCommand() { + _command = NULL; + _commandParams = NULL; + _numParams = 0; +} + +int Debugger::getNumber(const char *arg) const { + return strtol(arg, (char **)NULL, 0); +} + +void Debugger::copyCommand(int argc, const char **argv) { + _commandParams = (char **)malloc((uint)argc); + if (!_commandParams) + return; + + _numParams = argc; + + for (int i = 0; i < _numParams; i++) { + _commandParams[i] = (char *)malloc(strlen(argv[i])); + strcpy(_commandParams[i], ""); + strcpy(_commandParams[i], argv[i]); + } + + // Exit the debugger! + Cmd_Exit(0, 0); +} + +void Debugger::callCommand() { + if (_command) + (*_command)(_numParams, const_cast(_commandParams)); +} + +void Debugger::loadArchive(ArchiveIndex index) const { + _engine->getResourceManager()->loadArchive(index); + getScenes()->loadSceneDataFile(index); +} + +// Restore loaded archive +void Debugger::restoreArchive() const { + + ArchiveIndex index = kArchiveCd1; + + switch (getProgress().chapter) { + default: + case kChapter1: + index = kArchiveCd1; + break; + + case kChapter2: + case kChapter3: + index = kArchiveCd2; + break; + + case kChapter4: + case kChapter5: + index = kArchiveCd3; + break; + } + + _engine->getResourceManager()->loadArchive(index); + getScenes()->loadSceneDataFile(index); +} + +////////////////////////////////////////////////////////////////////////// +// Debugger commands +////////////////////////////////////////////////////////////////////////// +bool Debugger::cmdHelp(int, const char **) { + DebugPrintf("Debug flags\n"); + DebugPrintf("-----------\n"); + DebugPrintf(" debugflag_list - Lists the available debug flags and their status\n"); + DebugPrintf(" debugflag_enable - Enables a debug flag\n"); + DebugPrintf(" debugflag_disable - Disables a debug flag\n"); + DebugPrintf("\n"); + DebugPrintf("Commands\n"); + DebugPrintf("--------\n"); + DebugPrintf(" ls - list files in the archive\n"); + DebugPrintf("\n"); + DebugPrintf(" showframe - show a frame from a sequence\n"); + DebugPrintf(" showbg - show a background\n"); + DebugPrintf(" playseq - play a sequence\n"); + DebugPrintf(" playsnd - play a sound\n"); + DebugPrintf(" playsbe - play a subtitle\n"); + DebugPrintf(" playnis - play an animation\n"); + DebugPrintf("\n"); + DebugPrintf(" loadscene - load a scene\n"); + DebugPrintf(" fight - start a fight\n"); + DebugPrintf(" beetle - start the beetle game\n"); + DebugPrintf("\n"); + DebugPrintf(" delta - Adjust the time delta\n"); + DebugPrintf(" dump - Dump game data\n"); + DebugPrintf(" entity - Dump entity data\n"); + DebugPrintf("\n"); + DebugPrintf(" loadgame - load a saved game\n"); + DebugPrintf(" chapter - switch to a specific chapter\n"); + DebugPrintf(" clear - clear the screen\n"); + DebugPrintf("\n"); + return true; +} + +/** + * Command: list files in archive + * + * @param argc The argument count. + * @param argv The values. + * + * @return true if it was handled, false otherwise + */ +bool Debugger::cmdListFiles(int argc, const char **argv) { + if (argc == 2 || argc == 3) { + Common::String filter(const_cast(argv[1])); + + // Load the proper archive + if (argc == 3) + loadArchive((ArchiveIndex)getNumber(argv[2])); + + Common::ArchiveMemberList list; + int count = _engine->getResourceManager()->listMatchingMembers(list, filter); + + DebugPrintf("Number of matches: %d\n", count); + for (Common::ArchiveMemberList::iterator it = list.begin(); it != list.end(); ++it) + DebugPrintf(" %s\n", (*it)->getName().c_str()); + + // Restore archive + if (argc == 3) + restoreArchive(); + } else { + DebugPrintf("Syntax: ls (use * for all)\n ()"); + } + + return true; +} + +/** + * Command: Shows a frame + * + * @param argc The argument count. + * @param argv The values. + * + * @return true if it was handled, false otherwise + */ +bool Debugger::cmdShowFrame(int argc, const char **argv) { + if (argc == 3 || argc == 4) { + Common::String filename(const_cast(argv[1])); + filename += ".seq"; + + if (argc == 4) + loadArchive((ArchiveIndex)getNumber(argv[3])); + + if (!_engine->getResourceManager()->hasFile(filename)) { + DebugPrintf("Cannot find file: %s\n", filename.c_str()); + return true; + } + + // Store command + if (!hasCommand()) { + _command = WRAP_METHOD(Debugger, cmdShowFrame); + copyCommand(argc, argv); + + return Cmd_Exit(0, 0); + } else { + Sequence sequence(filename); + if (sequence.load(getArchive(filename))) { + _engine->getCursor()->show(false); + clearBg(GraphicsManager::kBackgroundOverlay); + + AnimFrame *frame = sequence.getFrame((uint16)getNumber(argv[2])); + if (!frame) { + DebugPrintf("Invalid frame index: %i\n", filename.c_str()); + resetCommand(); + return true; + } + + _engine->getGraphicsManager()->draw(frame, GraphicsManager::kBackgroundOverlay); + delete frame; + + askForRedraw(); + redrawScreen(); + + _engine->_system->delayMillis(1000); + _engine->getCursor()->show(true); + } + + resetCommand(); + + if (argc == 4) + restoreArchive(); + } + } else { + DebugPrintf("Syntax: cmd_showframe ()\n"); + } + return true; +} + +/** + * Command: shows a background + * + * @param argc The argument count. + * @param argv The values. + * + * @return true if it was handled, false otherwise + */ +bool Debugger::cmdShowBg(int argc, const char **argv) { + if (argc == 2 || argc == 3) { + Common::String filename(const_cast(argv[1])); + + if (argc == 3) + loadArchive((ArchiveIndex)getNumber(argv[2])); + + if (!_engine->getResourceManager()->hasFile(filename + ".BG")) { + DebugPrintf("Cannot find file: %s\n", (filename + ".BG").c_str()); + return true; + } + + // Store command + if (!hasCommand()) { + _command = WRAP_METHOD(Debugger, cmdShowBg); + copyCommand(argc, argv); + + return Cmd_Exit(0, 0); + } else { + clearBg(GraphicsManager::kBackgroundC); + + Background *background = _engine->getResourceManager()->loadBackground(filename); + if (background) { + _engine->getGraphicsManager()->draw(background, GraphicsManager::kBackgroundC); + delete background; + askForRedraw(); + } + + redrawScreen(); + + if (argc == 3) + restoreArchive(); + + // Pause for a second to be able to see the background + _engine->_system->delayMillis(1000); + + resetCommand(); + } + } else { + DebugPrintf("Syntax: showbg ()\n"); + } + return true; +} + +/** + * Command: plays a sequence. + * + * @param argc The argument count. + * @param argv The values. + * + * @return true if it was handled, false otherwise + */ +bool Debugger::cmdPlaySeq(int argc, const char **argv) { + if (argc == 2 || argc == 3) { + Common::String filename(const_cast(argv[1])); + filename += ".seq"; + + if (argc == 3) + loadArchive((ArchiveIndex)getNumber(argv[2])); + + if (!_engine->getResourceManager()->hasFile(filename)) { + DebugPrintf("Cannot find file: %s\n", filename.c_str()); + return true; + } + + // Store command + if (!hasCommand()) { + _command = WRAP_METHOD(Debugger, cmdPlaySeq); + copyCommand(argc, argv); + + return Cmd_Exit(0, 0); + } else { + Sequence *sequence = new Sequence(filename); + if (sequence->load(getArchive(filename))) { + + // Check that we have at least a frame to show + if (sequence->count() == 0) { + delete sequence; + return false; + } + + _engine->getCursor()->show(false); + + SequenceFrame player(sequence, 0, true); + do { + // Clear screen + clearBg(GraphicsManager::kBackgroundA); + + _engine->getGraphicsManager()->draw(&player, GraphicsManager::kBackgroundA); + + askForRedraw(); + redrawScreen(); + + // Handle right-click to interrupt sequence + Common::Event ev; + _engine->getEventManager()->pollEvent(ev); + if (ev.type == Common::EVENT_RBUTTONUP) + break; + + _engine->_system->delayMillis(175); + + // go to the next frame + } while (player.nextFrame()); + _engine->getCursor()->show(true); + } else { + // Sequence player is deleting his reference to the sequence, but we need to take care of it if the + // sequence could not be loaded + delete sequence; + } + + resetCommand(); + + if (argc == 3) + restoreArchive(); + } + } else { + DebugPrintf("Syntax: playseq ()\n"); + } + return true; +} + +/** + * Command: plays a sound + * + * @param argc The argument count. + * @param argv The values. + * + * @return true if it was handled, false otherwise + */ +bool Debugger::cmdPlaySnd(int argc, const char **argv) { + if (argc == 2 || argc == 3) { + + if (argc == 3) + loadArchive((ArchiveIndex)getNumber(argv[2])); + + // Add .SND at the end of the filename if needed + Common::String name(const_cast(argv[1])); + if (!name.contains('.')) + name += ".SND"; + + if (!_engine->getResourceManager()->hasFile(name)) { + DebugPrintf("Cannot find file: %s\n", name.c_str()); + return true; + } + + _engine->_system->getMixer()->stopAll(); + + _soundStream->load(getArchive(name)); + + if (argc == 3) + restoreArchive(); + } else { + DebugPrintf("Syntax: playsnd ()\n"); + } + return true; +} + +/** + * Command: plays subtitles + * + * @param argc The argument count. + * @param argv The values. + * + * @return true if it was handled, false otherwise + */ +bool Debugger::cmdPlaySbe(int argc, const char **argv) { + if (argc == 2 || argc == 3) { + Common::String filename(const_cast(argv[1])); + + if (argc == 3) + loadArchive((ArchiveIndex)getNumber(argv[2])); + + filename += ".sbe"; + + if (!_engine->getResourceManager()->hasFile(filename)) { + DebugPrintf("Cannot find file: %s\n", filename.c_str()); + return true; + } + + // Store command + if (!hasCommand()) { + _command = WRAP_METHOD(Debugger, cmdPlaySbe); + copyCommand(argc, argv); + + return Cmd_Exit(0, 0); + } else { + SubtitleManager subtitle(_engine->getFont()); + if (subtitle.load(getArchive(filename))) { + _engine->getCursor()->show(false); + for (uint16 i = 0; i < subtitle.getMaxTime(); i += 25) { + clearBg(GraphicsManager::kBackgroundAll); + + subtitle.setTime(i); + _engine->getGraphicsManager()->draw(&subtitle, GraphicsManager::kBackgroundOverlay); + + askForRedraw(); + redrawScreen(); + + // Handle right-click to interrupt sequence + Common::Event ev; + _engine->getEventManager()->pollEvent(ev); + if (ev.type == Common::EVENT_RBUTTONUP) + break; + + _engine->_system->delayMillis(500); + } + _engine->getCursor()->show(true); + } + + if (argc == 3) + restoreArchive(); + + resetCommand(); + } + } else { + DebugPrintf("Syntax: playsbe ()\n"); + } + return true; +} + +/** + * Command: plays a NIS animation sequence. + * + * @param argc The argument count. + * @param argv The values. + * + * @return true if it was handled, false otherwise + */ +bool Debugger::cmdPlayNis(int argc, const char **argv) { + if (argc == 2 || argc == 3) { + Common::String name(const_cast(argv[1])); + + if (argc == 3) + loadArchive((ArchiveIndex)getNumber(argv[2])); + + // If we got a nis filename, check that the file exists + if (name.contains('.') && _engine->getResourceManager()->hasFile(name)) { + DebugPrintf("Cannot find file: %s\n", name.c_str()); + return true; + } + + // Store command + if (!hasCommand()) { + _command = WRAP_METHOD(Debugger, cmdPlayNis); + copyCommand(argc, argv); + + return Cmd_Exit(0, 0); + } else { + // Make sure we are not called in a loop + _numParams = 0; + + + // Check if we got a nis filename or an animation index + if (name.contains('.')) { + Animation animation; + if (animation.load(getArchive(name))) { + _engine->getCursor()->show(false); + animation.play(); + _engine->getCursor()->show(true); + } + } else { + getAction()->playAnimation((EventIndex)atoi(name.c_str()), true); + } + + if (argc == 3) + restoreArchive(); + + resetCommand(); + } + } else { + DebugPrintf("Syntax: playnis ()\n"); + } + return true; +} + +/** + * Command: loads a scene + * + * @param argc The argument count. + * @param argv The values. + * + * @return true if it was handled, false otherwise + */ +bool Debugger::cmdLoadScene(int argc, const char **argv) { + if (argc == 2 || argc == 3) { + int cd = 1; + SceneIndex index = (SceneIndex)getNumber(argv[1]);; + + // Check args + if (argc == 3) + loadArchive((ArchiveIndex)getNumber(argv[2])); + + if (index > 2500) { + DebugPrintf("Error: invalid index value (0-2500)"); + return true; + } + + // Store command + if (!hasCommand()) { + _command = WRAP_METHOD(Debugger, cmdLoadScene); + copyCommand(argc, argv); + + return Cmd_Exit(0, 0); + } else { + + clearBg(GraphicsManager::kBackgroundAll); + + /************ DEBUG *************************/ + // Use to find scenes with certain values + + //for (int i = index; i < 2500; i++) { + // loadSceneObject(scene, i); + + // if (scene.getHeader() && scene.getHeader()->car == 5 && scene.getHeader()->position == 81) { + // DebugPrintf("Found scene: %d", i); + + // // Draw scene found + // _engine->getGraphicsManager()->draw(&scene, GraphicsManager::kBackgroundC); + + // askForRedraw(); + // redrawScreen(); + // _engine->_system->delayMillis(500); + + // break; + // } + //} + + //delete _sceneLoader; + //resetCommand(); + //return true; + + /*********************************************/ + Scene *scene = getScenes()->get(index); + if (!scene) { + DebugPrintf("Cannot load scene %i from CD %i", index, cd); + resetCommand(); + + return true; + } + + _engine->getGraphicsManager()->draw(scene, GraphicsManager::kBackgroundC); + + askForRedraw(); + redrawScreen(); + + // Pause for a second to be able to see the scene + _engine->_system->delayMillis(500); + + if (argc == 3) + restoreArchive(); + + resetCommand(); + } + } else { + DebugPrintf("Syntax: loadscene ()\n"); + } + return true; +} + +/** + * Command: starts a fight sequence + * + * @param argc The argument count. + * @param argv The values. + * + * @return true if it was handled, false otherwise + */ +bool Debugger::cmdFight(int argc, const char **argv) { + if (argc == 2) { + FightType type = (FightType)getNumber(argv[1]); + + // Load proper data file + ArchiveIndex index = kArchiveCd1; + switch (type) { + default: + goto error; + + case kFightMilos: + index = kArchiveCd1; + break; + + case kFightAnna: + index = kArchiveCd2; + break; + + case kFightIvo: + case kFightSalko: + case kFightVesna: + index = kArchiveCd3; + break; + } + + loadArchive(index); + + // Store command + if (!hasCommand()) { + _command = WRAP_METHOD(Debugger, cmdFight); + copyCommand(argc, argv); + + return false; + } else { + // Make sure we are not called in a loop + _numParams = 0; + + clearBg(GraphicsManager::kBackgroundAll); + askForRedraw(); + redrawScreen(); + + SceneIndex lastScene = getState()->scene; + + getFight()->setup(type) ? DebugPrintf("Lost fight!\n") : DebugPrintf("Won fight!\n"); + + // Pause for a second to be able to see the final scene + _engine->_system->delayMillis(1000); + + // Restore loaded archive + restoreArchive(); + + // Stop audio and restore scene + getSound()->stopAllSound(); + + clearBg(GraphicsManager::kBackgroundAll); + + Scene *scene = getScenes()->get(lastScene); + _engine->getGraphicsManager()->draw(scene, GraphicsManager::kBackgroundC); + + askForRedraw(); + redrawScreen(); + + resetCommand(); + } + } else { +error: + DebugPrintf("Syntax: fight (id=2001-2005)\n"); + } + + return true; +} + +/** + * Command: starts the beetle sequence + * + * @param argc The argument count. + * @param argv The values. + * + * @return true if it was handled, false otherwise + */ +bool Debugger::cmdBeetle(int argc, const char **argv) { + if (argc == 1) { + // Load proper data file (beetle game in in Cd2) + loadArchive(kArchiveCd2); + + // Store command + if (!hasCommand()) { + _command = WRAP_METHOD(Debugger, cmdBeetle); + copyCommand(argc, argv); + + return false; + } else { + clearBg(GraphicsManager::kBackgroundAll); + askForRedraw(); + redrawScreen(); + + // Save current state + SceneIndex previousScene = getState()->scene; + ObjectLocation previousLocation = getInventory()->get(kItemBeetle)->location; + ChapterIndex previousChapter = (ChapterIndex)getProgress().chapter; + + // Setup scene & inventory + getProgress().chapter = kChapter2; + Scene *scene = getScenes()->get(kSceneBeetle); + getInventory()->get(kItemBeetle)->location = kObjectLocation3; + + askForRedraw(); + redrawScreen(); + + // Load the beetle game + Action *action = NULL; + Beetle *beetle = new Beetle(_engine); + if (!beetle->isLoaded()) + beetle->load(); + + // Play the game + Common::Event ev; + bool playgame = true; + while (playgame) { + // Update beetle + beetle->update(); + + askForRedraw(); + redrawScreen(); + + while (g_engine->getEventManager()->pollEvent(ev)) { + + switch (ev.type) { + default: + break; + + case Common::EVENT_KEYDOWN: + // Exit beetle game on escape + if (ev.kbd.keycode == Common::KEYCODE_ESCAPE) + playgame = false; + + break; + + case Common::EVENT_MOUSEMOVE: { + // Update cursor + CursorStyle style = kCursorNormal; + SceneHotspot *hotspot = NULL; + if (scene->checkHotSpot(ev.mouse, &hotspot)) { + if (!action) + action = new Action(_engine); + + style = action->getCursor(*hotspot); + } + + _engine->getCursor()->setStyle(style); + break; + } + + + case Common::EVENT_LBUTTONUP: + case Common::EVENT_RBUTTONUP: + // Update coordinates + getLogic()->getGameState()->setCoordinates(ev.mouse); + + if (beetle->catchBeetle()) + playgame = false; + break; + } + + _engine->_system->delayMillis(10); + } + } + + // Cleanup + beetle->unload(); + delete beetle; + SAFE_DELETE(action); + + // Pause for a second to be able to see the final scene + _engine->_system->delayMillis(1000); + + // Restore state + getProgress().chapter = previousChapter; + getInventory()->get(kItemBeetle)->location = previousLocation; + + // Restore loaded archive + restoreArchive(); + + // Stop audio and restore scene + getSound()->stopAllSound(); + + clearBg(GraphicsManager::kBackgroundAll); + + Scene *oldScene = getScenes()->get(previousScene); + _engine->getGraphicsManager()->draw(oldScene, GraphicsManager::kBackgroundC); + + askForRedraw(); + redrawScreen(); + + resetCommand(); + } + } else { + DebugPrintf("Syntax: beetle\n"); + } + + return true; +} + +/** + * Command: adjusts the time delta + * + * @param argc The argument count. + * @param argv The values. + * + * @return true if it was handled, false otherwise + */ +bool Debugger::cmdTimeDelta(int argc, const char **argv) { + if (argc == 2) { + int delta = getNumber(argv[1]); + + if (delta <= 0 || delta > 500) + goto label_error; + + getState()->timeDelta = (uint)delta; + } else { +label_error: + DebugPrintf("Syntax: delta