From 8668eccd0bec7e565934d8d65519c05303ef621c Mon Sep 17 00:00:00 2001
From: Aesylwinn <kyleacooley@gmail.com>
Date: Wed, 17 Feb 2016 15:38:30 -0500
Subject: [PATCH] Topic Info verifier with fixes to InfoSelectWrapper class

---
 apps/opencs/CMakeLists.txt                    |   2 +-
 apps/opencs/model/tools/tools.cpp             |  16 +-
 apps/opencs/model/tools/topicinfocheck.cpp    | 441 ++++++++++++++++++
 apps/opencs/model/tools/topicinfocheck.hpp    |  95 ++++
 apps/opencs/model/world/infoselectwrapper.cpp |  41 +-
 apps/opencs/model/world/infoselectwrapper.hpp |   2 +
 6 files changed, 591 insertions(+), 6 deletions(-)
 create mode 100644 apps/opencs/model/tools/topicinfocheck.cpp
 create mode 100644 apps/opencs/model/tools/topicinfocheck.hpp

diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt
index 7b825232bc..a657bade20 100644
--- a/apps/opencs/CMakeLists.txt
+++ b/apps/opencs/CMakeLists.txt
@@ -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
     )
 
 opencs_hdrs_noqt (model/tools
diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp
index e750092b93..b6a04a236f 100644
--- a/apps/opencs/model/tools/tools.cpp
+++ b/apps/opencs/model/tools/tools.cpp
@@ -30,6 +30,7 @@
 #include "magiceffectcheck.hpp"
 #include "mergeoperation.hpp"
 #include "gmstcheck.hpp"
+#include "topicinfocheck.hpp"
 
 CSMDoc::OperationHolder *CSMTools::Tools::get (int type)
 {
@@ -111,9 +112,22 @@ 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)));
+
         mVerifier.setOperation (mVerifierOperation);
     }
 
