1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-01-16 16:29:55 +00:00

Merge branch openmw:master into handtohand-tooltip

This commit is contained in:
trav 2024-10-05 17:24:17 +00:00
commit 3d2dd9201d
78 changed files with 867 additions and 578 deletions

View file

@ -83,9 +83,7 @@ jobs:
max-size: 1000M max-size: 1000M
- name: Configure - name: Configure
run: | run: CI/before_script.osx.sh
rm -fr build # remove the build directory
CI/before_script.osx.sh
- name: Build - name: Build
run: | run: |
cd build cd build

View file

@ -501,7 +501,6 @@ Ubuntu_GCC_integration_tests_asan:
paths: paths:
- ccache/ - ccache/
script: script:
- rm -fr build # remove the build directory
- CI/before_install.osx.sh - CI/before_install.osx.sh
- export CCACHE_BASEDIR="$(pwd)" - export CCACHE_BASEDIR="$(pwd)"
- export CCACHE_DIR="$(pwd)/ccache" - export CCACHE_DIR="$(pwd)/ccache"
@ -521,7 +520,6 @@ Ubuntu_GCC_integration_tests_asan:
artifacts: artifacts:
paths: paths:
- build/OpenMW-*.dmg - build/OpenMW-*.dmg
- "build/**/*.log"
macOS14_Xcode15_arm64: macOS14_Xcode15_arm64:
extends: .MacOS extends: .MacOS

View file

@ -191,6 +191,7 @@
Bug #8097: GetEffect doesn't detect 0 magnitude spells Bug #8097: GetEffect doesn't detect 0 magnitude spells
Bug #8124: Normal weapon resistance is applied twice for NPCs Bug #8124: Normal weapon resistance is applied twice for NPCs
Bug #8132: Actors without hello responses turn to face the player Bug #8132: Actors without hello responses turn to face the player
Bug #8171: Items with more than 100% health can be repaired
Feature #1415: Infinite fall failsafe Feature #1415: Infinite fall failsafe
Feature #2566: Handle NAM9 records for manual cell references Feature #2566: Handle NAM9 records for manual cell references
Feature #3501: OpenMW-CS: Instance Editing - Shortcuts for axial locking Feature #3501: OpenMW-CS: Instance Editing - Shortcuts for axial locking

View file

@ -4,14 +4,9 @@ export HOMEBREW_NO_EMOJI=1
export HOMEBREW_NO_INSTALL_CLEANUP=1 export HOMEBREW_NO_INSTALL_CLEANUP=1
export HOMEBREW_AUTOREMOVE=1 export HOMEBREW_AUTOREMOVE=1
# workaround for gitlab's pre-installed brew
# purge large and unnecessary packages that get in our way and have caused issues
brew uninstall ruby php openjdk node postgresql maven curl || true
brew tap --repair brew tap --repair
brew update --quiet brew update --quiet
# Some of these tools can come from places other than brew, so check before installing
brew install curl xquartz gd fontconfig freetype harfbuzz brotli brew install curl xquartz gd fontconfig freetype harfbuzz brotli
command -v ccache >/dev/null 2>&1 || brew install ccache command -v ccache >/dev/null 2>&1 || brew install ccache
@ -27,8 +22,9 @@ cmake --version
qmake --version qmake --version
if [[ "${MACOS_AMD64}" ]]; then if [[ "${MACOS_AMD64}" ]]; then
curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/macos/openmw-deps-20221113.zip -o ~/openmw-deps.zip curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/macos/openmw-deps-20240802.zip -o ~/openmw-deps.zip
else
curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/macos/openmw-deps-20240802_arm64.zip -o ~/openmw-deps.zip
fi
unzip -o ~/openmw-deps.zip -d /tmp > /dev/null unzip -o ~/openmw-deps.zip -d /tmp > /dev/null
else
curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/macos/openmw-deps-20240818-arm64.tar.xz -o ~/openmw-deps.tar.xz
tar xf ~/openmw-deps.tar.xz -C /tmp > /dev/null
fi

View file

@ -3,6 +3,7 @@
# Silence a git warning # Silence a git warning
git config --global advice.detachedHead false git config --global advice.detachedHead false
rm -fr build
mkdir build mkdir build
cd build cd build

View file

@ -33,7 +33,8 @@ namespace
{ {
const ObjectId id(&shape); const ObjectId id(&shape);
osg::ref_ptr<Resource::BulletShape> bulletShape(new Resource::BulletShape); osg::ref_ptr<Resource::BulletShape> bulletShape(new Resource::BulletShape);
bulletShape->mFileName = "test.nif"; constexpr VFS::Path::NormalizedView test("test.nif");
bulletShape->mFileName = test;
bulletShape->mFileHash = "test_hash"; bulletShape->mFileHash = "test_hash";
ObjectTransform objectTransform; ObjectTransform objectTransform;
std::fill(std::begin(objectTransform.mPosition.pos), std::end(objectTransform.mPosition.pos), 0.1f); std::fill(std::begin(objectTransform.mPosition.pos), std::end(objectTransform.mPosition.pos), 0.1f);

View file

@ -131,7 +131,8 @@ namespace NavMeshTool
osg::ref_ptr<const Resource::BulletShape> shape = [&] { osg::ref_ptr<const Resource::BulletShape> shape = [&] {
try try
{ {
return bulletShapeManager.getShape(Misc::ResourceHelpers::correctMeshPath(model)); return bulletShapeManager.getShape(
VFS::Path::toNormalized(Misc::ResourceHelpers::correctMeshPath(model)));
} }
catch (const std::exception& e) catch (const std::exception& e)
{ {

View file

@ -408,12 +408,12 @@ void CSMPrefs::State::declare()
declareShortcut(mValues->mKeyBindings.mScriptEditorUncomment, "Uncomment Selection"); declareShortcut(mValues->mKeyBindings.mScriptEditorUncomment, "Uncomment Selection");
declareCategory("Models"); declareCategory("Models");
declareString(mValues->mModels.mBaseanim, "base animations").setTooltip("3rd person base model with textkeys-data"); declareString(mValues->mModels.mBaseanim, "Base Animations").setTooltip("Third person base model and animations");
declareString(mValues->mModels.mBaseanimkna, "base animations, kna") declareString(mValues->mModels.mBaseanimkna, "Base Animations, Beast")
.setTooltip("3rd person beast race base model with textkeys-data"); .setTooltip("Third person beast race base model and animations");
declareString(mValues->mModels.mBaseanimfemale, "base animations, female") declareString(mValues->mModels.mBaseanimfemale, "Base Animations, Female")
.setTooltip("3rd person female base model with textkeys-data"); .setTooltip("Third person female base model and animations");
declareString(mValues->mModels.mWolfskin, "base animations, wolf").setTooltip("3rd person werewolf skin"); declareString(mValues->mModels.mWolfskin, "Base Animations, Werewolf").setTooltip("Third person werewolf skin");
} }
void CSMPrefs::State::declareCategory(const std::string& key) void CSMPrefs::State::declareCategory(const std::string& key)

View file

@ -1,6 +1,7 @@
#include "stringsetting.hpp" #include "stringsetting.hpp"
#include <QLabel>
#include <QLineEdit> #include <QLineEdit>
#include <QMutexLocker> #include <QMutexLocker>
@ -26,17 +27,21 @@ CSMPrefs::StringSetting& CSMPrefs::StringSetting::setTooltip(const std::string&
CSMPrefs::SettingWidgets CSMPrefs::StringSetting::makeWidgets(QWidget* parent) CSMPrefs::SettingWidgets CSMPrefs::StringSetting::makeWidgets(QWidget* parent)
{ {
QLabel* label = new QLabel(getLabel(), parent);
mWidget = new QLineEdit(QString::fromStdString(getValue()), parent); mWidget = new QLineEdit(QString::fromStdString(getValue()), parent);
mWidget->setMinimumWidth(300);
if (!mTooltip.empty()) if (!mTooltip.empty())
{ {
QString tooltip = QString::fromUtf8(mTooltip.c_str()); QString tooltip = QString::fromUtf8(mTooltip.c_str());
label->setToolTip(tooltip);
mWidget->setToolTip(tooltip); mWidget->setToolTip(tooltip);
} }
connect(mWidget, &QLineEdit::textChanged, this, &StringSetting::textChanged); connect(mWidget, &QLineEdit::textChanged, this, &StringSetting::textChanged);
return SettingWidgets{ .mLabel = nullptr, .mInput = mWidget }; return SettingWidgets{ .mLabel = label, .mInput = mWidget };
} }
void CSMPrefs::StringSetting::updateWidget() void CSMPrefs::StringSetting::updateWidget()

View file

@ -4,13 +4,13 @@
#include "rotationflags.hpp" #include "rotationflags.hpp"
#include <deque> #include <deque>
#include <map>
#include <set> #include <set>
#include <span> #include <span>
#include <string_view> #include <string_view>
#include <vector> #include <vector>
#include <components/misc/rng.hpp> #include <components/misc/rng.hpp>
#include <components/vfs/pathutil.hpp>
#include "../mwworld/doorstate.hpp" #include "../mwworld/doorstate.hpp"
#include "../mwworld/globalvariablename.hpp" #include "../mwworld/globalvariablename.hpp"
@ -515,7 +515,7 @@ namespace MWBase
/// Spawn a blood effect for \a ptr at \a worldPosition /// Spawn a blood effect for \a ptr at \a worldPosition
virtual void spawnBloodEffect(const MWWorld::Ptr& ptr, const osg::Vec3f& worldPosition) = 0; virtual void spawnBloodEffect(const MWWorld::Ptr& ptr, const osg::Vec3f& worldPosition) = 0;
virtual void spawnEffect(const std::string& model, const std::string& textureOverride, virtual void spawnEffect(VFS::Path::NormalizedView model, const std::string& textureOverride,
const osg::Vec3f& worldPos, float scale = 1.f, bool isMagicVFX = true) const osg::Vec3f& worldPos, float scale = 1.f, bool isMagicVFX = true)
= 0; = 0;

View file

@ -794,18 +794,32 @@ namespace MWGui
if (!Settings::gui().mColorTopicEnable) if (!Settings::gui().mColorTopicEnable)
return; return;
const MyGUI::Colour& specialColour = Settings::gui().mColorTopicSpecific;
const MyGUI::Colour& oldColour = Settings::gui().mColorTopicExhausted;
for (const std::string& keyword : mKeywords) for (const std::string& keyword : mKeywords)
{ {
int flag = MWBase::Environment::get().getDialogueManager()->getTopicFlag(ESM::RefId::stringRefId(keyword)); int flag = MWBase::Environment::get().getDialogueManager()->getTopicFlag(ESM::RefId::stringRefId(keyword));
MyGUI::Button* button = mTopicsList->getItemWidget(keyword); MyGUI::Button* button = mTopicsList->getItemWidget(keyword);
const auto oldCaption = button->getCaption();
const MyGUI::IntSize oldSize = button->getSize();
bool changed = false;
if (flag & MWBase::DialogueManager::TopicType::Specific) if (flag & MWBase::DialogueManager::TopicType::Specific)
button->getSubWidgetText()->setTextColour(specialColour); {
button->changeWidgetSkin("MW_ListLine_Specific");
changed = true;
}
else if (flag & MWBase::DialogueManager::TopicType::Exhausted) else if (flag & MWBase::DialogueManager::TopicType::Exhausted)
button->getSubWidgetText()->setTextColour(oldColour); {
button->changeWidgetSkin("MW_ListLine_Exhausted");
changed = true;
}
if (changed)
{
button->setCaption(oldCaption);
button->getSubWidgetText()->setWordWrap(true);
button->getSubWidgetText()->setTextAlign(MyGUI::Align::Left);
button->setSize(oldSize);
}
} }
} }

View file

@ -55,7 +55,7 @@ namespace MWGui
{ {
int maxDurability = iter->getClass().getItemMaxHealth(*iter); int maxDurability = iter->getClass().getItemMaxHealth(*iter);
int durability = iter->getClass().getItemHealth(*iter); int durability = iter->getClass().getItemHealth(*iter);
if (maxDurability == durability || maxDurability == 0) if (maxDurability <= durability || maxDurability == 0)
continue; continue;
int basePrice = iter->getClass().getValue(*iter); int basePrice = iter->getClass().getValue(*iter);

View file

@ -288,7 +288,7 @@ namespace MWGui
if ((mFilter & Filter_OnlyRepairable) if ((mFilter & Filter_OnlyRepairable)
&& (!base.getClass().hasItemHealth(base) && (!base.getClass().hasItemHealth(base)
|| (base.getClass().getItemHealth(base) == base.getClass().getItemMaxHealth(base)) || (base.getClass().getItemHealth(base) >= base.getClass().getItemMaxHealth(base))
|| (base.getType() != ESM::Weapon::sRecordId && base.getType() != ESM::Armor::sRecordId))) || (base.getType() != ESM::Weapon::sRecordId && base.getType() != ESM::Armor::sRecordId)))
return false; return false;

View file

@ -319,14 +319,14 @@ namespace MWLua
std::string texture = options->get_or<std::string>("particleTextureOverride", ""); std::string texture = options->get_or<std::string>("particleTextureOverride", "");
float scale = options->get_or("scale", 1.f); float scale = options->get_or("scale", 1.f);
context.mLuaManager->addAction( context.mLuaManager->addAction(
[world, model = std::string(model), texture = std::move(texture), worldPos, scale, [world, model = VFS::Path::Normalized(model), texture = std::move(texture), worldPos, scale,
magicVfx]() { world->spawnEffect(model, texture, worldPos, scale, magicVfx); }, magicVfx]() { world->spawnEffect(model, texture, worldPos, scale, magicVfx); },
"openmw.vfx.spawn"); "openmw.vfx.spawn");
} }
else else
{ {
context.mLuaManager->addAction( context.mLuaManager->addAction([world, model = VFS::Path::Normalized(model),
[world, model = std::string(model), worldPos]() { world->spawnEffect(model, "", worldPos); }, worldPos]() { world->spawnEffect(model, "", worldPos); },
"openmw.vfx.spawn"); "openmw.vfx.spawn");
} }
}; };

View file

@ -238,6 +238,11 @@ namespace MWLua
} }
void LuaManager::synchronizedUpdate() void LuaManager::synchronizedUpdate()
{
mLua.protectedCall([&](LuaUtil::LuaView&) { synchronizedUpdateUnsafe(); });
}
void LuaManager::synchronizedUpdateUnsafe()
{ {
if (mNewGameStarted) if (mNewGameStarted)
{ {

View file

@ -170,6 +170,7 @@ namespace MWLua
LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr, LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr,
std::optional<LuaUtil::ScriptIdsWithInitializationData> autoStartConf = std::nullopt); std::optional<LuaUtil::ScriptIdsWithInitializationData> autoStartConf = std::nullopt);
void reloadAllScriptsImpl(); void reloadAllScriptsImpl();
void synchronizedUpdateUnsafe();
bool mInitialized = false; bool mInitialized = false;
bool mGlobalScriptsStarted = false; bool mGlobalScriptsStarted = false;

View file

@ -214,7 +214,7 @@ namespace
const ESM::Static* const fx const ESM::Static* const fx
= world->getStore().get<ESM::Static>().search(ESM::RefId::stringRefId("VFX_Soul_Trap")); = world->getStore().get<ESM::Static>().search(ESM::RefId::stringRefId("VFX_Soul_Trap"));
if (fx != nullptr) if (fx != nullptr)
world->spawnEffect(Misc::ResourceHelpers::correctMeshPath(fx->mModel), "", world->spawnEffect(VFS::Path::toNormalized(Misc::ResourceHelpers::correctMeshPath(fx->mModel)), "",
creature.getRefData().getPosition().asVec3()); creature.getRefData().getPosition().asVec3());
MWBase::Environment::get().getSoundManager()->playSound3D( MWBase::Environment::get().getSoundManager()->playSound3D(
@ -1806,7 +1806,8 @@ namespace MWMechanics
ESM::RefId::stringRefId("VFX_Summon_End")); ESM::RefId::stringRefId("VFX_Summon_End"));
if (fx) if (fx)
MWBase::Environment::get().getWorld()->spawnEffect( MWBase::Environment::get().getWorld()->spawnEffect(
Misc::ResourceHelpers::correctMeshPath(fx->mModel), "", ptr.getRefData().getPosition().asVec3()); VFS::Path::toNormalized(Misc::ResourceHelpers::correctMeshPath(fx->mModel)), "",
ptr.getRefData().getPosition().asVec3());
// Remove the summoned creature's summoned creatures as well // Remove the summoned creature's summoned creatures as well
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);

View file

@ -846,14 +846,12 @@ namespace MWMechanics
mAI = true; mAI = true;
} }
bool MechanicsManager::isBoundItem(const MWWorld::Ptr& item) namespace
{ {
static std::set<ESM::RefId> boundItemIDCache; std::set<ESM::RefId> makeBoundItemIdCache()
{
std::set<ESM::RefId> boundItemIDCache;
// If this is empty then we haven't executed the GMST cache logic yet; or there isn't any sMagicBound* GMST's
// for some reason
if (boundItemIDCache.empty())
{
// Build a list of known bound item ID's // Build a list of known bound item ID's
const MWWorld::Store<ESM::GameSetting>& gameSettings const MWWorld::Store<ESM::GameSetting>& gameSettings
= MWBase::Environment::get().getESMStore()->get<ESM::GameSetting>(); = MWBase::Environment::get().getESMStore()->get<ESM::GameSetting>();
@ -870,15 +868,16 @@ namespace MWMechanics
boundItemIDCache.insert(ESM::RefId::stringRefId(currentGMSTValue)); boundItemIDCache.insert(ESM::RefId::stringRefId(currentGMSTValue));
} }
return boundItemIDCache;
}
} }
// Perform bound item check and assign the Flag_Bound bit if it passes bool MechanicsManager::isBoundItem(const MWWorld::Ptr& item)
const ESM::RefId& tempItemID = item.getCellRef().getRefId(); {
static const std::set<ESM::RefId> boundItemIdCache = makeBoundItemIdCache();
if (boundItemIDCache.count(tempItemID) != 0) return boundItemIdCache.find(item.getCellRef().getRefId()) != boundItemIdCache.end();
return true;
return false;
} }
bool MechanicsManager::isAllowedToUse(const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, MWWorld::Ptr& victim) bool MechanicsManager::isAllowedToUse(const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, MWWorld::Ptr& victim)

