mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-16 13:49:55 +00:00
commit
448a13b6b2
31 changed files with 1935 additions and 357 deletions
|
@ -26,7 +26,7 @@ opencs_units_noqt (model/world
|
|||
universalid record commands columnbase scriptcontext cell refidcollection
|
||||
refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection tablemimedata cellcoordinates cellselection resources resourcesmanager scope
|
||||
pathgrid landtexture land nestedtablewrapper nestedcollection nestedcoladapterimp nestedinfocollection
|
||||
idcompletionmanager metadata
|
||||
idcompletionmanager npcstats metadata
|
||||
)
|
||||
|
||||
opencs_hdrs_noqt (model/world
|
||||
|
|
|
@ -315,6 +315,10 @@ namespace CSMWorld
|
|||
{ ColumnId_FileDescription, "File Description" },
|
||||
{ ColumnId_Author, "Author" },
|
||||
|
||||
{ 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" },
|
||||
|
|
|
@ -306,6 +306,10 @@ namespace CSMWorld
|
|||
ColumnId_FileDescription = 276,
|
||||
ColumnId_Author = 277,
|
||||
|
||||
ColumnId_SpellSrc = 278,
|
||||
ColumnId_SpellCost = 279,
|
||||
ColumnId_SpellChance = 280,
|
||||
|
||||
// Allocated to a separate value range, so we don't get a collision should we ever need
|
||||
// to extend the number of use values.
|
||||
ColumnId_UseValue1 = 0x10000,
|
||||
|
|
|
@ -11,6 +11,10 @@
|
|||
#include <components/esm/loadglob.hpp>
|
||||
#include <components/esm/cellref.hpp>
|
||||
|
||||
#include <components/autocalc/autocalc.hpp>
|
||||
#include <components/autocalc/autocalcspell.hpp>
|
||||
#include <components/autocalc/store.hpp>
|
||||
|
||||
#include "idtable.hpp"
|
||||
#include "idtree.hpp"
|
||||
#include "columnimp.hpp"
|
||||
|
@ -19,6 +23,71 @@
|
|||
#include "resourcesmanager.hpp"
|
||||
#include "resourcetable.hpp"
|
||||
#include "nestedcoladapterimp.hpp"
|
||||
#include "npcstats.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
class CSStore : public AutoCalc::StoreCommon
|
||||
{
|
||||
const CSMWorld::IdCollection<ESM::GameSetting>& mGmstTable;
|
||||
const CSMWorld::IdCollection<ESM::Skill>& mSkillTable;
|
||||
const CSMWorld::IdCollection<ESM::MagicEffect>& mMagicEffectTable;
|
||||
const CSMWorld::NestedIdCollection<ESM::Spell>& mSpells;
|
||||
|
||||
public:
|
||||
|
||||
CSStore(const CSMWorld::IdCollection<ESM::GameSetting>& gmst,
|
||||
const CSMWorld::IdCollection<ESM::Skill>& skills,
|
||||
const CSMWorld::IdCollection<ESM::MagicEffect>& magicEffects,
|
||||
const CSMWorld::NestedIdCollection<ESM::Spell>& spells)
|
||||
: mGmstTable(gmst), mSkillTable(skills), mMagicEffectTable(magicEffects), mSpells(spells)
|
||||
{ }
|
||||
|
||||
~CSStore() {}
|
||||
|
||||
virtual int findGmstInt(const std::string& name) const
|
||||
{
|
||||
return mGmstTable.getRecord(name).get().getInt();
|
||||
}
|
||||
|
||||
virtual float findGmstFloat(const std::string& name) const
|
||||
{
|
||||
return mGmstTable.getRecord(name).get().getFloat();
|
||||
}
|
||||
|
||||
virtual const ESM::Skill *findSkill(int index) const
|
||||
{
|
||||
// if the skill does not exist, throws std::runtime_error ("invalid ID: " + id)
|
||||
return &mSkillTable.getRecord(ESM::Skill::indexToId(index)).get();
|
||||
}
|
||||
|
||||
virtual const ESM::MagicEffect* findMagicEffect(int id) const
|
||||
{
|
||||
// if the magic effect does not exist, throws std::runtime_error ("invalid ID: " + id)
|
||||
return &mMagicEffectTable.getRecord(ESM::MagicEffect::indexToId((short)id)).get();
|
||||
}
|
||||
|
||||
virtual void getSpells(std::vector<ESM::Spell*>& spells)
|
||||
{
|
||||
// prepare data in a format used by OpenMW store
|
||||
for (int index = 0; index < mSpells.getSize(); ++index)
|
||||
spells.push_back(const_cast<ESM::Spell *>(&mSpells.getRecord(index).get()));
|
||||
}
|
||||
};
|
||||
|
||||
unsigned short autoCalculateMana(AutoCalc::StatsBase& stats)
|
||||
{
|
||||
return stats.getBaseAttribute(ESM::Attribute::Intelligence) * 2;
|
||||
}
|
||||
|
||||
unsigned short autoCalculateFatigue(AutoCalc::StatsBase& stats)
|
||||
{
|
||||
return stats.getBaseAttribute(ESM::Attribute::Strength)
|
||||
+ stats.getBaseAttribute(ESM::Attribute::Willpower)
|
||||
+ stats.getBaseAttribute(ESM::Attribute::Agility)
|
||||
+ stats.getBaseAttribute(ESM::Attribute::Endurance);
|
||||
}
|
||||
}
|
||||
|
||||
void CSMWorld::Data::addModel (QAbstractItemModel *model, UniversalId::Type type, bool update)
|
||||
{
|
||||
|
@ -61,7 +130,7 @@ int CSMWorld::Data::count (RecordBase::State state, const CollectionBase& collec
|
|||
}
|
||||
|
||||
CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager)
|
||||
: mEncoder (encoding), mPathgrids (mCells), mRefs (mCells),
|
||||
: mEncoder (encoding), mPathgrids (mCells), mReferenceables(self()), mRefs (mCells),
|
||||
mResourcesManager (resourcesManager), mReader (0), mDialogue (0), mReaderIndex(0)
|
||||
{
|
||||
int index = 0;
|
||||
|
@ -202,7 +271,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc
|
|||
mSpells.addColumn (new RecordStateColumn<ESM::Spell>);
|
||||
mSpells.addColumn (new 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));
|
||||
|
@ -525,11 +594,40 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc
|
|||
UniversalId::Type_Video);
|
||||
addModel (new IdTable (&mMetaData), UniversalId::Type_MetaData);
|
||||
|
||||
// for autocalc updates when gmst/race/class/skils tables change
|
||||
CSMWorld::IdTable *gmsts =
|
||||
static_cast<CSMWorld::IdTable*>(getTableModel(UniversalId::Type_Gmst));
|
||||
CSMWorld::IdTable *skills =
|
||||
static_cast<CSMWorld::IdTable*>(getTableModel(UniversalId::Type_Skill));
|
||||
CSMWorld::IdTable *classes =
|
||||
static_cast<CSMWorld::IdTable*>(getTableModel(UniversalId::Type_Class));
|
||||
CSMWorld::IdTree *races =
|
||||
static_cast<CSMWorld::IdTree*>(getTableModel(UniversalId::Type_Race));
|
||||
CSMWorld::IdTree *objects =
|
||||
static_cast<CSMWorld::IdTree*>(getTableModel(UniversalId::Type_Referenceable));
|
||||
|
||||
connect (gmsts, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)),
|
||||
this, SLOT (gmstDataChanged (const QModelIndex&, const QModelIndex&)));
|
||||
connect (skills, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)),
|
||||
this, SLOT (skillDataChanged (const QModelIndex&, const QModelIndex&)));
|
||||
connect (classes, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)),
|
||||
this, SLOT (classDataChanged (const QModelIndex&, const QModelIndex&)));
|
||||
connect (races, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)),
|
||||
this, SLOT (raceDataChanged (const QModelIndex&, const QModelIndex&)));
|
||||
connect (objects, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)),
|
||||
this, SLOT (npcDataChanged (const QModelIndex&, const QModelIndex&)));
|
||||
connect (this, SIGNAL (updateNpcAutocalc (int, const std::string&)),
|
||||
objects, SLOT (updateNpcAutocalc (int, const std::string&)));
|
||||
connect (this, SIGNAL (cacheNpcStats (const std::string&, NpcStats*)),
|
||||
this, SLOT (cacheNpcStatsEvent (const std::string&, NpcStats*)));
|
||||
|
||||
mRefLoadCache.clear(); // clear here rather than startLoading() and continueLoading() for multiple content files
|
||||
}
|
||||
|
||||
CSMWorld::Data::~Data()
|
||||
{
|
||||
clearNpcStatsCache();
|
||||
|
||||
for (std::vector<QAbstractItemModel *>::iterator iter (mModels.begin()); iter!=mModels.end(); ++iter)
|
||||
delete *iter;
|
||||
|
||||
|
@ -1161,3 +1259,270 @@ void CSMWorld::Data::rowsChanged (const QModelIndex& parent, int start, int end)
|
|||
{
|
||||
emit idListChanged();
|
||||
}
|
||||
|
||||
const CSMWorld::Data& CSMWorld::Data::self ()
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
void CSMWorld::Data::skillDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight)
|
||||
{
|
||||
// mData.mAttribute (affects attributes skill bonus autocalc)
|
||||
// mData.mSpecialization (affects skills autocalc)
|
||||
CSMWorld::IdTable *skillModel =
|
||||
static_cast<CSMWorld::IdTable*>(getTableModel(CSMWorld::UniversalId::Type_Skill));
|
||||
|
||||
int attributeColumn = skillModel->findColumnIndex(CSMWorld::Columns::ColumnId_Attribute);
|
||||
int specialisationColumn = skillModel->findColumnIndex(CSMWorld::Columns::ColumnId_Specialisation);
|
||||
|
||||
if ((topLeft.column() <= attributeColumn && attributeColumn <= bottomRight.column())
|
||||
|| (topLeft.column() <= specialisationColumn && specialisationColumn <= bottomRight.column()))
|
||||
{
|
||||
clearNpcStatsCache();
|
||||
|
||||
std::string empty;
|
||||
emit updateNpcAutocalc(0/*all*/, empty);
|
||||
}
|
||||
}
|
||||
|
||||
void CSMWorld::Data::classDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight)
|
||||
{
|
||||
// update autocalculated attributes/skills of every NPC with matching class
|
||||
// - mData.mAttribute[2]
|
||||
// - mData.mSkills[5][2]
|
||||
// - mData.mSpecialization
|
||||
CSMWorld::IdTable *classModel =
|
||||
static_cast<CSMWorld::IdTable*>(getTableModel(CSMWorld::UniversalId::Type_Class));
|
||||
|
||||
int attribute1Column = classModel->findColumnIndex(CSMWorld::Columns::ColumnId_Attribute1); // +1
|
||||
int majorSkill1Column = classModel->findColumnIndex(CSMWorld::Columns::ColumnId_MajorSkill1); // +4
|
||||
int minorSkill1Column = classModel->findColumnIndex(CSMWorld::Columns::ColumnId_MinorSkill1); // +4
|
||||
int specialisationColumn = classModel->findColumnIndex(CSMWorld::Columns::ColumnId_Specialisation);
|
||||
|
||||
if ((topLeft.column() > attribute1Column+1 || attribute1Column > bottomRight.column())
|
||||
&& (topLeft.column() > majorSkill1Column+4 || majorSkill1Column > bottomRight.column())
|
||||
&& (topLeft.column() > minorSkill1Column+4 || minorSkill1Column > bottomRight.column())
|
||||
&& (topLeft.column() > specialisationColumn || specialisationColumn > bottomRight.column()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// get the affected class
|
||||
int idColumn = classModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id);
|
||||
for (int classRow = topLeft.row(); classRow <= bottomRight.row(); ++classRow)
|
||||
{
|
||||
clearNpcStatsCache();
|
||||
|
||||
std::string classId =
|
||||
classModel->data(classModel->index(classRow, idColumn)).toString().toUtf8().constData();
|
||||
emit updateNpcAutocalc(1/*class*/, classId);
|
||||
}
|
||||
}
|
||||
|
||||
void CSMWorld::Data::raceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight)
|
||||
{
|
||||
// affects racial bonus attributes & skills
|
||||
// - mData.mAttributeValues[]
|
||||
// - mData.mBonus[].mBonus
|
||||
// - mPowers.mList[]
|
||||
CSMWorld::IdTree *raceModel =
|
||||
static_cast<CSMWorld::IdTree*>(getTableModel(CSMWorld::UniversalId::Type_Race));
|
||||
|
||||
int attrColumn = raceModel->findColumnIndex(CSMWorld::Columns::ColumnId_RaceAttributes);
|
||||
int bonusColumn = raceModel->findColumnIndex(CSMWorld::Columns::ColumnId_RaceSkillBonus);
|
||||
int powersColumn = raceModel->findColumnIndex(CSMWorld::Columns::ColumnId_PowerList);
|
||||
|
||||
bool match = false;
|
||||
int raceRow = topLeft.row();
|
||||
int raceEnd = bottomRight.row();
|
||||
if (topLeft.parent().isValid() && bottomRight.parent().isValid())
|
||||
{
|
||||
if ((topLeft.parent().column() <= attrColumn && attrColumn <= bottomRight.parent().column())
|
||||
|| (topLeft.parent().column() <= bonusColumn && bonusColumn <= bottomRight.parent().column())
|
||||
|| (topLeft.parent().column() <= powersColumn && powersColumn <= bottomRight.parent().column()))
|
||||
{
|
||||
match = true; // TODO: check for specific nested column?
|
||||
raceRow = topLeft.parent().row();
|
||||
raceEnd = bottomRight.parent().row();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((topLeft.column() <= attrColumn && attrColumn <= bottomRight.column())
|
||||
|| (topLeft.column() <= bonusColumn && bonusColumn <= bottomRight.column())
|
||||
|| (topLeft.column() <= powersColumn && powersColumn <= bottomRight.column()))
|
||||
{
|
||||
match = true; // maybe the whole table changed
|
||||
}
|
||||
}
|
||||
|
||||
if (!match)
|
||||
return;
|
||||
|
||||
// update autocalculated attributes/skills of every NPC with matching race
|
||||
int idColumn = raceModel->findColumnIndex(CSMWorld::Columns::ColumnId_Id);
|
||||
for (; raceRow <= raceEnd; ++raceRow)
|
||||
{
|
||||
clearNpcStatsCache();
|
||||
|
||||
std::string raceId =
|
||||
raceModel->data(raceModel->index(raceRow, idColumn)).toString().toUtf8().constData();
|
||||
emit updateNpcAutocalc(2/*race*/, raceId);
|
||||
}
|
||||
}
|
||||
|
||||
void CSMWorld::Data::npcDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight)
|
||||
{
|
||||
// TODO: for now always recalculate
|
||||
clearNpcStatsCache();
|
||||
}
|
||||
|
||||
void CSMWorld::Data::gmstDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight)
|
||||
{
|
||||
static const QStringList gmsts(QStringList()<< "fNPCbaseMagickaMult" << "fAutoSpellChance"
|
||||
<< "fEffectCostMult" << "iAutoSpellAlterationMax" << "iAutoSpellConjurationMax"
|
||||
<< "iAutoSpellDestructionMax" << "iAutoSpellIllusionMax" << "iAutoSpellMysticismMax"
|
||||
<< "iAutoSpellRestorationMax" << "iAutoSpellTimesCanCast" << "iAutoSpellAttSkillMin");
|
||||
|
||||
bool match = false;
|
||||
for (int row = topLeft.row(); row <= bottomRight.row(); ++row)
|
||||
{
|
||||
if (gmsts.contains(mGmsts.getRecord(row).get().mId.c_str()))
|
||||
{
|
||||
match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!match)
|
||||
return;
|
||||
|
||||
clearNpcStatsCache();
|
||||
|
||||
std::string empty;
|
||||
emit updateNpcAutocalc(0/*all*/, empty);
|
||||
}
|
||||
|
||||
void CSMWorld::Data::clearNpcStatsCache ()
|
||||
{
|
||||
for (std::map<std::string, CSMWorld::NpcStats*>::iterator it (mNpcStatCache.begin());
|
||||
it != mNpcStatCache.end(); ++it)
|
||||
delete it->second;
|
||||
|
||||
mNpcStatCache.clear();
|
||||
}
|
||||
|
||||
CSMWorld::NpcStats* CSMWorld::Data::npcAutoCalculate(const ESM::NPC& npc) const
|
||||
{
|
||||
CSMWorld::NpcStats * cachedStats = getCachedNpcData (npc.mId);
|
||||
if (cachedStats)
|
||||
return cachedStats;
|
||||
|
||||
const ESM::Race *race = &mRaces.getRecord(npc.mRace).get();
|
||||
const ESM::Class *class_ = &mClasses.getRecord(npc.mClass).get();
|
||||
|
||||
bool autoCalc = npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS;
|
||||
short level = npc.mNpdt52.mLevel;
|
||||
if (autoCalc)
|
||||
level = npc.mNpdt12.mLevel;
|
||||
|
||||
std::auto_ptr<CSMWorld::NpcStats> stats (new CSMWorld::NpcStats());
|
||||
|
||||
CSStore store(mGmsts, mSkills, mMagicEffects, mSpells);
|
||||
|
||||
if (autoCalc)
|
||||
{
|
||||
AutoCalc::autoCalcAttributesImpl (&npc, race, class_, level, *stats, &store);
|
||||
|
||||
stats->setHealth(autoCalculateHealth(level, class_, *stats));
|
||||
stats->setMana(autoCalculateMana(*stats));
|
||||
stats->setFatigue(autoCalculateFatigue(*stats));
|
||||
|
||||
AutoCalc::autoCalcSkillsImpl(&npc, race, class_, level, *stats, &store);
|
||||
|
||||
AutoCalc::autoCalculateSpells(race, *stats, &store);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (std::vector<std::string>::const_iterator it = npc.mSpells.mList.begin();
|
||||
it != npc.mSpells.mList.end(); ++it)
|
||||
{
|
||||
stats->addSpell(*it);
|
||||
}
|
||||
}
|
||||
|
||||
// update spell info
|
||||
const std::vector<std::string> &racePowers = race->mPowers.mList;
|
||||
for (unsigned int i = 0; i < racePowers.size(); ++i)
|
||||
{
|
||||
int type = -1;
|
||||
int spellIndex = mSpells.searchId(racePowers[i]);
|
||||
if (spellIndex != -1)
|
||||
type = mSpells.getRecord(spellIndex).get().mData.mType;
|
||||
stats->addPowers(racePowers[i], type);
|
||||
}
|
||||
// cost/chance
|
||||
int skills[ESM::Skill::Length];
|
||||
if (autoCalc)
|
||||
for (int i = 0; i< ESM::Skill::Length; ++i)
|
||||
skills[i] = stats->getBaseSkill(i);
|
||||
else
|
||||
for (int i = 0; i< ESM::Skill::Length; ++i)
|
||||
skills[i] = npc.mNpdt52.mSkills[i];
|
||||
|
||||
int attributes[ESM::Attribute::Length];
|
||||
if (autoCalc)
|
||||
for (int i = 0; i< ESM::Attribute::Length; ++i)
|
||||
attributes[i] = stats->getBaseAttribute(i);
|
||||
else
|
||||
{
|
||||
attributes[ESM::Attribute::Strength] = npc.mNpdt52.mStrength;
|
||||
attributes[ESM::Attribute::Willpower] = npc.mNpdt52.mWillpower;
|
||||
attributes[ESM::Attribute::Agility] = npc.mNpdt52.mAgility;
|
||||
attributes[ESM::Attribute::Speed] = npc.mNpdt52.mSpeed;
|
||||
attributes[ESM::Attribute::Endurance] = npc.mNpdt52.mEndurance;
|
||||
attributes[ESM::Attribute::Personality] = npc.mNpdt52.mPersonality;
|
||||
attributes[ESM::Attribute::Luck] = npc.mNpdt52.mLuck;
|
||||
}
|
||||
|
||||
const std::vector<CSMWorld::SpellInfo>& spells = stats->spells();
|
||||
for (std::vector<SpellInfo>::const_iterator it = spells.begin(); it != spells.end(); ++it)
|
||||
{
|
||||
int cost = -1;
|
||||
int spellIndex = mSpells.searchId((*it).mName);
|
||||
const ESM::Spell* spell = 0;
|
||||
if (spellIndex != -1)
|
||||
{
|
||||
spell = &mSpells.getRecord(spellIndex).get();
|
||||
cost = spell->mData.mCost;
|
||||
|
||||
int school;
|
||||
float skillTerm;
|
||||
AutoCalc::calcWeakestSchool(spell, skills, school, skillTerm, &store);
|
||||
float chance = calcAutoCastChance(spell, skills, attributes, school, &store);
|
||||
|
||||
stats->addCostAndChance((*it).mName, cost, (int)ceil(chance)); // percent
|
||||
}
|
||||
}
|
||||
|
||||
if (stats.get() == 0)
|
||||
return 0;
|
||||
|
||||
CSMWorld::NpcStats *result = stats.release();
|
||||
emit cacheNpcStats (npc.mId, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
void CSMWorld::Data::cacheNpcStatsEvent (const std::string& id, CSMWorld::NpcStats *stats)
|
||||
{
|
||||
mNpcStatCache[id] = stats;
|
||||
}
|
||||
|
||||
CSMWorld::NpcStats* CSMWorld::Data::getCachedNpcData (const std::string& id) const
|
||||
{
|
||||
std::map<std::string, CSMWorld::NpcStats*>::const_iterator it = mNpcStatCache.find(id);
|
||||
if (it != mNpcStatCache.end())
|
||||
return it->second;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -61,6 +61,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,6 +124,10 @@ namespace CSMWorld
|
|||
|
||||
static int count (RecordBase::State state, const CollectionBase& collection);
|
||||
|
||||
const Data& self ();
|
||||
|
||||
void clearNpcStatsCache ();
|
||||
|
||||
public:
|
||||
|
||||
Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager);
|
||||
|
@ -269,15 +276,37 @@ namespace CSMWorld
|
|||
int count (RecordBase::State state) const;
|
||||
///< Return number of top-level records with the given \a state.
|
||||
|
||||
NpcStats* npcAutoCalculate (const ESM::NPC& npc) const;
|
||||
|
||||
NpcStats* getCachedNpcData (const std::string& id) const;
|
||||
|
||||
signals:
|
||||
|
||||
void idListChanged();
|
||||
|
||||
// refresh NPC dialogue subviews via object table model
|
||||
void updateNpcAutocalc (int type, const std::string& id);
|
||||
|
||||
void cacheNpcStats (const std::string& id, NpcStats *stats) const;
|
||||
|
||||
private slots:
|
||||
|
||||
void dataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight);
|
||||
|
||||
void rowsChanged (const QModelIndex& parent, int start, int end);
|
||||
|
||||
// for autocalc updates when gmst/race/class/skils tables change
|
||||
void gmstDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight);
|
||||
|
||||
void raceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight);
|
||||
|
||||
void classDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight);
|
||||
|
||||
void skillDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight);
|
||||
|
||||
void npcDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight);
|
||||
|
||||
void cacheNpcStatsEvent (const std::string& id, NpcStats *stats);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -261,3 +261,8 @@ CSMWorld::NestedTableWrapperBase* CSMWorld::IdTree::nestedTable(const QModelInde
|
|||
|
||||
return mNestedCollection->nestedTable(index.row(), index.column());
|
||||
}
|
||||
|
||||
void CSMWorld::IdTree::updateNpcAutocalc (int type, const std::string& id)
|
||||
{
|
||||
emit refreshNpcDialogue (type, id);
|
||||
}
|
||||
|
|
|
@ -73,11 +73,17 @@ namespace CSMWorld
|
|||
|
||||
virtual bool hasChildren (const QModelIndex& index) const;
|
||||
|
||||
signals:
|
||||
signals:
|
||||
|
||||
void resetStart(const QString& id);
|
||||
void resetStart(const QString& id);
|
||||
|
||||
void resetEnd(const QString& id);
|
||||
void resetEnd(const QString& id);
|
||||
|
||||
void refreshNpcDialogue (int type, const std::string& id);
|
||||
|
||||
public slots:
|
||||
|
||||
void updateNpcAutocalc (int type, const std::string& id);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "idcollection.hpp"
|
||||
#include "pathgrid.hpp"
|
||||
#include "info.hpp"
|
||||
#include "usertype.hpp"
|
||||
|
||||
namespace CSMWorld
|
||||
{
|
||||
|
@ -1069,23 +1070,66 @@ namespace CSMWorld
|
|||
switch (subColIndex)
|
||||
{
|
||||
case 0: return isInterior;
|
||||
case 1: return (isInterior && !behaveLikeExterior) ?
|
||||
cell.mAmbi.mAmbient : QVariant(QVariant::UserType);
|
||||
case 2: return (isInterior && !behaveLikeExterior) ?
|
||||
cell.mAmbi.mSunlight : QVariant(QVariant::UserType);
|
||||
case 3: return (isInterior && !behaveLikeExterior) ?
|
||||
cell.mAmbi.mFog : QVariant(QVariant::UserType);
|
||||
case 4: return (isInterior && !behaveLikeExterior) ?
|
||||
cell.mAmbi.mFogDensity : QVariant(QVariant::UserType);
|
||||
case 1:
|
||||
{
|
||||
if (isInterior && !behaveLikeExterior)
|
||||
return cell.mAmbi.mAmbient;
|
||||
else
|
||||
{
|
||||
UserInt i(cell.mAmbi.mAmbient);
|
||||
return QVariant(QVariant::fromValue(i));
|
||||
}
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
if (isInterior && !behaveLikeExterior)
|
||||
return cell.mAmbi.mSunlight;
|
||||
else
|
||||
{
|
||||
UserInt i(cell.mAmbi.mSunlight);
|
||||
return QVariant(QVariant::fromValue(i));
|
||||
}
|
||||
}
|
||||
case 3:
|
||||
{
|
||||
if (isInterior && !behaveLikeExterior)
|
||||
return cell.mAmbi.mFog;
|
||||
else
|
||||
{
|
||||
UserInt i(cell.mAmbi.mFog);
|
||||
return QVariant(QVariant::fromValue(i));
|
||||
}
|
||||
}
|
||||
case 4:
|
||||
{
|
||||
if (isInterior && !behaveLikeExterior)
|
||||
return cell.mAmbi.mFogDensity;
|
||||
else
|
||||
{
|
||||
UserFloat f(cell.mAmbi.mFogDensity);
|
||||
return QVariant(QVariant::fromValue(f));
|
||||
}
|
||||
}
|
||||
case 5:
|
||||
{
|
||||
if (isInterior && !behaveLikeExterior && interiorWater)
|
||||
return cell.mWater;
|
||||
else
|
||||
return QVariant(QVariant::UserType);
|
||||
{
|
||||
UserFloat f(cell.mWater);
|
||||
return QVariant(QVariant::fromValue(f));
|
||||
}
|
||||
}
|
||||
case 6:
|
||||
{
|
||||
if (isInterior)
|
||||
{
|
||||
UserInt i(cell.mMapColor);
|
||||
return QVariant(QVariant::fromValue(i));
|
||||
}
|
||||
else
|
||||
return cell.mMapColor; // TODO: how to select?
|
||||
}
|
||||
case 6: return isInterior ?
|
||||
QVariant(QVariant::UserType) : cell.mMapColor; // TODO: how to select?
|
||||
//case 7: return isInterior ?
|
||||
//behaveLikeExterior : QVariant(QVariant::UserType);
|
||||
default: throw std::runtime_error("Cell subcolumn index out of range");
|
||||
|
|
138
apps/opencs/model/world/npcstats.cpp
Normal file
138
apps/opencs/model/world/npcstats.cpp
Normal file
|
@ -0,0 +1,138 @@
|
|||
#include "npcstats.hpp"
|
||||
|
||||
#include <components/esm/loadnpc.hpp>
|
||||
#include <components/esm/loadspel.hpp>
|
||||
|
||||
namespace CSMWorld
|
||||
{
|
||||
NpcStats::NpcStats() : mHealth(0), mMana(0), mFatigue(0)
|
||||
{
|
||||
for (int i = 0; i < ESM::Skill::Length; ++i)
|
||||
mSkill[i] = 0;
|
||||
}
|
||||
|
||||
NpcStats::NpcStats(const NpcStats &other)
|
||||
{
|
||||
for (int i = 0; i < ESM::Attribute::Length; ++i)
|
||||
mAttr[i] = other.mAttr[i];
|
||||
|
||||
mSpells = other.mSpells;
|
||||
|
||||
for (int i = 0; i < ESM::Skill::Length; ++i)
|
||||
mSkill[i] = 0;
|
||||
|
||||
mHealth = other.mHealth;
|
||||
mMana = other.mMana;
|
||||
mFatigue = other.mFatigue;
|
||||
}
|
||||
|
||||
NpcStats::~NpcStats()
|
||||
{}
|
||||
|
||||
unsigned char NpcStats::getBaseAttribute(int index) const
|
||||
{
|
||||
if (index < 0 || index >= ESM::Attribute::Length)
|
||||
throw std::runtime_error("attrib index out of bounds");
|
||||
|
||||
return mAttr[index];
|
||||
}
|
||||
|
||||
void NpcStats::setAttribute(int index, unsigned char value)
|
||||
{
|
||||
if (index < 0 || index >= ESM::Attribute::Length)
|
||||
throw std::runtime_error("attrib index out of bounds");
|
||||
|
||||
mAttr[index] = value;
|
||||
}
|
||||
|
||||
void NpcStats::addSpell(const std::string& id)
|
||||
{
|
||||
struct SpellInfo info;
|
||||
info.mName = id;
|
||||
info.mType = ESM::Spell::ST_Spell; // default type from autocalc
|
||||
info.mFromRace = false;
|
||||
info.mCost = 0;
|
||||
info.mChance = 0;
|
||||
|
||||
mSpells.insert(mSpells.begin(), info);
|
||||
}
|
||||
|
||||
void NpcStats::addPowers(const std::string& id, int type)
|
||||
{
|
||||
struct SpellInfo info;
|
||||
info.mName = id;
|
||||
info.mType = type;
|
||||
info.mFromRace = true;
|
||||
info.mCost = 0;
|
||||
info.mChance = 0;
|
||||
|
||||
mSpells.push_back(info);
|
||||
}
|
||||
|
||||
void NpcStats::addCostAndChance(const std::string& id, int cost, int chance)
|
||||
{
|
||||
// usually only a few spells, so simply iterate through rather than keeping a separate
|
||||
// lookup index or map
|
||||
for (std::vector<SpellInfo>::iterator it = mSpells.begin(); it != mSpells.end(); ++it)
|
||||
{
|
||||
if ((*it).mName == id)
|
||||
{
|
||||
(*it).mCost = cost;
|
||||
(*it).mChance = chance;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<SpellInfo>& NpcStats::spells() const
|
||||
{
|
||||
return mSpells;
|
||||
}
|
||||
|
||||
unsigned char NpcStats::getBaseSkill(int index) const
|
||||
{
|
||||
if (index < 0 || index >= ESM::Skill::Length)
|
||||
throw std::runtime_error("skill index out of bounds");
|
||||
|
||||
return mSkill[index];
|
||||
}
|
||||
|
||||
void NpcStats::setBaseSkill(int index, unsigned char value)
|
||||
{
|
||||
if (index < 0 || index >= ESM::Skill::Length)
|
||||
throw std::runtime_error("skill index out of bounds");
|
||||
|
||||
mSkill[index] = value;
|
||||
}
|
||||
|
||||
unsigned short NpcStats::getHealth()
|
||||
{
|
||||
return mHealth;
|
||||
}
|
||||
|
||||
void NpcStats::setHealth(unsigned short health)
|
||||
{
|
||||
mHealth = health;
|
||||
}
|
||||
|
||||
unsigned short NpcStats::getMana()
|
||||
{
|
||||
return mMana;
|
||||
}
|
||||
|
||||
void NpcStats::setMana(unsigned short mana)
|
||||
{
|
||||
mMana = mana;
|
||||
}
|
||||
|
||||
unsigned short NpcStats::getFatigue()
|
||||
{
|
||||
return mFatigue;
|
||||
}
|
||||
|
||||
void NpcStats::setFatigue(unsigned short fatigue)
|
||||
{
|
||||
mFatigue = fatigue;
|
||||
}
|
||||
}
|
||||
|
74
apps/opencs/model/world/npcstats.hpp
Normal file
74
apps/opencs/model/world/npcstats.hpp
Normal file
|
@ -0,0 +1,74 @@
|
|||
#ifndef CSM_WORLD_NPCSTATS_H
|
||||
#define CSM_WORLD_NPCSTATS_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <QMetaType>
|
||||
|
||||
#include <components/esm/attr.hpp>
|
||||
#include <components/esm/loadskil.hpp>
|
||||
#include <components/autocalc/autocalc.hpp>
|
||||
|
||||
namespace CSMWorld
|
||||
{
|
||||
struct SpellInfo
|
||||
{
|
||||
std::string mName;
|
||||
int mType;
|
||||
bool mFromRace;
|
||||
int mCost;
|
||||
int mChance;
|
||||
};
|
||||
|
||||
class NpcStats : public AutoCalc::StatsBase
|
||||
{
|
||||
|
||||
int mAttr[ESM::Attribute::Length];
|
||||
std::vector<SpellInfo> mSpells;
|
||||
int mSkill[ESM::Skill::Length];
|
||||
|
||||
unsigned short mHealth;
|
||||
unsigned short mMana;
|
||||
unsigned short mFatigue;
|
||||
|
||||
public:
|
||||
|
||||
NpcStats();
|
||||
|
||||
NpcStats(const NpcStats &other);
|
||||
|
||||
~NpcStats();
|
||||
|
||||
virtual unsigned char getBaseAttribute(int index) const;
|
||||
|
||||
virtual void setAttribute(int index, unsigned char value);
|
||||
|
||||
virtual void addSpell(const std::string& id);
|
||||
|
||||
void addPowers(const std::string& id, int type);
|
||||
|
||||
void addCostAndChance(const std::string& id, int cost, int chance);
|
||||
|
||||
const std::vector<SpellInfo>& spells() const;
|
||||
|
||||
virtual unsigned char getBaseSkill(int index) const;
|
||||
|
||||
virtual void setBaseSkill(int index, unsigned char value);
|
||||
|
||||
unsigned short getHealth();
|
||||
|
||||
void setHealth(unsigned short health);
|
||||
|
||||
unsigned short getMana();
|
||||
|
||||
void setMana(unsigned short mana);
|
||||
|
||||
unsigned short getFatigue();
|
||||
|
||||
void setFatigue(unsigned short fatigue);
|
||||
};
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(CSMWorld::NpcStats*)
|
||||
|
||||
#endif // CSM_WORLD_NPCSTATS_H
|
|
@ -5,7 +5,12 @@
|
|||
#include <utility>
|
||||
|
||||
#include <components/esm/loadcont.hpp>
|
||||
#include <components/esm/attr.hpp>
|
||||
|
||||
#include "nestedtablewrapper.hpp"
|
||||
#include "usertype.hpp"
|
||||
#include "idtree.hpp"
|
||||
#include "npcstats.hpp"
|
||||
|
||||
CSMWorld::PotionColumns::PotionColumns (const InventoryColumns& columns)
|
||||
: InventoryColumns (columns) {}
|
||||
|
@ -553,8 +558,8 @@ CSMWorld::NpcColumns::NpcColumns (const ActorColumns& actorColumns)
|
|||
mMisc(NULL)
|
||||
{}
|
||||
|
||||
CSMWorld::NpcRefIdAdapter::NpcRefIdAdapter (const NpcColumns& columns)
|
||||
: ActorRefIdAdapter<ESM::NPC> (UniversalId::Type_Npc, columns), mColumns (columns)
|
||||
CSMWorld::NpcRefIdAdapter::NpcRefIdAdapter (const NpcColumns& columns, const CSMWorld::Data& data)
|
||||
: ActorRefIdAdapter<ESM::NPC> (UniversalId::Type_Npc, columns), mColumns (columns), mData(data)
|
||||
{}
|
||||
|
||||
QVariant CSMWorld::NpcRefIdAdapter::getData (const RefIdColumn *column, const RefIdData& data, int index)
|
||||
|
@ -629,8 +634,47 @@ void CSMWorld::NpcRefIdAdapter::setData (const RefIdColumn *column, RefIdData& d
|
|||
npc.mFlags &= ~iter->second;
|
||||
|
||||
if (iter->second == ESM::NPC::Autocalc)
|
||||
npc.mNpdtType = (value.toInt() != 0) ? ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS
|
||||
: ESM::NPC::NPC_DEFAULT;
|
||||
{
|
||||
if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
|
||||
{
|
||||
CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc);
|
||||
|
||||
// update npc
|
||||
npc.mNpdtType = ESM::NPC::NPC_DEFAULT;
|
||||
npc.mNpdt52.mLevel = npc.mNpdt12.mLevel;
|
||||
|
||||
npc.mNpdt52.mStrength = stats->getBaseAttribute(ESM::Attribute::Strength);
|
||||
npc.mNpdt52.mIntelligence = stats->getBaseAttribute(ESM::Attribute::Intelligence);
|
||||
npc.mNpdt52.mWillpower = stats->getBaseAttribute(ESM::Attribute::Willpower);
|
||||
npc.mNpdt52.mAgility = stats->getBaseAttribute(ESM::Attribute::Agility);
|
||||
npc.mNpdt52.mSpeed = stats->getBaseAttribute(ESM::Attribute::Speed);
|
||||
npc.mNpdt52.mEndurance = stats->getBaseAttribute(ESM::Attribute::Endurance);
|
||||
npc.mNpdt52.mPersonality = stats->getBaseAttribute(ESM::Attribute::Personality);
|
||||
npc.mNpdt52.mLuck = stats->getBaseAttribute(ESM::Attribute::Luck);
|
||||
|
||||
for (int i = 0; i < ESM::Skill::Length; ++i)
|
||||
{
|
||||
npc.mNpdt52.mSkills[i] = stats->getBaseSkill(i);
|
||||
}
|
||||
|
||||
npc.mNpdt52.mHealth = stats->getHealth();
|
||||
npc.mNpdt52.mMana = stats->getMana();
|
||||
npc.mNpdt52.mFatigue = stats->getFatigue();
|
||||
npc.mNpdt52.mDisposition = npc.mNpdt12.mDisposition;
|
||||
npc.mNpdt52.mReputation = npc.mNpdt12.mReputation;
|
||||
npc.mNpdt52.mRank = npc.mNpdt12.mRank;
|
||||
npc.mNpdt52.mGold = npc.mNpdt12.mGold;
|
||||
|
||||
// TODO: add spells from autogenerated list like vanilla (but excluding any
|
||||
// race powers or abilities)
|
||||
}
|
||||
else
|
||||
{
|
||||
npc.mNpdtType = ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS;
|
||||
npc.mNpdt12.mLevel = npc.mNpdt52.mLevel; // for NPC's loaded as non-autocalc
|
||||
mData.npcAutoCalculate(npc);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -643,7 +687,7 @@ void CSMWorld::NpcRefIdAdapter::setData (const RefIdColumn *column, RefIdData& d
|
|||
record.setModified (npc);
|
||||
}
|
||||
|
||||
CSMWorld::NpcAttributesRefIdAdapter::NpcAttributesRefIdAdapter ()
|
||||
CSMWorld::NpcAttributesRefIdAdapter::NpcAttributesRefIdAdapter(const CSMWorld::Data& data) : mData(data)
|
||||
{}
|
||||
|
||||
void CSMWorld::NpcAttributesRefIdAdapter::addNestedRow (const RefIdColumn *column,
|
||||
|
@ -691,7 +735,8 @@ QVariant CSMWorld::NpcAttributesRefIdAdapter::getNestedData (const RefIdColumn *
|
|||
const Record<ESM::NPC>& record =
|
||||
static_cast<const Record<ESM::NPC>&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc)));
|
||||
|
||||
const ESM::NPC::NPDTstruct52& npcStruct = record.get().mNpdt52;
|
||||
const ESM::NPC npc = record.get();
|
||||
const ESM::NPC::NPDTstruct52& npcStruct = npc.mNpdt52;
|
||||
|
||||
if (subColIndex == 0)
|
||||
switch (subRowIndex)
|
||||
|
@ -707,18 +752,36 @@ QVariant CSMWorld::NpcAttributesRefIdAdapter::getNestedData (const RefIdColumn *
|
|||
default: return QVariant(); // throw an exception here?
|
||||
}
|
||||
else if (subColIndex == 1)
|
||||
switch (subRowIndex)
|
||||
if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
|
||||
{
|
||||
case 0: return static_cast<int>(npcStruct.mStrength);
|
||||
case 1: return static_cast<int>(npcStruct.mIntelligence);
|
||||
case 2: return static_cast<int>(npcStruct.mWillpower);
|
||||
case 3: return static_cast<int>(npcStruct.mAgility);
|
||||
case 4: return static_cast<int>(npcStruct.mSpeed);
|
||||
case 5: return static_cast<int>(npcStruct.mEndurance);
|
||||
case 6: return static_cast<int>(npcStruct.mPersonality);
|
||||
case 7: return static_cast<int>(npcStruct.mLuck);
|
||||
default: return QVariant(); // throw an exception here?
|
||||
CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc);
|
||||
|
||||
switch (subRowIndex)
|
||||
{
|
||||
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?
|
||||
}
|
||||
}
|
||||
else
|
||||
switch (subRowIndex)
|
||||
{
|
||||
case 0: return static_cast<int>(npcStruct.mStrength);
|
||||
case 1: return static_cast<int>(npcStruct.mIntelligence);
|
||||
case 2: return static_cast<int>(npcStruct.mWillpower);
|
||||
case 3: return static_cast<int>(npcStruct.mAgility);
|
||||
case 4: return static_cast<int>(npcStruct.mSpeed);
|
||||
case 5: return static_cast<int>(npcStruct.mEndurance);
|
||||
case 6: return static_cast<int>(npcStruct.mPersonality);
|
||||
case 7: return static_cast<int>(npcStruct.mLuck);
|
||||
default: return QVariant(); // throw an exception here?
|
||||
}
|
||||
else
|
||||
return QVariant(); // throw an exception here?
|
||||
}
|
||||
|
@ -761,7 +824,8 @@ int CSMWorld::NpcAttributesRefIdAdapter::getNestedRowsCount(const RefIdColumn *c
|
|||
return 8;
|
||||
}
|
||||
|
||||
CSMWorld::NpcSkillsRefIdAdapter::NpcSkillsRefIdAdapter ()
|
||||
CSMWorld::NpcSkillsRefIdAdapter::NpcSkillsRefIdAdapter(const CSMWorld::Data& data)
|
||||
: mData(data)
|
||||
{}
|
||||
|
||||
void CSMWorld::NpcSkillsRefIdAdapter::addNestedRow (const RefIdColumn *column,
|
||||
|
@ -809,7 +873,7 @@ QVariant CSMWorld::NpcSkillsRefIdAdapter::getNestedData (const RefIdColumn *colu
|
|||
const Record<ESM::NPC>& record =
|
||||
static_cast<const Record<ESM::NPC>&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc)));
|
||||
|
||||
const ESM::NPC::NPDTstruct52& npcStruct = record.get().mNpdt52;
|
||||
const ESM::NPC npc = record.get();
|
||||
|
||||
if (subRowIndex < 0 || subRowIndex >= ESM::Skill::Length)
|
||||
throw std::runtime_error ("index out of range");
|
||||
|
@ -817,7 +881,18 @@ QVariant CSMWorld::NpcSkillsRefIdAdapter::getNestedData (const RefIdColumn *colu
|
|||
if (subColIndex == 0)
|
||||
return QString(ESM::Skill::sSkillNames[subRowIndex].c_str());
|
||||
else if (subColIndex == 1)
|
||||
return static_cast<int>(npcStruct.mSkills[subRowIndex]);
|
||||
{
|
||||
if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
|
||||
{
|
||||
CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc);
|
||||
return static_cast<int>(stats->getBaseSkill(subRowIndex));
|
||||
}
|
||||
else
|
||||
{
|
||||
const ESM::NPC::NPDTstruct52& npcStruct = npc.mNpdt52;
|
||||
return static_cast<int>(npcStruct.mSkills[subRowIndex]);
|
||||
}
|
||||
}
|
||||
else
|
||||
return QVariant(); // throw an exception here?
|
||||
}
|
||||
|
@ -852,7 +927,7 @@ int CSMWorld::NpcSkillsRefIdAdapter::getNestedRowsCount(const RefIdColumn *colum
|
|||
return ESM::Skill::Length;
|
||||
}
|
||||
|
||||
CSMWorld::NpcMiscRefIdAdapter::NpcMiscRefIdAdapter ()
|
||||
CSMWorld::NpcMiscRefIdAdapter::NpcMiscRefIdAdapter(const CSMWorld::Data& data) : mData(data)
|
||||
{}
|
||||
|
||||
CSMWorld::NpcMiscRefIdAdapter::~NpcMiscRefIdAdapter()
|
||||
|
@ -888,16 +963,37 @@ QVariant CSMWorld::NpcMiscRefIdAdapter::getNestedData (const RefIdColumn *column
|
|||
const Record<ESM::NPC>& record =
|
||||
static_cast<const Record<ESM::NPC>&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Npc)));
|
||||
|
||||
bool autoCalc = (record.get().mFlags & ESM::NPC::Autocalc) != 0;
|
||||
const ESM::NPC npc = record.get();
|
||||
|
||||
bool autoCalc = (npc.mFlags & ESM::NPC::Autocalc) != 0;
|
||||
|
||||
if (autoCalc)
|
||||
{
|
||||
CSMWorld::NpcStats *stats = mData.npcAutoCalculate(npc);
|
||||
|
||||
switch (subColIndex)
|
||||
{
|
||||
case 0: return static_cast<int>(record.get().mNpdt12.mLevel);
|
||||
case 1: return QVariant(QVariant::UserType);
|
||||
case 2: return QVariant(QVariant::UserType);
|
||||
case 3: return QVariant(QVariant::UserType);
|
||||
case 4: return QVariant(QVariant::UserType);
|
||||
case 0: return static_cast<int>(npc.mNpdt12.mLevel);
|
||||
case 1:
|
||||
{
|
||||
UserInt i(0); // unknown
|
||||
return QVariant(QVariant::fromValue(i));
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
UserInt i(stats->getHealth());
|
||||
return QVariant(QVariant::fromValue(i));
|
||||
}
|
||||
case 3:
|
||||
{
|
||||
UserInt i(stats->getMana());
|
||||
return QVariant(QVariant::fromValue(i));
|
||||
}
|
||||
case 4:
|
||||
{
|
||||
UserInt i(stats->getFatigue());
|
||||
return QVariant(QVariant::fromValue(i));
|
||||
}
|
||||
case 5: return static_cast<int>(record.get().mNpdt12.mDisposition);
|
||||
case 6: return static_cast<int>(record.get().mNpdt12.mReputation);
|
||||
case 7: return static_cast<int>(record.get().mNpdt12.mRank);
|
||||
|
@ -905,6 +1001,7 @@ QVariant CSMWorld::NpcMiscRefIdAdapter::getNestedData (const RefIdColumn *column
|
|||
case 9: return record.get().mPersistent == true;
|
||||
default: return QVariant(); // throw an exception here?
|
||||
}
|
||||
}
|
||||
else
|
||||
switch (subColIndex)
|
||||
{
|
||||
|
@ -934,31 +1031,31 @@ void CSMWorld::NpcMiscRefIdAdapter::setNestedData (const RefIdColumn *column,
|
|||
if (autoCalc)
|
||||
switch(subColIndex)
|
||||
{
|
||||
case 0: npc.mNpdt12.mLevel = static_cast<short>(value.toInt()); break;
|
||||
case 0: npc.mNpdt12.mLevel = static_cast<short>(value.toInt()); break;
|
||||
case 1: return;
|
||||
case 2: return;
|
||||
case 3: return;
|
||||
case 4: return;
|
||||
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;
|
||||
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()); 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 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;
|
||||
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?
|
||||
}
|
||||
|
||||
|
@ -1063,3 +1160,238 @@ void CSMWorld::WeaponRefIdAdapter::setData (const RefIdColumn *column, RefIdData
|
|||
EnchantableRefIdAdapter<ESM::Weapon>::setData (column, data, index, value);
|
||||
}
|
||||
}
|
||||
|
||||
namespace CSMWorld
|
||||
{
|
||||
|
||||
template <>
|
||||
void NestedSpellRefIdAdapter<ESM::NPC>::addNestedRow (const RefIdColumn *column,
|
||||
RefIdData& data, int index, int position) const
|
||||
{
|
||||
Record<ESM::NPC>& record =
|
||||
static_cast<Record<ESM::NPC>&> (data.getRecord (RefIdData::LocalIndex (index, mType)));
|
||||
|
||||
if (record.get().mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
|
||||
return; // can't edit autocalculated spells
|
||||
|
||||
ESM::NPC caster = record.get();
|
||||
|
||||
std::vector<std::string>& list = caster.mSpells.mList;
|
||||
|
||||
std::string newString;
|
||||
|
||||
if (position >= (int)list.size())
|
||||
list.push_back(newString);
|
||||
else
|
||||
list.insert(list.begin()+position, newString);
|
||||
|
||||
record.setModified (caster);
|
||||
}
|
||||
|
||||
template <>
|
||||
void NestedSpellRefIdAdapter<ESM::NPC>::removeNestedRow (const RefIdColumn *column,
|
||||
RefIdData& data, int index, int rowToRemove) const
|
||||
{
|
||||
Record<ESM::NPC>& record =
|
||||
static_cast<Record<ESM::NPC>&> (data.getRecord (RefIdData::LocalIndex (index, mType)));
|
||||
|
||||
if (record.get().mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
|
||||
return; // can't edit autocalculated spells
|
||||
|
||||
ESM::NPC caster = record.get();
|
||||
|
||||
std::vector<std::string>& list = caster.mSpells.mList;
|
||||
|
||||
// avoid race power rows
|
||||
int size = 0;
|
||||
int raceIndex = mData.getRaces().searchId(caster.mRace);
|
||||
if (raceIndex != -1)
|
||||
size = mData.getRaces().getRecord(raceIndex).get().mPowers.mList.size();
|
||||
|
||||
if (rowToRemove < 0 || rowToRemove >= static_cast<int> (list.size() + size))
|
||||
throw std::runtime_error ("index out of range");
|
||||
|
||||
if (rowToRemove >= static_cast<int>(list.size()) && rowToRemove < static_cast<int>(list.size() + size))
|
||||
return; // hack, assumes the race powers are added at the end
|
||||
|
||||
list.erase (list.begin () + rowToRemove);
|
||||
|
||||
record.setModified (caster);
|
||||
}
|
||||
|
||||
template <>
|
||||
void NestedSpellRefIdAdapter<ESM::NPC>::setNestedData (const RefIdColumn *column,
|
||||
RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const
|
||||
{
|
||||
Record<ESM::NPC>& record =
|
||||
static_cast<Record<ESM::NPC>&> (data.getRecord (RefIdData::LocalIndex (row, mType)));
|
||||
|
||||
if (record.get().mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
|
||||
return; // can't edit autocalculated spells
|
||||
|
||||
ESM::NPC caster = record.get();
|
||||
std::vector<std::string>& list = caster.mSpells.mList;
|
||||
|
||||
// avoid race power rows
|
||||
int size = 0;
|
||||
int raceIndex = mData.getRaces().searchId(caster.mRace);
|
||||
if (raceIndex != -1)
|
||||
size = mData.getRaces().getRecord(raceIndex).get().mPowers.mList.size();
|
||||
|
||||
if (subRowIndex < 0 || subRowIndex >= static_cast<int> (list.size() + size))
|
||||
throw std::runtime_error ("index out of range");
|
||||
|
||||
if (subRowIndex >= static_cast<int>(list.size()) && subRowIndex < static_cast<int>(list.size() + size))
|
||||
return; // hack, assumes the race powers are added at the end
|
||||
|
||||
if (subColIndex == 0)
|
||||
list.at(subRowIndex) = std::string(value.toString().toUtf8());
|
||||
else
|
||||
throw std::runtime_error("Trying to access non-existing column in the nested table!");
|
||||
|
||||
record.setModified (caster);
|
||||
}
|
||||
|
||||
template <>
|
||||
QVariant NestedSpellRefIdAdapter<ESM::NPC>::getNestedData (const RefIdColumn *column,
|
||||
const RefIdData& data, int index, int subRowIndex, int subColIndex) const
|
||||
{
|
||||
const Record<ESM::NPC>& record =
|
||||
static_cast<const Record<ESM::NPC>&> (data.getRecord (RefIdData::LocalIndex (index, mType)));
|
||||
|
||||
const std::vector<SpellInfo>& spells = mData.npcAutoCalculate(record.get())->spells();
|
||||
|
||||
if (subRowIndex < 0 || subRowIndex >= static_cast<int> (spells.size()))
|
||||
throw std::runtime_error ("index out of range");
|
||||
|
||||
switch (subColIndex)
|
||||
{
|
||||
case 0: return QString::fromUtf8(spells[subRowIndex].mName.c_str());
|
||||
case 1: return spells[subRowIndex].mType;
|
||||
case 2: return spells[subRowIndex].mFromRace;
|
||||
case 3: return spells[subRowIndex].mCost;
|
||||
case 4: return spells[subRowIndex].mChance;
|
||||
default:
|
||||
throw std::runtime_error("Trying to access non-existing column in the nested table!");
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
int NestedSpellRefIdAdapter<ESM::NPC>::getNestedColumnsCount(const RefIdColumn *column,
|
||||
const RefIdData& data) const
|
||||
{
|
||||
return 5;
|
||||
}
|
||||
|
||||
template <>
|
||||
int NestedSpellRefIdAdapter<ESM::NPC>::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const
|
||||
{
|
||||
const Record<ESM::NPC>& record =
|
||||
static_cast<const Record<ESM::NPC>&> (data.getRecord (RefIdData::LocalIndex (index, mType)));
|
||||
|
||||
const std::vector<SpellInfo> spells = mData.npcAutoCalculate(record.get())->spells();
|
||||
return static_cast<int>(spells.size());
|
||||
}
|
||||
|
||||
template <>
|
||||
void NestedSpellRefIdAdapter<ESM::Creature>::addNestedRow (const RefIdColumn *column,
|
||||
RefIdData& data, int index, int position) const
|
||||
{
|
||||
Record<ESM::Creature>& record =
|
||||
static_cast<Record<ESM::Creature>&> (data.getRecord (RefIdData::LocalIndex (index, mType)));
|
||||
ESM::Creature caster = record.get();
|
||||
|
||||
std::vector<std::string>& list = caster.mSpells.mList;
|
||||
|
||||
std::string newString;
|
||||
|
||||
if (position >= (int)list.size())
|
||||
list.push_back(newString);
|
||||
else
|
||||
list.insert(list.begin()+position, newString);
|
||||
|
||||
record.setModified (caster);
|
||||
}
|
||||
|
||||
template <>
|
||||
void NestedSpellRefIdAdapter<ESM::Creature>::removeNestedRow (const RefIdColumn *column,
|
||||
RefIdData& data, int index, int rowToRemove) const
|
||||
{
|
||||
Record<ESM::Creature>& record =
|
||||
static_cast<Record<ESM::Creature>&> (data.getRecord (RefIdData::LocalIndex (index, mType)));
|
||||
ESM::Creature caster = record.get();
|
||||
|
||||
std::vector<std::string>& list = caster.mSpells.mList;
|
||||
|
||||
if (rowToRemove < 0 || rowToRemove >= static_cast<int> (list.size()))
|
||||
throw std::runtime_error ("index out of range");
|
||||
|
||||
list.erase (list.begin () + rowToRemove);
|
||||
|
||||
record.setModified (caster);
|
||||
}
|
||||
|
||||
template <>
|
||||
void NestedSpellRefIdAdapter<ESM::Creature>::setNestedData (const RefIdColumn *column,
|
||||
RefIdData& data, int row, const QVariant& value, int subRowIndex, int subColIndex) const
|
||||
{
|
||||
Record<ESM::Creature>& record =
|
||||
static_cast<Record<ESM::Creature>&> (data.getRecord (RefIdData::LocalIndex (row, mType)));
|
||||
ESM::Creature caster = record.get();
|
||||
std::vector<std::string>& list = caster.mSpells.mList;
|
||||
|
||||
if (subRowIndex < 0 || subRowIndex >= static_cast<int> (list.size()))
|
||||
throw std::runtime_error ("index out of range");
|
||||
|
||||
if (subColIndex == 0)
|
||||
list.at(subRowIndex) = std::string(value.toString().toUtf8());
|
||||
else
|
||||
throw std::runtime_error("Trying to access non-existing column in the nested table!");
|
||||
|
||||
record.setModified (caster);
|
||||
}
|
||||
|
||||
template<>
|
||||
QVariant NestedSpellRefIdAdapter<ESM::Creature>::getNestedData (const RefIdColumn *column,
|
||||
const RefIdData& data, int index, int subRowIndex, int subColIndex) const
|
||||
{
|
||||
const Record<ESM::Creature>& record =
|
||||
static_cast<const Record<ESM::Creature>&> (data.getRecord (RefIdData::LocalIndex (index, mType)));
|
||||
|
||||
const std::vector<std::string>& list = record.get().mSpells.mList;
|
||||
|
||||
if (subRowIndex < 0 || subRowIndex >= static_cast<int> (list.size()))
|
||||
throw std::runtime_error ("index out of range");
|
||||
|
||||
const std::string& content = list.at(subRowIndex);
|
||||
|
||||
int type = -1;
|
||||
int spellIndex = mData.getSpells().searchId(content);
|
||||
if (spellIndex != -1)
|
||||
type = mData.getSpells().getRecord(spellIndex).get().mData.mType;
|
||||
|
||||
if (subColIndex == 0)
|
||||
return QString::fromUtf8(content.c_str());
|
||||
else if (subColIndex == 1)
|
||||
return type;
|
||||
else
|
||||
throw std::runtime_error("Trying to access non-existing column in the nested table!");
|
||||
}
|
||||
|
||||
template <>
|
||||
int NestedSpellRefIdAdapter<ESM::Creature>::getNestedColumnsCount(const RefIdColumn *column,
|
||||
const RefIdData& data) const
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
template <>
|
||||
int NestedSpellRefIdAdapter<ESM::Creature>::getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const
|
||||
{
|
||||
const Record<ESM::Creature>& record =
|
||||
static_cast<const Record<ESM::Creature>&> (data.getRecord (RefIdData::LocalIndex (index, mType)));
|
||||
|
||||
return static_cast<int>(record.get().mSpells.mList.size());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,12 +11,17 @@
|
|||
#include <components/esm/loadappa.hpp>
|
||||
#include <components/esm/loadnpc.hpp>
|
||||
#include <components/esm/loadcrea.hpp>
|
||||
#include <components/esm/loadskil.hpp>
|
||||
#include <components/esm/loadclas.hpp>
|
||||
#include <components/esm/loadrace.hpp>
|
||||
|
||||
#include "record.hpp"
|
||||
#include "refiddata.hpp"
|
||||
#include "universalid.hpp"
|
||||
#include "refidadapter.hpp"
|
||||
#include "nestedtablewrapper.hpp"
|
||||
#include "idcollection.hpp"
|
||||
#include "data.hpp"
|
||||
|
||||
namespace CSMWorld
|
||||
{
|
||||
|
@ -802,10 +807,11 @@ namespace CSMWorld
|
|||
class NpcRefIdAdapter : public ActorRefIdAdapter<ESM::NPC>
|
||||
{
|
||||
NpcColumns mColumns;
|
||||
const Data& mData;
|
||||
|
||||
public:
|
||||
|
||||
NpcRefIdAdapter (const NpcColumns& columns);
|
||||
NpcRefIdAdapter (const NpcColumns& columns, const Data& data);
|
||||
|
||||
virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int index)
|
||||
const;
|
||||
|
@ -850,9 +856,11 @@ namespace CSMWorld
|
|||
|
||||
class NpcAttributesRefIdAdapter : public NestedRefIdAdapterBase
|
||||
{
|
||||
const Data& mData;
|
||||
|
||||
public:
|
||||
|
||||
NpcAttributesRefIdAdapter ();
|
||||
NpcAttributesRefIdAdapter (const Data& data);
|
||||
|
||||
virtual void addNestedRow (const RefIdColumn *column,
|
||||
RefIdData& data, int index, int position) const;
|
||||
|
@ -879,9 +887,11 @@ namespace CSMWorld
|
|||
|
||||
class NpcSkillsRefIdAdapter : public NestedRefIdAdapterBase
|
||||
{
|
||||
const Data& mData;
|
||||
|
||||
public:
|
||||
|
||||
NpcSkillsRefIdAdapter ();
|
||||
NpcSkillsRefIdAdapter (const Data& data);
|
||||
|
||||
virtual void addNestedRow (const RefIdColumn *column,
|
||||
RefIdData& data, int index, int position) const;
|
||||
|
@ -908,12 +918,14 @@ namespace CSMWorld
|
|||
|
||||
class NpcMiscRefIdAdapter : public NestedRefIdAdapterBase
|
||||
{
|
||||
const Data& mData;
|
||||
|
||||
NpcMiscRefIdAdapter (const NpcMiscRefIdAdapter&);
|
||||
NpcMiscRefIdAdapter& operator= (const NpcMiscRefIdAdapter&);
|
||||
|
||||
public:
|
||||
|
||||
NpcMiscRefIdAdapter ();
|
||||
NpcMiscRefIdAdapter (const Data& data);
|
||||
virtual ~NpcMiscRefIdAdapter();
|
||||
|
||||
virtual void addNestedRow (const RefIdColumn *column,
|
||||
|
@ -1161,6 +1173,7 @@ namespace CSMWorld
|
|||
class NestedSpellRefIdAdapter : public NestedRefIdAdapterBase
|
||||
{
|
||||
UniversalId::Type mType;
|
||||
const Data& mData;
|
||||
|
||||
// not implemented
|
||||
NestedSpellRefIdAdapter (const NestedSpellRefIdAdapter&);
|
||||
|
@ -1168,45 +1181,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
|
||||
|
@ -1232,55 +1215,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>
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "columns.hpp"
|
||||
#include "nestedtablewrapper.hpp"
|
||||
#include "nestedcoladapterimp.hpp"
|
||||
#include "data.hpp"
|
||||
|
||||
CSMWorld::RefIdColumn::RefIdColumn (int columnId, Display displayType, int flag,
|
||||
bool editable, bool userEditable)
|
||||
|
@ -36,7 +37,7 @@ const CSMWorld::RefIdAdapter& CSMWorld::RefIdCollection::findAdapter (UniversalI
|
|||
return *iter->second;
|
||||
}
|
||||
|
||||
CSMWorld::RefIdCollection::RefIdCollection()
|
||||
CSMWorld::RefIdCollection::RefIdCollection(const CSMWorld::Data& data)
|
||||
{
|
||||
BaseColumns baseColumns;
|
||||
|
||||
|
@ -145,12 +146,21 @@ CSMWorld::RefIdCollection::RefIdCollection()
|
|||
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,
|
||||
|
@ -437,7 +447,7 @@ CSMWorld::RefIdCollection::RefIdCollection()
|
|||
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()));
|
||||
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));
|
||||
|
@ -449,7 +459,7 @@ CSMWorld::RefIdCollection::RefIdCollection()
|
|||
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()));
|
||||
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));
|
||||
|
@ -461,10 +471,11 @@ CSMWorld::RefIdCollection::RefIdCollection()
|
|||
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()));
|
||||
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));
|
||||
new RefIdColumn (Columns::ColumnId_NpcLevel, CSMWorld::ColumnBase::Display_Integer,
|
||||
ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh));
|
||||
mColumns.back().addColumn(
|
||||
new RefIdColumn (Columns::ColumnId_NpcFactionID, CSMWorld::ColumnBase::Display_Integer));
|
||||
mColumns.back().addColumn(
|
||||
|
@ -609,7 +620,7 @@ CSMWorld::RefIdCollection::RefIdCollection()
|
|||
mAdapters.insert (std::make_pair (UniversalId::Type_Miscellaneous,
|
||||
new MiscRefIdAdapter (inventoryColumns, key)));
|
||||
mAdapters.insert (std::make_pair (UniversalId::Type_Npc,
|
||||
new NpcRefIdAdapter (npcColumns)));
|
||||
new NpcRefIdAdapter (npcColumns, data)));
|
||||
mAdapters.insert (std::make_pair (UniversalId::Type_Probe,
|
||||
new ToolRefIdAdapter<ESM::Probe> (UniversalId::Type_Probe, toolsColumns)));
|
||||
mAdapters.insert (std::make_pair (UniversalId::Type_Repair,
|
||||
|
|
|
@ -20,6 +20,7 @@ namespace CSMWorld
|
|||
class RefIdAdapter;
|
||||
struct NestedTableWrapperBase;
|
||||
class NestedRefIdAdapterBase;
|
||||
class Data;
|
||||
|
||||
class RefIdColumn : public NestableColumn
|
||||
{
|
||||
|
@ -56,7 +57,8 @@ namespace CSMWorld
|
|||
|
||||
public:
|
||||
|
||||
RefIdCollection();
|
||||
// race, classes and skills required for NPC autocalc
|
||||
RefIdCollection(const Data& data);
|
||||
|
||||
virtual ~RefIdCollection();
|
||||
|
||||
|
|
44
apps/opencs/model/world/usertype.hpp
Normal file
44
apps/opencs/model/world/usertype.hpp
Normal file
|
@ -0,0 +1,44 @@
|
|||
#ifndef CSM_WORLD_USERTYPE_H
|
||||
#define CSM_WORLD_USERTYPE_H
|
||||
|
||||
#include <QMetaType>
|
||||
#include <QVariant>
|
||||
|
||||
namespace CSMWorld
|
||||
{
|
||||
// Idea from ksimons @stackoverflow
|
||||
class UserInt
|
||||
{
|
||||
public:
|
||||
|
||||
UserInt() : mValue(0) { }
|
||||
UserInt(int value) : mValue(value) { }
|
||||
UserInt(const UserInt &other) { mValue = other.mValue; }
|
||||
~UserInt() { }
|
||||
int value() const { return mValue; }
|
||||
|
||||
private:
|
||||
|
||||
int mValue;
|
||||
};
|
||||
|
||||
class UserFloat
|
||||
{
|
||||
public:
|
||||
|
||||
UserFloat() : mValue(0) { }
|
||||
UserFloat(float value) : mValue(value) { }
|
||||
UserFloat(const UserFloat &other) { mValue = other.mValue; }
|
||||
~UserFloat() { }
|
||||
float value() const { return mValue; }
|
||||
|
||||
private:
|
||||
|
||||
float mValue;
|
||||
};
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(CSMWorld::UserInt)
|
||||
Q_DECLARE_METATYPE(CSMWorld::UserFloat)
|
||||
|
||||
#endif // CSM_WORLD_USERTYPE_H
|
|
@ -66,7 +66,7 @@ void CSVWorld::NotEditableSubDelegate::setEditorData (QWidget* editor, const QMo
|
|||
|
||||
CSMWorld::Columns::ColumnId columnId = static_cast<CSMWorld::Columns::ColumnId> (
|
||||
mTable->getColumnId (index.column()));
|
||||
|
||||
|
||||
if (QVariant::String == v.type())
|
||||
{
|
||||
label->setText(v.toString());
|
||||
|
@ -75,7 +75,7 @@ void CSVWorld::NotEditableSubDelegate::setEditorData (QWidget* editor, const QMo
|
|||
{
|
||||
int data = v.toInt();
|
||||
std::vector<std::string> enumNames (CSMWorld::Columns::getEnums (columnId));
|
||||
|
||||
|
||||
label->setText(QString::fromUtf8(enumNames.at(data).c_str()));
|
||||
}
|
||||
else
|
||||
|
@ -430,16 +430,18 @@ void CSVWorld::EditWidget::remake(int row)
|
|||
static_cast<CSMWorld::UniversalId::Type> (mTable->data (mTable->index (row, typeColumn)).toInt()),
|
||||
mTable->data (mTable->index (row, idColumn)).toString().toUtf8().constData());
|
||||
|
||||
NestedTable* table = new NestedTable(mDocument, id, mNestedModels.back(), this);
|
||||
table->resizeColumnsToContents();
|
||||
|
||||
if(mTable->index(row, i).data().type() == QVariant::UserType)
|
||||
bool editable = mTable->index(row, i).data().type() != QVariant::UserType;
|
||||
NestedTable* table = new NestedTable(mDocument, id, mNestedModels.back(), this, editable);
|
||||
if (!editable)
|
||||
{
|
||||
table->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
table->setEnabled(false);
|
||||
table->setSelectionMode(QAbstractItemView::NoSelection);
|
||||
table->setStyleSheet("QTableView { color: gray; }");
|
||||
table->horizontalHeader()->setStyleSheet("QHeaderView { color: gray; }");
|
||||
}
|
||||
else
|
||||
table->setEditTriggers(QAbstractItemView::SelectedClicked | QAbstractItemView::CurrentChanged);
|
||||
table->resizeColumnsToContents();
|
||||
|
||||
int rows = mTable->rowCount(mTable->index(row, i));
|
||||
int rowHeight = (rows == 0) ? table->horizontalHeader()->height() : table->rowHeight(0);
|
||||
|
@ -610,6 +612,14 @@ CSVWorld::SimpleDialogueSubView::SimpleDialogueSubView (const CSMWorld::Universa
|
|||
mEditWidget = new EditWidget(mainWidget,
|
||||
mTable->getModelIndex(mCurrentId, 0).row(), mTable, mCommandDispatcher, document, false);
|
||||
|
||||
if (id.getType() == CSMWorld::UniversalId::Type_Referenceable)
|
||||
{
|
||||
CSMWorld::IdTree *objectTable = static_cast<CSMWorld::IdTree*>(mTable);
|
||||
|
||||
connect (objectTable, SIGNAL (refreshNpcDialogue (int, const std::string&)),
|
||||
this, SLOT (refreshNpcDialogue (int, const std::string&)));
|
||||
}
|
||||
|
||||
mMainLayout->addWidget(mEditWidget);
|
||||
mEditWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
|
||||
|
||||
|
@ -699,6 +709,29 @@ void CSVWorld::SimpleDialogueSubView::changeCurrentId (const std::string& newId)
|
|||
mCommandDispatcher.setSelection(selection);
|
||||
}
|
||||
|
||||
void CSVWorld::SimpleDialogueSubView::refreshNpcDialogue (int type, const std::string& id)
|
||||
{
|
||||
int typeColumn = mTable->findColumnIndex (CSMWorld::Columns::ColumnId_RecordType);
|
||||
if (CSMWorld::UniversalId::Type_Npc
|
||||
!= mTable->data(mTable->getModelIndex(mCurrentId, typeColumn), Qt::DisplayRole).toInt())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int raceColumn = mTable->findColumnIndex (CSMWorld::Columns::ColumnId_Race);
|
||||
int classColumn = mTable->findColumnIndex (CSMWorld::Columns::ColumnId_Class);
|
||||
|
||||
if ((type == 0/*FIXME*/ && id == "") // skill or gmst changed
|
||||
|| (id == mTable->data(mTable->getModelIndex(mCurrentId, raceColumn),
|
||||
Qt::DisplayRole).toString().toUtf8().constData()) // race
|
||||
|| (id == mTable->data(mTable->getModelIndex(mCurrentId, classColumn),
|
||||
Qt::DisplayRole).toString().toUtf8().constData())) // class
|
||||
{
|
||||
int y = mEditWidget->verticalScrollBar()->value();
|
||||
mEditWidget->remake (mTable->getModelIndex(mCurrentId, 0).row());
|
||||
mEditWidget->verticalScrollBar()->setValue(y);
|
||||
}
|
||||
}
|
||||
|
||||
CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id,
|
||||
CSMDoc::Document& document, const CreatorFactoryBase& creatorFactory, bool sorting)
|
||||
|
@ -774,7 +807,7 @@ CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id,
|
|||
deleteButton->setDisabled (true);
|
||||
}
|
||||
|
||||
getMainLayout().addLayout (buttonsLayout);
|
||||
getMainLayout().addLayout (buttonsLayout);
|
||||
}
|
||||
|
||||
void CSVWorld::DialogueSubView::cloneRequest()
|
||||
|
@ -855,7 +888,6 @@ void CSVWorld::DialogueSubView::nextId ()
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
void CSVWorld::DialogueSubView::showPreview ()
|
||||
{
|
||||
QModelIndex currentIndex (getTable().getModelIndex (getCurrentId(), 0));
|
||||
|
|
|
@ -216,6 +216,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);
|
||||
};
|
||||
|
||||
class DialogueSubView : public SimpleDialogueSubView
|
||||
|
|
|
@ -13,12 +13,13 @@
|
|||
CSVWorld::NestedTable::NestedTable(CSMDoc::Document& document,
|
||||
CSMWorld::UniversalId id,
|
||||
CSMWorld::NestedTableProxyModel* model,
|
||||
QWidget* parent)
|
||||
QWidget* parent,
|
||||
bool editable)
|
||||
: DragRecordTable(document, parent),
|
||||
mAddNewRowAction(0),
|
||||
mRemoveRowAction(0),
|
||||
mModel(model)
|
||||
{
|
||||
mDispatcher = new CSMWorld::CommandDispatcher (document, id, this);
|
||||
|
||||
setSelectionBehavior (QAbstractItemView::SelectRows);
|
||||
setSelectionMode (QAbstractItemView::ExtendedSelection);
|
||||
|
||||
|
@ -31,30 +32,36 @@ CSVWorld::NestedTable::NestedTable(CSMDoc::Document& document,
|
|||
|
||||
int columns = model->columnCount(QModelIndex());
|
||||
|
||||
for(int i = 0 ; i < columns; ++i)
|
||||
{
|
||||
CSMWorld::ColumnBase::Display display = static_cast<CSMWorld::ColumnBase::Display> (
|
||||
model->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt());
|
||||
|
||||
CommandDelegate *delegate = CommandDelegateFactoryCollection::get().makeDelegate(display,
|
||||
mDispatcher,
|
||||
document,
|
||||
this);
|
||||
|
||||
setItemDelegateForColumn(i, delegate);
|
||||
}
|
||||
|
||||
setModel(model);
|
||||
|
||||
mAddNewRowAction = new QAction (tr ("Add new row"), this);
|
||||
setAcceptDrops(true);
|
||||
|
||||
connect(mAddNewRowAction, SIGNAL(triggered()),
|
||||
this, SLOT(addNewRowActionTriggered()));
|
||||
if (editable)
|
||||
{
|
||||
mDispatcher = new CSMWorld::CommandDispatcher (document, id, this);
|
||||
for(int i = 0 ; i < columns; ++i)
|
||||
{
|
||||
CSMWorld::ColumnBase::Display display = static_cast<CSMWorld::ColumnBase::Display> (
|
||||
model->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt());
|
||||
|
||||
mRemoveRowAction = new QAction (tr ("Remove row"), this);
|
||||
CommandDelegate *delegate = CommandDelegateFactoryCollection::get().makeDelegate(display,
|
||||
mDispatcher,
|
||||
document,
|
||||
this);
|
||||
|
||||
connect(mRemoveRowAction, SIGNAL(triggered()),
|
||||
this, SLOT(removeRowActionTriggered()));
|
||||
setItemDelegateForColumn(i, delegate);
|
||||
}
|
||||
|
||||
mAddNewRowAction = new QAction (tr ("Add new row"), this);
|
||||
|
||||
connect(mAddNewRowAction, SIGNAL(triggered()),
|
||||
this, SLOT(addNewRowActionTriggered()));
|
||||
|
||||
mRemoveRowAction = new QAction (tr ("Remove row"), this);
|
||||
|
||||
connect(mRemoveRowAction, SIGNAL(triggered()),
|
||||
this, SLOT(removeRowActionTriggered()));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<CSMWorld::UniversalId> CSVWorld::NestedTable::getDraggedRecords() const
|
||||
|
@ -65,6 +72,9 @@ std::vector<CSMWorld::UniversalId> CSVWorld::NestedTable::getDraggedRecords() co
|
|||
|
||||
void CSVWorld::NestedTable::contextMenuEvent (QContextMenuEvent *event)
|
||||
{
|
||||
if (!mRemoveRowAction || !mAddNewRowAction)
|
||||
return;
|
||||
|
||||
QModelIndexList selectedRows = selectionModel()->selectedRows();
|
||||
|
||||
QMenu menu(this);
|
||||
|
|
|
@ -35,7 +35,8 @@ namespace CSVWorld
|
|||
NestedTable(CSMDoc::Document& document,
|
||||
CSMWorld::UniversalId id,
|
||||
CSMWorld::NestedTableProxyModel* model,
|
||||
QWidget* parent = NULL);
|
||||
QWidget* parent = NULL,
|
||||
bool editable = true);
|
||||
|
||||
virtual std::vector<CSMWorld::UniversalId> getDraggedRecords() const;
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include "../../model/world/commanddispatcher.hpp"
|
||||
|
||||
#include "../widget/coloreditor.hpp"
|
||||
#include "../../model/world/usertype.hpp"
|
||||
#include "../widget/droplineedit.hpp"
|
||||
|
||||
#include "dialoguespinbox.hpp"
|
||||
|
@ -136,8 +137,8 @@ void CSVWorld::CommandDelegate::setModelDataImp (QWidget *editor, QAbstractItemM
|
|||
}
|
||||
else
|
||||
{
|
||||
NastyTableModelHack hack (*model);
|
||||
QStyledItemDelegate::setModelData (editor, &hack, index);
|
||||
NastyTableModelHack hack (*model);
|
||||
QStyledItemDelegate::setModelData (editor, &hack, index);
|
||||
new_ = hack.getData();
|
||||
}
|
||||
|
||||
|
@ -166,7 +167,7 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO
|
|||
const QModelIndex& index) const
|
||||
{
|
||||
CSMWorld::ColumnBase::Display display = getDisplayTypeFromIndex(index);
|
||||
|
||||
|
||||
// This createEditor() method is called implicitly from tables.
|
||||
// For boolean values in tables use the default editor (combobox).
|
||||
// Checkboxes is looking ugly in the table view.
|
||||
|
@ -324,8 +325,15 @@ void CSVWorld::CommandDelegate::setEditorData (QWidget *editor, const QModelInde
|
|||
|
||||
if (!n.isEmpty()) {
|
||||
if (!v.isValid())
|
||||
v = QVariant(editor->property(n).userType(), (const void *)0);
|
||||
editor->setProperty(n, v);
|
||||
editor->setProperty(n, QVariant(editor->property(n).userType(), (const void *)0));
|
||||
else if (v.type() == QVariant::UserType
|
||||
&& QString(v.typeName()) == "CSMWorld::UserFloat" && v.canConvert<CSMWorld::UserFloat>())
|
||||
editor->setProperty(n, QVariant(v.value<CSMWorld::UserFloat>().value()));
|
||||
else if (v.type() == QVariant::UserType
|
||||
&& QString(v.typeName()) == "CSMWorld::UserInt" && v.canConvert<CSMWorld::UserInt>())
|
||||
editor->setProperty(n, QVariant(v.value<CSMWorld::UserInt>().value()));
|
||||
else
|
||||
editor->setProperty(n, v);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ add_openmw_dir (mwworld
|
|||
cells localscripts customdata weather inventorystore ptr actionopen actionread
|
||||
actionequip timestamp actionalchemy cellstore actionapply actioneat
|
||||
esmstore store recordcmp fallback actionrepair actionsoulgem livecellref actiondoor
|
||||
contentloader esmloader actiontrap cellreflist projectilemanager cellref
|
||||
contentloader esmloader actiontrap cellreflist projectilemanager cellref mwstore
|
||||
)
|
||||
|
||||
add_openmw_dir (mwclass
|
||||
|
@ -78,7 +78,7 @@ add_openmw_dir (mwmechanics
|
|||
mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects
|
||||
drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor
|
||||
aiescort aiactivate aicombat repair enchanting pathfinding pathgrid security spellsuccess spellcasting
|
||||
disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning
|
||||
disease pickpocket levelledlist combat steering obstacle difficultyscaling aicombataction actor summoning
|
||||
)
|
||||
|
||||
add_openmw_dir (mwstate
|
||||
|
|
|
@ -11,6 +11,10 @@
|
|||
#include <components/esm/loadnpc.hpp>
|
||||
#include <components/esm/npcstate.hpp>
|
||||
|
||||
#include <components/autocalc/autocalc.hpp>
|
||||
#include <components/autocalc/autocalcspell.hpp>
|
||||
#include <components/autocalc/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,44 @@ 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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void autoCalculateAttributes (const ESM::NPC* npc, MWMechanics::CreatureStats& creatureStats)
|
||||
class Stats : public AutoCalc::StatsBase
|
||||
{
|
||||
MWMechanics::NpcStats& mNpcStats;
|
||||
|
||||
public:
|
||||
|
||||
Stats(MWMechanics::NpcStats& npcStats) : mNpcStats(npcStats) {}
|
||||
|
||||
virtual unsigned char getBaseAttribute(int index) const { return mNpcStats.getAttribute(index).getBase(); }
|
||||
|
||||
virtual void setAttribute(int index, unsigned char value) { mNpcStats.setAttribute(index, value); }
|
||||
|
||||
virtual void addSpell(const std::string& id) { mNpcStats.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::NpcStats& npcStats)
|
||||
{
|
||||
// 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 = npcStats.getLevel();
|
||||
|
||||
// skill bonus
|
||||
for (int attribute=0; attribute < ESM::Attribute::Length; ++attribute)
|
||||
{
|
||||
float modifierSum = 0;
|
||||
Stats stats(npcStats);
|
||||
|
||||
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;
|
||||
AutoCalc::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));
|
||||
npcStats.setHealth(AutoCalc::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 +109,13 @@ namespace
|
|||
|
||||
const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(npc->mRace);
|
||||
|
||||
Stats stats(npcStats);
|
||||
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
AutoCalc::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);
|
||||
AutoCalc::autoCalculateSpells(race, stats, &store);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -392,7 +260,7 @@ namespace MWClass
|
|||
// store
|
||||
ptr.getRefData().setCustomData (data.release());
|
||||
|
||||
getInventoryStore(ptr).autoEquip(ptr);
|
||||
getInventoryStore(ptr).autoEquip(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,8 +6,11 @@
|
|||
|
||||
#include <components/esm/stolenitems.hpp>
|
||||
|
||||
#include <components/autocalc/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 (AutoCalc::calcAutoCastChance(spell, skills, attributes, -1, &store) < fAutoPCSpellChance)
|
||||
continue;
|
||||
|
||||
if (!attrSkillCheck(spell, skills, attributes))
|
||||
if (!AutoCalc::attrSkillCheck(spell, skills, attributes, &store))
|
||||
continue;
|
||||
|
||||
selectedSpells.push_back(spell->mId);
|
||||
|
|
37
apps/openmw/mwworld/mwstore.cpp
Normal file
37
apps/openmw/mwworld/mwstore.cpp
Normal file
|
@ -0,0 +1,37 @@
|
|||
#include "mwstore.hpp"
|
||||
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwbase/environment.hpp"
|
||||
|
||||
#include "esmstore.hpp"
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
MWStore::MWStore()
|
||||
: mGmst(MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()),
|
||||
mSpells(MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>())
|
||||
{ }
|
||||
|
||||
MWStore::~MWStore()
|
||||
{ }
|
||||
|
||||
int MWStore::findGmstInt(const std::string& name) const { return mGmst.find(name)->getInt(); }
|
||||
|
||||
float MWStore::findGmstFloat(const std::string& name) const { return mGmst.find(name)->getFloat(); }
|
||||
|
||||
const ESM::Skill *MWStore::findSkill(int index) const
|
||||
{
|
||||
return MWBase::Environment::get().getWorld()->getStore().get<ESM::Skill>().find(index);
|
||||
}
|
||||
|
||||
const ESM::MagicEffect* MWStore::findMagicEffect(int id) const
|
||||
{
|
||||
return MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(id);
|
||||
}
|
||||
|
||||
void MWStore::getSpells(std::vector<ESM::Spell*>& spells)
|
||||
{
|
||||
for (Store<ESM::Spell>::iterator iter = mSpells.begin(); iter != mSpells.end(); ++iter)
|
||||
spells.push_back(const_cast<ESM::Spell*>(&*iter));
|
||||
}
|
||||
}
|
34
apps/openmw/mwworld/mwstore.hpp
Normal file
34
apps/openmw/mwworld/mwstore.hpp
Normal file
|
@ -0,0 +1,34 @@
|
|||
#ifndef GAME_MWWORLD_MWSTORE_H
|
||||
#define GAME_MWWORLD_MWSTORE_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <components/autocalc/store.hpp>
|
||||
|
||||
#include "store.hpp"
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
class MWStore : public AutoCalc::StoreCommon
|
||||
{
|
||||
const MWWorld::Store<ESM::GameSetting>& mGmst;
|
||||
const MWWorld::Store<ESM::Spell> &mSpells;
|
||||
|
||||
public:
|
||||
|
||||
MWStore();
|
||||
~MWStore();
|
||||
|
||||
virtual int findGmstInt(const std::string& name) const;
|
||||
|
||||
virtual float findGmstFloat(const std::string& name) const;
|
||||
|
||||
virtual const ESM::Skill *findSkill(int index) const;
|
||||
|
||||
virtual const ESM::MagicEffect* findMagicEffect(int id) const;
|
||||
|
||||
virtual void getSpells(std::vector<ESM::Spell*>& spells);
|
||||
};
|
||||
}
|
||||
|
||||
#endif // GAME_MWWORLD_MWSTORE_H
|
|
@ -119,6 +119,10 @@ add_component_dir (fontloader
|
|||
fontloader
|
||||
)
|
||||
|
||||
add_component_dir (autocalc
|
||||
autocalc autocalcspell
|
||||
)
|
||||
|
||||
add_component_dir (version
|
||||
version
|
||||
)
|
||||
|
|
208
components/autocalc/autocalc.cpp
Normal file
208
components/autocalc/autocalc.cpp
Normal file
|
@ -0,0 +1,208 @@
|
|||
#include "autocalc.hpp"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <components/esm/attr.hpp>
|
||||
#include <components/esm/loadmgef.hpp>
|
||||
#include <components/esm/loadskil.hpp>
|
||||
#include <components/esm/loadrace.hpp>
|
||||
#include <components/esm/loadclas.hpp>
|
||||
#include <components/esm/loadnpc.hpp>
|
||||
|
||||
#include "autocalcspell.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
int is_even(double d)
|
||||
{
|
||||
double int_part;
|
||||
|
||||
modf(d / 2.0, &int_part);
|
||||
return 2.0 * int_part == d;
|
||||
}
|
||||
|
||||
int round_ieee_754(double d)
|
||||
{
|
||||
double i = floor(d);
|
||||
d -= i;
|
||||
|
||||
if(d < 0.5)
|
||||
return static_cast<int>(i);
|
||||
if(d > 0.5)
|
||||
return static_cast<int>(i) + 1;
|
||||
if(is_even(i))
|
||||
return static_cast<int>(i);
|
||||
return static_cast<int>(i) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
namespace AutoCalc
|
||||
{
|
||||
void autoCalcAttributesImpl (const ESM::NPC* npc,
|
||||
const ESM::Race *race, const ESM::Class *class_, int level, StatsBase& stats, StoreCommon *store)
|
||||
{
|
||||
// race bonus
|
||||
bool male = (npc->mFlags & ESM::NPC::Female) == 0;
|
||||
|
||||
for (int i=0; i<ESM::Attribute::Length; ++i)
|
||||
{
|
||||
const ESM::Race::MaleFemale& attribute = race->mData.mAttributeValues[i];
|
||||
stats.setAttribute(i, male ? attribute.mMale : attribute.mFemale);
|
||||
}
|
||||
|
||||
// class bonus
|
||||
for (int i=0; i<2; ++i)
|
||||
{
|
||||
int attribute = class_->mData.mAttribute[i];
|
||||
if (attribute>=0 && attribute<ESM::Attribute::Length)
|
||||
stats.setAttribute(attribute, stats.getBaseAttribute(attribute) + 10);
|
||||
// else log an error?
|
||||
}
|
||||
|
||||
// skill bonus
|
||||
for (int attribute=0; attribute < ESM::Attribute::Length; ++attribute)
|
||||
{
|
||||
float modifierSum = 0;
|
||||
|
||||
for (int j=0; j<ESM::Skill::Length; ++j)
|
||||
{
|
||||
const ESM::Skill* skill = store->findSkill(j);
|
||||
|
||||
if (skill->mData.mAttribute != attribute)
|
||||
continue;
|
||||
|
||||
// is this a minor or major skill?
|
||||
float add=0.2f;
|
||||
for (int k=0; k<5; ++k)
|
||||
{
|
||||
if (class_->mData.mSkills[k][0] == j)
|
||||
add=0.5;
|
||||
}
|
||||
for (int k=0; k<5; ++k)
|
||||
{
|
||||
if (class_->mData.mSkills[k][1] == j)
|
||||
add=1.0;
|
||||
}
|
||||
modifierSum += add;
|
||||
}
|
||||
stats.setAttribute(attribute,
|
||||
std::min(round_ieee_754(stats.getBaseAttribute(attribute) + (level-1) * modifierSum), 100) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief autoCalculateSkills
|
||||
*
|
||||
* Skills are calculated with following formulae ( http://www.uesp.net/wiki/Morrowind:NPCs#Skills ):
|
||||
*
|
||||
* Skills: (Level - 1) × (Majority Multiplier + Specialization Multiplier)
|
||||
*
|
||||
* The Majority Multiplier is 1.0 for a Major or Minor Skill, or 0.1 for a Miscellaneous Skill.
|
||||
*
|
||||
* The Specialization Multiplier is 0.5 for a Skill in the same Specialization as the class,
|
||||
* zero for other Skills.
|
||||
*
|
||||
* and by adding class, race, specialization bonus.
|
||||
*/
|
||||
void autoCalcSkillsImpl (const ESM::NPC* npc,
|
||||
const ESM::Race *race, const ESM::Class *class_, int level, StatsBase& stats, StoreCommon *store)
|
||||
{
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
int bonus = (i==0) ? 10 : 25;
|
||||
|
||||
for (int i2 = 0; i2 < 5; ++i2)
|
||||
{
|
||||
int index = class_->mData.mSkills[i2][i];
|
||||
if (index >= 0 && index < ESM::Skill::Length)
|
||||
{
|
||||
stats.setBaseSkill (index, bonus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int skillIndex = 0; skillIndex < ESM::Skill::Length; ++skillIndex)
|
||||
{
|
||||
float majorMultiplier = 0.1f;
|
||||
float specMultiplier = 0.0f;
|
||||
|
||||
int raceBonus = 0;
|
||||
int specBonus = 0;
|
||||
|
||||
for (int raceSkillIndex = 0; raceSkillIndex < 7; ++raceSkillIndex)
|
||||
{
|
||||
if (race->mData.mBonus[raceSkillIndex].mSkill == skillIndex)
|
||||
{
|
||||
raceBonus = race->mData.mBonus[raceSkillIndex].mBonus;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int k = 0; k < 5; ++k)
|
||||
{
|
||||
// is this a minor or major skill?
|
||||
if ((class_->mData.mSkills[k][0] == skillIndex) || (class_->mData.mSkills[k][1] == skillIndex))
|
||||
{
|
||||
majorMultiplier = 1.0f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// is this skill in the same Specialization as the class?
|
||||
const ESM::Skill* skill = store->findSkill(skillIndex);
|
||||
if (skill->mData.mSpecialization == class_->mData.mSpecialization)
|
||||
{
|
||||
specMultiplier = 0.5f;
|
||||
specBonus = 5;
|
||||
}
|
||||
|
||||
stats.setBaseSkill(skillIndex,
|
||||
std::min(
|
||||
round_ieee_754(
|
||||
stats.getBaseSkill(skillIndex)
|
||||
+ 5
|
||||
+ raceBonus
|
||||
+ specBonus
|
||||
+(int(level)-1) * (majorMultiplier + specMultiplier)), 100)); // Must gracefully handle level 0
|
||||
}
|
||||
}
|
||||
|
||||
unsigned short autoCalculateHealth(int level, const ESM::Class *class_, const StatsBase& stats)
|
||||
{
|
||||
// initial health
|
||||
int strength = stats.getBaseAttribute(ESM::Attribute::Strength);
|
||||
int endurance = stats.getBaseAttribute(ESM::Attribute::Endurance);
|
||||
|
||||
int multiplier = 3;
|
||||
|
||||
if (class_->mData.mSpecialization == ESM::Class::Combat)
|
||||
multiplier += 2;
|
||||
else if (class_->mData.mSpecialization == ESM::Class::Stealth)
|
||||
multiplier += 1;
|
||||
|
||||
if (class_->mData.mAttribute[0] == ESM::Attribute::Endurance
|
||||
|| class_->mData.mAttribute[1] == ESM::Attribute::Endurance)
|
||||
multiplier += 1;
|
||||
|
||||
return static_cast<unsigned short>(floor(0.5f * (strength + endurance)) + multiplier * (level-1));
|
||||
}
|
||||
|
||||
void autoCalculateSpells(const ESM::Race *race, StatsBase& stats, StoreCommon *store)
|
||||
{
|
||||
int skills[ESM::Skill::Length];
|
||||
for (int i=0; i<ESM::Skill::Length; ++i)
|
||||
skills[i] = stats.getBaseSkill(i);
|
||||
|
||||
int attributes[ESM::Attribute::Length];
|
||||
for (int i=0; i<ESM::Attribute::Length; ++i)
|
||||
attributes[i] = stats.getBaseAttribute(i);
|
||||
|
||||
std::vector<std::string> spells = autoCalcNpcSpells(skills, attributes, race, store);
|
||||
for (std::vector<std::string>::iterator it = spells.begin(); it != spells.end(); ++it)
|
||||
stats.addSpell(*it);
|
||||
}
|
||||
|
||||
StatsBase::StatsBase() {}
|
||||
|
||||
StatsBase::~StatsBase() {}
|
||||
}
|
47
components/autocalc/autocalc.hpp
Normal file
47
components/autocalc/autocalc.hpp
Normal file
|
@ -0,0 +1,47 @@
|
|||
#ifndef COMPONENTS_AUTOCALC_AUTOCALC_H
|
||||
#define COMPONENTS_AUTOCALC_AUTOCALC_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "store.hpp"
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
struct NPC;
|
||||
struct Race;
|
||||
struct Class;
|
||||
}
|
||||
|
||||
namespace AutoCalc
|
||||
{
|
||||
// wrapper class for sharing the autocalc code between OpenMW and OpenCS
|
||||
class StatsBase
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
StatsBase();
|
||||
virtual ~StatsBase();
|
||||
|
||||
virtual unsigned char getBaseAttribute(int index) const = 0;
|
||||
|
||||
virtual void setAttribute(int index, unsigned char value) = 0;
|
||||
|
||||
virtual void addSpell(const std::string& id) = 0;
|
||||
|
||||
virtual unsigned char getBaseSkill(int index) const = 0;
|
||||
|
||||
virtual void setBaseSkill(int index, unsigned char value) = 0;
|
||||
};
|
||||
|
||||
void autoCalcAttributesImpl (const ESM::NPC* npc,
|
||||
const ESM::Race *race, const ESM::Class *class_, int level, StatsBase& stats, StoreCommon *store);
|
||||
|
||||
void autoCalcSkillsImpl (const ESM::NPC* npc,
|
||||
const ESM::Race *race, const ESM::Class *class_, int level, StatsBase& stats, StoreCommon *store);
|
||||
|
||||
unsigned short autoCalculateHealth(int level, const ESM::Class *class_, const StatsBase& stats);
|
||||
|
||||
void autoCalculateSpells(const ESM::Race *race, StatsBase& stats, StoreCommon *store);
|
||||
}
|
||||
#endif // COMPONENTS_AUTOCALC_AUTOCALC_H
|
249
components/autocalc/autocalcspell.cpp
Normal file
249
components/autocalc/autocalcspell.cpp
Normal file
|
@ -0,0 +1,249 @@
|
|||
#include "autocalcspell.hpp"
|
||||
|
||||
#include <climits>
|
||||
#include <cfloat>
|
||||
#include <set>
|
||||
|
||||
#include <components/esm/attr.hpp>
|
||||
#include <components/esm/loadmgef.hpp>
|
||||
#include <components/esm/loadrace.hpp>
|
||||
#include <components/esm/loadclas.hpp>
|
||||
#include <components/esm/loadspel.hpp>
|
||||
#include <components/esm/loadnpc.hpp>
|
||||
|
||||
#include "autocalc.hpp"
|
||||
|
||||
namespace AutoCalc
|
||||
{
|
||||
|
||||
struct SchoolCaps
|
||||
{
|
||||
int mCount;
|
||||
int mLimit;
|
||||
bool mReachedLimit;
|
||||
int mMinCost;
|
||||
std::string mWeakestSpell;
|
||||
};
|
||||
|
||||
std::vector<std::string> autoCalcNpcSpells(const int *actorSkills,
|
||||
const int *actorAttributes, const ESM::Race* race, StoreCommon *store)
|
||||
{
|
||||
static const float fNPCbaseMagickaMult = store->findGmstFloat("fNPCbaseMagickaMult");
|
||||
float baseMagicka = fNPCbaseMagickaMult * actorAttributes[ESM::Attribute::Intelligence];
|
||||
|
||||
static const std::string schools[] = {
|
||||
"alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
|
||||
};
|
||||
static int iAutoSpellSchoolMax[6];
|
||||
static bool init = false;
|
||||
if (!init)
|
||||
{
|
||||
for (int i=0; i<6; ++i)
|
||||
{
|
||||
const std::string& gmstName = "iAutoSpell" + schools[i] + "Max";
|
||||
iAutoSpellSchoolMax[i] = store->findGmstInt(gmstName);
|
||||
}
|
||||
init = true;
|
||||
}
|
||||
|
||||
std::map<int, SchoolCaps> schoolCaps;
|
||||
for (int i=0; i<6; ++i)
|
||||
{
|
||||
SchoolCaps caps;
|
||||
caps.mCount = 0;
|
||||
caps.mLimit = iAutoSpellSchoolMax[i];
|
||||
caps.mReachedLimit = iAutoSpellSchoolMax[i] <= 0;
|
||||
caps.mMinCost = INT_MAX;
|
||||
caps.mWeakestSpell.clear();
|
||||
schoolCaps[i] = caps;
|
||||
}
|
||||
|
||||
std::vector<std::string> selectedSpells;
|
||||
std::vector<ESM::Spell*> spells;
|
||||
store->getSpells(spells);
|
||||
|
||||
// Note: the algorithm heavily depends on the traversal order of the spells. For vanilla-compatible results the
|
||||
// Store must preserve the record ordering as it was in the content files.
|
||||
for (std::vector<ESM::Spell*>::const_iterator iter = spells.begin(); iter != spells.end(); ++iter)
|
||||
{
|
||||
ESM::Spell* spell = *iter;
|
||||
|
||||
if (spell->mData.mType != ESM::Spell::ST_Spell)
|
||||
continue;
|
||||
if (!(spell->mData.mFlags & ESM::Spell::F_Autocalc))
|
||||
continue;
|
||||
static const int iAutoSpellTimesCanCast = store->findGmstInt("iAutoSpellTimesCanCast");
|
||||
if (baseMagicka < iAutoSpellTimesCanCast * spell->mData.mCost)
|
||||
continue;
|
||||
|
||||
if (race && race->mPowers.exists(spell->mId))
|
||||
continue;
|
||||
|
||||
if (!attrSkillCheck(spell, actorSkills, actorAttributes, store))
|
||||
continue;
|
||||
|
||||
int school;
|
||||
float skillTerm;
|
||||
calcWeakestSchool(spell, actorSkills, school, skillTerm, store);
|
||||
assert(school >= 0 && school < 6);
|
||||
SchoolCaps& cap = schoolCaps[school];
|
||||
|
||||
if (cap.mReachedLimit && spell->mData.mCost <= cap.mMinCost)
|
||||
continue;
|
||||
|
||||
static const float fAutoSpellChance = store->findGmstFloat("fAutoSpellChance");
|
||||
if (calcAutoCastChance(spell, actorSkills, actorAttributes, school, store) < fAutoSpellChance)
|
||||
continue;
|
||||
|
||||
selectedSpells.push_back(spell->mId);
|
||||
|
||||
if (cap.mReachedLimit)
|
||||
{
|
||||
std::vector<std::string>::iterator found = std::find(selectedSpells.begin(), selectedSpells.end(), cap.mWeakestSpell);
|
||||
if (found != selectedSpells.end())
|
||||
selectedSpells.erase(found);
|
||||
|
||||
cap.mMinCost = INT_MAX;
|
||||
for (std::vector<std::string>::iterator weakIt = selectedSpells.begin(); weakIt != selectedSpells.end(); ++weakIt)
|
||||
{
|
||||
std::vector<ESM::Spell*>::const_iterator it = spells.begin();
|
||||
for (; it != spells.end(); ++it)
|
||||
{
|
||||
if ((*it)->mId == *weakIt)
|
||||
break;
|
||||
}
|
||||
|
||||
if (it == spells.end())
|
||||
continue;
|
||||
|
||||
const ESM::Spell* testSpell = *it;
|
||||
|
||||
//int testSchool;
|
||||
//float dummySkillTerm;
|
||||
//calcWeakestSchool(testSpell, actorSkills, testSchool, dummySkillTerm);
|
||||
|
||||
// Note: if there are multiple spells with the same cost, we pick the first one we found.
|
||||
// So the algorithm depends on the iteration order of the outer loop.
|
||||
if (
|
||||
// There is a huge bug here. It is not checked that weakestSpell is of the correct school.
|
||||
// As result multiple SchoolCaps could have the same mWeakestSpell. Erasing the weakest spell would then fail if another school
|
||||
// already erased it, and so the number of spells would often exceed the sum of limits.
|
||||
// This bug cannot be fixed without significantly changing the results of the spell autocalc, which will not have been playtested.
|
||||
//testSchool == school &&
|
||||
testSpell->mData.mCost < cap.mMinCost)
|
||||
{
|
||||
cap.mMinCost = testSpell->mData.mCost;
|
||||
cap.mWeakestSpell = testSpell->mId;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cap.mCount += 1;
|
||||
if (cap.mCount == cap.mLimit)
|
||||
cap.mReachedLimit = true;
|
||||
|
||||
if (spell->mData.mCost < cap.mMinCost)
|
||||
{
|
||||
cap.mWeakestSpell = spell->mId;
|
||||
cap.mMinCost = spell->mData.mCost;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return selectedSpells;
|
||||
}
|
||||
|
||||
bool attrSkillCheck (const ESM::Spell* spell,
|
||||
const int* actorSkills, const int* actorAttributes, StoreCommon *store)
|
||||
{
|
||||
const std::vector<ESM::ENAMstruct>& effects = spell->mEffects.mList;
|
||||
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = effects.begin(); effectIt != effects.end(); ++effectIt)
|
||||
{
|
||||
const ESM::MagicEffect* magicEffect = store->findMagicEffect(effectIt->mEffectID);
|
||||
static const int iAutoSpellAttSkillMin = store->findGmstInt("iAutoSpellAttSkillMin");
|
||||
|
||||
if ((magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill))
|
||||
{
|
||||
assert (effectIt->mSkill >= 0 && effectIt->mSkill < ESM::Skill::Length);
|
||||
if (actorSkills[effectIt->mSkill] < iAutoSpellAttSkillMin)
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute))
|
||||
{
|
||||
assert (effectIt->mAttribute >= 0 && effectIt->mAttribute < ESM::Attribute::Length);
|
||||
if (actorAttributes[effectIt->mAttribute] < iAutoSpellAttSkillMin)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ESM::Skill::SkillEnum mapSchoolToSkill(int school)
|
||||
{
|
||||
std::map<int, ESM::Skill::SkillEnum> schoolSkillMap; // maps spell school to skill id
|
||||
schoolSkillMap[0] = ESM::Skill::Alteration;
|
||||
schoolSkillMap[1] = ESM::Skill::Conjuration;
|
||||
schoolSkillMap[3] = ESM::Skill::Illusion;
|
||||
schoolSkillMap[2] = ESM::Skill::Destruction;
|
||||
schoolSkillMap[4] = ESM::Skill::Mysticism;
|
||||
schoolSkillMap[5] = ESM::Skill::Restoration;
|
||||
assert(schoolSkillMap.find(school) != schoolSkillMap.end());
|
||||
return schoolSkillMap[school];
|
||||
}
|
||||
|
||||
void calcWeakestSchool (const ESM::Spell* spell,
|
||||
const int* actorSkills, int& effectiveSchool, float& skillTerm, StoreCommon *store)
|
||||
{
|
||||
float minChance = FLT_MAX;
|
||||
|
||||
const ESM::EffectList& effects = spell->mEffects;
|
||||
for (std::vector<ESM::ENAMstruct>::const_iterator it = effects.mList.begin(); it != effects.mList.end(); ++it)
|
||||
{
|
||||
const ESM::ENAMstruct& effect = *it;
|
||||
float x = static_cast<float>(effect.mDuration);
|
||||
|
||||
const ESM::MagicEffect* magicEffect = store->findMagicEffect(effect.mEffectID);
|
||||
if (!(magicEffect->mData.mFlags & ESM::MagicEffect::UncappedDamage))
|
||||
x = std::max(1.f, x);
|
||||
|
||||
x *= 0.1f * magicEffect->mData.mBaseCost;
|
||||
x *= 0.5f * (effect.mMagnMin + effect.mMagnMax);
|
||||
x += effect.mArea * 0.05f * magicEffect->mData.mBaseCost;
|
||||
if (effect.mRange == ESM::RT_Target)
|
||||
x *= 1.5f;
|
||||
|
||||
static const float fEffectCostMult = store->findGmstFloat("fEffectCostMult");
|
||||
x *= fEffectCostMult;
|
||||
|
||||
float s = 2.f * actorSkills[mapSchoolToSkill(magicEffect->mData.mSchool)];
|
||||
if (s - x < minChance)
|
||||
{
|
||||
minChance = s - x;
|
||||
effectiveSchool = magicEffect->mData.mSchool;
|
||||
skillTerm = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float calcAutoCastChance(const ESM::Spell *spell,
|
||||
const int *actorSkills, const int *actorAttributes, int effectiveSchool, StoreCommon *store)
|
||||
{
|
||||
if (spell->mData.mType != ESM::Spell::ST_Spell)
|
||||
return 100.f;
|
||||
|
||||
if (spell->mData.mFlags & ESM::Spell::F_Always)
|
||||
return 100.f;
|
||||
|
||||
float skillTerm = 0;
|
||||
if (effectiveSchool != -1)
|
||||
skillTerm = 2.f * actorSkills[mapSchoolToSkill(effectiveSchool)];
|
||||
else
|
||||
calcWeakestSchool(spell, actorSkills, effectiveSchool, skillTerm, store); // Note effectiveSchool is unused after this
|
||||
|
||||
float castChance = skillTerm - spell->mData.mCost + 0.2f * actorAttributes[ESM::Attribute::Willpower] + 0.1f * actorAttributes[ESM::Attribute::Luck];
|
||||
return castChance;
|
||||
}
|
||||
}
|
39
components/autocalc/autocalcspell.hpp
Normal file
39
components/autocalc/autocalcspell.hpp
Normal file
|
@ -0,0 +1,39 @@
|
|||
#ifndef COMPONENTS_AUTOCALC_AUTOCALCSPELL_H
|
||||
#define COMPONENTS_AUTOCALC_AUTOCALCSPELL_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <components/esm/loadskil.hpp>
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
struct Spell;
|
||||
struct Race;
|
||||
}
|
||||
|
||||
namespace AutoCalc
|
||||
{
|
||||
|
||||
class StoreCommon;
|
||||
|
||||
/// Contains algorithm for calculating an NPC's spells based on stats
|
||||
|
||||
std::vector<std::string> autoCalcNpcSpells(const int* actorSkills,
|
||||
const int* actorAttributes, const ESM::Race* race, StoreCommon *store);
|
||||
|
||||
// Helpers
|
||||
|
||||
bool attrSkillCheck (const ESM::Spell* spell,
|
||||
const int* actorSkills, const int* actorAttributes, StoreCommon *store);
|
||||
|
||||
ESM::Skill::SkillEnum mapSchoolToSkill(int school);
|
||||
|
||||
void calcWeakestSchool(const ESM::Spell* spell,
|
||||
const int* actorSkills, int& effectiveSchool, float& skillTerm, StoreCommon *store);
|
||||
|
||||
float calcAutoCastChance(const ESM::Spell* spell,
|
||||
const int* actorSkills, const int* actorAttributes, int effectiveSchool, StoreCommon *store);
|
||||
|
||||
}
|
||||
|
||||
#endif // COMPONENTS_AUTOCALC_AUTOCALCSPELL_H
|
35
components/autocalc/store.hpp
Normal file
35
components/autocalc/store.hpp
Normal file
|
@ -0,0 +1,35 @@
|
|||
#ifndef COMPONENTS_AUTOCALC_STORE_H
|
||||
#define COMPONENTS_AUTOCALC_STORE_H
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
struct Spell;
|
||||
struct Skill;
|
||||
struct MagicEffect;
|
||||
}
|
||||
|
||||
namespace AutoCalc
|
||||
{
|
||||
// interface class for sharing the autocalc component between OpenMW and OpenCS
|
||||
class StoreCommon
|
||||
{
|
||||
|
||||
public:
|
||||
StoreCommon() {}
|
||||
virtual ~StoreCommon() {}
|
||||
|
||||
virtual int findGmstInt(const std::string& gmst) const = 0;
|
||||
|
||||
virtual float findGmstFloat(const std::string& gmst) const = 0;
|
||||
|
||||
virtual const ESM::Skill *findSkill(int index) const = 0;
|
||||
|
||||
virtual const ESM::MagicEffect* findMagicEffect(int id) const = 0;
|
||||
|
||||
virtual void getSpells(std::vector<ESM::Spell*>& spells) = 0;
|
||||
};
|
||||
}
|
||||
#endif // COMPONENTS_AUTOCALC_STORE_H
|
Loading…
Reference in a new issue