Rework music system

macos_ci_fix
Andrei Kortunov 9 months ago
parent 090da90302
commit e1cae5a029

@ -75,6 +75,7 @@
Feature #3537: Shader-based water ripples
Feature #5492: Let rain and snow collide with statics
Feature #6149: Dehardcode Lua API_REVISION
Feature #6152: Playing music via lua scripts
Feature #6447: Add LOD support to Object Paging
Feature #6491: Add support for Qt6
Feature #6556: Lua API for sounds
@ -99,6 +100,7 @@
Feature #7477: NegativeLight Magic Effect flag
Feature #7499: OpenMW-CS: Generate record filters by drag & dropping cell content to the filters field
Feature #7546: Start the game on Fredas
Feature #7568: Uninterruptable scripted music
Task #5896: Do not use deprecated MyGUI properties
Task #7113: Move from std::atoi to std::from_char
Task #7117: Replace boost::scoped_array with std::vector

@ -61,7 +61,7 @@ add_openmw_dir (mwscript
add_openmw_dir (mwlua
luamanagerimp object objectlists userdataserializer luaevents engineevents objectvariant
context globalscripts localscripts playerscripts luabindings objectbindings cellbindings mwscriptbindings
camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings postprocessingbindings stats debugbindings
camerabindings vfsbindings musicbindings uibindings soundbindings inputbindings nearbybindings postprocessingbindings stats debugbindings
types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus types/potion types/ingredient types/misc types/repair types/armor types/light types/static types/clothing types/levelledlist types/terminal
worker magicbindings factionbindings
)

@ -26,6 +26,11 @@ namespace ESM
class ESMWriter;
}
namespace MWSound
{
enum class MusicType;
}
namespace MWWorld
{
class Ptr;
@ -282,6 +287,9 @@ namespace MWBase
virtual float getAngleToPlayer(const MWWorld::Ptr& ptr) const = 0;
virtual MWMechanics::GreetingState getGreetingState(const MWWorld::Ptr& ptr) const = 0;
virtual bool isTurningToPlayer(const MWWorld::Ptr& ptr) const = 0;
virtual MWSound::MusicType getMusicType() const = 0;
virtual void setMusicType(MWSound::MusicType type) = 0;
};
}

@ -29,6 +29,14 @@ namespace MWSound
MaxCount
};
enum class MusicType
{
Special,
Explore,
Battle,
Scripted
};
class Sound;
class Stream;
struct Sound_Decoder;
@ -104,9 +112,13 @@ namespace MWBase
virtual void stopMusic() = 0;
///< Stops music if it's playing
virtual void streamMusic(const std::string& filename) = 0;
virtual void streamMusic(
const std::string& filename, MWSound::MusicType type = MWSound::MusicType::Scripted, float fade = 1.f)
= 0;
///< Play a soundifle
/// \param filename name of a sound file in "Music/" in the data directory.
/// \param filename name of a sound file in the data directory.
/// \param type music type.
/// \param fade time in seconds to fade out current track before start this one.
virtual bool isMusicPlaying() = 0;
///< Returns true if music is playing

@ -214,7 +214,8 @@ namespace MWGui
center();
// Play LevelUp Music
MWBase::Environment::get().getSoundManager()->streamMusic("Special/MW_Triumph.mp3");
MWBase::Environment::get().getSoundManager()->streamMusic(
"Music/Special/MW_Triumph.mp3", MWSound::MusicType::Special);
}
void LevelupDialog::onOkButtonClicked(MyGUI::Widget* sender)

@ -40,6 +40,7 @@
#include "factionbindings.hpp"
#include "inputbindings.hpp"
#include "magicbindings.hpp"
#include "musicbindings.hpp"
#include "nearbybindings.hpp"
#include "objectbindings.hpp"
#include "postprocessingbindings.hpp"
@ -392,6 +393,7 @@ namespace MWLua
{ "openmw.input", initInputPackage(context) },
{ "openmw.postprocessing", initPostprocessingPackage(context) },
{ "openmw.ui", initUserInterfacePackage(context) },
{ "openmw.music", initMusicPackage(context) },
};
}

@ -0,0 +1,24 @@
#include "musicbindings.hpp"
#include "luabindings.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/windowmanager.hpp"
#include "context.hpp"
#include "luamanagerimp.hpp"
namespace MWLua
{
sol::table initMusicPackage(const Context& context)
{
sol::table api(context.mLua->sol(), sol::create);
api["streamMusic"] = [](std::string_view fileName) {
MWBase::Environment::get().getSoundManager()->streamMusic(std::string(fileName));
};
api["stopMusic"] = []() { MWBase::Environment::get().getSoundManager()->stopMusic(); };
return LuaUtil::makeReadOnly(api);
}
}

