diff options
Diffstat (limited to 'engines/composer')
-rw-r--r-- | engines/composer/composer.cpp | 654 | ||||
-rw-r--r-- | engines/composer/composer.h | 45 | ||||
-rw-r--r-- | engines/composer/graphics.cpp | 696 | ||||
-rw-r--r-- | engines/composer/graphics.h | 74 | ||||
-rw-r--r-- | engines/composer/module.mk | 1 |
5 files changed, 774 insertions, 696 deletions
diff --git a/engines/composer/composer.cpp b/engines/composer/composer.cpp index 4ec3c1e356..656030f1f1 100644 --- a/engines/composer/composer.cpp +++ b/engines/composer/composer.cpp @@ -37,28 +37,18 @@ #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/graphics.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, @@ -134,49 +124,6 @@ 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); -} - -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.priority = _stream->readUint16LE(); - entry.state = _stream->readUint16LE(); - entry.counter = 0; - entry.prevValue = 0; - debug(8, "anim entry: %04x, %04x, %04x", entry.op, entry.priority, entry.state); - _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; @@ -325,364 +272,6 @@ bool Button::contains(const Common::Point &pos) const { } } -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<Animation *>::iterator i = _anims.begin(); i != _anims.end(); i++) { - Animation *anim = *i; - if (anim->_id != animId) - continue; - - stopAnimation(*i); - } - - Common::SeekableReadStream *stream = NULL; - Pipe *newPipe = NULL; - - // First, check the existing pipes. - for (Common::List<Pipe *>::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) { - if (!hasResource(ID_ANIM, animId)) { - warning("ignoring attempt to play invalid anim %d", animId); - return; - } - stream = getResource(ID_ANIM, animId); - - uint32 type = 0; - for (Common::List<Library>::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); - runEvent(1, animId, eventParam, 0); - if (newPipe) - newPipe->_anim = anim; -} - -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.prevValue) - continue; - if (localOnly) { - if (pipesOnly) - continue; - if (entry.op == kAnimOpDrawSprite) { - removeSprite(entry.prevValue, anim->_id); - } else if (entry.op == kAnimOpPlayWave) { - if (_currSoundPriority >= entry.priority) { - _mixer->stopAll(); - _audioStream = NULL; - } - } - } else { - if (entry.op != kAnimOpPlayAnim) - continue; - for (Common::List<Animation *>::iterator i = _anims.begin(); i != _anims.end(); i++) { - if ((*i)->_id == entry.prevValue) - stopAnimation(*i); - } - } - } - - // kill any pipe owned by the animation - for (Common::List<Pipe *>::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, uint16 priority, bool bufferingOnly) { - if (_audioStream && _audioStream->numQueuedStreams() != 0) { - if (_currSoundPriority < priority) - return; - if (_currSoundPriority > priority) { - _mixer->stopAll(); - _audioStream = NULL; - } - } - Common::SeekableReadStream *stream = NULL; - if (!bufferingOnly && hasResource(ID_WAVE, id)) { - stream = getResource(ID_WAVE, id); - } else { - for (Common::List<Pipe *>::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) - 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); - _currSoundPriority = priority; - delete stream; - if (!_mixer->isSoundHandleActive(_soundHandle)) - _mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundHandle, _audioStream); -} - -void ComposerEngine::processAnimFrame() { - for (Common::List<Animation *>::iterator i = _anims.begin(); i != _anims.end(); i++) { - Animation *anim = *i; - - anim->seekToCurrPos(); - - if (anim->_state <= 1) { - bool normalEnd = (anim->_state == 1); - if (normalEnd) { - runEvent(2, anim->_id, anim->_eventParam, 0); - } - stopAnimation(anim, true, normalEnd); - delete anim; - i = _anims.reverse_erase(i); - - continue; - } - - for (uint j = 0; j < anim->_entries.size(); j++) { - AnimationEntry &entry = anim->_entries[j]; - if (entry.op != kAnimOpEvent) - break; - if (entry.counter) { - entry.counter--; - } else { - 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; - } - - uint16 event = anim->_stream->readUint16LE(); - anim->_offset += 2; - if (event == 0xffff) { - entry.counter = anim->_stream->readUint16LE() - 1; - anim->_offset += 2; - } else { - debug(4, "anim: event %d", event); - runEvent(event, anim->_id, 0, 0); - } - } - } - } - - for (Common::List<Animation *>::iterator i = _anims.begin(); i != _anims.end(); i++) { - Animation *anim = *i; - - // did the anim get disabled? - if (anim->_state == 0) { - stopAnimation(anim, true, false); - delete anim; - i = _anims.reverse_erase(i); - continue; - } - - anim->_state--; - - bool foundWait = false; - for (uint j = 0; j < anim->_entries.size(); j++) { - AnimationEntry &entry = anim->_entries[j]; - - // only skip these at the start - if (!foundWait && (entry.op == kAnimOpEvent)) - continue; - foundWait = true; - - if (entry.counter) { - entry.counter--; - if ((entry.op == kAnimOpPlayWave) && entry.prevValue) { - debug(4, "anim: continue play wave %d", entry.prevValue); - playWaveForAnim(entry.prevValue, entry.priority, true); - } - } else { - anim->seekToCurrPos(); - 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; - } - - 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 kAnimOpEvent: - debug(4, "anim: event %d", data); - runEvent(data, anim->_id, 0, 0); - break; - case kAnimOpPlayWave: - debug(4, "anim: play wave %d", data); - playWaveForAnim(data, entry.priority, false); - break; - case kAnimOpPlayAnim: - debug(4, "anim: play anim %d", data); - playAnimation(data, anim->_basePos.x, anim->_basePos.y, 1); - break; - case kAnimOpDrawSprite: - if (!data || (entry.prevValue && (data != entry.prevValue))) { - debug(4, "anim: erase sprite %d", entry.prevValue); - removeSprite(entry.prevValue, anim->_id); - } - if (data) { - int16 x = anim->_stream->readSint16LE(); - int16 y = anim->_stream->readSint16LE(); - Common::Point pos(x, y); - anim->_offset += 4; - uint16 animId = anim->_id; - if (anim->_state == entry.state) - animId = 0; - debug(4, "anim: draw sprite %d at (relative) %d,%d", data, x, y); - bool wasVisible = spriteVisible(data, animId); - addSprite(data, animId, entry.priority, anim->_basePos + pos); - if (wasVisible) { - // make sure modified sprite isn't removed by another entry - for (uint k = 0; k < anim->_entries.size(); k++) { - if (anim->_entries[k].op != kAnimOpDrawSprite) - continue; - if (anim->_entries[k].prevValue == data) - anim->_entries[k].prevValue = 1; - } - } - } - break; - default: - warning("unknown anim op %d", entry.op); - } - - entry.prevValue = data; - } - } - } - } - - for (Common::List<Pipe *>::iterator j = _pipes.begin(); j != _pipes.end(); j++) { - Pipe *pipe = *j; - pipe->nextFrame(); - } -} - -bool ComposerEngine::spriteVisible(uint16 id, uint16 animId) { - for (Common::List<Sprite>::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; - 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<Sprite>::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<Sprite>::iterator i = _sprites.begin(); i != _sprites.end(); i++) { - if (sprite._zorder <= i->_zorder) - continue; - // insert *before* this sprite - _sprites.insert(i, sprite); - return; - } - _sprites.push_back(sprite); -} - -void ComposerEngine::removeSprite(uint16 id, uint16 animId) { - for (Common::List<Sprite>::iterator i = _sprites.begin(); i != _sprites.end(); i++) { - if (id && i->_id != id) - continue; - if (i->_animId && animId && (i->_animId != animId)) - continue; - i->_surface.free(); - i = _sprites.reverse_erase(i); - if (id) - break; - } -} - -const Sprite *ComposerEngine::getSpriteAtPos(const Common::Point &pos) { - for (Common::List<Sprite>::iterator i = _sprites.reverse_begin(); i != _sprites.end(); --i) { - // avoid highest-level objects (e.g. the cursor) - if (!i->_zorder) - continue; - - if (i->contains(pos)) - return &(*i); - } - - return NULL; -} - const Button *ComposerEngine::getButtonFor(const Sprite *sprite, const Common::Point &pos) { for (Common::List<Button>::iterator i = _buttons.reverse_begin(); i != _buttons.end(); --i) { if (!i->_active) @@ -1682,245 +1271,4 @@ int16 ComposerEngine::scriptFuncCall(uint16 id, int16 param1, int16 param2, int1 } } -void ComposerEngine::redraw() { - for (Common::List<Sprite>::iterator i = _sprites.begin(); i != _sprites.end(); i++) { - drawSprite(*i); - } - - _system->copyRectToScreen((byte *)_surface.pixels, _surface.pitch, 0, 0, _surface.w, _surface.h); - _system->updateScreen(); - _needsUpdate = false; -} - -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); -} - -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++; - for (uint i = 0; i < count; i++) { - *buffer = *(buffer - start); - buffer++; - } - } -} - -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 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; - // this is often overlapping (for repeating patterns) - for (uint i = 0; i < count; i++) { - *buffer = *(buffer - step - 1); - buffer++; - } - } - 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); - } -} - -Common::SeekableReadStream *ComposerEngine::getStreamForSprite(uint16 id) { - for (Common::List<Pipe *>::iterator k = _pipes.begin(); k != _pipes.end(); k++) { - Pipe *pipe = *k; - if (!pipe->hasResource(ID_BMAP, id)) - continue; - return pipe->getResource(ID_BMAP, id, true); - } - if (hasResource(ID_BMAP, id)) - return getResource(ID_BMAP, id); - return NULL; -} - -bool ComposerEngine::initSprite(Sprite &sprite) { - Common::SeekableReadStream *stream = getStreamForSprite(sprite._id); - if (!stream) - return false; - - uint16 type = stream->readUint16LE(); - int16 height = stream->readSint16LE(); - int16 width = stream->readSint16LE(); - uint32 size = stream->readUint32LE(); - debug(1, "loading BMAP: type %d, width %d, height %d, size %d", type, width, height, size); - - if (width > 0 && height > 0) { - sprite._surface.create(width, height, Graphics::PixelFormat::createFormatCLUT8()); - decompressBitmap(type, stream, (byte *)sprite._surface.pixels, size, width, height); - } else { - // there are some sprites (e.g. a -998x-998 one in Gregory's title screen) - // which have an invalid size, but the original engine doesn't notice for - // RLE sprites since the width/height is ignored until the actual draw - if (type != kBitmapRLESLWM) - error("sprite (type %d) had invalid size %dx%d", type, width, height); - } - delete stream; - - return true; -} - -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 (int j = 0; j < sprite._surface.h; j++) { - if (j + y < 0) - continue; - if (j + y >= _surface.h) - break; - byte *in = (byte *)sprite._surface.pixels + (sprite._surface.h - j - 1) * sprite._surface.w; - byte *out = pixels + ((j + y) * _surface.w) + x; - for (int i = 0; i < sprite._surface.w; i++) - if ((x + i >= 0) && (x + i < _surface.w) && in[i]) - out[i] = in[i]; - } -} - } // End of namespace Composer diff --git a/engines/composer/composer.h b/engines/composer/composer.h index 6fa10d9c9a..012bef160f 100644 --- a/engines/composer/composer.h +++ b/engines/composer/composer.h @@ -57,50 +57,9 @@ enum GameType { }; class Archive; +struct Animation; class ComposerEngine; - -struct Sprite { - uint16 _id; - uint16 _animId; - uint16 _zorder; - Common::Point _pos; - Graphics::Surface _surface; - - bool contains(const Common::Point &pos) const; -}; - -enum { - kAnimOpEvent = 1, - kAnimOpPlayWave = 2, - kAnimOpPlayAnim = 3, - kAnimOpDrawSprite = 4 -}; - -struct AnimationEntry { - uint32 state; - uint16 op; - uint16 priority; - uint16 counter; - uint16 prevValue; -}; - -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<AnimationEntry> _entries; - - uint32 _offset; - Common::SeekableReadStream *_stream; -}; +struct Sprite; struct PipeResourceEntry { uint32 size; diff --git a/engines/composer/graphics.cpp b/engines/composer/graphics.cpp new file mode 100644 index 0000000000..e6b87478c7 --- /dev/null +++ b/engines/composer/graphics.cpp @@ -0,0 +1,696 @@ +/* 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 "graphics/palette.h" + +#include "audio/audiostream.h" +#include "audio/decoders/raw.h" + +#include "composer/composer.h" +#include "composer/graphics.h" +#include "composer/resource.h" + +namespace Composer { + +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); +} + +enum { + kAnimOpEvent = 1, + kAnimOpPlayWave = 2, + kAnimOpPlayAnim = 3, + kAnimOpDrawSprite = 4 +}; + +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.priority = _stream->readUint16LE(); + entry.state = _stream->readUint16LE(); + entry.counter = 0; + entry.prevValue = 0; + debug(8, "anim entry: %04x, %04x, %04x", entry.op, entry.priority, entry.state); + _entries.push_back(entry); + } + + _offset = _stream->pos(); +} + +Animation::~Animation() { + delete _stream; +} + +void Animation::seekToCurrPos() { + _stream->seek(_offset, SEEK_SET); +} + +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<Animation *>::iterator i = _anims.begin(); i != _anims.end(); i++) { + Animation *anim = *i; + if (anim->_id != animId) + continue; + + stopAnimation(*i); + } + + Common::SeekableReadStream *stream = NULL; + Pipe *newPipe = NULL; + + // First, check the existing pipes. + for (Common::List<Pipe *>::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) { + if (!hasResource(ID_ANIM, animId)) { + warning("ignoring attempt to play invalid anim %d", animId); + return; + } + stream = getResource(ID_ANIM, animId); + + uint32 type = 0; + for (Common::List<Library>::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); + runEvent(1, animId, eventParam, 0); + if (newPipe) + newPipe->_anim = anim; +} + +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.prevValue) + continue; + if (localOnly) { + if (pipesOnly) + continue; + if (entry.op == kAnimOpDrawSprite) { + removeSprite(entry.prevValue, anim->_id); + } else if (entry.op == kAnimOpPlayWave) { + if (_currSoundPriority >= entry.priority) { + _mixer->stopAll(); + _audioStream = NULL; + } + } + } else { + if (entry.op != kAnimOpPlayAnim) + continue; + for (Common::List<Animation *>::iterator i = _anims.begin(); i != _anims.end(); i++) { + if ((*i)->_id == entry.prevValue) + stopAnimation(*i); + } + } + } + + // kill any pipe owned by the animation + for (Common::List<Pipe *>::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, uint16 priority, bool bufferingOnly) { + if (_audioStream && _audioStream->numQueuedStreams() != 0) { + if (_currSoundPriority < priority) + return; + if (_currSoundPriority > priority) { + _mixer->stopAll(); + _audioStream = NULL; + } + } + Common::SeekableReadStream *stream = NULL; + if (!bufferingOnly && hasResource(ID_WAVE, id)) { + stream = getResource(ID_WAVE, id); + } else { + for (Common::List<Pipe *>::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) + 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); + _currSoundPriority = priority; + delete stream; + if (!_mixer->isSoundHandleActive(_soundHandle)) + _mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundHandle, _audioStream); +} + +void ComposerEngine::processAnimFrame() { + for (Common::List<Animation *>::iterator i = _anims.begin(); i != _anims.end(); i++) { + Animation *anim = *i; + + anim->seekToCurrPos(); + + if (anim->_state <= 1) { + bool normalEnd = (anim->_state == 1); + if (normalEnd) { + runEvent(2, anim->_id, anim->_eventParam, 0); + } + stopAnimation(anim, true, normalEnd); + delete anim; + i = _anims.reverse_erase(i); + + continue; + } + + for (uint j = 0; j < anim->_entries.size(); j++) { + AnimationEntry &entry = anim->_entries[j]; + if (entry.op != kAnimOpEvent) + break; + if (entry.counter) { + entry.counter--; + } else { + 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; + } + + uint16 event = anim->_stream->readUint16LE(); + anim->_offset += 2; + if (event == 0xffff) { + entry.counter = anim->_stream->readUint16LE() - 1; + anim->_offset += 2; + } else { + debug(4, "anim: event %d", event); + runEvent(event, anim->_id, 0, 0); + } + } + } + } + + for (Common::List<Animation *>::iterator i = _anims.begin(); i != _anims.end(); i++) { + Animation *anim = *i; + + // did the anim get disabled? + if (anim->_state == 0) { + stopAnimation(anim, true, false); + delete anim; + i = _anims.reverse_erase(i); + continue; + } + + anim->_state--; + + bool foundWait = false; + for (uint j = 0; j < anim->_entries.size(); j++) { + AnimationEntry &entry = anim->_entries[j]; + + // only skip these at the start + if (!foundWait && (entry.op == kAnimOpEvent)) + continue; + foundWait = true; + + if (entry.counter) { + entry.counter--; + if ((entry.op == kAnimOpPlayWave) && entry.prevValue) { + debug(4, "anim: continue play wave %d", entry.prevValue); + playWaveForAnim(entry.prevValue, entry.priority, true); + } + } else { + anim->seekToCurrPos(); + 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; + } + + 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 kAnimOpEvent: + debug(4, "anim: event %d", data); + runEvent(data, anim->_id, 0, 0); + break; + case kAnimOpPlayWave: + debug(4, "anim: play wave %d", data); + playWaveForAnim(data, entry.priority, false); + break; + case kAnimOpPlayAnim: + debug(4, "anim: play anim %d", data); + playAnimation(data, anim->_basePos.x, anim->_basePos.y, 1); + break; + case kAnimOpDrawSprite: + if (!data || (entry.prevValue && (data != entry.prevValue))) { + debug(4, "anim: erase sprite %d", entry.prevValue); + removeSprite(entry.prevValue, anim->_id); + } + if (data) { + int16 x = anim->_stream->readSint16LE(); + int16 y = anim->_stream->readSint16LE(); + Common::Point pos(x, y); + anim->_offset += 4; + uint16 animId = anim->_id; + if (anim->_state == entry.state) + animId = 0; + debug(4, "anim: draw sprite %d at (relative) %d,%d", data, x, y); + bool wasVisible = spriteVisible(data, animId); + addSprite(data, animId, entry.priority, anim->_basePos + pos); + if (wasVisible) { + // make sure modified sprite isn't removed by another entry + for (uint k = 0; k < anim->_entries.size(); k++) { + if (anim->_entries[k].op != kAnimOpDrawSprite) + continue; + if (anim->_entries[k].prevValue == data) + anim->_entries[k].prevValue = 1; + } + } + } + break; + default: + warning("unknown anim op %d", entry.op); + } + + entry.prevValue = data; + } + } + } + } + + for (Common::List<Pipe *>::iterator j = _pipes.begin(); j != _pipes.end(); j++) { + Pipe *pipe = *j; + pipe->nextFrame(); + } +} + +bool ComposerEngine::spriteVisible(uint16 id, uint16 animId) { + for (Common::List<Sprite>::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; + 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<Sprite>::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<Sprite>::iterator i = _sprites.begin(); i != _sprites.end(); i++) { + if (sprite._zorder <= i->_zorder) + continue; + // insert *before* this sprite + _sprites.insert(i, sprite); + return; + } + _sprites.push_back(sprite); +} + +void ComposerEngine::removeSprite(uint16 id, uint16 animId) { + for (Common::List<Sprite>::iterator i = _sprites.begin(); i != _sprites.end(); i++) { + if (id && i->_id != id) + continue; + if (i->_animId && animId && (i->_animId != animId)) + continue; + i->_surface.free(); + i = _sprites.reverse_erase(i); + if (id) + break; + } +} + +const Sprite *ComposerEngine::getSpriteAtPos(const Common::Point &pos) { + for (Common::List<Sprite>::iterator i = _sprites.reverse_begin(); i != _sprites.end(); --i) { + // avoid highest-level objects (e.g. the cursor) + if (!i->_zorder) + continue; + + if (i->contains(pos)) + return &(*i); + } + + return NULL; +} + +void ComposerEngine::redraw() { + for (Common::List<Sprite>::iterator i = _sprites.begin(); i != _sprites.end(); i++) { + drawSprite(*i); + } + + _system->copyRectToScreen((byte *)_surface.pixels, _surface.pitch, 0, 0, _surface.w, _surface.h); + _system->updateScreen(); + _needsUpdate = false; +} + +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); +} + +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++; + for (uint i = 0; i < count; i++) { + *buffer = *(buffer - start); + buffer++; + } + } +} + +// bitmap compression types +enum { + kBitmapUncompressed = 0, + kBitmapSpp32 = 1, + kBitmapSLW8 = 3, + kBitmapRLESLWM = 4, + kBitmapSLWM = 5 +}; + +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 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; + // this is often overlapping (for repeating patterns) + for (uint i = 0; i < count; i++) { + *buffer = *(buffer - step - 1); + buffer++; + } + } + 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); + } +} + +Common::SeekableReadStream *ComposerEngine::getStreamForSprite(uint16 id) { + for (Common::List<Pipe *>::iterator k = _pipes.begin(); k != _pipes.end(); k++) { + Pipe *pipe = *k; + if (!pipe->hasResource(ID_BMAP, id)) + continue; + return pipe->getResource(ID_BMAP, id, true); + } + if (hasResource(ID_BMAP, id)) + return getResource(ID_BMAP, id); + return NULL; +} + +bool ComposerEngine::initSprite(Sprite &sprite) { + Common::SeekableReadStream *stream = getStreamForSprite(sprite._id); + if (!stream) + return false; + + uint16 type = stream->readUint16LE(); + int16 height = stream->readSint16LE(); + int16 width = stream->readSint16LE(); + uint32 size = stream->readUint32LE(); + debug(1, "loading BMAP: type %d, width %d, height %d, size %d", type, width, height, size); + + if (width > 0 && height > 0) { + sprite._surface.create(width, height, Graphics::PixelFormat::createFormatCLUT8()); + decompressBitmap(type, stream, (byte *)sprite._surface.pixels, size, width, height); + } else { + // there are some sprites (e.g. a -998x-998 one in Gregory's title screen) + // which have an invalid size, but the original engine doesn't notice for + // RLE sprites since the width/height is ignored until the actual draw + if (type != kBitmapRLESLWM) + error("sprite (type %d) had invalid size %dx%d", type, width, height); + } + delete stream; + + return true; +} + +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 (int j = 0; j < sprite._surface.h; j++) { + if (j + y < 0) + continue; + if (j + y >= _surface.h) + break; + byte *in = (byte *)sprite._surface.pixels + (sprite._surface.h - j - 1) * sprite._surface.w; + byte *out = pixels + ((j + y) * _surface.w) + x; + for (int i = 0; i < sprite._surface.w; i++) + if ((x + i >= 0) && (x + i < _surface.w) && in[i]) + out[i] = in[i]; + } +} + +} // End of namespace Composer diff --git a/engines/composer/graphics.h b/engines/composer/graphics.h new file mode 100644 index 0000000000..9331d8c623 --- /dev/null +++ b/engines/composer/graphics.h @@ -0,0 +1,74 @@ +/* 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_GRAPHICS_H +#define COMPOSER_GRAPHICS_H + +#include "common/rect.h" +#include "graphics/surface.h" + +namespace Composer { + +class ComposerEngine; + +struct Sprite { + uint16 _id; + uint16 _animId; + uint16 _zorder; + Common::Point _pos; + Graphics::Surface _surface; + + bool contains(const Common::Point &pos) const; +}; + +struct AnimationEntry { + uint32 state; + uint16 op; + uint16 priority; + uint16 counter; + uint16 prevValue; +}; + +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<AnimationEntry> _entries; + + uint32 _offset; + Common::SeekableReadStream *_stream; +}; + +} // End of namespace Composer + +#endif diff --git a/engines/composer/module.mk b/engines/composer/module.mk index 5637ffbc6e..cb95396125 100644 --- a/engines/composer/module.mk +++ b/engines/composer/module.mk @@ -3,6 +3,7 @@ MODULE := engines/composer MODULE_OBJS = \ composer.o \ detection.o \ + graphics.o \ resource.o # This module can be built as a plugin |