Merge branch 'filterview' into 'master'

Validate INFO filters when loading the record

See merge request OpenMW/openmw!4003
pull/3235/head
psi29a 4 weeks ago
commit 3600c6c7c7

@ -1,5 +1,6 @@
#include "labels.hpp" #include "labels.hpp"
#include <components/esm3/dialoguecondition.hpp>
#include <components/esm3/loadalch.hpp> #include <components/esm3/loadalch.hpp>
#include <components/esm3/loadbody.hpp> #include <components/esm3/loadbody.hpp>
#include <components/esm3/loadcell.hpp> #include <components/esm3/loadcell.hpp>
@ -572,13 +573,14 @@ std::string_view enchantTypeLabel(int idx)
std::string_view ruleFunction(int idx) std::string_view ruleFunction(int idx)
{ {
if (idx >= 0 && idx <= 72) if (idx >= ESM::DialogueCondition::Function_FacReactionLowest
&& idx <= ESM::DialogueCondition::Function_PcWerewolfKills)
{ {
static constexpr std::string_view ruleFunctions[] = { static constexpr std::string_view ruleFunctions[] = {
"Reaction Low", "Lowest Faction Reaction",
"Reaction High", "Highest Faction Reaction",
"Rank Requirement", "Rank Requirement",
"NPC? Reputation", "NPC Reputation",
"Health Percent", "Health Percent",
"Player Reputation", "Player Reputation",
"NPC Level", "NPC Level",
@ -648,6 +650,7 @@ std::string_view ruleFunction(int idx)
"Flee", "Flee",
"Should Attack", "Should Attack",
"Werewolf", "Werewolf",
"Werewolf Kills",
}; };
return ruleFunctions[idx]; return ruleFunctions[idx];
} }

@ -57,112 +57,82 @@ namespace
std::cout << " Cell Name: " << p.mCellName << std::endl; std::cout << " Cell Name: " << p.mCellName << std::endl;
} }
std::string ruleString(const ESM::DialInfo::SelectStruct& ss) std::string ruleString(const ESM::DialogueCondition& ss)
{ {
std::string rule = ss.mSelectRule; std::string_view type_str = "INVALID";
std::string_view func_str;
if (rule.length() < 5) switch (ss.mFunction)
return "INVALID";
char type = rule[1];
char indicator = rule[2];
std::string type_str = "INVALID";
std::string func_str = Misc::StringUtils::format("INVALID=%s", rule.substr(1, 3));
int func = Misc::StringUtils::toNumeric<int>(rule.substr(2, 2), 0);
switch (type)
{ {
case '1': case ESM::DialogueCondition::Function_Global:
type_str = "Function"; type_str = "Global";
func_str = std::string(ruleFunction(func)); func_str = ss.mVariable;
break; break;
case '2': case ESM::DialogueCondition::Function_Local:
if (indicator == 's') type_str = "Local";
type_str = "Global short"; func_str = ss.mVariable;
else if (indicator == 'l')
type_str = "Global long";
else if (indicator == 'f')
type_str = "Global float";
break; break;
case '3': case ESM::DialogueCondition::Function_Journal:
if (indicator == 's') type_str = "Journal";
type_str = "Local short"; func_str = ss.mVariable;
else if (indicator == 'l')
type_str = "Local long";
else if (indicator == 'f')
type_str = "Local float";
break; break;
case '4': case ESM::DialogueCondition::Function_Item:
if (indicator == 'J') type_str = "Item count";
type_str = "Journal"; func_str = ss.mVariable;
break; break;
case '5': case ESM::DialogueCondition::Function_Dead:
if (indicator == 'I') type_str = "Dead";
type_str = "Item type"; func_str = ss.mVariable;
break; break;
case '6': case ESM::DialogueCondition::Function_NotId:
if (indicator == 'D') type_str = "Not ID";
type_str = "NPC Dead"; func_str = ss.mVariable;
break; break;
case '7': case ESM::DialogueCondition::Function_NotFaction:
if (indicator == 'X') type_str = "Not Faction";
type_str = "Not ID"; func_str = ss.mVariable;
break; break;
case '8': case ESM::DialogueCondition::Function_NotClass:
if (indicator == 'F') type_str = "Not Class";
type_str = "Not Faction"; func_str = ss.mVariable;
break; break;
case '9': case ESM::DialogueCondition::Function_NotRace:
if (indicator == 'C') type_str = "Not Race";
type_str = "Not Class"; func_str = ss.mVariable;
break; break;
case 'A': case ESM::DialogueCondition::Function_NotCell:
if (indicator == 'R') type_str = "Not Cell";
type_str = "Not Race"; func_str = ss.mVariable;
break; break;
case 'B': case ESM::DialogueCondition::Function_NotLocal:
if (indicator == 'L') type_str = "Not Local";
type_str = "Not Cell"; func_str = ss.mVariable;
break;
case 'C':
if (indicator == 's')
type_str = "Not Local";
break; break;
default: default:
type_str = "Function";
func_str = ruleFunction(ss.mFunction);
break; break;
} }
// Append the variable name to the function string if any. std::string_view oper_str = "??";
if (type != '1') switch (ss.mComparison)
func_str = rule.substr(5);
// In the previous switch, we assumed that the second char was X
// for all types not qual to one. If this wasn't true, go back to
// the error message.
if (type != '1' && rule[3] != 'X')
func_str = Misc::StringUtils::format("INVALID=%s", rule.substr(1, 3));
char oper = rule[4];
std::string oper_str = "??";
switch (oper)
{ {
case '0': case ESM::DialogueCondition::Comp_Eq:
oper_str = "=="; oper_str = "==";
break; break;
case '1': case ESM::DialogueCondition::Comp_Ne:
oper_str = "!="; oper_str = "!=";
break; break;
case '2': case ESM::DialogueCondition::Comp_Gt:
oper_str = "> "; oper_str = "> ";
break; break;
case '3': case ESM::DialogueCondition::Comp_Ge:
oper_str = ">="; oper_str = ">=";
break; break;
case '4': case ESM::DialogueCondition::Comp_Ls:
oper_str = "< "; oper_str = "< ";
break; break;
case '5': case ESM::DialogueCondition::Comp_Le:
oper_str = "<="; oper_str = "<=";
break; break;
default: default:
@ -170,7 +140,7 @@ namespace
} }
std::ostringstream stream; std::ostringstream stream;
stream << ss.mValue; std::visit([&](auto value) { stream << value; }, ss.mValue);
std::string result std::string result
= Misc::StringUtils::format("%-12s %-32s %2s %s", type_str, func_str, oper_str, stream.str()); = Misc::StringUtils::format("%-12s %-32s %2s %s", type_str, func_str, oper_str, stream.str());
@ -842,7 +812,7 @@ namespace EsmTool
<< std::endl; << std::endl;
std::cout << " Type: " << dialogTypeLabel(mData.mData.mType) << std::endl; std::cout << " Type: " << dialogTypeLabel(mData.mData.mType) << std::endl;
for (const ESM::DialInfo::SelectStruct& rule : mData.mSelects) for (const auto& rule : mData.mSelects)
std::cout << " Select Rule: " << ruleString(rule) << std::endl; std::cout << " Select Rule: " << ruleString(rule) << std::endl;
if (!mData.mResultScript.empty()) if (!mData.mResultScript.empty())

@ -171,10 +171,9 @@ void CSMTools::TopicInfoCheckStage::perform(int stage, CSMDoc::Messages& message
// Check info conditions // Check info conditions
for (std::vector<ESM::DialInfo::SelectStruct>::const_iterator it = topicInfo.mSelects.begin(); for (const auto& select : topicInfo.mSelects)
it != topicInfo.mSelects.end(); ++it)
{ {
verifySelectStruct((*it), id, messages); verifySelectStruct(select, id, messages);
} }
} }
@ -308,49 +307,15 @@ bool CSMTools::TopicInfoCheckStage::verifyItem(
} }
bool CSMTools::TopicInfoCheckStage::verifySelectStruct( bool CSMTools::TopicInfoCheckStage::verifySelectStruct(
const ESM::DialInfo::SelectStruct& select, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) const ESM::DialogueCondition& select, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages)
{ {
CSMWorld::ConstInfoSelectWrapper infoCondition(select); CSMWorld::ConstInfoSelectWrapper infoCondition(select);
if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_None) if (select.mFunction == ESM::DialogueCondition::Function_None)
{ {
messages.add(id, "Invalid condition '" + infoCondition.toString() + "'", "", CSMDoc::Message::Severity_Error); messages.add(id, "Invalid condition '" + infoCondition.toString() + "'", "", CSMDoc::Message::Severity_Error);
return false; return false;
} }
else if (!infoCondition.variantTypeIsValid())
{
std::ostringstream stream;
stream << "Value of condition '" << infoCondition.toString() << "' has invalid ";
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;
}
stream << " type";
messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error);
return false;
}
else if (infoCondition.conditionIsAlwaysTrue()) else if (infoCondition.conditionIsAlwaysTrue())
{ {
messages.add( messages.add(
@ -365,48 +330,48 @@ bool CSMTools::TopicInfoCheckStage::verifySelectStruct(
} }
// Id checks // Id checks
if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Global if (select.mFunction == ESM::DialogueCondition::Function_Global
&& !verifyId(ESM::RefId::stringRefId(infoCondition.getVariableName()), mGlobals, id, messages)) && !verifyId(ESM::RefId::stringRefId(select.mVariable), mGlobals, id, messages))
{ {
return false; return false;
} }
else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Journal else if (select.mFunction == ESM::DialogueCondition::Function_Journal
&& !verifyId(ESM::RefId::stringRefId(infoCondition.getVariableName()), mJournals, id, messages)) && !verifyId(ESM::RefId::stringRefId(select.mVariable), mJournals, id, messages))
{ {
return false; return false;
} }
else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Item else if (select.mFunction == ESM::DialogueCondition::Function_Item
&& !verifyItem(ESM::RefId::stringRefId(infoCondition.getVariableName()), id, messages)) && !verifyItem(ESM::RefId::stringRefId(select.mVariable), id, messages))
{ {
return false; return false;
} }
else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Dead else if (select.mFunction == ESM::DialogueCondition::Function_Dead
&& !verifyActor(ESM::RefId::stringRefId(infoCondition.getVariableName()), id, messages)) && !verifyActor(ESM::RefId::stringRefId(select.mVariable), id, messages))
{ {
return false; return false;
} }
else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotId else if (select.mFunction == ESM::DialogueCondition::Function_NotId
&& !verifyActor(ESM::RefId::stringRefId(infoCondition.getVariableName()), id, messages)) && !verifyActor(ESM::RefId::stringRefId(select.mVariable), id, messages))
{ {
return false; return false;
} }
else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotFaction else if (select.mFunction == ESM::DialogueCondition::Function_NotFaction
&& !verifyId(ESM::RefId::stringRefId(infoCondition.getVariableName()), mFactions, id, messages)) && !verifyId(ESM::RefId::stringRefId(select.mVariable), mFactions, id, messages))
{ {
return false; return false;
} }
else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotClass else if (select.mFunction == ESM::DialogueCondition::Function_NotClass
&& !verifyId(ESM::RefId::stringRefId(infoCondition.getVariableName()), mClasses, id, messages)) && !verifyId(ESM::RefId::stringRefId(select.mVariable), mClasses, id, messages))
{ {
return false; return false;
} }
else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotRace else if (select.mFunction == ESM::DialogueCondition::Function_NotRace
&& !verifyId(ESM::RefId::stringRefId(infoCondition.getVariableName()), mRaces, id, messages)) && !verifyId(ESM::RefId::stringRefId(select.mVariable), mRaces, id, messages))
{ {
return false; return false;
} }
else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotCell else if (select.mFunction == ESM::DialogueCondition::Function_NotCell
&& !verifyCell(infoCondition.getVariableName(), id, messages)) && !verifyCell(select.mVariable, id, messages))
{ {
return false; return false;
} }

