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
- 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:

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

@ -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
------

@ -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

@ -379,45 +379,31 @@ namespace MWClass
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();
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<ESM::Cell>().find(x,y);
if (cell->mName != "")
dest = cell->mName;
else
{
const ESM::Region* region =
store.get<ESM::Region>().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<ESM::Cell>().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 + "}";
}

@ -435,10 +435,10 @@ namespace MWClass
{
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);
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<ESM::NPC> *npc = ptr.get<ESM::NPC>();
const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().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);

@ -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:

@ -6,6 +6,9 @@
#include <MyGUI_TextBox.h>
// correctIconPath
#include <components/resource/resourcesystem.hpp>
#include <components/vfs/manager.hpp>
#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);
}

@ -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();
}

@ -452,6 +452,8 @@ namespace MWMechanics
MWMechanics::DynamicStat<float> health = attackerStats.getHealth();
health.setCurrent(health.getCurrent() - x);
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)
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)

@ -18,8 +18,10 @@ namespace MWMechanics
}
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);
}

@ -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;

@ -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

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

@ -184,6 +184,9 @@ namespace MWRender
osg::ref_ptr<osg::Texture2D> 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));

@ -10,7 +10,7 @@
#include <components/sceneutil/visitor.hpp>
#include <components/sceneutil/positionattitudetransform.hpp>
#include <components/sceneutil/skeleton.hpp>
#include <components/settings/settings.hpp>
#include <components/misc/stringops.hpp>
#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);

@ -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);

@ -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; i<sizeof(basemodels)/sizeof(basemodels[0]); ++i)
{
workItem->mModels.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");

@ -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<class R>
@ -290,20 +290,20 @@ namespace MWScript
Interpreter::Type_Integer value = runtime[0].mInteger;
runtime.pop();
MWMechanics::Stat<int> 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<int> 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);
/*

@ -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);
}

@ -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<ESM::Region>().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<ESM::Region>().search (cell->mRegion))
return region->mName;
}
return mStore.get<ESM::GameSetting>().find ("sDefaultCellname")->mValue.getString();
}
@ -3461,7 +3467,7 @@ namespace MWWorld
// Check mana
bool godmode = (isPlayer && mGodMode);
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}";
fail = true;

@ -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

@ -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

@ -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(
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(
"<b>Morrowind.bsa</b> is missing!<br>\
Make sure your Morrowind installation is complete."
));
msgBox.exec();
return;
}
QString path(QDir::toNativeSeparators(info.absolutePath()));
QList<QListWidgetItem*> items = installationsList->findItems(path, Qt::MatchExactly);

@ -2,6 +2,8 @@
#define COMPONENTS_MISC_ENDIANNESS_H
#include <cstdint>
#include <cstring>
#include <type_traits>
namespace Misc
{
@ -15,20 +17,26 @@ namespace Misc
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);
std::memcpy(&v, &v16, sizeof(T));
}
if constexpr (sizeof(T) == 4)
{
uint32_t& v32 = *reinterpret_cast<uint32_t*>(&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<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 & 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));
}
}

@ -8,6 +8,7 @@
#include <BulletCollision/CollisionShapes/btTriangleMesh.h>
#include <components/sceneutil/visitor.hpp>
#include <components/vfs/manager.hpp>
#include <components/nifbullet/bulletnifloader.hpp>
@ -86,7 +87,17 @@ public:
return osg::ref_ptr<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;
}
@ -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<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);
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<BulletShape>();
{
NodeToShapeVisitor visitor;
node->accept(visitor);
shape = visitor.getShape();
if (!shape)
return osg::ref_ptr<BulletShape>();
}
}
mCache->addEntryToObjectCache(normalized, shape);

@ -9,6 +9,7 @@
#include <components/nifosg/nifloader.hpp>
#include <components/sceneutil/keyframe.hpp>
#include <components/sceneutil/osgacontroller.hpp>
#include <components/misc/stringops.hpp>
#include "animation.hpp"
#include "objectcache.hpp"
@ -17,11 +18,13 @@
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)
{
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();
@ -38,27 +41,19 @@ namespace Resource
osg::ref_ptr<Resource::Animation> 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<osgAnimation::BasicAnimationManager> bam = dynamic_cast<osgAnimation::BasicAnimationManager*> (scene->getUpdateCallback());
if (bam)
{
Resource::RetrieveAnimationsVisitor rav(*loaded.get(), bam);
Resource::RetrieveAnimationsVisitor rav(*loaded.get(), bam, normalized, mVFS);
scene->accept(rav);
}
}

@ -15,13 +15,21 @@ namespace Resource
class RetrieveAnimationsVisitor : public osg::NodeVisitor
{
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;
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<osgAnimation::BasicAnimationManager> mAnimationManager;
std::string mNormalized;
const VFS::Manager* mVFS;
};
}

@ -25,6 +25,7 @@
#include <components/sceneutil/util.hpp>
#include <components/sceneutil/controller.hpp>
#include <components/sceneutil/optimizer.hpp>
#include <components/sceneutil/visitor.hpp>
#include <components/shader/shadervisitor.hpp>
#include <components/shader/shadermanager.hpp>
@ -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<std::string>(reserved, reserved + sizeof(reserved)/sizeof(reserved[0]));

@ -1,5 +1,7 @@
#include "actorutil.hpp"
#include <components/settings/settings.hpp>
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");
}
}
}

@ -997,9 +997,9 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv)
}
// 1. Traverse main scene graph
cv.pushStateSet( _shadowRecievingPlaceholderStateSet.get() );
osg::ref_ptr<osgUtil::StateGraph> 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()<<std::endl;
@ -3004,9 +3004,9 @@ void MWShadowTechnique::cullShadowCastingScene(osgUtil::CullVisitor* cv, osg::Ca
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);

@ -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();

@ -17,6 +17,7 @@
#include <osgAnimation/UpdateMatrixTransform>
#include <components/debug/debuglog.hpp>
#include <components/misc/stringops.hpp>
#include <components/resource/animation.hpp>
#include <components/sceneutil/controller.hpp>
#include <components/sceneutil/keyframe.hpp>
@ -83,7 +84,7 @@ namespace SceneUtil
{
osgAnimation::UpdateMatrixTransform* umt = dynamic_cast<osgAnimation::UpdateMatrixTransform*>(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 &copy, const osg::CopyOp &copyop) : SceneUtil::KeyframeController(copy, copyop)
, mMergedAnimationTracks(copy.mMergedAnimationTracks)
, mEmulatedAnimations(copy.mEmulatedAnimations)
{
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

@ -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);

@ -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
----------

@ -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

@ -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;
}

Loading…
Cancel
Save