@ -0,0 +1,13 @@
#ifndef MWLUA_MUSICBINDINGS_H
#define MWLUA_MUSICBINDINGS_H
#include <sol/forward.hpp>
#include "context.hpp"
namespace MWLua
{
sol::table initMusicPackage(const Context&);
}
#endif // MWLUA_MUSICBINDINGS_H

@ -1285,7 +1285,7 @@ namespace MWMechanics
}
}
void Actors::updateCombatMusic()
bool Actors::playerHasHostiles() const
{
const MWWorld::Ptr player = getPlayer();
const osg::Vec3f playerPos = player.getRefData().getPosition().asVec3();
@ -1315,19 +1315,7 @@ namespace MWMechanics
}
}
// check if we still have any player enemies to switch music
if (mCurrentMusic != MusicType::Explore && !hasHostiles
&& !(player.getClass().getCreatureStats(player).isDead()
&& MWBase::Environment::get().getSoundManager()->isMusicPlaying()))
{
MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore"));
mCurrentMusic = MusicType::Explore;
}
else if (mCurrentMusic != MusicType::Battle && hasHostiles)
{
MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Battle"));
mCurrentMusic = MusicType::Battle;
}
return hasHostiles;
}
void Actors::predictAndAvoidCollisions(float duration) const
@ -1735,8 +1723,6 @@ namespace MWMechanics
killDeadActors();
updateSneaking(playerCharacter, duration);
}
updateCombatMusic();
}
void Actors::notifyDied(const MWWorld::Ptr& actor)
@ -1806,7 +1792,8 @@ namespace MWMechanics
// player's death animation is over
MWBase::Environment::get().getStateManager()->askLoadRecent();
// Play Death Music if it was the player dying
MWBase::Environment::get().getSoundManager()->streamMusic("Special/MW_Death.mp3");
MWBase::Environment::get().getSoundManager()->streamMusic(
"Music/Special/MW_Death.mp3", MWSound::MusicType::Special);
}
else
{

@ -74,9 +74,6 @@ namespace MWMechanics
void dropActors(const MWWorld::CellStore* cellStore, const MWWorld::Ptr& ignore);
///< Deregister all actors (except for \a ignore) in the given cell.
void updateCombatMusic();
///< Update combat music state
void update(float duration, bool paused);
///< Update actor stats and store desired velocity vectors in \a movement
@ -159,19 +156,14 @@ namespace MWMechanics
bool isReadyToBlock(const MWWorld::Ptr& ptr) const;
bool isAttackingOrSpell(const MWWorld::Ptr& ptr) const;
bool playerHasHostiles() const;
int getGreetingTimer(const MWWorld::Ptr& ptr) const;
float getAngleToPlayer(const MWWorld::Ptr& ptr) const;
GreetingState getGreetingState(const MWWorld::Ptr& ptr) const;
bool isTurningToPlayer(const MWWorld::Ptr& ptr) const;
private:
enum class MusicType
{
Title,
Explore,
Battle
};
std::map<ESM::RefId, int> mDeathCount;
std::list<Actor> mActors;
std::map<const MWWorld::LiveCellRefBase*, std::list<Actor>::iterator> mIndex;
@ -182,7 +174,6 @@ namespace MWMechanics
float mTimerUpdateHello = 0;
float mSneakTimer = 0; // Times update of sneak icon
float mSneakSkillTimer = 0; // Times sneak skill progress from "avoid notice"
MusicType mCurrentMusic = MusicType::Title;
void updateVisibility(const MWWorld::Ptr& ptr, CharacterController& ctrl) const;

@ -24,6 +24,7 @@
#include "../mwbase/dialoguemanager.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/statemanager.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/world.hpp"
@ -257,6 +258,7 @@ namespace MWMechanics
, mClassSelected(false)
, mRaceSelected(false)
, mAI(true)
, mMusicType(MWSound::MusicType::Special)
{
// buildPlayer no longer here, needs to be done explicitly after all subsystems are up and running
}
@ -340,6 +342,8 @@ namespace MWMechanics
mActors.update(duration, paused);
mObjects.update(duration, paused);
updateMusicState();
}
void MechanicsManager::processChangedSettings(const Settings::CategorySettingVector& changed)
@ -1572,6 +1576,31 @@ namespace MWMechanics
return (Misc::Rng::roll0to99(prng) >= target);
}
void MechanicsManager::updateMusicState()
{
bool musicPlaying = MWBase::Environment::get().getSoundManager()->isMusicPlaying();
// Can not interrupt scripted music by built-in playlists
if (mMusicType == MWSound::MusicType::Scripted && musicPlaying)
return;
const MWWorld::Ptr& player = MWMechanics::getPlayer();
bool hasHostiles = mActors.playerHasHostiles();
// check if we still have any player enemies to switch music
if (mMusicType != MWSound::MusicType::Explore && !hasHostiles
&& !(player.getClass().getCreatureStats(player).isDead() && musicPlaying))
{
MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore"));
mMusicType = MWSound::MusicType::Explore;
}
else if (mMusicType != MWSound::MusicType::Battle && hasHostiles)
{
MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Battle"));
mMusicType = MWSound::MusicType::Battle;
}
}
void MechanicsManager::startCombat(const MWWorld::Ptr& ptr, const MWWorld::Ptr& target)
{
CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);

