Merge branch 'raceless' into 'master'

Replace missing NPC races and default animations

Closes #6754

See merge request OpenMW/openmw!3703
qt6_macos
Alexei Kotov 5 months ago
commit 2ff2e3c2ef

@ -38,6 +38,7 @@
Bug #6657: Distant terrain tiles become black when using FWIW mod
Bug #6661: Saved games that have no preview screenshot cause issues or crashes
Bug #6716: mwscript comparison operator handling is too restrictive
Bug #6754: Beast to Non-beast transformation mod is not working on OpenMW
Bug #6807: Ultimate Galleon is not working properly
Bug #6893: Lua: Inconsistent behavior with actors affected by Disable and SetDelete commands
Bug #6894: Added item combines with equipped stack instead of creating a new unequipped stack

@ -1,6 +1,7 @@
#include "actorutil.hpp"
#include <components/settings/values.hpp>
#include <components/vfs/pathutil.hpp>
namespace MWRender
{
@ -29,4 +30,11 @@ namespace MWRender
return Settings::models().mXbaseanim1st;
}
}
bool isDefaultActorSkeleton(std::string_view model)
{
return VFS::Path::pathEqual(Settings::models().mBaseanimkna.get(), model)
|| VFS::Path::pathEqual(Settings::models().mBaseanimfemale.get(), model)
|| VFS::Path::pathEqual(Settings::models().mBaseanim.get(), model);
}
}

@ -2,10 +2,12 @@
#define OPENMW_APPS_OPENMW_MWRENDER_ACTORUTIL_H
#include <string>
#include <string_view>
namespace MWRender
{
const std::string& getActorSkeleton(bool firstPerson, bool female, bool beast, bool werewolf);
bool isDefaultActorSkeleton(std::string_view model);
}
#endif

@ -492,9 +492,13 @@ namespace MWRender
getActorSkeleton(is1stPerson, isFemale, isBeast, isWerewolf), mResourceSystem->getVFS());
std::string smodel = defaultSkeleton;
bool isBase = !isWerewolf;
if (!is1stPerson && !isWerewolf && !mNpc->mModel.empty())
smodel = Misc::ResourceHelpers::correctActorModelPath(
Misc::ResourceHelpers::correctMeshPath(mNpc->mModel), mResourceSystem->getVFS());
{
std::string model = Misc::ResourceHelpers::correctMeshPath(mNpc->mModel);
isBase = isDefaultActorSkeleton(model);
smodel = Misc::ResourceHelpers::correctActorModelPath(model, mResourceSystem->getVFS());
}
setObjectRoot(smodel, true, true, false);
@ -503,24 +507,26 @@ namespace MWRender
if (!is1stPerson)
{
const std::string& base = Settings::models().mXbaseanim;
if (smodel != base && !isWerewolf)
if (!isWerewolf)
addAnimSource(base, smodel);
if (smodel != defaultSkeleton && base != defaultSkeleton)
addAnimSource(defaultSkeleton, smodel);
addAnimSource(smodel, smodel);
if (!isBase)
addAnimSource(smodel, smodel);
if (!isWerewolf && mNpc->mRace.contains("argonian"))
if (!isWerewolf && isBeast && mNpc->mRace.contains("argonian"))
addAnimSource("meshes\\xargonian_swimkna.nif", smodel);
}
else
{
const std::string& base = Settings::models().mXbaseanim1st;
if (smodel != base && !isWerewolf)
if (!isWerewolf)
addAnimSource(base, smodel);
addAnimSource(smodel, smodel);
if (!isBase)
addAnimSource(smodel, smodel);
mObjectRoot->setNodeMask(Mask_FirstPerson);
mObjectRoot->addCullCallback(new OverrideFieldOfViewCallback(mFirstPersonFieldOfView));

@ -83,12 +83,22 @@ namespace
throw std::runtime_error("List of NPC classes is empty!");
}
const ESM::RefId& getDefaultRace(const MWWorld::Store<ESM::Race>& races)
{
auto it = races.begin();
if (it != races.end())
return it->mId;
throw std::runtime_error("List of NPC races is empty!");
}
std::vector<ESM::NPC> getNPCsToReplace(const MWWorld::Store<ESM::Faction>& factions,
const MWWorld::Store<ESM::Class>& classes, const MWWorld::Store<ESM::Script>& scripts,
const std::unordered_map<ESM::RefId, ESM::NPC>& npcs)
const MWWorld::Store<ESM::Class>& classes, const MWWorld::Store<ESM::Race>& races,
const MWWorld::Store<ESM::Script>& scripts, const std::unordered_map<ESM::RefId, ESM::NPC>& npcs)
{
// Cache first class from store - we will use it if current class is not found
const ESM::RefId& defaultCls = getDefaultClass(classes);
// Same for races
const ESM::RefId& defaultRace = getDefaultRace(races);
// Validate NPCs for non-existing class and faction.
// We will replace invalid entries by fixed ones
@ -113,8 +123,7 @@ namespace
}
}
const ESM::RefId& npcClass = npc.mClass;
const ESM::Class* cls = classes.search(npcClass);
const ESM::Class* cls = classes.search(npc.mClass);
if (!cls)
{
Log(Debug::Verbose) << "NPC " << npc.mId << " (" << npc.mName << ") has nonexistent class "
@ -123,6 +132,15 @@ namespace
changed = true;
}
const ESM::Race* race = races.search(npc.mRace);
if (!race)
{
Log(Debug::Verbose) << "NPC " << npc.mId << " (" << npc.mName << ") has nonexistent race " << npc.mRace
<< ", using " << defaultRace << " race as replacement.";
npc.mRace = defaultRace;
changed = true;
}
if (!npc.mScript.empty() && !scripts.search(npc.mScript))
{
Log(Debug::Verbose) << "NPC " << npc.mId << " (" << npc.mName << ") has nonexistent script "
@ -580,8 +598,8 @@ namespace MWWorld
void ESMStore::validate()
{
auto& npcs = getWritable<ESM::NPC>();
std::vector<ESM::NPC> npcsToReplace = getNPCsToReplace(
getWritable<ESM::Faction>(), getWritable<ESM::Class>(), getWritable<ESM::Script>(), npcs.mStatic);
std::vector<ESM::NPC> npcsToReplace = getNPCsToReplace(getWritable<ESM::Faction>(), getWritable<ESM::Class>(),
getWritable<ESM::Race>(), getWritable<ESM::Script>(), npcs.mStatic);
for (const ESM::NPC& npc : npcsToReplace)
{
@ -623,8 +641,8 @@ namespace MWWorld
auto& npcs = getWritable<ESM::NPC>();
auto& scripts = getWritable<ESM::Script>();
std::vector<ESM::NPC> npcsToReplace = getNPCsToReplace(
getWritable<ESM::Faction>(), getWritable<ESM::Class>(), getWritable<ESM::Script>(), npcs.mDynamic);
std::vector<ESM::NPC> npcsToReplace = getNPCsToReplace(getWritable<ESM::Faction>(), getWritable<ESM::Class>(),
getWritable<ESM::Race>(), getWritable<ESM::Script>(), npcs.mDynamic);
for (const ESM::NPC& npc : npcsToReplace)
npcs.insert(npc);

Loading…
Cancel
Save