diff --git a/apps/openmw/mwclass/esm4npc.cpp b/apps/openmw/mwclass/esm4npc.cpp index 638144eb66..eca33d0701 100644 --- a/apps/openmw/mwclass/esm4npc.cpp +++ b/apps/openmw/mwclass/esm4npc.cpp @@ -88,26 +88,34 @@ namespace MWClass auto data = std::make_unique(); const MWWorld::ESMStore* store = MWBase::Environment::get().getESMStore(); - auto npcRecs = withBaseTemplates(ptr.get()->mBase); + const ESM4::Npc* const base = ptr.get()->mBase; + auto npcRecs = withBaseTemplates(base); data->mTraits = chooseTemplate(npcRecs, ESM4::Npc::Template_UseTraits); + + if (data->mTraits == nullptr) + Log(Debug::Warning) << "Traits are not found for ESM4 NPC base record: \"" << base->mEditorId << "\" (" + << ESM::RefId(base->mId) << ")"; + data->mBaseData = chooseTemplate(npcRecs, ESM4::Npc::Template_UseBaseData); - if (!data->mTraits) - throw std::runtime_error("ESM4 NPC traits not found"); - if (!data->mBaseData) - throw std::runtime_error("ESM4 NPC base data not found"); + if (data->mBaseData == nullptr) + Log(Debug::Warning) << "Base data is not found for ESM4 NPC base record: \"" << base->mEditorId << "\" (" + << ESM::RefId(base->mId) << ")"; - data->mRace = store->get().find(data->mTraits->mRace); - if (data->mTraits->mIsTES4) - data->mIsFemale = data->mTraits->mBaseConfig.tes4.flags & ESM4::Npc::TES4_Female; - else if (data->mTraits->mIsFONV) - data->mIsFemale = data->mTraits->mBaseConfig.fo3.flags & ESM4::Npc::FO3_Female; - else if (data->mTraits->mIsFO4) - data->mIsFemale - = data->mTraits->mBaseConfig.fo4.flags & ESM4::Npc::TES5_Female; // FO4 flags are the same as TES5 - else - data->mIsFemale = data->mTraits->mBaseConfig.tes5.flags & ESM4::Npc::TES5_Female; + if (data->mTraits != nullptr) + { + data->mRace = store->get().find(data->mTraits->mRace); + if (data->mTraits->mIsTES4) + data->mIsFemale = data->mTraits->mBaseConfig.tes4.flags & ESM4::Npc::TES4_Female; + else if (data->mTraits->mIsFONV) + data->mIsFemale = data->mTraits->mBaseConfig.fo3.flags & ESM4::Npc::FO3_Female; + else if (data->mTraits->mIsFO4) + data->mIsFemale + = data->mTraits->mBaseConfig.fo4.flags & ESM4::Npc::TES5_Female; // FO4 flags are the same as TES5 + else + data->mIsFemale = data->mTraits->mBaseConfig.tes5.flags & ESM4::Npc::TES5_Female; + } if (auto inv = chooseTemplate(npcRecs, ESM4::Npc::Template_UseInventory)) { @@ -116,7 +124,7 @@ namespace MWClass if (auto* armor = ESM4Impl::resolveLevelled(ESM::FormId::fromUint32(item.item))) data->mEquippedArmor.push_back(armor); - else if (data->mTraits->mIsTES4) + else if (data->mTraits != nullptr && data->mTraits->mIsTES4) { const auto* clothing = ESM4Impl::resolveLevelled( ESM::FormId::fromUint32(item.item)); @@ -170,6 +178,8 @@ namespace MWClass std::string ESM4Npc::getModel(const MWWorld::ConstPtr& ptr) const { const ESM4NpcCustomData& data = getCustomData(ptr); + if (data.mTraits == nullptr) + return {}; std::string_view model; if (data.mTraits->mIsTES4) model = data.mTraits->mModel; @@ -181,6 +191,9 @@ namespace MWClass std::string_view ESM4Npc::getName(const MWWorld::ConstPtr& ptr) const { - return getCustomData(ptr).mBaseData->mFullName; + const ESM4::Npc* const baseData = getCustomData(ptr).mBaseData; + if (baseData == nullptr) + return {}; + return baseData->mFullName; } } diff --git a/apps/openmw/mwrender/esm4npcanimation.cpp b/apps/openmw/mwrender/esm4npcanimation.cpp index 3ea8f829ce..3a3ed37344 100644 --- a/apps/openmw/mwrender/esm4npcanimation.cpp +++ b/apps/openmw/mwrender/esm4npcanimation.cpp @@ -13,7 +13,6 @@ #include #include "../mwclass/esm4npc.hpp" -#include "../mwworld/customdata.hpp" #include "../mwworld/esmstore.hpp" namespace MWRender @@ -28,11 +27,13 @@ namespace MWRender void ESM4NpcAnimation::updateParts() { - if (!mObjectRoot.get()) + if (mObjectRoot == nullptr) return; const ESM4::Npc* traits = MWClass::ESM4Npc::getTraitsRecord(mPtr); + if (traits == nullptr) + return; if (traits->mIsTES4) - updatePartsTES4(); + updatePartsTES4(*traits); else if (traits->mIsFONV) { // Not implemented yet @@ -42,7 +43,7 @@ namespace MWRender // There is no easy way to distinguish TES5 and FO3. // In case of FO3 the function shouldn't crash the game and will // only lead to the NPC not being rendered. - updatePartsTES5(); + updatePartsTES5(*traits); } } @@ -65,9 +66,8 @@ namespace MWRender return rec->mModel; } - void ESM4NpcAnimation::updatePartsTES4() + void ESM4NpcAnimation::updatePartsTES4(const ESM4::Npc& traits) { - const ESM4::Npc* traits = MWClass::ESM4Npc::getTraitsRecord(mPtr); const ESM4::Race* race = MWClass::ESM4Npc::getRace(mPtr); bool isFemale = MWClass::ESM4Npc::isFemale(mPtr); @@ -77,13 +77,13 @@ namespace MWRender insertPart(bodyPart.mesh); for (const ESM4::Race::BodyPart& bodyPart : race->mHeadParts) insertPart(bodyPart.mesh); - if (!traits->mHair.isZeroOrUnset()) + if (!traits.mHair.isZeroOrUnset()) { const MWWorld::ESMStore* store = MWBase::Environment::get().getESMStore(); - if (const ESM4::Hair* hair = store->get().search(traits->mHair)) + if (const ESM4::Hair* hair = store->get().search(traits.mHair)) insertPart(hair->mModel); else - Log(Debug::Error) << "Hair not found: " << ESM::RefId(traits->mHair); + Log(Debug::Error) << "Hair not found: " << ESM::RefId(traits.mHair); } for (const ESM4::Armor* armor : MWClass::ESM4Npc::getEquippedArmor(mPtr)) @@ -111,11 +111,10 @@ namespace MWRender } } - void ESM4NpcAnimation::updatePartsTES5() + void ESM4NpcAnimation::updatePartsTES5(const ESM4::Npc& traits) { const MWWorld::ESMStore* store = MWBase::Environment::get().getESMStore(); - const ESM4::Npc* traits = MWClass::ESM4Npc::getTraitsRecord(mPtr); const ESM4::Race* race = MWClass::ESM4Npc::getRace(mPtr); bool isFemale = MWClass::ESM4Npc::isFemale(mPtr); @@ -132,9 +131,9 @@ namespace MWRender Log(Debug::Error) << "ArmorAddon not found: " << ESM::RefId(armaId); continue; } - bool compatibleRace = arma->mRacePrimary == traits->mRace; + bool compatibleRace = arma->mRacePrimary == traits.mRace; for (auto r : arma->mRaces) - if (r == traits->mRace) + if (r == traits.mRace) compatibleRace = true; if (compatibleRace) armorAddons.push_back(arma); @@ -143,12 +142,12 @@ namespace MWRender for (const ESM4::Armor* armor : MWClass::ESM4Npc::getEquippedArmor(mPtr)) findArmorAddons(armor); - if (!traits->mWornArmor.isZeroOrUnset()) + if (!traits.mWornArmor.isZeroOrUnset()) { - if (const ESM4::Armor* armor = store->get().search(traits->mWornArmor)) + if (const ESM4::Armor* armor = store->get().search(traits.mWornArmor)) findArmorAddons(armor); else - Log(Debug::Error) << "Worn armor not found: " << ESM::RefId(traits->mWornArmor); + Log(Debug::Error) << "Worn armor not found: " << ESM::RefId(traits.mWornArmor); } if (!race->mSkin.isZeroOrUnset()) { @@ -183,7 +182,7 @@ namespace MWRender std::set usedHeadPartTypes; if (usedParts & ESM4::Armor::TES5_Hair) usedHeadPartTypes.insert(ESM4::HeadPart::Type_Hair); - insertHeadParts(traits->mHeadParts, usedHeadPartTypes); + insertHeadParts(traits.mHeadParts, usedHeadPartTypes); insertHeadParts(isFemale ? race->mHeadPartIdsFemale : race->mHeadPartIdsMale, usedHeadPartTypes); } } diff --git a/apps/openmw/mwrender/esm4npcanimation.hpp b/apps/openmw/mwrender/esm4npcanimation.hpp index 7bb3fe1103..274c060b06 100644 --- a/apps/openmw/mwrender/esm4npcanimation.hpp +++ b/apps/openmw/mwrender/esm4npcanimation.hpp @@ -3,6 +3,11 @@ #include "animation.hpp" +namespace ESM4 +{ + struct Npc; +} + namespace MWRender { class ESM4NpcAnimation : public Animation @@ -18,8 +23,8 @@ namespace MWRender void insertHeadParts(const std::vector& partIds, std::set& usedHeadPartTypes); void updateParts(); - void updatePartsTES4(); - void updatePartsTES5(); + void updatePartsTES4(const ESM4::Npc& traits); + void updatePartsTES5(const ESM4::Npc& traits); }; }