diff --git a/apps/opencs/model/world/columnbase.cpp b/apps/opencs/model/world/columnbase.cpp index 659954f481..cf125aa639 100644 --- a/apps/opencs/model/world/columnbase.cpp +++ b/apps/opencs/model/world/columnbase.cpp @@ -81,6 +81,9 @@ bool CSMWorld::ColumnBase::isId (Display display) Display_PartRefType, Display_AiPackageType, Display_YesNo, + Display_InfoCondFunc, + Display_InfoCondVar, + Display_InfoCondComp, Display_None }; diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp index 71c22a9f07..2d2513774a 100644 --- a/apps/opencs/model/world/columnbase.hpp +++ b/apps/opencs/model/world/columnbase.hpp @@ -116,6 +116,9 @@ namespace CSMWorld Display_PartRefType, Display_AiPackageType, Display_YesNo, + Display_InfoCondFunc, + Display_InfoCondVar, + Display_InfoCondComp, //top level columns that nest other columns Display_NestedHeader diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index 9076aa0968..89ee6258b5 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -274,6 +274,11 @@ namespace CSMWorld { ColumnId_SkillImpact, "Skills" }, { ColumnId_InfoList, "Info List" }, + { ColumnId_InfoCondition, "Info Conditions" }, + { ColumnId_InfoCondFunc, "Function" }, + { ColumnId_InfoCondVar, "Func/Variable" }, + { ColumnId_InfoCondComp, "Comp" }, + { ColumnId_InfoCondValue, "Value" }, { ColumnId_OriginalCell, "Original Cell" }, { ColumnId_UseValue1, "Use value 1" }, @@ -502,6 +507,18 @@ namespace "No", "Yes", 0 }; + static const char *sInfoCondFunc[] = + { + " ", "Function", "Global", "Local", "Journal", + "Item", "Dead", "Not ID", "Not Faction", "Not Class", + "Not Race", "Not Cell", "Not Local", 0 + }; + + static const char *sInfoCondComp[] = + { + "!=", "<", "<=", "=", ">", ">=", 0 + }; + const char **getEnumNames (CSMWorld::Columns::ColumnId column) { switch (column) @@ -530,6 +547,10 @@ namespace case CSMWorld::Columns::ColumnId_PartRefType: return sPartRefType; case CSMWorld::Columns::ColumnId_AiPackageType: return sAiPackageType; case CSMWorld::Columns::ColumnId_AiWanderRepeat: return sAiWanderRepeat; + case CSMWorld::Columns::ColumnId_InfoCondFunc: return sInfoCondFunc; + // FIXME: don't have dynamic value enum delegate, use Display_String for now + //case CSMWorld::Columns::ColumnId_InfoCond: return sInfoCond; + case CSMWorld::Columns::ColumnId_InfoCondComp: return sInfoCondComp; default: return 0; } diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index b87f6c53d0..f971f3fd89 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -264,8 +264,13 @@ namespace CSMWorld ColumnId_SkillImpact = 240, // impact from magic effects ColumnId_InfoList = 241, + ColumnId_InfoCondition = 242, + ColumnId_InfoCondFunc = 243, + ColumnId_InfoCondVar = 244, + ColumnId_InfoCondComp = 245, + ColumnId_InfoCondValue = 246, - ColumnId_OriginalCell = 242, + ColumnId_OriginalCell = 247, // Allocated to a separate value range, so we don't get a collision should we ever need // to extend the number of use values. diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index fc4532fb0e..e2fab0a25b 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -242,6 +242,19 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mTopicInfos.addAdapter (std::make_pair(&mTopicInfos.getColumn(index), new InfoListAdapter ())); mTopicInfos.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_ScriptText, ColumnBase::Display_ScriptLines)); + // Special conditions + mTopicInfos.addColumn (new NestedParentColumn (Columns::ColumnId_InfoCondition)); + index = mTopicInfos.getColumns()-1; + mTopicInfos.addAdapter (std::make_pair(&mTopicInfos.getColumn(index), new InfoConditionAdapter ())); + mTopicInfos.getNestableColumn(index)->addColumn( + new NestedChildColumn (Columns::ColumnId_InfoCondFunc, ColumnBase::Display_InfoCondFunc)); + // FIXME: don't have dynamic value enum delegate, use Display_String for now + mTopicInfos.getNestableColumn(index)->addColumn( + new NestedChildColumn (Columns::ColumnId_InfoCondVar, ColumnBase::Display_String)); + mTopicInfos.getNestableColumn(index)->addColumn( + new NestedChildColumn (Columns::ColumnId_InfoCondComp, ColumnBase::Display_InfoCondComp)); + mTopicInfos.getNestableColumn(index)->addColumn( + new NestedChildColumn (Columns::ColumnId_Value, ColumnBase::Display_Var)); mJournalInfos.addColumn (new StringIdColumn (true)); mJournalInfos.addColumn (new RecordStateColumn); diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index d29155a478..a0d7645767 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -528,4 +528,356 @@ namespace CSMWorld { return 1; // fixed at size 1 } + + // ESM::DialInfo::SelectStruct.mSelectRule + // 012345... + // ^^^ ^^ + // ||| || + // ||| |+------------- condition variable string + // ||| +-------------- comparison type, ['0'..'5']; e.g. !=, <, >=, etc + // ||+---------------- function index (encoded, where function == '1') + // |+----------------- function, ['1'..'C']; e.g. Global, Local, Not ID, etc + // +------------------ unknown + // + InfoConditionAdapter::InfoConditionAdapter () {} + + void InfoConditionAdapter::addRow(Record& record, int position) const + { + Info info = record.get(); + + std::vector& conditions = info.mSelects; + + // blank row + ESM::DialInfo::SelectStruct condStruct; + condStruct.mSelectRule = "00000"; + condStruct.mValue = ESM::Variant(); + condStruct.mValue.setType(ESM::VT_Int); // default to ints + + conditions.insert(conditions.begin()+position, condStruct); + + record.setModified (info); + } + + void InfoConditionAdapter::removeRow(Record& record, int rowToRemove) const + { + Info info = record.get(); + + std::vector& conditions = info.mSelects; + + if (rowToRemove < 0 || rowToRemove >= static_cast (conditions.size())) + throw std::runtime_error ("index out of range"); + + conditions.erase(conditions.begin()+rowToRemove); + + record.setModified (info); + } + + void InfoConditionAdapter::setTable(Record& record, + const NestedTableWrapperBase& nestedTable) const + { + Info info = record.get(); + + info.mSelects = + static_cast >&>(nestedTable).mNestedTable; + + record.setModified (info); + } + + NestedTableWrapperBase* InfoConditionAdapter::table(const Record& record) const + { + // deleted by dtor of NestedTableStoring + return new NestedTableWrapper >(record.get().mSelects); + } + + // See the mappings in MWDialogue::SelectWrapper::getArgument + // from ESM::Attribute, ESM::Skill and MWMechanics::CreatureStats (for AI) + static std::map populateEncToInfoFunc() + { + std::map funcMap; + funcMap["00"] = "Rank Low"; + funcMap["01"] = "Rank High"; + funcMap["02"] = "Rank Requirement"; + funcMap["03"] = "Reputation"; + funcMap["04"] = "Health Percent"; + funcMap["05"] = "PC Reputation"; + funcMap["06"] = "PC Level"; + funcMap["07"] = "PC Health Percent"; + funcMap["08"] = "PC Magicka"; + funcMap["09"] = "PC Fatigue"; + funcMap["10"] = "PC Strength"; + funcMap["11"] = "PC Block"; + funcMap["12"] = "PC Armoror"; + funcMap["13"] = "PC Medium Armor"; + funcMap["14"] = "PC Heavy Armor"; + funcMap["15"] = "PC Blunt Weapon"; + funcMap["16"] = "PC Long Blade"; + funcMap["17"] = "PC Axe"; + funcMap["18"] = "PC Spear"; + funcMap["19"] = "PC Athletics"; + funcMap["20"] = "PC Enchant"; + funcMap["21"] = "PC Destruction"; + funcMap["22"] = "PC Alteration"; + funcMap["23"] = "PC Illusion"; + funcMap["24"] = "PC Conjuration"; + funcMap["25"] = "PC Mysticism"; + funcMap["26"] = "PC Restoration"; + funcMap["27"] = "PC Alchemy"; + funcMap["28"] = "PC Unarmored"; + funcMap["29"] = "PC Security"; + funcMap["30"] = "PC Sneak"; + funcMap["31"] = "PC Acrobatics"; + funcMap["32"] = "PC Light Armor"; + funcMap["33"] = "PC Short Blade"; + funcMap["34"] = "PC Marksman"; + funcMap["35"] = "PC Merchantile"; + funcMap["36"] = "PC Speechcraft"; + funcMap["37"] = "PC Hand To Hand"; + funcMap["38"] = "PC Sex"; + funcMap["39"] = "PC Expelled"; + funcMap["40"] = "PC Common Disease"; + funcMap["41"] = "PC Blight Disease"; + funcMap["42"] = "PC Clothing Modifier"; + funcMap["43"] = "PC Crime Level"; + funcMap["44"] = "Same Sex"; + funcMap["45"] = "Same Race"; + funcMap["46"] = "Same Faction"; + funcMap["47"] = "Faction Rank Difference"; + funcMap["48"] = "Detected"; + funcMap["49"] = "Alarmed"; + funcMap["50"] = "Choice"; + funcMap["51"] = "PC Intelligence"; + funcMap["52"] = "PC Willpower"; + funcMap["53"] = "PC Agility"; + funcMap["54"] = "PC Speed"; + funcMap["55"] = "PC Endurance"; + funcMap["56"] = "PC Personality"; + funcMap["57"] = "PC Luck"; + funcMap["58"] = "PC Corpus"; + funcMap["59"] = "Weather"; + funcMap["60"] = "PC Vampire"; + funcMap["61"] = "Level"; + funcMap["62"] = "Attacked"; + funcMap["63"] = "Talked To PC"; + funcMap["64"] = "PC Health"; + funcMap["65"] = "Creature Target"; + funcMap["66"] = "Friend Hit"; + funcMap["67"] = "Fight"; + funcMap["68"] = "Hello"; + funcMap["69"] = "Alarm"; + funcMap["70"] = "Flee"; + funcMap["71"] = "Should Attack"; + funcMap["72"] = "Werewolf"; + funcMap["73"] = "PC Werewolf Kills"; + return funcMap; + } + static const std::map sEncToInfoFunc = populateEncToInfoFunc(); + + QVariant InfoConditionAdapter::getData(const Record& record, + int subRowIndex, int subColIndex) const + { + Info info = record.get(); + + std::vector& conditions = info.mSelects; + + if (subRowIndex < 0 || subRowIndex >= static_cast (conditions.size())) + throw std::runtime_error ("index out of range"); + + switch (subColIndex) + { + case 0: + { + char condType = conditions[subRowIndex].mSelectRule[1]; + switch (condType) + { + case '0': return 0; // blank space + case '1': return 1; // Function + case '2': return 2; // Global + case '3': return 3; // Local + case '4': return 4; // Journal + case '5': return 5; // Item + case '6': return 6; // Dead + case '7': return 7; // Not ID + case '8': return 8; // Not Factio + case '9': return 9; // Not Class + case 'A': return 10; // Not Race + case 'B': return 11; // Not Cell + case 'C': return 12; // Not Local + default: return QVariant(); // TODO: log an error? + } + } + case 1: + { + if (conditions[subRowIndex].mSelectRule[1] == '1') + { + // throws an exception if the encoding is not found + return sEncToInfoFunc.at(conditions[subRowIndex].mSelectRule.substr(2, 2)).c_str(); + } + else + return QString(conditions[subRowIndex].mSelectRule.substr(5).c_str()); + } + case 2: + { + char compType = conditions[subRowIndex].mSelectRule[4]; + switch (compType) + { + case '0': return 3; // = + case '1': return 0; // != + case '2': return 4; // > + case '3': return 5; // >= + case '4': return 1; // < + case '5': return 2; // <= + default: return QVariant(); // TODO: log an error? + } + } + case 3: + { + switch (conditions[subRowIndex].mValue.getType()) + { + case ESM::VT_String: + { + return QString::fromUtf8 (conditions[subRowIndex].mValue.getString().c_str()); + } + case ESM::VT_Int: + case ESM::VT_Short: + case ESM::VT_Long: + { + return conditions[subRowIndex].mValue.getInteger(); + } + case ESM::VT_Float: + { + return conditions[subRowIndex].mValue.getFloat(); + } + default: return QVariant(); + } + } + default: throw std::runtime_error("Info condition subcolumn index out of range"); + } + } + + void InfoConditionAdapter::setData(Record& record, + const QVariant& value, int subRowIndex, int subColIndex) const + { + Info info = record.get(); + + std::vector& conditions = info.mSelects; + + if (subRowIndex < 0 || subRowIndex >= static_cast (conditions.size())) + throw std::runtime_error ("index out of range"); + + switch (subColIndex) + { + case 0: + { + // See sInfoCondFunc in columns.cpp for the enum values + switch (value.toInt()) + { + // FIXME: when these change the values of the other columns need to change + // correspondingly (and automatically) + case 1: + { + conditions[subRowIndex].mSelectRule[1] = '1'; // Function + // default to "Rank Low" + conditions[subRowIndex].mSelectRule[2] = '0'; + conditions[subRowIndex].mSelectRule[3] = '0'; + break; + } + case 2: conditions[subRowIndex].mSelectRule[1] = '2'; break; // Global + case 3: conditions[subRowIndex].mSelectRule[1] = '3'; break; // Local + case 4: conditions[subRowIndex].mSelectRule[1] = '4'; break; // Journal + case 5: conditions[subRowIndex].mSelectRule[1] = '5'; break; // Item + case 6: conditions[subRowIndex].mSelectRule[1] = '6'; break; // Dead + case 7: conditions[subRowIndex].mSelectRule[1] = '7'; break; // Not ID + case 8: conditions[subRowIndex].mSelectRule[1] = '8'; break; // Not Faction + case 9: conditions[subRowIndex].mSelectRule[1] = '9'; break; // Not Class + case 10: conditions[subRowIndex].mSelectRule[1] = 'A'; break; // Not Race + case 11: conditions[subRowIndex].mSelectRule[1] = 'B'; break; // Not Cell + case 12: conditions[subRowIndex].mSelectRule[1] = 'C'; break; // Not Local + default: return; // return without saving + } + break; + } + case 1: + { + if (conditions[subRowIndex].mSelectRule[1] == '1') + { + std::map::const_iterator it = sEncToInfoFunc.begin(); + for (;it != sEncToInfoFunc.end(); ++it) + { + if (it->second == value.toString().toUtf8().constData()) + { + std::string rule = conditions[subRowIndex].mSelectRule.substr(0, 2); + rule.append(it->first); + // leave old values for undo (NOTE: may not be vanilla's behaviour) + rule.append(conditions[subRowIndex].mSelectRule.substr(4)); + conditions[subRowIndex].mSelectRule = rule; + break; + } + } + + if (it == sEncToInfoFunc.end()) + return; // return without saving; TODO: maybe log an error here + } + else + { + // FIXME: validate the string values before saving, based on the current function + std::string rule = conditions[subRowIndex].mSelectRule.substr(0, 5); + conditions[subRowIndex].mSelectRule = rule.append(value.toString().toUtf8().constData()); + } + break; + } + case 2: + { + // See sInfoCondComp in columns.cpp for the enum values + switch (value.toInt()) + { + case 0: conditions[subRowIndex].mSelectRule[4] = '1'; break; // != + case 1: conditions[subRowIndex].mSelectRule[4] = '4'; break; // < + case 2: conditions[subRowIndex].mSelectRule[4] = '5'; break; // <= + case 3: conditions[subRowIndex].mSelectRule[4] = '0'; break; // = + case 4: conditions[subRowIndex].mSelectRule[4] = '2'; break; // > + case 5: conditions[subRowIndex].mSelectRule[4] = '3'; break; // >= + default: return; // return without saving + } + break; + } + case 3: + { + switch (conditions[subRowIndex].mValue.getType()) + { + case ESM::VT_String: + { + conditions[subRowIndex].mValue.setString (value.toString().toUtf8().constData()); + break; + } + case ESM::VT_Int: + case ESM::VT_Short: + case ESM::VT_Long: + { + conditions[subRowIndex].mValue.setInteger (value.toInt()); + break; + } + case ESM::VT_Float: + { + conditions[subRowIndex].mValue.setFloat (value.toFloat()); + break; + } + default: break; + } + break; + } + default: throw std::runtime_error("Info condition subcolumn index out of range"); + } + + record.setModified (info); + } + + int InfoConditionAdapter::getColumnsCount(const Record& record) const + { + return 4; + } + + int InfoConditionAdapter::getRowsCount(const Record& record) const + { + return static_cast(record.get().mSelects.size()); + } } diff --git a/apps/opencs/model/world/nestedcoladapterimp.hpp b/apps/opencs/model/world/nestedcoladapterimp.hpp index 96dcd943de..ea2037eb82 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.hpp +++ b/apps/opencs/model/world/nestedcoladapterimp.hpp @@ -412,6 +412,31 @@ namespace CSMWorld virtual int getRowsCount(const Record& record) const; }; + + class InfoConditionAdapter : public NestedColumnAdapter + { + public: + InfoConditionAdapter (); + + virtual void addRow(Record& record, int position) const; + + virtual void removeRow(Record& record, int rowToRemove) const; + + virtual void setTable(Record& record, + const NestedTableWrapperBase& nestedTable) const; + + virtual NestedTableWrapperBase* table(const Record& record) const; + + virtual QVariant getData(const Record& record, + int subRowIndex, int subColIndex) const; + + virtual void setData(Record& record, + const QVariant& value, int subRowIndex, int subColIndex) const; + + virtual int getColumnsCount(const Record& record) const; + + virtual int getRowsCount(const Record& record) const; + }; } #endif // CSM_WOLRD_NESTEDCOLADAPTERIMP_H diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp index 5908c67a19..6362f96590 100644 --- a/apps/opencs/view/doc/viewmanager.cpp +++ b/apps/opencs/view/doc/viewmanager.cpp @@ -90,7 +90,9 @@ CSVDoc::ViewManager::ViewManager (CSMDoc::DocumentManager& documentManager) { CSMWorld::ColumnBase::Display_EffectId, CSMWorld::Columns::ColumnId_EffectId, false }, { CSMWorld::ColumnBase::Display_PartRefType, CSMWorld::Columns::ColumnId_PartRefType, false }, { CSMWorld::ColumnBase::Display_AiPackageType, CSMWorld::Columns::ColumnId_AiPackageType, false }, - { CSMWorld::ColumnBase::Display_YesNo, CSMWorld::Columns::ColumnId_AiWanderRepeat, false } + { CSMWorld::ColumnBase::Display_YesNo, CSMWorld::Columns::ColumnId_AiWanderRepeat, false }, + { CSMWorld::ColumnBase::Display_InfoCondFunc, CSMWorld::Columns::ColumnId_InfoCondFunc, false }, + { CSMWorld::ColumnBase::Display_InfoCondComp, CSMWorld::Columns::ColumnId_InfoCondComp, false } }; for (std::size_t i=0; i