From ad040462436abea2ef2c62f734707c3d66b8b7d5 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Thu, 18 Jun 2015 22:02:08 +1000 Subject: [PATCH 01/16] Mimic vanilla CS behaviour with NPC stats auto calculations. Should resolve Bug #2663. - Currently does not auto-update when race, class or skills tables change. --- apps/opencs/model/world/data.cpp | 8 +- apps/opencs/model/world/data.hpp | 2 + .../model/world/nestedcoladapterimp.cpp | 66 ++- apps/opencs/model/world/refidadapterimp.cpp | 462 ++++++++++++++++-- apps/opencs/model/world/refidadapterimp.hpp | 36 +- apps/opencs/model/world/refidcollection.cpp | 14 +- apps/opencs/model/world/refidcollection.hpp | 4 +- apps/opencs/model/world/usertype.hpp | 44 ++ apps/opencs/view/world/dialoguesubview.cpp | 14 +- apps/opencs/view/world/nestedtable.cpp | 52 +- apps/opencs/view/world/nestedtable.hpp | 3 +- apps/opencs/view/world/util.cpp | 14 +- 12 files changed, 628 insertions(+), 91 deletions(-) create mode 100644 apps/opencs/model/world/usertype.hpp diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 529c8f88f..ffa1ff3c3 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -62,7 +62,8 @@ int CSMWorld::Data::count (RecordBase::State state, const CollectionBase& collec CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager) : mEncoder (encoding), mPathgrids (mCells), mRefs (mCells), - mResourcesManager (resourcesManager), mReader (0), mDialogue (0), mReaderIndex(0) + mResourcesManager (resourcesManager), mReader (0), mDialogue (0), mReaderIndex(0), + mReferenceables(self()) { int index = 0; @@ -1159,3 +1160,8 @@ void CSMWorld::Data::rowsChanged (const QModelIndex& parent, int start, int end) { emit idListChanged(); } + +const CSMWorld::Data& CSMWorld::Data::self () +{ + return *this; +} diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index 060e47bd9..f83f9de22 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -121,6 +121,8 @@ namespace CSMWorld static int count (RecordBase::State state, const CollectionBase& collection); + const CSMWorld::Data& self (); + public: Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager); diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index b7d09777d..14a436c83 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -6,6 +6,7 @@ #include "idcollection.hpp" #include "pathgrid.hpp" #include "info.hpp" +#include "usertype.hpp" namespace CSMWorld { @@ -1069,23 +1070,66 @@ namespace CSMWorld switch (subColIndex) { case 0: return isInterior; - case 1: return (isInterior && !behaveLikeExterior) ? - cell.mAmbi.mAmbient : QVariant(QVariant::UserType); - case 2: return (isInterior && !behaveLikeExterior) ? - cell.mAmbi.mSunlight : QVariant(QVariant::UserType); - case 3: return (isInterior && !behaveLikeExterior) ? - cell.mAmbi.mFog : QVariant(QVariant::UserType); - case 4: return (isInterior && !behaveLikeExterior) ? - cell.mAmbi.mFogDensity : QVariant(QVariant::UserType); + case 1: + { + if (isInterior && !behaveLikeExterior) + return cell.mAmbi.mAmbient; + else + { + UserInt i(cell.mAmbi.mAmbient); + return QVariant(QVariant::fromValue(i)); + } + } + case 2: + { + if (isInterior && !behaveLikeExterior) + return cell.mAmbi.mSunlight; + else + { + UserInt i(cell.mAmbi.mSunlight); + return QVariant(QVariant::fromValue(i)); + } + } + case 3: + { + if (isInterior && !behaveLikeExterior) + return cell.mAmbi.mFog; + else + { + UserInt i(cell.mAmbi.mFog); + return QVariant(QVariant::fromValue(i)); + } + } + case 4: + { + if (isInterior && !behaveLikeExterior) + return cell.mAmbi.mFogDensity; + else + { + UserFloat f(cell.mAmbi.mFogDensity); + return QVariant(QVariant::fromValue(f)); + } + } case 5: { if (isInterior && !behaveLikeExterior && interiorWater) return cell.mWater; else - return QVariant(QVariant::UserType); + { + UserFloat f(cell.mWater); + return QVariant(QVariant::fromValue(f)); + } + } + case 6: + { + if (isInterior) + { + UserInt i(cell.mMapColor); + return QVariant(QVariant::fromValue(i)); + } + else + return cell.mMapColor; // TODO: how to select? } - case 6: return isInterior ? - QVariant(QVariant::UserType) : cell.mMapColor; // TODO: how to select? //case 7: return isInterior ? //behaveLikeExterior : QVariant(QVariant::UserType); default: throw std::runtime_error("Cell subcolumn index out of range"); diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index 4c369ef24..86f0461af 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -5,7 +5,195 @@ #include #include +#include + #include "nestedtablewrapper.hpp" +#include "usertype.hpp" + +namespace +{ + +int is_even(double d) +{ + double int_part; + + modf(d / 2.0, &int_part); + return 2.0 * int_part == d; +} + +int round_ieee_754(double d) +{ + double i = floor(d); + d -= i; + + if(d < 0.5) + return static_cast(i); + if(d > 0.5) + return static_cast(i) + 1; + if(is_even(i)) + return static_cast(i); + return static_cast(i) + 1; +} + +std::vector autoCalculateAttributes (const ESM::NPC &npc, + const ESM::Race& race, const ESM::Class& class_, const CSMWorld::IdCollection& skillTable) +{ + // race bonus + bool male = (npc.mFlags & ESM::NPC::Female) == 0; + + if (npc.mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) + return std::vector(); + + short level = npc.mNpdt12.mLevel; + + int attr[ESM::Attribute::Length]; + + for (int i = 0; i < ESM::Attribute::Length; ++i) + { + const ESM::Race::MaleFemale& attribute = race.mData.mAttributeValues[i]; + attr[i] = male ? attribute.mMale : attribute.mFemale; + } + + // class bonus + for (int i = 0; i < 2; ++i) + { + int attribute = class_.mData.mAttribute[i]; + if (attribute >= 0 && attribute < ESM::Attribute::Length) + { + attr[attribute] = attr[attribute] + 10; + } + // else log an error + } + + std::vector result(ESM::Attribute::Length); + // skill bonus + for (int attribute = 0; attribute < ESM::Attribute::Length; ++attribute) + { + float modifierSum = 0; + + for (int j = 0; j < ESM::Skill::Length; ++j) + { + // if the skill does not exist, throws std::runtime_error ("invalid ID: " + id) + const ESM::Skill& skill = skillTable.getRecord(ESM::Skill::indexToId(j)).get(); + + if (skill.mData.mAttribute != attribute) + continue; + + // is this a minor or major skill? + float add = 0.2f; + for (int k = 0; k < 5; ++k) + { + if (class_.mData.mSkills[k][0] == j) + add = 0.5; + } + for (int k = 0; k < 5; ++k) + { + if (class_.mData.mSkills[k][1] == j) + add = 1.0; + } + modifierSum += add; + } + result[attribute] = std::min(round_ieee_754(attr[attribute] + (level-1) * modifierSum), 100); + } + + return result; +} + +std::vector autoCalculateSkills (const ESM::NPC &npc, + const ESM::Race& race, const ESM::Class& class_, const CSMWorld::IdCollection& skillTable) +{ + unsigned char skills[ESM::Skill::Length]; + for (int i = 0; i < ESM::Skill::Length; ++i) + skills[i] = 0; + + for (int i = 0; i < 2; ++i) + { + int bonus = (i == 0) ? 10 : 25; + + for (int i2 = 0; i2 < 5; ++i2) + { + int index = class_.mData.mSkills[i2][i]; + if (index >= 0 && index < ESM::Skill::Length) + { + skills[index] = bonus; + } + } + } + + std::vector result(ESM::Skill::Length); + for (int skillIndex = 0; skillIndex < ESM::Skill::Length; ++skillIndex) + { + float majorMultiplier = 0.1f; + float specMultiplier = 0.0f; + + int raceBonus = 0; + int specBonus = 0; + + for (int raceSkillIndex = 0; raceSkillIndex < 7; ++raceSkillIndex) + { + if (race.mData.mBonus[raceSkillIndex].mSkill == skillIndex) + { + raceBonus = race.mData.mBonus[raceSkillIndex].mBonus; + break; + } + } + + for (int k = 0; k < 5; ++k) + { + // is this a minor or major skill? + if ((class_.mData.mSkills[k][0] == skillIndex) || (class_.mData.mSkills[k][1] == skillIndex)) + { + majorMultiplier = 1.0f; + break; + } + } + + // is this skill in the same Specialization as the class? + const ESM::Skill& skill = skillTable.getRecord(ESM::Skill::indexToId(skillIndex)).get(); + if (skill.mData.mSpecialization == class_.mData.mSpecialization) + { + specMultiplier = 0.5f; + specBonus = 5; + } + + // Must gracefully handle level 0 + result[skillIndex] = std::min(round_ieee_754(skills[skillIndex] + 5 + raceBonus + specBonus + + (npc.mNpdt12.mLevel-1) * (majorMultiplier + specMultiplier)), 100); + } + + return result; +} + +unsigned short autoCalculateHealth(const ESM::NPC &npc, + const ESM::Class& class_, const std::vector& attr) +{ + int multiplier = 3; + + if (class_.mData.mSpecialization == ESM::Class::Combat) + multiplier += 2; + else if (class_.mData.mSpecialization == ESM::Class::Stealth) + multiplier += 1; + + if (class_.mData.mAttribute[0] == ESM::Attribute::Endurance + || class_.mData.mAttribute[1] == ESM::Attribute::Endurance) + multiplier += 1; + + return floor(0.5f * (attr[ESM::Attribute::Strength]+ attr[ESM::Attribute::Endurance])) + + multiplier * (npc.mNpdt12.mLevel-1); +} + +unsigned short autoCalculateMana(const std::vector& attr) +{ + return attr[ESM::Attribute::Intelligence] * 2; +} + +unsigned short autoCalculateFatigue(const std::vector& attr) +{ + return attr[ESM::Attribute::Strength] + attr[ESM::Attribute::Willpower] + + attr[ESM::Attribute::Agility] + attr[ESM::Attribute::Endurance]; +} + +} CSMWorld::PotionColumns::PotionColumns (const InventoryColumns& columns) : InventoryColumns (columns) {} @@ -553,8 +741,12 @@ CSMWorld::NpcColumns::NpcColumns (const ActorColumns& actorColumns) mMisc(NULL) {} -CSMWorld::NpcRefIdAdapter::NpcRefIdAdapter (const NpcColumns& columns) -: ActorRefIdAdapter (UniversalId::Type_Npc, columns), mColumns (columns) +CSMWorld::NpcRefIdAdapter::NpcRefIdAdapter (const NpcColumns& columns, + const CSMWorld::IdCollection& races, + const CSMWorld::IdCollection& classes, + const CSMWorld::IdCollection& skills) +: ActorRefIdAdapter (UniversalId::Type_Npc, columns), mColumns (columns), + mRaceTable(races), mClassTable(classes), mSkillTable(skills) {} QVariant CSMWorld::NpcRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, int index) @@ -629,8 +821,46 @@ void CSMWorld::NpcRefIdAdapter::setData (const RefIdColumn *column, RefIdData& d npc.mFlags &= ~iter->second; if (iter->second == ESM::NPC::Autocalc) - npc.mNpdtType = (value.toInt() != 0) ? ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS - : ESM::NPC::NPC_DEFAULT; + { + if(value.toInt() != 0) + { + npc.mNpdtType = ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS; + + // if the race/class does not exist, throws std::runtime_error ("invalid ID: " + id) + const ESM::Race& race = mRaceTable.getRecord(npc.mRace).get(); + const ESM::Class& class_ = mClassTable.getRecord(npc.mClass).get(); + std::vector attr = autoCalculateAttributes(npc, race, class_, mSkillTable); + if (attr.empty()) + return; + + std::vector skills = autoCalculateSkills(npc, race, class_, mSkillTable); + + ESM::NPC::NPDTstruct52& npcStruct = npc.mNpdt52; + + npcStruct.mLevel = npc.mNpdt12.mLevel; + npcStruct.mStrength = attr[ESM::Attribute::Strength]; + npcStruct.mIntelligence = attr[ESM::Attribute::Intelligence]; + npcStruct.mWillpower = attr[ESM::Attribute::Willpower]; + npcStruct.mAgility = attr[ESM::Attribute::Agility]; + npcStruct.mSpeed = attr[ESM::Attribute::Speed]; + npcStruct.mEndurance = attr[ESM::Attribute::Endurance]; + npcStruct.mPersonality = attr[ESM::Attribute::Personality]; + npcStruct.mLuck = attr[ESM::Attribute::Luck]; + for (int i = 0; i < ESM::Skill::Length; ++i) + { + npcStruct.mSkills[i] = skills[i]; + } + npcStruct.mHealth = autoCalculateHealth(npc, class_, attr); + npcStruct.mMana = autoCalculateMana(attr); + npcStruct.mFatigue = autoCalculateFatigue(attr); + npcStruct.mDisposition = npc.mNpdt12.mDisposition; + npcStruct.mReputation = npc.mNpdt12.mReputation; + npcStruct.mRank = npc.mNpdt12.mRank; + npcStruct.mGold = npc.mNpdt12.mGold; + } + else + npc.mNpdtType = ESM::NPC::NPC_DEFAULT; + } } else { @@ -643,7 +873,9 @@ void CSMWorld::NpcRefIdAdapter::setData (const RefIdColumn *column, RefIdData& d record.setModified (npc); } -CSMWorld::NpcAttributesRefIdAdapter::NpcAttributesRefIdAdapter () +CSMWorld::NpcAttributesRefIdAdapter::NpcAttributesRefIdAdapter(const CSMWorld::IdCollection& races, + const CSMWorld::IdCollection& classes, const CSMWorld::IdCollection& skills) + : mRaceTable(races), mClassTable(classes), mSkillTable(skills) {} void CSMWorld::NpcAttributesRefIdAdapter::addNestedRow (const RefIdColumn *column, @@ -691,7 +923,8 @@ QVariant CSMWorld::NpcAttributesRefIdAdapter::getNestedData (const RefIdColumn * const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc))); - const ESM::NPC::NPDTstruct52& npcStruct = record.get().mNpdt52; + const ESM::NPC npc = record.get(); + const ESM::NPC::NPDTstruct52& npcStruct = npc.mNpdt52; if (subColIndex == 0) switch (subRowIndex) @@ -707,18 +940,43 @@ QVariant CSMWorld::NpcAttributesRefIdAdapter::getNestedData (const RefIdColumn * default: return QVariant(); // throw an exception here? } else if (subColIndex == 1) - switch (subRowIndex) + // It may be possible to have mNpdt52 values different to autocalculated ones when + // first loaded, so re-calculate + if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) { - case 0: return static_cast(npcStruct.mStrength); - case 1: return static_cast(npcStruct.mIntelligence); - case 2: return static_cast(npcStruct.mWillpower); - case 3: return static_cast(npcStruct.mAgility); - case 4: return static_cast(npcStruct.mSpeed); - case 5: return static_cast(npcStruct.mEndurance); - case 6: return static_cast(npcStruct.mPersonality); - case 7: return static_cast(npcStruct.mLuck); - default: return QVariant(); // throw an exception here? + const ESM::Race& race = mRaceTable.getRecord(npc.mRace).get(); + const ESM::Class& class_ = mClassTable.getRecord(npc.mClass).get(); + std::vector attr = autoCalculateAttributes(npc, race, class_, mSkillTable); + + if (attr.empty()) + return QVariant(); + + switch (subRowIndex) + { + case 0: return static_cast(attr[ESM::Attribute::Strength]); + case 1: return static_cast(attr[ESM::Attribute::Intelligence]); + case 2: return static_cast(attr[ESM::Attribute::Willpower]); + case 3: return static_cast(attr[ESM::Attribute::Agility]); + case 4: return static_cast(attr[ESM::Attribute::Speed]); + case 5: return static_cast(attr[ESM::Attribute::Endurance]); + case 6: return static_cast(attr[ESM::Attribute::Personality]); + case 7: return static_cast(attr[ESM::Attribute::Luck]); + default: return QVariant(); // throw an exception here? + } } + else + switch (subRowIndex) + { + case 0: return static_cast(npcStruct.mStrength); + case 1: return static_cast(npcStruct.mIntelligence); + case 2: return static_cast(npcStruct.mWillpower); + case 3: return static_cast(npcStruct.mAgility); + case 4: return static_cast(npcStruct.mSpeed); + case 5: return static_cast(npcStruct.mEndurance); + case 6: return static_cast(npcStruct.mPersonality); + case 7: return static_cast(npcStruct.mLuck); + default: return QVariant(); // throw an exception here? + } else return QVariant(); // throw an exception here? } @@ -761,7 +1019,10 @@ int CSMWorld::NpcAttributesRefIdAdapter::getNestedRowsCount(const RefIdColumn *c return 8; } -CSMWorld::NpcSkillsRefIdAdapter::NpcSkillsRefIdAdapter () +CSMWorld::NpcSkillsRefIdAdapter::NpcSkillsRefIdAdapter(const CSMWorld::IdCollection& races, + const CSMWorld::IdCollection& classes, + const CSMWorld::IdCollection& skills) + : mRaceTable(races), mClassTable(classes), mSkillTable(skills) {} void CSMWorld::NpcSkillsRefIdAdapter::addNestedRow (const RefIdColumn *column, @@ -809,7 +1070,7 @@ QVariant CSMWorld::NpcSkillsRefIdAdapter::getNestedData (const RefIdColumn *colu const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc))); - const ESM::NPC::NPDTstruct52& npcStruct = record.get().mNpdt52; + const ESM::NPC npc = record.get(); if (subRowIndex < 0 || subRowIndex >= ESM::Skill::Length) throw std::runtime_error ("index out of range"); @@ -817,7 +1078,26 @@ QVariant CSMWorld::NpcSkillsRefIdAdapter::getNestedData (const RefIdColumn *colu if (subColIndex == 0) return QString(ESM::Skill::sSkillNames[subRowIndex].c_str()); else if (subColIndex == 1) - return static_cast(npcStruct.mSkills[subRowIndex]); + { + // It may be possible to have mNpdt52 values different to autocalculated ones when + // first loaded, so re-calculate + if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) + { + // if the race/class does not exist, throws std::runtime_error ("invalid ID: " + id) + const ESM::Race& race = mRaceTable.getRecord(npc.mRace).get(); + const ESM::Class& class_ = mClassTable.getRecord(npc.mClass).get(); + std::vector skills = autoCalculateSkills(npc, race, class_, mSkillTable); + + int value = static_cast(skills[subRowIndex]); + + return static_cast(skills[subRowIndex]); + } + else + { + const ESM::NPC::NPDTstruct52& npcStruct = npc.mNpdt52; + return static_cast(npcStruct.mSkills[subRowIndex]); + } + } else return QVariant(); // throw an exception here? } @@ -852,7 +1132,10 @@ int CSMWorld::NpcSkillsRefIdAdapter::getNestedRowsCount(const RefIdColumn *colum return ESM::Skill::Length; } -CSMWorld::NpcMiscRefIdAdapter::NpcMiscRefIdAdapter () +CSMWorld::NpcMiscRefIdAdapter::NpcMiscRefIdAdapter(const CSMWorld::IdCollection& races, + const CSMWorld::IdCollection& classes, + const CSMWorld::IdCollection& skills) + : mRaceTable(races), mClassTable(classes), mSkillTable(skills) {} CSMWorld::NpcMiscRefIdAdapter::~NpcMiscRefIdAdapter() @@ -888,16 +1171,45 @@ QVariant CSMWorld::NpcMiscRefIdAdapter::getNestedData (const RefIdColumn *column const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc))); - bool autoCalc = (record.get().mFlags & ESM::NPC::Autocalc) != 0; + const ESM::NPC npc = record.get(); + bool autoCalc = (npc.mFlags & ESM::NPC::Autocalc) != 0; + + // It may be possible to have mNpdt52 values different to autocalculated ones when + // first loaded, so re-calculate if (autoCalc) + { + // if the race/class does not exist, throws std::runtime_error ("invalid ID: " + id) + const ESM::Race& race = mRaceTable.getRecord(npc.mRace).get(); + const ESM::Class& class_ = mClassTable.getRecord(npc.mClass).get(); + std::vector attr = autoCalculateAttributes(npc, race, class_, mSkillTable); + + if (attr.empty()) + return QVariant(); + switch (subColIndex) { - case 0: return static_cast(record.get().mNpdt12.mLevel); - case 1: return QVariant(QVariant::UserType); - case 2: return QVariant(QVariant::UserType); - case 3: return QVariant(QVariant::UserType); - case 4: return QVariant(QVariant::UserType); + case 0: return static_cast(npc.mNpdt12.mLevel); + case 1: + { + UserInt i(0); // unknown + return QVariant(QVariant::fromValue(i)); + } + case 2: + { + UserInt i(autoCalculateHealth(npc, class_, attr)); + return QVariant(QVariant::fromValue(i)); + } + case 3: + { + UserInt i(autoCalculateMana(attr)); + return QVariant(QVariant::fromValue(i)); + } + case 4: + { + UserInt i(autoCalculateFatigue(attr)); + return QVariant(QVariant::fromValue(i)); + } case 5: return static_cast(record.get().mNpdt12.mDisposition); case 6: return static_cast(record.get().mNpdt12.mReputation); case 7: return static_cast(record.get().mNpdt12.mRank); @@ -905,6 +1217,7 @@ QVariant CSMWorld::NpcMiscRefIdAdapter::getNestedData (const RefIdColumn *column case 9: return record.get().mPersistent == true; default: return QVariant(); // throw an exception here? } + } else switch (subColIndex) { @@ -934,30 +1247,107 @@ void CSMWorld::NpcMiscRefIdAdapter::setNestedData (const RefIdColumn *column, if (autoCalc) switch(subColIndex) { - case 0: npc.mNpdt12.mLevel = static_cast(value.toInt()); break; + case 0: + { + npc.mNpdt12.mLevel = static_cast(value.toInt()); break; + + const ESM::Race& race = mRaceTable.getRecord(npc.mRace).get(); + const ESM::Class& class_ = mClassTable.getRecord(npc.mClass).get(); + std::vector attr = autoCalculateAttributes(npc, race, class_, mSkillTable); + if (attr.empty()) + return; + + ESM::NPC::NPDTstruct52& npcStruct = npc.mNpdt52; + + std::vector skills = autoCalculateSkills(npc, race, class_, mSkillTable); + + npcStruct.mLevel = npc.mNpdt12.mLevel; + npcStruct.mStrength = attr[ESM::Attribute::Strength]; + npcStruct.mIntelligence = attr[ESM::Attribute::Intelligence]; + npcStruct.mWillpower = attr[ESM::Attribute::Willpower]; + npcStruct.mAgility = attr[ESM::Attribute::Agility]; + npcStruct.mSpeed = attr[ESM::Attribute::Speed]; + npcStruct.mEndurance = attr[ESM::Attribute::Endurance]; + npcStruct.mPersonality = attr[ESM::Attribute::Personality]; + npcStruct.mLuck = attr[ESM::Attribute::Luck]; + for (int i = 0; i < ESM::Skill::Length; ++i) + { + npcStruct.mSkills[i] = skills[i]; + } + npcStruct.mHealth = autoCalculateHealth(npc, class_, attr); + npcStruct.mMana = autoCalculateMana(attr); + npcStruct.mFatigue = autoCalculateFatigue(attr); + + break; + } case 1: return; case 2: return; case 3: return; case 4: return; - case 5: npc.mNpdt12.mDisposition = static_cast(value.toInt()); break; - case 6: npc.mNpdt12.mReputation = static_cast(value.toInt()); break; - case 7: npc.mNpdt12.mRank = static_cast(value.toInt()); break; - case 8: npc.mNpdt12.mGold = value.toInt(); break; + case 5: + { + npc.mNpdt12.mDisposition = static_cast(value.toInt()); + npc.mNpdt52.mDisposition = npc.mNpdt12.mDisposition; + break; + } + case 6: + { + npc.mNpdt12.mReputation = static_cast(value.toInt()); + npc.mNpdt52.mReputation = npc.mNpdt12.mReputation; + break; + } + case 7: + { + npc.mNpdt12.mRank = static_cast(value.toInt()); + npc.mNpdt52.mRank = npc.mNpdt12.mRank; + break; + } + case 8: + { + npc.mNpdt12.mGold = value.toInt(); + npc.mNpdt52.mGold = npc.mNpdt12.mGold; + break; + } case 9: npc.mPersistent = value.toBool(); break; default: return; // throw an exception here? } else switch(subColIndex) { - case 0: npc.mNpdt52.mLevel = static_cast(value.toInt()); break; + case 0: + { + npc.mNpdt52.mLevel = static_cast(value.toInt()); + npc.mNpdt12.mLevel = npc.mNpdt52.mLevel; + break; + } case 1: npc.mNpdt52.mFactionID = static_cast(value.toInt()); break; case 2: npc.mNpdt52.mHealth = static_cast(value.toInt()); break; case 3: npc.mNpdt52.mMana = static_cast(value.toInt()); break; case 4: npc.mNpdt52.mFatigue = static_cast(value.toInt()); break; - case 5: npc.mNpdt52.mDisposition = static_cast(value.toInt()); break; - case 6: npc.mNpdt52.mReputation = static_cast(value.toInt()); break; - case 7: npc.mNpdt52.mRank = static_cast(value.toInt()); break; - case 8: npc.mNpdt52.mGold = value.toInt(); break; + case 5: + { + npc.mNpdt52.mDisposition = static_cast(value.toInt()); + npc.mNpdt12.mDisposition = npc.mNpdt52.mDisposition; + break; + } + case 6: + { + npc.mNpdt52.mReputation = static_cast(value.toInt()); + npc.mNpdt12.mReputation = npc.mNpdt52.mReputation; + break; + } + case 7: + { + npc.mNpdt52.mRank = static_cast(value.toInt()); + npc.mNpdt12.mRank = npc.mNpdt52.mRank; + break; + } + case 8: + { + npc.mNpdt52.mGold = value.toInt(); + npc.mNpdt12.mGold = npc.mNpdt52.mGold; + break; + } case 9: npc.mPersistent = value.toBool(); break; default: return; // throw an exception here? } diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index 869996da5..b12f84d5e 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -11,12 +11,16 @@ #include #include #include +#include +#include +#include #include "record.hpp" #include "refiddata.hpp" #include "universalid.hpp" #include "refidadapter.hpp" #include "nestedtablewrapper.hpp" +#include "idcollection.hpp" namespace CSMWorld { @@ -802,10 +806,16 @@ namespace CSMWorld class NpcRefIdAdapter : public ActorRefIdAdapter { NpcColumns mColumns; + const IdCollection& mRaceTable; + const IdCollection& mClassTable; + const IdCollection& mSkillTable; public: - NpcRefIdAdapter (const NpcColumns& columns); + NpcRefIdAdapter (const NpcColumns& columns, + const IdCollection& races, + const IdCollection& classes, + const IdCollection& skills); virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) const; @@ -850,9 +860,15 @@ namespace CSMWorld class NpcAttributesRefIdAdapter : public NestedRefIdAdapterBase { + const IdCollection& mRaceTable; + const IdCollection& mClassTable; + const IdCollection& mSkillTable; + public: - NpcAttributesRefIdAdapter (); + NpcAttributesRefIdAdapter (const IdCollection& races, + const IdCollection& classes, + const IdCollection& skills); virtual void addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const; @@ -879,9 +895,15 @@ namespace CSMWorld class NpcSkillsRefIdAdapter : public NestedRefIdAdapterBase { + const IdCollection& mRaceTable; + const IdCollection& mClassTable; + const IdCollection& mSkillTable; + public: - NpcSkillsRefIdAdapter (); + NpcSkillsRefIdAdapter (const IdCollection& races, + const IdCollection& classes, + const IdCollection& skills); virtual void addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const; @@ -908,12 +930,18 @@ namespace CSMWorld class NpcMiscRefIdAdapter : public NestedRefIdAdapterBase { + const IdCollection& mRaceTable; + const IdCollection& mClassTable; + const IdCollection& mSkillTable; + NpcMiscRefIdAdapter (const NpcMiscRefIdAdapter&); NpcMiscRefIdAdapter& operator= (const NpcMiscRefIdAdapter&); public: - NpcMiscRefIdAdapter (); + NpcMiscRefIdAdapter (const IdCollection& races, + const IdCollection& classes, + const IdCollection& skills); virtual ~NpcMiscRefIdAdapter(); virtual void addNestedRow (const RefIdColumn *column, diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 5495926b4..739e9cec4 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -10,6 +10,7 @@ #include "columns.hpp" #include "nestedtablewrapper.hpp" #include "nestedcoladapterimp.hpp" +#include "data.hpp" CSMWorld::RefIdColumn::RefIdColumn (int columnId, Display displayType, int flag, bool editable, bool userEditable) @@ -36,7 +37,7 @@ const CSMWorld::RefIdAdapter& CSMWorld::RefIdCollection::findAdapter (UniversalI return *iter->second; } -CSMWorld::RefIdCollection::RefIdCollection() +CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data) { BaseColumns baseColumns; @@ -437,7 +438,7 @@ CSMWorld::RefIdCollection::RefIdCollection() ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue)); npcColumns.mAttributes = &mColumns.back(); std::map attrMap; - attrMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcAttributesRefIdAdapter())); + attrMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcAttributesRefIdAdapter(data.getRaces(), data.getClasses(), data.getSkills()))); mNestedAdapters.push_back (std::make_pair(&mColumns.back(), attrMap)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_NpcAttributes, CSMWorld::ColumnBase::Display_String, false, false)); @@ -449,7 +450,7 @@ CSMWorld::RefIdCollection::RefIdCollection() ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue)); npcColumns.mSkills = &mColumns.back(); std::map skillsMap; - skillsMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcSkillsRefIdAdapter())); + skillsMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcSkillsRefIdAdapter(data.getRaces(), data.getClasses(), data.getSkills()))); mNestedAdapters.push_back (std::make_pair(&mColumns.back(), skillsMap)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_NpcSkills, CSMWorld::ColumnBase::Display_String, false, false)); @@ -461,10 +462,11 @@ CSMWorld::RefIdCollection::RefIdCollection() ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_List)); npcColumns.mMisc = &mColumns.back(); std::map miscMap; - miscMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcMiscRefIdAdapter())); + miscMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcMiscRefIdAdapter(data.getRaces(), data.getClasses(), data.getSkills()))); mNestedAdapters.push_back (std::make_pair(&mColumns.back(), miscMap)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_NpcLevel, CSMWorld::ColumnBase::Display_Integer)); + new RefIdColumn (Columns::ColumnId_NpcLevel, CSMWorld::ColumnBase::Display_Integer, + ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_NpcFactionID, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( @@ -609,7 +611,7 @@ CSMWorld::RefIdCollection::RefIdCollection() mAdapters.insert (std::make_pair (UniversalId::Type_Miscellaneous, new MiscRefIdAdapter (inventoryColumns, key))); mAdapters.insert (std::make_pair (UniversalId::Type_Npc, - new NpcRefIdAdapter (npcColumns))); + new NpcRefIdAdapter (npcColumns, data.getRaces(), data.getClasses(), data.getSkills()))); mAdapters.insert (std::make_pair (UniversalId::Type_Probe, new ToolRefIdAdapter (UniversalId::Type_Probe, toolsColumns))); mAdapters.insert (std::make_pair (UniversalId::Type_Repair, diff --git a/apps/opencs/model/world/refidcollection.hpp b/apps/opencs/model/world/refidcollection.hpp index 4d511d12d..e8e633663 100644 --- a/apps/opencs/model/world/refidcollection.hpp +++ b/apps/opencs/model/world/refidcollection.hpp @@ -20,6 +20,7 @@ namespace CSMWorld class RefIdAdapter; struct NestedTableWrapperBase; class NestedRefIdAdapterBase; + class Data; class RefIdColumn : public NestableColumn { @@ -56,7 +57,8 @@ namespace CSMWorld public: - RefIdCollection(); + // race, classes and skills required for NPC autocalc + RefIdCollection(const Data& data); virtual ~RefIdCollection(); diff --git a/apps/opencs/model/world/usertype.hpp b/apps/opencs/model/world/usertype.hpp new file mode 100644 index 000000000..e0b3c2e2f --- /dev/null +++ b/apps/opencs/model/world/usertype.hpp @@ -0,0 +1,44 @@ +#ifndef CSM_WORLD_USERTYPE_H +#define CSM_WORLD_USERTYPE_H + +#include +#include + +namespace CSMWorld +{ + // Idea from ksimons @stackoverflow + class UserInt + { + public: + + UserInt() : mValue(0) { } + UserInt(int value) : mValue(value) { } + UserInt(const UserInt &other) { mValue = other.mValue; } + ~UserInt() { } + int value() const { return mValue; } + + private: + + int mValue; + }; + + class UserFloat + { + public: + + UserFloat() : mValue(0) { } + UserFloat(float value) : mValue(value) { } + UserFloat(const UserFloat &other) { mValue = other.mValue; } + ~UserFloat() { } + float value() const { return mValue; } + + private: + + float mValue; + }; +} + +Q_DECLARE_METATYPE(CSMWorld::UserInt); +Q_DECLARE_METATYPE(CSMWorld::UserFloat); + +#endif // CSM_WORLD_USERTYPE_H diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index 66e8fcb7a..3c35b095d 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -468,17 +468,19 @@ void CSVWorld::EditWidget::remake(int row) static_cast (mTable->data (mTable->index (row, typeColumn)).toInt()), mTable->data (mTable->index (row, idColumn)).toString().toUtf8().constData()); - NestedTable* table = new NestedTable(mDocument, id, mNestedModels.back(), this); - // FIXME: does not work well when enum delegates are used - //table->resizeColumnsToContents(); - - if(mTable->index(row, i).data().type() == QVariant::UserType) + bool editable = mTable->index(row, i).data().type() != QVariant::UserType; + NestedTable* table = new NestedTable(mDocument, id, mNestedModels.back(), this, editable); + if (!editable) { table->setEditTriggers(QAbstractItemView::NoEditTriggers); - table->setEnabled(false); + table->setSelectionMode(QAbstractItemView::NoSelection); + table->setStyleSheet("QTableView { color: gray; }"); + table->horizontalHeader()->setStyleSheet("QHeaderView { color: gray; }"); } else table->setEditTriggers(QAbstractItemView::SelectedClicked | QAbstractItemView::CurrentChanged); + // FIXME: does not work well when enum delegates are used + //table->resizeColumnsToContents(); int rows = mTable->rowCount(mTable->index(row, i)); int rowHeight = (rows == 0) ? table->horizontalHeader()->height() : table->rowHeight(0); diff --git a/apps/opencs/view/world/nestedtable.cpp b/apps/opencs/view/world/nestedtable.cpp index 112873cb9..7f5658b88 100644 --- a/apps/opencs/view/world/nestedtable.cpp +++ b/apps/opencs/view/world/nestedtable.cpp @@ -13,13 +13,14 @@ CSVWorld::NestedTable::NestedTable(CSMDoc::Document& document, CSMWorld::UniversalId id, CSMWorld::NestedTableProxyModel* model, - QWidget* parent) + QWidget* parent, + bool editable) : QTableView(parent), + mAddNewRowAction(0), + mRemoveRowAction(0), mUndoStack(document.getUndoStack()), mModel(model) { - mDispatcher = new CSMWorld::CommandDispatcher (document, id, this); - setSelectionBehavior (QAbstractItemView::SelectRows); setSelectionMode (QAbstractItemView::ExtendedSelection); @@ -32,32 +33,36 @@ CSVWorld::NestedTable::NestedTable(CSMDoc::Document& document, int columns = model->columnCount(QModelIndex()); - for(int i = 0 ; i < columns; ++i) - { - CSMWorld::ColumnBase::Display display = static_cast ( - model->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); - - CommandDelegate *delegate = CommandDelegateFactoryCollection::get().makeDelegate(display, - mDispatcher, - document, - this); - - setItemDelegateForColumn(i, delegate); - } - setModel(model); setAcceptDrops(true); - mAddNewRowAction = new QAction (tr ("Add new row"), this); + if (editable) + { + mDispatcher = new CSMWorld::CommandDispatcher (document, id, this); + for(int i = 0 ; i < columns; ++i) + { + CSMWorld::ColumnBase::Display display = static_cast ( + model->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); - connect(mAddNewRowAction, SIGNAL(triggered()), - this, SLOT(addNewRowActionTriggered())); + CommandDelegate *delegate = CommandDelegateFactoryCollection::get().makeDelegate(display, + mDispatcher, + document, + this); - mRemoveRowAction = new QAction (tr ("Remove row"), this); + setItemDelegateForColumn(i, delegate); + } - connect(mRemoveRowAction, SIGNAL(triggered()), - this, SLOT(removeRowActionTriggered())); + mAddNewRowAction = new QAction (tr ("Add new row"), this); + + connect(mAddNewRowAction, SIGNAL(triggered()), + this, SLOT(addNewRowActionTriggered())); + + mRemoveRowAction = new QAction (tr ("Remove row"), this); + + connect(mRemoveRowAction, SIGNAL(triggered()), + this, SLOT(removeRowActionTriggered())); + } } void CSVWorld::NestedTable::dragEnterEvent(QDragEnterEvent *event) @@ -70,6 +75,9 @@ void CSVWorld::NestedTable::dragMoveEvent(QDragMoveEvent *event) void CSVWorld::NestedTable::contextMenuEvent (QContextMenuEvent *event) { + if (!mRemoveRowAction || !mAddNewRowAction) + return; + QModelIndexList selectedRows = selectionModel()->selectedRows(); QMenu menu(this); diff --git a/apps/opencs/view/world/nestedtable.hpp b/apps/opencs/view/world/nestedtable.hpp index 5db977942..30af6b211 100644 --- a/apps/opencs/view/world/nestedtable.hpp +++ b/apps/opencs/view/world/nestedtable.hpp @@ -36,7 +36,8 @@ namespace CSVWorld NestedTable(CSMDoc::Document& document, CSMWorld::UniversalId id, CSMWorld::NestedTableProxyModel* model, - QWidget* parent = NULL); + QWidget* parent = NULL, + bool editable = true); protected: void dragEnterEvent(QDragEnterEvent *event); diff --git a/apps/opencs/view/world/util.cpp b/apps/opencs/view/world/util.cpp index e1d165a24..0ecd7779f 100644 --- a/apps/opencs/view/world/util.cpp +++ b/apps/opencs/view/world/util.cpp @@ -17,6 +17,7 @@ #include "../../model/world/commands.hpp" #include "../../model/world/tablemimedata.hpp" #include "../../model/world/commanddispatcher.hpp" +#include "../../model/world/usertype.hpp" #include "dialoguespinbox.hpp" #include "scriptedit.hpp" @@ -153,7 +154,7 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO const QModelIndex& index) const { CSMWorld::ColumnBase::Display display = getDisplayTypeFromIndex(index); - + // This createEditor() method is called implicitly from tables. // For boolean values in tables use the default editor (combobox). // Checkboxes is looking ugly in the table view. @@ -295,8 +296,15 @@ void CSVWorld::CommandDelegate::setEditorData (QWidget *editor, const QModelInde if (!n.isEmpty()) { if (!v.isValid()) - v = QVariant(editor->property(n).userType(), (const void *)0); - editor->setProperty(n, v); + editor->setProperty(n, QVariant(editor->property(n).userType(), (const void *)0)); + else if (v.type() == QVariant::UserType + && QString(v.typeName()) == "CSMWorld::UserFloat" && v.canConvert()) + editor->setProperty(n, QVariant(v.value().value())); + else if (v.type() == QVariant::UserType + && QString(v.typeName()) == "CSMWorld::UserInt" && v.canConvert()) + editor->setProperty(n, QVariant(v.value().value())); + else + editor->setProperty(n, v); } } From 6b00d4ad9155d67ddf3c221b9d1b810e6ff30a86 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Wed, 24 Jun 2015 21:05:59 +1000 Subject: [PATCH 02/16] Move NPC autocalc to a separate component so that it can be shared between OpenMW and OpenCS. - Vanilla behaviour mimic'd where possible, except copying over autocalc spells to the npc's spell list when the status changes --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/model/world/columns.cpp | 4 + apps/opencs/model/world/columns.hpp | 4 + apps/opencs/model/world/data.cpp | 504 +++++++++++++- apps/opencs/model/world/data.hpp | 31 +- apps/opencs/model/world/idtree.cpp | 5 + apps/opencs/model/world/idtree.hpp | 12 +- apps/opencs/model/world/refidadapterimp.cpp | 638 ++++++++---------- apps/opencs/model/world/refidadapterimp.hpp | 119 +--- apps/opencs/model/world/refidcollection.cpp | 21 +- apps/opencs/view/world/dialoguesubview.cpp | 32 + apps/opencs/view/world/dialoguesubview.hpp | 2 + apps/openmw/CMakeLists.txt | 4 +- apps/openmw/mwclass/npc.cpp | 199 +----- apps/openmw/mwgui/levelupdialog.cpp | 4 +- .../mwmechanics/mechanicsmanagerimp.cpp | 10 +- apps/openmw/mwworld/esmstore.cpp | 4 +- apps/openmw/mwworld/esmstore.hpp | 6 +- apps/openmw/mwworld/store.hpp | 96 +-- components/CMakeLists.txt | 24 +- components/gameplay/autocalc.cpp | 207 ++++++ components/gameplay/autocalc.hpp | 47 ++ components/gameplay/autocalcspell.cpp | 240 +++++++ components/gameplay/autocalcspell.hpp | 38 ++ components/gameplay/store.hpp | 138 ++++ 25 files changed, 1631 insertions(+), 760 deletions(-) create mode 100644 components/gameplay/autocalc.cpp create mode 100644 components/gameplay/autocalc.hpp create mode 100644 components/gameplay/autocalcspell.cpp create mode 100644 components/gameplay/autocalcspell.hpp create mode 100644 components/gameplay/store.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index d49541590..4e7c4123a 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -26,7 +26,7 @@ opencs_units_noqt (model/world universalid record commands columnbase scriptcontext cell refidcollection refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection tablemimedata cellcoordinates cellselection resources resourcesmanager scope pathgrid landtexture land nestedtablewrapper nestedcollection nestedcoladapterimp nestedinfocollection - idcompletionmanager + idcompletionmanager npcstats ) opencs_hdrs_noqt (model/world diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index 9491c3246..f3f78d2c6 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -311,6 +311,10 @@ namespace CSMWorld { ColumnId_WaterLevel, "Water Level" }, { ColumnId_MapColor, "Map Color" }, + { ColumnId_SpellSrc, "From Race" }, + { ColumnId_SpellCost, "Cast Cost" }, + { ColumnId_SpellChance, "Cast Chance" }, + { ColumnId_UseValue1, "Use value 1" }, { ColumnId_UseValue2, "Use value 2" }, { ColumnId_UseValue3, "Use value 3" }, diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index 191bbdea8..456de27ec 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -302,6 +302,10 @@ namespace CSMWorld ColumnId_WaterLevel = 273, ColumnId_MapColor = 274, + ColumnId_SpellSrc = 275, + ColumnId_SpellCost = 276, + ColumnId_SpellChance = 277, + // Allocated to a separate value range, so we don't get a collision should we ever need // to extend the number of use values. ColumnId_UseValue1 = 0x10000, diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index ffa1ff3c3..4e393fbc3 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -11,6 +11,10 @@ #include #include +#include +#include +#include + #include "idtable.hpp" #include "idtree.hpp" #include "columnimp.hpp" @@ -19,6 +23,117 @@ #include "resourcesmanager.hpp" #include "resourcetable.hpp" #include "nestedcoladapterimp.hpp" +#include "npcstats.hpp" + +namespace +{ + class SpellStore : public GamePlay::CommonStore + { + const CSMWorld::NestedIdCollection& mSpells; + std::vector mLocal; + + public: + + SpellStore(const CSMWorld::NestedIdCollection& spells) + : mSpells(spells) + { + // prepare data in a format used by OpenMW store + for (int index = 0; index < mSpells.getSize(); ++index) + { + ESM::Spell *spell = const_cast(&mSpells.getRecord(index).get()); + mLocal.push_back(spell); + } + } + + ~SpellStore() {} + + typedef GamePlay::SharedIterator iterator; + + virtual iterator begin() const + { + return mLocal.begin(); + } + + virtual iterator end() const + { + return mLocal.end(); + } + + virtual const ESM::Spell *find(const std::string &id) const + { + return &mSpells.getRecord(id).get(); + } + + virtual size_t getSize() const + { + return mSpells.getSize(); + } + + private: + // not used in OpenCS + virtual void load(ESM::ESMReader &esm, const std::string &id) + { + } + }; + + class CSStore : public GamePlay::StoreWrap + { + const CSMWorld::IdCollection& mGmstTable; + const CSMWorld::IdCollection& mSkillTable; + const CSMWorld::IdCollection& mMagicEffectTable; + const SpellStore mSpellStore; + + public: + + CSStore(const CSMWorld::IdCollection& gmst, + const CSMWorld::IdCollection& skills, + const CSMWorld::IdCollection& magicEffects, + const CSMWorld::NestedIdCollection& spells) + : mGmstTable(gmst), mSkillTable(skills), mMagicEffectTable(magicEffects), mSpellStore(spells) + { } + ~CSStore() {} + + virtual int findGmstInt(const std::string& name) const + { + return mGmstTable.getRecord(name).get().getInt(); + } + + virtual float findGmstFloat(const std::string& name) const + { + return mGmstTable.getRecord(name).get().getFloat(); + } + + virtual const ESM::Skill *findSkill(int index) const + { + // if the skill does not exist, throws std::runtime_error ("invalid ID: " + id) + return &mSkillTable.getRecord(ESM::Skill::indexToId(index)).get(); + } + + virtual const ESM::MagicEffect* findMagicEffect(int id) const + { + // if the magic effect does not exist, throws std::runtime_error ("invalid ID: " + id) + return &mMagicEffectTable.getRecord(ESM::MagicEffect::indexToId((short)id)).get(); + } + + virtual const GamePlay::CommonStore& getSpells() const + { + return mSpellStore; + } + }; + + unsigned short autoCalculateMana(GamePlay::StatsBase& stats) + { + return stats.getBaseAttribute(ESM::Attribute::Intelligence) * 2; + } + + unsigned short autoCalculateFatigue(GamePlay::StatsBase& stats) + { + return stats.getBaseAttribute(ESM::Attribute::Strength) + + stats.getBaseAttribute(ESM::Attribute::Willpower) + + stats.getBaseAttribute(ESM::Attribute::Agility) + + stats.getBaseAttribute(ESM::Attribute::Endurance); + } +} void CSMWorld::Data::addModel (QAbstractItemModel *model, UniversalId::Type type, bool update) { @@ -203,7 +318,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mSpells.addColumn (new RecordStateColumn); mSpells.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Spell)); mSpells.addColumn (new NameColumn); - mSpells.addColumn (new SpellTypeColumn); + mSpells.addColumn (new SpellTypeColumn); // ColumnId_SpellType mSpells.addColumn (new CostColumn); mSpells.addColumn (new FlagColumn (Columns::ColumnId_AutoCalc, 0x1)); mSpells.addColumn (new FlagColumn (Columns::ColumnId_StarterSpell, 0x2)); @@ -517,11 +632,40 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc addModel (new ResourceTable (&mResourcesManager.get (UniversalId::Type_Videos)), UniversalId::Type_Video); + // for autocalc updates when gmst/race/class/skils tables change + CSMWorld::IdTable *gmsts = + static_cast(getTableModel(UniversalId::Type_Gmst)); + CSMWorld::IdTable *skills = + static_cast(getTableModel(UniversalId::Type_Skill)); + CSMWorld::IdTable *classes = + static_cast(getTableModel(UniversalId::Type_Class)); + CSMWorld::IdTree *races = + static_cast(getTableModel(UniversalId::Type_Race)); + CSMWorld::IdTree *objects = + static_cast(getTableModel(UniversalId::Type_Referenceable)); + + connect (gmsts, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (gmstDataChanged (const QModelIndex&, const QModelIndex&))); + connect (skills, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (skillDataChanged (const QModelIndex&, const QModelIndex&))); + connect (classes, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (classDataChanged (const QModelIndex&, const QModelIndex&))); + connect (races, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (raceDataChanged (const QModelIndex&, const QModelIndex&))); + connect (objects, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (npcDataChanged (const QModelIndex&, const QModelIndex&))); + connect (this, SIGNAL (updateNpcAutocalc (int, const std::string&)), + objects, SLOT (updateNpcAutocalc (int, const std::string&))); + connect (this, SIGNAL (cacheNpcStats (const std::string&, NpcStats*)), + this, SLOT (cacheNpcStatsEvent (const std::string&, NpcStats*))); + mRefLoadCache.clear(); // clear here rather than startLoading() and continueLoading() for multiple content files } CSMWorld::Data::~Data() { + clearNpcStatsCache(); + for (std::vector::iterator iter (mModels.begin()); iter!=mModels.end(); ++iter) delete *iter; @@ -1165,3 +1309,361 @@ const CSMWorld::Data& CSMWorld::Data::self () { return *this; } + +void CSMWorld::Data::skillDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +{ + // mData.mAttribute (affects attributes skill bonus autocalc) + // mData.mSpecialization (affects skills autocalc) + CSMWorld::IdTable *skillModel = + static_cast(getTableModel(CSMWorld::UniversalId::Type_Skill)); + + int attributeColumn = skillModel->findColumnIndex(CSMWorld::Columns::ColumnId_Attribute); + int specialisationColumn = skillModel->findColumnIndex(CSMWorld::Columns::ColumnId_Specialisation); + + if ((topLeft.column() <= attributeColumn && attributeColumn <= bottomRight.column()) + || (topLeft.column() <= specialisationColumn && specialisationColumn <= bottomRight.column())) + { + clearNpcStatsCache(); + + std::string empty; + emit updateNpcAutocalc(0/*all*/, empty); + } +} + +void CSMWorld::Data::classDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +{ + // update autocalculated attributes/skills of every NPC with matching class + // - mData.mAttribute[2] + // - mData.mSkills[5][2] + // - mData.mSpecialization + CSMWorld::IdTable *classModel = + static_cast(getTableModel(CSMWorld::UniversalId::Type_Class)); + + int attribute1Column = classModel->findColumnIndex(CSMWorld::Columns::ColumnId_Attribute1); // +1 + int majorSkill1Column = classModel->findColumnIndex(CSMWorld::Columns::ColumnId_MajorSkill1); // +4 + int minorSkill1Column = classModel->findColumnIndex(CSMWorld::Columns::ColumnId_MinorSkill1); // +4 + int specialisationColumn = classModel->findColumnIndex(CSMWorld::Columns::ColumnId_Specialisation); + + if ((topLeft.column() > attribute1Column+1 || attribute1Column > bottomRight.column()) + && (topLeft.column() > majorSkill1Column+4 || majorSkill1Column > bottomRight.column()) + && (topLeft.column() > minorSkill1Column+4 || minorSkill1Column > bottomRight.column()) + && (topLeft.column() > specialisationColumn || specialisationColumn > bottomRight.column())) + { + return; + } + + // get the affected class + int idColumn = classModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id); + for (int classRow = topLeft.row(); classRow <= bottomRight.row(); ++classRow) + { + clearNpcStatsCache(); + + std::string classId = + classModel->data(classModel->index(classRow, idColumn)).toString().toUtf8().constData(); + emit updateNpcAutocalc(1/*class*/, classId); + } +} + +void CSMWorld::Data::raceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +{ + // affects racial bonus attributes & skills + // - mData.mAttributeValues[] + // - mData.mBonus[].mBonus + CSMWorld::IdTree *raceModel = + static_cast(getTableModel(CSMWorld::UniversalId::Type_Race)); + + int attrColumn = raceModel->findColumnIndex(CSMWorld::Columns::ColumnId_RaceAttributes); + int bonusColumn = raceModel->findColumnIndex(CSMWorld::Columns::ColumnId_RaceSkillBonus); + + bool match = false; + if (topLeft.parent().isValid() && bottomRight.parent().isValid()) + { + if ((topLeft.parent().column() <= attrColumn && attrColumn <= bottomRight.parent().column()) + || (topLeft.parent().column() <= bonusColumn && bonusColumn <= bottomRight.parent().column())) + { + match = true; // TODO: check for specific nested column? + } + } + else + { + if ((topLeft.column() <= attrColumn && attrColumn <= bottomRight.column()) + || (topLeft.column() <= bonusColumn && bonusColumn <= bottomRight.column())) + { + match = true; // maybe the whole table changed + } + } + + if (!match) + return; + + // update autocalculated attributes/skills of every NPC with matching race + int idColumn = raceModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id); + for (int raceRow = topLeft.parent().row(); raceRow <= bottomRight.parent().row(); ++raceRow) + { + clearNpcStatsCache(); + + std::string raceId = + raceModel->data(raceModel->index(raceRow, idColumn)).toString().toUtf8().constData(); + emit updateNpcAutocalc(2/*race*/, raceId); + } +} + +// FIXME: currently ignoring level changes +void CSMWorld::Data::npcDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +{ + clearNpcStatsCache(); + + // Either autoCalc flag changed or NPC level changed + CSMWorld::IdTree *objectModel = + static_cast(getTableModel(CSMWorld::UniversalId::Type_Referenceable)); + + int autoCalcColumn = objectModel->findColumnIndex(CSMWorld::Columns::ColumnId_AutoCalc); + int miscColumn = objectModel->findColumnIndex(CSMWorld::Columns::ColumnId_NpcMisc); + + // first check for level + bool levelChanged = false; + if (topLeft.parent().isValid() && bottomRight.parent().isValid()) + { + if (topLeft.parent().column() <= miscColumn && miscColumn <= bottomRight.parent().column()) + { + for (int col = topLeft.column(); col <= bottomRight.column(); ++col) + { + int role = objectModel->nestedHeaderData(topLeft.parent().column(), + col, Qt::Horizontal, CSMWorld::ColumnBase::Role_ColumnId).toInt(); + if (role == CSMWorld::Columns::ColumnId_NpcLevel) + { + levelChanged = true; + break; + } + } + } + } + + // next check for autocalc + bool autoCalcChanged = false; + if (!topLeft.parent().isValid() && !bottomRight.parent().isValid()) + { + if ((topLeft.column() <= autoCalcColumn && autoCalcColumn <= bottomRight.column()) + || (topLeft.column() <= miscColumn && miscColumn <= bottomRight.column())) + { + autoCalcChanged = true; + } + } + + if (!levelChanged && !autoCalcChanged) + return; + + int row = 0; + int end = 0; + if (topLeft.parent().isValid()) + row = topLeft.parent().row(); + else + row = topLeft.row(); + + if (bottomRight.parent().isValid()) + end = bottomRight.parent().row(); + else + end = bottomRight.row(); + + for (; row <= end; ++row) + { + Record record = + static_cast&>(mReferenceables.getRecord(row)); + ESM::NPC &npc = record.get(); + + // If going from autocalc to non-autocalc, save the autocalc values + if (autoCalcChanged) + { + if (npc.mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) + saveAutoCalcValues(npc); // update attributes and skills + else + npc.mNpdt12.mLevel = npc.mNpdt52.mLevel; // for NPC's loaded as non-autocalc + + record.setModified(npc); + mReferenceables.replace(row, record); + } + } +} + +void CSMWorld::Data::gmstDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +{ + static const QStringList gmsts(QStringList()<< "fNPCbaseMagickaMult" << "fAutoSpellChance" + << "fEffectCostMult" << "iAutoSpellAlterationMax" << "iAutoSpellConjurationMax" + << "iAutoSpellDestructionMax" << "iAutoSpellIllusionMax" << "iAutoSpellMysticismMax" + << "iAutoSpellRestorationMax" << "iAutoSpellTimesCanCast" << "iAutoSpellAttSkillMin"); + + bool match = false; + for (int row = topLeft.row(); row <= bottomRight.row(); ++row) + { + if (gmsts.contains(mGmsts.getRecord(row).get().mId.c_str())) + { + match = true; + break; + } + } + + if (!match) + return; + + clearNpcStatsCache(); + + std::string empty; + emit updateNpcAutocalc(0/*all*/, empty); +} + +// FIXME: how to undo? +void CSMWorld::Data::saveAutoCalcValues(ESM::NPC& npc) +{ + CSMWorld::NpcStats * cachedStats = getCachedNpcData (npc.mId); + if (!cachedStats) + return; // silently fail + + CSMWorld::NpcStats* stats = npcAutoCalculate(npc); + + // update npc + npc.mNpdt52.mLevel = npc.mNpdt12.mLevel; + + npc.mNpdt52.mStrength = stats->getBaseAttribute(ESM::Attribute::Strength); + npc.mNpdt52.mIntelligence = stats->getBaseAttribute(ESM::Attribute::Intelligence); + npc.mNpdt52.mWillpower = stats->getBaseAttribute(ESM::Attribute::Willpower); + npc.mNpdt52.mAgility = stats->getBaseAttribute(ESM::Attribute::Agility); + npc.mNpdt52.mSpeed = stats->getBaseAttribute(ESM::Attribute::Speed); + npc.mNpdt52.mEndurance = stats->getBaseAttribute(ESM::Attribute::Endurance); + npc.mNpdt52.mPersonality = stats->getBaseAttribute(ESM::Attribute::Personality); + npc.mNpdt52.mLuck = stats->getBaseAttribute(ESM::Attribute::Luck); + + for (int i = 0; i < ESM::Skill::Length; ++i) + { + npc.mNpdt52.mSkills[i] = stats->getBaseSkill(i); + } + + npc.mNpdt52.mHealth = stats->getHealth(); + npc.mNpdt52.mMana = stats->getMana(); + npc.mNpdt52.mFatigue = stats->getFatigue(); + npc.mNpdt52.mDisposition = npc.mNpdt12.mDisposition; + npc.mNpdt52.mReputation = npc.mNpdt12.mReputation; + npc.mNpdt52.mRank = npc.mNpdt12.mRank; + npc.mNpdt52.mGold = npc.mNpdt12.mGold; + + // TODO: add spells from autogenerated list like vanilla (but excluding any race powers or abilities) +} + +void CSMWorld::Data::clearNpcStatsCache () +{ + for (std::map::iterator it (mNpcStatCache.begin()); + it != mNpcStatCache.end(); ++it) + delete it->second; + + mNpcStatCache.clear(); +} + +CSMWorld::NpcStats* CSMWorld::Data::npcAutoCalculate(const ESM::NPC& npc) const +{ + CSMWorld::NpcStats * cachedStats = getCachedNpcData (npc.mId); + if (cachedStats) + return cachedStats; + + const ESM::Race *race = &mRaces.getRecord(npc.mRace).get(); + const ESM::Class *class_ = &mClasses.getRecord(npc.mClass).get(); + + bool autoCalc = npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS; + short level = npc.mNpdt52.mLevel; + if (autoCalc) + level = npc.mNpdt12.mLevel; + + CSMWorld::NpcStats *stats = new CSMWorld::NpcStats(); + + CSStore store(mGmsts, mSkills, mMagicEffects, mSpells); + + if (autoCalc) + { + GamePlay::autoCalcAttributesImpl (&npc, race, class_, level, *stats, &store); + + stats->setHealth(autoCalculateHealth(level, class_, *stats)); + stats->setMana(autoCalculateMana(*stats)); + stats->setFatigue(autoCalculateFatigue(*stats)); + + GamePlay::autoCalcSkillsImpl(&npc, race, class_, level, *stats, &store); + + GamePlay::autoCalculateSpells(race, *stats, &store); + } + else + { + for (std::vector::const_iterator it = npc.mSpells.mList.begin(); + it != npc.mSpells.mList.end(); ++it) + { + stats->addSpells(*it); + } + } + + // update spell info + const std::vector &racePowers = race->mPowers.mList; + for (unsigned int i = 0; i < racePowers.size(); ++i) + { + int type = -1; + int spellIndex = mSpells.searchId(racePowers[i]); + if (spellIndex != -1) + type = mSpells.getRecord(spellIndex).get().mData.mType; + stats->addPowers(racePowers[i], type); + } + // cost/chance + int skills[ESM::Skill::Length]; + if (autoCalc) + for (int i = 0; i< ESM::Skill::Length; ++i) + skills[i] = stats->getBaseSkill(i); + else + for (int i = 0; i< ESM::Skill::Length; ++i) + skills[i] = npc.mNpdt52.mSkills[i]; + + int attributes[ESM::Attribute::Length]; + if (autoCalc) + for (int i = 0; i< ESM::Attribute::Length; ++i) + attributes[i] = stats->getBaseAttribute(i); + else + { + attributes[ESM::Attribute::Strength] = npc.mNpdt52.mStrength; + attributes[ESM::Attribute::Willpower] = npc.mNpdt52.mWillpower; + attributes[ESM::Attribute::Agility] = npc.mNpdt52.mAgility; + attributes[ESM::Attribute::Speed] = npc.mNpdt52.mSpeed; + attributes[ESM::Attribute::Endurance] = npc.mNpdt52.mEndurance; + attributes[ESM::Attribute::Personality] = npc.mNpdt52.mPersonality; + attributes[ESM::Attribute::Luck] = npc.mNpdt52.mLuck; + } + + const std::vector& spells = stats->spells(); + for (std::vector::const_iterator it = spells.begin(); it != spells.end(); ++it) + { + int cost = -1; + int spellIndex = mSpells.searchId((*it).mName); + const ESM::Spell* spell = 0; + if (spellIndex != -1) + { + spell = &mSpells.getRecord(spellIndex).get(); + cost = spell->mData.mCost; + + int school; + float skillTerm; + GamePlay::calcWeakestSchool(spell, skills, school, skillTerm, &store); + float chance = calcAutoCastChance(spell, skills, attributes, school, &store); + + stats->addCostAndChance((*it).mName, cost, (int)ceil(chance)); // percent + } + } + + emit cacheNpcStats (npc.mId, stats); + return stats; +} + +void CSMWorld::Data::cacheNpcStatsEvent (const std::string& id, CSMWorld::NpcStats *stats) +{ + mNpcStatCache[id] = stats; +} + +CSMWorld::NpcStats* CSMWorld::Data::getCachedNpcData (const std::string& id) const +{ + std::map::const_iterator it = mNpcStatCache.find(id); + if (it != mNpcStatCache.end()) + return it->second; + else + return 0; +} diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index f83f9de22..fdc76fd73 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -60,6 +60,7 @@ namespace CSMWorld { class ResourcesManager; class Resources; + class NpcStats; class Data : public QObject { @@ -108,6 +109,8 @@ namespace CSMWorld std::vector > mReaders; + std::map mNpcStatCache; + // not implemented Data (const Data&); Data& operator= (const Data&); @@ -121,7 +124,11 @@ namespace CSMWorld static int count (RecordBase::State state, const CollectionBase& collection); - const CSMWorld::Data& self (); + const Data& self (); + + void saveAutoCalcValues(ESM::NPC& npc); + + void clearNpcStatsCache (); public: @@ -277,15 +284,37 @@ namespace CSMWorld std::string getAuthor() const; + NpcStats* npcAutoCalculate (const ESM::NPC& npc) const; + + NpcStats* getCachedNpcData (const std::string& id) const; + signals: void idListChanged(); + // refresh NPC dialogue subviews via object table model + void updateNpcAutocalc (int type, const std::string& id); + + void cacheNpcStats (const std::string& id, NpcStats *stats) const; + private slots: void dataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); void rowsChanged (const QModelIndex& parent, int start, int end); + + // for autocalc updates when gmst/race/class/skils tables change + void gmstDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + void raceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + void classDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + void skillDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + void npcDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + void cacheNpcStatsEvent (const std::string& id, NpcStats *stats); }; } diff --git a/apps/opencs/model/world/idtree.cpp b/apps/opencs/model/world/idtree.cpp index 1e81d6ac2..fba672108 100644 --- a/apps/opencs/model/world/idtree.cpp +++ b/apps/opencs/model/world/idtree.cpp @@ -261,3 +261,8 @@ CSMWorld::NestedTableWrapperBase* CSMWorld::IdTree::nestedTable(const QModelInde return mNestedCollection->nestedTable(index.row(), index.column()); } + +void CSMWorld::IdTree::updateNpcAutocalc (int type, const std::string& id) +{ + emit refreshNpcDialogue (type, id); +} diff --git a/apps/opencs/model/world/idtree.hpp b/apps/opencs/model/world/idtree.hpp index 5337ed82b..b29a3ae93 100644 --- a/apps/opencs/model/world/idtree.hpp +++ b/apps/opencs/model/world/idtree.hpp @@ -73,11 +73,17 @@ namespace CSMWorld virtual bool hasChildren (const QModelIndex& index) const; - signals: + signals: - void resetStart(const QString& id); + void resetStart(const QString& id); - void resetEnd(const QString& id); + void resetEnd(const QString& id); + + void refreshNpcDialogue (int type, const std::string& id); + + public slots: + + void updateNpcAutocalc (int type, const std::string& id); }; } diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index 86f0461af..085c77ca3 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -9,191 +9,8 @@ #include "nestedtablewrapper.hpp" #include "usertype.hpp" - -namespace -{ - -int is_even(double d) -{ - double int_part; - - modf(d / 2.0, &int_part); - return 2.0 * int_part == d; -} - -int round_ieee_754(double d) -{ - double i = floor(d); - d -= i; - - if(d < 0.5) - return static_cast(i); - if(d > 0.5) - return static_cast(i) + 1; - if(is_even(i)) - return static_cast(i); - return static_cast(i) + 1; -} - -std::vector autoCalculateAttributes (const ESM::NPC &npc, - const ESM::Race& race, const ESM::Class& class_, const CSMWorld::IdCollection& skillTable) -{ - // race bonus - bool male = (npc.mFlags & ESM::NPC::Female) == 0; - - if (npc.mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) - return std::vector(); - - short level = npc.mNpdt12.mLevel; - - int attr[ESM::Attribute::Length]; - - for (int i = 0; i < ESM::Attribute::Length; ++i) - { - const ESM::Race::MaleFemale& attribute = race.mData.mAttributeValues[i]; - attr[i] = male ? attribute.mMale : attribute.mFemale; - } - - // class bonus - for (int i = 0; i < 2; ++i) - { - int attribute = class_.mData.mAttribute[i]; - if (attribute >= 0 && attribute < ESM::Attribute::Length) - { - attr[attribute] = attr[attribute] + 10; - } - // else log an error - } - - std::vector result(ESM::Attribute::Length); - // skill bonus - for (int attribute = 0; attribute < ESM::Attribute::Length; ++attribute) - { - float modifierSum = 0; - - for (int j = 0; j < ESM::Skill::Length; ++j) - { - // if the skill does not exist, throws std::runtime_error ("invalid ID: " + id) - const ESM::Skill& skill = skillTable.getRecord(ESM::Skill::indexToId(j)).get(); - - if (skill.mData.mAttribute != attribute) - continue; - - // is this a minor or major skill? - float add = 0.2f; - for (int k = 0; k < 5; ++k) - { - if (class_.mData.mSkills[k][0] == j) - add = 0.5; - } - for (int k = 0; k < 5; ++k) - { - if (class_.mData.mSkills[k][1] == j) - add = 1.0; - } - modifierSum += add; - } - result[attribute] = std::min(round_ieee_754(attr[attribute] + (level-1) * modifierSum), 100); - } - - return result; -} - -std::vector autoCalculateSkills (const ESM::NPC &npc, - const ESM::Race& race, const ESM::Class& class_, const CSMWorld::IdCollection& skillTable) -{ - unsigned char skills[ESM::Skill::Length]; - for (int i = 0; i < ESM::Skill::Length; ++i) - skills[i] = 0; - - for (int i = 0; i < 2; ++i) - { - int bonus = (i == 0) ? 10 : 25; - - for (int i2 = 0; i2 < 5; ++i2) - { - int index = class_.mData.mSkills[i2][i]; - if (index >= 0 && index < ESM::Skill::Length) - { - skills[index] = bonus; - } - } - } - - std::vector result(ESM::Skill::Length); - for (int skillIndex = 0; skillIndex < ESM::Skill::Length; ++skillIndex) - { - float majorMultiplier = 0.1f; - float specMultiplier = 0.0f; - - int raceBonus = 0; - int specBonus = 0; - - for (int raceSkillIndex = 0; raceSkillIndex < 7; ++raceSkillIndex) - { - if (race.mData.mBonus[raceSkillIndex].mSkill == skillIndex) - { - raceBonus = race.mData.mBonus[raceSkillIndex].mBonus; - break; - } - } - - for (int k = 0; k < 5; ++k) - { - // is this a minor or major skill? - if ((class_.mData.mSkills[k][0] == skillIndex) || (class_.mData.mSkills[k][1] == skillIndex)) - { - majorMultiplier = 1.0f; - break; - } - } - - // is this skill in the same Specialization as the class? - const ESM::Skill& skill = skillTable.getRecord(ESM::Skill::indexToId(skillIndex)).get(); - if (skill.mData.mSpecialization == class_.mData.mSpecialization) - { - specMultiplier = 0.5f; - specBonus = 5; - } - - // Must gracefully handle level 0 - result[skillIndex] = std::min(round_ieee_754(skills[skillIndex] + 5 + raceBonus + specBonus + - (npc.mNpdt12.mLevel-1) * (majorMultiplier + specMultiplier)), 100); - } - - return result; -} - -unsigned short autoCalculateHealth(const ESM::NPC &npc, - const ESM::Class& class_, const std::vector& attr) -{ - int multiplier = 3; - - if (class_.mData.mSpecialization == ESM::Class::Combat) - multiplier += 2; - else if (class_.mData.mSpecialization == ESM::Class::Stealth) - multiplier += 1; - - if (class_.mData.mAttribute[0] == ESM::Attribute::Endurance - || class_.mData.mAttribute[1] == ESM::Attribute::Endurance) - multiplier += 1; - - return floor(0.5f * (attr[ESM::Attribute::Strength]+ attr[ESM::Attribute::Endurance])) - + multiplier * (npc.mNpdt12.mLevel-1); -} - -unsigned short autoCalculateMana(const std::vector& attr) -{ - return attr[ESM::Attribute::Intelligence] * 2; -} - -unsigned short autoCalculateFatigue(const std::vector& attr) -{ - return attr[ESM::Attribute::Strength] + attr[ESM::Attribute::Willpower] - + attr[ESM::Attribute::Agility] + attr[ESM::Attribute::Endurance]; -} - -} +#include "idtree.hpp" +#include "npcstats.hpp" CSMWorld::PotionColumns::PotionColumns (const InventoryColumns& columns) : InventoryColumns (columns) {} @@ -741,12 +558,8 @@ CSMWorld::NpcColumns::NpcColumns (const ActorColumns& actorColumns) mMisc(NULL) {} -CSMWorld::NpcRefIdAdapter::NpcRefIdAdapter (const NpcColumns& columns, - const CSMWorld::IdCollection& races, - const CSMWorld::IdCollection& classes, - const CSMWorld::IdCollection& skills) -: ActorRefIdAdapter (UniversalId::Type_Npc, columns), mColumns (columns), - mRaceTable(races), mClassTable(classes), mSkillTable(skills) +CSMWorld::NpcRefIdAdapter::NpcRefIdAdapter (const NpcColumns& columns) +: ActorRefIdAdapter (UniversalId::Type_Npc, columns), mColumns (columns) {} QVariant CSMWorld::NpcRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, int index) @@ -821,46 +634,8 @@ void CSMWorld::NpcRefIdAdapter::setData (const RefIdColumn *column, RefIdData& d npc.mFlags &= ~iter->second; if (iter->second == ESM::NPC::Autocalc) - { - if(value.toInt() != 0) - { - npc.mNpdtType = ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS; - - // if the race/class does not exist, throws std::runtime_error ("invalid ID: " + id) - const ESM::Race& race = mRaceTable.getRecord(npc.mRace).get(); - const ESM::Class& class_ = mClassTable.getRecord(npc.mClass).get(); - std::vector attr = autoCalculateAttributes(npc, race, class_, mSkillTable); - if (attr.empty()) - return; - - std::vector skills = autoCalculateSkills(npc, race, class_, mSkillTable); - - ESM::NPC::NPDTstruct52& npcStruct = npc.mNpdt52; - - npcStruct.mLevel = npc.mNpdt12.mLevel; - npcStruct.mStrength = attr[ESM::Attribute::Strength]; - npcStruct.mIntelligence = attr[ESM::Attribute::Intelligence]; - npcStruct.mWillpower = attr[ESM::Attribute::Willpower]; - npcStruct.mAgility = attr[ESM::Attribute::Agility]; - npcStruct.mSpeed = attr[ESM::Attribute::Speed]; - npcStruct.mEndurance = attr[ESM::Attribute::Endurance]; - npcStruct.mPersonality = attr[ESM::Attribute::Personality]; - npcStruct.mLuck = attr[ESM::Attribute::Luck]; - for (int i = 0; i < ESM::Skill::Length; ++i) - { - npcStruct.mSkills[i] = skills[i]; - } - npcStruct.mHealth = autoCalculateHealth(npc, class_, attr); - npcStruct.mMana = autoCalculateMana(attr); - npcStruct.mFatigue = autoCalculateFatigue(attr); - npcStruct.mDisposition = npc.mNpdt12.mDisposition; - npcStruct.mReputation = npc.mNpdt12.mReputation; - npcStruct.mRank = npc.mNpdt12.mRank; - npcStruct.mGold = npc.mNpdt12.mGold; - } - else - npc.mNpdtType = ESM::NPC::NPC_DEFAULT; - } + npc.mNpdtType = (value.toInt() != 0) ? ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS + : ESM::NPC::NPC_DEFAULT; } else { @@ -873,9 +648,7 @@ void CSMWorld::NpcRefIdAdapter::setData (const RefIdColumn *column, RefIdData& d record.setModified (npc); } -CSMWorld::NpcAttributesRefIdAdapter::NpcAttributesRefIdAdapter(const CSMWorld::IdCollection& races, - const CSMWorld::IdCollection& classes, const CSMWorld::IdCollection& skills) - : mRaceTable(races), mClassTable(classes), mSkillTable(skills) +CSMWorld::NpcAttributesRefIdAdapter::NpcAttributesRefIdAdapter(const CSMWorld::Data& data) : mData(data) {} void CSMWorld::NpcAttributesRefIdAdapter::addNestedRow (const RefIdColumn *column, @@ -940,27 +713,20 @@ QVariant CSMWorld::NpcAttributesRefIdAdapter::getNestedData (const RefIdColumn * default: return QVariant(); // throw an exception here? } else if (subColIndex == 1) - // It may be possible to have mNpdt52 values different to autocalculated ones when - // first loaded, so re-calculate if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) { - const ESM::Race& race = mRaceTable.getRecord(npc.mRace).get(); - const ESM::Class& class_ = mClassTable.getRecord(npc.mClass).get(); - std::vector attr = autoCalculateAttributes(npc, race, class_, mSkillTable); - - if (attr.empty()) - return QVariant(); + CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc); switch (subRowIndex) { - case 0: return static_cast(attr[ESM::Attribute::Strength]); - case 1: return static_cast(attr[ESM::Attribute::Intelligence]); - case 2: return static_cast(attr[ESM::Attribute::Willpower]); - case 3: return static_cast(attr[ESM::Attribute::Agility]); - case 4: return static_cast(attr[ESM::Attribute::Speed]); - case 5: return static_cast(attr[ESM::Attribute::Endurance]); - case 6: return static_cast(attr[ESM::Attribute::Personality]); - case 7: return static_cast(attr[ESM::Attribute::Luck]); + case 0: return static_cast(stats->getBaseAttribute(ESM::Attribute::Strength)); + case 1: return static_cast(stats->getBaseAttribute(ESM::Attribute::Intelligence)); + case 2: return static_cast(stats->getBaseAttribute(ESM::Attribute::Willpower)); + case 3: return static_cast(stats->getBaseAttribute(ESM::Attribute::Agility)); + case 4: return static_cast(stats->getBaseAttribute(ESM::Attribute::Speed)); + case 5: return static_cast(stats->getBaseAttribute(ESM::Attribute::Endurance)); + case 6: return static_cast(stats->getBaseAttribute(ESM::Attribute::Personality)); + case 7: return static_cast(stats->getBaseAttribute(ESM::Attribute::Luck)); default: return QVariant(); // throw an exception here? } } @@ -1019,10 +785,8 @@ int CSMWorld::NpcAttributesRefIdAdapter::getNestedRowsCount(const RefIdColumn *c return 8; } -CSMWorld::NpcSkillsRefIdAdapter::NpcSkillsRefIdAdapter(const CSMWorld::IdCollection& races, - const CSMWorld::IdCollection& classes, - const CSMWorld::IdCollection& skills) - : mRaceTable(races), mClassTable(classes), mSkillTable(skills) +CSMWorld::NpcSkillsRefIdAdapter::NpcSkillsRefIdAdapter(const CSMWorld::Data& data) + : mData(data) {} void CSMWorld::NpcSkillsRefIdAdapter::addNestedRow (const RefIdColumn *column, @@ -1079,18 +843,10 @@ QVariant CSMWorld::NpcSkillsRefIdAdapter::getNestedData (const RefIdColumn *colu return QString(ESM::Skill::sSkillNames[subRowIndex].c_str()); else if (subColIndex == 1) { - // It may be possible to have mNpdt52 values different to autocalculated ones when - // first loaded, so re-calculate if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) { - // if the race/class does not exist, throws std::runtime_error ("invalid ID: " + id) - const ESM::Race& race = mRaceTable.getRecord(npc.mRace).get(); - const ESM::Class& class_ = mClassTable.getRecord(npc.mClass).get(); - std::vector skills = autoCalculateSkills(npc, race, class_, mSkillTable); - - int value = static_cast(skills[subRowIndex]); - - return static_cast(skills[subRowIndex]); + CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc); + return static_cast(stats->getBaseSkill(subRowIndex)); } else { @@ -1132,10 +888,7 @@ int CSMWorld::NpcSkillsRefIdAdapter::getNestedRowsCount(const RefIdColumn *colum return ESM::Skill::Length; } -CSMWorld::NpcMiscRefIdAdapter::NpcMiscRefIdAdapter(const CSMWorld::IdCollection& races, - const CSMWorld::IdCollection& classes, - const CSMWorld::IdCollection& skills) - : mRaceTable(races), mClassTable(classes), mSkillTable(skills) +CSMWorld::NpcMiscRefIdAdapter::NpcMiscRefIdAdapter(const CSMWorld::Data& data) : mData(data) {} CSMWorld::NpcMiscRefIdAdapter::~NpcMiscRefIdAdapter() @@ -1175,17 +928,9 @@ QVariant CSMWorld::NpcMiscRefIdAdapter::getNestedData (const RefIdColumn *column bool autoCalc = (npc.mFlags & ESM::NPC::Autocalc) != 0; - // It may be possible to have mNpdt52 values different to autocalculated ones when - // first loaded, so re-calculate if (autoCalc) { - // if the race/class does not exist, throws std::runtime_error ("invalid ID: " + id) - const ESM::Race& race = mRaceTable.getRecord(npc.mRace).get(); - const ESM::Class& class_ = mClassTable.getRecord(npc.mClass).get(); - std::vector attr = autoCalculateAttributes(npc, race, class_, mSkillTable); - - if (attr.empty()) - return QVariant(); + CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc); switch (subColIndex) { @@ -1197,17 +942,17 @@ QVariant CSMWorld::NpcMiscRefIdAdapter::getNestedData (const RefIdColumn *column } case 2: { - UserInt i(autoCalculateHealth(npc, class_, attr)); + UserInt i(stats->getHealth()); return QVariant(QVariant::fromValue(i)); } case 3: { - UserInt i(autoCalculateMana(attr)); + UserInt i(stats->getMana()); return QVariant(QVariant::fromValue(i)); } case 4: { - UserInt i(autoCalculateFatigue(attr)); + UserInt i(stats->getFatigue()); return QVariant(QVariant::fromValue(i)); } case 5: return static_cast(record.get().mNpdt12.mDisposition); @@ -1247,108 +992,31 @@ void CSMWorld::NpcMiscRefIdAdapter::setNestedData (const RefIdColumn *column, if (autoCalc) switch(subColIndex) { - case 0: - { - npc.mNpdt12.mLevel = static_cast(value.toInt()); break; - - const ESM::Race& race = mRaceTable.getRecord(npc.mRace).get(); - const ESM::Class& class_ = mClassTable.getRecord(npc.mClass).get(); - std::vector attr = autoCalculateAttributes(npc, race, class_, mSkillTable); - if (attr.empty()) - return; - - ESM::NPC::NPDTstruct52& npcStruct = npc.mNpdt52; - - std::vector skills = autoCalculateSkills(npc, race, class_, mSkillTable); - - npcStruct.mLevel = npc.mNpdt12.mLevel; - npcStruct.mStrength = attr[ESM::Attribute::Strength]; - npcStruct.mIntelligence = attr[ESM::Attribute::Intelligence]; - npcStruct.mWillpower = attr[ESM::Attribute::Willpower]; - npcStruct.mAgility = attr[ESM::Attribute::Agility]; - npcStruct.mSpeed = attr[ESM::Attribute::Speed]; - npcStruct.mEndurance = attr[ESM::Attribute::Endurance]; - npcStruct.mPersonality = attr[ESM::Attribute::Personality]; - npcStruct.mLuck = attr[ESM::Attribute::Luck]; - for (int i = 0; i < ESM::Skill::Length; ++i) - { - npcStruct.mSkills[i] = skills[i]; - } - npcStruct.mHealth = autoCalculateHealth(npc, class_, attr); - npcStruct.mMana = autoCalculateMana(attr); - npcStruct.mFatigue = autoCalculateFatigue(attr); - - break; - } + case 0: npc.mNpdt12.mLevel = static_cast(value.toInt()); break; case 1: return; case 2: return; case 3: return; case 4: return; - case 5: - { - npc.mNpdt12.mDisposition = static_cast(value.toInt()); - npc.mNpdt52.mDisposition = npc.mNpdt12.mDisposition; - break; - } - case 6: - { - npc.mNpdt12.mReputation = static_cast(value.toInt()); - npc.mNpdt52.mReputation = npc.mNpdt12.mReputation; - break; - } - case 7: - { - npc.mNpdt12.mRank = static_cast(value.toInt()); - npc.mNpdt52.mRank = npc.mNpdt12.mRank; - break; - } - case 8: - { - npc.mNpdt12.mGold = value.toInt(); - npc.mNpdt52.mGold = npc.mNpdt12.mGold; - break; - } - case 9: npc.mPersistent = value.toBool(); break; + case 5: npc.mNpdt12.mDisposition = static_cast(value.toInt()); break; + case 6: npc.mNpdt12.mReputation = static_cast(value.toInt()); break; + case 7: npc.mNpdt12.mRank = static_cast(value.toInt()); break; + case 8: npc.mNpdt12.mGold = value.toInt(); break; + case 9: npc.mPersistent = value.toBool(); break; default: return; // throw an exception here? } else switch(subColIndex) { - case 0: - { - npc.mNpdt52.mLevel = static_cast(value.toInt()); - npc.mNpdt12.mLevel = npc.mNpdt52.mLevel; - break; - } - case 1: npc.mNpdt52.mFactionID = static_cast(value.toInt()); break; - case 2: npc.mNpdt52.mHealth = static_cast(value.toInt()); break; - case 3: npc.mNpdt52.mMana = static_cast(value.toInt()); break; - case 4: npc.mNpdt52.mFatigue = static_cast(value.toInt()); break; - case 5: - { - npc.mNpdt52.mDisposition = static_cast(value.toInt()); - npc.mNpdt12.mDisposition = npc.mNpdt52.mDisposition; - break; - } - case 6: - { - npc.mNpdt52.mReputation = static_cast(value.toInt()); - npc.mNpdt12.mReputation = npc.mNpdt52.mReputation; - break; - } - case 7: - { - npc.mNpdt52.mRank = static_cast(value.toInt()); - npc.mNpdt12.mRank = npc.mNpdt52.mRank; - break; - } - case 8: - { - npc.mNpdt52.mGold = value.toInt(); - npc.mNpdt12.mGold = npc.mNpdt52.mGold; - break; - } - case 9: npc.mPersistent = value.toBool(); break; + case 0: npc.mNpdt52.mLevel = static_cast(value.toInt()); break; + case 1: npc.mNpdt52.mFactionID = static_cast(value.toInt()); break; + case 2: npc.mNpdt52.mHealth = static_cast(value.toInt()); break; + case 3: npc.mNpdt52.mMana = static_cast(value.toInt()); break; + case 4: npc.mNpdt52.mFatigue = static_cast(value.toInt()); break; + case 5: npc.mNpdt52.mDisposition = static_cast(value.toInt()); break; + case 6: npc.mNpdt52.mReputation = static_cast(value.toInt()); break; + case 7: npc.mNpdt52.mRank = static_cast(value.toInt()); break; + case 8: npc.mNpdt52.mGold = value.toInt(); break; + case 9: npc.mPersistent = value.toBool(); break; default: return; // throw an exception here? } @@ -1453,3 +1121,227 @@ void CSMWorld::WeaponRefIdAdapter::setData (const RefIdColumn *column, RefIdData EnchantableRefIdAdapter::setData (column, data, index, value); } } + +void CSMWorld::NestedSpellRefIdAdapter::addNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int position) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + + if (record.get().mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) + return; // can't edit autocalculated spells + + ESM::NPC caster = record.get(); + + std::vector& list = caster.mSpells.mList; + + std::string newString; + + if (position >= (int)list.size()) + list.push_back(newString); + else + list.insert(list.begin()+position, newString); + + record.setModified (caster); +} + +void CSMWorld::NestedSpellRefIdAdapter::removeNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int rowToRemove) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + + if (record.get().mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) + return; // can't edit autocalculated spells + + ESM::NPC caster = record.get(); + + std::vector& list = caster.mSpells.mList; + + // avoid race power rows + int size = 0; + int raceIndex = mData.getRaces().searchId(caster.mRace); + if (raceIndex != -1) + size = mData.getRaces().getRecord(raceIndex).get().mPowers.mList.size(); + + if (rowToRemove < 0 || rowToRemove >= static_cast (list.size() + size)) + throw std::runtime_error ("index out of range"); + + if (rowToRemove >= static_cast(list.size()) && rowToRemove < static_cast(list.size() + size)) + return; // hack, assumes the race powers are added at the end + + list.erase (list.begin () + rowToRemove); + + record.setModified (caster); +} + +void CSMWorld::NestedSpellRefIdAdapter::setNestedData (const RefIdColumn *column, + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (row, mType))); + + if (record.get().mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) + return; // can't edit autocalculated spells + + ESM::NPC caster = record.get(); + std::vector& list = caster.mSpells.mList; + + // avoid race power rows + int size = 0; + int raceIndex = mData.getRaces().searchId(caster.mRace); + if (raceIndex != -1) + size = mData.getRaces().getRecord(raceIndex).get().mPowers.mList.size(); + + if (subRowIndex < 0 || subRowIndex >= static_cast (list.size() + size)) + throw std::runtime_error ("index out of range"); + + if (subRowIndex >= static_cast(list.size()) && subRowIndex < static_cast(list.size() + size)) + return; // hack, assumes the race powers are added at the end + + if (subColIndex == 0) + list.at(subRowIndex) = std::string(value.toString().toUtf8()); + else + throw std::runtime_error("Trying to access non-existing column in the nested table!"); + + record.setModified (caster); +} + +QVariant CSMWorld::NestedSpellRefIdAdapter::getNestedData (const RefIdColumn *column, + const RefIdData& data, int index, int subRowIndex, int subColIndex) const +{ + const Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + + const std::vector& spells = mData.npcAutoCalculate(record.get())->spells(); + + if (subRowIndex < 0 || subRowIndex >= static_cast (spells.size())) + throw std::runtime_error ("index out of range"); + + switch (subColIndex) + { + case 0: return QString::fromUtf8(spells[subRowIndex].mName.c_str()); + case 1: return spells[subRowIndex].mType; + case 2: return spells[subRowIndex].mFromRace; + case 3: return spells[subRowIndex].mCost; + case 4: return spells[subRowIndex].mChance; + default: + throw std::runtime_error("Trying to access non-existing column in the nested table!"); + } +} + +int CSMWorld::NestedSpellRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, + const RefIdData& data) const +{ + return 5; +} + +int CSMWorld::NestedSpellRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const +{ + const Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + + const std::vector spells = mData.npcAutoCalculate(record.get())->spells(); + return static_cast(spells.size()); +} + +template <> +void CSMWorld::NestedSpellRefIdAdapter::addNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int position) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + ESM::Creature caster = record.get(); + + std::vector& list = caster.mSpells.mList; + + std::string newString; + + if (position >= (int)list.size()) + list.push_back(newString); + else + list.insert(list.begin()+position, newString); + + record.setModified (caster); +} + +template <> +void CSMWorld::NestedSpellRefIdAdapter::removeNestedRow (const RefIdColumn *column, + RefIdData& data, int index, int rowToRemove) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + ESM::Creature caster = record.get(); + + std::vector& list = caster.mSpells.mList; + + if (rowToRemove < 0 || rowToRemove >= static_cast (list.size())) + throw std::runtime_error ("index out of range"); + + list.erase (list.begin () + rowToRemove); + + record.setModified (caster); +} + +template <> +void CSMWorld::NestedSpellRefIdAdapter::setNestedData (const RefIdColumn *column, + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const +{ + Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (row, mType))); + ESM::Creature caster = record.get(); + std::vector& list = caster.mSpells.mList; + + if (subRowIndex < 0 || subRowIndex >= static_cast (list.size())) + throw std::runtime_error ("index out of range"); + + if (subColIndex == 0) + list.at(subRowIndex) = std::string(value.toString().toUtf8()); + else + throw std::runtime_error("Trying to access non-existing column in the nested table!"); + + record.setModified (caster); +} + +template<> +QVariant CSMWorld::NestedSpellRefIdAdapter::getNestedData (const RefIdColumn *column, + const RefIdData& data, int index, int subRowIndex, int subColIndex) const +{ + const Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + + const std::vector& list = record.get().mSpells.mList; + + if (subRowIndex < 0 || subRowIndex >= static_cast (list.size())) + throw std::runtime_error ("index out of range"); + + const std::string& content = list.at(subRowIndex); + + int type = -1; + int spellIndex = mData.getSpells().searchId(content); + if (spellIndex != -1) + type = mData.getSpells().getRecord(spellIndex).get().mData.mType; + + if (subColIndex == 0) + return QString::fromUtf8(content.c_str()); + else if (subColIndex == 1) + return type; + else + throw std::runtime_error("Trying to access non-existing column in the nested table!"); +} + +template <> +int CSMWorld::NestedSpellRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, + const RefIdData& data) const +{ + return 2; +} + +template <> +int CSMWorld::NestedSpellRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const +{ + const Record& record = + static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); + + return static_cast(record.get().mSpells.mList.size()); +} diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index b12f84d5e..acdc124eb 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -21,6 +21,7 @@ #include "refidadapter.hpp" #include "nestedtablewrapper.hpp" #include "idcollection.hpp" +#include "data.hpp" namespace CSMWorld { @@ -806,16 +807,10 @@ namespace CSMWorld class NpcRefIdAdapter : public ActorRefIdAdapter { NpcColumns mColumns; - const IdCollection& mRaceTable; - const IdCollection& mClassTable; - const IdCollection& mSkillTable; public: - NpcRefIdAdapter (const NpcColumns& columns, - const IdCollection& races, - const IdCollection& classes, - const IdCollection& skills); + NpcRefIdAdapter (const NpcColumns& columns); virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) const; @@ -860,15 +855,11 @@ namespace CSMWorld class NpcAttributesRefIdAdapter : public NestedRefIdAdapterBase { - const IdCollection& mRaceTable; - const IdCollection& mClassTable; - const IdCollection& mSkillTable; + const Data& mData; public: - NpcAttributesRefIdAdapter (const IdCollection& races, - const IdCollection& classes, - const IdCollection& skills); + NpcAttributesRefIdAdapter (const Data& data); virtual void addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const; @@ -895,15 +886,11 @@ namespace CSMWorld class NpcSkillsRefIdAdapter : public NestedRefIdAdapterBase { - const IdCollection& mRaceTable; - const IdCollection& mClassTable; - const IdCollection& mSkillTable; + const Data& mData; public: - NpcSkillsRefIdAdapter (const IdCollection& races, - const IdCollection& classes, - const IdCollection& skills); + NpcSkillsRefIdAdapter (const Data& data); virtual void addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const; @@ -930,18 +917,14 @@ namespace CSMWorld class NpcMiscRefIdAdapter : public NestedRefIdAdapterBase { - const IdCollection& mRaceTable; - const IdCollection& mClassTable; - const IdCollection& mSkillTable; + const Data& mData; NpcMiscRefIdAdapter (const NpcMiscRefIdAdapter&); NpcMiscRefIdAdapter& operator= (const NpcMiscRefIdAdapter&); public: - NpcMiscRefIdAdapter (const IdCollection& races, - const IdCollection& classes, - const IdCollection& skills); + NpcMiscRefIdAdapter (const Data& data); virtual ~NpcMiscRefIdAdapter(); virtual void addNestedRow (const RefIdColumn *column, @@ -1189,6 +1172,7 @@ namespace CSMWorld class NestedSpellRefIdAdapter : public NestedRefIdAdapterBase { UniversalId::Type mType; + const Data& mData; // not implemented NestedSpellRefIdAdapter (const NestedSpellRefIdAdapter&); @@ -1196,45 +1180,15 @@ namespace CSMWorld public: - NestedSpellRefIdAdapter(UniversalId::Type type) :mType(type) {} + NestedSpellRefIdAdapter(UniversalId::Type type, const Data& data) :mType(type), mData(data) {} virtual ~NestedSpellRefIdAdapter() {} virtual void addNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int position) const - { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); - ESXRecordT caster = record.get(); - - std::vector& list = caster.mSpells.mList; - - std::string newString; - - if (position >= (int)list.size()) - list.push_back(newString); - else - list.insert(list.begin()+position, newString); - - record.setModified (caster); - } + RefIdData& data, int index, int position) const; virtual void removeNestedRow (const RefIdColumn *column, - RefIdData& data, int index, int rowToRemove) const - { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); - ESXRecordT caster = record.get(); - - std::vector& list = caster.mSpells.mList; - - if (rowToRemove < 0 || rowToRemove >= static_cast (list.size())) - throw std::runtime_error ("index out of range"); - - list.erase (list.begin () + rowToRemove); - - record.setModified (caster); - } + RefIdData& data, int index, int rowToRemove) const; virtual void setNestedTable (const RefIdColumn* column, RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const @@ -1260,55 +1214,14 @@ namespace CSMWorld } virtual QVariant getNestedData (const RefIdColumn *column, - const RefIdData& data, int index, int subRowIndex, int subColIndex) const - { - const Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); - - const std::vector& list = record.get().mSpells.mList; - - if (subRowIndex < 0 || subRowIndex >= static_cast (list.size())) - throw std::runtime_error ("index out of range"); - - const std::string& content = list.at(subRowIndex); - - if (subColIndex == 0) - return QString::fromUtf8(content.c_str()); - else - throw std::runtime_error("Trying to access non-existing column in the nested table!"); - } + const RefIdData& data, int index, int subRowIndex, int subColIndex) const; virtual void setNestedData (const RefIdColumn *column, - RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const - { - Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (row, mType))); - ESXRecordT caster = record.get(); - std::vector& list = caster.mSpells.mList; + RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const; - if (subRowIndex < 0 || subRowIndex >= static_cast (list.size())) - throw std::runtime_error ("index out of range"); + virtual int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const; - if (subColIndex == 0) - list.at(subRowIndex) = std::string(value.toString().toUtf8()); - else - throw std::runtime_error("Trying to access non-existing column in the nested table!"); - - record.setModified (caster); - } - - virtual int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const - { - return 1; - } - - virtual int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const - { - const Record& record = - static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); - - return static_cast(record.get().mSpells.mList.size()); - } + virtual int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const; }; template diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 739e9cec4..d5232db63 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -146,12 +146,21 @@ CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data) actorsColumns.mSpells = &mColumns.back(); std::map spellsMap; spellsMap.insert(std::make_pair(UniversalId::Type_Npc, - new NestedSpellRefIdAdapter (UniversalId::Type_Npc))); + new NestedSpellRefIdAdapter (UniversalId::Type_Npc, data))); spellsMap.insert(std::make_pair(UniversalId::Type_Creature, - new NestedSpellRefIdAdapter (UniversalId::Type_Creature))); + new NestedSpellRefIdAdapter (UniversalId::Type_Creature, data))); mNestedAdapters.push_back (std::make_pair(&mColumns.back(), spellsMap)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_SpellId, CSMWorld::ColumnBase::Display_Spell)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_SpellType, CSMWorld::ColumnBase::Display_SpellType, false/*editable*/, false/*user editable*/)); + // creatures do not have below columns + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_SpellSrc, CSMWorld::ColumnBase::Display_YesNo, false, false)); // from race + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_SpellCost, CSMWorld::ColumnBase::Display_Integer, false, false)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_SpellChance, CSMWorld::ColumnBase::Display_Integer/*Percent*/, false, false)); // Nested table mColumns.push_back(RefIdColumn (Columns::ColumnId_NpcDestinations, @@ -438,7 +447,7 @@ CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data) ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue)); npcColumns.mAttributes = &mColumns.back(); std::map attrMap; - attrMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcAttributesRefIdAdapter(data.getRaces(), data.getClasses(), data.getSkills()))); + attrMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcAttributesRefIdAdapter(data))); mNestedAdapters.push_back (std::make_pair(&mColumns.back(), attrMap)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_NpcAttributes, CSMWorld::ColumnBase::Display_String, false, false)); @@ -450,7 +459,7 @@ CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data) ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue)); npcColumns.mSkills = &mColumns.back(); std::map skillsMap; - skillsMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcSkillsRefIdAdapter(data.getRaces(), data.getClasses(), data.getSkills()))); + skillsMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcSkillsRefIdAdapter(data))); mNestedAdapters.push_back (std::make_pair(&mColumns.back(), skillsMap)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_NpcSkills, CSMWorld::ColumnBase::Display_String, false, false)); @@ -462,7 +471,7 @@ CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data) ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_List)); npcColumns.mMisc = &mColumns.back(); std::map miscMap; - miscMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcMiscRefIdAdapter(data.getRaces(), data.getClasses(), data.getSkills()))); + miscMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcMiscRefIdAdapter(data))); mNestedAdapters.push_back (std::make_pair(&mColumns.back(), miscMap)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_NpcLevel, CSMWorld::ColumnBase::Display_Integer, @@ -611,7 +620,7 @@ CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data) mAdapters.insert (std::make_pair (UniversalId::Type_Miscellaneous, new MiscRefIdAdapter (inventoryColumns, key))); mAdapters.insert (std::make_pair (UniversalId::Type_Npc, - new NpcRefIdAdapter (npcColumns, data.getRaces(), data.getClasses(), data.getSkills()))); + new NpcRefIdAdapter (npcColumns))); mAdapters.insert (std::make_pair (UniversalId::Type_Probe, new ToolRefIdAdapter (UniversalId::Type_Probe, toolsColumns))); mAdapters.insert (std::make_pair (UniversalId::Type_Repair, diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index f11079ac4..34e310a49 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -678,6 +678,14 @@ CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, CSM connect(mEditWidget, SIGNAL(tableMimeDataDropped(QWidget*, const QModelIndex&, const CSMWorld::UniversalId&, const CSMDoc::Document*)), this, SLOT(tableMimeDataDropped(QWidget*, const QModelIndex&, const CSMWorld::UniversalId&, const CSMDoc::Document*))); + if (id.getType() == CSMWorld::UniversalId::Type_Referenceable) + { + CSMWorld::IdTree *objectTable = static_cast(mTable); + + connect (objectTable, SIGNAL (refreshNpcDialogue (int, const std::string&)), + this, SLOT (refreshNpcDialogue (int, const std::string&))); + } + mMainLayout->addWidget(mEditWidget); mEditWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); @@ -897,3 +905,27 @@ void CSVWorld::DialogueSubView::changeCurrentId (const std::string& newId) selection.push_back(mCurrentId); mCommandDispatcher.setSelection(selection); } + +void CSVWorld::DialogueSubView::refreshNpcDialogue (int type, const std::string& id) +{ + int typeColumn = mTable->findColumnIndex (CSMWorld::Columns::ColumnId_RecordType); + if (CSMWorld::UniversalId::Type_Npc + != mTable->data(mTable->getModelIndex(mCurrentId, typeColumn), Qt::DisplayRole).toInt()) + { + return; + } + + int raceColumn = mTable->findColumnIndex (CSMWorld::Columns::ColumnId_Race); + int classColumn = mTable->findColumnIndex (CSMWorld::Columns::ColumnId_Class); + + if ((type == 0/*FIXME*/ && id == "") // skill or gmst changed + || (id == mTable->data(mTable->getModelIndex(mCurrentId, raceColumn), + Qt::DisplayRole).toString().toUtf8().constData()) // race + || (id == mTable->data(mTable->getModelIndex(mCurrentId, classColumn), + Qt::DisplayRole).toString().toUtf8().constData())) // class + { + int y = mEditWidget->verticalScrollBar()->value(); + mEditWidget->remake (mTable->getModelIndex(mCurrentId, 0).row()); + mEditWidget->verticalScrollBar()->setValue(y); + } +} diff --git a/apps/opencs/view/world/dialoguesubview.hpp b/apps/opencs/view/world/dialoguesubview.hpp index 6cbd8ad77..28b267155 100644 --- a/apps/opencs/view/world/dialoguesubview.hpp +++ b/apps/opencs/view/world/dialoguesubview.hpp @@ -237,6 +237,8 @@ namespace CSVWorld void requestFocus (const std::string& id); void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); + + void refreshNpcDialogue (int type, const std::string& id); }; } diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index c7c701d20..d54152100 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -66,7 +66,7 @@ add_openmw_dir (mwworld cells localscripts customdata weather inventorystore ptr actionopen actionread actionequip timestamp actionalchemy cellstore actionapply actioneat esmstore store recordcmp fallback actionrepair actionsoulgem livecellref actiondoor - contentloader esmloader actiontrap cellreflist projectilemanager cellref + contentloader esmloader actiontrap cellreflist projectilemanager cellref mwstore ) add_openmw_dir (mwclass @@ -78,7 +78,7 @@ add_openmw_dir (mwmechanics mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor aiescort aiactivate aicombat repair enchanting pathfinding pathgrid security spellsuccess spellcasting - disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning + disease pickpocket levelledlist combat steering obstacle difficultyscaling aicombataction actor summoning ) add_openmw_dir (mwstate diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 3ca57aca8..4f419c635 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -11,6 +11,10 @@ #include #include +#include +#include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" @@ -24,7 +28,6 @@ #include "../mwmechanics/spellcasting.hpp" #include "../mwmechanics/disease.hpp" #include "../mwmechanics/combat.hpp" -#include "../mwmechanics/autocalcspell.hpp" #include "../mwmechanics/difficultyscaling.hpp" #include "../mwmechanics/character.hpp" @@ -36,6 +39,7 @@ #include "../mwworld/customdata.hpp" #include "../mwworld/physicssystem.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/mwstore.hpp" #include "../mwrender/actors.hpp" #include "../mwrender/renderinginterface.hpp" @@ -58,116 +62,47 @@ namespace return new NpcCustomData (*this); } - int is_even(double d) { - double int_part; - modf(d / 2.0, &int_part); - return 2.0 * int_part == d; - } + class Stats : public GamePlay::StatsBase + { + MWMechanics::CreatureStats& mCreatureStats; + MWMechanics::NpcStats& mNpcStats; - int round_ieee_754(double d) { - double i = floor(d); - d -= i; - if(d < 0.5) - return static_cast(i); - if(d > 0.5) - return static_cast(i) + 1; - if(is_even(i)) - return static_cast(i); - return static_cast(i) + 1; - } + public: + + Stats(MWMechanics::CreatureStats& creatureStats, MWMechanics::NpcStats& npcStats) + : mCreatureStats(creatureStats), mNpcStats(npcStats) {} + + virtual unsigned char getBaseAttribute(int index) const { return mCreatureStats.getAttribute(index).getBase(); } + + virtual void setAttribute(int index, unsigned char value) { mCreatureStats.setAttribute(index, value); } + + virtual void addSpells(std::string id) { mCreatureStats.getSpells().add(id); } + + virtual unsigned char getBaseSkill(int index) const { return mNpcStats.getSkill(index).getBase(); } + + virtual void setBaseSkill(int index, unsigned char value) { mNpcStats.getSkill(index).setBase(value); } + }; void autoCalculateAttributes (const ESM::NPC* npc, MWMechanics::CreatureStats& creatureStats) { - // race bonus const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get().find(npc->mRace); - bool male = (npc->mFlags & ESM::NPC::Female) == 0; - - int level = creatureStats.getLevel(); - for (int i=0; imData.mAttributeValues[i]; - creatureStats.setAttribute(i, male ? attribute.mMale : attribute.mFemale); - } - - // class bonus const ESM::Class *class_ = MWBase::Environment::get().getWorld()->getStore().get().find(npc->mClass); - for (int i=0; i<2; ++i) - { - int attribute = class_->mData.mAttribute[i]; - if (attribute>=0 && attribute<8) - { - creatureStats.setAttribute(attribute, - creatureStats.getAttribute(attribute).getBase() + 10); - } - } + int level = creatureStats.getLevel(); - // skill bonus - for (int attribute=0; attribute < ESM::Attribute::Length; ++attribute) - { - float modifierSum = 0; + MWMechanics::NpcStats dummy; // npc stats are needed for skills, which is not calculated here + Stats stats(creatureStats, dummy); - for (int j=0; jgetStore().get().find(j); + MWWorld::MWStore store; - if (skill->mData.mAttribute != attribute) - continue; + GamePlay::autoCalcAttributesImpl (npc, race, class_, level, stats, &store); - // is this a minor or major skill? - float add=0.2f; - for (int k=0; k<5; ++k) - { - if (class_->mData.mSkills[k][0] == j) - add=0.5; - } - for (int k=0; k<5; ++k) - { - if (class_->mData.mSkills[k][1] == j) - add=1.0; - } - modifierSum += add; - } - creatureStats.setAttribute(attribute, std::min( - round_ieee_754(creatureStats.getAttribute(attribute).getBase() - + (level-1) * modifierSum), 100) ); - } - - // initial health - int strength = creatureStats.getAttribute(ESM::Attribute::Strength).getBase(); - int endurance = creatureStats.getAttribute(ESM::Attribute::Endurance).getBase(); - - int multiplier = 3; - - if (class_->mData.mSpecialization == ESM::Class::Combat) - multiplier += 2; - else if (class_->mData.mSpecialization == ESM::Class::Stealth) - multiplier += 1; - - if (class_->mData.mAttribute[0] == ESM::Attribute::Endurance - || class_->mData.mAttribute[1] == ESM::Attribute::Endurance) - multiplier += 1; - - creatureStats.setHealth(floor(0.5f * (strength + endurance)) + multiplier * (creatureStats.getLevel() - 1)); + creatureStats.setHealth(GamePlay::autoCalculateHealth(level, class_, stats)); } - /** - * @brief autoCalculateSkills - * - * Skills are calculated with following formulae ( http://www.uesp.net/wiki/Morrowind:NPCs#Skills ): - * - * Skills: (Level - 1) × (Majority Multiplier + Specialization Multiplier) - * - * The Majority Multiplier is 1.0 for a Major or Minor Skill, or 0.1 for a Miscellaneous Skill. - * - * The Specialization Multiplier is 0.5 for a Skill in the same Specialization as the class, - * zero for other Skills. - * - * and by adding class, race, specialization bonus. - */ void autoCalculateSkills(const ESM::NPC* npc, MWMechanics::NpcStats& npcStats, const MWWorld::Ptr& ptr) { const ESM::Class *class_ = @@ -177,77 +112,13 @@ namespace const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get().find(npc->mRace); + Stats stats(npcStats, npcStats); - for (int i = 0; i < 2; ++i) - { - int bonus = (i==0) ? 10 : 25; + MWWorld::MWStore store; - for (int i2 = 0; i2 < 5; ++i2) - { - int index = class_->mData.mSkills[i2][i]; - if (index >= 0 && index < ESM::Skill::Length) - { - npcStats.getSkill(index).setBase (npcStats.getSkill(index).getBase() + bonus); - } - } - } + GamePlay::autoCalcSkillsImpl(npc, race, class_, level, stats, &store); - for (int skillIndex = 0; skillIndex < ESM::Skill::Length; ++skillIndex) - { - float majorMultiplier = 0.1f; - float specMultiplier = 0.0f; - - int raceBonus = 0; - int specBonus = 0; - - for (int raceSkillIndex = 0; raceSkillIndex < 7; ++raceSkillIndex) - { - if (race->mData.mBonus[raceSkillIndex].mSkill == skillIndex) - { - raceBonus = race->mData.mBonus[raceSkillIndex].mBonus; - break; - } - } - - for (int k = 0; k < 5; ++k) - { - // is this a minor or major skill? - if ((class_->mData.mSkills[k][0] == skillIndex) || (class_->mData.mSkills[k][1] == skillIndex)) - { - majorMultiplier = 1.0f; - break; - } - } - - // is this skill in the same Specialization as the class? - const ESM::Skill* skill = MWBase::Environment::get().getWorld()->getStore().get().find(skillIndex); - if (skill->mData.mSpecialization == class_->mData.mSpecialization) - { - specMultiplier = 0.5f; - specBonus = 5; - } - - npcStats.getSkill(skillIndex).setBase( - std::min( - round_ieee_754( - npcStats.getSkill(skillIndex).getBase() - + 5 - + raceBonus - + specBonus - +(int(level)-1) * (majorMultiplier + specMultiplier)), 100)); // Must gracefully handle level 0 - } - - int skills[ESM::Skill::Length]; - for (int i=0; i spells = MWMechanics::autoCalcNpcSpells(skills, attributes, race); - for (std::vector::iterator it = spells.begin(); it != spells.end(); ++it) - npcStats.getSpells().add(*it); + GamePlay::autoCalculateSpells(race, stats, &store); } } @@ -392,7 +263,7 @@ namespace MWClass // store ptr.getRefData().setCustomData (data.release()); - getInventoryStore(ptr).autoEquip(ptr); + getInventoryStore(ptr).autoEquip(ptr); } } diff --git a/apps/openmw/mwgui/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp index c392372ff..c6954d91d 100644 --- a/apps/openmw/mwgui/levelupdialog.cpp +++ b/apps/openmw/mwgui/levelupdialog.cpp @@ -4,6 +4,8 @@ #include #include +#include + #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -140,7 +142,7 @@ namespace MWGui if(world->getStore().get().isDynamic(cls->mId)) { // Choosing Stealth specialization and Speed/Agility as attributes, if possible. Otherwise fall back to first class found. - MWWorld::SharedIterator it = world->getStore().get().begin(); + GamePlay::SharedIterator it = world->getStore().get().begin(); for(; it != world->getStore().get().end(); ++it) { if(it->mData.mIsPlayable && it->mData.mSpecialization == 2 && it->mData.mAttribute[0] == 4 && it->mData.mAttribute[1] == 3) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index c829154e2..5d61bc1d2 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -6,8 +6,11 @@ #include +#include + #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" +#include "../mwworld/mwstore.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -23,7 +26,6 @@ #include #include "spellcasting.hpp" -#include "autocalcspell.hpp" #include @@ -252,10 +254,12 @@ namespace MWMechanics continue; static const float fAutoPCSpellChance = esmStore.get().find("fAutoPCSpellChance")->getFloat(); - if (calcAutoCastChance(spell, skills, attributes, -1) < fAutoPCSpellChance) + MWWorld::MWStore store; + + if (GamePlay::calcAutoCastChance(spell, skills, attributes, -1, &store) < fAutoPCSpellChance) continue; - if (!attrSkillCheck(spell, skills, attributes)) + if (!GamePlay::attrSkillCheck(spell, skills, attributes, &store)) continue; selectedSpells.push_back(spell->mId); diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 5d9beecb6..61518548b 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -66,7 +66,7 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) esm.getRecHeader(); // Look up the record type. - std::map::iterator it = mStores.find(n.val); + std::map::iterator it = mStores.find(n.val); if (it == mStores.end()) { if (n.val == ESM::REC_INFO) { @@ -130,7 +130,7 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) void ESMStore::setUp() { - std::map::iterator it = mStores.begin(); + std::map::iterator it = mStores.begin(); for (; it != mStores.end(); ++it) { it->second->setUp(); } diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 05b633956..61d361b3e 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -67,7 +67,7 @@ namespace MWWorld // Lookup of all IDs. Makes looking up references faster. Just // maps the id name to the record type. std::map mIds; - std::map mStores; + std::map mStores; ESM::NPC mPlayerTemplate; @@ -75,7 +75,7 @@ namespace MWWorld public: /// \todo replace with SharedIterator - typedef std::map::const_iterator iterator; + typedef std::map::const_iterator iterator; iterator begin() const { return mStores.begin(); @@ -144,7 +144,7 @@ namespace MWWorld void clearDynamic () { - for (std::map::iterator it = mStores.begin(); it != mStores.end(); ++it) + for (std::map::iterator it = mStores.begin(); it != mStores.end(); ++it) it->second->clearDynamic(); mNpcs.insert(mPlayerTemplate); diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index ab09782b1..852092a9a 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -10,6 +10,7 @@ #include #include +#include #include @@ -17,89 +18,10 @@ namespace MWWorld { - struct StoreBase - { - virtual ~StoreBase() {} - - virtual void setUp() {} - virtual void listIdentifier(std::vector &list) const {} - - virtual size_t getSize() const = 0; - virtual int getDynamicSize() const { return 0; } - virtual void load(ESM::ESMReader &esm, const std::string &id) = 0; - - virtual bool eraseStatic(const std::string &id) {return false;} - virtual void clearDynamic() {} - - virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const {} - - virtual void read (ESM::ESMReader& reader, const std::string& id) {} - ///< Read into dynamic storage - }; - - template - class SharedIterator - { - typedef typename std::vector::const_iterator Iter; - - Iter mIter; - - public: - SharedIterator() {} - - SharedIterator(const SharedIterator &orig) - : mIter(orig.mIter) - {} - - SharedIterator(const Iter &iter) - : mIter(iter) - {} - - SharedIterator &operator++() { - ++mIter; - return *this; - } - - SharedIterator operator++(int) { - SharedIterator iter = *this; - ++mIter; - - return iter; - } - - SharedIterator &operator--() { - --mIter; - return *this; - } - - SharedIterator operator--(int) { - SharedIterator iter = *this; - --mIter; - - return iter; - } - - bool operator==(const SharedIterator &x) const { - return mIter == x.mIter; - } - - bool operator!=(const SharedIterator &x) const { - return !(*this == x); - } - - const T &operator*() const { - return **mIter; - } - - const T *operator->() const { - return &(**mIter); - } - }; - class ESMStore; template - class Store : public StoreBase + class Store : public GamePlay::CommonStore { std::map mStatic; std::vector mShared; // Preserves the record order as it came from the content files (this @@ -137,7 +59,7 @@ namespace MWWorld : mStatic(orig.mData) {} - typedef SharedIterator iterator; + typedef GamePlay::SharedIterator iterator; // setUp needs to be called again after virtual void clearDynamic() @@ -380,7 +302,7 @@ namespace MWWorld } template <> - class Store : public StoreBase + class Store : public GamePlay::StoreBase { // For multiple ESM/ESP files we need one list per file. typedef std::vector LandTextureList; @@ -457,7 +379,7 @@ namespace MWWorld }; template <> - class Store : public StoreBase + class Store : public GamePlay::StoreBase { std::vector mStatic; @@ -472,7 +394,7 @@ namespace MWWorld }; public: - typedef SharedIterator iterator; + typedef GamePlay::SharedIterator iterator; virtual ~Store() { @@ -546,7 +468,7 @@ namespace MWWorld }; template <> - class Store : public StoreBase + class Store : public GamePlay::StoreBase { struct DynamicExtCmp { @@ -586,7 +508,7 @@ namespace MWWorld void handleMovedCellRefs(ESM::ESMReader& esm, ESM::Cell* cell); public: - typedef SharedIterator iterator; + typedef GamePlay::SharedIterator iterator; const ESM::Cell *search(const std::string &id) const { ESM::Cell cell; @@ -834,7 +756,7 @@ namespace MWWorld }; template <> - class Store : public StoreBase + class Store : public GamePlay::StoreBase { private: typedef std::map Interior; diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 1b33b10f6..8c37c8ef3 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -6,16 +6,16 @@ set (VERSION_HPP ${CMAKE_CURRENT_SOURCE_DIR}/version/version.hpp) if (GIT_CHECKOUT) add_custom_target (git-version COMMAND ${CMAKE_COMMAND} - -DGIT_EXECUTABLE=${GIT_EXECUTABLE} + -DGIT_EXECUTABLE=${GIT_EXECUTABLE} -DPROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR} - -DVERSION_HPP_IN=${VERSION_HPP_IN} - -DVERSION_HPP=${VERSION_HPP} - -DOPENMW_VERSION_MAJOR=${OPENMW_VERSION_MAJOR} - -DOPENMW_VERSION_MINOR=${OPENMW_VERSION_MINOR} - -DOPENMW_VERSION_RELEASE=${OPENMW_VERSION_RELEASE} - -DOPENMW_VERSION=${OPENMW_VERSION} + -DVERSION_HPP_IN=${VERSION_HPP_IN} + -DVERSION_HPP=${VERSION_HPP} + -DOPENMW_VERSION_MAJOR=${OPENMW_VERSION_MAJOR} + -DOPENMW_VERSION_MINOR=${OPENMW_VERSION_MINOR} + -DOPENMW_VERSION_RELEASE=${OPENMW_VERSION_RELEASE} + -DOPENMW_VERSION=${OPENMW_VERSION} -P ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/GitVersion.cmake - VERBATIM) + VERBATIM) else (GIT_CHECKOUT) configure_file(${VERSION_HPP_IN} ${VERSION_HPP}) endif (GIT_CHECKOUT) @@ -119,6 +119,10 @@ add_component_dir (fontloader fontloader ) +add_component_dir (gameplay + autocalc autocalcspell + ) + add_component_dir (version version ) @@ -161,14 +165,14 @@ include_directories(${BULLET_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) add_library(components STATIC ${COMPONENT_FILES} ${MOC_SRCS} ${ESM_UI_HDR}) -target_link_libraries(components +target_link_libraries(components ${Boost_SYSTEM_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_THREAD_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_WAVE_LIBRARY} ${OGRE_LIBRARIES} - ${OENGINE_LIBRARY} + ${OENGINE_LIBRARY} ${BULLET_LIBRARIES} ) diff --git a/components/gameplay/autocalc.cpp b/components/gameplay/autocalc.cpp new file mode 100644 index 000000000..a1888c434 --- /dev/null +++ b/components/gameplay/autocalc.cpp @@ -0,0 +1,207 @@ +#include "autocalc.hpp" + +#include +#include +#include +#include +#include +#include + +#include "autocalcspell.hpp" + +// Most of the code in this file was moved from apps/openmw/mwclass/npc.cpp +namespace +{ + int is_even(double d) + { + double int_part; + + modf(d / 2.0, &int_part); + return 2.0 * int_part == d; + } + + int round_ieee_754(double d) + { + double i = floor(d); + d -= i; + + if(d < 0.5) + return static_cast(i); + if(d > 0.5) + return static_cast(i) + 1; + if(is_even(i)) + return static_cast(i); + return static_cast(i) + 1; + } +} + +namespace GamePlay +{ + void autoCalcAttributesImpl (const ESM::NPC* npc, + const ESM::Race *race, const ESM::Class *class_, int level, StatsBase& stats, StoreWrap *store) + { + // race bonus + bool male = (npc->mFlags & ESM::NPC::Female) == 0; + + for (int i=0; imData.mAttributeValues[i]; + stats.setAttribute(i, male ? attribute.mMale : attribute.mFemale); + } + + // class bonus + for (int i=0; i<2; ++i) + { + int attribute = class_->mData.mAttribute[i]; + if (attribute>=0 && attributefindSkill(j); + + if (skill->mData.mAttribute != attribute) + continue; + + // is this a minor or major skill? + float add=0.2f; + for (int k=0; k<5; ++k) + { + if (class_->mData.mSkills[k][0] == j) + add=0.5; + } + for (int k=0; k<5; ++k) + { + if (class_->mData.mSkills[k][1] == j) + add=1.0; + } + modifierSum += add; + } + stats.setAttribute(attribute, + std::min(round_ieee_754(stats.getBaseAttribute(attribute) + (level-1) * modifierSum), 100) ); + } + } + + /** + * @brief autoCalculateSkills + * + * Skills are calculated with following formulae ( http://www.uesp.net/wiki/Morrowind:NPCs#Skills ): + * + * Skills: (Level - 1) × (Majority Multiplier + Specialization Multiplier) + * + * The Majority Multiplier is 1.0 for a Major or Minor Skill, or 0.1 for a Miscellaneous Skill. + * + * The Specialization Multiplier is 0.5 for a Skill in the same Specialization as the class, + * zero for other Skills. + * + * and by adding class, race, specialization bonus. + */ + void autoCalcSkillsImpl (const ESM::NPC* npc, + const ESM::Race *race, const ESM::Class *class_, int level, StatsBase& stats, StoreWrap *store) + { + for (int i = 0; i < 2; ++i) + { + int bonus = (i==0) ? 10 : 25; + + for (int i2 = 0; i2 < 5; ++i2) + { + int index = class_->mData.mSkills[i2][i]; + if (index >= 0 && index < ESM::Skill::Length) + { + stats.setBaseSkill (index, stats.getBaseSkill(index) + bonus); + } + } + } + + for (int skillIndex = 0; skillIndex < ESM::Skill::Length; ++skillIndex) + { + float majorMultiplier = 0.1f; + float specMultiplier = 0.0f; + + int raceBonus = 0; + int specBonus = 0; + + for (int raceSkillIndex = 0; raceSkillIndex < 7; ++raceSkillIndex) + { + if (race->mData.mBonus[raceSkillIndex].mSkill == skillIndex) + { + raceBonus = race->mData.mBonus[raceSkillIndex].mBonus; + break; + } + } + + for (int k = 0; k < 5; ++k) + { + // is this a minor or major skill? + if ((class_->mData.mSkills[k][0] == skillIndex) || (class_->mData.mSkills[k][1] == skillIndex)) + { + majorMultiplier = 1.0f; + break; + } + } + + // is this skill in the same Specialization as the class? + const ESM::Skill* skill = store->findSkill(skillIndex); + if (skill->mData.mSpecialization == class_->mData.mSpecialization) + { + specMultiplier = 0.5f; + specBonus = 5; + } + + stats.setBaseSkill(skillIndex, + std::min( + round_ieee_754( + stats.getBaseSkill(skillIndex) + + 5 + + raceBonus + + specBonus + +(int(level)-1) * (majorMultiplier + specMultiplier)), 100)); // Must gracefully handle level 0 + } + } + + unsigned short autoCalculateHealth(int level, const ESM::Class *class_, StatsBase& stats) + { + // initial health + int strength = stats.getBaseAttribute(ESM::Attribute::Strength); + int endurance = stats.getBaseAttribute(ESM::Attribute::Endurance); + + int multiplier = 3; + + if (class_->mData.mSpecialization == ESM::Class::Combat) + multiplier += 2; + else if (class_->mData.mSpecialization == ESM::Class::Stealth) + multiplier += 1; + + if (class_->mData.mAttribute[0] == ESM::Attribute::Endurance + || class_->mData.mAttribute[1] == ESM::Attribute::Endurance) + multiplier += 1; + + return static_cast(floor(0.5f * (strength + endurance)) + multiplier * (level-1)); + } + + void autoCalculateSpells(const ESM::Race *race, StatsBase& stats, StoreWrap *store) + { + int skills[ESM::Skill::Length]; + for (int i=0; i spells = autoCalcNpcSpells(skills, attributes, race, store); + for (std::vector::iterator it = spells.begin(); it != spells.end(); ++it) + stats.addSpells(*it); + } + + StatsBase::StatsBase() {} + + StatsBase::~StatsBase() {} +} diff --git a/components/gameplay/autocalc.hpp b/components/gameplay/autocalc.hpp new file mode 100644 index 000000000..522070413 --- /dev/null +++ b/components/gameplay/autocalc.hpp @@ -0,0 +1,47 @@ +#ifndef COMPONENTS_GAMEPLAY_AUTOCALC_H +#define COMPONENTS_GAMEPLAY_AUTOCALC_H + +#include + +#include "store.hpp" + +namespace ESM +{ + struct NPC; + struct Race; + struct Class; +} + +namespace GamePlay +{ + // wrapper class for sharing the autocalc code between OpenMW and OpenCS + class StatsBase + { + + public: + + StatsBase(); + virtual ~StatsBase(); + + virtual unsigned char getBaseAttribute(int index) const = 0; + + virtual void setAttribute(int index, unsigned char value) = 0; + + virtual void addSpells(std::string id) = 0; + + virtual unsigned char getBaseSkill(int index) const = 0; + + virtual void setBaseSkill(int index, unsigned char value) = 0; + }; + + void autoCalcAttributesImpl (const ESM::NPC* npc, + const ESM::Race *race, const ESM::Class *class_, int level, StatsBase& stats, StoreWrap *store); + + void autoCalcSkillsImpl (const ESM::NPC* npc, + const ESM::Race *race, const ESM::Class *class_, int level, StatsBase& stats, StoreWrap *store); + + unsigned short autoCalculateHealth(int level, const ESM::Class *class_, StatsBase& stats); + + void autoCalculateSpells(const ESM::Race *race, StatsBase& stats, StoreWrap *store); +} +#endif // COMPONENTS_GAMEPLAY_AUTOCALC_H diff --git a/components/gameplay/autocalcspell.cpp b/components/gameplay/autocalcspell.cpp new file mode 100644 index 000000000..8f2843367 --- /dev/null +++ b/components/gameplay/autocalcspell.cpp @@ -0,0 +1,240 @@ +#include "autocalcspell.hpp" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "autocalc.hpp" + +// Most of the code in this file was moved from apps/openmw/mwmechanics/autocalcspell.cpp +namespace GamePlay +{ + + struct SchoolCaps + { + int mCount; + int mLimit; + bool mReachedLimit; + int mMinCost; + std::string mWeakestSpell; + }; + + std::vector autoCalcNpcSpells(const int *actorSkills, + const int *actorAttributes, const ESM::Race* race, StoreWrap *store) + { + static const float fNPCbaseMagickaMult = store->findGmstFloat("fNPCbaseMagickaMult"); + float baseMagicka = fNPCbaseMagickaMult * actorAttributes[ESM::Attribute::Intelligence]; + + static const std::string schools[] = { + "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" + }; + static int iAutoSpellSchoolMax[6]; + static bool init = false; + if (!init) + { + for (int i=0; i<6; ++i) + { + const std::string& gmstName = "iAutoSpell" + schools[i] + "Max"; + iAutoSpellSchoolMax[i] = store->findGmstInt(gmstName); + } + init = true; + } + + std::map schoolCaps; + for (int i=0; i<6; ++i) + { + SchoolCaps caps; + caps.mCount = 0; + caps.mLimit = iAutoSpellSchoolMax[i]; + caps.mReachedLimit = iAutoSpellSchoolMax[i] <= 0; + caps.mMinCost = INT_MAX; + caps.mWeakestSpell.clear(); + schoolCaps[i] = caps; + } + + std::vector selectedSpells; + + const CommonStore &spells = store->getSpells(); + + // Note: the algorithm heavily depends on the traversal order of the spells. For vanilla-compatible results the + // Store must preserve the record ordering as it was in the content files. + for (CommonStore::iterator iter = spells.begin(); iter != spells.end(); ++iter) + { + const ESM::Spell* spell = &*iter; + + if (spell->mData.mType != ESM::Spell::ST_Spell) + continue; + if (!(spell->mData.mFlags & ESM::Spell::F_Autocalc)) + continue; + static const int iAutoSpellTimesCanCast = store->findGmstInt("iAutoSpellTimesCanCast"); + if (baseMagicka < iAutoSpellTimesCanCast * spell->mData.mCost) + continue; + + if (race && race->mPowers.exists(spell->mId)) + continue; + + if (!attrSkillCheck(spell, actorSkills, actorAttributes, store)) + continue; + + int school; + float skillTerm; + calcWeakestSchool(spell, actorSkills, school, skillTerm, store); + assert(school >= 0 && school < 6); + SchoolCaps& cap = schoolCaps[school]; + + if (cap.mReachedLimit && spell->mData.mCost <= cap.mMinCost) + continue; + + static const float fAutoSpellChance = store->findGmstFloat("fAutoSpellChance"); + if (calcAutoCastChance(spell, actorSkills, actorAttributes, school, store) < fAutoSpellChance) + continue; + + selectedSpells.push_back(spell->mId); + + if (cap.mReachedLimit) + { + std::vector::iterator found = std::find(selectedSpells.begin(), selectedSpells.end(), cap.mWeakestSpell); + if (found != selectedSpells.end()) + selectedSpells.erase(found); + + cap.mMinCost = INT_MAX; + for (std::vector::iterator weakIt = selectedSpells.begin(); weakIt != selectedSpells.end(); ++weakIt) + { + const ESM::Spell* testSpell = spells.find(*weakIt); + + //int testSchool; + //float dummySkillTerm; + //calcWeakestSchool(testSpell, actorSkills, testSchool, dummySkillTerm); + + // Note: if there are multiple spells with the same cost, we pick the first one we found. + // So the algorithm depends on the iteration order of the outer loop. + if ( + // There is a huge bug here. It is not checked that weakestSpell is of the correct school. + // As result multiple SchoolCaps could have the same mWeakestSpell. Erasing the weakest spell would then fail if another school + // already erased it, and so the number of spells would often exceed the sum of limits. + // This bug cannot be fixed without significantly changing the results of the spell autocalc, which will not have been playtested. + //testSchool == school && + testSpell->mData.mCost < cap.mMinCost) + { + cap.mMinCost = testSpell->mData.mCost; + cap.mWeakestSpell = testSpell->mId; + } + } + } + else + { + cap.mCount += 1; + if (cap.mCount == cap.mLimit) + cap.mReachedLimit = true; + + if (spell->mData.mCost < cap.mMinCost) + { + cap.mWeakestSpell = spell->mId; + cap.mMinCost = spell->mData.mCost; + } + } + } + + return selectedSpells; + } + + bool attrSkillCheck (const ESM::Spell* spell, + const int* actorSkills, const int* actorAttributes, StoreWrap *store) + { + const std::vector& effects = spell->mEffects.mList; + for (std::vector::const_iterator effectIt = effects.begin(); effectIt != effects.end(); ++effectIt) + { + const ESM::MagicEffect* magicEffect = store->findMagicEffect(effectIt->mEffectID); + static const int iAutoSpellAttSkillMin = store->findGmstInt("iAutoSpellAttSkillMin"); + + if ((magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill)) + { + assert (effectIt->mSkill >= 0 && effectIt->mSkill < ESM::Skill::Length); + if (actorSkills[effectIt->mSkill] < iAutoSpellAttSkillMin) + return false; + } + + if ((magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute)) + { + assert (effectIt->mAttribute >= 0 && effectIt->mAttribute < ESM::Attribute::Length); + if (actorAttributes[effectIt->mAttribute] < iAutoSpellAttSkillMin) + return false; + } + } + + return true; + } + + ESM::Skill::SkillEnum mapSchoolToSkill(int school) + { + std::map schoolSkillMap; // maps spell school to skill id + schoolSkillMap[0] = ESM::Skill::Alteration; + schoolSkillMap[1] = ESM::Skill::Conjuration; + schoolSkillMap[3] = ESM::Skill::Illusion; + schoolSkillMap[2] = ESM::Skill::Destruction; + schoolSkillMap[4] = ESM::Skill::Mysticism; + schoolSkillMap[5] = ESM::Skill::Restoration; + assert(schoolSkillMap.find(school) != schoolSkillMap.end()); + return schoolSkillMap[school]; + } + + void calcWeakestSchool (const ESM::Spell* spell, + const int* actorSkills, int& effectiveSchool, float& skillTerm, StoreWrap *store) + { + float minChance = FLT_MAX; + + const ESM::EffectList& effects = spell->mEffects; + for (std::vector::const_iterator it = effects.mList.begin(); it != effects.mList.end(); ++it) + { + const ESM::ENAMstruct& effect = *it; + float x = static_cast(effect.mDuration); + + const ESM::MagicEffect* magicEffect = store->findMagicEffect(effect.mEffectID); + if (!(magicEffect->mData.mFlags & ESM::MagicEffect::UncappedDamage)) + x = std::max(1.f, x); + + x *= 0.1f * magicEffect->mData.mBaseCost; + x *= 0.5f * (effect.mMagnMin + effect.mMagnMax); + x += effect.mArea * 0.05f * magicEffect->mData.mBaseCost; + if (effect.mRange == ESM::RT_Target) + x *= 1.5f; + + static const float fEffectCostMult = store->findGmstFloat("fEffectCostMult"); + x *= fEffectCostMult; + + float s = 2.f * actorSkills[mapSchoolToSkill(magicEffect->mData.mSchool)]; + if (s - x < minChance) + { + minChance = s - x; + effectiveSchool = magicEffect->mData.mSchool; + skillTerm = s; + } + } + } + + float calcAutoCastChance(const ESM::Spell *spell, + const int *actorSkills, const int *actorAttributes, int effectiveSchool, StoreWrap *store) + { + if (spell->mData.mType != ESM::Spell::ST_Spell) + return 100.f; + + if (spell->mData.mFlags & ESM::Spell::F_Always) + return 100.f; + + float skillTerm = 0; + if (effectiveSchool != -1) + skillTerm = 2.f * actorSkills[mapSchoolToSkill(effectiveSchool)]; + else + calcWeakestSchool(spell, actorSkills, effectiveSchool, skillTerm, store); // Note effectiveSchool is unused after this + + float castChance = skillTerm - spell->mData.mCost + 0.2f * actorAttributes[ESM::Attribute::Willpower] + 0.1f * actorAttributes[ESM::Attribute::Luck]; + return castChance; + } +} diff --git a/components/gameplay/autocalcspell.hpp b/components/gameplay/autocalcspell.hpp new file mode 100644 index 000000000..ceb9f197b --- /dev/null +++ b/components/gameplay/autocalcspell.hpp @@ -0,0 +1,38 @@ +#ifndef COMPONENTS_GAMEPLAY_AUTOCALCSPELL_H +#define COMPONENTS_GAMEPLAY_AUTOCALCSPELL_H + +#include + +#include + +namespace ESM +{ + struct Spell; + struct Race; +} + +namespace GamePlay +{ + +class StoreWrap; + +/// Contains algorithm for calculating an NPC's spells based on stats + +std::vector autoCalcNpcSpells(const int* actorSkills, + const int* actorAttributes, const ESM::Race* race, StoreWrap *store); + +// Helpers + +bool attrSkillCheck (const ESM::Spell* spell, const int* actorSkills, const int* actorAttributes, StoreWrap *store); + +ESM::Skill::SkillEnum mapSchoolToSkill(int school); + +void calcWeakestSchool(const ESM::Spell* spell, + const int* actorSkills, int& effectiveSchool, float& skillTerm, StoreWrap *store); + +float calcAutoCastChance(const ESM::Spell* spell, + const int* actorSkills, const int* actorAttributes, int effectiveSchool, StoreWrap *store); + +} + +#endif diff --git a/components/gameplay/store.hpp b/components/gameplay/store.hpp new file mode 100644 index 000000000..c4d7cba17 --- /dev/null +++ b/components/gameplay/store.hpp @@ -0,0 +1,138 @@ +#ifndef COMPONENTS_GAMEPLAY_STORE_H +#define COMPONENTS_GAMEPLAY_STORE_H + +#include +#include + +namespace Loading +{ + class Listener; +} + +namespace ESM +{ + class ESMWriter; + class ESMReader; + struct Spell; + struct Skill; + struct MagicEffect; +} + +namespace GamePlay +{ + // moved from apps/openmw/mwworld/store.hpp + struct StoreBase + { + virtual ~StoreBase() {} + + virtual void setUp() {} + virtual void listIdentifier(std::vector &list) const {} + + virtual size_t getSize() const = 0; + virtual int getDynamicSize() const { return 0; } + virtual void load(ESM::ESMReader &esm, const std::string &id) = 0; + + virtual bool eraseStatic(const std::string &id) {return false;} + virtual void clearDynamic() {} + + virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const {} + + virtual void read (ESM::ESMReader& reader, const std::string& id) {} + ///< Read into dynamic storage + }; + + // moved from apps/openmw/mwworld/store.hpp + template + class SharedIterator + { + typedef typename std::vector::const_iterator Iter; + + Iter mIter; + + public: + SharedIterator() {} + + SharedIterator(const SharedIterator &orig) + : mIter(orig.mIter) + {} + + SharedIterator(const Iter &iter) + : mIter(iter) + {} + + SharedIterator &operator++() { + ++mIter; + return *this; + } + + SharedIterator operator++(int) { + SharedIterator iter = *this; + ++mIter; + + return iter; + } + + SharedIterator &operator--() { + --mIter; + return *this; + } + + SharedIterator operator--(int) { + SharedIterator iter = *this; + --mIter; + + return iter; + } + + bool operator==(const SharedIterator &x) const { + return mIter == x.mIter; + } + + bool operator!=(const SharedIterator &x) const { + return !(*this == x); + } + + const T &operator*() const { + return **mIter; + } + + const T *operator->() const { + return &(**mIter); + } + }; + + // interface class for sharing the autocalc component between OpenMW and OpenCS + template + class CommonStore : public StoreBase + { + + public: + typedef SharedIterator iterator; + + virtual iterator begin() const = 0; + + virtual iterator end() const = 0; + + virtual const T *find(const std::string &id) const = 0; + }; + + // interface class for sharing the autocalc component between OpenMW and OpenCS + class StoreWrap + { + + public: + StoreWrap() {} + virtual ~StoreWrap() {} + + virtual int findGmstInt(const std::string& gmst) const = 0; + + virtual float findGmstFloat(const std::string& gmst) const = 0; + + virtual const ESM::Skill *findSkill(int index) const = 0; + + virtual const ESM::MagicEffect* findMagicEffect(int id) const = 0; + + virtual const CommonStore& getSpells() const = 0; + }; +} +#endif // COMPONENTS_GAMEPLAY_STORE_H From 18ec0d3e656be71d7e563c1cfe9761a80d0efab3 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Wed, 24 Jun 2015 21:21:35 +1000 Subject: [PATCH 03/16] Add missing files for autocalc. --- apps/opencs/model/world/npcstats.cpp | 138 +++++++++++++++++++++++++++ apps/opencs/model/world/npcstats.hpp | 74 ++++++++++++++ 2 files changed, 212 insertions(+) create mode 100644 apps/opencs/model/world/npcstats.cpp create mode 100644 apps/opencs/model/world/npcstats.hpp diff --git a/apps/opencs/model/world/npcstats.cpp b/apps/opencs/model/world/npcstats.cpp new file mode 100644 index 000000000..da14b473e --- /dev/null +++ b/apps/opencs/model/world/npcstats.cpp @@ -0,0 +1,138 @@ +#include "npcstats.hpp" + +#include +#include + +namespace CSMWorld +{ + NpcStats::NpcStats() : mHealth(0), mMana(0), mFatigue(0) + { + for (int i = 0; i < ESM::Skill::Length; ++i) + mSkill[i] = 0; + } + + NpcStats::NpcStats(const NpcStats &other) + { + for (int i = 0; i < ESM::Attribute::Length; ++i) + mAttr[i] = other.mAttr[i]; + + mSpells = other.mSpells; + + for (int i = 0; i < ESM::Skill::Length; ++i) + mSkill[i] = 0; + + mHealth = other.mHealth; + mMana = other.mMana; + mFatigue = other.mFatigue; + } + + NpcStats::~NpcStats() + {} + + unsigned char NpcStats::getBaseAttribute(int index) const + { + if (index < 0 || index >= ESM::Attribute::Length) + throw std::runtime_error("attrib index out of bounds"); + + return mAttr[index]; + } + + void NpcStats::setAttribute(int index, unsigned char value) + { + if (index < 0 || index >= ESM::Attribute::Length) + throw std::runtime_error("attrib index out of bounds"); + + mAttr[index] = value; + } + + void NpcStats::addSpells(std::string id) + { + struct SpellInfo info; + info.mName = id; + info.mType = ESM::Spell::ST_Spell; // default type from autocalc + info.mFromRace = false; + info.mCost = 0; + info.mChance = 0; + + mSpells.insert(mSpells.begin(), info); + } + + void NpcStats::addPowers(const std::string& id, int type) + { + struct SpellInfo info; + info.mName = id; + info.mType = type; + info.mFromRace = true; + info.mCost = 0; + info.mChance = 0; + + mSpells.push_back(info); + } + + void NpcStats::addCostAndChance(const std::string& id, int cost, int chance) + { + // usually only a few spells, so simply iterate through rather than keeping a separate + // lookup index or map + for (std::vector::iterator it = mSpells.begin(); it != mSpells.end(); ++it) + { + if ((*it).mName == id) + { + (*it).mCost = cost; + (*it).mChance = chance; + return; + } + } + } + + const std::vector& NpcStats::spells() const + { + return mSpells; + } + + unsigned char NpcStats::getBaseSkill(int index) const + { + if (index < 0 || index >= ESM::Skill::Length) + throw std::runtime_error("skill index out of bounds"); + + return mSkill[index]; + } + + void NpcStats::setBaseSkill(int index, unsigned char value) + { + if (index < 0 || index >= ESM::Skill::Length) + throw std::runtime_error("skill index out of bounds"); + + mSkill[index] = value; + } + + unsigned short NpcStats::getHealth() + { + return mHealth; + } + + void NpcStats::setHealth(unsigned short health) + { + mHealth = health; + } + + unsigned short NpcStats::getMana() + { + return mMana; + } + + void NpcStats::setMana(unsigned short mana) + { + mMana = mana; + } + + unsigned short NpcStats::getFatigue() + { + return mFatigue; + } + + void NpcStats::setFatigue(unsigned short fatigue) + { + mFatigue = fatigue; + } +} + diff --git a/apps/opencs/model/world/npcstats.hpp b/apps/opencs/model/world/npcstats.hpp new file mode 100644 index 000000000..a020e0499 --- /dev/null +++ b/apps/opencs/model/world/npcstats.hpp @@ -0,0 +1,74 @@ +#ifndef CSM_WORLD_NPCSTATS_H +#define CSM_WORLD_NPCSTATS_H + +#include + +#include + +#include +#include +#include + +namespace CSMWorld +{ + struct SpellInfo + { + std::string mName; + int mType; + bool mFromRace; + int mCost; + int mChance; + }; + + class NpcStats : public GamePlay::StatsBase + { + + int mAttr[ESM::Attribute::Length]; + std::vector mSpells; + int mSkill[ESM::Skill::Length]; + + unsigned short mHealth; + unsigned short mMana; + unsigned short mFatigue; + + public: + + NpcStats(); + + NpcStats(const NpcStats &other); + + ~NpcStats(); + + virtual unsigned char getBaseAttribute(int index) const; + + virtual void setAttribute(int index, unsigned char value); + + virtual void addSpells(std::string id); + + void addPowers(const std::string& id, int type); + + void addCostAndChance(const std::string& id, int cost, int chance); + + const std::vector& spells() const; + + virtual unsigned char getBaseSkill(int index) const; + + virtual void setBaseSkill(int index, unsigned char value); + + unsigned short getHealth(); + + void setHealth(unsigned short health); + + unsigned short getMana(); + + void setMana(unsigned short mana); + + unsigned short getFatigue(); + + void setFatigue(unsigned short fatigue); + }; +} + +Q_DECLARE_METATYPE(CSMWorld::NpcStats*); + +#endif // CSM_WORLD_NPCSTATS_H From 91b9ad399fbd0571fbd03b6779de4aed851703e3 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Wed, 24 Jun 2015 21:38:38 +1000 Subject: [PATCH 04/16] Include for fmod() and floor() --- components/gameplay/autocalc.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/gameplay/autocalc.cpp b/components/gameplay/autocalc.cpp index a1888c434..fbfc4a020 100644 --- a/components/gameplay/autocalc.cpp +++ b/components/gameplay/autocalc.cpp @@ -1,5 +1,7 @@ #include "autocalc.hpp" +#include + #include #include #include From 0cf98320179f0a6324b91a5c954ddb46f499bf22 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Wed, 24 Jun 2015 21:58:28 +1000 Subject: [PATCH 05/16] Add missing files for autocalc. Remove c++11 dependency. --- apps/opencs/model/world/npcstats.hpp | 2 +- apps/opencs/model/world/refidadapterimp.cpp | 6 ++++ apps/opencs/model/world/usertype.hpp | 4 +-- apps/openmw/mwworld/mwstore.cpp | 36 +++++++++++++++++++++ apps/openmw/mwworld/mwstore.hpp | 34 +++++++++++++++++++ 5 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 apps/openmw/mwworld/mwstore.cpp create mode 100644 apps/openmw/mwworld/mwstore.hpp diff --git a/apps/opencs/model/world/npcstats.hpp b/apps/opencs/model/world/npcstats.hpp index a020e0499..a5287d2b4 100644 --- a/apps/opencs/model/world/npcstats.hpp +++ b/apps/opencs/model/world/npcstats.hpp @@ -69,6 +69,6 @@ namespace CSMWorld }; } -Q_DECLARE_METATYPE(CSMWorld::NpcStats*); +Q_DECLARE_METATYPE(CSMWorld::NpcStats*) #endif // CSM_WORLD_NPCSTATS_H diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index 085c77ca3..56c4835f8 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -1122,6 +1122,7 @@ void CSMWorld::WeaponRefIdAdapter::setData (const RefIdColumn *column, RefIdData } } +template <> void CSMWorld::NestedSpellRefIdAdapter::addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const { @@ -1145,6 +1146,7 @@ void CSMWorld::NestedSpellRefIdAdapter::addNestedRow (const RefIdColum record.setModified (caster); } +template <> void CSMWorld::NestedSpellRefIdAdapter::removeNestedRow (const RefIdColumn *column, RefIdData& data, int index, int rowToRemove) const { @@ -1175,6 +1177,7 @@ void CSMWorld::NestedSpellRefIdAdapter::removeNestedRow (const RefIdCo record.setModified (caster); } +template <> void CSMWorld::NestedSpellRefIdAdapter::setNestedData (const RefIdColumn *column, RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const { @@ -1207,6 +1210,7 @@ void CSMWorld::NestedSpellRefIdAdapter::setNestedData (const RefIdColu record.setModified (caster); } +template <> QVariant CSMWorld::NestedSpellRefIdAdapter::getNestedData (const RefIdColumn *column, const RefIdData& data, int index, int subRowIndex, int subColIndex) const { @@ -1230,12 +1234,14 @@ QVariant CSMWorld::NestedSpellRefIdAdapter::getNestedData (const RefId } } +template <> int CSMWorld::NestedSpellRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const { return 5; } +template <> int CSMWorld::NestedSpellRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const { const Record& record = diff --git a/apps/opencs/model/world/usertype.hpp b/apps/opencs/model/world/usertype.hpp index e0b3c2e2f..23b5c90a4 100644 --- a/apps/opencs/model/world/usertype.hpp +++ b/apps/opencs/model/world/usertype.hpp @@ -38,7 +38,7 @@ namespace CSMWorld }; } -Q_DECLARE_METATYPE(CSMWorld::UserInt); -Q_DECLARE_METATYPE(CSMWorld::UserFloat); +Q_DECLARE_METATYPE(CSMWorld::UserInt) +Q_DECLARE_METATYPE(CSMWorld::UserFloat) #endif // CSM_WORLD_USERTYPE_H diff --git a/apps/openmw/mwworld/mwstore.cpp b/apps/openmw/mwworld/mwstore.cpp new file mode 100644 index 000000000..02c7106b6 --- /dev/null +++ b/apps/openmw/mwworld/mwstore.cpp @@ -0,0 +1,36 @@ +#include "mwstore.hpp" + +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" + +#include "esmstore.hpp" + +namespace MWWorld +{ + MWStore::MWStore() + : mGmst(MWBase::Environment::get().getWorld()->getStore().get()), + mSpells(MWBase::Environment::get().getWorld()->getStore().get()) + { } + + MWStore::~MWStore() + { } + + int MWStore::findGmstInt(const std::string& name) const { return mGmst.find(name)->getInt(); } + + float MWStore::findGmstFloat(const std::string& name) const { return mGmst.find(name)->getFloat(); } + + const ESM::Skill *MWStore::findSkill(int index) const + { + return MWBase::Environment::get().getWorld()->getStore().get().find(index); + } + + const ESM::MagicEffect* MWStore::findMagicEffect(int id) const + { + return MWBase::Environment::get().getWorld()->getStore().get().find(id); + } + + const GamePlay::CommonStore& MWStore::getSpells() const + { + return MWBase::Environment::get().getWorld()->getStore().get(); + } +} diff --git a/apps/openmw/mwworld/mwstore.hpp b/apps/openmw/mwworld/mwstore.hpp new file mode 100644 index 000000000..c616e4e1d --- /dev/null +++ b/apps/openmw/mwworld/mwstore.hpp @@ -0,0 +1,34 @@ +#ifndef GAME_MWWORLD_MWSTORE_H +#define GAME_MWWORLD_MWSTORE_H + +#include + +#include + +#include "store.hpp" + +namespace MWWorld +{ + class MWStore : public GamePlay::StoreWrap + { + const MWWorld::Store& mGmst; + const MWWorld::Store &mSpells; + + public: + + MWStore(); + ~MWStore(); + + virtual int findGmstInt(const std::string& name) const; + + virtual float findGmstFloat(const std::string& name) const; + + virtual const ESM::Skill *findSkill(int index) const; + + virtual const ESM::MagicEffect* findMagicEffect(int id) const; + + virtual const GamePlay::CommonStore& getSpells() const; + }; +} + +#endif // GAME_MWWORLD_MWSTORE_H From e5e4a04f8b3cf77147ab81dceb7947a821520160 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Wed, 24 Jun 2015 22:19:27 +1000 Subject: [PATCH 06/16] Remove more c++11 dependencies. --- apps/opencs/model/world/refidadapterimp.cpp | 33 ++++++++++++--------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index 56c4835f8..99ca76d73 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -1122,8 +1122,11 @@ void CSMWorld::WeaponRefIdAdapter::setData (const RefIdColumn *column, RefIdData } } +namespace CSMWorld +{ + template <> -void CSMWorld::NestedSpellRefIdAdapter::addNestedRow (const RefIdColumn *column, +void NestedSpellRefIdAdapter::addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const { Record& record = @@ -1147,7 +1150,7 @@ void CSMWorld::NestedSpellRefIdAdapter::addNestedRow (const RefIdColum } template <> -void CSMWorld::NestedSpellRefIdAdapter::removeNestedRow (const RefIdColumn *column, +void NestedSpellRefIdAdapter::removeNestedRow (const RefIdColumn *column, RefIdData& data, int index, int rowToRemove) const { Record& record = @@ -1178,7 +1181,7 @@ void CSMWorld::NestedSpellRefIdAdapter::removeNestedRow (const RefIdCo } template <> -void CSMWorld::NestedSpellRefIdAdapter::setNestedData (const RefIdColumn *column, +void NestedSpellRefIdAdapter::setNestedData (const RefIdColumn *column, RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const { Record& record = @@ -1211,13 +1214,13 @@ void CSMWorld::NestedSpellRefIdAdapter::setNestedData (const RefIdColu } template <> -QVariant CSMWorld::NestedSpellRefIdAdapter::getNestedData (const RefIdColumn *column, +QVariant NestedSpellRefIdAdapter::getNestedData (const RefIdColumn *column, const RefIdData& data, int index, int subRowIndex, int subColIndex) const { const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); - const std::vector& spells = mData.npcAutoCalculate(record.get())->spells(); + const std::vector& spells = mData.npcAutoCalculate(record.get())->spells(); if (subRowIndex < 0 || subRowIndex >= static_cast (spells.size())) throw std::runtime_error ("index out of range"); @@ -1235,24 +1238,24 @@ QVariant CSMWorld::NestedSpellRefIdAdapter::getNestedData (const RefId } template <> -int CSMWorld::NestedSpellRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, +int NestedSpellRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const { return 5; } template <> -int CSMWorld::NestedSpellRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const +int NestedSpellRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const { const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); - const std::vector spells = mData.npcAutoCalculate(record.get())->spells(); + const std::vector spells = mData.npcAutoCalculate(record.get())->spells(); return static_cast(spells.size()); } template <> -void CSMWorld::NestedSpellRefIdAdapter::addNestedRow (const RefIdColumn *column, +void NestedSpellRefIdAdapter::addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const { Record& record = @@ -1272,7 +1275,7 @@ void CSMWorld::NestedSpellRefIdAdapter::addNestedRow (const RefId } template <> -void CSMWorld::NestedSpellRefIdAdapter::removeNestedRow (const RefIdColumn *column, +void NestedSpellRefIdAdapter::removeNestedRow (const RefIdColumn *column, RefIdData& data, int index, int rowToRemove) const { Record& record = @@ -1290,7 +1293,7 @@ void CSMWorld::NestedSpellRefIdAdapter::removeNestedRow (const Re } template <> -void CSMWorld::NestedSpellRefIdAdapter::setNestedData (const RefIdColumn *column, +void NestedSpellRefIdAdapter::setNestedData (const RefIdColumn *column, RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const { Record& record = @@ -1310,7 +1313,7 @@ void CSMWorld::NestedSpellRefIdAdapter::setNestedData (const RefI } template<> -QVariant CSMWorld::NestedSpellRefIdAdapter::getNestedData (const RefIdColumn *column, +QVariant NestedSpellRefIdAdapter::getNestedData (const RefIdColumn *column, const RefIdData& data, int index, int subRowIndex, int subColIndex) const { const Record& record = @@ -1337,17 +1340,19 @@ QVariant CSMWorld::NestedSpellRefIdAdapter::getNestedData (const } template <> -int CSMWorld::NestedSpellRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, +int NestedSpellRefIdAdapter::getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const { return 2; } template <> -int CSMWorld::NestedSpellRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const +int NestedSpellRefIdAdapter::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const { const Record& record = static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); return static_cast(record.get().mSpells.mList.size()); } + +} From 7af43a115584fa173b696ba6f1d8dd5f843b19c4 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Thu, 25 Jun 2015 13:32:22 +1000 Subject: [PATCH 07/16] Address review feedback. --- apps/opencs/model/world/data.cpp | 81 +++------- apps/opencs/model/world/npcstats.hpp | 4 +- apps/openmw/mwclass/npc.cpp | 35 ++--- apps/openmw/mwgui/levelupdialog.cpp | 4 +- .../mwmechanics/mechanicsmanagerimp.cpp | 6 +- apps/openmw/mwworld/esmstore.cpp | 4 +- apps/openmw/mwworld/esmstore.hpp | 6 +- apps/openmw/mwworld/mwstore.cpp | 4 +- apps/openmw/mwworld/mwstore.hpp | 6 +- apps/openmw/mwworld/store.hpp | 100 +++++++++++-- components/CMakeLists.txt | 22 +-- .../{gameplay => autocalc}/autocalc.cpp | 8 +- .../{gameplay => autocalc}/autocalc.hpp | 14 +- .../{gameplay => autocalc}/autocalcspell.cpp | 28 ++-- .../{gameplay => autocalc}/autocalcspell.hpp | 19 +-- components/autocalc/store.hpp | 42 ++++++ components/gameplay/store.hpp | 138 ------------------ 17 files changed, 236 insertions(+), 285 deletions(-) rename components/{gameplay => autocalc}/autocalc.cpp (98%) rename components/{gameplay => autocalc}/autocalc.hpp (79%) rename components/{gameplay => autocalc}/autocalcspell.cpp (92%) rename components/{gameplay => autocalc}/autocalcspell.hpp (61%) create mode 100644 components/autocalc/store.hpp delete mode 100644 components/gameplay/store.hpp diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 5b4f43ad3..b789bd28f 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -11,9 +11,9 @@ #include #include -#include -#include -#include +#include +#include +#include #include "idtable.hpp" #include "idtree.hpp" @@ -27,15 +27,21 @@ namespace { - class SpellStore : public GamePlay::CommonStore + class CSStore : public AutoCalc::StoreCommon { + const CSMWorld::IdCollection& mGmstTable; + const CSMWorld::IdCollection& mSkillTable; + const CSMWorld::IdCollection& mMagicEffectTable; const CSMWorld::NestedIdCollection& mSpells; std::vector mLocal; public: - SpellStore(const CSMWorld::NestedIdCollection& spells) - : mSpells(spells) + CSStore(const CSMWorld::IdCollection& gmst, + const CSMWorld::IdCollection& skills, + const CSMWorld::IdCollection& magicEffects, + const CSMWorld::NestedIdCollection& spells) + : mGmstTable(gmst), mSkillTable(skills), mMagicEffectTable(magicEffects), mSpells(spells) { // prepare data in a format used by OpenMW store for (int index = 0; index < mSpells.getSize(); ++index) @@ -44,53 +50,6 @@ namespace mLocal.push_back(spell); } } - - ~SpellStore() {} - - typedef GamePlay::SharedIterator iterator; - - virtual iterator begin() const - { - return mLocal.begin(); - } - - virtual iterator end() const - { - return mLocal.end(); - } - - virtual const ESM::Spell *find(const std::string &id) const - { - return &mSpells.getRecord(id).get(); - } - - virtual size_t getSize() const - { - return mSpells.getSize(); - } - - private: - // not used in OpenCS - virtual void load(ESM::ESMReader &esm, const std::string &id) - { - } - }; - - class CSStore : public GamePlay::StoreWrap - { - const CSMWorld::IdCollection& mGmstTable; - const CSMWorld::IdCollection& mSkillTable; - const CSMWorld::IdCollection& mMagicEffectTable; - const SpellStore mSpellStore; - - public: - - CSStore(const CSMWorld::IdCollection& gmst, - const CSMWorld::IdCollection& skills, - const CSMWorld::IdCollection& magicEffects, - const CSMWorld::NestedIdCollection& spells) - : mGmstTable(gmst), mSkillTable(skills), mMagicEffectTable(magicEffects), mSpellStore(spells) - { } ~CSStore() {} virtual int findGmstInt(const std::string& name) const @@ -115,18 +74,18 @@ namespace return &mMagicEffectTable.getRecord(ESM::MagicEffect::indexToId((short)id)).get(); } - virtual const GamePlay::CommonStore& getSpells() const + virtual const std::vector& getSpells() const { - return mSpellStore; + return mLocal; } }; - unsigned short autoCalculateMana(GamePlay::StatsBase& stats) + unsigned short autoCalculateMana(AutoCalc::StatsBase& stats) { return stats.getBaseAttribute(ESM::Attribute::Intelligence) * 2; } - unsigned short autoCalculateFatigue(GamePlay::StatsBase& stats) + unsigned short autoCalculateFatigue(AutoCalc::StatsBase& stats) { return stats.getBaseAttribute(ESM::Attribute::Strength) + stats.getBaseAttribute(ESM::Attribute::Willpower) @@ -1579,15 +1538,15 @@ CSMWorld::NpcStats* CSMWorld::Data::npcAutoCalculate(const ESM::NPC& npc) const if (autoCalc) { - GamePlay::autoCalcAttributesImpl (&npc, race, class_, level, *stats, &store); + AutoCalc::autoCalcAttributesImpl (&npc, race, class_, level, *stats, &store); stats->setHealth(autoCalculateHealth(level, class_, *stats)); stats->setMana(autoCalculateMana(*stats)); stats->setFatigue(autoCalculateFatigue(*stats)); - GamePlay::autoCalcSkillsImpl(&npc, race, class_, level, *stats, &store); + AutoCalc::autoCalcSkillsImpl(&npc, race, class_, level, *stats, &store); - GamePlay::autoCalculateSpells(race, *stats, &store); + AutoCalc::autoCalculateSpells(race, *stats, &store); } else { @@ -1645,7 +1604,7 @@ CSMWorld::NpcStats* CSMWorld::Data::npcAutoCalculate(const ESM::NPC& npc) const int school; float skillTerm; - GamePlay::calcWeakestSchool(spell, skills, school, skillTerm, &store); + AutoCalc::calcWeakestSchool(spell, skills, school, skillTerm, &store); float chance = calcAutoCastChance(spell, skills, attributes, school, &store); stats->addCostAndChance((*it).mName, cost, (int)ceil(chance)); // percent diff --git a/apps/opencs/model/world/npcstats.hpp b/apps/opencs/model/world/npcstats.hpp index a5287d2b4..4d9be149f 100644 --- a/apps/opencs/model/world/npcstats.hpp +++ b/apps/opencs/model/world/npcstats.hpp @@ -7,7 +7,7 @@ #include #include -#include +#include namespace CSMWorld { @@ -20,7 +20,7 @@ namespace CSMWorld int mChance; }; - class NpcStats : public GamePlay::StatsBase + class NpcStats : public AutoCalc::StatsBase { int mAttr[ESM::Attribute::Length]; diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 4f419c635..64ed868a1 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -11,9 +11,9 @@ #include #include -#include -#include -#include +#include +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -62,28 +62,26 @@ namespace return new NpcCustomData (*this); } - class Stats : public GamePlay::StatsBase + class Stats : public AutoCalc::StatsBase { - MWMechanics::CreatureStats& mCreatureStats; MWMechanics::NpcStats& mNpcStats; public: - Stats(MWMechanics::CreatureStats& creatureStats, MWMechanics::NpcStats& npcStats) - : mCreatureStats(creatureStats), mNpcStats(npcStats) {} + Stats(MWMechanics::NpcStats& npcStats) : mNpcStats(npcStats) {} - virtual unsigned char getBaseAttribute(int index) const { return mCreatureStats.getAttribute(index).getBase(); } + virtual unsigned char getBaseAttribute(int index) const { return mNpcStats.getAttribute(index).getBase(); } - virtual void setAttribute(int index, unsigned char value) { mCreatureStats.setAttribute(index, value); } + virtual void setAttribute(int index, unsigned char value) { mNpcStats.setAttribute(index, value); } - virtual void addSpells(std::string id) { mCreatureStats.getSpells().add(id); } + virtual void addSpells(std::string id) { mNpcStats.getSpells().add(id); } virtual unsigned char getBaseSkill(int index) const { return mNpcStats.getSkill(index).getBase(); } virtual void setBaseSkill(int index, unsigned char value) { mNpcStats.getSkill(index).setBase(value); } }; - void autoCalculateAttributes (const ESM::NPC* npc, MWMechanics::CreatureStats& creatureStats) + void autoCalculateAttributes (const ESM::NPC* npc, MWMechanics::NpcStats& npcStats) { const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get().find(npc->mRace); @@ -91,16 +89,15 @@ namespace const ESM::Class *class_ = MWBase::Environment::get().getWorld()->getStore().get().find(npc->mClass); - int level = creatureStats.getLevel(); + int level = npcStats.getLevel(); - MWMechanics::NpcStats dummy; // npc stats are needed for skills, which is not calculated here - Stats stats(creatureStats, dummy); + Stats stats(npcStats); MWWorld::MWStore store; - GamePlay::autoCalcAttributesImpl (npc, race, class_, level, stats, &store); + AutoCalc::autoCalcAttributesImpl (npc, race, class_, level, stats, &store); - creatureStats.setHealth(GamePlay::autoCalculateHealth(level, class_, stats)); + npcStats.setHealth(AutoCalc::autoCalculateHealth(level, class_, stats)); } void autoCalculateSkills(const ESM::NPC* npc, MWMechanics::NpcStats& npcStats, const MWWorld::Ptr& ptr) @@ -112,13 +109,13 @@ namespace const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get().find(npc->mRace); - Stats stats(npcStats, npcStats); + Stats stats(npcStats); MWWorld::MWStore store; - GamePlay::autoCalcSkillsImpl(npc, race, class_, level, stats, &store); + AutoCalc::autoCalcSkillsImpl(npc, race, class_, level, stats, &store); - GamePlay::autoCalculateSpells(race, stats, &store); + AutoCalc::autoCalculateSpells(race, stats, &store); } } diff --git a/apps/openmw/mwgui/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp index c6954d91d..c392372ff 100644 --- a/apps/openmw/mwgui/levelupdialog.cpp +++ b/apps/openmw/mwgui/levelupdialog.cpp @@ -4,8 +4,6 @@ #include #include -#include - #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -142,7 +140,7 @@ namespace MWGui if(world->getStore().get().isDynamic(cls->mId)) { // Choosing Stealth specialization and Speed/Agility as attributes, if possible. Otherwise fall back to first class found. - GamePlay::SharedIterator it = world->getStore().get().begin(); + MWWorld::SharedIterator it = world->getStore().get().begin(); for(; it != world->getStore().get().end(); ++it) { if(it->mData.mIsPlayable && it->mData.mSpecialization == 2 && it->mData.mAttribute[0] == 4 && it->mData.mAttribute[1] == 3) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 5d61bc1d2..dc388555e 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -6,7 +6,7 @@ #include -#include +#include #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" @@ -256,10 +256,10 @@ namespace MWMechanics static const float fAutoPCSpellChance = esmStore.get().find("fAutoPCSpellChance")->getFloat(); MWWorld::MWStore store; - if (GamePlay::calcAutoCastChance(spell, skills, attributes, -1, &store) < fAutoPCSpellChance) + if (AutoCalc::calcAutoCastChance(spell, skills, attributes, -1, &store) < fAutoPCSpellChance) continue; - if (!GamePlay::attrSkillCheck(spell, skills, attributes, &store)) + if (!AutoCalc::attrSkillCheck(spell, skills, attributes, &store)) continue; selectedSpells.push_back(spell->mId); diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 61518548b..5d9beecb6 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -66,7 +66,7 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) esm.getRecHeader(); // Look up the record type. - std::map::iterator it = mStores.find(n.val); + std::map::iterator it = mStores.find(n.val); if (it == mStores.end()) { if (n.val == ESM::REC_INFO) { @@ -130,7 +130,7 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) void ESMStore::setUp() { - std::map::iterator it = mStores.begin(); + std::map::iterator it = mStores.begin(); for (; it != mStores.end(); ++it) { it->second->setUp(); } diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 61d361b3e..05b633956 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -67,7 +67,7 @@ namespace MWWorld // Lookup of all IDs. Makes looking up references faster. Just // maps the id name to the record type. std::map mIds; - std::map mStores; + std::map mStores; ESM::NPC mPlayerTemplate; @@ -75,7 +75,7 @@ namespace MWWorld public: /// \todo replace with SharedIterator - typedef std::map::const_iterator iterator; + typedef std::map::const_iterator iterator; iterator begin() const { return mStores.begin(); @@ -144,7 +144,7 @@ namespace MWWorld void clearDynamic () { - for (std::map::iterator it = mStores.begin(); it != mStores.end(); ++it) + for (std::map::iterator it = mStores.begin(); it != mStores.end(); ++it) it->second->clearDynamic(); mNpcs.insert(mPlayerTemplate); diff --git a/apps/openmw/mwworld/mwstore.cpp b/apps/openmw/mwworld/mwstore.cpp index 02c7106b6..8ebe91cd9 100644 --- a/apps/openmw/mwworld/mwstore.cpp +++ b/apps/openmw/mwworld/mwstore.cpp @@ -29,8 +29,8 @@ namespace MWWorld return MWBase::Environment::get().getWorld()->getStore().get().find(id); } - const GamePlay::CommonStore& MWStore::getSpells() const + const std::vector& MWStore::getSpells() const { - return MWBase::Environment::get().getWorld()->getStore().get(); + return MWBase::Environment::get().getWorld()->getStore().get().getShared(); } } diff --git a/apps/openmw/mwworld/mwstore.hpp b/apps/openmw/mwworld/mwstore.hpp index c616e4e1d..c43c58931 100644 --- a/apps/openmw/mwworld/mwstore.hpp +++ b/apps/openmw/mwworld/mwstore.hpp @@ -3,13 +3,13 @@ #include -#include +#include #include "store.hpp" namespace MWWorld { - class MWStore : public GamePlay::StoreWrap + class MWStore : public AutoCalc::StoreCommon { const MWWorld::Store& mGmst; const MWWorld::Store &mSpells; @@ -27,7 +27,7 @@ namespace MWWorld virtual const ESM::MagicEffect* findMagicEffect(int id) const; - virtual const GamePlay::CommonStore& getSpells() const; + virtual const std::vector& getSpells() const; }; } diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 852092a9a..c86a92f7a 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -10,7 +10,6 @@ #include #include -#include #include @@ -18,10 +17,89 @@ namespace MWWorld { + struct StoreBase + { + virtual ~StoreBase() {} + + virtual void setUp() {} + virtual void listIdentifier(std::vector &list) const {} + + virtual size_t getSize() const = 0; + virtual int getDynamicSize() const { return 0; } + virtual void load(ESM::ESMReader &esm, const std::string &id) = 0; + + virtual bool eraseStatic(const std::string &id) {return false;} + virtual void clearDynamic() {} + + virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const {} + + virtual void read (ESM::ESMReader& reader, const std::string& id) {} + ///< Read into dynamic storage + }; + + template + class SharedIterator + { + typedef typename std::vector::const_iterator Iter; + + Iter mIter; + + public: + SharedIterator() {} + + SharedIterator(const SharedIterator &orig) + : mIter(orig.mIter) + {} + + SharedIterator(const Iter &iter) + : mIter(iter) + {} + + SharedIterator &operator++() { + ++mIter; + return *this; + } + + SharedIterator operator++(int) { + SharedIterator iter = *this; + ++mIter; + + return iter; + } + + SharedIterator &operator--() { + --mIter; + return *this; + } + + SharedIterator operator--(int) { + SharedIterator iter = *this; + --mIter; + + return iter; + } + + bool operator==(const SharedIterator &x) const { + return mIter == x.mIter; + } + + bool operator!=(const SharedIterator &x) const { + return !(*this == x); + } + + const T &operator*() const { + return **mIter; + } + + const T *operator->() const { + return &(**mIter); + } + }; + class ESMStore; template - class Store : public GamePlay::CommonStore + class Store : public StoreBase { std::map mStatic; std::vector mShared; // Preserves the record order as it came from the content files (this @@ -59,7 +137,7 @@ namespace MWWorld : mStatic(orig.mData) {} - typedef GamePlay::SharedIterator iterator; + typedef SharedIterator iterator; // setUp needs to be called again after virtual void clearDynamic() @@ -156,6 +234,10 @@ namespace MWWorld return mShared.size(); } + const std::vector& getShared() const { + return mShared; + } + int getDynamicSize() const { return static_cast (mDynamic.size()); // truncated from unsigned __int64 if _MSC_VER && _WIN64 @@ -302,7 +384,7 @@ namespace MWWorld } template <> - class Store : public GamePlay::StoreBase + class Store : public StoreBase { // For multiple ESM/ESP files we need one list per file. typedef std::vector LandTextureList; @@ -379,7 +461,7 @@ namespace MWWorld }; template <> - class Store : public GamePlay::StoreBase + class Store : public StoreBase { std::vector mStatic; @@ -394,7 +476,7 @@ namespace MWWorld }; public: - typedef GamePlay::SharedIterator iterator; + typedef SharedIterator iterator; virtual ~Store() { @@ -468,7 +550,7 @@ namespace MWWorld }; template <> - class Store : public GamePlay::StoreBase + class Store : public StoreBase { struct DynamicExtCmp { @@ -508,7 +590,7 @@ namespace MWWorld void handleMovedCellRefs(ESM::ESMReader& esm, ESM::Cell* cell); public: - typedef GamePlay::SharedIterator iterator; + typedef SharedIterator iterator; const ESM::Cell *search(const std::string &id) const { ESM::Cell cell; @@ -756,7 +838,7 @@ namespace MWWorld }; template <> - class Store : public GamePlay::StoreBase + class Store : public StoreBase { private: typedef std::map Interior; diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 8c37c8ef3..2938dcd81 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -6,16 +6,16 @@ set (VERSION_HPP ${CMAKE_CURRENT_SOURCE_DIR}/version/version.hpp) if (GIT_CHECKOUT) add_custom_target (git-version COMMAND ${CMAKE_COMMAND} - -DGIT_EXECUTABLE=${GIT_EXECUTABLE} + -DGIT_EXECUTABLE=${GIT_EXECUTABLE} -DPROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR} - -DVERSION_HPP_IN=${VERSION_HPP_IN} - -DVERSION_HPP=${VERSION_HPP} - -DOPENMW_VERSION_MAJOR=${OPENMW_VERSION_MAJOR} - -DOPENMW_VERSION_MINOR=${OPENMW_VERSION_MINOR} - -DOPENMW_VERSION_RELEASE=${OPENMW_VERSION_RELEASE} - -DOPENMW_VERSION=${OPENMW_VERSION} + -DVERSION_HPP_IN=${VERSION_HPP_IN} + -DVERSION_HPP=${VERSION_HPP} + -DOPENMW_VERSION_MAJOR=${OPENMW_VERSION_MAJOR} + -DOPENMW_VERSION_MINOR=${OPENMW_VERSION_MINOR} + -DOPENMW_VERSION_RELEASE=${OPENMW_VERSION_RELEASE} + -DOPENMW_VERSION=${OPENMW_VERSION} -P ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/GitVersion.cmake - VERBATIM) + VERBATIM) else (GIT_CHECKOUT) configure_file(${VERSION_HPP_IN} ${VERSION_HPP}) endif (GIT_CHECKOUT) @@ -119,7 +119,7 @@ add_component_dir (fontloader fontloader ) -add_component_dir (gameplay +add_component_dir (autocalc autocalc autocalcspell ) @@ -165,14 +165,14 @@ include_directories(${BULLET_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) add_library(components STATIC ${COMPONENT_FILES} ${MOC_SRCS} ${ESM_UI_HDR}) -target_link_libraries(components +target_link_libraries(components ${Boost_SYSTEM_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_THREAD_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_WAVE_LIBRARY} ${OGRE_LIBRARIES} - ${OENGINE_LIBRARY} + ${OENGINE_LIBRARY} ${BULLET_LIBRARIES} ) diff --git a/components/gameplay/autocalc.cpp b/components/autocalc/autocalc.cpp similarity index 98% rename from components/gameplay/autocalc.cpp rename to components/autocalc/autocalc.cpp index fbfc4a020..78cd549ca 100644 --- a/components/gameplay/autocalc.cpp +++ b/components/autocalc/autocalc.cpp @@ -37,10 +37,10 @@ namespace } } -namespace GamePlay +namespace AutoCalc { void autoCalcAttributesImpl (const ESM::NPC* npc, - const ESM::Race *race, const ESM::Class *class_, int level, StatsBase& stats, StoreWrap *store) + const ESM::Race *race, const ESM::Class *class_, int level, StatsBase& stats, StoreCommon *store) { // race bonus bool male = (npc->mFlags & ESM::NPC::Female) == 0; @@ -106,7 +106,7 @@ namespace GamePlay * and by adding class, race, specialization bonus. */ void autoCalcSkillsImpl (const ESM::NPC* npc, - const ESM::Race *race, const ESM::Class *class_, int level, StatsBase& stats, StoreWrap *store) + const ESM::Race *race, const ESM::Class *class_, int level, StatsBase& stats, StoreCommon *store) { for (int i = 0; i < 2; ++i) { @@ -188,7 +188,7 @@ namespace GamePlay return static_cast(floor(0.5f * (strength + endurance)) + multiplier * (level-1)); } - void autoCalculateSpells(const ESM::Race *race, StatsBase& stats, StoreWrap *store) + void autoCalculateSpells(const ESM::Race *race, StatsBase& stats, StoreCommon *store) { int skills[ESM::Skill::Length]; for (int i=0; i @@ -12,7 +12,7 @@ namespace ESM struct Class; } -namespace GamePlay +namespace AutoCalc { // wrapper class for sharing the autocalc code between OpenMW and OpenCS class StatsBase @@ -35,13 +35,13 @@ namespace GamePlay }; void autoCalcAttributesImpl (const ESM::NPC* npc, - const ESM::Race *race, const ESM::Class *class_, int level, StatsBase& stats, StoreWrap *store); + const ESM::Race *race, const ESM::Class *class_, int level, StatsBase& stats, StoreCommon *store); void autoCalcSkillsImpl (const ESM::NPC* npc, - const ESM::Race *race, const ESM::Class *class_, int level, StatsBase& stats, StoreWrap *store); + const ESM::Race *race, const ESM::Class *class_, int level, StatsBase& stats, StoreCommon *store); unsigned short autoCalculateHealth(int level, const ESM::Class *class_, StatsBase& stats); - void autoCalculateSpells(const ESM::Race *race, StatsBase& stats, StoreWrap *store); + void autoCalculateSpells(const ESM::Race *race, StatsBase& stats, StoreCommon *store); } -#endif // COMPONENTS_GAMEPLAY_AUTOCALC_H +#endif // COMPONENTS_AUTOCALC_AUTOCALC_H diff --git a/components/gameplay/autocalcspell.cpp b/components/autocalc/autocalcspell.cpp similarity index 92% rename from components/gameplay/autocalcspell.cpp rename to components/autocalc/autocalcspell.cpp index 8f2843367..8f2883e1b 100644 --- a/components/gameplay/autocalcspell.cpp +++ b/components/autocalc/autocalcspell.cpp @@ -14,7 +14,7 @@ #include "autocalc.hpp" // Most of the code in this file was moved from apps/openmw/mwmechanics/autocalcspell.cpp -namespace GamePlay +namespace AutoCalc { struct SchoolCaps @@ -27,7 +27,7 @@ namespace GamePlay }; std::vector autoCalcNpcSpells(const int *actorSkills, - const int *actorAttributes, const ESM::Race* race, StoreWrap *store) + const int *actorAttributes, const ESM::Race* race, StoreCommon *store) { static const float fNPCbaseMagickaMult = store->findGmstFloat("fNPCbaseMagickaMult"); float baseMagicka = fNPCbaseMagickaMult * actorAttributes[ESM::Attribute::Intelligence]; @@ -61,13 +61,13 @@ namespace GamePlay std::vector selectedSpells; - const CommonStore &spells = store->getSpells(); + const std::vector& spells = store->getSpells(); // Note: the algorithm heavily depends on the traversal order of the spells. For vanilla-compatible results the // Store must preserve the record ordering as it was in the content files. - for (CommonStore::iterator iter = spells.begin(); iter != spells.end(); ++iter) + for (std::vector::const_iterator iter = spells.begin(); iter != spells.end(); ++iter) { - const ESM::Spell* spell = &*iter; + ESM::Spell* spell = *iter; if (spell->mData.mType != ESM::Spell::ST_Spell) continue; @@ -107,7 +107,17 @@ namespace GamePlay cap.mMinCost = INT_MAX; for (std::vector::iterator weakIt = selectedSpells.begin(); weakIt != selectedSpells.end(); ++weakIt) { - const ESM::Spell* testSpell = spells.find(*weakIt); + std::vector::const_iterator it = spells.begin(); + for (; it != spells.end(); ++it) + { + if ((*it)->mId == *weakIt) + break; + } + + if (it == spells.end()) + continue; + + const ESM::Spell* testSpell = *it; //int testSchool; //float dummySkillTerm; @@ -146,7 +156,7 @@ namespace GamePlay } bool attrSkillCheck (const ESM::Spell* spell, - const int* actorSkills, const int* actorAttributes, StoreWrap *store) + const int* actorSkills, const int* actorAttributes, StoreCommon *store) { const std::vector& effects = spell->mEffects.mList; for (std::vector::const_iterator effectIt = effects.begin(); effectIt != effects.end(); ++effectIt) @@ -186,7 +196,7 @@ namespace GamePlay } void calcWeakestSchool (const ESM::Spell* spell, - const int* actorSkills, int& effectiveSchool, float& skillTerm, StoreWrap *store) + const int* actorSkills, int& effectiveSchool, float& skillTerm, StoreCommon *store) { float minChance = FLT_MAX; @@ -220,7 +230,7 @@ namespace GamePlay } float calcAutoCastChance(const ESM::Spell *spell, - const int *actorSkills, const int *actorAttributes, int effectiveSchool, StoreWrap *store) + const int *actorSkills, const int *actorAttributes, int effectiveSchool, StoreCommon *store) { if (spell->mData.mType != ESM::Spell::ST_Spell) return 100.f; diff --git a/components/gameplay/autocalcspell.hpp b/components/autocalc/autocalcspell.hpp similarity index 61% rename from components/gameplay/autocalcspell.hpp rename to components/autocalc/autocalcspell.hpp index ceb9f197b..769e60248 100644 --- a/components/gameplay/autocalcspell.hpp +++ b/components/autocalc/autocalcspell.hpp @@ -1,5 +1,5 @@ -#ifndef COMPONENTS_GAMEPLAY_AUTOCALCSPELL_H -#define COMPONENTS_GAMEPLAY_AUTOCALCSPELL_H +#ifndef COMPONENTS_AUTOCALC_AUTOCALCSPELL_H +#define COMPONENTS_AUTOCALC_AUTOCALCSPELL_H #include @@ -11,28 +11,29 @@ namespace ESM struct Race; } -namespace GamePlay +namespace AutoCalc { -class StoreWrap; +class StoreCommon; /// Contains algorithm for calculating an NPC's spells based on stats std::vector autoCalcNpcSpells(const int* actorSkills, - const int* actorAttributes, const ESM::Race* race, StoreWrap *store); + const int* actorAttributes, const ESM::Race* race, StoreCommon *store); // Helpers -bool attrSkillCheck (const ESM::Spell* spell, const int* actorSkills, const int* actorAttributes, StoreWrap *store); +bool attrSkillCheck (const ESM::Spell* spell, + const int* actorSkills, const int* actorAttributes, StoreCommon *store); ESM::Skill::SkillEnum mapSchoolToSkill(int school); void calcWeakestSchool(const ESM::Spell* spell, - const int* actorSkills, int& effectiveSchool, float& skillTerm, StoreWrap *store); + const int* actorSkills, int& effectiveSchool, float& skillTerm, StoreCommon *store); float calcAutoCastChance(const ESM::Spell* spell, - const int* actorSkills, const int* actorAttributes, int effectiveSchool, StoreWrap *store); + const int* actorSkills, const int* actorAttributes, int effectiveSchool, StoreCommon *store); } -#endif +#endif // COMPONENTS_AUTOCALC_AUTOCALCSPELL_H diff --git a/components/autocalc/store.hpp b/components/autocalc/store.hpp new file mode 100644 index 000000000..65a29def7 --- /dev/null +++ b/components/autocalc/store.hpp @@ -0,0 +1,42 @@ +#ifndef COMPONENTS_AUTOCALC_STORE_H +#define COMPONENTS_AUTOCALC_STORE_H + +#include +#include + +namespace Loading +{ + class Listener; +} + +namespace ESM +{ + class ESMWriter; + class ESMReader; + struct Spell; + struct Skill; + struct MagicEffect; +} + +namespace AutoCalc +{ + // interface class for sharing the autocalc component between OpenMW and OpenCS + class StoreCommon + { + + public: + StoreCommon() {} + virtual ~StoreCommon() {} + + virtual int findGmstInt(const std::string& gmst) const = 0; + + virtual float findGmstFloat(const std::string& gmst) const = 0; + + virtual const ESM::Skill *findSkill(int index) const = 0; + + virtual const ESM::MagicEffect* findMagicEffect(int id) const = 0; + + virtual const std::vector& getSpells() const = 0; + }; +} +#endif // COMPONENTS_AUTOCALC_STORE_H diff --git a/components/gameplay/store.hpp b/components/gameplay/store.hpp deleted file mode 100644 index c4d7cba17..000000000 --- a/components/gameplay/store.hpp +++ /dev/null @@ -1,138 +0,0 @@ -#ifndef COMPONENTS_GAMEPLAY_STORE_H -#define COMPONENTS_GAMEPLAY_STORE_H - -#include -#include - -namespace Loading -{ - class Listener; -} - -namespace ESM -{ - class ESMWriter; - class ESMReader; - struct Spell; - struct Skill; - struct MagicEffect; -} - -namespace GamePlay -{ - // moved from apps/openmw/mwworld/store.hpp - struct StoreBase - { - virtual ~StoreBase() {} - - virtual void setUp() {} - virtual void listIdentifier(std::vector &list) const {} - - virtual size_t getSize() const = 0; - virtual int getDynamicSize() const { return 0; } - virtual void load(ESM::ESMReader &esm, const std::string &id) = 0; - - virtual bool eraseStatic(const std::string &id) {return false;} - virtual void clearDynamic() {} - - virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const {} - - virtual void read (ESM::ESMReader& reader, const std::string& id) {} - ///< Read into dynamic storage - }; - - // moved from apps/openmw/mwworld/store.hpp - template - class SharedIterator - { - typedef typename std::vector::const_iterator Iter; - - Iter mIter; - - public: - SharedIterator() {} - - SharedIterator(const SharedIterator &orig) - : mIter(orig.mIter) - {} - - SharedIterator(const Iter &iter) - : mIter(iter) - {} - - SharedIterator &operator++() { - ++mIter; - return *this; - } - - SharedIterator operator++(int) { - SharedIterator iter = *this; - ++mIter; - - return iter; - } - - SharedIterator &operator--() { - --mIter; - return *this; - } - - SharedIterator operator--(int) { - SharedIterator iter = *this; - --mIter; - - return iter; - } - - bool operator==(const SharedIterator &x) const { - return mIter == x.mIter; - } - - bool operator!=(const SharedIterator &x) const { - return !(*this == x); - } - - const T &operator*() const { - return **mIter; - } - - const T *operator->() const { - return &(**mIter); - } - }; - - // interface class for sharing the autocalc component between OpenMW and OpenCS - template - class CommonStore : public StoreBase - { - - public: - typedef SharedIterator iterator; - - virtual iterator begin() const = 0; - - virtual iterator end() const = 0; - - virtual const T *find(const std::string &id) const = 0; - }; - - // interface class for sharing the autocalc component between OpenMW and OpenCS - class StoreWrap - { - - public: - StoreWrap() {} - virtual ~StoreWrap() {} - - virtual int findGmstInt(const std::string& gmst) const = 0; - - virtual float findGmstFloat(const std::string& gmst) const = 0; - - virtual const ESM::Skill *findSkill(int index) const = 0; - - virtual const ESM::MagicEffect* findMagicEffect(int id) const = 0; - - virtual const CommonStore& getSpells() const = 0; - }; -} -#endif // COMPONENTS_GAMEPLAY_STORE_H From b1f07ba4fb39529faa433123a2e62a06936511bf Mon Sep 17 00:00:00 2001 From: cc9cii Date: Thu, 25 Jun 2015 18:57:32 +1000 Subject: [PATCH 08/16] Resolve merge issues and change the getSpells() interface. --- apps/opencs/model/world/data.cpp | 17 ++++++----------- apps/opencs/view/world/nestedtable.cpp | 16 ++++++++-------- apps/openmw/mwworld/mwstore.cpp | 5 +++-- apps/openmw/mwworld/mwstore.hpp | 2 +- apps/openmw/mwworld/store.hpp | 4 ---- components/autocalc/autocalcspell.cpp | 4 ++-- components/autocalc/store.hpp | 2 +- 7 files changed, 21 insertions(+), 29 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index b789bd28f..47115f378 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -33,7 +33,6 @@ namespace const CSMWorld::IdCollection& mSkillTable; const CSMWorld::IdCollection& mMagicEffectTable; const CSMWorld::NestedIdCollection& mSpells; - std::vector mLocal; public: @@ -42,14 +41,8 @@ namespace const CSMWorld::IdCollection& magicEffects, const CSMWorld::NestedIdCollection& spells) : mGmstTable(gmst), mSkillTable(skills), mMagicEffectTable(magicEffects), mSpells(spells) - { - // prepare data in a format used by OpenMW store - for (int index = 0; index < mSpells.getSize(); ++index) - { - ESM::Spell *spell = const_cast(&mSpells.getRecord(index).get()); - mLocal.push_back(spell); - } - } + { } + ~CSStore() {} virtual int findGmstInt(const std::string& name) const @@ -74,9 +67,11 @@ namespace return &mMagicEffectTable.getRecord(ESM::MagicEffect::indexToId((short)id)).get(); } - virtual const std::vector& getSpells() const + virtual void getSpells(std::vector& spells) { - return mLocal; + // prepare data in a format used by OpenMW store + for (int index = 0; index < mSpells.getSize(); ++index) + spells.push_back(const_cast(&mSpells.getRecord(index).get())); } }; diff --git a/apps/opencs/view/world/nestedtable.cpp b/apps/opencs/view/world/nestedtable.cpp index 5d37947d2..e4447397d 100644 --- a/apps/opencs/view/world/nestedtable.cpp +++ b/apps/opencs/view/world/nestedtable.cpp @@ -16,6 +16,8 @@ CSVWorld::NestedTable::NestedTable(CSMDoc::Document& document, QWidget* parent, bool editable) : DragRecordTable(document, parent), + mAddNewRowAction(0), + mRemoveRowAction(0), mModel(model) { setSelectionBehavior (QAbstractItemView::SelectRows); @@ -50,8 +52,6 @@ CSVWorld::NestedTable::NestedTable(CSMDoc::Document& document, setItemDelegateForColumn(i, delegate); } - setModel(model); - mAddNewRowAction = new QAction (tr ("Add new row"), this); connect(mAddNewRowAction, SIGNAL(triggered()), @@ -90,15 +90,15 @@ void CSVWorld::NestedTable::contextMenuEvent (QContextMenuEvent *event) void CSVWorld::NestedTable::removeRowActionTriggered() { mDocument.getUndoStack().push(new CSMWorld::DeleteNestedCommand(*(mModel->model()), - mModel->getParentId(), - selectionModel()->selectedRows().begin()->row(), - mModel->getParentColumn())); + mModel->getParentId(), + selectionModel()->selectedRows().begin()->row(), + mModel->getParentColumn())); } void CSVWorld::NestedTable::addNewRowActionTriggered() { mDocument.getUndoStack().push(new CSMWorld::AddNestedCommand(*(mModel->model()), - mModel->getParentId(), - selectionModel()->selectedRows().size(), - mModel->getParentColumn())); + mModel->getParentId(), + selectionModel()->selectedRows().size(), + mModel->getParentColumn())); } diff --git a/apps/openmw/mwworld/mwstore.cpp b/apps/openmw/mwworld/mwstore.cpp index 8ebe91cd9..bdc61033e 100644 --- a/apps/openmw/mwworld/mwstore.cpp +++ b/apps/openmw/mwworld/mwstore.cpp @@ -29,8 +29,9 @@ namespace MWWorld return MWBase::Environment::get().getWorld()->getStore().get().find(id); } - const std::vector& MWStore::getSpells() const + void MWStore::getSpells(std::vector& spells) { - return MWBase::Environment::get().getWorld()->getStore().get().getShared(); + for (Store::iterator iter = mSpells.begin(); iter != mSpells.end(); ++iter) + spells.push_back(const_cast(&*iter)); } } diff --git a/apps/openmw/mwworld/mwstore.hpp b/apps/openmw/mwworld/mwstore.hpp index c43c58931..ba5060b0f 100644 --- a/apps/openmw/mwworld/mwstore.hpp +++ b/apps/openmw/mwworld/mwstore.hpp @@ -27,7 +27,7 @@ namespace MWWorld virtual const ESM::MagicEffect* findMagicEffect(int id) const; - virtual const std::vector& getSpells() const; + virtual void MWStore::getSpells(std::vector& spells); }; } diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index c86a92f7a..ab09782b1 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -234,10 +234,6 @@ namespace MWWorld return mShared.size(); } - const std::vector& getShared() const { - return mShared; - } - int getDynamicSize() const { return static_cast (mDynamic.size()); // truncated from unsigned __int64 if _MSC_VER && _WIN64 diff --git a/components/autocalc/autocalcspell.cpp b/components/autocalc/autocalcspell.cpp index 8f2883e1b..01c25a695 100644 --- a/components/autocalc/autocalcspell.cpp +++ b/components/autocalc/autocalcspell.cpp @@ -60,8 +60,8 @@ namespace AutoCalc } std::vector selectedSpells; - - const std::vector& spells = store->getSpells(); + std::vector spells; + store->getSpells(spells); // Note: the algorithm heavily depends on the traversal order of the spells. For vanilla-compatible results the // Store must preserve the record ordering as it was in the content files. diff --git a/components/autocalc/store.hpp b/components/autocalc/store.hpp index 65a29def7..67061eef9 100644 --- a/components/autocalc/store.hpp +++ b/components/autocalc/store.hpp @@ -36,7 +36,7 @@ namespace AutoCalc virtual const ESM::MagicEffect* findMagicEffect(int id) const = 0; - virtual const std::vector& getSpells() const = 0; + virtual void getSpells(std::vector& spells) = 0; }; } #endif // COMPONENTS_AUTOCALC_STORE_H From 58923591cba8130feb277ad04256ac985aa67683 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Thu, 25 Jun 2015 19:25:47 +1000 Subject: [PATCH 09/16] Remove extra qualification. --- apps/openmw/mwworld/mwstore.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/mwstore.hpp b/apps/openmw/mwworld/mwstore.hpp index ba5060b0f..b833f1503 100644 --- a/apps/openmw/mwworld/mwstore.hpp +++ b/apps/openmw/mwworld/mwstore.hpp @@ -27,7 +27,7 @@ namespace MWWorld virtual const ESM::MagicEffect* findMagicEffect(int id) const; - virtual void MWStore::getSpells(std::vector& spells); + virtual void getSpells(std::vector& spells); }; } From 705253e4560b3c5a8e7c46c50363ffeb69c3b9d5 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Thu, 25 Jun 2015 19:54:07 +1000 Subject: [PATCH 10/16] Fix initialisation order. --- apps/opencs/model/world/data.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 47115f378..fc674b812 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -131,8 +131,8 @@ int CSMWorld::Data::count (RecordBase::State state, const CollectionBase& collec CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager) : mEncoder (encoding), mPathgrids (mCells), mRefs (mCells), - mResourcesManager (resourcesManager), mReader (0), mDialogue (0), mReaderIndex(0), - mReferenceables(self()) + mResourcesManager (resourcesManager), mReader (0), mDialogue (0), + mReferenceables(self()), mReaderIndex(0) { int index = 0; From 67b6c86a59909641b6f4905d1fc0a3045ce06907 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Thu, 25 Jun 2015 20:34:27 +1000 Subject: [PATCH 11/16] Fix initialisation order - properly this time. --- apps/opencs/model/world/data.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index fc674b812..ecb230f58 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -130,9 +130,8 @@ int CSMWorld::Data::count (RecordBase::State state, const CollectionBase& collec } CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager) -: mEncoder (encoding), mPathgrids (mCells), mRefs (mCells), - mResourcesManager (resourcesManager), mReader (0), mDialogue (0), - mReferenceables(self()), mReaderIndex(0) +: mEncoder (encoding), mPathgrids (mCells), mRefs (mCells), mReferenceables(self()), + mResourcesManager (resourcesManager), mReader (0), mDialogue (0), mReaderIndex(0) { int index = 0; From 273ff1cccbfa779795f68883916cfb22085922ac Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 26 Jun 2015 07:48:48 +1000 Subject: [PATCH 12/16] Address review comments. --- apps/opencs/model/world/data.cpp | 13 ++++++++----- apps/opencs/model/world/npcstats.cpp | 2 +- apps/opencs/model/world/npcstats.hpp | 2 +- apps/openmw/mwclass/npc.cpp | 2 +- components/autocalc/autocalc.cpp | 7 +++---- components/autocalc/autocalc.hpp | 4 ++-- components/autocalc/autocalcspell.cpp | 1 - components/autocalc/store.hpp | 7 ------- 8 files changed, 16 insertions(+), 22 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index ecb230f58..856b00e90 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -130,7 +130,7 @@ int CSMWorld::Data::count (RecordBase::State state, const CollectionBase& collec } CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager) -: mEncoder (encoding), mPathgrids (mCells), mRefs (mCells), mReferenceables(self()), +: mEncoder (encoding), mPathgrids (mCells), mReferenceables(self()), mRefs (mCells), mResourcesManager (resourcesManager), mReader (0), mDialogue (0), mReaderIndex(0) { int index = 0; @@ -1526,7 +1526,7 @@ CSMWorld::NpcStats* CSMWorld::Data::npcAutoCalculate(const ESM::NPC& npc) const if (autoCalc) level = npc.mNpdt12.mLevel; - CSMWorld::NpcStats *stats = new CSMWorld::NpcStats(); + std::auto_ptr stats (new CSMWorld::NpcStats()); CSStore store(mGmsts, mSkills, mMagicEffects, mSpells); @@ -1547,7 +1547,7 @@ CSMWorld::NpcStats* CSMWorld::Data::npcAutoCalculate(const ESM::NPC& npc) const for (std::vector::const_iterator it = npc.mSpells.mList.begin(); it != npc.mSpells.mList.end(); ++it) { - stats->addSpells(*it); + stats->addSpell(*it); } } @@ -1605,8 +1605,11 @@ CSMWorld::NpcStats* CSMWorld::Data::npcAutoCalculate(const ESM::NPC& npc) const } } - emit cacheNpcStats (npc.mId, stats); - return stats; + if (stats.get() == 0) + return 0; + + emit cacheNpcStats (npc.mId, stats.release()); + return stats.release(); } void CSMWorld::Data::cacheNpcStatsEvent (const std::string& id, CSMWorld::NpcStats *stats) diff --git a/apps/opencs/model/world/npcstats.cpp b/apps/opencs/model/world/npcstats.cpp index da14b473e..5b8484934 100644 --- a/apps/opencs/model/world/npcstats.cpp +++ b/apps/opencs/model/world/npcstats.cpp @@ -45,7 +45,7 @@ namespace CSMWorld mAttr[index] = value; } - void NpcStats::addSpells(std::string id) + void NpcStats::addSpell(const std::string& id) { struct SpellInfo info; info.mName = id; diff --git a/apps/opencs/model/world/npcstats.hpp b/apps/opencs/model/world/npcstats.hpp index 4d9be149f..8cefe586f 100644 --- a/apps/opencs/model/world/npcstats.hpp +++ b/apps/opencs/model/world/npcstats.hpp @@ -43,7 +43,7 @@ namespace CSMWorld virtual void setAttribute(int index, unsigned char value); - virtual void addSpells(std::string id); + virtual void addSpell(const std::string& id); void addPowers(const std::string& id, int type); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 64ed868a1..3b0234a25 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -74,7 +74,7 @@ namespace virtual void setAttribute(int index, unsigned char value) { mNpcStats.setAttribute(index, value); } - virtual void addSpells(std::string id) { mNpcStats.getSpells().add(id); } + virtual void addSpell(const std::string& id) { mNpcStats.getSpells().add(id); } virtual unsigned char getBaseSkill(int index) const { return mNpcStats.getSkill(index).getBase(); } diff --git a/components/autocalc/autocalc.cpp b/components/autocalc/autocalc.cpp index 78cd549ca..122d19763 100644 --- a/components/autocalc/autocalc.cpp +++ b/components/autocalc/autocalc.cpp @@ -11,7 +11,6 @@ #include "autocalcspell.hpp" -// Most of the code in this file was moved from apps/openmw/mwclass/npc.cpp namespace { int is_even(double d) @@ -117,7 +116,7 @@ namespace AutoCalc int index = class_->mData.mSkills[i2][i]; if (index >= 0 && index < ESM::Skill::Length) { - stats.setBaseSkill (index, stats.getBaseSkill(index) + bonus); + stats.setBaseSkill (index, bonus); } } } @@ -168,7 +167,7 @@ namespace AutoCalc } } - unsigned short autoCalculateHealth(int level, const ESM::Class *class_, StatsBase& stats) + unsigned short autoCalculateHealth(int level, const ESM::Class *class_, const StatsBase& stats) { // initial health int strength = stats.getBaseAttribute(ESM::Attribute::Strength); @@ -200,7 +199,7 @@ namespace AutoCalc std::vector spells = autoCalcNpcSpells(skills, attributes, race, store); for (std::vector::iterator it = spells.begin(); it != spells.end(); ++it) - stats.addSpells(*it); + stats.addSpell(*it); } StatsBase::StatsBase() {} diff --git a/components/autocalc/autocalc.hpp b/components/autocalc/autocalc.hpp index 3f4b6bae1..5cfe06b42 100644 --- a/components/autocalc/autocalc.hpp +++ b/components/autocalc/autocalc.hpp @@ -27,7 +27,7 @@ namespace AutoCalc virtual void setAttribute(int index, unsigned char value) = 0; - virtual void addSpells(std::string id) = 0; + virtual void addSpell(const std::string& id) = 0; virtual unsigned char getBaseSkill(int index) const = 0; @@ -40,7 +40,7 @@ namespace AutoCalc void autoCalcSkillsImpl (const ESM::NPC* npc, const ESM::Race *race, const ESM::Class *class_, int level, StatsBase& stats, StoreCommon *store); - unsigned short autoCalculateHealth(int level, const ESM::Class *class_, StatsBase& stats); + unsigned short autoCalculateHealth(int level, const ESM::Class *class_, const StatsBase& stats); void autoCalculateSpells(const ESM::Race *race, StatsBase& stats, StoreCommon *store); } diff --git a/components/autocalc/autocalcspell.cpp b/components/autocalc/autocalcspell.cpp index 01c25a695..78499a092 100644 --- a/components/autocalc/autocalcspell.cpp +++ b/components/autocalc/autocalcspell.cpp @@ -13,7 +13,6 @@ #include "autocalc.hpp" -// Most of the code in this file was moved from apps/openmw/mwmechanics/autocalcspell.cpp namespace AutoCalc { diff --git a/components/autocalc/store.hpp b/components/autocalc/store.hpp index 67061eef9..9a798a5ce 100644 --- a/components/autocalc/store.hpp +++ b/components/autocalc/store.hpp @@ -4,15 +4,8 @@ #include #include -namespace Loading -{ - class Listener; -} - namespace ESM { - class ESMWriter; - class ESMReader; struct Spell; struct Skill; struct MagicEffect; From 8c39f2b3769eeea40e36fcf52dceb514e4a27ac9 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 26 Jun 2015 08:52:39 +1000 Subject: [PATCH 13/16] Fix dereferencing a null pointer. --- apps/opencs/model/world/data.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 856b00e90..3cd501b4f 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -1608,8 +1608,9 @@ CSMWorld::NpcStats* CSMWorld::Data::npcAutoCalculate(const ESM::NPC& npc) const if (stats.get() == 0) return 0; - emit cacheNpcStats (npc.mId, stats.release()); - return stats.release(); + CSMWorld::NpcStats *result = stats.release(); + emit cacheNpcStats (npc.mId, result); + return result; } void CSMWorld::Data::cacheNpcStatsEvent (const std::string& id, CSMWorld::NpcStats *stats) From 78457a82345ebd04dada271c0e6004e0d40f9fbe Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 26 Jun 2015 13:50:09 +1000 Subject: [PATCH 14/16] Simplify npc data update and fix data copy when autocal flag changed. --- apps/opencs/model/world/data.cpp | 75 ++++++++------------------------ 1 file changed, 17 insertions(+), 58 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 3cd501b4f..3cb5d3cbb 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -1373,70 +1373,33 @@ void CSMWorld::Data::npcDataChanged (const QModelIndex& topLeft, const QModelInd static_cast(getTableModel(CSMWorld::UniversalId::Type_Referenceable)); int autoCalcColumn = objectModel->findColumnIndex(CSMWorld::Columns::ColumnId_AutoCalc); - int miscColumn = objectModel->findColumnIndex(CSMWorld::Columns::ColumnId_NpcMisc); - // first check for level - bool levelChanged = false; - if (topLeft.parent().isValid() && bottomRight.parent().isValid()) + // check for autocalc + if (topLeft.parent().isValid() || bottomRight.parent().isValid() + || topLeft.column() > autoCalcColumn || autoCalcColumn > bottomRight.column()) { - if (topLeft.parent().column() <= miscColumn && miscColumn <= bottomRight.parent().column()) - { - for (int col = topLeft.column(); col <= bottomRight.column(); ++col) - { - int role = objectModel->nestedHeaderData(topLeft.parent().column(), - col, Qt::Horizontal, CSMWorld::ColumnBase::Role_ColumnId).toInt(); - if (role == CSMWorld::Columns::ColumnId_NpcLevel) - { - levelChanged = true; - break; - } - } - } + return; } - // next check for autocalc - bool autoCalcChanged = false; - if (!topLeft.parent().isValid() && !bottomRight.parent().isValid()) - { - if ((topLeft.column() <= autoCalcColumn && autoCalcColumn <= bottomRight.column()) - || (topLeft.column() <= miscColumn && miscColumn <= bottomRight.column())) - { - autoCalcChanged = true; - } - } - - if (!levelChanged && !autoCalcChanged) - return; - - int row = 0; - int end = 0; - if (topLeft.parent().isValid()) - row = topLeft.parent().row(); - else - row = topLeft.row(); - - if (bottomRight.parent().isValid()) - end = bottomRight.parent().row(); - else - end = bottomRight.row(); - - for (; row <= end; ++row) + int row = topLeft.row(); + for (; row <= bottomRight.row(); ++row) { Record record = static_cast&>(mReferenceables.getRecord(row)); ESM::NPC &npc = record.get(); - // If going from autocalc to non-autocalc, save the autocalc values - if (autoCalcChanged) + if (npc.mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) { - if (npc.mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) - saveAutoCalcValues(npc); // update attributes and skills - else - npc.mNpdt12.mLevel = npc.mNpdt52.mLevel; // for NPC's loaded as non-autocalc - - record.setModified(npc); - mReferenceables.replace(row, record); + // first pretend autocalc to force recalculation + npc.mNpdtType = ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS; + saveAutoCalcValues(npc); // update attributes and skills + npc.mNpdtType = ESM::NPC::NPC_DEFAULT; } + else + npc.mNpdt12.mLevel = npc.mNpdt52.mLevel; // for NPC's loaded as non-autocalc + + record.setModified(npc); + mReferenceables.replace(row, record); } } @@ -1469,11 +1432,7 @@ void CSMWorld::Data::gmstDataChanged (const QModelIndex& topLeft, const QModelIn // FIXME: how to undo? void CSMWorld::Data::saveAutoCalcValues(ESM::NPC& npc) { - CSMWorld::NpcStats * cachedStats = getCachedNpcData (npc.mId); - if (!cachedStats) - return; // silently fail - - CSMWorld::NpcStats* stats = npcAutoCalculate(npc); + CSMWorld::NpcStats *stats = npcAutoCalculate(npc); // update npc npc.mNpdt52.mLevel = npc.mNpdt12.mLevel; From 3e29bb8a860cfdc3dc58855b616f2fc2f38b0a05 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 26 Jun 2015 16:10:50 +1000 Subject: [PATCH 15/16] Fix undo for NPC autocalc changes. Fix the lack of refresh after race powers subtable. --- apps/opencs/model/world/data.cpp | 83 +++------------------ apps/opencs/model/world/data.hpp | 2 - apps/opencs/model/world/refidadapterimp.cpp | 47 +++++++++++- apps/opencs/model/world/refidadapterimp.hpp | 3 +- apps/opencs/model/world/refidcollection.cpp | 2 +- 5 files changed, 58 insertions(+), 79 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 3cb5d3cbb..6d17e90b7 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -1324,25 +1324,33 @@ void CSMWorld::Data::raceDataChanged (const QModelIndex& topLeft, const QModelIn // affects racial bonus attributes & skills // - mData.mAttributeValues[] // - mData.mBonus[].mBonus + // - mPowers.mList[] CSMWorld::IdTree *raceModel = static_cast(getTableModel(CSMWorld::UniversalId::Type_Race)); int attrColumn = raceModel->findColumnIndex(CSMWorld::Columns::ColumnId_RaceAttributes); int bonusColumn = raceModel->findColumnIndex(CSMWorld::Columns::ColumnId_RaceSkillBonus); + int powersColumn = raceModel->findColumnIndex(CSMWorld::Columns::ColumnId_PowerList); bool match = false; + int raceRow = topLeft.row(); + int raceEnd = bottomRight.row(); if (topLeft.parent().isValid() && bottomRight.parent().isValid()) { if ((topLeft.parent().column() <= attrColumn && attrColumn <= bottomRight.parent().column()) - || (topLeft.parent().column() <= bonusColumn && bonusColumn <= bottomRight.parent().column())) + || (topLeft.parent().column() <= bonusColumn && bonusColumn <= bottomRight.parent().column()) + || (topLeft.parent().column() <= powersColumn && powersColumn <= bottomRight.parent().column())) { match = true; // TODO: check for specific nested column? + raceRow = topLeft.parent().row(); + raceEnd = bottomRight.parent().row(); } } else { if ((topLeft.column() <= attrColumn && attrColumn <= bottomRight.column()) - || (topLeft.column() <= bonusColumn && bonusColumn <= bottomRight.column())) + || (topLeft.column() <= bonusColumn && bonusColumn <= bottomRight.column()) + || (topLeft.column() <= powersColumn && powersColumn <= bottomRight.column())) { match = true; // maybe the whole table changed } @@ -1353,7 +1361,7 @@ void CSMWorld::Data::raceDataChanged (const QModelIndex& topLeft, const QModelIn // update autocalculated attributes/skills of every NPC with matching race int idColumn = raceModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id); - for (int raceRow = topLeft.parent().row(); raceRow <= bottomRight.parent().row(); ++raceRow) + for (; raceRow <= raceEnd; ++raceRow) { clearNpcStatsCache(); @@ -1363,44 +1371,10 @@ void CSMWorld::Data::raceDataChanged (const QModelIndex& topLeft, const QModelIn } } -// FIXME: currently ignoring level changes void CSMWorld::Data::npcDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { + // TODO: for now always recalculate clearNpcStatsCache(); - - // Either autoCalc flag changed or NPC level changed - CSMWorld::IdTree *objectModel = - static_cast(getTableModel(CSMWorld::UniversalId::Type_Referenceable)); - - int autoCalcColumn = objectModel->findColumnIndex(CSMWorld::Columns::ColumnId_AutoCalc); - - // check for autocalc - if (topLeft.parent().isValid() || bottomRight.parent().isValid() - || topLeft.column() > autoCalcColumn || autoCalcColumn > bottomRight.column()) - { - return; - } - - int row = topLeft.row(); - for (; row <= bottomRight.row(); ++row) - { - Record record = - static_cast&>(mReferenceables.getRecord(row)); - ESM::NPC &npc = record.get(); - - if (npc.mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) - { - // first pretend autocalc to force recalculation - npc.mNpdtType = ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS; - saveAutoCalcValues(npc); // update attributes and skills - npc.mNpdtType = ESM::NPC::NPC_DEFAULT; - } - else - npc.mNpdt12.mLevel = npc.mNpdt52.mLevel; // for NPC's loaded as non-autocalc - - record.setModified(npc); - mReferenceables.replace(row, record); - } } void CSMWorld::Data::gmstDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) @@ -1429,39 +1403,6 @@ void CSMWorld::Data::gmstDataChanged (const QModelIndex& topLeft, const QModelIn emit updateNpcAutocalc(0/*all*/, empty); } -// FIXME: how to undo? -void CSMWorld::Data::saveAutoCalcValues(ESM::NPC& npc) -{ - CSMWorld::NpcStats *stats = npcAutoCalculate(npc); - - // update npc - npc.mNpdt52.mLevel = npc.mNpdt12.mLevel; - - npc.mNpdt52.mStrength = stats->getBaseAttribute(ESM::Attribute::Strength); - npc.mNpdt52.mIntelligence = stats->getBaseAttribute(ESM::Attribute::Intelligence); - npc.mNpdt52.mWillpower = stats->getBaseAttribute(ESM::Attribute::Willpower); - npc.mNpdt52.mAgility = stats->getBaseAttribute(ESM::Attribute::Agility); - npc.mNpdt52.mSpeed = stats->getBaseAttribute(ESM::Attribute::Speed); - npc.mNpdt52.mEndurance = stats->getBaseAttribute(ESM::Attribute::Endurance); - npc.mNpdt52.mPersonality = stats->getBaseAttribute(ESM::Attribute::Personality); - npc.mNpdt52.mLuck = stats->getBaseAttribute(ESM::Attribute::Luck); - - for (int i = 0; i < ESM::Skill::Length; ++i) - { - npc.mNpdt52.mSkills[i] = stats->getBaseSkill(i); - } - - npc.mNpdt52.mHealth = stats->getHealth(); - npc.mNpdt52.mMana = stats->getMana(); - npc.mNpdt52.mFatigue = stats->getFatigue(); - npc.mNpdt52.mDisposition = npc.mNpdt12.mDisposition; - npc.mNpdt52.mReputation = npc.mNpdt12.mReputation; - npc.mNpdt52.mRank = npc.mNpdt12.mRank; - npc.mNpdt52.mGold = npc.mNpdt12.mGold; - - // TODO: add spells from autogenerated list like vanilla (but excluding any race powers or abilities) -} - void CSMWorld::Data::clearNpcStatsCache () { for (std::map::iterator it (mNpcStatCache.begin()); diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index fdc76fd73..e081318a6 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -126,8 +126,6 @@ namespace CSMWorld const Data& self (); - void saveAutoCalcValues(ESM::NPC& npc); - void clearNpcStatsCache (); public: diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index 99ca76d73..2bb980bfe 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -558,8 +558,8 @@ CSMWorld::NpcColumns::NpcColumns (const ActorColumns& actorColumns) mMisc(NULL) {} -CSMWorld::NpcRefIdAdapter::NpcRefIdAdapter (const NpcColumns& columns) -: ActorRefIdAdapter (UniversalId::Type_Npc, columns), mColumns (columns) +CSMWorld::NpcRefIdAdapter::NpcRefIdAdapter (const NpcColumns& columns, const CSMWorld::Data& data) +: ActorRefIdAdapter (UniversalId::Type_Npc, columns), mColumns (columns), mData(data) {} QVariant CSMWorld::NpcRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, int index) @@ -634,8 +634,47 @@ void CSMWorld::NpcRefIdAdapter::setData (const RefIdColumn *column, RefIdData& d npc.mFlags &= ~iter->second; if (iter->second == ESM::NPC::Autocalc) - npc.mNpdtType = (value.toInt() != 0) ? ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS - : ESM::NPC::NPC_DEFAULT; + { + if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) + { + CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc); + + // update npc + npc.mNpdtType = ESM::NPC::NPC_DEFAULT; + npc.mNpdt52.mLevel = npc.mNpdt12.mLevel; + + npc.mNpdt52.mStrength = stats->getBaseAttribute(ESM::Attribute::Strength); + npc.mNpdt52.mIntelligence = stats->getBaseAttribute(ESM::Attribute::Intelligence); + npc.mNpdt52.mWillpower = stats->getBaseAttribute(ESM::Attribute::Willpower); + npc.mNpdt52.mAgility = stats->getBaseAttribute(ESM::Attribute::Agility); + npc.mNpdt52.mSpeed = stats->getBaseAttribute(ESM::Attribute::Speed); + npc.mNpdt52.mEndurance = stats->getBaseAttribute(ESM::Attribute::Endurance); + npc.mNpdt52.mPersonality = stats->getBaseAttribute(ESM::Attribute::Personality); + npc.mNpdt52.mLuck = stats->getBaseAttribute(ESM::Attribute::Luck); + + for (int i = 0; i < ESM::Skill::Length; ++i) + { + npc.mNpdt52.mSkills[i] = stats->getBaseSkill(i); + } + + npc.mNpdt52.mHealth = stats->getHealth(); + npc.mNpdt52.mMana = stats->getMana(); + npc.mNpdt52.mFatigue = stats->getFatigue(); + npc.mNpdt52.mDisposition = npc.mNpdt12.mDisposition; + npc.mNpdt52.mReputation = npc.mNpdt12.mReputation; + npc.mNpdt52.mRank = npc.mNpdt12.mRank; + npc.mNpdt52.mGold = npc.mNpdt12.mGold; + + // TODO: add spells from autogenerated list like vanilla (but excluding any + // race powers or abilities) + } + else + { + npc.mNpdtType = ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS; + npc.mNpdt12.mLevel = npc.mNpdt52.mLevel; // for NPC's loaded as non-autocalc + mData.npcAutoCalculate(npc); + } + } } else { diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index acdc124eb..4a8288fa4 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -807,10 +807,11 @@ namespace CSMWorld class NpcRefIdAdapter : public ActorRefIdAdapter { NpcColumns mColumns; + const Data& mData; public: - NpcRefIdAdapter (const NpcColumns& columns); + NpcRefIdAdapter (const NpcColumns& columns, const Data& data); virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) const; diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index d5232db63..fc52fe388 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -620,7 +620,7 @@ CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data) mAdapters.insert (std::make_pair (UniversalId::Type_Miscellaneous, new MiscRefIdAdapter (inventoryColumns, key))); mAdapters.insert (std::make_pair (UniversalId::Type_Npc, - new NpcRefIdAdapter (npcColumns))); + new NpcRefIdAdapter (npcColumns, data))); mAdapters.insert (std::make_pair (UniversalId::Type_Probe, new ToolRefIdAdapter (UniversalId::Type_Probe, toolsColumns))); mAdapters.insert (std::make_pair (UniversalId::Type_Repair, From 6542ff111dd311c732f3c53b551c8b9d3022be06 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 27 Jun 2015 08:50:53 +1000 Subject: [PATCH 16/16] Fix merge issues. --- apps/opencs/view/world/dialoguesubview.cpp | 31 +++++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index 6ab5b18ac..a366f53ab 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -66,7 +66,7 @@ void CSVWorld::NotEditableSubDelegate::setEditorData (QWidget* editor, const QMo CSMWorld::Columns::ColumnId columnId = static_cast ( mTable->getColumnId (index.column())); - + if (QVariant::String == v.type()) { label->setText(v.toString()); @@ -75,7 +75,7 @@ void CSVWorld::NotEditableSubDelegate::setEditorData (QWidget* editor, const QMo { int data = v.toInt(); std::vector enumNames (CSMWorld::Columns::getEnums (columnId)); - + label->setText(QString::fromUtf8(enumNames.at(data).c_str())); } else @@ -709,6 +709,30 @@ void CSVWorld::SimpleDialogueSubView::changeCurrentId (const std::string& newId) mCommandDispatcher.setSelection(selection); } +void CSVWorld::SimpleDialogueSubView::refreshNpcDialogue (int type, const std::string& id) +{ + int typeColumn = mTable->findColumnIndex (CSMWorld::Columns::ColumnId_RecordType); + if (CSMWorld::UniversalId::Type_Npc + != mTable->data(mTable->getModelIndex(mCurrentId, typeColumn), Qt::DisplayRole).toInt()) + { + return; + } + + int raceColumn = mTable->findColumnIndex (CSMWorld::Columns::ColumnId_Race); + int classColumn = mTable->findColumnIndex (CSMWorld::Columns::ColumnId_Class); + + if ((type == 0/*FIXME*/ && id == "") // skill or gmst changed + || (id == mTable->data(mTable->getModelIndex(mCurrentId, raceColumn), + Qt::DisplayRole).toString().toUtf8().constData()) // race + || (id == mTable->data(mTable->getModelIndex(mCurrentId, classColumn), + Qt::DisplayRole).toString().toUtf8().constData())) // class + { + int y = mEditWidget->verticalScrollBar()->value(); + mEditWidget->remake (mTable->getModelIndex(mCurrentId, 0).row()); + mEditWidget->verticalScrollBar()->setValue(y); + } +} + CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, const CreatorFactoryBase& creatorFactory, bool sorting) : SimpleDialogueSubView (id, document) @@ -783,7 +807,7 @@ CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, deleteButton->setDisabled (true); } - getMainLayout().addLayout (buttonsLayout); + getMainLayout().addLayout (buttonsLayout); } void CSVWorld::DialogueSubView::cloneRequest() @@ -864,7 +888,6 @@ void CSVWorld::DialogueSubView::nextId () } } - void CSVWorld::DialogueSubView::showPreview () { QModelIndex currentIndex (getTable().getModelIndex (getCurrentId(), 0));