1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-01-16 17:19:56 +00:00

Merge pull request #4 from cc9cii/npc-autocalc

npc autocalc
This commit is contained in:
cc9cii 2015-06-27 09:44:47 +10:00
commit 448a13b6b2
31 changed files with 1935 additions and 357 deletions

View file

@ -26,7 +26,7 @@ opencs_units_noqt (model/world
universalid record commands columnbase scriptcontext cell refidcollection universalid record commands columnbase scriptcontext cell refidcollection
refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection tablemimedata cellcoordinates cellselection resources resourcesmanager scope refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection tablemimedata cellcoordinates cellselection resources resourcesmanager scope
pathgrid landtexture land nestedtablewrapper nestedcollection nestedcoladapterimp nestedinfocollection pathgrid landtexture land nestedtablewrapper nestedcollection nestedcoladapterimp nestedinfocollection
idcompletionmanager metadata idcompletionmanager npcstats metadata
) )
opencs_hdrs_noqt (model/world opencs_hdrs_noqt (model/world

View file

@ -315,6 +315,10 @@ namespace CSMWorld
{ ColumnId_FileDescription, "File Description" }, { ColumnId_FileDescription, "File Description" },
{ ColumnId_Author, "Author" }, { ColumnId_Author, "Author" },
{ ColumnId_SpellSrc, "From Race" },
{ ColumnId_SpellCost, "Cast Cost" },
{ ColumnId_SpellChance, "Cast Chance" },
{ ColumnId_UseValue1, "Use value 1" }, { ColumnId_UseValue1, "Use value 1" },
{ ColumnId_UseValue2, "Use value 2" }, { ColumnId_UseValue2, "Use value 2" },
{ ColumnId_UseValue3, "Use value 3" }, { ColumnId_UseValue3, "Use value 3" },

View file

@ -306,6 +306,10 @@ namespace CSMWorld
ColumnId_FileDescription = 276, ColumnId_FileDescription = 276,
ColumnId_Author = 277, ColumnId_Author = 277,
ColumnId_SpellSrc = 278,
ColumnId_SpellCost = 279,
ColumnId_SpellChance = 280,
// Allocated to a separate value range, so we don't get a collision should we ever need // Allocated to a separate value range, so we don't get a collision should we ever need
// to extend the number of use values. // to extend the number of use values.
ColumnId_UseValue1 = 0x10000, ColumnId_UseValue1 = 0x10000,

View file

@ -11,6 +11,10 @@
#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"
@ -19,6 +23,71 @@
#include "resourcesmanager.hpp" #include "resourcesmanager.hpp"
#include "resourcetable.hpp" #include "resourcetable.hpp"
#include "nestedcoladapterimp.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);
}
}
void CSMWorld::Data::addModel (QAbstractItemModel *model, UniversalId::Type type, bool update) void CSMWorld::Data::addModel (QAbstractItemModel *model, UniversalId::Type type, bool update)
{ {
@ -61,7 +130,7 @@ 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), 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)
{ {
int index = 0; int index = 0;
@ -202,7 +271,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc
mSpells.addColumn (new RecordStateColumn<ESM::Spell>); mSpells.addColumn (new RecordStateColumn<ESM::Spell>);
mSpells.addColumn (new FixedRecordTypeColumn<ESM::Spell> (UniversalId::Type_Spell)); mSpells.addColumn (new FixedRecordTypeColumn<ESM::Spell> (UniversalId::Type_Spell));
mSpells.addColumn (new NameColumn<ESM::Spell>); mSpells.addColumn (new NameColumn<ESM::Spell>);
mSpells.addColumn (new SpellTypeColumn<ESM::Spell>); mSpells.addColumn (new SpellTypeColumn<ESM::Spell>); // ColumnId_SpellType
mSpells.addColumn (new CostColumn<ESM::Spell>); mSpells.addColumn (new CostColumn<ESM::Spell>);
mSpells.addColumn (new FlagColumn<ESM::Spell> (Columns::ColumnId_AutoCalc, 0x1)); mSpells.addColumn (new FlagColumn<ESM::Spell> (Columns::ColumnId_AutoCalc, 0x1));
mSpells.addColumn (new FlagColumn<ESM::Spell> (Columns::ColumnId_StarterSpell, 0x2)); mSpells.addColumn (new FlagColumn<ESM::Spell> (Columns::ColumnId_StarterSpell, 0x2));
@ -525,11 +594,40 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc
UniversalId::Type_Video); UniversalId::Type_Video);
addModel (new IdTable (&mMetaData), UniversalId::Type_MetaData); addModel (new IdTable (&mMetaData), UniversalId::Type_MetaData);
// for autocalc updates when gmst/race/class/skils tables change
CSMWorld::IdTable *gmsts =
static_cast<CSMWorld::IdTable*>(getTableModel(UniversalId::Type_Gmst));
CSMWorld::IdTable *skills =
static_cast<CSMWorld::IdTable*>(getTableModel(UniversalId::Type_Skill));
CSMWorld::IdTable *classes =
static_cast<CSMWorld::IdTable*>(getTableModel(UniversalId::Type_Class));
CSMWorld::IdTree *races =
static_cast<CSMWorld::IdTree*>(getTableModel(UniversalId::Type_Race));
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*)));
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;
@ -1161,3 +1259,270 @@ void CSMWorld::Data::rowsChanged (const QModelIndex& parent, int start, int end)
{ {
emit idListChanged(); emit idListChanged();
} }
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;
const ESM::Race *race = &mRaces.getRecord(npc.mRace).get();
const ESM::Class *class_ = &mClasses.getRecord(npc.mClass).get();
bool autoCalc = npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS;
short level = npc.mNpdt52.mLevel;
if (autoCalc)
level = npc.mNpdt12.mLevel;
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

@ -61,6 +61,7 @@ namespace CSMWorld
{ {
class ResourcesManager; class ResourcesManager;
class Resources; class Resources;
class NpcStats;
class Data : public QObject class Data : public QObject
{ {
@ -108,6 +109,8 @@ namespace CSMWorld
std::vector<boost::shared_ptr<ESM::ESMReader> > mReaders; std::vector<boost::shared_ptr<ESM::ESMReader> > mReaders;
std::map<std::string, NpcStats*> mNpcStatCache;
// not implemented // not implemented
Data (const Data&); Data (const Data&);
Data& operator= (const Data&); Data& operator= (const Data&);
@ -121,6 +124,10 @@ namespace CSMWorld
static int count (RecordBase::State state, const CollectionBase& collection); static int count (RecordBase::State state, const CollectionBase& collection);
const Data& self ();
void clearNpcStatsCache ();
public: public:
Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager); Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager);
@ -269,15 +276,37 @@ 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;
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);
}; };
} }

View file

@ -261,3 +261,8 @@ CSMWorld::NestedTableWrapperBase* CSMWorld::IdTree::nestedTable(const QModelInde
return mNestedCollection->nestedTable(index.row(), index.column()); return mNestedCollection->nestedTable(index.row(), index.column());
} }
void CSMWorld::IdTree::updateNpcAutocalc (int type, const std::string& id)
{
emit refreshNpcDialogue (type, id);
}

View file

@ -73,11 +73,17 @@ namespace CSMWorld
virtual bool hasChildren (const QModelIndex& index) const; virtual bool hasChildren (const QModelIndex& index) const;
signals: signals:
void resetStart(const QString& id); void resetStart(const QString& id);
void resetEnd(const QString& id); void resetEnd(const QString& id);
void refreshNpcDialogue (int type, const std::string& id);
public slots:
void updateNpcAutocalc (int type, const std::string& id);
}; };
} }

View file

@ -6,6 +6,7 @@
#include "idcollection.hpp" #include "idcollection.hpp"
#include "pathgrid.hpp" #include "pathgrid.hpp"
#include "info.hpp" #include "info.hpp"
#include "usertype.hpp"
namespace CSMWorld namespace CSMWorld
{ {
@ -1069,23 +1070,66 @@ namespace CSMWorld
switch (subColIndex) switch (subColIndex)
{ {
case 0: return isInterior; case 0: return isInterior;
case 1: return (isInterior && !behaveLikeExterior) ? case 1:
cell.mAmbi.mAmbient : QVariant(QVariant::UserType); {
case 2: return (isInterior && !behaveLikeExterior) ? if (isInterior && !behaveLikeExterior)
cell.mAmbi.mSunlight : QVariant(QVariant::UserType); return cell.mAmbi.mAmbient;
case 3: return (isInterior && !behaveLikeExterior) ? else
cell.mAmbi.mFog : QVariant(QVariant::UserType); {
case 4: return (isInterior && !behaveLikeExterior) ? UserInt i(cell.mAmbi.mAmbient);
cell.mAmbi.mFogDensity : QVariant(QVariant::UserType); 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: case 5:
{ {
if (isInterior && !behaveLikeExterior && interiorWater) if (isInterior && !behaveLikeExterior && interiorWater)
return cell.mWater; return cell.mWater;
else 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 ? //case 7: return isInterior ?
//behaveLikeExterior : QVariant(QVariant::UserType); //behaveLikeExterior : QVariant(QVariant::UserType);
default: throw std::runtime_error("Cell subcolumn index out of range"); default: throw std::runtime_error("Cell subcolumn index out of range");

View file

@ -0,0 +1,138 @@
#include "npcstats.hpp"
#include <components/esm/loadnpc.hpp>
#include <components/esm/loadspel.hpp>
namespace CSMWorld
{
NpcStats::NpcStats() : mHealth(0), mMana(0), mFatigue(0)
{
for (int i = 0; i < ESM::Skill::Length; ++i)
mSkill[i] = 0;
}
NpcStats::NpcStats(const NpcStats &other)
{
for (int i = 0; i < ESM::Attribute::Length; ++i)
mAttr[i] = other.mAttr[i];
mSpells = other.mSpells;
for (int i = 0; i < ESM::Skill::Length; ++i)
mSkill[i] = 0;
mHealth = other.mHealth;
mMana = other.mMana;
mFatigue = other.mFatigue;
}
NpcStats::~NpcStats()
{}
unsigned char NpcStats::getBaseAttribute(int index) const
{
if (index < 0 || index >= ESM::Attribute::Length)
throw std::runtime_error("attrib index out of bounds");
return mAttr[index];
}
void NpcStats::setAttribute(int index, unsigned char value)
{
if (index < 0 || index >= ESM::Attribute::Length)
throw std::runtime_error("attrib index out of bounds");
mAttr[index] = value;
}
void NpcStats::addSpell(const std::string& id)
{
struct SpellInfo info;
info.mName = id;
info.mType = ESM::Spell::ST_Spell; // default type from autocalc
info.mFromRace = false;
info.mCost = 0;
info.mChance = 0;
mSpells.insert(mSpells.begin(), info);
}
void NpcStats::addPowers(const std::string& id, int type)
{
struct SpellInfo info;
info.mName = id;
info.mType = type;
info.mFromRace = true;
info.mCost = 0;
info.mChance = 0;
mSpells.push_back(info);
}
void NpcStats::addCostAndChance(const std::string& id, int cost, int chance)
{
// usually only a few spells, so simply iterate through rather than keeping a separate
// lookup index or map
for (std::vector<SpellInfo>::iterator it = mSpells.begin(); it != mSpells.end(); ++it)
{
if ((*it).mName == id)
{
(*it).mCost = cost;
(*it).mChance = chance;
return;
}
}
}
const std::vector<SpellInfo>& NpcStats::spells() const
{
return mSpells;
}
unsigned char NpcStats::getBaseSkill(int index) const
{
if (index < 0 || index >= ESM::Skill::Length)
throw std::runtime_error("skill index out of bounds");
return mSkill[index];
}
void NpcStats::setBaseSkill(int index, unsigned char value)
{
if (index < 0 || index >= ESM::Skill::Length)
throw std::runtime_error("skill index out of bounds");
mSkill[index] = value;
}
unsigned short NpcStats::getHealth()
{
return mHealth;
}
void NpcStats::setHealth(unsigned short health)
{
mHealth = health;
}
unsigned short NpcStats::getMana()
{
return mMana;
}
void NpcStats::setMana(unsigned short mana)
{
mMana = mana;
}
unsigned short NpcStats::getFatigue()
{
return mFatigue;
}
void NpcStats::setFatigue(unsigned short fatigue)
{
mFatigue = fatigue;
}
}

View file

@ -0,0 +1,74 @@
#ifndef CSM_WORLD_NPCSTATS_H
#define CSM_WORLD_NPCSTATS_H
#include <vector>
#include <QMetaType>
#include <components/esm/attr.hpp>
#include <components/esm/loadskil.hpp>
#include <components/autocalc/autocalc.hpp>
namespace CSMWorld
{
struct SpellInfo
{
std::string mName;
int mType;
bool mFromRace;
int mCost;
int mChance;
};
class NpcStats : public AutoCalc::StatsBase
{
int mAttr[ESM::Attribute::Length];
std::vector<SpellInfo> mSpells;
int mSkill[ESM::Skill::Length];
unsigned short mHealth;
unsigned short mMana;
unsigned short mFatigue;
public:
NpcStats();
NpcStats(const NpcStats &other);
~NpcStats();
virtual unsigned char getBaseAttribute(int index) const;
virtual void setAttribute(int index, unsigned char value);
virtual void addSpell(const std::string& id);
void addPowers(const std::string& id, int type);
void addCostAndChance(const std::string& id, int cost, int chance);
const std::vector<SpellInfo>& spells() const;
virtual unsigned char getBaseSkill(int index) const;
virtual void setBaseSkill(int index, unsigned char value);
unsigned short getHealth();
void setHealth(unsigned short health);
unsigned short getMana();
void setMana(unsigned short mana);
unsigned short getFatigue();
void setFatigue(unsigned short fatigue);
};
}
Q_DECLARE_METATYPE(CSMWorld::NpcStats*)
#endif // CSM_WORLD_NPCSTATS_H

View file

@ -5,7 +5,12 @@
#include <utility> #include <utility>
#include <components/esm/loadcont.hpp> #include <components/esm/loadcont.hpp>
#include <components/esm/attr.hpp>
#include "nestedtablewrapper.hpp" #include "nestedtablewrapper.hpp"
#include "usertype.hpp"
#include "idtree.hpp"
#include "npcstats.hpp"
CSMWorld::PotionColumns::PotionColumns (const InventoryColumns& columns) CSMWorld::PotionColumns::PotionColumns (const InventoryColumns& columns)
: InventoryColumns (columns) {} : InventoryColumns (columns) {}
@ -553,8 +558,8 @@ CSMWorld::NpcColumns::NpcColumns (const ActorColumns& actorColumns)
mMisc(NULL) mMisc(NULL)
{} {}
CSMWorld::NpcRefIdAdapter::NpcRefIdAdapter (const NpcColumns& columns) CSMWorld::NpcRefIdAdapter::NpcRefIdAdapter (const NpcColumns& columns, const CSMWorld::Data& data)
: ActorRefIdAdapter<ESM::NPC> (UniversalId::Type_Npc, columns), mColumns (columns) : ActorRefIdAdapter<ESM::NPC> (UniversalId::Type_Npc, columns), mColumns (columns), mData(data)
{} {}
QVariant CSMWorld::NpcRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, int index) QVariant CSMWorld::NpcRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, int index)
@ -629,8 +634,47 @@ void CSMWorld::NpcRefIdAdapter::setData (const RefIdColumn *column, RefIdData& d
npc.mFlags &= ~iter->second; npc.mFlags &= ~iter->second;
if (iter->second == ESM::NPC::Autocalc) if (iter->second == ESM::NPC::Autocalc)
npc.mNpdtType = (value.toInt() != 0) ? ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS {
: ESM::NPC::NPC_DEFAULT; if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
{
CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc);
// update npc
npc.mNpdtType = ESM::NPC::NPC_DEFAULT;
npc.mNpdt52.mLevel = npc.mNpdt12.mLevel;
npc.mNpdt52.mStrength = stats->getBaseAttribute(ESM::Attribute::Strength);
npc.mNpdt52.mIntelligence = stats->getBaseAttribute(ESM::Attribute::Intelligence);
npc.mNpdt52.mWillpower = stats->getBaseAttribute(ESM::Attribute::Willpower);
npc.mNpdt52.mAgility = stats->getBaseAttribute(ESM::Attribute::Agility);
npc.mNpdt52.mSpeed = stats->getBaseAttribute(ESM::Attribute::Speed);
npc.mNpdt52.mEndurance = stats->getBaseAttribute(ESM::Attribute::Endurance);
npc.mNpdt52.mPersonality = stats->getBaseAttribute(ESM::Attribute::Personality);
npc.mNpdt52.mLuck = stats->getBaseAttribute(ESM::Attribute::Luck);
for (int i = 0; i < ESM::Skill::Length; ++i)
{
npc.mNpdt52.mSkills[i] = stats->getBaseSkill(i);
}
npc.mNpdt52.mHealth = stats->getHealth();
npc.mNpdt52.mMana = stats->getMana();
npc.mNpdt52.mFatigue = stats->getFatigue();
npc.mNpdt52.mDisposition = npc.mNpdt12.mDisposition;
npc.mNpdt52.mReputation = npc.mNpdt12.mReputation;
npc.mNpdt52.mRank = npc.mNpdt12.mRank;
npc.mNpdt52.mGold = npc.mNpdt12.mGold;
// TODO: add spells from autogenerated list like vanilla (but excluding any
// race powers or abilities)
}
else
{
npc.mNpdtType = ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS;
npc.mNpdt12.mLevel = npc.mNpdt52.mLevel; // for NPC's loaded as non-autocalc
mData.npcAutoCalculate(npc);
}
}
} }
else else
{ {
@ -643,7 +687,7 @@ void CSMWorld::NpcRefIdAdapter::setData (const RefIdColumn *column, RefIdData& d
record.setModified (npc); record.setModified (npc);
} }
CSMWorld::NpcAttributesRefIdAdapter::NpcAttributesRefIdAdapter () CSMWorld::NpcAttributesRefIdAdapter::NpcAttributesRefIdAdapter(const CSMWorld::Data& data) : mData(data)
{} {}
void CSMWorld::NpcAttributesRefIdAdapter::addNestedRow (const RefIdColumn *column, void CSMWorld::NpcAttributesRefIdAdapter::addNestedRow (const RefIdColumn *column,
@ -691,7 +735,8 @@ QVariant CSMWorld::NpcAttributesRefIdAdapter::getNestedData (const RefIdColumn *
const Record<ESM::NPC>& record = const Record<ESM::NPC>& record =
static_cast<const Record<ESM::NPC>&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc))); 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) if (subColIndex == 0)
switch (subRowIndex) switch (subRowIndex)
@ -707,18 +752,36 @@ QVariant CSMWorld::NpcAttributesRefIdAdapter::getNestedData (const RefIdColumn *
default: return QVariant(); // throw an exception here? default: return QVariant(); // throw an exception here?
} }
else if (subColIndex == 1) else if (subColIndex == 1)
switch (subRowIndex) if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
{ {
case 0: return static_cast<int>(npcStruct.mStrength); CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc);
case 1: return static_cast<int>(npcStruct.mIntelligence);
case 2: return static_cast<int>(npcStruct.mWillpower); switch (subRowIndex)
case 3: return static_cast<int>(npcStruct.mAgility); {
case 4: return static_cast<int>(npcStruct.mSpeed); case 0: return static_cast<int>(stats->getBaseAttribute(ESM::Attribute::Strength));
case 5: return static_cast<int>(npcStruct.mEndurance); case 1: return static_cast<int>(stats->getBaseAttribute(ESM::Attribute::Intelligence));
case 6: return static_cast<int>(npcStruct.mPersonality); case 2: return static_cast<int>(stats->getBaseAttribute(ESM::Attribute::Willpower));
case 7: return static_cast<int>(npcStruct.mLuck); case 3: return static_cast<int>(stats->getBaseAttribute(ESM::Attribute::Agility));
default: return QVariant(); // throw an exception here? case 4: return static_cast<int>(stats->getBaseAttribute(ESM::Attribute::Speed));
case 5: return static_cast<int>(stats->getBaseAttribute(ESM::Attribute::Endurance));
case 6: return static_cast<int>(stats->getBaseAttribute(ESM::Attribute::Personality));
case 7: return static_cast<int>(stats->getBaseAttribute(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 else
return QVariant(); // throw an exception here? return QVariant(); // throw an exception here?
} }
@ -761,7 +824,8 @@ int CSMWorld::NpcAttributesRefIdAdapter::getNestedRowsCount(const RefIdColumn *c
return 8; return 8;
} }
CSMWorld::NpcSkillsRefIdAdapter::NpcSkillsRefIdAdapter () CSMWorld::NpcSkillsRefIdAdapter::NpcSkillsRefIdAdapter(const CSMWorld::Data& data)
: mData(data)
{} {}
void CSMWorld::NpcSkillsRefIdAdapter::addNestedRow (const RefIdColumn *column, void CSMWorld::NpcSkillsRefIdAdapter::addNestedRow (const RefIdColumn *column,
@ -809,7 +873,7 @@ QVariant CSMWorld::NpcSkillsRefIdAdapter::getNestedData (const RefIdColumn *colu
const Record<ESM::NPC>& record = const Record<ESM::NPC>& record =
static_cast<const Record<ESM::NPC>&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc))); 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) if (subRowIndex < 0 || subRowIndex >= ESM::Skill::Length)
throw std::runtime_error ("index out of range"); throw std::runtime_error ("index out of range");
@ -817,7 +881,18 @@ QVariant CSMWorld::NpcSkillsRefIdAdapter::getNestedData (const RefIdColumn *colu
if (subColIndex == 0) if (subColIndex == 0)
return QString(ESM::Skill::sSkillNames[subRowIndex].c_str()); return QString(ESM::Skill::sSkillNames[subRowIndex].c_str());
else if (subColIndex == 1) else if (subColIndex == 1)
return static_cast<int>(npcStruct.mSkills[subRowIndex]); {
if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
{
CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc);
return static_cast<int>(stats->getBaseSkill(subRowIndex));
}
else
{
const ESM::NPC::NPDTstruct52& npcStruct = npc.mNpdt52;
return static_cast<int>(npcStruct.mSkills[subRowIndex]);
}
}
else else
return QVariant(); // throw an exception here? return QVariant(); // throw an exception here?
} }
@ -852,7 +927,7 @@ int CSMWorld::NpcSkillsRefIdAdapter::getNestedRowsCount(const RefIdColumn *colum
return ESM::Skill::Length; return ESM::Skill::Length;
} }
CSMWorld::NpcMiscRefIdAdapter::NpcMiscRefIdAdapter () CSMWorld::NpcMiscRefIdAdapter::NpcMiscRefIdAdapter(const CSMWorld::Data& data) : mData(data)
{} {}
CSMWorld::NpcMiscRefIdAdapter::~NpcMiscRefIdAdapter() CSMWorld::NpcMiscRefIdAdapter::~NpcMiscRefIdAdapter()
@ -888,16 +963,37 @@ QVariant CSMWorld::NpcMiscRefIdAdapter::getNestedData (const RefIdColumn *column
const Record<ESM::NPC>& record = const Record<ESM::NPC>& record =
static_cast<const Record<ESM::NPC>&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc))); 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;
if (autoCalc) if (autoCalc)
{
CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc);
switch (subColIndex) switch (subColIndex)
{ {
case 0: return static_cast<int>(record.get().mNpdt12.mLevel); case 0: return static_cast<int>(npc.mNpdt12.mLevel);
case 1: return QVariant(QVariant::UserType); case 1:
case 2: return QVariant(QVariant::UserType); {
case 3: return QVariant(QVariant::UserType); UserInt i(0); // unknown
case 4: return QVariant(QVariant::UserType); return QVariant(QVariant::fromValue(i));
}
case 2:
{
UserInt i(stats->getHealth());
return QVariant(QVariant::fromValue(i));
}
case 3:
{
UserInt i(stats->getMana());
return QVariant(QVariant::fromValue(i));
}
case 4:
{
UserInt i(stats->getFatigue());
return QVariant(QVariant::fromValue(i));
}
case 5: return static_cast<int>(record.get().mNpdt12.mDisposition); case 5: return static_cast<int>(record.get().mNpdt12.mDisposition);
case 6: return static_cast<int>(record.get().mNpdt12.mReputation); case 6: return static_cast<int>(record.get().mNpdt12.mReputation);
case 7: return static_cast<int>(record.get().mNpdt12.mRank); case 7: return static_cast<int>(record.get().mNpdt12.mRank);
@ -905,6 +1001,7 @@ QVariant CSMWorld::NpcMiscRefIdAdapter::getNestedData (const RefIdColumn *column
case 9: return record.get().mPersistent == true; case 9: return record.get().mPersistent == true;
default: return QVariant(); // throw an exception here? default: return QVariant(); // throw an exception here?
} }
}
else else
switch (subColIndex) switch (subColIndex)
{ {
@ -934,31 +1031,31 @@ void CSMWorld::NpcMiscRefIdAdapter::setNestedData (const RefIdColumn *column,
if (autoCalc) if (autoCalc)
switch(subColIndex) switch(subColIndex)
{ {
case 0: npc.mNpdt12.mLevel = static_cast<short>(value.toInt()); break; case 0: npc.mNpdt12.mLevel = static_cast<short>(value.toInt()); break;
case 1: return; case 1: return;
case 2: return; case 2: return;
case 3: return; case 3: return;
case 4: return; case 4: return;
case 5: npc.mNpdt12.mDisposition = static_cast<signed char>(value.toInt()); break; 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 6: npc.mNpdt12.mReputation = static_cast<signed char>(value.toInt()); break;
case 7: npc.mNpdt12.mRank = 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 8: npc.mNpdt12.mGold = value.toInt(); break;
case 9: npc.mPersistent = value.toBool(); break; case 9: npc.mPersistent = value.toBool(); break;
default: return; // throw an exception here? default: return; // throw an exception here?
} }
else else
switch(subColIndex) switch(subColIndex)
{ {
case 0: npc.mNpdt52.mLevel = static_cast<short>(value.toInt()); break; case 0: npc.mNpdt52.mLevel = static_cast<short>(value.toInt()); break;
case 1: npc.mNpdt52.mFactionID = static_cast<char>(value.toInt()); 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 2: npc.mNpdt52.mHealth = static_cast<unsigned short>(value.toInt()); break;
case 3: npc.mNpdt52.mMana = 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 4: npc.mNpdt52.mFatigue = static_cast<unsigned short>(value.toInt()); break;
case 5: npc.mNpdt52.mDisposition = static_cast<signed char>(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 6: npc.mNpdt52.mReputation = static_cast<signed char>(value.toInt()); break;
case 7: npc.mNpdt52.mRank = 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 8: npc.mNpdt52.mGold = value.toInt(); break;
case 9: npc.mPersistent = value.toBool(); break; case 9: npc.mPersistent = value.toBool(); break;
default: return; // throw an exception here? default: return; // throw an exception here?
} }
@ -1063,3 +1160,238 @@ void CSMWorld::WeaponRefIdAdapter::setData (const RefIdColumn *column, RefIdData
EnchantableRefIdAdapter<ESM::Weapon>::setData (column, data, index, value); EnchantableRefIdAdapter<ESM::Weapon>::setData (column, data, index, value);
} }
} }
namespace CSMWorld
{
template <>
void NestedSpellRefIdAdapter<ESM::NPC>::addNestedRow (const RefIdColumn *column,
RefIdData& data, int index, int position) const
{
Record<ESM::NPC>& record =
static_cast<Record<ESM::NPC>&> (data.getRecord (RefIdData::LocalIndex (index, mType)));
if (record.get().mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
return; // can't edit autocalculated spells
ESM::NPC caster = record.get();
std::vector<std::string>& list = caster.mSpells.mList;
std::string newString;
if (position >= (int)list.size())
list.push_back(newString);
else
list.insert(list.begin()+position, newString);
record.setModified (caster);
}
template <>
void NestedSpellRefIdAdapter<ESM::NPC>::removeNestedRow (const RefIdColumn *column,
RefIdData& data, int index, int rowToRemove) const
{
Record<ESM::NPC>& record =
static_cast<Record<ESM::NPC>&> (data.getRecord (RefIdData::LocalIndex (index, mType)));
if (record.get().mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
return; // can't edit autocalculated spells
ESM::NPC caster = record.get();
std::vector<std::string>& list = caster.mSpells.mList;
// avoid race power rows
int size = 0;
int raceIndex = mData.getRaces().searchId(caster.mRace);
if (raceIndex != -1)
size = mData.getRaces().getRecord(raceIndex).get().mPowers.mList.size();
if (rowToRemove < 0 || rowToRemove >= static_cast<int> (list.size() + size))
throw std::runtime_error ("index out of range");
if (rowToRemove >= static_cast<int>(list.size()) && rowToRemove < static_cast<int>(list.size() + size))
return; // hack, assumes the race powers are added at the end
list.erase (list.begin () + rowToRemove);
record.setModified (caster);
}
template <>
void NestedSpellRefIdAdapter<ESM::NPC>::setNestedData (const RefIdColumn *column,
RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const
{
Record<ESM::NPC>& record =
static_cast<Record<ESM::NPC>&> (data.getRecord (RefIdData::LocalIndex (row, mType)));
if (record.get().mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
return; // can't edit autocalculated spells
ESM::NPC caster = record.get();
std::vector<std::string>& list = caster.mSpells.mList;
// avoid race power rows
int size = 0;
int raceIndex = mData.getRaces().searchId(caster.mRace);
if (raceIndex != -1)
size = mData.getRaces().getRecord(raceIndex).get().mPowers.mList.size();
if (subRowIndex < 0 || subRowIndex >= static_cast<int> (list.size() + size))
throw std::runtime_error ("index out of range");
if (subRowIndex >= static_cast<int>(list.size()) && subRowIndex < static_cast<int>(list.size() + size))
return; // hack, assumes the race powers are added at the end
if (subColIndex == 0)
list.at(subRowIndex) = std::string(value.toString().toUtf8());
else
throw std::runtime_error("Trying to access non-existing column in the nested table!");
record.setModified (caster);
}
template <>
QVariant NestedSpellRefIdAdapter<ESM::NPC>::getNestedData (const RefIdColumn *column,
const RefIdData& data, int index, int subRowIndex, int subColIndex) const
{
const Record<ESM::NPC>& record =
static_cast<const Record<ESM::NPC>&> (data.getRecord (RefIdData::LocalIndex (index, mType)));
const std::vector<SpellInfo>& spells = mData.npcAutoCalculate(record.get())->spells();
if (subRowIndex < 0 || subRowIndex >= static_cast<int> (spells.size()))
throw std::runtime_error ("index out of range");
switch (subColIndex)
{
case 0: return QString::fromUtf8(spells[subRowIndex].mName.c_str());
case 1: return spells[subRowIndex].mType;
case 2: return spells[subRowIndex].mFromRace;
case 3: return spells[subRowIndex].mCost;
case 4: return spells[subRowIndex].mChance;
default:
throw std::runtime_error("Trying to access non-existing column in the nested table!");
}
}
template <>
int NestedSpellRefIdAdapter<ESM::NPC>::getNestedColumnsCount(const RefIdColumn *column,
const RefIdData& data) const
{
return 5;
}
template <>
int NestedSpellRefIdAdapter<ESM::NPC>::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const
{
const Record<ESM::NPC>& record =
static_cast<const Record<ESM::NPC>&> (data.getRecord (RefIdData::LocalIndex (index, mType)));
const std::vector<SpellInfo> spells = mData.npcAutoCalculate(record.get())->spells();
return static_cast<int>(spells.size());
}
template <>
void NestedSpellRefIdAdapter<ESM::Creature>::addNestedRow (const RefIdColumn *column,
RefIdData& data, int index, int position) const
{
Record<ESM::Creature>& record =
static_cast<Record<ESM::Creature>&> (data.getRecord (RefIdData::LocalIndex (index, mType)));
ESM::Creature caster = record.get();
std::vector<std::string>& list = caster.mSpells.mList;
std::string newString;
if (position >= (int)list.size())
list.push_back(newString);
else
list.insert(list.begin()+position, newString);
record.setModified (caster);
}
template <>
void NestedSpellRefIdAdapter<ESM::Creature>::removeNestedRow (const RefIdColumn *column,
RefIdData& data, int index, int rowToRemove) const
{
Record<ESM::Creature>& record =
static_cast<Record<ESM::Creature>&> (data.getRecord (RefIdData::LocalIndex (index, mType)));
ESM::Creature caster = record.get();
std::vector<std::string>& list = caster.mSpells.mList;
if (rowToRemove < 0 || rowToRemove >= static_cast<int> (list.size()))
throw std::runtime_error ("index out of range");
list.erase (list.begin () + rowToRemove);
record.setModified (caster);
}
template <>
void NestedSpellRefIdAdapter<ESM::Creature>::setNestedData (const RefIdColumn *column,
RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const
{
Record<ESM::Creature>& record =
static_cast<Record<ESM::Creature>&> (data.getRecord (RefIdData::LocalIndex (row, mType)));
ESM::Creature caster = record.get();
std::vector<std::string>& list = caster.mSpells.mList;
if (subRowIndex < 0 || subRowIndex >= static_cast<int> (list.size()))
throw std::runtime_error ("index out of range");
if (subColIndex == 0)
list.at(subRowIndex) = std::string(value.toString().toUtf8());
else
throw std::runtime_error("Trying to access non-existing column in the nested table!");
record.setModified (caster);
}
template<>
QVariant NestedSpellRefIdAdapter<ESM::Creature>::getNestedData (const RefIdColumn *column,
const RefIdData& data, int index, int subRowIndex, int subColIndex) const
{
const Record<ESM::Creature>& record =
static_cast<const Record<ESM::Creature>&> (data.getRecord (RefIdData::LocalIndex (index, mType)));
const std::vector<std::string>& list = record.get().mSpells.mList;
if (subRowIndex < 0 || subRowIndex >= static_cast<int> (list.size()))
throw std::runtime_error ("index out of range");
const std::string& content = list.at(subRowIndex);
int type = -1;
int spellIndex = mData.getSpells().searchId(content);
if (spellIndex != -1)
type = mData.getSpells().getRecord(spellIndex).get().mData.mType;
if (subColIndex == 0)
return QString::fromUtf8(content.c_str());
else if (subColIndex == 1)
return type;
else
throw std::runtime_error("Trying to access non-existing column in the nested table!");
}
template <>
int NestedSpellRefIdAdapter<ESM::Creature>::getNestedColumnsCount(const RefIdColumn *column,
const RefIdData& data) const
{
return 2;
}
template <>
int NestedSpellRefIdAdapter<ESM::Creature>::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const
{
const Record<ESM::Creature>& record =
static_cast<const Record<ESM::Creature>&> (data.getRecord (RefIdData::LocalIndex (index, mType)));
return static_cast<int>(record.get().mSpells.mList.size());
}
}

View file

@ -11,12 +11,17 @@
#include <components/esm/loadappa.hpp> #include <components/esm/loadappa.hpp>
#include <components/esm/loadnpc.hpp> #include <components/esm/loadnpc.hpp>
#include <components/esm/loadcrea.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 "record.hpp"
#include "refiddata.hpp" #include "refiddata.hpp"
#include "universalid.hpp" #include "universalid.hpp"
#include "refidadapter.hpp" #include "refidadapter.hpp"
#include "nestedtablewrapper.hpp" #include "nestedtablewrapper.hpp"
#include "idcollection.hpp"
#include "data.hpp"
namespace CSMWorld namespace CSMWorld
{ {
@ -802,10 +807,11 @@ namespace CSMWorld
class NpcRefIdAdapter : public ActorRefIdAdapter<ESM::NPC> class NpcRefIdAdapter : public ActorRefIdAdapter<ESM::NPC>
{ {
NpcColumns mColumns; NpcColumns mColumns;
const Data& mData;
public: public:
NpcRefIdAdapter (const NpcColumns& columns); NpcRefIdAdapter (const NpcColumns& columns, const Data& data);
virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int index)
const; const;
@ -850,9 +856,11 @@ namespace CSMWorld
class NpcAttributesRefIdAdapter : public NestedRefIdAdapterBase class NpcAttributesRefIdAdapter : public NestedRefIdAdapterBase
{ {
const Data& mData;
public: public:
NpcAttributesRefIdAdapter (); NpcAttributesRefIdAdapter (const Data& data);
virtual void addNestedRow (const RefIdColumn *column, virtual void addNestedRow (const RefIdColumn *column,
RefIdData& data, int index, int position) const; RefIdData& data, int index, int position) const;
@ -879,9 +887,11 @@ namespace CSMWorld
class NpcSkillsRefIdAdapter : public NestedRefIdAdapterBase class NpcSkillsRefIdAdapter : public NestedRefIdAdapterBase
{ {
const Data& mData;
public: public:
NpcSkillsRefIdAdapter (); NpcSkillsRefIdAdapter (const Data& data);
virtual void addNestedRow (const RefIdColumn *column, virtual void addNestedRow (const RefIdColumn *column,
RefIdData& data, int index, int position) const; RefIdData& data, int index, int position) const;
@ -908,12 +918,14 @@ namespace CSMWorld
class NpcMiscRefIdAdapter : public NestedRefIdAdapterBase class NpcMiscRefIdAdapter : public NestedRefIdAdapterBase
{ {
const Data& mData;
NpcMiscRefIdAdapter (const NpcMiscRefIdAdapter&); NpcMiscRefIdAdapter (const NpcMiscRefIdAdapter&);
NpcMiscRefIdAdapter& operator= (const NpcMiscRefIdAdapter&); NpcMiscRefIdAdapter& operator= (const NpcMiscRefIdAdapter&);
public: public:
NpcMiscRefIdAdapter (); NpcMiscRefIdAdapter (const Data& data);
virtual ~NpcMiscRefIdAdapter(); virtual ~NpcMiscRefIdAdapter();
virtual void addNestedRow (const RefIdColumn *column, virtual void addNestedRow (const RefIdColumn *column,
@ -1161,6 +1173,7 @@ namespace CSMWorld
class NestedSpellRefIdAdapter : public NestedRefIdAdapterBase class NestedSpellRefIdAdapter : public NestedRefIdAdapterBase
{ {
UniversalId::Type mType; UniversalId::Type mType;
const Data& mData;
// not implemented // not implemented
NestedSpellRefIdAdapter (const NestedSpellRefIdAdapter&); NestedSpellRefIdAdapter (const NestedSpellRefIdAdapter&);
@ -1168,45 +1181,15 @@ namespace CSMWorld
public: public:
NestedSpellRefIdAdapter(UniversalId::Type type) :mType(type) {} NestedSpellRefIdAdapter(UniversalId::Type type, const Data& data) :mType(type), mData(data) {}
virtual ~NestedSpellRefIdAdapter() {} virtual ~NestedSpellRefIdAdapter() {}
virtual void addNestedRow (const RefIdColumn *column, virtual void addNestedRow (const RefIdColumn *column,
RefIdData& data, int index, int position) const RefIdData& data, int index, int position) const;
{
Record<ESXRecordT>& record =
static_cast<Record<ESXRecordT>&> (data.getRecord (RefIdData::LocalIndex (index, mType)));
ESXRecordT caster = record.get();
std::vector<std::string>& list = caster.mSpells.mList;
std::string newString;
if (position >= (int)list.size())
list.push_back(newString);
else
list.insert(list.begin()+position, newString);
record.setModified (caster);
}
virtual void removeNestedRow (const RefIdColumn *column, virtual void removeNestedRow (const RefIdColumn *column,
RefIdData& data, int index, int rowToRemove) const RefIdData& data, int index, int rowToRemove) const;
{
Record<ESXRecordT>& record =
static_cast<Record<ESXRecordT>&> (data.getRecord (RefIdData::LocalIndex (index, mType)));
ESXRecordT caster = record.get();
std::vector<std::string>& list = caster.mSpells.mList;
if (rowToRemove < 0 || rowToRemove >= static_cast<int> (list.size()))
throw std::runtime_error ("index out of range");
list.erase (list.begin () + rowToRemove);
record.setModified (caster);
}
virtual void setNestedTable (const RefIdColumn* column, virtual void setNestedTable (const RefIdColumn* column,
RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const
@ -1232,55 +1215,14 @@ namespace CSMWorld
} }
virtual QVariant getNestedData (const RefIdColumn *column, virtual QVariant getNestedData (const RefIdColumn *column,
const RefIdData& data, int index, int subRowIndex, int subColIndex) const const RefIdData& data, int index, int subRowIndex, int subColIndex) const;
{
const Record<ESXRecordT>& record =
static_cast<const Record<ESXRecordT>&> (data.getRecord (RefIdData::LocalIndex (index, mType)));
const std::vector<std::string>& list = record.get().mSpells.mList;
if (subRowIndex < 0 || subRowIndex >= static_cast<int> (list.size()))
throw std::runtime_error ("index out of range");
const std::string& content = list.at(subRowIndex);
if (subColIndex == 0)
return QString::fromUtf8(content.c_str());
else
throw std::runtime_error("Trying to access non-existing column in the nested table!");
}
virtual void setNestedData (const RefIdColumn *column, virtual void setNestedData (const RefIdColumn *column,
RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const;
{
Record<ESXRecordT>& record =
static_cast<Record<ESXRecordT>&> (data.getRecord (RefIdData::LocalIndex (row, mType)));
ESXRecordT caster = record.get();
std::vector<std::string>& list = caster.mSpells.mList;
if (subRowIndex < 0 || subRowIndex >= static_cast<int> (list.size())) virtual int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const;
throw std::runtime_error ("index out of range");
if (subColIndex == 0) virtual int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const;
list.at(subRowIndex) = std::string(value.toString().toUtf8());
else
throw std::runtime_error("Trying to access non-existing column in the nested table!");
record.setModified (caster);
}
virtual int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const
{
return 1;
}
virtual int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const
{
const Record<ESXRecordT>& record =
static_cast<const Record<ESXRecordT>&> (data.getRecord (RefIdData::LocalIndex (index, mType)));
return static_cast<int>(record.get().mSpells.mList.size());
}
}; };
template <typename ESXRecordT> template <typename ESXRecordT>

View file

@ -10,6 +10,7 @@
#include "columns.hpp" #include "columns.hpp"
#include "nestedtablewrapper.hpp" #include "nestedtablewrapper.hpp"
#include "nestedcoladapterimp.hpp" #include "nestedcoladapterimp.hpp"
#include "data.hpp"
CSMWorld::RefIdColumn::RefIdColumn (int columnId, Display displayType, int flag, CSMWorld::RefIdColumn::RefIdColumn (int columnId, Display displayType, int flag,
bool editable, bool userEditable) bool editable, bool userEditable)
@ -36,7 +37,7 @@ const CSMWorld::RefIdAdapter& CSMWorld::RefIdCollection::findAdapter (UniversalI
return *iter->second; return *iter->second;
} }
CSMWorld::RefIdCollection::RefIdCollection() CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data)
{ {
BaseColumns baseColumns; BaseColumns baseColumns;
@ -145,12 +146,21 @@ CSMWorld::RefIdCollection::RefIdCollection()
actorsColumns.mSpells = &mColumns.back(); actorsColumns.mSpells = &mColumns.back();
std::map<UniversalId::Type, NestedRefIdAdapterBase*> spellsMap; std::map<UniversalId::Type, NestedRefIdAdapterBase*> spellsMap;
spellsMap.insert(std::make_pair(UniversalId::Type_Npc, spellsMap.insert(std::make_pair(UniversalId::Type_Npc,
new NestedSpellRefIdAdapter<ESM::NPC> (UniversalId::Type_Npc))); new NestedSpellRefIdAdapter<ESM::NPC> (UniversalId::Type_Npc, data)));
spellsMap.insert(std::make_pair(UniversalId::Type_Creature, spellsMap.insert(std::make_pair(UniversalId::Type_Creature,
new NestedSpellRefIdAdapter<ESM::Creature> (UniversalId::Type_Creature))); new NestedSpellRefIdAdapter<ESM::Creature> (UniversalId::Type_Creature, data)));
mNestedAdapters.push_back (std::make_pair(&mColumns.back(), spellsMap)); mNestedAdapters.push_back (std::make_pair(&mColumns.back(), spellsMap));
mColumns.back().addColumn( mColumns.back().addColumn(
new RefIdColumn (Columns::ColumnId_SpellId, CSMWorld::ColumnBase::Display_Spell)); new RefIdColumn (Columns::ColumnId_SpellId, CSMWorld::ColumnBase::Display_Spell));
mColumns.back().addColumn(
new RefIdColumn (Columns::ColumnId_SpellType, CSMWorld::ColumnBase::Display_SpellType, false/*editable*/, false/*user editable*/));
// creatures do not have below columns
mColumns.back().addColumn(
new RefIdColumn (Columns::ColumnId_SpellSrc, CSMWorld::ColumnBase::Display_YesNo, false, false)); // from race
mColumns.back().addColumn(
new RefIdColumn (Columns::ColumnId_SpellCost, CSMWorld::ColumnBase::Display_Integer, false, false));
mColumns.back().addColumn(
new RefIdColumn (Columns::ColumnId_SpellChance, CSMWorld::ColumnBase::Display_Integer/*Percent*/, false, false));
// Nested table // Nested table
mColumns.push_back(RefIdColumn (Columns::ColumnId_NpcDestinations, mColumns.push_back(RefIdColumn (Columns::ColumnId_NpcDestinations,
@ -437,7 +447,7 @@ CSMWorld::RefIdCollection::RefIdCollection()
ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue)); ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue));
npcColumns.mAttributes = &mColumns.back(); npcColumns.mAttributes = &mColumns.back();
std::map<UniversalId::Type, NestedRefIdAdapterBase*> attrMap; 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)));
mNestedAdapters.push_back (std::make_pair(&mColumns.back(), attrMap)); mNestedAdapters.push_back (std::make_pair(&mColumns.back(), attrMap));
mColumns.back().addColumn( mColumns.back().addColumn(
new RefIdColumn (Columns::ColumnId_NpcAttributes, CSMWorld::ColumnBase::Display_String, false, false)); new RefIdColumn (Columns::ColumnId_NpcAttributes, CSMWorld::ColumnBase::Display_String, false, false));
@ -449,7 +459,7 @@ CSMWorld::RefIdCollection::RefIdCollection()
ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue)); ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue));
npcColumns.mSkills = &mColumns.back(); npcColumns.mSkills = &mColumns.back();
std::map<UniversalId::Type, NestedRefIdAdapterBase*> skillsMap; 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)));
mNestedAdapters.push_back (std::make_pair(&mColumns.back(), skillsMap)); mNestedAdapters.push_back (std::make_pair(&mColumns.back(), skillsMap));
mColumns.back().addColumn( mColumns.back().addColumn(
new RefIdColumn (Columns::ColumnId_NpcSkills, CSMWorld::ColumnBase::Display_String, false, false)); new RefIdColumn (Columns::ColumnId_NpcSkills, CSMWorld::ColumnBase::Display_String, false, false));
@ -461,10 +471,11 @@ CSMWorld::RefIdCollection::RefIdCollection()
ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_List)); ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_List));
npcColumns.mMisc = &mColumns.back(); npcColumns.mMisc = &mColumns.back();
std::map<UniversalId::Type, NestedRefIdAdapterBase*> miscMap; 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)));
mNestedAdapters.push_back (std::make_pair(&mColumns.back(), miscMap)); mNestedAdapters.push_back (std::make_pair(&mColumns.back(), miscMap));
mColumns.back().addColumn( 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( mColumns.back().addColumn(
new RefIdColumn (Columns::ColumnId_NpcFactionID, CSMWorld::ColumnBase::Display_Integer)); new RefIdColumn (Columns::ColumnId_NpcFactionID, CSMWorld::ColumnBase::Display_Integer));
mColumns.back().addColumn( mColumns.back().addColumn(
@ -609,7 +620,7 @@ CSMWorld::RefIdCollection::RefIdCollection()
mAdapters.insert (std::make_pair (UniversalId::Type_Miscellaneous, mAdapters.insert (std::make_pair (UniversalId::Type_Miscellaneous,
new MiscRefIdAdapter (inventoryColumns, key))); new MiscRefIdAdapter (inventoryColumns, key)));
mAdapters.insert (std::make_pair (UniversalId::Type_Npc, mAdapters.insert (std::make_pair (UniversalId::Type_Npc,
new NpcRefIdAdapter (npcColumns))); new NpcRefIdAdapter (npcColumns, data)));
mAdapters.insert (std::make_pair (UniversalId::Type_Probe, mAdapters.insert (std::make_pair (UniversalId::Type_Probe,
new ToolRefIdAdapter<ESM::Probe> (UniversalId::Type_Probe, toolsColumns))); new ToolRefIdAdapter<ESM::Probe> (UniversalId::Type_Probe, toolsColumns)));
mAdapters.insert (std::make_pair (UniversalId::Type_Repair, mAdapters.insert (std::make_pair (UniversalId::Type_Repair,

View file

@ -20,6 +20,7 @@ namespace CSMWorld
class RefIdAdapter; class RefIdAdapter;
struct NestedTableWrapperBase; struct NestedTableWrapperBase;
class NestedRefIdAdapterBase; class NestedRefIdAdapterBase;
class Data;
class RefIdColumn : public NestableColumn class RefIdColumn : public NestableColumn
{ {
@ -56,7 +57,8 @@ namespace CSMWorld
public: public:
RefIdCollection(); // race, classes and skills required for NPC autocalc
RefIdCollection(const Data& data);
virtual ~RefIdCollection(); 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

@ -66,7 +66,7 @@ void CSVWorld::NotEditableSubDelegate::setEditorData (QWidget* editor, const QMo
CSMWorld::Columns::ColumnId columnId = static_cast<CSMWorld::Columns::ColumnId> ( CSMWorld::Columns::ColumnId columnId = static_cast<CSMWorld::Columns::ColumnId> (
mTable->getColumnId (index.column())); mTable->getColumnId (index.column()));
if (QVariant::String == v.type()) if (QVariant::String == v.type())
{ {
label->setText(v.toString()); label->setText(v.toString());
@ -75,7 +75,7 @@ void CSVWorld::NotEditableSubDelegate::setEditorData (QWidget* editor, const QMo
{ {
int data = v.toInt(); int data = v.toInt();
std::vector<std::string> enumNames (CSMWorld::Columns::getEnums (columnId)); std::vector<std::string> enumNames (CSMWorld::Columns::getEnums (columnId));
label->setText(QString::fromUtf8(enumNames.at(data).c_str())); label->setText(QString::fromUtf8(enumNames.at(data).c_str()));
} }
else else
@ -430,16 +430,18 @@ void CSVWorld::EditWidget::remake(int row)
static_cast<CSMWorld::UniversalId::Type> (mTable->data (mTable->index (row, typeColumn)).toInt()), static_cast<CSMWorld::UniversalId::Type> (mTable->data (mTable->index (row, typeColumn)).toInt()),
mTable->data (mTable->index (row, idColumn)).toString().toUtf8().constData()); mTable->data (mTable->index (row, idColumn)).toString().toUtf8().constData());
NestedTable* table = new NestedTable(mDocument, id, mNestedModels.back(), this); bool editable = mTable->index(row, i).data().type() != QVariant::UserType;
table->resizeColumnsToContents(); NestedTable* table = new NestedTable(mDocument, id, mNestedModels.back(), this, editable);
if (!editable)
if(mTable->index(row, i).data().type() == QVariant::UserType)
{ {
table->setEditTriggers(QAbstractItemView::NoEditTriggers); table->setEditTriggers(QAbstractItemView::NoEditTriggers);
table->setEnabled(false); table->setSelectionMode(QAbstractItemView::NoSelection);
table->setStyleSheet("QTableView { color: gray; }");
table->horizontalHeader()->setStyleSheet("QHeaderView { color: gray; }");
} }
else else
table->setEditTriggers(QAbstractItemView::SelectedClicked | QAbstractItemView::CurrentChanged); table->setEditTriggers(QAbstractItemView::SelectedClicked | QAbstractItemView::CurrentChanged);
table->resizeColumnsToContents();
int rows = mTable->rowCount(mTable->index(row, i)); int rows = mTable->rowCount(mTable->index(row, i));
int rowHeight = (rows == 0) ? table->horizontalHeader()->height() : table->rowHeight(0); int rowHeight = (rows == 0) ? table->horizontalHeader()->height() : table->rowHeight(0);
@ -610,6 +612,14 @@ CSVWorld::SimpleDialogueSubView::SimpleDialogueSubView (const CSMWorld::Universa
mEditWidget = new EditWidget(mainWidget, mEditWidget = new EditWidget(mainWidget,
mTable->getModelIndex(mCurrentId, 0).row(), mTable, mCommandDispatcher, document, false); mTable->getModelIndex(mCurrentId, 0).row(), mTable, mCommandDispatcher, document, false);
if (id.getType() == CSMWorld::UniversalId::Type_Referenceable)
{
CSMWorld::IdTree *objectTable = static_cast<CSMWorld::IdTree*>(mTable);
connect (objectTable, SIGNAL (refreshNpcDialogue (int, const std::string&)),
this, SLOT (refreshNpcDialogue (int, const std::string&)));
}
mMainLayout->addWidget(mEditWidget); mMainLayout->addWidget(mEditWidget);
mEditWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); mEditWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
@ -699,6 +709,29 @@ void CSVWorld::SimpleDialogueSubView::changeCurrentId (const std::string& newId)
mCommandDispatcher.setSelection(selection); mCommandDispatcher.setSelection(selection);
} }
void CSVWorld::SimpleDialogueSubView::refreshNpcDialogue (int type, const std::string& id)
{
int typeColumn = mTable->findColumnIndex (CSMWorld::Columns::ColumnId_RecordType);
if (CSMWorld::UniversalId::Type_Npc
!= mTable->data(mTable->getModelIndex(mCurrentId, typeColumn), Qt::DisplayRole).toInt())
{
return;
}
int raceColumn = mTable->findColumnIndex (CSMWorld::Columns::ColumnId_Race);
int classColumn = mTable->findColumnIndex (CSMWorld::Columns::ColumnId_Class);
if ((type == 0/*FIXME*/ && id == "") // skill or gmst changed
|| (id == mTable->data(mTable->getModelIndex(mCurrentId, raceColumn),
Qt::DisplayRole).toString().toUtf8().constData()) // race
|| (id == mTable->data(mTable->getModelIndex(mCurrentId, classColumn),
Qt::DisplayRole).toString().toUtf8().constData())) // class
{
int y = mEditWidget->verticalScrollBar()->value();
mEditWidget->remake (mTable->getModelIndex(mCurrentId, 0).row());
mEditWidget->verticalScrollBar()->setValue(y);
}
}
CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id,
CSMDoc::Document& document, const CreatorFactoryBase& creatorFactory, bool sorting) CSMDoc::Document& document, const CreatorFactoryBase& creatorFactory, bool sorting)
@ -774,7 +807,7 @@ CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id,
deleteButton->setDisabled (true); deleteButton->setDisabled (true);
} }
getMainLayout().addLayout (buttonsLayout); getMainLayout().addLayout (buttonsLayout);
} }
void CSVWorld::DialogueSubView::cloneRequest() void CSVWorld::DialogueSubView::cloneRequest()
@ -855,7 +888,6 @@ void CSVWorld::DialogueSubView::nextId ()
} }
} }
void CSVWorld::DialogueSubView::showPreview () void CSVWorld::DialogueSubView::showPreview ()
{ {
QModelIndex currentIndex (getTable().getModelIndex (getCurrentId(), 0)); QModelIndex currentIndex (getTable().getModelIndex (getCurrentId(), 0));

View file

@ -216,6 +216,8 @@ namespace CSVWorld
void requestFocus (const std::string& id); void requestFocus (const std::string& id);
void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end);
void refreshNpcDialogue (int type, const std::string& id);
}; };
class DialogueSubView : public SimpleDialogueSubView class DialogueSubView : public SimpleDialogueSubView

