1
0
Fork 1
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:
cc9cii 2015-06-24 21:05:59 +10:00
parent ccf840da2b
commit 6b00d4ad91
25 changed files with 1631 additions and 760 deletions

View file

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

View file

@ -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" },

View file

@ -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,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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() {}
}

View 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

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

View 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

View 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