diff --git a/apps/opencs/model/tools/topicinfocheck.cpp b/apps/opencs/model/tools/topicinfocheck.cpp
new file mode 100644
index 0000000000..f6af2a9456
--- /dev/null
+++ b/apps/opencs/model/tools/topicinfocheck.cpp
@@ -0,0 +1,441 @@
+#include "topicinfocheck.hpp"
+
+#include <sstream>
+
+#include "../world/infoselectwrapper.hpp"
+
+CSMTools::TopicInfoCheckStage::TopicInfoCheckStage(
+    const CSMWorld::InfoCollection& topicInfos,
+    const CSMWorld::IdCollection<CSMWorld::Cell>& cells,
+    const CSMWorld::IdCollection<ESM::Class>& classes,
+    const CSMWorld::IdCollection<ESM::Faction>& factions,
+    const CSMWorld::IdCollection<ESM::GameSetting>& gmsts,
+    const CSMWorld::IdCollection<ESM::Global>& globals,
+    const CSMWorld::IdCollection<ESM::Dialogue>& journals,
+    const CSMWorld::IdCollection<ESM::Race>& races,
+    const CSMWorld::IdCollection<ESM::Region>& regions,
+    const CSMWorld::IdCollection<ESM::Dialogue> &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<CSMWorld::Cell>& 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<ESM::Region>& 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<ESM::GameSetting>& 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<CSMWorld::Info>& 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<ESM::Dialogue>& 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<ESM::DialInfo::SelectStruct>::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 <typename T>
+bool CSMTools::TopicInfoCheckStage::verifyId(const std::string& name, const CSMWorld::IdCollection<T>& 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 0000000000..510901dacc
--- /dev/null
+++ b/apps/opencs/model/tools/topicinfocheck.hpp
@@ -0,0 +1,95 @@
+#ifndef CSM_TOOLS_TOPICINFOCHECK_HPP
+#define CSM_TOOLS_TOPICINFOCHECK_HPP
+
+#include <set>
+
+#include <components/esm/loadclas.hpp>
+#include <components/esm/loaddial.hpp>
+#include <components/esm/loadfact.hpp>
+#include <components/esm/loadglob.hpp>
+#include <components/esm/loadgmst.hpp>
+#include <components/esm/loadrace.hpp>
+#include <components/esm/loadregn.hpp>
+
+#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<CSMWorld::Cell>& cells,
+            const CSMWorld::IdCollection<ESM::Class>& classes,
+            const CSMWorld::IdCollection<ESM::Faction>& factions,
+            const CSMWorld::IdCollection<ESM::GameSetting>& gmsts,
+            const CSMWorld::IdCollection<ESM::Global>& globals,
+            const CSMWorld::IdCollection<ESM::Dialogue>& journals,
+            const CSMWorld::IdCollection<ESM::Race>& races,
+            const CSMWorld::IdCollection<ESM::Region>& regions,
+            const CSMWorld::IdCollection<ESM::Dialogue>& 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<CSMWorld::Cell>& mCells;
+        const CSMWorld::IdCollection<ESM::Class>& mClasses;
+        const CSMWorld::IdCollection<ESM::Faction>& mFactions;
+        const CSMWorld::IdCollection<ESM::GameSetting>& mGameSettings;
+        const CSMWorld::IdCollection<ESM::Global>& mGlobals;
+        const CSMWorld::IdCollection<ESM::Dialogue>& mJournals;
+        const CSMWorld::IdCollection<ESM::Race>& mRaces;
+        const CSMWorld::IdCollection<ESM::Region>& mRegions;
+        const CSMWorld::IdCollection<ESM::Dialogue>& mTopics;
+
+        const CSMWorld::RefIdData& mReferencables;
+        const CSMWorld::Resources& mSoundFiles;
+
+        std::set<std::string> 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 <typename T>
+        bool verifyId(const std::string& name, const CSMWorld::IdCollection<T>& 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/infoselectwrapper.cpp b/apps/opencs/model/world/infoselectwrapper.cpp
index 42cbabf72d..0bc1ec22b8 100644
--- a/apps/opencs/model/world/infoselectwrapper.cpp
+++ b/apps/opencs/model/world/infoselectwrapper.cpp
@@ -226,8 +226,7 @@ bool CSMWorld::ConstInfoSelectWrapper::conditionIsNeverTrue() const
 
 bool CSMWorld::ConstInfoSelectWrapper::variantTypeIsValid() const
 {
-    return (mConstSelect.mValue.getType() == ESM::VT_Int || mConstSelect.mValue.getType() == ESM::VT_Short ||
-        mConstSelect.mValue.getType() == ESM::VT_Long || mConstSelect.mValue.getType() == ESM::VT_Float);
+    return (mConstSelect.mValue.getType() == ESM::VT_Int || mConstSelect.mValue.getType() == ESM::VT_Float);
 }
 
 const ESM::Variant& CSMWorld::ConstInfoSelectWrapper::getVariant() const
@@ -235,6 +234,40 @@ 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_Short:
+        case ESM::VT_Long:
+        case ESM::VT_Int:
+            stream << mConstSelect.mValue.getInteger();
+            break;
+            
+        case ESM::VT_Float:
+            stream << mConstSelect.mValue.getFloat();
+            break;
+            
+        case ESM::VT_String:
+            stream << mConstSelect.mValue.getString();
+            break;
+            
+        default:
+            stream << "(Invalid value type)";
+            break;
+    }
+    
+    return stream.str();
+}
+
 void CSMWorld::ConstInfoSelectWrapper::readRule()
 {
     if (mConstSelect.mSelectRule.size() < RuleMinSize)
@@ -554,9 +587,9 @@ std::pair<int, int> CSMWorld::ConstInfoSelectWrapper::getValidIntRange() const
         case Function_RankHigh:
         case Function_Reputation:
         case Function_PcReputation:
+        case Function_Journal:
             return std::pair<int, int>(IntMin, IntMax);
 
-        case Function_Journal:
         case Function_Item:
         case Function_Dead:
         case Function_PcLevel:
@@ -736,7 +769,7 @@ bool CSMWorld::ConstInfoSelectWrapper::conditionIsNeverTrue(std::pair<T1,T1> con
 
         case Relation_NotEqual:
             // If the value is the only value withing the range, it will never be true
-            return rangesOverlap(conditionRange, validRange);
+            return rangesMatch(conditionRange, validRange);
 
         default:
             throw std::logic_error("InfoCondition: operator can not be used to compare");
diff --git a/apps/opencs/model/world/infoselectwrapper.hpp b/apps/opencs/model/world/infoselectwrapper.hpp
index 8ccae0efa5..1aa86aecab 100644
--- a/apps/opencs/model/world/infoselectwrapper.hpp
+++ b/apps/opencs/model/world/infoselectwrapper.hpp
@@ -164,6 +164,8 @@ namespace CSMWorld
         bool variantTypeIsValid() const;
 
         const ESM::Variant& getVariant() const;
+        
+        std::string toString() const;
 
     protected: