|
|
|
@ -11,94 +11,333 @@
|
|
|
|
|
|
|
|
|
|
namespace CSMWorld
|
|
|
|
|
{
|
|
|
|
|
ActorAdapter::ActorAdapter(CSMWorld::Data& data)
|
|
|
|
|
const std::string& ActorAdapter::RaceData::getId() const
|
|
|
|
|
{
|
|
|
|
|
return mId;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 std::string& ActorAdapter::RaceData::getFemalePart(ESM::PartReferenceType index) const
|
|
|
|
|
{
|
|
|
|
|
return mFemaleParts[ESM::getMeshPart(index)];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const std::string& ActorAdapter::RaceData::getMalePart(ESM::PartReferenceType index) const
|
|
|
|
|
{
|
|
|
|
|
return mMaleParts[ESM::getMeshPart(index)];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ActorAdapter::RaceData::hasDependency(const std::string& id) const
|
|
|
|
|
{
|
|
|
|
|
return mDependencies.find(id) != mDependencies.end();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ActorAdapter::RaceData::setFemalePart(ESM::BodyPart::MeshPart index, const std::string& partId)
|
|
|
|
|
{
|
|
|
|
|
mFemaleParts[index] = partId;
|
|
|
|
|
addOtherDependency(partId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ActorAdapter::RaceData::setMalePart(ESM::BodyPart::MeshPart index, const std::string& partId)
|
|
|
|
|
{
|
|
|
|
|
mMaleParts[index] = partId;
|
|
|
|
|
addOtherDependency(partId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ActorAdapter::RaceData::addOtherDependency(const std::string& id)
|
|
|
|
|
{
|
|
|
|
|
if (!id.empty()) mDependencies.emplace(id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ActorAdapter::RaceData::reset(const std::string& id)
|
|
|
|
|
{
|
|
|
|
|
mId = id;
|
|
|
|
|
for (auto& str : mFemaleParts)
|
|
|
|
|
str.clear();
|
|
|
|
|
for (auto& str : mMaleParts)
|
|
|
|
|
str.clear();
|
|
|
|
|
mDependencies.clear();
|
|
|
|
|
|
|
|
|
|
// Mark self as a dependency
|
|
|
|
|
addOtherDependency(id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const std::string& ActorAdapter::ActorData::getId() const
|
|
|
|
|
{
|
|
|
|
|
return mId;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ActorAdapter::ActorData::isFemale() const
|
|
|
|
|
{
|
|
|
|
|
return mFemale;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const std::string& ActorAdapter::ActorData::getPart(ESM::PartReferenceType index) const
|
|
|
|
|
{
|
|
|
|
|
if (mParts[index].empty() && mRaceData && mRaceData->handlesPart(index))
|
|
|
|
|
{
|
|
|
|
|
return mFemale ? mRaceData->getFemalePart(index) : mRaceData->getMalePart(index);
|
|
|
|
|
}
|
|
|
|
|
return mParts[index];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ActorAdapter::ActorData::hasDependency(const std::string& id) const
|
|
|
|
|
{
|
|
|
|
|
return mDependencies.find(id) != mDependencies.end();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ActorAdapter::ActorData::setPart(ESM::PartReferenceType index, const std::string& partId)
|
|
|
|
|
{
|
|
|
|
|
mParts[index] = partId;
|
|
|
|
|
addOtherDependency(partId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ActorAdapter::ActorData::addOtherDependency(const std::string& id)
|
|
|
|
|
{
|
|
|
|
|
if (!id.empty()) mDependencies.emplace(id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ActorAdapter::ActorData::reset(const std::string& id, bool isFemale, RaceDataPtr raceData)
|
|
|
|
|
{
|
|
|
|
|
mId = id;
|
|
|
|
|
mFemale = isFemale;
|
|
|
|
|
mRaceData = raceData;
|
|
|
|
|
for (auto& str : mParts)
|
|
|
|
|
str.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())
|
|
|
|
|
{
|
|
|
|
|
connect(data.getTableModel(UniversalId::Type_Referenceable), SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)),
|
|
|
|
|
// Setup qt slots and signals
|
|
|
|
|
QAbstractItemModel* refModel = data.getTableModel(UniversalId::Type_Referenceable);
|
|
|
|
|
connect(refModel, SIGNAL(rowsInserted(const QModelIndex&, int, int)),
|
|
|
|
|
this, SLOT(handleReferenceablesInserted(const QModelIndex&, int, int)));
|
|
|
|
|
connect(refModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)),
|
|
|
|
|
this, SLOT(handleReferenceableChanged(const QModelIndex&, const QModelIndex&)));
|
|
|
|
|
connect(data.getTableModel(UniversalId::Type_Race), SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)),
|
|
|
|
|
connect(refModel, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)),
|
|
|
|
|
this, SLOT(handleReferenceablesAboutToBeRemoved(const QModelIndex&, int, int)));
|
|
|
|
|
|
|
|
|
|
QAbstractItemModel* raceModel = data.getTableModel(UniversalId::Type_Race);
|
|
|
|
|
connect(raceModel, SIGNAL(rowsInserted(const QModelIndex&, int, int)),
|
|
|
|
|
this, SLOT(handleRacesAboutToBeRemoved(const QModelIndex&, int, int)));
|
|
|
|
|
connect(raceModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)),
|
|
|
|
|
this, SLOT(handleRaceChanged(const QModelIndex&, const QModelIndex&)));
|
|
|
|
|
connect(data.getTableModel(UniversalId::Type_BodyPart), SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)),
|
|
|
|
|
connect(raceModel, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)),
|
|
|
|
|
this, SLOT(handleRacesAboutToBeRemoved(const QModelIndex&, int, int)));
|
|
|
|
|
|
|
|
|
|
QAbstractItemModel* partModel = data.getTableModel(UniversalId::Type_BodyPart);
|
|
|
|
|
connect(partModel, SIGNAL(rowsInserted(const QModelIndex&, int, int)),
|
|
|
|
|
this, SLOT(handleBodyPartsInserted(const QModelIndex&, int, int)));
|
|
|
|
|
connect(partModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)),
|
|
|
|
|
this, SLOT(handleBodyPartChanged(const QModelIndex&, const QModelIndex&)));
|
|
|
|
|
connect(partModel, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)),
|
|
|
|
|
this, SLOT(handleBodyPartsAboutToBeRemoved(const QModelIndex&, int, int)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const ActorAdapter::ActorPartMap* ActorAdapter::getActorParts(const std::string& refId, bool create)
|
|
|
|
|
ActorAdapter::ActorDataPtr ActorAdapter::getActorData(const std::string& id)
|
|
|
|
|
{
|
|
|
|
|
auto it = mCachedActors.find(refId);
|
|
|
|
|
if (it != mCachedActors.end())
|
|
|
|
|
{
|
|
|
|
|
return &it->second.parts;
|
|
|
|
|
}
|
|
|
|
|
else if (create)
|
|
|
|
|
// Return cached actor data if it exists
|
|
|
|
|
ActorDataPtr data = mCachedActors.get(id);
|
|
|
|
|
if (data)
|
|
|
|
|
{
|
|
|
|
|
updateActor(refId);
|
|
|
|
|
return getActorParts(refId, false);
|
|
|
|
|
return data;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
|
|
|
|
|
// Create the actor data
|
|
|
|
|
data.reset(new 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())
|
|
|
|
|
{
|
|
|
|
|
return nullptr;
|
|
|
|
|
for (int row = start; row <= end; ++row)
|
|
|
|
|
{
|
|
|
|
|
std::string refId = mReferenceables.getId(row);
|
|
|
|
|
markDirtyDependency(refId);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update affected
|
|
|
|
|
updateDirty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ActorAdapter::handleReferenceableChanged(const QModelIndex& topLeft, const QModelIndex& botRight)
|
|
|
|
|
{
|
|
|
|
|
// Setup
|
|
|
|
|
const int TypeColumn = mReferenceables.findColumnIndex(CSMWorld::Columns::ColumnId_RecordType);
|
|
|
|
|
int rowStart = getHighestIndex(topLeft).row();
|
|
|
|
|
int rowEnd = getHighestIndex(botRight).row();
|
|
|
|
|
int start = getHighestIndex(topLeft).row();
|
|
|
|
|
int end = getHighestIndex(botRight).row();
|
|
|
|
|
|
|
|
|
|
// Handle each record
|
|
|
|
|
for (int row = rowStart; row <= rowEnd; ++row)
|
|
|
|
|
for (int row = start; row <= end; ++row)
|
|
|
|
|
{
|
|
|
|
|
int type = mReferenceables.getData(row, TypeColumn).toInt();
|
|
|
|
|
if (type == CSMWorld::UniversalId::Type_Creature || type == CSMWorld::UniversalId::Type_Npc)
|
|
|
|
|
std::string 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)
|
|
|
|
|
{
|
|
|
|
|
// Update the cached npc or creature
|
|
|
|
|
std::string refId = mReferenceables.getId(row);
|
|
|
|
|
if (mCachedActors.find(refId) != mCachedActors.end())
|
|
|
|
|
updateActor(refId);
|
|
|
|
|
markDirtyDependency(refId);
|
|
|
|
|
}
|
|
|
|
|
else if (type == CSMWorld::UniversalId::Type_Armor || type == CSMWorld::UniversalId::Type_Clothing)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
std::string refId = mReferenceables.getId(row);
|
|
|
|
|
updateActorsWithDependency(refId);
|
|
|
|
|
std::string raceId = mReferenceables.getId(row);
|
|
|
|
|
markDirtyDependency(raceId);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update affected
|
|
|
|
|
updateDirty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ActorAdapter::handleRaceChanged(const QModelIndex& topLeft, const QModelIndex& botRight)
|
|
|
|
|
{
|
|
|
|
|
int rowStart = getHighestIndex(topLeft).row();
|
|
|
|
|
int rowEnd = getHighestIndex(botRight).row();
|
|
|
|
|
for (int row = rowStart; row <= rowEnd; ++row)
|
|
|
|
|
int start = getHighestIndex(topLeft).row();
|
|
|
|
|
int end = getHighestIndex(botRight).row();
|
|
|
|
|
for (int row = start; row <= end; ++row)
|
|
|
|
|
{
|
|
|
|
|
std::string raceId = mRaces.getId(row);
|
|
|
|
|
updateActorsWithDependency(raceId);
|
|
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
std::string 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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string partId = mBodyParts.getId(row);
|
|
|
|
|
markDirtyDependency(partId);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update affected
|
|
|
|
|
updateDirty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ActorAdapter::handleBodyPartChanged(const QModelIndex& topLeft, const QModelIndex& botRight)
|
|
|
|
|
{
|
|
|
|
|
int rowStart = getHighestIndex(topLeft).row();
|
|
|
|
|
int rowEnd = getHighestIndex(botRight).row();
|
|
|
|
|
for (int row = rowStart; row <= rowEnd; ++row)
|
|
|
|
|
{
|
|
|
|
|
// Manually update race specified by part
|
|
|
|
|
// Race specified by part may need update
|
|
|
|
|
auto& record = mBodyParts.getRecord(row);
|
|
|
|
|
if (!record.isDeleted())
|
|
|
|
|
{
|
|
|
|
|
updateRace(record.get().mRace);
|
|
|
|
|
markDirtyDependency(record.get().mRace);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update entries with a tracked dependency
|
|
|
|
|
std::string partId = mBodyParts.getId(row);
|
|
|
|
|
updateRacesWithDependency(partId);
|
|
|
|
|
updateActorsWithDependency(partId);
|
|
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
std::string 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())
|
|
|
|
@ -111,251 +350,223 @@ namespace CSMWorld
|
|
|
|
|
return name.size() >= 4 && name.find(".1st", name.size() - 4) != std::string::npos;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ActorAdapter::RaceData& ActorAdapter::getRaceData(const std::string& raceId)
|
|
|
|
|
ActorAdapter::RaceDataPtr ActorAdapter::getRaceData(const std::string& id)
|
|
|
|
|
{
|
|
|
|
|
// Return cached race data if it exists
|
|
|
|
|
RaceDataPtr data = mCachedRaces.get(id);
|
|
|
|
|
if (data) return data;
|
|
|
|
|
|
|
|
|
|
// Create the race data
|
|
|
|
|
data.reset(new RaceData());
|
|
|
|
|
setupRace(id, data);
|
|
|
|
|
mCachedRaces.insert(id, data);
|
|
|
|
|
return data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ActorAdapter::setupActor(const std::string& id, ActorDataPtr data)
|
|
|
|
|
{
|
|
|
|
|
auto it = mCachedRaces.find(raceId);
|
|
|
|
|
if (it != mCachedRaces.end())
|
|
|
|
|
int index = mReferenceables.searchId(id);
|
|
|
|
|
if (index == -1)
|
|
|
|
|
{
|
|
|
|
|
return it->second;
|
|
|
|
|
// Record does not exist
|
|
|
|
|
data->reset(id);
|
|
|
|
|
emit actorChanged(id);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto& record = mReferenceables.getRecord(index);
|
|
|
|
|
if (record.isDeleted())
|
|
|
|
|
{
|
|
|
|
|
// Record is deleted and therefore not accessible
|
|
|
|
|
data->reset(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, data);
|
|
|
|
|
emit actorChanged(id);
|
|
|
|
|
}
|
|
|
|
|
else if (type == UniversalId::Type_Npc)
|
|
|
|
|
{
|
|
|
|
|
// Valid npc record
|
|
|
|
|
setupNpc(id, data);
|
|
|
|
|
emit actorChanged(id);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Create and find result
|
|
|
|
|
updateRace(raceId);
|
|
|
|
|
return mCachedRaces.find(raceId)->second;
|
|
|
|
|
// Wrong record type
|
|
|
|
|
data->reset(id);
|
|
|
|
|
emit actorChanged(id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ActorAdapter::updateRace(const std::string& raceId)
|
|
|
|
|
void ActorAdapter::setupRace(const std::string& id, RaceDataPtr data)
|
|
|
|
|
{
|
|
|
|
|
// Retrieve or create cache entry
|
|
|
|
|
auto raceDataIt = mCachedRaces.find(raceId);
|
|
|
|
|
if (raceDataIt == mCachedRaces.end())
|
|
|
|
|
// Common setup
|
|
|
|
|
data->reset(id);
|
|
|
|
|
|
|
|
|
|
int index = mRaces.searchId(id);
|
|
|
|
|
if (index == -1)
|
|
|
|
|
{
|
|
|
|
|
auto result = mCachedRaces.emplace(raceId, RaceData());
|
|
|
|
|
raceDataIt = result.first;
|
|
|
|
|
// Record does not exist
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto& raceRecord = mRaces.getRecord(index);
|
|
|
|
|
if (raceRecord.isDeleted())
|
|
|
|
|
{
|
|
|
|
|
// Record is deleted, so not accessible
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto& raceData = raceDataIt->second;
|
|
|
|
|
raceData.femaleParts.clear();
|
|
|
|
|
raceData.maleParts.clear();
|
|
|
|
|
raceData.dependencies.clear();
|
|
|
|
|
// TODO move stuff in actor related to race here
|
|
|
|
|
|
|
|
|
|
// Construct entry
|
|
|
|
|
// Setup body parts
|
|
|
|
|
for (int i = 0; i < mBodyParts.getSize(); ++i)
|
|
|
|
|
{
|
|
|
|
|
auto& record = mBodyParts.getRecord(i);
|
|
|
|
|
if (!record.isDeleted() && record.get().mRace == raceId)
|
|
|
|
|
{
|
|
|
|
|
auto& part = record.get();
|
|
|
|
|
std::string partId = mBodyParts.getId(i);
|
|
|
|
|
auto& partRecord = mBodyParts.getRecord(i);
|
|
|
|
|
|
|
|
|
|
// Part could affect race data
|
|
|
|
|
raceData.dependencies.emplace(part.mId, true);
|
|
|
|
|
if (partRecord.isDeleted())
|
|
|
|
|
{
|
|
|
|
|
// Record is deleted, so not accessible.
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add base types
|
|
|
|
|
if (part.mData.mType == ESM::BodyPart::MT_Skin && !is1stPersonPart(part.mId))
|
|
|
|
|
{
|
|
|
|
|
auto type = (ESM::BodyPart::MeshPart) part.mData.mPart;
|
|
|
|
|
// Note: Prefer the first part encountered for duplicates. emplace() does not overwrite
|
|
|
|
|
if (part.mData.mFlags & ESM::BodyPart::BPF_Female)
|
|
|
|
|
raceData.femaleParts.emplace(type, part.mId);
|
|
|
|
|
else
|
|
|
|
|
raceData.maleParts.emplace(type, part.mId);
|
|
|
|
|
}
|
|
|
|
|
auto& part = partRecord.get();
|
|
|
|
|
if (part.mRace == id && part.mData.mType == ESM::BodyPart::MT_Skin && !is1stPersonPart(part.mId))
|
|
|
|
|
{
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateActorsWithDependency(raceId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ActorAdapter::updateActor(const std::string& refId)
|
|
|
|
|
void ActorAdapter::setupNpc(const std::string& id, ActorDataPtr data)
|
|
|
|
|
{
|
|
|
|
|
int index = mReferenceables.searchId(refId);
|
|
|
|
|
if (index != -1)
|
|
|
|
|
// 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(id, !npc.isMale(), raceData);
|
|
|
|
|
|
|
|
|
|
// Add inventory items
|
|
|
|
|
for (auto& item : npc.mInventory.mList)
|
|
|
|
|
{
|
|
|
|
|
int typeColumn = mReferenceables.findColumnIndex(CSMWorld::Columns::ColumnId_RecordType);
|
|
|
|
|
int recordType = mReferenceables.getData(index, typeColumn).toInt();
|
|
|
|
|
if (recordType == CSMWorld::UniversalId::Type_Creature)
|
|
|
|
|
updateCreature(refId);
|
|
|
|
|
else if (recordType == CSMWorld::UniversalId::Type_Npc)
|
|
|
|
|
updateNpc(refId);
|
|
|
|
|
if (item.mCount <= 0) continue;
|
|
|
|
|
std::string itemId = item.mItem.toString();
|
|
|
|
|
addNpcItem(itemId, data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add head and hair
|
|
|
|
|
data->setPart(ESM::PRT_Head, npc.mHead);
|
|
|
|
|
data->setPart(ESM::PRT_Hair, npc.mHair);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ActorAdapter::updateNpc(const std::string& refId)
|
|
|
|
|
void ActorAdapter::addNpcItem(const std::string& itemId, ActorDataPtr data)
|
|
|
|
|
{
|
|
|
|
|
auto& record = mReferenceables.getRecord(refId);
|
|
|
|
|
|
|
|
|
|
// Retrieve record if possible
|
|
|
|
|
if (record.isDeleted())
|
|
|
|
|
int index = mReferenceables.searchId(itemId);
|
|
|
|
|
if (index == -1)
|
|
|
|
|
{
|
|
|
|
|
mCachedActors.erase(refId);
|
|
|
|
|
emit actorChanged(refId);
|
|
|
|
|
// Item does not exist yet
|
|
|
|
|
data->addOtherDependency(itemId);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
auto& npc = dynamic_cast<const Record<ESM::NPC>&>(record).get();
|
|
|
|
|
|
|
|
|
|
// Create holder for cached data
|
|
|
|
|
auto actorIt = mCachedActors.find(refId);
|
|
|
|
|
if (actorIt == mCachedActors.end())
|
|
|
|
|
auto& record = mReferenceables.getRecord(index);
|
|
|
|
|
if (record.isDeleted())
|
|
|
|
|
{
|
|
|
|
|
auto result = mCachedActors.emplace(refId, ActorData());
|
|
|
|
|
actorIt = result.first;
|
|
|
|
|
// Item cannot be accessed yet
|
|
|
|
|
data->addOtherDependency(itemId);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
auto& actorData = actorIt->second;
|
|
|
|
|
|
|
|
|
|
// Reset old data
|
|
|
|
|
actorData.parts.clear();
|
|
|
|
|
actorData.dependencies.clear();
|
|
|
|
|
|
|
|
|
|
// Look at the npc's inventory first
|
|
|
|
|
for (auto& item : npc.mInventory.mList)
|
|
|
|
|
{
|
|
|
|
|
if (item.mCount > 0)
|
|
|
|
|
// Convenience function to add a parts list to actor data
|
|
|
|
|
auto addParts = [&](const ESM::PartReferenceList& list) {
|
|
|
|
|
for (auto& part : list.mParts)
|
|
|
|
|
{
|
|
|
|
|
std::string itemId = item.mItem.toString();
|
|
|
|
|
// Handle armor and clothing
|
|
|
|
|
int index = mReferenceables.searchId(itemId);
|
|
|
|
|
if (index != -1 && !mReferenceables.getRecord(index).isDeleted())
|
|
|
|
|
{
|
|
|
|
|
auto& itemRecord = mReferenceables.getRecord(index);
|
|
|
|
|
|
|
|
|
|
int typeColumn = mReferenceables.findColumnIndex(CSMWorld::Columns::ColumnId_RecordType);
|
|
|
|
|
int recordType = mReferenceables.getData(index, typeColumn).toInt();
|
|
|
|
|
if (recordType == CSMWorld::UniversalId::Type_Armor)
|
|
|
|
|
{
|
|
|
|
|
// Changes here could affect the actor
|
|
|
|
|
actorData.dependencies.emplace(itemId, true);
|
|
|
|
|
|
|
|
|
|
// Add any parts if there is room
|
|
|
|
|
auto& armor = dynamic_cast<const Record<ESM::Armor>&>(itemRecord).get();
|
|
|
|
|
for (auto& part : armor.mParts.mParts)
|
|
|
|
|
{
|
|
|
|
|
std::string bodyPartId;
|
|
|
|
|
if (!npc.isMale())
|
|
|
|
|
bodyPartId = part.mFemale;
|
|
|
|
|
if (bodyPartId.empty())
|
|
|
|
|
bodyPartId = part.mMale;
|
|
|
|
|
|
|
|
|
|
if (!bodyPartId.empty())
|
|
|
|
|
{
|
|
|
|
|
actorData.parts.emplace(static_cast<ESM::PartReferenceType>(part.mPart), bodyPartId);
|
|
|
|
|
actorData.dependencies.emplace(bodyPartId, true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (recordType == CSMWorld::UniversalId::Type_Clothing)
|
|
|
|
|
{
|
|
|
|
|
// Changes here could affect the actor
|
|
|
|
|
actorData.dependencies.emplace(itemId, true);
|
|
|
|
|
|
|
|
|
|
// Add any parts if there is room
|
|
|
|
|
auto& clothing = dynamic_cast<const Record<ESM::Clothing>&>(itemRecord).get();
|
|
|
|
|
for (auto& part : clothing.mParts.mParts)
|
|
|
|
|
{
|
|
|
|
|
std::string bodyPartId;
|
|
|
|
|
if (!npc.isMale())
|
|
|
|
|
bodyPartId = part.mFemale;
|
|
|
|
|
if (bodyPartId.empty())
|
|
|
|
|
bodyPartId = part.mMale;
|
|
|
|
|
|
|
|
|
|
if (!bodyPartId.empty())
|
|
|
|
|
{
|
|
|
|
|
actorData.parts.emplace(static_cast<ESM::PartReferenceType>(part.mPart), bodyPartId);
|
|
|
|
|
actorData.dependencies.emplace(bodyPartId, true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
std::string partId;
|
|
|
|
|
auto partType = (ESM::PartReferenceType) part.mPart;
|
|
|
|
|
|
|
|
|
|
// Lookup cached race parts
|
|
|
|
|
auto& raceData = getRaceData(npc.mRace);
|
|
|
|
|
if (data->isFemale())
|
|
|
|
|
partId = part.mFemale;
|
|
|
|
|
if (partId.empty())
|
|
|
|
|
partId = part.mMale;
|
|
|
|
|
|
|
|
|
|
// Changes to race could affect the actor
|
|
|
|
|
actorData.dependencies.emplace(npc.mRace, true);
|
|
|
|
|
if (!partId.empty()) data->setPart(partType, partId);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Fill in the rest with race specific body parts
|
|
|
|
|
for (int i = 0; i < ESM::PRT_Count; ++i)
|
|
|
|
|
int TypeColumn = mReferenceables.findColumnIndex(Columns::ColumnId_RecordType);
|
|
|
|
|
int type = mReferenceables.getData(index, TypeColumn).toInt();
|
|
|
|
|
if (type == UniversalId::Type_Armor)
|
|
|
|
|
{
|
|
|
|
|
auto type = static_cast<ESM::PartReferenceType>(i);
|
|
|
|
|
if (actorData.parts.find(type) == actorData.parts.end())
|
|
|
|
|
{
|
|
|
|
|
switch (type)
|
|
|
|
|
{
|
|
|
|
|
case ESM::PRT_Head:
|
|
|
|
|
actorData.parts.emplace(type, npc.mHead);
|
|
|
|
|
actorData.dependencies.emplace(npc.mHead, true);
|
|
|
|
|
break;
|
|
|
|
|
case ESM::PRT_Hair:
|
|
|
|
|
actorData.parts.emplace(type, npc.mHair);
|
|
|
|
|
actorData.dependencies.emplace(npc.mHair, true);
|
|
|
|
|
break;
|
|
|
|
|
case ESM::PRT_Skirt:
|
|
|
|
|
case ESM::PRT_Shield:
|
|
|
|
|
case ESM::PRT_RPauldron:
|
|
|
|
|
case ESM::PRT_LPauldron:
|
|
|
|
|
case ESM::PRT_Weapon:
|
|
|
|
|
// No body part associated
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
{
|
|
|
|
|
std::string bodyPartId;
|
|
|
|
|
// Check female map if applicable
|
|
|
|
|
if (!npc.isMale())
|
|
|
|
|
{
|
|
|
|
|
auto partIt = raceData.femaleParts.find(ESM::getMeshPart(type));
|
|
|
|
|
if (partIt != raceData.femaleParts.end())
|
|
|
|
|
bodyPartId = partIt->second;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check male map next
|
|
|
|
|
if (bodyPartId.empty() || npc.isMale())
|
|
|
|
|
{
|
|
|
|
|
auto partIt = raceData.maleParts.find(ESM::getMeshPart(type));
|
|
|
|
|
if (partIt != raceData.maleParts.end())
|
|
|
|
|
bodyPartId = partIt->second;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add to map
|
|
|
|
|
if (!bodyPartId.empty())
|
|
|
|
|
{
|
|
|
|
|
actorData.parts.emplace(type, bodyPartId);
|
|
|
|
|
actorData.dependencies.emplace(bodyPartId, true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
auto& armor = dynamic_cast<const Record<ESM::Armor>&>(record).get();
|
|
|
|
|
addParts(armor.mParts);
|
|
|
|
|
|
|
|
|
|
// 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();
|
|
|
|
|
addParts(clothing.mParts);
|
|
|
|
|
|
|
|
|
|
// Signal change to actor
|
|
|
|
|
emit actorChanged(refId);
|
|
|
|
|
// Changing parts could affect what is picked for rendering
|
|
|
|
|
data->addOtherDependency(itemId);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ActorAdapter::updateCreature(const std::string& refId)
|
|
|
|
|
void ActorAdapter::setupCreature(const std::string& id, ActorDataPtr data)
|
|
|
|
|
{
|
|
|
|
|
// Signal change to actor
|
|
|
|
|
emit actorChanged(refId);
|
|
|
|
|
data->reset(id);
|
|
|
|
|
|
|
|
|
|
// TODO move stuff from Actor here
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ActorAdapter::updateActorsWithDependency(const std::string& id)
|
|
|
|
|
void ActorAdapter::markDirtyDependency(const std::string& dep)
|
|
|
|
|
{
|
|
|
|
|
for (auto it : mCachedActors)
|
|
|
|
|
for (auto raceIt : mCachedRaces)
|
|
|
|
|
{
|
|
|
|
|
if (raceIt->hasDependency(dep))
|
|
|
|
|
mDirtyRaces.emplace(raceIt->getId());
|
|
|
|
|
}
|
|
|
|
|
for (auto actorIt : mCachedActors)
|
|
|
|
|
{
|
|
|
|
|
auto& deps = it.second.dependencies;
|
|
|
|
|
if (deps.find(id) != deps.end())
|
|
|
|
|
updateActor(it.first);
|
|
|
|
|
if (actorIt->hasDependency(dep))
|
|
|
|
|
mDirtyActors.emplace(actorIt->getId());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ActorAdapter::updateRacesWithDependency(const std::string& id)
|
|
|
|
|
void ActorAdapter::updateDirty()
|
|
|
|
|
{
|
|
|
|
|
for (auto it : mCachedRaces)
|
|
|
|
|
// Handle races before actors, since actors are dependent on race
|
|
|
|
|
for (auto& race : mDirtyRaces)
|
|
|
|
|
{
|
|
|
|
|
auto& deps = it.second.dependencies;
|
|
|
|
|
if (deps.find(id) != deps.end())
|
|
|
|
|
updateRace(it.first);
|
|
|
|
|
RaceDataPtr data = mCachedRaces.get(race);
|
|
|
|
|
if (data)
|
|
|
|
|
{
|
|
|
|
|
setupRace(race, data);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
mDirtyRaces.clear();
|
|
|
|
|
|
|
|
|
|
for (auto& actor : mDirtyActors)
|
|
|
|
|
{
|
|
|
|
|
ActorDataPtr data = mCachedActors.get(actor);
|
|
|
|
|
if (data)
|
|
|
|
|
{
|
|
|
|
|
setupActor(actor, data);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
mDirtyActors.clear();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|