scummvm/engines/dgds/scene_op.cpp
2025-03-02 14:52:01 +11:00

433 lines
13 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 "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<SceneOpCode>(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("%sSceneOp<op: %s%s args: %s", indent.c_str(), opName.c_str(), isConditional, argsStr.c_str());
str += DebugUtil::dumpStructList(indent, "conditionList", _conditionList);
if (!_conditionList.empty()) {
str += "\n";
str += indent;
}
str += ">";
return str;
}
bool SceneOp::runOp() const {
bool sceneChanged;
if (_opCode < 100) {
sceneChanged = runCommonOp();
} else {
// Game-specific opcode
switch (DgdsEngine::getInstance()->getGameId()) {
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<SceneOpCode>(_opCode));
Common::String str = Common::String::format("%sConditionalSceneOp<op: %s", indent.c_str(), opName.c_str());
str += DebugUtil::dumpStructList(indent, "conditionList", _conditionList);
if (!_conditionList.empty()) {
str += indent;
}
str += DebugUtil::dumpStructList(indent, "opList", _opList);
if (!_opList.empty()) {
str += "\n";
str += indent;
}
str += ">";
return str;
}
} // end namespace Dgds