diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 6401e4222..74f311c5f 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -26,7 +26,7 @@ opencs_units_noqt (model/world universalid record commands columnbase columnimp scriptcontext cell refidcollection refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection tablemimedata cellcoordinates cellselection resources resourcesmanager scope pathgrid landtexture land nestedtablewrapper nestedcollection nestedcoladapterimp nestedinfocollection - idcompletionmanager metadata defaultgmsts + idcompletionmanager metadata defaultgmsts infoselectwrapper ) opencs_hdrs_noqt (model/world @@ -42,7 +42,7 @@ opencs_units_noqt (model/tools mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck birthsigncheck spellcheck referencecheck referenceablecheck scriptcheck bodypartcheck startscriptcheck search searchoperation searchstage pathgridcheck soundgencheck magiceffectcheck - mergestages gmstcheck + mergestages gmstcheck topicinfocheck journalcheck ) opencs_hdrs_noqt (model/tools diff --git a/apps/opencs/model/tools/journalcheck.cpp b/apps/opencs/model/tools/journalcheck.cpp new file mode 100644 index 000000000..bdd14ddf0 --- /dev/null +++ b/apps/opencs/model/tools/journalcheck.cpp @@ -0,0 +1,79 @@ +#include "journalcheck.hpp" + +#include +#include + +CSMTools::JournalCheckStage::JournalCheckStage(const CSMWorld::IdCollection &journals, + const CSMWorld::InfoCollection& journalInfos) + : mJournals(journals), mJournalInfos(journalInfos) +{} + +int CSMTools::JournalCheckStage::setup() +{ + return mJournals.getSize(); +} + +void CSMTools::JournalCheckStage::perform(int stage, CSMDoc::Messages& messages) +{ + const CSMWorld::Record &journalRecord = mJournals.getRecord(stage); + + if (journalRecord.isDeleted()) + return; + + const ESM::Dialogue &journal = journalRecord.get(); + int statusNamedCount = 0; + int totalInfoCount = 0; + std::set questIndices; + + CSMWorld::InfoCollection::Range range = mJournalInfos.getTopicRange(journal.mId); + + for (CSMWorld::InfoCollection::RecordConstIterator it = range.first; it != range.second; ++it) + { + const CSMWorld::Record infoRecord = (*it); + + if (infoRecord.isDeleted()) + continue; + + const CSMWorld::Info& journalInfo = infoRecord.get(); + + totalInfoCount += 1; + + if (journalInfo.mQuestStatus == ESM::DialInfo::QS_Name) + { + statusNamedCount += 1; + } + + if (journalInfo.mResponse.empty()) + { + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_JournalInfo, journalInfo.mId); + + messages.add(id, "Journal Info: missing description", "", CSMDoc::Message::Severity_Warning); + } + + std::pair::iterator, bool> result = questIndices.insert(journalInfo.mData.mJournalIndex); + + // Duplicate index + if (result.second == false) + { + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_JournalInfo, journalInfo.mId); + + std::ostringstream stream; + stream << "Journal: duplicated quest index " << journalInfo.mData.mJournalIndex; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); + } + } + + if (totalInfoCount == 0) + { + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Journal, journal.mId); + + messages.add(id, "Journal: no defined Journal Infos", "", CSMDoc::Message::Severity_Warning); + } + else if (statusNamedCount > 1) + { + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Journal, journal.mId); + + messages.add(id, "Journal: multiple infos with quest status \"Named\"", "", CSMDoc::Message::Severity_Error); + } +} diff --git a/apps/opencs/model/tools/journalcheck.hpp b/apps/opencs/model/tools/journalcheck.hpp new file mode 100644 index 000000000..c9f619698 --- /dev/null +++ b/apps/opencs/model/tools/journalcheck.hpp @@ -0,0 +1,35 @@ +#ifndef CSM_TOOLS_JOURNALCHECK_H +#define CSM_TOOLS_JOURNALCHECK_H + +#include + +#include "../world/idcollection.hpp" +#include "../world/infocollection.hpp" + +#include "../doc/stage.hpp" + +namespace CSMTools +{ + /// \brief VerifyStage: make sure that journal infos are good + class JournalCheckStage : public CSMDoc::Stage + { + public: + + JournalCheckStage(const CSMWorld::IdCollection& journals, + const CSMWorld::InfoCollection& journalInfos); + + virtual int setup(); + ///< \return number of steps + + virtual void perform(int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages + + private: + + const CSMWorld::IdCollection& mJournals; + const CSMWorld::InfoCollection& mJournalInfos; + + }; +} + +#endif diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp index e750092b9..f538a716e 100644 --- a/apps/opencs/model/tools/tools.cpp +++ b/apps/opencs/model/tools/tools.cpp @@ -30,6 +30,8 @@ #include "magiceffectcheck.hpp" #include "mergeoperation.hpp" #include "gmstcheck.hpp" +#include "topicinfocheck.hpp" +#include "journalcheck.hpp" CSMDoc::OperationHolder *CSMTools::Tools::get (int type) { @@ -111,9 +113,24 @@ CSMDoc::OperationHolder *CSMTools::Tools::getVerifier() mData.getReferenceables(), mData.getResources (CSMWorld::UniversalId::Type_Icons), mData.getResources (CSMWorld::UniversalId::Type_Textures))); - + mVerifierOperation->appendStage (new GmstCheckStage (mData.getGmsts())); + mVerifierOperation->appendStage (new TopicInfoCheckStage (mData.getTopicInfos(), + mData.getCells(), + mData.getClasses(), + mData.getFactions(), + mData.getGmsts(), + mData.getGlobals(), + mData.getJournals(), + mData.getRaces(), + mData.getRegions(), + mData.getTopics(), + mData.getReferenceables().getDataSet(), + mData.getResources (CSMWorld::UniversalId::Type_SoundsRes))); + + mVerifierOperation->appendStage (new JournalCheckStage(mData.getJournals(), mData.getJournalInfos())); + mVerifier.setOperation (mVerifierOperation); } diff --git a/apps/opencs/model/tools/topicinfocheck.cpp b/apps/opencs/model/tools/topicinfocheck.cpp new file mode 100644 index 000000000..f6af2a945 --- /dev/null +++ b/apps/opencs/model/tools/topicinfocheck.cpp @@ -0,0 +1,441 @@ +#include "topicinfocheck.hpp" + +#include + +#include "../world/infoselectwrapper.hpp" + +CSMTools::TopicInfoCheckStage::TopicInfoCheckStage( + const CSMWorld::InfoCollection& topicInfos, + const CSMWorld::IdCollection& cells, + const CSMWorld::IdCollection& classes, + const CSMWorld::IdCollection& factions, + const CSMWorld::IdCollection& gmsts, + const CSMWorld::IdCollection& globals, + const CSMWorld::IdCollection& journals, + const CSMWorld::IdCollection& races, + const CSMWorld::IdCollection& regions, + const CSMWorld::IdCollection &topics, + const CSMWorld::RefIdData& referencables, + const CSMWorld::Resources& soundFiles) + : mTopicInfos(topicInfos), + mCells(cells), + mClasses(classes), + mFactions(factions), + mGameSettings(gmsts), + mGlobals(globals), + mJournals(journals), + mRaces(races), + mRegions(regions), + mTopics(topics), + mReferencables(referencables), + mSoundFiles(soundFiles) +{} + +int CSMTools::TopicInfoCheckStage::setup() +{ + // Generate list of cell names for reference checking + + mCellNames.clear(); + for (int i = 0; i < mCells.getSize(); ++i) + { + const CSMWorld::Record& cellRecord = mCells.getRecord(i); + + if (cellRecord.isDeleted()) + continue; + + mCellNames.insert(cellRecord.get().mName); + } + // Cell names can also include region names + for (int i = 0; i < mRegions.getSize(); ++i) + { + const CSMWorld::Record& regionRecord = mRegions.getRecord(i); + + if (regionRecord.isDeleted()) + continue; + + mCellNames.insert(regionRecord.get().mName); + } + // Default cell name + int index = mGameSettings.searchId("sDefaultCellname"); + if (index != -1) + { + const CSMWorld::Record& gmstRecord = mGameSettings.getRecord(index); + + if (!gmstRecord.isDeleted() && gmstRecord.get().mValue.getType() == ESM::VT_String) + { + mCellNames.insert(gmstRecord.get().mValue.getString()); + } + } + + return mTopicInfos.getSize(); +} + +void CSMTools::TopicInfoCheckStage::perform(int stage, CSMDoc::Messages& messages) +{ + const CSMWorld::Record& infoRecord = mTopicInfos.getRecord(stage); + + if (infoRecord.isDeleted()) + return; + + const CSMWorld::Info& topicInfo = infoRecord.get(); + + // There should always be a topic that matches + int topicIndex = mTopics.searchId(topicInfo.mTopicId); + + const CSMWorld::Record& topicRecord = mTopics.getRecord(topicIndex); + + if (topicRecord.isDeleted()) + return; + + const ESM::Dialogue& topic = topicRecord.get(); + + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_TopicInfo, topicInfo.mId); + + // Check fields + + if (!topicInfo.mActor.empty()) + { + verifyActor(topicInfo.mActor, id, messages); + } + + if (!topicInfo.mClass.empty()) + { + verifyId(topicInfo.mClass, mClasses, id, messages); + } + + if (!topicInfo.mCell.empty()) + { + verifyCell(topicInfo.mCell, id, messages); + } + + if (!topicInfo.mFaction.empty()) + { + if (verifyId(topicInfo.mFaction, mFactions, id, messages)) + { + verifyFactionRank(topicInfo.mFaction, topicInfo.mData.mRank, id, messages); + } + } + + if (!topicInfo.mPcFaction.empty()) + { + if (verifyId(topicInfo.mPcFaction, mFactions, id, messages)) + { + verifyFactionRank(topicInfo.mPcFaction, topicInfo.mData.mPCrank, id, messages); + } + } + + if (topicInfo.mData.mGender < -1 || topicInfo.mData.mGender > 1) + { + std::ostringstream stream; + messages.add(id, "Gender: Value is invalid", "", CSMDoc::Message::Severity_Error); + } + + if (!topicInfo.mRace.empty()) + { + verifyId(topicInfo.mRace, mRaces, id, messages); + } + + if (!topicInfo.mSound.empty()) + { + verifySound(topicInfo.mSound, id, messages); + } + + if (topicInfo.mResponse.empty() && topic.mType != ESM::Dialogue::Voice) + { + messages.add(id, "Response is empty", "", CSMDoc::Message::Severity_Warning); + } + + // Check info conditions + + for (std::vector::const_iterator it = topicInfo.mSelects.begin(); + it != topicInfo.mSelects.end(); ++it) + { + verifySelectStruct((*it), id, messages); + } +} + +// Verification functions + +bool CSMTools::TopicInfoCheckStage::verifyActor(const std::string& actor, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages) +{ + const std::string specifier = "Actor"; + + CSMWorld::RefIdData::LocalIndex index = mReferencables.searchId(actor); + + if (index.first == -1) + { + writeMissingIdError(specifier, actor, id, messages); + return false; + } + else if (mReferencables.getRecord(index).isDeleted()) + { + writeDeletedRecordError(specifier, actor, id, messages); + return false; + } + else if (index.second != CSMWorld::UniversalId::Type_Npc && index.second != CSMWorld::UniversalId::Type_Creature) + { + writeInvalidTypeError(specifier, actor, index.second, "NPC or Creature", id, messages); + return false; + } + + return true; +} + +bool CSMTools::TopicInfoCheckStage::verifyCell(const std::string& cell, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages) +{ + const std::string specifier = "Cell"; + + if (mCellNames.find(cell) == mCellNames.end()) + { + writeMissingIdError(specifier, cell, id, messages); + return false; + } + + return true; +} + +bool CSMTools::TopicInfoCheckStage::verifyFactionRank(const std::string& factionName, int rank, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages) +{ + if (rank < -1) + { + std::ostringstream stream; + stream << "Rank or PC Rank is set to " << rank << ", but should be set to -1 if no rank is required"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); + return false; + } + + int index = mFactions.searchId(factionName); + + const ESM::Faction &faction = mFactions.getRecord(index).get(); + + int limit = 0; + for (; limit < 10; ++limit) + { + if (faction.mRanks[limit].empty()) + break; + } + + if (rank >= limit) + { + std::ostringstream stream; + stream << "Rank or PC Rank is set to " << rank << " which is more than the maximum of " << limit - 1 + << " for the " << factionName << " faction"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); + return false; + } + + return true; +} + +bool CSMTools::TopicInfoCheckStage::verifyItem(const std::string& item, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages) +{ + const std::string specifier = "Item"; + + CSMWorld::RefIdData::LocalIndex index = mReferencables.searchId(item); + + if (index.first == -1) + { + writeMissingIdError(specifier, item, id, messages); + return false; + } + else if (mReferencables.getRecord(index).isDeleted()) + { + writeDeletedRecordError(specifier, item, id, messages); + return false; + } + else + { + switch (index.second) + { + case CSMWorld::UniversalId::Type_Potion: + case CSMWorld::UniversalId::Type_Apparatus: + case CSMWorld::UniversalId::Type_Armor: + case CSMWorld::UniversalId::Type_Book: + case CSMWorld::UniversalId::Type_Clothing: + case CSMWorld::UniversalId::Type_Ingredient: + case CSMWorld::UniversalId::Type_Light: + case CSMWorld::UniversalId::Type_Lockpick: + case CSMWorld::UniversalId::Type_Miscellaneous: + case CSMWorld::UniversalId::Type_Probe: + case CSMWorld::UniversalId::Type_Repair: + case CSMWorld::UniversalId::Type_Weapon: + case CSMWorld::UniversalId::Type_ItemLevelledList: + break; + + default: + writeInvalidTypeError(specifier, item, index.second, "Potion, Armor, Book, etc.", id, messages); + return false; + } + } + + return true; +} + +bool CSMTools::TopicInfoCheckStage::verifySelectStruct(const ESM::DialInfo::SelectStruct& select, + const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) +{ + CSMWorld::ConstInfoSelectWrapper infoCondition(select); + + if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_None) + { + messages.add(id, "Invalid Info Condition: " + infoCondition.toString(), "", CSMDoc::Message::Severity_Error); + return false; + } + else if (!infoCondition.variantTypeIsValid()) + { + std::ostringstream stream; + stream << "Info Condition: Value for \"" << infoCondition.toString() << "\" has a type of "; + + switch (select.mValue.getType()) + { + case ESM::VT_None: stream << "None"; break; + case ESM::VT_Short: stream << "Short"; break; + case ESM::VT_Int: stream << "Int"; break; + case ESM::VT_Long: stream << "Long"; break; + case ESM::VT_Float: stream << "Float"; break; + case ESM::VT_String: stream << "String"; break; + default: stream << "Unknown"; break; + } + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); + return false; + } + else if (infoCondition.conditionIsAlwaysTrue()) + { + std::ostringstream stream; + stream << "Info Condition: " << infoCondition.toString() << " is always true"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Warning); + return false; + } + else if (infoCondition.conditionIsNeverTrue()) + { + std::ostringstream stream; + stream << "Info Condition: " << infoCondition.toString() << " is never true"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Warning); + return false; + } + + // Id checks + if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Global && + !verifyId(infoCondition.getVariableName(), mGlobals, id, messages)) + { + return false; + } + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Journal && + !verifyId(infoCondition.getVariableName(), mJournals, id, messages)) + { + return false; + } + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Item && + !verifyItem(infoCondition.getVariableName(), id, messages)) + { + return false; + } + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Dead && + !verifyActor(infoCondition.getVariableName(), id, messages)) + { + return false; + } + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotId && + !verifyActor(infoCondition.getVariableName(), id, messages)) + { + return false; + } + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotFaction && + !verifyId(infoCondition.getVariableName(), mFactions, id, messages)) + { + return false; + } + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotClass && + !verifyId(infoCondition.getVariableName(), mClasses, id, messages)) + { + return false; + } + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotRace && + !verifyId(infoCondition.getVariableName(), mRaces, id, messages)) + { + return false; + } + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotCell && + !verifyCell(infoCondition.getVariableName(), id, messages)) + { + return false; + } + + return true; +} + +bool CSMTools::TopicInfoCheckStage::verifySound(const std::string& sound, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages) +{ + const std::string specifier = "Sound File"; + + if (mSoundFiles.searchId(sound) == -1) + { + writeMissingIdError(specifier, sound, id, messages); + return false; + } + + return true; +} + +template +bool CSMTools::TopicInfoCheckStage::verifyId(const std::string& name, const CSMWorld::IdCollection& collection, + const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) +{ + int index = collection.searchId(name); + + if (index == -1) + { + writeMissingIdError(T::getRecordType(), name, id, messages); + return false; + } + else if (collection.getRecord(index).isDeleted()) + { + writeDeletedRecordError(T::getRecordType(), name, id, messages); + return false; + } + + return true; +} + +// Error functions + +void CSMTools::TopicInfoCheckStage::writeMissingIdError(const std::string& specifier, const std::string& missingId, + const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) +{ + std::ostringstream stream; + stream << specifier << ": ID or name \"" << missingId << "\" could not be found"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); +} + +void CSMTools::TopicInfoCheckStage::writeDeletedRecordError(const std::string& specifier, const std::string& recordId, + const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) +{ + std::ostringstream stream; + stream << specifier << ": Deleted record with ID \"" << recordId << "\" is being referenced"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); +} + +void CSMTools::TopicInfoCheckStage::writeInvalidTypeError(const std::string& specifier, const std::string& invalidId, + CSMWorld::UniversalId::Type invalidType, const std::string& expectedType, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages) +{ + CSMWorld::UniversalId tempId(invalidType, invalidId); + + std::ostringstream stream; + stream << specifier << ": invalid type of " << tempId.getTypeName() << " was found for referencable \"" + << invalidId << "\" (can be of type " << expectedType << ")"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); +} diff --git a/apps/opencs/model/tools/topicinfocheck.hpp b/apps/opencs/model/tools/topicinfocheck.hpp new file mode 100644 index 000000000..510901dac --- /dev/null +++ b/apps/opencs/model/tools/topicinfocheck.hpp @@ -0,0 +1,95 @@ +#ifndef CSM_TOOLS_TOPICINFOCHECK_HPP +#define CSM_TOOLS_TOPICINFOCHECK_HPP + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "../world/cell.hpp" +#include "../world/idcollection.hpp" +#include "../world/infocollection.hpp" +#include "../world/refiddata.hpp" +#include "../world/resources.hpp" + +#include "../doc/stage.hpp" + +namespace CSMTools +{ + /// \brief VerifyStage: check topics + class TopicInfoCheckStage : public CSMDoc::Stage + { + public: + + TopicInfoCheckStage( + const CSMWorld::InfoCollection& topicInfos, + const CSMWorld::IdCollection& cells, + const CSMWorld::IdCollection& classes, + const CSMWorld::IdCollection& factions, + const CSMWorld::IdCollection& gmsts, + const CSMWorld::IdCollection& globals, + const CSMWorld::IdCollection& journals, + const CSMWorld::IdCollection& races, + const CSMWorld::IdCollection& regions, + const CSMWorld::IdCollection& topics, + const CSMWorld::RefIdData& referencables, + const CSMWorld::Resources& soundFiles); + + virtual int setup(); + ///< \return number of steps + + virtual void perform(int step, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages + + private: + + const CSMWorld::InfoCollection& mTopicInfos; + + const CSMWorld::IdCollection& mCells; + const CSMWorld::IdCollection& mClasses; + const CSMWorld::IdCollection& mFactions; + const CSMWorld::IdCollection& mGameSettings; + const CSMWorld::IdCollection& mGlobals; + const CSMWorld::IdCollection& mJournals; + const CSMWorld::IdCollection& mRaces; + const CSMWorld::IdCollection& mRegions; + const CSMWorld::IdCollection& mTopics; + + const CSMWorld::RefIdData& mReferencables; + const CSMWorld::Resources& mSoundFiles; + + std::set mCellNames; + + // These return false when not successful and write an error + bool verifyActor(const std::string& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + bool verifyCell(const std::string& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + bool verifyFactionRank(const std::string& name, int rank, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages); + bool verifyItem(const std::string& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + bool verifySelectStruct(const ESM::DialInfo::SelectStruct& select, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages); + bool verifySound(const std::string& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + + template + bool verifyId(const std::string& name, const CSMWorld::IdCollection& collection, + const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + + // Common error messages + void writeMissingIdError(const std::string& specifier, const std::string& missingId, + const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + + void writeDeletedRecordError(const std::string& specifier, const std::string& recordId, + const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + + void writeInvalidTypeError(const std::string& specifier, const std::string& invalidId, + CSMWorld::UniversalId::Type invalidType, const std::string& expectedType, + const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + }; +} + +#endif diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index d0d3a1671..32aa7edca 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -3,6 +3,7 @@ #include #include "universalid.hpp" +#include "infoselectwrapper.hpp" namespace CSMWorld { @@ -273,8 +274,8 @@ namespace CSMWorld { ColumnId_InfoList, "Info List" }, { ColumnId_InfoCondition, "Info Conditions" }, { ColumnId_InfoCondFunc, "Function" }, - { ColumnId_InfoCondVar, "Func/Variable" }, - { ColumnId_InfoCondComp, "Comp" }, + { ColumnId_InfoCondVar, "Variable/Object" }, + { ColumnId_InfoCondComp, "Relation" }, { ColumnId_InfoCondValue, "Values" }, { ColumnId_OriginalCell, "Original Cell" }, @@ -546,18 +547,6 @@ namespace "AI Wander", "AI Travel", "AI Follow", "AI Escort", "AI Activate", 0 }; - static const char *sInfoCondFunc[] = - { - " ", "Function", "Global", "Local", "Journal", - "Item", "Dead", "Not ID", "Not Faction", "Not Class", - "Not Race", "Not Cell", "Not Local", 0 - }; - - static const char *sInfoCondComp[] = - { - "!=", "<", "<=", "=", ">", ">=", 0 - }; - const char **getEnumNames (CSMWorld::Columns::ColumnId column) { switch (column) @@ -585,10 +574,8 @@ namespace case CSMWorld::Columns::ColumnId_EffectId: return sEffectId; case CSMWorld::Columns::ColumnId_PartRefType: return sPartRefType; case CSMWorld::Columns::ColumnId_AiPackageType: return sAiPackageType; - case CSMWorld::Columns::ColumnId_InfoCondFunc: return sInfoCondFunc; - // FIXME: don't have dynamic value enum delegate, use Display_String for now - //case CSMWorld::Columns::ColumnId_InfoCond: return sInfoCond; - case CSMWorld::Columns::ColumnId_InfoCondComp: return sInfoCondComp; + case CSMWorld::Columns::ColumnId_InfoCondFunc: return CSMWorld::ConstInfoSelectWrapper::FunctionEnumStrings; + case CSMWorld::Columns::ColumnId_InfoCondComp: return CSMWorld::ConstInfoSelectWrapper::RelationEnumStrings; default: return 0; } diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 6eccb7483..b54df596c 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -271,7 +271,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc new NestedChildColumn (Columns::ColumnId_InfoCondFunc, ColumnBase::Display_InfoCondFunc)); // FIXME: don't have dynamic value enum delegate, use Display_String for now mTopicInfos.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_InfoCondVar, ColumnBase::Display_String)); + new NestedChildColumn (Columns::ColumnId_InfoCondVar, ColumnBase::Display_InfoCondVar)); mTopicInfos.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_InfoCondComp, ColumnBase::Display_InfoCondComp)); mTopicInfos.getNestableColumn(index)->addColumn( diff --git a/apps/opencs/model/world/idcompletionmanager.cpp b/apps/opencs/model/world/idcompletionmanager.cpp index 20cd8652c..7f3221342 100644 --- a/apps/opencs/model/world/idcompletionmanager.cpp +++ b/apps/opencs/model/world/idcompletionmanager.cpp @@ -60,6 +60,10 @@ std::vector CSMWorld::IdCompletionManager::getDis { types.push_back(current->first); } + + // Hack for Display_InfoCondVar + types.push_back(CSMWorld::ColumnBase::Display_InfoCondVar); + return types; } @@ -104,7 +108,7 @@ void CSMWorld::IdCompletionManager::generateCompleters(CSMWorld::Data &data) QAbstractItemView *popup = new CSVWidget::CompleterPopup(); completer->setPopup(popup); // The completer takes ownership of the popup completer->setMaxVisibleItems(10); - + mCompleters[current->first] = completer; } } diff --git a/apps/opencs/model/world/infoselectwrapper.cpp b/apps/opencs/model/world/infoselectwrapper.cpp new file mode 100644 index 000000000..d036b1965 --- /dev/null +++ b/apps/opencs/model/world/infoselectwrapper.cpp @@ -0,0 +1,893 @@ +#include "infoselectwrapper.hpp" + +#include +#include +#include + +const size_t CSMWorld::ConstInfoSelectWrapper::RuleMinSize = 5; + +const size_t CSMWorld::ConstInfoSelectWrapper::FunctionPrefixOffset = 1; +const size_t CSMWorld::ConstInfoSelectWrapper::FunctionIndexOffset = 2; +const size_t CSMWorld::ConstInfoSelectWrapper::RelationIndexOffset = 4; +const size_t CSMWorld::ConstInfoSelectWrapper::VarNameOffset = 5; + +const char* CSMWorld::ConstInfoSelectWrapper::FunctionEnumStrings[] = +{ + "Rank Low", + "Rank High", + "Rank Requirement", + "Reputation", + "Health Percent", + "PC Reputation", + "PC Level", + "PC Health Percent", + "PC Magicka", + "PC Fatigue", + "PC Strength", + "PC Block", + "PC Armorer", + "PC Medium Armor", + "PC Heavy Armor", + "PC Blunt Weapon", + "PC Long Blade", + "PC Axe", + "PC Spear", + "PC Athletics", + "PC Enchant", + "PC Detruction", + "PC Alteration", + "PC Illusion", + "PC Conjuration", + "PC Mysticism", + "PC Restoration", + "PC Alchemy", + "PC Unarmored", + "PC Security", + "PC Sneak", + "PC Acrobatics", + "PC Light Armor", + "PC Short Blade", + "PC Marksman", + "PC Merchantile", + "PC Speechcraft", + "PC Hand to Hand", + "PC Sex", + "PC Expelled", + "PC Common Disease", + "PC Blight Disease", + "PC Clothing Modifier", + "PC Crime Level", + "Same Sex", + "Same Race", + "Same Faction", + "Faction Rank Difference", + "Detected", + "Alarmed", + "Choice", + "PC Intelligence", + "PC Willpower", + "PC Agility", + "PC Speed", + "PC Endurance", + "PC Personality", + "PC Luck", + "PC Corpus", + "Weather", + "PC Vampire", + "Level", + "Attacked", + "Talked to PC", + "PC Health", + "Creature Target", + "Friend Hit", + "Fight", + "Hello", + "Alarm", + "Flee", + "Should Attack", + "Werewolf", + "PC Werewolf Kills", + "Global", + "Local", + "Journal", + "Item", + "Dead", + "Not Id", + "Not Faction", + "Not Class", + "Not Race", + "Not Cell", + "Not Local", + 0 +}; + +const char* CSMWorld::ConstInfoSelectWrapper::RelationEnumStrings[] = +{ + "=", + "!=", + ">", + ">=", + "<", + "<=", + 0 +}; + +const char* CSMWorld::ConstInfoSelectWrapper::ComparisonEnumStrings[] = +{ + "Boolean", + "Integer", + "Numeric", + 0 +}; + +// static functions + +std::string CSMWorld::ConstInfoSelectWrapper::convertToString(FunctionName name) +{ + if (name < Function_None) + return FunctionEnumStrings[name]; + else + return "(Invalid Data: Function)"; +} + +std::string CSMWorld::ConstInfoSelectWrapper::convertToString(RelationType type) +{ + if (type < Relation_None) + return RelationEnumStrings[type]; + else + return "(Invalid Data: Relation)"; +} + +std::string CSMWorld::ConstInfoSelectWrapper::convertToString(ComparisonType type) +{ + if (type < Comparison_None) + return ComparisonEnumStrings[type]; + else + return "(Invalid Data: Comparison)"; +} + +// ConstInfoSelectWrapper + +CSMWorld::ConstInfoSelectWrapper::ConstInfoSelectWrapper(const ESM::DialInfo::SelectStruct& select) + : mConstSelect(select) +{ + readRule(); +} + +CSMWorld::ConstInfoSelectWrapper::FunctionName CSMWorld::ConstInfoSelectWrapper::getFunctionName() const +{ + return mFunctionName; +} + +CSMWorld::ConstInfoSelectWrapper::RelationType CSMWorld::ConstInfoSelectWrapper::getRelationType() const +{ + return mRelationType; +} + +CSMWorld::ConstInfoSelectWrapper::ComparisonType CSMWorld::ConstInfoSelectWrapper::getComparisonType() const +{ + return mComparisonType; +} + +bool CSMWorld::ConstInfoSelectWrapper::hasVariable() const +{ + return mHasVariable; +} + +const std::string& CSMWorld::ConstInfoSelectWrapper::getVariableName() const +{ + return mVariableName; +} + +bool CSMWorld::ConstInfoSelectWrapper::conditionIsAlwaysTrue() const +{ + if (!variantTypeIsValid()) + return false; + + if (mComparisonType == Comparison_Boolean || mComparisonType == Comparison_Integer) + { + if (mConstSelect.mValue.getType() == ESM::VT_Float) + return conditionIsAlwaysTrue(getConditionFloatRange(), getValidIntRange()); + else + return conditionIsAlwaysTrue(getConditionIntRange(), getValidIntRange()); + } + else if (mComparisonType == Comparison_Numeric) + { + if (mConstSelect.mValue.getType() == ESM::VT_Float) + return conditionIsAlwaysTrue(getConditionFloatRange(), getValidFloatRange()); + else + return conditionIsAlwaysTrue(getConditionIntRange(), getValidFloatRange()); + } + + return false; +} + +bool CSMWorld::ConstInfoSelectWrapper::conditionIsNeverTrue() const +{ + if (!variantTypeIsValid()) + return false; + + if (mComparisonType == Comparison_Boolean || mComparisonType == Comparison_Integer) + { + if (mConstSelect.mValue.getType() == ESM::VT_Float) + return conditionIsNeverTrue(getConditionFloatRange(), getValidIntRange()); + else + return conditionIsNeverTrue(getConditionIntRange(), getValidIntRange()); + } + else if (mComparisonType == Comparison_Numeric) + { + if (mConstSelect.mValue.getType() == ESM::VT_Float) + return conditionIsNeverTrue(getConditionFloatRange(), getValidFloatRange()); + else + return conditionIsNeverTrue(getConditionIntRange(), getValidFloatRange()); + } + + return false; +} + +bool CSMWorld::ConstInfoSelectWrapper::variantTypeIsValid() const +{ + return (mConstSelect.mValue.getType() == ESM::VT_Int || mConstSelect.mValue.getType() == ESM::VT_Float); +} + +const ESM::Variant& CSMWorld::ConstInfoSelectWrapper::getVariant() const +{ + return mConstSelect.mValue; +} + +std::string CSMWorld::ConstInfoSelectWrapper::toString() const +{ + std::ostringstream stream; + stream << convertToString(mFunctionName) << " "; + + if (mHasVariable) + stream << mVariableName << " "; + + stream << convertToString(mRelationType) << " "; + + switch (mConstSelect.mValue.getType()) + { + case ESM::VT_Int: + stream << mConstSelect.mValue.getInteger(); + break; + + case ESM::VT_Float: + stream << mConstSelect.mValue.getFloat(); + break; + + default: + stream << "(Invalid value type)"; + break; + } + + return stream.str(); +} + +void CSMWorld::ConstInfoSelectWrapper::readRule() +{ + if (mConstSelect.mSelectRule.size() < RuleMinSize) + throw std::runtime_error("InfoSelectWrapper: rule is to small"); + + readFunctionName(); + readRelationType(); + readVariableName(); + updateHasVariable(); + updateComparisonType(); +} + +void CSMWorld::ConstInfoSelectWrapper::readFunctionName() +{ + char functionPrefix = mConstSelect.mSelectRule[FunctionPrefixOffset]; + std::string functionIndex = mConstSelect.mSelectRule.substr(FunctionIndexOffset, 2); + int convertedIndex = -1; + + // Read in function index, form ## from 00 .. 73, skip leading zero + if (functionIndex[0] == '0') + functionIndex = functionIndex[1]; + + std::stringstream stream; + stream << functionIndex; + stream >> convertedIndex; + + switch (functionPrefix) + { + case '1': + if (convertedIndex >= 0 && convertedIndex <= 73) + mFunctionName = static_cast(convertedIndex); + else + mFunctionName = Function_None; + break; + + case '2': mFunctionName = Function_Global; break; + case '3': mFunctionName = Function_Local; break; + case '4': mFunctionName = Function_Journal; break; + case '5': mFunctionName = Function_Item; break; + case '6': mFunctionName = Function_Dead; break; + case '7': mFunctionName = Function_NotId; break; + case '8': mFunctionName = Function_NotFaction; break; + case '9': mFunctionName = Function_NotClass; break; + case 'A': mFunctionName = Function_NotRace; break; + case 'B': mFunctionName = Function_NotCell; break; + case 'C': mFunctionName = Function_NotLocal; break; + default: mFunctionName = Function_None; break; + } +} + +void CSMWorld::ConstInfoSelectWrapper::readRelationType() +{ + char relationIndex = mConstSelect.mSelectRule[RelationIndexOffset]; + + switch (relationIndex) + { + case '0': mRelationType = Relation_Equal; break; + case '1': mRelationType = Relation_NotEqual; break; + case '2': mRelationType = Relation_Greater; break; + case '3': mRelationType = Relation_GreaterOrEqual; break; + case '4': mRelationType = Relation_Less; break; + case '5': mRelationType = Relation_LessOrEqual; break; + default: mRelationType = Relation_None; + } +} + +void CSMWorld::ConstInfoSelectWrapper::readVariableName() +{ + if (mConstSelect.mSelectRule.size() >= VarNameOffset) + mVariableName = mConstSelect.mSelectRule.substr(VarNameOffset); + else + mVariableName.clear(); +} + +void CSMWorld::ConstInfoSelectWrapper::updateHasVariable() +{ + switch (mFunctionName) + { + case Function_Global: + case Function_Local: + case Function_Journal: + case Function_Item: + case Function_Dead: + case Function_NotId: + case Function_NotFaction: + case Function_NotClass: + case Function_NotRace: + case Function_NotCell: + case Function_NotLocal: + mHasVariable = true; + break; + + default: + mHasVariable = false; + break; + } +} + +void CSMWorld::ConstInfoSelectWrapper::updateComparisonType() +{ + switch (mFunctionName) + { + // Boolean + case Function_NotId: + case Function_NotFaction: + case Function_NotClass: + case Function_NotRace: + case Function_NotCell: + case Function_NotLocal: + case Function_PcExpelled: + case Function_PcCommonDisease: + case Function_PcBlightDisease: + case Function_SameSex: + case Function_SameRace: + case Function_SameFaction: + case Function_Detected: + case Function_Alarmed: + case Function_PcCorpus: + case Function_PcVampire: + case Function_Attacked: + case Function_TalkedToPc: + case Function_ShouldAttack: + case Function_Werewolf: + mComparisonType = Comparison_Boolean; + break; + + // Integer + case Function_Journal: + case Function_Item: + case Function_Dead: + case Function_RankLow: + case Function_RankHigh: + case Function_RankRequirement: + case Function_Reputation: + case Function_PcReputation: + case Function_PcLevel: + case Function_PcStrength: + case Function_PcBlock: + case Function_PcArmorer: + case Function_PcMediumArmor: + case Function_PcHeavyArmor: + case Function_PcBluntWeapon: + case Function_PcLongBlade: + case Function_PcAxe: + case Function_PcSpear: + case Function_PcAthletics: + case Function_PcEnchant: + case Function_PcDestruction: + case Function_PcAlteration: + case Function_PcIllusion: + case Function_PcConjuration: + case Function_PcMysticism: + case Function_PcRestoration: + case Function_PcAlchemy: + case Function_PcUnarmored: + case Function_PcSecurity: + case Function_PcSneak: + case Function_PcAcrobatics: + case Function_PcLightArmor: + case Function_PcShortBlade: + case Function_PcMarksman: + case Function_PcMerchantile: + case Function_PcSpeechcraft: + case Function_PcHandToHand: + case Function_PcGender: + case Function_PcClothingModifier: + case Function_PcCrimeLevel: + case Function_FactionRankDifference: + case Function_Choice: + case Function_PcIntelligence: + case Function_PcWillpower: + case Function_PcAgility: + case Function_PcSpeed: + case Function_PcEndurance: + case Function_PcPersonality: + case Function_PcLuck: + case Function_Weather: + case Function_Level: + case Function_CreatureTarget: + case Function_FriendHit: + case Function_Fight: + case Function_Hello: + case Function_Alarm: + case Function_Flee: + case Function_PcWerewolfKills: + mComparisonType = Comparison_Integer; + break; + + // Numeric + case Function_Global: + case Function_Local: + + case Function_Health_Percent: + case Function_PcHealthPercent: + case Function_PcMagicka: + case Function_PcFatigue: + case Function_PcHealth: + mComparisonType = Comparison_Numeric; + break; + + default: + mComparisonType = Comparison_None; + break; + } +} + +std::pair CSMWorld::ConstInfoSelectWrapper::getConditionIntRange() const +{ + const int IntMax = std::numeric_limits::max(); + const int IntMin = std::numeric_limits::min(); + const std::pair InvalidRange(IntMax, IntMin); + + int value = mConstSelect.mValue.getInteger(); + + switch (mRelationType) + { + case Relation_Equal: + case Relation_NotEqual: + return std::pair(value, value); + + case Relation_Greater: + if (value == IntMax) + { + return InvalidRange; + } + else + { + return std::pair(value + 1, IntMax); + } + break; + + case Relation_GreaterOrEqual: + return std::pair(value, IntMax); + + case Relation_Less: + if (value == IntMin) + { + return InvalidRange; + } + else + { + return std::pair(IntMin, value - 1); + } + + case Relation_LessOrEqual: + return std::pair(IntMin, value); + + default: + throw std::logic_error("InfoSelectWrapper: relation does not have a range"); + } +} + +std::pair CSMWorld::ConstInfoSelectWrapper::getConditionFloatRange() const +{ + const float FloatMax = std::numeric_limits::infinity(); + const float FloatMin = -std::numeric_limits::infinity(); + const float Epsilon = std::numeric_limits::epsilon(); + const std::pair InvalidRange(FloatMax, FloatMin); + + float value = mConstSelect.mValue.getFloat(); + + switch (mRelationType) + { + case Relation_Equal: + case Relation_NotEqual: + return std::pair(value, value); + + case Relation_Greater: + return std::pair(value + Epsilon, FloatMax); + + case Relation_GreaterOrEqual: + return std::pair(value, FloatMax); + + case Relation_Less: + return std::pair(FloatMin, value - Epsilon); + + case Relation_LessOrEqual: + return std::pair(FloatMin, value); + + default: + throw std::logic_error("InfoSelectWrapper: given relation does not have a range"); + } +} + +std::pair CSMWorld::ConstInfoSelectWrapper::getValidIntRange() const +{ + const int IntMax = std::numeric_limits::max(); + const int IntMin = std::numeric_limits::min(); + + switch (mFunctionName) + { + // Boolean + case Function_NotId: + case Function_NotFaction: + case Function_NotClass: + case Function_NotRace: + case Function_NotCell: + case Function_NotLocal: + case Function_PcExpelled: + case Function_PcCommonDisease: + case Function_PcBlightDisease: + case Function_SameSex: + case Function_SameRace: + case Function_SameFaction: + case Function_Detected: + case Function_Alarmed: + case Function_PcCorpus: + case Function_PcVampire: + case Function_Attacked: + case Function_TalkedToPc: + case Function_ShouldAttack: + case Function_Werewolf: + return std::pair(0, 1); + + // Integer + case Function_RankLow: + case Function_RankHigh: + case Function_Reputation: + case Function_PcReputation: + case Function_Journal: + return std::pair(IntMin, IntMax); + + case Function_Item: + case Function_Dead: + case Function_PcLevel: + case Function_PcStrength: + case Function_PcBlock: + case Function_PcArmorer: + case Function_PcMediumArmor: + case Function_PcHeavyArmor: + case Function_PcBluntWeapon: + case Function_PcLongBlade: + case Function_PcAxe: + case Function_PcSpear: + case Function_PcAthletics: + case Function_PcEnchant: + case Function_PcDestruction: + case Function_PcAlteration: + case Function_PcIllusion: + case Function_PcConjuration: + case Function_PcMysticism: + case Function_PcRestoration: + case Function_PcAlchemy: + case Function_PcUnarmored: + case Function_PcSecurity: + case Function_PcSneak: + case Function_PcAcrobatics: + case Function_PcLightArmor: + case Function_PcShortBlade: + case Function_PcMarksman: + case Function_PcMerchantile: + case Function_PcSpeechcraft: + case Function_PcHandToHand: + case Function_PcClothingModifier: + case Function_PcCrimeLevel: + case Function_Choice: + case Function_PcIntelligence: + case Function_PcWillpower: + case Function_PcAgility: + case Function_PcSpeed: + case Function_PcEndurance: + case Function_PcPersonality: + case Function_PcLuck: + case Function_Level: + case Function_PcWerewolfKills: + return std::pair(0, IntMax); + + case Function_Fight: + case Function_Hello: + case Function_Alarm: + case Function_Flee: + return std::pair(0, 100); + + case Function_Weather: + return std::pair(0, 9); + + case Function_FriendHit: + return std::pair(0, 4); + + case Function_RankRequirement: + return std::pair(0, 3); + + case Function_CreatureTarget: + return std::pair(0, 2); + + case Function_PcGender: + return std::pair(0, 1); + + case Function_FactionRankDifference: + return std::pair(-9, 9); + + // Numeric + case Function_Global: + case Function_Local: + return std::pair(IntMin, IntMax); + + case Function_PcMagicka: + case Function_PcFatigue: + case Function_PcHealth: + return std::pair(0, IntMax); + + case Function_Health_Percent: + case Function_PcHealthPercent: + return std::pair(0, 100); + + default: + throw std::runtime_error("InfoSelectWrapper: function does not exist"); + } +} + +std::pair CSMWorld::ConstInfoSelectWrapper::getValidFloatRange() const +{ + const float FloatMax = std::numeric_limits::infinity(); + const float FloatMin = -std::numeric_limits::infinity(); + + switch (mFunctionName) + { + // Numeric + case Function_Global: + case Function_Local: + case Function_NotLocal: + return std::pair(FloatMin, FloatMax); + + case Function_PcMagicka: + case Function_PcFatigue: + case Function_PcHealth: + return std::pair(0, FloatMax); + + case Function_Health_Percent: + case Function_PcHealthPercent: + return std::pair(0, 100); + + default: + throw std::runtime_error("InfoSelectWrapper: function does not exist or is not numeric"); + } +} + +template +bool CSMWorld::ConstInfoSelectWrapper::rangeContains(T1 value, std::pair range) const +{ + return (value >= range.first && value <= range.second); +} + +template +bool CSMWorld::ConstInfoSelectWrapper::rangeFullyContains(std::pair containingRange, + std::pair testRange) const +{ + return (containingRange.first <= testRange.first) && (testRange.second <= containingRange.second); +} + +template +bool CSMWorld::ConstInfoSelectWrapper::rangesOverlap(std::pair range1, std::pair range2) const +{ + // One of the bounds of either range should fall within the other range + return + (range1.first <= range2.first && range2.first <= range1.second) || + (range1.first <= range2.second && range2.second <= range1.second) || + (range2.first <= range1.first && range1.first <= range2.second) || + (range2.first <= range1.second && range1.second <= range2.second); +} + +template +bool CSMWorld::ConstInfoSelectWrapper::rangesMatch(std::pair range1, std::pair range2) const +{ + return (range1.first == range2.first && range1.second == range2.second); +} + +template +bool CSMWorld::ConstInfoSelectWrapper::conditionIsAlwaysTrue(std::pair conditionRange, + std::pair validRange) const +{ + switch (mRelationType) + { + case Relation_Equal: + return false; + + case Relation_NotEqual: + // If value is not within range, it will always be true + return !rangeContains(conditionRange.first, validRange); + + case Relation_Greater: + case Relation_GreaterOrEqual: + case Relation_Less: + case Relation_LessOrEqual: + // If the valid range is completely within the condition range, it will always be true + return rangeFullyContains(conditionRange, validRange); + + default: + throw std::logic_error("InfoCondition: operator can not be used to compare"); + } + + return false; +} + +template +bool CSMWorld::ConstInfoSelectWrapper::conditionIsNeverTrue(std::pair conditionRange, + std::pair validRange) const +{ + switch (mRelationType) + { + case Relation_Equal: + return !rangeContains(conditionRange.first, validRange); + + case Relation_NotEqual: + return false; + + case Relation_Greater: + case Relation_GreaterOrEqual: + case Relation_Less: + case Relation_LessOrEqual: + // If ranges do not overlap, it will never be true + return !rangesOverlap(conditionRange, validRange); + + default: + throw std::logic_error("InfoCondition: operator can not be used to compare"); + } + + return false; +} + +// InfoSelectWrapper + +CSMWorld::InfoSelectWrapper::InfoSelectWrapper(ESM::DialInfo::SelectStruct& select) + : CSMWorld::ConstInfoSelectWrapper(select), mSelect(select) +{ +} + +void CSMWorld::InfoSelectWrapper::setFunctionName(FunctionName name) +{ + mFunctionName = name; + updateHasVariable(); + updateComparisonType(); +} + +void CSMWorld::InfoSelectWrapper::setRelationType(RelationType type) +{ + mRelationType = type; +} + +void CSMWorld::InfoSelectWrapper::setVariableName(const std::string& name) +{ + mVariableName = name; +} + +void CSMWorld::InfoSelectWrapper::setDefaults() +{ + if (!variantTypeIsValid()) + mSelect.mValue.setType(ESM::VT_Int); + + switch (mComparisonType) + { + case Comparison_Boolean: + setRelationType(Relation_Equal); + mSelect.mValue.setInteger(1); + break; + + case Comparison_Integer: + case Comparison_Numeric: + setRelationType(Relation_Greater); + mSelect.mValue.setInteger(0); + break; + + default: + // Do nothing + break; + } + + update(); +} + +void CSMWorld::InfoSelectWrapper::update() +{ + std::ostringstream stream; + + // Leading 0 + stream << '0'; + + // Write Function + + bool writeIndex = false; + size_t functionIndex = static_cast(mFunctionName); + + switch (mFunctionName) + { + case Function_None: stream << '0'; break; + case Function_Global: stream << '2'; break; + case Function_Local: stream << '3'; break; + case Function_Journal: stream << '4'; break; + case Function_Item: stream << '5'; break; + case Function_Dead: stream << '6'; break; + case Function_NotId: stream << '7'; break; + case Function_NotFaction: stream << '8'; break; + case Function_NotClass: stream << '9'; break; + case Function_NotRace: stream << 'A'; break; + case Function_NotCell: stream << 'B'; break; + case Function_NotLocal: stream << 'C'; break; + default: stream << '1'; writeIndex = true; break; + } + + if (writeIndex && functionIndex < 10) // leading 0 + stream << '0' << functionIndex; + else if (writeIndex) + stream << functionIndex; + else + stream << "00"; + + // Write Relation + switch (mRelationType) + { + case Relation_Equal: stream << '0'; break; + case Relation_NotEqual: stream << '1'; break; + case Relation_Greater: stream << '2'; break; + case Relation_GreaterOrEqual: stream << '3'; break; + case Relation_Less: stream << '4'; break; + case Relation_LessOrEqual: stream << '5'; break; + default: stream << '0'; break; + } + + if (mHasVariable) + stream << mVariableName; + + mSelect.mSelectRule = stream.str(); +} + +ESM::Variant& CSMWorld::InfoSelectWrapper::getVariant() +{ + return mSelect.mValue; +} diff --git a/apps/opencs/model/world/infoselectwrapper.hpp b/apps/opencs/model/world/infoselectwrapper.hpp new file mode 100644 index 000000000..ce26a46dc --- /dev/null +++ b/apps/opencs/model/world/infoselectwrapper.hpp @@ -0,0 +1,243 @@ +#ifndef CSM_WORLD_INFOSELECTWRAPPER_H +#define CSM_WORLD_INFOSELECTWRAPPER_H + +#include + +namespace CSMWorld +{ + // ESM::DialInfo::SelectStruct.mSelectRule + // 012345... + // ^^^ ^^ + // ||| || + // ||| |+------------- condition variable string + // ||| +-------------- comparison type, ['0'..'5']; e.g. !=, <, >=, etc + // ||+---------------- function index (encoded, where function == '1') + // |+----------------- function, ['1'..'C']; e.g. Global, Local, Not ID, etc + // +------------------ unknown + // + + // Wrapper for DialInfo::SelectStruct + class ConstInfoSelectWrapper + { + public: + + // Order matters + enum FunctionName + { + Function_RankLow=0, + Function_RankHigh, + Function_RankRequirement, + Function_Reputation, + Function_Health_Percent, + Function_PcReputation, + Function_PcLevel, + Function_PcHealthPercent, + Function_PcMagicka, + Function_PcFatigue, + Function_PcStrength, + Function_PcBlock, + Function_PcArmorer, + Function_PcMediumArmor, + Function_PcHeavyArmor, + Function_PcBluntWeapon, + Function_PcLongBlade, + Function_PcAxe, + Function_PcSpear, + Function_PcAthletics, + Function_PcEnchant, + Function_PcDestruction, + Function_PcAlteration, + Function_PcIllusion, + Function_PcConjuration, + Function_PcMysticism, + Function_PcRestoration, + Function_PcAlchemy, + Function_PcUnarmored, + Function_PcSecurity, + Function_PcSneak, + Function_PcAcrobatics, + Function_PcLightArmor, + Function_PcShortBlade, + Function_PcMarksman, + Function_PcMerchantile, + Function_PcSpeechcraft, + Function_PcHandToHand, + Function_PcGender, + Function_PcExpelled, + Function_PcCommonDisease, + Function_PcBlightDisease, + Function_PcClothingModifier, + Function_PcCrimeLevel, + Function_SameSex, + Function_SameRace, + Function_SameFaction, + Function_FactionRankDifference, + Function_Detected, + Function_Alarmed, + Function_Choice, + Function_PcIntelligence, + Function_PcWillpower, + Function_PcAgility, + Function_PcSpeed, + Function_PcEndurance, + Function_PcPersonality, + Function_PcLuck, + Function_PcCorpus, + Function_Weather, + Function_PcVampire, + Function_Level, + Function_Attacked, + Function_TalkedToPc, + Function_PcHealth, + Function_CreatureTarget, + Function_FriendHit, + Function_Fight, + Function_Hello, + Function_Alarm, + Function_Flee, + Function_ShouldAttack, + Function_Werewolf, + Function_PcWerewolfKills=73, + + Function_Global, + Function_Local, + Function_Journal, + Function_Item, + Function_Dead, + Function_NotId, + Function_NotFaction, + Function_NotClass, + Function_NotRace, + Function_NotCell, + Function_NotLocal, + + Function_None + }; + + enum RelationType + { + Relation_Equal, + Relation_NotEqual, + Relation_Greater, + Relation_GreaterOrEqual, + Relation_Less, + Relation_LessOrEqual, + + Relation_None + }; + + enum ComparisonType + { + Comparison_Boolean, + Comparison_Integer, + Comparison_Numeric, + + Comparison_None + }; + + static const size_t RuleMinSize; + + static const size_t FunctionPrefixOffset; + static const size_t FunctionIndexOffset; + static const size_t RelationIndexOffset; + static const size_t VarNameOffset; + + static const char* FunctionEnumStrings[]; + static const char* RelationEnumStrings[]; + static const char* ComparisonEnumStrings[]; + + static std::string convertToString(FunctionName name); + static std::string convertToString(RelationType type); + static std::string convertToString(ComparisonType type); + + ConstInfoSelectWrapper(const ESM::DialInfo::SelectStruct& select); + + FunctionName getFunctionName() const; + RelationType getRelationType() const; + ComparisonType getComparisonType() const; + + bool hasVariable() const; + const std::string& getVariableName() const; + + bool conditionIsAlwaysTrue() const; + bool conditionIsNeverTrue() const; + bool variantTypeIsValid() const; + + const ESM::Variant& getVariant() const; + + std::string toString() const; + + protected: + + void readRule(); + void readFunctionName(); + void readRelationType(); + void readVariableName(); + void updateHasVariable(); + void updateComparisonType(); + + std::pair getConditionIntRange() const; + std::pair getConditionFloatRange() const; + + std::pair getValidIntRange() const; + std::pair getValidFloatRange() const; + + template + bool rangeContains(Type1 value, std::pair range) const; + + template + bool rangesOverlap(std::pair range1, std::pair range2) const; + + template + bool rangeFullyContains(std::pair containing, std::pair test) const; + + template + bool rangesMatch(std::pair range1, std::pair range2) const; + + template + bool conditionIsAlwaysTrue(std::pair conditionRange, std::pair validRange) const; + + template + bool conditionIsNeverTrue(std::pair conditionRange, std::pair validRange) const; + + FunctionName mFunctionName; + RelationType mRelationType; + ComparisonType mComparisonType; + + bool mHasVariable; + std::string mVariableName; + + private: + + const ESM::DialInfo::SelectStruct& mConstSelect; + }; + + // Wrapper for DialInfo::SelectStruct that can modify the wrapped select struct + class InfoSelectWrapper : public ConstInfoSelectWrapper + { + public: + + InfoSelectWrapper(ESM::DialInfo::SelectStruct& select); + + // Wrapped SelectStruct will not be modified until update() is called + void setFunctionName(FunctionName name); + void setRelationType(RelationType type); + void setVariableName(const std::string& name); + + // Modified wrapped SelectStruct + void update(); + + // This sets properties based on the function name to its defaults and updates the wrapped object + void setDefaults(); + + ESM::Variant& getVariant(); + + private: + + ESM::DialInfo::SelectStruct& mSelect; + + void writeRule(); + }; +} + +#endif diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index 92b4b9e62..82df7bd11 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -6,6 +6,7 @@ #include "idcollection.hpp" #include "pathgrid.hpp" #include "info.hpp" +#include "infoselectwrapper.hpp" namespace CSMWorld { @@ -529,16 +530,6 @@ namespace CSMWorld return 1; // fixed at size 1 } - // ESM::DialInfo::SelectStruct.mSelectRule - // 012345... - // ^^^ ^^ - // ||| || - // ||| |+------------- condition variable string - // ||| +-------------- comparison type, ['0'..'5']; e.g. !=, <, >=, etc - // ||+---------------- function index (encoded, where function == '1') - // |+----------------- function, ['1'..'C']; e.g. Global, Local, Not ID, etc - // +------------------ unknown - // InfoConditionAdapter::InfoConditionAdapter () {} void InfoConditionAdapter::addRow(Record& record, int position) const @@ -547,11 +538,11 @@ namespace CSMWorld std::vector& conditions = info.mSelects; - // blank row + // default row ESM::DialInfo::SelectStruct condStruct; - condStruct.mSelectRule = "00000"; + condStruct.mSelectRule = "01000"; condStruct.mValue = ESM::Variant(); - condStruct.mValue.setType(ESM::VT_Int); // default to ints + condStruct.mValue.setType(ESM::VT_Int); conditions.insert(conditions.begin()+position, condStruct); @@ -589,89 +580,6 @@ namespace CSMWorld return new NestedTableWrapper >(record.get().mSelects); } - // See the mappings in MWDialogue::SelectWrapper::getArgument - // from ESM::Attribute, ESM::Skill and MWMechanics::CreatureStats (for AI) - static std::map populateEncToInfoFunc() - { - std::map funcMap; - funcMap["00"] = "Rank Low"; - funcMap["01"] = "Rank High"; - funcMap["02"] = "Rank Requirement"; - funcMap["03"] = "Reputation"; - funcMap["04"] = "Health Percent"; - funcMap["05"] = "PC Reputation"; - funcMap["06"] = "PC Level"; - funcMap["07"] = "PC Health Percent"; - funcMap["08"] = "PC Magicka"; - funcMap["09"] = "PC Fatigue"; - funcMap["10"] = "PC Strength"; - funcMap["11"] = "PC Block"; - funcMap["12"] = "PC Armorer"; - funcMap["13"] = "PC Medium Armor"; - funcMap["14"] = "PC Heavy Armor"; - funcMap["15"] = "PC Blunt Weapon"; - funcMap["16"] = "PC Long Blade"; - funcMap["17"] = "PC Axe"; - funcMap["18"] = "PC Spear"; - funcMap["19"] = "PC Athletics"; - funcMap["20"] = "PC Enchant"; - funcMap["21"] = "PC Destruction"; - funcMap["22"] = "PC Alteration"; - funcMap["23"] = "PC Illusion"; - funcMap["24"] = "PC Conjuration"; - funcMap["25"] = "PC Mysticism"; - funcMap["26"] = "PC Restoration"; - funcMap["27"] = "PC Alchemy"; - funcMap["28"] = "PC Unarmored"; - funcMap["29"] = "PC Security"; - funcMap["30"] = "PC Sneak"; - funcMap["31"] = "PC Acrobatics"; - funcMap["32"] = "PC Light Armor"; - funcMap["33"] = "PC Short Blade"; - funcMap["34"] = "PC Marksman"; - funcMap["35"] = "PC Merchantile"; - funcMap["36"] = "PC Speechcraft"; - funcMap["37"] = "PC Hand To Hand"; - funcMap["38"] = "PC Sex"; - funcMap["39"] = "PC Expelled"; - funcMap["40"] = "PC Common Disease"; - funcMap["41"] = "PC Blight Disease"; - funcMap["42"] = "PC Clothing Modifier"; - funcMap["43"] = "PC Crime Level"; - funcMap["44"] = "Same Sex"; - funcMap["45"] = "Same Race"; - funcMap["46"] = "Same Faction"; - funcMap["47"] = "Faction Rank Difference"; - funcMap["48"] = "Detected"; - funcMap["49"] = "Alarmed"; - funcMap["50"] = "Choice"; - funcMap["51"] = "PC Intelligence"; - funcMap["52"] = "PC Willpower"; - funcMap["53"] = "PC Agility"; - funcMap["54"] = "PC Speed"; - funcMap["55"] = "PC Endurance"; - funcMap["56"] = "PC Personality"; - funcMap["57"] = "PC Luck"; - funcMap["58"] = "PC Corpus"; - funcMap["59"] = "Weather"; - funcMap["60"] = "PC Vampire"; - funcMap["61"] = "Level"; - funcMap["62"] = "Attacked"; - funcMap["63"] = "Talked To PC"; - funcMap["64"] = "PC Health"; - funcMap["65"] = "Creature Target"; - funcMap["66"] = "Friend Hit"; - funcMap["67"] = "Fight"; - funcMap["68"] = "Hello"; - funcMap["69"] = "Alarm"; - funcMap["70"] = "Flee"; - funcMap["71"] = "Should Attack"; - funcMap["72"] = "Werewolf"; - funcMap["73"] = "PC Werewolf Kills"; - return funcMap; - } - static const std::map sEncToInfoFunc = populateEncToInfoFunc(); - QVariant InfoConditionAdapter::getData(const Record& record, int subRowIndex, int subColIndex) const { @@ -682,70 +590,36 @@ namespace CSMWorld if (subRowIndex < 0 || subRowIndex >= static_cast (conditions.size())) throw std::runtime_error ("index out of range"); + ConstInfoSelectWrapper infoSelectWrapper(conditions[subRowIndex]); + switch (subColIndex) { case 0: { - char condType = conditions[subRowIndex].mSelectRule[1]; - switch (condType) - { - case '0': return 0; // blank space - case '1': return 1; // Function - case '2': return 2; // Global - case '3': return 3; // Local - case '4': return 4; // Journal - case '5': return 5; // Item - case '6': return 6; // Dead - case '7': return 7; // Not ID - case '8': return 8; // Not Factio - case '9': return 9; // Not Class - case 'A': return 10; // Not Race - case 'B': return 11; // Not Cell - case 'C': return 12; // Not Local - default: return QVariant(); // TODO: log an error? - } + return infoSelectWrapper.getFunctionName(); } case 1: { - if (conditions[subRowIndex].mSelectRule[1] == '1') - { - // throws an exception if the encoding is not found - return sEncToInfoFunc.at(conditions[subRowIndex].mSelectRule.substr(2, 2)).c_str(); - } + if (infoSelectWrapper.hasVariable()) + return QString(infoSelectWrapper.getVariableName().c_str()); else - return QString(conditions[subRowIndex].mSelectRule.substr(5).c_str()); + return ""; } case 2: { - char compType = conditions[subRowIndex].mSelectRule[4]; - switch (compType) - { - case '0': return 3; // = - case '1': return 0; // != - case '2': return 4; // > - case '3': return 5; // >= - case '4': return 1; // < - case '5': return 2; // <= - default: return QVariant(); // TODO: log an error? - } + return infoSelectWrapper.getRelationType(); } case 3: { - switch (conditions[subRowIndex].mValue.getType()) + switch (infoSelectWrapper.getVariant().getType()) { - case ESM::VT_String: - { - return QString::fromUtf8 (conditions[subRowIndex].mValue.getString().c_str()); - } case ESM::VT_Int: - case ESM::VT_Short: - case ESM::VT_Long: { - return conditions[subRowIndex].mValue.getInteger(); + return infoSelectWrapper.getVariant().getInteger(); } case ESM::VT_Float: { - return conditions[subRowIndex].mValue.getFloat(); + return infoSelectWrapper.getVariant().getFloat(); } default: return QVariant(); } @@ -764,101 +638,63 @@ namespace CSMWorld if (subRowIndex < 0 || subRowIndex >= static_cast (conditions.size())) throw std::runtime_error ("index out of range"); + InfoSelectWrapper infoSelectWrapper(conditions[subRowIndex]); + bool conversionResult = false; + switch (subColIndex) { - case 0: + case 0: // Function { - // See sInfoCondFunc in columns.cpp for the enum values - switch (value.toInt()) + infoSelectWrapper.setFunctionName(static_cast(value.toInt())); + + if (infoSelectWrapper.getComparisonType() != ConstInfoSelectWrapper::Comparison_Numeric && + infoSelectWrapper.getVariant().getType() != ESM::VT_Int) { - // FIXME: when these change the values of the other columns need to change - // correspondingly (and automatically) - case 1: - { - conditions[subRowIndex].mSelectRule[1] = '1'; // Function - // default to "Rank Low" - conditions[subRowIndex].mSelectRule[2] = '0'; - conditions[subRowIndex].mSelectRule[3] = '0'; - break; - } - case 2: conditions[subRowIndex].mSelectRule[1] = '2'; break; // Global - case 3: conditions[subRowIndex].mSelectRule[1] = '3'; break; // Local - case 4: conditions[subRowIndex].mSelectRule[1] = '4'; break; // Journal - case 5: conditions[subRowIndex].mSelectRule[1] = '5'; break; // Item - case 6: conditions[subRowIndex].mSelectRule[1] = '6'; break; // Dead - case 7: conditions[subRowIndex].mSelectRule[1] = '7'; break; // Not ID - case 8: conditions[subRowIndex].mSelectRule[1] = '8'; break; // Not Faction - case 9: conditions[subRowIndex].mSelectRule[1] = '9'; break; // Not Class - case 10: conditions[subRowIndex].mSelectRule[1] = 'A'; break; // Not Race - case 11: conditions[subRowIndex].mSelectRule[1] = 'B'; break; // Not Cell - case 12: conditions[subRowIndex].mSelectRule[1] = 'C'; break; // Not Local - default: return; // return without saving + infoSelectWrapper.getVariant().setType(ESM::VT_Int); } + + infoSelectWrapper.update(); break; } - case 1: + case 1: // Variable { - if (conditions[subRowIndex].mSelectRule[1] == '1') - { - std::map::const_iterator it = sEncToInfoFunc.begin(); - for (;it != sEncToInfoFunc.end(); ++it) - { - if (it->second == value.toString().toUtf8().constData()) - { - std::string rule = conditions[subRowIndex].mSelectRule.substr(0, 2); - rule.append(it->first); - // leave old values for undo (NOTE: may not be vanilla's behaviour) - rule.append(conditions[subRowIndex].mSelectRule.substr(4)); - conditions[subRowIndex].mSelectRule = rule; - break; - } - } - - if (it == sEncToInfoFunc.end()) - return; // return without saving; TODO: maybe log an error here - } - else - { - // FIXME: validate the string values before saving, based on the current function - std::string rule = conditions[subRowIndex].mSelectRule.substr(0, 5); - conditions[subRowIndex].mSelectRule = rule.append(value.toString().toUtf8().constData()); - } + infoSelectWrapper.setVariableName(value.toString().toUtf8().constData()); + infoSelectWrapper.update(); break; } - case 2: + case 2: // Relation { - // See sInfoCondComp in columns.cpp for the enum values - switch (value.toInt()) - { - case 0: conditions[subRowIndex].mSelectRule[4] = '1'; break; // != - case 1: conditions[subRowIndex].mSelectRule[4] = '4'; break; // < - case 2: conditions[subRowIndex].mSelectRule[4] = '5'; break; // <= - case 3: conditions[subRowIndex].mSelectRule[4] = '0'; break; // = - case 4: conditions[subRowIndex].mSelectRule[4] = '2'; break; // > - case 5: conditions[subRowIndex].mSelectRule[4] = '3'; break; // >= - default: return; // return without saving - } + infoSelectWrapper.setRelationType(static_cast(value.toInt())); + infoSelectWrapper.update(); break; } - case 3: + case 3: // Value { - switch (conditions[subRowIndex].mValue.getType()) + switch (infoSelectWrapper.getComparisonType()) { - case ESM::VT_String: + case ConstInfoSelectWrapper::Comparison_Numeric: { - conditions[subRowIndex].mValue.setString (value.toString().toUtf8().constData()); - break; - } - case ESM::VT_Int: - case ESM::VT_Short: - case ESM::VT_Long: - { - conditions[subRowIndex].mValue.setInteger (value.toInt()); + // QVariant seems to have issues converting 0 + if ((value.toInt(&conversionResult) && conversionResult) || value.toString().compare("0") == 0) + { + infoSelectWrapper.getVariant().setType(ESM::VT_Int); + infoSelectWrapper.getVariant().setInteger(value.toInt()); + } + else if (value.toFloat(&conversionResult) && conversionResult) + { + infoSelectWrapper.getVariant().setType(ESM::VT_Float); + infoSelectWrapper.getVariant().setFloat(value.toFloat()); + } break; } - case ESM::VT_Float: + case ConstInfoSelectWrapper::Comparison_Boolean: + case ConstInfoSelectWrapper::Comparison_Integer: { - conditions[subRowIndex].mValue.setFloat (value.toFloat()); + if ((value.toInt(&conversionResult) && conversionResult) || value.toString().compare("0") == 0) + { + infoSelectWrapper.getVariant().setType(ESM::VT_Int); + infoSelectWrapper.getVariant().setInteger(value.toInt()); + } break; } default: break; diff --git a/apps/opencs/view/world/idcompletiondelegate.cpp b/apps/opencs/view/world/idcompletiondelegate.cpp index 970490828..7f0f4ae46 100644 --- a/apps/opencs/view/world/idcompletiondelegate.cpp +++ b/apps/opencs/view/world/idcompletiondelegate.cpp @@ -1,6 +1,7 @@ #include "idcompletiondelegate.hpp" #include "../../model/world/idcompletionmanager.hpp" +#include "../../model/world/infoselectwrapper.hpp" #include "../widget/droplineedit.hpp" @@ -27,6 +28,56 @@ QWidget *CSVWorld::IdCompletionDelegate::createEditor(QWidget *parent, return NULL; } + // The completer for InfoCondVar needs to return a completer based on the first column + if (display == CSMWorld::ColumnBase::Display_InfoCondVar) + { + QModelIndex sibling = index.sibling(index.row(), 0); + int conditionFunction = sibling.model()->data(sibling, Qt::EditRole).toInt(); + + switch (conditionFunction) + { + case CSMWorld::ConstInfoSelectWrapper::Function_Global: + { + return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_GlobalVariable); + } + case CSMWorld::ConstInfoSelectWrapper::Function_Journal: + { + return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Journal); + } + case CSMWorld::ConstInfoSelectWrapper::Function_Item: + { + return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Referenceable); + } + case CSMWorld::ConstInfoSelectWrapper::Function_Dead: + case CSMWorld::ConstInfoSelectWrapper::Function_NotId: + { + return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Referenceable); + } + case CSMWorld::ConstInfoSelectWrapper::Function_NotFaction: + { + return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Faction); + } + case CSMWorld::ConstInfoSelectWrapper::Function_NotClass: + { + return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Class); + } + case CSMWorld::ConstInfoSelectWrapper::Function_NotRace: + { + return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Race); + } + case CSMWorld::ConstInfoSelectWrapper::Function_NotCell: + { + return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Cell); + } + case CSMWorld::ConstInfoSelectWrapper::Function_Local: + case CSMWorld::ConstInfoSelectWrapper::Function_NotLocal: + { + return new CSVWidget::DropLineEdit(display, parent); + } + default: return 0; // The rest of them can't be edited anyway + } + } + CSMWorld::IdCompletionManager &completionManager = getDocument().getIdCompletionManager(); CSVWidget::DropLineEdit *editor = new CSVWidget::DropLineEdit(display, parent); editor->setCompleter(completionManager.getCompleter(display).get());