View file

@ -13,12 +13,13 @@
CSVWorld::NestedTable::NestedTable(CSMDoc::Document& document, CSVWorld::NestedTable::NestedTable(CSMDoc::Document& document,
CSMWorld::UniversalId id, CSMWorld::UniversalId id,
CSMWorld::NestedTableProxyModel* model, CSMWorld::NestedTableProxyModel* model,
QWidget* parent) QWidget* parent,
bool editable)
: DragRecordTable(document, parent), : DragRecordTable(document, parent),
mAddNewRowAction(0),
mRemoveRowAction(0),
mModel(model) mModel(model)
{ {
mDispatcher = new CSMWorld::CommandDispatcher (document, id, this);
setSelectionBehavior (QAbstractItemView::SelectRows); setSelectionBehavior (QAbstractItemView::SelectRows);
setSelectionMode (QAbstractItemView::ExtendedSelection); setSelectionMode (QAbstractItemView::ExtendedSelection);
@ -31,30 +32,36 @@ CSVWorld::NestedTable::NestedTable(CSMDoc::Document& document,
int columns = model->columnCount(QModelIndex()); 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); setModel(model);
mAddNewRowAction = new QAction (tr ("Add new row"), this); setAcceptDrops(true);
connect(mAddNewRowAction, SIGNAL(triggered()), if (editable)
this, SLOT(addNewRowActionTriggered())); {
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());
mRemoveRowAction = new QAction (tr ("Remove row"), this); CommandDelegate *delegate = CommandDelegateFactoryCollection::get().makeDelegate(display,
mDispatcher,
document,
this);
connect(mRemoveRowAction, SIGNAL(triggered()), setItemDelegateForColumn(i, delegate);
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()));
}
} }
std::vector<CSMWorld::UniversalId> CSVWorld::NestedTable::getDraggedRecords() const std::vector<CSMWorld::UniversalId> CSVWorld::NestedTable::getDraggedRecords() const
@ -65,6 +72,9 @@ std::vector<CSMWorld::UniversalId> CSVWorld::NestedTable::getDraggedRecords() co
void CSVWorld::NestedTable::contextMenuEvent (QContextMenuEvent *event) void CSVWorld::NestedTable::contextMenuEvent (QContextMenuEvent *event)
{ {
if (!mRemoveRowAction || !mAddNewRowAction)
return;
QModelIndexList selectedRows = selectionModel()->selectedRows(); QModelIndexList selectedRows = selectionModel()->selectedRows();
QMenu menu(this); QMenu menu(this);

View file

@ -35,7 +35,8 @@ namespace CSVWorld
NestedTable(CSMDoc::Document& document, NestedTable(CSMDoc::Document& document,
CSMWorld::UniversalId id, CSMWorld::UniversalId id,
CSMWorld::NestedTableProxyModel* model, CSMWorld::NestedTableProxyModel* model,
QWidget* parent = NULL); QWidget* parent = NULL,
bool editable = true);
virtual std::vector<CSMWorld::UniversalId> getDraggedRecords() const; virtual std::vector<CSMWorld::UniversalId> getDraggedRecords() const;

View file

@ -19,6 +19,7 @@
#include "../../model/world/commanddispatcher.hpp" #include "../../model/world/commanddispatcher.hpp"
#include "../widget/coloreditor.hpp" #include "../widget/coloreditor.hpp"
#include "../../model/world/usertype.hpp"
#include "../widget/droplineedit.hpp" #include "../widget/droplineedit.hpp"
#include "dialoguespinbox.hpp" #include "dialoguespinbox.hpp"
@ -136,8 +137,8 @@ void CSVWorld::CommandDelegate::setModelDataImp (QWidget *editor, QAbstractItemM
} }
else else
{ {
NastyTableModelHack hack (*model); NastyTableModelHack hack (*model);
QStyledItemDelegate::setModelData (editor, &hack, index); QStyledItemDelegate::setModelData (editor, &hack, index);
new_ = hack.getData(); new_ = hack.getData();
} }
@ -166,7 +167,7 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO
const QModelIndex& index) const const QModelIndex& index) const
{ {
CSMWorld::ColumnBase::Display display = getDisplayTypeFromIndex(index); CSMWorld::ColumnBase::Display display = getDisplayTypeFromIndex(index);
// This createEditor() method is called implicitly from tables. // This createEditor() method is called implicitly from tables.
// For boolean values in tables use the default editor (combobox). // For boolean values in tables use the default editor (combobox).
// Checkboxes is looking ugly in the table view. // Checkboxes is looking ugly in the table view.
@ -324,8 +325,15 @@ void CSVWorld::CommandDelegate::setEditorData (QWidget *editor, const QModelInde
if (!n.isEmpty()) { if (!n.isEmpty()) {
if (!v.isValid()) if (!v.isValid())
v = QVariant(editor->property(n).userType(), (const void *)0); editor->setProperty(n, QVariant(editor->property(n).userType(), (const void *)0));
editor->setProperty(n, v); 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);
} }
} }

