1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-01-16 18:49:58 +00:00

Move NPC autocal code out to a separate class.

This commit is contained in:
cc9cii 2015-11-08 06:02:53 +11:00
parent c7c0023ed2
commit 04c5c0d82a
6 changed files with 466 additions and 385 deletions

View file

@ -19,7 +19,7 @@ opencs_hdrs_noqt (model/doc
opencs_units (model/world
idtable idtableproxymodel regionmap data commanddispatcher idtablebase resourcetable nestedtableproxymodel idtree infotableproxymodel
pathgridcommands
pathgridcommands npcautocalc
)
@ -36,7 +36,7 @@ opencs_hdrs_noqt (model/world
opencs_units (model/tools
tools reportmodel mergeoperation
tools reportmodel mergeoperation
)
opencs_units_noqt (model/tools

View file

@ -10,10 +10,6 @@
#include <components/esm/loadglob.hpp>
#include <components/esm/cellref.hpp>
#include <components/autocalc/autocalc.hpp>
#include <components/autocalc/autocalcspell.hpp>
#include <components/autocalc/store.hpp>
#include "idtable.hpp"
#include "idtree.hpp"
#include "columnimp.hpp"
@ -23,70 +19,7 @@
#include "resourcetable.hpp"
#include "nestedcoladapterimp.hpp"
#include "npcstats.hpp"
namespace
{
class CSStore : public AutoCalc::StoreCommon
{
const CSMWorld::IdCollection<ESM::GameSetting>& mGmstTable;
const CSMWorld::IdCollection<ESM::Skill>& mSkillTable;
const CSMWorld::IdCollection<ESM::MagicEffect>& mMagicEffectTable;
const CSMWorld::NestedIdCollection<ESM::Spell>& mSpells;
public:
CSStore(const CSMWorld::IdCollection<ESM::GameSetting>& gmst,
const CSMWorld::IdCollection<ESM::Skill>& skills,
const CSMWorld::IdCollection<ESM::MagicEffect>& magicEffects,
const CSMWorld::NestedIdCollection<ESM::Spell>& spells)
: mGmstTable(gmst), mSkillTable(skills), mMagicEffectTable(magicEffects), mSpells(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 void getSpells(std::vector<ESM::Spell*>& spells)
{
// prepare data in a format used by OpenMW store
for (int index = 0; index < mSpells.getSize(); ++index)
spells.push_back(const_cast<ESM::Spell *>(&mSpells.getRecord(index).get()));
}
};
unsigned short autoCalculateMana(AutoCalc::StatsBase& stats)
{
return stats.getBaseAttribute(ESM::Attribute::Intelligence) * 2;
}
unsigned short autoCalculateFatigue(AutoCalc::StatsBase& stats)
{
return stats.getBaseAttribute(ESM::Attribute::Strength)
+ stats.getBaseAttribute(ESM::Attribute::Willpower)
+ stats.getBaseAttribute(ESM::Attribute::Agility)
+ stats.getBaseAttribute(ESM::Attribute::Endurance);
}
}
#include "npcautocalc.hpp"
void CSMWorld::Data::addModel (QAbstractItemModel *model, UniversalId::Type type, bool update)
{
@ -129,8 +62,9 @@ int CSMWorld::Data::count (RecordBase::State state, const CollectionBase& collec
}
CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager)
: mEncoder (encoding), mPathgrids (mCells), mReferenceables(self()), mRefs (mCells),
mResourcesManager (resourcesManager), mReader (0), mDialogue (0), mReaderIndex(0)
: mEncoder (encoding), mPathgrids (mCells), mReferenceables (self()), mRefs (mCells),
mResourcesManager (resourcesManager), mReader (0), mDialogue (0), mReaderIndex(0),
mNpcAutoCalc (0)
{
int index = 0;
@ -621,32 +555,18 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc
CSMWorld::IdTree *objects =
static_cast<CSMWorld::IdTree*>(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*)));
mNpcAutoCalc = new NpcAutoCalc (self(), gmsts, skills, classes, races, objects);
mRefLoadCache.clear(); // clear here rather than startLoading() and continueLoading() for multiple content files
}
CSMWorld::Data::~Data()
{
clearNpcStatsCache();
for (std::vector<QAbstractItemModel *>::iterator iter (mModels.begin()); iter!=mModels.end(); ++iter)
delete *iter;
delete mReader;
delete mNpcAutoCalc;
}
const CSMWorld::IdCollection<ESM::Global>& CSMWorld::Data::getGlobals() const
@ -946,6 +866,11 @@ void CSMWorld::Data::setMetaData (const MetaData& metaData)
mMetaData.setRecord (0, record);
}
const CSMWorld::NpcAutoCalc& CSMWorld::Data::getNpcAutoCalc() const
{
return *mNpcAutoCalc;
}
QAbstractItemModel *CSMWorld::Data::getTableModel (const CSMWorld::UniversalId& id)
{
std::map<UniversalId::Type, QAbstractItemModel *>::iterator iter = mModelIndex.find (id.getType());
@ -1297,271 +1222,3 @@ 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<CSMWorld::IdTable*>(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<CSMWorld::IdTable*>(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
// - mPowers.mList[]
CSMWorld::IdTree *raceModel =
static_cast<CSMWorld::IdTree*>(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() <= 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() <= powersColumn && powersColumn <= 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 (; raceRow <= raceEnd; ++raceRow)
{
clearNpcStatsCache();
std::string raceId =
raceModel->data(raceModel->index(raceRow, idColumn)).toString().toUtf8().constData();
emit updateNpcAutocalc(2/*race*/, raceId);
}
}
void CSMWorld::Data::npcDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight)
{
// TODO: for now always recalculate
clearNpcStatsCache();
}
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);
}
void CSMWorld::Data::clearNpcStatsCache ()
{
for (std::map<std::string, CSMWorld::NpcStats*>::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;
int raceIndex = mRaces.searchId(npc.mRace);
int classIndex = mClasses.searchId(npc.mClass);
// this can happen when creating a new game from scratch
if (raceIndex == -1 || classIndex == -1)
return 0;
const ESM::Race *race = &mRaces.getRecord(raceIndex).get();
const ESM::Class *class_ = &mClasses.getRecord(classIndex).get();
bool autoCalc = npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS;
short level = npc.mNpdt52.mLevel;
if (autoCalc)
level = npc.mNpdt12.mLevel;
std::auto_ptr<CSMWorld::NpcStats> stats (new CSMWorld::NpcStats());
CSStore store(mGmsts, mSkills, mMagicEffects, mSpells);
if (autoCalc)
{
AutoCalc::autoCalcAttributesImpl (&npc, race, class_, level, *stats, &store);
stats->setHealth(autoCalculateHealth(level, class_, *stats));
stats->setMana(autoCalculateMana(*stats));
stats->setFatigue(autoCalculateFatigue(*stats));
AutoCalc::autoCalcSkillsImpl(&npc, race, class_, level, *stats, &store);
AutoCalc::autoCalculateSpells(race, *stats, &store);
}
else
{
for (std::vector<std::string>::const_iterator it = npc.mSpells.mList.begin();
it != npc.mSpells.mList.end(); ++it)
{
stats->addSpell(*it);
}
}
// update spell info
const std::vector<std::string> &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<CSMWorld::SpellInfo>& spells = stats->spells();
for (std::vector<SpellInfo>::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;
AutoCalc::calcWeakestSchool(spell, skills, school, skillTerm, &store);
float chance = calcAutoCastChance(spell, skills, attributes, school, &store);
stats->addCostAndChance((*it).mName, cost, (int)ceil(chance)); // percent
}
}
if (stats.get() == 0)
return 0;
CSMWorld::NpcStats *result = stats.release();
emit cacheNpcStats (npc.mId, result);
return result;
}
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<std::string, CSMWorld::NpcStats*>::const_iterator it = mNpcStatCache.find(id);
if (it != mNpcStatCache.end())
return it->second;
else
return 0;
}