View file

@ -72,12 +72,13 @@ namespace MWMechanics
{ {
if (effectInfo.mData.mRange == ESM::RT_Target) if (effectInfo.mData.mRange == ESM::RT_Target)
world->spawnEffect( world->spawnEffect(
Misc::ResourceHelpers::correctMeshPath(areaStatic->mModel), texture, mHitPosition, 1.0f); VFS::Path::toNormalized(Misc::ResourceHelpers::correctMeshPath(areaStatic->mModel)), texture,
mHitPosition, 1.0f);
continue; continue;
} }
else else
world->spawnEffect(Misc::ResourceHelpers::correctMeshPath(areaStatic->mModel), texture, mHitPosition, world->spawnEffect(VFS::Path::toNormalized(Misc::ResourceHelpers::correctMeshPath(areaStatic->mModel)),
static_cast<float>(effectInfo.mData.mArea * 2)); texture, mHitPosition, static_cast<float>(effectInfo.mData.mArea * 2));
// Play explosion sound (make sure to use NoTrack, since we will delete the projectile now) // Play explosion sound (make sure to use NoTrack, since we will delete the projectile now)
{ {
@ -539,7 +540,8 @@ namespace MWMechanics
} }
scale = std::max(scale, 1.f); scale = std::max(scale, 1.f);
MWBase::Environment::get().getWorld()->spawnEffect( MWBase::Environment::get().getWorld()->spawnEffect(
Misc::ResourceHelpers::correctMeshPath(castStatic->mModel), effect->mParticle, pos, scale); VFS::Path::toNormalized(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel)),
effect->mParticle, pos, scale);
} }
if (animation && !mCaster.getClass().isActor()) if (animation && !mCaster.getClass().isActor())

View file

@ -412,9 +412,9 @@ namespace MWPhysics
if (ptr.mRef->mData.mPhysicsPostponed) if (ptr.mRef->mData.mPhysicsPostponed)
return; return;
std::string animationMesh = mesh; const VFS::Path::Normalized animationMesh = ptr.getClass().useAnim()
if (ptr.getClass().useAnim()) ? Misc::ResourceHelpers::correctActorModelPath(mesh, mResourceSystem->getVFS())
animationMesh = Misc::ResourceHelpers::correctActorModelPath(mesh, mResourceSystem->getVFS()); : mesh;
osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance = mShapeManager->getInstance(animationMesh); osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance = mShapeManager->getInstance(animationMesh);
if (!shapeInstance || !shapeInstance->mCollisionShape) if (!shapeInstance || !shapeInstance->mCollisionShape)
return; return;
@ -562,7 +562,8 @@ namespace MWPhysics
void PhysicsSystem::addActor(const MWWorld::Ptr& ptr, const std::string& mesh) void PhysicsSystem::addActor(const MWWorld::Ptr& ptr, const std::string& mesh)
{ {
std::string animationMesh = Misc::ResourceHelpers::correctActorModelPath(mesh, mResourceSystem->getVFS()); const VFS::Path::Normalized animationMesh
= Misc::ResourceHelpers::correctActorModelPath(mesh, mResourceSystem->getVFS());
osg::ref_ptr<const Resource::BulletShape> shape = mShapeManager->getShape(animationMesh); osg::ref_ptr<const Resource::BulletShape> shape = mShapeManager->getShape(animationMesh);
// Try to get shape from basic model as fallback for creatures // Try to get shape from basic model as fallback for creatures
@ -570,7 +571,7 @@ namespace MWPhysics
{ {
if (animationMesh != mesh) if (animationMesh != mesh)
{ {
shape = mShapeManager->getShape(mesh); shape = mShapeManager->getShape(VFS::Path::toNormalized(mesh));
} }
} }
@ -590,7 +591,8 @@ namespace MWPhysics
int PhysicsSystem::addProjectile( int PhysicsSystem::addProjectile(
const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius) const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius)
{ {
osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance = mShapeManager->getInstance(mesh); osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance
= mShapeManager->getInstance(VFS::Path::toNormalized(mesh));
assert(shapeInstance); assert(shapeInstance);
float radius = computeRadius ? shapeInstance->mCollisionBox.mExtents.length() / 2.f : 1.f; float radius = computeRadius ? shapeInstance->mCollisionBox.mExtents.length() / 2.f : 1.f;

View file

@ -67,31 +67,29 @@ namespace MWRender
} }
PartHolderPtr ActorAnimation::attachMesh( PartHolderPtr ActorAnimation::attachMesh(
const std::string& model, std::string_view bonename, bool enchantedGlow, osg::Vec4f* glowColor) VFS::Path::NormalizedView model, std::string_view bonename, const osg::Vec4f* glowColor)
{ {
osg::Group* parent = getBoneByName(bonename); osg::Group* parent = getBoneByName(bonename);
if (!parent) if (!parent)
return nullptr; return nullptr;
osg::ref_ptr<osg::Node> instance osg::ref_ptr<osg::Node> instance = mResourceSystem->getSceneManager()->getInstance(model, parent);
= mResourceSystem->getSceneManager()->getInstance(VFS::Path::toNormalized(model), parent);
const NodeMap& nodeMap = getNodeMap(); const NodeMap& nodeMap = getNodeMap();
NodeMap::const_iterator found = nodeMap.find(bonename); NodeMap::const_iterator found = nodeMap.find(bonename);
if (found == nodeMap.end()) if (found == nodeMap.end())
return {}; return {};
if (enchantedGlow) if (glowColor != nullptr)
mGlowUpdater = SceneUtil::addEnchantedGlow(instance, mResourceSystem, *glowColor); mGlowUpdater = SceneUtil::addEnchantedGlow(instance, mResourceSystem, *glowColor);
return std::make_unique<PartHolder>(instance); return std::make_unique<PartHolder>(instance);
} }
osg::ref_ptr<osg::Node> ActorAnimation::attach( osg::ref_ptr<osg::Node> ActorAnimation::attach(
const std::string& model, std::string_view bonename, std::string_view bonefilter, bool isLight) VFS::Path::NormalizedView model, std::string_view bonename, std::string_view bonefilter, bool isLight)
{ {
osg::ref_ptr<const osg::Node> templateNode osg::ref_ptr<const osg::Node> templateNode = mResourceSystem->getSceneManager()->getTemplate(model);
= mResourceSystem->getSceneManager()->getTemplate(VFS::Path::toNormalized(model));
const NodeMap& nodeMap = getNodeMap(); const NodeMap& nodeMap = getNodeMap();
auto found = nodeMap.find(bonename); auto found = nodeMap.find(bonename);
@ -218,20 +216,20 @@ namespace MWRender
return; return;
} }
std::string mesh = getSheathedShieldMesh(*shield); const VFS::Path::Normalized mesh = getSheathedShieldMesh(*shield);
if (mesh.empty()) if (mesh.empty())
return; return;
std::string_view boneName = "Bip01 AttachShield"; constexpr std::string_view boneName = "Bip01 AttachShield";
osg::Vec4f glowColor = shield->getClass().getEnchantmentColor(*shield); const bool isEnchanted = !shield->getClass().getEnchantment(*shield).empty();
const std::string holsteredName = addSuffixBeforeExtension(mesh, "_sh"); const osg::Vec4f glowColor = isEnchanted ? shield->getClass().getEnchantmentColor(*shield) : osg::Vec4f();
bool isEnchanted = !shield->getClass().getEnchantment(*shield).empty(); const VFS::Path::Normalized holsteredName = addSuffixBeforeExtension(mesh, "_sh");
// If we have no dedicated sheath model, use basic shield model as fallback. // If we have no dedicated sheath model, use basic shield model as fallback.
if (!mResourceSystem->getVFS()->exists(holsteredName)) if (!mResourceSystem->getVFS()->exists(holsteredName))
mHolsteredShield = attachMesh(mesh, boneName, isEnchanted, &glowColor); mHolsteredShield = attachMesh(mesh, boneName, isEnchanted ? &glowColor : nullptr);
else else
mHolsteredShield = attachMesh(holsteredName, boneName, isEnchanted, &glowColor); mHolsteredShield = attachMesh(holsteredName, boneName, isEnchanted ? &glowColor : nullptr);
if (!mHolsteredShield) if (!mHolsteredShield)
return; return;
@ -245,8 +243,7 @@ namespace MWRender
// file. // file.
if (shieldNode && !shieldNode->getNumChildren()) if (shieldNode && !shieldNode->getNumChildren())
{ {
osg::ref_ptr<osg::Node> fallbackNode osg::ref_ptr<osg::Node> fallbackNode = mResourceSystem->getSceneManager()->getInstance(mesh, shieldNode);
= mResourceSystem->getSceneManager()->getInstance(VFS::Path::toNormalized(mesh), shieldNode);
if (isEnchanted) if (isEnchanted)
SceneUtil::addEnchantedGlow(shieldNode, mResourceSystem, glowColor); SceneUtil::addEnchantedGlow(shieldNode, mResourceSystem, glowColor);
} }
@ -341,20 +338,24 @@ namespace MWRender
if (MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown) if (MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown)
showHolsteredWeapons = false; showHolsteredWeapons = false;
std::string mesh = weapon->getClass().getCorrectedModel(*weapon); const VFS::Path::Normalized mesh = weapon->getClass().getCorrectedModel(*weapon);
std::string_view boneName = getHolsteredWeaponBoneName(*weapon); if (mesh.empty())
if (mesh.empty() || boneName.empty()) return;
const std::string_view boneName = getHolsteredWeaponBoneName(*weapon);
if (boneName.empty())
return; return;
// If the scabbard is not found, use the weapon mesh as fallback. // If the scabbard is not found, use the weapon mesh as fallback.
const std::string scabbardName = addSuffixBeforeExtension(mesh, "_sh"); const VFS::Path::Normalized scabbardName = addSuffixBeforeExtension(mesh, "_sh");
bool isEnchanted = !weapon->getClass().getEnchantment(*weapon).empty(); const bool isEnchanted = !weapon->getClass().getEnchantment(*weapon).empty();
if (!mResourceSystem->getVFS()->exists(scabbardName)) if (!mResourceSystem->getVFS()->exists(scabbardName))
{ {
if (showHolsteredWeapons) if (showHolsteredWeapons)
{ {
osg::Vec4f glowColor = weapon->getClass().getEnchantmentColor(*weapon); const osg::Vec4f glowColor
mScabbard = attachMesh(mesh, boneName, isEnchanted, &glowColor); = isEnchanted ? weapon->getClass().getEnchantmentColor(*weapon) : osg::Vec4f();
mScabbard = attachMesh(mesh, boneName, isEnchanted ? &glowColor : nullptr);
if (mScabbard) if (mScabbard)
resetControllers(mScabbard->getNode()); resetControllers(mScabbard->getNode());
} }
@ -384,7 +385,7 @@ namespace MWRender
if (!weaponNode->getNumChildren()) if (!weaponNode->getNumChildren())
{ {
osg::ref_ptr<osg::Node> fallbackNode osg::ref_ptr<osg::Node> fallbackNode
= mResourceSystem->getSceneManager()->getInstance(VFS::Path::toNormalized(mesh), weaponNode); = mResourceSystem->getSceneManager()->getInstance(mesh, weaponNode);
resetControllers(fallbackNode); resetControllers(fallbackNode);
} }

View file

@ -5,6 +5,8 @@
#include <osg/ref_ptr> #include <osg/ref_ptr>
#include <components/vfs/pathutil.hpp>
#include "../mwworld/containerstore.hpp" #include "../mwworld/containerstore.hpp"
#include "animation.hpp" #include "animation.hpp"
@ -45,21 +47,18 @@ namespace MWRender
protected: protected:
osg::Group* getBoneByName(std::string_view boneName) const; osg::Group* getBoneByName(std::string_view boneName) const;
virtual void updateHolsteredWeapon(bool showHolsteredWeapons); void updateHolsteredWeapon(bool showHolsteredWeapons);
virtual void updateHolsteredShield(bool showCarriedLeft); void updateHolsteredShield(bool showCarriedLeft);
virtual void updateQuiver(); void updateQuiver();
std::string getShieldMesh(const MWWorld::ConstPtr& shield, bool female) const; std::string getShieldMesh(const MWWorld::ConstPtr& shield, bool female) const;
virtual std::string getSheathedShieldMesh(const MWWorld::ConstPtr& shield) const; virtual std::string getSheathedShieldMesh(const MWWorld::ConstPtr& shield) const;
virtual std::string_view getHolsteredWeaponBoneName(const MWWorld::ConstPtr& weapon); std::string_view getHolsteredWeaponBoneName(const MWWorld::ConstPtr& weapon);
virtual PartHolderPtr attachMesh(
const std::string& model, std::string_view bonename, bool enchantedGlow, osg::Vec4f* glowColor); PartHolderPtr attachMesh(
virtual PartHolderPtr attachMesh(const std::string& model, std::string_view bonename) VFS::Path::NormalizedView model, std::string_view bonename, const osg::Vec4f* glowColor = nullptr);
{
osg::Vec4f stubColor = osg::Vec4f(0, 0, 0, 0);
return attachMesh(model, bonename, false, &stubColor);
}
osg::ref_ptr<osg::Node> attach( osg::ref_ptr<osg::Node> attach(
const std::string& model, std::string_view bonename, std::string_view bonefilter, bool isLight); VFS::Path::NormalizedView model, std::string_view bonename, std::string_view bonefilter, bool isLight);
PartHolderPtr mScabbard; PartHolderPtr mScabbard;
PartHolderPtr mHolsteredShield; PartHolderPtr mHolsteredShield;

View file

@ -388,18 +388,21 @@ namespace
std::string_view mEffectId; std::string_view mEffectId;
}; };
osg::ref_ptr<osg::LightModel> getVFXLightModelInstance() namespace
{ {
static osg::ref_ptr<osg::LightModel> lightModel = nullptr; osg::ref_ptr<osg::LightModel> makeVFXLightModelInstance()
if (!lightModel)
{ {
lightModel = new osg::LightModel; osg::ref_ptr<osg::LightModel> lightModel = new osg::LightModel;
lightModel->setAmbientIntensity({ 1, 1, 1, 1 }); lightModel->setAmbientIntensity({ 1, 1, 1, 1 });
return lightModel;
} }
const osg::ref_ptr<osg::LightModel>& getVFXLightModelInstance()
{
static const osg::ref_ptr<osg::LightModel> lightModel = makeVFXLightModelInstance();
return lightModel; return lightModel;
} }
}
void assignBoneBlendCallbackRecursive(MWRender::BoneAnimBlendController* controller, osg::Node* parent, bool isRoot) void assignBoneBlendCallbackRecursive(MWRender::BoneAnimBlendController* controller, osg::Node* parent, bool isRoot)
{ {
@ -1508,10 +1511,10 @@ namespace MWRender
} }
animationPath.replace(animationPath.size() - 4, 4, "/"); animationPath.replace(animationPath.size() - 4, 4, "/");
for (const auto& name : resourceSystem->getVFS()->getRecursiveDirectoryIterator(animationPath)) for (const VFS::Path::Normalized& name : resourceSystem->getVFS()->getRecursiveDirectoryIterator(animationPath))
{ {
if (Misc::getFileExtension(name) == "nif") if (Misc::getFileExtension(name) == "nif")
loadBonesFromFile(node, VFS::Path::toNormalized(name), resourceSystem); loadBonesFromFile(node, name, resourceSystem);
} }
} }

