From d8d8d093cc773173ea7812e3763562c96e3eae7d Mon Sep 17 00:00:00 2001 From: Eldbury Date: Sun, 19 Apr 2026 22:07:49 +0930 Subject: [PATCH 1/5] Honor StartNewModule movie arguments before transition --- include/reone/game/game.h | 4 ++ src/libs/game/game.cpp | 64 ++++++++++++++++------ src/libs/game/script/routine/impl/main.cpp | 18 +++++- 3 files changed, 68 insertions(+), 18 deletions(-) diff --git a/include/reone/game/game.h b/include/reone/game/game.h index 2f28420bc..dce70de67 100644 --- a/include/reone/game/game.h +++ b/include/reone/game/game.h @@ -183,6 +183,7 @@ class Game : boost::noncopyable { void loadModule(const std::string &name, std::string entry = ""); void scheduleModuleTransition(const std::string &moduleName, const std::string &entry); + void scheduleModuleTransitionWithMovies(const std::string &moduleName, const std::string &entry, std::vector movies); // END Module loading @@ -321,6 +322,7 @@ class Game : boost::noncopyable { Screen _screen {Screen::None}; std::shared_ptr _movie; + std::vector _moduleTransitionMovies; resource::CursorType _cursorType {resource::CursorType::None}; std::shared_ptr _cursor; float _gameSpeed {1.0f}; @@ -408,6 +410,8 @@ class Game : boost::noncopyable { // Updates + bool startVideo(const std::string &name); + bool playNextModuleTransitionMovie(); void updateMovie(float dt); void updateMusic(); void updateCamera(float dt); diff --git a/src/libs/game/game.cpp b/src/libs/game/game.cpp index 221e3c576..a8737dcdd 100644 --- a/src/libs/game/game.cpp +++ b/src/libs/game/game.cpp @@ -412,15 +412,8 @@ void Game::setCursorType(CursorType type) { } void Game::playVideo(const std::string &name) { - _movie = _services.resource.movies.get(name); - if (!_movie) { - return; - } - - if (_music) { - _music->stop(); - _music.reset(); - } + _moduleTransitionMovies.clear(); + startVideo(name); } void Game::playMusic(const std::string &resRef) { @@ -526,14 +519,6 @@ void Game::renderGUI() { } } -void Game::updateMovie(float dt) { - _movie->update(dt); - - if (_movie->isFinished()) { - _movie.reset(); - } -} - void Game::updateMusic() { if (_musicResRef.empty()) { return; @@ -563,6 +548,51 @@ void Game::stopMovement() { void Game::scheduleModuleTransition(const std::string &moduleName, const std::string &entry) { _nextModule = moduleName; _nextEntry = entry; + _moduleTransitionMovies.clear(); +} + +void Game::scheduleModuleTransitionWithMovies(const std::string &moduleName, const std::string &entry, std::vector movies) { + _nextModule = moduleName; + _nextEntry = entry; + _moduleTransitionMovies = std::move(movies); + + if (!_movie) { + playNextModuleTransitionMovie(); + } +} + +bool Game::startVideo(const std::string &name) { + _movie = _services.resource.movies.get(name); + if (!_movie) { + return false; + } + + if (_music) { + _music->stop(); + _music.reset(); + } + return true; +} + +bool Game::playNextModuleTransitionMovie() { + while (!_moduleTransitionMovies.empty()) { + auto name = std::move(_moduleTransitionMovies.front()); + _moduleTransitionMovies.erase(_moduleTransitionMovies.begin()); + + if (startVideo(name)) { + return true; + } + } + return false; +} + +void Game::updateMovie(float dt) { + _movie->update(dt); + + if (_movie->isFinished()) { + _movie.reset(); + playNextModuleTransitionMovie(); + } } void Game::updateCamera(float dt) { diff --git a/src/libs/game/script/routine/impl/main.cpp b/src/libs/game/script/routine/impl/main.cpp index 91d7143ea..a79406740 100644 --- a/src/libs/game/script/routine/impl/main.cpp +++ b/src/libs/game/script/routine/impl/main.cpp @@ -4389,9 +4389,25 @@ static Variable StartNewModule(const std::vector &args, const RoutineC // Transform auto moduleName = boost::to_lower_copy(sModuleName); auto waypoint = boost::to_lower_copy(sWayPoint); + std::vector movies; + auto addMovie = [&movies](const std::string &movie) { + if (!movie.empty()) { + movies.push_back(boost::to_lower_copy(movie)); + } + }; + addMovie(sMovie1); + addMovie(sMovie2); + addMovie(sMovie3); + addMovie(sMovie4); + addMovie(sMovie5); + addMovie(sMovie6); // Execute - ctx.game.scheduleModuleTransition(moduleName, waypoint); + if (movies.empty()) { + ctx.game.scheduleModuleTransition(moduleName, waypoint); + } else { + ctx.game.scheduleModuleTransitionWithMovies(moduleName, waypoint, std::move(movies)); + } return Variable::ofNull(); } From 9f065348337bfb63766b44bf2fb50a5542937689 Mon Sep 17 00:00:00 2001 From: Eldbury Date: Tue, 21 Apr 2026 17:37:04 +0930 Subject: [PATCH 2/5] Suppress world audio during movie playback --- include/reone/audio/mixer.h | 9 ++++++++- src/libs/audio/mixer.cpp | 16 ++++++++++++++-- src/libs/game/game.cpp | 2 ++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/include/reone/audio/mixer.h b/include/reone/audio/mixer.h index b3e797385..b1d0ff75e 100644 --- a/include/reone/audio/mixer.h +++ b/include/reone/audio/mixer.h @@ -32,6 +32,7 @@ class IAudioMixer { virtual ~IAudioMixer() = default; virtual void render() = 0; + virtual void stop(AudioType type) = 0; virtual std::shared_ptr play( std::shared_ptr clip, @@ -48,6 +49,7 @@ class AudioMixer : public IAudioMixer, boost::noncopyable { } void render() override; + void stop(AudioType type) override; std::shared_ptr play( std::shared_ptr clip, @@ -57,9 +59,14 @@ class AudioMixer : public IAudioMixer, boost::noncopyable { std::optional = std::nullopt) override; private: + struct ActiveSource { + std::shared_ptr source; + AudioType type {AudioType::Sound}; + }; + AudioOptions &_options; - std::vector> _sources; + std::vector _sources; float gainByType(AudioType type, float gain) const; }; diff --git a/src/libs/audio/mixer.cpp b/src/libs/audio/mixer.cpp index 4f3e37bb4..efcd5c1ee 100644 --- a/src/libs/audio/mixer.cpp +++ b/src/libs/audio/mixer.cpp @@ -23,7 +23,7 @@ namespace audio { void AudioMixer::render() { for (auto it = _sources.begin(); it != _sources.end();) { - auto &source = *it; + auto &source = it->source; source->render(); if (!source->isPlaying()) { it = _sources.erase(it); @@ -33,6 +33,18 @@ void AudioMixer::render() { } } +void AudioMixer::stop(AudioType type) { + for (auto it = _sources.begin(); it != _sources.end();) { + if (it->type != type) { + ++it; + continue; + } + + it->source->stop(); + it = _sources.erase(it); + } +} + std::shared_ptr AudioMixer::play(std::shared_ptr clip, AudioType type, float gain, @@ -45,7 +57,7 @@ std::shared_ptr AudioMixer::play(std::shared_ptr clip, std::move(position)); source->init(); source->play(); - _sources.push_back(source); + _sources.push_back(ActiveSource {source, type}); return source; } diff --git a/src/libs/game/game.cpp b/src/libs/game/game.cpp index a8737dcdd..1b2b1e6ad 100644 --- a/src/libs/game/game.cpp +++ b/src/libs/game/game.cpp @@ -567,6 +567,8 @@ bool Game::startVideo(const std::string &name) { return false; } + _services.audio.mixer.stop(AudioType::Sound); + if (_music) { _music->stop(); _music.reset(); From fa53460607fdbddb14f858d50a2a468c45814354 Mon Sep 17 00:00:00 2001 From: Eldbury Date: Tue, 21 Apr 2026 21:22:42 +0930 Subject: [PATCH 3/5] Update MockAudioMixer for new stop(AudioType) method --- test/fixtures/audio.h | 1 + 1 file changed, 1 insertion(+) diff --git a/test/fixtures/audio.h b/test/fixtures/audio.h index d9d7822c9..d57433ea9 100644 --- a/test/fixtures/audio.h +++ b/test/fixtures/audio.h @@ -46,6 +46,7 @@ class MockContext : public IContext, boost::noncopyable { class MockAudioMixer : public IAudioMixer, boost::noncopyable { public: MOCK_METHOD(void, render, (), (override)); + MOCK_METHOD(void, stop, (AudioType), (override)); MOCK_METHOD(std::shared_ptr, play, (std::shared_ptr, AudioType, float, bool, std::optional), (override)); }; From fa67191fc4913fdf3fa7a06f00b57a821168f5a0 Mon Sep 17 00:00:00 2001 From: Eldbury Date: Thu, 23 Apr 2026 22:09:01 +0930 Subject: [PATCH 4/5] Address movie transition review comments --- include/reone/audio/mixer.h | 2 ++ include/reone/game/game.h | 4 +++- src/libs/audio/mixer.cpp | 7 +++++++ src/libs/game/game.cpp | 19 +++++++++---------- test/fixtures/audio.h | 1 + 5 files changed, 22 insertions(+), 11 deletions(-) diff --git a/include/reone/audio/mixer.h b/include/reone/audio/mixer.h index b1d0ff75e..60c9f4454 100644 --- a/include/reone/audio/mixer.h +++ b/include/reone/audio/mixer.h @@ -33,6 +33,7 @@ class IAudioMixer { virtual void render() = 0; virtual void stop(AudioType type) = 0; + virtual void stopAll() = 0; virtual std::shared_ptr play( std::shared_ptr clip, @@ -50,6 +51,7 @@ class AudioMixer : public IAudioMixer, boost::noncopyable { void render() override; void stop(AudioType type) override; + void stopAll() override; std::shared_ptr play( std::shared_ptr clip, diff --git a/include/reone/game/game.h b/include/reone/game/game.h index dce70de67..33d859c43 100644 --- a/include/reone/game/game.h +++ b/include/reone/game/game.h @@ -17,6 +17,8 @@ #pragma once +#include + #include "reone/audio/source.h" #include "reone/graphics/cursor.h" #include "reone/input/event.h" @@ -322,7 +324,7 @@ class Game : boost::noncopyable { Screen _screen {Screen::None}; std::shared_ptr _movie; - std::vector _moduleTransitionMovies; + std::queue _moduleTransitionMovies; resource::CursorType _cursorType {resource::CursorType::None}; std::shared_ptr _cursor; float _gameSpeed {1.0f}; diff --git a/src/libs/audio/mixer.cpp b/src/libs/audio/mixer.cpp index efcd5c1ee..61542af76 100644 --- a/src/libs/audio/mixer.cpp +++ b/src/libs/audio/mixer.cpp @@ -45,6 +45,13 @@ void AudioMixer::stop(AudioType type) { } } +void AudioMixer::stopAll() { + for (auto &source : _sources) { + source.source->stop(); + } + _sources.clear(); +} + std::shared_ptr AudioMixer::play(std::shared_ptr clip, AudioType type, float gain, diff --git a/src/libs/game/game.cpp b/src/libs/game/game.cpp index 1b2b1e6ad..bcffd6f89 100644 --- a/src/libs/game/game.cpp +++ b/src/libs/game/game.cpp @@ -412,7 +412,7 @@ void Game::setCursorType(CursorType type) { } void Game::playVideo(const std::string &name) { - _moduleTransitionMovies.clear(); + _moduleTransitionMovies = std::queue(); startVideo(name); } @@ -548,13 +548,16 @@ void Game::stopMovement() { void Game::scheduleModuleTransition(const std::string &moduleName, const std::string &entry) { _nextModule = moduleName; _nextEntry = entry; - _moduleTransitionMovies.clear(); + _moduleTransitionMovies = std::queue(); } void Game::scheduleModuleTransitionWithMovies(const std::string &moduleName, const std::string &entry, std::vector movies) { _nextModule = moduleName; _nextEntry = entry; - _moduleTransitionMovies = std::move(movies); + _moduleTransitionMovies = std::queue(); + for (auto &movie : movies) { + _moduleTransitionMovies.push(std::move(movie)); + } if (!_movie) { playNextModuleTransitionMovie(); @@ -567,19 +570,15 @@ bool Game::startVideo(const std::string &name) { return false; } - _services.audio.mixer.stop(AudioType::Sound); - - if (_music) { - _music->stop(); - _music.reset(); - } + _services.audio.mixer.stopAll(); + _music.reset(); return true; } bool Game::playNextModuleTransitionMovie() { while (!_moduleTransitionMovies.empty()) { auto name = std::move(_moduleTransitionMovies.front()); - _moduleTransitionMovies.erase(_moduleTransitionMovies.begin()); + _moduleTransitionMovies.pop(); if (startVideo(name)) { return true; diff --git a/test/fixtures/audio.h b/test/fixtures/audio.h index d57433ea9..1cdcc8a7c 100644 --- a/test/fixtures/audio.h +++ b/test/fixtures/audio.h @@ -47,6 +47,7 @@ class MockAudioMixer : public IAudioMixer, boost::noncopyable { public: MOCK_METHOD(void, render, (), (override)); MOCK_METHOD(void, stop, (AudioType), (override)); + MOCK_METHOD(void, stopAll, (), (override)); MOCK_METHOD(std::shared_ptr, play, (std::shared_ptr, AudioType, float, bool, std::optional), (override)); }; From f810ac5752d09aeea7302c82bca4ed2e7feecf5b Mon Sep 17 00:00:00 2001 From: Eldbury Date: Thu, 23 Apr 2026 22:15:45 +0930 Subject: [PATCH 5/5] Preserve movie audio when stopping existing sources --- src/libs/game/game.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libs/game/game.cpp b/src/libs/game/game.cpp index bcffd6f89..856358d1e 100644 --- a/src/libs/game/game.cpp +++ b/src/libs/game/game.cpp @@ -565,13 +565,14 @@ void Game::scheduleModuleTransitionWithMovies(const std::string &moduleName, con } bool Game::startVideo(const std::string &name) { + _services.audio.mixer.stopAll(); + _music.reset(); + _movie = _services.resource.movies.get(name); if (!_movie) { return false; } - _services.audio.mixer.stopAll(); - _music.reset(); return true; }