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
birthsigncheck spellcheck referencecheck referenceablecheck scriptcheck bodypartcheck
startscriptcheck search searchoperation searchstage pathgridcheck soundgencheck magiceffectcheck
mergestages gmstcheck
mergestages gmstcheck topicinfocheck
)
opencs_hdrs_noqt (model/tools

@ -30,6 +30,7 @@
#include "magiceffectcheck.hpp"
#include "mergeoperation.hpp"
#include "gmstcheck.hpp"
#include "topicinfocheck.hpp"
CSMDoc::OperationHolder *CSMTools::Tools::get (int type)
{
@ -114,6 +115,19 @@ CSMDoc::OperationHolder *CSMTools::Tools::getVerifier()
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);
}

@ -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
{
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");

@ -165,6 +165,8 @@ namespace CSMWorld
const ESM::Variant& getVariant() const;
std::string toString() const;
protected:
void readRule();

Loading…
Cancel
Save