View file

@ -533,7 +533,7 @@ namespace MWRender
: CharacterPreview( : CharacterPreview(
parent, resourceSystem, MWMechanics::getPlayer(), 512, 512, osg::Vec3f(0, 125, 8), osg::Vec3f(0, 0, 8)) parent, resourceSystem, MWMechanics::getPlayer(), 512, 512, osg::Vec3f(0, 125, 8), osg::Vec3f(0, 0, 8))
, mBase(*mCharacter.get<ESM::NPC>()->mBase) , mBase(*mCharacter.get<ESM::NPC>()->mBase)
, mRef(&mBase) , mRef(ESM::makeBlankCellRef(), &mBase)
, mPitchRadians(osg::DegreesToRadians(6.f)) , mPitchRadians(osg::DegreesToRadians(6.f))
{ {
mCharacter = MWWorld::Ptr(&mRef, nullptr); mCharacter = MWWorld::Ptr(&mRef, nullptr);

View file

@ -111,7 +111,7 @@ namespace MWRender
MWWorld::ConstPtr item = *it; MWWorld::ConstPtr item = *it;
std::string_view bonename; std::string_view bonename;
std::string itemModel = item.getClass().getCorrectedModel(item); VFS::Path::Normalized itemModel = item.getClass().getCorrectedModel(item);
if (slot == MWWorld::InventoryStore::Slot_CarriedRight) if (slot == MWWorld::InventoryStore::Slot_CarriedRight)
{ {
if (item.getType() == ESM::Weapon::sRecordId) if (item.getType() == ESM::Weapon::sRecordId)

View file

@ -484,15 +484,24 @@ namespace MWRender
bool is1stPerson = mViewMode == VM_FirstPerson; bool is1stPerson = mViewMode == VM_FirstPerson;
bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0; bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0;
std::string_view base;
if (!isWerewolf)
{
if (!is1stPerson)
base = Settings::models().mXbaseanim.get().value();
else
base = Settings::models().mXbaseanim1st.get().value();
}
const std::string defaultSkeleton = Misc::ResourceHelpers::correctActorModelPath( const std::string defaultSkeleton = Misc::ResourceHelpers::correctActorModelPath(
getActorSkeleton(is1stPerson, isFemale, isBeast, isWerewolf), mResourceSystem->getVFS()); getActorSkeleton(is1stPerson, isFemale, isBeast, isWerewolf), mResourceSystem->getVFS());
std::string smodel = defaultSkeleton; std::string smodel = defaultSkeleton;
bool isBase = !isWerewolf; bool isCustomModel = false;
if (!is1stPerson && !isWerewolf && !mNpc->mModel.empty()) if (!is1stPerson && !isWerewolf && !mNpc->mModel.empty())
{ {
std::string model = Misc::ResourceHelpers::correctMeshPath(mNpc->mModel); std::string model = Misc::ResourceHelpers::correctMeshPath(mNpc->mModel);
isBase = isDefaultActorSkeleton(model); isCustomModel = !isDefaultActorSkeleton(model);
smodel = Misc::ResourceHelpers::correctActorModelPath(model, mResourceSystem->getVFS()); smodel = Misc::ResourceHelpers::correctActorModelPath(model, mResourceSystem->getVFS());
} }
@ -500,33 +509,21 @@ namespace MWRender
updateParts(); updateParts();
if (!is1stPerson) if (!base.empty())
{
const std::string& base = Settings::models().mXbaseanim.get().value();
if (!isWerewolf)
addAnimSource(base, smodel); addAnimSource(base, smodel);
if (!isBase) if (defaultSkeleton != base)
{
addAnimSource(defaultSkeleton, smodel); addAnimSource(defaultSkeleton, smodel);
addAnimSource(smodel, smodel);
}
else if (base != defaultSkeleton)
{
addAnimSource(defaultSkeleton, smodel);
}
if (!isWerewolf && isBeast && mNpc->mRace.contains("argonian")) if (isCustomModel)
addAnimSource("meshes\\xargonian_swimkna.nif", smodel);
}
else
{
if (!isWerewolf)
addAnimSource(Settings::models().mXbaseanim1st.get().value(), smodel);
if (!isBase)
addAnimSource(smodel, smodel); addAnimSource(smodel, smodel);
const bool customArgonianSwim = !is1stPerson && !isWerewolf && isBeast && mNpc->mRace.contains("argonian");
if (customArgonianSwim)
addAnimSource(Settings::models().mXargonianswimkna.get().value(), smodel);
if (is1stPerson)
{
mObjectRoot->setNodeMask(Mask_FirstPerson); mObjectRoot->setNodeMask(Mask_FirstPerson);
mObjectRoot->addCullCallback(new OverrideFieldOfViewCallback(mFirstPersonFieldOfView)); mObjectRoot->addCullCallback(new OverrideFieldOfViewCallback(mFirstPersonFieldOfView));
} }
@ -681,11 +678,11 @@ namespace MWRender
PartHolderPtr NpcAnimation::insertBoundedPart(const std::string& model, std::string_view bonename, PartHolderPtr NpcAnimation::insertBoundedPart(const std::string& model, std::string_view bonename,
std::string_view bonefilter, bool enchantedGlow, osg::Vec4f* glowColor, bool isLight) std::string_view bonefilter, bool enchantedGlow, osg::Vec4f* glowColor, bool isLight)
{ {
osg::ref_ptr<osg::Node> attached = attach(model, bonename, bonefilter, isLight); osg::ref_ptr<osg::Node> attached = attach(VFS::Path::toNormalized(model), bonename, bonefilter, isLight);
if (enchantedGlow) if (enchantedGlow)
mGlowUpdater = SceneUtil::addEnchantedGlow(attached, mResourceSystem, *glowColor); mGlowUpdater = SceneUtil::addEnchantedGlow(attached, mResourceSystem, *glowColor);
return std::make_unique<PartHolder>(attached); return std::make_unique<PartHolder>(std::move(attached));
} }
osg::Vec3f NpcAnimation::runAnimation(float timepassed) osg::Vec3f NpcAnimation::runAnimation(float timepassed)

View file

@ -20,6 +20,7 @@
#include <components/esm3/loaddoor.hpp> #include <components/esm3/loaddoor.hpp>
#include <components/esm3/loadstat.hpp> #include <components/esm3/loadstat.hpp>
#include <components/esm3/readerscache.hpp> #include <components/esm3/readerscache.hpp>
#include <components/misc/pathhelpers.hpp>
#include <components/misc/resourcehelpers.hpp> #include <components/misc/resourcehelpers.hpp>
#include <components/misc/rng.hpp> #include <components/misc/rng.hpp>
#include <components/resource/scenemanager.hpp> #include <components/resource/scenemanager.hpp>
@ -639,7 +640,7 @@ namespace MWRender
continue; continue;
const int type = store.findStatic(ref.mRefId); const int type = store.findStatic(ref.mRefId);
std::string model = getModel(type, ref.mRefId, store); VFS::Path::Normalized model = getModel(type, ref.mRefId, store);
if (model.empty()) if (model.empty())
continue; continue;
model = Misc::ResourceHelpers::correctMeshPath(model); model = Misc::ResourceHelpers::correctMeshPath(model);
@ -647,10 +648,10 @@ namespace MWRender
if (activeGrid && type != ESM::REC_STAT) if (activeGrid && type != ESM::REC_STAT)
{ {
model = Misc::ResourceHelpers::correctActorModelPath(model, mSceneManager->getVFS()); model = Misc::ResourceHelpers::correctActorModelPath(model, mSceneManager->getVFS());
std::string kfname = Misc::StringUtils::lowerCase(model); if (Misc::getFileExtension(model) == "nif")
if (kfname.size() > 4 && kfname.ends_with(".nif"))
{ {
kfname.replace(kfname.size() - 4, 4, ".kf"); VFS::Path::Normalized kfname = model;
kfname.changeExtension("kf");
if (mSceneManager->getVFS()->exists(kfname)) if (mSceneManager->getVFS()->exists(kfname))
continue; continue;
} }
@ -671,7 +672,7 @@ namespace MWRender
->second; ->second;
} }
osg::ref_ptr<const osg::Node> cnode = mSceneManager->getTemplate(VFS::Path::toNormalized(model), false); osg::ref_ptr<const osg::Node> cnode = mSceneManager->getTemplate(model, false);
if (activeGrid) if (activeGrid)
{ {

View file

@ -206,8 +206,14 @@ namespace MWRender
}; };
// PASS: Blot in all ripple spawners // PASS: Blot in all ripple spawners
mProgramBlobber->apply(state); state.pushStateSet(frameState.mStateset);
state.apply(frameState.mStateset); state.apply();
state.applyAttribute(mProgramBlobber);
for (const auto& [name, stack] : state.getUniformMap())
{
if (!stack.uniformVec.empty())
state.getLastAppliedProgramObject()->apply(*(stack.uniformVec.back().first));
}
if (mUseCompute) if (mUseCompute)
{ {
@ -225,8 +231,12 @@ namespace MWRender
} }
// PASS: Wave simulation // PASS: Wave simulation
mProgramSimulation->apply(state); state.applyAttribute(mProgramSimulation);
state.apply(frameState.mStateset); for (const auto& [name, stack] : state.getUniformMap())
{
if (!stack.uniformVec.empty())
state.getLastAppliedProgramObject()->apply(*(stack.uniformVec.back().first));
}
if (mUseCompute) if (mUseCompute)
{ {
@ -242,6 +252,8 @@ namespace MWRender
state.applyTextureAttribute(0, mTextures[1]); state.applyTextureAttribute(0, mTextures[1]);
osg::Geometry::drawImplementation(renderInfo); osg::Geometry::drawImplementation(renderInfo);
} }
state.popStateSet();
} }
osg::Texture* RipplesSurface::getColorTexture() const osg::Texture* RipplesSurface::getColorTexture() const

View file

@ -11,6 +11,7 @@
#include <components/esm3/loadcell.hpp> #include <components/esm3/loadcell.hpp>
#include <components/loadinglistener/reporter.hpp> #include <components/loadinglistener/reporter.hpp>
#include <components/misc/constants.hpp> #include <components/misc/constants.hpp>
#include <components/misc/pathhelpers.hpp>
#include <components/misc/resourcehelpers.hpp> #include <components/misc/resourcehelpers.hpp>
#include <components/misc/strings/algorithm.hpp> #include <components/misc/strings/algorithm.hpp>
#include <components/misc/strings/lower.hpp> #include <components/misc/strings/lower.hpp>
@ -105,8 +106,8 @@ namespace MWWorld
} }
} }
std::string mesh; VFS::Path::Normalized mesh;
std::string kfname; VFS::Path::Normalized kfname;
for (std::string_view path : mMeshes) for (std::string_view path : mMeshes)
{ {
if (mAbort) if (mAbort)
@ -121,19 +122,15 @@ namespace MWWorld
if (!vfs.exists(mesh)) if (!vfs.exists(mesh))
continue; continue;
size_t slashpos = mesh.find_last_of("/\\"); if (Misc::getFileName(mesh).starts_with('x') && Misc::getFileExtension(mesh) == "nif")
if (slashpos != std::string::npos && slashpos != mesh.size() - 1)
{
if (Misc::StringUtils::toLower(mesh[slashpos + 1]) == 'x'
&& Misc::StringUtils::ciEndsWith(mesh, ".nif"))
{ {
kfname = mesh; kfname = mesh;
kfname.replace(kfname.size() - 4, 4, ".kf"); kfname.changeExtension("kf");
if (vfs.exists(kfname)) if (vfs.exists(kfname))
mPreloadedObjects.insert(mKeyframeManager->get(kfname)); mPreloadedObjects.insert(mKeyframeManager->get(kfname));
} }
}
mPreloadedObjects.insert(mSceneManager->getTemplate(VFS::Path::toNormalized(mesh))); mPreloadedObjects.insert(mSceneManager->getTemplate(mesh));
if (mPreloadInstances) if (mPreloadInstances)
mPreloadedObjects.insert(mBulletShapeManager->cacheInstance(mesh)); mPreloadedObjects.insert(mBulletShapeManager->cacheInstance(mesh));
else else

View file

@ -317,9 +317,9 @@ namespace
} }
// new reference // new reference
MWWorld::LiveCellRef<T> ref(record); MWWorld::LiveCellRef<T> ref(ESM::makeBlankCellRef(), record);
ref.load(state); ref.load(state);
collection.mList.push_back(ref); collection.mList.push_back(std::move(ref));
MWWorld::LiveCellRefBase* base = &collection.mList.back(); MWWorld::LiveCellRefBase* base = &collection.mList.back();
MWBase::Environment::get().getWorldModel()->registerPtr(MWWorld::Ptr(base, cellstore)); MWBase::Environment::get().getWorldModel()->registerPtr(MWWorld::Ptr(base, cellstore));
@ -426,9 +426,9 @@ namespace MWWorld
liveCellRef.mData.setDeletedByContentFile(true); liveCellRef.mData.setDeletedByContentFile(true);
if (iter != mList.end()) if (iter != mList.end())
*iter = liveCellRef; *iter = std::move(liveCellRef);
else else
mList.push_back(liveCellRef); mList.push_back(std::move(liveCellRef));
} }
else else
{ {
@ -455,7 +455,7 @@ namespace MWWorld
LiveCellRef<X> liveCellRef(ref, ptr); LiveCellRef<X> liveCellRef(ref, ptr);
if (!isEnabled(ref, esmStore)) if (!isEnabled(ref, esmStore))
liveCellRef.mData.disable(); liveCellRef.mData.disable();
list.push_back(liveCellRef); list.push_back(std::move(liveCellRef));
} }
template <typename X> template <typename X>

View file

