Merge remote-tracking branch 'aesylwinn/topic-info-editor-improvements'
commit
9f14247562
@ -0,0 +1,79 @@
|
|||||||
|
#include "journalcheck.hpp"
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
CSMTools::JournalCheckStage::JournalCheckStage(const CSMWorld::IdCollection<ESM::Dialogue> &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<ESM::Dialogue> &journalRecord = mJournals.getRecord(stage);
|
||||||
|
|
||||||
|
if (journalRecord.isDeleted())
|
||||||
|
return;
|
||||||
|
|
||||||
|
const ESM::Dialogue &journal = journalRecord.get();
|
||||||
|
int statusNamedCount = 0;
|
||||||
|
int totalInfoCount = 0;
|
||||||
|
std::set<int> questIndices;
|
||||||
|
|
||||||
|
CSMWorld::InfoCollection::Range range = mJournalInfos.getTopicRange(journal.mId);
|
||||||
|
|
||||||
|
for (CSMWorld::InfoCollection::RecordConstIterator it = range.first; it != range.second; ++it)
|
||||||
|
{
|
||||||
|
const CSMWorld::Record<CSMWorld::Info> 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<std::set<int>::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);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
#ifndef CSM_TOOLS_JOURNALCHECK_H
|
||||||
|
#define CSM_TOOLS_JOURNALCHECK_H
|
||||||
|
|
||||||
|
#include <components/esm/loaddial.hpp>
|
||||||
|
|
||||||
|
#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<ESM::Dialogue>& 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<ESM::Dialogue>& mJournals;
|
||||||
|
const CSMWorld::InfoCollection& mJournalInfos;
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -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
|
@ -0,0 +1,893 @@
|
|||||||
|
#include "infoselectwrapper.hpp"
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
#include <sstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
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<FunctionName>(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<int, int> CSMWorld::ConstInfoSelectWrapper::getConditionIntRange() const
|
||||||
|
{
|
||||||
|
const int IntMax = std::numeric_limits<int>::max();
|
||||||
|
const int IntMin = std::numeric_limits<int>::min();
|
||||||
|
const std::pair<int, int> InvalidRange(IntMax, IntMin);
|
||||||
|
|
||||||
|
int value = mConstSelect.mValue.getInteger();
|
||||||
|
|
||||||
|
switch (mRelationType)
|
||||||
|
{
|
||||||
|
case Relation_Equal:
|
||||||
|
case Relation_NotEqual:
|
||||||
|
return std::pair<int, int>(value, value);
|
||||||
|
|
||||||
|
case Relation_Greater:
|
||||||
|
if (value == IntMax)
|
||||||
|
{
|
||||||
|
return InvalidRange;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return std::pair<int, int>(value + 1, IntMax);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Relation_GreaterOrEqual:
|
||||||
|
return std::pair<int, int>(value, IntMax);
|
||||||
|
|
||||||
|
case Relation_Less:
|
||||||
|
if (value == IntMin)
|
||||||
|
{
|
||||||
|
return InvalidRange;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return std::pair<int, int>(IntMin, value - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
case Relation_LessOrEqual:
|
||||||
|
return std::pair<int, int>(IntMin, value);
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw std::logic_error("InfoSelectWrapper: relation does not have a range");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<float, float> CSMWorld::ConstInfoSelectWrapper::getConditionFloatRange() const
|
||||||
|
{
|
||||||
|
const float FloatMax = std::numeric_limits<float>::infinity();
|
||||||
|
const float FloatMin = -std::numeric_limits<float>::infinity();
|
||||||
|
const float Epsilon = std::numeric_limits<float>::epsilon();
|
||||||
|
const std::pair<float, float> InvalidRange(FloatMax, FloatMin);
|
||||||
|
|
||||||
|
float value = mConstSelect.mValue.getFloat();
|
||||||
|
|
||||||
|
switch (mRelationType)
|
||||||
|
{
|
||||||
|
case Relation_Equal:
|
||||||
|
case Relation_NotEqual:
|
||||||
|
return std::pair<float, float>(value, value);
|
||||||
|
|
||||||
|
case Relation_Greater:
|
||||||
|
return std::pair<float, float>(value + Epsilon, FloatMax);
|
||||||
|
|
||||||
|
case Relation_GreaterOrEqual:
|
||||||
|
return std::pair<float, float>(value, FloatMax);
|
||||||
|
|
||||||
|
case Relation_Less:
|
||||||
|
return std::pair<float, float>(FloatMin, value - Epsilon);
|
||||||
|
|
||||||
|
case Relation_LessOrEqual:
|
||||||
|
return std::pair<float, float>(FloatMin, value);
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw std::logic_error("InfoSelectWrapper: given relation does not have a range");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<int, int> CSMWorld::ConstInfoSelectWrapper::getValidIntRange() const
|
||||||
|
{
|
||||||
|
const int IntMax = std::numeric_limits<int>::max();
|
||||||
|
const int IntMin = std::numeric_limits<int>::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<int, int>(0, 1);
|
||||||
|
|
||||||
|
// Integer
|
||||||
|
case Function_RankLow:
|
||||||
|
case Function_RankHigh:
|
||||||
|
case Function_Reputation:
|
||||||
|
case Function_PcReputation:
|
||||||
|
case Function_Journal:
|
||||||
|
return std::pair<int, int>(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<int, int>(0, IntMax);
|
||||||
|
|
||||||
|
case Function_Fight:
|
||||||
|
case Function_Hello:
|
||||||
|
case Function_Alarm:
|
||||||
|
case Function_Flee:
|
||||||
|
return std::pair<int, int>(0, 100);
|
||||||
|
|
||||||
|
case Function_Weather:
|
||||||
|
return std::pair<int, int>(0, 9);
|
||||||
|
|
||||||
|
case Function_FriendHit:
|
||||||
|
return std::pair<int, int>(0, 4);
|
||||||
|
|
||||||
|
case Function_RankRequirement:
|
||||||
|
return std::pair<int, int>(0, 3);
|
||||||
|
|
||||||
|
case Function_CreatureTarget:
|
||||||
|
return std::pair<int, int>(0, 2);
|
||||||
|
|
||||||
|
case Function_PcGender:
|
||||||
|
return std::pair<int, int>(0, 1);
|
||||||
|
|
||||||
|
case Function_FactionRankDifference:
|
||||||
|
return std::pair<int, int>(-9, 9);
|
||||||
|
|
||||||
|
// Numeric
|
||||||
|
case Function_Global:
|
||||||
|
case Function_Local:
|
||||||
|
return std::pair<int, int>(IntMin, IntMax);
|
||||||
|
|
||||||
|
case Function_PcMagicka:
|
||||||
|
case Function_PcFatigue:
|
||||||
|
case Function_PcHealth:
|
||||||
|
return std::pair<int, int>(0, IntMax);
|
||||||
|
|
||||||
|
case Function_Health_Percent:
|
||||||
|
case Function_PcHealthPercent:
|
||||||
|
return std::pair<int, int>(0, 100);
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("InfoSelectWrapper: function does not exist");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<float, float> CSMWorld::ConstInfoSelectWrapper::getValidFloatRange() const
|
||||||
|
{
|
||||||
|
const float FloatMax = std::numeric_limits<float>::infinity();
|
||||||
|
const float FloatMin = -std::numeric_limits<float>::infinity();
|
||||||
|
|
||||||
|
switch (mFunctionName)
|
||||||
|
{
|
||||||
|
// Numeric
|
||||||
|
case Function_Global:
|
||||||
|
case Function_Local:
|
||||||
|
case Function_NotLocal:
|
||||||
|
return std::pair<float, float>(FloatMin, FloatMax);
|
||||||
|
|
||||||
|
case Function_PcMagicka:
|
||||||
|
case Function_PcFatigue:
|
||||||
|
case Function_PcHealth:
|
||||||
|
return std::pair<float, float>(0, FloatMax);
|
||||||
|
|
||||||
|
case Function_Health_Percent:
|
||||||
|
case Function_PcHealthPercent:
|
||||||
|
return std::pair<float, float>(0, 100);
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("InfoSelectWrapper: function does not exist or is not numeric");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T1, typename T2>
|
||||||
|
bool CSMWorld::ConstInfoSelectWrapper::rangeContains(T1 value, std::pair<T2,T2> range) const
|
||||||
|
{
|
||||||
|
return (value >= range.first && value <= range.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T1, typename T2>
|
||||||
|
bool CSMWorld::ConstInfoSelectWrapper::rangeFullyContains(std::pair<T1,T1> containingRange,
|
||||||
|
std::pair<T2,T2> testRange) const
|
||||||
|
{
|
||||||
|
return (containingRange.first <= testRange.first) && (testRange.second <= containingRange.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T1, typename T2>
|
||||||
|
bool CSMWorld::ConstInfoSelectWrapper::rangesOverlap(std::pair<T1,T1> range1, std::pair<T2,T2> 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 <typename T1, typename T2>
|
||||||
|
bool CSMWorld::ConstInfoSelectWrapper::rangesMatch(std::pair<T1,T1> range1, std::pair<T2,T2> range2) const
|
||||||
|
{
|
||||||
|
return (range1.first == range2.first && range1.second == range2.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T1, typename T2>
|
||||||
|
bool CSMWorld::ConstInfoSelectWrapper::conditionIsAlwaysTrue(std::pair<T1,T1> conditionRange,
|
||||||
|
std::pair<T2,T2> 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 <typename T1, typename T2>
|
||||||
|
bool CSMWorld::ConstInfoSelectWrapper::conditionIsNeverTrue(std::pair<T1,T1> conditionRange,
|
||||||
|
std::pair<T2,T2> 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<size_t>(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;
|
||||||
|
}
|
@ -0,0 +1,243 @@
|
|||||||
|
#ifndef CSM_WORLD_INFOSELECTWRAPPER_H
|
||||||
|
#define CSM_WORLD_INFOSELECTWRAPPER_H
|
||||||
|
|
||||||
|
#include <components/esm/loadinfo.hpp>
|
||||||
|
|
||||||
|
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<int, int> getConditionIntRange() const;
|
||||||
|
std::pair<float, float> getConditionFloatRange() const;
|
||||||
|
|
||||||
|
std::pair<int, int> getValidIntRange() const;
|
||||||
|
std::pair<float, float> getValidFloatRange() const;
|
||||||
|
|
||||||
|
template <typename Type1, typename Type2>
|
||||||
|
bool rangeContains(Type1 value, std::pair<Type2,Type2> range) const;
|
||||||
|
|
||||||
|
template <typename Type1, typename Type2>
|
||||||
|
bool rangesOverlap(std::pair<Type1,Type1> range1, std::pair<Type2,Type2> range2) const;
|
||||||
|
|
||||||
|
template <typename Type1, typename Type2>
|
||||||
|
bool rangeFullyContains(std::pair<Type1,Type1> containing, std::pair<Type2,Type2> test) const;
|
||||||
|
|
||||||
|
template <typename Type1, typename Type2>
|
||||||
|
bool rangesMatch(std::pair<Type1,Type1> range1, std::pair<Type2,Type2> range2) const;
|
||||||
|
|
||||||
|
template <typename Type1, typename Type2>
|
||||||
|
bool conditionIsAlwaysTrue(std::pair<Type1,Type1> conditionRange, std::pair<Type2,Type2> validRange) const;
|
||||||
|
|
||||||
|
template <typename Type1, typename Type2>
|
||||||
|
bool conditionIsNeverTrue(std::pair<Type1,Type1> conditionRange, std::pair<Type2,Type2> 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
|
Loading…
Reference in New Issue