diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 810e23d38..6bfe0bf23 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -116,12 +116,13 @@ variables: &cs-targets - .\ActivateMSVC.ps1 - cmake --build . --config $config --target ($targets.Split(',')) - cd $config + - echo "CI_COMMIT_REF_NAME ${CI_COMMIT_REF_NAME}`nCI_JOB_ID ${CI_JOB_ID}`nCI_COMMIT_SHA ${CI_COMMIT_SHA}" | Out-File -Encoding UTF8 CI-ID.txt - | if (Get-ChildItem -Recurse *.pdb) { - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb' + 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb' CI-ID.txt Get-ChildItem -Recurse *.pdb | Remove-Item } - - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}.zip '*' + - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}.zip '*' after_script: - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log cache: @@ -206,12 +207,13 @@ Windows_Ninja_CS_RelWithDebInfo: - cd MSVC2019_64 - cmake --build . --config $config --target ($targets.Split(',')) - cd $config + - echo "CI_COMMIT_REF_NAME ${CI_COMMIT_REF_NAME}`nCI_JOB_ID ${CI_JOB_ID}`nCI_COMMIT_SHA ${CI_COMMIT_SHA}" | Out-File -Encoding UTF8 CI-ID.txt - | if (Get-ChildItem -Recurse *.pdb) { - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb' + 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip '*.pdb' CI-ID.txt Get-ChildItem -Recurse *.pdb | Remove-Item } - - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}.zip '*' + - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}.zip '*' after_script: - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log cache: diff --git a/AUTHORS.md b/AUTHORS.md index e6ff67293..53efdd286 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -215,6 +215,7 @@ Programmers Yohaulticetl Yuri Krupenin zelurker + Noah Gooder Documentation ------------- diff --git a/CHANGELOG.md b/CHANGELOG.md index aa74164d9..acfdc9e50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -98,6 +98,11 @@ Bug #5758: Paralyzed actors behavior is inconsistent with vanilla Bug #5762: Movement solver is insufficiently robust Bug #5821: NPCs from mods getting removed if mod order was changed + Bug #5835: OpenMW doesn't accept negative values for NPC's hello, alarm, fight, and flee + Bug #5836: OpenMW dialogue/greeting/voice filter doesn't accept negative Ai values for NPC's hello, alarm, fight, and flee + Bug #5838: Local map and other menus become blank in some locations while playing Wizards' Islands mod. + Bug #5840: GetSoundPlaying "Health Damage" doesn't play when NPC hits target with shield effect ( vanilla engine behavior ) + Bug #5841: Can't Cast Zero Cost Spells When Magicka is < 0 Feature #390: 3rd person look "over the shoulder" Feature #1536: Show more information about level on menu Feature #2386: Distant Statics in the form of Object Paging @@ -105,6 +110,7 @@ Feature #2686: Timestamps in openmw.log Feature #3171: OpenMW-CS: Instance drag selection Feature #4894: Consider actors as obstacles for pathfinding + Feature #4977: Use the "default icon.tga" when an item's icon is not found Feature #5043: Head Bobbing Feature #5199: Improve Scene Colors Feature #5297: Add a search function to the "Datafiles" tab of the OpenMW launcher @@ -130,6 +136,7 @@ Feature #5813: Instanced groundcover support Task #5480: Drop Qt4 support Task #5520: Improve cell name autocompleter implementation + Task #5844: Update 'toggle sneak' documentation 0.46.0 ------ diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 4e1bb491d..67f0eac31 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -218,6 +218,7 @@ namespace MWBase /// /// \note If cell==0, the cell the player is currently in will be used instead to /// generate a name. + virtual std::string getCellName(const ESM::Cell* cell) const = 0; virtual void removeRefScript (MWWorld::RefData *ref) = 0; //< Remove the script attached to ref from mLocalScripts diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 4d4d085d5..e48c6d3ff 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -379,45 +379,31 @@ namespace MWClass return info; } - std::string Door::getDestination (const MWWorld::LiveCellRef& door) + std::string Door::getDestination(const MWWorld::LiveCellRef& door) { const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); - std::string dest; - if (door.mRef.getDestCell() != "") - { - // door leads to an interior, use interior name as tooltip - dest = door.mRef.getDestCell(); - - /* - Start of tes3mp change (major) - - If there is a destination override in the mwmp::Worldstate for this door's original - destination, use it - */ - if (mwmp::Main::get().getNetworking()->getWorldstate()->destinationOverrides.count(dest) != 0) - dest = mwmp::Main::get().getNetworking()->getWorldstate()->destinationOverrides[dest]; - /* - End of tes3mp change (major) - */ - } - else + std::string dest = door.mRef.getDestCell(); + if (dest.empty()) { // door leads to exterior, use cell name (if any), otherwise translated region name - int x,y; - MWBase::Environment::get().getWorld()->positionToIndex (door.mRef.getDoorDest().pos[0], door.mRef.getDoorDest().pos[1], x, y); - const ESM::Cell* cell = store.get().find(x,y); - if (cell->mName != "") - dest = cell->mName; - else - { - const ESM::Region* region = - store.get().find(cell->mRegion); - - //name as is, not a token - return MyGUI::TextIterator::toTagsString(region->mName); - } + int x, y; + auto world = MWBase::Environment::get().getWorld(); + world->positionToIndex(door.mRef.getDoorDest().pos[0], door.mRef.getDoorDest().pos[1], x, y); + const ESM::Cell* cell = world->getStore().get().search(x, y); + dest = world->getCellName(cell); } + /* + Start of tes3mp addition + + If there is a destination override in the mwmp::Worldstate for this door's original + destination, use it + */ + else if (mwmp::Main::get().getNetworking()->getWorldstate()->destinationOverrides.count(dest) != 0) + dest = mwmp::Main::get().getNetworking()->getWorldstate()->destinationOverrides[dest]; + /* + End of tes3mp addition + */ return "#{sCell=" + dest + "}"; } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index d369cf673..2af3cb516 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -435,10 +435,10 @@ namespace MWClass { const MWWorld::LiveCellRef *ref = ptr.get(); - std::string model = "meshes\\base_anim.nif"; + std::string model = Settings::Manager::getString("baseanim", "Models"); const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().find(ref->mBase->mRace); if(race->mData.mFlags & ESM::Race::Beast) - model = "meshes\\base_animkna.nif"; + model = Settings::Manager::getString("baseanimkna", "Models"); return model; } @@ -448,12 +448,12 @@ namespace MWClass const MWWorld::LiveCellRef *npc = ptr.get(); const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().search(npc->mBase->mRace); if(race && race->mData.mFlags & ESM::Race::Beast) - models.emplace_back("meshes\\base_animkna.nif"); + models.emplace_back(Settings::Manager::getString("baseanimkna", "Models")); // keep these always loaded just in case - models.emplace_back("meshes/xargonian_swimkna.nif"); - models.emplace_back("meshes/xbase_anim_female.nif"); - models.emplace_back("meshes/xbase_anim.nif"); + models.emplace_back(Settings::Manager::getString("xargonianswimkna", "Models")); + models.emplace_back(Settings::Manager::getString("xbaseanimfemale", "Models")); + models.emplace_back(Settings::Manager::getString("xbaseanim", "Models")); if (!npc->mBase->mModel.empty()) models.push_back("meshes/"+npc->mBase->mModel); diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index a3c326ab8..334a9db39 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -316,7 +316,7 @@ int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) con case SelectWrapper::Function_AiSetting: return mActor.getClass().getCreatureStats (mActor).getAiSetting ( - (MWMechanics::CreatureStats::AiSetting)select.getArgument()).getModified(); + (MWMechanics::CreatureStats::AiSetting)select.getArgument()).getModified(false); case SelectWrapper::Function_PcAttribute: diff --git a/apps/openmw/mwgui/itemwidget.cpp b/apps/openmw/mwgui/itemwidget.cpp index 940e95a7e..7c7fa8828 100644 --- a/apps/openmw/mwgui/itemwidget.cpp +++ b/apps/openmw/mwgui/itemwidget.cpp @@ -6,6 +6,9 @@ #include // correctIconPath +#include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" @@ -106,7 +109,10 @@ namespace MWGui std::string invIcon = ptr.getClass().getInventoryIcon(ptr); if (invIcon.empty()) invIcon = "default icon.tga"; - setIcon(MWBase::Environment::get().getWindowManager()->correctIconPath(invIcon)); + invIcon = MWBase::Environment::get().getWindowManager()->correctIconPath(invIcon); + if (!MWBase::Environment::get().getResourceSystem()->getVFS()->exists(invIcon)) + invIcon = MWBase::Environment::get().getWindowManager()->correctIconPath("default icon.tga"); + setIcon(invIcon); } diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index c18755630..1c6b0c55e 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -996,29 +996,29 @@ namespace MWMechanics if (actor.getClass().hasInventoryStore(actor)) actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Poison); } - else if (effects.get(ESM::MagicEffect::CureParalyzation).getModifier() > 0) + if (effects.get(ESM::MagicEffect::CureParalyzation).getModifier() > 0) { creatureStats.getActiveSpells().purgeEffect(ESM::MagicEffect::Paralyze); creatureStats.getSpells().purgeEffect(ESM::MagicEffect::Paralyze); if (actor.getClass().hasInventoryStore(actor)) actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Paralyze); } - else if (effects.get(ESM::MagicEffect::CureCommonDisease).getModifier() > 0) + if (effects.get(ESM::MagicEffect::CureCommonDisease).getModifier() > 0) { creatureStats.getSpells().purgeCommonDisease(); } - else if (effects.get(ESM::MagicEffect::CureBlightDisease).getModifier() > 0) + if (effects.get(ESM::MagicEffect::CureBlightDisease).getModifier() > 0) { creatureStats.getSpells().purgeBlightDisease(); } - else if (effects.get(ESM::MagicEffect::CureCorprusDisease).getModifier() > 0) + if (effects.get(ESM::MagicEffect::CureCorprusDisease).getModifier() > 0) { creatureStats.getActiveSpells().purgeCorprusDisease(); creatureStats.getSpells().purgeCorprusDisease(); if (actor.getClass().hasInventoryStore(actor)) actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Corprus, true); } - else if (effects.get(ESM::MagicEffect::RemoveCurse).getModifier() > 0) + if (effects.get(ESM::MagicEffect::RemoveCurse).getModifier() > 0) { creatureStats.getSpells().purgeCurses(); } diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index ad5bb9897..f330d5128 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -452,6 +452,8 @@ namespace MWMechanics MWMechanics::DynamicStat health = attackerStats.getHealth(); health.setCurrent(health.getCurrent() - x); attackerStats.setHealth(health); + + MWBase::Environment::get().getSoundManager()->playSound3D(attacker, "Health Damage", 1.0f, 1.0f); } } diff --git a/apps/openmw/mwmechanics/spellutil.cpp b/apps/openmw/mwmechanics/spellutil.cpp index 8b2f5c46c..0c667e680 100644 --- a/apps/openmw/mwmechanics/spellutil.cpp +++ b/apps/openmw/mwmechanics/spellutil.cpp @@ -123,7 +123,7 @@ namespace MWMechanics if (spell->mData.mType != ESM::Spell::ST_Spell) return 100; - if (checkMagicka && stats.getMagicka().getCurrent() < spell->mData.mCost) + if (checkMagicka && spell->mData.mCost > 0 && stats.getMagicka().getCurrent() < spell->mData.mCost) return 0; if (spell->mData.mFlags & ESM::Spell::F_Always) diff --git a/apps/openmw/mwmechanics/stat.cpp b/apps/openmw/mwmechanics/stat.cpp index 7f71cf9b1..c87de2ccb 100644 --- a/apps/openmw/mwmechanics/stat.cpp +++ b/apps/openmw/mwmechanics/stat.cpp @@ -18,8 +18,10 @@ namespace MWMechanics } template - T Stat::getModified() const + T Stat::getModified(bool capped) const { + if(!capped) + return mModified; return std::max(static_cast(0), mModified); } diff --git a/apps/openmw/mwmechanics/stat.hpp b/apps/openmw/mwmechanics/stat.hpp index 5f49da48e..fb9dca922 100644 --- a/apps/openmw/mwmechanics/stat.hpp +++ b/apps/openmw/mwmechanics/stat.hpp @@ -28,7 +28,7 @@ namespace MWMechanics const T& getBase() const; - T getModified() const; + T getModified(bool capped = true) const; T getCurrentModified() const; T getModifier() const; T getCurrentModifier() const; diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index 9d2957409..5f0322a1f 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -38,7 +38,7 @@ namespace MWPhysics ContactCollectionCallback(const btCollisionObject * me, osg::Vec3f velocity) : mMe(me) { m_collisionFilterGroup = me->getBroadphaseHandle()->m_collisionFilterGroup; - m_collisionFilterMask = me->getBroadphaseHandle()->m_collisionFilterMask; + m_collisionFilterMask = me->getBroadphaseHandle()->m_collisionFilterMask & ~CollisionType_Projectile; mVelocity = Misc::Convert::toBullet(velocity); } btScalar addSingleResult(btManifoldPoint & contact, const btCollisionObjectWrapper * colObj0Wrap, int partId0, int index0, const btCollisionObjectWrapper * colObj1Wrap, int partId1, int index1) override diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index f8ff3780d..d8c759935 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1500,7 +1500,7 @@ namespace MWRender MWWorld::LiveCellRef *ref = mPtr.get(); if(ref->mBase->mFlags & ESM::Creature::Bipedal) { - defaultSkeleton = "meshes\\xbase_anim.nif"; + defaultSkeleton = Settings::Manager::getString("xbaseanim", "Models"); inject = true; } } diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 14735050c..262b03229 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -184,6 +184,9 @@ namespace MWRender osg::ref_ptr dummyTexture = new osg::Texture2D(); dummyTexture->setInternalFormat(GL_RED); dummyTexture->setTextureSize(1, 1); + // This might clash with a shadow map, so make sure it doesn't cast shadows + dummyTexture->setShadowComparison(true); + dummyTexture->setShadowCompareFunc(osg::Texture::ShadowCompareFunc::ALWAYS); stateset->setTextureAttributeAndModes(7, dummyTexture, osg::StateAttribute::ON); stateset->setTextureAttribute(7, noBlendAlphaEnv, osg::StateAttribute::ON); stateset->addUniform(new osg::Uniform("noAlpha", true)); diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index f1df6c90f..298711162 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -10,7 +10,7 @@ #include #include #include - +#include #include #include "../mwbase/environment.hpp" @@ -35,7 +35,7 @@ CreatureAnimation::CreatureAnimation(const MWWorld::Ptr &ptr, setObjectRoot(model, false, false, true); if((ref->mBase->mFlags&ESM::Creature::Bipedal)) - addAnimSource("meshes\\xbase_anim.nif", model); + addAnimSource(Settings::Manager::getString("xbaseanim", "Models"), model); addAnimSource(model, model); } } @@ -54,7 +54,7 @@ CreatureWeaponAnimation::CreatureWeaponAnimation(const MWWorld::Ptr &ptr, const if((ref->mBase->mFlags&ESM::Creature::Bipedal)) { - addAnimSource("meshes\\xbase_anim.nif", model); + addAnimSource(Settings::Manager::getString("xbaseanim", "Models"), model); } addAnimSource(model, model); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index d97e57115..b538f0b7b 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -524,7 +524,7 @@ void NpcAnimation::updateNpcBase() if(!is1stPerson) { - const std::string base = "meshes\\xbase_anim.nif"; + const std::string base = Settings::Manager::getString("xbaseanim", "Models"); if (smodel != base && !isWerewolf) addAnimSource(base, smodel); @@ -538,7 +538,7 @@ void NpcAnimation::updateNpcBase() } else { - const std::string base = "meshes\\xbase_anim.1st.nif"; + const std::string base = Settings::Manager::getString("xbaseanim1st", "Models"); if (smodel != base && !isWerewolf) addAnimSource(base, smodel); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 6ba4baec5..a3aee5c0f 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -456,12 +456,15 @@ namespace MWRender mSky->listAssetsToPreload(workItem->mModels, workItem->mTextures); mWater->listAssetsToPreload(workItem->mTextures); - const char* basemodels[] = {"xbase_anim", "xbase_anim.1st", "xbase_anim_female", "xbase_animkna"}; - for (size_t i=0; imModels.push_back(std::string("meshes/") + basemodels[i] + ".nif"); - workItem->mKeyframes.push_back(std::string("meshes/") + basemodels[i] + ".kf"); - } + workItem->mModels.push_back(Settings::Manager::getString("xbaseanim", "Models")); + workItem->mModels.push_back(Settings::Manager::getString("xbaseanim1st", "Models")); + workItem->mModels.push_back(Settings::Manager::getString("xbaseanimfemale", "Models")); + workItem->mModels.push_back(Settings::Manager::getString("xargonianswimkna", "Models")); + + workItem->mKeyframes.push_back(Settings::Manager::getString("xbaseanimkf", "Models")); + workItem->mKeyframes.push_back(Settings::Manager::getString("xbaseanim1stkf", "Models")); + workItem->mKeyframes.push_back(Settings::Manager::getString("xbaseanimfemalekf", "Models")); + workItem->mKeyframes.push_back(Settings::Manager::getString("xargonianswimknakf", "Models")); workItem->mTextures.emplace_back("textures/_land_default.dds"); diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index df33c8637..58d6641dd 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -255,7 +255,7 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - runtime.push(ptr.getClass().getCreatureStats (ptr).getAiSetting (mIndex).getModified()); + runtime.push(ptr.getClass().getCreatureStats (ptr).getAiSetting (mIndex).getModified(false)); } }; template @@ -290,20 +290,20 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - MWMechanics::Stat stat = ptr.getClass().getCreatureStats(ptr).getAiSetting(mIndex); - /* Start of tes3mp addition Track the original stat value, to ensure we don't send repetitive packets to the server about its changes */ + MWMechanics::Stat stat = ptr.getClass().getCreatureStats(ptr).getAiSetting(mIndex); + int initialValue = stat.getBase(); /* End of tes3mp addition - */ + */ - stat.setModified(value, 0); + ptr.getClass().getCreatureStats(ptr).setAiSetting(mIndex, value); ptr.getClass().setBaseAISetting(ptr.getCellRef().getRefId(), mIndex, value); /* diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 7b9d63b28..01ae5426c 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -198,6 +198,9 @@ namespace MWScript .getCreatureStats(ptr) .getDynamic(mIndex) .getCurrent(); + // GetMagicka shouldn't return negative values + if(mIndex == 1 && value < 0) + value = 0; } runtime.push (value); } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 31caa28ef..8590cd936 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -723,13 +723,19 @@ namespace MWWorld { if (!cell) cell = mWorldScene->getCurrentCell(); + return getCellName(cell->getCell()); + } - if (!cell->getCell()->isExterior() || !cell->getCell()->mName.empty()) - return cell->getCell()->mName; - - if (const ESM::Region* region = mStore.get().search (cell->getCell()->mRegion)) - return region->mName; + std::string World::getCellName(const ESM::Cell* cell) const + { + if (cell) + { + if (!cell->isExterior() || !cell->mName.empty()) + return cell->mName; + if (const ESM::Region* region = mStore.get().search (cell->mRegion)) + return region->mName; + } return mStore.get().find ("sDefaultCellname")->mValue.getString(); } @@ -3461,7 +3467,7 @@ namespace MWWorld // Check mana bool godmode = (isPlayer && mGodMode); MWMechanics::DynamicStat magicka = stats.getMagicka(); - if (magicka.getCurrent() < spell->mData.mCost && !godmode) + if (spell->mData.mCost > 0 && magicka.getCurrent() < spell->mData.mCost && !godmode) { message = "#{sMagicInsufficientSP}"; fail = true; diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 158b0c737..894bba733 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -315,6 +315,7 @@ namespace MWWorld /// /// \note If cell==0, the cell the player is currently in will be used instead to /// generate a name. + std::string getCellName(const ESM::Cell* cell) const override; void removeRefScript (MWWorld::RefData *ref) override; //< Remove the script attached to ref from mLocalScripts diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 5658a5eef..a3bb0c6f8 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -15,6 +15,7 @@ if (GTEST_FOUND AND GMOCK_FOUND) esm/test_fixed_string.cpp misc/test_stringops.cpp + misc/test_endianness.cpp nifloader/testbulletnifloader.cpp diff --git a/apps/openmw_test_suite/misc/test_endianness.cpp b/apps/openmw_test_suite/misc/test_endianness.cpp new file mode 100644 index 000000000..240e4a49a --- /dev/null +++ b/apps/openmw_test_suite/misc/test_endianness.cpp @@ -0,0 +1,122 @@ +#include +#include "components/misc/endianness.hpp" + +struct EndiannessTest : public ::testing::Test {}; + +TEST_F(EndiannessTest, test_swap_endianness_inplace1) +{ + uint8_t zero=0x00; + uint8_t ff=0xFF; + uint8_t fortytwo=0x42; + uint8_t half=128; + + Misc::swapEndiannessInplace(zero); + EXPECT_EQ(zero, 0x00); + + Misc::swapEndiannessInplace(ff); + EXPECT_EQ(ff, 0xFF); + + Misc::swapEndiannessInplace(fortytwo); + EXPECT_EQ(fortytwo, 0x42); + + Misc::swapEndiannessInplace(half); + EXPECT_EQ(half, 128); +} + +TEST_F(EndiannessTest, test_swap_endianness_inplace2) +{ + uint16_t zero = 0x0000; + uint16_t ffff = 0xFFFF; + uint16_t n12 = 0x0102; + uint16_t fortytwo = 0x0042; + + Misc::swapEndiannessInplace(zero); + EXPECT_EQ(zero, 0x0000); + Misc::swapEndiannessInplace(zero); + EXPECT_EQ(zero, 0x0000); + + Misc::swapEndiannessInplace(ffff); + EXPECT_EQ(ffff, 0xFFFF); + Misc::swapEndiannessInplace(ffff); + EXPECT_EQ(ffff, 0xFFFF); + + Misc::swapEndiannessInplace(n12); + EXPECT_EQ(n12, 0x0201); + Misc::swapEndiannessInplace(n12); + EXPECT_EQ(n12, 0x0102); + + Misc::swapEndiannessInplace(fortytwo); + EXPECT_EQ(fortytwo, 0x4200); + Misc::swapEndiannessInplace(fortytwo); + EXPECT_EQ(fortytwo, 0x0042); +} + +TEST_F(EndiannessTest, test_swap_endianness_inplace4) +{ + uint32_t zero = 0x00000000; + uint32_t n1234 = 0x01020304; + uint32_t ffff = 0xFFFFFFFF; + + Misc::swapEndiannessInplace(zero); + EXPECT_EQ(zero, 0x00000000); + Misc::swapEndiannessInplace(zero); + EXPECT_EQ(zero, 0x00000000); + + Misc::swapEndiannessInplace(n1234); + EXPECT_EQ(n1234, 0x04030201); + Misc::swapEndiannessInplace(n1234); + EXPECT_EQ(n1234, 0x01020304); + + Misc::swapEndiannessInplace(ffff); + EXPECT_EQ(ffff, 0xFFFFFFFF); + Misc::swapEndiannessInplace(ffff); + EXPECT_EQ(ffff, 0xFFFFFFFF); +} + +TEST_F(EndiannessTest, test_swap_endianness_inplace8) +{ + uint64_t zero = 0x0000'0000'0000'0000; + uint64_t n1234 = 0x0102'0304'0506'0708; + uint64_t ffff = 0xFFFF'FFFF'FFFF'FFFF; + + Misc::swapEndiannessInplace(zero); + EXPECT_EQ(zero, 0x0000'0000'0000'0000); + Misc::swapEndiannessInplace(zero); + EXPECT_EQ(zero, 0x0000'0000'0000'0000); + + Misc::swapEndiannessInplace(ffff); + EXPECT_EQ(ffff, 0xFFFF'FFFF'FFFF'FFFF); + Misc::swapEndiannessInplace(ffff); + EXPECT_EQ(ffff, 0xFFFF'FFFF'FFFF'FFFF); + + Misc::swapEndiannessInplace(n1234); + EXPECT_EQ(n1234, 0x0807'0605'0403'0201); + Misc::swapEndiannessInplace(n1234); + EXPECT_EQ(n1234, 0x0102'0304'0506'0708); +} + +TEST_F(EndiannessTest, test_swap_endianness_inplace_float) +{ + const uint32_t original = 0x4023d70a; + const uint32_t expected = 0x0ad72340; + + float number; + memcpy(&number, &original, sizeof(original)); + + Misc::swapEndiannessInplace(number); + + EXPECT_TRUE(!memcmp(&number, &expected, sizeof(expected))); +} + +TEST_F(EndiannessTest, test_swap_endianness_inplace_double) +{ + const uint64_t original = 0x040047ae147ae147ul; + const uint64_t expected = 0x47e17a14ae470004ul; + + double number; + memcpy(&number, &original, sizeof(original)); + + Misc::swapEndiannessInplace(number); + + EXPECT_TRUE(!memcmp(&number, &expected, sizeof(expected)) ); +} diff --git a/apps/wizard/existinginstallationpage.cpp b/apps/wizard/existinginstallationpage.cpp index 2434f42cf..886fcd95d 100644 --- a/apps/wizard/existinginstallationpage.cpp +++ b/apps/wizard/existinginstallationpage.cpp @@ -95,9 +95,9 @@ void Wizard::ExistingInstallationPage::on_browseButton_clicked() { QString selectedFile = QFileDialog::getOpenFileName( this, - tr("Select master file"), + tr("Select Morrowind.esm (located in Data Files)"), QDir::currentPath(), - QString(tr("Morrowind master file (*.esm)")), + QString(tr("Morrowind master file (Morrowind.esm)")), nullptr, QFileDialog::DontResolveSymlinks); @@ -110,7 +110,18 @@ void Wizard::ExistingInstallationPage::on_browseButton_clicked() return; if (!mWizard->findFiles(QLatin1String("Morrowind"), info.absolutePath())) - return; // No valid Morrowind installation found + { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error detecting Morrowind files")); + msgBox.setIcon(QMessageBox::Warning); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(QObject::tr( + "Morrowind.bsa is missing!
\ + Make sure your Morrowind installation is complete." + )); + msgBox.exec(); + return; + } QString path(QDir::toNativeSeparators(info.absolutePath())); QList items = installationsList->findItems(path, Qt::MatchExactly); diff --git a/components/misc/endianness.hpp b/components/misc/endianness.hpp index 1b43e584e..ad8aba18c 100644 --- a/components/misc/endianness.hpp +++ b/components/misc/endianness.hpp @@ -2,6 +2,8 @@ #define COMPONENTS_MISC_ENDIANNESS_H #include +#include +#include namespace Misc { @@ -15,20 +17,26 @@ namespace Misc if constexpr (sizeof(T) == 2) { - uint16_t& v16 = *reinterpret_cast(&v); + uint16_t v16; + std::memcpy(&v16, &v, sizeof(T)); v16 = (v16 >> 8) | (v16 << 8); + std::memcpy(&v, &v16, sizeof(T)); } if constexpr (sizeof(T) == 4) { - uint32_t& v32 = *reinterpret_cast(&v); - v32 = (v32 >> 24) | ((v32 >> 8) & 0xff00) | ((v32 & 0xff00) << 8) || v32 << 24; + uint32_t v32; + std::memcpy(&v32, &v, sizeof(T)); + v32 = (v32 >> 24) | ((v32 >> 8) & 0xff00) | ((v32 & 0xff00) << 8) | (v32 << 24); + std::memcpy(&v, &v32, sizeof(T)); } if constexpr (sizeof(T) == 8) { - uint64_t& v64 = *reinterpret_cast(&v); + uint64_t v64; + std::memcpy(&v64, &v, sizeof(T)); v64 = (v64 >> 56) | ((v64 & 0x00ff'0000'0000'0000) >> 40) | ((v64 & 0x0000'ff00'0000'0000) >> 24) | ((v64 & 0x0000'00ff'0000'0000) >> 8) | ((v64 & 0x0000'0000'ff00'0000) << 8) | ((v64 & 0x0000'0000'00ff'0000) << 24) | ((v64 & 0x0000'0000'0000'ff00) << 40) | (v64 << 56); + std::memcpy(&v, &v64, sizeof(T)); } } diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index bcadf51c4..ad37eda0d 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -8,6 +8,7 @@ #include +#include #include #include @@ -86,7 +87,17 @@ public: return osg::ref_ptr(); osg::ref_ptr shape (new BulletShape); - shape->mCollisionShape = new TriangleMeshShape(mTriangleMesh.release(), true); + btBvhTriangleMeshShape* triangleMeshShape = new TriangleMeshShape(mTriangleMesh.release(), true); + btVector3 aabbMin = triangleMeshShape->getLocalAabbMin(); + btVector3 aabbMax = triangleMeshShape->getLocalAabbMax(); + shape->mCollisionBox.extents[0] = (aabbMax[0] - aabbMin[0]) / 2.0f; + shape->mCollisionBox.extents[1] = (aabbMax[1] - aabbMin[1]) / 2.0f; + shape->mCollisionBox.extents[2] = (aabbMax[2] - aabbMin[2]) / 2.0f; + shape->mCollisionBox.center = osg::Vec3f( (aabbMax[0] + aabbMin[0]) / 2.0f, + (aabbMax[1] + aabbMin[1]) / 2.0f, + (aabbMax[2] + aabbMin[2]) / 2.0f ); + shape->mCollisionShape = triangleMeshShape; + return shape; } @@ -135,11 +146,31 @@ osg::ref_ptr BulletShapeManager::getShape(const std::string & osg::ref_ptr constNode (mSceneManager->getTemplate(normalized)); osg::ref_ptr node (const_cast(constNode.get())); // const-trickery required because there is no const version of NodeVisitor - NodeToShapeVisitor visitor; - node->accept(visitor); - shape = visitor.getShape(); + + // Check first if there's a custom collision node + unsigned int visitAllNodesMask = 0xffffffff; + SceneUtil::FindByNameVisitor nameFinder("Collision"); + nameFinder.setTraversalMask(visitAllNodesMask); + nameFinder.setNodeMaskOverride(visitAllNodesMask); + node->accept(nameFinder); + if (nameFinder.mFoundNode) + { + NodeToShapeVisitor visitor; + visitor.setTraversalMask(visitAllNodesMask); + visitor.setNodeMaskOverride(visitAllNodesMask); + nameFinder.mFoundNode->accept(visitor); + shape = visitor.getShape(); + } + + // Generate a collision shape from the mesh if (!shape) - return osg::ref_ptr(); + { + NodeToShapeVisitor visitor; + node->accept(visitor); + shape = visitor.getShape(); + if (!shape) + return osg::ref_ptr(); + } } mCache->addEntryToObjectCache(normalized, shape); diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index d739392e8..f4ab79519 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "animation.hpp" #include "objectcache.hpp" @@ -17,11 +18,13 @@ namespace Resource { - RetrieveAnimationsVisitor::RetrieveAnimationsVisitor(SceneUtil::KeyframeHolder& target, osg::ref_ptr animationManager) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN), mTarget(target), mAnimationManager(animationManager) {} + RetrieveAnimationsVisitor::RetrieveAnimationsVisitor(SceneUtil::KeyframeHolder& target, osg::ref_ptr animationManager, + const std::string& normalized, const VFS::Manager* vfs) : + osg::NodeVisitor(TRAVERSE_ALL_CHILDREN), mTarget(target), mAnimationManager(animationManager), mNormalized(normalized), mVFS(vfs) {} void RetrieveAnimationsVisitor::apply(osg::Node& node) { - if (node.libraryName() == std::string("osgAnimation") && node.className() == std::string("Bone") && node.getName() == std::string("bip01")) + if (node.libraryName() == std::string("osgAnimation") && node.className() == std::string("Bone") && Misc::StringUtils::lowerCase(node.getName()) == std::string("bip01")) { osg::ref_ptr callback = new SceneUtil::OsgAnimationController(); @@ -38,27 +41,19 @@ namespace Resource osg::ref_ptr mergedAnimationTrack = new Resource::Animation; std::string animationName = animation->getName(); - std::string start = animationName + std::string(": start"); - std::string stop = animationName + std::string(": stop"); + mergedAnimationTrack->setName(animationName); const osgAnimation::ChannelList& channels = animation->getChannels(); for (const auto& channel: channels) { mergedAnimationTrack->addChannel(channel.get()->clone()); // is ->clone needed? } - mergedAnimationTrack->setName(animation->getName()); + callback->addMergedAnimationTrack(mergedAnimationTrack); float startTime = animation->getStartTime(); float stopTime = startTime + animation->getDuration(); - // mTextKeys is a nif-thing, used by OpenMW's animation system - // Format is likely "AnimationName: [Keyword_optional] [Start OR Stop]" - // AnimationNames are keywords like idle2, idle3... AiPackages and various mechanics control which animations are played - // Keywords can be stuff like Loop, Equip, Unequip, Block, InventoryHandtoHand, InventoryWeaponOneHand, PickProbe, Slash, Thrust, Chop... even "Slash Small Follow" - mTarget.mTextKeys.emplace(startTime, std::move(start)); - mTarget.mTextKeys.emplace(stopTime, std::move(stop)); - SceneUtil::EmulatedAnimation emulatedAnimation; emulatedAnimation.mStartTime = startTime; emulatedAnimation.mStopTime = stopTime; @@ -66,12 +61,61 @@ namespace Resource emulatedAnimations.emplace_back(emulatedAnimation); } } + + // mTextKeys is a nif-thing, used by OpenMW's animation system + // Format is likely "AnimationName: [Keyword_optional] [Start OR Stop]" + // AnimationNames are keywords like idle2, idle3... AiPackages and various mechanics control which animations are played + // Keywords can be stuff like Loop, Equip, Unequip, Block, InventoryHandtoHand, InventoryWeaponOneHand, PickProbe, Slash, Thrust, Chop... even "Slash Small Follow" + // osgAnimation formats should have a .txt file with the same name, each line holding a textkey and whitespace separated time value + // e.g. idle: start 0.0333 + try + { + Files::IStreamPtr textKeysFile = mVFS->get(changeFileExtension(mNormalized, "txt")); + std::string line; + while ( getline (*textKeysFile, line) ) + { + mTarget.mTextKeys.emplace(parseTimeSignature(line), parseTextKey(line)); + } + } + catch (std::exception& e) + { + Log(Debug::Warning) << "No textkey file found for " << mNormalized; + } + callback->setEmulatedAnimations(emulatedAnimations); mTarget.mKeyframeControllers.emplace(node.getName(), callback); } traverse(node); } + + std::string RetrieveAnimationsVisitor::parseTextKey(const std::string& line) + { + size_t spacePos = line.find_last_of(' '); + if (spacePos != std::string::npos) + return line.substr(0, spacePos); + return ""; + } + + double RetrieveAnimationsVisitor::parseTimeSignature(const std::string& line) + { + size_t spacePos = line.find_last_of(' '); + double time = 0.0; + if (spacePos != std::string::npos && spacePos + 1 < line.size()) + time = std::stod(line.substr(spacePos + 1)); + return time; + } + + std::string RetrieveAnimationsVisitor::changeFileExtension(const std::string file, const std::string ext) + { + size_t extPos = file.find_last_of('.'); + if (extPos != std::string::npos && extPos+1 < file.size()) + { + return file.substr(0, extPos + 1) + ext; + } + return file; + } + } namespace Resource @@ -109,7 +153,7 @@ namespace Resource osg::ref_ptr bam = dynamic_cast (scene->getUpdateCallback()); if (bam) { - Resource::RetrieveAnimationsVisitor rav(*loaded.get(), bam); + Resource::RetrieveAnimationsVisitor rav(*loaded.get(), bam, normalized, mVFS); scene->accept(rav); } } diff --git a/components/resource/keyframemanager.hpp b/components/resource/keyframemanager.hpp index 3e992ac5e..87a20b97a 100644 --- a/components/resource/keyframemanager.hpp +++ b/components/resource/keyframemanager.hpp @@ -15,13 +15,21 @@ namespace Resource class RetrieveAnimationsVisitor : public osg::NodeVisitor { public: - RetrieveAnimationsVisitor(SceneUtil::KeyframeHolder& target, osg::ref_ptr animationManager); + RetrieveAnimationsVisitor(SceneUtil::KeyframeHolder& target, osg::ref_ptr animationManager, + const std::string& normalized, const VFS::Manager* vfs); virtual void apply(osg::Node& node) override; private: + + std::string changeFileExtension(const std::string file, const std::string ext); + std::string parseTextKey(const std::string& line); + double parseTimeSignature(const std::string& line); + SceneUtil::KeyframeHolder& mTarget; osg::ref_ptr mAnimationManager; + std::string mNormalized; + const VFS::Manager* mVFS; }; } diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 71f11e382..19cc96433 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -373,6 +374,14 @@ namespace Resource errormsg << "Error loading " << normalizedFilename << ": " << result.message() << " code " << result.status() << std::endl; throw std::runtime_error(errormsg.str()); } + + // Recognize and hide collision node + unsigned int hiddenNodeMask = 0; + SceneUtil::FindByNameVisitor nameFinder("Collision"); + result.getNode()->accept(nameFinder); + if (nameFinder.mFoundNode) + nameFinder.mFoundNode->setNodeMask(hiddenNodeMask); + return result.getNode(); } } @@ -390,7 +399,8 @@ namespace Resource { const char* reserved[] = {"Head", "Neck", "Chest", "Groin", "Right Hand", "Left Hand", "Right Wrist", "Left Wrist", "Shield Bone", "Right Forearm", "Left Forearm", "Right Upper Arm", "Left Upper Arm", "Right Foot", "Left Foot", "Right Ankle", "Left Ankle", "Right Knee", "Left Knee", "Right Upper Leg", "Left Upper Leg", "Right Clavicle", - "Left Clavicle", "Weapon Bone", "Tail", "Bip01", "Root Bone", "BoneOffset", "AttachLight", "Arrow", "Camera"}; + "Left Clavicle", "Weapon Bone", "Tail", "Bip01", "Root Bone", "BoneOffset", "AttachLight", "Arrow", "Camera", "Collision", "Right_Wrist", "Left_Wrist", + "Shield_Bone", "Right_Forearm", "Left_Forearm", "Right_Upper_Arm", "Left_Clavicle", "Weapon_Bone", "Root_Bone"}; reservedNames = std::vector(reserved, reserved + sizeof(reserved)/sizeof(reserved[0])); diff --git a/components/sceneutil/actorutil.cpp b/components/sceneutil/actorutil.cpp index 988a61f60..a0785e413 100644 --- a/components/sceneutil/actorutil.cpp +++ b/components/sceneutil/actorutil.cpp @@ -1,5 +1,7 @@ #include "actorutil.hpp" +#include + namespace SceneUtil { std::string getActorSkeleton(bool firstPerson, bool isFemale, bool isBeast, bool isWerewolf) @@ -7,24 +9,24 @@ namespace SceneUtil if (!firstPerson) { if (isWerewolf) - return "meshes\\wolf\\skin.nif"; + return Settings::Manager::getString("wolfskin", "Models"); else if (isBeast) - return "meshes\\base_animkna.nif"; + return Settings::Manager::getString("baseanimkna", "Models"); else if (isFemale) - return "meshes\\base_anim_female.nif"; + return Settings::Manager::getString("baseanimfemale", "Models"); else - return "meshes\\base_anim.nif"; + return Settings::Manager::getString("baseanim", "Models"); } else { if (isWerewolf) - return "meshes\\wolf\\skin.1st.nif"; + return Settings::Manager::getString("wolfskin1st", "Models"); else if (isBeast) - return "meshes\\base_animkna.1st.nif"; + return Settings::Manager::getString("baseanimkna1st", "Models"); else if (isFemale) - return "meshes\\base_anim_female.1st.nif"; + return Settings::Manager::getString("baseanimfemale1st", "Models"); else - return "meshes\\base_anim.1st.nif"; + return Settings::Manager::getString("xbaseanim1st", "Models"); } } } diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index 294780cfd..ba903f6db 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -997,9 +997,9 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) } // 1. Traverse main scene graph - cv.pushStateSet( _shadowRecievingPlaceholderStateSet.get() ); - - osg::ref_ptr decoratorStateGraph = cv.getCurrentStateGraph(); + auto* shadowReceiverStateSet = vdd->getStateSet(cv.getTraversalNumber()); + shadowReceiverStateSet->clear(); + cv.pushStateSet(shadowReceiverStateSet); cullShadowReceivingScene(&cv); @@ -1426,7 +1426,7 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) if (numValidShadows>0) { - decoratorStateGraph->setStateSet(selectStateSetForRenderingShadow(*vdd, cv.getTraversalNumber())); + prepareStateSetForRenderingShadow(*vdd, cv.getTraversalNumber()); } // OSG_NOTICE<<"End of shadow setup Projection matrix "<<*cv.getProjectionMatrix()< stateset = vdd.getStateSet(traversalNumber); diff --git a/components/sceneutil/mwshadowtechnique.hpp b/components/sceneutil/mwshadowtechnique.hpp index 5125247dd..7b934b798 100644 --- a/components/sceneutil/mwshadowtechnique.hpp +++ b/components/sceneutil/mwshadowtechnique.hpp @@ -231,7 +231,7 @@ namespace SceneUtil { virtual void cullShadowCastingScene(osgUtil::CullVisitor* cv, osg::Camera* camera) const; - virtual osg::StateSet* selectStateSetForRenderingShadow(ViewDependentData& vdd, unsigned int traversalNumber) const; + virtual osg::StateSet* prepareStateSetForRenderingShadow(ViewDependentData& vdd, unsigned int traversalNumber) const; protected: virtual ~MWShadowTechnique(); diff --git a/components/sceneutil/osgacontroller.cpp b/components/sceneutil/osgacontroller.cpp index 37aa525af..b2d819117 100644 --- a/components/sceneutil/osgacontroller.cpp +++ b/components/sceneutil/osgacontroller.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -83,7 +84,7 @@ namespace SceneUtil { osgAnimation::UpdateMatrixTransform* umt = dynamic_cast(cb); if (umt) - if (node.getName() != "bip01") link(umt); + if (Misc::StringUtils::lowerCase(node.getName()) != "bip01") link(umt); cb = cb->getNestedCallback(); } @@ -102,10 +103,14 @@ namespace SceneUtil } OsgAnimationController::OsgAnimationController(const OsgAnimationController ©, const osg::CopyOp ©op) : SceneUtil::KeyframeController(copy, copyop) - , mMergedAnimationTracks(copy.mMergedAnimationTracks) , mEmulatedAnimations(copy.mEmulatedAnimations) { mLinker = nullptr; + for (const auto& mergedAnimationTrack : copy.mMergedAnimationTracks) + { + Resource::Animation* copiedAnimationTrack = static_cast(mergedAnimationTrack.get()->clone(copyop)); + mMergedAnimationTracks.emplace_back(copiedAnimationTrack); + } } osg::Vec3f OsgAnimationController::getTranslation(float time) const diff --git a/components/sceneutil/visitor.cpp b/components/sceneutil/visitor.cpp index 00d18ffcb..60f99b2fe 100644 --- a/components/sceneutil/visitor.cpp +++ b/components/sceneutil/visitor.cpp @@ -60,7 +60,20 @@ namespace SceneUtil void NodeMapVisitor::apply(osg::MatrixTransform& trans) { // Take transformation for first found node in file - const std::string nodeName = Misc::StringUtils::lowerCase(trans.getName()); + std::string originalNodeName = Misc::StringUtils::lowerCase(trans.getName()); + + if (trans.libraryName() == std::string("osgAnimation")) + { + // Convert underscores to whitespaces as a workaround for Collada (OpenMW's animation system uses whitespace-separated names) + std::string underscore = "_"; + std::size_t foundUnderscore = originalNodeName.find(underscore); + + if (foundUnderscore != std::string::npos) + std::replace(originalNodeName.begin(), originalNodeName.end(), '_', ' '); + } + + const std::string nodeName = originalNodeName; + mMap.emplace(nodeName, &trans); traverse(trans); diff --git a/docs/source/reference/modding/settings/input.rst b/docs/source/reference/modding/settings/input.rst index 8a95686cf..d04c267a7 100644 --- a/docs/source/reference/modding/settings/input.rst +++ b/docs/source/reference/modding/settings/input.rst @@ -38,7 +38,7 @@ This setting causes the behavior of the sneak key (bound to Ctrl by default) to toggle sneaking on and off rather than requiring the key to be held down while sneaking. Players that spend significant time sneaking may find the character easier to control with this option enabled. -This setting can only be configured by editing the settings configuration file. +This setting can be toggled in the launcher under "Advanced" -> "Game Mechanics" -> "Toggle sneak". always run ---------- diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 3660f56f0..67d944d19 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -955,6 +955,51 @@ defer aabb update = true # Loading arbitrary meshes is not advised and may cause instability. load unsupported nif files = false +# 3rd person base animation model that looks also for the corresponding kf-file +xbaseanim = meshes/xbase_anim.nif + +# 3rd person base model with textkeys-data +baseanim = meshes/base_anim.nif + +# 1st person base animation model that looks also for corresponding kf-file +xbaseanim1st = meshes/xbase_anim.1st.nif + +# 3rd person beast race base model with textkeys-data +baseanimkna = meshes/base_animkna.nif + +# 1st person beast race base animation model +baseanimkna1st = meshes/base_animkna.1st.nif + +# 3rd person female base animation model +xbaseanimfemale = meshes/xbase_anim_female.nif + +# 3rd person female base model with textkeys-data +baseanimfemale = meshes/base_anim_female.nif + +# 1st person female base model with textkeys-data +baseanimfemale1st = meshes/base_anim_female.1st.nif + +# 3rd person werewolf skin +wolfskin = meshes/wolf/skin.nif + +# 1st person werewolf skin +wolfskin1st = meshes/wolf/skin.1st.nif + +# Argonian smimkna +xargonianswimkna = meshes/xargonian_swimkna.nif + +# File to load xbaseanim 3rd person animations +xbaseanimkf = meshes/xbase_anim.kf + +# File to load xbaseanim 3rd person animations +xbaseanim1stkf = meshes/xbase_anim.1st.kf + +# File to load xbaseanim animations from +xbaseanimfemalekf = meshes/xbase_anim_female.kf + +# File to load xargonianswimkna animations from +xargonianswimknakf = meshes/xargonian_swimkna.kf + [Groundcover] # enable separate groundcover handling diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl index 5eae89029..a2dcc758a 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -13,22 +13,13 @@ void perLight(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec3 vie #ifndef GROUNDCOVER lambert = max(lambert, 0.0); #else + float eyeCosine = dot(normalize(viewPos), viewNormal.xyz); + if (lambert < 0.0) { - float cosine = dot(normalize(viewPos), normalize(viewNormal.xyz)); - if (lambert >= 0.0) - cosine = -cosine; - - float mult = 1.0; - float divisor = 8.0; - - if (cosine < 0.0 && cosine >= -1.0/divisor) - mult = mix(1.0, 0.3, -cosine*divisor); - else if (cosine < -1.0/divisor) - mult = 0.3; - - lambert *= mult; - lambert = abs(lambert); + lambert = -lambert; + eyeCosine = -eyeCosine; } + lambert *= clamp(-8.0 * (1.0 - 0.3) * eyeCosine + 1.0, 0.3, 1.0); #endif diffuseOut = gl_LightSource[lightIndex].diffuse.xyz * lambert; }