diff --git a/.gitignore b/.gitignore index f25adf58e6..39033bd725 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ Doxygen .idea cmake-build-* files/windows/*.aps +.cache/clangd ## qt-creator CMakeLists.txt.user* .vs diff --git a/CHANGELOG.md b/CHANGELOG.md index 09cde80181..1afca19c6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Bug #4754: Stack of ammunition cannot be equipped partially Bug #4816: GetWeaponDrawn returns 1 before weapon is attached Bug #5057: Weapon swing sound plays at same pitch whether it hits or misses + Bug #5062: Root bone rotations for NPC animation don't work the same as for creature animation Bug #5129: Stuttering animation on Centurion Archer Bug #5280: Unskinned shapes in skinned equipment are rendered in the wrong place Bug #5371: Keyframe animation tracks are used for any file that begins with an X @@ -24,6 +25,7 @@ Bug #5977: Fatigueless NPCs' corpse underwater changes animation on game load Bug #6025: Subrecords cannot overlap records Bug #6027: Collisionshape becomes spiderweb-like when the mesh is too complex + Bug #6190: Unintuitive sun specularity time of day dependence Bug #6222: global map cell size can crash openmw if set to too high a value Bug #6313: Followers with high Fight can turn hostile Bug #6427: Enemy health bar disappears before damaging effect ends @@ -68,6 +70,7 @@ Bug #7298: Water ripples from projectiles sometimes are not spawned Bug #7307: Alchemy "Magic Effect" search string does not match on tool tip for effects related to attributes Bug #7322: Shadows don't cover groundcover depending on the view angle and perspective with compute scene bounds = primitives + Bug #7380: NiZBufferProperty issue Bug #7413: Generated wilderness cells don't spawn fish Bug #7415: Unbreakable lock discrepancies Bug #7428: AutoCalc flag is not used to calculate enchantment costs @@ -90,11 +93,15 @@ Bug #7639: NPCs don't use hand-to-hand if their other melee skills were damaged during combat Bug #7642: Items in repair and recharge menus aren't sorted alphabetically Bug #7647: NPC walk cycle bugs after greeting player + Bug #7654: Tooltips for enchantments with invalid effects cause crashes Bug #7660: Some inconsistencies regarding Invisibility breaking + Bug #7675: Successful lock spell doesn't produce a sound + Bug #7679: Scene luminance value flashes when toggling shaders Feature #3537: Shader-based water ripples Feature #5492: Let rain and snow collide with statics Feature #6149: Dehardcode Lua API_REVISION Feature #6152: Playing music via lua scripts + Feature #6188: Specular lighting from point light sources Feature #6447: Add LOD support to Object Paging Feature #6491: Add support for Qt6 Feature #6556: Lua API for sounds diff --git a/CI/install_debian_deps.sh b/CI/install_debian_deps.sh index bd767bb173..8d7c0a493f 100755 --- a/CI/install_debian_deps.sh +++ b/CI/install_debian_deps.sh @@ -86,7 +86,7 @@ declare -rA GROUPED_DEPS=( libswresample3 libswscale5 libtinyxml2.6.2v5 - libyaml-cpp0.7 + libyaml-cpp0.8 python3-pip xvfb " @@ -125,3 +125,4 @@ add-apt-repository -y ppa:openmw/openmw add-apt-repository -y ppa:openmw/openmw-daily add-apt-repository -y ppa:openmw/staging apt-get -qq -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends "${deps[@]}" >/dev/null +apt list --installed diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index e293055919..96c418c0c4 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -1204,7 +1204,8 @@ namespace EsmTool std::array weathers = { "Clear", "Cloudy", "Fog", "Overcast", "Rain", "Thunder", "Ash", "Blight", "Snow", "Blizzard" }; for (size_t i = 0; i < weathers.size(); ++i) - std::cout << " " << weathers[i] << ": " << mData.mData.mProbabilities[i] << std::endl; + std::cout << " " << weathers[i] << ": " << static_cast(mData.mData.mProbabilities[i]) + << std::endl; std::cout << " Map Color: " << mData.mMapColor << std::endl; if (!mData.mSleepList.empty()) std::cout << " Sleep List: " << mData.mSleepList << std::endl; diff --git a/apps/essimporter/convertinventory.cpp b/apps/essimporter/convertinventory.cpp index 2f03cfaf41..69a2ea4120 100644 --- a/apps/essimporter/convertinventory.cpp +++ b/apps/essimporter/convertinventory.cpp @@ -9,15 +9,14 @@ namespace ESSImport void convertInventory(const Inventory& inventory, ESM::InventoryState& state) { - int index = 0; + uint32_t index = 0; for (const auto& item : inventory.mItems) { ESM::ObjectState objstate; objstate.blank(); objstate.mRef = item; objstate.mRef.mRefID = ESM::RefId::stringRefId(item.mId); - objstate.mCount = std::abs(item.mCount); // restocking items have negative count in the savefile - // openmw handles them differently, so no need to set any flags + objstate.mCount = item.mCount; state.mItems.push_back(objstate); if (item.mRelativeEquipmentSlot != -1) // Note we should really write the absolute slot here, which we do not know about diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index b6192d3c02..4d3f0cc64f 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -26,7 +26,7 @@ #include #include #include -#include +#include #include #include "utils/profilescombobox.hpp" @@ -123,7 +123,7 @@ namespace Launcher int getMaxNavMeshDbFileSizeMiB() { - return Settings::Manager::getUInt64("max navmeshdb file size", "Navigator") / (1024 * 1024); + return Settings::navigator().mMaxNavmeshdbFileSize / (1024 * 1024); } std::optional findFirstPath(const QStringList& directories, const QString& fileName) @@ -359,9 +359,8 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) void Launcher::DataFilesPage::saveSettings(const QString& profile) { - if (const int value = ui.navMeshMaxSizeSpinBox->value(); value != getMaxNavMeshDbFileSizeMiB()) - Settings::Manager::setUInt64( - "max navmeshdb file size", "Navigator", static_cast(std::max(0, value)) * 1024 * 1024); + Settings::navigator().mMaxNavmeshdbFileSize.set( + static_cast(std::max(0, ui.navMeshMaxSizeSpinBox->value())) * 1024 * 1024); QString profileName = profile; diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 18fb57805f..952e0c9349 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -158,32 +158,32 @@ bool Launcher::GraphicsPage::loadSettings() lightingMethodComboBox->setCurrentIndex(lightingMethod); // Shadows - if (Settings::Manager::getBool("actor shadows", "Shadows")) + if (Settings::shadows().mActorShadows) actorShadowsCheckBox->setCheckState(Qt::Checked); - if (Settings::Manager::getBool("player shadows", "Shadows")) + if (Settings::shadows().mPlayerShadows) playerShadowsCheckBox->setCheckState(Qt::Checked); - if (Settings::Manager::getBool("terrain shadows", "Shadows")) + if (Settings::shadows().mTerrainShadows) terrainShadowsCheckBox->setCheckState(Qt::Checked); - if (Settings::Manager::getBool("object shadows", "Shadows")) + if (Settings::shadows().mObjectShadows) objectShadowsCheckBox->setCheckState(Qt::Checked); - if (Settings::Manager::getBool("enable indoor shadows", "Shadows")) + if (Settings::shadows().mEnableIndoorShadows) indoorShadowsCheckBox->setCheckState(Qt::Checked); - shadowComputeSceneBoundsComboBox->setCurrentIndex(shadowComputeSceneBoundsComboBox->findText( - QString(tr(Settings::Manager::getString("compute scene bounds", "Shadows").c_str())))); + shadowComputeSceneBoundsComboBox->setCurrentIndex( + shadowComputeSceneBoundsComboBox->findText(QString(tr(Settings::shadows().mComputeSceneBounds.get().c_str())))); - int shadowDistLimit = Settings::Manager::getInt("maximum shadow map distance", "Shadows"); + const int shadowDistLimit = Settings::shadows().mMaximumShadowMapDistance; if (shadowDistLimit > 0) { shadowDistanceCheckBox->setCheckState(Qt::Checked); shadowDistanceSpinBox->setValue(shadowDistLimit); } - float shadowFadeStart = Settings::Manager::getFloat("shadow fade start", "Shadows"); + const float shadowFadeStart = Settings::shadows().mShadowFadeStart; if (shadowFadeStart != 0) fadeStartSpinBox->setValue(shadowFadeStart); - int shadowRes = Settings::Manager::getInt("shadow map resolution", "Shadows"); + const int shadowRes = Settings::shadows().mShadowMapResolution; int shadowResIndex = shadowResolutionComboBox->findText(QString::number(shadowRes)); if (shadowResIndex != -1) shadowResolutionComboBox->setCurrentIndex(shadowResIndex); @@ -240,55 +240,36 @@ void Launcher::GraphicsPage::saveSettings() Settings::shaders().mLightingMethod.set(lightingMethodMap[lightingMethodComboBox->currentIndex()]); // Shadows - int cShadowDist = shadowDistanceCheckBox->checkState() != Qt::Unchecked ? shadowDistanceSpinBox->value() : 0; - if (Settings::Manager::getInt("maximum shadow map distance", "Shadows") != cShadowDist) - Settings::Manager::setInt("maximum shadow map distance", "Shadows", cShadowDist); - float cFadeStart = fadeStartSpinBox->value(); - if (cShadowDist > 0 && Settings::Manager::getFloat("shadow fade start", "Shadows") != cFadeStart) - Settings::Manager::setFloat("shadow fade start", "Shadows", cFadeStart); + const int cShadowDist = shadowDistanceCheckBox->checkState() != Qt::Unchecked ? shadowDistanceSpinBox->value() : 0; + Settings::shadows().mMaximumShadowMapDistance.set(cShadowDist); + const float cFadeStart = fadeStartSpinBox->value(); + if (cShadowDist > 0) + Settings::shadows().mShadowFadeStart.set(cFadeStart); - bool cActorShadows = actorShadowsCheckBox->checkState(); - bool cObjectShadows = objectShadowsCheckBox->checkState(); - bool cTerrainShadows = terrainShadowsCheckBox->checkState(); - bool cPlayerShadows = playerShadowsCheckBox->checkState(); + const bool cActorShadows = actorShadowsCheckBox->checkState() != Qt::Unchecked; + const bool cObjectShadows = objectShadowsCheckBox->checkState() != Qt::Unchecked; + const bool cTerrainShadows = terrainShadowsCheckBox->checkState() != Qt::Unchecked; + const bool cPlayerShadows = playerShadowsCheckBox->checkState() != Qt::Unchecked; if (cActorShadows || cObjectShadows || cTerrainShadows || cPlayerShadows) { - if (!Settings::Manager::getBool("enable shadows", "Shadows")) - Settings::Manager::setBool("enable shadows", "Shadows", true); - if (Settings::Manager::getBool("actor shadows", "Shadows") != cActorShadows) - Settings::Manager::setBool("actor shadows", "Shadows", cActorShadows); - if (Settings::Manager::getBool("player shadows", "Shadows") != cPlayerShadows) - Settings::Manager::setBool("player shadows", "Shadows", cPlayerShadows); - if (Settings::Manager::getBool("object shadows", "Shadows") != cObjectShadows) - Settings::Manager::setBool("object shadows", "Shadows", cObjectShadows); - if (Settings::Manager::getBool("terrain shadows", "Shadows") != cTerrainShadows) - Settings::Manager::setBool("terrain shadows", "Shadows", cTerrainShadows); + Settings::shadows().mEnableShadows.set(true); + Settings::shadows().mActorShadows.set(cActorShadows); + Settings::shadows().mPlayerShadows.set(cPlayerShadows); + Settings::shadows().mObjectShadows.set(cObjectShadows); + Settings::shadows().mTerrainShadows.set(cTerrainShadows); } else { - if (Settings::Manager::getBool("enable shadows", "Shadows")) - Settings::Manager::setBool("enable shadows", "Shadows", false); - if (Settings::Manager::getBool("actor shadows", "Shadows")) - Settings::Manager::setBool("actor shadows", "Shadows", false); - if (Settings::Manager::getBool("player shadows", "Shadows")) - Settings::Manager::setBool("player shadows", "Shadows", false); - if (Settings::Manager::getBool("object shadows", "Shadows")) - Settings::Manager::setBool("object shadows", "Shadows", false); - if (Settings::Manager::getBool("terrain shadows", "Shadows")) - Settings::Manager::setBool("terrain shadows", "Shadows", false); + Settings::shadows().mEnableShadows.set(false); + Settings::shadows().mActorShadows.set(false); + Settings::shadows().mPlayerShadows.set(false); + Settings::shadows().mObjectShadows.set(false); + Settings::shadows().mTerrainShadows.set(false); } - bool cIndoorShadows = indoorShadowsCheckBox->checkState(); - if (Settings::Manager::getBool("enable indoor shadows", "Shadows") != cIndoorShadows) - Settings::Manager::setBool("enable indoor shadows", "Shadows", cIndoorShadows); - - int cShadowRes = shadowResolutionComboBox->currentText().toInt(); - if (cShadowRes != Settings::Manager::getInt("shadow map resolution", "Shadows")) - Settings::Manager::setInt("shadow map resolution", "Shadows", cShadowRes); - - auto cComputeSceneBounds = shadowComputeSceneBoundsComboBox->currentText().toStdString(); - if (cComputeSceneBounds != Settings::Manager::getString("compute scene bounds", "Shadows")) - Settings::Manager::setString("compute scene bounds", "Shadows", cComputeSceneBounds); + Settings::shadows().mEnableIndoorShadows.set(indoorShadowsCheckBox->checkState() != Qt::Unchecked); + Settings::shadows().mShadowMapResolution.set(shadowResolutionComboBox->currentText().toInt()); + Settings::shadows().mComputeSceneBounds.set(shadowComputeSceneBoundsComboBox->currentText().toStdString()); } QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen) diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp index 5869cc3a73..b8539671b5 100644 --- a/apps/launcher/settingspage.cpp +++ b/apps/launcher/settingspage.cpp @@ -191,6 +191,7 @@ bool Launcher::SettingsPage::loadSettings() } loadSettingBool(Settings::game().mTurnToMovementDirection, *turnToMovementDirectionCheckBox); loadSettingBool(Settings::game().mSmoothMovement, *smoothMovementCheckBox); + loadSettingBool(Settings::game().mPlayerMovementIgnoresAnimation, *playerMovementIgnoresAnimationCheckBox); distantLandCheckBox->setCheckState( Settings::terrain().mDistantTerrain && Settings::terrain().mObjectPaging ? Qt::Checked : Qt::Unchecked); @@ -338,6 +339,7 @@ void Launcher::SettingsPage::saveSettings() saveSettingBool(*shieldSheathingCheckBox, Settings::game().mShieldSheathing); saveSettingBool(*turnToMovementDirectionCheckBox, Settings::game().mTurnToMovementDirection); saveSettingBool(*smoothMovementCheckBox, Settings::game().mSmoothMovement); + saveSettingBool(*playerMovementIgnoresAnimationCheckBox, Settings::game().mPlayerMovementIgnoresAnimation); const bool wantDistantLand = distantLandCheckBox->checkState() == Qt::Checked; if (wantDistantLand != (Settings::terrain().mDistantTerrain && Settings::terrain().mObjectPaging)) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index c8c1ada5a9..92483bd8c3 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -318,9 +318,14 @@ bool OMW::Engine::frame(float frametime) mViewer->eventTraversal(); mViewer->updateTraversal(); + // update GUI by world data { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); - mWorld->updateWindowManager(); + + if (mStateManager->getState() != MWBase::StateManager::State_NoGame) + { + mWorld->updateWindowManager(); + } } mLuaWorker->allowUpdate(); // if there is a separate Lua thread, it starts the update now diff --git a/apps/openmw/mwclass/esm4npc.cpp b/apps/openmw/mwclass/esm4npc.cpp index 78cbd89b50..638144eb66 100644 --- a/apps/openmw/mwclass/esm4npc.cpp +++ b/apps/openmw/mwclass/esm4npc.cpp @@ -34,11 +34,26 @@ namespace MWClass static const ESM4::Npc* chooseTemplate(const std::vector& recs, uint16_t flag) { - // In case of FO3 the function may return nullptr that will lead to "ESM4 NPC traits not found" - // exception and the NPC will not be added to the scene. But in any way it shouldn't cause a crash. for (const auto* rec : recs) - if (rec->mIsTES4 || rec->mIsFONV || !(rec->mBaseConfig.tes5.templateFlags & flag)) + { + if (rec->mIsTES4) return rec; + else if (rec->mIsFONV) + { + // TODO: FO3 should use this branch as well. But it is not clear how to distinguish FO3 from + // TES5. Currently FO3 uses wrong template flags that can lead to "ESM4 NPC traits not found" + // exception the NPC will not be added to the scene. But in any way it shouldn't cause a crash. + if (!(rec->mBaseConfig.fo3.templateFlags & flag)) + return rec; + } + else if (rec->mIsFO4) + { + if (!(rec->mBaseConfig.fo4.templateFlags & flag)) + return rec; + } + else if (!(rec->mBaseConfig.tes5.templateFlags & flag)) + return rec; + } return nullptr; } @@ -75,8 +90,8 @@ namespace MWClass const MWWorld::ESMStore* store = MWBase::Environment::get().getESMStore(); auto npcRecs = withBaseTemplates(ptr.get()->mBase); - data->mTraits = chooseTemplate(npcRecs, ESM4::Npc::TES5_UseTraits); - data->mBaseData = chooseTemplate(npcRecs, ESM4::Npc::TES5_UseBaseData); + data->mTraits = chooseTemplate(npcRecs, ESM4::Npc::Template_UseTraits); + data->mBaseData = chooseTemplate(npcRecs, ESM4::Npc::Template_UseBaseData); if (!data->mTraits) throw std::runtime_error("ESM4 NPC traits not found"); @@ -88,10 +103,13 @@ namespace MWClass data->mIsFemale = data->mTraits->mBaseConfig.tes4.flags & ESM4::Npc::TES4_Female; else if (data->mTraits->mIsFONV) data->mIsFemale = data->mTraits->mBaseConfig.fo3.flags & ESM4::Npc::FO3_Female; + else if (data->mTraits->mIsFO4) + data->mIsFemale + = data->mTraits->mBaseConfig.fo4.flags & ESM4::Npc::TES5_Female; // FO4 flags are the same as TES5 else data->mIsFemale = data->mTraits->mBaseConfig.tes5.flags & ESM4::Npc::TES5_Female; - if (auto inv = chooseTemplate(npcRecs, ESM4::Npc::TES5_UseInventory)) + if (auto inv = chooseTemplate(npcRecs, ESM4::Npc::Template_UseInventory)) { for (const ESM4::InventoryItem& item : inv->mInventory) { diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 610e34e3cd..932723c3fd 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -556,6 +556,20 @@ namespace MWGui std::unique_ptr action = ptr.getClass().use(ptr, force); action->execute(player); + // Handles partial equipping (final part) + if (mEquippedStackableCount.has_value()) + { + // the count to unequip + int count = ptr.getRefData().getCount() - mDragAndDrop->mDraggedCount - mEquippedStackableCount.value(); + if (count > 0) + { + MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr); + invStore.unequipItemQuantity(ptr, count); + updateItemView(); + } + mEquippedStackableCount.reset(); + } + if (isVisible()) { mItemView->update(); @@ -581,27 +595,21 @@ namespace MWGui } // Handles partial equipping - const std::pair, bool> slots = ptr.getClass().getEquipmentSlots(ptr); + mEquippedStackableCount.reset(); + const auto slots = ptr.getClass().getEquipmentSlots(ptr); if (!slots.first.empty() && slots.second) { - int equippedStackableCount = 0; MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr); MWWorld::ConstContainerStoreIterator slotIt = invStore.getSlot(slots.first.front()); - // Get the count before useItem() + // Save the currently equipped count before useItem() if (slotIt != invStore.end() && slotIt->getCellRef().getRefId() == ptr.getCellRef().getRefId()) - equippedStackableCount = slotIt->getRefData().getCount(); - - useItem(ptr); - int unequipCount = ptr.getRefData().getCount() - mDragAndDrop->mDraggedCount - equippedStackableCount; - if (unequipCount > 0) - { - invStore.unequipItemQuantity(ptr, unequipCount); - updateItemView(); - } + mEquippedStackableCount = slotIt->getRefData().getCount(); + else + mEquippedStackableCount = 0; } - else - MWBase::Environment::get().getLuaManager()->useItem(ptr, MWMechanics::getPlayer(), false); + + MWBase::Environment::get().getLuaManager()->useItem(ptr, MWMechanics::getPlayer(), false); // If item is ingredient or potion don't stop drag and drop to simplify action of taking more than one 1 // item diff --git a/apps/openmw/mwgui/inventorywindow.hpp b/apps/openmw/mwgui/inventorywindow.hpp index f3d8e3dcd6..9fc77ceec5 100644 --- a/apps/openmw/mwgui/inventorywindow.hpp +++ b/apps/openmw/mwgui/inventorywindow.hpp @@ -74,6 +74,7 @@ namespace MWGui DragAndDrop* mDragAndDrop; int mSelectedItem; + std::optional mEquippedStackableCount; MWWorld::Ptr mPtr; diff --git a/apps/openmw/mwgui/keyboardnavigation.cpp b/apps/openmw/mwgui/keyboardnavigation.cpp index a8fb52c95e..85c7d8ba88 100644 --- a/apps/openmw/mwgui/keyboardnavigation.cpp +++ b/apps/openmw/mwgui/keyboardnavigation.cpp @@ -183,6 +183,10 @@ namespace MWGui return switchFocus(D_Down, false); case MyGUI::KeyCode::Tab: return switchFocus(MyGUI::InputManager::getInstance().isShiftPressed() ? D_Prev : D_Next, true); + case MyGUI::KeyCode::Period: + return switchFocus(D_Prev, true); + case MyGUI::KeyCode::Slash: + return switchFocus(D_Next, true); case MyGUI::KeyCode::Return: case MyGUI::KeyCode::NumpadEnter: case MyGUI::KeyCode::Space: diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index 63e4fbc5cc..4f78c27f05 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -413,8 +413,8 @@ namespace MWGui text << Misc::fileTimeToString(mCurrentSlot->mTimeStamp, "%Y.%m.%d %T") << "\n"; if (mCurrentSlot->mProfile.mMaximumHealth > 0) - text << std::fixed << std::setprecision(0) << "#{sHealth} " << mCurrentSlot->mProfile.mCurrentHealth << "/" - << mCurrentSlot->mProfile.mMaximumHealth << "\n"; + text << "#{sHealth} " << static_cast(mCurrentSlot->mProfile.mCurrentHealth) << "/" + << static_cast(mCurrentSlot->mProfile.mMaximumHealth) << "\n"; text << "#{sLevel} " << mCurrentSlot->mProfile.mPlayerLevel << "\n"; text << "#{sCell=" << mCurrentSlot->mProfile.mPlayerCellName << "}\n"; diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp index debacfbed9..31e2689485 100644 --- a/apps/openmw/mwgui/widgets.cpp +++ b/apps/openmw/mwgui/widgets.cpp @@ -355,12 +355,10 @@ namespace MWGui::Widgets const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); - const ESM::MagicEffect* magicEffect = store.get().search(mEffectParams.mEffectID); + const ESM::MagicEffect* magicEffect = store.get().find(mEffectParams.mEffectID); const ESM::Attribute* attribute = store.get().search(mEffectParams.mAttribute); const ESM::Skill* skill = store.get().search(mEffectParams.mSkill); - assert(magicEffect); - auto windowManager = MWBase::Environment::get().getWindowManager(); std::string_view pt = windowManager->getGameSettingString("spoint", {}); diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 1d41bab34f..d98b7472bb 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1100,7 +1100,7 @@ namespace MWGui std::string_view settingSection = tag.substr(0, comma_pos); std::string_view settingTag = tag.substr(comma_pos + 1, tag.length()); - _result = Settings::Manager::getString(settingTag, settingSection); + _result = Settings::get(settingSection, settingTag).get().print(); } else if (tag.starts_with(tokenToFind)) { @@ -1115,7 +1115,7 @@ namespace MWGui else { std::vector split; - Misc::StringUtils::split(std::string{ tag }, split, ":"); + Misc::StringUtils::split(tag, split, ":"); l10n::Manager& l10nManager = *MWBase::Environment::get().getL10nManager(); diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 63a2838250..48b0c15381 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -344,6 +344,12 @@ namespace MWLua playerScripts->uiModeChanged(argId, false); } + void LuaManager::useItem(const MWWorld::Ptr& object, const MWWorld::Ptr& actor, bool force) + { + MWBase::Environment::get().getWorldModel()->registerPtr(object); + mEngineEvents.addToQueue(EngineEvents::OnUseItem{ getId(actor), getId(object), force }); + } + void LuaManager::objectAddedToScene(const MWWorld::Ptr& ptr) { mObjectLists.objectAddedToScene(ptr); // assigns generated RefNum if it is not set yet. diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index a725761dbd..404820cc6b 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -77,10 +77,7 @@ namespace MWLua { mEngineEvents.addToQueue(EngineEvents::OnActivate{ getId(actor), getId(object) }); } - void useItem(const MWWorld::Ptr& object, const MWWorld::Ptr& actor, bool force) override - { - mEngineEvents.addToQueue(EngineEvents::OnUseItem{ getId(actor), getId(object), force }); - } + void useItem(const MWWorld::Ptr& object, const MWWorld::Ptr& actor, bool force) override; void exteriorCreated(MWWorld::CellStore& cell) override { mEngineEvents.addToQueue(EngineEvents::OnNewExterior{ cell }); diff --git a/apps/openmw/mwlua/mwscriptbindings.cpp b/apps/openmw/mwlua/mwscriptbindings.cpp index 92957efb71..af88249d3e 100644 --- a/apps/openmw/mwlua/mwscriptbindings.cpp +++ b/apps/openmw/mwlua/mwscriptbindings.cpp @@ -140,7 +140,7 @@ namespace MWLua = sol::overload([](const GlobalStore& store, std::string_view globalId, float val) { auto g = store.search(ESM::RefId::deserializeText(globalId)); if (g == nullptr) - return; + throw std::runtime_error("No variable \"" + std::string(globalId) + "\" in GlobalStore"); char varType = MWBase::Environment::get().getWorld()->getGlobalVariableType(globalId); if (varType == 's' || varType == 'l') { diff --git a/apps/openmw/mwlua/stats.cpp b/apps/openmw/mwlua/stats.cpp index 7e8cbb1b5e..03822fd92a 100644 --- a/apps/openmw/mwlua/stats.cpp +++ b/apps/openmw/mwlua/stats.cpp @@ -457,6 +457,9 @@ namespace MWLua return nullptr; return &*rec.mSchool; }); + skillT["attribute"] = sol::readonly_property([](const ESM::Skill& rec) -> std::string { + return ESM::Attribute::indexToRefId(rec.mData.mAttribute).serializeText(); + }); auto schoolT = context.mLua->sol().new_usertype("MagicSchool"); schoolT[sol::meta_function::to_string] diff --git a/apps/openmw/mwlua/types/actor.cpp b/apps/openmw/mwlua/types/actor.cpp index 2afe3386ce..370e9e7f69 100644 --- a/apps/openmw/mwlua/types/actor.cpp +++ b/apps/openmw/mwlua/types/actor.cpp @@ -395,6 +395,11 @@ namespace MWLua return dist <= actorsProcessingRange; }; + actor["isDead"] = [](const Object& o) { + const auto& target = o.ptr(); + return target.getClass().getCreatureStats(target).isDead(); + }; + actor["getEncumbrance"] = [](const Object& actor) -> float { const MWWorld::Ptr ptr = actor.ptr(); return ptr.getClass().getEncumbrance(ptr); diff --git a/apps/openmw/mwmechanics/actorutil.cpp b/apps/openmw/mwmechanics/actorutil.cpp index c414ff3032..2d2980075e 100644 --- a/apps/openmw/mwmechanics/actorutil.cpp +++ b/apps/openmw/mwmechanics/actorutil.cpp @@ -39,6 +39,6 @@ namespace MWMechanics { const MagicEffects& magicEffects = actor.getClass().getCreatureStats(actor).getMagicEffects(); return (magicEffects.getOrDefault(ESM::MagicEffect::Invisibility).getMagnitude() > 0) - || (magicEffects.getOrDefault(ESM::MagicEffect::Chameleon).getMagnitude() > 75); + || (magicEffects.getOrDefault(ESM::MagicEffect::Chameleon).getMagnitude() >= 75); } } diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 068d91ab42..dd7b97b6a5 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2386,49 +2386,55 @@ namespace MWMechanics } } - osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim && !isScriptedAnimPlaying() ? 0.f : duration); - if (duration > 0.0f) - moved /= duration; - else - moved = osg::Vec3f(0.f, 0.f, 0.f); + osg::Vec3f movementFromAnimation + = mAnimation->runAnimation(mSkipAnim && !isScriptedAnimPlaying() ? 0.f : duration); - moved.x() *= scale; - moved.y() *= scale; - - // Ensure we're moving in generally the right direction... - if (speed > 0.f && moved != osg::Vec3f()) + if (mPtr.getClass().isActor() && isMovementAnimationControlled() && !isScriptedAnimPlaying()) { - float l = moved.length(); - if (std::abs(movement.x() - moved.x()) > std::abs(moved.x()) / 2 - || std::abs(movement.y() - moved.y()) > std::abs(moved.y()) / 2 - || std::abs(movement.z() - moved.z()) > std::abs(moved.z()) / 2) - { - moved = movement; - // For some creatures getSpeed doesn't work, so we adjust speed to the animation. - // TODO: Fix Creature::getSpeed. - float newLength = moved.length(); - if (newLength > 0 && !cls.isNpc()) - moved *= (l / newLength); - } - } + if (duration > 0.0f) + movementFromAnimation /= duration; + else + movementFromAnimation = osg::Vec3f(0.f, 0.f, 0.f); - if (mFloatToSurface && cls.isActor()) - { - if (cls.getCreatureStats(mPtr).isDead() - || (!godmode - && cls.getCreatureStats(mPtr) - .getMagicEffects() - .getOrDefault(ESM::MagicEffect::Paralyze) - .getModifier() - > 0)) - { - moved.z() = 1.0; - } - } + movementFromAnimation.x() *= scale; + movementFromAnimation.y() *= scale; - // Update movement - if (isMovementAnimationControlled() && mPtr.getClass().isActor() && !isScriptedAnimPlaying()) - world->queueMovement(mPtr, moved); + if (speed > 0.f && movementFromAnimation != osg::Vec3f()) + { + // Ensure we're moving in the right general direction. In vanilla, all horizontal movement is taken from + // animations, even when moving diagonally (which doesn't have a corresponding animation). So to acheive + // diagonal movement, we have to rotate the movement taken from the animation to the intended + // direction. + // + // Note that while a complete movement animation cycle will have a well defined direction, no individual + // frame will, and therefore we have to determine the direction based on the currently playing cycle + // instead. + float animMovementAngle = getAnimationMovementDirection(); + float targetMovementAngle = std::atan2(-movement.x(), movement.y()); + float diff = targetMovementAngle - animMovementAngle; + movementFromAnimation = osg::Quat(diff, osg::Vec3f(0, 0, 1)) * movementFromAnimation; + } + + if (!(isPlayer && Settings::game().mPlayerMovementIgnoresAnimation)) + movement = movementFromAnimation; + + if (mFloatToSurface) + { + if (cls.getCreatureStats(mPtr).isDead() + || (!godmode + && cls.getCreatureStats(mPtr) + .getMagicEffects() + .getOrDefault(ESM::MagicEffect::Paralyze) + .getModifier() + > 0)) + { + movement.z() = 1.0; + } + } + + // Update movement + world->queueMovement(mPtr, movement); + } mSkipAnim = false; @@ -2909,6 +2915,39 @@ namespace MWMechanics MWBase::Environment::get().getSoundManager()->playSound3D(mPtr, *soundId, volume, pitch); } + float CharacterController::getAnimationMovementDirection() const + { + switch (mMovementState) + { + case CharState_RunLeft: + case CharState_SneakLeft: + case CharState_SwimWalkLeft: + case CharState_SwimRunLeft: + case CharState_WalkLeft: + return osg::PI_2f; + case CharState_RunRight: + case CharState_SneakRight: + case CharState_SwimWalkRight: + case CharState_SwimRunRight: + case CharState_WalkRight: + return -osg::PI_2f; + case CharState_RunForward: + case CharState_SneakForward: + case CharState_SwimRunForward: + case CharState_SwimWalkForward: + case CharState_WalkForward: + return mAnimation->getLegsYawRadians(); + case CharState_RunBack: + case CharState_SneakBack: + case CharState_SwimWalkBack: + case CharState_SwimRunBack: + case CharState_WalkBack: + return mAnimation->getLegsYawRadians() - osg::PIf; + default: + return 0.0f; + } + } + void CharacterController::updateHeadTracking(float duration) { const osg::Node* head = mAnimation->getNode("Bip01 Head"); diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index c3d45fe0fb..63491ec776 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -319,6 +319,8 @@ namespace MWMechanics void playSwishSound() const; + float getAnimationMovementDirection() const; + MWWorld::MovementDirectionFlags getSupportedMovementDirections() const; }; } diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index 2e28aaa1f3..db9ec3e588 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -932,6 +932,9 @@ namespace MWMechanics if (target.getCellRef().getLockLevel() < magnitude) // If the door is not already locked to a higher value, lock it to spell magnitude { + MWBase::Environment::get().getSoundManager()->playSound3D( + target, ESM::RefId::stringRefId("Open Lock"), 1.f, 1.f); + if (caster == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicLockSuccess}"); target.getCellRef().lock(magnitude); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 0741e24a69..bac9dbb56c 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1235,9 +1235,11 @@ namespace MWRender mRootController->setEnabled(enable); if (enable) { - mRootController->setRotate(osg::Quat(mLegsYawRadians, osg::Vec3f(0, 0, 1)) - * osg::Quat(mBodyPitchRadians, osg::Vec3f(1, 0, 0))); + osg::Quat legYaw = osg::Quat(mLegsYawRadians, osg::Vec3f(0, 0, 1)); + mRootController->setRotate(legYaw * osg::Quat(mBodyPitchRadians, osg::Vec3f(1, 0, 0))); yawOffset = mLegsYawRadians; + // When yawing the root, also update the accumulated movement. + movement = legYaw * movement; } } if (mSpineController) diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 86371dc0bf..88ceeabd23 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -247,7 +247,7 @@ namespace MWRender defaultMat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); stateset->setAttribute(defaultMat); - SceneUtil::ShadowManager::disableShadowsForStateSet(stateset); + SceneUtil::ShadowManager::disableShadowsForStateSet(Settings::shadows(), *stateset); // assign large value to effectively turn off fog // shaders don't respect glDisable(GL_FOG) diff --git a/apps/openmw/mwrender/esm4npcanimation.cpp b/apps/openmw/mwrender/esm4npcanimation.cpp index 1f06e68bc2..3ea8f829ce 100644 --- a/apps/openmw/mwrender/esm4npcanimation.cpp +++ b/apps/openmw/mwrender/esm4npcanimation.cpp @@ -124,6 +124,8 @@ namespace MWRender auto findArmorAddons = [&](const ESM4::Armor* armor) { for (ESM::FormId armaId : armor->mAddOns) { + if (armaId.isZeroOrUnset()) + continue; const ESM4::ArmorAddon* arma = store->get().search(armaId); if (!arma) { diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 3d28bf477d..892a8b5428 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -763,7 +763,7 @@ namespace MWRender lightSource->setStateSetModes(*stateset, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); - SceneUtil::ShadowManager::disableShadowsForStateSet(stateset); + SceneUtil::ShadowManager::disableShadowsForStateSet(Settings::shadows(), *stateset); // override sun for local map SceneUtil::configureStateSetSunOverride(static_cast(mSceneRoot), light, stateset); diff --git a/apps/openmw/mwrender/luminancecalculator.cpp b/apps/openmw/mwrender/luminancecalculator.cpp index 5b7fe272aa..ae29b7fdcc 100644 --- a/apps/openmw/mwrender/luminancecalculator.cpp +++ b/apps/openmw/mwrender/luminancecalculator.cpp @@ -20,11 +20,6 @@ namespace MWRender mResolveProgram = shaderManager.getProgram(vertex, std::move(resolveFragment)); mLuminanceProgram = shaderManager.getProgram(vertex, std::move(luminanceFragment)); - } - - void LuminanceCalculator::compile() - { - int mipmapLevels = osg::Image::computeNumberOfMipmapLevels(mWidth, mHeight); for (auto& buffer : mBuffers) { @@ -38,7 +33,6 @@ namespace MWRender osg::Texture2D::MIN_FILTER, osg::Texture2D::LINEAR_MIPMAP_NEAREST); buffer.mipmappedSceneLuminanceTex->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::LINEAR); buffer.mipmappedSceneLuminanceTex->setTextureSize(mWidth, mHeight); - buffer.mipmappedSceneLuminanceTex->setNumMipmapLevels(mipmapLevels); buffer.luminanceTex = new osg::Texture2D; buffer.luminanceTex->setInternalFormat(GL_R16F); @@ -62,14 +56,6 @@ namespace MWRender buffer.luminanceProxyFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, osg::FrameBufferAttachment(buffer.luminanceProxyTex)); - buffer.resolveSceneLumFbo = new osg::FrameBufferObject; - buffer.resolveSceneLumFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, - osg::FrameBufferAttachment(buffer.mipmappedSceneLuminanceTex, mipmapLevels - 1)); - - buffer.sceneLumFbo = new osg::FrameBufferObject; - buffer.sceneLumFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, - osg::FrameBufferAttachment(buffer.mipmappedSceneLuminanceTex)); - buffer.sceneLumSS = new osg::StateSet; buffer.sceneLumSS->setAttributeAndModes(mLuminanceProgram); buffer.sceneLumSS->addUniform(new osg::Uniform("sceneTex", 0)); @@ -84,6 +70,26 @@ namespace MWRender mBuffers[0].resolveSS->setTextureAttributeAndModes(1, mBuffers[1].luminanceTex); mBuffers[1].resolveSS->setTextureAttributeAndModes(1, mBuffers[0].luminanceTex); + } + + void LuminanceCalculator::compile() + { + int mipmapLevels = osg::Image::computeNumberOfMipmapLevels(mWidth, mHeight); + + for (auto& buffer : mBuffers) + { + buffer.mipmappedSceneLuminanceTex->setTextureSize(mWidth, mHeight); + buffer.mipmappedSceneLuminanceTex->setNumMipmapLevels(mipmapLevels); + buffer.mipmappedSceneLuminanceTex->dirtyTextureObject(); + + buffer.resolveSceneLumFbo = new osg::FrameBufferObject; + buffer.resolveSceneLumFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, + osg::FrameBufferAttachment(buffer.mipmappedSceneLuminanceTex, mipmapLevels - 1)); + + buffer.sceneLumFbo = new osg::FrameBufferObject; + buffer.sceneLumFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, + osg::FrameBufferAttachment(buffer.mipmappedSceneLuminanceTex)); + } mCompiled = true; } @@ -114,13 +120,14 @@ namespace MWRender buffer.luminanceProxyFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); ext->glBlitFramebuffer(0, 0, 1, 1, 0, 0, 1, 1, GL_COLOR_BUFFER_BIT, GL_NEAREST); - if (dirty) + if (mIsBlank) { // Use current frame data for previous frame to warm up calculations and prevent popin mBuffers[(frameId + 1) % 2].resolveFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); ext->glBlitFramebuffer(0, 0, 1, 1, 0, 0, 1, 1, GL_COLOR_BUFFER_BIT, GL_NEAREST); buffer.luminanceProxyFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + mIsBlank = false; } buffer.resolveFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); diff --git a/apps/openmw/mwrender/luminancecalculator.hpp b/apps/openmw/mwrender/luminancecalculator.hpp index 71ea2f7971..8b51081e2f 100644 --- a/apps/openmw/mwrender/luminancecalculator.hpp +++ b/apps/openmw/mwrender/luminancecalculator.hpp @@ -58,6 +58,7 @@ namespace MWRender bool mCompiled = false; bool mEnabled = false; + bool mIsBlank = true; int mWidth = 1; int mHeight = 1; diff --git a/apps/openmw/mwrender/pingpongcanvas.cpp b/apps/openmw/mwrender/pingpongcanvas.cpp index 5ac68acf5f..4fecdf87f9 100644 --- a/apps/openmw/mwrender/pingpongcanvas.cpp +++ b/apps/openmw/mwrender/pingpongcanvas.cpp @@ -10,9 +10,11 @@ namespace MWRender { - PingPongCanvas::PingPongCanvas(Shader::ShaderManager& shaderManager) + PingPongCanvas::PingPongCanvas( + Shader::ShaderManager& shaderManager, const std::shared_ptr& luminanceCalculator) : mFallbackStateSet(new osg::StateSet) , mMultiviewResolveStateSet(new osg::StateSet) + , mLuminanceCalculator(luminanceCalculator) { setUseDisplayList(false); setUseVertexBufferObjects(true); @@ -26,8 +28,7 @@ namespace MWRender addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES, 0, 3)); - mLuminanceCalculator = LuminanceCalculator(shaderManager); - mLuminanceCalculator.disable(); + mLuminanceCalculator->disable(); Shader::ShaderManager::DefineMap defines; Stereo::shaderStereoDefines(defines); @@ -142,7 +143,7 @@ namespace MWRender .getTexture()); } - mLuminanceCalculator.dirty(mTextureScene->getTextureWidth(), mTextureScene->getTextureHeight()); + mLuminanceCalculator->dirty(mTextureScene->getTextureWidth(), mTextureScene->getTextureHeight()); if (Stereo::getStereo()) mRenderViewport @@ -158,11 +159,11 @@ namespace MWRender { GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT2_EXT }, { GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT } } }; - (mAvgLum) ? mLuminanceCalculator.enable() : mLuminanceCalculator.disable(); + (mAvgLum) ? mLuminanceCalculator->enable() : mLuminanceCalculator->disable(); // A histogram based approach is superior way to calculate scene luminance. Using mipmaps is more broadly // supported, so that's what we use for now. - mLuminanceCalculator.draw(*this, renderInfo, state, ext, frameId); + mLuminanceCalculator->draw(*this, renderInfo, state, ext, frameId); auto buffer = buffers[0]; @@ -195,6 +196,39 @@ namespace MWRender } }; + // When textures are created (or resized) we need to either dirty them and/or clear them. + // Otherwise, there will be undefined behavior when reading from a texture that has yet to be written to in a + // later pass. + for (const auto& attachment : mDirtyAttachments) + { + const auto [w, h] + = attachment.mSize.get(mTextureScene->getTextureWidth(), mTextureScene->getTextureHeight()); + + attachment.mTarget->setTextureSize(w, h); + if (attachment.mMipMap) + attachment.mTarget->setNumMipmapLevels(osg::Image::computeNumberOfMipmapLevels(w, h)); + attachment.mTarget->dirtyTextureObject(); + + osg::ref_ptr fbo = new osg::FrameBufferObject; + + fbo->setAttachment( + osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, osg::FrameBufferAttachment(attachment.mTarget)); + fbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + + glViewport(0, 0, attachment.mTarget->getTextureWidth(), attachment.mTarget->getTextureHeight()); + state.haveAppliedAttribute(osg::StateAttribute::VIEWPORT); + glClearColor(attachment.mClearColor.r(), attachment.mClearColor.g(), attachment.mClearColor.b(), + attachment.mClearColor.a()); + glClear(GL_COLOR_BUFFER_BIT); + + if (attachment.mTarget->getNumMipmapLevels() > 0) + { + state.setActiveTextureUnit(0); + state.applyTextureAttribute(0, attachment.mTarget); + ext->glGenerateMipmap(GL_TEXTURE_2D); + } + } + for (const size_t& index : filtered) { const auto& node = mPasses[index]; @@ -202,8 +236,8 @@ namespace MWRender node.mRootStateSet->setTextureAttribute(PostProcessor::Unit_Depth, mTextureDepth); if (mAvgLum) - node.mRootStateSet->setTextureAttribute( - PostProcessor::TextureUnits::Unit_EyeAdaptation, mLuminanceCalculator.getLuminanceTexture(frameId)); + node.mRootStateSet->setTextureAttribute(PostProcessor::TextureUnits::Unit_EyeAdaptation, + mLuminanceCalculator->getLuminanceTexture(frameId)); if (mTextureNormals) node.mRootStateSet->setTextureAttribute(PostProcessor::TextureUnits::Unit_Normals, mTextureNormals); @@ -238,6 +272,23 @@ namespace MWRender if (pass.mRenderTarget) { + if (mDirtyAttachments.size() > 0) + { + const auto [w, h] + = pass.mSize.get(mTextureScene->getTextureWidth(), mTextureScene->getTextureHeight()); + + // Custom render targets must be shared between frame ids, so it's impossible to double buffer + // without expensive copies. That means the only thread-safe place to resize is in the draw + // thread. + osg::Texture2D* texture = const_cast(dynamic_cast( + pass.mRenderTarget->getAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0) + .getTexture())); + + texture->setTextureSize(w, h); + texture->setNumMipmapLevels(pass.mRenderTexture->getNumMipmapLevels()); + texture->dirtyTextureObject(); + } + pass.mRenderTarget->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); if (pass.mRenderTexture->getNumMipmapLevels() > 0) @@ -311,5 +362,7 @@ namespace MWRender { bindDestinationFbo(); } + + mDirtyAttachments.clear(); } } diff --git a/apps/openmw/mwrender/pingpongcanvas.hpp b/apps/openmw/mwrender/pingpongcanvas.hpp index d8758303d7..a03e3591ae 100644 --- a/apps/openmw/mwrender/pingpongcanvas.hpp +++ b/apps/openmw/mwrender/pingpongcanvas.hpp @@ -22,7 +22,8 @@ namespace MWRender class PingPongCanvas : public osg::Geometry { public: - PingPongCanvas(Shader::ShaderManager& shaderManager); + PingPongCanvas( + Shader::ShaderManager& shaderManager, const std::shared_ptr& luminanceCalculator); void drawGeometry(osg::RenderInfo& renderInfo) const; @@ -30,6 +31,11 @@ namespace MWRender void dirty() { mDirty = true; } + void setDirtyAttachments(const std::vector& attachments) + { + mDirtyAttachments = attachments; + } + const fx::DispatchArray& getPasses() { return mPasses; } void setPasses(fx::DispatchArray&& passes); @@ -65,11 +71,12 @@ namespace MWRender osg::ref_ptr mTextureNormals; mutable bool mDirty = false; + mutable std::vector mDirtyAttachments; mutable osg::ref_ptr mRenderViewport; mutable osg::ref_ptr mMultiviewResolveFramebuffer; mutable osg::ref_ptr mDestinationFBO; mutable std::array, 3> mFbos; - mutable LuminanceCalculator mLuminanceCalculator; + mutable std::shared_ptr mLuminanceCalculator; }; } diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index c3106802db..2c77981244 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -118,9 +118,14 @@ namespace MWRender , mUsePostProcessing(Settings::postProcessing().mEnabled) , mSamples(Settings::video().mAntialiasing) , mPingPongCull(new PingPongCull(this)) - , mCanvases({ new PingPongCanvas(mRendering.getResourceSystem()->getSceneManager()->getShaderManager()), - new PingPongCanvas(mRendering.getResourceSystem()->getSceneManager()->getShaderManager()) }) { + auto& shaderManager = mRendering.getResourceSystem()->getSceneManager()->getShaderManager(); + + std::shared_ptr luminanceCalculator = std::make_shared(shaderManager); + + for (auto& canvas : mCanvases) + canvas = new PingPongCanvas(shaderManager, luminanceCalculator); + mHUDCamera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); mHUDCamera->setRenderOrder(osg::Camera::POST_RENDER); mHUDCamera->setClearColor(osg::Vec4(0.45, 0.45, 0.14, 1.0)); @@ -139,8 +144,7 @@ namespace MWRender if (Settings::shaders().mSoftParticles || Settings::postProcessing().mTransparentPostpass) { mTransparentDepthPostPass - = new TransparentDepthBinCallback(mRendering.getResourceSystem()->getSceneManager()->getShaderManager(), - Settings::postProcessing().mTransparentPostpass); + = new TransparentDepthBinCallback(shaderManager, Settings::postProcessing().mTransparentPostpass); osgUtil::RenderBin::getRenderBinPrototype("DepthSortedBin")->setDrawCallback(mTransparentDepthPostPass); } @@ -211,25 +215,14 @@ namespace MWRender if (Stereo::getStereo()) Stereo::Manager::instance().screenResolutionChanged(); - auto width = renderWidth(); - auto height = renderHeight(); - for (auto& technique : mTechniques) - { - for (auto& [name, rt] : technique->getRenderTargetsMap()) - { - const auto [w, h] = rt.mSize.get(width, height); - rt.mTarget->setTextureSize(w, h); - } - } - size_t frameId = frame() % 2; createObjectsForFrame(frameId); mRendering.updateProjectionMatrix(); - mRendering.setScreenRes(width, height); + mRendering.setScreenRes(renderWidth(), renderHeight()); - dirtyTechniques(); + dirtyTechniques(true); mDirty = true; mDirtyFrameId = !frameId; @@ -534,7 +527,7 @@ namespace MWRender mCanvases[frameId]->dirty(); } - void PostProcessor::dirtyTechniques() + void PostProcessor::dirtyTechniques(bool dirtyAttachments) { size_t frameId = frame() % 2; @@ -548,6 +541,8 @@ namespace MWRender mNormals = false; mPassLights = false; + std::vector attachmentsToDirty; + for (const auto& technique : mTechniques) { if (!technique->isValid()) @@ -613,8 +608,6 @@ namespace MWRender uniform->mName.c_str(), *type, uniform->getNumElements())); } - std::unordered_map renderTargetCache; - for (const auto& pass : technique->getPasses()) { int subTexUnit = texUnit; @@ -626,32 +619,39 @@ namespace MWRender if (!pass->getTarget().empty()) { - const auto& rt = technique->getRenderTargetsMap()[pass->getTarget()]; - - const auto [w, h] = rt.mSize.get(renderWidth(), renderHeight()); - - subPass.mRenderTexture = new osg::Texture2D(*rt.mTarget); - renderTargetCache[rt.mTarget] = subPass.mRenderTexture; - subPass.mRenderTexture->setTextureSize(w, h); - subPass.mRenderTexture->setName(std::string(pass->getTarget())); - - if (rt.mMipMap) - subPass.mRenderTexture->setNumMipmapLevels(osg::Image::computeNumberOfMipmapLevels(w, h)); + auto& renderTarget = technique->getRenderTargetsMap()[pass->getTarget()]; + subPass.mSize = renderTarget.mSize; + subPass.mRenderTexture = renderTarget.mTarget; + subPass.mMipMap = renderTarget.mMipMap; subPass.mRenderTarget = new osg::FrameBufferObject; subPass.mRenderTarget->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, osg::FrameBufferAttachment(subPass.mRenderTexture)); + + const auto [w, h] = renderTarget.mSize.get(renderWidth(), renderHeight()); subPass.mStateSet->setAttributeAndModes(new osg::Viewport(0, 0, w, h)); + + if (std::find_if(attachmentsToDirty.cbegin(), attachmentsToDirty.cend(), + [renderTarget](const auto& rt) { return renderTarget.mTarget == rt.mTarget; }) + == attachmentsToDirty.cend()) + { + attachmentsToDirty.push_back(fx::Types::RenderTarget(renderTarget)); + } } - for (const auto& whitelist : pass->getRenderTargets()) + for (const auto& name : pass->getRenderTargets()) { - auto it = technique->getRenderTargetsMap().find(whitelist); - if (it != technique->getRenderTargetsMap().end() && renderTargetCache[it->second.mTarget]) + auto& renderTarget = technique->getRenderTargetsMap()[name]; + subPass.mStateSet->setTextureAttribute(subTexUnit, renderTarget.mTarget); + subPass.mStateSet->addUniform(new osg::Uniform(name.c_str(), subTexUnit)); + + if (std::find_if(attachmentsToDirty.cbegin(), attachmentsToDirty.cend(), + [renderTarget](const auto& rt) { return renderTarget.mTarget == rt.mTarget; }) + == attachmentsToDirty.cend()) { - subPass.mStateSet->setTextureAttribute(subTexUnit, renderTargetCache[it->second.mTarget]); - subPass.mStateSet->addUniform(new osg::Uniform(std::string(it->first).c_str(), subTexUnit++)); + attachmentsToDirty.push_back(fx::Types::RenderTarget(renderTarget)); } + subTexUnit++; } node.mPasses.emplace_back(std::move(subPass)); @@ -668,6 +668,9 @@ namespace MWRender hud->updateTechniques(); mRendering.getSkyManager()->setSunglare(sunglare); + + if (dirtyAttachments) + mCanvases[frameId]->setDirtyAttachments(attachmentsToDirty); } PostProcessor::Status PostProcessor::enableTechnique( @@ -681,7 +684,7 @@ namespace MWRender int pos = std::min(location.value_or(mTechniques.size()), mTechniques.size()); mTechniques.insert(mTechniques.begin() + pos, technique); - dirtyTechniques(); + dirtyTechniques(Settings::ShaderManager::get().getMode() == Settings::ShaderManager::Mode::Debug); return Status_Toggled; } @@ -774,7 +777,7 @@ namespace MWRender for (auto& technique : mTemplates) technique->compile(); - dirtyTechniques(); + dirtyTechniques(true); } void PostProcessor::disableDynamicShaders() diff --git a/apps/openmw/mwrender/postprocessor.hpp b/apps/openmw/mwrender/postprocessor.hpp index 153ec8166b..e9f19bf6b5 100644 --- a/apps/openmw/mwrender/postprocessor.hpp +++ b/apps/openmw/mwrender/postprocessor.hpp @@ -204,7 +204,7 @@ namespace MWRender void createObjectsForFrame(size_t frameId); - void dirtyTechniques(); + void dirtyTechniques(bool dirtyAttachments = false); void update(size_t frameId); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 3b6716ce2c..c175817fa8 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -331,8 +331,8 @@ namespace MWRender // Shadows and radial fog have problems with fixed-function mode. bool forceShaders = Settings::fog().mRadialFog || Settings::fog().mExponentialFog || Settings::shaders().mSoftParticles || Settings::shaders().mForceShaders - || Settings::Manager::getBool("enable shadows", "Shadows") - || lightingMethod != SceneUtil::LightingMethod::FFP || reverseZ || mSkyBlending || Stereo::getMultiview(); + || Settings::shadows().mEnableShadows || lightingMethod != SceneUtil::LightingMethod::FFP || reverseZ + || mSkyBlending || Stereo::getMultiview(); resourceSystem->getSceneManager()->setForceShaders(forceShaders); // FIXME: calling dummy method because terrain needs to know whether lighting is clamped @@ -367,22 +367,22 @@ namespace MWRender sceneRoot->setName("Scene Root"); int shadowCastingTraversalMask = Mask_Scene; - if (Settings::Manager::getBool("actor shadows", "Shadows")) + if (Settings::shadows().mActorShadows) shadowCastingTraversalMask |= Mask_Actor; - if (Settings::Manager::getBool("player shadows", "Shadows")) + if (Settings::shadows().mPlayerShadows) shadowCastingTraversalMask |= Mask_Player; int indoorShadowCastingTraversalMask = shadowCastingTraversalMask; - if (Settings::Manager::getBool("object shadows", "Shadows")) + if (Settings::shadows().mObjectShadows) shadowCastingTraversalMask |= (Mask_Object | Mask_Static); - if (Settings::Manager::getBool("terrain shadows", "Shadows")) + if (Settings::shadows().mTerrainShadows) shadowCastingTraversalMask |= Mask_Terrain; mShadowManager = std::make_unique(sceneRoot, mRootNode, shadowCastingTraversalMask, - indoorShadowCastingTraversalMask, Mask_Terrain | Mask_Object | Mask_Static, + indoorShadowCastingTraversalMask, Mask_Terrain | Mask_Object | Mask_Static, Settings::shadows(), mResourceSystem->getSceneManager()->getShaderManager()); - Shader::ShaderManager::DefineMap shadowDefines = mShadowManager->getShadowDefines(); + Shader::ShaderManager::DefineMap shadowDefines = mShadowManager->getShadowDefines(Settings::shadows()); Shader::ShaderManager::DefineMap lightDefines = sceneRoot->getLightDefines(); Shader::ShaderManager::DefineMap globalDefines = mResourceSystem->getSceneManager()->getShaderManager().getGlobalDefines(); @@ -702,7 +702,7 @@ namespace MWRender { // need to wrap this in a StateUpdater? mSunLight->setDiffuse(diffuse); - mSunLight->setSpecular(specular); + mSunLight->setSpecular(osg::Vec4f(specular.x(), specular.y(), specular.z(), specular.w() * sunVis)); mPostProcessor->getStateUpdater()->setSunColor(diffuse); mPostProcessor->getStateUpdater()->setSunVis(sunVis); @@ -770,7 +770,7 @@ namespace MWRender if (enabled) mShadowManager->enableOutdoorMode(); else - mShadowManager->enableIndoorMode(); + mShadowManager->enableIndoorMode(Settings::shadows()); mPostProcessor->getStateUpdater()->setIsInterior(!enabled); } @@ -1319,6 +1319,7 @@ namespace MWRender const float lodFactor = Settings::terrain().mLodFactor; const bool groundcover = Settings::groundcover().mEnabled; const bool distantTerrain = Settings::terrain().mDistantTerrain; + const double expiryDelay = Settings::cells().mCacheExpiryDelay; if (distantTerrain || groundcover) { const int compMapResolution = Settings::terrain().mCompositeMapResolution; @@ -1329,7 +1330,7 @@ namespace MWRender const bool debugChunks = Settings::terrain().mDebugChunks; auto quadTreeWorld = std::make_unique(mSceneRoot, mRootNode, mResourceSystem, mTerrainStorage.get(), Mask_Terrain, Mask_PreCompile, Mask_Debug, compMapResolution, compMapLevel, - lodFactor, vertexLodMod, maxCompGeometrySize, debugChunks, worldspace); + lodFactor, vertexLodMod, maxCompGeometrySize, debugChunks, worldspace, expiryDelay); if (Settings::terrain().mObjectPaging) { newChunkMgr.mObjectPaging @@ -1351,7 +1352,7 @@ namespace MWRender } else newChunkMgr.mTerrain = std::make_unique(mSceneRoot, mRootNode, mResourceSystem, - mTerrainStorage.get(), Mask_Terrain, worldspace, Mask_PreCompile, Mask_Debug); + mTerrainStorage.get(), Mask_Terrain, worldspace, expiryDelay, Mask_PreCompile, Mask_Debug); newChunkMgr.mTerrain->setTargetFrameRate(Settings::cells().mTargetFramerate); float distanceMult = std::cos(osg::DegreesToRadians(std::min(mFieldOfView, 140.f)) / 2.f); diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 51018e93f9..a38030738a 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -220,7 +220,7 @@ namespace camera->setNodeMask(MWRender::Mask_RenderToTexture); camera->setCullMask(MWRender::Mask_Sky); camera->addChild(mEarlyRenderBinRoot); - SceneUtil::ShadowManager::disableShadowsForStateSet(camera->getOrCreateStateSet()); + SceneUtil::ShadowManager::disableShadowsForStateSet(Settings::shadows(), *camera->getOrCreateStateSet()); } private: @@ -271,7 +271,7 @@ namespace MWRender if (!mSceneManager->getForceShaders()) skyroot->getOrCreateStateSet()->setAttributeAndModes(new osg::Program(), osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED | osg::StateAttribute::ON); - SceneUtil::ShadowManager::disableShadowsForStateSet(skyroot->getOrCreateStateSet()); + SceneUtil::ShadowManager::disableShadowsForStateSet(Settings::shadows(), *skyroot->getOrCreateStateSet()); parentNode->addChild(skyroot); mEarlyRenderBinRoot = new osg::Group; diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 091ab99821..85df70adfc 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -265,7 +265,8 @@ namespace MWRender camera->setNodeMask(Mask_RenderToTexture); if (Settings::water().mRefractionScale != 1) // TODO: to be removed with issue #5709 - SceneUtil::ShadowManager::disableShadowsForStateSet(camera->getOrCreateStateSet()); + SceneUtil::ShadowManager::disableShadowsForStateSet( + Settings::shadows(), *camera->getOrCreateStateSet()); } void apply(osg::Camera* camera) override @@ -341,7 +342,7 @@ namespace MWRender camera->addChild(mClipCullNode); camera->setNodeMask(Mask_RenderToTexture); - SceneUtil::ShadowManager::disableShadowsForStateSet(camera->getOrCreateStateSet()); + SceneUtil::ShadowManager::disableShadowsForStateSet(Settings::shadows(), *camera->getOrCreateStateSet()); } void apply(osg::Camera* camera) override diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index d82125a5ce..b55a524a48 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -111,12 +111,12 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::getState( } void MWWorld::ContainerStore::storeEquipmentState( - const MWWorld::LiveCellRefBase& ref, int index, ESM::InventoryState& inventory) const + const MWWorld::LiveCellRefBase& ref, size_t index, ESM::InventoryState& inventory) const { } void MWWorld::ContainerStore::readEquipmentState( - const MWWorld::ContainerStoreIterator& iter, int index, const ESM::InventoryState& inventory) + const MWWorld::ContainerStoreIterator& iter, size_t index, const ESM::InventoryState& inventory) { } @@ -128,7 +128,7 @@ void MWWorld::ContainerStore::storeState(const LiveCellRef& ref, ESM::ObjectS template void MWWorld::ContainerStore::storeStates( - const CellRefList& collection, ESM::InventoryState& inventory, int& index, bool equipable) const + const CellRefList& collection, ESM::InventoryState& inventory, size_t& index, bool equipable) const { for (const LiveCellRef& liveCellRef : collection.mList) { @@ -926,7 +926,7 @@ void MWWorld::ContainerStore::writeState(ESM::InventoryState& state) const { state.mItems.clear(); - int index = 0; + size_t index = 0; storeStates(potions, state, index); storeStates(appas, state, index); storeStates(armors, state, index, true); @@ -947,12 +947,12 @@ void MWWorld::ContainerStore::readState(const ESM::InventoryState& inventory) mModified = true; mResolved = true; - int index = 0; + size_t index = 0; for (const ESM::ObjectState& state : inventory.mItems) { int type = MWBase::Environment::get().getESMStore()->find(state.mRef.mRefID); - int thisIndex = index++; + size_t thisIndex = index++; switch (type) { diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index 889fcf7463..fb2722dde8 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -161,16 +161,16 @@ namespace MWWorld void storeState(const LiveCellRef& ref, ESM::ObjectState& state) const; template - void storeStates( - const CellRefList& collection, ESM::InventoryState& inventory, int& index, bool equipable = false) const; + void storeStates(const CellRefList& collection, ESM::InventoryState& inventory, size_t& index, + bool equipable = false) const; void updateRechargingItems(); virtual void storeEquipmentState( - const MWWorld::LiveCellRefBase& ref, int index, ESM::InventoryState& inventory) const; + const MWWorld::LiveCellRefBase& ref, size_t index, ESM::InventoryState& inventory) const; virtual void readEquipmentState( - const MWWorld::ContainerStoreIterator& iter, int index, const ESM::InventoryState& inventory); + const MWWorld::ContainerStoreIterator& iter, size_t index, const ESM::InventoryState& inventory); public: ContainerStore(); diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 12fbc0d4df..0b661b4442 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -138,6 +138,59 @@ namespace return npcsToReplace; } + template + std::vector getSpellsToReplace( + const MWWorld::Store& spells, const MWWorld::Store& magicEffects) + { + std::vector spellsToReplace; + + for (RecordType spell : spells) + { + if (spell.mEffects.mList.empty()) + continue; + + bool changed = false; + auto iter = spell.mEffects.mList.begin(); + while (iter != spell.mEffects.mList.end()) + { + const ESM::MagicEffect* mgef = magicEffects.search(iter->mEffectID); + if (!mgef) + { + Log(Debug::Verbose) << RecordType::getRecordType() << " " << spell.mId + << ": dropping invalid effect (index " << iter->mEffectID << ")"; + iter = spell.mEffects.mList.erase(iter); + changed = true; + continue; + } + + if (!(mgef->mData.mFlags & ESM::MagicEffect::TargetAttribute) && iter->mAttribute != -1) + { + iter->mAttribute = -1; + Log(Debug::Verbose) << RecordType::getRecordType() << " " << spell.mId + << ": dropping unexpected attribute argument of " + << ESM::MagicEffect::indexToGmstString(iter->mEffectID) << " effect"; + changed = true; + } + + if (!(mgef->mData.mFlags & ESM::MagicEffect::TargetSkill) && iter->mSkill != -1) + { + iter->mSkill = -1; + Log(Debug::Verbose) << RecordType::getRecordType() << " " << spell.mId + << ": dropping unexpected skill argument of " + << ESM::MagicEffect::indexToGmstString(iter->mEffectID) << " effect"; + changed = true; + } + + ++iter; + } + + if (changed) + spellsToReplace.emplace_back(spell); + } + + return spellsToReplace; + } + // Custom enchanted items can reference scripts that no longer exist, this doesn't necessarily mean the base item no // longer exists however. So instead of removing the item altogether, we're only removing the script. template @@ -538,71 +591,24 @@ namespace MWWorld removeMissingScripts(getWritable(), getWritable().mStatic); - // Validate spell effects for invalid arguments - std::vector spellsToReplace; + // Validate spell effects and enchantments for invalid arguments auto& spells = getWritable(); - for (ESM::Spell spell : spells) - { - if (spell.mEffects.mList.empty()) - continue; - - bool changed = false; - auto iter = spell.mEffects.mList.begin(); - while (iter != spell.mEffects.mList.end()) - { - const ESM::MagicEffect* mgef = getWritable().search(iter->mEffectID); - if (!mgef) - { - Log(Debug::Verbose) << "Spell '" << spell.mId << "' has an invalid effect (index " - << iter->mEffectID << ") present. Dropping the effect."; - iter = spell.mEffects.mList.erase(iter); - changed = true; - continue; - } - - if (mgef->mData.mFlags & ESM::MagicEffect::TargetSkill) - { - if (iter->mAttribute != -1) - { - iter->mAttribute = -1; - Log(Debug::Verbose) - << ESM::MagicEffect::indexToGmstString(iter->mEffectID) << " effect of spell '" << spell.mId - << "' has an attribute argument present. Dropping the argument."; - changed = true; - } - } - else if (mgef->mData.mFlags & ESM::MagicEffect::TargetAttribute) - { - if (iter->mSkill != -1) - { - iter->mSkill = -1; - Log(Debug::Verbose) - << ESM::MagicEffect::indexToGmstString(iter->mEffectID) << " effect of spell '" << spell.mId - << "' has a skill argument present. Dropping the argument."; - changed = true; - } - } - else if (iter->mSkill != -1 || iter->mAttribute != -1) - { - iter->mSkill = -1; - iter->mAttribute = -1; - Log(Debug::Verbose) << ESM::MagicEffect::indexToGmstString(iter->mEffectID) << " effect of spell '" - << spell.mId << "' has argument(s) present. Dropping the argument(s)."; - changed = true; - } - - ++iter; - } - - if (changed) - spellsToReplace.emplace_back(spell); - } + auto& enchantments = getWritable(); + auto& magicEffects = getWritable(); + std::vector spellsToReplace = getSpellsToReplace(spells, magicEffects); for (const ESM::Spell& spell : spellsToReplace) { spells.eraseStatic(spell.mId); spells.insertStatic(spell); } + + std::vector enchantmentsToReplace = getSpellsToReplace(enchantments, magicEffects); + for (const ESM::Enchantment& enchantment : enchantmentsToReplace) + { + enchantments.eraseStatic(enchantment.mId); + enchantments.insertStatic(enchantment); + } } void ESMStore::movePlayerRecord() diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 9b3470a835..095f5d3cc1 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -46,32 +46,32 @@ void MWWorld::InventoryStore::initSlots(TSlots& slots_) } void MWWorld::InventoryStore::storeEquipmentState( - const MWWorld::LiveCellRefBase& ref, int index, ESM::InventoryState& inventory) const + const MWWorld::LiveCellRefBase& ref, size_t index, ESM::InventoryState& inventory) const { - for (int i = 0; i < static_cast(mSlots.size()); ++i) + for (int32_t i = 0; i < MWWorld::InventoryStore::Slots; ++i) + { if (mSlots[i].getType() != -1 && mSlots[i]->getBase() == &ref) - { - inventory.mEquipmentSlots[index] = i; - } + inventory.mEquipmentSlots[static_cast(index)] = i; + } if (mSelectedEnchantItem.getType() != -1 && mSelectedEnchantItem->getBase() == &ref) inventory.mSelectedEnchantItem = index; } void MWWorld::InventoryStore::readEquipmentState( - const MWWorld::ContainerStoreIterator& iter, int index, const ESM::InventoryState& inventory) + const MWWorld::ContainerStoreIterator& iter, size_t index, const ESM::InventoryState& inventory) { if (index == inventory.mSelectedEnchantItem) mSelectedEnchantItem = iter; - std::map::const_iterator found = inventory.mEquipmentSlots.find(index); + auto found = inventory.mEquipmentSlots.find(index); if (found != inventory.mEquipmentSlots.end()) { if (found->second < 0 || found->second >= MWWorld::InventoryStore::Slots) throw std::runtime_error("Invalid slot index in inventory state"); // make sure the item can actually be equipped in this slot - int slot = found->second; + int32_t slot = found->second; std::pair, bool> allowedSlots = iter->getClass().getEquipmentSlots(*iter); if (!allowedSlots.first.size()) return; diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index 6df5fa1e5a..0af6ee2b28 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -81,9 +81,9 @@ namespace MWWorld void fireEquipmentChangedEvent(); void storeEquipmentState( - const MWWorld::LiveCellRefBase& ref, int index, ESM::InventoryState& inventory) const override; + const MWWorld::LiveCellRefBase& ref, size_t index, ESM::InventoryState& inventory) const override; void readEquipmentState( - const MWWorld::ContainerStoreIterator& iter, int index, const ESM::InventoryState& inventory) override; + const MWWorld::ContainerStoreIterator& iter, size_t index, const ESM::InventoryState& inventory) override; ContainerStoreIterator findSlot(int slot) const; diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 0da70bdd48..5d739a9161 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -789,8 +789,7 @@ namespace MWWorld mRendering.configureFog( mResult.mFogDepth, underwaterFog, mResult.mDLFogFactor, mResult.mDLFogOffset / 100.0f, mResult.mFogColor); mRendering.setAmbientColour(mResult.mAmbientColor); - mRendering.setSunColour( - mResult.mSunColor, mResult.mSunColor * mResult.mGlareView * glareFade, mResult.mGlareView * glareFade); + mRendering.setSunColour(mResult.mSunColor, mResult.mSunColor, mResult.mGlareView * glareFade); mRendering.getSkyManager()->setWeather(mResult); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 5be1e52530..20e4122766 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -660,8 +660,8 @@ namespace MWWorld std::string_view World::getCellName(const MWWorld::Cell& cell) const { - if (!cell.isExterior() || !cell.getNameId().empty()) - return cell.getNameId(); + if (!cell.isExterior() || !cell.getDisplayName().empty()) + return cell.getDisplayName(); return ESM::visit(ESM::VisitOverload{ [&](const ESM::Cell& cellIn) -> std::string_view { return getCellName(&cellIn); }, diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index 72959f8591..7dce21bac6 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -411,7 +411,7 @@ namespace EXPECT_EQ(*result, expected); } - TEST_F(TestBulletNifLoader, for_root_bounding_box_should_return_shape_with_compound_shape_and_box_inside) + TEST_F(TestBulletNifLoader, for_root_bounding_box_should_return_shape_with_bounding_box_data) { mNode.mName = "Bounding Box"; mNode.mBounds.mType = Nif::BoundingVolume::Type::BOX_BV; @@ -427,15 +427,11 @@ namespace Resource::BulletShape expected; expected.mCollisionBox.mExtents = osg::Vec3f(1, 2, 3); expected.mCollisionBox.mCenter = osg::Vec3f(-1, -2, -3); - std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); - std::unique_ptr shape(new btCompoundShape); - shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); - expected.mCollisionShape.reset(shape.release()); EXPECT_EQ(*result, expected); } - TEST_F(TestBulletNifLoader, for_child_bounding_box_should_return_shape_with_compound_shape_with_box_inside) + TEST_F(TestBulletNifLoader, for_child_bounding_box_should_return_shape_with_bounding_box_data) { mNode.mName = "Bounding Box"; mNode.mBounds.mType = Nif::BoundingVolume::Type::BOX_BV; @@ -453,15 +449,11 @@ namespace Resource::BulletShape expected; expected.mCollisionBox.mExtents = osg::Vec3f(1, 2, 3); expected.mCollisionBox.mCenter = osg::Vec3f(-1, -2, -3); - std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); - std::unique_ptr shape(new btCompoundShape); - shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); - expected.mCollisionShape.reset(shape.release()); EXPECT_EQ(*result, expected); } - TEST_F(TestBulletNifLoader, for_root_with_bounds_and_child_bounding_box_but_should_use_bounding_box) + TEST_F(TestBulletNifLoader, for_root_with_bounds_and_child_bounding_box_should_use_bounding_box) { mNode.mName = "Bounding Box"; mNode.mBounds.mType = Nif::BoundingVolume::Type::BOX_BV; @@ -483,10 +475,6 @@ namespace Resource::BulletShape expected; expected.mCollisionBox.mExtents = osg::Vec3f(1, 2, 3); expected.mCollisionBox.mCenter = osg::Vec3f(-1, -2, -3); - std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); - std::unique_ptr shape(new btCompoundShape); - shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); - expected.mCollisionShape.reset(shape.release()); EXPECT_EQ(*result, expected); } @@ -519,10 +507,6 @@ namespace Resource::BulletShape expected; expected.mCollisionBox.mExtents = osg::Vec3f(1, 2, 3); expected.mCollisionBox.mCenter = osg::Vec3f(-1, -2, -3); - std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); - std::unique_ptr shape(new btCompoundShape); - shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); - expected.mCollisionShape.reset(shape.release()); EXPECT_EQ(*result, expected); } @@ -555,10 +539,6 @@ namespace Resource::BulletShape expected; expected.mCollisionBox.mExtents = osg::Vec3f(4, 5, 6); expected.mCollisionBox.mCenter = osg::Vec3f(-4, -5, -6); - std::unique_ptr box(new btBoxShape(btVector3(4, 5, 6))); - std::unique_ptr shape(new btCompoundShape); - shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-4, -5, -6)), box.release()); - expected.mCollisionShape.reset(shape.release()); EXPECT_EQ(*result, expected); } @@ -977,12 +957,12 @@ namespace } TEST_F(TestBulletNifLoader, - for_tri_shape_child_node_with_extra_data_string_equal_ncc_should_return_shape_with_cameraonly_collision) + for_root_node_with_extra_data_string_equal_ncc_should_return_shape_with_cameraonly_collision) { mNiStringExtraData.mData = "NCC__"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; - mNiTriShape.mExtra = Nif::ExtraPtr(&mNiStringExtraData); mNiTriShape.mParents.push_back(&mNiNode); + mNiNode.mExtra = Nif::ExtraPtr(&mNiStringExtraData); mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; Nif::NIFFile file("test.nif"); @@ -1005,13 +985,13 @@ namespace } TEST_F(TestBulletNifLoader, - for_tri_shape_child_node_with_not_first_extra_data_string_equal_ncc_should_return_shape_with_cameraonly_collision) + for_root_node_with_not_first_extra_data_string_equal_ncc_should_return_shape_with_cameraonly_collision) { mNiStringExtraData.mNext = Nif::ExtraPtr(&mNiStringExtraData2); mNiStringExtraData2.mData = "NCC__"; mNiStringExtraData2.recType = Nif::RC_NiStringExtraData; - mNiTriShape.mExtra = Nif::ExtraPtr(&mNiStringExtraData); mNiTriShape.mParents.push_back(&mNiNode); + mNiNode.mExtra = Nif::ExtraPtr(&mNiStringExtraData); mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; Nif::NIFFile file("test.nif"); @@ -1032,8 +1012,62 @@ namespace EXPECT_EQ(*result, expected); } + TEST_F( + TestBulletNifLoader, for_root_node_with_extra_data_string_starting_with_nc_should_return_shape_with_nocollision) + { + mNiStringExtraData.mData = "NC___"; + mNiStringExtraData.recType = Nif::RC_NiStringExtraData; + mNiTriShape.mParents.push_back(&mNiNode); + mNiNode.mExtra = Nif::ExtraPtr(&mNiStringExtraData); + mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; + + Nif::NIFFile file("test.nif"); + file.mRoots.push_back(&mNiNode); + file.mHash = mHash; + + const auto result = mLoader.load(file); + + std::unique_ptr triangles(new btTriangleMesh(false)); + triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + std::unique_ptr compound(new btCompoundShape); + compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); + + Resource::BulletShape expected; + expected.mCollisionShape.reset(compound.release()); + expected.mVisualCollisionType = Resource::VisualCollisionType::Default; + + EXPECT_EQ(*result, expected); + } + TEST_F(TestBulletNifLoader, - for_tri_shape_child_node_with_extra_data_string_starting_with_nc_should_return_shape_with_nocollision) + for_root_node_with_not_first_extra_data_string_starting_with_nc_should_return_shape_with_nocollision) + { + mNiStringExtraData.mNext = Nif::ExtraPtr(&mNiStringExtraData2); + mNiStringExtraData2.mData = "NC___"; + mNiStringExtraData2.recType = Nif::RC_NiStringExtraData; + mNiTriShape.mParents.push_back(&mNiNode); + mNiNode.mExtra = Nif::ExtraPtr(&mNiStringExtraData); + mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; + + Nif::NIFFile file("test.nif"); + file.mRoots.push_back(&mNiNode); + file.mHash = mHash; + + const auto result = mLoader.load(file); + + std::unique_ptr triangles(new btTriangleMesh(false)); + triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + std::unique_ptr compound(new btCompoundShape); + compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); + + Resource::BulletShape expected; + expected.mCollisionShape.reset(compound.release()); + expected.mVisualCollisionType = Resource::VisualCollisionType::Default; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_extra_data_string_should_ignore_extra_data) { mNiStringExtraData.mData = "NC___"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; @@ -1054,35 +1088,6 @@ namespace Resource::BulletShape expected; expected.mCollisionShape.reset(compound.release()); - expected.mVisualCollisionType = Resource::VisualCollisionType::Default; - - EXPECT_EQ(*result, expected); - } - - TEST_F(TestBulletNifLoader, - for_tri_shape_child_node_with_not_first_extra_data_string_starting_with_nc_should_return_shape_with_nocollision) - { - mNiStringExtraData.mNext = Nif::ExtraPtr(&mNiStringExtraData2); - mNiStringExtraData2.mData = "NC___"; - mNiStringExtraData2.recType = Nif::RC_NiStringExtraData; - mNiTriShape.mExtra = Nif::ExtraPtr(&mNiStringExtraData); - mNiTriShape.mParents.push_back(&mNiNode); - mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; - - Nif::NIFFile file("test.nif"); - file.mRoots.push_back(&mNiNode); - file.mHash = mHash; - - const auto result = mLoader.load(file); - - std::unique_ptr triangles(new btTriangleMesh(false)); - triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); - std::unique_ptr compound(new btCompoundShape); - compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); - - Resource::BulletShape expected; - expected.mCollisionShape.reset(compound.release()); - expected.mVisualCollisionType = Resource::VisualCollisionType::Default; EXPECT_EQ(*result, expected); } @@ -1121,33 +1126,13 @@ namespace EXPECT_EQ(*result, expected); } - TEST_F(TestBulletNifLoader, - for_tri_shape_child_node_with_extra_data_string_mrk_should_return_shape_with_null_collision_shape) - { - mNiStringExtraData.mData = "MRK"; - mNiStringExtraData.recType = Nif::RC_NiStringExtraData; - mNiTriShape.mExtra = Nif::ExtraPtr(&mNiStringExtraData); - mNiTriShape.mParents.push_back(&mNiNode); - mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; - - Nif::NIFFile file("test.nif"); - file.mRoots.push_back(&mNiNode); - file.mHash = mHash; - - const auto result = mLoader.load(file); - - Resource::BulletShape expected; - - EXPECT_EQ(*result, expected); - } - TEST_F(TestBulletNifLoader, bsx_editor_marker_flag_disables_collision_for_markers) { - mNiIntegerExtraData.mData = 34; // BSXFlags "has collision" | "editor marker" - mNiIntegerExtraData.recType = Nif::RC_BSXFlags; - mNiTriShape.mExtraList.push_back(Nif::ExtraPtr(&mNiIntegerExtraData)); mNiTriShape.mParents.push_back(&mNiNode); mNiTriShape.mName = "EditorMarker"; + mNiIntegerExtraData.mData = 34; // BSXFlags "has collision" | "editor marker" + mNiIntegerExtraData.recType = Nif::RC_BSXFlags; + mNiNode.mExtraList.push_back(Nif::ExtraPtr(&mNiIntegerExtraData)); mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; Nif::NIFFile file("test.nif"); @@ -1162,18 +1147,14 @@ namespace EXPECT_EQ(*result, expected); } - TEST_F(TestBulletNifLoader, - for_tri_shape_child_node_with_extra_data_string_mrk_and_other_collision_node_should_return_shape_with_triangle_mesh_shape_with_all_meshes) + TEST_F(TestBulletNifLoader, mrk_editor_marker_flag_disables_collision_for_markers) { + mNiTriShape.mParents.push_back(&mNiNode); + mNiTriShape.mName = "Tri EditorMarker"; mNiStringExtraData.mData = "MRK"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; - mNiTriShape.mExtra = Nif::ExtraPtr(&mNiStringExtraData); - mNiTriShape.mParents.push_back(&mNiNode2); - mNiNode2.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; - mNiNode2.recType = Nif::RC_RootCollisionNode; - mNiNode2.mParents.push_back(&mNiNode); - mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiNode2) }; - mNiNode.recType = Nif::RC_NiNode; + mNiNode.mExtra = Nif::ExtraPtr(&mNiStringExtraData); + mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; Nif::NIFFile file("test.nif"); file.mRoots.push_back(&mNiNode); @@ -1181,13 +1162,7 @@ namespace const auto result = mLoader.load(file); - std::unique_ptr triangles(new btTriangleMesh(false)); - triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); - std::unique_ptr compound(new btCompoundShape); - compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); - Resource::BulletShape expected; - expected.mCollisionShape.reset(compound.release()); EXPECT_EQ(*result, expected); } diff --git a/apps/openmw_test_suite/nifosg/testnifloader.cpp b/apps/openmw_test_suite/nifosg/testnifloader.cpp index a82fba15ca..f05d651301 100644 --- a/apps/openmw_test_suite/nifosg/testnifloader.cpp +++ b/apps/openmw_test_suite/nifosg/testnifloader.cpp @@ -108,7 +108,7 @@ osg::Group { )"); } - std::string formatOsgNodeForShaderProperty(std::string_view shaderPrefix) + std::string formatOsgNodeForBSShaderProperty(std::string_view shaderPrefix) { std::ostringstream oss; oss << R"( @@ -165,6 +165,72 @@ osg::Group { return oss.str(); } + std::string formatOsgNodeForBSLightingShaderProperty(std::string_view shaderPrefix) + { + std::ostringstream oss; + oss << R"( +osg::Group { + UniqueID 1 + DataVariance STATIC + UserDataContainer TRUE { + osg::DefaultUserDataContainer { + UniqueID 2 + UDC_UserObjects 1 { + osg::StringValueObject { + UniqueID 3 + Name "fileHash" + } + } + } + } + Children 1 { + osg::Group { + UniqueID 4 + DataVariance STATIC + UserDataContainer TRUE { + osg::DefaultUserDataContainer { + UniqueID 5 + UDC_UserObjects 3 { + osg::UIntValueObject { + UniqueID 6 + Name "recIndex" + Value 4294967295 + } + osg::StringValueObject { + UniqueID 7 + Name "shaderPrefix" + Value ")" + << shaderPrefix << R"(" + } + osg::BoolValueObject { + UniqueID 8 + Name "shaderRequired" + Value TRUE + } + } + } + } + StateSet TRUE { + osg::StateSet { + UniqueID 9 + ModeList 1 { + GL_DEPTH_TEST ON + } + AttributeList 1 { + osg::Depth { + UniqueID 10 + } + Value OFF + } + } + } + } + } +} +)"; + return oss.str(); + } + struct ShaderPrefixParams { unsigned int mShaderType; @@ -194,7 +260,7 @@ osg::Group { Nif::NIFFile file("test.nif"); file.mRoots.push_back(&node); auto result = Loader::load(file, &mImageManager); - EXPECT_EQ(serialize(*result), formatOsgNodeForShaderProperty(GetParam().mExpectedShaderPrefix)); + EXPECT_EQ(serialize(*result), formatOsgNodeForBSShaderProperty(GetParam().mExpectedShaderPrefix)); } INSTANTIATE_TEST_SUITE_P(Params, NifOsgLoaderBSShaderPrefixTest, ValuesIn(NifOsgLoaderBSShaderPrefixTest::sParams)); @@ -218,11 +284,13 @@ osg::Group { property.mTextureSet = nullptr; property.mController = nullptr; property.mType = GetParam().mShaderType; + property.mShaderFlags1 |= Nif::BSShaderFlags1::BSSFlag1_DepthTest; + property.mShaderFlags2 |= Nif::BSShaderFlags2::BSSFlag2_DepthWrite; node.mProperties.push_back(Nif::RecordPtrT(&property)); Nif::NIFFile file("test.nif"); file.mRoots.push_back(&node); auto result = Loader::load(file, &mImageManager); - EXPECT_EQ(serialize(*result), formatOsgNodeForShaderProperty(GetParam().mExpectedShaderPrefix)); + EXPECT_EQ(serialize(*result), formatOsgNodeForBSLightingShaderProperty(GetParam().mExpectedShaderPrefix)); } INSTANTIATE_TEST_SUITE_P( diff --git a/components/esm3/aipackage.hpp b/components/esm3/aipackage.hpp index 61aea2750a..7346a4af36 100644 --- a/components/esm3/aipackage.hpp +++ b/components/esm3/aipackage.hpp @@ -16,10 +16,10 @@ namespace ESM struct AIData { - unsigned short mHello; // This is the base value for greeting distance [0, 65535] + uint16_t mHello; // This is the base value for greeting distance [0, 65535] unsigned char mFight, mFlee, mAlarm; // These are probabilities [0, 100] char mU1, mU2, mU3; // Unknown values - int mServices; // See the Services enum + int32_t mServices; // See the Services enum void blank(); ///< Set record to default state (does not touch the ID). @@ -27,8 +27,8 @@ namespace ESM struct AIWander { - short mDistance; - short mDuration; + int16_t mDistance; + int16_t mDuration; unsigned char mTimeOfDay; unsigned char mIdle[8]; unsigned char mShouldRepeat; @@ -44,7 +44,7 @@ namespace ESM struct AITarget { float mX, mY, mZ; - short mDuration; + int16_t mDuration; NAME32 mId; unsigned char mShouldRepeat; unsigned char mPadding; diff --git a/components/esm3/aisequence.cpp b/components/esm3/aisequence.cpp index 668b36c871..21973acd1d 100644 --- a/components/esm3/aisequence.cpp +++ b/components/esm3/aisequence.cpp @@ -196,7 +196,7 @@ namespace ESM int count = 0; while (esm.isNextSub("AIPK")) { - int type; + int32_t type; esm.getHT(type); mPackages.emplace_back(); diff --git a/components/esm3/creaturelevliststate.hpp b/components/esm3/creaturelevliststate.hpp index f8fb7162ff..e7121cf8ac 100644 --- a/components/esm3/creaturelevliststate.hpp +++ b/components/esm3/creaturelevliststate.hpp @@ -9,7 +9,7 @@ namespace ESM struct CreatureLevListState final : public ObjectState { - int mSpawnActorId; + int32_t mSpawnActorId; bool mSpawn; void load(ESMReader& esm) override; diff --git a/components/esm3/creaturestats.hpp b/components/esm3/creaturestats.hpp index 63ba49cdaa..6e65a52354 100644 --- a/components/esm3/creaturestats.hpp +++ b/components/esm3/creaturestats.hpp @@ -47,9 +47,8 @@ namespace ESM std::vector mSummonGraveyard; TimeStamp mTradeTime; - int mGoldPool; - int mActorId; - // int mHitAttemptActorId; + int32_t mGoldPool; + int32_t mActorId; enum Flags { diff --git a/components/esm3/debugprofile.hpp b/components/esm3/debugprofile.hpp index fc48fb23f6..a86e84bfd5 100644 --- a/components/esm3/debugprofile.hpp +++ b/components/esm3/debugprofile.hpp @@ -24,14 +24,14 @@ namespace ESM Flag_Global = 4 // make available from main menu (i.e. not location specific) }; - unsigned int mRecordFlags; + uint32_t mRecordFlags; RefId mId; std::string mDescription; std::string mScriptText; - unsigned int mFlags; + uint32_t mFlags; void load(ESMReader& esm, bool& isDeleted); void save(ESMWriter& esm, bool isDeleted = false) const; diff --git a/components/esm3/doorstate.hpp b/components/esm3/doorstate.hpp index 5298327707..c23ffd5ad2 100644 --- a/components/esm3/doorstate.hpp +++ b/components/esm3/doorstate.hpp @@ -9,7 +9,7 @@ namespace ESM struct DoorState final : public ObjectState { - int mDoorState = 0; + int32_t mDoorState = 0; void load(ESMReader& esm) override; void save(ESMWriter& esm, bool inInventory = false) const override; diff --git a/components/esm3/filter.hpp b/components/esm3/filter.hpp index c4642285af..6a978a2596 100644 --- a/components/esm3/filter.hpp +++ b/components/esm3/filter.hpp @@ -17,7 +17,7 @@ namespace ESM static constexpr std::string_view getRecordType() { return "Filter"; } - unsigned int mRecordFlags; + uint32_t mRecordFlags; RefId mId; std::string mDescription; diff --git a/components/esm3/inventorystate.cpp b/components/esm3/inventorystate.cpp index e58d0335bf..84a52ff518 100644 --- a/components/esm3/inventorystate.cpp +++ b/components/esm3/inventorystate.cpp @@ -7,22 +7,25 @@ namespace ESM { + namespace + { + constexpr uint32_t sInvalidSlot = static_cast(-1); + } void InventoryState::load(ESMReader& esm) { // obsolete - int index = 0; + uint32_t index = 0; while (esm.isNextSub("IOBJ")) { - int unused; // no longer used - esm.getHT(unused); + esm.skipHT(); ObjectState state; // obsolete if (esm.isNextSub("SLOT")) { - int slot; + int32_t slot; esm.getHT(slot); mEquipmentSlots[index] = slot; } @@ -38,9 +41,9 @@ namespace ESM ++index; } - int itemsCount = 0; + uint32_t itemsCount = 0; esm.getHNOT(itemsCount, "ICNT"); - for (int i = 0; i < itemsCount; i++) + for (; itemsCount > 0; --itemsCount) { ObjectState state; @@ -62,7 +65,7 @@ namespace ESM { // Get its name ESM::RefId id = esm.getRefId(); - int count; + int32_t count; std::string parentGroup; // Then get its count esm.getHNT(count, "COUN"); @@ -91,9 +94,9 @@ namespace ESM while (esm.isNextSub("EQUI")) { esm.getSubHeader(); - int equipIndex; + int32_t equipIndex; esm.getT(equipIndex); - int slot; + int32_t slot; esm.getT(slot); mEquipmentSlots[equipIndex] = slot; } @@ -101,20 +104,24 @@ namespace ESM if (esm.isNextSub("EQIP")) { esm.getSubHeader(); - int slotsCount = 0; + uint32_t slotsCount = 0; esm.getT(slotsCount); - for (int i = 0; i < slotsCount; i++) + for (; slotsCount > 0; --slotsCount) { - int equipIndex; + int32_t equipIndex; esm.getT(equipIndex); - int slot; + int32_t slot; esm.getT(slot); mEquipmentSlots[equipIndex] = slot; } } - mSelectedEnchantItem = -1; - esm.getHNOT(mSelectedEnchantItem, "SELE"); + uint32_t selectedEnchantItem = sInvalidSlot; + esm.getHNOT(selectedEnchantItem, "SELE"); + if (selectedEnchantItem == sInvalidSlot) + mSelectedEnchantItem.reset(); + else + mSelectedEnchantItem = selectedEnchantItem; // Old saves had restocking levelled items in a special map // This turns items from that map into negative quantities @@ -132,7 +139,7 @@ namespace ESM void InventoryState::save(ESMWriter& esm) const { - int itemsCount = static_cast(mItems.size()); + uint32_t itemsCount = static_cast(mItems.size()); if (itemsCount > 0) { esm.writeHNT("ICNT", itemsCount); @@ -149,34 +156,32 @@ namespace ESM esm.writeHNString("LGRP", it->first.second); } - for (TEffectMagnitudes::const_iterator it = mPermanentMagicEffectMagnitudes.begin(); - it != mPermanentMagicEffectMagnitudes.end(); ++it) + for (const auto& [id, params] : mPermanentMagicEffectMagnitudes) { - esm.writeHNRefId("MAGI", it->first); + esm.writeHNRefId("MAGI", id); - const std::vector>& params = it->second; - for (std::vector>::const_iterator pIt = params.begin(); pIt != params.end(); ++pIt) + for (const auto& [rand, mult] : params) { - esm.writeHNT("RAND", pIt->first); - esm.writeHNT("MULT", pIt->second); + esm.writeHNT("RAND", rand); + esm.writeHNT("MULT", mult); } } - int slotsCount = static_cast(mEquipmentSlots.size()); + uint32_t slotsCount = static_cast(mEquipmentSlots.size()); if (slotsCount > 0) { esm.startSubRecord("EQIP"); esm.writeT(slotsCount); - for (std::map::const_iterator it = mEquipmentSlots.begin(); it != mEquipmentSlots.end(); ++it) + for (const auto& [index, slot] : mEquipmentSlots) { - esm.writeT(it->first); - esm.writeT(it->second); + esm.writeT(index); + esm.writeT(slot); } esm.endRecord("EQIP"); } - if (mSelectedEnchantItem != -1) - esm.writeHNT("SELE", mSelectedEnchantItem); + if (mSelectedEnchantItem) + esm.writeHNT("SELE", *mSelectedEnchantItem); } } diff --git a/components/esm3/inventorystate.hpp b/components/esm3/inventorystate.hpp index a0fd948951..050d1eb92f 100644 --- a/components/esm3/inventorystate.hpp +++ b/components/esm3/inventorystate.hpp @@ -2,6 +2,7 @@ #define OPENMW_ESM_INVENTORYSTATE_H #include +#include #include "objectstate.hpp" #include @@ -19,20 +20,15 @@ namespace ESM std::vector mItems; // - std::map mEquipmentSlots; + std::map mEquipmentSlots; - std::map, int> mLevelledItemMap; + std::map, int32_t> mLevelledItemMap; - typedef std::map>> TEffectMagnitudes; - TEffectMagnitudes mPermanentMagicEffectMagnitudes; + std::map>> mPermanentMagicEffectMagnitudes; - int mSelectedEnchantItem; // For inventories only + std::optional mSelectedEnchantItem; // For inventories only - InventoryState() - : mSelectedEnchantItem(-1) - { - } - virtual ~InventoryState() {} + virtual ~InventoryState() = default; virtual void load(ESMReader& esm); virtual void save(ESMWriter& esm) const; diff --git a/components/esm3/loadcell.cpp b/components/esm3/loadcell.cpp index b966338ae5..829cf9e916 100644 --- a/components/esm3/loadcell.cpp +++ b/components/esm3/loadcell.cpp @@ -17,7 +17,7 @@ namespace ESM ///< Translate 8bit/24bit code (stored in refNum.mIndex) into a proper refNum void adjustRefNum(RefNum& refNum, const ESMReader& reader) { - unsigned int local = (refNum.mIndex & 0xff000000) >> 24; + uint32_t local = (refNum.mIndex & 0xff000000) >> 24; // If we have an index value that does not make sense, assume that it was an addition // by the present plugin (but a faulty one) @@ -124,7 +124,7 @@ namespace ESM switch (esm.retSubName().toInt()) { case fourCC("INTV"): - int waterl; + int32_t waterl; esm.getHT(waterl); mWater = static_cast(waterl); mWaterInt = true; @@ -192,7 +192,7 @@ namespace ESM { if (mWaterInt) { - int water = (mWater >= 0) ? (int)(mWater + 0.5) : (int)(mWater - 0.5); + int32_t water = (mWater >= 0) ? static_cast(mWater + 0.5) : static_cast(mWater - 0.5); esm.writeHNT("INTV", water); } else @@ -218,13 +218,13 @@ namespace ESM } } - void Cell::saveTempMarker(ESMWriter& esm, int tempCount) const + void Cell::saveTempMarker(ESMWriter& esm, int32_t tempCount) const { if (tempCount != 0) esm.writeHNT("NAM0", tempCount); } - void Cell::restore(ESMReader& esm, int iCtx) const + void Cell::restore(ESMReader& esm, size_t iCtx) const { esm.restoreContext(mContextList.at(iCtx)); } @@ -321,7 +321,7 @@ namespace ESM void Cell::blank() { - mName = ""; + mName.clear(); mRegion = ESM::RefId(); mWater = 0; mWaterInt = false; diff --git a/components/esm3/loadcell.hpp b/components/esm3/loadcell.hpp index 0ba0777e7c..bfabdd58f9 100644 --- a/components/esm3/loadcell.hpp +++ b/components/esm3/loadcell.hpp @@ -34,7 +34,7 @@ namespace ESM RefNum mRefNum; // Coordinates of target exterior cell - int mTarget[2]; + int32_t mTarget[2]; // The content file format does not support moving objects to an interior cell. // The save game format does support moving to interior cells, but uses a different mechanism @@ -153,13 +153,13 @@ namespace ESM ESMReader& esm, bool saveContext = true); // Load everything, except NAME, DATAstruct and references void save(ESMWriter& esm, bool isDeleted = false) const; - void saveTempMarker(ESMWriter& esm, int tempCount) const; + void saveTempMarker(ESMWriter& esm, int32_t tempCount) const; bool isExterior() const { return !(mData.mFlags & Interior); } - int getGridX() const { return mData.mX; } + int32_t getGridX() const { return mData.mX; } - int getGridY() const { return mData.mY; } + int32_t getGridY() const { return mData.mY; } bool hasWater() const { return ((mData.mFlags & HasWater) != 0) || isExterior(); } @@ -172,7 +172,7 @@ namespace ESM // somewhere other than the file system, you need to pre-open the // ESMReader, and the filename must match the stored filename // exactly. - void restore(ESMReader& esm, int iCtx) const; + void restore(ESMReader& esm, size_t iCtx) const; std::string getDescription() const; ///< Return a short string describing the cell (mostly used for debugging/logging purpose) diff --git a/components/esm3/loadcont.hpp b/components/esm3/loadcont.hpp index 3921821da0..3c1fb6468e 100644 --- a/components/esm3/loadcont.hpp +++ b/components/esm3/loadcont.hpp @@ -19,7 +19,7 @@ namespace ESM struct ContItem { - int mCount{ 0 }; + int32_t mCount{ 0 }; ESM::RefId mItem; }; @@ -48,12 +48,12 @@ namespace ESM Unknown = 8 }; - unsigned int mRecordFlags; + uint32_t mRecordFlags; RefId mId, mScript; std::string mName, mModel; float mWeight; // Not sure, might be max total weight allowed? - int mFlags; + int32_t mFlags; InventoryList mInventory; void load(ESMReader& esm, bool& isDeleted); diff --git a/components/esm3/loadglob.hpp b/components/esm3/loadglob.hpp index eed53a5f7b..c78bc83917 100644 --- a/components/esm3/loadglob.hpp +++ b/components/esm3/loadglob.hpp @@ -25,7 +25,7 @@ namespace ESM /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Global"; } - unsigned int mRecordFlags; + uint32_t mRecordFlags; ESM::RefId mId; Variant mValue; diff --git a/components/esm3/loadgmst.hpp b/components/esm3/loadgmst.hpp index 72d30b9ea9..c827be5b6b 100644 --- a/components/esm3/loadgmst.hpp +++ b/components/esm3/loadgmst.hpp @@ -26,7 +26,7 @@ namespace ESM /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "GameSetting"; } - unsigned int mRecordFlags; + uint32_t mRecordFlags; RefId mId; Variant mValue; diff --git a/components/esm3/loadlevlist.cpp b/components/esm3/loadlevlist.cpp index 970174ada2..627edbadce 100644 --- a/components/esm3/loadlevlist.cpp +++ b/components/esm3/loadlevlist.cpp @@ -30,7 +30,7 @@ namespace ESM break; case fourCC("INDX"): { - int length = 0; + uint32_t length = 0; esm.getHT(length); mList.resize(length); @@ -87,12 +87,12 @@ namespace ESM esm.writeHNT("DATA", mFlags); esm.writeHNT("NNAM", mChanceNone); - esm.writeHNT("INDX", mList.size()); + esm.writeHNT("INDX", mList.size()); - for (std::vector::const_iterator it = mList.begin(); it != mList.end(); ++it) + for (const auto& item : mList) { - esm.writeHNCRefId(recName, it->mId); - esm.writeHNT("INTV", it->mLevel); + esm.writeHNCRefId(recName, item.mId); + esm.writeHNT("INTV", item.mLevel); } } diff --git a/components/esm3/loadlevlist.hpp b/components/esm3/loadlevlist.hpp index 809c89102e..536b865d90 100644 --- a/components/esm3/loadlevlist.hpp +++ b/components/esm3/loadlevlist.hpp @@ -24,15 +24,15 @@ namespace ESM struct LevelledListBase { - int mFlags; + int32_t mFlags; unsigned char mChanceNone; // Chance that none are selected (0-100) - unsigned int mRecordFlags; + uint32_t mRecordFlags; RefId mId; struct LevelItem { RefId mId; - short mLevel; + uint16_t mLevel; }; std::vector mList; diff --git a/components/esm3/loadltex.hpp b/components/esm3/loadltex.hpp index e6dc2de73e..fb95e8b9ed 100644 --- a/components/esm3/loadltex.hpp +++ b/components/esm3/loadltex.hpp @@ -29,7 +29,7 @@ namespace ESM // mId is merely a user friendly name for the texture in the editor. std::string mTexture; RefId mId; - int mIndex; + int32_t mIndex; void load(ESMReader& esm, bool& isDeleted); void save(ESMWriter& esm, bool isDeleted = false) const; diff --git a/components/esm3/loadmgef.cpp b/components/esm3/loadmgef.cpp index 7d3879a341..686afbc34a 100644 --- a/components/esm3/loadmgef.cpp +++ b/components/esm3/loadmgef.cpp @@ -36,7 +36,7 @@ namespace ESM esm.getSubNameIs("MEDT"); esm.getSubHeader(); - int school; + int32_t school; esm.getT(school); mData.mSchool = MagicSchool::indexToSkillRefId(school); esm.getT(mData.mBaseCost); diff --git a/components/esm3/loadmgef.hpp b/components/esm3/loadmgef.hpp index 9d68bad609..25ec7d0655 100644 --- a/components/esm3/loadmgef.hpp +++ b/components/esm3/loadmgef.hpp @@ -25,7 +25,7 @@ namespace ESM /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "MagicEffect"; } - unsigned int mRecordFlags; + uint32_t mRecordFlags; RefId mId; enum Flags @@ -74,9 +74,9 @@ namespace ESM { RefId mSchool; // Skill id float mBaseCost; - int mFlags; + int32_t mFlags; // Glow color for enchanted items with this effect - int mRed, mGreen, mBlue; + int32_t mRed, mGreen, mBlue; float mUnknown1; // Called "Size X" in CS float mSpeed; // Speed of fired projectile @@ -107,7 +107,7 @@ namespace ESM // there. They can be redefined in mods by setting the name in GMST // sEffectSummonCreature04/05 creature id in // sMagicCreature04ID/05ID. - int mIndex; + int32_t mIndex; void load(ESMReader& esm, bool& isDeleted); void save(ESMWriter& esm, bool isDeleted = false) const; diff --git a/components/esm3/loadnpc.cpp b/components/esm3/loadnpc.cpp index 24a79d2e4c..d844f7d2bc 100644 --- a/components/esm3/loadnpc.cpp +++ b/components/esm3/loadnpc.cpp @@ -82,7 +82,7 @@ namespace ESM break; case fourCC("FLAG"): hasFlags = true; - int flags; + int32_t flags; esm.getHT(flags); mFlags = flags & 0xFF; mBloodType = ((flags >> 8) & 0xFF) >> 2; diff --git a/components/esm3/loadnpc.hpp b/components/esm3/loadnpc.hpp index f0d726434b..af8c2a8574 100644 --- a/components/esm3/loadnpc.hpp +++ b/components/esm3/loadnpc.hpp @@ -79,28 +79,28 @@ namespace ESM struct NPDTstruct52 { - short mLevel; + int16_t mLevel; unsigned char mStrength, mIntelligence, mWillpower, mAgility, mSpeed, mEndurance, mPersonality, mLuck; // mSkill can grow up to 200, it must be unsigned std::array mSkills; char mUnknown1; - unsigned short mHealth, mMana, mFatigue; + uint16_t mHealth, mMana, mFatigue; unsigned char mDisposition, mReputation, mRank; char mUnknown2; - int mGold; + int32_t mGold; }; // 52 bytes // Structure for autocalculated characters. // This is only used for load and save operations. struct NPDTstruct12 { - short mLevel; + int16_t mLevel; // see above unsigned char mDisposition, mReputation, mRank; char mUnknown1, mUnknown2, mUnknown3; - int mGold; + int32_t mGold; }; // 12 bytes #pragma pack(pop) @@ -111,7 +111,7 @@ namespace ESM int getFactionRank() const; /// wrapper for mNpdt*, -1 = no rank - int mBloodType; + int32_t mBloodType; unsigned char mFlags; InventoryList mInventory; @@ -125,7 +125,7 @@ namespace ESM AIPackageList mAiPackage; - unsigned int mRecordFlags; + uint32_t mRecordFlags; RefId mId, mRace, mClass, mFaction, mScript; std::string mModel, mName; diff --git a/components/esm3/loadrace.hpp b/components/esm3/loadrace.hpp index 8dd60bdef1..8cb9d76118 100644 --- a/components/esm3/loadrace.hpp +++ b/components/esm3/loadrace.hpp @@ -27,13 +27,13 @@ namespace ESM struct SkillBonus { - int mSkill; // SkillEnum - int mBonus; + int32_t mSkill; // SkillEnum + int32_t mBonus; }; struct MaleFemale { - int mMale, mFemale; + int32_t mMale, mFemale; int getValue(bool male) const; }; @@ -63,13 +63,13 @@ namespace ESM // as 'height' times 128. This has not been tested yet. MaleFemaleF mHeight, mWeight; - int mFlags; // 0x1 - playable, 0x2 - beast race + int32_t mFlags; // 0x1 - playable, 0x2 - beast race }; // Size = 140 bytes RADTstruct mData; - unsigned int mRecordFlags; + uint32_t mRecordFlags; std::string mName, mDescription; RefId mId; SpellList mPowers; diff --git a/components/esm3/loadsndg.hpp b/components/esm3/loadsndg.hpp index fff4b98439..3337220d9d 100644 --- a/components/esm3/loadsndg.hpp +++ b/components/esm3/loadsndg.hpp @@ -36,9 +36,9 @@ namespace ESM }; // Type - int mType; + int32_t mType; - unsigned int mRecordFlags; + uint32_t mRecordFlags; RefId mId, mCreature, mSound; void load(ESMReader& esm, bool& isDeleted); diff --git a/components/esm3/loadsscr.hpp b/components/esm3/loadsscr.hpp index 6c9163e4e6..34349d8ea4 100644 --- a/components/esm3/loadsscr.hpp +++ b/components/esm3/loadsscr.hpp @@ -28,7 +28,7 @@ namespace ESM static std::string_view getRecordType() { return "StartScript"; } std::string mData; - unsigned int mRecordFlags; + uint32_t mRecordFlags; RefId mId; // Load a record and add it to the list diff --git a/components/esm3/loadstat.hpp b/components/esm3/loadstat.hpp index abe589563b..4c0341f4ea 100644 --- a/components/esm3/loadstat.hpp +++ b/components/esm3/loadstat.hpp @@ -31,7 +31,7 @@ namespace ESM /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Static"; } - unsigned int mRecordFlags; + uint32_t mRecordFlags; RefId mId; std::string mModel; diff --git a/components/esm3/loadtes3.hpp b/components/esm3/loadtes3.hpp index 7927b35cee..8b14d41645 100644 --- a/components/esm3/loadtes3.hpp +++ b/components/esm3/loadtes3.hpp @@ -20,11 +20,11 @@ namespace ESM versions are 1.2 and 1.3. These correspond to: 1.2 = 0x3f99999a and 1.3 = 0x3fa66666 */ - unsigned int version; - int type; // 0=esp, 1=esm, 32=ess (unused) + uint32_t version; + int32_t type; // 0=esp, 1=esm, 32=ess (unused) std::string author; // Author's name std::string desc; // File description - int records; // Number of records + int32_t records; // Number of records }; struct GMDT diff --git a/components/esm3/magiceffects.cpp b/components/esm3/magiceffects.cpp index 346cc2c568..a8a759949b 100644 --- a/components/esm3/magiceffects.cpp +++ b/components/esm3/magiceffects.cpp @@ -20,8 +20,8 @@ namespace ESM { while (esm.isNextSub("EFID")) { - int id; - std::pair params; + int32_t id; + std::pair params; esm.getHT(id); esm.getHNT(params.first, "BASE"); if (esm.getFormatVersion() <= MaxClearModifiersFormatVersion) diff --git a/components/esm3/magiceffects.hpp b/components/esm3/magiceffects.hpp index 3f141355c8..74a6e34743 100644 --- a/components/esm3/magiceffects.hpp +++ b/components/esm3/magiceffects.hpp @@ -16,7 +16,7 @@ namespace ESM struct MagicEffects { // - std::map> mEffects; + std::map> mEffects; void load(ESMReader& esm); void save(ESMWriter& esm) const; @@ -24,16 +24,16 @@ namespace ESM struct SummonKey { - SummonKey(int effectId, const ESM::RefId& sourceId, int index) + SummonKey(int32_t effectId, const ESM::RefId& sourceId, int32_t index) : mEffectId(effectId) , mSourceId(sourceId) , mEffectIndex(index) { } - int mEffectId; + int32_t mEffectId; ESM::RefId mSourceId; - int mEffectIndex; + int32_t mEffectIndex; }; inline auto makeTupleRef(const SummonKey& value) noexcept diff --git a/components/esm3/npcstats.cpp b/components/esm3/npcstats.cpp index c34205f6c2..a21ba807e4 100644 --- a/components/esm3/npcstats.cpp +++ b/components/esm3/npcstats.cpp @@ -21,7 +21,7 @@ namespace ESM Faction faction; - int expelled = 0; + int32_t expelled = 0; esm.getHNOT(expelled, "FAEX"); if (expelled) @@ -75,7 +75,7 @@ namespace ESM esm.getHNOT(hasWerewolfAttributes, "HWAT"); if (hasWerewolfAttributes) { - StatState dummy; + StatState dummy; for (int i = 0; i < ESM::Attribute::Length; ++i) dummy.load(esm, intFallback); mWerewolfDeprecatedData = true; @@ -130,21 +130,21 @@ namespace ESM void NpcStats::save(ESMWriter& esm) const { - for (auto iter(mFactions.begin()); iter != mFactions.end(); ++iter) + for (const auto& [id, faction] : mFactions) { - esm.writeHNRefId("FACT", iter->first); + esm.writeHNRefId("FACT", id); - if (iter->second.mExpelled) + if (faction.mExpelled) { - int expelled = 1; + int32_t expelled = 1; esm.writeHNT("FAEX", expelled); } - if (iter->second.mRank >= 0) - esm.writeHNT("FARA", iter->second.mRank); + if (faction.mRank >= 0) + esm.writeHNT("FARA", faction.mRank); - if (iter->second.mReputation) - esm.writeHNT("FARE", iter->second.mReputation); + if (faction.mReputation) + esm.writeHNT("FARE", faction.mReputation); } if (mDisposition) @@ -169,7 +169,7 @@ namespace ESM esm.writeHNT("LPRO", mLevelProgress); bool saveSkillIncreases = false; - for (int increase : mSkillIncrease) + for (int32_t increase : mSkillIncrease) { if (increase != 0) { @@ -183,8 +183,8 @@ namespace ESM if (mSpecIncreases[0] != 0 || mSpecIncreases[1] != 0 || mSpecIncreases[2] != 0) esm.writeHNT("SPEC", mSpecIncreases); - for (auto iter(mUsedIds.begin()); iter != mUsedIds.end(); ++iter) - esm.writeHNRefId("USED", *iter); + for (const RefId& id : mUsedIds) + esm.writeHNRefId("USED", id); if (mTimeToStartDrowning) esm.writeHNT("DRTI", mTimeToStartDrowning); diff --git a/components/esm3/npcstats.hpp b/components/esm3/npcstats.hpp index e80ec04c25..ccb58a12ad 100644 --- a/components/esm3/npcstats.hpp +++ b/components/esm3/npcstats.hpp @@ -23,8 +23,8 @@ namespace ESM struct Faction { bool mExpelled; - int mRank; - int mReputation; + int32_t mRank; + int32_t mReputation; Faction(); }; @@ -33,18 +33,18 @@ namespace ESM bool mWerewolfDeprecatedData; - std::map mFactions; // lower case IDs - int mDisposition; + std::map mFactions; + int32_t mDisposition; std::array, ESM::Skill::Length> mSkills; - int mBounty; - int mReputation; - int mWerewolfKills; - int mLevelProgress; - std::array mSkillIncrease; - std::array mSpecIncreases; - std::vector mUsedIds; // lower case IDs + int32_t mBounty; + int32_t mReputation; + int32_t mWerewolfKills; + int32_t mLevelProgress; + std::array mSkillIncrease; + std::array mSpecIncreases; + std::vector mUsedIds; float mTimeToStartDrowning; - int mCrimeId; + int32_t mCrimeId; /// Initialize to default state void blank(); diff --git a/components/esm3/objectstate.cpp b/components/esm3/objectstate.cpp index a56988843a..a7fe41d66c 100644 --- a/components/esm3/objectstate.cpp +++ b/components/esm3/objectstate.cpp @@ -49,7 +49,7 @@ namespace ESM esm.getHNOT(mFlags, "FLAG"); // obsolete - int unused; + int32_t unused; esm.getHNOT(unused, "LTIM"); mAnimationState.load(esm); @@ -179,6 +179,6 @@ namespace ESM throw std::logic_error(error.str()); } - ObjectState::~ObjectState() {} + ObjectState::~ObjectState() = default; } diff --git a/components/esm3/objectstate.hpp b/components/esm3/objectstate.hpp index 67f4d27706..4c09d16d18 100644 --- a/components/esm3/objectstate.hpp +++ b/components/esm3/objectstate.hpp @@ -32,9 +32,9 @@ namespace ESM Locals mLocals; LuaScripts mLuaScripts; unsigned char mEnabled; - int mCount; + int32_t mCount; Position mPosition; - unsigned int mFlags; + uint32_t mFlags; // Is there any class-specific state following the ObjectState bool mHasCustomState; diff --git a/components/esm3/player.hpp b/components/esm3/player.hpp index f691f22e86..7f9309765c 100644 --- a/components/esm3/player.hpp +++ b/components/esm3/player.hpp @@ -27,8 +27,8 @@ namespace ESM RefId mMarkedCell; ESM::RefId mBirthsign; - int mCurrentCrimeId; - int mPaidCrimeId; + int32_t mCurrentCrimeId; + int32_t mPaidCrimeId; float mSaveAttributes[Attribute::Length]; float mSaveSkills[Skill::Length]; diff --git a/components/esm3/projectilestate.hpp b/components/esm3/projectilestate.hpp index 7a7651f364..cab550b114 100644 --- a/components/esm3/projectilestate.hpp +++ b/components/esm3/projectilestate.hpp @@ -23,7 +23,7 @@ namespace ESM Vector3 mPosition; Quaternion mOrientation; - int mActorId; + int32_t mActorId; void load(ESMReader& esm); void save(ESMWriter& esm) const; diff --git a/components/esm3/queststate.hpp b/components/esm3/queststate.hpp index 5858714df0..6d9fd6c4fb 100644 --- a/components/esm3/queststate.hpp +++ b/components/esm3/queststate.hpp @@ -14,7 +14,7 @@ namespace ESM struct QuestState { ESM::RefId mTopic; // lower case id - int mState; + int32_t mState; unsigned char mFinished; void load(ESMReader& esm); diff --git a/components/esm3/savedgame.hpp b/components/esm3/savedgame.hpp index 2048244ac2..4632e98927 100644 --- a/components/esm3/savedgame.hpp +++ b/components/esm3/savedgame.hpp @@ -20,7 +20,7 @@ namespace ESM std::vector mContentFiles; std::string mPlayerName; - int mPlayerLevel; + int32_t mPlayerLevel; // ID of class ESM::RefId mPlayerClassId; @@ -34,7 +34,7 @@ namespace ESM std::string mDescription; std::vector mScreenshot; // raw jpg-encoded data - int mCurrentDay = 0; + int32_t mCurrentDay = 0; float mCurrentHealth = 0; float mMaximumHealth = 0; diff --git a/components/esm3/statstate.cpp b/components/esm3/statstate.cpp index b5ddc54985..7477d83e2d 100644 --- a/components/esm3/statstate.cpp +++ b/components/esm3/statstate.cpp @@ -21,19 +21,19 @@ namespace ESM // We changed stats values from integers to floats; ensure backwards compatibility if (intFallback) { - int base = 0; + int32_t base = 0; esm.getHNT(base, "STBA"); mBase = static_cast(base); - int mod = 0; + int32_t mod = 0; esm.getHNOT(mod, "STMO"); mMod = static_cast(mod); - int current = 0; + int32_t current = 0; esm.getHNOT(current, "STCU"); mCurrent = static_cast(current); - int oldDamage = 0; + int32_t oldDamage = 0; esm.getHNOT(oldDamage, "STDA"); mDamage = static_cast(oldDamage); } diff --git a/components/esm3/variant.cpp b/components/esm3/variant.cpp index 3d5daa2cb3..48621818eb 100644 --- a/components/esm3/variant.cpp +++ b/components/esm3/variant.cpp @@ -17,7 +17,7 @@ namespace ESM template struct GetValue { - constexpr T operator()(int value) const { return static_cast(value); } + constexpr T operator()(int32_t value) const { return static_cast(value); } constexpr T operator()(float value) const { return static_cast(value); } @@ -41,7 +41,7 @@ namespace ESM { } - void operator()(int& value) const { value = static_cast(mValue); } + void operator()(int32_t& value) const { value = static_cast(mValue); } void operator()(float& value) const { value = static_cast(mValue); } @@ -58,9 +58,9 @@ namespace ESM return std::get(mData); } - int Variant::getInteger() const + int32_t Variant::getInteger() const { - return std::visit(GetValue{}, mData); + return std::visit(GetValue{}, mData); } float Variant::getFloat() const @@ -194,17 +194,17 @@ namespace ESM case VT_Short: - stream << "variant short: " << std::get(mData); + stream << "variant short: " << std::get(mData); break; case VT_Int: - stream << "variant int: " << std::get(mData); + stream << "variant int: " << std::get(mData); break; case VT_Long: - stream << "variant long: " << std::get(mData); + stream << "variant long: " << std::get(mData); break; case VT_Float: @@ -259,7 +259,7 @@ namespace ESM std::get(mData) = std::move(value); } - void Variant::setInteger(int value) + void Variant::setInteger(int32_t value) { std::visit(SetValue(value), mData); } diff --git a/components/esm3/variant.hpp b/components/esm3/variant.hpp index d00ccb2746..ed72b1dc05 100644 --- a/components/esm3/variant.hpp +++ b/components/esm3/variant.hpp @@ -25,7 +25,7 @@ namespace ESM class Variant { VarType mType; - std::variant mData; + std::variant mData; public: enum Format @@ -54,7 +54,7 @@ namespace ESM { } - explicit Variant(int value) + explicit Variant(int32_t value) : mType(VT_Long) , mData(value) { @@ -71,7 +71,7 @@ namespace ESM const std::string& getString() const; ///< Will throw an exception, if value can not be represented as a string. - int getInteger() const; + int32_t getInteger() const; ///< Will throw an exception, if value can not be represented as an integer (implicit /// casting of float values is permitted). @@ -93,7 +93,7 @@ namespace ESM void setString(std::string&& value); ///< Will throw an exception, if type is not compatible with string. - void setInteger(int value); + void setInteger(int32_t value); ///< Will throw an exception, if type is not compatible with integer. void setFloat(float value); diff --git a/components/esm3/variantimp.cpp b/components/esm3/variantimp.cpp index 410f4ade8d..31248556ec 100644 --- a/components/esm3/variantimp.cpp +++ b/components/esm3/variantimp.cpp @@ -46,7 +46,7 @@ namespace ESM esm.writeHNString("STRV", in); } - void readESMVariantValue(ESMReader& esm, Variant::Format format, VarType type, int& out) + void readESMVariantValue(ESMReader& esm, Variant::Format format, VarType type, int32_t& out) { if (type != VT_Short && type != VT_Long && type != VT_Int) throw std::logic_error("not an integer type"); @@ -60,9 +60,9 @@ namespace ESM if (std::isnan(value)) out = 0; else - out = static_cast(value); + out = static_cast(value); else if (type == VT_Long) - out = static_cast(value); + out = static_cast(value); else esm.fail("unsupported global variable integer type"); } @@ -82,7 +82,7 @@ namespace ESM { if (type == VT_Short) { - short value; + int16_t value; esm.getHT(value); out = value; } @@ -95,7 +95,7 @@ namespace ESM } } - void writeESMVariantValue(ESMWriter& esm, Variant::Format format, VarType type, int in) + void writeESMVariantValue(ESMWriter& esm, Variant::Format format, VarType type, int32_t in) { if (type != VT_Short && type != VT_Long && type != VT_Int) throw std::logic_error("not an integer type"); @@ -126,7 +126,7 @@ namespace ESM else if (format == Variant::Format_Local) { if (type == VT_Short) - esm.writeHNT("STTV", static_cast(in)); + esm.writeHNT("STTV", static_cast(in)); else if (type == VT_Int) esm.writeHNT("INTV", in); else diff --git a/components/esm3/variantimp.hpp b/components/esm3/variantimp.hpp index 90f45a420f..365cff988a 100644 --- a/components/esm3/variantimp.hpp +++ b/components/esm3/variantimp.hpp @@ -12,13 +12,13 @@ namespace ESM void readESMVariantValue(ESMReader& reader, Variant::Format format, VarType type, float& value); - void readESMVariantValue(ESMReader& reader, Variant::Format format, VarType type, int& value); + void readESMVariantValue(ESMReader& reader, Variant::Format format, VarType type, int32_t& value); void writeESMVariantValue(ESMWriter& writer, Variant::Format format, VarType type, const std::string& value); void writeESMVariantValue(ESMWriter& writer, Variant::Format format, VarType type, float value); - void writeESMVariantValue(ESMWriter& writer, Variant::Format format, VarType type, int value); + void writeESMVariantValue(ESMWriter& writer, Variant::Format format, VarType type, int32_t value); struct ReadESMVariantValue { diff --git a/components/esm4/loadltex.cpp b/components/esm4/loadltex.cpp index 955ac938e3..9b2d12034f 100644 --- a/components/esm4/loadltex.cpp +++ b/components/esm4/loadltex.cpp @@ -76,6 +76,9 @@ void ESM4::LandTexture::load(ESM4::Reader& reader) case ESM4::SUB_MNAM: reader.getFormId(mMaterial); break; // TES5, FO4 + case ESM4::SUB_INAM: + reader.get(mMaterialFlags); + break; // SSE default: throw std::runtime_error("ESM4::LTEX::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } diff --git a/components/esm4/loadltex.hpp b/components/esm4/loadltex.hpp index bed5887f5c..33c1683ac0 100644 --- a/components/esm4/loadltex.hpp +++ b/components/esm4/loadltex.hpp @@ -63,6 +63,17 @@ namespace ESM4 // ---------------------- + // ------ SSE ----------- + + enum MaterialFlags + { + Flag_IsSnow = 0x1, + }; + + std::uint32_t mMaterialFlags; + + // ---------------------- + void load(ESM4::Reader& reader); // void save(ESM4::Writer& writer) const; diff --git a/components/esm4/loadnpc.cpp b/components/esm4/loadnpc.cpp index 251af13630..885263d67b 100644 --- a/components/esm4/loadnpc.cpp +++ b/components/esm4/loadnpc.cpp @@ -116,9 +116,11 @@ void ESM4::Npc::load(ESM4::Reader& reader) { switch (subHdr.dataSize) { + case 20: // FO4 + mIsFO4 = true; + [[fallthrough]]; case 16: // TES4 case 24: // FO3/FNV, TES5 - case 20: // FO4 reader.get(&mBaseConfig, subHdr.dataSize); break; default: diff --git a/components/esm4/loadnpc.hpp b/components/esm4/loadnpc.hpp index 6f4b3c7e24..04e56b7bd9 100644 --- a/components/esm4/loadnpc.hpp +++ b/components/esm4/loadnpc.hpp @@ -78,6 +78,7 @@ namespace ESM4 FO3_NoRotateHead = 0x40000000 }; + // In FO4 flags seem to be the same. enum ACBS_TES5 { TES5_Female = 0x00000001, @@ -101,27 +102,32 @@ namespace ESM4 TES5_Invulnerable = 0x80000000 }; + // All FO3+ games. enum Template_Flags { - TES5_UseTraits = 0x0001, // Destructible Object; Traits tab, including race, gender, height, weight, - // voice type, death item; Sounds tab; Animation tab; Character Gen tabs - TES5_UseStats = 0x0002, // Stats tab, including level, autocalc, skills, health/magicka/stamina, - // speed, bleedout, class - TES5_UseFactions = 0x0004, // both factions and assigned crime faction - TES5_UseSpellList = 0x0008, // both spells and perks - TES5_UseAIData = 0x0010, // AI Data tab, including aggression/confidence/morality, combat style and - // gift filter - TES5_UseAIPackage = 0x0020, // only the basic Packages listed on the AI Packages tab; - // rest of tab controlled by Def Pack List - TES5_UseBaseData = 0x0080, // including name and short name, and flags for Essential, Protected, - // Respawn, Summonable, Simple Actor, and Doesn't affect stealth meter - TES5_UseInventory = 0x0100, // Inventory tab, including all outfits and geared-up item - // -- but not death item - TES5_UseScript = 0x0200, - TES5_UseDefined = 0x0400, // Def Pack List (the dropdown-selected package lists on the AI Packages tab) - TES5_UseAtkData = 0x0800, // Attack Data tab, including override from behavior graph race, - // events, and data) - TES5_UseKeywords = 0x1000 + Template_UseTraits = 0x0001, // Destructible Object; Traits tab, including race, gender, height, weight, + // voice type, death item; Sounds tab; Animation tab; Character Gen tabs + Template_UseStats = 0x0002, // Stats tab, including level, autocalc, skills, health/magicka/stamina, + // speed, bleedout, class + Template_UseFactions = 0x0004, // both factions and assigned crime faction + Template_UseSpellList = 0x0008, // both spells and perks + Template_UseAIData = 0x0010, // AI Data tab, including aggression/confidence/morality, combat style and + // gift filter + Template_UseAIPackage = 0x0020, // only the basic Packages listed on the AI Packages tab; + // rest of tab controlled by Def Pack List + Template_UseModel = 0x0040, // FO3, FONV; probably not used in TES5+ + Template_UseBaseData = 0x0080, // including name and short name, and flags for Essential, Protected, + // Respawn, Summonable, Simple Actor, and Doesn't affect stealth meter + Template_UseInventory = 0x0100, // Inventory tab, including all outfits and geared-up item, + // but not death item + Template_UseScript = 0x0200, + + // The following flags were added in TES5+: + + Template_UseDefined = 0x0400, // Def Pack List (the dropdown-selected package lists on the AI Packages tab) + Template_UseAtkData = 0x0800, // Attack Data tab, including override from behavior graph race, + // events, and data) + Template_UseKeywords = 0x1000 }; #pragma pack(push, 1) @@ -172,6 +178,7 @@ namespace ESM4 bool mIsTES4; bool mIsFONV; + bool mIsFO4 = false; std::string mEditorId; std::string mFullName; diff --git a/components/esm4/loadrace.hpp b/components/esm4/loadrace.hpp index 9cd4d00891..125fefbd9f 100644 --- a/components/esm4/loadrace.hpp +++ b/components/esm4/loadrace.hpp @@ -110,7 +110,7 @@ namespace ESM4 ESM::FormId mId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details - bool mIsTES5; + bool mIsTES5 = false; std::string mEditorId; std::string mFullName; diff --git a/components/fx/pass.cpp b/components/fx/pass.cpp index 7a7329d755..d20813f1ce 100644 --- a/components/fx/pass.cpp +++ b/components/fx/pass.cpp @@ -12,7 +12,6 @@ #include #include -#include #include #include #include @@ -190,6 +189,11 @@ mat4 omw_InvProjectionMatrix() #endif } + vec3 omw_GetNormalsWorldSpace(vec2 uv) + { + return (vec4(omw_GetNormals(uv), 0.0) * omw.viewMatrix).rgb; + } + vec3 omw_GetWorldPosFromUV(vec2 uv) { float depth = omw_GetDepth(uv); @@ -321,9 +325,6 @@ float omw_EstimateFogCoverageFromUV(vec2 uv) if (mBlendEq) stateSet->setAttributeAndModes(new osg::BlendEquation(mBlendEq.value())); - - if (mClearColor) - stateSet->setAttributeAndModes(new SceneUtil::ClearColor(mClearColor.value(), GL_COLOR_BUFFER_BIT)); } void Pass::dirty() @@ -339,7 +340,7 @@ float omw_EstimateFogCoverageFromUV(vec2 uv) if (mCompiled) return; - mLegacyGLSL = technique.getGLSLVersion() != 330; + mLegacyGLSL = technique.getGLSLVersion() < 330; if (mType == Type::Pixel) { diff --git a/components/fx/pass.hpp b/components/fx/pass.hpp index 509127a163..e176afc699 100644 --- a/components/fx/pass.hpp +++ b/components/fx/pass.hpp @@ -72,7 +72,6 @@ namespace fx std::array mRenderTargets; std::string mTarget; - std::optional mClearColor; std::optional mBlendSource; std::optional mBlendDest; diff --git a/components/fx/technique.cpp b/components/fx/technique.cpp index 0b5d784ad9..defb581cfc 100644 --- a/components/fx/technique.cpp +++ b/components/fx/technique.cpp @@ -279,6 +279,7 @@ namespace fx rt.mTarget->setSourceType(GL_UNSIGNED_BYTE); rt.mTarget->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); rt.mTarget->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + rt.mTarget->setName(std::string(mBlockName)); while (!isNext() && !isNext()) { @@ -312,6 +313,8 @@ namespace fx rt.mTarget->setSourceFormat(parseSourceFormat()); else if (key == "mipmaps") rt.mMipMap = parseBool(); + else if (key == "clear_color") + rt.mClearColor = parseVec(); else error(Misc::StringUtils::format("unexpected key '%s'", std::string(key))); @@ -797,9 +800,6 @@ namespace fx if (!pass) pass = std::make_shared(); - bool clear = true; - osg::Vec4f clearColor = { 1, 1, 1, 1 }; - while (!isNext()) { expect("invalid key in block header"); @@ -843,10 +843,6 @@ namespace fx if (blendEq != osg::BlendEquation::FUNC_ADD) pass->mBlendEq = blendEq; } - else if (key == "clear") - clear = parseBool(); - else if (key == "clear_color") - clearColor = parseVec(); else error(Misc::StringUtils::format("unrecognized key '%s' in block header", std::string(key))); @@ -864,9 +860,6 @@ namespace fx return; } - if (clear) - pass->mClearColor = clearColor; - error("malformed block header"); } diff --git a/components/fx/technique.hpp b/components/fx/technique.hpp index 844e4b552a..ed356e0a37 100644 --- a/components/fx/technique.hpp +++ b/components/fx/technique.hpp @@ -54,10 +54,14 @@ namespace fx osg::ref_ptr mRenderTarget; osg::ref_ptr mRenderTexture; bool mResolve = false; + Types::SizeProxy mSize; + bool mMipMap; SubPass(const SubPass& other, const osg::CopyOp& copyOp = osg::CopyOp::SHALLOW_COPY) : mStateSet(new osg::StateSet(*other.mStateSet, copyOp)) , mResolve(other.mResolve) + , mSize(other.mSize) + , mMipMap(other.mMipMap) { if (other.mRenderTarget) mRenderTarget = new osg::FrameBufferObject(*other.mRenderTarget, copyOp); diff --git a/components/fx/types.hpp b/components/fx/types.hpp index 09f191c61c..0f33d29e1a 100644 --- a/components/fx/types.hpp +++ b/components/fx/types.hpp @@ -29,6 +29,16 @@ namespace fx std::optional mWidth; std::optional mHeight; + SizeProxy() = default; + + SizeProxy(const SizeProxy& other) + : mWidthRatio(other.mWidthRatio) + , mHeightRatio(other.mHeightRatio) + , mWidth(other.mWidth) + , mHeight(other.mHeight) + { + } + std::tuple get(int width, int height) const { int scaledWidth = width; @@ -53,6 +63,17 @@ namespace fx osg::ref_ptr mTarget = new osg::Texture2D; SizeProxy mSize; bool mMipMap = false; + osg::Vec4f mClearColor = osg::Vec4f(0.0, 0.0, 0.0, 1.0); + + RenderTarget() = default; + + RenderTarget(const RenderTarget& other) + : mTarget(other.mTarget) + , mSize(other.mSize) + , mMipMap(other.mMipMap) + , mClearColor(other.mClearColor) + { + } }; template diff --git a/components/lua_ui/image.cpp b/components/lua_ui/image.cpp index 0454dd19b4..ffe93a8d2d 100644 --- a/components/lua_ui/image.cpp +++ b/components/lua_ui/image.cpp @@ -8,9 +8,7 @@ namespace LuaUi { void LuaTileRect::_setAlign(const MyGUI::IntSize& _oldsize) { - mCurrentCoord.set(0, 0, mCroppedParent->getWidth(), mCroppedParent->getHeight()); - mAlign = MyGUI::Align::Stretch; - MyGUI::TileRect::_setAlign(_oldsize); + mCoord.set(0, 0, mCroppedParent->getWidth(), mCroppedParent->getHeight()); mTileSize = mSetTileSize; // zero tilesize stands for not tiling @@ -25,6 +23,8 @@ namespace LuaUi mTileSize.width = 1e7; if (mTileSize.height <= 0) mTileSize.height = 1e7; + + MyGUI::TileRect::_updateView(); } void LuaImage::initialize() @@ -55,13 +55,13 @@ namespace LuaUi if (texture != nullptr) textureSize = MyGUI::IntSize(texture->getWidth(), texture->getHeight()); - mTileRect->updateSize(MyGUI::IntSize(tileH ? textureSize.width : 0, tileV ? textureSize.height : 0)); - setImageTile(textureSize); - if (atlasCoord.width == 0) atlasCoord.width = textureSize.width; if (atlasCoord.height == 0) atlasCoord.height = textureSize.height; + + mTileRect->updateSize(MyGUI::IntSize(tileH ? atlasCoord.width : 0, tileV ? atlasCoord.height : 0)); + setImageTile(atlasCoord.size()); setImageCoord(atlasCoord); setColour(propertyValue("color", MyGUI::Colour(1, 1, 1, 1))); diff --git a/components/lua_ui/textedit.cpp b/components/lua_ui/textedit.cpp index a8c19fa8fd..e12bd20c35 100644 --- a/components/lua_ui/textedit.cpp +++ b/components/lua_ui/textedit.cpp @@ -8,6 +8,7 @@ namespace LuaUi { mEditBox = createWidget("LuaTextEdit", MyGUI::IntCoord(0, 0, 0, 0), MyGUI::Align::Default); mEditBox->eventEditTextChange += MyGUI::newDelegate(this, &LuaTextEdit::textChange); + mEditBox->setMaxTextLength(std::numeric_limits::max()); registerEvents(mEditBox); WidgetExtension::initialize(); } diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index 33c04a6d35..947fae1ab2 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -171,7 +171,7 @@ namespace Nif }; NiPosDataPtr mData; - TargetColor mTargetColor; + TargetColor mTargetColor = TargetColor::Ambient; void read(NIFStream* nif) override; void post(Reader& nif) override; diff --git a/components/nif/effect.cpp b/components/nif/effect.cpp index ae4c7947cb..47d1863352 100644 --- a/components/nif/effect.cpp +++ b/components/nif/effect.cpp @@ -45,7 +45,9 @@ namespace Nif { NiPointLight::read(nif); - nif->read(mCutoff); + nif->read(mOuterSpotAngle); + if (nif->getVersion() >= NIFStream::generateVersion(20, 2, 0, 5)) + nif->read(mInnerSpotAngle); nif->read(mExponent); } diff --git a/components/nif/effect.hpp b/components/nif/effect.hpp index 906a7fdedf..2dd18d5304 100644 --- a/components/nif/effect.hpp +++ b/components/nif/effect.hpp @@ -58,7 +58,8 @@ namespace Nif struct NiSpotLight : public NiPointLight { - float mCutoff; + float mOuterSpotAngle; + float mInnerSpotAngle{ 0.f }; float mExponent; void read(NIFStream* nif) override; }; diff --git a/components/nif/extra.cpp b/components/nif/extra.cpp index 2d222f5a54..4ebd0bf517 100644 --- a/components/nif/extra.cpp +++ b/components/nif/extra.cpp @@ -136,6 +136,11 @@ namespace Nif nif->readVector(mData, nif->get()); } + void BSCollisionQueryProxyExtraData::read(NIFStream* nif) + { + nif->readVector(mData, nif->get()); + } + void BSConnectPoint::Point::read(NIFStream* nif) { mParent = nif->getSizedString(); diff --git a/components/nif/extra.hpp b/components/nif/extra.hpp index 1efa4ae7bb..2b46c81e26 100644 --- a/components/nif/extra.hpp +++ b/components/nif/extra.hpp @@ -173,6 +173,13 @@ namespace Nif void read(NIFStream* nif) override; }; + struct BSCollisionQueryProxyExtraData : BSExtraData + { + std::vector mData; + + void read(NIFStream* nif) override; + }; + struct BSConnectPoint { struct Point diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 81a223e095..37e40938d3 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -248,6 +248,8 @@ namespace Nif { "BSBehaviorGraphExtraData", &construct }, { "BSBoneLODExtraData", &construct }, { "BSClothExtraData", &construct }, + { "BSCollisionQueryProxyExtraData", + &construct }, { "BSConnectPoint::Children", &construct }, { "BSConnectPoint::Parents", &construct }, { "BSDecalPlacementVectorExtraData", diff --git a/components/nif/particle.cpp b/components/nif/particle.cpp index 0581c5a1d1..d81d423fb6 100644 --- a/components/nif/particle.cpp +++ b/components/nif/particle.cpp @@ -231,7 +231,7 @@ namespace Nif info.read(nif); } - if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_F76) + if (nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO4) nif->skip(12); // Unknown if (nif->getVersion() >= NIFStream::generateVersion(20, 0, 0, 2) && nif->get() && hasData) @@ -420,8 +420,8 @@ namespace Nif { nif->read(mRotationSpeedVariation); - if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_F76) - nif->skip(5); // Unknown + if (nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO4) + nif->skip(17); // Unknown nif->read(mRotationAngle); nif->read(mRotationAngleVariation); diff --git a/components/nif/property.cpp b/components/nif/property.cpp index bcc70540c8..2a5f91385d 100644 --- a/components/nif/property.cpp +++ b/components/nif/property.cpp @@ -148,12 +148,6 @@ namespace Nif } else { - uint32_t numShaderFlags1 = 0, numShaderFlags2 = 0; - nif->read(numShaderFlags1); - if (nif->getBethVersion() >= 152) - nif->read(numShaderFlags2); - nif->readVector(mShaderFlags1Hashes, numShaderFlags1); - nif->readVector(mShaderFlags2Hashes, numShaderFlags2); if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_F76 && recType == RC_BSLightingShaderProperty) { nif->read(mType); @@ -181,6 +175,13 @@ namespace Nif break; } } + + uint32_t numShaderFlags1 = 0, numShaderFlags2 = 0; + nif->read(numShaderFlags1); + if (nif->getBethVersion() >= 152) + nif->read(numShaderFlags2); + nif->readVector(mShaderFlags1Hashes, numShaderFlags1); + nif->readVector(mShaderFlags2Hashes, numShaderFlags2); } nif->read(mUVOffset); @@ -324,7 +325,7 @@ namespace Nif { nif->read(mSubsurfaceRolloff); nif->read(mRimlightPower); - if (mRimlightPower == std::numeric_limits::max()) + if (nif->getBethVersion() == 130 && mRimlightPower == std::numeric_limits::max()) nif->read(mBacklightPower); } @@ -335,27 +336,27 @@ namespace Nif mWetness.read(nif); } - if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_STF) + if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_F76) + { mLuminance.read(nif); - - if (nif->getBethVersion() == NIFFile::BethVersion::BETHVER_F76) - { - nif->read(mDoTranslucency); - if (mDoTranslucency) - mTranslucency.read(nif); - if (nif->get() != 0) + if (nif->getBethVersion() == NIFFile::BethVersion::BETHVER_F76) { - mTextureArrays.resize(nif->get()); - for (std::vector& textureArray : mTextureArrays) - nif->getSizedStrings(textureArray, nif->get()); + nif->read(mDoTranslucency); + if (mDoTranslucency) + mTranslucency.read(nif); + if (nif->get() != 0) + { + mTextureArrays.resize(nif->get()); + for (std::vector& textureArray : mTextureArrays) + nif->getSizedStrings(textureArray, nif->get()); + } + } + if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_STF) + { + nif->skip(4); // Unknown + nif->skip(4); // Unknown + nif->skip(2); // Unknown } - } - - if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_STF) - { - nif->skip(4); // Unknown - nif->skip(4); // Unknown - nif->skip(2); // Unknown } switch (static_cast(mType)) @@ -439,7 +440,6 @@ namespace Nif if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_F76) { - nif->read(mRefractionPower); mReflectanceTexture = nif->getSizedString(); mLightingTexture = nif->getSizedString(); nif->read(mEmittanceColor); diff --git a/components/nif/property.hpp b/components/nif/property.hpp index da908f2eab..2506633867 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -109,6 +109,12 @@ namespace Nif { BSSFlag1_Specular = 0x00000001, BSSFlag1_Decal = 0x04000000, + BSSFlag1_DepthTest = 0x80000000, + }; + + enum BSShaderFlags2 + { + BSSFlag2_DepthWrite = 0x00000001, }; struct BSSPParallaxParams @@ -140,6 +146,8 @@ namespace Nif // Shader-specific flag methods must be handled on per-record basis bool specular() const { return mShaderFlags1 & BSSFlag1_Specular; } bool decal() const { return mShaderFlags1 & BSSFlag1_Decal; } + bool depthTest() const { return mShaderFlags1 & BSSFlag1_DepthTest; } + bool depthWrite() const { return mShaderFlags2 & BSSFlag2_DepthWrite; } }; struct BSShaderLightingProperty : BSShaderProperty @@ -287,15 +295,15 @@ namespace Nif { BSShaderTextureSetPtr mTextureSet; osg::Vec3f mEmissive; - float mEmissiveMult; + float mEmissiveMult{ 1.f }; std::string mRootMaterial; - uint32_t mClamp; - float mAlpha; + uint32_t mClamp{ 3 }; + float mAlpha{ 1.f }; float mRefractionStrength; float mGlossiness{ 80.f }; float mSmoothness{ 1.f }; osg::Vec3f mSpecular; - float mSpecStrength; + float mSpecStrength{ 1.f }; std::array mLightingEffects; float mSubsurfaceRolloff; float mRimlightPower; diff --git a/components/nif/record.hpp b/components/nif/record.hpp index d2a30b1317..699522d24c 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -77,6 +77,7 @@ namespace Nif RC_BSBound, RC_BSBoneLODExtraData, RC_BSClothExtraData, + RC_BSCollisionQueryProxyExtraData, RC_BSConnectPointChildren, RC_BSConnectPointParents, RC_BSDecalPlacementVectorExtraData, diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 95744a8cfe..ec46afec41 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -6,7 +6,6 @@ #include #include -#include #include #include @@ -171,23 +170,8 @@ namespace NifBullet } for (const Nif::NiAVObject* node : roots) - { - // Try to find a valid bounding box first. If one's found for any root node, use that. if (findBoundingBox(*node)) - { - const btVector3 extents = Misc::Convert::toBullet(mShape->mCollisionBox.mExtents); - const btVector3 center = Misc::Convert::toBullet(mShape->mCollisionBox.mCenter); - auto compound = std::make_unique(); - auto boxShape = std::make_unique(extents); - btTransform transform = btTransform::getIdentity(); - transform.setOrigin(center); - compound->addChildShape(transform, boxShape.get()); - std::ignore = boxShape.release(); - - mShape->mCollisionShape.reset(compound.release()); - return mShape; - } - } + break; HandleNodeArgs args; @@ -196,8 +180,6 @@ namespace NifBullet // TODO: investigate whether this should and could be optimized. args.mAnimated = pathFileNameStartsWithX(mShape->mFileName); - // If there's no bounding box, we'll have to generate a Bullet collision shape - // from the collision data present in every root node. for (const Nif::NiAVObject* node : roots) handleRoot(nif, *node, args); @@ -210,8 +192,7 @@ namespace NifBullet return mShape; } - // Find a boundingBox in the node hierarchy. - // Return: use bounding box for collision? + // Find a bounding box in the node hierarchy to use for actor collision bool BulletNifLoader::findBoundingBox(const Nif::NiAVObject& node) { if (Misc::StringUtils::ciEqual(node.mName, "Bounding Box")) @@ -282,6 +263,32 @@ namespace NifBullet args.mAutogenerated = colNode == nullptr; + // Check for extra data + for (const auto& e : node.getExtraList()) + { + if (e->recType == Nif::RC_NiStringExtraData) + { + // String markers may contain important information + // affecting the entire subtree of this node + auto sd = static_cast(e.getPtr()); + + // Editor marker flag + if (sd->mData == "MRK") + args.mHasTriMarkers = true; + else if (Misc::StringUtils::ciStartsWith(sd->mData, "NC")) + { + // NC prefix is case-insensitive but the second C in NCC flag needs be uppercase. + + // Collide only with camera. + if (sd->mData.length() > 2 && sd->mData[2] == 'C') + mShape->mVisualCollisionType = Resource::VisualCollisionType::Camera; + // No collision. + else + mShape->mVisualCollisionType = Resource::VisualCollisionType::Default; + } + } + } + // FIXME: BulletNifLoader should never have to provide rendered geometry for camera collision if (colNode && colNode->mChildren.empty()) { @@ -342,35 +349,6 @@ namespace NifBullet if (node.recType == Nif::RC_AvoidNode) args.mAvoid = true; - // Check for extra data - for (const auto& e : node.getExtraList()) - { - if (e->recType == Nif::RC_NiStringExtraData) - { - // String markers may contain important information - // affecting the entire subtree of this node - auto sd = static_cast(e.getPtr()); - - if (Misc::StringUtils::ciStartsWith(sd->mData, "NC")) - { - // NCC flag in vanilla is partly case sensitive: prefix NC is case insensitive but second C needs be - // uppercase - if (sd->mData.length() > 2 && sd->mData[2] == 'C') - // Collide only with camera. - mShape->mVisualCollisionType = Resource::VisualCollisionType::Camera; - else - // No collision. - mShape->mVisualCollisionType = Resource::VisualCollisionType::Default; - } - // Don't autogenerate collision if MRK is set. - // FIXME: verify if this covers the entire subtree - else if (sd->mData == "MRK" && args.mAutogenerated) - { - return; - } - } - } - if ((args.mAutogenerated || args.mIsCollisionNode) && isTypeNiGeometry(node.recType)) handleNiTriShape(static_cast(node), parent, args); @@ -392,11 +370,14 @@ namespace NifBullet void BulletNifLoader::handleNiTriShape( const Nif::NiGeometry& niGeometry, const Nif::Parent* nodeParent, HandleNodeArgs args) { - // mHasMarkers is specifically BSXFlags editor marker flag. - // If this changes, the check must be corrected. + // This flag comes from BSXFlags if (args.mHasMarkers && Misc::StringUtils::ciStartsWith(niGeometry.mName, "EditorMarker")) return; + // This flag comes from Morrowind + if (args.mHasTriMarkers && Misc::StringUtils::ciStartsWith(niGeometry.mName, "Tri EditorMarker")) + return; + if (niGeometry.mData.empty() || niGeometry.mData->mVertices.empty()) return; diff --git a/components/nifbullet/bulletnifloader.hpp b/components/nifbullet/bulletnifloader.hpp index 521bbe91dd..c87c1242de 100644 --- a/components/nifbullet/bulletnifloader.hpp +++ b/components/nifbullet/bulletnifloader.hpp @@ -53,6 +53,7 @@ namespace NifBullet struct HandleNodeArgs { bool mHasMarkers{ false }; + bool mHasTriMarkers{ false }; bool mAnimated{ false }; bool mIsCollisionNode{ false }; bool mAutogenerated{ false }; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 9945a8c40e..b7ef547bd6 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1964,6 +1964,21 @@ namespace NifOsg return texEnv; } + void handleDepthFlags(osg::StateSet* stateset, bool depthTest, bool depthWrite) + { + if (!depthWrite && !depthTest) + { + stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + return; + } + osg::ref_ptr depth = new osg::Depth; + depth->setWriteMask(depthWrite); + if (!depthTest) + depth->setFunction(osg::Depth::ALWAYS); + depth = shareAttribute(depth); + stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); + } + void handleTextureProperty(const Nif::NiTexturingProperty* texprop, const std::string& nodeName, osg::StateSet* stateset, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, std::vector& boundTextures, int animflags) @@ -2319,16 +2334,8 @@ namespace NifOsg { const Nif::NiZBufferProperty* zprop = static_cast(property); osg::StateSet* stateset = node->getOrCreateStateSet(); - stateset->setMode( - GL_DEPTH_TEST, zprop->depthTest() ? osg::StateAttribute::ON : osg::StateAttribute::OFF); - osg::ref_ptr depth = new osg::Depth; - depth->setWriteMask(zprop->depthWrite()); - // Morrowind ignores depth test function, unless a NiStencilProperty is present, in which case it - // uses a fixed depth function of GL_ALWAYS. - if (hasStencilProperty) - depth->setFunction(osg::Depth::ALWAYS); - depth = shareAttribute(depth); - stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); + // The test function from this property seems to be ignored. + handleDepthFlags(stateset, zprop->depthTest(), zprop->depthWrite()); break; } // OSG groups the material properties that NIFs have separate, so we have to parse them all again when @@ -2405,6 +2412,7 @@ namespace NifOsg } stateset->addUniform(new osg::Uniform("useFalloff", useFalloff)); handleTextureControllers(texprop, composite, imageManager, stateset, animflags); + handleDepthFlags(stateset, texprop->depthTest(), texprop->depthWrite()); break; } case Nif::RC_BSLightingShaderProperty: @@ -2422,6 +2430,7 @@ namespace NifOsg stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); if (texprop->treeAnim()) stateset->addUniform(new osg::Uniform("useTreeAnim", true)); + handleDepthFlags(stateset, texprop->depthTest(), texprop->depthWrite()); break; } case Nif::RC_BSEffectShaderProperty: @@ -2477,6 +2486,7 @@ namespace NifOsg handleTextureControllers(texprop, composite, imageManager, stateset, animflags); if (texprop->doubleSided()) stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + handleDepthFlags(stateset, texprop->depthTest(), texprop->depthWrite()); break; } // unused by mw diff --git a/components/sceneutil/lightcontroller.cpp b/components/sceneutil/lightcontroller.cpp index 98af5d21c8..caff6826f5 100644 --- a/components/sceneutil/lightcontroller.cpp +++ b/components/sceneutil/lightcontroller.cpp @@ -36,9 +36,11 @@ namespace SceneUtil // if (time == mLastTime) // return; + osg::Light* light = node->getLight(nv->getTraversalNumber()); + if (mType == LT_Normal) { - node->getLight(nv->getTraversalNumber())->setDiffuse(mDiffuseColor); + light->setDiffuse(mDiffuseColor); traverse(node, nv); return; } @@ -63,7 +65,10 @@ namespace SceneUtil mPhase = mPhase <= 0.5f ? 1.f : 0.25f; } - node->getLight(nv->getTraversalNumber())->setDiffuse(mDiffuseColor * mBrightness * node->getActorFade()); + osg::Vec4f result = mDiffuseColor * mBrightness * node->getActorFade(); + + light->setDiffuse(result); + light->setSpecular(result); traverse(node, nv); } diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 6bca92fdb4..8f7304416b 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -536,6 +536,7 @@ namespace SceneUtil configurePosition(lightMat, light->getPosition() * mViewMatrix); configureAmbient(lightMat, light->getAmbient()); configureDiffuse(lightMat, light->getDiffuse()); + configureSpecular(lightMat, light->getSpecular()); configureAttenuation(lightMat, light->getConstantAttenuation(), light->getLinearAttenuation(), light->getQuadraticAttenuation(), lightList[i]->mLightSource->getRadius()); @@ -1214,6 +1215,7 @@ namespace SceneUtil auto& buf = getUBOManager()->getLightBuffer(frameNum); buf->setDiffuse(index, light->getDiffuse()); buf->setAmbient(index, light->getAmbient()); + buf->setSpecular(index, light->getSpecular()); buf->setAttenuationRadius(index, osg::Vec4(light->getConstantAttenuation(), light->getLinearAttenuation(), light->getQuadraticAttenuation(), lightSource->getRadius())); diff --git a/components/sceneutil/lightutil.cpp b/components/sceneutil/lightutil.cpp index 9c0ebdf5c2..f69461fa3c 100644 --- a/components/sceneutil/lightutil.cpp +++ b/components/sceneutil/lightutil.cpp @@ -124,7 +124,7 @@ namespace SceneUtil } light->setDiffuse(diffuse); light->setAmbient(ambient); - light->setSpecular(osg::Vec4f(0, 0, 0, 0)); + light->setSpecular(diffuse); // ESM format doesn't provide specular lightSource->setLight(light); diff --git a/components/sceneutil/shadow.cpp b/components/sceneutil/shadow.cpp index f2748d70f1..04f3b65edd 100644 --- a/components/sceneutil/shadow.cpp +++ b/components/sceneutil/shadow.cpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include #include "mwshadowtechnique.hpp" @@ -13,9 +13,10 @@ namespace SceneUtil { using namespace osgShadow; - void ShadowManager::setupShadowSettings(Shader::ShaderManager& shaderManager) + void ShadowManager::setupShadowSettings( + const Settings::ShadowsCategory& settings, Shader::ShaderManager& shaderManager) { - mEnableShadows = Settings::Manager::getBool("enable shadows", "Shadows"); + mEnableShadows = settings.mEnableShadows; if (!mEnableShadows) { @@ -28,64 +29,58 @@ namespace SceneUtil mShadowSettings->setLightNum(0); mShadowSettings->setReceivesShadowTraversalMask(~0u); - const int numberOfShadowMapsPerLight - = std::clamp(Settings::Manager::getInt("number of shadow maps", "Shadows"), 1, 8); + const int numberOfShadowMapsPerLight = settings.mNumberOfShadowMaps; mShadowSettings->setNumShadowMapsPerLight(numberOfShadowMapsPerLight); mShadowSettings->setBaseShadowTextureUnit(shaderManager.reserveGlobalTextureUnits( Shader::ShaderManager::Slot::ShadowMaps, numberOfShadowMapsPerLight)); - const float maximumShadowMapDistance = Settings::Manager::getFloat("maximum shadow map distance", "Shadows"); + const float maximumShadowMapDistance = settings.mMaximumShadowMapDistance; if (maximumShadowMapDistance > 0) { - const float shadowFadeStart - = std::clamp(Settings::Manager::getFloat("shadow fade start", "Shadows"), 0.f, 1.f); + const float shadowFadeStart = settings.mShadowFadeStart; mShadowSettings->setMaximumShadowMapDistance(maximumShadowMapDistance); mShadowTechnique->setShadowFadeStart(maximumShadowMapDistance * shadowFadeStart); } - mShadowSettings->setMinimumShadowMapNearFarRatio( - Settings::Manager::getFloat("minimum lispsm near far ratio", "Shadows")); + mShadowSettings->setMinimumShadowMapNearFarRatio(settings.mMinimumLispsmNearFarRatio); - const std::string& computeSceneBounds = Settings::Manager::getString("compute scene bounds", "Shadows"); + const std::string& computeSceneBounds = settings.mComputeSceneBounds; if (Misc::StringUtils::ciEqual(computeSceneBounds, "primitives")) mShadowSettings->setComputeNearFarModeOverride(osg::CullSettings::COMPUTE_NEAR_FAR_USING_PRIMITIVES); else if (Misc::StringUtils::ciEqual(computeSceneBounds, "bounds")) mShadowSettings->setComputeNearFarModeOverride(osg::CullSettings::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES); - int mapres = Settings::Manager::getInt("shadow map resolution", "Shadows"); + const int mapres = settings.mShadowMapResolution; mShadowSettings->setTextureSize(osg::Vec2s(mapres, mapres)); - mShadowTechnique->setSplitPointUniformLogarithmicRatio( - Settings::Manager::getFloat("split point uniform logarithmic ratio", "Shadows")); - mShadowTechnique->setSplitPointDeltaBias(Settings::Manager::getFloat("split point bias", "Shadows")); + mShadowTechnique->setSplitPointUniformLogarithmicRatio(settings.mSplitPointUniformLogarithmicRatio); + mShadowTechnique->setSplitPointDeltaBias(settings.mSplitPointBias); - mShadowTechnique->setPolygonOffset(Settings::Manager::getFloat("polygon offset factor", "Shadows"), - Settings::Manager::getFloat("polygon offset units", "Shadows")); + mShadowTechnique->setPolygonOffset(settings.mPolygonOffsetFactor, settings.mPolygonOffsetUnits); - if (Settings::Manager::getBool("use front face culling", "Shadows")) + if (settings.mUseFrontFaceCulling) mShadowTechnique->enableFrontFaceCulling(); else mShadowTechnique->disableFrontFaceCulling(); - if (Settings::Manager::getBool("allow shadow map overlap", "Shadows")) + if (settings.mAllowShadowMapOverlap) mShadowSettings->setMultipleShadowMapHint(osgShadow::ShadowSettings::CASCADED); else mShadowSettings->setMultipleShadowMapHint(osgShadow::ShadowSettings::PARALLEL_SPLIT); - if (Settings::Manager::getBool("enable debug hud", "Shadows")) + if (settings.mEnableDebugHud) mShadowTechnique->enableDebugHUD(); else mShadowTechnique->disableDebugHUD(); } - void ShadowManager::disableShadowsForStateSet(osg::ref_ptr stateset) + void ShadowManager::disableShadowsForStateSet(const Settings::ShadowsCategory& settings, osg::StateSet& stateset) { - if (!Settings::Manager::getBool("enable shadows", "Shadows")) + if (!settings.mEnableShadows) return; - const int numberOfShadowMapsPerLight - = std::clamp(Settings::Manager::getInt("number of shadow maps", "Shadows"), 1, 8); + const int numberOfShadowMapsPerLight = settings.mNumberOfShadowMaps; int baseShadowTextureUnit = 8 - numberOfShadowMapsPerLight; @@ -99,18 +94,18 @@ namespace SceneUtil fakeShadowMapTexture->setShadowCompareFunc(osg::Texture::ShadowCompareFunc::ALWAYS); for (int i = baseShadowTextureUnit; i < baseShadowTextureUnit + numberOfShadowMapsPerLight; ++i) { - stateset->setTextureAttributeAndModes(i, fakeShadowMapTexture, + stateset.setTextureAttributeAndModes(i, fakeShadowMapTexture, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED); - stateset->addUniform( + stateset.addUniform( new osg::Uniform(("shadowTexture" + std::to_string(i - baseShadowTextureUnit)).c_str(), i)); - stateset->addUniform( + stateset.addUniform( new osg::Uniform(("shadowTextureUnit" + std::to_string(i - baseShadowTextureUnit)).c_str(), i)); } } ShadowManager::ShadowManager(osg::ref_ptr sceneRoot, osg::ref_ptr rootNode, unsigned int outdoorShadowCastingMask, unsigned int indoorShadowCastingMask, unsigned int worldMask, - Shader::ShaderManager& shaderManager) + const Settings::ShadowsCategory& settings, Shader::ShaderManager& shaderManager) : mShadowedScene(new osgShadow::ShadowedScene) , mShadowTechnique(new MWShadowTechnique) , mOutdoorShadowCastingMask(outdoorShadowCastingMask) @@ -126,7 +121,7 @@ namespace SceneUtil mShadowedScene->setNodeMask(sceneRoot->getNodeMask()); mShadowSettings = mShadowedScene->getShadowSettings(); - setupShadowSettings(shaderManager); + setupShadowSettings(settings, shaderManager); mShadowTechnique->setupCastingShader(shaderManager); mShadowTechnique->setWorldMask(worldMask); @@ -140,7 +135,7 @@ namespace SceneUtil Stereo::Manager::instance().setShadowTechnique(nullptr); } - Shader::ShaderManager::DefineMap ShadowManager::getShadowDefines() + Shader::ShaderManager::DefineMap ShadowManager::getShadowDefines(const Settings::ShadowsCategory& settings) { if (!mEnableShadows) return getShadowsDisabledDefines(); @@ -155,24 +150,19 @@ namespace SceneUtil definesWithShadows["shadow_texture_unit_list"] = definesWithShadows["shadow_texture_unit_list"].substr( 0, definesWithShadows["shadow_texture_unit_list"].length() - 1); - definesWithShadows["shadowMapsOverlap"] - = Settings::Manager::getBool("allow shadow map overlap", "Shadows") ? "1" : "0"; + definesWithShadows["shadowMapsOverlap"] = settings.mAllowShadowMapOverlap ? "1" : "0"; - definesWithShadows["useShadowDebugOverlay"] - = Settings::Manager::getBool("enable debug overlay", "Shadows") ? "1" : "0"; + definesWithShadows["useShadowDebugOverlay"] = settings.mEnableDebugOverlay ? "1" : "0"; // switch this to reading settings if it's ever exposed to the user definesWithShadows["perspectiveShadowMaps"] = mShadowSettings->getShadowMapProjectionHint() == ShadowSettings::PERSPECTIVE_SHADOW_MAP ? "1" : "0"; - definesWithShadows["disableNormalOffsetShadows"] - = Settings::Manager::getFloat("normal offset distance", "Shadows") == 0.0 ? "1" : "0"; + definesWithShadows["disableNormalOffsetShadows"] = settings.mNormalOffsetDistance == 0.0 ? "1" : "0"; - definesWithShadows["shadowNormalOffset"] - = std::to_string(Settings::Manager::getFloat("normal offset distance", "Shadows")); + definesWithShadows["shadowNormalOffset"] = std::to_string(settings.mNormalOffsetDistance); - definesWithShadows["limitShadowMapDistance"] - = Settings::Manager::getFloat("maximum shadow map distance", "Shadows") > 0 ? "1" : "0"; + definesWithShadows["limitShadowMapDistance"] = settings.mMaximumShadowMapDistance > 0 ? "1" : "0"; return definesWithShadows; } @@ -200,9 +190,9 @@ namespace SceneUtil return definesWithoutShadows; } - void ShadowManager::enableIndoorMode() + void ShadowManager::enableIndoorMode(const Settings::ShadowsCategory& settings) { - if (Settings::Manager::getBool("enable indoor shadows", "Shadows")) + if (settings.mEnableIndoorShadows) mShadowSettings->setCastsShadowTraversalMask(mIndoorShadowCastingMask); else mShadowTechnique->disableShadows(true); diff --git a/components/sceneutil/shadow.hpp b/components/sceneutil/shadow.hpp index 76fd2b7fdd..fd82e828b6 100644 --- a/components/sceneutil/shadow.hpp +++ b/components/sceneutil/shadow.hpp @@ -8,32 +8,38 @@ namespace osg class StateSet; class Group; } + namespace osgShadow { class ShadowSettings; class ShadowedScene; } +namespace Settings +{ + struct ShadowsCategory; +} + namespace SceneUtil { class MWShadowTechnique; class ShadowManager { public: - static void disableShadowsForStateSet(osg::ref_ptr stateSet); + static void disableShadowsForStateSet(const Settings::ShadowsCategory& settings, osg::StateSet& stateset); static Shader::ShaderManager::DefineMap getShadowsDisabledDefines(); - ShadowManager(osg::ref_ptr sceneRoot, osg::ref_ptr rootNode, + explicit ShadowManager(osg::ref_ptr sceneRoot, osg::ref_ptr rootNode, unsigned int outdoorShadowCastingMask, unsigned int indoorShadowCastingMask, unsigned int worldMask, - Shader::ShaderManager& shaderManager); + const Settings::ShadowsCategory& settings, Shader::ShaderManager& shaderManager); ~ShadowManager(); - void setupShadowSettings(Shader::ShaderManager& shaderManager); + void setupShadowSettings(const Settings::ShadowsCategory& settings, Shader::ShaderManager& shaderManager); - Shader::ShaderManager::DefineMap getShadowDefines(); + Shader::ShaderManager::DefineMap getShadowDefines(const Settings::ShadowsCategory& settings); - void enableIndoorMode(); + void enableIndoorMode(const Settings::ShadowsCategory& settings); void enableOutdoorMode(); diff --git a/components/settings/categories/camera.hpp b/components/settings/categories/camera.hpp index 4712f88b45..5b3a9b742b 100644 --- a/components/settings/categories/camera.hpp +++ b/components/settings/categories/camera.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_CAMERA_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_CAMERA_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/cells.hpp b/components/settings/categories/cells.hpp index 723004d674..86fe944acf 100644 --- a/components/settings/categories/cells.hpp +++ b/components/settings/categories/cells.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_CELLS_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_CELLS_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/fog.hpp b/components/settings/categories/fog.hpp index 5acf3d20c6..3bc75587f1 100644 --- a/components/settings/categories/fog.hpp +++ b/components/settings/categories/fog.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_FOG_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_FOG_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/game.hpp b/components/settings/categories/game.hpp index ded367a54c..4aec92d0b8 100644 --- a/components/settings/categories/game.hpp +++ b/components/settings/categories/game.hpp @@ -1,9 +1,9 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_GAME_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_GAME_H -#include "components/detournavigator/collisionshapetype.hpp" -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include +#include #include #include @@ -74,6 +74,7 @@ namespace Settings "unarmed creature attacks damage armor" }; SettingValue mActorCollisionShapeType{ mIndex, "Game", "actor collision shape type" }; + SettingValue mPlayerMovementIgnoresAnimation{ mIndex, "Game", "player movement ignores animation" }; }; } diff --git a/components/settings/categories/general.hpp b/components/settings/categories/general.hpp index 7bbb651ee8..a79ef83ea0 100644 --- a/components/settings/categories/general.hpp +++ b/components/settings/categories/general.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_GENERAL_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_GENERAL_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/groundcover.hpp b/components/settings/categories/groundcover.hpp index 48f97037ae..78615a4e76 100644 --- a/components/settings/categories/groundcover.hpp +++ b/components/settings/categories/groundcover.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_GROUNDCOVER_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_GROUNDCOVER_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/gui.hpp b/components/settings/categories/gui.hpp index 1d3a56af39..4a5e50fd8a 100644 --- a/components/settings/categories/gui.hpp +++ b/components/settings/categories/gui.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_GUI_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_GUI_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include @@ -28,10 +28,8 @@ namespace Settings SettingValue mSubtitles{ mIndex, "GUI", "subtitles" }; SettingValue mHitFader{ mIndex, "GUI", "hit fader" }; SettingValue mWerewolfOverlay{ mIndex, "GUI", "werewolf overlay" }; - SettingValue mColorBackgroundOwned{ mIndex, "GUI", "color background owned", - makeClampSanitizerFloat(0, 1) }; - SettingValue mColorCrosshairOwned{ mIndex, "GUI", "color crosshair owned", - makeClampSanitizerFloat(0, 1) }; + SettingValue mColorBackgroundOwned{ mIndex, "GUI", "color background owned" }; + SettingValue mColorCrosshairOwned{ mIndex, "GUI", "color crosshair owned" }; SettingValue mKeyboardNavigation{ mIndex, "GUI", "keyboard navigation" }; SettingValue mColorTopicEnable{ mIndex, "GUI", "color topic enable" }; SettingValue mColorTopicSpecific{ mIndex, "GUI", "color topic specific" }; diff --git a/components/settings/categories/hud.hpp b/components/settings/categories/hud.hpp index e97a81501d..fe05e39eda 100644 --- a/components/settings/categories/hud.hpp +++ b/components/settings/categories/hud.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_HUD_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_HUD_H -#include "components/settings/settingvalue.hpp" +#include #include #include diff --git a/components/settings/categories/input.hpp b/components/settings/categories/input.hpp index 1e59b45334..0a450f1dcd 100644 --- a/components/settings/categories/input.hpp +++ b/components/settings/categories/input.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_INPUT_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_INPUT_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/lua.hpp b/components/settings/categories/lua.hpp index da11a605eb..88d74b2d1f 100644 --- a/components/settings/categories/lua.hpp +++ b/components/settings/categories/lua.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_LUA_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_LUA_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/map.hpp b/components/settings/categories/map.hpp index bdccd6f205..8a80d5aa63 100644 --- a/components/settings/categories/map.hpp +++ b/components/settings/categories/map.hpp @@ -1,9 +1,9 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_MAP_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_MAP_H -#include "components/misc/constants.hpp" -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include +#include #include #include diff --git a/components/settings/categories/models.hpp b/components/settings/categories/models.hpp index fec8da7775..0d26eeba5f 100644 --- a/components/settings/categories/models.hpp +++ b/components/settings/categories/models.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_MODELS_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_MODELS_H -#include "components/settings/settingvalue.hpp" +#include #include #include diff --git a/components/settings/categories/navigator.hpp b/components/settings/categories/navigator.hpp index b820b2c950..d6d7adcd56 100644 --- a/components/settings/categories/navigator.hpp +++ b/components/settings/categories/navigator.hpp @@ -1,9 +1,9 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_NAVIGATOR_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_NAVIGATOR_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" #include +#include +#include #include #include diff --git a/components/settings/categories/physics.hpp b/components/settings/categories/physics.hpp index 41005a06cf..4720708db2 100644 --- a/components/settings/categories/physics.hpp +++ b/components/settings/categories/physics.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_PHYSICS_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_PHYSICS_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/postprocessing.hpp b/components/settings/categories/postprocessing.hpp index 04810b847c..7f5ddfba3e 100644 --- a/components/settings/categories/postprocessing.hpp +++ b/components/settings/categories/postprocessing.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_POSTPROCESSING_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_POSTPROCESSING_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/saves.hpp b/components/settings/categories/saves.hpp index e565d98564..3a64785412 100644 --- a/components/settings/categories/saves.hpp +++ b/components/settings/categories/saves.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_SAVES_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_SAVES_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/shaders.hpp b/components/settings/categories/shaders.hpp index 7efb891822..dce2531c1e 100644 --- a/components/settings/categories/shaders.hpp +++ b/components/settings/categories/shaders.hpp @@ -1,9 +1,9 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_SHADERS_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_SHADERS_H -#include "components/sceneutil/lightingmethod.hpp" -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include +#include #include #include diff --git a/components/settings/categories/shadows.hpp b/components/settings/categories/shadows.hpp index d716272bce..0da6f649c4 100644 --- a/components/settings/categories/shadows.hpp +++ b/components/settings/categories/shadows.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_SHADOWS_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_SHADOWS_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/sound.hpp b/components/settings/categories/sound.hpp index 43313e622d..995bce2a58 100644 --- a/components/settings/categories/sound.hpp +++ b/components/settings/categories/sound.hpp @@ -1,9 +1,9 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_SOUND_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_SOUND_H -#include "components/settings/hrtfmode.hpp" -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include +#include #include diff --git a/components/settings/categories/stereo.hpp b/components/settings/categories/stereo.hpp index 98fae8693f..aa903c5b53 100644 --- a/components/settings/categories/stereo.hpp +++ b/components/settings/categories/stereo.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_STEREO_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_STEREO_H -#include "components/settings/settingvalue.hpp" +#include #include #include diff --git a/components/settings/categories/stereoview.hpp b/components/settings/categories/stereoview.hpp index bcd0f57abc..7f08d9bc35 100644 --- a/components/settings/categories/stereoview.hpp +++ b/components/settings/categories/stereoview.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_STEREOVIEW_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_STEREOVIEW_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/terrain.hpp b/components/settings/categories/terrain.hpp index c2eef9dc20..f26cc264b8 100644 --- a/components/settings/categories/terrain.hpp +++ b/components/settings/categories/terrain.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_TERRAIN_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_TERRAIN_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/video.hpp b/components/settings/categories/video.hpp index 0e0f7a75bb..cb12ea079c 100644 --- a/components/settings/categories/video.hpp +++ b/components/settings/categories/video.hpp @@ -1,13 +1,12 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_VIDEO_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_VIDEO_H +#include #include #include #include #include -#include - #include #include #include diff --git a/components/settings/categories/water.hpp b/components/settings/categories/water.hpp index b88d38b023..2e04114244 100644 --- a/components/settings/categories/water.hpp +++ b/components/settings/categories/water.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_WATER_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_WATER_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/windows.hpp b/components/settings/categories/windows.hpp index 1fc249cc97..2f22e751e6 100644 --- a/components/settings/categories/windows.hpp +++ b/components/settings/categories/windows.hpp @@ -1,8 +1,7 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_WINDOWS_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_WINDOWS_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include #include #include diff --git a/components/terrain/cellborder.cpp b/components/terrain/cellborder.cpp index 06767531d3..b78691cd8d 100644 --- a/components/terrain/cellborder.cpp +++ b/components/terrain/cellborder.cpp @@ -6,6 +6,7 @@ #include "world.hpp" +#include #include #include #include @@ -25,7 +26,7 @@ namespace Terrain osg::ref_ptr CellBorder::createBorderGeometry(float x, float y, float size, Terrain::Storage* terrain, Resource::SceneManager* sceneManager, int mask, ESM::RefId worldspace, float offset, osg::Vec4f color) { - const int cellSize = ESM::Land::REAL_SIZE; + const int cellSize = ESM::getCellSize(worldspace); const int borderSegments = 40; osg::Vec3 cellCorner = osg::Vec3(x * cellSize, y * cellSize, 0); diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 52edd96b9f..bdf9f56790 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -280,8 +280,9 @@ namespace Terrain QuadTreeWorld::QuadTreeWorld(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, unsigned int nodeMask, unsigned int preCompileMask, unsigned int borderMask, int compMapResolution, float compMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize, - bool debugChunks, ESM::RefId worldspace) - : TerrainGrid(parent, compileRoot, resourceSystem, storage, nodeMask, worldspace, preCompileMask, borderMask) + bool debugChunks, ESM::RefId worldspace, double expiryDelay) + : TerrainGrid( + parent, compileRoot, resourceSystem, storage, nodeMask, worldspace, expiryDelay, preCompileMask, borderMask) , mViewDataMap(new ViewDataMap) , mQuadTreeBuilt(false) , mLodFactor(lodFactor) diff --git a/components/terrain/quadtreeworld.hpp b/components/terrain/quadtreeworld.hpp index 2524dc046f..fa800d2655 100644 --- a/components/terrain/quadtreeworld.hpp +++ b/components/terrain/quadtreeworld.hpp @@ -33,7 +33,7 @@ namespace Terrain QuadTreeWorld(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, unsigned int nodeMask, unsigned int preCompileMask, unsigned int borderMask, int compMapResolution, float comMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize, - bool debugChunks, ESM::RefId worldspace); + bool debugChunks, ESM::RefId worldspace, double expiryDelay); ~QuadTreeWorld(); diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index c88ae4e28f..31cc2703f2 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -517,3 +517,15 @@ will not be useful with another. * 0: Axis-aligned bounding box * 1: Rotating box * 2: Cylinder + +player movement ignores animation +--------------------------------- + +:Type: boolean +:Range: True/False +:Default: False + +In third person, the camera will sway along with the movement animations of the player. +Enabling this option disables this swaying by having the player character move independently of its animation. + +This setting can be controlled in the Settings tab of the launcher, under Visuals. diff --git a/docs/source/reference/modding/settings/navigator.rst b/docs/source/reference/modding/settings/navigator.rst index 0de061d8f0..55b9e19b19 100644 --- a/docs/source/reference/modding/settings/navigator.rst +++ b/docs/source/reference/modding/settings/navigator.rst @@ -13,16 +13,15 @@ enable :Range: True/False :Default: True -Enable navigator. -When enabled background threads are started to build nav mesh for world geometry. -Pathfinding system uses nav mesh to build paths. -When disabled only pathgrid is used to build paths. -Single-core CPU systems may have big performance impact on exiting interior location and moving across exterior world. +Enable navigator to make all settings in this category take effect. +When enabled, a navigation mesh (navmesh) is built in the background for world geometry to be used for pathfinding. +When disabled only the path grid is used to build paths. +Single-core CPU systems may have a big performance impact on existing interior location and moving across the exterior world. May slightly affect performance on multi-core CPU systems. -Multi-core CPU systems may have different latency for nav mesh update depending on other settings and system performance. -Moving across external world, entering/exiting location produce nav mesh update. -NPC and creatures may not be able to find path before nav mesh is built around them. -Try to disable this if you want to have old fashioned AI which doesn't know where to go when you stand behind that stone and casting a firebolt. +Multi-core CPU systems may have different latency for navigation mesh update depending on other settings and system performance. +Moving across external world, entering/exiting location produce navigation mesh update. +NPC and creatures may not be able to find path before navigation mesh is built around them. +Try to disable this if you want to have old fashioned AI which doesn't know where to go when you stand behind that stone and cast a firebolt. max tiles number ---------------- @@ -31,14 +30,14 @@ max tiles number :Range: >= 0 :Default: 512 -Number of tiles at nav mesh. +Number of tiles at navigation mesh. Nav mesh covers circle area around player. -This option allows to set an explicit limit for nav mesh size, how many tiles should fit into circle. -If actor is inside this area it able to find path over nav mesh. +This option allows to set an explicit limit for navigation mesh size, how many tiles should fit into circle. +If actor is inside this area it able to find path over navigation mesh. Increasing this value may decrease performance. .. note:: - Don't expect infinite nav mesh size increasing. + Don't expect infinite navigation mesh size increasing. This condition is always true: ``max tiles number * max polygons per tile <= 4194304``. It's a limitation of `Recastnavigation `_ library. @@ -49,9 +48,9 @@ wait until min distance to player :Range: >= 0 :Default: 5 -Distance in navmesh tiles around the player to keep loading screen until navigation mesh is generated. +Distance in navigation mesh tiles around the player to keep loading screen until navigation mesh is generated. Allows to complete cell loading only when minimal navigation mesh area is generated to correctly find path for actors -nearby the player. Increasing this value will keep loading screen longer but will slightly increase nav mesh generation +nearby the player. Increasing this value will keep loading screen longer but will slightly increase navigation mesh generation speed on systems bound by CPU. Zero means no waiting. enable nav mesh disk cache @@ -61,8 +60,8 @@ enable nav mesh disk cache :Range: True/False :Default: True -If true navmesh cache stored on disk will be used in addition to memory cache. -If navmesh tile is not present in memory cache, it will be looked up in the disk cache. +If true navigation mesh cache stored on disk will be used in addition to memory cache. +If navigation mesh tile is not present in memory cache, it will be looked up in the disk cache. If it's not found there it will be generated. write to navmeshdb @@ -72,7 +71,7 @@ write to navmeshdb :Range: True/False :Default: True -If true generated navmesh tiles will be stored into disk cache while game is running. +If true generated navigation mesh tiles will be stored into disk cache while game is running. max navmeshdb file size ----------------------- @@ -95,8 +94,8 @@ async nav mesh updater threads :Range: >= 1 :Default: 1 -Number of background threads to update nav mesh. -Increasing this value may decrease performance, but also may decrease or increase nav mesh update latency depending on number of CPU cores. +Number of background threads to update navigation mesh. +Increasing this value may decrease performance, but also may decrease or increase navigation mesh update latency depending on number of CPU cores. On systems with not less than 4 CPU cores latency dependens approximately like 1/log(n) from number of threads. Don't expect twice better latency by doubling this value. @@ -107,12 +106,12 @@ max nav mesh tiles cache size :Range: >= 0 :Default: 268435456 -Maximum total cached size of all nav mesh tiles in bytes. -Setting greater than zero value will reduce nav mesh update latency for previously visited locations. +Maximum total cached size of all navigation mesh tiles in bytes. +Setting greater than zero value will reduce navigation mesh update latency for previously visited locations. Increasing this value may increase total memory consumption, but potentially will reduce latency for recently visited locations. Limit this value by total available physical memory minus base game memory consumption and other applications. Game will not eat all memory at once. -Memory will be consumed in approximately linear dependency from number of nav mesh updates. +Memory will be consumed in approximately linear dependency from number of navigation mesh updates. But only for new locations or already dropped from cache. min update interval ms @@ -122,17 +121,17 @@ min update interval ms :Range: >= 0 :Default: 250 -Minimum time duration required to pass before next navmesh update for the same tile in milliseconds. +Minimum time duration required to pass before next navigation mesh update for the same tile in milliseconds. Only tiles affected where objects are transformed. Next update for tile with added or removed object will not be delayed. -Visible ingame effect is navmesh update around opening or closing door. +Visible ingame effect is navigation mesh update around opening or closing door. Primary usage is for rotating signs like in Seyda Neen at Arrille's Tradehouse entrance. Decreasing this value may increase CPU usage by background threads. Developer's settings ******************** -This section is for developers or anyone who wants to investigate how nav mesh system works in OpenMW. +This section is for developers or anyone who wants to learn how navigation mesh system works in OpenMW. enable write recast mesh to file -------------------------------- @@ -141,8 +140,8 @@ enable write recast mesh to file :Range: True/False :Default: False -Write recast mesh to file in .obj format for each use to update nav mesh. -Option is used to find out what world geometry is used to build nav mesh. +Write recast mesh to file in .obj format for each use to update navigation mesh. +Option is used to find out what world geometry is used to build navigation mesh. Potentially decreases performance. enable write nav mesh to file @@ -152,7 +151,7 @@ enable write nav mesh to file :Range: True/False :Default: False -Write nav mesh to file to be able to open by RecastDemo application. +Write navigation mesh to file to be able to open by RecastDemo application. Usually it is more useful to have both enable write recast mesh to file and this options enabled. RecastDemo supports .obj files. Potentially decreases performance. @@ -175,9 +174,9 @@ enable nav mesh file name revision :Range: True/False :Default: False -Write each nav mesh file with revision in name. +Write each navigation mesh file with revision in name. Otherwise will rewrite same file. -If it is unclear when nav mesh is changed use this option to dump multiple files for each state. +If it is unclear when navigation mesh is changed use this option to dump multiple files for each state. recast mesh path prefix ----------------------- @@ -195,7 +194,7 @@ nav mesh path prefix :Range: file system path :Default: "" -Write nav mesh file at path with this prefix. +Write navigation mesh file at path with this prefix. enable nav mesh render ---------------------- @@ -206,7 +205,7 @@ enable nav mesh render Render navigation mesh. Allows to do in-game debug. -Every nav mesh is visible and every update is noticeable. +Every navigation mesh is visible and every update is noticeable. Potentially decreases performance. nav mesh render mode @@ -246,17 +245,6 @@ Absent pieces usually mean a bug in recast mesh tiles building. Allows to do in-game debug. Potentially decreases performance. -nav mesh version ----------------- - -:Type: integer -:Range: > 0 -:Default: 1 - -Version of navigation mesh generation algorithm. -Should be increased each time there is a difference between output of makeNavMeshTileData function for the same input. -Changing the value will invalidate navmesh disk cache. - Expert settings *************** @@ -269,12 +257,12 @@ recast scale factor :Range: > 0.0 :Default: 0.029411764705882353 -Scale of nav mesh coordinates to world coordinates. Recastnavigation builds voxels for world geometry. +Scale of navigation mesh coordinates to world coordinates. Recastnavigation builds voxels for world geometry. Basically voxel size is 1 / "cell size". To reduce amount of voxels we apply scale factor, to make voxel size "recast scale factor" / "cell size". Default value calculates by this equation: sStepSizeUp * "recast scale factor" / "cell size" = 5 (max climb height should be equal to 4 voxels). -Changing this value will change generated nav mesh. Some locations may become unavailable for NPC and creatures. -Pay attention to slopes and roofs when change it. Increasing this value will reduce nav mesh update latency. +Changing this value will change generated navigation mesh. Some locations may become unavailable for NPC and creatures. +Pay attention to slopes and roofs when change it. Increasing this value will reduce navigation mesh update latency. max polygon path size --------------------- @@ -397,13 +385,13 @@ max polygons per tile :Range: 2^n, 0 < n < 22 :Default: 4096 -Maximum number of polygons per nav mesh tile. Maximum number of nav mesh tiles depends on +Maximum number of polygons per navigation mesh tile. Maximum number of navigation mesh tiles depends on this value. 22 bits is a limit to store both tile identifier and polygon identifier (tiles = 2^(22 - log2(polygons))). See `recastnavigation `_ for more details. .. Warning:: - Lower value may lead to ignored world geometry on nav mesh. - Greater value will reduce number of nav mesh tiles. + Lower value may lead to ignored world geometry on navigation mesh. + Greater value will reduce number of navigation mesh tiles. This condition is always true: ``max tiles number * max polygons per tile <= 4194304``. It's a limitation of `Recastnavigation `_ library. diff --git a/docs/source/reference/postprocessing/omwfx.rst b/docs/source/reference/postprocessing/omwfx.rst index 36a6f0883a..6d191e0a7b 100644 --- a/docs/source/reference/postprocessing/omwfx.rst +++ b/docs/source/reference/postprocessing/omwfx.rst @@ -189,19 +189,30 @@ The following functions can be accessed in any fragment or vertex shader. +--------------------------------------------------+-------------------------------------------------------------------------------+ | ``vec4 omw_GetLastPass(vec2 uv)`` | Returns RGBA color output of the last pass | +--------------------------------------------------+-------------------------------------------------------------------------------+ -| ``vec3 omw_GetNormals(vec2 uv)`` | Returns normalized worldspace normals [-1, 1] | -| | | -| | The values in sampler are in [0, 1] but are transformed to [-1, 1] | -+--------------------------------------------------+-----------------------+-------------------------------------------------------+ +| ``vec3 omw_GetNormals(vec2 uv)`` | Returns normalized view-space normals [-1, 1] | ++--------------------------------------------------+-------------------------------------------------------------------------------+ +| ``vec3 omw_GetNormalsWorldSpace(vec2 uv)`` | Returns normalized world-space normals [-1, 1] | ++--------------------------------------------------+-------------------------------------------------------------------------------+ | ``vec3 omw_GetWorldPosFromUV(vec2 uv)`` | Returns world position for given uv coordinate. | -+--------------------------------------------------+-----------------------+-------------------------------------------------------+ ++--------------------------------------------------+-------------------------------------------------------------------------------+ | ``float omw_GetLinearDepth(vec2 uv)`` | Returns the depth in game units for given uv coordinate. | -+--------------------------------------------------+-----------------------+-------------------------------------------------------+ ++--------------------------------------------------+-------------------------------------------------------------------------------+ | ``float omw_EstimateFogCoverageFromUV(vec2 uv)`` | Returns a fog coverage in the range from 0.0 (no fog) and 1.0 (full fog) | | | | | | Calculates an estimated fog coverage for given uv coordinate. | -+--------------------------------------------------+-----------------------+-------------------------------------------------------+ - ++--------------------------------------------------+-------------------------------------------------------------------------------+ +| ``int omw_GetPointLightCount()`` | Returns the number of point lights available to sample from in the scene. | ++--------------------------------------------------+-------------------------------------------------------------------------------+ +| ``vec3 omw_GetPointLightWorldPos(int index)`` | Returns the world space position of a point light. | ++--------------------------------------------------+-------------------------------------------------------------------------------+ +| ``vec3 omw_GetPointLightDiffuse(int index)`` | Returns the diffuse color of the point light. | ++--------------------------------------------------+-------------------------------------------------------------------------------+ +| ``int omw_GetPointLightAttenuation(int index)`` | Returns the attenuation values of the point light. | +| | | +| | The XYZ channels hold the constant, linear, and quadratic components. | ++--------------------------------------------------+-------------------------------------------------------------------------------+ +| ``float omw_GetPointLightRadius(int index)`` | Returns the radius of the point light, in game units. | ++--------------------------------------------------+-------------------------------------------------------------------------------+ Special Attributes ################## @@ -528,49 +539,138 @@ is not wanted and you want a custom render target. +------------------+---------------------+-----------------------------------------------------------------------------+ | mipmaps | boolean | Whether mipmaps should be generated every frame | +------------------+---------------------+-----------------------------------------------------------------------------+ +| clear_color | vec4 | The color the texture will be cleared to when it's first created | ++------------------+---------------------+-----------------------------------------------------------------------------+ -To use the render target a pass must be assigned to it, along with any optional clear or blend modes. +To use the render target a pass must be assigned to it, along with any optional blend modes. +As a restriction, only three render targets can be bound per pass with ``rt1``, ``rt2``, ``rt3``, respectively. -In the code snippet below a rendertarget is used to draw the red channel of a scene at half resolution, then a quarter. As a restriction, -only three render targets can be bound per pass with ``rt1``, ``rt2``, ``rt3``, respectively. +Blending modes can be useful at times. Below is a simple shader which, when activated, will slowly turn the screen pure red. +Notice how we only ever write the value `.01` to the `RT_Red` buffer. Since we're using appropriate blending modes the +color buffer will accumulate. .. code-block:: none - render_target RT_Downsample { - width_ratio = 0.5; - height_ratio = 0.5; - internal_format = r16f; + render_target RT_Red { + width = 4; + height = 4; + source_format = rgb; + internal_format = rgb16f; source_type = float; - source_format = red; + clear_color = vec4(1,0,0,1); } - render_target RT_Downsample4 { - width_ratio = 0.25; - height_ratio = 0.25; - } - - fragment downsample2x(target=RT_Downsample) { - + fragment red(target=RT_Red,blend=(add, src_color, one), rt1=RT_Red) { omw_In vec2 omw_TexCoord; void main() { - omw_FragColor.r = omw_GetLastShader(omw_TexCoord).r; + omw_FragColor.rgb = vec3(0.01,0,0); } } - fragment downsample4x(target=RT_Downsample4, rt1=RT_Downsample) { - + fragment view(rt1=RT_Red) { omw_In vec2 omw_TexCoord; void main() { - omw_FragColor = omw_Texture2D(RT_Downsample, omw_TexCoord); + omw_FragColor = omw_Texture2D(RT_Red, omw_TexCoord); } } -Now, when the `downsample2x` pass runs it will write to the target buffer instead of the default -one assigned by the engine. + technique { + author = "OpenMW"; + passes = red, view; + } + + +These custom render targets are persistent and ownership is given to the shader which defines them. +This gives potential to implement temporal effects by storing previous frame data in these buffers. +Below is an example which calculates a naive average scene luminance and transitions between values smoothly. + +.. code-block:: none + + render_target RT_Lum { + width = 256; + height = 256; + mipmaps = true; + source_format = rgb; + internal_format = rgb16f; + source_type = float; + min_filter = linear_mipmap_linear; + mag_filter = linear; + } + + render_target RT_LumAvg { + source_type = float; + source_format = rgb; + internal_format = rgb16f; + min_filter = nearest; + mag_filter = nearest; + } + + render_target RT_LumAvgLastFrame { + source_type = float; + source_format = rgb; + internal_format = rgb16f; + min_filter = nearest; + mag_filter = nearest; + } + + fragment calculateLum(target=RT_Lum) { + omw_In vec2 omw_TexCoord; + + void main() + { + vec3 orgi = pow(omw_GetLastShader(omw_TexCoord), vec4(2.2)).rgb; + omw_FragColor.rgb = orgi; + } + } + + fragment fetchLumAvg(target=RT_LumAvg, rt1=RT_Lum, rt2=RT_LumAvgLastFrame) { + omw_In vec2 omw_TexCoord; + + void main() + { + vec3 avgLumaCurrFrame = textureLod(RT_Lum, vec2(0.5, 0.5), 6).rgb; + vec3 avgLumaLastFrame = omw_Texture2D(RT_LumAvgLastFrame, vec2(0.5, 0.5)).rgb; + + const float speed = 0.9; + + vec3 avgLuma = avgLumaLastFrame + (avgLumaCurrFrame - avgLumaLastFrame) * (1.0 - exp(-omw.deltaSimulationTime * speed)); + + omw_FragColor.rgb = avgLuma; + } + } + + fragment adaptation(rt1=RT_LumAvg) { + omw_In vec2 omw_TexCoord; + + void main() + { + vec3 avgLuma = omw_Texture2D(RT_LumAvg, vec2(0.5, 0.5)).rgb; + + if (omw_TexCoord.y < 0.2) + omw_FragColor = vec4(avgLuma, 1.0); + else + omw_FragColor = omw_GetLastShader(omw_TexCoord); + } + } + + fragment store(target=RT_LumAvgLastFrame, rt1=RT_LumAvg) { + void main() + { + vec3 avgLuma = omw_Texture2D(RT_LumAvg, vec2(0.5, 0.5)).rgb; + omw_FragColor.rgb = avgLuma; + } + } + + technique { + author = "OpenMW"; + passes = calculateLum, fetchLumAvg, store, adaptation; + glsl_version = 330; + } + Simple Example ############## diff --git a/files/data/l10n/OMWShaders/en.yaml b/files/data/l10n/OMWShaders/en.yaml index 8221be933f..6588591f00 100644 --- a/files/data/l10n/OMWShaders/en.yaml +++ b/files/data/l10n/OMWShaders/en.yaml @@ -34,6 +34,7 @@ DisplayDepthFactorName: "Depth colour factor" DisplayDepthFactorDescription: "Determines correlation between pixel depth value and its output colour. High values lead to brighter image." DisplayDepthName: "Visualize depth buffer" DisplayNormalsName: "Visualize pass normals" +NormalsInWorldSpace: "Show normals in world space" ContrastLevelDescription: "Constrast level." ContrastLevelName: "Constrast" GammaLevelDescription: "Gamma level." diff --git a/files/data/l10n/OMWShaders/ru.yaml b/files/data/l10n/OMWShaders/ru.yaml index b886f72b54..7a2bcfe80d 100644 --- a/files/data/l10n/OMWShaders/ru.yaml +++ b/files/data/l10n/OMWShaders/ru.yaml @@ -33,6 +33,7 @@ DisplayDepthName: "Визуализация буфера глубины" DisplayDepthFactorDescription: "Определяет соотношение между значением глубины пикселя и его цветом. Чем выше значение, тем ярче будет изображение." DisplayDepthFactorName: "Соотношение цвета" DisplayNormalsName: "Визуализация нормалей" +NormalsInWorldSpace: "Показывать нормали мирового пространства" ContrastLevelDescription: "Контрастность изображения" ContrastLevelName: "Контрастность" GammaLevelDescription: "Яркость изображения" diff --git a/files/data/scripts/omw/playercontrols.lua b/files/data/scripts/omw/playercontrols.lua index 0bd7cf6bea..7b405180e8 100644 --- a/files/data/scripts/omw/playercontrols.lua +++ b/files/data/scripts/omw/playercontrols.lua @@ -199,10 +199,21 @@ local function onInputAction(action) end end +local function onSave() + return {sneaking = self.controls.sneak} +end + +local function onLoad(data) + if not data then return end + self.controls.sneak = data.sneaking or false +end + return { engineHandlers = { onFrame = onFrame, onInputAction = onInputAction, + onSave = onSave, + onLoad = onLoad, }, interfaceName = 'Controls', --- diff --git a/files/data/scripts/omw/ui.lua b/files/data/scripts/omw/ui.lua index 48412f6a0f..1e76b8e141 100644 --- a/files/data/scripts/omw/ui.lua +++ b/files/data/scripts/omw/ui.lua @@ -236,7 +236,7 @@ return { --- -- Returns if the player HUD is visible or not -- @function [parent=#UI] isHudVisible - -- @return #bool + -- @return #boolean isHudVisible = function() return ui._isHudVisible() end, -- TODO diff --git a/files/data/shaders/debug.omwfx b/files/data/shaders/debug.omwfx index 360dfa26cd..a0c8754ec4 100644 --- a/files/data/shaders/debug.omwfx +++ b/files/data/shaders/debug.omwfx @@ -19,6 +19,11 @@ uniform_bool uDisplayNormals { display_name = "#{OMWShaders:DisplayNormalsName}"; } +uniform_bool uNormalsInWorldSpace { + default = false; + display_name = "#{OMWShaders:NormalsInWorldSpace}"; +} + fragment main { omw_In vec2 omw_TexCoord; @@ -30,8 +35,12 @@ fragment main { if (uDisplayDepth) omw_FragColor = vec4(vec3(omw_GetLinearDepth(omw_TexCoord) / omw.far * uDepthFactor), 1.0); #if OMW_NORMALS - if (uDisplayNormals && (!uDisplayDepth || omw_TexCoord.x < 0.5)) - omw_FragColor.rgb = omw_GetNormals(omw_TexCoord) * 0.5 + 0.5; + if (uDisplayNormals && (!uDisplayDepth || omw_TexCoord.x < 0.5)) { + if (uNormalsInWorldSpace) + omw_FragColor.rgb = omw_GetNormalsWorldSpace(omw_TexCoord) * 0.5 + 0.5; + else + omw_FragColor.rgb = omw_GetNormals(omw_TexCoord) * 0.5 + 0.5; + } #endif } } diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 441136805b..1a2ec106d9 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -863,6 +863,7 @@ -- @field #string icon VFS path to the icon -- @field #string specialization Skill specialization. Either combat, magic, or stealth. -- @field #MagicSchoolData school Optional magic school +-- @field #string attribute The id of the skill's governing attribute -- @type MagicSchoolData -- @field #string name Human-readable name diff --git a/files/lua_api/openmw/interfaces.lua b/files/lua_api/openmw/interfaces.lua index 5048fc2f3e..d4a290aa47 100644 --- a/files/lua_api/openmw/interfaces.lua +++ b/files/lua_api/openmw/interfaces.lua @@ -20,6 +20,9 @@ --- -- @field [parent=#interfaces] scripts.omw.ui#scripts.omw.ui UI +--- +-- @field [parent=#interfaces] scripts.omw.usehandlers#scripts.omw.usehandlers ItemUsage + --- -- @function [parent=#interfaces] __index -- @param #interfaces self diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index b300351df3..73bed6799d 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -15,6 +15,12 @@ -- @param openmw.core#GameObject actor -- @return #number +--- +-- Check if the given actor is dead. +-- @function [parent=#Actor] isDead +-- @param openmw.core#GameObject actor +-- @return #boolean + --- -- Agent bounds to be used for pathfinding functions. -- @function [parent=#Actor] getPathfindingAgentBounds @@ -959,7 +965,6 @@ -- Whether teleportation for this player is enabled. -- @function [parent=#Player] isTeleportingEnabled -- @param openmw.core#GameObject player --- @param #boolean player -- @return #boolean --- diff --git a/files/lua_api/openmw/world.lua b/files/lua_api/openmw/world.lua index 13fa75e0ad..5baa624c5d 100644 --- a/files/lua_api/openmw/world.lua +++ b/files/lua_api/openmw/world.lua @@ -22,6 +22,10 @@ -- Functions related to MWScript. -- @type MWScriptFunctions +--- +-- @type MWScriptVariables +-- @map <#string, #number> + --- -- Returns local mwscript on ``object``. Returns `nil` if the script doesn't exist or is not started. -- @function [parent=#MWScriptFunctions] getLocalScript @@ -33,7 +37,7 @@ -- Returns mutable global variables. In multiplayer, these may be specific to the provided player. -- @function [parent=#MWScriptFunctions] getGlobalVariables -- @param openmw.core#GameObject player (optional) Will be used in multiplayer mode to get the globals if there is a separate instance for each player. Currently has no effect. --- @return #list<#number> +-- @return #MWScriptVariables --- -- Returns global mwscript with given recordId. Returns `nil` if the script doesn't exist or is not started. diff --git a/files/settings-default.cfg b/files/settings-default.cfg index bbc6b4d1c8..da1c97519a 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -365,6 +365,11 @@ unarmed creature attacks damage armor = false # 2 = Cylinder actor collision shape type = 0 +# When false the player character will base movement on animations. This will sway the camera +# while moving in third person like in vanilla, and reproduce movement bugs caused by glitchy +# vanilla animations. +player movement ignores animation = false + [General] # Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16). diff --git a/files/shaders/compatibility/bs/default.frag b/files/shaders/compatibility/bs/default.frag index 8c429947b0..f1be8da80c 100644 --- a/files/shaders/compatibility/bs/default.frag +++ b/files/shaders/compatibility/bs/default.frag @@ -95,7 +95,7 @@ void main() #endif if (matSpec != vec3(0.0)) - gl_FragData[0].xyz += getSpecular(viewNormal, normalize(passViewPos.xyz), shininess, matSpec) * shadowing; + gl_FragData[0].xyz += matSpec * getSpecular(viewNormal, passViewPos, shininess, shadowing); gl_FragData[0] = applyFogAtDist(gl_FragData[0], euclideanDepth, linearDepth, far); diff --git a/files/shaders/compatibility/objects.frag b/files/shaders/compatibility/objects.frag index 4caf6c97e2..b86678af87 100644 --- a/files/shaders/compatibility/objects.frag +++ b/files/shaders/compatibility/objects.frag @@ -242,7 +242,7 @@ vec3 viewNormal = normalize(gl_NormalMatrix * normal); matSpec *= specStrength; if (matSpec != vec3(0.0)) { - gl_FragData[0].xyz += getSpecular(viewNormal, viewVec, shininess, matSpec) * shadowing; + gl_FragData[0].xyz += matSpec * getSpecular(viewNormal, passViewPos, shininess, shadowing); } gl_FragData[0] = applyFogAtPos(gl_FragData[0], passViewPos, far); diff --git a/files/shaders/compatibility/terrain.frag b/files/shaders/compatibility/terrain.frag index a2fbddf3f7..744a56d18b 100644 --- a/files/shaders/compatibility/terrain.frag +++ b/files/shaders/compatibility/terrain.frag @@ -108,7 +108,7 @@ void main() if (matSpec != vec3(0.0)) { - gl_FragData[0].xyz += getSpecular(viewNormal, normalize(passViewPos), shininess, matSpec) * shadowing; + gl_FragData[0].xyz += matSpec * getSpecular(viewNormal, passViewPos, shininess, shadowing); } gl_FragData[0] = applyFogAtDist(gl_FragData[0], euclideanDepth, linearDepth, far); diff --git a/files/shaders/compatibility/water.frag b/files/shaders/compatibility/water.frag index 83fb8a53e0..987dab10a6 100644 --- a/files/shaders/compatibility/water.frag +++ b/files/shaders/compatibility/water.frag @@ -43,6 +43,7 @@ const float SCATTER_AMOUNT = 0.3; // amount of sunlight scatter const vec3 SCATTER_COLOUR = vec3(0.0,1.0,0.95); // colour of sunlight scattering const vec3 SUN_EXT = vec3(0.45, 0.55, 0.68); //sunlight extinction +const float SUN_SPEC_FADING_THRESHOLD = 0.15; // visibility at which sun specularity starts to fade const float SPEC_HARDNESS = 256.0; // specular highlights hardness @@ -172,6 +173,8 @@ void main(void) vec3 waterColor = WATER_COLOR * sunFade; vec4 sunSpec = lcalcSpecular(0); + // alpha component is sun visibility; we want to start fading specularity when visibility is low + sunSpec.a = min(1.0, sunSpec.a / SUN_SPEC_FADING_THRESHOLD); // artificial specularity to make rain ripples more noticeable vec3 skyColorEstimate = vec3(max(0.0, mix(-0.3, 1.0, sunFade))); @@ -179,7 +182,7 @@ void main(void) #if REFRACTION // no alpha here, so make sure raindrop ripple specularity gets properly subdued - rainSpecular *= clamp(fresnel*6.0 + specular * sunSpec.w, 0.0, 1.0); + rainSpecular *= clamp(fresnel*6.0 + specular * sunSpec.a, 0.0, 1.0); // refraction vec3 refraction = sampleRefractionMap(screenCoords - screenCoordsOffset).rgb; @@ -200,7 +203,7 @@ void main(void) vec3 scatterColour = mix(SCATTER_COLOUR*vec3(1.0,0.4,0.0), SCATTER_COLOUR, clamp(1.0-exp(-sunHeight*SUN_EXT), 0.0, 1.0)); vec3 lR = reflect(lVec, lNormal); float lightScatter = clamp(dot(lVec,lNormal)*0.7+0.3, 0.0, 1.0) * clamp(dot(lR, vVec)*2.0-1.2, 0.0, 1.0) * SCATTER_AMOUNT * sunFade * clamp(1.0-exp(-sunHeight), 0.0, 1.0); - gl_FragData[0].xyz = mix( mix(refraction, scatterColour, lightScatter), reflection, fresnel) + specular * sunSpec.xyz + rainSpecular; + gl_FragData[0].xyz = mix(mix(refraction, scatterColour, lightScatter), reflection, fresnel) + specular * sunSpec.rgb * sunSpec.a + rainSpecular; gl_FragData[0].w = 1.0; // wobbly water: hard-fade into refraction texture at extremely low depth, with a wobble based on normal mapping @@ -213,8 +216,8 @@ void main(void) shoreOffset = clamp(mix(shoreOffset, 1.0, clamp(linearDepth / WOBBLY_SHORE_FADE_DISTANCE, 0.0, 1.0)), 0.0, 1.0); gl_FragData[0].xyz = mix(rawRefraction, gl_FragData[0].xyz, shoreOffset); #else - gl_FragData[0].xyz = mix(reflection, waterColor, (1.0-fresnel)*0.5) + specular * sunSpec.xyz + rainSpecular; - gl_FragData[0].w = clamp(fresnel*6.0 + specular * sunSpec.w, 0.0, 1.0); //clamp(fresnel*2.0 + specular * gl_LightSource[0].specular.w, 0.0, 1.0); + gl_FragData[0].xyz = mix(reflection, waterColor, (1.0-fresnel)*0.5) + specular * sunSpec.rgb * sunSpec.a + rainSpecular; + gl_FragData[0].w = clamp(fresnel*6.0 + specular * sunSpec.a, 0.0, 1.0); //clamp(fresnel*2.0 + specular * gl_LightSource[0].specular.a, 0.0, 1.0); #endif gl_FragData[0] = applyFogAtDist(gl_FragData[0], radialDepth, linearDepth, far); diff --git a/files/shaders/lib/light/lighting.glsl b/files/shaders/lib/light/lighting.glsl index 8c1262ba4d..8351fce8a0 100644 --- a/files/shaders/lib/light/lighting.glsl +++ b/files/shaders/lib/light/lighting.glsl @@ -90,15 +90,47 @@ void doLighting(vec3 viewPos, vec3 viewNormal, out vec3 diffuseLight, out vec3 a } } -vec3 getSpecular(vec3 viewNormal, vec3 viewDirection, float shininess, vec3 matSpec) +float calcSpecIntensity(vec3 viewNormal, vec3 viewDir, float shininess, vec3 lightDir) { - vec3 lightDir = normalize(lcalcPosition(0)); - float NdotL = dot(viewNormal, lightDir); - if (NdotL <= 0.0) - return vec3(0.0); - vec3 halfVec = normalize(lightDir - viewDirection); - float NdotH = dot(viewNormal, halfVec); - return pow(max(NdotH, 0.0), max(1e-4, shininess)) * lcalcSpecular(0).xyz * matSpec; + if (dot(viewNormal, lightDir) > 0.0) + { + vec3 halfVec = normalize(lightDir - viewDir); + float NdotH = max(dot(viewNormal, halfVec), 0.0); + return pow(NdotH, shininess); + } + + return 0.0; +} + +vec3 getSpecular(vec3 viewNormal, vec3 viewPos, float shininess, float shadowing) +{ + shininess = max(shininess, 1e-4); + vec3 viewDir = normalize(viewPos); + vec3 specularLight = lcalcSpecular(0).xyz * calcSpecIntensity(viewNormal, viewDir, shininess, normalize(lcalcPosition(0))); + specularLight *= shadowing; + + for (int i = @startLight; i < @endLight; ++i) + { +#if @lightingMethodUBO + int lightIndex = PointLightIndex[i]; +#else + int lightIndex = i; +#endif + + vec3 lightPos = lcalcPosition(lightIndex) - viewPos; + float lightDistance = length(lightPos); + +#if !@lightingMethodFFP + if (lightDistance > lcalcRadius(lightIndex) * 2.0) + continue; +#endif + + float illumination = lcalcIllumination(lightIndex, lightDistance); + float intensity = calcSpecIntensity(viewNormal, viewDir, shininess, normalize(lightPos)); + specularLight += lcalcSpecular(lightIndex).xyz * intensity * illumination; + } + + return specularLight; } #endif diff --git a/files/ui/settingspage.ui b/files/ui/settingspage.ui index c61b4f4229..0340509205 100644 --- a/files/ui/settingspage.ui +++ b/files/ui/settingspage.ui @@ -14,7 +14,7 @@ - 0 + 1 @@ -116,10 +116,10 @@ - <html><head/><body><p>Enable navigator. When enabled background threads are started to build nav mesh for world geometry. Pathfinding system uses nav mesh to build paths. When disabled only pathgrid is used to build paths. Single-core CPU systems may have big performance impact on exiting interior location and moving across exterior world. May slightly affect performance on multi-core CPU systems. Multi-core CPU systems may have different latency for nav mesh update depending on other settings and system performance. Moving across external world, entering/exiting location produce nav mesh update. NPC and creatures may not be able to find path before nav mesh is built around them. Try to disable this if you want to have old fashioned AI which doesn’t know where to go when you stand behind that stone and casting a firebolt.</p></body></html> + <html><head/><body><p><a name="docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0"/>When enabled, a navigation mesh is built in the background for world geometry to be used for pathfinding. When disabled only the path grid is used to build paths. Single-core CPU systems may have a big performance impact on existing interior location and moving across the exterior world. May slightly affect performance on multi-core CPU systems. Multi-core CPU systems may have different latency for nav mesh update depending on other settings and system performance. Moving across external world, entering/exiting location produce nav mesh update. NPC and creatures may not be able to find path before nav mesh is built around them. Try to disable this if you want to have old fashioned AI which doesn't know where to go when you stand behind that stone and cast a firebolt.</p></body></html> - Build nav mesh for world geometry + Use navigation mesh for pathfinding @@ -293,8 +293,8 @@ 0 0 - 680 - 882 + 671 + 774 @@ -377,6 +377,16 @@ + + + + <html><head/><body><p>In third person, the camera will sway along with the movement animations of the player. Enabling this option disables this swaying by having the player character move independently of its animation. This was the default behavior of OpenMW 0.48 and earlier.</p></body></html> + + + Player movement ignores animation + + +