From d9c91ff3d93d85a11b4646a31dbc7a0d8d05ad8e Mon Sep 17 00:00:00 2001
From: Andrei Kortunov <andrei.kortunov@yandex.ru>
Date: Wed, 16 Aug 2023 20:23:31 +0400
Subject: [PATCH] Add bindings for ESM::Sound records

---
 apps/openmw/mwlua/soundbindings.cpp | 41 ++++++++++++++++++++++++++++-
 files/lua_api/openmw/core.lua       | 17 ++++++++++++
 2 files changed, 57 insertions(+), 1 deletion(-)

diff --git a/apps/openmw/mwlua/soundbindings.cpp b/apps/openmw/mwlua/soundbindings.cpp
index 7a89354fce..7f3bdc0ecf 100644
--- a/apps/openmw/mwlua/soundbindings.cpp
+++ b/apps/openmw/mwlua/soundbindings.cpp
@@ -5,6 +5,12 @@
 #include "../mwbase/soundmanager.hpp"
 #include "../mwbase/windowmanager.hpp"
 
+#include <components/esm3/loadsoun.hpp>
+#include <components/misc/resourcehelpers.hpp>
+#include <components/vfs/pathutil.hpp>
+
+#include "../mwworld/esmstore.hpp"
+
 #include "luamanagerimp.hpp"
 
 namespace MWLua
@@ -92,7 +98,8 @@ namespace MWLua
 
     sol::table initCoreSoundBindings(const Context& context)
     {
-        sol::table api(context.mLua->sol(), sol::create);
+        sol::state_view& lua = context.mLua->sol();
+        sol::table api(lua, sol::create);
 
         api["playSound3d"]
             = [](std::string_view soundId, const Object& object, const sol::optional<sol::table>& options) {
@@ -154,6 +161,38 @@ namespace MWLua
             },
             []() { return MWBase::Environment::get().getSoundManager()->sayActive(MWWorld::ConstPtr()); });
 
+        // Sound store
+        using SoundStore = MWWorld::Store<ESM::Sound>;
+        const SoundStore* soundStore = &MWBase::Environment::get().getWorld()->getStore().get<ESM::Sound>();
+        sol::usertype<SoundStore> soundStoreT = lua.new_usertype<SoundStore>("ESM3_SoundStore");
+        soundStoreT[sol::meta_function::to_string]
+            = [](const SoundStore& store) { return "ESM3_SoundStore{" + std::to_string(store.getSize()) + " sounds}"; };
+        soundStoreT[sol::meta_function::length] = [](const SoundStore& store) { return store.getSize(); };
+        soundStoreT[sol::meta_function::index] = sol::overload(
+            [](const SoundStore& store, size_t index) -> const ESM::Sound* { return store.at(index - 1); },
+            [](const SoundStore& store, std::string_view soundId) -> const ESM::Sound* {
+                return store.find(ESM::RefId::deserializeText(soundId));
+            });
+        soundStoreT[sol::meta_function::pairs] = lua["ipairsForArray"].template get<sol::function>();
+        soundStoreT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get<sol::function>();
+
+        api["sounds"] = soundStore;
+
+        // Sound record
+        auto soundT = lua.new_usertype<ESM::Sound>("ESM3_Sound");
+        soundT[sol::meta_function::to_string]
+            = [](const ESM::Sound& rec) -> std::string { return "ESM3_Sound[" + rec.mId.toDebugString() + "]"; };
+        soundT["id"] = sol::readonly_property([](const ESM::Sound& rec) { return rec.mId.serializeText(); });
+        soundT["volume"]
+            = sol::readonly_property([](const ESM::Sound& rec) -> unsigned char { return rec.mData.mVolume; });
+        soundT["minRange"]
+            = sol::readonly_property([](const ESM::Sound& rec) -> unsigned char { return rec.mData.mMinRange; });
+        soundT["maxRange"]
+            = sol::readonly_property([](const ESM::Sound& rec) -> unsigned char { return rec.mData.mMaxRange; });
+        soundT["fileName"] = sol::readonly_property([](const ESM::Sound& rec) -> std::string {
+            return VFS::Path::normalizeFilename(Misc::ResourceHelpers::correctSoundPath(rec.mSound));
+        });
+
         return LuaUtil::makeReadOnly(api);
     }
 }
diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua
index a19913f1f5..edb481ada9 100644
--- a/files/lua_api/openmw/core.lua
+++ b/files/lua_api/openmw/core.lua
@@ -843,4 +843,21 @@
 -- @usage -- check global voice
 -- local isActive = isSayActive();
 
+---
+-- @type Sound
+-- @field #string id Sound id
+-- @field #string fileName Normalized path to sound file in VFS
+-- @field #number volume Raw sound volume, from 0 to 255
+-- @field #number minRange Raw minimal range value, from 0 to 255
+-- @field #number maxRange Raw maximal range value, from 0 to 255
+
+--- List of all @{#Sound}s.
+-- @field [parent=#Sound] #list<#Sound> sounds
+-- @usage local sound = core.sound.sounds['Ashstorm']  -- get by id
+-- @usage local sound = core.sound.sounds[1]  -- get by index
+-- @usage -- Print all sound files paths
+-- for _, sound in pairs(core.sound.sounds) do
+--     print(sound.fileName)
+-- end
+
 return nil