From a4f42017d7a43ae0e8cd7c61d6af746a19c410f5 Mon Sep 17 00:00:00 2001 From: Sven Hesse Date: Thu, 1 Sep 2011 17:52:13 +0200 Subject: GOB: Add class ANIFile Handles ANI files, describing animations. Used in hardcoded "actiony" parts of gob games, like Geisha's minigames. --- engines/gob/anifile.cpp | 326 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 326 insertions(+) create mode 100644 engines/gob/anifile.cpp (limited to 'engines/gob/anifile.cpp') diff --git a/engines/gob/anifile.cpp b/engines/gob/anifile.cpp new file mode 100644 index 0000000000..1a905f1083 --- /dev/null +++ b/engines/gob/anifile.cpp @@ -0,0 +1,326 @@ +/* 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/stream.h" +#include "common/substream.h" + +#include "gob/gob.h" +#include "gob/util.h" +#include "gob/dataio.h" +#include "gob/surface.h" +#include "gob/video.h" +#include "gob/anifile.h" + +namespace Gob { + +ANIFile::Layer::Layer() : surface(0), coordinates(0) { +} + +ANIFile::Layer::~Layer() { + delete coordinates; + delete surface; +} + + +ANIFile::ANIFile(GobEngine *vm, const Common::String &fileName, + uint16 width, uint8 bpp) : _vm(vm), + _width(width), _bpp(bpp), _hasPadding(false) { + + Common::SeekableReadStream *ani = _vm->_dataIO->getFile(fileName); + if (ani) { + Common::SeekableSubReadStreamEndian sub(ani, 0, ani->size(), false, DisposeAfterUse::YES); + + load(sub, fileName); + return; + } + + // File doesn't exist, try to open the big-endian'd alternate file + Common::String alternateFileName = fileName; + alternateFileName.setChar('_', 0); + + ani = _vm->_dataIO->getFile(alternateFileName); + if (ani) { + Common::SeekableSubReadStreamEndian sub(ani, 0, ani->size(), true, DisposeAfterUse::YES); + + // The big endian version pads a few fields to even size + _hasPadding = true; + + load(sub, fileName); + return; + } + + warning("ANIFile::ANIFile(): No such file \"%s\"", fileName.c_str()); +} + +ANIFile::~ANIFile() { +} + +void ANIFile::load(Common::SeekableSubReadStreamEndian &ani, const Common::String &fileName) { + ani.skip(2); // Unused + + uint16 animationCount = ani.readUint16(); + uint16 layerCount = ani.readUint16(); + + if (layerCount < 1) + warning("ANIFile::load(): Less than one layer (%d) in file \"%s\"", + layerCount, fileName.c_str()); + + // Load the layers + if (layerCount > 0) { + ani.skip(13); // The first layer is ignored? + if (_hasPadding) + ani.skip(1); + + _layers.resize(layerCount - 1); + for (LayerArray::iterator l = _layers.begin(); l != _layers.end(); ++l) + loadLayer(*l, ani); + } + + _maxWidth = 0; + _maxHeight = 0; + + // Load the animations + _animations.resize(animationCount); + _frames.resize(animationCount); + + for (uint16 animation = 0; animation < animationCount; animation++) { + loadAnimation(_animations[animation], _frames[animation], ani); + + _maxWidth = MAX(_maxWidth , _animations[animation].width); + _maxHeight = MAX(_maxHeight, _animations[animation].height); + } +} + +void ANIFile::loadAnimation(Animation &animation, FrameArray &frames, + Common::SeekableSubReadStreamEndian &ani) { + + // Animation properties + + animation.name = Util::readString(ani, 13); + if (_hasPadding) + ani.skip(1); + + ani.skip(13); // The name a second time?!? + if (_hasPadding) + ani.skip(1); + + ani.skip(2); // Unknown + + animation.x = (int16) ani.readUint16(); + animation.y = (int16) ani.readUint16(); + animation.deltaX = (int16) ani.readUint16(); + animation.deltaY = (int16) ani.readUint16(); + + animation.transp = ani.readByte() != 0; + + if (_hasPadding) + ani.skip(1); + + uint16 frameCount = ani.readUint16(); + + // Load the frames + + frames.resize(MAX(1, frameCount)); + loadFrames(frames, ani); + + animation.frameCount = frames.size(); + + animation.width = 0; + animation.height = 0; + + // Calculate the areas of each frame + + animation.frameAreas.resize(animation.frameCount); + for (uint16 i = 0; i < animation.frameCount; i++) { + const ChunkList &frame = frames[i]; + FrameArea &area = animation.frameAreas[i]; + + area.left = area.top = 0x7FFF; + area.right = area.bottom = -0x7FFF; + + for (ChunkList::const_iterator c = frame.begin(); c != frame.end(); c++) { + const Layer *layer; + const RXYFile::Coordinates *coords; + + if (!getPart(c->layer, c->part, layer, coords)) + continue; + + const uint16 width = coords->right - coords->left + 1; + const uint16 height = coords->bottom - coords->top + 1; + + const uint16 l = c->x; + const uint16 t = c->y; + const uint16 r = l + width - 1; + const uint16 b = t + height - 1; + + area.left = MIN(area.left , l); + area.top = MIN(area.top , t); + area.right = MAX(area.right , r); + area.bottom = MAX(area.bottom, b); + } + + if ((area.left <= area.right) && (area.top <= area.bottom)) { + animation.width = MAX(animation.width , area.right - area.left + 1); + animation.height = MAX(animation.height, area.bottom - area.top + 1); + } + } +} + +void ANIFile::loadFrames(FrameArray &frames, Common::SeekableSubReadStreamEndian &ani) { + uint32 curFrame = 0; + + bool end = false; + while (!end) { + frames[curFrame].push_back(AnimationChunk()); + AnimationChunk &chunk = frames[curFrame].back(); + + uint8 layerFlags = ani.readByte(); + + // Chunk properties + chunk.layer = (layerFlags & 0x0F) - 1; + chunk.part = ani.readByte(); + chunk.x = (int8) ani.readByte(); + chunk.y = (int8) ani.readByte(); + + // X multiplier/offset + int16 xOff = ((layerFlags & 0xC0) >> 6) << 7; + if (chunk.x >= 0) + chunk.x += xOff; + else + chunk.x -= xOff; + + // Y multiplier/offset + int16 yOff = ((layerFlags & 0x30) >> 4) << 7; + if (chunk.y >= 0) + chunk.y += yOff; + else + chunk.y -= yOff; + + uint8 multiPart = ani.readByte(); + if (multiPart == 0xFF) // No more frames in this animation + end = true; + else if (multiPart != 0x01) // No more chunks in this frame + curFrame++; + + // Shouldn't happen, but just to be safe + if (curFrame >= frames.size()) + frames.resize(curFrame + 1); + + if (_hasPadding) + ani.skip(1); + + if (ani.eos() || ani.err()) + error("ANIFile::loadFrames(): Read error"); + } +} + +void ANIFile::loadLayer(Layer &layer, Common::SeekableSubReadStreamEndian &ani) { + Common::String file = Util::readString(ani, 13); + if (_hasPadding) + ani.skip(1); + + if (file.empty()) + return; + + Common::String fileRXY = Util::setExtension(file, ".RXY"); + Common::String fileCMP = Util::setExtension(file, ".CMP"); + if (!_vm->_dataIO->hasFile(fileRXY) || !_vm->_dataIO->hasFile(fileCMP)) + return; + + loadLayer(layer, fileRXY, fileCMP); +} + +void ANIFile::loadLayer(Layer &layer, const Common::String &fileRXY, + const Common::String &fileCMP) { + + Common::SeekableReadStream *dataRXY = _vm->_dataIO->getFile(fileRXY); + if (!dataRXY) + return; + + layer.coordinates = new RXYFile(*dataRXY); + layer.surface = new Surface(_width, layer.coordinates->getHeight(), _bpp); + + _vm->_video->drawPackedSprite(fileCMP.c_str(), *layer.surface); +} + +uint16 ANIFile::getAnimationCount() const { + return _animations.size(); +} + +void ANIFile::getMaxSize(uint16 &width, uint16 &height) const { + width = _maxWidth; + height = _maxHeight; +} + +const ANIFile::Animation &ANIFile::getAnimationInfo(uint16 animation) const { + assert(animation < _animations.size()); + + return _animations[animation]; +} + +bool ANIFile::getPart(uint16 layer, uint16 part, + const Layer *&l, const RXYFile::Coordinates *&c) const { + + if (layer >= _layers.size()) + return false; + + l = &_layers[layer]; + if (!l->surface || !l->coordinates) + return false; + + if (part >= l->coordinates->size()) + return false; + + c = &(*l->coordinates)[part]; + if (c->left == 0xFFFF) + return false; + + return true; +} + +void ANIFile::draw(Surface &dest, uint16 animation, uint16 frame, int16 x, int16 y) const { + if (animation >= _animations.size()) + return; + + const Animation &anim = _animations[animation]; + if (frame >= anim.frameCount) + return; + + const ChunkList &chunks = _frames[animation][frame]; + + for (ChunkList::const_iterator c = chunks.begin(); c != chunks.end(); ++c) + drawLayer(dest, c->layer, c->part, x + c->x, y + c->y, anim.transp ? 0 : -1); +} + +void ANIFile::drawLayer(Surface &dest, uint16 layer, uint16 part, + int16 x, int16 y, int32 transp) const { + + const Layer *l; + const RXYFile::Coordinates *c; + + if (!getPart(layer, part, l, c)) + return; + + dest.blit(*l->surface, c->left, c->top, c->right, c->bottom, x, y, transp); +} + +} // End of namespace Gob -- cgit v1.2.3