scummvm/engines/kyra/sequence/seqplayer_eob_segacd.cpp
2022-09-18 21:59:23 +02:00

624 lines
18 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/>.
*
*/
#ifdef ENABLE_EOB
#include "kyra/engine/eob.h"
#include "kyra/graphics/screen_eob.h"
#include "kyra/graphics/screen_eob_segacd.h"
#include "kyra/resource/resource.h"
#include "kyra/resource/resource_segacd.h"
#include "kyra/sequence/seqplayer_eob_segacd.h"
#include "common/system.h"
namespace Kyra {
SegaSequencePlayer::SegaSequencePlayer(EoBEngine *vm, Screen_EoB *screen, SegaCDResource *res) : _vm(vm), _screen(screen), _res(res), _tileSets(0), _debugResyncCnt(0), _speechAnimType(0),
_playingID(1), _waterdeepScene(0), _playSpeechAnimation(0), _frameTimer(0), _waterdeepSceneTimer(0), _speechAnimTimer(0), _speechAnimNo(0), _speechAnimFrame(0),
_newTrack(-1), _pauseStart(0), _fastForward(false), _renderer(_screen->sega_getRenderer()), _animator(_screen->sega_getAnimator()) {
#define SQOPC(x) _opcodes.push_back(new SQOpcode(this, &SegaSequencePlayer::x, #x))
SQOPC(s_initDrawObject);
SQOPC(s_drawTileSet);
SQOPC(s_loadTileDataSingle);
SQOPC(s_drawTileSetCustom);
SQOPC(s_drawTileSetCustomTopToBottom);
SQOPC(s_fillRect);
SQOPC(s_void);
SQOPC(s_initSprite);
SQOPC(s_removeSprite);
SQOPC(s_displayTextJp);
SQOPC(s_fadeToNeutral);
SQOPC(s_fadeToBlack);
SQOPC(s_fadeToNeutral2);
SQOPC(s_fadeToWhite);
SQOPC(s_setPalette);
SQOPC(s_vScroll);
SQOPC(s_hScroll);
SQOPC(s_paletteOps);
SQOPC(s_initSpriteCustomCoords);
SQOPC(s_fillRectWithPattern);
SQOPC(s_loadTileDataSeries);
SQOPC(s_drawTileSetSeries);
SQOPC(s_initSpriteSeries);
SQOPC(s_initSpriteCustom);
SQOPC(s_drawTileSetCustomCoords);
SQOPC(s_waitForPaletteFade);
SQOPC(s_clearSprites);
SQOPC(s_moveSprites2);
SQOPC(s_moveSprites);
SQOPC(s_moveMorphSprite);
SQOPC(s_unpauseCD);
SQOPC(s_toggleWaterDeepAnimations);
SQOPC(s_assignSpeechAnimGraphics);
SQOPC(s_toggleSpeechAnimation);
SQOPC(s_orbZoomEffect);
SQOPC(s_stopCD);
SQOPC(s_playCD);
SQOPC(s_displayTextEn);
SQOPC(s_loadCustomPalettes);
SQOPC(s_playSoundEffect);
#undef SQOPC
_scrollManager = new ScrollManager(_renderer);
assert(_scrollManager);
_tileSets = new TileSet[100]();
assert(_tileSets);
_drawObjects = new DrawObject[100]();
assert(_drawObjects);
memset(_speechAnimDrawOps, 0, sizeof(_speechAnimDrawOps));
_scaleSrcBuffer = new uint8[0x5800]();
assert(_scaleSrcBuffer);
_scaleOutBuffer = new uint8[0x5800]();
assert(_scaleOutBuffer);
_scaleStampMap = new uint16[0x100]();
assert(_scaleStampMap);
_scaleTraceVectors = new uint16[0x580]();
assert(_scaleTraceVectors);
int temp;
_wdDsX = _vm->staticres()->loadRawDataBe16(kEoB1IntroWdDsX, temp);
_wdDsY = _vm->staticres()->loadRawData(kEoB1IntroWdDsY, temp);
_wdAnimSprites = _vm->staticres()->loadRawData(kEoB1WdAnimSprites, temp);
_speechAnimData = _vm->staticres()->loadRawData(kEoB1SpeechAnimData, temp);
_cdaTracks = _vm->staticres()->loadRawData(kEoB1SequenceTrackMap, temp);
for (int i = 0; i < 6; ++i)
_patternTables[i] = _vm->staticres()->loadRawDataBe16(kEoB1PatternTable0 + i, temp);
}
SegaSequencePlayer::~SegaSequencePlayer() {
delete[] _drawObjects;
delete[] _tileSets;
delete[] _scaleSrcBuffer;
delete[] _scaleOutBuffer;
delete[] _scaleStampMap;
delete[] _scaleTraceVectors;
for (Common::Array<SQOpcode*>::iterator i = _opcodes.begin(); i != _opcodes.end(); ++i)
delete (*i);
delete _scrollManager;
}
bool SegaSequencePlayer::play(int id) {
_renderer->render(0);
_screen->sega_fadeToBlack(2);
_screen->clearPage(0);
_animator->clearSprites();
_scrollManager->setVScrollTimers(0, 1, 0, 0, 1, 0);
_scrollManager->setHScrollTimers(0, 1, 0, 0, 1, 0);
_vm->_txt->clearDim(2);
_renderer->fillRectWithTiles(2, 0, 0, 40, 28, 0xE6C2);
_renderer->fillRectWithTiles(0, 0, 0, 64, 42, 0);
_renderer->fillRectWithTiles(1, 0, 0, 64, 28, 0);
_renderer->fillRectWithTiles(2, 1, (id == 53 || id == 54) ? 22 : 20, 38, 6, 0xE51C, true);
_debugResyncCnt = 0;
_playingID = id;
_newTrack = -1;
_vm->_allowSkip = true;
_vm->resetSkipFlag();
for (bool runLoop = true; runLoop && !(_vm->shouldQuit() || _vm->skipFlag()); ) {
uint32 offset = (id - 1) * 0x3C000;
if (id >= 54)
offset -= 0x8000;
if (id >= 55)
offset -= 0x18000;
uint32 size = (id == 53) ? 0x34000 : ((id == 54) ? 0x24000 : 0x3C000);
if (!_res->loadContainer("VISUAL", offset, size))
return false;
Common::SeekableReadStreamEndian *in = _res->resStreamEndian(0);
if (!in)
return false;
_screen->sega_loadCustomPaletteData(in);
delete in;
_screen->sega_selectPalette(31, 0, false);
_screen->sega_selectPalette(32, 1, false);
_screen->sega_selectPalette(30, 3, false);
in = _res->resStreamEndian(2);
if (!in)
return false;
uint32 len = in->size();
uint8 *tileData = new uint8[len];
in->read(tileData, len);
delete in;
in = _res->resStreamEndian(1);
if (!in)
return false;
memset(_tileSets, 0, 100 * sizeof(TileSet));
for (TileSet *t = _tileSets; !in->eos(); ++t) {
uint32 off = in->readUint32();
if ((off & 0xFFFF) == 0xFFFF)
break;
t->data = (const uint16*)(tileData + off);
t->width = in->readUint16();
t->height = in->readUint16();
}
delete in;
in = _res->resStreamEndian(3);
if (!in)
return false;
len = in->size();
uint8 *frames = new uint8[len];
in->read(frames, len);
delete in;
if (id == 53)
_vm->delay(2000);
else if (id == 55 || id == 56)
_vm->snd_playSong(2);
run(frames);
delete[] frames;
delete[] tileData;
if (++id != 54)
runLoop = false;
}
debugC(3, kDebugLevelSequence, "Total millis out of sync: %d", _debugResyncCnt);
if (_vm->shouldQuit() || _vm->skipFlag()) {
if (!(_playingID == 55 || _playingID == 56))
_vm->snd_stopSound();
}
_vm->_allowSkip = false;
_vm->resetSkipFlag();
_playingID = 1;
return true;
}
void SegaSequencePlayer::pause(bool togglePause) {
if (togglePause)
_pauseStart = _vm->_system->getMillis();
else
_frameTimer += (_vm->_system->getMillis() - _pauseStart);
}
void SegaSequencePlayer::run(const uint8 *data) {
_waterdeepScene = _playSpeechAnimation = false;
_frameTimer = _vm->_system->getMillis();
_fastForward = false;
for (bool runLoop = true; runLoop; ) {
// In case of a skip or shouldQuit event we fastforward through the sequence instead of just aborting. This way
// we ensure that we have the same palettes and scroll offsets as if the sequence had been played normally.
_fastForward = _vm->shouldQuit() || _vm->skipFlag();
uint16 frameSize = READ_BE_UINT16(data);
if (!frameSize)
return;
uint32 frameStart = _vm->_system->getMillis();
uint16 timeStamp = READ_BE_UINT16(data + 2);
uint32 nextFrame = _frameTimer + (timeStamp * 16667) / 1000;
bool insertDelay = false;
if (_vm->_system->getMillis() >= nextFrame || _fastForward) {
debugC(5, kDebugLevelSequence, "SeqPlayer: Timestamp %08d", timeStamp);
for (uint16 timeStamp2 = timeStamp; timeStamp2 == timeStamp; ) {
uint16 op = READ_BE_UINT16(data + 4);
_opcodes[op]->run(data + 6);
_screen->clearPage(0);
frameSize = READ_BE_UINT16(data);
data += (frameSize & ~1);
timeStamp2 = READ_BE_UINT16(data + 2);
}
} else {
insertDelay = true;
}
if (_waterdeepScene)
animateWaterdeepScene();
if (_playSpeechAnimation)
updateSpeechAnimations();
_animator->update();
if (_fastForward) {
_scrollManager->fastForward();
} else {
_scrollManager->updateScrollTimers();
_renderer->render(0);
_screen->sega_updatePaletteFaders(-1);
_screen->updateScreen();
}
if (insertDelay) {
int diff = _vm->_system->getMillis() - (frameStart + 16);
if (diff < 0)
_vm->delay((uint32)-diff);
}
}
}
void SegaSequencePlayer::animateWaterdeepScene() {
if (--_waterdeepSceneTimer > 0)
return;
_waterdeepSceneTimer = 5;
for (int i = 0; i < 5; ++i) {
int rnd = _vm->_rnd.getRandomNumber(30);
DrawObject *d = &_drawObjects[10 + rnd];
_animator->initSprite(_wdAnimSprites[rnd] + 3, _wdDsX[_wdAnimSprites[rnd]] - 80, _wdDsY[_wdAnimSprites[rnd]] + 32, d->nTblVal, d->addr);
}
}
void SegaSequencePlayer::updateSpeechAnimations() {
if (--_speechAnimTimer > 0)
return;
int animDrawOp = -1;
for (bool runLoop = true; runLoop; ) {
if (_speechAnimTimer == 0) {
const uint8 *pos = &_speechAnimData[_speechAnimNo * 12 + _speechAnimFrame];
_speechAnimTimer = pos[1];
if (pos[0] != 0xFF) {
animDrawOp = pos[0];
runLoop = false;
}
_speechAnimFrame += 2;
} else {
_speechAnimTimer = 0;
}
if (animDrawOp == -1) {
_speechAnimNo = (_speechAnimType == 2) ? _vm->_rnd.getRandomNumberRng(4, 6) : _vm->_rnd.getRandomNumberRng(0, 3);
_speechAnimFrame = 0;
}
}
updateSpeechAnimGraphics(animDrawOp);
}
void SegaSequencePlayer::updateSpeechAnimGraphics(int animDrawOp) {
assert(animDrawOp < 6);
DrawObject *d = &_drawObjects[_speechAnimDrawOps[animDrawOp * 2]];
if (_speechAnimDrawOps[animDrawOp * 2 + 1])
_renderer->loadToVRAM(d->tileData, (d->width * d->height) << 5, (d->nTblVal & 0x7FF) << 5);
else
_renderer->fillRectWithTiles(d->addr, d->x, d->y, d->width, d->height, d->nTblVal, true);
}
#define ARG(x) READ_BE_UINT16(pos + x)
#define S_ARG(x) (int16)ARG(x)
void SegaSequencePlayer::s_initDrawObject(const uint8 *pos) {
int idx = ARG(0);
DrawObject *w = &_drawObjects[idx];
TileSet *t = &_tileSets[idx];
w->agg = ARG(2);
w->tileData = t->data;
w->width = t->width;
w->height = t->height;
w->nTblVal = ARG(4) == 0xFFFF ? _drawObjects[idx - 1].width * _drawObjects[idx - 1].height + _drawObjects[idx - 1].nTblVal : (ARG(4) == 0xFFFE ? _drawObjects[idx - 1].nTblVal : ARG(4));
w->x = ARG(6);
w->y = ARG(8);
w->addr = ARG(10);
}
void SegaSequencePlayer::s_drawTileSet(const uint8 *pos) {
DrawObject *w = &_drawObjects[ARG(0)];
_renderer->fillRectWithTiles(w->addr, w->x, w->y, w->width, w->height, w->nTblVal, true);
}
void SegaSequencePlayer::s_loadTileDataSingle(const uint8 *pos) {
DrawObject *w = &_drawObjects[ARG(0)];
_renderer->loadToVRAM(w->tileData, (w->width * w-> height) << 5, (w->nTblVal & 0x7FF) << 5);
}
void SegaSequencePlayer::s_drawTileSetCustom(const uint8 *pos) {
_renderer->fillRectWithTiles(ARG(8), ARG(0), ARG(2), ARG(4), ARG(6), ARG(10), true);
}
void SegaSequencePlayer::s_drawTileSetCustomTopToBottom(const uint8 *pos) {
_renderer->fillRectWithTiles(ARG(8), ARG(0), ARG(2), ARG(4), ARG(6), ARG(10), true, true);
}
void SegaSequencePlayer::s_fillRect(const uint8 *pos) {
_renderer->fillRectWithTiles(ARG(8), ARG(0), ARG(2), ARG(4), ARG(6), ARG(10));
}
void SegaSequencePlayer::s_initSprite(const uint8 *pos) {
DrawObject *d = &_drawObjects[ARG(2)];
_animator->initSprite(ARG(0), d->x << 3, d->y << 3, d->nTblVal, d->addr);
}
void SegaSequencePlayer::s_removeSprite(const uint8 *pos) {
_animator->initSprite(ARG(0), 0x4000, 0, 0, 0);
}
void SegaSequencePlayer::s_displayTextJp(const uint8 *pos) {
if (_vm->gameFlags().lang != Common::JA_JPN)
return;
const char *str = (const char*)pos;
_vm->_txt->clearDim(2);
int w = _screen->getTextWidth(str);
int x = 0;
int y = 0;
if (w < 288) {
x = 152 - (w >> 1);
y = 16;
}
_vm->_txt->printShadedText(str, x, y, -1, 0xEE);
}
void SegaSequencePlayer::s_fadeToNeutral(const uint8 *pos) {
_screen->sega_fadeToNeutral(ARG(0));
}
void SegaSequencePlayer::s_fadeToBlack(const uint8 *pos) {
_screen->sega_fadeToBlack(ARG(0));
}
void SegaSequencePlayer::s_fadeToNeutral2(const uint8 *pos) {
_screen->sega_fadeToNeutral(ARG(0));
}
void SegaSequencePlayer::s_fadeToWhite(const uint8 *pos) {
_screen->sega_fadeToWhite(ARG(0));
}
void SegaSequencePlayer::s_setPalette(const uint8 *pos) {
_screen->sega_selectPalette(ARG(2) + 31, ARG(0), false);
}
void SegaSequencePlayer::s_vScroll(const uint8 *pos) {
_scrollManager->setVScrollTimers(ARG(0), S_ARG(2), ARG(4), ARG(6), S_ARG(8), ARG(10));
}
void SegaSequencePlayer::s_hScroll(const uint8 *pos) {
_scrollManager->setHScrollTimers(ARG(0), S_ARG(2), ARG(4), ARG(6), S_ARG(8), ARG(10));
}
void SegaSequencePlayer::s_paletteOps(const uint8 *pos) {
_screen->sega_paletteOps(S_ARG(0), S_ARG(2), S_ARG(4));
}
void SegaSequencePlayer::s_initSpriteCustomCoords(const uint8 *pos) {
DrawObject *d = &_drawObjects[ARG(2)];
_animator->initSprite(ARG(0), S_ARG(4), S_ARG(6), d->nTblVal, d->addr);
}
void SegaSequencePlayer::s_fillRectWithPattern(const uint8 *pos) {
assert(ARG(12) < 6);
_renderer->fillRectWithTiles(ARG(8), ARG(0), ARG(2), ARG(4), ARG(6), ARG(10), false, false, _patternTables[ARG(12)]);
}
void SegaSequencePlayer::s_loadTileDataSeries(const uint8 *pos) {
for (DrawObject *d = &_drawObjects[ARG(0)]; d != &_drawObjects[ARG(0) + ARG(2)]; ++d)
_renderer->loadToVRAM(d->tileData, (d->width * d->height) << 5, (d->nTblVal & 0x7FF) << 5);
}
void SegaSequencePlayer::s_drawTileSetSeries(const uint8 *pos) {
for (DrawObject *d = &_drawObjects[ARG(0)]; d != &_drawObjects[ARG(0) + ARG(2)]; ++d)
_renderer->fillRectWithTiles(d->addr, d->x, d->y, d->width, d->height, d->nTblVal, true);
}
void SegaSequencePlayer::s_initSpriteSeries(const uint8 *pos) {
int id = ARG(0);
for (DrawObject *d = &_drawObjects[ARG(2)]; d != &_drawObjects[ARG(2) + ARG(4)]; ++d)
_animator->initSprite(id++, d->x << 3, d->y << 3, d->nTblVal, d->addr);
}
void SegaSequencePlayer::s_initSpriteCustom(const uint8 *pos) {
_animator->initSprite(ARG(0), S_ARG(4), S_ARG(6), ARG(2), ARG(8));
}
void SegaSequencePlayer::s_drawTileSetCustomCoords(const uint8 *pos) {
DrawObject *w = &_drawObjects[ARG(0)];
_renderer->fillRectWithTiles(w->addr, ARG(2), ARG(4), w->width, w->height, w->nTblVal, true);
}
void SegaSequencePlayer::s_waitForPaletteFade(const uint8*) {
_screen->sega_fadePalette(0, 0, -1, true, true);
}
void SegaSequencePlayer::s_clearSprites(const uint8*) {
_animator->clearSprites();
}
void SegaSequencePlayer::s_moveSprites2(const uint8 *pos) {
_animator->moveSprites2(ARG(0), ARG(2), S_ARG(4), S_ARG(6));
}
void SegaSequencePlayer::s_moveSprites(const uint8 *pos) {
_animator->moveSprites(ARG(0), ARG(2), S_ARG(4), S_ARG(6));
}
void SegaSequencePlayer::s_moveMorphSprite(const uint8 *pos) {
_animator->moveMorphSprite(ARG(0), ARG(2), S_ARG(4), S_ARG(6));
}
void SegaSequencePlayer::s_unpauseCD(const uint8 *pos) {
// We don't support this in our AudioCD API. The original will use s_playCD() to seek to a track,
// wait for the seek to finish and then pause. It then uses this opcode to actually start the playback.
// Since s_playCD() and s_unpauseCD() are not always called on the same frame I emulate the original
// behavior like this.
if (_newTrack != -1)
_vm->snd_playSong(_newTrack, false);
_newTrack = -1;
}
void SegaSequencePlayer::s_toggleWaterDeepAnimations(const uint8 *pos) {
_waterdeepScene = ARG(0);
_waterdeepSceneTimer = 0;
}
void SegaSequencePlayer::s_assignSpeechAnimGraphics(const uint8 *pos) {
if (ARG(0) == 100) {
_speechAnimType = ARG(2);
} else {
assert(ARG(0) < 6);
_speechAnimDrawOps[ARG(0) * 2] = ARG(2);
_speechAnimDrawOps[ARG(0) * 2 + 1] = ARG(4);
}
}
void SegaSequencePlayer::s_toggleSpeechAnimation(const uint8 *pos) {
_playSpeechAnimation = ARG(0);
_speechAnimTimer = 0;
if (_playSpeechAnimation)
updateSpeechAnimGraphics(0);
}
void SegaSequencePlayer::s_orbZoomEffect(const uint8*) {
_renderer->memsetVRAM(0x2AA0, 0, 0x5800);
DrawObject *d = &_drawObjects[16];
memset(_scaleSrcBuffer, 0, 0x5800);
memcpy(_scaleSrcBuffer + 128, d->tileData, (d->width * d->height) << 5);
_renderer->fillRectWithTiles(0, 4, 0, 32, 22, 0x2155, true, true);
memset(_scaleStampMap, 0, 0x100 * sizeof(uint16));
uint16 *dst2 = &_scaleStampMap[(7 << 4) + 6];
uint16 t = 1;
for (int h = 0; h < 9; ++h) {
uint16 *dst = dst2;
for (int w = 0; w < 10; ++w)
*dst++ = t++;
dst2 += 16;
}
int step = 512;
for (int i = 0; i < 90; ++i) {
uint32 nextFrame = _vm->_system->getMillis() + 64;
uint16 *dst = _scaleTraceVectors;
uint32 xtr = 0x58000 - step * 128;
uint32 ytr = 0x59000 - step * 88;
for (int ii = 0; ii < 176; ++ii) {
*dst++ = xtr >> 8;
*dst++ = ytr >> 8;
*dst++ = step;
*dst++ = 0;
ytr += step;
}
memset(_scaleOutBuffer, 0, 0x5800);
_screen->sega_gfxScale(_scaleOutBuffer, 256, 176, 21, _scaleSrcBuffer, _scaleStampMap, _scaleTraceVectors);
_renderer->loadToVRAM(_scaleOutBuffer, 0x5800, 0x2AA0);
if (!_fastForward) {
_renderer->render(0);
_screen->updateScreen();
_vm->delayUntil(nextFrame);
}
step += 16;
}
}
void SegaSequencePlayer::s_stopCD(const uint8*) {
_vm->snd_stopSound();
}
void SegaSequencePlayer::s_playCD(const uint8 *pos) {
int track = _cdaTracks[ARG(0)];
// The original seeks to the requested CD track here and pauses it.
// The actual playback is then triggered via s_unpauseCD().
if (track)
_newTrack = track;
_vm->snd_stopSound();
}
void SegaSequencePlayer::s_displayTextEn(const uint8 *pos) {
if (_vm->gameFlags().lang == Common::JA_JPN)
return;
const char *str = (const char*)pos;
_vm->_txt->clearDim(2);
if (_playingID >= 55) {
int cs = _screen->setFontStyles(_screen->_currentFont, Font::kStyleFullWidth);
_vm->_txt->printShadedText(str, 0, 0, -1, 0xEE);
_screen->setFontStyles(_screen->_currentFont, cs);
} else {
int x = 0;
int y = 0;
if (_playingID >= 53) {
x = 152 - (_screen->getTextWidth(str) >> 1);
y = 16;
}
_vm->_txt->printShadedText(str, x, y, -1, 0xEE);
}
}
void SegaSequencePlayer::s_loadCustomPalettes(const uint8 *pos) {
Common::SeekableReadStreamEndian *in = _res->resStreamEndian(0);
in->seek(ARG(0) << 5);
_screen->sega_loadCustomPaletteData(in);
delete in;
}
void SegaSequencePlayer::s_playSoundEffect(const uint8 *pos) {
if (!_fastForward)
_vm->snd_playSoundEffect(ARG(0));
}
#undef S_ARG
#undef ARG
} // End of namespace Kyra
#endif // ENABLE_EOB