/* 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 "graphics/cursorman.h" #include "dgds/scene_op.h" #include "dgds/dgds.h" #include "dgds/debug_util.h" #include "dgds/globals.h" #include "dgds/inventory.h" #include "dgds/scene.h" #include "dgds/dragon_native.h" #include "dgds/hoc_intro.h" #include "dgds/minigames/dragon_arcade.h" #include "dgds/minigames/china_train.h" #include "dgds/minigames/china_tank.h" namespace Dgds { static Common::String _sceneOpCodeName(SceneOpCode code) { code = static_cast(code & ~kSceneOpHasConditionalOpsFlag); switch (code) { case kSceneOpNone: return "none"; case kSceneOpChangeScene: return "changeScene"; case kSceneOpNoop: return "noop"; case kSceneOpGlobal: return "global"; case kSceneOpSegmentStateOps: return "sceneOpSegmentStateOps"; case kSceneOpSetItemAttr: return "setItemAttr"; case kSceneOpSetDragItem: return "setDragItem"; case kSceneOpOpenInventory: return "openInventory"; case kSceneOpShowDlg: return "showdlg"; case kSceneOpShowInvButton: return "showInvButton"; case kSceneOpHideInvButton: return "hideInvButton"; case kSceneOpEnableTrigger: return "enabletrigger"; case kSceneOpChangeSceneToStored: return "changeSceneToStored"; case kSceneOpAddFlagToDragItem: return "addFlagToDragItem"; case kSceneOpMoveItemsBetweenScenes: return "moveItemsBetweenScenes"; case kSceneOpOpenInventoryZoom: return "openInventoryZoom"; case kSceneOpShowClock: return "showClock"; case kSceneOpHideClock: return "hideClock"; case kSceneOpShowMouse: return "showMouse"; case kSceneOpHideMouse: return "hideMouse"; case kSceneOpLoadTalkDataAndSetFlags: return "loadTalkDataAndSetFlags"; case kSceneOpClearVisibleTalkHeads: return "clearVisibleTalksHeads"; case kSceneOpLoadTalkData: return "loadTalkData"; case kSceneOpLoadDDSData: return "loadDDSData"; case kSceneOpFreeDDSData: return "freeDDSData"; case kSceneOpFreeTalkData: return "freeTalkData"; default: break; } if (DgdsEngine::getInstance()->getGameId() == GID_DRAGON) { switch (code) { case kSceneOpPasscode: return "passcode"; case kSceneOpMeanwhile: return "meanwhile"; case kSceneOpOpenGameOverMenu: return "openGameOverMenu"; case kSceneOpTiredDialog: return "openTiredDialog"; case kSceneOpArcadeTick: return "sceneOpArcadeTick"; case kSceneOpDrawDragonCountdown1: return "drawDragonCountdown1"; case kSceneOpDrawDragonCountdown2: return "drawDragonCountdown2"; case kSceneOpOpenPlaySkipIntroMenu: return "openPlaySkipIntroMovie"; case kSceneOpOpenBetterSaveGameMenu: return "openBetterSaveGameMenu"; default: break; } } else if (DgdsEngine::getInstance()->getGameId() == GID_HOC) { switch (code) { case kSceneOpChinaTankInit: return "tankInit"; case kSceneOpChinaTankEnd: return "tankEnd"; case kSceneOpChinaTankTick: return "tankTick"; case kSceneOpChinaScrollLeft: return "scrollLeft"; case kSceneOpChinaScrollRight: return "scrollRight"; case kSceneOpShellGameInit: return "shellGameInit"; case kSceneOpShellGameEnd: return "shellGameEnd"; case kSceneOpShellGameTick: return "shellGameTick"; case kSceneOpChinaTrainInit: return "trainInit"; case kSceneOpChinaTrainEnd: return "trainEnd"; case kSceneOpChinaTrainTick: return "trainTick"; case kSceneOpChinaOpenGameOverMenu: return "gameOverMenu"; case kSceneOpChinaOpenSkipCreditsMenu: return "skipCreditsMenu"; case kSceneOpChinaOnIntroInit: return "chinaOnIntroInit"; case kSceneOpChinaOnIntroTick: return "chinaOnIntroTick"; case kSceneOpChinaOnIntroEnd: return "chinaOnIntroEnd"; default: break; } } else if (DgdsEngine::getInstance()->getGameId() == GID_WILLY) { switch (code) { case kSceneOpOpenBeamishGameOverMenu: return "openGameOverMenu"; case kSceneOpOpenBeamishOpenSkipCreditsMenu: return "skipCreditsMenu"; default: break; } } return Common::String::format("sceneOp%d", (int)code); } Common::String SceneOp::dump(const Common::String &indent) const { Common::String argsStr; if (_args.empty()) { argsStr = "[]"; } else { argsStr = "["; for (uint i : _args) argsStr += Common::String::format("%d ", i); argsStr.setChar(']', argsStr.size() - 1); } const Common::String opName = _sceneOpCodeName(_opCode); const char *isConditional = (_opCode & kSceneOpHasConditionalOpsFlag) ? "(cond)": ""; Common::String str = Common::String::format("%sSceneOpgetGameId()) { case GID_DRAGON: sceneChanged = runDragonOp(); break; case GID_HOC: sceneChanged = runChinaOp(); break; case GID_WILLY: sceneChanged = runBeamishOp(); break; default: error("TODO: Implement game-specific scene op for this game"); } } return sceneChanged; } bool SceneOp::runCommonOp() const { DgdsEngine *engine = DgdsEngine::getInstance(); switch (_opCode) { case kSceneOpChangeScene: if (engine->changeScene(_args[0])) return true; break; case kSceneOpNoop: // // This is run when the "quit willy beamish help" dialog item is called. // There may be a nicer way to do this? // if (engine->getScene()->getNum() == 80 && engine->getGameId() == GID_WILLY) { int16 sceneNo = engine->getGameGlobals()->getGlobal(0x61); if (engine->changeScene(sceneNo)) return true; } break; case kSceneOpGlobal: // The globals are held by the GDS scene engine->getGDSScene()->globalOps(_args); break; case kSceneOpSegmentStateOps: SDSScene::segmentStateOps(_args); break; case kSceneOpSetItemAttr: SDSScene::setItemAttrOp(_args); break; case kSceneOpSetDragItem: SDSScene::setDragItemOp(_args); break; case kSceneOpOpenInventory: engine->getInventory()->open(); // This implicitly changes scene num break; case kSceneOpShowDlg: if (_args.size() == 1) engine->getScene()->showDialog(0, _args[0]); else if (_args.size() > 1) engine->getScene()->showDialog(_args[0], _args[1]); break; case kSceneOpShowInvButton: engine->getScene()->addInvButtonToHotAreaList(); break; case kSceneOpHideInvButton: engine->getScene()->removeInvButtonFromHotAreaList(); break; case kSceneOpEnableTrigger: if (_args.size() == 1) engine->getScene()->enableTrigger(0, _args[0]); else if (_args.size() > 1) engine->getScene()->enableTrigger(_args[1], _args[0]); break; case kSceneOpChangeSceneToStored: { int16 sceneNo = engine->getGameGlobals()->getGlobal(0x61); if (engine->changeScene(sceneNo)) return true; break; } case kSceneOpAddFlagToDragItem: { GameItem *item = engine->getScene()->getDragItem(); if (item) { item->_flags |= kItemStateDragging; // TODO: Use hot x/y or just position? Common::Point lastMouse = engine->getLastMouseMinusHot(); item->_rect.x = lastMouse.x; item->_rect.y = lastMouse.y; } break; } case kSceneOpOpenInventoryZoom: engine->getInventory()->setShowZoomBox(true); engine->getInventory()->open(); return true; case kSceneOpMoveItemsBetweenScenes: { int16 fromScene = engine->getGameGlobals()->getGlobal(0x55); int16 toScene = engine->getGameGlobals()->getGlobal(0x54); for (auto &item : engine->getGDSScene()->getGameItems()) { if (item._inSceneNum == fromScene) item._inSceneNum = toScene; } break; } case kSceneOpShowClock: engine->setShowClock(true); break; case kSceneOpHideClock: engine->setShowClock(false); break; case kSceneOpShowMouse: CursorMan.showMouse(true); break; case kSceneOpHideMouse: CursorMan.showMouse(false); break; case kSceneOpLoadTalkDataAndSetFlags: // args: tdsnum to load, headnum engine->getScene()->loadTalkDataAndSetFlags(_args[0], _args[1]); break; case kSceneOpClearVisibleTalkHeads: // args: none engine->getScene()->clearVisibleTalkers(); break; case kSceneOpLoadTalkData: // args: tds num to load engine->getScene()->loadTalkData(_args[0]); break; case kSceneOpLoadDDSData: // args: dds num to load if (_args[0]) engine->getScene()->loadDialogData(_args[0]); break; case kSceneOpFreeDDSData: // args: dds num to free engine->getScene()->freeDialogData(_args[0]); break; case kSceneOpFreeTalkData: // args: tds num to free engine->getScene()->freeTalkData(_args[0]); break; default: warning("TODO: Implement generic scene op %d", _opCode); break; } return false; } bool SceneOp::runDragonOp() const { DgdsEngine *engine = DgdsEngine::getInstance(); switch (_opCode) { case kSceneOpPasscode: DragonNative::updatePasscodeGlobal(); break; case kSceneOpMeanwhile: // TODO: Should we draw "meanwhile" like the original? it just gets overwritten with the image anyway. // Probably need to do something here to avoid flashing.. //engine->_compositionBuffer.fillRect(Common::Rect(SCREEN_WIDTH, SCREEN_HEIGHT), 0); break; case kSceneOpOpenGameOverMenu: engine->setMenuToTrigger(kMenuGameOver); break; case kSceneOpTiredDialog: engine->getInventory()->close(); engine->getScene()->addAndShowTiredDialog(); break; case kSceneOpArcadeTick: // TODO: Add a configuration option to skip arcade sequence? // g_system->displayMessageOnOSD(_("Skipping DGDS arcade sequence")); // engine->getGameGlobals()->setGlobal(0x21, 6); engine->getDragonArcade()->arcadeTick(); break; case kSceneOpDrawDragonCountdown1: DragonNative::drawCountdown(FontManager::k4x5Font, 141, 56); break; case kSceneOpDrawDragonCountdown2: DragonNative::drawCountdown(FontManager::k8x8Font, 250, 42); break; case kSceneOpOpenPlaySkipIntroMenu: engine->setMenuToTrigger(kMenuSkipPlayIntro); break; case kSceneOpOpenBetterSaveGameMenu: engine->setMenuToTrigger(kMenuSaveBeforeArcade); break; default: error("Unexpected Dragon scene opcode %d", _opCode); break; } return false; } bool SceneOp::runChinaOp() const { DgdsEngine *engine = DgdsEngine::getInstance(); switch (_opCode) { case kSceneOpChinaTankInit: engine->getChinaTank()->init(); break; case kSceneOpChinaTankEnd: engine->getChinaTank()->end(); break; case kSceneOpChinaTankTick: engine->getChinaTank()->tick(); break; case kSceneOpShellGameTick: engine->getShellGame()->shellGameTick(); break; case kSceneOpShellGameEnd: engine->getShellGame()->shellGameEnd(); break; case kSceneOpChinaTrainInit: engine->getChinaTrain()->init(); break; case kSceneOpChinaTrainEnd: engine->getChinaTrain()->end(); break; case kSceneOpChinaTrainTick: engine->getChinaTrain()->tick(); break; case kSceneOpChinaOpenGameOverMenu: engine->setMenuToTrigger(kMenuGameOver); break; case kSceneOpChinaOpenSkipCreditsMenu: engine->setMenuToTrigger(kMenuSkipPlayIntro); break; case kSceneOpChinaOnIntroInit: engine->getHocIntro()->init(); break; case kSceneOpChinaOnIntroTick: engine->getHocIntro()->tick(); break; case kSceneOpChinaOnIntroEnd: engine->getHocIntro()->end(); break; case kSceneOpChinaScrollIntro: case kSceneOpChinaScrollLeft: case kSceneOpChinaScrollRight: // These map to null functions. break; default: warning("TODO: Implement china-specific scene opcode: (%s)", dump("").c_str()); break; } return false; } bool SceneOp::runBeamishOp() const { DgdsEngine *engine = DgdsEngine::getInstance(); if (_opCode & kSceneOpHasConditionalOpsFlag) { uint16 opcode = _opCode & ~kSceneOpHasConditionalOpsFlag; for (const ConditionalSceneOp &cop : engine->getScene()->getConditionalOps()) { if (cop._opCode == opcode && SceneConditions::check(cop._conditionList)) { if (!Scene::runOps(cop._opList)) return true; } } return false; } switch (_opCode) { case kSceneOpOpenBeamishGameOverMenu: engine->setMenuToTrigger(kMenuGameOver); break; case kSceneOpOpenBeamishOpenSkipCreditsMenu: engine->setMenuToTrigger(kMenuSkipPlayIntro); break; default: warning("TODO: Implement beamish-specific scene opcode %d", _opCode); break; } return false; } Common::String ConditionalSceneOp::dump(const Common::String &indent) const { const Common::String opName = _sceneOpCodeName(static_cast(_opCode)); Common::String str = Common::String::format("%sConditionalSceneOp