From 870bb491af6ddf01e8706d6c0dafd64c03659a7a Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 31 Jul 2015 09:05:26 +1000 Subject: [PATCH] Fix for issue #6 (https://github.com/cc9cii/openmw/issues/6) where dialogue subview for editing an NPC fails with an "invalid ID" exception. * NPC autocalc code was looking for non-existent values of race and class, this is now validated first. * Also took the opportunity to grey out the spells table when auto-calculated. The template specialisation is a bit ugly, though. --- apps/opencs/model/world/data.cpp | 14 ++- apps/opencs/model/world/refidadapterimp.cpp | 126 +++++++++++++++++++- apps/opencs/model/world/refidadapterimp.hpp | 43 ------- apps/opencs/view/world/nestedtable.cpp | 31 +++-- 4 files changed, 145 insertions(+), 69 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index c7e676361..648761b11 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -552,7 +552,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mMetaData.addColumn (new FormatColumn); mMetaData.addColumn (new AuthorColumn); mMetaData.addColumn (new FileDescriptionColumn); - + addModel (new IdTable (&mGlobals), UniversalId::Type_Global); addModel (new IdTable (&mGmsts), UniversalId::Type_Gmst); addModel (new IdTable (&mSkills), UniversalId::Type_Skill); @@ -968,7 +968,7 @@ int CSMWorld::Data::startLoading (const boost::filesystem::path& path, bool base mMetaData.setRecord (0, Record (RecordBase::State_ModifiedOnly, 0, &metaData)); } - + return mReader->getRecordCount(); } @@ -1419,8 +1419,14 @@ CSMWorld::NpcStats* CSMWorld::Data::npcAutoCalculate(const ESM::NPC& npc) const if (cachedStats) return cachedStats; - const ESM::Race *race = &mRaces.getRecord(npc.mRace).get(); - const ESM::Class *class_ = &mClasses.getRecord(npc.mClass).get(); + int raceIndex = mRaces.searchId(npc.mRace); + int classIndex = mClasses.searchId(npc.mClass); + // this can happen when creating a new game from scratch + if (raceIndex == -1 || classIndex == -1) + return 0; + + const ESM::Race *race = &mRaces.getRecord(raceIndex).get(); + const ESM::Class *class_ = &mClasses.getRecord(classIndex).get(); bool autoCalc = npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS; short level = npc.mNpdt52.mLevel; diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index 2bb980bfe..16579c2c7 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -638,6 +638,11 @@ void CSMWorld::NpcRefIdAdapter::setData (const RefIdColumn *column, RefIdData& d if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) { CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc); + if (!stats) + { + record.setModified (npc); + return; + } // update npc npc.mNpdtType = ESM::NPC::NPC_DEFAULT; @@ -755,6 +760,8 @@ QVariant CSMWorld::NpcAttributesRefIdAdapter::getNestedData (const RefIdColumn * if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) { CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc); + if (!stats) + return QVariant(); switch (subRowIndex) { @@ -885,6 +892,9 @@ QVariant CSMWorld::NpcSkillsRefIdAdapter::getNestedData (const RefIdColumn *colu if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) { CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc); + if (!stats) + return QVariant(); + return static_cast(stats->getBaseSkill(subRowIndex)); } else @@ -981,17 +991,23 @@ QVariant CSMWorld::NpcMiscRefIdAdapter::getNestedData (const RefIdColumn *column } case 2: { - UserInt i(stats->getHealth()); + UserInt i(0); + if (stats) + i = UserInt(stats->getHealth()); return QVariant(QVariant::fromValue(i)); } case 3: { - UserInt i(stats->getMana()); + UserInt i(0); + if (stats) + i = UserInt(stats->getMana()); return QVariant(QVariant::fromValue(i)); } case 4: { - UserInt i(stats->getFatigue()); + UserInt i(0); + if (stats) + i = UserInt(stats->getFatigue()); return QVariant(QVariant::fromValue(i)); } case 5: return static_cast(record.get().mNpdt12.mDisposition); @@ -1164,6 +1180,54 @@ void CSMWorld::WeaponRefIdAdapter::setData (const RefIdColumn *column, RefIdData namespace CSMWorld { +template<> +QVariant ActorRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, + int index) const +{ + const Record& record = static_cast&> ( + data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); + + if (column==mActors.mHasAi) + return record.get().mHasAI!=0; + + if (column==mActors.mHello) + return record.get().mAiData.mHello; + + if (column==mActors.mFlee) + return record.get().mAiData.mFlee; + + if (column==mActors.mFight) + return record.get().mAiData.mFight; + + if (column==mActors.mAlarm) + return record.get().mAiData.mAlarm; + + if (column==mActors.mInventory) + return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + + if (column==mActors.mSpells) + { + if ((record.get().mFlags & ESM::NPC::Autocalc) != 0) + return QVariant(QVariant::UserType); + else + return true; + } + + if (column==mActors.mDestinations) + return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + + if (column==mActors.mAiPackages) + return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + + std::map::const_iterator iter = + mActors.mServices.find (column); + + if (iter!=mActors.mServices.end()) + return (record.get().mAiData.mServices & iter->second)!=0; + + return NameRefIdAdapter::getData (column, data, index); +} + template <> void NestedSpellRefIdAdapter::addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const @@ -1259,7 +1323,11 @@ QVariant NestedSpellRefIdAdapter::getNestedData (const RefIdColumn *co const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); - const std::vector& spells = mData.npcAutoCalculate(record.get())->spells(); + CSMWorld::NpcStats *stats = mData.npcAutoCalculate(record.get()); + if (!stats) + return QVariant(); + + const std::vector& spells = stats->spells(); if (subRowIndex < 0 || subRowIndex >= static_cast (spells.size())) throw std::runtime_error ("index out of range"); @@ -1289,8 +1357,54 @@ int NestedSpellRefIdAdapter::getNestedRowsCount(const RefIdColumn *col const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); - const std::vector spells = mData.npcAutoCalculate(record.get())->spells(); - return static_cast(spells.size()); + CSMWorld::NpcStats *stats = mData.npcAutoCalculate(record.get()); + if (!stats) + return 0; + + return static_cast(stats->spells().size()); +} + +template<> +QVariant ActorRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, + int index) const +{ + const Record& record = static_cast&> ( + data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); + + if (column==mActors.mHasAi) + return record.get().mHasAI!=0; + + if (column==mActors.mHello) + return record.get().mAiData.mHello; + + if (column==mActors.mFlee) + return record.get().mAiData.mFlee; + + if (column==mActors.mFight) + return record.get().mAiData.mFight; + + if (column==mActors.mAlarm) + return record.get().mAiData.mAlarm; + + if (column==mActors.mInventory) + return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + + if (column==mActors.mSpells) + return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + + if (column==mActors.mDestinations) + return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + + if (column==mActors.mAiPackages) + return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() + + std::map::const_iterator iter = + mActors.mServices.find (column); + + if (iter!=mActors.mServices.end()) + return (record.get().mAiData.mServices & iter->second)!=0; + + return NameRefIdAdapter::getData (column, data, index); } template <> diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index 4a8288fa4..9fc296231 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -523,49 +523,6 @@ namespace CSMWorld : NameRefIdAdapter (type, columns), mActors (columns) {} - template - QVariant ActorRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, - int index) const - { - const Record& record = static_cast&> ( - data.getRecord (RefIdData::LocalIndex (index, BaseRefIdAdapter::getType()))); - - if (column==mActors.mHasAi) - return record.get().mHasAI!=0; - - if (column==mActors.mHello) - return record.get().mAiData.mHello; - - if (column==mActors.mFlee) - return record.get().mAiData.mFlee; - - if (column==mActors.mFight) - return record.get().mAiData.mFight; - - if (column==mActors.mAlarm) - return record.get().mAiData.mAlarm; - - if (column==mActors.mInventory) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() - - if (column==mActors.mSpells) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() - - if (column==mActors.mDestinations) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() - - if (column==mActors.mAiPackages) - return true; // to show nested tables in dialogue subview, see IdTree::hasChildren() - - std::map::const_iterator iter = - mActors.mServices.find (column); - - if (iter!=mActors.mServices.end()) - return (record.get().mAiData.mServices & iter->second)!=0; - - return NameRefIdAdapter::getData (column, data, index); - } - template void ActorRefIdAdapter::setData (const RefIdColumn *column, RefIdData& data, int index, const QVariant& value) const diff --git a/apps/opencs/view/world/nestedtable.cpp b/apps/opencs/view/world/nestedtable.cpp index de3b3aa16..89b171891 100644 --- a/apps/opencs/view/world/nestedtable.cpp +++ b/apps/opencs/view/world/nestedtable.cpp @@ -22,6 +22,8 @@ CSVWorld::NestedTable::NestedTable(CSMDoc::Document& document, mEditIdAction(0), mModel(model) { + mDispatcher = new CSMWorld::CommandDispatcher (document, id, this); + setSelectionBehavior (QAbstractItemView::SelectRows); setSelectionMode (QAbstractItemView::ExtendedSelection); @@ -34,26 +36,23 @@ CSVWorld::NestedTable::NestedTable(CSMDoc::Document& document, int columns = model->columnCount(QModelIndex()); - setModel(model); + for(int i = 0 ; i < columns; ++i) + { + CSMWorld::ColumnBase::Display display = static_cast ( + model->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); - setAcceptDrops(true); + CommandDelegate *delegate = CommandDelegateFactoryCollection::get().makeDelegate(display, + mDispatcher, + document, + this); + + setItemDelegateForColumn(i, delegate); + } + + setModel(model); if (editable) { - mDispatcher = new CSMWorld::CommandDispatcher (document, id, this); - for(int i = 0 ; i < columns; ++i) - { - CSMWorld::ColumnBase::Display display = static_cast ( - model->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); - - CommandDelegate *delegate = CommandDelegateFactoryCollection::get().makeDelegate(display, - mDispatcher, - document, - this); - - setItemDelegateForColumn(i, delegate); - } - mAddNewRowAction = new QAction (tr ("Add new row"), this); connect(mAddNewRowAction, SIGNAL(triggered()),