diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 488e3c65f5..a305d90d93 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -35,7 +35,7 @@ opencs_units (model/tools ) opencs_units_noqt (model/tools - stage verifier mandatoryid + stage verifier mandatoryid skillcheck ) diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 47cf1f6642..deafe8eb84 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -198,6 +198,16 @@ void CSMDoc::Document::createBase() } /// \todo add GMSTs + + for (int i=0; i<27; ++i) + { + ESM::Skill record; + record.mIndex = i; + record.mId = ESM::Skill::getIndexToId (record.mIndex); + record.blank(); + + getData().getSkills().add (record); + } } CSMDoc::Document::Document (const std::vector& files, bool new_) diff --git a/apps/opencs/model/tools/skillcheck.cpp b/apps/opencs/model/tools/skillcheck.cpp new file mode 100644 index 0000000000..897aeab473 --- /dev/null +++ b/apps/opencs/model/tools/skillcheck.cpp @@ -0,0 +1,37 @@ + +#include "skillcheck.hpp" + +#include + +#include + +#include "../world/universalid.hpp" + +CSMTools::SkillCheckStage::SkillCheckStage (const CSMWorld::IdCollection& skills) +: mSkills (skills) +{} + +int CSMTools::SkillCheckStage::setup() +{ + return mSkills.getSize(); +} + +void CSMTools::SkillCheckStage::perform (int stage, std::vector& messages) +{ + const ESM::Skill& skill = mSkills.getRecord (stage).get(); + + CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Skill, skill.mId); + + for (int i=0; i<4; ++i) + if (skill.mData.mUseValue[i]<0) + { + std::ostringstream stream; + + stream << id.toString() << "|Use value #" << i << " of " << skill.mId << " is negative"; + + messages.push_back (stream.str()); + } + + if (skill.mDescription.empty()) + messages.push_back (id.toString() + "|" + skill.mId + " has an empty description"); +} \ No newline at end of file diff --git a/apps/opencs/model/tools/skillcheck.hpp b/apps/opencs/model/tools/skillcheck.hpp new file mode 100644 index 0000000000..30a3f01cad --- /dev/null +++ b/apps/opencs/model/tools/skillcheck.hpp @@ -0,0 +1,29 @@ +#ifndef CSM_TOOLS_SKILLCHECK_H +#define CSM_TOOLS_SKILLCHECK_H + +#include + +#include "../world/idcollection.hpp" + +#include "stage.hpp" + +namespace CSMTools +{ + /// \brief VerifyStage: make sure that skill records are internally consistent + class SkillCheckStage : public Stage + { + const CSMWorld::IdCollection& mSkills; + + public: + + SkillCheckStage (const CSMWorld::IdCollection& skills); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, std::vector& messages); + ///< Messages resulting from this tage will be appended to \a messages. + }; +} + +#endif diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp index 8dd1c0fe6f..33cc3cc61b 100644 --- a/apps/opencs/model/tools/tools.cpp +++ b/apps/opencs/model/tools/tools.cpp @@ -12,6 +12,7 @@ #include "reportmodel.hpp" #include "mandatoryid.hpp" +#include "skillcheck.hpp" CSMTools::Operation *CSMTools::Tools::get (int type) { @@ -51,6 +52,8 @@ CSMTools::Verifier *CSMTools::Tools::getVerifier() mVerifier->appendStage (new MandatoryIdStage (mData.getGlobals(), CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Globals), mandatoryIds)); + + mVerifier->appendStage (new SkillCheckStage (mData.getSkills())); } return mVerifier; diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp index c44abda2b2..b5863f8e4e 100644 --- a/apps/opencs/model/world/columnbase.hpp +++ b/apps/opencs/model/world/columnbase.hpp @@ -31,7 +31,9 @@ namespace CSMWorld Display_Float, Display_Var, Display_GmstVarType, - Display_GlobalVarType + Display_GlobalVarType, + Display_Specialisation, + Display_Attribute }; std::string mTitle; diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index 1492e96b7d..7764f58709 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -1,6 +1,8 @@ #ifndef CSM_WOLRD_COLUMNS_H #define CSM_WOLRD_COLUMNS_H +#include + #include "columnbase.hpp" namespace CSMWorld @@ -177,6 +179,112 @@ namespace CSMWorld return true; } }; + + template + struct DescriptionColumn : public Column + { + DescriptionColumn() : Column ("Description", ColumnBase::Display_String) {} + + virtual QVariant get (const Record& record) const + { + return QString::fromUtf8 (record.get().mDescription.c_str()); + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + + record2.mDescription = data.toString().toUtf8().constData(); + + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + }; + + template + struct SpecialisationColumn : public Column + { + SpecialisationColumn() : Column ("Specialisation", ColumnBase::Display_Specialisation) {} + + virtual QVariant get (const Record& record) const + { + return record.get().mData.mSpecialization; + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + + record2.mData.mSpecialization = data.toInt(); + + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + }; + + template + struct UseValueColumn : public Column + { + int mIndex; + + UseValueColumn (int index) + : Column ("Use value #" + boost::lexical_cast (index), + ColumnBase::Display_Float), mIndex (index) + {} + + virtual QVariant get (const Record& record) const + { + return record.get().mData.mUseValue[mIndex]; + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + + record2.mData.mUseValue[mIndex] = data.toInt(); + + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + }; + + template + struct AttributeColumn : public Column + { + AttributeColumn() : Column ("Attribute", ColumnBase::Display_Attribute) {} + + virtual QVariant get (const Record& record) const + { + return record.get().mData.mAttribute; + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + + record2.mData.mAttribute = data.toInt(); + + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + }; + } #endif \ No newline at end of file diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 06e638b07f..4b10a66836 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -36,8 +36,17 @@ CSMWorld::Data::Data() mGmsts.addColumn (new VarTypeColumn (ColumnBase::Display_GmstVarType)); mGmsts.addColumn (new VarValueColumn); + mSkills.addColumn (new StringIdColumn); + mSkills.addColumn (new RecordStateColumn); + mSkills.addColumn (new AttributeColumn); + mSkills.addColumn (new SpecialisationColumn); + for (int i=0; i<4; ++i) + mSkills.addColumn (new UseValueColumn (i)); + mSkills.addColumn (new DescriptionColumn); + addModel (new IdTable (&mGlobals), UniversalId::Type_Globals, UniversalId::Type_Global); addModel (new IdTable (&mGmsts), UniversalId::Type_Gmsts, UniversalId::Type_Gmst); + addModel (new IdTable (&mSkills), UniversalId::Type_Skills, UniversalId::Type_Skill); } CSMWorld::Data::~Data() @@ -66,6 +75,16 @@ CSMWorld::IdCollection& CSMWorld::Data::getGmsts() return mGmsts; } +const CSMWorld::IdCollection& CSMWorld::Data::getSkills() const +{ + return mSkills; +} + +CSMWorld::IdCollection& CSMWorld::Data::getSkills() +{ + return mSkills; +} + QAbstractItemModel *CSMWorld::Data::getTableModel (const UniversalId& id) { std::map::iterator iter = mModelIndex.find (id.getType()); @@ -102,7 +121,7 @@ void CSMWorld::Data::loadFile (const boost::filesystem::path& path, bool base) { case ESM::REC_GLOB: mGlobals.load (reader, base); break; case ESM::REC_GMST: mGmsts.load (reader, base); break; - + case ESM::REC_SKIL: mSkills.load (reader, base); break; default: diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index e42d5c1026..ac953dbece 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -8,6 +8,7 @@ #include #include +#include #include "idcollection.hpp" #include "universalid.hpp" @@ -20,6 +21,7 @@ namespace CSMWorld { IdCollection mGlobals; IdCollection mGmsts; + IdCollection mSkills; std::vector mModels; std::map mModelIndex; @@ -44,6 +46,10 @@ namespace CSMWorld IdCollection& getGmsts(); + const IdCollection& getSkills() const; + + IdCollection& getSkills(); + QAbstractItemModel *getTableModel (const UniversalId& id); ///< If no table model is available for \a id, an exception is thrown. /// diff --git a/apps/opencs/model/world/idcollection.hpp b/apps/opencs/model/world/idcollection.hpp index 9b69dfb889..3bf53349e6 100644 --- a/apps/opencs/model/world/idcollection.hpp +++ b/apps/opencs/model/world/idcollection.hpp @@ -74,6 +74,8 @@ namespace CSMWorld virtual const RecordBase& getRecord (const std::string& id) const = 0; + virtual const RecordBase& getRecord (int index) const = 0; + virtual void load (ESM::ESMReader& reader, bool base) = 0; }; @@ -139,7 +141,9 @@ namespace CSMWorld /// /// \attention Throw san exception, if the type of \a record does not match. - virtual const RecordBase& getRecord (const std::string& id) const; + virtual const Record& getRecord (const std::string& id) const; + + virtual const Record& getRecord (int index) const; virtual void load (ESM::ESMReader& reader, bool base); @@ -373,11 +377,18 @@ namespace CSMWorld } template - const RecordBase& IdCollection::getRecord (const std::string& id) const + const Record& IdCollection::getRecord (const std::string& id) const { int index = getIndex (id); return mRecords.at (index); } + + template + const Record& IdCollection::getRecord (int index) const + { + return mRecords.at (index); + } + } #endif diff --git a/apps/opencs/model/world/record.hpp b/apps/opencs/model/world/record.hpp index 53bb7ea2ce..e442d0a391 100644 --- a/apps/opencs/model/world/record.hpp +++ b/apps/opencs/model/world/record.hpp @@ -81,9 +81,7 @@ namespace CSMWorld throw std::logic_error ("attempt to modify a deleted record"); mModified = modified; - - if (mState!=State_ModifiedOnly) - mState = mBase==mModified ? State_BaseOnly : State_Modified; + mState = State_Modified; } template diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp index c006852bc6..57c276a6d3 100644 --- a/apps/opencs/model/world/universalid.cpp +++ b/apps/opencs/model/world/universalid.cpp @@ -19,6 +19,7 @@ namespace { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, "empty" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Globals, "Global Variables" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Gmsts, "Game Settings" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Skills, "Skills" }, { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0 } // end marker }; @@ -27,6 +28,7 @@ namespace { { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Global, "Global Variable" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Gmst, "Game Setting" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Skill, "Skill" }, { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0 } // end marker }; @@ -43,7 +45,7 @@ CSMWorld::UniversalId::UniversalId (const std::string& universalId) { std::string::size_type index = universalId.find (':'); - if (index==std::string::npos) + if (index!=std::string::npos) { std::string type = universalId.substr (0, index); diff --git a/apps/opencs/model/world/universalid.hpp b/apps/opencs/model/world/universalid.hpp index 9ff7d17b18..a412cb6b1e 100644 --- a/apps/opencs/model/world/universalid.hpp +++ b/apps/opencs/model/world/universalid.hpp @@ -37,8 +37,9 @@ namespace CSMWorld Type_Global, Type_VerificationResults, Type_Gmsts, - Type_Gmst - + Type_Gmst, + Type_Skills, + Type_Skill }; private: diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index 995d3ca2e2..267ddf26cc 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -86,6 +86,10 @@ void CSVDoc::View::setupWorldMenu() connect (gmsts, SIGNAL (triggered()), this, SLOT (addGmstsSubView())); world->addAction (gmsts); + QAction *skills = new QAction (tr ("Skills"), this); + connect (skills, SIGNAL (triggered()), this, SLOT (addSkillsSubView())); + world->addAction (skills); + mVerify = new QAction (tr ("&Verify"), this); connect (mVerify, SIGNAL (triggered()), this, SLOT (verify())); world->addAction (mVerify); @@ -244,6 +248,11 @@ void CSVDoc::View::addGmstsSubView() addSubView (CSMWorld::UniversalId::Type_Gmsts); } +void CSVDoc::View::addSkillsSubView() +{ + addSubView (CSMWorld::UniversalId::Type_Skills); +} + void CSVDoc::View::abortOperation (int type) { mDocument->abortOperation (type); diff --git a/apps/opencs/view/doc/view.hpp b/apps/opencs/view/doc/view.hpp index e91a4d4a80..4c5aa40784 100644 --- a/apps/opencs/view/doc/view.hpp +++ b/apps/opencs/view/doc/view.hpp @@ -116,6 +116,7 @@ namespace CSVDoc void addGmstsSubView(); + void addSkillsSubView(); }; } diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp index 33300f67a7..bc87728945 100644 --- a/apps/opencs/view/doc/viewmanager.cpp +++ b/apps/opencs/view/doc/viewmanager.cpp @@ -38,6 +38,17 @@ void CSVDoc::ViewManager::updateIndices() CSVDoc::ViewManager::ViewManager (CSMDoc::DocumentManager& documentManager) : mDocumentManager (documentManager), mExitOnSaveStateChange(false), mUserWarned(false) { + static const char *sSpecialisations[] = + { + "Combat", "Magic", "Stealth", 0 + }; + + static const char *sAttributes[] = + { + "Strength", "Intelligence", "Willpower", "Agility", "Speed", "Endurance", "Personality", + "Luck", 0 + }; + mDelegateFactories = new CSVWorld::CommandDelegateFactoryCollection; mDelegateFactories->add (CSMWorld::ColumnBase::Display_GmstVarType, @@ -45,6 +56,12 @@ CSVDoc::ViewManager::ViewManager (CSMDoc::DocumentManager& documentManager) mDelegateFactories->add (CSMWorld::ColumnBase::Display_GlobalVarType, new CSVWorld::VarTypeDelegateFactory (ESM::VT_Short, ESM::VT_Long, ESM::VT_Float)); + + mDelegateFactories->add (CSMWorld::ColumnBase::Display_Specialisation, + new CSVWorld::EnumDelegateFactory (sSpecialisations)); + + mDelegateFactories->add (CSMWorld::ColumnBase::Display_Attribute, + new CSVWorld::EnumDelegateFactory (sAttributes)); } CSVDoc::ViewManager::~ViewManager() diff --git a/apps/opencs/view/world/enumdelegate.cpp b/apps/opencs/view/world/enumdelegate.cpp index 7a8b45373d..0dd0a1d594 100644 --- a/apps/opencs/view/world/enumdelegate.cpp +++ b/apps/opencs/view/world/enumdelegate.cpp @@ -1,6 +1,7 @@ #include "enumdelegate.hpp" +#include #include #include @@ -89,6 +90,16 @@ void CSVWorld::EnumDelegate::paint (QPainter *painter, const QStyleOptionViewIte } +CSVWorld::EnumDelegateFactory::EnumDelegateFactory() {} + +CSVWorld::EnumDelegateFactory::EnumDelegateFactory (const char **names) +{ + assert (names); + + for (int i=0; names[i]; ++i) + add (i, names[i]); +} + CSVWorld::CommandDelegate *CSVWorld::EnumDelegateFactory::makeDelegate (QUndoStack& undoStack, QObject *parent) const { diff --git a/apps/opencs/view/world/enumdelegate.hpp b/apps/opencs/view/world/enumdelegate.hpp index f11252371e..752ed5be72 100644 --- a/apps/opencs/view/world/enumdelegate.hpp +++ b/apps/opencs/view/world/enumdelegate.hpp @@ -45,6 +45,11 @@ namespace CSVWorld public: + EnumDelegateFactory(); + + EnumDelegateFactory (const char **names); + ///< \param names Array of char pointer with a 0-pointer as end mark + virtual CommandDelegate *makeDelegate (QUndoStack& undoStack, QObject *parent) const; ///< The ownership of the returned CommandDelegate is transferred to the caller. diff --git a/apps/opencs/view/world/subviews.cpp b/apps/opencs/view/world/subviews.cpp index 351007ded5..bdff0017b4 100644 --- a/apps/opencs/view/world/subviews.cpp +++ b/apps/opencs/view/world/subviews.cpp @@ -14,6 +14,9 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) manager.add (CSMWorld::UniversalId::Type_Gmsts, new CSVDoc::SubViewFactoryWithCreateFlag (false)); + manager.add (CSMWorld::UniversalId::Type_Skills, + new CSVDoc::SubViewFactoryWithCreateFlag (false)); + manager.add (CSMWorld::UniversalId::Type_Global, new CSVDoc::SubViewFactoryWithCreateFlag (true)); } \ No newline at end of file diff --git a/components/esm/loadskil.cpp b/components/esm/loadskil.cpp index a4d21c5912..b9d588eef4 100644 --- a/components/esm/loadskil.cpp +++ b/components/esm/loadskil.cpp @@ -1,5 +1,7 @@ #include "loadskil.hpp" +#include + #include "esmreader.hpp" #include "esmwriter.hpp" @@ -98,6 +100,10 @@ void Skill::load(ESMReader &esm) esm.getHNT(mIndex, "INDX"); esm.getHNT(mData, "SKDT", 24); mDescription = esm.getHNOString("DESC"); + + // create an ID from the index and the name (only used in the editor and likely to change in the + // future) + mId = getIndexToId (mIndex); } void Skill::save(ESMWriter &esm) { @@ -105,4 +111,29 @@ void Skill::save(ESMWriter &esm) esm.writeHNT("SKDT", mData, 24); esm.writeHNOString("DESC", mDescription); } + + void Skill::blank() + { + mData.mAttribute = 0; + mData.mSpecialization = 0; + mData.mUseValue[0] = mData.mUseValue[1] = mData.mUseValue[2] = mData.mUseValue[3] = 1.0; + mDescription.clear(); + } + + std::string Skill::getIndexToId (int index) + { + std::ostringstream stream; + + stream << "#"; + + if (index<10) + stream << "0"; + + stream << index; + + if (index>=0 && index