@ -84,7 +84,7 @@ namespace CSMTools
const ESM::RefId& name, int rank, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); const ESM::RefId& name, int rank, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages);
bool verifyItem(const ESM::RefId& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); bool verifyItem(const ESM::RefId& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages);
bool verifySelectStruct( bool verifySelectStruct(
const ESM::DialInfo::SelectStruct& select, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); const ESM::DialogueCondition& select, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages);
bool verifySound(const std::string& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); bool verifySound(const std::string& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages);
template <typename T> template <typename T>

File diff suppressed because it is too large Load Diff

@ -7,133 +7,13 @@
#include <components/esm3/loadinfo.hpp> #include <components/esm3/loadinfo.hpp>
namespace ESM #include <QVariant>
{
class Variant;
}
namespace CSMWorld 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 class ConstInfoSelectWrapper
{ {
public: 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 enum ComparisonType
{ {
Comparison_Boolean, Comparison_Boolean,
@ -143,25 +23,13 @@ namespace CSMWorld
Comparison_None 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* FunctionEnumStrings[];
static const char* RelationEnumStrings[]; 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); ConstInfoSelectWrapper(const ESM::DialogueCondition& select);
FunctionName getFunctionName() const; ESM::DialogueCondition::Function getFunctionName() const;
RelationType getRelationType() const; ESM::DialogueCondition::Comparison getRelationType() const;
ComparisonType getComparisonType() const; ComparisonType getComparisonType() const;
bool hasVariable() const; bool hasVariable() const;
@ -169,17 +37,12 @@ namespace CSMWorld
bool conditionIsAlwaysTrue() const; bool conditionIsAlwaysTrue() const;
bool conditionIsNeverTrue() const; bool conditionIsNeverTrue() const;
bool variantTypeIsValid() const;
const ESM::Variant& getVariant() const; QVariant getValue() const;
std::string toString() const; std::string toString() const;
protected: protected:
void readRule();
void readFunctionName();
void readRelationType();
void readVariableName();
void updateHasVariable(); void updateHasVariable();
void updateComparisonType(); void updateComparisonType();
@ -207,38 +70,29 @@ namespace CSMWorld
template <typename Type1, typename Type2> template <typename Type1, typename Type2>
bool conditionIsNeverTrue(std::pair<Type1, Type1> conditionRange, std::pair<Type2, Type2> validRange) const; bool conditionIsNeverTrue(std::pair<Type1, Type1> conditionRange, std::pair<Type2, Type2> validRange) const;
FunctionName mFunctionName;
RelationType mRelationType;
ComparisonType mComparisonType; ComparisonType mComparisonType;
bool mHasVariable; bool mHasVariable;
std::string mVariableName;
private: private:
const ESM::DialInfo::SelectStruct& mConstSelect; const ESM::DialogueCondition& mConstSelect;
}; };
// Wrapper for DialInfo::SelectStruct that can modify the wrapped select struct // Wrapper for DialogueCondition that can modify the wrapped select struct
class InfoSelectWrapper : public ConstInfoSelectWrapper class InfoSelectWrapper : public ConstInfoSelectWrapper
{ {
public: public:
InfoSelectWrapper(ESM::DialInfo::SelectStruct& select); InfoSelectWrapper(ESM::DialogueCondition& select);
// Wrapped SelectStruct will not be modified until update() is called // Wrapped SelectStruct will not be modified until update() is called
void setFunctionName(FunctionName name); void setFunctionName(ESM::DialogueCondition::Function name);
void setRelationType(RelationType type); void setRelationType(ESM::DialogueCondition::Comparison type);
void setVariableName(const std::string& name); void setVariableName(const std::string& name);
void setValue(int value);
// Modified wrapped SelectStruct void setValue(float value);
void update();
// This sets properties based on the function name to its defaults and updates the wrapped object
void setDefaults();
ESM::Variant& getVariant();
private: private:
ESM::DialInfo::SelectStruct& mSelect; ESM::DialogueCondition& mSelect;
void writeRule(); void writeRule();
}; };

@ -538,13 +538,11 @@ namespace CSMWorld
{ {
Info info = record.get(); Info info = record.get();
std::vector<ESM::DialInfo::SelectStruct>& conditions = info.mSelects; auto& conditions = info.mSelects;
// default row // default row
ESM::DialInfo::SelectStruct condStruct; ESM::DialogueCondition condStruct;
condStruct.mSelectRule = "01000"; condStruct.mIndex = conditions.size();
condStruct.mValue = ESM::Variant();
condStruct.mValue.setType(ESM::VT_Int);
conditions.insert(conditions.begin() + position, condStruct); conditions.insert(conditions.begin() + position, condStruct);
@ -555,7 +553,7 @@ namespace CSMWorld
{ {
Info info = record.get(); Info info = record.get();
std::vector<ESM::DialInfo::SelectStruct>& conditions = info.mSelects; auto& conditions = info.mSelects;
if (rowToRemove < 0 || rowToRemove >= static_cast<int>(conditions.size())) if (rowToRemove < 0 || rowToRemove >= static_cast<int>(conditions.size()))
throw std::runtime_error("index out of range"); throw std::runtime_error("index out of range");
@ -569,8 +567,8 @@ namespace CSMWorld
{ {
Info info = record.get(); Info info = record.get();
info.mSelects = static_cast<const NestedTableWrapper<std::vector<ESM::DialInfo::SelectStruct>>&>(nestedTable) info.mSelects
.mNestedTable; = static_cast<const NestedTableWrapper<std::vector<ESM::DialogueCondition>>&>(nestedTable).mNestedTable;
record.setModified(info); record.setModified(info);
} }
@ -578,14 +576,14 @@ namespace CSMWorld
NestedTableWrapperBase* InfoConditionAdapter::table(const Record<Info>& record) const NestedTableWrapperBase* InfoConditionAdapter::table(const Record<Info>& record) const
{ {
// deleted by dtor of NestedTableStoring // deleted by dtor of NestedTableStoring
return new NestedTableWrapper<std::vector<ESM::DialInfo::SelectStruct>>(record.get().mSelects); return new NestedTableWrapper<std::vector<ESM::DialogueCondition>>(record.get().mSelects);
} }
QVariant InfoConditionAdapter::getData(const Record<Info>& record, int subRowIndex, int subColIndex) const QVariant InfoConditionAdapter::getData(const Record<Info>& record, int subRowIndex, int subColIndex) const
{ {
Info info = record.get(); Info info = record.get();
std::vector<ESM::DialInfo::SelectStruct>& conditions = info.mSelects; auto& conditions = info.mSelects;
if (subRowIndex < 0 || subRowIndex >= static_cast<int>(conditions.size())) if (subRowIndex < 0 || subRowIndex >= static_cast<int>(conditions.size()))
throw std::runtime_error("index out of range"); throw std::runtime_error("index out of range");
@ -607,23 +605,11 @@ namespace CSMWorld
} }
case 2: case 2:
{ {
return infoSelectWrapper.getRelationType(); return infoSelectWrapper.getRelationType() - ESM::DialogueCondition::Comp_Eq;
} }
case 3: case 3:
{ {
switch (infoSelectWrapper.getVariant().getType()) return infoSelectWrapper.getValue();
{
case ESM::VT_Int:
{
return infoSelectWrapper.getVariant().getInteger();
}
case ESM::VT_Float:
{
return infoSelectWrapper.getVariant().getFloat();
}
default:
return QVariant();
}
} }
default: default:
throw std::runtime_error("Info condition subcolumn index out of range"); throw std::runtime_error("Info condition subcolumn index out of range");
@ -635,7 +621,7 @@ namespace CSMWorld
{ {
Info info = record.get(); Info info = record.get();
std::vector<ESM::DialInfo::SelectStruct>& conditions = info.mSelects; auto& conditions = info.mSelects;
if (subRowIndex < 0 || subRowIndex >= static_cast<int>(conditions.size())) if (subRowIndex < 0 || subRowIndex >= static_cast<int>(conditions.size()))
throw std::runtime_error("index out of range"); throw std::runtime_error("index out of range");
@ -647,27 +633,18 @@ namespace CSMWorld
{ {
case 0: // Function case 0: // Function
{ {
infoSelectWrapper.setFunctionName(static_cast<ConstInfoSelectWrapper::FunctionName>(value.toInt())); infoSelectWrapper.setFunctionName(static_cast<ESM::DialogueCondition::Function>(value.toInt()));
if (infoSelectWrapper.getComparisonType() != ConstInfoSelectWrapper::Comparison_Numeric
&& infoSelectWrapper.getVariant().getType() != ESM::VT_Int)
{
infoSelectWrapper.getVariant().setType(ESM::VT_Int);
}
infoSelectWrapper.update();
break; break;
} }
case 1: // Variable case 1: // Variable
{ {
infoSelectWrapper.setVariableName(value.toString().toUtf8().constData()); infoSelectWrapper.setVariableName(value.toString().toUtf8().constData());
infoSelectWrapper.update();
break; break;
} }
case 2: // Relation case 2: // Relation
{ {
infoSelectWrapper.setRelationType(static_cast<ConstInfoSelectWrapper::RelationType>(value.toInt())); infoSelectWrapper.setRelationType(
infoSelectWrapper.update(); static_cast<ESM::DialogueCondition::Comparison>(value.toInt() + ESM::DialogueCondition::Comp_Eq));
break; break;
} }
case 3: // Value case 3: // Value
@ -679,13 +656,11 @@ namespace CSMWorld
// QVariant seems to have issues converting 0 // QVariant seems to have issues converting 0
if ((value.toInt(&conversionResult) && conversionResult) || value.toString().compare("0") == 0) if ((value.toInt(&conversionResult) && conversionResult) || value.toString().compare("0") == 0)
{ {
infoSelectWrapper.getVariant().setType(ESM::VT_Int); infoSelectWrapper.setValue(value.toInt());
infoSelectWrapper.getVariant().setInteger(value.toInt());
} }
else if (value.toFloat(&conversionResult) && conversionResult) else if (value.toFloat(&conversionResult) && conversionResult)
{ {
infoSelectWrapper.getVariant().setType(ESM::VT_Float); infoSelectWrapper.setValue(value.toFloat());
infoSelectWrapper.getVariant().setFloat(value.toFloat());
} }
break; break;
} }
@ -694,8 +669,7 @@ namespace CSMWorld
{ {
if ((value.toInt(&conversionResult) && conversionResult) || value.toString().compare("0") == 0) if ((value.toInt(&conversionResult) && conversionResult) || value.toString().compare("0") == 0)
{ {
infoSelectWrapper.getVariant().setType(ESM::VT_Int); infoSelectWrapper.setValue(value.toInt());
infoSelectWrapper.getVariant().setInteger(value.toInt());
} }
break; break;
} }

@ -46,41 +46,41 @@ QWidget* CSVWorld::IdCompletionDelegate::createEditor(QWidget* parent, const QSt
switch (conditionFunction) switch (conditionFunction)
{ {
case CSMWorld::ConstInfoSelectWrapper::Function_Global: case ESM::DialogueCondition::Function_Global:
{ {
return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_GlobalVariable); return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_GlobalVariable);
} }
case CSMWorld::ConstInfoSelectWrapper::Function_Journal: case ESM::DialogueCondition::Function_Journal:
{ {
return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Journal); return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Journal);
} }
case CSMWorld::ConstInfoSelectWrapper::Function_Item: case ESM::DialogueCondition::Function_Item:
{ {
return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Referenceable); return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Referenceable);
} }
case CSMWorld::ConstInfoSelectWrapper::Function_Dead: case ESM::DialogueCondition::Function_Dead:
case CSMWorld::ConstInfoSelectWrapper::Function_NotId: case ESM::DialogueCondition::Function_NotId:
{ {
return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Referenceable); return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Referenceable);
} }
case CSMWorld::ConstInfoSelectWrapper::Function_NotFaction: case ESM::DialogueCondition::Function_NotFaction:
{ {
return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Faction); return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Faction);
} }
case CSMWorld::ConstInfoSelectWrapper::Function_NotClass: case ESM::DialogueCondition::Function_NotClass:
{ {
return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Class); return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Class);
} }
case CSMWorld::ConstInfoSelectWrapper::Function_NotRace: case ESM::DialogueCondition::Function_NotRace:
{ {
return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Race); return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Race);
} }
case CSMWorld::ConstInfoSelectWrapper::Function_NotCell: case ESM::DialogueCondition::Function_NotCell:
{ {
return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Cell); return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Cell);
} }
case CSMWorld::ConstInfoSelectWrapper::Function_Local: case ESM::DialogueCondition::Function_Local:
case CSMWorld::ConstInfoSelectWrapper::Function_NotLocal: case ESM::DialogueCondition::Function_NotLocal:
{ {
return new CSVWidget::DropLineEdit(display, parent); return new CSVWidget::DropLineEdit(display, parent);
} }

@ -30,16 +30,16 @@ namespace
{ {
bool matchesStaticFilters(const MWDialogue::SelectWrapper& select, const MWWorld::Ptr& actor) bool matchesStaticFilters(const MWDialogue::SelectWrapper& select, const MWWorld::Ptr& actor)
{ {
const ESM::RefId selectId = ESM::RefId::stringRefId(select.getName()); const ESM::RefId selectId = select.getId();
if (select.getFunction() == MWDialogue::SelectWrapper::Function_NotId) if (select.getFunction() == ESM::DialogueCondition::Function_NotId)
return actor.getCellRef().getRefId() != selectId; return actor.getCellRef().getRefId() != selectId;
if (actor.getClass().isNpc()) if (actor.getClass().isNpc())
{ {
if (select.getFunction() == MWDialogue::SelectWrapper::Function_NotFaction) if (select.getFunction() == ESM::DialogueCondition::Function_NotFaction)
return actor.getClass().getPrimaryFaction(actor) != selectId; return actor.getClass().getPrimaryFaction(actor) != selectId;
else if (select.getFunction() == MWDialogue::SelectWrapper::Function_NotClass) else if (select.getFunction() == ESM::DialogueCondition::Function_NotClass)
return actor.get<ESM::NPC>()->mBase->mClass != selectId; return actor.get<ESM::NPC>()->mBase->mClass != selectId;
else if (select.getFunction() == MWDialogue::SelectWrapper::Function_NotRace) else if (select.getFunction() == ESM::DialogueCondition::Function_NotRace)
return actor.get<ESM::NPC>()->mBase->mRace != selectId; return actor.get<ESM::NPC>()->mBase->mRace != selectId;
} }
return true; return true;
@ -47,7 +47,7 @@ namespace
bool matchesStaticFilters(const ESM::DialInfo& info, const MWWorld::Ptr& actor) bool matchesStaticFilters(const ESM::DialInfo& info, const MWWorld::Ptr& actor)
{ {
for (const ESM::DialInfo::SelectStruct& select : info.mSelects) for (const auto& select : info.mSelects)
{ {
MWDialogue::SelectWrapper wrapper = select; MWDialogue::SelectWrapper wrapper = select;
if (wrapper.getType() == MWDialogue::SelectWrapper::Type_Boolean) if (wrapper.getType() == MWDialogue::SelectWrapper::Type_Boolean)
@ -62,7 +62,7 @@ namespace
} }
else if (wrapper.getType() == MWDialogue::SelectWrapper::Type_Numeric) else if (wrapper.getType() == MWDialogue::SelectWrapper::Type_Numeric)
{ {
if (wrapper.getFunction() == MWDialogue::SelectWrapper::Function_Local) if (wrapper.getFunction() == ESM::DialogueCondition::Function_Local)
{ {
const ESM::RefId& scriptName = actor.getClass().getScript(actor); const ESM::RefId& scriptName = actor.getClass().getScript(actor);
if (scriptName.empty()) if (scriptName.empty())
@ -207,9 +207,8 @@ bool MWDialogue::Filter::testPlayer(const ESM::DialInfo& info) const
bool MWDialogue::Filter::testSelectStructs(const ESM::DialInfo& info) const bool MWDialogue::Filter::testSelectStructs(const ESM::DialInfo& info) const
{ {
for (std::vector<ESM::DialInfo::SelectStruct>::const_iterator iter(info.mSelects.begin()); for (const auto& select : info.mSelects)
iter != info.mSelects.end(); ++iter) if (!testSelectStruct(select))
if (!testSelectStruct(*iter))
return false; return false;
return true; return true;
@ -270,11 +269,11 @@ bool MWDialogue::Filter::testSelectStruct(const SelectWrapper& select) const
// If the actor is a creature, we pass all conditions only applicable to NPCs. // If the actor is a creature, we pass all conditions only applicable to NPCs.
return true; return true;
if (select.getFunction() == SelectWrapper::Function_Choice && mChoice == -1) if (select.getFunction() == ESM::DialogueCondition::Function_Choice && mChoice == -1)
// If not currently in a choice, we reject all conditions that test against choices. // If not currently in a choice, we reject all conditions that test against choices.
return false; return false;
if (select.getFunction() == SelectWrapper::Function_Weather if (select.getFunction() == ESM::DialogueCondition::Function_Weather
&& !(MWBase::Environment::get().getWorld()->isCellExterior() && !(MWBase::Environment::get().getWorld()->isCellExterior()
|| MWBase::Environment::get().getWorld()->isCellQuasiExterior())) || MWBase::Environment::get().getWorld()->isCellQuasiExterior()))
// Reject weather conditions in interior cells // Reject weather conditions in interior cells
@ -305,29 +304,31 @@ bool MWDialogue::Filter::testSelectStructNumeric(const SelectWrapper& select) co
{ {
switch (select.getFunction()) switch (select.getFunction())
{ {
case SelectWrapper::Function_Global: case ESM::DialogueCondition::Function_Global:
// internally all globals are float :( // internally all globals are float :(
return select.selectCompare(MWBase::Environment::get().getWorld()->getGlobalFloat(select.getName())); return select.selectCompare(MWBase::Environment::get().getWorld()->getGlobalFloat(select.getName()));
case SelectWrapper::Function_Local: case ESM::DialogueCondition::Function_Local:
{ {
return testFunctionLocal(select); return testFunctionLocal(select);
} }
case SelectWrapper::Function_NotLocal: case ESM::DialogueCondition::Function_NotLocal:
{ {
return !testFunctionLocal(select); return !testFunctionLocal(select);
} }
case SelectWrapper::Function_PcHealthPercent: case ESM::DialogueCondition::Function_PcHealthPercent:
{ {
MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::Ptr player = MWMechanics::getPlayer();
return select.selectCompare( return select.selectCompare(
static_cast<int>(player.getClass().getCreatureStats(player).getHealth().getRatio() * 100)); static_cast<int>(player.getClass().getCreatureStats(player).getHealth().getRatio() * 100));
} }
case SelectWrapper::Function_PcDynamicStat: case ESM::DialogueCondition::Function_PcMagicka:
case ESM::DialogueCondition::Function_PcFatigue:
case ESM::DialogueCondition::Function_PcHealth:
{ {
MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::Ptr player = MWMechanics::getPlayer();
@ -336,7 +337,7 @@ bool MWDialogue::Filter::testSelectStructNumeric(const SelectWrapper& select) co
return select.selectCompare(value); return select.selectCompare(value);
} }
case SelectWrapper::Function_HealthPercent: case ESM::DialogueCondition::Function_Health_Percent:
{ {
return select.selectCompare( return select.selectCompare(
static_cast<int>(mActor.getClass().getCreatureStats(mActor).getHealth().getRatio() * 100)); static_cast<int>(mActor.getClass().getCreatureStats(mActor).getHealth().getRatio() * 100));
@ -354,27 +355,29 @@ int MWDialogue::Filter::getSelectStructInteger(const SelectWrapper& select) cons
switch (select.getFunction()) switch (select.getFunction())
{ {
case SelectWrapper::Function_Journal: case ESM::DialogueCondition::Function_Journal:
return MWBase::Environment::get().getJournal()->getJournalIndex(ESM::RefId::stringRefId(select.getName())); return MWBase::Environment::get().getJournal()->getJournalIndex(select.getId());
case SelectWrapper::Function_Item: case ESM::DialogueCondition::Function_Item:
{ {
MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); MWWorld::ContainerStore& store = player.getClass().getContainerStore(player);
return store.count(ESM::RefId::stringRefId(select.getName())); return store.count(select.getId());
} }
case SelectWrapper::Function_Dead: case ESM::DialogueCondition::Function_Dead:
return MWBase::Environment::get().getMechanicsManager()->countDeaths( return MWBase::Environment::get().getMechanicsManager()->countDeaths(select.getId());
ESM::RefId::stringRefId(select.getName()));
case SelectWrapper::Function_Choice: case ESM::DialogueCondition::Function_Choice:
return mChoice; return mChoice;
case SelectWrapper::Function_AiSetting: case ESM::DialogueCondition::Function_Fight:
case ESM::DialogueCondition::Function_Hello:
case ESM::DialogueCondition::Function_Alarm:
case ESM::DialogueCondition::Function_Flee:
{ {
int argument = select.getArgument(); int argument = select.getArgument();
if (argument < 0 || argument > 3) if (argument < 0 || argument > 3)
@ -387,32 +390,65 @@ int MWDialogue::Filter::getSelectStructInteger(const SelectWrapper& select) cons
.getAiSetting(static_cast<MWMechanics::AiSetting>(argument)) .getAiSetting(static_cast<MWMechanics::AiSetting>(argument))
.getModified(false); .getModified(false);
} }
case SelectWrapper::Function_PcAttribute: case ESM::DialogueCondition::Function_PcStrength:
case ESM::DialogueCondition::Function_PcIntelligence:
case ESM::DialogueCondition::Function_PcWillpower:
case ESM::DialogueCondition::Function_PcAgility:
case ESM::DialogueCondition::Function_PcSpeed:
case ESM::DialogueCondition::Function_PcEndurance:
case ESM::DialogueCondition::Function_PcPersonality:
case ESM::DialogueCondition::Function_PcLuck:
{ {
ESM::RefId attribute = ESM::Attribute::indexToRefId(select.getArgument()); ESM::RefId attribute = ESM::Attribute::indexToRefId(select.getArgument());
return player.getClass().getCreatureStats(player).getAttribute(attribute).getModified(); return player.getClass().getCreatureStats(player).getAttribute(attribute).getModified();
} }
case SelectWrapper::Function_PcSkill: case ESM::DialogueCondition::Function_PcBlock:
case ESM::DialogueCondition::Function_PcArmorer:
case ESM::DialogueCondition::Function_PcMediumArmor:
case ESM::DialogueCondition::Function_PcHeavyArmor:
case ESM::DialogueCondition::Function_PcBluntWeapon:
case ESM::DialogueCondition::Function_PcLongBlade:
case ESM::DialogueCondition::Function_PcAxe:
case ESM::DialogueCondition::Function_PcSpear:
case ESM::DialogueCondition::Function_PcAthletics:
case ESM::DialogueCondition::Function_PcEnchant:
case ESM::DialogueCondition::Function_PcDestruction:
case ESM::DialogueCondition::Function_PcAlteration:
case ESM::DialogueCondition::Function_PcIllusion:
case ESM::DialogueCondition::Function_PcConjuration:
case ESM::DialogueCondition::Function_PcMysticism:
case ESM::DialogueCondition::Function_PcRestoration:
case ESM::DialogueCondition::Function_PcAlchemy:
case ESM::DialogueCondition::Function_PcUnarmored:
case ESM::DialogueCondition::Function_PcSecurity:
case ESM::DialogueCondition::Function_PcSneak:
case ESM::DialogueCondition::Function_PcAcrobatics:
case ESM::DialogueCondition::Function_PcLightArmor:
case ESM::DialogueCondition::Function_PcShortBlade:
case ESM::DialogueCondition::Function_PcMarksman:
case ESM::DialogueCondition::Function_PcMerchantile:
case ESM::DialogueCondition::Function_PcSpeechcraft:
case ESM::DialogueCondition::Function_PcHandToHand:
{ {
ESM::RefId skill = ESM::Skill::indexToRefId(select.getArgument()); ESM::RefId skill = ESM::Skill::indexToRefId(select.getArgument());
return static_cast<int>(player.getClass().getNpcStats(player).getSkill(skill).getModified()); return static_cast<int>(player.getClass().getNpcStats(player).getSkill(skill).getModified());
} }
case SelectWrapper::Function_FriendlyHit: case ESM::DialogueCondition::Function_FriendHit:
{ {
int hits = mActor.getClass().getCreatureStats(mActor).getFriendlyHits(); int hits = mActor.getClass().getCreatureStats(mActor).getFriendlyHits();
return hits > 4 ? 4 : hits; return hits > 4 ? 4 : hits;
} }
case SelectWrapper::Function_PcLevel: case ESM::DialogueCondition::Function_PcLevel:
return player.getClass().getCreatureStats(player).getLevel(); return player.getClass().getCreatureStats(player).getLevel();
case SelectWrapper::Function_PcGender: case ESM::DialogueCondition::Function_PcGender:
return player.get<ESM::NPC>()->mBase->isMale() ? 0 : 1; return player.get<ESM::NPC>()->mBase->isMale() ? 0 : 1;
case SelectWrapper::Function_PcClothingModifier: case ESM::DialogueCondition::Function_PcClothingModifier:
{ {
const MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); const MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player);
@ -429,11 +465,11 @@ int MWDialogue::Filter::getSelectStructInteger(const SelectWrapper& select) cons
return value; return value;
} }
case SelectWrapper::Function_PcCrimeLevel: case ESM::DialogueCondition::Function_PcCrimeLevel:
return player.getClass().getNpcStats(player).getBounty(); return player.getClass().getNpcStats(player).getBounty();
case SelectWrapper::Function_RankRequirement: case ESM::DialogueCondition::Function_RankRequirement:
{ {
const ESM::RefId& faction = mActor.getClass().getPrimaryFaction(mActor); const ESM::RefId& faction = mActor.getClass().getPrimaryFaction(mActor);
if (faction.empty()) if (faction.empty())
@ -455,23 +491,23 @@ int MWDialogue::Filter::getSelectStructInteger(const SelectWrapper& select) cons
return result; return result;
} }
case SelectWrapper::Function_Level: case ESM::DialogueCondition::Function_Level:
return mActor.getClass().getCreatureStats(mActor).getLevel(); return mActor.getClass().getCreatureStats(mActor).getLevel();
case SelectWrapper::Function_PCReputation: case ESM::DialogueCondition::Function_PcReputation:
return player.getClass().getNpcStats(player).getReputation(); return player.getClass().getNpcStats(player).getReputation();
case SelectWrapper::Function_Weather: case ESM::DialogueCondition::Function_Weather:
return MWBase::Environment::get().getWorld()->getCurrentWeather(); return MWBase::Environment::get().getWorld()->getCurrentWeather();
case SelectWrapper::Function_Reputation: case ESM::DialogueCondition::Function_Reputation:
return mActor.getClass().getNpcStats(mActor).getReputation(); return mActor.getClass().getNpcStats(mActor).getReputation();
case SelectWrapper::Function_FactionRankDiff: case ESM::DialogueCondition::Function_FactionRankDifference:
{ {
const ESM::RefId& faction = mActor.getClass().getPrimaryFaction(mActor); const ESM::RefId& faction = mActor.getClass().getPrimaryFaction(mActor);
@ -483,14 +519,14 @@ int MWDialogue::Filter::getSelectStructInteger(const SelectWrapper& select) cons
return rank - npcRank; return rank - npcRank;
} }
case SelectWrapper::Function_WerewolfKills: case ESM::DialogueCondition::Function_PcWerewolfKills:
return player.getClass().getNpcStats(player).getWerewolfKills(); return player.getClass().getNpcStats(player).getWerewolfKills();
case SelectWrapper::Function_RankLow: case ESM::DialogueCondition::Function_FacReactionLowest:
case SelectWrapper::Function_RankHigh: case ESM::DialogueCondition::Function_FacReactionHighest:
{ {
bool low = select.getFunction() == SelectWrapper::Function_RankLow; bool low = select.getFunction() == ESM::DialogueCondition::Function_FacReactionLowest;
const ESM::RefId& factionId = mActor.getClass().getPrimaryFaction(mActor); const ESM::RefId& factionId = mActor.getClass().getPrimaryFaction(mActor);
@ -513,7 +549,7 @@ int MWDialogue::Filter::getSelectStructInteger(const SelectWrapper& select) cons
return value; return value;
} }
case SelectWrapper::Function_CreatureTargetted: case ESM::DialogueCondition::Function_CreatureTarget:
{ {
MWWorld::Ptr target; MWWorld::Ptr target;
@ -540,53 +576,49 @@ bool MWDialogue::Filter::getSelectStructBoolean(const SelectWrapper& select) con
switch (select.getFunction()) switch (select.getFunction())
{ {
case SelectWrapper::Function_False: case ESM::DialogueCondition::Function_NotId:
return false; return mActor.getCellRef().getRefId() != select.getId();
case SelectWrapper::Function_NotId:
return !(mActor.getCellRef().getRefId() == ESM::RefId::stringRefId(select.getName()));
case SelectWrapper::Function_NotFaction: case ESM::DialogueCondition::Function_NotFaction:
return !(mActor.getClass().getPrimaryFaction(mActor) == ESM::RefId::stringRefId(select.getName())); return mActor.getClass().getPrimaryFaction(mActor) != select.getId();
case SelectWrapper::Function_NotClass: case ESM::DialogueCondition::Function_NotClass:
return !(mActor.get<ESM::NPC>()->mBase->mClass == ESM::RefId::stringRefId(select.getName())); return mActor.get<ESM::NPC>()->mBase->mClass != select.getId();
case SelectWrapper::Function_NotRace: case ESM::DialogueCondition::Function_NotRace:
return !(mActor.get<ESM::NPC>()->mBase->mRace == ESM::RefId::stringRefId(select.getName())); return mActor.get<ESM::NPC>()->mBase->mRace != select.getId();
case SelectWrapper::Function_NotCell: case ESM::DialogueCondition::Function_NotCell:
{ {
std::string_view actorCell = MWBase::Environment::get().getWorld()->getCellName(mActor.getCell()); std::string_view actorCell = MWBase::Environment::get().getWorld()->getCellName(mActor.getCell());
return !Misc::StringUtils::ciStartsWith(actorCell, select.getName()); return !Misc::StringUtils::ciStartsWith(actorCell, select.getCellName());
} }
case SelectWrapper::Function_SameGender: case ESM::DialogueCondition::Function_SameSex:
return (player.get<ESM::NPC>()->mBase->mFlags & ESM::NPC::Female) return (player.get<ESM::NPC>()->mBase->mFlags & ESM::NPC::Female)
== (mActor.get<ESM::NPC>()->mBase->mFlags & ESM::NPC::Female); == (mActor.get<ESM::NPC>()->mBase->mFlags & ESM::NPC::Female);
case SelectWrapper::Function_SameRace: case ESM::DialogueCondition::Function_SameRace:
return mActor.get<ESM::NPC>()->mBase->mRace == player.get<ESM::NPC>()->mBase->mRace; return mActor.get<ESM::NPC>()->mBase->mRace == player.get<ESM::NPC>()->mBase->mRace;
case SelectWrapper::Function_SameFaction: case ESM::DialogueCondition::Function_SameFaction:
return player.getClass().getNpcStats(player).isInFaction(mActor.getClass().getPrimaryFaction(mActor)); return player.getClass().getNpcStats(player).isInFaction(mActor.getClass().getPrimaryFaction(mActor));
case SelectWrapper::Function_PcCommonDisease: case ESM::DialogueCondition::Function_PcCommonDisease:
return player.getClass().getCreatureStats(player).hasCommonDisease(); return player.getClass().getCreatureStats(player).hasCommonDisease();
case SelectWrapper::Function_PcBlightDisease: case ESM::DialogueCondition::Function_PcBlightDisease:
return player.getClass().getCreatureStats(player).hasBlightDisease(); return player.getClass().getCreatureStats(player).hasBlightDisease();
case SelectWrapper::Function_PcCorprus: case ESM::DialogueCondition::Function_PcCorprus:
return player.getClass() return player.getClass()
.getCreatureStats(player) .getCreatureStats(player)
@ -595,7 +627,7 @@ bool MWDialogue::Filter::getSelectStructBoolean(const SelectWrapper& select) con
.getMagnitude() .getMagnitude()
!= 0; != 0;
case SelectWrapper::Function_PcExpelled: case ESM::DialogueCondition::Function_PcExpelled:
{ {
const ESM::RefId& faction = mActor.getClass().getPrimaryFaction(mActor); const ESM::RefId& faction = mActor.getClass().getPrimaryFaction(mActor);
@ -605,7 +637,7 @@ bool MWDialogue::Filter::getSelectStructBoolean(const SelectWrapper& select) con
return player.getClass().getNpcStats(player).getExpelled(faction); return player.getClass().getNpcStats(player).getExpelled(faction);
} }
case SelectWrapper::Function_PcVampire: case ESM::DialogueCondition::Function_PcVampire:
return player.getClass() return player.getClass()
.getCreatureStats(player) .getCreatureStats(player)
@ -614,27 +646,27 @@ bool MWDialogue::Filter::getSelectStructBoolean(const SelectWrapper& select) con
.getMagnitude() .getMagnitude()
> 0; > 0;
case SelectWrapper::Function_TalkedToPc: case ESM::DialogueCondition::Function_TalkedToPc:
return mTalkedToPlayer; return mTalkedToPlayer;
case SelectWrapper::Function_Alarmed: case ESM::DialogueCondition::Function_Alarmed:
return mActor.getClass().getCreatureStats(mActor).isAlarmed(); return mActor.getClass().getCreatureStats(mActor).isAlarmed();
case SelectWrapper::Function_Detected: case ESM::DialogueCondition::Function_Detected:
return MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, mActor); return MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, mActor);
case SelectWrapper::Function_Attacked: case ESM::DialogueCondition::Function_Attacked:
return mActor.getClass().getCreatureStats(mActor).getAttacked(); return mActor.getClass().getCreatureStats(mActor).getAttacked();
case SelectWrapper::Function_ShouldAttack: case ESM::DialogueCondition::Function_ShouldAttack:
return MWBase::Environment::get().getMechanicsManager()->isAggressive(mActor, MWMechanics::getPlayer()); return MWBase::Environment::get().getMechanicsManager()->isAggressive(mActor, MWMechanics::getPlayer());
case SelectWrapper::Function_Werewolf: case ESM::DialogueCondition::Function_Werewolf:
return mActor.getClass().getNpcStats(mActor).isWerewolf(); return mActor.getClass().getNpcStats(mActor).isWerewolf();

@ -10,431 +10,264 @@
namespace namespace
{ {
template <typename T1, typename T2> template <typename T1, typename T2>
bool selectCompareImp(char comp, T1 value1, T2 value2) bool selectCompareImp(ESM::DialogueCondition::Comparison comp, T1 value1, T2 value2)
{ {
switch (comp) switch (comp)
{ {
case '0': case ESM::DialogueCondition::Comp_Eq:
return value1 == value2; return value1 == value2;
case '1': case ESM::DialogueCondition::Comp_Ne:
return value1 != value2; return value1 != value2;
case '2': case ESM::DialogueCondition::Comp_Gt:
return value1 > value2; return value1 > value2;
case '3': case ESM::DialogueCondition::Comp_Ge:
return value1 >= value2; return value1 >= value2;
case '4': case ESM::DialogueCondition::Comp_Ls:
return value1 < value2; return value1 < value2;
case '5': case ESM::DialogueCondition::Comp_Le:
return value1 <= value2; return value1 <= value2;
default:
throw std::runtime_error("unknown compare type in dialogue info select");
} }
throw std::runtime_error("unknown compare type in dialogue info select");
} }
template <typename T> template <typename T>
bool selectCompareImp(const ESM::DialInfo::SelectStruct& select, T value1) bool selectCompareImp(const ESM::DialogueCondition& select, T value1)
{
if (select.mValue.getType() == ESM::VT_Int)
{
return selectCompareImp(select.mSelectRule[4], value1, select.mValue.getInteger());
}
else if (select.mValue.getType() == ESM::VT_Float)
{
return selectCompareImp(select.mSelectRule[4], value1, select.mValue.getFloat());
}
else
throw std::runtime_error("unsupported variable type in dialogue info select");
}
}
MWDialogue::SelectWrapper::Function MWDialogue::SelectWrapper::decodeFunction() const
{
const int index = Misc::StringUtils::toNumeric<int>(mSelect.mSelectRule.substr(2, 2), 0);
switch (index)
{ {
case 0: return std::visit(
return Function_RankLow; [&](auto value) { return selectCompareImp(select.mComparison, value1, value); }, select.mValue);
case 1:
return Function_RankHigh;
case 2:
return Function_RankRequirement;
case 3:
return Function_Reputation;
case 4:
return Function_HealthPercent;
case 5:
return Function_PCReputation;
case 6:
return Function_PcLevel;
case 7:
return Function_PcHealthPercent;
case 8:
case 9:
return Function_PcDynamicStat;
case 10:
return Function_PcAttribute;
case 11:
case 12:
case 13:
case 14:
case 15:
case 16:
case 17:
case 18:
case 19:
case 20:
case 21:
case 22:
case 23:
case 24:
case 25:
case 26:
case 27:
case 28:
case 29:
case 30:
case 31:
case 32:
case 33:
case 34:
case 35:
case 36:
case 37:
return Function_PcSkill;
case 38:
return Function_PcGender;
case 39:
return Function_PcExpelled;
case 40:
return Function_PcCommonDisease;
case 41:
return Function_PcBlightDisease;
case 42:
return Function_PcClothingModifier;
case 43:
return Function_PcCrimeLevel;
case 44:
return Function_SameGender;
case 45:
return Function_SameRace;
case 46:
return Function_SameFaction;
case 47:
return Function_FactionRankDiff;
case 48:
return Function_Detected;
case 49:
return Function_Alarmed;
case 50:
return Function_Choice;
case 51:
case 52:
case 53:
case 54:
case 55:
case 56:
case 57:
return Function_PcAttribute;
case 58:
return Function_PcCorprus;
case 59:
return Function_Weather;
case 60:
return Function_PcVampire;
case 61:
return Function_Level;
case 62:
return Function_Attacked;
case 63:
return Function_TalkedToPc;
case 64:
return Function_PcDynamicStat;
case 65:
return Function_CreatureTargetted;
case 66:
return Function_FriendlyHit;
case 67:
case 68:
case 69:
case 70:
return Function_AiSetting;
case 71:
return Function_ShouldAttack;
case 72:
return Function_Werewolf;
case 73:
return Function_WerewolfKills;
} }
return Function_False;
} }
MWDialogue::SelectWrapper::SelectWrapper(const ESM::DialInfo::SelectStruct& select) MWDialogue::SelectWrapper::SelectWrapper(const ESM::DialogueCondition& select)
: mSelect(select) : mSelect(select)
{ {
} }
MWDialogue::SelectWrapper::Function MWDialogue::SelectWrapper::getFunction() const ESM::DialogueCondition::Function MWDialogue::SelectWrapper::getFunction() const
{ {
char type = mSelect.mSelectRule[1]; return mSelect.mFunction;
switch (type)
{
case '1':
return decodeFunction();
case '2':
return Function_Global;
case '3':
return Function_Local;
case '4':
return Function_Journal;
case '5':
return Function_Item;
case '6':
return Function_Dead;
case '7':
return Function_NotId;
case '8':
return Function_NotFaction;
case '9':
return Function_NotClass;
case 'A':
return Function_NotRace;
case 'B':
return Function_NotCell;
case 'C':
return Function_NotLocal;
}
return Function_None;
} }
int MWDialogue::SelectWrapper::getArgument() const int MWDialogue::SelectWrapper::getArgument() const
{ {
if (mSelect.mSelectRule[1] != '1') switch (mSelect.mFunction)
return 0;
int index = 0;
std::istringstream(mSelect.mSelectRule.substr(2, 2)) >> index;
switch (index)
{ {
// AI settings // AI settings
case 67: case ESM::DialogueCondition::Function_Fight:
return 1; return 1;
case 68: case ESM::DialogueCondition::Function_Hello:
return 0; return 0;
case 69: case ESM::DialogueCondition::Function_Alarm:
return 3; return 3;
case 70: case ESM::DialogueCondition::Function_Flee:
return 2; return 2;
// attributes // attributes
case 10: case ESM::DialogueCondition::Function_PcStrength:
return 0; return 0;
case 51: case ESM::DialogueCondition::Function_PcIntelligence:
return 1; return 1;
case 52: case ESM::DialogueCondition::Function_PcWillpower:
return 2; return 2;
case 53: case ESM::DialogueCondition::Function_PcAgility:
return 3; return 3;
case 54: case ESM::DialogueCondition::Function_PcSpeed:
return 4; return 4;
case 55: case ESM::DialogueCondition::Function_PcEndurance:
return 5; return 5;
case 56: case ESM::DialogueCondition::Function_PcPersonality:
return 6; return 6;
case 57: case ESM::DialogueCondition::Function_PcLuck:
return 7; return 7;
// skills // skills
case 11: case ESM::DialogueCondition::Function_PcBlock:
return 0; return 0;
case 12: case ESM::DialogueCondition::Function_PcArmorer:
return 1; return 1;
case 13: case ESM::DialogueCondition::Function_PcMediumArmor:
return 2; return 2;
case 14: case ESM::DialogueCondition::Function_PcHeavyArmor:
return 3; return 3;
case 15: case ESM::DialogueCondition::Function_PcBluntWeapon:
return 4; return 4;
case 16: case ESM::DialogueCondition::Function_PcLongBlade:
return 5; return 5;
case 17: case ESM::DialogueCondition::Function_PcAxe:
return 6; return 6;
case 18: case ESM::DialogueCondition::Function_PcSpear:
return 7; return 7;
case 19: case ESM::DialogueCondition::Function_PcAthletics:
return 8; return 8;
case 20: case ESM::DialogueCondition::Function_PcEnchant:
return 9; return 9;
case 21: case ESM::DialogueCondition::Function_PcDestruction:
return 10; return 10;
case 22: case ESM::DialogueCondition::Function_PcAlteration:
return 11; return 11;
case 23: case ESM::DialogueCondition::Function_PcIllusion:
return 12; return 12;
case 24: case ESM::DialogueCondition::Function_PcConjuration:
return 13; return 13;
case 25: case ESM::DialogueCondition::Function_PcMysticism:
return 14; return 14;
case 26: case ESM::DialogueCondition::Function_PcRestoration:
return 15; return 15;
case 27: case ESM::DialogueCondition::Function_PcAlchemy:
return 16; return 16;
case 28: case ESM::DialogueCondition::Function_PcUnarmored:
return 17; return 17;
case 29: case ESM::DialogueCondition::Function_PcSecurity:
return 18; return 18;
case 30: case ESM::DialogueCondition::Function_PcSneak:
return 19; return 19;
case 31: case ESM::DialogueCondition::Function_PcAcrobatics:
return 20; return 20;
case 32: case ESM::DialogueCondition::Function_PcLightArmor:
return 21; return 21;
case 33: case ESM::DialogueCondition::Function_PcShortBlade:
return 22; return 22;
case 34: case ESM::DialogueCondition::Function_PcMarksman:
return 23; return 23;
case 35: case ESM::DialogueCondition::Function_PcMerchantile:
return 24; return 24;
case 36: case ESM::DialogueCondition::Function_PcSpeechcraft:
return 25; return 25;
case 37: case ESM::DialogueCondition::Function_PcHandToHand:
return 26; return 26;
// dynamic stats // dynamic stats
case 8: case ESM::DialogueCondition::Function_PcMagicka:
return 1; return 1;
case 9: case ESM::DialogueCondition::Function_PcFatigue:
return 2; return 2;
case 64: case ESM::DialogueCondition::Function_PcHealth:
return 0;
default:
return 0; return 0;
} }
return 0;
} }
MWDialogue::SelectWrapper::Type MWDialogue::SelectWrapper::getType() const MWDialogue::SelectWrapper::Type MWDialogue::SelectWrapper::getType() const
{ {
static const Function integerFunctions[] = { switch (mSelect.mFunction)
Function_Journal, {
Function_Item, case ESM::DialogueCondition::Function_Journal:
Function_Dead, case ESM::DialogueCondition::Function_Item:
Function_Choice, case ESM::DialogueCondition::Function_Dead:
Function_AiSetting, case ESM::DialogueCondition::Function_Choice:
Function_PcAttribute, case ESM::DialogueCondition::Function_Fight:
Function_PcSkill, case ESM::DialogueCondition::Function_Hello:
Function_FriendlyHit, case ESM::DialogueCondition::Function_Alarm:
Function_PcLevel, case ESM::DialogueCondition::Function_Flee:
Function_PcGender, case ESM::DialogueCondition::Function_PcStrength:
Function_PcClothingModifier, case ESM::DialogueCondition::Function_PcIntelligence:
Function_PcCrimeLevel, case ESM::DialogueCondition::Function_PcWillpower:
Function_RankRequirement, case ESM::DialogueCondition::Function_PcAgility:
Function_Level, case ESM::DialogueCondition::Function_PcSpeed:
Function_PCReputation, case ESM::DialogueCondition::Function_PcEndurance:
Function_Weather, case ESM::DialogueCondition::Function_PcPersonality:
Function_Reputation, case ESM::DialogueCondition::Function_PcLuck:
Function_FactionRankDiff, case ESM::DialogueCondition::Function_PcBlock:
Function_WerewolfKills, case ESM::DialogueCondition::Function_PcArmorer:
Function_RankLow, case ESM::DialogueCondition::Function_PcMediumArmor:
Function_RankHigh, case ESM::DialogueCondition::Function_PcHeavyArmor:
Function_CreatureTargetted, case ESM::DialogueCondition::Function_PcBluntWeapon:
// end marker case ESM::DialogueCondition::Function_PcLongBlade:
Function_None, case ESM::DialogueCondition::Function_PcAxe:
}; case ESM::DialogueCondition::Function_PcSpear:
case ESM::DialogueCondition::Function_PcAthletics:
static const Function numericFunctions[] = { case ESM::DialogueCondition::Function_PcEnchant:
Function_Global, case ESM::DialogueCondition::Function_PcDestruction:
Function_Local, case ESM::DialogueCondition::Function_PcAlteration:
Function_NotLocal, case ESM::DialogueCondition::Function_PcIllusion:
Function_PcDynamicStat, case ESM::DialogueCondition::Function_PcConjuration:
Function_PcHealthPercent, case ESM::DialogueCondition::Function_PcMysticism:
Function_HealthPercent, case ESM::DialogueCondition::Function_PcRestoration:
// end marker case ESM::DialogueCondition::Function_PcAlchemy:
Function_None, case ESM::DialogueCondition::Function_PcUnarmored:
}; case ESM::DialogueCondition::Function_PcSecurity:
case ESM::DialogueCondition::Function_PcSneak:
static const Function booleanFunctions[] = { case ESM::DialogueCondition::Function_PcAcrobatics:
Function_False, case ESM::DialogueCondition::Function_PcLightArmor:
Function_SameGender, case ESM::DialogueCondition::Function_PcShortBlade:
Function_SameRace, case ESM::DialogueCondition::Function_PcMarksman:
Function_SameFaction, case ESM::DialogueCondition::Function_PcMerchantile:
Function_PcCommonDisease, case ESM::DialogueCondition::Function_PcSpeechcraft:
Function_PcBlightDisease, case ESM::DialogueCondition::Function_PcHandToHand:
Function_PcCorprus, case ESM::DialogueCondition::Function_FriendHit:
Function_PcExpelled, case ESM::DialogueCondition::Function_PcLevel:
Function_PcVampire, case ESM::DialogueCondition::Function_PcGender:
Function_TalkedToPc, case ESM::DialogueCondition::Function_PcClothingModifier:
Function_Alarmed, case ESM::DialogueCondition::Function_PcCrimeLevel:
Function_Detected, case ESM::DialogueCondition::Function_RankRequirement:
Function_Attacked, case ESM::DialogueCondition::Function_Level:
Function_ShouldAttack, case ESM::DialogueCondition::Function_PcReputation:
Function_Werewolf, case ESM::DialogueCondition::Function_Weather:
// end marker case ESM::DialogueCondition::Function_Reputation:
Function_None, case ESM::DialogueCondition::Function_FactionRankDifference:
}; case ESM::DialogueCondition::Function_PcWerewolfKills:
case ESM::DialogueCondition::Function_FacReactionLowest:
static const Function invertedBooleanFunctions[] = { case ESM::DialogueCondition::Function_FacReactionHighest:
Function_NotId, case ESM::DialogueCondition::Function_CreatureTarget:
Function_NotFaction,
Function_NotClass,
Function_NotRace,
Function_NotCell,
// end marker
Function_None,
};
Function function = getFunction();
for (int i = 0; integerFunctions[i] != Function_None; ++i)
if (integerFunctions[i] == function)
return Type_Integer; return Type_Integer;
case ESM::DialogueCondition::Function_Global:
for (int i = 0; numericFunctions[i] != Function_None; ++i) case ESM::DialogueCondition::Function_Local:
if (numericFunctions[i] == function) case ESM::DialogueCondition::Function_NotLocal:
case ESM::DialogueCondition::Function_PcHealth:
case ESM::DialogueCondition::Function_PcMagicka:
case ESM::DialogueCondition::Function_PcFatigue:
case ESM::DialogueCondition::Function_PcHealthPercent:
case ESM::DialogueCondition::Function_Health_Percent:
return Type_Numeric; return Type_Numeric;
case ESM::DialogueCondition::Function_SameSex:
for (int i = 0; booleanFunctions[i] != Function_None; ++i) case ESM::DialogueCondition::Function_SameRace:
if (booleanFunctions[i] == function) case ESM::DialogueCondition::Function_SameFaction:
case ESM::DialogueCondition::Function_PcCommonDisease:
case ESM::DialogueCondition::Function_PcBlightDisease:
case ESM::DialogueCondition::Function_PcCorprus:
case ESM::DialogueCondition::Function_PcExpelled:
case ESM::DialogueCondition::Function_PcVampire:
case ESM::DialogueCondition::Function_TalkedToPc:
case ESM::DialogueCondition::Function_Alarmed:
case ESM::DialogueCondition::Function_Detected:
case ESM::DialogueCondition::Function_Attacked:
case ESM::DialogueCondition::Function_ShouldAttack:
case ESM::DialogueCondition::Function_Werewolf:
return Type_Boolean; return Type_Boolean;
case ESM::DialogueCondition::Function_NotId:
for (int i = 0; invertedBooleanFunctions[i] != Function_None; ++i) case ESM::DialogueCondition::Function_NotFaction:
if (invertedBooleanFunctions[i] == function) case ESM::DialogueCondition::Function_NotClass:
case ESM::DialogueCondition::Function_NotRace:
case ESM::DialogueCondition::Function_NotCell:
return Type_Inverted; return Type_Inverted;
default:
return Type_None; return Type_None;
};
} }
bool MWDialogue::SelectWrapper::isNpcOnly() const bool MWDialogue::SelectWrapper::isNpcOnly() const
{ {
static const Function functions[] = { switch (mSelect.mFunction)
Function_NotFaction, {
Function_NotClass, case ESM::DialogueCondition::Function_NotFaction:
Function_NotRace, case ESM::DialogueCondition::Function_NotClass:
Function_SameGender, case ESM::DialogueCondition::Function_NotRace:
Function_SameRace, case ESM::DialogueCondition::Function_SameSex:
Function_SameFaction, case ESM::DialogueCondition::Function_SameRace:
Function_RankRequirement, case ESM::DialogueCondition::Function_SameFaction:
Function_Reputation, case ESM::DialogueCondition::Function_RankRequirement:
Function_FactionRankDiff, case ESM::DialogueCondition::Function_Reputation:
Function_Werewolf, case ESM::DialogueCondition::Function_FactionRankDifference:
Function_WerewolfKills, case ESM::DialogueCondition::Function_Werewolf:
Function_RankLow, case ESM::DialogueCondition::Function_PcWerewolfKills:
Function_RankHigh, case ESM::DialogueCondition::Function_FacReactionLowest:
// end marker case ESM::DialogueCondition::Function_FacReactionHighest:
Function_None,
};
Function function = getFunction();
for (int i = 0; functions[i] != Function_None; ++i)
if (functions[i] == function)
return true; return true;
default:
return false; return false;
}
} }
bool MWDialogue::SelectWrapper::selectCompare(int value) const bool MWDialogue::SelectWrapper::selectCompare(int value) const
@ -454,5 +287,15 @@ bool MWDialogue::SelectWrapper::selectCompare(bool value) const
std::string MWDialogue::SelectWrapper::getName() const std::string MWDialogue::SelectWrapper::getName() const
{ {
return Misc::StringUtils::lowerCase(std::string_view(mSelect.mSelectRule).substr(5)); return Misc::StringUtils::lowerCase(mSelect.mVariable);
}
std::string_view MWDialogue::SelectWrapper::getCellName() const
{
return mSelect.mVariable;
}
ESM::RefId MWDialogue::SelectWrapper::getId() const
{
return ESM::RefId::stringRefId(mSelect.mVariable);
} }

@ -7,62 +7,9 @@ namespace MWDialogue
{ {
class SelectWrapper class SelectWrapper
{ {
const ESM::DialInfo::SelectStruct& mSelect; const ESM::DialogueCondition& mSelect;
public: public:
enum Function
{
Function_None,
Function_False,
Function_Journal,
Function_Item,
Function_Dead,
Function_NotId,
Function_NotFaction,
Function_NotClass,
Function_NotRace,
Function_NotCell,
Function_NotLocal,
Function_Local,
Function_Global,
Function_SameGender,
Function_SameRace,
Function_SameFaction,
Function_Choice,
Function_PcCommonDisease,
Function_PcBlightDisease,
Function_PcCorprus,
Function_AiSetting,
Function_PcAttribute,
Function_PcSkill,
Function_PcExpelled,
Function_PcVampire,
Function_FriendlyHit,
Function_TalkedToPc,
Function_PcLevel,
Function_PcHealthPercent,
Function_PcDynamicStat,
Function_PcGender,
Function_PcClothingModifier,
Function_PcCrimeLevel,
Function_RankRequirement,
Function_HealthPercent,
Function_Level,
Function_PCReputation,
Function_Weather,
Function_Reputation,
Function_Alarmed,
Function_FactionRankDiff,
Function_Detected,
Function_Attacked,
Function_ShouldAttack,
Function_CreatureTargetted,
Function_Werewolf,
Function_WerewolfKills,
Function_RankLow,
Function_RankHigh
};
enum Type enum Type
{ {
Type_None, Type_None,
@ -72,13 +19,10 @@ namespace MWDialogue
Type_Inverted Type_Inverted
}; };
private:
Function decodeFunction() const;
public: public:
SelectWrapper(const ESM::DialInfo::SelectStruct& select); SelectWrapper(const ESM::DialogueCondition& select);
Function getFunction() const; ESM::DialogueCondition::Function getFunction() const;
int getArgument() const; int getArgument() const;
@ -95,6 +39,10 @@ namespace MWDialogue
std::string getName() const; std::string getName() const;
///< Return case-smashed name. ///< Return case-smashed name.
std::string_view getCellName() const;
ESM::RefId getId() const;
}; };
} }

@ -6,6 +6,7 @@
#include <components/esm3/esmwriter.hpp> #include <components/esm3/esmwriter.hpp>
#include <components/esm3/loadcont.hpp> #include <components/esm3/loadcont.hpp>
#include <components/esm3/loaddial.hpp> #include <components/esm3/loaddial.hpp>
#include <components/esm3/loadinfo.hpp>
#include <components/esm3/loadregn.hpp> #include <components/esm3/loadregn.hpp>
#include <components/esm3/loadscpt.hpp> #include <components/esm3/loadscpt.hpp>
#include <components/esm3/loadweap.hpp> #include <components/esm3/loadweap.hpp>
@ -603,6 +604,83 @@ namespace ESM
EXPECT_EQ(result.mIcon, record.mIcon); EXPECT_EQ(result.mIcon, record.mIcon);
} }
TEST_P(Esm3SaveLoadRecordTest, infoShouldNotChange)
{
DialInfo record = {
.mData = {
.mType = ESM::Dialogue::Topic,
.mDisposition = 1,
.mRank = 2,
.mGender = ESM::DialInfo::NA,
.mPCrank = 3,
},
.mSelects = {
ESM::DialogueCondition{
.mVariable = {},
.mValue = 42,
.mIndex = 0,
.mFunction = ESM::DialogueCondition::Function_Level,
.mComparison = ESM::DialogueCondition::Comp_Eq
},
ESM::DialogueCondition{
.mVariable = generateRandomString(32),
.mValue = 0,
.mIndex = 1,
.mFunction = ESM::DialogueCondition::Function_NotLocal,
.mComparison = ESM::DialogueCondition::Comp_Eq
},
},
.mId = generateRandomRefId(32),
.mPrev = generateRandomRefId(32),
.mNext = generateRandomRefId(32),
.mActor = generateRandomRefId(32),
.mRace = generateRandomRefId(32),
.mClass = generateRandomRefId(32),
.mFaction = generateRandomRefId(32),
.mPcFaction = generateRandomRefId(32),
.mCell = generateRandomRefId(32),
.mSound = generateRandomString(32),
.mResponse = generateRandomString(32),
.mResultScript = generateRandomString(32),
.mFactionLess = false,
.mQuestStatus = ESM::DialInfo::QS_None,
};
DialInfo result;
saveAndLoadRecord(record, GetParam(), result);
EXPECT_EQ(result.mData.mType, record.mData.mType);
EXPECT_EQ(result.mData.mDisposition, record.mData.mDisposition);
EXPECT_EQ(result.mData.mRank, record.mData.mRank);
EXPECT_EQ(result.mData.mGender, record.mData.mGender);
EXPECT_EQ(result.mData.mPCrank, record.mData.mPCrank);
EXPECT_EQ(result.mId, record.mId);
EXPECT_EQ(result.mPrev, record.mPrev);
EXPECT_EQ(result.mNext, record.mNext);
EXPECT_EQ(result.mActor, record.mActor);
EXPECT_EQ(result.mRace, record.mRace);
EXPECT_EQ(result.mClass, record.mClass);
EXPECT_EQ(result.mFaction, record.mFaction);
EXPECT_EQ(result.mPcFaction, record.mPcFaction);
EXPECT_EQ(result.mCell, record.mCell);
EXPECT_EQ(result.mSound, record.mSound);
EXPECT_EQ(result.mResponse, record.mResponse);
EXPECT_EQ(result.mResultScript, record.mResultScript);
EXPECT_EQ(result.mFactionLess, record.mFactionLess);
EXPECT_EQ(result.mQuestStatus, record.mQuestStatus);
EXPECT_EQ(result.mSelects.size(), record.mSelects.size());
for (size_t i = 0; i < result.mSelects.size(); ++i)
{
const auto& resultS = result.mSelects[i];
const auto& recordS = record.mSelects[i];
EXPECT_EQ(resultS.mVariable, recordS.mVariable);
EXPECT_EQ(resultS.mValue, recordS.mValue);
EXPECT_EQ(resultS.mIndex, recordS.mIndex);
EXPECT_EQ(resultS.mFunction, recordS.mFunction);
EXPECT_EQ(resultS.mComparison, recordS.mComparison);
}
}
INSTANTIATE_TEST_SUITE_P(FormatVersions, Esm3SaveLoadRecordTest, ValuesIn(getFormats())); INSTANTIATE_TEST_SUITE_P(FormatVersions, Esm3SaveLoadRecordTest, ValuesIn(getFormats()));
} }
} }

@ -181,7 +181,7 @@ add_component_dir (esm3
inventorystate containerstate npcstate creaturestate dialoguestate statstate npcstats creaturestats inventorystate containerstate npcstate creaturestate dialoguestate statstate npcstats creaturestats
weatherstate quickkeys fogstate spellstate activespells creaturelevliststate doorstate projectilestate debugprofile weatherstate quickkeys fogstate spellstate activespells creaturelevliststate doorstate projectilestate debugprofile
aisequence magiceffects custommarkerstate stolenitems transport animationstate controlsstate mappings readerscache aisequence magiceffects custommarkerstate stolenitems transport animationstate controlsstate mappings readerscache
infoorder timestamp formatversion landrecorddata selectiongroup infoorder timestamp formatversion landrecorddata selectiongroup dialoguecondition
) )
add_component_dir (esmterrain add_component_dir (esmterrain

@ -0,0 +1,206 @@
#include "dialoguecondition.hpp"
#include "esmreader.hpp"
#include "esmwriter.hpp"
#include "variant.hpp"
#include <components/debug/debuglog.hpp>
#include <components/misc/concepts.hpp>
#include <components/misc/strings/conversion.hpp>
namespace ESM
{
std::optional<DialogueCondition> DialogueCondition::load(ESMReader& esm, ESM::RefId context)
{
std::string rule = esm.getHString();
ESM::Variant variant;
variant.read(esm, Variant::Format_Info);
if (rule.size() < 5)
{
Log(Debug::Warning) << "Found invalid SCVR rule of size " << rule.size() << " in INFO " << context;
return {};
}
if (rule[4] < '0' || rule[4] > '5')
{
Log(Debug::Warning) << "Found invalid SCVR comparison operator " << static_cast<int>(rule[4]) << " in INFO "
<< context;
return {};
}
DialogueCondition condition;
if (rule[0] >= '0' && rule[0] <= '9')
condition.mIndex = rule[0] - '0';
else
{
Log(Debug::Info) << "Found invalid SCVR index " << static_cast<int>(rule[0]) << " in INFO " << context;
condition.mIndex = 0;
}
if (rule[1] == '1')
{
int function = Misc::StringUtils::toNumeric<int>(std::string_view{ rule }.substr(2, 2), -1);
if (function >= Function_FacReactionLowest && function <= Function_PcWerewolfKills)
condition.mFunction = static_cast<Function>(function);
else
{
Log(Debug::Warning) << "Encountered invalid SCVR function index " << function << " in INFO " << context;
return {};
}
}
else if ((rule[1] > '1' && rule[1] <= '9') || (rule[1] >= 'A' && rule[1] <= 'C'))
{
if (rule.size() == 5)
{
Log(Debug::Warning) << "Missing variable for SCVR of type " << rule[1] << " in INFO " << context;
return {};
}
bool malformed = rule[3] != 'X';
if (rule[1] == '2')
{
condition.mFunction = Function_Global;
malformed |= rule[2] != 'f' && rule[2] != 'l' && rule[2] != 's';
}
else if (rule[1] == '3')
{
condition.mFunction = Function_Local;
malformed |= rule[2] != 'f' && rule[2] != 'l' && rule[2] != 's';
}
else if (rule[1] == '4')
{
condition.mFunction = Function_Journal;
malformed |= rule[2] != 'J';
}
else if (rule[1] == '5')
{
condition.mFunction = Function_Item;
malformed |= rule[2] != 'I';
}
else if (rule[1] == '6')
{
condition.mFunction = Function_Dead;
malformed |= rule[2] != 'D';
}
else if (rule[1] == '7')
{
condition.mFunction = Function_NotId;
malformed |= rule[2] != 'X';
}
else if (rule[1] == '8')
{
condition.mFunction = Function_NotFaction;
malformed |= rule[2] != 'F';
}
else if (rule[1] == '9')
{
condition.mFunction = Function_NotClass;
malformed |= rule[2] != 'C';
}
else if (rule[1] == 'A')
{
condition.mFunction = Function_NotRace;
malformed |= rule[2] != 'R';
}
else if (rule[1] == 'B')
{
condition.mFunction = Function_NotCell;
malformed |= rule[2] != 'L';
}
else if (rule[1] == 'C')
{
condition.mFunction = Function_NotLocal;
malformed |= rule[2] != 'f' && rule[2] != 'l' && rule[2] != 's';
}
if (malformed)
Log(Debug::Info) << "Found malformed SCVR rule in INFO " << context;
}
else
{
Log(Debug::Warning) << "Found invalid SCVR function " << static_cast<int>(rule[1]) << " in INFO "
<< context;
return {};
}
condition.mComparison = static_cast<Comparison>(rule[4]);
condition.mVariable = rule.substr(5);
if (variant.getType() == VT_Int)
condition.mValue = variant.getInteger();
else if (variant.getType() == VT_Float)
condition.mValue = variant.getFloat();
else
{
Log(Debug::Warning) << "Found invalid SCVR variant " << variant.getType() << " in INFO " << context;
return {};
}
return condition;
}
void DialogueCondition::save(ESMWriter& esm) const
{
auto variant = std::visit([](auto value) { return ESM::Variant(value); }, mValue);
if (variant.getType() != VT_Float)
variant.setType(VT_Int);
std::string rule;
rule.reserve(5 + mVariable.size());
rule += static_cast<char>(mIndex + '0');
const auto appendVariableType = [&]() {
if (variant.getType() == VT_Float)
rule += "fX";
else
{
int32_t value = variant.getInteger();
if (static_cast<int16_t>(value) == value)
rule += "sX";
else
rule += "lX";
}
};
if (mFunction == Function_Global)
{
rule += '2';
appendVariableType();
}
else if (mFunction == Function_Local)
{
rule += '3';
appendVariableType();
}
else if (mFunction == Function_Journal)
rule += "4JX";
else if (mFunction == Function_Item)
rule += "5IX";
else if (mFunction == Function_Dead)
rule += "6DX";
else if (mFunction == Function_NotId)
rule += "7XX";
else if (mFunction == Function_NotFaction)
rule += "8FX";
else if (mFunction == Function_NotClass)
rule += "9CX";
else if (mFunction == Function_NotRace)
rule += "ARX";
else if (mFunction == Function_NotCell)
rule += "BLX";
else if (mFunction == Function_NotLocal)
{
rule += 'C';
appendVariableType();
}
else
{
rule += "100";
char* start = rule.data() + rule.size();
char* end = start;
if (mFunction < Function_PcStrength)
start--;
else
start -= 2;
auto result = std::to_chars(start, end, static_cast<int>(mFunction));
if (result.ec != std::errc())
{
Log(Debug::Error) << "Failed to save SCVR rule";
return;
}
}
rule += static_cast<char>(mComparison);
rule += mVariable;
esm.writeHNString("SCVR", rule);
variant.write(esm, Variant::Format_Info);
}
}

@ -0,0 +1,134 @@
#ifndef OPENMW_ESM3_DIALOGUECONDITION_H
#define OPENMW_ESM3_DIALOGUECONDITION_H
#include <cstdint>
#include <optional>
#include <string>
#include <variant>
#include <components/esm/refid.hpp>
namespace ESM
{
class ESMReader;
class ESMWriter;
struct DialogueCondition
{
enum Function : std::int8_t
{
Function_FacReactionLowest = 0,
Function_FacReactionHighest,
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_PcCorprus,
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, // Editor only
};
enum Comparison : char
{
Comp_Eq = '0',
Comp_Ne = '1',
Comp_Gt = '2',
Comp_Ge = '3',
Comp_Ls = '4',
Comp_Le = '5',
Comp_None = ' ', // Editor only
};
std::string mVariable;
std::variant<int32_t, float> mValue = 0;
std::uint8_t mIndex = 0;
Function mFunction = Function_None;
Comparison mComparison = Comp_None;
static std::optional<DialogueCondition> load(ESMReader& esm, ESM::RefId context);
void save(ESMWriter& esm) const;
};
}
#endif

@ -66,10 +66,9 @@ namespace ESM
break; break;
case fourCC("SCVR"): case fourCC("SCVR"):
{ {
SelectStruct ss; auto filter = DialogueCondition::load(esm, mId);
ss.mSelectRule = esm.getHString(); if (filter)
ss.mValue.read(esm, Variant::Format_Info); mSelects.emplace_back(std::move(*filter));
mSelects.push_back(ss);
break; break;
} }
case fourCC("BNAM"): case fourCC("BNAM"):
@ -120,11 +119,8 @@ namespace ESM
esm.writeHNOCString("SNAM", mSound); esm.writeHNOCString("SNAM", mSound);
esm.writeHNOString("NAME", mResponse); esm.writeHNOString("NAME", mResponse);
for (std::vector<SelectStruct>::const_iterator it = mSelects.begin(); it != mSelects.end(); ++it) for (const auto& rule : mSelects)
{ rule.save(esm);
esm.writeHNString("SCVR", it->mSelectRule);
it->mValue.write(esm, Variant::Format_Info);
}
esm.writeHNOString("BNAM", mResultScript); esm.writeHNOString("BNAM", mResultScript);

@ -4,8 +4,10 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "components/esm/defs.hpp" #include <components/esm/defs.hpp>
#include "components/esm/refid.hpp" #include <components/esm/refid.hpp>
#include "dialoguecondition.hpp"
#include "variant.hpp" #include "variant.hpp"
namespace ESM namespace ESM
@ -47,13 +49,6 @@ namespace ESM
}; // 12 bytes }; // 12 bytes
DATAstruct mData; DATAstruct mData;
// The rules for whether or not we will select this dialog item.
struct SelectStruct
{
std::string mSelectRule; // This has a complicated format
Variant mValue;
};
// Journal quest indices (introduced with the quest system in Tribunal) // Journal quest indices (introduced with the quest system in Tribunal)
enum QuestStatus enum QuestStatus
{ {
@ -65,7 +60,7 @@ namespace ESM
// Rules for when to include this item in the final list of options // Rules for when to include this item in the final list of options
// visible to the player. // visible to the player.
std::vector<SelectStruct> mSelects; std::vector<DialogueCondition> mSelects;
// Id of this, previous and next INFO items // Id of this, previous and next INFO items
RefId mId, mPrev, mNext; RefId mId, mPrev, mNext;

Loading…
Cancel
Save