scummvm/engines/m4/animation.cpp

537 lines
15 KiB
C++

/* 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 "m4/assets.h"
#include "m4/animation.h"
#include "m4/compression.h"
#include "m4/mads_scene.h"
namespace M4 {
// TODO: this code needs cleanup
MadsAnimation::MadsAnimation(MadsM4Engine *vm, MadsView *view): Animation(vm), _view(view) {
_font = NULL;
_resetFlag = false;
_freeFlag = false;
_skipLoad = false;
_unkIndex = -1;
_messageCtr= 0;
_field12 = 0;
_currentFrame = 0;
_oldFrameEntry = 0;
_nextFrameTimer = _madsVm->_currentTimer;
_nextScrollTimer = 0;
}
MadsAnimation::~MadsAnimation() {
for (uint i = 0; i < _messages.size(); ++i) {
if (_messages[i].kernelMsgIndex >= 0)
_view->_kernelMessages.remove(_messages[i].kernelMsgIndex);
}
// Further deletion logic
if (_field12) {
_view->_spriteSlots.deleteSprites(_spriteListIndexes[_spriteListIndex]);
}
}
#define FILENAME_SIZE 13
/**
* Initialises and loads the data of an animation
*/
void MadsAnimation::initialise(const Common::String &filename, uint16 flags, M4Surface *surface, M4Surface *depthSurface) {
MadsPack anim(filename.c_str(), _vm);
bool madsRes = filename[0] == '*';
char buffer[20];
int streamIndex = 1;
// Chunk 1: header
// header
Common::SeekableReadStream *animStream = anim.getItemStream(0);
int spriteListCount = animStream->readUint16LE();
int miscEntriesCount = animStream->readUint16LE();
int frameEntryCount = animStream->readUint16LE();
int messagesCount = animStream->readUint16LE();
animStream->skip(1);
_flags = animStream->readByte();
animStream->skip(2);
_animMode = animStream->readUint16LE();
_roomNumber = animStream->readUint16LE();
animStream->skip(2);
_field12 = animStream->readUint16LE() != 0;
_spriteListIndex = animStream->readUint16LE();
_scrollX = animStream->readSint16LE();
_scrollY = animStream->readSint16LE();
_scrollTicks = animStream->readUint16LE();
animStream->skip(8);
animStream->read(buffer, FILENAME_SIZE);
buffer[FILENAME_SIZE] = '\0';
_interfaceFile = Common::String(buffer);
for (int i = 0; i < 10; ++i) {
animStream->read(buffer, FILENAME_SIZE);
buffer[FILENAME_SIZE] = '\0';
_spriteSetNames[i] = Common::String(buffer);
}
animStream->skip(81);
animStream->read(buffer, FILENAME_SIZE);
buffer[FILENAME_SIZE] = '\0';
_lbmFilename = Common::String(buffer);
animStream->skip(365);
animStream->read(buffer, FILENAME_SIZE);
buffer[FILENAME_SIZE] = '\0';
_spritesFilename = Common::String(buffer);
animStream->skip(48);
animStream->read(buffer, FILENAME_SIZE);
buffer[FILENAME_SIZE] = '\0';
_soundName = Common::String(buffer);
animStream->skip(13);
animStream->read(buffer, FILENAME_SIZE);
buffer[FILENAME_SIZE] = '\0';
_dsrName = Common::String(buffer);
animStream->read(buffer, FILENAME_SIZE);
buffer[FILENAME_SIZE] = '\0';
Common::String fontResource(buffer);
if (_animMode == 4)
flags |= 0x4000;
if (flags & 0x100)
loadInterface(surface, depthSurface);
// Initialise the reference list
for (int i = 0; i < spriteListCount; ++i)
_spriteListIndexes.push_back(-1);
delete animStream;
if (messagesCount > 0) {
// Chunk 2
// Following is a list of any messages for the animation
animStream = anim.getItemStream(streamIndex++);
for (int i = 0; i < messagesCount; ++i) {
AnimMessage rec;
rec.soundId = animStream->readSint16LE();
animStream->read(rec.msg, 64);
animStream->skip(4);
rec.pos.x = animStream->readSint16LE();
rec.pos.y = animStream->readSint16LE();
rec.flags = animStream->readUint16LE();
rec.rgb1.r = animStream->readByte() << 2;
rec.rgb1.g = animStream->readByte() << 2;
rec.rgb1.b = animStream->readByte() << 2;
rec.rgb2.r = animStream->readByte() << 2;
rec.rgb2.g = animStream->readByte() << 2;
rec.rgb2.b = animStream->readByte() << 2;
animStream->skip(2); // Space for kernelMsgIndex
rec.kernelMsgIndex = -1;
animStream->skip(6);
rec.startFrame = animStream->readUint16LE();
rec.endFrame = animStream->readUint16LE();
animStream->skip(2);
_messages.push_back(rec);
}
delete animStream;
}
if (frameEntryCount > 0) {
// Chunk 3: animation frame info
animStream = anim.getItemStream(streamIndex++);
for (int i = 0; i < frameEntryCount; i++) {
AnimFrameEntry rec;
rec.frameNumber = animStream->readUint16LE();
rec.seqIndex = animStream->readByte();
rec.spriteSlot.spriteListIndex = animStream->readByte();
rec.spriteSlot.frameNumber = animStream->readUint16LE();
rec.spriteSlot.xp = animStream->readSint16LE();
rec.spriteSlot.yp = animStream->readSint16LE();
rec.spriteSlot.depth = animStream->readSByte();
rec.spriteSlot.scale = (int8)animStream->readByte();
_frameEntries.push_back(rec);
}
delete animStream;
}
if (miscEntriesCount > 0) {
// Chunk 4: Misc Data
animStream = anim.getItemStream(streamIndex);
for (int i = 0; i < miscEntriesCount; ++i) {
AnimMiscEntry rec;
rec.soundNum = animStream->readByte();
rec.msgIndex = animStream->readSByte();
rec.numTicks = animStream->readUint16LE();
rec.posAdjust.x = animStream->readUint16LE();
rec.posAdjust.y = animStream->readUint16LE();
animStream->readUint16LE();
_miscEntries.push_back(rec);
}
delete animStream;
}
// If the animation specifies a font, then load it for access
if (_flags & ANIM_CUSTOM_FONT) {
Common::String fontName;
if (madsRes)
fontName += "*";
fontName += fontResource;
if (fontName != "")
_font = _vm->_font->getFont(fontName.c_str());
else
warning("Attempted to set a font with an empty name");
}
// If a speech file is specified, then load it
if (!_dsrName.empty())
_vm->_sound->loadDSRFile(_dsrName.c_str());
// Load all the sprite sets for the animation
for (int i = 0; i < spriteListCount; ++i) {
if (_field12 && (i == _spriteListIndex))
// Skip over field, since it's manually loaded
continue;
_spriteListIndexes[i] = _view->_spriteSlots.addSprites(_spriteSetNames[i].c_str());
}
if (_field12) {
Common::String resName;
if (madsRes)
resName += "*";
resName += _spriteSetNames[_spriteListIndex];
_spriteListIndexes[_spriteListIndex] = _view->_spriteSlots.addSprites(resName.c_str());
}
// TODO: Unknown section about handling sprite set list combined with messages size
// TODO: The original has two separate loops for the loop below based on _animMode == 4. Is it
// perhaps that in that mode the sprite frames has a different format..?
// Remap the sprite list index fields from the initial value to the indexes of the loaded
// sprite sets for the animation
for (uint i = 0; i < _frameEntries.size(); ++i) {
int idx = _frameEntries[i].spriteSlot.spriteListIndex;
_frameEntries[i].spriteSlot.spriteListIndex = _spriteListIndexes[idx];
}
if (hasScroll())
_nextScrollTimer = _madsVm->_currentTimer + _scrollTicks;
}
/**
* Loads an animation file for display
*/
void MadsAnimation::load(const Common::String &filename, int abortTimers) {
initialise(filename, 0, NULL, NULL);
_messageCtr = 0;
_skipLoad = true;
/* TODO: figure out extra stuff in this routine
if (_field12) {
_unkIndex = -1;
int listIndex = _spriteListIndexes[_spriteListIndex];
SpriteAsset &spriteSet = _view->_spriteSlots.getSprite(listIndex);
..?..
}
*/
// Initialise miscellaneous fields
_currentFrame = 0;
_oldFrameEntry = 0;
_nextFrameTimer = _madsVm->_currentTimer;
_abortTimers = abortTimers;
_abortMode = _madsVm->scene()->_abortTimersMode2;
if (_madsVm->_scene)
_actionNouns = _madsVm->scene()->_action._action;
// Initialise kernel message list
for (uint i = 0; i < _messages.size(); ++i)
_messages[i].kernelMsgIndex = -1;
}
void MadsAnimation::update() {
if (_field12) {
int spriteListIndex = _spriteListIndexes[_spriteListIndex];
int newIndex = -1;
for (uint idx = _oldFrameEntry; idx < _frameEntries.size(); ++idx) {
if (_frameEntries[idx].frameNumber > _currentFrame)
break;
if (_frameEntries[idx].spriteSlot.spriteListIndex == spriteListIndex)
newIndex = _frameEntries[idx].spriteSlot.frameNumber;
}
if (newIndex >= 0)
load1(newIndex);
}
// Check for scroll change
bool screenChanged = false;
// Handle any scrolling of the screen surface
if (hasScroll() && (_madsVm->_currentTimer >= _nextScrollTimer)) {
_view->_bgSurface->scrollX(_scrollX);
_view->_bgSurface->scrollY(_scrollY);
_nextScrollTimer = _madsVm->_currentTimer + _scrollTicks;
screenChanged = true;
}
// If it's not time for the next frame, then exit
if (_madsVm->_currentTimer < _nextFrameTimer) {
if (screenChanged)
_view->_spriteSlots.fullRefresh();
return;
}
// Loop checks for any prior animation sprite slots to be expired
for (int slotIndex = 0; slotIndex < _view->_spriteSlots.startIndex; ++slotIndex) {
if (_view->_spriteSlots[slotIndex].seqIndex >= 0x80) {
// Flag the frame as animation sprite slot
_view->_spriteSlots[slotIndex].spriteType = EXPIRED_SPRITE;
}
}
// 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 starting any sound for this frame
AnimMiscEntry &misc = _miscEntries[_currentFrame];
if (misc.soundNum)
_vm->_sound->playSound(misc.soundNum);
// Handle any offset adjustment for sprites as of this frame
if (_view->_posAdjust.x != misc.posAdjust.x) {
_view->_posAdjust.x = misc.posAdjust.x;
screenChanged = true;
}
if (_view->_posAdjust.y != misc.posAdjust.y) {
_view->_posAdjust.y = misc.posAdjust.y;
screenChanged = true;
}
if (screenChanged) {
// Signal the entire screen needs refreshing
_view->_spriteSlots.fullRefresh();
}
int spriteSlotsMax = _view->_spriteSlots.startIndex;
// 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 < spriteSlotsMax)) {
int seqIndex = _frameEntries[_oldFrameEntry].seqIndex - _view->_spriteSlots[index].seqIndex;
if (seqIndex == 0x80) {
if (_view->_spriteSlots[index] == _frameEntries[_oldFrameEntry].spriteSlot) {
_view->_spriteSlots[index].spriteType = SPRITE_ZERO;
spriteSlotIndex = -1;
}
}
++index;
continue;
}
if (spriteSlotIndex == 0) {
int slotIndex = _view->_spriteSlots.getIndex();
MadsSpriteSlot &slot = _view->_spriteSlots[slotIndex];
slot.copy(_frameEntries[_oldFrameEntry].spriteSlot);
slot.seqIndex = _frameEntries[_oldFrameEntry].seqIndex + 0x80;
SpriteAsset &spriteSet = _view->_spriteSlots.getSprite(
_view->_spriteSlots[slotIndex].spriteListIndex);
slot.spriteType = spriteSet.isBackground() ? BACKGROUND_SPRITE : FOREGROUND_SPRITE;
}
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)) {
_view->_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 colour 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.r, me.rgb1.g, me.rgb1.b);
_vm->_palette->setEntry(colIndex + 1, me.rgb2.r, me.rgb2.g, me.rgb2.b);
// Add a kernel message to display the given text
me.kernelMsgIndex = _view->_kernelMessages.add(me.pos, colIndex * 0x101 + 0x100, 0, 0, INDEFINITE_TIMEOUT, me.msg);
assert(me.kernelMsgIndex >= 0);
// Play the associated sound, if it exists
if (me.soundId > 0)
_vm->_sound->playDSRSound(me.soundId - 1, 255, false);
++_messageCtr;
}
}
// Move to the next frame
_currentFrame++;
if (_currentFrame >= (int)_miscEntries.size()) {
// Animation is complete
if (_abortTimers != 0) {
_view->_abortTimers = _abortTimers;
_view->_abortTimersMode = _abortMode;
if (_abortMode != ABORTMODE_1) {
// Copy the noun list
if (_madsVm->_scene)
_madsVm->scene()->_action._action = _actionNouns;
}
}
}
int frameNum = MIN(_currentFrame, (int)_miscEntries.size() - 1);
_nextFrameTimer = _madsVm->_currentTimer + _miscEntries[frameNum].numTicks;
}
void MadsAnimation::setCurrentFrame(int frameNumber) {
_currentFrame = frameNumber;
_oldFrameEntry = 0;
_freeFlag = false;
_nextScrollTimer = _nextFrameTimer = _madsVm->_currentTimer;
}
int MadsAnimation::getCurrentFrame() {
return _currentFrame;
}
void MadsAnimation::load1(int frameNumber) {
if (_skipLoad)
return;
Common::Point pt;
int listIndex = _spriteListIndexes[_spriteListIndex];
SpriteAsset &spriteSet = _view->_spriteSlots.getSprite(listIndex);
if (_unkIndex < 0) {
M4Surface *frame = spriteSet.getFrame(0);
pt.x = frame->bounds().left;
pt.y = frame->bounds().top;
} else {
pt.x = _unkList[_unkIndex].x;
pt.y = _unkList[_unkIndex].y;
_unkIndex = 1 - _unkIndex;
}
if (proc1(spriteSet, pt, frameNumber))
error("proc1 failure");
}
bool MadsAnimation::proc1(SpriteAsset &spriteSet, const Common::Point &pt, int frameNumber) {
return 0;
}
void MadsAnimation::loadInterface(M4Surface *&interfaceSurface, M4Surface *&depthSurface) {
if (_animMode <= 2) {
MadsSceneResources sceneResources;
sceneResources.load(_roomNumber, _interfaceFile.c_str(), 0, depthSurface, interfaceSurface);
} else if (_animMode == 4) {
// Load a scene interface
interfaceSurface->madsLoadInterface(_interfaceFile);
} else {
// This mode allocates two large surfaces for the animation
// TODO: Are these ever properly freed?
error("Anim mode %d - need to check free logic", _animMode);
assert(!interfaceSurface);
assert(!depthSurface);
depthSurface = new M4Surface(MADS_SURFACE_WIDTH, MADS_SCREEN_HEIGHT);
interfaceSurface = new M4Surface(MADS_SURFACE_WIDTH, MADS_SCREEN_HEIGHT);
depthSurface->clear();
interfaceSurface->clear();
}
}
} // End of namespace M4