View file

@ -66,7 +66,7 @@ add_openmw_dir (mwworld
cells localscripts customdata weather inventorystore ptr actionopen actionread cells localscripts customdata weather inventorystore ptr actionopen actionread
actionequip timestamp actionalchemy cellstore actionapply actioneat actionequip timestamp actionalchemy cellstore actionapply actioneat
esmstore store recordcmp fallback actionrepair actionsoulgem livecellref actiondoor esmstore store recordcmp fallback actionrepair actionsoulgem livecellref actiondoor
contentloader esmloader actiontrap cellreflist projectilemanager cellref contentloader esmloader actiontrap cellreflist projectilemanager cellref mwstore
) )
add_openmw_dir (mwclass add_openmw_dir (mwclass
@ -78,7 +78,7 @@ add_openmw_dir (mwmechanics
mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects
drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor
aiescort aiactivate aicombat repair enchanting pathfinding pathgrid security spellsuccess spellcasting aiescort aiactivate aicombat repair enchanting pathfinding pathgrid security spellsuccess spellcasting
disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning disease pickpocket levelledlist combat steering obstacle difficultyscaling aicombataction actor summoning
) )
add_openmw_dir (mwstate add_openmw_dir (mwstate

View file

@ -11,6 +11,10 @@
#include <components/esm/loadnpc.hpp> #include <components/esm/loadnpc.hpp>
#include <components/esm/npcstate.hpp> #include <components/esm/npcstate.hpp>
#include <components/autocalc/autocalc.hpp>
#include <components/autocalc/autocalcspell.hpp>
#include <components/autocalc/store.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/mechanicsmanager.hpp"
@ -24,7 +28,6 @@
#include "../mwmechanics/spellcasting.hpp" #include "../mwmechanics/spellcasting.hpp"
#include "../mwmechanics/disease.hpp" #include "../mwmechanics/disease.hpp"
#include "../mwmechanics/combat.hpp" #include "../mwmechanics/combat.hpp"
#include "../mwmechanics/autocalcspell.hpp"
#include "../mwmechanics/difficultyscaling.hpp" #include "../mwmechanics/difficultyscaling.hpp"
#include "../mwmechanics/character.hpp" #include "../mwmechanics/character.hpp"
@ -36,6 +39,7 @@
#include "../mwworld/customdata.hpp" #include "../mwworld/customdata.hpp"
#include "../mwworld/physicssystem.hpp" #include "../mwworld/physicssystem.hpp"
#include "../mwworld/cellstore.hpp" #include "../mwworld/cellstore.hpp"
#include "../mwworld/mwstore.hpp"
#include "../mwrender/actors.hpp" #include "../mwrender/actors.hpp"
#include "../mwrender/renderinginterface.hpp" #include "../mwrender/renderinginterface.hpp"
@ -58,116 +62,44 @@ namespace
return new NpcCustomData (*this); return new NpcCustomData (*this);
} }
int is_even(double d) { class Stats : public AutoCalc::StatsBase
double int_part; {
modf(d / 2.0, &int_part); MWMechanics::NpcStats& mNpcStats;
return 2.0 * int_part == d;
} public:
int round_ieee_754(double d) { Stats(MWMechanics::NpcStats& npcStats) : mNpcStats(npcStats) {}
double i = floor(d);
d -= i; virtual unsigned char getBaseAttribute(int index) const { return mNpcStats.getAttribute(index).getBase(); }
if(d < 0.5)
return static_cast<int>(i); virtual void setAttribute(int index, unsigned char value) { mNpcStats.setAttribute(index, value); }
if(d > 0.5)
return static_cast<int>(i) + 1; virtual void addSpell(const std::string& id) { mNpcStats.getSpells().add(id); }
if(is_even(i))
return static_cast<int>(i); virtual unsigned char getBaseSkill(int index) const { return mNpcStats.getSkill(index).getBase(); }
return static_cast<int>(i) + 1;
} virtual void setBaseSkill(int index, unsigned char value) { mNpcStats.getSkill(index).setBase(value); }
};
void autoCalculateAttributes (const ESM::NPC* npc, MWMechanics::CreatureStats& creatureStats)
void autoCalculateAttributes (const ESM::NPC* npc, MWMechanics::NpcStats& npcStats)
{ {
// race bonus
const ESM::Race *race = const ESM::Race *race =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(npc->mRace); MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(npc->mRace);
bool male = (npc->mFlags & ESM::NPC::Female) == 0;
int level = creatureStats.getLevel();
for (int i=0; i<ESM::Attribute::Length; ++i)
{
const ESM::Race::MaleFemale& attribute = race->mData.mAttributeValues[i];
creatureStats.setAttribute(i, male ? attribute.mMale : attribute.mFemale);
}
// class bonus
const ESM::Class *class_ = const ESM::Class *class_ =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Class>().find(npc->mClass); MWBase::Environment::get().getWorld()->getStore().get<ESM::Class>().find(npc->mClass);
for (int i=0; i<2; ++i) int level = npcStats.getLevel();
{
int attribute = class_->mData.mAttribute[i];
if (attribute>=0 && attribute<8)
{
creatureStats.setAttribute(attribute,
creatureStats.getAttribute(attribute).getBase() + 10);
}
}
// skill bonus Stats stats(npcStats);
for (int attribute=0; attribute < ESM::Attribute::Length; ++attribute)
{
float modifierSum = 0;
for (int j=0; j<ESM::Skill::Length; ++j) MWWorld::MWStore store;
{
const ESM::Skill* skill = MWBase::Environment::get().getWorld()->getStore().get<ESM::Skill>().find(j);
if (skill->mData.mAttribute != attribute) AutoCalc::autoCalcAttributesImpl (npc, race, class_, level, stats, &store);
continue;
// is this a minor or major skill? npcStats.setHealth(AutoCalc::autoCalculateHealth(level, class_, stats));
float add=0.2f;
for (int k=0; k<5; ++k)
{
if (class_->mData.mSkills[k][0] == j)
add=0.5;
}
for (int k=0; k<5; ++k)
{
if (class_->mData.mSkills[k][1] == j)
add=1.0;
}
modifierSum += add;
}
creatureStats.setAttribute(attribute, std::min(
round_ieee_754(creatureStats.getAttribute(attribute).getBase()
+ (level-1) * modifierSum), 100) );
}
// initial health
int strength = creatureStats.getAttribute(ESM::Attribute::Strength).getBase();
int endurance = creatureStats.getAttribute(ESM::Attribute::Endurance).getBase();
int multiplier = 3;
if (class_->mData.mSpecialization == ESM::Class::Combat)
multiplier += 2;
else if (class_->mData.mSpecialization == ESM::Class::Stealth)
multiplier += 1;
if (class_->mData.mAttribute[0] == ESM::Attribute::Endurance
|| class_->mData.mAttribute[1] == ESM::Attribute::Endurance)
multiplier += 1;
creatureStats.setHealth(floor(0.5f * (strength + endurance)) + multiplier * (creatureStats.getLevel() - 1));
} }
/**
* @brief autoCalculateSkills
*
* Skills are calculated with following formulae ( http://www.uesp.net/wiki/Morrowind:NPCs#Skills ):
*
* Skills: (Level - 1) × (Majority Multiplier + Specialization Multiplier)
*
* The Majority Multiplier is 1.0 for a Major or Minor Skill, or 0.1 for a Miscellaneous Skill.
*
* The Specialization Multiplier is 0.5 for a Skill in the same Specialization as the class,
* zero for other Skills.
*
* and by adding class, race, specialization bonus.
*/
void autoCalculateSkills(const ESM::NPC* npc, MWMechanics::NpcStats& npcStats, const MWWorld::Ptr& ptr) void autoCalculateSkills(const ESM::NPC* npc, MWMechanics::NpcStats& npcStats, const MWWorld::Ptr& ptr)
{ {
const ESM::Class *class_ = const ESM::Class *class_ =
@ -177,77 +109,13 @@ namespace
const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(npc->mRace); const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(npc->mRace);
Stats stats(npcStats);
for (int i = 0; i < 2; ++i) MWWorld::MWStore store;
{
int bonus = (i==0) ? 10 : 25;
for (int i2 = 0; i2 < 5; ++i2) AutoCalc::autoCalcSkillsImpl(npc, race, class_, level, stats, &store);
{
int index = class_->mData.mSkills[i2][i];
if (index >= 0 && index < ESM::Skill::Length)
{
npcStats.getSkill(index).setBase (npcStats.getSkill(index).getBase() + bonus);
}
}
}
for (int skillIndex = 0; skillIndex < ESM::Skill::Length; ++skillIndex) AutoCalc::autoCalculateSpells(race, stats, &store);
{
float majorMultiplier = 0.1f;
float specMultiplier = 0.0f;
int raceBonus = 0;
int specBonus = 0;
for (int raceSkillIndex = 0; raceSkillIndex < 7; ++raceSkillIndex)
{
if (race->mData.mBonus[raceSkillIndex].mSkill == skillIndex)
{
raceBonus = race->mData.mBonus[raceSkillIndex].mBonus;
break;
}
}
for (int k = 0; k < 5; ++k)
{
// is this a minor or major skill?
if ((class_->mData.mSkills[k][0] == skillIndex) || (class_->mData.mSkills[k][1] == skillIndex))
{
majorMultiplier = 1.0f;
break;
}
}
// is this skill in the same Specialization as the class?
const ESM::Skill* skill = MWBase::Environment::get().getWorld()->getStore().get<ESM::Skill>().find(skillIndex);
if (skill->mData.mSpecialization == class_->mData.mSpecialization)
{
specMultiplier = 0.5f;
specBonus = 5;
}
npcStats.getSkill(skillIndex).setBase(
std::min(
round_ieee_754(
npcStats.getSkill(skillIndex).getBase()
+ 5
+ raceBonus
+ specBonus
+(int(level)-1) * (majorMultiplier + specMultiplier)), 100)); // Must gracefully handle level 0
}
int skills[ESM::Skill::Length];
for (int i=0; i<ESM::Skill::Length; ++i)
skills[i] = npcStats.getSkill(i).getBase();
int attributes[ESM::Attribute::Length];
for (int i=0; i<ESM::Attribute::Length; ++i)
attributes[i] = npcStats.getAttribute(i).getBase();
std::vector<std::string> spells = MWMechanics::autoCalcNpcSpells(skills, attributes, race);
for (std::vector<std::string>::iterator it = spells.begin(); it != spells.end(); ++it)
npcStats.getSpells().add(*it);
} }
} }
@ -392,7 +260,7 @@ namespace MWClass
// store // store
ptr.getRefData().setCustomData (data.release()); ptr.getRefData().setCustomData (data.release());
getInventoryStore(ptr).autoEquip(ptr); getInventoryStore(ptr).autoEquip(ptr);
} }
} }