@ -101,9 +101,9 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::getState(
if (!record) if (!record)
return ContainerStoreIterator(this); return ContainerStoreIterator(this);
LiveCellRef<T> ref(record); LiveCellRef<T> ref(ESM::makeBlankCellRef(), record);
ref.load(state); ref.load(state);
collection.mList.push_back(ref); collection.mList.push_back(std::move(ref));
auto it = ContainerStoreIterator(this, --collection.mList.end()); auto it = ContainerStoreIterator(this, --collection.mList.end());
MWBase::Environment::get().getWorldModel()->registerPtr(*it); MWBase::Environment::get().getWorldModel()->registerPtr(*it);

View file

@ -15,35 +15,55 @@
#include "ptr.hpp" #include "ptr.hpp"
#include "worldmodel.hpp" #include "worldmodel.hpp"
MWWorld::LiveCellRefBase::LiveCellRefBase(unsigned int type, const ESM::CellRef& cref) namespace MWWorld
{
LiveCellRefBase::LiveCellRefBase(unsigned int type, const ESM::CellRef& cref)
: mClass(&Class::get(type)) : mClass(&Class::get(type))
, mRef(cref) , mRef(cref)
, mData(cref) , mData(cref)
{ {
} }
MWWorld::LiveCellRefBase::LiveCellRefBase(unsigned int type, const ESM4::Reference& cref) LiveCellRefBase::LiveCellRefBase(unsigned int type, const ESM4::Reference& cref)
: mClass(&Class::get(type)) : mClass(&Class::get(type))
, mRef(cref) , mRef(cref)
, mData(cref) , mData(cref)
{ {
} }
MWWorld::LiveCellRefBase::LiveCellRefBase(unsigned int type, const ESM4::ActorCharacter& cref) LiveCellRefBase::LiveCellRefBase(unsigned int type, const ESM4::ActorCharacter& cref)
: mClass(&Class::get(type)) : mClass(&Class::get(type))
, mRef(cref) , mRef(cref)
, mData(cref) , mData(cref)
{ {
} }
MWWorld::LiveCellRefBase::~LiveCellRefBase() LiveCellRefBase::LiveCellRefBase(LiveCellRefBase&& other) noexcept
: mClass(other.mClass)
, mRef(std::move(other.mRef))
, mData(std::move(other.mData))
, mWorldModel(std::exchange(other.mWorldModel, nullptr))
{ {
MWBase::Environment::get().getWorldModel()->deregisterLiveCellRef(*this);
} }
void MWWorld::LiveCellRefBase::loadImp(const ESM::ObjectState& state) LiveCellRefBase::~LiveCellRefBase()
{ {
mRef = MWWorld::CellRef(state.mRef); if (mWorldModel != nullptr)
mWorldModel->deregisterLiveCellRef(*this);
}
LiveCellRefBase& LiveCellRefBase::operator=(LiveCellRefBase&& other) noexcept
{
mClass = other.mClass;
mRef = std::move(other.mRef);
mData = std::move(other.mData);
mWorldModel = std::exchange(other.mWorldModel, nullptr);
return *this;
}
void LiveCellRefBase::loadImp(const ESM::ObjectState& state)
{
mRef = CellRef(state.mRef);
mData = RefData(state, mData.isDeletedByContentFile()); mData = RefData(state, mData.isDeletedByContentFile());
Ptr ptr(this); Ptr ptr(this);
@ -83,7 +103,7 @@ void MWWorld::LiveCellRefBase::loadImp(const ESM::ObjectState& state)
MWBase::Environment::get().getLuaManager()->loadLocalScripts(ptr, state.mLuaScripts); MWBase::Environment::get().getLuaManager()->loadLocalScripts(ptr, state.mLuaScripts);
} }
void MWWorld::LiveCellRefBase::saveImp(ESM::ObjectState& state) const void LiveCellRefBase::saveImp(ESM::ObjectState& state) const
{ {
mRef.writeState(state); mRef.writeState(state);
@ -96,23 +116,21 @@ void MWWorld::LiveCellRefBase::saveImp(ESM::ObjectState& state) const
mClass->writeAdditionalState(ptr, state); mClass->writeAdditionalState(ptr, state);
} }
bool MWWorld::LiveCellRefBase::checkStateImp(const ESM::ObjectState& state) bool LiveCellRefBase::checkStateImp(const ESM::ObjectState& state)
{ {
return true; return true;
} }
unsigned int MWWorld::LiveCellRefBase::getType() const unsigned int LiveCellRefBase::getType() const
{ {
return mClass->getType(); return mClass->getType();
} }
bool MWWorld::LiveCellRefBase::isDeleted() const bool LiveCellRefBase::isDeleted() const
{ {
return mData.isDeletedByContentFile() || mRef.getCount(false) == 0; return mData.isDeletedByContentFile() || mRef.getCount(false) == 0;
} }
namespace MWWorld
{
std::string makeDynamicCastErrorMessage(const LiveCellRefBase* value, std::string_view recordType) std::string makeDynamicCastErrorMessage(const LiveCellRefBase* value, std::string_view recordType)
{ {
std::stringstream message; std::stringstream message;

View file

@ -17,6 +17,7 @@ namespace MWWorld
class Ptr; class Ptr;
class ESMStore; class ESMStore;
class Class; class Class;
class WorldModel;
template <typename X> template <typename X>
struct LiveCellRef; struct LiveCellRef;
@ -29,17 +30,28 @@ namespace MWWorld
/** Information about this instance, such as 3D location and rotation /** Information about this instance, such as 3D location and rotation
* and individual type-dependent data. * and individual type-dependent data.
*/ */
MWWorld::CellRef mRef; CellRef mRef;
/** runtime-data */ /** runtime-data */
RefData mData; RefData mData;
LiveCellRefBase(unsigned int type, const ESM::CellRef& cref = ESM::CellRef()); WorldModel* mWorldModel = nullptr;
LiveCellRefBase(unsigned int type, const ESM::CellRef& cref);
LiveCellRefBase(unsigned int type, const ESM4::Reference& cref); LiveCellRefBase(unsigned int type, const ESM4::Reference& cref);
LiveCellRefBase(unsigned int type, const ESM4::ActorCharacter& cref); LiveCellRefBase(unsigned int type, const ESM4::ActorCharacter& cref);
LiveCellRefBase(const LiveCellRefBase& other) = default;
LiveCellRefBase(LiveCellRefBase&& other) noexcept;
/* Need this for the class to be recognized as polymorphic */ /* Need this for the class to be recognized as polymorphic */
virtual ~LiveCellRefBase(); virtual ~LiveCellRefBase();
LiveCellRefBase& operator=(const LiveCellRefBase& other) = default;
LiveCellRefBase& operator=(LiveCellRefBase&& other) noexcept;
virtual void load(const ESM::ObjectState& state) = 0; virtual void load(const ESM::ObjectState& state) = 0;
///< Load state into a LiveCellRef, that has already been initialised with base and class. ///< Load state into a LiveCellRef, that has already been initialised with base and class.
/// ///
@ -132,12 +144,6 @@ namespace MWWorld
{ {
} }
LiveCellRef(const X* b = nullptr)
: LiveCellRefBase(X::sRecordId)
, mBase(b)
{
}
// The object that this instance is based on. // The object that this instance is based on.
const X* mBase; const X* mBase;

View file

@ -36,8 +36,20 @@
namespace MWWorld namespace MWWorld
{ {
namespace
{
ESM::CellRef makePlayerCellRef()
{
ESM::CellRef result;
result.blank();
result.mRefID = ESM::RefId::stringRefId("Player");
return result;
}
}
Player::Player(const ESM::NPC* player) Player::Player(const ESM::NPC* player)
: mCellStore(nullptr) : mPlayer(makePlayerCellRef(), player)
, mCellStore(nullptr)
, mLastKnownExteriorPosition(0, 0, 0) , mLastKnownExteriorPosition(0, 0, 0)
, mMarkedPosition(ESM::Position()) , mMarkedPosition(ESM::Position())
, mMarkedCell(nullptr) , mMarkedCell(nullptr)
@ -46,11 +58,6 @@ namespace MWWorld
, mPaidCrimeId(-1) , mPaidCrimeId(-1)
, mJumping(false) , mJumping(false)
{ {
ESM::CellRef cellRef;
cellRef.blank();
cellRef.mRefID = ESM::RefId::stringRefId("Player");
mPlayer = LiveCellRef<ESM::NPC>(cellRef, player);
ESM::Position playerPos = mPlayer.mData.getPosition(); ESM::Position playerPos = mPlayer.mData.getPosition();
playerPos.pos[0] = playerPos.pos[1] = playerPos.pos[2] = 0; playerPos.pos[0] = playerPos.pos[1] = playerPos.pos[2] = 0;
mPlayer.mData.setPosition(playerPos); mPlayer.mData.setPosition(playerPos);

View file

@ -451,8 +451,7 @@ namespace MWWorld
} }
else if (!ESM::isEsm4Ext(worldspace)) else if (!ESM::isEsm4Ext(worldspace))
{ {
static std::vector<float> defaultHeight; static const std::vector<float> defaultHeight(verts * verts, ESM::Land::DEFAULT_HEIGHT);
defaultHeight.resize(verts * verts, ESM::Land::DEFAULT_HEIGHT);
mPhysics->addHeightField(defaultHeight.data(), cellX, cellY, worldsize, verts, mPhysics->addHeightField(defaultHeight.data(), cellX, cellY, worldsize, verts,
ESM::Land::DEFAULT_HEIGHT, ESM::Land::DEFAULT_HEIGHT, land.get()); ESM::Land::DEFAULT_HEIGHT, ESM::Land::DEFAULT_HEIGHT, land.get());
} }
@ -1126,20 +1125,20 @@ namespace MWWorld
void Scene::preload(const std::string& mesh, bool useAnim) void Scene::preload(const std::string& mesh, bool useAnim)
{ {
std::string meshPath = mesh; const VFS::Path::Normalized meshPath = useAnim
if (useAnim) ? Misc::ResourceHelpers::correctActorModelPath(mesh, mRendering.getResourceSystem()->getVFS())
meshPath = Misc::ResourceHelpers::correctActorModelPath(meshPath, mRendering.getResourceSystem()->getVFS()); : mesh;
if (!mRendering.getResourceSystem()->getSceneManager()->checkLoaded(meshPath, mRendering.getReferenceTime())) if (mRendering.getResourceSystem()->getSceneManager()->checkLoaded(meshPath, mRendering.getReferenceTime()))
{ return;
osg::ref_ptr<PreloadMeshItem> item(new PreloadMeshItem(
VFS::Path::toNormalized(meshPath), mRendering.getResourceSystem()->getSceneManager())); osg::ref_ptr<PreloadMeshItem> item(
new PreloadMeshItem(meshPath, mRendering.getResourceSystem()->getSceneManager()));
mRendering.getWorkQueue()->addWorkItem(item); mRendering.getWorkQueue()->addWorkItem(item);
const auto isDone = [](const osg::ref_ptr<SceneUtil::WorkItem>& v) { return v->isDone(); }; const auto isDone = [](const osg::ref_ptr<SceneUtil::WorkItem>& v) { return v->isDone(); };
mWorkItems.erase(std::remove_if(mWorkItems.begin(), mWorkItems.end(), isDone), mWorkItems.end()); mWorkItems.erase(std::remove_if(mWorkItems.begin(), mWorkItems.end(), isDone), mWorkItems.end());
mWorkItems.emplace_back(std::move(item)); mWorkItems.emplace_back(std::move(item));
} }
}
void Scene::preloadCells(float dt) void Scene::preloadCells(float dt)
{ {

View file

@ -3644,10 +3644,10 @@ namespace MWWorld
mRendering->spawnEffect(model, texture, worldPosition, 1.0f, false); mRendering->spawnEffect(model, texture, worldPosition, 1.0f, false);
} }
void World::spawnEffect(const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos, void World::spawnEffect(VFS::Path::NormalizedView model, const std::string& textureOverride,
float scale, bool isMagicVFX) const osg::Vec3f& worldPos, float scale, bool isMagicVFX)
{ {
mRendering->spawnEffect(VFS::Path::toNormalized(model), textureOverride, worldPos, scale, isMagicVFX); mRendering->spawnEffect(model, textureOverride, worldPos, scale, isMagicVFX);
} }
struct ResetActorsVisitor struct ResetActorsVisitor

View file

@ -7,6 +7,7 @@
#include <components/esm3/readerscache.hpp> #include <components/esm3/readerscache.hpp>
#include <components/misc/rng.hpp> #include <components/misc/rng.hpp>
#include <components/settings/settings.hpp> #include <components/settings/settings.hpp>
#include <components/vfs/pathutil.hpp>
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
@ -603,8 +604,8 @@ namespace MWWorld
/// Spawn a blood effect for \a ptr at \a worldPosition /// Spawn a blood effect for \a ptr at \a worldPosition
void spawnBloodEffect(const MWWorld::Ptr& ptr, const osg::Vec3f& worldPosition) override; void spawnBloodEffect(const MWWorld::Ptr& ptr, const osg::Vec3f& worldPosition) override;
void spawnEffect(const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos, void spawnEffect(VFS::Path::NormalizedView model, const std::string& textureOverride,
float scale = 1.f, bool isMagicVFX = true) override; const osg::Vec3f& worldPos, float scale = 1.f, bool isMagicVFX = true) override;
/// @see MWWorld::WeatherManager::isInStorm /// @see MWWorld::WeatherManager::isInStorm
bool isInStorm() const override; bool isInStorm() const override;

View file

@ -3,6 +3,7 @@
#include <algorithm> #include <algorithm>
#include <cassert> #include <cassert>
#include <optional> #include <optional>
#include <stdexcept>
#include <components/debug/debuglog.hpp> #include <components/debug/debuglog.hpp>
#include <components/esm/defs.hpp> #include <components/esm/defs.hpp>
@ -339,6 +340,20 @@ namespace MWWorld
throw std::runtime_error(std::string("Can't find cell with name ") + std::string(name)); throw std::runtime_error(std::string("Can't find cell with name ") + std::string(name));
return *result; return *result;
} }
void WorldModel::registerPtr(const Ptr& ptr)
{
if (ptr.mRef == nullptr)
throw std::logic_error("Ptr with nullptr mRef is not allowed to be registered");
mPtrRegistry.insert(ptr);
ptr.mRef->mWorldModel = this;
}
void WorldModel::deregisterLiveCellRef(LiveCellRefBase& ref) noexcept
{
mPtrRegistry.remove(ref);
ref.mWorldModel = nullptr;
}
} }
MWWorld::Ptr MWWorld::WorldModel::getPtrByRefId(const ESM::RefId& name) MWWorld::Ptr MWWorld::WorldModel::getPtrByRefId(const ESM::RefId& name)

View file

@ -77,9 +77,9 @@ namespace MWWorld
std::size_t getPtrRegistryRevision() const { return mPtrRegistry.getRevision(); } std::size_t getPtrRegistryRevision() const { return mPtrRegistry.getRevision(); }
void registerPtr(const Ptr& ptr) { mPtrRegistry.insert(ptr); } void registerPtr(const Ptr& ptr);
void deregisterLiveCellRef(const LiveCellRefBase& ref) noexcept { mPtrRegistry.remove(ref); } void deregisterLiveCellRef(LiveCellRefBase& ref) noexcept;
void assignSaveFileRefNum(ESM::CellRef& ref) { mPtrRegistry.assign(ref); } void assignSaveFileRefNum(ESM::CellRef& ref) { mPtrRegistry.assign(ref); }

View file

@ -9,6 +9,7 @@ file(GLOB UNITTEST_SRC_FILES
mwworld/test_store.cpp mwworld/test_store.cpp
mwworld/testduration.cpp mwworld/testduration.cpp
mwworld/testtimestamp.cpp mwworld/testtimestamp.cpp
mwworld/testptr.cpp
mwdialogue/test_keywordsearch.cpp mwdialogue/test_keywordsearch.cpp

View file

@ -1,11 +1,28 @@
#include <components/debug/debugging.hpp> #include <components/debug/debugging.hpp>
#include <components/misc/strings/conversion.hpp>
#include <components/settings/parser.hpp>
#include <components/settings/values.hpp>
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <filesystem>
int main(int argc, char* argv[]) int main(int argc, char* argv[])
{ {
Log::sMinDebugLevel = Debug::getDebugLevel(); Log::sMinDebugLevel = Debug::getDebugLevel();
const std::filesystem::path settingsDefaultPath = std::filesystem::path{ OPENMW_PROJECT_SOURCE_DIR } / "files"
/ Misc::StringUtils::stringToU8String("settings-default.cfg");
Settings::SettingsFileParser parser;
parser.loadSettingsFile(settingsDefaultPath, Settings::Manager::mDefaultSettings);
Settings::StaticValues::initDefaults();
Settings::Manager::mUserSettings = Settings::Manager::mDefaultSettings;
Settings::StaticValues::init();
testing::InitGoogleTest(&argc, argv); testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS(); return RUN_ALL_TESTS();
} }

View file

@ -0,0 +1,82 @@
#include "apps/openmw/mwclass/npc.hpp"
#include "apps/openmw/mwworld/esmstore.hpp"
#include "apps/openmw/mwworld/livecellref.hpp"
#include "apps/openmw/mwworld/ptr.hpp"
#include "apps/openmw/mwworld/worldmodel.hpp"
#include <components/esm3/loadnpc.hpp>
#include <components/esm3/readerscache.hpp>
#include <gtest/gtest.h>
namespace MWWorld
{
namespace
{
using namespace testing;
TEST(MWWorldPtrTest, toStringShouldReturnHumanReadableTextRepresentationOfPtrWithNullRef)
{
Ptr ptr;
EXPECT_EQ(ptr.toString(), "null object");
}
TEST(MWWorldPtrTest, toStringShouldReturnHumanReadableTextRepresentationOfPtrWithDeletedRef)
{
MWClass::Npc::registerSelf();
ESM::NPC npc;
npc.blank();
npc.mId = ESM::RefId::stringRefId("Player");
ESMStore store;
store.insert(npc);
ESM::CellRef cellRef;
cellRef.blank();
cellRef.mRefID = npc.mId;
cellRef.mRefNum = ESM::RefNum{ .mIndex = 0x2a, .mContentFile = 0xd };
LiveCellRef<ESM::NPC> liveCellRef(cellRef, &npc);
liveCellRef.mData.setDeletedByContentFile(true);
Ptr ptr(&liveCellRef);
EXPECT_EQ(ptr.toString(), "deleted object0xd00002a (NPC, \"player\")");
}
TEST(MWWorldPtrTest, toStringShouldReturnHumanReadableTextRepresentationOfPtr)
{
MWClass::Npc::registerSelf();
ESM::NPC npc;
npc.blank();
npc.mId = ESM::RefId::stringRefId("Player");
ESMStore store;
store.insert(npc);
ESM::CellRef cellRef;
cellRef.blank();
cellRef.mRefID = npc.mId;
cellRef.mRefNum = ESM::RefNum{ .mIndex = 0x2a, .mContentFile = 0xd };
LiveCellRef<ESM::NPC> liveCellRef(cellRef, &npc);
Ptr ptr(&liveCellRef);
EXPECT_EQ(ptr.toString(), "object0xd00002a (NPC, \"player\")");
}
TEST(MWWorldPtrTest, underlyingLiveCellRefShouldBeDeregisteredOnDestruction)
{
MWClass::Npc::registerSelf();
ESM::NPC npc;
npc.blank();
npc.mId = ESM::RefId::stringRefId("Player");
ESMStore store;
store.insert(npc);
ESM::ReadersCache readersCache;
WorldModel worldModel(store, readersCache);
ESM::CellRef cellRef;
cellRef.blank();
cellRef.mRefID = npc.mId;
cellRef.mRefNum = ESM::FormId{ .mIndex = 0x2a, .mContentFile = 0xd };
{
LiveCellRef<ESM::NPC> liveCellRef(cellRef, &npc);
Ptr ptr(&liveCellRef);
worldModel.registerPtr(ptr);
ASSERT_EQ(worldModel.getPtr(cellRef.mRefNum), ptr);
}
EXPECT_EQ(worldModel.getPtr(cellRef.mRefNum), Ptr());
}
}
}

View file

@ -239,7 +239,7 @@ namespace Debug
group->push(state); group->push(state);
lastAppliedStack.push_back(group); lastAppliedStack.push_back(group);
} }
if (!(lastAppliedStack.back() == this)) if (lastAppliedStack.empty() || !(lastAppliedStack.back() == this))
{ {
push(state); push(state);
lastAppliedStack.push_back(this); lastAppliedStack.push_back(this);

View file

@ -56,7 +56,7 @@ void ESM::LuaScriptsCfg::load(ESMReader& esm)
{ {
mScripts.emplace_back(); mScripts.emplace_back();
ESM::LuaScriptCfg& script = mScripts.back(); ESM::LuaScriptCfg& script = mScripts.back();
script.mScriptPath = esm.getHString(); script.mScriptPath = VFS::Path::Normalized(esm.getHString());
esm.getSubNameIs("LUAF"); esm.getSubNameIs("LUAF");
esm.getSubHeader(); esm.getSubHeader();
@ -161,7 +161,7 @@ void ESM::LuaScripts::load(ESMReader& esm)
{ {
while (esm.isNextSub("LUAS")) while (esm.isNextSub("LUAS"))
{ {
std::string name = esm.getHString(); VFS::Path::Normalized name(esm.getHString());
std::string data = loadLuaBinaryData(esm); std::string data = loadLuaBinaryData(esm);
std::vector<LuaTimer> timers; std::vector<LuaTimer> timers;
while (esm.isNextSub("LUAT")) while (esm.isNextSub("LUAT"))

View file

@ -287,4 +287,10 @@ namespace ESM
loadDataImpl<false>(esm, isDeleted, cellRef); loadDataImpl<false>(esm, isDeleted, cellRef);
} }
CellRef makeBlankCellRef()
{
CellRef result;
result.blank();
return result;
}
} }

View file

@ -105,6 +105,8 @@ namespace ESM
}; };
void skipLoadCellRef(ESMReader& esm, bool wideRefNum = false); void skipLoadCellRef(ESMReader& esm, bool wideRefNum = false);
CellRef makeBlankCellRef();
} }
#endif #endif

View file

@ -132,52 +132,60 @@ namespace ESM
esm.writeHNOString("DESC", mDescription); esm.writeHNOString("DESC", mDescription);
} }
namespace
{
std::map<short, short> makeEffectsMap()
{
std::map<short, short> effects;
effects[MagicEffect::Effects::DisintegrateArmor] = MagicEffect::Effects::Sanctuary;
effects[MagicEffect::Effects::DisintegrateWeapon] = MagicEffect::Effects::Sanctuary;
for (int i = MagicEffect::Effects::DrainAttribute; i <= MagicEffect::Effects::DamageSkill; ++i)
effects[i] = MagicEffect::Effects::ResistMagicka;
for (int i = MagicEffect::Effects::AbsorbAttribute; i <= MagicEffect::Effects::AbsorbSkill; ++i)
effects[i] = MagicEffect::Effects::ResistMagicka;
for (int i = MagicEffect::Effects::WeaknessToFire; i <= MagicEffect::Effects::WeaknessToNormalWeapons; ++i)
effects[i] = MagicEffect::Effects::ResistMagicka;
effects[MagicEffect::Effects::Burden] = MagicEffect::Effects::ResistMagicka;
effects[MagicEffect::Effects::Charm] = MagicEffect::Effects::ResistMagicka;
effects[MagicEffect::Effects::Silence] = MagicEffect::Effects::ResistMagicka;
effects[MagicEffect::Effects::Blind] = MagicEffect::Effects::ResistMagicka;
effects[MagicEffect::Effects::Sound] = MagicEffect::Effects::ResistMagicka;
for (int i = 0; i < 2; ++i)
{
effects[MagicEffect::Effects::CalmHumanoid + i] = MagicEffect::Effects::ResistMagicka;
effects[MagicEffect::Effects::FrenzyHumanoid + i] = MagicEffect::Effects::ResistMagicka;
effects[MagicEffect::Effects::DemoralizeHumanoid + i] = MagicEffect::Effects::ResistMagicka;
effects[MagicEffect::Effects::RallyHumanoid + i] = MagicEffect::Effects::ResistMagicka;
}
effects[MagicEffect::Effects::TurnUndead] = MagicEffect::Effects::ResistMagicka;
effects[MagicEffect::Effects::FireDamage] = MagicEffect::Effects::ResistFire;
effects[MagicEffect::Effects::FrostDamage] = MagicEffect::Effects::ResistFrost;
effects[MagicEffect::Effects::ShockDamage] = MagicEffect::Effects::ResistShock;
effects[MagicEffect::Effects::Vampirism] = MagicEffect::Effects::ResistCommonDisease;
effects[MagicEffect::Effects::Corprus] = MagicEffect::Effects::ResistCorprusDisease;
effects[MagicEffect::Effects::Poison] = MagicEffect::Effects::ResistPoison;
effects[MagicEffect::Effects::Paralyze] = MagicEffect::Effects::ResistParalysis;
return effects;
}
}
short MagicEffect::getResistanceEffect(short effect) short MagicEffect::getResistanceEffect(short effect)
{ {
// Source https://wiki.openmw.org/index.php?title=Research:Magic#Effect_attribute // Source https://wiki.openmw.org/index.php?title=Research:Magic#Effect_attribute
// <Effect, Effect providing resistance against first effect> // <Effect, Effect providing resistance against first effect>
static std::map<short, short> effects; static const std::map<short, short> effects = makeEffectsMap();
if (effects.empty())
{
effects[DisintegrateArmor] = Sanctuary;
effects[DisintegrateWeapon] = Sanctuary;
for (int i = DrainAttribute; i <= DamageSkill; ++i) if (const auto it = effects.find(effect); it != effects.end())
effects[i] = ResistMagicka; return it->second;
for (int i = AbsorbAttribute; i <= AbsorbSkill; ++i)
effects[i] = ResistMagicka;
for (int i = WeaknessToFire; i <= WeaknessToNormalWeapons; ++i)
effects[i] = ResistMagicka;
effects[Burden] = ResistMagicka;
effects[Charm] = ResistMagicka;
effects[Silence] = ResistMagicka;
effects[Blind] = ResistMagicka;
effects[Sound] = ResistMagicka;
for (int i = 0; i < 2; ++i)
{
effects[CalmHumanoid + i] = ResistMagicka;
effects[FrenzyHumanoid + i] = ResistMagicka;
effects[DemoralizeHumanoid + i] = ResistMagicka;
effects[RallyHumanoid + i] = ResistMagicka;
}
effects[TurnUndead] = ResistMagicka;
effects[FireDamage] = ResistFire;
effects[FrostDamage] = ResistFrost;
effects[ShockDamage] = ResistShock;
effects[Vampirism] = ResistCommonDisease;
effects[Corprus] = ResistCorprusDisease;
effects[Poison] = ResistPoison;
effects[Paralyze] = ResistParalysis;
}
if (effects.find(effect) != effects.end())
return effects[effect];
else
return -1; return -1;
} }

