diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 529c8f88f9..ffa1ff3c3b 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 060e47bd95..f83f9de227 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 b7d09777d4..14a436c838 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 4c369ef24f..86f0461af1 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 869996da5b..b12f84d5e5 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 5495926b46..739e9cec49 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 4d511d12da..e8e6336635 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 0000000000..e0b3c2e2f5 --- /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 66e8fcb7a2..3c35b095d9 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 112873cb9a..7f5658b880 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 5db977942a..30af6b2112 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 e1d165a24f..0ecd7779f8 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); } }