mirror of
https://github.com/OpenMW/openmw.git
synced 2025-01-16 15:29:55 +00:00
Implement Lua-based music
This commit is contained in:
parent
c3d02c0b41
commit
5a1ec8ce87
28 changed files with 462 additions and 171 deletions
|
@ -998,7 +998,7 @@ void OMW::Engine::go()
|
||||||
mWindowManager->pushGuiMode(MWGui::GM_MainMenu);
|
mWindowManager->pushGuiMode(MWGui::GM_MainMenu);
|
||||||
|
|
||||||
if (mVFS->exists(MWSound::titleMusic))
|
if (mVFS->exists(MWSound::titleMusic))
|
||||||
mSoundManager->streamMusic(MWSound::titleMusic, MWSound::MusicType::Special);
|
mSoundManager->streamMusic(MWSound::titleMusic, MWSound::MusicType::Normal);
|
||||||
else
|
else
|
||||||
Log(Debug::Warning) << "Title music not found";
|
Log(Debug::Warning) << "Title music not found";
|
||||||
|
|
||||||
|
|
|
@ -33,10 +33,8 @@ namespace MWSound
|
||||||
|
|
||||||
enum class MusicType
|
enum class MusicType
|
||||||
{
|
{
|
||||||
Special,
|
Normal,
|
||||||
Explore,
|
MWScript
|
||||||
Battle,
|
|
||||||
Scripted
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class Sound;
|
class Sound;
|
||||||
|
@ -126,11 +124,6 @@ namespace MWBase
|
||||||
virtual bool isMusicPlaying() = 0;
|
virtual bool isMusicPlaying() = 0;
|
||||||
///< Returns true if music is playing
|
///< Returns true if music is playing
|
||||||
|
|
||||||
virtual void playPlaylist(VFS::Path::NormalizedView playlist) = 0;
|
|
||||||
///< Start playing music from the selected folder
|
|
||||||
/// \param name of the folder that contains the playlist
|
|
||||||
/// Title music playlist is predefined
|
|
||||||
|
|
||||||
virtual void say(const MWWorld::ConstPtr& reference, VFS::Path::NormalizedView filename) = 0;
|
virtual void say(const MWWorld::ConstPtr& reference, VFS::Path::NormalizedView filename) = 0;
|
||||||
///< Make an actor say some text.
|
///< Make an actor say some text.
|
||||||
/// \param filename name of a sound file in the VFS
|
/// \param filename name of a sound file in the VFS
|
||||||
|
|
|
@ -218,7 +218,7 @@ namespace MWGui
|
||||||
center();
|
center();
|
||||||
|
|
||||||
// Play LevelUp Music
|
// Play LevelUp Music
|
||||||
MWBase::Environment::get().getSoundManager()->streamMusic(MWSound::triumphMusic, MWSound::MusicType::Special);
|
MWBase::Environment::get().getSoundManager()->streamMusic(MWSound::triumphMusic, MWSound::MusicType::Normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LevelupDialog::onOkButtonClicked(MyGUI::Widget* sender)
|
void LevelupDialog::onOkButtonClicked(MyGUI::Widget* sender)
|
||||||
|
|
|
@ -141,7 +141,7 @@ namespace MWLua
|
||||||
api["streamMusic"] = [](std::string_view fileName, const sol::optional<sol::table>& options) {
|
api["streamMusic"] = [](std::string_view fileName, const sol::optional<sol::table>& options) {
|
||||||
auto args = getStreamMusicArgs(options);
|
auto args = getStreamMusicArgs(options);
|
||||||
MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager();
|
MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager();
|
||||||
sndMgr->streamMusic(VFS::Path::Normalized(fileName), MWSound::MusicType::Scripted, args.mFade);
|
sndMgr->streamMusic(VFS::Path::Normalized(fileName), MWSound::MusicType::Normal, args.mFade);
|
||||||
};
|
};
|
||||||
|
|
||||||
api["say"]
|
api["say"]
|
||||||
|
|
|
@ -1290,40 +1290,6 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Actors::playerHasHostiles() const
|
|
||||||
{
|
|
||||||
const MWWorld::Ptr player = getPlayer();
|
|
||||||
const osg::Vec3f playerPos = player.getRefData().getPosition().asVec3();
|
|
||||||
bool hasHostiles = false; // need to know this to play Battle music
|
|
||||||
const bool aiActive = MWBase::Environment::get().getMechanicsManager()->isAIActive();
|
|
||||||
|
|
||||||
if (aiActive)
|
|
||||||
{
|
|
||||||
const int actorsProcessingRange = Settings::game().mActorsProcessingRange;
|
|
||||||
for (const Actor& actor : mActors)
|
|
||||||
{
|
|
||||||
if (actor.getPtr() == player)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
const bool inProcessingRange
|
|
||||||
= (playerPos - actor.getPtr().getRefData().getPosition().asVec3()).length2()
|
|
||||||
<= actorsProcessingRange * actorsProcessingRange;
|
|
||||||
if (inProcessingRange)
|
|
||||||
{
|
|
||||||
MWMechanics::CreatureStats& stats = actor.getPtr().getClass().getCreatureStats(actor.getPtr());
|
|
||||||
bool isDead = stats.isDead() && stats.isDeathAnimationFinished();
|
|
||||||
if (!isDead && stats.getAiSequence().isInCombat())
|
|
||||||
{
|
|
||||||
hasHostiles = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return hasHostiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Actors::predictAndAvoidCollisions(float duration) const
|
void Actors::predictAndAvoidCollisions(float duration) const
|
||||||
{
|
{
|
||||||
if (!MWBase::Environment::get().getMechanicsManager()->isAIActive())
|
if (!MWBase::Environment::get().getMechanicsManager()->isAIActive())
|
||||||
|
@ -1798,9 +1764,6 @@ 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
|
|
||||||
MWBase::Environment::get().getSoundManager()->streamMusic(
|
|
||||||
MWSound::deathMusic, MWSound::MusicType::Special);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -162,8 +162,6 @@ 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;
|
||||||
|
|
|
@ -250,7 +250,7 @@ namespace MWMechanics
|
||||||
, mClassSelected(false)
|
, mClassSelected(false)
|
||||||
, mRaceSelected(false)
|
, mRaceSelected(false)
|
||||||
, mAI(true)
|
, mAI(true)
|
||||||
, mMusicType(MWSound::MusicType::Special)
|
, mMusicType(MWSound::MusicType::Normal)
|
||||||
{
|
{
|
||||||
// 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
|
||||||
}
|
}
|
||||||
|
@ -334,8 +334,6 @@ 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)
|
||||||
|
@ -1665,31 +1663,6 @@ 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(MWSound::explorePlaylist);
|
|
||||||
mMusicType = MWSound::MusicType::Explore;
|
|
||||||
}
|
|
||||||
else if (mMusicType != MWSound::MusicType::Battle && hasHostiles)
|
|
||||||
{
|
|
||||||
MWBase::Environment::get().getSoundManager()->playPlaylist(MWSound::battlePlaylist);
|
|
||||||
mMusicType = MWSound::MusicType::Battle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MechanicsManager::startCombat(
|
void MechanicsManager::startCombat(
|
||||||
const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, const std::set<MWWorld::Ptr>* targetAllies)
|
const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, const std::set<MWWorld::Ptr>* targetAllies)
|
||||||
{
|
{
|
||||||
|
|
|
@ -249,7 +249,6 @@ namespace MWMechanics
|
||||||
void setMusicType(MWSound::MusicType type) override { mMusicType = type; }
|
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);
|
||||||
|
|
|
@ -67,7 +67,7 @@ namespace MWScript
|
||||||
runtime.pop();
|
runtime.pop();
|
||||||
|
|
||||||
MWBase::Environment::get().getSoundManager()->streamMusic(
|
MWBase::Environment::get().getSoundManager()->streamMusic(
|
||||||
Misc::ResourceHelpers::correctMusicPath(music), MWSound::MusicType::Scripted);
|
Misc::ResourceHelpers::correctMusicPath(music), MWSound::MusicType::MWScript);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -262,7 +262,6 @@ namespace MWSound
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Log(Debug::Info) << "Playing \"" << filename << "\"";
|
Log(Debug::Info) << "Playing \"" << filename << "\"";
|
||||||
mLastPlayedMusic = filename;
|
|
||||||
|
|
||||||
DecoderPtr decoder = getDecoder();
|
DecoderPtr decoder = getDecoder();
|
||||||
try
|
try
|
||||||
|
@ -298,41 +297,6 @@ namespace MWSound
|
||||||
mMusic->setFadeout(fadeOut);
|
mMusic->setFadeout(fadeOut);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SoundManager::startRandomTitle()
|
|
||||||
{
|
|
||||||
const auto playlist = mMusicFiles.find(mCurrentPlaylist);
|
|
||||||
|
|
||||||
if (playlist == mMusicFiles.end() || playlist->second.empty())
|
|
||||||
{
|
|
||||||
advanceMusic(VFS::Path::NormalizedView());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::vector<VFS::Path::Normalized>& filelist = playlist->second;
|
|
||||||
|
|
||||||
auto& tracklist = mMusicToPlay[mCurrentPlaylist];
|
|
||||||
|
|
||||||
// Do a Fisher-Yates shuffle
|
|
||||||
|
|
||||||
// Repopulate if playlist is empty
|
|
||||||
if (tracklist.empty())
|
|
||||||
{
|
|
||||||
tracklist.resize(filelist.size());
|
|
||||||
std::iota(tracklist.begin(), tracklist.end(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
int i = Misc::Rng::rollDice(tracklist.size());
|
|
||||||
|
|
||||||
// Reshuffle if last played music is the same after a repopulation
|
|
||||||
if (filelist[tracklist[i]] == mLastPlayedMusic)
|
|
||||||
i = (i + 1) % tracklist.size();
|
|
||||||
|
|
||||||
// Remove music from list after advancing music
|
|
||||||
advanceMusic(filelist[tracklist[i]]);
|
|
||||||
tracklist[i] = tracklist.back();
|
|
||||||
tracklist.pop_back();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SoundManager::isMusicPlaying()
|
bool SoundManager::isMusicPlaying()
|
||||||
{
|
{
|
||||||
return mMusic && mOutput->isStreamPlaying(mMusic.get());
|
return mMusic && mOutput->isStreamPlaying(mMusic.get());
|
||||||
|
@ -343,45 +307,11 @@ namespace MWSound
|
||||||
const auto mechanicsManager = MWBase::Environment::get().getMechanicsManager();
|
const auto mechanicsManager = MWBase::Environment::get().getMechanicsManager();
|
||||||
|
|
||||||
// Can not interrupt scripted music by built-in playlists
|
// Can not interrupt scripted music by built-in playlists
|
||||||
if (mechanicsManager->getMusicType() == MusicType::Scripted && type != MusicType::Scripted
|
if (mechanicsManager->getMusicType() == MusicType::MWScript && type != MusicType::MWScript)
|
||||||
&& type != MusicType::Special)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
mechanicsManager->setMusicType(type);
|
mechanicsManager->setMusicType(type);
|
||||||
advanceMusic(filename, fade);
|
advanceMusic(filename, fade);
|
||||||
if (type == MWSound::MusicType::Battle)
|
|
||||||
mCurrentPlaylist = battlePlaylist;
|
|
||||||
else if (type == MWSound::MusicType::Explore)
|
|
||||||
mCurrentPlaylist = explorePlaylist;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SoundManager::playPlaylist(VFS::Path::NormalizedView playlist)
|
|
||||||
{
|
|
||||||
if (mCurrentPlaylist == playlist)
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto it = mMusicFiles.find(playlist);
|
|
||||||
|
|
||||||
if (it == mMusicFiles.end())
|
|
||||||
{
|
|
||||||
std::vector<VFS::Path::Normalized> filelist;
|
|
||||||
const VFS::Path::Normalized playlistPath
|
|
||||||
= Misc::ResourceHelpers::correctMusicPath(playlist) / VFS::Path::NormalizedView();
|
|
||||||
for (const auto& name : mVFS->getRecursiveDirectoryIterator(VFS::Path::NormalizedView(playlistPath)))
|
|
||||||
filelist.push_back(name);
|
|
||||||
|
|
||||||
it = mMusicFiles.emplace_hint(it, playlist, std::move(filelist));
|
|
||||||
}
|
|
||||||
|
|
||||||
// No Battle music? Use Explore playlist
|
|
||||||
if (playlist == battlePlaylist && it->second.empty())
|
|
||||||
{
|
|
||||||
playPlaylist(explorePlaylist);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mCurrentPlaylist = playlist;
|
|
||||||
startRandomTitle();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SoundManager::say(const MWWorld::ConstPtr& ptr, VFS::Path::NormalizedView filename)
|
void SoundManager::say(const MWWorld::ConstPtr& ptr, VFS::Path::NormalizedView filename)
|
||||||
|
@ -1022,10 +952,6 @@ namespace MWSound
|
||||||
duration = mTimePassed;
|
duration = mTimePassed;
|
||||||
mTimePassed = 0.0f;
|
mTimePassed = 0.0f;
|
||||||
|
|
||||||
// Make sure music is still playing
|
|
||||||
if (!isMusicPlaying() && !mCurrentPlaylist.value().empty())
|
|
||||||
startRandomTitle();
|
|
||||||
|
|
||||||
Environment env = Env_Normal;
|
Environment env = Env_Normal;
|
||||||
if (mListenerUnderwater)
|
if (mListenerUnderwater)
|
||||||
env = Env_Underwater;
|
env = Env_Underwater;
|
||||||
|
@ -1165,7 +1091,7 @@ namespace MWSound
|
||||||
if (isMainMenu && !isMusicPlaying())
|
if (isMainMenu && !isMusicPlaying())
|
||||||
{
|
{
|
||||||
if (mVFS->exists(MWSound::titleMusic))
|
if (mVFS->exists(MWSound::titleMusic))
|
||||||
streamMusic(MWSound::titleMusic, MWSound::MusicType::Special);
|
streamMusic(MWSound::titleMusic, MWSound::MusicType::Normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSounds(duration);
|
updateSounds(duration);
|
||||||
|
|
|
@ -52,12 +52,6 @@ namespace MWSound
|
||||||
|
|
||||||
std::unique_ptr<Sound_Output> mOutput;
|
std::unique_ptr<Sound_Output> mOutput;
|
||||||
|
|
||||||
// Caches available music tracks by <playlist name, (sound files) >
|
|
||||||
std::unordered_map<VFS::Path::Normalized, std::vector<VFS::Path::Normalized>, VFS::Path::Hash, std::equal_to<>>
|
|
||||||
mMusicFiles;
|
|
||||||
std::unordered_map<std::string, std::vector<int>> mMusicToPlay; // A list with music files not yet played
|
|
||||||
VFS::Path::Normalized mLastPlayedMusic; // The music file that was last played
|
|
||||||
|
|
||||||
WaterSoundUpdater mWaterSoundUpdater;
|
WaterSoundUpdater mWaterSoundUpdater;
|
||||||
|
|
||||||
SoundBufferPool mSoundBuffers;
|
SoundBufferPool mSoundBuffers;
|
||||||
|
@ -127,7 +121,6 @@ namespace MWSound
|
||||||
|
|
||||||
void streamMusicFull(VFS::Path::NormalizedView filename);
|
void streamMusicFull(VFS::Path::NormalizedView filename);
|
||||||
void advanceMusic(VFS::Path::NormalizedView filename, float fadeOut = 1.f);
|
void advanceMusic(VFS::Path::NormalizedView filename, float fadeOut = 1.f);
|
||||||
void startRandomTitle();
|
|
||||||
|
|
||||||
void cull3DSound(SoundBase* sound);
|
void cull3DSound(SoundBase* sound);
|
||||||
|
|
||||||
|
@ -185,11 +178,6 @@ namespace MWSound
|
||||||
bool isMusicPlaying() override;
|
bool isMusicPlaying() override;
|
||||||
///< Returns true if music is playing
|
///< Returns true if music is playing
|
||||||
|
|
||||||
void playPlaylist(VFS::Path::NormalizedView playlist) override;
|
|
||||||
///< Start playing music from the selected folder
|
|
||||||
/// \param name of the folder that contains the playlist
|
|
||||||
/// Title music playlist is predefined
|
|
||||||
|
|
||||||
void say(const MWWorld::ConstPtr& reference, VFS::Path::NormalizedView filename) override;
|
void say(const MWWorld::ConstPtr& reference, VFS::Path::NormalizedView filename) override;
|
||||||
///< Make an actor say some text.
|
///< Make an actor say some text.
|
||||||
/// \param filename name of a sound file in the VFS
|
/// \param filename name of a sound file in the VFS
|
||||||
|
|
|
@ -396,7 +396,6 @@ namespace MWWorld
|
||||||
{
|
{
|
||||||
// Make sure that we do not continue to play a Title music after a new game video.
|
// Make sure that we do not continue to play a Title music after a new game video.
|
||||||
MWBase::Environment::get().getSoundManager()->stopMusic();
|
MWBase::Environment::get().getSoundManager()->stopMusic();
|
||||||
MWBase::Environment::get().getSoundManager()->playPlaylist(MWSound::explorePlaylist);
|
|
||||||
MWBase::Environment::get().getWindowManager()->playVideo(video, true);
|
MWBase::Environment::get().getWindowManager()->playVideo(video, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ paths=(
|
||||||
scripts/omw/mechanics/animationcontroller.lua
|
scripts/omw/mechanics/animationcontroller.lua
|
||||||
scripts/omw/input/gamepadcontrols.lua
|
scripts/omw/input/gamepadcontrols.lua
|
||||||
scripts/omw/camera/camera.lua
|
scripts/omw/camera/camera.lua
|
||||||
|
scripts/omw/music/music.lua
|
||||||
scripts/omw/mwui/init.lua
|
scripts/omw/mwui/init.lua
|
||||||
scripts/omw/settings/player.lua
|
scripts/omw/settings/player.lua
|
||||||
scripts/omw/ui.lua
|
scripts/omw/ui.lua
|
||||||
|
|
|
@ -41,6 +41,7 @@ Lua API reference
|
||||||
interface_controls
|
interface_controls
|
||||||
interface_gamepadcontrols
|
interface_gamepadcontrols
|
||||||
interface_item_usage
|
interface_item_usage
|
||||||
|
interface_music
|
||||||
interface_mwui
|
interface_mwui
|
||||||
interface_settings
|
interface_settings
|
||||||
interface_skill_progression
|
interface_skill_progression
|
||||||
|
|
8
docs/source/reference/lua-scripting/interface_music.rst
Normal file
8
docs/source/reference/lua-scripting/interface_music.rst
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
Interface Music
|
||||||
|
===============
|
||||||
|
|
||||||
|
.. include:: version.rst
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
:file: generated_html/scripts_omw_music_music.html
|
||||||
|
|
|
@ -39,6 +39,9 @@
|
||||||
* - :ref:`MWUI <Interface MWUI>`
|
* - :ref:`MWUI <Interface MWUI>`
|
||||||
- by player scripts
|
- by player scripts
|
||||||
- Morrowind-style UI templates.
|
- Morrowind-style UI templates.
|
||||||
|
* - :ref:`Music <Interface Music>`
|
||||||
|
- by player scripts
|
||||||
|
- Provides access to music playlists.
|
||||||
* - :ref:`UI <Interface UI>`
|
* - :ref:`UI <Interface UI>`
|
||||||
- by player scripts
|
- by player scripts
|
||||||
- | High-level UI modes interface. Allows to override parts
|
- | High-level UI modes interface. Allows to override parts
|
||||||
|
|
|
@ -48,6 +48,13 @@ set(BUILTIN_DATA_FILES
|
||||||
l10n/OMWEngine/sv.yaml
|
l10n/OMWEngine/sv.yaml
|
||||||
l10n/OMWEngine/fr.yaml
|
l10n/OMWEngine/fr.yaml
|
||||||
|
|
||||||
|
# L10n for music system
|
||||||
|
l10n/OMWMusic/de.yaml
|
||||||
|
l10n/OMWMusic/en.yaml
|
||||||
|
l10n/OMWMusic/ru.yaml
|
||||||
|
l10n/OMWMusic/sv.yaml
|
||||||
|
l10n/OMWMusic/fr.yaml
|
||||||
|
|
||||||
# L10n for post-processing HUD and built-in shaders
|
# L10n for post-processing HUD and built-in shaders
|
||||||
l10n/OMWShaders/de.yaml
|
l10n/OMWShaders/de.yaml
|
||||||
l10n/OMWShaders/en.yaml
|
l10n/OMWShaders/en.yaml
|
||||||
|
@ -79,6 +86,10 @@ set(BUILTIN_DATA_FILES
|
||||||
scripts/omw/mechanics/animationcontroller.lua
|
scripts/omw/mechanics/animationcontroller.lua
|
||||||
scripts/omw/mechanics/playercontroller.lua
|
scripts/omw/mechanics/playercontroller.lua
|
||||||
scripts/omw/settings/menu.lua
|
scripts/omw/settings/menu.lua
|
||||||
|
scripts/omw/music/actor.lua
|
||||||
|
scripts/omw/music/helpers.lua
|
||||||
|
scripts/omw/music/music.lua
|
||||||
|
scripts/omw/music/settings.lua
|
||||||
scripts/omw/settings/player.lua
|
scripts/omw/settings/player.lua
|
||||||
scripts/omw/settings/global.lua
|
scripts/omw/settings/global.lua
|
||||||
scripts/omw/settings/common.lua
|
scripts/omw/settings/common.lua
|
||||||
|
|
|
@ -31,3 +31,8 @@ MENU: scripts/omw/console/menu.lua
|
||||||
PLAYER: scripts/omw/console/player.lua
|
PLAYER: scripts/omw/console/player.lua
|
||||||
GLOBAL: scripts/omw/console/global.lua
|
GLOBAL: scripts/omw/console/global.lua
|
||||||
CUSTOM: scripts/omw/console/local.lua
|
CUSTOM: scripts/omw/console/local.lua
|
||||||
|
|
||||||
|
# Music system
|
||||||
|
PLAYER: scripts/omw/music/music.lua
|
||||||
|
MENU: scripts/omw/music/settings.lua
|
||||||
|
NPC,CREATURE: scripts/omw/music/actor.lua
|
||||||
|
|
7
files/data/l10n/OMWMusic/de.yaml
Executable file
7
files/data/l10n/OMWMusic/de.yaml
Executable file
|
@ -0,0 +1,7 @@
|
||||||
|
Music: "OpenMW: Musik"
|
||||||
|
settingsPageDescription: "OpenMW-Musikeinstellungen"
|
||||||
|
|
||||||
|
musicSettings: "Musik"
|
||||||
|
|
||||||
|
CombatMusicEnabled: "Kampfmusik abspielen"
|
||||||
|
CombatMusicEnabledDescription: "Falls aktiviert, wechselt das Spiel zu Kampfmusik, sobald sich Akteure im Kampf befinden."
|
7
files/data/l10n/OMWMusic/en.yaml
Executable file
7
files/data/l10n/OMWMusic/en.yaml
Executable file
|
@ -0,0 +1,7 @@
|
||||||
|
Music: "OpenMW Music"
|
||||||
|
settingsPageDescription: "OpenMW Music settings"
|
||||||
|
|
||||||
|
musicSettings: "Music configuration"
|
||||||
|
|
||||||
|
CombatMusicEnabled: "Play combat music"
|
||||||
|
CombatMusicEnabledDescription: "If enabled, the game switches to combat music if there are actors in combat."
|
7
files/data/l10n/OMWMusic/fr.yaml
Executable file
7
files/data/l10n/OMWMusic/fr.yaml
Executable file
|
@ -0,0 +1,7 @@
|
||||||
|
#Music: "OpenMW Music"
|
||||||
|
#settingsPageDescription: "OpenMW Music settings"
|
||||||
|
|
||||||
|
#musicSettings: "Music configuration"
|
||||||
|
|
||||||
|
#CombatMusicEnabled: "Play combat music"
|
||||||
|
#CombatMusicEnabledDescription: "Whether to switch to combat music when there are actors in combat."
|
7
files/data/l10n/OMWMusic/ru.yaml
Executable file
7
files/data/l10n/OMWMusic/ru.yaml
Executable file
|
@ -0,0 +1,7 @@
|
||||||
|
Music: "Музыка OpenMW"
|
||||||
|
settingsPageDescription: "Настройки музыки OpenMW"
|
||||||
|
|
||||||
|
musicSettings: "Настройки музыки"
|
||||||
|
|
||||||
|
CombatMusicEnabled: "Играть боевую музыку"
|
||||||
|
CombatMusicEnabledDescription: "Нужно ли переключаться на боевую музыку, когда есть сражающиеся персонажи."
|
7
files/data/l10n/OMWMusic/sv.yaml
Executable file
7
files/data/l10n/OMWMusic/sv.yaml
Executable file
|
@ -0,0 +1,7 @@
|
||||||
|
Music: "OpenMW Musik"
|
||||||
|
settingsPageDescription: "OpenMW Musikinställningar"
|
||||||
|
|
||||||
|
musicSettings: "Musikkonfiguration"
|
||||||
|
|
||||||
|
CombatMusicEnabled: "Spela stridsmusik"
|
||||||
|
CombatMusicEnabledDescription: "Om aktiv kommer spelet byta till stridsmusik när det finns figurer som är i strid."
|
63
files/data/scripts/omw/music/actor.lua
Executable file
63
files/data/scripts/omw/music/actor.lua
Executable file
|
@ -0,0 +1,63 @@
|
||||||
|
local AI = require("openmw.interfaces").AI
|
||||||
|
local self = require("openmw.self")
|
||||||
|
local types = require("openmw.types")
|
||||||
|
local nearby = require("openmw.nearby")
|
||||||
|
|
||||||
|
local targets = {}
|
||||||
|
|
||||||
|
local function emitTargetsChanged()
|
||||||
|
for _, actor in ipairs(nearby.players) do
|
||||||
|
actor:sendEvent("OMWMusicCombatTargetsChanged", { actor = self, targets = targets })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function onUpdate()
|
||||||
|
if types.Actor.isDeathFinished(self) or not types.Actor.isInActorsProcessingRange(self) then
|
||||||
|
if next(targets) ~= nil then
|
||||||
|
targets = {}
|
||||||
|
emitTargetsChanged()
|
||||||
|
end
|
||||||
|
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Early-out for actors without targets and without combat state
|
||||||
|
-- TODO: use events or engine handlers to detect when targets change
|
||||||
|
local isStanceNothing = types.Actor.getStance(self) == types.Actor.STANCE.Nothing
|
||||||
|
if isStanceNothing and next(targets) == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local newTargets = AI.getTargets("Combat")
|
||||||
|
|
||||||
|
local changed = false
|
||||||
|
if #newTargets ~= #targets then
|
||||||
|
changed = true
|
||||||
|
else
|
||||||
|
for i, target in ipairs(targets) do
|
||||||
|
if target ~= newTargets[i] then
|
||||||
|
changed = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
targets = newTargets
|
||||||
|
if changed then
|
||||||
|
emitTargetsChanged()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function onInactive()
|
||||||
|
if next(targets) ~= nil then
|
||||||
|
targets = {}
|
||||||
|
emitTargetsChanged()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
engineHandlers = {
|
||||||
|
onUpdate = onUpdate,
|
||||||
|
onInactive = onInactive,
|
||||||
|
},
|
||||||
|
}
|
106
files/data/scripts/omw/music/helpers.lua
Executable file
106
files/data/scripts/omw/music/helpers.lua
Executable file
|
@ -0,0 +1,106 @@
|
||||||
|
local debug = require('openmw.debug')
|
||||||
|
local storage = require('openmw.storage')
|
||||||
|
local vfs = require('openmw.vfs')
|
||||||
|
|
||||||
|
local playlistsSection = storage.playerSection('OMWMusicPlaylistsTrackOrder')
|
||||||
|
playlistsSection:setLifeTime(storage.LIFE_TIME.GameSession)
|
||||||
|
|
||||||
|
local function getTracksFromDirectory(path)
|
||||||
|
local result = {}
|
||||||
|
for fileName in vfs.pathsWithPrefix(path) do
|
||||||
|
table.insert(result, fileName)
|
||||||
|
end
|
||||||
|
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
local function initMissingPlaylistFields(playlist)
|
||||||
|
if playlist.id == nil or playlist.priority == nil then
|
||||||
|
error("Can not register playlist: 'id' and 'priority' are mandatory fields")
|
||||||
|
end
|
||||||
|
|
||||||
|
if playlist.tracks == nil then
|
||||||
|
playlist.tracks = getTracksFromDirectory(string.format("music/%s/", playlist.id))
|
||||||
|
end
|
||||||
|
|
||||||
|
if playlist.active == nil then
|
||||||
|
playlist.active = false
|
||||||
|
end
|
||||||
|
|
||||||
|
if playlist.randomize == nil then
|
||||||
|
playlist.randomize = false
|
||||||
|
end
|
||||||
|
|
||||||
|
if playlist.cycleTracks == nil then
|
||||||
|
playlist.cycleTracks = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if playlist.playOneTrack == nil then
|
||||||
|
playlist.playOneTrack = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function shuffle(data)
|
||||||
|
for i = #data, 1, -1 do
|
||||||
|
local j = math.random(i)
|
||||||
|
data[i], data[j] = data[j], data[i]
|
||||||
|
end
|
||||||
|
return data
|
||||||
|
end
|
||||||
|
|
||||||
|
local function initTracksOrder(tracks, randomize)
|
||||||
|
local tracksOrder = {}
|
||||||
|
for i, track in ipairs(tracks) do
|
||||||
|
tracksOrder[i] = i
|
||||||
|
end
|
||||||
|
|
||||||
|
if randomize then
|
||||||
|
shuffle(tracksOrder)
|
||||||
|
end
|
||||||
|
|
||||||
|
return tracksOrder
|
||||||
|
end
|
||||||
|
|
||||||
|
local function isPlaylistActive(playlist)
|
||||||
|
return playlist.active and next(playlist.tracks) ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getStoredTracksOrder()
|
||||||
|
-- We need a writeable playlists table here.
|
||||||
|
return playlistsSection:asTable()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function setStoredTracksOrder(playlistId, playlistTracksOrder)
|
||||||
|
playlistsSection:set(playlistId, playlistTracksOrder)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getActivePlaylistByPriority(playlists)
|
||||||
|
local newPlaylist = nil
|
||||||
|
for _, playlist in pairs(playlists) do
|
||||||
|
if isPlaylistActive(playlist) then
|
||||||
|
if newPlaylist == nil or playlist.priority < newPlaylist.priority or
|
||||||
|
(playlist.priority == newPlaylist.priority and playlist.registrationOrder > newPlaylist.registrationOrder) then
|
||||||
|
newPlaylist = playlist
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return newPlaylist
|
||||||
|
end
|
||||||
|
|
||||||
|
local function isInCombat(fightingActors)
|
||||||
|
return next(fightingActors) ~= nil and debug.isAIEnabled()
|
||||||
|
end
|
||||||
|
|
||||||
|
local functions = {
|
||||||
|
getActivePlaylistByPriority = getActivePlaylistByPriority,
|
||||||
|
getStoredTracksOrder = getStoredTracksOrder,
|
||||||
|
getTracksFromDirectory = getTracksFromDirectory,
|
||||||
|
initMissingPlaylistFields = initMissingPlaylistFields,
|
||||||
|
initTracksOrder = initTracksOrder,
|
||||||
|
isInCombat = isInCombat,
|
||||||
|
isPlaylistActive = isPlaylistActive,
|
||||||
|
setStoredTracksOrder = setStoredTracksOrder
|
||||||
|
}
|
||||||
|
|
||||||
|
return functions
|
189
files/data/scripts/omw/music/music.lua
Executable file
189
files/data/scripts/omw/music/music.lua
Executable file
|
@ -0,0 +1,189 @@
|
||||||
|
local ambient = require('openmw.ambient')
|
||||||
|
local core = require('openmw.core')
|
||||||
|
local self = require('openmw.self')
|
||||||
|
local storage = require('openmw.storage')
|
||||||
|
local types = require('openmw.types')
|
||||||
|
|
||||||
|
local musicSettings = storage.playerSection('SettingsOMWMusic')
|
||||||
|
|
||||||
|
local helpers = require('scripts.omw.music.helpers')
|
||||||
|
|
||||||
|
local registeredPlaylists = {}
|
||||||
|
local playlistsTracksOrder = helpers.getStoredTracksOrder()
|
||||||
|
local fightingActors = {}
|
||||||
|
local registrationOrder = 0
|
||||||
|
|
||||||
|
local currentPlaylist = nil
|
||||||
|
local skippedOneFrame = false
|
||||||
|
|
||||||
|
local battlePriority = 10
|
||||||
|
local explorePriority = 100
|
||||||
|
|
||||||
|
local function registerPlaylist(playlist)
|
||||||
|
helpers.initMissingPlaylistFields(playlist)
|
||||||
|
|
||||||
|
local existingOrder = playlistsTracksOrder[playlist.id]
|
||||||
|
if not existingOrder or next(existingOrder) == nil or math.max(unpack(existingOrder)) > #playlist.tracks then
|
||||||
|
local newPlaylistOrder = helpers.initTracksOrder(playlist.tracks, playlist.randomize)
|
||||||
|
playlistsTracksOrder[playlist.id] = newPlaylistOrder
|
||||||
|
helpers.setStoredTracksOrder(playlist.id, newPlaylistOrder)
|
||||||
|
else
|
||||||
|
playlistsTracksOrder[playlist.id] = existingOrder
|
||||||
|
end
|
||||||
|
|
||||||
|
if registeredPlaylists[playlist.id] == nil then
|
||||||
|
playlist.registrationOrder = registrationOrder
|
||||||
|
registrationOrder = registrationOrder + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
registeredPlaylists[playlist.id] = playlist
|
||||||
|
end
|
||||||
|
|
||||||
|
local function setPlaylistActive(id, state)
|
||||||
|
if id == nil then
|
||||||
|
error("Playlist ID is nil")
|
||||||
|
end
|
||||||
|
|
||||||
|
local playlist = registeredPlaylists[id]
|
||||||
|
if playlist then
|
||||||
|
playlist.active = state
|
||||||
|
else
|
||||||
|
error(string.format("Playlist '%s' is not registered.", id))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function onCombatTargetsChanged(eventData)
|
||||||
|
if eventData.actor == nil then return end
|
||||||
|
|
||||||
|
if next(eventData.targets) ~= nil then
|
||||||
|
fightingActors[eventData.actor.id] = true
|
||||||
|
else
|
||||||
|
fightingActors[eventData.actor.id] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function playerDied()
|
||||||
|
ambient.streamMusic("music/special/mw_death.mp3")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function switchPlaylist(newPlaylist)
|
||||||
|
local newPlaylistOrder = playlistsTracksOrder[newPlaylist.id]
|
||||||
|
local nextTrackIndex = table.remove(newPlaylistOrder)
|
||||||
|
|
||||||
|
if nextTrackIndex == nil then
|
||||||
|
error("Can not fetch track: nextTrackIndex is nil")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- If there are no tracks left, fill playlist again.
|
||||||
|
if next(newPlaylistOrder) == nil then
|
||||||
|
newPlaylistOrder = helpers.initTracksOrder(newPlaylist.tracks, newPlaylist.randomize)
|
||||||
|
|
||||||
|
if not newPlaylist.cycleTracks then
|
||||||
|
newPlaylist.deactivateAfterEnd = true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- If next track for randomized playist will be the same as one we want to play, swap it with random track.
|
||||||
|
if newPlaylist.randomize and #newPlaylistOrder > 1 and newPlaylistOrder[1] == nextTrackIndex then
|
||||||
|
local index = math.random(2, #newPlaylistOrder)
|
||||||
|
newPlaylistOrder[1], newPlaylistOrder[index] = newPlaylistOrder[index], newPlaylistOrder[1]
|
||||||
|
end
|
||||||
|
|
||||||
|
playlistsTracksOrder[newPlaylist.id] = newPlaylistOrder
|
||||||
|
end
|
||||||
|
|
||||||
|
helpers.setStoredTracksOrder(newPlaylist.id, newPlaylistOrder)
|
||||||
|
|
||||||
|
local trackPath = newPlaylist.tracks[nextTrackIndex]
|
||||||
|
if trackPath == nil then
|
||||||
|
error(string.format("Can not fetch track with index %s from playlist '%s'.", nextTrackIndex, newPlaylist.id))
|
||||||
|
else
|
||||||
|
ambient.streamMusic(trackPath, newPlaylist.fadeOut)
|
||||||
|
if newPlaylist.playOneTrack then
|
||||||
|
newPlaylist.deactivateAfterEnd = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
currentPlaylist = newPlaylist
|
||||||
|
end
|
||||||
|
|
||||||
|
local function onFrame(dt)
|
||||||
|
-- Skip a first frame to allow other scripts to update playlists state.
|
||||||
|
if not skippedOneFrame then
|
||||||
|
skippedOneFrame = true
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if not core.sound.isEnabled() then return end
|
||||||
|
|
||||||
|
-- Do not allow to switch playlists when player is dead
|
||||||
|
local musicPlaying = ambient.isMusicPlaying()
|
||||||
|
if types.Actor.isDead(self) and musicPlaying then return end
|
||||||
|
|
||||||
|
local combatMusicEnabled = musicSettings:get("CombatMusicEnabled") and helpers.isInCombat(fightingActors)
|
||||||
|
setPlaylistActive("battle", combatMusicEnabled)
|
||||||
|
|
||||||
|
local newPlaylist = helpers.getActivePlaylistByPriority(registeredPlaylists)
|
||||||
|
|
||||||
|
if not newPlaylist then
|
||||||
|
ambient.stopMusic()
|
||||||
|
|
||||||
|
if currentPlaylist ~= nil then
|
||||||
|
currentPlaylist.deactivateAfterEnd = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
currentPlaylist = nil
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if newPlaylist == currentPlaylist and musicPlaying then return end
|
||||||
|
|
||||||
|
if newPlaylist and newPlaylist.deactivateAfterEnd then
|
||||||
|
newPlaylist.deactivateAfterEnd = nil
|
||||||
|
newPlaylist.active = false
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
switchPlaylist(newPlaylist)
|
||||||
|
end
|
||||||
|
|
||||||
|
registerPlaylist({ id = "battle", priority = battlePriority, randomize = true })
|
||||||
|
registerPlaylist({ id = "explore", priority = explorePriority, randomize = true, active = true })
|
||||||
|
|
||||||
|
return {
|
||||||
|
---
|
||||||
|
-- @module Music
|
||||||
|
-- @usage require('openmw.interfaces').Music
|
||||||
|
interfaceName = 'Music',
|
||||||
|
interface = {
|
||||||
|
--- Interface version
|
||||||
|
-- @field [parent=#Music] #number version
|
||||||
|
version = 0,
|
||||||
|
---
|
||||||
|
-- Set state for playlist with given ID
|
||||||
|
-- @function [parent=#Music] setPlaylistActive
|
||||||
|
-- @param #string id Playlist ID
|
||||||
|
-- @param #boolean state Playlist is active
|
||||||
|
setPlaylistActive = setPlaylistActive,
|
||||||
|
---
|
||||||
|
-- Register given playlist
|
||||||
|
-- @function [parent=#Music] registerPlaylist
|
||||||
|
-- @param #table playlist Playlist data. Can contain:
|
||||||
|
--
|
||||||
|
-- * `id` - #string, playlist ID
|
||||||
|
-- * `priority` - #number, playlist priority (lower value means higher priority)
|
||||||
|
-- * `fadeOut` - #number, Time in seconds to fade out current track before starting this one. If nil, allow the engine to choose the value.
|
||||||
|
-- * `tracks` - #list<#string>, Paths of track files for playlist (if nil, use all tracks from 'music/{id}/' folder)
|
||||||
|
-- * `active` - #boolean, tells if playlist is active (default is false)
|
||||||
|
-- * `randomize` - #boolean, tells if playlist should shuffle its tracks during playback (default is false). When all tracks are played, they are randomized again.
|
||||||
|
-- * `playOne` - #boolean, tells if playlist should be automatically deactivated after one track is played (default is false)
|
||||||
|
-- * `cycleTracks` - #boolean, if true, tells to start playlist from beginning once all tracks are played, otherwise playlist becomes deactivated (default is true).
|
||||||
|
registerPlaylist = registerPlaylist
|
||||||
|
},
|
||||||
|
engineHandlers = {
|
||||||
|
onFrame = onFrame
|
||||||
|
},
|
||||||
|
eventHandlers = {
|
||||||
|
Died = playerDied,
|
||||||
|
OMWMusicCombatTargetsChanged = onCombatTargetsChanged
|
||||||
|
}
|
||||||
|
}
|
27
files/data/scripts/omw/music/settings.lua
Executable file
27
files/data/scripts/omw/music/settings.lua
Executable file
|
@ -0,0 +1,27 @@
|
||||||
|
local I = require('openmw.interfaces')
|
||||||
|
local storage = require('openmw.storage')
|
||||||
|
|
||||||
|
I.Settings.registerPage({
|
||||||
|
key = 'OMWMusic',
|
||||||
|
l10n = 'OMWMusic',
|
||||||
|
name = 'Music',
|
||||||
|
description = 'settingsPageDescription',
|
||||||
|
})
|
||||||
|
|
||||||
|
I.Settings.registerGroup({
|
||||||
|
key = "SettingsOMWMusic",
|
||||||
|
page = 'OMWMusic',
|
||||||
|
l10n = 'OMWMusic',
|
||||||
|
name = 'musicSettings',
|
||||||
|
permanentStorage = true,
|
||||||
|
order = 0,
|
||||||
|
settings = {
|
||||||
|
{
|
||||||
|
key = 'CombatMusicEnabled',
|
||||||
|
renderer = 'checkbox',
|
||||||
|
name = 'CombatMusicEnabled',
|
||||||
|
description = 'CombatMusicEnabledDescription',
|
||||||
|
default = true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
|
@ -17,6 +17,9 @@
|
||||||
---
|
---
|
||||||
-- @field [parent=#interfaces] scripts.omw.mwui.init#scripts.omw.mwui.init MWUI
|
-- @field [parent=#interfaces] scripts.omw.mwui.init#scripts.omw.mwui.init MWUI
|
||||||
|
|
||||||
|
---
|
||||||
|
-- @field [parent=#interfaces] scripts.omw.music.music#scripts.omw.music.music Music
|
||||||
|
|
||||||
---
|
---
|
||||||
-- @field [parent=#interfaces] scripts.omw.settings.player#scripts.omw.settings.player Settings
|
-- @field [parent=#interfaces] scripts.omw.settings.player#scripts.omw.settings.player Settings
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue