Merge branch 'filterview' into 'master'

Validate INFO filters when loading the record

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

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

@ -57,112 +57,82 @@ namespace
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)
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)
switch (ss.mFunction)
{
case '1':
type_str = "Function";
func_str = std::string(ruleFunction(func));
case ESM::DialogueCondition::Function_Global:
type_str = "Global";
func_str = ss.mVariable;
break;
case '2':
if (indicator == 's')
type_str = "Global short";
else if (indicator == 'l')
type_str = "Global long";
else if (indicator == 'f')
type_str = "Global float";
case ESM::DialogueCondition::Function_Local:
type_str = "Local";
func_str = ss.mVariable;
break;
case '3':
if (indicator == 's')
type_str = "Local short";
else if (indicator == 'l')
type_str = "Local long";
else if (indicator == 'f')
type_str = "Local float";
break;
case '4':
if (indicator == 'J')
case ESM::DialogueCondition::Function_Journal:
type_str = "Journal";
func_str = ss.mVariable;
break;
case '5':
if (indicator == 'I')
type_str = "Item type";
case ESM::DialogueCondition::Function_Item:
type_str = "Item count";
func_str = ss.mVariable;
break;
case '6':
if (indicator == 'D')
type_str = "NPC Dead";
case ESM::DialogueCondition::Function_Dead:
type_str = "Dead";
func_str = ss.mVariable;
break;
case '7':
if (indicator == 'X')
case ESM::DialogueCondition::Function_NotId:
type_str = "Not ID";
func_str = ss.mVariable;
break;
case '8':
if (indicator == 'F')
case ESM::DialogueCondition::Function_NotFaction:
type_str = "Not Faction";
func_str = ss.mVariable;
break;
case '9':
if (indicator == 'C')
case ESM::DialogueCondition::Function_NotClass:
type_str = "Not Class";
func_str = ss.mVariable;
break;
case 'A':
if (indicator == 'R')
case ESM::DialogueCondition::Function_NotRace:
type_str = "Not Race";
func_str = ss.mVariable;
break;
case 'B':
if (indicator == 'L')
case ESM::DialogueCondition::Function_NotCell:
type_str = "Not Cell";
func_str = ss.mVariable;
break;
case 'C':
if (indicator == 's')
case ESM::DialogueCondition::Function_NotLocal:
type_str = "Not Local";
func_str = ss.mVariable;
break;
default:
type_str = "Function";
func_str = ruleFunction(ss.mFunction);
break;
}
// Append the variable name to the function string if any.
if (type != '1')
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)
std::string_view oper_str = "??";
switch (ss.mComparison)
{
case '0':
case ESM::DialogueCondition::Comp_Eq:
oper_str = "==";
break;
case '1':
case ESM::DialogueCondition::Comp_Ne:
oper_str = "!=";
break;
case '2':
case ESM::DialogueCondition::Comp_Gt:
oper_str = "> ";
break;
case '3':
case ESM::DialogueCondition::Comp_Ge:
oper_str = ">=";
break;
case '4':
case ESM::DialogueCondition::Comp_Ls:
oper_str = "< ";
break;
case '5':
case ESM::DialogueCondition::Comp_Le:
oper_str = "<=";
break;
default:
@ -170,7 +140,7 @@ namespace
}
std::ostringstream stream;
stream << ss.mValue;
std::visit([&](auto value) { stream << value; }, ss.mValue);
std::string result
= Misc::StringUtils::format("%-12s %-32s %2s %s", type_str, func_str, oper_str, stream.str());
@ -842,7 +812,7 @@ namespace EsmTool
<< 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;
if (!mData.mResultScript.empty())

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

@ -84,7 +84,7 @@ namespace CSMTools
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 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);
template <typename T>

File diff suppressed because it is too large Load Diff

@ -7,133 +7,13 @@
#include <components/esm3/loadinfo.hpp>
namespace ESM
{
class Variant;
}
#include <QVariant>
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,
@ -143,25 +23,13 @@ namespace CSMWorld
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);
ConstInfoSelectWrapper(const ESM::DialogueCondition& select);
FunctionName getFunctionName() const;
RelationType getRelationType() const;
ESM::DialogueCondition::Function getFunctionName() const;
ESM::DialogueCondition::Comparison getRelationType() const;
ComparisonType getComparisonType() const;
bool hasVariable() const;
@ -169,17 +37,12 @@ namespace CSMWorld
bool conditionIsAlwaysTrue() const;
bool conditionIsNeverTrue() const;
bool variantTypeIsValid() const;
const ESM::Variant& getVariant() const;
QVariant getValue() const;
std::string toString() const;
protected:
void readRule();
void readFunctionName();
void readRelationType();
void readVariableName();
void updateHasVariable();
void updateComparisonType();
@ -207,38 +70,29 @@ namespace CSMWorld
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;
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
{
public:
InfoSelectWrapper(ESM::DialInfo::SelectStruct& select);
InfoSelectWrapper(ESM::DialogueCondition& select);
// Wrapped SelectStruct will not be modified until update() is called
void setFunctionName(FunctionName name);
void setRelationType(RelationType type);
void setFunctionName(ESM::DialogueCondition::Function name);
void setRelationType(ESM::DialogueCondition::Comparison 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();
void setValue(int value);
void setValue(float value);
private:
ESM::DialInfo::SelectStruct& mSelect;
ESM::DialogueCondition& mSelect;
void writeRule();
};

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

@ -46,41 +46,41 @@ QWidget* CSVWorld::IdCompletionDelegate::createEditor(QWidget* parent, const QSt
switch (conditionFunction)
{
case CSMWorld::ConstInfoSelectWrapper::Function_Global:
case ESM::DialogueCondition::Function_Global:
{
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);
}
case CSMWorld::ConstInfoSelectWrapper::Function_Item:
case ESM::DialogueCondition::Function_Item:
{
return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Referenceable);
}
case CSMWorld::ConstInfoSelectWrapper::Function_Dead:
case CSMWorld::ConstInfoSelectWrapper::Function_NotId:
case ESM::DialogueCondition::Function_Dead:
case ESM::DialogueCondition::Function_NotId:
{
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);
}
case CSMWorld::ConstInfoSelectWrapper::Function_NotClass:
case ESM::DialogueCondition::Function_NotClass:
{
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);
}
case CSMWorld::ConstInfoSelectWrapper::Function_NotCell:
case ESM::DialogueCondition::Function_NotCell:
{
return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Cell);
}
case CSMWorld::ConstInfoSelectWrapper::Function_Local:
case CSMWorld::ConstInfoSelectWrapper::Function_NotLocal:
case ESM::DialogueCondition::Function_Local:
case ESM::DialogueCondition::Function_NotLocal:
{
return new CSVWidget::DropLineEdit(display, parent);
}

@ -30,16 +30,16 @@ namespace
{
bool matchesStaticFilters(const MWDialogue::SelectWrapper& select, const MWWorld::Ptr& actor)
{
const ESM::RefId selectId = ESM::RefId::stringRefId(select.getName());
if (select.getFunction() == MWDialogue::SelectWrapper::Function_NotId)
const ESM::RefId selectId = select.getId();
if (select.getFunction() == ESM::DialogueCondition::Function_NotId)
return actor.getCellRef().getRefId() != selectId;
if (actor.getClass().isNpc())
{
if (select.getFunction() == MWDialogue::SelectWrapper::Function_NotFaction)
if (select.getFunction() == ESM::DialogueCondition::Function_NotFaction)
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;
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 true;
@ -47,7 +47,7 @@ namespace
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;
if (wrapper.getType() == MWDialogue::SelectWrapper::Type_Boolean)
@ -62,7 +62,7 @@ namespace
}
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);
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
{
for (std::vector<ESM::DialInfo::SelectStruct>::const_iterator iter(info.mSelects.begin());
iter != info.mSelects.end(); ++iter)
if (!testSelectStruct(*iter))
for (const auto& select : info.mSelects)
if (!testSelectStruct(select))
return false;
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.
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.
return false;
if (select.getFunction() == SelectWrapper::Function_Weather
if (select.getFunction() == ESM::DialogueCondition::Function_Weather
&& !(MWBase::Environment::get().getWorld()->isCellExterior()
|| MWBase::Environment::get().getWorld()->isCellQuasiExterior()))
// Reject weather conditions in interior cells
@ -305,29 +304,31 @@ bool MWDialogue::Filter::testSelectStructNumeric(const SelectWrapper& select) co
{
switch (select.getFunction())
{
case SelectWrapper::Function_Global:
case ESM::DialogueCondition::Function_Global:
// internally all globals are float :(
return select.selectCompare(MWBase::Environment::get().getWorld()->getGlobalFloat(select.getName()));
case SelectWrapper::Function_Local:
case ESM::DialogueCondition::Function_Local:
{
return testFunctionLocal(select);
}
case SelectWrapper::Function_NotLocal:
case ESM::DialogueCondition::Function_NotLocal:
{
return !testFunctionLocal(select);
}
case SelectWrapper::Function_PcHealthPercent:
case ESM::DialogueCondition::Function_PcHealthPercent:
{
MWWorld::Ptr player = MWMechanics::getPlayer();
return select.selectCompare(
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();
@ -336,7 +337,7 @@ bool MWDialogue::Filter::testSelectStructNumeric(const SelectWrapper& select) co
return select.selectCompare(value);
}
case SelectWrapper::Function_HealthPercent:
case ESM::DialogueCondition::Function_Health_Percent:
{
return select.selectCompare(
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())
{
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);
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(
ESM::RefId::stringRefId(select.getName()));
return MWBase::Environment::get().getMechanicsManager()->countDeaths(select.getId());
case SelectWrapper::Function_Choice:
case ESM::DialogueCondition::Function_Choice:
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();
if (argument < 0 || argument > 3)
@ -387,32 +390,65 @@ int MWDialogue::Filter::getSelectStructInteger(const SelectWrapper& select) cons
.getAiSetting(static_cast<MWMechanics::AiSetting>(argument))
.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());
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());
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();
return hits > 4 ? 4 : hits;
}
case SelectWrapper::Function_PcLevel:
case ESM::DialogueCondition::Function_PcLevel:
return player.getClass().getCreatureStats(player).getLevel();
case SelectWrapper::Function_PcGender:
case ESM::DialogueCondition::Function_PcGender:
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);
@ -429,11 +465,11 @@ int MWDialogue::Filter::getSelectStructInteger(const SelectWrapper& select) cons
return value;
}
case SelectWrapper::Function_PcCrimeLevel:
case ESM::DialogueCondition::Function_PcCrimeLevel:
return player.getClass().getNpcStats(player).getBounty();
case SelectWrapper::Function_RankRequirement:
case ESM::DialogueCondition::Function_RankRequirement:
{
const ESM::RefId& faction = mActor.getClass().getPrimaryFaction(mActor);
if (faction.empty())
@ -455,23 +491,23 @@ int MWDialogue::Filter::getSelectStructInteger(const SelectWrapper& select) cons
return result;
}
case SelectWrapper::Function_Level:
case ESM::DialogueCondition::Function_Level:
return mActor.getClass().getCreatureStats(mActor).getLevel();
case SelectWrapper::Function_PCReputation:
case ESM::DialogueCondition::Function_PcReputation:
return player.getClass().getNpcStats(player).getReputation();
case SelectWrapper::Function_Weather:
case ESM::DialogueCondition::Function_Weather:
return MWBase::Environment::get().getWorld()->getCurrentWeather();
case SelectWrapper::Function_Reputation:
case ESM::DialogueCondition::Function_Reputation:
return mActor.getClass().getNpcStats(mActor).getReputation();
case SelectWrapper::Function_FactionRankDiff:
case ESM::DialogueCondition::Function_FactionRankDifference:
{
const ESM::RefId& faction = mActor.getClass().getPrimaryFaction(mActor);
@ -483,14 +519,14 @@ int MWDialogue::Filter::getSelectStructInteger(const SelectWrapper& select) cons
return rank - npcRank;
}
case SelectWrapper::Function_WerewolfKills:
case ESM::DialogueCondition::Function_PcWerewolfKills:
return player.getClass().getNpcStats(player).getWerewolfKills();
case SelectWrapper::Function_RankLow:
case SelectWrapper::Function_RankHigh:
case ESM::DialogueCondition::Function_FacReactionLowest:
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);
@ -513,7 +549,7 @@ int MWDialogue::Filter::getSelectStructInteger(const SelectWrapper& select) cons
return value;
}
case SelectWrapper::Function_CreatureTargetted:
case ESM::DialogueCondition::Function_CreatureTarget:
{
MWWorld::Ptr target;
@ -540,53 +576,49 @@ bool MWDialogue::Filter::getSelectStructBoolean(const SelectWrapper& select) con
switch (select.getFunction())
{
case SelectWrapper::Function_False:
case ESM::DialogueCondition::Function_NotId:
return false;
case SelectWrapper::Function_NotId:
return !(mActor.getCellRef().getRefId() == ESM::RefId::stringRefId(select.getName()));
return mActor.getCellRef().getRefId() != select.getId();
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());
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)
== (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;
case SelectWrapper::Function_SameFaction:
case ESM::DialogueCondition::Function_SameFaction:
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();
case SelectWrapper::Function_PcBlightDisease:
case ESM::DialogueCondition::Function_PcBlightDisease:
return player.getClass().getCreatureStats(player).hasBlightDisease();
case SelectWrapper::Function_PcCorprus:
case ESM::DialogueCondition::Function_PcCorprus:
return player.getClass()
.getCreatureStats(player)
@ -595,7 +627,7 @@ bool MWDialogue::Filter::getSelectStructBoolean(const SelectWrapper& select) con
.getMagnitude()
!= 0;
case SelectWrapper::Function_PcExpelled:
case ESM::DialogueCondition::Function_PcExpelled:
{
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);
}
case SelectWrapper::Function_PcVampire:
case ESM::DialogueCondition::Function_PcVampire:
return player.getClass()
.getCreatureStats(player)
@ -614,27 +646,27 @@ bool MWDialogue::Filter::getSelectStructBoolean(const SelectWrapper& select) con
.getMagnitude()
> 0;
case SelectWrapper::Function_TalkedToPc:
case ESM::DialogueCondition::Function_TalkedToPc:
return mTalkedToPlayer;
case SelectWrapper::Function_Alarmed:
case ESM::DialogueCondition::Function_Alarmed:
return mActor.getClass().getCreatureStats(mActor).isAlarmed();
case SelectWrapper::Function_Detected:
case ESM::DialogueCondition::Function_Detected:
return MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, mActor);
case SelectWrapper::Function_Attacked:
case ESM::DialogueCondition::Function_Attacked:
return mActor.getClass().getCreatureStats(mActor).getAttacked();
case SelectWrapper::Function_ShouldAttack:
case ESM::DialogueCondition::Function_ShouldAttack:
return MWBase::Environment::get().getMechanicsManager()->isAggressive(mActor, MWMechanics::getPlayer());
case SelectWrapper::Function_Werewolf:
case ESM::DialogueCondition::Function_Werewolf:
return mActor.getClass().getNpcStats(mActor).isWerewolf();

@ -10,432 +10,265 @@
namespace
{
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)
{
case '0':
case ESM::DialogueCondition::Comp_Eq:
return value1 == value2;
case '1':
case ESM::DialogueCondition::Comp_Ne:
return value1 != value2;
case '2':
case ESM::DialogueCondition::Comp_Gt:
return value1 > value2;
case '3':
case ESM::DialogueCondition::Comp_Ge:
return value1 >= value2;
case '4':
case ESM::DialogueCondition::Comp_Ls:
return value1 < value2;
case '5':
case ESM::DialogueCondition::Comp_Le:
return value1 <= value2;
}
default:
throw std::runtime_error("unknown compare type in dialogue info select");
}
template <typename T>
bool selectCompareImp(const ESM::DialInfo::SelectStruct& 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)
template <typename T>
bool selectCompareImp(const ESM::DialogueCondition& select, T value1)
{
case 0:
return Function_RankLow;
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 std::visit(
[&](auto value) { return selectCompareImp(select.mComparison, value1, value); }, select.mValue);
}
return Function_False;
}
MWDialogue::SelectWrapper::SelectWrapper(const ESM::DialInfo::SelectStruct& select)
MWDialogue::SelectWrapper::SelectWrapper(const ESM::DialogueCondition& select)
: mSelect(select)
{
}
MWDialogue::SelectWrapper::Function MWDialogue::SelectWrapper::getFunction() const
{
char type = mSelect.mSelectRule[1];
switch (type)
ESM::DialogueCondition::Function MWDialogue::SelectWrapper::getFunction() const
{
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;
return mSelect.mFunction;
}
int MWDialogue::SelectWrapper::getArgument() const
{
if (mSelect.mSelectRule[1] != '1')
return 0;
int index = 0;
std::istringstream(mSelect.mSelectRule.substr(2, 2)) >> index;
switch (index)
switch (mSelect.mFunction)
{
// AI settings
case 67:
case ESM::DialogueCondition::Function_Fight:
return 1;
case 68:
case ESM::DialogueCondition::Function_Hello:
return 0;
case 69:
case ESM::DialogueCondition::Function_Alarm:
return 3;
case 70:
case ESM::DialogueCondition::Function_Flee:
return 2;
// attributes
case 10:
case ESM::DialogueCondition::Function_PcStrength:
return 0;
case 51:
case ESM::DialogueCondition::Function_PcIntelligence:
return 1;
case 52:
case ESM::DialogueCondition::Function_PcWillpower:
return 2;
case 53:
case ESM::DialogueCondition::Function_PcAgility:
return 3;
case 54:
case ESM::DialogueCondition::Function_PcSpeed:
return 4;
case 55:
case ESM::DialogueCondition::Function_PcEndurance:
return 5;
case 56:
case ESM::DialogueCondition::Function_PcPersonality:
return 6;
case 57:
case ESM::DialogueCondition::Function_PcLuck:
return 7;
// skills
case 11:
case ESM::DialogueCondition::Function_PcBlock:
return 0;
case 12:
case ESM::DialogueCondition::Function_PcArmorer:
return 1;
case 13:
case ESM::DialogueCondition::Function_PcMediumArmor:
return 2;
case 14:
case ESM::DialogueCondition::Function_PcHeavyArmor:
return 3;
case 15:
case ESM::DialogueCondition::Function_PcBluntWeapon:
return 4;
case 16:
case ESM::DialogueCondition::Function_PcLongBlade:
return 5;
case 17:
case ESM::DialogueCondition::Function_PcAxe:
return 6;
case 18:
case ESM::DialogueCondition::Function_PcSpear:
return 7;
case 19:
case ESM::DialogueCondition::Function_PcAthletics:
return 8;
case 20:
case ESM::DialogueCondition::Function_PcEnchant:
return 9;
case 21:
case ESM::DialogueCondition::Function_PcDestruction:
return 10;
case 22:
case ESM::DialogueCondition::Function_PcAlteration:
return 11;
case 23:
case ESM::DialogueCondition::Function_PcIllusion:
return 12;
case 24:
case ESM::DialogueCondition::Function_PcConjuration:
return 13;
case 25:
case ESM::DialogueCondition::Function_PcMysticism:
return 14;
case 26:
case ESM::DialogueCondition::Function_PcRestoration:
return 15;
case 27:
case ESM::DialogueCondition::Function_PcAlchemy:
return 16;
case 28:
case ESM::DialogueCondition::Function_PcUnarmored:
return 17;
case 29:
case ESM::DialogueCondition::Function_PcSecurity:
return 18;
case 30:
case ESM::DialogueCondition::Function_PcSneak:
return 19;
case 31:
case ESM::DialogueCondition::Function_PcAcrobatics:
return 20;
case 32:
case ESM::DialogueCondition::Function_PcLightArmor:
return 21;
case 33:
case ESM::DialogueCondition::Function_PcShortBlade:
return 22;
case 34:
case ESM::DialogueCondition::Function_PcMarksman:
return 23;
case 35:
case ESM::DialogueCondition::Function_PcMerchantile:
return 24;
case 36:
case ESM::DialogueCondition::Function_PcSpeechcraft:
return 25;
case 37:
case ESM::DialogueCondition::Function_PcHandToHand:
return 26;
// dynamic stats
case 8:
case ESM::DialogueCondition::Function_PcMagicka:
return 1;
case 9:
case ESM::DialogueCondition::Function_PcFatigue:
return 2;
case 64:
case ESM::DialogueCondition::Function_PcHealth:
return 0;
}
default:
return 0;
}
}
MWDialogue::SelectWrapper::Type MWDialogue::SelectWrapper::getType() const
{
static const Function integerFunctions[] = {
Function_Journal,
Function_Item,
Function_Dead,
Function_Choice,
Function_AiSetting,
Function_PcAttribute,
Function_PcSkill,
Function_FriendlyHit,
Function_PcLevel,
Function_PcGender,
Function_PcClothingModifier,
Function_PcCrimeLevel,
Function_RankRequirement,
Function_Level,
Function_PCReputation,
Function_Weather,
Function_Reputation,
Function_FactionRankDiff,
Function_WerewolfKills,
Function_RankLow,
Function_RankHigh,
Function_CreatureTargetted,
// end marker
Function_None,
};
static const Function numericFunctions[] = {
Function_Global,
Function_Local,
Function_NotLocal,
Function_PcDynamicStat,
Function_PcHealthPercent,
Function_HealthPercent,
// end marker
Function_None,
};
static const Function booleanFunctions[] = {
Function_False,
Function_SameGender,
Function_SameRace,
Function_SameFaction,
Function_PcCommonDisease,
Function_PcBlightDisease,
Function_PcCorprus,
Function_PcExpelled,
Function_PcVampire,
Function_TalkedToPc,
Function_Alarmed,
Function_Detected,
Function_Attacked,
Function_ShouldAttack,
Function_Werewolf,
// end marker
Function_None,
};
static const Function invertedBooleanFunctions[] = {
Function_NotId,
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)
switch (mSelect.mFunction)
{
case ESM::DialogueCondition::Function_Journal:
case ESM::DialogueCondition::Function_Item:
case ESM::DialogueCondition::Function_Dead:
case ESM::DialogueCondition::Function_Choice:
case ESM::DialogueCondition::Function_Fight:
case ESM::DialogueCondition::Function_Hello:
case ESM::DialogueCondition::Function_Alarm:
case ESM::DialogueCondition::Function_Flee:
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:
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:
case ESM::DialogueCondition::Function_FriendHit:
case ESM::DialogueCondition::Function_PcLevel:
case ESM::DialogueCondition::Function_PcGender:
case ESM::DialogueCondition::Function_PcClothingModifier:
case ESM::DialogueCondition::Function_PcCrimeLevel:
case ESM::DialogueCondition::Function_RankRequirement:
case ESM::DialogueCondition::Function_Level:
case ESM::DialogueCondition::Function_PcReputation:
case ESM::DialogueCondition::Function_Weather:
case ESM::DialogueCondition::Function_Reputation:
case ESM::DialogueCondition::Function_FactionRankDifference:
case ESM::DialogueCondition::Function_PcWerewolfKills:
case ESM::DialogueCondition::Function_FacReactionLowest:
case ESM::DialogueCondition::Function_FacReactionHighest:
case ESM::DialogueCondition::Function_CreatureTarget:
return Type_Integer;
for (int i = 0; numericFunctions[i] != Function_None; ++i)
if (numericFunctions[i] == function)
case ESM::DialogueCondition::Function_Global:
case ESM::DialogueCondition::Function_Local:
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;
for (int i = 0; booleanFunctions[i] != Function_None; ++i)
if (booleanFunctions[i] == function)
case ESM::DialogueCondition::Function_SameSex:
case ESM::DialogueCondition::Function_SameRace:
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;
for (int i = 0; invertedBooleanFunctions[i] != Function_None; ++i)
if (invertedBooleanFunctions[i] == function)
case ESM::DialogueCondition::Function_NotId:
case ESM::DialogueCondition::Function_NotFaction:
case ESM::DialogueCondition::Function_NotClass:
case ESM::DialogueCondition::Function_NotRace:
case ESM::DialogueCondition::Function_NotCell:
return Type_Inverted;
default:
return Type_None;
};
}
bool MWDialogue::SelectWrapper::isNpcOnly() const
{
static const Function functions[] = {
Function_NotFaction,
Function_NotClass,
Function_NotRace,
Function_SameGender,
Function_SameRace,
Function_SameFaction,
Function_RankRequirement,
Function_Reputation,
Function_FactionRankDiff,
Function_Werewolf,
Function_WerewolfKills,
Function_RankLow,
Function_RankHigh,
// end marker
Function_None,
};
Function function = getFunction();
for (int i = 0; functions[i] != Function_None; ++i)
if (functions[i] == function)
switch (mSelect.mFunction)
{
case ESM::DialogueCondition::Function_NotFaction:
case ESM::DialogueCondition::Function_NotClass:
case ESM::DialogueCondition::Function_NotRace:
case ESM::DialogueCondition::Function_SameSex:
case ESM::DialogueCondition::Function_SameRace:
case ESM::DialogueCondition::Function_SameFaction:
case ESM::DialogueCondition::Function_RankRequirement:
case ESM::DialogueCondition::Function_Reputation:
case ESM::DialogueCondition::Function_FactionRankDifference:
case ESM::DialogueCondition::Function_Werewolf:
case ESM::DialogueCondition::Function_PcWerewolfKills:
case ESM::DialogueCondition::Function_FacReactionLowest:
case ESM::DialogueCondition::Function_FacReactionHighest:
return true;
default:
return false;
}
}
bool MWDialogue::SelectWrapper::selectCompare(int value) const
{
@ -454,5 +287,15 @@ bool MWDialogue::SelectWrapper::selectCompare(bool value) 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
{
const ESM::DialInfo::SelectStruct& mSelect;
const ESM::DialogueCondition& mSelect;
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
{
Type_None,
@ -72,13 +19,10 @@ namespace MWDialogue
Type_Inverted
};
private:
Function decodeFunction() const;
public:
SelectWrapper(const ESM::DialInfo::SelectStruct& select);
SelectWrapper(const ESM::DialogueCondition& select);
Function getFunction() const;
ESM::DialogueCondition::Function getFunction() const;
int getArgument() const;
@ -95,6 +39,10 @@ namespace MWDialogue
std::string getName() const;
///< 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/loadcont.hpp>
#include <components/esm3/loaddial.hpp>
#include <components/esm3/loadinfo.hpp>
#include <components/esm3/loadregn.hpp>
#include <components/esm3/loadscpt.hpp>
#include <components/esm3/loadweap.hpp>
@ -603,6 +604,83 @@ namespace ESM
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()));
}
}

