Merge remote-tracking branch 'aesylwinn/topic-info-editor-improvements'

move
Marc Zinnschlag 9 years ago
commit 9f14247562

@ -26,7 +26,7 @@ opencs_units_noqt (model/world
universalid record commands columnbase columnimp scriptcontext cell refidcollection
refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection tablemimedata cellcoordinates cellselection resources resourcesmanager scope
pathgrid landtexture land nestedtablewrapper nestedcollection nestedcoladapterimp nestedinfocollection
idcompletionmanager metadata defaultgmsts
idcompletionmanager metadata defaultgmsts infoselectwrapper
)
opencs_hdrs_noqt (model/world
@ -42,7 +42,7 @@ opencs_units_noqt (model/tools
mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck
birthsigncheck spellcheck referencecheck referenceablecheck scriptcheck bodypartcheck
startscriptcheck search searchoperation searchstage pathgridcheck soundgencheck magiceffectcheck
mergestages gmstcheck
mergestages gmstcheck topicinfocheck journalcheck
)
opencs_hdrs_noqt (model/tools

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

@ -30,6 +30,8 @@
#include "magiceffectcheck.hpp"
#include "mergeoperation.hpp"
#include "gmstcheck.hpp"
#include "topicinfocheck.hpp"
#include "journalcheck.hpp"
CSMDoc::OperationHolder *CSMTools::Tools::get (int type)
{
@ -114,6 +116,21 @@ 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)));
mVerifierOperation->appendStage (new JournalCheckStage(mData.getJournals(), mData.getJournalInfos()));
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

@ -3,6 +3,7 @@
#include <components/misc/stringops.hpp>
#include "universalid.hpp"
#include "infoselectwrapper.hpp"
namespace CSMWorld
{
@ -273,8 +274,8 @@ namespace CSMWorld
{ ColumnId_InfoList, "Info List" },
{ ColumnId_InfoCondition, "Info Conditions" },
{ ColumnId_InfoCondFunc, "Function" },
{ ColumnId_InfoCondVar, "Func/Variable" },
{ ColumnId_InfoCondComp, "Comp" },
{ ColumnId_InfoCondVar, "Variable/Object" },
{ ColumnId_InfoCondComp, "Relation" },
{ ColumnId_InfoCondValue, "Values" },
{ ColumnId_OriginalCell, "Original Cell" },
@ -546,18 +547,6 @@ namespace
"AI Wander", "AI Travel", "AI Follow", "AI Escort", "AI Activate", 0
};
static const char *sInfoCondFunc[] =
{
" ", "Function", "Global", "Local", "Journal",
"Item", "Dead", "Not ID", "Not Faction", "Not Class",
"Not Race", "Not Cell", "Not Local", 0
};
static const char *sInfoCondComp[] =
{
"!=", "<", "<=", "=", ">", ">=", 0
};
const char **getEnumNames (CSMWorld::Columns::ColumnId column)
{
switch (column)
@ -585,10 +574,8 @@ namespace
case CSMWorld::Columns::ColumnId_EffectId: return sEffectId;
case CSMWorld::Columns::ColumnId_PartRefType: return sPartRefType;
case CSMWorld::Columns::ColumnId_AiPackageType: return sAiPackageType;
case CSMWorld::Columns::ColumnId_InfoCondFunc: return sInfoCondFunc;
// FIXME: don't have dynamic value enum delegate, use Display_String for now
//case CSMWorld::Columns::ColumnId_InfoCond: return sInfoCond;
case CSMWorld::Columns::ColumnId_InfoCondComp: return sInfoCondComp;
case CSMWorld::Columns::ColumnId_InfoCondFunc: return CSMWorld::ConstInfoSelectWrapper::FunctionEnumStrings;
case CSMWorld::Columns::ColumnId_InfoCondComp: return CSMWorld::ConstInfoSelectWrapper::RelationEnumStrings;
default: return 0;
}

@ -271,7 +271,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc
new NestedChildColumn (Columns::ColumnId_InfoCondFunc, ColumnBase::Display_InfoCondFunc));
// FIXME: don't have dynamic value enum delegate, use Display_String for now
mTopicInfos.getNestableColumn(index)->addColumn(
new NestedChildColumn (Columns::ColumnId_InfoCondVar, ColumnBase::Display_String));
new NestedChildColumn (Columns::ColumnId_InfoCondVar, ColumnBase::Display_InfoCondVar));
mTopicInfos.getNestableColumn(index)->addColumn(
new NestedChildColumn (Columns::ColumnId_InfoCondComp, ColumnBase::Display_InfoCondComp));
mTopicInfos.getNestableColumn(index)->addColumn(

@ -60,6 +60,10 @@ std::vector<CSMWorld::ColumnBase::Display> CSMWorld::IdCompletionManager::getDis
{
types.push_back(current->first);
}
// Hack for Display_InfoCondVar
types.push_back(CSMWorld::ColumnBase::Display_InfoCondVar);
return types;
}

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

@ -6,6 +6,7 @@
#include "idcollection.hpp"
#include "pathgrid.hpp"
#include "info.hpp"
#include "infoselectwrapper.hpp"
namespace CSMWorld
{
@ -529,16 +530,6 @@ namespace CSMWorld
return 1; // fixed at size 1
}
// ESM::DialInfo::SelectStruct.mSelectRule
// 012345...
// ^^^ ^^
// ||| ||
// ||| |+------------- condition variable string
// ||| +-------------- comparison type, ['0'..'5']; e.g. !=, <, >=, etc
// ||+---------------- function index (encoded, where function == '1')
// |+----------------- function, ['1'..'C']; e.g. Global, Local, Not ID, etc
// +------------------ unknown
//
InfoConditionAdapter::InfoConditionAdapter () {}
void InfoConditionAdapter::addRow(Record<Info>& record, int position) const
@ -547,11 +538,11 @@ namespace CSMWorld
std::vector<ESM::DialInfo::SelectStruct>& conditions = info.mSelects;
// blank row
// default row
ESM::DialInfo::SelectStruct condStruct;
condStruct.mSelectRule = "00000";
condStruct.mSelectRule = "01000";
condStruct.mValue = ESM::Variant();
condStruct.mValue.setType(ESM::VT_Int); // default to ints
condStruct.mValue.setType(ESM::VT_Int);
conditions.insert(conditions.begin()+position, condStruct);
@ -589,89 +580,6 @@ namespace CSMWorld
return new NestedTableWrapper<std::vector<ESM::DialInfo::SelectStruct> >(record.get().mSelects);
}
// See the mappings in MWDialogue::SelectWrapper::getArgument
// from ESM::Attribute, ESM::Skill and MWMechanics::CreatureStats (for AI)
static std::map<const std::string, std::string> populateEncToInfoFunc()
{
std::map<const std::string, std::string> funcMap;
funcMap["00"] = "Rank Low";
funcMap["01"] = "Rank High";
funcMap["02"] = "Rank Requirement";
funcMap["03"] = "Reputation";
funcMap["04"] = "Health Percent";
funcMap["05"] = "PC Reputation";
funcMap["06"] = "PC Level";
funcMap["07"] = "PC Health Percent";
funcMap["08"] = "PC Magicka";
funcMap["09"] = "PC Fatigue";
funcMap["10"] = "PC Strength";
funcMap["11"] = "PC Block";
funcMap["12"] = "PC Armorer";
funcMap["13"] = "PC Medium Armor";
funcMap["14"] = "PC Heavy Armor";
funcMap["15"] = "PC Blunt Weapon";
funcMap["16"] = "PC Long Blade";
funcMap["17"] = "PC Axe";
funcMap["18"] = "PC Spear";
funcMap["19"] = "PC Athletics";
funcMap["20"] = "PC Enchant";
funcMap["21"] = "PC Destruction";
funcMap["22"] = "PC Alteration";
funcMap["23"] = "PC Illusion";
funcMap["24"] = "PC Conjuration";
funcMap["25"] = "PC Mysticism";
funcMap["26"] = "PC Restoration";
funcMap["27"] = "PC Alchemy";
funcMap["28"] = "PC Unarmored";
funcMap["29"] = "PC Security";
funcMap["30"] = "PC Sneak";
funcMap["31"] = "PC Acrobatics";
funcMap["32"] = "PC Light Armor";
funcMap["33"] = "PC Short Blade";
funcMap["34"] = "PC Marksman";
funcMap["35"] = "PC Merchantile";
funcMap["36"] = "PC Speechcraft";
funcMap["37"] = "PC Hand To Hand";
funcMap["38"] = "PC Sex";
funcMap["39"] = "PC Expelled";
funcMap["40"] = "PC Common Disease";
funcMap["41"] = "PC Blight Disease";
funcMap["42"] = "PC Clothing Modifier";
funcMap["43"] = "PC Crime Level";
funcMap["44"] = "Same Sex";
funcMap["45"] = "Same Race";
funcMap["46"] = "Same Faction";
funcMap["47"] = "Faction Rank Difference";
funcMap["48"] = "Detected";
funcMap["49"] = "Alarmed";
funcMap["50"] = "Choice";
funcMap["51"] = "PC Intelligence";
funcMap["52"] = "PC Willpower";
funcMap["53"] = "PC Agility";
funcMap["54"] = "PC Speed";
funcMap["55"] = "PC Endurance";
funcMap["56"] = "PC Personality";
funcMap["57"] = "PC Luck";
funcMap["58"] = "PC Corpus";
funcMap["59"] = "Weather";
funcMap["60"] = "PC Vampire";
funcMap["61"] = "Level";
funcMap["62"] = "Attacked";
funcMap["63"] = "Talked To PC";
funcMap["64"] = "PC Health";
funcMap["65"] = "Creature Target";
funcMap["66"] = "Friend Hit";
funcMap["67"] = "Fight";
funcMap["68"] = "Hello";
funcMap["69"] = "Alarm";
funcMap["70"] = "Flee";
funcMap["71"] = "Should Attack";
funcMap["72"] = "Werewolf";
funcMap["73"] = "PC Werewolf Kills";
return funcMap;
}
static const std::map<const std::string, std::string> sEncToInfoFunc = populateEncToInfoFunc();
QVariant InfoConditionAdapter::getData(const Record<Info>& record,
int subRowIndex, int subColIndex) const
{
@ -682,70 +590,36 @@ namespace CSMWorld
if (subRowIndex < 0 || subRowIndex >= static_cast<int> (conditions.size()))
throw std::runtime_error ("index out of range");
ConstInfoSelectWrapper infoSelectWrapper(conditions[subRowIndex]);
switch (subColIndex)
{
case 0:
{
char condType = conditions[subRowIndex].mSelectRule[1];
switch (condType)
{
case '0': return 0; // blank space
case '1': return 1; // Function
case '2': return 2; // Global
case '3': return 3; // Local
case '4': return 4; // Journal
case '5': return 5; // Item
case '6': return 6; // Dead
case '7': return 7; // Not ID
case '8': return 8; // Not Factio
case '9': return 9; // Not Class
case 'A': return 10; // Not Race
case 'B': return 11; // Not Cell
case 'C': return 12; // Not Local
default: return QVariant(); // TODO: log an error?
}
return infoSelectWrapper.getFunctionName();
}
case 1:
{
if (conditions[subRowIndex].mSelectRule[1] == '1')
{
// throws an exception if the encoding is not found
return sEncToInfoFunc.at(conditions[subRowIndex].mSelectRule.substr(2, 2)).c_str();
}
if (infoSelectWrapper.hasVariable())
return QString(infoSelectWrapper.getVariableName().c_str());
else
return QString(conditions[subRowIndex].mSelectRule.substr(5).c_str());
return "";
}
case 2:
{
char compType = conditions[subRowIndex].mSelectRule[4];
switch (compType)
{
case '0': return 3; // =
case '1': return 0; // !=
case '2': return 4; // >
case '3': return 5; // >=
case '4': return 1; // <
case '5': return 2; // <=
default: return QVariant(); // TODO: log an error?
}
return infoSelectWrapper.getRelationType();
}
case 3:
{
switch (conditions[subRowIndex].mValue.getType())
switch (infoSelectWrapper.getVariant().getType())
{
case ESM::VT_String:
{
return QString::fromUtf8 (conditions[subRowIndex].mValue.getString().c_str());
}
case ESM::VT_Int:
case ESM::VT_Short:
case ESM::VT_Long:
{
return conditions[subRowIndex].mValue.getInteger();
return infoSelectWrapper.getVariant().getInteger();
}
case ESM::VT_Float:
{
return conditions[subRowIndex].mValue.getFloat();
return infoSelectWrapper.getVariant().getFloat();
}
default: return QVariant();
}
@ -764,101 +638,63 @@ namespace CSMWorld
if (subRowIndex < 0 || subRowIndex >= static_cast<int> (conditions.size()))
throw std::runtime_error ("index out of range");
InfoSelectWrapper infoSelectWrapper(conditions[subRowIndex]);
bool conversionResult = false;
switch (subColIndex)
{
case 0:
case 0: // Function
{
// See sInfoCondFunc in columns.cpp for the enum values
switch (value.toInt())
infoSelectWrapper.setFunctionName(static_cast<ConstInfoSelectWrapper::FunctionName>(value.toInt()));
if (infoSelectWrapper.getComparisonType() != ConstInfoSelectWrapper::Comparison_Numeric &&
infoSelectWrapper.getVariant().getType() != ESM::VT_Int)
{
// FIXME: when these change the values of the other columns need to change
// correspondingly (and automatically)
case 1:
{
conditions[subRowIndex].mSelectRule[1] = '1'; // Function
// default to "Rank Low"
conditions[subRowIndex].mSelectRule[2] = '0';
conditions[subRowIndex].mSelectRule[3] = '0';
break;
}
case 2: conditions[subRowIndex].mSelectRule[1] = '2'; break; // Global
case 3: conditions[subRowIndex].mSelectRule[1] = '3'; break; // Local
case 4: conditions[subRowIndex].mSelectRule[1] = '4'; break; // Journal
case 5: conditions[subRowIndex].mSelectRule[1] = '5'; break; // Item
case 6: conditions[subRowIndex].mSelectRule[1] = '6'; break; // Dead
case 7: conditions[subRowIndex].mSelectRule[1] = '7'; break; // Not ID
case 8: conditions[subRowIndex].mSelectRule[1] = '8'; break; // Not Faction
case 9: conditions[subRowIndex].mSelectRule[1] = '9'; break; // Not Class
case 10: conditions[subRowIndex].mSelectRule[1] = 'A'; break; // Not Race
case 11: conditions[subRowIndex].mSelectRule[1] = 'B'; break; // Not Cell
case 12: conditions[subRowIndex].mSelectRule[1] = 'C'; break; // Not Local
default: return; // return without saving
infoSelectWrapper.getVariant().setType(ESM::VT_Int);
}
infoSelectWrapper.update();
break;
}
case 1:
case 1: // Variable
{
if (conditions[subRowIndex].mSelectRule[1] == '1')
{
std::map<const std::string, std::string>::const_iterator it = sEncToInfoFunc.begin();
for (;it != sEncToInfoFunc.end(); ++it)
{
if (it->second == value.toString().toUtf8().constData())
{
std::string rule = conditions[subRowIndex].mSelectRule.substr(0, 2);
rule.append(it->first);
// leave old values for undo (NOTE: may not be vanilla's behaviour)
rule.append(conditions[subRowIndex].mSelectRule.substr(4));
conditions[subRowIndex].mSelectRule = rule;
break;
}
}
if (it == sEncToInfoFunc.end())
return; // return without saving; TODO: maybe log an error here
}
else
{
// FIXME: validate the string values before saving, based on the current function
std::string rule = conditions[subRowIndex].mSelectRule.substr(0, 5);
conditions[subRowIndex].mSelectRule = rule.append(value.toString().toUtf8().constData());
}
infoSelectWrapper.setVariableName(value.toString().toUtf8().constData());
infoSelectWrapper.update();
break;
}
case 2:
case 2: // Relation
{
// See sInfoCondComp in columns.cpp for the enum values
switch (value.toInt())
{
case 0: conditions[subRowIndex].mSelectRule[4] = '1'; break; // !=
case 1: conditions[subRowIndex].mSelectRule[4] = '4'; break; // <
case 2: conditions[subRowIndex].mSelectRule[4] = '5'; break; // <=
case 3: conditions[subRowIndex].mSelectRule[4] = '0'; break; // =
case 4: conditions[subRowIndex].mSelectRule[4] = '2'; break; // >
case 5: conditions[subRowIndex].mSelectRule[4] = '3'; break; // >=
default: return; // return without saving
}
infoSelectWrapper.setRelationType(static_cast<ConstInfoSelectWrapper::RelationType>(value.toInt()));
infoSelectWrapper.update();
break;
}
case 3:
case 3: // Value
{
switch (conditions[subRowIndex].mValue.getType())
switch (infoSelectWrapper.getComparisonType())
{
case ESM::VT_String:
case ConstInfoSelectWrapper::Comparison_Numeric:
{
conditions[subRowIndex].mValue.setString (value.toString().toUtf8().constData());
break;
}
case ESM::VT_Int:
case ESM::VT_Short:
case ESM::VT_Long:
{
conditions[subRowIndex].mValue.setInteger (value.toInt());
// QVariant seems to have issues converting 0
if ((value.toInt(&conversionResult) && conversionResult) || value.toString().compare("0") == 0)
{
infoSelectWrapper.getVariant().setType(ESM::VT_Int);
infoSelectWrapper.getVariant().setInteger(value.toInt());
}
else if (value.toFloat(&conversionResult) && conversionResult)
{
infoSelectWrapper.getVariant().setType(ESM::VT_Float);
infoSelectWrapper.getVariant().setFloat(value.toFloat());
}
break;
}
case ESM::VT_Float:
case ConstInfoSelectWrapper::Comparison_Boolean:
case ConstInfoSelectWrapper::Comparison_Integer:
{
conditions[subRowIndex].mValue.setFloat (value.toFloat());
if ((value.toInt(&conversionResult) && conversionResult) || value.toString().compare("0") == 0)
{
infoSelectWrapper.getVariant().setType(ESM::VT_Int);
infoSelectWrapper.getVariant().setInteger(value.toInt());
}
break;
}
default: break;

@ -1,6 +1,7 @@
#include "idcompletiondelegate.hpp"
#include "../../model/world/idcompletionmanager.hpp"
#include "../../model/world/infoselectwrapper.hpp"
#include "../widget/droplineedit.hpp"
@ -27,6 +28,56 @@ QWidget *CSVWorld::IdCompletionDelegate::createEditor(QWidget *parent,
return NULL;
}
// The completer for InfoCondVar needs to return a completer based on the first column
if (display == CSMWorld::ColumnBase::Display_InfoCondVar)
{
QModelIndex sibling = index.sibling(index.row(), 0);
int conditionFunction = sibling.model()->data(sibling, Qt::EditRole).toInt();
switch (conditionFunction)
{
case CSMWorld::ConstInfoSelectWrapper::Function_Global:
{
return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_GlobalVariable);
}
case CSMWorld::ConstInfoSelectWrapper::Function_Journal:
{
return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Journal);
}
case CSMWorld::ConstInfoSelectWrapper::Function_Item:
{
return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Referenceable);
}
case CSMWorld::ConstInfoSelectWrapper::Function_Dead:
case CSMWorld::ConstInfoSelectWrapper::Function_NotId:
{
return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Referenceable);
}
case CSMWorld::ConstInfoSelectWrapper::Function_NotFaction:
{
return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Faction);
}
case CSMWorld::ConstInfoSelectWrapper::Function_NotClass:
{
return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Class);
}
case CSMWorld::ConstInfoSelectWrapper::Function_NotRace:
{
return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Race);
}
case CSMWorld::ConstInfoSelectWrapper::Function_NotCell:
{
return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Cell);
}
case CSMWorld::ConstInfoSelectWrapper::Function_Local:
case CSMWorld::ConstInfoSelectWrapper::Function_NotLocal:
{
return new CSVWidget::DropLineEdit(display, parent);
}
default: return 0; // The rest of them can't be edited anyway
}
}
CSMWorld::IdCompletionManager &completionManager = getDocument().getIdCompletionManager();
CSVWidget::DropLineEdit *editor = new CSVWidget::DropLineEdit(display, parent);
editor->setCompleter(completionManager.getCompleter(display).get());

Loading…
Cancel
Save