mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-03-30 11:06:43 +00:00
Move NPC autocal code out to a separate class.
This commit is contained in:
parent
c7c0023ed2
commit
04c5c0d82a
6 changed files with 466 additions and 385 deletions
|
@ -19,7 +19,7 @@ opencs_hdrs_noqt (model/doc
|
||||||
|
|
||||||
opencs_units (model/world
|
opencs_units (model/world
|
||||||
idtable idtableproxymodel regionmap data commanddispatcher idtablebase resourcetable nestedtableproxymodel idtree infotableproxymodel
|
idtable idtableproxymodel regionmap data commanddispatcher idtablebase resourcetable nestedtableproxymodel idtree infotableproxymodel
|
||||||
pathgridcommands
|
pathgridcommands npcautocalc
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -10,10 +10,6 @@
|
||||||
#include <components/esm/loadglob.hpp>
|
#include <components/esm/loadglob.hpp>
|
||||||
#include <components/esm/cellref.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 "idtable.hpp"
|
||||||
#include "idtree.hpp"
|
#include "idtree.hpp"
|
||||||
#include "columnimp.hpp"
|
#include "columnimp.hpp"
|
||||||
|
@ -23,70 +19,7 @@
|
||||||
#include "resourcetable.hpp"
|
#include "resourcetable.hpp"
|
||||||
#include "nestedcoladapterimp.hpp"
|
#include "nestedcoladapterimp.hpp"
|
||||||
#include "npcstats.hpp"
|
#include "npcstats.hpp"
|
||||||
|
#include "npcautocalc.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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CSMWorld::Data::addModel (QAbstractItemModel *model, UniversalId::Type type, bool update)
|
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)
|
CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager)
|
||||||
: mEncoder (encoding), mPathgrids (mCells), mReferenceables(self()), mRefs (mCells),
|
: mEncoder (encoding), mPathgrids (mCells), mReferenceables (self()), mRefs (mCells),
|
||||||
mResourcesManager (resourcesManager), mReader (0), mDialogue (0), mReaderIndex(0)
|
mResourcesManager (resourcesManager), mReader (0), mDialogue (0), mReaderIndex(0),
|
||||||
|
mNpcAutoCalc (0)
|
||||||
{
|
{
|
||||||
int index = 0;
|
int index = 0;
|
||||||
|
|
||||||
|
@ -621,32 +555,18 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc
|
||||||
CSMWorld::IdTree *objects =
|
CSMWorld::IdTree *objects =
|
||||||
static_cast<CSMWorld::IdTree*>(getTableModel(UniversalId::Type_Referenceable));
|
static_cast<CSMWorld::IdTree*>(getTableModel(UniversalId::Type_Referenceable));
|
||||||
|
|
||||||
connect (gmsts, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)),
|
mNpcAutoCalc = new NpcAutoCalc (self(), gmsts, skills, classes, races, objects);
|
||||||
this, SLOT (gmstDataChanged (const QModelIndex&, const QModelIndex&)));
|
|
||||||
connect (skills, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)),
|
|
||||||
this, SLOT (skillDataChanged (const QModelIndex&, const QModelIndex&)));
|
|
||||||
connect (classes, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)),
|
|
||||||
this, SLOT (classDataChanged (const QModelIndex&, const QModelIndex&)));
|
|
||||||
connect (races, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)),
|
|
||||||
this, SLOT (raceDataChanged (const QModelIndex&, const QModelIndex&)));
|
|
||||||
connect (objects, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)),
|
|
||||||
this, SLOT (npcDataChanged (const QModelIndex&, const QModelIndex&)));
|
|
||||||
connect (this, SIGNAL (updateNpcAutocalc (int, const std::string&)),
|
|
||||||
objects, SLOT (updateNpcAutocalc (int, const std::string&)));
|
|
||||||
connect (this, SIGNAL (cacheNpcStats (const std::string&, NpcStats*)),
|
|
||||||
this, SLOT (cacheNpcStatsEvent (const std::string&, NpcStats*)));
|
|
||||||
|
|
||||||
mRefLoadCache.clear(); // clear here rather than startLoading() and continueLoading() for multiple content files
|
mRefLoadCache.clear(); // clear here rather than startLoading() and continueLoading() for multiple content files
|
||||||
}
|
}
|
||||||
|
|
||||||
CSMWorld::Data::~Data()
|
CSMWorld::Data::~Data()
|
||||||
{
|
{
|
||||||
clearNpcStatsCache();
|
|
||||||
|
|
||||||
for (std::vector<QAbstractItemModel *>::iterator iter (mModels.begin()); iter!=mModels.end(); ++iter)
|
for (std::vector<QAbstractItemModel *>::iterator iter (mModels.begin()); iter!=mModels.end(); ++iter)
|
||||||
delete *iter;
|
delete *iter;
|
||||||
|
|
||||||
delete mReader;
|
delete mReader;
|
||||||
|
delete mNpcAutoCalc;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CSMWorld::IdCollection<ESM::Global>& CSMWorld::Data::getGlobals() const
|
const CSMWorld::IdCollection<ESM::Global>& CSMWorld::Data::getGlobals() const
|
||||||
|
@ -946,6 +866,11 @@ void CSMWorld::Data::setMetaData (const MetaData& metaData)
|
||||||
mMetaData.setRecord (0, record);
|
mMetaData.setRecord (0, record);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CSMWorld::NpcAutoCalc& CSMWorld::Data::getNpcAutoCalc() const
|
||||||
|
{
|
||||||
|
return *mNpcAutoCalc;
|
||||||
|
}
|
||||||
|
|
||||||
QAbstractItemModel *CSMWorld::Data::getTableModel (const CSMWorld::UniversalId& id)
|
QAbstractItemModel *CSMWorld::Data::getTableModel (const CSMWorld::UniversalId& id)
|
||||||
{
|
{
|
||||||
std::map<UniversalId::Type, QAbstractItemModel *>::iterator iter = mModelIndex.find (id.getType());
|
std::map<UniversalId::Type, QAbstractItemModel *>::iterator iter = mModelIndex.find (id.getType());
|
||||||
|
@ -1297,271 +1222,3 @@ const CSMWorld::Data& CSMWorld::Data::self ()
|
||||||
{
|
{
|
||||||
return *this;
|
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;
|
|
||||||
}
|
|
||||||
|
|
|
@ -62,6 +62,7 @@ namespace CSMWorld
|
||||||
class ResourcesManager;
|
class ResourcesManager;
|
||||||
class Resources;
|
class Resources;
|
||||||
class NpcStats;
|
class NpcStats;
|
||||||
|
class NpcAutoCalc;
|
||||||
|
|
||||||
class Data : public QObject
|
class Data : public QObject
|
||||||
{
|
{
|
||||||
|
@ -109,7 +110,7 @@ namespace CSMWorld
|
||||||
|
|
||||||
std::vector<boost::shared_ptr<ESM::ESMReader> > mReaders;
|
std::vector<boost::shared_ptr<ESM::ESMReader> > mReaders;
|
||||||
|
|
||||||
std::map<std::string, NpcStats*> mNpcStatCache;
|
NpcAutoCalc *mNpcAutoCalc;
|
||||||
|
|
||||||
// not implemented
|
// not implemented
|
||||||
Data (const Data&);
|
Data (const Data&);
|
||||||
|
@ -282,37 +283,17 @@ namespace CSMWorld
|
||||||
int count (RecordBase::State state) const;
|
int count (RecordBase::State state) const;
|
||||||
///< Return number of top-level records with the given \a state.
|
///< Return number of top-level records with the given \a state.
|
||||||
|
|
||||||
NpcStats* npcAutoCalculate (const ESM::NPC& npc) const;
|
const NpcAutoCalc& getNpcAutoCalc() const;
|
||||||
|
|
||||||
NpcStats* getCachedNpcData (const std::string& id) const;
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
||||||
void idListChanged();
|
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:
|
private slots:
|
||||||
|
|
||||||
void dataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight);
|
void dataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight);
|
||||||
|
|
||||||
void rowsChanged (const QModelIndex& parent, int start, int end);
|
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);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
367
apps/opencs/model/world/npcautocalc.cpp
Normal file
367
apps/opencs/model/world/npcautocalc.cpp
Normal 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;
|
||||||
|
}
|
75
apps/opencs/model/world/npcautocalc.hpp
Normal file
75
apps/opencs/model/world/npcautocalc.hpp
Normal 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
|
|
@ -12,6 +12,7 @@
|
||||||
#include "usertype.hpp"
|
#include "usertype.hpp"
|
||||||
#include "idtree.hpp"
|
#include "idtree.hpp"
|
||||||
#include "npcstats.hpp"
|
#include "npcstats.hpp"
|
||||||
|
#include "npcautocalc.hpp"
|
||||||
|
|
||||||
CSMWorld::PotionColumns::PotionColumns (const InventoryColumns& columns)
|
CSMWorld::PotionColumns::PotionColumns (const InventoryColumns& columns)
|
||||||
: 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)
|
if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
|
||||||
{
|
{
|
||||||
CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc);
|
CSMWorld::NpcStats *stats = mData.getNpcAutoCalc().npcAutoCalculate(npc);
|
||||||
if (!stats)
|
if (!stats)
|
||||||
{
|
{
|
||||||
record.setModified (npc);
|
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.mNpdtType = ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS;
|
||||||
npc.mNpdt12.mLevel = npc.mNpdt52.mLevel; // for NPC's loaded as non-autocalc
|
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)
|
else if (subColIndex == 1)
|
||||||
if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
|
if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
|
||||||
{
|
{
|
||||||
CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc);
|
CSMWorld::NpcStats *stats = mData.getNpcAutoCalc().npcAutoCalculate(npc);
|
||||||
if (!stats)
|
if (!stats)
|
||||||
return QVariant();
|
return QVariant();
|
||||||
|
|
||||||
|
@ -1020,7 +1021,7 @@ QVariant CSMWorld::NpcSkillsRefIdAdapter::getNestedData (const RefIdColumn *colu
|
||||||
{
|
{
|
||||||
if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
|
if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
|
||||||
{
|
{
|
||||||
CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc);
|
CSMWorld::NpcStats *stats = mData.getNpcAutoCalc().npcAutoCalculate(npc);
|
||||||
if (!stats)
|
if (!stats)
|
||||||
return QVariant();
|
return QVariant();
|
||||||
|
|
||||||
|
@ -1108,7 +1109,7 @@ QVariant CSMWorld::NpcMiscRefIdAdapter::getNestedData (const RefIdColumn *column
|
||||||
|
|
||||||
if (autoCalc)
|
if (autoCalc)
|
||||||
{
|
{
|
||||||
CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc);
|
CSMWorld::NpcStats *stats = mData.getNpcAutoCalc().npcAutoCalculate(npc);
|
||||||
|
|
||||||
switch (subColIndex)
|
switch (subColIndex)
|
||||||
{
|
{
|
||||||
|
@ -1732,7 +1733,7 @@ QVariant NestedSpellRefIdAdapter<ESM::NPC>::getNestedData (const RefIdColumn *co
|
||||||
const Record<ESM::NPC>& record =
|
const Record<ESM::NPC>& record =
|
||||||
static_cast<const Record<ESM::NPC>&> (data.getRecord (RefIdData::LocalIndex (index, mType)));
|
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)
|
if (!stats)
|
||||||
return QVariant();
|
return QVariant();
|
||||||
|
|
||||||
|
@ -1766,7 +1767,7 @@ int NestedSpellRefIdAdapter<ESM::NPC>::getNestedRowsCount(const RefIdColumn *col
|
||||||
const Record<ESM::NPC>& record =
|
const Record<ESM::NPC>& record =
|
||||||
static_cast<const Record<ESM::NPC>&> (data.getRecord (RefIdData::LocalIndex (index, mType)));
|
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)
|
if (!stats)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue