1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-01-29 08:45:36 +00:00

Rework music system

This commit is contained in:
Andrei Kortunov 2023-08-18 11:03:37 +04:00
parent 090da90302
commit e1cae5a029
24 changed files with 189 additions and 47 deletions

View file

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

View file

@ -61,7 +61,7 @@ add_openmw_dir (mwscript
add_openmw_dir (mwlua add_openmw_dir (mwlua
luamanagerimp object objectlists userdataserializer luaevents engineevents objectvariant luamanagerimp object objectlists userdataserializer luaevents engineevents objectvariant
context globalscripts localscripts playerscripts luabindings objectbindings cellbindings mwscriptbindings 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 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 worker magicbindings factionbindings
) )

View file

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

View file

@ -29,6 +29,14 @@ namespace MWSound
MaxCount MaxCount
}; };
enum class MusicType
{
Special,
Explore,
Battle,
Scripted
};
class Sound; class Sound;
class Stream; class Stream;
struct Sound_Decoder; struct Sound_Decoder;
@ -104,9 +112,13 @@ namespace MWBase
virtual void stopMusic() = 0; virtual void stopMusic() = 0;
///< Stops music if it's playing ///< 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 ///< 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; virtual bool isMusicPlaying() = 0;
///< Returns true if music is playing ///< Returns true if music is playing

View file

@ -214,7 +214,8 @@ namespace MWGui
center(); center();
// Play LevelUp Music // 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) void LevelupDialog::onOkButtonClicked(MyGUI::Widget* sender)

View file

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

View file

@ -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);
}
}

View file

@ -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

View file

