From 40a6c232e9169a9cfe0d6a698b728c8565cd8959 Mon Sep 17 00:00:00 2001 From: Alyssa Milburn Date: Thu, 14 Jul 2011 20:08:06 +0200 Subject: COMPOSER: Add a first attempt at an engine. --- base/plugins.cpp | 3 + configure | 1 + engines/composer/composer.cpp | 889 +++++++++++++++++++++++++++++++++++++++++ engines/composer/composer.h | 197 +++++++++ engines/composer/detection.cpp | 139 +++++++ engines/composer/module.mk | 14 + engines/composer/resource.cpp | 250 ++++++++++++ engines/composer/resource.h | 93 +++++ engines/engines.mk | 5 + 9 files changed, 1591 insertions(+) create mode 100644 engines/composer/composer.cpp create mode 100644 engines/composer/composer.h create mode 100644 engines/composer/detection.cpp create mode 100644 engines/composer/module.mk create mode 100644 engines/composer/resource.cpp create mode 100644 engines/composer/resource.h diff --git a/base/plugins.cpp b/base/plugins.cpp index 4413995b88..4fa1a961da 100644 --- a/base/plugins.cpp +++ b/base/plugins.cpp @@ -97,6 +97,9 @@ public: #if PLUGIN_ENABLED_STATIC(CINE) LINK_PLUGIN(CINE) #endif + #if PLUGIN_ENABLED_STATIC(COMPOSER) + LINK_PLUGIN(COMPOSER) + #endif #if PLUGIN_ENABLED_STATIC(CRUISE) LINK_PLUGIN(CRUISE) #endif diff --git a/configure b/configure index 74541e3d98..b161c5a81e 100755 --- a/configure +++ b/configure @@ -84,6 +84,7 @@ add_engine agi "AGI" yes add_engine agos "AGOS" yes "agos2" add_engine agos2 "AGOS 2 games" yes add_engine cine "Cinematique evo 1" yes +add_engine composer "Magic Composer" no add_engine cruise "Cinematique evo 2" yes add_engine draci "Dragon History" yes add_engine drascula "Drascula: The Vampire Strikes Back" yes diff --git a/engines/composer/composer.cpp b/engines/composer/composer.cpp new file mode 100644 index 0000000000..b2dcc3b36c --- /dev/null +++ b/engines/composer/composer.cpp @@ -0,0 +1,889 @@ +/* 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 "common/scummsys.h" + +#include "common/config-manager.h" +#include "common/events.h" +#include "common/file.h" +#include "common/random.h" +#include "common/fs.h" +#include "common/keyboard.h" +#include "common/substream.h" +#include "common/memstream.h" + +#include "graphics/cursorman.h" +#include "graphics/surface.h" +#include "graphics/pixelformat.h" +#include "graphics/palette.h" + +#include "engines/util.h" +#include "engines/advancedDetector.h" + +#include "audio/audiostream.h" +#include "audio/decoders/raw.h" + +#include "composer/composer.h" +#include "composer/resource.h" + +namespace Composer { + +// bitmap compression types +enum { + kBitmapUncompressed = 0, + kBitmapSpp32 = 1, + kBitmapSLW8 = 3, + kBitmapRLESLWM = 4, + kBitmapSLWM = 5 +}; + +// new script ops +enum { + kOpPlusPlus = 0x1, + kOpMinusMinus = 0x2, + kOpAssign = 0x3, + kOpAdd = 0x4, + kOpSubtract = 0x5, + kOpMultiply = 0x6, + kOpDivide = 0x7, + kOpModulo = 0x8, + kOpMaybeAlsoAssign = 0x9, + kOpBooleanAssign = 0xA, + kOpNegate = 0xB, + kOpAnd = 0xC, + kOpOr = 0xD, + kOpXor = 0xE, + kOpNotPositive = 0xF, + kOpSqrt = 0x10, + kOpRandom = 0x11, + kOpExecuteScript = 0x12, + kOpCallFunc = 0x13, + kOpBoolLessThanEq = 0x14, + kOpBoolLessThan = 0x15, + kOpBoolGreaterThanEq = 0x16, + kOpBoolGreaterThan = 0x17, + kOpBoolEqual = 0x18, + kOpBoolNotEqual = 0x19, + kOpSaveArgs = 0x1A, + kOpRestoreArgs = 0x1B, + kOpSetReturnValue = 0x20, + kOpLessThanEq = 0x21, + kOpLessThan = 0x22, + kOpGreaterThanEq = 0x23, + kOpGreaterThan = 0x24, + kOpEqual = 0x25, + kOpNotEqual = 0x26, + kOpJump = 0x80, + kOpJumpIfNot = 0x81, + kOpJumpIf = 0x82, + kOpJumpIfNotValue = 0x83, + kOpJumpIfValue = 0x84 +}; + +enum { + kFuncPlayAnim = 35001, + kFuncStopAnim = 35002, + kFuncQueueScript = 35004, + kFuncDequeueScript = 35005, + kFuncHideMouse = 35009, + kFuncLoadPage = 35014, + kFuncUnloadPage = 35015, + kFuncSetPalette = 35016, + kFuncQueueScriptOnce = 35019 +}; + +// TODO: params: x, y, event param for done +Animation::Animation(Common::SeekableReadStream *stream, uint16 id, Common::Point basePos, uint32 eventParam) + : _stream(stream), _id(id), _basePos(basePos), _eventParam(eventParam) { + uint32 size = _stream->readUint32LE(); + _state = _stream->readUint32LE() + 1; + + // probably total size? + uint32 unknown = _stream->readUint32LE(); + + debug(8, "anim: size %d, state %08x, unknown %08x", size, _state, unknown); + + for (uint i = 0; i < size; i++) { + AnimationEntry entry; + entry.op = _stream->readUint16LE(); + entry.word6 = _stream->readUint16LE(); + entry.dword0 = _stream->readUint16LE(); + entry.counter = 0; + entry.word10 = 0; + debug(8, "anim entry: %04x, %04x, %04x", entry.op, entry.word6, entry.dword0); + _entries.push_back(entry); + } + + _offset = _stream->pos(); +} + +Animation::~Animation() { + delete _stream; +} + +void Animation::seekToCurrPos() { + _stream->seek(_offset, SEEK_SET); +} + +Pipe::Pipe(Common::SeekableReadStream *stream) { + _offset = 0; + _stream = stream; + + nextFrame(); +} + +void Pipe::nextFrame() { + _stream->seek(_offset, SEEK_SET); + + uint32 tagCount = _stream->readUint32LE(); + _offset += 4; + for (uint i = 0; i < tagCount; i++) { + uint32 tag = _stream->readUint32BE(); + uint32 count = _stream->readUint32LE(); + _offset += 8; + + ResourceMap &resMap = _types[tag]; + + _offset += (12 * count); + //uint32 baseOffset = _offset; + for (uint j = 0; j < count; j++) { + uint32 offset = _stream->readUint32LE(); + uint32 size = _stream->readUint32LE(); + uint16 id = _stream->readUint16LE(); + uint32 unknown = _stream->readUint16LE(); // frame id? + debug(9, "pipe: %s/%d: offset %d, size %d, unknown %d", tag2str(tag), id, offset, size, unknown); + + PipeResourceEntry entry; + entry.size = size; + entry.offset = _offset; + resMap[id].entries.push_back(entry); + + _offset += size; + } + _stream->seek(_offset, SEEK_SET); + } +} + +bool Pipe::hasResource(uint32 tag, uint16 id) const { + if (!_types.contains(tag)) + return false; + + return _types[tag].contains(id); +} + +Common::SeekableReadStream *Pipe::getResource(uint32 tag, uint16 id, bool buffering) { + if (!_types.contains(tag)) + error("Pipe does not contain '%s' %04x", tag2str(tag), id); + + const ResourceMap &resMap = _types[tag]; + + if (!resMap.contains(id)) + error("Archive does not contain '%s' %04x", tag2str(tag), id); + + const PipeResource &res = resMap[id]; + + if (!buffering) { + assert(res.entries.size() == 1); + return new Common::SeekableSubReadStream(_stream, res.entries[0].offset, res.entries[0].offset + res.entries[0].size); + } + + uint32 size = 0; + for (uint i = 0; i < res.entries.size(); i++) + size += res.entries[i].size; + + byte *buffer = new byte[size]; + uint32 offset = 0; + for (uint i = 0; i < res.entries.size(); i++) { + _stream->seek(res.entries[i].offset, SEEK_SET); + _stream->read(buffer + offset, res.entries[i].size); + offset += res.entries[i].size; + } + _types[tag].erase(id); + return new Common::MemoryReadStream(buffer, size, DisposeAfterUse::YES); +} + +void ComposerEngine::playAnimation(uint16 animId, int16 x, int16 y, int16 eventParam) { + // First, we check if this animation is already playing, + // and if it is, we sabotage that running one first. + for (Common::List::iterator i = _anims.begin(); i != _anims.end(); i++) { + Animation *anim = *i; + if (anim->_id != animId) + continue; + + // disable the animation + anim->_state = 0; + + // stop any animations it may have spawned + for (uint j = 0; j < anim->_entries.size(); j++) { + if (anim->_entries[j].op == 3) + ; // TODO: stop anim + } + + // kill any pipes owned by the animation + for (Common::List::iterator j = _pipes.begin(); j != _pipes.end(); j++) { + Pipe *pipe = *j; + if (pipe->_anim != anim) + continue; + j = _pipes.reverse_erase(j); + delete pipe; + break; + } + + break; + } + + Common::SeekableReadStream *stream = NULL; + Pipe *newPipe = NULL; + + // First, check the existing pipes. + for (Common::List::iterator j = _pipes.begin(); j != _pipes.end(); j++) { + Pipe *pipe = *j; + if (!pipe->hasResource(ID_ANIM, animId)) + continue; + stream = pipe->getResource(ID_ANIM, animId, false); + break; + } + + // If we didn't find it, try the libraries. + if (!stream) { + stream = getResource(ID_ANIM, animId); + + uint32 type = 0; + for (Common::List::iterator i = _libraries.begin(); i != _libraries.end(); i++) + if (i->_archive->hasResource(ID_ANIM, animId)) { + type = i->_archive->getResourceFlags(ID_ANIM, animId); + break; + } + + // If the resource is a pipe itself, then load the pipe + // and then fish the requested animation out of it. + if (type != 1) { + newPipe = new Pipe(stream); + _pipes.push_front(newPipe); + stream = newPipe->getResource(ID_ANIM, animId, false); + } + } + + Animation *anim = new Animation(stream, animId, Common::Point(x, y), eventParam); + _anims.push_back(anim); + if (newPipe) + newPipe->_anim = anim; + runEvent(1, animId, eventParam, 0); +} + +void ComposerEngine::playWaveForAnim(uint16 id, bool bufferingOnly) { + Common::SeekableReadStream *stream = NULL; + if (!bufferingOnly && hasResource(ID_WAVE, id)) { + stream = getResource(ID_WAVE, id); + } else { + for (Common::List::iterator k = _pipes.begin(); k != _pipes.end(); k++) { + Pipe *pipe = *k; + if (!pipe->hasResource(ID_WAVE, id)) + continue; + stream = pipe->getResource(ID_WAVE, id, true); + break; + } + } + if (!stream) + return; + // FIXME: non-pipe buffers have fixed wav header (data at +44, size at +40) + // FIXME: deal with word6 (priority) + byte *buffer = (byte *)malloc(stream->size()); + stream->read(buffer, stream->size()); + _audioStream->queueBuffer(buffer, stream->size(), DisposeAfterUse::YES, Audio::FLAG_UNSIGNED); + delete stream; + if (!_mixer->isSoundHandleActive(_soundHandle)) + _mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundHandle, _audioStream); +} + +void ComposerEngine::processAnimFrame() { + for (Common::List::iterator i = _anims.begin(); i != _anims.end(); i++) { + Animation *anim = *i; + + if (anim->_state <= 1) { + if (anim->_state == 1) { + runEvent(2, anim->_id, anim->_eventParam, 0); + } else { + // TODO: stop anything which was running + } + // TODO: kill pipes + delete anim; + i = _anims.reverse_erase(i); + + continue; + } + + anim->seekToCurrPos(); + + for (uint j = 0; j < anim->_entries.size(); j++) { + AnimationEntry &entry = anim->_entries[j]; + if (entry.op != 1) + break; + if (entry.counter) { + entry.counter--; + } else { + uint16 event = anim->_stream->readUint16LE(); + anim->_offset += 2; + if (event == 0xffff) { + entry.counter = anim->_stream->readUint16LE() - 1; + anim->_offset += 2; + } else { + runEvent(event, anim->_id, 0, 0); + } + } + } + } + + for (Common::List::iterator i = _anims.begin(); i != _anims.end(); i++) { + Animation *anim = *i; + + anim->_state--; + + for (uint j = 0; j < anim->_entries.size(); j++) { + AnimationEntry &entry = anim->_entries[j]; + + // TODO: only skip these at the start + if (entry.op == 1) + continue; + + if (entry.counter) { + entry.counter--; + if (entry.op == 2 && entry.word10) { + debug(4, "anim: continue play wave %d", entry.word10); + playWaveForAnim(entry.word10, true); + } + } else { + anim->seekToCurrPos(); + + uint16 data = anim->_stream->readUint16LE(); + anim->_offset += 2; + if (data == 0xffff) { + entry.counter = anim->_stream->readUint16LE() - 1; + anim->_offset += 2; + } else { + switch (entry.op) { + case 1: + // TODO + warning("ignoring tingie (%d)", data); + break; + case 2: + debug(4, "anim: play wave %d", data); + playWaveForAnim(data, false); + break; + case 3: + debug(4, "anim: play anim %d", data); + playAnimation(data, anim->_basePos.x, anim->_basePos.y, 1); + break; + case 4: + if (entry.word10 && (!data || data != entry.word10)) { + // TODO: erase old sprite + warning("ignoring anim sprite erase (%d)", entry.word10); + } + if (data) { + uint16 x = anim->_stream->readUint16LE(); + uint16 y = anim->_stream->readUint16LE(); + anim->_offset += 4; + // TODO: sprite change + warning("ignoring anim sprite draw (%d @ %d,%d)", data, x, y); + } + break; + default: + warning("unknown anim op %d", entry.op); + } + + entry.word10 = data; + } + } + } + } + + for (Common::List::iterator j = _pipes.begin(); j != _pipes.end(); j++) { + Pipe *pipe = *j; + pipe->nextFrame(); + } +} + +ComposerEngine::ComposerEngine(OSystem *syst, const ComposerGameDescription *gameDesc) : Engine(syst), _gameDescription(gameDesc) { + _rnd = new Common::RandomSource("composer"); + _audioStream = NULL; +} + +ComposerEngine::~ComposerEngine() { + DebugMan.clearAllDebugChannels(); + + for (Common::List::iterator i = _libraries.begin(); i != _libraries.end(); i++) + delete i->_archive; + + delete _audioStream; + delete _rnd; +} + +Common::Error ComposerEngine::run() { + Common::Event event; + + _vars.resize(1000); + for (uint i = 0; i < _vars.size(); i++) + _vars[i] = 0; + + _queuedScripts.resize(10); + for (uint i = 0; i < _queuedScripts.size(); i++) { + _queuedScripts[i]._count = 0; + _queuedScripts[i]._scriptId = 0; + } + + _bookIni.loadFromFile("programs/book.ini"); + + uint width = 640; + if (_bookIni.hasKey("Width", "Common")) + width = atoi(getStringFromConfig("Common", "Width").c_str()); + uint height = 480; + if (_bookIni.hasKey("Height", "Common")) + height = atoi(getStringFromConfig("Common", "Height").c_str()); + initGraphics(width, height, true); + _surface.create(width, height, Graphics::PixelFormat::createFormatCLUT8()); + + _audioStream = Audio::makeQueuingAudioStream(22050, false); + + loadLibrary(0); + + uint fps = atoi(getStringFromConfig("Common", "FPS").c_str()); + uint frameTime = 1000 / fps; + uint32 lastDrawTime = 0; + while (!shouldQuit()) { + if (hasResource(ID_BMAP, 1000)) + drawBMAP(1000, 0, 0); + + _system->copyRectToScreen((byte *)_surface.pixels, _surface.pitch, 0, 0, _surface.w, _surface.h); + _system->updateScreen(); + + uint32 thisTime = _system->getMillis(); + for (uint i = 0; i < _queuedScripts.size(); i++) { + QueuedScript &script = _queuedScripts[i]; + if (!script._count) + continue; + if (script._baseTime + script._duration > thisTime) + continue; + if (script._count != 0xffff) + script._count--; + script._baseTime = thisTime; + runScript(script._scriptId, i, 0, 0); + } + + // _system->delayMillis(frameTime + lastDrawTime - thisTime); + if (lastDrawTime + frameTime <= thisTime) { + lastDrawTime += frameTime; + + processAnimFrame(); + } + + while (_eventMan->pollEvent(event)) { + switch (event.type) { + case Common::EVENT_LBUTTONDOWN: + break; + + case Common::EVENT_LBUTTONUP: + break; + + case Common::EVENT_RBUTTONDOWN: + break; + + case Common::EVENT_MOUSEMOVE: + break; + + case Common::EVENT_KEYDOWN: + switch (event.kbd.keycode) { + case Common::KEYCODE_d: + /*if (event.kbd.hasFlags(Common::KBD_CTRL)) { + // Start the debugger + getDebugger()->attach(); + getDebugger()->onFrame(); + }*/ + break; + + case Common::KEYCODE_ESCAPE: + quitGame(); + break; + + default: + break; + } + break; + + case Common::EVENT_QUIT: + case Common::EVENT_RTL: + quitGame(); + break; + + default: + break; + } + } + + _system->delayMillis(20); + } + + return Common::kNoError; +} + +Common::String ComposerEngine::getStringFromConfig(const Common::String §ion, const Common::String &key) { + Common::String value; + if (!_bookIni.getKey(key, section, value)) + error("failed to find key '%s' in section '%s' of book config", key.c_str(), section.c_str()); + return value; +} + +Common::String ComposerEngine::getFilename(const Common::String §ion, uint id) { + Common::String key = Common::String::format("%d", id); + Common::String filename = getStringFromConfig(section, key); + while (filename.size() && (filename[0] == '~' || filename[0] == ':' || filename[0] == '\\')) + filename = filename.c_str() + 1; + Common::String outFilename; + for (uint i = 0; i < filename.size(); i++) { + if (filename[i] == '\\') + outFilename += '/'; + else + outFilename += filename[i]; + } + return outFilename; +} + +void ComposerEngine::loadLibrary(uint id) { + if (!id) + id = atoi(getStringFromConfig("Common", "StartUp").c_str()); + Common::String filename = getFilename("Libs", id); + + Library library; + + library._id = id; + library._archive = new ComposerArchive(); + if (!library._archive->openFile(filename)) + error("failed to open '%s'", filename.c_str()); + // FIXME: push in front? + _libraries.push_front(library); + + /*Common::SeekableReadStream *stream = _archive->getResource(ID_ANIM, 1004); + byte buf[stream->size()]; + stream->read(buf, stream->size()); + Common::hexdump(buf, stream->size()); + delete stream;*/ + + // TODO: set background properly + // + // TODO: better CTBL logic + loadCTBL(1000, 100); + + // Run the startup script. + runScript(1000, 0, 0, 0); + + runEvent(3, id, 0, 0); +} + +bool ComposerEngine::hasResource(uint32 tag, uint16 id) { + for (Common::List::iterator i = _libraries.begin(); i != _libraries.end(); i++) + if (i->_archive->hasResource(tag, id)) + return true; + + return false; +} + +Common::SeekableReadStream *ComposerEngine::getResource(uint32 tag, uint16 id) { + for (Common::List::iterator i = _libraries.begin(); i != _libraries.end(); i++) + if (i->_archive->hasResource(tag, id)) + return i->_archive->getResource(tag, id); + + error("No loaded library contains '%s' %04x", tag2str(tag), id); +} + +void ComposerEngine::runEvent(uint16 id, int16 param1, int16 param2, int16 param3) { + if (!hasResource(ID_EVNT, id)) + return; + + Common::SeekableReadStream *stream = getResource(ID_EVNT, id); + if (stream->size() != 2) + error("bad EVNT size %d", stream->size()); + uint16 scriptId = stream->readUint16LE(); + delete stream; + + debug(2, "running event %d via script %d(%d, %d, %d)", id, scriptId, param1, param2, param3); + + runScript(scriptId, param1, param2, param3); +} + +int16 ComposerEngine::runScript(uint16 id, int16 param1, int16 param2, int16 param3) { + _vars[1] = param1; + _vars[2] = param2; + _vars[3] = param3; + + runScript(id); + + return _vars[0]; +} + +int16 ComposerEngine::getArg(uint16 arg, uint16 type) { + switch (type) { + case 0: + return (int16)arg; + case 1: + return (int16)_vars[arg]; + case 2: + return (int16)_vars[_vars[arg]]; + default: + error("invalid argument type %d (getting arg %d)", type, arg); + } +} + +void ComposerEngine::setArg(uint16 arg, uint16 type, uint16 val) { + switch (type) { + case 1: + _vars[arg] = val; + break; + case 2: + _vars[_vars[arg]] = val; + break; + default: + error("invalid argument type %d (setting arg %d)", type, arg); + } + +} + +void ComposerEngine::runScript(uint16 id) { + if (!hasResource(ID_SCRP, id)) { + warning("ignoring attempt to run script %d, because it doesn't exist", id); + return; + } + + uint stackBase = _stack.size(); + _stack.resize(_stack.size() + 19); + + Common::SeekableReadStream *stream = getResource(ID_SCRP, id); + if (stream->size() < 2) + error("SCRP was too small (%d)", stream->size()); + uint16 size = stream->readUint16LE(); + if (stream->size() < 2 + 2*size) + error("SCRP was too small (%d, but claimed %d entries)", stream->size(), size); + uint16 *script = new uint16[size]; + for (uint i = 0; i < size; i++) + script[i] = stream->readUint16LE(); + delete stream; + + uint16 pos = 0; + bool lastResult = false; + while (pos < size) { + int16 val1, val2, val3; + + byte op = (byte)script[pos]; + uint numParams = (script[pos] & 0x300) >> 8; // 2 bits + if (pos + numParams >= size) + error("script ran out of content"); + uint arg1 = (script[pos] & 0xc00) >> 10; // 2 bits + uint arg2 = (script[pos] & 0x3000) >> 12; // 2 bits + uint arg3 = (script[pos] & 0xC000) >> 14; // 2 bits + switch (op) { + case kOpPlusPlus: + val1 = getArg(script[pos + 1], arg1); + debug(9, "[%d/%d]++ (now %d)", script[pos + 1], arg1, val1 + 1); + setArg(script[pos + 1], arg1, val1 + 1); + break; + case kOpAdd: + val2 = getArg(script[pos + 2], arg2); + val3 = getArg(script[pos + 3], arg3); + debug(9, "[%d/%d] = [%d/%d]=%d + [%d/%d]=%d (%d)", script[pos + 1], arg1, script[pos + 2], arg2, val2, script[pos+3], arg3, val3, val2 + val3); + setArg(script[pos + 1], arg1, val2 + val3); + break; + case kOpAssign: + val2 = getArg(script[pos + 2], arg2); + if (numParams != 2) + error("kOpAssign had wrong number of params (%d)", numParams); + debug(9, "[%d/%d] = [%d/%d] (%d)", script[pos + 1], arg1, script[pos + 2], arg2, val2); + setArg(script[pos + 1], arg1, val2); + break; + case kOpCallFunc: + val1 = getArg(script[pos + 1], arg1); + if (numParams != 1) + error("kOpCallFunc had wrong number of params (%d)", numParams); + debug(8, "%d(%d, %d, %d)", (uint16)val1, _vars[1], _vars[2], _vars[3]); + _vars[0] = scriptFuncCall(val1, _vars[1], _vars[2], _vars[3]); + break; + /*case kOpBoolGreaterThan: + lastResult = (getArg(script[pos + 1], arg1) > getArg(script[pos + 2], arg2)); + break;*/ + case kOpBoolEqual: + val1 = getArg(script[pos + 1], arg1); + val2 = getArg(script[pos + 2], arg2); + debug(9, "[%d/%d] == [%d/%d]? (%d > %d)", script[pos + 1], arg1, script[pos + 2], arg2, val1, val2); + lastResult = (val1 == val2); + break; + case kOpSaveArgs: + debug(9, "save args"); + for (uint i = 1; i < 19; i++) + _stack[stackBase + i] = _vars[i]; + break; + case kOpRestoreArgs: + debug(9, "restore args"); + for (uint i = 1; i < 19; i++) + _vars[i] = _stack[stackBase + i]; + break; + case kOpGreaterThan: + val2 = getArg(script[pos + 2], arg2); + val3 = getArg(script[pos + 3], arg3); + debug(9, "[%d/%d] = [%d/%d] > [%d/%d]? (%d > %d)", script[pos + 1], arg1, script[pos + 2], arg2, script[pos + 3], arg3, val2, val3); + setArg(script[pos + 1], arg1, (val3 > val2) ? 1 : 0); + break; + case kOpJump: + val1 = getArg(script[pos + 1], arg1); + debug(9, "jump by [%d/%d]=%d", script[pos + 1], arg1, val1); + pos += val1; + break; + case kOpJumpIfNot: + if (lastResult) + break; + val1 = getArg(script[pos + 1], arg1); + debug(9, "jump if not, by [%d/%d]=%d", script[pos + 1], arg1, val1); + pos += val1; + break; + case kOpJumpIfNotValue: + val1 = getArg(script[pos + 1], arg1); + val2 = getArg(script[pos + 2], arg2); + debug(9, "jump if not [%d/%d]=%d", script[pos + 1], arg1, val1); + if (val1) + break; + debug(9, "--> jump by [%d/%d]=%d", script[pos + 2], arg2, val2); + pos += val2; + break; + default: + error("unknown script op 0x%02x", op); + } + pos += (1 + numParams); + } + + delete[] script; + _stack.resize(_stack.size() - 19); +} + +int16 ComposerEngine::scriptFuncCall(uint16 id, int16 param1, int16 param2, int16 param3) { + switch (id) { + case kFuncPlayAnim: + debug(3, "kFuncPlayAnim(%d, %d, %d)", param1, param2, param3); + playAnimation(param1, param2, param3, 0); + break; + case kFuncStopAnim: + // TODO + warning("ignoring kFuncStopAnim(%d)", param1); + break; + case kFuncQueueScript: + debug(3, "kFuncQueueScript(%d, %d, %d)", param1, param2, param3); + _queuedScripts[param1]._baseTime = _system->getMillis(); + _queuedScripts[param1]._duration = 10 * param2; + _queuedScripts[param1]._count = 0xffff; + _queuedScripts[param1]._scriptId = param3; + return 0; + case kFuncDequeueScript: + debug(3, "kFuncDequeueScript(%d)", param1); + _queuedScripts[param1]._count = 0; + _queuedScripts[param1]._scriptId = 0; + return 0; + case kFuncHideMouse: + // TODO + warning("ignoring kFuncHideMouse(%d)", param1); + return 0; + break; + case kFuncLoadPage: + debug(3, "kFuncLoadPage(%d)", param1); + loadLibrary(param1); + return 1; + case kFuncUnloadPage: + // TODO + warning("ignoring kFuncUnloadPage(%d)", param1); + return 1; + case kFuncSetPalette: + // TODO: incomplete? + debug(4, "kFuncSetPalette(%d, %d)", param1, param2); + loadCTBL(param1, param2); + return 1; + break; + case kFuncQueueScriptOnce: + debug(3, "kFuncQueueScriptOnce(%d, %d, %d)", param1, param2, param3); + _queuedScripts[param1]._baseTime = _system->getMillis(); + _queuedScripts[param1]._duration = 10 * param2; + _queuedScripts[param1]._count = 1; + _queuedScripts[param1]._scriptId = param3; + return 0; + default: + error("unknown scriptFuncCall %d(%d, %d, %d)", (uint32)id, param1, param2, param3); + } +} + +void ComposerEngine::loadCTBL(uint id, uint fadePercent) { + Common::SeekableReadStream *stream = getResource(ID_CTBL, id); + + uint16 numEntries = stream->readUint16LE(); + debug(1, "CTBL: %d entries", numEntries); + + assert(numEntries <= 256); + assert(stream->size() == 2 + (numEntries * 3)); + + byte buffer[256 * 3]; + stream->read(buffer, numEntries * 3); + delete stream; + + for (uint i = 0; i < numEntries * 3; i++) + buffer[i] = ((unsigned int)buffer[i] * fadePercent) / 100; + + _system->getPaletteManager()->setPalette(buffer, 0, numEntries); +} + +void ComposerEngine::decompressBitmap(uint16 type, Common::SeekableReadStream *stream, byte *buffer, uint32 size) { + switch (type) { + case kBitmapUncompressed: + assert(stream->size() - (uint)stream->pos() == size); + stream->read(buffer, size); + break; + default: + error("decompressBitmap can't handle type %d", type); + } +} + +void ComposerEngine::drawBMAP(uint id, uint x, uint y) { + Common::SeekableReadStream *stream = getResource(ID_BMAP, id); + + uint16 type = stream->readUint16LE(); + uint16 height = stream->readUint16LE(); + uint16 width = stream->readUint16LE(); + uint32 size = stream->readUint32LE(); + debug(1, "BMAP: type %d, width %d, height %d, size %d", type, width, height, size); + + byte *buffer = new byte[width * height]; + decompressBitmap(type, stream, buffer, size); + + // incoming data is BMP-style (bottom-up), so flip it + byte *pixels = (byte *)_surface.pixels; + for (uint j = 0; j < height; j++) { + memcpy(pixels + (j * width), buffer + (height - j - 1) * width, width); + } + + delete[] buffer; + delete stream; +} + +} // End of namespace Composer diff --git a/engines/composer/composer.h b/engines/composer/composer.h new file mode 100644 index 0000000000..5d4233a1ba --- /dev/null +++ b/engines/composer/composer.h @@ -0,0 +1,197 @@ +/* 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 COMPOSER_H +#define COMPOSER_H + +#include "common/config-file.h" +#include "common/random.h" +#include "common/system.h" +#include "common/debug.h" +#include "common/debug-channels.h" +#include "common/textconsole.h" +#include "common/rect.h" + +#include "engines/engine.h" +#include "engines/util.h" + +#include "graphics/surface.h" + +#include "audio/mixer.h" + +#include "composer/resource.h" + +namespace Audio { + class QueuingAudioStream; +} + +namespace Composer { + +struct ComposerGameDescription; + +enum GameType { + GType_ComposerV1, + GType_ComposerV2 +}; + +class Archive; +class ComposerEngine; + +class Sprite { +}; + +struct AnimationEntry { + uint32 dword0; + uint16 op; + uint16 word6; + uint16 counter; + uint16 word10; +}; + +struct Animation { + Animation(Common::SeekableReadStream *stream, uint16 id, Common::Point basePos, uint32 eventParam); + ~Animation(); + + void seekToCurrPos(); + + uint16 _id; + Common::Point _basePos; + uint32 _eventParam; + + uint32 _state; + + Common::Array _entries; + + uint32 _offset; + Common::SeekableReadStream *_stream; +}; + +struct PipeResourceEntry { + uint32 size; + uint32 offset; +}; + +struct PipeResource { + Common::Array entries; +}; + +class Pipe { +public: + Pipe(Common::SeekableReadStream *stream); + void nextFrame(); + + Animation *_anim; + + bool hasResource(uint32 tag, uint16 id) const; + Common::SeekableReadStream *getResource(uint32 tag, uint16 id, bool buffering); + +protected: + Common::SeekableReadStream *_stream; + + typedef Common::HashMap ResourceMap; + typedef Common::HashMap TypeMap; + TypeMap _types; + + uint32 _offset; +}; + +class Button { +public: + Button(ComposerEngine *vm, uint16 id); +}; + +struct Library { + uint _id; + Archive *_archive; +}; + +struct QueuedScript { + uint32 _baseTime; + uint32 _duration; + uint32 _count; + uint16 _scriptId; +}; + +class ComposerEngine : public Engine { +protected: + Common::Error run(); + +public: + ComposerEngine(OSystem *syst, const ComposerGameDescription *gameDesc); + virtual ~ComposerEngine(); + + virtual bool hasFeature(EngineFeature f) const; + + int getGameType() const; + const char *getGameId() const; + uint32 getFeatures() const; + Common::Language getLanguage() const; + + const ComposerGameDescription *_gameDescription; + +private: + Common::RandomSource *_rnd; + + Audio::SoundHandle _soundHandle; + Audio::QueuingAudioStream *_audioStream; + + Graphics::Surface _surface; + Common::ConfigFile _bookIni; + Common::List _libraries; + + Common::Array _stack; + Common::Array _vars; + + Common::Array _queuedScripts; + Common::List _anims; + Common::List _pipes; + + Common::String getStringFromConfig(const Common::String §ion, const Common::String &key); + Common::String getFilename(const Common::String §ion, uint id); + void loadLibrary(uint id); + + bool hasResource(uint32 tag, uint16 id); + Common::SeekableReadStream *getResource(uint32 tag, uint16 id); + + void runEvent(uint16 id, int16 param1, int16 param2, int16 param3); + int16 runScript(uint16 id, int16 param1, int16 param2, int16 param3); + + int16 getArg(uint16 arg, uint16 type); + void setArg(uint16 arg, uint16 type, uint16 val); + void runScript(uint16 id); + int16 scriptFuncCall(uint16 id, int16 param1, int16 param2, int16 param3); + + void playAnimation(uint16 animId, int16 param1, int16 param2, int16 param3); + void playWaveForAnim(uint16 id, bool bufferingOnly); + void processAnimFrame(); + + void loadCTBL(uint id, uint fadePercent); + void decompressBitmap(uint16 type, Common::SeekableReadStream *stream, byte *buffer, uint32 size); + void drawBMAP(uint id, uint x, uint y); +}; + +} // End of namespace Composer + +#endif diff --git a/engines/composer/detection.cpp b/engines/composer/detection.cpp new file mode 100644 index 0000000000..8704a5eabc --- /dev/null +++ b/engines/composer/detection.cpp @@ -0,0 +1,139 @@ +/* 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 "base/plugins.h" +#include "engines/advancedDetector.h" + +#include "composer/composer.h" + +namespace Composer { + +struct ComposerGameDescription { + ADGameDescription desc; + + int gameType; +}; + +int ComposerEngine::getGameType() const { + return _gameDescription->gameType; +} + +const char *ComposerEngine::getGameId() const { + return _gameDescription->desc.gameid; +} + +uint32 ComposerEngine::getFeatures() const { + return _gameDescription->desc.flags; +} + +Common::Language ComposerEngine::getLanguage() const { + return _gameDescription->desc.language; +} + +} + +static const PlainGameDescriptor composerGames[] = { + {"darby", "Darby the Dragon"}, + {"demo1", "Magic Tales Demo 1"}, + {0, 0} +}; + +namespace Composer { + +using Common::GUIO_NONE; + +static const ComposerGameDescription gameDescriptions[] = { + { + { + "demo1", + 0, + {{"book.ini", 0, "dbc98c566f4ac61b544443524585dccb", -1}, + AD_LISTEND}, + Common::EN_ANY, + Common::kPlatformWindows, + ADGF_NO_FLAGS, + Common::GUIO_NONE + }, + GType_ComposerV1 + }, + + { + { + "darby", + 0, + {{"install.inf", 0, "e83cc20ee18a2e138da1aadfc640dff2", -1}, + AD_LISTEND}, + Common::EN_ANY, + Common::kPlatformWindows, + ADGF_NO_FLAGS, + Common::GUIO_NONE + }, + GType_ComposerV2 + }, + + { AD_TABLE_END_MARKER, 0 } +}; + +} // End of namespace Composer + +using namespace Composer; + +class ComposerMetaEngine : public AdvancedMetaEngine { +public: + ComposerMetaEngine() : AdvancedMetaEngine(Composer::gameDescriptions, sizeof(Composer::ComposerGameDescription), composerGames) {} + + virtual const char *getName() const { + return "Magic Composer Engine"; + } + + virtual const char *getOriginalCopyright() const { + return "Animation Magic"; + } + + virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const; + virtual bool hasFeature(MetaEngineFeature f) const; +}; + +bool ComposerMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const { + const Composer::ComposerGameDescription *gd = (const Composer::ComposerGameDescription *)desc; + if (gd) { + *engine = new Composer::ComposerEngine(syst, gd); + } + return gd != 0; +} + +bool ComposerMetaEngine::hasFeature(MetaEngineFeature f) const { + return false; +} + +bool Composer::ComposerEngine::hasFeature(EngineFeature f) const { + return (f == kSupportsRTL); +} + +#if PLUGIN_ENABLED_DYNAMIC(COMPOSER) +REGISTER_PLUGIN_DYNAMIC(COMPOSER, PLUGIN_TYPE_ENGINE, ComposerMetaEngine); +#else +REGISTER_PLUGIN_STATIC(COMPOSER, PLUGIN_TYPE_ENGINE, ComposerMetaEngine); +#endif diff --git a/engines/composer/module.mk b/engines/composer/module.mk new file mode 100644 index 0000000000..5637ffbc6e --- /dev/null +++ b/engines/composer/module.mk @@ -0,0 +1,14 @@ +MODULE := engines/composer + +MODULE_OBJS = \ + composer.o \ + detection.o \ + resource.o + +# This module can be built as a plugin +ifdef BUILD_PLUGINS +PLUGIN := 1 +endif + +# Include common rules +include $(srcdir)/rules.mk diff --git a/engines/composer/resource.cpp b/engines/composer/resource.cpp new file mode 100644 index 0000000000..390649ba52 --- /dev/null +++ b/engines/composer/resource.cpp @@ -0,0 +1,250 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "composer/resource.h" + +#include "common/debug.h" +#include "common/substream.h" +#include "common/util.h" +#include "common/textconsole.h" + +namespace Composer { + +// Base Archive code +// (copied from clone2727's mohawk code) + +Archive::Archive() { + _stream = 0; +} + +Archive::~Archive() { + close(); +} + +bool Archive::openFile(const Common::String &fileName) { + Common::File *file = new Common::File(); + + if (!file->open(fileName)) { + delete file; + return false; + } + + if (!openStream(file)) { + close(); + return false; + } + + return true; +} + +void Archive::close() { + _types.clear(); + delete _stream; _stream = 0; +} + +bool Archive::hasResource(uint32 tag, uint16 id) const { + if (!_types.contains(tag)) + return false; + + return _types[tag].contains(id); +} + +bool Archive::hasResource(uint32 tag, const Common::String &resName) const { + if (!_types.contains(tag) || resName.empty()) + return false; + + const ResourceMap &resMap = _types[tag]; + + for (ResourceMap::const_iterator it = resMap.begin(); it != resMap.end(); it++) + if (it->_value.name.matchString(resName)) + return true; + + return false; +} + +Common::SeekableReadStream *Archive::getResource(uint32 tag, uint16 id) { + if (!_types.contains(tag)) + error("Archive does not contain '%s' %04x", tag2str(tag), id); + + const ResourceMap &resMap = _types[tag]; + + if (!resMap.contains(id)) + error("Archive does not contain '%s' %04x", tag2str(tag), id); + + const Resource &res = resMap[id]; + + return new Common::SeekableSubReadStream(_stream, res.offset, res.offset + res.size); +} + +uint32 Archive::getResourceFlags(uint32 tag, uint16 id) const { + if (!_types.contains(tag)) + error("Archive does not contain '%s' %04x", tag2str(tag), id); + + const ResourceMap &resMap = _types[tag]; + + if (!resMap.contains(id)) + error("Archive does not contain '%s' %04x", tag2str(tag), id); + + const Resource &res = resMap[id]; + + return res.flags; +} + +uint32 Archive::getOffset(uint32 tag, uint16 id) const { + if (!_types.contains(tag)) + error("Archive does not contain '%s' %04x", tag2str(tag), id); + + const ResourceMap &resMap = _types[tag]; + + if (!resMap.contains(id)) + error("Archive does not contain '%s' %04x", tag2str(tag), id); + + return resMap[id].offset; +} + +uint16 Archive::findResourceID(uint32 tag, const Common::String &resName) const { + if (!_types.contains(tag) || resName.empty()) + return 0xFFFF; + + const ResourceMap &resMap = _types[tag]; + + for (ResourceMap::const_iterator it = resMap.begin(); it != resMap.end(); it++) + if (it->_value.name.matchString(resName)) + return it->_key; + + return 0xFFFF; +} + +Common::String Archive::getName(uint32 tag, uint16 id) const { + if (!_types.contains(tag)) + error("Archive does not contain '%s' %04x", tag2str(tag), id); + + const ResourceMap &resMap = _types[tag]; + + if (!resMap.contains(id)) + error("Archive does not contain '%s' %04x", tag2str(tag), id); + + return resMap[id].name; +} + +Common::Array Archive::getResourceTypeList() const { + Common::Array typeList; + + for (TypeMap::const_iterator it = _types.begin(); it != _types.end(); it++) + typeList.push_back(it->_key); + + return typeList; +} + +Common::Array Archive::getResourceIDList(uint32 type) const { + Common::Array idList; + + if (!_types.contains(type)) + return idList; + + const ResourceMap &resMap = _types[type]; + + for (ResourceMap::const_iterator it = resMap.begin(); it != resMap.end(); it++) + idList.push_back(it->_key); + + return idList; +} + +// Composer Archive code + +bool ComposerArchive::openStream(Common::SeekableReadStream *stream) { + // Make sure no other file is open... + close(); + + bool newStyle = false; + uint32 headerSize = stream->readUint32LE(); + if (headerSize == SWAP_CONSTANT_32(ID_LBRC)) { + // new-style file + newStyle = true; + headerSize = stream->readUint32LE(); + uint32 zeros = stream->readUint32LE(); + if (zeros != 0) + error("invalid LBRC header (%d instead of zeros)", zeros); + } + + uint16 numResourceTypes = stream->readUint16LE(); + if (newStyle) { + uint16 unknown = stream->readUint16LE(); + debug(4, "skipping unknown %04x", unknown); + } + + debug(4, "Reading LBRC resource table with %d entries", numResourceTypes); + for (uint i = 0; i < numResourceTypes; i++) { + uint32 tag = stream->readUint32BE(); + uint32 tableOffset = stream->readUint32LE(); + debug(4, "Type '%s' at offset %d", tag2str(tag), tableOffset); + // starting from the start of the resource table, which differs + // according to whether we have the 10 extra bytes for newStyle + if (newStyle) + tableOffset += 16; + else + tableOffset += 6; + + ResourceMap &resMap = _types[tag]; + + uint32 oldPos = stream->pos(); + stream->seek(tableOffset); + + while (true) { + if (stream->eos()) + error("LBRC file ran out of stream"); + + uint32 offset, size, id, flags; + if (newStyle) { + offset = stream->readUint32LE(); + if (!offset) + break; + size = stream->readUint32LE(); + id = stream->readUint16LE(); + flags = stream->readUint16LE(); // set to 1 for preload, otherwise no preload + /*uint32 junk = */ stream->readUint32LE(); + } else { + id = stream->readUint16LE(); + if (!id) + break; + offset = stream->readUint32LE(); + offset += headerSize; + size = stream->readUint32LE(); + flags = stream->readUint16LE(); // FIXME + + } + + Resource &res = resMap[id]; + res.offset = offset; + res.size = size; + res.flags = flags; + debug(4, "Id %d, offset %d, size %d, flags %08x", id, offset, size, flags); + } + + stream->seek(oldPos); + } + + _stream = stream; + return true; +} + +} // End of namespace Composer diff --git a/engines/composer/resource.h b/engines/composer/resource.h new file mode 100644 index 0000000000..4cadd80e22 --- /dev/null +++ b/engines/composer/resource.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. + * + */ + +#include "common/scummsys.h" +#include "common/endian.h" +#include "common/hashmap.h" +#include "common/file.h" +#include "common/str.h" + +#ifndef COMPOSER_RESOURCE_H +#define COMPOSER_RESOURCE_H + +namespace Composer { + +#define ID_LBRC MKTAG('L','B','R','C') // Main FourCC + +#define ID_ANIM MKTAG('A','N','I','M') // Animation +#define ID_BMAP MKTAG('B','M','A','P') // Bitmap +#define ID_BUTN MKTAG('B','U','T','N') // Button +#define ID_CTBL MKTAG('C','T','B','L') // Color Table +#define ID_EVNT MKTAG('E','V','N','T') // Event +#define ID_PIPE MKTAG('P','I','P','E') // Pipe +#define ID_SCRP MKTAG('S','C','R','P') // Script +#define ID_VARI MKTAG('V','A','R','I') // Variables +#define ID_WAVE MKTAG('W','A','V','E') // Wave + +class Archive { +public: + Archive(); + virtual ~Archive(); + + bool openFile(const Common::String &fileName); + virtual bool openStream(Common::SeekableReadStream *stream) = 0; + void close(); + + bool isOpen() const { return _stream != 0; } + + bool hasResource(uint32 tag, uint16 id) const; + bool hasResource(uint32 tag, const Common::String &resName) const; + Common::SeekableReadStream *getResource(uint32 tag, uint16 id); + uint32 getResourceFlags(uint32 tag, uint16 id) const; + uint32 getOffset(uint32 tag, uint16 id) const; + uint16 findResourceID(uint32 tag, const Common::String &resName) const; + Common::String getName(uint32 tag, uint16 id) const; + + Common::Array getResourceTypeList() const; + Common::Array getResourceIDList(uint32 type) const; + +protected: + Common::SeekableReadStream *_stream; + + struct Resource { + uint32 offset; + uint32 size; + Common::String name; + uint32 flags; + }; + + typedef Common::HashMap ResourceMap; + typedef Common::HashMap TypeMap; + TypeMap _types; +}; + +class ComposerArchive : public Archive { +public: + ComposerArchive() : Archive() {} + ~ComposerArchive() {} + + bool openStream(Common::SeekableReadStream *stream); +}; + +} // End of namespace Composer + +#endif diff --git a/engines/engines.mk b/engines/engines.mk index dc09fbd54e..de11496b7b 100644 --- a/engines/engines.mk +++ b/engines/engines.mk @@ -31,6 +31,11 @@ DEFINES += -DENABLE_CINE=$(ENABLE_CINE) MODULES += engines/cine endif +ifdef ENABLE_COMPOSER +DEFINES += -DENABLE_COMPOSER=$(ENABLE_COMPOSER) +MODULES += engines/composer +endif + ifdef ENABLE_CRUISE DEFINES += -DENABLE_CRUISE=$(ENABLE_CRUISE) MODULES += engines/cruise -- cgit v1.2.3 From 3935b3ed6be6bde5272b0d84a3dbe0df54f205fc Mon Sep 17 00:00:00 2001 From: Alyssa Milburn Date: Thu, 14 Jul 2011 23:36:49 +0200 Subject: COMPOSER: Manage/render sprites. --- engines/composer/composer.cpp | 181 ++++++++++++++++++++++++++++++++++++++---- engines/composer/composer.h | 13 ++- 2 files changed, 177 insertions(+), 17 deletions(-) diff --git a/engines/composer/composer.cpp b/engines/composer/composer.cpp index b2dcc3b36c..3651a49328 100644 --- a/engines/composer/composer.cpp +++ b/engines/composer/composer.cpp @@ -212,7 +212,7 @@ Common::SeekableReadStream *Pipe::getResource(uint32 tag, uint16 id, bool buffer for (uint i = 0; i < res.entries.size(); i++) size += res.entries[i].size; - byte *buffer = new byte[size]; + byte *buffer = (byte *)malloc(size); uint32 offset = 0; for (uint i = 0; i < res.entries.size(); i++) { _stream->seek(res.entries[i].offset, SEEK_SET); @@ -397,15 +397,19 @@ void ComposerEngine::processAnimFrame() { break; case 4: if (entry.word10 && (!data || data != entry.word10)) { - // TODO: erase old sprite - warning("ignoring anim sprite erase (%d)", entry.word10); + debug(4, "anim: erase sprite %d", entry.word10); + removeSprite(data, anim->_id); } if (data) { uint16 x = anim->_stream->readUint16LE(); uint16 y = anim->_stream->readUint16LE(); + Common::Point pos(x, y); anim->_offset += 4; - // TODO: sprite change - warning("ignoring anim sprite draw (%d @ %d,%d)", data, x, y); + uint16 animId = anim->_id; + if (anim->_state == entry.dword0) + animId = 0; + debug(4, "anim: draw sprite %d at (relative) %d,%d", data, x, y); + addSprite(data, animId, entry.word6, anim->_basePos + pos); } break; default: @@ -424,6 +428,33 @@ void ComposerEngine::processAnimFrame() { } } +void ComposerEngine::addSprite(uint16 id, uint16 animId, uint16 zorder, const Common::Point &pos) { + Sprite sprite; + sprite.id = id; + sprite.animId = animId; + sprite.zorder = zorder; + sprite.pos = pos; + + for (Common::List::iterator i = _sprites.begin(); i != _sprites.end(); i++) { + if (sprite.zorder < i->zorder) + continue; + _sprites.insert(i, sprite); + return; + } + _sprites.push_back(sprite); +} + +void ComposerEngine::removeSprite(uint16 id, uint16 animId) { + for (Common::List::iterator i = _sprites.begin(); i != _sprites.end(); i++) { + if (i->id != id) + continue; + if (animId && i->animId != animId) + continue; + i = _sprites.reverse_erase(i); + return; + } +} + ComposerEngine::ComposerEngine(OSystem *syst, const ComposerGameDescription *gameDesc) : Engine(syst), _gameDescription(gameDesc) { _rnd = new Common::RandomSource("composer"); _audioStream = NULL; @@ -471,12 +502,6 @@ Common::Error ComposerEngine::run() { uint frameTime = 1000 / fps; uint32 lastDrawTime = 0; while (!shouldQuit()) { - if (hasResource(ID_BMAP, 1000)) - drawBMAP(1000, 0, 0); - - _system->copyRectToScreen((byte *)_surface.pixels, _surface.pitch, 0, 0, _surface.w, _surface.h); - _system->updateScreen(); - uint32 thisTime = _system->getMillis(); for (uint i = 0; i < _queuedScripts.size(); i++) { QueuedScript &script = _queuedScripts[i]; @@ -494,6 +519,15 @@ Common::Error ComposerEngine::run() { if (lastDrawTime + frameTime <= thisTime) { lastDrawTime += frameTime; + if (hasResource(ID_BMAP, 1000)) + drawBMAP(1000, 0, 0); + for (Common::List::iterator i = _sprites.begin(); i != _sprites.end(); i++) { + drawBMAP(i->id, i->pos.x, i->pos.y); + } + + _system->copyRectToScreen((byte *)_surface.pixels, _surface.pitch, 0, 0, _surface.w, _surface.h); + _system->updateScreen(); + processAnimFrame(); } @@ -853,19 +887,130 @@ void ComposerEngine::loadCTBL(uint id, uint fadePercent) { _system->getPaletteManager()->setPalette(buffer, 0, numEntries); } -void ComposerEngine::decompressBitmap(uint16 type, Common::SeekableReadStream *stream, byte *buffer, uint32 size) { +static void decompressSLWM(byte *buffer, Common::SeekableReadStream *stream) { + uint bitsLeft = 0; + uint16 lastBits; + byte currBit; + while (true) { + if (bitsLeft == 0) { bitsLeft = 16; lastBits = stream->readUint16LE(); } + currBit = (lastBits & 1); lastBits >>= 1; bitsLeft--; + + if (currBit) { + // single byte + *buffer++ = stream->readByte(); + continue; + } + + if (bitsLeft == 0) { bitsLeft = 16; lastBits = stream->readUint16LE(); } + currBit = (lastBits & 1); lastBits >>= 1; bitsLeft--; + + uint start; + uint count; + if (currBit) { + uint orMask = stream->readByte(); + uint in = stream->readByte(); + count = in & 7; + start = ((in & ~7) << 5) | orMask; + if (!count) { + count = stream->readByte(); + if (!count) + break; + count -= 2; + } + } else { + // count encoded in the next two bits + count = 0; + + if (bitsLeft == 0) { bitsLeft = 16; lastBits = stream->readUint16LE(); } + currBit = (lastBits & 1); lastBits >>= 1; bitsLeft--; + + count = (count << 1) | currBit; + + if (bitsLeft == 0) { bitsLeft = 16; lastBits = stream->readUint16LE(); } + currBit = (lastBits & 1); lastBits >>= 1; bitsLeft--; + + count = (count << 1) | currBit; + + start = stream->readByte(); + } + + count += 2; + start++; + memcpy(buffer, buffer - start, count); + buffer += count; + } +} + +void ComposerEngine::decompressBitmap(uint16 type, Common::SeekableReadStream *stream, byte *buffer, uint32 size, uint width, uint height) { switch (type) { case kBitmapUncompressed: assert(stream->size() - (uint)stream->pos() == size); + assert(size == width * height); stream->read(buffer, size); break; + case kBitmapRLESLWM: + { + uint32 bufSize = stream->readUint32LE(); + byte *tempBuf = new byte[bufSize]; + decompressSLWM(tempBuf, stream); + + uint instrPos = tempBuf[0] + 1; + instrPos += READ_LE_UINT16(tempBuf + instrPos) + 2; + byte *instr = tempBuf + instrPos; + + uint line = 0; + while (line++ < height) { + uint pixels = 0; + + while (pixels < width) { + byte data = *instr++; + byte color = tempBuf[(data & 0x7F) + 1]; + if (!(data & 0x80)) { + *buffer++ = color; + pixels++; + } else { + byte count = *instr++; + if (!count) { + while (pixels++ < width) + *buffer++ = color; + break; + } + for (uint i = 0; i < count; i++) { + *buffer++ = color; + pixels++; + } + } + } + } + delete[] tempBuf; + } + break; + case kBitmapSLWM: + decompressSLWM(buffer, stream); + break; default: error("decompressBitmap can't handle type %d", type); } } void ComposerEngine::drawBMAP(uint id, uint x, uint y) { - Common::SeekableReadStream *stream = getResource(ID_BMAP, id); + Common::SeekableReadStream *stream = NULL; + if (hasResource(ID_BMAP, id)) + stream = getResource(ID_BMAP, id); + else + for (Common::List::iterator k = _pipes.begin(); k != _pipes.end(); k++) { + Pipe *pipe = *k; + if (!pipe->hasResource(ID_BMAP, id)) + continue; + stream = pipe->getResource(ID_BMAP, id, true); + break; + } + + if (!stream) { + // FIXME + warning("couldn't find BMAP %d", id); + return; + } uint16 type = stream->readUint16LE(); uint16 height = stream->readUint16LE(); @@ -874,12 +1019,18 @@ void ComposerEngine::drawBMAP(uint id, uint x, uint y) { debug(1, "BMAP: type %d, width %d, height %d, size %d", type, width, height, size); byte *buffer = new byte[width * height]; - decompressBitmap(type, stream, buffer, size); + decompressBitmap(type, stream, buffer, size, width, height); + + debug(1, "draw at %d,%d", x, y); // incoming data is BMP-style (bottom-up), so flip it byte *pixels = (byte *)_surface.pixels; for (uint j = 0; j < height; j++) { - memcpy(pixels + (j * width), buffer + (height - j - 1) * width, width); + byte *in = buffer + (height - j - 1) * width; + byte *out = pixels + ((j + y) * _surface.w) + x; + for (uint i = 0; i < width; i++) + if (in[i]) + out[i] = in[i]; } delete[] buffer; diff --git a/engines/composer/composer.h b/engines/composer/composer.h index 5d4233a1ba..e535bfbc6e 100644 --- a/engines/composer/composer.h +++ b/engines/composer/composer.h @@ -59,7 +59,11 @@ enum GameType { class Archive; class ComposerEngine; -class Sprite { +struct Sprite { + uint16 id; + uint16 animId; + uint16 zorder; + Common::Point pos; }; struct AnimationEntry { @@ -158,6 +162,8 @@ private: Audio::QueuingAudioStream *_audioStream; Graphics::Surface _surface; + Common::List _sprites; + Common::ConfigFile _bookIni; Common::List _libraries; @@ -187,8 +193,11 @@ private: void playWaveForAnim(uint16 id, bool bufferingOnly); void processAnimFrame(); + void addSprite(uint16 id, uint16 animId, uint16 zorder, const Common::Point &pos); + void removeSprite(uint16 id, uint16 animId); + void loadCTBL(uint id, uint fadePercent); - void decompressBitmap(uint16 type, Common::SeekableReadStream *stream, byte *buffer, uint32 size); + void decompressBitmap(uint16 type, Common::SeekableReadStream *stream, byte *buffer, uint32 size, uint width, uint height); void drawBMAP(uint id, uint x, uint y); }; -- cgit v1.2.3 From 24ca79e740ece52072e026d39f34234c24333c5c Mon Sep 17 00:00:00 2001 From: Alyssa Milburn Date: Fri, 15 Jul 2011 00:02:28 +0200 Subject: COMPOSER: Detect Gregory and the Hot Air Balloon. --- engines/composer/detection.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/engines/composer/detection.cpp b/engines/composer/detection.cpp index 8704a5eabc..c646a0e43e 100644 --- a/engines/composer/detection.cpp +++ b/engines/composer/detection.cpp @@ -56,6 +56,7 @@ Common::Language ComposerEngine::getLanguage() const { static const PlainGameDescriptor composerGames[] = { {"darby", "Darby the Dragon"}, + {"gregory", "Gregory and the Hot Air Balloon"}, {"demo1", "Magic Tales Demo 1"}, {0, 0} }; @@ -93,6 +94,20 @@ static const ComposerGameDescription gameDescriptions[] = { GType_ComposerV2 }, + { + { + "gregory", + 0, + {{"install.inf", 0, "b7e9d6f7949d412dad0a183375069844", -1}, + AD_LISTEND}, + Common::EN_ANY, + Common::kPlatformWindows, + ADGF_NO_FLAGS, + Common::GUIO_NONE + }, + GType_ComposerV2 + }, + { AD_TABLE_END_MARKER, 0 } }; -- cgit v1.2.3 From add71297bd283516ff3bd75f13bea69b6898adaa Mon Sep 17 00:00:00 2001 From: Alyssa Milburn Date: Fri, 15 Jul 2011 15:18:59 +0200 Subject: COMPOSER: Don't read off the end of pipes. --- engines/composer/composer.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/engines/composer/composer.cpp b/engines/composer/composer.cpp index 3651a49328..fba699660d 100644 --- a/engines/composer/composer.cpp +++ b/engines/composer/composer.cpp @@ -154,6 +154,9 @@ Pipe::Pipe(Common::SeekableReadStream *stream) { } void Pipe::nextFrame() { + if (_offset == _stream->size()) + return; + _stream->seek(_offset, SEEK_SET); uint32 tagCount = _stream->readUint32LE(); -- cgit v1.2.3 From 8517c17b0440bd8368479ea0fa3e048e050e028b Mon Sep 17 00:00:00 2001 From: Alyssa Milburn Date: Fri, 15 Jul 2011 15:19:37 +0200 Subject: COMPOSER: Handle single-directory games. --- engines/composer/composer.cpp | 18 +++++++++++++++++- engines/composer/composer.h | 1 + 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/engines/composer/composer.cpp b/engines/composer/composer.cpp index fba699660d..961665bafc 100644 --- a/engines/composer/composer.cpp +++ b/engines/composer/composer.cpp @@ -486,7 +486,12 @@ Common::Error ComposerEngine::run() { _queuedScripts[i]._scriptId = 0; } - _bookIni.loadFromFile("programs/book.ini"); + _directoriesToStrip = 1; + if (!_bookIni.loadFromFile("book.ini")) { + _directoriesToStrip = 0; + if (!_bookIni.loadFromFile("programs/book.ini")) + error("failed to find book.ini"); + } uint width = 640; if (_bookIni.hasKey("Width", "Common")) @@ -595,6 +600,17 @@ Common::String ComposerEngine::getFilename(const Common::String §ion, uint i Common::String filename = getStringFromConfig(section, key); while (filename.size() && (filename[0] == '~' || filename[0] == ':' || filename[0] == '\\')) filename = filename.c_str() + 1; + + uint slashesToStrip = _directoriesToStrip; + while (slashesToStrip--) { + for (uint i = 0; i < filename.size(); i++) { + if (filename[i] != '\\') + continue; + filename = filename.c_str() + i + 1; + break; + } + } + Common::String outFilename; for (uint i = 0; i < filename.size(); i++) { if (filename[i] == '\\') diff --git a/engines/composer/composer.h b/engines/composer/composer.h index e535bfbc6e..b18b3575f2 100644 --- a/engines/composer/composer.h +++ b/engines/composer/composer.h @@ -164,6 +164,7 @@ private: Graphics::Surface _surface; Common::List _sprites; + uint _directoriesToStrip; Common::ConfigFile _bookIni; Common::List _libraries; -- cgit v1.2.3 From 7849466760eca03d099d27e4b3dc9a9e1e388bb4 Mon Sep 17 00:00:00 2001 From: Alyssa Milburn Date: Fri, 15 Jul 2011 15:20:04 +0200 Subject: COMPOSER: Detect 'Liam Finds a Story' and the bundled demos. --- engines/composer/detection.cpp | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/engines/composer/detection.cpp b/engines/composer/detection.cpp index c646a0e43e..8d4cdc5aa4 100644 --- a/engines/composer/detection.cpp +++ b/engines/composer/detection.cpp @@ -57,7 +57,9 @@ Common::Language ComposerEngine::getLanguage() const { static const PlainGameDescriptor composerGames[] = { {"darby", "Darby the Dragon"}, {"gregory", "Gregory and the Hot Air Balloon"}, - {"demo1", "Magic Tales Demo 1"}, + {"liam", "Liam Finds a Story"}, + {"magictalesdemo1", "Magic Tales Demo 1"}, + {"magictalesdemo2", "Magic Tales Demo 2"}, {0, 0} }; @@ -66,9 +68,10 @@ namespace Composer { using Common::GUIO_NONE; static const ComposerGameDescription gameDescriptions[] = { + // from Liam Finds a Story CD { { - "demo1", + "magictalesdemo1", 0, {{"book.ini", 0, "dbc98c566f4ac61b544443524585dccb", -1}, AD_LISTEND}, @@ -80,6 +83,35 @@ static const ComposerGameDescription gameDescriptions[] = { GType_ComposerV1 }, + { + { + "liam", + 0, + {{"install.inf", 0, "320d2f1d4f8dd96947676ae25d6688c6", -1}, + AD_LISTEND}, + Common::EN_ANY, + Common::kPlatformWindows, + ADGF_NO_FLAGS, + Common::GUIO_NONE + }, + GType_ComposerV2 + }, + + // from Liam Finds a Story CD + { + { + "magictalesdemo2", + 0, + {{"book.ini", 0, "3dede2522bb0886c95667b082987a87f", -1}, + AD_LISTEND}, + Common::EN_ANY, + Common::kPlatformWindows, + ADGF_NO_FLAGS, + Common::GUIO_NONE + }, + GType_ComposerV2 + }, + { { "darby", -- cgit v1.2.3 From f81010887fe498e2b8275aa5a82dd84547f0a264 Mon Sep 17 00:00:00 2001 From: Alyssa Milburn Date: Fri, 15 Jul 2011 21:10:10 +0200 Subject: COMPOSER: Stub remaining script function calls. --- engines/composer/composer.cpp | 113 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 103 insertions(+), 10 deletions(-) diff --git a/engines/composer/composer.cpp b/engines/composer/composer.cpp index 961665bafc..9e059a4a53 100644 --- a/engines/composer/composer.cpp +++ b/engines/composer/composer.cpp @@ -104,13 +104,33 @@ enum { enum { kFuncPlayAnim = 35001, kFuncStopAnim = 35002, + // (no 35003) kFuncQueueScript = 35004, kFuncDequeueScript = 35005, - kFuncHideMouse = 35009, + kFuncSetCursor = 35006, + kFuncGetCursor = 35007, + kFuncShowCursor = 35008, + kFuncHideCursor = 35009, + // (no 35010) + kFuncActivateButton = 35011, + kFuncDeactivateButton = 35012, + kFuncNewPage = 35013, kFuncLoadPage = 35014, kFuncUnloadPage = 35015, kFuncSetPalette = 35016, - kFuncQueueScriptOnce = 35019 + kFuncSaveVars = 35017, + kFuncLoadVars = 35018, + kFuncQueueScriptOnce = 35019, + kFuncGetMousePos = 35020, + kFuncChangeBackground = 35021, + kFuncSetBackgroundColor = 35022, + kFuncClearSprites = 35023, + kFuncAddSprite = 35024, + kFuncRemoveSprite = 35025, + kFuncQuit = 35026, + kFuncSaveData = 35027, + kFuncLoadData = 35028, + kFuncGetSpriteSize = 35029 }; // TODO: params: x, y, event param for done @@ -154,7 +174,7 @@ Pipe::Pipe(Common::SeekableReadStream *stream) { } void Pipe::nextFrame() { - if (_offset == _stream->size()) + if (_offset == (uint)_stream->size()) return; _stream->seek(_offset, SEEK_SET); @@ -839,11 +859,11 @@ int16 ComposerEngine::scriptFuncCall(uint16 id, int16 param1, int16 param2, int1 case kFuncPlayAnim: debug(3, "kFuncPlayAnim(%d, %d, %d)", param1, param2, param3); playAnimation(param1, param2, param3, 0); - break; + return 1; // TODO: return 0 on failure case kFuncStopAnim: // TODO warning("ignoring kFuncStopAnim(%d)", param1); - break; + return 0; case kFuncQueueScript: debug(3, "kFuncQueueScript(%d, %d, %d)", param1, param2, param3); _queuedScripts[param1]._baseTime = _system->getMillis(); @@ -856,12 +876,36 @@ int16 ComposerEngine::scriptFuncCall(uint16 id, int16 param1, int16 param2, int1 _queuedScripts[param1]._count = 0; _queuedScripts[param1]._scriptId = 0; return 0; - case kFuncHideMouse: + case kFuncSetCursor: + warning("ignoring kSetCursor(%d, %d, %d)", param1, param2, param3); + // TODO: return old cursor + return 0; + case kFuncGetCursor: + warning("ignoring kFuncGetCursor()"); + // TODO: return cursor + return 0; + case kFuncShowCursor: // TODO - warning("ignoring kFuncHideMouse(%d)", param1); + warning("ignoring kFuncShowCursor(%d)", param1); return 0; - break; + case kFuncHideCursor: + // TODO + warning("ignoring kFuncHideCursor(%d)", param1); + return 0; + case kFuncActivateButton: + // TODO + warning("ignoring kFuncActivateButton(%d)", param1); + return 1; + case kFuncDeactivateButton: + // TODO + warning("ignoring kFuncDeactivateButton(%d)", param1); + return 1; + case kFuncNewPage: + // TODO + warning("ignoring kFuncNewPage(%d, %d)", param1, param2); + return 1; case kFuncLoadPage: + // TODO debug(3, "kFuncLoadPage(%d)", param1); loadLibrary(param1); return 1; @@ -870,11 +914,19 @@ int16 ComposerEngine::scriptFuncCall(uint16 id, int16 param1, int16 param2, int1 warning("ignoring kFuncUnloadPage(%d)", param1); return 1; case kFuncSetPalette: - // TODO: incomplete? + // TODO: return 0 if not disabling (0) and doesn't exist debug(4, "kFuncSetPalette(%d, %d)", param1, param2); loadCTBL(param1, param2); + // TODO: incomplete? + return 1; + case kFuncSaveVars: + // TODO + warning("ignoring kFuncSaveVars(%d)", param1); + return 1; + case kFuncLoadVars: + // TODO + warning("ignoring kFuncLoadVars(%d, %d, %d)", param1, param2, param3); return 1; - break; case kFuncQueueScriptOnce: debug(3, "kFuncQueueScriptOnce(%d, %d, %d)", param1, param2, param3); _queuedScripts[param1]._baseTime = _system->getMillis(); @@ -882,6 +934,47 @@ int16 ComposerEngine::scriptFuncCall(uint16 id, int16 param1, int16 param2, int1 _queuedScripts[param1]._count = 1; _queuedScripts[param1]._scriptId = param3; return 0; + case kFuncGetMousePos: + // TODO + warning("ignoring kFuncGetMousePos(%d, %d)", param1, param2); + return 0; + case kFuncChangeBackground: + // TODO + warning("ignoring kFuncChangeBackground(%d)", param1); + // TODO: return 1 if background existed, else 0 + return 0; + case kFuncSetBackgroundColor: + // TODO + warning("ignoring kFuncSetBackgroundColor(%d)", param1); + return 0; + case kFuncClearSprites: + // TODO + warning("ignoring kFuncClearSprites()"); + return 0; + case kFuncAddSprite: + // TODO + warning("ignoring kFuncAddSprite(%d, %d, %d)", param1, param2, param3); + return 0; + case kFuncRemoveSprite: + // TODO + warning("ignoring kFuncRemoveSprite(%d, %d)", param1, param2); + return 0; + case kFuncQuit: + // TODO + warning("ignoring kFuncQuit()"); + return 0; + case kFuncSaveData: + // TODO + warning("ignoring kFuncSaveData(%d, %d, %d)", param1, param2, param3); + return 1; + case kFuncLoadData: + // TODO + warning("ignoring kFuncLoadData(%d, %d, %d)", param1, param2, param3); + return 1; + case kFuncGetSpriteSize: + // TODO + warning("ignoring kFuncGetSpriteSize(%d, %d, %d)", param1, param2, param3); + return 0; default: error("unknown scriptFuncCall %d(%d, %d, %d)", (uint32)id, param1, param2, param3); } -- cgit v1.2.3 From f396e77cacdea88214baada40ce464b2baabdfa1 Mon Sep 17 00:00:00 2001 From: Alyssa Milburn Date: Fri, 15 Jul 2011 23:43:13 +0200 Subject: COMPOSER: Fix removeSprite behaviour. --- engines/composer/composer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engines/composer/composer.cpp b/engines/composer/composer.cpp index 9e059a4a53..9b907765d0 100644 --- a/engines/composer/composer.cpp +++ b/engines/composer/composer.cpp @@ -421,7 +421,7 @@ void ComposerEngine::processAnimFrame() { case 4: if (entry.word10 && (!data || data != entry.word10)) { debug(4, "anim: erase sprite %d", entry.word10); - removeSprite(data, anim->_id); + removeSprite(entry.word10, anim->_id); } if (data) { uint16 x = anim->_stream->readUint16LE(); @@ -471,7 +471,7 @@ void ComposerEngine::removeSprite(uint16 id, uint16 animId) { for (Common::List::iterator i = _sprites.begin(); i != _sprites.end(); i++) { if (i->id != id) continue; - if (animId && i->animId != animId) + if (i->animId && animId && (i->animId != animId)) continue; i = _sprites.reverse_erase(i); return; -- cgit v1.2.3 From 97d7767e895955673d4ad756f741e46c40e76b51 Mon Sep 17 00:00:00 2001 From: Alyssa Milburn Date: Fri, 15 Jul 2011 23:44:00 +0200 Subject: COMPOSER: Decompress sprites at load time. --- engines/composer/composer.cpp | 54 +++++++++++++++++++++++-------------------- engines/composer/composer.h | 4 +++- 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/engines/composer/composer.cpp b/engines/composer/composer.cpp index 9b907765d0..cde2e0bb0a 100644 --- a/engines/composer/composer.cpp +++ b/engines/composer/composer.cpp @@ -457,9 +457,13 @@ void ComposerEngine::addSprite(uint16 id, uint16 animId, uint16 zorder, const Co sprite.animId = animId; sprite.zorder = zorder; sprite.pos = pos; + if (!initSprite(sprite)) { + warning("ignoring addSprite on invalid sprite %d", id); + return; + } for (Common::List::iterator i = _sprites.begin(); i != _sprites.end(); i++) { - if (sprite.zorder < i->zorder) + if (sprite.zorder > i->zorder) continue; _sprites.insert(i, sprite); return; @@ -473,6 +477,7 @@ void ComposerEngine::removeSprite(uint16 id, uint16 animId) { continue; if (i->animId && animId && (i->animId != animId)) continue; + i->surface.free(); i = _sprites.reverse_erase(i); return; } @@ -547,10 +552,8 @@ Common::Error ComposerEngine::run() { if (lastDrawTime + frameTime <= thisTime) { lastDrawTime += frameTime; - if (hasResource(ID_BMAP, 1000)) - drawBMAP(1000, 0, 0); for (Common::List::iterator i = _sprites.begin(); i != _sprites.end(); i++) { - drawBMAP(i->id, i->pos.x, i->pos.y); + drawSprite(*i); } _system->copyRectToScreen((byte *)_surface.pixels, _surface.pitch, 0, 0, _surface.w, _surface.h); @@ -662,7 +665,8 @@ void ComposerEngine::loadLibrary(uint id) { delete stream;*/ // TODO: set background properly - // + addSprite(1000, 0, 0, Common::Point()); + // TODO: better CTBL logic loadCTBL(1000, 100); @@ -1105,48 +1109,48 @@ void ComposerEngine::decompressBitmap(uint16 type, Common::SeekableReadStream *s } } -void ComposerEngine::drawBMAP(uint id, uint x, uint y) { +bool ComposerEngine::initSprite(Sprite &sprite) { Common::SeekableReadStream *stream = NULL; - if (hasResource(ID_BMAP, id)) - stream = getResource(ID_BMAP, id); + if (hasResource(ID_BMAP, sprite.id)) + stream = getResource(ID_BMAP, sprite.id); else for (Common::List::iterator k = _pipes.begin(); k != _pipes.end(); k++) { Pipe *pipe = *k; - if (!pipe->hasResource(ID_BMAP, id)) + if (!pipe->hasResource(ID_BMAP, sprite.id)) continue; - stream = pipe->getResource(ID_BMAP, id, true); + stream = pipe->getResource(ID_BMAP, sprite.id, true); break; } - if (!stream) { - // FIXME - warning("couldn't find BMAP %d", id); - return; - } + if (!stream) + return false; uint16 type = stream->readUint16LE(); uint16 height = stream->readUint16LE(); uint16 width = stream->readUint16LE(); uint32 size = stream->readUint32LE(); - debug(1, "BMAP: type %d, width %d, height %d, size %d", type, width, height, size); + debug(1, "loading BMAP: type %d, width %d, height %d, size %d", type, width, height, size); + + sprite.surface.create(width, height, Graphics::PixelFormat::createFormatCLUT8()); + decompressBitmap(type, stream, (byte *)sprite.surface.pixels, size, width, height); + delete stream; - byte *buffer = new byte[width * height]; - decompressBitmap(type, stream, buffer, size, width, height); + return true; +} - debug(1, "draw at %d,%d", x, y); +void ComposerEngine::drawSprite(const Sprite &sprite) { + int x = sprite.pos.x; + int y = sprite.pos.y; // incoming data is BMP-style (bottom-up), so flip it byte *pixels = (byte *)_surface.pixels; - for (uint j = 0; j < height; j++) { - byte *in = buffer + (height - j - 1) * width; + for (uint j = 0; j < sprite.surface.h; j++) { + byte *in = (byte *)sprite.surface.pixels + (sprite.surface.h - j - 1) * sprite.surface.w; byte *out = pixels + ((j + y) * _surface.w) + x; - for (uint i = 0; i < width; i++) + for (uint i = 0; i < sprite.surface.w; i++) if (in[i]) out[i] = in[i]; } - - delete[] buffer; - delete stream; } } // End of namespace Composer diff --git a/engines/composer/composer.h b/engines/composer/composer.h index b18b3575f2..4955a0ce3a 100644 --- a/engines/composer/composer.h +++ b/engines/composer/composer.h @@ -64,6 +64,7 @@ struct Sprite { uint16 animId; uint16 zorder; Common::Point pos; + Graphics::Surface surface; }; struct AnimationEntry { @@ -199,7 +200,8 @@ private: void loadCTBL(uint id, uint fadePercent); void decompressBitmap(uint16 type, Common::SeekableReadStream *stream, byte *buffer, uint32 size, uint width, uint height); - void drawBMAP(uint id, uint x, uint y); + bool initSprite(Sprite &sprite); + void drawSprite(const Sprite &sprite); }; } // End of namespace Composer -- cgit v1.2.3 From acfd13633d97a121fe5581544feba692aa81b5a8 Mon Sep 17 00:00:00 2001 From: Alyssa Milburn Date: Sat, 16 Jul 2011 00:56:18 +0200 Subject: COMPOSER: Always handle multiple resource entries in pipes. --- engines/composer/composer.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/engines/composer/composer.cpp b/engines/composer/composer.cpp index cde2e0bb0a..936f1ff595 100644 --- a/engines/composer/composer.cpp +++ b/engines/composer/composer.cpp @@ -226,11 +226,16 @@ Common::SeekableReadStream *Pipe::getResource(uint32 tag, uint16 id, bool buffer const PipeResource &res = resMap[id]; - if (!buffering) { - assert(res.entries.size() == 1); - return new Common::SeekableSubReadStream(_stream, res.entries[0].offset, res.entries[0].offset + res.entries[0].size); + if (res.entries.size() == 1) { + Common::SeekableReadStream *stream = new Common::SeekableSubReadStream(_stream, + res.entries[0].offset, res.entries[0].offset + res.entries[0].size); + if (buffering) + _types[tag].erase(id); + return stream; } + // If there are multiple entries in the pipe, we have to concaternate them together. + uint32 size = 0; for (uint i = 0; i < res.entries.size(); i++) size += res.entries[i].size; @@ -242,7 +247,8 @@ Common::SeekableReadStream *Pipe::getResource(uint32 tag, uint16 id, bool buffer _stream->read(buffer + offset, res.entries[i].size); offset += res.entries[i].size; } - _types[tag].erase(id); + if (buffering) + _types[tag].erase(id); return new Common::MemoryReadStream(buffer, size, DisposeAfterUse::YES); } -- cgit v1.2.3 From 28d4aaf095e4f3979d1f6dc75b53d73f412e83a0 Mon Sep 17 00:00:00 2001 From: Alyssa Milburn Date: Sat, 16 Jul 2011 00:57:13 +0200 Subject: COMPOSER: Fix z-ordering issues. --- engines/composer/composer.cpp | 3 ++- engines/composer/composer.h | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/engines/composer/composer.cpp b/engines/composer/composer.cpp index 936f1ff595..acd1d33e65 100644 --- a/engines/composer/composer.cpp +++ b/engines/composer/composer.cpp @@ -457,7 +457,7 @@ void ComposerEngine::processAnimFrame() { } } -void ComposerEngine::addSprite(uint16 id, uint16 animId, uint16 zorder, const Common::Point &pos) { +void ComposerEngine::addSprite(uint16 id, uint16 animId, int16 zorder, const Common::Point &pos) { Sprite sprite; sprite.id = id; sprite.animId = animId; @@ -471,6 +471,7 @@ void ComposerEngine::addSprite(uint16 id, uint16 animId, uint16 zorder, const Co for (Common::List::iterator i = _sprites.begin(); i != _sprites.end(); i++) { if (sprite.zorder > i->zorder) continue; + i++; _sprites.insert(i, sprite); return; } diff --git a/engines/composer/composer.h b/engines/composer/composer.h index 4955a0ce3a..a90eb61459 100644 --- a/engines/composer/composer.h +++ b/engines/composer/composer.h @@ -62,7 +62,7 @@ class ComposerEngine; struct Sprite { uint16 id; uint16 animId; - uint16 zorder; + int16 zorder; Common::Point pos; Graphics::Surface surface; }; @@ -195,7 +195,7 @@ private: void playWaveForAnim(uint16 id, bool bufferingOnly); void processAnimFrame(); - void addSprite(uint16 id, uint16 animId, uint16 zorder, const Common::Point &pos); + void addSprite(uint16 id, uint16 animId, int16 zorder, const Common::Point &pos); void removeSprite(uint16 id, uint16 animId); void loadCTBL(uint id, uint fadePercent); -- cgit v1.2.3 From 70ae2c74e04ac84503cfe9c1a555495700d55e68 Mon Sep 17 00:00:00 2001 From: Alyssa Milburn Date: Sat, 16 Jul 2011 00:57:50 +0200 Subject: COMPOSER: Handle loading/unloading pages properly. --- engines/composer/composer.cpp | 51 ++++++++++++++++++++++++++++++++++++++----- engines/composer/composer.h | 10 +++++++++ 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/engines/composer/composer.cpp b/engines/composer/composer.cpp index acd1d33e65..6228292676 100644 --- a/engines/composer/composer.cpp +++ b/engines/composer/composer.cpp @@ -542,6 +542,14 @@ Common::Error ComposerEngine::run() { uint frameTime = 1000 / fps; uint32 lastDrawTime = 0; while (!shouldQuit()) { + for (uint i = 0; i < _pendingPageChanges.size(); i++) { + if (_pendingPageChanges[i]._remove) + unloadLibrary(_pendingPageChanges[i]._pageId); + else + loadLibrary(_pendingPageChanges[i]._pageId); + } + _pendingPageChanges.clear(); + uint32 thisTime = _system->getMillis(); for (uint i = 0; i < _queuedScripts.size(); i++) { QueuedScript &script = _queuedScripts[i]; @@ -683,6 +691,37 @@ void ComposerEngine::loadLibrary(uint id) { runEvent(3, id, 0, 0); } +void ComposerEngine::unloadLibrary(uint id) { + for (Common::List::iterator i = _libraries.begin(); i != _libraries.end(); i++) { + if (i->_id != id) + continue; + + for (Common::List::iterator j = _anims.begin(); j != _anims.end(); j++) { + delete *j; + } + _anims.clear(); + for (Common::List::iterator j = _pipes.begin(); j != _pipes.end(); j++) { + delete *j; + } + _pipes.clear(); + + for (Common::List::iterator j = _sprites.begin(); j != _sprites.end(); j++) { + j->surface.free(); + } + _sprites.clear(); + + for (uint j = 0; j < _queuedScripts.size(); j++) { + _queuedScripts[j]._count = 0; + _queuedScripts[j]._scriptId = 0; + } + + _libraries.erase(i); + return; + } + + error("tried to unload library %d, which isn't loaded", id); +} + bool ComposerEngine::hasResource(uint32 tag, uint16 id) { for (Common::List::iterator i = _libraries.begin(); i != _libraries.end(); i++) if (i->_archive->hasResource(tag, id)) @@ -912,17 +951,17 @@ int16 ComposerEngine::scriptFuncCall(uint16 id, int16 param1, int16 param2, int1 warning("ignoring kFuncDeactivateButton(%d)", param1); return 1; case kFuncNewPage: - // TODO - warning("ignoring kFuncNewPage(%d, %d)", param1, param2); + debug(3, "kFuncNewPage(%d, %d)", param1, param2); + _pendingPageChanges.push_back(PendingPageChange(param1, true)); + _pendingPageChanges.push_back(PendingPageChange(param2, false)); return 1; case kFuncLoadPage: - // TODO debug(3, "kFuncLoadPage(%d)", param1); - loadLibrary(param1); + _pendingPageChanges.push_back(PendingPageChange(param1, false)); return 1; case kFuncUnloadPage: - // TODO - warning("ignoring kFuncUnloadPage(%d)", param1); + debug(3, "ignoring kFuncUnloadPage(%d)", param1); + _pendingPageChanges.push_back(PendingPageChange(param1, true)); return 1; case kFuncSetPalette: // TODO: return 0 if not disabling (0) and doesn't exist diff --git a/engines/composer/composer.h b/engines/composer/composer.h index a90eb61459..eba51759a2 100644 --- a/engines/composer/composer.h +++ b/engines/composer/composer.h @@ -139,6 +139,14 @@ struct QueuedScript { uint16 _scriptId; }; +struct PendingPageChange { + PendingPageChange() { } + PendingPageChange(uint16 id, bool remove) : _pageId(id), _remove(remove) { } + + uint16 _pageId; + bool _remove; +}; + class ComposerEngine : public Engine { protected: Common::Error run(); @@ -168,6 +176,7 @@ private: uint _directoriesToStrip; Common::ConfigFile _bookIni; Common::List _libraries; + Common::Array _pendingPageChanges; Common::Array _stack; Common::Array _vars; @@ -179,6 +188,7 @@ private: Common::String getStringFromConfig(const Common::String §ion, const Common::String &key); Common::String getFilename(const Common::String §ion, uint id); void loadLibrary(uint id); + void unloadLibrary(uint id); bool hasResource(uint32 tag, uint16 id); Common::SeekableReadStream *getResource(uint32 tag, uint16 id); -- cgit v1.2.3 From 9e127a10327ced6e7172efbb237672b07df4a0a0 Mon Sep 17 00:00:00 2001 From: Alyssa Milburn Date: Sat, 16 Jul 2011 00:59:00 +0200 Subject: COMPOSER: Don't die on missing/bad anims. --- engines/composer/composer.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/engines/composer/composer.cpp b/engines/composer/composer.cpp index 6228292676..cafb5e7bd9 100644 --- a/engines/composer/composer.cpp +++ b/engines/composer/composer.cpp @@ -296,6 +296,10 @@ void ComposerEngine::playAnimation(uint16 animId, int16 x, int16 y, int16 eventP // If we didn't find it, try the libraries. if (!stream) { + if (!hasResource(ID_ANIM, animId)) { + warning("ignoring attempt to play invalid anim %d", animId); + return; + } stream = getResource(ID_ANIM, animId); uint32 type = 0; @@ -350,6 +354,12 @@ void ComposerEngine::processAnimFrame() { for (Common::List::iterator i = _anims.begin(); i != _anims.end(); i++) { Animation *anim = *i; + anim->seekToCurrPos(); + if (anim->_stream->pos() == anim->_stream->size()) { + warning("anim with id %d ended too soon", anim->_id); + anim->_state = 0; + } + if (anim->_state <= 1) { if (anim->_state == 1) { runEvent(2, anim->_id, anim->_eventParam, 0); @@ -363,8 +373,6 @@ void ComposerEngine::processAnimFrame() { continue; } - anim->seekToCurrPos(); - for (uint j = 0; j < anim->_entries.size(); j++) { AnimationEntry &entry = anim->_entries[j]; if (entry.op != 1) @@ -679,8 +687,9 @@ void ComposerEngine::loadLibrary(uint id) { Common::hexdump(buf, stream->size()); delete stream;*/ - // TODO: set background properly - addSprite(1000, 0, 0, Common::Point()); + // add background sprite, if it exists + if (hasResource(ID_BMAP, 1000)) + addSprite(1000, 0, -1, Common::Point()); // TODO: better CTBL logic loadCTBL(1000, 100); -- cgit v1.2.3 From ec77c2f6fdbb1344d428244d5cacbac7dd48d83a Mon Sep 17 00:00:00 2001 From: Alyssa Milburn Date: Sat, 16 Jul 2011 01:04:45 +0200 Subject: COMPOSER: Don't complain about on-time animation ending. --- engines/composer/composer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engines/composer/composer.cpp b/engines/composer/composer.cpp index cafb5e7bd9..34e383293c 100644 --- a/engines/composer/composer.cpp +++ b/engines/composer/composer.cpp @@ -355,7 +355,7 @@ void ComposerEngine::processAnimFrame() { Animation *anim = *i; anim->seekToCurrPos(); - if (anim->_stream->pos() == anim->_stream->size()) { + if ((anim->_state > 1) && (anim->_stream->pos() == anim->_stream->size())) { warning("anim with id %d ended too soon", anim->_id); anim->_state = 0; } -- cgit v1.2.3 From 54f26b6c452b4b09e0af76d67498899ff8bc28f6 Mon Sep 17 00:00:00 2001 From: Alyssa Milburn Date: Sat, 16 Jul 2011 16:03:28 +0200 Subject: COMPOSER: Implement the rest of the script opcodes. --- engines/composer/composer.cpp | 257 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 249 insertions(+), 8 deletions(-) diff --git a/engines/composer/composer.cpp b/engines/composer/composer.cpp index 34e383293c..ec4d45a71b 100644 --- a/engines/composer/composer.cpp +++ b/engines/composer/composer.cpp @@ -833,68 +833,298 @@ void ComposerEngine::runScript(uint16 id) { uint arg3 = (script[pos] & 0xC000) >> 14; // 2 bits switch (op) { case kOpPlusPlus: + if (numParams != 1) + error("kOpPlusPlus had wrong number of params (%d)", numParams); val1 = getArg(script[pos + 1], arg1); debug(9, "[%d/%d]++ (now %d)", script[pos + 1], arg1, val1 + 1); setArg(script[pos + 1], arg1, val1 + 1); break; + case kOpMinusMinus: + if (numParams != 1) + error("kOpMinusMinus had wrong number of params (%d)", numParams); + val1 = getArg(script[pos + 1], arg1); + debug(9, "[%d/%d]-- (now %d)", script[pos + 1], arg1, val1 - 1); + setArg(script[pos + 1], arg1, val1 - 1); + break; + case kOpAssign: + if (numParams != 2) + error("kOpAssign had wrong number of params (%d)", numParams); + val2 = getArg(script[pos + 2], arg2); + debug(9, "[%d/%d] = [%d/%d] (%d)", script[pos + 1], arg1, script[pos + 2], arg2, val2); + setArg(script[pos + 1], arg1, val2); + break; case kOpAdd: + if (numParams != 3) + error("kOpAdd had wrong number of params (%d)", numParams); val2 = getArg(script[pos + 2], arg2); val3 = getArg(script[pos + 3], arg3); debug(9, "[%d/%d] = [%d/%d]=%d + [%d/%d]=%d (%d)", script[pos + 1], arg1, script[pos + 2], arg2, val2, script[pos+3], arg3, val3, val2 + val3); setArg(script[pos + 1], arg1, val2 + val3); break; - case kOpAssign: + case kOpSubtract: + if (numParams != 3) + error("kOpSubtract had wrong number of params (%d)", numParams); + val2 = getArg(script[pos + 2], arg2); + val3 = getArg(script[pos + 3], arg3); + debug(9, "[%d/%d] = [%d/%d]=%d - [%d/%d]=%d (%d)", script[pos + 1], arg1, script[pos + 2], arg2, val2, script[pos+3], arg3, val3, val2 - val3); + setArg(script[pos + 1], arg1, val2 - val3); + break; + case kOpMultiply: + if (numParams != 3) + error("kOpMultiply had wrong number of params (%d)", numParams); + val2 = getArg(script[pos + 2], arg2); + val3 = getArg(script[pos + 3], arg3); + debug(9, "[%d/%d] = [%d/%d]=%d * [%d/%d]=%d (%d)", script[pos + 1], arg1, script[pos + 2], arg2, val2, script[pos+3], arg3, val3, val2 * val3); + setArg(script[pos + 1], arg1, val2 * val3); + break; + case kOpDivide: + if (numParams != 3) + error("kOpDivide had wrong number of params (%d)", numParams); + val2 = getArg(script[pos + 2], arg2); + val3 = getArg(script[pos + 3], arg3); + if (val3 == 0) + error("script tried to divide by zero"); + debug(9, "[%d/%d] = [%d/%d]=%d / [%d/%d]=%d (%d)", script[pos + 1], arg1, script[pos + 2], arg2, val2, script[pos+3], arg3, val3, val2 / val3); + setArg(script[pos + 1], arg1, val2 / val3); + break; + case kOpModulo: + if (numParams != 3) + error("kOpModulo had wrong number of params (%d)", numParams); val2 = getArg(script[pos + 2], arg2); + val3 = getArg(script[pos + 3], arg3); + if (val3 == 0) + error("script tried to divide by zero (modulo)"); + debug(9, "[%d/%d] = [%d/%d]=%d %% [%d/%d]=%d (%d)", script[pos + 1], arg1, script[pos + 2], arg2, val2, script[pos+3], arg3, val3, val2 % val3); + setArg(script[pos + 1], arg1, val2 % val3); + break; + case kOpMaybeAlsoAssign: if (numParams != 2) - error("kOpAssign had wrong number of params (%d)", numParams); - debug(9, "[%d/%d] = [%d/%d] (%d)", script[pos + 1], arg1, script[pos + 2], arg2, val2); + error("kOpMaybeAlsoAssign had wrong number of params (%d)", numParams); + val2 = getArg(script[pos + 2], arg2); + debug(9, "[%d/%d] =(?) [%d/%d] (%d)", script[pos + 1], arg1, script[pos + 2], arg2, val2); setArg(script[pos + 1], arg1, val2); break; - case kOpCallFunc: + case kOpBooleanAssign: + if (numParams != 2) + error("kOpBooleanAssign had wrong number of params (%d)", numParams); + val2 = getArg(script[pos + 2], arg2); + debug(9, "[%d/%d] = [%d/%d] (%d) ? 1 : 0", script[pos + 1], arg1, script[pos + 2], arg2, val2); + setArg(script[pos + 1], arg1, val2 ? 1 : 0); + break; + case kOpNegate: + if (numParams != 2) + error("kOpNegate had wrong number of params (%d)", numParams); + val2 = getArg(script[pos + 2], arg2); + debug(9, "[%d/%d] = -[%d/%d] (%d)", script[pos + 1], arg1, script[pos + 2], arg2, val2); + setArg(script[pos + 1], arg1, -val2); + break; + case kOpAnd: + if (numParams != 3) + error("kOpAnd had wrong number of params (%d)", numParams); + val2 = getArg(script[pos + 2], arg2); + val3 = getArg(script[pos + 3], arg3); + debug(9, "[%d/%d] = [%d/%d]=%d & [%d/%d]=%d (%d)", script[pos + 1], arg1, script[pos + 2], arg2, val2, script[pos+3], arg3, val3, val2 & val3); + setArg(script[pos + 1], arg1, val2 & val3); + break; + case kOpOr: + if (numParams != 3) + error("kOpOr had wrong number of params (%d)", numParams); + val2 = getArg(script[pos + 2], arg2); + val3 = getArg(script[pos + 3], arg3); + debug(9, "[%d/%d] = [%d/%d]=%d | [%d/%d]=%d (%d)", script[pos + 1], arg1, script[pos + 2], arg2, val2, script[pos+3], arg3, val3, val2 | val3); + setArg(script[pos + 1], arg1, val2 | val3); + break; + case kOpXor: + if (numParams != 3) + error("kOpXor had wrong number of params (%d)", numParams); + val2 = getArg(script[pos + 2], arg2); + val3 = getArg(script[pos + 3], arg3); + debug(9, "[%d/%d] = [%d/%d]=%d ^ [%d/%d]=%d (%d)", script[pos + 1], arg1, script[pos + 2], arg2, val2, script[pos+3], arg3, val3, val2 ^ val3); + setArg(script[pos + 1], arg1, val2 ^ val3); + break; + case kOpNotPositive: + if (numParams != 2) + error("kOpNotPositive had wrong number of params (%d)", numParams); + val2 = getArg(script[pos + 2], arg2); + debug(9, "[%d/%d] = [%d/%d] (%d) < 1", script[pos + 1], arg1, script[pos + 2], arg2, val2); + setArg(script[pos + 1], arg1, (val2 < 1) ? 1 : 0); + break; + case kOpSqrt: + if (numParams != 2) + error("kOpSqrt had wrong number of params (%d)", numParams); + val2 = getArg(script[pos + 2], arg2); + debug(9, "[%d/%d] = sqrt([%d/%d] (%d))", script[pos + 1], arg1, script[pos + 2], arg2, val2); + setArg(script[pos + 1], arg1, (int16)sqrt((double)val2)); + break; + case kOpRandom: + if (numParams != 3) + error("kOpRandom had wrong number of params (%d)", numParams); + val2 = getArg(script[pos + 2], arg2); + val3 = getArg(script[pos + 3], arg3); + val1 = _rnd->getRandomNumberRng(val2, val3); + debug(9, "[%d/%d] = rnd([%d/%d]=%d, [%d/%d]=%d) (%d)", script[pos + 1], arg1, script[pos + 2], arg2, val2, script[pos+3], arg3, val3, val1); + setArg(script[pos + 1], arg1, val1); + break; + case kOpExecuteScript: + if (numParams != 1) + error("kOpExecuteScript had wrong number of params (%d)", numParams); val1 = getArg(script[pos + 1], arg1); + debug(8, "run script [%d/%d]=%d", script[pos + 1], arg1, val1); + runScript(val1); + debug(8, "done run script"); + break; + case kOpCallFunc: if (numParams != 1) error("kOpCallFunc had wrong number of params (%d)", numParams); + val1 = getArg(script[pos + 1], arg1); debug(8, "%d(%d, %d, %d)", (uint16)val1, _vars[1], _vars[2], _vars[3]); _vars[0] = scriptFuncCall(val1, _vars[1], _vars[2], _vars[3]); break; - /*case kOpBoolGreaterThan: - lastResult = (getArg(script[pos + 1], arg1) > getArg(script[pos + 2], arg2)); - break;*/ + case kOpBoolLessThanEq: + if (numParams != 2) + error("kOpBoolLessThanEq had wrong number of params (%d)", numParams); + val1 = getArg(script[pos + 1], arg1); + val2 = getArg(script[pos + 2], arg2); + debug(9, "[%d/%d] <= [%d/%d]? (%d <= %d)", script[pos + 1], arg1, script[pos + 2], arg2, val1, val2); + lastResult = (val1 <= val2); + break; + case kOpBoolLessThan: + if (numParams != 2) + error("kOpBoolLessThan had wrong number of params (%d)", numParams); + val1 = getArg(script[pos + 1], arg1); + val2 = getArg(script[pos + 2], arg2); + debug(9, "[%d/%d] < [%d/%d]? (%d < %d)", script[pos + 1], arg1, script[pos + 2], arg2, val1, val2); + lastResult = (val1 < val2); + break; + case kOpBoolGreaterThanEq: + if (numParams != 2) + error("kOpBoolGreaterThanEq had wrong number of params (%d)", numParams); + val1 = getArg(script[pos + 1], arg1); + val2 = getArg(script[pos + 2], arg2); + debug(9, "[%d/%d] >= [%d/%d]? (%d >= %d)", script[pos + 1], arg1, script[pos + 2], arg2, val1, val2); + lastResult = (val1 >= val2); + break; + case kOpBoolGreaterThan: + if (numParams != 2) + error("kOpBoolGreaterThan had wrong number of params (%d)", numParams); + val1 = getArg(script[pos + 1], arg1); + val2 = getArg(script[pos + 2], arg2); + debug(9, "[%d/%d] > [%d/%d]? (%d > %d)", script[pos + 1], arg1, script[pos + 2], arg2, val1, val2); + lastResult = (val1 > val2); + break; case kOpBoolEqual: + if (numParams != 2) + error("kOpBoolEqual had wrong number of params (%d)", numParams); val1 = getArg(script[pos + 1], arg1); val2 = getArg(script[pos + 2], arg2); - debug(9, "[%d/%d] == [%d/%d]? (%d > %d)", script[pos + 1], arg1, script[pos + 2], arg2, val1, val2); + debug(9, "[%d/%d] == [%d/%d]? (%d == %d)", script[pos + 1], arg1, script[pos + 2], arg2, val1, val2); lastResult = (val1 == val2); break; + case kOpBoolNotEqual: + if (numParams != 2) + error("kOpBoolNotEqual had wrong number of params (%d)", numParams); + val1 = getArg(script[pos + 1], arg1); + val2 = getArg(script[pos + 2], arg2); + debug(9, "[%d/%d] != [%d/%d]? (%d != %d)", script[pos + 1], arg1, script[pos + 2], arg2, val1, val2); + lastResult = (val1 != val2); + break; case kOpSaveArgs: + if (numParams != 0) + error("kOpSaveArgs had wrong number of params (%d)", numParams); debug(9, "save args"); for (uint i = 1; i < 19; i++) _stack[stackBase + i] = _vars[i]; break; case kOpRestoreArgs: + if (numParams != 0) + error("kOpRestoreArgs had wrong number of params (%d)", numParams); debug(9, "restore args"); for (uint i = 1; i < 19; i++) _vars[i] = _stack[stackBase + i]; break; + case kOpSetReturnValue: + if (numParams != 1) + error("kOpSetReturnValue had wrong number of params (%d)", numParams); + val1 = getArg(script[pos + 1], arg1); + debug(9, "return [%d/%d]=%d", script[pos + 1], arg1, val1); + _vars[0] = val1; + break; + case kOpLessThanEq: + if (numParams != 3) + error("kOpLessThanEq had wrong number of params (%d)", numParams); + val2 = getArg(script[pos + 2], arg2); + val3 = getArg(script[pos + 3], arg3); + debug(9, "[%d/%d] = [%d/%d] <= [%d/%d]? (%d <= %d)", script[pos + 1], arg1, script[pos + 2], arg2, script[pos + 3], arg3, val2, val3); + setArg(script[pos + 1], arg1, (val3 <= val2) ? 1 : 0); + break; + case kOpLessThan: + if (numParams != 3) + error("kOpLessThan had wrong number of params (%d)", numParams); + val2 = getArg(script[pos + 2], arg2); + val3 = getArg(script[pos + 3], arg3); + debug(9, "[%d/%d] = [%d/%d] < [%d/%d]? (%d < %d)", script[pos + 1], arg1, script[pos + 2], arg2, script[pos + 3], arg3, val2, val3); + setArg(script[pos + 1], arg1, (val3 < val2) ? 1 : 0); + break; + case kOpGreaterThanEq: + if (numParams != 3) + error("kOpGreaterThanEq had wrong number of params (%d)", numParams); + val2 = getArg(script[pos + 2], arg2); + val3 = getArg(script[pos + 3], arg3); + debug(9, "[%d/%d] = [%d/%d] >= [%d/%d]? (%d >= %d)", script[pos + 1], arg1, script[pos + 2], arg2, script[pos + 3], arg3, val2, val3); + setArg(script[pos + 1], arg1, (val3 >= val2) ? 1 : 0); + break; case kOpGreaterThan: + if (numParams != 3) + error("kOpGreaterThan had wrong number of params (%d)", numParams); val2 = getArg(script[pos + 2], arg2); val3 = getArg(script[pos + 3], arg3); debug(9, "[%d/%d] = [%d/%d] > [%d/%d]? (%d > %d)", script[pos + 1], arg1, script[pos + 2], arg2, script[pos + 3], arg3, val2, val3); setArg(script[pos + 1], arg1, (val3 > val2) ? 1 : 0); break; + case kOpEqual: + if (numParams != 3) + error("kOpEqual had wrong number of params (%d)", numParams); + val2 = getArg(script[pos + 2], arg2); + val3 = getArg(script[pos + 3], arg3); + debug(9, "[%d/%d] = [%d/%d] == [%d/%d]? (%d == %d)", script[pos + 1], arg1, script[pos + 2], arg2, script[pos + 3], arg3, val2, val3); + setArg(script[pos + 1], arg1, (val3 == val2) ? 1 : 0); + break; + case kOpNotEqual: + if (numParams != 3) + error("kOpNotEqual had wrong number of params (%d)", numParams); + val2 = getArg(script[pos + 2], arg2); + val3 = getArg(script[pos + 3], arg3); + debug(9, "[%d/%d] = [%d/%d] != [%d/%d]? (%d != %d)", script[pos + 1], arg1, script[pos + 2], arg2, script[pos + 3], arg3, val2, val3); + setArg(script[pos + 1], arg1, (val3 != val2) ? 1 : 0); + break; case kOpJump: + if (numParams != 1) + error("kOpJump had wrong number of params (%d)", numParams); val1 = getArg(script[pos + 1], arg1); debug(9, "jump by [%d/%d]=%d", script[pos + 1], arg1, val1); pos += val1; break; case kOpJumpIfNot: + if (numParams != 1) + error("kOpJumpIfNot had wrong number of params (%d)", numParams); if (lastResult) break; val1 = getArg(script[pos + 1], arg1); debug(9, "jump if not, by [%d/%d]=%d", script[pos + 1], arg1, val1); pos += val1; break; + case kOpJumpIf: + if (numParams != 1) + error("kOpJumpIf had wrong number of params (%d)", numParams); + if (!lastResult) + break; + val1 = getArg(script[pos + 1], arg1); + debug(9, "jump if, by [%d/%d]=%d", script[pos + 1], arg1, val1); + pos += val1; + break; case kOpJumpIfNotValue: + if (numParams != 2) + error("kOpJumpIfNotValue had wrong number of params (%d)", numParams); val1 = getArg(script[pos + 1], arg1); val2 = getArg(script[pos + 2], arg2); debug(9, "jump if not [%d/%d]=%d", script[pos + 1], arg1, val1); @@ -903,6 +1133,17 @@ void ComposerEngine::runScript(uint16 id) { debug(9, "--> jump by [%d/%d]=%d", script[pos + 2], arg2, val2); pos += val2; break; + case kOpJumpIfValue: + if (numParams != 2) + error("kOpJumpIfValue had wrong number of params (%d)", numParams); + val1 = getArg(script[pos + 1], arg1); + val2 = getArg(script[pos + 2], arg2); + debug(9, "jump if [%d/%d]=%d", script[pos + 1], arg1, val1); + if (!val1) + break; + debug(9, "--> jump by [%d/%d]=%d", script[pos + 2], arg2, val2); + pos += val2; + break; default: error("unknown script op 0x%02x", op); } -- cgit v1.2.3 From aa52698d7ee5488e37f8c13c8a212259614b453e Mon Sep 17 00:00:00 2001 From: Alyssa Milburn Date: Sat, 16 Jul 2011 20:02:19 +0200 Subject: COMPOSER: Fix z-ordering (properly, this time). --- engines/composer/composer.cpp | 8 ++++---- engines/composer/composer.h | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/engines/composer/composer.cpp b/engines/composer/composer.cpp index ec4d45a71b..4edf513a35 100644 --- a/engines/composer/composer.cpp +++ b/engines/composer/composer.cpp @@ -465,7 +465,7 @@ void ComposerEngine::processAnimFrame() { } } -void ComposerEngine::addSprite(uint16 id, uint16 animId, int16 zorder, const Common::Point &pos) { +void ComposerEngine::addSprite(uint16 id, uint16 animId, uint16 zorder, const Common::Point &pos) { Sprite sprite; sprite.id = id; sprite.animId = animId; @@ -477,9 +477,9 @@ void ComposerEngine::addSprite(uint16 id, uint16 animId, int16 zorder, const Com } for (Common::List::iterator i = _sprites.begin(); i != _sprites.end(); i++) { - if (sprite.zorder > i->zorder) + if (sprite.zorder <= i->zorder) continue; - i++; + // insert *before* this sprite _sprites.insert(i, sprite); return; } @@ -689,7 +689,7 @@ void ComposerEngine::loadLibrary(uint id) { // add background sprite, if it exists if (hasResource(ID_BMAP, 1000)) - addSprite(1000, 0, -1, Common::Point()); + addSprite(1000, 0, 0xffff, Common::Point()); // TODO: better CTBL logic loadCTBL(1000, 100); diff --git a/engines/composer/composer.h b/engines/composer/composer.h index eba51759a2..3fce79e362 100644 --- a/engines/composer/composer.h +++ b/engines/composer/composer.h @@ -62,7 +62,7 @@ class ComposerEngine; struct Sprite { uint16 id; uint16 animId; - int16 zorder; + uint16 zorder; Common::Point pos; Graphics::Surface surface; }; @@ -205,7 +205,7 @@ private: void playWaveForAnim(uint16 id, bool bufferingOnly); void processAnimFrame(); - void addSprite(uint16 id, uint16 animId, int16 zorder, const Common::Point &pos); + void addSprite(uint16 id, uint16 animId, uint16 zorder, const Common::Point &pos); void removeSprite(uint16 id, uint16 animId); void loadCTBL(uint id, uint fadePercent); -- cgit v1.2.3 From 821023678f55d2ff02985a954a23946982a5e6d1 Mon Sep 17 00:00:00 2001 From: Alyssa Milburn Date: Sat, 16 Jul 2011 20:03:23 +0200 Subject: COMPOSER: Improve stopping anims. --- engines/composer/composer.cpp | 86 ++++++++++++++++++++++++++++--------------- engines/composer/composer.h | 1 + 2 files changed, 58 insertions(+), 29 deletions(-) diff --git a/engines/composer/composer.cpp b/engines/composer/composer.cpp index 4edf513a35..d2da61f5d6 100644 --- a/engines/composer/composer.cpp +++ b/engines/composer/composer.cpp @@ -260,25 +260,7 @@ void ComposerEngine::playAnimation(uint16 animId, int16 x, int16 y, int16 eventP if (anim->_id != animId) continue; - // disable the animation - anim->_state = 0; - - // stop any animations it may have spawned - for (uint j = 0; j < anim->_entries.size(); j++) { - if (anim->_entries[j].op == 3) - ; // TODO: stop anim - } - - // kill any pipes owned by the animation - for (Common::List::iterator j = _pipes.begin(); j != _pipes.end(); j++) { - Pipe *pipe = *j; - if (pipe->_anim != anim) - continue; - j = _pipes.reverse_erase(j); - delete pipe; - break; - } - + stopAnimation(*i); break; } @@ -325,6 +307,43 @@ void ComposerEngine::playAnimation(uint16 animId, int16 x, int16 y, int16 eventP runEvent(1, animId, eventParam, 0); } +void ComposerEngine::stopAnimation(Animation *anim, bool localOnly, bool pipesOnly) { + // disable the animation + anim->_state = 0; + + // stop any animations it may have spawned + for (uint j = 0; j < anim->_entries.size(); j++) { + AnimationEntry &entry = anim->_entries[j]; + if (!entry.word10) + continue; + if (localOnly) { + if (pipesOnly) + continue; + // TODO: stop audio if needed + if (entry.op != 4) + continue; + removeSprite(entry.word10, anim->_id); + } else { + if (entry.op != 3) + continue; + for (Common::List::iterator i = _anims.begin(); i != _anims.end(); i++) { + if ((*i)->_id == entry.word10) + stopAnimation(*i); + } + } + } + + // kill any pipe owned by the animation + for (Common::List::iterator j = _pipes.begin(); j != _pipes.end(); j++) { + Pipe *pipe = *j; + if (pipe->_anim != anim) + continue; + j = _pipes.reverse_erase(j); + delete pipe; + break; + } +} + void ComposerEngine::playWaveForAnim(uint16 id, bool bufferingOnly) { Common::SeekableReadStream *stream = NULL; if (!bufferingOnly && hasResource(ID_WAVE, id)) { @@ -355,18 +374,13 @@ void ComposerEngine::processAnimFrame() { Animation *anim = *i; anim->seekToCurrPos(); - if ((anim->_state > 1) && (anim->_stream->pos() == anim->_stream->size())) { - warning("anim with id %d ended too soon", anim->_id); - anim->_state = 0; - } if (anim->_state <= 1) { - if (anim->_state == 1) { + bool normalEnd = (anim->_state == 1); + if (normalEnd) { runEvent(2, anim->_id, anim->_eventParam, 0); - } else { - // TODO: stop anything which was running } - // TODO: kill pipes + stopAnimation(anim, true, normalEnd); delete anim; i = _anims.reverse_erase(i); @@ -380,6 +394,12 @@ void ComposerEngine::processAnimFrame() { if (entry.counter) { entry.counter--; } else { + if ((anim->_state > 1) && (anim->_stream->pos() == anim->_stream->size())) { + warning("anim with id %d ended too soon", anim->_id); + anim->_state = 0; + break; + } + uint16 event = anim->_stream->readUint16LE(); anim->_offset += 2; if (event == 0xffff) { @@ -412,6 +432,11 @@ void ComposerEngine::processAnimFrame() { } } else { anim->seekToCurrPos(); + if ((anim->_state > 1) && (anim->_stream->pos() == anim->_stream->size())) { + warning("anim with id %d ended too soon", anim->_id); + anim->_state = 0; + break; + } uint16 data = anim->_stream->readUint16LE(); anim->_offset += 2; @@ -1161,8 +1186,11 @@ int16 ComposerEngine::scriptFuncCall(uint16 id, int16 param1, int16 param2, int1 playAnimation(param1, param2, param3, 0); return 1; // TODO: return 0 on failure case kFuncStopAnim: - // TODO - warning("ignoring kFuncStopAnim(%d)", param1); + debug(3, "ignoring kFuncStopAnim(%d)", param1); + for (Common::List::iterator i = _anims.begin(); i != _anims.end(); i++) { + if ((*i)->_id == param1) + stopAnimation(*i); + } return 0; case kFuncQueueScript: debug(3, "kFuncQueueScript(%d, %d, %d)", param1, param2, param3); diff --git a/engines/composer/composer.h b/engines/composer/composer.h index 3fce79e362..8f22174592 100644 --- a/engines/composer/composer.h +++ b/engines/composer/composer.h @@ -202,6 +202,7 @@ private: int16 scriptFuncCall(uint16 id, int16 param1, int16 param2, int16 param3); void playAnimation(uint16 animId, int16 param1, int16 param2, int16 param3); + void stopAnimation(Animation *anim, bool localOnly = false, bool pipesOnly = false); void playWaveForAnim(uint16 id, bool bufferingOnly); void processAnimFrame(); -- cgit v1.2.3 From c95320bb170a01ad4193246213d5c4a01d3b588d Mon Sep 17 00:00:00 2001 From: Alyssa Milburn Date: Sat, 16 Jul 2011 20:04:23 +0200 Subject: COMPOSER: Clear audio on new page. --- engines/composer/composer.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/engines/composer/composer.cpp b/engines/composer/composer.cpp index d2da61f5d6..60b31df77b 100644 --- a/engines/composer/composer.cpp +++ b/engines/composer/composer.cpp @@ -363,6 +363,8 @@ void ComposerEngine::playWaveForAnim(uint16 id, bool bufferingOnly) { // FIXME: deal with word6 (priority) byte *buffer = (byte *)malloc(stream->size()); stream->read(buffer, stream->size()); + if (!_audioStream) + _audioStream = Audio::makeQueuingAudioStream(22050, false); _audioStream->queueBuffer(buffer, stream->size(), DisposeAfterUse::YES, Audio::FLAG_UNSIGNED); delete stream; if (!_mixer->isSoundHandleActive(_soundHandle)) @@ -534,7 +536,6 @@ ComposerEngine::~ComposerEngine() { for (Common::List::iterator i = _libraries.begin(); i != _libraries.end(); i++) delete i->_archive; - delete _audioStream; delete _rnd; } @@ -567,8 +568,6 @@ Common::Error ComposerEngine::run() { initGraphics(width, height, true); _surface.create(width, height, Graphics::PixelFormat::createFormatCLUT8()); - _audioStream = Audio::makeQueuingAudioStream(22050, false); - loadLibrary(0); uint fps = atoi(getStringFromConfig("Common", "FPS").c_str()); @@ -744,6 +743,9 @@ void ComposerEngine::unloadLibrary(uint id) { } _sprites.clear(); + _mixer->stopAll(); + _audioStream = NULL; + for (uint j = 0; j < _queuedScripts.size(); j++) { _queuedScripts[j]._count = 0; _queuedScripts[j]._scriptId = 0; -- cgit v1.2.3 From 1cf09e3fbf5e64183e82b8845f083ed05bc3149d Mon Sep 17 00:00:00 2001 From: Alyssa Milburn Date: Sat, 16 Jul 2011 20:04:49 +0200 Subject: COMPOSER: Misc sprite changes. --- engines/composer/composer.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/engines/composer/composer.cpp b/engines/composer/composer.cpp index 60b31df77b..cbb80d47d5 100644 --- a/engines/composer/composer.cpp +++ b/engines/composer/composer.cpp @@ -493,6 +493,9 @@ void ComposerEngine::processAnimFrame() { } void ComposerEngine::addSprite(uint16 id, uint16 animId, uint16 zorder, const Common::Point &pos) { + // TODO: re-use old sprite + removeSprite(id, animId); + Sprite sprite; sprite.id = id; sprite.animId = animId; @@ -521,7 +524,6 @@ void ComposerEngine::removeSprite(uint16 id, uint16 animId) { continue; i->surface.free(); i = _sprites.reverse_erase(i); - return; } } @@ -1457,8 +1459,10 @@ bool ComposerEngine::initSprite(Sprite &sprite) { uint32 size = stream->readUint32LE(); debug(1, "loading BMAP: type %d, width %d, height %d, size %d", type, width, height, size); - sprite.surface.create(width, height, Graphics::PixelFormat::createFormatCLUT8()); - decompressBitmap(type, stream, (byte *)sprite.surface.pixels, size, width, height); + if (width && height) { + sprite.surface.create(width, height, Graphics::PixelFormat::createFormatCLUT8()); + decompressBitmap(type, stream, (byte *)sprite.surface.pixels, size, width, height); + } delete stream; return true; -- cgit v1.2.3 From 6f80bce8167536153592a1f70144362972ae5157 Mon Sep 17 00:00:00 2001 From: Alyssa Milburn Date: Sat, 16 Jul 2011 20:05:30 +0200 Subject: COMPOSER: Add the remaining bitmap decompressors (SLW8 is still buggy). --- engines/composer/composer.cpp | 44 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/engines/composer/composer.cpp b/engines/composer/composer.cpp index cbb80d47d5..b47a2fec72 100644 --- a/engines/composer/composer.cpp +++ b/engines/composer/composer.cpp @@ -1392,6 +1392,50 @@ void ComposerEngine::decompressBitmap(uint16 type, Common::SeekableReadStream *s assert(size == width * height); stream->read(buffer, size); break; + case kBitmapSpp32: + byte lookup[16]; + stream->read(lookup, 16); + while (size--) { + uint in = stream->readByte(); + byte lowBits = in & 0xF; + byte highBits = (in & 0xF0) >> 4; + if (highBits == 0xf) { + // run of a single color + uint count = (uint)stream->readByte() + 3; + size--; + memset(buffer, lookup[lowBits], count); + buffer += count; + } else { + // two pixels + *buffer++ = lookup[highBits]; + *buffer++ = lookup[lowBits]; + } + } + break; + case kBitmapSLW8: + while (size--) { + byte val = stream->readByte(); + if (val != 0xff) { + *buffer++ = val; + continue; + } + uint count = stream->readByte(); + size--; + + uint16 step; + if (!(count & 0x80)) { + step = stream->readByte(); + size--; + } else { + count = (count ^ 0x80); + step = stream->readUint16LE(); + size -= 2; + } + count += 4; + memcpy(buffer, buffer - step - 1, count); + buffer += count; + } + break; case kBitmapRLESLWM: { uint32 bufSize = stream->readUint32LE(); -- cgit v1.2.3 From c7f1a87d8ba2b8feb002b2036025854cf1a92c1f Mon Sep 17 00:00:00 2001 From: Alyssa Milburn Date: Sat, 16 Jul 2011 20:55:07 +0200 Subject: COMPOSER: Misc tweaks/fixes. --- engines/composer/composer.cpp | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/engines/composer/composer.cpp b/engines/composer/composer.cpp index b47a2fec72..2e10e073bb 100644 --- a/engines/composer/composer.cpp +++ b/engines/composer/composer.cpp @@ -261,7 +261,6 @@ void ComposerEngine::playAnimation(uint16 animId, int16 x, int16 y, int16 eventP continue; stopAnimation(*i); - break; } Common::SeekableReadStream *stream = NULL; @@ -396,7 +395,7 @@ void ComposerEngine::processAnimFrame() { if (entry.counter) { entry.counter--; } else { - if ((anim->_state > 1) && (anim->_stream->pos() == anim->_stream->size())) { + if ((anim->_state > 1) && (anim->_stream->pos() + 2 > anim->_stream->size())) { warning("anim with id %d ended too soon", anim->_id); anim->_state = 0; break; @@ -419,12 +418,14 @@ void ComposerEngine::processAnimFrame() { anim->_state--; + bool foundWait = false; for (uint j = 0; j < anim->_entries.size(); j++) { AnimationEntry &entry = anim->_entries[j]; - // TODO: only skip these at the start - if (entry.op == 1) + // only skip these at the start + if (!foundWait && (entry.op == 1)) continue; + foundWait = true; if (entry.counter) { entry.counter--; @@ -434,7 +435,7 @@ void ComposerEngine::processAnimFrame() { } } else { anim->seekToCurrPos(); - if ((anim->_state > 1) && (anim->_stream->pos() == anim->_stream->size())) { + if ((anim->_state > 1) && (anim->_stream->pos() + 2 > anim->_stream->size())) { warning("anim with id %d ended too soon", anim->_id); anim->_state = 0; break; @@ -581,6 +582,8 @@ Common::Error ComposerEngine::run() { unloadLibrary(_pendingPageChanges[i]._pageId); else loadLibrary(_pendingPageChanges[i]._pageId); + + lastDrawTime = _system->getMillis(); } _pendingPageChanges.clear(); @@ -597,9 +600,12 @@ Common::Error ComposerEngine::run() { runScript(script._scriptId, i, 0, 0); } - // _system->delayMillis(frameTime + lastDrawTime - thisTime); if (lastDrawTime + frameTime <= thisTime) { - lastDrawTime += frameTime; + // catch up if we're more than 5 frames behind + if (lastDrawTime + (frameTime * 5) <= thisTime) + lastDrawTime = thisTime - (frameTime * 5); + else + lastDrawTime += frameTime; for (Common::List::iterator i = _sprites.begin(); i != _sprites.end(); i++) { drawSprite(*i); @@ -1190,7 +1196,7 @@ int16 ComposerEngine::scriptFuncCall(uint16 id, int16 param1, int16 param2, int1 playAnimation(param1, param2, param3, 0); return 1; // TODO: return 0 on failure case kFuncStopAnim: - debug(3, "ignoring kFuncStopAnim(%d)", param1); + debug(3, "kFuncStopAnim(%d)", param1); for (Common::List::iterator i = _anims.begin(); i != _anims.end(); i++) { if ((*i)->_id == param1) stopAnimation(*i); @@ -1490,7 +1496,7 @@ bool ComposerEngine::initSprite(Sprite &sprite) { Pipe *pipe = *k; if (!pipe->hasResource(ID_BMAP, sprite.id)) continue; - stream = pipe->getResource(ID_BMAP, sprite.id, true); + stream = pipe->getResource(ID_BMAP, sprite.id, false); break; } -- cgit v1.2.3 From 1a9ec35de6aac704fdfbd5e3a928ef68bb59094e Mon Sep 17 00:00:00 2001 From: Alyssa Milburn Date: Sat, 16 Jul 2011 20:55:25 +0200 Subject: COMPOSER: Try handling keyDown event. --- engines/composer/composer.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/engines/composer/composer.cpp b/engines/composer/composer.cpp index 2e10e073bb..639b077ed7 100644 --- a/engines/composer/composer.cpp +++ b/engines/composer/composer.cpp @@ -641,13 +641,16 @@ Common::Error ComposerEngine::run() { }*/ break; - case Common::KEYCODE_ESCAPE: - quitGame(); + case Common::KEYCODE_q: + if (event.kbd.hasFlags(Common::KBD_CTRL)) + quitGame(); break; default: break; } + + runEvent(5, event.kbd.keycode, 0, 0); break; case Common::EVENT_QUIT: -- cgit v1.2.3 From 85df146ad42c79644d1916a9f2660603a8294d4a Mon Sep 17 00:00:00 2001 From: Alyssa Milburn Date: Sun, 17 Jul 2011 15:10:58 +0200 Subject: COMPOSER: Don't use memcpy for overlapping copies (fixes gaps in some sprites). --- engines/composer/composer.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/engines/composer/composer.cpp b/engines/composer/composer.cpp index 639b077ed7..accbe7dda5 100644 --- a/engines/composer/composer.cpp +++ b/engines/composer/composer.cpp @@ -1389,8 +1389,10 @@ static void decompressSLWM(byte *buffer, Common::SeekableReadStream *stream) { count += 2; start++; - memcpy(buffer, buffer - start, count); - buffer += count; + for (uint i = 0; i < count; i++) { + *buffer = *(buffer - start); + buffer++; + } } } @@ -1441,8 +1443,11 @@ void ComposerEngine::decompressBitmap(uint16 type, Common::SeekableReadStream *s size -= 2; } count += 4; - memcpy(buffer, buffer - step - 1, count); - buffer += count; + // this is often overlapping (for repeating patterns) + for (uint i = 0; i < count; i++) { + *buffer = *(buffer - step - 1); + buffer++; + } } break; case kBitmapRLESLWM: -- cgit v1.2.3 From 1608d5a2afbb288a8c3a77b5b252eea402f8ad19 Mon Sep 17 00:00:00 2001 From: Alyssa Milburn Date: Sun, 17 Jul 2011 23:03:51 +0200 Subject: COMPOSER: Various additions/improvements to sprite/mouse code. --- engines/composer/composer.cpp | 359 ++++++++++++++++++++++++++++++++++++------ engines/composer/composer.h | 53 ++++++- 2 files changed, 356 insertions(+), 56 deletions(-) diff --git a/engines/composer/composer.cpp b/engines/composer/composer.cpp index accbe7dda5..a4ec9feb97 100644 --- a/engines/composer/composer.cpp +++ b/engines/composer/composer.cpp @@ -133,6 +133,17 @@ enum { kFuncGetSpriteSize = 35029 }; +bool Sprite::contains(const Common::Point &pos) const { + Common::Point adjustedPos = pos - _pos; + + if (adjustedPos.x < 0 || adjustedPos.x >= _surface.w) + return false; + if (adjustedPos.y < 0 || adjustedPos.y >= _surface.h) + return false; + byte *pixels = (byte *)_surface.pixels; + return (pixels[(_surface.h - adjustedPos.y - 1) * _surface.w + adjustedPos.x] == 0); +} + // TODO: params: x, y, event param for done Animation::Animation(Common::SeekableReadStream *stream, uint16 id, Common::Point basePos, uint32 eventParam) : _stream(stream), _id(id), _basePos(basePos), _eventParam(eventParam) { @@ -252,6 +263,68 @@ Common::SeekableReadStream *Pipe::getResource(uint32 tag, uint16 id, bool buffer return new Common::MemoryReadStream(buffer, size, DisposeAfterUse::YES); } +Button::Button(Common::SeekableReadStream *stream, uint16 id) { + _id = id; + + _type = stream->readUint16LE(); + _active = (_type & 0x8000) ? true : false; + _type &= 0xfff; + debug(9, "button: type %d, active %d", _type, _active); + + _zorder = stream->readUint16LE(); + _scriptId = stream->readUint16LE(); + _scriptIdRollOn = stream->readUint16LE(); + _scriptIdRollOff = stream->readUint16LE(); + + stream->skip(4); + + uint16 size = stream->readUint16LE(); + + switch (_type) { + case kButtonRect: + case kButtonEllipse: + if (size != 4) + error("button %d of type %d had %d points, not 4", id, _type, size); + _rect.left = stream->readSint16LE(); + _rect.top = stream->readSint16LE(); + _rect.right = stream->readSint16LE(); + _rect.bottom = stream->readSint16LE(); + debug(9, "button: (%d, %d, %d, %d)", _rect.left, _rect.top, _rect.right, _rect.bottom); + break; + case kButtonSprites: + for (uint i = 0; i < size; i++) { + _spriteIds.push_back(stream->readSint16LE()); + } + break; + default: + error("unknown button type %d", _type); + } + + delete stream; +} + +bool Button::contains(const Common::Point &pos) const { + switch (_type) { + case kButtonRect: + return _rect.contains(pos); + case kButtonEllipse: + if (!_rect.contains(pos)) + return false; + { + int16 a = _rect.height() / 2; + int16 b = _rect.width() / 2; + if (!a || !b) + return false; + Common::Point adjustedPos = pos - Common::Point(_rect.left + a, _rect.top + b); + return ((adjustedPos.x*adjustedPos.x)/a*2 + (adjustedPos.y*adjustedPos.y)/b*2 < 1); + } + case kButtonSprites: + return false; + default: + error("internal error (button type %d)", _type); + } +} + void ComposerEngine::playAnimation(uint16 animId, int16 x, int16 y, int16 eventParam) { // First, we check if this animation is already playing, // and if it is, we sabotage that running one first. @@ -493,22 +566,58 @@ void ComposerEngine::processAnimFrame() { } } -void ComposerEngine::addSprite(uint16 id, uint16 animId, uint16 zorder, const Common::Point &pos) { - // TODO: re-use old sprite - removeSprite(id, animId); +bool ComposerEngine::spriteVisible(uint16 id, uint16 animId) { + for (Common::List::iterator i = _sprites.begin(); i != _sprites.end(); i++) { + if (i->_id != id) + continue; + if (i->_animId && animId && (i->_animId != animId)) + continue; + return true; + } + + return false; +} +void ComposerEngine::addSprite(uint16 id, uint16 animId, uint16 zorder, const Common::Point &pos) { Sprite sprite; - sprite.id = id; - sprite.animId = animId; - sprite.zorder = zorder; - sprite.pos = pos; - if (!initSprite(sprite)) { - warning("ignoring addSprite on invalid sprite %d", id); - return; + bool foundSprite = false; + + // re-use old sprite, if any (the BMAP for this id might well have + // changed in the meantime, but the scripts depend on that!) + for (Common::List::iterator i = _sprites.begin(); i != _sprites.end(); i++) { + if (i->_id != id) + continue; + if (i->_animId && animId && (i->_animId != animId)) + continue; + + // if the zordering is identical, modify it in-place + if (i->_zorder == zorder) { + i->_animId = animId; + i->_pos = pos; + return; + } + + // otherwise, take a copy and remove it from the list + sprite = *i; + foundSprite = true; + _sprites.erase(i); + break; + } + + sprite._animId = animId; + sprite._zorder = zorder; + sprite._pos = pos; + + if (!foundSprite) { + sprite._id = id; + if (!initSprite(sprite)) { + warning("ignoring addSprite on invalid sprite %d", id); + return; + } } for (Common::List::iterator i = _sprites.begin(); i != _sprites.end(); i++) { - if (sprite.zorder <= i->zorder) + if (sprite._zorder <= i->_zorder) continue; // insert *before* this sprite _sprites.insert(i, sprite); @@ -519,15 +628,49 @@ void ComposerEngine::addSprite(uint16 id, uint16 animId, uint16 zorder, const Co void ComposerEngine::removeSprite(uint16 id, uint16 animId) { for (Common::List::iterator i = _sprites.begin(); i != _sprites.end(); i++) { - if (i->id != id) + if (id && i->_id != id) continue; - if (i->animId && animId && (i->animId != animId)) + if (i->_animId && animId && (i->_animId != animId)) continue; - i->surface.free(); + i->_surface.free(); i = _sprites.reverse_erase(i); + if (id) + break; } } +const Sprite *ComposerEngine::getSpriteAtPos(const Common::Point &pos) { + for (Common::List::iterator i = _sprites.begin(); i != _sprites.end(); i++) { + if (i->contains(pos)) + return &(*i); + } + + return NULL; +} + +const Button *ComposerEngine::getButtonFor(const Sprite *sprite, const Common::Point &pos) { + for (Common::List