Something went wrong. Try again.
OpenMW is an open implementation of the Morrowind engine
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708#include "actoradapter.hpp"
#include <QModelIndex>
#include <algorithm>#include <string>#include <string_view>#include <vector>
#include <apps/opencs/model/prefs/state.hpp>#include <apps/opencs/model/world/columns.hpp>#include <apps/opencs/model/world/idcollection.hpp>#include <apps/opencs/model/world/record.hpp>#include <apps/opencs/model/world/refidcollection.hpp>#include <apps/opencs/model/world/universalid.hpp>
#include <components/esm3/loadarmo.hpp>#include <components/esm3/loadclot.hpp>#include <components/esm3/loadcont.hpp>#include <components/esm3/loadcrea.hpp>#include <components/esm3/loadnpc.hpp>#include <components/esm3/loadrace.hpp>#include <components/esm3/mappings.hpp>#include <components/settings/settings.hpp>
#include "data.hpp"
namespace CSMWorld{ const ESM::RefId& ActorAdapter::RaceData::getId() const { return mId; }
bool ActorAdapter::RaceData::isBeast() const { return mIsBeast; }
ActorAdapter::RaceData::RaceData() { mIsBeast = false; }
bool ActorAdapter::RaceData::handlesPart(ESM::PartReferenceType type) const { switch (type) { case ESM::PRT_Skirt: case ESM::PRT_Shield: case ESM::PRT_RPauldron: case ESM::PRT_LPauldron: case ESM::PRT_Weapon: return false; default: return true; } }
const ESM::RefId& ActorAdapter::RaceData::getFemalePart(ESM::PartReferenceType index) const { return mFemaleParts[ESM::getMeshPart(index)]; }
const ESM::RefId& ActorAdapter::RaceData::getMalePart(ESM::PartReferenceType index) const { return mMaleParts[ESM::getMeshPart(index)]; }
const osg::Vec2f& ActorAdapter::RaceData::getGenderWeightHeight(bool isFemale) { return isFemale ? mWeightsHeights.mFemaleWeightHeight : mWeightsHeights.mMaleWeightHeight; }
bool ActorAdapter::RaceData::hasDependency(const ESM::RefId& id) const { return mDependencies.find(id) != mDependencies.end(); }
void ActorAdapter::RaceData::setFemalePart(ESM::BodyPart::MeshPart index, const ESM::RefId& partId) { mFemaleParts[index] = partId; addOtherDependency(partId); }
void ActorAdapter::RaceData::setMalePart(ESM::BodyPart::MeshPart index, const ESM::RefId& partId) { mMaleParts[index] = partId; addOtherDependency(partId); }
void ActorAdapter::RaceData::addOtherDependency(const ESM::RefId& id) { if (!id.empty()) mDependencies.emplace(id); }
void ActorAdapter::RaceData::reset_data(const ESM::RefId& id, const WeightsHeights& raceStats, bool isBeast) { mId = id; mIsBeast = isBeast; mWeightsHeights = raceStats; for (auto& str : mFemaleParts) str = ESM::RefId(); for (auto& str : mMaleParts) str = ESM::RefId(); mDependencies.clear();
// Mark self as a dependency addOtherDependency(id); }
ActorAdapter::ActorData::ActorData() { mCreature = false; mFemale = false; }
const ESM::RefId& ActorAdapter::ActorData::getId() const { return mId; }
bool ActorAdapter::ActorData::isCreature() const { return mCreature; }
bool ActorAdapter::ActorData::isFemale() const { return mFemale; }
VFS::Path::Normalized ActorAdapter::ActorData::getSkeleton() const { constexpr VFS::Path::NormalizedView meshes("meshes"); if (mCreature || !mSkeletonOverride.empty()) return VFS::Path::join(meshes, mSkeletonOverride);
bool beast = mRaceData ? mRaceData->isBeast() : false;
if (beast) return VFS::Path::toNormalized(CSMPrefs::get()["Models"]["baseanimkna"].toString()); else if (mFemale) return VFS::Path::toNormalized(CSMPrefs::get()["Models"]["baseanimfemale"].toString()); else return VFS::Path::toNormalized(CSMPrefs::get()["Models"]["baseanim"].toString()); }
ESM::RefId ActorAdapter::ActorData::getPart(ESM::PartReferenceType index) const { auto it = mParts.find(index); if (it == mParts.end()) { if (mRaceData && mRaceData->handlesPart(index)) { if (mFemale) { // Note: we should use male parts for females as fallback if (const ESM::RefId femalePart = mRaceData->getFemalePart(index); !femalePart.empty()) return femalePart; }
return mRaceData->getMalePart(index); }
return {}; }
return it->second.first; }
const osg::Vec2f& ActorAdapter::ActorData::getRaceWeightHeight() const { return mRaceData->getGenderWeightHeight(isFemale()); }
bool ActorAdapter::ActorData::hasDependency(const ESM::RefId& id) const { return mDependencies.find(id) != mDependencies.end(); }
void ActorAdapter::ActorData::setPart(ESM::PartReferenceType index, const ESM::RefId& partId, int priority) { auto it = mParts.find(index); if (it != mParts.end()) { if (it->second.second >= priority) return; }
mParts[index] = std::make_pair(partId, priority); addOtherDependency(partId); }
void ActorAdapter::ActorData::addOtherDependency(const ESM::RefId& id) { if (!id.empty()) mDependencies.emplace(id); }
void ActorAdapter::ActorData::reset_data( const ESM::RefId& id, const std::string& skeleton, bool isCreature, bool isFemale, RaceDataPtr raceData) { mId = id; mCreature = isCreature; mFemale = isFemale; mSkeletonOverride = skeleton; mRaceData = raceData; mParts.clear(); mDependencies.clear();
// Mark self and race as a dependency addOtherDependency(id); if (raceData) addOtherDependency(raceData->getId()); }
ActorAdapter::ActorAdapter(Data& data) : mReferenceables(data.getReferenceables()) , mRaces(data.getRaces()) , mBodyParts(data.getBodyParts()) { // Setup qt slots and signals QAbstractItemModel* refModel = data.getTableModel(UniversalId::Type_Referenceable); connect(refModel, &QAbstractItemModel::rowsInserted, this, &ActorAdapter::handleReferenceablesInserted); connect(refModel, &QAbstractItemModel::dataChanged, this, &ActorAdapter::handleReferenceableChanged); connect(refModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &ActorAdapter::handleReferenceablesAboutToBeRemoved);
QAbstractItemModel* raceModel = data.getTableModel(UniversalId::Type_Race); connect(raceModel, &QAbstractItemModel::rowsInserted, this, &ActorAdapter::handleRacesAboutToBeRemoved); connect(raceModel, &QAbstractItemModel::dataChanged, this, &ActorAdapter::handleRaceChanged); connect(raceModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &ActorAdapter::handleRacesAboutToBeRemoved);
QAbstractItemModel* partModel = data.getTableModel(UniversalId::Type_BodyPart); connect(partModel, &QAbstractItemModel::rowsInserted, this, &ActorAdapter::handleBodyPartsInserted); connect(partModel, &QAbstractItemModel::dataChanged, this, &ActorAdapter::handleBodyPartChanged); connect( partModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &ActorAdapter::handleBodyPartsAboutToBeRemoved); }
ActorAdapter::ActorDataPtr ActorAdapter::getActorData(const ESM::RefId& id) { // Return cached actor data if it exists ActorDataPtr data = mCachedActors.get(id); if (data) { return data; }
// Create the actor data data = std::make_shared<ActorData>(); setupActor(id, data); mCachedActors.insert(id, data); return data; }
void ActorAdapter::handleReferenceablesInserted(const QModelIndex& parent, int start, int end) { // Only rows added at the top level are pertinent. Others are caught by dataChanged handler. if (!parent.isValid()) { for (int row = start; row <= end; ++row) { auto refId = mReferenceables.getId(row); markDirtyDependency(refId); } }
// Update affected updateDirty(); }
void ActorAdapter::handleReferenceableChanged(const QModelIndex& topLeft, const QModelIndex& botRight) { int start = getHighestIndex(topLeft).row(); int end = getHighestIndex(botRight).row();
// A change to record status (ex. Deleted) returns an invalid botRight if (end == -1) end = start;
// Handle each record for (int row = start; row <= end; ++row) { auto refId = mReferenceables.getId(row); markDirtyDependency(refId); }
// Update affected updateDirty(); }
void ActorAdapter::handleReferenceablesAboutToBeRemoved(const QModelIndex& parent, int start, int end) { // Only rows at the top are pertinent. if (!parent.isValid()) { for (int row = start; row <= end; ++row) { auto refId = mReferenceables.getId(row); markDirtyDependency(refId); } } }
void ActorAdapter::handleReferenceablesRemoved(const QModelIndex& parent, int start, int end) { // Changes specified in handleReferenceablesAboutToBeRemoved updateDirty(); }
void ActorAdapter::handleRacesInserted(const QModelIndex& parent, int start, int end) { // Only rows added at the top are pertinent. if (!parent.isValid()) { for (int row = start; row <= end; ++row) { auto raceId = mReferenceables.getId(row); markDirtyDependency(raceId); } }
// Update affected updateDirty(); }
void ActorAdapter::handleRaceChanged(const QModelIndex& topLeft, const QModelIndex& botRight) { int start = getHighestIndex(topLeft).row(); int end = getHighestIndex(botRight).row();
// A change to record status (ex. Deleted) returns an invalid botRight if (end == -1) end = start;
for (int row = start; row <= end; ++row) { auto raceId = mRaces.getId(row); markDirtyDependency(raceId); }
// Update affected updateDirty(); }
void ActorAdapter::handleRacesAboutToBeRemoved(const QModelIndex& parent, int start, int end) { // Only changes at the top are pertinent. if (!parent.isValid()) { for (int row = start; row <= end; ++row) { auto raceId = mRaces.getId(row); markDirtyDependency(raceId); } } }
void ActorAdapter::handleRacesRemoved(const QModelIndex& parent, int start, int end) { // Changes specified in handleRacesAboutToBeRemoved updateDirty(); }
void ActorAdapter::handleBodyPartsInserted(const QModelIndex& parent, int start, int end) { // Only rows added at the top are pertinent. if (!parent.isValid()) { for (int row = start; row <= end; ++row) { // Race specified by part may need update auto& record = mBodyParts.getRecord(row); if (!record.isDeleted()) { markDirtyDependency(record.get().mRace); }
auto partId = mBodyParts.getId(row); markDirtyDependency(partId); } }
// Update affected updateDirty(); }
void ActorAdapter::handleBodyPartChanged(const QModelIndex& topLeft, const QModelIndex& botRight) { int start = getHighestIndex(topLeft).row(); int end = getHighestIndex(botRight).row();
// A change to record status (ex. Deleted) returns an invalid botRight if (end == -1) end = start;
for (int row = start; row <= end; ++row) { // Race specified by part may need update auto& record = mBodyParts.getRecord(row); if (!record.isDeleted()) { markDirtyDependency(record.get().mRace); }
// Update entries with a tracked dependency auto partId = mBodyParts.getId(row); markDirtyDependency(partId); }
// Update affected updateDirty(); }
void ActorAdapter::handleBodyPartsAboutToBeRemoved(const QModelIndex& parent, int start, int end) { // Only changes at the top are pertinent. if (!parent.isValid()) { for (int row = start; row <= end; ++row) { auto partId = mBodyParts.getId(row); markDirtyDependency(partId); } } }
void ActorAdapter::handleBodyPartsRemoved(const QModelIndex& parent, int start, int end) { // Changes specified in handleBodyPartsAboutToBeRemoved updateDirty(); }
QModelIndex ActorAdapter::getHighestIndex(QModelIndex index) const { while (index.parent().isValid()) index = index.parent(); return index; }
ActorAdapter::RaceDataPtr ActorAdapter::getRaceData(const ESM::RefId& id) { // Return cached race data if it exists RaceDataPtr data = mCachedRaces.get(id); if (data) return data;
// Create the race data data = std::make_shared<RaceData>(); setupRace(id, data); mCachedRaces.insert(id, data); return data; }
void ActorAdapter::setupActor(const ESM::RefId& id, ActorDataPtr data) { int index = mReferenceables.searchId(id); if (index == -1) { // Record does not exist data->reset_data(id); emit actorChanged(id); return; }
auto& record = mReferenceables.getRecord(index); if (record.isDeleted()) { // Record is deleted and therefore not accessible data->reset_data(id); emit actorChanged(id); return; }
const int typeColumn = mReferenceables.findColumnIndex(Columns::ColumnId_RecordType); int type = mReferenceables.getData(index, typeColumn).toInt(); if (type == UniversalId::Type_Creature) { // Valid creature record setupCreature(id, std::move(data)); emit actorChanged(id); } else if (type == UniversalId::Type_Npc) { // Valid npc record setupNpc(id, std::move(data)); emit actorChanged(id); } else { // Wrong record type data->reset_data(id); emit actorChanged(id); } }
void ActorAdapter::setupRace(const ESM::RefId& id, RaceDataPtr data) { int index = mRaces.searchId(id); if (index == -1) { // Record does not exist data->reset_data(id); return; }
auto& raceRecord = mRaces.getRecord(index); if (raceRecord.isDeleted()) { // Record is deleted, so not accessible data->reset_data(id); return; }
auto& race = raceRecord.get();
WeightsHeights scaleStats = { osg::Vec2f(race.mData.mMaleWeight, race.mData.mMaleHeight), osg::Vec2f(race.mData.mFemaleWeight, race.mData.mFemaleHeight) };
data->reset_data(id, scaleStats, race.mData.mFlags & ESM::Race::Beast);
// Setup body parts for (int i = 0; i < mBodyParts.getSize(); ++i) { auto& partRecord = mBodyParts.getRecord(i);
if (partRecord.isDeleted()) { // Record is deleted, so not accessible. continue; }
auto& part = partRecord.get(); if (part.mRace == id && part.mData.mType == ESM::BodyPart::MT_Skin && !ESM::isFirstPersonBodyPart(part)) { auto type = (ESM::BodyPart::MeshPart)part.mData.mPart; bool female = part.mData.mFlags & ESM::BodyPart::BPF_Female; if (female) data->setFemalePart(type, part.mId); else data->setMalePart(type, part.mId); } } }
void ActorAdapter::setupNpc(const ESM::RefId& id, ActorDataPtr data) { // Common setup, record is known to exist and is not deleted int index = mReferenceables.searchId(id); auto& npc = dynamic_cast<const Record<ESM::NPC>&>(mReferenceables.getRecord(index)).get();
RaceDataPtr raceData = getRaceData(npc.mRace); data->reset_data(id, "", false, !npc.isMale(), std::move(raceData));
// Add head and hair data->setPart(ESM::PRT_Head, npc.mHead, 0); data->setPart(ESM::PRT_Hair, npc.mHair, 0);
// Add inventory items for (auto& item : npc.mInventory.mList) { if (item.mCount <= 0) continue; auto itemId = item.mItem; addNpcItem(itemId, data); } }
void ActorAdapter::addNpcItem(const ESM::RefId& itemId, ActorDataPtr data) { int index = mReferenceables.searchId(itemId); if (index == -1) { // Item does not exist yet data->addOtherDependency(itemId); return; }
auto& record = mReferenceables.getRecord(index); if (record.isDeleted()) { // Item cannot be accessed yet data->addOtherDependency(itemId); return; }
// Convenience function to add a parts list to actor data auto addParts = [&](const std::vector<ESM::PartReference>& list, int priority) { for (auto& part : list) { ESM::RefId partId; auto partType = (ESM::PartReferenceType)part.mPart;
if (data->isFemale()) partId = part.mFemale; if (partId.empty()) partId = part.mMale;
data->setPart(partType, partId, priority);
// An another vanilla quirk: hide hairs if an item replaces Head part if (partType == ESM::PRT_Head) data->setPart(ESM::PRT_Hair, ESM::RefId(), priority); } };
const int typeColumn = mReferenceables.findColumnIndex(Columns::ColumnId_RecordType); int type = mReferenceables.getData(index, typeColumn).toInt(); if (type == UniversalId::Type_Armor) { auto& armor = dynamic_cast<const Record<ESM::Armor>&>(record).get(); addParts(armor.mParts.mParts, 1);
// Changing parts could affect what is picked for rendering data->addOtherDependency(itemId); } else if (type == UniversalId::Type_Clothing) { auto& clothing = dynamic_cast<const Record<ESM::Clothing>&>(record).get();
std::vector<ESM::PartReferenceType> parts; if (clothing.mData.mType == ESM::Clothing::Robe) { parts = { ESM::PRT_Groin, ESM::PRT_Skirt, ESM::PRT_RLeg, ESM::PRT_LLeg, ESM::PRT_RUpperarm, ESM::PRT_LUpperarm, ESM::PRT_RKnee, ESM::PRT_LKnee, ESM::PRT_RForearm, ESM::PRT_LForearm, ESM::PRT_Cuirass }; } else if (clothing.mData.mType == ESM::Clothing::Skirt) { parts = { ESM::PRT_Groin, ESM::PRT_RLeg, ESM::PRT_LLeg }; }
std::vector<ESM::PartReference> reservedList; for (const auto& p : parts) { ESM::PartReference pr; pr.mPart = p; reservedList.emplace_back(pr); }
int priority = static_cast<int>(parts.size()); addParts(clothing.mParts.mParts, priority); addParts(reservedList, priority);
// Changing parts could affect what is picked for rendering data->addOtherDependency(itemId); } }
void ActorAdapter::setupCreature(const ESM::RefId& id, ActorDataPtr data) { // Record is known to exist and is not deleted int index = mReferenceables.searchId(id); auto& creature = dynamic_cast<const Record<ESM::Creature>&>(mReferenceables.getRecord(index)).get();
data->reset_data(id, creature.mModel.getOriginal(), true); }
void ActorAdapter::markDirtyDependency(const ESM::RefId& dep) { for (auto raceIt : mCachedRaces) { if (raceIt->hasDependency(dep)) mDirtyRaces.emplace(raceIt->getId()); } for (auto actorIt : mCachedActors) { if (actorIt->hasDependency(dep)) mDirtyActors.emplace(actorIt->getId()); } }
void ActorAdapter::updateDirty() { // Handle races before actors, since actors are dependent on race for (auto& race : mDirtyRaces) { RaceDataPtr data = mCachedRaces.get(race); if (data) { setupRace(race, std::move(data)); // Race was changed. Need to mark actor dependencies as dirty. // Cannot use markDirtyDependency because that would invalidate // the current iterator. for (auto actorIt : mCachedActors) { if (actorIt->hasDependency(race)) mDirtyActors.emplace(actorIt->getId()); } } } mDirtyRaces.clear();
for (auto& actor : mDirtyActors) { ActorDataPtr data = mCachedActors.get(actor); if (data) { setupActor(actor, std::move(data)); } } mDirtyActors.clear(); }}