/* 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