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