@ -1285,7 +1285,7 @@ namespace MWMechanics
} }
} }
void Actors::updateCombatMusic() bool Actors::playerHasHostiles() const
{ {
const MWWorld::Ptr player = getPlayer(); const MWWorld::Ptr player = getPlayer();
const osg::Vec3f playerPos = player.getRefData().getPosition().asVec3(); 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 return hasHostiles;
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;
}
} }
void Actors::predictAndAvoidCollisions(float duration) const void Actors::predictAndAvoidCollisions(float duration) const
@ -1735,8 +1723,6 @@ namespace MWMechanics
killDeadActors(); killDeadActors();
updateSneaking(playerCharacter, duration); updateSneaking(playerCharacter, duration);
} }
updateCombatMusic();
} }
void Actors::notifyDied(const MWWorld::Ptr& actor) void Actors::notifyDied(const MWWorld::Ptr& actor)
@ -1806,7 +1792,8 @@ namespace MWMechanics
// player's death animation is over // player's death animation is over
MWBase::Environment::get().getStateManager()->askLoadRecent(); MWBase::Environment::get().getStateManager()->askLoadRecent();
// Play Death Music if it was the player dying // 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 else
{ {

View file

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

View file

@ -24,6 +24,7 @@
#include "../mwbase/dialoguemanager.hpp" #include "../mwbase/dialoguemanager.hpp"
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/statemanager.hpp" #include "../mwbase/statemanager.hpp"
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
@ -257,6 +258,7 @@ namespace MWMechanics
, mClassSelected(false) , mClassSelected(false)
, mRaceSelected(false) , mRaceSelected(false)
, mAI(true) , mAI(true)
, mMusicType(MWSound::MusicType::Special)
{ {
// buildPlayer no longer here, needs to be done explicitly after all subsystems are up and running // 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); mActors.update(duration, paused);
mObjects.update(duration, paused); mObjects.update(duration, paused);
updateMusicState();
} }
void MechanicsManager::processChangedSettings(const Settings::CategorySettingVector& changed) void MechanicsManager::processChangedSettings(const Settings::CategorySettingVector& changed)
@ -1572,6 +1576,31 @@ namespace MWMechanics
return (Misc::Rng::roll0to99(prng) >= target); 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) void MechanicsManager::startCombat(const MWWorld::Ptr& ptr, const MWWorld::Ptr& target)
{ {
CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);

View file

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

View file

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

View file

@ -14,6 +14,7 @@
#include <components/vfs/pathutil.hpp> #include <components/vfs/pathutil.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/statemanager.hpp" #include "../mwbase/statemanager.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
@ -250,7 +251,7 @@ namespace MWSound
if (filename.empty()) if (filename.empty())
return; return;
Log(Debug::Info) << "Playing " << filename; Log(Debug::Info) << "Playing \"" << filename << "\"";
mLastPlayedMusic = filename; mLastPlayedMusic = filename;
DecoderPtr decoder = getDecoder(); DecoderPtr decoder = getDecoder();
@ -260,7 +261,7 @@ namespace MWSound
} }
catch (std::exception& e) 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; return;
} }
@ -274,7 +275,7 @@ namespace MWSound
mOutput->streamSound(decoder, mMusic.get()); mOutput->streamSound(decoder, mMusic.get());
} }
void SoundManager::advanceMusic(const std::string& filename) void SoundManager::advanceMusic(const std::string& filename, float fadeOut)
{ {
if (!isMusicPlaying()) if (!isMusicPlaying())
{ {
@ -284,7 +285,7 @@ namespace MWSound
mNextMusic = filename; mNextMusic = filename;
mMusic->setFadeout(1.f); mMusic->setFadeout(fadeOut);
} }
void SoundManager::startRandomTitle() void SoundManager::startRandomTitle()
@ -319,16 +320,30 @@ namespace MWSound
tracklist.pop_back(); tracklist.pop_back();
} }
void SoundManager::streamMusic(const std::string& filename)
{
advanceMusic("Music/" + filename);
}
bool SoundManager::isMusicPlaying() bool SoundManager::isMusicPlaying()
{ {
return mMusic && mOutput->isStreamPlaying(mMusic.get()); return mMusic && mOutput->isStreamPlaying(mMusic.get());
} }
void SoundManager::streamMusic(const std::string& filename, MusicType type, float fade)
{
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) void SoundManager::playPlaylist(const std::string& playlist)
{ {
if (mCurrentPlaylist == playlist) if (mCurrentPlaylist == playlist)
@ -337,7 +352,8 @@ namespace MWSound
if (mMusicFiles.find(playlist) == mMusicFiles.end()) if (mMusicFiles.find(playlist) == mMusicFiles.end())
{ {
std::vector<std::string> filelist; 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); filelist.push_back(name);
mMusicFiles[playlist] = filelist; mMusicFiles[playlist] = filelist;

View file

@ -127,7 +127,7 @@ namespace MWSound
StreamPtr playVoice(DecoderPtr decoder, const osg::Vec3f& pos, bool playlocal); StreamPtr playVoice(DecoderPtr decoder, const osg::Vec3f& pos, bool playlocal);
void streamMusicFull(const std::string& filename); 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 startRandomTitle();
void cull3DSound(SoundBase* sound); void cull3DSound(SoundBase* sound);
@ -176,9 +176,12 @@ namespace MWSound
void stopMusic() override; void stopMusic() override;
///< Stops music if it's playing ///< 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 ///< 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; bool isMusicPlaying() override;
///< Returns true if music is playing ///< Returns true if music is playing

View file

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

View file

@ -156,6 +156,11 @@ std::string Misc::ResourceHelpers::correctSoundPath(const std::string& resPath)
return "sound\\" + 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) std::string_view Misc::ResourceHelpers::meshPathForESM3(std::string_view resPath)
{ {
constexpr std::string_view prefix = "meshes"; constexpr std::string_view prefix = "meshes";

View file

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

View file

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

View file

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

View file

@ -27,6 +27,8 @@
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.input <Package openmw.input>` | by player scripts | | User input. | |: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.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. | |:ref:`openmw.camera <Package openmw.camera>` | by player scripts | | Controls camera. |

View file

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

View file

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

View file

@ -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