View file

@ -62,6 +62,7 @@ namespace CSMWorld
class ResourcesManager;
class Resources;
class NpcStats;
class NpcAutoCalc;
class Data : public QObject
{
@ -109,7 +110,7 @@ namespace CSMWorld
std::vector<boost::shared_ptr<ESM::ESMReader> > mReaders;
std::map<std::string, NpcStats*> mNpcStatCache;
NpcAutoCalc *mNpcAutoCalc;
// not implemented
Data (const Data&);
@ -282,37 +283,17 @@ namespace CSMWorld
int count (RecordBase::State state) const;
///< Return number of top-level records with the given \a state.
NpcStats* npcAutoCalculate (const ESM::NPC& npc) const;
NpcStats* getCachedNpcData (const std::string& id) const;
const NpcAutoCalc& getNpcAutoCalc() 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);
};
}

View file

@ -0,0 +1,367 @@
#include "npcautocalc.hpp"
#include <QStringList>
#include <components/autocalc/autocalc.hpp>
#include <components/autocalc/autocalcspell.hpp>
#include <components/autocalc/store.hpp>
#include "npcstats.hpp"
#include "data.hpp"
#include "idtable.hpp"
#include "idtree.hpp"
namespace
{
class CSStore : public AutoCalc::StoreCommon
{
const CSMWorld::IdCollection<ESM::GameSetting>& mGmstTable;
const CSMWorld::IdCollection<ESM::Skill>& mSkillTable;
const CSMWorld::IdCollection<ESM::MagicEffect>& mMagicEffectTable;
const CSMWorld::NestedIdCollection<ESM::Spell>& mSpells;
public:
CSStore(const CSMWorld::IdCollection<ESM::GameSetting>& gmst,
const CSMWorld::IdCollection<ESM::Skill>& skills,
const CSMWorld::IdCollection<ESM::MagicEffect>& magicEffects,
const CSMWorld::NestedIdCollection<ESM::Spell>& spells)
: mGmstTable(gmst), mSkillTable(skills), mMagicEffectTable(magicEffects), mSpells(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 void getSpells(std::vector<ESM::Spell*>& spells)
{
// prepare data in a format used by OpenMW store
for (int index = 0; index < mSpells.getSize(); ++index)
spells.push_back(const_cast<ESM::Spell *>(&mSpells.getRecord(index).get()));
}
};
unsigned short autoCalculateMana(const AutoCalc::StatsBase& stats)
{
return stats.getBaseAttribute(ESM::Attribute::Intelligence) * 2;
}
unsigned short autoCalculateFatigue(const AutoCalc::StatsBase& stats)
{
return stats.getBaseAttribute(ESM::Attribute::Strength)
+ stats.getBaseAttribute(ESM::Attribute::Willpower)
+ stats.getBaseAttribute(ESM::Attribute::Agility)
+ stats.getBaseAttribute(ESM::Attribute::Endurance);
}
}
CSMWorld::NpcAutoCalc::NpcAutoCalc (const Data& data,
const IdTable *gmsts, const IdTable *skills, const IdTable *classes, const IdTree *races, const IdTree *objects)
: mData(data), mSkillModel(skills), mClassModel(classes), mRaceModel(races)
{
// for autocalc updates when gmst/race/class/skils tables change
connect (gmsts, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)),
this, SLOT (gmstDataChanged (const QModelIndex&, const QModelIndex&)));
connect (mSkillModel, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)),
this, SLOT (skillDataChanged (const QModelIndex&, const QModelIndex&)));
connect (mClassModel, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)),
this, SLOT (classDataChanged (const QModelIndex&, const QModelIndex&)));
connect (mRaceModel, 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*)));
}
CSMWorld::NpcAutoCalc::~NpcAutoCalc()
{
clearNpcStatsCache();
}
void CSMWorld::NpcAutoCalc::skillDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight)
{
// mData.mAttribute (affects attributes skill bonus autocalc)
// mData.mSpecialization (affects skills autocalc)
int attributeColumn = mSkillModel->findColumnIndex(CSMWorld::Columns::ColumnId_Attribute);
int specialisationColumn = mSkillModel->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::NpcAutoCalc::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
int attribute1Column = mClassModel->findColumnIndex(CSMWorld::Columns::ColumnId_Attribute1); // +1
int majorSkill1Column = mClassModel->findColumnIndex(CSMWorld::Columns::ColumnId_MajorSkill1); // +4
int minorSkill1Column = mClassModel->findColumnIndex(CSMWorld::Columns::ColumnId_MinorSkill1); // +4
int specialisationColumn = mClassModel->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 = mClassModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id);
for (int classRow = topLeft.row(); classRow <= bottomRight.row(); ++classRow)
{
clearNpcStatsCache();
std::string classId =
mClassModel->data(mClassModel->index(classRow, idColumn)).toString().toUtf8().constData();
emit updateNpcAutocalc(1/*class*/, classId);
}
}
void CSMWorld::NpcAutoCalc::raceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight)
{
// affects racial bonus attributes & skills
// - mData.mAttributeValues[]
// - mData.mBonus[].mBonus
// - mPowers.mList[]
int attrColumn = mRaceModel->findColumnIndex(CSMWorld::Columns::ColumnId_RaceAttributes);
int bonusColumn = mRaceModel->findColumnIndex(CSMWorld::Columns::ColumnId_RaceSkillBonus);
int powersColumn = mRaceModel->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() <= 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() <= powersColumn && powersColumn <= bottomRight.column()))
{
match = true; // maybe the whole table changed
}
}
if (!match)
return;
// update autocalculated attributes/skills of every NPC with matching race
int idColumn = mRaceModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id);
for (; raceRow <= raceEnd; ++raceRow)
{
clearNpcStatsCache();
std::string raceId =
mRaceModel->data(mRaceModel->index(raceRow, idColumn)).toString().toUtf8().constData();
emit updateNpcAutocalc(2/*race*/, raceId);
}
}
void CSMWorld::NpcAutoCalc::npcDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight)
{
// TODO: for now always recalculate
clearNpcStatsCache();
// TODO: check if below signal slows things down
std::string empty;
emit updateNpcAutocalc(0/*all*/, empty);
}
void CSMWorld::NpcAutoCalc::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(mData.getGmsts().getRecord(row).get().mId.c_str()))
{
match = true;
break;
}
}
if (!match)
return;
clearNpcStatsCache();
std::string empty;
emit updateNpcAutocalc(0/*all*/, empty);
}
void CSMWorld::NpcAutoCalc::clearNpcStatsCache ()
{
for (std::map<const std::string, CSMWorld::NpcStats*>::iterator it (mNpcStatCache.begin());
it != mNpcStatCache.end(); ++it)
delete it->second;
mNpcStatCache.clear();
}
CSMWorld::NpcStats* CSMWorld::NpcAutoCalc::npcAutoCalculate(const ESM::NPC& npc) const
{
CSMWorld::NpcStats *cachedStats = getCachedNpcData (npc.mId);
if (cachedStats)
return cachedStats;
int raceIndex = mData.getRaces().searchId(npc.mRace);
int classIndex = mData.getClasses().searchId(npc.mClass);
// this can happen when creating a new game from scratch
if (raceIndex == -1 || classIndex == -1)
return 0;
const ESM::Race *race = &mData.getRaces().getRecord(raceIndex).get();
const ESM::Class *class_ = &mData.getClasses().getRecord(classIndex).get();
bool autoCalc = npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS;
short level = npc.mNpdt52.mLevel;
if (autoCalc)
level = npc.mNpdt12.mLevel;
std::auto_ptr<CSMWorld::NpcStats> stats (new CSMWorld::NpcStats());
CSStore store(mData.getGmsts(), mData.getSkills(), mData.getMagicEffects(), static_cast<const CSMWorld::NestedIdCollection<ESM::Spell>&>(mData.getSpells()));
if (autoCalc)
{
AutoCalc::autoCalcAttributesImpl (&npc, race, class_, level, *stats, &store);
stats->setHealth(autoCalculateHealth(level, class_, *stats));
stats->setMana(autoCalculateMana(*stats));
stats->setFatigue(autoCalculateFatigue(*stats));
AutoCalc::autoCalcSkillsImpl(&npc, race, class_, level, *stats, &store);
AutoCalc::autoCalculateSpells(race, *stats, &store);
}
else
{
for (std::vector<std::string>::const_iterator it = npc.mSpells.mList.begin();
it != npc.mSpells.mList.end(); ++it)
{
stats->addSpell(*it);
}
}
// update spell info
const std::vector<std::string> &racePowers = race->mPowers.mList;
for (unsigned int i = 0; i < racePowers.size(); ++i)
{
int type = -1;
int spellIndex = mData.getSpells().searchId(racePowers[i]);
if (spellIndex != -1)
type = mData.getSpells().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<CSMWorld::SpellInfo>& spells = stats->spells();
for (std::vector<SpellInfo>::const_iterator it = spells.begin(); it != spells.end(); ++it)
{
int cost = -1;
int spellIndex = mData.getSpells().searchId((*it).mName);
const ESM::Spell* spell = 0;
if (spellIndex != -1)
{
spell = &mData.getSpells().getRecord(spellIndex).get();
cost = spell->mData.mCost;
int school;
float skillTerm;
AutoCalc::calcWeakestSchool(spell, skills, school, skillTerm, &store);
float chance = calcAutoCastChance(spell, skills, attributes, school, &store);
stats->addCostAndChance((*it).mName, cost, (int)ceil(chance)); // percent
}
}
if (stats.get() == 0)
return 0;
CSMWorld::NpcStats *result = stats.release();
//emit cacheNpcStats (npc.mId, result);
mNpcStatCache[npc.mId] = result;
return result;
}
//void CSMWorld::NpcAutoCalc::cacheNpcStatsEvent (const std::string& id, CSMWorld::NpcStats *stats)
//{
//mNpcStatCache[id] = stats;
//}
CSMWorld::NpcStats* CSMWorld::NpcAutoCalc::getCachedNpcData (const std::string& id) const
{
std::map<const std::string, CSMWorld::NpcStats*>::const_iterator it = mNpcStatCache.find(id);
if (it != mNpcStatCache.end())
return it->second;
else
return 0;
}

View file

@ -0,0 +1,75 @@
#ifndef CSM_WORLD_NPCAUTOCALC_H
#define CSM_WORLD_NPCAUTOCALC_H
#include <string>
#include <map>
#include <QObject>
#include <QModelIndex>
namespace ESM
{
struct NPC;
}
namespace CSMWorld
{
class Data;
class NpcStats;
class IdTable;
class IdTree;
class NpcAutoCalc : public QObject
{
Q_OBJECT
const Data& mData;
const IdTable *mSkillModel;
const IdTable *mClassModel;
const IdTree *mRaceModel;
mutable std::map<const std::string, NpcStats*> mNpcStatCache;
public:
NpcAutoCalc (const Data& data, const IdTable *gmsts, const IdTable *skills, const IdTable *classes,
const IdTree *races, const IdTree *objects);
~NpcAutoCalc ();
NpcStats* npcAutoCalculate (const ESM::NPC& npc) const;
private:
// not implemented
NpcAutoCalc (const NpcAutoCalc&);
NpcAutoCalc& operator= (const NpcAutoCalc&);
NpcStats* getCachedNpcData (const std::string& id) const;
void clearNpcStatsCache ();
signals:
// 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:
// 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);
};
}
#endif // CSM_WORLD_NPCAUTOCALC_H

View file

@ -12,6 +12,7 @@
#include "usertype.hpp"
#include "idtree.hpp"
#include "npcstats.hpp"
#include "npcautocalc.hpp"
CSMWorld::PotionColumns::PotionColumns (const InventoryColumns& columns)
: InventoryColumns (columns) {}
@ -777,7 +778,7 @@ void CSMWorld::NpcRefIdAdapter::setData (const RefIdColumn *column, RefIdData& d
{
if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
{
CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc);
CSMWorld::NpcStats *stats = mData.getNpcAutoCalc().npcAutoCalculate(npc);
if (!stats)
{
record.setModified (npc);
@ -817,7 +818,7 @@ void CSMWorld::NpcRefIdAdapter::setData (const RefIdColumn *column, RefIdData& d
{
npc.mNpdtType = ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS;
npc.mNpdt12.mLevel = npc.mNpdt52.mLevel; // for NPC's loaded as non-autocalc
mData.npcAutoCalculate(npc);
mData.getNpcAutoCalc().npcAutoCalculate(npc);
}
}
}
@ -888,7 +889,7 @@ QVariant CSMWorld::NpcAttributesRefIdAdapter::getNestedData (const RefIdColumn *
else if (subColIndex == 1)
if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
{
CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc);
CSMWorld::NpcStats *stats = mData.getNpcAutoCalc().npcAutoCalculate(npc);
if (!stats)
return QVariant();
@ -1020,7 +1021,7 @@ QVariant CSMWorld::NpcSkillsRefIdAdapter::getNestedData (const RefIdColumn *colu
{
if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
{
CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc);
CSMWorld::NpcStats *stats = mData.getNpcAutoCalc().npcAutoCalculate(npc);
if (!stats)
return QVariant();
@ -1108,7 +1109,7 @@ QVariant CSMWorld::NpcMiscRefIdAdapter::getNestedData (const RefIdColumn *column
if (autoCalc)
{
CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc);
CSMWorld::NpcStats *stats = mData.getNpcAutoCalc().npcAutoCalculate(npc);
switch (subColIndex)
{
@ -1732,7 +1733,7 @@ QVariant NestedSpellRefIdAdapter<ESM::NPC>::getNestedData (const RefIdColumn *co
const Record<ESM::NPC>& record =
static_cast<const Record<ESM::NPC>&> (data.getRecord (RefIdData::LocalIndex (index, mType)));
CSMWorld::NpcStats *stats = mData.npcAutoCalculate(record.get());
CSMWorld::NpcStats *stats = mData.getNpcAutoCalc().npcAutoCalculate(record.get());
if (!stats)
return QVariant();
@ -1766,7 +1767,7 @@ int NestedSpellRefIdAdapter<ESM::NPC>::getNestedRowsCount(const RefIdColumn *col
const Record<ESM::NPC>& record =
static_cast<const Record<ESM::NPC>&> (data.getRecord (RefIdData::LocalIndex (index, mType)));
CSMWorld::NpcStats *stats = mData.npcAutoCalculate(record.get());
CSMWorld::NpcStats *stats = mData.getNpcAutoCalc().npcAutoCalculate(record.get());
if (!stats)
return 0;