View file

@ -320,16 +320,18 @@ namespace ESM4
std::filesystem::path path = strings / (prefix + language + suffix); std::filesystem::path path = strings / (prefix + language + suffix);
if (mVFS != nullptr) if (mVFS != nullptr)
{ {
std::string vfsPath = Files::pathToUnicodeString(path); VFS::Path::Normalized vfsPath(Files::pathToUnicodeString(path));
if (!mVFS->exists(vfsPath)) Files::IStreamPtr stream = mVFS->find(vfsPath);
if (stream == nullptr)
{ {
path = strings / (prefix + altLanguage + suffix); path = strings / (prefix + altLanguage + suffix);
vfsPath = Files::pathToUnicodeString(path); vfsPath = VFS::Path::Normalized(Files::pathToUnicodeString(path));
stream = mVFS->find(vfsPath);
} }
if (mVFS->exists(vfsPath)) if (stream != nullptr)
{ {
const Files::IStreamPtr stream = mVFS->get(vfsPath);
buildLStringIndex(stringType, *stream); buildLStringIndex(stringType, *stream);
return; return;
} }

View file

@ -113,6 +113,7 @@ namespace LuaUi
ContentView content(LuaUtil::cast<sol::table>(contentObj)); ContentView content(LuaUtil::cast<sol::table>(contentObj));
result.resize(content.size()); result.resize(content.size());
size_t minSize = std::min(children.size(), content.size()); size_t minSize = std::min(children.size(), content.size());
std::vector<WidgetExtension*> toDestroy;
for (size_t i = 0; i < minSize; i++) for (size_t i = 0; i < minSize; i++)
{ {
WidgetExtension* ext = children[i]; WidgetExtension* ext = children[i];
@ -121,7 +122,7 @@ namespace LuaUi
{ {
WidgetExtension* root = pluckElementRoot(child, depth); WidgetExtension* root = pluckElementRoot(child, depth);
if (ext != root) if (ext != root)
destroyChild(ext); toDestroy.emplace_back(ext);
result[i] = root; result[i] = root;
} }
else else
@ -133,14 +134,12 @@ namespace LuaUi
} }
else else
{ {
destroyChild(ext); toDestroy.emplace_back(ext);
ext = createWidget(newLayout, false, depth); ext = createWidget(newLayout, false, depth);
} }
result[i] = ext; result[i] = ext;
} }
} }
for (size_t i = minSize; i < children.size(); i++)
destroyChild(children[i]);
for (size_t i = minSize; i < content.size(); i++) for (size_t i = minSize; i < content.size(); i++)
{ {
sol::object child = content.at(i); sol::object child = content.at(i);
@ -149,6 +148,11 @@ namespace LuaUi
else else
result[i] = createWidget(child.as<sol::table>(), false, depth); result[i] = createWidget(child.as<sol::table>(), false, depth);
} }
// Don't destroy anything until element creation has had a chance to throw
for (size_t i = minSize; i < children.size(); i++)
destroyChild(children[i]);
for (WidgetExtension* ext : toDestroy)
destroyChild(ext);
return result; return result;
} }
@ -217,7 +221,9 @@ namespace LuaUi
std::string setLayer(WidgetExtension* ext, const sol::table& layout) std::string setLayer(WidgetExtension* ext, const sol::table& layout)
{ {
MyGUI::ILayer* layerNode = ext->widget()->getLayer(); MyGUI::ILayer* layerNode = ext->widget()->getLayer();
std::string currentLayer = layerNode ? layerNode->getName() : std::string(); std::string_view currentLayer;
if (layerNode)
currentLayer = layerNode->getName();
std::string newLayer = layout.get_or(LayoutKeys::layer, std::string()); std::string newLayer = layout.get_or(LayoutKeys::layer, std::string());
if (!newLayer.empty() && !MyGUI::LayerManager::getInstance().isExist(newLayer)) if (!newLayer.empty() && !MyGUI::LayerManager::getInstance().isExist(newLayer))
throw std::logic_error(std::string("Layer ") + newLayer + " doesn't exist"); throw std::logic_error(std::string("Layer ") + newLayer + " doesn't exist");
@ -278,9 +284,20 @@ namespace LuaUi
WidgetExtension* parent = mRoot->getParent(); WidgetExtension* parent = mRoot->getParent();
auto children = parent->children(); auto children = parent->children();
auto it = std::find(children.begin(), children.end(), mRoot); auto it = std::find(children.begin(), children.end(), mRoot);
mRoot = createWidget(layout(), true, 0);
assert(it != children.end()); assert(it != children.end());
try
{
mRoot = createWidget(layout(), true, 0);
*it = mRoot; *it = mRoot;
}
catch (...)
{
// Remove mRoot from its parent's children even if we couldn't replace it
children.erase(it);
parent->setChildren(children);
mRoot = nullptr;
throw;
}
parent->setChildren(children); parent->setChildren(children);
mRoot->updateCoord(); mRoot->updateCoord();
} }
@ -300,6 +317,10 @@ namespace LuaUi
{ {
if (mRoot != nullptr) if (mRoot != nullptr)
{ {
// If someone decided to destroy an element used as another element's content, we need to detach it
// first so the parent doesn't end up holding a stale pointer
if (WidgetExtension* parent = mRoot->getParent())
parent->detachChildrenIf([&](WidgetExtension* child) { return child == mRoot; });
destroyRoot(mRoot); destroyRoot(mRoot);
mRoot = nullptr; mRoot = nullptr;
} }

View file

@ -126,6 +126,7 @@ namespace LuaUi
{ {
mParent = nullptr; mParent = nullptr;
widget()->detachFromWidget(); widget()->detachFromWidget();
widget()->detachFromLayer();
} }
WidgetExtension* WidgetExtension::findDeep(std::string_view flagName) WidgetExtension* WidgetExtension::findDeep(std::string_view flagName)

View file

@ -179,7 +179,7 @@ namespace LuaUi
void updateVisible(); void updateVisible();
void detachChildrenIf(auto&& predicate, std::vector<WidgetExtension*> children) void detachChildrenIf(auto&& predicate, std::vector<WidgetExtension*>& children)
{ {
for (auto it = children.begin(); it != children.end();) for (auto it = children.begin(); it != children.end();)
{ {

View file

@ -228,7 +228,7 @@ bool Misc::ResourceHelpers::isHiddenMarker(const ESM::RefId& id)
namespace namespace
{ {
std::string getLODMeshNameImpl(std::string resPath, const VFS::Manager* vfs, std::string_view pattern) std::string getLODMeshNameImpl(std::string resPath, std::string_view pattern)
{ {
if (auto w = Misc::findExtension(resPath); w != std::string::npos) if (auto w = Misc::findExtension(resPath); w != std::string::npos)
resPath.insert(w, pattern); resPath.insert(w, pattern);
@ -237,7 +237,7 @@ namespace
std::string getBestLODMeshName(std::string const& resPath, const VFS::Manager* vfs, std::string_view pattern) std::string getBestLODMeshName(std::string const& resPath, const VFS::Manager* vfs, std::string_view pattern)
{ {
if (const auto& result = getLODMeshNameImpl(resPath, vfs, pattern); vfs->exists(result)) if (std::string result = getLODMeshNameImpl(resPath, pattern); vfs->exists(result))
return result; return result;
return resPath; return resPath;
} }

View file

@ -95,7 +95,7 @@ namespace osgMyGUI
if (!mImageManager) if (!mImageManager)
throw std::runtime_error("No imagemanager set"); throw std::runtime_error("No imagemanager set");
osg::ref_ptr<osg::Image> image(mImageManager->getImage(VFS::Path::toNormalized(fname))); osg::ref_ptr<osg::Image> image(mImageManager->getImage(VFS::Path::Normalized(fname)));
mTexture = new osg::Texture2D(image); mTexture = new osg::Texture2D(image);
mTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); mTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
mTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); mTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);

View file

@ -53,7 +53,7 @@ namespace NifBullet
mShape->mFileName = nif.getFilename(); mShape->mFileName = nif.getFilename();
if (roots.empty()) if (roots.empty())
{ {
warn("Found no root nodes in NIF file " + mShape->mFileName); warn("Found no root nodes in NIF file " + mShape->mFileName.value());
return mShape; return mShape;
} }
@ -93,7 +93,7 @@ namespace NifBullet
} }
else else
{ {
warn("Invalid Bounding Box node bounds in file " + mShape->mFileName); warn("Invalid Bounding Box node bounds in file " + mShape->mFileName.value());
} }
return true; return true;
} }

View file

@ -1,7 +1,6 @@
#ifndef OPENMW_COMPONENTS_RESOURCE_BULLETSHAPE_H #ifndef OPENMW_COMPONENTS_RESOURCE_BULLETSHAPE_H
#define OPENMW_COMPONENTS_RESOURCE_BULLETSHAPE_H #define OPENMW_COMPONENTS_RESOURCE_BULLETSHAPE_H
#include <array>
#include <map> #include <map>
#include <memory> #include <memory>
@ -12,6 +11,8 @@
#include <BulletCollision/CollisionShapes/btBvhTriangleMeshShape.h> #include <BulletCollision/CollisionShapes/btBvhTriangleMeshShape.h>
#include <BulletCollision/CollisionShapes/btScaledBvhTriangleMeshShape.h> #include <BulletCollision/CollisionShapes/btScaledBvhTriangleMeshShape.h>
#include <components/vfs/pathutil.hpp>
class btCollisionShape; class btCollisionShape;
namespace NifBullet namespace NifBullet
@ -56,7 +57,7 @@ namespace Resource
// we store the node's record index mapped to the child index of the shape in the btCompoundShape. // we store the node's record index mapped to the child index of the shape in the btCompoundShape.
std::map<int, int> mAnimatedShapes; std::map<int, int> mAnimatedShapes;
std::string mFileName; VFS::Path::Normalized mFileName;
std::string mFileHash; std::string mFileHash;
VisualCollisionType mVisualCollisionType = VisualCollisionType::None; VisualCollisionType mVisualCollisionType = VisualCollisionType::None;

View file

@ -106,30 +106,27 @@ namespace Resource
{ {
} }
BulletShapeManager::~BulletShapeManager() {} BulletShapeManager::~BulletShapeManager() = default;
osg::ref_ptr<const BulletShape> BulletShapeManager::getShape(const std::string& name) osg::ref_ptr<const BulletShape> BulletShapeManager::getShape(VFS::Path::NormalizedView name)
{ {
const VFS::Path::Normalized normalized(name); if (osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(name))
return osg::ref_ptr<BulletShape>(static_cast<BulletShape*>(obj.get()));
osg::ref_ptr<BulletShape> shape; osg::ref_ptr<BulletShape> shape;
osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(normalized);
if (obj) if (Misc::getFileExtension(name.value()) == "nif")
shape = osg::ref_ptr<BulletShape>(static_cast<BulletShape*>(obj.get()));
else
{
if (Misc::getFileExtension(normalized) == "nif")
{ {
NifBullet::BulletNifLoader loader; NifBullet::BulletNifLoader loader;
shape = loader.load(*mNifFileManager->get(normalized)); shape = loader.load(*mNifFileManager->get(name));
} }
else else
{ {
// TODO: support .bullet shape files // TODO: support .bullet shape files
osg::ref_ptr<const osg::Node> constNode(mSceneManager->getTemplate(normalized)); osg::ref_ptr<const osg::Node> constNode(mSceneManager->getTemplate(name));
osg::ref_ptr<osg::Node> node(const_cast<osg::Node*>( // const-trickery required because there is no const version of NodeVisitor
constNode.get())); // const-trickery required because there is no const version of NodeVisitor osg::ref_ptr<osg::Node> node(const_cast<osg::Node*>(constNode.get()));
// Check first if there's a custom collision node // Check first if there's a custom collision node
unsigned int visitAllNodesMask = 0xffffffff; unsigned int visitAllNodesMask = 0xffffffff;
@ -158,41 +155,34 @@ namespace Resource
if (shape != nullptr) if (shape != nullptr)
{ {
shape->mFileName = normalized; shape->mFileName = name;
constNode->getUserValue(Misc::OsgUserValues::sFileHash, shape->mFileHash); constNode->getUserValue(Misc::OsgUserValues::sFileHash, shape->mFileHash);
} }
} }
mCache->addEntryToObjectCache(normalized, shape); mCache->addEntryToObjectCache(name.value(), shape);
}
return shape; return shape;
} }
osg::ref_ptr<BulletShapeInstance> BulletShapeManager::cacheInstance(const std::string& name) osg::ref_ptr<BulletShapeInstance> BulletShapeManager::cacheInstance(VFS::Path::NormalizedView name)
{ {
const std::string normalized = VFS::Path::normalizeFilename(name); osg::ref_ptr<BulletShapeInstance> instance = createInstance(name);
if (instance != nullptr)
osg::ref_ptr<BulletShapeInstance> instance = createInstance(normalized); mInstanceCache->addEntryToObjectCache(name, instance.get());
if (instance)
mInstanceCache->addEntryToObjectCache(normalized, instance.get());
return instance; return instance;
} }
osg::ref_ptr<BulletShapeInstance> BulletShapeManager::getInstance(const std::string& name) osg::ref_ptr<BulletShapeInstance> BulletShapeManager::getInstance(VFS::Path::NormalizedView name)
{ {
const std::string normalized = VFS::Path::normalizeFilename(name); if (osg::ref_ptr<osg::Object> obj = mInstanceCache->takeFromObjectCache(name))
osg::ref_ptr<osg::Object> obj = mInstanceCache->takeFromObjectCache(normalized);
if (obj.get())
return static_cast<BulletShapeInstance*>(obj.get()); return static_cast<BulletShapeInstance*>(obj.get());
else return createInstance(name);
return createInstance(normalized);
} }
osg::ref_ptr<BulletShapeInstance> BulletShapeManager::createInstance(const std::string& name) osg::ref_ptr<BulletShapeInstance> BulletShapeManager::createInstance(VFS::Path::NormalizedView name)
{ {
osg::ref_ptr<const BulletShape> shape = getShape(name); if (osg::ref_ptr<const BulletShape> shape = getShape(name))
if (shape)
return makeInstance(std::move(shape)); return makeInstance(std::move(shape));
return osg::ref_ptr<BulletShapeInstance>(); return osg::ref_ptr<BulletShapeInstance>();
} }

View file

@ -1,11 +1,10 @@
#ifndef OPENMW_COMPONENTS_BULLETSHAPEMANAGER_H #ifndef OPENMW_COMPONENTS_BULLETSHAPEMANAGER_H
#define OPENMW_COMPONENTS_BULLETSHAPEMANAGER_H #define OPENMW_COMPONENTS_BULLETSHAPEMANAGER_H
#include <map>
#include <string>
#include <osg/ref_ptr> #include <osg/ref_ptr>
#include <components/vfs/pathutil.hpp>
#include "bulletshape.hpp" #include "bulletshape.hpp"
#include "resourcemanager.hpp" #include "resourcemanager.hpp"
@ -30,16 +29,16 @@ namespace Resource
~BulletShapeManager(); ~BulletShapeManager();
/// @note May return a null pointer if the object has no shape. /// @note May return a null pointer if the object has no shape.
osg::ref_ptr<const BulletShape> getShape(const std::string& name); osg::ref_ptr<const BulletShape> getShape(VFS::Path::NormalizedView name);
/// Create an instance of the given shape and cache it for later use, so that future calls to getInstance() can /// Create an instance of the given shape and cache it for later use, so that future calls to getInstance() can
/// simply return the cached instance instead of having to create a new one. /// simply return the cached instance instead of having to create a new one.
/// @note The returned ref_ptr may be kept by the caller to ensure that the instance stays in cache for as long /// @note The returned ref_ptr may be kept by the caller to ensure that the instance stays in cache for as long
/// as needed. /// as needed.
osg::ref_ptr<BulletShapeInstance> cacheInstance(const std::string& name); osg::ref_ptr<BulletShapeInstance> cacheInstance(VFS::Path::NormalizedView name);
/// @note May return a null pointer if the object has no shape. /// @note May return a null pointer if the object has no shape.
osg::ref_ptr<BulletShapeInstance> getInstance(const std::string& name); osg::ref_ptr<BulletShapeInstance> getInstance(VFS::Path::NormalizedView name);
/// @see ResourceManager::updateCache /// @see ResourceManager::updateCache
void updateCache(double referenceTime) override; void updateCache(double referenceTime) override;
@ -49,7 +48,7 @@ namespace Resource
void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; void reportStats(unsigned int frameNumber, osg::Stats* stats) const override;
private: private:
osg::ref_ptr<BulletShapeInstance> createInstance(const std::string& name); osg::ref_ptr<BulletShapeInstance> createInstance(VFS::Path::NormalizedView name);
osg::ref_ptr<MultiObjectCache> mInstanceCache; osg::ref_ptr<MultiObjectCache> mInstanceCache;
SceneManager* mSceneManager; SceneManager* mSceneManager;

View file

@ -16,10 +16,6 @@
#include <osg/ref_ptr> #include <osg/ref_ptr>
#include <algorithm> #include <algorithm>
#include <memory>
#include <stdexcept>
#include <string>
#include <string_view>
#include <utility> #include <utility>
#include <vector> #include <vector>
@ -97,7 +93,7 @@ namespace Resource
for (CellRef& cellRef : cellRefs) for (CellRef& cellRef : cellRefs)
{ {
std::string model(getModel(esmData, cellRef.mRefId, cellRef.mType)); VFS::Path::Normalized model(getModel(esmData, cellRef.mRefId, cellRef.mType));
if (model.empty()) if (model.empty())
continue; continue;
@ -107,7 +103,8 @@ namespace Resource
osg::ref_ptr<const Resource::BulletShape> shape = [&] { osg::ref_ptr<const Resource::BulletShape> shape = [&] {
try try
{ {
return bulletShapeManager.getShape("meshes/" + model); constexpr VFS::Path::NormalizedView prefix("meshes");
return bulletShapeManager.getShape(prefix / model);
} }
catch (const std::exception& e) catch (const std::exception& e)
{ {

View file

@ -6,11 +6,6 @@
namespace Resource namespace Resource
{ {
MultiObjectCache::MultiObjectCache() {}
MultiObjectCache::~MultiObjectCache() {}
void MultiObjectCache::removeUnreferencedObjectsInCache() void MultiObjectCache::removeUnreferencedObjectsInCache()
{ {
std::vector<osg::ref_ptr<osg::Object>> objectsToRemove; std::vector<osg::ref_ptr<osg::Object>> objectsToRemove;
@ -44,7 +39,7 @@ namespace Resource
_objectCache.clear(); _objectCache.clear();
} }
void MultiObjectCache::addEntryToObjectCache(const std::string& filename, osg::Object* object) void MultiObjectCache::addEntryToObjectCache(VFS::Path::NormalizedView filename, osg::Object* object)
{ {
if (!object) if (!object)
{ {
@ -52,23 +47,23 @@ namespace Resource
return; return;
} }
std::lock_guard<std::mutex> lock(_objectCacheMutex); std::lock_guard<std::mutex> lock(_objectCacheMutex);
_objectCache.insert(std::make_pair(filename, object)); _objectCache.emplace(filename, object);
} }
osg::ref_ptr<osg::Object> MultiObjectCache::takeFromObjectCache(const std::string& fileName) osg::ref_ptr<osg::Object> MultiObjectCache::takeFromObjectCache(VFS::Path::NormalizedView fileName)
{ {
std::lock_guard<std::mutex> lock(_objectCacheMutex); std::lock_guard<std::mutex> lock(_objectCacheMutex);
++mGet; ++mGet;
ObjectCacheMap::iterator found = _objectCache.find(fileName); const auto it = _objectCache.find(fileName);
if (found == _objectCache.end()) if (it != _objectCache.end())
return osg::ref_ptr<osg::Object>();
else
{ {
osg::ref_ptr<osg::Object> object = std::move(found->second); osg::ref_ptr<osg::Object> object = std::move(it->second);
_objectCache.erase(found); _objectCache.erase(it);
++mHit; ++mHit;
return object; return object;
} }
return nullptr;
} }
void MultiObjectCache::releaseGLObjects(osg::State* state) void MultiObjectCache::releaseGLObjects(osg::State* state)

View file

@ -3,11 +3,12 @@
#include <map> #include <map>
#include <mutex> #include <mutex>
#include <string>
#include <osg/Referenced> #include <osg/Referenced>
#include <osg/ref_ptr> #include <osg/ref_ptr>
#include <components/vfs/pathutil.hpp>
#include "cachestats.hpp" #include "cachestats.hpp"
namespace osg namespace osg
@ -23,18 +24,15 @@ namespace Resource
class MultiObjectCache : public osg::Referenced class MultiObjectCache : public osg::Referenced
{ {
public: public:
MultiObjectCache();
~MultiObjectCache();
void removeUnreferencedObjectsInCache(); void removeUnreferencedObjectsInCache();
/** Remove all objects from the cache. */ /** Remove all objects from the cache. */
void clear(); void clear();
void addEntryToObjectCache(const std::string& filename, osg::Object* object); void addEntryToObjectCache(VFS::Path::NormalizedView filename, osg::Object* object);
/** Take an Object from cache. Return nullptr if no object found. */ /** Take an Object from cache. Return nullptr if no object found. */
osg::ref_ptr<osg::Object> takeFromObjectCache(const std::string& fileName); osg::ref_ptr<osg::Object> takeFromObjectCache(VFS::Path::NormalizedView fileName);
/** call releaseGLObjects on all objects attached to the object cache.*/ /** call releaseGLObjects on all objects attached to the object cache.*/
void releaseGLObjects(osg::State* state); void releaseGLObjects(osg::State* state);
@ -42,7 +40,7 @@ namespace Resource
CacheStats getStats() const; CacheStats getStats() const;
protected: protected:
typedef std::multimap<std::string, osg::ref_ptr<osg::Object>> ObjectCacheMap; typedef std::multimap<VFS::Path::Normalized, osg::ref_ptr<osg::Object>, std::less<>> ObjectCacheMap;
ObjectCacheMap _objectCache; ObjectCacheMap _objectCache;
mutable std::mutex _objectCacheMutex; mutable std::mutex _objectCacheMutex;

View file

@ -597,9 +597,9 @@ namespace Resource
mShaderManager->setShaderPath(path); mShaderManager->setShaderPath(path);
} }
bool SceneManager::checkLoaded(const std::string& name, double timeStamp) bool SceneManager::checkLoaded(VFS::Path::NormalizedView name, double timeStamp)
{ {
return mCache->checkInObjectCache(VFS::Path::normalizeFilename(name), timeStamp); return mCache->checkInObjectCache(name, timeStamp);
} }
void SceneManager::setUpNormalsRTForStateSet(osg::StateSet* stateset, bool enabled) void SceneManager::setUpNormalsRTForStateSet(osg::StateSet* stateset, bool enabled)

View file

@ -152,7 +152,7 @@ namespace Resource
void setShaderPath(const std::filesystem::path& path); void setShaderPath(const std::filesystem::path& path);
/// Check if a given scene is loaded and if so, update its usage timestamp to prevent it from being unloaded /// Check if a given scene is loaded and if so, update its usage timestamp to prevent it from being unloaded
bool checkLoaded(const std::string& name, double referenceTime); bool checkLoaded(VFS::Path::NormalizedView name, double referenceTime);
/// Get a read-only copy of this scene "template" /// Get a read-only copy of this scene "template"
/// @note If the given filename does not exist or fails to load, an error marker mesh will be used instead. /// @note If the given filename does not exist or fails to load, an error marker mesh will be used instead.

View file

@ -82,6 +82,9 @@ namespace SceneUtil
std::string_view mFilter; std::string_view mFilter;
}; };
namespace
{
void mergeUserData(const osg::UserDataContainer* source, osg::Object* target) void mergeUserData(const osg::UserDataContainer* source, osg::Object* target)
{ {
if (!source) if (!source)
@ -97,6 +100,17 @@ namespace SceneUtil
} }
} }
osg::ref_ptr<osg::StateSet> makeFrontFaceStateSet()
{
osg::ref_ptr<osg::FrontFace> frontFace = new osg::FrontFace;
frontFace->setMode(osg::FrontFace::CLOCKWISE);
osg::ref_ptr<osg::StateSet> frontFaceStateSet = new osg::StateSet;
frontFaceStateSet->setAttributeAndModes(frontFace, osg::StateAttribute::ON);
return frontFaceStateSet;
}
}
osg::ref_ptr<osg::Node> attach(osg::ref_ptr<const osg::Node> toAttach, osg::Node* master, std::string_view filter, osg::ref_ptr<osg::Node> attach(osg::ref_ptr<const osg::Node> toAttach, osg::Node* master, std::string_view filter,
osg::Group* attachNode, Resource::SceneManager* sceneManager, const osg::Quat* attitude) osg::Group* attachNode, Resource::SceneManager* sceneManager, const osg::Quat* attitude)
{ {
@ -159,14 +173,8 @@ namespace SceneUtil
// Note: for absolute correctness we would need to check the current front face for every mesh then // Note: for absolute correctness we would need to check the current front face for every mesh then
// invert it However MW isn't doing this either, so don't. Assuming all meshes are using backface // invert it However MW isn't doing this either, so don't. Assuming all meshes are using backface
// culling is more efficient. // culling is more efficient.
static osg::ref_ptr<osg::StateSet> frontFaceStateSet; static const osg::ref_ptr<osg::StateSet> frontFaceStateSet = makeFrontFaceStateSet();
if (!frontFaceStateSet)
{
frontFaceStateSet = new osg::StateSet;
osg::FrontFace* frontFace = new osg::FrontFace;
frontFace->setMode(osg::FrontFace::CLOCKWISE);
frontFaceStateSet->setAttributeAndModes(frontFace, osg::StateAttribute::ON);
}
trans->setStateSet(frontFaceStateSet); trans->setStateSet(frontFaceStateSet);
} }

View file

@ -33,7 +33,11 @@ namespace Settings
SettingValue<bool> mKeyboardNavigation{ mIndex, "GUI", "keyboard navigation" }; SettingValue<bool> mKeyboardNavigation{ mIndex, "GUI", "keyboard navigation" };
SettingValue<bool> mColorTopicEnable{ mIndex, "GUI", "color topic enable" }; SettingValue<bool> mColorTopicEnable{ mIndex, "GUI", "color topic enable" };
SettingValue<MyGUI::Colour> mColorTopicSpecific{ mIndex, "GUI", "color topic specific" }; SettingValue<MyGUI::Colour> mColorTopicSpecific{ mIndex, "GUI", "color topic specific" };
SettingValue<MyGUI::Colour> mColorTopicSpecificOver{ mIndex, "GUI", "color topic specific over" };
SettingValue<MyGUI::Colour> mColorTopicSpecificPressed{ mIndex, "GUI", "color topic specific pressed" };
SettingValue<MyGUI::Colour> mColorTopicExhausted{ mIndex, "GUI", "color topic exhausted" }; SettingValue<MyGUI::Colour> mColorTopicExhausted{ mIndex, "GUI", "color topic exhausted" };
SettingValue<MyGUI::Colour> mColorTopicExhaustedOver{ mIndex, "GUI", "color topic exhausted over" };
SettingValue<MyGUI::Colour> mColorTopicExhaustedPressed{ mIndex, "GUI", "color topic exhausted pressed" };
}; };
} }

View file

@ -1,7 +1,7 @@
#ifndef COMPONENTS_TERRAIN_DEFS_HPP #ifndef COMPONENTS_TERRAIN_DEFS_HPP
#define COMPONENTS_TERRAIN_DEFS_HPP #define COMPONENTS_TERRAIN_DEFS_HPP
#include <string> #include <components/vfs/pathutil.hpp>
namespace Terrain namespace Terrain
{ {
@ -16,8 +16,8 @@ namespace Terrain
struct LayerInfo struct LayerInfo
{ {
std::string mDiffuseMap; VFS::Path::Normalized mDiffuseMap;
std::string mNormalMap; VFS::Path::Normalized mNormalMap;
bool mParallax; // Height info in normal map alpha channel? bool mParallax; // Height info in normal map alpha channel?
bool mSpecular; // Specular info in diffuse map alpha channel? bool mSpecular; // Specular info in diffuse map alpha channel?

View file

@ -404,6 +404,25 @@ namespace Terrain
} }
} }
namespace
{
osg::ref_ptr<osg::StateSet> makeStateSet()
{
osg::ref_ptr<osg::StateSet> stateSet = new osg::StateSet;
stateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF);
stateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
stateSet->setAttributeAndModes(
new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE),
osg::StateAttribute::ON);
osg::ref_ptr<osg::Material> material = new osg::Material;
material->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 1, 1));
material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, 1));
material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, 1));
stateSet->setAttributeAndModes(material, osg::StateAttribute::ON);
stateSet->setRenderBinDetails(100, "RenderBin");
return stateSet;
}
void updateWaterCullingView( void updateWaterCullingView(
HeightCullCallback* callback, ViewData* vd, osgUtil::CullVisitor* cv, float cellworldsize, bool outofworld) HeightCullCallback* callback, ViewData* vd, osgUtil::CullVisitor* cv, float cellworldsize, bool outofworld)
{ {
@ -421,8 +440,8 @@ namespace Terrain
for (unsigned int i = 0; i < vd->getNumEntries(); ++i) for (unsigned int i = 0; i < vd->getNumEntries(); ++i)
{ {
ViewDataEntry& entry = vd->getEntry(i); ViewDataEntry& entry = vd->getEntry(i);
osg::BoundingBox bb osg::BoundingBox bb = static_cast<TerrainDrawable*>(entry.mRenderingNode->asGroup()->getChild(0))
= static_cast<TerrainDrawable*>(entry.mRenderingNode->asGroup()->getChild(0))->getWaterBoundingBox(); ->getWaterBoundingBox();
if (!bb.valid()) if (!bb.valid())
continue; continue;
osg::Vec3f ofs( osg::Vec3f ofs(
@ -440,28 +459,14 @@ namespace Terrain
osg::Box* b = new osg::Box; osg::Box* b = new osg::Box;
b->set(bb.center(), bb._max - bb.center()); b->set(bb.center(), bb._max - bb.center());
osg::ShapeDrawable* drw = new osg::ShapeDrawable(b); osg::ShapeDrawable* drw = new osg::ShapeDrawable(b);
static osg::ref_ptr<osg::StateSet> stateset = nullptr; static const osg::ref_ptr<osg::StateSet> stateset = makeStateSet();
if (!stateset)
{
stateset = new osg::StateSet;
stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF);
stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
stateset->setAttributeAndModes(
new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE),
osg::StateAttribute::ON);
osg::Material* m = new osg::Material;
m->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 1, 1));
m->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, 1));
m->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, 1));
stateset->setAttributeAndModes(m, osg::StateAttribute::ON);
stateset->setRenderBinDetails(100, "RenderBin");
}
drw->setStateSet(stateset); drw->setStateSet(stateset);
drw->accept(*cv); drw->accept(*cv);
} }
callback->setLowZ(lowZ); callback->setLowZ(lowZ);
cv->popCurrentMask(); cv->popCurrentMask();
} }
}
void QuadTreeWorld::accept(osg::NodeVisitor& nv) void QuadTreeWorld::accept(osg::NodeVisitor& nv)
{ {

View file

@ -35,24 +35,22 @@ namespace Terrain
mCache->call(f); mCache->call(f);
} }
osg::ref_ptr<osg::Texture2D> TextureManager::getTexture(const std::string& name) osg::ref_ptr<osg::Texture2D> TextureManager::getTexture(VFS::Path::NormalizedView name)
{ {
// don't bother with case folding, since there is only one way of referring to terrain textures we can assume // don't bother with case folding, since there is only one way of referring to terrain textures we can assume
// the case is always the same // the case is always the same
osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(name); osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(name);
if (obj)
if (obj != nullptr)
return static_cast<osg::Texture2D*>(obj.get()); return static_cast<osg::Texture2D*>(obj.get());
else
{ osg::ref_ptr<osg::Texture2D> texture(new osg::Texture2D(mSceneManager->getImageManager()->getImage(name)));
osg::ref_ptr<osg::Texture2D> texture(
new osg::Texture2D(mSceneManager->getImageManager()->getImage(VFS::Path::toNormalized(name))));
texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
mSceneManager->applyFilterSettings(texture); mSceneManager->applyFilterSettings(texture);
mCache->addEntryToObjectCache(name, texture.get()); mCache->addEntryToObjectCache(name.value(), texture.get());
return texture; return texture;
} }
}
void TextureManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const void TextureManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const
{ {

View file

@ -1,9 +1,8 @@
#ifndef OPENMW_COMPONENTS_TERRAIN_TEXTUREMANAGER_H #ifndef OPENMW_COMPONENTS_TERRAIN_TEXTUREMANAGER_H
#define OPENMW_COMPONENTS_TERRAIN_TEXTUREMANAGER_H #define OPENMW_COMPONENTS_TERRAIN_TEXTUREMANAGER_H
#include <string>
#include <components/resource/resourcemanager.hpp> #include <components/resource/resourcemanager.hpp>
#include <components/vfs/pathutil.hpp>
namespace Resource namespace Resource
{ {
@ -25,7 +24,7 @@ namespace Terrain
void updateTextureFiltering(); void updateTextureFiltering();
osg::ref_ptr<osg::Texture2D> getTexture(const std::string& name); osg::ref_ptr<osg::Texture2D> getTexture(VFS::Path::NormalizedView name);
void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; void reportStats(unsigned int frameNumber, osg::Stats* stats) const override;

View file

@ -157,8 +157,8 @@ color topic specific
-------------------- --------------------
:Type: RGBA floating point :Type: RGBA floating point
:Range: 0.0 to 1.0 :Range: 0.0 to 1.0 for each channel
:Default: empty :Default: 0.45 0.5 0.8 1 (blue)
This setting overrides the colour of dialogue topics that have a response unique to the actors speaking. This setting overrides the colour of dialogue topics that have a response unique to the actors speaking.
The value is composed of four floating point values representing the red, green, blue and alpha channels. The value is composed of four floating point values representing the red, green, blue and alpha channels.
@ -166,15 +166,67 @@ The alpha value is currently ignored.
A topic response is considered unique if its Actor filter field contains the speaking actor's object ID and hasn't yet been read. A topic response is considered unique if its Actor filter field contains the speaking actor's object ID and hasn't yet been read.
color topic specific over
-------------------------
:Type: RGBA floating point
:Range: 0.0 to 1.0 for each channel
:Default: 0.6 0.6 0.85 1 (blue)
This setting provides an "over" colour to dialogue topics that meet the color topic specific criteria.
The value is composed of four floating point values representing the red, green, blue and alpha channels.
The alpha value is currently ignored.
A dialogue topic is considered "over" if it is the active GUI element through keyboard or mouse events.
color topic specific pressed
----------------------------
:Type: RGBA floating point
:Range: 0.0 to 1.0 for each channel
:Default: 0.3 0.35 0.75 1 (blue)
This setting provides an "pressed" colour to dialogue topics that meet the color topic specific criteria.
The value is composed of four floating point values representing the red, green, blue and alpha channels.
The alpha value is currently ignored.
A dialogue topic is considered "pressed" if it is the active GUI element and it receives a sustained keyboard or mouse event.
color topic exhausted color topic exhausted
--------------------- ---------------------
:Type: RGBA floating point :Type: RGBA floating point
:Range: 0.0 to 1.0 :Range: 0.0 to 1.0 for each channel
:Default: empty :Default: 0.3 0.3 0.3 1 (grey)
This setting overrides the colour of dialogue topics which have been "exhausted" by the player. This setting overrides the colour of dialogue topics which have been "exhausted" by the player.
The value is composed of four floating point values representing the red, green, blue and alpha channels. The value is composed of four floating point values representing the red, green, blue and alpha channels.
The alpha value is currently ignored. The alpha value is currently ignored.
A topic is considered "exhausted" if the response the player is about to see has already been seen. A topic is considered "exhausted" if the response the player is about to see has already been seen.
color topic exhausted over
--------------------------
:Type: RGBA floating point
:Range: 0.0 to 1.0 for each channel
:Default: 0.55 0.55 0.55 1 (grey)
This setting provides an "over" colour to dialogue topics that meet the color topic exhausted criteria.
The value is composed of four floating point values representing the red, green, blue and alpha channels.
The alpha value is currently ignored.
A dialogue topic is considered "over" if it is the active GUI element through keyboard or mouse events.
color topic exhausted pressed
-----------------------------
:Type: RGBA floating point
:Range: 0.0 to 1.0 for each channel
:Default: 0.45 0.45 0.45 1 (grey)
This setting provides a "pressed" colour to dialogue topics that meet the color topic exhausted criteria.
The value is composed of four floating point values representing the red, green, blue and alpha channels.
The alpha value is currently ignored.
A dialogue topic is considered "pressed" if it is the active GUI element and it receives a sustained keyboard or mouse event.

View file

@ -1,5 +1,5 @@
Camera: "Caméra dOpenMW" Camera: "OpenMW : Caméra"
settingsPageDescription: "Configuration de la caméra dOpenMW" settingsPageDescription: "Paramètres de la caméra dOpenMW"
thirdPersonSettings: "Vue à la troisième personne" thirdPersonSettings: "Vue à la troisième personne"

View file

@ -1,19 +1,19 @@
ControlsPage: "OpenMW : Contrôles" ControlsPage: "OpenMW : Contrôles"
ControlsPageDescription: "Paramètres additionnels de contrôle" ControlsPageDescription: "Paramètres additionnels des contrôles d'OpenMW"
MovementSettings: "Mouvements" MovementSettings: "Mouvements"
alwaysRun: "Course permanente" alwaysRun: "Course permanente"
alwaysRunDescription: | alwaysRunDescription: |
Actif : Le personnage se déplace par défaut en courant ; \n\n Actif : Le personnage se déplace par défaut en courant ;
Inactif : Le personnage se déplace par défaut en marchant.\n\n Inactif : Le personnage se déplace par défaut en marchant.
La touche Maj. inverse temporairement ce paramètre.\n\n La touche Maj. inverse temporairement ce paramètre.
La touche Verr Maj inverse ce paramètre lorsqu'elle est verrouillée. La touche Verr Maj inverse ce paramètre lorsqu'elle est verrouillée.
toggleSneak: "Mode discrétion maintenu" toggleSneak: "Mode discrétion maintenu"
toggleSneakDescription: | toggleSneakDescription: |
Une simple pression de la touche associée (Ctrl par défaut) active le mode discrétion, une seconde pression désactive le mode discrétion.\n\n Une simple pression de la touche associée (Ctrl par défaut) active le mode discrétion, une seconde pression désactive le mode discrétion.
Il n'est plus nécessaire de maintenir une touche appuyée pour que le mode discrétion soit actif.\n\n Il n'est plus nécessaire de maintenir une touche appuyée pour que le mode discrétion soit actif.
Certains joueurs ayant une utilisation intensive du mode discrétion considèrent qu'il est plus aisé de contrôler leur personnage ainsi. Certains joueurs ayant une utilisation intensive du mode discrétion considèrent qu'il est plus aisé de contrôler leur personnage ainsi.
smoothControllerMovement: "Mouvements à la manette adoucis" smoothControllerMovement: "Mouvements à la manette adoucis"

View file

@ -1,7 +1,7 @@
#Music: "OpenMW Music" Music: "OpenMW : Musique"
#settingsPageDescription: "OpenMW Music settings" settingsPageDescription: "Paramètre de la musique d'OpenMW"
#musicSettings: "Music configuration" musicSettings: "Configuration de la musique"
#CombatMusicEnabled: "Play combat music" CombatMusicEnabled: "Jouer la musique de combat"
#CombatMusicEnabledDescription: "Whether to switch to combat music when there are actors in combat." CombatMusicEnabledDescription: "Si activé, le jeu bascule vers la musique de combat dès qu'un personnage est en combat."

View file

@ -124,6 +124,28 @@
</BasisSkin> </BasisSkin>
</Resource> </Resource>
<Resource type="ResourceSkin" name="MW_ListLine_Specific" size="5 5">
<Property key="FontName" value="Default"/>
<Property key="TextAlign" value="Left VCenter"/>
<BasisSkin type="SimpleText" offset="2 0 1 5" align="Stretch">
<State name="normal" colour="#{setting=GUI,color topic specific}"/>
<State name="highlighted" colour="#{setting=GUI,color topic specific over}"/>
<State name="pushed" colour="#{setting=GUI,color topic specific pressed}"/>
</BasisSkin>
</Resource>
<Resource type="ResourceSkin" name="MW_ListLine_Exhausted" size="5 5">
<Property key="FontName" value="Default"/>
<Property key="TextAlign" value="Left VCenter"/>
<BasisSkin type="SimpleText" offset="2 0 1 5" align="Stretch">
<State name="normal" colour="#{setting=GUI,color topic exhausted}"/>
<State name="highlighted" colour="#{setting=GUI,color topic exhausted over}"/>
<State name="pushed" colour="#{setting=GUI,color topic exhausted pressed}"/>
</BasisSkin>
</Resource>
<Resource type="ResourceLayout" name="MW_List" size="516 516" align="Left Top"> <Resource type="ResourceLayout" name="MW_List" size="516 516" align="Left Top">
<Widget type="Widget" position="0 0 516 516" name="Root"> <Widget type="Widget" position="0 0 516 516" name="Root">
<Property key="NeedKey" value="true"/> <Property key="NeedKey" value="true"/>

View file

@ -24,7 +24,7 @@ local function onUpdate()
-- Early-out for actors without targets and without combat state -- Early-out for actors without targets and without combat state
-- TODO: use events or engine handlers to detect when targets change -- TODO: use events or engine handlers to detect when targets change
local isStanceNothing = types.Actor.getStance(self) == types.Actor.STANCE.Nothing local isStanceNothing = types.Actor.getStance(self) == types.Actor.STANCE.Nothing
if isStanceNothing and next(targets) == nil then if isStanceNothing and next(targets) == nil and not AI.isFleeing() then
return return
end end

View file

@ -1,6 +1,6 @@
--- ---
-- `openmw.ambient` controls background sounds, specific to given player (2D-sounds). -- `openmw.ambient` controls background 2D sounds specific to a given player.
-- Can be used only by menu scripts and local scripts, that are attached to a player. -- Can be used only by menu scripts and local scripts that are attached to a player.
-- @module ambient -- @module ambient
-- @usage local ambient = require('openmw.ambient') -- @usage local ambient = require('openmw.ambient')
@ -12,11 +12,11 @@
-- @param #string soundId ID of Sound record to play -- @param #string soundId ID of Sound record to play
-- @param #table options An optional table with additional optional arguments. Can contain: -- @param #table options An optional table with additional optional arguments. Can contain:
-- --
-- * `timeOffset` - a floating point number >= 0, to skip some time (in seconds) from beginning of sound file (default: 0); -- * `timeOffset` - a floating point number >= 0, to skip some time (in seconds) from the beginning of the sound (default: 0);
-- * `volume` - a floating point number >= 0, to set a sound volume (default: 1); -- * `volume` - a floating point number >= 0, to set the sound's volume (default: 1);
-- * `pitch` - a floating point number >= 0, to set a sound pitch (default: 1); -- * `pitch` - a floating point number >= 0, to set the sound's pitch (default: 1);
-- * `scale` - a boolean, to set if sound pitch should be scaled by simulation time scaling (default: true); -- * `scale` - a boolean, to set if the sound's 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); -- * `loop` - a boolean, to set if the sound should be repeated when it ends (default: false);
-- @usage local params = { -- @usage local params = {
-- timeOffset=0.1 -- timeOffset=0.1
-- volume=0.3, -- volume=0.3,
@ -29,14 +29,14 @@
--- ---
-- Play a 2D sound file -- Play a 2D sound file
-- @function [parent=#ambient] playSoundFile -- @function [parent=#ambient] playSoundFile
-- @param #string fileName Path to sound file in VFS -- @param #string fileName Path to a sound file in VFS
-- @param #table options An optional table with additional optional arguments. Can contain: -- @param #table options An optional table with additional optional arguments. Can contain:
-- --
-- * `timeOffset` - a floating point number >= 0, to skip some time (in seconds) from beginning of sound file (default: 0); -- * `timeOffset` - a floating point number >= 0, to skip some time (in seconds) from the beginning of the sound file (default: 0);
-- * `volume` - a floating point number >= 0, to set a sound volume (default: 1); -- * `volume` - a floating point number >= 0, to set the sound's volume (default: 1);
-- * `pitch` - a floating point number >= 0, to set a sound pitch (default: 1); -- * `pitch` - a floating point number >= 0, to set the sound's pitch (default: 1);
-- * `scale` - a boolean, to set if sound pitch should be scaled by simulation time scaling (default: true); -- * `scale` - a boolean, to set if the sound's 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); -- * `loop` - a boolean, to set if the sound should be repeated when it ends (default: false);
-- @usage local params = { -- @usage local params = {
-- timeOffset=0.1 -- timeOffset=0.1
-- volume=0.3, -- volume=0.3,
@ -55,37 +55,37 @@
--- ---
-- Stop a sound file -- Stop a sound file
-- @function [parent=#ambient] stopSoundFile -- @function [parent=#ambient] stopSoundFile
-- @param #string fileName Path to sound file in VFS -- @param #string fileName Path to a sound file in VFS
-- @usage ambient.stopSoundFile("Sound\\test.mp3"); -- @usage ambient.stopSoundFile("Sound\\test.mp3");
--- ---
-- Check if sound is playing -- Check if a sound is playing
-- @function [parent=#ambient] isSoundPlaying -- @function [parent=#ambient] isSoundPlaying
-- @param #string soundId ID of Sound record to check -- @param #string soundId ID of Sound record to check
-- @return #boolean -- @return #boolean
-- @usage local isPlaying = ambient.isSoundPlaying("shock bolt"); -- @usage local isPlaying = ambient.isSoundPlaying("shock bolt");
--- ---
-- Check if sound file is playing -- Check if a sound file is playing
-- @function [parent=#ambient] isSoundFilePlaying -- @function [parent=#ambient] isSoundFilePlaying
-- @param #string fileName Path to sound file in VFS -- @param #string fileName Path to a sound file in VFS
-- @return #boolean -- @return #boolean
-- @usage local isPlaying = ambient.isSoundFilePlaying("Sound\\test.mp3"); -- @usage local isPlaying = ambient.isSoundFilePlaying("Sound\\test.mp3");
--- ---
-- Play a sound file as a music track -- Play a sound file as a music track
-- @function [parent=#ambient] streamMusic -- @function [parent=#ambient] streamMusic
-- @param #string fileName Path to file in VFS -- @param #string fileName Path to a file in VFS
-- @param #table options An optional table with additional optional arguments. Can contain: -- @param #table options An optional table with additional optional arguments. Can contain:
-- --
-- * `fadeOut` - a floating point number >= 0, time (in seconds) to fade out current track before playing this one (default 1.0); -- * `fadeOut` - a floating point number >= 0, time (in seconds) to fade out the current track before playing this one (default 1.0);
-- @usage local params = { -- @usage local params = {
-- fadeOut=2.0 -- fadeOut=2.0
-- }; -- };
-- ambient.streamMusic("Music\\Test\\Test.mp3", params) -- ambient.streamMusic("Music\\Test\\Test.mp3", params)
--- ---
-- Stop to play current music -- Stop the currently playing music
-- @function [parent=#ambient] stopMusic -- @function [parent=#ambient] stopMusic
-- @usage ambient.stopMusic(); -- @usage ambient.stopMusic();
@ -98,7 +98,7 @@
--- ---
-- Play an ambient voiceover. -- Play an ambient voiceover.
-- @function [parent=#ambient] say -- @function [parent=#ambient] say
-- @param #string fileName Path to sound file in VFS -- @param #string fileName Path to a sound file in VFS
-- @param #string text Subtitle text (optional) -- @param #string text Subtitle text (optional)
-- @usage -- play voiceover and print messagebox -- @usage -- play voiceover and print messagebox
-- ambient.say("Sound\\Vo\\Misc\\voice.mp3", "Subtitle text") -- ambient.say("Sound\\Vo\\Misc\\voice.mp3", "Subtitle text")
@ -108,7 +108,7 @@
--- ---
-- Stop an ambient voiceover -- Stop an ambient voiceover
-- @function [parent=#ambient] stopSay -- @function [parent=#ambient] stopSay
-- @param #string fileName Path to sound file in VFS -- @param #string fileName Path to a sound file in VFS
-- @usage ambient.stopSay(); -- @usage ambient.stopSay();
--- ---

View file

@ -1,5 +1,5 @@
--- ---
-- `openmw.animation` defines functions that allow control of character animations -- `openmw.animation` defines functions that allow control of character animations.
-- Note that for some methods, such as @{openmw.animation#playBlended} you should use the associated methods on the -- Note that for some methods, such as @{openmw.animation#playBlended} you should use the associated methods on the
-- [AnimationController](interface_animation.html) interface rather than invoking this API directly. -- [AnimationController](interface_animation.html) interface rather than invoking this API directly.
-- @module animation -- @module animation
@ -55,7 +55,7 @@
-- @return #boolean -- @return #boolean
--- ---
-- Skips animations for one frame, equivalent to mwscript's SkipAnim -- Skips animations for one frame, equivalent to mwscript's SkipAnim.
-- Can be used only in local scripts on self. -- Can be used only in local scripts on self.
-- @function [parent=#animation] skipAnimationThisFrame -- @function [parent=#animation] skipAnimationThisFrame
-- @param openmw.core#GameObject actor -- @param openmw.core#GameObject actor
@ -98,7 +98,7 @@
--- ---
-- Cancels and removes the animation group from the list of active animations -- Cancels and removes the animation group from the list of active animations.
-- Can be used only in local scripts on self. -- Can be used only in local scripts on self.
-- @function [parent=#animation] cancel -- @function [parent=#animation] cancel
-- @param openmw.core#GameObject actor -- @param openmw.core#GameObject actor

View file

@ -1,5 +1,5 @@
--- ---
-- `openmw.async` contains timers and coroutine utils. All functions require -- `openmw.async` contains timers and coroutine utilities. All functions require
-- the package itself as a first argument. -- the package itself as a first argument.
-- @module async -- @module async
-- @usage local async = require('openmw.async') -- @usage local async = require('openmw.async')
@ -16,7 +16,7 @@
--- ---
-- Calls callback(arg) in `delay` simulation seconds. -- Calls callback(arg) in `delay` simulation seconds.
-- Callback must be registered in advance. -- The callback must be registered in advance.
-- @function [parent=#async] newSimulationTimer -- @function [parent=#async] newSimulationTimer
-- @param self -- @param self
-- @param #number delay -- @param #number delay
@ -25,7 +25,7 @@
--- ---
-- Calls callback(arg) in `delay` game seconds. -- Calls callback(arg) in `delay` game seconds.
-- Callback must be registered in advance. -- The callback must be registered in advance.
-- @function [parent=#async] newGameTimer -- @function [parent=#async] newGameTimer
-- @param self -- @param self
-- @param #number delay -- @param #number delay
@ -49,7 +49,7 @@
-- @param #function func -- @param #function func
--- ---
-- Wraps Lua function with `Callback` object that can be used in async API calls. -- Wraps a Lua function with a `Callback` object that can be used in async API calls.
-- @function [parent=#async] callback -- @function [parent=#async] callback
-- @param self -- @param self
-- @param #function func -- @param #function func

View file

@ -218,14 +218,14 @@ keyboard navigation = true
color topic enable = false color topic enable = false
# The color of dialogue topic keywords that gives unique actor responses # The color of dialogue topic keywords that gives unique actor responses
# Format R G B A or empty for no special formatting
# Default to blue
color topic specific = 0.45 0.5 0.8 1 color topic specific = 0.45 0.5 0.8 1
color topic specific over = 0.6 0.6 0.85 1
color topic specific pressed = 0.3 0.35 0.75 1
# The color of dialogue topic keywords that gives already read responses # The color of dialogue topic keywords that gives already read responses
# Format R G B A or empty for no special formatting
# Default to grey
color topic exhausted = 0.3 0.3 0.3 1 color topic exhausted = 0.3 0.3 0.3 1
color topic exhausted over = 0.55 0.55 0.55 1
color topic exhausted pressed = 0.45 0.45 0.45 1
[HUD] [HUD]