1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-01-30 12:32:36 +00:00

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.
This commit is contained in:
cc9cii 2015-06-18 22:02:08 +10:00
parent 62c5f185d7
commit ad04046243
12 changed files with 628 additions and 91 deletions

View File

@ -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;
}

View File

@ -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);

View File

@ -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");

View File

@ -5,7 +5,195 @@
#include <utility>
#include <components/esm/loadcont.hpp>
#include <components/esm/attr.hpp>
#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<int>(i);
if(d > 0.5)
return static_cast<int>(i) + 1;
if(is_even(i))
return static_cast<int>(i);
return static_cast<int>(i) + 1;
}
std::vector<int> autoCalculateAttributes (const ESM::NPC &npc,
const ESM::Race& race, const ESM::Class& class_, const CSMWorld::IdCollection<ESM::Skill>& skillTable)
{
// race bonus
bool male = (npc.mFlags & ESM::NPC::Female) == 0;
if (npc.mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
return std::vector<int>();
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<int> 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<unsigned char> autoCalculateSkills (const ESM::NPC &npc,
const ESM::Race& race, const ESM::Class& class_, const CSMWorld::IdCollection<ESM::Skill>& 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<unsigned char> 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<int>& 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<int>& attr)
{
return attr[ESM::Attribute::Intelligence] * 2;
}
unsigned short autoCalculateFatigue(const std::vector<int>& 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<ESM::NPC> (UniversalId::Type_Npc, columns), mColumns (columns)
CSMWorld::NpcRefIdAdapter::NpcRefIdAdapter (const NpcColumns& columns,
const CSMWorld::IdCollection<ESM::Race>& races,
const CSMWorld::IdCollection<ESM::Class>& classes,
const CSMWorld::IdCollection<ESM::Skill>& skills)
: ActorRefIdAdapter<ESM::NPC> (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<int> attr = autoCalculateAttributes(npc, race, class_, mSkillTable);
if (attr.empty())
return;
std::vector<unsigned char> 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<ESM::Race>& races,
const CSMWorld::IdCollection<ESM::Class>& classes, const CSMWorld::IdCollection<ESM::Skill>& 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<ESM::NPC>& record =
static_cast<const Record<ESM::NPC>&> (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<int>(npcStruct.mStrength);
case 1: return static_cast<int>(npcStruct.mIntelligence);
case 2: return static_cast<int>(npcStruct.mWillpower);
case 3: return static_cast<int>(npcStruct.mAgility);
case 4: return static_cast<int>(npcStruct.mSpeed);
case 5: return static_cast<int>(npcStruct.mEndurance);
case 6: return static_cast<int>(npcStruct.mPersonality);
case 7: return static_cast<int>(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<int> attr = autoCalculateAttributes(npc, race, class_, mSkillTable);
if (attr.empty())
return QVariant();
switch (subRowIndex)
{
case 0: return static_cast<int>(attr[ESM::Attribute::Strength]);
case 1: return static_cast<int>(attr[ESM::Attribute::Intelligence]);
case 2: return static_cast<int>(attr[ESM::Attribute::Willpower]);
case 3: return static_cast<int>(attr[ESM::Attribute::Agility]);
case 4: return static_cast<int>(attr[ESM::Attribute::Speed]);
case 5: return static_cast<int>(attr[ESM::Attribute::Endurance]);
case 6: return static_cast<int>(attr[ESM::Attribute::Personality]);
case 7: return static_cast<int>(attr[ESM::Attribute::Luck]);
default: return QVariant(); // throw an exception here?
}
}
else
switch (subRowIndex)
{
case 0: return static_cast<int>(npcStruct.mStrength);
case 1: return static_cast<int>(npcStruct.mIntelligence);
case 2: return static_cast<int>(npcStruct.mWillpower);
case 3: return static_cast<int>(npcStruct.mAgility);
case 4: return static_cast<int>(npcStruct.mSpeed);
case 5: return static_cast<int>(npcStruct.mEndurance);
case 6: return static_cast<int>(npcStruct.mPersonality);
case 7: return static_cast<int>(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<ESM::Race>& races,
const CSMWorld::IdCollection<ESM::Class>& classes,
const CSMWorld::IdCollection<ESM::Skill>& 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<ESM::NPC>& record =
static_cast<const Record<ESM::NPC>&> (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<int>(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<unsigned char> skills = autoCalculateSkills(npc, race, class_, mSkillTable);
int value = static_cast<int>(skills[subRowIndex]);
return static_cast<int>(skills[subRowIndex]);
}
else
{
const ESM::NPC::NPDTstruct52& npcStruct = npc.mNpdt52;
return static_cast<int>(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<ESM::Race>& races,
const CSMWorld::IdCollection<ESM::Class>& classes,
const CSMWorld::IdCollection<ESM::Skill>& skills)
: mRaceTable(races), mClassTable(classes), mSkillTable(skills)
{}
CSMWorld::NpcMiscRefIdAdapter::~NpcMiscRefIdAdapter()
@ -888,16 +1171,45 @@ QVariant CSMWorld::NpcMiscRefIdAdapter::getNestedData (const RefIdColumn *column
const Record<ESM::NPC>& record =
static_cast<const Record<ESM::NPC>&> (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<int> attr = autoCalculateAttributes(npc, race, class_, mSkillTable);
if (attr.empty())
return QVariant();
switch (subColIndex)
{
case 0: return static_cast<int>(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<int>(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<int>(record.get().mNpdt12.mDisposition);
case 6: return static_cast<int>(record.get().mNpdt12.mReputation);
case 7: return static_cast<int>(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<short>(value.toInt()); break;
case 0:
{
npc.mNpdt12.mLevel = static_cast<short>(value.toInt()); break;
const ESM::Race& race = mRaceTable.getRecord(npc.mRace).get();
const ESM::Class& class_ = mClassTable.getRecord(npc.mClass).get();
std::vector<int> attr = autoCalculateAttributes(npc, race, class_, mSkillTable);
if (attr.empty())
return;
ESM::NPC::NPDTstruct52& npcStruct = npc.mNpdt52;
std::vector<unsigned char> 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<signed char>(value.toInt()); break;
case 6: npc.mNpdt12.mReputation = static_cast<signed char>(value.toInt()); break;
case 7: npc.mNpdt12.mRank = static_cast<signed char>(value.toInt()); break;
case 8: npc.mNpdt12.mGold = value.toInt(); break;
case 5:
{
npc.mNpdt12.mDisposition = static_cast<signed char>(value.toInt());
npc.mNpdt52.mDisposition = npc.mNpdt12.mDisposition;
break;
}
case 6:
{
npc.mNpdt12.mReputation = static_cast<signed char>(value.toInt());
npc.mNpdt52.mReputation = npc.mNpdt12.mReputation;
break;
}
case 7:
{
npc.mNpdt12.mRank = static_cast<signed char>(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<short>(value.toInt()); break;
case 0:
{
npc.mNpdt52.mLevel = static_cast<short>(value.toInt());
npc.mNpdt12.mLevel = npc.mNpdt52.mLevel;
break;
}
case 1: npc.mNpdt52.mFactionID = static_cast<char>(value.toInt()); break;
case 2: npc.mNpdt52.mHealth = static_cast<unsigned short>(value.toInt()); break;
case 3: npc.mNpdt52.mMana = static_cast<unsigned short>(value.toInt()); break;
case 4: npc.mNpdt52.mFatigue = static_cast<unsigned short>(value.toInt()); break;
case 5: npc.mNpdt52.mDisposition = static_cast<signed char>(value.toInt()); break;
case 6: npc.mNpdt52.mReputation = static_cast<signed char>(value.toInt()); break;
case 7: npc.mNpdt52.mRank = static_cast<signed char>(value.toInt()); break;
case 8: npc.mNpdt52.mGold = value.toInt(); break;
case 5:
{
npc.mNpdt52.mDisposition = static_cast<signed char>(value.toInt());
npc.mNpdt12.mDisposition = npc.mNpdt52.mDisposition;
break;
}
case 6:
{
npc.mNpdt52.mReputation = static_cast<signed char>(value.toInt());
npc.mNpdt12.mReputation = npc.mNpdt52.mReputation;
break;
}
case 7:
{
npc.mNpdt52.mRank = static_cast<signed char>(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?
}

View File

@ -11,12 +11,16 @@
#include <components/esm/loadappa.hpp>
#include <components/esm/loadnpc.hpp>
#include <components/esm/loadcrea.hpp>
#include <components/esm/loadskil.hpp>
#include <components/esm/loadclas.hpp>
#include <components/esm/loadrace.hpp>
#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<ESM::NPC>
{
NpcColumns mColumns;
const IdCollection<ESM::Race>& mRaceTable;
const IdCollection<ESM::Class>& mClassTable;
const IdCollection<ESM::Skill>& mSkillTable;
public:
NpcRefIdAdapter (const NpcColumns& columns);
NpcRefIdAdapter (const NpcColumns& columns,
const IdCollection<ESM::Race>& races,
const IdCollection<ESM::Class>& classes,
const IdCollection<ESM::Skill>& 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<ESM::Race>& mRaceTable;
const IdCollection<ESM::Class>& mClassTable;
const IdCollection<ESM::Skill>& mSkillTable;
public:
NpcAttributesRefIdAdapter ();
NpcAttributesRefIdAdapter (const IdCollection<ESM::Race>& races,
const IdCollection<ESM::Class>& classes,
const IdCollection<ESM::Skill>& 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<ESM::Race>& mRaceTable;
const IdCollection<ESM::Class>& mClassTable;
const IdCollection<ESM::Skill>& mSkillTable;
public:
NpcSkillsRefIdAdapter ();
NpcSkillsRefIdAdapter (const IdCollection<ESM::Race>& races,
const IdCollection<ESM::Class>& classes,
const IdCollection<ESM::Skill>& 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<ESM::Race>& mRaceTable;
const IdCollection<ESM::Class>& mClassTable;
const IdCollection<ESM::Skill>& mSkillTable;
NpcMiscRefIdAdapter (const NpcMiscRefIdAdapter&);
NpcMiscRefIdAdapter& operator= (const NpcMiscRefIdAdapter&);
public:
NpcMiscRefIdAdapter ();
NpcMiscRefIdAdapter (const IdCollection<ESM::Race>& races,
const IdCollection<ESM::Class>& classes,
const IdCollection<ESM::Skill>& skills);
virtual ~NpcMiscRefIdAdapter();
virtual void addNestedRow (const RefIdColumn *column,

View File

@ -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<UniversalId::Type, NestedRefIdAdapterBase*> 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<UniversalId::Type, NestedRefIdAdapterBase*> 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<UniversalId::Type, NestedRefIdAdapterBase*> 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<ESM::Probe> (UniversalId::Type_Probe, toolsColumns)));
mAdapters.insert (std::make_pair (UniversalId::Type_Repair,

View File

@ -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();

View File

@ -0,0 +1,44 @@
#ifndef CSM_WORLD_USERTYPE_H
#define CSM_WORLD_USERTYPE_H
#include <QMetaType>
#include <QVariant>
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

View File

@ -468,17 +468,19 @@ void CSVWorld::EditWidget::remake(int row)
static_cast<CSMWorld::UniversalId::Type> (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);

View File

@ -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<CSMWorld::ColumnBase::Display> (
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<CSMWorld::ColumnBase::Display> (
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);

View File

@ -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);

View File

@ -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<CSMWorld::UserFloat>())
editor->setProperty(n, QVariant(v.value<CSMWorld::UserFloat>().value()));
else if (v.type() == QVariant::UserType
&& QString(v.typeName()) == "CSMWorld::UserInt" && v.canConvert<CSMWorld::UserInt>())
editor->setProperty(n, QVariant(v.value<CSMWorld::UserInt>().value()));
else
editor->setProperty(n, v);
}
}