View file

@ -6,8 +6,11 @@
#include <components/esm/stolenitems.hpp> #include <components/esm/stolenitems.hpp>
#include <components/autocalc/autocalcspell.hpp>
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
#include "../mwworld/inventorystore.hpp" #include "../mwworld/inventorystore.hpp"
#include "../mwworld/mwstore.hpp"
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
@ -23,7 +26,6 @@
#include <OgreSceneNode.h> #include <OgreSceneNode.h>
#include "spellcasting.hpp" #include "spellcasting.hpp"
#include "autocalcspell.hpp"
#include <limits.h> #include <limits.h>
@ -252,10 +254,12 @@ namespace MWMechanics
continue; continue;
static const float fAutoPCSpellChance = esmStore.get<ESM::GameSetting>().find("fAutoPCSpellChance")->getFloat(); static const float fAutoPCSpellChance = esmStore.get<ESM::GameSetting>().find("fAutoPCSpellChance")->getFloat();
if (calcAutoCastChance(spell, skills, attributes, -1) < fAutoPCSpellChance) MWWorld::MWStore store;
if (AutoCalc::calcAutoCastChance(spell, skills, attributes, -1, &store) < fAutoPCSpellChance)
continue; continue;
if (!attrSkillCheck(spell, skills, attributes)) if (!AutoCalc::attrSkillCheck(spell, skills, attributes, &store))
continue; continue;
selectedSpells.push_back(spell->mId); selectedSpells.push_back(spell->mId);

View file

@ -0,0 +1,37 @@
#include "mwstore.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "esmstore.hpp"
namespace MWWorld
{
MWStore::MWStore()
: mGmst(MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()),
mSpells(MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>())
{ }
MWStore::~MWStore()
{ }
int MWStore::findGmstInt(const std::string& name) const { return mGmst.find(name)->getInt(); }
float MWStore::findGmstFloat(const std::string& name) const { return mGmst.find(name)->getFloat(); }
const ESM::Skill *MWStore::findSkill(int index) const
{
return MWBase::Environment::get().getWorld()->getStore().get<ESM::Skill>().find(index);
}
const ESM::MagicEffect* MWStore::findMagicEffect(int id) const
{
return MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(id);
}
void MWStore::getSpells(std::vector<ESM::Spell*>& spells)
{
for (Store<ESM::Spell>::iterator iter = mSpells.begin(); iter != mSpells.end(); ++iter)
spells.push_back(const_cast<ESM::Spell*>(&*iter));
}
}

View file

@ -0,0 +1,34 @@
#ifndef GAME_MWWORLD_MWSTORE_H
#define GAME_MWWORLD_MWSTORE_H
#include <string>
#include <components/autocalc/store.hpp>
#include "store.hpp"
namespace MWWorld
{
class MWStore : public AutoCalc::StoreCommon
{
const MWWorld::Store<ESM::GameSetting>& mGmst;
const MWWorld::Store<ESM::Spell> &mSpells;
public:
MWStore();
~MWStore();
virtual int findGmstInt(const std::string& name) const;
virtual float findGmstFloat(const std::string& name) const;
virtual const ESM::Skill *findSkill(int index) const;
virtual const ESM::MagicEffect* findMagicEffect(int id) const;
virtual void getSpells(std::vector<ESM::Spell*>& spells);
};
}
#endif // GAME_MWWORLD_MWSTORE_H

