diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index 8e2de6494..68e0dcc09 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -106,10 +106,10 @@ std::string ruleString(ESM::DialInfo::SelectStruct ss) { case '0': oper_str = "=="; break; case '1': oper_str = "!="; break; - case '2': oper_str = "< "; break; - case '3': oper_str = "<="; break; - case '4': oper_str = "> "; break; - case '5': oper_str = ">="; break; + case '2': oper_str = "> "; break; + case '3': oper_str = ">="; break; + case '4': oper_str = "< "; break; + case '5': oper_str = "<="; break; } std::ostringstream stream; diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index ec4fb40d5..3050a8705 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -57,13 +57,14 @@ opencs_hdrs_noqt (view/doc opencs_units (view/world - table tablesubview scriptsubview util regionmapsubview + table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator + cellcreator referenceablecreator referencecreator ) opencs_units_noqt (view/world dialoguesubview subviews enumdelegate vartypedelegate recordstatusdelegate refidtypedelegate datadisplaydelegate - scripthighlighter + scripthighlighter idvalidator ) diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index eaded5b70..43ecaca63 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -3,7 +3,7 @@ #include -#include "idtableproxymodel.hpp" +#include "idtable.hpp" #include "idtable.hpp" CSMWorld::ModifyCommand::ModifyCommand (QAbstractItemModel& model, const QModelIndex& index, @@ -25,15 +25,28 @@ void CSMWorld::ModifyCommand::undo() mModel.setData (mIndex, mOld); } -CSMWorld::CreateCommand::CreateCommand (IdTableProxyModel& model, const std::string& id, QUndoCommand *parent) -: QUndoCommand (parent), mModel (model), mId (id) +CSMWorld::CreateCommand::CreateCommand (IdTable& model, const std::string& id, QUndoCommand *parent) +: QUndoCommand (parent), mModel (model), mId (id), mType (UniversalId::Type_None) { setText (("Create record " + id).c_str()); } +void CSMWorld::CreateCommand::addValue (int column, const QVariant& value) +{ + mValues[column] = value; +} + +void CSMWorld::CreateCommand::setType (UniversalId::Type type) +{ + mType = type; +} + void CSMWorld::CreateCommand::redo() { - mModel.addRecord (mId); + mModel.addRecord (mId, mType); + + for (std::map::const_iterator iter (mValues.begin()); iter!=mValues.end(); ++iter) + mModel.setData (mModel.getModelIndex (mId, iter->first), iter->second); } void CSMWorld::CreateCommand::undo() diff --git a/apps/opencs/model/world/commands.hpp b/apps/opencs/model/world/commands.hpp index af419215d..3fc2522ea 100644 --- a/apps/opencs/model/world/commands.hpp +++ b/apps/opencs/model/world/commands.hpp @@ -4,17 +4,20 @@ #include "record.hpp" #include +#include #include #include #include +#include "universalid.hpp" + class QModelIndex; class QAbstractItemModel; namespace CSMWorld { - class IdTableProxyModel; + class IdTable; class IdTable; class RecordBase; @@ -37,12 +40,18 @@ namespace CSMWorld class CreateCommand : public QUndoCommand { - IdTableProxyModel& mModel; + IdTable& mModel; std::string mId; + UniversalId::Type mType; + std::map mValues; public: - CreateCommand (IdTableProxyModel& model, const std::string& id, QUndoCommand *parent = 0); + CreateCommand (IdTable& model, const std::string& id, QUndoCommand *parent = 0); + + void setType (UniversalId::Type type); + + void addValue (int column, const QVariant& value); virtual void redo(); diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 0cb28b911..79bae0db4 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -304,6 +304,16 @@ CSMWorld::RefIdCollection& CSMWorld::Data::getReferenceables() return mReferenceables; } +const CSMWorld::RefCollection& CSMWorld::Data::getReferences() const +{ + return mRefs; +} + +CSMWorld::RefCollection& CSMWorld::Data::getReferences() +{ + return mRefs; +} + QAbstractItemModel *CSMWorld::Data::getTableModel (const UniversalId& id) { std::map::iterator iter = mModelIndex.find (id.getType()); @@ -398,4 +408,22 @@ void CSMWorld::Data::loadFile (const boost::filesystem::path& path, bool base) reader.skipRecord(); } } -} \ No newline at end of file +} + +bool CSMWorld::Data::hasId (const std::string& id) const +{ + return + getGlobals().searchId (id)!=-1 || + getGmsts().searchId (id)!=-1 || + getSkills().searchId (id)!=-1 || + getClasses().searchId (id)!=-1 || + getFactions().searchId (id)!=-1 || + getRaces().searchId (id)!=-1 || + getSounds().searchId (id)!=-1 || + getScripts().searchId (id)!=-1 || + getRegions().searchId (id)!=-1 || + getBirthsigns().searchId (id)!=-1 || + getSpells().searchId (id)!=-1 || + getCells().searchId (id)!=-1 || + getReferenceables().searchId (id)!=-1; +} diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index 3eea897e6..aebdd6ecd 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -115,6 +115,10 @@ namespace CSMWorld RefIdCollection& getReferenceables(); + const RefCollection& getReferences() const; + + RefCollection& getReferences(); + QAbstractItemModel *getTableModel (const UniversalId& id); ///< If no table model is available for \a id, an exception is thrown. /// @@ -126,6 +130,8 @@ namespace CSMWorld void loadFile (const boost::filesystem::path& path, bool base); ///< Merging content of a file into base or modified. + + bool hasId (const std::string& id) const; }; } diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp index 25258398b..3d36b5503 100644 --- a/apps/opencs/model/world/idtable.cpp +++ b/apps/opencs/model/world/idtable.cpp @@ -116,13 +116,13 @@ QModelIndex CSMWorld::IdTable::parent (const QModelIndex& index) const return QModelIndex(); } -void CSMWorld::IdTable::addRecord (const std::string& id) +void CSMWorld::IdTable::addRecord (const std::string& id, UniversalId::Type type) { int index = mIdCollection->getAppendIndex(); beginInsertRows (QModelIndex(), index, index); - mIdCollection->appendBlankRecord (id); + mIdCollection->appendBlankRecord (id, type); endInsertRows(); } diff --git a/apps/opencs/model/world/idtable.hpp b/apps/opencs/model/world/idtable.hpp index 98bf6ad6d..556d7f0ba 100644 --- a/apps/opencs/model/world/idtable.hpp +++ b/apps/opencs/model/world/idtable.hpp @@ -3,6 +3,8 @@ #include +#include "universalid.hpp" + namespace CSMWorld { class CollectionBase; @@ -44,7 +46,8 @@ namespace CSMWorld virtual QModelIndex parent (const QModelIndex& index) const; - void addRecord (const std::string& id); + void addRecord (const std::string& id, UniversalId::Type type = UniversalId::Type_None); + ///< \param type Will be ignored, unless the collection supports multiple record types QModelIndex getModelIndex (const std::string& id, int column) const; diff --git a/apps/opencs/model/world/idtableproxymodel.cpp b/apps/opencs/model/world/idtableproxymodel.cpp index 78995f60b..e99e1575c 100644 --- a/apps/opencs/model/world/idtableproxymodel.cpp +++ b/apps/opencs/model/world/idtableproxymodel.cpp @@ -7,11 +7,6 @@ CSMWorld::IdTableProxyModel::IdTableProxyModel (QObject *parent) : QSortFilterProxyModel (parent) {} -void CSMWorld::IdTableProxyModel::addRecord (const std::string& id) -{ - dynamic_cast (*sourceModel()).addRecord (id); -} - QModelIndex CSMWorld::IdTableProxyModel::getModelIndex (const std::string& id, int column) const { return mapFromSource (dynamic_cast (*sourceModel()).getModelIndex (id, column)); diff --git a/apps/opencs/model/world/idtableproxymodel.hpp b/apps/opencs/model/world/idtableproxymodel.hpp index 3f1537cce..200b99fe2 100644 --- a/apps/opencs/model/world/idtableproxymodel.hpp +++ b/apps/opencs/model/world/idtableproxymodel.hpp @@ -15,8 +15,6 @@ namespace CSMWorld IdTableProxyModel (QObject *parent = 0); - virtual void addRecord (const std::string& id); - virtual QModelIndex getModelIndex (const std::string& id, int column) const; }; } diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index 6a1e8045b..085817753 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -21,10 +21,7 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool while (cell2.getNextRef (reader, ref)) { /// \todo handle deleted and moved references - std::ostringstream stream; - stream << "ref#" << mNextId++; - - ref.load (reader, cell2, stream.str()); + ref.load (reader, cell2, getNewId()); Record record2; record2.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; @@ -34,4 +31,11 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool } mCells.setRecord (cellIndex, cell); +} + +std::string CSMWorld::RefCollection::getNewId() +{ + std::ostringstream stream; + stream << "ref#" << mNextId++; + return stream.str(); } \ No newline at end of file diff --git a/apps/opencs/model/world/refcollection.hpp b/apps/opencs/model/world/refcollection.hpp index a2063590b..895315a17 100644 --- a/apps/opencs/model/world/refcollection.hpp +++ b/apps/opencs/model/world/refcollection.hpp @@ -21,6 +21,8 @@ namespace CSMWorld void load (ESM::ESMReader& reader, int cellIndex, bool base); ///< Load a sequence of references. + + std::string getNewId(); }; } diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp index ecd3fc4cd..42ebd1f80 100644 --- a/apps/opencs/model/world/universalid.cpp +++ b/apps/opencs/model/world/universalid.cpp @@ -12,83 +12,83 @@ namespace CSMWorld::UniversalId::Class mClass; CSMWorld::UniversalId::Type mType; const char *mName; + const char *mIcon; }; static const TypeData sNoArg[] = { - { 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_RecordList, CSMWorld::UniversalId::Type_Classes, "Classes" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Factions, "Factions" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Races, "Races" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Sounds, "Sounds" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Scripts, "Scripts" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Regions, "Regions" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Birthsigns, "Birthsigns" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Spells, "Spells" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Cells, "Cells" }, + { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, "empty", 0 }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Globals, "Global Variables", 0 }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Gmsts, "Game Settings", 0 }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Skills, "Skills", 0 }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Classes, "Classes", 0 }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Factions, "Factions", 0 }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Races, "Races", 0 }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Sounds, "Sounds", 0 }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Scripts, "Scripts", 0 }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Regions, "Regions", 0 }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Birthsigns, "Birthsigns", 0 }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Spells, "Spells", 0 }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Cells, "Cells", 0 }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Referenceables, - "Referenceables" }, + "Referenceables", 0 }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_References, - "References" }, + "References", 0 }, { CSMWorld::UniversalId::Class_NonRecord, CSMWorld::UniversalId::Type_RegionMap, - "Region Map" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Filters, "Filters" }, + "Region Map", 0 }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Filters, "Filters", 0 }, - { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0 } // end marker + { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0, 0 } // end marker }; static const TypeData sIdArg[] = { - { 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_Record, CSMWorld::UniversalId::Type_Class, "Class" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Faction, "Faction" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Race, "Race" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Sound, "Sound" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Script, "Script" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Region, "Region" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Birthsign, "Birthsign" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Spell, "Spell" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Cell, "Cell" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Referenceable, "Referenceables" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Activator, "Activator" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Potion, "Potion" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Apparatus, "Apparatus" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Armor, "Armor" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Book, "Book" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Clothing, "Clothing" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Container, "Container" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Creature, "Creature" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Door, "Door" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Ingredient, "Ingredient" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_CreatureLevelledList, - "Creature Levelled List" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_ItemLevelledList, - "Item Levelled List" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Light, "Light" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Lockpick, "Lockpick" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Miscellaneous, - "Miscellaneous" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Npc, "NPC" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Probe, "Probe" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Repair, "Repair" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Static, "Static" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Weapon, "Weapon" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Reference, "Reference" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Filter, "Reference" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Global, "Global Variable", 0 }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Gmst, "Game Setting", 0 }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Skill, "Skill", 0 }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Class, "Class", 0 }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Faction, "Faction", 0 }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Race, "Race", 0 }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Sound, "Sound", 0 }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Script, "Script", 0 }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Region, "Region", 0 }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Birthsign, "Birthsign", 0 }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Spell, "Spell", 0 }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Cell, "Cell", 0 }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Referenceable, "Referenceables", 0 }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Activator, "Activator", ":./activator.png" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Potion, "Potion", ":./potion.png" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Apparatus, "Apparatus", ":./apparatus.png" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Armor, "Armor", ":./armor.png" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Book, "Book", ":./book.png" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Clothing, "Clothing", ":./clothing.png" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Container, "Container", ":./container.png" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Creature, "Creature", ":./creature.png" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Door, "Door", ":./door.png" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Ingredient, "Ingredient", ":./ingredient.png" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_CreatureLevelledList, + "Creature Levelled List", ":./creature.png" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_ItemLevelledList, + "Item Levelled List", ":./leveled-item.png" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Light, "Light", ":./light.png" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Lockpick, "Lockpick", ":./lockpick.png" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Miscellaneous, + "Miscellaneous", ":./miscellaneous.png" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Npc, "NPC", ":./npc.png" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Probe, "Probe", ":./probe.png" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Repair, "Repair", ":./repair.png" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Static, "Static", ":./static.png" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Weapon, "Weapon", ":./weapon.png" }, + { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_Reference, "Reference", 0 }, - { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0 } // end marker + { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0, 0 } // end marker }; static const TypeData sIndexArg[] = { - { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_VerificationResults, "Verification Results" }, + { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_VerificationResults, "Verification Results", 0 }, - { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0 } // end marker + { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0, 0 } // end marker }; static const unsigned int IDARG_SIZE = sizeof (sIdArg) / sizeof (TypeData); @@ -270,6 +270,28 @@ std::string CSMWorld::UniversalId::toString() const return stream.str(); } +std::string CSMWorld::UniversalId::getIcon() const +{ + const TypeData *typeData = mArgumentType==ArgumentType_None ? sNoArg : + (mArgumentType==ArgumentType_Id ? sIdArg : sIndexArg); + + for (int i=0; typeData[i].mName; ++i) + if (typeData[i].mType==mType) + return typeData[i].mIcon ? typeData[i].mIcon : ""; + + throw std::logic_error ("failed to retrieve UniversalId type icon"); +} + +std::vector CSMWorld::UniversalId::listReferenceableTypes() +{ + std::vector list; + + for (int i=0; sIdArg[i].mName; ++i) + if (sIdArg[i].mClass==Class_RefRecord) + list.push_back (sIdArg[i].mType); + + return list; +} std::pair CSMWorld::UniversalId::getIdArgPair (unsigned int index) { diff --git a/apps/opencs/model/world/universalid.hpp b/apps/opencs/model/world/universalid.hpp index 4a736483e..8042c3dfd 100644 --- a/apps/opencs/model/world/universalid.hpp +++ b/apps/opencs/model/world/universalid.hpp @@ -3,6 +3,7 @@ #include #include +#include #include @@ -14,13 +15,14 @@ namespace CSMWorld enum Class { - Class_None = 0, - Class_Record, - Class_SubRecord, - Class_RecordList, - Class_Collection, // multiple types of records combined - Class_Transient, // not part of the world data or the project data - Class_NonRecord // record like data that is not part of the world + Class_None = 0, + Class_Record, + Class_RefRecord, // referenceable record + Class_SubRecord, + Class_RecordList, + Class_Collection, // multiple types of records combined + Class_Transient, // not part of the world data or the project data + Class_NonRecord // record like data that is not part of the world }; enum ArgumentType @@ -128,6 +130,11 @@ namespace CSMWorld std::string toString() const; + std::string getIcon() const; + ///< Will return an empty string, if no icon is available. + + static std::vector listReferenceableTypes(); + static std::pair getIdArgPair (unsigned int index); static unsigned int getIdArgSize (); }; diff --git a/apps/opencs/view/doc/subview.cpp b/apps/opencs/view/doc/subview.cpp index 731adabb3..09361a1c0 100644 --- a/apps/opencs/view/doc/subview.cpp +++ b/apps/opencs/view/doc/subview.cpp @@ -1,15 +1,10 @@ #include "subview.hpp" - CSVDoc::SubView::SubView (const CSMWorld::UniversalId& id) : mUniversalId (id) { /// \todo add a button to the title bar that clones this sub view setWindowTitle (mUniversalId.toString().c_str()); - - /// \todo remove (for testing only) - setMinimumWidth (100); - setMinimumHeight (60); } CSMWorld::UniversalId CSVDoc::SubView::getUniversalId() const @@ -20,3 +15,5 @@ CSMWorld::UniversalId CSVDoc::SubView::getUniversalId() const void CSVDoc::SubView::updateEditorSetting (const QString &settingName, const QString &settingValue) { } + +void CSVDoc::SubView::setStatusBar (bool show) {} \ No newline at end of file diff --git a/apps/opencs/view/doc/subview.hpp b/apps/opencs/view/doc/subview.hpp index 280a2a487..aa073f81d 100644 --- a/apps/opencs/view/doc/subview.hpp +++ b/apps/opencs/view/doc/subview.hpp @@ -37,6 +37,9 @@ namespace CSVDoc virtual void setEditLock (bool locked) = 0; virtual void updateEditorSetting (const QString &, const QString &); + virtual void setStatusBar (bool show); + ///< Default implementation: ignored + signals: void focusId (const CSMWorld::UniversalId& universalId); diff --git a/apps/opencs/view/doc/subviewfactoryimp.hpp b/apps/opencs/view/doc/subviewfactoryimp.hpp index d16e0b2b7..2c4158f85 100644 --- a/apps/opencs/view/doc/subviewfactoryimp.hpp +++ b/apps/opencs/view/doc/subviewfactoryimp.hpp @@ -22,28 +22,20 @@ namespace CSVDoc return new SubViewT (id, document); } - template - class SubViewFactoryWithCreateFlag : public SubViewFactoryBase + + template + class SubViewFactoryWithCreator : public SubViewFactoryBase { - bool mCreateAndDelete; - public: - SubViewFactoryWithCreateFlag (bool createAndDelete); - virtual CSVDoc::SubView *makeSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); }; - template - SubViewFactoryWithCreateFlag::SubViewFactoryWithCreateFlag (bool createAndDelete) - : mCreateAndDelete (createAndDelete) - {} - - template - CSVDoc::SubView *SubViewFactoryWithCreateFlag::makeSubView (const CSMWorld::UniversalId& id, - CSMDoc::Document& document) + template + CSVDoc::SubView *SubViewFactoryWithCreator::makeSubView ( + const CSMWorld::UniversalId& id, CSMDoc::Document& document) { - return new SubViewT (id, document, mCreateAndDelete); + return new SubViewT (id, document, CreatorFactoryT()); } } diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index 1317f525a..6801ea20d 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -79,6 +79,11 @@ void CSVDoc::View::setupViewMenu() connect (newWindow, SIGNAL (triggered()), this, SLOT (newView())); view->addAction (newWindow); + mShowStatusBar = new QAction (tr ("Show Status Bar"), this); + mShowStatusBar->setCheckable (true); + connect (mShowStatusBar, SIGNAL (toggled (bool)), this, SLOT (toggleShowStatusBar (bool))); + view->addAction (mShowStatusBar); + QAction *filters = new QAction (tr ("Filters"), this); connect (filters, SIGNAL (triggered()), this, SLOT (addFiltersSubView())); view->addAction (filters); @@ -286,6 +291,9 @@ void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id) /// \todo add an user setting to reuse sub views (on a per document basis or on a per top level view basis) SubView *view = mSubViewFactory.makeSubView (id, *mDocument); + + view->setStatusBar (mShowStatusBar->isChecked()); + mSubViewWindow.addDockWidget (Qt::TopDockWidgetArea, view); connect (view, SIGNAL (focusId (const CSMWorld::UniversalId&)), this, @@ -445,3 +453,12 @@ void CSVDoc::View::updateEditorSetting (const QString &settingName, const QStrin else if (settingName == "Height") resizeViewHeight (settingValue.toInt()); } + +void CSVDoc::View::toggleShowStatusBar (bool show) +{ + foreach (QObject *view, mSubViewWindow.children()) + { + if (CSVDoc::SubView *subView = dynamic_cast (view)) + subView->setStatusBar (show); + } +} diff --git a/apps/opencs/view/doc/view.hpp b/apps/opencs/view/doc/view.hpp index 203172df1..56c0b3edd 100644 --- a/apps/opencs/view/doc/view.hpp +++ b/apps/opencs/view/doc/view.hpp @@ -38,6 +38,7 @@ namespace CSVDoc QAction *mRedo; QAction *mSave; QAction *mVerify; + QAction *mShowStatusBar; std::vector mEditingActions; Operations *mOperations; SubViewFactoryManager mSubViewFactory; @@ -160,6 +161,8 @@ namespace CSVDoc void addFiltersSubView(); void showUserSettings(); + + void toggleShowStatusBar (bool show); }; } diff --git a/apps/opencs/view/world/cellcreator.cpp b/apps/opencs/view/world/cellcreator.cpp new file mode 100644 index 000000000..74fb06870 --- /dev/null +++ b/apps/opencs/view/world/cellcreator.cpp @@ -0,0 +1,81 @@ + +#include "cellcreator.hpp" + +#include +#include + +#include +#include +#include + +std::string CSVWorld::CellCreator::getId() const +{ + if (mType->currentIndex()==0) + return GenericCreator::getId(); + + std::ostringstream stream; + + stream << "#" << mX->value() << " " << mY->value(); + + return stream.str(); +} + +CSVWorld::CellCreator::CellCreator (CSMWorld::Data& data, QUndoStack& undoStack, + const CSMWorld::UniversalId& id) +: GenericCreator (data, undoStack, id) +{ + mY = new QSpinBox (this); + mY->setVisible (false); + mY->setMinimum (std::numeric_limits::min()); + mY->setMaximum (std::numeric_limits::max()); + connect (mY, SIGNAL (valueChanged (int)), this, SLOT (valueChanged (int))); + insertAtBeginning (mY, true); + + mYLabel = new QLabel ("Y", this); + mYLabel->setVisible (false); + insertAtBeginning (mYLabel, false); + + mX = new QSpinBox (this); + mX->setVisible (false); + mX->setMinimum (std::numeric_limits::min()); + mX->setMaximum (std::numeric_limits::max()); + connect (mX, SIGNAL (valueChanged (int)), this, SLOT (valueChanged (int))); + insertAtBeginning (mX, true); + + mXLabel = new QLabel ("X", this); + mXLabel->setVisible (false); + insertAtBeginning (mXLabel, false); + + mType = new QComboBox (this); + + mType->addItem ("Interior Cell"); + mType->addItem ("Exterior Cell"); + + connect (mType, SIGNAL (currentIndexChanged (int)), this, SLOT (setType (int))); + + insertAtBeginning (mType, false); +} + +void CSVWorld::CellCreator::reset() +{ + mX->setValue (0); + mY->setValue (0); + mType->setCurrentIndex (0); + GenericCreator::reset(); +} + +void CSVWorld::CellCreator::setType (int index) +{ + setManualEditing (index==0); + mXLabel->setVisible (index==1); + mX->setVisible (index==1); + mYLabel->setVisible (index==1); + mY->setVisible (index==1); + + update(); +} + +void CSVWorld::CellCreator::valueChanged (int index) +{ + update(); +} \ No newline at end of file diff --git a/apps/opencs/view/world/cellcreator.hpp b/apps/opencs/view/world/cellcreator.hpp new file mode 100644 index 000000000..a5473e2c9 --- /dev/null +++ b/apps/opencs/view/world/cellcreator.hpp @@ -0,0 +1,40 @@ +#ifndef CSV_WORLD_CELLCREATOR_H +#define CSV_WORLD_CELLCREATOR_H + +class QLabel; +class QSpinBox; +class QComboBox; + +#include "genericcreator.hpp" + +namespace CSVWorld +{ + class CellCreator : public GenericCreator + { + Q_OBJECT + + QComboBox *mType; + QLabel *mXLabel; + QSpinBox *mX; + QLabel *mYLabel; + QSpinBox *mY; + + protected: + + virtual std::string getId() const; + + public: + + CellCreator (CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id); + + virtual void reset(); + + private slots: + + void setType (int index); + + void valueChanged (int index); + }; +} + +#endif diff --git a/apps/opencs/view/world/creator.cpp b/apps/opencs/view/world/creator.cpp new file mode 100644 index 000000000..d753a2b47 --- /dev/null +++ b/apps/opencs/view/world/creator.cpp @@ -0,0 +1,13 @@ + +#include "creator.hpp" + +CSVWorld::Creator:: ~Creator() {} + +CSVWorld::CreatorFactoryBase::~CreatorFactoryBase() {} + + +CSVWorld::Creator *CSVWorld::NullCreatorFactory::makeCreator (CSMWorld::Data& data, + QUndoStack& undoStack, const CSMWorld::UniversalId& id) const +{ + return 0; +} \ No newline at end of file diff --git a/apps/opencs/view/world/creator.hpp b/apps/opencs/view/world/creator.hpp new file mode 100644 index 000000000..df9b116ee --- /dev/null +++ b/apps/opencs/view/world/creator.hpp @@ -0,0 +1,86 @@ +#ifndef CSV_WORLD_CREATOR_H +#define CSV_WORLD_CREATOR_H + +#include + +class QUndoStack; + +namespace CSMWorld +{ + class Data; + class UniversalId; +} + +namespace CSVWorld +{ + /// \brief Record creator UI base class + class Creator : public QWidget + { + Q_OBJECT + + public: + + virtual ~Creator(); + + virtual void reset() = 0; + + virtual void setEditLock (bool locked) = 0; + + signals: + + void done(); + + void requestFocus (const std::string& id); + ///< Request owner of this creator to focus the just created \a id. The owner may + /// ignore this request. + }; + + /// \brief Base class for Creator factory + class CreatorFactoryBase + { + public: + + virtual ~CreatorFactoryBase(); + + virtual Creator *makeCreator (CSMWorld::Data& data, QUndoStack& undoStack, + const CSMWorld::UniversalId& id) const = 0; + ///< The ownership of the returned Creator is transferred to the caller. + /// + /// \note The function can return a 0-pointer, which means no UI for creating/deleting + /// records should be provided. + }; + + /// \brief Creator factory that does not produces any creator + class NullCreatorFactory : public CreatorFactoryBase + { + public: + + virtual Creator *makeCreator (CSMWorld::Data& data, QUndoStack& undoStack, + const CSMWorld::UniversalId& id) const; + ///< The ownership of the returned Creator is transferred to the caller. + /// + /// \note The function always returns 0. + }; + + template + class CreatorFactory : public CreatorFactoryBase + { + public: + + virtual Creator *makeCreator (CSMWorld::Data& data, QUndoStack& undoStack, + const CSMWorld::UniversalId& id) const; + ///< The ownership of the returned Creator is transferred to the caller. + /// + /// \note The function can return a 0-pointer, which means no UI for creating/deleting + /// records should be provided. + }; + + template + Creator *CreatorFactory::makeCreator (CSMWorld::Data& data, QUndoStack& undoStack, + const CSMWorld::UniversalId& id) const + { + return new CreatorT (data, undoStack, id); + } +} + +#endif \ No newline at end of file diff --git a/apps/opencs/view/world/genericcreator.cpp b/apps/opencs/view/world/genericcreator.cpp new file mode 100644 index 000000000..7502b6555 --- /dev/null +++ b/apps/opencs/view/world/genericcreator.cpp @@ -0,0 +1,135 @@ + +#include "genericcreator.hpp" + +#include + +#include +#include +#include +#include + +#include "../../model/world/commands.hpp" +#include "../../model/world/data.hpp" +#include "../../model/world/idtable.hpp" + +#include "idvalidator.hpp" + +void CSVWorld::GenericCreator::update() +{ + mErrors = getErrors(); + + mCreate->setToolTip (QString::fromUtf8 (mErrors.c_str())); + mId->setToolTip (QString::fromUtf8 (mErrors.c_str())); + + mCreate->setEnabled (mErrors.empty() && !mLocked); +} + +void CSVWorld::GenericCreator::setManualEditing (bool enabled) +{ + mId->setVisible (enabled); +} + +void CSVWorld::GenericCreator::insertAtBeginning (QWidget *widget, bool stretched) +{ + mLayout->insertWidget (0, widget, stretched ? 1 : 0); +} + +void CSVWorld::GenericCreator::insertBeforeButtons (QWidget *widget, bool stretched) +{ + mLayout->insertWidget (mLayout->count()-2, widget, stretched ? 1 : 0); +} + +std::string CSVWorld::GenericCreator::getId() const +{ + return mId->text().toUtf8().constData(); +} + +void CSVWorld::GenericCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const {} + +const CSMWorld::Data& CSVWorld::GenericCreator::getData() const +{ + return mData; +} + +CSMWorld::Data& CSVWorld::GenericCreator::getData() +{ + return mData; +} + +CSVWorld::GenericCreator::GenericCreator (CSMWorld::Data& data, QUndoStack& undoStack, + const CSMWorld::UniversalId& id) +: mData (data), mUndoStack (undoStack), mListId (id), mLocked (false) +{ + mLayout = new QHBoxLayout; + mLayout->setContentsMargins (0, 0, 0, 0); + + mId = new QLineEdit; + mId->setValidator (new IdValidator (this)); + mLayout->addWidget (mId, 1); + + mCreate = new QPushButton ("Create"); + mLayout->addWidget (mCreate); + + QPushButton *cancelButton = new QPushButton ("Cancel"); + mLayout->addWidget (cancelButton); + + setLayout (mLayout); + + connect (cancelButton, SIGNAL (clicked (bool)), this, SIGNAL (done())); + connect (mCreate, SIGNAL (clicked (bool)), this, SLOT (create())); + + connect (mId, SIGNAL (textChanged (const QString&)), this, SLOT (textChanged (const QString&))); +} + +void CSVWorld::GenericCreator::setEditLock (bool locked) +{ + mLocked = locked; + update(); +} + +void CSVWorld::GenericCreator::reset() +{ + mId->setText (""); + update(); +} + +std::string CSVWorld::GenericCreator::getErrors() const +{ + std::string errors; + + std::string id = getId(); + + if (id.empty()) + { + errors = "Missing ID"; + } + else if (mData.hasId (id)) + { + errors = "ID is already in use"; + } + + return errors; +} + +void CSVWorld::GenericCreator::textChanged (const QString& text) +{ + update(); +} + +void CSVWorld::GenericCreator::create() +{ + if (!mLocked) + { + std::string id = getId(); + + std::auto_ptr command (new CSMWorld::CreateCommand ( + dynamic_cast (*mData.getTableModel (mListId)), id)); + + configureCreateCommand (*command); + + mUndoStack.push (command.release()); + + emit done(); + emit requestFocus (id); + } +} \ No newline at end of file diff --git a/apps/opencs/view/world/genericcreator.hpp b/apps/opencs/view/world/genericcreator.hpp new file mode 100644 index 000000000..5409f0839 --- /dev/null +++ b/apps/opencs/view/world/genericcreator.hpp @@ -0,0 +1,73 @@ +#ifndef CSV_WORLD_GENERICCREATOR_H +#define CSV_WORLD_GENERICCREATOR_H + +class QPushButton; +class QLineEdit; +class QHBoxLayout; + +#include "creator.hpp" + +#include "../../model/world/universalid.hpp" + +namespace CSMWorld +{ + class CreateCommand; +} + +namespace CSVWorld +{ + class GenericCreator : public Creator + { + Q_OBJECT + + CSMWorld::Data& mData; + QUndoStack& mUndoStack; + CSMWorld::UniversalId mListId; + QPushButton *mCreate; + QLineEdit *mId; + std::string mErrors; + QHBoxLayout *mLayout; + bool mLocked; + + protected: + + void update(); + + virtual void setManualEditing (bool enabled); + ///< Enable/disable manual ID editing (enabled by default). + + void insertAtBeginning (QWidget *widget, bool stretched); + + void insertBeforeButtons (QWidget *widget, bool stretched); + + virtual std::string getId() const; + + virtual void configureCreateCommand (CSMWorld::CreateCommand& command) const; + + const CSMWorld::Data& getData() const; + + CSMWorld::Data& getData(); + + public: + + GenericCreator (CSMWorld::Data& data, QUndoStack& undoStack, + const CSMWorld::UniversalId& id); + + virtual void setEditLock (bool locked); + + virtual void reset(); + + virtual std::string getErrors() const; + ///< Return formatted error descriptions for the current state of the creator. if an empty + /// string is returned, there is no error. + + + private slots: + + void textChanged (const QString& text); + + void create(); + }; +} + +#endif \ No newline at end of file diff --git a/apps/opencs/view/world/idvalidator.cpp b/apps/opencs/view/world/idvalidator.cpp new file mode 100644 index 000000000..cf6e5d77b --- /dev/null +++ b/apps/opencs/view/world/idvalidator.cpp @@ -0,0 +1,26 @@ + +#include "idvalidator.hpp" + +bool CSVWorld::IdValidator::isValid (const QChar& c, bool first) const +{ + if (c.isLetter() || c=='_') + return true; + + if (!first && (c.isDigit() || c.isSpace())) + return true; + + return false; +} + +CSVWorld::IdValidator::IdValidator (QObject *parent) : QValidator (parent) {} + +QValidator::State CSVWorld::IdValidator::validate (QString& input, int& pos) const +{ + bool first = true; + + for (QString::const_iterator iter (input.begin()); iter!=input.end(); ++iter, first = false) + if (!isValid (*iter, first)) + return QValidator::Invalid; + + return QValidator::Acceptable; +} \ No newline at end of file diff --git a/apps/opencs/view/world/idvalidator.hpp b/apps/opencs/view/world/idvalidator.hpp new file mode 100644 index 000000000..db0ecb27a --- /dev/null +++ b/apps/opencs/view/world/idvalidator.hpp @@ -0,0 +1,23 @@ +#ifndef CSV_WORLD_IDVALIDATOR_H +#define CSV_WORLD_IDVALIDATOR_H + +#include + +namespace CSVWorld +{ + class IdValidator : public QValidator + { + private: + + bool isValid (const QChar& c, bool first) const; + + public: + + IdValidator (QObject *parent = 0); + + virtual State validate (QString& input, int& pos) const; + + }; +} + +#endif diff --git a/apps/opencs/view/world/referenceablecreator.cpp b/apps/opencs/view/world/referenceablecreator.cpp new file mode 100644 index 000000000..718fe9ca7 --- /dev/null +++ b/apps/opencs/view/world/referenceablecreator.cpp @@ -0,0 +1,43 @@ + +#include "referenceablecreator.hpp" + +#include +#include + +#include "../../model/world/universalid.hpp" +#include "../../model/world/commands.hpp" + +void CSVWorld::ReferenceableCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const +{ + command.setType ( + static_cast (mType->itemData (mType->currentIndex()).toInt())); +} + +CSVWorld::ReferenceableCreator::ReferenceableCreator (CSMWorld::Data& data, QUndoStack& undoStack, + const CSMWorld::UniversalId& id) +: GenericCreator (data, undoStack, id) +{ + QLabel *label = new QLabel ("Type", this); + insertBeforeButtons (label, false); + + std::vector types = CSMWorld::UniversalId::listReferenceableTypes(); + + mType = new QComboBox (this); + + for (std::vector::const_iterator iter (types.begin()); + iter!=types.end(); ++iter) + { + CSMWorld::UniversalId id (*iter, ""); + + mType->addItem (QIcon (id.getIcon().c_str()), id.getTypeName().c_str(), + static_cast (id.getType())); + } + + insertBeforeButtons (mType, false); +} + +void CSVWorld::ReferenceableCreator::reset() +{ + mType->setCurrentIndex (0); + GenericCreator::reset(); +} \ No newline at end of file diff --git a/apps/opencs/view/world/referenceablecreator.hpp b/apps/opencs/view/world/referenceablecreator.hpp new file mode 100644 index 000000000..06e0e582b --- /dev/null +++ b/apps/opencs/view/world/referenceablecreator.hpp @@ -0,0 +1,30 @@ +#ifndef CSV_WORLD_REFERENCEABLECREATOR_H +#define CSV_WORLD_REFERENCEABLECREATOR_H + +class QComboBox; + +#include "genericcreator.hpp" + +namespace CSVWorld +{ + class ReferenceableCreator : public GenericCreator + { + Q_OBJECT + + QComboBox *mType; + + private: + + virtual void configureCreateCommand (CSMWorld::CreateCommand& command) const; + + public: + + ReferenceableCreator (CSMWorld::Data& data, QUndoStack& undoStack, + const CSMWorld::UniversalId& id); + + virtual void reset(); + + }; +} + +#endif diff --git a/apps/opencs/view/world/referencecreator.cpp b/apps/opencs/view/world/referencecreator.cpp new file mode 100644 index 000000000..48a168a35 --- /dev/null +++ b/apps/opencs/view/world/referencecreator.cpp @@ -0,0 +1,70 @@ + +#include "referencecreator.hpp" + +#include +#include + +#include "../../model/world/data.hpp" +#include "../../model/world/commands.hpp" + +std::string CSVWorld::ReferenceCreator::getId() const +{ + return mId; +} + +void CSVWorld::ReferenceCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const +{ + /// \todo avoid using hard-coded column numbers + command.addValue (2, mCell->text()); +} + +CSVWorld::ReferenceCreator::ReferenceCreator (CSMWorld::Data& data, QUndoStack& undoStack, + const CSMWorld::UniversalId& id) +: GenericCreator (data, undoStack, id) +{ + QLabel *label = new QLabel ("Cell", this); + insertBeforeButtons (label, false); + + mCell = new QLineEdit (this); + insertBeforeButtons (mCell, true); + + setManualEditing (false); + + connect (mCell, SIGNAL (textChanged (const QString&)), this, SLOT (cellChanged())); +} + +void CSVWorld::ReferenceCreator::reset() +{ + mCell->setText (""); + mId = getData().getReferences().getNewId(); + GenericCreator::reset(); +} + +std::string CSVWorld::ReferenceCreator::getErrors() const +{ + std::string errors = GenericCreator::getErrors(); + + std::string cell = mCell->text().toUtf8().constData(); + + if (cell.empty()) + { + if (!errors.empty()) + errors += "
"; + + errors += "Missing Cell ID"; + } + else if (getData().getCells().searchId (cell)==-1) + { + if (!errors.empty()) + errors += "
"; + + errors += "Invalid Cell ID"; + } + + return errors; +} + +void CSVWorld::ReferenceCreator::cellChanged() +{ + update(); +} \ No newline at end of file diff --git a/apps/opencs/view/world/referencecreator.hpp b/apps/opencs/view/world/referencecreator.hpp new file mode 100644 index 000000000..27f81564f --- /dev/null +++ b/apps/opencs/view/world/referencecreator.hpp @@ -0,0 +1,40 @@ +#ifndef CSV_WORLD_REFERENCECREATOR_H +#define CSV_WORLD_REFERENCECREATOR_H + +#include "genericcreator.hpp" + +class QLineEdit; + +namespace CSVWorld +{ + class ReferenceCreator : public GenericCreator + { + Q_OBJECT + + QLineEdit *mCell; + std::string mId; + + private: + + virtual std::string getId() const; + + virtual void configureCreateCommand (CSMWorld::CreateCommand& command) const; + + public: + + ReferenceCreator (CSMWorld::Data& data, QUndoStack& undoStack, + const CSMWorld::UniversalId& id); + + virtual void reset(); + + virtual std::string getErrors() const; + ///< Return formatted error descriptions for the current state of the creator. if an empty + /// string is returned, there is no error. + + private slots: + + void cellChanged(); + }; +} + +#endif diff --git a/apps/opencs/view/world/refidtypedelegate.cpp b/apps/opencs/view/world/refidtypedelegate.cpp index 40bf88584..bf3acbb20 100755 --- a/apps/opencs/view/world/refidtypedelegate.cpp +++ b/apps/opencs/view/world/refidtypedelegate.cpp @@ -27,26 +27,15 @@ CSVWorld::RefIdTypeDelegateFactory::UidTypeList CSVWorld::RefIdTypeDelegateFacto { UidTypeList list; - list.push_back (std::make_pair (CSMWorld::UniversalId::Type_Activator, ":./activator.png")); - list.push_back (std::make_pair (CSMWorld::UniversalId::Type_Potion, ":./potion.png")); - list.push_back (std::make_pair (CSMWorld::UniversalId::Type_Apparatus, ":./apparatus.png")); - list.push_back (std::make_pair (CSMWorld::UniversalId::Type_Armor, ":./armor.png")); - list.push_back (std::make_pair (CSMWorld::UniversalId::Type_Book, ":./book.png")); - list.push_back (std::make_pair (CSMWorld::UniversalId::Type_Clothing, ":./clothing.png")); - list.push_back (std::make_pair (CSMWorld::UniversalId::Type_Container, ":./container.png")); - list.push_back (std::make_pair (CSMWorld::UniversalId::Type_Creature, ":./creature.png")); - list.push_back (std::make_pair (CSMWorld::UniversalId::Type_Door, ":./door.png")); - list.push_back (std::make_pair (CSMWorld::UniversalId::Type_Ingredient, ":./ingredient.png")); - list.push_back (std::make_pair (CSMWorld::UniversalId::Type_CreatureLevelledList, ":./creature.png")); - list.push_back (std::make_pair (CSMWorld::UniversalId::Type_ItemLevelledList, ":./item.png")); - list.push_back (std::make_pair (CSMWorld::UniversalId::Type_Light, ":./light.png")); - list.push_back (std::make_pair (CSMWorld::UniversalId::Type_Lockpick, ":./lockpick.png")); - list.push_back (std::make_pair (CSMWorld::UniversalId::Type_Miscellaneous, ":./misc.png")); - list.push_back (std::make_pair (CSMWorld::UniversalId::Type_Npc, ":./npc.png")); - list.push_back (std::make_pair (CSMWorld::UniversalId::Type_Probe, ":./probe.png")); - list.push_back (std::make_pair (CSMWorld::UniversalId::Type_Repair, ":./repair.png")); - list.push_back (std::make_pair (CSMWorld::UniversalId::Type_Static, ":./static.png")); - list.push_back (std::make_pair (CSMWorld::UniversalId::Type_Weapon, ":./weapon.png")); + std::vector types = CSMWorld::UniversalId::listReferenceableTypes(); + + for (std::vector::const_iterator iter (types.begin()); + iter!=types.end(); ++iter) + { + CSMWorld::UniversalId id (*iter, ""); + + list.push_back (std::make_pair (id.getType(), id.getIcon().c_str())); + } return list; } diff --git a/apps/opencs/view/world/subviews.cpp b/apps/opencs/view/world/subviews.cpp index be114dad2..2ca711a59 100644 --- a/apps/opencs/view/world/subviews.cpp +++ b/apps/opencs/view/world/subviews.cpp @@ -7,14 +7,20 @@ #include "dialoguesubview.hpp" #include "scriptsubview.hpp" #include "regionmapsubview.hpp" +#include "genericcreator.hpp" +#include "cellcreator.hpp" +#include "referenceablecreator.hpp" +#include "referencecreator.hpp" void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) { + // Regular record tables (including references which are actually sub-records, but are promoted + // to top-level records within the editor) manager.add (CSMWorld::UniversalId::Type_Gmsts, - new CSVDoc::SubViewFactoryWithCreateFlag (false)); + new CSVDoc::SubViewFactoryWithCreator); manager.add (CSMWorld::UniversalId::Type_Skills, - new CSVDoc::SubViewFactoryWithCreateFlag (false)); + new CSVDoc::SubViewFactoryWithCreator); static const CSMWorld::UniversalId::Type sTableTypes[] = { @@ -27,21 +33,27 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) CSMWorld::UniversalId::Type_Regions, CSMWorld::UniversalId::Type_Birthsigns, CSMWorld::UniversalId::Type_Spells, - CSMWorld::UniversalId::Type_Cells, - CSMWorld::UniversalId::Type_Referenceables, - CSMWorld::UniversalId::Type_References, CSMWorld::UniversalId::Type_Filters, CSMWorld::UniversalId::Type_None // end marker }; for (int i=0; sTableTypes[i]!=CSMWorld::UniversalId::Type_None; ++i) - manager.add (sTableTypes[i], new CSVDoc::SubViewFactoryWithCreateFlag (true)); + manager.add (sTableTypes[i], + new CSVDoc::SubViewFactoryWithCreator >); + manager.add (CSMWorld::UniversalId::Type_Cells, + new CSVDoc::SubViewFactoryWithCreator >); + + manager.add (CSMWorld::UniversalId::Type_Referenceables, + new CSVDoc::SubViewFactoryWithCreator >); + + manager.add (CSMWorld::UniversalId::Type_References, + new CSVDoc::SubViewFactoryWithCreator >); + + // Subviews for editing/viewing individual records manager.add (CSMWorld::UniversalId::Type_Script, new CSVDoc::SubViewFactory); + // Other stuff (combined record tables) manager.add (CSMWorld::UniversalId::Type_RegionMap, new CSVDoc::SubViewFactory); - -// manager.add (CSMWorld::UniversalId::Type_Global, -// new CSVDoc::SubViewFactoryWithCreateFlag (true)); } \ No newline at end of file diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 90ce42b34..fdbf67e42 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -44,19 +44,30 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) std::vector CSVWorld::Table::listRevertableSelectedIds() const { - QModelIndexList selectedRows = selectionModel()->selectedRows(); - + /// \todo Do not use hardcoded column numbers std::vector revertableIds; - for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); ++iter) + if (mProxyModel->columnCount()>0) { - std::string id = mProxyModel->data (*iter).toString().toStdString(); + QModelIndexList selectedRows = selectionModel()->selectedRows(); - CSMWorld::RecordBase::State state = - static_cast (mModel->data (mModel->getModelIndex (id, 1)).toInt()); + for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); + ++iter) + { + QModelIndex index = mProxyModel->mapToSource (mProxyModel->index (iter->row(), 0)); - if (state!=CSMWorld::RecordBase::State_BaseOnly) - revertableIds.push_back (id); + CSMWorld::RecordBase::State state = + static_cast ( + mModel->data (mModel->index (index.row(), 1)).toInt()); + + if (state!=CSMWorld::RecordBase::State_BaseOnly) + { + std::string id = mModel->data (mModel->index (index.row(), 0)). + toString().toUtf8().constData(); + + revertableIds.push_back (id); + } + } } return revertableIds; @@ -64,19 +75,30 @@ std::vector CSVWorld::Table::listRevertableSelectedIds() const std::vector CSVWorld::Table::listDeletableSelectedIds() const { - QModelIndexList selectedRows = selectionModel()->selectedRows(); - + /// \todo Do not use hardcoded column numbers std::vector deletableIds; - for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); ++iter) + if (mProxyModel->columnCount()>0) { - std::string id = mProxyModel->data (*iter).toString().toStdString(); + QModelIndexList selectedRows = selectionModel()->selectedRows(); - CSMWorld::RecordBase::State state = - static_cast (mModel->data (mModel->getModelIndex (id, 1)).toInt()); + for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); + ++iter) + { + QModelIndex index = mProxyModel->mapToSource (mProxyModel->index (iter->row(), 0)); - if (state!=CSMWorld::RecordBase::State_Deleted) - deletableIds.push_back (id); + CSMWorld::RecordBase::State state = + static_cast ( + mModel->data (mModel->index (index.row(), 1)).toInt()); + + if (state!=CSMWorld::RecordBase::State_Deleted) + { + std::string id = mModel->data (mModel->index (index.row(), 0)). + toString().toUtf8().constData(); + + deletableIds.push_back (id); + } + } } return deletableIds; @@ -126,7 +148,7 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, Q if (createAndDelete) { mCreateAction = new QAction (tr ("Add Record"), this); - connect (mCreateAction, SIGNAL (triggered()), this, SLOT (createRecord())); + connect (mCreateAction, SIGNAL (triggered()), this, SIGNAL (createRequest())); addAction (mCreateAction); } @@ -137,6 +159,17 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, Q mDeleteAction = new QAction (tr ("Delete Record"), this); connect (mDeleteAction, SIGNAL (triggered()), this, SLOT (deleteRecord())); addAction (mDeleteAction); + + connect (mProxyModel, SIGNAL (rowsInserted (const QModelIndex&, int, int)), + this, SLOT (tableSizeUpdate())); + + /// \note This signal could instead be connected to a slot that filters out changes not affecting + /// the records status column (for permanence reasons) + connect (mProxyModel, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (tableSizeUpdate())); + + connect (selectionModel(), SIGNAL (selectionChanged (const QItemSelection&, const QItemSelection&)), + this, SLOT (selectionSizeUpdate ())); } void CSVWorld::Table::setEditLock (bool locked) @@ -154,22 +187,6 @@ CSMWorld::UniversalId CSVWorld::Table::getUniversalId (int row) const mProxyModel->data (mProxyModel->index (row, 0)).toString().toStdString()); } -#include /// \todo remove -void CSVWorld::Table::createRecord() -{ - if (!mEditLock) - { - /// \todo ask the user for an ID instead. - static int index = 0; - - std::ostringstream stream; - stream << "id" << index++; - - mUndoStack.push (new CSMWorld::CreateCommand (*mProxyModel, stream.str())); - } - -} - void CSVWorld::Table::revertRecord() { if (!mEditLock) @@ -231,3 +248,46 @@ void CSVWorld::Table::updateEditorSetting (const QString &settingName, const QSt updateEditorSetting (settingName, settingValue)) emit dataChanged (mModel->index (0, i), mModel->index (mModel->rowCount()-1, i)); } + +void CSVWorld::Table::tableSizeUpdate() +{ + int size = 0; + int deleted = 0; + int modified = 0; + + if (mModel->columnCount()>0) + { + int rows = mModel->rowCount(); + + for (int i=0; imapToSource (mProxyModel->index (i, 0)); + + /// \todo Do not use hardcoded column numbers + int state = mModel->data (mModel->index (index.row(), 1)).toInt(); + + switch (state) + { + case CSMWorld::RecordBase::State_BaseOnly: ++size; break; + case CSMWorld::RecordBase::State_Modified: ++size; ++modified; break; + case CSMWorld::RecordBase::State_ModifiedOnly: ++size; ++modified; break; + case CSMWorld::RecordBase:: State_Deleted: ++deleted; ++modified; break; + } + } + } + + tableSizeChanged (size, deleted, modified); +} + +void CSVWorld::Table::selectionSizeUpdate() +{ + selectionSizeChanged (selectionModel()->selectedRows().size()); +} + +void CSVWorld::Table::requestFocus (const std::string& id) +{ + QModelIndex index = mProxyModel->getModelIndex (id, 0); + + if (index.isValid()) + scrollTo (index, QAbstractItemView::PositionAtTop); +} \ No newline at end of file diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index eedfeb8a1..0c24e7b54 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -60,15 +60,31 @@ namespace CSVWorld void editRequest (int row); - private slots: + void selectionSizeChanged (int size); - void createRecord(); + void tableSizeChanged (int size, int deleted, int modified); + ///< \param size Number of not deleted records + /// \param deleted Number of deleted records + /// \param modified Number of added and modified records + + void createRequest(); + + private slots: void revertRecord(); void deleteRecord(); void editRecord(); + + public slots: + + void tableSizeUpdate(); + + void selectionSizeUpdate(); + + void requestFocus (const std::string& id); + }; } diff --git a/apps/opencs/view/world/tablebottombox.cpp b/apps/opencs/view/world/tablebottombox.cpp new file mode 100644 index 000000000..6cf21a132 --- /dev/null +++ b/apps/opencs/view/world/tablebottombox.cpp @@ -0,0 +1,156 @@ + +#include "tablebottombox.hpp" + +#include + +#include +#include +#include + +#include "creator.hpp" + +void CSVWorld::TableBottomBox::updateStatus() +{ + if (mShowStatusBar) + { + static const char *sLabels[4] = { "record", "deleted", "touched", "selected" }; + static const char *sLabelsPlural[4] = { "records", "deleted", "touched", "selected" }; + + std::ostringstream stream; + + bool first = true; + + for (int i=0; i<4; ++i) + { + if (mStatusCount[i]>0) + { + if (first) + first = false; + else + stream << ", "; + + stream + << mStatusCount[i] << ' ' + << (mStatusCount[i]==1 ? sLabels[i] : sLabelsPlural[i]); + } + } + + mStatus->setText (QString::fromUtf8 (stream.str().c_str())); + } +} + +CSVWorld::TableBottomBox::TableBottomBox (const CreatorFactoryBase& creatorFactory, + CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id, QWidget *parent) +: QWidget (parent), mShowStatusBar (false), mCreating (false) +{ + for (int i=0; i<4; ++i) + mStatusCount[i] = 0; + + setVisible (false); + + mLayout = new QStackedLayout; + mLayout->setContentsMargins (0, 0, 0, 0); + + mStatus = new QLabel; + + mStatusBar = new QStatusBar; + + mStatusBar->addWidget (mStatus); + + mLayout->addWidget (mStatusBar); + + setLayout (mLayout); + + mCreator = creatorFactory.makeCreator (data, undoStack, id); + + mLayout->addWidget (mCreator); + + connect (mCreator, SIGNAL (done()), this, SLOT (createRequestDone())); + + connect (mCreator, SIGNAL (requestFocus (const std::string&)), + this, SIGNAL (requestFocus (const std::string&))); +} + +void CSVWorld::TableBottomBox::setEditLock (bool locked) +{ + if (mCreator) + mCreator->setEditLock (locked); +} + +CSVWorld::TableBottomBox::~TableBottomBox() +{ + delete mCreator; +} + +void CSVWorld::TableBottomBox::setStatusBar (bool show) +{ + if (show!=mShowStatusBar) + { + setVisible (show || mCreating); + + mShowStatusBar = show; + + if (show) + updateStatus(); + } +} + +bool CSVWorld::TableBottomBox::canCreateAndDelete() const +{ + return mCreator; +} + +void CSVWorld::TableBottomBox::createRequestDone() +{ + if (!mShowStatusBar) + setVisible (false); + else + updateStatus(); + + mLayout->setCurrentWidget (mStatusBar); + + mCreating = false; +} + +void CSVWorld::TableBottomBox::selectionSizeChanged (int size) +{ + if (mStatusCount[3]!=size) + { + mStatusCount[3] = size; + updateStatus(); + } +} + +void CSVWorld::TableBottomBox::tableSizeChanged (int size, int deleted, int modified) +{ + bool changed = false; + + if (mStatusCount[0]!=size) + { + mStatusCount[0] = size; + changed = true; + } + + if (mStatusCount[1]!=deleted) + { + mStatusCount[1] = deleted; + changed = true; + } + + if (mStatusCount[2]!=modified) + { + mStatusCount[2] = modified; + changed = true; + } + + if (changed) + updateStatus(); +} + +void CSVWorld::TableBottomBox::createRequest() +{ + mCreator->reset(); + mLayout->setCurrentWidget (mCreator); + setVisible (true); + mCreating = true; +} \ No newline at end of file diff --git a/apps/opencs/view/world/tablebottombox.hpp b/apps/opencs/view/world/tablebottombox.hpp new file mode 100644 index 000000000..a5ae5e0bd --- /dev/null +++ b/apps/opencs/view/world/tablebottombox.hpp @@ -0,0 +1,82 @@ +#ifndef CSV_WORLD_BOTTOMBOX_H +#define CSV_WORLD_BOTTOMBOX_H + +#include + +class QLabel; +class QStackedLayout; +class QStatusBar; +class QUndoStack; + +namespace CSMWorld +{ + class Data; + class UniversalId; +} + +namespace CSVWorld +{ + class CreatorFactoryBase; + class Creator; + + class TableBottomBox : public QWidget + { + Q_OBJECT + + bool mShowStatusBar; + QLabel *mStatus; + QStatusBar *mStatusBar; + int mStatusCount[4]; + Creator *mCreator; + bool mCreating; + QStackedLayout *mLayout; + + private: + + // not implemented + TableBottomBox (const TableBottomBox&); + TableBottomBox& operator= (const TableBottomBox&); + + void updateStatus(); + + public: + + TableBottomBox (const CreatorFactoryBase& creatorFactory, CSMWorld::Data& data, + QUndoStack& undoStack, const CSMWorld::UniversalId& id, QWidget *parent = 0); + + virtual ~TableBottomBox(); + + void setEditLock (bool locked); + + void setStatusBar (bool show); + + bool canCreateAndDelete() const; + ///< Is record creation and deletion supported? + /// + /// \note The BotomBox does not partake in the deletion of records. + + signals: + + void requestFocus (const std::string& id); + ///< Request owner of this box to focus the just created \a id. The owner may + /// ignore this request. + + private slots: + + void createRequestDone(); + ///< \note This slot being called does not imply success. + + public slots: + + void selectionSizeChanged (int size); + + void tableSizeChanged (int size, int deleted, int modified); + ///< \param size Number of not deleted records + /// \param deleted Number of deleted records + /// \param modified Number of added and modified records + + void createRequest(); + }; +} + +#endif diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index 65cba4b02..af3d186e8 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -1,22 +1,55 @@ #include "tablesubview.hpp" +#include + #include "../../model/doc/document.hpp" #include "table.hpp" +#include "tablebottombox.hpp" +#include "creator.hpp" CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, - bool createAndDelete) + const CreatorFactoryBase& creatorFactory) : SubView (id) { - setWidget (mTable = new Table (id, document.getData(), document.getUndoStack(), createAndDelete)); + QVBoxLayout *layout = new QVBoxLayout; + + layout->setContentsMargins (QMargins (0, 0, 0, 0)); + + layout->addWidget (mBottom = + new TableBottomBox (creatorFactory, document.getData(), document.getUndoStack(), id, this), 0); + + layout->insertWidget (0, mTable = + new Table (id, document.getData(), document.getUndoStack(), mBottom->canCreateAndDelete()), 2); + + QWidget *widget = new QWidget; + + widget->setLayout (layout); + + setWidget (widget); connect (mTable, SIGNAL (editRequest (int)), this, SLOT (editRequest (int))); + + connect (mTable, SIGNAL (selectionSizeChanged (int)), + mBottom, SLOT (selectionSizeChanged (int))); + connect (mTable, SIGNAL (tableSizeChanged (int, int, int)), + mBottom, SLOT (tableSizeChanged (int, int, int))); + + mTable->tableSizeUpdate(); + mTable->selectionSizeUpdate(); + + if (mBottom->canCreateAndDelete()) + connect (mTable, SIGNAL (createRequest()), mBottom, SLOT (createRequest())); + + connect (mBottom, SIGNAL (requestFocus (const std::string&)), + mTable, SLOT (requestFocus (const std::string&))); } void CSVWorld::TableSubView::setEditLock (bool locked) { mTable->setEditLock (locked); + mBottom->setEditLock (locked); } void CSVWorld::TableSubView::editRequest (int row) @@ -28,3 +61,8 @@ void CSVWorld::TableSubView::updateEditorSetting(const QString &settingName, con { mTable->updateEditorSetting(settingName, settingValue); } + +void CSVWorld::TableSubView::setStatusBar (bool show) +{ + mBottom->setStatusBar (show); +} \ No newline at end of file diff --git a/apps/opencs/view/world/tablesubview.hpp b/apps/opencs/view/world/tablesubview.hpp index ccee50dbc..d61c78935 100644 --- a/apps/opencs/view/world/tablesubview.hpp +++ b/apps/opencs/view/world/tablesubview.hpp @@ -13,18 +13,26 @@ namespace CSMDoc namespace CSVWorld { class Table; + class TableBottomBox; + class CreatorFactoryBase; class TableSubView : public CSVDoc::SubView { Q_OBJECT Table *mTable; + TableBottomBox *mBottom; public: - TableSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, bool createAndDelete); + TableSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, + const CreatorFactoryBase& creatorFactory); + virtual void setEditLock (bool locked); - void updateEditorSetting (const QString &, const QString &); + + virtual void updateEditorSetting (const QString& key, const QString& value); + + virtual void setStatusBar (bool show); private slots: diff --git a/apps/openmw/mwbase/dialoguemanager.hpp b/apps/openmw/mwbase/dialoguemanager.hpp index 12996cc30..58731d1c7 100644 --- a/apps/openmw/mwbase/dialoguemanager.hpp +++ b/apps/openmw/mwbase/dialoguemanager.hpp @@ -40,6 +40,8 @@ namespace MWBase virtual MWWorld::Ptr getActor() const = 0; ///< Return the actor the player is currently talking to. + virtual void say(const MWWorld::Ptr &actor, const std::string &topic) const = 0; + //calbacks for the GUI virtual void keywordSelected (const std::string& keyword) = 0; virtual void goodbyeSelected() = 0; diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 36d4ab8bb..9c9c448ed 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -215,6 +215,7 @@ namespace MWBase virtual void removeStaticMessageBox() = 0; virtual void enterPressed () = 0; + virtual void activateKeyPressed () = 0; virtual int readPressedButton() = 0; ///< returns the index of the pressed button or -1 if no button was pressed (->MessageBoxmanager->InteractiveMessageBox) @@ -237,6 +238,8 @@ namespace MWBase virtual void processChangedSettings(const Settings::CategorySettingVector& changed) = 0; + virtual void windowResized(int x, int y) = 0; + virtual void executeInConsole (const std::string& path) = 0; virtual void setLoadingProgress (const std::string& stage, int depth, int current, int total) = 0; @@ -263,6 +266,8 @@ namespace MWBase virtual void changePointer (const std::string& name) = 0; + virtual void setEnemy (const MWWorld::Ptr& enemy) = 0; + virtual const Translation::Storage& getTranslationDataStorage() const = 0; virtual void setKeyFocusWidget (MyGUI::Widget* widget) = 0; diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index a67ac666d..480bcf9cf 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -201,6 +201,8 @@ namespace MWBase virtual void setMoonColour (bool red) = 0; + virtual void modRegion(const std::string ®ionid, const std::vector &chances) = 0; + virtual float getTimeScaleFactor() const = 0; virtual void changeToInteriorCell (const std::string& cellName, @@ -218,6 +220,10 @@ namespace MWBase virtual MWWorld::Ptr getFacedObject() = 0; ///< Return pointer to the object the player is looking at, if it is within activation range + /// Returns a pointer to the object the provided object is facing (if within the + /// specified distance). This will attempt to use the "Bip01 Head" node as a basis. + virtual MWWorld::Ptr getFacedObject(const MWWorld::Ptr &ptr, float distance) = 0; + virtual void adjustPosition (const MWWorld::Ptr& ptr) = 0; ///< Adjust position after load to be on ground. Must be called after model load. @@ -372,6 +378,12 @@ namespace MWBase /// Find default position inside interior cell specified by name /// \return false if interior with given name not exists, true otherwise virtual bool findInteriorPosition(const std::string &name, ESM::Position &pos) = 0; + + /// Enables or disables use of teleport spell effects (recall, intervention, etc). + virtual void enableTeleporting(bool enable) = 0; + + /// Returns true if teleport spell effects are allowed. + virtual bool isTeleportingEnabled() const = 0; }; } diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index f5bd434b1..960f57430 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -11,6 +11,7 @@ #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" +#include "../mwbase/soundmanager.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/actiontalk.hpp" @@ -148,6 +149,70 @@ namespace MWClass return dynamic_cast (*ptr.getRefData().getCustomData()).mCreatureStats; } + + void Creature::hit(const MWWorld::Ptr& ptr, int type) const + { + } + + void Creature::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, bool successful) const + { + // NOTE: 'object' and/or 'attacker' may be empty. + + if(!successful) + { + // TODO: Handle HitAttemptOnMe script function + + // Missed + MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "miss", 1.0f, 1.0f); + return; + } + + if(!object.isEmpty()) + getCreatureStats(ptr).setLastHitObject(MWWorld::Class::get(object).getId(object)); + + if(!attacker.isEmpty() && attacker.getRefData().getHandle() == "player") + { + const std::string &script = ptr.get()->mBase->mScript; + /* Set the OnPCHitMe script variable. The script is responsible for clearing it. */ + if(!script.empty()) + ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1); + } + + if(ishealth) + { + if(damage > 0.0f) + MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "Health Damage", 1.0f, 1.0f); + float health = getCreatureStats(ptr).getHealth().getCurrent() - damage; + setActorHealth(ptr, health, attacker); + } + else + { + MWMechanics::DynamicStat fatigue(getCreatureStats(ptr).getFatigue()); + fatigue.setCurrent(fatigue.getCurrent() - damage); + getCreatureStats(ptr).setFatigue(fatigue); + } + } + + void Creature::setActorHealth(const MWWorld::Ptr& ptr, float health, const MWWorld::Ptr& attacker) const + { + MWMechanics::CreatureStats &crstats = getCreatureStats(ptr); + bool wasDead = crstats.isDead(); + + MWMechanics::DynamicStat stat(crstats.getHealth()); + stat.setCurrent(health); + crstats.setHealth(stat); + + if(!wasDead && crstats.isDead()) + { + // actor was just killed + } + else if(wasDead && !crstats.isDead()) + { + // actor was just resurrected + } + } + + boost::shared_ptr Creature::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { @@ -355,6 +420,8 @@ namespace MWClass return 5; if(name == "scream") return 6; + if(name == "land") + return 7; throw std::runtime_error(std::string("Unexpected soundgen type: ")+name); } diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index 356ecfb64..0d8694ff8 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -42,6 +42,12 @@ namespace MWClass virtual MWMechanics::CreatureStats& getCreatureStats (const MWWorld::Ptr& ptr) const; ///< Return creature stats + virtual void hit(const MWWorld::Ptr& ptr, int type) const; + + virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, bool successful) const; + + virtual void setActorHealth(const MWWorld::Ptr& ptr, float health, const MWWorld::Ptr& attacker) const; + virtual boost::shared_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const; ///< Generate action for activation diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index b6af9f0e5..e91055f1d 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -13,6 +13,8 @@ #include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/dialoguemanager.hpp" +#include "../mwbase/soundmanager.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" @@ -113,7 +115,19 @@ namespace // initial health int strength = creatureStats.getAttribute(ESM::Attribute::Strength).getBase(); int endurance = creatureStats.getAttribute(ESM::Attribute::Endurance).getBase(); - creatureStats.setHealth(static_cast (0.5 * (strength + endurance)) + 4 * (creatureStats.getLevel() - 1)); + + 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(static_cast (0.5 * (strength + endurance)) + multiplier * (creatureStats.getLevel() - 1)); } } @@ -288,6 +302,243 @@ namespace MWClass return dynamic_cast (*ptr.getRefData().getCustomData()).mNpcStats; } + + void Npc::hit(const MWWorld::Ptr& ptr, int type) const + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + const MWWorld::Store &gmst = world->getStore().get(); + + // Get the weapon used (if hand-to-hand, weapon = inv.end()) + MWWorld::InventoryStore &inv = getInventoryStore(ptr); + MWWorld::ContainerStoreIterator weaponslot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + MWWorld::Ptr weapon = ((weaponslot != inv.end()) ? *weaponslot : MWWorld::Ptr()); + if(!weapon.isEmpty() && weapon.getTypeName() != typeid(ESM::Weapon).name()) + weapon = MWWorld::Ptr(); + + float dist = 100.0f * (!weapon.isEmpty() ? + weapon.get()->mBase->mData.mReach : + gmst.find("fHandToHandReach")->getFloat()); + MWWorld::Ptr victim = world->getFacedObject(ptr, dist); + if(victim.isEmpty()) // Didn't hit anything + return; + + const MWWorld::Class &othercls = MWWorld::Class::get(victim); + if(!othercls.isActor() || othercls.getCreatureStats(victim).isDead()) + { + // Can't hit non-actors, or dead actors + return; + } + + if(ptr.getRefData().getHandle() == "player") + MWBase::Environment::get().getWindowManager()->setEnemy(ptr); + + int weapskill = ESM::Skill::HandToHand; + if(!weapon.isEmpty()) + weapskill = MWWorld::Class::get(weapon).getEquipmentSkill(weapon); + + MWMechanics::CreatureStats &crstats = getCreatureStats(ptr); + MWMechanics::NpcStats &npcstats = getNpcStats(ptr); + const MWMechanics::MagicEffects &mageffects = crstats.getMagicEffects(); + float hitchance = npcstats.getSkill(weapskill).getModified() + + (crstats.getAttribute(ESM::Attribute::Agility).getModified() / 5.0f) + + (crstats.getAttribute(ESM::Attribute::Luck).getModified() / 10.0f); + hitchance *= crstats.getFatigueTerm(); + hitchance += mageffects.get(MWMechanics::EffectKey(ESM::MagicEffect::FortifyAttack)).mMagnitude - + mageffects.get(MWMechanics::EffectKey(ESM::MagicEffect::Blind)).mMagnitude; + hitchance -= othercls.getEvasion(victim); + + if((::rand()/(RAND_MAX+1.0)) > hitchance/100.0f) + { + othercls.onHit(victim, 0.0f, false, weapon, ptr, false); + return; + } + + bool healthdmg; + float damage = 0.0f; + if(!weapon.isEmpty()) + { + const bool weaphashealth = get(weapon).hasItemHealth(weapon); + const unsigned char *attack = NULL; + if(type == MWMechanics::CreatureStats::AT_Chop) + attack = weapon.get()->mBase->mData.mChop; + else if(type == MWMechanics::CreatureStats::AT_Slash) + attack = weapon.get()->mBase->mData.mSlash; + else if(type == MWMechanics::CreatureStats::AT_Thrust) + attack = weapon.get()->mBase->mData.mThrust; + if(attack) + { + damage = attack[0] + ((attack[1]-attack[0])*npcstats.getAttackStrength()); + damage *= 0.5f + (crstats.getAttribute(ESM::Attribute::Luck).getModified() / 100.0f); + if(weaphashealth) + { + int weapmaxhealth = weapon.get()->mBase->mData.mHealth; + if(weapon.getCellRef().mCharge == -1) + weapon.getCellRef().mCharge = weapmaxhealth; + damage *= float(weapon.getCellRef().mCharge) / weapmaxhealth; + } + if(!othercls.hasDetected(victim, ptr)) + { + damage *= gmst.find("fCombatCriticalStrikeMult")->getFloat(); + MWBase::Environment::get().getWindowManager()->messageBox("#{sTargetCriticalStrike}"); + MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f); + } + weapon.getCellRef().mCharge -= std::min(std::max(1, + (int)(damage * gmst.find("fWeaponDamageMult")->getFloat())), + weapon.getCellRef().mCharge); + } + healthdmg = true; + } + else + { + // Note: MCP contains an option to include Strength in hand-to-hand damage + // calculations. Some mods recommend using it, so we may want to include am + // option for it. + float minstrike = gmst.find("fMinHandToHandMult")->getFloat(); + float maxstrike = gmst.find("fMaxHandToHandMult")->getFloat(); + damage = npcstats.getSkill(weapskill).getModified(); + damage *= minstrike + ((maxstrike-minstrike)*npcstats.getAttackStrength()); + if(!othercls.hasDetected(victim, ptr)) + { + damage *= gmst.find("fCombatCriticalStrikeMult")->getFloat(); + MWBase::Environment::get().getWindowManager()->messageBox("#{sTargetCriticalStrike}"); + MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f); + } + + healthdmg = (othercls.getCreatureStats(victim).getFatigue().getCurrent() < 1.0f || + npcstats.isWerewolf()); + if(healthdmg) + damage *= gmst.find("fHandtoHandHealthPer")->getFloat(); + } + if(ptr.getRefData().getHandle() == "player") + skillUsageSucceeded(ptr, weapskill, 0); + + othercls.onHit(victim, damage, healthdmg, weapon, ptr, true); + } + + void Npc::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, bool successful) const + { + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + + // NOTE: 'object' and/or 'attacker' may be empty. + + if(!successful) + { + // TODO: Handle HitAttemptOnMe script function + + // Missed + sndMgr->playSound3D(ptr, "miss", 1.0f, 1.0f); + return; + } + + if(!object.isEmpty()) + getCreatureStats(ptr).setLastHitObject(get(object).getId(object)); + + if(!attacker.isEmpty() && attacker.getRefData().getHandle() == "player") + { + const std::string &script = ptr.get()->mBase->mScript; + /* Set the OnPCHitMe script variable. The script is responsible for clearing it. */ + if(!script.empty()) + ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1); + } + + if(damage > 0.0f) + { + // 'ptr' is losing health. Play a 'hit' voiced dialog entry if not already saying + // something, alert the character controller, scripts, etc. + + MWBase::Environment::get().getDialogueManager()->say(ptr, "hit"); + + if(object.isEmpty()) + { + if(ishealth) + damage /= std::min(1.0f + getArmorRating(ptr)/std::max(1.0f, damage), 4.0f); + sndMgr->playSound3D(ptr, "Hand To Hand Hit", 1.0f, 1.0f); + } + else if(ishealth) + { + // Hit percentages: + // cuirass = 30% + // shield, helmet, greaves, boots, pauldrons = 10% each + // guantlets = 5% each + static const int hitslots[20] = { + MWWorld::InventoryStore::Slot_Cuirass, MWWorld::InventoryStore::Slot_Cuirass, + MWWorld::InventoryStore::Slot_Cuirass, MWWorld::InventoryStore::Slot_Cuirass, + MWWorld::InventoryStore::Slot_Cuirass, MWWorld::InventoryStore::Slot_Cuirass, + MWWorld::InventoryStore::Slot_CarriedLeft, MWWorld::InventoryStore::Slot_CarriedLeft, + MWWorld::InventoryStore::Slot_Helmet, MWWorld::InventoryStore::Slot_Helmet, + MWWorld::InventoryStore::Slot_Greaves, MWWorld::InventoryStore::Slot_Greaves, + MWWorld::InventoryStore::Slot_Boots, MWWorld::InventoryStore::Slot_Boots, + MWWorld::InventoryStore::Slot_LeftPauldron, MWWorld::InventoryStore::Slot_LeftPauldron, + MWWorld::InventoryStore::Slot_RightPauldron, MWWorld::InventoryStore::Slot_RightPauldron, + MWWorld::InventoryStore::Slot_LeftGauntlet, MWWorld::InventoryStore::Slot_RightGauntlet + }; + int hitslot = hitslots[(int)(::rand()/(RAND_MAX+1.0)*20.0)]; + + float damagediff = damage; + damage /= std::min(1.0f + getArmorRating(ptr)/std::max(1.0f, damage), 4.0f); + damagediff -= damage; + + MWWorld::InventoryStore &inv = getInventoryStore(ptr); + MWWorld::ContainerStoreIterator armorslot = inv.getSlot(hitslot); + MWWorld::Ptr armor = ((armorslot != inv.end()) ? *armorslot : MWWorld::Ptr()); + if(!armor.isEmpty() && armor.getTypeName() == typeid(ESM::Armor).name()) + { + ESM::CellRef &armorref = armor.getCellRef(); + if(armorref.mCharge == -1) + armorref.mCharge = armor.get()->mBase->mData.mHealth; + armorref.mCharge -= std::min(std::max(1, (int)damagediff), + armorref.mCharge); + switch(get(armor).getEquipmentSkill(armor)) + { + case ESM::Skill::LightArmor: + sndMgr->playSound3D(ptr, "Light Armor Hit", 1.0f, 1.0f); + break; + case ESM::Skill::MediumArmor: + sndMgr->playSound3D(ptr, "Medium Armor Hit", 1.0f, 1.0f); + break; + case ESM::Skill::HeavyArmor: + sndMgr->playSound3D(ptr, "Heavy Armor Hit", 1.0f, 1.0f); + break; + } + } + } + } + + if(ishealth) + { + if(damage > 0.0f) + sndMgr->playSound3D(ptr, "Health Damage", 1.0f, 1.0f); + float health = getCreatureStats(ptr).getHealth().getCurrent() - damage; + setActorHealth(ptr, health, attacker); + } + else + { + MWMechanics::DynamicStat fatigue(getCreatureStats(ptr).getFatigue()); + fatigue.setCurrent(fatigue.getCurrent() - damage); + getCreatureStats(ptr).setFatigue(fatigue); + } + } + + void Npc::setActorHealth(const MWWorld::Ptr& ptr, float health, const MWWorld::Ptr& attacker) const + { + MWMechanics::CreatureStats &crstats = getCreatureStats(ptr); + bool wasDead = crstats.isDead(); + + MWMechanics::DynamicStat stat(crstats.getHealth()); + stat.setCurrent(health); + crstats.setHealth(stat); + + if(!wasDead && crstats.isDead()) + { + // actor was just killed + } + else if(wasDead && !crstats.isDead()) + { + // actor was just resurrected + } + } + + boost::shared_ptr Npc::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { @@ -592,18 +843,19 @@ namespace MWClass float Npc::getArmorRating (const MWWorld::Ptr& ptr) const { - MWWorld::InventoryStore& invStore = MWWorld::Class::get(ptr).getInventoryStore(ptr); - const MWWorld::Store &gmst = - MWBase::Environment::get().getWorld()->getStore().get(); + const MWBase::World *world = MWBase::Environment::get().getWorld(); + const MWWorld::Store &gmst = world->getStore().get(); - int ratings[MWWorld::InventoryStore::Slots]; + MWMechanics::NpcStats &stats = getNpcStats(ptr); + MWWorld::InventoryStore &invStore = getInventoryStore(ptr); int iBaseArmorSkill = gmst.find("iBaseArmorSkill")->getInt(); float fUnarmoredBase1 = gmst.find("fUnarmoredBase1")->getFloat(); float fUnarmoredBase2 = gmst.find("fUnarmoredBase2")->getFloat(); - int unarmoredSkill = MWWorld::Class::get(ptr).getNpcStats(ptr).getSkill(ESM::Skill::Unarmored).getModified(); + int unarmoredSkill = stats.getSkill(ESM::Skill::Unarmored).getModified(); - for (int i = 0; i < MWWorld::InventoryStore::Slots; ++i) + int ratings[MWWorld::InventoryStore::Slots]; + for(int i = 0;i < MWWorld::InventoryStore::Slots;i++) { MWWorld::ContainerStoreIterator it = invStore.getSlot(i); if (it == invStore.end() || it->getTypeName() != typeid(ESM::Armor).name()) @@ -613,28 +865,27 @@ namespace MWClass } else { - MWWorld::LiveCellRef *ref = - it->get(); + MWWorld::LiveCellRef *ref = it->get(); int armorSkillType = MWWorld::Class::get(*it).getEquipmentSkill(*it); - int armorSkill = MWWorld::Class::get(ptr).getNpcStats(ptr).getSkill(armorSkillType).getModified(); + int armorSkill = stats.getSkill(armorSkillType).getModified(); - if (ref->mBase->mData.mWeight == 0) + if(ref->mBase->mData.mWeight == 0) ratings[i] = ref->mBase->mData.mArmor; else ratings[i] = ref->mBase->mData.mArmor * armorSkill / iBaseArmorSkill; } } - float shield = MWWorld::Class::get(ptr).getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::Shield).mMagnitude; + float shield = getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::Shield).mMagnitude; - return ratings[MWWorld::InventoryStore::Slot_Cuirass] * 0.3 + return ratings[MWWorld::InventoryStore::Slot_Cuirass] * 0.3f + (ratings[MWWorld::InventoryStore::Slot_CarriedLeft] + ratings[MWWorld::InventoryStore::Slot_Helmet] + ratings[MWWorld::InventoryStore::Slot_Greaves] + ratings[MWWorld::InventoryStore::Slot_Boots] + ratings[MWWorld::InventoryStore::Slot_LeftPauldron] + ratings[MWWorld::InventoryStore::Slot_RightPauldron] - ) * 0.1 - + (ratings[MWWorld::InventoryStore::Slot_LeftGauntlet] + MWWorld::InventoryStore::Slot_RightGauntlet) - * 0.05 + ) * 0.1f + + (ratings[MWWorld::InventoryStore::Slot_LeftGauntlet] + ratings[MWWorld::InventoryStore::Slot_RightGauntlet]) + * 0.05f + shield; } @@ -689,7 +940,7 @@ namespace MWClass case ESM::Skill::LightArmor: return "FootLightLeft"; case ESM::Skill::MediumArmor: - return "FootMediumLeft"; + return "FootMedLeft"; case ESM::Skill::HeavyArmor: return "FootHeavyLeft"; } @@ -714,7 +965,7 @@ namespace MWClass case ESM::Skill::LightArmor: return "FootLightRight"; case ESM::Skill::MediumArmor: - return "FootMediumRight"; + return "FootMedRight"; case ESM::Skill::HeavyArmor: return "FootHeavyRight"; } @@ -730,6 +981,8 @@ namespace MWClass return ""; if(name == "scream") return ""; + if(name == "land") + return ""; throw std::runtime_error(std::string("Unexpected soundgen type: ")+name); } diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index 2a73df593..3d429c982 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -68,6 +68,12 @@ namespace MWClass virtual MWWorld::InventoryStore& getInventoryStore (const MWWorld::Ptr& ptr) const; ///< Return inventory store + virtual void hit(const MWWorld::Ptr& ptr, int type) const; + + virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, bool successful) const; + + virtual void setActorHealth(const MWWorld::Ptr& ptr, float health, const MWWorld::Ptr& attacker) const; + virtual boost::shared_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const; ///< Generate action for activation diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index cb394d089..db07fb553 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -22,6 +22,13 @@ namespace MWClass { + std::string Weapon::getId (const MWWorld::Ptr& ptr) const + { + MWWorld::LiveCellRef *ref = ptr.get(); + + return ref->mBase->mId; + } + void Weapon::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { const std::string model = getModel(ptr); diff --git a/apps/openmw/mwclass/weapon.hpp b/apps/openmw/mwclass/weapon.hpp index 3902ef612..181c637db 100644 --- a/apps/openmw/mwclass/weapon.hpp +++ b/apps/openmw/mwclass/weapon.hpp @@ -12,6 +12,9 @@ namespace MWClass public: + virtual std::string getId (const MWWorld::Ptr& ptr) const; + ///< Return ID of \a ptr + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 7093ff91e..06a6e335d 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -25,6 +25,7 @@ #include "../mwbase/scriptmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/soundmanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" @@ -557,6 +558,30 @@ namespace MWDialogue return false; } + void DialogueManager::say(const MWWorld::Ptr &actor, const std::string &topic) const + { + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + if(!sndMgr->sayDone(actor)) + { + // Actor is already saying something. + return; + } + + const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + const ESM::Dialogue *dial = store.get().find(topic); + + Filter filter(actor, 0, false); + const ESM::DialInfo *info = filter.search(*dial, false); + if(info != NULL) + { + MWBase::WindowManager *winMgr = MWBase::Environment::get().getWindowManager(); + if(winMgr->getSubtitlesEnabled()) + winMgr->messageBox(info->mResponse); + sndMgr->say(actor, info->mSound); + } + } + + std::vector ParseHyperText(const std::string& text) { std::vector result; diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp index de1ac77c6..13bab4dae 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp @@ -68,6 +68,8 @@ namespace MWDialogue virtual bool checkServiceRefused (); + virtual void say(const MWWorld::Ptr &actor, const std::string &topic) const; + //calbacks for the GUI virtual void keywordSelected (const std::string& keyword); virtual void goodbyeSelected(); diff --git a/apps/openmw/mwgui/countdialog.cpp b/apps/openmw/mwgui/countdialog.cpp index c429b0ccf..02ccbbc05 100644 --- a/apps/openmw/mwgui/countdialog.cpp +++ b/apps/openmw/mwgui/countdialog.cpp @@ -49,11 +49,16 @@ namespace MWGui mItemEdit->setCaption(boost::lexical_cast(maxCount)); } - void CountDialog::onCancelButtonClicked(MyGUI::Widget* _sender) + void CountDialog::cancel() { setVisible(false); } + void CountDialog::onCancelButtonClicked(MyGUI::Widget* _sender) + { + cancel(); + } + void CountDialog::onOkButtonClicked(MyGUI::Widget* _sender) { eventOkClicked(NULL, mSlider->getScrollPosition()+1); diff --git a/apps/openmw/mwgui/countdialog.hpp b/apps/openmw/mwgui/countdialog.hpp index 82a833bb7..06de3eb88 100644 --- a/apps/openmw/mwgui/countdialog.hpp +++ b/apps/openmw/mwgui/countdialog.hpp @@ -10,6 +10,7 @@ namespace MWGui public: CountDialog(); void open(const std::string& item, const std::string& message, const int maxCount); + void cancel(); typedef MyGUI::delegates::CMultiDelegate2 EventHandle_WidgetInt; @@ -25,7 +26,7 @@ namespace MWGui MyGUI::TextBox* mLabelText; MyGUI::Button* mOkButton; MyGUI::Button* mCancelButton; - + void onCancelButtonClicked(MyGUI::Widget* _sender); void onOkButtonClicked(MyGUI::Widget* _sender); void onEditTextChange(MyGUI::EditBox* _sender); diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index 469c45936..4eea0d0d0 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -9,6 +9,8 @@ #include "../mwworld/player.hpp" #include "../mwworld/class.hpp" +#include "../mwmechanics/creaturestats.hpp" + #include "inventorywindow.hpp" #include "console.hpp" #include "spellicons.hpp" @@ -47,6 +49,7 @@ namespace MWGui , mWeaponVisible(true) , mSpellVisible(true) , mWorldMouseOver(false) + , mEnemyHealthTimer(0) { setCoord(0,0, width, height); @@ -55,6 +58,7 @@ namespace MWGui getWidget(mHealth, "Health"); getWidget(mMagicka, "Magicka"); getWidget(mStamina, "Stamina"); + getWidget(mEnemyHealth, "EnemyHealth"); mHealthManaStaminaBaseLeft = mHealthFrame->getLeft(); MyGUI::Widget *healthFrame, *magickaFrame, *fatigueFrame; @@ -320,6 +324,13 @@ namespace MWGui mCellNameBox->setVisible(false); if (mWeaponSpellTimer < 0) mWeaponSpellBox->setVisible(false); + + mEnemyHealthTimer -= dt; + if (mEnemyHealth->getVisible() && mEnemyHealthTimer < 0) + { + mEnemyHealth->setVisible(false); + mWeaponSpellBox->setPosition(mWeaponSpellBox->getPosition() + MyGUI::IntPoint(0,20)); + } } void HUD::onResChange(int width, int height) @@ -535,6 +546,25 @@ namespace MWGui void HUD::update() { mSpellIcons->updateWidgets(mEffectBox, true); + + if (!mEnemy.isEmpty() && mEnemyHealth->getVisible()) + { + MWMechanics::CreatureStats& stats = MWWorld::Class::get(mEnemy).getCreatureStats(mEnemy); + mEnemyHealth->setProgressRange(100); + mEnemyHealth->setProgressPosition(stats.getHealth().getCurrent() / stats.getHealth().getModified() * 100); + } + } + + void HUD::setEnemy(const MWWorld::Ptr &enemy) + { + mEnemy = enemy; + mEnemyHealthTimer = 5; + if (!mEnemyHealth->getVisible()) + mWeaponSpellBox->setPosition(mWeaponSpellBox->getPosition() - MyGUI::IntPoint(0,20)); + mEnemyHealth->setVisible(true); + MWMechanics::CreatureStats& stats = MWWorld::Class::get(mEnemy).getCreatureStats(mEnemy); + mEnemyHealth->setProgressRange(100); + mEnemyHealth->setProgressPosition(stats.getHealth().getCurrent() / stats.getHealth().getModified() * 100); } } diff --git a/apps/openmw/mwgui/hud.hpp b/apps/openmw/mwgui/hud.hpp index 1dd53683b..76d878269 100644 --- a/apps/openmw/mwgui/hud.hpp +++ b/apps/openmw/mwgui/hud.hpp @@ -46,12 +46,14 @@ namespace MWGui void update(); + void setEnemy(const MWWorld::Ptr& enemy); + private: - MyGUI::ProgressPtr mHealth, mMagicka, mStamina; + MyGUI::ProgressBar *mHealth, *mMagicka, *mStamina, *mEnemyHealth; MyGUI::Widget* mHealthFrame; MyGUI::Widget *mWeapBox, *mSpellBox; MyGUI::ImageBox *mWeapImage, *mSpellImage; - MyGUI::ProgressPtr mWeapStatus, mSpellStatus; + MyGUI::ProgressBar *mWeapStatus, *mSpellStatus; MyGUI::Widget *mEffectBox, *mMinimapBox; MyGUI::Button* mMinimapButton; MyGUI::ScrollView* mMinimap; @@ -89,6 +91,9 @@ namespace MWGui SpellIcons* mSpellIcons; + MWWorld::Ptr mEnemy; + float mEnemyHealthTimer; + void onWorldClicked(MyGUI::Widget* _sender); void onWorldMouseOver(MyGUI::Widget* _sender, int x, int y); void onWorldMouseLostFocus(MyGUI::Widget* _sender, MyGUI::Widget* _new); diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index 2fc50f257..3a969c7ca 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -146,10 +146,10 @@ namespace MWGui mMessageBoxSpeed = speed; } - void MessageBoxManager::enterPressed () + void MessageBoxManager::okayPressed () { if(mInterMessageBoxe != NULL) - mInterMessageBoxe->enterPressed(); + mInterMessageBoxe->okayPressed(); } int MessageBoxManager::readPressedButton () @@ -379,7 +379,7 @@ namespace MWGui } } - void InteractiveMessageBox::enterPressed() + void InteractiveMessageBox::okayPressed() { std::string ok = Misc::StringUtils::lowerCase(MyGUI::LanguageManager::getInstance().replaceTags("#{sOK}")); diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp index 0e47b0323..4ef645f5e 100644 --- a/apps/openmw/mwgui/messagebox.hpp +++ b/apps/openmw/mwgui/messagebox.hpp @@ -40,7 +40,7 @@ namespace MWGui bool removeMessageBox (MessageBox *msgbox); void setMessageBoxSpeed (int speed); - void enterPressed(); + void okayPressed(); int readPressedButton (); typedef MyGUI::delegates::CMultiDelegate1 EventHandle_Int; @@ -82,7 +82,7 @@ namespace MWGui { public: InteractiveMessageBox (MessageBoxManager& parMessageBoxManager, const std::string& message, const std::vector& buttons); - void enterPressed (); + void okayPressed (); void mousePressed (MyGUI::Widget* _widget); int readPressedButton (); diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 97bccd01f..4ce6ac0bf 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -7,6 +7,8 @@ #include #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" @@ -91,6 +93,7 @@ namespace MWGui WindowBase("openmw_settings_window.layout") { getWidget(mOkButton, "OkButton"); + getWidget(mBestAttackButton, "BestAttackButton"); getWidget(mSubtitlesButton, "SubtitlesButton"); getWidget(mCrosshairButton, "CrosshairButton"); getWidget(mResolutionList, "ResolutionList"); @@ -131,6 +134,7 @@ namespace MWGui mSubtitlesButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mCrosshairButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); + mBestAttackButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mInvertYButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onOkButtonClicked); mShadersButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onShadersToggled); @@ -170,16 +174,14 @@ namespace MWGui mResetControlsButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onResetDefaultBindings); // fill resolution list - Ogre::RenderSystem* rs = Ogre::Root::getSingleton().getRenderSystem(); - Ogre::StringVector videoModes = rs->getConfigOptions()["Video Mode"].possibleValues; + int screen = Settings::Manager::getInt("screen", "Video"); + int numDisplayModes = SDL_GetNumDisplayModes(screen); std::vector < std::pair > resolutions; - for (Ogre::StringVector::const_iterator it=videoModes.begin(); - it!=videoModes.end(); ++it) + for (int i = 0; i < numDisplayModes; i++) { - - int resX, resY; - parseResolution (resX, resY, *it); - resolutions.push_back(std::make_pair(resX, resY)); + SDL_DisplayMode mode; + SDL_GetDisplayMode(screen, i, &mode); + resolutions.push_back(std::make_pair(mode.w, mode.h)); } std::sort(resolutions.begin(), resolutions.end(), sortResolutions); for (std::vector < std::pair >::const_iterator it=resolutions.begin(); @@ -200,6 +202,7 @@ namespace MWGui mSubtitlesButton->setCaptionWithReplacing(Settings::Manager::getBool("subtitles", "GUI") ? "#{sOn}" : "#{sOff}"); mCrosshairButton->setCaptionWithReplacing(Settings::Manager::getBool("crosshair", "HUD") ? "#{sOn}" : "#{sOff}"); + mBestAttackButton->setCaptionWithReplacing(Settings::Manager::getBool("best attack", "Game") ? "#{sOn}" : "#{sOff}"); float fovVal = (Settings::Manager::getFloat("field of view", "General")-sFovMin)/(sFovMax-sFovMin); mFOVSlider->setScrollPosition(fovVal * (mFOVSlider->getScrollRange()-1)); @@ -269,15 +272,12 @@ namespace MWGui if (index == MyGUI::ITEM_NONE) return; - /* ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); dialog->open("#{sNotifyMessage67}"); dialog->eventOkClicked.clear(); dialog->eventOkClicked += MyGUI::newDelegate(this, &SettingsWindow::onResolutionAccept); dialog->eventCancelClicked.clear(); dialog->eventCancelClicked += MyGUI::newDelegate(this, &SettingsWindow::onResolutionCancel); - */ - onResolutionAccept(); } void SettingsWindow::onResolutionAccept() @@ -290,9 +290,6 @@ namespace MWGui Settings::Manager::setInt("resolution y", "Video", resY); apply(); - - MWBase::Environment::get().getWindowManager()-> - messageBox("New resolution will be applied after a restart", std::vector()); } void SettingsWindow::onResolutionCancel() @@ -361,8 +358,6 @@ namespace MWGui { Settings::Manager::setBool("fullscreen", "Video", newState); apply(); - MWBase::Environment::get().getWindowManager()-> - messageBox("Fullscreen will be applied after a restart", std::vector()); } } else if (_sender == mVSyncButton) @@ -407,6 +402,8 @@ namespace MWGui Settings::Manager::setBool("crosshair", "HUD", newState); else if (_sender == mSubtitlesButton) Settings::Manager::setBool("subtitles", "GUI", newState); + else if (_sender == mBestAttackButton) + Settings::Manager::setBool("best attack", "Game", newState); apply(); } diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index 20e9907d9..42ed5bf6d 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -32,6 +32,7 @@ namespace MWGui MyGUI::ScrollBar* mToolTipDelaySlider; MyGUI::Button* mSubtitlesButton; MyGUI::Button* mCrosshairButton; + MyGUI::Button* mBestAttackButton; // graphics MyGUI::ListBox* mResolutionList; diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 38bc0481d..72d44cda5 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -663,7 +663,13 @@ namespace MWGui void WindowManager::enterPressed () { - mMessageBoxManager->enterPressed(); + mMessageBoxManager->okayPressed(); + } + + void WindowManager::activateKeyPressed () + { + mMessageBoxManager->okayPressed(); + mCountDialog->cancel(); } int WindowManager::readPressedButton () @@ -877,49 +883,31 @@ namespace MWGui setUseHardwareCursors(Settings::Manager::getBool("hardware cursors", "GUI")); - //bool changeRes = false; - bool windowRecreated = false; for (Settings::CategorySettingVector::const_iterator it = changed.begin(); it != changed.end(); ++it) { - /*if (it->first == "Video" && ( - it->second == "resolution x" - || it->second == "resolution y")) - { - changeRes = true; - }*/ - if (it->first == "Video" && it->second == "vsync") - windowRecreated = true; - else if (it->first == "HUD" && it->second == "crosshair") + if (it->first == "HUD" && it->second == "crosshair") mCrosshairEnabled = Settings::Manager::getBool ("crosshair", "HUD"); else if (it->first == "GUI" && it->second == "subtitles") mSubtitlesEnabled = Settings::Manager::getBool ("subtitles", "GUI"); } + } - /* - if (changeRes) - { - int x = Settings::Manager::getInt("resolution x", "Video"); - int y = Settings::Manager::getInt("resolution y", "Video"); - mHud->onResChange(x, y); - mConsole->onResChange(x, y); - mMenu->onResChange(x, y); - mSettingsWindow->center(); - mAlchemyWindow->center(); - mScrollWindow->center(); - mBookWindow->center(); - mQuickKeysMenu->center(); - mSpellBuyingWindow->center(); - mLoadingScreen->onResChange (x,y); - mDragAndDrop->mDragAndDropWidget->setSize(MyGUI::IntSize(x, y)); - mInputBlocker->setSize(MyGUI::IntSize(x,y)); - } - */ - if (windowRecreated) - { - mGuiManager->updateWindow (mRendering->getWindow ()); - mLoadingScreen->updateWindow (mRendering->getWindow ()); - } + void WindowManager::windowResized(int x, int y) + { + mHud->onResChange(x, y); + mConsole->onResChange(x, y); + mMenu->onResChange(x, y); + mSettingsWindow->center(); + mAlchemyWindow->center(); + mScrollWindow->center(); + mBookWindow->center(); + mQuickKeysMenu->center(); + mSpellBuyingWindow->center(); + mLoadingScreen->onResChange (x,y); + mDragAndDrop->mDragAndDropWidget->setSize(MyGUI::IntSize(x, y)); + mInputBlocker->setSize(MyGUI::IntSize(x,y)); + mGuiManager->windowResized(); } void WindowManager::pushGuiMode(GuiMode mode) @@ -1318,4 +1306,9 @@ namespace MWGui SDL_StopTextInput(); } + void WindowManager::setEnemy(const MWWorld::Ptr &enemy) + { + mHud->setEnemy(enemy); + } + } diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 42f2f4dc2..cc1eb6df1 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -204,6 +204,7 @@ namespace MWGui virtual void staticMessageBox(const std::string& message); virtual void removeStaticMessageBox(); virtual void enterPressed (); + virtual void activateKeyPressed (); virtual int readPressedButton (); ///< returns the index of the pressed button or -1 if no button was pressed (->MessageBoxmanager->InteractiveMessageBox) virtual void onFrame (float frameDuration); @@ -225,6 +226,8 @@ namespace MWGui virtual void processChangedSettings(const Settings::CategorySettingVector& changed); + virtual void windowResized(int x, int y); + virtual void executeInConsole (const std::string& path); virtual void setLoadingProgress (const std::string& stage, int depth, int current, int total); @@ -254,6 +257,8 @@ namespace MWGui virtual void changePointer (const std::string& name); + virtual void setEnemy (const MWWorld::Ptr& enemy); + virtual const Translation::Storage& getTranslationDataStorage() const; void onSoulgemDialogButtonPressed (int button); diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 95d98e35f..973089d75 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -20,6 +20,7 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwgui/bookwindow.hpp" +#include "../mwmechanics/creaturestats.hpp" using namespace ICS; @@ -160,6 +161,26 @@ namespace MWInput resetIdleTime (); int action = channel->getNumber(); + + if (action == A_Use) + { + MWWorld::Class::get(mPlayer.getPlayer()).getCreatureStats(mPlayer.getPlayer()).setAttackingOrSpell(currentValue); + if (currentValue == 1) + { + int type = MWMechanics::CreatureStats::AT_Chop; + bool forward = (mInputBinder->getChannel(A_MoveForward)->getValue() > 0 + || mInputBinder->getChannel(A_MoveBackward)->getValue() > 0); + bool side = (mInputBinder->getChannel(A_MoveLeft)->getValue() > 0 + || mInputBinder->getChannel(A_MoveRight)->getValue() > 0); + if (side && !forward) + type = MWMechanics::CreatureStats::AT_Slash; + if (forward && !side) + type = MWMechanics::CreatureStats::AT_Thrust; + + MWWorld::Class::get(mPlayer.getPlayer()).getCreatureStats(mPlayer.getPlayer()).setAttackType(type); + } + } + if (currentValue == 1) { // trigger action activated @@ -183,13 +204,15 @@ namespace MWInput case A_Activate: resetIdleTime(); - if (mWindows.getMode() == MWGui::GM_Container) { - toggleContainer (); - } else if (MWBase::Environment::get().getWindowManager()->isGuiMode()) { - MWBase::Environment::get().getWindowManager()->enterPressed(); - } else { - activate(); + if (mWindows.isGuiMode()) + { + if (mWindows.getMode() == MWGui::GM_Container) + toggleContainer (); + else + MWBase::Environment::get().getWindowManager()->activateKeyPressed(); } + else + activate(); break; case A_Journal: toggleJournal (); @@ -364,7 +387,7 @@ namespace MWInput if (mControlSwitch["playerviewswitch"]) { // work around preview mode toggle when pressing Alt+Tab - if (actionIsActive(A_TogglePOV) && !mInputManager->isModifierHeld(KMOD_ALT)) { + if (actionIsActive(A_TogglePOV) && !mInputManager->isModifierHeld(SDL_Keymod(KMOD_ALT))) { if (mPreviewPOVDelay <= 0.5 && (mPreviewPOVDelay += dt) > 0.5) { @@ -413,13 +436,9 @@ namespace MWInput void InputManager::processChangedSettings(const Settings::CategorySettingVector& changed) { - bool changeRes = false; for (Settings::CategorySettingVector::const_iterator it = changed.begin(); it != changed.end(); ++it) { - if (it->first == "Video" && (it->second == "resolution x" || it->second == "resolution y")) - changeRes = true; - if (it->first == "Input" && it->second == "invert y axis") mInvertY = Settings::Manager::getBool("invert y axis", "Input"); @@ -430,9 +449,6 @@ namespace MWInput mUISensitivity = Settings::Manager::getFloat("ui sensitivity", "Input"); } - - if (changeRes) - adjustMouseRegion(Settings::Manager::getInt("resolution x", "Video"), Settings::Manager::getInt("resolution y", "Video")); } bool InputManager::getControlSwitch (const std::string& sw) @@ -512,7 +528,6 @@ namespace MWInput return true; // MyGUI has no use for these events MyGUI::InputManager::getInstance().injectMousePress(mMouseX, mMouseY, sdlButtonToMyGUI(id)); - if (MyGUI::InputManager::getInstance ().getMouseFocusWidget () != 0) { MyGUI::Button* b = MyGUI::InputManager::getInstance ().getMouseFocusWidget ()->castType(false); @@ -597,15 +612,18 @@ namespace MWInput return true; } - bool InputManager::windowFocusChange(bool have_focus) + void InputManager::windowFocusChange(bool have_focus) { - return true; } - bool InputManager::windowVisibilityChange(bool visible) + void InputManager::windowVisibilityChange(bool visible) { //TODO: Pause game? - return true; + } + + void InputManager::windowResized(int x, int y) + { + mOgre.windowResized(x,y); } void InputManager::toggleMainMenu() @@ -665,10 +683,8 @@ namespace MWInput if (MyGUI::InputManager::getInstance ().isModalAny()) return; - bool gameMode = !mWindows.isGuiMode(); - // Toggle between game mode and inventory mode - if(gameMode) + if(!mWindows.isGuiMode()) mWindows.pushGuiMode(MWGui::GM_Inventory); else { @@ -685,9 +701,7 @@ namespace MWInput if (MyGUI::InputManager::getInstance ().isModalAny()) return; - bool gameMode = !mWindows.isGuiMode(); - - if(!gameMode) + if(mWindows.isGuiMode()) { if (mWindows.getMode() == MWGui::GM_Container) mWindows.popGuiMode(); @@ -697,17 +711,14 @@ namespace MWInput } - void InputManager::toggleConsole() { if (MyGUI::InputManager::getInstance ().isModalAny()) return; - bool gameMode = !mWindows.isGuiMode(); - // Switch to console mode no matter what mode we are currently // in, except of course if we are already in console mode - if (!gameMode) + if (mWindows.isGuiMode()) { if (mWindows.getMode() == MWGui::GM_Console) mWindows.popGuiMode(); @@ -724,9 +735,7 @@ namespace MWInput return; // Toggle between game mode and journal mode - bool gameMode = !mWindows.isGuiMode(); - - if(gameMode && MWBase::Environment::get().getWindowManager ()->getJournalAllowed()) + if(!mWindows.isGuiMode() && MWBase::Environment::get().getWindowManager ()->getJournalAllowed()) { MWBase::Environment::get().getSoundManager()->playSound ("book open", 1.0, 1.0); mWindows.pushGuiMode(MWGui::GM_Journal); diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index 0c7940fd6..ef7ef75a8 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -94,8 +94,9 @@ namespace MWInput virtual bool mouseReleased( const SDL_MouseButtonEvent &arg, Uint8 id ); virtual bool mouseMoved( const SFO::MouseMotionEvent &arg ); - virtual bool windowVisibilityChange( bool visible ); - virtual bool windowFocusChange( bool have_focus ); + virtual void windowVisibilityChange( bool visible ); + virtual void windowFocusChange( bool have_focus ); + virtual void windowResized (int x, int y); virtual void channelChanged(ICS::Channel* channel, float currentValue, float previousValue); diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index baa264aa4..fb273c7c1 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -221,7 +221,11 @@ namespace MWMechanics for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();iter++) { - if(!MWWorld::Class::get(iter->first).getCreatureStats(iter->first).isDead()) + const MWWorld::Class &cls = MWWorld::Class::get(iter->first); + CreatureStats &stats = cls.getCreatureStats(iter->first); + + stats.setLastHitObject(std::string()); + if(!stats.isDead()) { if(iter->second->isDead()) iter->second->resurrect(); @@ -230,7 +234,7 @@ namespace MWMechanics if(iter->first.getTypeName() == typeid(ESM::NPC).name()) updateNpc(iter->first, totalDuration, paused); - if(!MWWorld::Class::get(iter->first).getCreatureStats(iter->first).isDead()) + if(!stats.isDead()) continue; } @@ -238,16 +242,15 @@ namespace MWMechanics // \todo remove workaround, once player death can be handled if(iter->first.getRefData().getHandle()=="player") { - MWMechanics::DynamicStat stat ( - MWWorld::Class::get(iter->first).getCreatureStats(iter->first).getHealth()); + MWMechanics::DynamicStat stat(stats.getHealth()); - if (stat.getModified()<1) + if(stat.getModified()<1) { - stat.setModified (1, 0); - MWWorld::Class::get(iter->first).getCreatureStats(iter->first).setHealth(stat); + stat.setModified(1, 0); + stats.setHealth(stat); } - MWWorld::Class::get(iter->first).getCreatureStats(iter->first).resurrect(); + stats.resurrect(); continue; } @@ -256,11 +259,10 @@ namespace MWMechanics iter->second->kill(); - ++mDeathCount[MWWorld::Class::get(iter->first).getId(iter->first)]; + ++mDeathCount[cls.getId(iter->first)]; - if(MWWorld::Class::get(iter->first).isEssential(iter->first)) - MWBase::Environment::get().getWindowManager()->messageBox( - "#{sKilledEssential}"); + if(cls.isEssential(iter->first)) + MWBase::Environment::get().getWindowManager()->messageBox("#{sKilledEssential}"); } } diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index f0db91bac..5bfdcf0a7 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -35,6 +35,23 @@ #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" +namespace +{ + +int getBestAttack (const ESM::Weapon* weapon) +{ + int slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1])/2; + int chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1])/2; + int thrust = (weapon->mData.mThrust[0] + weapon->mData.mThrust[1])/2; + if (slash >= chop && slash >= thrust) + return MWMechanics::CreatureStats::AT_Slash; + else if (chop >= slash && chop >= thrust) + return MWMechanics::CreatureStats::AT_Chop; + else + return MWMechanics::CreatureStats::AT_Thrust; +} + +} namespace MWMechanics { @@ -215,6 +232,67 @@ void CharacterController::getWeaponGroup(WeaponType weaptype, std::string &group } +MWWorld::ContainerStoreIterator CharacterController::getActiveWeapon(NpcStats &stats, MWWorld::InventoryStore &inv, WeaponType *weaptype) +{ + if(stats.getDrawState() == DrawState_Spell) + { + *weaptype = WeapType_Spell; + return inv.end(); + } + + if(stats.getDrawState() == MWMechanics::DrawState_Weapon) + { + MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if(weapon == inv.end()) + *weaptype = WeapType_HandToHand; + else + { + const std::string &type = weapon->getTypeName(); + if(type == typeid(ESM::Lockpick).name() || type == typeid(ESM::Probe).name()) + *weaptype = WeapType_PickProbe; + else if(type == typeid(ESM::Weapon).name()) + { + MWWorld::LiveCellRef *ref = weapon->get(); + ESM::Weapon::Type type = (ESM::Weapon::Type)ref->mBase->mData.mType; + switch(type) + { + case ESM::Weapon::ShortBladeOneHand: + case ESM::Weapon::LongBladeOneHand: + case ESM::Weapon::BluntOneHand: + case ESM::Weapon::AxeOneHand: + case ESM::Weapon::Arrow: + case ESM::Weapon::Bolt: + *weaptype = WeapType_OneHand; + break; + case ESM::Weapon::LongBladeTwoHand: + case ESM::Weapon::BluntTwoClose: + case ESM::Weapon::AxeTwoHand: + *weaptype = WeapType_TwoHand; + break; + case ESM::Weapon::BluntTwoWide: + case ESM::Weapon::SpearTwoWide: + *weaptype = WeapType_TwoWide; + break; + case ESM::Weapon::MarksmanBow: + *weaptype = WeapType_BowAndArrow; + break; + case ESM::Weapon::MarksmanCrossbow: + *weaptype = WeapType_Crossbow; + break; + case ESM::Weapon::MarksmanThrown: + *weaptype = WeapType_ThowWeapon; + break; + } + } + } + + return weapon; + } + + return inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); +} + + CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim) : mPtr(ptr) , mAnimation(anim) @@ -222,22 +300,33 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim , mMovementState(CharState_None) , mMovementSpeed(0.0f) , mDeathState(CharState_None) + , mUpperBodyState(UpperCharState_Nothing) , mWeaponType(WeapType_None) , mSkipAnim(false) , mSecondsOfRunning(0) , mSecondsOfSwimming(0) - , mUpdateWeapon(true) { if(!mAnimation) return; - if(MWWorld::Class::get(mPtr).isActor()) + const MWWorld::Class &cls = MWWorld::Class::get(mPtr); + if(cls.isActor()) { /* Accumulate along X/Y only for now, until we can figure out how we should * handle knockout and death which moves the character down. */ mAnimation->setAccumulation(Ogre::Vector3(1.0f, 1.0f, 0.0f)); - if(!MWWorld::Class::get(mPtr).getCreatureStats(mPtr).isDead()) + if(mPtr.getTypeName() == typeid(ESM::NPC).name()) + { + getActiveWeapon(cls.getNpcStats(mPtr), cls.getInventoryStore(mPtr), &mWeaponType); + if(mWeaponType != WeapType_None) + { + getWeaponGroup(mWeaponType, mCurrentWeapon); + mUpperBodyState = UpperCharState_WeapEquiped; + } + } + + if(!cls.getCreatureStats(mPtr).isDead()) mIdleState = CharState_Idle; else { @@ -277,6 +366,223 @@ void CharacterController::updatePtr(const MWWorld::Ptr &ptr) } +bool CharacterController::updateNpcState() +{ + const MWWorld::Class &cls = MWWorld::Class::get(mPtr); + CreatureStats &crstats = cls.getCreatureStats(mPtr); + NpcStats &stats = cls.getNpcStats(mPtr); + WeaponType weaptype = WeapType_None; + MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); + MWWorld::ContainerStoreIterator weapon = getActiveWeapon(stats, inv, &weaptype); + + bool forcestateupdate = false; + if(weaptype != mWeaponType) + { + forcestateupdate = true; + + std::string weapgroup; + if(weaptype == WeapType_None) + { + getWeaponGroup(mWeaponType, weapgroup); + mAnimation->play(weapgroup, Priority_Weapon, + MWRender::Animation::Group_UpperBody, true, + 1.0f, "unequip start", "unequip stop", 0.0f, 0); + mUpperBodyState = UpperCharState_UnEquipingWeap; + } + else + { + getWeaponGroup(weaptype, weapgroup); + mAnimation->showWeapons(false); + mAnimation->play(weapgroup, Priority_Weapon, + MWRender::Animation::Group_UpperBody, true, + 1.0f, "equip start", "equip stop", 0.0f, 0); + mUpperBodyState = UpperCharState_EquipingWeap; + } + + if(weapon != inv.end() && !(weaptype == WeapType_None && mWeaponType == WeapType_Spell)) + { + std::string soundid = (weaptype == WeapType_None) ? + MWWorld::Class::get(*weapon).getDownSoundId(*weapon) : + MWWorld::Class::get(*weapon).getUpSoundId(*weapon); + if(!soundid.empty()) + { + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + sndMgr->playSound3D(mPtr, soundid, 1.0f, 1.0f); + } + } + + mWeaponType = weaptype; + getWeaponGroup(mWeaponType, mCurrentWeapon); + } + + + bool isWeapon = (weapon != inv.end() && weapon->getTypeName() == typeid(ESM::Weapon).name()); + float weapSpeed = 1.0f; + if(isWeapon) + weapSpeed = weapon->get()->mBase->mData.mSpeed; + + float complete; + bool animPlaying; + if(crstats.getAttackingOrSpell()) + { + if(mUpperBodyState == UpperCharState_WeapEquiped) + { + mAttackType.clear(); + if(mWeaponType == WeapType_Spell) + { + const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + + const std::string spellid = crstats.getSpells().getSelectedSpell(); + if(!spellid.empty()) + { + static const std::string schools[] = { + "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" + }; + + const ESM::Spell *spell = store.get().find(spellid); + const ESM::ENAMstruct &effectentry = spell->mEffects.mList.at(0); + + const ESM::MagicEffect *effect; + effect = store.get().find(effectentry.mEffectID); + + switch(effectentry.mRange) + { + case 0: mAttackType = "self"; break; + case 1: mAttackType = "touch"; break; + case 2: mAttackType = "target"; break; + } + + mAnimation->play(mCurrentWeapon, Priority_Weapon, + MWRender::Animation::Group_UpperBody, true, + weapSpeed, mAttackType+" start", mAttackType+" stop", + 0.0f, 0); + mUpperBodyState = UpperCharState_CastingSpell; + + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + if(!effect->mCastSound.empty()) + sndMgr->playSound3D(mPtr, effect->mCastSound, 1.0f, 1.0f); + else + sndMgr->playSound3D(mPtr, schools[effect->mData.mSchool]+" cast", 1.0f, 1.0f); + } + } + else if(mWeaponType != WeapType_PickProbe) + { + if(mWeaponType == WeapType_Crossbow || mWeaponType == WeapType_BowAndArrow || + mWeaponType == WeapType_ThowWeapon) + mAttackType = "shoot"; + else + { + int attackType = crstats.getAttackType(); + if(isWeapon && Settings::Manager::getBool("best attack", "Game")) + attackType = getBestAttack(weapon->get()->mBase); + + if (attackType == MWMechanics::CreatureStats::AT_Chop) + mAttackType = "chop"; + else if (attackType == MWMechanics::CreatureStats::AT_Slash) + mAttackType = "slash"; + else + mAttackType = "thrust"; + } + + mAnimation->play(mCurrentWeapon, Priority_Weapon, + MWRender::Animation::Group_UpperBody, false, + weapSpeed, mAttackType+" start", mAttackType+" min attack", + 0.0f, 0); + mUpperBodyState = UpperCharState_StartToMinAttack; + } + } + animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); + } + else + { + animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); + if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack) + { + if(mAttackType != "shoot") + { + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + // NOTE: SwishL, SwishM, SwishS - large, medium, small. + // Based on weapon weight, speed, or attack strength? + sndMgr->playSound3D(mPtr, "SwishL", 1.0f, 1.0f); + } + stats.setAttackStrength(complete); + + mAnimation->disable(mCurrentWeapon); + mAnimation->play(mCurrentWeapon, Priority_Weapon, + MWRender::Animation::Group_UpperBody, false, + weapSpeed, mAttackType+" max attack", mAttackType+" min hit", + 1.0f-complete, 0); + mUpperBodyState = UpperCharState_MaxAttackToMinHit; + } + } + + if(!animPlaying) + { + if(mUpperBodyState == UpperCharState_EquipingWeap || + mUpperBodyState == UpperCharState_FollowStartToFollowStop || + mUpperBodyState == UpperCharState_CastingSpell) + mUpperBodyState = UpperCharState_WeapEquiped; + else if(mUpperBodyState == UpperCharState_UnEquipingWeap) + mUpperBodyState = UpperCharState_Nothing; + } + else if(complete >= 1.0f) + { + if(mUpperBodyState == UpperCharState_StartToMinAttack) + { + mAnimation->disable(mCurrentWeapon); + mAnimation->play(mCurrentWeapon, Priority_Weapon, + MWRender::Animation::Group_UpperBody, false, + weapSpeed, mAttackType+" min attack", mAttackType+" max attack", + 0.0f, 0); + mUpperBodyState = UpperCharState_MinAttackToMaxAttack; + } + else if(mUpperBodyState == UpperCharState_MaxAttackToMinHit) + { + mAnimation->disable(mCurrentWeapon); + if(mAttackType == "shoot") + mAnimation->play(mCurrentWeapon, Priority_Weapon, + MWRender::Animation::Group_UpperBody, false, + weapSpeed, mAttackType+" min hit", mAttackType+" follow start", + 0.0f, 0); + else + mAnimation->play(mCurrentWeapon, Priority_Weapon, + MWRender::Animation::Group_UpperBody, false, + weapSpeed, mAttackType+" min hit", mAttackType+" hit", + 0.0f, 0); + mUpperBodyState = UpperCharState_MinHitToHit; + } + else if(mUpperBodyState == UpperCharState_MinHitToHit) + { + mAnimation->disable(mCurrentWeapon); + if(mAttackType == "shoot") + mAnimation->play(mCurrentWeapon, Priority_Weapon, + MWRender::Animation::Group_UpperBody, true, + weapSpeed, mAttackType+" follow start", mAttackType+" follow stop", + 0.0f, 0); + else + mAnimation->play(mCurrentWeapon, Priority_Weapon, + MWRender::Animation::Group_UpperBody, true, + weapSpeed, mAttackType+" large follow start", mAttackType+" large follow stop", + 0.0f, 0); + mUpperBodyState = UpperCharState_FollowStartToFollowStop; + } + } + + + MWWorld::ContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); + if(torch != inv.end() && torch->getTypeName() == typeid(ESM::Light).name()) + { + if(!mAnimation->isPlaying("torch")) + mAnimation->play("torch", Priority_Torch, + MWRender::Animation::Group_LeftArm, false, + 1.0f, "start", "stop", 0.0f, (~(size_t)0)); + } + else if(mAnimation->isPlaying("torch")) + mAnimation->disable("torch"); + + return forcestateupdate; +} + void CharacterController::update(float duration, Movement &movement) { const MWWorld::Class &cls = MWWorld::Class::get(mPtr); @@ -418,117 +724,7 @@ void CharacterController::update(float duration, Movement &movement) movement.mRotation[2] += rot.z; if(mPtr.getTypeName() == typeid(ESM::NPC).name()) - { - NpcStats &stats = cls.getNpcStats(mPtr); - WeaponType weaptype = WeapType_None; - MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); - MWWorld::ContainerStoreIterator weapon = inv.end(); - - if(stats.getDrawState() == DrawState_Spell) - weaptype = WeapType_Spell; - else if(stats.getDrawState() == MWMechanics::DrawState_Weapon) - { - weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if(weapon == inv.end()) - weaptype = WeapType_HandToHand; - else - { - const std::string &type = weapon->getTypeName(); - if(type == typeid(ESM::Lockpick).name() || type == typeid(ESM::Probe).name()) - weaptype = WeapType_PickProbe; - else if(type == typeid(ESM::Weapon).name()) - { - MWWorld::LiveCellRef *ref = weapon->get(); - ESM::Weapon::Type type = (ESM::Weapon::Type)ref->mBase->mData.mType; - switch(type) - { - case ESM::Weapon::ShortBladeOneHand: - case ESM::Weapon::LongBladeOneHand: - case ESM::Weapon::BluntOneHand: - case ESM::Weapon::AxeOneHand: - case ESM::Weapon::Arrow: - case ESM::Weapon::Bolt: - weaptype = WeapType_OneHand; - break; - case ESM::Weapon::LongBladeTwoHand: - case ESM::Weapon::BluntTwoClose: - case ESM::Weapon::AxeTwoHand: - weaptype = WeapType_TwoHand; - break; - case ESM::Weapon::BluntTwoWide: - case ESM::Weapon::SpearTwoWide: - weaptype = WeapType_TwoWide; - break; - case ESM::Weapon::MarksmanBow: - weaptype = WeapType_BowAndArrow; - break; - case ESM::Weapon::MarksmanCrossbow: - weaptype = WeapType_Crossbow; - break; - case ESM::Weapon::MarksmanThrown: - weaptype = WeapType_ThowWeapon; - break; - } - } - } - } - else - weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - - if(mUpdateWeapon) - { - forcestateupdate = (mWeaponType != weaptype); - mWeaponType = weaptype; - mUpdateWeapon = false; - } - - if(weaptype != mWeaponType) - { - forcestateupdate = true; - - std::string weapgroup; - if(weaptype == WeapType_None) - { - getWeaponGroup(mWeaponType, weapgroup); - mAnimation->play(weapgroup, Priority_Weapon, - MWRender::Animation::Group_UpperBody, true, - 1.0f, "unequip start", "unequip stop", 0.0f, 0); - } - else - { - getWeaponGroup(weaptype, weapgroup); - mAnimation->showWeapons(false); - mAnimation->play(weapgroup, Priority_Weapon, - MWRender::Animation::Group_UpperBody, true, - 1.0f, "equip start", "equip stop", 0.0f, 0); - } - - mWeaponType = weaptype; - - if(weapon != inv.end()) - { - std::string soundid = (mWeaponType == WeapType_None) ? - MWWorld::Class::get(*weapon).getDownSoundId(*weapon) : - MWWorld::Class::get(*weapon).getUpSoundId(*weapon); - if(!soundid.empty()) - { - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - sndMgr->playSound3D(mPtr, soundid, 1.0f, 1.0f); - } - } - } - - MWWorld::ContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); - if(torch != inv.end() && torch->getTypeName() == typeid(ESM::Light).name()) - { - if(!mAnimation->isPlaying("torch")) - mAnimation->play("torch", Priority_Torch, - MWRender::Animation::Group_LeftArm, false, - 1.0f, "start", "stop", 0.0f, (~(size_t)0)); - } - else if(mAnimation->isPlaying("torch")) - mAnimation->disable("torch"); - } + forcestateupdate = updateNpcState(); refreshCurrentAnims(idlestate, movestate, forcestateupdate); } diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index ea354a6d7..3cd23b57b 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -5,6 +5,12 @@ #include "../mwworld/ptr.hpp" +namespace MWWorld +{ + class ContainerStoreIterator; + class InventoryStore; +} + namespace MWRender { class Animation; @@ -14,6 +20,7 @@ namespace MWMechanics { class Movement; +class NpcStats; enum Priority { Priority_Default, @@ -95,6 +102,19 @@ enum WeaponType { WeapType_Spell }; +enum UpperBodyCharacterState { + UpperCharState_Nothing, + UpperCharState_EquipingWeap, + UpperCharState_UnEquipingWeap, + UpperCharState_WeapEquiped, + UpperCharState_StartToMinAttack, + UpperCharState_MinAttackToMaxAttack, + UpperCharState_MaxAttackToMinHit, + UpperCharState_MinHitToHit, + UpperCharState_FollowStartToFollowStop, + UpperCharState_CastingSpell +}; + class CharacterController { MWWorld::Ptr mPtr; @@ -113,22 +133,31 @@ class CharacterController CharacterState mDeathState; std::string mCurrentDeath; - WeaponType mWeaponType; - bool mSkipAnim; + UpperBodyCharacterState mUpperBodyState; - // Workaround for playing weapon draw animation and sound when going to new cell - bool mUpdateWeapon; + WeaponType mWeaponType; + std::string mCurrentWeapon; + + bool mSkipAnim; // counted for skill increase float mSecondsOfSwimming; float mSecondsOfRunning; + std::string mAttackType; // slash, chop or thrust + void refreshCurrentAnims(CharacterState idle, CharacterState movement, bool force=false); static void getWeaponGroup(WeaponType weaptype, std::string &group); + static MWWorld::ContainerStoreIterator getActiveWeapon(NpcStats &stats, + MWWorld::InventoryStore &inv, + WeaponType *weaptype); + void clearAnimQueue(); + bool updateNpcState(); + public: CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim); virtual ~CharacterController(); diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 569ccd592..5b4621905 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -12,7 +12,8 @@ namespace MWMechanics CreatureStats::CreatureStats() : mLevel (0), mLevelHealthBonus(0.f), mDead (false), mDied (false), mFriendlyHits (0), mTalkedTo (false), mAlarmed (false), - mAttacked (false), mHostile (false) + mAttacked (false), mHostile (false), + mAttackingOrSpell(false) { for (int i=0; i<4; ++i) mAiSettings[i] = 0; @@ -109,6 +110,11 @@ namespace MWMechanics return mMagicEffects; } + const bool &CreatureStats::getAttackingOrSpell() const + { + return mAttackingOrSpell; + } + int CreatureStats::getLevel() const { return mLevel; @@ -210,6 +216,11 @@ namespace MWMechanics mMagicEffects = effects; } + void CreatureStats::setAttackingOrSpell(const bool &attackingOrSpell) + { + mAttackingOrSpell = attackingOrSpell; + } + void CreatureStats::setAiSetting (int index, int value) { assert (index>=0 && index<4); @@ -307,4 +318,14 @@ namespace MWMechanics { return false; } + + void CreatureStats::setLastHitObject(const std::string& objectid) + { + mLastHitObject = objectid; + } + + const std::string &CreatureStats::getLastHitObject() const + { + return mLastHitObject; + } } diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index da8b5842a..fe46ed072 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -34,6 +34,11 @@ namespace MWMechanics bool mAlarmed; bool mAttacked; bool mHostile; + bool mAttackingOrSpell;//for the player, this is true if the left mouse button is pressed, false if not. + + int mAttackType; + + std::string mLastHitObject; // The last object to hit this actor public: CreatureStats(); @@ -54,6 +59,8 @@ namespace MWMechanics const MagicEffects & getMagicEffects() const; + const bool & getAttackingOrSpell() const; + int getLevel() const; int getAiSetting (int index) const; @@ -83,6 +90,17 @@ namespace MWMechanics void setMagicEffects(const MagicEffects &effects); + void setAttackingOrSpell(const bool &attackingOrSpell); + + enum AttackType + { + AT_Slash, + AT_Thrust, + AT_Chop + }; + void setAttackType(int attackType) { mAttackType = attackType; } + int getAttackType() { return mAttackType; } + void setLevel(int level); void setAiSetting (int index, int value); @@ -139,6 +157,9 @@ namespace MWMechanics void setHostile (bool hostile); bool getCreatureTargetted() const; + + void setLastHitObject(const std::string &objectid); + const std::string &getLastHitObject() const; }; } diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index def91a6c5..50c127856 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -31,6 +31,7 @@ MWMechanics::NpcStats::NpcStats() , mWerewolf (false) , mWerewolfKills (0) , mProfit(0) +, mAttackStrength(0.0f) { mSkillIncreases.resize (ESM::Attribute::Length); for (int i=0; isecond != tag) + const NifOgre::TextKeyMap::const_iterator groupstart = findGroupStart(keys, groupname); + + std::string starttag = groupname+": "+start; + NifOgre::TextKeyMap::const_iterator startkey(groupstart); + while(startkey != keys.end() && startkey->second != starttag) startkey++; if(startkey == keys.end() && start == "loop start") { - tag = groupname+": start"; - startkey = keys.begin(); - while(startkey != keys.end() && startkey->second != tag) + starttag = groupname+": start"; + startkey = groupstart; + while(startkey != keys.end() && startkey->second != starttag) startkey++; } if(startkey == keys.end()) return false; - tag = groupname+": "+stop; - NifOgre::TextKeyMap::const_iterator stopkey(startkey); - while(stopkey != keys.end() && stopkey->second != tag) + const std::string stoptag = groupname+": "+stop; + NifOgre::TextKeyMap::const_iterator stopkey(groupstart); + while(stopkey != keys.end() && stopkey->second != stoptag) stopkey++; if(stopkey == keys.end()) return false; - if(startkey == stopkey) + if(startkey->first > stopkey->first) return false; - state.mStartKey = startkey; - state.mLoopStartKey = startkey; - state.mStopKey = stopkey; - state.mNextKey = startkey; + state.mStartTime = startkey->first; + state.mLoopStartTime = startkey->first; + state.mLoopStopTime = stopkey->first; + state.mStopTime = stopkey->first; - state.mTime = state.mStartKey->first + ((state.mStopKey->first - state.mStartKey->first) * startpoint); - - tag = groupname+": loop start"; - while(state.mNextKey->first <= state.mTime && state.mNextKey != state.mStopKey) + state.mTime = state.mStartTime + ((state.mStopTime - state.mStartTime) * startpoint); + if(state.mTime > state.mStartTime) { - if(state.mNextKey->second == tag) - state.mLoopStartKey = state.mNextKey; - state.mNextKey++; + const std::string loopstarttag = groupname+": loop start"; + const std::string loopstoptag = groupname+": loop stop"; + NifOgre::TextKeyMap::const_iterator key(groupstart); + while(key->first <= state.mTime && key != stopkey) + { + if(key->second == loopstarttag) + state.mLoopStartTime = key->first; + else if(key->second == loopstoptag) + state.mLoopStopTime = key->first; + key++; + } } return true; } -bool Animation::doLoop(AnimState &state) -{ - if(state.mLoopCount == 0) - return false; - state.mLoopCount--; - state.mTime = state.mLoopStartKey->first; - state.mNextKey = state.mLoopStartKey; - state.mNextKey++; - state.mPlaying = true; - - return true; -} - - -bool Animation::handleTextKey(AnimState &state, const std::string &groupname, const NifOgre::TextKeyMap::const_iterator &key) +void Animation::handleTextKey(AnimState &state, const std::string &groupname, const NifOgre::TextKeyMap::const_iterator &key) { float time = key->first; const std::string &evt = key->second; @@ -508,7 +504,7 @@ bool Animation::handleTextKey(AnimState &state, const std::string &groupname, co { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); sndMgr->playSound3D(mPtr, evt.substr(7), 1.0f, 1.0f); - return true; + return; } if(evt.compare(0, 10, "soundgen: ") == 0) { @@ -521,58 +517,40 @@ bool Animation::handleTextKey(AnimState &state, const std::string &groupname, co type = MWBase::SoundManager::Play_TypeFoot; sndMgr->playSound3D(mPtr, sound, 1.0f, 1.0f, type); } - return true; + return; } if(evt.compare(0, groupname.size(), groupname) != 0 || evt.compare(groupname.size(), 2, ": ") != 0) { // Not ours, skip it - return true; + return; } size_t off = groupname.size()+2; size_t len = evt.size() - off; - if(evt.compare(off, len, "start") == 0 || evt.compare(off, len, "loop start") == 0) - { - state.mLoopStartKey = key; - return true; - } - - if(evt.compare(off, len, "loop stop") == 0 || evt.compare(off, len, "stop") == 0) - { - if(doLoop(state)) - { - if(state.mTime >= time) - return false; - } - return true; - } - - if(evt.compare(off, len, "equip attach") == 0) - { + if(evt.compare(off, len, "loop start") == 0) + state.mLoopStartTime = key->first; + else if(evt.compare(off, len, "loop stop") == 0) + state.mLoopStopTime = key->first; + else if(evt.compare(off, len, "equip attach") == 0) showWeapons(true); - return true; - } - if(evt.compare(off, len, "unequip detach") == 0) - { + else if(evt.compare(off, len, "unequip detach") == 0) showWeapons(false); - return true; - } - - /* Nothing to do for these */ - if(evt.compare(off, len, "equip start") == 0 || evt.compare(off, len, "equip stop") == 0 || - evt.compare(off, len, "unequip start") == 0 || evt.compare(off, len, "unequip stop") == 0) - return true; - - std::cerr<< "Unhandled animation textkey: "<mTextKeys; AnimState state; - if(reset(state, (*iter)->mTextKeys, groupname, start, stop, startpoint)) + if(reset(state, textkeys, groupname, start, stop, startpoint)) { state.mSource = *iter; state.mSpeedMult = speedmult; state.mLoopCount = loops; - state.mPlaying = true; + state.mPlaying = (state.mTime < state.mStopTime); state.mPriority = priority; state.mGroups = groups; state.mAutoDisable = autodisable; mStates[groupname] = state; + NifOgre::TextKeyMap::const_iterator textkey(textkeys.lower_bound(state.mTime)); + while(textkey != textkeys.end() && textkey->first <= state.mTime) + { + handleTextKey(state, groupname, textkey); + textkey++; + } + + if(state.mTime >= state.mLoopStopTime && state.mLoopCount > 0) + { + state.mLoopCount--; + state.mTime = state.mLoopStartTime; + state.mPlaying = true; + if(state.mTime >= state.mLoopStopTime) + break; + + textkey = textkeys.lower_bound(state.mTime); + while(textkey != textkeys.end() && textkey->first <= state.mTime) + { + handleTextKey(state, groupname, textkey); + textkey++; + } + } + break; } } if(iter == mAnimSources.rend()) - std::cerr<< "Failed to find animation "<second.mTime - iter->second.mStartKey->first) / - (iter->second.mStopKey->first - iter->second.mStartKey->first); + if(complete) + { + if(iter->second.mStopTime > iter->second.mStartTime) + *complete = (iter->second.mTime - iter->second.mStartTime) / + (iter->second.mStopTime - iter->second.mStartTime); + else + *complete = (iter->second.mPlaying ? 0.0f : 1.0f); + } if(speedmult) *speedmult = iter->second.mSpeedMult; - if(start) *start = iter->second.mStartKey->second.substr(groupname.size()+2); - if(stop) *stop = iter->second.mStopKey->second.substr(groupname.size()+2); return true; } @@ -714,27 +718,52 @@ Ogre::Vector3 Animation::runAnimation(float duration) while(stateiter != mStates.end()) { AnimState &state = stateiter->second; + const NifOgre::TextKeyMap &textkeys = state.mSource->mTextKeys; + NifOgre::TextKeyMap::const_iterator textkey(textkeys.upper_bound(state.mTime)); + float timepassed = duration * state.mSpeedMult; while(state.mPlaying) { float targetTime = state.mTime + timepassed; - if(state.mNextKey->first > targetTime) + if(textkey == textkeys.end() || textkey->first > targetTime) { if(mNonAccumCtrl && stateiter->first == mAnimationValuePtr[0]->getAnimName()) updatePosition(state.mTime, targetTime, movement); - state.mTime = targetTime; - break; + state.mTime = std::min(targetTime, state.mStopTime); + } + else + { + if(mNonAccumCtrl && stateiter->first == mAnimationValuePtr[0]->getAnimName()) + updatePosition(state.mTime, textkey->first, movement); + state.mTime = textkey->first; } - NifOgre::TextKeyMap::const_iterator key(state.mNextKey++); - if(mNonAccumCtrl && stateiter->first == mAnimationValuePtr[0]->getAnimName()) - updatePosition(state.mTime, key->first, movement); - state.mTime = key->first; - - state.mPlaying = (key != state.mStopKey); + state.mPlaying = (state.mTime < state.mStopTime); timepassed = targetTime - state.mTime; - if(!handleTextKey(state, stateiter->first, key)) + while(textkey != textkeys.end() && textkey->first <= state.mTime) + { + handleTextKey(state, stateiter->first, textkey); + textkey++; + } + + if(state.mTime >= state.mLoopStopTime && state.mLoopCount > 0) + { + state.mLoopCount--; + state.mTime = state.mLoopStartTime; + state.mPlaying = true; + if(state.mTime >= state.mLoopStopTime) + break; + + textkey = textkeys.lower_bound(state.mTime); + while(textkey != textkeys.end() && textkey->first <= state.mTime) + { + handleTextKey(state, stateiter->first, textkey); + textkey++; + } + } + + if(timepassed <= 0.0f) break; } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index bc7fd3d22..0b8f3a02f 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -52,6 +52,15 @@ protected: virtual void setValue(Ogre::Real value); }; + class NullAnimationValue : public Ogre::ControllerValue + { + public: + virtual Ogre::Real getValue() const + { return 0.0f; } + virtual void setValue(Ogre::Real value) + { } + }; + struct AnimSource : public Ogre::AnimationAlloc { NifOgre::TextKeyMap mTextKeys; std::vector > mControllers[sNumGroups]; @@ -60,10 +69,10 @@ protected: struct AnimState { Ogre::SharedPtr mSource; - NifOgre::TextKeyMap::const_iterator mStartKey; - NifOgre::TextKeyMap::const_iterator mLoopStartKey; - NifOgre::TextKeyMap::const_iterator mStopKey; - NifOgre::TextKeyMap::const_iterator mNextKey; + float mStartTime; + float mLoopStartTime; + float mLoopStopTime; + float mStopTime; float mTime; float mSpeedMult; @@ -75,7 +84,8 @@ protected: int mGroups; bool mAutoDisable; - AnimState() : mTime(0.0f), mSpeedMult(1.0f), mPlaying(false), mLoopCount(0), + AnimState() : mStartTime(0.0f), mLoopStartTime(0.0f), mLoopStopTime(0.0f), mStopTime(0.0f), + mTime(0.0f), mSpeedMult(1.0f), mPlaying(false), mLoopCount(0), mPriority(0), mGroups(0), mAutoDisable(true) { } }; @@ -98,6 +108,7 @@ protected: AnimStateMap mStates; Ogre::SharedPtr mAnimationValuePtr[sNumGroups]; + Ogre::SharedPtr mNullAnimationValuePtr; ObjectAttachMap mAttachedObjects; @@ -131,9 +142,7 @@ protected: const std::string &groupname, const std::string &start, const std::string &stop, float startpoint); - bool doLoop(AnimState &state); - - bool handleTextKey(AnimState &state, const std::string &groupname, const NifOgre::TextKeyMap::const_iterator &key); + void handleTextKey(AnimState &state, const std::string &groupname, const NifOgre::TextKeyMap::const_iterator &key); /* Sets the root model of the object. If 'baseonly' is true, then any meshes or particle * systems in the model are ignored (useful for NPCs, where only the skeleton is needed for @@ -199,11 +208,9 @@ public: * \param groupname Animation group to check. * \param complete Stores completion amount (0 = at start key, 0.5 = half way between start and stop keys), etc. * \param speedmult Stores the animation speed multiplier - * \param start Stores the start key - * \param stop Stores the stop key * \return True if the animation is active, false otherwise. */ - bool getInfo(const std::string &groupname, float *complete=NULL, float *speedmult=NULL, std::string *start=NULL, std::string *stop=NULL) const; + bool getInfo(const std::string &groupname, float *complete=NULL, float *speedmult=NULL) const; /** Disables the specified animation group; * \param groupname Animation group to disable. diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index f8f954bf3..941b8fde7 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -25,7 +25,11 @@ namespace MWRender mHeight(128.f), mCameraDistance(300.f), mDistanceAdjusted(false), - mAnimation(NULL) + mAnimation(NULL), + mNearest(30.f), + mFurthest(800.f), + mIsNearest(false), + mIsFurthest(false) { mVanity.enabled = false; mVanity.allowed = true; @@ -232,16 +236,21 @@ namespace MWRender if(mFirstPersonView && !mPreviewMode && !mVanity.enabled) return; + mIsFurthest = false; + mIsNearest = false; + Ogre::Vector3 v(0.f, 0.f, dist); if (adjust) { v += mCamera->getPosition(); } - if (v.z > 800.f) { - v.z = 800.f; - } else if (v.z < 10.f) { + if (v.z >= mFurthest) { + v.z = mFurthest; + mIsFurthest = true; + } else if (!override && v.z < 10.f) { v.z = 10.f; - } else if (override && v.z < 50.f) { - v.z = 50.f; + } else if (override && v.z <= mNearest) { + v.z = mNearest; + mIsNearest = true; } mCamera->setPosition(v); @@ -302,34 +311,13 @@ namespace MWRender } } - float Camera::getHeight() - { - if(mCamera->isParentTagPoint()) - { - Ogre::TagPoint *tag = static_cast(mCamera->getParentNode()); - return tag->_getFullLocalTransform().getTrans().z; - } - return mCamera->getParentNode()->getPosition().z; - } - - bool Camera::getPosition(Ogre::Vector3 &player, Ogre::Vector3 &camera) + void Camera::getPosition(Ogre::Vector3 &focal, Ogre::Vector3 &camera) { mCamera->getParentSceneNode()->needUpdate(true); + camera = mCamera->getRealPosition(); - player = mTrackingPtr.getRefData().getBaseNode()->getPosition(); - - return mFirstPersonView && !mVanity.enabled && !mPreviewMode; - } - - Ogre::Vector3 Camera::getPosition() - { - return mTrackingPtr.getRefData().getBaseNode()->getPosition(); - } - - void Camera::getSightAngles(float &pitch, float &yaw) - { - pitch = mMainCam.pitch; - yaw = mMainCam.yaw; + focal = Ogre::Vector3((mCamera->getParentNode()->_getFullTransform() * + Ogre::Vector4(0.0f, 0.0f, 0.0f, 1.0f)).ptr()); } void Camera::togglePlayerLooking(bool enable) @@ -341,4 +329,14 @@ namespace MWRender { return mPreviewMode || mVanity.enabled; } + + bool Camera::isNearest() + { + return mIsNearest; + } + + bool Camera::isFurthest() + { + return mIsFurthest; + } } diff --git a/apps/openmw/mwrender/camera.hpp b/apps/openmw/mwrender/camera.hpp index 3418efcc9..cbfbd0919 100644 --- a/apps/openmw/mwrender/camera.hpp +++ b/apps/openmw/mwrender/camera.hpp @@ -33,6 +33,10 @@ namespace MWRender bool mFirstPersonView; bool mPreviewMode; bool mFreeLook; + float mNearest; + float mFurthest; + bool mIsNearest; + bool mIsFurthest; struct { bool enabled, allowed; @@ -93,18 +97,16 @@ namespace MWRender void setAnimation(NpcAnimation *anim); - float getHeight(); - - /// Stores player and camera world positions in passed arguments - /// \return true if camera at the eye-place - bool getPosition(Ogre::Vector3 &player, Ogre::Vector3 &camera); - Ogre::Vector3 getPosition(); - - void getSightAngles(float &pitch, float &yaw); + /// Stores focal and camera world positions in passed arguments + void getPosition(Ogre::Vector3 &focal, Ogre::Vector3 &camera); void togglePlayerLooking(bool enable); bool isVanityOrPreviewModeEnabled(); + + bool isNearest(); + + bool isFurthest(); }; } diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index f2df5ccd5..df95e9f47 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -424,6 +424,10 @@ Ogre::Vector3 NpcAnimation::runAnimation(float timepassed) for(size_t i = 0;i < sPartListSize;i++) { + std::vector >::iterator ctrl(mObjectParts[i].mControllers.begin()); + for(;ctrl != mObjectParts[i].mControllers.end();ctrl++) + ctrl->update(); + Ogre::Entity *ent = mObjectParts[i].mSkelBase; if(!ent) continue; updateSkeletonInstance(baseinst, ent->getSkeleton()); @@ -481,6 +485,19 @@ bool NpcAnimation::addOrReplaceIndividualPart(int type, int group, int priority, if(type == sPartList[i].type) { mObjectParts[i] = insertBoundedPart(mesh, group, sPartList[i].name); + + // TODO: + // type == ESM::PRT_Head should get an animation source based on the current output of + // the actor's 'say' sound (0 = silent, 1 = loud?). + // type == ESM::PRT_Weapon should get an animation source based on the current offset + // of the weapon attack animation (from its beginning, or start marker?) + std::vector >::iterator ctrl(mObjectParts[i].mControllers.begin()); + for(;ctrl != mObjectParts[i].mControllers.end();ctrl++) + { + if(ctrl->getSource().isNull()) + ctrl->setSource(mNullAnimationValuePtr); + } + break; } } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 19518c2c4..2f48a0ce9 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -16,6 +16,8 @@ #include #include +#include + #include #include @@ -77,9 +79,9 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b } mRendering.createScene("PlayerCam", Settings::Manager::getFloat("field of view", "General"), 5); - mRendering.setWindowEventListener(this); mRendering.getWindow()->addListener(this); + mRendering.setWindowListener(this); mCompositors = new Compositors(mRendering.getViewport()); @@ -170,7 +172,7 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b mOcclusionQuery = new OcclusionQuery(&mRendering, mSkyManager->getSunNode()); - mVideoPlayer = new VideoPlayer(mRendering.getScene ()); + mVideoPlayer = new VideoPlayer(mRendering.getScene (), mRendering.getWindow()); mVideoPlayer->setResolution (Settings::Manager::getInt ("resolution x", "Video"), Settings::Manager::getInt ("resolution y", "Video")); mSun = 0; @@ -186,7 +188,6 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b RenderingManager::~RenderingManager () { mRendering.getWindow()->removeListener(this); - mRendering.removeWindowEventListener(this); delete mPlayerAnimation; delete mCamera; @@ -323,18 +324,18 @@ void RenderingManager::update (float duration, bool paused) // player position MWWorld::RefData &data = player.getRefData(); - float *_playerPos = data.getPosition().pos; - Ogre::Vector3 playerPos(_playerPos[0], _playerPos[1], _playerPos[2]); + Ogre::Vector3 playerPos(data.getPosition().pos); - Ogre::Vector3 orig, dest; - if(!mCamera->getPosition(orig, dest)) + mCamera->setCameraDistance(); + if(!mCamera->isFirstPerson()) { - orig.z += mCamera->getHeight() * mRootNode->getScale().z; + Ogre::Vector3 orig, dest; + mCamera->getPosition(orig, dest); btVector3 btOrig(orig.x, orig.y, orig.z); btVector3 btDest(dest.x, dest.y, dest.z); - std::pair test = mPhysicsEngine->sphereCast(mRendering.getCamera()->getNearClipDistance()*2.5, btOrig, btDest); - if (test.first) + std::pair test = mPhysicsEngine->sphereCast(mRendering.getCamera()->getNearClipDistance()*2.5, btOrig, btDest); + if(test.first) mCamera->setCameraDistance(test.second * orig.distance(dest), false, false); } @@ -718,7 +719,7 @@ Compositors* RenderingManager::getCompositors() void RenderingManager::processChangedSettings(const Settings::CategorySettingVector& settings) { - //bool changeRes = false; + bool changeRes = false; bool rebuild = false; // rebuild static geometry (necessary after any material changes) for (Settings::CategorySettingVector::const_iterator it=settings.begin(); it != settings.end(); ++it) @@ -732,11 +733,11 @@ void RenderingManager::processChangedSettings(const Settings::CategorySettingVec if (!MWBase::Environment::get().getWorld()->isCellExterior() && !MWBase::Environment::get().getWorld()->isCellQuasiExterior()) configureFog(*MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getCell()); } - /*else if (it->first == "Video" && ( + else if (it->first == "Video" && ( it->second == "resolution x" || it->second == "resolution y" || it->second == "fullscreen")) - changeRes = true;*/ + changeRes = true; else if (it->second == "field of view" && it->first == "General") mRendering.setFov(Settings::Manager::getFloat("field of view", "General")); else if ((it->second == "texture filtering" && it->first == "General") @@ -790,24 +791,31 @@ void RenderingManager::processChangedSettings(const Settings::CategorySettingVec } } - /* if (changeRes) { unsigned int x = Settings::Manager::getInt("resolution x", "Video"); unsigned int y = Settings::Manager::getInt("resolution y", "Video"); + bool fullscreen = Settings::Manager::getBool("fullscreen", "Video"); - SDL_SetWindowFullscreen(mRendering.getSDLWindow(), 0); + SDL_Window* window = mRendering.getSDLWindow(); - if (x != mRendering.getWindow()->getWidth() || y != mRendering.getWindow()->getHeight()) + SDL_SetWindowFullscreen(window, 0); + + if (SDL_GetWindowFlags(window) & SDL_WINDOW_MAXIMIZED) + SDL_RestoreWindow(window); + + if (fullscreen) { - SDL_SetWindowSize(mRendering.getSDLWindow(), x, y); - mRendering.getWindow()->resize(x, y); + SDL_DisplayMode mode; + SDL_GetWindowDisplayMode(window, &mode); + mode.w = x; + mode.h = y; + SDL_SetWindowDisplayMode(window, &mode); + SDL_SetWindowFullscreen(window, fullscreen); } - - SDL_SetWindowFullscreen(mRendering.getSDLWindow(), Settings::Manager::getBool("fullscreen", "Video") ? SDL_WINDOW_FULLSCREEN : 0); - //mRendering.getWindow()->setFullscreen(Settings::Manager::getBool("fullscreen", "Video"), x, y); + else + SDL_SetWindowSize(window, x, y); } - */ mWater->processChangedSettings(settings); @@ -825,24 +833,14 @@ void RenderingManager::setMenuTransparency(float val) tex->getBuffer()->unlock(); } -void RenderingManager::windowResized(Ogre::RenderWindow* rw) +void RenderingManager::windowResized(int x, int y) { - Settings::Manager::setInt("resolution x", "Video", rw->getWidth()); - Settings::Manager::setInt("resolution y", "Video", rw->getHeight()); - mRendering.adjustViewport(); mCompositors->recreate(); - mVideoPlayer->setResolution (rw->getWidth(), rw->getHeight()); + mVideoPlayer->setResolution (x, y); - const Settings::CategorySettingVector& changed = Settings::Manager::apply(); - MWBase::Environment::get().getInputManager()->processChangedSettings(changed); - MWBase::Environment::get().getWindowManager()->processChangedSettings(changed); -} - -void RenderingManager::windowClosed(Ogre::RenderWindow* rw) -{ - Ogre::Root::getSingleton ().queueEndRendering (); + MWBase::Environment::get().getWindowManager()->windowResized(x,y); } void RenderingManager::applyCompositors() @@ -905,7 +903,15 @@ void RenderingManager::setCameraDistance(float dist, bool adjust, bool override) { if(!mCamera->isVanityOrPreviewModeEnabled() && !mCamera->isFirstPerson()) { - mCamera->setCameraDistance(-dist / 120.f * 10, adjust, override); + if(mCamera->isNearest() && dist > 0.f) + mCamera->toggleViewMode(); + else + mCamera->setCameraDistance(-dist / 120.f * 10, adjust, override); + } + else if(mCamera->isFirstPerson() && dist < 0.f) + { + mCamera->toggleViewMode(); + mCamera->setCameraDistance(0.f, false, override); } } diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index cdcde8246..8b55d90bf 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -50,7 +50,7 @@ namespace MWRender class VideoPlayer; class Animation; -class RenderingManager: private RenderingInterface, public Ogre::WindowEventListener, public Ogre::RenderTargetListener +class RenderingManager: private RenderingInterface, public Ogre::RenderTargetListener, public OEngine::Render::WindowSizeListener { private: virtual MWRender::Objects& getObjects(); @@ -204,8 +204,7 @@ public: void frameStarted(float dt); protected: - virtual void windowResized(Ogre::RenderWindow* rw); - virtual void windowClosed(Ogre::RenderWindow* rw); + virtual void windowResized(int x, int y); private: sh::Factory* mFactory; diff --git a/apps/openmw/mwrender/videoplayer.cpp b/apps/openmw/mwrender/videoplayer.cpp index 08642c5a3..bc9bef531 100644 --- a/apps/openmw/mwrender/videoplayer.cpp +++ b/apps/openmw/mwrender/videoplayer.cpp @@ -8,6 +8,7 @@ #include #include +#include #include @@ -16,6 +17,7 @@ #include "../mwbase/soundmanager.hpp" #include "../mwsound/sound_decoder.hpp" #include "../mwsound/sound.hpp" +#include "../mwbase/inputmanager.hpp" #include "renderconst.hpp" @@ -1032,13 +1034,14 @@ public: #endif // defined OPENMW_USE_FFMPEG -VideoPlayer::VideoPlayer(Ogre::SceneManager* sceneMgr) +VideoPlayer::VideoPlayer(Ogre::SceneManager* sceneMgr, Ogre::RenderWindow* window) : mState(NULL) , mSceneMgr(sceneMgr) , mVideoMaterial(NULL) , mRectangle(NULL) , mNode(NULL) , mAllowSkipping(false) + , mWindow(window) { mVideoMaterial = Ogre::MaterialManager::getSingleton().getByName("VideoMaterial", "General"); if (mVideoMaterial.isNull ()) @@ -1129,6 +1132,14 @@ void VideoPlayer::playVideo(const std::string &resourceName, bool allowSkipping) try { mState = new VideoState; mState->init(resourceName); + + while (isPlaying()) + { + MWBase::Environment::get().getInputManager()->update(0, false); + update(); + mWindow->update(); + } + } catch(std::exception& e) { std::cerr<< "Failed to play video: "< + class OpHitOnMe : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + std::string objectID = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + MWMechanics::CreatureStats &stats = MWWorld::Class::get(ptr).getCreatureStats(ptr); + runtime.push(::Misc::StringUtils::ciEqual(objectID, stats.getLastHitObject())); + } + }; + + template + class OpEnableTeleporting : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + world->enableTeleporting(Enable); + } + }; + const int opcodeXBox = 0x200000c; const int opcodeOnActivate = 0x200000d; const int opcodeActivate = 0x2000075; @@ -650,6 +679,12 @@ namespace MWScript const int opcodePlayBink = 0x20001f7; + const int opcodeHitOnMe = 0x2000213; + const int opcodeHitOnMeExplicit = 0x2000214; + + const int opcodeDisableTeleporting = 0x2000215; + const int opcodeEnableTeleporting = 0x2000216; + void registerExtensions (Compiler::Extensions& extensions) { extensions.registerFunction ("xbox", 'l', "", opcodeXBox); @@ -692,6 +727,9 @@ namespace MWScript extensions.registerFunction ("getstandingpc", 'l', "", opcodeGetStandingPc, opcodeGetStandingPcExplicit); extensions.registerFunction ("getstandingactor", 'l', "", opcodeGetStandingActor, opcodeGetStandingActorExplicit); extensions.registerFunction ("getwindspeed", 'f', "", opcodeGetWindSpeed); + extensions.registerFunction ("hitonme", 'l', "S", opcodeHitOnMe, opcodeHitOnMeExplicit); + extensions.registerInstruction ("disableteleporting", "", opcodeDisableTeleporting); + extensions.registerInstruction ("enableteleporting", "", opcodeEnableTeleporting); } void installOpcodes (Interpreter::Interpreter& interpreter) @@ -745,6 +783,10 @@ namespace MWScript interpreter.installSegment5 (opcodeGetStandingActor, new OpGetStandingActor); interpreter.installSegment5 (opcodeGetStandingActorExplicit, new OpGetStandingActor); interpreter.installSegment5 (opcodeGetWindSpeed, new OpGetWindSpeed); + interpreter.installSegment5 (opcodeHitOnMe, new OpHitOnMe); + interpreter.installSegment5 (opcodeHitOnMeExplicit, new OpHitOnMe); + interpreter.installSegment5 (opcodeDisableTeleporting, new OpEnableTeleporting); + interpreter.installSegment5 (opcodeEnableTeleporting, new OpEnableTeleporting); } } } diff --git a/apps/openmw/mwscript/skyextensions.cpp b/apps/openmw/mwscript/skyextensions.cpp index e16c2ec23..393c9c55b 100644 --- a/apps/openmw/mwscript/skyextensions.cpp +++ b/apps/openmw/mwscript/skyextensions.cpp @@ -96,6 +96,28 @@ namespace MWScript } }; + class OpModRegion : public Interpreter::Opcode1 + { + public: + + virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) + { + std::string region = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + std::vector chances; + chances.reserve(10); + while(arg0 > 0) + { + chances.push_back(std::max(0, std::min(127, runtime[0].mInteger))); + runtime.pop(); + arg0--; + } + + MWBase::Environment::get().getWorld()->modRegion(region, chances); + } + }; + const int opcodeToggleSky = 0x2000021; const int opcodeTurnMoonWhite = 0x2000022; const int opcodeTurnMoonRed = 0x2000023; @@ -103,6 +125,7 @@ namespace MWScript const int opcodeGetSecundaPhase = 0x2000025; const int opcodeGetCurrentWeather = 0x200013f; const int opcodeChangeWeather = 0x2000140; + const int opcodeModRegion = 0x20026; void registerExtensions (Compiler::Extensions& extensions) { @@ -114,6 +137,7 @@ namespace MWScript extensions.registerFunction ("getmasserphase", 'l', "", opcodeGetMasserPhase); extensions.registerFunction ("getsecundaphase", 'l', "", opcodeGetSecundaPhase); extensions.registerFunction ("getcurrentweather", 'l', "", opcodeGetCurrentWeather); + extensions.registerInstruction ("modregion", "S/llllllllll", opcodeModRegion); } void installOpcodes (Interpreter::Interpreter& interpreter) @@ -125,6 +149,7 @@ namespace MWScript interpreter.installSegment5 (opcodeGetSecundaPhase, new OpGetSecundaPhase); interpreter.installSegment5 (opcodeGetCurrentWeather, new OpGetCurrentWeather); interpreter.installSegment5 (opcodeChangeWeather, new OpChangeWeather); + interpreter.installSegment3 (opcodeModRegion, new OpModRegion); } } } diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 8192ff5ae..1e431d54a 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -499,18 +499,14 @@ namespace MWSound soundIter = regn->mSoundList.begin(); while(soundIter != regn->mSoundList.end()) { - const std::string go = soundIter->mSound.toString(); - int chance = (int) soundIter->mChance; - //std::cout << "Sound: " << go.name <<" Chance:" << chance << "\n"; - soundIter++; - if(r - pos < chance) + if(r - pos < soundIter->mChance) { - //play sound - std::cout << "Sound: " << go <<" Chance:" << chance << "\n"; - playSound(go, 1.0f, 1.0f); + playSound(soundIter->mSound.toString(), 1.0f, 1.0f); break; } - pos += chance; + pos += soundIter->mChance; + + soundIter++; } } @@ -550,6 +546,13 @@ namespace MWSound mActiveSounds.erase(snditer++); else { + const MWWorld::Ptr &ptr = snditer->second.first; + if(!ptr.isEmpty()) + { + const ESM::Position &pos = ptr.getRefData().getPosition(); + const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]); + snditer->first->setPosition(objpos); + } snditer->first->update(); snditer++; } diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 6f420942b..123027c9c 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -13,6 +13,8 @@ #include "containerstore.hpp" #include "../mwgui/tooltips.hpp" +#include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/magiceffects.hpp" namespace MWWorld { @@ -77,6 +79,33 @@ namespace MWWorld throw std::runtime_error ("class does not have item health"); } + float Class::getEvasion(const Ptr& ptr) const + { + MWMechanics::CreatureStats &crstats = getCreatureStats(ptr); + const MWMechanics::MagicEffects &mageffects = crstats.getMagicEffects(); + float evasion = (crstats.getAttribute(ESM::Attribute::Agility).getModified() / 5.0f) + + (crstats.getAttribute(ESM::Attribute::Luck).getModified() / 10.0f); + evasion *= crstats.getFatigueTerm(); + evasion += mageffects.get(MWMechanics::EffectKey(ESM::MagicEffect::Sanctuary)).mMagnitude; + + return evasion; + } + + void Class::hit(const Ptr& ptr, int type) const + { + throw std::runtime_error("class cannot hit"); + } + + void Class::onHit(const Ptr& ptr, float damage, bool ishealth, const Ptr& object, const Ptr& attacker, bool successful) const + { + throw std::runtime_error("class cannot be hit"); + } + + void Class::setActorHealth(const Ptr& ptr, float health, const Ptr& attacker) const + { + throw std::runtime_error("class does not have actor health"); + } + boost::shared_ptr Class::activate (const Ptr& ptr, const Ptr& actor) const { return boost::shared_ptr (new NullAction); diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index eed100024..bfeeaba9f 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -105,6 +105,28 @@ namespace MWWorld ///< Return item max health or throw an exception, if class does not have item health /// (default implementation: throw an exceoption) + virtual float getEvasion(const Ptr& ptr) const; + ///< Gets the chance the given object can evade an attack + + virtual void hit(const Ptr& ptr, int type=-1) const; + ///< Execute a melee hit, using the current weapon. This will check the relevant skills + /// of the given attacker, and whoever is hit. + /// \param type - type of attack, one of the MWMechanics::CreatureStats::AttackType + /// enums. ignored for creature attacks. + /// (default implementation: throw an exceoption) + + virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, bool successful) const; + ///< Alerts \a ptr that it's being hit for \a damage points to health if \a ishealth is + /// true (else fatigue) by \a object (sword, arrow, etc). \a attacker specifies the + /// actor responsible for the attack, and \a successful specifies if the hit is + /// successful or not. + + virtual void setActorHealth(const Ptr& ptr, float health, const Ptr& attacker=Ptr()) const; + ///< Sets a new current health value for the actor, optionally specifying the object causing + /// the change. Use this instead of using CreatureStats directly as this will make sure the + /// correct dialog and actor states are properly handled when being hurt or healed. + /// (default implementation: throw an exceoption) + virtual boost::shared_ptr activate (const Ptr& ptr, const Ptr& actor) const; ///< Generate action for activation (default implementation: return a null action). diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index 388152376..9cf944656 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -304,6 +304,19 @@ namespace MWWorld return results; } + std::pair PhysicsSystem::getFacedHandle(const Ogre::Vector3 &origin_, const Ogre::Quaternion &orient_, float queryDistance) + { + Ogre::Vector3 dest_ = origin_ + orient_.yAxis()*queryDistance; + + btVector3 origin(origin_.x, origin_.y, origin_.z); + btVector3 dest(dest_.x, dest_.y, dest_.z); + + std::pair result = mEngine->rayTest(origin, dest); + result.second *= queryDistance; + return result; + } + + void PhysicsSystem::setCurrentWater(bool hasWater, int waterHeight) { // TODO: store and use diff --git a/apps/openmw/mwworld/physicssystem.hpp b/apps/openmw/mwworld/physicssystem.hpp index d7e853260..2b8cb00af 100644 --- a/apps/openmw/mwworld/physicssystem.hpp +++ b/apps/openmw/mwworld/physicssystem.hpp @@ -55,6 +55,9 @@ namespace MWWorld Ogre::Vector3 traceDown(const MWWorld::Ptr &ptr); std::pair getFacedHandle (MWWorld::World& world, float queryDistance); + std::pair getFacedHandle(const Ogre::Vector3 &origin, + const Ogre::Quaternion &orientation, + float queryDistance); std::vector < std::pair > getFacedHandles (float queryDistance); std::vector < std::pair > getFacedHandles (float mouseX, float mouseY, float queryDistance); diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 6cdb13147..587b7847b 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -537,8 +537,8 @@ void WeatherManager::stopSounds(bool stopAll) std::vector::iterator it = mSoundsPlaying.begin(); while (it!=mSoundsPlaying.end()) { - if (stopAll || \ - !((*it == mWeatherSettings[mCurrentWeather].mAmbientLoopSoundID) || \ + if (stopAll || + !((*it == mWeatherSettings[mCurrentWeather].mAmbientLoopSoundID) || (*it == mWeatherSettings[mCurrentWeather].mRainLoopSoundID))) { MWBase::Environment::get().getSoundManager()->stopSound(*it); @@ -551,29 +551,37 @@ void WeatherManager::stopSounds(bool stopAll) Ogre::String WeatherManager::nextWeather(const ESM::Region* region) const { + std::vector probability; + + RegionModMap::const_iterator iter = mRegionMods.find(Misc::StringUtils::lowerCase(region->mId)); + if(iter != mRegionMods.end()) + probability = iter->second; + else + { + probability.reserve(10); + probability.push_back(region->mData.mClear); + probability.push_back(region->mData.mCloudy); + probability.push_back(region->mData.mFoggy); + probability.push_back(region->mData.mOvercast); + probability.push_back(region->mData.mRain); + probability.push_back(region->mData.mThunder); + probability.push_back(region->mData.mAsh); + probability.push_back(region->mData.mBlight); + probability.push_back(region->mData.mA); + probability.push_back(region->mData.mB); + } + /* * All probabilities must add to 100 (responsibility of the user). * If chances A and B has values 30 and 70 then by generating * 100 numbers 1..100, 30% will be lesser or equal 30 and * 70% will be greater than 30 (in theory). */ - const int probability[] = { - region->mData.mClear, - region->mData.mCloudy, - region->mData.mFoggy, - region->mData.mOvercast, - region->mData.mRain, - region->mData.mThunder, - region->mData.mAsh, - region->mData.mBlight, - region->mData.mA, - region->mData.mB - }; // 10 elements int chance = (rand() % 100) + 1; // 1..100 int sum = 0; - int i = 0; - for (; i < 10; ++i) + unsigned int i = 0; + for (; i < probability.size(); ++i) { sum += probability[i]; if (chance < sum) @@ -681,6 +689,15 @@ void WeatherManager::changeWeather(const std::string& region, const unsigned int setWeather(weather); } +void WeatherManager::modRegion(const std::string ®ionid, const std::vector &chances) +{ + mRegionMods[Misc::StringUtils::lowerCase(regionid)] = chances; + // Start transitioning right away if the region no longer supports the current weather type + unsigned int current = getWeatherID(); + if(current >= chances.size() || chances[current] == 0) + mWeatherUpdateTime = 0.0f; +} + float WeatherManager::getWindSpeed() const { return mWindSpeed; diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index 1a787aae8..a2a07cec7 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -149,6 +149,8 @@ namespace MWWorld unsigned int getWeatherID() const; + void modRegion(const std::string ®ionid, const std::vector &chances); + private: float mHour; int mDay, mMonth; @@ -188,6 +190,9 @@ namespace MWWorld Ogre::String nextWeather(const ESM::Region* region) const; WeatherResult mResult; + typedef std::map > RegionModMap; + RegionModMap mRegionMods; + float mSunriseTime; float mSunsetTime; float mSunriseDuration; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index d839a051a..33d4719c0 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -18,6 +18,7 @@ #include "../mwmechanics/movement.hpp" #include "../mwrender/sky.hpp" +#include "../mwrender/animation.hpp" #include "../mwclass/door.hpp" @@ -165,7 +166,7 @@ namespace MWWorld : mPlayer (0), mLocalScripts (mStore), mGlobalVariables (0), mSky (true), mCells (mStore, mEsm), mNumFacing(0), mActivationDistanceOverride (mActivationDistanceOverride), - mFallback(fallbackMap), mPlayIntro(0) + mFallback(fallbackMap), mPlayIntro(0), mTeleportEnabled(true) { mPhysics = new PhysicsSystem(renderer); mPhysEngine = mPhysics->getEngine(); @@ -776,6 +777,28 @@ namespace MWWorld return object; } + MWWorld::Ptr World::getFacedObject(const MWWorld::Ptr &ptr, float distance) + { + const ESM::Position &posdata = ptr.getRefData().getPosition(); + Ogre::Vector3 pos(posdata.pos); + Ogre::Quaternion rot = Ogre::Quaternion(Ogre::Radian(posdata.rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * + Ogre::Quaternion(Ogre::Radian(posdata.rot[0]), Ogre::Vector3::UNIT_X); + + MWRender::Animation *anim = mRendering->getAnimation(ptr); + if(anim != NULL) + { + Ogre::Node *node = anim->getNode("Head"); + if(node != NULL) + pos += node->_getDerivedPosition(); + } + + std::pair result = mPhysics->getFacedHandle(pos, rot, distance); + if(result.first.empty()) + return MWWorld::Ptr(); + + return searchPtrViaHandle(result.first); + } + void World::deleteObject (const Ptr& ptr) { if (ptr.getRefData().getCount()>0) @@ -1351,6 +1374,11 @@ namespace MWWorld mWeatherManager->changeWeather(region, id); } + void World::modRegion(const std::string ®ionid, const std::vector &chances) + { + mWeatherManager->modRegion(regionid, chances); + } + OEngine::Render::Fader* World::getFader() { return mRendering->getFader(); @@ -1821,4 +1849,15 @@ namespace MWWorld } return false; } + + void World::enableTeleporting(bool enable) + { + mTeleportEnabled = enable; + } + + bool World::isTeleportingEnabled() const + { + return mTeleportEnabled; + } + } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 1b436373b..252248e40 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -117,6 +117,8 @@ namespace MWWorld int mPlayIntro; + bool mTeleportEnabled; + public: World (OEngine::Render::OgreRenderer& renderer, @@ -235,6 +237,8 @@ namespace MWWorld virtual void setMoonColour (bool red); + virtual void modRegion(const std::string ®ionid, const std::vector &chances); + virtual float getTimeScaleFactor() const; virtual void changeToInteriorCell (const std::string& cellName, @@ -252,6 +256,10 @@ namespace MWWorld virtual MWWorld::Ptr getFacedObject(); ///< Return pointer to the object the player is looking at, if it is within activation range + /// Returns a pointer to the object the provided object is facing (if within the + /// specified distance). This will attempt to use the "Bip01 Head" node as a basis. + virtual MWWorld::Ptr getFacedObject(const MWWorld::Ptr &ptr, float distance); + virtual void deleteObject (const Ptr& ptr); virtual void moveObject (const Ptr& ptr, float x, float y, float z); @@ -421,6 +429,12 @@ namespace MWWorld /// Find position in interior cell near door entrance /// \return false if interior with given name not exists, true otherwise virtual bool findInteriorPosition(const std::string &name, ESM::Position &pos); + + /// Enables or disables use of teleport spell effects (recall, intervention, etc). + virtual void enableTeleporting(bool enable); + + /// Returns true if teleport spell effects are allowed. + virtual bool isTeleportingEnabled() const; }; } diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp index 2ecde7f60..1a4fc235b 100644 --- a/components/nif/recordptr.hpp +++ b/components/nif/recordptr.hpp @@ -71,16 +71,26 @@ public: } /// Look up the actual object from the index - X* getPtr() const + const X* getPtr() const { assert(ptr != NULL); return ptr; } - X& get() const + X* getPtr() + { + assert(ptr != NULL); + return ptr; + } + + const X& get() const + { return *getPtr(); } + X& get() { return *getPtr(); } /// Syntactic sugar - X* operator->() const + const X* operator->() const + { return getPtr(); } + X* operator->() { return getPtr(); } /// Pointers are allowed to be empty @@ -116,6 +126,8 @@ public: const Ptr& operator[](size_t index) const { return list.at(index); } + Ptr& operator[](size_t index) + { return list.at(index); } size_t length() const { return list.size(); } diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp index f9344caa4..3bb9ea230 100644 --- a/components/nifogre/ogrenifloader.cpp +++ b/components/nifogre/ogrenifloader.cpp @@ -688,6 +688,21 @@ class NIFObjectLoader } std::string::size_type nextpos = std::min(str.find('\r', pos), str.find('\n', pos)); + if(nextpos != std::string::npos) + { + do { + nextpos--; + } while(nextpos > pos && ::isspace(str[nextpos])); + nextpos++; + } + else if(::isspace(*str.rbegin())) + { + std::string::const_iterator last = str.end(); + do { + last--; + } while(last != str.begin() && ::isspace(*last)); + nextpos = std::distance(str.begin(), ++last); + } std::string result = str.substr(pos, nextpos-pos); textkeys.insert(std::make_pair(tk->list[i].time, Misc::StringUtils::toLower(result))); @@ -743,7 +758,7 @@ class NIFObjectLoader e = e->extra; } - if(!node->controller.empty()) + if(!node->controller.empty() && (node->parent || node->recType != Nif::RC_NiNode)) createNodeControllers(name, node->controller, objectlist, animflags); if(node->recType == Nif::RC_NiCamera) diff --git a/extern/sdl4ogre/CMakeLists.txt b/extern/sdl4ogre/CMakeLists.txt index 493d1ce47..5b31974e5 100644 --- a/extern/sdl4ogre/CMakeLists.txt +++ b/extern/sdl4ogre/CMakeLists.txt @@ -5,6 +5,7 @@ set(SDL4OGRE_LIBRARY "sdl4ogre") set(SDL4OGRE_SOURCE_FILES sdlinputwrapper.cpp sdlcursormanager.cpp + sdlwindowhelper.cpp ) set(SDL4OGRE_HEADER_FILES diff --git a/extern/sdl4ogre/events.h b/extern/sdl4ogre/events.h index 13f8b3101..e6e8434cb 100644 --- a/extern/sdl4ogre/events.h +++ b/extern/sdl4ogre/events.h @@ -65,10 +65,12 @@ public: virtual ~WindowListener() {} /** @remarks The window's visibility changed */ - virtual bool windowVisibilityChange( bool visible ) = 0; + virtual void windowVisibilityChange( bool visible ) {}; /** @remarks The window got / lost input focus */ - virtual bool windowFocusChange( bool have_focus ) = 0; + virtual void windowFocusChange( bool have_focus ) {} + + virtual void windowResized (int x, int y) {} }; } diff --git a/extern/sdl4ogre/sdlinputwrapper.cpp b/extern/sdl4ogre/sdlinputwrapper.cpp index 89dc65065..a30f10d6a 100644 --- a/extern/sdl4ogre/sdlinputwrapper.cpp +++ b/extern/sdl4ogre/sdlinputwrapper.cpp @@ -4,13 +4,6 @@ #include #include -/* -#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX -# include -# include -# include -#endif -*/ namespace SFO { @@ -19,7 +12,6 @@ namespace SFO InputWrapper::InputWrapper(SDL_Window* window, Ogre::RenderWindow* ogreWindow) : mSDLWindow(window), mOgreWindow(ogreWindow), - mOwnWindow(false), mWarpCompensate(false), mMouseRelative(false), mGrabPointer(false), @@ -27,14 +19,18 @@ namespace SFO mMouseZ(0), mMouseY(0), mMouseX(0), - mMouseInWindow(true) + mMouseInWindow(true), + mJoyListener(NULL), + mKeyboardListener(NULL), + mMouseListener(NULL), + mWindowListener(NULL) { _setupOISKeys(); } InputWrapper::~InputWrapper() { - if(mSDLWindow != NULL && mOwnWindow) + if(mSDLWindow != NULL) SDL_DestroyWindow(mSDLWindow); mSDLWindow = NULL; } @@ -76,6 +72,25 @@ namespace SFO case SDL_TEXTINPUT: mKeyboardListener->textInput(evt.text); break; + case SDL_JOYAXISMOTION: + if (mJoyListener) + mJoyListener->axisMoved(evt.jaxis, evt.jaxis.axis); + break; + case SDL_JOYBUTTONDOWN: + if (mJoyListener) + mJoyListener->buttonPressed(evt.jbutton, evt.jbutton.button); + break; + case SDL_JOYBUTTONUP: + if (mJoyListener) + mJoyListener->buttonReleased(evt.jbutton, evt.jbutton.button); + break; + case SDL_JOYDEVICEADDED: + //SDL_JoystickOpen(evt.jdevice.which); + //std::cout << "Detected a new joystick: " << SDL_JoystickNameForIndex(evt.jdevice.which) << std::endl; + break; + case SDL_JOYDEVICEREMOVED: + //std::cout << "A joystick has been removed" << std::endl; + break; case SDL_WINDOWEVENT: handleWindowEvent(evt); break; @@ -100,25 +115,62 @@ namespace SFO SDL_SetWindowGrab(mSDLWindow, SDL_FALSE); SDL_SetRelativeMouseMode(SDL_FALSE); break; - case SDL_WINDOWEVENT_RESIZED: + case SDL_WINDOWEVENT_SIZE_CHANGED: + int w,h; + SDL_GetWindowSize(mSDLWindow, &w, &h); + // TODO: Fix Ogre to handle this more consistently +#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 + mOgreWindow->windowMovedOrResized(); +#else + mOgreWindow->resize(w, h); +#endif + if (mWindowListener) + mWindowListener->windowResized(evt.window.data1, evt.window.data2); + + case SDL_WINDOWEVENT_RESIZED: + // TODO: Fix Ogre to handle this more consistently + #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 + mOgreWindow->windowMovedOrResized(); + #else + mOgreWindow->resize(evt.window.data1, evt.window.data2); + #endif + if (mWindowListener) + mWindowListener->windowResized(evt.window.data1, evt.window.data2); + break; + case SDL_WINDOWEVENT_FOCUS_GAINED: + if (mWindowListener) + mWindowListener->windowFocusChange(true); + break; case SDL_WINDOWEVENT_FOCUS_LOST: + if (mWindowListener) + mWindowListener->windowFocusChange(false); + break; case SDL_WINDOWEVENT_CLOSE: break; case SDL_WINDOWEVENT_SHOWN: mOgreWindow->setVisible(true); + if (mWindowListener) + mWindowListener->windowVisibilityChange(true); break; case SDL_WINDOWEVENT_HIDDEN: mOgreWindow->setVisible(false); + if (mWindowListener) + mWindowListener->windowVisibilityChange(false); break; } } - bool InputWrapper::isModifierHeld(int mod) + bool InputWrapper::isModifierHeld(SDL_Keymod mod) { return SDL_GetModState() & mod; } + bool InputWrapper::isKeyDown(SDL_Scancode key) + { + return SDL_GetKeyboardState(NULL)[key]; + } + /// \brief Moves the mouse to the specified point within the viewport void InputWrapper::warpMouse(int x, int y) { diff --git a/extern/sdl4ogre/sdlinputwrapper.hpp b/extern/sdl4ogre/sdlinputwrapper.hpp index 08b329925..66bf60c7c 100644 --- a/extern/sdl4ogre/sdlinputwrapper.hpp +++ b/extern/sdl4ogre/sdlinputwrapper.hpp @@ -22,9 +22,11 @@ namespace SFO void setMouseEventCallback(MouseListener* listen) { mMouseListener = listen; } void setKeyboardEventCallback(KeyListener* listen) { mKeyboardListener = listen; } void setWindowEventCallback(WindowListener* listen) { mWindowListener = listen; } + void setJoyEventCallback(JoyListener* listen) { mJoyListener = listen; } void capture(); - bool isModifierHeld(int mod); + bool isModifierHeld(SDL_Keymod mod); + bool isKeyDown(SDL_Scancode key); void setMouseRelative(bool relative); bool getMouseRelative() { return mMouseRelative; } @@ -47,6 +49,7 @@ namespace SFO SFO::MouseListener* mMouseListener; SFO::KeyListener* mKeyboardListener; SFO::WindowListener* mWindowListener; + SFO::JoyListener* mJoyListener; typedef boost::unordered_map KeyMap; KeyMap mKeyMap; @@ -66,7 +69,6 @@ namespace SFO SDL_Window* mSDLWindow; Ogre::RenderWindow* mOgreWindow; - bool mOwnWindow; }; } diff --git a/extern/sdl4ogre/sdlwindowhelper.cpp b/extern/sdl4ogre/sdlwindowhelper.cpp new file mode 100644 index 000000000..14371c947 --- /dev/null +++ b/extern/sdl4ogre/sdlwindowhelper.cpp @@ -0,0 +1,115 @@ +#include "sdlwindowhelper.hpp" + +#include +#include + +#include +#include + +namespace SFO +{ + +SDLWindowHelper::SDLWindowHelper (SDL_Window* window, int w, int h, + const std::string& title, bool fullscreen, Ogre::NameValuePairList params) + : mSDLWindow(window) +{ + //get the native whnd + struct SDL_SysWMinfo wmInfo; + SDL_VERSION(&wmInfo.version); + + if (SDL_GetWindowWMInfo(mSDLWindow, &wmInfo) == -1) + throw std::runtime_error("Couldn't get WM Info!"); + + Ogre::String winHandle; + + switch (wmInfo.subsystem) + { +#ifdef WIN32 + case SDL_SYSWM_WINDOWS: + // Windows code + winHandle = Ogre::StringConverter::toString((unsigned long)wmInfo.info.win.window); + break; +#elif __MACOSX__ + case SDL_SYSWM_COCOA: + //required to make OGRE play nice with our window + params.insert(std::make_pair("macAPI", "cocoa")); + params.insert(std::make_pair("macAPICocoaUseNSView", "true")); + + winHandle = Ogre::StringConverter::toString(WindowContentViewHandle(wmInfo)); + break; +#else + case SDL_SYSWM_X11: + winHandle = Ogre::StringConverter::toString((unsigned long)wmInfo.info.x11.window); + break; +#endif + default: + throw std::runtime_error("Unexpected WM!"); + break; + } + + /// \todo externalWindowHandle is deprecated according to the source code. Figure out a way to get parentWindowHandle + /// to work properly. On Linux/X11 it causes an occasional GLXBadDrawable error. + params.insert(std::make_pair("externalWindowHandle", winHandle)); + + mWindow = Ogre::Root::getSingleton().createRenderWindow(title, w, h, fullscreen, ¶ms); +} + +void SDLWindowHelper::setWindowIcon(const std::string &name) +{ + Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton().load(name, Ogre::ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME); + if (texture.isNull()) + { + std::stringstream error; + error << "Window icon not found: " << name; + throw std::runtime_error(error.str()); + } + Ogre::Image image; + texture->convertToImage(image); + + SDL_Surface* surface = SDL_CreateRGBSurface(0,texture->getWidth(),texture->getHeight(),32,0xFF000000,0x00FF0000,0x0000FF00,0x000000FF); + + //copy the Ogre texture to an SDL surface + for(size_t x = 0; x < texture->getWidth(); ++x) + { + for(size_t y = 0; y < texture->getHeight(); ++y) + { + Ogre::ColourValue clr = image.getColourAt(x, y, 0); + + //set the pixel on the SDL surface to the same value as the Ogre texture's + int bpp = surface->format->BytesPerPixel; + /* Here p is the address to the pixel we want to set */ + Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp; + Uint32 pixel = SDL_MapRGBA(surface->format, clr.r*255, clr.g*255, clr.b*255, clr.a*255); + switch(bpp) { + case 1: + *p = pixel; + break; + + case 2: + *(Uint16 *)p = pixel; + break; + + case 3: + if(SDL_BYTEORDER == SDL_BIG_ENDIAN) { + p[0] = (pixel >> 16) & 0xff; + p[1] = (pixel >> 8) & 0xff; + p[2] = pixel & 0xff; + } else { + p[0] = pixel & 0xff; + p[1] = (pixel >> 8) & 0xff; + p[2] = (pixel >> 16) & 0xff; + } + break; + + case 4: + *(Uint32 *)p = pixel; + break; + } + } + } + + SDL_SetWindowIcon(mSDLWindow, surface); + SDL_FreeSurface(surface); +} + +} diff --git a/extern/sdl4ogre/sdlwindowhelper.hpp b/extern/sdl4ogre/sdlwindowhelper.hpp new file mode 100644 index 000000000..834716b22 --- /dev/null +++ b/extern/sdl4ogre/sdlwindowhelper.hpp @@ -0,0 +1,31 @@ +#ifndef SDL4OGRE_SDLWINDOWHELPER_H +#define SDL4OGRE_SDLWINDOWHELPER_H + +#include + +namespace Ogre +{ + class RenderWindow; +} +struct SDL_Window; + +namespace SFO +{ + + /// @brief Creates an Ogre window from an SDL window and allows setting an Ogre texture as window icon + class SDLWindowHelper + { + public: + SDLWindowHelper (SDL_Window* window, int w, int h, const std::string& title, bool fullscreen, Ogre::NameValuePairList params); + void setWindowIcon(const std::string& name); + Ogre::RenderWindow* getWindow() { return mWindow; } + + private: + Ogre::RenderWindow* mWindow; + SDL_Window* mSDLWindow; + }; + +} + + +#endif diff --git a/files/mygui/openmw_hud.layout b/files/mygui/openmw_hud.layout index 67d0632f8..c69323fe5 100644 --- a/files/mygui/openmw_hud.layout +++ b/files/mygui/openmw_hud.layout @@ -3,6 +3,11 @@ + + + + + diff --git a/files/mygui/openmw_hud_energybar.skin.xml b/files/mygui/openmw_hud_energybar.skin.xml index 03d2835d6..9ec065e36 100644 --- a/files/mygui/openmw_hud_energybar.skin.xml +++ b/files/mygui/openmw_hud_energybar.skin.xml @@ -42,6 +42,11 @@ + + + + + @@ -69,4 +74,12 @@ + + + + + + + + diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index ab91aed78..a54f3086e 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -44,6 +44,12 @@ + + + + + + diff --git a/files/mygui/smallbars.png b/files/mygui/smallbars.png index f938412c2..3c007a55c 100644 Binary files a/files/mygui/smallbars.png and b/files/mygui/smallbars.png differ diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 613f12432..ac56604d1 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -163,3 +163,7 @@ ui sensitivity = 1.0 camera y multiplier = 1.0 ui y multiplier = 1.0 + +[Game] +# Always use the most powerful attack when striking with a weapon (chop, slash or thrust) +best attack = false diff --git a/libs/openengine/gui/manager.cpp b/libs/openengine/gui/manager.cpp index a0a4ab0ae..9fd57e2c1 100644 --- a/libs/openengine/gui/manager.cpp +++ b/libs/openengine/gui/manager.cpp @@ -612,6 +612,11 @@ void MyGUIManager::updateWindow (Ogre::RenderWindow *wnd) } } +void MyGUIManager::windowResized() +{ + mRenderManager->setActiveViewport(0); +} + void MyGUIManager::shutdown() { mGui->shutdown (); diff --git a/libs/openengine/gui/manager.hpp b/libs/openengine/gui/manager.hpp index 9535f2a24..cca70dfcf 100644 --- a/libs/openengine/gui/manager.hpp +++ b/libs/openengine/gui/manager.hpp @@ -43,6 +43,8 @@ namespace GUI void updateWindow (Ogre::RenderWindow* wnd); + void windowResized(); + void setup(Ogre::RenderWindow *wnd, Ogre::SceneManager *mgr, bool logging=false, const std::string& logDir = std::string("")); void shutdown(); diff --git a/libs/openengine/ogre/renderer.cpp b/libs/openengine/ogre/renderer.cpp index dbfdfaaa5..cda35b16c 100644 --- a/libs/openengine/ogre/renderer.cpp +++ b/libs/openengine/ogre/renderer.cpp @@ -3,7 +3,6 @@ #include "particles.hpp" #include -#include #include "OgreRoot.h" #include "OgreRenderWindow.h" @@ -19,6 +18,8 @@ #include +#include + #include #include #include @@ -54,9 +55,6 @@ void OgreRenderer::cleanup() delete mRoot; mRoot = NULL; - if (mWindowIconSurface) - SDL_FreeSurface(mWindowIconSurface); - // If we don't do this, the desktop resolution is not restored on exit SDL_SetWindowFullscreen(mSDLWindow, 0); @@ -237,25 +235,6 @@ void OgreRenderer::configure(const std::string &logPath, rs->setConfigOption ("RTT Preferred Mode", rttMode); } -void OgreRenderer::recreateWindow(const std::string &title, const WindowSettings &settings) -{ - Ogre::ColourValue viewportBG = mView->getBackgroundColour(); - - mRoot->destroyRenderTarget(mWindow); - NameValuePairList params; - params.insert(std::make_pair("title", title)); - params.insert(std::make_pair("FSAA", settings.fsaa)); - params.insert(std::make_pair("vsync", settings.vsync ? "true" : "false")); - - mWindow = mRoot->createRenderWindow(title, settings.window_x, settings.window_y, settings.fullscreen, ¶ms); - - // Create one viewport, entire window - mView = mWindow->addViewport(mCamera); - mView->setBackgroundColour(viewportBG); - - adjustViewport(); -} - void OgreRenderer::createWindow(const std::string &title, const WindowSettings& settings) { assert(mRoot); @@ -266,75 +245,32 @@ void OgreRenderer::createWindow(const std::string &title, const WindowSettings& params.insert(std::make_pair("FSAA", settings.fsaa)); params.insert(std::make_pair("vsync", settings.vsync ? "true" : "false")); - int pos_x = SDL_WINDOWPOS_UNDEFINED, - pos_y = SDL_WINDOWPOS_UNDEFINED; + int pos_x = SDL_WINDOWPOS_CENTERED_DISPLAY(settings.screen), + pos_y = SDL_WINDOWPOS_CENTERED_DISPLAY(settings.screen); if(settings.fullscreen) { - SDL_Rect display_bounds; - if(SDL_GetDisplayBounds(settings.screen, &display_bounds) != 0) - throw std::runtime_error("Couldn't get display bounds!"); - pos_x = display_bounds.x; - pos_y = display_bounds.y; + pos_x = SDL_WINDOWPOS_UNDEFINED_DISPLAY(settings.screen); + pos_y = SDL_WINDOWPOS_UNDEFINED_DISPLAY(settings.screen); } + // Create an application window with the following settings: mSDLWindow = SDL_CreateWindow( - "OpenMW", // window title - pos_x, // initial x position - pos_y, // initial y position - settings.window_x, // width, in pixels - settings.window_y, // height, in pixels + "OpenMW", // window title + pos_x, // initial x position + pos_y, // initial y position + settings.window_x, // width, in pixels + settings.window_y, // height, in pixels SDL_WINDOW_SHOWN - | (settings.fullscreen ? SDL_WINDOW_FULLSCREEN : 0) + | (settings.fullscreen ? SDL_WINDOW_FULLSCREEN : 0) | SDL_WINDOW_RESIZABLE ); - //get the native whnd - struct SDL_SysWMinfo wmInfo; - SDL_VERSION(&wmInfo.version); - - if(-1 == SDL_GetWindowWMInfo(mSDLWindow, &wmInfo)) - throw std::runtime_error("Couldn't get WM Info!"); - - Ogre::String winHandle; - - switch(wmInfo.subsystem) - { -#ifdef WIN32 - case SDL_SYSWM_WINDOWS: - // Windows code - winHandle = Ogre::StringConverter::toString((unsigned long)wmInfo.info.win.window); - break; -#elif __MACOSX__ - case SDL_SYSWM_COCOA: - //required to make OGRE play nice with our window - params.insert(std::make_pair("macAPI", "cocoa")); - params.insert(std::make_pair("macAPICocoaUseNSView", "true")); - - winHandle = Ogre::StringConverter::toString(WindowContentViewHandle(wmInfo)); - break; -#else - case SDL_SYSWM_X11: - winHandle = Ogre::StringConverter::toString((unsigned long)wmInfo.info.x11.window); - break; -#endif - default: - throw std::runtime_error("Unexpected WM!"); - break; - } - - /// \todo externalWindowHandle is deprecated according to the source code. Figure out a way to get parentWindowHandle - /// to work properly. On Linux/X11 it causes an occasional GLXBadDrawable error. - params.insert(std::make_pair("externalWindowHandle", winHandle)); - - mWindow = mRoot->createRenderWindow(title, settings.window_x, settings.window_y, settings.fullscreen, ¶ms); - - // Set the window icon + SFO::SDLWindowHelper helper(mSDLWindow, settings.window_x, settings.window_y, title, settings.fullscreen, params); if (settings.icon != "") - { - mWindowIconSurface = ogreTextureToSDLSurface(settings.icon); - SDL_SetWindowIcon(mSDLWindow, mWindowIconSurface); - } + helper.setWindowIcon(settings.icon); + mWindow = helper.getWindow(); + // create the semi-transparent black background texture used by the GUI. // has to be created in code with TU_DYNAMIC_WRITE_ONLY param @@ -380,73 +316,12 @@ void OgreRenderer::adjustViewport() } } -void OgreRenderer::setWindowEventListener(Ogre::WindowEventListener* listener) -{ - Ogre::WindowEventUtilities::addWindowEventListener(mWindow, listener); -} - -void OgreRenderer::removeWindowEventListener(Ogre::WindowEventListener* listener) -{ - Ogre::WindowEventUtilities::removeWindowEventListener(mWindow, listener); -} - void OgreRenderer::setFov(float fov) { mCamera->setFOVy(Degree(fov)); } -SDL_Surface* OgreRenderer::ogreTextureToSDLSurface(const std::string& name) +void OgreRenderer::windowResized(int x, int y) { - Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton().load(name, Ogre::ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME); - if (texture.isNull()) - { - std::stringstream error; - error << "Window icon not found: " << name; - throw std::runtime_error(error.str()); - } - Ogre::Image image; - texture->convertToImage(image); - - SDL_Surface* surface = SDL_CreateRGBSurface(0,texture->getWidth(),texture->getHeight(),32,0xFF000000,0x00FF0000,0x0000FF00,0x000000FF); - - //copy the Ogre texture to an SDL surface - for(size_t x = 0; x < texture->getWidth(); ++x) - { - for(size_t y = 0; y < texture->getHeight(); ++y) - { - Ogre::ColourValue clr = image.getColourAt(x, y, 0); - - //set the pixel on the SDL surface to the same value as the Ogre texture's - int bpp = surface->format->BytesPerPixel; - /* Here p is the address to the pixel we want to set */ - Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp; - Uint32 pixel = SDL_MapRGBA(surface->format, clr.r*255, clr.g*255, clr.b*255, clr.a*255); - switch(bpp) { - case 1: - *p = pixel; - break; - - case 2: - *(Uint16 *)p = pixel; - break; - - case 3: - if(SDL_BYTEORDER == SDL_BIG_ENDIAN) { - p[0] = (pixel >> 16) & 0xff; - p[1] = (pixel >> 8) & 0xff; - p[2] = pixel & 0xff; - } else { - p[0] = pixel & 0xff; - p[1] = (pixel >> 8) & 0xff; - p[2] = (pixel >> 16) & 0xff; - } - break; - - case 4: - *(Uint32 *)p = pixel; - break; - } - } - } - return surface; + mWindowListener->windowResized(x,y); } diff --git a/libs/openengine/ogre/renderer.hpp b/libs/openengine/ogre/renderer.hpp index f4b38c52d..9e83bf4f6 100644 --- a/libs/openengine/ogre/renderer.hpp +++ b/libs/openengine/ogre/renderer.hpp @@ -74,6 +74,12 @@ namespace OEngine class Fader; + class WindowSizeListener + { + public: + virtual void windowResized (int x, int y) = 0; + }; + class OgreRenderer { #if OGRE_PLATFORM == OGRE_PLATFORM_APPLE @@ -83,7 +89,6 @@ namespace OEngine #endif Ogre::RenderWindow *mWindow; SDL_Window *mSDLWindow; - SDL_Surface *mWindowIconSurface; Ogre::SceneManager *mScene; Ogre::Camera *mCamera; Ogre::Viewport *mView; @@ -107,7 +112,7 @@ namespace OEngine std::vector mAffectorFactories; bool logging; - SDL_Surface* ogreTextureToSDLSurface(const std::string& name); + WindowSizeListener* mWindowListener; public: OgreRenderer() @@ -117,7 +122,7 @@ namespace OEngine , mScene(NULL) , mCamera(NULL) , mView(NULL) - , mWindowIconSurface(NULL) + , mWindowListener(NULL) #ifdef ENABLE_PLUGIN_CgProgramManager , mCgPlugin(NULL) #endif @@ -140,9 +145,6 @@ namespace OEngine ~OgreRenderer() { cleanup(); } - void setWindowEventListener(Ogre::WindowEventListener* listener); - void removeWindowEventListener(Ogre::WindowEventListener* listener); - /** Configure the renderer. This will load configuration files and set up the Root and logging classes. */ void configure( @@ -154,8 +156,6 @@ namespace OEngine /// Create a window with the given title void createWindow(const std::string &title, const WindowSettings& settings); - void recreateWindow (const std::string &title, const WindowSettings& settings); - /// Set up the scene manager, camera and viewport void createScene(const std::string& camName="Camera",// Camera name float fov=55, // Field of view angle @@ -181,6 +181,8 @@ namespace OEngine float getFPS(); + void windowResized(int x, int y); + /// Get the Root Ogre::Root *getRoot() { return mRoot; } @@ -202,6 +204,8 @@ namespace OEngine /// Viewport Ogre::Viewport *getViewport() { return mView; } + void setWindowListener(WindowSizeListener* listener) { mWindowListener = listener; } + void adjustViewport(); }; }