Files
2026-02-02 04:50:13 +01:00

1436 lines
46 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 "common/rect.h"
#include "common/serializer.h"
#include "graphics/managed_surface.h"
#include "dgds/ttm.h"
#include "dgds/ads.h"
#include "dgds/dgds.h"
#include "dgds/game_palettes.h"
#include "dgds/includes.h"
#include "dgds/image.h"
#include "dgds/sound.h"
#include "dgds/font.h"
#include "dgds/sound_raw.h"
#include "dgds/drawing.h"
#include "dgds/scene.h"
namespace Dgds {
void GetPutRegion::reset() {
_area = Common::Rect(SCREEN_WIDTH, SCREEN_HEIGHT);
_surf.reset();
}
Common::Error TTMEnviro::syncState(Common::Serializer &s) {
DgdsEngine *engine = DgdsEngine::getInstance();
for (auto &shape : _scriptShapes) {
bool hasShape = shape.get() != nullptr;
s.syncAsByte(hasShape);
if (hasShape) {
Common::String name;
if (s.isLoading()) {
s.syncString(name);
shape.reset(new Image(engine->getResourceManager(), engine->getDecompressor()));
shape->loadBitmap(name);
} else {
name = shape->getFilename();
s.syncString(name);
}
}
}
uint16 ngetput = _getPuts.size();
s.syncAsUint16LE(ngetput);
_getPuts.resize(ngetput);
for (uint i = 0; i < ngetput; i++) {
s.syncAsUint16LE(_getPuts[i]._area.left);
s.syncAsUint16LE(_getPuts[i]._area.top);
s.syncAsUint16LE(_getPuts[i]._area.right);
s.syncAsUint16LE(_getPuts[i]._area.bottom);
if (s.isLoading()) {
_getPuts[i]._surf.reset(new Graphics::ManagedSurface());
} else {
// TODO: Save the getput buffer contents here?
}
}
for (uint i = 0; i < ARRAYSIZE(_scriptPals); i++)
s.syncAsSint32LE(_scriptPals[i]);
for (uint i = 0; i < ARRAYSIZE(_strings); i++)
s.syncString(_strings[i]);
// TODO: Save the font list.
return Common::kNoError;
}
Common::Error TTMSeq::syncState(Common::Serializer &s) {
s.syncAsSint16LE(_gotoFrame);
s.syncAsSint16LE(_currentFrame);
s.syncAsSint16LE(_lastFrame);
s.syncAsByte(_selfLoop);
s.syncAsByte(_executed);
s.syncAsUint32LE(_timeNext);
s.syncAsUint32LE(_timeCut);
s.syncAsUint16LE(_drawWin.left);
s.syncAsUint16LE(_drawWin.top);
s.syncAsUint16LE(_drawWin.right);
s.syncAsUint16LE(_drawWin.bottom);
s.syncAsSint16LE(_currentFontId);
s.syncAsSint16LE(_currentPalId);
s.syncAsSint16LE(_currentSongId);
s.syncAsSint16LE(_currentBmpId);
s.syncAsSint16LE(_currentGetPutId);
s.syncAsSint16LE(_brushNum);
s.syncAsByte(_drawColFG);
s.syncAsByte(_drawColBG);
s.syncAsSint16LE(_runPlayed);
s.syncAsSint16LE(_runCount);
s.syncAsSint16LE(_timeInterval);
s.syncAsUint32LE(_runFlag);
s.syncAsSint16LE(_scriptFlag);
return Common::kNoError;
}
TTMInterpreter::TTMInterpreter(DgdsEngine *vm) : _vm(vm), _stackDepth(0) {}
bool TTMInterpreter::load(const Common::String &filename, TTMEnviro &scriptData) {
TTMParser dgds(_vm->getResourceManager(), _vm->getDecompressor());
bool parseResult = dgds.parse(&scriptData, filename);
_stackDepth = 0;
scriptData.scr->seek(0);
return parseResult;
}
void TTMInterpreter::unload() {
}
/*static*/
const char *TTMInterpreter::ttmOpName(uint16 op) {
switch (op) {
case 0x0000: return "FINISH";
case 0x0020: return "SAVE(free?) BACKGROUND";
case 0x0070: return "FREE PALETTE";
case 0x0080: return "FREE SHAPE";
case 0x0090: return "FREE FONT";
case 0x00B0: return "NULLOP";
case 0x00C0: return "FREE BACKGROUND";
case 0x0110: return "PURGE";
case 0x0210: return "song something?";
case 0x0220: return "STOP CURRENT MUSIC";
case 0x0230: return "FADE CURRENT MUSIC";
case 0x0400: return "PALETTE RESET / STOP PAL BLOCK SWAP";
case 0x0500: return "UNKNOWN 0x0500 (flip mode ON?)";
case 0x0510: return "UNKNOWN 0x0510 (flip mode OFF?)";
case 0x0ff0: return "FINISH FRAME / DRAW";
case 0x1020: return "SET DELAY";
case 0x1030: return "SET BRUSH";
case 0x1050: return "SELECT BMP";
case 0x1060: return "SELECT PAL";
case 0x1070: return "SELECT FONT";
case 0x1090: return "SELECT SONG";
case 0x10a0: return "SET SCENE";
case 0x1100: // fall through
case 0x1110: return "SET SCENE";
case 0x1120: return "SET GETPUT NUM";
case 0x1200: return "GOTO";
case 0x1300: return "PLAY SFX";
case 0x1310: return "STOP SFX";
case 0x2000: return "SET DRAW COLORS";
case 0x2010: return "SET FRAME";
case 0x2020: return "SET RANDOM DELAY";
case 0x2030: return "SET SCROLL OFFSET";
case 0x2300: return "PAL SET BLOCK SWAP 0";
case 0x2310: return "PAL SET BLOCK SWAP 1";
case 0x2320: return "PAL SET BLOCK SWAP 2";
case 0x2400: return "PAL DO BLOCK SWAP";
case 0x3000: return "GOSUB";
case 0x3100: return "SCROLL";
case 0x3200: return "CDS FIND TARGET";
case 0x3300: return "CDS GOSUB";
case 0x4000: return "SET CLIP WINDOW";
case 0x4110: return "FADE OUT";
case 0x4120: return "FADE IN";
case 0x4200: return "STORE AREA";
case 0x4210: return "SAVE GETPUT REGION";
case 0x5000: return "SET DYNAMIC RECT";
case 0xa000: return "DRAW PIXEL";
case 0xa010: return "WIPE DISSOLVE";
case 0xa020: return "WIPE 20?";
case 0xa030: return "WIPE OUT-TO-IN";
case 0xa040: return "WIPE INTERLACED";
case 0xa050: return "WIPE LEFT-TO-RIGHT";
case 0xa060: return "WIPE RIGHT-TO-LEFT";
case 0xa070: return "WIPE TOP-TO-BOTTOM";
case 0xa080: return "WIPE BOTTOM-TO-TOP";
case 0xa090: return "WIPE IN-TO-OUT";
case 0xa0a0: return "DRAW LINE";
case 0xa100: return "DRAW FILLED RECT";
case 0xa110: return "DRAW EMPTY RECT";
case 0xa200: return "DRAW STRING 0";
case 0xa210: return "DRAW STRING 1";
case 0xa220: return "DRAW STRING 2";
case 0xa230: return "DRAW STRING 3";
case 0xa240: return "DRAW STRING 4";
case 0xa250: return "DRAW STRING 5";
case 0xa260: return "DRAW STRING 6";
case 0xa270: return "DRAW STRING 7";
case 0xa280: return "DRAW STRING 8";
case 0xa290: return "DRAW STRING 9";
case 0xa300: return "DRAW STRINGS AS DLG";
case 0xa400: return "DRAW FILLED CIRCLE";
case 0xa420: return "DRAW EMPTY CIRCLE";
case 0xa500: return "DRAW BMP";
case 0xa510: return "DRAW SPRITE FLIPV";
case 0xa520: return "DRAW SPRITE FLIPH";
case 0xa530: return "DRAW SPRITE FLIPHV";
case 0xa600: return "DRAW GETPUT";
case 0xa700: return "DRAW SCROLL";
case 0xaf00: return "DRAW FLOOD FILL";
case 0xaf10: return "DRAW EMPTY POLY";
case 0xaf20: return "DRAW FILLED POLY";
case 0xb000: return "INIT CREDITS SCROLL";
case 0xb010: return "DRAW CREDITS SCROLL";
case 0xb600: return "COPY BUFFER";
case 0xf010: return "LOAD SCR";
case 0xf020: return "LOAD BMP";
case 0xf040: return "LOAD FONT";
case 0xf050: return "LOAD PAL";
case 0xf060: return "LOAD SONG";
case 0xf080: return "LOAD SCROLL";
case 0xf100: return "SET STRING 0";
case 0xf110: return "SET STRING 1";
case 0xf120: return "SET STRING 2";
case 0xf130: return "SET STRING 3";
case 0xf140: return "SET STRING 4";
case 0xf150: return "SET STRING 5";
case 0xf160: return "SET STRING 6";
case 0xf170: return "SET STRING 7";
case 0xf180: return "SET STRING 8";
case 0xf190: return "SET STRING 9";
case 0xc020: return "LOAD SAMPLE";
case 0xc030: return "SELECT SAMPLE";
case 0xc040: return "DESELECT SAMPLE";
case 0xc050: return "PLAY SAMPLE";
case 0xc060: return "STOP SAMPLE";
case 0xc070: return "PAUSE SAMPLE";
case 0xc080: return "UNPAUSE SAMPLE";
case 0xc090: return "MUTE SAMPLE";
case 0xc0a0: return "UNMUTE SAMPLE";
case 0xc0b0: return "SAMPLE PRIORITY";
case 0xc0c0: return "SAMPLE HOLD";
case 0xc0d0: return "SAMPLE BEND";
case 0xc0e0: return "FADE SONG";
case 0xc0f0: return "SONG CONTROLLER??";
case 0xc100: return "SAMPLE VOL";
case 0xc210: return "LOAD RAW SFX";
case 0xc220: return "PLAY RAW SFX";
case 0xc240: return "STOP RAW SFX";
case 0xc250: return "SYNC RAW SFX";
case 0xcf10: return "SFX MASTER VOL";
default: return "UNKNOWN!!";
}
}
static void _copyRectToScreen(const Graphics::ManagedSurface &src, const Common::Rect &r) {
if (r.isEmpty())
return;
Common::Rect copyRect = r;
copyRect.clip(Common::Rect(SCREEN_WIDTH, SCREEN_HEIGHT));
if (copyRect.isEmpty())
return;
Graphics::Surface *surf = g_system->lockScreen();
surf->copyRectToSurface(src.rawSurface(), copyRect.left, copyRect.top, copyRect);
g_system->unlockScreen();
}
static void _dissolveToScreen(const Graphics::ManagedSurface &src, const Common::Rect &r) {
if (r.isEmpty())
return;
// This is basically copied from SCI - it seems to be the same algo in DGDS?
uint16 mask = 0x40, stepNr = 0;
Common::Rect pixelRect;
Graphics::Surface *surf = g_system->lockScreen();
do {
mask = (mask & 1) ? (mask >> 1) ^ 0xB400 : mask >> 1;
if (mask >= SCREEN_WIDTH * SCREEN_HEIGHT)
continue;
pixelRect.left = mask % SCREEN_WIDTH; pixelRect.right = pixelRect.left + 1;
pixelRect.top = mask / SCREEN_WIDTH; pixelRect.bottom = pixelRect.top + 1;
pixelRect.clip(r);
if (!pixelRect.isEmpty())
surf->copyRectToSurface(src.rawSurface(), pixelRect.left, pixelRect.top, pixelRect);
if ((stepNr & 0x3FF) == 0) {
g_system->unlockScreen();
g_system->updateScreen();
g_system->delayMillis(5);
surf = g_system->lockScreen();
}
stepNr++;
} while (mask != 0x40);
g_system->unlockScreen();
}
static void _doScroll(Graphics::ManagedSurface &compBuf, int16 dir, int16 steps, int16 offset) {
// Scroll the contents of the composition buffer on to the screen
// Dir 0/1 means y (scroll camera toward bottom / top)
// Dir 2/3 means x (scroll camera toward right / left)
//
// This is not at all how the original does it, but we have a bit
// more memory and cpu to play with so an extra 64k screen buffer
// and more copies is ok for simpler code.
//
// Example uses:
// Heart of China bar scene (scroll left-to-right and right-to-left)
//
Graphics::Surface *screen = g_system->lockScreen();
Graphics::Surface screenCopy;
screenCopy.copyFrom(*screen);
steps = CLIP(steps, (int16)1, offset);
const Common::Rect screenRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
Common::String dumpFname = Common::String::format("comp-before-scroll-%d", dir);
DgdsEngine::dumpFrame(compBuf, dumpFname.c_str());
dumpFname = Common::String::format("screen-before-scroll-%d", dir);
DgdsEngine::dumpFrame(screenCopy, dumpFname.c_str());
for (int16 i = 1; i <= steps; i++) {
int stepval = ((int)i * offset) / steps;
int xoff = (dir <= 1 ? 0 : (dir == 2 ? stepval : -stepval));
int yoff = (dir >= 2 ? 0 : (dir == 1 ? stepval : -stepval));
Common::Rect srcRectFromOrigScreen(Common::Point(xoff, yoff), SCREEN_WIDTH, SCREEN_HEIGHT);
srcRectFromOrigScreen.clip(screenRect);
if (abs(xoff) < SCREEN_WIDTH && abs(yoff) < SCREEN_HEIGHT)
screen->copyRectToSurface(screenCopy, MAX(0, -xoff), MAX(0, -yoff), srcRectFromOrigScreen);
switch (dir) {
case 0: {
// Draw composition buf to the top of screen buf
error("TODO: Implement TTM scroll direction 0");
break;
}
case 1: {
// Draw composition buf below the screen buf
error("TODO: Implement TTM scroll direction 1");
break;
}
case 2: {
// Draw composition buf to right of screen buf (camera moves to right)
const Common::Rect rectFromCompBuf(0, 0, SCREEN_WIDTH - srcRectFromOrigScreen.width(), SCREEN_HEIGHT);
screen->copyRectToSurface(compBuf, srcRectFromOrigScreen.width(), 0, rectFromCompBuf);
break;
}
case 3: {
// Draw composition buf to left of screen buf (camera moves to left)
const Common::Rect rectFromCompBuf(srcRectFromOrigScreen.width(), 0, SCREEN_WIDTH, SCREEN_HEIGHT);
screen->copyRectToSurface(compBuf, 0, 0, rectFromCompBuf);
break;
}
default:
error("TTM scroll invalid scroll direction: %d", dir);
break;
}
g_system->unlockScreen();
g_system->updateScreen();
g_system->delayMillis(5);
screen = g_system->lockScreen();
}
g_system->unlockScreen();
screenCopy.free();
}
void TTMInterpreter::doWipeOp(uint16 code, const TTMEnviro &env, const TTMSeq &seq, const Common::Rect &r) {
//
// In the original games, these operations copy certain parts of the buffer on to
// the screen, and rely on the system's speed to make it happen faster than a regular
// game frame.
//
// This gives a "wipe" effect. In ScummVM we try to emulate the same final effect with
// some fudge-factors to update the screen roughly every 10 rows copied. This gives
// a reasonably nice result.
//
// Example uses:
// Left-to-right wipe: the "AAAHHHHH" in Dragon intro
// outside-to-inside or in-to-out: Blood cell video from Karyn in Dragon (scene 94)
//
switch(code) {
case 0xa010:
_dissolveToScreen(_vm->_compositionBuffer, r);
break;
case 0xa020:
_copyRectToScreen(_vm->_compositionBuffer, r);
g_system->updateScreen();
break;
case 0xa040: // Interlaced effect
for (int i = 1; i < 11; i++) {
int yoffset = 0;
while (r.top + i + yoffset <= r.bottom) {
if (r.width()) {
_copyRectToScreen(_vm->_compositionBuffer, Common::Rect(Common::Point(r.left, r.top + i + yoffset), r.width(), 1));
}
yoffset += 10;
}
if (i % 3 == 0)
g_system->updateScreen();
}
break;
case 0xa050: // Copy from left-to-right
case 0xa060: // Copy from right-to-left
for (int i = 3; i <= r.width() - 3; i++) {
int x;
if (code == 0xa060) {
x = r.right - i;
} else { // a050
x = i - 3;
}
_copyRectToScreen(_vm->_compositionBuffer, Common::Rect(Common::Point(r.left + x, r.top), 3, r.height()));
if (i % 10 == 0)
g_system->updateScreen();
}
break;
case 0xa070: // Copy from top to bottom
case 0xa080: // Copy from bottom to top
for (int i = 3; i <= r.height() - 3; i++) {
int y;
if (code == 0xa080) {
y = r.bottom - i;
} else { // a070
y = i - 3;
}
_copyRectToScreen(_vm->_compositionBuffer, Common::Rect(Common::Point(r.left, r.top + y), r.width(), 3));
if (i % 10 == 0)
g_system->updateScreen();
}
break;
case 0xa030: // Copy from outside to middle
case 0xa090: // Copy from middle to outside
{
int halfwidth = r.width() / 2;
int halfheight = r.height() / 2;
uint maxside = MAX(halfheight, halfwidth);
long widthScale = 1000 * halfwidth / maxside;
long heightScale = 1000 * halfheight / maxside;
uint i = (code == 0xa030) ? maxside : 1;
do {
if (code == 0xa030) {
if (i < 1)
return;
i--;
} else {
if (i > maxside)
return;
i++;
}
int xoff = widthScale * i / 1000;
int yoff = heightScale * i / 1000;
int16 xinside = MAX((r.left + halfwidth) - xoff, 0);
int16 yinside = MAX((r.top + halfheight) - yoff, 0);
uint16 width = xoff * 2;
uint16 height = yoff * 2;
if (code == 0xa030) {
_copyRectToScreen(_vm->_compositionBuffer, Common::Rect(Common::Point(r.left, r.top), xinside - r.left, r.bottom - r.top));
_copyRectToScreen(_vm->_compositionBuffer, Common::Rect(Common::Point(xinside + width, r.top), (r.right - xinside) - xoff * 2, r.bottom - r.top));
_copyRectToScreen(_vm->_compositionBuffer, Common::Rect(Common::Point(xinside, r.top), width, yinside - r.top));
_copyRectToScreen(_vm->_compositionBuffer, Common::Rect(Common::Point(xinside, yinside + height), width, r.bottom - yinside - yoff * 2));
} else {
_copyRectToScreen(_vm->_compositionBuffer, Common::Rect(Common::Point(xinside, yinside), width, height));
}
if (i % 10 == 0)
g_system->updateScreen();
} while( true );
break;
}
default:
error("unsupported opcode for doSaveRegion 0x%04x", code);
}
}
int16 TTMInterpreter::doInitCreditScrollOp(const Image *img) {
assert(img);
int16 maxWidth = 0;
for (int i = 0; i < img->loadedFrameCount(); i++)
maxWidth = MAX(maxWidth, img->width(i));
return maxWidth;
}
bool TTMInterpreter::doCreditsScrollOp(const Image *img, int16 ygap, int16 ymax, int16 xoff, int16 measuredWidth, const Common::Rect &clipRect) {
int nframes = img->loadedFrameCount();
bool scrollFinished = true;
int y = SCREEN_HEIGHT - ymax;
for (int i = 0; i < nframes; i++) {
int width = img->width(i);
int height = img->height(i);
if (y > -measuredWidth) {
Common::Rect drawWin(Common::Point(xoff, y), width, height);
drawWin.clip(clipRect);
if (!drawWin.isEmpty()) {
img->drawBitmap(i, xoff, y, drawWin, _vm->_compositionBuffer);
}
scrollFinished = false;
}
y += ygap + height;
if (y > SCREEN_HEIGHT)
break;
}
return scrollFinished;
}
void TTMInterpreter::doDrawDialogForStrings(const TTMEnviro &env, const TTMSeq &seq, int16 x, int16 y, int16 width, int16 height) {
int16 fontno = seq._currentFontId;
if (fontno >= (int16)env._fonts.size()) {
warning("Trying to draw font no %d but only loaded %d", fontno, env._fonts.size());
fontno = 0;
}
const FontManager *mgr = _vm->getFontMan();
const DgdsFont *font = mgr->getFont(env._fonts[fontno]);
int16 lineHeight = font->getFontHeight();
// FIXME: this doesn't match original code but seems to be needed to make it right??
int16 charWidth = font->getMaxCharWidth() / 2;
if (lineHeight == 0 || charWidth == 0)
return;
// Black background
Common::Rect drawRect = Common::Rect(Common::Point(x, y), width - 3, height - 3);
_vm->_compositionBuffer.fillRect(drawRect, 0);
drawRect.grow(-1);
// Header area color
_vm->_compositionBuffer.fillRect(drawRect, seq._drawColBG);
// Main text bg
_vm->_compositionBuffer.fillRect(Common::Rect(Common::Point(x, y + lineHeight + 2), width - 3, height - lineHeight - 5), 0);
_vm->_compositionBuffer.fillRect(Common::Rect(Common::Point(x + 1, y + lineHeight + 4), width - 5, height - lineHeight - 8), seq._drawColFG);
// Drop shadow.
_vm->_compositionBuffer.fillRect(Common::Rect(Common::Point(x + width - 3, y + 3), 3, height - 3), 0);
_vm->_compositionBuffer.fillRect(Common::Rect(Common::Point(x + 3, y + height - 3), width - 3, 3), 0);
for (int i = 0; i < ARRAYSIZE(env._strings); i++) {
const Common::String &str = env._strings[i];
int16 lineX;
int16 lineY;
if ((int)str.size() * charWidth < width) {
if (i == 0) {
int16 lineWidth = font->getStringWidth(str);
// Heading is centered
lineX = x + (width - lineWidth) / 2;
lineY = y + 2;
} else {
if (height <= (i + 2) * lineHeight)
continue;
lineX = x + charWidth;
lineY = y + i * lineHeight + 5;
}
font->drawString(_vm->_compositionBuffer.surfacePtr(), str, lineX, lineY, width, 0);
}
}
}
/// Handle 0xa5xx draw ops
void TTMInterpreter::doDrawSpriteOp(const TTMEnviro &env, const TTMSeq &seq, uint16 op, byte count, const int16 *ivals) {
int frameno;
int bmpNo;
int dstWidth = 0;
int dstHeight = 0;
if (count == 6) {
frameno = ivals[2];
bmpNo = ivals[3];
dstWidth = ivals[4];
dstHeight = ivals[5];
} else if (count == 4) {
frameno = ivals[2];
bmpNo = ivals[3];
} else {
frameno = seq._brushNum;
bmpNo = seq._currentBmpId;
}
ImageFlipMode flipMode = kImageFlipNone;
if (op == 0xa510)
flipMode = kImageFlipV;
else if (op == 0xa520)
flipMode = kImageFlipH;
else if (op == 0xa530)
flipMode = kImageFlipHV;
Common::SharedPtr<Image> img = env._scriptShapes[bmpNo];
if (img) {
int x = ivals[0];
int y = ivals[1];
// Use env offset if we are in gosub
if (_stackDepth > 0) {
x += env._xOff;
y += env._yOff;
}
img->drawBitmap(frameno, x, y, seq._drawWin, _vm->_compositionBuffer, flipMode, dstWidth, dstHeight);
} else {
warning("Trying to draw image %d in env %d which is not loaded", bmpNo, env._enviro);
}
}
void TTMInterpreter::doFadeOutOp(int16 colorno, int16 ncolors, int16 targetcol, int16 speed) {
if (speed == 0) {
// Immediately fade to black.
_vm->getGamePals()->clearPalette();
} else {
// The original tight-loops here with 640 steps and i/10 as the fade level..
// bring that down a bit to use less cpu.
// Speed 4 should complete fade in 2 seconds (eg, Dynamix logo fade)
// TODO: this is a pretty bad way to do it - should pump messages in this loop?
for (int i = 0; i < 320; i += speed) {
int fade = MIN(i / 5, 63);
_vm->getGamePals()->setFade(colorno, ncolors, targetcol, fade * 4);
g_system->updateScreen();
g_system->delayMillis(5);
}
}
// Logic here is different in Dragon + HOC. They clear all buffers after fade
if (_vm->getGameId() == GID_DRAGON || _vm->getGameId() == GID_HOC) {
_vm->_compositionBuffer.fillRect(Common::Rect(SCREEN_WIDTH, SCREEN_HEIGHT), 0);
_vm->getBackgroundBuffer().fillRect(Common::Rect(SCREEN_WIDTH, SCREEN_HEIGHT), 0);
} else {
// In Willy Beamish, copy comp->screen and comp->back
g_system->copyRectToScreen(_vm->_compositionBuffer.getPixels(), SCREEN_WIDTH, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
g_system->updateScreen();
_vm->getBackgroundBuffer().blitFrom(_vm->_compositionBuffer);
}
// Stored area is cleared in all games.
_vm->getStoredAreaBuffer().fillRect(Common::Rect(SCREEN_WIDTH, SCREEN_HEIGHT), 0);
// Reset to previous palette.
_vm->getGamePals()->setPalette();
}
void TTMInterpreter::doFadeInOp(int16 colorno, int16 ncolors, int16 targetcol, int16 speed) {
if (speed == 0) {
_vm->getGamePals()->setPalette();
} else {
for (int i = 320; i > 0; i -= speed) {
int fade = MAX(0, MIN(i / 5, 63));
_vm->getGamePals()->setFade(colorno, ncolors, targetcol, fade * 4);
if (i == 320) {
// update screen first to make the initial fade-in work
g_system->copyRectToScreen(_vm->_compositionBuffer.getPixels(), SCREEN_WIDTH, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
}
g_system->updateScreen();
g_system->delayMillis(5);
}
}
}
void TTMInterpreter::handleOperation(TTMEnviro &env, TTMSeq &seq, uint16 op, byte count, const int16 *ivals, const Common::String &sval, const Common::Array<Common::Point> &pts) {
switch (op) {
case 0x0000: // FINISH: void
break;
case 0x0020: // SAVE (free?) BACKGROUND
if (seq._executed) // this is a one-shot op
break;
//
// This appears in the credits, intro sequence, and the
// "meanwhile" event with the factory in DRAGON. It should
// copy the front buffer to the stored buffer, then the stored
// buffer to the background?
//
// Credits - (no scr loaded) Store large image on black bg after loading and before txt scroll
// Intro - (no scr loaded) After each screen change, draw and save the new comic frame as bg
// on "aaaaah" scene, called after only drawing the AAAH and calling store area
// Meanwhile - (scr loaded) Save the foreground people onto the background before walk animation
//
if (DgdsEngine::getInstance()->getGameId() == GID_WILLY) {
_vm->getStoredAreaBuffer().blitFrom(_vm->_compositionBuffer);
_vm->getBackgroundBuffer().fillRect(Common::Rect(SCREEN_WIDTH, SCREEN_HEIGHT), 0);
} else {
_vm->getStoredAreaBuffer().fillRect(Common::Rect(SCREEN_WIDTH, SCREEN_HEIGHT), 0);
_vm->getBackgroundBuffer().blitFrom(_vm->_compositionBuffer);
}
break;
case 0x0070: // FREE PALETTE
if (seq._executed) // this is a one-shot op
break;
warning("TODO: Implement me: op 0x0070 free palette (current pal)");
seq._currentPalId = 0;
break;
case 0x0080: // FREE SHAPE
// This is a one-shot op only in Willy Beamish - in HoC and Dragon it's done every time
// (although it may not make any difference)
if (seq._executed && DgdsEngine::getInstance()->getGameId() == GID_WILLY)
break;
//debug(1, "0x0080: Free from slot %d for seq %d env %d", seq._currentBmpId, seq._seqNum, env._enviro);
env._scriptShapes[seq._currentBmpId].reset();
break;
case 0x0090: // FREE FONT
if (seq._executed) // this is a one-shot op
break;
if (seq._currentFontId >= (int16)env._fonts.size()) {
warning("Request to free font not loaded %d", seq._currentFontId);
break;
}
env._fonts.remove_at(seq._currentFontId);
seq._currentFontId = 0;
break;
case 0x00B0:
// Does nothing?
break;
case 0x00C0: // (one-shot) FREE GETPUT (free getput item pointed to by _currentGetPutId)
if (seq._executed) // this is a one-shot op
break;
env._getPuts[seq._currentGetPutId].reset();
break;
case 0x0110: // PURGE void
// only set if not running from CDS script
_vm->adsInterpreter()->setHitTTMOp0110();
break;
case 0x0220: // STOP CURRENT MUSIC
if (seq._executed) // this is a one-shot op
break;
_vm->_soundPlayer->stopMusic();
break;
case 0x0400: // RESET PALETTE?
if (seq._executed) // this is a one-shot op
break;
warning("TODO: 0x0400 Reset palette / stop pal cycle");
break;
case 0x0500: // FLIP MODE ON
DgdsEngine::getInstance()->setFlipMode(true);
break;
case 0x0510: // FLIP MODE OFF
DgdsEngine::getInstance()->setFlipMode(false);
break;
case 0x0ff0: // REFRESH: void
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.
int delayMillis = (int)round(ivals[0] * MS_PER_FRAME);
_vm->adsInterpreter()->setScriptDelay(delayMillis);
break;
}
case 0x1030: // SET BRUSH: id:int [-1:n]
seq._brushNum = ivals[0];
break;
case 0x1050: // SELECT BMP: id:int [0:n]
//debug(1, "0x1051: Select bmp %d for seq %d from env %d", ivals[0], seq._seqNum, env._enviro);
seq._currentBmpId = ivals[0];
break;
case 0x1060: // SELECT PAL: id:int [0]
seq._currentPalId = ivals[0];
if (seq._executed) // this is a mostly on-shot op.
break;
_vm->getGamePals()->selectPalNum(env._scriptPals[ivals[0]]);
break;
case 0x1070: // SELECT FONT i:int
seq._currentFontId = ivals[0];
break;
case 0x1090: // SELECT SONG: id:int [0]
seq._currentSongId = ivals[0];
break;
case 0x10a0: // SET SCENE?: i:int [0..n], often 0, called on scene change?
// In the original this sets a global that seems to be never used?
break;
case 0x1100: // SET_SCENE: i:int [1..n]
case 0x1110: // SET_SCENE: i:int [1..n]
// DESCRIPTION IN TTM TAGS. num only used as GOTO target.
break;
case 0x1120: // SET GETPUT NUM
seq._currentGetPutId = ivals[0];
break;
case 0x1200: // GOTO
_vm->adsInterpreter()->setGotoTarget(findGOTOTarget(env, seq, ivals[0]));
break;
case 0x1300: // PLAY SFX i:int - eg [72], found in Dragon + HoC intro
if (seq._executed) // this is a one-shot op.
break;
_vm->_soundPlayer->playSFX(ivals[0]);
break;
case 0x1310: // STOP SFX i:int eg [107]
if (seq._executed) // this is a one-shot op.
break;
_vm->_soundPlayer->stopSfxByNum(ivals[0]);
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 0x2020: { // SET RANDOM SLEEP: min,max: int (eg, 60,300)
if (seq._executed) // this is a one-shot op.
break;
uint sleep = _vm->getRandom().getRandomNumberRng(ivals[0], ivals[1]);
_vm->adsInterpreter()->setScriptDelay((int)(sleep * MS_PER_FRAME));
break;
}
case 0x2030: { // SET SCROLL mode,val: int
if (seq._executed) // this is a one-shot op.
break;
// mode chooses x/y and +/- for scroll offset.
switch(ivals[0]) {
case 0:
env._yScroll -= ivals[1];
break;
case 1:
env._yScroll += ivals[1];
break;
case 2:
env._xScroll -= ivals[1];
break;
case 3:
env._xScroll += ivals[1];
break;
default:
error("TTM 0x2030 Invalid scroll mode %d (should be 0-3)", ivals[0]);
}
break;
}
case 0x2300:
case 0x2310:
case 0x2320: {
uint num = (op & 0xf) > 4;
uint start = ivals[0];
uint end = ivals[1];
warning("TODO: 0x%04x Palette configure block swap %d (%d - %d)", op, num, start, end);
break;
}
case 0x2400:
if (seq._executed) // this is a one-shot op.
break;
warning("TODO: 0x%04x Palette do block swaps 0x%x, 0x%x", op, ivals[0], ivals[1]);
break;
case 0x3000: { // GOSUB xoff,yoff,frame
_stackDepth++;
bool prevHitOp0110Val = _vm->adsInterpreter()->getHitTTMOp0110();
int32 target = findGOTOTarget(env, seq, ivals[2]);
if (target == -1)
error("TTM gosub to frame %d which doesn't exist", target);
int64 prevPos = env.scr->pos();
env.scr->seek(env._frameOffsets[target]);
env._xOff = ivals[0];
env._yOff = ivals[1];
run(env, seq);
env.scr->seek(prevPos);
if (_vm->adsInterpreter()->getHitTTMOp0110() && !prevHitOp0110Val)
_vm->adsInterpreter()->setHitTTMOp0110(false);
_stackDepth--;
break;
}
case 0x3100: { // SCROLL dir,steps,distance eg (2, 100, 185) in beamish intro
if (seq._executed) // this is a one-shot op.
break;
_doScroll(_vm->_compositionBuffer, ivals[0], ivals[1], ivals[2]);
// After scroll, we need to store the screen contents into the
// stored area buffer, and copy the background to the front?
Graphics::Surface *screen = g_system->lockScreen();
_vm->getStoredAreaBuffer().blitFrom(*screen);
g_system->unlockScreen();
_vm->_compositionBuffer.blitFrom(_vm->getStoredAreaBuffer());
break;
}
case 0x4000: // SET CLIP WINDOW x,y,x2,y2:int [0..320,0..200]
// NOTE: params are xmax/ymax, NOT w/h
seq._drawWin = Common::Rect(ivals[0], ivals[1], ivals[2] + 1, ivals[3] + 1);
break;
case 0x4110: // FADE OUT: colorno,ncolors,targetcol,speed:byte
if (seq._executed) // this is a one-shot op.
break;
doFadeOutOp(ivals[0], ivals[1], ivals[2], ivals[3]);
break;
case 0x4120: { // FADE IN: colorno,ncolors,targetcol,speed:byte
if (seq._executed) // this is a one-shot op.
break;
doFadeInOp(ivals[0], ivals[1], ivals[2], ivals[3]);
break;
}
case 0x4200: { // STORE AREA: x,y,w,h:int [0..n] ; makes this area of foreground persist in the next frames.
if (seq._executed) // this is a one-shot op
break;
Common::Rect rect(Common::Point(ivals[0], ivals[1]), ivals[2], ivals[3]);
_vm->getStoredAreaBuffer().blitFrom(_vm->_compositionBuffer, rect, rect);
break;
}
case 0x4210: { // SAVE GETPUT REGION (getput area) x,y,w,h:int
if (seq._executed) // this is a one-shot op.
break;
if (seq._currentGetPutId >= (int)env._getPuts.size()) {
env._getPuts.resize(seq._currentGetPutId + 1);
}
const Common::Rect rect(Common::Point(ivals[0], ivals[1]), ivals[2], ivals[3]);
env._getPuts[seq._currentGetPutId]._area = rect;
// Getput reads an area from the front buffer.
Graphics::ManagedSurface *surf = new Graphics::ManagedSurface(rect.width(), rect.height(), _vm->_compositionBuffer.format);
surf->blitFrom(_vm->_compositionBuffer, rect, Common::Point(0, 0));
env._getPuts[seq._currentGetPutId]._surf.reset(surf);
break;
}
case 0x5000: // SET DYNAMIC RECT: x,y,w,h,num ??
_vm->getScene()->setDynamicSceneRect(ivals[4], ivals[0], ivals[1], ivals[2], ivals[3]);
break;
case 0xa000: // DRAW PIXEL x,y:int
if (seq._drawWin.contains(ivals[0], ivals[1]))
_vm->_compositionBuffer.setPixel(ivals[0], ivals[1], seq._drawColFG);
break;
case 0xa010:
case 0xa020:
case 0xa030:
case 0xa040:
case 0xa050:
case 0xa060:
case 0xa070:
case 0xa080:
case 0xa090:
doWipeOp(op, env, seq, Common::Rect(Common::Point(ivals[0], ivals[1]), ivals[2], ivals[3]));
break;
case 0xa0a0: { // DRAW LINE x1,y1,x2,y2:int
Graphics::Surface clipSurf(_vm->_compositionBuffer.getSubArea(seq._drawWin));
clipSurf.drawLine(
ivals[0] - seq._drawWin.left,
ivals[1] - seq._drawWin.top,
ivals[2] - seq._drawWin.left,
ivals[3] - seq._drawWin.top, seq._drawColFG);
break;
}
case 0xa100: { // DRAW FILLED RECT x,y,w,h:int [0..320,0..200]
// Draw fill first
Common::Rect r(Common::Point(ivals[0], ivals[1]), ivals[2], ivals[3]);
r.grow(-1);
r.clip(seq._drawWin);
_vm->_compositionBuffer.fillRect(r, seq._drawColBG);
}
// then to draw the border, we (drum roll)...
// FALL THROUGH
case 0xa110: { // DRAW EMPTY RECT x1,y1,x2,y2:int
const Common::Rect r(Common::Point(ivals[0], ivals[1]), ivals[2], ivals[3]);
Drawing::rectClipped(r, seq._drawWin, &_vm->_compositionBuffer, seq._drawColFG);
break;
}
case 0xa200: // 0xa2n0 DRAW STRING n: x,y,w,h:int - draw the nth string from the string table
case 0xa210:
case 0xa220:
case 0xa230:
case 0xa240:
case 0xa250:
case 0xa260:
case 0xa270:
case 0xa280:
case 0xa290: {
int16 fontno = seq._currentFontId;
if (fontno >= (int16)env._fonts.size()) {
warning("Trying to draw font no %d but only loaded %d", fontno, env._fonts.size());
fontno = 0;
}
uint strnum = (op & 0x70) >> 4;
const Common::String &str = env._strings[strnum];
const FontManager *mgr = _vm->getFontMan();
const DgdsFont *font = mgr->getFont(env._fonts[fontno]);
// Note: ignore the y-height argument (ivals[3]) for now. If width is 0, draw as much as we can.
int width = ivals[2];
if (width == 0)
width = SCREEN_WIDTH - ivals[0];
font->drawString(&(_vm->_compositionBuffer), str, ivals[0], ivals[1], width, seq._drawColFG);
break;
}
case 0xa300:
doDrawDialogForStrings(env, seq, ivals[0], ivals[1], ivals[2], ivals[3]);
break;
case 0xa400: { // DRAW FILLED CIRCLE
// FIXME: This should honor seq._drawWin
int16 xr = ivals[2] / 2;
int16 yr = ivals[3] / 2;
Drawing::filledCircle(ivals[0] + xr, ivals[1] + yr, xr, yr, &_vm->_compositionBuffer, seq._drawColFG, seq._drawColBG);
break;
}
case 0xa420: { // DRAW EMPTY CIRCLE
// FIXME: This should honor seq._drawWin
int16 xr = ivals[2] / 2;
int16 yr = ivals[3] / 2;
Drawing::emptyCircle(ivals[0] + xr, ivals[1] + yr, xr, yr, &_vm->_compositionBuffer, seq._drawColFG);
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] (CHINA+)
doDrawSpriteOp(env, seq, op, count, ivals);
break;
case 0xa600: { // DRAW GETPUT: i:int
if (seq._executed) // this is a one-shot op.
break;
int16 i = ivals[0];
if (i >= (int16)env._getPuts.size() || !env._getPuts[i]._surf.get()) {
warning("Trying to put getput region %d we never got", i);
break;
}
const Common::Rect &r = env._getPuts[i]._area;
// Getput should overwrite the contents
_vm->_compositionBuffer.blitFrom(*(env._getPuts[i]._surf.get()),
Common::Point(r.left, r.top));
break;
}
case 0xa700: { // DRAW SCROLL x,y,w,h
if (!env._scrollShape) {
warning("Trying to draw scroll with no scrollshape loaded");
} else {
env._scrollShape->drawScrollBitmap(ivals[0], ivals[1], ivals[2], ivals[3],
env._xScroll, env._yScroll, seq._drawWin, _vm->_compositionBuffer);
}
break;
}
case 0xaf00: { // FLOOD FILL x,y
Graphics::Surface *surf = _vm->_compositionBuffer.surfacePtr();
byte oldCol = surf->getPixel(ivals[0], ivals[1]);
Graphics::FloodFill f(surf, oldCol, seq._drawColFG);
f.addSeed(ivals[0], ivals[1]);
f.fill();
break;
}
case 0xaf10: { // DRAW EMPTY POLY [pts]
Graphics::Surface clipSurf(_vm->_compositionBuffer.getSubArea(seq._drawWin));
for (uint i = 1; i < pts.size(); i++) {
const Common::Point &p1 = pts[i - 1];
const Common::Point &p2 = pts[i];
clipSurf.drawLine(
p1.x - seq._drawWin.left,
p1.y - seq._drawWin.top,
p2.x - seq._drawWin.left,
p2.y - seq._drawWin.top, seq._drawColFG);
}
if (pts.size() > 2)
clipSurf.drawLine(
pts.back().x - seq._drawWin.left,
pts.back().y - seq._drawWin.top,
pts[0].x - seq._drawWin.left,
pts[0].y - seq._drawWin.top, seq._drawColFG);
break;
}
case 0xaf20: { // DRAW FILLED POLY [pts]
Graphics::Surface clipSurf(_vm->_compositionBuffer.getSubArea(seq._drawWin));
Common::Array<int> xvals(pts.size());
Common::Array<int> yvals(pts.size());
for (uint i = 0; i < pts.size(); i++) {
xvals[i] = pts[i].x - seq._drawWin.left;
yvals[i] = pts[i].y - seq._drawWin.top;
}
Common::Rect bbox(seq._drawWin);
bbox.moveTo(0, 0);
clipSurf.drawPolygonScan(xvals.data(), yvals.data(), pts.size(), bbox,
seq._drawColFG);
break;
}
case 0xb000: // INIT CREDITS SCRLL
if (seq._executed) // this is a one-shot op
break;
env._creditScrollMeasure = doInitCreditScrollOp(env._scriptShapes[seq._currentBmpId].get());
env._creditScrollYOffset = 0;
break;
case 0xb010: { // DRAW CREDITS SCROLL ygap,ystep
const Image *img = env._scriptShapes[seq._currentBmpId].get();
if (img && img->isLoaded()) {
bool finished = doCreditsScrollOp(env._scriptShapes[seq._currentBmpId].get(), ivals[0], env._creditScrollYOffset,
ivals[2], env._creditScrollMeasure, seq._drawWin);
env._creditScrollYOffset += ivals[1];
if (finished)
_vm->adsInterpreter()->setHitTTMOp0110();
}
break;
}
case 0xb600: { // COPY BUFFER: x, y, w, h, srcbuf, dstbuf
// srcbuf and dstbuf are buffer numbers.
// 0 - background
// 1 - stored area
// 2 - composition
if (seq._executed) // this is a one-shot op
break;
const Common::Rect r(Common::Point(ivals[0], ivals[1]), ivals[2], ivals[3]);
int16 b1 = ivals[4];
int16 b2 = ivals[5];
Graphics::ManagedSurface &src = ((b1 == 2) ? _vm->_compositionBuffer :
(b1 == 0 ? _vm->getBackgroundBuffer() : _vm->getStoredAreaBuffer()));
Graphics::ManagedSurface &dst = ((b2 == 2) ? _vm->_compositionBuffer :
(b2 == 0 ? _vm->getBackgroundBuffer() : _vm->getStoredAreaBuffer()));
dst.blitFrom(src, r, r);
break;
}
case 0xc020: { // LOAD SAMPLE: filename:str
// Ignore this? Tries to load music files which don't exist in Willy
// Beamish - everything is integrated into WILLYSND.SX and WILLYMUS.SX
// _vm->_soundPlayer->loadMusic(sval.c_str());
// _vm->_soundPlayer->stopMusic();
break;
}
case 0xc030: { // SELECT SAMPLE: int: i
// Often this is just the same number as gets passed to the following
// PLAY SAMPLE, but if PLAY SAMPLE gets 0 then this number should be
// used.
env._lastSelectedSample = ivals[0];
break;
}
case 0xc040: { // DESELECT SAMPLE: int: i
// This is not quite the same as the original. It looks like:
// * 0 is "deselect all samples"
// * -1 is "deselect stopped samples"
// * -2 is "deselect playing samples"
// (-1 and -2 might be the other way around?)
// -1 may not be used anywhere? -2 is used in SQ5 demo.
if (ivals[0] == 0 || ivals[0] == -2) {
_vm->_soundPlayer->stopAllSfx();
}
env._lastSelectedSample = 0;
break;
}
case 0xc050: { // PLAY SAMPLE: int: i
if (seq._executed) // this is a one-shot op
break;
int16 sample = ivals[0] ? ivals[0] : env._lastSelectedSample;
if (sample)
_vm->_soundPlayer->playMusicOrSFX(sample);
break;
}
case 0xc060: { // STOP SAMPLE: int: i
if (ivals[0] == -2)
_vm->_soundPlayer->stopAllSfx();
else if (ivals[0] == -1)
// Handled in original but maybe never used?
warning("TODO: Implement stop sample for arg -1");
else
_vm->_soundPlayer->stopMusicOrSFX(ivals[0]);
break;
}
case 0xc070: { // PAUSE SAMPLE: int: i
_vm->_soundPlayer->pauseMusicOrSFX(ivals[0]);
break;
}
case 0xc080: { // UNPAUSE SAMPLE: int: i
_vm->_soundPlayer->unpauseMusicOrSFX(ivals[0]);
break;
}
case 0xc210: { // LOAD RAW SFX filename:str
if (seq._executed) // this is a one-shot op
break;
// Stop any existing raw sound before we deallocate it.
if (env._soundRaw)
env._soundRaw->stop();
if (_vm->getResourceManager()->hasResource(sval)) {
SoundRaw *snd = new SoundRaw(_vm->getResourceManager(), _vm->getDecompressor());
snd->load(sval);
env._soundRaw.reset(snd);
} else {
warning("TTM 0xC21F: Skip loading RAW %s, not found.", sval.c_str());
}
break;
}
case 0xc220: { // PLAY RAW SFX
if (seq._executed) // this is a one-shot op
break;
if (env._soundRaw)
env._soundRaw->play();
else
warning("TTM 0xC220: Trying to play raw SFX but nothing loaded");
break;
}
case 0xc240: { // STOP RAW SFX
if (env._soundRaw) {
env._soundRaw->stop();
} else {
warning("TTM 0xC240: Trying to stop raw SFX but nothing loaded");
}
break;
}
case 0xf010: { // LOAD SCR: filename:str
if (seq._executed) // this is a one-shot op
break;
Image tmp(_vm->getResourceManager(), _vm->getDecompressor());
tmp.drawScreen(sval, _vm->getBackgroundBuffer());
_vm->_compositionBuffer.blitFrom(_vm->getBackgroundBuffer());
_vm->getStoredAreaBuffer().fillRect(Common::Rect(SCREEN_WIDTH, SCREEN_HEIGHT), 0);
_vm->setBackgroundFile(sval);
break;
}
case 0xf020: // LOAD BMP: filename:str
if (seq._executed) // this is a one-shot op
break;
//debug(1, "0xf020: Load bitmap %s to slot %d for env %d", sval.c_str(), env._enviro, seq._currentBmpId);
if (_vm->getResourceManager()->hasResource(sval)) {
env._scriptShapes[seq._currentBmpId].reset(new Image(_vm->getResourceManager(), _vm->getDecompressor()));
env._scriptShapes[seq._currentBmpId]->loadBitmap(sval);
} else {
// This happens in Willy Beamish talkie CDS files.
debug("TTM 0xF020: Skip loading BMP %s, not found.", sval.c_str());
}
break;
case 0xf040: { // LOAD FONT: filename:str
if (seq._executed) // this is a one-shot op
break;
const FontManager *mgr = _vm->getFontMan();
env._fonts.push_back(mgr->fontTypeByName(sval));
break;
}
case 0xf050: { // LOAD PAL: filename:str
if (seq._executed) // this is a one-shot op
break;
int newPalNum = _vm->getGamePals()->loadPalette(sval);
env._scriptPals[seq._currentPalId] = newPalNum;
break;
}
case 0xf060: // LOAD SONG: filename:str
if (seq._executed) // this is a one-shot op
break;
if (_vm->getPlatform() == Common::kPlatformAmiga) {
// TODO: remove hard-coded stuff..
_vm->_soundPlayer->playAmigaSfx(sval.c_str(), 0, 255);
} else {
if (_vm->_soundPlayer->loadMusic(sval.c_str()))
_vm->_soundPlayer->playMusic(seq._currentSongId);
}
break;
case 0xf080: { // LOAD SCROLL: filename:str
if (seq._executed) // this is a one-shot op
break;
env._scrollShape.reset(new Image(_vm->getResourceManager(), _vm->getDecompressor()));
env._scrollShape->loadBitmap(sval);
env._xScroll = 0;
env._yScroll = 0;
break;
}
case 0xf100: // 0xf1n0 - SET STRING n: s:str - set the nth string in the table
case 0xf110:
case 0xf120:
case 0xf130:
case 0xf140:
case 0xf150:
case 0xf160:
case 0xf170:
case 0xf180:
case 0xf190: {
uint strnum = (op & 0xf0) >> 4;
env._strings[strnum] = sval;
break;
}
default:
if (count < 15)
warning("Unimplemented 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 TTM opcode: 0x%04X (%s, sval: %s)", op,
ttmOpName(op), sval.c_str());
break;
}
}
Common::String TTMInterpreter::readTTMStringVal(Common::SeekableReadStream *scr) {
Common::String sval;
byte ch[2];
do {
ch[0] = scr->readByte();
ch[1] = scr->readByte();
if (ch[0])
sval += ch[0];
if (ch[1])
sval += ch[1];
} while (ch[0] != 0 && ch[1] != 0);
return sval;
}
bool TTMInterpreter::run(TTMEnviro &env, TTMSeq &seq) {
Common::SeekableReadStream *scr = env.scr;
if (!scr || scr->pos() >= scr->size())
return false;
debug(10, "TTM: Run env %d seq %d (%s) frame %d (scr offset %d, %s)", seq._enviro, seq._seqNum,
env._tags[seq._seqNum].c_str(), seq._currentFrame, (int)scr->pos(),
seq._executed ? "already executed" : "first execution");
uint16 code = 0;
while (code != 0x0ff0 && scr->pos() < scr->size()) {
code = scr->readUint16LE();
uint16 op = code & 0xFFF0;
byte count = code & 0x000F;
int16 ivals[8] = {0, 0, 0, 0, 0, 0, 0, 0};
Common::String sval;
Common::Array<Common::Point> pts;
if (count > 8 && count != 0x0f)
error("Invalid TTM opcode %04x requires %d locals", code, count);
debugN(10, "\tOP: 0x%4.4x %2u ", op, count);
if (count == 0x0F) {
// Special case for these codes, they are not strings,
// but lists of points.
if (code == 0xaf1f || code == 0xaf2f) {
uint16 npoints = scr->readSint16LE();
pts.resize(npoints);
for (uint i = 0; i < npoints; i++)
pts[i].x = scr->readUint16LE();
for (uint i = 0; i < npoints; i++)
pts[i].y = scr->readUint16LE();
debugN(10, "%d points: [", npoints);
for (uint i = 0; i < npoints; i++)
debugN(10, "(%d,%d)", pts[i].x, pts[i].y);
debugN(10, "]");
} else {
sval = readTTMStringVal(scr);
debugN(10, "\"%s\"", sval.c_str());
}
} else {
for (byte i = 0; i < count; i++) {
ivals[i] = scr->readSint16LE();
if (i > 0)
debugN(10, ", ");
debugN(10, "%d", ivals[i]);
}
}
debug(10, " (%s)", ttmOpName(op));
handleOperation(env, seq, op, count, ivals, sval, pts);
}
return true;
}
int32 TTMInterpreter::findGOTOTarget(const TTMEnviro &env, const TTMSeq &seq, int16 targetFrame) {
int64 startpos = env.scr->pos();
int32 retval = -1;
for (int32 i = 0; i < (int)env._frameOffsets.size(); i++) {
if (env._frameOffsets[i] < 0)
continue;
env.scr->seek(env._frameOffsets[i]);
uint16 op = env.scr->readUint16LE();
if (op == 0x1101 || op == 0x1111) {
uint16 frameno = env.scr->readUint16LE();
if (frameno == targetFrame) {
retval = i;
break;
}
}
}
env.scr->seek(startpos);
return retval;
}
void TTMInterpreter::findAndAddSequences(TTMEnviro &env, Common::Array<Common::SharedPtr<TTMSeq>> &seqArray) {
int16 envno = env._enviro;
env.scr->seek(0);
uint16 op = 0;
for (uint frame = 0; frame < env._totalFrames; frame++) {
env._frameOffsets[frame] = env.scr->pos();
//debug(1, "findAndAddSequences: frame %d at offset %d", frame, (int)env.scr->pos());
op = env.scr->readUint16LE();
while (op != 0x0ff0 && env.scr->pos() < env.scr->size()) {
//debug(1, "findAndAddSequences: check ttm op %04x", op);
switch (op & 0xf) {
case 0:
break;
case 1:
if (op == 0x1111) {
Common::SharedPtr<TTMSeq> newseq(new TTMSeq());
newseq->_enviro = envno;
newseq->_seqNum = env.scr->readUint16LE();
newseq->_startFrame = frame;
newseq->_currentFrame = frame;
newseq->_lastFrame = -1;
//debug(1, "findAndAddSequences: found env %d seq %d at %d", newseq._enviro, newseq._seqNum, (int)env.scr->pos());
seqArray.push_back(newseq);
} else {
env.scr->skip(2);
}
break;
case 0xf: {
if (op == 0xaf1f || op == 0xaf2f) {
int16 nbytes = env.scr->readUint16LE() * 4;
env.scr->skip(nbytes);
} else {
readTTMStringVal(env.scr);
}
break;
}
default:
env.scr->skip((op & 0xf) * 2);
break;
}
op = env.scr->readUint16LE();
}
}
env.scr->seek(0);
}
void TTMSeq::reset() {
_currentFontId = 0;
_currentPalId = 0;
_currentSongId = 0;
if (DgdsEngine::getInstance()->getGameId() == GID_DRAGON) {
// These slots are not reset in HOC onward
_currentBmpId = 0;
_currentGetPutId = 0;
}
_currentFrame = _startFrame;
_gotoFrame = -1;
_drawColBG = 0xf;
_drawColFG = 0xf;
_brushNum = 0;
_timeInterval = 0;
_timeNext = 0;
_runCount = 0;
_runPlayed = 0;
_executed = false;
_runFlag = kRunTypeStopped;
_scriptFlag = 0;
_selfLoop = false;
_drawWin = Common::Rect(SCREEN_WIDTH, SCREEN_HEIGHT);
}
} // end namespace Dgds