@ -181,7 +181,7 @@ add_component_dir (esm3
inventorystate containerstate npcstate creaturestate dialoguestate statstate npcstats creaturestats
weatherstate quickkeys fogstate spellstate activespells creaturelevliststate doorstate projectilestate debugprofile
aisequence magiceffects custommarkerstate stolenitems transport animationstate controlsstate mappings readerscache
infoorder timestamp formatversion landrecorddata selectiongroup
infoorder timestamp formatversion landrecorddata selectiongroup dialoguecondition
)
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;
case fourCC("SCVR"):
{
SelectStruct ss;
ss.mSelectRule = esm.getHString();
ss.mValue.read(esm, Variant::Format_Info);
mSelects.push_back(ss);
auto filter = DialogueCondition::load(esm, mId);
if (filter)
mSelects.emplace_back(std::move(*filter));
break;
}
case fourCC("BNAM"):
@ -120,11 +119,8 @@ namespace ESM
esm.writeHNOCString("SNAM", mSound);
esm.writeHNOString("NAME", mResponse);
for (std::vector<SelectStruct>::const_iterator it = mSelects.begin(); it != mSelects.end(); ++it)
{
esm.writeHNString("SCVR", it->mSelectRule);
it->mValue.write(esm, Variant::Format_Info);
}
for (const auto& rule : mSelects)
rule.save(esm);
esm.writeHNOString("BNAM", mResultScript);

@ -4,8 +4,10 @@
#include <string>
#include <vector>
#include "components/esm/defs.hpp"
#include "components/esm/refid.hpp"
#include <components/esm/defs.hpp>
#include <components/esm/refid.hpp>
#include "dialoguecondition.hpp"
#include "variant.hpp"
namespace ESM
@ -47,13 +49,6 @@ namespace ESM
}; // 12 bytes
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)
enum QuestStatus
{
@ -65,7 +60,7 @@ namespace ESM
// Rules for when to include this item in the final list of options
// visible to the player.
std::vector<SelectStruct> mSelects;
std::vector<DialogueCondition> mSelects;
// Id of this, previous and next INFO items
RefId mId, mPrev, mNext;

Loading…
Cancel
Save