View file

@ -119,6 +119,10 @@ add_component_dir (fontloader
fontloader fontloader
) )
add_component_dir (autocalc
autocalc autocalcspell
)
add_component_dir (version add_component_dir (version
version version
) )

View file

@ -0,0 +1,208 @@
#include "autocalc.hpp"
#include <cmath>
#include <components/esm/attr.hpp>
#include <components/esm/loadmgef.hpp>
#include <components/esm/loadskil.hpp>
#include <components/esm/loadrace.hpp>
#include <components/esm/loadclas.hpp>
#include <components/esm/loadnpc.hpp>
#include "autocalcspell.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;
}
}
namespace AutoCalc
{
void autoCalcAttributesImpl (const ESM::NPC* npc,
const ESM::Race *race, const ESM::Class *class_, int level, StatsBase& stats, StoreCommon *store)
{
// race bonus
bool male = (npc->mFlags & ESM::NPC::Female) == 0;
for (int i=0; i<ESM::Attribute::Length; ++i)
{
const ESM::Race::MaleFemale& attribute = race->mData.mAttributeValues[i];
stats.setAttribute(i, male ? attribute.mMale : attribute.mFemale);
}
// class bonus
for (int i=0; i<2; ++i)
{
int attribute = class_->mData.mAttribute[i];
if (attribute>=0 && attribute<ESM::Attribute::Length)
stats.setAttribute(attribute, stats.getBaseAttribute(attribute) + 10);
// else log an error?
}
// skill bonus
for (int attribute=0; attribute < ESM::Attribute::Length; ++attribute)
{
float modifierSum = 0;
for (int j=0; j<ESM::Skill::Length; ++j)
{
const ESM::Skill* skill = store->findSkill(j);
if (skill->mData.mAttribute != attribute)
continue;
// is this a minor or major skill?
float add=0.2f;
for (int k=0; k<5; ++k)
{
if (class_->mData.mSkills[k][0] == j)
add=0.5;
}
for (int k=0; k<5; ++k)
{
if (class_->mData.mSkills[k][1] == j)
add=1.0;
}
modifierSum += add;
}
stats.setAttribute(attribute,
std::min(round_ieee_754(stats.getBaseAttribute(attribute) + (level-1) * modifierSum), 100) );
}
}
/**
* @brief autoCalculateSkills
*
* Skills are calculated with following formulae ( http://www.uesp.net/wiki/Morrowind:NPCs#Skills ):
*
* Skills: (Level - 1) × (Majority Multiplier + Specialization Multiplier)
*
* The Majority Multiplier is 1.0 for a Major or Minor Skill, or 0.1 for a Miscellaneous Skill.
*
* The Specialization Multiplier is 0.5 for a Skill in the same Specialization as the class,
* zero for other Skills.
*
* and by adding class, race, specialization bonus.
*/
void autoCalcSkillsImpl (const ESM::NPC* npc,
const ESM::Race *race, const ESM::Class *class_, int level, StatsBase& stats, StoreCommon *store)
{
for (int i = 0; i < 2; ++i)
{
int bonus = (i==0) ? 10 : 25;
for (int i2 = 0; i2 < 5; ++i2)
{
int index = class_->mData.mSkills[i2][i];
if (index >= 0 && index < ESM::Skill::Length)
{
stats.setBaseSkill (index, bonus);
}
}
}
for (int skillIndex = 0; skillIndex < ESM::Skill::Length; ++skillIndex)
{
float majorMultiplier = 0.1f;
float specMultiplier = 0.0f;
int raceBonus = 0;
int specBonus = 0;
for (int raceSkillIndex = 0; raceSkillIndex < 7; ++raceSkillIndex)
{
if (race->mData.mBonus[raceSkillIndex].mSkill == skillIndex)
{
raceBonus = race->mData.mBonus[raceSkillIndex].mBonus;
break;
}
}
for (int k = 0; k < 5; ++k)
{
// is this a minor or major skill?
if ((class_->mData.mSkills[k][0] == skillIndex) || (class_->mData.mSkills[k][1] == skillIndex))
{
majorMultiplier = 1.0f;
break;
}
}
// is this skill in the same Specialization as the class?
const ESM::Skill* skill = store->findSkill(skillIndex);
if (skill->mData.mSpecialization == class_->mData.mSpecialization)
{
specMultiplier = 0.5f;
specBonus = 5;
}
stats.setBaseSkill(skillIndex,
std::min(
round_ieee_754(
stats.getBaseSkill(skillIndex)
+ 5
+ raceBonus
+ specBonus
+(int(level)-1) * (majorMultiplier + specMultiplier)), 100)); // Must gracefully handle level 0
}
}
unsigned short autoCalculateHealth(int level, const ESM::Class *class_, const StatsBase& stats)
{
// initial health
int strength = stats.getBaseAttribute(ESM::Attribute::Strength);
int endurance = stats.getBaseAttribute(ESM::Attribute::Endurance);
int multiplier = 3;
if (class_->mData.mSpecialization == ESM::Class::Combat)
multiplier += 2;
else if (class_->mData.mSpecialization == ESM::Class::Stealth)
multiplier += 1;
if (class_->mData.mAttribute[0] == ESM::Attribute::Endurance
|| class_->mData.mAttribute[1] == ESM::Attribute::Endurance)
multiplier += 1;
return static_cast<unsigned short>(floor(0.5f * (strength + endurance)) + multiplier * (level-1));
}
void autoCalculateSpells(const ESM::Race *race, StatsBase& stats, StoreCommon *store)
{
int skills[ESM::Skill::Length];
for (int i=0; i<ESM::Skill::Length; ++i)
skills[i] = stats.getBaseSkill(i);
int attributes[ESM::Attribute::Length];
for (int i=0; i<ESM::Attribute::Length; ++i)
attributes[i] = stats.getBaseAttribute(i);
std::vector<std::string> spells = autoCalcNpcSpells(skills, attributes, race, store);
for (std::vector<std::string>::iterator it = spells.begin(); it != spells.end(); ++it)
stats.addSpell(*it);
}
StatsBase::StatsBase() {}
StatsBase::~StatsBase() {}
}

View file

