scummvm/engines/lure/animseq.cpp
2021-12-26 18:48:43 +01:00

300 lines
8.6 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "lure/animseq.h"
#include "lure/decode.h"
#include "lure/events.h"
#include "lure/lure.h"
#include "lure/palette.h"
#include "lure/sound.h"
#include "common/endian.h"
namespace Lure {
// delay
// Delays for a given number of milliseconds. If it returns true, it indicates that
// Escape has been pressed, and the introduction should be aborted.
AnimAbortType AnimationSequence::delay(uint32 milliseconds) {
Events &events = Events::getReference();
uint32 delayCtr = g_system->getMillis() + milliseconds;
while (g_system->getMillis() < delayCtr) {
while (events.pollEvent()) {
if ((events.type() == Common::EVENT_KEYDOWN) && (events.event().kbd.ascii != 0)) {
if (events.event().kbd.keycode == Common::KEYCODE_ESCAPE)
return ABORT_END_INTRO;
else
return ABORT_NEXT_SCENE;
} else if (events.type() == Common::EVENT_LBUTTONDOWN) {
return ABORT_NEXT_SCENE;
} else if ((events.type() == Common::EVENT_QUIT) || (events.type() == Common::EVENT_RETURN_TO_LAUNCHER)) {
return ABORT_END_INTRO;
} else if (events.type() == Common::EVENT_MAINMENU) {
return ABORT_NONE;
}
}
uint32 delayAmount = delayCtr - g_system->getMillis();
if (delayAmount > 10) delayAmount = 10;
g_system->delayMillis(delayAmount);
}
return ABORT_NONE;
}
// egaDecodeFrame
// Decodes a single frame of a EGA animation sequence
void AnimationSequence::egaDecodeFrame(byte *&pPixels) {
Screen &screen = Screen::getReference();
byte *screenData = screen.screen_raw();
// Skip over the list of blocks that are changed
int numBlocks = *pPixels++;
pPixels += numBlocks;
// Loop through the list of same/changed pixel ranges
int len = *pPixels++;
int offset = MENUBAR_Y_SIZE * FULL_SCREEN_WIDTH *
EGA_NUM_LAYERS / EGA_PIXELS_PER_BYTE;
while ((offset += len) < FULL_SCREEN_WIDTH * FULL_SCREEN_HEIGHT / 2) {
int repeatLen = *pPixels++;
if (repeatLen > 0) {
byte *pDest = screenData + (offset / EGA_NUM_LAYERS) * EGA_PIXELS_PER_BYTE;
// Copy over the following bytes - each four bytes contain the four
// planes worth of data for 8 sequential pixels
while (repeatLen-- > 0) {
int planeNum = offset % EGA_NUM_LAYERS;
byte v = *pPixels++;
for (int bitCtr = 0; bitCtr < 8; ++bitCtr, v <<= 1) {
if ((v & 0x80) != 0)
*(pDest + bitCtr) |= 1 << planeNum;
else
*(pDest + bitCtr) &= ~(1 << planeNum);
}
if ((++offset % EGA_NUM_LAYERS) == 0)
pDest += EGA_PIXELS_PER_BYTE;
}
}
// Get next skip bytes length
len = *pPixels++;
}
}
// vgaDecodeFrame
// Decodes a single frame of a VGA animation sequence
void AnimationSequence::vgaDecodeFrame(byte *&pPixels, byte *&pLines) {
Screen &screen = Screen::getReference();
byte *screenData = screen.screen_raw();
uint16 screenPos = 0;
uint16 len;
while (screenPos < SCREEN_SIZE) {
// Get line length
len = (uint16) *pLines++;
if (len == 0) {
len = READ_LE_UINT16(pLines);
pLines += 2;
}
// Move the splice over
memcpy(screenData, pPixels, len);
screenData += len;
screenPos += len;
pPixels += len;
// Get the offset inc amount
len = (uint16) *pLines++;
if (len == 0) {
len = READ_LE_UINT16(pLines);
pLines += 2;
}
screenData += len;
screenPos += len;
}
}
AnimationSequence::AnimationSequence(uint16 screenId, Palette &palette, bool fadeIn, int frameDelay,
const AnimSoundSequence *soundList, uint8 loops): _screenId(screenId), _palette(palette),
_frameDelay(frameDelay), _soundList(soundList), _loops(loops) {
Screen &screen = Screen::getReference();
PictureDecoder decoder;
Disk &d = Disk::getReference();
// Get the data and decode it. Note that VGA decompression is used
// even if the decompressed contents is actually EGA data
MemoryBlock *data = d.getEntry(_screenId);
_decodedData = decoder.vgaDecode(data, MAX_ANIM_DECODER_BUFFER_SIZE);
delete data;
_isEGA = LureEngine::getReference().isEGA();
if (_isEGA) {
// Setup for EGA animation
_lineRefs = nullptr;
// Reset the palette and clear the screen for EGA decoding
screen.setPaletteEmpty(RES_PALETTE_ENTRIES);
screen.screen().empty();
byte *pSrc = _decodedData->data();
pSrc = showInitialScreen(pSrc);
screen.setPalette(&_palette, 0, _palette.numEntries());
// Set pointers for animation
_pPixelsStart = _pPixels = pSrc;
_pPixelsEnd = _decodedData->data() + _decodedData->size() - 1;
_pLinesStart = _pLines = nullptr;
_pLinesEnd = nullptr;
} else {
// Setup for VGA animation
_lineRefs = d.getEntry(_screenId + 1);
// Reset the palette and set the initial starting screen
screen.setPaletteEmpty(RES_PALETTE_ENTRIES);
showInitialScreen();
// Set the palette
if (fadeIn) screen.paletteFadeIn(&_palette);
else screen.setPalette(&_palette, 0, _palette.numEntries());
// Set up frame pointers
_pPixelsStart = _pPixels = _decodedData->data() + SCREEN_SIZE;
_pPixelsEnd = _decodedData->data() + _decodedData->size() - 1;
_pLinesStart = _pLines = _lineRefs->data();
_pLinesEnd = _lineRefs->data() + _lineRefs->size() - 1;
}
}
AnimationSequence::~AnimationSequence() {
delete _lineRefs;
delete _decodedData;
// Renable GMM saving/loading now that the animation is done
LureEngine::getReference()._saveLoadAllowed = true;
}
// show
// Main method for displaying the animation
AnimAbortType AnimationSequence::show() {
Screen &screen = Screen::getReference();
AnimAbortType result;
const AnimSoundSequence *soundFrame = _soundList;
int frameCtr = 0;
// Disable GMM saving/loading whilst animation is running
LureEngine::getReference()._saveLoadAllowed = false;
// Loop through displaying the animations
while (_loops > 0) {
if (_pPixels < _pPixelsEnd && (_isEGA || _pLines < _pLinesEnd)) {
if ((soundFrame != nullptr) && (soundFrame->rolandSoundId != 0xFF) && (frameCtr == 0))
Sound.musicInterface_Play(
Sound.isRoland() ? soundFrame->rolandSoundId : soundFrame->adlibSoundId, soundFrame->music);
if (_isEGA)
egaDecodeFrame(_pPixels);
else {
vgaDecodeFrame(_pPixels, _pLines);
}
// Make the decoded frame visible
screen.update();
} else {
// Animation has finished.
_loops--;
if (_loops > 0) {
// Animation will be repeated, so reset
// and show the first frame again.
_pPixels = _pPixelsStart;
_pLines = _pLinesStart;
showInitialScreen(_decodedData->data());
}
}
result = delay(_frameDelay * 1000 / 50);
if (result != ABORT_NONE) return result;
if ((soundFrame != nullptr) && (++frameCtr == soundFrame->numFrames)) {
frameCtr = 0;
++soundFrame;
if (soundFrame->numFrames == 0) soundFrame = nullptr;
}
}
return ABORT_NONE;
}
bool AnimationSequence::step() {
Screen &screen = Screen::getReference();
if (_pPixels >= _pPixelsEnd) return false;
if (_isEGA)
egaDecodeFrame(_pPixels);
else {
if (_pLines >= _pLinesEnd) return false;
vgaDecodeFrame(_pPixels, _pLines);
}
// Make the decoded frame visible
screen.update();
screen.setPalette(&_palette);
return true;
}
byte *AnimationSequence::showInitialScreen(byte *pSrc) {
Screen &screen = Screen::getReference();
if (_isEGA) {
// Load the screen - each four bytes contain the four planes
// worth of data for 8 sequential pixels
byte *pDest = screen.screen().data().data() +
(FULL_SCREEN_WIDTH * MENUBAR_Y_SIZE);
for (int ctr = 0; ctr < FULL_SCREEN_WIDTH * (FULL_SCREEN_HEIGHT -
MENUBAR_Y_SIZE) / 8; ++ctr, pDest += EGA_PIXELS_PER_BYTE) {
for (int planeCtr = 0; planeCtr < EGA_NUM_LAYERS; ++planeCtr, ++pSrc) {
byte v = *pSrc;
for (int bitCtr = 0; bitCtr < 8; ++bitCtr, v <<= 1) {
if ((v & 0x80) != 0)
*(pDest + bitCtr) |= 1 << planeCtr;
}
}
}
} else {
screen.screen().data().copyFrom(_decodedData, 0, 0, FULL_SCREEN_HEIGHT * FULL_SCREEN_WIDTH);
}
screen.update();
return pSrc;
}
} // End of namespace Lure