aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--engines/composer/composer.cpp654
-rw-r--r--engines/composer/composer.h45
-rw-r--r--engines/composer/graphics.cpp696
-rw-r--r--engines/composer/graphics.h74
-rw-r--r--engines/composer/module.mk1
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