mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-16 11:49:56 +00:00
Move NPC autocalc to a separate component so that it can be shared between OpenMW and OpenCS.
- Vanilla behaviour mimic'd where possible, except copying over autocalc spells to the npc's spell list when the status changes
This commit is contained in:
parent
ccf840da2b
commit
6b00d4ad91
25 changed files with 1631 additions and 760 deletions
|
@ -26,7 +26,7 @@ opencs_units_noqt (model/world
|
|||
universalid record commands columnbase scriptcontext cell refidcollection
|
||||
refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection tablemimedata cellcoordinates cellselection resources resourcesmanager scope
|
||||
pathgrid landtexture land nestedtablewrapper nestedcollection nestedcoladapterimp nestedinfocollection
|
||||
idcompletionmanager
|
||||
idcompletionmanager npcstats
|
||||
)
|
||||
|
||||
opencs_hdrs_noqt (model/world
|
||||
|
|
|
@ -311,6 +311,10 @@ namespace CSMWorld
|
|||
{ ColumnId_WaterLevel, "Water Level" },
|
||||
{ ColumnId_MapColor, "Map Color" },
|
||||
|
||||
{ ColumnId_SpellSrc, "From Race" },
|
||||
{ ColumnId_SpellCost, "Cast Cost" },
|
||||
{ ColumnId_SpellChance, "Cast Chance" },
|
||||
|
||||
{ ColumnId_UseValue1, "Use value 1" },
|
||||
{ ColumnId_UseValue2, "Use value 2" },
|
||||
{ ColumnId_UseValue3, "Use value 3" },
|
||||
|
|
|
@ -302,6 +302,10 @@ namespace CSMWorld
|
|||
ColumnId_WaterLevel = 273,
|
||||
ColumnId_MapColor = 274,
|
||||
|
||||
ColumnId_SpellSrc = 275,
|
||||
ColumnId_SpellCost = 276,
|
||||
ColumnId_SpellChance = 277,
|
||||
|
||||
// Allocated to a separate value range, so we don't get a collision should we ever need
|
||||
// to extend the number of use values.
|
||||
ColumnId_UseValue1 = 0x10000,
|
||||
|
|
|
@ -11,6 +11,10 @@
|
|||
#include <components/esm/loadglob.hpp>
|
||||
#include <components/esm/cellref.hpp>
|
||||
|
||||
#include <components/gameplay/autocalc.hpp>
|
||||
#include <components/gameplay/autocalcspell.hpp>
|
||||
#include <components/gameplay/store.hpp>
|
||||
|
||||
#include "idtable.hpp"
|
||||
#include "idtree.hpp"
|
||||
#include "columnimp.hpp"
|
||||
|
@ -19,6 +23,117 @@
|
|||
#include "resourcesmanager.hpp"
|
||||
#include "resourcetable.hpp"
|
||||
#include "nestedcoladapterimp.hpp"
|
||||
#include "npcstats.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
class SpellStore : public GamePlay::CommonStore <ESM::Spell>
|
||||
{
|
||||
const CSMWorld::NestedIdCollection<ESM::Spell>& mSpells;
|
||||
std::vector<ESM::Spell *> mLocal;
|
||||
|
||||
public:
|
||||
|
||||
SpellStore(const CSMWorld::NestedIdCollection<ESM::Spell>& spells)
|
||||
: mSpells(spells)
|
||||
{
|
||||
// prepare data in a format used by OpenMW store
|
||||
for (int index = 0; index < mSpells.getSize(); ++index)
|
||||
{
|
||||
ESM::Spell *spell = const_cast<ESM::Spell *>(&mSpells.getRecord(index).get());
|
||||
mLocal.push_back(spell);
|
||||
}
|
||||
}
|
||||
|
||||
~SpellStore() {}
|
||||
|
||||
typedef GamePlay::SharedIterator<ESM::Spell> iterator;
|
||||
|
||||
virtual iterator begin() const
|
||||
{
|
||||
return mLocal.begin();
|
||||
}
|
||||
|
||||
virtual iterator end() const
|
||||
{
|
||||
return mLocal.end();
|
||||
}
|
||||
|
||||
virtual const ESM::Spell *find(const std::string &id) const
|
||||
{
|
||||
return &mSpells.getRecord(id).get();
|
||||
}
|
||||
|
||||
virtual size_t getSize() const
|
||||
{
|
||||
return mSpells.getSize();
|
||||
}
|
||||
|
||||
private:
|
||||
// not used in OpenCS
|
||||
virtual void load(ESM::ESMReader &esm, const std::string &id)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class CSStore : public GamePlay::StoreWrap
|
||||
{
|
||||
const CSMWorld::IdCollection<ESM::GameSetting>& mGmstTable;
|
||||
const CSMWorld::IdCollection<ESM::Skill>& mSkillTable;
|
||||
const CSMWorld::IdCollection<ESM::MagicEffect>& mMagicEffectTable;
|
||||
const SpellStore mSpellStore;
|
||||
|
||||
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), mSpellStore(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 const GamePlay::CommonStore<ESM::Spell>& getSpells() const
|
||||
{
|
||||
return mSpellStore;
|
||||
}
|
||||
};
|
||||
|
||||
unsigned short autoCalculateMana(GamePlay::StatsBase& stats)
|
||||
{
|
||||
return stats.getBaseAttribute(ESM::Attribute::Intelligence) * 2;
|
||||
}
|
||||
|
||||
unsigned short autoCalculateFatigue(GamePlay::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)
|
||||
{
|
||||
|
@ -203,7 +318,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc
|
|||
mSpells.addColumn (new RecordStateColumn<ESM::Spell>);
|
||||
mSpells.addColumn (new FixedRecordTypeColumn<ESM::Spell> (UniversalId::Type_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 FlagColumn<ESM::Spell> (Columns::ColumnId_AutoCalc, 0x1));
|
||||
mSpells.addColumn (new FlagColumn<ESM::Spell> (Columns::ColumnId_StarterSpell, 0x2));
|
||||
|
@ -517,11 +632,40 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc
|
|||
addModel (new ResourceTable (&mResourcesManager.get (UniversalId::Type_Videos)),
|
||||
UniversalId::Type_Video);
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
CSMWorld::Data::~Data()
|
||||
{
|
||||
clearNpcStatsCache();
|
||||
|
||||
for (std::vector<QAbstractItemModel *>::iterator iter (mModels.begin()); iter!=mModels.end(); ++iter)
|
||||
delete *iter;
|
||||
|
||||
|
@ -1165,3 +1309,361 @@ 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
|
||||
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);
|
||||
|
||||
bool match = false;
|
||||
if (topLeft.parent().isValid() && bottomRight.parent().isValid())
|
||||
{
|
||||
if ((topLeft.parent().column() <= attrColumn && attrColumn <= bottomRight.parent().column())
|
||||
|| (topLeft.parent().column() <= bonusColumn && bonusColumn <= bottomRight.parent().column()))
|
||||
{
|
||||
match = true; // TODO: check for specific nested column?
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((topLeft.column() <= attrColumn && attrColumn <= bottomRight.column())
|
||||
|| (topLeft.column() <= bonusColumn && bonusColumn <= 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 (int raceRow = topLeft.parent().row(); raceRow <= bottomRight.parent().row(); ++raceRow)
|
||||
{
|
||||
clearNpcStatsCache();
|
||||
|
||||
std::string raceId =
|
||||
raceModel->data(raceModel->index(raceRow, idColumn)).toString().toUtf8().constData();
|
||||
emit updateNpcAutocalc(2/*race*/, raceId);
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: currently ignoring level changes
|
||||
void CSMWorld::Data::npcDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight)
|
||||
{
|
||||
clearNpcStatsCache();
|
||||
|
||||
// Either autoCalc flag changed or NPC level changed
|
||||
CSMWorld::IdTree *objectModel =
|
||||
static_cast<CSMWorld::IdTree*>(getTableModel(CSMWorld::UniversalId::Type_Referenceable));
|
||||
|
||||
int autoCalcColumn = objectModel->findColumnIndex(CSMWorld::Columns::ColumnId_AutoCalc);
|
||||
int miscColumn = objectModel->findColumnIndex(CSMWorld::Columns::ColumnId_NpcMisc);
|
||||
|
||||
// first check for level
|
||||
bool levelChanged = false;
|
||||
if (topLeft.parent().isValid() && bottomRight.parent().isValid())
|
||||
{
|
||||
if (topLeft.parent().column() <= miscColumn && miscColumn <= bottomRight.parent().column())
|
||||
{
|
||||
for (int col = topLeft.column(); col <= bottomRight.column(); ++col)
|
||||
{
|
||||
int role = objectModel->nestedHeaderData(topLeft.parent().column(),
|
||||
col, Qt::Horizontal, CSMWorld::ColumnBase::Role_ColumnId).toInt();
|
||||
if (role == CSMWorld::Columns::ColumnId_NpcLevel)
|
||||
{
|
||||
levelChanged = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// next check for autocalc
|
||||
bool autoCalcChanged = false;
|
||||
if (!topLeft.parent().isValid() && !bottomRight.parent().isValid())
|
||||
{
|
||||
if ((topLeft.column() <= autoCalcColumn && autoCalcColumn <= bottomRight.column())
|
||||
|| (topLeft.column() <= miscColumn && miscColumn <= bottomRight.column()))
|
||||
{
|
||||
autoCalcChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!levelChanged && !autoCalcChanged)
|
||||
return;
|
||||
|
||||
int row = 0;
|
||||
int end = 0;
|
||||
if (topLeft.parent().isValid())
|
||||
row = topLeft.parent().row();
|
||||
else
|
||||
row = topLeft.row();
|
||||
|
||||
if (bottomRight.parent().isValid())
|
||||
end = bottomRight.parent().row();
|
||||
else
|
||||
end = bottomRight.row();
|
||||
|
||||
for (; row <= end; ++row)
|
||||
{
|
||||
Record<ESM::NPC> record =
|
||||
static_cast<const Record<ESM::NPC>&>(mReferenceables.getRecord(row));
|
||||
ESM::NPC &npc = record.get();
|
||||
|
||||
// If going from autocalc to non-autocalc, save the autocalc values
|
||||
if (autoCalcChanged)
|
||||
{
|
||||
if (npc.mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
|
||||
saveAutoCalcValues(npc); // update attributes and skills
|
||||
else
|
||||
npc.mNpdt12.mLevel = npc.mNpdt52.mLevel; // for NPC's loaded as non-autocalc
|
||||
|
||||
record.setModified(npc);
|
||||
mReferenceables.replace(row, record);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// FIXME: how to undo?
|
||||
void CSMWorld::Data::saveAutoCalcValues(ESM::NPC& npc)
|
||||
{
|
||||
CSMWorld::NpcStats * cachedStats = getCachedNpcData (npc.mId);
|
||||
if (!cachedStats)
|
||||
return; // silently fail
|
||||
|
||||
CSMWorld::NpcStats* stats = npcAutoCalculate(npc);
|
||||
|
||||
// update npc
|
||||
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)
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
CSMWorld::NpcStats *stats = new CSMWorld::NpcStats();
|
||||
|
||||
CSStore store(mGmsts, mSkills, mMagicEffects, mSpells);
|
||||
|
||||
if (autoCalc)
|
||||
{
|
||||
GamePlay::autoCalcAttributesImpl (&npc, race, class_, level, *stats, &store);
|
||||
|
||||
stats->setHealth(autoCalculateHealth(level, class_, *stats));
|
||||
stats->setMana(autoCalculateMana(*stats));
|
||||
stats->setFatigue(autoCalculateFatigue(*stats));
|
||||
|
||||
GamePlay::autoCalcSkillsImpl(&npc, race, class_, level, *stats, &store);
|
||||
|
||||
GamePlay::autoCalculateSpells(race, *stats, &store);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (std::vector<std::string>::const_iterator it = npc.mSpells.mList.begin();
|
||||
it != npc.mSpells.mList.end(); ++it)
|
||||
{
|
||||
stats->addSpells(*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;
|
||||
GamePlay::calcWeakestSchool(spell, skills, school, skillTerm, &store);
|
||||
float chance = calcAutoCastChance(spell, skills, attributes, school, &store);
|
||||
|
||||
stats->addCostAndChance((*it).mName, cost, (int)ceil(chance)); // percent
|
||||
}
|
||||
}
|
||||
|
||||
emit cacheNpcStats (npc.mId, stats);
|
||||
return stats;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -60,6 +60,7 @@ namespace CSMWorld
|
|||
{
|
||||
class ResourcesManager;
|
||||
class Resources;
|
||||
class NpcStats;
|
||||
|
||||
class Data : public QObject
|
||||
{
|
||||
|
@ -108,6 +109,8 @@ namespace CSMWorld
|
|||
|
||||
std::vector<boost::shared_ptr<ESM::ESMReader> > mReaders;
|
||||
|
||||
std::map<std::string, NpcStats*> mNpcStatCache;
|
||||
|
||||
// not implemented
|
||||
Data (const Data&);
|
||||
Data& operator= (const Data&);
|
||||
|
@ -121,7 +124,11 @@ namespace CSMWorld
|
|||
|
||||
static int count (RecordBase::State state, const CollectionBase& collection);
|
||||
|
||||
const CSMWorld::Data& self ();
|
||||
const Data& self ();
|
||||
|
||||
void saveAutoCalcValues(ESM::NPC& npc);
|
||||
|
||||
void clearNpcStatsCache ();
|
||||
|
||||
public:
|
||||
|
||||
|
@ -277,15 +284,37 @@ namespace CSMWorld
|
|||
|
||||
std::string getAuthor() const;
|
||||
|
||||
NpcStats* npcAutoCalculate (const ESM::NPC& npc) const;
|
||||
|
||||
NpcStats* getCachedNpcData (const std::string& id) const;
|
||||
|
||||
signals:
|
||||
|
||||
void idListChanged();
|
||||
|
||||
// refresh NPC dialogue subviews via object table model
|
||||
void updateNpcAutocalc (int type, const std::string& id);
|
||||
|
||||
void cacheNpcStats (const std::string& id, NpcStats *stats) const;
|
||||
|
||||
private slots:
|
||||
|
||||
void dataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight);
|
||||
|
||||
void rowsChanged (const QModelIndex& parent, int start, int end);
|
||||
|
||||
// for autocalc updates when gmst/race/class/skils tables change
|
||||
void gmstDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight);
|
||||
|
||||
void raceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight);
|
||||
|
||||
void classDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight);
|
||||
|
||||
void skillDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight);
|
||||
|
||||
void npcDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight);
|
||||
|
||||
void cacheNpcStatsEvent (const std::string& id, NpcStats *stats);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -261,3 +261,8 @@ CSMWorld::NestedTableWrapperBase* CSMWorld::IdTree::nestedTable(const QModelInde
|
|||
|
||||
return mNestedCollection->nestedTable(index.row(), index.column());
|
||||
}
|
||||
|
||||
void CSMWorld::IdTree::updateNpcAutocalc (int type, const std::string& id)
|
||||
{
|
||||
emit refreshNpcDialogue (type, id);
|
||||
}
|
||||
|
|
|
@ -73,11 +73,17 @@ namespace CSMWorld
|
|||
|
||||
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);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -9,191 +9,8 @@
|
|||
|
||||
#include "nestedtablewrapper.hpp"
|
||||
#include "usertype.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
int is_even(double d)
|
||||
{
|
||||
double int_part;
|
||||
|
||||
modf(d / 2.0, &int_part);
|
||||
return 2.0 * int_part == d;
|
||||
}
|
||||
|
||||
int round_ieee_754(double d)
|
||||
{
|
||||
double i = floor(d);
|
||||
d -= i;
|
||||
|
||||
if(d < 0.5)
|
||||
return static_cast<int>(i);
|
||||
if(d > 0.5)
|
||||
return static_cast<int>(i) + 1;
|
||||
if(is_even(i))
|
||||
return static_cast<int>(i);
|
||||
return static_cast<int>(i) + 1;
|
||||
}
|
||||
|
||||
std::vector<int> autoCalculateAttributes (const ESM::NPC &npc,
|
||||
const ESM::Race& race, const ESM::Class& class_, const CSMWorld::IdCollection<ESM::Skill>& skillTable)
|
||||
{
|
||||
// race bonus
|
||||
bool male = (npc.mFlags & ESM::NPC::Female) == 0;
|
||||
|
||||
if (npc.mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
|
||||
return std::vector<int>();
|
||||
|
||||
short level = npc.mNpdt12.mLevel;
|
||||
|
||||
int attr[ESM::Attribute::Length];
|
||||
|
||||
for (int i = 0; i < ESM::Attribute::Length; ++i)
|
||||
{
|
||||
const ESM::Race::MaleFemale& attribute = race.mData.mAttributeValues[i];
|
||||
attr[i] = male ? attribute.mMale : attribute.mFemale;
|
||||
}
|
||||
|
||||
// class bonus
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
int attribute = class_.mData.mAttribute[i];
|
||||
if (attribute >= 0 && attribute < ESM::Attribute::Length)
|
||||
{
|
||||
attr[attribute] = attr[attribute] + 10;
|
||||
}
|
||||
// else log an error
|
||||
}
|
||||
|
||||
std::vector<int> result(ESM::Attribute::Length);
|
||||
// skill bonus
|
||||
for (int attribute = 0; attribute < ESM::Attribute::Length; ++attribute)
|
||||
{
|
||||
float modifierSum = 0;
|
||||
|
||||
for (int j = 0; j < ESM::Skill::Length; ++j)
|
||||
{
|
||||
// if the skill does not exist, throws std::runtime_error ("invalid ID: " + id)
|
||||
const ESM::Skill& skill = skillTable.getRecord(ESM::Skill::indexToId(j)).get();
|
||||
|
||||
if (skill.mData.mAttribute != attribute)
|
||||
continue;
|
||||
|
||||
// is this a minor or major skill?
|
||||
float add = 0.2f;
|
||||
for (int k = 0; k < 5; ++k)
|
||||
{
|
||||
if (class_.mData.mSkills[k][0] == j)
|
||||
add = 0.5;
|
||||
}
|
||||
for (int k = 0; k < 5; ++k)
|
||||
{
|
||||
if (class_.mData.mSkills[k][1] == j)
|
||||
add = 1.0;
|
||||
}
|
||||
modifierSum += add;
|
||||
}
|
||||
result[attribute] = std::min(round_ieee_754(attr[attribute] + (level-1) * modifierSum), 100);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<unsigned char> autoCalculateSkills (const ESM::NPC &npc,
|
||||
const ESM::Race& race, const ESM::Class& class_, const CSMWorld::IdCollection<ESM::Skill>& skillTable)
|
||||
{
|
||||
unsigned char skills[ESM::Skill::Length];
|
||||
for (int i = 0; i < ESM::Skill::Length; ++i)
|
||||
skills[i] = 0;
|
||||
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
int bonus = (i == 0) ? 10 : 25;
|
||||
|
||||
for (int i2 = 0; i2 < 5; ++i2)
|
||||
{
|
||||
int index = class_.mData.mSkills[i2][i];
|
||||
if (index >= 0 && index < ESM::Skill::Length)
|
||||
{
|
||||
skills[index] = bonus;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<unsigned char> result(ESM::Skill::Length);
|
||||
for (int skillIndex = 0; skillIndex < ESM::Skill::Length; ++skillIndex)
|
||||
{
|
||||
float majorMultiplier = 0.1f;
|
||||
float specMultiplier = 0.0f;
|
||||
|
||||
int raceBonus = 0;
|
||||
int specBonus = 0;
|
||||
|
||||
for (int raceSkillIndex = 0; raceSkillIndex < 7; ++raceSkillIndex)
|
||||
{
|
||||
if (race.mData.mBonus[raceSkillIndex].mSkill == skillIndex)
|
||||
{
|
||||
raceBonus = race.mData.mBonus[raceSkillIndex].mBonus;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int k = 0; k < 5; ++k)
|
||||
{
|
||||
// is this a minor or major skill?
|
||||
if ((class_.mData.mSkills[k][0] == skillIndex) || (class_.mData.mSkills[k][1] == skillIndex))
|
||||
{
|
||||
majorMultiplier = 1.0f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// is this skill in the same Specialization as the class?
|
||||
const ESM::Skill& skill = skillTable.getRecord(ESM::Skill::indexToId(skillIndex)).get();
|
||||
if (skill.mData.mSpecialization == class_.mData.mSpecialization)
|
||||
{
|
||||
specMultiplier = 0.5f;
|
||||
specBonus = 5;
|
||||
}
|
||||
|
||||
// Must gracefully handle level 0
|
||||
result[skillIndex] = std::min(round_ieee_754(skills[skillIndex] + 5 + raceBonus + specBonus +
|
||||
(npc.mNpdt12.mLevel-1) * (majorMultiplier + specMultiplier)), 100);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
unsigned short autoCalculateHealth(const ESM::NPC &npc,
|
||||
const ESM::Class& class_, const std::vector<int>& attr)
|
||||
{
|
||||
int multiplier = 3;
|
||||
|
||||
if (class_.mData.mSpecialization == ESM::Class::Combat)
|
||||
multiplier += 2;
|
||||
else if (class_.mData.mSpecialization == ESM::Class::Stealth)
|
||||
multiplier += 1;
|
||||
|
||||
if (class_.mData.mAttribute[0] == ESM::Attribute::Endurance
|
||||
|| class_.mData.mAttribute[1] == ESM::Attribute::Endurance)
|
||||
multiplier += 1;
|
||||
|
||||
return floor(0.5f * (attr[ESM::Attribute::Strength]+ attr[ESM::Attribute::Endurance]))
|
||||
+ multiplier * (npc.mNpdt12.mLevel-1);
|
||||
}
|
||||
|
||||
unsigned short autoCalculateMana(const std::vector<int>& attr)
|
||||
{
|
||||
return attr[ESM::Attribute::Intelligence] * 2;
|
||||
}
|
||||
|
||||
unsigned short autoCalculateFatigue(const std::vector<int>& attr)
|
||||
{
|
||||
return attr[ESM::Attribute::Strength] + attr[ESM::Attribute::Willpower]
|
||||
+ attr[ESM::Attribute::Agility] + attr[ESM::Attribute::Endurance];
|
||||
}
|
||||
|
||||
}
|
||||
#include "idtree.hpp"
|
||||
#include "npcstats.hpp"
|
||||
|
||||
CSMWorld::PotionColumns::PotionColumns (const InventoryColumns& columns)
|
||||
: InventoryColumns (columns) {}
|
||||
|
@ -741,12 +558,8 @@ CSMWorld::NpcColumns::NpcColumns (const ActorColumns& actorColumns)
|
|||
mMisc(NULL)
|
||||
{}
|
||||
|
||||
CSMWorld::NpcRefIdAdapter::NpcRefIdAdapter (const NpcColumns& columns,
|
||||
const CSMWorld::IdCollection<ESM::Race>& races,
|
||||
const CSMWorld::IdCollection<ESM::Class>& classes,
|
||||
const CSMWorld::IdCollection<ESM::Skill>& skills)
|
||||
: ActorRefIdAdapter<ESM::NPC> (UniversalId::Type_Npc, columns), mColumns (columns),
|
||||
mRaceTable(races), mClassTable(classes), mSkillTable(skills)
|
||||
CSMWorld::NpcRefIdAdapter::NpcRefIdAdapter (const NpcColumns& columns)
|
||||
: ActorRefIdAdapter<ESM::NPC> (UniversalId::Type_Npc, columns), mColumns (columns)
|
||||
{}
|
||||
|
||||
QVariant CSMWorld::NpcRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, int index)
|
||||
|
@ -821,46 +634,8 @@ void CSMWorld::NpcRefIdAdapter::setData (const RefIdColumn *column, RefIdData& d
|
|||
npc.mFlags &= ~iter->second;
|
||||
|
||||
if (iter->second == ESM::NPC::Autocalc)
|
||||
{
|
||||
if(value.toInt() != 0)
|
||||
{
|
||||
npc.mNpdtType = ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS;
|
||||
|
||||
// if the race/class does not exist, throws std::runtime_error ("invalid ID: " + id)
|
||||
const ESM::Race& race = mRaceTable.getRecord(npc.mRace).get();
|
||||
const ESM::Class& class_ = mClassTable.getRecord(npc.mClass).get();
|
||||
std::vector<int> attr = autoCalculateAttributes(npc, race, class_, mSkillTable);
|
||||
if (attr.empty())
|
||||
return;
|
||||
|
||||
std::vector<unsigned char> skills = autoCalculateSkills(npc, race, class_, mSkillTable);
|
||||
|
||||
ESM::NPC::NPDTstruct52& npcStruct = npc.mNpdt52;
|
||||
|
||||
npcStruct.mLevel = npc.mNpdt12.mLevel;
|
||||
npcStruct.mStrength = attr[ESM::Attribute::Strength];
|
||||
npcStruct.mIntelligence = attr[ESM::Attribute::Intelligence];
|
||||
npcStruct.mWillpower = attr[ESM::Attribute::Willpower];
|
||||
npcStruct.mAgility = attr[ESM::Attribute::Agility];
|
||||
npcStruct.mSpeed = attr[ESM::Attribute::Speed];
|
||||
npcStruct.mEndurance = attr[ESM::Attribute::Endurance];
|
||||
npcStruct.mPersonality = attr[ESM::Attribute::Personality];
|
||||
npcStruct.mLuck = attr[ESM::Attribute::Luck];
|
||||
for (int i = 0; i < ESM::Skill::Length; ++i)
|
||||
{
|
||||
npcStruct.mSkills[i] = skills[i];
|
||||
}
|
||||
npcStruct.mHealth = autoCalculateHealth(npc, class_, attr);
|
||||
npcStruct.mMana = autoCalculateMana(attr);
|
||||
npcStruct.mFatigue = autoCalculateFatigue(attr);
|
||||
npcStruct.mDisposition = npc.mNpdt12.mDisposition;
|
||||
npcStruct.mReputation = npc.mNpdt12.mReputation;
|
||||
npcStruct.mRank = npc.mNpdt12.mRank;
|
||||
npcStruct.mGold = npc.mNpdt12.mGold;
|
||||
}
|
||||
else
|
||||
npc.mNpdtType = ESM::NPC::NPC_DEFAULT;
|
||||
}
|
||||
npc.mNpdtType = (value.toInt() != 0) ? ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS
|
||||
: ESM::NPC::NPC_DEFAULT;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -873,9 +648,7 @@ void CSMWorld::NpcRefIdAdapter::setData (const RefIdColumn *column, RefIdData& d
|
|||
record.setModified (npc);
|
||||
}
|
||||
|
||||
CSMWorld::NpcAttributesRefIdAdapter::NpcAttributesRefIdAdapter(const CSMWorld::IdCollection<ESM::Race>& races,
|
||||
const CSMWorld::IdCollection<ESM::Class>& classes, const CSMWorld::IdCollection<ESM::Skill>& skills)
|
||||
: mRaceTable(races), mClassTable(classes), mSkillTable(skills)
|
||||
CSMWorld::NpcAttributesRefIdAdapter::NpcAttributesRefIdAdapter(const CSMWorld::Data& data) : mData(data)
|
||||
{}
|
||||
|
||||
void CSMWorld::NpcAttributesRefIdAdapter::addNestedRow (const RefIdColumn *column,
|
||||
|
@ -940,27 +713,20 @@ QVariant CSMWorld::NpcAttributesRefIdAdapter::getNestedData (const RefIdColumn *
|
|||
default: return QVariant(); // throw an exception here?
|
||||
}
|
||||
else if (subColIndex == 1)
|
||||
// It may be possible to have mNpdt52 values different to autocalculated ones when
|
||||
// first loaded, so re-calculate
|
||||
if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
|
||||
{
|
||||
const ESM::Race& race = mRaceTable.getRecord(npc.mRace).get();
|
||||
const ESM::Class& class_ = mClassTable.getRecord(npc.mClass).get();
|
||||
std::vector<int> attr = autoCalculateAttributes(npc, race, class_, mSkillTable);
|
||||
|
||||
if (attr.empty())
|
||||
return QVariant();
|
||||
CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc);
|
||||
|
||||
switch (subRowIndex)
|
||||
{
|
||||
case 0: return static_cast<int>(attr[ESM::Attribute::Strength]);
|
||||
case 1: return static_cast<int>(attr[ESM::Attribute::Intelligence]);
|
||||
case 2: return static_cast<int>(attr[ESM::Attribute::Willpower]);
|
||||
case 3: return static_cast<int>(attr[ESM::Attribute::Agility]);
|
||||
case 4: return static_cast<int>(attr[ESM::Attribute::Speed]);
|
||||
case 5: return static_cast<int>(attr[ESM::Attribute::Endurance]);
|
||||
case 6: return static_cast<int>(attr[ESM::Attribute::Personality]);
|
||||
case 7: return static_cast<int>(attr[ESM::Attribute::Luck]);
|
||||
case 0: return static_cast<int>(stats->getBaseAttribute(ESM::Attribute::Strength));
|
||||
case 1: return static_cast<int>(stats->getBaseAttribute(ESM::Attribute::Intelligence));
|
||||
case 2: return static_cast<int>(stats->getBaseAttribute(ESM::Attribute::Willpower));
|
||||
case 3: return static_cast<int>(stats->getBaseAttribute(ESM::Attribute::Agility));
|
||||
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?
|
||||
}
|
||||
}
|
||||
|
@ -1019,10 +785,8 @@ int CSMWorld::NpcAttributesRefIdAdapter::getNestedRowsCount(const RefIdColumn *c
|
|||
return 8;
|
||||
}
|
||||
|
||||
CSMWorld::NpcSkillsRefIdAdapter::NpcSkillsRefIdAdapter(const CSMWorld::IdCollection<ESM::Race>& races,
|
||||
const CSMWorld::IdCollection<ESM::Class>& classes,
|
||||
const CSMWorld::IdCollection<ESM::Skill>& skills)
|
||||
: mRaceTable(races), mClassTable(classes), mSkillTable(skills)
|
||||
CSMWorld::NpcSkillsRefIdAdapter::NpcSkillsRefIdAdapter(const CSMWorld::Data& data)
|
||||
: mData(data)
|
||||
{}
|
||||
|
||||
void CSMWorld::NpcSkillsRefIdAdapter::addNestedRow (const RefIdColumn *column,
|
||||
|
@ -1079,18 +843,10 @@ QVariant CSMWorld::NpcSkillsRefIdAdapter::getNestedData (const RefIdColumn *colu
|
|||
return QString(ESM::Skill::sSkillNames[subRowIndex].c_str());
|
||||
else if (subColIndex == 1)
|
||||
{
|
||||
// It may be possible to have mNpdt52 values different to autocalculated ones when
|
||||
// first loaded, so re-calculate
|
||||
if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
|
||||
{
|
||||
// if the race/class does not exist, throws std::runtime_error ("invalid ID: " + id)
|
||||
const ESM::Race& race = mRaceTable.getRecord(npc.mRace).get();
|
||||
const ESM::Class& class_ = mClassTable.getRecord(npc.mClass).get();
|
||||
std::vector<unsigned char> skills = autoCalculateSkills(npc, race, class_, mSkillTable);
|
||||
|
||||
int value = static_cast<int>(skills[subRowIndex]);
|
||||
|
||||
return static_cast<int>(skills[subRowIndex]);
|
||||
CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc);
|
||||
return static_cast<int>(stats->getBaseSkill(subRowIndex));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1132,10 +888,7 @@ int CSMWorld::NpcSkillsRefIdAdapter::getNestedRowsCount(const RefIdColumn *colum
|
|||
return ESM::Skill::Length;
|
||||
}
|
||||
|
||||
CSMWorld::NpcMiscRefIdAdapter::NpcMiscRefIdAdapter(const CSMWorld::IdCollection<ESM::Race>& races,
|
||||
const CSMWorld::IdCollection<ESM::Class>& classes,
|
||||
const CSMWorld::IdCollection<ESM::Skill>& skills)
|
||||
: mRaceTable(races), mClassTable(classes), mSkillTable(skills)
|
||||
CSMWorld::NpcMiscRefIdAdapter::NpcMiscRefIdAdapter(const CSMWorld::Data& data) : mData(data)
|
||||
{}
|
||||
|
||||
CSMWorld::NpcMiscRefIdAdapter::~NpcMiscRefIdAdapter()
|
||||
|
@ -1175,17 +928,9 @@ QVariant CSMWorld::NpcMiscRefIdAdapter::getNestedData (const RefIdColumn *column
|
|||
|
||||
bool autoCalc = (npc.mFlags & ESM::NPC::Autocalc) != 0;
|
||||
|
||||
// It may be possible to have mNpdt52 values different to autocalculated ones when
|
||||
// first loaded, so re-calculate
|
||||
if (autoCalc)
|
||||
{
|
||||
// if the race/class does not exist, throws std::runtime_error ("invalid ID: " + id)
|
||||
const ESM::Race& race = mRaceTable.getRecord(npc.mRace).get();
|
||||
const ESM::Class& class_ = mClassTable.getRecord(npc.mClass).get();
|
||||
std::vector<int> attr = autoCalculateAttributes(npc, race, class_, mSkillTable);
|
||||
|
||||
if (attr.empty())
|
||||
return QVariant();
|
||||
CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc);
|
||||
|
||||
switch (subColIndex)
|
||||
{
|
||||
|
@ -1197,17 +942,17 @@ QVariant CSMWorld::NpcMiscRefIdAdapter::getNestedData (const RefIdColumn *column
|
|||
}
|
||||
case 2:
|
||||
{
|
||||
UserInt i(autoCalculateHealth(npc, class_, attr));
|
||||
UserInt i(stats->getHealth());
|
||||
return QVariant(QVariant::fromValue(i));
|
||||
}
|
||||
case 3:
|
||||
{
|
||||
UserInt i(autoCalculateMana(attr));
|
||||
UserInt i(stats->getMana());
|
||||
return QVariant(QVariant::fromValue(i));
|
||||
}
|
||||
case 4:
|
||||
{
|
||||
UserInt i(autoCalculateFatigue(attr));
|
||||
UserInt i(stats->getFatigue());
|
||||
return QVariant(QVariant::fromValue(i));
|
||||
}
|
||||
case 5: return static_cast<int>(record.get().mNpdt12.mDisposition);
|
||||
|
@ -1247,108 +992,31 @@ void CSMWorld::NpcMiscRefIdAdapter::setNestedData (const RefIdColumn *column,
|
|||
if (autoCalc)
|
||||
switch(subColIndex)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
npc.mNpdt12.mLevel = static_cast<short>(value.toInt()); break;
|
||||
|
||||
const ESM::Race& race = mRaceTable.getRecord(npc.mRace).get();
|
||||
const ESM::Class& class_ = mClassTable.getRecord(npc.mClass).get();
|
||||
std::vector<int> attr = autoCalculateAttributes(npc, race, class_, mSkillTable);
|
||||
if (attr.empty())
|
||||
return;
|
||||
|
||||
ESM::NPC::NPDTstruct52& npcStruct = npc.mNpdt52;
|
||||
|
||||
std::vector<unsigned char> skills = autoCalculateSkills(npc, race, class_, mSkillTable);
|
||||
|
||||
npcStruct.mLevel = npc.mNpdt12.mLevel;
|
||||
npcStruct.mStrength = attr[ESM::Attribute::Strength];
|
||||
npcStruct.mIntelligence = attr[ESM::Attribute::Intelligence];
|
||||
npcStruct.mWillpower = attr[ESM::Attribute::Willpower];
|
||||
npcStruct.mAgility = attr[ESM::Attribute::Agility];
|
||||
npcStruct.mSpeed = attr[ESM::Attribute::Speed];
|
||||
npcStruct.mEndurance = attr[ESM::Attribute::Endurance];
|
||||
npcStruct.mPersonality = attr[ESM::Attribute::Personality];
|
||||
npcStruct.mLuck = attr[ESM::Attribute::Luck];
|
||||
for (int i = 0; i < ESM::Skill::Length; ++i)
|
||||
{
|
||||
npcStruct.mSkills[i] = skills[i];
|
||||
}
|
||||
npcStruct.mHealth = autoCalculateHealth(npc, class_, attr);
|
||||
npcStruct.mMana = autoCalculateMana(attr);
|
||||
npcStruct.mFatigue = autoCalculateFatigue(attr);
|
||||
|
||||
break;
|
||||
}
|
||||
case 0: npc.mNpdt12.mLevel = static_cast<short>(value.toInt()); break;
|
||||
case 1: return;
|
||||
case 2: return;
|
||||
case 3: return;
|
||||
case 4: return;
|
||||
case 5:
|
||||
{
|
||||
npc.mNpdt12.mDisposition = static_cast<signed char>(value.toInt());
|
||||
npc.mNpdt52.mDisposition = npc.mNpdt12.mDisposition;
|
||||
break;
|
||||
}
|
||||
case 6:
|
||||
{
|
||||
npc.mNpdt12.mReputation = static_cast<signed char>(value.toInt());
|
||||
npc.mNpdt52.mReputation = npc.mNpdt12.mReputation;
|
||||
break;
|
||||
}
|
||||
case 7:
|
||||
{
|
||||
npc.mNpdt12.mRank = static_cast<signed char>(value.toInt());
|
||||
npc.mNpdt52.mRank = npc.mNpdt12.mRank;
|
||||
break;
|
||||
}
|
||||
case 8:
|
||||
{
|
||||
npc.mNpdt12.mGold = value.toInt();
|
||||
npc.mNpdt52.mGold = npc.mNpdt12.mGold;
|
||||
break;
|
||||
}
|
||||
case 9: npc.mPersistent = value.toBool(); break;
|
||||
case 5: npc.mNpdt12.mDisposition = static_cast<signed char>(value.toInt()); break;
|
||||
case 6: npc.mNpdt12.mReputation = static_cast<signed char>(value.toInt()); break;
|
||||
case 7: npc.mNpdt12.mRank = static_cast<signed char>(value.toInt()); break;
|
||||
case 8: npc.mNpdt12.mGold = value.toInt(); break;
|
||||
case 9: npc.mPersistent = value.toBool(); break;
|
||||
default: return; // throw an exception here?
|
||||
}
|
||||
else
|
||||
switch(subColIndex)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
npc.mNpdt52.mLevel = static_cast<short>(value.toInt());
|
||||
npc.mNpdt12.mLevel = npc.mNpdt52.mLevel;
|
||||
break;
|
||||
}
|
||||
case 1: npc.mNpdt52.mFactionID = static_cast<char>(value.toInt()); break;
|
||||
case 2: npc.mNpdt52.mHealth = static_cast<unsigned short>(value.toInt()); break;
|
||||
case 3: npc.mNpdt52.mMana = static_cast<unsigned short>(value.toInt()); break;
|
||||
case 4: npc.mNpdt52.mFatigue = static_cast<unsigned short>(value.toInt()); break;
|
||||
case 5:
|
||||
{
|
||||
npc.mNpdt52.mDisposition = static_cast<signed char>(value.toInt());
|
||||
npc.mNpdt12.mDisposition = npc.mNpdt52.mDisposition;
|
||||
break;
|
||||
}
|
||||
case 6:
|
||||
{
|
||||
npc.mNpdt52.mReputation = static_cast<signed char>(value.toInt());
|
||||
npc.mNpdt12.mReputation = npc.mNpdt52.mReputation;
|
||||
break;
|
||||
}
|
||||
case 7:
|
||||
{
|
||||
npc.mNpdt52.mRank = static_cast<signed char>(value.toInt());
|
||||
npc.mNpdt12.mRank = npc.mNpdt52.mRank;
|
||||
break;
|
||||
}
|
||||
case 8:
|
||||
{
|
||||
npc.mNpdt52.mGold = value.toInt();
|
||||
npc.mNpdt12.mGold = npc.mNpdt52.mGold;
|
||||
break;
|
||||
}
|
||||
case 9: npc.mPersistent = value.toBool(); break;
|
||||
case 0: npc.mNpdt52.mLevel = static_cast<short>(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 3: npc.mNpdt52.mMana = static_cast<unsigned short>(value.toInt()); break;
|
||||
case 4: npc.mNpdt52.mFatigue = static_cast<unsigned short>(value.toInt()); break;
|
||||
case 5: npc.mNpdt52.mDisposition = static_cast<signed char>(value.toInt()); break;
|
||||
case 6: npc.mNpdt52.mReputation = static_cast<signed char>(value.toInt()); break;
|
||||
case 7: npc.mNpdt52.mRank = static_cast<signed char>(value.toInt()); break;
|
||||
case 8: npc.mNpdt52.mGold = value.toInt(); break;
|
||||
case 9: npc.mPersistent = value.toBool(); break;
|
||||
default: return; // throw an exception here?
|
||||
}
|
||||
|
||||
|
@ -1453,3 +1121,227 @@ void CSMWorld::WeaponRefIdAdapter::setData (const RefIdColumn *column, RefIdData
|
|||
EnchantableRefIdAdapter<ESM::Weapon>::setData (column, data, index, value);
|
||||
}
|
||||
}
|
||||
|
||||
void CSMWorld::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);
|
||||
}
|
||||
|
||||
void CSMWorld::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);
|
||||
}
|
||||
|
||||
void CSMWorld::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);
|
||||
}
|
||||
|
||||
QVariant CSMWorld::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<CSMWorld::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!");
|
||||
}
|
||||
}
|
||||
|
||||
int CSMWorld::NestedSpellRefIdAdapter<ESM::NPC>::getNestedColumnsCount(const RefIdColumn *column,
|
||||
const RefIdData& data) const
|
||||
{
|
||||
return 5;
|
||||
}
|
||||
|
||||
int CSMWorld::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<CSMWorld::SpellInfo> spells = mData.npcAutoCalculate(record.get())->spells();
|
||||
return static_cast<int>(spells.size());
|
||||
}
|
||||
|
||||
template <>
|
||||
void CSMWorld::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 CSMWorld::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 CSMWorld::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 CSMWorld::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 CSMWorld::NestedSpellRefIdAdapter<ESM::Creature>::getNestedColumnsCount(const RefIdColumn *column,
|
||||
const RefIdData& data) const
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
template <>
|
||||
int CSMWorld::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());
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include "refidadapter.hpp"
|
||||
#include "nestedtablewrapper.hpp"
|
||||
#include "idcollection.hpp"
|
||||
#include "data.hpp"
|
||||
|
||||
namespace CSMWorld
|
||||
{
|
||||
|
@ -806,16 +807,10 @@ namespace CSMWorld
|
|||
class NpcRefIdAdapter : public ActorRefIdAdapter<ESM::NPC>
|
||||
{
|
||||
NpcColumns mColumns;
|
||||
const IdCollection<ESM::Race>& mRaceTable;
|
||||
const IdCollection<ESM::Class>& mClassTable;
|
||||
const IdCollection<ESM::Skill>& mSkillTable;
|
||||
|
||||
public:
|
||||
|
||||
NpcRefIdAdapter (const NpcColumns& columns,
|
||||
const IdCollection<ESM::Race>& races,
|
||||
const IdCollection<ESM::Class>& classes,
|
||||
const IdCollection<ESM::Skill>& skills);
|
||||
NpcRefIdAdapter (const NpcColumns& columns);
|
||||
|
||||
virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int index)
|
||||
const;
|
||||
|
@ -860,15 +855,11 @@ namespace CSMWorld
|
|||
|
||||
class NpcAttributesRefIdAdapter : public NestedRefIdAdapterBase
|
||||
{
|
||||
const IdCollection<ESM::Race>& mRaceTable;
|
||||
const IdCollection<ESM::Class>& mClassTable;
|
||||
const IdCollection<ESM::Skill>& mSkillTable;
|
||||
const Data& mData;
|
||||
|
||||
public:
|
||||
|
||||
NpcAttributesRefIdAdapter (const IdCollection<ESM::Race>& races,
|
||||
const IdCollection<ESM::Class>& classes,
|
||||
const IdCollection<ESM::Skill>& skills);
|
||||
NpcAttributesRefIdAdapter (const Data& data);
|
||||
|
||||
virtual void addNestedRow (const RefIdColumn *column,
|
||||
RefIdData& data, int index, int position) const;
|
||||
|
@ -895,15 +886,11 @@ namespace CSMWorld
|
|||
|
||||
class NpcSkillsRefIdAdapter : public NestedRefIdAdapterBase
|
||||
{
|
||||
const IdCollection<ESM::Race>& mRaceTable;
|
||||
const IdCollection<ESM::Class>& mClassTable;
|
||||
const IdCollection<ESM::Skill>& mSkillTable;
|
||||
const Data& mData;
|
||||
|
||||
public:
|
||||
|
||||
NpcSkillsRefIdAdapter (const IdCollection<ESM::Race>& races,
|
||||
const IdCollection<ESM::Class>& classes,
|
||||
const IdCollection<ESM::Skill>& skills);
|
||||
NpcSkillsRefIdAdapter (const Data& data);
|
||||
|
||||
virtual void addNestedRow (const RefIdColumn *column,
|
||||
RefIdData& data, int index, int position) const;
|
||||
|
@ -930,18 +917,14 @@ namespace CSMWorld
|
|||
|
||||
class NpcMiscRefIdAdapter : public NestedRefIdAdapterBase
|
||||
{
|
||||
const IdCollection<ESM::Race>& mRaceTable;
|
||||
const IdCollection<ESM::Class>& mClassTable;
|
||||
const IdCollection<ESM::Skill>& mSkillTable;
|
||||
const Data& mData;
|
||||
|
||||
NpcMiscRefIdAdapter (const NpcMiscRefIdAdapter&);
|
||||
NpcMiscRefIdAdapter& operator= (const NpcMiscRefIdAdapter&);
|
||||
|
||||
public:
|
||||
|
||||
NpcMiscRefIdAdapter (const IdCollection<ESM::Race>& races,
|
||||
const IdCollection<ESM::Class>& classes,
|
||||
const IdCollection<ESM::Skill>& skills);
|
||||
NpcMiscRefIdAdapter (const Data& data);
|
||||
virtual ~NpcMiscRefIdAdapter();
|
||||
|
||||
virtual void addNestedRow (const RefIdColumn *column,
|
||||
|
@ -1189,6 +1172,7 @@ namespace CSMWorld
|
|||
class NestedSpellRefIdAdapter : public NestedRefIdAdapterBase
|
||||
{
|
||||
UniversalId::Type mType;
|
||||
const Data& mData;
|
||||
|
||||
// not implemented
|
||||
NestedSpellRefIdAdapter (const NestedSpellRefIdAdapter&);
|
||||
|
@ -1196,45 +1180,15 @@ namespace CSMWorld
|
|||
|
||||
public:
|
||||
|
||||
NestedSpellRefIdAdapter(UniversalId::Type type) :mType(type) {}
|
||||
NestedSpellRefIdAdapter(UniversalId::Type type, const Data& data) :mType(type), mData(data) {}
|
||||
|
||||
virtual ~NestedSpellRefIdAdapter() {}
|
||||
|
||||
virtual void addNestedRow (const RefIdColumn *column,
|
||||
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);
|
||||
}
|
||||
RefIdData& data, int index, int position) const;
|
||||
|
||||
virtual void removeNestedRow (const RefIdColumn *column,
|
||||
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);
|
||||
}
|
||||
RefIdData& data, int index, int rowToRemove) const;
|
||||
|
||||
virtual void setNestedTable (const RefIdColumn* column,
|
||||
RefIdData& data, int index, const NestedTableWrapperBase& nestedTable) const
|
||||
|
@ -1260,55 +1214,14 @@ namespace CSMWorld
|
|||
}
|
||||
|
||||
virtual QVariant getNestedData (const RefIdColumn *column,
|
||||
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!");
|
||||
}
|
||||
const RefIdData& data, int index, int subRowIndex, int subColIndex) const;
|
||||
|
||||
virtual void setNestedData (const RefIdColumn *column,
|
||||
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;
|
||||
RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const;
|
||||
|
||||
if (subRowIndex < 0 || subRowIndex >= static_cast<int> (list.size()))
|
||||
throw std::runtime_error ("index out of range");
|
||||
virtual int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
virtual int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const;
|
||||
};
|
||||
|
||||
template <typename ESXRecordT>
|
||||
|
|
|
@ -146,12 +146,21 @@ CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data)
|
|||
actorsColumns.mSpells = &mColumns.back();
|
||||
std::map<UniversalId::Type, NestedRefIdAdapterBase*> spellsMap;
|
||||
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,
|
||||
new NestedSpellRefIdAdapter<ESM::Creature> (UniversalId::Type_Creature)));
|
||||
new NestedSpellRefIdAdapter<ESM::Creature> (UniversalId::Type_Creature, data)));
|
||||
mNestedAdapters.push_back (std::make_pair(&mColumns.back(), spellsMap));
|
||||
mColumns.back().addColumn(
|
||||
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
|
||||
mColumns.push_back(RefIdColumn (Columns::ColumnId_NpcDestinations,
|
||||
|
@ -438,7 +447,7 @@ CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data)
|
|||
ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue));
|
||||
npcColumns.mAttributes = &mColumns.back();
|
||||
std::map<UniversalId::Type, NestedRefIdAdapterBase*> attrMap;
|
||||
attrMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcAttributesRefIdAdapter(data.getRaces(), data.getClasses(), data.getSkills())));
|
||||
attrMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcAttributesRefIdAdapter(data)));
|
||||
mNestedAdapters.push_back (std::make_pair(&mColumns.back(), attrMap));
|
||||
mColumns.back().addColumn(
|
||||
new RefIdColumn (Columns::ColumnId_NpcAttributes, CSMWorld::ColumnBase::Display_String, false, false));
|
||||
|
@ -450,7 +459,7 @@ CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data)
|
|||
ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue));
|
||||
npcColumns.mSkills = &mColumns.back();
|
||||
std::map<UniversalId::Type, NestedRefIdAdapterBase*> skillsMap;
|
||||
skillsMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcSkillsRefIdAdapter(data.getRaces(), data.getClasses(), data.getSkills())));
|
||||
skillsMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcSkillsRefIdAdapter(data)));
|
||||
mNestedAdapters.push_back (std::make_pair(&mColumns.back(), skillsMap));
|
||||
mColumns.back().addColumn(
|
||||
new RefIdColumn (Columns::ColumnId_NpcSkills, CSMWorld::ColumnBase::Display_String, false, false));
|
||||
|
@ -462,7 +471,7 @@ CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data)
|
|||
ColumnBase::Display_NestedHeader, ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_List));
|
||||
npcColumns.mMisc = &mColumns.back();
|
||||
std::map<UniversalId::Type, NestedRefIdAdapterBase*> miscMap;
|
||||
miscMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcMiscRefIdAdapter(data.getRaces(), data.getClasses(), data.getSkills())));
|
||||
miscMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcMiscRefIdAdapter(data)));
|
||||
mNestedAdapters.push_back (std::make_pair(&mColumns.back(), miscMap));
|
||||
mColumns.back().addColumn(
|
||||
new RefIdColumn (Columns::ColumnId_NpcLevel, CSMWorld::ColumnBase::Display_Integer,
|
||||
|
@ -611,7 +620,7 @@ CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data)
|
|||
mAdapters.insert (std::make_pair (UniversalId::Type_Miscellaneous,
|
||||
new MiscRefIdAdapter (inventoryColumns, key)));
|
||||
mAdapters.insert (std::make_pair (UniversalId::Type_Npc,
|
||||
new NpcRefIdAdapter (npcColumns, data.getRaces(), data.getClasses(), data.getSkills())));
|
||||
new NpcRefIdAdapter (npcColumns)));
|
||||
mAdapters.insert (std::make_pair (UniversalId::Type_Probe,
|
||||
new ToolRefIdAdapter<ESM::Probe> (UniversalId::Type_Probe, toolsColumns)));
|
||||
mAdapters.insert (std::make_pair (UniversalId::Type_Repair,
|
||||
|
|
|
@ -678,6 +678,14 @@ CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, CSM
|
|||
connect(mEditWidget, SIGNAL(tableMimeDataDropped(QWidget*, const QModelIndex&, const CSMWorld::UniversalId&, const CSMDoc::Document*)),
|
||||
this, SLOT(tableMimeDataDropped(QWidget*, const QModelIndex&, const CSMWorld::UniversalId&, const CSMDoc::Document*)));
|
||||
|
||||
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);
|
||||
mEditWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
|
||||
|
||||
|
@ -897,3 +905,27 @@ void CSVWorld::DialogueSubView::changeCurrentId (const std::string& newId)
|
|||
selection.push_back(mCurrentId);
|
||||
mCommandDispatcher.setSelection(selection);
|
||||
}
|
||||
|
||||
void CSVWorld::DialogueSubView::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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -237,6 +237,8 @@ namespace CSVWorld
|
|||
void requestFocus (const std::string& id);
|
||||
|
||||
void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end);
|
||||
|
||||
void refreshNpcDialogue (int type, const std::string& id);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -66,7 +66,7 @@ add_openmw_dir (mwworld
|
|||
cells localscripts customdata weather inventorystore ptr actionopen actionread
|
||||
actionequip timestamp actionalchemy cellstore actionapply actioneat
|
||||
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
|
||||
|
@ -78,7 +78,7 @@ add_openmw_dir (mwmechanics
|
|||
mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects
|
||||
drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor
|
||||
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
|
||||
|
|
|
@ -11,6 +11,10 @@
|
|||
#include <components/esm/loadnpc.hpp>
|
||||
#include <components/esm/npcstate.hpp>
|
||||
|
||||
#include <components/gameplay/autocalc.hpp>
|
||||
#include <components/gameplay/autocalcspell.hpp>
|
||||
#include <components/gameplay/store.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwbase/mechanicsmanager.hpp"
|
||||
|
@ -24,7 +28,6 @@
|
|||
#include "../mwmechanics/spellcasting.hpp"
|
||||
#include "../mwmechanics/disease.hpp"
|
||||
#include "../mwmechanics/combat.hpp"
|
||||
#include "../mwmechanics/autocalcspell.hpp"
|
||||
#include "../mwmechanics/difficultyscaling.hpp"
|
||||
#include "../mwmechanics/character.hpp"
|
||||
|
||||
|
@ -36,6 +39,7 @@
|
|||
#include "../mwworld/customdata.hpp"
|
||||
#include "../mwworld/physicssystem.hpp"
|
||||
#include "../mwworld/cellstore.hpp"
|
||||
#include "../mwworld/mwstore.hpp"
|
||||
|
||||
#include "../mwrender/actors.hpp"
|
||||
#include "../mwrender/renderinginterface.hpp"
|
||||
|
@ -58,116 +62,47 @@ namespace
|
|||
return new NpcCustomData (*this);
|
||||
}
|
||||
|
||||
int is_even(double d) {
|
||||
double int_part;
|
||||
modf(d / 2.0, &int_part);
|
||||
return 2.0 * int_part == d;
|
||||
}
|
||||
class Stats : public GamePlay::StatsBase
|
||||
{
|
||||
MWMechanics::CreatureStats& mCreatureStats;
|
||||
MWMechanics::NpcStats& mNpcStats;
|
||||
|
||||
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;
|
||||
}
|
||||
public:
|
||||
|
||||
Stats(MWMechanics::CreatureStats& creatureStats, MWMechanics::NpcStats& npcStats)
|
||||
: mCreatureStats(creatureStats), mNpcStats(npcStats) {}
|
||||
|
||||
virtual unsigned char getBaseAttribute(int index) const { return mCreatureStats.getAttribute(index).getBase(); }
|
||||
|
||||
virtual void setAttribute(int index, unsigned char value) { mCreatureStats.setAttribute(index, value); }
|
||||
|
||||
virtual void addSpells(std::string id) { mCreatureStats.getSpells().add(id); }
|
||||
|
||||
virtual unsigned char getBaseSkill(int index) const { return mNpcStats.getSkill(index).getBase(); }
|
||||
|
||||
virtual void setBaseSkill(int index, unsigned char value) { mNpcStats.getSkill(index).setBase(value); }
|
||||
};
|
||||
|
||||
void autoCalculateAttributes (const ESM::NPC* npc, MWMechanics::CreatureStats& creatureStats)
|
||||
{
|
||||
// race bonus
|
||||
const ESM::Race *race =
|
||||
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_ =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Class>().find(npc->mClass);
|
||||
|
||||
for (int i=0; i<2; ++i)
|
||||
{
|
||||
int attribute = class_->mData.mAttribute[i];
|
||||
if (attribute>=0 && attribute<8)
|
||||
{
|
||||
creatureStats.setAttribute(attribute,
|
||||
creatureStats.getAttribute(attribute).getBase() + 10);
|
||||
}
|
||||
}
|
||||
int level = creatureStats.getLevel();
|
||||
|
||||
// skill bonus
|
||||
for (int attribute=0; attribute < ESM::Attribute::Length; ++attribute)
|
||||
{
|
||||
float modifierSum = 0;
|
||||
MWMechanics::NpcStats dummy; // npc stats are needed for skills, which is not calculated here
|
||||
Stats stats(creatureStats, dummy);
|
||||
|
||||
for (int j=0; j<ESM::Skill::Length; ++j)
|
||||
{
|
||||
const ESM::Skill* skill = MWBase::Environment::get().getWorld()->getStore().get<ESM::Skill>().find(j);
|
||||
MWWorld::MWStore store;
|
||||
|
||||
if (skill->mData.mAttribute != attribute)
|
||||
continue;
|
||||
GamePlay::autoCalcAttributesImpl (npc, race, class_, level, stats, &store);
|
||||
|
||||
// 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;
|
||||
}
|
||||
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));
|
||||
creatureStats.setHealth(GamePlay::autoCalculateHealth(level, class_, stats));
|
||||
}
|
||||
|
||||
/**
|
||||
* @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)
|
||||
{
|
||||
const ESM::Class *class_ =
|
||||
|
@ -177,77 +112,13 @@ namespace
|
|||
|
||||
const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(npc->mRace);
|
||||
|
||||
Stats stats(npcStats, npcStats);
|
||||
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
int bonus = (i==0) ? 10 : 25;
|
||||
MWWorld::MWStore store;
|
||||
|
||||
for (int i2 = 0; i2 < 5; ++i2)
|
||||
{
|
||||
int index = class_->mData.mSkills[i2][i];
|
||||
if (index >= 0 && index < ESM::Skill::Length)
|
||||
{
|
||||
npcStats.getSkill(index).setBase (npcStats.getSkill(index).getBase() + bonus);
|
||||
}
|
||||
}
|
||||
}
|
||||
GamePlay::autoCalcSkillsImpl(npc, race, class_, level, stats, &store);
|
||||
|
||||
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 = 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);
|
||||
GamePlay::autoCalculateSpells(race, stats, &store);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -392,7 +263,7 @@ namespace MWClass
|
|||
// store
|
||||
ptr.getRefData().setCustomData (data.release());
|
||||
|
||||
getInventoryStore(ptr).autoEquip(ptr);
|
||||
getInventoryStore(ptr).autoEquip(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
#include <MyGUI_ImageBox.h>
|
||||
#include <MyGUI_EditBox.h>
|
||||
|
||||
#include <components/gameplay/store.hpp>
|
||||
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
|
@ -140,7 +142,7 @@ namespace MWGui
|
|||
if(world->getStore().get<ESM::Class>().isDynamic(cls->mId))
|
||||
{
|
||||
// Choosing Stealth specialization and Speed/Agility as attributes, if possible. Otherwise fall back to first class found.
|
||||
MWWorld::SharedIterator<ESM::Class> it = world->getStore().get<ESM::Class>().begin();
|
||||
GamePlay::SharedIterator<ESM::Class> it = world->getStore().get<ESM::Class>().begin();
|
||||
for(; it != world->getStore().get<ESM::Class>().end(); ++it)
|
||||
{
|
||||
if(it->mData.mIsPlayable && it->mData.mSpecialization == 2 && it->mData.mAttribute[0] == 4 && it->mData.mAttribute[1] == 3)
|
||||
|
|
|
@ -6,8 +6,11 @@
|
|||
|
||||
#include <components/esm/stolenitems.hpp>
|
||||
|
||||
#include <components/gameplay/autocalcspell.hpp>
|
||||
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
#include "../mwworld/inventorystore.hpp"
|
||||
#include "../mwworld/mwstore.hpp"
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
|
@ -23,7 +26,6 @@
|
|||
#include <OgreSceneNode.h>
|
||||
|
||||
#include "spellcasting.hpp"
|
||||
#include "autocalcspell.hpp"
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
|
@ -252,10 +254,12 @@ namespace MWMechanics
|
|||
continue;
|
||||
|
||||
static const float fAutoPCSpellChance = esmStore.get<ESM::GameSetting>().find("fAutoPCSpellChance")->getFloat();
|
||||
if (calcAutoCastChance(spell, skills, attributes, -1) < fAutoPCSpellChance)
|
||||
MWWorld::MWStore store;
|
||||
|
||||
if (GamePlay::calcAutoCastChance(spell, skills, attributes, -1, &store) < fAutoPCSpellChance)
|
||||
continue;
|
||||
|
||||
if (!attrSkillCheck(spell, skills, attributes))
|
||||
if (!GamePlay::attrSkillCheck(spell, skills, attributes, &store))
|
||||
continue;
|
||||
|
||||
selectedSpells.push_back(spell->mId);
|
||||
|
|
|
@ -66,7 +66,7 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener)
|
|||
esm.getRecHeader();
|
||||
|
||||
// Look up the record type.
|
||||
std::map<int, StoreBase *>::iterator it = mStores.find(n.val);
|
||||
std::map<int, GamePlay::StoreBase *>::iterator it = mStores.find(n.val);
|
||||
|
||||
if (it == mStores.end()) {
|
||||
if (n.val == ESM::REC_INFO) {
|
||||
|
@ -130,7 +130,7 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener)
|
|||
|
||||
void ESMStore::setUp()
|
||||
{
|
||||
std::map<int, StoreBase *>::iterator it = mStores.begin();
|
||||
std::map<int, GamePlay::StoreBase *>::iterator it = mStores.begin();
|
||||
for (; it != mStores.end(); ++it) {
|
||||
it->second->setUp();
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ namespace MWWorld
|
|||
// Lookup of all IDs. Makes looking up references faster. Just
|
||||
// maps the id name to the record type.
|
||||
std::map<std::string, int> mIds;
|
||||
std::map<int, StoreBase *> mStores;
|
||||
std::map<int, GamePlay::StoreBase *> mStores;
|
||||
|
||||
ESM::NPC mPlayerTemplate;
|
||||
|
||||
|
@ -75,7 +75,7 @@ namespace MWWorld
|
|||
|
||||
public:
|
||||
/// \todo replace with SharedIterator<StoreBase>
|
||||
typedef std::map<int, StoreBase *>::const_iterator iterator;
|
||||
typedef std::map<int, GamePlay::StoreBase *>::const_iterator iterator;
|
||||
|
||||
iterator begin() const {
|
||||
return mStores.begin();
|
||||
|
@ -144,7 +144,7 @@ namespace MWWorld
|
|||
|
||||
void clearDynamic ()
|
||||
{
|
||||
for (std::map<int, StoreBase *>::iterator it = mStores.begin(); it != mStores.end(); ++it)
|
||||
for (std::map<int, GamePlay::StoreBase *>::iterator it = mStores.begin(); it != mStores.end(); ++it)
|
||||
it->second->clearDynamic();
|
||||
|
||||
mNpcs.insert(mPlayerTemplate);
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <openengine/misc/rng.hpp>
|
||||
|
||||
#include <components/esm/esmwriter.hpp>
|
||||
#include <components/gameplay/store.hpp>
|
||||
|
||||
#include <components/loadinglistener/loadinglistener.hpp>
|
||||
|
||||
|
@ -17,89 +18,10 @@
|
|||
|
||||
namespace MWWorld
|
||||
{
|
||||
struct StoreBase
|
||||
{
|
||||
virtual ~StoreBase() {}
|
||||
|
||||
virtual void setUp() {}
|
||||
virtual void listIdentifier(std::vector<std::string> &list) const {}
|
||||
|
||||
virtual size_t getSize() const = 0;
|
||||
virtual int getDynamicSize() const { return 0; }
|
||||
virtual void load(ESM::ESMReader &esm, const std::string &id) = 0;
|
||||
|
||||
virtual bool eraseStatic(const std::string &id) {return false;}
|
||||
virtual void clearDynamic() {}
|
||||
|
||||
virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const {}
|
||||
|
||||
virtual void read (ESM::ESMReader& reader, const std::string& id) {}
|
||||
///< Read into dynamic storage
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class SharedIterator
|
||||
{
|
||||
typedef typename std::vector<T *>::const_iterator Iter;
|
||||
|
||||
Iter mIter;
|
||||
|
||||
public:
|
||||
SharedIterator() {}
|
||||
|
||||
SharedIterator(const SharedIterator &orig)
|
||||
: mIter(orig.mIter)
|
||||
{}
|
||||
|
||||
SharedIterator(const Iter &iter)
|
||||
: mIter(iter)
|
||||
{}
|
||||
|
||||
SharedIterator &operator++() {
|
||||
++mIter;
|
||||
return *this;
|
||||
}
|
||||
|
||||
SharedIterator operator++(int) {
|
||||
SharedIterator iter = *this;
|
||||
++mIter;
|
||||
|
||||
return iter;
|
||||
}
|
||||
|
||||
SharedIterator &operator--() {
|
||||
--mIter;
|
||||
return *this;
|
||||
}
|
||||
|
||||
SharedIterator operator--(int) {
|
||||
SharedIterator iter = *this;
|
||||
--mIter;
|
||||
|
||||
return iter;
|
||||
}
|
||||
|
||||
bool operator==(const SharedIterator &x) const {
|
||||
return mIter == x.mIter;
|
||||
}
|
||||
|
||||
bool operator!=(const SharedIterator &x) const {
|
||||
return !(*this == x);
|
||||
}
|
||||
|
||||
const T &operator*() const {
|
||||
return **mIter;
|
||||
}
|
||||
|
||||
const T *operator->() const {
|
||||
return &(**mIter);
|
||||
}
|
||||
};
|
||||
|
||||
class ESMStore;
|
||||
|
||||
template <class T>
|
||||
class Store : public StoreBase
|
||||
class Store : public GamePlay::CommonStore<T>
|
||||
{
|
||||
std::map<std::string, T> mStatic;
|
||||
std::vector<T *> mShared; // Preserves the record order as it came from the content files (this
|
||||
|
@ -137,7 +59,7 @@ namespace MWWorld
|
|||
: mStatic(orig.mData)
|
||||
{}
|
||||
|
||||
typedef SharedIterator<T> iterator;
|
||||
typedef GamePlay::SharedIterator<T> iterator;
|
||||
|
||||
// setUp needs to be called again after
|
||||
virtual void clearDynamic()
|
||||
|
@ -380,7 +302,7 @@ namespace MWWorld
|
|||
}
|
||||
|
||||
template <>
|
||||
class Store<ESM::LandTexture> : public StoreBase
|
||||
class Store<ESM::LandTexture> : public GamePlay::StoreBase
|
||||
{
|
||||
// For multiple ESM/ESP files we need one list per file.
|
||||
typedef std::vector<ESM::LandTexture> LandTextureList;
|
||||
|
@ -457,7 +379,7 @@ namespace MWWorld
|
|||
};
|
||||
|
||||
template <>
|
||||
class Store<ESM::Land> : public StoreBase
|
||||
class Store<ESM::Land> : public GamePlay::StoreBase
|
||||
{
|
||||
std::vector<ESM::Land *> mStatic;
|
||||
|
||||
|
@ -472,7 +394,7 @@ namespace MWWorld
|
|||
};
|
||||
|
||||
public:
|
||||
typedef SharedIterator<ESM::Land> iterator;
|
||||
typedef GamePlay::SharedIterator<ESM::Land> iterator;
|
||||
|
||||
virtual ~Store<ESM::Land>()
|
||||
{
|
||||
|
@ -546,7 +468,7 @@ namespace MWWorld
|
|||
};
|
||||
|
||||
template <>
|
||||
class Store<ESM::Cell> : public StoreBase
|
||||
class Store<ESM::Cell> : public GamePlay::StoreBase
|
||||
{
|
||||
struct DynamicExtCmp
|
||||
{
|
||||
|
@ -586,7 +508,7 @@ namespace MWWorld
|
|||
void handleMovedCellRefs(ESM::ESMReader& esm, ESM::Cell* cell);
|
||||
|
||||
public:
|
||||
typedef SharedIterator<ESM::Cell> iterator;
|
||||
typedef GamePlay::SharedIterator<ESM::Cell> iterator;
|
||||
|
||||
const ESM::Cell *search(const std::string &id) const {
|
||||
ESM::Cell cell;
|
||||
|
@ -834,7 +756,7 @@ namespace MWWorld
|
|||
};
|
||||
|
||||
template <>
|
||||
class Store<ESM::Pathgrid> : public StoreBase
|
||||
class Store<ESM::Pathgrid> : public GamePlay::StoreBase
|
||||
{
|
||||
private:
|
||||
typedef std::map<std::string, ESM::Pathgrid> Interior;
|
||||
|
|
|
@ -6,16 +6,16 @@ set (VERSION_HPP ${CMAKE_CURRENT_SOURCE_DIR}/version/version.hpp)
|
|||
if (GIT_CHECKOUT)
|
||||
add_custom_target (git-version
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DGIT_EXECUTABLE=${GIT_EXECUTABLE}
|
||||
-DGIT_EXECUTABLE=${GIT_EXECUTABLE}
|
||||
-DPROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR}
|
||||
-DVERSION_HPP_IN=${VERSION_HPP_IN}
|
||||
-DVERSION_HPP=${VERSION_HPP}
|
||||
-DOPENMW_VERSION_MAJOR=${OPENMW_VERSION_MAJOR}
|
||||
-DOPENMW_VERSION_MINOR=${OPENMW_VERSION_MINOR}
|
||||
-DOPENMW_VERSION_RELEASE=${OPENMW_VERSION_RELEASE}
|
||||
-DOPENMW_VERSION=${OPENMW_VERSION}
|
||||
-DVERSION_HPP_IN=${VERSION_HPP_IN}
|
||||
-DVERSION_HPP=${VERSION_HPP}
|
||||
-DOPENMW_VERSION_MAJOR=${OPENMW_VERSION_MAJOR}
|
||||
-DOPENMW_VERSION_MINOR=${OPENMW_VERSION_MINOR}
|
||||
-DOPENMW_VERSION_RELEASE=${OPENMW_VERSION_RELEASE}
|
||||
-DOPENMW_VERSION=${OPENMW_VERSION}
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/GitVersion.cmake
|
||||
VERBATIM)
|
||||
VERBATIM)
|
||||
else (GIT_CHECKOUT)
|
||||
configure_file(${VERSION_HPP_IN} ${VERSION_HPP})
|
||||
endif (GIT_CHECKOUT)
|
||||
|
@ -119,6 +119,10 @@ add_component_dir (fontloader
|
|||
fontloader
|
||||
)
|
||||
|
||||
add_component_dir (gameplay
|
||||
autocalc autocalcspell
|
||||
)
|
||||
|
||||
add_component_dir (version
|
||||
version
|
||||
)
|
||||
|
@ -161,14 +165,14 @@ include_directories(${BULLET_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR})
|
|||
|
||||
add_library(components STATIC ${COMPONENT_FILES} ${MOC_SRCS} ${ESM_UI_HDR})
|
||||
|
||||
target_link_libraries(components
|
||||
target_link_libraries(components
|
||||
${Boost_SYSTEM_LIBRARY}
|
||||
${Boost_FILESYSTEM_LIBRARY}
|
||||
${Boost_THREAD_LIBRARY}
|
||||
${Boost_PROGRAM_OPTIONS_LIBRARY}
|
||||
${Boost_WAVE_LIBRARY}
|
||||
${OGRE_LIBRARIES}
|
||||
${OENGINE_LIBRARY}
|
||||
${OENGINE_LIBRARY}
|
||||
${BULLET_LIBRARIES}
|
||||
)
|
||||
|
||||
|
|
207
components/gameplay/autocalc.cpp
Normal file
207
components/gameplay/autocalc.cpp
Normal file
|
@ -0,0 +1,207 @@
|
|||
#include "autocalc.hpp"
|
||||
|
||||
#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"
|
||||
|
||||
// Most of the code in this file was moved from apps/openmw/mwclass/npc.cpp
|
||||
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 GamePlay
|
||||
{
|
||||
void autoCalcAttributesImpl (const ESM::NPC* npc,
|
||||
const ESM::Race *race, const ESM::Class *class_, int level, StatsBase& stats, StoreWrap *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, StoreWrap *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, stats.getBaseSkill(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_, 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, StoreWrap *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.addSpells(*it);
|
||||
}
|
||||
|
||||
StatsBase::StatsBase() {}
|
||||
|
||||
StatsBase::~StatsBase() {}
|
||||
}
|
47
components/gameplay/autocalc.hpp
Normal file
47
components/gameplay/autocalc.hpp
Normal file
|
@ -0,0 +1,47 @@
|
|||
#ifndef COMPONENTS_GAMEPLAY_AUTOCALC_H
|
||||
#define COMPONENTS_GAMEPLAY_AUTOCALC_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "store.hpp"
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
struct NPC;
|
||||
struct Race;
|
||||
struct Class;
|
||||
}
|
||||
|
||||
namespace GamePlay
|
||||
{
|
||||
// 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 addSpells(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, StoreWrap *store);
|
||||
|
||||
void autoCalcSkillsImpl (const ESM::NPC* npc,
|
||||
const ESM::Race *race, const ESM::Class *class_, int level, StatsBase& stats, StoreWrap *store);
|
||||
|
||||
unsigned short autoCalculateHealth(int level, const ESM::Class *class_, StatsBase& stats);
|
||||
|
||||
void autoCalculateSpells(const ESM::Race *race, StatsBase& stats, StoreWrap *store);
|
||||
}
|
||||
#endif // COMPONENTS_GAMEPLAY_AUTOCALC_H
|
240
components/gameplay/autocalcspell.cpp
Normal file
240
components/gameplay/autocalcspell.cpp
Normal file
|
@ -0,0 +1,240 @@
|
|||
#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"
|
||||
|
||||
// Most of the code in this file was moved from apps/openmw/mwmechanics/autocalcspell.cpp
|
||||
namespace GamePlay
|
||||
{
|
||||
|
||||
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, StoreWrap *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;
|
||||
|
||||
const CommonStore<ESM::Spell> &spells = store->getSpells();
|
||||
|
||||
// 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 (CommonStore<ESM::Spell>::iterator iter = spells.begin(); iter != spells.end(); ++iter)
|
||||
{
|
||||
const 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)
|
||||
{
|
||||
const ESM::Spell* testSpell = spells.find(*weakIt);
|
||||
|
||||
//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, StoreWrap *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, StoreWrap *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, StoreWrap *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;
|
||||
}
|
||||
}
|
38
components/gameplay/autocalcspell.hpp
Normal file
38
components/gameplay/autocalcspell.hpp
Normal file
|
@ -0,0 +1,38 @@
|
|||
#ifndef COMPONENTS_GAMEPLAY_AUTOCALCSPELL_H
|
||||
#define COMPONENTS_GAMEPLAY_AUTOCALCSPELL_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <components/esm/loadskil.hpp>
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
struct Spell;
|
||||
struct Race;
|
||||
}
|
||||
|
||||
namespace GamePlay
|
||||
{
|
||||
|
||||
class StoreWrap;
|
||||
|
||||
/// 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, StoreWrap *store);
|
||||
|
||||
// Helpers
|
||||
|
||||
bool attrSkillCheck (const ESM::Spell* spell, const int* actorSkills, const int* actorAttributes, StoreWrap *store);
|
||||
|
||||
ESM::Skill::SkillEnum mapSchoolToSkill(int school);
|
||||
|
||||
void calcWeakestSchool(const ESM::Spell* spell,
|
||||
const int* actorSkills, int& effectiveSchool, float& skillTerm, StoreWrap *store);
|
||||
|
||||
float calcAutoCastChance(const ESM::Spell* spell,
|
||||
const int* actorSkills, const int* actorAttributes, int effectiveSchool, StoreWrap *store);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
138
components/gameplay/store.hpp
Normal file
138
components/gameplay/store.hpp
Normal file
|
@ -0,0 +1,138 @@
|
|||
#ifndef COMPONENTS_GAMEPLAY_STORE_H
|
||||
#define COMPONENTS_GAMEPLAY_STORE_H
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace Loading
|
||||
{
|
||||
class Listener;
|
||||
}
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
class ESMWriter;
|
||||
class ESMReader;
|
||||
struct Spell;
|
||||
struct Skill;
|
||||
struct MagicEffect;
|
||||
}
|
||||
|
||||
namespace GamePlay
|
||||
{
|
||||
// moved from apps/openmw/mwworld/store.hpp
|
||||
struct StoreBase
|
||||
{
|
||||
virtual ~StoreBase() {}
|
||||
|
||||
virtual void setUp() {}
|
||||
virtual void listIdentifier(std::vector<std::string> &list) const {}
|
||||
|
||||
virtual size_t getSize() const = 0;
|
||||
virtual int getDynamicSize() const { return 0; }
|
||||
virtual void load(ESM::ESMReader &esm, const std::string &id) = 0;
|
||||
|
||||
virtual bool eraseStatic(const std::string &id) {return false;}
|
||||
virtual void clearDynamic() {}
|
||||
|
||||
virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const {}
|
||||
|
||||
virtual void read (ESM::ESMReader& reader, const std::string& id) {}
|
||||
///< Read into dynamic storage
|
||||
};
|
||||
|
||||
// moved from apps/openmw/mwworld/store.hpp
|
||||
template <class T>
|
||||
class SharedIterator
|
||||
{
|
||||
typedef typename std::vector<T *>::const_iterator Iter;
|
||||
|
||||
Iter mIter;
|
||||
|
||||
public:
|
||||
SharedIterator() {}
|
||||
|
||||
SharedIterator(const SharedIterator &orig)
|
||||
: mIter(orig.mIter)
|
||||
{}
|
||||
|
||||
SharedIterator(const Iter &iter)
|
||||
: mIter(iter)
|
||||
{}
|
||||
|
||||
SharedIterator &operator++() {
|
||||
++mIter;
|
||||
return *this;
|
||||
}
|
||||
|
||||
SharedIterator operator++(int) {
|
||||
SharedIterator iter = *this;
|
||||
++mIter;
|
||||
|
||||
return iter;
|
||||
}
|
||||
|
||||
SharedIterator &operator--() {
|
||||
--mIter;
|
||||
return *this;
|
||||
}
|
||||
|
||||
SharedIterator operator--(int) {
|
||||
SharedIterator iter = *this;
|
||||
--mIter;
|
||||
|
||||
return iter;
|
||||
}
|
||||
|
||||
bool operator==(const SharedIterator &x) const {
|
||||
return mIter == x.mIter;
|
||||
}
|
||||
|
||||
bool operator!=(const SharedIterator &x) const {
|
||||
return !(*this == x);
|
||||
}
|
||||
|
||||
const T &operator*() const {
|
||||
return **mIter;
|
||||
}
|
||||
|
||||
const T *operator->() const {
|
||||
return &(**mIter);
|
||||
}
|
||||
};
|
||||
|
||||
// interface class for sharing the autocalc component between OpenMW and OpenCS
|
||||
template <class T>
|
||||
class CommonStore : public StoreBase
|
||||
{
|
||||
|
||||
public:
|
||||
typedef SharedIterator<T> iterator;
|
||||
|
||||
virtual iterator begin() const = 0;
|
||||
|
||||
virtual iterator end() const = 0;
|
||||
|
||||
virtual const T *find(const std::string &id) const = 0;
|
||||
};
|
||||
|
||||
// interface class for sharing the autocalc component between OpenMW and OpenCS
|
||||
class StoreWrap
|
||||
{
|
||||
|
||||
public:
|
||||
StoreWrap() {}
|
||||
virtual ~StoreWrap() {}
|
||||
|
||||
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 const CommonStore<ESM::Spell>& getSpells() const = 0;
|
||||
};
|
||||
}
|
||||
#endif // COMPONENTS_GAMEPLAY_STORE_H
|
Loading…
Reference in a new issue