1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-01-31 20:45:34 +00:00

Replace missing NPC races and default animations

This commit is contained in:
Evil Eye 2023-12-29 16:55:32 +01:00
parent 78459314bf
commit 5bd5c84018

View file

@ -17,8 +17,10 @@
#include <components/loadinglistener/loadinglistener.hpp>
#include <components/lua/configuration.hpp>
#include <components/misc/algorithm.hpp>
#include <components/misc/resourcehelpers.hpp>
#include "../mwmechanics/spelllist.hpp"
#include "../mwrender/actorutil.hpp"
namespace
{
@ -83,12 +85,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 +125,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 +134,41 @@ namespace
changed = true;
}
const ESM::Race* race = races.search(npc.mRace);
if (race)
{
// TESCS sometimes writes the default animation nif to the animation subrecord. This harmless (as it
// will match the NPC's race) until the NPC's race is changed. If the player record contains a default
// non-beast race animation and the player selects a beast race in chargen, animations aren't applied
// properly. Morrowind.exe handles this gracefully, so we clear the animation here to force the default.
if (!npc.mModel.empty() && npc.mId == "player")
{
const bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0;
const std::string& defaultModel = MWRender::getActorSkeleton(false, !npc.isMale(), isBeast, false);
std::string model = Misc::ResourceHelpers::correctMeshPath(npc.mModel);
if (model.size() == defaultModel.size())
{
std::replace(model.begin(), model.end(), '/', '\\');
std::string normalizedDefault = defaultModel;
std::replace(normalizedDefault.begin(), normalizedDefault.end(), '/', '\\');
if (Misc::StringUtils::ciEqual(normalizedDefault, model))
{
npc.mModel.clear();
changed = true;
}
}
}
}
else
{
Log(Debug::Verbose) << "NPC " << npc.mId << " (" << npc.mName << ") has nonexistent race " << npc.mRace
<< ", using " << defaultRace << " race as replacement.";
npc.mRace = defaultRace;
// Remove animations that might be race specific
npc.mModel.clear();
changed = true;
}
if (!npc.mScript.empty() && !scripts.search(npc.mScript))
{
Log(Debug::Verbose) << "NPC " << npc.mId << " (" << npc.mName << ") has nonexistent script "
@ -580,8 +626,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 +669,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);