From 5695018cd3bbdfdcd695ed3d65770cba38fd33ad Mon Sep 17 00:00:00 2001 From: Kaloyan Chehlarski Date: Sun, 11 Feb 2024 00:31:38 +0100 Subject: [PATCH] NANCY: Implement InteractiveVideo Implemented an action record type that adds hotspots on top of a SecondaryMovie. --- engines/nancy/action/arfactory.cpp | 3 + engines/nancy/action/interactivevideo.cpp | 148 ++++++++++++++++++++++ engines/nancy/action/interactivevideo.h | 76 +++++++++++ engines/nancy/action/secondarymovie.cpp | 7 + engines/nancy/action/secondarymovie.h | 3 + engines/nancy/module.mk | 1 + engines/nancy/state/scene.cpp | 12 ++ engines/nancy/state/scene.h | 4 + 8 files changed, 254 insertions(+) create mode 100644 engines/nancy/action/interactivevideo.cpp create mode 100644 engines/nancy/action/interactivevideo.h diff --git a/engines/nancy/action/arfactory.cpp b/engines/nancy/action/arfactory.cpp index aad9a948fba..6be4f83cd8f 100644 --- a/engines/nancy/action/arfactory.cpp +++ b/engines/nancy/action/arfactory.cpp @@ -27,6 +27,7 @@ #include "engines/nancy/action/autotext.h" #include "engines/nancy/action/conversation.h" +#include "engines/nancy/action/interactivevideo.h" #include "engines/nancy/action/overlay.h" #include "engines/nancy/action/secondaryvideo.h" #include "engines/nancy/action/secondarymovie.h" @@ -119,6 +120,8 @@ ActionRecord *ActionManager::createActionRecord(uint16 type, Common::SeekableRea newRec->_isTerse = true; return newRec; } + case 26: + return new InteractiveVideo(); case 40: if (g_nancy->getGameType() < kGameTypeNancy2) { // Only used in TVD diff --git a/engines/nancy/action/interactivevideo.cpp b/engines/nancy/action/interactivevideo.cpp new file mode 100644 index 00000000000..2bbf50d8ff1 --- /dev/null +++ b/engines/nancy/action/interactivevideo.cpp @@ -0,0 +1,148 @@ +/* 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 "engines/nancy/nancy.h" +#include "engines/nancy/util.h" +#include "engines/nancy/resource.h" +#include "engines/nancy/video.h" +#include "engines/nancy/input.h" + +#include "engines/nancy/action/interactivevideo.h" +#include "engines/nancy/action/secondarymovie.h" +#include "engines/nancy/state/scene.h" + +namespace Nancy { +namespace Action { + +void InteractiveVideo::readData(Common::SeekableReadStream &stream) { + Common::Path ivFilename; + readFilename(stream, ivFilename); + + stream.skip(2); + + _flags.resize(15); + _cursors.resize(5); + + for (uint i = 0; i < 15; ++i) { + _flags[i].label = stream.readSint16LE(); + _flags[i].flag = stream.readSint16LE(); + } + + for (uint i = 0; i < 5; ++ i) { + _cursors[i] = stream.readSint16LE(); + } + + Common::SeekableReadStream *ivFile = SearchMan.createReadStreamForMember(ivFilename.append(".iv")); + assert(ivFile); + + readFilename(*ivFile, _videoName); + + uint32 numFrames = ivFile->readUint32LE(); + _frames.resize(numFrames); + for (uint i = 0; i < numFrames; ++i) { + InteractiveFrame &frame = _frames[i]; + frame.frameID = ivFile->readUint16LE(); + uint16 numHotspots = ivFile->readUint16LE(); + frame.triggerOnNoHotspot = ivFile->readByte(); + frame.noHSFlagID = ivFile->readSint16LE(); + frame.noHSCursorID = ivFile->readSint16LE(); + + frame.hotspots.resize(numHotspots); + for (uint j = 0; j < numHotspots; ++j) { + ivFile->skip(4); + readRect(*ivFile, frame.hotspots[j].hotspot); + frame.hotspots[j].flagID = ivFile->readSint16LE(); + frame.hotspots[j].cursorID = ivFile->readSint16LE(); + } + } + + delete ivFile; +} + +void InteractiveVideo::execute() { + switch (_state) { + case kBegin: + _movieAR = NancySceneState.getActiveMovie(); + if (!_movieAR || _movieAR->_state == kRun) { + _state = kRun; + } + + break; + case kRun: + if (_movieAR->_state == kActionTrigger || _movieAR->_isFinished) { + _state = kActionTrigger; + } + + break; + case kActionTrigger: + finishExecution(); + break; + } +} + +void InteractiveVideo::handleInput(NancyInput &input) { + if (_state != kRun) { + return; + } + + int curFrame = _movieAR->_decoder->getCurFrame(); + if (curFrame < 0) { + return; + } + + for (auto &frame : _frames) { + if (frame.frameID == curFrame) { + // Found data for the current video frame + + // First, look through the hotspots for the frame + for (auto &hotspot : frame.hotspots) { + if (NancySceneState.getViewport().convertViewportToScreen(hotspot.hotspot).contains(input.mousePos)) { + // Mouse is in a hotspot, change cursor and set flag if clicked + if (hotspot.cursorID >= 0 && _cursors[hotspot.cursorID] >= 0) { + g_nancy->_cursor->setCursorType((CursorManager::CursorType)_cursors[hotspot.cursorID]); + } + + if (input.input & NancyInput::kLeftMouseButtonUp) { + NancySceneState.setEventFlag(_flags[hotspot.flagID]); + } + + return; + } + } + + // Mouse is not in a hotspot for the frame, check if we have a default action + if (frame.triggerOnNoHotspot) { + if (frame.noHSCursorID >= 0 && _cursors[frame.noHSCursorID] >= 0) { + g_nancy->_cursor->setCursorType((CursorManager::CursorType)_cursors[frame.noHSCursorID]); + } + + if (input.input & NancyInput::kLeftMouseButtonUp) { + NancySceneState.setEventFlag(_flags[frame.noHSFlagID]); + } + } + + return; + } + } +} + +} // End of namespace Action +} // End of namespace Nancy diff --git a/engines/nancy/action/interactivevideo.h b/engines/nancy/action/interactivevideo.h new file mode 100644 index 00000000000..774c6c43d25 --- /dev/null +++ b/engines/nancy/action/interactivevideo.h @@ -0,0 +1,76 @@ +/* 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 . + * + */ + +#ifndef NANCY_ACTION_INTERACTIVEVIDEO_H +#define NANCY_ACTION_INTERACTIVEVIDEO_H + +#include "engines/nancy/action/actionrecord.h" + +namespace Nancy { +namespace Action { + +class ActionManager; +class PlaySecondaryMovie; + +class InteractiveVideo : public ActionRecord { + friend class ActionManager; + friend class PlaySecondaryMovie; +public: + InteractiveVideo() {} + virtual ~InteractiveVideo() {} + + void readData(Common::SeekableReadStream &stream) override; + void execute() override; + + void handleInput(NancyInput &input) override; + +protected: + Common::String getRecordTypeName() const override { return "InteractiveVideo"; } + + struct InteractiveHotspot { + Common::Rect hotspot; + int16 flagID = -1; + int16 cursorID = -1; + }; + + struct InteractiveFrame { + uint16 frameID = 0; + bool triggerOnNoHotspot = false; + int16 noHSFlagID = -1; + int16 noHSCursorID = -1; + Common::Array hotspots; + }; + + Common::Array _flags; + Common::Array _cursors; + + // IV file data + Common::Path _videoName; + Common::Array _frames; + + // Pointer to a movie AR + PlaySecondaryMovie *_movieAR = nullptr; +}; + +} // End of namespace Action +} // End of namespace Nancy + +#endif // NANCY_ACTION_INTERACTIVEVIDEO_H diff --git a/engines/nancy/action/secondarymovie.cpp b/engines/nancy/action/secondarymovie.cpp index 81cb115eb2e..90a1c43bfc2 100644 --- a/engines/nancy/action/secondarymovie.cpp +++ b/engines/nancy/action/secondarymovie.cpp @@ -38,6 +38,10 @@ namespace Action { PlaySecondaryMovie::~PlaySecondaryMovie() { delete _decoder; + if (NancySceneState.getActiveMovie() == this) { + NancySceneState.setActiveMovie(nullptr); + } + if (_playerCursorAllowed == kNoPlayerCursorAllowed) { g_nancy->setMouseEnabled(true); } @@ -148,6 +152,8 @@ void PlaySecondaryMovie::execute() { g_nancy->setMouseEnabled(false); } + NancySceneState.setActiveMovie(this); + _state = kRun; if (Common::Rect(_decoder->getWidth(), _decoder->getHeight()) == NancySceneState.getViewport().getBounds()) { @@ -243,6 +249,7 @@ void PlaySecondaryMovie::execute() { } } + NancySceneState.setActiveMovie(nullptr); finishExecution(); // Allow looping diff --git a/engines/nancy/action/secondarymovie.h b/engines/nancy/action/secondarymovie.h index 05e2acb917e..351bcdde1a3 100644 --- a/engines/nancy/action/secondarymovie.h +++ b/engines/nancy/action/secondarymovie.h @@ -31,6 +31,8 @@ class VideoDecoder; namespace Nancy { namespace Action { +class InteractiveVideo; + // Plays an AVF or Bink video. Optionally supports: // - playing a sound; // - reverse playback; @@ -40,6 +42,7 @@ namespace Action { // - changing the scene after playback ends // Mostly used for cinematics, with some occasional uses for background animations class PlaySecondaryMovie : public RenderActionRecord { + friend class InteractiveVideo; public: static const byte kMovieSceneChange = 5; static const byte kMovieNoSceneChange = 6; diff --git a/engines/nancy/module.mk b/engines/nancy/module.mk index 288a1a80e5f..0f949472312 100644 --- a/engines/nancy/module.mk +++ b/engines/nancy/module.mk @@ -11,6 +11,7 @@ MODULE_OBJS = \ action/soundrecords.o \ action/miscrecords.o \ action/conversation.o \ + action/interactivevideo.o \ action/overlay.o \ action/secondarymovie.o \ action/secondaryvideo.o \ diff --git a/engines/nancy/state/scene.cpp b/engines/nancy/state/scene.cpp index 1dec0d1af10..9d394a19ac6 100644 --- a/engines/nancy/state/scene.cpp +++ b/engines/nancy/state/scene.cpp @@ -126,6 +126,7 @@ Scene::Scene() : _clock(nullptr), _actionManager(), _difficulty(0), + _activeMovie(nullptr), _activeConversation(nullptr), _lightning(nullptr), _destroyOnExit(false), @@ -831,6 +832,14 @@ void Scene::init() { g_nancy->_graphics->redrawAll(); } +void Scene::setActiveMovie(Action::PlaySecondaryMovie *activeMovie) { + _activeMovie = activeMovie; +} + +Action::PlaySecondaryMovie *Scene::getActiveMovie() { + return _activeMovie; +} + void Scene::setActiveConversation(Action::ConversationSound *activeConversation) { _activeConversation = activeConversation; } @@ -1225,6 +1234,9 @@ void Scene::clearSceneData() { // Hopefully this doesn't cause issues with earlier games. _textbox.clear(); } + + _activeConversation = nullptr; + _activeMovie = nullptr; } void Scene::clearPuzzleData() { diff --git a/engines/nancy/state/scene.h b/engines/nancy/state/scene.h index 9d2a8ea7076..04358e2578a 100644 --- a/engines/nancy/state/scene.h +++ b/engines/nancy/state/scene.h @@ -50,6 +50,7 @@ struct SceneChangeDescription; namespace Action { class ConversationSound; +class PlaySecondaryMovie; } namespace Misc { @@ -176,6 +177,8 @@ public: SceneChangeDescription &getNextSceneInfo() { return _sceneState.nextScene; } const SceneSummary &getSceneSummary() const { return _sceneState.summary; } + void setActiveMovie(Action::PlaySecondaryMovie *activeMovie); + Action::PlaySecondaryMovie *getActiveMovie(); void setActiveConversation(Action::ConversationSound *activeConversation); Action::ConversationSound *getActiveConversation(); @@ -286,6 +289,7 @@ private: Common::HashMap _puzzleData; Action::ActionManager _actionManager; + Action::PlaySecondaryMovie *_activeMovie; Action::ConversationSound *_activeConversation; // Contains a screenshot of the Scene state from the last time it was exited