mirror of
https://github.com/scummvm/scummvm.git
synced 2025-04-02 10:52:32 -04:00
641 lines
19 KiB
C++
641 lines
19 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 "dgds/head.h"
|
|
#include "dgds/dgds.h"
|
|
#include "dgds/image.h"
|
|
#include "dgds/includes.h"
|
|
#include "dgds/sound_raw.h"
|
|
#include "dgds/scene.h"
|
|
#include "dgds/dialog.h"
|
|
#include "dgds/ads.h"
|
|
|
|
#include "graphics/cursorman.h"
|
|
|
|
namespace Dgds {
|
|
|
|
ImageFlipMode Conversation::_lastHeadFrameFlipMode = kImageFlipNone;
|
|
int16 Conversation::_lastHeadFrameX = 0;
|
|
int16 Conversation::_lastHeadFrameY = 0;
|
|
|
|
void TalkDataHead::drawHead(Graphics::ManagedSurface &dst, const TalkData &data) const {
|
|
uint drawtype = _drawType ? _drawType : 1;
|
|
// Use specific head shape if available (eg, in Willy Beamish), if not use talk data shape
|
|
Common::SharedPtr<Image> img = _shape;
|
|
if (!img)
|
|
img = data._shape;
|
|
|
|
switch (drawtype) {
|
|
case 1:
|
|
if (img)
|
|
drawHeadType1(dst, *img);
|
|
break;
|
|
case 2:
|
|
if (img)
|
|
drawHeadType2(dst, *img);
|
|
break;
|
|
case 3:
|
|
if (DgdsEngine::getInstance()->getGameId() == GID_WILLY)
|
|
drawHeadType3Beamish(dst, data);
|
|
else if (img)
|
|
drawHeadType3(dst, *img);
|
|
break;
|
|
default:
|
|
error("Unsupported head draw type %d", drawtype);
|
|
}
|
|
}
|
|
|
|
void TalkDataHead::drawHeadType1(Graphics::ManagedSurface &dst, const Image &img) const {
|
|
Common::Rect r = _rect.toCommonRect();
|
|
dst.fillRect(r, _drawCol);
|
|
r.grow(-1);
|
|
dst.fillRect(r, _drawCol == 0 ? 15 : 0);
|
|
r.left += 2;
|
|
r.top += 2;
|
|
const int x = _rect.x;
|
|
const int y = _rect.y;
|
|
if (img.isLoaded()) {
|
|
for (const auto &frame : _headFrames) {
|
|
img.drawBitmap(frame._frameNo & 0xff, x + frame._xoff, y + frame._yoff, r, dst);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TalkDataHead::drawHeadType2(Graphics::ManagedSurface &dst, const Image &img) const {
|
|
if (!img.isLoaded())
|
|
return;
|
|
const Common::Rect r = _rect.toCommonRect();
|
|
for (const auto &frame : _headFrames) {
|
|
img.drawBitmap(frame._frameNo & 0xff, r.left + frame._xoff, r.top + frame._yoff, r, dst);
|
|
}
|
|
}
|
|
|
|
void TalkDataHead::drawHeadType3Beamish(Graphics::ManagedSurface &dst, const TalkData &data) const {
|
|
const Common::Rect r = _rect.toCommonRect();
|
|
Common::Rect fillRect(r);
|
|
fillRect.grow(-1);
|
|
|
|
bool bgDone = false;
|
|
|
|
for (const auto &frame : _headFrames) {
|
|
int frameNo = frame._frameNo & 0x7fff;
|
|
bool useHeadShape = frame._frameNo & 0x8000;
|
|
|
|
Common::SharedPtr<Image> img = useHeadShape ? _shape : data._shape;
|
|
|
|
if (!bgDone && img) {
|
|
dst.frameRect(r, 8);
|
|
dst.fillRect(fillRect, _drawCol);
|
|
bgDone = true;
|
|
}
|
|
|
|
ImageFlipMode flip = kImageFlipNone;
|
|
// Yes, the numerical values are reversed here (1 -> 2 and 2 -> 1).
|
|
// The head flip flags are reversed from the image draw flags.
|
|
if (frame._flipFlags & 1)
|
|
flip = static_cast<ImageFlipMode>(flip | kImageFlipH);
|
|
if (frame._flipFlags & 2)
|
|
flip = static_cast<ImageFlipMode>(flip | kImageFlipV);
|
|
|
|
// Slight hack from original CD version - record the flip mode and offset
|
|
// for this frame and use it for drawing the head sprites in the script.
|
|
if (flip != Conversation::_lastHeadFrameFlipMode)
|
|
debug(10, "Changing CDS head flip mode %d -> %d", Conversation::_lastHeadFrameFlipMode, flip);
|
|
Conversation::_lastHeadFrameFlipMode = flip;
|
|
Conversation::_lastHeadFrameX = frame._xoff;
|
|
Conversation::_lastHeadFrameY = frame._yoff;
|
|
|
|
if (!img || !img->isLoaded() || frameNo >= img->loadedFrameCount())
|
|
continue;
|
|
|
|
img->drawBitmap(frameNo, r.left + frame._xoff, r.top + frame._yoff, fillRect, dst);
|
|
}
|
|
}
|
|
|
|
void TalkDataHead::drawHeadType3(Graphics::ManagedSurface &dst, const Image &img) const {
|
|
Common::Rect r = _rect.toCommonRect();
|
|
dst.fillRect(r, 0);
|
|
if (!img.isLoaded())
|
|
return;
|
|
for (const auto &frame : _headFrames) {
|
|
int frameNo = frame._frameNo;
|
|
if (frameNo < img.loadedFrameCount())
|
|
img.drawBitmap(frameNo, r.left + frame._xoff, r.top + frame._yoff, r, dst);
|
|
else
|
|
dst.fillRect(r, 4);
|
|
}
|
|
}
|
|
|
|
void TalkDataHead::clearHead() {
|
|
warning("TODO: Clear head");
|
|
_flags = static_cast<HeadFlags>(_flags & ~(kHeadFlagFinished | kHeadFlag8 | kHeadFlag10 | kHeadFlagVisible));
|
|
|
|
/* This seems to just be a "needs redraw" flag, but we always redraw
|
|
for (auto tds : _talkData) {
|
|
for (auto h : tds._heads) {
|
|
if ((h._flags & kHeadFlagVisible) && !(h._flags & (kHeadFlag8 | kHeadFlag10 | kHeadFlag80))) {
|
|
if (h._rect.toCommonRect().intersects(head._rect.toCommonRect())) {
|
|
h._flags = static_cast<HeadFlags>(h._flags | kHeadFlag4);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
}
|
|
|
|
|
|
void TalkData::clearVisibleHeads() {
|
|
for (auto &head : _heads) {
|
|
if (head._flags & kHeadFlagVisible)
|
|
head.clearHead();
|
|
}
|
|
}
|
|
|
|
void TalkData::drawAndUpdateVisibleHeads(Graphics::ManagedSurface &dst) {
|
|
for (auto &h : _heads) {
|
|
if (h._flags & kHeadFlagVisible) {
|
|
if (!(h._flags & kHeadFlagOpening)) {
|
|
h.drawHead(dst, *this);
|
|
} else {
|
|
h._flags = static_cast<HeadFlags>(h._flags & ~kHeadFlagOpening);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool TalkData::hasVisibleHead() const {
|
|
for (const auto &h : _heads) {
|
|
if (h._flags & kHeadFlagVisible && !(h._flags & kHeadFlagOpening))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
CDSTTMInterpreter::CDSTTMInterpreter(DgdsEngine *vm) : TTMInterpreter(vm) {
|
|
_storedAreaBuffer.create(SCREEN_WIDTH, SCREEN_HEIGHT, Graphics::PixelFormat::createFormatCLUT8());
|
|
}
|
|
|
|
|
|
void CDSTTMInterpreter::handleOperation(TTMEnviro &env_, TTMSeq &seq, uint16 op, byte count, const int16 *ivals, const Common::String &sval, const Common::Array<Common::Point> &pts) {
|
|
CDSTTMEnviro &env = static_cast<CDSTTMEnviro &>(env_);
|
|
|
|
switch (op) {
|
|
case 0x0080: // FREE SHAPE
|
|
env._scriptShapes[0].reset();
|
|
break;
|
|
case 0x0110: // PURGE void
|
|
break;
|
|
case 0x0FF0: // REFRESH
|
|
break;
|
|
case 0x1020: { // SET DELAY: i:int [0..n]
|
|
// TODO: Probably should do this accounting (as well as timeCut and dialogs)
|
|
// in game frames, not millis.
|
|
int16 delayMillis = (int16)round(ivals[0] * MS_PER_FRAME);
|
|
env._cdsDelay = MAX(env._cdsDelay, delayMillis);
|
|
break;
|
|
}
|
|
case 0x1050: // SELECT BMP: id:int [0]
|
|
seq._currentBmpId = ivals[0];
|
|
break;
|
|
case 0x1060: // SELECT PAL: id:int [0]
|
|
seq._currentPalId = ivals[0];
|
|
break;
|
|
case 0x1100: // SET_SCENE: i:int [1..n]
|
|
case 0x1110: // SET_SCENE: i:int [1..n]
|
|
break;
|
|
case 0x2000: // SET (DRAW) COLORS: fgcol,bgcol:int [0..255]
|
|
seq._drawColFG = static_cast<byte>(ivals[0]); // aka Line Color
|
|
seq._drawColBG = static_cast<byte>(ivals[1]); // aka Fill Color
|
|
break;
|
|
case 0x3200: // CDS FIND GOTO TARGET frameno
|
|
seq._gotoFrame = findGOTOTarget(env, seq, ivals[0]);
|
|
break;
|
|
case 0x3300: // CDS GOSUB - first 2 args are ignored by original
|
|
if (!env._cdsJumped && seq._gotoFrame + ivals[2] != seq._currentFrame && seq._gotoFrame >= 0) {
|
|
env._cdsJumped = true;
|
|
int64 prevPos = env.scr->pos();
|
|
int16 currentFrame = seq._currentFrame;
|
|
env.scr->seek(env._frameOffsets[seq._gotoFrame + ivals[2]]);
|
|
seq._currentFrame = seq._gotoFrame;
|
|
run(env, seq);
|
|
seq._currentFrame = currentFrame;
|
|
env.scr->seek(prevPos);
|
|
env._cdsJumped = false;
|
|
}
|
|
break;
|
|
case 0x4200: { // STORE AREA: x,y,w,h:int [0..n] ; makes this area of foreground persist in the next frames.
|
|
if (env._cdsDidStoreArea) // this is a one-shot op
|
|
break;
|
|
|
|
if (env._storedAreaRect.width * 2 < (int)ivals[2]) {
|
|
env._storedAreaRect = DgdsRect(ivals[0] + env._xOff, ivals[1] + env._yOff, ivals[2], ivals[3]);
|
|
}
|
|
const Common::Rect rect = env._storedAreaRect.toCommonRect();
|
|
_storedAreaBuffer.blitFrom(_vm->_compositionBuffer, rect, rect);
|
|
env._cdsDidStoreArea = true;
|
|
break;
|
|
}
|
|
case 0xa500: // DRAW SPRITE: x,y,frameno,bmpno:int [-n,+n]
|
|
case 0xa510: // DRAW SPRITE FLIP V x,y:int
|
|
case 0xa520: // DRAW SPRITE FLIP H: x,y:int
|
|
case 0xa530: { // DRAW SPRITE FLIP HV: x,y,frameno,bmpno:int [-n,+n]
|
|
if (!env._scriptShapes[0]) {
|
|
warning("CDS: Trying to draw after script shape freed");
|
|
break;
|
|
}
|
|
ImageFlipMode flip = Conversation::_lastHeadFrameFlipMode;
|
|
int16 x = ivals[0] + env._xOff;
|
|
int16 y = ivals[1] + env._yOff;
|
|
int16 frameno = ivals[2];
|
|
int16 bmpNo = ivals[3];
|
|
Common::SharedPtr<Image> img = env._scriptShapes[bmpNo];
|
|
if (flip & kImageFlipH)
|
|
x = env._xOff + (env._scriptShapes[0]->width(0) - ivals[0] - img->width(frameno));
|
|
if (frameno >= img->loadedFrameCount()) {
|
|
warning("CDS script tried to draw frame %d but img only has %d", frameno, img->loadedFrameCount());
|
|
} else {
|
|
img->drawBitmap(frameno, x, y, seq._drawWin, _vm->_compositionBuffer, flip, img->width(frameno), img->height(frameno));
|
|
}
|
|
break;
|
|
}
|
|
case 0xc220: // PLAY RAW SFX
|
|
if (env._cdsPlayedSound) // this is a one-shot op
|
|
break;
|
|
if (!env._soundRaw) {
|
|
warning("TODO: Trying to play raw SFX but nothing loaded");
|
|
} else {
|
|
env._soundRaw->play();
|
|
env._cdsPlayedSound = true;
|
|
}
|
|
break;
|
|
case 0xc250: { // SYNC RAW SFX
|
|
uint16 hi = (uint16)ivals[1];
|
|
uint16 lo = (uint16)ivals[0];
|
|
uint32 offset = ((uint32)hi << 16) + lo;
|
|
// The offset is in bytes of the sample.
|
|
uint32 playedOffset = env._soundRaw->playedOffset();
|
|
if (playedOffset < offset) {
|
|
// Not played to this point yet. Add a delay until that point.
|
|
env._cdsDelay = MAX(env._cdsDelay, (int16)(1000 * (offset - playedOffset) / 11025));
|
|
debug(10, "CDS SYNC SFX: played %d wait for %d (%d ms)", playedOffset, offset, env._cdsDelay);
|
|
}
|
|
break;
|
|
}
|
|
case 0xa100: // DRAW FILLED RECT
|
|
case 0xa110: // DRAW EMPTY RECT x1,y1,x2,y2:int
|
|
// Appear in the scripts but not implemented in the original.
|
|
break;
|
|
case 0xc200: // ??? SFX: ??,?? - not implemented in willy, ignore?
|
|
case 0xc210: // LOAD RAW SFX: filename:str
|
|
case 0xf010: // LOAD SCR: filename:str
|
|
case 0xf020: // LOAD BMP: filename:str
|
|
case 0xf050: // LOAD PAL: filename:str
|
|
// Ignore all these in CDS scripts. The original implements them but only to load
|
|
// patch data outside of the CDS files. That data isn't present in the final
|
|
// games so we can just ignore the opcode.
|
|
break;
|
|
default:
|
|
if (count < 15)
|
|
warning("Unimplemented CDS TTM opcode: 0x%04X (%s, %d args) (ivals: %d %d %d %d)",
|
|
op, ttmOpName(op), count, ivals[0], ivals[1], ivals[2], ivals[3]);
|
|
else
|
|
warning("Unimplemented CDS TTM opcode: 0x%04X (%s, sval: %s)", op,
|
|
ttmOpName(op), sval.c_str());
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
Conversation::~Conversation() {
|
|
unloadData();
|
|
}
|
|
|
|
void Conversation::unloadData() {
|
|
debug(10, "CDS: Unloading data (%d,%d)", _dlgNum, _dlgFileNum);
|
|
_ttmScript.reset();
|
|
|
|
_ttmEnv._scriptShapes[0].reset();
|
|
if (_ttmEnv._soundRaw)
|
|
_ttmEnv._soundRaw->stop();
|
|
_ttmEnv = CDSTTMEnviro();
|
|
_loadState = 0;
|
|
_lastHeadFrameX = 0;
|
|
_lastHeadFrameY = 0;
|
|
}
|
|
|
|
void Conversation::clear() {
|
|
_dlgNum = -1;
|
|
_dlgFileNum = -1;
|
|
_subNum = -1;
|
|
_finished = false;
|
|
_haveHeadData = false;
|
|
_stopScript = false;
|
|
}
|
|
|
|
bool Conversation::isForDlg(const Dialog *dlg) const {
|
|
return dlg && dlg->_num == _dlgNum && dlg->_fileNum == _dlgFileNum;
|
|
}
|
|
|
|
void Conversation::loadData(uint16 dlgFileNum, uint16 dlgNum, int16 sub, bool haveHeadData) {
|
|
unloadData();
|
|
clear();
|
|
|
|
DgdsEngine *engine = DgdsEngine::getInstance();
|
|
|
|
// These files are only present in Willy Beamish CD version
|
|
if (engine->getGameId() != GID_WILLY)
|
|
return;
|
|
|
|
_dlgNum = dlgNum;
|
|
_dlgFileNum = dlgFileNum;
|
|
_subNum = sub;
|
|
_nextExecMs = 0;
|
|
_runTempFrame = -1;
|
|
_tempFrameNum = 0;
|
|
_thisFrameMs = 0;
|
|
_stopScript = false;
|
|
_haveHeadData = haveHeadData;
|
|
|
|
if (!haveHeadData)
|
|
_drawRect = DgdsRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
|
|
|
|
ResourceManager *resourceManager = engine->getResourceManager();
|
|
Decompressor *decompressor = engine->getDecompressor();
|
|
|
|
Common::String fname;
|
|
if (sub >= 0) {
|
|
assert(sub < 26);
|
|
fname = Common::String::format("F%dB%d%c.CDS", dlgFileNum, dlgNum, 'A' + sub);
|
|
} else {
|
|
fname = Common::String::format("F%dB%d.CDS", dlgFileNum, dlgNum);
|
|
}
|
|
|
|
if (!resourceManager->hasResource(fname))
|
|
return;
|
|
|
|
debug(10, "CDS: Load CDS resource %s", fname.c_str());
|
|
|
|
// The scripts are designed so the resources are patchable, but by default
|
|
// they use the sound and image data from the CDS file.
|
|
_ttmEnv._soundRaw.reset(new SoundRaw(resourceManager, decompressor));
|
|
_ttmEnv._soundRaw->load(fname);
|
|
_ttmEnv._scriptShapes[0].reset(new Image(resourceManager, decompressor));
|
|
_ttmEnv._scriptShapes[0]->loadBitmap(fname);
|
|
_ttmScript.reset(new CDSTTMInterpreter(engine));
|
|
_ttmScript->load(fname, _ttmEnv);
|
|
_ttmScript->findAndAddSequences(_ttmEnv, _ttmSeqs);
|
|
|
|
_loadState = 1;
|
|
|
|
// Then run START
|
|
for (const auto &tag : _ttmEnv._tags) {
|
|
if (tag._value.equalsIgnoreCase("START"))
|
|
_ttmEnv._cdsFrame = tag._key;
|
|
}
|
|
_tempFrameNum = _ttmScript->findGOTOTarget(_ttmEnv, *_ttmSeqs[0], _ttmEnv._cdsFrame);
|
|
_ttmEnv._cdsFrame = _tempFrameNum;
|
|
|
|
// Always run frame 1 on init.
|
|
runScriptFrame(1);
|
|
}
|
|
|
|
bool Conversation::runScriptFrame(int16 frameNum) {
|
|
if (!_ttmScript || frameNum >= _ttmEnv._totalFrames)
|
|
return false;
|
|
|
|
_ttmEnv.scr->seek(_ttmEnv._frameOffsets[frameNum]);
|
|
TTMSeq *seq = _ttmSeqs[0].get();
|
|
seq->_currentFrame = frameNum;
|
|
|
|
debug(10, "CDS: Running TTM sequence %d frame %d current millis %d", seq->_seqNum,
|
|
seq->_currentFrame, _thisFrameMs);
|
|
|
|
seq->_drawWin = _drawRect.toCommonRect();
|
|
return _ttmScript->run(_ttmEnv, *seq);
|
|
}
|
|
|
|
void Conversation::checkAndRunScript() {
|
|
if (!_ttmScript || _finished)
|
|
return;
|
|
|
|
DgdsEngine *engine = DgdsEngine::getInstance();
|
|
|
|
// Always add the stored buffer first. Copy the whole thing to overwrite other state.
|
|
engine->_compositionBuffer.transBlitFrom(_ttmScript->getStoredAreaBuffer());
|
|
|
|
if (_runTempFrame) {
|
|
runScriptFrame(_tempFrameNum);
|
|
}
|
|
runScriptFrame(_ttmEnv._cdsFrame);
|
|
if (_ttmEnv._cdsDelay > 0) {
|
|
_nextExecMs = _thisFrameMs + _ttmEnv._cdsDelay;
|
|
debug(10, "CDS: This fame %d. Next frame will be on or after %d", _thisFrameMs, _nextExecMs);
|
|
_ttmEnv._cdsDelay = -1;
|
|
} else {
|
|
_nextExecMs = 0;
|
|
}
|
|
}
|
|
|
|
void Conversation::incrementFrame() {
|
|
if (!_ttmScript)
|
|
return;
|
|
|
|
// TODO: check load type 2 here?
|
|
|
|
if (_loadState == 1 && _ttmEnv._scriptShapes[0] && _ttmEnv._scriptShapes[0]->loadedFrameCount() > 0) {
|
|
_ttmEnv._storedAreaRect.x = _drawRect.x;
|
|
_ttmEnv._storedAreaRect.y = _drawRect.y;
|
|
_ttmEnv._storedAreaRect.width = _ttmEnv._scriptShapes[0]->getFrames()[0]->w + 8;
|
|
_ttmEnv._storedAreaRect.height = _ttmEnv._scriptShapes[0]->getFrames()[0]->h;
|
|
_loadState = 2;
|
|
}
|
|
|
|
if (_runTempFrame)
|
|
_runTempFrame--;
|
|
|
|
if (!_nextExecMs || _thisFrameMs >= _nextExecMs) {
|
|
debug(10, "CDS: Increment frame %d -> %d", _ttmEnv._cdsFrame, _ttmEnv._cdsFrame + 1);
|
|
_ttmEnv._cdsFrame++;
|
|
}
|
|
}
|
|
|
|
bool Conversation::isScriptRunning() {
|
|
// HACK: Keep viewing blueprints from inventory running until
|
|
// interrupted.. other dialogs should stop when they
|
|
// have finished or their sounds or hit the end.
|
|
bool isBlueprints = (_dlgNum == 16 && _dlgFileNum == 67 && _subNum == -1);
|
|
|
|
return (_ttmScript &&
|
|
((isBlueprints || _ttmEnv._soundRaw->isPlaying())
|
|
||
|
|
(_ttmEnv._cdsFrame < _ttmEnv._totalFrames)
|
|
));
|
|
}
|
|
|
|
void Conversation::pumpMessages() {
|
|
Common::Event ev;
|
|
DgdsEngine *engine = DgdsEngine::getInstance();
|
|
|
|
while (engine->getEventManager()->pollEvent(ev)) {
|
|
if (ev.type == Common::EVENT_LBUTTONDOWN ||
|
|
ev.type == Common::EVENT_RBUTTONDOWN ||
|
|
ev.type == Common::EVENT_KEYDOWN) {
|
|
_stopScript = true;
|
|
engine->getScene()->setIgnoreMouseUp();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Conversation::runScript() {
|
|
if (!_ttmScript)
|
|
return;
|
|
|
|
//
|
|
// If we have head data, run the script and don't return until it's
|
|
// finished - stop all other activity.
|
|
//
|
|
// If not, just run the script at the same time as it's supposed to animate over
|
|
// the top of the other game movements.
|
|
//
|
|
if (_haveHeadData)
|
|
runScriptExclusive();
|
|
else
|
|
runScriptStep();
|
|
}
|
|
|
|
void Conversation::runScriptStep() {
|
|
DgdsEngine *engine = DgdsEngine::getInstance();
|
|
|
|
if (_runTempFrame == -1)
|
|
_runTempFrame = 2;
|
|
|
|
if (!isScriptRunning())
|
|
return;
|
|
|
|
_thisFrameMs = engine->getThisFrameMs();
|
|
if (!_nextExecMs || _nextExecMs <= _thisFrameMs) {
|
|
incrementFrame();
|
|
checkAndRunScript();
|
|
}
|
|
}
|
|
|
|
|
|
void Conversation::runScriptExclusive() {
|
|
DgdsEngine *engine = DgdsEngine::getInstance();
|
|
engine->disableKeymapper();
|
|
|
|
_nextExecMs = 0;
|
|
_drawRect.x += _lastHeadFrameX;
|
|
_drawRect.y += _lastHeadFrameY;
|
|
_ttmEnv._xOff = _drawRect.x;
|
|
_ttmEnv._yOff = _drawRect.y;
|
|
_runTempFrame = 2;
|
|
|
|
if (_drawRect.width == 0) {
|
|
_drawRect.width = SCREEN_WIDTH;
|
|
_drawRect.height = SCREEN_HEIGHT;
|
|
}
|
|
|
|
int frameCount = 0;
|
|
uint32 startMillis = g_system->getMillis();
|
|
|
|
DgdsEngine::dumpFrame(engine->_compositionBuffer, "cds-comp-before-script");
|
|
|
|
engine->getScene()->checkDialogActive();
|
|
// update here to make sure the dialog gets drawn.
|
|
engine->getScene()->drawAndUpdateDialogs(&engine->_compositionBuffer);
|
|
|
|
DgdsEngine::dumpFrame(engine->_compositionBuffer, "cds-comp-before-script-with-dlg");
|
|
|
|
_ttmScript->getStoredAreaBuffer().blitFrom(engine->_compositionBuffer);
|
|
|
|
CursorMan.showMouse(false);
|
|
|
|
g_system->copyRectToScreen(engine->_compositionBuffer.getPixels(), SCREEN_WIDTH, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
|
|
g_system->updateScreen();
|
|
|
|
// If we have head data we should dim the palette
|
|
engine->dimPalForWillyDialog(_haveHeadData);
|
|
|
|
debug(10, "CDS: Start execution of (%d,%d) - frame %d maxFrame %d", _dlgNum, _dlgFileNum,
|
|
_ttmEnv._cdsFrame, _ttmEnv._totalFrames);
|
|
|
|
while (isScriptRunning() && !engine->shouldQuit()) {
|
|
_thisFrameMs = g_system->getMillis();
|
|
pumpMessages();
|
|
if (_stopScript)
|
|
break;
|
|
|
|
if (!_nextExecMs || _nextExecMs <= _thisFrameMs) {
|
|
incrementFrame();
|
|
|
|
checkAndRunScript();
|
|
|
|
// Redraw active dialogs eg to make sure thought bubble dots are
|
|
// over the moving heads
|
|
engine->getScene()->drawAndUpdateDialogs(&engine->_compositionBuffer);
|
|
|
|
Common::Rect r = _drawRect.toCommonRect();
|
|
r.clip(Common::Rect(SCREEN_WIDTH, SCREEN_HEIGHT));
|
|
const byte *srcPtr = (const byte *)engine->_compositionBuffer.getPixels() + r.top * SCREEN_WIDTH + r.left;
|
|
g_system->copyRectToScreen(srcPtr, SCREEN_WIDTH, r.left, r.top, r.width(), r.height());
|
|
}
|
|
|
|
g_system->updateScreen();
|
|
|
|
frameCount++;
|
|
// Limit to 15 FPS
|
|
static const int framesPerSecond = 15;
|
|
uint32 thisFrameEndMillis = g_system->getMillis();
|
|
uint32 elapsedMillis = thisFrameEndMillis - startMillis;
|
|
const uint32 targetMillis = (frameCount * 1000 / framesPerSecond);
|
|
if (targetMillis > elapsedMillis) {
|
|
while (targetMillis > elapsedMillis) {
|
|
pumpMessages();
|
|
g_system->updateScreen();
|
|
g_system->delayMillis(5);
|
|
elapsedMillis = g_system->getMillis() - startMillis;
|
|
}
|
|
} else if (targetMillis < elapsedMillis) {
|
|
startMillis = thisFrameEndMillis;
|
|
frameCount = 0;
|
|
}
|
|
}
|
|
|
|
debug(10, "CDS: Finished execution of (%d,%d) - stop %s frame %d maxFrame %d", _dlgNum, _dlgFileNum,
|
|
_stopScript ? "true" : "false", _ttmEnv._cdsFrame, _ttmEnv._totalFrames);
|
|
|
|
CursorMan.showMouse(true);
|
|
engine->enableKeymapper();
|
|
if (_ttmEnv._soundRaw)
|
|
_ttmEnv._soundRaw->stop();
|
|
|
|
// If the dialog was cleared, set the force-clear flag in the scene. Otherwise, just set
|
|
// a flag the scene can check when updating dialogs.
|
|
if (_stopScript)
|
|
engine->getScene()->setShouldClearDlg();
|
|
else
|
|
_finished = true;
|
|
|
|
unloadData();
|
|
}
|
|
|
|
} // end namespace Dgds
|