1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-03-29 22:20:33 +00:00

Implement Lua bindings for sound system

This commit is contained in:
Andrei Kortunov 2023-07-19 17:21:17 +04:00
parent 18f3e937cb
commit 7ce9fc25c5
21 changed files with 605 additions and 51 deletions

View File

@ -61,7 +61,7 @@ add_openmw_dir (mwscript
add_openmw_dir (mwlua add_openmw_dir (mwlua
luamanagerimp object worldview userdataserializer luaevents engineevents objectvariant luamanagerimp object worldview userdataserializer luaevents engineevents objectvariant
context globalscripts localscripts playerscripts luabindings objectbindings cellbindings mwscriptbindings context globalscripts localscripts playerscripts luabindings objectbindings cellbindings mwscriptbindings
camerabindings uibindings inputbindings nearbybindings postprocessingbindings stats debugbindings camerabindings 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 worker magicbindings
) )

View File

@ -52,6 +52,7 @@ namespace MWSound
NoScaling = 1 << 4, /* Don't scale audio with simulation time */ NoScaling = 1 << 4, /* Don't scale audio with simulation time */
NoEnvNoScaling = NoEnv | NoScaling, NoEnvNoScaling = NoEnv | NoScaling,
LoopNoEnv = Loop | NoEnv, LoopNoEnv = Loop | NoEnv,
LoopNoEnvNoScaling = Loop | NoEnv | NoScaling,
LoopRemoveAtDistance = Loop | RemoveAtDistance LoopRemoveAtDistance = Loop | RemoveAtDistance
}; };
@ -117,11 +118,11 @@ namespace MWBase
virtual void say(const MWWorld::ConstPtr& reference, const std::string& filename) = 0; virtual void say(const MWWorld::ConstPtr& reference, const std::string& filename) = 0;
///< Make an actor say some text. ///< Make an actor say some text.
/// \param filename name of a sound file in "Sound/" in the data directory. /// \param filename name of a sound file in the VFS
virtual void say(const std::string& filename) = 0; virtual void say(const std::string& filename) = 0;
///< Say some text, without an actor ref ///< Say some text, without an actor ref
/// \param filename name of a sound file in "Sound/" in the data directory. /// \param filename name of a sound file in the VFS
virtual bool sayActive(const MWWorld::ConstPtr& reference = MWWorld::ConstPtr()) const = 0; virtual bool sayActive(const MWWorld::ConstPtr& reference = MWWorld::ConstPtr()) const = 0;
///< Is actor not speaking? ///< Is actor not speaking?
@ -155,6 +156,12 @@ namespace MWBase
///< Play a sound, independently of 3D-position ///< Play a sound, independently of 3D-position
///< @param offset Number of seconds into the sound to start playback. ///< @param offset Number of seconds into the sound to start playback.
virtual Sound* playSound(std::string_view fileName, float volume, float pitch, Type type = Type::Sfx,
PlayMode mode = PlayMode::Normal, float offset = 0)
= 0;
///< Play a sound, independently of 3D-position
///< @param offset Number of seconds into the sound to start playback.
virtual Sound* playSound3D(const MWWorld::ConstPtr& reference, const ESM::RefId& soundId, float volume, virtual Sound* playSound3D(const MWWorld::ConstPtr& reference, const ESM::RefId& soundId, float volume,
float pitch, Type type = Type::Sfx, PlayMode mode = PlayMode::Normal, float offset = 0) float pitch, Type type = Type::Sfx, PlayMode mode = PlayMode::Normal, float offset = 0)
= 0; = 0;
@ -162,6 +169,13 @@ namespace MWBase
///< Play_NoTrack is specified. ///< Play_NoTrack is specified.
///< @param offset Number of seconds into the sound to start playback. ///< @param offset Number of seconds into the sound to start playback.
virtual Sound* playSound3D(const MWWorld::ConstPtr& reference, std::string_view fileName, float volume,
float pitch, Type type = Type::Sfx, PlayMode mode = PlayMode::Normal, float offset = 0)
= 0;
///< Play a 3D sound attached to an MWWorld::Ptr. Will be updated automatically with the Ptr's position, unless
///< Play_NoTrack is specified.
///< @param offset Number of seconds into the sound to start playback.
virtual Sound* playSound3D(const osg::Vec3f& initialPos, const ESM::RefId& soundId, float volume, float pitch, virtual Sound* playSound3D(const osg::Vec3f& initialPos, const ESM::RefId& soundId, float volume, float pitch,
Type type = Type::Sfx, PlayMode mode = PlayMode::Normal, float offset = 0) Type type = Type::Sfx, PlayMode mode = PlayMode::Normal, float offset = 0)
= 0; = 0;
@ -172,7 +186,10 @@ namespace MWBase
///< Stop the given sound from playing ///< Stop the given sound from playing
virtual void stopSound3D(const MWWorld::ConstPtr& reference, const ESM::RefId& soundId) = 0; virtual void stopSound3D(const MWWorld::ConstPtr& reference, const ESM::RefId& soundId) = 0;
///< Stop the given object from playing the given sound, ///< Stop the given object from playing the given sound.
virtual void stopSound3D(const MWWorld::ConstPtr& reference, std::string_view fileName) = 0;
///< Stop the given object from playing the given sound.
virtual void stopSound3D(const MWWorld::ConstPtr& reference) = 0; virtual void stopSound3D(const MWWorld::ConstPtr& reference) = 0;
///< Stop the given object from playing all sounds. ///< Stop the given object from playing all sounds.
@ -190,6 +207,10 @@ namespace MWBase
///< Is the given sound currently playing on the given object? ///< Is the given sound currently playing on the given object?
/// If you want to check if sound played with playSound is playing, use empty Ptr /// If you want to check if sound played with playSound is playing, use empty Ptr
virtual bool getSoundPlaying(const MWWorld::ConstPtr& reference, std::string_view fileName) const = 0;
///< Is the given sound currently playing on the given object?
/// If you want to check if sound played with playSound is playing, use empty Ptr
virtual void pauseSounds(MWSound::BlockerType blocker, int types = int(Type::Mask)) = 0; virtual void pauseSounds(MWSound::BlockerType blocker, int types = int(Type::Mask)) = 0;
///< Pauses all currently playing sounds, including music. ///< Pauses all currently playing sounds, including music.

View File

@ -23,6 +23,8 @@
#include <components/interpreter/defines.hpp> #include <components/interpreter/defines.hpp>
#include <components/interpreter/interpreter.hpp> #include <components/interpreter/interpreter.hpp>
#include <components/misc/resourcehelpers.hpp>
#include <components/settings/values.hpp> #include <components/settings/values.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
@ -650,7 +652,7 @@ namespace MWDialogue
if (Settings::gui().mSubtitles) if (Settings::gui().mSubtitles)
winMgr->messageBox(info->mResponse); winMgr->messageBox(info->mResponse);
if (!info->mSound.empty()) if (!info->mSound.empty())
sndMgr->say(actor, info->mSound); sndMgr->say(actor, Misc::ResourceHelpers::correctSoundPath(info->mSound));
if (!info->mResultScript.empty()) if (!info->mResultScript.empty())
executeScript(info->mResultScript, actor); executeScript(info->mResultScript, actor);
} }

View File

@ -4,6 +4,7 @@
#include <components/debug/debuglog.hpp> #include <components/debug/debuglog.hpp>
#include <components/fallback/fallback.hpp> #include <components/fallback/fallback.hpp>
#include <components/misc/resourcehelpers.hpp>
#include <components/misc/rng.hpp> #include <components/misc/rng.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
@ -656,7 +657,7 @@ namespace MWGui
+= MyGUI::newDelegate(this, &CharacterCreation::onClassQuestionChosen); += MyGUI::newDelegate(this, &CharacterCreation::onClassQuestionChosen);
mGenerateClassQuestionDialog->setVisible(true); mGenerateClassQuestionDialog->setVisible(true);
MWBase::Environment::get().getSoundManager()->say(step.mSound); MWBase::Environment::get().getSoundManager()->say(Misc::ResourceHelpers::correctSoundPath(step.mSound));
} }
void CharacterCreation::selectGeneratedClass() void CharacterCreation::selectGeneratedClass()

View File

@ -38,6 +38,7 @@
#include "nearbybindings.hpp" #include "nearbybindings.hpp"
#include "objectbindings.hpp" #include "objectbindings.hpp"
#include "postprocessingbindings.hpp" #include "postprocessingbindings.hpp"
#include "soundbindings.hpp"
#include "types/types.hpp" #include "types/types.hpp"
#include "uibindings.hpp" #include "uibindings.hpp"
@ -123,6 +124,7 @@ namespace MWLua
{ std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer) }); { std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer) });
}; };
api["contentFiles"] = initContentFilesBindings(lua->sol()); api["contentFiles"] = initContentFilesBindings(lua->sol());
api["sound"] = initCoreSoundBindings(context);
api["getFormId"] = [](std::string_view contentFile, unsigned int index) -> std::string { api["getFormId"] = [](std::string_view contentFile, unsigned int index) -> std::string {
const std::vector<std::string>& contentList = MWBase::Environment::get().getWorld()->getContentFiles(); const std::vector<std::string>& contentList = MWBase::Environment::get().getWorld()->getContentFiles();
for (size_t i = 0; i < contentList.size(); ++i) for (size_t i = 0; i < contentList.size(); ++i)
@ -324,6 +326,7 @@ namespace MWLua
std::map<std::string, sol::object> initPlayerPackages(const Context& context) std::map<std::string, sol::object> initPlayerPackages(const Context& context)
{ {
return { return {
{ "openmw.ambient", initAmbientPackage(context) },
{ "openmw.camera", initCameraPackage(context.mLua->sol()) }, { "openmw.camera", initCameraPackage(context.mLua->sol()) },
{ "openmw.debug", initDebugPackage(context) }, { "openmw.debug", initDebugPackage(context) },
{ "openmw.input", initInputPackage(context) }, { "openmw.input", initInputPackage(context) },

View File

@ -0,0 +1,159 @@
#include "soundbindings.hpp"
#include "luabindings.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/windowmanager.hpp"
#include "luamanagerimp.hpp"
namespace MWLua
{
struct PlaySoundArgs
{
bool mScale = true;
bool mLoop = false;
float mVolume = 1.f;
float mPitch = 1.f;
float mTimeOffset = 0.f;
};
PlaySoundArgs getPlaySoundArgs(const sol::optional<sol::table>& options)
{
PlaySoundArgs args;
if (options.has_value())
{
args.mLoop = options->get_or("loop", false);
args.mVolume = options->get_or("volume", 1.f);
args.mPitch = options->get_or("pitch", 1.f);
args.mTimeOffset = options->get_or("timeOffset", 0.f);
args.mScale = options->get_or("scale", true);
}
return args;
}
MWSound::PlayMode getPlayMode(const PlaySoundArgs& args, bool is3D)
{
if (is3D)
{
if (args.mLoop)
return MWSound::PlayMode::LoopRemoveAtDistance;
return MWSound::PlayMode::Normal;
}
if (args.mLoop && !args.mScale)
return MWSound::PlayMode::LoopNoEnvNoScaling;
else if (args.mLoop)
return MWSound::PlayMode::LoopNoEnv;
else if (!args.mScale)
return MWSound::PlayMode::NoEnvNoScaling;
return MWSound::PlayMode::NoEnv;
}
sol::table initAmbientPackage(const Context& context)
{
sol::table api(context.mLua->sol(), sol::create);
api["playSound"] = [](std::string_view soundId, const sol::optional<sol::table>& options) {
auto args = getPlaySoundArgs(options);
auto playMode = getPlayMode(args, false);
ESM::RefId sound = ESM::RefId::deserializeText(soundId);
MWBase::Environment::get().getSoundManager()->playSound(
sound, args.mVolume, args.mPitch, MWSound::Type::Sfx, playMode, args.mTimeOffset);
};
api["playSoundFile"] = [](std::string_view fileName, const sol::optional<sol::table>& options) {
auto args = getPlaySoundArgs(options);
auto playMode = getPlayMode(args, false);
MWBase::Environment::get().getSoundManager()->playSound(
fileName, args.mVolume, args.mPitch, MWSound::Type::Sfx, playMode, args.mTimeOffset);
};
api["stopSound"] = [](std::string_view soundId) {
ESM::RefId sound = ESM::RefId::deserializeText(soundId);
MWBase::Environment::get().getSoundManager()->stopSound3D(MWWorld::Ptr(), sound);
};
api["stopSoundFile"] = [](std::string_view fileName) {
MWBase::Environment::get().getSoundManager()->stopSound3D(MWWorld::Ptr(), fileName);
};
api["isSoundPlaying"] = [](std::string_view soundId) {
ESM::RefId sound = ESM::RefId::deserializeText(soundId);
return MWBase::Environment::get().getSoundManager()->getSoundPlaying(MWWorld::Ptr(), sound);
};
api["isSoundFilePlaying"] = [](std::string_view fileName) {
return MWBase::Environment::get().getSoundManager()->getSoundPlaying(MWWorld::Ptr(), fileName);
};
return LuaUtil::makeReadOnly(api);
}
sol::table initCoreSoundBindings(const Context& context)
{
sol::table api(context.mLua->sol(), sol::create);
api["playSound3d"]
= [](std::string_view soundId, const Object& object, const sol::optional<sol::table>& options) {
auto args = getPlaySoundArgs(options);
auto playMode = getPlayMode(args, true);
ESM::RefId sound = ESM::RefId::deserializeText(soundId);
MWBase::Environment::get().getSoundManager()->playSound3D(
object.ptr(), sound, args.mVolume, args.mPitch, MWSound::Type::Sfx, playMode, args.mTimeOffset);
};
api["playSoundFile3d"]
= [](std::string_view fileName, const Object& object, const sol::optional<sol::table>& options) {
auto args = getPlaySoundArgs(options);
auto playMode = getPlayMode(args, true);
MWBase::Environment::get().getSoundManager()->playSound3D(object.ptr(), fileName, args.mVolume,
args.mPitch, MWSound::Type::Sfx, playMode, args.mTimeOffset);
};
api["stopSound3d"] = [](std::string_view soundId, const Object& object) {
ESM::RefId sound = ESM::RefId::deserializeText(soundId);
MWBase::Environment::get().getSoundManager()->stopSound3D(object.ptr(), sound);
};
api["stopSoundFile3d"] = [](std::string_view fileName, const Object& object) {
MWBase::Environment::get().getSoundManager()->stopSound3D(object.ptr(), fileName);
};
api["isSoundPlaying"] = [](std::string_view soundId, const Object& object) {
ESM::RefId sound = ESM::RefId::deserializeText(soundId);
return MWBase::Environment::get().getSoundManager()->getSoundPlaying(object.ptr(), sound);
};
api["isSoundFilePlaying"] = [](std::string_view fileName, const Object& object) {
return MWBase::Environment::get().getSoundManager()->getSoundPlaying(object.ptr(), fileName);
};
api["say"] = sol::overload(
[luaManager = context.mLuaManager](
std::string_view fileName, const Object& object, sol::optional<std::string_view> text) {
MWBase::Environment::get().getSoundManager()->say(object.ptr(), std::string(fileName));
if (text)
luaManager->addUIMessage(*text);
},
[luaManager = context.mLuaManager](std::string_view fileName, sol::optional<std::string_view> text) {
MWBase::Environment::get().getSoundManager()->say(std::string(fileName));
if (text)
luaManager->addUIMessage(*text);
});
api["stopSay"] = sol::overload(
[](const Object& object) {
const MWWorld::Ptr& objPtr = object.ptr();
MWBase::Environment::get().getSoundManager()->stopSay(objPtr);
},
[]() { MWBase::Environment::get().getSoundManager()->stopSay(MWWorld::ConstPtr()); });
api["isSayActive"] = sol::overload(
[](const Object& object) {
const MWWorld::Ptr& objPtr = object.ptr();
return MWBase::Environment::get().getSoundManager()->sayActive(objPtr);
},
[]() { return MWBase::Environment::get().getSoundManager()->sayActive(MWWorld::ConstPtr()); });
return LuaUtil::makeReadOnly(api);
}
}

View File

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

View File

@ -5,6 +5,7 @@
#include <components/interpreter/interpreter.hpp> #include <components/interpreter/interpreter.hpp>
#include <components/interpreter/opcodes.hpp> #include <components/interpreter/opcodes.hpp>
#include <components/interpreter/runtime.hpp> #include <components/interpreter/runtime.hpp>
#include <components/misc/resourcehelpers.hpp>
#include <components/settings/values.hpp> #include <components/settings/values.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
@ -38,7 +39,7 @@ namespace MWScript
std::string_view text = runtime.getStringLiteral(runtime[0].mInteger); std::string_view text = runtime.getStringLiteral(runtime[0].mInteger);
runtime.pop(); runtime.pop();
MWBase::Environment::get().getSoundManager()->say(ptr, file); MWBase::Environment::get().getSoundManager()->say(ptr, Misc::ResourceHelpers::correctSoundPath(file));
if (Settings::gui().mSubtitles) if (Settings::gui().mSubtitles)
context.messageBox(text); context.messageBox(text);

View File

@ -5,6 +5,7 @@
#include <components/debug/debuglog.hpp> #include <components/debug/debuglog.hpp>
#include <components/esm3/loadsoun.hpp> #include <components/esm3/loadsoun.hpp>
#include <components/misc/resourcehelpers.hpp>
#include <components/settings/settings.hpp> #include <components/settings/settings.hpp>
#include <components/vfs/pathutil.hpp> #include <components/vfs/pathutil.hpp>
@ -61,6 +62,35 @@ namespace MWSound
return nullptr; return nullptr;
} }
Sound_Buffer* SoundBufferPool::lookup(std::string_view fileName) const
{
auto soundId = ESM::RefId::stringRefId(fileName);
return lookup(soundId);
}
Sound_Buffer* SoundBufferPool::loadSfx(Sound_Buffer* sfx)
{
if (sfx->getHandle() != nullptr)
return sfx;
auto [handle, size] = mOutput->loadSound(sfx->getResourceName());
if (handle == nullptr)
return {};
sfx->mHandle = handle;
mBufferCacheSize += size;
if (mBufferCacheSize > mBufferCacheMax)
{
unloadUnused();
if (!mUnusedBuffers.empty() && mBufferCacheSize > mBufferCacheMax)
Log(Debug::Warning) << "No unused sound buffers to free, using " << mBufferCacheSize << " bytes!";
}
mUnusedBuffers.push_front(sfx);
return sfx;
}
Sound_Buffer* SoundBufferPool::load(const ESM::RefId& soundId) Sound_Buffer* SoundBufferPool::load(const ESM::RefId& soundId)
{ {
if (mBufferNameMap.empty()) if (mBufferNameMap.empty())
@ -81,25 +111,23 @@ namespace MWSound
sfx = insertSound(soundId, *sound); sfx = insertSound(soundId, *sound);
} }
if (sfx->getHandle() == nullptr) return loadSfx(sfx);
}
Sound_Buffer* SoundBufferPool::load(std::string_view fileName)
{
auto soundId = ESM::RefId::stringRefId(fileName);
Sound_Buffer* sfx;
const auto it = mBufferNameMap.find(soundId);
if (it != mBufferNameMap.end())
sfx = it->second;
else
{ {
auto [handle, size] = mOutput->loadSound(sfx->getResourceName()); sfx = insertSound(fileName);
if (handle == nullptr)
return {};
sfx->mHandle = handle;
mBufferCacheSize += size;
if (mBufferCacheSize > mBufferCacheMax)
{
unloadUnused();
if (!mUnusedBuffers.empty() && mBufferCacheSize > mBufferCacheMax)
Log(Debug::Warning) << "No unused sound buffers to free, using " << mBufferCacheSize << " bytes!";
}
mUnusedBuffers.push_front(sfx);
} }
return sfx; return loadSfx(sfx);
} }
void SoundBufferPool::clear() void SoundBufferPool::clear()
@ -113,6 +141,24 @@ namespace MWSound
mUnusedBuffers.clear(); mUnusedBuffers.clear();
} }
Sound_Buffer* SoundBufferPool::insertSound(std::string_view fileName)
{
static const AudioParams audioParams
= makeAudioParams(MWBase::Environment::get().getESMStore()->get<ESM::GameSetting>());
float volume = 1.f;
float min = std::max(audioParams.mAudioDefaultMinDistance * audioParams.mAudioMinDistanceMult, 1.f);
float max = std::max(min, audioParams.mAudioDefaultMaxDistance * audioParams.mAudioMaxDistanceMult);
min = std::max(min, 1.0f);
max = std::max(min, max);
Sound_Buffer& sfx = mSoundBuffers.emplace_back(fileName, volume, min, max);
mBufferNameMap.emplace(ESM::RefId::stringRefId(fileName), &sfx);
return &sfx;
}
Sound_Buffer* SoundBufferPool::insertSound(const ESM::RefId& soundId, const ESM::Sound& sound) Sound_Buffer* SoundBufferPool::insertSound(const ESM::RefId& soundId, const ESM::Sound& sound)
{ {
static const AudioParams audioParams static const AudioParams audioParams
@ -132,7 +178,8 @@ namespace MWSound
min = std::max(min, 1.0f); min = std::max(min, 1.0f);
max = std::max(min, max); max = std::max(min, max);
Sound_Buffer& sfx = mSoundBuffers.emplace_back("Sound/" + sound.mSound, volume, min, max); Sound_Buffer& sfx
= mSoundBuffers.emplace_back(Misc::ResourceHelpers::correctSoundPath(sound.mSound), volume, min, max);
VFS::Path::normalizeFilenameInPlace(sfx.mResourceName); VFS::Path::normalizeFilenameInPlace(sfx.mResourceName);
mBufferNameMap.emplace(soundId, &sfx); mBufferNameMap.emplace(soundId, &sfx);

View File

@ -69,10 +69,17 @@ namespace MWSound
/// minRange, and maxRange) /// minRange, and maxRange)
Sound_Buffer* lookup(const ESM::RefId& soundId) const; Sound_Buffer* lookup(const ESM::RefId& soundId) const;
/// Lookup a sound by file name for its sound data (resource name, local volume,
/// minRange, and maxRange)
Sound_Buffer* lookup(std::string_view fileName) const;
/// Lookup a soundId for its sound data (resource name, local volume, /// Lookup a soundId for its sound data (resource name, local volume,
/// minRange, and maxRange), and ensure it's ready for use. /// minRange, and maxRange), and ensure it's ready for use.
Sound_Buffer* load(const ESM::RefId& soundId); Sound_Buffer* load(const ESM::RefId& soundId);
// Lookup for a sound by file name, and ensure it's ready for use.
Sound_Buffer* load(std::string_view fileName);
void use(Sound_Buffer& sfx) void use(Sound_Buffer& sfx)
{ {
if (sfx.mUses++ == 0) if (sfx.mUses++ == 0)
@ -92,6 +99,8 @@ namespace MWSound
void clear(); void clear();
private: private:
Sound_Buffer* loadSfx(Sound_Buffer* sfx);
Sound_Output* mOutput; Sound_Output* mOutput;
std::deque<Sound_Buffer> mSoundBuffers; std::deque<Sound_Buffer> mSoundBuffers;
std::unordered_map<ESM::RefId, Sound_Buffer*> mBufferNameMap; std::unordered_map<ESM::RefId, Sound_Buffer*> mBufferNameMap;
@ -102,6 +111,7 @@ namespace MWSound
std::deque<Sound_Buffer*> mUnusedBuffers; std::deque<Sound_Buffer*> mUnusedBuffers;
inline Sound_Buffer* insertSound(const ESM::RefId& soundId, const ESM::Sound& sound); inline Sound_Buffer* insertSound(const ESM::RefId& soundId, const ESM::Sound& sound);
inline Sound_Buffer* insertSound(std::string_view fileName);
inline void unloadUnused(); inline void unloadUnused();
}; };

View File

@ -11,6 +11,7 @@
#include <components/misc/resourcehelpers.hpp> #include <components/misc/resourcehelpers.hpp>
#include <components/misc/rng.hpp> #include <components/misc/rng.hpp>
#include <components/vfs/manager.hpp> #include <components/vfs/manager.hpp>
#include <components/vfs/pathutil.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/statemanager.hpp" #include "../mwbase/statemanager.hpp"
@ -36,6 +37,7 @@ namespace MWSound
constexpr float sMinUpdateInterval = 1.0f / 30.0f; constexpr float sMinUpdateInterval = 1.0f / 30.0f;
constexpr float sSfxFadeInDuration = 1.0f; constexpr float sSfxFadeInDuration = 1.0f;
constexpr float sSfxFadeOutDuration = 1.0f; constexpr float sSfxFadeOutDuration = 1.0f;
constexpr float sSoundCullDistance = 2000.f;
WaterSoundUpdaterSettings makeWaterSoundUpdaterSettings() WaterSoundUpdaterSettings makeWaterSoundUpdaterSettings()
{ {
@ -357,7 +359,7 @@ namespace MWSound
if (!mOutput->isInitialized()) if (!mOutput->isInitialized())
return; return;
DecoderPtr decoder = loadVoice("Sound/" + filename); DecoderPtr decoder = loadVoice(filename);
if (!decoder) if (!decoder)
return; return;
@ -389,7 +391,7 @@ namespace MWSound
if (!mOutput->isInitialized()) if (!mOutput->isInitialized())
return; return;
DecoderPtr decoder = loadVoice("Sound/" + filename); DecoderPtr decoder = loadVoice(filename);
if (!decoder) if (!decoder)
return; return;
@ -486,14 +488,22 @@ namespace MWSound
return mOutput->getStreamDelay(stream); return mOutput->getStreamDelay(stream);
} }
Sound* SoundManager::playSound( bool SoundManager::remove3DSoundAtDistance(PlayMode mode, const MWWorld::ConstPtr& ptr) const
const ESM::RefId& soundId, float volume, float pitch, Type type, PlayMode mode, float offset)
{ {
if (!mOutput->isInitialized()) if (!mOutput->isInitialized())
return nullptr; return true;
Sound_Buffer* sfx = mSoundBuffers.load(soundId); const osg::Vec3f objpos(ptr.getRefData().getPosition().asVec3());
if (!sfx) const float squaredDist = (mListenerPos - objpos).length2();
if ((mode & PlayMode::RemoveAtDistance) && squaredDist > sSoundCullDistance * sSoundCullDistance)
return true;
return false;
}
Sound* SoundManager::playSound(Sound_Buffer* sfx, float volume, float pitch, Type type, PlayMode mode, float offset)
{
if (!mOutput->isInitialized())
return nullptr; return nullptr;
// Only one copy of given sound can be played at time, so stop previous copy // Only one copy of given sound can be played at time, so stop previous copy
@ -517,25 +527,45 @@ namespace MWSound
return result; return result;
} }
Sound* SoundManager::playSound3D(const MWWorld::ConstPtr& ptr, const ESM::RefId& soundId, float volume, float pitch, Sound* SoundManager::playSound(
std::string_view fileName, float volume, float pitch, Type type, PlayMode mode, float offset)
{
if (!mOutput->isInitialized())
return nullptr;
std::string normalizedName = VFS::Path::normalizeFilename(fileName);
Sound_Buffer* sfx = mSoundBuffers.load(normalizedName);
if (!sfx)
return nullptr;
return playSound(sfx, volume, pitch, type, mode, offset);
}
Sound* SoundManager::playSound(
const ESM::RefId& soundId, float volume, float pitch, Type type, PlayMode mode, float offset)
{
if (!mOutput->isInitialized())
return nullptr;
Sound_Buffer* sfx = mSoundBuffers.load(soundId);
if (!sfx)
return nullptr;
return playSound(sfx, volume, pitch, type, mode, offset);
}
Sound* SoundManager::playSound3D(const MWWorld::ConstPtr& ptr, Sound_Buffer* sfx, float volume, float pitch,
Type type, PlayMode mode, float offset) Type type, PlayMode mode, float offset)
{ {
if (!mOutput->isInitialized()) if (!mOutput->isInitialized())
return nullptr; return nullptr;
const osg::Vec3f objpos(ptr.getRefData().getPosition().asVec3());
const float squaredDist = (mListenerPos - objpos).length2();
if ((mode & PlayMode::RemoveAtDistance) && squaredDist > 2000 * 2000)
return nullptr;
// Look up the sound in the ESM data
Sound_Buffer* sfx = mSoundBuffers.load(soundId);
if (!sfx)
return nullptr;
// Only one copy of given sound can be played at time on ptr, so stop previous copy // Only one copy of given sound can be played at time on ptr, so stop previous copy
stopSound(sfx, ptr); stopSound(sfx, ptr);
const osg::Vec3f objpos(ptr.getRefData().getPosition().asVec3());
const float squaredDist = (mListenerPos - objpos).length2();
bool played; bool played;
SoundPtr sound = getSoundRef(); SoundPtr sound = getSoundRef();
if (!(mode & PlayMode::NoPlayerLocal) && ptr == MWMechanics::getPlayer()) if (!(mode & PlayMode::NoPlayerLocal) && ptr == MWMechanics::getPlayer())
@ -578,6 +608,35 @@ namespace MWSound
return result; return result;
} }
Sound* SoundManager::playSound3D(const MWWorld::ConstPtr& ptr, const ESM::RefId& soundId, float volume, float pitch,
Type type, PlayMode mode, float offset)
{
if (remove3DSoundAtDistance(mode, ptr))
return nullptr;
// Look up the sound in the ESM data
Sound_Buffer* sfx = mSoundBuffers.load(soundId);
if (!sfx)
return nullptr;
return playSound3D(ptr, sfx, volume, pitch, type, mode, offset);
}
Sound* SoundManager::playSound3D(const MWWorld::ConstPtr& ptr, std::string_view fileName, float volume, float pitch,
Type type, PlayMode mode, float offset)
{
if (remove3DSoundAtDistance(mode, ptr))
return nullptr;
// Look up the sound
std::string normalizedName = VFS::Path::normalizeFilename(fileName);
Sound_Buffer* sfx = mSoundBuffers.load(normalizedName);
if (!sfx)
return nullptr;
return playSound3D(ptr, sfx, volume, pitch, type, mode, offset);
}
Sound* SoundManager::playSound3D(const osg::Vec3f& initialPos, const ESM::RefId& soundId, float volume, float pitch, Sound* SoundManager::playSound3D(const osg::Vec3f& initialPos, const ESM::RefId& soundId, float volume, float pitch,
Type type, PlayMode mode, float offset) Type type, PlayMode mode, float offset)
{ {
@ -644,6 +703,13 @@ namespace MWSound
stopSound(sfx, ptr); stopSound(sfx, ptr);
} }
void SoundManager::stopSound3D(const MWWorld::ConstPtr& ptr, std::string_view fileName)
{
std::string normalizedName = VFS::Path::normalizeFilename(fileName);
auto soundId = ESM::RefId::stringRefId(normalizedName);
stopSound3D(ptr, soundId);
}
void SoundManager::stopSound3D(const MWWorld::ConstPtr& ptr) void SoundManager::stopSound3D(const MWWorld::ConstPtr& ptr)
{ {
SoundMap::iterator snditer = mActiveSounds.find(ptr.mRef); SoundMap::iterator snditer = mActiveSounds.find(ptr.mRef);
@ -700,6 +766,13 @@ namespace MWSound
} }
} }
bool SoundManager::getSoundPlaying(const MWWorld::ConstPtr& ptr, std::string_view fileName) const
{
std::string normalizedName = VFS::Path::normalizeFilename(fileName);
auto soundId = ESM::RefId::stringRefId(normalizedName);
return getSoundPlaying(ptr, soundId);
}
bool SoundManager::getSoundPlaying(const MWWorld::ConstPtr& ptr, const ESM::RefId& soundId) const bool SoundManager::getSoundPlaying(const MWWorld::ConstPtr& ptr, const ESM::RefId& soundId) const
{ {
SoundMap::const_iterator snditer = mActiveSounds.find(ptr.mRef); SoundMap::const_iterator snditer = mActiveSounds.find(ptr.mRef);
@ -849,8 +922,8 @@ namespace MWSound
void SoundManager::cull3DSound(SoundBase* sound) void SoundManager::cull3DSound(SoundBase* sound)
{ {
// Hard-coded distance of 2000.0f is from vanilla Morrowind // Hard-coded distance is from an original engine
const float maxDist = sound->getDistanceCull() ? 2000.0f : sound->getMaxDistance(); const float maxDist = sound->getDistanceCull() ? sSoundCullDistance : sound->getMaxDistance();
const float squaredMaxDist = maxDist * maxDist; const float squaredMaxDist = maxDist * maxDist;
const osg::Vec3f pos = sound->getPosition(); const osg::Vec3f pos = sound->getPosition();

View File

@ -132,6 +132,13 @@ namespace MWSound
void cull3DSound(SoundBase* sound); void cull3DSound(SoundBase* sound);
bool remove3DSoundAtDistance(PlayMode mode, const MWWorld::ConstPtr& ptr) const;
Sound* playSound(Sound_Buffer* sfx, float volume, float pitch, Type type = Type::Sfx,
PlayMode mode = PlayMode::Normal, float offset = 0);
Sound* playSound3D(const MWWorld::ConstPtr& ptr, Sound_Buffer* sfx, float volume, float pitch, Type type,
PlayMode mode, float offset);
void updateSounds(float duration); void updateSounds(float duration);
void updateRegionSound(float duration); void updateRegionSound(float duration);
void updateWaterSound(); void updateWaterSound();
@ -183,11 +190,11 @@ namespace MWSound
void say(const MWWorld::ConstPtr& reference, const std::string& filename) override; void say(const MWWorld::ConstPtr& reference, const std::string& filename) override;
///< Make an actor say some text. ///< Make an actor say some text.
/// \param filename name of a sound file in "Sound/" in the data directory. /// \param filename name of a sound file in the VFS
void say(const std::string& filename) override; void say(const std::string& filename) override;
///< Say some text, without an actor ref ///< Say some text, without an actor ref
/// \param filename name of a sound file in "Sound/" in the data directory. /// \param filename name of a sound file in the VFS
bool sayActive(const MWWorld::ConstPtr& reference = MWWorld::ConstPtr()) const override; bool sayActive(const MWWorld::ConstPtr& reference = MWWorld::ConstPtr()) const override;
///< Is actor not speaking? ///< Is actor not speaking?
@ -219,12 +226,23 @@ namespace MWSound
///< Play a sound, independently of 3D-position ///< Play a sound, independently of 3D-position
///< @param offset Number of seconds into the sound to start playback. ///< @param offset Number of seconds into the sound to start playback.
Sound* playSound(std::string_view fileName, float volume, float pitch, Type type = Type::Sfx,
PlayMode mode = PlayMode::Normal, float offset = 0) override;
///< Play a sound, independently of 3D-position
///< @param offset Number of seconds into the sound to start playback.
Sound* playSound3D(const MWWorld::ConstPtr& reference, const ESM::RefId& soundId, float volume, float pitch, Sound* playSound3D(const MWWorld::ConstPtr& reference, const ESM::RefId& soundId, float volume, float pitch,
Type type = Type::Sfx, PlayMode mode = PlayMode::Normal, float offset = 0) override; Type type = Type::Sfx, PlayMode mode = PlayMode::Normal, float offset = 0) override;
///< Play a 3D sound attached to an MWWorld::Ptr. Will be updated automatically with the Ptr's position, unless ///< Play a 3D sound attached to an MWWorld::Ptr. Will be updated automatically with the Ptr's position, unless
///< Play_NoTrack is specified. ///< Play_NoTrack is specified.
///< @param offset Number of seconds into the sound to start playback. ///< @param offset Number of seconds into the sound to start playback.
Sound* playSound3D(const MWWorld::ConstPtr& reference, std::string_view fileName, float volume, float pitch,
Type type = Type::Sfx, PlayMode mode = PlayMode::Normal, float offset = 0) override;
///< Play a 3D sound attached to an MWWorld::Ptr. Will be updated automatically with the Ptr's position, unless
///< Play_NoTrack is specified.
///< @param offset Number of seconds into the sound to start playback.
Sound* playSound3D(const osg::Vec3f& initialPos, const ESM::RefId& soundId, float volume, float pitch, Sound* playSound3D(const osg::Vec3f& initialPos, const ESM::RefId& soundId, float volume, float pitch,
Type type, PlayMode mode, float offset = 0) override; Type type, PlayMode mode, float offset = 0) override;
///< Play a 3D sound at \a initialPos. If the sound should be moving, it must be updated using ///< Play a 3D sound at \a initialPos. If the sound should be moving, it must be updated using
@ -236,7 +254,10 @@ namespace MWSound
/// @note no-op if \a sound is null /// @note no-op if \a sound is null
void stopSound3D(const MWWorld::ConstPtr& reference, const ESM::RefId& soundId) override; void stopSound3D(const MWWorld::ConstPtr& reference, const ESM::RefId& soundId) override;
///< Stop the given object from playing the given sound, ///< Stop the given object from playing the given sound.
void stopSound3D(const MWWorld::ConstPtr& reference, std::string_view fileName) override;
///< Stop the given object from playing the given sound.
void stopSound3D(const MWWorld::ConstPtr& reference) override; void stopSound3D(const MWWorld::ConstPtr& reference) override;
///< Stop the given object from playing all sounds. ///< Stop the given object from playing all sounds.
@ -253,6 +274,9 @@ namespace MWSound
bool getSoundPlaying(const MWWorld::ConstPtr& reference, const ESM::RefId& soundId) const override; bool getSoundPlaying(const MWWorld::ConstPtr& reference, const ESM::RefId& soundId) const override;
///< Is the given sound currently playing on the given object? ///< Is the given sound currently playing on the given object?
bool getSoundPlaying(const MWWorld::ConstPtr& reference, std::string_view fileName) const override;
///< Is the given sound currently playing on the given object?
void pauseSounds(MWSound::BlockerType blocker, int types = int(Type::Mask)) override; void pauseSounds(MWSound::BlockerType blocker, int types = int(Type::Mask)) override;
///< Pauses all currently playing sounds, including music. ///< Pauses all currently playing sounds, including music.

View File

@ -151,6 +151,11 @@ std::string Misc::ResourceHelpers::correctMeshPath(const std::string& resPath, c
return "meshes\\" + resPath; return "meshes\\" + resPath;
} }
std::string Misc::ResourceHelpers::correctSoundPath(const std::string& resPath)
{
return "sound\\" + 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

@ -35,6 +35,9 @@ namespace Misc
// Adds "meshes\\". // Adds "meshes\\".
std::string correctMeshPath(const std::string& resPath, const VFS::Manager* vfs); std::string correctMeshPath(const std::string& resPath, const VFS::Manager* vfs);
// Adds "sound\\".
std::string correctSoundPath(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

@ -19,6 +19,7 @@ Lua API reference
openmw_self openmw_self
openmw_nearby openmw_nearby
openmw_input openmw_input
openmw_ambient
openmw_ui openmw_ui
openmw_camera openmw_camera
openmw_postprocessing openmw_postprocessing

View File

@ -0,0 +1,5 @@
Package openmw.ambient
======================
.. raw:: html
:file: generated_html/openmw_ambient.html

View File

@ -21,6 +21,8 @@
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.nearby <Package openmw.nearby>` | by local scripts | | Read-only access to the nearest area of the game world. | |:ref:`openmw.nearby <Package openmw.nearby>` | by local scripts | | Read-only access to the nearest area of the game world. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.ambient <Package openmw.ambient>` | by player scripts | | Controls background sounds for given player. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.input <Package openmw.input>` | by player scripts | | User input. | |:ref:`openmw.input <Package openmw.input>` | by player scripts | | User input. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|: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>`. |

View File

@ -9,6 +9,7 @@ set(LUA_API_FILES
math.doclua math.doclua
string.doclua string.doclua
table.doclua table.doclua
openmw/ambient.lua
openmw/async.lua openmw/async.lua
openmw/core.lua openmw/core.lua
openmw/nearby.lua openmw/nearby.lua

View File

@ -0,0 +1,75 @@
---
-- `openmw.ambient` controls background sounds, specific to given player (2D-sounds).
-- Can be used only by local scripts, that are attached to a player.
-- @module ambient
-- @usage local ambient = require('openmw.ambient')
---
-- Play a 2D sound
-- @function [parent=#ambient] playSound
-- @param #string soundId ID of Sound record to play
-- @param #table options An optional table with additional optional arguments. Can contain:
--
-- * `timeOffset` - a floating point number >= 0, to some time (in second) from beginning of sound file (default: 0);
-- * `volume` - a floating point number >= 0, to set a sound volume (default: 1);
-- * `pitch` - a floating point number >= 0, to set a sound pitch (default: 1);
-- * `scale` - a boolean, to set if sound pitch should be scaled by simulation time scaling (default: true);
-- * `loop` - a boolean, to set if sound should be repeated when it ends (default: false);
-- @usage local params = {
-- timeOffset=0.1
-- volume=0.3,
-- scale=false,
-- pitch=1.0,
-- loop=true
-- };
-- ambient.playSound("shock bolt", params)
---
-- Play a 2D sound file
-- @function [parent=#ambient] playSoundFile
-- @param #string fileName Path to sound file in VFS
-- @param #table options An optional table with additional optional arguments. Can contain:
--
-- * `timeOffset` - a floating point number >= 0, to some time (in second) from beginning of sound file (default: 0);
-- * `volume` - a floating point number >= 0, to set a sound volume (default: 1);
-- * `pitch` - a floating point number >= 0, to set a sound pitch (default: 1);
-- * `scale` - a boolean, to set if sound pitch should be scaled by simulation time scaling (default: true);
-- * `loop` - a boolean, to set if sound should be repeated when it ends (default: false);
-- @usage local params = {
-- timeOffset=0.1
-- volume=0.3,
-- scale=false,
-- pitch=1.0,
-- loop=true
-- };
-- ambient.playSoundFile("Sound\\test.mp3", params)
---
-- Stop a sound
-- @function [parent=#ambient] stopSound
-- @param #string soundId ID of Sound record to stop
-- @usage ambient.stopSound("shock bolt");
---
-- Stop a sound file
-- @function [parent=#ambient] stopSoundFile
-- @param #string fileName Path to sound file in VFS
-- @usage ambient.stopSoundFile("Sound\\test.mp3");
---
-- Check if sound is playing
-- @function [parent=#ambient] isSoundPlaying
-- @param #string soundId ID of Sound record to check
-- @return #boolean
-- @usage local isPlaying = ambient.isSoundPlaying("shock bolt");
---
-- Check if sound file is playing
-- @function [parent=#ambient] isSoundFilePlaying
-- @param #string fileName Path to sound file in VFS
-- @return #boolean
-- @usage local isPlaying = ambient.isSoundFilePlaying("Sound\\test.mp3");
return nil

View File

@ -737,4 +737,110 @@
-- @field #number magnitudeBase -- @field #number magnitudeBase
-- @field #number magnitudeModifier -- @field #number magnitudeModifier
---
-- Play a 3D sound, attached to object
-- @function [parent=#core] playSound3d
-- @param #string soundId ID of Sound record to play
-- @param #GameObject object Object to which we attach the sound
-- @param #table options An optional table with additional optional arguments. Can contain:
--
-- * `timeOffset` - a floating point number >= 0, to some time (in second) from beginning of sound file (default: 0);
-- * `volume` - a floating point number >= 0, to set a sound volume (default: 1);
-- * `pitch` - a floating point number >= 0, to set a sound pitch (default: 1);
-- * `loop` - a boolean, to set if sound should be repeated when it ends (default: false);
-- @usage local params = {
-- timeOffset=0.1
-- volume=0.3,
-- loop=false,
-- pitch=1.0
-- };
-- core.sound.playSound3d("shock bolt", object, params)
---
-- Play a 3D sound file, attached to object
-- @function [parent=#core] playSoundFile3d
-- @param #string fileName Path to sound file in VFS
-- @param #GameObject object Object to which we attach the sound
-- @param #table options An optional table with additional optional arguments. Can contain:
--
-- * `timeOffset` - a floating point number >= 0, to some time (in second) from beginning of sound file (default: 0);
-- * `volume` - a floating point number >= 0, to set a sound volume (default: 1);
-- * `pitch` - a floating point number >= 0, to set a sound pitch (default: 1);
-- * `loop` - a boolean, to set if sound should be repeated when it ends (default: false);
-- @usage local params = {
-- timeOffset=0.1
-- volume=0.3,
-- loop=false,
-- pitch=1.0
-- };
-- core.sound.playSoundFile3d("Sound\\test.mp3", object, params)
---
-- Stop a 3D sound, attached to object
-- @function [parent=#core] stopSound3d
-- @param #string soundId ID of Sound record to stop
-- @param #GameObject object Object on which we want to stop sound
-- @usage core.sound.stopSound("shock bolt", object);
---
-- Stop a 3D sound file, attached to object
-- @function [parent=#core] stopSoundFile3d
-- @param #string fileName Path to sound file in VFS
-- @param #GameObject object Object on which we want to stop sound
-- @usage core.sound.stopSoundFile("Sound\\test.mp3", object);
---
-- Check if sound is playing on given object
-- @function [parent=#core] isSoundPlaying
-- @param #string soundId ID of Sound record to check
-- @param #GameObject object Object on which we want to check sound
-- @return #boolean
-- @usage local isPlaying = core.sound.isSoundPlaying("shock bolt", object);
---
-- Check if sound file is playing on given object
-- @function [parent=#core] isSoundFilePlaying
-- @param #string fileName Path to sound file in VFS
-- @param #GameObject object Object on which we want to check sound
-- @return #boolean
-- @usage local isPlaying = core.sound.isSoundFilePlaying("Sound\\test.mp3", object);
---
-- Play an animated voiceover. Has two overloads:
--
-- * With an "object" argument: play sound for given object, with speaking animation if possible equipment slots.
-- * Without an "object" argument: play sound globally, without object
-- @function [parent=#core] say
-- @param #string fileName Path to sound file in VFS
-- @param #GameObject object Object on which we want to play an animated voiceover (optional)
-- @param #string text Subtitle text (optional)
-- @usage -- play voiceover for object and print messagebox
-- core.sound.say("Sound\\Vo\\Misc\\voice.mp3", object, "Subtitle text")
-- @usage -- play voiceover globally and print messagebox
-- core.sound.say("Sound\\Vo\\Misc\\voice.mp3", "Subtitle text")
-- @usage -- play voiceover for object without messagebox
-- core.sound.say("Sound\\Vo\\Misc\\voice.mp3", object)
-- @usage -- play voiceover globally without messagebox
-- core.sound.say("Sound\\Vo\\Misc\\voice.mp3")
---
-- Stop animated voiceover
-- @function [parent=#core] stopSay
-- @param #string fileName Path to sound file in VFS
-- @param #GameObject object Object on which we want to stop an animated voiceover (optional)
-- @usage -- stop voice for given object
-- core.sound.stopSay(object);
-- @usage -- stop global voice
-- core.sound.stopSay();
---
-- Check if animated voiceover is playing
-- @function [parent=#core] isSayActive
-- @param #GameObject object Object on which we want to check an animated voiceover (optional)
-- @return #boolean
-- @usage -- check voice for given object
-- local isActive = isSayActive(object);
-- @usage -- check global voice
-- local isActive = isSayActive();
return nil return nil

View File

@ -127,10 +127,10 @@
--- ---
-- Get equipment. -- Get equipment.
-- Has two overloads: -- Has two overloads:
-- 1) With a single argument: returns a table `slot` -> @{openmw.core#GameObject} of currently equipped items. --
-- See @{#EQUIPMENT_SLOT}. Returns empty table if the actor doesn't have -- * With a single argument: returns a table `slot` -> @{openmw.core#GameObject} of currently equipped items.
-- equipment slots. -- See @{#EQUIPMENT_SLOT}. Returns empty table if the actor doesn't have equipment slots.
-- 2) With two arguments: returns an item equipped to the given slot. -- * With two arguments: returns an item equipped to the given slot.
-- @function [parent=#Actor] getEquipment -- @function [parent=#Actor] getEquipment
-- @param openmw.core#GameObject actor -- @param openmw.core#GameObject actor
-- @param #number slot Optional number of the equipment slot -- @param #number slot Optional number of the equipment slot