mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-04-28 12:58:00 +03:00
Merge branch 'esm4sound' into 'master'
Some checks failed
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
Some checks failed
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled
Initial support of ESM4 sounds See merge request OpenMW/openmw!3784
This commit is contained in:
commit
7d979735a5
11 changed files with 96 additions and 28 deletions
|
@ -26,6 +26,15 @@ namespace
|
|||
std::unique_ptr<VFS::Manager> mVFS = TestingOpenMW::createTestVFS({});
|
||||
constexpr VFS::Path::NormalizedView path("sound/foo.wav");
|
||||
EXPECT_EQ(correctSoundPath(path, *mVFS), "sound/foo.mp3");
|
||||
|
||||
auto correctESM4SoundPath = [](auto path, auto* vfs) {
|
||||
return Misc::ResourceHelpers::correctResourcePath({ { "sound" } }, path, vfs, ".mp3");
|
||||
};
|
||||
|
||||
EXPECT_EQ(correctESM4SoundPath("foo.WAV", mVFS.get()), "sound\\foo.mp3");
|
||||
EXPECT_EQ(correctESM4SoundPath("SOUND/foo.WAV", mVFS.get()), "sound\\foo.mp3");
|
||||
EXPECT_EQ(correctESM4SoundPath("DATA\\SOUND\\foo.WAV", mVFS.get()), "sound\\foo.mp3");
|
||||
EXPECT_EQ(correctESM4SoundPath("\\Data/Sound\\foo.WAV", mVFS.get()), "sound\\foo.mp3");
|
||||
}
|
||||
|
||||
namespace
|
||||
|
|
|
@ -149,5 +149,9 @@ namespace MWLua
|
|||
addModelProperty(record);
|
||||
record["isAutomatic"] = sol::readonly_property(
|
||||
[](const ESM4::Door& rec) -> bool { return rec.mDoorFlags & ESM4::Door::Flag_AutomaticDoor; });
|
||||
record["openSound"] = sol::readonly_property(
|
||||
[](const ESM4::Door& rec) -> std::string { return ESM::RefId(rec.mOpenSound).serializeText(); });
|
||||
record["closeSound"] = sol::readonly_property(
|
||||
[](const ESM4::Door& rec) -> std::string { return ESM::RefId(rec.mCloseSound).serializeText(); });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,10 @@
|
|||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/esm3/loadsoun.hpp>
|
||||
#include <components/esm4/loadsndr.hpp>
|
||||
#include <components/esm4/loadsoun.hpp>
|
||||
#include <components/misc/resourcehelpers.hpp>
|
||||
#include <components/resource/resourcesystem.hpp>
|
||||
#include <components/settings/values.hpp>
|
||||
#include <components/vfs/pathutil.hpp>
|
||||
|
||||
|
@ -99,7 +102,12 @@ namespace MWSound
|
|||
{
|
||||
if (mBufferNameMap.empty())
|
||||
{
|
||||
for (const ESM::Sound& sound : MWBase::Environment::get().getESMStore()->get<ESM::Sound>())
|
||||
const MWWorld::ESMStore* esmstore = MWBase::Environment::get().getESMStore();
|
||||
for (const ESM::Sound& sound : esmstore->get<ESM::Sound>())
|
||||
insertSound(sound.mId, sound);
|
||||
for (const ESM4::Sound& sound : esmstore->get<ESM4::Sound>())
|
||||
insertSound(sound.mId, sound);
|
||||
for (const ESM4::SoundReference& sound : esmstore->get<ESM4::SoundReference>())
|
||||
insertSound(sound.mId, sound);
|
||||
}
|
||||
|
||||
|
@ -190,6 +198,28 @@ namespace MWSound
|
|||
return &sfx;
|
||||
}
|
||||
|
||||
Sound_Buffer* SoundBufferPool::insertSound(const ESM::RefId& soundId, const ESM4::Sound& sound)
|
||||
{
|
||||
std::string path = Misc::ResourceHelpers::correctResourcePath(
|
||||
{ { "sound" } }, sound.mSoundFile, MWBase::Environment::get().getResourceSystem()->getVFS(), ".mp3");
|
||||
float volume = 1, min = 1, max = 255; // TODO: needs research
|
||||
Sound_Buffer& sfx = mSoundBuffers.emplace_back(VFS::Path::Normalized(std::move(path)), volume, min, max);
|
||||
mBufferNameMap.emplace(soundId, &sfx);
|
||||
return &sfx;
|
||||
}
|
||||
|
||||
Sound_Buffer* SoundBufferPool::insertSound(const ESM::RefId& soundId, const ESM4::SoundReference& sound)
|
||||
{
|
||||
std::string path = Misc::ResourceHelpers::correctResourcePath(
|
||||
{ { "sound" } }, sound.mSoundFile, MWBase::Environment::get().getResourceSystem()->getVFS(), ".mp3");
|
||||
float volume = 1, min = 1, max = 255; // TODO: needs research
|
||||
// TODO: sound.mSoundId can link to another SoundReference, probably we will need to add additional lookups to
|
||||
// ESMStore.
|
||||
Sound_Buffer& sfx = mSoundBuffers.emplace_back(VFS::Path::Normalized(std::move(path)), volume, min, max);
|
||||
mBufferNameMap.emplace(soundId, &sfx);
|
||||
return &sfx;
|
||||
}
|
||||
|
||||
void SoundBufferPool::unloadUnused()
|
||||
{
|
||||
while (!mUnusedBuffers.empty() && mBufferCacheSize > mBufferCacheMin)
|
||||
|
|
|
@ -14,6 +14,12 @@ namespace ESM
|
|||
struct Sound;
|
||||
}
|
||||
|
||||
namespace ESM4
|
||||
{
|
||||
struct Sound;
|
||||
struct SoundReference;
|
||||
}
|
||||
|
||||
namespace VFS
|
||||
{
|
||||
class Manager;
|
||||
|
@ -111,10 +117,12 @@ namespace MWSound
|
|||
// NOTE: unused buffers are stored in front-newest order.
|
||||
std::deque<Sound_Buffer*> mUnusedBuffers;
|
||||
|
||||
inline Sound_Buffer* insertSound(const ESM::RefId& soundId, const ESM::Sound& sound);
|
||||
inline Sound_Buffer* insertSound(std::string_view fileName);
|
||||
Sound_Buffer* insertSound(const ESM::RefId& soundId, const ESM::Sound& sound);
|
||||
Sound_Buffer* insertSound(const ESM::RefId& soundId, const ESM4::Sound& sound);
|
||||
Sound_Buffer* insertSound(const ESM::RefId& soundId, const ESM4::SoundReference& sound);
|
||||
Sound_Buffer* insertSound(std::string_view fileName);
|
||||
|
||||
inline void unloadUnused();
|
||||
void unloadUnused();
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -105,6 +105,8 @@ namespace ESM4
|
|||
struct Potion;
|
||||
struct Race;
|
||||
struct Reference;
|
||||
struct Sound;
|
||||
struct SoundReference;
|
||||
struct Static;
|
||||
struct StaticCollection;
|
||||
struct Terminal;
|
||||
|
@ -146,8 +148,8 @@ namespace MWWorld
|
|||
Store<ESM4::Land>, Store<ESM4::LandTexture>, Store<ESM4::LevelledCreature>, Store<ESM4::LevelledItem>,
|
||||
Store<ESM4::LevelledNpc>, Store<ESM4::Light>, Store<ESM4::MiscItem>, Store<ESM4::MovableStatic>,
|
||||
Store<ESM4::Npc>, Store<ESM4::Outfit>, Store<ESM4::Potion>, Store<ESM4::Race>, Store<ESM4::Reference>,
|
||||
Store<ESM4::Static>, Store<ESM4::StaticCollection>, Store<ESM4::Terminal>, Store<ESM4::Tree>,
|
||||
Store<ESM4::Weapon>, Store<ESM4::World>>;
|
||||
Store<ESM4::Sound>, Store<ESM4::SoundReference>, Store<ESM4::Static>, Store<ESM4::StaticCollection>,
|
||||
Store<ESM4::Terminal>, Store<ESM4::Tree>, Store<ESM4::Weapon>, Store<ESM4::World>>;
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
|
|
|
@ -1349,6 +1349,8 @@ template class MWWorld::TypedDynamicStore<ESM4::Npc>;
|
|||
template class MWWorld::TypedDynamicStore<ESM4::Outfit>;
|
||||
template class MWWorld::TypedDynamicStore<ESM4::Potion>;
|
||||
template class MWWorld::TypedDynamicStore<ESM4::Race>;
|
||||
template class MWWorld::TypedDynamicStore<ESM4::Sound>;
|
||||
template class MWWorld::TypedDynamicStore<ESM4::SoundReference>;
|
||||
template class MWWorld::TypedDynamicStore<ESM4::Static>;
|
||||
template class MWWorld::TypedDynamicStore<ESM4::StaticCollection>;
|
||||
template class MWWorld::TypedDynamicStore<ESM4::Terminal>;
|
||||
|
|
|
@ -74,6 +74,8 @@
|
|||
#include <components/esm4/loadrace.hpp>
|
||||
#include <components/esm4/loadrefr.hpp>
|
||||
#include <components/esm4/loadscol.hpp>
|
||||
#include <components/esm4/loadsndr.hpp>
|
||||
#include <components/esm4/loadsoun.hpp>
|
||||
#include <components/esm4/loadstat.hpp>
|
||||
#include <components/esm4/loadterm.hpp>
|
||||
#include <components/esm4/loadtree.hpp>
|
||||
|
|
|
@ -33,14 +33,10 @@ bool Misc::ResourceHelpers::changeExtensionToDds(std::string& path)
|
|||
return changeExtension(path, ".dds");
|
||||
}
|
||||
|
||||
std::string Misc::ResourceHelpers::correctResourcePath(
|
||||
std::span<const std::string_view> topLevelDirectories, std::string_view resPath, const VFS::Manager* vfs)
|
||||
// If `ext` is not empty we first search file with extension `ext`, then if not found fallback to original extension.
|
||||
std::string Misc::ResourceHelpers::correctResourcePath(std::span<const std::string_view> topLevelDirectories,
|
||||
std::string_view resPath, const VFS::Manager* vfs, std::string_view ext)
|
||||
{
|
||||
/* Bethesda at some point converted all their BSA
|
||||
* textures from tga to dds for increased load speed, but all
|
||||
* texture file name references were kept as .tga.
|
||||
*/
|
||||
|
||||
std::string correctedPath = Misc::StringUtils::lowerCase(resPath);
|
||||
|
||||
// Flatten slashes
|
||||
|
@ -80,14 +76,14 @@ std::string Misc::ResourceHelpers::correctResourcePath(
|
|||
|
||||
std::string origExt = correctedPath;
|
||||
|
||||
// since we know all (GOTY edition or less) textures end
|
||||
// in .dds, we change the extension
|
||||
bool changedToDds = changeExtensionToDds(correctedPath);
|
||||
// replace extension if `ext` is specified (used for .tga -> .dds, .wav -> .mp3)
|
||||
bool isExtChanged = !ext.empty() && changeExtension(correctedPath, ext);
|
||||
|
||||
if (vfs->exists(correctedPath))
|
||||
return correctedPath;
|
||||
// if it turns out that the above wasn't true in all cases (not for vanilla, but maybe mods)
|
||||
// verify, and revert if false (this call succeeds quickly, but fails slowly)
|
||||
if (changedToDds && vfs->exists(origExt))
|
||||
|
||||
// fall back to original extension
|
||||
if (isExtChanged && vfs->exists(origExt))
|
||||
return origExt;
|
||||
|
||||
// fall back to a resource in the top level directory if it exists
|
||||
|
@ -98,7 +94,7 @@ std::string Misc::ResourceHelpers::correctResourcePath(
|
|||
if (vfs->exists(fallback))
|
||||
return fallback;
|
||||
|
||||
if (changedToDds)
|
||||
if (isExtChanged)
|
||||
{
|
||||
fallback = topLevelDirectories.front();
|
||||
fallback += '\\';
|
||||
|
@ -110,19 +106,23 @@ std::string Misc::ResourceHelpers::correctResourcePath(
|
|||
return correctedPath;
|
||||
}
|
||||
|
||||
// Note: Bethesda at some point converted all their BSA textures from tga to dds for increased load speed,
|
||||
// but all texture file name references were kept as .tga. So we pass ext=".dds" to all helpers
|
||||
// looking for textures.
|
||||
|
||||
std::string Misc::ResourceHelpers::correctTexturePath(std::string_view resPath, const VFS::Manager* vfs)
|
||||
{
|
||||
return correctResourcePath({ { "textures", "bookart" } }, resPath, vfs);
|
||||
return correctResourcePath({ { "textures", "bookart" } }, resPath, vfs, ".dds");
|
||||
}
|
||||
|
||||
std::string Misc::ResourceHelpers::correctIconPath(std::string_view resPath, const VFS::Manager* vfs)
|
||||
{
|
||||
return correctResourcePath({ { "icons" } }, resPath, vfs);
|
||||
return correctResourcePath({ { "icons" } }, resPath, vfs, ".dds");
|
||||
}
|
||||
|
||||
std::string Misc::ResourceHelpers::correctBookartPath(std::string_view resPath, const VFS::Manager* vfs)
|
||||
{
|
||||
return correctResourcePath({ { "bookart", "textures" } }, resPath, vfs);
|
||||
return correctResourcePath({ { "bookart", "textures" } }, resPath, vfs, ".dds");
|
||||
}
|
||||
|
||||
std::string Misc::ResourceHelpers::correctBookartPath(
|
||||
|
@ -199,6 +199,12 @@ std::string_view Misc::ResourceHelpers::meshPathForESM3(std::string_view resPath
|
|||
VFS::Path::Normalized Misc::ResourceHelpers::correctSoundPath(
|
||||
VFS::Path::NormalizedView resPath, const VFS::Manager& vfs)
|
||||
{
|
||||
// Note: likely should be replaced with
|
||||
// return correctResourcePath({ { "sound" } }, resPath, vfs, ".mp3");
|
||||
// but there is a slight difference in behaviour:
|
||||
// - `correctResourcePath(..., ".mp3")` first checks `.mp3`, then tries the original extension
|
||||
// - the implementation below first tries the original extension, then falls back to `.mp3`.
|
||||
|
||||
// Workaround: Bethesda at some point converted some of the files to mp3, but the references were kept as .wav.
|
||||
if (!vfs.exists(resPath))
|
||||
{
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
#ifndef MISC_RESOURCEHELPERS_H
|
||||
#define MISC_RESOURCEHELPERS_H
|
||||
|
||||
#include <components/vfs/pathutil.hpp>
|
||||
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <components/vfs/pathutil.hpp>
|
||||
|
||||
namespace VFS
|
||||
{
|
||||
class Manager;
|
||||
|
@ -25,8 +25,8 @@ namespace Misc
|
|||
namespace ResourceHelpers
|
||||
{
|
||||
bool changeExtensionToDds(std::string& path);
|
||||
std::string correctResourcePath(
|
||||
std::span<const std::string_view> topLevelDirectories, std::string_view resPath, const VFS::Manager* vfs);
|
||||
std::string correctResourcePath(std::span<const std::string_view> topLevelDirectories, std::string_view resPath,
|
||||
const VFS::Manager* vfs, std::string_view ext = "");
|
||||
std::string correctTexturePath(std::string_view resPath, const VFS::Manager* vfs);
|
||||
std::string correctIconPath(std::string_view resPath, const VFS::Manager* vfs);
|
||||
std::string correctBookartPath(std::string_view resPath, const VFS::Manager* vfs);
|
||||
|
@ -49,6 +49,7 @@ namespace Misc
|
|||
std::string_view meshPathForESM3(std::string_view resPath);
|
||||
|
||||
VFS::Path::Normalized correctSoundPath(VFS::Path::NormalizedView resPath, const VFS::Manager& vfs);
|
||||
std::string correctESM4SoundPath(std::string_view resPath, const VFS::Manager* vfs);
|
||||
|
||||
/// marker objects that have a hardcoded function in the game logic, should be hidden from the player
|
||||
bool isHiddenMarker(const ESM::RefId& id);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
local async = require('openmw.async')
|
||||
local core = require('openmw.core')
|
||||
local types = require('openmw.types')
|
||||
local world = require('openmw.world')
|
||||
|
||||
|
@ -6,8 +7,9 @@ local EnableObject = async:registerTimerCallback('EnableObject', function(obj) o
|
|||
|
||||
local function ESM4DoorActivation(door, actor)
|
||||
-- TODO: Implement lockpicking minigame
|
||||
-- TODO: Play door opening animation and sound
|
||||
-- TODO: Play door opening animation
|
||||
local Door4 = types.ESM4Door
|
||||
core.sound.playSound3d(Door4.record(door).openSound, actor)
|
||||
if Door4.isTeleport(door) then
|
||||
actor:teleport(Door4.destCell(door), Door4.destPosition(door), Door4.destRotation(door))
|
||||
else
|
||||
|
|
|
@ -2449,5 +2449,7 @@
|
|||
-- @field #string id Record id
|
||||
-- @field #string name Human-readable name
|
||||
-- @field #string model VFS path to the model
|
||||
-- @field #string openSound FormId of the door opening sound
|
||||
-- @field #string closeSound FormId of the door closing sound
|
||||
|
||||
return nil
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue