aboutsummaryrefslogtreecommitdiff
path: root/engines/mads/animation.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/mads/animation.cpp')
-rw-r--r--engines/mads/animation.cpp593
1 files changed, 593 insertions, 0 deletions
diff --git a/engines/mads/animation.cpp b/engines/mads/animation.cpp
new file mode 100644
index 0000000000..512a3979f9
--- /dev/null
+++ b/engines/mads/animation.cpp
@@ -0,0 +1,593 @@
+/* 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 "mads/animation.h"
+#include "mads/compression.h"
+
+#define FILENAME_SIZE 13
+
+namespace MADS {
+
+void AAHeader::load(Common::SeekableReadStream *f) {
+ _spriteSetsCount = f->readUint16LE();
+ _miscEntriesCount = f->readUint16LE();
+ _frameEntriesCount = f->readUint16LE();
+ _messagesCount = f->readUint16LE();
+ f->skip(1);
+ _flags = f->readByte();
+
+ f->skip(2);
+ _bgType = (AnimBgType)f->readUint16LE();
+ _roomNumber = f->readUint16LE();
+ f->skip(2);
+ _manualFlag = f->readUint16LE() != 0;
+ _spritesIndex = f->readUint16LE();
+ _scrollPosition.x = f->readSint16LE();
+ _scrollPosition.y = f->readSint16LE();
+ _scrollTicks = f->readUint32LE();
+ f->skip(6);
+
+ char buffer[FILENAME_SIZE];
+ f->read(buffer, FILENAME_SIZE);
+ buffer[FILENAME_SIZE - 1] = '\0';
+ _interfaceFile = Common::String(buffer);
+
+ for (int i = 0; i < 50; ++i) {
+ f->read(buffer, FILENAME_SIZE);
+ buffer[FILENAME_SIZE - 1] = '\0';
+ if (i < _spriteSetsCount)
+ _spriteSetNames.push_back(Common::String(buffer));
+ }
+
+ f->read(buffer, FILENAME_SIZE);
+ buffer[FILENAME_SIZE - 1] = '\0';
+ _soundName = Common::String(buffer);
+
+ f->skip(13);
+ f->read(buffer, FILENAME_SIZE);
+ buffer[FILENAME_SIZE - 1] = '\0';
+ _dsrName = Common::String(buffer);
+
+ f->read(buffer, FILENAME_SIZE);
+ buffer[FILENAME_SIZE - 1] = '\0';
+ _fontResource = Common::String(buffer);
+}
+
+/*------------------------------------------------------------------------*/
+
+void AnimMessage::load(Common::SeekableReadStream *f) {
+ _soundId = f->readSint16LE();
+
+ char buffer[64];
+ f->read(&buffer[0], 64);
+ _msg = Common::String(buffer);
+ f->skip(4);
+ _pos.x = f->readSint16LE();
+ _pos.y = f->readSint16LE();
+ _flags = f->readUint16LE();
+ _rgb1[0] = f->readByte() << 2;
+ _rgb1[1] = f->readByte() << 2;
+ _rgb1[2] = f->readByte() << 2;
+ _rgb2[0] = f->readByte() << 2;
+ _rgb2[1] = f->readByte() << 2;
+ _rgb2[2] = f->readByte() << 2;
+ f->skip(2); // Space for kernelMsgIndex
+ _kernelMsgIndex = -1;
+ f->skip(6);
+ _startFrame = f->readUint16LE();
+ _endFrame = f->readUint16LE();
+ f->skip(2);
+}
+
+void AnimFrameEntry::load(Common::SeekableReadStream *f, bool uiFlag) {
+ if (uiFlag) {
+ f->skip(2);
+ _frameNumber = -1; // Unused
+ _seqIndex = f->readByte();
+ _spriteSlot._spritesIndex = f->readByte();
+ _spriteSlot._frameNumber = (int8)f->readByte();
+ f->skip(1);
+ _spriteSlot._position.x = f->readSint16LE();
+ _spriteSlot._position.y = f->readSint16LE();
+ } else {
+ _frameNumber = f->readUint16LE();
+ if (_frameNumber & 0x8000)
+ _frameNumber = -(_frameNumber & 0x7fff);
+
+ _seqIndex = f->readByte();
+ _spriteSlot._spritesIndex = f->readByte();
+ _spriteSlot._frameNumber = f->readUint16LE();
+ if (_spriteSlot._frameNumber & 0x8000)
+ _spriteSlot._frameNumber = -(_spriteSlot._frameNumber & 0x7fff);
+
+ _spriteSlot._position.x = f->readSint16LE();
+ _spriteSlot._position.y = f->readSint16LE();
+ _spriteSlot._depth = f->readSByte();
+ _spriteSlot._scale = (int8)f->readByte();
+ }
+}
+
+/*------------------------------------------------------------------------*/
+
+void AnimMiscEntry::load(Common::SeekableReadStream *f) {
+ _soundId = f->readByte();
+ _msgIndex = f->readSByte();
+ _numTicks = f->readUint16LE();
+ _posAdjust.x = f->readSint16LE();
+ _posAdjust.y = f->readSint16LE();
+ _field8 = f->readUint16LE();
+}
+
+/*------------------------------------------------------------------------*/
+
+void AnimUIEntry::load(Common::SeekableReadStream *f) {
+ _probability = f->readUint16LE();
+ _imageCount = f->readUint16LE();
+ _firstImage = f->readUint16LE();
+ _lastImage = f->readUint16LE();
+ _counter = f->readSint16LE();
+ for (int i = 0; i < ANIM_SPAWN_COUNT; ++i)
+ _spawn[i] = f->readByte();
+ for (int i = 0; i < ANIM_SPAWN_COUNT; ++i)
+ _spawnFrame[i] = f->readUint16LE();
+ _sound = f->readUint16LE() & 0xFF;
+ _soundFrame = f->readUint16LE();
+}
+
+/*------------------------------------------------------------------------*/
+
+Animation *Animation::init(MADSEngine *vm, Scene *scene) {
+ return new Animation(vm, scene);
+}
+
+Animation::Animation(MADSEngine *vm, Scene *scene) : _vm(vm), _scene(scene) {
+ _font = nullptr;
+ _resetFlag = false;
+ _messageCtr = 0;
+ _skipLoad = false;
+ _freeFlag = false;
+ _unkIndex = -1;
+ _nextFrameTimer = 0;
+ _nextScrollTimer = 0;
+ _trigger = 0;
+ _triggerMode = SEQUENCE_TRIGGER_PREPARE;
+ _actionDetails._verbId = VERB_NONE;
+ _actionDetails._objectNameId = -1;
+ _actionDetails._indirectObjectId = -1;
+ _currentFrame = 0;
+ _oldFrameEntry = 0;
+}
+
+Animation::~Animation() {
+ Scene &scene = _vm->_game->_scene;
+
+ if (_header._manualFlag)
+ scene._sprites.remove(_spriteListIndexes[_header._spritesIndex]);
+
+ for (int idx = 0; idx < _header._spriteSetsCount; ++idx) {
+ if (!_header._manualFlag || _header._spritesIndex != idx)
+ scene._sprites.remove(_spriteListIndexes[idx]);
+ }
+}
+
+void Animation::load(UserInterface &interfaceSurface, DepthSurface &depthSurface,
+ const Common::String &resName, int flags, Common::Array<PaletteCycle> *palCycles,
+ SceneInfo *sceneInfo) {
+ Common::String resourceName = resName;
+ if (!resourceName.contains("."))
+ resourceName += ".AA";
+
+ File f(resourceName);
+ MadsPack madsPack(&f);
+
+ Common::SeekableReadStream *stream = madsPack.getItemStream(0);
+ _header.load(stream);
+ delete stream;
+
+ if (_header._bgType == ANIMBG_INTERFACE)
+ flags |= PALFLAG_RESERVED;
+
+ if (flags & ANIMFLAG_LOAD_BACKGROUND) {
+ loadInterface(interfaceSurface, depthSurface, _header, flags, palCycles, sceneInfo);
+ }
+ if (flags & ANIMFLAG_LOAD_BACKGROUND_ONLY) {
+ // No data
+ _header._messagesCount = 0;
+ _header._frameEntriesCount = 0;
+ _header._miscEntriesCount = 0;
+ }
+
+ // Initialize the reference list
+ _spriteListIndexes.clear();
+ for (int i = 0; i < _header._spriteSetsCount; ++i)
+ _spriteListIndexes.push_back(-1);
+
+ int streamIndex = 1;
+ _messages.clear();
+ if (_header._messagesCount > 0) {
+ // Chunk 2: Following is a list of any messages for the animation
+ Common::SeekableReadStream *msgStream = madsPack.getItemStream(streamIndex++);
+
+ for (int i = 0; i < _header._messagesCount; ++i) {
+ AnimMessage rec;
+ rec.load(msgStream);
+ _messages.push_back(rec);
+ }
+
+ delete msgStream;
+ }
+
+ _frameEntries.clear();
+ if (_header._frameEntriesCount > 0) {
+ // Chunk 3: animation frame info
+ Common::SeekableReadStream *frameStream = madsPack.getItemStream(streamIndex++);
+
+ for (int i = 0; i < _header._frameEntriesCount; i++) {
+ AnimFrameEntry rec;
+ rec.load(frameStream, flags & ANIMFLAG_LOAD_BACKGROUND);
+ _frameEntries.push_back(rec);
+ }
+
+ delete frameStream;
+ }
+
+ _miscEntries.clear();
+ _uiEntries.clear();
+ if (_header._miscEntriesCount > 0) {
+ // Chunk 4: Misc Data
+ Common::SeekableReadStream *miscStream = madsPack.getItemStream(streamIndex++);
+
+ if (flags & ANIMFLAG_LOAD_BACKGROUND) {
+ for (int i = 0; i < _header._miscEntriesCount; ++i) {
+ AnimUIEntry rec;
+ rec.load(miscStream);
+ _uiEntries.push_back(rec);
+ }
+ } else {
+ for (int i = 0; i < _header._miscEntriesCount; ++i) {
+ AnimMiscEntry rec;
+ rec.load(miscStream);
+ _miscEntries.push_back(rec);
+ }
+ }
+
+ delete miscStream;
+ }
+
+ // If the animation specifies a font, then load it for access
+ delete _font;
+ if (_header._flags & ANIMFLAG_CUSTOM_FONT) {
+ Common::String fontName = "*" + _header._fontResource;
+ _font = _vm->_font->getFont(fontName.c_str());
+ } else {
+ _font = nullptr;
+ }
+
+ // Load all the sprite sets for the animation
+ for (uint i = 0; i < _spriteSets.size(); ++i)
+ delete _spriteSets[i];
+ _spriteSets.clear();
+ _spriteSets.resize(_header._spriteSetsCount);
+
+ for (int i = 0; i < _header._spriteSetsCount; ++i) {
+ if (_header._manualFlag && (i == _header._spritesIndex)) {
+ // Skip over field, since it's manually loaded
+ _spriteSets[i] = nullptr;
+ } else {
+ _spriteSets[i] = new SpriteAsset(_vm, _header._spriteSetNames[i], flags);
+ _spriteListIndexes[i] = _vm->_game->_scene._sprites.add(_spriteSets[i]);
+ }
+ }
+
+ if (_header._manualFlag) {
+ Common::String assetResName = "*" + _header._spriteSetNames[_header._spritesIndex];
+ SpriteAsset *sprites = new SpriteAsset(_vm, assetResName, flags);
+ _spriteSets[_header._spritesIndex] = sprites;
+
+ _spriteListIndexes[_header._spritesIndex] = _scene->_sprites.add(sprites);
+ }
+
+ Common::Array<int> usageList;
+ for (int idx = 0; idx < _header._spriteSetsCount; ++idx)
+ usageList.push_back(_spriteSets[idx]->_usageIndex);
+
+ if (usageList.size() > 0) {
+ int spritesUsageIndex = _spriteSets[0]->_usageIndex;
+ _vm->_palette->_paletteUsage.updateUsage(usageList, spritesUsageIndex);
+ }
+
+ // Remaps the sprite list indexes for frames to the loaded sprite list indexes
+ for (uint i = 0; i < _frameEntries.size(); ++i) {
+ int spriteListIndex = _frameEntries[i]._spriteSlot._spritesIndex;
+ _frameEntries[i]._spriteSlot._spritesIndex = _spriteListIndexes[spriteListIndex];
+ }
+
+ f.close();
+}
+
+void Animation::preLoad(const Common::String &resName, int level) {
+ // No implementation in ScummVM, since access is fast enough that data
+ // doesn't need to be preloaded
+}
+
+void Animation::startAnimation(int endTrigger) {
+ _messageCtr = 0;
+ _skipLoad = true;
+
+ if (_header._manualFlag) {
+ _unkIndex = -1;
+ //SpriteAsset *asset = _scene->_sprites[_spriteListIndexes[_header._spritesIndex]];
+
+ // TODO: Weird stuff with _unkList. Seems like it's treated as pointers
+ // here, but in processText, it's used as POINTs?
+
+ loadFrame(1);
+ }
+
+ if (_vm->_game->_kernelMode == KERNEL_ACTIVE_CODE)
+ _vm->_palette->refreshSceneColors();
+
+ _currentFrame = 0;
+ _oldFrameEntry = 0;
+ _nextFrameTimer = _vm->_game->_scene._frameStartTime;
+ _trigger = endTrigger;
+ _triggerMode = _vm->_game->_triggerSetupMode;
+ _actionDetails = _vm->_game->_scene._action._activeAction;
+
+ for (int idx = 0; idx < _header._messagesCount; ++idx) {
+ _messages[idx]._kernelMsgIndex = -1;
+ }
+}
+
+void Animation::loadFrame(int frameNumber) {
+ Scene &scene = _vm->_game->_scene;
+ if (_skipLoad)
+ return;
+
+ Common::Point pt;
+ int spriteListIndex = _spriteListIndexes[_header._spritesIndex];
+ SpriteAsset &spriteSet = *scene._sprites[spriteListIndex];
+
+ if (_unkIndex < 0) {
+ MSurface *frame = spriteSet.getFrame(0);
+ pt.x = frame->getBounds().left;
+ pt.y = frame->getBounds().top;
+ } else {
+ pt.x = _unkList[_unkIndex].x;
+ pt.y = _unkList[_unkIndex].y;
+ _unkIndex = 1 - _unkIndex;
+ }
+
+ if (drawFrame(spriteSet, pt, frameNumber))
+ error("drawFrame failure");
+}
+
+bool Animation::drawFrame(SpriteAsset &spriteSet, const Common::Point &pt, int frameNumber) {
+ return 0;
+}
+
+void Animation::loadInterface(UserInterface &interfaceSurface, DepthSurface &depthSurface,
+ AAHeader &header, int flags, Common::Array<PaletteCycle> *palCycles, SceneInfo *sceneInfo) {
+ _scene->_depthStyle = 0;
+ if (header._bgType <= ANIMBG_FULL_SIZE) {
+ _vm->_palette->_paletteUsage.setEmpty();
+ sceneInfo->load(header._roomNumber, flags, header._interfaceFile, 0, depthSurface, interfaceSurface);
+ _scene->_depthStyle = sceneInfo->_depthStyle == 2 ? 1 : 0;
+ if (palCycles) {
+ palCycles->clear();
+ for (uint i = 0; i < sceneInfo->_paletteCycles.size(); ++i)
+ palCycles->push_back(sceneInfo->_paletteCycles[i]);
+ }
+ } else if (header._bgType == ANIMBG_INTERFACE) {
+ // Load a scene interface
+ Common::String resourceName = "*" + header._interfaceFile;
+ interfaceSurface.load(resourceName);
+
+ if (palCycles)
+ palCycles->clear();
+ } else {
+ // Original has useless code here
+ }
+}
+
+bool Animation::hasScroll() const {
+ return (_header._scrollPosition.x != 0) || (_header._scrollPosition.y != 0);
+}
+
+void Animation::update() {
+ Scene &scene = _vm->_game->_scene;
+
+ if (_header._manualFlag) {
+ int spriteListIndex = _spriteListIndexes[_header._spritesIndex];
+ int newIndex = -1;
+
+ for (uint idx = _oldFrameEntry; idx < _frameEntries.size(); ++idx) {
+ if (_frameEntries[idx]._frameNumber > _currentFrame)
+ break;
+ if (_frameEntries[idx]._spriteSlot._spritesIndex == spriteListIndex)
+ newIndex = _frameEntries[idx]._spriteSlot._frameNumber;
+ }
+
+ if (newIndex >= 0)
+ loadFrame(newIndex);
+ }
+
+ // If it's not time for the next frame, then exit
+ if (_vm->_game->_scene._frameStartTime < _nextFrameTimer)
+ return;
+
+ for (uint idx = 0; idx < scene._spriteSlots.size(); ++idx) {
+ if (scene._spriteSlots[idx]._seqIndex >= 0x80)
+ scene._spriteSlots[idx]._flags = IMG_ERASE;
+ }
+
+ // Validate the current frame
+ if (_currentFrame >= (int)_miscEntries.size()) {
+ // Is the animation allowed to be repeated?
+ if (_resetFlag) {
+ _currentFrame = 0;
+ _oldFrameEntry = 0;
+ } else {
+ _freeFlag = true;
+ return;
+ }
+ }
+
+ // Handle executing any sound command for this frame
+ AnimMiscEntry &misc = _miscEntries[_currentFrame];
+ if (misc._soundId)
+ _vm->_sound->command(misc._soundId);
+
+ // Handle any screen scrolling
+ if (hasScroll()) {
+ scene._backgroundSurface.scrollX(_header._scrollPosition.x);
+ scene._backgroundSurface.scrollY(_header._scrollPosition.y);
+ scene._spriteSlots.fullRefresh();
+ }
+
+ // Handle any offset adjustment for sprites as of this frame
+ bool paChanged = false;
+ if (scene._posAdjust.x != misc._posAdjust.x) {
+ scene._posAdjust.x = misc._posAdjust.x;
+ paChanged = true;
+ }
+ if (scene._posAdjust.y != misc._posAdjust.y) {
+ scene._posAdjust.y = misc._posAdjust.y;
+ paChanged = true;
+ }
+
+ if (paChanged) {
+ int newIndex = scene._spriteSlots.add();
+ scene._spriteSlots[newIndex]._seqIndex = -1;
+ scene._spriteSlots[newIndex]._flags = IMG_REFRESH;
+ }
+
+ // Main frame animation loop - frames get animated by being placed, as necessary, into the
+ // main sprite slot array
+ while ((uint)_oldFrameEntry < _frameEntries.size()) {
+ if (_frameEntries[_oldFrameEntry]._frameNumber > _currentFrame)
+ break;
+ else if (_frameEntries[_oldFrameEntry]._frameNumber == _currentFrame) {
+ // Found the correct frame
+ int spriteSlotIndex = 0;
+ int index = 0;
+
+ for (;;) {
+ if ((spriteSlotIndex == 0) && (index < (int)scene._spriteSlots.size())) {
+ int seqIndex = _frameEntries[_oldFrameEntry]._seqIndex - scene._spriteSlots[index]._seqIndex;
+ if (seqIndex == 0x80) {
+ if (scene._spriteSlots[index] == _frameEntries[_oldFrameEntry]._spriteSlot) {
+ scene._spriteSlots[index]._flags = IMG_STATIC;
+ spriteSlotIndex = -1;
+ }
+ }
+ ++index;
+ continue;
+ }
+
+ if (spriteSlotIndex == 0) {
+ int slotIndex = scene._spriteSlots.add();
+ SpriteSlot &slot = scene._spriteSlots[slotIndex];
+ slot.copy(_frameEntries[_oldFrameEntry]._spriteSlot);
+ slot._seqIndex = _frameEntries[_oldFrameEntry]._seqIndex + 0x80;
+
+ SpriteAsset &spriteSet = *scene._sprites[
+ scene._spriteSlots[slotIndex]._spritesIndex];
+ slot._flags = spriteSet.isBackground() ? IMG_DELTA : IMG_UPDATE;
+ }
+ break;
+ }
+ }
+
+ ++_oldFrameEntry;
+ }
+
+ // Handle the display of any messages
+ for (uint idx = 0; idx < _messages.size(); ++idx) {
+ if (_messages[idx]._kernelMsgIndex >= 0) {
+ // Handle currently active message
+ if ((_currentFrame < _messages[idx]._startFrame) || (_currentFrame > _messages[idx]._endFrame)) {
+ scene._kernelMessages.remove(_messages[idx]._kernelMsgIndex);
+ _messages[idx]._kernelMsgIndex = -1;
+ --_messageCtr;
+ }
+ } else if ((_currentFrame >= _messages[idx]._startFrame) && (_currentFrame <= _messages[idx]._endFrame)) {
+ // Start displaying the message
+ AnimMessage &me = _messages[idx];
+
+ // The color index to use is dependant on how many messages are currently on-screen
+ uint8 colIndex;
+ switch (_messageCtr) {
+ case 1:
+ colIndex = 252;
+ break;
+ case 2:
+ colIndex = 16;
+ break;
+ default:
+ colIndex = 250;
+ break;
+ }
+
+ _vm->_palette->setEntry(colIndex, me._rgb1[0], me._rgb1[1], me._rgb1[2]);
+ _vm->_palette->setEntry(colIndex + 1, me._rgb2[0], me._rgb2[1], me._rgb2[2]);
+
+ // Add a kernel message to display the given text
+ me._kernelMsgIndex = scene._kernelMessages.add(me._pos, colIndex * 0x101 + 0x100,
+ 0, 0, INDEFINITE_TIMEOUT, me._msg);
+ assert(me._kernelMsgIndex >= 0);
+ ++_messageCtr;
+ }
+ }
+
+ // Move to the next frame
+ _currentFrame++;
+ if (_currentFrame >= (int)_miscEntries.size()) {
+ // Animation is complete
+ if (_trigger != 0) {
+ _vm->_game->_trigger = _trigger;
+ _vm->_game->_triggerMode = _triggerMode;
+
+ if (_triggerMode != SEQUENCE_TRIGGER_DAEMON) {
+ // Copy the noun list
+ scene._action._activeAction = _actionDetails;
+ }
+ }
+ }
+
+ int frameNum = MIN(_currentFrame, (int)_miscEntries.size() - 1);
+ _nextFrameTimer = _vm->_game->_scene._frameStartTime + _miscEntries[frameNum]._numTicks;
+}
+
+void Animation::setCurrentFrame(int frameNumber) {
+ _currentFrame = frameNumber;
+ _oldFrameEntry = 0;
+ _freeFlag = false;
+
+ _nextScrollTimer = _nextFrameTimer = _vm->_game->_scene._frameStartTime;
+}
+
+void Animation::setNextFrameTimer(int frameNumber) {
+ _nextFrameTimer = frameNumber;
+}
+
+} // End of namespace MADS