Add OpenMW commits up to 14 Feb 2021

# Conflicts:
#   apps/openmw/mwclass/door.cpp
#   apps/openmw/mwscript/aiextensions.cpp
pull/593/head
David Cernat 4 years ago
commit 7e188f2dd6

@ -116,12 +116,13 @@ variables: &cs-targets
- .\ActivateMSVC.ps1 - .\ActivateMSVC.ps1
- cmake --build . --config $config --target ($targets.Split(',')) - cmake --build . --config $config --target ($targets.Split(','))
- cd $config - 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) { 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 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: after_script:
- Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log
cache: cache:
@ -206,12 +207,13 @@ Windows_Ninja_CS_RelWithDebInfo:
- cd MSVC2019_64 - cd MSVC2019_64
- cmake --build . --config $config --target ($targets.Split(',')) - cmake --build . --config $config --target ($targets.Split(','))
- cd $config - 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) { 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 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: after_script:
- Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log
cache: cache:

@ -215,6 +215,7 @@ Programmers
Yohaulticetl Yohaulticetl
Yuri Krupenin Yuri Krupenin
zelurker zelurker
Noah Gooder
Documentation Documentation
------------- -------------

@ -98,6 +98,11 @@
Bug #5758: Paralyzed actors behavior is inconsistent with vanilla Bug #5758: Paralyzed actors behavior is inconsistent with vanilla
Bug #5762: Movement solver is insufficiently robust Bug #5762: Movement solver is insufficiently robust
Bug #5821: NPCs from mods getting removed if mod order was changed 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 #390: 3rd person look "over the shoulder"
Feature #1536: Show more information about level on menu Feature #1536: Show more information about level on menu
Feature #2386: Distant Statics in the form of Object Paging Feature #2386: Distant Statics in the form of Object Paging
@ -105,6 +110,7 @@
Feature #2686: Timestamps in openmw.log Feature #2686: Timestamps in openmw.log
Feature #3171: OpenMW-CS: Instance drag selection Feature #3171: OpenMW-CS: Instance drag selection
Feature #4894: Consider actors as obstacles for pathfinding 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 #5043: Head Bobbing
Feature #5199: Improve Scene Colors Feature #5199: Improve Scene Colors
Feature #5297: Add a search function to the "Datafiles" tab of the OpenMW launcher Feature #5297: Add a search function to the "Datafiles" tab of the OpenMW launcher
@ -130,6 +136,7 @@
Feature #5813: Instanced groundcover support Feature #5813: Instanced groundcover support
Task #5480: Drop Qt4 support Task #5480: Drop Qt4 support
Task #5520: Improve cell name autocompleter implementation Task #5520: Improve cell name autocompleter implementation
Task #5844: Update 'toggle sneak' documentation
0.46.0 0.46.0
------ ------

@ -218,6 +218,7 @@ namespace MWBase
/// ///
/// \note If cell==0, the cell the player is currently in will be used instead to /// \note If cell==0, the cell the player is currently in will be used instead to
/// generate a name. /// generate a name.
virtual std::string getCellName(const ESM::Cell* cell) const = 0;
virtual void removeRefScript (MWWorld::RefData *ref) = 0; virtual void removeRefScript (MWWorld::RefData *ref) = 0;
//< Remove the script attached to ref from mLocalScripts //< Remove the script attached to ref from mLocalScripts

@ -379,45 +379,31 @@ namespace MWClass
return info; return info;
} }
std::string Door::getDestination (const MWWorld::LiveCellRef<ESM::Door>& door) std::string Door::getDestination(const MWWorld::LiveCellRef<ESM::Door>& door)
{ {
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
std::string dest; std::string dest = door.mRef.getDestCell();
if (door.mRef.getDestCell() != "") if (dest.empty())
{
// 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
{ {
// door leads to exterior, use cell name (if any), otherwise translated region name // door leads to exterior, use cell name (if any), otherwise translated region name
int x,y; int x, y;
MWBase::Environment::get().getWorld()->positionToIndex (door.mRef.getDoorDest().pos[0], door.mRef.getDoorDest().pos[1], x, y); auto world = MWBase::Environment::get().getWorld();
const ESM::Cell* cell = store.get<ESM::Cell>().find(x,y); world->positionToIndex(door.mRef.getDoorDest().pos[0], door.mRef.getDoorDest().pos[1], x, y);
if (cell->mName != "") const ESM::Cell* cell = world->getStore().get<ESM::Cell>().search(x, y);
dest = cell->mName; dest = world->getCellName(cell);
else
{
const ESM::Region* region =
store.get<ESM::Region>().find(cell->mRegion);
//name as is, not a token
return MyGUI::TextIterator::toTagsString(region->mName);
}
} }
/*
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 + "}"; return "#{sCell=" + dest + "}";
} }

@ -435,10 +435,10 @@ namespace MWClass
{ {
const MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>(); const MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
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<ESM::Race>().find(ref->mBase->mRace); const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(ref->mBase->mRace);
if(race->mData.mFlags & ESM::Race::Beast) if(race->mData.mFlags & ESM::Race::Beast)
model = "meshes\\base_animkna.nif"; model = Settings::Manager::getString("baseanimkna", "Models");
return model; return model;
} }
@ -448,12 +448,12 @@ namespace MWClass
const MWWorld::LiveCellRef<ESM::NPC> *npc = ptr.get<ESM::NPC>(); const MWWorld::LiveCellRef<ESM::NPC> *npc = ptr.get<ESM::NPC>();
const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().search(npc->mBase->mRace); const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().search(npc->mBase->mRace);
if(race && race->mData.mFlags & ESM::Race::Beast) 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 // keep these always loaded just in case
models.emplace_back("meshes/xargonian_swimkna.nif"); models.emplace_back(Settings::Manager::getString("xargonianswimkna", "Models"));
models.emplace_back("meshes/xbase_anim_female.nif"); models.emplace_back(Settings::Manager::getString("xbaseanimfemale", "Models"));
models.emplace_back("meshes/xbase_anim.nif"); models.emplace_back(Settings::Manager::getString("xbaseanim", "Models"));
if (!npc->mBase->mModel.empty()) if (!npc->mBase->mModel.empty())
models.push_back("meshes/"+npc->mBase->mModel); models.push_back("meshes/"+npc->mBase->mModel);

@ -316,7 +316,7 @@ int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) con
case SelectWrapper::Function_AiSetting: case SelectWrapper::Function_AiSetting:
return mActor.getClass().getCreatureStats (mActor).getAiSetting ( return mActor.getClass().getCreatureStats (mActor).getAiSetting (
(MWMechanics::CreatureStats::AiSetting)select.getArgument()).getModified(); (MWMechanics::CreatureStats::AiSetting)select.getArgument()).getModified(false);
case SelectWrapper::Function_PcAttribute: case SelectWrapper::Function_PcAttribute:

@ -6,6 +6,9 @@
#include <MyGUI_TextBox.h> #include <MyGUI_TextBox.h>
// correctIconPath // correctIconPath
#include <components/resource/resourcesystem.hpp>
#include <components/vfs/manager.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
@ -106,7 +109,10 @@ namespace MWGui
std::string invIcon = ptr.getClass().getInventoryIcon(ptr); std::string invIcon = ptr.getClass().getInventoryIcon(ptr);
if (invIcon.empty()) if (invIcon.empty())
invIcon = "default icon.tga"; 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);
} }

@ -996,29 +996,29 @@ namespace MWMechanics
if (actor.getClass().hasInventoryStore(actor)) if (actor.getClass().hasInventoryStore(actor))
actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Poison); 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.getActiveSpells().purgeEffect(ESM::MagicEffect::Paralyze);
creatureStats.getSpells().purgeEffect(ESM::MagicEffect::Paralyze); creatureStats.getSpells().purgeEffect(ESM::MagicEffect::Paralyze);
if (actor.getClass().hasInventoryStore(actor)) if (actor.getClass().hasInventoryStore(actor))
actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Paralyze); 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(); creatureStats.getSpells().purgeCommonDisease();
} }
else if (effects.get(ESM::MagicEffect::CureBlightDisease).getModifier() > 0) if (effects.get(ESM::MagicEffect::CureBlightDisease).getModifier() > 0)
{ {
creatureStats.getSpells().purgeBlightDisease(); creatureStats.getSpells().purgeBlightDisease();
} }
else if (effects.get(ESM::MagicEffect::CureCorprusDisease).getModifier() > 0) if (effects.get(ESM::MagicEffect::CureCorprusDisease).getModifier() > 0)
{ {
creatureStats.getActiveSpells().purgeCorprusDisease(); creatureStats.getActiveSpells().purgeCorprusDisease();
creatureStats.getSpells().purgeCorprusDisease(); creatureStats.getSpells().purgeCorprusDisease();
if (actor.getClass().hasInventoryStore(actor)) if (actor.getClass().hasInventoryStore(actor))
actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Corprus, true); 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(); creatureStats.getSpells().purgeCurses();
} }

@ -452,6 +452,8 @@ namespace MWMechanics
MWMechanics::DynamicStat<float> health = attackerStats.getHealth(); MWMechanics::DynamicStat<float> health = attackerStats.getHealth();
health.setCurrent(health.getCurrent() - x); health.setCurrent(health.getCurrent() - x);
attackerStats.setHealth(health); attackerStats.setHealth(health);
MWBase::Environment::get().getSoundManager()->playSound3D(attacker, "Health Damage", 1.0f, 1.0f);
} }
} }

@ -123,7 +123,7 @@ namespace MWMechanics
if (spell->mData.mType != ESM::Spell::ST_Spell) if (spell->mData.mType != ESM::Spell::ST_Spell)
return 100; return 100;
if (checkMagicka && stats.getMagicka().getCurrent() < spell->mData.mCost) if (checkMagicka && spell->mData.mCost > 0 && stats.getMagicka().getCurrent() < spell->mData.mCost)
return 0; return 0;
if (spell->mData.mFlags & ESM::Spell::F_Always) if (spell->mData.mFlags & ESM::Spell::F_Always)

@ -18,8 +18,10 @@ namespace MWMechanics
} }
template<typename T> template<typename T>
T Stat<T>::getModified() const T Stat<T>::getModified(bool capped) const
{ {
if(!capped)
return mModified;
return std::max(static_cast<T>(0), mModified); return std::max(static_cast<T>(0), mModified);
} }

@ -28,7 +28,7 @@ namespace MWMechanics
const T& getBase() const; const T& getBase() const;
T getModified() const; T getModified(bool capped = true) const;
T getCurrentModified() const; T getCurrentModified() const;
T getModifier() const; T getModifier() const;
T getCurrentModifier() const; T getCurrentModifier() const;

@ -38,7 +38,7 @@ namespace MWPhysics
ContactCollectionCallback(const btCollisionObject * me, osg::Vec3f velocity) : mMe(me) ContactCollectionCallback(const btCollisionObject * me, osg::Vec3f velocity) : mMe(me)
{ {
m_collisionFilterGroup = me->getBroadphaseHandle()->m_collisionFilterGroup; 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); mVelocity = Misc::Convert::toBullet(velocity);
} }
btScalar addSingleResult(btManifoldPoint & contact, const btCollisionObjectWrapper * colObj0Wrap, int partId0, int index0, const btCollisionObjectWrapper * colObj1Wrap, int partId1, int index1) override btScalar addSingleResult(btManifoldPoint & contact, const btCollisionObjectWrapper * colObj0Wrap, int partId0, int index0, const btCollisionObjectWrapper * colObj1Wrap, int partId1, int index1) override

@ -1500,7 +1500,7 @@ namespace MWRender
MWWorld::LiveCellRef<ESM::Creature> *ref = mPtr.get<ESM::Creature>(); MWWorld::LiveCellRef<ESM::Creature> *ref = mPtr.get<ESM::Creature>();
if(ref->mBase->mFlags & ESM::Creature::Bipedal) if(ref->mBase->mFlags & ESM::Creature::Bipedal)
{ {
defaultSkeleton = "meshes\\xbase_anim.nif"; defaultSkeleton = Settings::Manager::getString("xbaseanim", "Models");
inject = true; inject = true;
} }
} }

@ -184,6 +184,9 @@ namespace MWRender
osg::ref_ptr<osg::Texture2D> dummyTexture = new osg::Texture2D(); osg::ref_ptr<osg::Texture2D> dummyTexture = new osg::Texture2D();
dummyTexture->setInternalFormat(GL_RED); dummyTexture->setInternalFormat(GL_RED);
dummyTexture->setTextureSize(1, 1); 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->setTextureAttributeAndModes(7, dummyTexture, osg::StateAttribute::ON);
stateset->setTextureAttribute(7, noBlendAlphaEnv, osg::StateAttribute::ON); stateset->setTextureAttribute(7, noBlendAlphaEnv, osg::StateAttribute::ON);
stateset->addUniform(new osg::Uniform("noAlpha", true)); stateset->addUniform(new osg::Uniform("noAlpha", true));

@ -10,7 +10,7 @@
#include <components/sceneutil/visitor.hpp> #include <components/sceneutil/visitor.hpp>
#include <components/sceneutil/positionattitudetransform.hpp> #include <components/sceneutil/positionattitudetransform.hpp>
#include <components/sceneutil/skeleton.hpp> #include <components/sceneutil/skeleton.hpp>
#include <components/settings/settings.hpp>
#include <components/misc/stringops.hpp> #include <components/misc/stringops.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
@ -35,7 +35,7 @@ CreatureAnimation::CreatureAnimation(const MWWorld::Ptr &ptr,
setObjectRoot(model, false, false, true); setObjectRoot(model, false, false, true);
if((ref->mBase->mFlags&ESM::Creature::Bipedal)) if((ref->mBase->mFlags&ESM::Creature::Bipedal))
addAnimSource("meshes\\xbase_anim.nif", model); addAnimSource(Settings::Manager::getString("xbaseanim", "Models"), model);
addAnimSource(model, model); addAnimSource(model, model);
} }
} }
@ -54,7 +54,7 @@ CreatureWeaponAnimation::CreatureWeaponAnimation(const MWWorld::Ptr &ptr, const
if((ref->mBase->mFlags&ESM::Creature::Bipedal)) if((ref->mBase->mFlags&ESM::Creature::Bipedal))
{ {
addAnimSource("meshes\\xbase_anim.nif", model); addAnimSource(Settings::Manager::getString("xbaseanim", "Models"), model);
} }
addAnimSource(model, model); addAnimSource(model, model);

@ -524,7 +524,7 @@ void NpcAnimation::updateNpcBase()
if(!is1stPerson) if(!is1stPerson)
{ {
const std::string base = "meshes\\xbase_anim.nif"; const std::string base = Settings::Manager::getString("xbaseanim", "Models");
if (smodel != base && !isWerewolf) if (smodel != base && !isWerewolf)
addAnimSource(base, smodel); addAnimSource(base, smodel);
@ -538,7 +538,7 @@ void NpcAnimation::updateNpcBase()
} }
else else
{ {
const std::string base = "meshes\\xbase_anim.1st.nif"; const std::string base = Settings::Manager::getString("xbaseanim1st", "Models");
if (smodel != base && !isWerewolf) if (smodel != base && !isWerewolf)
addAnimSource(base, smodel); addAnimSource(base, smodel);

@ -456,12 +456,15 @@ namespace MWRender
mSky->listAssetsToPreload(workItem->mModels, workItem->mTextures); mSky->listAssetsToPreload(workItem->mModels, workItem->mTextures);
mWater->listAssetsToPreload(workItem->mTextures); mWater->listAssetsToPreload(workItem->mTextures);
const char* basemodels[] = {"xbase_anim", "xbase_anim.1st", "xbase_anim_female", "xbase_animkna"}; workItem->mModels.push_back(Settings::Manager::getString("xbaseanim", "Models"));
for (size_t i=0; i<sizeof(basemodels)/sizeof(basemodels[0]); ++i) workItem->mModels.push_back(Settings::Manager::getString("xbaseanim1st", "Models"));
{ workItem->mModels.push_back(Settings::Manager::getString("xbaseanimfemale", "Models"));
workItem->mModels.push_back(std::string("meshes/") + basemodels[i] + ".nif"); workItem->mModels.push_back(Settings::Manager::getString("xargonianswimkna", "Models"));
workItem->mKeyframes.push_back(std::string("meshes/") + basemodels[i] + ".kf");
} 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"); workItem->mTextures.emplace_back("textures/_land_default.dds");

@ -255,7 +255,7 @@ namespace MWScript
{ {
MWWorld::Ptr ptr = R()(runtime); 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<class R> template<class R>
@ -290,20 +290,20 @@ namespace MWScript
Interpreter::Type_Integer value = runtime[0].mInteger; Interpreter::Type_Integer value = runtime[0].mInteger;
runtime.pop(); runtime.pop();
MWMechanics::Stat<int> stat = ptr.getClass().getCreatureStats(ptr).getAiSetting(mIndex);
/* /*
Start of tes3mp addition Start of tes3mp addition
Track the original stat value, to ensure we don't send repetitive packets to the server Track the original stat value, to ensure we don't send repetitive packets to the server
about its changes about its changes
*/ */
MWMechanics::Stat<int> stat = ptr.getClass().getCreatureStats(ptr).getAiSetting(mIndex);
int initialValue = stat.getBase(); int initialValue = stat.getBase();
/* /*
End of tes3mp addition End of tes3mp addition
*/ */
stat.setModified(value, 0); ptr.getClass().getCreatureStats(ptr).setAiSetting(mIndex, value);
ptr.getClass().setBaseAISetting(ptr.getCellRef().getRefId(), mIndex, value); ptr.getClass().setBaseAISetting(ptr.getCellRef().getRefId(), mIndex, value);
/* /*

@ -198,6 +198,9 @@ namespace MWScript
.getCreatureStats(ptr) .getCreatureStats(ptr)
.getDynamic(mIndex) .getDynamic(mIndex)
.getCurrent(); .getCurrent();
// GetMagicka shouldn't return negative values
if(mIndex == 1 && value < 0)
value = 0;
} }
runtime.push (value); runtime.push (value);
} }

@ -723,13 +723,19 @@ namespace MWWorld
{ {
if (!cell) if (!cell)
cell = mWorldScene->getCurrentCell(); cell = mWorldScene->getCurrentCell();
return getCellName(cell->getCell());
}
if (!cell->getCell()->isExterior() || !cell->getCell()->mName.empty()) std::string World::getCellName(const ESM::Cell* cell) const
return cell->getCell()->mName; {
if (cell)
if (const ESM::Region* region = mStore.get<ESM::Region>().search (cell->getCell()->mRegion)) {
return region->mName; if (!cell->isExterior() || !cell->mName.empty())
return cell->mName;
if (const ESM::Region* region = mStore.get<ESM::Region>().search (cell->mRegion))
return region->mName;
}
return mStore.get<ESM::GameSetting>().find ("sDefaultCellname")->mValue.getString(); return mStore.get<ESM::GameSetting>().find ("sDefaultCellname")->mValue.getString();
} }
@ -3461,7 +3467,7 @@ namespace MWWorld
// Check mana // Check mana
bool godmode = (isPlayer && mGodMode); bool godmode = (isPlayer && mGodMode);
MWMechanics::DynamicStat<float> magicka = stats.getMagicka(); MWMechanics::DynamicStat<float> magicka = stats.getMagicka();
if (magicka.getCurrent() < spell->mData.mCost && !godmode) if (spell->mData.mCost > 0 && magicka.getCurrent() < spell->mData.mCost && !godmode)
{ {
message = "#{sMagicInsufficientSP}"; message = "#{sMagicInsufficientSP}";
fail = true; fail = true;

@ -315,6 +315,7 @@ namespace MWWorld
/// ///
/// \note If cell==0, the cell the player is currently in will be used instead to /// \note If cell==0, the cell the player is currently in will be used instead to
/// generate a name. /// generate a name.
std::string getCellName(const ESM::Cell* cell) const override;
void removeRefScript (MWWorld::RefData *ref) override; void removeRefScript (MWWorld::RefData *ref) override;
//< Remove the script attached to ref from mLocalScripts //< Remove the script attached to ref from mLocalScripts

@ -15,6 +15,7 @@ if (GTEST_FOUND AND GMOCK_FOUND)
esm/test_fixed_string.cpp esm/test_fixed_string.cpp
misc/test_stringops.cpp misc/test_stringops.cpp
misc/test_endianness.cpp
nifloader/testbulletnifloader.cpp nifloader/testbulletnifloader.cpp

@ -0,0 +1,122 @@
#include <gtest/gtest.h>
#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)) );
}

@ -95,9 +95,9 @@ void Wizard::ExistingInstallationPage::on_browseButton_clicked()
{ {
QString selectedFile = QFileDialog::getOpenFileName( QString selectedFile = QFileDialog::getOpenFileName(
this, this,
tr("Select master file"), tr("Select Morrowind.esm (located in Data Files)"),
QDir::currentPath(), QDir::currentPath(),
QString(tr("Morrowind master file (*.esm)")), QString(tr("Morrowind master file (Morrowind.esm)")),
nullptr, nullptr,
QFileDialog::DontResolveSymlinks); QFileDialog::DontResolveSymlinks);
@ -110,7 +110,18 @@ void Wizard::ExistingInstallationPage::on_browseButton_clicked()
return; return;
if (!mWizard->findFiles(QLatin1String("Morrowind"), info.absolutePath())) 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(
"<b>Morrowind.bsa</b> is missing!<br>\
Make sure your Morrowind installation is complete."
));
msgBox.exec();
return;
}
QString path(QDir::toNativeSeparators(info.absolutePath())); QString path(QDir::toNativeSeparators(info.absolutePath()));
QList<QListWidgetItem*> items = installationsList->findItems(path, Qt::MatchExactly); QList<QListWidgetItem*> items = installationsList->findItems(path, Qt::MatchExactly);

@ -2,6 +2,8 @@
#define COMPONENTS_MISC_ENDIANNESS_H #define COMPONENTS_MISC_ENDIANNESS_H
#include <cstdint> #include <cstdint>
#include <cstring>
#include <type_traits>
namespace Misc namespace Misc
{ {
@ -15,20 +17,26 @@ namespace Misc
if constexpr (sizeof(T) == 2) if constexpr (sizeof(T) == 2)
{ {
uint16_t& v16 = *reinterpret_cast<uint16_t*>(&v); uint16_t v16;
std::memcpy(&v16, &v, sizeof(T));
v16 = (v16 >> 8) | (v16 << 8); v16 = (v16 >> 8) | (v16 << 8);
std::memcpy(&v, &v16, sizeof(T));
} }
if constexpr (sizeof(T) == 4) if constexpr (sizeof(T) == 4)
{ {
uint32_t& v32 = *reinterpret_cast<uint32_t*>(&v); uint32_t v32;
v32 = (v32 >> 24) | ((v32 >> 8) & 0xff00) | ((v32 & 0xff00) << 8) || v32 << 24; 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) if constexpr (sizeof(T) == 8)
{ {
uint64_t& v64 = *reinterpret_cast<uint64_t*>(&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 = (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'00ff'0000'0000) >> 8) | ((v64 & 0x0000'0000'ff00'0000) << 8)
| ((v64 & 0x0000'0000'00ff'0000) << 24) | ((v64 & 0x0000'0000'0000'ff00) << 40) | (v64 << 56); | ((v64 & 0x0000'0000'00ff'0000) << 24) | ((v64 & 0x0000'0000'0000'ff00) << 40) | (v64 << 56);
std::memcpy(&v, &v64, sizeof(T));
} }
} }

@ -8,6 +8,7 @@
#include <BulletCollision/CollisionShapes/btTriangleMesh.h> #include <BulletCollision/CollisionShapes/btTriangleMesh.h>
#include <components/sceneutil/visitor.hpp>
#include <components/vfs/manager.hpp> #include <components/vfs/manager.hpp>
#include <components/nifbullet/bulletnifloader.hpp> #include <components/nifbullet/bulletnifloader.hpp>
@ -86,7 +87,17 @@ public:
return osg::ref_ptr<BulletShape>(); return osg::ref_ptr<BulletShape>();
osg::ref_ptr<BulletShape> shape (new BulletShape); osg::ref_ptr<BulletShape> 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; return shape;
} }
@ -135,11 +146,31 @@ osg::ref_ptr<const BulletShape> BulletShapeManager::getShape(const std::string &
osg::ref_ptr<const osg::Node> constNode (mSceneManager->getTemplate(normalized)); osg::ref_ptr<const osg::Node> constNode (mSceneManager->getTemplate(normalized));
osg::ref_ptr<osg::Node> node (const_cast<osg::Node*>(constNode.get())); // const-trickery required because there is no const version of NodeVisitor osg::ref_ptr<osg::Node> node (const_cast<osg::Node*>(constNode.get())); // const-trickery required because there is no const version of NodeVisitor
NodeToShapeVisitor visitor;
node->accept(visitor); // Check first if there's a custom collision node
shape = visitor.getShape(); 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) if (!shape)
return osg::ref_ptr<BulletShape>(); {
NodeToShapeVisitor visitor;
node->accept(visitor);
shape = visitor.getShape();
if (!shape)
return osg::ref_ptr<BulletShape>();
}
} }
mCache->addEntryToObjectCache(normalized, shape); mCache->addEntryToObjectCache(normalized, shape);

@ -9,6 +9,7 @@
#include <components/nifosg/nifloader.hpp> #include <components/nifosg/nifloader.hpp>
#include <components/sceneutil/keyframe.hpp> #include <components/sceneutil/keyframe.hpp>
#include <components/sceneutil/osgacontroller.hpp> #include <components/sceneutil/osgacontroller.hpp>
#include <components/misc/stringops.hpp>
#include "animation.hpp" #include "animation.hpp"
#include "objectcache.hpp" #include "objectcache.hpp"
@ -17,11 +18,13 @@
namespace Resource namespace Resource
{ {
RetrieveAnimationsVisitor::RetrieveAnimationsVisitor(SceneUtil::KeyframeHolder& target, osg::ref_ptr<osgAnimation::BasicAnimationManager> animationManager) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN), mTarget(target), mAnimationManager(animationManager) {} RetrieveAnimationsVisitor::RetrieveAnimationsVisitor(SceneUtil::KeyframeHolder& target, osg::ref_ptr<osgAnimation::BasicAnimationManager> 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) 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<SceneUtil::OsgAnimationController> callback = new SceneUtil::OsgAnimationController(); osg::ref_ptr<SceneUtil::OsgAnimationController> callback = new SceneUtil::OsgAnimationController();
@ -38,27 +41,19 @@ namespace Resource
osg::ref_ptr<Resource::Animation> mergedAnimationTrack = new Resource::Animation; osg::ref_ptr<Resource::Animation> mergedAnimationTrack = new Resource::Animation;
std::string animationName = animation->getName(); std::string animationName = animation->getName();
std::string start = animationName + std::string(": start"); mergedAnimationTrack->setName(animationName);
std::string stop = animationName + std::string(": stop");
const osgAnimation::ChannelList& channels = animation->getChannels(); const osgAnimation::ChannelList& channels = animation->getChannels();
for (const auto& channel: channels) for (const auto& channel: channels)
{ {
mergedAnimationTrack->addChannel(channel.get()->clone()); // is ->clone needed? mergedAnimationTrack->addChannel(channel.get()->clone()); // is ->clone needed?
} }
mergedAnimationTrack->setName(animation->getName());
callback->addMergedAnimationTrack(mergedAnimationTrack); callback->addMergedAnimationTrack(mergedAnimationTrack);
float startTime = animation->getStartTime(); float startTime = animation->getStartTime();
float stopTime = startTime + animation->getDuration(); 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; SceneUtil::EmulatedAnimation emulatedAnimation;
emulatedAnimation.mStartTime = startTime; emulatedAnimation.mStartTime = startTime;
emulatedAnimation.mStopTime = stopTime; emulatedAnimation.mStopTime = stopTime;
@ -66,12 +61,61 @@ namespace Resource
emulatedAnimations.emplace_back(emulatedAnimation); 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); callback->setEmulatedAnimations(emulatedAnimations);
mTarget.mKeyframeControllers.emplace(node.getName(), callback); mTarget.mKeyframeControllers.emplace(node.getName(), callback);
} }
traverse(node); 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 namespace Resource
@ -109,7 +153,7 @@ namespace Resource
osg::ref_ptr<osgAnimation::BasicAnimationManager> bam = dynamic_cast<osgAnimation::BasicAnimationManager*> (scene->getUpdateCallback()); osg::ref_ptr<osgAnimation::BasicAnimationManager> bam = dynamic_cast<osgAnimation::BasicAnimationManager*> (scene->getUpdateCallback());
if (bam) if (bam)
{ {
Resource::RetrieveAnimationsVisitor rav(*loaded.get(), bam); Resource::RetrieveAnimationsVisitor rav(*loaded.get(), bam, normalized, mVFS);
scene->accept(rav); scene->accept(rav);
} }
} }

@ -15,13 +15,21 @@ namespace Resource
class RetrieveAnimationsVisitor : public osg::NodeVisitor class RetrieveAnimationsVisitor : public osg::NodeVisitor
{ {
public: public:
RetrieveAnimationsVisitor(SceneUtil::KeyframeHolder& target, osg::ref_ptr<osgAnimation::BasicAnimationManager> animationManager); RetrieveAnimationsVisitor(SceneUtil::KeyframeHolder& target, osg::ref_ptr<osgAnimation::BasicAnimationManager> animationManager,
const std::string& normalized, const VFS::Manager* vfs);
virtual void apply(osg::Node& node) override; virtual void apply(osg::Node& node) override;
private: 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; SceneUtil::KeyframeHolder& mTarget;
osg::ref_ptr<osgAnimation::BasicAnimationManager> mAnimationManager; osg::ref_ptr<osgAnimation::BasicAnimationManager> mAnimationManager;
std::string mNormalized;
const VFS::Manager* mVFS;
}; };
} }

@ -25,6 +25,7 @@
#include <components/sceneutil/util.hpp> #include <components/sceneutil/util.hpp>
#include <components/sceneutil/controller.hpp> #include <components/sceneutil/controller.hpp>
#include <components/sceneutil/optimizer.hpp> #include <components/sceneutil/optimizer.hpp>
#include <components/sceneutil/visitor.hpp>
#include <components/shader/shadervisitor.hpp> #include <components/shader/shadervisitor.hpp>
#include <components/shader/shadermanager.hpp> #include <components/shader/shadermanager.hpp>
@ -373,6 +374,14 @@ namespace Resource
errormsg << "Error loading " << normalizedFilename << ": " << result.message() << " code " << result.status() << std::endl; errormsg << "Error loading " << normalizedFilename << ": " << result.message() << " code " << result.status() << std::endl;
throw std::runtime_error(errormsg.str()); 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(); 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", 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 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<std::string>(reserved, reserved + sizeof(reserved)/sizeof(reserved[0])); reservedNames = std::vector<std::string>(reserved, reserved + sizeof(reserved)/sizeof(reserved[0]));

@ -1,5 +1,7 @@
#include "actorutil.hpp" #include "actorutil.hpp"
#include <components/settings/settings.hpp>
namespace SceneUtil namespace SceneUtil
{ {
std::string getActorSkeleton(bool firstPerson, bool isFemale, bool isBeast, bool isWerewolf) std::string getActorSkeleton(bool firstPerson, bool isFemale, bool isBeast, bool isWerewolf)
@ -7,24 +9,24 @@ namespace SceneUtil
if (!firstPerson) if (!firstPerson)
{ {
if (isWerewolf) if (isWerewolf)
return "meshes\\wolf\\skin.nif"; return Settings::Manager::getString("wolfskin", "Models");
else if (isBeast) else if (isBeast)
return "meshes\\base_animkna.nif"; return Settings::Manager::getString("baseanimkna", "Models");
else if (isFemale) else if (isFemale)
return "meshes\\base_anim_female.nif"; return Settings::Manager::getString("baseanimfemale", "Models");
else else
return "meshes\\base_anim.nif"; return Settings::Manager::getString("baseanim", "Models");
} }
else else
{ {
if (isWerewolf) if (isWerewolf)
return "meshes\\wolf\\skin.1st.nif"; return Settings::Manager::getString("wolfskin1st", "Models");
else if (isBeast) else if (isBeast)
return "meshes\\base_animkna.1st.nif"; return Settings::Manager::getString("baseanimkna1st", "Models");
else if (isFemale) else if (isFemale)
return "meshes\\base_anim_female.1st.nif"; return Settings::Manager::getString("baseanimfemale1st", "Models");
else else
return "meshes\\base_anim.1st.nif"; return Settings::Manager::getString("xbaseanim1st", "Models");
} }
} }
} }

@ -997,9 +997,9 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv)
} }
// 1. Traverse main scene graph // 1. Traverse main scene graph
cv.pushStateSet( _shadowRecievingPlaceholderStateSet.get() ); auto* shadowReceiverStateSet = vdd->getStateSet(cv.getTraversalNumber());
shadowReceiverStateSet->clear();
osg::ref_ptr<osgUtil::StateGraph> decoratorStateGraph = cv.getCurrentStateGraph(); cv.pushStateSet(shadowReceiverStateSet);
cullShadowReceivingScene(&cv); cullShadowReceivingScene(&cv);
@ -1426,7 +1426,7 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv)
if (numValidShadows>0) if (numValidShadows>0)
{ {
decoratorStateGraph->setStateSet(selectStateSetForRenderingShadow(*vdd, cv.getTraversalNumber())); prepareStateSetForRenderingShadow(*vdd, cv.getTraversalNumber());
} }
// OSG_NOTICE<<"End of shadow setup Projection matrix "<<*cv.getProjectionMatrix()<<std::endl; // OSG_NOTICE<<"End of shadow setup Projection matrix "<<*cv.getProjectionMatrix()<<std::endl;
@ -3004,9 +3004,9 @@ void MWShadowTechnique::cullShadowCastingScene(osgUtil::CullVisitor* cv, osg::Ca
return; return;
} }
osg::StateSet* MWShadowTechnique::selectStateSetForRenderingShadow(ViewDependentData& vdd, unsigned int traversalNumber) const osg::StateSet* MWShadowTechnique::prepareStateSetForRenderingShadow(ViewDependentData& vdd, unsigned int traversalNumber) const
{ {
OSG_INFO<<" selectStateSetForRenderingShadow() "<<vdd.getStateSet(traversalNumber)<<std::endl; OSG_INFO<<" prepareStateSetForRenderingShadow() "<<vdd.getStateSet(traversalNumber)<<std::endl;
osg::ref_ptr<osg::StateSet> stateset = vdd.getStateSet(traversalNumber); osg::ref_ptr<osg::StateSet> stateset = vdd.getStateSet(traversalNumber);

@ -231,7 +231,7 @@ namespace SceneUtil {
virtual void cullShadowCastingScene(osgUtil::CullVisitor* cv, osg::Camera* camera) const; 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: protected:
virtual ~MWShadowTechnique(); virtual ~MWShadowTechnique();

@ -17,6 +17,7 @@
#include <osgAnimation/UpdateMatrixTransform> #include <osgAnimation/UpdateMatrixTransform>
#include <components/debug/debuglog.hpp> #include <components/debug/debuglog.hpp>
#include <components/misc/stringops.hpp>
#include <components/resource/animation.hpp> #include <components/resource/animation.hpp>
#include <components/sceneutil/controller.hpp> #include <components/sceneutil/controller.hpp>
#include <components/sceneutil/keyframe.hpp> #include <components/sceneutil/keyframe.hpp>
@ -83,7 +84,7 @@ namespace SceneUtil
{ {
osgAnimation::UpdateMatrixTransform* umt = dynamic_cast<osgAnimation::UpdateMatrixTransform*>(cb); osgAnimation::UpdateMatrixTransform* umt = dynamic_cast<osgAnimation::UpdateMatrixTransform*>(cb);
if (umt) if (umt)
if (node.getName() != "bip01") link(umt); if (Misc::StringUtils::lowerCase(node.getName()) != "bip01") link(umt);
cb = cb->getNestedCallback(); cb = cb->getNestedCallback();
} }
@ -102,10 +103,14 @@ namespace SceneUtil
} }
OsgAnimationController::OsgAnimationController(const OsgAnimationController &copy, const osg::CopyOp &copyop) : SceneUtil::KeyframeController(copy, copyop) OsgAnimationController::OsgAnimationController(const OsgAnimationController &copy, const osg::CopyOp &copyop) : SceneUtil::KeyframeController(copy, copyop)
, mMergedAnimationTracks(copy.mMergedAnimationTracks)
, mEmulatedAnimations(copy.mEmulatedAnimations) , mEmulatedAnimations(copy.mEmulatedAnimations)
{ {
mLinker = nullptr; mLinker = nullptr;
for (const auto& mergedAnimationTrack : copy.mMergedAnimationTracks)
{
Resource::Animation* copiedAnimationTrack = static_cast<Resource::Animation*>(mergedAnimationTrack.get()->clone(copyop));
mMergedAnimationTracks.emplace_back(copiedAnimationTrack);
}
} }
osg::Vec3f OsgAnimationController::getTranslation(float time) const osg::Vec3f OsgAnimationController::getTranslation(float time) const

@ -60,7 +60,20 @@ namespace SceneUtil
void NodeMapVisitor::apply(osg::MatrixTransform& trans) void NodeMapVisitor::apply(osg::MatrixTransform& trans)
{ {
// Take transformation for first found node in file // 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); mMap.emplace(nodeName, &trans);
traverse(trans); traverse(trans);

@ -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. 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. 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 always run
---------- ----------

@ -955,6 +955,51 @@ defer aabb update = true
# Loading arbitrary meshes is not advised and may cause instability. # Loading arbitrary meshes is not advised and may cause instability.
load unsupported nif files = false 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] [Groundcover]
# enable separate groundcover handling # enable separate groundcover handling

@ -13,22 +13,13 @@ void perLight(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec3 vie
#ifndef GROUNDCOVER #ifndef GROUNDCOVER
lambert = max(lambert, 0.0); lambert = max(lambert, 0.0);
#else #else
float eyeCosine = dot(normalize(viewPos), viewNormal.xyz);
if (lambert < 0.0)
{ {
float cosine = dot(normalize(viewPos), normalize(viewNormal.xyz)); lambert = -lambert;
if (lambert >= 0.0) eyeCosine = -eyeCosine;
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 *= clamp(-8.0 * (1.0 - 0.3) * eyeCosine + 1.0, 0.3, 1.0);
#endif #endif
diffuseOut = gl_LightSource[lightIndex].diffuse.xyz * lambert; diffuseOut = gl_LightSource[lightIndex].diffuse.xyz * lambert;
} }

Loading…
Cancel
Save