/* 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 .
*
*/
#include "dgds/minigames/dragon_arcade_ttm.h"
#include "dgds/minigames/dragon_arcade.h"
#include "dgds/ads.h"
#include "dgds/drawing.h"
#include "dgds/sound.h"
#include "dgds/includes.h"
namespace Dgds {
Common::String ArcadeFloor::dump() const {
return Common::String::format("ArcadeFloor",
x, x + width, yval, flag);
}
DragonArcadeTTM::DragonArcadeTTM(ArcadeNPCState *npcState) : _npcState(npcState),
_currentTTMNum(0), _currentNPCRunningTTM(0), _drawXOffset(0), _drawYOffset(0),
_startYOffset(0), _doingInit(false), _drawColBG(0), _drawColFG(0)
{
ARRAYCLEAR(_shapes3);
}
void DragonArcadeTTM::clearDataPtrs() {
for (int i = 0; i < 5; i++) {
_ttmEnvs[i] = TTMEnviro();
}
// TODO: Is this used anywhere?
// INT_39e5_3cb8 = -1;
}
int16 DragonArcadeTTM::load(const char *filename) {
TTMEnviro *env = nullptr;
int16 envNum;
for (envNum = 0; envNum < ARRAYSIZE(_ttmEnvs); envNum++) {
if (_ttmEnvs[envNum].scr == nullptr) {
env = &_ttmEnvs[envNum];
debug(1, "Arcade TTM load %s into env %d", filename, envNum);
break;
}
}
if (!env)
error("Trying to load too many TTMs in Dragon arcade");
DgdsEngine *engine = DgdsEngine::getInstance();
TTMParser dgds(engine->getResourceManager(), engine->getDecompressor());
bool parseResult = dgds.parse(env, filename);
if (!parseResult)
error("Error loading dgds arcade script %s", filename);
env->scr->seek(0);
return envNum;
}
void DragonArcadeTTM::finishTTMParse(int16 envNum) {
TTMEnviro &env = _ttmEnvs[envNum];
if (!env.scr)
error("DragonArcadeTTM::finishTTMParse: script env %d not loaded", envNum);
// Discover the frame offsets
uint16 op = 0;
for (uint frame = 0; frame < env._totalFrames; frame++) {
env._frameOffsets[frame] = env.scr->pos();
op = env.scr->readUint16LE();
while (op != 0x0ff0 && env.scr->pos() < env.scr->size()) {
switch (op & 0xf) {
case 0:
break;
case 0xf: {
TTMInterpreter::readTTMStringVal(env.scr);
break;
}
default:
env.scr->skip((op & 0xf) * 2);
break;
}
op = env.scr->readUint16LE();
}
}
env.scr->seek(0);
}
int16 DragonArcadeTTM::runNextPage(int16 pageNum) {
_shapes2[_currentTTMNum] = _shapes[_currentTTMNum];
// TODO: what is this?
//UINT_39e5_3ca2 = 0;
if (pageNum < _ttmEnvs[_currentTTMNum]._totalFrames && pageNum > -1 &&
_ttmEnvs[_currentTTMNum]._frameOffsets[pageNum] > -1) {
return runScriptPage(pageNum);
} else {
return 0;
}
}
int16 DragonArcadeTTM::handleOperation(TTMEnviro &env, int16 page, uint16 op, byte count, const int16 *ivals, const Common::String &sval) {
DgdsEngine *engine = DgdsEngine::getInstance();
Graphics::ManagedSurface &compBuffer = engine->_compositionBuffer;
switch (op) {
case 0x0020:
// This doesn't seem explicitly handled in the original, but appears in
// arcade sequence 2 - just ignore it??
break;
case 0x0070:
// Do nothing.
break;
case 0x0080: // FREE SHAPE
_allShapes[_shapes3[_currentTTMNum] * 5 + _currentTTMNum].reset();
_shapes[_currentTTMNum].reset();
break;
case 0x1021: // SET DELAY
engine->adsInterpreter()->setScriptDelay((int)(ivals[0] * MS_PER_FRAME));
break;
case 0x1031: // SET BRUSH
//debug(1, "Set brush %d for slot %d", ivals[0], _currentTTMNum);
if (!_shapes2[_currentTTMNum]) {
_brushes[_currentTTMNum].reset();
} else {
_brushes[_currentTTMNum] = Brush(_shapes2[_currentTTMNum], ivals[0]);
}
break;
case 0x1051: // SET SHAPE
_shapes3[_currentTTMNum] = ivals[0];
//debug(1, "Set img %d into slot %d", ivals[0], _currentTTMNum);
_shapes[_currentTTMNum] = _allShapes[ivals[0] * 5 + _currentTTMNum];
_shapes2[_currentTTMNum] = _allShapes[ivals[0] * 5 + _currentTTMNum];
break;
case 0x1061:
// Do nothing (ignore arg)
break;
case 0x1101:
case 0x1111:
// Do nothing (ignore arg)
break;
case 0x1201:
// This doesn't seem explicitly handled in the original, but appears in
// arcade sequence 1 - just ignore it??
break;
case 0x2002: // SET COLORS
_drawColFG = (byte)ivals[0];
_drawColBG = (byte)ivals[1];
break;
case 0x2012: { // PLAY SOUND
int16 sound;
if (ivals[0] == 0 || ivals[0] == 1) {
sound = 0x26;
} else if (ivals[0] == 2) {
sound = 0x4f;
} else {
break;
}
engine->_soundPlayer->playSFX(sound);
break;
}
case 0x4504: { // SET NPC POS 1
int16 x = _drawXOffset + ivals[0];
int16 y = _drawYOffset + ivals[1] + 2;
_npcState[_currentNPCRunningTTM].x_11 = x;
_npcState[_currentNPCRunningTTM].x_12 = x + ivals[2];
_npcState[_currentNPCRunningTTM].y_11 = y;
_npcState[_currentNPCRunningTTM].y_12 = y + ivals[3];
break;
}
case 0x4514: {// SET NPC POS 2
int16 x = _drawXOffset + ivals[0];
int16 y = _drawYOffset + ivals[1] + 2;
_npcState[_currentNPCRunningTTM].x_21 = x;
_npcState[_currentNPCRunningTTM].x_22 = x + ivals[2];
_npcState[_currentNPCRunningTTM].y_21 = y;
_npcState[_currentNPCRunningTTM].y_22 = y + ivals[3];
break;
}
case 0xA0A4: { // DRAW LINE
compBuffer.drawLine(_drawXOffset + ivals[0], _drawYOffset + ivals[1] + 2, _drawXOffset + ivals[2], _drawYOffset + ivals[3] + 2, _drawColFG);
break;
}
case 0xA104: // DRAW FILLED RECT
if (_doingInit) {
ArcadeFloor data;
data.x = (page - 1) * SCREEN_WIDTH + ivals[0];
data.width = ivals[2];
data.yval = (byte)ivals[1];
data.flag = false;
debug(1, "Floor: %s", data.dump().c_str());
_floorData.push_back(data);
} else {
const Common::Rect rect(Common::Point(ivals[0], ivals[1]), ivals[2], ivals[3]);
compBuffer.fillRect(rect, _drawColFG);
}
break;
case 0xA114: // DRAW EMPTY RECT
if (_doingInit) {
ArcadeFloor data;
data.x = (page - 1) * SCREEN_WIDTH + ivals[0];
data.width = ivals[2];
data.yval = (byte)ivals[1];
data.flag = true;
debug(1, "Floor: %s", data.dump().c_str());
_floorData.push_back(data);
} else {
const Common::Rect r(Common::Point(ivals[0], ivals[1]), ivals[2], ivals[3]);
const Common::Rect drawWin(SCREEN_WIDTH, SCREEN_HEIGHT);
Drawing::rectClipped(r, drawWin, &compBuffer, _drawColFG);
}
break;
case 0xA404: { // DRAW FILLED CIRCLE
int16 r = ivals[3] / 2;
Drawing::filledCircle(ivals[0], ivals[1], r, r, &compBuffer, _drawColFG, _drawColBG);
break;
}
case 0xA424: { // DRAW EMPTY CIRCLE
int16 r = ivals[3] / 2;
Drawing::emptyCircle(ivals[0], ivals[1], r, r, &compBuffer, _drawColFG);
break;
}
case 0xA502:
case 0xA512:
case 0xA522:
case 0xA532: { // DRAW SHAPE
if (_doingInit)
break;
ImageFlipMode flipMode = kImageFlipNone;
if (op == 0xa512)
flipMode = kImageFlipV;
else if (op == 0xa522)
flipMode = kImageFlipH;
else if (op == 0xa532)
flipMode = kImageFlipHV;
// Only draw in the scroll area
const Common::Rect drawWin(Common::Point(8, 8), SCREEN_WIDTH - 16, 117);
if (_currentNPCRunningTTM == 0) {
int16 x = ivals[0] + _npcState[0].x - 152;
int16 y = ivals[1] + _startYOffset + 2;
if (_brushes[_currentTTMNum].isValid())
_brushes[_currentTTMNum].getShape()->drawBitmap(_brushes[_currentTTMNum].getFrame(), x, y, drawWin, compBuffer, flipMode);
_npcState[0].y = y;
} else {
int16 x = ivals[0] + _drawXOffset;
int16 y = ivals[1] + _drawYOffset + 2;
if (_brushes[_currentTTMNum].isValid())
_brushes[_currentTTMNum].getShape()->drawBitmap(_brushes[_currentTTMNum].getFrame(), x, y, drawWin, compBuffer, flipMode);
_npcState[_currentNPCRunningTTM].x = x;
_npcState[_currentNPCRunningTTM].y = y;
}
break;
}
case 0xF02F: {
_shapes[_currentTTMNum].reset(new Image(engine->getResourceManager(), engine->getDecompressor()));
_shapes[_currentTTMNum]->loadBitmap(sval);
debug(1, "Load img %s into slot %d", sval.c_str(), _currentTTMNum);
_shapes2[_currentTTMNum] = _shapes[_currentTTMNum];
_allShapes[_shapes3[_currentTTMNum] * 5 + _currentTTMNum] = _shapes[_currentTTMNum];
break;
}
case 0x505F:
// Do nothing (ignore arg)
break;
default:
warning("Unsupported TTM opcode 0x%04x for Dragon arcade.", op);
break;
}
return 0;
}
int16 DragonArcadeTTM::runScriptPage(int16 pageNum) {
Common::SeekableReadStream *scr = _ttmEnvs[_currentTTMNum].scr;
scr->seek(_ttmEnvs[_currentTTMNum]._frameOffsets[pageNum]);
uint16 opcode = scr->readUint16LE();
while (opcode != 0x0ff0 && opcode) {
int16 ivals[4] { 0, 0, 0, 0 };
Common::String sval;
byte count = (byte)(opcode & 0xf);
if (count <= 4) {
for (int i = 0; i < count; i++)
ivals[i] = scr->readUint16LE();
} else if (count == 0xf) {
sval = TTMInterpreter::readTTMStringVal(scr);
} else {
error("Unsupported TTM opcode 0x%04x with %d args for Dragon arcade.", opcode, count);
}
handleOperation(_ttmEnvs[_currentTTMNum], pageNum, opcode, count, ivals, sval);
opcode = scr->readUint16LE();
}
return 1;
}
void DragonArcadeTTM::runPagesForEachNPC(int16 xScrollOffset) {
for (_currentNPCRunningTTM = 19; _currentNPCRunningTTM > 0; _currentNPCRunningTTM--) {
ArcadeNPCState &npcState = _npcState[_currentNPCRunningTTM];
if (npcState.byte12) {
npcState.x_21 = 0;
npcState.x_11 = 0;
npcState.x_22 = 0;
npcState.x_12 = 0;
npcState.y_21 = 0;
npcState.y_11 = 0;
npcState.y_22 = 0;
npcState.y_12 = 0;
_drawXOffset = npcState.xx - xScrollOffset * 8 - 152;
_drawYOffset = npcState.yy;
_currentTTMNum = npcState.ttmNum;
// The original does this comparison, but it seems like a bug (should be &&)
// We could correct the check, but better to maintain bug compatibility.
// if (_drawXOffset > -20 || _drawXOffset < 340)
runNextPage(npcState.ttmPage);
}
}
}
void DragonArcadeTTM::freePages(uint16 num) {
delete _ttmEnvs[num].scr;
_ttmEnvs[num] = TTMEnviro();
}
void DragonArcadeTTM::freeShapes() {
_shapes3[_currentTTMNum] = 0;
_shapes[_currentTTMNum].reset();
_shapes2[_currentTTMNum].reset();
for (int i = 0; i < 6; i++) {
_allShapes[i * 5 + _currentTTMNum].reset();
}
}
} // end namespace Dgds