Topic Info verifier with fixes to InfoSelectWrapper class

move
Aesylwinn 9 years ago
parent e89609e5b0
commit 8668eccd0b

@ -42,7 +42,7 @@ opencs_units_noqt (model/tools
mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck
birthsigncheck spellcheck referencecheck referenceablecheck scriptcheck bodypartcheck birthsigncheck spellcheck referencecheck referenceablecheck scriptcheck bodypartcheck
startscriptcheck search searchoperation searchstage pathgridcheck soundgencheck magiceffectcheck startscriptcheck search searchoperation searchstage pathgridcheck soundgencheck magiceffectcheck
mergestages gmstcheck mergestages gmstcheck topicinfocheck
) )
opencs_hdrs_noqt (model/tools opencs_hdrs_noqt (model/tools

@ -30,6 +30,7 @@
#include "magiceffectcheck.hpp" #include "magiceffectcheck.hpp"
#include "mergeoperation.hpp" #include "mergeoperation.hpp"
#include "gmstcheck.hpp" #include "gmstcheck.hpp"
#include "topicinfocheck.hpp"
CSMDoc::OperationHolder *CSMTools::Tools::get (int type) CSMDoc::OperationHolder *CSMTools::Tools::get (int type)
{ {
@ -111,9 +112,22 @@ CSMDoc::OperationHolder *CSMTools::Tools::getVerifier()
mData.getReferenceables(), mData.getReferenceables(),
mData.getResources (CSMWorld::UniversalId::Type_Icons), mData.getResources (CSMWorld::UniversalId::Type_Icons),
mData.getResources (CSMWorld::UniversalId::Type_Textures))); mData.getResources (CSMWorld::UniversalId::Type_Textures)));
mVerifierOperation->appendStage (new GmstCheckStage (mData.getGmsts())); 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); mVerifier.setOperation (mVerifierOperation);
} }

@ -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);
}

@ -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

@ -226,8 +226,7 @@ bool CSMWorld::ConstInfoSelectWrapper::conditionIsNeverTrue() const
bool CSMWorld::ConstInfoSelectWrapper::variantTypeIsValid() const bool CSMWorld::ConstInfoSelectWrapper::variantTypeIsValid() const
{ {
return (mConstSelect.mValue.getType() == ESM::VT_Int || mConstSelect.mValue.getType() == ESM::VT_Short || return (mConstSelect.mValue.getType() == ESM::VT_Int || mConstSelect.mValue.getType() == ESM::VT_Float);
mConstSelect.mValue.getType() == ESM::VT_Long || mConstSelect.mValue.getType() == ESM::VT_Float);
} }
const ESM::Variant& CSMWorld::ConstInfoSelectWrapper::getVariant() const const ESM::Variant& CSMWorld::ConstInfoSelectWrapper::getVariant() const
@ -235,6 +234,40 @@ const ESM::Variant& CSMWorld::ConstInfoSelectWrapper::getVariant() const
return mConstSelect.mValue; 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() void CSMWorld::ConstInfoSelectWrapper::readRule()
{ {
if (mConstSelect.mSelectRule.size() < RuleMinSize) if (mConstSelect.mSelectRule.size() < RuleMinSize)
@ -554,9 +587,9 @@ std::pair<int, int> CSMWorld::ConstInfoSelectWrapper::getValidIntRange() const
case Function_RankHigh: case Function_RankHigh:
case Function_Reputation: case Function_Reputation:
case Function_PcReputation: case Function_PcReputation:
case Function_Journal:
return std::pair<int, int>(IntMin, IntMax); return std::pair<int, int>(IntMin, IntMax);
case Function_Journal:
case Function_Item: case Function_Item:
case Function_Dead: case Function_Dead:
case Function_PcLevel: case Function_PcLevel:
@ -736,7 +769,7 @@ bool CSMWorld::ConstInfoSelectWrapper::conditionIsNeverTrue(std::pair<T1,T1> con
case Relation_NotEqual: case Relation_NotEqual:
// If the value is the only value withing the range, it will never be true // If the value is the only value withing the range, it will never be true
return rangesOverlap(conditionRange, validRange); return rangesMatch(conditionRange, validRange);
default: default:
throw std::logic_error("InfoCondition: operator can not be used to compare"); throw std::logic_error("InfoCondition: operator can not be used to compare");

@ -164,6 +164,8 @@ namespace CSMWorld
bool variantTypeIsValid() const; bool variantTypeIsValid() const;
const ESM::Variant& getVariant() const; const ESM::Variant& getVariant() const;
std::string toString() const;
protected: protected:

Loading…
Cancel
Save