@ -11,6 +11,11 @@
#include "npcstats.hpp"
#include "objects.hpp"
namespace MWSound
{
enum class MusicType;
}
namespace MWWorld
{
class CellStore;
@ -33,6 +38,8 @@ namespace MWMechanics
typedef std::map<ESM::RefId, OwnerMap> StolenItemsMap;
StolenItemsMap mStolenItems;
MWSound::MusicType mMusicType;
public:
void buildPlayer();
///< build player according to stored class/race/birthsign information. Will
@ -232,7 +239,11 @@ namespace MWMechanics
GreetingState getGreetingState(const MWWorld::Ptr& ptr) const override;
bool isTurningToPlayer(const MWWorld::Ptr& ptr) const override;
MWSound::MusicType getMusicType() const override { return mMusicType; }
void setMusicType(MWSound::MusicType type) override { mMusicType = type; }
private:
void updateMusicState();
bool canCommitCrimeAgainst(const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker);
bool canReportCrime(
const MWWorld::Ptr& actor, const MWWorld::Ptr& victim, std::set<MWWorld::Ptr>& playerFollowers);

@ -63,10 +63,11 @@ namespace MWScript
public:
void execute(Interpreter::Runtime& runtime) override
{
std::string sound{ runtime.getStringLiteral(runtime[0].mInteger) };
std::string music{ runtime.getStringLiteral(runtime[0].mInteger) };
runtime.pop();
MWBase::Environment::get().getSoundManager()->streamMusic(sound);
MWBase::Environment::get().getSoundManager()->streamMusic(
Misc::ResourceHelpers::correctMusicPath(music));
}
};

@ -14,6 +14,7 @@
#include <components/vfs/pathutil.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/statemanager.hpp"
#include "../mwbase/world.hpp"
@ -250,7 +251,7 @@ namespace MWSound
if (filename.empty())
return;
Log(Debug::Info) << "Playing " << filename;
Log(Debug::Info) << "Playing \"" << filename << "\"";
mLastPlayedMusic = filename;
DecoderPtr decoder = getDecoder();
@ -260,7 +261,7 @@ namespace MWSound
}
catch (std::exception& e)
{
Log(Debug::Error) << "Failed to load audio from " << filename << ": " << e.what();
Log(Debug::Error) << "Failed to load audio from \"" << filename << "\": " << e.what();
return;
}
@ -274,7 +275,7 @@ namespace MWSound
mOutput->streamSound(decoder, mMusic.get());
}
void SoundManager::advanceMusic(const std::string& filename)
void SoundManager::advanceMusic(const std::string& filename, float fadeOut)
{
if (!isMusicPlaying())
{
@ -284,7 +285,7 @@ namespace MWSound
mNextMusic = filename;
mMusic->setFadeout(1.f);
mMusic->setFadeout(fadeOut);
}
void SoundManager::startRandomTitle()
@ -319,14 +320,28 @@ namespace MWSound
tracklist.pop_back();
}
void SoundManager::streamMusic(const std::string& filename)
bool SoundManager::isMusicPlaying()
{
advanceMusic("Music/" + filename);
return mMusic && mOutput->isStreamPlaying(mMusic.get());
}
bool SoundManager::isMusicPlaying()
void SoundManager::streamMusic(const std::string& filename, MusicType type, float fade)
{
return mMusic && mOutput->isStreamPlaying(mMusic.get());
const auto mechanicsManager = MWBase::Environment::get().getMechanicsManager();
// Can not interrupt scripted music by built-in playlists
if (mechanicsManager->getMusicType() == MusicType::Scripted && type != MusicType::Scripted
&& type != MusicType::Special)
return;
std::string normalizedName = VFS::Path::normalizeFilename(filename);
mechanicsManager->setMusicType(type);
advanceMusic(normalizedName, fade);
if (type == MWSound::MusicType::Battle)
mCurrentPlaylist = "Battle";
else if (type == MWSound::MusicType::Explore)
mCurrentPlaylist = "Explore";
}
void SoundManager::playPlaylist(const std::string& playlist)
@ -337,7 +352,8 @@ namespace MWSound
if (mMusicFiles.find(playlist) == mMusicFiles.end())
{
std::vector<std::string> filelist;
for (const auto& name : mVFS->getRecursiveDirectoryIterator("Music/" + playlist + '/'))
auto playlistPath = Misc::ResourceHelpers::correctMusicPath(playlist) + '/';
for (const auto& name : mVFS->getRecursiveDirectoryIterator(playlistPath))
filelist.push_back(name);
mMusicFiles[playlist] = filelist;

@ -127,7 +127,7 @@ namespace MWSound
StreamPtr playVoice(DecoderPtr decoder, const osg::Vec3f& pos, bool playlocal);
void streamMusicFull(const std::string& filename);
void advanceMusic(const std::string& filename);
void advanceMusic(const std::string& filename, float fadeOut = 1.f);
void startRandomTitle();
void cull3DSound(SoundBase* sound);
@ -176,9 +176,12 @@ namespace MWSound
void stopMusic() override;
///< Stops music if it's playing
void streamMusic(const std::string& filename) override;
void streamMusic(const std::string& filename, MWSound::MusicType type = MWSound::MusicType::Scripted,
float fade = 1.f) override;
///< Play a soundifle
/// \param filename name of a sound file in "Music/" in the data directory.
/// \param filename name of a sound file in the data directory.
/// \param type music type.
/// \param fade time in seconds to fade out current track before start this one.
bool isMusicPlaying() override;
///< Returns true if music is playing

@ -391,7 +391,10 @@ namespace MWWorld
{
std::string_view video = Fallback::Map::getString("Movies_New_Game");
if (!video.empty())
{
MWBase::Environment::get().getSoundManager()->stopMusic();
MWBase::Environment::get().getWindowManager()->playVideo(video, true);
}
}
// enable collision

@ -156,6 +156,11 @@ std::string Misc::ResourceHelpers::correctSoundPath(const std::string& resPath)
return "sound\\" + resPath;
}
std::string Misc::ResourceHelpers::correctMusicPath(const std::string& resPath)
{
return "music\\" + resPath;
}
std::string_view Misc::ResourceHelpers::meshPathForESM3(std::string_view resPath)
{
constexpr std::string_view prefix = "meshes";

@ -38,6 +38,9 @@ namespace Misc
// Adds "sound\\".
std::string correctSoundPath(const std::string& resPath);
// Adds "music\\".
std::string correctMusicPath(const std::string& resPath);
// Removes "meshes\\".
std::string_view meshPathForESM3(std::string_view resPath);

@ -23,6 +23,7 @@ Lua API reference
openmw_nearby
openmw_input
openmw_ambient
openmw_music
openmw_ui
openmw_camera
openmw_postprocessing

@ -0,0 +1,7 @@
Package openmw.music
====================
.. include:: version.rst
.. raw:: html
:file: generated_html/openmw_music.html

@ -27,6 +27,8 @@
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.input <Package openmw.input>` | by player scripts | | User input. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.music <Package openmw.music>` | by player scripts | | Music system. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.ui <Package openmw.ui>` | by player scripts | | Controls :ref:`user interface <User interface reference>`. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.camera <Package openmw.camera>` | by player scripts | | Controls camera. |

@ -77,6 +77,7 @@ local env = {
nearby = require('openmw.nearby'),
self = require('openmw.self'),
input = require('openmw.input'),
music = require('openmw.music'),
ui = require('openmw.ui'),
camera = require('openmw.camera'),
aux_util = require('openmw_aux.util'),

@ -13,6 +13,7 @@ set(LUA_API_FILES
openmw/async.lua
openmw/core.lua
openmw/debug.lua
openmw/music.lua
openmw/nearby.lua
openmw/postprocessing.lua
openmw/self.lua

@ -0,0 +1,19 @@
---
-- `openmw.music` provides access to music system.
-- @module music
-- @usage local music = require('openmw.music')
---
-- Play a sound file as a music track
-- @function [parent=#music] streamMusic
-- @param #string fileName Path to file in VFS
-- @usage music.streamMusic("Music\\Test\\Test.mp3");
---
-- Stop to play current music
-- @function [parent=#music] stopMusic
-- @usage music.stopMusic();
return nil
Loading…
Cancel
Save