@ -0,0 +1,47 @@
#ifndef COMPONENTS_AUTOCALC_AUTOCALC_H
#define COMPONENTS_AUTOCALC_AUTOCALC_H
#include <string>
#include "store.hpp"
namespace ESM
{
struct NPC;
struct Race;
struct Class;
}
namespace AutoCalc
{
// wrapper class for sharing the autocalc code between OpenMW and OpenCS
class StatsBase
{
public:
StatsBase();
virtual ~StatsBase();
virtual unsigned char getBaseAttribute(int index) const = 0;
virtual void setAttribute(int index, unsigned char value) = 0;
virtual void addSpell(const std::string& id) = 0;
virtual unsigned char getBaseSkill(int index) const = 0;
virtual void setBaseSkill(int index, unsigned char value) = 0;
};
void autoCalcAttributesImpl (const ESM::NPC* npc,
const ESM::Race *race, const ESM::Class *class_, int level, StatsBase& stats, StoreCommon *store);
void autoCalcSkillsImpl (const ESM::NPC* npc,
const ESM::Race *race, const ESM::Class *class_, int level, StatsBase& stats, StoreCommon *store);
unsigned short autoCalculateHealth(int level, const ESM::Class *class_, const StatsBase& stats);
void autoCalculateSpells(const ESM::Race *race, StatsBase& stats, StoreCommon *store);
}
#endif // COMPONENTS_AUTOCALC_AUTOCALC_H

View file

@ -0,0 +1,249 @@
#include "autocalcspell.hpp"
#include <climits>
#include <cfloat>
#include <set>
#include <components/esm/attr.hpp>
#include <components/esm/loadmgef.hpp>
#include <components/esm/loadrace.hpp>
#include <components/esm/loadclas.hpp>
#include <components/esm/loadspel.hpp>
#include <components/esm/loadnpc.hpp>
#include "autocalc.hpp"
namespace AutoCalc
{
struct SchoolCaps
{
int mCount;
int mLimit;
bool mReachedLimit;
int mMinCost;
std::string mWeakestSpell;
};
std::vector<std::string> autoCalcNpcSpells(const int *actorSkills,
const int *actorAttributes, const ESM::Race* race, StoreCommon *store)
{
static const float fNPCbaseMagickaMult = store->findGmstFloat("fNPCbaseMagickaMult");
float baseMagicka = fNPCbaseMagickaMult * actorAttributes[ESM::Attribute::Intelligence];
static const std::string schools[] = {
"alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
};
static int iAutoSpellSchoolMax[6];
static bool init = false;
if (!init)
{
for (int i=0; i<6; ++i)
{
const std::string& gmstName = "iAutoSpell" + schools[i] + "Max";
iAutoSpellSchoolMax[i] = store->findGmstInt(gmstName);
}
init = true;
}
std::map<int, SchoolCaps> schoolCaps;
for (int i=0; i<6; ++i)
{
SchoolCaps caps;
caps.mCount = 0;
caps.mLimit = iAutoSpellSchoolMax[i];
caps.mReachedLimit = iAutoSpellSchoolMax[i] <= 0;
caps.mMinCost = INT_MAX;
caps.mWeakestSpell.clear();
schoolCaps[i] = caps;
}
std::vector<std::string> selectedSpells;
std::vector<ESM::Spell*> spells;
store->getSpells(spells);
// Note: the algorithm heavily depends on the traversal order of the spells. For vanilla-compatible results the
// Store must preserve the record ordering as it was in the content files.
for (std::vector<ESM::Spell*>::const_iterator iter = spells.begin(); iter != spells.end(); ++iter)
{
ESM::Spell* spell = *iter;
if (spell->mData.mType != ESM::Spell::ST_Spell)
continue;
if (!(spell->mData.mFlags & ESM::Spell::F_Autocalc))
continue;
static const int iAutoSpellTimesCanCast = store->findGmstInt("iAutoSpellTimesCanCast");
if (baseMagicka < iAutoSpellTimesCanCast * spell->mData.mCost)
continue;
if (race && race->mPowers.exists(spell->mId))
continue;
if (!attrSkillCheck(spell, actorSkills, actorAttributes, store))
continue;
int school;
float skillTerm;
calcWeakestSchool(spell, actorSkills, school, skillTerm, store);
assert(school >= 0 && school < 6);
SchoolCaps& cap = schoolCaps[school];
if (cap.mReachedLimit && spell->mData.mCost <= cap.mMinCost)
continue;
static const float fAutoSpellChance = store->findGmstFloat("fAutoSpellChance");
if (calcAutoCastChance(spell, actorSkills, actorAttributes, school, store) < fAutoSpellChance)
continue;
selectedSpells.push_back(spell->mId);
if (cap.mReachedLimit)
{
std::vector<std::string>::iterator found = std::find(selectedSpells.begin(), selectedSpells.end(), cap.mWeakestSpell);
if (found != selectedSpells.end())
selectedSpells.erase(found);
cap.mMinCost = INT_MAX;
for (std::vector<std::string>::iterator weakIt = selectedSpells.begin(); weakIt != selectedSpells.end(); ++weakIt)
{
std::vector<ESM::Spell*>::const_iterator it = spells.begin();
for (; it != spells.end(); ++it)
{
if ((*it)->mId == *weakIt)
break;
}
if (it == spells.end())
continue;
const ESM::Spell* testSpell = *it;
//int testSchool;
//float dummySkillTerm;
//calcWeakestSchool(testSpell, actorSkills, testSchool, dummySkillTerm);
// Note: if there are multiple spells with the same cost, we pick the first one we found.
// So the algorithm depends on the iteration order of the outer loop.
if (
// There is a huge bug here. It is not checked that weakestSpell is of the correct school.
// As result multiple SchoolCaps could have the same mWeakestSpell. Erasing the weakest spell would then fail if another school
// already erased it, and so the number of spells would often exceed the sum of limits.
// This bug cannot be fixed without significantly changing the results of the spell autocalc, which will not have been playtested.
//testSchool == school &&
testSpell->mData.mCost < cap.mMinCost)
{
cap.mMinCost = testSpell->mData.mCost;
cap.mWeakestSpell = testSpell->mId;
}
}
}
else
{
cap.mCount += 1;
if (cap.mCount == cap.mLimit)
cap.mReachedLimit = true;
if (spell->mData.mCost < cap.mMinCost)
{
cap.mWeakestSpell = spell->mId;
cap.mMinCost = spell->mData.mCost;
}
}
}
return selectedSpells;
}
bool attrSkillCheck (const ESM::Spell* spell,
const int* actorSkills, const int* actorAttributes, StoreCommon *store)
{
const std::vector<ESM::ENAMstruct>& effects = spell->mEffects.mList;
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = effects.begin(); effectIt != effects.end(); ++effectIt)
{
const ESM::MagicEffect* magicEffect = store->findMagicEffect(effectIt->mEffectID);
static const int iAutoSpellAttSkillMin = store->findGmstInt("iAutoSpellAttSkillMin");
if ((magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill))
{
assert (effectIt->mSkill >= 0 && effectIt->mSkill < ESM::Skill::Length);
if (actorSkills[effectIt->mSkill] < iAutoSpellAttSkillMin)
return false;
}
if ((magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute))
{
assert (effectIt->mAttribute >= 0 && effectIt->mAttribute < ESM::Attribute::Length);
if (actorAttributes[effectIt->mAttribute] < iAutoSpellAttSkillMin)
return false;
}
}
return true;
}
ESM::Skill::SkillEnum mapSchoolToSkill(int school)
{
std::map<int, ESM::Skill::SkillEnum> schoolSkillMap; // maps spell school to skill id
schoolSkillMap[0] = ESM::Skill::Alteration;
schoolSkillMap[1] = ESM::Skill::Conjuration;
schoolSkillMap[3] = ESM::Skill::Illusion;
schoolSkillMap[2] = ESM::Skill::Destruction;
schoolSkillMap[4] = ESM::Skill::Mysticism;
schoolSkillMap[5] = ESM::Skill::Restoration;
assert(schoolSkillMap.find(school) != schoolSkillMap.end());
return schoolSkillMap[school];
}
void calcWeakestSchool (const ESM::Spell* spell,
const int* actorSkills, int& effectiveSchool, float& skillTerm, StoreCommon *store)
{
float minChance = FLT_MAX;
const ESM::EffectList& effects = spell->mEffects;
for (std::vector<ESM::ENAMstruct>::const_iterator it = effects.mList.begin(); it != effects.mList.end(); ++it)
{
const ESM::ENAMstruct& effect = *it;
float x = static_cast<float>(effect.mDuration);
const ESM::MagicEffect* magicEffect = store->findMagicEffect(effect.mEffectID);
if (!(magicEffect->mData.mFlags & ESM::MagicEffect::UncappedDamage))
x = std::max(1.f, x);
x *= 0.1f * magicEffect->mData.mBaseCost;
x *= 0.5f * (effect.mMagnMin + effect.mMagnMax);
x += effect.mArea * 0.05f * magicEffect->mData.mBaseCost;
if (effect.mRange == ESM::RT_Target)
x *= 1.5f;
static const float fEffectCostMult = store->findGmstFloat("fEffectCostMult");
x *= fEffectCostMult;
float s = 2.f * actorSkills[mapSchoolToSkill(magicEffect->mData.mSchool)];
if (s - x < minChance)
{
minChance = s - x;
effectiveSchool = magicEffect->mData.mSchool;
skillTerm = s;
}
}
}
float calcAutoCastChance(const ESM::Spell *spell,
const int *actorSkills, const int *actorAttributes, int effectiveSchool, StoreCommon *store)
{
if (spell->mData.mType != ESM::Spell::ST_Spell)
return 100.f;
if (spell->mData.mFlags & ESM::Spell::F_Always)
return 100.f;
float skillTerm = 0;
if (effectiveSchool != -1)
skillTerm = 2.f * actorSkills[mapSchoolToSkill(effectiveSchool)];
else
calcWeakestSchool(spell, actorSkills, effectiveSchool, skillTerm, store); // Note effectiveSchool is unused after this
float castChance = skillTerm - spell->mData.mCost + 0.2f * actorAttributes[ESM::Attribute::Willpower] + 0.1f * actorAttributes[ESM::Attribute::Luck];
return castChance;
}
}

View file

@ -0,0 +1,39 @@
#ifndef COMPONENTS_AUTOCALC_AUTOCALCSPELL_H
#define COMPONENTS_AUTOCALC_AUTOCALCSPELL_H
#include <vector>
#include <components/esm/loadskil.hpp>
namespace ESM
{
struct Spell;
struct Race;
}
namespace AutoCalc
{
class StoreCommon;
/// Contains algorithm for calculating an NPC's spells based on stats
std::vector<std::string> autoCalcNpcSpells(const int* actorSkills,
const int* actorAttributes, const ESM::Race* race, StoreCommon *store);
// Helpers
bool attrSkillCheck (const ESM::Spell* spell,
const int* actorSkills, const int* actorAttributes, StoreCommon *store);
ESM::Skill::SkillEnum mapSchoolToSkill(int school);
void calcWeakestSchool(const ESM::Spell* spell,
const int* actorSkills, int& effectiveSchool, float& skillTerm, StoreCommon *store);
float calcAutoCastChance(const ESM::Spell* spell,
const int* actorSkills, const int* actorAttributes, int effectiveSchool, StoreCommon *store);
}
#endif // COMPONENTS_AUTOCALC_AUTOCALCSPELL_H

View file

@ -0,0 +1,35 @@
#ifndef COMPONENTS_AUTOCALC_STORE_H
#define COMPONENTS_AUTOCALC_STORE_H
#include <vector>
#include <string>
namespace ESM
{
struct Spell;
struct Skill;
struct MagicEffect;
}
namespace AutoCalc
{
// interface class for sharing the autocalc component between OpenMW and OpenCS
class StoreCommon
{
public:
StoreCommon() {}
virtual ~StoreCommon() {}
virtual int findGmstInt(const std::string& gmst) const = 0;
virtual float findGmstFloat(const std::string& gmst) const = 0;
virtual const ESM::Skill *findSkill(int index) const = 0;
virtual const ESM::MagicEffect* findMagicEffect(int id) const = 0;
virtual void getSpells(std::vector<ESM::Spell*>& spells) = 0;
};
}
#endif // COMPONENTS_AUTOCALC_STORE_H