diff --git a/CMakeLists.txt b/CMakeLists.txt index c4abe65498..5263d849e8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,7 +81,7 @@ message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 49) set(OPENMW_VERSION_RELEASE 0) -set(OPENMW_LUA_API_REVISION 57) +set(OPENMW_LUA_API_REVISION 58) set(OPENMW_POSTPROCESSING_API_REVISION 1) set(OPENMW_VERSION_COMMITHASH "") diff --git a/apps/openmw/mwlua/soundbindings.cpp b/apps/openmw/mwlua/soundbindings.cpp index ad4a498153..2023f2b341 100644 --- a/apps/openmw/mwlua/soundbindings.cpp +++ b/apps/openmw/mwlua/soundbindings.cpp @@ -11,6 +11,7 @@ #include #include "luamanagerimp.hpp" +#include "objectvariant.hpp" namespace { @@ -28,6 +29,27 @@ namespace float mFade = 1.f; }; + MWWorld::Ptr getMutablePtrOrThrow(const MWLua::ObjectVariant& variant) + { + if (variant.isLObject()) + throw std::runtime_error("Local scripts can only modify object they are attached to."); + + MWWorld::Ptr ptr = variant.ptr(); + if (ptr.isEmpty()) + throw std::runtime_error("Invalid object"); + + return ptr; + } + + MWWorld::Ptr getPtrOrThrow(const MWLua::ObjectVariant& variant) + { + MWWorld::Ptr ptr = variant.ptr(); + if (ptr.isEmpty()) + throw std::runtime_error("Invalid object"); + + return ptr; + } + PlaySoundArgs getPlaySoundArgs(const sol::optional& options) { PlaySoundArgs args; @@ -121,6 +143,17 @@ namespace MWLua sndMgr->streamMusic(std::string(fileName), MWSound::MusicType::Scripted, args.mFade); }; + api["say"] + = [luaManager = context.mLuaManager](std::string_view fileName, sol::optional text) { + MWBase::Environment::get().getSoundManager()->say(VFS::Path::Normalized(fileName)); + if (text) + luaManager->addUIMessage(*text); + }; + + api["stopSay"] = []() { MWBase::Environment::get().getSoundManager()->stopSay(MWWorld::ConstPtr()); }; + api["isSayActive"] + = []() { return MWBase::Environment::get().getSoundManager()->sayActive(MWWorld::ConstPtr()); }; + api["isMusicPlaying"] = []() { return MWBase::Environment::get().getSoundManager()->isMusicPlaying(); }; api["stopMusic"] = []() { MWBase::Environment::get().getSoundManager()->stopMusic(); }; @@ -137,64 +170,61 @@ namespace MWLua api["isEnabled"] = []() { return MWBase::Environment::get().getSoundManager()->isEnabled(); }; api["playSound3d"] - = [](std::string_view soundId, const Object& object, const sol::optional& options) { + = [](std::string_view soundId, const sol::object& object, const sol::optional& options) { auto args = getPlaySoundArgs(options); auto playMode = getPlayMode(args, true); ESM::RefId sound = ESM::RefId::deserializeText(soundId); + MWWorld::Ptr ptr = getMutablePtrOrThrow(ObjectVariant(object)); MWBase::Environment::get().getSoundManager()->playSound3D( - object.ptr(), sound, args.mVolume, args.mPitch, MWSound::Type::Sfx, playMode, args.mTimeOffset); + ptr, sound, args.mVolume, args.mPitch, MWSound::Type::Sfx, playMode, args.mTimeOffset); }; api["playSoundFile3d"] - = [](std::string_view fileName, const Object& object, const sol::optional& options) { + = [](std::string_view fileName, const sol::object& object, const sol::optional& options) { auto args = getPlaySoundArgs(options); auto playMode = getPlayMode(args, true); + MWWorld::Ptr ptr = getMutablePtrOrThrow(ObjectVariant(object)); - MWBase::Environment::get().getSoundManager()->playSound3D(object.ptr(), fileName, args.mVolume, - args.mPitch, MWSound::Type::Sfx, playMode, args.mTimeOffset); + MWBase::Environment::get().getSoundManager()->playSound3D( + ptr, fileName, args.mVolume, args.mPitch, MWSound::Type::Sfx, playMode, args.mTimeOffset); }; - api["stopSound3d"] = [](std::string_view soundId, const Object& object) { + api["stopSound3d"] = [](std::string_view soundId, const sol::object& object) { ESM::RefId sound = ESM::RefId::deserializeText(soundId); - MWBase::Environment::get().getSoundManager()->stopSound3D(object.ptr(), sound); + MWWorld::Ptr ptr = getMutablePtrOrThrow(ObjectVariant(object)); + MWBase::Environment::get().getSoundManager()->stopSound3D(ptr, sound); }; - api["stopSoundFile3d"] = [](std::string_view fileName, const Object& object) { - MWBase::Environment::get().getSoundManager()->stopSound3D(object.ptr(), fileName); + api["stopSoundFile3d"] = [](std::string_view fileName, const sol::object& object) { + MWWorld::Ptr ptr = getMutablePtrOrThrow(ObjectVariant(object)); + MWBase::Environment::get().getSoundManager()->stopSound3D(ptr, fileName); }; - api["isSoundPlaying"] = [](std::string_view soundId, const Object& object) { + api["isSoundPlaying"] = [](std::string_view soundId, const sol::object& object) { ESM::RefId sound = ESM::RefId::deserializeText(soundId); - return MWBase::Environment::get().getSoundManager()->getSoundPlaying(object.ptr(), sound); + const MWWorld::Ptr& ptr = getPtrOrThrow(ObjectVariant(object)); + return MWBase::Environment::get().getSoundManager()->getSoundPlaying(ptr, sound); }; - api["isSoundFilePlaying"] = [](std::string_view fileName, const Object& object) { - return MWBase::Environment::get().getSoundManager()->getSoundPlaying(object.ptr(), fileName); + api["isSoundFilePlaying"] = [](std::string_view fileName, const sol::object& object) { + const MWWorld::Ptr& ptr = getPtrOrThrow(ObjectVariant(object)); + return MWBase::Environment::get().getSoundManager()->getSoundPlaying(ptr, fileName); }; - api["say"] = sol::overload( - [luaManager = context.mLuaManager]( - std::string_view fileName, const Object& object, sol::optional text) { - MWBase::Environment::get().getSoundManager()->say(object.ptr(), VFS::Path::Normalized(fileName)); - if (text) - luaManager->addUIMessage(*text); - }, - [luaManager = context.mLuaManager](std::string_view fileName, sol::optional text) { - MWBase::Environment::get().getSoundManager()->say(VFS::Path::Normalized(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()); }); + api["say"] = [luaManager = context.mLuaManager]( + std::string_view fileName, const sol::object& object, sol::optional text) { + MWWorld::Ptr ptr = getMutablePtrOrThrow(ObjectVariant(object)); + MWBase::Environment::get().getSoundManager()->say(ptr, VFS::Path::Normalized(fileName)); + if (text) + luaManager->addUIMessage(*text); + }; + api["stopSay"] = [](const sol::object& object) { + MWWorld::Ptr ptr = getMutablePtrOrThrow(ObjectVariant(object)); + MWBase::Environment::get().getSoundManager()->stopSay(ptr); + }; + api["isSayActive"] = [](const sol::object& object) { + const MWWorld::Ptr& ptr = getPtrOrThrow(ObjectVariant(object)); + return MWBase::Environment::get().getSoundManager()->sayActive(ptr); + }; using SoundStore = MWWorld::Store; sol::usertype soundStoreT = lua.new_usertype("ESM3_SoundStore"); diff --git a/files/lua_api/openmw/ambient.lua b/files/lua_api/openmw/ambient.lua index c10e50ff4a..ff776f84fb 100644 --- a/files/lua_api/openmw/ambient.lua +++ b/files/lua_api/openmw/ambient.lua @@ -95,4 +95,26 @@ -- @return #boolean -- @usage local isPlaying = ambient.isMusicPlaying(); +--- +-- Play an ambient voiceover. +-- @function [parent=#ambient] say +-- @param #string fileName Path to sound file in VFS +-- @param #string text Subtitle text (optional) +-- @usage -- play voiceover and print messagebox +-- ambient.say("Sound\\Vo\\Misc\\voice.mp3", "Subtitle text") +-- @usage -- play voiceover, without messagebox +-- ambient.say("Sound\\Vo\\Misc\\voice.mp3") + +--- +-- Stop an ambient voiceover +-- @function [parent=#ambient] stopSay +-- @param #string fileName Path to sound file in VFS +-- @usage ambient.stopSay(); + +--- +-- Check if an ambient voiceover is playing +-- @function [parent=#Sound] isSayActive +-- @return #boolean +-- @usage local isActive = isSayActive(); + return nil diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 66fb817362..46978ce903 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -714,6 +714,8 @@ --- -- Play a 3D sound, attached to object +-- +-- In local scripts can be used only on self. -- @function [parent=#Sound] playSound3d -- @param #string soundId ID of Sound record to play -- @param #GameObject object Object to which we attach the sound @@ -733,6 +735,8 @@ --- -- Play a 3D sound file, attached to object +-- +-- In local scripts can be used only on self. -- @function [parent=#Sound] playSoundFile3d -- @param #string fileName Path to sound file in VFS -- @param #GameObject object Object to which we attach the sound @@ -752,6 +756,8 @@ --- -- Stop a 3D sound, attached to object +-- +-- In local scripts can be used only on self. -- @function [parent=#Sound] stopSound3d -- @param #string soundId ID of Sound record to stop -- @param #GameObject object Object on which we want to stop sound @@ -759,6 +765,8 @@ --- -- Stop a 3D sound file, attached to object +-- +-- In local scripts can be used only on self. -- @function [parent=#Sound] stopSoundFile3d -- @param #string fileName Path to sound file in VFS -- @param #GameObject object Object on which we want to stop sound @@ -781,42 +789,32 @@ -- @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 --- * Without an "object" argument: play sound globally, without object +-- Play an animated voiceover. +-- In local scripts can be used only on self. -- @function [parent=#Sound] 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 #GameObject object Object on which we want to play an animated voiceover -- @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 +-- @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 +-- Stop an animated voiceover +-- +-- In local scripts can be used only on self. -- @function [parent=#Sound] 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(); +-- @param #GameObject object Object on which we want to stop an animated voiceover +-- @usage core.sound.stopSay(object); --- --- Check if animated voiceover is playing +-- Check if an animated voiceover is playing -- @function [parent=#Sound] isSayActive --- @param #GameObject object Object on which we want to check an animated voiceover (optional) +-- @param #GameObject object Object on which we want to check an animated voiceover -- @return #boolean --- @usage -- check voice for given object --- local isActive = isSayActive(object); --- @usage -- check global voice --- local isActive = isSayActive(); +-- @usage local isActive = isSayActive(object); --- -- @type SoundRecord